← Назад в базу знаний

Как антифрод-системы маркетплейсов отличают IPv6-датацентр от residential по TTL и хопам

Антифрод маркетплейсов давно перестал смотреть только на IP-адрес. Чёрные списки подсетей устарели лет пять назад: провайдеры продают /64 из residential-пулов, а датацентры арендуют адреса у тех же RIR. Поэтому в ход пошли метрики сетевого уровня — TTL, hop count, TCP fingerprint, RTT. По ним видно, идёт ли трафик с домашнего роутера или с виртуалки в Hetzner.

Разберём, как это работает на IPv6, чем отличается от IPv4 и почему один TTL уже ничего не решает.

Почему IPv6 сломал старую логику TTL

В IPv4 TTL — это счётчик, который уменьшается на каждом маршрутизаторе. Классическая эвристика: если TTL=64, скорее всего Linux с дефолтом, если 128 — Windows, если 255 — сеть. Антифрод вычитал начальный TTL из наблюдаемого и получал число хопов. Меньше 5 хопов — почти наверняка датацентр рядом с целевым сервером.

IPv6 формально использует Hop Limit — то же поле, но с другим именем. Значение по умолчанию у Linux — 64, у Windows — 128. Разница в том, что IPv6-маршрутизация часто короче: крупные IX вроде DE-CIX и AMS-IX анонсируют префиксы напрямую, и путь от residential-абонента до сервера маркетплейса может быть 8-12 хопов, тогда как от датацентра — 3-5. Именно эта разница и стала главным сигналом.

Но тут начинается зоопарк. Мобильные операторы вроде МТС или Vodafone часто ставят CGNAT с Hop Limit=63 или 62 на выходе. Некоторые CDN-провайдеры переписывают поле. Cloudflare, например, исторически выставляет Hop Limit=63 на исходящих. Так что один только TTL даёт ложные срабатывания в обе стороны.

Что реально считает антифрод

Маркетплейсы (Avito, Ozon, Wildberries, eBay) собирают десятки сигналов одновременно. По сети — это:

- Hop count до клиента (через ICMPv6 Time Exceeded или TCP-анализ)

- Начальный TTL, восстановленный из наблюдаемого

- TCP/IP fingerprint (p0f, JA3/JA4, TCP timestamps)

- RTT первого пакета (SYN → SYN-ACK)

- MTU Path Discovery — датацентры часто отдают 1500, residential с PPPoE — 1492

- Наличие IPv6-фрагментации, размер окна, опции TCP

Отдельно смотрят на IPv6-специфику: extension headers, flow label, наличие Privacy Extensions (RFC 4941). Residential-устройства почти всегда используют временные адреса с рандомизированным интерфейсным идентификатором. Серверы в датацентрах — стабильные EUI-64 или фиксированные суффиксы.

Восстановление начального TTL

Практический приём: сервер маркетплейса видит пакет с Hop Limit=54. Возможные начальные значения — 64, 128, 255. Ближайшее сверху — 64, значит хопов было 10. Если видит 60 — хопов 4, начальный 64. Это уже подозрительно.

Проверить можно простым скриптом через scapy или обычным ping6 с трассировкой:

```bash

traceroute6 -n -q 1 -w 1 2a03:2880:f10c:83:face:b00c:0:25de

```

На Linux можно вытащить TTL из ответа:

```bash

ping6 -c 1 -W 1 google.com | grep -oP 'hlim=\K\d+'

```

Python-вариант с восстановлением начального значения:

```python

import subprocess, re

def probe_hop_limit(host):

out = subprocess.run(

["ping6", "-c", "1", "-W", "1", host],

capture_output=True, text=True

).stdout

m = re.search(r"hlim=(\d+)", out)

if not m:

return None

observed = int(m.group(1))

for initial in (64, 128, 255):

if observed <= initial:

return initial, initial - observed

return None, None

print(probe_hop_limit("2606:4700:4700::1111"))

```

Для residential-подключения в Москве до сервера в Франкфурте типично 12-15 хопов. От виртуалки в том же Франкфурте — 3-4.

TCP fingerprint как добивка

TTL легко подделать через iptables или sysctl. А вот TCP-стек — сложнее. p0f смотрит на:

- Window size (у Linux 2.6+ — 29200, у Windows 10 — 65535)

- TCP options: MSS, SACK permitted, Timestamps, NOP, Window Scale

- Порядок опций — уникален для каждой ОС и версии ядра

Датацентр-прокси на Debian 12 с ядром 6.1 выдаст один fingerprint. Реальный iPhone через Wi-Fi — совсем другой. Антифрод маркетплейса сравнивает fingerprint с заявленным User-Agent. Если UA говорит "iPhone Safari", а TCP-стек — Linux 6.x, это красный флаг.

Вот как выглядит сбор сигналов в Python через p0f-подобный анализ на стороне сервера:

```python

from scapy.all import sniff, TCP, IP, IPv6

def on_packet(pkt):

if IPv6 in pkt and TCP in pkt:

tcp = pkt[TCP]

opts = [o[0] for o in tcp.options]

print({

"src": pkt[IPv6].src,

"hop_limit": pkt[IPv6].hlim,

"window": tcp.window,

"options": opts,

"mss": dict(tcp.options).get("MSS"),

})

sniff(filter="tcp and ip6", prn=on_packet, count=20)

```

По набору опций и window size можно с высокой точностью отделить Linux-сервер от мобильного клиента.

RTT и географическая привязка

Маркетплейсы знают примерную топологию интернета. Если RTT до клиента 2 мс, а заявленный адрес — residential в Новосибирске, физика не сходится: свет по оптоволокну идёт 5 мкс на километр, минимум 30 мс до Москвы. Значит, клиент сидит в датацентре рядом с сервером.

Типичные цифры:

| Сценарий | RTT | Hop count | Начальный TTL |

|---|---|---|---|

| Residential Москва → сервер во Франкфурте | 35-45 мс | 12-16 | 64 |

| VPS Hetzner → сервер во Франкфурте | 1-3 мс | 2-4 | 64 |

| Мобильный LTE → сервер в Москве | 40-70 мс | 10-14 | 63 |

| Cloudflare Worker → origin | 0.5-2 мс | 1-2 | 63 |

Комбинация RTT < 5 мс + hop count < 5 + стабильный IPv6-суффикс = датацентр с вероятностью 95%.

Кейс: парсинг цен через /64 из OVH

Команда занималась мониторингом цен на маркетплейсе электроники. Арендовали /64 в OVH, разворачивали 5000 адресов, каждый запрос — с нового. Первые сутки работало. На вторые — капча на 80% запросов.

Причина: OVH анонсирует свой /64 через один пограничный маршрутизатор. Hop count до сервера маркетплейса — 4. TTL во всех запросах — 60, начальный 64. RTT стабильно 3-5 мс. Антифрод увидел 5000 разных IPv6, но все с одинаковым fingerprint, одинаковым hop count и RTT в узком диапазоне 3±1 мс. Это подпись датацентра.

Решение — не в подмене TTL (это лечится за час), а в распределении по residential-прокси. Для реальных задач сбора данных с маркетплейсов нужны residential-пулы с настоящими домашними подключениями, а не арендованные /64. Сервисы вроде lexic.ml отдают IPv6 из residential-диапазонов, где hop count и RTT совпадают с реальными абонентами конкретного региона.

Кейс: подмена TTL и что пошло не так

Инженер решил обойти детект через iptables:

```bash

ip6tables -t mangle -A POSTROUTING -j HL --hl-set 64

```

Все исходящие пакеты получили Hop Limit=64. Логика: сервер увидит 64, решит, что клиент в одном хопе. Но это работает наоборот. Сервер в датацентре получает пакет с HL=64, вычитает свой начальный 64 — выходит 0 хопов. Такого не бывает: даже localhost имеет минимум один интерфейс. Антифрод помечает трафик как аномальный.

Правильная подмена — выставить HL так, чтобы после всех маршрутизаторов получилось правдоподобное значение. Например, если до сервера 4 хопа, ставить 64+4=68. Но тогда ломается обратный путь и ICMPv6 Path MTU Discovery начинает сыпать ошибки. Плюс p0f всё равно видит Linux-ядро, а не macOS.

MTU как дополнительный сигнал

IPv6 не фрагментирует пакеты на маршрутизаторах — только источник. Минимальный MTU для IPv6 — 1280 байт. Residential с PPPoE обычно имеет MTU 1492, с DHCPv6 — 1500. Датацентры почти всегда 1500.

Если клиент заявляет себя как домашний пользователь в Германии, а его PMTU = 1500 без единой фрагментации — это не PPPoE. Значит, либо кабель, либо датацентр. Антифрод делает активную проверку: отправляет пакет 1500 байт с DF и смотрит на ICMPv6 Packet Too Big. Residential-провайдеры часто отдают MTU 1480-1492, датацентры — 1500 ровно.

Privacy Extensions и суффикс адреса

RFC 4941 предписывает residential-устройствам использовать временные адреса. Суффикс меняется каждые несколько часов, интерфейсный идентификатор выглядит случайным. Windows и macOS это делают по умолчанию. Linux — только если включён `net.ipv6.conf.all.use_tempaddr=2`.

Серверы в датацентрах обычно используют статический суффикс: EUI-64 из MAC или фиксированный адрес. Если из /64 приходят 5000 адресов с суффиксами, отличающимися только в последних 16 битах — это один хост с несколькими адресами, а не 5000 клиентов.

Проверка на сервере:

```python

def is_suspicious_suffix(addr: str) -> bool:

suffix = addr.split(":")[-4:]

as_int = int("".join(suffix), 16)

низкие значения суффикса — ручная настройка

return as_int < 0x100000000

```

Что делать, если нужен реальный residential

Три подхода, каждый со своими граблями.

Первый — покупать доступ к residential-прокси-сетям, где трафик идёт через реальные устройства абонентов. Дорого, но hop count и TTL совпадают с настоящими. Провайдеры вроде Bright Data или Smartproxy держат пулы, но маркетплейсы их тоже знают и банят целыми ASN.

Второй — мобильные прокси. LTE-операторы выдают IPv6 с CGNAT, hop count 10-14, RTT 40-70 мс, Privacy Extensions включены. Это самый чистый вариант по сетевым метрикам, но дорогой и с ротацией раз в 5-15 минут.

Третий — распределённые residential-пулы, где один IPv6 принадлежит реальному абоненту и не переиспользуется сотнями клиентов. Тут важна не только сеть, но и репутация: если адрес уже светился в парсинге, его забанят по поведенческим метрикам, а не по TTL.

Итог

Антифрод маркетплейса не смотрит на один сигнал. TTL и hop count — вход, но решение принимается по совокупности: TCP fingerprint, RTT, MTU, поведение суффикса, история адреса. Подделать один параметр легко, подделать все сразу — почти невозможно без реального residential-подключения.

Если задача — обойти детект, начинать надо не с iptables, а с вопроса: соответствует ли вся сетевая картина заявленному региону и типу подключения. Один неверный параметр из десяти обычно не палит. Три-четыре — палят гарантированно.

✔️Купить прокси