/************************************************************************ Realms of Aurealis James Rhone aka Vall of RoA cli_route.c This file represents a small prog which runs along with the mud to gather client connection requests and route them to the mud via a local socket, each client connection will have a seperate process assoc. with it to eliminate stress on the main mud process. The mud will spawn off the master cli_router process during bootup passing it the port to monitor. The router will then monitor that port and upon connection attempt, it will fork off a child router and establish a unix socket connection to the main mud and begin routing data between the main mud and the client connection. The parent will continue to monitor the public client port (usually mud port+1). This design was adopted after a couple of other designs ended up not working out very well under heavier loads. This design allows the mud to assume that there will be hardly any delays between itself and clients. The lag will be felt at the router's end, if any exists. Previous designs called for the main mud to fork itself off everytime a client stream needed to be sent so the rest of the mud wouldnt be lagged. Forking a 20 meg process 8 or 9 times simultaneously while tracking memory with dmalloc is not a healthy thing to do and often confused dmallco to the point that it would give up. Thus, this the latest design I have come up with... ******** 100% Completely Original Code ******** *** BE AWARE OF ALL RIGHTS AND RESERVATIONS *** ******** Heavily modified and expanded ******** All rights reserved henceforth. Please note that no guarantees are associated with any code from Realms of Aurealis. All code which has been released to the general public has been done so with an 'as is' pretense. RoA is based on both Diku and CircleMUD and ALL licenses from both *MUST* be adhered to as well as the RoA license. *** Read, Learn, Understand, Improve *** *************************************************************************/ #include "sysdep.h" #include "conf.h" #include <sys/socket.h> #include <sys/wait.h> #include <sys/resource.h> #include <netinet/in.h> #include <arpa/telnet.h> #include <arpa/inet.h> #include <netdb.h> #include <signal.h> #include <errno.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/un.h> #include <unistd.h> #include "structures.h" #include "utils.h" #include "clicomm.h" // prototypes void watch(int port); int init_socket(int port); void nonblock(int s); int reaper(); int goodnight(); // globals... extern int errno; extern int MASTER_DESCRIPTOR; int mother; /* global mother descriptor */ BOOL done = FALSE; BOOL child = FALSE; char local_sockname[64]; char buf[MAX_STRING_LENGTH]; int main(int argc, char **argv) { int port; if (argc != 2) { fprintf(stderr, "%s: Usage: %s <port#>\n",*argv, *argv); exit(-1); } if ((port = atoi(argv[1])) <= 1024) { fprintf(stderr, "%s: Illegal port: %d\n", *argv, port); exit(-1); } // set up our af_unix socket name based on parent's pid... sprintf(local_sockname, "/tmp/rcli_sock_%d", getppid()); fprintf(stderr, "cli_router: Local socketname set to %s.\n", local_sockname); watch(port); return 1; } int reaper() { int status; int pid; log("SIGCHLD received.\n"); while ((pid = wait3(&status, WNOHANG, (struct rusage *)0)) > 0) fprintf(stderr, "cli_router: Child %d exited.\n", pid); // reset handler... signal (SIGCHLD, (void *) reaper); return 1; } int goodnight() { log("Signal received. Closing master descriptor and exiting.\n"); close(mother); if (child) { done = TRUE; return 1; } // kill all close all... exit(3); } // generic internet socket creation function... int passivesock(char *service, char *protocol, int qlen) { struct servent *pse; struct protoent *ppe; struct sockaddr_in sin; int s, type; u_short portbase = 0; bzero((char *)&sin, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; if ((pse = getservbyname(service, protocol))) sin.sin_port = htons(ntohs((u_short)pse->s_port) + portbase); else if ((sin.sin_port = htons((u_short)atoi(service))) == 0) { fprintf(stderr, "cli_router: Can't get %s service entry.\n",service); exit(-1); } if ((ppe = getprotobyname(protocol)) == 0) { fprintf(stderr, "cli_router: Can't get %s proto entry.\n",protocol); exit(-1); } if (!strcmp(protocol, "udp")) type = SOCK_DGRAM; else type = SOCK_STREAM; s = socket(PF_INET, type, ppe->p_proto); if (s < 0) { fprintf(stderr, "cli_router: Can't create socket.\n"); exit (-1); } if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) { fprintf(stderr, "cli_router: Can't bind socket.\n"); exit (-1); } if (type == SOCK_STREAM && listen(s, qlen) < 0) { fprintf(stderr, "cli_router: Can't listen on stream socket.\n"); exit (-1); } return s; } int passiveTCP(char *service, int qlen) { return passivesock(service, "tcp", qlen); } void log(char *buf) { fprintf(stderr, "cli_router: %s\n", buf); fflush(stderr); } int myread(int sok, char *trans, int length) { int cnt, sofar; sofar = 0; while (sofar < length) { cnt = read(sok, trans + sofar, length - sofar); if (cnt < 0) { if (errno == EAGAIN) { #ifdef DEBUG_MAX sprintf(buf, "%d out of %d bytes read...", sofar, length); log(buf); #endif continue; } else return cnt; } else if (!cnt) return cnt; sofar += cnt; #ifdef DEBUG_MAX sprintf(buf, "%d out of %d bytes read...", sofar, length); log(buf); #endif } return sofar; } int mywrite(int sok, char *trans, int length) { int cnt, sofar; sofar = 0; while (sofar < length) { cnt = write(sok, trans + sofar, length - sofar); if (cnt < 0) { if (errno == EAGAIN) { #ifdef DEBUG_MAX sprintf(buf, "%d out of %d bytes wrote...", sofar, length); log(buf); #endif continue; } else return cnt; } else if (!cnt) return cnt; sofar += cnt; #ifdef DEBUG_MAX sprintf(buf, "%d out of %d bytes wrote...", sofar, length); log(buf); #endif } return sofar; } // read in a data block from this descriptor // this allocs memory for the data block, BTW int read_dblock(void **blk_ptr, int desc) { int size; int nbytes; char *msg; void (*func)(); func = signal(SIGPIPE, SIG_IGN); errno = 0; // error check here... nbytes = myread(desc, (char *)&size, sizeof(int)); if (nbytes != sizeof(int)) { #ifdef DEBUG_MAX printf("ERROR: %d bytes out of %d read on desc: %d\n", nbytes, sizeof(int), desc); perror("hmm:"); #endif return -1; } #ifdef DEBUG_MAX sprintf(buf, "Allocating for %d size...", size); log(buf); #endif if (!size) { log("SYSERR: read_dblock, 0 bytes read."); return -1; } msg = (char *)malloc(size); if (!msg) { log("SYSERR: Read error in read_dblock...out of memory."); return -1; } nbytes = myread(desc, msg, size); if (nbytes != size) { #ifdef DEBUG_MAX printf("ERROR: %d bytes out of %d read on desc: %d\n", nbytes, size, desc); perror("hmm:"); #endif return -1; } // else... we read the right size... *blk_ptr = msg; signal(SIGPIPE, func); return nbytes; } // send a dblock over this descriptor int write_dblock(dblock *blk, int desc) { int size, bufsize, retval = -1, nbytes = 0; char *sndbuf; void (*func)(); func = signal(SIGPIPE, SIG_IGN); size = sizeof(dblock) + blk->streamlen; bufsize = size + sizeof(int); CREATE(sndbuf, char, bufsize); if (!sndbuf) { log("write_dblock out of memory...?"); return -1; } // assemble the three pieces... header, block, stream memcpy(sndbuf, (void *) &size, sizeof(int)); memcpy(&(sndbuf[sizeof(int)]), blk, sizeof(dblock)); memcpy(&(sndbuf[sizeof(int)+sizeof(dblock)]), blk->stream, blk->streamlen); nbytes = mywrite(desc, sndbuf, bufsize); if (nbytes != bufsize) { FREENULL(sndbuf); sprintf(buf, "SYSERR: %d bytes out of %d wrote on desc: %d\n", nbytes, bufsize, desc); log(buf); perror("write_dblock:"); return -1; } else retval = size; FREENULL(sndbuf); signal(SIGPIPE, func); return retval; } // have to deep free this one void wax_dblock(dblock *blk) { if (blk) { FREENULL(blk->stream); FREENULL(blk); } } // create an AF_UNIX socket connection to main mud listening to // socket file /tmp/roa_cli_socket (LOCAL_SOCKNAME) void interface_to_roa(int sok) { struct sockaddr_un myadr_un; struct hostent *sp; int addrlen, mud, cnt, opt, nread, nwrite; fd_set iset, eset; struct timeval time; int connected = FALSE; dblock *dblk; addrlen = sizeof(struct sockaddr_un); memset((char *)&myadr_un, 0, addrlen); myadr_un.sun_family = AF_UNIX; strcpy(myadr_un.sun_path, local_sockname); mud = socket(AF_UNIX, SOCK_STREAM, 0); if (mud < 0) { fprintf(stderr, "cli_router: Can't create AF_UNIX socket to main mud...\n"); exit(-1); } nonblock(sok); // basically, sit in a select until activity... time.tv_usec = 0; time.tv_sec = 0; // now select on both sockets... first one in the gate gets privs... while (!done) { FD_ZERO(&iset); FD_ZERO(&eset); FD_SET(mud, &iset); FD_SET(sok, &iset); if (select((mud > sok ? mud : sok) + 1, &iset, NULL, &eset, NULL) < 0) { fprintf(stderr, "cli_router: Error in select...\n"); exit(-1); } if (FD_ISSET(mud, &eset) || FD_ISSET(sok, &eset)) { fprintf(stderr, "cli_router: Socket in exception set, exiting.\n"); exit(-1); } // if the client has sent us something... if (FD_ISSET(sok, &iset)) { // read a chunk from the client cok if (read_dblock((void*)&dblk, sok) <= 0) { fprintf(stderr, "cli_router: Error reading from client sok, exiting.\n"); wax_dblock(dblk); exit(-1); } // if we have a stream, offset the pointer correctly... if (dblk->streamlen > 0) dblk->stream = (void *) &dblk[1]; if (!connected) { if (connect(mud, (struct sockaddr *)&myadr_un, addrlen) < 0) { fprintf(stderr, "cli_router: Can't connect to main mud unix socket...\n"); exit(-1); } nonblock(mud); connected = TRUE; fprintf(stderr, "cli_router: Child %d connected to main mud.\n", getpid()); } // now write that chunk to the mud... if (write_dblock(dblk, mud) <= 0) { fprintf(stderr, "cli_router: Error writing to mud, exiting.\n"); wax_dblock(dblk); exit(-1); } // ok, we send it on over, now destroy it wax_dblock(dblk); } // if the mud has sent us something... if (FD_ISSET(mud, &iset) && connected) { // read a chunk from the mud if (read_dblock((void*)&dblk, mud) <= 0) { fprintf(stderr, "cli_router: Error reading from mud, exiting.\n"); wax_dblock(dblk); exit(-1); } // if we have a stream, offset the pointer correctly... if (dblk->streamlen > 0) dblk->stream = (void *) &dblk[1]; // now write that dblock to the client... if (write_dblock(dblk, sok) <= 0) { log("Error writing to client sok, exiting."); wax_dblock(dblk); exit(-1); } // ok, we send it on over, now destroy it wax_dblock(dblk); } } // ok we must be done... close(sok); close(mud); fprintf(fprintf, "cli_router: Child %d connection closed.\n", getpid()); exit(3); } void watch(int port) { int con; signal (SIGCHLD, (void *) reaper); signal (SIGINT, (void *) goodnight); signal (SIGTERM, (void *) goodnight); signal (SIGHUP, (void *) goodnight); signal (SIGABRT, (void *) goodnight); mother = init_socket(port); fprintf(stderr, "cli_router: Master socket opened on port %d.\n",port); // sleep on accept... accept and fork off for (;;) { con = accept(mother, (struct sockaddr *) 0, 0); if (con < 0) continue; // if we're the child... if (!fork()) { fprintf(stderr, "cli_router: Connection detected, child %d forked.\n",getpid()); child = TRUE; // child ignores sig child signal (SIGCHLD, SIG_IGN); // no need for mother descriptor in child close(mother); // start gabbin with mud and client... interface_to_roa(con); close(con); exit(1); } else // parent just close the socket... close(con); } } // initialize the socket we're gonna listen to for client connections... // uses our own hostname... int init_socket(int port) { int s; char *opt; char hostname[1024]; struct sockaddr_in sa; struct hostent *hp; bzero(&sa, sizeof(struct sockaddr_in )); gethostname(hostname, 1023); hp = gethostbyname(hostname); if (!hp) { perror("gethostbyname"); exit(-1); } sa.sin_family = hp->h_addrtype; sa.sin_port = htons(port); s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) { perror("Init-socket"); exit(-1); } if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) < 0) { perror("bind"); close(s); exit(-1); } if (listen(s, 5) < 0) { perror("listen"); close(s); exit(-1); } return(s); } // stop a socket from blocking on read/write... void nonblock(int s) { int flags; flags = fcntl(s, F_GETFL); flags |= O_NONBLOCK; if (fcntl(s, F_SETFL, flags) < 0) { perror("Fatal error executing nonblock (comm.c)"); exit(1); } }