Sockets programming

Discuss programming topics for the various GPL'd game engine sources.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Sockets programming

Post by Baker »

Finalizing this is a major headache. :D

Code: Select all


// Returns the address family of an address or hostname.
// AF_INET, AF_INET6, or AF_UNSPEC if unknown.
int getaddrfamily (const char *addr)
{
	int ret;
    struct addrinfo hint = {0}, *info =0;
    //memset(&hint, 0, sizeof(hint));
   
	if (!addr) return AF_UNSPEC;

	hint.ai_family = AF_UNSPEC;
    // TODO: We have DNS lookup disabled, do we always want this behavior?
    hint.ai_flags = AI_NUMERICHOST;
    ret = getaddrinfo (addr, 0, &hint, &info);
    if (ret)
        return AF_UNSPEC;;
    ret = info->ai_family;
    freeaddrinfo (info);
    return ret;
}


// Determines from address
void sock_set (struct sockaddr_storage *sas, const char *_addr, int *port)
{
	const char *addr = _addr ? _addr : "localhost";

	int _familyadr  = getaddrfamily (addr);
	int _familysas  = (_familyadr == AF_UNSPEC) ? sas->ss_family : _familyadr;
	int family		= (_familysas == AF_UNSPEC) ? AF_INET6 : _familysas;

	unsigned short _usport = (unsigned short)*port;

	struct sockaddr_in  *sas_in4 = (struct sockaddr_in  *)sas;
	struct sockaddr_in6 *sas_in6 = (struct sockaddr_in6 *)sas;

	switch (family)
	{
	case AF_INET:
		if (!strcasecmp("localhost", addr)) addr = "127.0.0.1";
		sas_in4->sin_family = AF_INET;
        inet_pton (AF_INET, addr ? addr : "0.0.0.0", &sas_in4->sin_addr);
		sas_in4->sin_port = htons(_usport);
		break;

	default:			// <--- Unspecified gets IPv6, although we set this above
	case AF_INET6:
		if (!strcasecmp("localhost", addr)) addr = "::1";
		sas_in6->sin6_family = AF_INET6;
		inet_pton (AF_INET6, addr ? addr : "0.0.0.0", &sas_in6->sin6_addr);
		sas_in6->sin6_port = htons(_usport);
        break;
	}
}


void sock_set_any (struct sockaddr_storage *sas, int *port)
{	
	int _familysas  = sas->ss_family;
	int family		= (_familysas == AF_UNSPEC) ? AF_INET6 : _familysas;

	unsigned short _usport = (unsigned short)*port;	

	struct sockaddr_in  *sas_in4 = (struct sockaddr_in  *)sas;
	struct sockaddr_in6 *sas_in6 = (struct sockaddr_in6 *)sas;

	switch (family)
	{
	case AF_INET:
		sas_in4->sin_family = AF_INET;
		INETADDR_SETANY((struct sockaddr *)sas);
		sas_in4->sin_port = htons(_usport);
		break;

	default:			// <--- Unspecified gets IPv6, although we set this above
	case AF_INET6:
		sas_in6->sin6_family = AF_INET6;
		INETADDR_SETANY((struct sockaddr *)sas);
		sas_in6->sin6_port = htons(_usport);
		break;
	}
}


// preferably address length is INET_ADDRSTRLEN6 or longer
void sock_get (struct sockaddr_storage *sas, char *addr, size_t len, int *port)
{
	unsigned short _usport;

	struct sockaddr_in  *sas_in4 = (struct sockaddr_in  *)sas;
	struct sockaddr_in6 *sas_in6 = (struct sockaddr_in6 *)sas;

	switch(sas->ss_family) 
	{
    case AF_INET:
        inet_ntop (AF_INET, &sas_in4->sin_addr, addr, len);
        _usport = ntohs (sas_in4->sin_port);
        break;

    case AF_INET6:
        inet_ntop (AF_INET6, &sas_in6->sin6_addr, addr, len);
        _usport = ntohs (sas_in6->sin6_port);
        break;

	default:
		Core_Error ("sockaddr_storage_set unknown type");
	}
	
	if (port) *port = _usport;
}


void sock_string (struct sockaddr_storage *sas, char *addr, size_t len)
{
	char str[SYSTEM_STRING_SIZE];
	int port;
	sock_get (sas, str, sizeof str, &port);
	c_snprintfc (addr, len, "%s port %i", str, port);
}


// not thread safe (the static)
const char *sock_string_nts_ (struct sockaddr_storage *sas)
{
	static char str[SYSTEM_STRING_SIZE];
	int port;
	sock_get (sas, str, sizeof(str), &port);
	c_strlcat (str, va (" port %i", port));
	return (const char*)str;
}


void sock_string_addr (struct sockaddr_storage *sas, char *addr, size_t len)
{
	char str[SYSTEM_STRING_SIZE];
	sock_get (sas, str, sizeof str, NULL);
	strlcpy (addr, str, len);
}


// not thread safe
const char *sock_string_addr_nts_ (struct sockaddr_storage *sas)
{
	static char str[SYSTEM_STRING_SIZE];
	sock_get (sas, str, sizeof str, NULL);
	return (const char*)str;
}


cbool Net_SetSocketNonBlocking (sys_socket_t sockfd)
{
	cbool result = true;
	int on = 1, off = 0;

#ifndef PLATFORM_WINDOWS
	int flags = fcntl(fd, F_GETFL, 0);
	if ((fcntl (sockfd, F_SETFL, O_NONBLOCK)) == -1)
		result = false;
#endif

	if (sys_ioctlsocket (sockfd, FIONBIO, &on) == -1) 
		result = false;

	return result;
}


cbool sock_cmp (struct sockaddr_storage *sas1, struct sockaddr_storage *sas2)
{
	if (sas1->ss_family == sas2->ss_family)
	{
		struct sockaddr_in  *sas1_in4 = (struct sockaddr_in  *)sas1;
		struct sockaddr_in6 *sas1_in6 = (struct sockaddr_in6 *)sas1;
		struct sockaddr_in  *sas2_in4 = (struct sockaddr_in  *)sas2;
		struct sockaddr_in6 *sas2_in6 = (struct sockaddr_in6 *)sas2;

		switch (sas1->ss_family)
		{
		case AF_INET:
			if (memcmp (&sas1_in4->sin_addr, &sas2_in4->sin_addr, sizeof(struct in_addr)) != 0)
				return false; // No match
		
			// IP address matches, now compare port
			return (sas1_in4->sin_port == sas2_in4->sin_port);
		
		case AF_INET6:
			if (memcmp (&sas1_in6->sin6_addr, &sas2_in6->sin6_addr, sizeof(struct in6_addr)) != 0)
				return false; // No match
		
			// IP address matches, now compare port
			return (sas1_in6->sin6_port == sas2_in6->sin6_port);

		default:
			Core_Error ("sock_cmp " "ss_family unknown");
		}
	}

	return false;
}


cbool sock_cmp_addr (struct sockaddr_storage *sas1, struct sockaddr_storage *sas2)
{
	if (sas1->ss_family == sas2->ss_family)
	{
		struct sockaddr_in  *sas1_in4 = (struct sockaddr_in  *)sas1;
		struct sockaddr_in6 *sas1_in6 = (struct sockaddr_in6 *)sas1;
		struct sockaddr_in  *sas2_in4 = (struct sockaddr_in  *)sas2;
		struct sockaddr_in6 *sas2_in6 = (struct sockaddr_in6 *)sas2;

		switch (sas1->ss_family)
		{
		case AF_INET:
			return (memcmp (&sas1_in4->sin_addr, &sas2_in4->sin_addr, sizeof(struct in_addr)) == 0);
		
		case AF_INET6:
			return (memcmp (&sas1_in6->sin6_addr, &sas2_in6->sin6_addr, sizeof(struct in6_addr)) == 0);
			
		default:
			Core_Error ("sock_cmp_addr " "ss_family unknown");
		}
	}

	return false;
}


int sock_getport (struct sockaddr_storage *sas)
{
	struct sockaddr_in  *sas_in4 = (struct sockaddr_in  *)sas;
	struct sockaddr_in6 *sas_in6 = (struct sockaddr_in6 *)sas;

	switch (sas->ss_family)
	{
	case AF_INET:
		return (int)ntohs(sas_in4->sin_port);

	case AF_INET6:
		return (int)ntohs(sas_in6->sin6_port);

	default:
		Core_Error ("sock_getport " "ss_family unknown");	
		return 0; // often unreachable, depends on Core_Error assignment
	}
}


void sock_setport (struct sockaddr_storage *sas, int port)
{
	unsigned short _usport = (unsigned short)port;

	struct sockaddr_in  *sas_in4 = (struct sockaddr_in  *)sas;
	struct sockaddr_in6 *sas_in6 = (struct sockaddr_in6 *)sas;

	switch (sas->ss_family)
	{
	case AF_INET:
		sas_in4->sin_port = htons(_usport);
		return;

	case AF_INET6:
		sas_in6->sin6_port = htons(_usport);
        return;

	default:
		Core_Error ("sock_setport " "ss_family unknown");
	}	
}
That that is just the tip of the iceberg. Especially with NetQuake.

Outstanding stuff: make use single port for serving (true NAT fix).

Not sure what to do about "status" command, and "test" and 'test2" commands. I will need to find out if using IP6 addresses break those queries. test and test2 are used by server browsers. Many other small issues I need to address. Not sure how to handle ban lists, etc or the idea that in NetQuake you mask (out) the final octet for some level of privacy (I don't think, for example, Quakeworld reveals anything about player ip addresses except to admins).

Other outstanding item -ip x.x.x.x in server command line.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Sockets programming

Post by Baker »

Code: Select all

		ret = dfunc.AddrCompare (&clientaddr, &s->addr);
		if (ret >= 0)
		{
			// is this a duplicate connection request?
			if (ret == 0 && net_time - s->connecttime < 2.0)
			{
				// yes, so send a duplicate reply
				SZ_Clear(&net_message);
...

Code: Select all

				return NULL;
			}
			// it's somebody coming back in from a crash/disconnect
			// so close the old qsocket and let their retry get them back in
			//NET_Close(s);  // JPG - finally got rid of the worst mistake in Quake
			//return NULL;
Pretty comical. The result of this (without the above lines commented out) is an connection attempt to a NetQuake server with the same IP address will disconnect an existing player with that IP address.

So to abuse that, packet spoofing a connection request with the IP of an existing player would kick an existing player off a server.

This has been patched for years in NetQuake on ProQuake servers (which is nearly all --- with a few of DarkPlaces servers running NetQuake protocol being the rest), but prior to the source code release in 1999, every NetQuake server would have been vulnerable to this.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Post Reply