diff --git a/app/dns/nameserver.go b/app/dns/nameserver.go index b145f9eb..89f5b25e 100644 --- a/app/dns/nameserver.go +++ b/app/dns/nameserver.go @@ -44,9 +44,11 @@ func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dis switch { case strings.EqualFold(u.String(), "localhost"): return NewLocalNameServer(queryStrategy), nil - case strings.EqualFold(u.Scheme, "https"): // DOH Remote mode - return NewDoHNameServer(u, dispatcher, queryStrategy) - case strings.EqualFold(u.Scheme, "https+local"): // DOH Local mode + case strings.EqualFold(u.Scheme, "https"): // DNS-over-HTTPS Remote mode + return NewDoHNameServer(u, dispatcher, queryStrategy, false) + case strings.EqualFold(u.Scheme, "h2c"): // DNS-over-HTTPS h2c Remote mode + return NewDoHNameServer(u, dispatcher, queryStrategy, true) + case strings.EqualFold(u.Scheme, "https+local"): // DNS-over-HTTPS Local mode return NewDoHLocalNameServer(u, queryStrategy), nil case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode return NewQUICNameServer(u, queryStrategy) diff --git a/app/dns/nameserver_doh.go b/app/dns/nameserver_doh.go index 7c25bb8d..177c7561 100644 --- a/app/dns/nameserver_doh.go +++ b/app/dns/nameserver_doh.go @@ -3,6 +3,7 @@ package dns import ( "bytes" "context" + "crypto/tls" "fmt" "io" "net/http" @@ -23,6 +24,7 @@ import ( "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/transport/internet" "golang.org/x/net/dns/dnsmessage" + "golang.org/x/net/http2" ) // DoHNameServer implemented DNS over HTTPS (RFC8484) Wire Format, @@ -41,49 +43,59 @@ type DoHNameServer struct { } // NewDoHNameServer creates DOH server object for remote resolving. -func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, queryStrategy QueryStrategy) (*DoHNameServer, error) { - errors.LogInfo(context.Background(), "DNS: created Remote DOH client for ", url.String()) +func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, queryStrategy QueryStrategy, h2c bool) (*DoHNameServer, error) { + url.Scheme = "https" + errors.LogInfo(context.Background(), "DNS: created Remote DNS-over-HTTPS client for ", url.String(), ", with h2c ", h2c) s := baseDOHNameServer(url, "DOH", queryStrategy) s.dispatcher = dispatcher - tr := &http.Transport{ - MaxIdleConns: 30, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 30 * 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 - } - link, err := s.dispatcher.Dispatch(toDnsContext(ctx, s.dohURL), dest) - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: + dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { + dest, err := net.ParseDestination(network + ":" + addr) + if err != nil { + return nil, err + } + link, err := s.dispatcher.Dispatch(toDnsContext(ctx, s.dohURL), dest) + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: - } - if err != nil { - return nil, err - } + } + 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 + 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 + } + + 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, }, } - s.httpClient = &http.Client{ - Timeout: time.Second * 180, - Transport: tr, + 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 @@ -118,7 +130,7 @@ func NewDoHLocalNameServer(url *url.URL, queryStrategy QueryStrategy) *DoHNameSe Timeout: time.Second * 180, Transport: tr, } - errors.LogInfo(context.Background(), "DNS: created Local DOH client for ", url.String()) + errors.LogInfo(context.Background(), "DNS: created Local DNS-over-HTTPS client for ", url.String()) return s } diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 09412613..16b659c4 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -410,6 +410,7 @@ type TLSConfig struct { PinnedPeerCertificatePublicKeySha256 *[]string `json:"pinnedPeerCertificatePublicKeySha256"` CurvePreferences *StringList `json:"curvePreferences"` MasterKeyLog string `json:"masterKeyLog"` + ServerNameToVerify string `json:"serverNameToVerify"` } // Build implements Buildable. @@ -468,6 +469,10 @@ func (c *TLSConfig) Build() (proto.Message, error) { } config.MasterKeyLog = c.MasterKeyLog + config.ServerNameToVerify = c.ServerNameToVerify + if config.ServerNameToVerify != "" && config.Fingerprint == "unsafe" { + return nil, errors.New(`serverNameToVerify only works with uTLS for now`) + } return config, nil } diff --git a/transport/internet/tls/config.go b/transport/internet/tls/config.go index 2ecabffb..8278cafb 100644 --- a/transport/internet/tls/config.go +++ b/transport/internet/tls/config.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/hmac" + "crypto/rand" "crypto/tls" "crypto/x509" "encoding/base64" @@ -303,6 +304,14 @@ func (c *Config) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Cert return nil } +type RandCarrier struct { + ServerNameToVerify string +} + +func (r *RandCarrier) Read(p []byte) (n int, err error) { + return rand.Read(p) +} + // GetTLSConfig converts this Config into tls.Config. func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { root, err := c.getCertPool() @@ -321,6 +330,9 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { } config := &tls.Config{ + Rand: &RandCarrier{ + ServerNameToVerify: c.ServerNameToVerify, + }, ClientSessionCache: globalSessionCache, RootCAs: root, InsecureSkipVerify: c.AllowInsecure, diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index 1c16e697..e59fe502 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -214,7 +214,8 @@ type Config struct { 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"` // Lists of string as CurvePreferences values. - CurvePreferences []string `protobuf:"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3" json:"curve_preferences,omitempty"` + CurvePreferences []string `protobuf:"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3" json:"curve_preferences,omitempty"` + ServerNameToVerify string `protobuf:"bytes,17,opt,name=server_name_to_verify,json=serverNameToVerify,proto3" json:"server_name_to_verify,omitempty"` } func (x *Config) Reset() { @@ -352,6 +353,13 @@ func (x *Config) GetCurvePreferences() []string { return nil } +func (x *Config) GetServerNameToVerify() string { + if x != nil { + return x.ServerNameToVerify + } + return "" +} + var File_transport_internet_tls_config_proto protoreflect.FileDescriptor var file_transport_internet_tls_config_proto_rawDesc = []byte{ @@ -383,7 +391,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, 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, - 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xe0, 0x05, 0x0a, 0x06, 0x43, 0x6f, 0x6e, + 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0x93, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 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, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65, @@ -429,15 +437,18 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x52, 0x0c, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x75, 0x72, 0x76, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x75, 0x72, 0x76, 0x65, - 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x42, 0x73, 0x0a, 0x1f, 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, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, - 0x5a, 0x30, 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, 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, + 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x76, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x54, 0x6f, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x42, 0x73, + 0x0a, 0x1f, 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, 0x2e, 0x74, 0x6c, + 0x73, 0x50, 0x01, 0x5a, 0x30, 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, 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 ( diff --git a/transport/internet/tls/config.proto b/transport/internet/tls/config.proto index 0fc1d01d..bb2564a3 100644 --- a/transport/internet/tls/config.proto +++ b/transport/internet/tls/config.proto @@ -87,4 +87,6 @@ message Config { // Lists of string as CurvePreferences values. repeated string curve_preferences = 16; + + string server_name_to_verify = 17; } diff --git a/transport/internet/tls/tls.go b/transport/internet/tls/tls.go index 4b8fa486..e15231f4 100644 --- a/transport/internet/tls/tls.go +++ b/transport/internet/tls/tls.go @@ -134,12 +134,17 @@ func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) ne } func copyConfig(c *tls.Config) *utls.Config { + serverNameToVerify := "" + if r, ok := c.Rand.(*RandCarrier); ok { + serverNameToVerify = r.ServerNameToVerify + } return &utls.Config{ - RootCAs: c.RootCAs, - ServerName: c.ServerName, - InsecureSkipVerify: c.InsecureSkipVerify, - VerifyPeerCertificate: c.VerifyPeerCertificate, - KeyLogWriter: c.KeyLogWriter, + RootCAs: c.RootCAs, + ServerName: c.ServerName, + InsecureSkipVerify: c.InsecureSkipVerify, + VerifyPeerCertificate: c.VerifyPeerCertificate, + KeyLogWriter: c.KeyLogWriter, + InsecureServerNameToVerify: serverNameToVerify, } }