Skip to content

Commit 2522cfd

Browse files
authored
DNS DoH: Add h2c Remote mode (with TLS serverNameToVerify)
#4313 (comment) Applies refraction-networking/utls#161 Closes #4313
1 parent a0822cb commit 2522cfd

File tree

7 files changed

+105
-56
lines changed

7 files changed

+105
-56
lines changed

app/dns/nameserver.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,11 @@ func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dis
4444
switch {
4545
case strings.EqualFold(u.String(), "localhost"):
4646
return NewLocalNameServer(queryStrategy), nil
47-
case strings.EqualFold(u.Scheme, "https"): // DOH Remote mode
48-
return NewDoHNameServer(u, dispatcher, queryStrategy)
49-
case strings.EqualFold(u.Scheme, "https+local"): // DOH Local mode
47+
case strings.EqualFold(u.Scheme, "https"): // DNS-over-HTTPS Remote mode
48+
return NewDoHNameServer(u, dispatcher, queryStrategy, false)
49+
case strings.EqualFold(u.Scheme, "h2c"): // DNS-over-HTTPS h2c Remote mode
50+
return NewDoHNameServer(u, dispatcher, queryStrategy, true)
51+
case strings.EqualFold(u.Scheme, "https+local"): // DNS-over-HTTPS Local mode
5052
return NewDoHLocalNameServer(u, queryStrategy), nil
5153
case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode
5254
return NewQUICNameServer(u, queryStrategy)

app/dns/nameserver_doh.go

+49-37
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dns
33
import (
44
"bytes"
55
"context"
6+
"crypto/tls"
67
"fmt"
78
"io"
89
"net/http"
@@ -23,6 +24,7 @@ import (
2324
"github.com/xtls/xray-core/features/routing"
2425
"github.com/xtls/xray-core/transport/internet"
2526
"golang.org/x/net/dns/dnsmessage"
27+
"golang.org/x/net/http2"
2628
)
2729

2830
// DoHNameServer implemented DNS over HTTPS (RFC8484) Wire Format,
@@ -41,49 +43,59 @@ type DoHNameServer struct {
4143
}
4244

4345
// NewDoHNameServer creates DOH server object for remote resolving.
44-
func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, queryStrategy QueryStrategy) (*DoHNameServer, error) {
45-
errors.LogInfo(context.Background(), "DNS: created Remote DOH client for ", url.String())
46+
func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, queryStrategy QueryStrategy, h2c bool) (*DoHNameServer, error) {
47+
url.Scheme = "https"
48+
errors.LogInfo(context.Background(), "DNS: created Remote DNS-over-HTTPS client for ", url.String(), ", with h2c ", h2c)
4649
s := baseDOHNameServer(url, "DOH", queryStrategy)
4750

4851
s.dispatcher = dispatcher
49-
tr := &http.Transport{
50-
MaxIdleConns: 30,
51-
IdleConnTimeout: 90 * time.Second,
52-
TLSHandshakeTimeout: 30 * time.Second,
53-
ForceAttemptHTTP2: true,
54-
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
55-
dest, err := net.ParseDestination(network + ":" + addr)
56-
if err != nil {
57-
return nil, err
58-
}
59-
link, err := s.dispatcher.Dispatch(toDnsContext(ctx, s.dohURL), dest)
60-
select {
61-
case <-ctx.Done():
62-
return nil, ctx.Err()
63-
default:
52+
dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
53+
dest, err := net.ParseDestination(network + ":" + addr)
54+
if err != nil {
55+
return nil, err
56+
}
57+
link, err := s.dispatcher.Dispatch(toDnsContext(ctx, s.dohURL), dest)
58+
select {
59+
case <-ctx.Done():
60+
return nil, ctx.Err()
61+
default:
6462

65-
}
66-
if err != nil {
67-
return nil, err
68-
}
63+
}
64+
if err != nil {
65+
return nil, err
66+
}
6967

70-
cc := common.ChainedClosable{}
71-
if cw, ok := link.Writer.(common.Closable); ok {
72-
cc = append(cc, cw)
73-
}
74-
if cr, ok := link.Reader.(common.Closable); ok {
75-
cc = append(cc, cr)
76-
}
77-
return cnc.NewConnection(
78-
cnc.ConnectionInputMulti(link.Writer),
79-
cnc.ConnectionOutputMulti(link.Reader),
80-
cnc.ConnectionOnClose(cc),
81-
), nil
82-
},
68+
cc := common.ChainedClosable{}
69+
if cw, ok := link.Writer.(common.Closable); ok {
70+
cc = append(cc, cw)
71+
}
72+
if cr, ok := link.Reader.(common.Closable); ok {
73+
cc = append(cc, cr)
74+
}
75+
return cnc.NewConnection(
76+
cnc.ConnectionInputMulti(link.Writer),
77+
cnc.ConnectionOutputMulti(link.Reader),
78+
cnc.ConnectionOnClose(cc),
79+
), nil
8380
}
81+
8482
s.httpClient = &http.Client{
85-
Timeout: time.Second * 180,
86-
Transport: tr,
83+
Timeout: time.Second * 180,
84+
Transport: &http.Transport{
85+
MaxIdleConns: 30,
86+
IdleConnTimeout: 90 * time.Second,
87+
TLSHandshakeTimeout: 30 * time.Second,
88+
ForceAttemptHTTP2: true,
89+
DialContext: dialContext,
90+
},
91+
}
92+
if h2c {
93+
s.httpClient.Transport = &http2.Transport{
94+
IdleConnTimeout: 90 * time.Second,
95+
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
96+
return dialContext(ctx, network, addr)
97+
},
98+
}
8799
}
88100

89101
return s, nil
@@ -118,7 +130,7 @@ func NewDoHLocalNameServer(url *url.URL, queryStrategy QueryStrategy) *DoHNameSe
118130
Timeout: time.Second * 180,
119131
Transport: tr,
120132
}
121-
errors.LogInfo(context.Background(), "DNS: created Local DOH client for ", url.String())
133+
errors.LogInfo(context.Background(), "DNS: created Local DNS-over-HTTPS client for ", url.String())
122134
return s
123135
}
124136

infra/conf/transport_internet.go

+5
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ type TLSConfig struct {
410410
PinnedPeerCertificatePublicKeySha256 *[]string `json:"pinnedPeerCertificatePublicKeySha256"`
411411
CurvePreferences *StringList `json:"curvePreferences"`
412412
MasterKeyLog string `json:"masterKeyLog"`
413+
ServerNameToVerify string `json:"serverNameToVerify"`
413414
}
414415

415416
// Build implements Buildable.
@@ -468,6 +469,10 @@ func (c *TLSConfig) Build() (proto.Message, error) {
468469
}
469470

470471
config.MasterKeyLog = c.MasterKeyLog
472+
config.ServerNameToVerify = c.ServerNameToVerify
473+
if config.ServerNameToVerify != "" && config.Fingerprint == "unsafe" {
474+
return nil, errors.New(`serverNameToVerify only works with uTLS for now`)
475+
}
471476

472477
return config, nil
473478
}

transport/internet/tls/config.go

+12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"crypto/hmac"
7+
"crypto/rand"
78
"crypto/tls"
89
"crypto/x509"
910
"encoding/base64"
@@ -303,6 +304,14 @@ func (c *Config) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Cert
303304
return nil
304305
}
305306

307+
type RandCarrier struct {
308+
ServerNameToVerify string
309+
}
310+
311+
func (r *RandCarrier) Read(p []byte) (n int, err error) {
312+
return rand.Read(p)
313+
}
314+
306315
// GetTLSConfig converts this Config into tls.Config.
307316
func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
308317
root, err := c.getCertPool()
@@ -321,6 +330,9 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
321330
}
322331

323332
config := &tls.Config{
333+
Rand: &RandCarrier{
334+
ServerNameToVerify: c.ServerNameToVerify,
335+
},
324336
ClientSessionCache: globalSessionCache,
325337
RootCAs: root,
326338
InsecureSkipVerify: c.AllowInsecure,

transport/internet/tls/config.pb.go

+22-11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

transport/internet/tls/config.proto

+2
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,6 @@ message Config {
8787

8888
// Lists of string as CurvePreferences values.
8989
repeated string curve_preferences = 16;
90+
91+
string server_name_to_verify = 17;
9092
}

transport/internet/tls/tls.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,17 @@ func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) ne
134134
}
135135

136136
func copyConfig(c *tls.Config) *utls.Config {
137+
serverNameToVerify := ""
138+
if r, ok := c.Rand.(*RandCarrier); ok {
139+
serverNameToVerify = r.ServerNameToVerify
140+
}
137141
return &utls.Config{
138-
RootCAs: c.RootCAs,
139-
ServerName: c.ServerName,
140-
InsecureSkipVerify: c.InsecureSkipVerify,
141-
VerifyPeerCertificate: c.VerifyPeerCertificate,
142-
KeyLogWriter: c.KeyLogWriter,
142+
RootCAs: c.RootCAs,
143+
ServerName: c.ServerName,
144+
InsecureSkipVerify: c.InsecureSkipVerify,
145+
VerifyPeerCertificate: c.VerifyPeerCertificate,
146+
KeyLogWriter: c.KeyLogWriter,
147+
InsecureServerNameToVerify: serverNameToVerify,
143148
}
144149
}
145150

0 commit comments

Comments
 (0)