mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-19 16:16:13 +00:00
XDNS finalmask: Support DoH ("https://", tlsConfig, domain fronting, etc., quicConfig, Chrome Client Hello by default) with empty HTTP User-Agent (#7140)
https://github.com/XTLS/Xray-core/pull/7103#issuecomment-6118333973 https://github.com/XTLS/Xray-core/pull/7140#issuecomment-6161808702 https://github.com/XTLS/Xray-core/pull/7140#issuecomment-6167254749 https://github.com/XTLS/Xray-core/pull/7140#issuecomment-6185965142 https://github.com/XTLS/Xray-core/pull/7140#issuecomment-6200775375
This commit is contained in:
@@ -9,12 +9,14 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
googleuuid "github.com/google/uuid"
|
||||
"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/fragment"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||
"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/xicmp"
|
||||
"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"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -799,7 +802,10 @@ type XDNSDomain 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 {
|
||||
@@ -834,6 +840,26 @@ func (c *XDNS) Build() (proto.Message, error) {
|
||||
}
|
||||
}
|
||||
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 {
|
||||
var u *url.URL
|
||||
var e error
|
||||
@@ -845,18 +871,21 @@ func (c *XDNS) Build() (proto.Message, error) {
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
var addr string
|
||||
switch u.Scheme {
|
||||
case "tcp", "udp":
|
||||
host := u.Hostname()
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
port = "53"
|
||||
}
|
||||
addr = net.JoinHostPort(host, port)
|
||||
case "https":
|
||||
addr = u.String()
|
||||
default:
|
||||
return nil, errors.New("invalid protocol")
|
||||
}
|
||||
var host, port string
|
||||
host = u.Hostname()
|
||||
port = u.Port()
|
||||
if port == "" {
|
||||
port = "53"
|
||||
}
|
||||
resolvers = append(resolvers, &xdns.ResolverProto{Type: u.Scheme, Addr: net.JoinHostPort(host, port)})
|
||||
resolvers = append(resolvers, &xdns.ResolverProto{Type: u.Scheme, Addr: addr, Worker: worker, TlsConfig: tlsConfig, QuicConfig: quicConfig})
|
||||
}
|
||||
}
|
||||
if c.ExtraPoll < 0 || c.ExtraPoll > 3 {
|
||||
@@ -1149,6 +1178,91 @@ type QuicParamsConfig struct {
|
||||
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 {
|
||||
Tcp []Mask `json:"tcp"`
|
||||
Udp []Mask `json:"udp"`
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
|
||||
)
|
||||
|
||||
type TransportProtocol string
|
||||
@@ -228,102 +226,25 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
||||
|
||||
if c.FinalMask != nil {
|
||||
for _, mask := range c.FinalMask.Tcp {
|
||||
u, err := mask.Build(true)
|
||||
proto, err := mask.Build(true)
|
||||
if err != nil {
|
||||
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 {
|
||||
u, err := mask.Build(false)
|
||||
proto, err := mask.Build(false)
|
||||
if err != nil {
|
||||
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 {
|
||||
profile := strings.ToLower(c.FinalMask.QuicParams.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 := c.FinalMask.QuicParams.BrutalUp.Bps()
|
||||
proto, err := c.FinalMask.QuicParams.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
down, err := c.FinalMask.QuicParams.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")
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
config.QuicParams = proto.(*internet.QuicParams)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user