diff --git a/go.mod b/go.mod index 9588881..0b76e2c 100644 --- a/go.mod +++ b/go.mod @@ -9,3 +9,10 @@ require ( golang.org/x/crypto v0.39.0 golang.org/x/sys v0.33.0 ) + +require ( + github.com/andybalholm/brotli v1.0.6 // indirect + github.com/cloudflare/circl v1.5.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect +) diff --git a/go.sum b/go.sum index ea33dbf..9d8067a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,16 @@ +github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= +github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI= github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/refraction-networking/utls v1.7.3 h1:L0WRhHY7Oq1T0zkdzVZMR6zWZv+sXbHB9zcuvsAEqCo= @@ -8,3 +19,5 @@ golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/record_detect.go b/record_detect.go index 2e0fc2b..c7962ed 100644 --- a/record_detect.go +++ b/record_detect.go @@ -14,18 +14,20 @@ import ( var GlobalPostHandshakeRecordsLock sync.Mutex -var GlobalPostHandshakeRecordsLens map[*Config]map[string][]int +var GlobalPostHandshakeRecordsLens map[*Config]map[string][]TrafficPacket -func DetectPostHandshakeRecordsLens(config *Config) map[string][]int { +func DetectPostHandshakeRecordsLens(config *Config) map[string][]TrafficPacket { GlobalPostHandshakeRecordsLock.Lock() defer GlobalPostHandshakeRecordsLock.Unlock() if GlobalPostHandshakeRecordsLens == nil { - GlobalPostHandshakeRecordsLens = make(map[*Config]map[string][]int) + GlobalPostHandshakeRecordsLens = make(map[*Config]map[string][]TrafficPacket) } if GlobalPostHandshakeRecordsLens[config] == nil { - GlobalPostHandshakeRecordsLens[config] = make(map[string][]int) + GlobalPostHandshakeRecordsLens[config] = make(map[string][]TrafficPacket) for sni := range config.ServerNames { + var tcpStart = time.Now() target, err := net.Dial("tcp", config.Dest) + var tcpDone = time.Now() if err != nil { continue } @@ -38,24 +40,39 @@ func DetectPostHandshakeRecordsLens(config *Config) map[string][]int { Conn: target, PostHandshakeRecordsLens: GlobalPostHandshakeRecordsLens[config], Sni: sni, + TcpStart: tcpStart, + TcpDone: tcpDone, } uConn := utls.UClient(detectConn, &utls.Config{ ServerName: sni, }, utls.HelloChrome_Auto) + detectConn.HandshakeStart = time.Now() if err = uConn.Handshake(); err != nil { continue } + detectConn.HandshakeDone = time.Now() io.Copy(io.Discard, uConn) } } return GlobalPostHandshakeRecordsLens[config] } +type TrafficPacket struct { + Direction bool + Lens []int + SinceHandshake time.Duration +} + type DetectConn struct { net.Conn - PostHandshakeRecordsLens map[string][]int + PostHandshakeRecordsLens map[string][]TrafficPacket Sni string CcsSent bool + + TcpStart time.Time + TcpDone time.Time + HandshakeStart time.Time + HandshakeDone time.Time } func (c *DetectConn) Write(b []byte) (n int, err error) { @@ -70,15 +87,24 @@ func (c *DetectConn) Read(b []byte) (n int, err error) { return c.Conn.Read(b) } c.Conn.SetReadDeadline(time.Now().Add(5 * time.Second)) - data, _ := io.ReadAll(c.Conn) for { - if len(data) >= 5 && bytes.Equal(data[:3], []byte{23, 3, 3}) { - length := int(binary.BigEndian.Uint16(data[3:5])) + 5 - c.PostHandshakeRecordsLens[c.Sni] = append(c.PostHandshakeRecordsLens[c.Sni], length) - data = data[length:] - } else { - break + data := make([]byte, 0, 2048) + n, err = c.Conn.Read(data[len(data):cap(data)]) + if err != nil { + return 0, err + } + newPacket := TrafficPacket { + SinceHandshake: time.Since(c.HandshakeDone), + } + c.PostHandshakeRecordsLens[c.Sni] = append(c.PostHandshakeRecordsLens[c.Sni], newPacket) + for len(data) > 0 { + if len(data) >= 5 && bytes.Equal(data[:3], []byte{23, 3, 3}) { + length := int(binary.BigEndian.Uint16(data[3:5])) + 5 + newPacket.Lens = append(newPacket.Lens, length) + data = data[length:] + } else { + break + } } } - return 0, io.EOF } diff --git a/tls.go b/tls.go index 22d4960..ac86734 100644 --- a/tls.go +++ b/tls.go @@ -374,18 +374,20 @@ func Server(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) { if err != nil { break } - for _, length := range postHandshakeRecordsLens[hs.clientHello.serverName] { - plainText := make([]byte, length-16) - plainText[0] = 23 - plainText[1] = 3 - plainText[2] = 3 - plainText[3] = byte((length - 5) >> 8) - plainText[4] = byte((length - 5)) - plainText[5] = 23 - postHandshakeRecord := hs.c.out.cipher.(aead).Seal(plainText[:5], hs.c.out.seq[:], plainText[5:], plainText[:5]) - hs.c.out.incSeq() - hs.c.write(postHandshakeRecord) - fmt.Printf("REALITY remoteAddr: %v\tlen(postHandshakeRecord): %v\n", remoteAddr, len(postHandshakeRecord)) + for _, packet := range postHandshakeRecordsLens[hs.clientHello.serverName] { + for _, length := range packet.Lens { + plainText := make([]byte, length-16) + plainText[0] = 23 + plainText[1] = 3 + plainText[2] = 3 + plainText[3] = byte((length - 5) >> 8) + plainText[4] = byte((length - 5)) + plainText[5] = 23 + postHandshakeRecord := hs.c.out.cipher.(aead).Seal(plainText[:5], hs.c.out.seq[:], plainText[5:], plainText[:5]) + hs.c.out.incSeq() + hs.c.write(postHandshakeRecord) + fmt.Printf("REALITY remoteAddr: %v\tlen(postHandshakeRecord): %v\n", remoteAddr, len(postHandshakeRecord)) + } } hs.c.isHandshakeComplete.Store(true) break