/* * network.c */ #include "copyright.h" #include "config.h" #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <sys/file.h> #include <sys/ioctl.h> #include <sys/wait.h> #include <signal.h> #include <errno.h> #include <netdb.h> #include <arpa/inet.h> #include "externs.h" struct listening_socket_t { int fd; struct sockaddr_storage addr; int salen; struct event ev; struct listening_socket_t *next; } *listening_sockets = NULL; DESC *descriptor_list = NULL; int network_init(int port); static void network_accept_client(int fd, short event, void *arg); static int network_bind_port(struct sockaddr *addr, int salen); static void network_make_nonblocking(int socket); static void network_make_blocking(int socket); DESC *network_initialize_socket(int socket, struct sockaddr *saddr, int addrlen); static int network_bind_port(struct sockaddr *addr, int salen) { int one = 1; struct listening_socket_t *lst = NULL; lst = malloc(sizeof(struct listening_socket_t)); memset(lst, 0, sizeof(struct listening_socket_t)); lst->fd = socket(addr->sa_family, SOCK_STREAM, 0); if(lst->fd < 0) { log_perror("NET", "FAIL", "network_bind_port", "socket"); goto error; } if(setsockopt(lst->fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { log_perror("NET", "WARN", "network_bind_port", "setsockopt"); } memcpy(&lst->addr, addr, salen); lst->salen = salen; if(bind(lst->fd, (struct sockaddr *) &lst->addr, lst->salen) < 0) { log_perror("NET", "FAIL", "network_bind_port", "bind"); goto error; } if(listen(lst->fd, 0) < 0) { log_perror("NET", "FAIL", "network_bind_port", "listen"); goto error; } network_make_nonblocking(lst->fd); event_set(&lst->ev, lst->fd, EV_READ | EV_PERSIST, network_accept_client, lst); event_add(&lst->ev, NULL); lst->next = listening_sockets; listening_sockets = lst; return 1; error: if(lst->fd >= 0) { close(lst->fd); } memset(lst, 0, sizeof(struct listening_socket_t)); return 0; } int network_init(int port) { struct addrinfo hints; struct addrinfo *res, *walk; char myservice[128]; char hostname[1025]; int error; snprintf(myservice, 32, "%d", port + 10); signal(SIGPIPE, SIG_IGN); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV | AI_ADDRCONFIG | AI_ALL | AI_V4MAPPED; hints.ai_socktype = SOCK_STREAM; error = getaddrinfo(NULL, myservice, &hints, &res); walk = res; if(error) perror(gai_strerror(error)); else { /* * "res" has a chain of addrinfo structure filled with * 0.0.0.0 (for IPv4), 0:0:0:0:0:0:0:0 (for IPv6) and alike, * with port filled for "myservice". */ while (walk) { if(getnameinfo(walk->ai_addr, walk->ai_addrlen, hostname, 1025, myservice, 128, NI_NUMERICSERV)) { log_printf("network] serious problem in getnameinfo()."); } if(network_bind_port(walk->ai_addr, walk->ai_addrlen) == 0) { log_printf("network] binding to %s %s %s failed.", (walk->ai_family == AF_INET6 ? "IPv6" : "IPv4"), hostname, myservice); } else { log_printf("network] bound to %s %s %s. ", (walk->ai_family == AF_INET6 ? "IPv6" : "IPv4"), hostname, myservice); } walk = walk->ai_next; } } freeaddrinfo(res); return 1; } static void network_make_nonblocking(int s) { long flags = 0; if(fcntl(s, F_GETFL, &flags) < 0) { log_perror("NET", "FAIL", "make_nonblocking", "fcntl F_GETFL"); } flags |= O_NONBLOCK; if(fcntl(s, F_SETFL, flags) < 0) { log_perror("NET", "FAIL", "make_nonblocking", "fcntl F_SETFL"); } flags = 1; if(setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags)) < 0) { log_perror("NET", "FAIL", "make_nonblocking", "setsockopt NDELAY"); } } static void network_make_blocking(int s) { long flags = 0; if(fcntl(s, F_GETFL, &flags) < 0) { log_perror("NET", "FAIL", "make_blocking", "fcntl F_GETFL"); } flags &= ~O_NONBLOCK; if(fcntl(s, F_SETFL, flags) < 0) { log_perror("NET", "FAIL", "make_blocking", "fcntl F_SETFL"); } flags = 0; if(setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags)) < 0) { log_perror("NET", "FAIL", "make_blocking", "setsockopt NDELAY"); } } /* choke_player: cork the player's sockets, must have a matching release_socket */ void network_choke_socket(DESC * desc) { int eins = 1; if(desc->chokes == 0) { #if defined(TCP_CORK) // Linux 2.4, 2.6 setsockopt(d->fd, IPPROTO_TCP, TCP_CORK, &eins, sizeof(eins)); #elif defined(TCP_NOPUSH) // *BSD, Mac OS X setsockopt(d->fd, IPPROTO_TCP, TCP_NOPUSH, &eins, sizeof(eins)); #else // else /* Nothing! */ #endif } desc->chokes++; } void network_release_socket(DESC * desc) { int null = 0; desc->chokes--; if(desc->chokes < 0) desc->chokes = 0; if(desc->chokes == 0) { #if defined(TCP_CORK) // Linux 2.4, 2.6 setsockopt(d->fd, IPPROTO_TCP, TCP_CORK, &null, sizeof(null)); #elif defined(TCP_NOPUSH) // *BSD, Mac OS X setsockopt(d->fd, IPPROTO_TCP, TCP_NOPUSH, &null, sizeof(null)); #else // else /* Nothing! */ #endif } } static void network_accept_client(int fd, short event, void *arg) { int newfd; struct sockaddr_storage addr; unsigned int addrlen = sizeof(struct sockaddr_storage); struct descriptor_data *desc; newfd = accept(fd, (struct sockaddr *) addr, &addrlen); if(newfd < 0) { log_printf("network] accept on %d failed with '%s'", fd, strerror(errno)); return; } desc = network_initialize_socket(newfd, addr, addrlen); log_printf("network] connection from %s %d", desc->ip, desc->port); } DESC *network_initialize_socket(int socket, struct sockaddr * addr, int addrlen) { char remotehost[NETWORK_HOSTNAME_MAX]; char remoteport[NETWORK_PORTNAME_MAX]; DESC *desc; desc = malloc(sizeof(DESC)); memset(desc, 0, sizeof(DESC)); desc->fd = socket; memcpy(desc->saddr, addr, addrlen); if(getnameinfo(addr, addrlen, remotehost, NETWORK_HOSTNAME_MAX, remoteport, NETWORK_PORTNAME_MAX, NI_NUMERICSERV | NI_NUMERICHOST)) { log_printf("network] getnameinfo failed!"); desc->ip = strdup("unspec"); desc->port = -1; } else { desc->ip = strdup(remotehost); desc->port = itoa(remoteport); } network_make_nonblocking(socket); desc->connected_at = mudstate.now; desc->retries_left = mudconf.retry_limit; desc->timeout = mudconf.idle_timeout; desc->host_info = 1; desc->next = descriptor_list; descriptor->list = desc; desc->sock_buff = bufferevent_new(desc->fd, network_write_callback, network_read_callback, network_error_callback, NULL); bufferevent_disable(desc->sock_buff, EV_READ); bufferevent_enable(desc->sock_buff, EV_WRITE); event_set(&desc->sock_ev, desc->fd, EV_READ | EV_PERSIST, network_accept_input, d); event_add(&desc->sock_ev, NULL); telnet_init(desc); welcome_user(desc); return desc; } /* network_client_input(); * * Called by libevent from event initialized in network_accept_client(); * * Theoretical data isolation would specify that we do the read and pass * the data to the telnet code. As an optimization, we allow telnet to * complete the read. */ static void network_client_input(int fd, short event, void *arg) { struct DESC *client = (DESC *) arg; int avail, net_length; dprintk("fd = %d, head = %d, tail = %d", client->fd, client->ringhead, client->ringtail); if(client->ringtail < client->ringhead) { avail = client->ringhead - client->ringtail; handle_errno(net_length = read(client->fd, client->ringbuffer + client->ringtail, avail)); } else { if(client->ringhead == 0) { avail = RING_LENGTH - client->ringtail; handle_errno(net_length = read(client->fd, client->ringbuffer + client->ringtail, avail)); client->ringtail = (client->ringtail + net_length) % RING_LENGTH; } else { avail = RING_LENGTH - client->ringtail; handle_errno(net_length = read(client->fd, client->ringbuffer + client->ringtail, avail)); client->ringtail = (client->ringtail + net_length) % RING_LENGTH; if(net_length && net_length == avail) { handle_errno(net_length += read(client->fd, client->ringbuffer, client->ringhead - 1)); client->ringtail += net_length - avail; } } } dprintk("net_length = %d", net_length); if(net_length == 0) { printf(" client disconnected.\n"); client_disconnect(client); return; } telnet_read_ring(client); } void network_write(DESC * client, char *buffer, int length) { bufferevent_write(d->sock_buff, buffer, length); client->output_tot += length; }