Czy STUN nie służy po prostu do wykrywania publicznego adresu IP i portu hosta? To mam już zrobione, serwer przesyła hostom nawzajem ich adresy IP. Czyli powinienem w tym przypadku zamiast komunikacji TCP użyć UDP i ten sam sposób który opisałem powyżej zadziała?
Tutaj ten port jest bardzo ważny, a nie wiem, czy w ogóle zwracasz na niego uwagę. Niezależnie od wszystkiego, w praktyce musisz użyć UDP, bo w TCP musiałbyś liczyć na to, że któryś z routerów przepuści nieprawidłowy pakiet (pierwszy przepuści SYN/ACK bez wcześniejszego SYN, ewentualnie drugi przepuści SYN jako odpowiedź na SYN) i jeszcze byłyby potrzebne raw sockets, a podczas komunikacji UDP nie ma różnicy między pierwszym a drugim pakietem, a całe omijanie NAT-u opiera się na oszukiwaniu go, że coś jest odpowiedzią na wcześniej wysłany pakiet, pomimo tego że naprawdę nie jest.
Musisz uważać, skąd wysyłasz pakiet do serwera STUN - raczej nie z portu, który system operacyjny wybrał automatycznie, chyba że śledzisz, jaki wybrał.
Jeżeli założymy, że nie trzeba obsługiwać symetrycznych NAT-ów, tylko wszystkie inne, z których najgorszym rodzajem jest port-restricted:
- musisz wymyślić jakieś dwa porty źródłowe, załóżmy że są to 11000 i 13000, nie możesz pozwalać systemowi na ich wybór
- załóżmy, że pierwsze urządzenie ma wewnętrzny IP 192.168.1.5 i zewnętrzny 87.205.x.x
- załóżmy, że drugie urządzenie ma zewnętrzny IP 192.168.60.2 i zewnętrzny 188.99.x.x
- na pierwszym urządzeniu musisz wysłać do serwera STUN żądanie, ustawiając źródłowy IP/port gniazda na 192.168.1.5/11000, dostaniesz odpowiedź, że żądanie przyszło z IP i portu 87.205.x.x/13521 (tak, router może zmienić port, chociaż najpopularniejszy netfilter tego nie robi, gdy nie musi)
- na drugim urządzeniu musisz wysłać do serwera STUN żądanie, ustawiając źródłowy IP/port gniazda na 192.168.60.2/13000, dostaniesz odpowiedź, że żądanie przyszło z IP i portu 188.99.x.x/14222
- urządzenia muszą przez jakiś serwer wymienić się tymi zewnętrznymi IP i portami
- pierwsze urządzenie musi z gniazda przypisanego funkcją bind() lub podobną do 192.168.1.5/11000 wysłać coś do 188.99.x.x/14222, router przed pierwszym urządzeniem zrobi z tego 87.205.x.x/13521 ->188.99.x.x/14222, router po drugiej stronie nie będzie wiedział, co z tym zrobić i odrzuci, ale wysłanie spowoduje że router przed pierwszym urządzeniem na chwilę "przekieruje port" w celu odebrania potencjalnych odpowiedzi, ale jako że jest port-restricted, to przekierowanie będzie obowiązywało tylko dla datagramów przychodzących od 188.99.x.x/14222
- drugie urządzenie musi z gniazda przypisanego do 192.168.60.2/13000 wysłać coś do 87.205.x.x/13521, router przed drugim urządzeniem zrobi z tego 188.99.x.x/14222 -> 87.205.x.x/13521, router po drugiej stronie potraktuje to jako odpowiedź na ten wcześniej wysłany datagram i przekieruje do pierwszego urządzenia, router przed drugim urządzeniem na chwilę "przekieruje port", aby odbierać odpowiedzi na to, co właśnie wysłał
- pierwsze urządzenie odbierze na swoim porcie 11000 to, co w powyższym punkcie wysłało drugie urządzenie
- teraz pierwsze urządzenie może coś wysłać z gniazda przypisanego do 192.168.1.5/11000 do 188.99.x.x/14222, router przed pierwszym urządzeniem zrobi z tego 87.205.x.x/13521 ->188.99.x.x/14222, router po drugiej stronie potraktuje jako odpowiedź i przekaże do urządzenia drugiego
- drugie urządzenie odbierze na swoim porcie 13000 to, co w powyższym punkcie wysłało pierwsze urządzenie
i masz dwukierunkową komunikację UDP - cztery wyższe punkty możesz powtarzać, możesz też wysyłać więcej niż jeden datagram bez czekania za każdym razem na odpowiedź. Teraz przydałoby się użyć jakiegoś RUDP, QUIC czy innego protokołu robiącego ponad UDP coś podobnego do TCP - numerującego pakiety, ponownie wysyłającego w przypadku nieodebrania, regulującego szybkość itp.
Z symetrycznym NAT-em (w konsolach do gier oznaczonym "Type 3" lub "Strict"), a operator komórkowy może takiego używać, problem jest taki, że przy używaniu STUN-a przetłumaczy np. 192.168.1.5/11000 na 87.205.x.x/13521, ale przy późniejszej właściwej komunikacji przetłumaczy np. 192.168.1.5/11000 na 87.205.x.x/13530, co jeszcze można rozwiązać jak Skype, próbując wszystkie porty od 13521 do np. 13541, ale jeżeli wybiera całkowicie losowo, nic się nie uda, stąd też symetryczny NAT jest w ogólności nie do obejścia, chyba że są specyficzne przypadki, np. router po drugiej stronie nie jest port-restricted, tylko traktuje jako odpowiedzi pakiety z niepasującym portem źródłowym (odpowiedzią na pakiet A:B->C:D jest dla niego pakiet C:cokolwiek->A:B, niekoniecznie C:D->A:B albo nawet jest full cone, czyli przyjmuje cokolwiek:cokolwiek->A:B), ale takich routerów jest niewiele. Warto zauważyć, że przy rzadkiej sytuacji: po jednej stronie NAT symetryczny ("Type 3" w PlayStation), po drugiej niesymetryczny inny niż port-restricted ("Type 1" w PlayStation) komunikację musi rozpocząć urządzenie za niesymetrycznym NAT-em (wysyłając coś na dowolny zamknięty port) - jeżeli zastosowanie wymaga, żeby było odwrotnie, trzeba zrobić callback przez serwer, tzn. jedno urządzenie prosi serwer, żeby poinformował drugie, że drugie musi nawiązać komunikację z pierwszym.
Co to znaczy "prawidłowe" numery portów źródłowych? Powinienem użyć jakichś konkretnych?
Musisz uważać na numery portów źródłowych, podałem przykład wyżej, inaczej nic się nie uda.
Przypominam, że pakiet ma IP źródłowy, port źródłowy, IP docelowy, port docelowy. Jeżeli, tak jak jest w większości programów klienckich, nie ustawiasz tych dwóch pierwszych bind() lub czymś podobnym, system ustawia: IP karty sieciowej powiązanej z bramą domyślną i automatycznie wybrany wysoki port.
I o co chodzi z "przepływami UDP"?
Przepływ to "połączenie", tylko nie można go tak nazwać, bo UDP jest bezpołączeniowy. Router z NAT-em musi wiedzieć, czy coś jest odpowiedzią, czy czymś całkowicie niepowiązanym. Dla większości routerów przepływem jest zbiór pakietów mających taką samą czwórkę IP źródłowy, port źródłowy, IP docelowy, port docelowy, wraz z odpowiedziami, czyli pakietami mającymi zamienione IP/porty źródłowe z docelowymi, gdzie nie może być za dużego odstępu czasowego np. większego niż 3-minutowy bez żadnego ruchu. W związku z tym będziesz też musiał zrobić jakiś keep-alive, tzn. przesyłanie śmieciowych pakietów np. co 30 sekund, jeżeli chcesz żeby router nie przestawał odbierać przychodzących danych.