X-Git-Url: http://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fmastersrv%2Fmaster.c;h=183d7454a8d2dc3601ddd2e3b2b9a9f1a1936e04;hb=8769066a9aa4f618a3a508b5aaca284ae85545af;hp=9b48afc9a4f091803ac449ab87f0dbd6fca59adf;hpb=b9aef1ff89ea1efc30b87b33b0f4209d6cf86dae;p=d2df-sdl.git diff --git a/src/mastersrv/master.c b/src/mastersrv/master.c index 9b48afc..183d745 100644 --- a/src/mastersrv/master.c +++ b/src/mastersrv/master.c @@ -1,3 +1,18 @@ +/* Copyright (C) Doom 2D: Forever Developers + * + * 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, version 3 of the License ONLY. + * + * 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, see . + */ + #include #include #include @@ -8,6 +23,7 @@ #include #include +#define ENET_DEBUG 1 #include #include @@ -21,8 +37,9 @@ #define DEFAULT_SPAM_CAP 10 #define DEFAULT_MAX_SERVERS MS_MAX_SERVERS #define DEFAULT_MAX_PER_HOST 4 -#define DEFAULT_TIMEOUT 100 -#define DEFAULT_BAN_TIME (3 * 86400) +#define DEFAULT_SERVER_TIMEOUT 100 +#define DEFAULT_CLIENT_TIMEOUT 3 +#define DEFAULT_SPAM_TIMEOUT 1 #define DEFAULT_PORT 25665 #define NET_BUFSIZE 65536 @@ -90,6 +107,7 @@ typedef struct server_s { char map[MAX_STRLEN + 2]; time_t death_time; time_t timestamp; + ENetPeer *peer; // who sent this server in } server_t; // real servers @@ -123,7 +141,9 @@ static ban_record_t *banlist; // settings static int ms_port = DEFAULT_PORT; -static int ms_timeout = DEFAULT_TIMEOUT; +static int ms_sv_timeout = DEFAULT_SERVER_TIMEOUT; +static int ms_cl_timeout = DEFAULT_CLIENT_TIMEOUT; +static int ms_spam_timeout = DEFAULT_SPAM_TIMEOUT; static int ms_spam_cap = DEFAULT_SPAM_CAP; static char ms_motd[MAX_STRLEN + 1] = ""; static char ms_urgent[MAX_STRLEN + 1] = ""; @@ -158,14 +178,14 @@ static const char *u_strtime(const time_t t) { static inline const char *u_logprefix(const enum log_severity_e s) { switch (s) { - case LOG_WARN: return "WARNING:"; - case LOG_ERROR: return "ERROR:"; + case LOG_WARN: return "WARNING: "; + case LOG_ERROR: return "ERROR: "; default: return ""; } } static void u_log(const enum log_severity_e severity, const char *fmt, ...) { - printf("[%s] %s ", u_strtime(time(NULL)), u_logprefix(severity)); + printf("[%s] %s", u_strtime(time(NULL)), u_logprefix(severity)); va_list args; va_start(args, fmt); vprintf(fmt, args); @@ -195,6 +215,17 @@ static bool u_strisprint(const char *str) { return true; } +static bool u_strisver(const char *str) { + if (!str || !*str) + return false; + for (const char *p = str; *p; ++p) { + // version strings consist of 0-9 . and space + if (!isdigit(*p) && *p != '.' && *p != ' ') + return false; + } + return true; +} + static const char *u_iptostr(const enet_uint32 host) { ENetAddress addr = { .host = host, .port = 0 }; char *buf = u_vabuf(); @@ -328,24 +359,32 @@ void b_write_server(enet_buf_t *buf, const server_t *s) { /* server functions */ -static void sv_remove(const enet_uint32 host, const enet_uint16 port) { - for (int i = 0; i < max_servers; ++i) { - if (servers[i].host == host && servers[i].port == port) { - servers[i].host = 0; - servers[i].port = 0; - --num_servers; +static inline void sv_remove(server_t *sv) { + if (sv->host) { + // drop the associated peer, if any + if (sv->peer && sv->peer->state == ENET_PEER_STATE_CONNECTED && sv->peer->data == sv) { + sv->peer->data = NULL; + enet_peer_reset(sv->peer); } + sv->host = 0; + sv->port = 0; + sv->peer = NULL; + --num_servers; + } +} + +static void sv_remove_by_addr(const enet_uint32 host, const enet_uint16 port) { + for (int i = 0; i < max_servers; ++i) { + if (servers[i].host == host && servers[i].port == port) + sv_remove(servers + i); } } static void sv_remove_by_host(enet_uint32 host, enet_uint32 mask) { host &= mask; for (int i = 0; i < max_servers; ++i) { - if (servers[i].host && (servers[i].host & mask) == host) { - servers[i].host = 0; - servers[i].port = 0; - --num_servers; - } + if (servers[i].host && (servers[i].host & mask) == host) + sv_remove(servers + i); } } @@ -383,6 +422,14 @@ static inline server_t *sv_find_or_add(const enet_uint32 host, const enet_uint32 return empty; } +static inline void sv_clear_peer(ENetPeer *peer) { + server_t *sv = peer->data; + if (sv) { + sv->peer = NULL; + peer->data = NULL; + } +} + /* ban list functions */ static inline time_t ban_get_time(const int cnt) { @@ -570,6 +617,7 @@ static void ban_add(const enet_uint32 host, const char *reason) { static inline void ban_peer(ENetPeer *peer, const char *reason) { if (peer) { ban_add(peer->address.host, reason); + sv_clear_peer(peer); enet_peer_reset(peer); } } @@ -638,8 +686,18 @@ static bool handle_msg(const enet_uint8 msgid, ENetPeer *peer) { return true; } // only then update the times - sv->death_time = now + ms_timeout; + sv->death_time = now + ms_sv_timeout; sv->timestamp = now; + // check if we're updating from a new peer + if (sv->peer != peer) { + // if there was an old one, kill it + if (sv->peer) { + sv->peer->data = NULL; + enet_peer_reset(sv->peer); + } + sv->peer = peer; + peer->data = sv; + } u_log(LOG_NOTE, "updated server #%d:", sv - servers); u_printsv(sv); } else { @@ -665,7 +723,7 @@ static bool handle_msg(const enet_uint8 msgid, ENetPeer *peer) { // then add that shit *sv = tmpsv; sv->host = peer->address.host; - sv->death_time = now + ms_timeout; + sv->death_time = now + ms_sv_timeout; sv->timestamp = now; if (!ban_sanity_check(sv)) { sv->host = 0; @@ -673,6 +731,8 @@ static bool handle_msg(const enet_uint8 msgid, ENetPeer *peer) { ban_peer(peer, "tripped sanity check"); return true; } + sv->peer = peer; + peer->data = sv; ++num_servers; u_log(LOG_NOTE, "added new server #%d:", sv - servers); u_printsv(sv); @@ -685,11 +745,15 @@ static bool handle_msg(const enet_uint8 msgid, ENetPeer *peer) { ban_peer(peer, "malformed MSG_RM"); return true; } - sv_remove(peer->address.host, tmpsv.port); + sv_remove_by_addr(peer->address.host, tmpsv.port); + // this peer can be disconnected pretty much immediately since he has no servers left, tell him to fuck off + sv_clear_peer(peer); + enet_peer_disconnect_later(peer, 0); return true; case NET_MSG_LIST: buf_send.pos = 0; + buf_send.overflow = 0; b_write_uint8(&buf_send, NET_MSG_LIST); clientver[0] = 0; @@ -709,6 +773,11 @@ static bool handle_msg(const enet_uint8 msgid, ENetPeer *peer) { return true; } + if (clientver[0] && !u_strisver(clientver)) { + ban_peer(peer, "malformed MSG_LIST clientver"); + return true; + } + for (int i = 0; i < max_servers; ++i) { if (servers[i].host) b_write_server(&buf_send, servers + i); @@ -725,7 +794,11 @@ static bool handle_msg(const enet_uint8 msgid, ENetPeer *peer) { ENetPacket *p = enet_packet_create(buf_send.data, buf_send.pos, ENET_PACKET_FLAG_RELIABLE); enet_peer_send(peer, NET_CH_MAIN, p); - enet_host_flush(ms_host); + // enet_host_flush(ms_host); + + // this peer can be disconnected pretty much immediately after receiving the server list, tell him to fuck off + sv_clear_peer(peer); + enet_peer_disconnect_later(peer, 0); u_log(LOG_NOTE, "sent server list to %s:%d (ver %s)", u_iptostr(peer->address.host), peer->address.port, clientver[0] ? clientver : ""); return true; @@ -742,10 +815,12 @@ static void print_usage(void) { printf("Available options:\n"); printf("-h show this message and exit\n"); printf("-p N listen on port N (default: %d)\n", DEFAULT_PORT); - printf("-t N seconds before server is removed from list (default: %d)\n", DEFAULT_TIMEOUT); + printf("-t N seconds before server is removed from list (default: %d)\n", DEFAULT_SERVER_TIMEOUT); + printf("-c N how long a client is allowed to hold the connection active (default: %d)\n", DEFAULT_CLIENT_TIMEOUT); printf("-s N max number of servers in server list, 1-%d (default: %d)\n", MS_MAX_SERVERS, DEFAULT_MAX_SERVERS); printf("-d N if N > 0, disallow more than N servers on the same IP (default: %d)\n", DEFAULT_MAX_PER_HOST); - printf("-f N crappy spam filter: ban people after they send N requests in a row too fast (default: %d)\n", DEFAULT_SPAM_CAP); + printf("-f N crappy spam filter: ban clients after they send N requests in a row too fast (default: %d)\n", DEFAULT_SPAM_CAP); + printf("-w N how often does a client have to send packets for the filter to kick in, i.e. once every N sec (default: %d)\n", DEFAULT_SPAM_TIMEOUT); fflush(stdout); } @@ -772,7 +847,7 @@ static bool parse_args(int argc, char **argv) { if (argc < 2) return true; - if (!strcmp(argv[0], "-h")) { + if (!strcmp(argv[1], "-h")) { print_usage(); return false; } @@ -780,10 +855,12 @@ static bool parse_args(int argc, char **argv) { for (int i = 1; i < argc; ++i) { const bool success = parse_int_arg(argc, argv, i, "-p", 1, 0xFFFF, &ms_port) - || parse_int_arg(argc, argv, i, "-t", 1, 0x7FFFFFFF, &ms_timeout) + || parse_int_arg(argc, argv, i, "-t", 1, 0x7FFFFFFF, &ms_sv_timeout) + || parse_int_arg(argc, argv, i, "-c", 1, 0x7FFFFFFF, &ms_cl_timeout) || parse_int_arg(argc, argv, i, "-s", 1, MS_MAX_SERVERS, &max_servers) || parse_int_arg(argc, argv, i, "-d", 0, MS_MAX_SERVERS, &max_servers_per_host) - || parse_int_arg(argc, argv, i, "-f", 0, 0xFFFF, &ms_spam_cap); + || parse_int_arg(argc, argv, i, "-f", 0, 0xFFFF, &ms_spam_cap) + || parse_int_arg(argc, argv, i, "-w", 1, 0x7FFFFFFF, &ms_spam_timeout); if (success) { ++i; } else { @@ -796,15 +873,15 @@ static bool parse_args(int argc, char **argv) { } // a stupid thing to filter sustained spam from a single IP -static bool spam_filter(ENetPeer *peer) { - const time_t now = time(NULL); +static bool spam_filter(ENetPeer *peer, const time_t now) { if (peer->address.host == cl_last_addr) { // spam === sending shit faster than once a second - if (now - cl_last_time < 1) { + if (now - cl_last_time < ms_spam_timeout) { if (cl_spam_cnt > 1) u_log(LOG_WARN, "address %s is sending packets too fast", u_iptostr(peer->address.host)); if (++cl_spam_cnt >= ms_spam_cap) { ban_peer(peer, "spam"); + cl_last_addr = 0; return true; } } else { @@ -818,6 +895,11 @@ static bool spam_filter(ENetPeer *peer) { return false; } +// filter incoming UDP packets before the protocol kicks in +static int packet_filter(ENetHost *host, ENetEvent *event) { + return !!ban_check(host->receivedAddress.host); +} + int main(int argc, char **argv) { if (enet_initialize() != 0) u_fatal("could not init enet"); @@ -848,63 +930,75 @@ int main(int argc, char **argv) { ENetAddress addr; addr.host = 0; addr.port = ms_port; - ms_host = enet_host_create(&addr, MS_MAX_CLIENTS, NET_CH_COUNT, 0, 0); + ms_host = enet_host_create(&addr, MS_MAX_CLIENTS, NET_CH_COUNT + 1, 0, 0); if (!ms_host) u_fatal("could not create enet host on port %d", ms_port); + ms_host->intercept = packet_filter; + bool running = true; enet_uint8 msgid = 0; ENetEvent event; while (running) { - while (enet_host_service(ms_host, &event, 5000) > 0) { - if (!event.peer) { - continue; // can this even happen? - } else if (ban_check(event.peer->address.host)) { - enet_peer_reset(event.peer); - continue; - } + while (enet_host_service(ms_host, &event, 10) > 0) { + const time_t now = time(NULL); + const bool filtered = !event.peer || (ms_spam_cap && spam_filter(event.peer, now)); + + if (!filtered) { + switch (event.type) { + case ENET_EVENT_TYPE_CONNECT: + u_log(LOG_NOTE, "%s:%d connected", u_iptostr(event.peer->address.host), event.peer->address.port); + if (event.peer->channelCount != NET_CH_COUNT) + ban_peer(event.peer, "what is this"); + else + enet_peer_timeout(event.peer, 0, 0, ms_cl_timeout * 1000); + break; + + case ENET_EVENT_TYPE_RECEIVE: + if (!event.packet || event.packet->dataLength == 0) { + ban_peer(event.peer, "empty packet"); + break; + } + // set up receive buffer + buf_recv.pos = 0; + buf_recv.overflow = 0; + buf_recv.data = event.packet->data; + buf_recv.size = event.packet->dataLength; + // read message id and handle the message + msgid = b_read_uint8(&buf_recv); + if (!handle_msg(msgid, event.peer)) { + // cheeky cunt sending invalid messages + ban_peer(event.peer, "unknown message"); + } + break; - if (event.type != ENET_EVENT_TYPE_DISCONNECT) - if (spam_filter(event.peer)) - continue; + case ENET_EVENT_TYPE_DISCONNECT: - switch (event.type) { - case ENET_EVENT_TYPE_CONNECT: - u_log(LOG_NOTE, "%s:%d connected", u_iptostr(event.peer->address.host), event.peer->address.port); - break; + // u_log(LOG_NOTE, "%s:%d disconnected", u_iptostr(event.peer->address.host), event.peer->address.port); + break; - case ENET_EVENT_TYPE_RECEIVE: - if (!event.packet || event.packet->dataLength == 0) { - ban_peer(event.peer, "empty packet"); + default: break; - } - // set up receive buffer - buf_recv.pos = 0; - buf_recv.overflow = 0; - buf_recv.data = event.packet->data; - buf_recv.size = event.packet->dataLength; - // read message id and handle the message - msgid = b_read_uint8(&buf_recv); - if (!handle_msg(msgid, event.peer)) { - // cheeky cunt sending invalid messages - ban_peer(event.peer, "unknown message"); - } - break; + } + } else if (event.peer) { + // u_log(LOG_WARN, "filtered event %d from %s", event.type, u_iptostr(event.peer->address.host)); + sv_clear_peer(event.peer); + enet_peer_reset(event.peer); + } - default: - break; + if (event.packet) { + buf_recv.data = NULL; + enet_packet_destroy(event.packet); } } const time_t now = time(NULL); + + // time out servers for (int i = 0; i < max_servers; ++i) { - if (servers[i].host) { - if (servers[i].death_time <= now) { - u_log(LOG_NOTE, "server #%d %s:%d timed out", i, u_iptostr(servers[i].host), servers[i].port); - servers[i].host = 0; - servers[i].port = 0; - --num_servers; - } + if (servers[i].host && servers[i].death_time <= now) { + u_log(LOG_NOTE, "server #%d %s:%d timed out", i, u_iptostr(servers[i].host), servers[i].port); + sv_remove(servers + i); } } }