mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-09-15 21:00:27 +00:00
Add query_client_subnet and query_dnssec DNS rule items and remove_client_subnet DNS route action option
This commit is contained in:
@@ -42,6 +42,7 @@ type DNSQueryOptions struct {
|
||||
RewriteTTL *uint32
|
||||
Timeout time.Duration
|
||||
ClientSubnet netip.Prefix
|
||||
RemoveClientSubnet bool
|
||||
}
|
||||
|
||||
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (DNSQueryOptions, error) {
|
||||
|
||||
@@ -94,6 +94,8 @@ type InboundContext struct {
|
||||
SourceMACAddress net.HardwareAddr
|
||||
SourceHostname string
|
||||
QueryType uint16
|
||||
QueryClientSubnet netip.Prefix
|
||||
QueryDNSSEC bool
|
||||
FakeIP bool
|
||||
PreMatch bool
|
||||
|
||||
|
||||
+12
-6
@@ -107,12 +107,15 @@ func (k dnsCacheKey) persistentName() string {
|
||||
}
|
||||
|
||||
func (c *Client) newCacheKey(transport adapter.DNSTransport, question dns.Question, message *dns.Msg, options adapter.DNSQueryOptions) dnsCacheKey {
|
||||
clientSubnet := options.ClientSubnet
|
||||
if !clientSubnet.IsValid() {
|
||||
clientSubnet = c.clientSubnet
|
||||
}
|
||||
if !clientSubnet.IsValid() {
|
||||
clientSubnet = clientSubnetFromMessage(message)
|
||||
var clientSubnet netip.Prefix
|
||||
if !options.RemoveClientSubnet {
|
||||
clientSubnet = options.ClientSubnet
|
||||
if !clientSubnet.IsValid() {
|
||||
clientSubnet = c.clientSubnet
|
||||
}
|
||||
if !clientSubnet.IsValid() {
|
||||
clientSubnet = clientSubnetFromMessage(message)
|
||||
}
|
||||
}
|
||||
return dnsCacheKey{
|
||||
Question: question,
|
||||
@@ -679,6 +682,9 @@ func (c *Client) backgroundRefreshDNS(transport adapter.DNSTransport, key dnsCac
|
||||
}
|
||||
|
||||
func (c *Client) prepareExchangeMessage(message *dns.Msg, options adapter.DNSQueryOptions) *dns.Msg {
|
||||
if options.RemoveClientSubnet {
|
||||
return removeClientSubnet(message)
|
||||
}
|
||||
clientSubnet := options.ClientSubnet
|
||||
if !clientSubnet.IsValid() {
|
||||
clientSubnet = c.clientSubnet
|
||||
|
||||
@@ -2,6 +2,9 @@ package dns
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
@@ -25,12 +28,37 @@ func clientSubnetFromMessage(message *dns.Msg) netip.Prefix {
|
||||
if !addressLoaded {
|
||||
return netip.Prefix{}
|
||||
}
|
||||
return netip.PrefixFrom(address, int(subnetOption.SourceNetmask))
|
||||
return netip.PrefixFrom(address.Unmap(), int(subnetOption.SourceNetmask))
|
||||
}
|
||||
}
|
||||
return netip.Prefix{}
|
||||
}
|
||||
|
||||
func removeClientSubnet(message *dns.Msg) *dns.Msg {
|
||||
if !slices.ContainsFunc(message.Extra, func(record dns.RR) bool {
|
||||
optRecord, isOPTRecord := record.(*dns.OPT)
|
||||
if !isOPTRecord {
|
||||
return false
|
||||
}
|
||||
return slices.ContainsFunc(optRecord.Option, func(option dns.EDNS0) bool {
|
||||
return option.Option() == dns.EDNS0SUBNET
|
||||
})
|
||||
}) {
|
||||
return message
|
||||
}
|
||||
message = message.Copy()
|
||||
for _, record := range message.Extra {
|
||||
optRecord, isOPTRecord := record.(*dns.OPT)
|
||||
if !isOPTRecord {
|
||||
continue
|
||||
}
|
||||
optRecord.Option = common.Filter(optRecord.Option, func(option dns.EDNS0) bool {
|
||||
return option.Option() != dns.EDNS0SUBNET
|
||||
})
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
func setClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, clone bool) *dns.Msg {
|
||||
var (
|
||||
optRecord *dns.OPT
|
||||
|
||||
@@ -326,6 +326,11 @@ func (r *Router) matchDNS(ctx context.Context, rules []adapter.DNSRule, allowFak
|
||||
}
|
||||
if action.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = action.ClientSubnet
|
||||
options.RemoveClientSubnet = false
|
||||
}
|
||||
if action.RemoveClientSubnet {
|
||||
options.ClientSubnet = netip.Prefix{}
|
||||
options.RemoveClientSubnet = true
|
||||
}
|
||||
return transport, currentRule, currentRuleIndex
|
||||
case *R.RuleActionDNSRouteOptions:
|
||||
@@ -343,6 +348,11 @@ func (r *Router) matchDNS(ctx context.Context, rules []adapter.DNSRule, allowFak
|
||||
}
|
||||
if action.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = action.ClientSubnet
|
||||
options.RemoveClientSubnet = false
|
||||
}
|
||||
if action.RemoveClientSubnet {
|
||||
options.ClientSubnet = netip.Prefix{}
|
||||
options.RemoveClientSubnet = true
|
||||
}
|
||||
case *R.RuleActionReject:
|
||||
return nil, currentRule, currentRuleIndex
|
||||
@@ -373,6 +383,11 @@ func (r *Router) applyDNSRouteOptions(options *adapter.DNSQueryOptions, routeOpt
|
||||
}
|
||||
if routeOptions.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = routeOptions.ClientSubnet
|
||||
options.RemoveClientSubnet = false
|
||||
}
|
||||
if routeOptions.RemoveClientSubnet {
|
||||
options.ClientSubnet = netip.Prefix{}
|
||||
options.RemoveClientSubnet = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -945,6 +960,8 @@ func (r *Router) resolveLookupStrategy(options adapter.DNSQueryOptions) C.Domain
|
||||
func withLookupQueryMetadata(ctx context.Context, qType uint16) context.Context {
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
metadata.QueryType = qType
|
||||
metadata.QueryClientSubnet = netip.Prefix{}
|
||||
metadata.QueryDNSSEC = false
|
||||
metadata.IPVersion = 0
|
||||
switch qType {
|
||||
case mDNS.TypeA:
|
||||
@@ -1069,6 +1086,9 @@ func (r *Router) prepareExchange(ctx context.Context, message *mDNS.Msg) (*dnsEx
|
||||
metadata.IPVersion = 6
|
||||
}
|
||||
metadata.Domain = FqdnToDomain(message.Question[0].Name)
|
||||
metadata.QueryClientSubnet = clientSubnetFromMessage(message)
|
||||
edns0Option := message.IsEdns0()
|
||||
metadata.QueryDNSSEC = edns0Option != nil && edns0Option.Do()
|
||||
return &dnsExchangeContext{
|
||||
ctx: ctx,
|
||||
rules: rules,
|
||||
|
||||
@@ -14,6 +14,8 @@ icon: material/alert-decagram
|
||||
:material-plus: [response_ns](#response_ns)
|
||||
:material-plus: [response_extra](#response_extra)
|
||||
:material-plus: [package_name_regex](#package_name_regex)
|
||||
:material-plus: [query_client_subnet](#query_client_subnet)
|
||||
:material-plus: [query_dnssec](#query_dnssec)
|
||||
:material-alert: [ip_version](#ip_version)
|
||||
:material-alert: [query_type](#query_type)
|
||||
|
||||
@@ -77,6 +79,11 @@ icon: material/alert-decagram
|
||||
"HTTPS",
|
||||
32768
|
||||
],
|
||||
"query_client_subnet": [
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"query_dnssec": false,
|
||||
"network": "tcp",
|
||||
"auth_user": [
|
||||
"usera",
|
||||
@@ -292,6 +299,22 @@ Not limited if empty.
|
||||
|
||||
DNS query type. Values can be integers or type name strings.
|
||||
|
||||
#### query_client_subnet
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
Match the `edns0-subnet` OPT extra record (EDNS Client Subnet) in the query.
|
||||
|
||||
A listed prefix matches when it is no more specific than the received client subnet and contains its address.
|
||||
|
||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||
|
||||
#### query_dnssec
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
Match queries with the DNSSEC OK (`DO`) bit set.
|
||||
|
||||
#### network
|
||||
|
||||
`tcp` or `udp`.
|
||||
|
||||
@@ -14,6 +14,8 @@ icon: material/alert-decagram
|
||||
:material-plus: [response_ns](#response_ns)
|
||||
:material-plus: [response_extra](#response_extra)
|
||||
:material-plus: [package_name_regex](#package_name_regex)
|
||||
:material-plus: [query_client_subnet](#query_client_subnet)
|
||||
:material-plus: [query_dnssec](#query_dnssec)
|
||||
:material-alert: [ip_version](#ip_version)
|
||||
:material-alert: [query_type](#query_type)
|
||||
|
||||
@@ -77,6 +79,11 @@ icon: material/alert-decagram
|
||||
"HTTPS",
|
||||
32768
|
||||
],
|
||||
"query_client_subnet": [
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"query_dnssec": false,
|
||||
"network": "tcp",
|
||||
"auth_user": [
|
||||
"usera",
|
||||
@@ -284,6 +291,22 @@ icon: material/alert-decagram
|
||||
|
||||
DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
#### query_client_subnet
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
匹配查询中的 `edns0-subnet` OPT 附加记录(EDNS 客户端子网)。
|
||||
|
||||
列出的前缀在不比收到的客户端子网更具体、且包含其地址时匹配。
|
||||
|
||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
||||
|
||||
#### query_dnssec
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
匹配设置了 DNSSEC OK (`DO`) 位的查询。
|
||||
|
||||
#### network
|
||||
|
||||
`tcp` 或 `udp`。
|
||||
|
||||
@@ -10,7 +10,8 @@ icon: material/new-box
|
||||
:material-plus: [disable_optimistic_cache](#disable_optimistic_cache)
|
||||
:material-plus: [timeout](#timeout)
|
||||
:material-plus: [race](#race)
|
||||
:material-plus: [speculative](#speculative)
|
||||
:material-plus: [speculative](#speculative)
|
||||
:material-plus: [remove_client_subnet](#remove_client_subnet)
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
@@ -68,7 +69,8 @@ matched. The result may therefore depend on server speed only among race rules.
|
||||
"disable_optimistic_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"timeout": "",
|
||||
"client_subnet": null
|
||||
"client_subnet": null,
|
||||
"remove_client_subnet": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -135,6 +137,14 @@ If value is an IP address instead of prefix, `/32` or `/128` will be appended au
|
||||
|
||||
Will override `dns.client_subnet`.
|
||||
|
||||
#### remove_client_subnet
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
Remove the `edns0-subnet` OPT extra record from the query, and suppress `dns.client_subnet`.
|
||||
|
||||
Conflict with `client_subnet`.
|
||||
|
||||
### evaluate
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
@@ -149,7 +159,8 @@ Will override `dns.client_subnet`.
|
||||
"disable_optimistic_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"timeout": "",
|
||||
"client_subnet": null
|
||||
"client_subnet": null,
|
||||
"remove_client_subnet": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -217,6 +228,14 @@ If value is an IP address instead of prefix, `/32` or `/128` will be appended au
|
||||
|
||||
Will override `dns.client_subnet`.
|
||||
|
||||
#### remove_client_subnet
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
Remove the `edns0-subnet` OPT extra record from the query, and suppress `dns.client_subnet`.
|
||||
|
||||
Conflict with `client_subnet`.
|
||||
|
||||
### respond
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
@@ -242,7 +261,8 @@ Only allowed after a preceding top-level `evaluate` rule. If the action is reach
|
||||
"disable_optimistic_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"timeout": "",
|
||||
"client_subnet": null
|
||||
"client_subnet": null,
|
||||
"remove_client_subnet": false
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ icon: material/new-box
|
||||
:material-plus: [disable_optimistic_cache](#disable_optimistic_cache)
|
||||
:material-plus: [timeout](#timeout)
|
||||
:material-plus: [race](#race)
|
||||
:material-plus: [speculative](#speculative)
|
||||
:material-plus: [speculative](#speculative)
|
||||
:material-plus: [remove_client_subnet](#remove_client_subnet)
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
@@ -61,7 +62,8 @@ icon: material/new-box
|
||||
"disable_optimistic_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"timeout": "",
|
||||
"client_subnet": null
|
||||
"client_subnet": null,
|
||||
"remove_client_subnet": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -125,6 +127,14 @@ icon: material/new-box
|
||||
|
||||
将覆盖 `dns.client_subnet`.
|
||||
|
||||
#### remove_client_subnet
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
移除查询中的 `edns0-subnet` OPT 附加记录,并抑制 `dns.client_subnet`。
|
||||
|
||||
与 `client_subnet` 冲突。
|
||||
|
||||
### evaluate
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
@@ -139,7 +149,8 @@ icon: material/new-box
|
||||
"disable_optimistic_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"timeout": "",
|
||||
"client_subnet": null
|
||||
"client_subnet": null,
|
||||
"remove_client_subnet": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -203,6 +214,14 @@ icon: material/new-box
|
||||
|
||||
将覆盖 `dns.client_subnet`.
|
||||
|
||||
#### remove_client_subnet
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
移除查询中的 `edns0-subnet` OPT 附加记录,并抑制 `dns.client_subnet`。
|
||||
|
||||
与 `client_subnet` 冲突。
|
||||
|
||||
### respond
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
@@ -228,7 +247,8 @@ icon: material/new-box
|
||||
"disable_optimistic_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"timeout": "",
|
||||
"client_subnet": null
|
||||
"client_subnet": null,
|
||||
"remove_client_subnet": false
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1082,6 +1082,22 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"query_client_subnet": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"query_dnssec": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"network": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -1680,6 +1696,9 @@
|
||||
},
|
||||
"client_subnet": {
|
||||
"type": "string"
|
||||
},
|
||||
"remove_client_subnet": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1718,6 +1737,9 @@
|
||||
},
|
||||
"client_subnet": {
|
||||
"type": "string"
|
||||
},
|
||||
"remove_client_subnet": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1763,6 +1785,9 @@
|
||||
},
|
||||
"client_subnet": {
|
||||
"type": "string"
|
||||
},
|
||||
"remove_client_subnet": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -8692,6 +8717,22 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"query_client_subnet": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"query_dnssec": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"network": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -230,6 +230,7 @@ type AbstractDNSRouteActionOptions struct {
|
||||
DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"`
|
||||
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
||||
RemoveClientSubnet bool `json:"remove_client_subnet,omitempty"`
|
||||
}
|
||||
|
||||
type DNSRouteOptionsActionOptions AbstractDNSRouteActionOptions
|
||||
|
||||
@@ -138,6 +138,8 @@ type RawDefaultDNSRule struct {
|
||||
Inbound badoption.Listable[string] `json:"inbound,omitempty" reference:"inbound"`
|
||||
IPVersion int `json:"ip_version,omitempty" enum:"4,6"`
|
||||
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
||||
QueryClientSubnet badoption.Listable[*badoption.Prefixable] `json:"query_client_subnet,omitempty"`
|
||||
QueryDNSSEC bool `json:"query_dnssec,omitempty"`
|
||||
Network badoption.Listable[string] `json:"network,omitempty" enum:"tcp,udp"`
|
||||
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
|
||||
Protocol badoption.Listable[string] `json:"protocol,omitempty" enum:"tls,http,quic,dns,stun,bittorrent,dtls,ssh,rdp,ntp"`
|
||||
|
||||
@@ -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, ","), ")")
|
||||
}
|
||||
|
||||
|
||||
@@ -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), " ") + "]"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user