Udp hole punching для symmetric nat трохи теорії і майже чесний експеримент, savepearlharbor

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

Деякий час назад зацікавився сабжем. Пошук дав багато матеріалу, при вивченні якого виник ряд питань і захотілося деякі теоретичні моменти перевірити на практиці. Кому цікаво - прошу.

Для тих, хто не в темі коротко про те, що таке 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 і дати можливість хостам за ним спілкуватися безпосередньо?

У загальному випадку маємо таку просту схемку.

хост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 з'єднання буде входять, т. Е. Він буде виступати в ролі сервера.

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

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

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

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

3. Дії з пункту 7 можуть бути невизначено довгими. Якраз цей момент викликав мою цікавість і бажання спробувати.

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 потрібного результату можна досягти за відносно розумний час. Воно може стати ще більш розумним при більш агресивному переборі. Звичайно, на «промислове» застосування не тягне, але ... Але це можливо. Описаний алгоритм і експеримент дає поживу для роздумів на тему прискорення-оптимізації-многопоточного_подхода та інше подібне.