Skip to content

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 detection

3. 覆盖决策

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

实现要点

  1. LRU 淘汰:虚假 IP 缓存基于 LRU 机制。当缓存满时,最早的条目会被淘汰。如果应用程序仍有到被淘汰虚假 IP 的活跃连接,反向查找将失败。应合理设置 LRU 大小(默认 65535)。

  2. 基于时间的 IP 生成:虚假 IP 由 currentTimeMillis % poolSize 推导而来。这使地址分布在整个地址池中,但可能产生冲突。冲突解决循环会处理这种情况。

  3. 仅元数据嗅探:Fake-IP 嗅探器不需要读取任何有效载荷字节。它在任何数据到达之前检查目的 IP 是否在地址池中,因此是即时的。

  4. 虚假 IP 始终完全覆盖:即使设置了 routeOnly: true,虚假 IP 也必须被完全覆盖(不仅仅用于路由)。虚假 IP 没有实际含义——直接连接会失败。

  5. IPv4 + IPv6 地址池:使用 HolderMulti 分别配置一个 IPv4 地址池和一个 IPv6 地址池。DNS 模块会根据查询类型(A 或 AAAA)返回相应类型。

  6. DNS 必须被劫持:要使 Fake-IP 正常工作,应用程序的 DNS 查询必须到达 Xray 的 DNS 模块。通常需要:

    • TUN 配合 dns.hijack 路由规则
    • 或将系统 DNS 指向 Xray 的 DNS 监听器

用于重新实现目的的技术分析。