UNIX Socket FAQ

A forum for questions and answers about network programming on Linux and all other Unix-like systems

You are not logged in.

#26 2012-07-14 03:29 PM

RobSeace
Administrator
From: Boston, MA
Registered: 2002-06-12
Posts: 3,839
Website

Re: Best portable gaming socket architecture?

Offline

#27 2012-07-26 02:13 PM

RipRage
Member
From: England
Registered: 2010-01-06
Posts: 146

Re: Best portable gaming socket architecture?

Thanks for that Rob! Very helpful!

I would like to discuss a couple of things if it's ok. I finally found some free time to implement a server based on this structure and it seems to work very well! I kept it simple to begin with, 1 worker thread and a communication thread. Communication between the both via the queue and pipe seems to work well also!

Before I go any further I wanted to discuss security and stress testing.

Because the server will be dealing with login/registration messages in addition to normal game messages, It will need to be as secure as possible! So I was thinking of perhaps adding SSL for the login/registration or even have the whole game run over SSL (not sure how that would affect performance ? )

Lastly, what would be the best way to stress test this design? I did experiment by creating a client that sends a 100 login requests at once. I ran two clients at the same time but the second would only receive packets once the first had finished! However I delayed the sending of each packet by a few MS and the server could then haddle both of them. But, this was not a realistic test because it was performed over the loop back...

Last edited by RipRage (2012-07-27 12:37 AM)

Offline

#28 2012-07-27 09:30 PM

i3839
Oddministrator
From: Amsterdam
Registered: 2003-06-07
Posts: 2,239

Re: Best portable gaming socket architecture?

Rob is on vacation, but he'll probably read this within a few days anyway.

Main downside of SSL is that it increases latency. Whether that is a problem
or not depends on the type of game.

Blindly using SSL doesn't automatically increase security though. It doesn't
protect against (initial) man-in-the-middle attacks, for instance. To avoid those
you need a certificate or some way to have the server's public key known by
the client. But if you want to e.g. push game updates or data to clients then
using SSL is probably a good idea. But if all you want is authentication then
the server can as well send some random data and ask the client to hash that
together with their name and password and return the result.

As for stress testing, just have a few thousand clients at the same time doing
random stuff. Stress testing doesn't help testing how secure something is
though.

If you run such tests locally then the result you see is expected because it takes
very little time to do only a 100 logins. To be sure you need to add a time stamp
for each login request and keep sending a lot more logging requests. If one client
can starve the other then you forgot about handling requests in FIFO order.

Offline

#29 2012-07-29 01:22 AM

RipRage
Member
From: England
Registered: 2010-01-06
Posts: 146

Re: Best portable gaming socket architecture?

Offline

#30 2012-07-29 07:57 AM

i3839
Oddministrator
From: Amsterdam
Registered: 2003-06-07
Posts: 2,239

Re: Best portable gaming socket architecture?

Testing for security is very very difficult, because security holes
are pretty much by definition caused by something you didn't
think about. Once you write a test for something, you fixed that
hole already. It's still useful, but more to prevent the same bug
resurfacing later on.

Stress testing helps with testing how easy the server is to DoS,
and it can shake out a few bugs here and there, and thus make
it more secure as a side effect.

But to really properly test for security you have to write down all
your (security relevant) assumptions and write a test for each of
them. This is a lot harder and a lot more work than you expect,
because it's very easy to make assumptions without realising you
made them. And this are assumptions on all abstraction levels
present within the code, so not just about what data to expect
from clients, but also how different parts of the program behave
and interact.

The fewer assumptions your code makes, the more secure your
software is. In practice this means the code does the tests at runtime
instead of at test time.

If your code is multi-threaded then it's not deterministic and you
can have all kinds of timing related bugs which you can't easily
test for at all. Only easy to solve bugs are deadlocks.

In the end you can only make sure your code is secure by analysing
it. There are some formal verification tools, e.g. Promela and Spin,
but the problem of those is that they only work for limited, well
defined algorithmic kind of codes. And they don't catch "translation"
errors. They still only check what you ask them to check, and you
can't ask "Is it secure?"

Offline

#31 2012-07-30 12:17 PM

RipRage
Member
From: England
Registered: 2010-01-06
Posts: 146

Re: Best portable gaming socket architecture?

Last edited by RipRage (2012-07-30 06:23 PM)

Offline

#32 2012-08-06 04:28 PM

RobSeace
Administrator
From: Boston, MA
Registered: 2002-06-12
Posts: 3,839
Website

Re: Best portable gaming socket architecture?

Offline

#33 2012-08-14 12:49 PM

RipRage
Member
From: England
Registered: 2010-01-06
Posts: 146

Re: Best portable gaming socket architecture?

Hi Rob, thanks for your reply! I implemented a function based on select() from Jon c snader's book which allows me to place timeouts on individual sockets.

I just wanted to ask if it's ok for me to post my code from the communication thread here for you guys to take a look at and possibly give me any advice on improving it ?

Last edited by RipRage (2012-08-14 12:51 PM)

Offline

#34 2012-08-14 01:22 PM

RobSeace
Administrator
From: Boston, MA
Registered: 2002-06-12
Posts: 3,839
Website

Re: Best portable gaming socket architecture?

Sure, we always like to see code around here...  If it's a really huge amount of code, sometimes linking to it elsewhere is preferable, just because it's probably easier to download it and read it that way...  But, for most code, as long as you throw code tags around it and manage to preserve sane indentation, it's just fine to post it here...

Offline

#35 2012-08-14 05:47 PM

RipRage
Member
From: England
Registered: 2010-01-06
Posts: 146

Re: Best portable gaming socket architecture?

Thanks Rob. I need to figure out how to code a client for windows on linux using wine, so i have posted a windows version of the communication thread. To make things simpler i swapped the inbound queue for a socket pair. Once i am happy with it i will port it to unix and change socket_events() to sit on top of poll() and swap the socket pair for a pipe.

/* Communication thread */
extern void netcom_thread (void *param)
{
	char incoming[BUF_SIZE];
	char outgoing[BUF_SIZE];
	char ipbuf[IP_ADDRSIZE];

	netcom_info *coms = (netcom_info *) param;
	SOCKADDR_STORAGE peer;
	TIMEVAL tv;

	int peerlen, sock_id;
	int rbytes, i, j;

	/* Main communication loop */
	for (;;)
	{
		coms->read = coms->master;
		if (socket_events(coms->max + 1, &coms->read, NULL, NULL) < 0)
		{
			netcore_error(0, winsock, "select()");
			_endthreadex(1);
		}

		/* Loop through readable sockets */
		for (i = coms->min; i <= coms->max; i++)
		{
			if (FD_ISSET(i, &coms->read))
			{
				if (i == coms->listener)
				{
					get_epoch_time(&tv);

					peerlen = sizeof(peer);
					if ((coms->newsock = accept(i, (SOCKADDR *)&peer, &peerlen)) < 0)
						netcore_error(0, winsock, "accept()");

					else
					{
						get_ip_address((SOCKADDR *)&peer, ipbuf, IP_ADDRSIZE);

						netcore_error(0, 0, "New inbound connection from %s on socket %d\n",
							ipbuf, coms->newsock);

						/* Build a SYS_PEER_NEWCON message and send to worker */
						start_encoding(incoming, sizeof(incoming));
						encode_message("cllsl", SYS_PEER_NEWCON, tv.tv_sec, tv.tv_usec,
							ipbuf, coms->newsock);

						if (send_packet(coms->worker, incoming, get_encode_size()) == -1)
						{
							netcore_error(0, winsock, "send_packet()");
							closesocket(coms->newsock);
						}
						else
						{
							/* Start timer */
							start_timeout(kick_peer, (void *)&coms, coms->newsock, 600000);
							update_netcoms(coms);
						}
					}
				}
				else if (i == coms->worker)
				{
					/* Receive packet from worker */
					if ((rbytes = recv_packet(i, outgoing, BUF_SIZE)) <= 0)
					{
						if (rbytes < 0)
							netcore_error(0, winsock, "recv_packet()");

						/* Worker died! */
						if (rbytes == 0)
						{
							netcore_error(0, 0, "Worker died! Terminating!\n");
							_endthreadex(1);
						}
					}
					else
					{
						/* Get socket identifer */
						start_decoding(outgoing, rbytes);
						decode_message("l", &sock_id);

						/* Send to an individual */
						if (sock_id != SYS_PEER_SENDTOALL)
						{
							if (FD_ISSET(sock_id, &coms->master))
							{
								if (socket_ready(sock_id, 0, 3, SOCK_WRITE))
								{
									if (send_packet(sock_id, outgoing, rbytes - get_decode_size()) == -1)
										netcore_error(0, winsock, "send_packet()");
								}
							}
						}
						else
						{
							/* Send to everyone */
							for (j = coms->min; j <= coms->max; j++)
							{
								if (FD_ISSET(j, &coms->master))
								{
									if (j != coms->listener && j != coms->worker)
									{
										if (socket_ready(j, 0, 3, SOCK_WRITE))
										{
											if (send_packet(j, outgoing, rbytes - get_decode_size()) == -1)
												netcore_error(0, winsock, "send_packet()");
										}
									}
								}
							}
						}
					}
				}
				else
				{
					/* Data from client */
					get_epoch_time(&tv);

					start_encoding(incoming, sizeof(incoming));
					encode_message("lll", i, tv.tv_sec, tv.tv_usec);

					if ((rbytes = recv_packet(i, get_encode(), BUF_SIZE - get_encode_size())) <= 0)
					{
						if (rbytes < 0)
							netcore_error(0, winsock, "recv_packet()");

						if (rbytes == 0)
							netcore_error(0, 0, "Peer hung up on socket %d\n", i);

						FD_CLR(i, &coms->master);
						closesocket(i);
					}
					else
					{
						/* If there is a timer set, cancel it  */
						stop_timeout(i);

						/* Forward packet to worker */
						if (send_packet(coms->worker, incoming, rbytes) == -1)
							netcore_error(0, winsock, "send_packet()");
					}
				}
			}
		}
	}
}

Edit: Sorry about the indentation, not sure why it's done that...

Last edited by RipRage (2012-08-14 08:04 PM)

Offline

#36 2012-08-14 08:49 PM

RobSeace
Administrator
From: Boston, MA
Registered: 2002-06-12
Posts: 3,839
Website

Re: Best portable gaming socket architecture?

Offline

#37 2012-08-16 04:00 PM

RipRage
Member
From: England
Registered: 2010-01-06
Posts: 146

Re: Best portable gaming socket architecture?

extern int32 encode_message (char *buf, int size, char *format, ...)
{
	va_list ap;

	u_int32 L;	/* Unsigned 32-bit */
	int32 l;	/* Signed 32-bit */

	u_int16 N;	/* Unsigned 16-bit */
	int16 n;	/* Signed 16-bit */

	u_int8 C;	/* Unsigned 8-bit */
	int8 c;		/* Signed 8-bit */

	int32 smax = 0;
	int16 slen = 0;

	char *b, *s;

	va_start(ap, format);

	for (b = buf; *format != '\0'; format++)
	{
		switch (*format)
		{
		case 'L':	/* Unsigned 32-bit */
			{
				if (smax + 4 > size)
					break;
				else
					smax += 4;

				L = (u_int32)va_arg(ap, u_int32);
				pack_u32(b, L);

				b += 4;
				break;
			}

		case 'l':	/* Signed 32-bit */
			{
				if (smax + 4 > size)
					break;
				else
					smax += 4;

				l = (int32)va_arg(ap, int32);
				pack_i32(b, l);

				b += 4;
				break;
			}

		case 'N':	/* Unsigned 16-bit */
			{
				if (smax + 2 > size)
					break;
				else
					smax += 2;

				N = (u_int16)va_arg(ap, unsigned int);	/* Promoted */
				pack_u16(b, N);

				b += 2;
				break;
			}

		case 'n':	/* Signed 16-bit */
			{
				if (smax + 2 > size)
					break;
				else
					smax += 2;

				n = (int16)va_arg(ap, int);	/* Promoted */
				pack_i16(b, n);

				b += 2;
				break;
			}

		case 'C':	/* Unsigned 8-bit */
			{
				if (smax + 1 > size)
					break;
				else
					smax += 1;

				C = (u_int8)va_arg(ap, unsigned int); /* Promoted */
				
				*b++ = c;
				break;
			}

		case 'c':
			{
				if (smax + 1 > size)
					break;
				else
					smax += 1;

				c = (int8)va_arg(ap, int); /* Promoted */

				*b++ = c;
				break;
			}

		case 's':
			{
				s = va_arg(ap, char *);
				slen = strlen(s);

				if (smax + slen + 2 > size)
					break;
				else
					smax += slen + 2;

				pack_i16(b, slen);
				b += 2;

				memcpy(b, s, slen);
				b += slen;
				break;
			}
		}
	}

	va_end(ap);

	return smax;
}

Last edited by RipRage (2012-08-16 04:38 PM)

Offline

#38 2012-08-16 09:55 PM

RobSeace
Administrator
From: Boston, MA
Registered: 2002-06-12
Posts: 3,839
Website

Re: Best portable gaming socket architecture?

As long as pack_*() isn't doing anything weird, I think that looks thread-safe...

But, I see a typo bug: in the 'C' case, you set the variable "C", but reference "c"...  That's what you get from using confusingly similar variable names! ;-)

Also, in the overflow tests, the "break"s you do are just going to break the switch, but stay in the loop, which means that potentially shorter following items could get added even though some earlier items were skipped over...  I'd think saner behavior would be to just stop completely at that point, probably with some kind of return value indicating the truncation to the caller...  (Either a simple -1, or you could go fancy like snprintf() does and return how many bytes you WOULD have filled in if the buffer had been large enough, so that the caller can detect the return value being larger than their passed size, and know exactly how much larger the buffer needs to be...  That would actually be pretty simple to do with your code as it is: just before the "break" in the overflow cases, still do the increase of "smax" anyway...  Ie: basically move it prior to the test, change the test to simply "smax > size", and eliminate the "else"...)

Offline

#39 2012-08-18 06:40 PM

RipRage
Member
From: England
Registered: 2010-01-06
Posts: 146

Re: Best portable gaming socket architecture?

void pack_i32 (char *b, int32 i)
{
   u_int32 n = htonl((u_int32)i);

   memcpy(b, (char *)&n, sizeof(u_int32));
}

Offline

Board footer

Powered by FluxBB