Merge branch 'master' into 1.1
authorGuus Sliepen <guus@tinc-vpn.org>
Sat, 17 Apr 2010 10:21:53 +0000 (12:21 +0200)
committerGuus Sliepen <guus@tinc-vpn.org>
Sat, 17 Apr 2010 10:21:53 +0000 (12:21 +0200)
Conflicts:
NEWS
README
configure.in
src/net.c
src/net.h

12 files changed:
1  2 
NEWS
doc/tinc.texi
src/conf.c
src/ipv4.h
src/ipv6.h
src/net.c
src/net.h
src/net_setup.c
src/netutl.c
src/protocol_auth.c
src/route.c
src/subnet.h

diff --combined NEWS
--- 1/NEWS
--- 2/NEWS
+++ b/NEWS
@@@ -1,9 -1,19 +1,25 @@@
 +Version 1.1-cvs              Work in progress
 +
 + * Use libevent to handle I/O events and timeouts.
 +
 + * Use splay trees instead of AVL trees.
 +
+ Version 1.0.14               not released yet
+  * Fixed reading configuration files that do not end with a newline. Again.
+ Version 1.0.13               Apr 11 2010
+  * Allow building tinc without LZO and/or Zlib.
+  * Clamp MSS of TCP packets in both directions.
+  * Experimental StrictSubnets, Forwarding and DirectOnly options,
+    giving more control over information and packets received from/sent to other
+    nodes.
+  * Ensure tinc never sends symbolic names for ports over the wire.
  Version 1.0.12               Feb  3 2010
  
   * Really allow fast roaming of hosts to other nodes in a switched VPN.
diff --combined doc/tinc.texi
@@@ -37,7 -37,6 +37,7 @@@ permission notice identical to this one
  
  @page
  @vskip 0pt plus 1filll
 +@cindex copyright
  This is the info manual for @value{PACKAGE} version @value{VERSION}, a Virtual Private Network daemon.
  
  Copyright @copyright{} 1998-2010 Ivo Timmermans,
@@@ -55,7 -54,7 +55,7 @@@ permission notice identical to this one
  
  @end titlepage
  
 -@ifnottex
 +@ifinfo
  @c ==================================================================
  @node Top
  @top Top
  * Installation::
  * Configuration::
  * Running tinc::
 +* Controlling tinc::
  * Technical information::
  * Platform specific information::
  * About us::
  * Concept Index::               All used terms explained
  @end menu
 -@end ifnottex
 +@end ifinfo
  
  @c ==================================================================
  @node    Introduction
@@@ -339,7 -337,6 +339,7 @@@ having them installed, configure will g
  * OpenSSL::
  * zlib::
  * lzo::
 +* libevent::
  @end menu
  
  
@@@ -452,27 -449,6 +452,27 @@@ make sure you build development and run
  default).
  
  
 +@c ==================================================================
 +@node       libevent
 +@subsection libevent
 +
 +@cindex libevent
 +For the main event loop, tinc uses the libevent library.
 +
 +If this library is not installed, you wil get an error when configuring
 +tinc for build.
 +
 +You can use your operating system's package manager to install this if
 +available.  Make sure you install the development AND runtime versions
 +of this package.
 +
 +If you have to install libevent manually, you can get the source code
 +from @url{http://monkey.org/~provos/libevent/}.  Instructions on how to configure,
 +build and install this package are included within the package.  Please
 +make sure you build development and runtime libraries (which is the
 +default).
 +
 +
  @c
  @c
  @c
@@@ -843,14 -819,14 +843,14 @@@ to start with an Ethernet header
  @end table
  
  @cindex DirectOnly
- @item DirectOnly = <yes|no> (no)
+ @item DirectOnly = <yes|no> (no) [experimental]
  When this option is enabled, packets that cannot be sent directly to the destination node,
  but which would have to be forwarded by an intermediate node, are dropped instead.
  When combined with the IndirectData option,
  packets for nodes for which we do not have a meta connection with are also dropped.
  
  @cindex Forwarding
- @item Forwarding = <off|internal|kernel> (internal)
+ @item Forwarding = <off|internal|kernel> (internal) [experimental]
  This option selects the way indirect packets are forwarded.
  
  @table @asis
@@@ -967,7 -943,7 +967,7 @@@ accidental eavesdropping if you are edi
  @cindex PrivateKeyFile
  @item PrivateKeyFile = <@var{path}> (@file{@value{sysconfdir}/tinc/@var{netname}/rsa_key.priv})
  This is the full path name of the RSA private key file that was
 -generated by @samp{tincd --generate-keys}.  It must be a full path, not a
 +generated by @samp{tincctl generate-keys}.  It must be a full path, not a
  relative directory.
  
  Note that there must be exactly one of PrivateKey
@@@ -1067,7 -1043,7 +1067,7 @@@ This is the RSA public key for this hos
  @cindex PublicKeyFile
  @item PublicKeyFile = <@var{path}> [obsolete]
  This is the full path name of the RSA public key file that was generated
 -by @samp{tincd --generate-keys}.  It must be a full path, not a relative
 +by @samp{tincctl generate-keys}.  It must be a full path, not a relative
  directory.
  
  @cindex PEM format
@@@ -1103,6 -1079,7 +1103,6 @@@ example: netmask 255.255.255.0 would be
  /22. This conforms to standard CIDR notation as described in
  @uref{ftp://ftp.isi.edu/in-notes/rfc1519.txt, RFC1519}
  
 -@cindex Subnet weight
  A Subnet can be given a weight to indicate its priority over identical Subnets
  owned by different nodes. The default weight is 10. Lower values indicate
  higher priority. Packets will be sent to the node with the highest priority,
@@@ -1110,12 -1087,15 +1110,12 @@@ unless that node is not reachable, in w
  priority will be tried, and so on.
  
  @cindex TCPonly
 -@item TCPonly = <yes|no> (no) [deprecated]
 +@item TCPonly = <yes|no> (no)
  If this variable is set to yes, then the packets are tunnelled over a
  TCP connection instead of a UDP connection.  This is especially useful
  for those who want to run a tinc daemon from behind a masquerading
  firewall, or if UDP packet routing is disabled somehow.
  Setting this options also implicitly sets IndirectData.
 -
 -Since version 1.0.10, tinc will automatically detect whether communication via
 -UDP is possible or not.
  @end table
  
  
@@@ -1204,6 -1184,10 +1204,6 @@@ this is set to the port number it uses 
  @item SUBNET
  When a subnet becomes (un)reachable, this is set to the subnet.
  
 -@cindex WEIGHT
 -@item WEIGHT
 -When a subnet becomes (un)reachable, this is set to the subnet weight.
 -
  @end table
  
  
@@@ -1250,7 -1234,7 +1250,7 @@@ Now that you have already created the m
  you can easily create a public/private keypair by entering the following command:
  
  @example
 -tincd -n @var{netname} -K
 +tincctl -n @var{netname} generate-keys
  @end example
  
  Tinc will generate a public and a private key and ask you where to put them.
@@@ -1479,7 -1463,7 +1479,7 @@@ Address = 4.5.6.
  A, B, C and D all have generated a public/private keypair with the following command:
  
  @example
 -tincd -n company -K
 +tincctl -n company generate-keys
  @end example
  
  The private key is stored in @file{@value{sysconfdir}/tinc/company/rsa_key.priv},
@@@ -1545,12 -1529,20 +1545,12 @@@ This will also disable the automatic re
  Set debug level to @var{level}.  The higher the debug level, the more gets
  logged.  Everything goes via syslog.
  
 -@item -k, --kill[=@var{signal}]
 -Attempt to kill a running tincd (optionally with the specified @var{signal} instead of SIGTERM) and exit.
 -Use it in conjunction with the -n option to make sure you kill the right tinc daemon.
 -Under native Windows the optional argument is ignored,
 -the service will always be stopped and removed.
 -
  @item -n, --net=@var{netname}
  Use configuration for net @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 --controlsocket=@var{filename}
 +Open control socket at @var{filename}. If unspecified, the default is
 +@file{@value{localstatedir}/run/tinc.@var{netname}.control}.
  
  @item -L, --mlock
  Lock tinc into main memory.
@@@ -1560,6 -1552,9 +1560,6 @@@ This will prevent sensitive data like s
  Write log entries to a file instead of to the system logging facility.
  If @var{file} is omitted, the default is @file{@value{localstatedir}/log/tinc.@var{netname}.log}.
  
 -@item --pidfile=@var{file}
 -Write PID to @var{file} instead of @file{@value{localstatedir}/run/tinc.@var{netname}.pid}.
 -
  @item --bypass-security
  Disables encryption and authentication.
  Only useful for debugging.
@@@ -1599,11 -1594,31 +1599,11 @@@ You can also send the following signal
  @c from the manpage
  @table @samp
  
 -@item ALRM
 -Forces tinc to try to connect to all uplinks immediately.
 -Usually tinc attempts to do this itself,
 -but increases the time it waits between the attempts each time it failed,
 -and if tinc didn't succeed to connect to an uplink the first time after it started,
 -it defaults to the maximum time of 15 minutes.
 -
  @item HUP
  Partially rereads configuration files.
  Connections to hosts whose host config file are removed are closed.
  New outgoing connections specified in @file{tinc.conf} will be made.
  
 -@item INT
 -Temporarily increases debug level to 5.
 -Send this signal again to revert to the original level.
 -
 -@item USR1
 -Dumps the connection list to syslog.
 -
 -@item USR2
 -Dumps virtual network device statistics, all known nodes, edges and subnets to syslog.
 -
 -@item WINCH
 -Purges all information remembered about unreachable nodes.
 -
  @end table
  
  @c ==================================================================
@@@ -1667,7 -1682,7 +1667,7 @@@ Do you have a firewall or a NAT device 
  If so, check that it allows TCP and UDP traffic on port 655.
  If it masquerades and the host running tinc is behind it, make sure that it forwards TCP and UDP traffic to port 655 to the host running tinc.
  You can add @samp{TCPOnly = yes} to your host config file to force tinc to only use a single TCP connection,
 -this works through most firewalls and NATs. Since version 1.0.10, tinc will automatically fall back to TCP if direct communication via UDP is not possible.
 +this works through most firewalls and NATs.
  
  @end itemize
  
@@@ -1766,8 -1781,6 +1766,8 @@@ or if that is not the case, try changin
  
  @itemize
  @item If you see this only sporadically, it is harmless and caused by a node sending packets using an old key.
 +@item If you see this often and another node is not reachable anymore, then a NAT (masquerading firewall) is changing the source address of UDP packets.
 +You can add @samp{TCPOnly = yes} to host configuration files to force all VPN traffic to go over a TCP connection.
  @end itemize
  
  @item Got bad/bogus/unauthorized REQUEST from foo (1.2.3.4 port 12345)
@@@ -1798,110 -1811,6 +1798,110 @@@ Be sure to include the following inform
  @item The output of any command that fails to work as it should (like ping or traceroute).
  @end itemize
  
 +@c ==================================================================
 +@node    Controlling tinc
 +@chapter Controlling tinc
 +
 +You can control and inspect a running @samp{tincd} through the @samp{tincctl}
 +command. A quick example:
 +
 +@example
 +tincctl -n @var{netname} reload
 +@end example
 +
 +@menu
 +* tincctl runtime options::
 +* tincctl commands::
 +@end menu
 +
 +
 +@c ==================================================================
 +@node    tincctl runtime options
 +@section tincctl runtime options
 +
 +@c from the manpage
 +@table @option
 +@item -c, --config=@var{path}
 +Read configuration options from the directory @var{path}.  The default is
 +@file{@value{sysconfdir}/tinc/@var{netname}/}.
 +
 +@item -n, --net=@var{netname}
 +Use configuration for net @var{netname}. @xref{Multiple networks}.
 +
 +@item --controlsocket=@var{filename}
 +Open control socket at @var{filename}. If unspecified, the default is
 +@file{@value{localstatedir}/run/tinc.@var{netname}.control}.
 +
 +@item --help
 +Display a short reminder of runtime options and commands, then terminate.
 +
 +@item --version
 +Output version information and exit.
 +
 +@end table
 +
 +
 +@c ==================================================================
 +@node    tincctl commands
 +@section tincctl commands
 +
 +@c from the manpage
 +@table @code
 +
 +@item start
 +Start @samp{tincd}.
 +
 +@item stop
 +Stop @samp{tincd}.
 +
 +@item restart
 +Restart @samp{tincd}.
 +
 +@item reload
 +Partially rereads configuration files. Connections to hosts whose host
 +config files are removed are closed. New outgoing connections specified
 +in @file{tinc.conf} will be made.
 +
 +@item pid
 +Shows the PID of the currently running @samp{tincd}.
 +
 +@item generate-keys [@var{bits}]
 +Generate public/private keypair of @var{bits} length. If @var{bits} is not specified,
 +1024 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).
 +
 +@item dump nodes
 +Dump a list of all known nodes in the VPN.
 +
 +@item dump edges
 +Dump a list of all known connections in the VPN.
 +
 +@item dump subnets
 +Dump a list of all known subnets in the VPN.
 +
 +@item dump connections
 +Dump a list of all meta connections with ourself.
 +
 +@item dump graph
 +Dump a graph of the VPN in dotty format.
 +
 +@item purge
 +Purges all information remembered about unreachable nodes.
 +
 +@item debug @var{level}
 +Sets debug level to @var{level}.
 +
 +@item retry
 +Forces tinc to try to connect to all uplinks immediately.
 +Usually tinc attempts to do this itself,
 +but increases the time it waits between the attempts each time it failed,
 +and if tinc didn't succeed to connect to an uplink the first time after it started,
 +it defaults to the maximum time of 15 minutes.
 +
 +@end table
 +
 +
  @c ==================================================================
  @node    Technical information
  @chapter Technical information
diff --combined src/conf.c
@@@ -22,7 -22,7 +22,7 @@@
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "conf.h"
  #include "logger.h"
  #include "netutl.h"                           /* for str2address */
@@@ -30,7 -30,7 +30,7 @@@
  #include "utils.h"                            /* for cp */
  #include "xalloc.h"
  
 -avl_tree_t *config_tree;
 +splay_tree_t *config_tree;
  
  int pinginterval = 0;                 /* seconds between pings */
  int pingtimeout = 0;                  /* seconds to wait for response */
@@@ -53,12 -53,12 +53,12 @@@ static int config_compare(const config_
                return strcmp(a->file, b->file);
  }
  
 -void init_configuration(avl_tree_t ** config_tree) {
 -      *config_tree = avl_alloc_tree((avl_compare_t) config_compare, (avl_action_t) free_config);
 +void init_configuration(splay_tree_t ** config_tree) {
 +      *config_tree = splay_alloc_tree((splay_compare_t) config_compare, (splay_action_t) free_config);
  }
  
 -void exit_configuration(avl_tree_t ** config_tree) {
 -      avl_delete_tree(*config_tree);
 +void exit_configuration(splay_tree_t ** config_tree) {
 +      splay_delete_tree(*config_tree);
        *config_tree = NULL;
  }
  
@@@ -79,18 -79,18 +79,18 @@@ void free_config(config_t *cfg) 
        free(cfg);
  }
  
 -void config_add(avl_tree_t *config_tree, config_t *cfg) {
 -      avl_insert(config_tree, cfg);
 +void config_add(splay_tree_t *config_tree, config_t *cfg) {
 +      splay_insert(config_tree, cfg);
  }
  
 -config_t *lookup_config(avl_tree_t *config_tree, char *variable) {
 +config_t *lookup_config(splay_tree_t *config_tree, char *variable) {
        config_t cfg, *found;
  
        cfg.variable = variable;
        cfg.file = "";
        cfg.line = 0;
  
 -      found = avl_search_closest_greater(config_tree, &cfg);
 +      found = splay_search_closest_greater(config_tree, &cfg);
  
        if(!found)
                return NULL;
        return found;
  }
  
 -config_t *lookup_config_next(avl_tree_t *config_tree, const config_t *cfg) {
 -      avl_node_t *node;
 +config_t *lookup_config_next(splay_tree_t *config_tree, const config_t *cfg) {
 +      splay_node_t *node;
        config_t *found;
  
 -      node = avl_search_node(config_tree, cfg);
 +      node = splay_search_node(config_tree, cfg);
  
        if(node) {
                if(node->next) {
@@@ -193,9 -193,9 +193,9 @@@ bool get_config_subnet(const config_t *
        /* Teach newbies what subnets are... */
  
        if(((subnet.type == SUBNET_IPV4)
 -              && !maskcheck(&subnet.net.ipv4.address, subnet.net.ipv4.prefixlength, sizeof(ipv4_t)))
 +              && !maskcheck(&subnet.net.ipv4.address, subnet.net.ipv4.prefixlength, sizeof subnet.net.ipv4.address))
                || ((subnet.type == SUBNET_IPV6)
 -              && !maskcheck(&subnet.net.ipv6.address, subnet.net.ipv6.prefixlength, sizeof(ipv6_t)))) {
 +              && !maskcheck(&subnet.net.ipv6.address, subnet.net.ipv6.prefixlength, sizeof subnet.net.ipv6.address))) {
                logger(LOG_ERR, "Network address and prefix length do not match for configuration variable %s in %s line %d",
                           cfg->variable, cfg->file, cfg->line);
                return false;
@@@ -224,7 -224,7 +224,7 @@@ static char *readline(FILE * fp, char *
        newline = strchr(p, '\n');
  
        if(!newline)
-               return NULL;
+               return buf;
  
        *newline = '\0';        /* kill newline */
        if(newline > p && newline[-1] == '\r')  /* and carriage return if necessary */
    Parse a configuration file and put the results in the configuration tree
    starting at *base.
  */
 -bool read_config_file(avl_tree_t *config_tree, const char *fname) {
 +int read_config_file(splay_tree_t *config_tree, const char *fname) {
-       int err = -2;                           /* Parse error */
        FILE *fp;
        char buffer[MAX_STRING_SIZE];
        char *line;
@@@ -333,6 -332,66 +332,6 @@@ bool read_server_config() 
        return x;
  }
  
 -FILE *ask_and_open(const char *filename, const char *what) {
 -      FILE *r;
 -      char *directory;
 -      char line[PATH_MAX];
 -      const char *fn;
 -
 -      /* Check stdin and stdout */
 -      if(!isatty(0) || !isatty(1)) {
 -              /* Argh, they are running us from a script or something.  Write
 -                 the files to the current directory and let them burn in hell
 -                 for ever. */
 -              fn = filename;
 -      } else {
 -              /* Ask for a file and/or directory name. */
 -              fprintf(stdout, "Please enter a file to save %s to [%s]: ",
 -                              what, filename);
 -              fflush(stdout);
 -
 -              fn = readline(stdin, line, sizeof line);
 -
 -              if(!fn) {
 -                      fprintf(stderr, "Error while reading stdin: %s\n",
 -                                      strerror(errno));
 -                      return NULL;
 -              }
 -
 -              if(!strlen(fn))
 -                      /* User just pressed enter. */
 -                      fn = filename;
 -      }
 -
 -#ifdef HAVE_MINGW
 -      if(fn[0] != '\\' && fn[0] != '/' && !strchr(fn, ':')) {
 -#else
 -      if(fn[0] != '/') {
 -#endif
 -              /* The directory is a relative path or a filename. */
 -              char *p;
 -
 -              directory = get_current_dir_name();
 -              xasprintf(&p, "%s/%s", directory, fn);
 -              free(directory);
 -              fn = p;
 -      }
 -
 -      umask(0077);                            /* Disallow everything for group and other */
 -
 -      /* Open it first to keep the inode busy */
 -
 -      r = fopen(fn, "r+") ?: fopen(fn, "w+");
 -
 -      if(!r) {
 -              fprintf(stderr, "Error opening file `%s': %s\n",
 -                              fn, strerror(errno));
 -              return NULL;
 -      }
 -
 -      return r;
 -}
 -
  bool disable_old_keys(FILE *f) {
        char buf[100];
        long pos;
diff --combined src/ipv4.h
  #define ICMP_NET_UNREACH 0
  #endif
  
+ #ifndef ICMP_NET_ANO
+ #define ICMP_NET_ANO 9
+ #endif
  #ifndef IP_MSS
  #define       IP_MSS          576
  #endif
diff --combined src/ipv6.h
@@@ -95,6 -95,7 +95,7 @@@ struct icmp6_hdr 
  #define ICMP6_DST_UNREACH_NOROUTE 0
  #define ICMP6_DST_UNREACH 1
  #define ICMP6_PACKET_TOO_BIG 2
+ #define ICMP6_DST_UNREACH_ADMIN 1
  #define ICMP6_DST_UNREACH_ADDR 3
  #define ND_NEIGHBOR_SOLICIT 135
  #define ND_NEIGHBOR_ADVERT 136
diff --combined src/net.c
+++ b/src/net.c
  
  #include "system.h"
  
 -#include <openssl/rand.h>
 -
  #include "utils.h"
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "conf.h"
  #include "connection.h"
  #include "device.h"
 -#include "event.h"
  #include "graph.h"
  #include "logger.h"
  #include "meta.h"
  #include "netutl.h"
  #include "process.h"
  #include "protocol.h"
 -#include "route.h"
  #include "subnet.h"
  #include "xalloc.h"
  
 -bool do_purge = false;
 -volatile bool running = false;
 -
 -time_t now = 0;
 -
  /* Purge edges and subnets of unreachable nodes. Use carefully. */
  
 -static void purge(void) {
 -      avl_node_t *nnode, *nnext, *enode, *enext, *snode, *snext;
 +void purge(void) {
 +      splay_node_t *nnode, *nnext, *enode, *enext, *snode, *snext;
        node_t *n;
        edge_t *e;
        subnet_t *s;
        }
  }
  
 -/*
 -  put all file descriptors in an fd_set array
 -  While we're at it, purge stuff that needs to be removed.
 -*/
 -static int build_fdset(fd_set *readset, fd_set *writeset) {
 -      avl_node_t *node, *next;
 -      connection_t *c;
 -      int i, max = 0;
 -
 -      FD_ZERO(readset);
 -      FD_ZERO(writeset);
 -
 -      for(node = connection_tree->head; node; node = next) {
 -              next = node->next;
 -              c = node->data;
 -
 -              if(c->status.remove) {
 -                      connection_del(c);
 -                      if(!connection_tree->head)
 -                              purge();
 -              } else {
 -                      FD_SET(c->socket, readset);
 -                      if(c->outbuflen > 0)
 -                              FD_SET(c->socket, writeset);
 -                      if(c->socket > max)
 -                              max = c->socket;
 -              }
 -      }
 -
 -      for(i = 0; i < listen_sockets; i++) {
 -              FD_SET(listen_socket[i].tcp, readset);
 -              if(listen_socket[i].tcp > max)
 -                      max = listen_socket[i].tcp;
 -              FD_SET(listen_socket[i].udp, readset);
 -              if(listen_socket[i].udp > max)
 -                      max = listen_socket[i].udp;
 -      }
 -
 -      if(device_fd >= 0)
 -              FD_SET(device_fd, readset);
 -      if(device_fd > max)
 -              max = device_fd;
 -      
 -      return max;
 -}
 -
  /*
    Terminate a connection:
    - Close the socket
    - Deactivate the host
  */
  void terminate_connection(connection_t *c, bool report) {
 -      if(c->status.remove)
 -              return;
 -
        ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Closing connection with %s (%s)",
                           c->name, c->hostname);
  
 -      c->status.remove = true;
        c->status.active = false;
  
        if(c->node)
  
        /* Check if this was our outgoing connection */
  
 -      if(c->outgoing) {
 +      if(c->outgoing)
                retry_outgoing(c->outgoing);
 -              c->outgoing = NULL;
 -      }
  
 -      free(c->outbuf);
 -      c->outbuf = NULL;
 -      c->outbuflen = 0;
 -      c->outbufsize = 0;
 -      c->outbufstart = 0;
 +      connection_del(c);
  }
  
  /*
    end does not reply in time, we consider them dead
    and close the connection.
  */
 -static void check_dead_connections(void) {
 -      avl_node_t *node, *next;
 +static void timeout_handler(int fd, short events, void *event) {
 +      splay_node_t *node, *next;
        connection_t *c;
 +      time_t now = time(NULL);
  
        for(node = connection_tree->head; node; node = next) {
                next = node->next;
                                if(c->status.pinged) {
                                        ifdebug(CONNECTIONS) logger(LOG_INFO, "%s (%s) didn't respond to PING in %ld seconds",
                                                           c->name, c->hostname, now - c->last_ping_time);
 -                                      c->status.timeout = true;
                                        terminate_connection(c, true);
 +                                      continue;
                                } else if(c->last_ping_time + pinginterval < now) {
                                        send_ping(c);
                                }
                        } else {
 -                              if(c->status.remove) {
 -                                      logger(LOG_WARNING, "Old connection_t for %s (%s) status %04x still lingering, deleting...",
 -                                                 c->name, c->hostname, bitfield_to_int(&c->status, sizeof c->status));
 -                                      connection_del(c);
 -                                      continue;
 -                              }
 -                              ifdebug(CONNECTIONS) logger(LOG_WARNING, "Timeout from %s (%s) during authentication",
 -                                                 c->name, c->hostname);
                                if(c->status.connecting) {
 +                                      ifdebug(CONNECTIONS)
 +                                              logger(LOG_WARNING, "Timeout while connecting to %s (%s)", c->name, c->hostname);
                                        c->status.connecting = false;
                                        closesocket(c->socket);
                                        do_outgoing_connection(c);
                                } else {
 +                                      ifdebug(CONNECTIONS) logger(LOG_WARNING, "Timeout from %s (%s) during authentication", c->name, c->hostname);
                                        terminate_connection(c, false);
 +                                      continue;
                                }
                        }
                }
 -
 -              if(c->outbuflen > 0 && c->last_flushed_time + pingtimeout < now) {
 -                      if(c->status.active) {
 -                              ifdebug(CONNECTIONS) logger(LOG_INFO,
 -                                              "%s (%s) could not flush for %ld seconds (%d bytes remaining)",
 -                                              c->name, c->hostname, now - c->last_flushed_time, c->outbuflen);
 -                              c->status.timeout = true;
 -                              terminate_connection(c, true);
 -                      }
 -              }
 -      }
 -}
 -
 -/*
 -  check all connections to see if anything
 -  happened on their sockets
 -*/
 -static void check_network_activity(fd_set * readset, fd_set * writeset) {
 -      connection_t *c;
 -      avl_node_t *node;
 -      int result, i;
 -      socklen_t len = sizeof(result);
 -      vpn_packet_t packet;
 -
 -      /* check input from kernel */
 -      if(device_fd >= 0 && FD_ISSET(device_fd, readset)) {
 -              if(read_packet(&packet)) {
 -                      packet.priority = 0;
 -                      route(myself, &packet);
 -              }
        }
  
 -      /* check meta connections */
 -      for(node = connection_tree->head; node; node = node->next) {
 -              c = node->data;
 -
 -              if(c->status.remove)
 -                      continue;
 -
 -              if(FD_ISSET(c->socket, readset)) {
 -                      if(c->status.connecting) {
 -                              c->status.connecting = false;
 -                              getsockopt(c->socket, SOL_SOCKET, SO_ERROR, &result, &len);
 -
 -                              if(!result)
 -                                      finish_connecting(c);
 -                              else {
 -                                      ifdebug(CONNECTIONS) logger(LOG_DEBUG,
 -                                                         "Error while connecting to %s (%s): %s",
 -                                                         c->name, c->hostname, sockstrerror(result));
 -                                      closesocket(c->socket);
 -                                      do_outgoing_connection(c);
 -                                      continue;
 -                              }
 -                      }
 -
 -                      if(!receive_meta(c)) {
 -                              terminate_connection(c, c->status.active);
 -                              continue;
 -                      }
 -              }
 +      event_add(event, &(struct timeval){pingtimeout, 0});
 +}
  
 -              if(FD_ISSET(c->socket, writeset)) {
 -                      if(!flush_meta(c)) {
 -                              terminate_connection(c, c->status.active);
 -                              continue;
 -                      }
 +void handle_meta_connection_data(int fd, short events, void *data) {
 +      connection_t *c = data;
 +      int result;
 +      socklen_t len = sizeof result;
 +
 +      if(c->status.connecting) {
 +              c->status.connecting = false;
 +
 +              getsockopt(c->socket, SOL_SOCKET, SO_ERROR, &result, &len);
 +
 +              if(!result)
 +                      finish_connecting(c);
 +              else {
 +                      ifdebug(CONNECTIONS) logger(LOG_DEBUG,
 +                                         "Error while connecting to %s (%s): %s",
 +                                         c->name, c->hostname, sockstrerror(result));
 +                      closesocket(c->socket);
 +                      do_outgoing_connection(c);
 +                      return;
                }
        }
  
 -      for(i = 0; i < listen_sockets; i++) {
 -              if(FD_ISSET(listen_socket[i].udp, readset))
 -                      handle_incoming_vpn_data(listen_socket[i].udp);
 -
 -              if(FD_ISSET(listen_socket[i].tcp, readset))
 -                      handle_new_meta_connection(listen_socket[i].tcp);
 +      if (!receive_meta(c)) {
 +              terminate_connection(c, c->status.active);
 +              return;
        }
  }
  
 -/*
 -  this is where it all happens...
 -*/
 -int main_loop(void) {
 -      fd_set readset, writeset;
 -      struct timeval tv;
 -      int r, maxfd;
 -      time_t last_ping_check, last_config_check, last_graph_dump;
 -      event_t *event;
 -
 -      last_ping_check = now;
 -      last_config_check = now;
 -      last_graph_dump = now;
 -      
 -      srand(now);
 -
 -      running = true;
 -
 -      while(running) {
 -              now = time(NULL);
 +static void sigterm_handler(int signal, short events, void *data) {
 +      logger(LOG_NOTICE, "Got %s signal", strsignal(signal));
 +      event_loopexit(NULL);
 +}
  
 -      //      tv.tv_sec = 1 + (rand() & 7);   /* Approx. 5 seconds, randomized to prevent global synchronisation effects */
 -              tv.tv_sec = 1;
 -              tv.tv_usec = 0;
 +static void sighup_handler(int signal, short events, void *data) {
 +      logger(LOG_NOTICE, "Got %s signal", strsignal(signal));
 +      reload_configuration();
 +}
  
 -              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
 -              r = select(maxfd + 1, &readset, &writeset, NULL, &tv);
 -#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;
 +      last_config_check = time(NULL);
  
 -                              ifdebug(STATUS) logger(LOG_INFO, "Expiring symmetric keys");
++      /* If StrictSubnet is set, expire deleted Subnets and read new ones in */
 -                              for(node = node_tree->head; node; node = node->next) {
 -                                      n = node->data;
 -                                      if(n->inkey) {
 -                                              free(n->inkey);
 -                                              n->inkey = NULL;
 -                                      }
 -                              }
++      if(strictsubnets) {
++              subnet_t *subnet;
 -                              send_key_changed(broadcast, myself);
 -                              keyexpires = now + keylifetime;
 -                      }
++              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);
++                              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);
++                              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;
 -                      
 -                      /* 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);
 -                      }
 +      /* Try to make outgoing connections */
 +      
 +      try_outgoing_connections();
  
 -                      last_config_check = now;
 +      return 0;
 +}
  
 -                      /* If StrictSubnet is set, expire deleted Subnets and read new ones in */
 +void retry(void) {
 +      connection_t *c;
 +      splay_node_t *node;
  
 -                      if(strictsubnets) {
 -                              subnet_t *subnet;
 +      for(node = connection_tree->head; node; node = node->next) {
 +              c = node->data;
 +              
 +              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);
 +              }
 +      }
 +}
  
 -                              for(node = subnet_tree->head; node; node = node->next) {
 -                                      subnet = node->data;
 -                                      subnet->expires = 1;
 -                              }
 +/*
 +  this is where it all happens...
 +*/
 +int main_loop(void) {
 +      struct event timeout_event;
 +      struct event sighup_event;
 +      struct event sigterm_event;
 +      struct event sigquit_event;
  
 -                              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);
 -                                              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);
 -                                              if(subnet->owner->status.reachable)
 -                                                      subnet_update(subnet->owner, subnet, true);
 -                                      }
 -                              }
 -                      }
 +      timeout_set(&timeout_event, timeout_handler, &timeout_event);
 +      event_add(&timeout_event, &(struct timeval){pingtimeout, 0});
  
 -                      /* Try to make outgoing connections */
 -                      
 -                      try_outgoing_connections();
 -              }
 -              
 -              /* Dump graph if wanted every 60 seconds*/
 +#ifdef SIGHUP
 +      signal_set(&sighup_event, SIGHUP, sighup_handler, NULL);
 +      signal_add(&sighup_event, NULL);
 +#endif
 +#ifdef SIGTERM
 +      signal_set(&sigterm_event, SIGTERM, sigterm_handler, NULL);
 +      signal_add(&sigterm_event, NULL);
 +#endif
 +#ifdef SIGQUIT
 +      signal_set(&sigquit_event, SIGQUIT, sigterm_handler, NULL);
 +      signal_add(&sigquit_event, NULL);
 +#endif
  
 -              if(last_graph_dump + 60 < now) {
 -                      dump_graph();
 -                      last_graph_dump = now;
 -              }
 +      if(event_loop(0) < 0) {
 +              logger(LOG_ERR, "Error while waiting for input: %s", strerror(errno));
 +              return 1;
        }
  
 +      signal_del(&sighup_event);
 +      signal_del(&sigterm_event);
 +      signal_del(&sigquit_event);
 +      event_del(&timeout_event);
 +
        return 0;
  }
diff --combined src/net.h
+++ b/src/net.h
@@@ -21,9 -21,9 +21,9 @@@
  #ifndef __TINC_NET_H__
  #define __TINC_NET_H__
  
 -#include <openssl/evp.h>
 -
  #include "ipv6.h"
 +#include "cipher.h"
 +#include "digest.h"
  
  #ifdef ENABLE_JUMBOGRAMS
  #define MTU 9018                              /* 9000 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */
  #define MTU 1518                              /* 1500 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */
  #endif
  
 -#define MAXSIZE (MTU + 4 + EVP_MAX_BLOCK_LENGTH + EVP_MAX_MD_SIZE + MTU/64 + 20)      /* MTU + seqno + padding + HMAC + compressor overhead */
 +#define MAXSIZE (MTU + 4 + CIPHER_MAX_BLOCK_SIZE + DIGEST_MAX_SIZE + MTU/64 + 20)     /* MTU + seqno + padding + HMAC + compressor overhead */
  #define MAXBUFSIZE ((MAXSIZE > 2048 ? MAXSIZE : 2048) + 128)  /* Enough room for a request with a MAXSIZEd packet or a 8192 bits RSA key */
  
 -#define MAXSOCKETS 128                        /* Overkill... */
 +#define MAXSOCKETS 8                  /* Probably overkill... */
  
  typedef struct mac_t {
        uint8_t x[6];
@@@ -84,8 -84,6 +84,8 @@@ typedef struct vpn_packet_t 
  } vpn_packet_t;
  
  typedef struct listen_socket_t {
 +      struct event ev_tcp;
 +      struct event ev_udp;
        int tcp;
        int udp;
        sockaddr_t sa;
@@@ -100,7 -98,7 +100,7 @@@ typedef struct outgoing_t 
        struct config_t *cfg;
        struct addrinfo *ai;
        struct addrinfo *aip;
 -      struct event *event;
 +      struct event ev;
  } outgoing_t;
  
  extern list_t *outgoing_list;
@@@ -111,19 -109,22 +111,19 @@@ extern int addressfamily
  
  extern listen_socket_t listen_socket[MAXSOCKETS];
  extern int listen_sockets;
 -extern int keyexpires;
  extern int keylifetime;
  extern bool do_prune;
 -extern bool do_purge;
  extern char *myport;
 -extern time_t now;
  
  /* Yes, very strange placement indeed, but otherwise the typedefs get all tangled up */
  #include "connection.h"
  #include "node.h"
  
  extern void retry_outgoing(outgoing_t *);
 -extern void handle_incoming_vpn_data(int);
 +extern void handle_incoming_vpn_data(int, short, void *);
  extern void finish_connecting(struct connection_t *);
  extern void do_outgoing_connection(struct connection_t *);
 -extern bool handle_new_meta_connection(int);
 +extern void handle_new_meta_connection(int, short, void *);
  extern int setup_listen_socket(const sockaddr_t *);
  extern int setup_vpn_in_socket(const sockaddr_t *);
  extern void send_packet(const struct node_t *, vpn_packet_t *);
@@@ -138,12 -139,7 +138,13 @@@ extern void terminate_connection(struc
  extern void flush_queue(struct node_t *);
  extern bool read_rsa_public_key(struct connection_t *);
  extern void send_mtu_probe(struct node_t *);
 +extern void handle_device_data(int, short, void *);
 +extern void handle_meta_connection_data(int, short, void *);
 +extern void regenerate_key();
 +extern void purge(void);
 +extern void retry(void);
 +extern int reload_configuration(void);
+ extern void load_all_subnets();
  
  #ifndef HAVE_MINGW
  #define closesocket(s) close(s)
diff --combined src/net_setup.c
  
  #include "system.h"
  
 -#include <openssl/pem.h>
 -#include <openssl/rsa.h>
 -#include <openssl/rand.h>
 -#include <openssl/err.h>
 -#include <openssl/evp.h>
 -
 -#include "avl_tree.h"
 +#include "splay_tree.h"
 +#include "cipher.h"
  #include "conf.h"
  #include "connection.h"
 +#include "control.h"
  #include "device.h"
 -#include "event.h"
 +#include "digest.h"
  #include "graph.h"
  #include "logger.h"
  #include "net.h"
  #include "process.h"
  #include "protocol.h"
  #include "route.h"
 +#include "rsa.h"
  #include "subnet.h"
  #include "utils.h"
  #include "xalloc.h"
  
  char *myport;
 +static struct event device_ev;
  
  bool read_rsa_public_key(connection_t *c) {
        FILE *fp;
        char *fname;
 -      char *key;
 -
 -      if(!c->rsa_key) {
 -              c->rsa_key = RSA_new();
 -//            RSA_blinding_on(c->rsa_key, NULL);
 -      }
 +      char *n;
 +      bool result;
  
        /* First, check for simple PublicKey statement */
  
 -      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;
 +      if(get_config_string(lookup_config(c->config_tree, "PublicKey"), &n)) {
 +              result = rsa_set_hex_public_key(&c->rsa, n, "FFFF");
 +              free(n);
 +              return result;
        }
  
        /* Else, check for PublicKeyFile statement and read it */
  
 -      if(get_config_string(lookup_config(c->config_tree, "PublicKeyFile"), &fname)) {
 -              fp = fopen(fname, "r");
 -
 -              if(!fp) {
 -                      logger(LOG_ERR, "Error reading RSA public key file `%s': %s",
 -                                 fname, strerror(errno));
 -                      free(fname);
 -                      return false;
 -              }
 -
 -              free(fname);
 -              c->rsa_key = PEM_read_RSAPublicKey(fp, &c->rsa_key, NULL, NULL);
 -              fclose(fp);
 -
 -              if(c->rsa_key)
 -                      return true;            /* Woohoo. */
 -
 -              /* If it fails, try PEM_read_RSA_PUBKEY. */
 -              fp = fopen(fname, "r");
 +      if(!get_config_string(lookup_config(c->config_tree, "PublicKeyFile"), &fname))
 +              xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
  
 -              if(!fp) {
 -                      logger(LOG_ERR, "Error reading RSA public key file `%s': %s",
 -                                 fname, strerror(errno));
 -                      free(fname);
 -                      return false;
 -              }
 -
 -              free(fname);
 -              c->rsa_key = PEM_read_RSA_PUBKEY(fp, &c->rsa_key, NULL, NULL);
 -              fclose(fp);
 -
 -              if(c->rsa_key) {
 -//                            RSA_blinding_on(c->rsa_key, NULL);
 -                      return true;
 -              }
 +      fp = fopen(fname, "r");
  
 -              logger(LOG_ERR, "Reading RSA public key file `%s' failed: %s",
 +      if(!fp) {
 +              logger(LOG_ERR, "Error reading RSA public key file `%s': %s",
                           fname, strerror(errno));
 +              free(fname);
                return false;
        }
  
 -      /* Else, check if a harnessed public key is in the config file */
 -
 -      xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
 -      fp = fopen(fname, "r");
 -
 -      if(fp) {
 -              c->rsa_key = PEM_read_RSAPublicKey(fp, &c->rsa_key, NULL, NULL);
 -              fclose(fp);
 -      }
 -
 -      free(fname);
 -
 -      if(c->rsa_key)
 -              return true;
 -
 -      /* Try again with PEM_read_RSA_PUBKEY. */
 -
 -      xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
 -      fp = fopen(fname, "r");
 -
 -      if(fp) {
 -              c->rsa_key = PEM_read_RSA_PUBKEY(fp, &c->rsa_key, NULL, NULL);
 -//            RSA_blinding_on(c->rsa_key, NULL);
 -              fclose(fp);
 -      }
 +      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);
 -
 -      if(c->rsa_key)
 -              return true;
 -
 -      logger(LOG_ERR, "No public key for %s specified!", c->name);
 -
 -      return false;
 +      return result;
  }
  
 -bool read_rsa_private_key(void) {
 +bool read_rsa_private_key() {
        FILE *fp;
 -      char *fname, *key, *pubkey;
 -      struct stat s;
 +      char *fname;
 +      char *n, *d;
 +      bool result;
 +
 +      /* First, check for simple PrivateKey statement */
  
 -      if(get_config_string(lookup_config(config_tree, "PrivateKey"), &key)) {
 -              if(!get_config_string(lookup_config(myself->connection->config_tree, "PublicKey"), &pubkey)) {
 +      if(get_config_string(lookup_config(config_tree, "PrivateKey"), &d)) {
 +              if(!get_config_string(lookup_config(myself->connection->config_tree, "PublicKey"), &n)) {
                        logger(LOG_ERR, "PrivateKey used but no PublicKey found!");
 +                      free(d);
                        return false;
                }
 -              myself->connection->rsa_key = RSA_new();
 -//            RSA_blinding_on(myself->connection->rsa_key, NULL);
 -              BN_hex2bn(&myself->connection->rsa_key->d, key);
 -              BN_hex2bn(&myself->connection->rsa_key->n, pubkey);
 -              BN_hex2bn(&myself->connection->rsa_key->e, "FFFF");
 -              free(key);
 -              free(pubkey);
 +              result = rsa_set_hex_private_key(&myself->connection->rsa, n, "FFFF", d);
 +              free(n);
 +              free(d);
                return true;
        }
  
 +      /* Else, check for PrivateKeyFile statement and read it */
 +
        if(!get_config_string(lookup_config(config_tree, "PrivateKeyFile"), &fname))
                xasprintf(&fname, "%s/rsa_key.priv", confbase);
  
        }
  
  #if !defined(HAVE_MINGW) && !defined(HAVE_CYGWIN)
 +      struct stat s;
 +
        if(fstat(fileno(fp), &s)) {
 -              logger(LOG_ERR, "Could not stat RSA private key file `%s': %s'",
 -                              fname, strerror(errno));
 +              logger(LOG_ERR, "Could not stat RSA private key file `%s': %s'", fname, strerror(errno));
                free(fname);
                return false;
        }
                logger(LOG_WARNING, "Warning: insecure file permissions for RSA private key file `%s'!", fname);
  #endif
  
 -      myself->connection->rsa_key = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
 +      result = rsa_read_pem_private_key(&myself->connection->rsa, fp);
        fclose(fp);
  
 -      if(!myself->connection->rsa_key) {
 -              logger(LOG_ERR, "Reading RSA private key file `%s' failed: %s",
 -                         fname, strerror(errno));
 -              free(fname);
 -              return false;
 +      if(!result) 
 +              logger(LOG_ERR, "Reading RSA private key file `%s' failed: %s", fname, strerror(errno));
 +      free(fname);
 +      return result;
 +}
 +
 +static struct event keyexpire_event;
 +
 +static void keyexpire_handler(int fd, short events, void *data) {
 +      regenerate_key();
 +}
 +
 +void regenerate_key() {
 +      if(timeout_initialized(&keyexpire_event)) {
 +              ifdebug(STATUS) logger(LOG_INFO, "Expiring symmetric keys");
 +              event_del(&keyexpire_event);
 +              send_key_changed(broadcast, myself);
 +      } else {
 +              timeout_set(&keyexpire_event, keyexpire_handler, NULL);
        }
  
 -      free(fname);
 -      return true;
 +      event_add(&keyexpire_event, &(struct timeval){keylifetime, 0});
  }
  
  /*
    Read Subnets from all host config files
  */
static void load_all_subnets(void) {
+ void load_all_subnets(void) {
        DIR *dir;
        struct dirent *ent;
        char *dname;
        char *fname;
 -      avl_tree_t *config_tree;
 +      splay_tree_t *config_tree;
        config_t *cfg;
-       subnet_t *s;
+       subnet_t *s, *s2;
        node_t *n;
        bool result;
  
                        continue;
  
                n = lookup_node(ent->d_name);
-               if(n)
-                       continue;
                #ifdef _DIRENT_HAVE_D_TYPE
                //if(ent->d_type != DT_REG)
                //      continue;
                if(!result)
                        continue;
  
-               n = new_node();
-               n->name = xstrdup(ent->d_name);
-               node_add(n);
+               if(!n) {
+                       n = new_node();
+                       n->name = xstrdup(ent->d_name);
+                       node_add(n);
+               }
  
                for(cfg = lookup_config(config_tree, "Subnet"); cfg; cfg = lookup_config_next(config_tree, cfg)) {
                        if(!get_config_subnet(cfg, &s))
                                continue;
  
-                       subnet_add(n, s);
+                       if((s2 = lookup_subnet(n, s))) {
+                               s2->expires = -1;
+                       } else {
+                               subnet_add(n, s);
+                       }
                }
  
                exit_configuration(&config_tree);
@@@ -262,6 -312,16 +265,16 @@@ bool setup_myself(void) 
                        && !get_config_string(lookup_config(myself->connection->config_tree, "Port"), &myport))
                myport = xstrdup("655");
  
+       if(!atoi(myport)) {
+               struct addrinfo *ai = str2addrinfo("localhost", myport, SOCK_DGRAM);
+               sockaddr_t sa;
+               if(!ai || !ai->ai_addr)
+                       return false;
+               free(myport);
+               memcpy(&sa, ai->ai_addr, ai->ai_addrlen);
+               sockaddr2str(&sa, NULL, &myport);
+       }
        /* Read in all the subnets specified in the host configuration file */
  
        cfg = lookup_config(myself->connection->config_tree, "Subnet");
  
        /* Generate packet encryption key */
  
 -      if(get_config_string
 -         (lookup_config(myself->connection->config_tree, "Cipher"), &cipher)) {
 -              if(!strcasecmp(cipher, "none")) {
 -                      myself->incipher = NULL;
 -              } else {
 -                      myself->incipher = EVP_get_cipherbyname(cipher);
 +      if(!get_config_string(lookup_config(myself->connection->config_tree, "Cipher"), &cipher))
 +              cipher = xstrdup("blowfish");
  
 -                      if(!myself->incipher) {
 -                              logger(LOG_ERR, "Unrecognized cipher type!");
 -                              return false;
 -                      }
 -              }
 -      } else
 -              myself->incipher = EVP_bf_cbc();
 -
 -      if(myself->incipher)
 -              myself->inkeylength = myself->incipher->key_len + myself->incipher->iv_len;
 -      else
 -              myself->inkeylength = 1;
 -
 -      myself->connection->outcipher = EVP_bf_ofb();
 +      if(!cipher_open_by_name(&myself->incipher, cipher)) {
 +              logger(LOG_ERR, "Unrecognized cipher type!");
 +              return false;
 +      }
  
        if(!get_config_int(lookup_config(config_tree, "KeyExpire"), &keylifetime))
                keylifetime = 3600;
  
 -      keyexpires = now + keylifetime;
 -      
 +      regenerate_key();
 +
        /* Check if we want to use message authentication codes... */
  
 -      if(get_config_string(lookup_config(myself->connection->config_tree, "Digest"), &digest)) {
 -              if(!strcasecmp(digest, "none")) {
 -                      myself->indigest = NULL;
 -              } else {
 -                      myself->indigest = EVP_get_digestbyname(digest);
 +      if(!get_config_string(lookup_config(myself->connection->config_tree, "Digest"), &digest))
 +              digest = xstrdup("sha1");
  
 -                      if(!myself->indigest) {
 -                              logger(LOG_ERR, "Unrecognized digest type!");
 -                              return false;
 -                      }
 -              }
 -      } else
 -              myself->indigest = EVP_sha1();
 -
 -      myself->connection->outdigest = EVP_sha1();
 -
 -      if(get_config_int(lookup_config(myself->connection->config_tree, "MACLength"), &myself->inmaclength)) {
 -              if(myself->indigest) {
 -                      if(myself->inmaclength > myself->indigest->md_size) {
 -                              logger(LOG_ERR, "MAC length exceeds size of digest!");
 -                              return false;
 -                      } else if(myself->inmaclength < 0) {
 -                              logger(LOG_ERR, "Bogus MAC length!");
 -                              return false;
 -                      }
 -              }
 -      } else
 -              myself->inmaclength = 4;
 +      int maclength = 4;
 +      get_config_int(lookup_config(myself->connection->config_tree, "MACLength"), &maclength);
 +
 +      if(maclength < 0) {
 +              logger(LOG_ERR, "Bogus MAC length!");
 +              return false;
 +      }
  
 -      myself->connection->outmaclength = 0;
 +      if(!digest_open_by_name(&myself->indigest, digest, maclength)) {
 +              logger(LOG_ERR, "Unrecognized digest type!");
 +              return false;
 +      }
  
        /* Compression */
  
        if(!setup_device())
                return false;
  
 +      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));
 +                      close_device();
 +                      return false;
 +              }
 +      }
 +
        /* Run tinc-up script to further initialize the tap interface */
        xasprintf(&envp[0], "NETNAME=%s", netname ? : "");
        xasprintf(&envp[1], "DEVICE=%s", device ? : "");
                listen_socket[listen_sockets].udp =
                        setup_vpn_in_socket((sockaddr_t *) aip->ai_addr);
  
 -              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_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();
 +              }
 +
 +              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();
 +              }
  
                ifdebug(CONNECTIONS) {
                        hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr);
  
                memcpy(&listen_socket[listen_sockets].sa, aip->ai_addr, aip->ai_addrlen);
                listen_sockets++;
 +
 +              if(listen_sockets >= MAXSOCKETS) {
 +                      logger(LOG_WARNING, "Maximum of %d listening sockets reached", MAXSOCKETS);
 +                      break;
 +              }
        }
  
        freeaddrinfo(ai);
    initialize network
  */
  bool setup_network(void) {
 -      now = time(NULL);
 -
 -      init_events();
        init_connections();
        init_subnets();
        init_nodes();
    close all open network connections
  */
  void close_network_connections(void) {
 -      avl_node_t *node, *next;
 +      splay_node_t *node, *next;
        connection_t *c;
        char *envp[5];
        int i;
        for(node = connection_tree->head; node; node = next) {
                next = node->next;
                c = node->data;
 -              c->outgoing = NULL;
 +              c->outgoing = false;
                terminate_connection(c, false);
        }
  
 -      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);
  
        if(myself && myself->connection) {
        }
  
        for(i = 0; i < listen_sockets; i++) {
 +              event_del(&listen_socket[i].ev_tcp);
 +              event_del(&listen_socket[i].ev_udp);
                close(listen_socket[i].tcp);
                close(listen_socket[i].udp);
        }
        exit_subnets();
        exit_nodes();
        exit_connections();
 -      exit_events();
  
        execute_script("tinc-down", envp);
  
diff --combined src/netutl.c
@@@ -88,7 -88,7 +88,7 @@@ void sockaddr2str(const sockaddr_t *sa
                return;
        }
  
 -      err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof(address), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV);
 +      err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof address, port, sizeof port, NI_NUMERICHOST | NI_NUMERICSERV);
  
        if(err) {
                logger(LOG_ERR, "Error while translating addresses: %s",
        if(scopeid)
                *scopeid = '\0';                /* Descope. */
  
-       *addrstr = xstrdup(address);
-       *portstr = xstrdup(port);
+       if(addrstr)
+               *addrstr = xstrdup(address);
+       if(portstr)
+               *portstr = xstrdup(port);
  }
  
  char *sockaddr2hostname(const sockaddr_t *sa) {
                return str;
        }
  
 -      err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof(address), port, sizeof(port),
 +      err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof address, port, sizeof port,
                                        hostnames ? 0 : (NI_NUMERICHOST | NI_NUMERICSERV));
        if(err) {
                logger(LOG_ERR, "Error while looking up hostname: %s",
@@@ -179,20 -181,20 +181,20 @@@ int sockaddrcmp(const sockaddr_t *a, co
                        return strcmp(a->unknown.port, b->unknown.port);
  
                case AF_INET:
 -                      result = memcmp(&a->in.sin_addr, &b->in.sin_addr, sizeof(a->in.sin_addr));
 +                      result = memcmp(&a->in.sin_addr, &b->in.sin_addr, sizeof a->in.sin_addr);
  
                        if(result)
                                return result;
  
 -                      return memcmp(&a->in.sin_port, &b->in.sin_port, sizeof(a->in.sin_port));
 +                      return memcmp(&a->in.sin_port, &b->in.sin_port, sizeof a->in.sin_port);
  
                case AF_INET6:
 -                      result = memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr));
 +                      result = memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof a->in6.sin6_addr);
  
                        if(result)
                                return result;
  
 -                      return memcmp(&a->in6.sin6_port, &b->in6.sin6_port, sizeof(a->in6.sin6_port));
 +                      return memcmp(&a->in6.sin6_port, &b->in6.sin6_port, sizeof a->in6.sin6_port);
  
                default:
                        logger(LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!",
diff --combined src/protocol_auth.c
  
  #include "system.h"
  
 -#include <openssl/sha.h>
 -#include <openssl/rand.h>
 -#include <openssl/err.h>
 -#include <openssl/evp.h>
 -
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "conf.h"
  #include "connection.h"
 +#include "control.h"
 +#include "control_common.h"
 +#include "crypto.h"
  #include "edge.h"
  #include "graph.h"
  #include "logger.h"
  #include "netutl.h"
  #include "node.h"
  #include "protocol.h"
 +#include "rsa.h"
  #include "utils.h"
  #include "xalloc.h"
  
  bool send_id(connection_t *c) {
 +      gettimeofday(&c->start, NULL);
 +
        return send_request(c, "%d %s %d", ID, myself->connection->name,
                                                myself->connection->protocol_version);
  }
  
 -bool id_h(connection_t *c) {
 +bool id_h(connection_t *c, char *request) {
        char name[MAX_STRING_SIZE];
  
 -      if(sscanf(c->buffer, "%*d " MAX_STRING " %d", name, &c->protocol_version) != 2) {
 +      if(sscanf(request, "%*d " MAX_STRING " %d", name, &c->protocol_version) != 2) {
                logger(LOG_ERR, "Got bad %s from %s (%s)", "ID", c->name,
                           c->hostname);
                return false;
        }
  
 +      /* Check if this is a control connection */
 +
 +      if(name[0] == '^' && !strcmp(name + 1, controlcookie)) {
 +              c->status.control = true;
 +              c->allow_request = CONTROL;
 +              c->last_ping_time = time(NULL) + 3600;
 +              return send_request(c, "%d %d %d", ACK, TINC_CTL_VERSION_CURRENT, getpid());
 +      }
 +
        /* Check if identity is a valid name */
  
        if(!check_id(name)) {
  }
  
  bool send_metakey(connection_t *c) {
 -      char *buffer;
 -      int len;
 -      bool x;
 +      size_t len = rsa_size(&c->rsa);
 +      char key[len];
 +      char enckey[len];
 +      char hexkey[2 * len + 1];
  
 -      len = RSA_size(c->rsa_key);
 -
 -      /* Allocate buffers for the meta key */
 -
 -      buffer = alloca(2 * len + 1);
 +      if(!cipher_open_blowfish_ofb(&c->outcipher))
 +              return false;
        
 -      c->outkey = xrealloc(c->outkey, len);
 -
 -      if(!c->outctx)
 -              c->outctx = xmalloc_and_zero(sizeof(*c->outctx));
 +      if(!digest_open_sha1(&c->outdigest, -1))
 +              return false;
  
 -      /* Copy random data to the buffer */
 +      /* Create a random key */
  
 -      RAND_pseudo_bytes((unsigned char *)c->outkey, len);
 +      randomize(key, len);
  
        /* The message we send must be smaller than the modulus of the RSA key.
           By definition, for a key of k bits, the following formula holds:
           This can be done by setting the most significant bit to zero.
         */
  
 -      c->outkey[0] &= 0x7F;
 +      key[0] &= 0x7F;
 +
 +      cipher_set_key_from_rsa(&c->outcipher, key, len, true);
  
        ifdebug(SCARY_THINGS) {
 -              bin2hex(c->outkey, buffer, len);
 -              buffer[len * 2] = '\0';
 -              logger(LOG_DEBUG, "Generated random meta key (unencrypted): %s",
 -                         buffer);
 +              bin2hex(key, hexkey, len);
 +              hexkey[len * 2] = '\0';
 +              logger(LOG_DEBUG, "Generated random meta key (unencrypted): %s", hexkey);
        }
  
        /* Encrypt the random data
           with a length equal to that of the modulus of the RSA key.
         */
  
 -      if(RSA_public_encrypt(len, (unsigned char *)c->outkey, (unsigned char *)buffer, c->rsa_key, RSA_NO_PADDING) != len) {
 -              logger(LOG_ERR, "Error during encryption of meta key for %s (%s)",
 -                         c->name, c->hostname);
 +      if(!rsa_public_encrypt(&c->rsa, key, len, enckey)) {
 +              logger(LOG_ERR, "Error during encryption of meta key for %s (%s)", c->name, c->hostname);
                return false;
        }
  
        /* Convert the encrypted random data to a hexadecimal formatted string */
  
 -      bin2hex(buffer, buffer, len);
 -      buffer[len * 2] = '\0';
 +      bin2hex(enckey, hexkey, len);
 +      hexkey[len * 2] = '\0';
  
        /* Send the meta key */
  
 -      x = send_request(c, "%d %d %d %d %d %s", METAKEY,
 -                                       c->outcipher ? c->outcipher->nid : 0,
 -                                       c->outdigest ? c->outdigest->type : 0, c->outmaclength,
 -                                       c->outcompression, buffer);
 -
 -      /* Further outgoing requests are encrypted with the key we just generated */
 -
 -      if(c->outcipher) {
 -              if(!EVP_EncryptInit(c->outctx, c->outcipher,
 -                                      (unsigned char *)c->outkey + len - c->outcipher->key_len,
 -                                      (unsigned char *)c->outkey + len - c->outcipher->key_len -
 -                                      c->outcipher->iv_len)) {
 -                      logger(LOG_ERR, "Error during initialisation of cipher for %s (%s): %s",
 -                                      c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL));
 -                      return false;
 -              }
 -
 -              c->status.encryptout = true;
 -      }
 -
 -      return x;
 +      bool result = send_request(c, "%d %d %d %d %d %s", METAKEY,
 +                       cipher_get_nid(&c->outcipher),
 +                       digest_get_nid(&c->outdigest), c->outmaclength,
 +                       c->outcompression, hexkey);
 +      
 +      c->status.encryptout = true;
 +      return result;
  }
  
 -bool metakey_h(connection_t *c) {
 -      char buffer[MAX_STRING_SIZE];
 +bool metakey_h(connection_t *c, char *request) {
 +      char hexkey[MAX_STRING_SIZE];
        int cipher, digest, maclength, compression;
 -      int len;
 +      size_t len = rsa_size(&myself->connection->rsa);
 +      char enckey[len];
 +      char key[len];
  
 -      if(sscanf(c->buffer, "%*d %d %d %d %d " MAX_STRING, &cipher, &digest, &maclength, &compression, buffer) != 5) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s)", "METAKEY", c->name,
 -                         c->hostname);
 +      if(sscanf(request, "%*d %d %d %d %d " MAX_STRING, &cipher, &digest, &maclength, &compression, hexkey) != 5) {
 +              logger(LOG_ERR, "Got bad %s from %s (%s)", "METAKEY", c->name, c->hostname);
                return false;
        }
  
 -      len = RSA_size(myself->connection->rsa_key);
 -
        /* Check if the length of the meta key is all right */
  
 -      if(strlen(buffer) != len * 2) {
 +      if(strlen(hexkey) != len * 2) {
                logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong keylength");
                return false;
        }
  
 -      /* Allocate buffers for the meta key */
 -
 -      c->inkey = xrealloc(c->inkey, len);
 -
 -      if(!c->inctx)
 -              c->inctx = xmalloc_and_zero(sizeof(*c->inctx));
 -
        /* Convert the challenge from hexadecimal back to binary */
  
 -      hex2bin(buffer, buffer, len);
 +      hex2bin(hexkey, enckey, len);
  
        /* Decrypt the meta key */
  
 -      if(RSA_private_decrypt(len, (unsigned char *)buffer, (unsigned char *)c->inkey, myself->connection->rsa_key, RSA_NO_PADDING) != len) {  /* See challenge() */
 -              logger(LOG_ERR, "Error during decryption of meta key for %s (%s)",
 -                         c->name, c->hostname);
 +      if(!rsa_private_decrypt(&myself->connection->rsa, enckey, len, key)) {
 +              logger(LOG_ERR, "Error during decryption of meta key for %s (%s)", c->name, c->hostname);
                return false;
        }
  
        ifdebug(SCARY_THINGS) {
 -              bin2hex(c->inkey, buffer, len);
 -              buffer[len * 2] = '\0';
 -              logger(LOG_DEBUG, "Received random meta key (unencrypted): %s", buffer);
 +              bin2hex(key, hexkey, len);
 +              hexkey[len * 2] = '\0';
 +              logger(LOG_DEBUG, "Received random meta key (unencrypted): %s", hexkey);
        }
  
 -      /* All incoming requests will now be encrypted. */
 -
        /* Check and lookup cipher and digest algorithms */
  
 -      if(cipher) {
 -              c->incipher = EVP_get_cipherbynid(cipher);
 -              
 -              if(!c->incipher) {
 -                      logger(LOG_ERR, "%s (%s) uses unknown cipher!", c->name, c->hostname);
 -                      return false;
 -              }
 -
 -              if(!EVP_DecryptInit(c->inctx, c->incipher,
 -                                      (unsigned char *)c->inkey + len - c->incipher->key_len,
 -                                      (unsigned char *)c->inkey + len - c->incipher->key_len -
 -                                      c->incipher->iv_len)) {
 -                      logger(LOG_ERR, "Error during initialisation of cipher from %s (%s): %s",
 -                                      c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL));
 -                      return false;
 -              }
 -
 -              c->status.decryptin = true;
 -      } else {
 -              c->incipher = NULL;
 +      if(!cipher_open_by_nid(&c->incipher, cipher) || !cipher_set_key_from_rsa(&c->incipher, key, len, false)) {
 +              logger(LOG_ERR, "Error during initialisation of cipher from %s (%s)", c->name, c->hostname);
 +              return false;
        }
  
 -      c->inmaclength = maclength;
 -
 -      if(digest) {
 -              c->indigest = EVP_get_digestbynid(digest);
 -
 -              if(!c->indigest) {
 -                      logger(LOG_ERR, "Node %s (%s) uses unknown digest!", c->name, c->hostname);
 -                      return false;
 -              }
 -
 -              if(c->inmaclength > c->indigest->md_size || c->inmaclength < 0) {
 -                      logger(LOG_ERR, "%s (%s) uses bogus MAC length!", c->name, c->hostname);
 -                      return false;
 -              }
 -      } else {
 -              c->indigest = NULL;
 +      if(!digest_open_by_nid(&c->indigest, digest, -1)) {
 +              logger(LOG_ERR, "Error during initialisation of digest from %s (%s)", c->name, c->hostname);
 +              return false;
        }
  
 -      c->incompression = compression;
 +      c->status.decryptin = true;
  
        c->allow_request = CHALLENGE;
  
  }
  
  bool send_challenge(connection_t *c) {
 -      char *buffer;
 -      int len;
 -
 -      /* CHECKME: what is most reasonable value for len? */
 -
 -      len = RSA_size(c->rsa_key);
 -
 -      /* Allocate buffers for the challenge */
 +      size_t len = rsa_size(&c->rsa);
 +      char buffer[len * 2 + 1];
  
 -      buffer = alloca(2 * len + 1);
 -
 -      c->hischallenge = xrealloc(c->hischallenge, len);
 +      if(!c->hischallenge)
 +              c->hischallenge = xrealloc(c->hischallenge, len);
  
        /* Copy random data to the buffer */
  
 -      RAND_pseudo_bytes((unsigned char *)c->hischallenge, len);
 +      randomize(c->hischallenge, len);
  
        /* Convert to hex */
  
        return send_request(c, "%d %s", CHALLENGE, buffer);
  }
  
 -bool challenge_h(connection_t *c) {
 +bool challenge_h(connection_t *c, char *request) {
        char buffer[MAX_STRING_SIZE];
 -      int len;
 +      size_t len = rsa_size(&myself->connection->rsa);
 +      size_t digestlen = digest_length(&c->outdigest);
 +      char digest[digestlen];
  
 -      if(sscanf(c->buffer, "%*d " MAX_STRING, buffer) != 1) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s)", "CHALLENGE", c->name,
 -                         c->hostname);
 +      if(sscanf(request, "%*d " MAX_STRING, buffer) != 1) {
 +              logger(LOG_ERR, "Got bad %s from %s (%s)", "CHALLENGE", c->name, c->hostname);
                return false;
        }
  
 -      len = RSA_size(myself->connection->rsa_key);
 -
        /* Check if the length of the challenge is all right */
  
        if(strlen(buffer) != len * 2) {
 -              logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name,
 -                         c->hostname, "wrong challenge length");
 +              logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge length");
                return false;
        }
  
 -      /* Allocate buffers for the challenge */
 -
 -      c->mychallenge = xrealloc(c->mychallenge, len);
 -
        /* Convert the challenge from hexadecimal back to binary */
  
 -      hex2bin(buffer, c->mychallenge, len);
 +      hex2bin(buffer, buffer, len);
  
        c->allow_request = CHAL_REPLY;
  
 -      /* Rest is done by send_chal_reply() */
 -
 -      return send_chal_reply(c);
 -}
 -
 -bool send_chal_reply(connection_t *c) {
 -      char hash[EVP_MAX_MD_SIZE * 2 + 1];
 -      EVP_MD_CTX ctx;
 -
        /* Calculate the hash from the challenge we received */
  
 -      if(!EVP_DigestInit(&ctx, c->indigest)
 -                      || !EVP_DigestUpdate(&ctx, c->mychallenge, RSA_size(myself->connection->rsa_key))
 -                      || !EVP_DigestFinal(&ctx, (unsigned char *)hash, NULL)) {
 -              logger(LOG_ERR, "Error during calculation of response for %s (%s): %s",
 -                      c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL));
 -              return false;
 -      }
 +      digest_create(&c->indigest, buffer, len, digest);
  
        /* Convert the hash to a hexadecimal formatted string */
  
 -      bin2hex(hash, hash, c->indigest->md_size);
 -      hash[c->indigest->md_size * 2] = '\0';
 +      bin2hex(digest, buffer, digestlen);
 +      buffer[digestlen * 2] = '\0';
  
        /* Send the reply */
  
 -      return send_request(c, "%d %s", CHAL_REPLY, hash);
 +      return send_request(c, "%d %s", CHAL_REPLY, buffer);
  }
  
 -bool chal_reply_h(connection_t *c) {
 +bool chal_reply_h(connection_t *c, char *request) {
        char hishash[MAX_STRING_SIZE];
 -      char myhash[EVP_MAX_MD_SIZE];
 -      EVP_MD_CTX ctx;
  
 -      if(sscanf(c->buffer, "%*d " MAX_STRING, hishash) != 1) {
 +      if(sscanf(request, "%*d " MAX_STRING, hishash) != 1) {
                logger(LOG_ERR, "Got bad %s from %s (%s)", "CHAL_REPLY", c->name,
                           c->hostname);
                return false;
  
        /* Check if the length of the hash is all right */
  
 -      if(strlen(hishash) != c->outdigest->md_size * 2) {
 -              logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name,
 -                         c->hostname, "wrong challenge reply length");
 +      if(strlen(hishash) != digest_length(&c->outdigest) * 2) {
 +              logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge reply length");
                return false;
        }
  
        /* Convert the hash to binary format */
  
 -      hex2bin(hishash, hishash, c->outdigest->md_size);
 -
 -      /* Calculate the hash from the challenge we sent */
 +      hex2bin(hishash, hishash, digest_length(&c->outdigest));
  
 -      if(!EVP_DigestInit(&ctx, c->outdigest)
 -                      || !EVP_DigestUpdate(&ctx, c->hischallenge, RSA_size(c->rsa_key))
 -                      || !EVP_DigestFinal(&ctx, (unsigned char *)myhash, NULL)) {
 -              logger(LOG_ERR, "Error during calculation of response from %s (%s): %s",
 -                      c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL));
 -              return false;
 -      }
 -
 -      /* Verify the incoming hash with the calculated hash */
 -
 -      if(memcmp(hishash, myhash, c->outdigest->md_size)) {
 -              logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name,
 -                         c->hostname, "wrong challenge reply");
 -
 -              ifdebug(SCARY_THINGS) {
 -                      bin2hex(myhash, hishash, SHA_DIGEST_LENGTH);
 -                      hishash[SHA_DIGEST_LENGTH * 2] = '\0';
 -                      logger(LOG_DEBUG, "Expected challenge reply: %s", hishash);
 -              }
 +      /* Verify the hash */
  
 +      if(!digest_verify(&c->outdigest, c->hischallenge, rsa_size(&c->rsa), hishash)) {
 +              logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge reply");
                return false;
        }
  
           Send an acknowledgement with the rest of the information needed.
         */
  
 +      free(c->hischallenge);
 +      c->hischallenge = NULL;
        c->allow_request = ACK;
  
        return send_ack(c);
@@@ -368,7 -464,7 +368,7 @@@ bool send_ack(connection_t *c) 
  }
  
  static void send_everything(connection_t *c) {
 -      avl_node_t *node, *node2;
 +      splay_node_t *node, *node2;
        node_t *n;
        subnet_t *s;
        edge_t *e;
        }
  }
  
 -bool ack_h(connection_t *c) {
 +bool ack_h(connection_t *c, char *request) {
        char hisport[MAX_STRING_SIZE];
-       char *hisaddress, *dummy;
+       char *hisaddress;
        int weight, mtu;
        uint32_t options;
        node_t *n;
        bool choice;
  
 -      if(sscanf(c->buffer, "%*d " MAX_STRING " %d %x", hisport, &weight, &options) != 3) {
 +      if(sscanf(request, "%*d " MAX_STRING " %d %x", hisport, &weight, &options) != 3) {
                logger(LOG_ERR, "Got bad %s from %s (%s)", "ACK", c->name,
                           c->hostname);
                return false;
        } else {
                if(n->connection) {
                        /* Oh dear, we already have a connection to this node. */
 -                      ifdebug(CONNECTIONS) logger(LOG_DEBUG, "Established a second connection with %s (%s), closing old connection",
 -                                         n->name, n->hostname);
 +                      ifdebug(CONNECTIONS) logger(LOG_DEBUG, "Established a second connection with %s (%s), closing old connection", n->connection->name, n->connection->hostname);
 +
 +                      if(n->connection->outgoing) {
 +                              if(c->outgoing)
 +                                      logger(LOG_WARNING, "Two outgoing connections to the same node!");
 +                              else
 +                                      c->outgoing = n->connection->outgoing;
 +
 +                              n->connection->outgoing = NULL;
 +                      }
 +
                        terminate_connection(n->connection, false);
                        /* Run graph algorithm to purge key and make sure up/down scripts are rerun with new IP addresses and stuff */
                        graph();
        c->edge = new_edge();
        c->edge->from = myself;
        c->edge->to = n;
-       sockaddr2str(&c->address, &hisaddress, &dummy);
+       sockaddr2str(&c->address, &hisaddress, NULL);
        c->edge->address = str2sockaddr(hisaddress, hisport);
        free(hisaddress);
-       free(dummy);
        c->edge->weight = (weight + c->estimated_weight) / 2;
        c->edge->connection = c;
        c->edge->options = c->options;
diff --combined src/route.c
@@@ -20,7 -20,7 +20,7 @@@
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "connection.h"
  #include "ethernet.h"
  #include "ipv4.h"
@@@ -50,10 -50,11 +50,13 @@@ static const size_t ip6_size = sizeof(s
  static const size_t icmp6_size = sizeof(struct icmp6_hdr);
  static const size_t ns_size = sizeof(struct nd_neighbor_solicit);
  static const size_t opt_size = sizeof(struct nd_opt_hdr);
- #define max(a, b) ((a) > (b) ? (a) : (b))
+ #ifndef MAX
+ #define MAX(a, b) ((a) > (b) ? (a) : (b))
+ #endif
  
 +static struct event age_subnets_event;
 +
  /* RFC 1071 */
  
  static uint16_t inet_checksum(void *data, int len, uint16_t prevsum) {
@@@ -77,7 -78,6 +80,7 @@@
  static bool ratelimit(int frequency) {
        static time_t lasttime = 0;
        static int count = 0;
 +      time_t now = time(NULL);
        
        if(lasttime == now) {
                if(++count > frequency)
@@@ -177,43 -177,9 +180,43 @@@ static void swap_mac_addresses(vpn_pack
        memcpy(&packet->data[6], &tmp, sizeof tmp);
  }
        
 +static void age_subnets(int fd, short events, void *data) {
 +      subnet_t *s;
 +      connection_t *c;
 +      splay_node_t *node, *next, *node2;
 +      bool left = false;
 +      time_t now = time(NULL);
 +
 +      for(node = myself->subnet_tree->head; node; node = next) {
 +              next = node->next;
 +              s = node->data;
 +              if(s->expires && s->expires < now) {
 +                      ifdebug(TRAFFIC) {
 +                              char netstr[MAXNETSTR];
 +                              if(net2str(netstr, sizeof netstr, s))
 +                                      logger(LOG_INFO, "Subnet %s expired", netstr);
 +                      }
 +
 +                      for(node2 = connection_tree->head; node2; node2 = node2->next) {
 +                              c = node2->data;
 +                              if(c->status.active)
 +                                      send_del_subnet(c, s);
 +                      }
 +
 +                      subnet_del(myself, s);
 +              } else {
 +                      if(s->expires)
 +                              left = true;
 +              }
 +      }
 +
 +      if(left)
 +              event_add(&age_subnets_event, &(struct timeval){10, 0});
 +}
 +
  static void learn_mac(mac_t *address) {
        subnet_t *subnet;
 -      avl_node_t *node;
 +      splay_node_t *node;
        connection_t *c;
  
        subnet = lookup_subnet_mac(myself, address);
  
                subnet = new_subnet();
                subnet->type = SUBNET_MAC;
 -              subnet->expires = now + macexpire;
 +              subnet->expires = time(NULL) + macexpire;
                subnet->net.mac.address = *address;
                subnet->weight = 10;
                subnet_add(myself, subnet);
                        if(c->status.active)
                                send_add_subnet(c, subnet);
                }
 -      }
 -
 -      if(subnet->expires)
 -              subnet->expires = now + macexpire;
 -}
 -
 -void age_subnets(void) {
 -      subnet_t *s;
 -      connection_t *c;
 -      avl_node_t *node, *next, *node2;
 -
 -      for(node = myself->subnet_tree->head; node; node = next) {
 -              next = node->next;
 -              s = node->data;
 -              if(s->expires && s->expires < now) {
 -                      ifdebug(TRAFFIC) {
 -                              char netstr[MAXNETSTR];
 -                              if(net2str(netstr, sizeof netstr, s))
 -                                      logger(LOG_INFO, "Subnet %s expired", netstr);
 -                      }
  
 -                      for(node2 = connection_tree->head; node2; node2 = node2->next) {
 -                              c = node2->data;
 -                              if(c->status.active)
 -                                      send_del_subnet(c, s);
 -                      }
 -
 -                      subnet_update(myself, s, false);
 -                      subnet_del(myself, s);
 -              }
 +              if(!timeout_initialized(&age_subnets_event))
 +                      timeout_set(&age_subnets_event, age_subnets, NULL);
 +              event_add(&age_subnets_event, &(struct timeval){10, 0});
 +      } else {
 +              if(subnet->expires)
 +                      subnet->expires = time(NULL) + macexpire;
        }
  }
  
@@@ -412,10 -401,10 +415,10 @@@ static void route_ipv4_unicast(node_t *
        if(directonly && subnet->owner != via)
                return route_ipv4_unreachable(source, packet, ICMP_DEST_UNREACH, ICMP_NET_ANO);
  
-       if(via && packet->len > max(via->mtu, 590) && via != myself) {
+       if(via && packet->len > MAX(via->mtu, 590) && via != myself) {
                ifdebug(TRAFFIC) logger(LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu);
                if(packet->data[20] & 0x40) {
-                       packet->len = max(via->mtu, 590);
+                       packet->len = MAX(via->mtu, 590);
                        route_ipv4_unreachable(source, packet, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED);
                } else {
                        fragment_ipv4_packet(via, packet);
@@@ -507,7 -496,7 +510,7 @@@ static void route_ipv6_unreachable(node
  
        /* Generate checksum */
        
 -      checksum = inet_checksum(&pseudo, sizeof(pseudo), ~0);
 +      checksum = inet_checksum(&pseudo, sizeof pseudo, ~0);
        checksum = inet_checksum(&icmp6, icmp6_size, checksum);
        checksum = inet_checksum(packet->data + ether_size + ip6_size + icmp6_size, ntohl(pseudo.length) - icmp6_size, checksum);
  
@@@ -563,9 -552,9 +566,9 @@@ static void route_ipv6_unicast(node_t *
        if(directonly && subnet->owner != via)
                return route_ipv6_unreachable(source, packet, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN);
  
-       if(via && packet->len > max(via->mtu, 1294) && via != myself) {
+       if(via && packet->len > MAX(via->mtu, 1294) && via != myself) {
                ifdebug(TRAFFIC) logger(LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu);
-               packet->len = max(via->mtu, 1294);
+               packet->len = MAX(via->mtu, 1294);
                route_ipv6_unreachable(source, packet, ICMP6_PACKET_TOO_BIG, 0);
                return;
        }
@@@ -634,7 -623,7 +637,7 @@@ static void route_neighborsol(node_t *s
  
        /* Generate checksum */
  
 -      checksum = inet_checksum(&pseudo, sizeof(pseudo), ~0);
 +      checksum = inet_checksum(&pseudo, sizeof pseudo, ~0);
        checksum = inet_checksum(&ns, ns_size, checksum);
        if(has_opt) {
                checksum = inet_checksum(&opt, opt_size, checksum);
  
        /* Generate checksum */
  
 -      checksum = inet_checksum(&pseudo, sizeof(pseudo), ~0);
 +      checksum = inet_checksum(&pseudo, sizeof pseudo, ~0);
        checksum = inet_checksum(&ns, ns_size, checksum);
        if(has_opt) {
                checksum = inet_checksum(&opt, opt_size, checksum);
@@@ -758,7 -747,7 +761,7 @@@ static void route_arp(node_t *source, v
        /* Check if this is a valid ARP request */
  
        if(ntohs(arp.arp_hrd) != ARPHRD_ETHER || ntohs(arp.arp_pro) != ETH_P_IP ||
 -         arp.arp_hln != ETH_ALEN || arp.arp_pln != sizeof(addr) || ntohs(arp.arp_op) != ARPOP_REQUEST) {
 +         arp.arp_hln != ETH_ALEN || arp.arp_pln != sizeof addr || ntohs(arp.arp_op) != ARPOP_REQUEST) {
                ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: received unknown type ARP request");
                return;
        }
        memcpy(packet->data, packet->data + ETH_ALEN, ETH_ALEN);        /* copy destination address */
        packet->data[ETH_ALEN * 2 - 1] ^= 0xFF; /* mangle source address so it looks like it's not from us */
  
 -      memcpy(&addr, arp.arp_tpa, sizeof(addr));       /* save protocol addr */
 -      memcpy(arp.arp_tpa, arp.arp_spa, sizeof(addr)); /* swap destination and source protocol address */
 -      memcpy(arp.arp_spa, &addr, sizeof(addr));       /* ... */
 +      memcpy(&addr, arp.arp_tpa, sizeof addr);        /* save protocol addr */
 +      memcpy(arp.arp_tpa, arp.arp_spa, sizeof addr);  /* swap destination and source protocol address */
 +      memcpy(arp.arp_spa, &addr, sizeof addr);        /* ... */
  
        memcpy(arp.arp_tha, arp.arp_sha, ETH_ALEN);     /* set target hard/proto addr */
        memcpy(arp.arp_sha, packet->data + ETH_ALEN, ETH_ALEN); /* add fake source hard addr */
diff --combined src/subnet.h
@@@ -64,13 -64,15 +64,15 @@@ typedef struct subnet_t 
  
  #define MAXNETSTR 64
  
 -extern avl_tree_t *subnet_tree;
++extern splay_tree_t *subnet_tree;
  extern int subnet_compare(const struct subnet_t *, const struct subnet_t *);
  extern subnet_t *new_subnet(void) __attribute__ ((__malloc__));
  extern void free_subnet(subnet_t *);
  extern void init_subnets(void);
  extern void exit_subnets(void);
 -extern avl_tree_t *new_subnet_tree(void) __attribute__ ((__malloc__));
 -extern void free_subnet_tree(avl_tree_t *);
 +extern splay_tree_t *new_subnet_tree(void) __attribute__ ((__malloc__));
 +extern void free_subnet_tree(splay_tree_t *);
  extern void subnet_add(struct node_t *, subnet_t *);
  extern void subnet_del(struct node_t *, subnet_t *);
  extern void subnet_update(struct node_t *, subnet_t *, bool);
@@@ -80,7 -82,7 +82,7 @@@ extern subnet_t *lookup_subnet(const st
  extern subnet_t *lookup_subnet_mac(const struct node_t *, const mac_t *);
  extern subnet_t *lookup_subnet_ipv4(const ipv4_t *);
  extern subnet_t *lookup_subnet_ipv6(const ipv6_t *);
 -extern void dump_subnets(void);
 +extern bool dump_subnets(struct connection_t *);
  extern void subnet_cache_flush(void);
  
  #endif                                                        /* __TINC_SUBNET_H__ */