package transport import ( "context" "strings" "sync" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" mDNS "github.com/miekg/dns" ) type AsyncExchanger = func(ctx context.Context, callback func(response *mDNS.Msg, err error)) // ExchangeSequential tries exchangers in order until accept returns true // (nil accept means err == nil); the last result is delivered as-is. func ExchangeSequential(ctx context.Context, exchangers []AsyncExchanger, accept func(response *mDNS.Msg, err error) bool, callback func(response *mDNS.Msg, err error)) { if len(exchangers) == 0 { callback(nil, E.New("missing exchangers")) return } if accept == nil { accept = func(response *mDNS.Msg, err error) bool { return err == nil } } sequential := &sequentialExchange{ ctx: ctx, exchangers: exchangers, accept: accept, callback: callback, } sequential.run(0) } type sequentialExchange struct { ctx context.Context exchangers []AsyncExchanger accept func(response *mDNS.Msg, err error) bool callback func(response *mDNS.Msg, err error) } func (s *sequentialExchange) run(index int) { for index < len(s.exchangers) { ctxErr := s.ctx.Err() if ctxErr != nil { s.callback(nil, ctxErr) return } currentIndex := index state := &sequentialCallState{} s.exchangers[currentIndex](s.ctx, func(response *mDNS.Msg, err error) { if currentIndex == len(s.exchangers)-1 || s.accept(response, err) { s.callback(response, err) return } state.access.Lock() if state.returned { state.access.Unlock() s.run(currentIndex + 1) return } state.continued = true state.access.Unlock() }) state.access.Lock() state.returned = true continued := state.continued state.access.Unlock() if !continued { return } index = currentIndex + 1 } } type sequentialCallState struct { access sync.Mutex returned bool continued bool } func ExchangeNames(ctx context.Context, names []string, question mDNS.Question, exchangerFor func(fqdn string) AsyncExchanger, callback func(response *mDNS.Msg, err error)) { if len(names) == 0 { callback(nil, E.New("missing name candidates")) return } search := &nameSearchExchange{question: question} nameExchangers := common.Map(names, func(fqdn string) AsyncExchanger { return search.wrap(fqdn, exchangerFor(fqdn)) }) ExchangeSequential(ctx, nameExchangers, func(response *mDNS.Msg, err error) bool { return err == nil && response.Rcode != mDNS.RcodeNameError }, func(response *mDNS.Msg, err error) { if err != nil || response.Rcode == mDNS.RcodeNameError { search.access.Lock() nameErrorResponse := search.nameErrorResponse search.access.Unlock() if nameErrorResponse != nil { response, err = nameErrorResponse, nil } } callback(response, err) }) } type nameSearchExchange struct { question mDNS.Question access sync.Mutex nameErrorResponse *mDNS.Msg } func (s *nameSearchExchange) wrap(fqdn string, exchanger AsyncExchanger) AsyncExchanger { return func(ctx context.Context, callback func(response *mDNS.Msg, err error)) { exchanger(ctx, func(response *mDNS.Msg, err error) { if err == nil { restoreOriginalQuestion(response, fqdn, s.question) if response.Rcode == mDNS.RcodeNameError { s.access.Lock() if s.nameErrorResponse == nil || fqdn == s.question.Name { s.nameErrorResponse = response } s.access.Unlock() } } callback(response, err) }) } } // Stub resolvers discard Answer RRs whose owner name does not match the question. func restoreOriginalQuestion(response *mDNS.Msg, fqdn string, question mDNS.Question) { response.Question = []mDNS.Question{question} for _, record := range response.Answer { if strings.EqualFold(record.Header().Name, fqdn) { record.Header().Name = question.Name } } } func NewFanOutRequest(message *mDNS.Msg, fqdn string, authenticatedData bool) *mDNS.Msg { question := message.Question[0] question.Name = fqdn request := &mDNS.Msg{ MsgHdr: mDNS.MsgHdr{ Id: message.Id, RecursionDesired: true, AuthenticatedData: authenticatedData, }, Question: []mDNS.Question{question}, Compress: true, } request.SetEdns0(buf.UDPBufferSize, false) return request }