From aa4e941e539f8c886bf36af4f003e62e24964528 Mon Sep 17 00:00:00 2001 From: "git@daemon.de" Date: Sun, 26 Apr 2015 13:26:37 +0200 Subject: [PATCH] ipv6 works now as well, added fork, added syslog --- Makefile | 2 +- README.md | 2 +- TODO | 8 ---- client.c | 7 ++-- client.h | 1 + host.c | 42 ++++++++++++++++----- host.h | 2 +- log.c | 41 ++++++++++++++++++++ log.h | 39 +++++++++++++++++++ net.c | 111 +++++++++++++++++++++++++++++++++++++----------------- net.h | 7 +++- udpxd.c | 43 +++++++++++++++------ udpxd.h | 2 +- udpxd.pod | 57 +++++++++++++++++++++++----- 14 files changed, 280 insertions(+), 84 deletions(-) create mode 100644 log.c create mode 100644 log.h diff --git a/Makefile b/Makefile index 21d7b73..046b87a 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 = host.o client.o net.o udpxd.o +OBJS = host.o client.o net.o udpxd.o log.o DST = udpxd PREFIX = /usr/local UID = root diff --git a/README.md b/README.md index 204eecc..cd46aa9 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This is the README file for the network program udpxd. udpxd can be used to forward or proxy UDP client traffic to another port on another system. It also supports binding to a specific ip address which will be used as the source -for outgoing packets. +for outgoing packets. It supports ip version 4 and 6. ## Documentation diff --git a/TODO b/TODO index f622f30..3c65742 100644 --- a/TODO +++ b/TODO @@ -1,12 +1,4 @@ MUST: -- support ipv6 - + half done: not tested yet, just compiles. - + check for bind_h: must be same proto as dst_h (both v4 or both v6!) - + v6ize verbose outputs - + check for scopeid for dst_h (no route to host?), also the id - must match for bind_h -- daemonize -- syslog - if compiled with -O2, gcc mangles the dst sockaddr_in pointers in some weird ways MAYBE: diff --git a/client.c b/client.c index 4ee8342..b7342ab 100644 --- a/client.c +++ b/client.c @@ -20,6 +20,7 @@ */ #include "client.h" +#include "log.h" void client_del(client_t *client) { HASH_DEL(clients, client); @@ -72,10 +73,8 @@ void client_clean(int asap) { client_iter(clients, current) { diff = now - current->lastseen; if(diff >= MAXAGE || asap) { - if(VERBOSE) { - fprintf(stderr, "closing socket %s:%d for client %s:%d (aged out after %d seconds)\n", - current->src->ip, current->src->port, current->dst->ip, current->dst->port, MAXAGE); - } + verbose("closing socket %s:%d for client %s:%d (aged out after %d seconds)\n", + 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 3158ff6..a74f111 100644 --- a/client.h +++ b/client.h @@ -54,6 +54,7 @@ typedef struct _client_t client_t; extern client_t *clients; extern int VERBOSE; +extern int FORKED; /* wrapper for HASH_ITER */ /** Iterate over the list of clients. diff --git a/host.c b/host.c index 1f50772..751976a 100644 --- a/host.c +++ b/host.c @@ -40,17 +40,24 @@ host_t *get_host(char *ip, int port, struct sockaddr_in *v4, struct sockaddr_in6 tmp->sin6_family = AF_INET6; tmp->sin6_port = htons( port ); - + + unsigned int scope = get_v6_scope(ip); if (is_linklocal((struct in6_addr*)&tmp->sin6_addr)) - tmp->sin6_scope_id = get_v6_scope(ip); + tmp->sin6_scope_id = scope; else tmp->sin6_scope_id = 0; host->is_v6 = 1; host->sock = (struct sockaddr*)tmp; host->size = sizeof(struct sockaddr_in6); - host->ip = malloc(INET6_ADDRSTRLEN+1); - memcpy(host->ip, ip, INET6_ADDRSTRLEN); + if(tmp->sin6_scope_id != 0) { + host->ip = malloc(INET6_ADDRSTRLEN + 9); /* plus [ % ] \0 , scope*/ + sprintf(host->ip, "[%s%%%d]", ip, scope); + } + else { + host->ip = malloc(INET6_ADDRSTRLEN + 3); /* plus [ ] \0 */ + sprintf(host->ip, "[%s]", ip); + } } else { struct sockaddr_in *tmp = malloc(sizeof(struct sockaddr_in)); @@ -58,6 +65,7 @@ host_t *get_host(char *ip, int port, struct sockaddr_in *v4, struct sockaddr_in6 tmp->sin_family = AF_INET; tmp->sin_addr.s_addr = inet_addr( ip ); tmp->sin_port = htons( port ); + host->sock = (struct sockaddr*)tmp; host->size = sizeof(struct sockaddr_in); host->ip = malloc(INET_ADDRSTRLEN+1); @@ -69,20 +77,31 @@ host_t *get_host(char *ip, int port, struct sockaddr_in *v4, struct sockaddr_in6 memcpy(tmp, v4, sizeof(struct sockaddr_in)); host->ip = malloc(INET_ADDRSTRLEN); inet_ntop(AF_INET, (struct in_addr *)&tmp->sin_addr, host->ip, INET_ADDRSTRLEN); + host->port = ntohs(tmp->sin_port); host->sock = (struct sockaddr*)tmp; host->size = sizeof(struct sockaddr_in); - //fprintf(stderr, "%s sock: %p\n", host->ip, tmp); } else if(v6 != NULL) { struct sockaddr_in6 *tmp = malloc(sizeof(struct sockaddr_in6)); memcpy(tmp, v6, sizeof(struct sockaddr_in6)); - host->ip = malloc(INET6_ADDRSTRLEN); - inet_ntop(AF_INET, (struct in6_addr *)&tmp->sin6_addr, host->ip, INET6_ADDRSTRLEN); + char *myip = malloc(INET6_ADDRSTRLEN); + inet_ntop(AF_INET6, (struct in6_addr *)&tmp->sin6_addr, myip, INET6_ADDRSTRLEN); + host->port = ntohs(tmp->sin6_port); host->sock = (struct sockaddr*)tmp; host->is_v6 = 1; host->size = sizeof(struct sockaddr_in6); + + if(tmp->sin6_scope_id != 0) { + host->ip = malloc(INET6_ADDRSTRLEN + 9); /* plus [ % ] \0 , scope*/ + sprintf(host->ip, "[%s%%%d]", ip, tmp->sin6_scope_id); + } + else { + host->ip = malloc(INET6_ADDRSTRLEN + 3); /* plus [ ] \0 */ + sprintf(host->ip, "[%s]", myip); + } + free(myip); } else { fprintf(stderr, "call invalid!\n"); @@ -92,8 +111,12 @@ host_t *get_host(char *ip, int port, struct sockaddr_in *v4, struct sockaddr_in6 return host; } -char *is_v6(char *ip) { - return strchr(ip, ':'); +int is_v6(char *ip) { + char *IS = strchr(ip, ':'); + if(IS == NULL) + return 0; + else + return 1; } /* via http://stackoverflow.com/questions/13504934/binding-sockets-to-ipv6-addresses @@ -126,7 +149,6 @@ unsigned get_v6_scope(const char *ip){ } } freeifaddrs(addrs); - fprintf(stderr, "scope: %d\n", scope); return scope; } diff --git a/host.h b/host.h index 91c44c0..4722a50 100644 --- a/host.h +++ b/host.h @@ -49,7 +49,7 @@ typedef struct _host_t host_t; unsigned get_v6_scope(const char *ip); int is_linklocal(struct in6_addr *a); host_t *get_host(char *ip, int port, struct sockaddr_in *v4, struct sockaddr_in6 *v6); -char *is_v6(char *ip); +int is_v6(char *ip); void host_dump(host_t *host); void host_clean(host_t *host); diff --git a/log.c b/log.c new file mode 100644 index 0000000..70851e0 --- /dev/null +++ b/log.c @@ -0,0 +1,41 @@ +/* + This file is part of udpxd. + + Copyright (C) 2015 T.v.Dein. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + You can contact me by mail: . +*/ + +#include "log.h" + + + +void verbose(const char * fmt, ...) { + if(VERBOSE) { + char *msg = NULL; + va_list ap; + va_start(ap, fmt); + vasprintf(&msg, fmt, ap); + va_end(ap); + + if(FORKED) { + syslog(LOG_INFO, msg); + } + else { + fprintf(stderr, msg); + } + } +} diff --git a/log.h b/log.h new file mode 100644 index 0000000..7b7a420 --- /dev/null +++ b/log.h @@ -0,0 +1,39 @@ +/* + This file is part of udpxd. + + Copyright (C) 2015 T.v.Dein. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + You can contact me by mail: . +*/ + +#ifndef _HAVE_LOG_H +#define _HAVE_LOG_H + +#define _WITH_DPRINTF + +#include +#include +#include +#include +#include +#include + +extern int VERBOSE; +extern int FORKED; + +void verbose(const char * fmt, ...); + +#endif diff --git a/net.c b/net.c index e698080..d53dc43 100644 --- a/net.c +++ b/net.c @@ -22,6 +22,7 @@ #include "net.h" #include "client.h" #include "host.h" +#include "log.h" @@ -87,10 +88,57 @@ int bindsocket( host_t *sock_h) { return fd; } -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; +int start_listener (char *inip, char *inpt, char *srcip, char *dstip, char *dstpt, char *pidfile) { + host_t *listen_h, *dst_h, *bind_h; + + if(FORKED) { + // fork + pid_t pid, sid; + FILE *fd; + + pid = fork(); + + if (pid < 0) { + perror("fork error"); + return 1; + } + + if (pid > 0) { + /* leave parent */ + if((fd = fopen(pidfile, "w")) == NULL) { + perror("failed to write pidfile"); + return 1; + } + else { + fprintf(fd, "%d\n", pid); + fclose(fd); + } + return 0; + } + + + sid = setsid(); + if (sid < 0) { + perror("set sid error"); + return 1; + } + + if ((chdir("/")) < 0) { + perror("failed to chdir to /"); + return 1; + } + + umask(0); + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + openlog("udpxd", LOG_NOWAIT|LOG_PID, LOG_USER); + } + + listen_h = get_host(inip, atoi(inpt), NULL, NULL); + dst_h = get_host(dstip, atoi(dstpt), NULL, NULL); + bind_h = NULL; if(srcip != NULL) { bind_h = get_host(srcip, 0, NULL, NULL); @@ -102,19 +150,18 @@ int start_listener (char *inip, char *inpt, char *srcip, char *dstip, char *dstp 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); + verbose("Listening on %s:%s, forwarding to %s:%s", + listen_h->ip, inpt, dst_h->ip, dstpt); if(srcip != NULL) - fprintf(stderr, ", binding to %s\n", srcip); + verbose(", binding to %s\n", bind_h->ip); else - fprintf(stderr, "\n"); + verbose("\n"); } main_loop(listen, listen_h, bind_h, dst_h); @@ -123,6 +170,8 @@ int start_listener (char *inip, char *inpt, char *srcip, char *dstip, char *dstp host_clean(listen_h); host_clean(dst_h); + closelog(); + return 0; } @@ -148,21 +197,15 @@ void handle_inside(int inside, host_t *listen_h, host_t *bind_h, host_t *dst_h) free(src); - if(VERBOSE) { - fprintf(stderr, "New incomming request from %s:%d with %d bytes\n", - src_h->ip, src_h->port, len); - } - if(len > 0) { /* do we know it ? */ 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 ", - src_h->ip, src_h->port, dst_h->ip, dst_h->port); - - } + verbose("Client %s:%d is known, forwarding %d bytes to %s:%d ", + src_h->ip, src_h->port, len, dst_h->ip, dst_h->port); + verb_prbind(bind_h); + 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); @@ -173,11 +216,9 @@ void handle_inside(int inside, host_t *listen_h, host_t *bind_h, host_t *dst_h) } else { /* unknown client, open new out socket */ - if(VERBOSE) { - fprintf(stderr, "Client %s:%d is unknown, forwarding data to %s:%d ", - src_h->ip, src_h->port, dst_h->ip, dst_h->port); - - } + verbose("Client %s:%d is unknown, forwarding %d bytes to %s:%d ", + src_h->ip, src_h->port, len, dst_h->ip, dst_h->port); + verb_prbind(bind_h); output = bindsocket(bind_h); @@ -205,20 +246,9 @@ void handle_inside(int inside, host_t *listen_h, host_t *bind_h, host_t *dst_h) } client_add(client); - - if(VERBOSE) { - 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"); - } - } } } } - - /* FIXME: free? */ } /* handle answer from the outside */ @@ -310,3 +340,14 @@ void int_handler(int sig) { signal(sig, SIG_IGN); longjmp(JumpBuffer, 1); } + +void verb_prbind (host_t *bind_h) { + if(VERBOSE) { + if(strcmp(bind_h->ip, "0.0.0.0") != 0 || strcmp(bind_h->ip, "[::0]") != 0) { + verbose("from %s:%d\n", bind_h->ip, bind_h->port); + } + else { + verbose("\n"); + } + } +} diff --git a/net.h b/net.h index b4bc21a..022ff30 100644 --- a/net.h +++ b/net.h @@ -30,7 +30,9 @@ #include #include #include +#include +#include #include #include #include @@ -44,6 +46,7 @@ extern client_t *clients; extern int VERBOSE; +extern int FORKED; @@ -51,13 +54,13 @@ 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 start_listener (char *inip, char *inpt, char *srcip, char *dstip, char *dstpt, char *pidfile); int fill_set(fd_set *fds); int get_sender(fd_set *fds); int bindsocket( host_t *sock_h); void int_handler(int sig); - +void verb_prbind (host_t *bind_h); #define _IS_LINK_LOCAL(a) do { IN6_IS_ADDR_LINKLOCAL(a); } while(0) diff --git a/udpxd.c b/udpxd.c index 26780fa..d23f7fb 100644 --- a/udpxd.c +++ b/udpxd.c @@ -26,6 +26,7 @@ /* global client list */ client_t *clients = NULL; int VERBOSE = 0; +int FORKED = 1; /* parse ip:port */ int parse_ip(char *src, char *ip, char *pt) { @@ -90,6 +91,8 @@ int parse_ip(char *src, char *ip, char *pt) { int main ( int argc, char* argv[] ) { int opt, err; char *inip, *inpt, *srcip, *dstip, *dstpt; + char pidfile[MAX_BUFFER_SIZE]; + err = 0; static struct option longopts[] = { @@ -98,7 +101,9 @@ int main ( int argc, char* argv[] ) { { "dest", required_argument, NULL, 'd' }, { "version", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, - { "verbose", no_argument, NULL, 'V' } + { "verbose", no_argument, NULL, 'V' }, + { "foreground",no_argument, NULL, 'f' }, + { "pidfile", required_argument, NULL, 'p' }, }; if( argc < 2 ) { @@ -107,13 +112,17 @@ int main ( int argc, char* argv[] ) { } srcip = dstip = inip = dstpt = inpt = NULL; + strncpy(pidfile, "/var/run/udpxd.pid", 19); - while ((opt = getopt_long(argc, argv, "l:b:d:vVh?", longopts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "l:b:d:vfVh?", longopts, NULL)) != -1) { switch (opt) { case 'v': fprintf(stderr, "This is %s version %s\n", argv[0], UDPXD_VERSION); return 1; break; + case 'f': + FORKED = 0; + break; case 'h': case '?': usage(); @@ -146,6 +155,9 @@ int main ( int argc, char* argv[] ) { srcip = malloc(INET6_ADDRSTRLEN+1); strncpy(srcip, optarg, strlen(optarg)); break; + case 'p': + strncpy(pidfile, optarg, strlen(optarg)); + break; default: usage(); return 1; @@ -165,12 +177,17 @@ int main ( int argc, char* argv[] ) { err = 1; } + if(srcip != NULL) { + if(is_v6(srcip) != is_v6(dstip)) { + fprintf(stderr, "Bind ip and destination ip must be both v4 or v6 and can't be mixed!\n"); + err = 1; + } + } + if(! err) { - err = start_listener (inip, inpt, srcip, dstip, dstpt); + err = start_listener (inip, inpt, srcip, dstip, dstpt, pidfile); } - /* FIXME: add sighandler */ - if(srcip != NULL) free(srcip); if(dstip != NULL) @@ -187,14 +204,16 @@ int main ( int argc, char* argv[] ) { void usage() { fprintf(stderr, - "Usage: udpxd [-lbdvhV]\n\n" + "Usage: udpxd [-lbdfpvhV]\n\n" "Options:\n" - "--listen -l listen for incoming requests\n" - "--bind -b bind ip used for outgoing requests\n" - "--dest -d destination to forward requests to\n" - "--help -h -? print help message\n" - "--version -v print program version\n" - "--verbose -V enable verbose logging\n\n" + "--listen -l listen for incoming requests\n" + "--bind -b bind ip used for outgoing requests\n" + "--dest -d destination to forward requests to\n" + "--foreground -f don't fork into background\n" + "--pidfile -p pidfile, default: /var/run/udpxd.pid\n" + "--help -h -? print help message\n" + "--version -v print program version\n" + "--verbose -V enable verbose logging\n\n" "Options -l and -d are mandatory.\n\n" "This is udpxd version %s.\n", UDPXD_VERSION ); diff --git a/udpxd.h b/udpxd.h index 8391353..1523409 100644 --- a/udpxd.h +++ b/udpxd.h @@ -36,7 +36,7 @@ #include #include -#define UDPXD_VERSION "0.0.1" +#define UDPXD_VERSION "0.0.2" void usage(); diff --git a/udpxd.pod b/udpxd.pod index 015f507..812fef7 100644 --- a/udpxd.pod +++ b/udpxd.pod @@ -4,15 +4,17 @@ udpxd - A general purpose UDP relay/port forwarder/proxy =head1 SYNOPSIS - Usage: udpxd [-lbdvhV] - + Usage: udpxd [-lbdfpvhV] + Options: - --listen -l listen for incoming requests - --bind -b bind ip used for outgoing requests - --dest -d destination to forward requests to - --help -h -? print help message - --version -v print program version - --verbose -V enable verbose logging + --listen -l listen for incoming requests + --bind -b bind ip used for outgoing requests + --dest -d destination to forward requests to + --foreground -f don't fork into background + --pidfile -p pidfile, default: /var/run/udpxd.pid + --help -h -? print help message + --version -v print program version + --verbose -V enable verbose logging =head1 DESCRIPTION @@ -39,6 +41,32 @@ with B<-b>. The options B<-l> and B<-d> are mandatory. +If the option B<-f> has not been specified, udpxd forks into +the background and becomes a daemon. It writes it pidfile to +C, which can be changed with the B<-p> +option. + +B + +Udpxd supports ip version 4 and 6, it doesn't support hostnames, +-l, -d and -b must be ip addresses. In order to specify an ipv6 +address and a port, use: + + -l [::1]:53 + +that is, surround the ipv6 address with brackets. + +Port forwardings can be mixed: + + listen | forward to + -------+----------- + ipv4 | ipv4 + ipv6 | ipv4 + ipv4 | ipv6 + ipv6 | ipv6 + =head1 EXAMPLES Let's say you operate a multihomed unix system named 'foo' @@ -82,9 +110,20 @@ In this case for the client everything looks as before, but the ntp server on the other end will see ntp requests coming from 192.168.1.45 instead. +Here we listen on the ip v6 loopback address and forward traffic +to another ip v6 destination address: + + udpxd -l [::1]:53 -d [2001:4860:4860::8888]:53 + +Or, we could listen on an ip v4 address and forward to an ip v6 +address: + + udpxd -l 192.168.1.1:53 -d [2001:4860:4860::8888]:53 + =head1 FILES -udpxd currently does not write or open any files. +B: created if running in daemon mode (-f not +specified). =head1 BUGS