Интеграция 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 и подключаетсяАрхитектура
flowchart TB
App([Приложение]) -->|"DNS: google.com?"| DNS["Модуль DNS Xray"]
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 -->|"DispatchLink(ctx,<br/>198.18.0.42:443)"| Dispatcher["Диспетчер"]
Dispatcher --> Sniff["Сниффер"]
Sniff --> FakeDNS["Сниффер FakeDNS"]
FakeDNS -->|"198.18.0.42 в пуле?<br/>→ google.com"| Override["Переопределение dest:<br/>google.com:443"]
Override --> Router["Маршрутизатор"]
Router -->|"совпадение правила<br/>по домену"| Outbound["Исходящее соединение"]
Outbound -->|"Разрешение google.com<br/>→ настоящий IP"| Server([Реальный сервер])FakeDNS Holder (app/dns/fakedns/fake.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
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}
}Обратный поиск
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
}Проверка принадлежности к пулу
func (fkdns *Holder) IsIPInIPPool(ip net.Address) bool {
if ip.Family().IsDomain() {
return false
}
return fkdns.ipRange.Contains(ip.IP())
}Поддержка нескольких пулов
HolderMulti поддерживает раздельные пулы поддельных IP для IPv4 и IPv6:
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
}Интеграция со сниффингом диспетчера
Интеграция Fake-IP в диспетчере состоит из трёх компонентов:
1. Сниффер метаданных FakeDNS
// 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 разрешает домен, но также требуется определение протокола на основе содержимого:
// "fakedns+others" sniffer
// Returns Fake-IP domain as the domain result
// But uses content sniffing (TLS/HTTP) for protocol detection3. Решение о переопределении
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
}
}
}В пути диспетчеризации:
// 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)
}Конфигурация
{
"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-соединения
sequenceDiagram
participant App as Приложение
participant DNS as DNS Xray
participant FDH as FakeDNS Holder
participant TUN
participant D as Диспетчер
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-запросы приложения должны попадать в модуль DNS Xray. Обычно для этого требуется:
- TUN с правилами маршрутизации
dns.hijack - Или системный DNS, указывающий на DNS-слушатель Xray
- TUN с правилами маршрутизации