mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-03-06 17:16:20 +03:00
DNS DoH: Use Chrome's fingerprint & keepAlivePeriod, Add header padding by default
https://github.com/XTLS/Xray-core/discussions/4430#discussioncomment-12374292
This commit is contained in:
parent
b9cb93d3c2
commit
e466b0497c
@ -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
|
||||||
|
@ -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.
|
// NewDoHNameServer creates DOH/DOHL client object for remote/local resolving.
|
||||||
func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, queryStrategy QueryStrategy, h2c bool) (*DoHNameServer, error) {
|
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)
|
mode := "DOH"
|
||||||
s := baseDOHNameServer(url, "DOH", queryStrategy)
|
if dispatcher == nil {
|
||||||
|
mode = "DOHL"
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
errors.LogInfo(context.Background(), "DNS: created ", mode, " client for ", url.String(), ", with h2c ", h2c)
|
||||||
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 {
|
|
||||||
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))
|
||||||
|
@ -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,
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
|
||||||
}
|
|
||||||
|
@ -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 int32(crypto.RandBetween(int64(c.From), int64(c.To)))
|
||||||
return c.From
|
|
||||||
}
|
|
||||||
bigInt, _ := rand.Int(rand.Reader, big.NewInt(int64(c.To-c.From)))
|
|
||||||
return c.From + int32(bigInt.Int64())
|
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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"`
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user