mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-11 17:47:20 +10:00
dns: ignore split lookup errors on partial success
This commit is contained in:
@@ -380,31 +380,7 @@ type exchangeWithRulesResult struct {
|
||||
|
||||
const dnsRespondMissingResponseMessage = "respond action requires an evaluated response from a preceding evaluate action"
|
||||
|
||||
type lookupSplitHardError struct {
|
||||
cause error
|
||||
}
|
||||
|
||||
func (e *lookupSplitHardError) Error() string {
|
||||
return e.cause.Error()
|
||||
}
|
||||
|
||||
func (e *lookupSplitHardError) Unwrap() error {
|
||||
return e.cause
|
||||
}
|
||||
|
||||
func newLookupSplitHardError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &lookupSplitHardError{cause: err}
|
||||
}
|
||||
|
||||
func isLookupSplitHardError(err error) bool {
|
||||
var target *lookupSplitHardError
|
||||
return errors.As(err, &target)
|
||||
}
|
||||
|
||||
func (r *Router) exchangeWithRules(ctx context.Context, rules []adapter.DNSRule, message *mDNS.Msg, options adapter.DNSQueryOptions, allowFakeIP bool, hardFailMissingTransport bool) exchangeWithRulesResult {
|
||||
func (r *Router) exchangeWithRules(ctx context.Context, rules []adapter.DNSRule, message *mDNS.Msg, options adapter.DNSQueryOptions, allowFakeIP bool) exchangeWithRulesResult {
|
||||
metadata := adapter.ContextFrom(ctx)
|
||||
if metadata == nil {
|
||||
panic("no context")
|
||||
@@ -428,11 +404,7 @@ func (r *Router) exchangeWithRules(ctx context.Context, rules []adapter.DNSRule,
|
||||
transport, status := r.resolveDNSRoute(action.Server, action.RuleActionDNSRouteOptions, allowFakeIP, &queryOptions)
|
||||
switch status {
|
||||
case dnsRouteStatusMissing:
|
||||
err := E.New("transport not found: ", action.Server)
|
||||
if hardFailMissingTransport {
|
||||
return exchangeWithRulesResult{err: newLookupSplitHardError(err)}
|
||||
}
|
||||
r.logger.ErrorContext(ctx, err)
|
||||
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
|
||||
evaluatedResponse = nil
|
||||
evaluatedTransport = nil
|
||||
continue
|
||||
@@ -458,7 +430,7 @@ func (r *Router) exchangeWithRules(ctx context.Context, rules []adapter.DNSRule,
|
||||
case *R.RuleActionRespond:
|
||||
if evaluatedResponse == nil {
|
||||
return exchangeWithRulesResult{
|
||||
err: newLookupSplitHardError(E.New(dnsRespondMissingResponseMessage)),
|
||||
err: E.New(dnsRespondMissingResponseMessage),
|
||||
}
|
||||
}
|
||||
return exchangeWithRulesResult{
|
||||
@@ -470,11 +442,7 @@ func (r *Router) exchangeWithRules(ctx context.Context, rules []adapter.DNSRule,
|
||||
transport, status := r.resolveDNSRoute(action.Server, action.RuleActionDNSRouteOptions, allowFakeIP, &queryOptions)
|
||||
switch status {
|
||||
case dnsRouteStatusMissing:
|
||||
err := E.New("transport not found: ", action.Server)
|
||||
if hardFailMissingTransport {
|
||||
return exchangeWithRulesResult{err: newLookupSplitHardError(err)}
|
||||
}
|
||||
r.logger.ErrorContext(ctx, err)
|
||||
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
|
||||
continue
|
||||
case dnsRouteStatusSkipped:
|
||||
continue
|
||||
@@ -579,58 +547,22 @@ func (r *Router) lookupWithRules(ctx context.Context, rules []adapter.DNSRule, d
|
||||
return r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeAAAA, lookupOptions)
|
||||
}
|
||||
var (
|
||||
response4 []netip.Addr
|
||||
response6 []netip.Addr
|
||||
ordinaryErr4 error
|
||||
ordinaryErr6 error
|
||||
hardErr4 error
|
||||
hardErr6 error
|
||||
response4 []netip.Addr
|
||||
response6 []netip.Addr
|
||||
)
|
||||
var group task.Group
|
||||
group.Append("exchange4", func(ctx context.Context) error {
|
||||
result, err := r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeA, lookupOptions)
|
||||
response4 = result
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if E.IsClosedOrCanceled(err) {
|
||||
return err
|
||||
}
|
||||
if isLookupSplitHardError(err) {
|
||||
hardErr4 = err
|
||||
return nil
|
||||
}
|
||||
ordinaryErr4 = err
|
||||
return nil
|
||||
return err
|
||||
})
|
||||
group.Append("exchange6", func(ctx context.Context) error {
|
||||
result, err := r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeAAAA, lookupOptions)
|
||||
response6 = result
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if E.IsClosedOrCanceled(err) {
|
||||
return err
|
||||
}
|
||||
if isLookupSplitHardError(err) {
|
||||
hardErr6 = err
|
||||
return nil
|
||||
}
|
||||
ordinaryErr6 = err
|
||||
return nil
|
||||
return err
|
||||
})
|
||||
err := group.Run(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = E.Errors(hardErr4, hardErr6)
|
||||
if len(response4) == 0 && len(response6) == 0 {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, E.Errors(ordinaryErr4, ordinaryErr6)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sortAddresses(response4, response6, strategy), nil
|
||||
@@ -647,7 +579,7 @@ func (r *Router) lookupWithRulesType(ctx context.Context, rules []adapter.DNSRul
|
||||
Qclass: mDNS.ClassINET,
|
||||
}},
|
||||
}
|
||||
exchangeResult := r.exchangeWithRules(withLookupQueryMetadata(ctx, qType), rules, request, options, false, true)
|
||||
exchangeResult := r.exchangeWithRules(withLookupQueryMetadata(ctx, qType), rules, request, options, false)
|
||||
if exchangeResult.rejectAction != nil {
|
||||
return nil, exchangeResult.rejectAction.Error(ctx)
|
||||
}
|
||||
@@ -706,7 +638,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
}
|
||||
response, err = r.client.Exchange(ctx, transport, message, options, nil)
|
||||
} else if !legacyDNSMode {
|
||||
exchangeResult := r.exchangeWithRules(ctx, rules, message, options, true, false)
|
||||
exchangeResult := r.exchangeWithRules(ctx, rules, message, options, true)
|
||||
response, transport, err = exchangeResult.response, exchangeResult.transport, exchangeResult.err
|
||||
} else {
|
||||
var (
|
||||
|
||||
@@ -1855,101 +1855,6 @@ func TestLookupLegacyDNSModeDisabledAllowsPartialSuccessForExchangeFailure(t *te
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("1.1.1.1")}, addresses)
|
||||
}
|
||||
|
||||
func TestLookupLegacyDNSModeDisabledRespondWithoutEvaluatedResponseIsHardError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
||||
router := newTestRouter(t, []option.DNSRule{
|
||||
{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
||||
Domain: badoption.Listable[string]{"example.com"},
|
||||
},
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeEvaluate,
|
||||
RouteOptions: option.DNSRouteActionOptions{Server: "upstream"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
||||
Domain: badoption.Listable[string]{"example.com"},
|
||||
},
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeRespond,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &fakeDNSTransportManager{
|
||||
defaultTransport: defaultTransport,
|
||||
transports: map[string]adapter.DNSTransport{
|
||||
"default": defaultTransport,
|
||||
"upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP},
|
||||
},
|
||||
}, &fakeDNSClient{
|
||||
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
require.Equal(t, "upstream", transport.Tag())
|
||||
switch message.Question[0].Qtype {
|
||||
case mDNS.TypeA:
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil
|
||||
case mDNS.TypeAAAA:
|
||||
return nil, E.New("upstream exchange failed")
|
||||
default:
|
||||
return nil, E.New("unexpected qtype")
|
||||
}
|
||||
},
|
||||
})
|
||||
router.legacyDNSMode = false
|
||||
|
||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{})
|
||||
require.Nil(t, addresses)
|
||||
require.ErrorContains(t, err, dnsRespondMissingResponseMessage)
|
||||
}
|
||||
|
||||
func TestLookupLegacyDNSModeDisabledTransportNotFoundIsHardError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
||||
router := newTestRouter(t, []option.DNSRule{{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
||||
Domain: badoption.Listable[string]{"example.com"},
|
||||
QueryType: badoption.Listable[option.DNSQueryType]{option.DNSQueryType(mDNS.TypeAAAA)},
|
||||
},
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeRoute,
|
||||
RouteOptions: option.DNSRouteActionOptions{Server: "missing"},
|
||||
},
|
||||
},
|
||||
}}, &fakeDNSTransportManager{
|
||||
defaultTransport: defaultTransport,
|
||||
transports: map[string]adapter.DNSTransport{
|
||||
"default": defaultTransport,
|
||||
},
|
||||
}, &fakeDNSClient{
|
||||
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
require.Equal(t, "default", transport.Tag())
|
||||
switch message.Question[0].Qtype {
|
||||
case mDNS.TypeA:
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil
|
||||
case mDNS.TypeAAAA:
|
||||
return FixedResponse(0, message.Question[0], nil, 60), nil
|
||||
default:
|
||||
return nil, E.New("unexpected qtype")
|
||||
}
|
||||
},
|
||||
})
|
||||
router.legacyDNSMode = false
|
||||
|
||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{})
|
||||
require.Nil(t, addresses)
|
||||
require.ErrorContains(t, err, "transport not found: missing")
|
||||
}
|
||||
|
||||
func TestLookupLegacyDNSModeDisabledAllowsPartialSuccessForRcodeError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user