Browse Source

Add custom prefix support in EDNS0 client subnet options

世界 1 year ago
parent
commit
da9e22b4e6

+ 1 - 1
adapter/router.go

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

+ 3 - 1
docs/configuration/dns/index.md

@@ -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"
 
-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`.

+ 4 - 2
docs/configuration/dns/index.zh.md

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

+ 5 - 3
docs/configuration/dns/rule.md

@@ -125,7 +125,7 @@ icon: material/new-box
         "server": "local",
         "disable_cache": false,
         "rewrite_ttl": 100,
-        "client_subnet": "127.0.0.1"
+        "client_subnet": "127.0.0.1/24"
       },
       {
         "type": "logical",
@@ -134,7 +134,7 @@ icon: material/new-box
         "server": "local",
         "disable_cache": false,
         "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"
 
-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`.
 

+ 5 - 3
docs/configuration/dns/rule.zh.md

@@ -124,7 +124,7 @@ icon: material/new-box
         ],
         "server": "local",
         "disable_cache": false,
-        "client_subnet": "127.0.0.1"
+        "client_subnet": "127.0.0.1/24"
       },
       {
         "type": "logical",
@@ -132,7 +132,7 @@ icon: material/new-box
         "rules": [],
         "server": "local",
         "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 起"
 
-默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。
+默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
+
+如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
 
 将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。
 

+ 3 - 1
docs/configuration/dns/server.md

@@ -100,7 +100,9 @@ Default outbound will be used if empty.
 
 !!! 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`.
 

+ 3 - 1
docs/configuration/dns/server.zh.md

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

+ 1 - 1
docs/manual/proxy/client.md

@@ -441,7 +441,7 @@ flowchart TB
               {
                 "rule_set": "geoip-cn",
                 "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
               }
             ]
           },

+ 1 - 0
experimental/cachefile/cache.go

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

+ 18 - 10
experimental/cachefile/rdrc.go

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

+ 2 - 2
option/dns.go

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

+ 8 - 8
option/rule_dns.go

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

+ 34 - 0
option/types.go

@@ -51,6 +51,40 @@ func (a *ListenAddress) Build() netip.Addr {
 	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
 
 func (v *NetworkList) UnmarshalJSON(content []byte) error {

+ 2 - 2
route/router.go

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

+ 6 - 6
route/rule_dns.go

@@ -40,7 +40,7 @@ type DefaultDNSRule struct {
 	abstractDefaultRule
 	disableCache bool
 	rewriteTTL   *uint32
-	clientSubnet *netip.Addr
+	clientSubnet *netip.Prefix
 }
 
 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,
 		rewriteTTL:   options.RewriteTTL,
-		clientSubnet: (*netip.Addr)(options.ClientSubnet),
+		clientSubnet: (*netip.Prefix)(options.ClientSubnet),
 	}
 	if len(options.Inbound) > 0 {
 		item := NewInboundRule(options.Inbound)
@@ -234,7 +234,7 @@ func (r *DefaultDNSRule) RewriteTTL() *uint32 {
 	return r.rewriteTTL
 }
 
-func (r *DefaultDNSRule) ClientSubnet() *netip.Addr {
+func (r *DefaultDNSRule) ClientSubnet() *netip.Prefix {
 	return r.clientSubnet
 }
 
@@ -272,7 +272,7 @@ type LogicalDNSRule struct {
 	abstractLogicalRule
 	disableCache bool
 	rewriteTTL   *uint32
-	clientSubnet *netip.Addr
+	clientSubnet *netip.Prefix
 }
 
 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,
 		rewriteTTL:   options.RewriteTTL,
-		clientSubnet: (*netip.Addr)(options.ClientSubnet),
+		clientSubnet: (*netip.Prefix)(options.ClientSubnet),
 	}
 	switch options.Mode {
 	case C.LogicalTypeAnd:
@@ -312,7 +312,7 @@ func (r *LogicalDNSRule) RewriteTTL() *uint32 {
 	return r.rewriteTTL
 }
 
-func (r *LogicalDNSRule) ClientSubnet() *netip.Addr {
+func (r *LogicalDNSRule) ClientSubnet() *netip.Prefix {
 	return r.clientSubnet
 }