mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-04-28 14:21:25 +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...))
|
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.
|
// LookupHosts implements dns.HostsLookup.
|
||||||
func (s *DNS) LookupHosts(domain string) *net.Address {
|
func (s *DNS) LookupHosts(domain string) *net.Address {
|
||||||
domain = strings.TrimSuffix(domain, ".")
|
domain = strings.TrimSuffix(domain, ".")
|
||||||
|
@ -29,6 +29,12 @@ type record struct {
|
|||||||
AAAA *IPRecord
|
AAAA *IPRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HTTPSRecord struct {
|
||||||
|
keypair map[string]string
|
||||||
|
Expire time.Time
|
||||||
|
RCode dnsmessage.RCode
|
||||||
|
}
|
||||||
|
|
||||||
// IPRecord is a cacheable item for a resolved domain
|
// IPRecord is a cacheable item for a resolved domain
|
||||||
type IPRecord struct {
|
type IPRecord struct {
|
||||||
ReqID uint16
|
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)
|
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.
|
// Client is the interface for DNS client.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
server Server
|
server Server
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
mdns "github.com/miekg/dns"
|
||||||
utls "github.com/refraction-networking/utls"
|
utls "github.com/refraction-networking/utls"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/crypto"
|
"github.com/xtls/xray-core/common/crypto"
|
||||||
@ -42,6 +43,8 @@ type DoHNameServer struct {
|
|||||||
dohURL string
|
dohURL string
|
||||||
name string
|
name string
|
||||||
queryStrategy QueryStrategy
|
queryStrategy QueryStrategy
|
||||||
|
|
||||||
|
HTTPSCache map[string]*HTTPSRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDoHNameServer creates DOH/DOHL client object for remote/local resolving.
|
// 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,
|
name: mode + "//" + url.Host,
|
||||||
dohURL: url.String(),
|
dohURL: url.String(),
|
||||||
queryStrategy: queryStrategy,
|
queryStrategy: queryStrategy,
|
||||||
|
HTTPSCache: make(map[string]*HTTPSRecord),
|
||||||
}
|
}
|
||||||
s.cleanup = &task.Periodic{
|
s.cleanup = &task.Periodic{
|
||||||
Interval: time.Minute,
|
Interval: time.Minute,
|
||||||
@ -207,6 +211,21 @@ func (s *DoHNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
|||||||
common.Must(s.cleanup.Start())
|
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 {
|
func (s *DoHNameServer) newReqID() uint16 {
|
||||||
return 0
|
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) {
|
func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, error) {
|
||||||
body := bytes.NewBuffer(b)
|
body := bytes.NewBuffer(b)
|
||||||
req, err := http.NewRequest("POST", s.dohURL, body)
|
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
|
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.
|
// 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
|
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)
|
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)
|
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 {
|
type HostsLookup interface {
|
||||||
LookupHosts(domain string) *net.Address
|
LookupHosts(domain string) *net.Address
|
||||||
}
|
}
|
||||||
|
@ -106,6 +106,14 @@ func lookupIP(domain string, strategy DomainStrategy, localAddr net.Address) ([]
|
|||||||
return ips, err
|
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 {
|
func canLookupIP(ctx context.Context, dst net.Destination, sockopt *SocketConfig) bool {
|
||||||
if dst.Address.Family().IsIP() || dnsClient == nil {
|
if dst.Address.Family().IsIP() || dnsClient == nil {
|
||||||
return false
|
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.
|
// dnsQuery is the real func for sending type65 query for given domain to given DNS server.
|
||||||
// return ECH config, TTL and error
|
// return ECH config, TTL and error
|
||||||
func dnsQuery(server string, domain string) ([]byte, uint32, 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)
|
m := new(dns.Msg)
|
||||||
var dnsResolve []byte
|
var dnsResolve []byte
|
||||||
m.SetQuestion(dns.Fqdn(domain), dns.TypeHTTPS)
|
m.SetQuestion(dns.Fqdn(domain), dns.TypeHTTPS)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user