UDP hole punching для Symmetric NAT: немного теории и почти честный эксперимент

от автора

Доброго времени суток, коллеги.

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

Для тех, кто не в теме коротко о том, что такое Symmetric NAT.

Рассмотрим простую схему, в которой

хост1, хост2 — пользовательские хосты за NAT-ами
NAT1, NAT2 — пограничные устройства, обеспечивающие NAT

Когда хост1 инициирует исходящее соединение (TCP или UDP) на некий реальный_IP, устройство NAT1 заменяет в исходящем пакете IP источника на свой (необязательно на свой, но примем это для простоты и наглядности), а так же в общем случае заменяет PORT_SRC на случайный PORT_SRC1.

хост1 ____ PORT_SRC, PORT_DST —> устройство NAT1 PORT_SRC1, PORT_DST —-> реальный_IP

Так вот, NAT будет симметричным, если от хоста реальный_IP ответный пакет будет пропущен устройством NAT1 тогда и только тогда, когда собственно IP источника в таком ответном пакете будет реальный_IP, порт источника будет PORT_DST, а порт назначения — PORT_SRC1. Т. е. реальный_IP ответит от своего адреса, с того же порта, на который был коннект, и на тот же порт, с которого был исходящий коннект с устройства NAT1. Не трудно заметить, что термин «симметрия» тут затесался не зря.

Что же мы имеем, когда стоит задача «пробить» симметричный NAT и дать возможность хостам за ним общаться напрямую?

В общем случае имеем следующую простую схемку.

хост1 —-> NAT1 SRC1_случайный, DST1_по_желанию —->

< — DST2_по_желанию, SRC2_случайный NAT2 < — хост2

SRC1_случайный — порт источника после NAT-подмены устройством NAT1;
DST1_по_желанию — порт назначения для исходящего соединения, устанавливаемого с хост1 через NAT1, может выбираться нами;
DST2_по_желанию — порт назначения для пакета, отправляемого с хоста2 через NAT2 на NAT1, может выбираться нами;
SRC2_случайный — порт источника после NAT-подмены устройством NAT2.

Если внимательно посмотреть на эту схемку, то затруднения для «пробоя» очевидны. Для того, что бы обмен трафиком зарботал, должно быть выполнено: SRC1_случайный = DST2_по_желанию и DST1_по_желанию = SRC2_случайный.
Устройства хост1 и хост2 не могут контроллировать порты SRC1_случайный и SRC2_случайный. В общем случае они будут действительно случайны, плюс к этому зависеть от IP назначения и порта назначения. Масса материалов, найденных поиском по этой теме игнорировали сей простой факт, считая, что достаточно простого STUN сервера, что бы узнать эти неизвестные.
Как бы да. Если взять NAT, организованный с помощью, скажем iptables, то конструкции вида

-t nat -A POSTROUTING -j MASQUERADE
или
-t nat -A POSTROUTING -j SNAT —to-source

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

Для того же iptables указанные конструкции можно заменить на

-t nat -A POSTROUTING -j MASQUERADE… —random
или
-t nat -A POSTROUTING -j SNAT —to-source… —random

и ситуация станет менее веселой. Исходящий порт начнет выбираться при NAT-преобразовании случайным.

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

Ни хост1 ни хост2 не знают о SRC1_случайный и SRC2_случайный, влиять они на них могут только косвенно, например, меняя для своих исходящих пакетов порт источника или достаточным таймаутом между посылкой данных, что бы записи для NAT-трансляций на NAT-устройствах успевали устареть и сброситься.
Хост2 должен кроме всего прочего узнать DST1_по_желанию (в общем случае, порт может быть и заранее согласован, но будем рассматривать наихудший вариант).

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

1. Строго необходим посредник, с которым хост1 и хост2 устанавливают полноценные управляющие соединения со свободным двусторонним прохождением трафика. Иницаторы таких контрол-сессий — хосты за NAT. Посредник распространяет среди конечных хостов информацию о их белых IP.

2. Допустим, хост1 будет инициатором целевого соединения/клиентом, а для хост2 соединение будет входящим, т. е. он будет выступать в роли сервера.

3. Хост1 начинает отсылать пакеты на реальный адрес NAT2, выбрав DST1_по_желанию произвольно.

4. Хост1 по управляющему соединению сообщает посреднику о выбранном DST1_по_желанию и продолжает посылать пакеты с теми же параметрами (порт источника, порт назначения) для поддержания постоянной трансляции на NAT1, что бы не изменился SRC1_случайный. Задержку между отправками можно определить экспериментально, в силу тривиальности задачи не расписываю ее и не включаю в данный текст.

5. Посредник сканирует NAT1, подменяя IP источника на адрес NAT2, обеспечивая порт источника DST1_по_желанию и перебирая порт назначения в диапазоне от 1024 (или весь диапазон 65535?) и выше. Все это необходимо, ведь мы определились ранее, что NAT не зря назван симметричным.
Когда в результате перебора (если нас не забанят к этому времени на NAT1) мы пошлем наконец пакет с портом назначения SRC1_случайный, то такой пакет, если не помешают какие-то особо хитрые механизмы защиты, выходящие за рамки NAT-функционала, будет пропущен к хост1. Что он и детектирует и далее по управляющему каналу уведомит посредника, а тот запомнит угаданное значение SRC1_случайный.

6. Посредник сообщает по управляющему каналу на хост2 SRC1_случайный.

7. Хост2 начинает посылать пакеты на внешний адрес NAT1, порт назначения в этих пакетах должен быть DST2_по_желанию = SRC1_случайный. Делать он это вынужден будет до тех пор, пока SRC2_случайный, выбираемый устройством NAT2, не совпадет с DST1_по_желанию.
Когда это долгожданное совпадение произойдет, возможны два варианта.
Первый. Такой пакет дойдет до хост1, тот уведомит об этом посредника, посредник уведомит хост2 и «счастливая» сессия будет продолжена.
Второй. Хост2, не желая лишний раз злить NAT1 «долбежкой», менял в своих исходящих пакетах IP-ttl на такое значение, что бы через NAT2 они проходили, но не доходили до NAT1. Трансляции на NAT2 при этом создаются, пакеты до NAT1 не доходят, но когда заветная трансляция будет создана, пакет от хост1 (а мы не забыли, что он свою трансляцию поддерживает описанным ранее примемом, а именно — «долбится» на NAT2 с постоянными параметрами) дойдет до хост2, и уже тот в свою очередь уведомит посредника и так далее.

Как видно, в общем случае пробой симметричного NAT не такая уж простая и быстрая задача. На тех или иных реализациях (вспоминаем iptables) она может быть существенно упрощена и превратиться в процедуру с гарантированным успехом, но, повторюсь, не в общем случае.
Какие наиболее «узкие» места описанного алгоритма?

1. Необходимость спуфинга в пункте 5. Посредник должен иметь такой способ доступа к сети, который это позволил бы сделать и не получить при этом проблем со стороны провайдера.

2. Сканирование большого диапазона портов в пункте 5. Позволю себе снова повториться, в случае «правильного» NAT-а нельзя хосту1 просто установить соедиение с посредником и предположить, что SRC1_случайный в случае коннекта к NAT2 будет таким же при прочих равных. Смена адреса назначения приведет к смене SRC1_случайный. Скан, конечно, можно провести достаточно быстро и агрессивно, можно осторожней, но дольше. В любом случае это слабое место.

3. Действия из пункта 7 могут быть неопределенно долгими. Как раз этот момент вызвал мое любопытство и желание попробовать.

4. Ну и конечно, кто-то, кому надоело читать столько букв, может сказать: «Нафиг оно надо, гонять весь целевой трафик через посредника и все!». Тут возразить конечно сложно, перед теми я извиняюсь за отнятое данным текстом время.

Теперь к практике. Возможно разочарую тех, кого тема заинтересовала, но посредника я не писал 🙂 В его роли выступил tcpdump в нужном месте и в нужное время, а так же глаза и руки автора 🙂 Но кое-какие интересные результаты я все же получил.

Итак. Имеем виндовую рабочую станцию с 3G = хост1. На модемном соединении выдался серый адрес 10.140.80.130. Это внутренний адрес хоста1 за NAT-ом.
Имеем роутер AT AR-750s c белым адресом xx.xx.xx.xx и хостом2 непосредственно за этим роутером.
Выбираем произвольный DST1_по_желанию, в моем случае он был равен 21393.
Начинаем с периодичностью в 10 секунд с хоста1 посылать UDP пакеты на xx.xx.xx.xx: 21393.
Пропуская стадию злобного скана, доносим «по секрету» информацию о порте 21393 до хоста2, а так же «подсмотренную» информацию о том, что SRC1_случайный за NAT-ом сотового оператора у нас 45499 (ну и IP, от которого нас NAT-ит мироед тоже = yy.yy.yy.yy).
С хоста2 начинаем «долбится» на yy.yy.yy.yy: 45499 и ждем, когда нам повезет и наш исходящий пакет получит порт источника за NAT-ом равный 21393. C ttl я не баловался, факт пробоя опредял сниферами на хост1 и хост2. Скорость генерации пакетов с хоста2 была ~5 пакетов в секунду. Роутер NAT2 при этом был слегка нагружен посторонним (слышали бы это пользователи) трафиком.
Первый «пробой» случился примерно через 8 часов после начала эксперимента. Потом их случилось еще несколько штук, т. к. все это хозяйство было оставлено работать на ночь. Последующие стали происходить пошустрее в несколько раз, тут можно пофантазировать о влиянии «постороннего» трафика.

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

Вывод снифера в «хитрой» точке съема трафика, в которой он (трафик) виден уже после NAT-а на внешнем интерфейсе роутера AR-750

02:19:05.060809 IP xx.xx.xx.xx.21393 > yy.yy.yy.yy.45499: UDP, length 0
05:07:00.178149 IP xx.xx.xx.xx.21393 > yy.yy.yy.yy.45499: UDP, length 0
06:28:35.355623 IP xx.xx.xx.xx.21393 > yy.yy.yy.yy.45499: UDP, length 0
07:16:29.764069 IP xx.xx.xx.xx.21393 > yy.yy.yy.yy.45499: UDP, length 0
11:28:06.899109 IP xx.xx.xx.xx.21393 > yy.yy.yy.yy.45499: UDP, length 0

Вывод снифера на хосте1, в котором «засечен» пробой (время не синхронизировано, отсюда некоторое несоответствие временных меток распечатке из дампа трафика хоста2).

02:18:20.480468 IP xx.xx.xx.xx.21393 > 10.140.80.130.2429: UDP, length 0
05:06:15.496093 IP xx.xx.xx.xx.21393 > 10.140.80.130.2429: UDP, length 0
06:27:50.464843 IP xx.xx.xx.xx.21393 > 10.140.80.130.2429: UDP, length 0
07:15:44.839843 IP xx.xx.xx.xx.21393 > 10.140.80.130.2429: UDP, length 0
11:27:21.589843 IP xx.xx.xx.xx.21393 > 10.140.80.130.2429: UDP, length 0

Был еще результат дампа непосредственно на хосте2, в котором было видно, что поддерживающие неизменной трансляцию на NAT1 пакеты из пункта 4 до хост2 стали доходить, но я его посеял.

Вот такой не совсем честный эксперимент. Но он показал, что даже при относительно невысокой частоте перебора в пункте 7 нужного результата можно достичь за относительно разумное время. Оно может стать еще более разумным при более агрессивном переборе. Конечно, на «промышленное» применение не тянет, но… Но это возможно. Описанный алгоритм и эксперимент дает пищу для размышлений на тему ускорения-оптимизации-многопоточного_подхода и прочее подобное.

ссылка на оригинал статьи http://habrahabr.ru/post/155803/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *