buldthensnip/network.c

1468 lines
32 KiB
C

/*
This file is part of Iceball.
Iceball 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 3 of the License, or
(at your option) any later version.
Iceball 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 Iceball. If not, see <http://www.gnu.org/licenses/>.
*/
#include "common.h"
#ifdef WIN32
WSADATA windows_sucks;
#endif
int server_sockfd_ipv4 = -1;
int server_sockfd_ipv6 = -1;
client_t to_server;
client_t to_client_local;
client_t to_clients[CLIENT_MAX];
// TODO: binary search tree
// SNIP: http://www.eyrie.org/~eagle/software/rra-c-util/
/*
* Replacement for a missing inet_ntop.
*
* Provides an implementation of inet_ntop that only supports IPv4 addresses
* for hosts that are missing it. If you want IPv6 support, you need to have
* a real inet_ntop function; this function is only provided so that code can
* call inet_ntop unconditionally without needing to worry about whether the
* host supports IPv6.
*
* The canonical version of this file is maintained in the rra-c-util package,
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Written by Russ Allbery <rra@stanford.edu>
*
* The authors hereby relinquish any claim to any copyright that they may have
* in this work, whether granted under contract or by operation of law or
* international treaty, and hereby commit to the public, at large, that they
* shall not, at any time in the future, seek to enforce any copyright in this
* work against any person or entity, or prevent any person or entity from
* copying, publishing, distributing or creating derivative works of this
* work.
*/
// Modified by GreaseMonkey, to support IPv6 and also to glue in nicely.
// This is also to be treated as public domain.
// ANYHOW, let's go.
// This may already be defined by the system headers.
#ifndef INET_ADDRSTRLEN
# define INET_ADDRSTRLEN 16
#endif
// Systems old enough to not support inet_ntop may not have this either.
#ifndef EAFNOSUPPORT
# define EAFNOSUPPORT EDOM
#endif
#if WIN32
const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt)
{
const uint8_t *p;
if (af == AF_INET)
{
if (cnt < INET_ADDRSTRLEN)
return NULL;
p = src;
snprintf(dst, cnt, "%u.%u.%u.%u",
(unsigned int) (p[0] & 0xff), (unsigned int) (p[1] & 0xff),
(unsigned int) (p[2] & 0xff), (unsigned int) (p[3] & 0xff));
} else if (af == AF_INET6) {
if (cnt < 5*8)
return NULL;
p = src;
snprintf(dst, cnt, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
(unsigned int) (p[0] & 0xff), (unsigned int) (p[1] & 0xff),
(unsigned int) (p[2] & 0xff), (unsigned int) (p[3] & 0xff),
(unsigned int) (p[4] & 0xff), (unsigned int) (p[5] & 0xff),
(unsigned int) (p[6] & 0xff), (unsigned int) (p[7] & 0xff),
(unsigned int) (p[8] & 0xff), (unsigned int) (p[9] & 0xff),
(unsigned int) (p[10] & 0xff), (unsigned int) (p[11] & 0xff),
(unsigned int) (p[12] & 0xff), (unsigned int) (p[13] & 0xff),
(unsigned int) (p[14] & 0xff), (unsigned int) (p[15] & 0xff));
} else {
return NULL;
}
return dst;
}
#endif
// END SNIP
int net_packet_push(int len, const char *data, int sockfd, packet_t **head, packet_t **tail)
{
if(len > PACKET_LEN_MAX)
{
fprintf(stderr, "net_packet_new: packet too large (%i > %i)\n"
, len, PACKET_LEN_MAX);
return 1;
}
if(len < 1)
{
fprintf(stderr, "net_packet_new: packet too small (%i < 1)\n"
, len);
return 1;
}
packet_t *pkt = malloc(sizeof(packet_t)+len);
if(pkt == NULL)
{
error_perror("net_packet_new");
return 1;
}
memcpy(pkt->data, data, len);
pkt->len = len;
pkt->sockfd = sockfd;
if(*head == NULL)
{
pkt->p = pkt->n = NULL;
*head = *tail = pkt;
} else {
(*tail)->n = pkt;
pkt->p = (*tail);
pkt->n = NULL;
*tail = pkt;
}
return 0;
}
int net_packet_push_lua(int len, const char *data, int sockfd, packet_t **head, packet_t **tail)
{
if(len+4 > PACKET_LEN_MAX)
{
fprintf(stderr, "net_packet_new: packet too large (%i > %i)\n"
, len, PACKET_LEN_MAX-4);
return 1;
}
if(len < 1)
{
fprintf(stderr, "net_packet_new: packet too small (%i < 1)\n"
, len);
return 1;
}
int poffs = (len >= 64 ? 3 : 1);
packet_t *pkt = malloc(sizeof(packet_t)+len+poffs);
if(pkt == NULL)
{
error_perror("net_packet_new");
return 1;
}
pkt->data[0] = (poffs == 1 ? len+0x3F : 0x7F);
if(poffs != 1)
{
pkt->data[1] = len&255;
pkt->data[2] = len>>8;
}
memcpy(poffs + pkt->data, data, len);
pkt->len = len + poffs;
pkt->sockfd = sockfd;
if(*head == NULL)
{
pkt->p = pkt->n = NULL;
*head = *tail = pkt;
} else {
(*tail)->n = pkt;
pkt->p = (*tail);
pkt->n = NULL;
*tail = pkt;
}
return 0;
}
packet_t *net_packet_pop(packet_t **head, packet_t **tail)
{
if(*head == NULL)
return NULL;
packet_t *pkt = *head;
*head = pkt->n;
if(*head == NULL)
*tail = NULL;
else
(*head)->p = NULL;
pkt->p = pkt->n = NULL;
return pkt;
}
void net_packet_free(packet_t *pkt, packet_t **head, packet_t **tail)
{
if(pkt->p != NULL)
pkt->p->n = pkt->n;
if(pkt->n != NULL)
pkt->n->p = pkt->p;
if(head != NULL && pkt == *head)
*head = pkt->n;
if(tail != NULL && pkt == *tail)
*tail = pkt->p;
free(pkt);
}
void net_deinit_client(client_t *cli)
{
while(cli->head != NULL)
net_packet_free(cli->head, &(cli->head), &(cli->tail));
while(cli->send_head != NULL)
net_packet_free(cli->send_head, &(cli->send_head), &(cli->send_tail));
cli->sockfd = -1;
}
void net_kick_sockfd_immediate(int sockfd, char *msg)
{
char buf[260];
buf[0] = 0x17;
buf[1] = strlen(msg);
memcpy(buf+2, msg, (int)(uint8_t)buf[1]);
buf[2+(int)(uint8_t)buf[1]] = 0x00;
fprintf(stderr, "KICK: %i \"%s\"\n", sockfd, msg);
// only send what's necessary
send(sockfd, buf, ((int)(uint8_t)buf[1])+1, 0);
// call hook_disconnect
{
lua_getglobal(lstate_server, "server");
lua_getfield(lstate_server, -1, "hook_disconnect");
lua_remove(lstate_server, -2);
if(!lua_isnil(lstate_server, -1))
{
if(sockfd >= 0)
lua_pushinteger(lstate_server, sockfd);
else
lua_pushboolean(lstate_server, 1);
lua_pushboolean(lstate_server, 1);
lua_pushstring(lstate_server, msg);
if(lua_pcall(lstate_server, 3, 0, 0) != 0)
{
printf("ERROR running server Lua (hook_disconnect): %s\n", lua_tostring(lstate_server, -1));
lua_pop(lstate_server, 1);
return;
}
} else {
lua_pop(lstate_server, 1);
}
}
// nuke it
close(sockfd);
}
void net_kick_client_immediate(client_t *cli, char *msg)
{
if(cli == &to_client_local)
{
fprintf(stderr, "KICK: local \"%s\"\n", msg);
fprintf(stderr, "PANIC: I don't know how to handle a local client kick yet!\n");
fflush(stderr);
abort();
}
if(cli == NULL)
return;
net_kick_sockfd_immediate(cli->sockfd, msg);
cli->sockfd = -1;
net_deinit_client(cli);
}
const char *net_aux_gettype_str(int ftype)
{
switch(ftype)
{
case UD_LUA:
return "lua";
case UD_MAP:
return "map";
case UD_MAP_ICEMAP:
return "icemap";
case UD_MAP_VXL:
return "vxl";
case UD_PMF:
return "pmf";
case UD_IMG:
case UD_IMG_TGA:
return "tga";
case UD_JSON:
return "json";
}
return NULL;
}
char *net_fetch_file(const char *fname, int *flen)
{
FILE *fp = fopen(fname, "rb");
if(fp == NULL)
{
perror("net_fetch_file");
return NULL;
}
int buf_len = 512;
int buf_pos = 0;
char *buf = malloc(buf_len+1);
// TODO: check if NULL
int buf_cpy;
while(!feof(fp))
{
int fetch_len = buf_len-buf_pos;
buf_cpy = fread(&buf[buf_pos], 1, fetch_len, fp);
if(buf_cpy == -1)
{
fclose(fp);
free(buf);
return NULL;
}
buf_pos += buf_cpy;
if(feof(fp))
break;
buf_len += (buf_len>>1)+1;
buf = realloc(buf, buf_len+1);
}
fclose(fp);
*flen = buf_pos;
buf[buf_pos] = '\0';
return buf;
}
void net_eat_c2s(client_t *cli)
{
int i;
// TODO: sanity checks / handle fatal errors correctly
packet_t *pkt, *npkt;
for(pkt = cli->head; pkt != NULL; pkt = npkt)
{
npkt = pkt->n;
// TODO: actually discern the source
client_t *other = &to_client_local;
switch(pkt->data[0])
{
case 0x30: {
// 0x30 flags namelen name[namelen] 0x00
// file transfer request
char *fname = pkt->data + 3;
int udtype = pkt->data[1] & 15;
const char *ftype = net_aux_gettype_str(udtype);
printf("file request: %02X %s \"%s\"\n",
udtype, (ftype == NULL ? "*ERROR*" : ftype), fname);
// check if we're allowed to fetch that
if(!path_type_server_readable(path_get_type(fname)))
{
// error! ignoring for now.
fprintf(stderr, "S->C transfer error: access denied\n");
net_packet_free(pkt, &(cli->head), &(cli->tail));
break;
}
// check if we have a file in the queue
if(other->sfetch_udtype != UD_INVALID)
{
// error! ignoring for now.
fprintf(stderr, "S->C transfer error: still sending file\n");
net_packet_free(pkt, &(cli->head), &(cli->tail));
break;
}
// k let's give this a whirl
// TODO: allow transferring of objects
other->sfetch_ubuf = net_fetch_file(fname, &(other->sfetch_ulen));
if(other->sfetch_ubuf != NULL)
{
other->sfetch_udtype = udtype;
uLongf cbound = compressBound(other->sfetch_ulen);
other->sfetch_cbuf = malloc(cbound);
// TODO: check if NULL
if(compress((Bytef *)(other->sfetch_cbuf), &cbound,
(Bytef *)(other->sfetch_ubuf), other->sfetch_ulen))
{
// abort
fprintf(stderr, "S->C transfer error: could not compress!\n");
if(other->sfetch_cbuf != NULL)
free(other->sfetch_cbuf);
free(other->sfetch_ubuf);
other->sfetch_cbuf = NULL;
other->sfetch_ubuf = NULL;
char buf[] = "\x35";
net_packet_push(1, buf, pkt->sockfd,
&(cli->send_head), &(cli->send_tail));
net_packet_free(pkt, &(cli->head), &(cli->tail));
break;
}
other->sfetch_clen = (int)cbound;
free(other->sfetch_ubuf);
other->sfetch_ubuf = NULL;
// assemble packets...
// initial packet
{
char buf[9];
buf[0] = 0x31;
*(uint32_t *)&buf[1] = other->sfetch_clen;
*(uint32_t *)&buf[5] = other->sfetch_ulen;
net_packet_push(9, buf, pkt->sockfd,
&(cli->send_head), &(cli->send_tail));
}
// data packets
{
char buf[1+4+2+1024];
buf[0] = 0x33;
for(i = 0; i < other->sfetch_clen; i += 1024)
{
int plen = other->sfetch_clen - i;
if(plen > 1024)
plen = 1024;
*(uint32_t *)&buf[1] = (uint32_t)i;
*(uint16_t *)&buf[5] = (uint16_t)plen;
memcpy(&buf[7], &(other->sfetch_cbuf[i]), plen);
net_packet_push(plen+7, buf, pkt->sockfd,
&(cli->send_head), &(cli->send_tail));
}
}
// success packet
{
char buf[1];
buf[0] = 0x32;
net_packet_push(1, buf, pkt->sockfd,
&(cli->send_head), &(cli->send_tail));
}
// all good!
free(other->sfetch_cbuf);
other->sfetch_cbuf = NULL;
other->sfetch_udtype = UD_INVALID;
} else {
// abort
char buf[] = "\x35";
net_packet_push(1, buf, pkt->sockfd,
&(cli->send_head), &(cli->send_tail));
}
net_packet_free(pkt, &(cli->head), &(cli->tail));
} break;
case 0x34: {
// 0x34:
// abort incoming file transfer
// TODO: actually abort
net_packet_free(pkt, &(cli->head), &(cli->tail));
} break;
default:
if(pkt->data[0] >= 0x40 && ((uint8_t)pkt->data[0]) <= 0x7F)
break;
net_packet_free(pkt, &(cli->head), &(cli->tail));
break;
}
}
}
void net_eat_s2c(client_t *cli)
{
// TODO: sanity checks / handle fatal errors correctly
packet_t *pkt, *npkt;
client_t *other = &to_server;
for(pkt = cli->head; pkt != NULL; pkt = npkt)
{
npkt = pkt->n;
switch(pkt->data[0])
{
case 0x0F: {
if(pkt->data[pkt->len-1] != '\x00')
{
fprintf(stderr, "ERROR: string not zero-terminated!\n");
} else if(mod_basedir == NULL) {
mod_basedir = strdup(2+pkt->data);
boot_mode |= 8;
printf("base dir = \"%s\"\n", mod_basedir);
} else {
fprintf(stderr, "ERROR: base dir already defined!\n");
// TODO: make this fatal
}
net_packet_free(pkt, &(cli->head), &(cli->tail));
} break;
case 0x31: {
// 0x31 clen.u32 ulen.u32:
// file transfer initiation
int clen = (int)*(uint32_t *)&(pkt->data[1]);
int ulen = (int)*(uint32_t *)&(pkt->data[5]);
//printf("clen=%i ulen=%i\n", clen, ulen);
cli->cfetch_clen = clen;
cli->cfetch_ulen = ulen;
cli->cfetch_cbuf = malloc(clen);
cli->cfetch_ubuf = NULL;
cli->cfetch_cpos = 0;
// TODO: check if NULL
net_packet_free(pkt, &(cli->head), &(cli->tail));
} break;
case 0x32: {
// 0x32:
// file transfer end
//printf("transfer END\n");
cli->cfetch_ubuf = malloc(cli->cfetch_ulen);
// TODO: check if NULL
uLongf dlen = cli->cfetch_ulen;
if(uncompress((Bytef *)(cli->cfetch_ubuf), &dlen,
(Bytef *)(cli->cfetch_cbuf), cli->cfetch_clen) != Z_OK)
{
fprintf(stderr, "FETCH ERROR: could not decompress!\n");
// TODO: make this fatal
free(cli->cfetch_cbuf);
free(cli->cfetch_ubuf);
cli->cfetch_cbuf = NULL;
cli->cfetch_ubuf = NULL;
net_packet_free(pkt, &(cli->head), &(cli->tail));
break;
}
free(cli->cfetch_cbuf);
cli->cfetch_cbuf = NULL;
net_packet_free(pkt, &(cli->head), &(cli->tail));
} break;
case 0x33: {
// 0x33: offset.u32 len.u16 data[len]:
// file transfer data
int offs = (int)*(uint32_t *)&(pkt->data[1]);
int plen = (int)*(uint16_t *)&(pkt->data[5]);
//printf("pdata %08X: %i bytes\n", offs, plen);
if(plen <= 0 || plen > 1024)
{
fprintf(stderr, "FETCH ERROR: length too long/short!\n");
} else if(offs < 0 || offs+plen > cli->cfetch_clen) {
fprintf(stderr, "FETCH ERROR: buffer overrun!\n");
// TODO: make this fatal
} else {
memcpy(offs + cli->cfetch_cbuf, &(pkt->data[7]), plen);
cli->cfetch_cpos = offs + plen;
}
net_packet_free(pkt, &(cli->head), &(cli->tail));
} break;
case 0x35: {
// 0x35:
// abort outgoing file transfer
//printf("abort transfer\n");
// TODO: actually abort
net_packet_free(pkt, &(cli->head), &(cli->tail));
} break;
default:
if(pkt->data[0] >= 0x40 && ((uint8_t)pkt->data[0]) <= 0x7F)
break;
net_packet_free(pkt, &(cli->head), &(cli->tail));
break;
}
}
}
int net_flush_parse_onepkt(const char *data, int len)
{
if(len <= 0)
return 0;
int cmd = data[0];
int ilen = 0;
if(cmd >= 0x40 && cmd <= 0x7E)
{
ilen = cmd-0x3E;
} else if(cmd == 0x7F) {
if(len < 4)
return 0;
ilen = (int)*(uint16_t *)&data[1];
ilen += 3;
} else switch(cmd) {
case 0x0F: // baselen base[baselen] 0x00:
case 0x17: // msglen msg[msglen] 0x00:
{
if(len < 2)
return 0;
ilen = (int)(uint8_t)data[1];
ilen += 3;
} break;
case 0x30: // flags namelen name[namelen] 0x00:
{
if(len < 4)
return 0;
ilen = (int)(uint8_t)data[2];
ilen += 4;
} break;
case 0x31: // clen.u32 ulen.u32:
ilen = 9;
break;
case 0x33: // offset.u32 len.u16 data[len]
{
if(len < 7)
return 0;
ilen = (int)*(uint16_t *)&data[5];
ilen += 7;
} break;
case 0x32:
case 0x34:
case 0x35:
ilen = 1;
break;
default:
// TODO: terminate cleanly instead of locking
return 0;
}
//printf("cmd=%02X ilen=%i blen=%i\n", cmd, ilen, len);
if(ilen > PACKET_LEN_MAX)
{
// TODO: terminate cleanly instead of locking
return 0;
}
return (ilen <= len ? ilen : 0);
}
void net_flush_parse_c2s(client_t *cli)
{
// TODO!
int offs = 0;
int len;
char *data;
len = cli->spkt_len;
data = cli->spkt_buf;
while(offs < len)
{
int nlen = net_flush_parse_onepkt(data+offs, len-offs);
//printf("nlen=%i\n",nlen);
if(nlen <= 0)
break;
net_packet_push(nlen, data+offs,
cli->sockfd, &(to_server.head), &(to_server.tail));
offs += nlen;
}
if(offs != 0)
{
//printf("offs=%i len=%i\n", offs, len);
if(offs < len)
memmove(data, data+offs, len-offs);
cli->spkt_len -= offs;
}
}
void net_flush_parse_s2c(client_t *cli)
{
int offs = 0;
int len;
char *data;
len = cli->rpkt_len;
data = cli->rpkt_buf;
while(offs < len)
{
int nlen = net_flush_parse_onepkt(data+offs, len-offs);
if(nlen <= 0)
break;
//printf("nlen=%i\n",nlen);
net_packet_push(nlen, data+offs,
cli->sockfd, &(cli->head), &(cli->tail));
offs += nlen;
}
if(offs != 0)
{
//printf("offs=%i len=%i\n", offs, len);
if(offs < len)
{
//printf("LET THE MOVE\n");
memmove(data, data+offs, len-offs);
}
cli->rpkt_len -= offs;
//printf("new len: %i\n", cli->rpkt_len);
}
}
client_t *net_find_sockfd(int sockfd)
{
int i;
client_t *cli;
if(sockfd == SOCKFD_LOCAL)
{
return &to_client_local;
} else if(sockfd >= 0) {
for(i = 0; i < CLIENT_MAX; i++)
if(to_clients[i].sockfd == sockfd)
return &to_clients[i];
} else {
return NULL;
}
return NULL;
}
client_t *net_alloc_sockfd(int sockfd)
{
int i;
client_t *cli = net_find_sockfd(sockfd);
if(cli != NULL)
return cli;
for(i = 0; i < CLIENT_MAX; i++)
{
if(to_clients[i].sockfd == -1)
{
to_clients[i].sockfd = sockfd;
return &to_clients[i];
}
}
return NULL;
}
int net_flush_transfer(client_t *cfrom, client_t *cto, packet_t *pfrom)
{
if(cto->isfull)
{
//printf("still full!\n");
return 1;
}
int len = (cto == &to_server ? cfrom->spkt_len : cto->rpkt_len);
if(pfrom->len + len > PACKET_LEN_MAX*2)
{
// TODO: send this somehow
//printf("FULL!\n");
cto->isfull = 1;
return 1;
} else {
if(pfrom->p != NULL)
pfrom->p->n = pfrom->n;
else
cfrom->send_head = pfrom->n;
if(pfrom->n != NULL)
pfrom->n->p = pfrom->p;
else
cfrom->send_tail = pfrom->p;
// here's the linkcopy version
/*
pfrom->n = NULL;
pfrom->p = NULL;
if(cto->tail == NULL)
{
cto->tail = cto->head = pfrom;
} else {
packet_t *p2 = cto->tail;
cto->tail = pfrom;
p2->n = pfrom;
pfrom->p = p2;
};
return 0;
*/
// and of course the serialised version:
if(cto == &to_server)
{
memcpy(cfrom->spkt_buf + cfrom->spkt_len, pfrom->data, pfrom->len);
cfrom->spkt_len += pfrom->len;
} else {
memcpy(cto->rpkt_buf + cto->rpkt_len, pfrom->data, pfrom->len);
cto->rpkt_len += pfrom->len;
}
}
return 0;
}
void net_flush_accept_one(int sockfd, struct sockaddr_storage *ss, socklen_t slen)
{
char xstr[128];
xstr[0] = '?';
xstr[1] = '\0';
int cport = 0;
switch(ss->ss_family)
{
case AF_INET:
inet_ntop(AF_INET, &(((struct sockaddr_in *)(ss))->sin_addr)
, xstr, 127);
cport = ((struct sockaddr_in *)(ss))->sin_port;
break;
case AF_INET6:
inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)(ss))->sin6_addr)
, xstr, 127);
cport = ((struct sockaddr_in6 *)(ss))->sin6_port;
break;
}
printf("connection from %s, port %i, family %i\n", xstr, cport, ss->ss_family);
// disable Nagle's algo
int yes = 1;
if(setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (void *)&yes, sizeof(yes)) == -1)
{
net_kick_sockfd_immediate(sockfd, "Could not disable Nagle's algorithm!"
" Kicked because gameplay will be complete shit otherwise.");
return;
}
// set connection nonblocking
yes = 1;
#ifdef WIN32
if(ioctlsocket(sockfd,FIONBIO,(void *)&yes) == -1) {
#else
if(fcntl(sockfd, F_SETFL,
fcntl(sockfd, F_GETFL) | O_NONBLOCK) == -1) {
#endif
net_kick_sockfd_immediate(sockfd, "Could not set up a nonblocking connection!");
return;
}
// get a slot
client_t *cli = net_alloc_sockfd(sockfd);
if(cli == NULL)
{
net_kick_sockfd_immediate(sockfd, "Server ran out of free slots!");
return;
}
// call hook_connect
lua_getglobal(lstate_server, "server");
lua_getfield(lstate_server, -1, "hook_connect");
lua_remove(lstate_server, -2);
if(!lua_isnil(lstate_server, -1))
{
lua_pushinteger(lstate_server, sockfd);
lua_newtable(lstate_server);
switch(ss->ss_family)
{
case AF_INET:
case AF_INET6:
if(ss->ss_family == AF_INET6)
lua_pushstring(lstate_server, "tcp/ip6");
else
lua_pushstring(lstate_server, "tcp/ip");
lua_setfield(lstate_server, -2, "proto");
lua_newtable(lstate_server);
lua_pushstring(lstate_server, xstr);
lua_setfield(lstate_server, -2, "ip");
lua_pushnil(lstate_server); // not supported yet!
lua_setfield(lstate_server, -2, "host");
lua_pushinteger(lstate_server, cport);
lua_setfield(lstate_server, -2, "cport");
lua_pushinteger(lstate_server, net_port);
lua_setfield(lstate_server, -2, "sport");
lua_setfield(lstate_server, -2, "addr");
break;
}
if(lua_pcall(lstate_server, 2, 0, 0) != 0)
{
printf("ERROR running server Lua (hook_connect): %s\n", lua_tostring(lstate_server, -1));
lua_pop(lstate_server, 2);
net_kick_sockfd_immediate(sockfd, "hook_connect failed on server");
return;
}
} else {
lua_pop(lstate_server, 1);
}
// send pkg basedir packet
{
char buf[260];
buf[0] = 0x0F;
buf[1] = strlen(mod_basedir);
memcpy(buf+2, mod_basedir, (int)(uint8_t)buf[1]);
buf[2+(int)(uint8_t)buf[1]] = 0x00;
net_packet_push(2+((int)(uint8_t)buf[1])+1, buf, sockfd,
&(to_server.send_head), &(to_server.send_tail));
}
}
void net_flush_accept(void)
{
if(server_sockfd_ipv6 != -1)
{
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
int sockfd = accept(server_sockfd_ipv6, (struct sockaddr *)&ss, &slen);
if(sockfd == -1)
{
#ifdef WIN32
int err = WSAGetLastError();
if(err != WSAEWOULDBLOCK)
#else
int err = errno;
if(err != EAGAIN && err != EWOULDBLOCK)
#endif
{
perror("net_flush_accept(accept.6)");
}
} else {
net_flush_accept_one(sockfd, &ss, slen);
}
}
if(server_sockfd_ipv4 != -1)
{
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
int sockfd = accept(server_sockfd_ipv4, (struct sockaddr *)&ss, &slen);
if(sockfd == -1)
{
#ifdef WIN32
int err = WSAGetLastError();
if(err != WSAEWOULDBLOCK)
#else
int err = errno;
if(err != EAGAIN && err != EWOULDBLOCK)
#endif
{
perror("net_flush_accept(accept.4)");
}
} else {
net_flush_accept_one(sockfd, &ss, slen);
}
}
}
void net_flush_snr(client_t *cli)
{
if(cli == &to_client_local)
{
if(boot_mode & 2)
{
// don't do anything, it's already in the buffer
return;
} else {
{
int bs = send(cli->sockfd, cli->spkt_buf,
cli->spkt_len, 0);
if(bs == -1)
{
#ifdef WIN32
int err = WSAGetLastError();
if(err != WSAEWOULDBLOCK)
#else
int err = errno;
if(err != EAGAIN && err != EWOULDBLOCK)
#endif
{
perror("net_flush_snr(client.send)");
net_kick_client_immediate(cli, "Error sending packet!");
return;
}
} else if(bs > 0) {
//printf("sent data! %i\n", bs);
cli->spkt_len -= bs;
memmove(cli->spkt_buf, cli->spkt_buf+bs, cli->spkt_len);
}
}
{
int bs = recv(cli->sockfd, cli->rpkt_buf+cli->rpkt_len,
PACKET_LEN_MAX*2-cli->rpkt_len, 0);
if(bs == -1)
{
#ifdef WIN32
int err = WSAGetLastError();
if(err != WSAEWOULDBLOCK)
#else
int err = errno;
if(err != EAGAIN && err != EWOULDBLOCK)
#endif
{
perror("net_flush_snr(client.recv)");
net_kick_client_immediate(cli, "Error receiving packet!");
return;
}
} else if(bs == 0) {
fprintf(stderr, "%i: recv: connection axed\n", cli->sockfd);
net_kick_client_immediate(cli, "Connection axed.");
return;
} else {
//printf("got data! %i\n", bs);
cli->rpkt_len += bs;
}
}
}
} else {
//printf("send sockfd %i %i %i\n", cli->sockfd, cli->rpkt_len, cli->rpkt_len);
{
int bs = send(cli->sockfd, cli->rpkt_buf,
cli->rpkt_len, 0);
if(bs == -1)
{
#ifdef WIN32
int err = WSAGetLastError();
if(err != WSAEWOULDBLOCK)
#else
int err = errno;
if(err != EAGAIN && err != EWOULDBLOCK)
#endif
{
perror("net_flush_snr(server.send)");
net_kick_client_immediate(cli, "Error sending packet!");
return;
}
} else if(bs > 0) {
//printf("server sent data! %i\n", bs);
cli->rpkt_len -= bs;
memmove(cli->rpkt_buf, cli->rpkt_buf+bs, cli->rpkt_len);
}
}
{
int bs = recv(cli->sockfd, cli->spkt_buf+cli->spkt_len,
PACKET_LEN_MAX*2-cli->spkt_len, 0);
if(bs == -1)
{
#ifdef WIN32
int err = WSAGetLastError();
if(err != WSAEWOULDBLOCK)
#else
int err = errno;
if(err != EAGAIN && err != EWOULDBLOCK)
#endif
{
perror("net_flush_snr(server.recv)");
net_kick_client_immediate(cli, "Error receiving packet!");
return;
}
} else if(bs == 0) {
fprintf(stderr, "%i: recv: connection axed\n", cli->sockfd);
net_kick_client_immediate(cli, "Connection axed.");
return;
} else {
//printf("server got data! %i\n", bs);
cli->spkt_len += bs;
}
}
}
}
void net_flush(void)
{
packet_t *pkt, *npkt;
int i;
if(boot_mode & 2)
{
net_flush_accept();
// clear full flags
for(i = 0; i < CLIENT_MAX; i++)
to_clients[i].isfull = 0;
to_client_local.isfull = 0;
to_server.isfull = 0;
// serialise the packets
for(pkt = to_server.send_head; pkt != NULL; pkt = npkt)
{
npkt = pkt->n;
//printf("pkt = %016llX\n", pkt);
client_t *cli = net_find_sockfd(pkt->sockfd);
if(cli == NULL)
{
fprintf(stderr, "EDOOFUS: given sockfd %i could not be found!\n"
, pkt->sockfd);
net_packet_free(pkt, &(to_server.send_head), &(to_server.send_tail));
//fflush(stderr);
//abort();
} else {
net_flush_transfer(&to_server, cli, pkt);
}
}
for(i = 0; i < CLIENT_MAX; i++)
if(to_clients[i].sockfd != -1)
net_flush_snr(&to_clients[i]);
// parse the incoming stuff
for(i = 0; i < CLIENT_MAX; i++)
if(to_clients[i].sockfd != -1)
net_flush_parse_c2s(&to_clients[i]);
net_flush_parse_c2s(&to_client_local);
}
if(boot_mode & 1)
{
to_server.isfull = 0;
for(pkt = to_client_local.send_head; pkt != NULL; pkt = npkt)
{
npkt = pkt->n;
net_flush_transfer(&to_client_local, &to_server, pkt);
}
net_flush_snr(&to_client_local);
net_flush_parse_s2c(&to_client_local);
}
if(boot_mode & 2)
net_eat_c2s(&to_server);
if(boot_mode & 1)
net_eat_s2c(&to_client_local);
}
int net_gethost(char *name, int port, struct sockaddr *sa, size_t alen)
{
char port_str[32];
struct addrinfo ainf;
memset(&ainf, 0, sizeof(ainf));
ainf.ai_flags = 0;
ainf.ai_family = AF_UNSPEC;
ainf.ai_socktype = SOCK_STREAM;
ainf.ai_protocol = 0;
snprintf(port_str, 31, "%i", net_port);
struct addrinfo *res;
int err = getaddrinfo(name, port_str, &ainf, &res);
if(err != 0)
{
fprintf(stderr, "net_gethost: %s\n", gai_strerror(err));
return 1;
}
struct addrinfo *best,*fol;
best = NULL;
for(fol = res; fol != NULL; fol = fol->ai_next)
{
char xstr[128];
xstr[0] = '?';
xstr[1] = '\0';
switch(fol->ai_family)
{
case AF_INET: {
inet_ntop(AF_INET, &(((struct sockaddr_in *)(fol->ai_addr))->sin_addr)
, xstr, 127);
} break;
case AF_INET6: {
inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)(fol->ai_addr))->sin6_addr)
, xstr, 127);
} break;
}
printf("lookup: %s\n", xstr);
// NOTE: prioritising IPv4 over IPv6.
if(best == NULL)
{
best = fol;
} else {
if(fol->ai_family == AF_INET && best->ai_family == AF_INET6)
best = fol;
}
}
memcpy(sa, best->ai_addr, best->ai_addrlen);
//freeaddrinfo(res);
return 0;
}
int net_connect(void)
{
switch(boot_mode & 3)
{
case 1: {
// client only
struct sockaddr_storage sa;
if(net_gethost(net_addr, net_port, (struct sockaddr *)&sa, sizeof(sa)))
return 1;
int sockfd = socket(sa.ss_family, SOCK_STREAM, 0);
if(sockfd == -1)
return error_perror("net_connect(socket)");
if(connect(sockfd, (struct sockaddr *)&sa, sizeof(sa)) == -1)
return error_perror("net_connect(connect)");
int yes = 1;
#ifdef WIN32
if(ioctlsocket(sockfd, FIONBIO, (void *)&yes))
#else
if(fcntl(sockfd, F_SETFL,
fcntl(sockfd, F_GETFL) | O_NONBLOCK))
#endif
return error_perror("net_connect(nonblock)");
if(setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (void *)&yes, sizeof(yes)) == -1)
return error_perror("net_connect(nodelay)");
to_client_local.sockfd = sockfd;
} break;
case 3: {
// client + server
to_client_local.sockfd = SOCKFD_LOCAL;
} break;
}
return 0;
}
void net_disconnect(void)
{
int i;
if(to_client_local.sockfd >= 0)
{
#ifdef WIN32
closesocket(to_client_local.sockfd);
#else
close(to_client_local.sockfd);
#endif
to_client_local.sockfd = -1;
}
for(i = 0; i < CLIENT_MAX; i++)
if(to_clients[i].sockfd >= 0)
{
#ifdef WIN32
closesocket(to_clients[i].sockfd);
#else
close(to_clients[i].sockfd);
#endif
to_clients[i].sockfd = -1;
}
}
int net_bind(void)
{
if(net_port == 0)
return 0;
struct sockaddr_in6 sa6;
struct sockaddr_in sa4;
server_sockfd_ipv6 = socket(AF_INET6, SOCK_STREAM, 0);
server_sockfd_ipv4 = socket(AF_INET, SOCK_STREAM, 0);
sa6.sin6_family = AF_INET6;
sa6.sin6_port = htons(net_port);
sa6.sin6_addr = in6addr_any;
sa6.sin6_flowinfo = 0;
sa6.sin6_scope_id = 0;
sa4.sin_family = AF_INET;
sa4.sin_port = htons(net_port);
sa4.sin_addr.s_addr = INADDR_ANY;
int yes = 1; // who the hell uses solaris anyway
int tflags;
if(setsockopt(server_sockfd_ipv6, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes)) == -1)
{
perror("net_bind(reuseaddr.6)");
server_sockfd_ipv6 = -1;
}
yes = 1;
if(setsockopt(server_sockfd_ipv4, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes)) == -1)
{
perror("net_bind(reuseaddr.4)");
server_sockfd_ipv4 = -1;
}
yes = 1;
if(server_sockfd_ipv6 != -1)
{
if(bind(server_sockfd_ipv6, (void *)&sa6, sizeof(sa6)) == -1)
{
perror("net_bind(bind.6)");
server_sockfd_ipv6 = -1;
} else if(listen(server_sockfd_ipv6, 5) == -1) {
perror("net_bind(listen.6)");
server_sockfd_ipv6 = -1;
#ifdef WIN32
} else if(ioctlsocket(server_sockfd_ipv6,FIONBIO,(void *)&yes)) {
#else
} else if(fcntl(server_sockfd_ipv6, F_SETFL,
fcntl(server_sockfd_ipv6, F_GETFL) | O_NONBLOCK)) {
#endif
perror("net_bind(nonblock.6)");
server_sockfd_ipv6 = -1;
}
}
yes = 1;
if(server_sockfd_ipv4 != -1)
{
if(bind(server_sockfd_ipv4, (void *)&sa4, sizeof(sa4)) == -1)
{
perror("net_bind(bind.4)");
server_sockfd_ipv4 = -1;
} else if(listen(server_sockfd_ipv4, 5) == -1) {
perror("net_bind(listen.4)");
server_sockfd_ipv4 = -1;
#ifdef WIN32
} else if(ioctlsocket(server_sockfd_ipv4,FIONBIO,(void *)&yes)) {
#else
} else if(fcntl(server_sockfd_ipv4, F_SETFL,
fcntl(server_sockfd_ipv4, F_GETFL) | O_NONBLOCK)) {
#endif
perror("net_bind(nonblock.4)");
server_sockfd_ipv4 = -1;
}
}
if(server_sockfd_ipv4 == -1 && server_sockfd_ipv6 == -1)
return 1;
printf("sockfds: IPv4 = %i, IPv6 = %i\n"
, server_sockfd_ipv4
, server_sockfd_ipv6);
return 0;
}
void net_unbind(void)
{
if(server_sockfd_ipv4 != -1)
{
close(server_sockfd_ipv4);
server_sockfd_ipv4 = -1;
}
if(server_sockfd_ipv6 != -1)
{
close(server_sockfd_ipv6);
server_sockfd_ipv6 = -1;
}
}
int net_init(void)
{
int i;
#ifdef WIN32
// complete hackjob
if(WSAStartup(MAKEWORD(2,0), &windows_sucks) != 0)
{
fprintf(stderr, "net_init: WSAStartup failed\n");
return 1;
}
#endif
for(i = 0; i < CLIENT_MAX; i++)
{
to_clients[i].sockfd = -1;
to_clients[i].head = to_clients[i].tail = NULL;
to_clients[i].send_head = to_clients[i].send_tail = NULL;
}
to_server.sockfd = -1;
to_server.head = to_server.tail = NULL;
to_server.send_head = to_server.send_tail = NULL;
to_client_local.sockfd = -1;
to_client_local.head = to_client_local.tail = NULL;
to_client_local.send_head = to_client_local.send_tail = NULL;
return 0;
}
void net_deinit(void)
{
int i;
net_deinit_client(&to_server);
net_deinit_client(&to_client_local);
for(i = 0; i < CLIENT_MAX; i++)
net_deinit_client(&(to_clients[i]));
#ifdef WIN32
WSACleanup();
#endif
}