dns: ignore split lookup errors on partial success

This commit is contained in:
世界
2026-04-02 20:18:36 +08:00
parent afe6137cc9
commit bd545e2ccc
2 changed files with 10 additions and 173 deletions

View File

@@ -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 (

View File

@@ -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()