Fake-IP 集成
Fake-IP 是一种技术,DNS 查询会返回从保留地址池中分配的临时虚假 IP 地址。当流量到达虚假 IP 时,系统会查找原始域名并基于域名(而非无意义的 IP)进行路由。
为什么需要 Fake-IP?
在没有 Fake-IP 的情况下,基于 TUN 的透明代理面临一个困境:
应用 → DNS 查询 "google.com" → 真实 DNS → 142.250.80.14
应用 → 连接 142.250.80.14 → TUN → Xray
→ Xray 看到 IP 142.250.80.14,但不知道域名 "google.com"
→ 无法进行基于域名的路由!使用 Fake-IP 后:
应用 → DNS 查询 "google.com" → Fake DNS → 198.18.0.42(虚假地址)
应用 → 连接 198.18.0.42 → TUN → Xray
→ Xray 检测到 198.18.0.42 在虚假地址池中
→ 查找:198.18.0.42 → "google.com"
→ 基于域名 "google.com" 进行路由
→ 出站解析真实 IP 并建立连接架构
mermaid
flowchart TB
App([应用程序]) -->|"DNS: google.com?"| DNS["Xray DNS 模块"]
DNS -->|"返回 198.18.0.42<br/>(虚假 IP)"| App
App -->|"连接<br/>198.18.0.42"| TUN["TUN 接口"]
TUN -->|"dest=198.18.0.42"| Handler["TUN Handler"]
Handler -->|"DispatchLink(ctx,<br/>198.18.0.42:443)"| Dispatcher["Dispatcher"]
Dispatcher --> Sniff["嗅探器"]
Sniff --> FakeDNS["FakeDNS 嗅探器"]
FakeDNS -->|"198.18.0.42 在池中?<br/>→ google.com"| Override["覆盖目的地:<br/>google.com:443"]
Override --> Router["路由器"]
Router -->|"基于域名<br/>规则匹配"| Outbound["出站"]
Outbound -->|"解析 google.com<br/>→ 真实 IP"| Server([真实服务器])FakeDNS Holder(app/dns/fakedns/fake.go)
go
type Holder struct {
domainToIP cache.Lru // LRU cache: domain → fake IP
ipRange *net.IPNet // CIDR pool (e.g., 198.18.0.0/15)
mu *sync.Mutex
config *FakeDnsPool
}IP 分配
go
func (fkdns *Holder) GetFakeIPForDomain(domain string) []net.Address {
fkdns.mu.Lock()
defer fkdns.mu.Unlock()
// Check cache first
if v, ok := fkdns.domainToIP.Get(domain); ok {
return []net.Address{v.(net.Address)}
}
// Generate new fake IP based on current time
currentTimeMillis := uint64(time.Now().UnixNano() / 1e6)
ones, bits := fkdns.ipRange.Mask.Size()
rooms := bits - ones
if rooms < 64 {
currentTimeMillis %= (uint64(1) << rooms)
}
bigIntIP := big.NewInt(0).SetBytes(fkdns.ipRange.IP)
bigIntIP.Add(bigIntIP, new(big.Int).SetUint64(currentTimeMillis))
// Handle collision: increment until unused IP found
for {
ip := net.IPAddress(bigIntIP.Bytes())
if _, ok := fkdns.domainToIP.PeekKeyFromValue(ip); !ok {
break // IP not in use
}
bigIntIP.Add(bigIntIP, big.NewInt(1))
if !fkdns.ipRange.Contains(bigIntIP.Bytes()) {
bigIntIP = big.NewInt(0).SetBytes(fkdns.ipRange.IP) // wrap around
}
}
fkdns.domainToIP.Put(domain, ip)
return []net.Address{ip}
}反向查找
go
func (fkdns *Holder) GetDomainFromFakeDNS(ip net.Address) string {
if !fkdns.ipRange.Contains(ip.IP()) {
return "" // not a fake IP
}
if k, ok := fkdns.domainToIP.GetKeyFromValue(ip); ok {
return k.(string)
}
return "" // fake IP but domain expired from LRU cache
}地址池检查
go
func (fkdns *Holder) IsIPInIPPool(ip net.Address) bool {
if ip.Family().IsDomain() {
return false
}
return fkdns.ipRange.Contains(ip.IP())
}多地址池支持
HolderMulti 支持独立的 IPv4 和 IPv6 虚假地址池:
go
type HolderMulti struct {
holders []*Holder // e.g., [0]=198.18.0.0/15, [1]=fc00::/18
}
func (h *HolderMulti) GetFakeIPForDomain3(domain string, ipv4, ipv6 bool) []net.Address {
var ret []net.Address
for _, v := range h.holders {
ret = append(ret, v.GetFakeIPForDomain3(domain, ipv4, ipv6)...)
}
return ret // may return both IPv4 and IPv6 fake IPs
}与 Dispatcher 嗅探的集成
Fake-IP 在调度器中的集成包含三个部分:
1. FakeDNS 元数据嗅探器
go
// app/dispatcher/fakednssniffer.go
func newFakeDNSSniffer(ctx) (protocolSnifferWithMetadata, error) {
return protocolSnifferWithMetadata{
protocolSniffer: func(ctx, _ []byte) (SniffResult, error) {
ob := session.OutboundsFromContext(ctx)
dest := ob[len(ob)-1].Target
if fkr0.IsIPInIPPool(dest.Address) {
domain := fkr0.GetDomainFromFakeDNS(dest.Address)
if domain != "" {
return &fakeDNSSniffResult{domain: domain}, nil
}
}
return nil, common.ErrNoClue
},
metadataSniffer: true, // No payload needed
network: net.Network_TCP,
}
}2. FakeDNS+Others 组合嗅探器
当 Fake-IP 解析出域名后,如果还需要基于内容的协议检测:
go
// "fakedns+others" sniffer
// Returns Fake-IP domain as the domain result
// But uses content sniffing (TLS/HTTP) for protocol detection3. 覆盖决策
go
func (d *DefaultDispatcher) shouldOverride(ctx, result, request, destination) bool {
for _, p := range request.OverrideDestinationForProtocol {
// Always override fake IPs, regardless of RouteOnly
if fkr0.IsIPInIPPool(destination.Address) && p == "fakedns" {
return true
}
}
}在分发路径中:
go
// After sniffing:
isFakeIP := fkr0.IsIPInIPPool(ob.Target.Address)
if sniffingRequest.RouteOnly && !isFakeIP {
ob.RouteTarget = destination // route-only: don't change target
} else {
ob.Target = destination // full override (always for fake IPs)
}配置
json
{
"dns": {
"servers": [
{
"address": "fakedns",
"domains": ["geosite:cn"]
},
"8.8.8.8"
]
},
"fakedns": {
"ipPool": "198.18.0.0/15",
"poolSize": 65535
},
"inbounds": [{
"tag": "tun-in",
"protocol": "tun",
"sniffing": {
"enabled": true,
"destOverride": ["fakedns+others"]
}
}]
}关键配置说明:
fakedns块配置 IP 地址池和 LRU 大小- DNS 服务器地址设为
"fakedns"可启用 Fake-IP 响应 - 嗅探的
destOverride中包含"fakedns"或"fakedns+others"
Fake-IP 连接的生命周期
mermaid
sequenceDiagram
participant App as 应用程序
participant DNS as Xray DNS
participant FDH as FakeDNS Holder
participant TUN
participant D as Dispatcher
participant S as 嗅探器
participant R as 路由器
participant O as 出站
App->>DNS: 查询 A google.com
DNS->>FDH: GetFakeIPForDomain("google.com")
FDH-->>DNS: 198.18.1.42
DNS-->>App: A 198.18.1.42
App->>TUN: TCP SYN 到 198.18.1.42:443
TUN->>D: DispatchLink(ctx, 198.18.1.42:443)
D->>S: SniffMetadata(ctx)
S->>FDH: IsIPInIPPool(198.18.1.42)?
FDH-->>S: 是
S->>FDH: GetDomainFromFakeDNS(198.18.1.42)
FDH-->>S: "google.com"
S-->>D: FakeDNS 结果: google.com
D->>D: 覆盖目标: google.com:443
D->>R: PickRoute(google.com:443)
R-->>D: outbound-proxy
D->>O: Dispatch(google.com:443)
O->>O: 解析 google.com → 142.250.80.14
O->>O: 连接 142.250.80.14:443实现要点
LRU 淘汰:虚假 IP 缓存基于 LRU 机制。当缓存满时,最早的条目会被淘汰。如果应用程序仍有到被淘汰虚假 IP 的活跃连接,反向查找将失败。应合理设置 LRU 大小(默认 65535)。
基于时间的 IP 生成:虚假 IP 由
currentTimeMillis % poolSize推导而来。这使地址分布在整个地址池中,但可能产生冲突。冲突解决循环会处理这种情况。仅元数据嗅探:Fake-IP 嗅探器不需要读取任何有效载荷字节。它在任何数据到达之前检查目的 IP 是否在地址池中,因此是即时的。
虚假 IP 始终完全覆盖:即使设置了
routeOnly: true,虚假 IP 也必须被完全覆盖(不仅仅用于路由)。虚假 IP 没有实际含义——直接连接会失败。IPv4 + IPv6 地址池:使用
HolderMulti分别配置一个 IPv4 地址池和一个 IPv6 地址池。DNS 模块会根据查询类型(A 或 AAAA)返回相应类型。DNS 必须被劫持:要使 Fake-IP 正常工作,应用程序的 DNS 查询必须到达 Xray 的 DNS 模块。通常需要:
- TUN 配合
dns.hijack路由规则 - 或将系统 DNS 指向 Xray 的 DNS 监听器
- TUN 配合