mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 08:31:30 +00:00
Add fakeip support
This commit is contained in:
parent
aa94cfb876
commit
9bca5a517f
|
@ -13,6 +13,7 @@ type ClashServer interface {
|
||||||
PreStarter
|
PreStarter
|
||||||
Mode() string
|
Mode() string
|
||||||
StoreSelected() bool
|
StoreSelected() bool
|
||||||
|
StoreFakeIP() bool
|
||||||
CacheFile() ClashCacheFile
|
CacheFile() ClashCacheFile
|
||||||
HistoryStorage() *urltest.HistoryStorage
|
HistoryStorage() *urltest.HistoryStorage
|
||||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
||||||
|
@ -22,6 +23,7 @@ type ClashServer interface {
|
||||||
type ClashCacheFile interface {
|
type ClashCacheFile interface {
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
StoreSelected(group string, selected string) error
|
StoreSelected(group string, selected string) error
|
||||||
|
FakeIPStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tracker interface {
|
type Tracker interface {
|
||||||
|
|
23
adapter/fakeip.go
Normal file
23
adapter/fakeip.go
Normal file
|
@ -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
adapter/fakeip_metadata.go
Normal file
50
adapter/fakeip_metadata.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -21,6 +21,8 @@ type Router interface {
|
||||||
Outbound(tag string) (Outbound, bool)
|
Outbound(tag string) (Outbound, bool)
|
||||||
DefaultOutbound(network string) Outbound
|
DefaultOutbound(network string) Outbound
|
||||||
|
|
||||||
|
FakeIPStore() FakeIPStore
|
||||||
|
|
||||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
|
||||||
|
|
25
docs/configuration/dns/fakeip.md
Normal file
25
docs/configuration/dns/fakeip.md
Normal file
|
@ -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
docs/configuration/dns/fakeip.zh.md
Normal file
25
docs/configuration/dns/fakeip.zh.md
Normal file
|
@ -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 地址范围。
|
|
@ -11,7 +11,8 @@
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_expire": false,
|
"disable_expire": false,
|
||||||
"reverse_mapping": false
|
"reverse_mapping": false,
|
||||||
|
"fakeip": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
|----------|--------------------------------|
|
|----------|--------------------------------|
|
||||||
| `server` | List of [DNS Server](./server) |
|
| `server` | List of [DNS Server](./server) |
|
||||||
| `rules` | List of [DNS Rule](./rule) |
|
| `rules` | List of [DNS Rule](./rule) |
|
||||||
|
| `fakeip` | [FakeIP](./fakeip) |
|
||||||
|
|
||||||
#### final
|
#### 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.
|
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.
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_expire": false,
|
"disable_expire": false,
|
||||||
"reverse_mapping": false
|
"reverse_mapping": false,
|
||||||
|
"fakeip": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,3 +52,7 @@
|
||||||
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
|
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
|
||||||
|
|
||||||
由于此过程依赖于应用程序在发出请求之前解析域名的行为,因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。
|
由于此过程依赖于应用程序在发出请求之前解析域名的行为,因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。
|
||||||
|
|
||||||
|
#### fakeip
|
||||||
|
|
||||||
|
[FakeIP](./fakeip) 设置。
|
||||||
|
|
|
@ -84,14 +84,16 @@
|
||||||
"direct"
|
"direct"
|
||||||
],
|
],
|
||||||
"server": "local",
|
"server": "local",
|
||||||
"disable_cache": false
|
"disable_cache": false,
|
||||||
|
"rewrite_ttl": 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "and",
|
"mode": "and",
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"server": "local",
|
"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.
|
Disable cache and save cache in this query.
|
||||||
|
|
||||||
|
#### rewrite_ttl
|
||||||
|
|
||||||
|
Rewrite TTL in DNS responses.
|
||||||
|
|
||||||
### Logical Fields
|
### Logical Fields
|
||||||
|
|
||||||
#### type
|
#### type
|
||||||
|
|
|
@ -243,6 +243,10 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||||
|
|
||||||
在此查询中禁用缓存。
|
在此查询中禁用缓存。
|
||||||
|
|
||||||
|
#### rewrite_ttl
|
||||||
|
|
||||||
|
重写 DNS 回应中的 TTL。
|
||||||
|
|
||||||
### 逻辑字段
|
### 逻辑字段
|
||||||
|
|
||||||
#### type
|
#### type
|
||||||
|
|
|
@ -30,17 +30,18 @@ The tag of the dns server.
|
||||||
|
|
||||||
The address of the dns server.
|
The address of the dns server.
|
||||||
|
|
||||||
| Protocol | Format |
|
| Protocol | Format |
|
||||||
|----------|-------------------------------|
|
|---------------------|-------------------------------|
|
||||||
| `System` | `local` |
|
| `System` | `local` |
|
||||||
| `TCP` | `tcp://1.0.0.1` |
|
| `TCP` | `tcp://1.0.0.1` |
|
||||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||||
| `TLS` | `tls://dns.google` |
|
| `TLS` | `tls://dns.google` |
|
||||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||||
| `QUIC` | `quic://dns.adguard.com` |
|
| `QUIC` | `quic://dns.adguard.com` |
|
||||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||||
| `RCode` | `rcode://refused` |
|
| `RCode` | `rcode://refused` |
|
||||||
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
|
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
|
||||||
|
| [FakeIP](./fakeip) | `fakeip` |
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
|
|
|
@ -30,17 +30,18 @@ DNS 服务器的标签。
|
||||||
|
|
||||||
DNS 服务器的地址。
|
DNS 服务器的地址。
|
||||||
|
|
||||||
| 协议 | 格式 |
|
| 协议 | 格式 |
|
||||||
|----------|------------------------------|
|
|--------------------|------------------------------|
|
||||||
| `System` | `local` |
|
| `System` | `local` |
|
||||||
| `TCP` | `tcp://1.0.0.1` |
|
| `TCP` | `tcp://1.0.0.1` |
|
||||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||||
| `TLS` | `tls://dns.google` |
|
| `TLS` | `tls://dns.google` |
|
||||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||||
| `QUIC` | `quic://dns.adguard.com` |
|
| `QUIC` | `quic://dns.adguard.com` |
|
||||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||||
| `RCode` | `rcode://refused` |
|
| `RCode` | `rcode://refused` |
|
||||||
| `DHCP` | `dhcp://auto` 或 `dhcp://en0` |
|
| `DHCP` | `dhcp://auto` 或 `dhcp://en0` |
|
||||||
|
| [FakeIP](./fakeip) | `fakeip` |
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
|
|
18
docs/faq/fakeip.md
Normal file
18
docs/faq/fakeip.md
Normal file
|
@ -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
docs/faq/fakeip.zh.md
Normal file
17
docs/faq/fakeip.zh.md
Normal file
|
@ -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 被缓存的环境中丢失记录。
|
|
@ -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
|
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.
|
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
|
#### Naive outbound
|
||||||
|
|
||||||
NaïveProxy's main function is chromium's network stack, and it makes no sense to implement only its transport protocol.
|
NaïveProxy's main function is chromium's network stack, and it makes no sense to implement only its transport protocol.
|
||||||
|
|
|
@ -9,11 +9,6 @@
|
||||||
|
|
||||||
如果 sing-box 缺少功能 X 让您感到困扰,请原谅我们并调查 sing-box 确实有的功能。 您可能会发现它们以有趣的方式弥补了 X 的缺失。
|
如果 sing-box 缺少功能 X 让您感到困扰,请原谅我们并调查 sing-box 确实有的功能。 您可能会发现它们以有趣的方式弥补了 X 的缺失。
|
||||||
|
|
||||||
#### Fake IP
|
|
||||||
|
|
||||||
Fake IP(也称 Fake DNS)是一种侵入性和不完善的 DNS 解决方案,它打破了预期的行为,导致 DNS 泄漏并使许多软件无法使用。
|
|
||||||
一些缺乏 DNS 处理和缓存的软件推荐使用它,但 sing-box 不需要。
|
|
||||||
|
|
||||||
#### Naive 出站
|
#### Naive 出站
|
||||||
|
|
||||||
NaïveProxy 的主要功能是 chromium 的网络栈,仅实现它的传输协议是舍本逐末的。
|
NaïveProxy 的主要功能是 chromium 的网络栈,仅实现它的传输协议是舍本逐末的。
|
||||||
|
|
|
@ -3,21 +3,28 @@ package clashapi
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cacheRouter() http.Handler {
|
func cacheRouter(router adapter.Router) http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Post("/fakeip/flush", flushFakeip)
|
r.Post("/fakeip/flush", flushFakeip(router))
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func flushFakeip(w http.ResponseWriter, r *http.Request) {
|
func flushFakeip(router adapter.Router) func(w http.ResponseWriter, r *http.Request) {
|
||||||
/*if err := cachefile.Cache().FlushFakeip(); err != nil {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
render.Status(r, http.StatusInternalServerError)
|
if cacheFile := router.ClashServer().CacheFile(); cacheFile != nil {
|
||||||
render.JSON(w, r, newError(err.Error()))
|
err := cacheFile.FakeIPReset()
|
||||||
return
|
if err != nil {
|
||||||
}*/
|
render.Status(r, http.StatusInternalServerError)
|
||||||
render.NoContent(w, r)
|
render.JSON(w, r, newError(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render.NoContent(w, r)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
77
experimental/clashapi/cachefile/fakeip.go
Normal file
77
experimental/clashapi/cachefile/fakeip.go
Normal file
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
|
@ -44,6 +44,7 @@ type Server struct {
|
||||||
urlTestHistory *urltest.HistoryStorage
|
urlTestHistory *urltest.HistoryStorage
|
||||||
mode string
|
mode string
|
||||||
storeSelected bool
|
storeSelected bool
|
||||||
|
storeFakeIP bool
|
||||||
cacheFilePath string
|
cacheFilePath string
|
||||||
cacheFile adapter.ClashCacheFile
|
cacheFile adapter.ClashCacheFile
|
||||||
}
|
}
|
||||||
|
@ -61,12 +62,13 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
||||||
trafficManager: trafficManager,
|
trafficManager: trafficManager,
|
||||||
urlTestHistory: urltest.NewHistoryStorage(),
|
urlTestHistory: urltest.NewHistoryStorage(),
|
||||||
mode: strings.ToLower(options.DefaultMode),
|
mode: strings.ToLower(options.DefaultMode),
|
||||||
|
storeSelected: options.StoreSelected,
|
||||||
|
storeFakeIP: options.StoreFakeIP,
|
||||||
}
|
}
|
||||||
if server.mode == "" {
|
if server.mode == "" {
|
||||||
server.mode = "rule"
|
server.mode = "rule"
|
||||||
}
|
}
|
||||||
if options.StoreSelected {
|
if options.StoreSelected || options.StoreFakeIP {
|
||||||
server.storeSelected = true
|
|
||||||
cachePath := os.ExpandEnv(options.CacheFile)
|
cachePath := os.ExpandEnv(options.CacheFile)
|
||||||
if cachePath == "" {
|
if cachePath == "" {
|
||||||
cachePath = "cache.db"
|
cachePath = "cache.db"
|
||||||
|
@ -99,7 +101,7 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
||||||
r.Mount("/providers/rules", ruleProviderRouter())
|
r.Mount("/providers/rules", ruleProviderRouter())
|
||||||
r.Mount("/script", scriptRouter())
|
r.Mount("/script", scriptRouter())
|
||||||
r.Mount("/profile", profileRouter())
|
r.Mount("/profile", profileRouter())
|
||||||
r.Mount("/cache", cacheRouter())
|
r.Mount("/cache", cacheRouter(router))
|
||||||
r.Mount("/dns", dnsRouter(router))
|
r.Mount("/dns", dnsRouter(router))
|
||||||
})
|
})
|
||||||
if options.ExternalUI != "" {
|
if options.ExternalUI != "" {
|
||||||
|
@ -156,6 +158,10 @@ func (s *Server) StoreSelected() bool {
|
||||||
return s.storeSelected
|
return s.storeSelected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) StoreFakeIP() bool {
|
||||||
|
return s.storeFakeIP
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) CacheFile() adapter.ClashCacheFile {
|
func (s *Server) CacheFile() adapter.ClashCacheFile {
|
||||||
return s.cacheFile
|
return s.cacheFile
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ nav:
|
||||||
- configuration/dns/index.md
|
- configuration/dns/index.md
|
||||||
- DNS Server: configuration/dns/server.md
|
- DNS Server: configuration/dns/server.md
|
||||||
- DNS Rule: configuration/dns/rule.md
|
- DNS Rule: configuration/dns/rule.md
|
||||||
|
- FakeIP: configuration/dns/fakeip.md
|
||||||
- NTP:
|
- NTP:
|
||||||
- configuration/ntp/index.md
|
- configuration/ntp/index.md
|
||||||
- Route:
|
- Route:
|
||||||
|
@ -102,6 +103,7 @@ nav:
|
||||||
- URLTest: configuration/outbound/urltest.md
|
- URLTest: configuration/outbound/urltest.md
|
||||||
- FAQ:
|
- FAQ:
|
||||||
- faq/index.md
|
- faq/index.md
|
||||||
|
- FakeIP: faq/fakeip.md
|
||||||
- Known Issues: faq/known-issues.md
|
- Known Issues: faq/known-issues.md
|
||||||
- Examples:
|
- Examples:
|
||||||
- examples/index.md
|
- examples/index.md
|
||||||
|
@ -111,6 +113,7 @@ nav:
|
||||||
- Shadowsocks: examples/shadowsocks.md
|
- Shadowsocks: examples/shadowsocks.md
|
||||||
- ShadowTLS: examples/shadowtls.md
|
- ShadowTLS: examples/shadowtls.md
|
||||||
- Clash API: examples/clash-api.md
|
- Clash API: examples/clash-api.md
|
||||||
|
- WireGuard Direct: examples/wireguard-direct.md
|
||||||
- Contributing:
|
- Contributing:
|
||||||
- contributing/index.md
|
- contributing/index.md
|
||||||
- Developing:
|
- Developing:
|
||||||
|
|
|
@ -6,6 +6,7 @@ type ClashAPIOptions struct {
|
||||||
Secret string `json:"secret,omitempty"`
|
Secret string `json:"secret,omitempty"`
|
||||||
DefaultMode string `json:"default_mode,omitempty"`
|
DefaultMode string `json:"default_mode,omitempty"`
|
||||||
StoreSelected bool `json:"store_selected,omitempty"`
|
StoreSelected bool `json:"store_selected,omitempty"`
|
||||||
|
StoreFakeIP bool `json:"store_fakeip,omitempty"`
|
||||||
CacheFile string `json:"cache_file,omitempty"`
|
CacheFile string `json:"cache_file,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,15 +5,10 @@ type DNSOptions struct {
|
||||||
Rules []DNSRule `json:"rules,omitempty"`
|
Rules []DNSRule `json:"rules,omitempty"`
|
||||||
Final string `json:"final,omitempty"`
|
Final string `json:"final,omitempty"`
|
||||||
ReverseMapping bool `json:"reverse_mapping,omitempty"`
|
ReverseMapping bool `json:"reverse_mapping,omitempty"`
|
||||||
|
FakeIP *DNSFakeIPOptions `json:"fakeip,omitempty"`
|
||||||
DNSClientOptions
|
DNSClientOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSClientOptions struct {
|
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
|
||||||
DisableExpire bool `json:"disable_expire,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSServerOptions struct {
|
type DNSServerOptions struct {
|
||||||
Tag string `json:"tag,omitempty"`
|
Tag string `json:"tag,omitempty"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
|
@ -23,3 +18,15 @@ type DNSServerOptions struct {
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
Detour string `json:"detour,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"`
|
||||||
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/sagernet/sing-box/ntp"
|
"github.com/sagernet/sing-box/ntp"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/outbound"
|
"github.com/sagernet/sing-box/outbound"
|
||||||
|
"github.com/sagernet/sing-box/transport/fakeip"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing-vmess"
|
"github.com/sagernet/sing-vmess"
|
||||||
|
@ -66,6 +67,7 @@ type Router struct {
|
||||||
transportMap map[string]dns.Transport
|
transportMap map[string]dns.Transport
|
||||||
transportDomainStrategy map[dns.Transport]dns.DomainStrategy
|
transportDomainStrategy map[dns.Transport]dns.DomainStrategy
|
||||||
dnsReverseMapping *DNSReverseMapping
|
dnsReverseMapping *DNSReverseMapping
|
||||||
|
fakeIPStore adapter.FakeIPStore
|
||||||
interfaceFinder myInterfaceFinder
|
interfaceFinder myInterfaceFinder
|
||||||
autoDetectInterface bool
|
autoDetectInterface bool
|
||||||
defaultInterface string
|
defaultInterface string
|
||||||
|
@ -178,12 +180,8 @@ func NewRouter(
|
||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else if notIpAddress != nil {
|
} else if notIpAddress != nil && strings.Contains(server.Address, ".") {
|
||||||
switch serverURL.Scheme {
|
return nil, E.New("parse dns server[", tag, "]: missing address_resolver")
|
||||||
case "rcode", "dhcp":
|
|
||||||
default:
|
|
||||||
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)
|
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()
|
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()
|
usePlatformDefaultInterfaceMonitor := platformInterface != nil && platformInterface.UsePlatformDefaultInterfaceMonitor()
|
||||||
needInterfaceMonitor := options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool {
|
needInterfaceMonitor := options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool {
|
||||||
return inbound.HTTPOptions.SetSystemProxy || inbound.MixedOptions.SetSystemProxy || inbound.TunOptions.AutoRoute
|
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, "]")
|
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 {
|
for i, transport := range r.transports {
|
||||||
err := transport.Start()
|
err := transport.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -517,6 +533,12 @@ func (r *Router) Close() error {
|
||||||
return E.Cause(err, "close time service")
|
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
|
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 {
|
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
if metadata.InboundDetour != "" {
|
if metadata.InboundDetour != "" {
|
||||||
if metadata.LastInbound == 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()}
|
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
|
||||||
return r.RoutePacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata)
|
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 {
|
if metadata.InboundOptions.SniffEnabled {
|
||||||
buffer := buf.NewPacket()
|
buffer := buf.NewPacket()
|
||||||
buffer.FullReset()
|
buffer.FullReset()
|
||||||
|
@ -675,6 +714,21 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
metadata.Network = N.NetworkUDP
|
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 {
|
if metadata.InboundOptions.SniffEnabled {
|
||||||
buffer := buf.NewPacket()
|
buffer := buf.NewPacket()
|
||||||
buffer.FullReset()
|
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)
|
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)
|
return detour.NewPacketConnection(ctx, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,10 @@ func (r *abstractDefaultRule) UpdateGeosite() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
if len(r.allItems) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
for _, item := range r.items {
|
for _, item := range r.items {
|
||||||
if !item.Match(metadata) {
|
if !item.Match(metadata) {
|
||||||
return r.invert
|
return r.invert
|
||||||
|
|
44
transport/fakeip/memory.go
Normal file
44
transport/fakeip/memory.go
Normal file
|
@ -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
transport/fakeip/packet.go
Normal file
55
transport/fakeip/packet.go
Normal file
|
@ -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
transport/fakeip/server.go
Normal file
83
transport/fakeip/server.go
Normal file
|
@ -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
transport/fakeip/store.go
Normal file
108
transport/fakeip/store.go
Normal file
|
@ -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()
|
||||||
|
}
|
Loading…
Reference in a new issue