/* hm_base - hearthmod base library Copyright (C) 2016 Filip Pancik 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 3 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, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // fcntl #include #include // close #include #include #include #include #include #include #define ASYNC_HANG_TIMEOUT 14 struct ht_s **async_clients; static unsigned long long client_index = 0; static int async_client_accept(struct conn_client_s *client); static void *connector_addclient(struct conn_server_s *cs, struct conn_client_s *cc); void shutdown_server(struct conn_server_s *cs) { struct conn_client_holder_s *c, *dc; struct hm_pool_s *p; hm_log(LOG_INFO, cs->log, "{Connector}: shutting down server, clients: %d", cs->clients); for(c = cs->clients_head; c != NULL; ) { if(c->signal_shutdown == 0 && c->client) { c->client->error_callback(c->client, CL_SERVERSHUTDOWN_ERR); } dc = c; c = c->next; hm_pfree(cs->pool, dc); } p = cs->pool; ev_io_stop(cs->loop, &cs->listener); connector_fd_close(cs->fd); hm_pfree(p, cs); } static void client_error(struct conn_client_s *c, enum clerr_e err) { async_shutdown_client(c); } int setnonblock(int fd) { int nb; nb = 1; return ioctl(fd, FIONBIO, &nb); } #ifdef HM_LOBBYSERVER static char read_byte(char **dst, const char *end) { char out; if(*dst + sizeof(char) > end) { return -1; } out = *((char *)(*dst)); (*dst)++; return out; } static int read_uint(char **dst, const char *end) { int num = 0, num2; int i; for(i = 0; (i < 5 && *dst < end); i++) { num2 = read_byte(dst, end); if(i == 4 && (num2 & 240) != 0) { abort(); } if((num2 & 128) == 0) { return num | (unsigned int)((unsigned int)num2 << 7 * i); } num |= (unsigned int)((unsigned int)(num2 & 127) << 7 * i); } return num; } static void recv_append_lobby(struct conn_client_s *c, char *src, int nsrc) { unsigned short h; int i, total; struct conn_server_s *cs = c->parent; if(nsrc < 2) { return; } hm_log(LOG_DEBUG, c->log, "{Connector}: Received %d bytes", nsrc); /** hbs lobby client */ if(nsrc > 12 && *(int *)src == -1) { hm_log(LOG_DEBUG, c->log, "{Connector}: HBS lobby client packet"); cs->recv(c, src, nsrc); return; } /** hbs game client */ hm_log(LOG_DEBUG, c->log, "{Connector}: HBS game client packet"); h = (unsigned char )src[0]; h <<= 8; h |= (unsigned char )src[1]; if(h == 0) { return; } h += 2; for(total = 0, i = 2; i < h; i++) { if(src[i] == 0x28 && (i + 1) < h) { char *ptr = &src[i + 1]; total = read_uint(&ptr, ptr + nsrc - h); break; } } total += h; cs->recv(c, src, total); if(total < nsrc) { hm_log(LOG_DEBUG, c->log, "{Connector}: Next packet of %d bytes prepared", nsrc - total); recv_append_lobby(c, src + total, nsrc - total); } } #endif // HM_LOBBYSERVER static void recv_append(struct conn_client_s *c) { #ifdef HM_GAMESERVER int sz; char *next; struct conn_server_s *cs = c->parent; next = rb_recv_read(&c->rb, &sz); rb_recv_pop(&c->rb); cs->recv(c, next, sz); #elif defined HM_LOBBYSERVER int sz; char *next; next = rb_recv_read(&c->rb, &sz); c->net_nbuf = sz; memcpy(c->net_buf, next, sz); rb_recv_pop(&c->rb); recv_append_lobby(c, c->net_buf, c->net_nbuf); #endif } static void connector_client(struct ev_loop *loop, ev_io *w, int revents) { (void) revents; struct sockaddr_storage addr; socklen_t sl = sizeof(addr); int client; struct conn_client_s *cc; struct conn_server_s *cs = w->data; assert(cs); client = accept(w->fd, (struct sockaddr *) &addr, &sl); if(client == -1) { switch (errno) { case EMFILE: hm_log(LOG_ERR, cs->log, "{Connector}: accept() failed; too many open files for this process"); break; case ENFILE: hm_log(LOG_ERR, cs->log, "{client} accept() failed; too many open files for this system"); break; default: assert(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN); break; } return; } int flag = 1; int ret = setsockopt(client, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)); if(ret == -1) { hm_log(LOG_ERR, cs->log, "{Connector}: Couldn't setsockopt on client (TCP_NODELAY)"); } #ifdef TCP_CWND int cwnd = 10; ret = setsockopt(client, IPPROTO_TCP, TCP_CWND, &cwnd, sizeof(cwnd)); if(ret == -1) { hm_log(LOG_ERR, cs->log, "Couldn't setsockopt on client (TCP_CWND)"); } #endif setnonblock(client); setkeepalive(client); #define PEER_NAME #ifdef PEER_NAME socklen_t len; struct sockaddr_storage paddr; char ipstr[INET6_ADDRSTRLEN]; int pport, peer; len = sizeof(paddr); peer = getpeername(client, (struct sockaddr*)&paddr, &len); // deal with both IPv4 and IPv6: if(paddr.ss_family == AF_INET) { struct sockaddr_in *s = (struct sockaddr_in *)&paddr; pport = ntohs(s->sin_port); inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof(ipstr)); } else { // AF_INET6 struct sockaddr_in6 *s = (struct sockaddr_in6 *)&paddr; pport = ntohs(s->sin6_port); inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof(ipstr)); } if(peer == -1) { hm_log(LOG_WARNING, cs->log, "{Connector}: Couldn't retrieve peer name"); } #endif cc = hm_palloc(cs->pool, sizeof(struct conn_client_s)); if(cc == NULL) { hm_log(LOG_WARNING, cs->log, "{Connector}: Memory allocation failed"); return; } memset(cc, 0, sizeof(struct conn_client_s)); cc->loop = loop; cc->fd = client; cc->pool = cs->pool; cc->log = cs->log; cc->read.data = cc; cc->write.data = cc; cc->parent = cs; cc->error_callback = client_error; cc->client_dc = cs->client_dc; #ifdef PEER_NAME cc->nip = strlen(ipstr); memcpy(cc->ip, ipstr, cc->nip); cc->port = pport; #endif if(connector_addclient(cs, cc) == NULL) { hm_pfree(cs->pool, cc); return; } cc->recv = recv_append; async_client_accept(cc); } int connector_server(struct conn_server_s *cs) { int t = 1; int timeout = 1; struct addrinfo *ai, hints; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; const int gai_err = getaddrinfo(cs->host, cs->port, &hints, &ai); if(gai_err != 0) { hm_log(LOG_CRIT, cs->log, "{Connector}: [%s]", gai_strerror(gai_err)); return -1; } cs->fd = socket(ai->ai_family, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP); if(cs->fd == -1) { hm_log(LOG_CRIT, cs->log, "{Connector}: socket() failed"); return -2; } hm_log(LOG_INFO, cs->log, "{Connector}: opening server on %s:%s fd: %d", cs->host, cs->port, cs->fd); #ifdef SO_REUSEADDR setsockopt(cs->fd, SOL_SOCKET, SO_REUSEADDR, &t, sizeof(int)); #endif #ifdef SO_REUSEPORT setsockopt(cs->fd, SOL_SOCKET, SO_REUSEPORT, &t, sizeof(int)); #endif if(bind(cs->fd, ai->ai_addr, ai->ai_addrlen)) { hm_log(LOG_CRIT, cs->log, "{Connector}: bind() failed"); return -3; } #if TCP_DEFER_ACCEPT setsockopt(cs->fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout, sizeof(int)); #endif freeaddrinfo(ai); listen(cs->fd, 128); ev_io_init(&cs->listener, connector_client, cs->fd, EV_READ); cs->listener.data = cs; ev_io_start(cs->loop, &cs->listener); return 0; } static void *connector_addclient(struct conn_server_s *cs, struct conn_client_s *cc) { struct conn_client_holder_s *cch; cch = hm_palloc(cs->pool, sizeof(*cch)); if(cch == NULL) { hm_log(LOG_WARNING, cs->log, "{Connector}: Memory allocation failed"); return NULL; } memset(cch, 0, sizeof(*cch)); cch->client = cc; cch->next = cs->clients_head; cc->shutdown_signal_holder = &cch->signal_shutdown; cs->clients_head = cch; cs->clients++; cc->holder = cch; hm_log(LOG_NOTICE, cs->log, "{Connector}: adding client %.*s:%d, total: %d", cc->nip, cc->ip, cc->port, cs->clients); return cch; } int async_shutdown_client(struct conn_client_s *c) { if(c == NULL) { return -1; } ev_io_stop(c->loop, &c->read); ev_io_stop(c->loop, &c->write); ev_timer_stop(c->loop, &c->hang_timer); /** pop in case it wasn't sent */ rb_send_pop(&c->rb, c->pool); /** let holder know we're about to shutdown */ if(c->shutdown_signal_holder) { *c->shutdown_signal_holder = 1; } if(c->monitor == 1 && c->parent != NULL) { c->parent->clients--; } hm_log(LOG_DEBUG, c->log, "{Connector}: async removing client %.*s:%d fd: %d type: %d alive since: %s", c->nip, c->ip, c->port, c->fd, c->type, c->date); connector_fd_close(c->fd); if(c->client_dc) { c->client_dc(c->data, c->foreign_client_index, c->hbs_id); } #ifdef HM_GAMESERVER HT_REM(async_clients, c->client_index, strlen(c->client_index), c->pool); #endif hm_pfree(c->pool, c); return 0; } void async_handle_socket_errno(struct conn_client_s *c) { if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { return; } if(c == NULL) { return; } if(errno == ECONNRESET) { hm_log(LOG_ERR, c->log, "{Connector} Connection reset by peer"); } else if(errno == ETIMEDOUT) { hm_log(LOG_ERR, c->log, "{Connector} Connection to backend timed out"); } else if(errno == EPIPE) { hm_log(LOG_ERR, c->log, "{Connector} Broken pipe to backend (EPIPE)"); } else { hm_log(LOG_ERR, c->log, "{Connector} errno: %d (%s)", errno, strerror(errno)); } } static void async_hang_timeout(struct ev_loop *loop, struct ev_timer *timer, int revents) { struct conn_client_s *c = (struct conn_client_s *)timer->data; assert(c); hm_log(LOG_DEBUG, c->log, "{Connector}: read timeout"); if(c->error_callback) { c->error_callback(c, CL_HANGTIMEOUT_ERR); } } #define NETDUMP(type, _buf, qq)\ int i;\ if(1 == 1) {\ if(type == 0) {\ hm_log(LOG_DEBUG, c->log, "sent: [%d] bytes %d", qq, fd);\ } else {\ hm_log(LOG_DEBUG, c->log, "received: [%d] bytes to fd %d", qq, fd);\ }\ printf("Ascii:\n--------\n");\ for(i = 0; i < qq; i++) {\ printf("%c", _buf[i] );\ }\ printf("Binary: ");\ for( i = 0; i < qq; i++ ) {\ printf("%.2x", (unsigned char)_buf[i] );\ }\ printf("\n");\ } static void async_read(struct ev_loop *loop, ev_io *w, int revents) { (void) revents; int t = 0; struct conn_client_s *c = (struct conn_client_s *)w->data; int fd = w->fd, used; char *buf = rb_recv_ptr(&c->rb, &used); if(c->want_shutdown) { if(c->error_callback) { c->error_callback(c, CL_WANTSHUTDOWN_ERR); } return; } hm_log(LOG_DEBUG, c->log, "Received from fd %d", fd); t = recv(fd, buf, RB_SLOT_SIZE - used, 0); if(t > 0) { //NETDUMP(1, buf, t) rb_recv_append(&c->rb, t); if(rb_recv_is_full(&c->rb)) { ev_io_stop(c->loop, &c->read); if(c->error_callback) { c->error_callback(c, CL_READRBFULL_ERR); } return; } if(c->hang_timer_enabled == 1) { ev_timer_stop(c->loop, &c->hang_timer); ev_timer_again(c->loop, &c->hang_timer); } c->recv(c); } else if(t == 0) { async_handle_socket_errno(c); if(c->error_callback) { c->error_callback(c, CL_READZERO_ERR); } } else { async_handle_socket_errno(c); if(c->error_callback) { c->error_callback(c, CL_READ_ERR); } } } static void async_write(struct ev_loop *loop, ev_io *w, int revents) { (void)revents; int t; struct conn_client_s *c = (struct conn_client_s *)w->data; int fd = w->fd; int sz; assert(c); char *next = rb_send_next(&c->rb, &sz); if(sz == 0) { ev_io_stop(loop, &c->write); return; } t = send(fd, next, sz, MSG_NOSIGNAL); if(t > 0) { //NETDUMP(0, next, t) rb_send_skip(&c->rb, t); if(rb_send_is_empty(&c->rb)) { ev_io_stop(loop, &c->write); } } else { //assert(t == -1); async_handle_socket_errno(c); if(c->error_callback) { c->error_callback(c, CL_WRITE_ERR); } } } static void timestring(char *b, const int nb) { char buf[128]; time_t s; struct timespec spec; long long ms; struct tm ts; clock_gettime(CLOCK_REALTIME, &spec); s = spec.tv_sec; ms = round(spec.tv_nsec / 1.0e6); ts = *localtime(&s); strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &ts); snprintf(b, nb, "[%s.%03lld] ", buf, ms); } int async_client(struct conn_client_s *client, const int monitor, const int hang) { struct sockaddr_in servaddr; char ip[32]; assert(client); /** TCP & non-blocking */ client->fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); if(-1 == client->fd) { hm_log(LOG_ERR, client->log, "{Connector}: node init socket error: %d", client->fd); client->error_callback(client, CL_SOCKET_ERR); return -1; } snprintf(ip, sizeof(ip), "%.*s", client->nip, client->ip); setkeepalive(client->fd); memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr(ip); servaddr.sin_port = htons(client->port); ev_io_init(&client->write, async_write, client->fd, EV_WRITE); ev_io_init(&client->read, async_read, client->fd, EV_READ); ev_init(&client->hang_timer, async_hang_timeout); if(hang == 1) { client->hang_timer.repeat = ASYNC_HANG_TIMEOUT; client->hang_timer.data = client; client->hang_timer_enabled = 1; } else { client->hang_timer_enabled = 0; } client->read.data = client; client->write.data = client; timestring(client->date, sizeof(client->date)); if(monitor == 1) { connector_addclient(client->parent, client); client->monitor = 1; } ev_io_start(client->loop, &client->read); ev_timer_again(client->loop, &client->hang_timer); if(connect(client->fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != -1 && errno != EINPROGRESS) { async_shutdown_client(client); hm_log(LOG_ERR, client->log, "{Connector}: connect() errno: %d", errno); return -1; } return 0; } static int async_client_accept(struct conn_client_s *client) { ev_io_init(&client->write, async_write, client->fd, EV_WRITE); ev_io_init(&client->read, async_read, client->fd, EV_READ); //ev_init(&client->hang_timer, async_hang_timeout); client->hang_timer.repeat = ASYNC_HANG_TIMEOUT; //client->hang_timer.data = client; client->read.data = client; client->write.data = client; ev_io_start(client->loop, &client->read); timestring(client->date, sizeof(client->date)); //ev_timer_again(client->loop, &client->hang_timer); snprintf(client->client_index, sizeof(client->client_index), "%lld", client_index++); #ifdef HM_GAMESERVER HT_ADD_WA(async_clients, client->client_index, strlen(client->client_index), client, sizeof(client), client->pool); #endif return 0; }