@@ -12,6 +12,7 @@ import (
12
12
"sync"
13
13
"time"
14
14
15
+ mdns "github.com/miekg/dns"
15
16
utls "github.com/refraction-networking/utls"
16
17
"github.com/xtls/xray-core/common"
17
18
"github.com/xtls/xray-core/common/crypto"
@@ -42,6 +43,8 @@ type DoHNameServer struct {
42
43
dohURL string
43
44
name string
44
45
queryStrategy QueryStrategy
46
+
47
+ HTTPSCache map [string ]* HTTPSRecord
45
48
}
46
49
47
50
// NewDoHNameServer creates DOH/DOHL client object for remote/local resolving.
@@ -58,6 +61,7 @@ func NewDoHNameServer(url *url.URL, queryStrategy QueryStrategy, dispatcher rout
58
61
name : mode + "//" + url .Host ,
59
62
dohURL : url .String (),
60
63
queryStrategy : queryStrategy ,
64
+ HTTPSCache : make (map [string ]* HTTPSRecord ),
61
65
}
62
66
s .cleanup = & task.Periodic {
63
67
Interval : time .Minute ,
@@ -207,6 +211,21 @@ func (s *DoHNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
207
211
common .Must (s .cleanup .Start ())
208
212
}
209
213
214
+ func (s * DoHNameServer ) updateHTTPS (domain string , HTTPSRec * HTTPSRecord ) {
215
+ s .Lock ()
216
+ rec , found := s .HTTPSCache [domain ]
217
+ if ! found {
218
+ s .HTTPSCache [domain ] = HTTPSRec
219
+ }
220
+ if found && rec .Expire .Before (time .Now ()) {
221
+ s .HTTPSCache [domain ] = HTTPSRec
222
+ }
223
+ errors .LogInfo (context .Background (), s .name , " got answer: " , domain , " " , "HTTPS" , " -> " , HTTPSRec .keypair )
224
+
225
+ s .pub .Publish (domain + "HTTPS" , nil )
226
+ s .Unlock ()
227
+ }
228
+
210
229
func (s * DoHNameServer ) newReqID () uint16 {
211
230
return 0
212
231
}
@@ -271,6 +290,59 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP n
271
290
}
272
291
}
273
292
293
+ func (s * DoHNameServer ) sendHTTPSQuery (ctx context.Context , domain string ) {
294
+ errors .LogInfo (ctx , s .name , " querying HTTPS record for: " , domain )
295
+ var deadline time.Time
296
+ if d , ok := ctx .Deadline (); ok {
297
+ deadline = d
298
+ } else {
299
+ deadline = time .Now ().Add (time .Second * 5 )
300
+ }
301
+ dnsCtx := ctx
302
+ // reserve internal dns server requested Inbound
303
+ if inbound := session .InboundFromContext (ctx ); inbound != nil {
304
+ dnsCtx = session .ContextWithInbound (dnsCtx , inbound )
305
+ }
306
+ dnsCtx = session .ContextWithContent (dnsCtx , & session.Content {
307
+ Protocol : "https" ,
308
+ SkipDNSResolve : true ,
309
+ })
310
+ var cancel context.CancelFunc
311
+ dnsCtx , cancel = context .WithDeadline (dnsCtx , deadline )
312
+ defer cancel ()
313
+
314
+ m := new (mdns.Msg )
315
+ m .SetQuestion (mdns .Fqdn (domain ), mdns .TypeHTTPS )
316
+ m .Id = 0
317
+ msg , _ := m .Pack ()
318
+ response , err := s .dohHTTPSContext (dnsCtx , msg )
319
+ if err != nil {
320
+ errors .LogError (ctx , err , "failed to retrieve HTTPS query response for " , domain )
321
+ return
322
+ }
323
+ respMsg := new (mdns.Msg )
324
+ err = respMsg .Unpack (response )
325
+ if err != nil {
326
+ errors .LogError (ctx , err , "failed to parse HTTPS query response for " , domain )
327
+ return
328
+ }
329
+ var Record = HTTPSRecord {
330
+ keypair : map [string ]string {},
331
+ }
332
+ if len (respMsg .Answer ) > 0 {
333
+ for _ , answer := range respMsg .Answer {
334
+ if https , ok := answer .(* mdns.HTTPS ); ok && https .Hdr .Name == mdns .Fqdn (domain ) {
335
+ for _ , value := range https .Value {
336
+ Record .keypair [value .Key ().String ()] = value .String ()
337
+ }
338
+ }
339
+ }
340
+ }
341
+ Record .Expire = time .Now ().Add (time .Duration (respMsg .Answer [0 ].Header ().Ttl ) * time .Second )
342
+
343
+ s .updateHTTPS (domain , & Record )
344
+ }
345
+
274
346
func (s * DoHNameServer ) dohHTTPSContext (ctx context.Context , b []byte ) ([]byte , error ) {
275
347
body := bytes .NewBuffer (b )
276
348
req , err := http .NewRequest ("POST" , s .dohURL , body )
@@ -341,6 +413,27 @@ func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOpt
341
413
return nil , errRecordNotFound
342
414
}
343
415
416
+ func (s * DoHNameServer ) findRecordsForDomain (domain string , Querytype string ) (any , error ) {
417
+ switch Querytype {
418
+ case "HTTPS" :
419
+ s .RLock ()
420
+ record , found := s .HTTPSCache [domain ]
421
+ s .RUnlock ()
422
+ if ! found {
423
+ return nil , errRecordNotFound
424
+ }
425
+ if len (record .keypair ) == 0 {
426
+ return nil , dns_feature .ErrEmptyResponse
427
+ }
428
+ if record .Expire .Before (time .Now ()) {
429
+ return nil , errRecordNotFound
430
+ }
431
+ return record , nil
432
+ default :
433
+ return nil , errors .New ("unsupported query type: " + Querytype )
434
+ }
435
+ }
436
+
344
437
// QueryIP implements Server.
345
438
func (s * DoHNameServer ) QueryIP (ctx context.Context , domain string , clientIP net.IP , option dns_feature.IPOption , disableCache bool ) ([]net.IP , error ) { // nolint: dupl
346
439
fqdn := Fqdn (domain )
@@ -403,3 +496,45 @@ func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net
403
496
}
404
497
}
405
498
}
499
+
500
+ // QueryHTTPS implements EnhancedServer.
501
+ func (s * DoHNameServer ) QueryHTTPS (ctx context.Context , domain string , disableCache bool ) (map [string ]string , error ) { // nolint: dupl
502
+ fqdn := Fqdn (domain )
503
+
504
+ if disableCache {
505
+ errors .LogDebug (ctx , "DNS cache is disabled. Querying HTTPS for " , domain , " at " , s .name )
506
+ } else {
507
+ Record , err := s .findRecordsForDomain (fqdn , "HTTPS" )
508
+ if err == nil || err == dns_feature .ErrEmptyResponse {
509
+ errors .LogDebugInner (ctx , err , s .name , " cache HIT " , domain , " -> " , Record .(HTTPSRecord ).keypair )
510
+ return Record .(HTTPSRecord ).keypair , err
511
+ }
512
+ }
513
+ sub := s .pub .Subscribe (fqdn + "HTTPS" )
514
+ defer sub .Close ()
515
+ done := make (chan interface {})
516
+ go func () {
517
+ if sub != nil {
518
+ select {
519
+ case <- sub .Wait ():
520
+ case <- ctx .Done ():
521
+ }
522
+ }
523
+ close (done )
524
+ }()
525
+ s .sendHTTPSQuery (ctx , fqdn )
526
+
527
+ for {
528
+ Record , err := s .findRecordsForDomain (fqdn , "HTTPS" )
529
+ if err != errRecordNotFound {
530
+ errors .LogDebug (ctx , s .name , " got HTTPS answer: " , domain , " -> " , Record .(* HTTPSRecord ).keypair )
531
+ return Record .(* HTTPSRecord ).keypair , err
532
+ }
533
+
534
+ select {
535
+ case <- ctx .Done ():
536
+ return nil , ctx .Err ()
537
+ case <- done :
538
+ }
539
+ }
540
+ }
0 commit comments