Add custom prefix support in EDNS0 client subnet options

This commit is contained in:
世界 2024-05-12 15:06:21 +08:00
parent 1fa7de078b
commit 3f6f879780
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
15 changed files with 96 additions and 41 deletions

View file

@ -86,7 +86,7 @@ type DNSRule interface {
Rule Rule
DisableCache() bool DisableCache() bool
RewriteTTL() *uint32 RewriteTTL() *uint32
ClientSubnet() *netip.Addr ClientSubnet() *netip.Prefix
WithAddressLimit() bool WithAddressLimit() bool
MatchAddressLimit(metadata *InboundContext) bool MatchAddressLimit(metadata *InboundContext) bool
} }

View file

@ -73,6 +73,8 @@ problematic in environments such as macOS, where DNS is proxied and cached by th
!!! question "Since sing-box 1.9.0" !!! question "Since sing-box 1.9.0"
Append a `edns0-subnet` OPT extra record with the specified IP address to every query by default. Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Can be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`. Can be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`.

View file

@ -71,8 +71,10 @@ icon: material/new-box
!!! question "自 sing-box 1.9.0 起" !!! question "自 sing-box 1.9.0 起"
默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。 默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
如果值是 IP 地址而不是前缀,则会自动附加 `/32``/128`
可以被 `servers.[].client_subnet``rules.[].client_subnet` 覆盖。 可以被 `servers.[].client_subnet``rules.[].client_subnet` 覆盖。
#### fakeip #### fakeip

View file

@ -125,7 +125,7 @@ icon: material/new-box
"server": "local", "server": "local",
"disable_cache": false, "disable_cache": false,
"rewrite_ttl": 100, "rewrite_ttl": 100,
"client_subnet": "127.0.0.1" "client_subnet": "127.0.0.1/24"
}, },
{ {
"type": "logical", "type": "logical",
@ -134,7 +134,7 @@ icon: material/new-box
"server": "local", "server": "local",
"disable_cache": false, "disable_cache": false,
"rewrite_ttl": 100, "rewrite_ttl": 100,
"client_subnet": "127.0.0.1" "client_subnet": "127.0.0.1/24"
} }
] ]
} }
@ -339,7 +339,9 @@ Rewrite TTL in DNS responses.
!!! question "Since sing-box 1.9.0" !!! question "Since sing-box 1.9.0"
Append a `edns0-subnet` OPT extra record with the specified IP address to every query by default. Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Will overrides `dns.client_subnet` and `servers.[].client_subnet`. Will overrides `dns.client_subnet` and `servers.[].client_subnet`.

View file

@ -124,7 +124,7 @@ icon: material/new-box
], ],
"server": "local", "server": "local",
"disable_cache": false, "disable_cache": false,
"client_subnet": "127.0.0.1" "client_subnet": "127.0.0.1/24"
}, },
{ {
"type": "logical", "type": "logical",
@ -132,7 +132,7 @@ icon: material/new-box
"rules": [], "rules": [],
"server": "local", "server": "local",
"disable_cache": false, "disable_cache": false,
"client_subnet": "127.0.0.1" "client_subnet": "127.0.0.1/24"
} }
] ]
} }
@ -337,7 +337,9 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
!!! question "自 sing-box 1.9.0 起" !!! question "自 sing-box 1.9.0 起"
默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。 默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
如果值是 IP 地址而不是前缀,则会自动附加 `/32``/128`
将覆盖 `dns.client_subnet``servers.[].client_subnet` 将覆盖 `dns.client_subnet``servers.[].client_subnet`

View file

@ -100,7 +100,9 @@ Default outbound will be used if empty.
!!! question "Since sing-box 1.9.0" !!! question "Since sing-box 1.9.0"
Append a `edns0-subnet` OPT extra record with the specified IP address to every query by default. Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Can be overrides by `rules.[].client_subnet`. Can be overrides by `rules.[].client_subnet`.

View file

@ -100,7 +100,9 @@ DNS 服务器的地址。
!!! question "自 sing-box 1.9.0 起" !!! question "自 sing-box 1.9.0 起"
默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。 默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
如果值是 IP 地址而不是前缀,则会自动附加 `/32``/128`
可以被 `rules.[].client_subnet` 覆盖。 可以被 `rules.[].client_subnet` 覆盖。

View file

@ -441,7 +441,7 @@ flowchart TB
{ {
"rule_set": "geoip-cn", "rule_set": "geoip-cn",
"server": "google", "server": "google",
"client_subnet": "114.114.114.114" // Any China client IP address "client_subnet": "114.114.114.114/24" // Any China client IP address
} }
] ]
}, },

View file

@ -57,6 +57,7 @@ type CacheFile struct {
type saveRDRCCacheKey struct { type saveRDRCCacheKey struct {
TransportName string TransportName string
QuestionName string QuestionName string
QType uint16
} }
func New(ctx context.Context, options option.CacheFileOptions) *CacheFile { func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {

View file

@ -9,7 +9,7 @@ import (
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
) )
var bucketRDRC = []byte("rdrc") var bucketRDRC = []byte("rdrc2")
func (c *CacheFile) StoreRDRC() bool { func (c *CacheFile) StoreRDRC() bool {
return c.storeRDRC return c.storeRDRC
@ -19,13 +19,17 @@ func (c *CacheFile) RDRCTimeout() time.Duration {
return c.rdrcTimeout return c.rdrcTimeout
} }
func (c *CacheFile) LoadRDRC(transportName string, qName string) (rejected bool) { func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) (rejected bool) {
c.saveRDRCAccess.RLock() c.saveRDRCAccess.RLock()
rejected, cached := c.saveRDRC[saveRDRCCacheKey{transportName, qName}] rejected, cached := c.saveRDRC[saveRDRCCacheKey{transportName, qName, qType}]
c.saveRDRCAccess.RUnlock() c.saveRDRCAccess.RUnlock()
if cached { if cached {
return return
} }
key := buf.Get(2 + len(qName))
binary.BigEndian.PutUint16(key, qType)
copy(key[2:], qName)
defer buf.Put(key)
var deleteCache bool var deleteCache bool
err := c.DB.View(func(tx *bbolt.Tx) error { err := c.DB.View(func(tx *bbolt.Tx) error {
bucket := c.bucket(tx, bucketRDRC) bucket := c.bucket(tx, bucketRDRC)
@ -36,7 +40,7 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string) (rejected bool)
if bucket == nil { if bucket == nil {
return nil return nil
} }
content := bucket.Get([]byte(qName)) content := bucket.Get(key)
if content == nil { if content == nil {
return nil return nil
} }
@ -61,13 +65,13 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string) (rejected bool)
if bucket == nil { if bucket == nil {
return nil return nil
} }
return bucket.Delete([]byte(qName)) return bucket.Delete(key)
}) })
} }
return return
} }
func (c *CacheFile) SaveRDRC(transportName string, qName string) error { func (c *CacheFile) SaveRDRC(transportName string, qName string, qType uint16) error {
return c.DB.Batch(func(tx *bbolt.Tx) error { return c.DB.Batch(func(tx *bbolt.Tx) error {
bucket, err := c.createBucket(tx, bucketRDRC) bucket, err := c.createBucket(tx, bucketRDRC)
if err != nil { if err != nil {
@ -77,20 +81,24 @@ func (c *CacheFile) SaveRDRC(transportName string, qName string) error {
if err != nil { if err != nil {
return err return err
} }
key := buf.Get(2 + len(qName))
binary.BigEndian.PutUint16(key, qType)
copy(key[2:], qName)
defer buf.Put(key)
expiresAt := buf.Get(8) expiresAt := buf.Get(8)
defer buf.Put(expiresAt) defer buf.Put(expiresAt)
binary.BigEndian.PutUint64(expiresAt, uint64(time.Now().Add(c.rdrcTimeout).Unix())) binary.BigEndian.PutUint64(expiresAt, uint64(time.Now().Add(c.rdrcTimeout).Unix()))
return bucket.Put([]byte(qName), expiresAt) return bucket.Put(key, expiresAt)
}) })
} }
func (c *CacheFile) SaveRDRCAsync(transportName string, qName string, logger logger.Logger) { func (c *CacheFile) SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger) {
saveKey := saveRDRCCacheKey{transportName, qName} saveKey := saveRDRCCacheKey{transportName, qName, qType}
c.saveRDRCAccess.Lock() c.saveRDRCAccess.Lock()
c.saveRDRC[saveKey] = true c.saveRDRC[saveKey] = true
c.saveRDRCAccess.Unlock() c.saveRDRCAccess.Unlock()
go func() { go func() {
err := c.SaveRDRC(transportName, qName) err := c.SaveRDRC(transportName, qName, qType)
if err != nil { if err != nil {
logger.Warn("save RDRC: ", err) logger.Warn("save RDRC: ", err)
} }

View file

@ -19,7 +19,7 @@ type DNSServerOptions struct {
AddressFallbackDelay Duration `json:"address_fallback_delay,omitempty"` AddressFallbackDelay Duration `json:"address_fallback_delay,omitempty"`
Strategy DomainStrategy `json:"strategy,omitempty"` Strategy DomainStrategy `json:"strategy,omitempty"`
Detour string `json:"detour,omitempty"` Detour string `json:"detour,omitempty"`
ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
} }
type DNSClientOptions struct { type DNSClientOptions struct {
@ -27,7 +27,7 @@ type DNSClientOptions struct {
DisableCache bool `json:"disable_cache,omitempty"` DisableCache bool `json:"disable_cache,omitempty"`
DisableExpire bool `json:"disable_expire,omitempty"` DisableExpire bool `json:"disable_expire,omitempty"`
IndependentCache bool `json:"independent_cache,omitempty"` IndependentCache bool `json:"independent_cache,omitempty"`
ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
} }
type DNSFakeIPOptions struct { type DNSFakeIPOptions struct {

View file

@ -101,7 +101,7 @@ type DefaultDNSRule struct {
Server string `json:"server,omitempty"` Server string `json:"server,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"` DisableCache bool `json:"disable_cache,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
} }
func (r DefaultDNSRule) IsValid() bool { func (r DefaultDNSRule) IsValid() bool {
@ -115,13 +115,13 @@ func (r DefaultDNSRule) IsValid() bool {
} }
type LogicalDNSRule struct { type LogicalDNSRule struct {
Mode string `json:"mode"` Mode string `json:"mode"`
Rules []DNSRule `json:"rules,omitempty"` Rules []DNSRule `json:"rules,omitempty"`
Invert bool `json:"invert,omitempty"` Invert bool `json:"invert,omitempty"`
Server string `json:"server,omitempty"` Server string `json:"server,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"` DisableCache bool `json:"disable_cache,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
} }
func (r LogicalDNSRule) IsValid() bool { func (r LogicalDNSRule) IsValid() bool {

View file

@ -51,6 +51,40 @@ func (a *ListenAddress) Build() netip.Addr {
return (netip.Addr)(*a) return (netip.Addr)(*a)
} }
type AddrPrefix netip.Prefix
func (a AddrPrefix) MarshalJSON() ([]byte, error) {
prefix := netip.Prefix(a)
if prefix.Bits() == prefix.Addr().BitLen() {
return json.Marshal(prefix.Addr().String())
} else {
return json.Marshal(prefix.String())
}
}
func (a *AddrPrefix) UnmarshalJSON(content []byte) error {
var value string
err := json.Unmarshal(content, &value)
if err != nil {
return err
}
prefix, prefixErr := netip.ParsePrefix(value)
if prefixErr == nil {
*a = AddrPrefix(prefix)
return nil
}
addr, addrErr := netip.ParseAddr(value)
if addrErr == nil {
*a = AddrPrefix(netip.PrefixFrom(addr, addr.BitLen()))
return nil
}
return prefixErr
}
func (a AddrPrefix) Build() netip.Prefix {
return netip.Prefix(a)
}
type NetworkList string type NetworkList string
func (v *NetworkList) UnmarshalJSON(content []byte) error { func (v *NetworkList) UnmarshalJSON(content []byte) error {

View file

@ -27,7 +27,7 @@ import (
"github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing-box/outbound"
"github.com/sagernet/sing-box/transport/fakeip" "github.com/sagernet/sing-box/transport/fakeip"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
mux "github.com/sagernet/sing-mux" "github.com/sagernet/sing-mux"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing-vmess" "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
@ -235,7 +235,7 @@ func NewRouter(
return nil, E.New("parse dns server[", tag, "]: missing address_resolver") return nil, E.New("parse dns server[", tag, "]: missing address_resolver")
} }
} }
var clientSubnet netip.Addr var clientSubnet netip.Prefix
if server.ClientSubnet != nil { if server.ClientSubnet != nil {
clientSubnet = server.ClientSubnet.Build() clientSubnet = server.ClientSubnet.Build()
} else if dnsOptions.ClientSubnet != nil { } else if dnsOptions.ClientSubnet != nil {

View file

@ -40,7 +40,7 @@ type DefaultDNSRule struct {
abstractDefaultRule abstractDefaultRule
disableCache bool disableCache bool
rewriteTTL *uint32 rewriteTTL *uint32
clientSubnet *netip.Addr clientSubnet *netip.Prefix
} }
func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
@ -51,7 +51,7 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
}, },
disableCache: options.DisableCache, disableCache: options.DisableCache,
rewriteTTL: options.RewriteTTL, rewriteTTL: options.RewriteTTL,
clientSubnet: (*netip.Addr)(options.ClientSubnet), clientSubnet: (*netip.Prefix)(options.ClientSubnet),
} }
if len(options.Inbound) > 0 { if len(options.Inbound) > 0 {
item := NewInboundRule(options.Inbound) item := NewInboundRule(options.Inbound)
@ -234,7 +234,7 @@ func (r *DefaultDNSRule) RewriteTTL() *uint32 {
return r.rewriteTTL return r.rewriteTTL
} }
func (r *DefaultDNSRule) ClientSubnet() *netip.Addr { func (r *DefaultDNSRule) ClientSubnet() *netip.Prefix {
return r.clientSubnet return r.clientSubnet
} }
@ -272,7 +272,7 @@ type LogicalDNSRule struct {
abstractLogicalRule abstractLogicalRule
disableCache bool disableCache bool
rewriteTTL *uint32 rewriteTTL *uint32
clientSubnet *netip.Addr clientSubnet *netip.Prefix
} }
func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
@ -284,7 +284,7 @@ func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options
}, },
disableCache: options.DisableCache, disableCache: options.DisableCache,
rewriteTTL: options.RewriteTTL, rewriteTTL: options.RewriteTTL,
clientSubnet: (*netip.Addr)(options.ClientSubnet), clientSubnet: (*netip.Prefix)(options.ClientSubnet),
} }
switch options.Mode { switch options.Mode {
case C.LogicalTypeAnd: case C.LogicalTypeAnd:
@ -312,7 +312,7 @@ func (r *LogicalDNSRule) RewriteTTL() *uint32 {
return r.rewriteTTL return r.rewriteTTL
} }
func (r *LogicalDNSRule) ClientSubnet() *netip.Addr { func (r *LogicalDNSRule) ClientSubnet() *netip.Prefix {
return r.clientSubnet return r.clientSubnet
} }