Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1
authorGuus Sliepen <guus@tinc-vpn.org>
Wed, 22 Feb 2012 13:23:59 +0000 (14:23 +0100)
committerGuus Sliepen <guus@tinc-vpn.org>
Wed, 22 Feb 2012 13:23:59 +0000 (14:23 +0100)
Conflicts:
NEWS
README
configure.in
doc/tincd.8.in
src/Makefile.am
src/bsd/device.c
src/connection.c
src/connection.h
src/cygwin/device.c
src/device.h
src/dropin.h
src/linux/device.c
src/mingw/device.c
src/net.c
src/net_packet.c
src/net_setup.c
src/net_socket.c
src/process.c
src/protocol.c
src/protocol_key.c
src/raw_socket_device.c
src/route.c
src/solaris/device.c
src/tincd.c
src/uml_device.c

31 files changed:
1  2 
NEWS
THANKS
configure.in
doc/tinc.conf.5.in
doc/tinc.texi
doc/tincd.8.in
src/Makefile.am
src/bsd/device.c
src/connection.c
src/connection.h
src/cygwin/device.c
src/device.h
src/ipv4.h
src/linux/device.c
src/mingw/device.c
src/net.c
src/net_packet.c
src/net_setup.c
src/net_socket.c
src/node.h
src/openssl/prf.c
src/protocol.c
src/protocol_auth.c
src/protocol_edge.c
src/protocol_key.c
src/raw_socket_device.c
src/route.c
src/route.h
src/solaris/device.c
src/tincd.c
src/uml_device.c

diff --cc NEWS
--- 1/NEWS
--- 2/NEWS
+++ b/NEWS
@@@ -1,33 -1,10 +1,40 @@@
 +Version 1.1pre2              Juli 17 2011
 +
 + * .cookie files are renamed to .pid files, which are compatible with 1.0.x.
 +
 + * Experimental protocol enhancements that can be enabled with the option
 +   ExperimentalProtocol = yes:
 +
 +   * Ephemeral ECDH key exchange will be used for both the meta protocol and
 +     UDP session keys.
 +   * Key exchanges are signed with ECDSA.
 +   * ECDSA public keys are automatically exchanged after RSA authentication if
 +     nodes do not know each other's ECDSA public key yet.
 +
 +Version 1.1pre1              June 25 2011
 +
 + * Control interface allows control of a running tinc daemon. Used by:
 +   * tincctl, a commandline utility
 +   * tinc-gui, a preliminary GUI implemented in Python/wxWidgets
 +
 + * Code cleanups and reorganization. 
 +
 + * Repleacable cryptography backend, currently supports OpenSSL and libgcrypt.
 +
 + * Use libevent to handle I/O events and timeouts.
 +
 + * Use splay trees instead of AVL trees to manage internal datastructures.
 +
 + Thanks to Scott Lamb and Sven-Haegar Koch for their contributions to this
 + version of tinc.
 +
+ Version 1.0.16               July 23 2011
+  * Fixed a performance issue with TCP communication under Windows.
+  * Fixed code that, during network outages, would cause tinc to exit when it
+    thought two nodes with identical Names were on the VPN.
  Version 1.0.15               June 24 2011
  
   * Improved logging to file.
diff --cc THANKS
Simple merge
diff --cc configure.in
@@@ -100,8 -118,8 +117,8 @@@ dnl Checks for header files
  dnl We do this in multiple stages, because unlike Linux all the other operating systems really suck and don't include their own dependencies.
  
  AC_HEADER_STDC
 -AC_CHECK_HEADERS([stdbool.h syslog.h sys/file.h sys/ioctl.h sys/mman.h sys/param.h sys/resource.h sys/socket.h sys/time.h time.h sys/uio.h sys/wait.h netdb.h arpa/inet.h dirent.h])
 -AC_CHECK_HEADERS([net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h netpacket/packet.h],
 +AC_CHECK_HEADERS([stdbool.h syslog.h sys/file.h sys/ioctl.h sys/mman.h sys/param.h sys/resource.h sys/socket.h sys/time.h sys/uio.h sys/un.h sys/wait.h netdb.h arpa/inet.h dirent.h])
- AC_CHECK_HEADERS([net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h time.h],
++AC_CHECK_HEADERS([net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h time.h netpacket/packet.h],
    [], [], [#include "have.h"]
  )
  AC_CHECK_HEADERS([netinet/if_ether.h netinet/ip.h netinet/ip6.h],
Simple merge
diff --cc doc/tinc.texi
@@@ -1595,11 -1615,18 +1639,17 @@@ This will let tinc read all configurati
  Specifying . for @var{netname} is the same as not specifying any @var{netname}.
  @xref{Multiple networks}.
  
 -@item -K, --generate-keys[=@var{bits}]
 -Generate public/private keypair of @var{bits} length. If @var{bits} is not specified,
 -2048 is the default. tinc will ask where you want to store the files,
 -but will default to the configuration directory (you can use the -c or -n option
 -in combination with -K). After that, tinc will quit.
 +@item --pidfile=@var{filename}
 +Store a cookie in @var{filename} which allows tincctl to authenticate.
 +If unspecified, the default is
 +@file{@value{localstatedir}/run/tinc.@var{netname}.pid}.
  
+ @item -o, --option=[@var{HOST}.]@var{KEY}=@var{VALUE}
+ Without specifying a @var{HOST}, this will set server configuration variable @var{KEY} to @var{VALUE}.
+ If specified as @var{HOST}.@var{KEY}=@var{VALUE},
+ this will set the host configuration variable @var{KEY} of the host named @var{HOST} to @var{VALUE}.
+ This option can be used more than once to specify multiple configuration variables.
  @item -L, --mlock
  Lock tinc into main memory.
  This will prevent sensitive data like shared private keys to be written to the system swap files/partitions.
diff --cc doc/tincd.8.in
@@@ -8,13 -8,17 +8,14 @@@
  .Nd tinc VPN daemon
  .Sh SYNOPSIS
  .Nm
- .Op Fl cdDKnLRU
 -.Op Fl cdDkKnoLRU
++.Op Fl cdDKnoLRU
  .Op Fl -config Ns = Ns Ar DIR
  .Op Fl -no-detach
  .Op Fl -debug Ns Op = Ns Ar LEVEL
 -.Op Fl -kill Ns Op = Ns Ar SIGNAL
  .Op Fl -net Ns = Ns Ar NETNAME
 -.Op Fl -generate-keys Ns Op = Ns Ar BITS
+ .Op Fl -option Ns = Ns Ar [HOST.]KEY=VALUE
  .Op Fl -mlock
  .Op Fl -logfile Ns Op = Ns Ar FILE
 -.Op Fl -pidfile Ns = Ns Ar FILE
  .Op Fl -bypass-security
  .Op Fl -chroot
  .Op Fl -user Ns = Ns Ar USER
@@@ -61,6 -73,29 +62,22 @@@ fo
  .Ar NETNAME
  is the same as not specifying any
  .Ar NETNAME .
 -.It Fl K, -generate-keys Ns Op = Ns Ar BITS
 -Generate public/private RSA keypair and exit.
 -If
 -.Ar BITS
 -is omitted, the default length will be 2048 bits.
 -When saving keys to existing files, tinc will not delete the old keys,
 -you have to remove them manually.
+ .It Fl o, -option Ns = Ns Ar [HOST.]KEY=VALUE
+ Without specifying a
+ .Ar HOST ,
+ this will set server configuration variable
+ .Ar KEY 
+ to
+ .Ar VALUE .
+ If specified as
+ .Ar HOST.KEY=VALUE ,
+ this will set the host configuration variable 
+ .Ar KEY
+ of the host named
+ .Ar HOST
+ to
+ .Ar VALUE .
+ This option can be used more than once to specify multiple configuration variables.
  .It Fl L, -mlock
  Lock tinc into main memory.
  This will prevent sensitive data like shared private keys to be written to the system swap files/partitions.
diff --cc src/Makefile.am
@@@ -1,29 -1,22 +1,38 @@@
  ## Produce this file with automake to get Makefile.in
  
 -sbin_PROGRAMS = tincd
 +sbin_PROGRAMS = tincd tincctl sptps_test
  
- EXTRA_DIST = linux bsd solaris cygwin mingw raw_socket uml_socket openssl gcrypt
 -EXTRA_DIST = linux/device.c bsd/device.c solaris/device.c cygwin/device.c mingw/device.c mingw/common.h
++EXTRA_DIST = linux bsd solaris cygwin mingw openssl gcrypt
  
 -tincd_SOURCES = conf.c connection.c edge.c event.c graph.c logger.c meta.c net.c net_packet.c net_setup.c     \
 -      net_socket.c netutl.c node.c process.c protocol.c protocol_auth.c protocol_edge.c protocol_misc.c       \
 +tincd_SOURCES = \
 +      utils.c getopt.c getopt1.c list.c splay_tree.c dropin.c fake-getaddrinfo.c fake-getnameinfo.c \
 +      buffer.c conf.c connection.c control.c edge.c graph.c logger.c meta.c net.c net_packet.c net_setup.c \
 +      net_socket.c netutl.c node.c process.c protocol.c protocol_auth.c protocol_edge.c protocol_misc.c \
-       protocol_key.c protocol_subnet.c route.c subnet.c tincd.c
+       protocol_key.c protocol_subnet.c route.c subnet.c tincd.c \
+       dummy_device.c raw_socket_device.c
+       
+ if UML
+ tincd_SOURCES += uml_device.c
+ endif
+ if VDE
+ tincd_SOURCES += vde_device.c
+ endif
  
 +nodist_tincd_SOURCES = \
 +      device.c cipher.c crypto.c ecdh.c ecdsa.c digest.c prf.c rsa.c
 +
 +tincctl_SOURCES = \
 +      utils.c getopt.c getopt1.c dropin.c \
 +      list.c tincctl.c top.c
 +
 +nodist_tincctl_SOURCES = \
 +      ecdsagen.c rsagen.c
 +
 +sptps_test_SOURCES = \
 +      logger.c cipher.c crypto.c ecdh.c ecdsa.c digest.c prf.c \
 +      sptps.c sptps_test.c
 +
  if TUNEMU
  tincd_SOURCES += bsd/tunemu.c
  endif
@@@ -190,8 -194,8 +194,8 @@@ static void close_device(void) 
        free(iface);
  }
  
- bool read_packet(vpn_packet_t *packet) {
static bool read_packet(vpn_packet_t *packet) {
 -      int lenin;
 +      int inlen;
  
        switch(device_type) {
                case DEVICE_TYPE_TUN:
  #include "utils.h"
  #include "xalloc.h"
  
 -avl_tree_t *connection_tree;  /* Meta connections */
 +splay_tree_t *connection_tree;        /* Meta connections */
- connection_t *broadcast;
+ connection_t *everyone;
  
  static int connection_compare(const connection_t *a, const connection_t *b) {
        return a < b ? -1 : a == b ? 0 : 1;
  }
  
  void init_connections(void) {
 -      connection_tree = avl_alloc_tree((avl_compare_t) connection_compare, (avl_action_t) free_connection);
 +      connection_tree = splay_alloc_tree((splay_compare_t) connection_compare, (splay_action_t) free_connection);
-       broadcast = new_connection();
-       broadcast->name = xstrdup("everyone");
-       broadcast->hostname = xstrdup("BROADCAST");
+       everyone = new_connection();
+       everyone->name = xstrdup("everyone");
+       everyone->hostname = xstrdup("BROADCAST");
  }
  
  void exit_connections(void) {
 -      avl_delete_tree(connection_tree);
 +      splay_delete_tree(connection_tree);
-       free_connection(broadcast);
+       free_connection(everyone);
  }
  
  connection_t *new_connection(void) {
@@@ -93,13 -89,19 +93,13 @@@ typedef struct connection_t 
        int tcplen;                                     /* length of incoming TCPpacket */
        int allow_request;                      /* defined if there's only one request possible */
  
 -      char *outbuf;                           /* metadata output buffer */
 -      int outbufstart;                        /* index of first meaningful byte in output buffer */
 -      int outbuflen;                          /* number of meaningful bytes in output buffer */
 -      int outbufsize;                         /* number of bytes allocated to output buffer */
 -
        time_t last_ping_time;          /* last time we saw some activity from the other end or pinged them */
 -      time_t last_flushed_time;       /* last time buffer was empty. Only meaningful if outbuflen > 0 */
  
 -      avl_tree_t *config_tree;        /* Pointer to configuration tree belonging to him */
 +      splay_tree_t *config_tree;      /* Pointer to configuration tree belonging to him */
  } connection_t;
  
 -extern avl_tree_t *connection_tree;
 +extern splay_tree_t *connection_tree;
- extern connection_t *broadcast;
+ extern connection_t *everyone;
  
  extern void init_connections(void);
  extern void exit_connections(void);
@@@ -225,10 -225,10 +225,10 @@@ static void close_device(void) 
        free(iface);
  }
  
- bool read_packet(vpn_packet_t *packet) {
static bool read_packet(vpn_packet_t *packet) {
 -      int lenin;
 +      int inlen;
  
 -      if((lenin = read(sp[0], packet->data, MTU)) <= 0) {
 +      if((inlen = read(sp[0], packet->data, MTU)) <= 0) {
                logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
                           device, strerror(errno));
                return false;
        return true;
  }
  
- bool write_packet(vpn_packet_t *packet) {
static bool write_packet(vpn_packet_t *packet) {
 -      long lenout;
 +      long outlen;
  
        ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s",
                           packet->len, device_info);
diff --cc src/device.h
  
  extern int device_fd;
  extern char *device;
 -
  extern char *iface;
  
- extern bool setup_device(void);
- extern void close_device(void);
- extern bool read_packet(struct vpn_packet_t *);
- extern bool write_packet(struct vpn_packet_t *);
- extern void dump_device_stats(void);
 +extern uint64_t device_in_packets;
 +extern uint64_t device_in_bytes;
 +extern uint64_t device_out_packets;
 +extern uint64_t device_out_bytes;
 +
+ typedef struct devops_t {
+       bool (*setup)(void);
+       void (*close)(void);
+       bool (*read)(struct vpn_packet_t *);
+       bool (*write)(struct vpn_packet_t *);
+       void (*dump_stats)(void);
+ } devops_t;
+ extern const devops_t os_devops;
+ extern const devops_t dummy_devops;
+ extern const devops_t raw_socket_devops;
+ extern const devops_t uml_devops;
+ extern const devops_t vde_devops;
+ extern devops_t devops;
  
  #endif                                                        /* __TINC_DEVICE_H__ */
diff --cc src/ipv4.h
Simple merge
@@@ -44,12 -49,13 +45,12 @@@ static char *type = NULL
  static char ifrname[IFNAMSIZ];
  static char *device_info;
  
 -static uint64_t device_total_in = 0;
 -static uint64_t device_total_out = 0;
 +uint64_t device_in_packets = 0;
 +uint64_t device_in_bytes = 0;
 +uint64_t device_out_packets = 0;
 +uint64_t device_out_bytes = 0;
  
- bool setup_device(void) {
static bool setup_device(void) {
 -      struct ifreq ifr;
 -      bool t1q = false;
 -
        if(!get_config_string(lookup_config(config_tree, "Device"), &device))
                device = xstrdup(DEFAULT_DEVICE);
  
                return false;
        }
  
 -#ifdef HAVE_LINUX_IF_TUN_H
 -      /* Ok now check if this is an old ethertap or a new tun/tap thingie */
 -
 -      memset(&ifr, 0, sizeof(ifr));
+ #ifdef FD_CLOEXEC
+       fcntl(device_fd, F_SETFD, FD_CLOEXEC);
+ #endif
 +      struct ifreq ifr = {{{0}}};
  
-       if(routing_mode == RMODE_ROUTER) {
+       get_config_string(lookup_config(config_tree, "DeviceType"), &type);
+       if(type && strcasecmp(type, "tun") && strcasecmp(type, "tap")) {
+               logger(LOG_ERR, "Unknown device type %s!", type);
+               return false;
+       }
+       if((type && !strcasecmp(type, "tun")) || (!type && routing_mode == RMODE_ROUTER)) {
                ifr.ifr_flags = IFF_TUN;
                device_type = DEVICE_TYPE_TUN;
                device_info = "Linux tun/tap device (tun mode)";
@@@ -113,8 -142,8 +126,8 @@@ static void close_device(void) 
        free(iface);
  }
  
- bool read_packet(vpn_packet_t *packet) {
static bool read_packet(vpn_packet_t *packet) {
 -      int lenin;
 +      int inlen;
        
        switch(device_type) {
                case DEVICE_TYPE_TUN:
@@@ -182,8 -225,16 +195,16 @@@ static bool write_packet(vpn_packet_t *
        return true;
  }
  
- void dump_device_stats(void) {
static void dump_device_stats(void) {
        logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device);
 -      logger(LOG_DEBUG, " total bytes in:  %10"PRIu64, device_total_in);
 -      logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out);
 +      logger(LOG_DEBUG, " total bytes in:  %10"PRIu64, device_in_bytes);
 +      logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_out_bytes);
  }
+ const devops_t os_devops = {
+       .setup = setup_device,
+       .close = close_device,
+       .read = read_packet,
+       .write = write_packet,
+       .dump_stats = dump_device_stats,
+ };
@@@ -221,8 -221,8 +221,8 @@@ static bool read_packet(vpn_packet_t *p
        return false;
  }
  
- bool write_packet(vpn_packet_t *packet) {
static bool write_packet(vpn_packet_t *packet) {
 -      long lenout;
 +      long outlen;
        OVERLAPPED overlapped = {0};
  
        ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s",
diff --cc src/net.c
+++ b/src/net.c
@@@ -117,9 -178,12 +117,9 @@@ void terminate_connection(connection_t 
        if(c->node)
                c->node->connection = NULL;
  
 -      if(c->socket)
 -              closesocket(c->socket);
 -
        if(c->edge) {
                if(report && !tunnelserver)
-                       send_del_edge(broadcast, c->edge);
+                       send_del_edge(everyone, c->edge);
  
                edge_del(c->edge);
  
@@@ -237,156 -350,263 +237,156 @@@ void handle_meta_connection_data(int fd
        }
  }
  
 -/*
 -  this is where it all happens...
 -*/
 -int main_loop(void) {
 -      fd_set readset, writeset;
 -#ifdef HAVE_PSELECT
 -      struct timespec tv;
 -      sigset_t omask, block_mask;
 -      time_t next_event;
 -#else
 -      struct timeval tv;
 -#endif
 -      int r, maxfd;
 -      time_t last_ping_check, last_config_check, last_graph_dump;
 -      event_t *event;
 +static void sigterm_handler(int signal, short events, void *data) {
 +      logger(LOG_NOTICE, "Got %s signal", strsignal(signal));
 +      event_loopexit(NULL);
 +}
  
 -      last_ping_check = now;
 -      last_config_check = now;
 -      last_graph_dump = now;
 -      
 -      srand(now);
 -
 -#ifdef HAVE_PSELECT
 -      if(lookup_config(config_tree, "GraphDumpFile"))
 -              graph_dump = true;
 -      /* Block SIGHUP & SIGALRM */
 -      sigemptyset(&block_mask);
 -      sigaddset(&block_mask, SIGHUP);
 -      sigaddset(&block_mask, SIGALRM);
 -      sigprocmask(SIG_BLOCK, &block_mask, &omask);
 -#endif
 +static void sighup_handler(int signal, short events, void *data) {
 +      logger(LOG_NOTICE, "Got %s signal", strsignal(signal));
 +      reopenlogger();
 +      reload_configuration();
 +}
  
 -      running = true;
 -
 -      while(running) {
 -#ifdef HAVE_PSELECT
 -              next_event = last_ping_check + pingtimeout;
 -              if(graph_dump && next_event > last_graph_dump + 60)
 -                      next_event = last_graph_dump + 60;
 -
 -              if((event = peek_next_event()) && next_event > event->time)
 -                      next_event = event->time;
 -
 -              if(next_event <= now)
 -                      tv.tv_sec = 0;
 -              else
 -                      tv.tv_sec = next_event - now;
 -              tv.tv_nsec = 0;
 -#else
 -              tv.tv_sec = 1;
 -              tv.tv_usec = 0;
 -#endif
 +static void sigalrm_handler(int signal, short events, void *data) {
 +      logger(LOG_NOTICE, "Got %s signal", strsignal(signal));
 +      retry();
 +}
  
 -              maxfd = build_fdset(&readset, &writeset);
 +int reload_configuration(void) {
 +      connection_t *c;
 +      splay_node_t *node, *next;
 +      char *fname;
 +      struct stat s;
 +      static time_t last_config_check = 0;
  
 -#ifdef HAVE_MINGW
 -              LeaveCriticalSection(&mutex);
 -#endif
 -#ifdef HAVE_PSELECT
 -              r = pselect(maxfd + 1, &readset, &writeset, NULL, &tv, &omask);
 -#else
 -              r = select(maxfd + 1, &readset, &writeset, NULL, &tv);
 -#endif
 -              now = time(NULL);
 -#ifdef HAVE_MINGW
 -              EnterCriticalSection(&mutex);
 -#endif
 +      /* Reread our own configuration file */
  
 -              if(r < 0) {
 -                      if(!sockwouldblock(sockerrno)) {
 -                              logger(LOG_ERR, "Error while waiting for input: %s", sockstrerror(sockerrno));
 -                              dump_connections();
 -                              return 1;
 -                      }
 -              }
 +      exit_configuration(&config_tree);
 +      init_configuration(&config_tree);
  
 -              if(r > 0)
 -                      check_network_activity(&readset, &writeset);
 +      if(!read_server_config()) {
 +              logger(LOG_ERR, "Unable to reread configuration file, exitting.");
 +              event_loopexit(NULL);
 +              return EINVAL;
 +      }
  
 -              if(do_purge) {
 -                      purge();
 -                      do_purge = false;
 +      /* Close connections to hosts that have a changed or deleted host config file */
 +      
 +      for(node = connection_tree->head; node; node = next) {
 +              c = node->data;
 +              next = node->next;
 +              
 +              if(c->outgoing) {
 +                      free(c->outgoing->name);
 +                      if(c->outgoing->ai)
 +                              freeaddrinfo(c->outgoing->ai);
 +                      free(c->outgoing);
 +                      c->outgoing = NULL;
                }
 +              
 +              xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
 +              if(stat(fname, &s) || s.st_mtime > last_config_check)
 +                      terminate_connection(c, c->status.active);
 +              free(fname);
 +      }
  
 -              /* Let's check if everybody is still alive */
 -
 -              if(last_ping_check + pingtimeout <= now) {
 -                      check_dead_connections();
 -                      last_ping_check = now;
 -
 -                      if(routing_mode == RMODE_SWITCH)
 -                              age_subnets();
 -
 -                      age_past_requests();
 -
 -                      /* Should we regenerate our key? */
 -
 -                      if(keyexpires <= now) {
 -                              avl_node_t *node;
 -                              node_t *n;
 -
 -                              ifdebug(STATUS) logger(LOG_INFO, "Expiring symmetric keys");
 +      last_config_check = time(NULL);
  
 -                              for(node = node_tree->head; node; node = node->next) {
 -                                      n = node->data;
 -                                      if(n->inkey) {
 -                                              free(n->inkey);
 -                                              n->inkey = NULL;
 -                                      }
 -                              }
 +      /* If StrictSubnet is set, expire deleted Subnets and read new ones in */
  
 -                              send_key_changed();
 -                              keyexpires = now + keylifetime;
 -                      }
 +      if(strictsubnets) {
 +              subnet_t *subnet;
  
 -                      /* Detect ADD_EDGE/DEL_EDGE storms that are caused when
 -                       * two tinc daemons with the same name are on the VPN.
 -                       * If so, sleep a while. If this happens multiple times
 -                       * in a row, sleep longer. */
 -
 -                      if(contradicting_del_edge > 100 && contradicting_add_edge > 100) {
 -                              logger(LOG_WARNING, "Possible node with same Name as us! Sleeping %d seconds.", sleeptime);
 -                              usleep(sleeptime * 1000000LL);
 -                              sleeptime *= 2;
 -                              if(sleeptime < 0)
 -                                      sleeptime = 3600;
 -                      } else {
 -                              sleeptime /= 2;
 -                              if(sleeptime < 10)
 -                                      sleeptime = 10;
 -                      }
  
 -                      contradicting_add_edge = 0;
 -                      contradicting_del_edge = 0;
 +              for(node = subnet_tree->head; node; node = node->next) {
 +                      subnet = node->data;
 +                      subnet->expires = 1;
                }
  
 -              if(sigalrm) {
 -                      avl_node_t *node;
 -                      logger(LOG_INFO, "Flushing event queue");
 -                      expire_events();
 -                      for(node = connection_tree->head; node; node = node->next) {
 -                              connection_t *c = node->data;
 -                              send_ping(c);
 +              load_all_subnets();
 +
 +              for(node = subnet_tree->head; node; node = next) {
 +                      next = node->next;
 +                      subnet = node->data;
 +                      if(subnet->expires == 1) {
-                               send_del_subnet(broadcast, subnet);
++                              send_del_subnet(everyone, subnet);
 +                              if(subnet->owner->status.reachable)
 +                                      subnet_update(subnet->owner, subnet, false);
 +                              subnet_del(subnet->owner, subnet);
 +                      } else if(subnet->expires == -1) {
 +                              subnet->expires = 0;
 +                      } else {
-                               send_add_subnet(broadcast, subnet);
++                              send_add_subnet(everyone, subnet);
 +                              if(subnet->owner->status.reachable)
 +                                      subnet_update(subnet->owner, subnet, true);
                        }
 -                      sigalrm = false;
                }
 +      }
  
 -              while((event = get_expired_event())) {
 -                      event->handler(event->data);
 -                      free_event(event);
 -              }
 -
 -              if(sighup) {
 -                      connection_t *c;
 -                      avl_node_t *node, *next;
 -                      char *fname;
 -                      struct stat s;
 -                      
 -                      sighup = false;
 -
 -                      reopenlogger();
 -                      
 -                      /* Reread our own configuration file */
 -
 -                      exit_configuration(&config_tree);
 -                      init_configuration(&config_tree);
 -
 -                      if(!read_server_config()) {
 -                              logger(LOG_ERR, "Unable to reread configuration file, exitting.");
 -                              return 1;
 -                      }
 -
 -                      /* Cancel non-active outgoing connections */
 -
 -                      for(node = connection_tree->head; node; node = next) {
 -                              next = node->next;
 -                              c = node->data;
 -
 -                              c->outgoing = NULL;
 -
 -                              if(c->status.connecting) {
 -                                      terminate_connection(c, false);
 -                                      connection_del(c);
 -                              }
 -                      }
 -
 -                      /* Wipe list of outgoing connections */
 -
 -                      for(list_node_t *node = outgoing_list->head; node; node = node->next) {
 -                              outgoing_t *outgoing = node->data;
 -
 -                              if(outgoing->event)
 -                                      event_del(outgoing->event);
 -                      }
 -
 -                      list_delete_list(outgoing_list);
 -
 -                      /* Close connections to hosts that have a changed or deleted host config file */
 -                      
 -                      for(node = connection_tree->head; node; node = node->next) {
 -                              c = node->data;
 -                              
 -                              xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
 -                              if(stat(fname, &s) || s.st_mtime > last_config_check)
 -                                      terminate_connection(c, c->status.active);
 -                              free(fname);
 -                      }
 -
 -                      last_config_check = now;
 -
 -                      /* If StrictSubnet is set, expire deleted Subnets and read new ones in */
 -
 -                      if(strictsubnets) {
 -                              subnet_t *subnet;
 +      /* Try to make outgoing connections */
 +      
 +      try_outgoing_connections();
  
 -                              for(node = subnet_tree->head; node; node = node->next) {
 -                                      subnet = node->data;
 -                                      subnet->expires = 1;
 -                              }
 +      return 0;
 +}
  
 -                              load_all_subnets();
 -
 -                              for(node = subnet_tree->head; node; node = next) {
 -                                      next = node->next;
 -                                      subnet = node->data;
 -                                      if(subnet->expires == 1) {
 -                                              send_del_subnet(everyone, subnet);
 -                                              if(subnet->owner->status.reachable)
 -                                                      subnet_update(subnet->owner, subnet, false);
 -                                              subnet_del(subnet->owner, subnet);
 -                                      } else if(subnet->expires == -1) {
 -                                              subnet->expires = 0;
 -                                      } else {
 -                                              send_add_subnet(everyone, subnet);
 -                                              if(subnet->owner->status.reachable)
 -                                                      subnet_update(subnet->owner, subnet, true);
 -                                      }
 -                              }
 -                      }
 +void retry(void) {
 +      connection_t *c;
 +      splay_node_t *node;
  
 -                      /* Try to make outgoing connections */
 -                      
 -                      try_outgoing_connections();
 -              }
 +      for(node = connection_tree->head; node; node = node->next) {
 +              c = node->data;
                
 -              /* Dump graph if wanted every 60 seconds*/
 -
 -              if(last_graph_dump + 60 <= now) {
 -                      dump_graph();
 -                      last_graph_dump = now;
 +              if(c->outgoing && !c->node) {
 +                      if(timeout_initialized(&c->outgoing->ev))
 +                              event_del(&c->outgoing->ev);
 +                      if(c->status.connecting)
 +                              close(c->socket);
 +                      c->outgoing->timeout = 0;
 +                      do_outgoing_connection(c);
                }
        }
 +}
 +
 +/*
 +  this is where it all happens...
 +*/
 +int main_loop(void) {
 +      struct event timeout_event;
 +
 +      timeout_set(&timeout_event, timeout_handler, &timeout_event);
 +      event_add(&timeout_event, &(struct timeval){pingtimeout, 0});
 +
 +#ifndef HAVE_MINGW
 +      struct event sighup_event;
 +      struct event sigterm_event;
 +      struct event sigquit_event;
 +      struct event sigalrm_event;
 +
 +      signal_set(&sighup_event, SIGHUP, sighup_handler, NULL);
 +      signal_add(&sighup_event, NULL);
 +      signal_set(&sigterm_event, SIGTERM, sigterm_handler, NULL);
 +      signal_add(&sigterm_event, NULL);
 +      signal_set(&sigquit_event, SIGQUIT, sigterm_handler, NULL);
 +      signal_add(&sigquit_event, NULL);
 +      signal_set(&sigalrm_event, SIGALRM, sigalrm_handler, NULL);
 +      signal_add(&sigalrm_event, NULL);
 +#endif
 +
 +      if(event_loop(0) < 0) {
 +              logger(LOG_ERR, "Error while waiting for input: %s", strerror(errno));
 +              return 1;
 +      }
  
 -#ifdef HAVE_PSELECT
 -      /* Restore SIGHUP & SIGALARM mask */
 -      sigprocmask(SIG_SETMASK, &omask, NULL);
 +#ifndef HAVE_MINGW
 +      signal_del(&sighup_event);
 +      signal_del(&sigterm_event);
 +      signal_del(&sigquit_event);
 +      signal_del(&sigalrm_event);
  #endif
  
 +      event_del(&timeout_event);
 +
        return 0;
  }
@@@ -375,13 -383,12 +375,12 @@@ static void send_udppacket(node_t *n, v
        vpn_packet_t *inpkt = origpkt;
        int nextpkt = 0;
        vpn_packet_t *outpkt;
 -      int origlen;
 -      int outlen, outpad;
 +      int origlen = origpkt->len;
 +      size_t outlen;
  #if defined(SOL_IP) && defined(IP_TOS)
        static int priority = 0;
 +      int origpriority = origpkt->priority;
  #endif
-       int sock;
 -      int origpriority;
  
        if(!n->status.reachable) {
                ifdebug(TRAFFIC) logger(LOG_INFO, "Trying to send UDP packet to unreachable node %s (%s)", n->name, n->hostname);
@@@ -505,9 -519,7 +506,9 @@@ void send_packet(node_t *n, vpn_packet_
        if(n == myself) {
                if(overwrite_mac)
                         memcpy(packet->data, mymac.x, ETH_ALEN);
-               write_packet(packet);
 +              n->out_packets++;
 +              n->out_bytes += packet->len;
+               devops.write(packet);
                return;
        }
  
@@@ -631,17 -636,7 +632,19 @@@ void handle_incoming_vpn_data(int sock
                        return;
        }
  
 -      n->sock = sock;
++      n->sock = (intptr_t)data;
        receive_udppacket(n, &pkt);
  }
-       if(read_packet(&packet)) {
 +
 +void handle_device_data(int sock, short events, void *data) {
 +      vpn_packet_t packet;
 +
 +      packet.priority = 0;
 +
++      if(devops.read(&packet)) {
 +              myself->in_packets++;
 +              myself->in_bytes += packet.len;
 +              route(myself, &packet);
 +      }
 +}
diff --cc src/net_setup.c
  #include "xalloc.h"
  
  char *myport;
 +static struct event device_ev;
+ devops_t devops;
  
 -bool read_rsa_public_key(connection_t *c) {
 +bool node_read_ecdsa_public_key(node_t *n) {
 +      if(ecdsa_active(&n->ecdsa))
 +              return true;
 +
 +      splay_tree_t *config_tree;
        FILE *fp;
        char *fname;
 -      char *key;
 +      char *p;
 +      bool result = false;
  
 -      if(!c->rsa_key) {
 -              c->rsa_key = RSA_new();
 -//            RSA_blinding_on(c->rsa_key, NULL);
 -      }
 +      xasprintf(&fname, "%s/hosts/%s", confbase, n->name);
  
 -      /* First, check for simple PublicKey statement */
 +      init_configuration(&config_tree);
 +      if(!read_config_file(config_tree, fname))
 +              goto exit;
  
 -      if(get_config_string(lookup_config(c->config_tree, "PublicKey"), &key)) {
 -              BN_hex2bn(&c->rsa_key->n, key);
 -              BN_hex2bn(&c->rsa_key->e, "FFFF");
 -              free(key);
 -              return true;
 +      /* First, check for simple ECDSAPublicKey statement */
 +
 +      if(get_config_string(lookup_config(config_tree, "ECDSAPublicKey"), &p)) {
 +              result = ecdsa_set_base64_public_key(&n->ecdsa, p);
 +              free(p);
 +              goto exit;
        }
  
 -      /* Else, check for PublicKeyFile statement and read it */
 +      /* Else, check for ECDSAPublicKeyFile statement and read it */
  
 -      if(get_config_string(lookup_config(c->config_tree, "PublicKeyFile"), &fname)) {
 -              fp = fopen(fname, "r");
 +      free(fname);
  
 -              if(!fp) {
 -                      logger(LOG_ERR, "Error reading RSA public key file `%s': %s",
 -                                 fname, strerror(errno));
 -                      free(fname);
 -                      return false;
 -              }
 +      if(!get_config_string(lookup_config(config_tree, "ECDSAPublicKeyFile"), &fname))
 +              xasprintf(&fname, "%s/hosts/%s", confbase, n->name);
  
 -              free(fname);
 -              c->rsa_key = PEM_read_RSAPublicKey(fp, &c->rsa_key, NULL, NULL);
 -              fclose(fp);
 +      fp = fopen(fname, "r");
  
 -              if(c->rsa_key)
 -                      return true;            /* Woohoo. */
 +      if(!fp) {
 +              logger(LOG_ERR, "Error reading ECDSA public key file `%s': %s", fname, strerror(errno));
 +              goto exit;
 +      }
  
 -              /* If it fails, try PEM_read_RSA_PUBKEY. */
 -              fp = fopen(fname, "r");
 +      result = ecdsa_read_pem_public_key(&n->ecdsa, fp);
 +      fclose(fp);
  
 -              if(!fp) {
 -                      logger(LOG_ERR, "Error reading RSA public key file `%s': %s",
 -                                 fname, strerror(errno));
 -                      free(fname);
 -                      return false;
 -              }
 +exit:
 +      exit_configuration(&config_tree);
 +      free(fname);
 +      return result;
 +}
  
 -              free(fname);
 -              c->rsa_key = PEM_read_RSA_PUBKEY(fp, &c->rsa_key, NULL, NULL);
 -              fclose(fp);
 +bool read_ecdsa_public_key(connection_t *c) {
 +      FILE *fp;
 +      char *fname;
 +      char *p;
 +      bool result;
  
 -              if(c->rsa_key) {
 -//                            RSA_blinding_on(c->rsa_key, NULL);
 -                      return true;
 -              }
 +      /* First, check for simple ECDSAPublicKey statement */
  
 -              logger(LOG_ERR, "Reading RSA public key file `%s' failed: %s",
 -                         fname, strerror(errno));
 -              return false;
 +      if(get_config_string(lookup_config(c->config_tree, "ECDSAPublicKey"), &p)) {
 +              result = ecdsa_set_base64_public_key(&c->ecdsa, p);
 +              free(p);
 +              return result;
        }
  
 -      /* Else, check if a harnessed public key is in the config file */
 +      /* Else, check for ECDSAPublicKeyFile statement and read it */
 +
 +      if(!get_config_string(lookup_config(c->config_tree, "ECDSAPublicKeyFile"), &fname))
 +              xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
  
 -      xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
        fp = fopen(fname, "r");
  
        if(!fp) {
@@@ -157,54 -138,17 +157,53 @@@ bool read_rsa_public_key(connection_t *
                return false;
        }
  
 -      c->rsa_key = PEM_read_RSA_PUBKEY(fp, &c->rsa_key, NULL, NULL);
 -//    RSA_blinding_on(c->rsa_key, NULL);
 +      result = rsa_read_pem_public_key(&c->rsa, fp);
        fclose(fp);
 +
 +      if(!result) 
 +              logger(LOG_ERR, "Reading RSA public key file `%s' failed: %s", fname, strerror(errno));
        free(fname);
 +      return result;
 +}
  
 -      if(c->rsa_key)
 -              return true;
 +static bool read_ecdsa_private_key(void) {
 +      FILE *fp;
 +      char *fname;
 +      bool result;
  
 -      logger(LOG_ERR, "No public key for %s specified!", c->name);
 +      /* Check for PrivateKeyFile statement and read it */
 +
 +      if(!get_config_string(lookup_config(config_tree, "ECDSAPrivateKeyFile"), &fname))
 +              xasprintf(&fname, "%s/ecdsa_key.priv", confbase);
 +
 +      fp = fopen(fname, "r");
 +
 +      if(!fp) {
-               logger(LOG_ERR, "Error reading ECDSA private key file `%s': %s",
-                          fname, strerror(errno));
++              logger(LOG_ERR, "Error reading ECDSA private key file `%s': %s", fname, strerror(errno));
 +              free(fname);
 +              return false;
 +      }
 +
 +#if !defined(HAVE_MINGW) && !defined(HAVE_CYGWIN)
 +      struct stat s;
 +
 +      if(fstat(fileno(fp), &s)) {
 +              logger(LOG_ERR, "Could not stat ECDSA private key file `%s': %s'", fname, strerror(errno));
 +              free(fname);
 +              return false;
 +      }
  
 -      return false;
 +      if(s.st_mode & ~0100700)
 +              logger(LOG_WARNING, "Warning: insecure file permissions for ECDSA private key file `%s'!", fname);
 +#endif
 +
 +      result = ecdsa_read_pem_private_key(&myself->connection->ecdsa, fp);
 +      fclose(fp);
 +
 +      if(!result) 
 +              logger(LOG_ERR, "Reading ECDSA private key file `%s' failed: %s", fname, strerror(errno));
 +      free(fname);
 +      return result;
  }
  
  static bool read_rsa_private_key(void) {
@@@ -589,19 -542,26 +590,36 @@@ static bool setup_myself(void) 
  
        /* Open device */
  
-       if(!setup_device())
+       devops = os_devops;
+       if(get_config_string(lookup_config(config_tree, "DeviceType"), &type)) {
+               if(!strcasecmp(type, "dummy"))
+                       devops = dummy_devops;
+               else if(!strcasecmp(type, "raw_socket"))
+                       devops = raw_socket_devops;
+ #ifdef ENABLE_UML
+               else if(!strcasecmp(type, "uml"))
+                       devops = uml_devops;
+ #endif
+ #ifdef ENABLE_VDE
+               else if(!strcasecmp(type, "vde"))
+                       devops = vde_devops;
+ #endif
+       }
+       if(!devops.setup())
                return false;
  
-                       close_device();
 +      if(device_fd >= 0) {
 +              event_set(&device_ev, device_fd, EV_READ|EV_PERSIST, handle_device_data, NULL);
 +
 +              if (event_add(&device_ev, NULL) < 0) {
 +                      logger(LOG_ERR, "event_add failed: %s", strerror(errno));
++                      devops.close();
 +                      return false;
 +              }
 +      }
 +
        /* Run tinc-up script to further initialize the tap interface */
        xasprintf(&envp[0], "NETNAME=%s", netname ? : "");
        xasprintf(&envp[1], "DEVICE=%s", device ? : "");
  
        /* Open sockets */
  
-       get_config_string(lookup_config(config_tree, "BindToAddress"), &address);
+       listen_sockets = 0;
+       cfg = lookup_config(config_tree, "BindToAddress");
  
-       hint.ai_family = addressfamily;
-       hint.ai_socktype = SOCK_STREAM;
-       hint.ai_protocol = IPPROTO_TCP;
-       hint.ai_flags = AI_PASSIVE;
+       do {
+               get_config_string(cfg, &address);
+               if(cfg)
+                       cfg = lookup_config_next(config_tree, cfg);
  
-       err = getaddrinfo(address, myport, &hint, &ai);
+               hint.ai_family = addressfamily;
+               hint.ai_socktype = SOCK_STREAM;
+               hint.ai_protocol = IPPROTO_TCP;
+               hint.ai_flags = AI_PASSIVE;
  
-       if(err || !ai) {
-               logger(LOG_ERR, "System call `%s' failed: %s", "getaddrinfo",
-                          gai_strerror(err));
-               return false;
-       }
+               err = getaddrinfo(address, myport, &hint, &ai);
+               free(address);
  
-       listen_sockets = 0;
+               if(err || !ai) {
+                       logger(LOG_ERR, "System call `%s' failed: %s", "getaddrinfo",
+                                  gai_strerror(err));
+                       return false;
+               }
  
-       for(aip = ai; aip; aip = aip->ai_next) {
-               listen_socket[listen_sockets].tcp =
-                       setup_listen_socket((sockaddr_t *) aip->ai_addr);
+               for(aip = ai; aip; aip = aip->ai_next) {
+                       if(listen_sockets >= MAXSOCKETS) {
+                               logger(LOG_ERR, "Too many listening sockets");
+                               return false;
+                       }
  
-               if(listen_socket[listen_sockets].tcp < 0)
-                       continue;
+                       listen_socket[listen_sockets].tcp =
+                               setup_listen_socket((sockaddr_t *) aip->ai_addr);
  
-               listen_socket[listen_sockets].udp =
-                       setup_vpn_in_socket((sockaddr_t *) aip->ai_addr);
+                       if(listen_socket[listen_sockets].tcp < 0)
+                               continue;
  
-               if(listen_socket[listen_sockets].udp < 0) {
-                       close(listen_socket[listen_sockets].tcp);
-                       continue;
-               }
+                       listen_socket[listen_sockets].udp =
+                               setup_vpn_in_socket((sockaddr_t *) aip->ai_addr);
  
-               event_set(&listen_socket[listen_sockets].ev_tcp,
-                                 listen_socket[listen_sockets].tcp,
-                                 EV_READ|EV_PERSIST,
-                                 handle_new_meta_connection, NULL);
-               if(event_add(&listen_socket[listen_sockets].ev_tcp, NULL) < 0) {
-                       logger(LOG_ERR, "event_add failed: %s", strerror(errno));
-                       abort();
-               }
 -                      if(listen_socket[listen_sockets].udp < 0)
++                      if(listen_socket[listen_sockets].udp < 0) {
++                              close(listen_socket[listen_sockets].tcp);
+                               continue;
++                      }
 +
-               event_set(&listen_socket[listen_sockets].ev_udp,
-                                 listen_socket[listen_sockets].udp,
-                                 EV_READ|EV_PERSIST,
-                                 handle_incoming_vpn_data, NULL);
-               if(event_add(&listen_socket[listen_sockets].ev_udp, NULL) < 0) {
-                       logger(LOG_ERR, "event_add failed: %s", strerror(errno));
-                       abort();
-               }
++                      event_set(&listen_socket[listen_sockets].ev_tcp,
++                                        listen_socket[listen_sockets].tcp,
++                                        EV_READ|EV_PERSIST,
++                                        handle_new_meta_connection, NULL);
++                      if(event_add(&listen_socket[listen_sockets].ev_tcp, NULL) < 0) {
++                              logger(LOG_ERR, "event_add failed: %s", strerror(errno));
++                              abort();
++                      }
 +
-               ifdebug(CONNECTIONS) {
-                       hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr);
-                       logger(LOG_NOTICE, "Listening on %s", hostname);
-                       free(hostname);
-               }
++                      event_set(&listen_socket[listen_sockets].ev_udp,
++                                        listen_socket[listen_sockets].udp,
++                                        EV_READ|EV_PERSIST,
++                                        handle_incoming_vpn_data, (void *)(intptr_t)listen_sockets);
++                      if(event_add(&listen_socket[listen_sockets].ev_udp, NULL) < 0) {
++                              logger(LOG_ERR, "event_add failed: %s", strerror(errno));
++                              abort();
++                      }
  
-               memcpy(&listen_socket[listen_sockets].sa, aip->ai_addr, aip->ai_addrlen);
-               listen_sockets++;
+                       ifdebug(CONNECTIONS) {
+                               hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr);
+                               logger(LOG_NOTICE, "Listening on %s", hostname);
+                               free(hostname);
+                       }
  
-               if(listen_sockets >= MAXSOCKETS) {
-                       logger(LOG_WARNING, "Maximum of %d listening sockets reached", MAXSOCKETS);
-                       break;
+                       memcpy(&listen_socket[listen_sockets].sa, aip->ai_addr, aip->ai_addrlen);
+                       listen_sockets++;
                }
-       }
  
-       freeaddrinfo(ai);
+               freeaddrinfo(ai);
+       } while(cfg);
  
        if(listen_sockets)
                logger(LOG_NOTICE, "Ready");
Simple merge
diff --cc src/node.h
Simple merge
index 13841c4,0000000..1c432c7
mode 100644,000000..100644
--- /dev/null
@@@ -1,73 -1,0 +1,75 @@@
 +/*
 +    prf.c -- Pseudo-Random Function for key material generation
 +    Copyright (C) 2011 Guus Sliepen <guus@tinc-vpn.org>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License along
 +    with this program; if not, write to the Free Software Foundation, Inc.,
 +    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 +*/
 +
 +#include "system.h"
 +
++#include <openssl/obj_mac.h>
++
 +#include "digest.h"
 +#include "prf.h"
 +
 +/* Generate key material from a master secret and a seed, based on RFC 4346 section 5.
 +   We use SHA512 instead of MD5 and SHA1.
 + */
 +
 +static bool prf_xor(int nid, const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, ssize_t outlen) {
 +      digest_t digest;
 +      
 +      if(!digest_open_by_nid(&digest, nid, -1))
 +              return false;
 +
 +      if(!digest_set_key(&digest, secret, secretlen))
 +              return false;
 +
 +      size_t len = digest_length(&digest);
 +
 +      /* Data is what the "inner" HMAC function processes.
 +         It consists of the previous HMAC result plus the seed.
 +       */
 +
 +      char data[len + seedlen];
 +      memset(data, 0, len);
 +      memcpy(data + len, seed, seedlen);
 +
 +      char hash[len];
 +
 +      while(outlen > 0) {
 +              /* Inner HMAC */
 +              digest_create(&digest, data, len + seedlen, data);
 +
 +              /* Outer HMAC */
 +              digest_create(&digest, data, len + seedlen, hash);
 +
 +              /* XOR the results of the outer HMAC into the out buffer */
 +              for(int i = 0; i < len && i < outlen; i++)
 +                      *out++ ^= hash[i];
 +
 +              outlen -= len;
 +      }
 +
 +      digest_close(&digest);
 +      return true;
 +}
 +
 +bool prf(const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, size_t outlen) {
 +      /* This construction allows us to easily switch back to a scheme where the PRF is calculated using two different digest algorithms. */
 +      memset(out, 0, outlen);
 +
 +      return prf_xor(NID_sha512, secret, secretlen, seed, seedlen, out, outlen);
 +}
diff --cc src/protocol.c
@@@ -94,39 -94,42 +94,39 @@@ bool send_request(connection_t *c, cons
                                   c->name, c->hostname);
        }
  
 -      buffer[len++] = '\n';
 +      request[len++] = '\n';
  
-       if(c == broadcast) {
+       if(c == everyone) {
 -              broadcast_meta(NULL, buffer, len);
 +              broadcast_meta(NULL, request, len);
                return true;
        } else
 -              return send_meta(c, buffer, len);
 +              return send_meta(c, request, len);
  }
  
 -void forward_request(connection_t *from) {
 -      int request;
 -
 +void forward_request(connection_t *from, char *request) {
 +      /* Note: request is not zero terminated anymore after a call to this function! */
        ifdebug(PROTOCOL) {
 -              sscanf(from->buffer, "%d", &request);
                ifdebug(META)
                        logger(LOG_DEBUG, "Forwarding %s from %s (%s): %s",
 -                                 request_name[request], from->name, from->hostname,
 -                                 from->buffer);
 +                                 request_name[atoi(request)], from->name, from->hostname, request);
                else
                        logger(LOG_DEBUG, "Forwarding %s from %s (%s)",
 -                                 request_name[request], from->name, from->hostname);
 +                                 request_name[atoi(request)], from->name, from->hostname);
        }
  
 -      from->buffer[from->reqlen - 1] = '\n';
 -
 -      broadcast_meta(from, from->buffer, from->reqlen);
 +      int len = strlen(request);
 +      request[len++] = '\n';
 +      broadcast_meta(from, request, len);
  }
  
 -bool receive_request(connection_t *c) {
 -      int request;
 +bool receive_request(connection_t *c, char *request) {
 +      int reqno = atoi(request);
  
 -      if(sscanf(c->buffer, "%d", &request) == 1) {
 -              if((request < 0) || (request >= LAST) || !request_handlers[request]) {
 +      if(reqno || *request == '0') {
 +              if((reqno < 0) || (reqno >= LAST) || !request_handlers[reqno]) {
                        ifdebug(META)
                                logger(LOG_DEBUG, "Unknown request from %s (%s): %s",
 -                                         c->name, c->hostname, c->buffer);
 +                                         c->name, c->hostname, request);
                        else
                                logger(LOG_ERR, "Unknown request from %s (%s)",
                                           c->name, c->hostname);
Simple merge
Simple merge
  static bool mykeyused = false;
  
  void send_key_changed(void) {
 -      avl_node_t *node;
 +      splay_node_t *node;
        connection_t *c;
  
-       send_request(broadcast, "%d %x %s", KEY_CHANGED, rand(), myself->name);
+       send_request(everyone, "%d %x %s", KEY_CHANGED, rand(), myself->name);
  
        /* Immediately send new keys to directly connected nodes to keep UDP mappings alive */
  
@@@ -57,7 -56,11 +56,12 @@@ static bool setup_device(void) 
                return false;
        }
  
 -      memset(&ifr, 0, sizeof(ifr));
 +      memset(&ifr, 0, sizeof ifr);
++
+ #ifdef FD_CLOEXEC
+       fcntl(device_fd, F_SETFD, FD_CLOEXEC);
+ #endif
        strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ);
        if(ioctl(device_fd, SIOCGIFINDEX, &ifr)) {
                close(device_fd);
@@@ -88,10 -91,10 +92,10 @@@ static void close_device(void) 
        free(iface);
  }
  
- bool read_packet(vpn_packet_t *packet) {
static bool read_packet(vpn_packet_t *packet) {
 -      int lenin;
 +      int inlen;
  
 -      if((lenin = read(device_fd, packet->data, MTU)) <= 0) {
 +      if((inlen = read(device_fd, packet->data, MTU)) <= 0) {
                logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
                           device, strerror(errno));
                return false;
diff --cc src/route.c
@@@ -40,8 -39,8 +41,9 @@@ bool directonly = false
  bool priorityinheritance = false;
  int macexpire = 600;
  bool overwrite_mac = false;
+ bool broadcast = true;
  mac_t mymac = {{0xFE, 0xFD, 0, 0, 0, 0}};
 +bool pcap = false;
  
  /* Sizes of various headers */
  
@@@ -863,23 -849,51 +866,67 @@@ static void route_mac(node_t *source, v
        send_packet(subnet->owner, packet);
  }
  
 +static void send_pcap(vpn_packet_t *packet) {
 +      pcap = false;
 +      for(splay_node_t *node = connection_tree->head; node; node = node->next) {
 +              connection_t *c = node->data;
 +              if(!c->status.pcap)
 +                      continue;
 +              else
 +                      pcap = true;
 +              if(send_request(c, "%d %d %d", CONTROL, REQ_PCAP, packet->len))
 +                      send_meta(c, (char *)packet->data, packet->len);
 +      }
 +}
 +
+ static bool do_decrement_ttl(node_t *source, vpn_packet_t *packet) {
+       uint16_t type = packet->data[12] << 8 | packet->data[13];
+       switch (type) {
+               case ETH_P_IP:
+                       if(!checklength(source, packet, 14 + 32))
+                               return false;
+                       if(packet->data[22] < 1) {
+                               route_ipv4_unreachable(source, packet, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL);
+                               return false;
+                       }
+                       uint16_t old = packet->data[22] << 8 | packet->data[23];
+                       packet->data[22]--;
+                       uint16_t new = packet->data[22] << 8 | packet->data[23];
+                       uint32_t checksum = packet->data[24] << 8 | packet->data[25];
+                       checksum += old + (~new & 0xFFFF);
+                       while(checksum >> 16)
+                               checksum = (checksum & 0xFFFF) + (checksum >> 16);
+                       packet->data[24] = checksum >> 8;
+                       packet->data[25] = checksum & 0xff;
+                       return true;
+               case ETH_P_IPV6:
+                       if(!checklength(source, packet, 14 + 40))
+                               return false;
+                       if(packet->data[21] < 1) {
+                               route_ipv6_unreachable(source, packet, ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT);
+                               return false;
+                       }
+                       packet->data[21]--;
+                       return true;
+               default:
+                       return true;
+       }
+ }
  void route(node_t *source, vpn_packet_t *packet) {
 +      if(pcap)
 +              send_pcap(packet);
 +
        if(forwarding_mode == FMODE_KERNEL && source != myself) {
                send_packet(myself, packet);
                return;
diff --cc src/route.h
@@@ -38,11 -38,12 +38,13 @@@ typedef enum fmode_t 
  
  extern rmode_t routing_mode;
  extern fmode_t forwarding_mode;
+ extern bool decrement_ttl;
  extern bool directonly;
  extern bool overwrite_mac;
+ extern bool broadcast;
  extern bool priorityinheritance;
  extern int macexpire;
 +extern bool pcap;
  
  extern mac_t mymac;
  
@@@ -114,10 -126,10 +126,10 @@@ static void close_device(void) 
        free(iface);
  }
  
- bool read_packet(vpn_packet_t *packet) {
static bool read_packet(vpn_packet_t *packet) {
 -      int lenin;
 +      int inlen;
  
 -      if((lenin = read(device_fd, packet->data + 14, MTU - 14)) <= 0) {
 +      if((inlen = read(device_fd, packet->data + 14, MTU - 14)) <= 0) {
                logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
                           device, strerror(errno));
                return false;
diff --cc src/tincd.c
@@@ -122,18 -130,20 +123,18 @@@ static void usage(bool status) 
                                program_name);
        else {
                printf("Usage: %s [option]...\n\n", program_name);
-               printf( "  -c, --config=DIR          Read configuration options from DIR.\n"
-                               "  -D, --no-detach           Don't fork and detach.\n"
-                               "  -d, --debug[=LEVEL]       Increase debug level or set it to LEVEL.\n"
-                               "  -n, --net=NETNAME         Connect to net NETNAME.\n"
-                               "  -L, --mlock               Lock tinc into main memory.\n"
-                               "      --logfile[=FILENAME]  Write log entries to a logfile.\n"
-                               "      --pidfile=FILENAME    Write PID and control socket cookie to FILENAME.\n"
-                               "      --bypass-security     Disables meta protocol security, for debugging.\n"
-                               "  -o [HOST.]KEY=VALUE       Set global/host configuration value.\n"
-                               "  -R, --chroot              chroot to NET dir at startup.\n"
-                               "  -U, --user=USER           setuid to given USER at startup.\n"                                "      --help                    Display this help and exit.\n"
-                               "      --version             Output version information and exit.\n\n");
 -              printf("  -c, --config=DIR               Read configuration options from DIR.\n"
 -                              "  -D, --no-detach                Don't fork and detach.\n"
 -                              "  -d, --debug[=LEVEL]            Increase debug level or set it to LEVEL.\n"
 -                              "  -k, --kill[=SIGNAL]            Attempt to kill a running tincd and exit.\n"
 -                              "  -n, --net=NETNAME              Connect to net NETNAME.\n"
 -                              "  -K, --generate-keys[=BITS]     Generate public/private RSA keypair.\n"
 -                              "  -L, --mlock                    Lock tinc into main memory.\n"
 -                              "      --logfile[=FILENAME]       Write log entries to a logfile.\n"
 -                              "      --pidfile=FILENAME         Write PID to FILENAME.\n"
 -                              "  -o, --option=[HOST.]KEY=VALUE  Set global/host configuration value.\n"
 -                              "  -R, --chroot                   chroot to NET dir at startup.\n"
 -                              "  -U, --user=USER                setuid to given USER at startup.\n"
 -                              "      --help                     Display this help and exit.\n"
 -                              "      --version                  Output version information and exit.\n\n");
++              printf( "  -c, --config=DIR              Read configuration options from DIR.\n"
++                              "  -D, --no-detach               Don't fork and detach.\n"
++                              "  -d, --debug[=LEVEL]           Increase debug level or set it to LEVEL.\n"
++                              "  -n, --net=NETNAME             Connect to net NETNAME.\n"
++                              "  -L, --mlock                   Lock tinc into main memory.\n"
++                              "      --logfile[=FILENAME]      Write log entries to a logfile.\n"
++                              "      --pidfile=FILENAME        Write PID and control socket cookie to FILENAME.\n"
++                              "      --bypass-security         Disables meta protocol security, for debugging.\n"
++                              "  -o, --option[HOST.]KEY=VALUE  Set global/host configuration value.\n"
++                              "  -R, --chroot                  chroot to NET dir at startup.\n"
++                              "  -U, --user=USER               setuid to given USER at startup.\n"                            "      --help                    Display this help and exit.\n"
++                              "      --version                 Output version information and exit.\n\n");
                printf("Report bugs to tinc@tinc-vpn.org.\n");
        }
  }
@@@ -169,8 -179,8 +179,8 @@@ void close_device(void) 
        if(iface) free(iface);
  }
  
- bool read_packet(vpn_packet_t *packet) {
static bool read_packet(vpn_packet_t *packet) {
 -      int lenin;
 +      int inlen;
  
        switch(state) {
                case 0: {