mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-03-14 13:06:14 +03:00
Add support for internal DNS system
This commit is contained in:
parent
4999fd5b7b
commit
5f504888b6
@ -210,6 +210,34 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
|
||||
return nil, errors.New("returning nil for domain ", domain).Base(errors.Combine(errs...))
|
||||
}
|
||||
|
||||
func (s *DNS) LookupHTTPS(domain string) (map[string]string, error) {
|
||||
errs := []error{}
|
||||
ctx := session.ContextWithInbound(s.ctx, &session.Inbound{Tag: s.tag})
|
||||
for _, client := range s.sortClients(domain) {
|
||||
if strings.EqualFold(client.Name(), "FakeDNS") {
|
||||
errors.LogDebug(s.ctx, "skip DNS resolution for domain ", domain, " at server ", client.Name())
|
||||
continue
|
||||
}
|
||||
EnhancedServer, ok := client.server.(EnhancedServer)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
HTTPSRecord, err := EnhancedServer.QueryHTTPS(ctx, domain, s.disableCache)
|
||||
if len(HTTPSRecord) > 0 {
|
||||
return HTTPSRecord, nil
|
||||
}
|
||||
if err != nil {
|
||||
errors.LogInfoInner(s.ctx, err, "failed to lookup HTTPS for domain ", domain, " at server ", client.Name())
|
||||
errs = append(errs, err)
|
||||
}
|
||||
// 5 for RcodeRefused in miekg/dns, hardcode to reduce binary size
|
||||
if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch && err != dns.ErrEmptyResponse && dns.RCodeFromError(err) != 5 {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil, errors.New("returning nil for domain ", domain).Base(errors.Combine(errs...))
|
||||
}
|
||||
|
||||
// LookupHosts implements dns.HostsLookup.
|
||||
func (s *DNS) LookupHosts(domain string) *net.Address {
|
||||
domain = strings.TrimSuffix(domain, ".")
|
||||
|
@ -29,6 +29,12 @@ type record struct {
|
||||
AAAA *IPRecord
|
||||
}
|
||||
|
||||
type HTTPSRecord struct {
|
||||
keypair map[string]string
|
||||
Expire time.Time
|
||||
RCode dnsmessage.RCode
|
||||
}
|
||||
|
||||
// IPRecord is a cacheable item for a resolved domain
|
||||
type IPRecord struct {
|
||||
ReqID uint16
|
||||
|
@ -23,6 +23,13 @@ type Server interface {
|
||||
QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, disableCache bool) ([]net.IP, error)
|
||||
}
|
||||
|
||||
// Server is the interface for Enhanced Name Server.
|
||||
type EnhancedServer interface {
|
||||
Server
|
||||
// QueryHTTPS sends HTTPS queries to its configured server.
|
||||
QueryHTTPS(ctx context.Context, domain string, disableCache bool) (map[string]string, error)
|
||||
}
|
||||
|
||||
// Client is the interface for DNS client.
|
||||
type Client struct {
|
||||
server Server
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
mdns "github.com/miekg/dns"
|
||||
utls "github.com/refraction-networking/utls"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
@ -42,6 +43,8 @@ type DoHNameServer struct {
|
||||
dohURL string
|
||||
name string
|
||||
queryStrategy QueryStrategy
|
||||
|
||||
HTTPSCache map[string]*HTTPSRecord
|
||||
}
|
||||
|
||||
// NewDoHNameServer creates DOH/DOHL client object for remote/local resolving.
|
||||
@ -58,6 +61,7 @@ func NewDoHNameServer(url *url.URL, queryStrategy QueryStrategy, dispatcher rout
|
||||
name: mode + "//" + url.Host,
|
||||
dohURL: url.String(),
|
||||
queryStrategy: queryStrategy,
|
||||
HTTPSCache: make(map[string]*HTTPSRecord),
|
||||
}
|
||||
s.cleanup = &task.Periodic{
|
||||
Interval: time.Minute,
|
||||
@ -207,6 +211,21 @@ func (s *DoHNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
||||
common.Must(s.cleanup.Start())
|
||||
}
|
||||
|
||||
func (s *DoHNameServer) updateHTTPS(domain string, HTTPSRec *HTTPSRecord) {
|
||||
s.Lock()
|
||||
rec, found := s.HTTPSCache[domain]
|
||||
if !found {
|
||||
s.HTTPSCache[domain] = HTTPSRec
|
||||
}
|
||||
if found && rec.Expire.Before(time.Now()) {
|
||||
s.HTTPSCache[domain] = HTTPSRec
|
||||
}
|
||||
errors.LogInfo(context.Background(), s.name, " got answer: ", domain, " ", "HTTPS", " -> ", HTTPSRec.keypair)
|
||||
|
||||
s.pub.Publish(domain+"HTTPS", nil)
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func (s *DoHNameServer) newReqID() uint16 {
|
||||
return 0
|
||||
}
|
||||
@ -271,6 +290,59 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP n
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DoHNameServer) sendHTTPSQuery(ctx context.Context, domain string) {
|
||||
errors.LogInfo(ctx, s.name, " querying HTTPS record for: ", domain)
|
||||
var deadline time.Time
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
deadline = d
|
||||
} else {
|
||||
deadline = time.Now().Add(time.Second * 5)
|
||||
}
|
||||
dnsCtx := ctx
|
||||
// reserve internal dns server requested Inbound
|
||||
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||
dnsCtx = session.ContextWithInbound(dnsCtx, inbound)
|
||||
}
|
||||
dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
|
||||
Protocol: "https",
|
||||
SkipDNSResolve: true,
|
||||
})
|
||||
var cancel context.CancelFunc
|
||||
dnsCtx, cancel = context.WithDeadline(dnsCtx, deadline)
|
||||
defer cancel()
|
||||
|
||||
m := new(mdns.Msg)
|
||||
m.SetQuestion(mdns.Fqdn(domain), mdns.TypeHTTPS)
|
||||
m.Id = 0
|
||||
msg, _ := m.Pack()
|
||||
response, err := s.dohHTTPSContext(dnsCtx, msg)
|
||||
if err != nil {
|
||||
errors.LogError(ctx, err, "failed to retrieve HTTPS query response for ", domain)
|
||||
return
|
||||
}
|
||||
respMsg := new(mdns.Msg)
|
||||
err = respMsg.Unpack(response)
|
||||
if err != nil {
|
||||
errors.LogError(ctx, err, "failed to parse HTTPS query response for ", domain)
|
||||
return
|
||||
}
|
||||
var Record = HTTPSRecord{
|
||||
keypair: map[string]string{},
|
||||
}
|
||||
if len(respMsg.Answer) > 0 {
|
||||
for _, answer := range respMsg.Answer {
|
||||
if https, ok := answer.(*mdns.HTTPS); ok && https.Hdr.Name == mdns.Fqdn(domain) {
|
||||
for _, value := range https.Value {
|
||||
Record.keypair[value.Key().String()] = value.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Record.Expire = time.Now().Add(time.Duration(respMsg.Answer[0].Header().Ttl) * time.Second)
|
||||
|
||||
s.updateHTTPS(domain, &Record)
|
||||
}
|
||||
|
||||
func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, error) {
|
||||
body := bytes.NewBuffer(b)
|
||||
req, err := http.NewRequest("POST", s.dohURL, body)
|
||||
@ -341,6 +413,27 @@ func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOpt
|
||||
return nil, errRecordNotFound
|
||||
}
|
||||
|
||||
func (s *DoHNameServer) findRecordsForDomain(domain string, Querytype string) (any, error) {
|
||||
switch Querytype {
|
||||
case "HTTPS":
|
||||
s.RLock()
|
||||
record, found := s.HTTPSCache[domain]
|
||||
s.RUnlock()
|
||||
if !found {
|
||||
return nil, errRecordNotFound
|
||||
}
|
||||
if len(record.keypair) == 0 {
|
||||
return nil, dns_feature.ErrEmptyResponse
|
||||
}
|
||||
if record.Expire.Before(time.Now()) {
|
||||
return nil, errRecordNotFound
|
||||
}
|
||||
return record, nil
|
||||
default:
|
||||
return nil, errors.New("unsupported query type: " + Querytype)
|
||||
}
|
||||
}
|
||||
|
||||
// QueryIP implements Server.
|
||||
func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) { // nolint: dupl
|
||||
fqdn := Fqdn(domain)
|
||||
@ -403,3 +496,44 @@ func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// QueryHTTPS implements EnhancedServer.
|
||||
func (s *DoHNameServer) QueryHTTPS(ctx context.Context, domain string, disableCache bool) (map[string]string, error) { // nolint: dupl
|
||||
fqdn := Fqdn(domain)
|
||||
|
||||
if disableCache {
|
||||
errors.LogDebug(ctx, "DNS cache is disabled. Querying HTTPS for ", domain, " at ", s.name)
|
||||
} else {
|
||||
Record, err := s.findRecordsForDomain(fqdn, "HTTPS")
|
||||
if err == nil || err == dns_feature.ErrEmptyResponse {
|
||||
errors.LogDebugInner(ctx, err, s.name, " cache HIT ", domain, " -> ", Record.(HTTPSRecord).keypair)
|
||||
return Record.(HTTPSRecord).keypair, err
|
||||
}
|
||||
}
|
||||
sub := s.pub.Subscribe(fqdn + "HTTPS")
|
||||
defer sub.Close()
|
||||
done := make(chan interface{})
|
||||
go func() {
|
||||
if sub != nil {
|
||||
select {
|
||||
case <-sub.Wait():
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
s.sendHTTPSQuery(ctx, fqdn)
|
||||
|
||||
for {
|
||||
Record, err := s.findRecordsForDomain(fqdn, "HTTPS")
|
||||
if err != errRecordNotFound {
|
||||
return Record.(*HTTPSRecord).keypair, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,13 @@ type Client interface {
|
||||
LookupIP(domain string, option IPOption) ([]net.IP, error)
|
||||
}
|
||||
|
||||
type EnhancedClient interface {
|
||||
Client
|
||||
|
||||
// LookupHTTPS returns HTTPS records for the given domain.
|
||||
LookupHTTPS(domain string) (map[string]string, error)
|
||||
}
|
||||
|
||||
type HostsLookup interface {
|
||||
LookupHosts(domain string) *net.Address
|
||||
}
|
||||
|
@ -106,6 +106,14 @@ func lookupIP(domain string, strategy DomainStrategy, localAddr net.Address) ([]
|
||||
return ips, err
|
||||
}
|
||||
|
||||
func LookupHTTPS(domain string) (map[string]string, error) {
|
||||
if dnsClient == nil {
|
||||
return nil, nil
|
||||
}
|
||||
HTTPSRecord, err := dnsClient.(dns.EnhancedClient).LookupHTTPS(domain)
|
||||
return HTTPSRecord, err
|
||||
}
|
||||
|
||||
func canLookupIP(ctx context.Context, dst net.Destination, sockopt *SocketConfig) bool {
|
||||
if dst.Address.Family().IsIP() || dnsClient == nil {
|
||||
return false
|
||||
|
@ -138,6 +138,22 @@ func QueryRecord(domain string, server string) ([]byte, error) {
|
||||
// dnsQuery is the real func for sending type65 query for given domain to given DNS server.
|
||||
// return ECH config, TTL and error
|
||||
func dnsQuery(server string, domain string) ([]byte, uint32, error) {
|
||||
if server == "xray" {
|
||||
HTTPSRecord, err := internet.LookupHTTPS(domain)
|
||||
if err !=nil {
|
||||
return []byte{}, 0, errors.New("failed to lookup HTTPS record with xray internal DNS: ", err)
|
||||
}
|
||||
ECH := HTTPSRecord["ech"]
|
||||
if ECH == "" {
|
||||
return []byte{}, 0, errors.New("no ech record found")
|
||||
}
|
||||
Base64echConfigList, err := goech.ECHConfigListFromBase64(ECH)
|
||||
if err != nil {
|
||||
return []byte{}, 0, errors.New("failed to unmarshal ECHConfigList: ", err)
|
||||
}
|
||||
echConfigList, _ := Base64echConfigList.MarshalBinary()
|
||||
return echConfigList, 600, nil
|
||||
}
|
||||
m := new(dns.Msg)
|
||||
var dnsResolve []byte
|
||||
m.SetQuestion(dns.Fqdn(domain), dns.TypeHTTPS)
|
||||
|
Loading…
x
Reference in New Issue
Block a user