Смена мобильного IP каждые две минуты не делает клиента новым для антибота. Разбираю четыре слоя, по которым его узнают за прокси (TCP/IP, TLS, HTTP/2, поведение), и показываю на суточном логе из 2 272 207 смен IP на 9 067 модемах, почему сама ротация по таймеру превращается в поведенческий отпечаток. Плюс честный список того, что в этом разборе не получилось.

С чего началось

Один из самых частых вопросов к поддержке мобильных прокси звучит примерно так. Стоит ротация раз в две минуты, IP каждый раз новый, а сайт всё равно узнаёт клиента и режет ему выдачу или подсовывает капчу. Отвечать «дело в отпечатке браузера» можно, но это ответ ни о чём.

Я работаю инженером в mobileproxy.space и решил ответить подробнее. Клиента за мобильным прокси можно узнать по четырём слоям, и смена IP не трогает ни один из них. Три слоя ниже сетевого адреса я разберу как механику, без цифр, потому что стенда для замеров у нас пока нет. Четвёртый, поведенческий, посмотрю на реальной телеметрии нашей сети за сутки. И главный сюрприз в том, что сама ротация по таймеру и есть поведенческий отпечаток.

Половина читателей этого хаба ловит ботов, а не запускает их, поэтому пишу так, чтобы можно было поймать меня за руку на любой цифре.

Откуда цифры

Единственный источник чисел в статье это внутренний лог, куда пишется каждая смена IP на каждом модеме сети. Окно разбора с 21.09.2026 12:00 по 22.09.2026 11:52 МСК, всё посчитано агрегатами по этому логу, без выборочных примеров «на глаз».

За эти сутки в логе 2 272 207 смен IP на 9 067 модемах и 7 712 прокси‑портах. Смен по таймеру 1 195 083 (52,6%), по ссылке или API 1 077 124 (47,4%). Активных модемов в любой час суток было от 5 553 до 6 223.

Интервалы между сменами одного модема на полном логе считать было долго, поэтому взял выборку 10% модемов по остатку от деления идентификатора. Получилось 113 785 интервалов по таймеру и 98 089 по ссылке. Запрос интервалов (оконная LAG по каждому модему; имена таблицы и полей здесь условные; возврат того же IP считался тем же приёмом через LAG по адресу):

SELECT who,

       CASE WHEN gap < 60   THEN 'a <60'

            WHEN gap < 120  THEN 'b 60-119'

            WHEN gap < 300  THEN 'c 120-299'

            WHEN gap < 600  THEN 'd 300-599'

            WHEN gap < 1800 THEN 'e 600-1799'

            ELSE 'f 1800+' END AS bucket,

       COUNT(*) AS n

FROM (

  SELECT who,

         CAST(ts AS SIGNED)

         - CAST(LAG(ts) OVER (PARTITION BY modem_id ORDER BY ts, id) AS SIGNED) AS gap

  FROM ip_change_log

  WHERE modem_id MOD 10 = 0

) t

WHERE gap IS NOT NULL

GROUP BY who, bucket;

На этом же запросе я нашёл дефект нашего логгера, но о нём ниже, в разделе про то, что пошло не так.

Где заканчивается каждый слой

Чтобы дальше было понятно, кто что видит, нужно один раз проследить путь запроса.

Клиент подключается к порту на нашей ноде. У каждого порта два входа, HTTP и SOCKS5, причём SOCKS всегда на единицу больше HTTP. Это, пожалуй, единственное соглашение в сети, которое ни разу не обсуждалось на созвонах, его просто все знают. Прокси‑демон принимает соединение, а агент ноды управляет модемами, в том числе отдаёт команду на смену IP. Дальше демон открывает новое TCP‑соединение через модем, трафик проходит NAT оператора и приходит на целевой сервер.

TCP‑сессия клиента заканчивается на ноде. TLS‑сессия проходит через ноду насквозь, потому что для HTTPS прокси работает как туннель CONNECT. HTTP/2 живёт внутри TLS и тоже проходит насквозь. Поведение целиком на стороне клиента и его скриптов.

Смена IP меняет только адрес после NAT оператора.

Слой 1. TCP/IP

Первое, что видит сервер, ещё до всякого TLS, это SYN‑пакет. В нём есть стартовый TTL, размер окна, MSS, набор и порядок TCP‑опций, масштабирование окна, SACK, таймстампы. Каждая операционная система и даже каждая крупная версия ядра выставляет их по‑своему, и на этом уже много лет работает пассивное определение ОС, тот же p0f.

Через мобильный прокси целевой сервер видит не стек клиента, а стек нашей ноды, потому что именно она открывает соединение к серверу. С точки зрения маскировки это плюс, ОС клиента наружу не торчит. С точки зрения связывания сессий это минус. Стек ноды один и тот же при каждой смене IP, и все адреса, которые модем получил за день, приходят на сервер с одинаковым TCP‑профилем.

Одного TCP‑профиля для опознания мало, он общий у огромного числа машин на такой же ОС. Но как признак «это точно не смартфон» он работает. Мобильный IP из пула оператора и серверный стек в одном SYN это несовпадение, которое антибот вполне может учесть.

Что делает с TTL и опциями NAT оператора, я не проверял. Предположительно адреса и порты переписываются, а опции проходят нетронутыми, но это как раз то, ради чего нужен стенд.

Слой 2. TLS, JA3 и JA4

ClientHello прокси не трогает вообще. Сервер получает его ровно таким, каким его собрала TLS‑библиотека клиента. Из него считается JA3, хеш от версии протокола, списка шифров, списка расширений, эллиптических кривых и форматов точек. JA4 добавляет к этому ALPN, признак SNI, количество шифров и расширений и сортирует списки, чтобы хеш не ломался от рандомизации порядка расширений в свежих браузерах.

Одна и та же сборка Chrome даёт одинаковый JA4 на любом IP. Тот же curl или requests на Python дают свой, узнаваемый и совершенно не браузерный отпечаток. Если один хеш прилетает с мобильного адреса, потом с другого адреса того же оператора, потом с третьего, а между запросами пауза строго две минуты, связать эти сессии несложно.

Есть и более прямой механизм. TLS позволяет возобновить сессию по тикету или PSK. Клиент, который после смены IP предъявляет тикет от предыдущего соединения, сам сообщает серверу, что он тот же самый. Никакой эвристики не нужно.

Отключают ли возобновление наши клиенты, я не знаю. Данных о содержимом их TLS у нас нет и быть не должно.

Слой 3. HTTP/2

Внутри TLS живёт ещё один набор параметров, который клиент выставляет при открытии HTTP/2-соединения. Первый кадр SETTINGS с размером таблицы заголовков, лимитом потоков и начальным окном. Кадр WINDOW_UPDATE. Кадры PRIORITY, которые часть браузеров шлёт при старте, а часть нет. Порядок псевдозаголовков в первом запросе, у разных браузеров разный, у HTTP‑библиотек тоже. Из этого собирается стабильный отпечаток, и он, как и JA4, не зависит от IP.

Сюда же честно нужно отнести и совсем скучное. Cookie, localStorage, ETag в кеше, идентификатор в теле запроса. Клиент, который меняет IP каждые две минуты и ходит с одной и той же сессионной cookie, тратит смены зря. Сколько таких среди наших пользователей, я оценить не могу, прокси не видит содержимое HTTPS.

Три слоя, и ни один из них не знает, что IP поменялся.

Слой 4. Поведение, на этот раз с цифрами

Здесь у меня есть данные, и они оказались интереснее, чем я ждал.

Гребёнка ровных минут

Смены по таймеру распределены по интервалам так. p10 и p50 по 120 с, p90 равен 420 с, p99 равен 1 199 с. Из 113 785 интервалов 102 231, то есть 89,8%, кратны ровно минуте. Пики с допуском плюс‑минус две секунды:

Интервал

Доля интервалов по таймеру

120 с

55,1%

180 с

16,3%

300 с

14,7%

600 с

5,0%

900 с

2,2%

Больше половины всех таймерных ротаций в сети идут ровно раз в две минуты.

Интервалы между сменами IP одного модема: таймер даёт гребёнку ровных минут, ссылка — размытое облако (выборка 10 % модемов)
Интервалы между сменами IP одного модема: таймер даёт гребёнку ровных минут, ссылка — размытое облако (выборка 10% модемов)

Смены по ссылке ведут себя иначе. Если выбросить интервалы короче 30 с (почему, объясню в разделе про дефекты), получается p10 = 47 с, p50 = 283 с, p90 = 1 305 с, p99 = 5 140 с. Кратных минуте среди них около 0,8%. Но и здесь есть свой пик, 305 с, на него приходится 15,6% записей. Это клиентские скрипты с циклом «спать пять минут, дёрнуть ссылку», где к 300 с добавляется время самой смены. Второй кластер сидит около 1 310–1 325 с, та же история.

Один реальный порт с таймером. За два часа 60 смен, все интервалы от 119 до 121 с. Все 60 адресов разные, из трёх сетей /16 одного оператора. Каждая смена заняла от 2,2 до 19,3 с.

Теперь поставьте себя на место антибота, у которого уже есть JA4 этого клиента. Он видит поток запросов с одинаковым отпечатком, в котором каждые две минуты адрес меняется на соседний из того же пула, а перед сменой запросы на несколько секунд пропадают. На человека с телефоном это не похоже. Это похоже на cron.

Порт со сменой по ссылке из того же лога выглядел иначе. 21 запись за три часа, из них 6 дублей, а реальные интервалы разбросаны от 115 до 1 838 с. Гребёнки нет.

Новый IP не всегда новый

После смены по таймеру новый адрес совпал с предыдущим в 1,27% случаев (1 401 из 110 728), попал в пять предыдущих в 1,97%, в десять предыдущих в 2,29%. После смены по ссылке с интервалом от 30 с уже 8,0%, 9,2% и 9,7% (4 266, 4 900 и 5 165 из 53 302).

А вот если между двумя сменами по ссылке прошло меньше 30 с, в 68,1% случаев (27 674 из 40 658) вернулся тот же IP. Модем ещё не переключился, повторный запрос забрал старый адрес. Практический вывод для тех, кто дёргает API из скрипта. Не проверяйте IP сразу после команды, дождитесь ответа о завершении.

За сутки медианный модем сделал 97 смен и получил 89 уникальных адресов, p90 равен 713 сменам и 585 адресам, максимум 8 534 смены и 6 127 адресов. Доля смен, давших ещё не виденный за сутки этим модемом адрес, у тихих модемов 67%, у активных 80–87%.

И отдельная группа. 562 модема сделали в среднем по 45 смен и весь день получали один и тот же адрес. Липкий NAT оператора, смена проходит, адрес снаружи не меняется. Как это выглядит с той стороны, понятно и без графика.

Сколько разных адресов получает один модем за сутки (все 9 067 модемов)
Сколько разных адресов получает один модем за сутки (все 9 067 модемов)

Сколько адресов проходит через один порт за час

Считал по прокси‑часам, всего их 139 105 на 7 712 портах.

Уникальных IP за час на порт

Прокси‑часов

Доля

1

10 843

7,8%

2

11 342

8,2%

3–5

26 631

19,1%

6–10

22 371

16,1%

11–20

39 065

28,1%

21–30

25 022

18,0%

31–60

2 353

1,7%

60 и больше

1 478

1,1% (в среднем 150,6 адреса в час)

Самая частая картина, 11–20 адресов в час на порт, это как раз таймер на две‑пять минут. 1,7% прокси‑часов с 31–60 адресами и 1,1% с 60 и больше это смена чаще, чем раз в минуту. Что человек хочет получить, меняя мобильный IP чаще, чем каждые полминуты, я честно не понимаю. С точки зрения слоёв 1–3 он просто раздаёт свой отпечаток по полторы сотни адресов за час.

Один порт — сколько адресов в час (139 105 прокси-часов)
Один порт — сколько адресов в час (139 105 прокси‑часов)

Пул оператора

Здесь самое неприятное. Наш парк за сутки увидел вот такую часть адресного пространства операторов.

Оператор

Модемов

Смен

Уникальных IP

Сетей /24

Сетей /16

Уник. IP от смен

Средняя смена

Билайн

3 017

700 365

104 867

605

26

15,0%

4,80 с

МегаФон

2 479

686 221

88 884

652

26

13,0%

4,48 с

Tele2

740

217 872

73 613

865

21

33,8%

4,06 с

Yota

713

170 170

30 636

586

23

18,0%

4,75 с

МТС

494

111 685

34 016

588

12

30,5%

3,81 с

Kyivstar

301

92 253

46 362

1 087

8

50,3%

3,97 с

Vodafone UA

179

69 671

44 311

633

7

63,6%

5,89 с

life UA

176

62 349

26 889

183

4

43,1%

4,27 с

Предпоследний столбец читается так. У Билайна и МегаФона семь смен из восьми возвращают адрес, который за эти сутки уже получал кто‑то из наших модемов. У Kyivstar каждая вторая смена даёт адрес, которого сегодня ещё не было. При этом весь дневной трафик Билайна с 3 017 модемов уложился в 605 сетей /24 и 26 сетей /16.

Для антибота это означает, что «новый» мобильный IP с высокой вероятностью уже вчера или час назад приходил с тем же JA4 и тем же ритмом. Репутация копится на /24, и 605 сетей это совсем не тот объём, который сложно запомнить.

Размер видимого пула оператора: уникальные IP, модемы и сети /24 за сутки
Размер видимого пула оператора: уникальные IP, модемы и сети /24 за сутки

Ритм суток

Смены по таймеру идут ровно, от 48 459 до 50 895 в час, круглые сутки без провалов. Ручные и API‑смены растут с 29 782 в 5:00 МСК до 56 467 в 10:00, в 1,9 раза. У людей есть рабочий день. У cron его нет, и это видно даже на агрегате по всей сети, что уж говорить про отдельный клиентский отпечаток.

Суточный профиль: таймер ровный, ручные и API-смены растут к 10:00 МСК
Суточный профиль: таймер ровный, ручные и API‑смены растут к 10:00 МСК

Пауза на смену

Сама смена, от команды модему до появления нового адреса, по таймеру занимает в среднем 4,59 с (минимум 0,55 с, максимум 31,95 с). Быстрее 3 с уложилось 42,5% смен, быстрее 5 с 75,2%, быстрее 10 с 92,8%, быстрее 20 с 99,4%. По ссылке в среднем 5,07 с и хвост длиннее, максимум 54,89 с. Быстрее 3 с там 37,8%, быстрее 5 с 67,7%, быстрее 10 с 90,6%.

По операторам разброс небольшой, от 3,81 с у МТС до 5,89 с у Vodafone UA. Почему у Vodafone UA дольше всех, я не разбирался, возможно, дело в конкретных нодах, где стоят эти 179 модемов.

Для отпечатка важно другое. Перед каждой сменой у клиента образуется пауза в несколько секунд, и на таймере она повторяется с той же периодичностью, что и сама смена. Ещё один зубец на той же гребёнке.

Сколько длится сама смена IP: таймер против ссылки
Сколько длится сама смена IP: таймер против ссылки

Что не получилось

Дефект логгера. 25,9% записей о сменах по ссылке (25 419 из 98 089 в выборке) оказались точными дублями, интервал 0–2 с, тот же IP, та же длительность. Одно событие писалось в лог два или три раза. Мы про это не знали, пока я не построил гистограмму интервалов и не увидел столб в нуле. Все цифры по сменам по ссылке в статье посчитаны либо без интервалов короче 30 с, либо с явной оговоркой. Где именно теряется идемпотентность записи, ещё предстоит найти, разбор уже идёт.

Нет стенда. Всё про TCP/IP, JA3/JA4, HTTP/2 и возобновление TLS выше это описание механики. Стенд с захватом трафика на выходе из модема и на принимающем сервере ещё предстоит собрать, и я не хочу писать «мы проверили», пока его нет.

Разбор только за сутки. Одно окно в 24 часа означает, что я не вижу недельных циклов, выходных и того, как пул оператора дрейфует со временем. Длинный ряд — отдельная работа с отдельной методологией.

Поле оператора. В таблице модемов примерно у 8 тысяч записей оператор не заполнен, такие модемы в операторскую таблицу не вошли. Сколько из них было активно именно в эти сутки, я не сверял, так что таблица по операторам это срез по тем, у кого поле заполнено, и ничего больше.

Выборка 10% по остатку от деления идентификатора. Смещения по операторам или нодам я не ожидаю, но и не проверял.

Что с этим делать

Обходить антибот я не обещаю, и список ниже не про это. Он про то, чтобы не платить за смены IP, которые ничего не дают.

  • Смена IP не сбрасывает ни TCP‑профиль ноды, ни ваш JA4, ни параметры HTTP/2, ни cookie и TLS‑тикеты. Всё, что выше сетевого адреса, находится на вашей стороне, и решать это надо там.

  • Таймер с ровным периодом сам по себе сигнал. Если ротация нужна, привязывайте её к событию (закончилась задача, закрылась сессия) или хотя бы добавляйте случайный сдвиг. Пик 305 с у скриптов показывает, что даже «ручной» цикл легко превращается в такой же таймер.

  • Не запрашивайте IP сразу после команды. При интервале меньше 30 с в 68,1% случаев вернётся старый.

  • Смена чаще раза в минуту размазывает один отпечаток по сотне адресов и не даёт ничего взамен. 1,7% плюс 1,1% прокси‑часов делают именно это.

  • Считайте репутацию по /24 и по оператору. У крупных российских операторов пул, который мы видим за сутки, невелик, и «новый» адрес чаще всего уже был.

Тем, кто на стороне защиты, всё это наверняка знакомо, но сформулирую как признак: одинаковый TLS‑отпечаток, пауза в несколько секунд каждые ровно 120 или 300 секунд и смена адреса внутри одного пула оператора сразу после паузы.

А для меня самый неожиданный итог разбора вообще не про антиботы. Четверть записей в одном из наших логов были дублями, и заметил это никто не мониторинг, а гистограмма для статьи. Теперь два открытых вопроса. Где именно агент ноды пишет событие дважды, и что на самом деле делает CGNAT оператора с TCP‑опциями на выходе из модема. На второй без стенда я отвечать не буду.

Автор работает в mobileproxy.space, телеметрия в статье снята с внутреннего лога смен IP сервиса.


Комментарии (1)


  1. Coprolog
    23.09.2026 09:12

    А как же канвас? Если канваса нет, там и без смены айпишника понятно, что ботяра