mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-08-23 01:56:48 +08:00
Compare commits
29 Commits
v25.2.18
...
dev-ECH-in
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5f504888b6 | ||
![]() |
4999fd5b7b | ||
![]() |
7f6a825bfe | ||
![]() |
6d5be86947 | ||
![]() |
0923f53b21 | ||
![]() |
8554549f2c | ||
![]() |
cab4321956 | ||
![]() |
26199629f7 | ||
![]() |
53ceaf87a5 | ||
![]() |
73e98665ac | ||
![]() |
52c46bc508 | ||
![]() |
9bdf866471 | ||
![]() |
16eee1b89c | ||
![]() |
dde0a4f272 | ||
![]() |
e15dff94b5 | ||
![]() |
e466b0497c | ||
![]() |
b9cb93d3c2 | ||
![]() |
8d46f7e14c | ||
![]() |
4b616f5cd0 | ||
![]() |
06b4a7ce4d | ||
![]() |
4c12e1686b | ||
![]() |
225d151cd3 | ||
![]() |
d451078e72 | ||
![]() |
ce2384cccc | ||
![]() |
be43f66b63 | ||
![]() |
71a6d89c23 | ||
![]() |
89792aee9d | ||
![]() |
b786a50aee | ||
![]() |
b38a53e629 |
@@ -210,6 +210,34 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
|
|||||||
return nil, errors.New("returning nil for domain ", domain).Base(errors.Combine(errs...))
|
return nil, errors.New("returning nil for domain ", domain).Base(errors.Combine(errs...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DNS) LookupHTTPS(domain string) (map[string]string, error) {
|
||||||
|
errs := []error{}
|
||||||
|
ctx := session.ContextWithInbound(s.ctx, &session.Inbound{Tag: s.tag})
|
||||||
|
for _, client := range s.sortClients(domain) {
|
||||||
|
if strings.EqualFold(client.Name(), "FakeDNS") {
|
||||||
|
errors.LogDebug(s.ctx, "skip DNS resolution for domain ", domain, " at server ", client.Name())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
EnhancedServer, ok := client.server.(EnhancedServer)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
HTTPSRecord, err := EnhancedServer.QueryHTTPS(ctx, domain, s.disableCache)
|
||||||
|
if len(HTTPSRecord) > 0 {
|
||||||
|
return HTTPSRecord, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
errors.LogInfoInner(s.ctx, err, "failed to lookup HTTPS for domain ", domain, " at server ", client.Name())
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
// 5 for RcodeRefused in miekg/dns, hardcode to reduce binary size
|
||||||
|
if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch && err != dns.ErrEmptyResponse && dns.RCodeFromError(err) != 5 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("returning nil for domain ", domain).Base(errors.Combine(errs...))
|
||||||
|
}
|
||||||
|
|
||||||
// LookupHosts implements dns.HostsLookup.
|
// LookupHosts implements dns.HostsLookup.
|
||||||
func (s *DNS) LookupHosts(domain string) *net.Address {
|
func (s *DNS) LookupHosts(domain string) *net.Address {
|
||||||
domain = strings.TrimSuffix(domain, ".")
|
domain = strings.TrimSuffix(domain, ".")
|
||||||
|
@@ -29,6 +29,12 @@ type record struct {
|
|||||||
AAAA *IPRecord
|
AAAA *IPRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HTTPSRecord struct {
|
||||||
|
keypair map[string]string
|
||||||
|
Expire time.Time
|
||||||
|
RCode dnsmessage.RCode
|
||||||
|
}
|
||||||
|
|
||||||
// IPRecord is a cacheable item for a resolved domain
|
// IPRecord is a cacheable item for a resolved domain
|
||||||
type IPRecord struct {
|
type IPRecord struct {
|
||||||
ReqID uint16
|
ReqID uint16
|
||||||
|
@@ -23,6 +23,13 @@ type Server interface {
|
|||||||
QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, disableCache bool) ([]net.IP, error)
|
QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, disableCache bool) ([]net.IP, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Server is the interface for Enhanced Name Server.
|
||||||
|
type EnhancedServer interface {
|
||||||
|
Server
|
||||||
|
// QueryHTTPS sends HTTPS queries to its configured server.
|
||||||
|
QueryHTTPS(ctx context.Context, domain string, disableCache bool) (map[string]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
// Client is the interface for DNS client.
|
// Client is the interface for DNS client.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
server Server
|
server Server
|
||||||
@@ -45,11 +52,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,14 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
mdns "github.com/miekg/dns"
|
||||||
|
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 +35,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
|
||||||
@@ -40,117 +43,89 @@ type DoHNameServer struct {
|
|||||||
dohURL string
|
dohURL string
|
||||||
name string
|
name string
|
||||||
queryStrategy QueryStrategy
|
queryStrategy QueryStrategy
|
||||||
|
|
||||||
|
HTTPSCache map[string]*HTTPSRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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,
|
||||||
|
HTTPSCache: make(map[string]*HTTPSRecord),
|
||||||
}
|
}
|
||||||
s.cleanup = &task.Periodic{
|
s.cleanup = &task.Periodic{
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,6 +211,21 @@ func (s *DoHNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
|||||||
common.Must(s.cleanup.Start())
|
common.Must(s.cleanup.Start())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DoHNameServer) updateHTTPS(domain string, HTTPSRec *HTTPSRecord) {
|
||||||
|
s.Lock()
|
||||||
|
rec, found := s.HTTPSCache[domain]
|
||||||
|
if !found {
|
||||||
|
s.HTTPSCache[domain] = HTTPSRec
|
||||||
|
}
|
||||||
|
if found && rec.Expire.Before(time.Now()) {
|
||||||
|
s.HTTPSCache[domain] = HTTPSRec
|
||||||
|
}
|
||||||
|
errors.LogInfo(context.Background(), s.name, " got answer: ", domain, " ", "HTTPS", " -> ", HTTPSRec.keypair)
|
||||||
|
|
||||||
|
s.pub.Publish(domain+"HTTPS", nil)
|
||||||
|
s.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *DoHNameServer) newReqID() uint16 {
|
func (s *DoHNameServer) newReqID() uint16 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -300,6 +290,59 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP n
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DoHNameServer) sendHTTPSQuery(ctx context.Context, domain string) {
|
||||||
|
errors.LogInfo(ctx, s.name, " querying HTTPS record for: ", domain)
|
||||||
|
var deadline time.Time
|
||||||
|
if d, ok := ctx.Deadline(); ok {
|
||||||
|
deadline = d
|
||||||
|
} else {
|
||||||
|
deadline = time.Now().Add(time.Second * 5)
|
||||||
|
}
|
||||||
|
dnsCtx := ctx
|
||||||
|
// reserve internal dns server requested Inbound
|
||||||
|
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||||
|
dnsCtx = session.ContextWithInbound(dnsCtx, inbound)
|
||||||
|
}
|
||||||
|
dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
|
||||||
|
Protocol: "https",
|
||||||
|
SkipDNSResolve: true,
|
||||||
|
})
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
dnsCtx, cancel = context.WithDeadline(dnsCtx, deadline)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
m := new(mdns.Msg)
|
||||||
|
m.SetQuestion(mdns.Fqdn(domain), mdns.TypeHTTPS)
|
||||||
|
m.Id = 0
|
||||||
|
msg, _ := m.Pack()
|
||||||
|
response, err := s.dohHTTPSContext(dnsCtx, msg)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(ctx, err, "failed to retrieve HTTPS query response for ", domain)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respMsg := new(mdns.Msg)
|
||||||
|
err = respMsg.Unpack(response)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(ctx, err, "failed to parse HTTPS query response for ", domain)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var Record = HTTPSRecord{
|
||||||
|
keypair: map[string]string{},
|
||||||
|
}
|
||||||
|
if len(respMsg.Answer) > 0 {
|
||||||
|
for _, answer := range respMsg.Answer {
|
||||||
|
if https, ok := answer.(*mdns.HTTPS); ok && https.Hdr.Name == mdns.Fqdn(domain) {
|
||||||
|
for _, value := range https.Value {
|
||||||
|
Record.keypair[value.Key().String()] = value.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Record.Expire = time.Now().Add(time.Duration(respMsg.Answer[0].Header().Ttl) * time.Second)
|
||||||
|
|
||||||
|
s.updateHTTPS(domain, &Record)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, error) {
|
func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, error) {
|
||||||
body := bytes.NewBuffer(b)
|
body := bytes.NewBuffer(b)
|
||||||
req, err := http.NewRequest("POST", s.dohURL, body)
|
req, err := http.NewRequest("POST", s.dohURL, body)
|
||||||
@@ -310,6 +353,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))
|
||||||
@@ -368,6 +413,27 @@ func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOpt
|
|||||||
return nil, errRecordNotFound
|
return nil, errRecordNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DoHNameServer) findRecordsForDomain(domain string, Querytype string) (any, error) {
|
||||||
|
switch Querytype {
|
||||||
|
case "HTTPS":
|
||||||
|
s.RLock()
|
||||||
|
record, found := s.HTTPSCache[domain]
|
||||||
|
s.RUnlock()
|
||||||
|
if !found {
|
||||||
|
return nil, errRecordNotFound
|
||||||
|
}
|
||||||
|
if len(record.keypair) == 0 {
|
||||||
|
return nil, dns_feature.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
if record.Expire.Before(time.Now()) {
|
||||||
|
return nil, errRecordNotFound
|
||||||
|
}
|
||||||
|
return record, nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported query type: " + Querytype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// QueryIP implements Server.
|
// QueryIP implements Server.
|
||||||
func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) { // nolint: dupl
|
func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) { // nolint: dupl
|
||||||
fqdn := Fqdn(domain)
|
fqdn := Fqdn(domain)
|
||||||
@@ -430,3 +496,44 @@ func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryHTTPS implements EnhancedServer.
|
||||||
|
func (s *DoHNameServer) QueryHTTPS(ctx context.Context, domain string, disableCache bool) (map[string]string, error) { // nolint: dupl
|
||||||
|
fqdn := Fqdn(domain)
|
||||||
|
|
||||||
|
if disableCache {
|
||||||
|
errors.LogDebug(ctx, "DNS cache is disabled. Querying HTTPS for ", domain, " at ", s.name)
|
||||||
|
} else {
|
||||||
|
Record, err := s.findRecordsForDomain(fqdn, "HTTPS")
|
||||||
|
if err == nil || err == dns_feature.ErrEmptyResponse {
|
||||||
|
errors.LogDebugInner(ctx, err, s.name, " cache HIT ", domain, " -> ", Record.(HTTPSRecord).keypair)
|
||||||
|
return Record.(HTTPSRecord).keypair, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sub := s.pub.Subscribe(fqdn + "HTTPS")
|
||||||
|
defer sub.Close()
|
||||||
|
done := make(chan interface{})
|
||||||
|
go func() {
|
||||||
|
if sub != nil {
|
||||||
|
select {
|
||||||
|
case <-sub.Wait():
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
s.sendHTTPSQuery(ctx, fqdn)
|
||||||
|
|
||||||
|
for {
|
||||||
|
Record, err := s.findRecordsForDomain(fqdn, "HTTPS")
|
||||||
|
if err != errRecordNotFound {
|
||||||
|
return Record.(*HTTPSRecord).keypair, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -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,
|
||||||
|
@@ -324,6 +324,7 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest
|
|||||||
if w.sniffingConfig != nil {
|
if w.sniffingConfig != nil {
|
||||||
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
|
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
|
||||||
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
|
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
|
||||||
|
content.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded
|
||||||
content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
|
content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
|
||||||
content.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly
|
content.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly
|
||||||
}
|
}
|
||||||
|
@@ -273,7 +273,16 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
|
|||||||
outbounds := session.OutboundsFromContext(ctx)
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
ob := outbounds[len(outbounds)-1]
|
ob := outbounds[len(outbounds)-1]
|
||||||
if h.senderSettings.ViaCidr == "" {
|
if h.senderSettings.ViaCidr == "" {
|
||||||
ob.Gateway = h.senderSettings.Via.AsAddress()
|
if h.senderSettings.Via.AsAddress().Family().IsDomain() && h.senderSettings.Via.AsAddress().Domain() == "origin" {
|
||||||
|
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||||
|
origin, _, err := net.SplitHostPort(inbound.Conn.LocalAddr().String())
|
||||||
|
if err == nil {
|
||||||
|
ob.Gateway = net.ParseAddress(origin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ob.Gateway = h.senderSettings.Via.AsAddress()
|
||||||
|
}
|
||||||
} else { //Get a random address.
|
} else { //Get a random address.
|
||||||
ob.Gateway = ParseRandomIPv6(h.senderSettings.Via.AsAddress(), h.senderSettings.ViaCidr)
|
ob.Gateway = ParseRandomIPv6(h.senderSettings.Via.AsAddress(), h.senderSettings.ViaCidr)
|
||||||
}
|
}
|
||||||
|
@@ -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()
|
||||||
|
}
|
||||||
|
@@ -120,7 +120,7 @@ func (w *ServerWorker) handleStatusKeepAlive(meta *FrameMetadata, reader *buf.Bu
|
|||||||
func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, reader *buf.BufferedReader) error {
|
func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, reader *buf.BufferedReader) error {
|
||||||
// deep-clone outbounds because it is going to be mutated concurrently
|
// deep-clone outbounds because it is going to be mutated concurrently
|
||||||
// (Target and OriginalTarget)
|
// (Target and OriginalTarget)
|
||||||
ctx = session.ContextCloneOutbounds(ctx)
|
ctx = session.ContextCloneOutboundsAndContent(ctx)
|
||||||
errors.LogInfo(ctx, "received request for ", meta.Target)
|
errors.LogInfo(ctx, "received request for ", meta.Target)
|
||||||
{
|
{
|
||||||
msg := &log.AccessMessage{
|
msg := &log.AccessMessage{
|
||||||
|
@@ -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
|
||||||
|
@@ -63,7 +63,7 @@ func SniffHTTP(b []byte, c context.Context) (*SniffHeader, error) {
|
|||||||
ShouldSniffAttr := true
|
ShouldSniffAttr := true
|
||||||
// If content.Attributes have information, that means it comes from HTTP inbound PlainHTTP mode.
|
// If content.Attributes have information, that means it comes from HTTP inbound PlainHTTP mode.
|
||||||
// It will set attributes, so skip it.
|
// It will set attributes, so skip it.
|
||||||
if content == nil || content.AttributeLen() != 0 {
|
if content == nil || len(content.Attributes) != 0 {
|
||||||
ShouldSniffAttr = false
|
ShouldSniffAttr = false
|
||||||
}
|
}
|
||||||
if err := beginWithHTTPMethod(b); err != nil {
|
if err := beginWithHTTPMethod(b); err != nil {
|
||||||
|
@@ -42,7 +42,7 @@ func ContextWithOutbounds(ctx context.Context, outbounds []*Outbound) context.Co
|
|||||||
return context.WithValue(ctx, outboundSessionKey, outbounds)
|
return context.WithValue(ctx, outboundSessionKey, outbounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ContextCloneOutbounds(ctx context.Context) context.Context {
|
func ContextCloneOutboundsAndContent(ctx context.Context) context.Context {
|
||||||
outbounds := OutboundsFromContext(ctx)
|
outbounds := OutboundsFromContext(ctx)
|
||||||
newOutbounds := make([]*Outbound, len(outbounds))
|
newOutbounds := make([]*Outbound, len(outbounds))
|
||||||
for i, ob := range outbounds {
|
for i, ob := range outbounds {
|
||||||
@@ -55,7 +55,15 @@ func ContextCloneOutbounds(ctx context.Context) context.Context {
|
|||||||
newOutbounds[i] = &v
|
newOutbounds[i] = &v
|
||||||
}
|
}
|
||||||
|
|
||||||
return ContextWithOutbounds(ctx, newOutbounds)
|
content := ContentFromContext(ctx)
|
||||||
|
newContent := Content{}
|
||||||
|
if content != nil {
|
||||||
|
newContent = *content
|
||||||
|
if content.Attributes != nil {
|
||||||
|
panic("content.Attributes != nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ContextWithContent(ContextWithOutbounds(ctx, newOutbounds), &newContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func OutboundsFromContext(ctx context.Context) []*Outbound {
|
func OutboundsFromContext(ctx context.Context) []*Outbound {
|
||||||
|
@@ -4,7 +4,6 @@ package session // import "github.com/xtls/xray-core/common/session"
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sync"
|
|
||||||
|
|
||||||
c "github.com/xtls/xray-core/common/ctx"
|
c "github.com/xtls/xray-core/common/ctx"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
@@ -75,8 +74,8 @@ type Outbound struct {
|
|||||||
|
|
||||||
// SniffingRequest controls the behavior of content sniffing.
|
// SniffingRequest controls the behavior of content sniffing.
|
||||||
type SniffingRequest struct {
|
type SniffingRequest struct {
|
||||||
ExcludeForDomain []string
|
ExcludeForDomain []string // read-only once set
|
||||||
OverrideDestinationForProtocol []string
|
OverrideDestinationForProtocol []string // read-only once set
|
||||||
Enabled bool
|
Enabled bool
|
||||||
MetadataOnly bool
|
MetadataOnly bool
|
||||||
RouteOnly bool
|
RouteOnly bool
|
||||||
@@ -92,10 +91,6 @@ type Content struct {
|
|||||||
Attributes map[string]string
|
Attributes map[string]string
|
||||||
|
|
||||||
SkipDNSResolve bool
|
SkipDNSResolve bool
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
|
|
||||||
isLocked bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sockopt is the settings for socket connection.
|
// Sockopt is the settings for socket connection.
|
||||||
@@ -104,22 +99,8 @@ type Sockopt struct {
|
|||||||
Mark int32
|
Mark int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some how when using mux, there will be a same ctx between different requests
|
|
||||||
// This will cause problem as it's designed for single request, like concurrent map writes
|
|
||||||
// Add a Mutex as a temp solution
|
|
||||||
|
|
||||||
// SetAttribute attaches additional string attributes to content.
|
// SetAttribute attaches additional string attributes to content.
|
||||||
func (c *Content) SetAttribute(name string, value string) {
|
func (c *Content) SetAttribute(name string, value string) {
|
||||||
if c.isLocked {
|
|
||||||
errors.LogError(context.Background(), "Multiple goroutines are tring to access one routing content, tring to write ", name, ":", value)
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
|
||||||
c.isLocked = true
|
|
||||||
defer func() {
|
|
||||||
c.isLocked = false
|
|
||||||
c.mu.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if c.Attributes == nil {
|
if c.Attributes == nil {
|
||||||
c.Attributes = make(map[string]string)
|
c.Attributes = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -128,24 +109,8 @@ func (c *Content) SetAttribute(name string, value string) {
|
|||||||
|
|
||||||
// Attribute retrieves additional string attributes from content.
|
// Attribute retrieves additional string attributes from content.
|
||||||
func (c *Content) Attribute(name string) string {
|
func (c *Content) Attribute(name string) string {
|
||||||
c.mu.Lock()
|
|
||||||
c.isLocked = true
|
|
||||||
defer func() {
|
|
||||||
c.isLocked = false
|
|
||||||
c.mu.Unlock()
|
|
||||||
}()
|
|
||||||
if c.Attributes == nil {
|
if c.Attributes == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return c.Attributes[name]
|
return c.Attributes[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Content) AttributeLen() int {
|
|
||||||
c.mu.Lock()
|
|
||||||
c.isLocked = true
|
|
||||||
defer func() {
|
|
||||||
c.isLocked = false
|
|
||||||
c.mu.Unlock()
|
|
||||||
}()
|
|
||||||
return len(c.Attributes)
|
|
||||||
}
|
|
||||||
|
@@ -18,8 +18,8 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
Version_x byte = 25
|
Version_x byte = 25
|
||||||
Version_y byte = 2
|
Version_y byte = 3
|
||||||
Version_z byte = 18
|
Version_z byte = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@@ -24,6 +24,13 @@ type Client interface {
|
|||||||
LookupIP(domain string, option IPOption) ([]net.IP, error)
|
LookupIP(domain string, option IPOption) ([]net.IP, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EnhancedClient interface {
|
||||||
|
Client
|
||||||
|
|
||||||
|
// LookupHTTPS returns HTTPS records for the given domain.
|
||||||
|
LookupHTTPS(domain string) (map[string]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
type HostsLookup interface {
|
type HostsLookup interface {
|
||||||
LookupHosts(domain string) *net.Address
|
LookupHosts(domain string) *net.Address
|
||||||
}
|
}
|
||||||
|
12
go.mod
12
go.mod
@@ -1,18 +1,18 @@
|
|||||||
module github.com/xtls/xray-core
|
module github.com/xtls/xray-core
|
||||||
|
|
||||||
go 1.23
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0
|
github.com/OmarTariq612/goech v0.0.1
|
||||||
github.com/cloudflare/circl v1.6.0
|
github.com/cloudflare/circl v1.6.0
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
||||||
github.com/golang/mock v1.7.0-rc.1
|
github.com/golang/mock v1.7.0-rc.1
|
||||||
github.com/google/go-cmp v0.6.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/miekg/dns v1.1.63
|
github.com/miekg/dns v1.1.63
|
||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/pelletier/go-toml v1.9.5
|
||||||
github.com/pires/go-proxyproto v0.8.0
|
github.com/pires/go-proxyproto v0.8.0
|
||||||
github.com/quic-go/quic-go v0.49.0
|
github.com/quic-go/quic-go v0.50.0
|
||||||
github.com/refraction-networking/utls v1.6.7
|
github.com/refraction-networking/utls v1.6.7
|
||||||
github.com/sagernet/sing v0.5.1
|
github.com/sagernet/sing v0.5.1
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||||
@@ -22,7 +22,7 @@ require (
|
|||||||
github.com/vishvananda/netlink v1.3.0
|
github.com/vishvananda/netlink v1.3.0
|
||||||
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d
|
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.33.0
|
golang.org/x/crypto v0.35.0
|
||||||
golang.org/x/net v0.35.0
|
golang.org/x/net v0.35.0
|
||||||
golang.org/x/sync v0.11.0
|
golang.org/x/sync v0.11.0
|
||||||
golang.org/x/sys v0.30.0
|
golang.org/x/sys v0.30.0
|
||||||
@@ -31,7 +31,7 @@ require (
|
|||||||
google.golang.org/protobuf v1.36.5
|
google.golang.org/protobuf v1.36.5
|
||||||
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0
|
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0
|
||||||
h12.io/socks v1.0.3
|
h12.io/socks v1.0.3
|
||||||
lukechampine.com/blake3 v1.3.0
|
lukechampine.com/blake3 v1.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
20
go.sum
20
go.sum
@@ -1,5 +1,5 @@
|
|||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I=
|
github.com/OmarTariq612/goech v0.0.1 h1:/0c918Bk1ik65GXDj2k7SOK78DyZr30Jmq9euy1/HXg=
|
||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
|
github.com/OmarTariq612/goech v0.0.1/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
|
||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||||
@@ -24,8 +24,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
|||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
|
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
|
||||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -54,8 +54,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
github.com/quic-go/quic-go v0.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94=
|
github.com/quic-go/quic-go v0.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo=
|
||||||
github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s=
|
github.com/quic-go/quic-go v0.50.0/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
|
||||||
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
||||||
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
@@ -95,8 +95,8 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
|||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
|
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
|
||||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
@@ -160,5 +160,5 @@ gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 h1:P+U/06iIKPQ3DLcg+zBfSCia
|
|||||||
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
|
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
|
||||||
h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
|
h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
|
||||||
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
||||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=
|
||||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=
|
||||||
|
@@ -412,6 +412,8 @@ type TLSConfig struct {
|
|||||||
MasterKeyLog string `json:"masterKeyLog"`
|
MasterKeyLog string `json:"masterKeyLog"`
|
||||||
ServerNameToVerify string `json:"serverNameToVerify"`
|
ServerNameToVerify string `json:"serverNameToVerify"`
|
||||||
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
|
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
|
||||||
|
ECHConfigList string `json:"echConfigList"`
|
||||||
|
EchKeySets string `json:"echKeySets"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
@@ -483,6 +485,16 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
config.VerifyPeerCertInNames = c.VerifyPeerCertInNames
|
config.VerifyPeerCertInNames = c.VerifyPeerCertInNames
|
||||||
|
|
||||||
|
config.EchConfigList = c.ECHConfigList
|
||||||
|
|
||||||
|
if c.EchKeySets != "" {
|
||||||
|
EchPrivateKey, err := base64.StdEncoding.DecodeString(c.EchKeySets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid ECH Config", c.EchKeySets)
|
||||||
|
}
|
||||||
|
config.EchKeySets = EchPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,6 +514,7 @@ type REALITYConfig struct {
|
|||||||
|
|
||||||
Fingerprint string `json:"fingerprint"`
|
Fingerprint string `json:"fingerprint"`
|
||||||
ServerName string `json:"serverName"`
|
ServerName string `json:"serverName"`
|
||||||
|
Password string `json:"password"`
|
||||||
PublicKey string `json:"publicKey"`
|
PublicKey string `json:"publicKey"`
|
||||||
ShortId string `json:"shortId"`
|
ShortId string `json:"shortId"`
|
||||||
SpiderX string `json:"spiderX"`
|
SpiderX string `json:"spiderX"`
|
||||||
@@ -610,11 +623,14 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
|
|||||||
if len(c.ServerNames) != 0 {
|
if len(c.ServerNames) != 0 {
|
||||||
return nil, errors.New(`non-empty "serverNames", please use "serverName" instead`)
|
return nil, errors.New(`non-empty "serverNames", please use "serverName" instead`)
|
||||||
}
|
}
|
||||||
|
if c.Password != "" {
|
||||||
|
c.PublicKey = c.Password
|
||||||
|
}
|
||||||
if c.PublicKey == "" {
|
if c.PublicKey == "" {
|
||||||
return nil, errors.New(`empty "publicKey"`)
|
return nil, errors.New(`empty "password"`)
|
||||||
}
|
}
|
||||||
if config.PublicKey, err = base64.RawURLEncoding.DecodeString(c.PublicKey); err != nil || len(config.PublicKey) != 32 {
|
if config.PublicKey, err = base64.RawURLEncoding.DecodeString(c.PublicKey); err != nil || len(config.PublicKey) != 32 {
|
||||||
return nil, errors.New(`invalid "publicKey": `, c.PublicKey)
|
return nil, errors.New(`invalid "password": `, c.PublicKey)
|
||||||
}
|
}
|
||||||
if len(c.ShortIds) != 0 {
|
if len(c.ShortIds) != 0 {
|
||||||
return nil, errors.New(`non-empty "shortIds", please use "shortId" instead`)
|
return nil, errors.New(`non-empty "shortIds", please use "shortId" instead`)
|
||||||
@@ -711,6 +727,7 @@ type SocketConfig struct {
|
|||||||
Interface string `json:"interface"`
|
Interface string `json:"interface"`
|
||||||
TcpMptcp bool `json:"tcpMptcp"`
|
TcpMptcp bool `json:"tcpMptcp"`
|
||||||
CustomSockopt []*CustomSockoptConfig `json:"customSockopt"`
|
CustomSockopt []*CustomSockoptConfig `json:"customSockopt"`
|
||||||
|
AddressPortStrategy string `json:"addressPortStrategy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
@@ -780,6 +797,26 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
|||||||
customSockopts = append(customSockopts, customSockopt)
|
customSockopts = append(customSockopts, customSockopt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addressPortStrategy := internet.AddressPortStrategy_None
|
||||||
|
switch strings.ToLower(c.AddressPortStrategy) {
|
||||||
|
case "none", "":
|
||||||
|
addressPortStrategy = internet.AddressPortStrategy_None
|
||||||
|
case "srvportonly":
|
||||||
|
addressPortStrategy = internet.AddressPortStrategy_SrvPortOnly
|
||||||
|
case "srvaddressonly":
|
||||||
|
addressPortStrategy = internet.AddressPortStrategy_SrvAddressOnly
|
||||||
|
case "srvportandaddress":
|
||||||
|
addressPortStrategy = internet.AddressPortStrategy_SrvPortAndAddress
|
||||||
|
case "txtportonly":
|
||||||
|
addressPortStrategy = internet.AddressPortStrategy_TxtPortOnly
|
||||||
|
case "txtaddressonly":
|
||||||
|
addressPortStrategy = internet.AddressPortStrategy_TxtAddressOnly
|
||||||
|
case "txtportandaddress":
|
||||||
|
addressPortStrategy = internet.AddressPortStrategy_TxtPortAndAddress
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported address and port strategy: ", c.AddressPortStrategy)
|
||||||
|
}
|
||||||
|
|
||||||
return &internet.SocketConfig{
|
return &internet.SocketConfig{
|
||||||
Mark: c.Mark,
|
Mark: c.Mark,
|
||||||
Tfo: tfo,
|
Tfo: tfo,
|
||||||
@@ -798,6 +835,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
|||||||
Interface: c.Interface,
|
Interface: c.Interface,
|
||||||
TcpMptcp: c.TcpMptcp,
|
TcpMptcp: c.TcpMptcp,
|
||||||
CustomSockopt: customSockopts,
|
CustomSockopt: customSockopts,
|
||||||
|
AddressPortStrategy: addressPortStrategy,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -292,7 +292,9 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
|||||||
senderSettings.ViaCidr = strings.Split(*c.SendThrough, "/")[1]
|
senderSettings.ViaCidr = strings.Split(*c.SendThrough, "/")[1]
|
||||||
} else {
|
} else {
|
||||||
if address.Family().IsDomain() {
|
if address.Family().IsDomain() {
|
||||||
return nil, errors.New("unable to send through: " + address.String())
|
if address.Address.Domain() != "origin" {
|
||||||
|
return nil, errors.New("unable to send through: " + address.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
senderSettings.Via = address.Build()
|
senderSettings.Via = address.Build()
|
||||||
|
@@ -89,12 +89,11 @@ func whichProtoc(suffix, targetedVersion string) (string, error) {
|
|||||||
|
|
||||||
path, err := exec.LookPath(protoc)
|
path, err := exec.LookPath(protoc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errStr := fmt.Sprintf(`
|
return "", fmt.Errorf(`
|
||||||
Command "%s" not found.
|
Command "%s" not found.
|
||||||
Make sure that %s is in your system path or current path.
|
Make sure that %s is in your system path or current path.
|
||||||
Download %s v%s or later from https://github.com/protocolbuffers/protobuf/releases
|
Download %s v%s or later from https://github.com/protocolbuffers/protobuf/releases
|
||||||
`, protoc, protoc, protoc, targetedVersion)
|
`, protoc, protoc, protoc, targetedVersion)
|
||||||
return "", fmt.Errorf(errStr)
|
|
||||||
}
|
}
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
@@ -50,17 +50,17 @@ func executeTypedMessageToJson(cmd *base.Command, args []string) {
|
|||||||
|
|
||||||
reader, err := confloader.LoadConfig(cmd.Flag.Arg(0))
|
reader, err := confloader.LoadConfig(cmd.Flag.Arg(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf(err.Error())
|
base.Fatalf("failed to load config: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := io.ReadAll(reader)
|
b, err := io.ReadAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf(err.Error())
|
base.Fatalf("failed to read config: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tm := cserial.TypedMessage{}
|
tm := cserial.TypedMessage{}
|
||||||
if err = json.Unmarshal(b, &tm); err != nil {
|
if err = json.Unmarshal(b, &tm); err != nil {
|
||||||
base.Fatalf(err.Error())
|
base.Fatalf("failed to unmarshal config: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if j, ok := creflect.MarshalToJson(&tm, injectTypeInfo); ok {
|
if j, ok := creflect.MarshalToJson(&tm, injectTypeInfo); ok {
|
||||||
|
@@ -53,12 +53,12 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(unnamedArgs) < 1 {
|
if len(unnamedArgs) < 1 {
|
||||||
base.Fatalf("empty config list")
|
base.Fatalf("invalid config list length: %d", len(unnamedArgs))
|
||||||
}
|
}
|
||||||
|
|
||||||
pbConfig, err := core.LoadConfig("auto", unnamedArgs)
|
pbConfig, err := core.LoadConfig("auto", unnamedArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf(err.Error())
|
base.Fatalf("failed to load config: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if optDump {
|
if optDump {
|
||||||
|
@@ -1,10 +1,8 @@
|
|||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/OmarTariq612/goech"
|
"github.com/OmarTariq612/goech"
|
||||||
"github.com/cloudflare/circl/hpke"
|
"github.com/cloudflare/circl/hpke"
|
||||||
@@ -13,13 +11,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var cmdECH = &base.Command{
|
var cmdECH = &base.Command{
|
||||||
UsageLine: `{{.Exec}} tls ech [--serverName (string)] [--json]`,
|
UsageLine: `{{.Exec}} tls ech [--serverName (string)] [--pem]`,
|
||||||
Short: `Generate TLS-ECH certificates`,
|
Short: `Generate TLS-ECH certificates`,
|
||||||
Long: `
|
Long: `
|
||||||
Generate TLS-ECH certificates.
|
Generate TLS-ECH certificates.
|
||||||
|
|
||||||
Set serverName to your custom string: {{.Exec}} tls ech --serverName (string)
|
Set serverName to your custom string: {{.Exec}} tls ech --serverName (string)
|
||||||
Generate into json format: {{.Exec}} tls ech --json
|
Generate into pem format: {{.Exec}} tls ech --pem
|
||||||
`, // Enable PQ signature schemes: {{.Exec}} tls ech --pq-signature-schemes-enabled
|
`, // Enable PQ signature schemes: {{.Exec}} tls ech --pq-signature-schemes-enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +27,7 @@ func init() {
|
|||||||
|
|
||||||
var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "")
|
var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "")
|
||||||
var input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", "")
|
var input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", "")
|
||||||
var input_json = cmdECH.Flag.Bool("json", false, "True == turn on json output")
|
var input_pem = cmdECH.Flag.Bool("pem", false, "True == turn on pem output")
|
||||||
|
|
||||||
func executeECH(cmd *base.Command, args []string) {
|
func executeECH(cmd *base.Command, args []string) {
|
||||||
var kem hpke.KEM
|
var kem hpke.KEM
|
||||||
@@ -40,30 +38,26 @@ func executeECH(cmd *base.Command, args []string) {
|
|||||||
kem = hpke.KEM_X25519_HKDF_SHA256
|
kem = hpke.KEM_X25519_HKDF_SHA256
|
||||||
}
|
}
|
||||||
|
|
||||||
echKeySet, err := goech.GenerateECHKeySet(0, *input_serverName, kem)
|
echKeySet, err := goech.GenerateECHKeySet(0, *input_serverName, kem, nil)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
configBuffer, _ := echKeySet.ECHConfig.MarshalBinary()
|
// Make single key set to a list with only one element
|
||||||
keyBuffer, _ := echKeySet.MarshalBinary()
|
ECHConfigList := make(goech.ECHConfigList, 1)
|
||||||
|
ECHConfigList[0] = echKeySet.ECHConfig
|
||||||
|
ECHKeySetList := make(goech.ECHKeySetList, 1)
|
||||||
|
ECHKeySetList[0] = echKeySet
|
||||||
|
configBuffer, _ := ECHConfigList.MarshalBinary()
|
||||||
|
keyBuffer, _ := ECHKeySetList.MarshalBinary()
|
||||||
|
configStr, _ := ECHConfigList.ToBase64()
|
||||||
|
keySetStr, _ := ECHKeySetList.ToBase64()
|
||||||
|
|
||||||
configPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer}))
|
configPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer}))
|
||||||
keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer}))
|
keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer}))
|
||||||
if *input_json {
|
if *input_pem {
|
||||||
jECHConfigs := map[string]interface{}{
|
|
||||||
"configs": strings.Split(strings.TrimSpace(string(configPEM)), "\n"),
|
|
||||||
}
|
|
||||||
jECHKey := map[string]interface{}{
|
|
||||||
"key": strings.Split(strings.TrimSpace(string(keyPEM)), "\n"),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, i := range []map[string]interface{}{jECHConfigs, jECHKey} {
|
|
||||||
content, err := json.MarshalIndent(i, "", " ")
|
|
||||||
common.Must(err)
|
|
||||||
os.Stdout.Write(content)
|
|
||||||
os.Stdout.WriteString("\n")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
os.Stdout.WriteString(configPEM)
|
os.Stdout.WriteString(configPEM)
|
||||||
os.Stdout.WriteString(keyPEM)
|
os.Stdout.WriteString(keyPEM)
|
||||||
|
} else {
|
||||||
|
os.Stdout.WriteString("ECH config list: \n" + configStr + "\n")
|
||||||
|
os.Stdout.WriteString("ECH Key sets: \n" + keySetStr + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
|
@@ -547,8 +547,8 @@ func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
|
|||||||
conn = pc.Raw()
|
conn = pc.Raw()
|
||||||
// 8192 > 4096, there is no need to process pc's bufReader
|
// 8192 > 4096, there is no need to process pc's bufReader
|
||||||
}
|
}
|
||||||
if uc, ok := conn.(*internet.UDSWrapperConn); ok {
|
if uc, ok := conn.(*internet.UnixConnWrapper); ok {
|
||||||
conn = uc.Conn
|
conn = uc.UnixConn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return conn, readCounter, writerCounter
|
return conn, readCounter, writerCounter
|
||||||
|
@@ -95,6 +95,67 @@ func (DomainStrategy) EnumDescriptor() ([]byte, []int) {
|
|||||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{0}
|
return file_transport_internet_config_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AddressPortStrategy int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
AddressPortStrategy_None AddressPortStrategy = 0
|
||||||
|
AddressPortStrategy_SrvPortOnly AddressPortStrategy = 1
|
||||||
|
AddressPortStrategy_SrvAddressOnly AddressPortStrategy = 2
|
||||||
|
AddressPortStrategy_SrvPortAndAddress AddressPortStrategy = 3
|
||||||
|
AddressPortStrategy_TxtPortOnly AddressPortStrategy = 4
|
||||||
|
AddressPortStrategy_TxtAddressOnly AddressPortStrategy = 5
|
||||||
|
AddressPortStrategy_TxtPortAndAddress AddressPortStrategy = 6
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for AddressPortStrategy.
|
||||||
|
var (
|
||||||
|
AddressPortStrategy_name = map[int32]string{
|
||||||
|
0: "None",
|
||||||
|
1: "SrvPortOnly",
|
||||||
|
2: "SrvAddressOnly",
|
||||||
|
3: "SrvPortAndAddress",
|
||||||
|
4: "TxtPortOnly",
|
||||||
|
5: "TxtAddressOnly",
|
||||||
|
6: "TxtPortAndAddress",
|
||||||
|
}
|
||||||
|
AddressPortStrategy_value = map[string]int32{
|
||||||
|
"None": 0,
|
||||||
|
"SrvPortOnly": 1,
|
||||||
|
"SrvAddressOnly": 2,
|
||||||
|
"SrvPortAndAddress": 3,
|
||||||
|
"TxtPortOnly": 4,
|
||||||
|
"TxtAddressOnly": 5,
|
||||||
|
"TxtPortAndAddress": 6,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x AddressPortStrategy) Enum() *AddressPortStrategy {
|
||||||
|
p := new(AddressPortStrategy)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x AddressPortStrategy) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (AddressPortStrategy) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_transport_internet_config_proto_enumTypes[1].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (AddressPortStrategy) Type() protoreflect.EnumType {
|
||||||
|
return &file_transport_internet_config_proto_enumTypes[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x AddressPortStrategy) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use AddressPortStrategy.Descriptor instead.
|
||||||
|
func (AddressPortStrategy) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_transport_internet_config_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
type SocketConfig_TProxyMode int32
|
type SocketConfig_TProxyMode int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -131,11 +192,11 @@ func (x SocketConfig_TProxyMode) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (SocketConfig_TProxyMode) Descriptor() protoreflect.EnumDescriptor {
|
func (SocketConfig_TProxyMode) Descriptor() protoreflect.EnumDescriptor {
|
||||||
return file_transport_internet_config_proto_enumTypes[1].Descriptor()
|
return file_transport_internet_config_proto_enumTypes[2].Descriptor()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (SocketConfig_TProxyMode) Type() protoreflect.EnumType {
|
func (SocketConfig_TProxyMode) Type() protoreflect.EnumType {
|
||||||
return &file_transport_internet_config_proto_enumTypes[1]
|
return &file_transport_internet_config_proto_enumTypes[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber {
|
func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber {
|
||||||
@@ -434,23 +495,24 @@ type SocketConfig struct {
|
|||||||
Tproxy SocketConfig_TProxyMode `protobuf:"varint,3,opt,name=tproxy,proto3,enum=xray.transport.internet.SocketConfig_TProxyMode" json:"tproxy,omitempty"`
|
Tproxy SocketConfig_TProxyMode `protobuf:"varint,3,opt,name=tproxy,proto3,enum=xray.transport.internet.SocketConfig_TProxyMode" json:"tproxy,omitempty"`
|
||||||
// ReceiveOriginalDestAddress is for enabling IP_RECVORIGDSTADDR socket
|
// ReceiveOriginalDestAddress is for enabling IP_RECVORIGDSTADDR socket
|
||||||
// option. This option is for UDP only.
|
// option. This option is for UDP only.
|
||||||
ReceiveOriginalDestAddress bool `protobuf:"varint,4,opt,name=receive_original_dest_address,json=receiveOriginalDestAddress,proto3" json:"receive_original_dest_address,omitempty"`
|
ReceiveOriginalDestAddress bool `protobuf:"varint,4,opt,name=receive_original_dest_address,json=receiveOriginalDestAddress,proto3" json:"receive_original_dest_address,omitempty"`
|
||||||
BindAddress []byte `protobuf:"bytes,5,opt,name=bind_address,json=bindAddress,proto3" json:"bind_address,omitempty"`
|
BindAddress []byte `protobuf:"bytes,5,opt,name=bind_address,json=bindAddress,proto3" json:"bind_address,omitempty"`
|
||||||
BindPort uint32 `protobuf:"varint,6,opt,name=bind_port,json=bindPort,proto3" json:"bind_port,omitempty"`
|
BindPort uint32 `protobuf:"varint,6,opt,name=bind_port,json=bindPort,proto3" json:"bind_port,omitempty"`
|
||||||
AcceptProxyProtocol bool `protobuf:"varint,7,opt,name=accept_proxy_protocol,json=acceptProxyProtocol,proto3" json:"accept_proxy_protocol,omitempty"`
|
AcceptProxyProtocol bool `protobuf:"varint,7,opt,name=accept_proxy_protocol,json=acceptProxyProtocol,proto3" json:"accept_proxy_protocol,omitempty"`
|
||||||
DomainStrategy DomainStrategy `protobuf:"varint,8,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"domain_strategy,omitempty"`
|
DomainStrategy DomainStrategy `protobuf:"varint,8,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"domain_strategy,omitempty"`
|
||||||
DialerProxy string `protobuf:"bytes,9,opt,name=dialer_proxy,json=dialerProxy,proto3" json:"dialer_proxy,omitempty"`
|
DialerProxy string `protobuf:"bytes,9,opt,name=dialer_proxy,json=dialerProxy,proto3" json:"dialer_proxy,omitempty"`
|
||||||
TcpKeepAliveInterval int32 `protobuf:"varint,10,opt,name=tcp_keep_alive_interval,json=tcpKeepAliveInterval,proto3" json:"tcp_keep_alive_interval,omitempty"`
|
TcpKeepAliveInterval int32 `protobuf:"varint,10,opt,name=tcp_keep_alive_interval,json=tcpKeepAliveInterval,proto3" json:"tcp_keep_alive_interval,omitempty"`
|
||||||
TcpKeepAliveIdle int32 `protobuf:"varint,11,opt,name=tcp_keep_alive_idle,json=tcpKeepAliveIdle,proto3" json:"tcp_keep_alive_idle,omitempty"`
|
TcpKeepAliveIdle int32 `protobuf:"varint,11,opt,name=tcp_keep_alive_idle,json=tcpKeepAliveIdle,proto3" json:"tcp_keep_alive_idle,omitempty"`
|
||||||
TcpCongestion string `protobuf:"bytes,12,opt,name=tcp_congestion,json=tcpCongestion,proto3" json:"tcp_congestion,omitempty"`
|
TcpCongestion string `protobuf:"bytes,12,opt,name=tcp_congestion,json=tcpCongestion,proto3" json:"tcp_congestion,omitempty"`
|
||||||
Interface string `protobuf:"bytes,13,opt,name=interface,proto3" json:"interface,omitempty"`
|
Interface string `protobuf:"bytes,13,opt,name=interface,proto3" json:"interface,omitempty"`
|
||||||
V6Only bool `protobuf:"varint,14,opt,name=v6only,proto3" json:"v6only,omitempty"`
|
V6Only bool `protobuf:"varint,14,opt,name=v6only,proto3" json:"v6only,omitempty"`
|
||||||
TcpWindowClamp int32 `protobuf:"varint,15,opt,name=tcp_window_clamp,json=tcpWindowClamp,proto3" json:"tcp_window_clamp,omitempty"`
|
TcpWindowClamp int32 `protobuf:"varint,15,opt,name=tcp_window_clamp,json=tcpWindowClamp,proto3" json:"tcp_window_clamp,omitempty"`
|
||||||
TcpUserTimeout int32 `protobuf:"varint,16,opt,name=tcp_user_timeout,json=tcpUserTimeout,proto3" json:"tcp_user_timeout,omitempty"`
|
TcpUserTimeout int32 `protobuf:"varint,16,opt,name=tcp_user_timeout,json=tcpUserTimeout,proto3" json:"tcp_user_timeout,omitempty"`
|
||||||
TcpMaxSeg int32 `protobuf:"varint,17,opt,name=tcp_max_seg,json=tcpMaxSeg,proto3" json:"tcp_max_seg,omitempty"`
|
TcpMaxSeg int32 `protobuf:"varint,17,opt,name=tcp_max_seg,json=tcpMaxSeg,proto3" json:"tcp_max_seg,omitempty"`
|
||||||
Penetrate bool `protobuf:"varint,18,opt,name=penetrate,proto3" json:"penetrate,omitempty"`
|
Penetrate bool `protobuf:"varint,18,opt,name=penetrate,proto3" json:"penetrate,omitempty"`
|
||||||
TcpMptcp bool `protobuf:"varint,19,opt,name=tcp_mptcp,json=tcpMptcp,proto3" json:"tcp_mptcp,omitempty"`
|
TcpMptcp bool `protobuf:"varint,19,opt,name=tcp_mptcp,json=tcpMptcp,proto3" json:"tcp_mptcp,omitempty"`
|
||||||
CustomSockopt []*CustomSockopt `protobuf:"bytes,20,rep,name=customSockopt,proto3" json:"customSockopt,omitempty"`
|
CustomSockopt []*CustomSockopt `protobuf:"bytes,20,rep,name=customSockopt,proto3" json:"customSockopt,omitempty"`
|
||||||
|
AddressPortStrategy AddressPortStrategy `protobuf:"varint,21,opt,name=address_port_strategy,json=addressPortStrategy,proto3,enum=xray.transport.internet.AddressPortStrategy" json:"address_port_strategy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SocketConfig) Reset() {
|
func (x *SocketConfig) Reset() {
|
||||||
@@ -623,6 +685,13 @@ func (x *SocketConfig) GetCustomSockopt() []*CustomSockopt {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *SocketConfig) GetAddressPortStrategy() AddressPortStrategy {
|
||||||
|
if x != nil {
|
||||||
|
return x.AddressPortStrategy
|
||||||
|
}
|
||||||
|
return AddressPortStrategy_None
|
||||||
|
}
|
||||||
|
|
||||||
var File_transport_internet_config_proto protoreflect.FileDescriptor
|
var File_transport_internet_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_transport_internet_config_proto_rawDesc = []byte{
|
var file_transport_internet_config_proto_rawDesc = []byte{
|
||||||
@@ -678,7 +747,7 @@ var file_transport_internet_config_proto_rawDesc = []byte{
|
|||||||
0x28, 0x09, 0x52, 0x03, 0x6f, 0x70, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
0x28, 0x09, 0x52, 0x03, 0x6f, 0x70, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a,
|
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a,
|
||||||
0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70,
|
0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70,
|
||||||
0x65, 0x22, 0x9b, 0x07, 0x0a, 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
|
0x65, 0x22, 0xfd, 0x07, 0x0a, 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
|
||||||
0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
|
0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
|
||||||
0x52, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x66, 0x6f, 0x18, 0x02, 0x20,
|
0x52, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x66, 0x6f, 0x18, 0x02, 0x20,
|
||||||
0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x06, 0x74, 0x70, 0x72, 0x6f,
|
0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x06, 0x74, 0x70, 0x72, 0x6f,
|
||||||
@@ -732,28 +801,44 @@ var file_transport_internet_config_proto_rawDesc = []byte{
|
|||||||
0x74, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74,
|
0x74, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74,
|
||||||
0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65,
|
0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65,
|
||||||
0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74, 0x52,
|
0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74, 0x52,
|
||||||
0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74, 0x22, 0x2f,
|
0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74, 0x12, 0x60,
|
||||||
0x0a, 0x0a, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x07, 0x0a, 0x03,
|
0x0a, 0x15, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73,
|
||||||
0x4f, 0x66, 0x66, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x10,
|
0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e,
|
||||||
0x01, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x10, 0x02, 0x2a,
|
0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69,
|
||||||
0xa9, 0x01, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
|
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50,
|
||||||
0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a,
|
0x6f, 0x72, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x13, 0x61, 0x64, 0x64,
|
||||||
0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45,
|
0x72, 0x65, 0x73, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
|
||||||
0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50,
|
0x22, 0x2f, 0x0a, 0x0a, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x07,
|
||||||
0x36, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x36, 0x10,
|
0x0a, 0x03, 0x4f, 0x66, 0x66, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x50, 0x72, 0x6f, 0x78,
|
||||||
0x04, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x34, 0x10, 0x05, 0x12,
|
0x79, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x10,
|
||||||
0x0c, 0x0a, 0x08, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x06, 0x12, 0x0d, 0x0a,
|
0x02, 0x2a, 0xa9, 0x01, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61,
|
||||||
0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09,
|
0x74, 0x65, 0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12,
|
||||||
0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x08, 0x12, 0x0e, 0x0a, 0x0a, 0x46,
|
0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55,
|
||||||
0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x36, 0x10, 0x09, 0x12, 0x0e, 0x0a, 0x0a, 0x46,
|
0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f,
|
||||||
0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x34, 0x10, 0x0a, 0x42, 0x67, 0x0a, 0x1b, 0x63,
|
0x49, 0x50, 0x36, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34,
|
||||||
0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
|
0x36, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x34, 0x10,
|
||||||
0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69,
|
0x05, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x06, 0x12,
|
||||||
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72,
|
0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x07, 0x12, 0x0d,
|
||||||
0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
|
0x0a, 0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x08, 0x12, 0x0e, 0x0a,
|
||||||
0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x17, 0x58, 0x72, 0x61,
|
0x0a, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x36, 0x10, 0x09, 0x12, 0x0e, 0x0a,
|
||||||
0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65,
|
0x0a, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x34, 0x10, 0x0a, 0x2a, 0x97, 0x01,
|
||||||
0x72, 0x6e, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x0a, 0x13, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x72,
|
||||||
|
0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12,
|
||||||
|
0x0f, 0x0a, 0x0b, 0x53, 0x72, 0x76, 0x50, 0x6f, 0x72, 0x74, 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x01,
|
||||||
|
0x12, 0x12, 0x0a, 0x0e, 0x53, 0x72, 0x76, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4f, 0x6e,
|
||||||
|
0x6c, 0x79, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x72, 0x76, 0x50, 0x6f, 0x72, 0x74, 0x41,
|
||||||
|
0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x54,
|
||||||
|
0x78, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e,
|
||||||
|
0x54, 0x78, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x05,
|
||||||
|
0x12, 0x15, 0x0a, 0x11, 0x54, 0x78, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x41, 0x6e, 0x64, 0x41, 0x64,
|
||||||
|
0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x06, 0x42, 0x67, 0x0a, 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x78,
|
||||||
|
0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e,
|
||||||
|
0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||||
|
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63,
|
||||||
|
0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e,
|
||||||
|
0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x17, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72,
|
||||||
|
0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
|
||||||
|
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -768,33 +853,35 @@ func file_transport_internet_config_proto_rawDescGZIP() []byte {
|
|||||||
return file_transport_internet_config_proto_rawDescData
|
return file_transport_internet_config_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
|
||||||
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||||
var file_transport_internet_config_proto_goTypes = []any{
|
var file_transport_internet_config_proto_goTypes = []any{
|
||||||
(DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy
|
(DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy
|
||||||
(SocketConfig_TProxyMode)(0), // 1: xray.transport.internet.SocketConfig.TProxyMode
|
(AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy
|
||||||
(*TransportConfig)(nil), // 2: xray.transport.internet.TransportConfig
|
(SocketConfig_TProxyMode)(0), // 2: xray.transport.internet.SocketConfig.TProxyMode
|
||||||
(*StreamConfig)(nil), // 3: xray.transport.internet.StreamConfig
|
(*TransportConfig)(nil), // 3: xray.transport.internet.TransportConfig
|
||||||
(*ProxyConfig)(nil), // 4: xray.transport.internet.ProxyConfig
|
(*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig
|
||||||
(*CustomSockopt)(nil), // 5: xray.transport.internet.CustomSockopt
|
(*ProxyConfig)(nil), // 5: xray.transport.internet.ProxyConfig
|
||||||
(*SocketConfig)(nil), // 6: xray.transport.internet.SocketConfig
|
(*CustomSockopt)(nil), // 6: xray.transport.internet.CustomSockopt
|
||||||
(*serial.TypedMessage)(nil), // 7: xray.common.serial.TypedMessage
|
(*SocketConfig)(nil), // 7: xray.transport.internet.SocketConfig
|
||||||
(*net.IPOrDomain)(nil), // 8: xray.common.net.IPOrDomain
|
(*serial.TypedMessage)(nil), // 8: xray.common.serial.TypedMessage
|
||||||
|
(*net.IPOrDomain)(nil), // 9: xray.common.net.IPOrDomain
|
||||||
}
|
}
|
||||||
var file_transport_internet_config_proto_depIdxs = []int32{
|
var file_transport_internet_config_proto_depIdxs = []int32{
|
||||||
7, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
|
8, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
|
||||||
8, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
|
9, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
|
||||||
2, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
|
3, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
|
||||||
7, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
|
8, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
|
||||||
6, // 4: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
|
7, // 4: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
|
||||||
1, // 5: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
|
2, // 5: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
|
||||||
0, // 6: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
0, // 6: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||||
5, // 7: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
|
6, // 7: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
|
||||||
8, // [8:8] is the sub-list for method output_type
|
1, // 8: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
|
||||||
8, // [8:8] is the sub-list for method input_type
|
9, // [9:9] is the sub-list for method output_type
|
||||||
8, // [8:8] is the sub-list for extension type_name
|
9, // [9:9] is the sub-list for method input_type
|
||||||
8, // [8:8] is the sub-list for extension extendee
|
9, // [9:9] is the sub-list for extension type_name
|
||||||
0, // [0:8] is the sub-list for field type_name
|
9, // [9:9] is the sub-list for extension extendee
|
||||||
|
0, // [0:9] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_transport_internet_config_proto_init() }
|
func init() { file_transport_internet_config_proto_init() }
|
||||||
@@ -807,7 +894,7 @@ func file_transport_internet_config_proto_init() {
|
|||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_transport_internet_config_proto_rawDesc,
|
RawDescriptor: file_transport_internet_config_proto_rawDesc,
|
||||||
NumEnums: 2,
|
NumEnums: 3,
|
||||||
NumMessages: 5,
|
NumMessages: 5,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
|
@@ -23,6 +23,16 @@ enum DomainStrategy {
|
|||||||
FORCE_IP64 = 10;
|
FORCE_IP64 = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum AddressPortStrategy {
|
||||||
|
None = 0;
|
||||||
|
SrvPortOnly = 1;
|
||||||
|
SrvAddressOnly = 2;
|
||||||
|
SrvPortAndAddress = 3;
|
||||||
|
TxtPortOnly = 4;
|
||||||
|
TxtAddressOnly = 5;
|
||||||
|
TxtPortAndAddress = 6;
|
||||||
|
}
|
||||||
|
|
||||||
message TransportConfig {
|
message TransportConfig {
|
||||||
// Transport protocol name.
|
// Transport protocol name.
|
||||||
string protocol_name = 3;
|
string protocol_name = 3;
|
||||||
@@ -116,4 +126,6 @@ message SocketConfig {
|
|||||||
bool tcp_mptcp = 19;
|
bool tcp_mptcp = 19;
|
||||||
|
|
||||||
repeated CustomSockopt customSockopt = 20;
|
repeated CustomSockopt customSockopt = 20;
|
||||||
|
|
||||||
|
AddressPortStrategy address_port_strategy = 21;
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,9 @@ package internet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
gonet "net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/dice"
|
"github.com/xtls/xray-core/common/dice"
|
||||||
@@ -103,6 +106,14 @@ func lookupIP(domain string, strategy DomainStrategy, localAddr net.Address) ([]
|
|||||||
return ips, err
|
return ips, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LookupHTTPS(domain string) (map[string]string, error) {
|
||||||
|
if dnsClient == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
HTTPSRecord, err := dnsClient.(dns.EnhancedClient).LookupHTTPS(domain)
|
||||||
|
return HTTPSRecord, err
|
||||||
|
}
|
||||||
|
|
||||||
func canLookupIP(ctx context.Context, dst net.Destination, sockopt *SocketConfig) bool {
|
func canLookupIP(ctx context.Context, dst net.Destination, sockopt *SocketConfig) bool {
|
||||||
if dst.Address.Family().IsIP() || dnsClient == nil {
|
if dst.Address.Family().IsIP() || dnsClient == nil {
|
||||||
return false
|
return false
|
||||||
@@ -140,6 +151,93 @@ func redirect(ctx context.Context, dst net.Destination, obt string) net.Conn {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkAddressPortStrategy(ctx context.Context, dest net.Destination, sockopt *SocketConfig) (*net.Destination, error) {
|
||||||
|
if sockopt.AddressPortStrategy == AddressPortStrategy_None {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
newDest := dest
|
||||||
|
var OverridePort, OverrideAddress bool
|
||||||
|
var OverrideBy string
|
||||||
|
switch sockopt.AddressPortStrategy {
|
||||||
|
case AddressPortStrategy_SrvPortOnly:
|
||||||
|
OverridePort = true
|
||||||
|
OverrideAddress = false
|
||||||
|
OverrideBy = "srv"
|
||||||
|
case AddressPortStrategy_SrvAddressOnly:
|
||||||
|
OverridePort = false
|
||||||
|
OverrideAddress = true
|
||||||
|
OverrideBy = "srv"
|
||||||
|
case AddressPortStrategy_SrvPortAndAddress:
|
||||||
|
OverridePort = true
|
||||||
|
OverrideAddress = true
|
||||||
|
OverrideBy = "srv"
|
||||||
|
case AddressPortStrategy_TxtPortOnly:
|
||||||
|
OverridePort = true
|
||||||
|
OverrideAddress = false
|
||||||
|
OverrideBy = "txt"
|
||||||
|
case AddressPortStrategy_TxtAddressOnly:
|
||||||
|
OverridePort = false
|
||||||
|
OverrideAddress = true
|
||||||
|
OverrideBy = "txt"
|
||||||
|
case AddressPortStrategy_TxtPortAndAddress:
|
||||||
|
OverridePort = true
|
||||||
|
OverrideAddress = true
|
||||||
|
OverrideBy = "txt"
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown AddressPortStrategy")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dest.Address.Family().IsDomain() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if OverrideBy == "srv" {
|
||||||
|
errors.LogDebug(ctx, "query SRV record for "+dest.Address.String())
|
||||||
|
parts := strings.SplitN(dest.Address.String(), ".", 3)
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return nil, errors.New("invalid address format", dest.Address.String())
|
||||||
|
}
|
||||||
|
_, srvRecords, err := gonet.DefaultResolver.LookupSRV(context.Background(), parts[0][1:], parts[1][1:], parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to lookup SRV record").Base(err)
|
||||||
|
}
|
||||||
|
errors.LogDebug(ctx, "SRV record: "+fmt.Sprintf("addr=%s, port=%d, priority=%d, weight=%d", srvRecords[0].Target, srvRecords[0].Port, srvRecords[0].Priority, srvRecords[0].Weight))
|
||||||
|
if OverridePort {
|
||||||
|
newDest.Port = net.Port(srvRecords[0].Port)
|
||||||
|
}
|
||||||
|
if OverrideAddress {
|
||||||
|
newDest.Address = net.ParseAddress(srvRecords[0].Target)
|
||||||
|
}
|
||||||
|
return &newDest, nil
|
||||||
|
}
|
||||||
|
if OverrideBy == "txt" {
|
||||||
|
errors.LogDebug(ctx, "query TXT record for "+dest.Address.String())
|
||||||
|
txtRecords, err := gonet.DefaultResolver.LookupTXT(ctx, dest.Address.String())
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(ctx, "failed to lookup SRV record: "+err.Error())
|
||||||
|
return nil, errors.New("failed to lookup SRV record").Base(err)
|
||||||
|
}
|
||||||
|
for _, txtRecord := range txtRecords {
|
||||||
|
errors.LogDebug(ctx, "TXT record: "+txtRecord)
|
||||||
|
addr_s, port_s, _ := net.SplitHostPort(string(txtRecord))
|
||||||
|
addr := net.ParseAddress(addr_s)
|
||||||
|
port, err := net.PortFromString(port_s)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if OverridePort {
|
||||||
|
newDest.Port = port
|
||||||
|
}
|
||||||
|
if OverrideAddress {
|
||||||
|
newDest.Address = addr
|
||||||
|
}
|
||||||
|
return &newDest, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// DialSystem calls system dialer to create a network connection.
|
// DialSystem calls system dialer to create a network connection.
|
||||||
func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig) (net.Conn, error) {
|
func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig) (net.Conn, error) {
|
||||||
var src net.Address
|
var src net.Address
|
||||||
@@ -152,6 +250,11 @@ func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig
|
|||||||
return effectiveSystemDialer.Dial(ctx, src, dest, sockopt)
|
return effectiveSystemDialer.Dial(ctx, src, dest, sockopt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if newDest, err := checkAddressPortStrategy(ctx, dest, sockopt); err == nil && newDest != nil {
|
||||||
|
errors.LogInfo(ctx, "replace destination with "+newDest.String())
|
||||||
|
dest = *newDest
|
||||||
|
}
|
||||||
|
|
||||||
if canLookupIP(ctx, dest, sockopt) {
|
if canLookupIP(ctx, dest, sockopt) {
|
||||||
ips, err := lookupIP(dest.Address.String(), sockopt.DomainStrategy, src)
|
ips, err := lookupIP(dest.Address.String(), sockopt.DomainStrategy, src)
|
||||||
if err == nil && len(ips) > 0 {
|
if err == nil && len(ips) > 0 {
|
||||||
|
@@ -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,
|
||||||
|
@@ -24,8 +24,6 @@ import (
|
|||||||
"github.com/xtls/xray-core/transport/internet/reality"
|
"github.com/xtls/xray-core/transport/internet/reality"
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
"golang.org/x/net/http2"
|
|
||||||
"golang.org/x/net/http2/h2c"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type requestHandler struct {
|
type requestHandler struct {
|
||||||
@@ -47,21 +45,6 @@ type httpSession struct {
|
|||||||
isFullyConnected *done.Instance
|
isFullyConnected *done.Instance
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *requestHandler) maybeReapSession(isFullyConnected *done.Instance, sessionId string) {
|
|
||||||
shouldReap := done.New()
|
|
||||||
go func() {
|
|
||||||
time.Sleep(30 * time.Second)
|
|
||||||
shouldReap.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-isFullyConnected.Wait():
|
|
||||||
return
|
|
||||||
case <-shouldReap.Wait():
|
|
||||||
h.sessions.Delete(sessionId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *requestHandler) upsertSession(sessionId string) *httpSession {
|
func (h *requestHandler) upsertSession(sessionId string) *httpSession {
|
||||||
// fast path
|
// fast path
|
||||||
currentSessionAny, ok := h.sessions.Load(sessionId)
|
currentSessionAny, ok := h.sessions.Load(sessionId)
|
||||||
@@ -84,7 +67,21 @@ func (h *requestHandler) upsertSession(sessionId string) *httpSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h.sessions.Store(sessionId, s)
|
h.sessions.Store(sessionId, s)
|
||||||
go h.maybeReapSession(s.isFullyConnected, sessionId)
|
|
||||||
|
shouldReap := done.New()
|
||||||
|
go func() {
|
||||||
|
time.Sleep(30 * time.Second)
|
||||||
|
shouldReap.Close()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-shouldReap.Wait():
|
||||||
|
h.sessions.Delete(sessionId)
|
||||||
|
s.uploadQueue.Close()
|
||||||
|
case <-s.isFullyConnected.Wait():
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,12 +180,13 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
|||||||
writer.WriteHeader(http.StatusBadRequest)
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
uploadDone := done.New()
|
httpSC := &httpServerConn{
|
||||||
|
Instance: done.New(),
|
||||||
|
Reader: request.Body,
|
||||||
|
ResponseWriter: writer,
|
||||||
|
}
|
||||||
err = currentSession.uploadQueue.Push(Packet{
|
err = currentSession.uploadQueue.Push(Packet{
|
||||||
Reader: &httpRequestBodyReader{
|
Reader: httpSC,
|
||||||
requestReader: request.Body,
|
|
||||||
uploadDone: uploadDone,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogInfoInner(context.Background(), err, "failed to upload (PushReader)")
|
errors.LogInfoInner(context.Background(), err, "failed to upload (PushReader)")
|
||||||
@@ -200,25 +198,21 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
|||||||
scStreamUpServerSecs := h.config.GetNormalizedScStreamUpServerSecs()
|
scStreamUpServerSecs := h.config.GetNormalizedScStreamUpServerSecs()
|
||||||
if referrer != "" && scStreamUpServerSecs.To > 0 {
|
if referrer != "" && scStreamUpServerSecs.To > 0 {
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
|
||||||
recover()
|
|
||||||
}()
|
|
||||||
for {
|
for {
|
||||||
_, err := writer.Write(bytes.Repeat([]byte{'X'}, int(h.config.GetNormalizedXPaddingBytes().rand())))
|
_, err := httpSC.Write(bytes.Repeat([]byte{'X'}, int(h.config.GetNormalizedXPaddingBytes().rand())))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
writer.(http.Flusher).Flush()
|
|
||||||
time.Sleep(time.Duration(scStreamUpServerSecs.rand()) * time.Second)
|
time.Sleep(time.Duration(scStreamUpServerSecs.rand()) * time.Second)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case <-request.Context().Done():
|
case <-request.Context().Done():
|
||||||
case <-uploadDone.Wait():
|
case <-httpSC.Wait():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uploadDone.Close()
|
httpSC.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,11 +256,6 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
|||||||
|
|
||||||
writer.WriteHeader(http.StatusOK)
|
writer.WriteHeader(http.StatusOK)
|
||||||
} else if request.Method == "GET" || sessionId == "" { // stream-down, stream-one
|
} else if request.Method == "GET" || sessionId == "" { // stream-down, stream-one
|
||||||
responseFlusher, ok := writer.(http.Flusher)
|
|
||||||
if !ok {
|
|
||||||
panic("expected http.ResponseWriter to be an http.Flusher")
|
|
||||||
}
|
|
||||||
|
|
||||||
if sessionId != "" {
|
if sessionId != "" {
|
||||||
// after GET is done, the connection is finished. disable automatic
|
// after GET is done, the connection is finished. disable automatic
|
||||||
// session reaping, and handle it in defer
|
// session reaping, and handle it in defer
|
||||||
@@ -287,20 +276,18 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteHeader(http.StatusOK)
|
writer.WriteHeader(http.StatusOK)
|
||||||
|
writer.(http.Flusher).Flush()
|
||||||
|
|
||||||
responseFlusher.Flush()
|
httpSC := &httpServerConn{
|
||||||
|
Instance: done.New(),
|
||||||
downloadDone := done.New()
|
Reader: request.Body,
|
||||||
|
ResponseWriter: writer,
|
||||||
|
}
|
||||||
conn := splitConn{
|
conn := splitConn{
|
||||||
writer: &httpResponseBodyWriter{
|
writer: httpSC,
|
||||||
responseWriter: writer,
|
reader: httpSC,
|
||||||
downloadDone: downloadDone,
|
|
||||||
responseFlusher: responseFlusher,
|
|
||||||
},
|
|
||||||
reader: request.Body,
|
|
||||||
localAddr: h.localAddr,
|
|
||||||
remoteAddr: remoteAddr,
|
remoteAddr: remoteAddr,
|
||||||
|
localAddr: h.localAddr,
|
||||||
}
|
}
|
||||||
if sessionId != "" { // if not stream-one
|
if sessionId != "" { // if not stream-one
|
||||||
conn.reader = currentSession.uploadQueue
|
conn.reader = currentSession.uploadQueue
|
||||||
@@ -311,7 +298,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
|||||||
// "A ResponseWriter may not be used after [Handler.ServeHTTP] has returned."
|
// "A ResponseWriter may not be used after [Handler.ServeHTTP] has returned."
|
||||||
select {
|
select {
|
||||||
case <-request.Context().Done():
|
case <-request.Context().Done():
|
||||||
case <-downloadDone.Wait():
|
case <-httpSC.Wait():
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.Close()
|
conn.Close()
|
||||||
@@ -321,45 +308,30 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpRequestBodyReader struct {
|
type httpServerConn struct {
|
||||||
requestReader io.ReadCloser
|
|
||||||
uploadDone *done.Instance
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpRequestBodyReader) Read(b []byte) (int, error) {
|
|
||||||
return c.requestReader.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpRequestBodyReader) Close() error {
|
|
||||||
defer c.uploadDone.Close()
|
|
||||||
return c.requestReader.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpResponseBodyWriter struct {
|
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
responseWriter http.ResponseWriter
|
*done.Instance
|
||||||
responseFlusher http.Flusher
|
io.Reader // no need to Close request.Body
|
||||||
downloadDone *done.Instance
|
http.ResponseWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *httpResponseBodyWriter) Write(b []byte) (int, error) {
|
func (c *httpServerConn) Write(b []byte) (int, error) {
|
||||||
c.Lock()
|
c.Lock()
|
||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
if c.downloadDone.Done() {
|
if c.Done() {
|
||||||
return 0, io.ErrClosedPipe
|
return 0, io.ErrClosedPipe
|
||||||
}
|
}
|
||||||
n, err := c.responseWriter.Write(b)
|
n, err := c.ResponseWriter.Write(b)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.responseFlusher.Flush()
|
c.ResponseWriter.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *httpResponseBodyWriter) Close() error {
|
func (c *httpServerConn) Close() error {
|
||||||
c.Lock()
|
c.Lock()
|
||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
c.downloadDone.Close()
|
return c.Instance.Close()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
@@ -452,11 +424,15 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
|
|||||||
|
|
||||||
handler.localAddr = l.listener.Addr()
|
handler.localAddr = l.listener.Addr()
|
||||||
|
|
||||||
// h2cHandler can handle both plaintext HTTP/1.1 and h2c
|
// server can handle both plaintext HTTP/1.1 and h2c
|
||||||
|
protocols := new(http.Protocols)
|
||||||
|
protocols.SetHTTP1(true)
|
||||||
|
protocols.SetUnencryptedHTTP2(true)
|
||||||
l.server = http.Server{
|
l.server = http.Server{
|
||||||
Handler: h2c.NewHandler(handler, &http2.Server{}),
|
Handler: handler,
|
||||||
ReadHeaderTimeout: time.Second * 4,
|
ReadHeaderTimeout: time.Second * 4,
|
||||||
MaxHeaderBytes: 8192,
|
MaxHeaderBytes: 8192,
|
||||||
|
Protocols: protocols,
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
if err := l.server.Serve(l.listener); err != nil {
|
if err := l.server.Serve(l.listener); err != nil {
|
||||||
|
@@ -3,10 +3,8 @@ package splithttp_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
gotls "crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
gonet "net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -23,7 +21,6 @@ import (
|
|||||||
. "github.com/xtls/xray-core/transport/internet/splithttp"
|
. "github.com/xtls/xray-core/transport/internet/splithttp"
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
"golang.org/x/net/http2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_ListenXHAndDial(t *testing.T) {
|
func Test_ListenXHAndDial(t *testing.T) {
|
||||||
@@ -201,17 +198,11 @@ func Test_ListenXHAndDial_H2C(t *testing.T) {
|
|||||||
common.Must(err)
|
common.Must(err)
|
||||||
defer listen.Close()
|
defer listen.Close()
|
||||||
|
|
||||||
|
protocols := new(http.Protocols)
|
||||||
|
protocols.SetUnencryptedHTTP2(true)
|
||||||
client := http.Client{
|
client := http.Client{
|
||||||
Transport: &http2.Transport{
|
Transport: &http.Transport{
|
||||||
// So http2.Transport doesn't complain the URL scheme isn't 'https'
|
Protocols: protocols,
|
||||||
AllowHTTP: true,
|
|
||||||
// even with AllowHTTP, http2.Transport will attempt to establish
|
|
||||||
// the connection using DialTLSContext. Disable TLS with custom
|
|
||||||
// dial context.
|
|
||||||
DialTLSContext: func(ctx context.Context, network, addr string, cfg *gotls.Config) (gonet.Conn, error) {
|
|
||||||
var d gonet.Dialer
|
|
||||||
return d.DialContext(ctx, network, addr)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,6 +20,7 @@ type Packet struct {
|
|||||||
|
|
||||||
type uploadQueue struct {
|
type uploadQueue struct {
|
||||||
reader io.ReadCloser
|
reader io.ReadCloser
|
||||||
|
nomore bool
|
||||||
pushedPackets chan Packet
|
pushedPackets chan Packet
|
||||||
writeCloseMutex sync.Mutex
|
writeCloseMutex sync.Mutex
|
||||||
heap uploadHeap
|
heap uploadHeap
|
||||||
@@ -42,19 +43,15 @@ func (h *uploadQueue) Push(p Packet) error {
|
|||||||
h.writeCloseMutex.Lock()
|
h.writeCloseMutex.Lock()
|
||||||
defer h.writeCloseMutex.Unlock()
|
defer h.writeCloseMutex.Unlock()
|
||||||
|
|
||||||
runtime.Gosched()
|
|
||||||
if h.reader != nil && p.Reader != nil {
|
|
||||||
p.Reader.Close()
|
|
||||||
return errors.New("h.reader already exists")
|
|
||||||
}
|
|
||||||
|
|
||||||
if h.closed {
|
if h.closed {
|
||||||
if p.Reader != nil {
|
|
||||||
p.Reader.Close()
|
|
||||||
}
|
|
||||||
return errors.New("packet queue closed")
|
return errors.New("packet queue closed")
|
||||||
}
|
}
|
||||||
|
if h.nomore {
|
||||||
|
return errors.New("h.reader already exists")
|
||||||
|
}
|
||||||
|
if p.Reader != nil {
|
||||||
|
h.nomore = true
|
||||||
|
}
|
||||||
h.pushedPackets <- p
|
h.pushedPackets <- p
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -65,9 +62,20 @@ func (h *uploadQueue) Close() error {
|
|||||||
|
|
||||||
if !h.closed {
|
if !h.closed {
|
||||||
h.closed = true
|
h.closed = true
|
||||||
|
runtime.Gosched() // hope Read() gets the packet
|
||||||
|
f:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case p := <-h.pushedPackets:
|
||||||
|
if p.Reader != nil {
|
||||||
|
h.reader = p.Reader
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break f
|
||||||
|
}
|
||||||
|
}
|
||||||
close(h.pushedPackets)
|
close(h.pushedPackets)
|
||||||
}
|
}
|
||||||
runtime.Gosched()
|
|
||||||
if h.reader != nil {
|
if h.reader != nil {
|
||||||
return h.reader.Close()
|
return h.reader.Close()
|
||||||
}
|
}
|
||||||
|
@@ -44,32 +44,32 @@ func getControlFunc(ctx context.Context, sockopt *SocketConfig, controllers []co
|
|||||||
// For some reason, other component of ray will assume the listener is a TCP listener and have valid remote address.
|
// For some reason, other component of ray will assume the listener is a TCP listener and have valid remote address.
|
||||||
// But in fact it doesn't. So we need to wrap the listener to make it return 0.0.0.0(unspecified) as remote address.
|
// But in fact it doesn't. So we need to wrap the listener to make it return 0.0.0.0(unspecified) as remote address.
|
||||||
// If other issues encountered, we should able to fix it here.
|
// If other issues encountered, we should able to fix it here.
|
||||||
type listenUDSWrapper struct {
|
type UnixListenerWrapper struct {
|
||||||
net.Listener
|
*net.UnixListener
|
||||||
locker *FileLocker
|
locker *FileLocker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *listenUDSWrapper) Accept() (net.Conn, error) {
|
func (l *UnixListenerWrapper) Accept() (net.Conn, error) {
|
||||||
conn, err := l.Listener.Accept()
|
conn, err := l.UnixListener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &UDSWrapperConn{Conn: conn}, nil
|
return &UnixConnWrapper{UnixConn: conn.(*net.UnixConn)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *listenUDSWrapper) Close() error {
|
func (l *UnixListenerWrapper) Close() error {
|
||||||
if l.locker != nil {
|
if l.locker != nil {
|
||||||
l.locker.Release()
|
l.locker.Release()
|
||||||
l.locker = nil
|
l.locker = nil
|
||||||
}
|
}
|
||||||
return l.Listener.Close()
|
return l.UnixListener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
type UDSWrapperConn struct {
|
type UnixConnWrapper struct {
|
||||||
net.Conn
|
*net.UnixConn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *UDSWrapperConn) RemoteAddr() net.Addr {
|
func (conn *UnixConnWrapper) RemoteAddr() net.Addr {
|
||||||
return &net.TCPAddr{
|
return &net.TCPAddr{
|
||||||
IP: []byte{0, 0, 0, 0},
|
IP: []byte{0, 0, 0, 0},
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *S
|
|||||||
locker.Release()
|
locker.Release()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
l = &listenUDSWrapper{Listener: l, locker: locker}
|
l = &UnixListenerWrapper{UnixListener: l.(*net.UnixListener), locker: locker}
|
||||||
if filePerm == nil {
|
if filePerm == nil {
|
||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
@@ -444,6 +444,12 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
|||||||
config.KeyLogWriter = writer
|
config.KeyLogWriter = writer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(c.EchConfigList) > 0 || len(c.EchKeySets) > 0 {
|
||||||
|
err := ApplyECH(c, config)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(context.Background(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
@@ -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"`
|
||||||
@@ -217,6 +217,8 @@ type Config struct {
|
|||||||
// @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried.
|
// @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried.
|
||||||
// @Critical
|
// @Critical
|
||||||
VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"`
|
VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"`
|
||||||
|
EchConfigList string `protobuf:"bytes,18,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"`
|
||||||
|
EchKeySets []byte `protobuf:"bytes,19,opt,name=ech_key_sets,json=echKeySets,proto3" json:"ech_key_sets,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
func (x *Config) Reset() {
|
||||||
@@ -361,6 +363,20 @@ func (x *Config) GetVerifyPeerCertInNames() []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetEchConfigList() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.EchConfigList
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetEchKeySets() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.EchKeySets
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var File_transport_internet_tls_config_proto protoreflect.FileDescriptor
|
var File_transport_internet_tls_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
||||||
@@ -392,7 +408,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
|||||||
0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a,
|
0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a,
|
||||||
0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46,
|
0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46,
|
||||||
0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59,
|
0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59,
|
||||||
0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0x9a, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
|
0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xe4, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
|
||||||
0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73,
|
0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73,
|
||||||
0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c,
|
0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c,
|
||||||
0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65,
|
0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65,
|
||||||
@@ -442,15 +458,19 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
|||||||
0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f,
|
0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f,
|
||||||
0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15,
|
0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15,
|
||||||
0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e,
|
0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e,
|
||||||
0x4e, 0x61, 0x6d, 0x65, 0x73, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61,
|
0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x6e,
|
||||||
0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
|
0x66, 0x69, 0x67, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
|
||||||
0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68,
|
0x65, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x20, 0x0a,
|
||||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79,
|
0x0c, 0x65, 0x63, 0x68, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x73, 0x18, 0x13, 0x20,
|
||||||
0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f,
|
0x01, 0x28, 0x0c, 0x52, 0x0a, 0x65, 0x63, 0x68, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x74, 0x73, 0x42,
|
||||||
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58,
|
0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e,
|
||||||
0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e,
|
0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74,
|
||||||
0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||||
0x6f, 0x33,
|
0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f,
|
||||||
|
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
||||||
|
0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72,
|
||||||
|
0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
|
||||||
|
0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@@ -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;
|
||||||
@@ -91,4 +91,8 @@ message Config {
|
|||||||
@Critical
|
@Critical
|
||||||
*/
|
*/
|
||||||
repeated string verify_peer_cert_in_names = 17;
|
repeated string verify_peer_cert_in_names = 17;
|
||||||
|
|
||||||
|
string ech_config_list = 18;
|
||||||
|
|
||||||
|
bytes ech_key_sets = 19;
|
||||||
}
|
}
|
||||||
|
255
transport/internet/tls/ech.go
Normal file
255
transport/internet/tls/ech.go
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/OmarTariq612/goech"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ApplyECH(c *Config, config *tls.Config) error {
|
||||||
|
var ECHConfig []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
nameToQuery := c.ServerName
|
||||||
|
var DNSServer string
|
||||||
|
|
||||||
|
// for client
|
||||||
|
if len(c.EchConfigList) != 0 {
|
||||||
|
// direct base64 config
|
||||||
|
if strings.HasPrefix(c.EchConfigList, "base64") {
|
||||||
|
Base64ECHConfigList := c.EchConfigList[len("base64://"):]
|
||||||
|
ECHConfigList, err := goech.ECHConfigListFromBase64(Base64ECHConfigList)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Failed to unmarshal ECHConfigList: ", err)
|
||||||
|
}
|
||||||
|
ECHConfig, _ = ECHConfigList.MarshalBinary()
|
||||||
|
} else { // query config from dns
|
||||||
|
parts := strings.Split(c.EchConfigList, "+")
|
||||||
|
if len(parts) == 2 {
|
||||||
|
// parse ECH DNS server in format of "example.com+https://1.1.1.1/dns-query"
|
||||||
|
nameToQuery = parts[0]
|
||||||
|
DNSServer = parts[1]
|
||||||
|
} else if len(parts) == 1 {
|
||||||
|
// normal format
|
||||||
|
DNSServer = parts[0]
|
||||||
|
} else {
|
||||||
|
return errors.New("Invalid ECH DNS server format: ", c.EchConfigList)
|
||||||
|
}
|
||||||
|
if nameToQuery == "" {
|
||||||
|
return errors.New("Using DNS for ECH Config needs serverName or use Server format example.com+https://1.1.1.1/dns-query")
|
||||||
|
}
|
||||||
|
ECHConfig, err = QueryRecord(nameToQuery, DNSServer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.EncryptedClientHelloConfigList = ECHConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// for server
|
||||||
|
if len(c.EchKeySets) != 0 {
|
||||||
|
var keys []tls.EncryptedClientHelloKey
|
||||||
|
KeySets, err := goech.UnmarshalECHKeySetList(c.EchKeySets)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Failed to unmarshal ECHKeySetList: ", err)
|
||||||
|
}
|
||||||
|
for idx, keySet := range KeySets {
|
||||||
|
ECHConfig, err := keySet.ECHConfig.MarshalBinary()
|
||||||
|
ECHPrivateKey, err := keySet.PrivateKey.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Failed to marshal ECHKey in index: ", idx, "with err: ", err)
|
||||||
|
}
|
||||||
|
keys = append(keys, tls.EncryptedClientHelloKey{
|
||||||
|
Config: ECHConfig,
|
||||||
|
PrivateKey: ECHPrivateKey})
|
||||||
|
}
|
||||||
|
config.EncryptedClientHelloKeys = keys
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type record struct {
|
||||||
|
echConfig []byte
|
||||||
|
expire time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
dnsCache sync.Map
|
||||||
|
// global Lock? I'm not sure if this needs finer grained locks.
|
||||||
|
// If we do this, we will need to nest another layer of struct
|
||||||
|
updating sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueryRecord returns the ECH config for given domain.
|
||||||
|
// If the record is not in cache or expired, it will query the DNS server and update the cache.
|
||||||
|
func QueryRecord(domain string, server string) ([]byte, error) {
|
||||||
|
val, found := dnsCache.Load(domain)
|
||||||
|
rec, _ := val.(record)
|
||||||
|
if found && rec.expire.After(time.Now()) {
|
||||||
|
errors.LogDebug(context.Background(), "Cache hit for domain: ", domain)
|
||||||
|
return rec.echConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
updating.Lock()
|
||||||
|
defer updating.Unlock()
|
||||||
|
// Try to get cache again after lock, in case another goroutine has updated it
|
||||||
|
// This might happen when the core tring is just stared and multiple goroutines are trying to query the same domain
|
||||||
|
val, found = dnsCache.Load(domain)
|
||||||
|
rec, _ = val.(record)
|
||||||
|
if found && rec.expire.After(time.Now()) {
|
||||||
|
errors.LogDebug(context.Background(), "ECH Config cache hit for domain: ", domain, " after trying to get update lock")
|
||||||
|
return rec.echConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query ECH config from DNS server
|
||||||
|
errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server)
|
||||||
|
echConfig, ttl, err := dnsQuery(server, domain)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set minimum TTL to 600 seconds
|
||||||
|
if ttl < 600 {
|
||||||
|
ttl = 600
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cache
|
||||||
|
newRecored := record{
|
||||||
|
echConfig: echConfig,
|
||||||
|
expire: time.Now().Add(time.Second * time.Duration(ttl)),
|
||||||
|
}
|
||||||
|
dnsCache.Store(domain, newRecored)
|
||||||
|
return echConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dnsQuery is the real func for sending type65 query for given domain to given DNS server.
|
||||||
|
// return ECH config, TTL and error
|
||||||
|
func dnsQuery(server string, domain string) ([]byte, uint32, error) {
|
||||||
|
if server == "xray" {
|
||||||
|
HTTPSRecord, err := internet.LookupHTTPS(domain)
|
||||||
|
if err !=nil {
|
||||||
|
return []byte{}, 0, errors.New("failed to lookup HTTPS record with xray internal DNS: ", err)
|
||||||
|
}
|
||||||
|
ECH := HTTPSRecord["ech"]
|
||||||
|
if ECH == "" {
|
||||||
|
return []byte{}, 0, errors.New("no ech record found")
|
||||||
|
}
|
||||||
|
Base64echConfigList, err := goech.ECHConfigListFromBase64(ECH)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, errors.New("failed to unmarshal ECHConfigList: ", err)
|
||||||
|
}
|
||||||
|
echConfigList, _ := Base64echConfigList.MarshalBinary()
|
||||||
|
return echConfigList, 600, nil
|
||||||
|
}
|
||||||
|
m := new(dns.Msg)
|
||||||
|
var dnsResolve []byte
|
||||||
|
m.SetQuestion(dns.Fqdn(domain), dns.TypeHTTPS)
|
||||||
|
// for DOH server
|
||||||
|
if strings.HasPrefix(server, "https://") {
|
||||||
|
// always 0 in DOH
|
||||||
|
m.Id = 0
|
||||||
|
msg, err := m.Pack()
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
// All traffic sent by core should via xray's internet.DialSystem
|
||||||
|
// This involves the behavior of some Android VPN GUI clients
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
Transport: tr,
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", server, bytes.NewReader(msg))
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/dns-message")
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return []byte{}, 0, errors.New("query failed with response code:", resp.StatusCode)
|
||||||
|
}
|
||||||
|
dnsResolve = respBody
|
||||||
|
} else if strings.HasPrefix(server, "udp://") { // for classic udp dns server
|
||||||
|
udpServerAddr := server[len("udp://"):]
|
||||||
|
// default port 53 if not specified
|
||||||
|
if !strings.Contains(udpServerAddr, ":") {
|
||||||
|
udpServerAddr = udpServerAddr + ":53"
|
||||||
|
}
|
||||||
|
dest, err := net.ParseDestination("udp" + ":" + udpServerAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, errors.New("failed to parse udp dns server ", udpServerAddr, " for ECH: ", err)
|
||||||
|
}
|
||||||
|
dnsTimeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
// use xray's internet.DialSystem as mentioned above
|
||||||
|
conn, err := internet.DialSystem(dnsTimeoutCtx, dest, nil)
|
||||||
|
defer conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
msg, err := m.Pack()
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
conn.Write(msg)
|
||||||
|
udpResponse := make([]byte, 512)
|
||||||
|
_, err = conn.Read(udpResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
dnsResolve = udpResponse
|
||||||
|
}
|
||||||
|
respMsg := new(dns.Msg)
|
||||||
|
err := respMsg.Unpack(dnsResolve)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, errors.New("failed to unpack dns response for ECH: ", err)
|
||||||
|
}
|
||||||
|
if len(respMsg.Answer) > 0 {
|
||||||
|
for _, answer := range respMsg.Answer {
|
||||||
|
if https, ok := answer.(*dns.HTTPS); ok && https.Hdr.Name == dns.Fqdn(domain) {
|
||||||
|
for _, v := range https.Value {
|
||||||
|
if echConfig, ok := v.(*dns.SVCBECHConfig); ok {
|
||||||
|
errors.LogDebug(context.Background(), "Get ECH config:", echConfig.String(), " TTL:", respMsg.Answer[0].Header().Ttl)
|
||||||
|
return echConfig.ECH, answer.Header().Ttl, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []byte{}, 0, errors.New("no ech record found")
|
||||||
|
}
|
@@ -151,10 +151,14 @@ func init() {
|
|||||||
weights := utls.DefaultWeights
|
weights := utls.DefaultWeights
|
||||||
weights.TLSVersMax_Set_VersionTLS13 = 1
|
weights.TLSVersMax_Set_VersionTLS13 = 1
|
||||||
weights.FirstKeyShare_Set_CurveP256 = 0
|
weights.FirstKeyShare_Set_CurveP256 = 0
|
||||||
randomized := utls.HelloRandomized
|
randomized := utls.HelloRandomizedALPN
|
||||||
randomized.Seed, _ = utls.NewPRNGSeed()
|
randomized.Seed, _ = utls.NewPRNGSeed()
|
||||||
randomized.Weights = &weights
|
randomized.Weights = &weights
|
||||||
|
randomizednoalpn := utls.HelloRandomizedNoALPN
|
||||||
|
randomizednoalpn.Seed, _ = utls.NewPRNGSeed()
|
||||||
|
randomizednoalpn.Weights = &weights
|
||||||
PresetFingerprints["randomized"] = &randomized
|
PresetFingerprints["randomized"] = &randomized
|
||||||
|
PresetFingerprints["randomizednoalpn"] = &randomizednoalpn
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFingerprint(name string) (fingerprint *utls.ClientHelloID) {
|
func GetFingerprint(name string) (fingerprint *utls.ClientHelloID) {
|
||||||
@@ -175,17 +179,18 @@ func GetFingerprint(name string) (fingerprint *utls.ClientHelloID) {
|
|||||||
|
|
||||||
var PresetFingerprints = map[string]*utls.ClientHelloID{
|
var PresetFingerprints = map[string]*utls.ClientHelloID{
|
||||||
// Recommended preset options in GUI clients
|
// Recommended preset options in GUI clients
|
||||||
"chrome": &utls.HelloChrome_Auto,
|
"chrome": &utls.HelloChrome_Auto,
|
||||||
"firefox": &utls.HelloFirefox_Auto,
|
"firefox": &utls.HelloFirefox_Auto,
|
||||||
"safari": &utls.HelloSafari_Auto,
|
"safari": &utls.HelloSafari_Auto,
|
||||||
"ios": &utls.HelloIOS_Auto,
|
"ios": &utls.HelloIOS_Auto,
|
||||||
"android": &utls.HelloAndroid_11_OkHttp,
|
"android": &utls.HelloAndroid_11_OkHttp,
|
||||||
"edge": &utls.HelloEdge_Auto,
|
"edge": &utls.HelloEdge_Auto,
|
||||||
"360": &utls.Hello360_Auto,
|
"360": &utls.Hello360_Auto,
|
||||||
"qq": &utls.HelloQQ_Auto,
|
"qq": &utls.HelloQQ_Auto,
|
||||||
"random": nil,
|
"random": nil,
|
||||||
"randomized": nil,
|
"randomized": nil,
|
||||||
"unsafe": nil,
|
"randomizednoalpn": nil,
|
||||||
|
"unsafe": nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
var ModernFingerprints = map[string]*utls.ClientHelloID{
|
var ModernFingerprints = map[string]*utls.ClientHelloID{
|
||||||
@@ -193,12 +198,14 @@ var ModernFingerprints = map[string]*utls.ClientHelloID{
|
|||||||
"hellofirefox_99": &utls.HelloFirefox_99,
|
"hellofirefox_99": &utls.HelloFirefox_99,
|
||||||
"hellofirefox_102": &utls.HelloFirefox_102,
|
"hellofirefox_102": &utls.HelloFirefox_102,
|
||||||
"hellofirefox_105": &utls.HelloFirefox_105,
|
"hellofirefox_105": &utls.HelloFirefox_105,
|
||||||
|
"hellofirefox_120": &utls.HelloFirefox_120,
|
||||||
"hellochrome_83": &utls.HelloChrome_83,
|
"hellochrome_83": &utls.HelloChrome_83,
|
||||||
"hellochrome_87": &utls.HelloChrome_87,
|
"hellochrome_87": &utls.HelloChrome_87,
|
||||||
"hellochrome_96": &utls.HelloChrome_96,
|
"hellochrome_96": &utls.HelloChrome_96,
|
||||||
"hellochrome_100": &utls.HelloChrome_100,
|
"hellochrome_100": &utls.HelloChrome_100,
|
||||||
"hellochrome_102": &utls.HelloChrome_102,
|
"hellochrome_102": &utls.HelloChrome_102,
|
||||||
"hellochrome_106_shuffle": &utls.HelloChrome_106_Shuffle,
|
"hellochrome_106_shuffle": &utls.HelloChrome_106_Shuffle,
|
||||||
|
"hellochrome_120": &utls.HelloChrome_120,
|
||||||
"helloios_13": &utls.HelloIOS_13,
|
"helloios_13": &utls.HelloIOS_13,
|
||||||
"helloios_14": &utls.HelloIOS_14,
|
"helloios_14": &utls.HelloIOS_14,
|
||||||
"helloedge_85": &utls.HelloEdge_85,
|
"helloedge_85": &utls.HelloEdge_85,
|
||||||
|
Reference in New Issue
Block a user