Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal

в 9:00, , рубрики: Без рубрики

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 1


Бывало ли у вас такое, что вы подключились к общественной сети или мобильному интернету, но он себя странно ведёт. Надпись «Подключено, без доступа к интернету» отсутствует, устройство думает, что всё нормально. Вы открываете браузер и видите «Пройдите проверку личности в сети нашего кафе» или в случае с мобильным интернетом «Пополнить баланс можно тут, тут и тут».

Так вот, переходя ближе к теме. Вы можете воспроизвести подобное поведение очень просто прямо сейчас при помощи вашего мобильного телефона и ноутбука. Я в своём конкретном кейсе буду пользоваться услугами оператора красного цвета, однако проблема актуальна для всех текущих 4 монополистов рынка сотовой связи. Как вам, скорее всего, уже известно, они около года меняют свою политику, внедряя одно интересное нововведение — с вас требуют дополнительной платы за раздачу интернета поверх основного пакета. То есть вы не можете взять и использовать свои 7 гигов на месяц как ресурс для раздачи при помощи точки доступа. Для точки доступа вам предлагают отдельный, зачастую совсем невыгодный тариф. Конечно, можно сменить основной тариф на специальный «тариф для раздачи» и платить втридорога, но, как вы понимаете, сегодня мы в потребителя будем играть совсем недолго. Сейчас по пунктам нужно доказать нечестность подобной политики и с чувством завершённого введения перейдём к непосредственно технотексту.

Подобные условия пользования, само собой, порождают внутреннее недовольство пользователей:

За интернет они платят? — Да.

Раздача как-либо использует ресурсы провайдера сверх нормы? — Нет.

Так в чём же дело?

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

Как вы понимаете, согласиться на такой бред можно только от безысходности. Думаю, не стоит доказывать, что абсолютно неэластичная экономика рынка в сфере услуг связи является признаком монополии. Если же не душнить, то лучше всего мысль передаст эта фраза: «Какие бы условия пользования оператор ни поставил — всё равно схавают».

Ещё немного демагогии

ФАС — это такие важные дяди, которые (если верить 2ГИС) сидят в Москве и делают то, что обязана делать монополия на насилие и территорию (Государство): говорят, что они следят за соблюдением законодательства в сфере конкуренции, естественных монополий, рекламы и т. д. и т. п.

Если вы погуглите запросы вроде «ФАС против операторов», то узнаете, что эта тема мусолится уже по меньшей мере 5 лет. И в этот раз в адрес операторов сотовой связи были предъявлены обвинения по поводу «платной раздачи трафика». Произошло это более двух месяцев назад, когда я ещё только планировал эту статью.

UPD (10 ноября)

Все операторы на данный момент «согласились» отменить платную раздачу трафика, так что начало этой статьи может оказаться не таким попсовым на выходе, как планировалось.
Пока ещё ничего на практике не изменилось и плата всё ещё взимается, но уже можно рассчитывать на возвращение привычной всем раздачи на не являющихся «безлимитными» тарифах.

UPD (15 ноября)

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

Манипуляции, которые я разберу далее, останутся в таком случае актуальны для «безлимитных» тарифов, где раздача в принципе не предусмотрена, и в бесплатных сетях, где пролетают пинги, но требуется верификация по номеру телефона или, что ещё хуже, по СНИЛС.

Да, уже есть общественные сети, где идентификация в Wi-Fi происходит через Госуслуги — такой вот нефритовый стержень в колёса свободного интернета.

Подобная схема будет работать и в сетях, где идёт блокировка OpenVPN (с ним мы ещё сегодня пообщаемся) по косвенным признакам или просто всего неопознанного трафика кроме служебного. Собственно, эта идея же настолько стара, как и сам интернет. Она была на пике своём ещё в десятых годах, а вот современный её вид мы с вами рассмотрим далее.
Если вы уже поняли предмет нашего разговора из заголовка, то листайте сразу до оглавления. В противном же случае, предлагаю начать исследовать тему по порядку.

Как это работает?

Сейчас я вам расскажу одну историю из жизни: 4 года назад (в 2019 году) я случайно оказался уж очень далеко от дома. Если быть точнее, то в сибирском смешанном лесу наедине с вышкой сотовой связи и симкой от «Мегафона». Тогда ещё трава была зеленее, а раздача интернета не требовала доп. платы — она просто не работала на моём безлимитном тарифе. Попытки найти способ использовать сеть с ноутбука меня привели к обсуждению вроде этого (4pda, 2014 год). Умельцы на тот момент уже выяснили способ определения раздачи, который использовался для блокировки трафика. Это была простая блокировка по TTL.

▍ TTL

В подробности вникнем позже, пока достаточно такого объяснения:

Каждое устройство отправляет пакет в интернет, делая в нём запись TTL (Time To Live), равную целому положительному числу. Каждое следующее устройство, которое будет пропускать этот пакет через себя на пути к получателю, по идее должно переписывать (TTL) на (TTL-1).

Оператор с лёгкостью замечает, что одно устройство отправляет пакеты с разными TTL, и блокирует те, в которых TTL больше на единицу. Какие же в таком случае есть варианты обхода блокировки?

  1. Отправлять с устройства, подключённого к точке доступа, все пакеты с заранее увеличенным на единицу TTL. В таком случае после ретрансляции телефоном трафик будет выглядеть как его собственный (почти). Однако это невозможно без root (sudo, Admin) прав на подключённом устройстве.
  2. На стороне телефона закастомить точку доступа так, чтобы она не уменьшала TTL. Как вы понимаете, для этого нужно получить контроль над IP уровнем (об этом тоже позже) пакета и редактировать его. Однако, это тоже не представляется возможным без root (sudo) прав на точке доступа. Природа этого тоже будет разобрана в данной статье.

Собственно, первый способ совершенно не универсален и требует root на всех устройствах в схеме, кроме точки доступа. Второй же позволяет ограничиться одним разблокированным устройством. Так вот, в сибирском лесу я оказался тогда с бородатым Galaxy Note 2 (2012 год выпуска), на котором уже давно стояла LineageOS и был root, благо на старых самсах не было никакого Knox, ваш телефон не терял гарантию при перепрошивке и даже не требовал на это разрешения центрального сервера компании-производителя, которое сейчас нужно ждать по несколько недель — да, да Xiaomi у меня тоже побывал.

Готовое приложение с многозначным названием вроде TTL editor на тот момент решило все мои проблемы и обеспечило пусть и медленным (в лесу, всё же), зато расшаренным на ноут интернетом.

▍ DPI

И всей дальнейшей статьи банально не было бы, если бы в прошлом году я не поехал в тот же лес с тем же железом и не обломался с треском, узнав, что теперь трафик блокируют по паттернам его содержимого. Даже в пассивном режиме Windows отправляет массу служебных пакетов на служебные серверы, которые заранее считаются оператором «несвойственными для android/ios».

Вот каких подколов можно ожидать, когда поставщик услуг, которому ты платишь деньги, может абсолютно не считаться с пользователями и «затягивать удавку».

Ладно, ладно, я понимаю, что такой около-пикабушный гонор никому тут не интересен. Если вы ещё здесь, то я безумно извиняюсь за столь зелёное вступление.

Решаем проблему самостоятельно

Моя первая статья на Хабре носит название Ping пакеты как временное хранилище данных на python raw socket, написана она была буквально за одну ночь в состоянии аффекта (хабра-эффекта) и, как и полагается статье из песочницы, не блещет оформлением или качеством текста. Вопреки ожиданиям, эта статья не будет являться переосмыслением (ремейком, ребилдом, повтором) вышеупомянутой. Уровень материала, изложенного тут, обещает быть выше и не будет смахивать на ликбез по видам сетевого оборудования.

Порог вхождения в дальнейший текст — понимание разницы между белыми и серыми IP-адресами. Да, вот такая нынче на Хабре инфляция параметра «сложность» на посте, спасибо можете передать научпопу и новостям.

В момент написания старой статьи я ещё не знал, что переизобретаю PingFS.

«Ты же прямо сейчас собираешься переизобрести PingTunnel, лицемер ты чёртов!» — сказали бы вы сейчас и оказались бы отчасти правы.

В то же время идею мне подкинули в комментариях:

Тык

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 2

В общем, размазанное вступление ещё и технической части делать не очень хочется — и без этого есть что рассказать.

Суть проблемы же была озвучена в первом абзаце, а её решение (о боже!) в заголовке. Так что считаю разумным перейти сразу же к оглавлению.

  1. Введение
  2. Ещё немного демагогии
  3. Как это работает?
  4. Решаем проблему самостоятельно
  5. Реальный пакет из WireShark
  6. Начинаем реализовывать
  7. NAT?
  8. ICMP тоже пробивает дыры?
  9. Инкапсуляция
  10. Настраиваем OpenVPN под Windows
  11. Так как же оператор транслирует icmp?
  12. Заключительные штрихи
  13. Вместо заключения

Раз уж тезис о PingTunnel мной был озвучен, то кажется необходимым его развернуть.

PingTunnel — утилита для передачи TCP/UDP-трафика через ICMP.

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 3

Есть две ноды, между которыми возможна связь по IP (например, в глобальном интернете).

  • Первая — pingtunnel client (ретранслятор, инкапсулятор).
  • Вторая — pingtunnel server (приёмник, прокси).

Обе pingtunnel-ноды (далее client и server) знают IP-адреса друг друга.

Пользователи за первой нодой подключаются к её порту, а она, в свою очередь, туннелирует все пакеты, полученные из него при помощи ICMP, до удалённой прокси-ноды (server).

PingTunnel Server же от своего имени общается с неким однозначным и неизменным target server.

С точки зрения юзера за pingtunnel clientом, он может общаться с самим клиентом, как если бы это был target server.

Как вы могли заметить, концептуальные неприятности всплывают практически сразу:

Самыми универсальными и везде используемыми типами icmp однозначно являются req (Echo request), repl (Echo reply), time limit (TTL exceeded) и unreach (Host unreachable) — позже мы разберём их подробнее.

Для большей стабильности трансфера полезной нагрузки в многоуровневых сетях по icmp стоит использовать самую популярную связку (req,repl) или (req,time) в качестве альтернативы. Из этого вытекает первое неудобство: сетевой интерфейс ping server должен быть в «режиме простоя» — не заниматься собственным творчеством и не отвечать на icmp самостоятельно.

Решение этой проблемы весьма очевидно — включить режим простоя, однако для понимания самой сути проблемы нужно взглянуть на принципиальное различие между Windows и Linux при работе с сетью.

На самом деле, в стандартном инструментарии Windows не предусмотрена работа с адаптером на пакетном уровне (L2, Ethernet). Историю этой проблемы я разгребать не хотел бы, а первый же ответ на stackoverflow гласит, что причиной тому Win32 limitations — спасибо за многословность. Ну да оно и не важно, ведь оконные люди таки сварили себе почти настоящие сырые сокеты при помощи костыльного драйвера, названного WinPcap.

WinPcap — программный инструмент, работающий в среде Windows и позволяющий взаимодействовать с драйверами сетевых интерфейсов. Функционал программы обеспечивает приложениям возможность захвата и передачи сетевых пакетов в обход стека протоколов.

Самый популярный ui к pcap — это, конечно, WireShark. Его мы и будем использовать для дальнейших экспериментов и наблюдения за поведением скриптов.

Однако переписать стандартные возможности драйвера подобное решение всё равно не может. Пинги всё так же будут получать автоматические ответы от ОС, пока мы её саму любезно не попросим об одолжении.

Как оказалось, этим занимается встроенный брандмауэр, конфигурируемый панелькой wf.msc. Тыкаем Win+R + wf.msc, идём Правила для входящих > Общий доступ к принтерам ... входящий ICMP и выключаем все пунктики.

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 4

На Linux можно было бы просто прописать net.ipv4.icmp_echo_ignore_all = ‘1’

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

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 5

Теперь идём по пути наименьшего сопротивления и находим первое же готовое решение по запросу PingTunnel: github.com/esrrhs/pingtunnel.

Качаем эту реализацию, делаем всё в соответствии с readme-шкой и видим такую картину:

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 6

На сервере ситуация аналогична, обе стороны бесконечно ждут подключения. В Wireshark`е же всё выглядит исключительно правдоподобно. Как вы уже могли догадаться, ноды просто не принимают пакеты — для этого нужно использовать pcap.

Второй шанс готовым реализациям я решил дать с ptunnelNG. На его GitHub указано, что он должен работать с WinPcpap и Pcap для полноценной совместимости с Windows…

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 7

Npcap стоит, ptunnelNG из коробки его не видит даже в PATH. Можно было бы пойти дальше и перебрать ещё пару-тройку готовых решений, но они все имеют фатальный недостаток. Вы можете справедливо заметить, что упорство всё перетрёт, и я могу где-нибудь найти конфиги, или в крайнем случае изменить и перекомпилировать сам pingtunnelNG — но мне ещё в статье про RuGPT3.5 очертенело чинить чужой код, так что кажется, что пришло время делать свой велосипед.

Своей задачей я ставлю реализовать всё максимально коротко и понятно, пусть и недостаточно качественно для широкополосного подключения. Компромиссом в предыдущих реализациях выступал Go, я же предпочту Python для получения максимально минималистичного proof of concept. (Спойлер: в итоге пропускная способность получилась отличная, и скорость итераций Python даже близко не стала узким местом).

Первым шагом же нужно будет сделать вспомогательный модуль для отправки кастомных ping-пакетов. Для этого предлагаю быстро вспомнить конкретные слои пакета ICMP и связать их с нашими уровнями доступа и возможностями перехвата.

Типичная блок-схема сетевого фрейма c пакетом icmp, которую нам выдаст любой поисковик по запросу «структура icmp пакета»:

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 8

Предлагаю не мусолить картинку, как это обычно делается, а сразу смотреть всё на примере живого пакета. Не просто так же мы сниффер поставили, верно?

Реальный пакет из WireShark

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 9

▍ L2 | Ethernet | Канальный уровень (14 байт)

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 10

В этом заголовке находятся MAC-адреса получателя и отправителя. PCAP обеспечивает нам возможность перехвата пакетов, начиная с этого уровня, однако тут нужно упомянуть канальную фильтрацию.

Канальная фильтрация — нормальное состояние Ethernet сетевой карты, ведь если MAC-адрес назначения в заголовке принятого пакета не совпадает с MAC-адресом текущего сетевого интерфейса и не является широковещательным, то разумным поведением кажется отсеивание такого пакета.

Другими словами, сетевая карта не даст нашему устройству «получить» пакет с битым или некорректным L2-заголовком, так что ничего интересного мы не увидим (даже в wireshark).

Если же вы хотите видеть реально всё, что поднимается с аппаратурного уровня (L1) до канального, то вы можете попросить ОС о переключении драйвера сетевой карты в неразборчивый режим (работает даже под Windows), однако эта информация вам может понадобиться только если вы будете заниматься хардкорными вещами в пределах одной L2-подсети (где между устройствами есть прямая физическая связь по Wi-Fi или Ethernet) вроде MAC spoofing, mitm и в общем случае пентестом.

Т. е. если вы не киберфрик из 2000-х и у вас в подвале не лежит приблуда как на картинке ниже, то вам это знать не обязательно. В противном случае вы уже и без меня знаете, что под Linux можно создавать сокеты сразу L2-уровня и буквально биндиться не к IP-адресу, а к MAC для выполнения самых грязных и мутных фокусов, упомянутых выше.

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 11

▍ L3 | IP | Пакетный уровень (20 байт)

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 12

На этом слое уже будут лежать IP адреса получателя и отправителя, т. е. будет обеспечена IP-маршрутизация.

А также хеш суммы для верификации, а именно этот уровень обеспечивает корректность заголовка транспортных протоколов, таких как TCP, UDP, ICMP и т. д. Это значит, что уже на L3 известен конкретный транспортныйуправляющий протокол следующего уровня, в нашем примере Protocol: ICMP (1)

Мы же ещё можем обратить внимание на тот самый TTL из начала статьи, который в моём случае изначально равен 128 (в то время как дефолтный TTL на линуксах и ведроидах равен 64).

▍ L4 | ICMP | Протокол управляющих сообщений (8 байт)

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 13

Собственно, наш сегодняшний пациент. Пока стоит обратить внимание на поле type. В нашем конкретном пакете type=8 (echo req). В ответ на него ожидается type=0 (echo reply) в качестве успеха преодоления маршрута, и какой-либо другой служебный ICMP в случае неудачи, например type=3 (unreachable) или, внимание, type=11 (TTL Exceeded).

TTL Exceeded отправляется в ответ железкой, которая получила пакет с опустившимся до нуля TTL, который либо просто не долетел до получателя, либо заблудился и зациклился.

Кстати, именно на TTL основан tracert — утилита windows для «трассировки маршрута». Она просто отправляет на указанный адрес ICMP-пакеты, каждый раз с TTL += 1. Таким образом на всех нодах по пути к получателю умрёт (TTL упадёт до нуля) хотя бы один пакет, и каждая должна будет раскрыть своё существование ответным ICMP 11 пакетом. Кстати, traceroute на линуксе работает не совсем так же (См Tracert vs Traceroute).

▍ Data | PayLoad

Последний сегмент любого транспортного пакета — пустое место, куда можно положить служебные сообщения, сегменты файлов, запросы местоположения врагов в шутере, и даже записать туда другие пакеты, возможно, даже пакеты совсем другого типа и протокола. В общем свободное место есть — дальше же всё зависит от вашей фантазии.

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 14

Начинаем реализовывать

Сейчас мы разберём минимальный и полностью читаемый генератор ICMP-пакета. Для этого используется так называемый RAW socket.

RAW сокет позволяет инициализировать себя с кастомным protocol code (то самое поле L3 уровня в нашем пакете, где было написано 1 — ICMP). L4-уровень же становится полностью подконтролен пользователю.

Для создания такого сокета в Windows нужны права Администратора. Важно упомянуть, что это всё ещё не настоящий SOL_SOCKET, как в Linux, и вы не можете кастомить IP-заголовок так, как вам захочется (т. е. IP-адрес вы так не измените). Единственное же, что подлежит изменению — поле protocol code.

▍ Send ICMP

Для работы с ICMP нам нужно вписать в поле protocol code число 1. Но, полагаю, будет более честно абстрагироваться от этого и достать эту единичку встроенной в Python socket-функцией getprotobyname.

import socket
import struct

ICMP_CODE = socket.getprotobyname('icmp')

Теперь нам нужно посчитать хеш-сумму пакета. Алгоритм я угнал с какого-то открытого ресурса и уже не вспомню его принципа работы (это мне ещё аукнется). Вы можете и сами оценить эту функцию взглядом:


def checksum(source_string):
    sum = 0
    count_to = (len(source_string) / 2) * 2
    count = 0
    while count < count_to:
        this_val = source_string[count + 1]*256+source_string[count]
        sum = sum + this_val
        sum = sum & 0xffffffff
        count = count + 2
    if count_to < len(source_string):
        sum = sum + source_string[len(source_string) - 1]
        sum = sum & 0xffffffff
    sum = (sum >> 16) + (sum & 0xffff)
    sum = sum + (sum >> 16)
    answer = ~sum
    answer = answer & 0xffff
    answer = answer >> 8 | (answer << 8 & 0xff00)
    return answer

Теперь по классике конструируем заголовок из наших чиселок при помощи struct.

Тут есть пара нюансов:

  1. Пакет нужно собрать дважды. Сначала с полем хеш-суммы, равным 0. Посчитать для него хеш-сумму. И только после этого итоговую версию пакета с посчитанным хешем, записанным в это самое поле. (Ну, не можем же мы посчитать хеш от данных, где используется этот же самый хеш, верно?)
  2. Если длина пакета не кратна 2 (нечётна), то я дописываю в конец кастомный паддинг 123. Это первое, что приходит в голову, когда мы эмпирически выясняем, что checksum() не работает со строками, длина которых нечётна.


def create_packet(id,data):
    
    ICMP_ECHO_REQUEST=0 #Код типа ICMP - в нашем случае ECHO
    header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, 0, id, 1)
    data = data+b"x00x11x22"*(len(data)%2)

    my_checksum = checksum(header + data)
    header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), id, 1)
    return header + data

На самом деле вы можете сейчас перемотать текст чуть выше и увидеть в коде функции checksum-строку (len/2)*2, т. е. на самом деле я угнал плохой перевод с python2 на python3 — такая вот суровая участь Legacy enjoyer`а… (В python2 один слеш выполняет над int целочисленное деление, в то время как в python3 сразу переводит во float и делит адекватно).

Теперь реализуем функцию send, которая создаст RAW socket и отправит через него сырые данные.


def send(dest_addr,pkt):
    my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE)
    host = socket.gethostbyname(dest_addr)

    while pkt:
        sent = my_socket.sendto(pkt, (dest_addr, 1))
        pkt = pkt[sent:]

    return my_socket

Использовать наши новые функции можно так: send("8.8.8.8",create_packet(ID,b"Hello!"))

Не забываем, что всё это работает только при наличии прав Администратора!

▍ Recv ICMP

Раз уж самым простым решением для приёма icmp-пакетов под Windows является их перехват на уровне pcap, а мы хотим автоматизировать пайплайн на каждой из сторон, моментально отправляющий и получающий ICMP-пакеты, то нам нужен API на Питоне для pcap.

Само собой, его уже придумали до нас. Вторая же строка поиска (потому что на первой реклама скам-курсов, привет, поиск от Яндекса) выдаёт нам миловидную библиотечку PyPcap.

pip install pypcap

Preparing metadata (setup.py) ... error

error: subprocess-exited-with-error

× python setup.py egg_info did not run successfully.
│ exit code: 1
╰─> [1 lines of output]
pcap.h not found
[end of output]

Ну, на первый взгляд, ситуация не критическая. Да, библиотечка не несёт в зависимостях исходники pcap, отказываясь устанавливаться без них. Вот что говорит readme на GitHub либы:

Note for Windows users: Please download the Npcap SDK, unpack the archive and put it into the sibling directory as wpdpack (setup.py will discover it).

Спасибо и на этом. Качаем dev-версию npcap в рабочую папку установки и пробуем ещё раз:

building 'pcap' extension error

error: Microsoft Visual C++ 14.0 or greater is required. Get it with «Microsoft C++ Build Tools»: visualstudio.microsoft.com/visual-cpp-build-tools

Знаете, когда pip начинает жаловаться на C++ Build Tools и отправляет вас скачивать жирнющий Visual Studio, то, скорее всего, это не поможет. Вы 30 минут будете медитировать перед полоской загрузки, скачивающей вам на компьютер 5 гигабайт инструментов сборки, и как итог pip начнёт падать не с ошибкой Build Tools, а банально из-за ошибок компиляции C-кода. Чистоты эксперимента ради я даже попробовал повторить всё в средах Python 3.8 и 3.10 вместо моей дефолтной 3.12 и абсолютно ничего не получил с этого. Можете даже не пытаться.

Репозиторий GitHub этой библиотеки не менялся уже 5 лет, так что на поддержку рассчитывать не приходится. Благо её уже форкнули и пофиксили. Теперь нашим мессией станет pcap-ct.
pip install pcap-ct

И, о чудо, всё устанавливается с первого раза.

Теперь мы можем создать объект sniffer, обеспечивающий нам заветный recieve любых пакетов, которые мы получаем сетевой картой.

import pcap
sniffer = pcap.pcap(name=None, promisc=True, immediate=True, timeout_ms=50)

Однако это может не сработать на вашем устройстве, как не заработало на моём сервере, где одновременно сосуществуют около десятка сетевых интерфейсов. Для того, чтобы указать конкретный, нужно редактировать аргумент name на идентификатор девайса вашего конкретного адаптера.

Рабочий для меня вариант выглядит так:

sniffer = pcap.pcap(name=r"DeviceNPF_{EC1D1645-C46F-45B4-96F2-19179FFF3CFB}", promisc=True, immediate=True, timeout_ms=50)

Но как же из него читать пакеты? Есть два способа:

  1. Итерация по объекту sniffer (он является генератором) — то есть вызов sniffer.__next__(). За нас это может сделать for packet_num, packet in sniffer: ..., он будет работать бесконечно, автоматически ожидая нового пакета между итерациями, если буфер закончился.
  2. Считывание всего буфера за раз при помощи pkts = sniffer.readpkts() — не останавливает выполнение кода, возвращает всё, что есть на текущий момент, в форме списка. Позже можно также итерироваться по принципу for packet_num, packet in pkts: ..., этот for уже не останавливает выполнение, ведь он просто проходится по считанному буферу и заканчивает свою работу.

Остаётся отфильтровать трафик от всех протоколов, кроме ICMP, и избавиться от broadcast-пакетов, ожидая в Destination IP (поле L3-заголовка) адрес, ассоциируемый с нужным нам интерфейсом.

Это я сделаю ближе к концу, когда буду писать основной livecycle ноды.

Пока на время отвлечёмся в пользу теории и чуть лучше спланируем наши дальнейшие действия.

NAT?

Если вы смоги переварить всё вышеизложенное, то, вероятнее всего, вы не являетесь юзером, который считает, что интернет — это магия, снизошедшая на нас от умных дедов, которые смоги его сделать 80 лет назад. Хотя, даже если вы никогда не ковыряли NAT-трансляцию, а за вас это всегда делал сисадмин (у каждого свой стек как-никак), могу предложить вам свою статью для базового понимания NAT. Дальше я в любом случае буду ссылаться на hole-punch и, пожалуй, даже начну прямо сейчас:

ICMP тоже пробивает дыры?

Спойлер: Иначе и быть не может.

Представим себе абстрактную рабочую сеть с gateway`ем, выполняющим NAT-трансляцию. Трансляция работает с L3, то есть просто переписывает адреса в IP-заголовке (ведь все уровни ниже IP не имеют логического смысла на стыке сетей).

Индикатором необходимости трансляции в приходящем пакете является port.

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

Если вы относитесь к этим недалёким экспертам и думаете, что «ICMP не использует порты» или «ICMP работает на зарезервированном 1-м порте», то пришло время приоткрыть завесу этой тайны.

И правда, если мы загуглим «Что такое сетевой порт?», то мы получим примерно такой ответ:

Сетевой порт — идентифицируемый номером системный ресурс, выделяемый приложению, выполняемому на некотором сетевом хосте для связи с приложениями, выполняемыми на других сетевых хостах (в том числе c другими приложениями на этом же хосте).

Весьма мутно, не находите? Давайте сами найдём это поле port в пакете. Ловим TCP-пакет вайршарком и…

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 15

Source Port и Dest Port находятся внутри TCP (транспортного заголовка). На IP-уровне ещё никаких портов не предусмотрено.

Пингуем 8.8.8.8 и ловим icmp-пакет, чтобы и его препарировать

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 16

Как и ожидалось, никакого port нет. По той простой причине, что UDP- и TCP-порты являются сущностями транспортного уровня и появляются вместе с самими протоколами семейств UDP/TCP. Из этого, кстати, следует, что два потока информации, отправленные в «один и тот же порт» протоколов разных транспортных семейств и не перемешиваются.

Ладно, возвращаемся к ICMP. Если из нашей условной подсети разные люди «пингуют» один и тот же внешний адрес, то обратная трансляция для NAT становится неоднозначной. Для решения этой неопределённости в ICMP-пакете есть поле ID. Казалось бы — альтернатива портам, но если мы посмотрим в нашем пакете на поле идентификатора, то мы увидим там очень оригинальную и идентичную для всех пакетов единицу. Судя по всему — это фейл Windows (ну или просто игнорирование фичи, заложенной в концепцию изначально).

Так как же тогда однозначно определять обратный маршрут для пакета?

Поиск информации на эту тему приводит нас к подобной идее: For ICMP query/reply type messages like Echoes (pings), NAPT uses the ICMP Query ID (sometimes just called the ICMP ID) the same way it would use a TCP or UDP port number.

То есть привычную нам роль порта для icmp, как и предполагалось, играет поле ID.
Попробуем найти однозначный ответ на максимально живом примере — моём роутере. Mikrotik открыто позволяет мониторить даже самые мелочи, так что icmp nat сессия не должна будет уйти незамеченной. Заходим на веб-майку и следуем пути IP>Firewall>Connections.

Пингуем 8.8.8.8 изнутри и находим активную сессию трансляции…

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 17

И к сожалению, не находим ничего ценного. Сколько бы устройств изнутри ни отправляли пакеты на сколь угодно много устройств снаружи, маршрутизация работает правильно, даже если id у всех пинг-пакетов один и тот же. И как бы ни прискорбно это было осознавать, мне так и не удалось найти спецификацию алгоритма трансляции, который бы вёл себя подобным образом. Я думаю, что роутер просто запоминает пары GrayAddress + 8.8.8.8 во временную память и выполняет обратную трансляцию согласно прямому соответствию адреса отправителя адресу из записи в таблице. Однако для корректной работы такой схемы после обратной трансляции запись нужно удалять и давать шанс сработать следующей записи (например, соответствующей другому устройству подсети, которое пингует 8.8.8.8).

Но с реальностью это совершенно не стыкуется. На один ping req вам может прийти ответ из огромного количества repl, так что схема со стеком записей не похожа на правду, даже в нашей небольшой домашней сетке.

С CG-NAT, подобными тем, что используются операторами сотовой связи, ситуация обстоит логичнее, но в то же время усложняет изначальный концепт. Подробнее о них мы поговорим позже, уже ближе к готовой реализации.

Инкапсуляция

Инкапсуляция — это включение всего пакета одного протокола (то есть его заголовки и данные) внутрь пакета другого протокола в качестве передаваемой информации.

В нашем случае это необходимость обеспечения слоя совместимости сырых данных из icmp-пакета (например, дампа L3-пакета) с реальным стеком L3-отправителя (для захвата и инкапсуляции) и приёмника (для развёртки и маршрутизации).

Для того, чтобы превращать свои данные в сетевые сущности, были придуманы виртуальные сетевые драйверы. Это логические интерфейсы, создаваемые на уровне операционной системы, трафик которых можно полностью контролировать из приложений при помощи стандартных файловых дескрипторов. Традиционно они делятся на два вида:

  1. L3-интерфейсы (network TUNnel) или TUN. Решают именно ту задачу, что я описал выше — дают приложению интерфейс контроля уровня IP. Именно они создаются всякими готовыми VPN-сервисами вроде Radmin, Hamachi и Windscrible.
  2. L2-интерфейсы (network TAP — в переводе «отвод») или просто TAP. Кстати, если вы не перешли по ссылке той пикчи со странным девайсом из начала статьи, то стоит упомянуть, что именно он изначально назывался TAP, и по его внешнему виду в принципе уже можно понять, что делает «сетевой отвод», будь он физический (как на картинке) или логический (как ваши мосты в WindowsLinux). С практической точки зрения используется для «соединения» уже существующих L2-интерфейсов и обычно не используется для VPN.

Слава богу, что нам не придётся самим изучать api виртуальных адаптеров и страдать от проблем инкапсуляции streaming-протоколов в message based.

Как вы могли догадаться, готовое решение существует. И является им широко известный OpenVPN. Он, кстати, позволяет не только соединять два tun/tap посредством udp, но и прописывать один из этих tun как gateway для клиента и использовать основной интерфейс сервера как shared adapter для перенаправления ВСЕГО трафика клиента через сервер — это же сыграет с нами злую шутку позже, но поверьте, результат стоит всех трудностей на пути).

Настраиваем OpenVPN под Windows

Да, в этом блоке я в общих чертах распишу процесс поднятия сервера с shared-адаптером под виндой, ведь там имеются некоторые подводные камни.

Для начала нужно будет скачать сам OpenVPN Server на домашний компьютер (тот, у которого ICMP проброшен во внешний мир).

Думаю, процесс установки не вызовет больших трудностей, да и гайдов в интернете полно.
Единственное, что стоит упомянуть, это необходимость установки wintun драйвера. В новых версиях OpenVPN он идёт в комплекте, в более старых же установщиках (кои могут предлагаться в гайдах) он может не качаться автоматически, в таком случае об этом нужно будет позаботиться вручную. В остальном же процесс установки выглядит примерно так:

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 18

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

  • На сервере:
    1. ca.crt
    2. server.crt
    3. server.key
    4. dh.pem

  • Если вы продвинутый пользователь, или гайд вас к этому принудил, то тут могут быть и другие доп. методы защиты вроде TLS — я же ограничусь классическим минимумом.
  • Для клиента:
    1. ca.crt
    2. client1.crt
    3. client1.key
    4. dh.pem

  • client1 — это имя собственное, выданное клиенту при генерации. Оно может быть другим у вас.

Теперь предлагаю посмотреть на наших новых друзей — TAP- и TUN-адаптеры. Увидеть все свои адаптеры можно в "Панели управления" -> Сеть и Интернет -> Центр управления сетями -> Изменение параметров адаптера.

Забавно, что в новых версиях Windows панель управления всё больше и больше вытесняется новым системным приложением «Настройки», в которых мелкомягкие понемногу заново изобретают панель управления. Я серьёзно.

Они хотели сделать панель управления в новом красивом интерфейсе и вместо того, что бы просто закастомить движок рендера UI, они стали заново изобретать велосипед, в котором нет половины функций. Возможно, они элементарно не смогли интегрировать свой новый мусор вроде персонализации и настроек сервисов с аккаунтами в уже готовую и безотказно работающую десятки лет архитектуру, а может быть просто имитируют развитие продукта. Я даже недавно столкнулся со сборкой WIndows, где вообще вырезана панель управления — вот это было реально приключение.

Думаю, останусь пока верен модульной структуре органов управления компьютера и подобно панели брандмауэра, что мы уже видели в начале статьи, вызову этот компонент при помощи:
Win+R -> ncpa.cpl.

Видим, что всё, что нам может понадобиться, уже готово к употреблению:

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 19

Теперь можно переходить к конфигурации.

OVPN Конфиг сервера

port 27005
proto udp
dev tun
#topology subnet
ca "C:\Program Files\OpenVPN\easy-rsa\pki\ca.crt"
cert "C:\Program Files\OpenVPN\easy-rsa\pki\issued\server.crt"
key "C:\Program Files\OpenVPN\easy-rsa\pki\private\server.key" # This file should be kept secret
dh "C:\Program Files\OpenVPN\easy-rsa\pki\dh.pem"
#tls-auth "C:\Program Files\OpenVPN\bin\ta.key" 0
server 10.8.0.0 255.255.255.0
windows-driver wintun

push "redirect-gateway local def1 bypass-dhcp"
route-gateway 10.8.0.1
push "dhcp-option DNS 8.8.8.8"

keepalive 10 600
persist-key
persist-tun
cipher AES-256-CBC
comp-lzo
status "C:\Program Files\OpenVPN\log\status.log"
log "C:\Program Files\OpenVPN\log\openvpn.log"
verb 4
#mute 20

OVPN Конфиг клиента

client
dev tun
proto udp
remote 127.0.0.1 27005
resolv-retry infinite
nobind
#persist-key
#persist-tun

ca ca.crt
cert client1.crt
key client1.key
dh dh.pem
#tls-auth ta.key 1

cipher AES-256-CBC
verb 0
comp-lzo
connect-retry-max 25

Если вам лень осознавать написанное в этих конфигах, то вот основные поинты, которые можно из них выцепить:

  • Сервер поднимается на 27005 порту.
  • Сервер заставляет клиента исполнить команду redirect-gateway local def1 bypass-dhcp, которая выбирает в качестве default-gateway на клиентской машине TUN-адаптер. К результату: клиент будет пытаться отправить весь свой интернет-трафик через сервер.

Запускаем сервер и видим, что к TUN-адаптеру «подключился провод».

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 20

Чтобы клиент мог использовать сервер как прокси для всего трафика, у него должен быть прописан default gateway как 10.8.0.1 (наш сервер в сети vpn) — это для нас сделала инструкция redirect-gateway, однако со стороны сервера тоже нужна поддержка. Для этого нам нужно на сервере «поделиться» интернетом основного адаптера с TUN-адаптером.

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 21

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 22

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 23

Соглашаемся на то, что Windows перепишет адрес OpenVPN TUN на взятый из воздуха дефолтный. OpenVPN сам поправит его на нормальный. Зачем Windows это делает? Для меня всегда было загадкой. Называется эта вставлялка палок в колёса APIPA (Automatic Private IP Addressing) и нужна исключительно, чтоб портить жизнь честным сисадминам. Ходят слухи, что где-то там в реестре это отключаемо, но мы тут собрались не для того, что бы индусский код чинить.

Теперь обратим внимание на то, что в конфиге клиента осуществляется попытка подключения к 127.0.0.1:27005, что вообще-то является localhost`ом. Всё верно — на этом порту мы поднимем наш супер мега крутой туннель, написанный на Python.

Аналогичная схема будет работать и на стороне сервера, что сделает общую модель примерно такой:

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 24

Теперь пришло время добить остатки проблем, связанные с ICMP.

Так как же оператор транслирует icmp?

Для исходящего пакета CG-NAT генерирует временный ID и сам вписывает его в поле id вашего icmp-пакета, запоминая при этом ваш локальный адрес и 8.8.8.8 как однозначно соответствующие ему.

Этот временный идентификатор будет сохранён нашим уловным 8.8.8.8 в ответном пакете, следовательно CG-NAT сможет однозначно транслировать ответ обратно вам. Для удобства, если вы отправляете сразу поток пакетов с одного внутреннего адреса на один и тот же внешний, то транслятор не выдаёт новый id для каждого, а подобно обычной транспортной nat-сессии использует уже существующую запись о соответствии GaryIP -> 8.8.8.8 [ID:123].

Это создаёт необходимость для нашего serverа отвечать клиенту icmp reply пакетами только с тем id же, что записан в icmp req пакетах ОТ клиента. В таком случае мы будем поддерживать уже существующий тоннель в CG-NAT и сможем отправлять много reply и req в любых пропорциях.

Заключительные штрихи

Как ни странно, большую часть работы мы уже выполнили, пока разбирались с генерацией и захватом ICMP-пакетов. Остаётся написать пару циклов, которые будут заниматься трансфером данных в обе стороны и получить готовое решение для такой задачи:

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 25

На самом деле нам осталось только написать основные циклы сервера и клиента, которые будут бесконечно применять созданные нами инструменты.

▍ Клиент

Создаём функцию, которая будет играть роль ресивера для всех пакетов OpenVPN Client и отправлять при помощи наших обёрток для icmp.

global addr
addr=("127.0.0.1",123)
def vpn2tun(sock):
	global addr
	while 1:
		d,a=sock.recvfrom(9999)
		addr=a
		print(len(d),a)
		try:
			send(icmpclient,create_packet(123,d))
		except Exception as e: print(e)
		#time.sleep(0.1)

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

import threading
t=threading.Thread(target=vpn2tun,args=(sock,))
t.start()

for _,p in sniffer:
	if p[23]==1 and p[34]==0 and socket.inet_ntoa(p[30:34])==me_in_local:
		print(len(p[42:].removesuffix(b"x00x11x22")))
		sock.sendto(p[42:].removesuffix(b"x00x11x22"), addr)

▍ Сервер

Аналогичный клиентскому цикл трансляции, но только в обратном направлении. Пока в качестве id будем использовать некую глобальную переменную (её будет определять цикл захвата icmp).

def vpn2tun(sock):
	while 1:
		global id
		send(icmpclient,create_packet(id,sock.recv(9999)))

В цикл захвата icmp добавляется ещё один небольшой поинт в сравнении с клиентом: адаптация под ID пакета, выданный CG-NAT. Для этого мы просто будем запоминать идентификатор приходящих пакетов, записывая его глобальную переменную id.

В остальном ничего необычного: итерируемся, фильтруем, отправляем в OpenVPN сервер на 27005 порту.

id=123
import threading
t=threading.Thread(target=vpn2tun,args=(sock,))
t.start()

for _,p in sniffer:
    if p[23]==1 and socket.inet_ntoa(p[30:34])==me_in_local and p[34]==8:
        id=int.from_bytes(p[38:40],"little",signed=False)
        print(len(p[42:].removesuffix(b"x00x11x22")))
        sock.sendto(p[42:].removesuffix(b"x00x11x22"),("127.0.0.1", 27005))

▍ Тест

В логах сервера появилось успешное подключение клиента:

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 26

Проверим, что диалог идёт в форме icmp:

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 27

Таблицы маршрутизации

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

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 28

Процессор при этом загружен на 100%.

Не нужно быть гением или пророком, чтобы понять, что трафик зацикливается. OpenVPN пытается отправлять пакеты по UDP на домашний сервер, пакеты инкапсулируются в ICMP, которые тоже стремятся попасть на свой Destination IP, но повторно уходят в интерфейс 10.8.0.1 (OpenVPN TUN). Процесс повторяется бесконечное количество раз.

И нет ничего странного в том, что упёрся в потолок именно график BYTES OUT, ведь чисто технически OpenVPN выполняет преобразование и отсылает всё в 127.0.0.1:55555 (наш туннель). Другими словами, его ни в чём винить нельзя.

Для избежания подобного недоразумения нужно понять, почему пакет вообще летит в 10.8.0.1?
Как-то мы обошли эту тему стороной, но как вообще понять, какой адаптер должен быть ответственен за отправку того или иного IP пакета? На разных интерфейсах могут быть разные устройства с одинаковым IP, соответствующем нашему Destination, не отправлять же на оба…
Да и вообще, маршрутизировать пакеты между L2 адаптерами, основываясь на IP заголовке как минимум странно… — Ну, вообще-то нет.

Это практически так и работает и называется данное действие Маршрутизацией (Routing) и является стандартом для любых ОС, будь то Windows, Linux, RouterOS или какой-нибудь OpenWRT.

То есть пакет проходит через ряд фильтров — таблицу маршрутизации (Routetable) и определяется с интерфейсом, на который ему нужно отправиться по правилам вида «Если твой Dst IP вида 192.168.X.X, то твою судьбу определит интерфейс wlan (WIFI)» или последнее правило, срабатывающее если предыдущие не сработали — «Если твой Dst IP любой, то тебе на Ethernet интерфейс».

На Windows вы можете прописать route print и увидеть подобную моей картину:

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 29

К слову, дальнейшую маршрутизацию MAC устройства тоже выполняют основываясь на IP, и это нормально. Например Ethernet в свою очередь выбирает физический адрес получателя, основываясь на ARP таблице, со строками вида «192.168.0.1 -> 01:02:...:05:06)»

Вот, кстати, вывод кусочка ARP таблицы в Windows: arp -a

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 30

Но это нас уже не сильно интересует. Нашу задачу в общем случае можно обозвать выборочной отправкой трафика через VPN (кстати, в этом треде чел тоже «воюет» с оператором СоС) и решить при помощи изменения тех самых таблиц маршрутизации.

Наша задача сейчас адаптировать свои routes так, чтобы наши транспортные ICMP отправлялись не на адаптер OpenVPN, а на ваш физический адаптер. На самом деле, в routes не обязательно указывать MAC как пункт назначения, можно использовать привязанный к нему адрес gateway`я. Т. е. туда можно вписать серый ip вашей точки доступа.

route add <Белый адрес сервера> <Серый адрес gateway в вашей локалке>

Победа! Теперь наш ICMP не зацикливается, а сразу вылетает из петли инкапсуляции в сторону точки доступа.

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 31

How To Use?

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

  • Дом 36.190.160.236 с Mikrotik 192.168.88.1, пробросившим icmp на домашний сервер 192.168.88.15
  • Точка доступа с адресом белым адресом 83.235.46.166 и серым 10.100.1.2
  • Ноутбук, подключённый к ней с адресом 10.100.23.247

Вот пара one to go скриптов с единственной зависимостью — pcap. Один крутится дома, второй на ноутбуке.

Обратите внимание на переменные, соответствующие IP-адресам — их вам нужно будет задать вручную, если вы решите попробовать всё сами.

SERV

import pcap
sniffer = pcap.pcap(name='\Device\NPF_{A99EFA4B-B984-4E10-A34C-B15B161A1697}', promisc=True, timeout_ms=50)


import socket
import struct
import random

ICMP_CODE = socket.getprotobyname('icmp')

icmpclient="83.235.46.166"
me_in_local="192.168.88.15"
sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
#sock.setsockopt(socket.SOL_SOCKET, 25, b"eth0")
sock.bind(("127.0.0.1", 55555))
import socket
import struct

ICMP_CODE = 1



def checksum(source_string):
    sum = 0
    count_to = (len(source_string) / 2) * 2
    count = 0
    while count < count_to:
        this_val = source_string[count + 1]*256+source_string[count]
        sum = sum + this_val
        sum = sum & 0xffffffff
        count = count + 2
    if count_to < len(source_string):
        sum = sum + source_string[len(source_string) - 1]
        sum = sum & 0xffffffff
    sum = (sum >> 16) + (sum & 0xffff)
    sum = sum + (sum >> 16)
    answer = ~sum
    answer = answer & 0xffff
    answer = answer >> 8 | (answer << 8 & 0xff00)
    return answer



def create_packet(id,data):
    
    ICMP_ECHO_REQUEST=0 #Код типа ICMP - в нашем случае ECHO
    header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, 0, id, 1)
    data = data+b"x00x11x22"*(len(data)%2)

    my_checksum = checksum(header + data)
    header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), id, 1)
    return header + data



def send(dest_addr,pkt):
    my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE)
    host = socket.gethostbyname(dest_addr)

    while pkt:
        sent = my_socket.sendto(pkt, (dest_addr, 1))
        pkt = pkt[sent:]

    return my_socket

def vpn2tun(sock):
	while 1:
		global id
		send(icmpclient,create_packet(id,sock.recv(9999)))

id=123
import threading
t=threading.Thread(target=vpn2tun,args=(sock,))
t.start()

for _,p in sniffer:
    #print(p)
    if p[23]==1 and socket.inet_ntoa(p[30:34])==me_in_local and p[34]==8:
        id=int.from_bytes(p[38:40],"little",signed=False)
        print(len(p[42:].removesuffix(b"x00x11x22")))
        sock.sendto(p[42:].removesuffix(b"x00x11x22"),("127.0.0.1", 27005))

CLI

import pcap
sniffer = pcap.pcap(name=None, promisc=True, immediate=True, timeout_ms=50)


import socket
import struct
import random
import time

ICMP_CODE = socket.getprotobyname('icmp')

icmpclient="36.190.160.236"
me_in_local="10.100.23.247"

sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
sock.bind(("", 55555))
def checksum(source_string):
    sum = 0
    count_to = (len(source_string) / 2) * 2
    count = 0
    while count < count_to:
        this_val = source_string[count + 1]*256+source_string[count]
        sum = sum + this_val
        sum = sum & 0xffffffff
        count = count + 2
    if count_to < len(source_string):
        sum = sum + source_string[len(source_string) - 1]
        sum = sum & 0xffffffff
    sum = (sum >> 16) + (sum & 0xffff)
    sum = sum + (sum >> 16)
    answer = ~sum
    answer = answer & 0xffff
    answer = answer >> 8 | (answer << 8 & 0xff00)
    return answer

def create_packet(id,data):
    ICMP_ECHO_REQUEST=8 #Код типа ICMP - в нашем случае ECHO
    header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, 0, id, 1)
    data = data+b"x00x11x22"*(len(data)%2)

    my_checksum = checksum(header + data)
    header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), id, 1)
    return header + data

def send(dest_addr,data):
    my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE)
    host = socket.gethostbyname(dest_addr)

    #packet_id = random.randint(0,65535)
    #packet = create_packet(packet_id,data)
    
    while data:
        sent = my_socket.sendto(data, (dest_addr, 1))
        data = data[sent:]

    return my_socket

global addr
addr=("127.0.0.1",123)
def vpn2tun(sock):
	global addr
	while 1:
		d,a=sock.recvfrom(9999)
		addr=a
		print(len(d),a)
		try:
			send(icmpclient,create_packet(123,d))
		except Exception as e: print(e)
		#time.sleep(0.1)

import threading
t=threading.Thread(target=vpn2tun,args=(sock,))
t.start()

for _,p in sniffer:
	if p[23]==1 and p[34]==0 and socket.inet_ntoa(p[30:34])==me_in_local:
		print(len(p[42:].removesuffix(b"x00x11x22")))
		sock.sendto(p[42:].removesuffix(b"x00x11x22"), addr)

Отключаем скам в приложении оператора

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

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 32

Теперь наша задача выключить назойливый «Режим модема», который ОпСоС норовит вам включить, когда вы палите раздачу. У красного оператора эти тумблеры спрятаны в меню Статистика использования > Добавить трафик > Лучший выбор — забавно, правда?

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 33

Теперь у нас не списываются деньги, но и «раздача недоступна»

Остаётся прописать на клиенте статический маршрут командой route add 37.192.163.240 10.100.1.2 и можно бесплатно пользоваться раздачей интернета!

Далее предлагаю оценить диагноз, который нам поставит speedtest — раздутая ведь штука, пусть и бесполезная…

Bandwidth без каких-либо манипуляций

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 34

VPN over icmp

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 35

Вместо заключения

Полагаю, этот материал позволил мне вам доказать, что icmp-туннели живы даже через десятки лет после их пика популярности, когда ещё не были распространены NAT, а ipv4-диапазон адресов казался вполне себе вместительным.

Была распространена L2-маршрутизация (коммутаторы aka свитчи) можно было гонять icmp с фейковым src ip, не соответствующим твоему. Поскольку по дороге никто не проверяет соответствие src ip и с MAC из arp таблицы, а без NAT в этом нет необходимости, то такой хитрый пакет будет исправно доставлен до адресата и адресат отправит его не обратно тебе, а на выбранную вами цель в поле src ip. Это позволяет сделать массу разных полезных и даже неэтичных вещей.

Пакуем весь трафик в Ping message, чтобы не платить за интернет | ICMP NAT traversal - 36

Однако это ушло в прошлое, и теперь нас ждёт только прекрасный интернет будущего, который, учитывая современные тенденции, может быть суверенен внутри союзных групп стран и жёстко цензурится white-листами доверенных доменов совершенно не таким, каким бы его хотели видеть создатели и конечные пользователи.

На этом я, пожалуй, закончу своё повествование, но, как всегда, ожидаю вас под постом — здравая критика в комментариях исключительно приветствуется.

Узнавайте о новых акциях и промокодах первыми из нашего Telegram-канала 💰

Автор: Andрeй ✅

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js