0
votes

Punching the UDP network works and works. However, when it comes to TCP, it's possible that I'm writing something wrong but I'm not a beginner programmer, or maybe I don't understand something.

Of course, I'll shorten a bit, we assume that we already have something as trivial as connection to an external server :) Packet synchronization between threads as well as their creation, queuing, serialization, sending raw bytes and much more I skip because it is about the element of creating TCP sockets and not what works.

We will use the TcpListener class for the server.

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);
            }
        }

We start listening

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

When the server receives the connection, we create a new socket

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();
        }

We create a new socket at the client and try to establish a connection

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);
                //It throws me a error SocketOption so im comment this.
                //socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, reuseAddress);
            }

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

The connection works without any problems and data transfer.

Unfortunately, here as we know, we must start a new socket listening on the same address and port that was created when connecting to the server. I do this for every client.

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();
        }

So, as they write everywhere ... client "B" sends a packet to the server that wants to establish a connection, the server sends information to the client "A" about the client "B" and vice versa

Then they both try to connect with the new socket? No problem...

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);
        }

No matter how many times I try, the connection is rejected ... the addresses match everywhere and yet it does not work, so I have nothing to write about in this case, especially since UDP works for me.

What I wrote only works on the same NAT network. Unfortunately, I noticed that on the same NAT created two connections. One is the result of trying to connect the new socket A to B and the other is the result of receiving a new connection from B to A, so each client has one unnecessary socket connected by a local address. So all NAT TCP / IP Punch NAT doesn't work for me. I can actually use UDP but I really need TCP. I have been sitting on it for several months in my free time but nowhere can I find an example from the code and not the theory of which there is a lot. I accumulated a lot of knowledge for 8 years and from 2 I write applications using sockets and finally I need to punch the net.

Why won't I use the ready solution? I need my own which is fully open using only UDP and TCP because some target devices only support these protocols. I also used the Socket class but this one did not give me a working copy.

Maybe someone will be able to help me for which I would be very grateful and certainly the post will also help others understand this.

Regards Octavian

1
You're assuming that both NATs will reuse the same external port. Do you know that for sure? From my experience, this isn't common. It's also completely possible that one or both NATs prevents hole punching.Collin Dauphinee
I know that the end of the port may change XXXX {1-9} but it doesn't change with me it's still the same at least I didn't notice anything. 90% of routers support punching, or at least they write :D I assumed that if I manage UDP then why I can not write something wrong via TCP. Because if it doesn't work and I can't use UPnP. So how do I get a stable connection? Ok, I can use a gateway that will receive and send packets, but I really only have one solution to connect 10 users to one another host behind the NAT network ? :/TitanOktan
You know that the port your server sees doesn't change, but how do you know that your NAT doesn't use a different port for a different address? See stackoverflow.com/questions/2443471/… for more details; you're unlikely to have any success, this is a technique that was only barely viable some of the time in the 90s/early 2000s, and it's just become less reliable as NAT technology advances. UPnP is now widely supported and almost always a better option.Collin Dauphinee

1 Answers

0
votes

NAT hole punching only works with UDP, and even that is a hack.

NAT firewall implementations will start tracking a TCP stream when they see the initial SYN packet leaving the network. To capture incoming TCP streams you need to create an incoming rule on the router, there's no way around this. If the router supports UPnP, you can ask it to create that rule for you dynamically.

Since UDP doesn't have a SYN packet equivalent, routers will start tracking the stream on any outgoing packet. This is why NAT hole punching works. If both end points are behind NAT and just assume that the link will work. Both can just start sending UDP packets to each other. The routers will add the connection state, and map the incoming packets to each endpoint.