世界 2 роки тому
батько
коміт
9bca5a517f

+ 2 - 0
adapter/experimental.go

@@ -13,6 +13,7 @@ type ClashServer interface {
 	PreStarter
 	Mode() string
 	StoreSelected() bool
+	StoreFakeIP() bool
 	CacheFile() ClashCacheFile
 	HistoryStorage() *urltest.HistoryStorage
 	RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
@@ -22,6 +23,7 @@ type ClashServer interface {
 type ClashCacheFile interface {
 	LoadSelected(group string) string
 	StoreSelected(group string, selected string) error
+	FakeIPStorage
 }
 
 type Tracker interface {

+ 23 - 0
adapter/fakeip.go

@@ -0,0 +1,23 @@
+package adapter
+
+import (
+	"net/netip"
+
+	"github.com/sagernet/sing-dns"
+)
+
+type FakeIPStore interface {
+	Service
+	Contains(address netip.Addr) bool
+	Create(domain string, strategy dns.DomainStrategy) (netip.Addr, error)
+	Lookup(address netip.Addr) (string, bool)
+	Reset() error
+}
+
+type FakeIPStorage interface {
+	FakeIPMetadata() *FakeIPMetadata
+	FakeIPSaveMetadata(metadata *FakeIPMetadata) error
+	FakeIPStore(address netip.Addr, domain string) error
+	FakeIPLoad(address netip.Addr) (string, bool)
+	FakeIPReset() error
+}

+ 50 - 0
adapter/fakeip_metadata.go

@@ -0,0 +1,50 @@
+package adapter
+
+import (
+	"bytes"
+	"encoding"
+	"encoding/binary"
+	"io"
+	"net/netip"
+
+	"github.com/sagernet/sing/common"
+)
+
+type FakeIPMetadata struct {
+	Inet4Range   netip.Prefix
+	Inet6Range   netip.Prefix
+	Inet4Current netip.Addr
+	Inet6Current netip.Addr
+}
+
+func (m *FakeIPMetadata) MarshalBinary() (data []byte, err error) {
+	var buffer bytes.Buffer
+	for _, marshaler := range []encoding.BinaryMarshaler{m.Inet4Range, m.Inet6Range, m.Inet4Current, m.Inet6Current} {
+		data, err = marshaler.MarshalBinary()
+		if err != nil {
+			return
+		}
+		common.Must(binary.Write(&buffer, binary.BigEndian, uint16(len(data))))
+		buffer.Write(data)
+	}
+	data = buffer.Bytes()
+	return
+}
+
+func (m *FakeIPMetadata) UnmarshalBinary(data []byte) error {
+	reader := bytes.NewReader(data)
+	for _, unmarshaler := range []encoding.BinaryUnmarshaler{&m.Inet4Range, &m.Inet6Range, &m.Inet4Current, &m.Inet6Current} {
+		var length uint16
+		common.Must(binary.Read(reader, binary.BigEndian, &length))
+		element := make([]byte, length)
+		_, err := io.ReadFull(reader, element)
+		if err != nil {
+			return err
+		}
+		err = unmarshaler.UnmarshalBinary(element)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

+ 2 - 0
adapter/router.go

@@ -21,6 +21,8 @@ type Router interface {
 	Outbound(tag string) (Outbound, bool)
 	DefaultOutbound(network string) Outbound
 
+	FakeIPStore() FakeIPStore
+
 	RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
 	RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
 

+ 25 - 0
docs/configuration/dns/fakeip.md

@@ -0,0 +1,25 @@
+# FakeIP
+
+### Structure
+
+```json
+{
+  "enabled": true,
+  "inet4_range": "198.18.0.0/15",
+  "inet6_range": "fc00::/18"
+}
+```
+
+### Fields
+
+#### enabled
+
+Enable FakeIP service.
+
+#### inet4_range
+
+IPv4 address range for FakeIP.
+
+#### inet6_address
+
+IPv6 address range for FakeIP.

+ 25 - 0
docs/configuration/dns/fakeip.zh.md

@@ -0,0 +1,25 @@
+# FakeIP
+
+### 结构
+
+```json
+{
+  "enabled": true,
+  "inet4_range": "198.18.0.0/15",
+  "inet6_range": "fc00::/18"
+}
+```
+
+### 字段
+
+#### enabled
+
+启用 FakeIP 服务。
+
+#### inet4_range
+
+用于 FakeIP 的 IPv4 地址范围。
+
+#### inet6_range
+
+用于 FakeIP 的 IPv6 地址范围。

+ 9 - 2
docs/configuration/dns/index.md

@@ -11,7 +11,8 @@
     "strategy": "",
     "disable_cache": false,
     "disable_expire": false,
-    "reverse_mapping": false
+    "reverse_mapping": false,
+    "fakeip": {}
   }
 }
 
@@ -23,6 +24,7 @@
 |----------|--------------------------------|
 | `server` | List of [DNS Server](./server) |
 | `rules`  | List of [DNS Rule](./rule)     |
+| `fakeip` | [FakeIP](./fakeip)             |
 
 #### final
 
@@ -50,4 +52,9 @@ Disable dns cache expire.
 
 Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
 
-Since this process relies on the act of resolving domain names by an application before making a request, it can be problematic in environments such as macOS, where DNS is proxied and cached by the system.
+Since this process relies on the act of resolving domain names by an application before making a request, it can be
+problematic in environments such as macOS, where DNS is proxied and cached by the system.
+
+#### fakeip
+
+[FakeIP](./fakeip) settings.

+ 6 - 1
docs/configuration/dns/index.zh.md

@@ -11,7 +11,8 @@
     "strategy": "",
     "disable_cache": false,
     "disable_expire": false,
-    "reverse_mapping": false
+    "reverse_mapping": false,
+    "fakeip": {}
   }
 }
 
@@ -51,3 +52,7 @@
 在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
 
 由于此过程依赖于应用程序在发出请求之前解析域名的行为,因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。
+
+#### fakeip
+
+[FakeIP](./fakeip) 设置。

+ 8 - 2
docs/configuration/dns/rule.md

@@ -84,14 +84,16 @@
           "direct"
         ],
         "server": "local",
-        "disable_cache": false
+        "disable_cache": false,
+        "rewrite_ttl": 100
       },
       {
         "type": "logical",
         "mode": "and",
         "rules": [],
         "server": "local",
-        "disable_cache": false
+        "disable_cache": false,
+        "rewrite_ttl": 100
       }
     ]
   }
@@ -244,6 +246,10 @@ Tag of the target dns server.
 
 Disable cache and save cache in this query.
 
+#### rewrite_ttl
+
+Rewrite TTL in DNS responses.
+
 ### Logical Fields
 
 #### type

+ 4 - 0
docs/configuration/dns/rule.zh.md

@@ -243,6 +243,10 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
 
 在此查询中禁用缓存。
 
+#### rewrite_ttl
+
+重写 DNS 回应中的 TTL。
+
 ### 逻辑字段
 
 #### type

+ 12 - 11
docs/configuration/dns/server.md

@@ -30,17 +30,18 @@ The tag of the dns server.
 
 The address of the dns server.
 
-| Protocol | Format                        |
-|----------|-------------------------------|
-| `System` | `local`                       |
-| `TCP`    | `tcp://1.0.0.1`               |
-| `UDP`    | `8.8.8.8` `udp://8.8.4.4`     |
-| `TLS`    | `tls://dns.google`            |
-| `HTTPS`  | `https://1.1.1.1/dns-query`   |
-| `QUIC`   | `quic://dns.adguard.com`      |
-| `HTTP3`  | `h3://8.8.8.8/dns-query`      |
-| `RCode`  | `rcode://refused`             |
-| `DHCP`   | `dhcp://auto` or `dhcp://en0` |
+| Protocol            | Format                        |
+|---------------------|-------------------------------|
+| `System`            | `local`                       |
+| `TCP`               | `tcp://1.0.0.1`               |
+| `UDP`               | `8.8.8.8` `udp://8.8.4.4`     |
+| `TLS`               | `tls://dns.google`            |
+| `HTTPS`             | `https://1.1.1.1/dns-query`   |
+| `QUIC`              | `quic://dns.adguard.com`      |
+| `HTTP3`             | `h3://8.8.8.8/dns-query`      |
+| `RCode`             | `rcode://refused`             |
+| `DHCP`              | `dhcp://auto` or `dhcp://en0` |
+|  [FakeIP](./fakeip) | `fakeip`                      |
 
 !!! warning ""
 

+ 12 - 11
docs/configuration/dns/server.zh.md

@@ -30,17 +30,18 @@ DNS 服务器的标签。
 
 DNS 服务器的地址。
 
-| 协议       | 格式                           |
-|----------|------------------------------|
-| `System` | `local`                      |
-| `TCP`    | `tcp://1.0.0.1`              |
-| `UDP`    | `8.8.8.8` `udp://8.8.4.4`    |
-| `TLS`    | `tls://dns.google`           |
-| `HTTPS`  | `https://1.1.1.1/dns-query`  |
-| `QUIC`   | `quic://dns.adguard.com`     |
-| `HTTP3`  | `h3://8.8.8.8/dns-query`     |
-| `RCode`  | `rcode://refused`            |
-| `DHCP`   | `dhcp://auto` 或 `dhcp://en0` |
+| 协议                 | 格式                           |
+|--------------------|------------------------------|
+| `System`           | `local`                      |
+| `TCP`              | `tcp://1.0.0.1`              |
+| `UDP`              | `8.8.8.8` `udp://8.8.4.4`    |
+| `TLS`              | `tls://dns.google`           |
+| `HTTPS`            | `https://1.1.1.1/dns-query`  |
+| `QUIC`             | `quic://dns.adguard.com`     |
+| `HTTP3`            | `h3://8.8.8.8/dns-query`     |
+| `RCode`            | `rcode://refused`            |
+| `DHCP`             | `dhcp://auto` 或 `dhcp://en0` |
+| [FakeIP](./fakeip) | `fakeip`                     |
 
 !!! warning ""
 

+ 18 - 0
docs/faq/fakeip.md

@@ -0,0 +1,18 @@
+# FakeIP
+
+FakeIP refers to a type of behavior in a program that simultaneously hijacks both DNS and connection requests. It
+responds to DNS requests with virtual results and restores mapping when accepting connections.
+
+#### Advantage
+
+* 
+
+#### Limitation
+
+* Its mechanism breaks applications that depend on returning correct remote addresses.
+* Only A and AAAA (IP) requests are supported, which may break applications that rely on other requests.
+
+#### Recommendation
+
+* If using tun, make sure FakeIP ranges is included in the tun's routes.
+* Enable `experimental.clash_api.store_fakeip` to persist FakeIP records, or use `dns.rules.rewrite_ttl` to avoid losing records after program restart in DNS cached environments.

+ 17 - 0
docs/faq/fakeip.zh.md

@@ -0,0 +1,17 @@
+# FakeIP
+
+FakeIP 是指同时劫持 DNS 和连接请求的程序中的一种行为。它通过虚拟结果响应 DNS 请求,在接受连接时恢复映射。
+
+#### 优点
+
+*
+
+#### 限制
+
+* 它的机制会破坏依赖于返回正确远程地址的应用程序。
+* 仅支持 A 和 AAAA(IP)请求,这可能会破坏依赖于其他请求的应用程序。
+
+#### 建议
+
+* 如果使用 tun,请确保 tun 路由中包含 FakeIP 地址范围。
+* 启用 `experimental.clash_api.store_fakeip` 以持久化 FakeIP 记录,或者使用 `dns.rules.rewrite_ttl` 避免程序重启后在 DNS 被缓存的环境中丢失记录。

+ 0 - 6
docs/faq/index.md

@@ -11,12 +11,6 @@ it doesn't fit, because it compromises performance or design clarity, or because
 If it bothers you that sing-box is missing feature X, please forgive us and investigate the features that sing-box does
 have. You might find that they compensate in interesting ways for the lack of X.
 
-#### Fake IP
-
-Fake IP (also called Fake DNS) is an invasive and imperfect DNS solution that breaks expected behavior, causes DNS leaks
-and makes many software unusable. It is recommended by some software that lacks DNS processing and caching, but sing-box
-does not need this.
-
 #### Naive outbound
 
 NaïveProxy's main function is chromium's network stack, and it makes no sense to implement only its transport protocol.

+ 0 - 5
docs/faq/index.zh.md

@@ -9,11 +9,6 @@
 
 如果 sing-box 缺少功能 X 让您感到困扰,请原谅我们并调查 sing-box 确实有的功能。 您可能会发现它们以有趣的方式弥补了 X 的缺失。
 
-#### Fake IP
-
-Fake IP(也称 Fake DNS)是一种侵入性和不完善的 DNS 解决方案,它打破了预期的行为,导致 DNS 泄漏并使许多软件无法使用。
-一些缺乏 DNS 处理和缓存的软件推荐使用它,但 sing-box 不需要。
-
 #### Naive 出站
 
 NaïveProxy 的主要功能是 chromium 的网络栈,仅实现它的传输协议是舍本逐末的。

+ 16 - 9
experimental/clashapi/cache.go

@@ -3,21 +3,28 @@ package clashapi
 import (
 	"net/http"
 
+	"github.com/sagernet/sing-box/adapter"
+
 	"github.com/go-chi/chi/v5"
 	"github.com/go-chi/render"
 )
 
-func cacheRouter() http.Handler {
+func cacheRouter(router adapter.Router) http.Handler {
 	r := chi.NewRouter()
-	r.Post("/fakeip/flush", flushFakeip)
+	r.Post("/fakeip/flush", flushFakeip(router))
 	return r
 }
 
-func flushFakeip(w http.ResponseWriter, r *http.Request) {
-	/*if err := cachefile.Cache().FlushFakeip(); err != nil {
-		render.Status(r, http.StatusInternalServerError)
-		render.JSON(w, r, newError(err.Error()))
-		return
-	}*/
-	render.NoContent(w, r)
+func flushFakeip(router adapter.Router) func(w http.ResponseWriter, r *http.Request) {
+	return func(w http.ResponseWriter, r *http.Request) {
+		if cacheFile := router.ClashServer().CacheFile(); cacheFile != nil {
+			err := cacheFile.FakeIPReset()
+			if err != nil {
+				render.Status(r, http.StatusInternalServerError)
+				render.JSON(w, r, newError(err.Error()))
+				return
+			}
+		}
+		render.NoContent(w, r)
+	}
 }

+ 77 - 0
experimental/clashapi/cachefile/fakeip.go

@@ -0,0 +1,77 @@
+package cachefile
+
+import (
+	"net/netip"
+	"os"
+
+	"github.com/sagernet/sing-box/adapter"
+
+	"go.etcd.io/bbolt"
+)
+
+var (
+	bucketFakeIP = []byte("fakeip")
+	keyMetadata  = []byte("metadata")
+)
+
+func (c *CacheFile) FakeIPMetadata() *adapter.FakeIPMetadata {
+	var metadata adapter.FakeIPMetadata
+	err := c.DB.View(func(tx *bbolt.Tx) error {
+		bucket := tx.Bucket(bucketFakeIP)
+		if bucket == nil {
+			return nil
+		}
+		metadataBinary := bucket.Get(keyMetadata)
+		if len(metadataBinary) == 0 {
+			return os.ErrInvalid
+		}
+		return metadata.UnmarshalBinary(metadataBinary)
+	})
+	if err != nil {
+		return nil
+	}
+	return &metadata
+}
+
+func (c *CacheFile) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) error {
+	return c.DB.Batch(func(tx *bbolt.Tx) error {
+		bucket, err := tx.CreateBucketIfNotExists(bucketFakeIP)
+		if err != nil {
+			return err
+		}
+		metadataBinary, err := metadata.MarshalBinary()
+		if err != nil {
+			return err
+		}
+		return bucket.Put(keyMetadata, metadataBinary)
+	})
+}
+
+func (c *CacheFile) FakeIPStore(address netip.Addr, domain string) error {
+	return c.DB.Batch(func(tx *bbolt.Tx) error {
+		bucket, err := tx.CreateBucketIfNotExists(bucketFakeIP)
+		if err != nil {
+			return err
+		}
+		return bucket.Put(address.AsSlice(), []byte(domain))
+	})
+}
+
+func (c *CacheFile) FakeIPLoad(address netip.Addr) (string, bool) {
+	var domain string
+	_ = c.DB.View(func(tx *bbolt.Tx) error {
+		bucket := tx.Bucket(bucketFakeIP)
+		if bucket == nil {
+			return nil
+		}
+		domain = string(bucket.Get(address.AsSlice()))
+		return nil
+	})
+	return domain, domain != ""
+}
+
+func (c *CacheFile) FakeIPReset() error {
+	return c.DB.Batch(func(tx *bbolt.Tx) error {
+		return tx.DeleteBucket(bucketFakeIP)
+	})
+}

+ 9 - 3
experimental/clashapi/server.go

@@ -44,6 +44,7 @@ type Server struct {
 	urlTestHistory *urltest.HistoryStorage
 	mode           string
 	storeSelected  bool
+	storeFakeIP    bool
 	cacheFilePath  string
 	cacheFile      adapter.ClashCacheFile
 }
@@ -61,12 +62,13 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
 		trafficManager: trafficManager,
 		urlTestHistory: urltest.NewHistoryStorage(),
 		mode:           strings.ToLower(options.DefaultMode),
+		storeSelected:  options.StoreSelected,
+		storeFakeIP:    options.StoreFakeIP,
 	}
 	if server.mode == "" {
 		server.mode = "rule"
 	}
-	if options.StoreSelected {
-		server.storeSelected = true
+	if options.StoreSelected || options.StoreFakeIP {
 		cachePath := os.ExpandEnv(options.CacheFile)
 		if cachePath == "" {
 			cachePath = "cache.db"
@@ -99,7 +101,7 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
 		r.Mount("/providers/rules", ruleProviderRouter())
 		r.Mount("/script", scriptRouter())
 		r.Mount("/profile", profileRouter())
-		r.Mount("/cache", cacheRouter())
+		r.Mount("/cache", cacheRouter(router))
 		r.Mount("/dns", dnsRouter(router))
 	})
 	if options.ExternalUI != "" {
@@ -156,6 +158,10 @@ func (s *Server) StoreSelected() bool {
 	return s.storeSelected
 }
 
+func (s *Server) StoreFakeIP() bool {
+	return s.storeFakeIP
+}
+
 func (s *Server) CacheFile() adapter.ClashCacheFile {
 	return s.cacheFile
 }

+ 3 - 0
mkdocs.yml

@@ -48,6 +48,7 @@ nav:
           - configuration/dns/index.md
           - DNS Server: configuration/dns/server.md
           - DNS Rule: configuration/dns/rule.md
+          - FakeIP: configuration/dns/fakeip.md
       - NTP:
           - configuration/ntp/index.md
       - Route:
@@ -102,6 +103,7 @@ nav:
           - URLTest: configuration/outbound/urltest.md
   - FAQ:
       - faq/index.md
+      - FakeIP: faq/fakeip.md
       - Known Issues: faq/known-issues.md
   - Examples:
       - examples/index.md
@@ -111,6 +113,7 @@ nav:
       - Shadowsocks: examples/shadowsocks.md
       - ShadowTLS: examples/shadowtls.md
       - Clash API: examples/clash-api.md
+      - WireGuard Direct: examples/wireguard-direct.md
   - Contributing:
       - contributing/index.md
       - Developing:

+ 1 - 0
option/clash.go

@@ -6,6 +6,7 @@ type ClashAPIOptions struct {
 	Secret             string `json:"secret,omitempty"`
 	DefaultMode        string `json:"default_mode,omitempty"`
 	StoreSelected      bool   `json:"store_selected,omitempty"`
+	StoreFakeIP        bool   `json:"store_fakeip,omitempty"`
 	CacheFile          string `json:"cache_file,omitempty"`
 }
 

+ 13 - 6
option/dns.go

@@ -5,15 +5,10 @@ type DNSOptions struct {
 	Rules          []DNSRule          `json:"rules,omitempty"`
 	Final          string             `json:"final,omitempty"`
 	ReverseMapping bool               `json:"reverse_mapping,omitempty"`
+	FakeIP         *DNSFakeIPOptions  `json:"fakeip,omitempty"`
 	DNSClientOptions
 }
 
-type DNSClientOptions struct {
-	Strategy      DomainStrategy `json:"strategy,omitempty"`
-	DisableCache  bool           `json:"disable_cache,omitempty"`
-	DisableExpire bool           `json:"disable_expire,omitempty"`
-}
-
 type DNSServerOptions struct {
 	Tag                  string         `json:"tag,omitempty"`
 	Address              string         `json:"address"`
@@ -23,3 +18,15 @@ type DNSServerOptions struct {
 	Strategy             DomainStrategy `json:"strategy,omitempty"`
 	Detour               string         `json:"detour,omitempty"`
 }
+
+type DNSClientOptions struct {
+	Strategy      DomainStrategy `json:"strategy,omitempty"`
+	DisableCache  bool           `json:"disable_cache,omitempty"`
+	DisableExpire bool           `json:"disable_expire,omitempty"`
+}
+
+type DNSFakeIPOptions struct {
+	Enabled    bool          `json:"enabled,omitempty"`
+	Inet4Range *ListenPrefix `json:"inet4_range,omitempty"`
+	Inet6Range *ListenPrefix `json:"inet6_range,omitempty"`
+}

+ 63 - 6
route/router.go

@@ -24,6 +24,7 @@ import (
 	"github.com/sagernet/sing-box/ntp"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-box/outbound"
+	"github.com/sagernet/sing-box/transport/fakeip"
 	"github.com/sagernet/sing-dns"
 	"github.com/sagernet/sing-tun"
 	"github.com/sagernet/sing-vmess"
@@ -66,6 +67,7 @@ type Router struct {
 	transportMap                       map[string]dns.Transport
 	transportDomainStrategy            map[dns.Transport]dns.DomainStrategy
 	dnsReverseMapping                  *DNSReverseMapping
+	fakeIPStore                        adapter.FakeIPStore
 	interfaceFinder                    myInterfaceFinder
 	autoDetectInterface                bool
 	defaultInterface                   string
@@ -178,12 +180,8 @@ func NewRouter(
 					} else {
 						continue
 					}
-				} else if notIpAddress != nil {
-					switch serverURL.Scheme {
-					case "rcode", "dhcp":
-					default:
-						return nil, E.New("parse dns server[", tag, "]: missing address_resolver")
-					}
+				} else if notIpAddress != nil && strings.Contains(server.Address, ".") {
+					return nil, E.New("parse dns server[", tag, "]: missing address_resolver")
 				}
 			}
 			transport, err := dns.CreateTransport(tag, ctx, logFactory.NewLogger(F.ToString("dns/transport[", tag, "]")), detour, server.Address)
@@ -239,6 +237,18 @@ func NewRouter(
 		router.dnsReverseMapping = NewDNSReverseMapping()
 	}
 
+	if fakeIPOptions := dnsOptions.FakeIP; fakeIPOptions != nil && dnsOptions.FakeIP.Enabled {
+		var inet4Range netip.Prefix
+		var inet6Range netip.Prefix
+		if fakeIPOptions.Inet4Range != nil {
+			inet4Range = fakeIPOptions.Inet4Range.Build()
+		}
+		if fakeIPOptions.Inet6Range != nil {
+			inet6Range = fakeIPOptions.Inet6Range.Build()
+		}
+		router.fakeIPStore = fakeip.NewStore(router, inet4Range, inet6Range)
+	}
+
 	usePlatformDefaultInterfaceMonitor := platformInterface != nil && platformInterface.UsePlatformDefaultInterfaceMonitor()
 	needInterfaceMonitor := options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool {
 		return inbound.HTTPOptions.SetSystemProxy || inbound.MixedOptions.SetSystemProxy || inbound.TunOptions.AutoRoute
@@ -452,6 +462,12 @@ func (r *Router) Start() error {
 			return E.Cause(err, "initialize DNS rule[", i, "]")
 		}
 	}
+	if r.fakeIPStore != nil {
+		err := r.fakeIPStore.Start()
+		if err != nil {
+			return err
+		}
+	}
 	for i, transport := range r.transports {
 		err := transport.Start()
 		if err != nil {
@@ -517,6 +533,12 @@ func (r *Router) Close() error {
 			return E.Cause(err, "close time service")
 		})
 	}
+	if r.fakeIPStore != nil {
+		r.logger.Trace("closing fakeip store")
+		err = E.Append(err, r.fakeIPStore.Close(), func(err error) error {
+			return E.Cause(err, "close fakeip store")
+		})
+	}
 	return err
 }
 
@@ -533,6 +555,10 @@ func (r *Router) DefaultOutbound(network string) adapter.Outbound {
 	}
 }
 
+func (r *Router) FakeIPStore() adapter.FakeIPStore {
+	return r.fakeIPStore
+}
+
 func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
 	if metadata.InboundDetour != "" {
 		if metadata.LastInbound == metadata.InboundDetour {
@@ -585,6 +611,19 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
 		metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
 		return r.RoutePacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata)
 	}
+
+	if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) {
+		domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr)
+		if !loaded {
+			return E.New("missing fakeip context")
+		}
+		metadata.Destination = M.Socksaddr{
+			Fqdn: domain,
+			Port: metadata.Destination.Port,
+		}
+		r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
+	}
+
 	if metadata.InboundOptions.SniffEnabled {
 		buffer := buf.NewPacket()
 		buffer.FullReset()
@@ -675,6 +714,21 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
 		return nil
 	}
 	metadata.Network = N.NetworkUDP
+
+	var originAddress M.Socksaddr
+	if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) {
+		domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr)
+		if !loaded {
+			return E.New("missing fakeip context")
+		}
+		originAddress = metadata.Destination
+		metadata.Destination = M.Socksaddr{
+			Fqdn: domain,
+			Port: metadata.Destination.Port,
+		}
+		r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
+	}
+
 	if metadata.InboundOptions.SniffEnabled {
 		buffer := buf.NewPacket()
 		buffer.FullReset()
@@ -733,6 +787,9 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
 			conn = statsService.RoutedPacketConnection(metadata.Inbound, detour.Tag(), metadata.User, conn)
 		}
 	}
+	if originAddress.IsValid() {
+		conn = fakeip.NewNATPacketConn(conn, originAddress, metadata.Destination)
+	}
 	return detour.NewPacketConnection(ctx, conn, metadata)
 }
 

+ 4 - 0
route/rule_abstract.go

@@ -57,6 +57,10 @@ func (r *abstractDefaultRule) UpdateGeosite() error {
 }
 
 func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
+	if len(r.allItems) == 0 {
+		return true
+	}
+
 	for _, item := range r.items {
 		if !item.Match(metadata) {
 			return r.invert

+ 44 - 0
transport/fakeip/memory.go

@@ -0,0 +1,44 @@
+package fakeip
+
+import (
+	"net/netip"
+
+	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing/common/cache"
+)
+
+var _ adapter.FakeIPStorage = (*MemoryStorage)(nil)
+
+type MemoryStorage struct {
+	metadata    *adapter.FakeIPMetadata
+	domainCache *cache.LruCache[netip.Addr, string]
+}
+
+func NewMemoryStorage() *MemoryStorage {
+	return &MemoryStorage{
+		domainCache: cache.New[netip.Addr, string](),
+	}
+}
+
+func (s *MemoryStorage) FakeIPMetadata() *adapter.FakeIPMetadata {
+	return s.metadata
+}
+
+func (s *MemoryStorage) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) error {
+	s.metadata = metadata
+	return nil
+}
+
+func (s *MemoryStorage) FakeIPStore(address netip.Addr, domain string) error {
+	s.domainCache.Store(address, domain)
+	return nil
+}
+
+func (s *MemoryStorage) FakeIPLoad(address netip.Addr) (string, bool) {
+	return s.domainCache.Load(address)
+}
+
+func (s *MemoryStorage) FakeIPReset() error {
+	s.domainCache = cache.New[netip.Addr, string]()
+	return nil
+}

+ 55 - 0
transport/fakeip/packet.go

@@ -0,0 +1,55 @@
+package fakeip
+
+import (
+	"github.com/sagernet/sing/common/buf"
+	M "github.com/sagernet/sing/common/metadata"
+	N "github.com/sagernet/sing/common/network"
+)
+
+var _ N.PacketConn = (*NATPacketConn)(nil)
+
+type NATPacketConn struct {
+	N.PacketConn
+	origin      M.Socksaddr
+	destination M.Socksaddr
+}
+
+func NewNATPacketConn(conn N.PacketConn, origin M.Socksaddr, destination M.Socksaddr) *NATPacketConn {
+	return &NATPacketConn{
+		PacketConn:  conn,
+		origin:      socksaddrWithoutPort(origin),
+		destination: socksaddrWithoutPort(destination),
+	}
+}
+
+func (c *NATPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
+	destination, err = c.PacketConn.ReadPacket(buffer)
+	if socksaddrWithoutPort(destination) == c.origin {
+		destination = M.Socksaddr{
+			Addr: c.destination.Addr,
+			Fqdn: c.destination.Fqdn,
+			Port: destination.Port,
+		}
+	}
+	return
+}
+
+func (c *NATPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
+	if socksaddrWithoutPort(destination) == c.destination {
+		destination = M.Socksaddr{
+			Addr: c.origin.Addr,
+			Fqdn: c.origin.Fqdn,
+			Port: destination.Port,
+		}
+	}
+	return c.PacketConn.WritePacket(buffer, destination)
+}
+
+func (c *NATPacketConn) Upstream() any {
+	return c.PacketConn
+}
+
+func socksaddrWithoutPort(destination M.Socksaddr) M.Socksaddr {
+	destination.Port = 0
+	return destination
+}

+ 83 - 0
transport/fakeip/server.go

@@ -0,0 +1,83 @@
+package fakeip
+
+import (
+	"context"
+	"net/netip"
+	"os"
+
+	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-dns"
+	E "github.com/sagernet/sing/common/exceptions"
+	"github.com/sagernet/sing/common/logger"
+	N "github.com/sagernet/sing/common/network"
+
+	mDNS "github.com/miekg/dns"
+)
+
+var _ dns.Transport = (*Server)(nil)
+
+func init() {
+	dns.RegisterTransport([]string{"fakeip"}, NewTransport)
+}
+
+type Server struct {
+	name   string
+	router adapter.Router
+	store  adapter.FakeIPStore
+	logger logger.ContextLogger
+}
+
+func NewTransport(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) {
+	router := adapter.RouterFromContext(ctx)
+	if router == nil {
+		return nil, E.New("missing router in context")
+	}
+	return &Server{
+		name:   name,
+		router: router,
+		logger: logger,
+	}, nil
+}
+
+func (s *Server) Name() string {
+	return s.name
+}
+
+func (s *Server) Start() error {
+	s.store = s.router.FakeIPStore()
+	if s.store == nil {
+		return E.New("fakeip not enabled")
+	}
+	return nil
+}
+
+func (s *Server) Close() error {
+	return nil
+}
+
+func (s *Server) Raw() bool {
+	return false
+}
+
+func (s *Server) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
+	return nil, os.ErrInvalid
+}
+
+func (s *Server) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
+	var addresses []netip.Addr
+	if strategy != dns.DomainStrategyUseIPv6 {
+		inet4Address, err := s.store.Create(domain, dns.DomainStrategyUseIPv4)
+		if err != nil {
+			return nil, err
+		}
+		addresses = append(addresses, inet4Address)
+	}
+	if strategy != dns.DomainStrategyUseIPv4 {
+		inet6Address, err := s.store.Create(domain, dns.DomainStrategyUseIPv6)
+		if err != nil {
+			return nil, err
+		}
+		addresses = append(addresses, inet6Address)
+	}
+	return addresses, nil
+}

+ 108 - 0
transport/fakeip/store.go

@@ -0,0 +1,108 @@
+package fakeip
+
+import (
+	"net/netip"
+
+	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-dns"
+	E "github.com/sagernet/sing/common/exceptions"
+)
+
+var _ adapter.FakeIPStore = (*Store)(nil)
+
+type Store struct {
+	router       adapter.Router
+	inet4Range   netip.Prefix
+	inet6Range   netip.Prefix
+	storage      adapter.FakeIPStorage
+	inet4Current netip.Addr
+	inet6Current netip.Addr
+}
+
+func NewStore(router adapter.Router, inet4Range netip.Prefix, inet6Range netip.Prefix) *Store {
+	return &Store{
+		router:     router,
+		inet4Range: inet4Range,
+		inet6Range: inet6Range,
+	}
+}
+
+func (s *Store) Start() error {
+	var storage adapter.FakeIPStorage
+	if clashServer := s.router.ClashServer(); clashServer != nil && clashServer.StoreFakeIP() {
+		if cacheFile := clashServer.CacheFile(); cacheFile != nil {
+			storage = cacheFile
+		}
+	}
+	if storage == nil {
+		storage = NewMemoryStorage()
+	}
+	metadata := storage.FakeIPMetadata()
+	if metadata != nil && metadata.Inet4Range == s.inet4Range && metadata.Inet6Range == s.inet6Range {
+		s.inet4Current = metadata.Inet4Current
+		s.inet6Current = metadata.Inet6Current
+	} else {
+		if s.inet4Range.IsValid() {
+			s.inet4Current = s.inet4Range.Addr().Next().Next()
+		}
+		if s.inet6Range.IsValid() {
+			s.inet6Current = s.inet6Range.Addr().Next().Next()
+		}
+	}
+	s.storage = storage
+	return nil
+}
+
+func (s *Store) Contains(address netip.Addr) bool {
+	return s.inet4Range.Contains(address) || s.inet6Range.Contains(address)
+}
+
+func (s *Store) Close() error {
+	if s.storage == nil {
+		return nil
+	}
+	return s.storage.FakeIPSaveMetadata(&adapter.FakeIPMetadata{
+		Inet4Range:   s.inet4Range,
+		Inet6Range:   s.inet6Range,
+		Inet4Current: s.inet4Current,
+		Inet6Current: s.inet6Current,
+	})
+}
+
+func (s *Store) Create(domain string, strategy dns.DomainStrategy) (netip.Addr, error) {
+	var address netip.Addr
+	if strategy == dns.DomainStrategyUseIPv4 {
+		if !s.inet4Current.IsValid() {
+			return netip.Addr{}, E.New("missing IPv4 fakeip address range")
+		}
+		nextAddress := s.inet4Current.Next()
+		if !s.inet4Range.Contains(nextAddress) {
+			nextAddress = s.inet4Range.Addr().Next().Next()
+		}
+		s.inet4Current = nextAddress
+		address = nextAddress
+	} else {
+		if !s.inet6Current.IsValid() {
+			return netip.Addr{}, E.New("missing IPv6 fakeip address range")
+		}
+		nextAddress := s.inet6Current.Next()
+		if !s.inet6Range.Contains(nextAddress) {
+			nextAddress = s.inet6Range.Addr().Next().Next()
+		}
+		s.inet6Current = nextAddress
+		address = nextAddress
+	}
+	err := s.storage.FakeIPStore(address, domain)
+	if err != nil {
+		return netip.Addr{}, err
+	}
+	return address, nil
+}
+
+func (s *Store) Lookup(address netip.Addr) (string, bool) {
+	return s.storage.FakeIPLoad(address)
+}
+
+func (s *Store) Reset() error {
+	return s.storage.FakeIPReset()
+}