Fix non-local DoH requests & Apply routing (#147)

This commit is contained in:
Arthur Morgan 2021-01-13 15:53:08 +08:00 committed by GitHub
parent 822afb0cc8
commit 11a851f957
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -29,6 +29,7 @@ import (
// which is compatible with traditional dns over udp(RFC1035), // which is compatible with traditional dns over udp(RFC1035),
// thus most of the DOH implementation is copied from udpns.go // thus most of the DOH implementation is copied from udpns.go
type DoHNameServer struct { type DoHNameServer struct {
dispatcher routing.Dispatcher
sync.RWMutex sync.RWMutex
ips map[string]record ips map[string]record
pub *pubsub.Service pub *pubsub.Service
@ -45,40 +46,8 @@ func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, clientIP net.
newError("DNS: created Remote DOH client for ", url.String()).AtInfo().WriteToLog() newError("DNS: created Remote DOH client for ", url.String()).AtInfo().WriteToLog()
s := baseDOHNameServer(url, "DOH", clientIP) s := baseDOHNameServer(url, "DOH", clientIP)
// Dispatched connection will be closed (interrupted) after each request s.dispatcher = dispatcher
// This makes DOH inefficient without a keep-alived connection
// See: core/app/proxyman/outbound/handler.go:113
// Using mux (https request wrapped in a stream layer) improves the situation.
// Recommend to use NewDoHLocalNameServer (DOHL:) if xray instance is running on
// a normal network eg. the server side of xray
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 := dispatcher.Dispatch(ctx, dest)
if err != nil {
return nil, err
}
return cnc.NewConnection(
cnc.ConnectionInputMulti(link.Writer),
cnc.ConnectionOutputMulti(link.Reader),
), nil
},
}
dispatchedClient := &http.Client{
Transport: tr,
Timeout: 60 * time.Second,
}
s.httpClient = dispatchedClient
return s, nil return s, nil
} }
@ -211,6 +180,11 @@ func (s *DoHNameServer) newReqID() uint16 {
func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option IPOption) { func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option IPOption) {
newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx)) newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx))
if s.name+"." == "DOH//"+domain {
newError(s.name, " tries to resolve itself! Use IP or set \"hosts\" instead.").AtError().WriteToLog(session.ExportIDToError(ctx))
return
}
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP)) reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP))
var deadline time.Time var deadline time.Time
@ -233,11 +207,11 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option IPO
dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{ dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
Protocol: "https", Protocol: "https",
SkipRoutePick: true, //SkipRoutePick: true,
}) })
// forced to use mux for DOH // forced to use mux for DOH
dnsCtx = session.ContextWithMuxPrefered(dnsCtx, true) // dnsCtx = session.ContextWithMuxPrefered(dnsCtx, true)
var cancel context.CancelFunc var cancel context.CancelFunc
dnsCtx, cancel = context.WithDeadline(dnsCtx, deadline) dnsCtx, cancel = context.WithDeadline(dnsCtx, deadline)
@ -245,17 +219,17 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option IPO
b, err := dns.PackMessage(r.msg) b, err := dns.PackMessage(r.msg)
if err != nil { if err != nil {
newError("failed to pack dns query").Base(err).AtError().WriteToLog() newError("failed to pack dns query for ", domain).Base(err).AtError().WriteToLog()
return return
} }
resp, err := s.dohHTTPSContext(dnsCtx, b.Bytes()) resp, err := s.dohHTTPSContext(dnsCtx, b.Bytes())
if err != nil { if err != nil {
newError("failed to retrieve response").Base(err).AtError().WriteToLog() newError("failed to retrieve response for ", domain).Base(err).AtError().WriteToLog()
return return
} }
rec, err := parseResponse(resp) rec, err := parseResponse(resp)
if err != nil { if err != nil {
newError("failed to handle DOH response").Base(err).AtError().WriteToLog() newError("failed to handle DOH response for ", domain).Base(err).AtError().WriteToLog()
return return
} }
s.updateIP(r, rec) s.updateIP(r, rec)
@ -273,7 +247,44 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte,
req.Header.Add("Accept", "application/dns-message") req.Header.Add("Accept", "application/dns-message")
req.Header.Add("Content-Type", "application/dns-message") req.Header.Add("Content-Type", "application/dns-message")
resp, err := s.httpClient.Do(req.WithContext(ctx)) hc := s.httpClient
// Dispatched connection will be closed (interrupted) after each request
// This makes DOH inefficient without a keep-alived connection
// See: core/app/proxyman/outbound/handler.go:113
// Using mux (https request wrapped in a stream layer) improves the situation.
// Recommend to use NewDoHLocalNameServer (DOHL:) if xray instance is running on
// a normal network eg. the server side of xray
if s.dispatcher != nil {
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(ctx, dest)
if err != nil {
return nil, err
}
return cnc.NewConnection(
cnc.ConnectionInputMulti(link.Writer),
cnc.ConnectionOutputMulti(link.Reader),
), nil
},
}
hc = &http.Client{
Timeout: time.Second * 180,
Transport: tr,
}
}
resp, err := hc.Do(req.WithContext(ctx))
if err != nil { if err != nil {
return nil, err return nil, err
} }