Cześć wszystkim
Przebicie UDP działa i działało. Natomiast jeśli chodzi o TCP Możliwe że ja coś źle piszę ale nie jestem początkującym programistą ale chyba czegoś nie rozumiem.

Oczywiście skrócę trochę, zakładamy że coś tak trywialnego jak połączenie z serwerem zewnętrznym już mamy :)
Synchronizacja pakietów między wątkami jak i ich tworzenie, kolejkowanie, serializację, wysyłanie surowych bajtów i wiele wiele innych pomijam bo chodzi o element stworzenia gniazd TCP a nie o to co działa.

Użyjemy klasy TcpListener do serwera.

public void InitializeServer(IPAddress address, int port)
        {
            try
            {
                // 127.0.0.1 accept only local connections, 0.0.0.0 is open for whole internet connections

                listener = new TcpListener(address, port);
                socket = listener.Server;

                // Enable NAT Translation
                listener.AllowNatTraversal(true);

                // Start listening for example 10 client requests.
                listener.Start(listenQueue);

                Debug.Log($"[L{socket.LocalEndPoint}]Server start... ", EDebugLvl.Log);

                OnServerInitialize(true);

                // Enter the listening loop.
                StartListener();
            }
            catch (SocketException e)
            {
                Debug.LogError($"SocketException: {e}", EDebugLvl.Error);
                OnServerInitialize(false);
            }
        }

Rozpoczynamy nasłuch

private void StartListener()
        {
            Debug.Log("\nWaiting for a connection... ");
            listener.BeginAcceptTcpClient(AcceptCallback, listener);
        }

W momencie gdy serwer odbierze połączenie tworzymy nowe gniazdo

private void AcceptCallback(IAsyncResult ar)
        {
            TcpListener server = (TcpListener)ar.AsyncState;
            TcpClient newClient = null;

            try
            {
                newClient = server.EndAcceptTcpClient(ar);
            }
            catch (Exception e)
            {
                Debug.LogError(e.ToString());
            }

            if (newClient != null && newClient.Connected)
            {

                //...

                client.StartRead();
            }

            //Loop
            StartListener();
        }

U klienta tworzymy nowe gniazdo i próbujemy nawiązać połączenie

public void Connect(IPEndPoint remote, IPEndPoint bind = null, bool reuseAddress = false)
        {
            if (bind == null)
            {
                client = new TcpClient();
            }
            else
            {
                client = new TcpClient(bind);
            }
            
            socket = client.Client;

            if (reuseAddress)
            {
                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, reuseAddress);
                //To mi wyrzuca błąd.
                //socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, reuseAddress);
            }

            client.BeginConnect(remote.Address, remote.Port, ConnectCallback, null);
        }

Połączenie działa bez problemu oraz przesyłanie danych.

Niestety tutaj musimy rozpocząć nowym gniazdem nasłuch na tym samym adresie oraz porcie które zostało utworzone przy połączeniu z serwerem. Robię to u każdego klienta.

public void StartHost(Client server)
        {
            if (server != null && server.socket.Connected)
            {
                IPEndPoint localHost = (IPEndPoint)server.socket.LocalEndPoint;
                InitializeHost(localHost.Address, localHost.Port);
            }
        }
public void InitializeHost(IPAddress address, int port, bool reuse = false)
        {
            try
            {
                listener = new TcpListener(address, port);
                socket = listener.Server;

                if (reuse)
                {
                    socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                    socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, true);
                }

                // Enable NAT Translation
                listener.AllowNatTraversal(true);

                // Start listening for example 10 client requests.
                listener.Start(listenQueue);

                Debug.Log($"\n[L{socket.LocalEndPoint}]Host start... ", EDebugLvl.Log);

                OnServerInitialize(true);

                // Enter the listening loop.
                StartListener();
            }
            catch (SocketException e)
            {
                Debug.LogError($"SocketException: {e}", EDebugLvl.Error);
                OnServerInitialize(false);
            }
        }
private void StartListener()
        {
            Debug.Log("\nWaiting for a connection... ");
            listener.BeginAcceptTcpClient(AcceptCallback, listener);
        }
private void AcceptCallback(IAsyncResult ar)
        {
            TcpListener server = (TcpListener)ar.AsyncState;
            TcpClient newClient = null;

            try
            {
                newClient = server.EndAcceptTcpClient(ar);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            if (newClient != null && newClient.Connected)
            {

                //...

                client.StartRead();
            }

            //Loop
            StartListener();
        }

A więc tak jak piszą wszędzie... klient B wysyła do serwera pakiet że chce nawiązać połączenie, serwer wysyła do klienta A informacje o kliencie B i na odwrót

Po czym oboje próbują nawiązać połączenie nowym gniazdem ? No dobra...

public void Connect(IPEndPoint remote, IPEndPoint bind = null, bool reuseAddress = false)
        {
            if (bind == null)
            {
                client = new TcpClient();
            }
            else
            {
                client = new TcpClient(bind);
            }
            
            socket = client.Client;

            if (reuseAddress)
            {
                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, reuseAddress);
                //socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, reuseAddress);
            }

            client.BeginConnect(remote.Address, remote.Port, ConnectCallback, null);
        }
private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                client.EndConnect(ar);
            }
            catch (Exception e)
            {
                Debug.LogError(e.ToString(), EDebugLvl.ConnectionError);
            }
            if (client.Connected)
            {
                Debug.Log($"[P{socket.RemoteEndPoint}, L{socket.LocalEndPoint}]Connected", EDebugLvl.ConnectionLog);
                stream = new NetworkStream(socket, FileAccess.ReadWrite, true);
                StartRead();
            }
            ConnectedComplete(this, socket.Connected);
        }

Nie ważne ile razy będę próbował, połączenie jest odrzucane... adresy wszędzie się zgadzają a mimo to nie działa więc nie mam co tutaj do pisania w tej sprawie tym bardziej że działa mi UDP.

To co napisałem działa tylko w tej samej sieci NAT . Niestety zauważyłem że na tym samym NAT stworzyło aż dwa połączenia. Jedno jest wynikiem próby połączenia nowym gniazdem A z B a drugie jest wynikiem odebrania nowego połączenia z B do A więc każdy z klientów posiada niepotrzebne jedno gniazdo połączone lokalnym adresem. Więc całe przebicie NAT TCP/IP u mnie nie działa. Faktycznie mogę użyć UDP ale koniecznie potrzebne mi TCP. Siedzę nad tym od kilku miesięcy w wolnym czasie ale nigdzie nie mogę znaleźć przykładu z kodu a nie teorii czego jest bardzo dużo.
Zgromadziłem sporo wiedzy przez 8 lat a od 2 piszę aplikacje używając gniazd aż w końcu potrzebne mi przebicie.

Czemu nie użyję gotowego rozwiązania ? Potrzebuję własnego które jest w pełni otwarte używając tylko UDP i TCP ponieważ niektóre urządzenia docelowe wspierają tylko te protokoły. Używałem również klasy Socket ale i ta nie dała mi działającego egzemplarza.

Może będzie ktoś wstanie mi pomóc za co bym był bardzo wdzięczny a na pewno post pomoże też innym to zrozumieć.
Pozdrawiam