From 405c0867352b0d146db04288af654cb62a902164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ky=C5=8Dchikut=C5=8D=20=7C=20=E3=82=AD=E3=83=A7=E3=82=A6?= =?UTF-8?q?=E3=83=81=E3=82=AF=E3=83=88=E3=82=A6?= Date: Fri, 24 Nov 2023 17:25:22 +0330 Subject: [PATCH 1/7] feat: add TLS fragmentation Introduce a feature to fragment TLS clientHello packets to circumvent TLS-based blockings (mostly seen in Iran's firewall) --- common/dialer/default.go | 30 +++++- common/dialer/default_go1.20.go | 8 +- common/dialer/default_nongo1.20.go | 5 +- common/dialer/extended_tcp.go | 55 +++++++++++ common/dialer/extended_tcp_stub.go | 36 +++++++ common/dialer/fragment.go | 154 +++++++++++++++++++++++++++++ common/dialer/tfo.go | 19 ---- common/dialer/tfo_stub.go | 20 ---- option/fragment.go | 38 +++++++ option/outbound.go | 29 +++--- 10 files changed, 331 insertions(+), 63 deletions(-) create mode 100644 common/dialer/extended_tcp.go create mode 100644 common/dialer/extended_tcp_stub.go create mode 100644 common/dialer/fragment.go delete mode 100644 common/dialer/tfo_stub.go create mode 100644 option/fragment.go diff --git a/common/dialer/default.go b/common/dialer/default.go index 9fbd1d8ef2..799c1f407b 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -98,11 +98,33 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi } setMultiPathTCP(&dialer4) } - tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen) + if options.TLSFragment.Enabled && options.TCPFastOpen { + return nil, E.New("TLS Fragmentation is not compatible with TCP Fast Open, set `tcp_fast_open` to `false` in your outbound if you intend to enable TLS fragmentation.") + } + var tlsFragment TLSFragment + if options.TLSFragment.Enabled { + tlsFragment.Enabled = true + + sleep, err := option.ParseIntRange(options.TLSFragment.Sleep) + if err != nil { + return nil, E.Cause(err, "invalid TLS fragment sleep period supplied") + } + tlsFragment.SleepMin = sleep[0] + tlsFragment.SleepMax = sleep[1] + + size, err := option.ParseIntRange(options.TLSFragment.Size) + if err != nil { + return nil, E.Cause(err, "invalid TLS fragment size supplied") + } + tlsFragment.SizeMin = size[0] + tlsFragment.SizeMax = size[1] + + } + tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen, tlsFragment) if err != nil { return nil, err } - tcpDialer6, err := newTCPDialer(dialer6, options.TCPFastOpen) + tcpDialer6, err := newTCPDialer(dialer6, options.TCPFastOpen, tlsFragment) if err != nil { return nil, err } @@ -130,9 +152,9 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address } } if !address.IsIPv6() { - return trackConn(DialSlowContext(&d.dialer4, ctx, network, address)) + return trackConn(d.dialer4.DialContext(ctx, network, address)) } else { - return trackConn(DialSlowContext(&d.dialer6, ctx, network, address)) + return trackConn(d.dialer6.DialContext(ctx, network, address)) } } diff --git a/common/dialer/default_go1.20.go b/common/dialer/default_go1.20.go index 8c3507c213..26e14f6f8e 100644 --- a/common/dialer/default_go1.20.go +++ b/common/dialer/default_go1.20.go @@ -4,12 +4,10 @@ package dialer import ( "net" - - "github.com/sagernet/tfo-go" ) -type tcpDialer = tfo.Dialer +type tcpDialer = ExtendedTCPDialer -func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) { - return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil +func newTCPDialer(dialer net.Dialer, tfoEnabled bool, tlsFragment TLSFragment) (tcpDialer, error) { + return tcpDialer{Dialer: dialer, DisableTFO: !tfoEnabled, TLSFragment: tlsFragment}, nil } diff --git a/common/dialer/default_nongo1.20.go b/common/dialer/default_nongo1.20.go index 215024245a..e9eb4195e2 100644 --- a/common/dialer/default_nongo1.20.go +++ b/common/dialer/default_nongo1.20.go @@ -10,9 +10,12 @@ import ( type tcpDialer = net.Dialer -func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) { +func newTCPDialer(dialer net.Dialer, tfoEnabled bool, tlsFragment TLSFragment) (tcpDialer, error) { if tfoEnabled { return dialer, E.New("TCP Fast Open requires go1.20, please recompile your binary.") } + if tlsFragment.Enabled { + return tcpDialer{Dialer: dialer, DisableTFO: true, TLSFragment: tlsFragment}, nil + } return dialer, nil } diff --git a/common/dialer/extended_tcp.go b/common/dialer/extended_tcp.go new file mode 100644 index 0000000000..65b7ef4e40 --- /dev/null +++ b/common/dialer/extended_tcp.go @@ -0,0 +1,55 @@ +//go:build go1.20 + +package dialer + +import ( + "context" + "net" + + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/tfo-go" +) + +// Custom TCP dialer with extra features such as "TCP Fast Open" or "TLS Fragmentation" +type ExtendedTCPDialer struct { + net.Dialer + DisableTFO bool + TLSFragment TLSFragment +} + +func (d *ExtendedTCPDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if (d.DisableTFO && !d.TLSFragment.Enabled) || N.NetworkName(network) != N.NetworkTCP { + switch N.NetworkName(network) { + case N.NetworkTCP, N.NetworkUDP: + return d.Dialer.DialContext(ctx, network, destination.String()) + default: + return d.Dialer.DialContext(ctx, network, destination.AddrString()) + } + } + // Create a TLS-Fragmented dialer + if d.TLSFragment.Enabled { + fragmentConn := &fragmentConn{ + dialer: &d.Dialer, + fragment: d.TLSFragment, + network: network, + destination: destination, + } + conn, err := d.Dialer.DialContext(ctx, network, destination.String()) + if err != nil { + fragmentConn.err = err + return nil, err + } + fragmentConn.conn = conn + return fragmentConn, nil + } + // Create a TFO dialer + return &slowOpenConn{ + dialer: &tfo.Dialer{Dialer: d.Dialer, DisableTFO: d.DisableTFO}, + ctx: ctx, + network: network, + destination: destination, + create: make(chan struct{}), + }, + nil +} diff --git a/common/dialer/extended_tcp_stub.go b/common/dialer/extended_tcp_stub.go new file mode 100644 index 0000000000..5055c37300 --- /dev/null +++ b/common/dialer/extended_tcp_stub.go @@ -0,0 +1,36 @@ +//go:build !go1.20 + +package dialer + +import ( + "context" + "net" + + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func (d *ExtendedTCPDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if !d.TLSFragment.Enabled || N.NetworkName(network) != N.NetworkTCP { + switch N.NetworkName(network) { + case N.NetworkTCP, N.NetworkUDP: + return d.Dialer.DialContext(ctx, network, destination.String()) + default: + return d.Dialer.DialContext(ctx, network, destination.AddrString()) + } + } + // Create a TLS-Fragmented dialer + fragmentConn := &fragmentConn{ + dialer: &d.Dialer, + fragment: d.TLSFragment, + network: network, + destination: destination, + } + conn, err := d.Dialer.DialContext(ctx, network, destination.String()) + if err != nil { + fragmentConn.err = err + return nil, err + } + fragmentConn.conn = conn + return fragmentConn, nil +} diff --git a/common/dialer/fragment.go b/common/dialer/fragment.go new file mode 100644 index 0000000000..b087e0da9a --- /dev/null +++ b/common/dialer/fragment.go @@ -0,0 +1,154 @@ +package dialer + +import ( + "crypto/rand" + "encoding/binary" + "io" + "math/big" + "net" + "os" + "time" + + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio" + M "github.com/sagernet/sing/common/metadata" +) + +type TLSFragment struct { + Enabled bool + SizeMin uint64 + SizeMax uint64 + SleepMin uint64 + SleepMax uint64 +} + +type fragmentConn struct { + dialer *net.Dialer + fragment TLSFragment + network string + destination M.Socksaddr + conn net.Conn + err error +} + +func (c *fragmentConn) Read(b []byte) (n int, err error) { + if c.conn == nil { + return 0, c.err + } + return c.conn.Read(b) +} + +func randBetween(left int64, right int64) int64 { + if left == right { + return left + } + bigInt, _ := rand.Int(rand.Reader, big.NewInt(right-left)) + return left + bigInt.Int64() +} + +func (c *fragmentConn) Write(b []byte) (n int, err error) { + if c.conn == nil { + return 0, c.err + } + // Check if payload is a valid TLS clientHello packet + if len(b) >= 5 && b[0] == 22 { + clientHelloLen := int(binary.BigEndian.Uint16(b[3:5])) + clientHelloData := b[5:] + + for i := 0; i < clientHelloLen; { + fragmentEnd := i + int(randBetween(int64(c.fragment.SizeMin), int64(c.fragment.SizeMax))) + if fragmentEnd > clientHelloLen { + fragmentEnd = clientHelloLen + } + + fragment := clientHelloData[i:fragmentEnd] + i = fragmentEnd + + header := make([]byte, 5) + header[0] = b[0] + binary.BigEndian.PutUint16(header[1:], binary.BigEndian.Uint16(b[1:3])) + binary.BigEndian.PutUint16(header[3:], uint16(len(fragment))) + payload := append(header, fragment...) + + _, err := c.conn.Write(payload) + if err != nil { + c.err = err + return 0, c.err + } + + randomInterval := randBetween(int64(c.fragment.SleepMin), int64(c.fragment.SleepMax)) + time.Sleep(time.Duration(randomInterval)) + } + + return len(b), nil + } + + // Write directly if not a clientHello packet + return c.conn.Write(b) +} + +func (c *fragmentConn) Close() error { + return common.Close(c.conn) +} + +func (c *fragmentConn) LocalAddr() net.Addr { + if c.conn == nil { + return M.Socksaddr{} + } + return c.conn.LocalAddr() +} + +func (c *fragmentConn) RemoteAddr() net.Addr { + if c.conn == nil { + return M.Socksaddr{} + } + return c.conn.RemoteAddr() +} + +func (c *fragmentConn) SetDeadline(t time.Time) error { + if c.conn == nil { + return os.ErrInvalid + } + return c.conn.SetDeadline(t) +} + +func (c *fragmentConn) SetReadDeadline(t time.Time) error { + if c.conn == nil { + return os.ErrInvalid + } + return c.conn.SetReadDeadline(t) +} + +func (c *fragmentConn) SetWriteDeadline(t time.Time) error { + if c.conn == nil { + return os.ErrInvalid + } + return c.conn.SetWriteDeadline(t) +} + +func (c *fragmentConn) Upstream() any { + return c.conn +} + +func (c *fragmentConn) ReaderReplaceable() bool { + return c.conn != nil +} + +func (c *fragmentConn) WriterReplaceable() bool { + return c.conn != nil +} + +func (c *fragmentConn) LazyHeadroom() bool { + return c.conn == nil +} + +func (c *fragmentConn) NeedHandshake() bool { + return c.conn == nil +} + +func (c *fragmentConn) WriteTo(w io.Writer) (n int64, err error) { + if c.conn == nil { + return 0, c.err + } + return bufio.Copy(w, c.conn) +} diff --git a/common/dialer/tfo.go b/common/dialer/tfo.go index 0b0c9fccc3..ae446e5bb2 100644 --- a/common/dialer/tfo.go +++ b/common/dialer/tfo.go @@ -14,7 +14,6 @@ import ( "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/tfo-go" ) @@ -29,24 +28,6 @@ type slowOpenConn struct { err error } -func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP { - switch N.NetworkName(network) { - case N.NetworkTCP, N.NetworkUDP: - return dialer.Dialer.DialContext(ctx, network, destination.String()) - default: - return dialer.Dialer.DialContext(ctx, network, destination.AddrString()) - } - } - return &slowOpenConn{ - dialer: dialer, - ctx: ctx, - network: network, - destination: destination, - create: make(chan struct{}), - }, nil -} - func (c *slowOpenConn) Read(b []byte) (n int, err error) { if c.conn == nil { select { diff --git a/common/dialer/tfo_stub.go b/common/dialer/tfo_stub.go deleted file mode 100644 index 144902e5be..0000000000 --- a/common/dialer/tfo_stub.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build !go1.20 - -package dialer - -import ( - "context" - "net" - - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - switch N.NetworkName(network) { - case N.NetworkTCP, N.NetworkUDP: - return dialer.DialContext(ctx, network, destination.String()) - default: - return dialer.DialContext(ctx, network, destination.AddrString()) - } -} diff --git a/option/fragment.go b/option/fragment.go new file mode 100644 index 0000000000..c25225b0a6 --- /dev/null +++ b/option/fragment.go @@ -0,0 +1,38 @@ +package option + +import ( + "strconv" + "strings" + + E "github.com/sagernet/sing/common/exceptions" +) + +type TLSFragmentOptions struct { + Enabled bool `json:"enabled,omitempty"` + Size string `json:"size,omitempty"` // Fragment size in Bytes + Sleep string `json:"sleep,omitempty"` // Time to sleep between sending the fragments in milliseconds +} + +func ParseIntRange(str string) ([]uint64, error) { + var err error + result := make([]uint64, 2) + + splitString := strings.Split(str, "-") + if len(splitString) == 2 { + result[0], err = strconv.ParseUint(splitString[0], 10, 64) + if err != nil { + return nil, E.Cause(err, "Error parsing string to integer") + } + result[1], err = strconv.ParseUint(splitString[1], 10, 64) + if err != nil { + return nil, E.Cause(err, "Error parsing string to integer") + } + } else { + result[0], err = strconv.ParseUint(splitString[0], 10, 64) + if err != nil { + return nil, E.Cause(err, "Error parsing string to integer") + } + result[1] = result[0] + } + return result, err +} diff --git a/option/outbound.go b/option/outbound.go index 2985319ea5..36ddd11ab5 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -130,20 +130,21 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error { } type DialerOptions struct { - Detour string `json:"detour,omitempty"` - BindInterface string `json:"bind_interface,omitempty"` - Inet4BindAddress *ListenAddress `json:"inet4_bind_address,omitempty"` - Inet6BindAddress *ListenAddress `json:"inet6_bind_address,omitempty"` - ProtectPath string `json:"protect_path,omitempty"` - RoutingMark int `json:"routing_mark,omitempty"` - ReuseAddr bool `json:"reuse_addr,omitempty"` - ConnectTimeout Duration `json:"connect_timeout,omitempty"` - TCPFastOpen bool `json:"tcp_fast_open,omitempty"` - TCPMultiPath bool `json:"tcp_multi_path,omitempty"` - UDPFragment *bool `json:"udp_fragment,omitempty"` - UDPFragmentDefault bool `json:"-"` - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` - FallbackDelay Duration `json:"fallback_delay,omitempty"` + Detour string `json:"detour,omitempty"` + BindInterface string `json:"bind_interface,omitempty"` + Inet4BindAddress *ListenAddress `json:"inet4_bind_address,omitempty"` + Inet6BindAddress *ListenAddress `json:"inet6_bind_address,omitempty"` + ProtectPath string `json:"protect_path,omitempty"` + RoutingMark int `json:"routing_mark,omitempty"` + ReuseAddr bool `json:"reuse_addr,omitempty"` + ConnectTimeout Duration `json:"connect_timeout,omitempty"` + TCPFastOpen bool `json:"tcp_fast_open,omitempty"` + TCPMultiPath bool `json:"tcp_multi_path,omitempty"` + TLSFragment TLSFragmentOptions `json:"tls_fragment,omitempty"` + UDPFragment *bool `json:"udp_fragment,omitempty"` + UDPFragmentDefault bool `json:"-"` + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` + FallbackDelay Duration `json:"fallback_delay,omitempty"` } type ServerOptions struct { From 806a469519c67eddf6cf9ae733c16275e400af6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ky=C5=8Dchikut=C5=8D=20=7C=20=E3=82=AD=E3=83=A7=E3=82=A6?= =?UTF-8?q?=E3=83=81=E3=82=AF=E3=83=88=E3=82=A6?= Date: Fri, 24 Nov 2023 17:36:46 +0330 Subject: [PATCH 2/7] docs: update documentation to reflect the new TLS fragments settings --- docs/configuration/shared/dial.md | 9 +++++++-- docs/configuration/shared/tls-fragment.md | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 docs/configuration/shared/tls-fragment.md diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index 8139c7518c..927fa13ad9 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -11,6 +11,7 @@ "connect_timeout": "5s", "tcp_fast_open": false, "tcp_multi_path": false, + "tls_fragment": {}, "udp_fragment": false, "domain_strategy": "prefer_ipv6", "fallback_delay": "300ms" @@ -63,6 +64,10 @@ Enable TCP Fast Open. Enable TCP Multi Path. +#### tls_fragment + +Enable TLS fragmentation, see [TLS Fragment](/configuration/shared/tls-fragment) for details. + #### udp_fragment Enable UDP fragmentation. @@ -84,7 +89,7 @@ If set, the requested domain name will be resolved to IP before connect. | Outbound | Effected domains | Fallback Value | |----------|--------------------------|-------------------------------------------| -| `direct` | Domain in request | Take `inbound.domain_strategy` if not set | +| `direct` | Domain in request | Take `inbound.domain_strategy` if not set | | others | Domain in server address | / | #### fallback_delay @@ -94,4 +99,4 @@ That is, is the amount of time to wait for connection to succeed before assuming that IPv4/IPv6 is misconfigured and falling back to other type of addresses. If zero, a default delay of 300ms is used. -Only take effect when `domain_strategy` is set. \ No newline at end of file +Only take effect when `domain_strategy` is set. diff --git a/docs/configuration/shared/tls-fragment.md b/docs/configuration/shared/tls-fragment.md new file mode 100644 index 0000000000..5bd2042ebd --- /dev/null +++ b/docs/configuration/shared/tls-fragment.md @@ -0,0 +1,23 @@ +### Structure + +```json +{ + "enabled": false, + "size": "100-200", + "sleep": "10-20", +} +``` + +### Fields + +#### enabled + +Enable TLS clientHello packet fragmentation. + +#### size + +Random range of fragmented packets' size in bytes. + +#### sleep + +Random range of sleeping in between sending fragments. From 6b3e2330d4ee62d8ddf4c401b7aee7201a4f1472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ky=C5=8Dchikut=C5=8D=20=7C=20=E3=82=AD=E3=83=A7=E3=82=A6?= =?UTF-8?q?=E3=83=81=E3=82=AF=E3=83=88=E3=82=A6?= Date: Mon, 11 Dec 2023 19:13:15 +0330 Subject: [PATCH 3/7] fix: check for invalid input ranges --- option/fragment.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/option/fragment.go b/option/fragment.go index c25225b0a6..662b3e6564 100644 --- a/option/fragment.go +++ b/option/fragment.go @@ -1,6 +1,7 @@ package option import ( + "fmt" "strconv" "strings" @@ -21,16 +22,20 @@ func ParseIntRange(str string) ([]uint64, error) { if len(splitString) == 2 { result[0], err = strconv.ParseUint(splitString[0], 10, 64) if err != nil { - return nil, E.Cause(err, "Error parsing string to integer") + return nil, E.Cause(err, "error parsing string to integer") } result[1], err = strconv.ParseUint(splitString[1], 10, 64) if err != nil { - return nil, E.Cause(err, "Error parsing string to integer") + return nil, E.Cause(err, "error parsing string to integer") + } + + if result[1] < result[0] { + return nil, E.Cause(E.New(fmt.Sprintf("upper bound value (%d) must be greater than or equal to lower bound value (%d)", result[1], result[0])), "invalid range") } } else { result[0], err = strconv.ParseUint(splitString[0], 10, 64) if err != nil { - return nil, E.Cause(err, "Error parsing string to integer") + return nil, E.Cause(err, "error parsing string to integer") } result[1] = result[0] } From c8682840de9dd695345c59cee8468a174e41de71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ky=C5=8Dchikut=C5=8D=20=7C=20=E3=82=AD=E3=83=A7=E3=82=A6?= =?UTF-8?q?=E3=83=81=E3=82=AF=E3=83=88=E3=82=A6?= Date: Mon, 11 Dec 2023 19:15:20 +0330 Subject: [PATCH 4/7] perf: remove unnecessary pointer pass to fragmentConn --- common/dialer/extended_tcp.go | 2 +- common/dialer/fragment.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/dialer/extended_tcp.go b/common/dialer/extended_tcp.go index 65b7ef4e40..9cdb4e40c9 100644 --- a/common/dialer/extended_tcp.go +++ b/common/dialer/extended_tcp.go @@ -30,7 +30,7 @@ func (d *ExtendedTCPDialer) DialContext(ctx context.Context, network string, des // Create a TLS-Fragmented dialer if d.TLSFragment.Enabled { fragmentConn := &fragmentConn{ - dialer: &d.Dialer, + dialer: d.Dialer, fragment: d.TLSFragment, network: network, destination: destination, diff --git a/common/dialer/fragment.go b/common/dialer/fragment.go index b087e0da9a..f9e6772720 100644 --- a/common/dialer/fragment.go +++ b/common/dialer/fragment.go @@ -23,7 +23,7 @@ type TLSFragment struct { } type fragmentConn struct { - dialer *net.Dialer + dialer net.Dialer fragment TLSFragment network string destination M.Socksaddr From a2875ab768a03e5b6d7b44db947e25fa653ddac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ky=C5=8Dchikut=C5=8D=20=7C=20=E3=82=AD=E3=83=A7=E3=82=A6?= =?UTF-8?q?=E3=83=81=E3=82=AF=E3=83=88=E3=82=A6?= Date: Mon, 11 Dec 2023 19:16:57 +0330 Subject: [PATCH 5/7] perf: make sleeps optional This should reduce latency and improve resource usage. --- common/dialer/fragment.go | 64 ++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/common/dialer/fragment.go b/common/dialer/fragment.go index f9e6772720..5f47247d11 100644 --- a/common/dialer/fragment.go +++ b/common/dialer/fragment.go @@ -3,6 +3,7 @@ package dialer import ( "crypto/rand" "encoding/binary" + "fmt" "io" "math/big" "net" @@ -50,41 +51,42 @@ func (c *fragmentConn) Write(b []byte) (n int, err error) { if c.conn == nil { return 0, c.err } - // Check if payload is a valid TLS clientHello packet - if len(b) >= 5 && b[0] == 22 { - clientHelloLen := int(binary.BigEndian.Uint16(b[3:5])) - clientHelloData := b[5:] - - for i := 0; i < clientHelloLen; { - fragmentEnd := i + int(randBetween(int64(c.fragment.SizeMin), int64(c.fragment.SizeMax))) - if fragmentEnd > clientHelloLen { - fragmentEnd = clientHelloLen - } - - fragment := clientHelloData[i:fragmentEnd] - i = fragmentEnd - - header := make([]byte, 5) - header[0] = b[0] - binary.BigEndian.PutUint16(header[1:], binary.BigEndian.Uint16(b[1:3])) - binary.BigEndian.PutUint16(header[3:], uint16(len(fragment))) - payload := append(header, fragment...) - - _, err := c.conn.Write(payload) - if err != nil { - c.err = err - return 0, c.err - } - - randomInterval := randBetween(int64(c.fragment.SleepMin), int64(c.fragment.SleepMax)) - time.Sleep(time.Duration(randomInterval)) + // Do not fragment if it's not a TLS clientHello packet + if len(b) < 5 || b[0] != 22 { + fmt.Println("not a TLS clientHello, writing n bytes:", len(b)) + return c.conn.Write(b) + } + + fmt.Println("received a TLS clientHello, writing n bytes:", len(b)) + clientHelloLen := int(binary.BigEndian.Uint16(b[3:5])) + clientHelloData := b[5:] + + for fragmentStart := 0; fragmentStart < clientHelloLen; { + fragmentEnd := fragmentStart + int(randBetween(int64(c.fragment.SizeMin), int64(c.fragment.SizeMax))) + if fragmentEnd > clientHelloLen { + fragmentEnd = clientHelloLen + } + + header := make([]byte, 5) + header[0] = b[0] + binary.BigEndian.PutUint16(header[1:], binary.BigEndian.Uint16(b[1:3])) + binary.BigEndian.PutUint16(header[3:], uint16(fragmentEnd-fragmentStart)) + payload := append(header, clientHelloData[fragmentStart:fragmentEnd]...) + + _, err := c.conn.Write(payload) + if err != nil { + c.err = err + return 0, c.err + } + + if c.fragment.SleepMax != 0 { + time.Sleep(time.Duration(randBetween(int64(c.fragment.SleepMin), int64(c.fragment.SleepMax))) * time.Millisecond) } - return len(b), nil + fragmentStart = fragmentEnd } - // Write directly if not a clientHello packet - return c.conn.Write(b) + return len(b), nil } func (c *fragmentConn) Close() error { From 0c011faafe428051441917c6e53f35237edf7664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ky=C5=8Dchikut=C5=8D=20=7C=20=E3=82=AD=E3=83=A7=E3=82=A6?= =?UTF-8?q?=E3=83=81=E3=82=AF=E3=83=88=E3=82=A6?= Date: Wed, 13 Dec 2023 01:36:51 +0330 Subject: [PATCH 6/7] style: remove printlns --- common/dialer/fragment.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/common/dialer/fragment.go b/common/dialer/fragment.go index 5f47247d11..0043ee6f2c 100644 --- a/common/dialer/fragment.go +++ b/common/dialer/fragment.go @@ -3,7 +3,6 @@ package dialer import ( "crypto/rand" "encoding/binary" - "fmt" "io" "math/big" "net" @@ -53,11 +52,9 @@ func (c *fragmentConn) Write(b []byte) (n int, err error) { } // Do not fragment if it's not a TLS clientHello packet if len(b) < 5 || b[0] != 22 { - fmt.Println("not a TLS clientHello, writing n bytes:", len(b)) return c.conn.Write(b) } - fmt.Println("received a TLS clientHello, writing n bytes:", len(b)) clientHelloLen := int(binary.BigEndian.Uint16(b[3:5])) clientHelloData := b[5:] From 4d5973ad7064e0daf45a8baac5b793289b316f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ky=C5=8Dchikut=C5=8D=20=7C=20=E3=82=AD=E3=83=A7=E3=82=A6?= =?UTF-8?q?=E3=83=81=E3=82=AF=E3=83=88=E3=82=A6?= Date: Sat, 23 Dec 2023 19:22:30 +0330 Subject: [PATCH 7/7] fix: remove pointer for the tls dialer in case of older go builds --- common/dialer/extended_tcp_stub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/dialer/extended_tcp_stub.go b/common/dialer/extended_tcp_stub.go index 5055c37300..44770e2d48 100644 --- a/common/dialer/extended_tcp_stub.go +++ b/common/dialer/extended_tcp_stub.go @@ -21,7 +21,7 @@ func (d *ExtendedTCPDialer) DialContext(ctx context.Context, network string, des } // Create a TLS-Fragmented dialer fragmentConn := &fragmentConn{ - dialer: &d.Dialer, + dialer: d.Dialer, fragment: d.TLSFragment, network: network, destination: destination,