Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve TLS Fragmentation #12

Merged
merged 8 commits into from Jan 5, 2024
Merged
2 changes: 1 addition & 1 deletion common/dialer/extended_tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion common/dialer/extended_tcp_stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
N "github.com/sagernet/sing/common/network"
)

func (d *ExtendedTCPDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {

Check failure on line 13 in common/dialer/extended_tcp_stub.go

View workflow job for this annotation

GitHub Actions / Debug build (Go 1.18)

undefined: ExtendedTCPDialer
if !d.TLSFragment.Enabled || N.NetworkName(network) != N.NetworkTCP {
switch N.NetworkName(network) {
case N.NetworkTCP, N.NetworkUDP:
Expand All @@ -21,7 +21,7 @@
}
// Create a TLS-Fragmented dialer
fragmentConn := &fragmentConn{
dialer: &d.Dialer,
dialer: d.Dialer,
fragment: d.TLSFragment,
network: network,
destination: destination,
Expand Down
63 changes: 31 additions & 32 deletions common/dialer/fragment.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type TLSFragment struct {
}

type fragmentConn struct {
dialer *net.Dialer
dialer net.Dialer
fragment TLSFragment
network string
destination M.Socksaddr
Expand All @@ -50,41 +50,40 @@ 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 {
return c.conn.Write(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 {
Expand Down
11 changes: 8 additions & 3 deletions option/fragment.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package option

import (
"fmt"
"strconv"
"strings"

Expand All @@ -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]
}
Expand Down
Loading