Просмотр исходного кода

DNS DoH: Use Chrome's fingerprint & keepAlivePeriod, Add header padding by default

https://github.com/XTLS/Xray-core/discussions/4430#discussioncomment-12374292
RPRX 1 год назад
Родитель
Сommit
e466b0497c

+ 5 - 3
app/dns/nameserver.go

@@ -45,11 +45,13 @@ func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dis
 		case strings.EqualFold(u.String(), "localhost"):
 		case strings.EqualFold(u.String(), "localhost"):
 			return NewLocalNameServer(queryStrategy), nil
 			return NewLocalNameServer(queryStrategy), nil
 		case strings.EqualFold(u.Scheme, "https"): // DNS-over-HTTPS Remote mode
 		case strings.EqualFold(u.Scheme, "https"): // DNS-over-HTTPS Remote mode
-			return NewDoHNameServer(u, dispatcher, queryStrategy, false)
+			return NewDoHNameServer(u, queryStrategy, dispatcher, false), nil
 		case strings.EqualFold(u.Scheme, "h2c"): // DNS-over-HTTPS h2c Remote mode
 		case strings.EqualFold(u.Scheme, "h2c"): // DNS-over-HTTPS h2c Remote mode
-			return NewDoHNameServer(u, dispatcher, queryStrategy, true)
+			return NewDoHNameServer(u, queryStrategy, dispatcher, true), nil
 		case strings.EqualFold(u.Scheme, "https+local"): // DNS-over-HTTPS Local mode
 		case strings.EqualFold(u.Scheme, "https+local"): // DNS-over-HTTPS Local mode
-			return NewDoHLocalNameServer(u, queryStrategy), nil
+			return NewDoHNameServer(u, queryStrategy, nil, false), nil
+		case strings.EqualFold(u.Scheme, "h2c+local"): // DNS-over-HTTPS h2c Local mode
+			return NewDoHNameServer(u, queryStrategy, nil, true), nil
 		case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode
 		case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode
 			return NewQUICNameServer(u, queryStrategy)
 			return NewQUICNameServer(u, queryStrategy)
 		case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode
 		case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode

+ 71 - 98
app/dns/nameserver_doh.go

@@ -8,10 +8,13 @@ import (
 	"io"
 	"io"
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
+	"strings"
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
+	utls "github.com/refraction-networking/utls"
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common"
+	"github.com/xtls/xray-core/common/crypto"
 	"github.com/xtls/xray-core/common/errors"
 	"github.com/xtls/xray-core/common/errors"
 	"github.com/xtls/xray-core/common/log"
 	"github.com/xtls/xray-core/common/log"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/net"
@@ -31,7 +34,6 @@ import (
 // which is compatible with traditional dns over udp(RFC1035),
 // which is compatible with traditional dns over udp(RFC1035),
 // thus most of the DOH implementation is copied from udpns.go
 // thus most of the DOH implementation is copied from udpns.go
 type DoHNameServer struct {
 type DoHNameServer struct {
-	dispatcher routing.Dispatcher
 	sync.RWMutex
 	sync.RWMutex
 	ips           map[string]*record
 	ips           map[string]*record
 	pub           *pubsub.Service
 	pub           *pubsub.Service
@@ -42,108 +44,18 @@ type DoHNameServer struct {
 	queryStrategy QueryStrategy
 	queryStrategy QueryStrategy
 }
 }
 
 
-// NewDoHNameServer creates DOH server object for remote resolving.
-func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, queryStrategy QueryStrategy, h2c bool) (*DoHNameServer, error) {
+// NewDoHNameServer creates DOH/DOHL client object for remote/local resolving.
+func NewDoHNameServer(url *url.URL, queryStrategy QueryStrategy, dispatcher routing.Dispatcher, h2c bool) *DoHNameServer {
 	url.Scheme = "https"
 	url.Scheme = "https"
-	errors.LogInfo(context.Background(), "DNS: created Remote DNS-over-HTTPS client for ", url.String(), ", with h2c ", h2c)
-	s := baseDOHNameServer(url, "DOH", queryStrategy)
-
-	s.dispatcher = dispatcher
-	dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
-		dest, err := net.ParseDestination(network + ":" + addr)
-		if err != nil {
-			return nil, err
-		}
-		dnsCtx := toDnsContext(ctx, s.dohURL)
-		if h2c {
-			dnsCtx = session.ContextWithMitmAlpn11(dnsCtx, false) // for insurance
-			dnsCtx = session.ContextWithMitmServerName(dnsCtx, url.Hostname())
-		}
-		link, err := s.dispatcher.Dispatch(dnsCtx, dest)
-		select {
-		case <-ctx.Done():
-			return nil, ctx.Err()
-		default:
-
-		}
-		if err != nil {
-			return nil, err
-		}
-
-		cc := common.ChainedClosable{}
-		if cw, ok := link.Writer.(common.Closable); ok {
-			cc = append(cc, cw)
-		}
-		if cr, ok := link.Reader.(common.Closable); ok {
-			cc = append(cc, cr)
-		}
-		return cnc.NewConnection(
-			cnc.ConnectionInputMulti(link.Writer),
-			cnc.ConnectionOutputMulti(link.Reader),
-			cnc.ConnectionOnClose(cc),
-		), nil
+	mode := "DOH"
+	if dispatcher == nil {
+		mode = "DOHL"
 	}
 	}
-
-	s.httpClient = &http.Client{
-		Timeout: time.Second * 180,
-		Transport: &http.Transport{
-			MaxIdleConns:        30,
-			IdleConnTimeout:     90 * time.Second,
-			TLSHandshakeTimeout: 30 * time.Second,
-			ForceAttemptHTTP2:   true,
-			DialContext:         dialContext,
-		},
-	}
-	if h2c {
-		s.httpClient.Transport = &http2.Transport{
-			IdleConnTimeout: 90 * time.Second,
-			DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
-				return dialContext(ctx, network, addr)
-			},
-		}
-	}
-
-	return s, nil
-}
-
-// NewDoHLocalNameServer creates DOH client object for local resolving
-func NewDoHLocalNameServer(url *url.URL, queryStrategy QueryStrategy) *DoHNameServer {
-	url.Scheme = "https"
-	s := baseDOHNameServer(url, "DOHL", queryStrategy)
-	tr := &http.Transport{
-		IdleConnTimeout:   90 * time.Second,
-		ForceAttemptHTTP2: true,
-		DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
-			dest, err := net.ParseDestination(network + ":" + addr)
-			if err != nil {
-				return nil, err
-			}
-			conn, err := internet.DialSystem(ctx, dest, nil)
-			log.Record(&log.AccessMessage{
-				From:   "DNS",
-				To:     s.dohURL,
-				Status: log.AccessAccepted,
-				Detour: "local",
-			})
-			if err != nil {
-				return nil, err
-			}
-			return conn, nil
-		},
-	}
-	s.httpClient = &http.Client{
-		Timeout:   time.Second * 180,
-		Transport: tr,
-	}
-	errors.LogInfo(context.Background(), "DNS: created Local DNS-over-HTTPS client for ", url.String())
-	return s
-}
-
-func baseDOHNameServer(url *url.URL, prefix string, queryStrategy QueryStrategy) *DoHNameServer {
+	errors.LogInfo(context.Background(), "DNS: created ", mode, " client for ", url.String(), ", with h2c ", h2c)
 	s := &DoHNameServer{
 	s := &DoHNameServer{
 		ips:           make(map[string]*record),
 		ips:           make(map[string]*record),
 		pub:           pubsub.NewService(),
 		pub:           pubsub.NewService(),
-		name:          prefix + "//" + url.Host,
+		name:          mode + "//" + url.Host,
 		dohURL:        url.String(),
 		dohURL:        url.String(),
 		queryStrategy: queryStrategy,
 		queryStrategy: queryStrategy,
 	}
 	}
@@ -151,6 +63,65 @@ func baseDOHNameServer(url *url.URL, prefix string, queryStrategy QueryStrategy)
 		Interval: time.Minute,
 		Interval: time.Minute,
 		Execute:  s.Cleanup,
 		Execute:  s.Cleanup,
 	}
 	}
+	s.httpClient = &http.Client{
+		Transport: &http2.Transport{
+			IdleConnTimeout: net.ConnIdleTimeout,
+			ReadIdleTimeout: net.ChromeH2KeepAlivePeriod,
+			DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
+				dest, err := net.ParseDestination(network + ":" + addr)
+				if err != nil {
+					return nil, err
+				}
+				var conn net.Conn
+				if dispatcher != nil {
+					dnsCtx := toDnsContext(ctx, s.dohURL)
+					if h2c {
+						dnsCtx = session.ContextWithMitmAlpn11(dnsCtx, false) // for insurance
+						dnsCtx = session.ContextWithMitmServerName(dnsCtx, url.Hostname())
+					}
+					link, err := dispatcher.Dispatch(dnsCtx, dest)
+					select {
+					case <-ctx.Done():
+						return nil, ctx.Err()
+					default:
+					}
+					if err != nil {
+						return nil, err
+					}
+					cc := common.ChainedClosable{}
+					if cw, ok := link.Writer.(common.Closable); ok {
+						cc = append(cc, cw)
+					}
+					if cr, ok := link.Reader.(common.Closable); ok {
+						cc = append(cc, cr)
+					}
+					conn = cnc.NewConnection(
+						cnc.ConnectionInputMulti(link.Writer),
+						cnc.ConnectionOutputMulti(link.Reader),
+						cnc.ConnectionOnClose(cc),
+					)
+				} else {
+					log.Record(&log.AccessMessage{
+						From:   "DNS",
+						To:     s.dohURL,
+						Status: log.AccessAccepted,
+						Detour: "local",
+					})
+					conn, err = internet.DialSystem(ctx, dest, nil)
+					if err != nil {
+						return nil, err
+					}
+				}
+				if !h2c {
+					conn = utls.UClient(conn, &utls.Config{ServerName: url.Hostname()}, utls.HelloChrome_Auto)
+					if err := conn.(*utls.UConn).HandshakeContext(ctx); err != nil {
+						return nil, err
+					}
+				}
+				return conn, nil
+			},
+		},
+	}
 	return s
 	return s
 }
 }
 
 
@@ -310,6 +281,8 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte,
 	req.Header.Add("Accept", "application/dns-message")
 	req.Header.Add("Accept", "application/dns-message")
 	req.Header.Add("Content-Type", "application/dns-message")
 	req.Header.Add("Content-Type", "application/dns-message")
 
 
+	req.Header.Set("X-Padding", strings.Repeat("X", int(crypto.RandBetween(100, 1000))))
+
 	hc := s.httpClient
 	hc := s.httpClient
 
 
 	resp, err := hc.Do(req.WithContext(ctx))
 	resp, err := hc.Do(req.WithContext(ctx))

+ 4 - 4
app/dns/nameserver_doh_test.go

@@ -17,7 +17,7 @@ func TestDOHNameServer(t *testing.T) {
 	url, err := url.Parse("https+local://1.1.1.1/dns-query")
 	url, err := url.Parse("https+local://1.1.1.1/dns-query")
 	common.Must(err)
 	common.Must(err)
 
 
-	s := NewDoHLocalNameServer(url, QueryStrategy_USE_IP)
+	s := NewDoHNameServer(url, QueryStrategy_USE_IP, nil, false)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv4Enable: true,
@@ -34,7 +34,7 @@ func TestDOHNameServerWithCache(t *testing.T) {
 	url, err := url.Parse("https+local://1.1.1.1/dns-query")
 	url, err := url.Parse("https+local://1.1.1.1/dns-query")
 	common.Must(err)
 	common.Must(err)
 
 
-	s := NewDoHLocalNameServer(url, QueryStrategy_USE_IP)
+	s := NewDoHNameServer(url, QueryStrategy_USE_IP, nil, false)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv4Enable: true,
@@ -62,7 +62,7 @@ func TestDOHNameServerWithIPv4Override(t *testing.T) {
 	url, err := url.Parse("https+local://1.1.1.1/dns-query")
 	url, err := url.Parse("https+local://1.1.1.1/dns-query")
 	common.Must(err)
 	common.Must(err)
 
 
-	s := NewDoHLocalNameServer(url, QueryStrategy_USE_IP4)
+	s := NewDoHNameServer(url, QueryStrategy_USE_IP4, nil, false)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv4Enable: true,
@@ -85,7 +85,7 @@ func TestDOHNameServerWithIPv6Override(t *testing.T) {
 	url, err := url.Parse("https+local://1.1.1.1/dns-query")
 	url, err := url.Parse("https+local://1.1.1.1/dns-query")
 	common.Must(err)
 	common.Must(err)
 
 
-	s := NewDoHLocalNameServer(url, QueryStrategy_USE_IP6)
+	s := NewDoHNameServer(url, QueryStrategy_USE_IP6, nil, false)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv4Enable: true,

+ 13 - 0
common/crypto/crypto.go

@@ -1,2 +1,15 @@
 // Package crypto provides common crypto libraries for Xray.
 // Package crypto provides common crypto libraries for Xray.
 package crypto // import "github.com/xtls/xray-core/common/crypto"
 package crypto // import "github.com/xtls/xray-core/common/crypto"
+
+import (
+	"crypto/rand"
+	"math/big"
+)
+
+func RandBetween(from int64, to int64) int64 {
+	if from == to {
+		return from
+	}
+	bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
+	return from + bigInt.Int64()
+}

+ 12 - 0
common/net/net.go

@@ -1,2 +1,14 @@
 // Package net is a drop-in replacement to Golang's net package, with some more functionalities.
 // Package net is a drop-in replacement to Golang's net package, with some more functionalities.
 package net // import "github.com/xtls/xray-core/common/net"
 package net // import "github.com/xtls/xray-core/common/net"
+
+import "time"
+
+// defines the maximum time an idle TCP session can survive in the tunnel, so
+// it should be consistent across HTTP versions and with other transports.
+const ConnIdleTimeout = 300 * time.Second
+
+// consistent with quic-go
+const QuicgoH3KeepAlivePeriod = 10 * time.Second
+
+// consistent with chrome
+const ChromeH2KeepAlivePeriod = 45 * time.Second

+ 7 - 15
proxy/freedom/freedom.go

@@ -4,12 +4,12 @@ import (
 	"context"
 	"context"
 	"crypto/rand"
 	"crypto/rand"
 	"io"
 	"io"
-	"math/big"
 	"time"
 	"time"
 
 
 	"github.com/pires/go-proxyproto"
 	"github.com/pires/go-proxyproto"
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/buf"
 	"github.com/xtls/xray-core/common/buf"
+	"github.com/xtls/xray-core/common/crypto"
 	"github.com/xtls/xray-core/common/dice"
 	"github.com/xtls/xray-core/common/dice"
 	"github.com/xtls/xray-core/common/errors"
 	"github.com/xtls/xray-core/common/errors"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/net"
@@ -414,7 +414,7 @@ func (w *NoisePacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
 				noise = n.Packet
 				noise = n.Packet
 			} else {
 			} else {
 				//Random noise
 				//Random noise
-				noise, err = GenerateRandomBytes(randBetween(int64(n.LengthMin),
+				noise, err = GenerateRandomBytes(crypto.RandBetween(int64(n.LengthMin),
 					int64(n.LengthMax)))
 					int64(n.LengthMax)))
 			}
 			}
 			if err != nil {
 			if err != nil {
@@ -423,7 +423,7 @@ func (w *NoisePacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
 			w.Writer.WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes(noise)})
 			w.Writer.WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes(noise)})
 
 
 			if n.DelayMin != 0 || n.DelayMax != 0 {
 			if n.DelayMin != 0 || n.DelayMax != 0 {
-				time.Sleep(time.Duration(randBetween(int64(n.DelayMin), int64(n.DelayMax))) * time.Millisecond)
+				time.Sleep(time.Duration(crypto.RandBetween(int64(n.DelayMin), int64(n.DelayMax))) * time.Millisecond)
 			}
 			}
 		}
 		}
 
 
@@ -452,7 +452,7 @@ func (f *FragmentWriter) Write(b []byte) (int, error) {
 		buf := make([]byte, 1024)
 		buf := make([]byte, 1024)
 		var hello []byte
 		var hello []byte
 		for from := 0; ; {
 		for from := 0; ; {
-			to := from + int(randBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax)))
+			to := from + int(crypto.RandBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax)))
 			if to > len(data) {
 			if to > len(data) {
 				to = len(data)
 				to = len(data)
 			}
 			}
@@ -466,7 +466,7 @@ func (f *FragmentWriter) Write(b []byte) (int, error) {
 				hello = append(hello, buf[:5+l]...)
 				hello = append(hello, buf[:5+l]...)
 			} else {
 			} else {
 				_, err := f.writer.Write(buf[:5+l])
 				_, err := f.writer.Write(buf[:5+l])
-				time.Sleep(time.Duration(randBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond)
+				time.Sleep(time.Duration(crypto.RandBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond)
 				if err != nil {
 				if err != nil {
 					return 0, err
 					return 0, err
 				}
 				}
@@ -493,13 +493,13 @@ func (f *FragmentWriter) Write(b []byte) (int, error) {
 		return f.writer.Write(b)
 		return f.writer.Write(b)
 	}
 	}
 	for from := 0; ; {
 	for from := 0; ; {
-		to := from + int(randBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax)))
+		to := from + int(crypto.RandBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax)))
 		if to > len(b) {
 		if to > len(b) {
 			to = len(b)
 			to = len(b)
 		}
 		}
 		n, err := f.writer.Write(b[from:to])
 		n, err := f.writer.Write(b[from:to])
 		from += n
 		from += n
-		time.Sleep(time.Duration(randBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond)
+		time.Sleep(time.Duration(crypto.RandBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond)
 		if err != nil {
 		if err != nil {
 			return from, err
 			return from, err
 		}
 		}
@@ -509,14 +509,6 @@ func (f *FragmentWriter) Write(b []byte) (int, error) {
 	}
 	}
 }
 }
 
 
-// stolen from github.com/xtls/xray-core/transport/internet/reality
-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 GenerateRandomBytes(n int64) ([]byte, error) {
 func GenerateRandomBytes(n int64) ([]byte, error) {
 	b := make([]byte, n)
 	b := make([]byte, n)
 	_, err := rand.Read(b)
 	_, err := rand.Read(b)

+ 7 - 16
transport/internet/reality/reality.go

@@ -8,7 +8,6 @@ import (
 	"crypto/ecdh"
 	"crypto/ecdh"
 	"crypto/ed25519"
 	"crypto/ed25519"
 	"crypto/hmac"
 	"crypto/hmac"
-	"crypto/rand"
 	"crypto/sha256"
 	"crypto/sha256"
 	"crypto/sha512"
 	"crypto/sha512"
 	gotls "crypto/tls"
 	gotls "crypto/tls"
@@ -16,7 +15,6 @@ import (
 	"encoding/binary"
 	"encoding/binary"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
-	"math/big"
 	"net/http"
 	"net/http"
 	"reflect"
 	"reflect"
 	"regexp"
 	"regexp"
@@ -27,6 +25,7 @@ import (
 
 
 	utls "github.com/refraction-networking/utls"
 	utls "github.com/refraction-networking/utls"
 	"github.com/xtls/reality"
 	"github.com/xtls/reality"
+	"github.com/xtls/xray-core/common/crypto"
 	"github.com/xtls/xray-core/common/errors"
 	"github.com/xtls/xray-core/common/errors"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/core"
 	"github.com/xtls/xray-core/core"
@@ -213,13 +212,13 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati
 				}
 				}
 				times := 1
 				times := 1
 				if !first {
 				if !first {
-					times = int(randBetween(config.SpiderY[4], config.SpiderY[5]))
+					times = int(crypto.RandBetween(config.SpiderY[4], config.SpiderY[5]))
 				}
 				}
 				for j := 0; j < times; j++ {
 				for j := 0; j < times; j++ {
 					if !first && j == 0 {
 					if !first && j == 0 {
 						req.Header.Set("Referer", firstURL)
 						req.Header.Set("Referer", firstURL)
 					}
 					}
-					req.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", int(randBetween(config.SpiderY[0], config.SpiderY[1])))})
+					req.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", int(crypto.RandBetween(config.SpiderY[0], config.SpiderY[1])))})
 					if resp, err = client.Do(req); err != nil {
 					if resp, err = client.Do(req); err != nil {
 						break
 						break
 					}
 					}
@@ -243,18 +242,18 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati
 					}
 					}
 					maps.Unlock()
 					maps.Unlock()
 					if !first {
 					if !first {
-						time.Sleep(time.Duration(randBetween(config.SpiderY[6], config.SpiderY[7])) * time.Millisecond) // interval
+						time.Sleep(time.Duration(crypto.RandBetween(config.SpiderY[6], config.SpiderY[7])) * time.Millisecond) // interval
 					}
 					}
 				}
 				}
 			}
 			}
 			get(true)
 			get(true)
-			concurrency := int(randBetween(config.SpiderY[2], config.SpiderY[3]))
+			concurrency := int(crypto.RandBetween(config.SpiderY[2], config.SpiderY[3]))
 			for i := 0; i < concurrency; i++ {
 			for i := 0; i < concurrency; i++ {
 				go get(false)
 				go get(false)
 			}
 			}
 			// Do not close the connection
 			// Do not close the connection
 		}()
 		}()
-		time.Sleep(time.Duration(randBetween(config.SpiderY[8], config.SpiderY[9])) * time.Millisecond) // return
+		time.Sleep(time.Duration(crypto.RandBetween(config.SpiderY[8], config.SpiderY[9])) * time.Millisecond) // return
 		return nil, errors.New("REALITY: processed invalid connection").AtWarning()
 		return nil, errors.New("REALITY: processed invalid connection").AtWarning()
 	}
 	}
 	return uConn, nil
 	return uConn, nil
@@ -271,7 +270,7 @@ var maps struct {
 }
 }
 
 
 func getPathLocked(paths map[string]struct{}) string {
 func getPathLocked(paths map[string]struct{}) string {
-	stopAt := int(randBetween(0, int64(len(paths)-1)))
+	stopAt := int(crypto.RandBetween(0, int64(len(paths)-1)))
 	i := 0
 	i := 0
 	for s := range paths {
 	for s := range paths {
 		if i == stopAt {
 		if i == stopAt {
@@ -281,11 +280,3 @@ func getPathLocked(paths map[string]struct{}) string {
 	}
 	}
 	return "/"
 	return "/"
 }
 }
-
-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()
-}

+ 2 - 7
transport/internet/splithttp/config.go

@@ -1,13 +1,12 @@
 package splithttp
 package splithttp
 
 
 import (
 import (
-	"crypto/rand"
-	"math/big"
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
 	"strings"
 	"strings"
 
 
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common"
+	"github.com/xtls/xray-core/common/crypto"
 	"github.com/xtls/xray-core/transport/internet"
 	"github.com/xtls/xray-core/transport/internet"
 )
 )
 
 
@@ -184,9 +183,5 @@ func init() {
 }
 }
 
 
 func (c RangeConfig) rand() int32 {
 func (c RangeConfig) rand() int32 {
-	if c.From == c.To {
-		return c.From
-	}
-	bigInt, _ := rand.Int(rand.Reader, big.NewInt(int64(c.To-c.From)))
-	return c.From + int32(bigInt.Int64())
+	return int32(crypto.RandBetween(int64(c.From), int64(c.To)))
 }
 }

+ 7 - 17
transport/internet/splithttp/dialer.go

@@ -30,16 +30,6 @@ import (
 	"golang.org/x/net/http2"
 	"golang.org/x/net/http2"
 )
 )
 
 
-// defines the maximum time an idle TCP session can survive in the tunnel, so
-// it should be consistent across HTTP versions and with other transports.
-const connIdleTimeout = 300 * time.Second
-
-// consistent with quic-go
-const quicgoH3KeepAlivePeriod = 10 * time.Second
-
-// consistent with chrome
-const chromeH2KeepAlivePeriod = 45 * time.Second
-
 type dialerConf struct {
 type dialerConf struct {
 	net.Destination
 	net.Destination
 	*internet.MemoryStreamConfig
 	*internet.MemoryStreamConfig
@@ -154,13 +144,13 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
 
 
 	if httpVersion == "3" {
 	if httpVersion == "3" {
 		if keepAlivePeriod == 0 {
 		if keepAlivePeriod == 0 {
-			keepAlivePeriod = quicgoH3KeepAlivePeriod
+			keepAlivePeriod = net.QuicgoH3KeepAlivePeriod
 		}
 		}
 		if keepAlivePeriod < 0 {
 		if keepAlivePeriod < 0 {
 			keepAlivePeriod = 0
 			keepAlivePeriod = 0
 		}
 		}
 		quicConfig := &quic.Config{
 		quicConfig := &quic.Config{
-			MaxIdleTimeout: connIdleTimeout,
+			MaxIdleTimeout: net.ConnIdleTimeout,
 
 
 			// these two are defaults of quic-go/http3. the default of quic-go (no
 			// these two are defaults of quic-go/http3. the default of quic-go (no
 			// http3) is different, so it is hardcoded here for clarity.
 			// http3) is different, so it is hardcoded here for clarity.
@@ -168,7 +158,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
 			MaxIncomingStreams: -1,
 			MaxIncomingStreams: -1,
 			KeepAlivePeriod:    keepAlivePeriod,
 			KeepAlivePeriod:    keepAlivePeriod,
 		}
 		}
-		transport = &http3.RoundTripper{
+		transport = &http3.Transport{
 			QUICConfig:      quicConfig,
 			QUICConfig:      quicConfig,
 			TLSClientConfig: gotlsConfig,
 			TLSClientConfig: gotlsConfig,
 			Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
 			Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
@@ -198,7 +188,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
 						return nil, err
 						return nil, err
 					}
 					}
 				default:
 				default:
-					udpConn = &internet.FakePacketConn{c}
+					udpConn = &internet.FakePacketConn{Conn: c}
 					udpAddr, err = net.ResolveUDPAddr("udp", c.RemoteAddr().String())
 					udpAddr, err = net.ResolveUDPAddr("udp", c.RemoteAddr().String())
 					if err != nil {
 					if err != nil {
 						return nil, err
 						return nil, err
@@ -210,7 +200,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
 		}
 		}
 	} else if httpVersion == "2" {
 	} else if httpVersion == "2" {
 		if keepAlivePeriod == 0 {
 		if keepAlivePeriod == 0 {
-			keepAlivePeriod = chromeH2KeepAlivePeriod
+			keepAlivePeriod = net.ChromeH2KeepAlivePeriod
 		}
 		}
 		if keepAlivePeriod < 0 {
 		if keepAlivePeriod < 0 {
 			keepAlivePeriod = 0
 			keepAlivePeriod = 0
@@ -219,7 +209,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
 			DialTLSContext: func(ctxInner context.Context, network string, addr string, cfg *gotls.Config) (net.Conn, error) {
 			DialTLSContext: func(ctxInner context.Context, network string, addr string, cfg *gotls.Config) (net.Conn, error) {
 				return dialContext(ctxInner)
 				return dialContext(ctxInner)
 			},
 			},
-			IdleConnTimeout: connIdleTimeout,
+			IdleConnTimeout: net.ConnIdleTimeout,
 			ReadIdleTimeout: keepAlivePeriod,
 			ReadIdleTimeout: keepAlivePeriod,
 		}
 		}
 	} else {
 	} else {
@@ -230,7 +220,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
 		transport = &http.Transport{
 		transport = &http.Transport{
 			DialTLSContext:  httpDialContext,
 			DialTLSContext:  httpDialContext,
 			DialContext:     httpDialContext,
 			DialContext:     httpDialContext,
-			IdleConnTimeout: connIdleTimeout,
+			IdleConnTimeout: net.ConnIdleTimeout,
 			// chunked transfer download with KeepAlives is buggy with
 			// chunked transfer download with KeepAlives is buggy with
 			// http.Client and our custom dial context.
 			// http.Client and our custom dial context.
 			DisableKeepAlives: true,
 			DisableKeepAlives: true,

+ 1 - 1
transport/internet/tls/config.pb.go

@@ -207,7 +207,7 @@ type Config struct {
 	// @Critical
 	// @Critical
 	PinnedPeerCertificateChainSha256 [][]byte `protobuf:"bytes,13,rep,name=pinned_peer_certificate_chain_sha256,json=pinnedPeerCertificateChainSha256,proto3" json:"pinned_peer_certificate_chain_sha256,omitempty"`
 	PinnedPeerCertificateChainSha256 [][]byte `protobuf:"bytes,13,rep,name=pinned_peer_certificate_chain_sha256,json=pinnedPeerCertificateChainSha256,proto3" json:"pinned_peer_certificate_chain_sha256,omitempty"`
 	// @Document Some certificate public key sha256 hashes.
 	// @Document Some certificate public key sha256 hashes.
-	// @Document After normal validation (required), if the verified cert's public key hash does not match any of these values, the connection will be aborted.
+	// @Document After normal validation (required), if one of certs in verified chain matches one of these values, the connection will be eventually accepted.
 	// @Critical
 	// @Critical
 	PinnedPeerCertificatePublicKeySha256 [][]byte `protobuf:"bytes,14,rep,name=pinned_peer_certificate_public_key_sha256,json=pinnedPeerCertificatePublicKeySha256,proto3" json:"pinned_peer_certificate_public_key_sha256,omitempty"`
 	PinnedPeerCertificatePublicKeySha256 [][]byte `protobuf:"bytes,14,rep,name=pinned_peer_certificate_public_key_sha256,json=pinnedPeerCertificatePublicKeySha256,proto3" json:"pinned_peer_certificate_public_key_sha256,omitempty"`
 	MasterKeyLog                         string   `protobuf:"bytes,15,opt,name=master_key_log,json=masterKeyLog,proto3" json:"master_key_log,omitempty"`
 	MasterKeyLog                         string   `protobuf:"bytes,15,opt,name=master_key_log,json=masterKeyLog,proto3" json:"master_key_log,omitempty"`

+ 1 - 1
transport/internet/tls/config.proto

@@ -76,7 +76,7 @@ message Config {
   repeated bytes pinned_peer_certificate_chain_sha256 = 13;
   repeated bytes pinned_peer_certificate_chain_sha256 = 13;
 
 
   /* @Document Some certificate public key sha256 hashes.
   /* @Document Some certificate public key sha256 hashes.
-     @Document After normal validation (required), if the verified cert's public key hash does not match any of these values, the connection will be aborted.
+     @Document After normal validation (required), if one of certs in verified chain matches one of these values, the connection will be eventually accepted.
      @Critical
      @Critical
   */
   */
   repeated bytes pinned_peer_certificate_public_key_sha256 = 14;
   repeated bytes pinned_peer_certificate_public_key_sha256 = 14;