diff --git a/Makefile b/Makefile index 2b1d09d..21d7b73 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ # warning: do not set -O to 2, see TODO CFLAGS = -Wall -Wextra -Werror -O1 -g LDFLAGS= -OBJS = client.o net.o udpxd.o +OBJS = host.o client.o net.o udpxd.o DST = udpxd PREFIX = /usr/local UID = root diff --git a/client.c b/client.c index 86d34f2..fa2e24b 100644 --- a/client.c +++ b/client.c @@ -35,10 +35,10 @@ client_t *client_find_fd(int fd) { return client; /* maybe NULL! */ } -client_t *client_find_src(struct sockaddr_in *src) { +client_t *client_find_src(host_t *src) { client_t *current = NULL; client_iter(clients, current) { - if (current->src == src) + if(strcmp(current->src->ip, src->ip) == 0 && current->src->port == src->port) return current; } return NULL; @@ -48,7 +48,7 @@ void client_seen(client_t *client) { client->lastseen = (long)time(0); } -client_t *client_new(int fd, struct sockaddr_in *src, struct sockaddr_in *dst) { +client_t *client_new(int fd, host_t *src, host_t *dst) { client_t *client = malloc(sizeof(client_t)); client->socket = fd; client->src = src; @@ -73,10 +73,8 @@ void client_clean() { diff = now - current->lastseen; if(diff >= MAXAGE) { if(VERBOSE) { - char *srcip = inet_ntoa(current->src->sin_addr); - char *bindip = inet_ntoa(current->dst->sin_addr); fprintf(stderr, "closing socket %s:%d for client %s:%d (aged out after %d seconds)\n", - srcip, ntohs(current->src->sin_port), bindip, ntohs(current->dst->sin_port), MAXAGE); + current->src->ip, current->src->port, current->dst->ip, current->dst->port, MAXAGE); } client_close(current); } diff --git a/client.h b/client.h index 4848f2b..105a439 100644 --- a/client.h +++ b/client.h @@ -39,14 +39,16 @@ typedef uint8_t byte; #endif #include "uthash.h" +#include "host.h" #define MAXAGE 3600 /* seconds after which to close outgoing sockets and forget client src */ struct _client_t { int socket; /* bind socket for outgoing traffic */ - struct sockaddr_in *src; /* client src (ip+port) from incoming socket */ - struct sockaddr_in *dst; /* client dst (ip+port) to outgoing socket */ + host_t *src; /* client src (ip+port) from incoming socket */ + host_t *dst; /* client dst (ip+port) to outgoing socket */ uint64_t lastseen; /* when did we recv last time from it */ + size_t size; /* sockaddr size */ UT_hash_handle hh; }; typedef struct _client_t client_t; @@ -78,8 +80,8 @@ void client_close(client_t *client); void client_clean(); client_t *client_find_fd(int fd); -client_t *client_find_src(struct sockaddr_in *src); -client_t *client_new(int fd, struct sockaddr_in *src, struct sockaddr_in *dst); +client_t *client_find_src(host_t *src); +client_t *client_new(int fd, host_t *src, host_t *dst); #endif diff --git a/net.c b/net.c index 32df7ba..a5b8bea 100644 --- a/net.c +++ b/net.c @@ -21,6 +21,8 @@ #include "net.h" #include "client.h" +#include "host.h" + char *ntoa(struct sockaddr_in *src) { @@ -29,6 +31,7 @@ char *ntoa(struct sockaddr_in *src) { return ip; } + /* called each time when the loop restarts to feed select() correctly */ int fill_set(fd_set *fds) { int max = 0; @@ -58,57 +61,106 @@ int get_sender(fd_set *fds) { return i; } + /* bind to a socket, either for listen() or for outgoing src ip binding */ -int bindsocket( char* ip, int port ) { +int bindsocket( host_t *sock_h) { int fd; - struct sockaddr_in addr; + int err = 0; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = inet_addr( ip ); - addr.sin_port = htons( port ); + if(sock_h->is_v6) { + fd = socket( PF_INET6, SOCK_DGRAM, IPPROTO_UDP ); + } + else { + fd = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP ); + } - fd = socket( PF_INET, SOCK_DGRAM, IPPROTO_IP ); - if( -1 == bind( fd, (struct sockaddr*)&addr, sizeof( addr ) ) ) { - fprintf( stderr, "Cannot bind address (%s:%d)\n", ip, port ); - exit( 1 ); + if( -1 == bind( fd, (struct sockaddr*)sock_h->sock, sock_h->size ) ) { + err = 1; + } + + if(err) { + fprintf( stderr, "Cannot bind address ([%s]:%d)\n", sock_h->ip, sock_h->port ); + perror(NULL); + return -1; } return fd; } -/* handle new or known incoming requests */ -void handle_inside(int inside, char *bindip, struct sockaddr_in *dst) { - int len; - unsigned char buffer[MAX_BUFFER_SIZE]; - struct sockaddr_in *src; - client_t *client; - int output; - char *srcip; - char *dstip = ntoa(dst); - size_t size = sizeof(struct sockaddr_in); - src = malloc(size); - - len = recvfrom( inside, buffer, sizeof( buffer ), 0, (struct sockaddr*)src, (socklen_t *)&size ); - srcip = ntoa(src); +int start_listener (char *inip, char *inpt, char *srcip, char *dstip, char *dstpt) { + host_t *listen_h = get_host(inip, atoi(inpt), NULL, NULL); + host_t *dst_h = get_host(dstip, atoi(dstpt), NULL, NULL); + host_t *bind_h = NULL; + + if(srcip != NULL) { + bind_h = get_host(srcip, 0, NULL, NULL); + } + else { + if(dst_h->is_v6) + bind_h = get_host("::0", 0, NULL, NULL); + else + bind_h = get_host("0.0.0.0", 0, NULL, NULL); + } + + int listen = bindsocket(listen_h); + + if(listen == -1) + return 1; + + if(VERBOSE) { + fprintf(stderr, "Listening on %s:%s, forwarding to %s:%s", + inip, inpt, dstip, dstpt); + if(srcip != NULL) + fprintf(stderr, ", binding to %s\n", srcip); + else + fprintf(stderr, "\n"); + } + + main_loop(listen, listen_h, bind_h, dst_h); + + return 0; +} + +/* handle new or known incoming requests + FIXME: check client handling: + http://long.ccaba.upc.es/long/045Guidelines/eva/ipv6.html#daytimeServer6 +*/ +void handle_inside(int inside, host_t *listen_h, host_t *bind_h, host_t *dst_h) { + int len; + unsigned char buffer[MAX_BUFFER_SIZE]; + void *src; + client_t *client; + host_t *src_h; + int output; + size_t size = listen_h->size; + + src = malloc(size); + + len = recvfrom( inside, buffer, sizeof( buffer ), 0, + (struct sockaddr*)src, (socklen_t *)&size ); + + if(listen_h->is_v6) + src_h = get_host(NULL, 0, NULL, (struct sockaddr_in6 *)src); + else + src_h = get_host(NULL, 0, (struct sockaddr_in *)src, NULL); if(VERBOSE) { - char *srcip = ntoa(src); fprintf(stderr, "New incomming request from %s:%d with %d bytes\n", - srcip, ntohs(src->sin_port), len); + src_h->ip, src_h->port, len); } if(len > 0) { /* do we know it ? */ - client = client_find_src(src); + client = client_find_src(src_h); if(client != NULL) { /* yes, we know it, send req out via existing bind socket */ if(VERBOSE) { fprintf(stderr, "Client %s:%d is known, forwarding data to %s:%d\n", - srcip, ntohs(src->sin_port), dstip, ntohs(dst->sin_port)); - + src_h->ip, src_h->port, dst_h->ip, dst_h->port); + } - if(sendto(client->socket, buffer, len, 0, (struct sockaddr*)dst, size) < 0) { - fprintf(stderr, "unable to forward to %s:%d\n", dstip, ntohs(dst->sin_port)); + if(sendto(client->socket, buffer, len, 0, (struct sockaddr*)dst_h->sock, dst_h->size) < 0) { + fprintf(stderr, "unable to forward to %s:%d\n", dst_h->ip, dst_h->port); perror(NULL); } else { @@ -119,29 +171,38 @@ void handle_inside(int inside, char *bindip, struct sockaddr_in *dst) { /* unknown client, open new out socket */ if(VERBOSE) { fprintf(stderr, "Client %s:%d is unknown, forwarding data to %s:%d ", - srcip, ntohs(src->sin_port), dstip, ntohs(dst->sin_port)); + src_h->ip, src_h->port, dst_h->ip, dst_h->port); } - if(bindip == NULL) - output = bindsocket("0.0.0.0", 0); - else - output = bindsocket(bindip, 0); + output = bindsocket(bind_h); /* send req out */ - if(sendto(output, buffer, len, 0, (struct sockaddr*)dst, size) < 0) { - fprintf(stderr, "unable to forward to %s:%d\n", dstip, ntohs(dst->sin_port)); + if(sendto(output, buffer, len, 0, (struct sockaddr*)dst_h->sock, dst_h->size) < 0) { + fprintf(stderr, "unable to forward to %s:%d\n", dst_h->ip, dst_h->port); perror(NULL); } else { - struct sockaddr_in *ret = malloc(size); - getsockname(output, (struct sockaddr*)ret, (socklen_t *)&size); - client = client_new(output, src, ret); + size = listen_h->size; + host_t *ret_h; + if(listen_h->is_v6) { + struct sockaddr_in6 *ret = malloc(size); + getsockname(output, (struct sockaddr*)ret, (socklen_t *)&size); + ret_h = get_host(NULL, 0, NULL, ret); + client = client_new(output, src_h, ret_h); + } + else { + struct sockaddr_in *ret = malloc(size); + getsockname(output, (struct sockaddr*)ret, (socklen_t *)&size); + ret_h = get_host(NULL, 0, ret, NULL); + client = client_new(output, src_h, ret_h); + } + client_add(client); + if(VERBOSE) { - if(bindip != NULL) { - char *bindip = ntoa(ret); - fprintf(stderr, "from %s:%d\n", bindip, ntohs(ret->sin_port)); + if(strcmp(bind_h->ip, "0.0.0.0") != 0 || strcmp(bind_h->ip, "::0") != 0) { + fprintf(stderr, "from %s:%d\n", ret_h->ip, ret_h->port); } else { fprintf(stderr, "\n"); @@ -151,18 +212,17 @@ void handle_inside(int inside, char *bindip, struct sockaddr_in *dst) { } } - free(dstip); - free(srcip); + /* FIXME: free? */ } /* handle answer from the outside */ -void handle_outside(int inside, int outside) { +void handle_outside(int inside, int outside, host_t *outside_h) { int len; unsigned char buffer[MAX_BUFFER_SIZE]; - struct sockaddr_in *src; + void *src; client_t *client; - size_t size = sizeof(struct sockaddr_in); + size_t size = outside_h->size; src = malloc(size); len = recvfrom( outside, buffer, sizeof( buffer ), 0, (struct sockaddr*)src, (socklen_t *)&size ); @@ -172,7 +232,7 @@ void handle_outside(int inside, int outside) { client = client_find_fd(outside); if(client != NULL) { /* yes, we know it */ - if(sendto(inside, buffer, len, 0, (struct sockaddr*)client->src, size) < 0) { + if(sendto(inside, buffer, len, 0, (struct sockaddr*)client->src, client->size) < 0) { perror("unable to send back to client"); /* FIXME: add src+port */ client_close(client); } @@ -180,3 +240,33 @@ void handle_outside(int inside, int outside) { } } +/* runs forever, handles incoming requests on the inside and answers on the outside */ +int main_loop(int listensocket, host_t *listen_h, host_t *bind_h, host_t *dst_h) { + int max, sender; + fd_set fds; + + for(;;) { + FD_ZERO(&fds); + max = fill_set(&fds); + + FD_SET(listensocket, &fds); + if (listensocket > max) + max = listensocket; + + select(max + 1, &fds, NULL, NULL, NULL); + + if (FD_ISSET(listensocket, &fds)) { + /* incoming client on the inside, get src, bind output fd, add to list + if known, otherwise just handle it */ + handle_inside(listensocket, listen_h, bind_h, dst_h); + } + else { + /* remote answer came in on an output fd, proxy back to the inside */ + sender = get_sender(&fds); + handle_outside(listensocket, sender, dst_h); + } + + /* close old outputs, if any */ + client_clean(); + } +} diff --git a/net.h b/net.h index e0caca1..d23e117 100644 --- a/net.h +++ b/net.h @@ -35,6 +35,7 @@ #include #include + #include "client.h" #define MAX_BUFFER_SIZE 65535 @@ -42,10 +43,20 @@ extern client_t *clients; extern int VERBOSE; + + +void handle_inside(int inside, host_t *listen_h, host_t *bind_h, host_t *dst_h); +void handle_outside(int inside, int outside, host_t *outside_h); + +int main_loop(int listensocket, host_t *listen_h, host_t *bind_h, host_t *dst_h); +int start_listener (char *inip, char *inpt, char *srcip, char *dstip, char *dstpt); + int fill_set(fd_set *fds); int get_sender(fd_set *fds); -int bindsocket( char* ip, int port ); -void handle_inside(int inside, char *bindip, struct sockaddr_in *dst); -void handle_outside(int inside, int outside); +int bindsocket( host_t *sock_h); + + + +#define _IS_LINK_LOCAL(a) do { IN6_IS_ADDR_LINKLOCAL(a); } while(0) #endif diff --git a/udpxd.c b/udpxd.c index 2f32192..26780fa 100644 --- a/udpxd.c +++ b/udpxd.c @@ -27,43 +27,70 @@ client_t *clients = NULL; int VERBOSE = 0; -/* runs forever, handles incoming requests on the inside and answers on the outside */ -int main_loop(int listensocket, char *bindip, struct sockaddr_in *dst) { - int max, sender; - fd_set fds; +/* parse ip:port */ +int parse_ip(char *src, char *ip, char *pt) { + char *ptr = NULL; - for(;;) { - FD_ZERO(&fds); - max = fill_set(&fds); + if (strchr(optarg, '[')) { + /* v6 */ + ptr = strtok(&src[1], "]"); - FD_SET(listensocket, &fds); - if (listensocket > max) - max = listensocket; - - select(max + 1, &fds, NULL, NULL, NULL); - - if (FD_ISSET(listensocket, &fds)) { - /* incoming client on the inside, get src, bind output fd, add to list - if known, otherwise just handle it */ - handle_inside(listensocket, bindip, dst); - } - else { - /* remote answer came in on an output fd, proxy back to the inside */ - sender = get_sender(&fds); - handle_outside(listensocket, sender); - } - - /* close old outputs, if any */ - client_clean(); + if(strlen(ptr) > INET6_ADDRSTRLEN) { + fprintf(stderr, "ip v6 address is too long!\n"); + return 1; } + + strncpy(ip, ptr, strlen(ptr)+1); + ptr = strtok(NULL, "]"); + if(ptr) + ptr = &ptr[1]; /* remove : */ + } + else if(strchr(optarg, ':')) { + /* v4 */ + ptr = strtok(src, ":"); + + if(strlen(ptr) > INET_ADDRSTRLEN) { + fprintf(stderr, "ip v4 address is too long!\n"); + return 1; + } + + strncpy(ip, ptr, strlen(ptr)+1); + ptr = strtok(NULL, ":"); + } + else { + fprintf(stderr, "Invalid ip/port specification!\n"); + return 1; + } + + if(ptr != NULL) { + /* got a port */ + if(strlen(ptr) > 5) { + fprintf(stderr, "port is too long!\n"); + return 1; + } + else { + if(atoi(ptr) > 65535) { + fprintf(stderr, "maximum port number possible: 65535!\n"); + return 1; + } + strncpy(pt, ptr, strlen(ptr)+1); + } + } + else { + fprintf(stderr, "Port is missing!\n"); + return 1; + } + + return 0; } + + int main ( int argc, char* argv[] ) { - int listen, opt; + int opt, err; char *inip, *inpt, *srcip, *dstip, *dstpt; - struct sockaddr_in *dst; - char colon[] = ":"; + err = 0; static struct option longopts[] = { { "listen", required_argument, NULL, 'l' }, @@ -80,7 +107,7 @@ int main ( int argc, char* argv[] ) { } srcip = dstip = inip = dstpt = inpt = NULL; - + while ((opt = getopt_long(argc, argv, "l:b:d:vVh?", longopts, NULL)) != -1) { switch (opt) { case 'v': @@ -96,49 +123,27 @@ int main ( int argc, char* argv[] ) { VERBOSE = 1; break; case 'l': - if(strchr(optarg, ':')) { - char *ptr = NULL; - ptr = strtok(optarg, colon); - inip = malloc( strlen(ptr)+1); - strncpy(inip, ptr, strlen(ptr)+1); - ptr = strtok(NULL, colon); - if(ptr != NULL) { - inpt = malloc( strlen(ptr)+1); - strncpy(inpt, ptr, strlen(ptr)+1); - } - else { - fprintf(stderr, "Listen port for parameter -l is missing!\n"); - return 0; - } - } - else { + inip = malloc(INET6_ADDRSTRLEN+1); + inpt = malloc(6); + if (parse_ip(optarg, inip, inpt) != 0) { fprintf(stderr, "Parameter -l has the format !\n"); - return 0; + err = 1; } break; case 'd': - if(strchr(optarg, ':')) { - char *ptr = NULL; - ptr = strtok(optarg, colon); - dstip = malloc( strlen(ptr)+1); - strncpy(dstip, ptr, strlen(ptr)+1); - ptr = strtok(NULL, colon); - if(ptr != NULL) { - dstpt = malloc( strlen(ptr)+1); - strncpy(dstpt, ptr, strlen(ptr)+1); - } - else { - fprintf(stderr, "Destination port for parameter -d is missing!\n"); - return 0; - } - } - else { + dstip = malloc(INET6_ADDRSTRLEN+1); + dstpt = malloc(6); + if (parse_ip(optarg, dstip, dstpt) != 0) { fprintf(stderr, "Parameter -d has the format !\n"); - return 0; + err = 1; } break; case 'b': - srcip = malloc(strlen(optarg)); + if(strlen(optarg) > INET6_ADDRSTRLEN) { + fprintf(stderr, "Bind ip address is too long!\n"); + err = 1; + } + srcip = malloc(INET6_ADDRSTRLEN+1); strncpy(srcip, optarg, strlen(optarg)); break; default: @@ -151,36 +156,33 @@ int main ( int argc, char* argv[] ) { if(inip == NULL) { fprintf(stderr, "-l parameter is required!\n"); usage(); - return 1; + err = 1; } if(dstip == NULL) { fprintf(stderr, "-d parameter is required!\n"); usage(); - return 1; + err = 1; } - listen = bindsocket(inip, atoi(inpt)); - - dst = malloc(sizeof(struct sockaddr_in)); - dst->sin_family = AF_INET; - dst->sin_addr.s_addr = inet_addr( dstip ); - dst->sin_port = htons( atoi( dstpt ) ); - - if(VERBOSE) { - fprintf(stderr, "Listening on %s:%s, forwarding to %s:%s", - inip, inpt, dstip, dstpt); - if(srcip != NULL) - fprintf(stderr, ", binding to %s\n", srcip); - else - fprintf(stderr, "\n"); + if(! err) { + err = start_listener (inip, inpt, srcip, dstip, dstpt); } - main_loop(listen, srcip, dst); + /* FIXME: add sighandler */ - /* FIXME: add sighandler, clean up mem */ - - return 0; + if(srcip != NULL) + free(srcip); + if(dstip != NULL) + free(dstip); + if(inip != NULL) + free(inip); + if(inpt != NULL) + free(inpt); + if(dstpt != NULL) + free(dstpt); + + return err; } void usage() { diff --git a/udpxd.h b/udpxd.h index dbe4cf8..fd32f1b 100644 --- a/udpxd.h +++ b/udpxd.h @@ -41,5 +41,6 @@ int main_loop(int listensocket, char *bindip, struct sockaddr_in *dst); void usage(); +int parse_ip(char *src, char *ip, char *pt); #endif