complete though slightly messy
[rrq/rrqnet.git] / rrqnet.c
index fa8d3f660963624871d97736458ecb5a1e3140e4..2711039cfddaccd81fc4fcd0bda51bdffda3f6ff 100644 (file)
--- a/rrqnet.c
+++ b/rrqnet.c
@@ -26,6 +26,7 @@
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/types.h>
+#include <sys/socket.h>
 #include <time.h>
 #include <unistd.h>
 
@@ -64,7 +65,8 @@ struct Allowed {
 
 // Details of actualized connections.
 struct Remote {
-    struct SockAddr uaddr;
+    struct SockAddr uaddr;     // The remote IP address
+    struct SockAddr laddr;     // The local IP address
     struct Allowed *spec;      // Rule being instantiated
     struct timeval rec_when;   // Last received packet time, in seconds
 };
@@ -82,7 +84,8 @@ struct Interface  {
 typedef struct _PacketItem {
     QueueItem base;
     int fd;
-    struct SockAddr src;
+    struct SockAddr src; // the remote IP for this packet
+    struct SockAddr dst; // the local IP for this packet
     ssize_t len;
     unsigned char buffer[ BUFSIZE ];
 } PacketItem;
@@ -188,6 +191,12 @@ static struct {
 // Flag to signal the UDP socket as being ipv6 or not (forced ipv4)
 static int udp6 = 1;
 
+// The given UDP source address, if any
+static struct {
+    int family;
+    unsigned char address[16];
+} udp_source;
+
 // Flag to indicate tpg transport patch = avoid UDP payload of 1470
 // bytes by adding 2 tag-along bytes
 static int tpg_quirk = 0;
@@ -574,6 +583,7 @@ static int parse_bits(char *bits,int max,struct Allowed *into) {
 // Formats: <ipv4-address>[/<bits>][:<port>][=keyfile]
 // Formats: <ipv6-address>[/<bits>][=keyfile]
 // Formats: \[<ipv6-address>[/<bits>]\][:<port>][=keyfile]
+// Formats: hostname:port[=keyfile]
 static int parse_allowed(char *arg,struct Allowed *into) {
     static char buffer[10000];
     int n = strlen( arg );
@@ -727,6 +737,38 @@ static int parse_mcast(char *arg) {
     return 0;
 }
 
+//** IP address parsing utility for UDP source address
+// Return 0 if ok and 1 otherwise
+// Formats: <ipv4-address> or <ipv6-address>
+// The ipv4 address should be a multicast address in ranges
+// 224.0.0.0/22, 232.0.0.0/7, 234.0.0.0/8 or 239.0.0.0/8
+// though it's not checked here.
+static int parse_udp_source(char *arg) {
+    if ( inet_pton( AF_INET6, arg, udp_source.address ) ) {
+       // An ipv6 address is given.
+       if ( udp6 ) {
+           udp_source.family = AF_INET6;
+           return 0;
+       }
+       return 1;
+    }
+    if ( ! inet_pton( AF_INET, arg, udp_source.address ) ) {
+       return 1;
+    }
+
+    // An ipv4 address is given.
+    if ( udp6 ) {
+       // Translate into ipv6-encoded ipv4
+       memmove( udp_source.address + 12, udp_source.address, 4 );
+       memset( udp_source.address, 0, 10 );
+       memset( udp_source.address + 10, -1, 2 );
+       udp_source.family = AF_INET6;
+    } else {
+       udp_source.family = AF_INET;
+    }
+    return 0;
+}
+
 // Utility that sets upt the multicast socket, which is used for
 // receiving multicast packets.
 static void setup_mcast() {
@@ -879,9 +921,9 @@ static void write_remote(unsigned char *buf, int n,struct Remote *r) {
     // A packet buffer
     unsigned char output[ BUFSIZE ];
     if ( n < 12 ) {
-       VERBOSE2OUT( "SEND %d bytes to %s\n", n, inet_stoa( &r->uaddr ) );
+       VERBOSE2OUT( "SENDing %d bytes to %s\n", n, inet_stoa( &r->uaddr ) );
     } else {
-       VERBOSE2OUT( "SEND %d bytes %s -> %s to %s\n", n,
+       VERBOSE2OUT( "SENDing %d bytes %s -> %s to %s\n", n,
                     inet_mtoa( buf+6 ), inet_mtoa( buf ),
                     inet_stoa( &r->uaddr ) );
     }
@@ -908,28 +950,68 @@ static void write_remote(unsigned char *buf, int n,struct Remote *r) {
     } else if ( r->spec->psk.keyfile ) {
        encrypt( output, n, &r->spec->psk );
     }
-    struct sockaddr *sock = &r->uaddr.in;
+    // Setup the packet addressing
+    struct in_pktinfo pkt4info = {
+       .ipi_ifindex = 0,  /* Interface index */
+       .ipi_spec_dst.s_addr = 0, /* Local address */
+       .ipi_addr.s_addr = 0,     /* Header Destination address */
+    };
+    struct in6_pktinfo pkt6info = {
+       .ipi6_addr.s6_addr32 = { 0, 0, 0, 0 },
+       .ipi6_ifindex = 0,
+    };
+    void *pktinfo = 0;
+    int pktinfosize = 0;
+    struct sockaddr_in *sock4 = &r->uaddr.in4;
+    struct sockaddr_in6 *sock6 = &r->uaddr.in6;
+    void *sock;
     size_t size;
-    if ( sock->sa_family == AF_INET6 ) {
+    if ( udp6 ) {
        // Note that the size of +struct sockaddr_in6+ is actually
        // larger than the size of +struct sockaddr+ (due to the
        // addition of the +sin6_flowinfo+ field). It results in the
        // following cuteness for passing arguments to +sendto+.
+       sock = sock6;
        size = sizeof( struct sockaddr_in6 );
        VERBOSE2OUT( "IPv6 UDP %d %s\n",
-                    udp_fd, inet_stoa( (struct SockAddr*) sock ) );
+                    udp_fd, inet_stoa( &r->laddr ) );
+       if ( r->laddr.in.sa_family ) {
+           memcpy( &pkt6info.ipi6_addr, &sock6->sin6_addr, 16 );
+           pktinfo = &pkt6info;
+           pktinfosize = sizeof( pkt6info );
+       }
     } else {
+       sock = sock4;
        size = sizeof( struct sockaddr_in );
        VERBOSE2OUT( "IPv4 UDP %d %s\n",
-                    udp_fd, inet_stoa( (struct SockAddr*) sock ) );
+                    udp_fd, inet_stoa( &r->laddr ) );
+       if ( r->laddr.in.sa_family ) {
+           memcpy( &pkt4info.ipi_spec_dst, &sock4->sin_addr, 4 );
+           pktinfo = &pkt4info;
+           pktinfosize = sizeof( pkt4info );
+       }
     }
-    VERBOSE2OUT( "SEND %d bytes to %s [%s -> %s]\n",
-                n, inet_stoa( (struct SockAddr*) sock ),
+    VERBOSE2OUT( "SEND %d bytes from %s to %s [%s -> %s]\n",
+                n,
+                inet_stoa( &r->laddr ),
+                inet_stoa( &r->uaddr ),
                 ( n < 12 )? "" : inet_mtoa( buf+6 ),
                 ( n < 12 )? "" : inet_mtoa( buf )
              );
-    // IS sendto thread safe??
-    if ( sendto( udp_fd, output, n, 0, sock, size ) < n ) {
+    // IS sendmsg thread safe??
+    struct iovec data[1] = {{ output, n }};
+    (void)pktinfo;
+    (void)pktinfosize;
+    struct msghdr msg = {
+       .msg_name = sock,
+       .msg_namelen = size,
+       .msg_iov = data,
+       .msg_iovlen = 1,
+       .msg_control = 0, //pktinfo,
+       .msg_controllen = 0, //pktinfosize,
+       .msg_flags = 0 // unused
+    };
+    if ( sendmsg( udp_fd, &msg, 0 ) < n ) {
        perror( "Writing socket" );
        // Invalidate remote temporarily instead? But if it's an
        // "uplink" it should be retried eventually...
@@ -1099,7 +1181,10 @@ static struct Interface *input_check(
 }
 
 // Check packet and deliver out
-static void route_packet(unsigned char *buf,int len,struct SockAddr *src) {
+static void route_packet(PacketItem *pi) {
+    unsigned char *buf = pi->buffer;
+    int len = pi->len;
+    struct SockAddr *src = &pi->src;
     struct Interface *x = input_check( buf, len, src );
     if ( x == 0 ) {
        return; // not a nice packet
@@ -1125,6 +1210,8 @@ static void route_packet(unsigned char *buf,int len,struct SockAddr *src) {
        VERBOSE2OUT( "RECV route %s -> %s to %s\n",
                     inet_mtoa( buf+6 ), inet_mtoa( buf ),
                     inet_stoa( &y->remote->uaddr ) );
+       // Set the local address for the remote
+       memcpy( &y->remote->laddr, &pi->dst, sizeof( pi->dst ) );
        write_remote( buf, len, y->remote );
        return;
     }
@@ -1182,13 +1269,14 @@ static void *packet_handler(void *data) {
     for ( ;; ) {
        PacketItem *todo = (PacketItem *) Queue_getItem( &todolist.full );
        if ( todo->fd == mcast.fd ) {
-           // Patch multicast address as source for multicast packet
-           route_packet( todo->buffer, todo->len, &mcast.sock );
+           // Patch in the multicast address as source for multicast packet
+           memcpy( &todo->src, &mcast.sock, sizeof( todo->src ) );
+           route_packet( todo );
        } else {
            if ( udp6 ) {
                unmap_if_mapped( &todo->src );
            }
-           route_packet( todo->buffer, todo->len, &todo->src );
+           route_packet( todo );
        }
        Queue_addItem( &todolist.free, (QueueItem*) todo );
     }
@@ -1217,6 +1305,51 @@ void todolist_initialize(int nbuf,int nthr) {
     }
 }
 
+static ssize_t recvpacket(int fd,PacketItem *p) {
+    int flags = 0;
+    socklen_t addrlen =
+       udp6? sizeof( p->src.in6 ) : sizeof( p->src.in4 );
+    struct iovec buffer = {
+       .iov_base = p->buffer,
+       .iov_len = BUFSIZE
+    };
+    char data[1000];
+    struct msghdr msg = {
+       .msg_name =  &p->src.in,
+       .msg_namelen = addrlen,
+       .msg_iov = &buffer,
+       .msg_iovlen = 1,
+       .msg_control = data,
+       .msg_controllen = sizeof( data ),
+       .msg_flags = 0 // Return value
+    };
+    p->len = recvmsg( fd, &msg, flags );
+    struct cmsghdr *cmsg = CMSG_FIRSTHDR( &msg );
+    VERBOSE3OUT( "anc %p %ld\n", cmsg, p->len );
+    for ( ; cmsg; cmsg = CMSG_NXTHDR( &msg, cmsg ) ) {
+       VERBOSE3OUT( "anc type = %d\n", cmsg->cmsg_type );
+    }
+    cmsg = CMSG_FIRSTHDR( &msg );
+    if ( cmsg ) {
+       if ( udp6 ) {
+           struct in6_pktinfo *pinf = (struct in6_pktinfo*) CMSG_DATA( cmsg );
+           p->dst.in6.sin6_family = AF_INET6;
+           memcpy( &p->dst.in6.sin6_addr, &pinf->ipi6_addr, 16 );
+           VERBOSE3OUT( "DEST= udp6 %d %s\n",
+                        pinf->ipi6_ifindex, inet_stoa( &p->dst ) );
+       } else {
+           struct in_pktinfo *pinf = (struct in_pktinfo*) CMSG_DATA( cmsg );
+           p->dst.in4.sin_family = AF_INET;
+           p->dst.in4.sin_addr = pinf->ipi_addr;
+           VERBOSE3OUT( "DEST= %d %s\n",
+                        pinf->ipi_ifindex, inet_stoa( &p->dst ) );
+       }
+    }
+    return p->len;
+}
+
+
+
 // Read a full UDP packet into the given buffer, associate with a
 // connection, or create a new connection, the decrypt the as
 // specified, and capture the sender MAC address. The connection table
@@ -1227,18 +1360,18 @@ static void *doreadUDP(void *data) {
     int fd = ((ReaderData *) data)->fd;
     while ( 1 ) {
        PacketItem *todo = (PacketItem *) Queue_getItem( &todolist.free );
-       socklen_t addrlen =
-           udp6? sizeof( todo->src.in6 ) : sizeof( todo->src.in4 );
-       memset( &todo->src, 0, sizeof( todo->src ) );
        todo->fd = fd;
-       todo->len = recvfrom(
-           fd, todo->buffer, BUFSIZE, 0, &todo->src.in, &addrlen );
-       if ( todo->len == -1) {
+       memset( &todo->src, 0, sizeof( struct SockAddr ) );
+       memset( &todo->dst, 0, sizeof( struct SockAddr ) );
+       todo->len = 0;
+       VERBOSE3OUT( "Reading packet\n" );
+       ssize_t len = recvpacket( fd, todo );
+       if ( len == -1) {
            perror( "Receiving UDP" );
            exit( 1 );
        }
 #ifdef GPROF
-       if ( todo->len == 17 &&
+       if ( len == 17 &&
             memcmp( todo->buffer, "STOPSTOPSTOPSTOP", 16 ) == 0 ) {
            exit( 0 );
        }
@@ -1309,11 +1442,18 @@ static void heartbeat(int fd) {
 // Tell how to use this program and exit with failure.
 static void usage(void) {
     fprintf( stderr, "Packet tunneling over UDP, multiple channels, " );
-    fprintf( stderr, "version 0.2.5\n" );
+    fprintf( stderr, "version 1.5.3\n" );
     fprintf( stderr, "Usage: " );
-    fprintf( stderr,
- "%s [-v] [-4] [-B n] [-T n] [-m mcast] [-t tap] port [remote]+ \n",
-            progname );
+    fprintf( stderr, "%s [options] port [remote]+ \n", progname );
+    fprintf( stderr, "** options must be given or omitted in order!!\n" );
+    fprintf( stderr, " -v        = verbose log, -vv or -vvv for more logs\n" );
+    fprintf( stderr, " -tpg      = UDP transport quirk: avoid bad sizes\n" );
+    fprintf( stderr, " -4        = use an ipv4 UDP socket\n" );
+    fprintf( stderr, " -B n      = use n buffers (2*threads) by default\n");
+    fprintf( stderr, " -T n      = use n delivery threads (5 bu default)\n" );
+    fprintf( stderr, " -m mcast  = allow remotes on multicast address\n" );
+    fprintf( stderr, " -t tap    = use the nominated tap (or - for stdio)\n" );
+    fprintf( stderr, " -S source = use given source address for UDP\n" );
     exit( 1 );
 }
 
@@ -1471,9 +1611,18 @@ int main(int argc, char *argv[]) {
        i += 2;
        ENSUREARGS( 1 );
     }
+    // Then optional source address for UDP
+    if ( strncmp( "-S", argv[i], 2 ) == 0 ) {
+       ENSUREARGS( 2 );
+       if ( parse_udp_source( argv[i+1] ) ) {
+           usage();
+       }
+       i += 2;
+       ENSUREARGS( 1 );
+    }
     // then: required port
     if ( sscanf( argv[i++], "%d", &port ) != 1 ) {
-       fprintf( stderr, "Bad local port" );
+       fprintf( stderr, "Bad local port: %s\n", argv[i-1] );
        usage();
     }
     // then: any number of allowed remotes
@@ -1546,13 +1695,22 @@ int main(int argc, char *argv[]) {
        struct sockaddr_in udp_addr = {
            .sin_family = AF_INET,
            .sin_port = htons( port ),
-           .sin_addr.s_addr = htonl(INADDR_ANY),
        };
+       if ( udp_source.family == 0 ) {
+           udp_addr.sin_addr.s_addr = htonl( INADDR_ANY );
+       } else {
+           udp_addr.sin_addr.s_addr = *((uint32_t*) udp_source.address); 
+       }
        if ( bind( udp_fd, (struct sockaddr*) &udp_addr, sizeof(udp_addr))) {
            fprintf( stderr, "Error binding socket!\n");
            exit(1);
        }
        VERBOSEOUT( "Using ipv4 UDP at %d\n", udp_fd );
+       int opt = 1;
+       if ( setsockopt( udp_fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) ) {
+           fprintf( stderr, "Error configuring socket!\n");
+            exit(1);
+       }
     } else {
        // set up ipv6 socket
        if ( ( udp_fd = socket( AF_INET6, SOCK_DGRAM, 0 ) ) == 0 ) {
@@ -1562,13 +1720,19 @@ int main(int argc, char *argv[]) {
        struct sockaddr_in6 udp6_addr = {
            .sin6_family = AF_INET6,
            .sin6_port = htons( port ),
-           .sin6_addr = IN6ADDR_ANY_INIT,
        };
+       memcpy( udp6_addr.sin6_addr.s6_addr, udp_source.address, 16 );
        if ( bind( udp_fd, (struct sockaddr*) &udp6_addr, sizeof(udp6_addr))) {
            fprintf( stderr, "Error binding socket!\n");
            exit(1);
        }
        VERBOSEOUT( "Using ipv6 UDP at %d\n", udp_fd );
+       int opt = 1;
+       if ( setsockopt(
+                udp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &opt, sizeof(opt)) ) {
+           fprintf( stderr, "Error configuring socket!\n");
+            exit(1);
+       }
     }
     // If not using stdio for local traffic, then stdin and stdout are
     // closed here, so as to avoid that any other traffic channel gets