project_files/frontlib/socket.c
author unc0rr
Thu, 22 Nov 2012 00:56:36 +0400
changeset 8091 e0a76056a633
parent 7314 6171f0bad318
child 8094 6c5b4e69f03d
child 10017 de822cd3df3a
permissions -rw-r--r--
Follow IANA suggestions better

/*
 * Hedgewars, a free turn based strategy game
 * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "socket.h"
#include "util/logging.h"
#include "util/util.h"
#include <stdlib.h>
#include <SDL_net.h>
#include <time.h>

struct _flib_tcpsocket {
	TCPsocket sock;
	SDLNet_SocketSet sockset;
};

struct _flib_acceptor {
	TCPsocket sock;
	uint16_t port;
};

static uint32_t getPeerIp(TCPsocket sock) {
	IPaddress *addr = SDLNet_TCP_GetPeerAddress(sock);
	return SDLNet_Read32(&addr->host);
}

static bool connectionIsLocal(TCPsocket sock) {
	return getPeerIp(sock) == (uint32_t)((127UL<<24)+1); // 127.0.0.1
}

static flib_tcpsocket *createSocket(TCPsocket sdlsock) {
	flib_tcpsocket *result = flib_calloc(1, sizeof(flib_tcpsocket));
	if(result) {
		result->sock = sdlsock;
		result->sockset = SDLNet_AllocSocketSet(1);

		if(!result->sockset) {
			flib_log_e("Can't allocate socket: Out of memory!");
			SDLNet_FreeSocketSet(result->sockset);
			free(result);
			result = NULL;
		} else {
			SDLNet_AddSocket(result->sockset, (SDLNet_GenericSocket)result->sock);
		}
	}
	return result;
}

TCPsocket listen(uint16_t port) {
	IPaddress addr;
	addr.host = INADDR_ANY;
	SDLNet_Write16(port, &addr.port);
	TCPsocket sock = SDLNet_TCP_Open(&addr);
	if(!sock) {
		flib_log_w("Unable to listen on port %u: %s", (unsigned)port, SDLNet_GetError());
	}
	return sock;
}

flib_acceptor *flib_acceptor_create(uint16_t port) {
	flib_acceptor *result = flib_calloc(1, sizeof(flib_acceptor));
	if(result) {
		if(port > 0) {
			result->port = port;
			result->sock = listen(result->port);
		} else {
			/* SDL_net does not seem to have a way to listen on a random unused port
			   and find out which port that is, so let's try to find one ourselves. */
			srand(time(NULL));
			for(int i=0; !result->sock && i<1000; i++) {
				// IANA suggests using ports in the range 49152-65535 for things like this
				result->port = 49152+(rand()%(65536-49152));
				result->sock = listen(result->port);
			}
		}
		if(!result->sock) {
			flib_log_e("Failed to create acceptor.");
			free(result);
			result = NULL;
		}
	}
	return result;
}

uint16_t flib_acceptor_listenport(flib_acceptor *acceptor) {
	if(!acceptor) {
		flib_log_e("Call to flib_acceptor_listenport with acceptor==null");
		return 0;
	}
	return acceptor->port;
}

void flib_acceptor_close(flib_acceptor *acceptor) {
	if(acceptor) {
		SDLNet_TCP_Close(acceptor->sock);
		free(acceptor);
	}
}

flib_tcpsocket *flib_socket_accept(flib_acceptor *acceptor, bool localOnly) {
	flib_tcpsocket *result = NULL;
	if(!acceptor) {
		flib_log_e("Call to flib_socket_accept with acceptor==null");
	} else {
		TCPsocket sock = NULL;
		while(!result && (sock = SDLNet_TCP_Accept(acceptor->sock))) {
			if(localOnly && !connectionIsLocal(sock)) {
				flib_log_i("Rejected nonlocal connection attempt from %s", flib_format_ip(getPeerIp(sock)));
			} else {
				result = createSocket(sock);
			}
			if(!result) {
				SDLNet_TCP_Close(sock);
			}
		}
	}
	return result;
}

flib_tcpsocket *flib_socket_connect(const char *host, uint16_t port) {
	flib_tcpsocket *result = NULL;
	if(!host || port==0) {
		flib_log_e("Invalid parameter in flib_socket_connect");
	} else {
		IPaddress ip;
		if(SDLNet_ResolveHost(&ip,host,port)==-1) {
		   flib_log_e("SDLNet_ResolveHost: %s\n", SDLNet_GetError());
		} else {
			TCPsocket sock=SDLNet_TCP_Open(&ip);
			if(!sock) {
				flib_log_e("SDLNet_TCP_Open: %s\n", SDLNet_GetError());
			} else {
				result = createSocket(sock);
				if(result) {
					sock = NULL;
				}
			}
			SDLNet_TCP_Close(sock);
		}
	}
	return result;
}

void flib_socket_close(flib_tcpsocket *sock) {
	if(sock) {
		SDLNet_DelSocket(sock->sockset, (SDLNet_GenericSocket)sock->sock);
		SDLNet_TCP_Close(sock->sock);
		SDLNet_FreeSocketSet(sock->sockset);
		free(sock);
	}
}

int flib_socket_nbrecv(flib_tcpsocket *sock, void *data, int maxlen) {
	if(!sock || (maxlen>0 && !data)) {
		flib_log_e("Call to flib_socket_nbrecv with sock==null or data==null");
		return -1;
	}
	int readySockets = SDLNet_CheckSockets(sock->sockset, 0);
	if(readySockets>0) {
		int size = SDLNet_TCP_Recv(sock->sock, data, maxlen);
		return size>0 ? size : -1;
	} else if(readySockets==0) {
		return 0;
	} else {
		flib_log_e("Error in select system call: %s", SDLNet_GetError());
		return -1;
	}
}

int flib_socket_send(flib_tcpsocket *sock, const void *data, int len) {
	if(!sock || (len>0 && !data)) {
		flib_log_e("Call to flib_socket_send with sock==null or data==null");
		return -1;
	}
	return SDLNet_TCP_Send(sock->sock, data, len);
}