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
+122 -8
View File
@@ -9,12 +9,14 @@ import (
"fmt" "fmt"
"net/netip" "net/netip"
"net/url" "net/url"
"os"
"regexp" "regexp"
"strings" "strings"
googleuuid "github.com/google/uuid" googleuuid "github.com/google/uuid"
"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/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask/fragment" "github.com/xtls/xray-core/transport/internet/finalmask/fragment"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom" "github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm" "github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
@@ -28,6 +30,7 @@ import (
"github.com/xtls/xray-core/transport/internet/finalmask/xdns" "github.com/xtls/xray-core/transport/internet/finalmask/xdns"
"github.com/xtls/xray-core/transport/internet/finalmask/xicmp" "github.com/xtls/xray-core/transport/internet/finalmask/xicmp"
"github.com/xtls/xray-core/transport/internet/finalmask/xmc" "github.com/xtls/xray-core/transport/internet/finalmask/xmc"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
"github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/tls"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
@@ -799,7 +802,10 @@ type XDNSDomain struct {
} }
type XDNSResolver struct { type XDNSResolver struct {
Addrs []string `json:"addrs"` Addrs []string `json:"addrs"`
Worker int32 `json:"worker"`
TlsConfig *TLSConfig `json:"tlsConfig"`
QuicConfig *QuicParamsConfig `json:"quicConfig"`
} }
type XDNS struct { type XDNS struct {
@@ -834,6 +840,26 @@ func (c *XDNS) Build() (proto.Message, error) {
} }
} }
for i := range c.Resolvers { for i := range c.Resolvers {
worker := c.Resolvers[i].Worker
if worker == 0 {
worker = 32
}
var tlsConfig *tls.Config
var quicConfig *internet.QuicParams
if c.Resolvers[i].TlsConfig != nil {
proto, err := c.Resolvers[i].TlsConfig.Build()
if err != nil {
return nil, err
}
tlsConfig = proto.(*tls.Config)
}
if c.Resolvers[i].QuicConfig != nil {
proto, err := c.Resolvers[i].QuicConfig.Build()
if err != nil {
return nil, err
}
quicConfig = proto.(*internet.QuicParams)
}
for j := range c.Resolvers[i].Addrs { for j := range c.Resolvers[i].Addrs {
var u *url.URL var u *url.URL
var e error var e error
@@ -845,18 +871,21 @@ func (c *XDNS) Build() (proto.Message, error) {
if e != nil { if e != nil {
return nil, e return nil, e
} }
var addr string
switch u.Scheme { switch u.Scheme {
case "tcp", "udp": case "tcp", "udp":
host := u.Hostname()
port := u.Port()
if port == "" {
port = "53"
}
addr = net.JoinHostPort(host, port)
case "https":
addr = u.String()
default: default:
return nil, errors.New("invalid protocol") return nil, errors.New("invalid protocol")
} }
var host, port string resolvers = append(resolvers, &xdns.ResolverProto{Type: u.Scheme, Addr: addr, Worker: worker, TlsConfig: tlsConfig, QuicConfig: quicConfig})
host = u.Hostname()
port = u.Port()
if port == "" {
port = "53"
}
resolvers = append(resolvers, &xdns.ResolverProto{Type: u.Scheme, Addr: net.JoinHostPort(host, port)})
} }
} }
if c.ExtraPoll < 0 || c.ExtraPoll > 3 { if c.ExtraPoll < 0 || c.ExtraPoll > 3 {
@@ -1149,6 +1178,91 @@ type QuicParamsConfig struct {
DisableStatelessReset bool `json:"disableStatelessReset"` DisableStatelessReset bool `json:"disableStatelessReset"`
} }
func (config *QuicParamsConfig) Build() (proto.Message, error) {
profile := strings.ToLower(config.BbrProfile)
switch profile {
case "", string(bbr.ProfileConservative), string(bbr.ProfileStandard), string(bbr.ProfileAggressive):
if profile == "" {
profile = string(bbr.ProfileStandard)
}
default:
return nil, errors.New("unknown bbr profile")
}
up, err := config.BrutalUp.Bps()
if err != nil {
return nil, err
}
down, err := config.BrutalDown.Bps()
if err != nil {
return nil, err
}
if up > 0 && up < 65536 {
return nil, errors.New("BrutalUp must be at least 65536 bytes per second")
}
if down > 0 && down < 65536 {
return nil, errors.New("BrutalDown must be at least 65536 bytes per second")
}
config.Congestion = strings.ToLower(config.Congestion)
switch config.Congestion {
case "", "brutal", "reno", "bbr":
case "force-brutal":
if up == 0 {
return nil, errors.New("force-brutal requires up")
}
default:
return nil, errors.New("unknown congestion control: ", config.Congestion, ", valid values: reno, bbr, brutal, force-brutal")
}
if config.InitStreamReceiveWindow > 0 && config.InitStreamReceiveWindow < 16384 {
return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
}
if config.MaxStreamReceiveWindow > 0 && config.MaxStreamReceiveWindow < 16384 {
return nil, errors.New("MaxStreamReceiveWindow must be at least 16384")
}
if config.InitConnectionReceiveWindow > 0 && config.InitConnectionReceiveWindow < 16384 {
return nil, errors.New("InitConnectionReceiveWindow must be at least 16384")
}
if config.MaxConnectionReceiveWindow > 0 && config.MaxConnectionReceiveWindow < 16384 {
return nil, errors.New("MaxConnectionReceiveWindow must be at least 16384")
}
if config.MaxIdleTimeout != 0 && (config.MaxIdleTimeout < 4 || config.MaxIdleTimeout > 120) {
return nil, errors.New("MaxIdleTimeout must be between 4 and 120")
}
if config.KeepAlivePeriod != 0 && (config.KeepAlivePeriod < 2 || config.KeepAlivePeriod > 60) {
return nil, errors.New("KeepAlivePeriod must be between 2 and 60")
}
if config.MaxIncomingStreams != 0 && config.MaxIncomingStreams < 8 {
return nil, errors.New("MaxIncomingStreams must be at least 8")
}
if config.Debug {
os.Setenv("HYSTERIA_BBR_DEBUG", "true")
os.Setenv("HYSTERIA_BRUTAL_DEBUG", "true")
}
return &internet.QuicParams{
Congestion: config.Congestion,
BbrProfile: profile,
BrutalUp: up,
BrutalDown: down,
BrutalDisableLossCompensation: config.BrutalDisableLossCompensation,
InitStreamReceiveWindow: config.InitStreamReceiveWindow,
MaxStreamReceiveWindow: config.MaxStreamReceiveWindow,
InitConnReceiveWindow: config.InitConnectionReceiveWindow,
MaxConnReceiveWindow: config.MaxConnectionReceiveWindow,
MaxIdleTimeout: config.MaxIdleTimeout,
KeepAlivePeriod: config.KeepAlivePeriod,
DisablePathMtuDiscovery: config.DisablePathMTUDiscovery,
DisableChromeParrot: config.DisableChromeParrot,
DisableGSO: config.DisableGSO,
MaxIncomingStreams: config.MaxIncomingStreams,
DisableStatelessReset: config.DisableStatelessReset,
}, nil
}
type FinalMask struct { type FinalMask struct {
Tcp []Mask `json:"tcp"` Tcp []Mask `json:"tcp"`
Udp []Mask `json:"udp"` Udp []Mask `json:"udp"`
+6 -85
View File
@@ -1,13 +1,11 @@
package conf package conf
import ( import (
"os"
"strings" "strings"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
) )
type TransportProtocol string type TransportProtocol string
@@ -228,102 +226,25 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
if c.FinalMask != nil { if c.FinalMask != nil {
for _, mask := range c.FinalMask.Tcp { for _, mask := range c.FinalMask.Tcp {
u, err := mask.Build(true) proto, err := mask.Build(true)
if err != nil { if err != nil {
return nil, errors.New("failed to build mask with type ", mask.Type).Base(err) return nil, errors.New("failed to build mask with type ", mask.Type).Base(err)
} }
config.Tcpmasks = append(config.Tcpmasks, serial.ToTypedMessage(u)) config.Tcpmasks = append(config.Tcpmasks, serial.ToTypedMessage(proto))
} }
for _, mask := range c.FinalMask.Udp { for _, mask := range c.FinalMask.Udp {
u, err := mask.Build(false) proto, err := mask.Build(false)
if err != nil { if err != nil {
return nil, errors.New("failed to build mask with type ", mask.Type).Base(err) return nil, errors.New("failed to build mask with type ", mask.Type).Base(err)
} }
config.Udpmasks = append(config.Udpmasks, serial.ToTypedMessage(u)) config.Udpmasks = append(config.Udpmasks, serial.ToTypedMessage(proto))
} }
if c.FinalMask.QuicParams != nil { if c.FinalMask.QuicParams != nil {
profile := strings.ToLower(c.FinalMask.QuicParams.BbrProfile) proto, err := c.FinalMask.QuicParams.Build()
switch profile {
case "", string(bbr.ProfileConservative), string(bbr.ProfileStandard), string(bbr.ProfileAggressive):
if profile == "" {
profile = string(bbr.ProfileStandard)
}
default:
return nil, errors.New("unknown bbr profile")
}
up, err := c.FinalMask.QuicParams.BrutalUp.Bps()
if err != nil { if err != nil {
return nil, err return nil, err
} }
down, err := c.FinalMask.QuicParams.BrutalDown.Bps() config.QuicParams = proto.(*internet.QuicParams)
if err != nil {
return nil, err
}
if up > 0 && up < 65536 {
return nil, errors.New("BrutalUp must be at least 65536 bytes per second")
}
if down > 0 && down < 65536 {
return nil, errors.New("BrutalDown must be at least 65536 bytes per second")
}
c.FinalMask.QuicParams.Congestion = strings.ToLower(c.FinalMask.QuicParams.Congestion)
switch c.FinalMask.QuicParams.Congestion {
case "", "brutal", "reno", "bbr":
case "force-brutal":
if up == 0 {
return nil, errors.New("force-brutal requires up")
}
default:
return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: reno, bbr, brutal, force-brutal")
}
if c.FinalMask.QuicParams.InitStreamReceiveWindow > 0 && c.FinalMask.QuicParams.InitStreamReceiveWindow < 16384 {
return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
}
if c.FinalMask.QuicParams.MaxStreamReceiveWindow > 0 && c.FinalMask.QuicParams.MaxStreamReceiveWindow < 16384 {
return nil, errors.New("MaxStreamReceiveWindow must be at least 16384")
}
if c.FinalMask.QuicParams.InitConnectionReceiveWindow > 0 && c.FinalMask.QuicParams.InitConnectionReceiveWindow < 16384 {
return nil, errors.New("InitConnectionReceiveWindow must be at least 16384")
}
if c.FinalMask.QuicParams.MaxConnectionReceiveWindow > 0 && c.FinalMask.QuicParams.MaxConnectionReceiveWindow < 16384 {
return nil, errors.New("MaxConnectionReceiveWindow must be at least 16384")
}
if c.FinalMask.QuicParams.MaxIdleTimeout != 0 && (c.FinalMask.QuicParams.MaxIdleTimeout < 4 || c.FinalMask.QuicParams.MaxIdleTimeout > 120) {
return nil, errors.New("MaxIdleTimeout must be between 4 and 120")
}
if c.FinalMask.QuicParams.KeepAlivePeriod != 0 && (c.FinalMask.QuicParams.KeepAlivePeriod < 2 || c.FinalMask.QuicParams.KeepAlivePeriod > 60) {
return nil, errors.New("KeepAlivePeriod must be between 2 and 60")
}
if c.FinalMask.QuicParams.MaxIncomingStreams != 0 && c.FinalMask.QuicParams.MaxIncomingStreams < 8 {
return nil, errors.New("MaxIncomingStreams must be at least 8")
}
if c.FinalMask.QuicParams.Debug {
os.Setenv("HYSTERIA_BBR_DEBUG", "true")
os.Setenv("HYSTERIA_BRUTAL_DEBUG", "true")
}
config.QuicParams = &internet.QuicParams{
Congestion: c.FinalMask.QuicParams.Congestion,
BbrProfile: profile,
BrutalUp: up,
BrutalDown: down,
BrutalDisableLossCompensation: c.FinalMask.QuicParams.BrutalDisableLossCompensation,
InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,
MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow,
InitConnReceiveWindow: c.FinalMask.QuicParams.InitConnectionReceiveWindow,
MaxConnReceiveWindow: c.FinalMask.QuicParams.MaxConnectionReceiveWindow,
MaxIdleTimeout: c.FinalMask.QuicParams.MaxIdleTimeout,
KeepAlivePeriod: c.FinalMask.QuicParams.KeepAlivePeriod,
DisablePathMtuDiscovery: c.FinalMask.QuicParams.DisablePathMTUDiscovery,
DisableChromeParrot: c.FinalMask.QuicParams.DisableChromeParrot,
DisableGSO: c.FinalMask.QuicParams.DisableGSO,
MaxIncomingStreams: c.FinalMask.QuicParams.MaxIncomingStreams,
DisableStatelessReset: c.FinalMask.QuicParams.DisableStatelessReset,
}
} }
} }
+48 -13
View File
@@ -8,6 +8,8 @@ package xdns
import ( import (
_ "github.com/xtls/xray-core/common/serial" _ "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" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
@@ -102,6 +104,9 @@ type ResolverProto struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,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 unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -150,6 +155,27 @@ func (x *ResolverProto) GetAddr() string {
return "" 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 { type Config struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Domains []*DomainProto `protobuf:"bytes,1,rep,name=domains,proto3" json:"domains,omitempty"` 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 = "" + const file_transport_internet_finalmask_xdns_config_proto_rawDesc = "" +
"\n" + "\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" + "\vDomainProto\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1b\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1b\n" +
"\tlen_limit\x18\x02 \x01(\x05R\blenLimit\x12\x1f\n" + "\tlen_limit\x18\x02 \x01(\x05R\blenLimit\x12\x1f\n" +
"\vlabel_limit\x18\x03 \x01(\x05R\n" + "\vlabel_limit\x18\x03 \x01(\x05R\n" +
"labelLimit\x12\x14\n" + "labelLimit\x12\x14\n" +
"\x05types\x18\x04 \x03(\x05R\x05types\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" + "\rResolverProto\x12\x12\n" +
"\x04type\x18\x01 \x01(\tR\x04type\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" + "\x06Config\x12M\n" +
"\adomains\x18\x01 \x03(\v23.xray.transport.internet.finalmask.xdns.DomainProtoR\adomains\x12S\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" + "\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_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_transport_internet_finalmask_xdns_config_proto_goTypes = []any{ var file_transport_internet_finalmask_xdns_config_proto_goTypes = []any{
(*DomainProto)(nil), // 0: xray.transport.internet.finalmask.xdns.DomainProto (*DomainProto)(nil), // 0: xray.transport.internet.finalmask.xdns.DomainProto
(*ResolverProto)(nil), // 1: xray.transport.internet.finalmask.xdns.ResolverProto (*ResolverProto)(nil), // 1: xray.transport.internet.finalmask.xdns.ResolverProto
(*Config)(nil), // 2: xray.transport.internet.finalmask.xdns.Config (*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{ 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 3, // 0: xray.transport.internet.finalmask.xdns.ResolverProto.tls_config:type_name -> xray.transport.internet.tls.Config
1, // 1: xray.transport.internet.finalmask.xdns.Config.resolvers:type_name -> xray.transport.internet.finalmask.xdns.ResolverProto 4, // 1: xray.transport.internet.finalmask.xdns.ResolverProto.quic_config:type_name -> xray.transport.internet.QuicParams
2, // [2:2] is the sub-list for method output_type 0, // 2: xray.transport.internet.finalmask.xdns.Config.domains:type_name -> xray.transport.internet.finalmask.xdns.DomainProto
2, // [2:2] is the sub-list for method input_type 1, // 3: xray.transport.internet.finalmask.xdns.Config.resolvers:type_name -> xray.transport.internet.finalmask.xdns.ResolverProto
2, // [2:2] is the sub-list for extension type_name 4, // [4:4] is the sub-list for method output_type
2, // [2:2] is the sub-list for extension extendee 4, // [4:4] is the sub-list for method input_type
0, // [0:2] is the sub-list for field type_name 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() } 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; option java_multiple_files = true;
import "common/serial/typed_message.proto"; import "common/serial/typed_message.proto";
import "transport/internet/tls/config.proto";
import "transport/internet/config.proto";
message DomainProto { message DomainProto {
string name = 1; string name = 1;
@@ -19,6 +21,9 @@ message DomainProto {
message ResolverProto { message ResolverProto {
string type = 1; string type = 1;
string addr = 2; string addr = 2;
int32 worker = 3;
xray.transport.internet.tls.Config tls_config = 4;
xray.transport.internet.QuicParams quic_config = 5;
} }
message Config { message Config {
@@ -20,6 +20,8 @@ func NewResolver(config *ResolverProto, dialer *finalmask.Dialer) (Resolver, err
return NewTCPResolver(config, dialer) return NewTCPResolver(config, dialer)
case "udp": case "udp":
return NewUDPResolver(config, dialer) return NewUDPResolver(config, dialer)
case "https":
return NewDOHResolver(config, dialer)
default: default:
return nil, errors.New("unknown type") 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. // grpcUtlsInfo contains the auth information for a TLS authenticated connection.
// It implements the AuthInfo interface. // It implements the AuthInfo interface.
type grpcUtlsInfo struct { type grpcUtlsInfo struct {
State utls.ConnectionState State gotls.ConnectionState
credentials.CommonAuthInfo credentials.CommonAuthInfo
// This API is experimental. // This API is experimental.
SPIFFEID *url.URL SPIFFEID *url.URL
+18
View File
@@ -154,6 +154,24 @@ func (c *UConn) NegotiatedProtocol() string {
return state.NegotiatedProtocol 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 { func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) net.Conn {
utlsConn := utls.UClient(c, copyConfig(config), *fingerprint) utlsConn := utls.UClient(c, copyConfig(config), *fingerprint)
return &UConn{UConn: utlsConn} return &UConn{UConn: utlsConn}