From c593cf1bd8dc2bfe6c0e6958757f30944ea3ab50 Mon Sep 17 00:00:00 2001 From: Ralph Ronnquist Date: Wed, 17 Jun 2020 20:44:57 +1000 Subject: [PATCH 1/1] initial renaming --- .gitignore | 11 + Makefile | 59 ++ debian/changelog | 5 + debian/compat | 1 + debian/control | 19 + debian/copyright | 22 + debian/rules | 27 + debian/source/format | 1 + debian/udptap.debhelper.log | 22 + debian/udptap.substvars | 2 + htable.c | 135 +++ htable.h | 71 ++ queue.c | 71 ++ queue.h | 25 + readme.adoc | 19 + rrqnet-cron.sh | 36 + rrqnet-cron.sh.8.adoc | 125 +++ rrqnet.8.adoc | 444 ++++++++++ rrqnet.c | 1648 +++++++++++++++++++++++++++++++++++ set-source-route.sh | 38 + sockaddr.h | 16 + 21 files changed, 2797 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100644 debian/udptap.debhelper.log create mode 100644 debian/udptap.substvars create mode 100644 htable.c create mode 100644 htable.h create mode 100644 queue.c create mode 100644 queue.h create mode 100644 readme.adoc create mode 100755 rrqnet-cron.sh create mode 100644 rrqnet-cron.sh.8.adoc create mode 100644 rrqnet.8.adoc create mode 100644 rrqnet.c create mode 100755 set-source-route.sh create mode 100644 sockaddr.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..56f6f3d --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +debian/.debhelper +debian/debhelper-build-stamp +debian/files +debian/rrqnet.debhelper.log +debian/rrqnet.substvars +debian/rrqnet +rrqnet +rrqnet-cron.sh.8 +rrqnet-cron.sh.8.html +rrqnet.8 +rrqnet.8.html diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cee9a5b --- /dev/null +++ b/Makefile @@ -0,0 +1,59 @@ +SBINDIR = $(DESTDIR)/usr/local/sbin +ETCDIR = $(DESTDIR)/etc/rrqnet +MAN1DIR = $(DESTDIR)/usr/local/share/man/man1 +MAN8DIR = $(DESTDIR)/usr/local/share/man/man8 + +SBINFILES = rrqnet rrqnet-cron.sh +ETCFILES = set-source-route.sh +MAN1FILES = +MAN8FILES = rrqnet.8 rrqnet-cron.sh.8 +HTMLDOC = $(MAN8FILES:%=%.html) + +all: $(SBINFILES) $(ETCFILES) $(MAN1FILES) $(MAN8FILES) $(HTMLDOC) + +$(HTMLDOC): %.html: %.adoc + asciidoc -bhtml $^ + +$(MAN8FILES): %: %.adoc + a2x -d manpage -f manpage $^ + +rrqnet: LDFLAGS += -lpthread +rrqnet: rrqnet.c htable.h htable.c sockaddr.h queue.h queue.c + +rrqnet.E: rrqnet.c htable.c + $(CC) -W -Wall $^ > $@ + +COMPILEOPTS = -g -W -Wall +#COMPILEOPTS = -pg -no-pie -g -DGPROF + +$(filter-out %.sh,$(SBINFILES)): %: %.c + $(CC) $(COMPILEOPTS) -static -o $@ $^ $(LDFLAGS) + +.PHONY: clean +clean: + rm -f $(filter-out %.sh,$(SBINFILES)) + +# Installation targets + +INSTALLTARGETS = $(addprefix $(SBINDIR)/,$(SBINFILES)) +INSTALLTARGETS += $(addprefix $(ETCDIR)/,$(ETCFILES)) +INSTALLTARGETS += $(addprefix $(MAN1DIR)/,$(MAN1FILES)) +INSTALLTARGETS += $(addprefix $(MAN8DIR)/,$(MAN8FILES)) + +#INSTALL = install -b -S orig +INSTALL = install + +$(addprefix $(ETCDIR)/,conf.d keys): + mkdir -p $@ + +$(ETCCFG)/cron.sh: rrqnet-cron.sh + $(INSTALL) -D -T $< $@ + +$(SBINDIR)/% $(ETCDIR)/% $(MAN1DIR)/% $(MAN8DIR)/%: % + $(INSTALL) -D -T $< $@ + +install: $(INSTALLTARGETS) + +BUILDPACKAGE = -us -uc --build=full +deb: + PREFIX= INCLUDE_PREFIX=/usr dpkg-buildpackage $(BUILDPACKAGE) diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..12b5fbd --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +rrqnet (0.1) experimental; urgency=medium + + * Initial release (restart from udptap=0.2.5) + + -- Ralph Ronnquist Wed, 17 Jun 2020 20:34:04 +1000 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..a95bc78 --- /dev/null +++ b/debian/control @@ -0,0 +1,19 @@ +Source: rrqnet +Section: admin +Priority: optional +Maintainer: Ralph Ronnquist +Build-Depends: debhelper (>= 9), asciidoc, docbook-xml, libxslt1-dev, xsltproc, + docbook-xsl +Standards-Version: 3.9.8 +Homepage: +Vcs-Git: https://gitea.devuan.dev/devuan/rrqnet.git + +Package: rrqnet +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, bash, bridge-utils, iproute2, + lsof, net-tools, util-linux +Description: Packet tunneling over UDP, multiple channels + rrqnet is a bi-directional networking plug that channels packets + between a UDP port and either or a tap interface or standard + input/output. It is configured on the command line by declarations of + the remotes it may communicate with. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..02a524d --- /dev/null +++ b/debian/copyright @@ -0,0 +1,22 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: rrqnet +Source: + +Files: debian/* +Copyright: 2020 Ralph Ronnquist +License: GPL-2+ + This package 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 2 of the License, or + (at your option) any later version. + . + This package 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 + . + On Debian systems, the complete text of the GNU General + Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..9ba3ef6 --- /dev/null +++ b/debian/rules @@ -0,0 +1,27 @@ +#!/usr/bin/make -f +# See debhelper(7) (uncomment to enable) +# output every command that modifies files on the build system. +#export DH_VERBOSE = 1 + + +# see FEATURE AREAS in dpkg-buildflags(1) +#export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +# see ENVIRONMENT in dpkg-buildflags(1) +# package maintainers to append CFLAGS +#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic +# package maintainers to append LDFLAGS +#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed + + +%: + dh $@ + + +# dh_make generated override targets +# This is example for Cmake (See https://bugs.debian.org/641051 ) +#override_dh_auto_configure: +# dh_auto_configure -- # -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) + +override_dh_usrlocal: + true diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/debian/udptap.debhelper.log b/debian/udptap.debhelper.log new file mode 100644 index 0000000..24c133d --- /dev/null +++ b/debian/udptap.debhelper.log @@ -0,0 +1,22 @@ +dh_update_autotools_config +dh_auto_configure +dh_auto_build +dh_auto_test +dh_prep +dh_auto_install +dh_installdocs +dh_installchangelogs +dh_perl +dh_usrlocal +dh_link +dh_strip_nondeterminism +dh_compress +dh_fixperms +dh_missing +dh_strip +dh_makeshlibs +dh_shlibdeps +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb diff --git a/debian/udptap.substvars b/debian/udptap.substvars new file mode 100644 index 0000000..978fc8b --- /dev/null +++ b/debian/udptap.substvars @@ -0,0 +1,2 @@ +misc:Depends= +misc:Pre-Depends= diff --git a/htable.c b/htable.c new file mode 100644 index 0000000..02f08e9 --- /dev/null +++ b/htable.c @@ -0,0 +1,135 @@ +#include +#include +#include "htable.h" + +//// Generic hash table implementation + +// Determine the index for a key. On match, it returns the index into +// the table. On mismatch it returns -i-1 for the first free spot +// where the keyed element would fit. +static int htindex(htable *table,unsigned char *key) { + if ( table->data == 0 ) { + table->data = calloc( 256, sizeof( unsigned char* ) ); + table->size = 256; + } + unsigned int hash = (*table->hashcode)( table, key ) % table->size; + unsigned int i = hash; + int hole = -1; + for ( ;; ) { + unsigned char *x = table->data[ i++ ]; + if ( x == 0 ) { + return ( hole >= 0 )? -hole : - (int) i; + } + if ( x == (unsigned char *)1 ) { + if ( hole < 0 ) { + hole = i; + } + } else if ( memcmp( x + table->offset, key, table->esize ) == 0 ) { + return i-1; + } + if ( i >= table->size ) { + i = 0; + } + if ( i == hash ) { + break; + } + } + return -1; +} + +// Find the keyed element, and assign the x pointer, or assign 0. +// Returns 1 if element is found and 0 otherwise. +int htfind(htable *table,void *key,unsigned char **x) { + pthread_mutex_lock( &table->lock ); + int i = htindex( table, key ); + if ( i < 0 ) { + *x = 0; + pthread_mutex_unlock( &table->lock ); + return 0; + } + *x = table->data[ i ]; + pthread_mutex_unlock( &table->lock ); + return 1; +} + +// Forward +static void htgrow(htable *table); + +// Add the given element. +void htadd(htable *table,unsigned char *p) { + pthread_mutex_lock( &table->lock ); + if ( table->fill >= table->size / 2 ) { + htgrow( table ); + } + int i = htindex( table, p + table->offset ); + if ( i < 0 ) { + i = -i - 1; + if ( table->data[ i ] != 0 ) { + table->holes--; + } else { + table->fill++; + } + } else { + // There is a match already what to do? + } + table->data[ i ] = p; + pthread_mutex_unlock( &table->lock ); +} + +// Return the next element starting at i, or 0 if there are no more. +// Also increment the index to be of the element + 1, or -1 if there +// are no more elements. +static unsigned char *htnext(htable *table,int *i) { + if ( *i < 0 ) { + return 0; + } + unsigned char **p = table->data + *i; + unsigned char **e = table->data + table->size; + for ( ; p < e; p++ ) { + (*i) += 1; + if ( *p != 0 || *p != (unsigned char*)1 ) { + return *p; + } + } + (*i) = -1; + return 0; +} + +static void htrehash(htable *table,unsigned int size) { + htable old = *table; + table->data = calloc( size, sizeof( unsigned char * ) ); + table->size = size; + table->fill = 0; + table->holes = 0; + int i = 0; + unsigned char *p; + while ( ( p = htnext( &old, &i ) ) != 0 ) { + htadd( table, p ); + } + free( old.data ); +} + +static void htgrow(htable *table) { + if ( table->data == 0 ) { + table->data = calloc( 256, sizeof( unsigned char * ) ); + table->size = 256; + table->fill = 0; + table->holes = 0; + return; + } + htrehash( table, table->size << 8 ); +} + +// Delete the given element. +void htdelete(htable *table,unsigned char *p) { + pthread_mutex_lock( &table->lock ); + int i = htindex( table, p + table->offset ); + if ( i >= 0 ) { + table->data[ i ] = (unsigned char *)1; + table->holes += 1; + if ( table->holes > table->fill / 2 ) { + htrehash( table, table->size ); + } + } + pthread_mutex_unlock( &table->lock ); +} diff --git a/htable.h b/htable.h new file mode 100644 index 0000000..bc2614c --- /dev/null +++ b/htable.h @@ -0,0 +1,71 @@ +// A hashtable implementation with vectorized hashcode function. +// +// A hashtable is array of pointers, or the values 0 and 1, which mark +// unused and cleared slots respectively. +// +// Hash collision is handled by using the next usused or cleared slot +// by increasing, cycled indexing. An addition when the fill exceeds +// half the size causes a rehash into a new, double size table, +// without any cleared slots (i.e., the fill might be reduced after +// rehash, due to cleared slots). +// +// The fill count includes cleared entries, which counts deleted +// entries. A deletion that makes the number of cleared slots to +// exceed half the fill also causes a rehash into a new, same size +// table, without any cleared clots. +// +// The data elements have a key part, which is a contiguous portion of +// bytes at a given offset. The offset and size of the key are the +// same for all elements. +// +#ifndef HTABLE_H +#define HTABLE_H + +#define __USE_GNU 1 +#include + +// The hashtable head data structure: htable +typedef struct _htable { + unsigned char **data; // array of entries + unsigned int size; // total array size + unsigned int offset; // offset into elements for the key part + unsigned int esize; // byte size of the key part + unsigned int fill; // number of added elements + unsigned int holes; // number of deleted + int (*hashcode)(struct _htable *table,unsigned char *key); + pthread_mutex_t lock; +} htable; + +// Determine the index for a key. On match, it returns the index into +// the table. On mismatch it returns -i-1 for the first free spot +// where the keyed element would fit. +//int htindex(htable *table,unsigned char *key); + +// Find the keyed element, and assign the x pointer, or assign 0. +// Returns 1 if element is found and 0 otherwise. +int htfind(htable *table,void *key,unsigned char **x); + +// Add the given element. +void htadd(htable *table,unsigned char *p); + +// Return the next element starting at i, or 0 if there are no more. +// Also increment the index to be of the element + 1, or -1 if there +// are no more elements. +//unsigned char *htnext(htable *table,int *i); + +// Delete the given element. +void htdelete(htable *table,unsigned char *p); + +// Special macro for htable initialization giving the record type, the +// key field and hashcode funtion. +// type = (base) data type for the elements +// member = the key field +// hashcodefn = function computing hashcode for a key +// assigns .compare = memcmp +#define HTABLEINIT(type,member,hashcodefn) \ + { .data = 0, .size = 0, .fill = 0, \ + .holes = 0, .offset = offsetof( type, member ), \ + .esize = sizeof( ((type *)0)->member ), .hashcode = hashcodefn, \ + .lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP } + +#endif // HTABLE_H diff --git a/queue.c b/queue.c new file mode 100644 index 0000000..0ec3132 --- /dev/null +++ b/queue.c @@ -0,0 +1,71 @@ +#include +#include +#include "queue.h" + +void Queue_addItem(Queue *list,QueueItem *item) { + if ( pthread_mutex_lock( &list->mutex ) ) { + perror( "FATAL" ); + exit( 1 ); + } + item->next = 0; // just in case + if ( list->last ) { + list->last->next = item; + } else { + list->head = item; + } + list->last = item; + if ( sem_post( &list->count ) ) { + perror( "FATAL" ); + exit( 1 ); + } + if ( pthread_mutex_unlock( &list->mutex ) ) { + perror( "FATAL" ); + exit( 1 ); + } +} + +QueueItem *Queue_getItem(Queue *list) { + QueueItem *item; + if ( sem_wait( &list->count ) ) { + perror( "FATAL" ); + exit( 1 ); + } + if ( pthread_mutex_lock( &list->mutex ) ) { + perror( "FATAL" ); + exit( 1 ); + } + item = list->head; + list->head = item->next; + if ( list->head == 0 ) { + list->last = 0; + } + if ( pthread_mutex_unlock( &list->mutex ) ) { + perror( "FATAL" ); + exit( 1 ); + } + return item; +} + +void Queue_initialize(Queue *list,int n,size_t size) { + if ( pthread_mutex_lock( &list->mutex ) ) { + perror( "FATAL" ); + exit( 1 ); + } + if ( list->head == 0 ) { + int i = 0; + for ( ; i < n; i++ ) { + QueueItem *x = (QueueItem *) calloc( 1, size ); + if ( list->head ) { + list->last->next = x; + } else { + list->head = x; + } + list->last = x; + } + sem_init( &list->count, 0, n ); + } + if ( pthread_mutex_unlock( &list->mutex ) ) { + perror( "FATAL" ); + exit( 1 ); + } +} diff --git a/queue.h b/queue.h new file mode 100644 index 0000000..76478a8 --- /dev/null +++ b/queue.h @@ -0,0 +1,25 @@ +#ifndef queue_H +#define queue_H + +#include "sockaddr.h" +#include +#include + +typedef struct _QueueItem { + struct _QueueItem *next; + char data[]; +} QueueItem; + +typedef struct _Queue { + QueueItem *head; + QueueItem *last; + sem_t count; + pthread_mutex_t mutex; +} Queue; + +extern void Queue_addItem(Queue *list,QueueItem *item); +extern QueueItem *Queue_getItem(Queue *list); +extern void Queue_initialize(Queue *list,int n,size_t size); + +#endif + diff --git a/readme.adoc b/readme.adoc new file mode 100644 index 0000000..cac4a71 --- /dev/null +++ b/readme.adoc @@ -0,0 +1,19 @@ +About rrqnet +============ + +This repository holds the sources for *rrqnet*, which concern VPN +building over UDP transport. It operates at Ethernet level, which +means it transports both ipv4 and ipv6, and it can thus be used for +ipv6 over ipv4 tunneling, or ipv4 tunneling over ipv6, as well as ipv4 +VPN or ipv6 VPN. + +MAN PAGES +--------- + * link:rrqnet.8.adoc[The rrqnet man page] + * link:rrqnet-cron.sh.8.adoc[The rrqnet-cron.sh man page] + +Devuan Packages +--------------- + + * The *rrqnet* package contains the binaries and their man pages, +as well as the utility scripting. diff --git a/rrqnet-cron.sh b/rrqnet-cron.sh new file mode 100755 index 0000000..68d04e6 --- /dev/null +++ b/rrqnet-cron.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# +# Cron-driven bot to start a rrqnet cables or switch unless already up + +RRQNET=/usr/local/sbin/rrqnet + +function start-switch() { + . $1 + exec $RRQNET $VERBOSE -4 $PORT ${VPN[@]} +} + +function start-cable() { + . $1 + ip link show dev $TAP > /dev/null || \ + { ip tuntap add $TAP mode tap ; ip link set dev $TAP up ; } + [ -z "$MAC" ] || ifconfig $TAP | grep -q "ether $MAC" || \ + ifconfig $TAP hw ether $MAC + [ -z "$IP" ] || ip addr show dev $TAP | grep -q $IP || \ + ip addr add $IP dev $TAP + [ -z "$BR" ] || brctl show | grep -q $TAP || \ + brctl addif $BR $TAP + exec $RRQNET $VERBOSE -4 ${OPTIONS[@]} -t $TAP $PORT ${VPN[@]} +} + +for CABLE in $* ; do + CONF=/etc/rrqnet/conf.d/$CABLE.conf + eval $(grep ^PORT= $CONF) + lsof -i :$PORT > /dev/null && continue + eval $(grep ^TAP= $CONF) + LOG=/tmp/$CABLE.log + if [ -z "$TAP" ] ; then + ( start-switch $CONF < /dev/null >> $LOG 2>&1 & ) + else + ( start-cable $CONF /dev/null >> $LOG 2>&1 & ) + fi +done diff --git a/rrqnet-cron.sh.8.adoc b/rrqnet-cron.sh.8.adoc new file mode 100644 index 0000000..3c6e39f --- /dev/null +++ b/rrqnet-cron.sh.8.adoc @@ -0,0 +1,125 @@ +rrqnet-cron.sh(8) +================= +:doctype: manpage +:revdate: {sys:date "+%Y-%m-%d %H:%M:%S"} + +NAME +---- +rrqnet-cron.sh - Management script to uphold a *rrqnet* plug. + +SYNOPSIS +-------- +*rrqnet-cron.sh* _vpn_ ... + +DESCRIPTION +----------- + +*rrqnet-cron.sh* is a management script for upholding a *rrqnet* plug +for a nominated VPN confguration. The given _vpn_, or several, is the +pathname relative to the configuration root directory and with a ++.conf+ extension added, as in +/etc/rrqnet/conf.d/+*vpn*+.conf+. + +The following is a configuration file example: + +./etc/rrqnet/conf.d/tap0-client.conf +---- +TAP=tap0 +MAC=02:00:00:00:01:00 +BR= +IP=192.168.10.2 +PORT=1500 +OPTIONS=( ) +VPN=( 10.61.4.72:2020=/etc/rrqnet/keys/example.key ) +VERBOSE=-v +---- + + * The `TAP` assignment names the tap interface to use. + * The optional MAC assignment, if provided, tells *rrqnet-cron.sh* to + set the Ethernet address of the tap interface as given. + * The optional `BR` assignment, if provided, tells *rrqnet-cron.sh* + to add the tap interface to the bridge upon start. + * The optional `IP` assignment, if provided, tells *rrqnet-cron.sh* + how to configure the tap interface when it is brought up. If empty, + the tap interface is brought up without confgiured IP address. + * The `PORT` assignment declares which port *rrqnet* should listen + on. It will listen on that port on all interfaces. + * The optional `OPTIONS` is intended for the -B and -T options to + *rrqnet*. + * The `VPN` assignment declares the remotes for *rrqnet*. + * The optional `VERBOSE` assignment, which must be `-v`, `-vv` or + `-vvv` unless empty, defines the verbosity level for *rrqnet*. + +The above example declares an uplink remote at example ivp4 address +`10.61.4.72`, port 2020, and using a transport encryption key. The +remote host at that IP address should have a corresponding +declaration, perhaps as follows: + +./etc/rrqnet/conf.d/tap0-server.conf +---- +TAP=tap0 +IP=192.168.10.1 +PORT=2020 +VPN=( 0.0.0.0/0=/etc/rrqnet/keys/example.key ) +---- + +That "server" declaration is allows UDP packets from any host and +port, requiring the them to use the same transport encryption key. The +*rrqnet* "server" plug then works like a switch, which forwards +packets between connections as well as to and from the tap. Actual +connections are identified by the remote MAC addresses, and it's up to +the remote ends to resolve IP addresses to the MAC addresses on the +virtual net, which in the example would be +192.168.10.0/24+. + +The +VPN+ variable may have multiple remote declarations, and include +both up-links and down-links, with or without thransport encryption +keys. E.g., +---- +VPN=( 192.168.0.0/16:1400 10.61.4.72:2020=/sec/example.key ) +---- + +A VPN assignment like that would both allow remotes in IP range ++192.168.0.0/16+, port 1400, without transport key, and have an +up-link to that example "server" remote above (though, for the sake of +example, having its key residing at a different pathname). + +crontab set up +~~~~~~~~~~~~~~ + +The script *rrqnet-cron.sh* is intended to be set up in *crontab*, by +a line as follows: +---- +* * * * * /usr/local/sbin/rrqnet-cron.sh tap0-client +---- + +By that *crontab* line, the script will be invoked every minute for +unsuting that the *rrqnet* plug declared by ++/etc/rrqnet/conf.d/tap0-client.conf+, is still running, and otherwise +start or restart it. + +The script uses a lock file that gets named by the `TAP` assignment, +as in +/var/lock/rrqnet-tap0+, for the example. This allows an +alternative management set up, for a *rrqnet* cable to be maintained +with an almost immediate restart when it goes down, through a simple +loop like the following: +---- +# while flock /var/lock/rrqnet-tap0 ; do rrqnet-cron.sh tap0 done +---- +That control loop would be waiting for the running *rrqnet* to release +the file lock on +/var/lock/rrqnet-tap0+, and then, almost immediately +restart the virtual cable. + +NOTES +----- + +Note that *rrqnet-cron.sh* sources the configuration file and exits +after optionally spawning a *rrqnet* daemon. On may therefore safely +just change the cable set up, and kill *rrqnet* in order apply that +changed set up. + +SEE ALSO +-------- +*rrqnet(8)* - Packet tunneling over UDP, multiple channels + +AUTHOR +------ +Ralph Rönnquist diff --git a/rrqnet.8.adoc b/rrqnet.8.adoc new file mode 100644 index 0000000..5afd0d8 --- /dev/null +++ b/rrqnet.8.adoc @@ -0,0 +1,444 @@ +rrqnet(8) +========= +:doctype: manpage +:revdate: {sys:date "+%Y-%m-%d %H:%M:%S"} +:COLON: : +:EQUALS: = + +NAME +---- +rrqnet - Packet tunneling over UDP, multiple channels + +SYNOPSIS +-------- +*rrqnet* [ OPTIONS ] _port_ ( _remote_ [*-i* _mac_]* )* + +DESCRIPTION +----------- +*rrqnet* is a bi-directional networking plug that channels Ethernet +packets between a UDP port and either a tap interface or standard +input/output. It is configured on the command line by declarations of +the remotes it may communicate with. + +OPTIONS +------- + +Note that any options must be given in the fixed order: + + [-v] [-4] [-B n] [-T n] [-m mcast] [-t tap] + +*-v*:: + +This tells *rrqnet* to log its operation on +stderr+. Use *-vv* to +also see logs about connections and messaging, or *-vvv* for *rrqnet* +to be insanely verbose on +stderr+ about virtually everything. + +*-4*:: + +This directs *rrqnet* to use an ipv4-only socket for its UDP. By +default it opens a dual ipv4/ipv6 socket and internally it then uses +address mapping for ipv4 (i.e. the ::ffff/96 prefix). + +*-B* _n_:: + +This sets the number of receive buffers *rrqnet* should use. The +default is computed to be twice the number of dispatch threads (see +*-T* below). Receive buffers are pre-allocated and recycled. + +*-T* _n_:: + +This sets the number of dispatch threads *rrqnet* should use. The +default is 5. The (additional) main thread handles packet reception, +where it immediately puts received packets into the buffer queue which +is serviced by the dispatch threads for optional decryption, dispatch +decision, optional encryption and delivery. + +*-m* _mcast_:: + +This tells *rrqnet* to open an ipv4 UDP multicast channel as an +additional remote channel. + +*-t* _tap_:: + +This tells *rrqnet* to open the nominated tap (the _tap_ interface +name) as local channel. + +* When a tap is used, stdin and stdout are closed, but stderr remains +open. + +* Without a *-t* argument, *rrqnet* will merely operate as a virtual +switch among its channels. + +* With "*-*" as tap name, *rrqnet* will use stdin/stdout as local +networking channel in a format compatible with VDE plugs. + +_address-block[:port][=cryptfile]_ [ *-i* _mac_[,_mac_]* ]:: + +Remotes are declared as +ipv4+ or +ipv6+ network address blocks +optionally with port and transport encryption key file pathname, and +optionally with specific MAC addresses (in a comma separated list) to +ignore. Note that an ipv6 address block might need surrounding square +brackets, to avoid confusion with the port number. + +DETAILED DESCRIPTION +-------------------- + +The intended use of *rrqnet* is to provide VPN (virtual private +network) connectivity between hosts. Each VPN host runs its own +*rrqnet* daemon to channel the traffic to/from tap interfaces on the +hosts via UDP messaging between the hosts. + +*rrqnet* is prepared for almost any network layout, even including a +collection of fully connected hosts, although the more common is a +"star' formation. See the EXAMPLES section for inspiration. + +*rrqnet* includes logic aiming to protect against broadcast cycles. +Howewer it does not have the more advanced spanning tree logic that is +offered by bridge interfaces. In general it's best to avoid cycles and +rather run several *rrqnet* on a host with their local taps connected +in a bridge interface. + +By default *rrqnet* opens an +ipv6+ socket on the given port. This +mode handles both +ipv6+ and +ipv4+ remotes with +ipv4+ remotes +handled by means of ipv6-mapped address translations. If *-4* is +given, *rrqnet* opens an +ipv4+ socket instead, and it cannot then +have +ipv6+ remotes. + +A *rrqnet* daemon delivers the packets received from the local end, +i.e., the _tap_ or _stdio_, to known remote ends according the +targeted MAC addresses and established remote channels. Likewise, +packets from remotes are delivered to the local end or to other +remotes according to the targeted MAC addresses. If a packet is an +Ethernet broadcast, it is delivered to all (known) channels except the +one it came from. + +If *rrqnet* is started without *-t* option it will operate like an +Ethernet switch that provides connectivity among its +UDP+ channels +without involving the host network other than for the tunneling. + + +REMOTE DECLARTIONS +~~~~~~~~~~~~~~~~~~ + +.ipv4 address block + +This format declares remotes by +ipv4+ address, with optional network +prefix length (0-32), optional port (1-65535) and/or optional key file +pathname. *updtap* will accept packets from sources that match. If the +network prefix length, +n+, is omitted or given as 32, and a port is +given, then the remote is taken as an _uplink_. + +.matching ipv4 uplink and downlink +==== +---- +[1.2.3.4]# rrqnet -t vpn0 2300 5.6.7.1:2300=/sec/vpn0.key +[5.6.7.1]# rrqnet -t vpn0 2300 1.2.3.0/24:2300=/sec/vpn0.key +---- +==== + +.plain ipv6 address block + +This format declares remotes by ipv6 address, with optional network +prefix length (0-128) and/or optional key file pathname. *updtap* will +accept packets from sources that match. This format (without square +brackets) is without port number part, and it thus only declares a +prefix mask for allowed sender hosts. + +.ipv6 address block within square brackets + +This format declares remotes by ipv6 address, with optional network +prefix length (0-128) within square brackets, then optionally a port +number and/or an optional key file pathname. *updtap* will accept +packets from sources that match. If the network prefix length, +n+, is +omitted, or given as 128, and a port number is given, then it declares +an _uplink_. + +.matching ipv6 uplink and downlink +==== +---- +[fd::4]# rrqnet -t vpn0 2300 '[fe::1:4]:2300=/sec/vpn0.key' +[fe::1:4]# rrqnet -t vpn0 2300 '[fd::/120]:2300=/sec/vpn0.key' +---- +==== + +Remotes are declarations to match the source IP addresses for UDP +traffic. It is either a full host and port address, or an address +block with or without port number. A full network address and port +(e.g +[fe::1:4]:2300+ of example 2) declares an _uplink_ that the +declaring *rrqnet* daemon will establish and maintain by means of +regular heartbeat messaging. An address block declaration defines the +mask for allowed incoming connections, aka _downlinks_, that teh +declaring dameon expects are maintained as uplinks by the remote +*rrqnet* daemons. + +The *-i* option, if used for a remote declaration, is followed by a +comma separated list of the MAC addresses to ignore on the associated +channel. The *rrqnet* daemon will then just drop any packet with those +MAC addresses (whether source or destination) on the remotes of the +channel. + +MULTICAST CHANNEL +----------------- + +With the *-m* option, the *rrqnet* daemon also listens for packets on +the declared ipv4 multicast address. It is then treated as a separate, +persistent remote channel. + +A multicast channel is declared using the format: +*ipv4:port{=keyfile}*. I.e. it includes the multicast ipv4 address, +the port number (1-65535), and optionally a key file pathname. The +multicast channel is an additional communication channel, but anything +received on it will be treated as being from the multicast IP rather +than the actual source IP. + +.multicast example without other remotes +==== +---- +# rrqnet -m 244.0.2.1:2000 -t vpn0 +---- +==== + +The multicast channel is compatible with QEMU multicast socket. + +TRANSPORT ENCRYPTION +-------------------- + +Transport encryption is added to a channel by menas of using a shared +key. This is a sizable file, say 1 Mb, of binary data that is used for +scrambling the network packets. For example, 1 Mb random data is fine. + +.preparing 1 Mb key file with random content +==== +---- +dd if=/dev/random of=/sec/keyfile bs=1M count=1 +---- +==== + +The key file needs to be copied to all hosts. + +A channel that has a key file is declared by appending it to the +channel declaration, as in the following example. + +.another example of a downlink with encryption +==== +---- +[10.0.0.1]# rrqnet -v -t tap0 1400 10.2.0.0/16:1400=/sec/keyfile +---- +==== + +That declaration says that all channels with hosts of ipv4 address ++10.2.0.0/16+, port 1400, use the file +/sec/keyfile+ for transport +encryption. + + +FURTHER EXAMPLES +---------------- + +Simple rrqnet set up (ipv6) +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This is an example set up for connecting two hosts with *rrqnet*, +without transport encryption. We pretend these hosts are mutually +reachable with ipv6 addresses +fe::2+ and +fe::1:3+ respectively, and +we want to use the ipv6 network +fd::1000:0/120+ over *rrqnet*. A +nominal set up might then be as follows: + +.simple rrqnet set up (ipv6) +==== +---- +[fe::2]# ip tuntap add tap0 mode tap +[fe::2]# ifconfig tap0 fd::1000:10/120 up +[fe::2]# rrqnet -v -t tap0 1400 '[fe::1:3]:1400' & +-- +[fe::1:3]# ip tuntap add tap0 mode tap +[fe::1:3]# ifconfig tap0 fd::1000:20/120 up +[fe::1:3]# rrqnet -v -t tap0 1400 '[fe::2]:1400' & +---- +==== + +Thus, the host +fe::2+ is set up with a tap, +tap0+, having +ipv6+ +address and net +fd::1000:10/120+, and a *rrqnet* daemon for the tap +and UDP port +1400+ that uplinks to a remote *rrqnet* at +fe::1:3+ +port +1400+. Similarly, the host +fe::1:3+ is set up with a tap ++tap0+, having +ipv6+ address and net +fd::1000:20/120+, and a *rrqnet* +daemon for the tap and UDP port +1400+ that uplinks to a remote +*rrqnet* at +fe::2+ port +1400+. + +This example also needs ipv6 address resolution set up, which uses the +MAC addresses of the two taps. Let's say it's +02:00:00:00:00:02+ for ++tap0+ on +fe::2+ and +04:00:00:00:00:04+ for +tap0+ on +fe::1:3+. +Then address resolution is established with the following: + +.example of ipv6 address resolution set up +==== +---- +[fe::2]# ip neigh add fd::1000:20 dev tap0 lladdr 04:00:00:00:00:04 +-- +[fe::1:3]# ip neigh add fd::1000:10 dev tap0 lladdr 02:00:00:00:00:02 +---- +==== + +Simple rrqnet set up (ipv4) +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is an example set up for connecting two hosts with *rrqnet*, +without transport encryption. We pretend these hosts are mutually +reachable with ipv4 addresses +10.0.0.1+ and +192.168.0.1+ +respectively, and we want to use the ipv4 network +10.100.100.0/24+ +over *rrqnet*. A nominal set up might be as follows: + +.Simple rrqnet set up (ipv4) +==== +---- +[10.0.0.1]# ip tuntap add tap0 mode tap +[10.0.0.1]# ifconfig tap0 10.100.100.1/24 up +[10.0.0.1]# rrqnet -v -t tap0 1400 192.168.0.1:1400 & +-- +[192.168.0.1]# ip tuntap add tap0 mode tap +[192.168.0.1]# ifconfig tap0 10.100.100.2/24 up +[192.168.0.1]# rrqnet -v -t tap0 1400 10.0.0.1:1400 & +---- +==== + +Thus, the host +10.0.0.1+ is set up with a tap, +tap0+, having ipv4 +address and net +10.100.100.1/24+, and a *rrqnet* daemon for the tap +and UDP port +1400+ that uplinks to a remote *rrqnet* at +192.168.0.1+ +port +1400+. Similarly, the host +192.168.0.1+ is set up with a tap ++tap0+, having +ipv4+ address and net +10.100.100.2/24+, and a +*rrqnet* daemon for the tap and UDP port +1400+ that uplinks to a +remote *rrqnet* at +10.0.0.1+ port 1400. + +The kernel automagically performs ipv4 address resolution to learn the +MAC addresses associated with the remote ipv4 addresses through the +taps. + +rrqnet set up through NAT +~~~~~~~~~~~~~~~~~~~~~~~~~ + +If one of the hosts, say +192.168.0.1+ is behind a NAT router with +different IP, say, a dynamic IP on the net +10.2.0.0/16+, we need a +different set up. In this scenario, the first host would be set up as +a "server" and the second a client that would utilize the router's NAT +function for return traffic. The set up would be as follows: + +.rrqnet set up through NAT +==== +---- +[10.0.0.1]# ip tuntap add tap0 mode tap +[10.0.0.1]# ifconfig tap0 10.100.100.1 up +[10.0.0.1]# rrqnet -v -t tap0 1400 10.2.0.0/16:1400 & +-- +[192.168.0.1]# ip tuntap add tap0 mode tap +[192.168.0.1]# ifconfig tap0 10.100.100.2 up +[192.168.0.1]# rrqnet -v -t tap0 1400 10.0.0.1:1400 & +---- +==== + +Thus, the "server" is set up to allow connections from any host on the +network +10.2.0.0/16+, port 1400, while the "client" is set up the +same way as in the simple example above. The client will establish and +uphold the connection by virtue of its 30 second "heart beat", and +return traffic will be channeled via the router's NAT function. + +Note that the server sees the _external_ IP of the client and not its +_internal_ IP. The server's *rrqnet* therefor has a remote declaration +to allow messages from that external IP, and in the example case, even +an address block of a 16 bit common prefix (the choice of a 16 bit +prefix is merely for the sake of this example). + +Multiple client hosts +~~~~~~~~~~~~~~~~~~~~~ + +In a "client-server" set up, there can be any number of "client" +hosts. However, the "clients" behind a common NAT router must then use +distinct ports as otherwise the router will be confused about the +return traffic. + +With multiple remote channels, a *rrqnet* daemon serves as a network +switch that forwards traffic in between the channels as well as to and +from the "server" tap. The daemon also forwards Ethernet broadcasts +out on all established channels in support of ARP messaging. + +Further, a *rrqnet* daemon may be both a "server" with down-link +channels, and a "client" with one or more up-link channels, all at the +same time. Such a daemon forwards traffic between all established +channels by means of the Ethernet addresses, as well as broadcasts +onto all channels + +Stdio network +~~~~~~~~~~~~~ +The *rrqnet* daemon may be set up to use standard input/output rather +than a tap for local network traffic. This operation mode has some +rare use cases, such as linking two *rrqnet* daemons, or connecting to +a VDE network. For example: + +.stdio network between two updtap plugs +==== +---- +# dpipe rrqnet 1400 0.0.0.0/0=keyfile0 = rrqnet 1401 0.0.0.0/0=keyfile1 & +---- +==== + +That example set up would make a connection between the two "server" +daemons operating at different UDP ports, accepting messages from any +ipv4 host, where port +1400+ has +keyfile0+ for transport encryption, +and +1401+ has +keyfile1+ for transport encryption. + +Another example would be for connecting the *rrqnet* traffic to a VDE +network via a +vde_plug+ as in the following example: + +.stdio network to a vde_plug +==== +---- +# dpipe rrqnet 1400 0.0.0.0/0 = vde_plug /tmp/vde1.ctl & +---- +==== + +Note that *rrqnet* and +vde_plug+ use compatible stdio packet +representation. + +NOTES +----- + +The UDP receiver in *rrqnet* accepts packets from the specified remote +ends only, but it doesn't perform any payload verification. Messages +smaller than 12 bytes are taken as "heartbeats", and larger messages +are first decrypted as applicable, then treated as Ethernet messages +and delivered according to their destination MAC addresses. *rrqnet* +versions after 0.2.3 adds some data to the Ethernet packet in the UDP +communication. + +*rrqnet* bridges its connections, and forwards Ethernet broadcasts to +all known end-points except the incoming one. The input logic takes +care of avoiding broadcast cycles. Still, the safe advice is that one +should avoid a cyclic *rrqnet* set up except for single-hop cliques of +host groups. + +*rrqnet* does not have any Spanning Tree Logic (STL), but only some +simple timing logic based on binding MAC addresses to remotes. That +binding is sticky for a short time: 6s for broadcast and 20s for +unicast. Any packet received during that time from the same MAC +address via another remote is dropped. Also, a downlink without +incoming traffic for 3 minutes is considered stale. + +*rrqnet* sends a "heartbeat" of an empty UDP message on its uplinks +every 30 seconds. This is done in order to maintain the channel with +the remote end without actually binding any MAC address. + +When the local input is a tap, *rrqnet* closes standard input and +standard output, but not standard error, before entering the packet +handling loop. + +Using +-t -+ for stdin/stdout packet traffic is compatible with ++vde_plug+. + +SEE ALSO +-------- +*rrqnet-cron.sh(8)* - Management script to uphold a *rrqnet* plug. + +*udptun(8)* - Packet tunneling over UDP. + +*vde_plug(1)* - Virtual Distributed Ethernet plug. + +AUTHOR +------ +Ralph Rönnquist diff --git a/rrqnet.c b/rrqnet.c new file mode 100644 index 0000000..ba5bb58 --- /dev/null +++ b/rrqnet.c @@ -0,0 +1,1648 @@ +// This program is a UDP based tunneling of stdin/out Ethernet packets. +// +// A rrqnet program is a bi-directional networking plug that channels +// packets between a UDP port and stdin/out. It is configured on the +// command line with channel rules that declares which remotes it may +// communicate with. Allowed remotes are specified in the format +// "ip[/n][:port][=key]", to indicate which subnet and port to accept, +// and nominating the associated keyfile to use for channel +// encryption. +// +// The program maintains a table of actualized connections, as an +// association between MAC addresses and IP:port addresses. This table +// is used for resolving destination for outgoing packets, including +// the forwarding of broadcasts. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "htable.h" +#include "queue.h" + +//// Data structures + +// "Private Shared Key" details. +struct PSK { + char *keyfile; + unsigned int seed; // Encryption seed + unsigned char *key; // Encryption key + unsigned int key_length; // Encryption key length +}; + +// Compacted IP address ipv4/ipv6 +struct CharAddr { + int width; // 4=ipv4 and 16=ipv6 + union { + unsigned char bytes[16]; + struct in_addr in4; + struct in6_addr in6; + }; +}; + +// Details of channel rules. +struct Allowed { + char *source; // Orginal rule + struct CharAddr addr; + unsigned int bits; // Bits of IP prefix + unsigned short port; // Port (0=any) + struct PSK psk; // Associated key + htable ignored_mac; // MAC to ignore by this spec +}; + +// Details of actualized connections. +struct Remote { + struct SockAddr uaddr; + struct Allowed *spec; // Rule being instantiated + struct timeb rec_when; // Last received packet time, in seconds +}; + +// Details of an interface at a remote. +struct Interface { + unsigned char mac[6]; // MAC address used last (key for by_mac table) + struct timeb rec_when; // Last packet time, in seconds + struct Remote *remote; +}; + +// Maximal packet size .. allow for jumbo frames (9000) +#define BUFSIZE 10000 + +typedef struct _PacketItem { + QueueItem base; + int fd; + struct SockAddr src; + ssize_t len; + unsigned char buffer[ BUFSIZE ]; +} PacketItem; + +// heartbeat interval, in seconds +#define HEARTBEAT 30 +#define HEARTBEAT_MILLIS ( HEARTBEAT * 1000 ) + +// Macros for timing, for struct timeb variables +#define TIMEB_MILLIS(TM) (((int64_t) (TM)->time * 1000) + (TM)->millitm ) +#define DIFFB_MILLIS(TM1,TM2) ( TIMEB_MILLIS(TM1) - TIMEB_MILLIS(TM2) ) + +// RECENT(T,M) is the time logic for requiring a gap time (in +// milliseconds) before shifting a MAC to a new remote. The limit is +// 6000 for broadcast and 20000 for unicast. +#define RECENT(T,M) ((M) < ((T)? 6000 : 20000 )) + +// VERYOLD_MILLIS is used for discarding downlink remotes whose latest +// activity is older than this. +#define VERYOLD_MILLIS 180000 + + +////////// Variables + +// Allowed remote specs are held in a table sorted by IP prefix. +static struct { + struct Allowed **table; + unsigned int count; +} allowed; + +// Actual remotes are kept in a hash table keyed by their +uaddr+ +// field, and another hash table keps Interface records for all MAC +// addresses sourced from some remote, keyed by their +mac+ field. The +// latter is used both for resolving destinations for outgoing +// packets, and for limiting broadcast cycles. The former table is +// used for limiting incoming packets to allowed sources, and then +// decrypt the payload accordingly. +static int hashcode_uaddr(struct _htable *table,unsigned char *key); +static int hashcode_mac(struct _htable *table,unsigned char *key); +static struct { + htable by_mac; // struct Interface hash table + htable by_addr; // struct Remote hash table +} remotes = { + .by_mac = HTABLEINIT( struct Interface, mac, hashcode_mac ), + .by_addr = HTABLEINIT( struct Remote, uaddr, hashcode_uaddr ) +}; + +#define Interface_LOCK if ( pthread_mutex_lock( &remotes.by_mac.lock ) ) { \ + perror( "FATAL" ); exit( 1 ); } + +#define Interface_UNLOCK if (pthread_mutex_unlock( &remotes.by_mac.lock ) ) { \ + perror( "FATAL" ); exit( 1 ); } + +#define Interface_FIND(m,r) \ + htfind( &remotes.by_mac, m, (unsigned char **)&r ) + +#define Interface_ADD(r) \ + htadd( &remotes.by_mac, (unsigned char *)r ) + +#define Interface_DEL(r) \ + htdelete( &remotes.by_mac, (unsigned char *) r ) + +#define Remote_LOCK if ( pthread_mutex_lock( &remotes.by_addr.lock ) ) { \ + perror( "FATAL" ); exit( 1 ); } + +#define Remote_UNLOCK if ( pthread_mutex_unlock( &remotes.by_addr.lock ) ) { \ + perror( "FATAL" ); exit( 1 ); } + +#define Remote_FIND(a,r) \ + htfind( &remotes.by_addr, (unsigned char *)a, (unsigned char **) &r ) + +#define Remote_ADD(r) \ + htadd( &remotes.by_addr, (unsigned char *) r ) + +#define Remote_DEL(r) \ + htdelete( &remotes.by_addr, (unsigned char *) r ) + +#define Ignored_FIND(a,m,x) \ + htfind( &a->ignored_mac, m, (unsigned char **)&x ) + +#define Ignored_ADD(a,x) \ + htadd( &a->ignored_mac, (unsigned char *)x ) + +// Input channels +static int stdio = 0; // Default is neither stdio nor tap +static char *tap = 0; // Name of tap, if any, or "-" for stdio +static int tap_fd = 0; // Also used for stdin in stdio mode +static int udp_fd; +static int threads_count = 0; +static int buffers_count = 0; + +// Setup for multicast channel +static struct { + struct ip_mreqn group; + struct SockAddr sock; + int fd; + struct PSK psk; +} mcast; + +// Flag to signal the UDP socket as being ipv6 or not (forced ipv4) +static int udp6 = 1; + +// Flag whether to make some stderr outputs or not. +// 1 = normal verbosity, 2 = more output, 3 = source debug level stuff +static int verbose; + +// Note: allows a thread to lock/unlock recursively +static pthread_mutex_t crypting = PTHREAD_MUTEX_INITIALIZER; + +// Note: allows a thread to lock/unlock recursively +static pthread_mutex_t printing = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; + +#define PRINTLOCK \ + if ( pthread_mutex_lock( &printing ) ) { perror( "FATAL" ); exit(1); } + +#define PRINTUNLOCK \ + if ( pthread_mutex_unlock( &printing ) ) { perror( "FATAL" ); exit(1); } + +#define PRINT( X ) { PRINTLOCK; X; PRINTUNLOCK; } + +#define VERBOSEOUT(fmt, ...) \ + if ( verbose >= 1 ) PRINT( fprintf( stderr, fmt, ##__VA_ARGS__ ) ) + +#define VERBOSE2OUT(fmt, ...) \ + if ( verbose >= 2 ) PRINT( fprintf( stderr, fmt, ##__VA_ARGS__ ) ) + +#define VERBOSE3OUT(fmt, ...) \ + if ( verbose >= 3 ) PRINT( fprintf( stderr, fmt, ##__VA_ARGS__ ) ) + +// A buffer for reading stdin in fragmented way, to allow outgoing +// packets during the reading of stdin packets, if it's fragmented. +static struct { + unsigned char buffer[ BUFSIZE ]; // Packet data + unsigned int end; // Packet size + unsigned int cur; // Amount read so far +} input; + +// The actual name of this program (argv[0]) +static unsigned char *progname; + +// Compute a hashcode for the given SockAddr key +static int hashcode_uaddr( + __attribute__((unused)) struct _htable *table,unsigned char *key) +{ + struct SockAddr *s = (struct SockAddr *) key; + key = (unsigned char*) &s->in; + unsigned char *e = key + ( ( s->in.sa_family == AF_INET )? + sizeof( struct sockaddr_in ) : + sizeof( struct sockaddr_in6 ) ); + int x = 0; + while ( key < e ) { + x += *(key++); + } + return x; +} + +// Compute a hashcode for the given MAC addr key +static int hashcode_mac(struct _htable *table,unsigned char *key) { + int x = 0; + int i = 0; + if ( table->size == 256 ) { + for ( ; i < 6; i++ ) { + x += *(key++); + } + return x; + } + uint16_t *p = (uint16_t *) key; + for ( ; i < 3; i++ ) { + x += *( p++ ); + } + return x; +} + +// Make a text representation of bytes as ipv4 or ipv6 +static char *inet_nmtoa(unsigned char *b,int w) { + static char buffer[20000]; + int i = 0; + char * p = buffer; + if ( w == 4 ) { + sprintf( p,"%d.%d.%d.%d", b[0], b[1], b[2], b[3] ); + } else if ( w == 16 ){ + sprintf( p, + "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", + b[0], b[1], b[2], b[3], + b[4], b[5], b[6], b[7], + b[8], b[9], b[10], b[11], + b[12], b[13], b[14], b[15] ); + } else { + VERBOSE3OUT( "HEX data of %d bytes\n", i ); + for ( ; i < w && i < 19000; i++, p += 3 ) { + sprintf( p, "%02x:", b[i] ); + } + if ( w > 0 ) { + *(--p) = 0; + } + } + return buffer; +} + +// Form a MAC address string from 6 MAC address bytes, into one of the +// 4 static buffer, whose use are cycled. +static char *inet_mtoa(unsigned char *mac) { + static char buffer[4][30]; + static int i = 0; + if ( i > 3 ) { + i = 0; + } + sprintf( buffer[i], "%02x:%02x:%02x:%02x:%02x:%02x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] ); + return buffer[i++]; +} + +// Form a socket address string from Sockaddr, into one of the +// 4 static buffer, whose use are cycled. +static char *inet_stoa(struct SockAddr *a) { + static char buffer[1000]; + static char out[4][1000]; + static int i = 0; + if ( i > 3 ) { + i = 0; + } + if ( a->in.sa_family == AF_INET ) { + sprintf( out[i], "%s:%d", + inet_ntop( AF_INET, &a->in4.sin_addr, buffer, 100 ), + ntohs( a->in4.sin_port ) ); + } else if ( a->in.sa_family == AF_INET6 ) { + sprintf( out[i], "[%s]:%d", + inet_ntop( AF_INET6, &a->in6.sin6_addr, buffer, 100 ), + ntohs( a->in6.sin6_port ) ); + } else { + sprintf( out[i], "" ); + } + return out[i++]; +} + +// Debugging: string representation of an Allowed record. +static char *show_allowed(struct Allowed *a) { + static char buffer[20000]; + if ( a == 0 ) { + sprintf( buffer, "{tap/stdio}" ); + } else { + sprintf( buffer, "%hd (%d) %s %p", + a->port, a->bits, inet_nmtoa( a->addr.bytes, a->addr.width ), + a->psk.key ); + } + return buffer; +} + +// Recognize uplink specification +static int is_uplink(struct Allowed *a) { + return a->bits == (unsigned int) ( a->addr.width * 8 ) && a->port != 0; +} + +// Add a new Interface for a Remote. If non-null, the interface is +// also added to the interface table. +static struct Interface *add_interface(unsigned char *mac,struct Remote *r) { + struct Interface *x = calloc( 1, sizeof( struct Interface ) ); + memcpy( x->mac, mac, sizeof( x->mac ) ); + x->remote = r; + if ( r ) { + Interface_ADD( x ); + } + return x; +} + +// Add a new remote for a given address and spec. +static struct Remote *add_remote(struct SockAddr *a,struct Allowed *s) { + struct Remote *r = calloc( 1, sizeof( struct Remote ) ); + if ( a != 0 ) { + memcpy( &r->uaddr, a, sizeof( r->uaddr ) ); + } + r->spec = s; + VERBOSE2OUT( "add_remote %s from spec: %s\n", + inet_stoa( &r->uaddr ), + ( s == 0 )? ( (a == 0)? "{tap/stdio}" : "{multicast}" ) + : show_allowed( s ) ); + Remote_ADD( r ); + return r; +} + +// Add a new ignored interface on a channel +static int add_ignored(struct Allowed *link,unsigned char *mac) { + struct Interface *x = add_interface( mac, 0 ); + if ( x == 0 ) { + return 1; // error: out of memory + } + Ignored_ADD( link, x ); + return 0; +} + +// Parse ignored interfaces +// Comma separated list of MAC addresses +static int parse_ignored_interfaces(char *arg,struct Allowed *link) { + int a, b, c, d, e, f, g; + while ( *arg ) { + if ( sscanf( arg,"%x:%x:%x:%x:%x:%x%n",&a,&b,&c,&d,&e,&f,&g ) != 6 ) { + // Not a mac addr + return 1; + } + if ( (a|b|c|d|e|f) & ~0xff ) { + return 1; // some %x is not hex + } + unsigned char mac[6] = { a, b, c, d, e, f }; + if ( add_ignored( link, mac ) ) { + // Out of memory ?? + return 1; + } + VERBOSEOUT( "Ignoring: %s on channel %s\n", + inet_mtoa( mac ), link->source ); + arg += g; + if ( *arg == 0 ) { + break; + } + if ( *(arg++) != ',' ) { + return 1; // Not comma separated + } + } + return 0; +} + +//** IP address parsing utility +// Clear bits after +static void clearbitsafter(struct CharAddr *a,unsigned int bits) { + unsigned int max = a->width * 8; + int i; + for ( i = a->width; i < 16; i++ ) { + a->bytes[ i ] = 0; + } + for ( i = a->width - 1; i >= 0; i--, max -= 8 ) { + if ( max - 8 < bits ) { + break; + } + a->bytes[ i ] = 0; + } + if ( i >= 0 && max >= bits ) { + a->bytes[ i ] &= ( 0xFF << ( bits - max ) ); + } +} + +//** IP address parsing utility +// Find the PSK for the given +file+ in the +loaded+ table (of +count+ size) +static struct PSK *findLoadedKeyfile(char *file,struct PSK *loaded,int count) { + VERBOSE3OUT( "find %s\n", file ); + for ( count--; count >= 0; count-- ) { + if ( strcmp( file, loaded[ count ].keyfile ) ) { + VERBOSE3OUT( "found %d\n", count ); + return &loaded[ count ]; + } + } + VERBOSE3OUT( "found nothing\n" ); + return 0; +} + +//** IP address parsing utility +// Load a key file into dynamically allocated memory, and update the +// given PSK header for it. +static void loadkey(struct PSK *psk) { + static struct PSK *loaded = 0; + static int count = 0; + if ( psk->keyfile == 0 ) { + return; + } + struct PSK *old = findLoadedKeyfile( psk->keyfile, loaded, count ); + if ( old ) { + memcpy( psk, old, sizeof( struct PSK ) ); + return; + } + int e; + unsigned char *p; + int n; + struct stat filestat; + psk->keyfile = strdup( psk->keyfile ); + int fd = open( (char*) psk->keyfile, O_RDONLY ); + psk->seed = 0; + if ( fd < 0 ) { + perror( "open key file" ); + exit( 1 ); + } + if ( fstat( fd, &filestat ) ) { + perror( "stat of key file" ); + exit( 1 ); + } + psk->key_length = filestat.st_size; + if ( psk->key_length < 256 ) { + fprintf( stderr, "Too small key file: %d %s\n", psk->key_length, + psk->keyfile ); + exit( 1 ); + } + psk->key = malloc( psk->key_length ); + if ( psk->key == 0 ) { + fprintf( stderr, "Cannot allocate %d bytes for %s\n", + psk->key_length, psk->keyfile ); + exit( 1 ); + } + e = psk->key_length; + p = psk->key; + while ( ( n = read( fd, p, e ) ) > 0 ) { + e -= n; + p += n; + } + close( fd ); + if ( e != 0 ) { + fprintf( stderr, "Failed loading key %s\n", psk->keyfile ); + exit( 1 ); + } + for ( e = 0; (unsigned) e < psk->key_length; e++ ) { + psk->seed += psk->key[ e ]; + } + if ( psk->seed == 0 ) { + fprintf( stderr, "Bad key %s; adds up to 0\n", psk->keyfile ); + exit( 1 ); + } + count++; + if ( loaded ) { + loaded = realloc( loaded, ( count * sizeof( struct PSK ) ) ); + } else { + loaded = malloc( sizeof( struct PSK ) ); + } + memcpy( &loaded[ count-1 ], psk, sizeof( struct PSK ) ); + VERBOSE3OUT( "%d: %s %d %p %d\n", count-1, psk->keyfile, psk->seed, + psk->key, psk->key_length ); +} + +//** IP address parsing utility +// Fill out a CharAddr and *port from a SockAddr +static void set_charaddrport( + struct CharAddr *ca,unsigned short *port,struct SockAddr *sa) +{ + memset( ca, 0, sizeof( struct CharAddr ) ); + ca->width = ( sa->in.sa_family == AF_INET )? 4 : 16; + if ( ca->width == 4 ) { + memcpy( &ca->in4, &sa->in4.sin_addr, 4 ); + *port = ntohs( sa->in4.sin_port ); + } else { + memcpy( &ca->in6, &sa->in6.sin6_addr, 16 ); + *port = ntohs( sa->in6.sin6_port ); + } +} + +//** IP address parsing utility +// Fill out a SockAddr from a CharAddr and port +static void set_sockaddr(struct SockAddr *sa,struct CharAddr *ca,int port) { + memset( sa, 0, sizeof( struct SockAddr ) ); + if ( ca->width == 4 ) { + sa->in4.sin_family = AF_INET; + sa->in4.sin_port = htons( port ); + memcpy( &sa->in4.sin_addr, &ca->in4, 4 ); + } else { + sa->in6.sin6_family = AF_INET6; + sa->in6.sin6_port = htons( port ); + memcpy( &sa->in6.sin6_addr, &ca->in6, 16 ); + } +} + +//** IP address parsing utility +// Capture an optional port sub phrase [:] +static int parse_port(char *port,struct Allowed *into) { + into->port = 0; + if ( port ) { + *(port++) = 0; + int p; + if ( sscanf( port, "%d", &p ) != 1 || p < 1 || p > 65535 ) { + // Bad port number + return 1; + } + into->port = p; + } + return 0; +} + +//** IP address parsing utility +// Capture an optional bits sub phrase [/] +static int parse_bits(char *bits,int max,struct Allowed *into) { + into->bits = max; + if ( bits ) { + *(bits++) = 0; + int b; + if ( sscanf( bits, "%d", &b ) != 1 || b < 0 || b > max ) { + return 1; + } + into->bits = b; + } + return 0; +} + +//** IP address parsing utility +// Parse a command line argument as a declaration of an allowed +// remote into the given . +// Return 0 if ok and 1 otherwise +// Formats: [/][:][=keyfile] +// Formats: [/][=keyfile] +// Formats: \[[/]\][:][=keyfile] +static int parse_allowed(char *arg,struct Allowed *into) { + static char buffer[10000]; + int n = strlen( arg ); + if ( n > 9000 ) { + return 1; // excessively large argument + } + strcpy( buffer, arg ); + into->source = arg; + char * keyfile = strchr( buffer, '=' ); + if ( keyfile ) { + *(keyfile++) = 0; + into->psk.keyfile = keyfile; + } +#define B(b) b, b+1, b+2, b+3 + if ( sscanf( buffer, "%hhu.%hhu.%hhu.%hhu", B(into->addr.bytes) ) == 4 ) { +#undef B + // ipv4 address + into->addr.width = 4; + if ( parse_port( strchr( buffer, ':' ), into ) ) { + fprintf( stderr, "bad port\n" ); + return 1; + } + if ( parse_bits( strchr( buffer, '/' ), 32, into ) ) { + fprintf( stderr, "bad bits\n" ); + return 1; + } + return 0; + } + // ipv6 address + char * address = buffer; + into->port = 0; + if ( *buffer == '[' ) { + // bracketed form, necessary for port + char *end = strchr( buffer, ']' ); + if ( end == 0 ) { + return 1; // bad argument + } + address++; + *(end++) = 0; + if ( *end == ':' && parse_port( end, into ) ) { + return 1; + } + } + into->addr.width = 16; + if ( parse_bits( strchr( address, '/' ), 128, into ) ) { + return 1; + } + if ( inet_pton( AF_INET6, address, into->addr.bytes ) != 1 ) { + return 1; // Bad IPv6 + } + return 0; +} + +//** IP address parsing utility +// Add a new channel spec into the table +// spec == 0 for the tap/stdio channel +static struct Allowed *add_allowed(char *spec) { + struct Allowed *into = calloc( 1, sizeof(struct Allowed) ); + htable x = HTABLEINIT( struct Interface, mac, hashcode_mac ); + into->ignored_mac = x; + if ( spec != 0 ) { + if ( parse_allowed( spec, into ) ) { + fprintf( stderr, "Bad remote spec: %s\n", spec ); + return 0; + } + } + int i; + if ( allowed.table == 0 ) { + // First entry. + allowed.table = calloc( 1, sizeof(struct Allowed*) ); + allowed.count = 1; + i = 0; + } else { + i = allowed.count++; + allowed.table = realloc( allowed.table, + allowed.count * sizeof(struct Allowed*) ); + if ( allowed.table == 0 ) { + fprintf( stderr, "OUT OF MEMORY\n" ); + exit( 1 ); + } + } + allowed.table[i] = into; + + loadkey( &into->psk ); + VERBOSE3OUT( "Allowed %s { %s }\n", into->source, show_allowed( into ) ); + if ( is_uplink( into ) ) { + struct SockAddr addr; + set_sockaddr( &addr, &into->addr, into->port ); + VERBOSEOUT( "Add uplink %s\n", show_allowed( into ) ); + (void) add_remote( &addr, into ); + } + return into; +} + +static int parse_threads_count(char *arg) { + if ( ( sscanf( arg, "%u", &threads_count ) != 1 ) || threads_count < 1 ) { + return 1; + } + VERBOSEOUT( "** Threads count = %d\n", threads_count ); + return 0; +} + +static int parse_buffers_count(char *arg) { + if ( ( sscanf( arg, "%u", &buffers_count ) != 1 ) || buffers_count < 1 ) { + return 1; + } + VERBOSEOUT( "** Buffers count = %d\n", buffers_count ); + return 0; +} + +//** IP address parsing utility for multicast phrase +// Return 0 if ok and 1 otherwise +// Formats: :[=keyfile] +// 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_mcast(char *arg) { + static char buffer[10000]; + int n = strlen( arg ); + if ( n > 9000 ) { + return 1; // excessively large argument + } + memcpy( buffer, arg, n ); + char *p = buffer + n - 1; + for ( ; p > buffer && *p != ':' && *p != '='; p-- ) { } + if ( *p == '=' ) { + mcast.psk.keyfile = p+1; + *p = 0; + loadkey( &mcast.psk ); + for ( ; p > buffer && *p != ':' ; p-- ) { } + } + if ( *p != ':' ) { + fprintf( stderr, "Multicast port is required\n" ); + return 1; // Port number is required + } + *(p++) = 0; + if ( inet_pton( AF_INET, buffer, &mcast.group.imr_multiaddr.s_addr )==0 ) { + fprintf( stderr, "Multicast address required\n" ); + return 1; + } + char *e; + long int port = strtol( p, &e, 10 ); + if ( *e != 0 || port < 1 || port > 65535 ) { + fprintf( stderr, "Bad multicast port\n" ); + return 1; + } + mcast.group.imr_address.s_addr = htonl(INADDR_ANY); + mcast.sock.in4.sin_family = AF_INET; + mcast.sock.in4.sin_addr.s_addr = htonl(INADDR_ANY); + mcast.sock.in4.sin_port = htons( atoi( p ) ); + return 0; +} + +// Utility that sets upt the multicast socket, which is used for +// receiving multicast packets. +static void setup_mcast() { + // set up ipv4 socket + if ( ( mcast.fd = socket( AF_INET, SOCK_DGRAM, 0 ) ) == 0 ) { + perror( "creating socket"); + exit(1); + } + if ( setsockopt( mcast.fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, + (char *) &mcast.group, sizeof( mcast.group ) ) < 0) { + perror( "Joining multicast group" ); + exit( 1 ); + } + int reuse = 1; + if ( setsockopt( mcast.fd, SOL_SOCKET, SO_REUSEADDR, + &reuse, sizeof( int ) ) < 0 ) { + perror( "SO_REUSEADDR" ); + exit( 1 ); + } + if ( bind( mcast.fd, (struct sockaddr*) &mcast.sock.in, + sizeof( struct sockaddr ) ) ) { + fprintf( stderr, "Error binding socket!\n"); + exit(1); + } + // Change mcast address to be the group multiaddress, and add + // a persistent "remote" for it. + mcast.sock.in4.sin_addr.s_addr = mcast.group.imr_multiaddr.s_addr; + add_remote( &mcast.sock, 0 ); +} + +// Find the applicable channel rule for a given ip:port address +static struct Allowed *is_allowed_remote(struct SockAddr *addr) { + struct CharAddr ca; + int width = ( addr->in.sa_family == AF_INET )? 4 : 16; + unsigned short port; + int i = 0; + for ( ; (unsigned) i < allowed.count; i++ ) { + struct Allowed *a = allowed.table[i]; + if ( a->addr.width != width ) { + continue; + } + set_charaddrport( &ca, &port, addr ); + if ( a->port != 0 && a->port != port ) { + continue; + } + clearbitsafter( &ca, a->bits ); + if ( memcmp( &ca, &a->addr, sizeof( struct CharAddr ) ) == 0 ) { + return a; + } + } + return 0; // Disallowed +} + +// Simple PSK encryption: +// +// First, xor each byte with a key byte that is picked from the key +// by means of an index that includes the prior encoding. Also, +// compute the sum of encrypted bytes into a "magic" that is added the +// "seed" for seeding the random number generator. Secondly reorder +// the bytes using successive rand number picks from the seeded +// generator. +// +static void encrypt(unsigned char *buf,unsigned int n,struct PSK *psk) { + unsigned int k; + unsigned int r; + unsigned char b; + unsigned int magic; + VERBOSE3OUT( "encrypt by %s %p\n", psk->keyfile, psk->key ); + for ( k = 0, r = 0, magic = 0; k < n; k++ ) { + r = ( r + magic + k ) % psk->key_length; + buf[k] ^= psk->key[ r ]; + magic += buf[k]; + } + pthread_mutex_lock( &crypting ); + srand( psk->seed + magic ); + for ( k = 0; k < n; k++ ) { + r = rand() % n; + b = buf[k]; + buf[k] = buf[r]; + buf[r] = b; + } + pthread_mutex_unlock( &crypting ); +} + +// Corresponding decryption procedure . +static void decrypt(unsigned char *buf,unsigned int n,struct PSK *psk) { + unsigned int randoms[ BUFSIZE ]; + unsigned int k; + unsigned int r; + unsigned char b; + unsigned int magic = 0; + for ( k = 0; k < n; k++ ) { + magic += buf[k]; + } + pthread_mutex_lock( &crypting ); + srand( psk->seed + magic ); + for ( k = 0; k < n; k++ ) { + randoms[k] = rand() % n; + } + pthread_mutex_unlock( &crypting ); + for ( k = n; k > 0; ) { + r = randoms[ --k ]; + b = buf[k]; + buf[k] = buf[r]; + buf[r] = b; + } + for ( k = 0, r = 0, magic = 0; k < n; k++ ) { + r = ( r + magic + k ) % psk->key_length; + magic += buf[k]; + buf[k] ^= psk->key[r]; + } +} + +// Write a buffer data to given file descriptor (basically tap_fd in +// this program). This is never fragmented. +static int dowrite(int fd, unsigned char *buf, int n) { + int w; + if ( ( w = write( fd, buf, n ) ) < 0){ + perror( "Writing data" ); + w = -1; + } + return w; +} + +// Write to the tap/stdio; adding length prefix for stdio +static int write_tap(unsigned char *buf, int n) { + uint8_t tag0 = *( buf + 12 ); + if ( tag0 == 8 ) { + uint16_t size = ntohs( *(uint16_t*)(buf + 16) ); + if ( size <= 1500 ) { + if ( ( verbose >= 2 ) && ( n != size + 14 ) ) { + VERBOSEOUT( "clip %d to %d\n", n, size + 14 ); + } + n = size + 14; // Clip of any tail + } + } + if ( stdio ) { + uint16_t plength = htons( n ); + if ( dowrite( 1, (unsigned char *) &plength, + sizeof( plength ) ) < 0 ) { + return -11; + } + return dowrite( 1, buf, n ); + } + return dowrite( tap_fd, buf, n ); +} + +// Write a packet via the given Interface with encryption as specified. +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 ) ); + } else { + VERBOSE2OUT( "SEND %s -> %s to %s\n", + inet_mtoa( buf+6 ), inet_mtoa( buf ), + inet_stoa( &r->uaddr ) ); + } + memcpy( output, buf, n ); // Use the private buffer for delivery + if ( r->spec == 0 ) { + if ( r->uaddr.in.sa_family == 0 ) { + // Output to tap/stdio + if ( write_tap( buf, n ) < 0 ) { + // panic error + fprintf( stderr, "Cannot write to tap/stdio: exiting!\n" ); + exit( 1 ); + } + return; + } + // Fall through for multicast + if ( mcast.psk.keyfile ) { + encrypt( output, n, &mcast.psk ); + } + } else if ( r->spec->psk.keyfile ) { + encrypt( output, n, &r->spec->psk ); + } + struct sockaddr *sock = &r->uaddr.in; + size_t size; + if ( sock->sa_family == AF_INET6 ) { + // 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+. + size = sizeof( struct sockaddr_in6 ); + VERBOSE2OUT( "IPv6 UDP %d %s\n", + udp_fd, inet_stoa( (struct SockAddr*) sock ) ); + } else { + size = sizeof( struct sockaddr_in ); + VERBOSE2OUT( "IPv4 UDP %d %s\n", + udp_fd, inet_stoa( (struct SockAddr*) sock ) ); + } + VERBOSE2OUT( "SEND %d bytes to %s [%s -> %s]\n", + n, inet_stoa( (struct SockAddr*) sock ), + ( 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 ) { + perror( "Writing socket" ); + // Invalidate remote temporarily instead? But if it's an + // "uplink" it should be retried eventually... + // For now: just ignore the error. + // exit( 1 ); + } +} + +// Delete a Remote and all its interfaces +static void delete_remote(struct Remote *r) { + VERBOSE2OUT( "DELETE Remote and all its interfaces %s\n", + inet_stoa( &r->uaddr ) ); + unsigned int i = 0; + struct Interface *x; + Interface_LOCK; + for ( ; i < remotes.by_mac.size; i++ ) { + unsigned char *tmp = remotes.by_mac.data[i]; + if ( tmp == 0 || tmp == (unsigned char *)1 ) { + continue; + } + x = (struct Interface *) tmp; + if ( x->remote == r ) { + Interface_DEL( x ); + free( x ); + } + } + Interface_UNLOCK; + Remote_DEL( r ); + free( r ); +} + +// Unmap an ipv4-mapped ipv6 address +static void unmap_if_mapped(struct SockAddr *s) { + if ( s->in.sa_family != AF_INET6 || + memcmp( "\000\000\000\000\000\000\000\000\000\000\377\377", + &s->in6.sin6_addr, 12 ) ) { + return; + } + VERBOSE2OUT( "unmap %s\n", + inet_nmtoa( (unsigned char*) s, sizeof( struct SockAddr ) ) ); + s->in.sa_family = AF_INET; + memcpy( &s->in4.sin_addr, s->in6.sin6_addr.s6_addr + 12, 4 ); + memset( s->in6.sin6_addr.s6_addr + 4, 0, 12 ); + VERBOSE2OUT( "becomes %s\n", + inet_nmtoa( (unsigned char*) s, sizeof( struct SockAddr ) ) ); +} + +// Route the packet from the given src +static struct Interface *input_check( + unsigned char *buf,ssize_t len,struct SockAddr *src ) +{ + VERBOSE2OUT( "RECV %ld bytes from %s\n", len, inet_stoa( src ) ); + struct Remote *r = 0; + struct timeb now = { 0 }; + if ( ftime( &now ) ) { + perror( "RECV time" ); + now.time = time( 0 ); + } + Remote_FIND( src, r ); + if ( r == 0 ) { + struct Allowed *a = is_allowed_remote( src ); + if ( a == 0 ) { + VERBOSEOUT( "Ignoring %s\n", inet_stoa( src ) ); + return 0; // Disallowed + } + VERBOSEOUT( "New remote %s by %s\n", inet_stoa( src ), a->source ); + r = add_remote( src, a ); + //r->rec_when = now; // Set activity stamp of new remote + } + if ( len <= 12 ) { + // Ignore short data, but maintain channel + r->rec_when = now; // Update activity stamp touched remote + return 0; + } + // Now decrypt the data as needed + if ( r->spec ) { + if ( r->spec->psk.seed ) { + decrypt( buf, len, &r->spec->psk ); + } + } else if ( r->uaddr.in.sa_family == 0 && mcast.psk.keyfile ) { + decrypt( buf, len, &mcast.psk ); + } + VERBOSE2OUT( "RECV %s -> %s from %s\n", + inet_mtoa( buf+6 ), inet_mtoa( buf ), + inet_stoa( &r->uaddr ) ); + // Note: the payload is now decrypted, and known to be from +r+ + struct Interface *x = 0; + // Packets concerning an ignored interface should be ignored. + if ( r->spec && r->spec->ignored_mac.data ) { + Ignored_FIND( r->spec, buf+6, x ); + if ( x ) { + VERBOSE2OUT( "Dropped MAC %s from %s on %s\n", + inet_mtoa( buf+6 ), inet_stoa( &r->uaddr ), + r->spec->source ); + return 0; + } + Ignored_FIND( r->spec, buf, x ); + if ( x ) { + VERBOSE2OUT( "Dropped MAC %s to %s on %s\n", + inet_mtoa( buf ), inet_stoa( &r->uaddr ), + r->spec->source ); + return 0; + } + } + Interface_FIND( buf+6, x ); + if ( x == 0 ) { + // Totally new MAC. Should bind it to the remote + VERBOSEOUT( "New MAC %s from %s\n", + inet_mtoa( buf+6 ), inet_stoa( src ) ); + x = add_interface( buf+6, r ); + r->rec_when = now; // Update activity stamp for remote + x->rec_when = now; + return x; + } + // Seen that MAC already + if ( x->remote == r ) { + VERBOSE2OUT( "RECV %s from %s again\n", + inet_mtoa( buf+6 ), inet_stoa( &x->remote->uaddr ) ); + r->rec_when = now; // Update activity stamp + x->rec_when = now; // Update activity stamp + return x; + } + // MAC clash from two different connections + // r = current + // x->remote = previous + VERBOSE2OUT( "RECV %s from %s previously from %s\n", + inet_mtoa( buf+6 ), + inet_stoa( &r->uaddr ), + inet_stoa( &x->remote->uaddr ) ); + if ( r->spec ) { + // The packet source MAC has arrived on other than its + // previous channel. It thus gets dropped if tap/stdin is the + // primary channel, or the time since the last packet for that + // interface is less than RECENT, with different limits for + // broadcast and unicast. + int64_t dmac = DIFFB_MILLIS( &now, &x->rec_when); + if ( x->remote->spec == 0 || RECENT( *buf & 1, dmac ) ) { + if ( verbose >= 2 ) { + fprintf( + stderr, + "Dropped. MAC %s (%ld) from %s, should be %s\n", + inet_mtoa( buf+6 ), dmac, + inet_stoa( src ), inet_stoa( &x->remote->uaddr ) ); + } + return 0; + } + // Check if previous package on the interface was recent + } else if ( r->uaddr.in.sa_family ) { + // Multicast incoming clashing with tap/stdio + VERBOSE3OUT( "Dropped multicast loopback\n" ); + return 0; + } + + // New remote takes over the MAC + VERBOSEOUT( "MAC %s from %s cancels previous %s\n", + inet_mtoa( buf+6 ), inet_stoa( src ), + inet_stoa( &x->remote->uaddr ) ); + x->remote = r; // Change remote for MAC + // Note that this may leave the old x->remote without any interface + r->rec_when = now; // Update activity stamp + x->rec_when = now; // Update activity stamp + return x; +} + +// Check packet and deliver out +static void route_packet(unsigned char *buf,int len,struct SockAddr *src) { + struct Interface *x = input_check( buf, len, src ); + if ( x == 0 ) { + return; // not a nice packet + } + if ( ( *buf & 1 ) == 0 ) { + // unicast + struct Interface *y = 0; // reuse for destination interface + Interface_FIND( buf, y ); + if ( y == 0 ) { + VERBOSE2OUT( "RECV %s -> %s from %s without channel and dropped\n", + inet_mtoa( buf+6 ), inet_mtoa( buf ), + inet_stoa( &x->remote->uaddr ) ); + return; + } + if ( x->remote == y->remote ) { + VERBOSEOUT( "RECV loop for %s -> %s from %s to %s\n", + inet_mtoa( buf+6 ), inet_mtoa( buf ), + inet_stoa( &x->remote->uaddr ), + inet_stoa( &y->remote->uaddr ) ); + Interface_DEL( y ); // Need to see this interface again + return; + } + VERBOSE2OUT( "RECV route %s -> %s to %s\n", + inet_mtoa( buf+6 ), inet_mtoa( buf ), + inet_stoa( &y->remote->uaddr ) ); + write_remote( buf, len, y->remote ); + return; + } + // broadcast. +x+ is source interface + // x->rec_when is not updated + struct timeb now = { 0 }; + if ( ftime( &now ) ) { + perror( "RECV time" ); + now.time = time( 0 ); + } + VERBOSE2OUT( "BC %s -> %s from %s\n", + inet_mtoa( buf+6 ), inet_mtoa( buf ), + inet_stoa( &x->remote->uaddr ) ); + struct Remote *r; + unsigned int i = 0; + Remote_LOCK; + for ( ; i < remotes.by_addr.size; i++ ) { + unsigned char *tmp = remotes.by_addr.data[i]; + if ( tmp == 0 || tmp == (unsigned char *)1 ) { + continue; + } + r = (struct Remote *) tmp; + VERBOSE3OUT( "BC check %s\n", inet_stoa( &r->uaddr ) ); + if ( r == x->remote ) { + VERBOSE3OUT( "BC r == x->remote\n" ); + continue; + } + if ( r->spec && ! is_uplink( r->spec ) && + DIFFB_MILLIS( &now, &r->rec_when ) > VERYOLD_MILLIS ) { + // remove old downlink connection + VERBOSEOUT( "Old remote discarded %s (%ld)\n", + inet_stoa( &r->uaddr ), + TIMEB_MILLIS( &r->rec_when ) ); + // Removing a downlink might have threading implications + delete_remote( r ); + continue; + } + // Send packet to the remote + // Only no-clash or to the tap/stdin + write_remote( buf, len, r ); + } + Remote_UNLOCK; +} + +// The packet handling queues +static struct { + Queue full; + Queue free; +} todolist; + +// The threadcontrol program for handling packets. +static void *packet_handler(void *data) { + (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 ); + } else { + if ( udp6 ) { + unmap_if_mapped( &todo->src ); + } + route_packet( todo->buffer, todo->len, &todo->src ); + } + Queue_addItem( &todolist.free, (QueueItem*) todo ); + } + return 0; +} + +void todolist_initialize(int nbuf,int nthr) { + if ( pthread_mutex_init( &todolist.full.mutex, 0 ) || + sem_init( &todolist.full.count, 0, 0 ) ) { + perror( "FATAL" ); + exit( 1 ); + } + if ( pthread_mutex_init( &todolist.free.mutex, 0 ) || + sem_init( &todolist.free.count, 0, 0 ) ) { + perror( "FATAL" ); + exit( 1 ); + } + Queue_initialize( &todolist.free, nbuf, sizeof( PacketItem ) ); + for ( ; nthr > 0; nthr-- ) { + pthread_t thread; // Temporary thread id + pthread_create( &thread, 0, packet_handler, 0 ); + } +} + +// 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 +// is updated for the new MAC address, However, if there is then a MAC +// address clash in the connection table, then the associated remote +// is removed, and the packet is dropped. +static void doreadUDP(int fd) { + 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) { + perror( "Receiving UDP" ); + exit( 1 ); + } +#ifdef GPROF + if ( len == 17 && memcmp( buf, "STOPSTOPSTOPSTOP", 16 ) == 0 ) { + exit( 0 ); + } +#endif + Queue_addItem( &todolist.full, (QueueItem*) todo ); +} + +// Handle packet received on the tap/stdio channel +static void received_tap(unsigned char *buf, int len) { + static struct Remote *tap_remote = 0; + if ( tap_remote == 0 ) { + Remote_LOCK; + if ( tap_remote == 0 ) { + tap_remote = add_remote( 0, 0 ); + } + Remote_UNLOCK; + } + PacketItem *todo = (PacketItem*) Queue_getItem( &todolist.free ); + memcpy( (void*)todo->buffer, (void*)buf, len ); + memcpy( (void*)&todo->src, + (void*)&tap_remote->uaddr, + sizeof( struct SockAddr ) ); + todo->fd = 0; + todo->len = len; + Queue_addItem( &todolist.full, (QueueItem*) todo ); +} + +// Read up to n bytes from the given file descriptor into the buffer +static int doread(int fd, unsigned char *buf, int n) { + ssize_t len; + if ( ( len = read( fd, buf, n ) ) < 0 ) { + perror( "Reading stdin" ); + exit( 1 ); + } + return len; +} + +// Read n bytes from the given file descriptor into the buffer. +// If partial is allowed, then return amount read, otherwise keep +// reading until full. +static int read_into(int fd, unsigned char *buf, int n,int partial) { + int r, x = n; + while( x > 0 ) { + if ( (r = doread( fd, buf, x ) ) == 0 ) { + return 0 ; + } + x -= r; + buf += r; + if ( partial ) { + return n - x; + } + } + return n; +} + +// Go through all uplinks and issue a "heart beat" +static void heartbeat(int fd) { + static unsigned char data[10]; + VERBOSE3OUT( "heartbeat fd=%d\n", fd ); + struct Remote *r; + unsigned int i = 0; + struct timeb now; + if ( ftime( &now ) ) { + perror( "HEARTBEAT time" ); + now.time = time( 0 ); + now.millitm = 0; + } + Remote_LOCK; + for ( ; i < remotes.by_addr.size; i++ ) { + unsigned char *tmp = remotes.by_addr.data[i]; + if ( tmp == 0 || tmp == (unsigned char *)1 ) { + continue; + } + r = (struct Remote *) tmp; + VERBOSE3OUT( "heartbeat check %s\n", inet_stoa( &r->uaddr ) ); + if ( r->spec && is_uplink( r->spec ) ) { + if ( DIFFB_MILLIS( &now, &r->rec_when ) > HEARTBEAT_MILLIS ) { + VERBOSE3OUT( "heartbeat %s\n", inet_stoa( &r->uaddr ) ); + write_remote( data, 0, r ); + } + } + } + Remote_UNLOCK; +} + +// The threadcontrol program for issuing heartbeat packets. +// Regular heartbeat +static void *hearbeater_thread(void *data) { + (void) data; + for ( ;; ) { + sleep( HEARTBEAT ); + heartbeat( udp_fd ); + } + return 0; +} + +// 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, "Usage: " ); + fprintf( stderr, + "%s [-v] [-4] [-B n] [-T n] [-m mcast] [-t tap] port [remote]+ \n", + progname ); + exit( 1 ); +} + +// Open the given tap +static int tun_alloc(char *dev, int flags) { + struct ifreq ifr; + int fd, err; + if ( ( fd = open( "/dev/net/tun", O_RDWR ) ) < 0 ) { + perror( "Opening /dev/net/tun" ); + return fd; + } + memset( &ifr, 0, sizeof( ifr ) ); + ifr.ifr_flags = flags; + if ( *dev ) { + strcpy( ifr.ifr_name, dev ); + } + if ( ( err = ioctl( fd, TUNSETIFF, (void *) &ifr ) ) < 0 ) { + perror( "ioctl(TUNSETIFF)" ); + close( fd ); + return err; + } + strcpy( dev, ifr.ifr_name ); + return fd; +} + +static void doreadTap() { + if ( stdio ) { + if ( input.end == 0 ) { + uint16_t plength; + int n = read_into( 0, (unsigned char *) &plength, + sizeof( plength ), 0 ); + if ( n == 0 ) { + // Tap/stdio closed => exit silently + exit( 0 ); + } + input.end = ntohs( plength ); + input.cur = 0; + } + size_t e = input.end - input.cur; + unsigned char *p = input.buffer + input.cur; + if ( input.end > BUFSIZE ) { + // Oversize packets should be read and discarded + if ( e > BUFSIZE ) { + e = BUFSIZE; + } + p = input.buffer; + } + input.cur += read_into( 0, p, e, 1 ); + } else { + input.end = doread( tap_fd, input.buffer, BUFSIZE ); + input.cur = input.end; + } + if ( input.end == input.cur ) { + VERBOSE3OUT( "TAP/stdio input %d bytes\n", input.end ); + if ( input.end <= BUFSIZE ) { + received_tap( input.buffer, input.end ); + } + input.end = 0; // Ready for next packet + } + // End handling tap +} + +// MAXFD is Finalized before multi-threading, and then remains constant. +static int MAXFD; + +// This is the main packet handling loop +static int packet_loop() { + // A packet buffer for receiving UDP + unsigned char buffer[ BUFSIZE ]; + int n; + //time_t next_heartbeat = time(0); + int cycle = 0; // which channel to check first + while( 1 ) { + fd_set rd_set; + FD_ZERO( &rd_set ); + if ( mcast.fd ) { + FD_SET( mcast.fd, &rd_set ); + } + FD_SET( udp_fd, &rd_set ); + if ( udp6 ) { + FD_SET( udp_fd, &rd_set ); + } + if ( tap ) { // tap/stdio + FD_SET( tap_fd, &rd_set ); + } + n = select( MAXFD, &rd_set, NULL, NULL, NULL ); + VERBOSE3OUT( "select got %d\n", n ); + if ( n < 0 ) { + if ( errno == EINTR ) { + continue; + } + perror("select"); + exit(1); + } + if ( n == 0 ) { + continue; + } + // Process input with alternating priority across channels + for ( ;; cycle++ ) { + if ( cycle >= 3 ) { + cycle = 0; + } + if ( cycle == 0 && FD_ISSET( udp_fd, &rd_set ) ) { + // Check and process UDP socket + doreadUDP( udp_fd ) ; + cycle = 1; + break; + } + if ( cycle == 1 && FD_ISSET( mcast.fd, &rd_set ) ) { + // Check and process multicast socket + doreadUDP( mcast.fd ) ; + cycle = 2; + break; + } + if ( cycle == 2 && FD_ISSET( tap_fd, &rd_set ) ) { + // Check and process tap/stdio socket + doreadTap( tap_fd, buffer ); + cycle = 0; + break; + } + } + } + return 0; +} + +// Application main function +// Parentheses mark optional +// $* = (-v) (-4) (-B n) (-T n) (-m mcast) (-t port) (ip:)port (remote)+ +// remote = ipv4(/maskwidth)(:port)(=key) +// remote = ipv6(/maskwidth)(=key) +// remote = [ipv6(/maskwidth)](:port)(=key) +// ip = ipv4 | [ipv6] +int main(int argc, char *argv[]) { + int port, i; + progname = (unsigned char *) argv[0]; + ///// Parse command line arguments + i = 1; +#define ENSUREARGS(n) if ( argc < i + n ) usage() + ENSUREARGS( 1 ); + // First: optional -v, -vv or -vvv + if ( strncmp( "-v", argv[i], 2 ) == 0 ) { + if ( strncmp( "-v", argv[i], 3 ) == 0 ) { + verbose = 1; + } else if ( strncmp( "-vv", argv[i], 4 ) == 0 ) { + verbose = 2; + } else if ( strncmp( "-vvv", argv[i], 5 ) == 0 ) { + verbose = 3; + } else { + usage(); + } + i++; + ENSUREARGS( 1 ); + } + // then: optional -4 + if ( strncmp( "-4", argv[i], 2 ) == 0 ) { + udp6 = 0; + i++; + ENSUREARGS( 1 ); + } + // then: optional -B buffers + if ( strncmp( "-B", argv[i], 2 ) == 0 ) { + ENSUREARGS( 2 ); + if ( parse_buffers_count( argv[i+1] ) ) { + usage(); + } + i += 2; + ENSUREARGS( 1 ); + } + // then: optional -T threads + if ( strncmp( "-T", argv[i], 2 ) == 0 ) { + ENSUREARGS( 2 ); + if ( parse_threads_count( argv[i+1] ) ) { + usage(); + } + i += 2; + ENSUREARGS( 1 ); + } + // then: optional -m mcast + if ( strncmp( "-m", argv[i], 2 ) == 0 ) { + ENSUREARGS( 2 ); + if ( parse_mcast( argv[i+1] ) ) { + usage(); + } + i += 2; + ENSUREARGS( 1 ); + } + // then: optional -t tap + if ( strncmp( "-t", argv[i], 2 ) == 0 ) { + ENSUREARGS( 2 ); + tap = argv[i+1]; + i += 2; + ENSUREARGS( 1 ); + } + // then: required port + if ( sscanf( argv[i++], "%d", &port ) != 1 ) { + fprintf( stderr, "Bad local port" ); + usage(); + } + // then: any number of allowed remotes + struct Allowed *last_allowed = 0; + for ( ; i < argc; i++ ) { + if ( last_allowed ) { + // optionally adding ignored interfaces + if ( strncmp( "-i", argv[i], 2 ) == 0 ) { + ENSUREARGS( 2 ); + if ( parse_ignored_interfaces( argv[i+1], last_allowed ) ) { + usage(); + } + i += 1; + continue; + } + } + if ( ( last_allowed = add_allowed( argv[i] ) ) == 0 ) { + fprintf( stderr, "Cannot load remote %s. Exiting.\n", argv[i] ); + exit( 1 ); + } + } + // end of command line parsing + + // Initialize buffers and threads + if ( threads_count == 0 ) { + threads_count = 5; + } + if ( buffers_count < threads_count ) { + buffers_count = 2 * threads_count; + } + todolist_initialize( buffers_count, threads_count ); + + MAXFD = 0; + // Set up the tap/stdio channel + if ( tap ) { + // set up the nominated tap + if ( strcmp( "-", tap ) ) { // Unless "-" + tap_fd = tun_alloc( tap, IFF_TAP | IFF_NO_PI ); + if ( tap_fd < 0 ) { + fprintf( stderr, "Error connecting to interface %s!\n", tap); + exit(1); + } + VERBOSEOUT( "Using tap %s at %d\n", tap, tap_fd ); + MAXFD = tap_fd; + stdio = 0; + // pretend a zero packet on the tap, for initializing. + received_tap( 0, 0 ); + } else { + // set up for stdin/stdout local traffix + setbuf( stdout, NULL ); // No buffering on stdout. + tap_fd = 0; // actually stdin + MAXFD = 0; + stdio = 1; + } + } else { + stdio = 0; + } + // Set up the multicast UDP channel (all interfaces) + if ( mcast.group.imr_multiaddr.s_addr ) { + setup_mcast(); + unsigned char *x = (unsigned char *) &mcast.group.imr_multiaddr.s_addr; + VERBOSEOUT( "Using multicast %s:%d at %d\n", + inet_nmtoa( x, 4 ), ntohs( mcast.sock.in4.sin_port ), + mcast.fd ); + if ( mcast.fd > MAXFD ) { + MAXFD = mcast.fd; + } + } + // Set up the unicast UPD channel (all interfaces) + if ( udp6 == 0 ) { + // set up ipv4 socket + if ( ( udp_fd = socket( AF_INET, SOCK_DGRAM, 0 ) ) == 0 ) { + perror( "creating socket"); + exit(1); + } + struct sockaddr_in udp_addr = { + .sin_family = AF_INET, + .sin_port = htons( port ), + .sin_addr.s_addr = htonl(INADDR_ANY), + }; + 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 ); + } else { + // set up ipv6 socket + if ( ( udp_fd = socket( AF_INET6, SOCK_DGRAM, 0 ) ) == 0 ) { + perror( "creating socket"); + exit(1); + } + struct sockaddr_in6 udp6_addr = { + .sin6_family = AF_INET6, + .sin6_port = htons( port ), + .sin6_addr = IN6ADDR_ANY_INIT, + }; + 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 ); + } + if ( udp_fd > MAXFD ) { + MAXFD = udp_fd; + } + MAXFD++ ; + // If not using stdio for local traffic, then stdin and stdout are + // closed here, so as to avoid that any other traffic channel gets + // 0 or 1 as its file descriptor. Note: stderr (2) is left open. + if ( ! stdio ) { + close( 0 ); + close( 1 ); + } + VERBOSE2OUT( "Socket loop tap=%d mcast=%d udp=%d max=%d\n", + tap_fd, mcast.fd, udp_fd, MAXFD ); + + // Start heartbeater thread + pthread_t thread; // Temporary thread id -- not used + pthread_create( &thread, 0, hearbeater_thread, 0 ); + + // Handle packets + return packet_loop(); +} diff --git a/set-source-route.sh b/set-source-route.sh new file mode 100755 index 0000000..81af6c0 --- /dev/null +++ b/set-source-route.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# +# rrqnet helper script for establishing a rule based route table for a +# given interface with given IP with default route to given gateway IP +# on the interface link. +# $1 = interface +# $2 = interface IP +# $3 = gateway IP +# +# This will retry every second until the setup is successful. Should +# be spawned in a conf file, eg: +# /etc/rrqnet/set-source-route.sh $TAP $IP $GW > /dev/null 2>&1 & +# + +TAP=$1 +IP=$2 +GW=$3 +: ${TIX:=200} + +function set-source-route() { + grep -q "$TIX $TAP" /etc/iproute2/rt_tables || \ + echo "$TIX $TAP" >> /etc/iproute2/rt_tables + if [ -z "$(ip rule list from ${IP%/*})" ] ; then + ip rule add from ${IP%/*} lookup $TAP || return 1 + fi + if [ -z "$(ip route show table $TAP | grep ^$GW)" ] ; then + ip route add $GW dev $TAP scope link src ${IP%/*} table $TAP || \ + return 1 + fi + if [ -z "$(ip route show table $TAP | grep default)" ] ; then + ip route add default via $GW dev $TAP table $TAP || return 1 + fi + ip route show table $TAP +} + +set-source-route && exit 0 +sleep 1 +exec $0 $* diff --git a/sockaddr.h b/sockaddr.h new file mode 100644 index 0000000..8156b2c --- /dev/null +++ b/sockaddr.h @@ -0,0 +1,16 @@ +#ifndef sockaddr_H +#define sockaddr_H + +#include +#include + +// IP Address union, with flag field +struct SockAddr { + union { + struct sockaddr in; + struct sockaddr_in in4; + struct sockaddr_in6 in6; + } ; +}; + +#endif -- 2.39.2