/************************************************************************
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);
}
}