LjhAUMEM
2026-10-15 22:41:19 +00:00
committed by GitHub
parent 0888beb38a
commit 10879a4a8b
8 changed files with 503 additions and 107 deletions
+48 -13
View File
@@ -8,6 +8,8 @@ package xdns
import (
_ "github.com/xtls/xray-core/common/serial"
internet "github.com/xtls/xray-core/transport/internet"
tls "github.com/xtls/xray-core/transport/internet/tls"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
@@ -102,6 +104,9 @@ type ResolverProto struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"`
Worker int32 `protobuf:"varint,3,opt,name=worker,proto3" json:"worker,omitempty"`
TlsConfig *tls.Config `protobuf:"bytes,4,opt,name=tls_config,json=tlsConfig,proto3" json:"tls_config,omitempty"`
QuicConfig *internet.QuicParams `protobuf:"bytes,5,opt,name=quic_config,json=quicConfig,proto3" json:"quic_config,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -150,6 +155,27 @@ func (x *ResolverProto) GetAddr() string {
return ""
}
func (x *ResolverProto) GetWorker() int32 {
if x != nil {
return x.Worker
}
return 0
}
func (x *ResolverProto) GetTlsConfig() *tls.Config {
if x != nil {
return x.TlsConfig
}
return nil
}
func (x *ResolverProto) GetQuicConfig() *internet.QuicParams {
if x != nil {
return x.QuicConfig
}
return nil
}
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
Domains []*DomainProto `protobuf:"bytes,1,rep,name=domains,proto3" json:"domains,omitempty"`
@@ -214,17 +240,22 @@ var File_transport_internet_finalmask_xdns_config_proto protoreflect.FileDescrip
const file_transport_internet_finalmask_xdns_config_proto_rawDesc = "" +
"\n" +
".transport/internet/finalmask/xdns/config.proto\x12&xray.transport.internet.finalmask.xdns\x1a!common/serial/typed_message.proto\"\x8b\x01\n" +
".transport/internet/finalmask/xdns/config.proto\x12&xray.transport.internet.finalmask.xdns\x1a!common/serial/typed_message.proto\x1a#transport/internet/tls/config.proto\x1a\x1ftransport/internet/config.proto\"\x8b\x01\n" +
"\vDomainProto\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1b\n" +
"\tlen_limit\x18\x02 \x01(\x05R\blenLimit\x12\x1f\n" +
"\vlabel_limit\x18\x03 \x01(\x05R\n" +
"labelLimit\x12\x14\n" +
"\x05types\x18\x04 \x03(\x05R\x05types\x12\x14\n" +
"\x05edns0\x18\x05 \x01(\x05R\x05edns0\"7\n" +
"\x05edns0\x18\x05 \x01(\x05R\x05edns0\"\xd9\x01\n" +
"\rResolverProto\x12\x12\n" +
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
"\x04addr\x18\x02 \x01(\tR\x04addr\"\xcb\x01\n" +
"\x04addr\x18\x02 \x01(\tR\x04addr\x12\x16\n" +
"\x06worker\x18\x03 \x01(\x05R\x06worker\x12B\n" +
"\n" +
"tls_config\x18\x04 \x01(\v2#.xray.transport.internet.tls.ConfigR\ttlsConfig\x12D\n" +
"\vquic_config\x18\x05 \x01(\v2#.xray.transport.internet.QuicParamsR\n" +
"quicConfig\"\xcb\x01\n" +
"\x06Config\x12M\n" +
"\adomains\x18\x01 \x03(\v23.xray.transport.internet.finalmask.xdns.DomainProtoR\adomains\x12S\n" +
"\tresolvers\x18\x02 \x03(\v25.xray.transport.internet.finalmask.xdns.ResolverProtoR\tresolvers\x12\x1d\n" +
@@ -246,18 +277,22 @@ func file_transport_internet_finalmask_xdns_config_proto_rawDescGZIP() []byte {
var file_transport_internet_finalmask_xdns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_transport_internet_finalmask_xdns_config_proto_goTypes = []any{
(*DomainProto)(nil), // 0: xray.transport.internet.finalmask.xdns.DomainProto
(*ResolverProto)(nil), // 1: xray.transport.internet.finalmask.xdns.ResolverProto
(*Config)(nil), // 2: xray.transport.internet.finalmask.xdns.Config
(*DomainProto)(nil), // 0: xray.transport.internet.finalmask.xdns.DomainProto
(*ResolverProto)(nil), // 1: xray.transport.internet.finalmask.xdns.ResolverProto
(*Config)(nil), // 2: xray.transport.internet.finalmask.xdns.Config
(*tls.Config)(nil), // 3: xray.transport.internet.tls.Config
(*internet.QuicParams)(nil), // 4: xray.transport.internet.QuicParams
}
var file_transport_internet_finalmask_xdns_config_proto_depIdxs = []int32{
0, // 0: xray.transport.internet.finalmask.xdns.Config.domains:type_name -> xray.transport.internet.finalmask.xdns.DomainProto
1, // 1: xray.transport.internet.finalmask.xdns.Config.resolvers:type_name -> xray.transport.internet.finalmask.xdns.ResolverProto
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
3, // 0: xray.transport.internet.finalmask.xdns.ResolverProto.tls_config:type_name -> xray.transport.internet.tls.Config
4, // 1: xray.transport.internet.finalmask.xdns.ResolverProto.quic_config:type_name -> xray.transport.internet.QuicParams
0, // 2: xray.transport.internet.finalmask.xdns.Config.domains:type_name -> xray.transport.internet.finalmask.xdns.DomainProto
1, // 3: xray.transport.internet.finalmask.xdns.Config.resolvers:type_name -> xray.transport.internet.finalmask.xdns.ResolverProto
4, // [4:4] is the sub-list for method output_type
4, // [4:4] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
}
func init() { file_transport_internet_finalmask_xdns_config_proto_init() }
@@ -7,6 +7,8 @@ option java_package = "com.xray.transport.internet.finalmask.xdns";
option java_multiple_files = true;
import "common/serial/typed_message.proto";
import "transport/internet/tls/config.proto";
import "transport/internet/config.proto";
message DomainProto {
string name = 1;
@@ -19,6 +21,9 @@ message DomainProto {
message ResolverProto {
string type = 1;
string addr = 2;
int32 worker = 3;
xray.transport.internet.tls.Config tls_config = 4;
xray.transport.internet.QuicParams quic_config = 5;
}
message Config {
@@ -20,6 +20,8 @@ func NewResolver(config *ResolverProto, dialer *finalmask.Dialer) (Resolver, err
return NewTCPResolver(config, dialer)
case "udp":
return NewUDPResolver(config, dialer)
case "https":
return NewDOHResolver(config, dialer)
default:
return nil, errors.New("unknown type")
}
@@ -0,0 +1,301 @@
package xdns
import (
"bytes"
"context"
gotls "crypto/tls"
"io"
"net/http"
"runtime"
"strconv"
"sync"
"time"
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
"github.com/xtls/xray-core/transport/internet/tls"
)
type DOHResolver struct {
ctx context.Context
cancel func()
client *http.Client
url string
time time.Time
udpAddr *net.UDPAddr
readCh chan []byte
sendCh chan []byte
closeCh chan struct{}
wg sync.WaitGroup
mu sync.Mutex
}
func NewDOHResolver(config *ResolverProto, dialer *finalmask.Dialer) (Resolver, error) {
if config.Worker < 1 {
return nil, errors.New("invalid worker")
}
if config.TlsConfig == nil {
return nil, errors.New("empty tls config")
}
ctx, cancel := context.WithCancel(context.Background())
var tr http.RoundTripper
var setaddr func(addr net.Addr)
switch {
case len(config.TlsConfig.NextProtocol) == 1 && config.TlsConfig.NextProtocol[0] == "h3":
quicParams := config.QuicConfig
if quicParams == nil {
quicParams = &internet.QuicParams{}
}
quicConfig := &quic.Config{
InitialStreamReceiveWindow: quicParams.InitStreamReceiveWindow,
MaxStreamReceiveWindow: quicParams.MaxStreamReceiveWindow,
InitialConnectionReceiveWindow: quicParams.InitConnReceiveWindow,
MaxConnectionReceiveWindow: quicParams.MaxConnReceiveWindow,
MaxIdleTimeout: time.Duration(quicParams.MaxIdleTimeout) * time.Second,
KeepAlivePeriod: time.Duration(quicParams.KeepAlivePeriod) * time.Second,
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery || (runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin"),
ChromeParrot: !quicParams.DisableChromeParrot,
}
tr = &http3.Transport{
TLSClientConfig: config.TlsConfig.GetTLSConfig(),
QUICConfig: quicConfig,
Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (*quic.Conn, error) {
dest, err := net.ParseDestination("udp:" + addr)
if err != nil {
return nil, err
}
conn, err := dialer.DialUDP(dest)
if err != nil {
return nil, err
}
setaddr(conn.RemoteAddr())
tr := &quic.Transport{Conn: conn.(*net.PacketConnWrapper).PacketConn, DisableGSO: quicParams.DisableGSO}
if !quicParams.DisableChromeParrot {
tr.ConnectionIDGenerator = quic.ZeroLengthConnectionIDGenerator{}
tlsCfg.GetCertificate = nil
}
qc, err := tr.Dial(ctx, conn.RemoteAddr(), tlsCfg, cfg)
if err != nil {
return nil, err
}
context.AfterFunc(qc.Context(), func() { tr.Close(); conn.Close() })
switch quicParams.Congestion {
case "", "reno":
case "bbr":
congestion.UseBBR(qc, bbr.Profile(quicParams.BbrProfile))
case "force-brutal":
congestion.UseBrutal(qc, quicParams.BrutalUp, quicParams.BrutalDisableLossCompensation)
default:
panic(quicParams.Congestion)
}
return qc, nil
},
}
default:
tlsConfig := config.TlsConfig.GetTLSConfig()
tr = &http.Transport{
ForceAttemptHTTP2: true,
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
dest, err := net.ParseDestination("tcp:" + addr)
if err != nil {
return nil, err
}
conn, err := dialer.DialTCP(dest)
if err != nil {
return nil, err
}
setaddr(conn.RemoteAddr())
var tlsConn net.Conn
if fingerprint := tls.GetFingerprint(config.TlsConfig.Fingerprint); fingerprint != nil {
tlsConn = tls.UClient(conn, tlsConfig, fingerprint)
err = tlsConn.(*tls.UConn).HandshakeContext(ctx)
} else {
tlsConn = gotls.Client(conn, tlsConfig)
err = tlsConn.(*gotls.Conn).HandshakeContext(ctx)
}
if err != nil {
_ = conn.Close()
return nil, err
}
return tlsConn, nil
},
}
}
client := &http.Client{
Transport: tr,
}
r := &DOHResolver{
ctx: ctx,
cancel: cancel,
client: client,
url: config.Addr,
udpAddr: &net.UDPAddr{IP: []byte{0, 0, 0, 0}},
readCh: make(chan []byte),
sendCh: make(chan []byte),
closeCh: make(chan struct{}),
}
for range config.Worker {
r.wg.Add(1)
go r.worker()
}
setaddr = r.setaddr
return r, nil
}
func (r *DOHResolver) closed() bool {
select {
case <-r.closeCh:
return true
default:
return false
}
}
func (r *DOHResolver) setaddr(addr net.Addr) {
r.mu.Lock()
defer r.mu.Unlock()
switch v := addr.(type) {
case *net.TCPAddr:
r.udpAddr = &net.UDPAddr{IP: v.IP, Port: v.Port}
case *net.UDPAddr:
r.udpAddr = v
default:
panic(v)
}
}
func (r *DOHResolver) cooldown(t time.Time) bool {
r.mu.Lock()
defer r.mu.Unlock()
if t == (time.Time{}) {
return time.Now().Before(r.time)
}
if t.After(r.time) {
r.time = t
}
return true
}
func (r *DOHResolver) do(p []byte) error {
req := common.Must2(http.NewRequestWithContext(r.ctx, "POST", r.url, bytes.NewReader(p)))
req.Header.Set("Accept", "application/dns-message")
req.Header.Set("Content-Type", "application/dns-message")
req.Header.Set("User-Agent", "")
resp, err := r.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
p := pool4K.Get().([]byte)
off := 0
for {
n, err := resp.Body.Read(p[off:])
if n > 0 {
off += n
}
if off == len(p) || err != nil {
if err != io.EOF {
pool4K.Put(p[:cap(p)])
return err
}
break
}
}
select {
case <-r.closeCh:
pool4K.Put(p[:cap(p)])
case r.readCh <- p[:off]:
}
return nil
case http.StatusTooManyRequests:
if value := resp.Header.Get("Retry-After"); value != "" {
if t, err := http.ParseTime(value); err == nil {
r.cooldown(t)
} else if i, err := strconv.ParseUint(value, 10, 32); err == nil {
r.cooldown(time.Now().Add(time.Duration(i) * time.Second))
}
}
}
return errors.New("code ", resp.StatusCode, " header ", resp.Header)
}
func (r *DOHResolver) worker() {
defer r.wg.Done()
var p []byte
for {
select {
case <-r.closeCh:
return
default:
select {
case <-r.closeCh:
return
case p = <-r.sendCh:
}
}
if r.cooldown(time.Time{}) {
continue
}
if err := r.do(p); err != nil {
errors.LogErrorInner(context.Background(), err, "do err")
}
}
}
func (r *DOHResolver) Addr() *net.UDPAddr {
r.mu.Lock()
defer r.mu.Unlock()
return r.udpAddr
}
func (r *DOHResolver) Read(p []byte) (n int, err error) {
packet, ok := <-r.readCh
if ok {
n = copy(p, packet)
pool4K.Put(packet[:cap(packet)])
return n, nil
}
return 0, io.ErrClosedPipe
}
func (r *DOHResolver) Send(p []byte) {
select {
case <-r.closeCh:
return
default:
b := make([]byte, len(p))
copy(b, p)
select {
case <-r.closeCh:
return
case r.sendCh <- b:
}
}
}
func (r *DOHResolver) Close() {
r.mu.Lock()
if r.closed() {
r.mu.Unlock()
return
}
close(r.closeCh)
r.mu.Unlock()
r.cancel()
r.wg.Wait()
r.client.CloseIdleConnections()
close(r.readCh)
// close(r.sendCh)
}
+1 -1
View File
@@ -14,7 +14,7 @@ import (
// grpcUtlsInfo contains the auth information for a TLS authenticated connection.
// It implements the AuthInfo interface.
type grpcUtlsInfo struct {
State utls.ConnectionState
State gotls.ConnectionState
credentials.CommonAuthInfo
// This API is experimental.
SPIFFEID *url.URL
+18
View File
@@ -154,6 +154,24 @@ func (c *UConn) NegotiatedProtocol() string {
return state.NegotiatedProtocol
}
func (c *UConn) ConnectionState() tls.ConnectionState {
s := c.UConn.ConnectionState()
var state tls.ConnectionState
state.HandshakeComplete = s.HandshakeComplete
state.Version = s.Version
state.NegotiatedProtocol = s.NegotiatedProtocol
state.DidResume = s.DidResume
state.ServerName = s.ServerName
state.CipherSuite = s.CipherSuite
state.PeerCertificates = s.PeerCertificates
state.VerifiedChains = s.VerifiedChains
state.SignedCertificateTimestamps = s.SignedCertificateTimestamps
state.OCSPResponse = s.OCSPResponse
state.TLSUnique = s.TLSUnique
state.ECHAccepted = s.ECHAccepted
return state
}
func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) net.Conn {
utlsConn := utls.UClient(c, copyConfig(config), *fingerprint)
return &UConn{UConn: utlsConn}