mirror of
https://github.com/XTLS/REALITY.git
synced 2026-09-22 23:08:01 +03:00
Add maxUselessRecords (ChangeCipherSpec, etc.) detection (#29)
https://github.com/XTLS/BBS/issues/16 https://github.com/XTLS/BBS/issues/19
This commit is contained in:
@@ -67,7 +67,7 @@ const (
|
||||
recordHeaderLen = 5 // record header length
|
||||
maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB)
|
||||
maxHandshakeCertificateMsg = 262144 // maximum certificate message size (256 KiB)
|
||||
maxUselessRecords = 16 // maximum number of consecutive non-advancing records
|
||||
maxUselessRecords = 32 // maximum number of consecutive non-advancing records
|
||||
)
|
||||
|
||||
// TLS record types.
|
||||
|
||||
@@ -25,16 +25,17 @@ import (
|
||||
// A Conn represents a secured connection.
|
||||
// It implements the net.Conn interface.
|
||||
type Conn struct {
|
||||
AuthKey []byte
|
||||
ClientVer [3]byte
|
||||
ClientTime time.Time
|
||||
ClientShortId [8]byte
|
||||
AuthKey []byte
|
||||
ClientVer [3]byte
|
||||
ClientTime time.Time
|
||||
ClientShortId [8]byte
|
||||
MaxUselessRecords int
|
||||
|
||||
// constant
|
||||
conn net.Conn
|
||||
isClient bool
|
||||
handshakeFn func(context.Context) error // (*Conn).clientHandshake or serverHandshake
|
||||
quic *quicState // nil for non-QUIC connections
|
||||
quic *quicState // nil for non-QUIC connections
|
||||
|
||||
// isHandshakeComplete is true if the connection is currently transferring
|
||||
// application data (i.e. is not currently processing a handshake).
|
||||
@@ -827,7 +828,10 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error {
|
||||
// a warning alert, empty application_data, or a change_cipher_spec in TLS 1.3.
|
||||
func (c *Conn) retryReadRecord(expectChangeCipherSpec bool) error {
|
||||
c.retryCount++
|
||||
if c.retryCount > maxUselessRecords {
|
||||
if c.MaxUselessRecords <= 0 {
|
||||
c.MaxUselessRecords = maxUselessRecords
|
||||
}
|
||||
if c.retryCount > c.MaxUselessRecords {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return c.in.setErrorLocked(errors.New("tls: too many ignored records"))
|
||||
}
|
||||
@@ -1248,7 +1252,7 @@ func (c *Conn) unmarshalHandshakeMessage(data []byte, transcript transcriptHash)
|
||||
if transcript != nil {
|
||||
transcript.Write(data)
|
||||
}
|
||||
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
@@ -1375,7 +1379,7 @@ func (c *Conn) handlePostHandshakeMessage() error {
|
||||
return err
|
||||
}
|
||||
c.retryCount++
|
||||
if c.retryCount > maxUselessRecords {
|
||||
if c.retryCount > c.MaxUselessRecords {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return c.in.setErrorLocked(errors.New("tls: too many non-advancing records"))
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
module github.com/xtls/reality
|
||||
|
||||
go 1.24
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/cloudflare/circl v1.6.1
|
||||
github.com/cloudflare/circl v1.6.3
|
||||
github.com/juju/ratelimit v1.0.2
|
||||
github.com/pires/go-proxyproto v0.8.1
|
||||
github.com/refraction-networking/utls v1.8.1
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/sys v0.37.0
|
||||
github.com/pires/go-proxyproto v0.11.0
|
||||
github.com/refraction-networking/utls v1.8.2
|
||||
golang.org/x/crypto v0.48.0
|
||||
golang.org/x/sys v0.41.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
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=
|
||||
@@ -11,13 +11,13 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
||||
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.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo=
|
||||
github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
|
||||
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
|
||||
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
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=
|
||||
|
||||
+90
-4
@@ -4,9 +4,11 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
@@ -14,6 +16,7 @@ import (
|
||||
)
|
||||
|
||||
var GlobalPostHandshakeRecordsLens sync.Map
|
||||
var GlobalMaxCSSMsgCount sync.Map
|
||||
|
||||
func DetectPostHandshakeRecordsLens(config *Config) {
|
||||
for sni := range config.ServerNames {
|
||||
@@ -36,7 +39,7 @@ func DetectPostHandshakeRecordsLens(config *Config) {
|
||||
return
|
||||
}
|
||||
}
|
||||
detectConn := &DetectConn{
|
||||
detectConn := &PostHandshakeRecordDetectConn{
|
||||
Conn: target,
|
||||
Key: key,
|
||||
}
|
||||
@@ -60,25 +63,58 @@ func DetectPostHandshakeRecordsLens(config *Config) {
|
||||
}
|
||||
io.Copy(io.Discard, uConn)
|
||||
}()
|
||||
go func() {
|
||||
target, err := net.Dial(config.Type, config.Dest)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if config.Xver == 1 || config.Xver == 2 {
|
||||
if _, err = proxyproto.HeaderProxyFromAddrs(config.Xver, target.LocalAddr(), target.RemoteAddr()).WriteTo(target); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
fingerprint := utls.HelloChrome_Auto
|
||||
nextProtos := []string{"h2", "http/1.1"}
|
||||
if alpn != 2 {
|
||||
fingerprint = utls.HelloGolang
|
||||
}
|
||||
if alpn == 1 {
|
||||
nextProtos = []string{"http/1.1"}
|
||||
}
|
||||
if alpn == 0 {
|
||||
nextProtos = nil
|
||||
}
|
||||
conn := &CCSDetectConn{
|
||||
Conn: target,
|
||||
Key: key,
|
||||
}
|
||||
uConn := utls.UClient(conn, &utls.Config{
|
||||
ServerName: sni, // needs new loopvar behaviour
|
||||
NextProtos: nextProtos,
|
||||
}, fingerprint)
|
||||
if err = uConn.Handshake(); err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DetectConn struct {
|
||||
type PostHandshakeRecordDetectConn struct {
|
||||
net.Conn
|
||||
Key string
|
||||
CcsSent bool
|
||||
}
|
||||
|
||||
func (c *DetectConn) Write(b []byte) (n int, err error) {
|
||||
func (c *PostHandshakeRecordDetectConn) Write(b []byte) (n int, err error) {
|
||||
if len(b) >= 3 && bytes.Equal(b[:3], []byte{20, 3, 3}) {
|
||||
c.CcsSent = true
|
||||
}
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (c *DetectConn) Read(b []byte) (n int, err error) {
|
||||
func (c *PostHandshakeRecordDetectConn) Read(b []byte) (n int, err error) {
|
||||
if !c.CcsSent {
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
@@ -97,3 +133,53 @@ func (c *DetectConn) Read(b []byte) (n int, err error) {
|
||||
GlobalPostHandshakeRecordsLens.Store(c.Key, postHandshakeRecordsLens)
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
var CCSMsg = []byte{0x14, 0x3, 0x3, 0x0, 0x1, 0x1}
|
||||
|
||||
type CCSDetectConn struct {
|
||||
net.Conn
|
||||
Key string
|
||||
}
|
||||
|
||||
func (c *CCSDetectConn) Write(b []byte) (n int, err error) {
|
||||
if len(b) >= 3 && bytes.Equal(b[:3], []byte{20, 3, 3}) {
|
||||
var hasAlert atomic.Bool
|
||||
go func() {
|
||||
defer hasAlert.Store(true)
|
||||
buf := make([]byte, 512)
|
||||
for {
|
||||
_, err = c.Conn.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if buf[0] == 0x15 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
sendProbePayload := func(count int) bool {
|
||||
msg := bytes.Repeat(CCSMsg, count)
|
||||
c.Conn.Write(msg)
|
||||
time.Sleep(1 * time.Second)
|
||||
if hasAlert.Load() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
if sendProbePayload(2) {
|
||||
GlobalMaxCSSMsgCount.Store(c.Key, 1)
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
if sendProbePayload(15) {
|
||||
GlobalMaxCSSMsgCount.Store(c.Key, 16)
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
if sendProbePayload(16) {
|
||||
GlobalMaxCSSMsgCount.Store(c.Key, 32)
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
GlobalMaxCSSMsgCount.Store(c.Key, math.MaxInt)
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ func Server(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
go func() { // TODO: Probe target's maxUselessRecords and some time-outs in advance.
|
||||
go func() { // TODO: Probe some time-outs in advance.
|
||||
if handshakeLen-len(s2cSaved) > 0 {
|
||||
io.ReadFull(target, buf[:handshakeLen-len(s2cSaved)])
|
||||
}
|
||||
@@ -422,6 +422,9 @@ func Server(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) {
|
||||
}
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
if maxUseless, ok := GlobalMaxCSSMsgCount.Load(key); ok {
|
||||
hs.c.MaxUselessRecords = maxUseless.(int)
|
||||
}
|
||||
}
|
||||
hs.c.isHandshakeComplete.Store(true)
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user