initial renaming
authorRalph Ronnquist <ralph.ronnquist@gmail.com>
Wed, 17 Jun 2020 10:44:57 +0000 (20:44 +1000)
committerRalph Ronnquist <ralph.ronnquist@gmail.com>
Wed, 17 Jun 2020 10:44:57 +0000 (20:44 +1000)
21 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/rules [new file with mode: 0755]
debian/source/format [new file with mode: 0644]
debian/udptap.debhelper.log [new file with mode: 0644]
debian/udptap.substvars [new file with mode: 0644]
htable.c [new file with mode: 0644]
htable.h [new file with mode: 0644]
queue.c [new file with mode: 0644]
queue.h [new file with mode: 0644]
readme.adoc [new file with mode: 0644]
rrqnet-cron.sh [new file with mode: 0755]
rrqnet-cron.sh.8.adoc [new file with mode: 0644]
rrqnet.8.adoc [new file with mode: 0644]
rrqnet.c [new file with mode: 0644]
set-source-route.sh [new file with mode: 0755]
sockaddr.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..56f6f3d
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..12b5fbd
--- /dev/null
@@ -0,0 +1,5 @@
+rrqnet (0.1) experimental; urgency=medium
+
+  * Initial release (restart from udptap=0.2.5)
+
+ -- Ralph Ronnquist <ralph.ronnquist@gmail.com>  Wed, 17 Jun 2020 20:34:04 +1000
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..ec63514
--- /dev/null
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..a95bc78
--- /dev/null
@@ -0,0 +1,19 @@
+Source: rrqnet
+Section: admin
+Priority: optional
+Maintainer: Ralph Ronnquist <ralph.ronnquist@gmail.com>
+Build-Depends: debhelper (>= 9), asciidoc, docbook-xml, libxslt1-dev, xsltproc,
+ docbook-xsl
+Standards-Version: 3.9.8
+Homepage: <insert the upstream URL, if relevant>
+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 (file)
index 0000000..02a524d
--- /dev/null
@@ -0,0 +1,22 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: rrqnet
+Source: <url://example.com>
+
+Files: debian/*
+Copyright: 2020 Ralph Ronnquist <ralph@ascii>
+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 <https://www.gnu.org/licenses/>
+ .
+ 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 (executable)
index 0000000..9ba3ef6
--- /dev/null
@@ -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 (file)
index 0000000..89ae9db
--- /dev/null
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/debian/udptap.debhelper.log b/debian/udptap.debhelper.log
new file mode 100644 (file)
index 0000000..24c133d
--- /dev/null
@@ -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 (file)
index 0000000..978fc8b
--- /dev/null
@@ -0,0 +1,2 @@
+misc:Depends=
+misc:Pre-Depends=
diff --git a/htable.c b/htable.c
new file mode 100644 (file)
index 0000000..02f08e9
--- /dev/null
+++ b/htable.c
@@ -0,0 +1,135 @@
+#include <stdlib.h>
+#include <string.h>
+#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 (file)
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 <pthread.h>
+
+// 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 (file)
index 0000000..0ec3132
--- /dev/null
+++ b/queue.c
@@ -0,0 +1,71 @@
+#include <stdio.h>
+#include <stdlib.h>
+#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 (file)
index 0000000..76478a8
--- /dev/null
+++ b/queue.h
@@ -0,0 +1,25 @@
+#ifndef queue_H
+#define queue_H
+
+#include "sockaddr.h"
+#include <semaphore.h>
+#include <pthread.h>
+
+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 (file)
index 0000000..cac4a71
--- /dev/null
@@ -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 (executable)
index 0000000..68d04e6
--- /dev/null
@@ -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 (file)
index 0000000..3c6e39f
--- /dev/null
@@ -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 <ralph.ronnquist@gmail.com>
diff --git a/rrqnet.8.adoc b/rrqnet.8.adoc
new file mode 100644 (file)
index 0000000..5afd0d8
--- /dev/null
@@ -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 <ralph.ronnquist@gmail.com>
diff --git a/rrqnet.c b/rrqnet.c
new file mode 100644 (file)
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 <errno.h>
+#include <fcntl.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/timeb.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#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], "<tap/stdio>" );
+    }
+    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 <bits>
+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 [:<port>]
+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 [/<bits>]
+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 <addr>.
+// Return 0 if ok and 1 otherwise
+// Formats: <ipv4-address>[/<bits>][:<port>][=keyfile]
+// Formats: <ipv6-address>[/<bits>][=keyfile]
+// Formats: \[<ipv6-address>[/<bits>]\][:<port>][=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 <allowed> 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: <ipv4-address>:<port>[=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 (executable)
index 0000000..81af6c0
--- /dev/null
@@ -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 (file)
index 0000000..8156b2c
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef sockaddr_H
+#define sockaddr_H
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+// IP Address union, with flag field
+struct SockAddr {
+    union {
+       struct sockaddr in;
+       struct sockaddr_in in4;
+       struct sockaddr_in6 in6;
+    } ;
+};
+
+#endif