Add query_client_subnet and query_dnssec DNS rule items and remove_client_subnet DNS route action option

This commit is contained in:
世界
2026-08-30 17:41:45 +08:00
parent 4e91d92c5f
commit b3a45e0839
16 changed files with 297 additions and 15 deletions
+10
View File
@@ -139,6 +139,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
DisableOptimisticCache: action.RouteOptions.DisableOptimisticCache,
RewriteTTL: action.RouteOptions.RewriteTTL,
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
RemoveClientSubnet: action.RouteOptions.RemoveClientSubnet,
},
}
case C.RuleActionTypeEvaluate:
@@ -153,6 +154,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
DisableOptimisticCache: action.EvaluateOptions.DisableOptimisticCache,
RewriteTTL: action.EvaluateOptions.RewriteTTL,
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.EvaluateOptions.ClientSubnet)),
RemoveClientSubnet: action.EvaluateOptions.RemoveClientSubnet,
},
}
case C.RuleActionTypeRespond:
@@ -165,6 +167,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
DisableOptimisticCache: action.RouteOptionsOptions.DisableOptimisticCache,
RewriteTTL: action.RouteOptionsOptions.RewriteTTL,
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)),
RemoveClientSubnet: action.RouteOptionsOptions.RemoveClientSubnet,
}
case C.RuleActionTypeReject:
return &RuleActionReject{
@@ -349,6 +352,9 @@ func formatDNSRouteAction(action string, server string, speculative bool, option
if options.ClientSubnet.IsValid() {
descriptions = append(descriptions, F.ToString("client-subnet=", options.ClientSubnet))
}
if options.RemoveClientSubnet {
descriptions = append(descriptions, "remove-client-subnet")
}
return F.ToString(action, "(", strings.Join(descriptions, ","), ")")
}
@@ -359,6 +365,7 @@ type RuleActionDNSRouteOptions struct {
DisableOptimisticCache bool
RewriteTTL *uint32
ClientSubnet netip.Prefix
RemoveClientSubnet bool
}
func (r *RuleActionDNSRouteOptions) Type() string {
@@ -382,6 +389,9 @@ func (r *RuleActionDNSRouteOptions) String() string {
if r.ClientSubnet.IsValid() {
descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet))
}
if r.RemoveClientSubnet {
descriptions = append(descriptions, "remove-client-subnet")
}
return F.ToString("route-options(", strings.Join(descriptions, ","), ")")
}
+22
View File
@@ -73,6 +73,18 @@ func validateDNSRuleAction(action option.DNSRuleAction) error {
if action.Action == C.RuleActionTypeReject && action.RejectOptions.Method == C.RuleActionRejectMethodReply {
return E.New("reject method `reply` is not supported for DNS rules")
}
var routeOptions option.AbstractDNSRouteActionOptions
switch action.Action {
case "", C.RuleActionTypeRoute:
routeOptions = action.RouteOptions.AbstractDNSRouteActionOptions
case C.RuleActionTypeEvaluate:
routeOptions = action.EvaluateOptions.AbstractDNSRouteActionOptions
case C.RuleActionTypeRouteOptions:
routeOptions = option.AbstractDNSRouteActionOptions(action.RouteOptionsOptions)
}
if routeOptions.RemoveClientSubnet && routeOptions.ClientSubnet != nil {
return E.New("`client_subnet` and `remove_client_subnet` are mutually exclusive")
}
if action.Race {
switch action.Action {
case "", C.RuleActionTypeRoute, C.RuleActionTypeRespond, C.RuleActionTypeReject, C.RuleActionTypePredefined:
@@ -127,6 +139,16 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.QueryClientSubnet) > 0 {
item := NewQueryClientSubnetItem(options.QueryClientSubnet)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if options.QueryDNSSEC {
item := NewQueryDNSSECItem()
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.Network) > 0 {
item := NewNetworkItem(options.Network)
rule.items = append(rule.items, item)
@@ -0,0 +1,42 @@
package rule
import (
"net/netip"
"slices"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/json/badoption"
)
var _ RuleItem = (*QueryClientSubnetItem)(nil)
type QueryClientSubnetItem struct {
prefixes []netip.Prefix
}
func NewQueryClientSubnetItem(prefixables badoption.Listable[*badoption.Prefixable]) *QueryClientSubnetItem {
return &QueryClientSubnetItem{
prefixes: common.Map(prefixables, func(it *badoption.Prefixable) netip.Prefix {
return it.Build(netip.Prefix{})
}),
}
}
func (r *QueryClientSubnetItem) Match(metadata *adapter.InboundContext) bool {
clientSubnet := metadata.QueryClientSubnet
if !clientSubnet.IsValid() {
return false
}
return slices.ContainsFunc(r.prefixes, func(prefix netip.Prefix) bool {
return clientSubnet.Bits() >= prefix.Bits() && prefix.Contains(clientSubnet.Addr())
})
}
func (r *QueryClientSubnetItem) String() string {
if len(r.prefixes) == 1 {
return "query_client_subnet=" + r.prefixes[0].String()
}
return "query_client_subnet=[" + strings.Join(common.Map(r.prefixes, netip.Prefix.String), " ") + "]"
}
+21
View File
@@ -0,0 +1,21 @@
package rule
import (
"github.com/sagernet/sing-box/adapter"
)
var _ RuleItem = (*QueryDNSSECItem)(nil)
type QueryDNSSECItem struct{}
func NewQueryDNSSECItem() *QueryDNSSECItem {
return &QueryDNSSECItem{}
}
func (r *QueryDNSSECItem) Match(metadata *adapter.InboundContext) bool {
return metadata.QueryDNSSEC
}
func (r *QueryDNSSECItem) String() string {
return "query_dnssec=true"
}