Page 1 of 2

NAT Fix -- Solves the "connection accepted" issue

Posted: Fri Dec 11, 2009 8:32 am
by Baker
In cl_main.c (<--- not platform specific! Fix on any platform easy.) :
/*
=====================
CL_EstablishConnection

Host should be either "local" or a net address to be passed on
=====================
*/
void CL_EstablishConnection (char *host)
{
if (cls.state == ca_dedicated)
return;

if (cls.demoplayback)
return;

CL_Disconnect ();

cls.netcon = NET_Connect (host);
if (!cls.netcon)
Host_Error ("CL_Connect: connect failed\n");
Con_DPrintf ("CL_EstablishConnection: connected to %s\n", host);

cls.demonum = -1; // not in the demo loop now
cls.state = ca_connected;
cls.signon = 0; // need all the signon messages before playing

MSG_WriteByte (&cls.message, clc_nop); // ProQuake NAT Fix

}
The notes and many changes to the network code in ProQuake make this difficult to isolate. I remembered seeing this singular change in the Q2K4 engine's source recently examinating Quake2 model support.

In both ProQuake and JoeQuake there about 3-4 changes marked NAT fix. Not sure why.

I was able to verify that this change alone solves the issue. Adding it in resolves the "connection accepted situation" and commenting it out generates "connection accepted". Despite all the places marked NAT fix in the ProQuake and JoeQuake source code, this single change above appears to be the only part necessary.

And the mystery of what exactly is required to do the NAT fix has boggled people such as Entar, aguirRe, myself and aggravated a lot of PSP developers and Wii people and so forth because winquake/glquake ports invariably inherit this problem.

Posted: Fri Dec 11, 2009 11:08 pm
by Team Xlink
This has caused me so much frustration and has torn me to shreds trying to figure this out.

Baker, your are a life saver, I don't know how I can thank you enough for this.

I have went through ProQuakes, and JoeQuakes Source countless times and have tried so many different things, I can't thank you enough for what you have just done for me.


Thank you.

Posted: Sat Dec 12, 2009 12:02 am
by Baker
Team Xlink wrote:This has caused me so much frustration and has torn me to shreds trying to figure this out.

Baker, your are a life saver, I don't know how I can thank you enough for this.

I have went through ProQuakes, and JoeQuakes Source countless times and have tried so many different things, I can't thank you enough for what you have just done for me.


Thank you.
It frustrated me to death in the past as well.

No need to thank me --- you solved my frustration with never being able to figure out how the hell to compile for the PSP [which led to me finally understanding how to use cygwin and then compiling FlashQuake].

Posted: Sat Dec 12, 2009 2:19 am
by mh
One question about this - it's a protocol change isn't it? So one can't just add it to any engine in other words cos a client that has it would need a server that also has it?

Posted: Sat Dec 12, 2009 2:20 am
by Team Xlink
mh wrote:One question about this - it's a protocol change isn't it? So one can't just add it to any engine in other words cos a client that has it would need a server that also has it?
I think it is only for the client because the Nat fix fixes the problem on the client side where the client doesn't complete the handshake.

So, Client Side I believe.

Reasoning

Posted: Sat Dec 12, 2009 10:51 am
by Spike
Reasoning:

A NAT will route packets from the client to the server and back.
If you have multiple clients/computers on the inside of the NAT and a new packet arrives, the NAT is only able to route the packet to the client computer if it knows which client computer it is meant to be sent to.
With the NQ protocols, the server listens on one port, and opens a new port for each client. It also initiates the connection on that new port.
This means that the client can begin connecting to the server, but once it has got a connection, the server starts sending packets to the router, and the router receives packets which it does not know how to route.
The solution is to send a dummy packet from the client's port to the server's new port for that client, thus opening a route on the client's NAT which basically tells the router how to route packets properly.

Complications:
Broken routers:
There are some routers which are so mind numbingly stupid that they close the connection again after 2 mins, and which will only be reopened by a client->server packet. NQ clients only send packets on receipt of a packet from a server, and in this case, no NAT fix will save you. Such routers are of course absurdly broken. A work around is to just send dummy nop packets (or even corrupt ones) every 2 mins, or each time you lose your connection, lol.
Server NATS:
Additionally, this is a client-side NAT fix only. For servers behind a NAT or router, the server will listen on a known port for new clients, and then auto-allocate a new port for each client. You can forward the known port, but the port-per-client ports are generated almost at random, depending on the operating system. Its generally a bad plan to route 4096+ to that server... The solution to this is to change port allocation to a known range, or to rewrite the network code to always use a single port for every client. This is probably why there are multiple chunks of proquake code marked as NAT fixes, perhaps.

Alternatives:
As mentioned above, the server can be modified to use a single serverside port. This provides a NAT fix for all clients' NATS as well as the server. But its a lot of code. FTE does this, as does DP, but it doesn't fix the client if they connect elsewhere, of course.

You don't have to send a meaningful packet. You could send a 'corrupt' packet to the server, so long as you send at least one udp payload byte from the correct client port to the correct server port. The server does not need to parse it, it needs merely to be routed by the client's NAT.
Generally UDP over wireless is quite lossy, so you may wish to send a couple of packets (with a time gap) if the client is on a wifi connection before the router, but over ethernet more than 1 is overkill, its not really possible to tell however.

Posted: Sat Dec 12, 2009 2:55 pm
by frag.machine
mh wrote:One question about this - it's a protocol change isn't it? So one can't just add it to any engine in other words cos a client that has it would need a server that also has it?
Not really. AFAIK this just adds an already existing (and harmless, since there's no server behavior for client nop's) message to the protocol handshake so the connection is kept alive. In theory, the client could send periodically a nop message to the server without breaking compatibility.

Posted: Sat Dec 12, 2009 3:58 pm
by r00k
Looking closely it seems that the nat fix tells outbound packets to new connecting clients to wait until in datagram_getmessage

Code: Select all

		// joe: NAT fix from ProQuake
		if (sock->net_wait)
		{
			sock->addr = readaddr;
			strcpy (sock->address, sfunc.AddrToString(&readaddr));
			sock->net_wait = false;
		}

where it updates the port... ?

Posted: Sat Dec 12, 2009 4:22 pm
by Baker
There does happen to be a server-side NAT fix written by Yugo2Heck:

http://quakeone.com/q1files/downloads/l ... -ssp.patch
http://quakeone.com/forums/quake-talk/q ... #post11770

Back in the "older" days (2004-2006), I recommend someone with trouble connecting to try to connect to quake.ihoc.net to see if they could connect and if so, I knew they weren't using an NAT fixed client.

And you'd see a lot of "I can only connect to IHOC! I can't connect to any other server, please help OMG!".

However the best server answer would be to do whatever DarkPlaces and FTE do to use a single port. It would make server setup much easier for server hosts unfamiliar with NQ, which typically run into firewall issues because only post 26000 is open.
r00k wrote:Looking closely it seems that the nat fix tells outbound packets to new connecting clients to wait until in datagram_getmessage

Code: Select all

		// joe: NAT fix from ProQuake
		if (sock->net_wait)
		{
			sock->addr = readaddr;
			strcpy (sock->address, sfunc.AddrToString(&readaddr));
			sock->net_wait = false;
		}

where it updates the port... ?
@Rook: I don't claim to understand the networking code in code all that well, but I do know I didn't need to use the above code you quoted code to do a client-side fix for GLQuake. Just the one yellow line of code in post #1. I tested the NAT-fixed GLQuake against both ProQuake servers and against non-ProQuake servers (Clan Rum) and the single line of code allowed me to connect to both of them without "connection accepted" (and commenting out the single line would cause the "connection accepted" problem to come back).

Posted: Sun Dec 13, 2009 9:13 am
by r00k
Hmm, that clc_nop just sends an emtpy packet.
Curious though if yugo's patch will still allow two machines on same network to connect to the same server?

Posted: Sun Dec 13, 2009 5:00 pm
by Team Xlink
r00k wrote:Hmm, that clc_nop just sends an emtpy packet.
Curious though if yugo's patch will still allow two machines on same network to connect to the same server?
Correct me if I am wrong but isn't this already possible?

I swear I have done this.


EDIT: My mistakem it turns out I did this when I was hosting a public server so I had multiple clients connect to it. Sorry, about that.

Posted: Mon Dec 14, 2009 6:12 pm
by r00k
I meant would this patch DISALLOW multiple connections from the SAME IP address? :|

The client re-init's the socket after connection accepted, then the server scans the connected client's ip address, then reconnects the proper port. If two players have same IP but diff ports there may be a conflict. Hmm, or maybe two people sharing the same pov :O lol!

Posted: Sat Aug 21, 2010 8:32 pm
by Baker
Meanwhile, I might add to this thread that this NAT fix is good but not complete. It isn't enough in some situations, but regardless is a good improvement over not having it.

A European NetQuake player found a sitation where this NAT fix was insufficient in DirectQ and still got connection accepted.

MH rummaged through ProQuake's code some more and added a 2nd chunk and maybe 3rd chunk of an NAT fix.

Posted: Sat Aug 21, 2010 8:49 pm
by mh
I basically copied in all of ProQuake's net_dgrm.c :D

Not too certain exactly what the required code is, but it seems to be that this chunk is key:

Code: Select all

	// joe, from ProQuake: make NAT work by opening a new socket
	if (sock->mod == MOD_PROQUAKE && sock->mod_version >= 34)
	{
		clientsock = dfunc.OpenSocket(0);
		if (clientsock == -1)
			goto ErrorReturn;
		dfunc.CloseSocket(newsock);
		newsock = clientsock;
		sock->socket = newsock;
	}

Posted: Mon Aug 23, 2010 6:59 am
by r00k
mh wrote:I basically copied in all of ProQuake's net_dgrm.c :D

Not too certain exactly what the required code is, but it seems to be that this chunk is key:

Code: Select all

	// joe, from ProQuake: make NAT work by opening a new socket
	if (sock->mod == MOD_PROQUAKE && sock->mod_version >= 34)
	{
		clientsock = dfunc.OpenSocket(0);
		if (clientsock == -1)
			goto ErrorReturn;
		dfunc.CloseSocket(newsock);
		newsock = clientsock;
		sock->socket = newsock;
	}
wait a sec now MOD_PROQUAKE fakes a pq client (to a pq server) and the mod_version is any pq client >= 3.40, all that mumbo jumbo with sock->mod can be nixed. It's just JPG's way of doing a cls.protocol at the socket level. Primarily used for precise angle read/writes, but later used for cheatfree stuff. What proquake servers do is first connect on one socket, then opens another socket after initial signon.

i removed all that net_wait stuff an djust use yugo's code in _Datagram_CheckNewConnections

Code: Select all

	if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL)
	{
		/* Yugo2Heck: ProQuake 3.4 (and up) clients close their request socket
		** and make a new socket for the session.  This means we
		** connected back to the wrong socket when accepting the
		** connection.  So we look for a "bogus" request packet 
		** from that client on a different port, and re-connect 
		** back to that port instead.
		*/
		for (s = net_activeSockets; s; s = s->next)
		{
			if (s->driver != net_driverlevel)
				continue;

			ret = dfunc.AddrCompare(&clientaddr, &s->addr);
			if (ret == 1)
			{	//same client, different port: reconnect back
				dfunc.Connect(s->socket, &clientaddr);								
				return NULL;
			}
		}
	}