#include #include #include #include #include #include #include #include #include #include #include #include #include #define PROG_NAME "reverse_proxy" #define VERSION "0.1" #define DG(x...) { if (debug_on) { fprintf(stdout, x); } } #define DPF { if (pid_file) { unlink(pid_file); } } #define Exit(x) { DPF ; _exit(x); } #define Free(x) { free(x); x = 0; } #define OOM { fprintf(stderr, "Out of memory\n"); Exit(3); } #define Strdup(_new,_old) { _new = strdup(_old); if (!_new) OOM } #define MB1 1048576 #define DEFAULT_SRV_PORT 5110 #define DEFAULT_CLT_PORT 5112 #define Max(a,b) (((a)>(b))?a:b) #define Min(a,b) (((a)<(b))?a:b) #define DBUF { DG("mbuf : %u of %u bytes, start@%u %u long %s\n", \ mbuf_size, buffer_size, mbuf_start, mbuf_len, \ mbuf_start+mbuf_start>mbuf_size?"(cycled)":""); } #ifndef INFTIM #define INFTIM -1 #endif static void help(char *); static void detect_my_addr(void); static void version(void); static void forkme(void); static void wait_server(void); static int parse_host_port(char *, int *, char **, int); static void print_src(char *, struct sockaddr_in *, int); static void data_loop(void); static int write_full(int, void *, int); static void reestablish(int *, struct sockaddr_in *); static int debug_on = 0; static int use_udp = 0; static int discard = 0; static int always_bgr = 0; static int always_fgr = 0; static unsigned int buffer_size = MB1; static char * pid_file = (char*)NULL; static char * bind_srv = (char*)NULL; static int bind_srv_port = DEFAULT_SRV_PORT; static char * bind_clt = (char*)NULL; static int bind_clt_port = DEFAULT_CLT_PORT; static int srv_sock; static void * mbuf = 0; static unsigned int mbuf_start; static unsigned int mbuf_len; static unsigned int mbuf_size; int main(int argc, char ** argv) { int has_i = 0; int c; while ( ( c = getopt(argc, argv, "vbfmhutdi:r:s:p:")) != -1) { switch (c) { case 'm': detect_my_addr(); break; case 'h': help(argv[0]); Exit(0); break; case 'f': always_fgr = 1; break; case 'b': always_bgr = 1; break; case 'u': use_udp = 1; break; case 't': discard = 1; break; case 'd': debug_on = 1; break; case 'v': version(); Exit(0); case 'i': { char * aux; Strdup(aux, optarg); char * bux; int mul = 1; has_i = 1; if (!*aux) { fprintf(stderr, "invalid buffer size : %s\n", optarg); Exit(2); } switch (tolower(aux[strlen(aux)-1])) { case 'g': mul *= 1024; case 'm': mul *= 1024; case 'k': mul *= 1024; case 'b': aux[strlen(aux)-1] = 0; break; } if (!*aux) { fprintf(stderr, "invalid buffer size : %s\n", optarg); Exit(2); } buffer_size = strtol(aux, &bux, 10); if (*bux) { fprintf(stderr, "invalid buffer size : %s\n", optarg); Exit(2); } buffer_size *= mul; free(aux); } break; case 'r': { if (parse_host_port(optarg, &bind_srv_port, &bind_srv, DEFAULT_SRV_PORT)) { Exit(2); } } break; case 's': { if (parse_host_port(optarg, &bind_clt_port, &bind_clt, DEFAULT_CLT_PORT)) { Exit(2); } } break; case 'p': Strdup(pid_file, optarg); break; case ':': case '?': Exit(2); default: fprintf(stderr, "I'm sorry, but '%c' is not" " an option I know of\n", c); Exit(2); } } if (debug_on) { version(); } if (has_i && discard) { fprintf(stderr, "buffer size is ignored as -t was specified\n"); } if (always_bgr && always_fgr) { fprintf(stderr, "Can't accept both -b and -f\n"); Exit(2); } DG("Using %s protocol\n", use_udp?"UDP":"TCP"); if (discard) { DG("no buffer used, all unclaimed data will be discarded\n"); } else { DG("buffer size is %u bytes\n", buffer_size); } if (pid_file) { DG("Using pid file %s\n", pid_file); FILE * p = fopen(pid_file, "w"); fprintf(p, "%d\n", getpid()); fclose(p); DG("my PID is %d\n", getpid()); } if (always_bgr) { DG("Always background mode specified, forking...\n"); forkme(); } wait_server(); if (!always_bgr && !always_fgr) { DG("Server connection established, forking...\n"); forkme(); } data_loop(); return 0; } void help(char * prog) { char * dup = 0; #define Y(a,b...) fprintf(stdout, a "\n", b); #define X(a) fprintf(stdout, a "\n"); if (strchr(prog, '/')) { Strdup(dup, prog); prog = strrchr(dup, '/') + 1; if (!*prog) { // ended with slash??? if (prog == (dup+1)) { // just slash??? prog = PROG_NAME; dup = 0; } else { prog = strrchr(strrchr(dup, '/') - 1, '/') + 1; if (!prog) { // no other slash ??? prog = PROG_NAME; dup = 0; } else if (!*prog) { // ok, that's impossible fprintf(stderr, "having real troubles figuring my name...\n"); prog = PROG_NAME; dup = 0; } } } } Y("Usage: %s -m", prog); Y(" %s [-f | -b] [-u] [-t] [-d] [-i size] [-r [host:]port]", prog); X(" [-s [host:]port] [-p pid_file]"); Y(" %s -h | -v\n", prog); X(" -h : display this page, and exit"); X(" -m : display the IP address that server should connect to, and quit"); X(" -t : discard any input when client is not connected"); X(" -d : produce debug output"); X(" -i : specify maximum buffer size, in bytes. This option is ignored if"); X(" -t is specified. The default max buffer size is 1 Mb"); X(" the size parameter can be prefixed with B, K, M or G to specify"); X(" size in bytes, kilobytes, megabytes, or gigabytes explicitely"); X(" If the modifier is not specified, value is treated as bytes"); X(" Note, that the buffer size is not immediately allocated, so your"); X(" system may run out of memory if client doesn't connect and the"); X(" buffer value is too large"); X(" Also note that the size of the buffer is limited by your current"); X(" architecture"); X(" -u : use UDP instead of TCP (TCP is the default)"); X(" -f : don't go into background after server is connected"); X(" -r : specify optinal host and port to listen on for server connections"); X(" By default, server connections are expected on port 5110"); X(" and host is '*'"); X(" -p : absolute pathname for a pid file to create. The file is deleted"); X(" on program exit, unless it's killed with KILL"); X(" -s : specify optional host and port to listen on for client connections"); X(" By default, client connections are expected on port 5112"); X(" and host is '*'\n"); X("If started up without -h or -m, this program will wait for the server"); X("to connect. When the server is connected, it will go into background"); X("(unless -f option is specified)"); X("accepting connections from the client. Only one server connection is"); X("expected. Once a server connection terminates, this program will also"); X("terminate. Only one client connection is allowed at a time.\n"); X("If the server is sending bits while client is not connected,"); X("the data is kept in a buffer unless -t option is specified."); X("Should the buffer overflow, the oldest data is discarded.\n"); X("If the mode is UDP, then the server is considered connected after the"); X("first packet received by this program. The program will never terminate"); X("in UDP mode, unless there is a system error waiting for the packets."); X("In either TCP or UDP mode, the program is terminated if there any system"); X("error receiving the packets from the server. If there is an error"); X("talking to the client, the client is then considered disconnected.\n") X("Released by Pawel S. Veselov (pawel.veselov@xxxxxxxxx) 2008 under GPL"); #undef X if (dup) { free(dup); } } void detect_my_addr() { fprintf(stdout, "not implemented yet, sorry\n"); Exit(1); } void version() { fprintf(stdout, "%s version %s\n", PROG_NAME, VERSION); } void forkme() { pid_t pid; pid = fork(); if (pid == -1) { perror("fork()"); Exit(1); } if (pid == 0) { // I'm a child ! setsid(); } else { // I'm a parent ! if (pid_file) { DG("Updating pid file %s with PID %d\n", pid_file, pid); FILE * p = fopen(pid_file, "w"); fprintf(p, "%d\n", pid); fclose(p); } else { DG("Child PID is %d\n", pid); } Exit(0); } } void wait_server() { int type; int ss; struct sockaddr_in sin; struct hostent * hinfo; type = use_udp ? SOCK_DGRAM : SOCK_STREAM; ss = socket(PF_INET, type, 0); if (ss == -1) { perror("socket"); Exit(1); } DG("Server socket allocated, fd %d\n", ss); type = 1; if (setsockopt(ss, SOL_SOCKET, SO_REUSEADDR, (void*)&type, sizeof(int))) { perror("setsockopt(SO_REUSEADDR=1)"); } bzero(&sin, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = htons(bind_srv_port); if (bind_srv) { hinfo = gethostbyname(bind_srv); if (!hinfo || !hinfo->h_length) { fprintf(stderr, "Failed to resolve host %s\n", bind_srv); Exit(1); } memcpy(&sin.sin_addr, *(hinfo->h_addr_list), sizeof(sin.sin_addr)); } else { sin.sin_addr = inet_makeaddr(INADDR_ANY, INADDR_ANY); } if (bind(ss, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) { perror("bind"); close(ss); Exit(1); } DG("Server socket bound to %s:%d\n", bind_srv?bind_srv:"*", bind_srv_port); if (!use_udp) { listen(ss, 5); } srv_sock = ss; if (use_udp) { char buf; socklen_t srv_addr_len = sizeof(struct sockaddr_in); struct sockaddr_in srv_addr; while (1) { ssize_t l = recvfrom(ss, &buf, 1, MSG_PEEK, (struct sockaddr*)&srv_addr, &srv_addr_len); if (l == -1) { if (errno == EINTR || errno == EAGAIN) { continue; } perror("recv()"); close(ss); Exit(0); } if (l > 0) { break; } } print_src("UDP server", &srv_addr, srv_addr_len); if (connect(ss, (struct sockaddr*)&srv_addr, srv_addr_len)) { perror("connect() : connecting to server endpoint"); Exit(1); } } else { struct sockaddr_in remote; socklen_t slen = sizeof(remote); while (1) { int ns = accept(ss, (struct sockaddr*)&remote, &slen); if (ns == -1) { if (errno == EINTR) { continue; } perror("accept"); close(ss); Exit(1); } print_src("TCP server", &remote, slen); srv_sock = ns; break; } } } void print_src(char * prefix, struct sockaddr_in * sin, int len) { struct hostent * h = gethostbyaddr(sin, len, AF_INET); char * name; if (h && h->h_name) { name = h->h_name; } else { char * ip = inet_ntoa(sin->sin_addr); h = gethostbyname(ip); if (h && h->h_name) { name = h->h_name; } else { name = ip; } } fprintf(stdout, "%s: connected from %s\n", prefix, name); } int parse_host_port(char * str, int * port, char ** host, int def_port) { char * pstr; char * dstr = 0; struct hostent * hinfo; #define R(x) { if (dstr) { free(dstr); } ; return x; } if (!str || !*str) { // empty *port = def_port; *host = (char*)0; R(0); } Strdup(dstr, str); pstr = strchr(dstr, ':'); if (!pstr) { // no colon, so the string is pure port *port = strtol(dstr, &pstr, 10); if (*pstr || *port <= 0 || *port >= 65536 ) { // if parsing was incomplete, or if port value is 0 fprintf(stderr, "bad port number : %s\n", str); R(1); } R(0); } *pstr = 0; pstr++; if (*pstr) { char * aux; *port = strtol(pstr, &aux, 10); if (*aux || *port < 0 || *port >= 65536) { fprintf(stderr, "bad port number in %s\n", str); R(1); } } else { *port = def_port; } if (!*dstr) { *host = (char*)0; R(0); } hinfo = gethostbyname(dstr); if (!hinfo || (!hinfo->h_length)) { fprintf(stderr, "Failed to resolve %s from %s\n", dstr, str); R(1); } *host = strdup(dstr); R(0); #undef R } int write_full(int fd, void * buf, int len) { while (len) { int nr; nr = write(fd, buf, len>SSIZE_MAX?SSIZE_MAX:len); if (nr == -1) { if (errno == EAGAIN) { continue; } return len; } len -= nr; buf += nr; } return 0; } void reestablish(int * sock, struct sockaddr_in * baddr) { int aux = 1; close(*sock); if (!use_udp) { return; } *sock = socket(PF_INET, SOCK_DGRAM, 0); if (*sock == -1) { perror("socket"); Exit(1); } DG("Clent socket re-allocated, fd %d\n", *sock); if (setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, (void*)&aux, sizeof(int))) { perror("setsockopt(SO_REUSEADDR=1)"); } if (bind(*sock, (struct sockaddr*)baddr, sizeof(struct sockaddr_in))) { perror("bind"); Exit(1); } } void data_loop(void) { int type; int ss; struct sockaddr_in sin; struct hostent * hinfo; struct pollfd polls[2]; int has_clt = 0; // set up client listener type = use_udp ? SOCK_DGRAM : SOCK_STREAM; ss = socket(PF_INET, type, 0); if (ss == -1) { perror("socket"); Exit(1); } DG("Client socket allocated, fd %d\n", ss); type = 1; if (setsockopt(ss, SOL_SOCKET, SO_REUSEADDR, (void*)&type, sizeof(int))) { perror("setsockopt(SO_REUSEADDR=1)"); } bzero(&sin, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = htons(bind_clt_port); if (bind_clt) { hinfo = gethostbyname(bind_clt); if (!hinfo || !hinfo->h_length) { fprintf(stderr, "Failed to resolve host %s\n", bind_clt); Exit(1); } memcpy(&sin.sin_addr, *(hinfo->h_addr_list), sizeof(sin.sin_addr)); } else { sin.sin_addr = inet_makeaddr(INADDR_ANY, INADDR_ANY); } if (bind(ss, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) { perror("bind"); close(ss); Exit(1); } DG("Client socket bound to %s:%d\n", bind_clt?bind_clt:"*", bind_clt_port); if (use_udp) { polls[1].fd = ss; } else { listen(ss, 5); int flags = fcntl(ss, F_GETFL); if (flags == -1) { perror("fcntl(GETFL)"); Exit(1); } if (fcntl(ss, F_SETFL, flags | O_NONBLOCK) == -1) { perror("fcntl(SETFL)"); Exit(1); } } polls[0].fd = srv_sock; polls[0].events = POLLIN; while (1) { int rc; int len; int timeout; struct sockaddr_in remote; socklen_t rlen = sizeof(remote); if (!has_clt && !use_udp) { rc = accept(ss, (struct sockaddr*)&remote, &rlen); if (rc >= 0) { has_clt = 1; polls[1].fd = rc; print_src("TCP client", &remote, rlen); } else { if (errno != EWOULDBLOCK && errno != EAGAIN) { perror("accept()"); Exit(1); } } } if (!discard && has_clt && mbuf && mbuf_len) { int err = 0; // aha, we got something in the buffer // let's dump it out first if (mbuf_start == mbuf_size) { mbuf_start = 0; } int max_len = mbuf_size - mbuf_start; if (mbuf_len < max_len) { int rem = write_full(polls[1].fd, mbuf + mbuf_start, mbuf_len); DG("wrote out %d bytes from buffer\n", mbuf_len-rem); mbuf_start += mbuf_len - rem; mbuf_len = rem; DBUF; err = rem; } else { int rem = write_full(polls[1].fd, mbuf + mbuf_start, max_len); DG("wrote out %d bytes from buffer\n", max_len-rem); mbuf_start += max_len - rem; mbuf_len -= max_len - rem; DBUF; err = rem; if (!rem) { // we were successful in writing out // the bottom of the buffer mbuf_start = 0; rem = write_full(polls[1].fd, mbuf, mbuf_len); DG("wrote out %d bytes from buffer\n", mbuf_len - rem); mbuf_len = rem; mbuf_start += mbuf_len - rem; DBUF; err = rem; } } if (err) { if (debug_on) { perror("write(client)"); DG("The client socket is closed due to " "problems writing to the client"); } has_clt = 0; reestablish(&polls[1].fd, &sin); } } polls[1].revents = 0; if (has_clt || use_udp) { polls[1].events = POLLIN; timeout = INFTIM; len = 2; } else { polls[1].events = 0; len = 1; timeout = 1; } while (1) { rc = poll(polls, len, timeout); if (rc < 0) { if (errno == EINTR) { continue; } perror("poll"); Exit(1); } break; } // check if our server descriptor is still good. if (polls[0].revents & (POLLERR|POLLHUP|POLLNVAL)) { fprintf(stdout, "Server disconnected.\n"); Exit(0); } // do we have data on the client ? if (polls[1].revents & POLLIN) { char buf[1024]; int nr; if (use_udp && !has_clt) { // need to connect UDP socket now socklen_t clt_addr_len = sizeof(struct sockaddr_in); struct sockaddr_in clt_addr; nr = recvfrom(polls[1].fd, buf, 1024, 0, (struct sockaddr*)&clt_addr, &clt_addr_len); if (nr) { if (!connect(polls[1].fd, (struct sockaddr*)&clt_addr, clt_addr_len)) { has_clt = 1; print_src("UDP client", &clt_addr, clt_addr_len); } else { perror("connect"); } } } else { nr = read(polls[1].fd, buf, 1024); } DG("read %d bytes from the client\n", nr); if (nr < 0) { if (errno == EAGAIN) { continue; } perror("read"); DG("disconnecting client socket per read error\n"); has_clt = 0; reestablish(&polls[1].fd, &sin); } else if (nr > 0) { int nw = write_full(polls[0].fd, buf, nr); if (nw) { perror("write()"); fprintf(stderr, "Failed to send data to server, abort.\n"); Exit(1); } DG("wrote %d bytes to the server\n", nr); } else if (!nr) { DG("client disconnected - EOF received\n"); has_clt = 0; reestablish(&polls[1].fd, &sin); } } if (polls[1].revents & (POLLERR|POLLHUP|POLLNVAL)) { DG("client disconnected\n"); has_clt = 0; reestablish(&polls[1].fd, &sin); } if (polls[0].revents & POLLIN) { // server has data ! char buf[1024]; void * vbuf = buf; int err = !has_clt; int nr = read(polls[0].fd, buf, 1024); if (nr < 0) { if (errno == EAGAIN) { continue; } perror("read"); fprintf(stderr, "Error reading from server socket, abort.\n"); Exit(1); } else if (!nr) { fprintf(stderr, "EOF from the server, abort\n"); Exit(0); } DG("Read %d bytes from the server\n", nr); if (!err) { // we didn't have any problems writing the buffer out, // or did not have a buffer to write to // so let's try to write out to the client int rem = write_full(polls[1].fd, buf, nr); DG("Wrote %d bytes to the client\n", nr - rem); vbuf += nr - rem; nr = rem; if (rem) { DG("Failed to transmit bytes to the client, " "discarding client socket\n"); has_clt = 0; reestablish(&polls[1].fd, &sin); } } if (!discard && nr) { // something was left over :( unsigned int ptr, plen; if (!mbuf) { mbuf_size = Min(Max(nr, MB1), buffer_size); mbuf = malloc(mbuf_size); if (!mbuf) { OOM; } mbuf_start = 0; mbuf_len = 0; DG("buffer initialized\n"); DBUF; } // let's see if there is a room and a need to expand // the buffer. if (mbuf_size < buffer_size) { if (nr > (mbuf_size - mbuf_len)) { mbuf_size = Min(buffer_size, Max(mbuf_size + nr, mbuf_size + MB1)); mbuf = realloc(mbuf, mbuf_size); DG("buffer extended\n"); if (!mbuf) { OOM; } DBUF; } } // ok, we stretched the buffer to it's maximum, or // the buffer doesn't need more stretching. if (mbuf_start == mbuf_size) { mbuf_start = 0; } // defensive ptr = mbuf_start + mbuf_len; if (ptr > mbuf_size) { ptr -= mbuf_size; // cycled over the top } if (nr > mbuf_size) { // that's really sad. We don't have space to host the // whole buffer. Only the latest data will survive vbuf += (nr - mbuf_size); nr = mbuf_size; } mbuf_len += nr; if (mbuf_len > mbuf_size) { mbuf_len = mbuf_size; } plen = Min(nr, mbuf_size - ptr); memcpy(mbuf + ptr, vbuf, plen); // check did we roll over the start ? if (ptr < mbuf_start && ptr + plen > mbuf_start) { mbuf_start = ptr + plen; } DG("stored %d bytes into buffer\n", plen); DBUF; nr -= plen; vbuf += plen; if (nr > 0) { memcpy(mbuf, vbuf, nr); if (nr > mbuf_start) { mbuf_start = nr; } DG("stored %d bytes into buffer\n", nr); DBUF; } } } } }