Merge branch 'master' into 1.1
authorSven-Haegar Koch <haegar@sdinet.de>
Fri, 26 Mar 2010 15:51:03 +0000 (16:51 +0100)
committerSven-Haegar Koch <haegar@sdinet.de>
Fri, 26 Mar 2010 15:51:03 +0000 (16:51 +0100)
Conflicts:
NEWS
README
configure.in
have.h
src/conf.c
src/conf.h
src/net.c
src/net_packet.c
src/protocol_key.c
src/protocol_subnet.c
src/route.c
src/tincd.c

22 files changed:
1  2 
NEWS
README
doc/tinc.texi
have.h
src/conf.c
src/connection.c
src/connection.h
src/graph.c
src/net.c
src/net_packet.c
src/net_setup.c
src/net_socket.c
src/node.h
src/protocol.c
src/protocol.h
src/protocol_auth.c
src/protocol_key.c
src/protocol_subnet.c
src/route.c
src/route.h
src/subnet.c
src/tincd.c

diff --combined NEWS
--- 1/NEWS
--- 2/NEWS
+++ b/NEWS
@@@ -1,9 -1,18 +1,24 @@@
 +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.12               Feb  3 2010
+  * Really allow fast roaming of hosts to other nodes in a switched VPN.
+  * Fixes missing or incorrect environment variables when calling host-up/down
+    and subnet-up/down scripts in some cases.
+  * Allow port to be specified in Address statements.
+  * Clamp MSS of TCP packets to the discovered path MTU.
+  * Let two nodes behind NAT learn each others current UDP address and port via
+    a third node, potentially allowing direct communications in a similar way to
+    STUN.
  Version 1.0.11               Nov  1 2009
  
   * Fixed potential crash when the HUP signal is sent.
diff --combined README
--- 1/README
--- 2/README
+++ b/README
@@@ -1,7 -1,7 +1,7 @@@
 -This is the README file for tinc version 1.0.12. Installation
 +This is the README file for tinc version 1.1-cvs. Installation
  instructions may be found in the INSTALL file.
  
- tinc is Copyright (C) 1998-2009 by:
+ tinc is Copyright (C) 1998-2010 by:
  
  Ivo Timmermans,
  Guus Sliepen <guus@tinc-vpn.org>,
@@@ -55,7 -55,7 +55,7 @@@ should be changed into "Device", and "D
  Compatibility
  -------------
  
 -Version 1.0.12 is compatible with 1.0pre8, 1.0 and later, but not with older
 +Version 1.1-cvs is compatible with 1.0pre8, 1.0 and later, but not with older
  versions of tinc.
  
  
@@@ -78,9 -78,6 +78,9 @@@ Since 1.0, the lzo library is also use
  library whether or not you plan to enable compression. You can find it at
  http://www.oberhumer.com/opensource/lzo/.
  
 +Since 1.1, the libevent library is used for the main event loop. You can find
 +it at http://monkey.org/~provos/libevent/.
 +
  In order to compile tinc, you will need a GNU C compiler environment.
  
  
diff --combined doc/tinc.texi
@@@ -15,7 -15,7 +15,7 @@@
  
  This is the info manual for @value{PACKAGE} version @value{VERSION}, a Virtual Private Network daemon.
  
- Copyright @copyright{} 1998-2009 Ivo Timmermans,
+ Copyright @copyright{} 1998-2010 Ivo Timmermans,
  Guus Sliepen <guus@@tinc-vpn.org> and
  Wessel Dankers <wsl@@tinc-vpn.org>.
  
@@@ -37,10 -37,9 +37,10 @@@ 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-2009 Ivo Timmermans,
+ Copyright @copyright{} 1998-2010 Ivo Timmermans,
  Guus Sliepen <guus@@tinc-vpn.org> and
  Wessel Dankers <wsl@@tinc-vpn.org>.
  
@@@ -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
@@@ -842,6 -818,33 +842,33 @@@ Tinc will expect packets read from the 
  to start with an Ethernet header.
  @end table
  
+ @cindex DirectOnly
+ @item DirectOnly = <yes|no> (no)
+ 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)
+ This option selects the way indirect packets are forwarded.
+ @table @asis
+ @item off
+ Incoming packets that are not meant for the local node,
+ but which should be forwarded to another node, are dropped.
+ @item internal
+ Incoming packets that are meant for another node are forwarded by tinc internally.
+ This is the default mode, and unless you really know you need another forwarding mode, don't change it.
+ @item kernel
+ Incoming packets are always sent to the TUN/TAP device, even if the packets are not for the local node.
+ This is less efficient, but allows the kernel to apply its routing and firewall rules on them,
+ and can also help debugging.
+ @end table
  @cindex GraphDumpFile
  @item GraphDumpFile = <@var{filename}> [experimental]
  If this option is present,
@@@ -940,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
@@@ -952,11 -955,18 +979,18 @@@ specified in the configuration file
  When this option is used the priority of the tincd process will be adjusted.
  Increasing the priority may help to reduce latency and packet loss on the VPN.
  
+ @cindex StrictSubnets
+ @item StrictSubnets <yes|no> (no) [experimental]
+ When this option is enabled tinc will only use Subnet statements which are
+ present in the host config files in the local
+ @file{@value{sysconfdir}/tinc/@var{netname}/hosts/} directory.
  @cindex TunnelServer
  @item TunnelServer = <yes|no> (no) [experimental]
  When this option is enabled tinc will no longer forward information between other tinc daemons,
- and will only allow nodes and subnets on the VPN which are present in the
+ and will only allow connections with nodes for which host config files are present in the local
  @file{@value{sysconfdir}/tinc/@var{netname}/hosts/} directory.
+ Setting this options also implicitly sets StrictSubnets.
  
  @end table
  
  
  @table @asis
  @cindex Address
- @item Address = <@var{IP address}|@var{hostname}> [recommended]
+ @item Address = <@var{IP address}|@var{hostname}> [<port>] [recommended]
  This variable is only required if you want to connect to this host.  It
  must resolve to the external IP address where the host can be reached,
  not the one that is internal to the VPN.
+ If no port is specified, the default Port is used.
  
  @cindex Cipher
  @item Cipher = <@var{cipher}> (blowfish)
@@@ -979,6 -990,12 +1014,12 @@@ Any cipher supported by OpenSSL is reco
  Furthermore, specifying "none" will turn off packet encryption.
  It is best to use only those ciphers which support CBC mode.
  
+ @cindex ClampMSS
+ @item ClampMSS = <yes|no> (yes)
+ This option specifies whether tinc should clamp the maximum segment size (MSS)
+ of TCP packets to the path MTU. This helps in situations where ICMP
+ Fragmentation Needed or Packet too Big messages are dropped by firewalls.
  @cindex Compression
  @item Compression = <@var{level}> (0)
  This option sets the level of compression used for UDP packets.
@@@ -1026,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
@@@ -1062,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,
@@@ -1069,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
  
  
@@@ -1163,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
  
  
@@@ -1209,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.
@@@ -1323,7 -1348,7 +1364,7 @@@ Address = 1.2.3.
  
  Note that the IP addresses of eth0 and tap0 are the same.
  This is quite possible, if you make sure that the netmasks of the interfaces are different.
- It is in fact recommended to give give both real internal network interfaces and tap interfaces the same IP address,
+ It is in fact recommended to give both real internal network interfaces and tap interfaces the same IP address,
  since that will make things a lot easier to remember and set up.
  
  
@@@ -1346,8 -1371,8 +1387,8 @@@ ConnectTo = Branch
  @end example
  
  Note here that the internal address (on eth0) doesn't have to be the
- same as on the tap0 device.  Also, ConnectTo is given so that no-one can
connect to this node.
+ same as on the tap0 device.  Also, ConnectTo is given so that this node will
always try to connect to BranchA.
  
  On all hosts, in @file{@value{sysconfdir}/tinc/company/hosts/BranchB}:
  
@@@ -1438,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},
@@@ -1504,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.
@@@ -1519,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.
@@@ -1558,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 ==================================================================
@@@ -1626,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
  
@@@ -1725,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)
@@@ -1757,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 have.h
--- 1/have.h
--- 2/have.h
+++ b/have.h
@@@ -37,7 -37,6 +37,7 @@@
  #else
  #define WINVER WindowsXP
  #endif
 +#define WIN32_LEAN_AND_MEAN
  #include <w32api.h>
  #include <windows.h>
  #include <ws2tcpip.h>
  #include <sys/time.h>
  #endif
  
 +#ifdef HAVE_TIME_H
 +#include <time.h>
 +#endif
 +
  #ifdef HAVE_SYS_TYPES_H
  #include <sys/types.h>
  #endif
  #include <sys/uio.h>
  #endif
  
 +#ifdef HAVE_SYS_UN_H
 +#include <sys/un.h>
 +#endif
 +
+ #ifdef HAVE_DIRENT_H
+ #include <dirent.h>
+ #endif
  /* SunOS really wants sys/socket.h BEFORE net/if.h,
     and FreeBSD wants these lines below the rest. */
  
  #include <netinet/if_ether.h>
  #endif
  
 +#ifdef HAVE_EVENT_H
 +#include <event.h>
 +#endif
 +
  #endif /* __TINC_SYSTEM_H__ */
diff --combined src/conf.c
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "conf.h"
  #include "logger.h"
  #include "netutl.h"                           /* for str2address */
+ #include "protocol.h"
  #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 */
@@@ -52,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;
  }
  
@@@ -78,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) {
@@@ -192,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;
  }
  
  /*
-   Read exactly one line and strip the trailing newline if any.  If the
-   file was on EOF, return NULL. Otherwise, return all the data in a
-   dynamically allocated buffer.
-   If line is non-NULL, it will be used as an initial buffer, to avoid
-   unnecessary mallocing each time this function is called.  If buf is
-   given, and buf needs to be expanded, the var pointed to by buflen
-   will be increased.
+   Read exactly one line and strip the trailing newline if any.
  */
- static char *readline(FILE * fp, char **buf, size_t *buflen) {
+ static char *readline(FILE * fp, char *buf, size_t buflen) {
        char *newline = NULL;
        char *p;
-       char *line;                                     /* The array that contains everything that has been read so far */
-       char *idx;                                      /* Read into this pointer, which points to an offset within line */
-       size_t size, newsize;           /* The size of the current array pointed to by line */
-       size_t maxlen;                          /* Maximum number of characters that may be read with fgets.  This is newsize - oldsize. */
  
        if(feof(fp))
                return NULL;
  
-       if(buf && buflen) {
-               size = *buflen;
-               line = *buf;
-       } else {
-               size = 100;
-               line = xmalloc(size);
-       }
-       maxlen = size;
-       idx = line;
-       *idx = 0;
+       p = fgets(buf, buflen, fp);
  
-       for(;;) {
-               errno = 0;
-               p = fgets(idx, maxlen, fp);
-               if(!p) {                                /* EOF or error */
-                       if(feof(fp))
-                               break;
+       if(!p)
+               return NULL;
  
-                       /* otherwise: error; let the calling function print an error message if applicable */
-                       free(line);
-                       return NULL;
-               }
+       newline = strchr(p, '\n');
  
-               newline = strchr(p, '\n');
-               if(!newline) {                  /* We haven't yet read everything to the end of the line */
-                       newsize = size << 1;
-                       line = xrealloc(line, newsize);
-                       idx = &line[size - 1];
-                       maxlen = newsize - size + 1;
-                       size = newsize;
-               } else {
-                       *newline = '\0';        /* kill newline */
-                       if(newline > p && newline[-1] == '\r')  /* and carriage return if necessary */
-                               newline[-1] = '\0';
-                       break;                          /* yay */
-               }
-       }
+       if(!newline)
+               return NULL;
  
-       if(buf && buflen) {
-               *buflen = size;
-               *buf = line;
-       }
+       *newline = '\0';        /* kill newline */
+       if(newline > p && newline[-1] == '\r')  /* and carriage return if necessary */
+               newline[-1] = '\0';
  
-       return line;
+       return buf;
  }
  
  /*
    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, *line;
+       char buffer[MAX_STRING_SIZE];
+       char *line;
        char *variable, *value, *eol;
        int lineno = 0;
        int len;
        bool ignore = false;
        config_t *cfg;
-       size_t bufsize;
+       bool result = false;
  
        fp = fopen(fname, "r");
  
        if(!fp) {
-               logger(LOG_ERR, "Cannot open config file %s: %s", fname,
-                          strerror(errno));
-               return -3;
+               logger(LOG_ERR, "Cannot open config file %s: %s", fname, strerror(errno));
+               return false;
        }
  
-       bufsize = 100;
-       buffer = xmalloc(bufsize);
        for(;;) {
-               if(feof(fp)) {
-                       err = 0;
-                       break;
-               }
-               line = readline(fp, &buffer, &bufsize);
+               line = readline(fp, buffer, sizeof buffer);
  
                if(!line) {
-                       err = -1;
+                       if(feof(fp))
+                               result = true;
                        break;
                }
  
                config_add(config_tree, cfg);
        }
  
-       free(buffer);
        fclose(fp);
  
-       return err;
+       return result;
  }
  
  bool read_server_config() {
        char *fname;
-       int x;
+       bool x;
  
        xasprintf(&fname, "%s/tinc.conf", confbase);
        x = read_config_file(config_tree, fname);
  
-       if(x == -1) {                           /* System error: complain */
+       if(!x) {                                /* System error: complain */
                logger(LOG_ERR, "Failed to read `%s': %s", fname, strerror(errno));
        }
  
        free(fname);
  
-       return x == 0;
+       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;
+       bool disabled = false;
+       rewind(f);
+       pos = ftell(f);
+       while(fgets(buf, sizeof buf, f)) {
+               if(!strncmp(buf, "-----BEGIN RSA", 14)) {       
+                       buf[11] = 'O';
+                       buf[12] = 'L';
+                       buf[13] = 'D';
+                       fseek(f, pos, SEEK_SET);
+                       fputs(buf, f);
+                       disabled = true;
+               }
+               else if(!strncmp(buf, "-----END RSA", 12)) {    
+                       buf[ 9] = 'O';
+                       buf[10] = 'L';
+                       buf[11] = 'D';
+                       fseek(f, pos, SEEK_SET);
+                       fputs(buf, f);
+                       disabled = true;
+               }
+               pos = ftell(f);
+       }
+       return disabled;
  }
diff --combined src/connection.c
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
 +#include "cipher.h"
  #include "conf.h"
 +#include "control_common.h"
  #include "list.h"
  #include "logger.h"
  #include "net.h"                              /* Don't ask. */
@@@ -33,7 -31,7 +33,7 @@@
  #include "utils.h"
  #include "xalloc.h"
  
 -avl_tree_t *connection_tree;  /* Meta connections */
 +splay_tree_t *connection_tree;        /* Meta connections */
  connection_t *broadcast;
  
  static int connection_compare(const connection_t *a, const connection_t *b) {
  }
  
  void init_connections(void) {
 -      connection_tree = avl_alloc_tree((avl_compare_t) connection_compare, (avl_action_t) free_connection);
 +      connection_tree = splay_alloc_tree((splay_compare_t) connection_compare, (splay_action_t) free_connection);
        broadcast = new_connection();
        broadcast->name = xstrdup("everyone");
        broadcast->hostname = xstrdup("BROADCAST");
  }
  
  void exit_connections(void) {
 -      avl_delete_tree(connection_tree);
 +      splay_delete_tree(connection_tree);
        free_connection(broadcast);
  }
  
  connection_t *new_connection(void) {
 -      connection_t *c;
 -
 -      c = xmalloc_and_zero(sizeof(connection_t));
 -
 -      if(!c)
 -              return NULL;
 -
 -      gettimeofday(&c->start, NULL);
 -
 -      return c;
 +      return xmalloc_and_zero(sizeof(connection_t));
  }
  
  void free_connection(connection_t *c) {
 +      if(!c)
 +              return;
 +
        if(c->name)
                free(c->name);
  
        if(c->hostname)
                free(c->hostname);
  
 -      if(c->inkey)
 -              free(c->inkey);
 -
 -      if(c->outkey)
 -              free(c->outkey);
 -
 -      if(c->inctx) {
 -              EVP_CIPHER_CTX_cleanup(c->inctx);
 -              free(c->inctx);
 -      }
 -
 -      if(c->outctx) {
 -              EVP_CIPHER_CTX_cleanup(c->outctx);
 -              free(c->outctx);
 -      }
 -
 -      if(c->mychallenge)
 -              free(c->mychallenge);
 +      cipher_close(&c->incipher);
 +      cipher_close(&c->outcipher);
  
        if(c->hischallenge)
                free(c->hischallenge);
        if(c->config_tree)
                exit_configuration(&c->config_tree);
  
 -      if(c->outbuf)
 -              free(c->outbuf);
 -
 -      if(c->rsa_key)
 -              RSA_free(c->rsa_key);
 +      if(c->buffer)
 +              bufferevent_free(c->buffer);
 +      
 +      if(event_initialized(&c->inevent))
 +              event_del(&c->inevent);
  
        free(c);
  }
  
  void connection_add(connection_t *c) {
 -      avl_insert(connection_tree, c);
 +      splay_insert(connection_tree, c);
  }
  
  void connection_del(connection_t *c) {
 -      avl_delete(connection_tree, c);
 +      splay_delete(connection_tree, c);
  }
  
 -void dump_connections(void) {
 -      avl_node_t *node;
 +bool dump_connections(connection_t *cdump) {
 +      splay_node_t *node;
        connection_t *c;
  
 -      logger(LOG_DEBUG, "Connections:");
 -
        for(node = connection_tree->head; node; node = node->next) {
                c = node->data;
 -              logger(LOG_DEBUG, " %s at %s options %x socket %d status %04x outbuf %d/%d/%d",
 -                         c->name, c->hostname, c->options, c->socket, bitfield_to_int(&c->status, sizeof c->status),
 -                         c->outbufsize, c->outbufstart, c->outbuflen);
 +              send_request(cdump, "%d %d %s at %s options %x socket %d status %04x",
 +                              CONTROL, REQ_DUMP_CONNECTIONS,
 +                              c->name, c->hostname, c->options, c->socket,
 +                              bitfield_to_int(&c->status, sizeof c->status));
        }
  
 -      logger(LOG_DEBUG, "End of connections.");
 +      return send_request(cdump, "%d %d", CONTROL, REQ_DUMP_CONNECTIONS);
  }
  
  bool read_connection_config(connection_t *c) {
        char *fname;
-       int x;
+       bool x;
  
        xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
        x = read_config_file(c->config_tree, fname);
        free(fname);
  
-       return x == 0;
+       return x;
  }
diff --combined src/connection.h
@@@ -1,6 -1,6 +1,6 @@@
  /*
      connection.h -- header for connection.c
-     Copyright (C) 2000-2009 Guus Sliepen <guus@tinc-vpn.org>,
+     Copyright (C) 2000-2010 Guus Sliepen <guus@tinc-vpn.org>,
                    2000-2005 Ivo Timmermans
  
      This program is free software; you can redistribute it and/or modify
  #ifndef __TINC_CONNECTION_H__
  #define __TINC_CONNECTION_H__
  
 -#include <openssl/rsa.h>
 -#include <openssl/evp.h>
 -
 -#include "avl_tree.h"
 +#include "cipher.h"
 +#include "digest.h"
 +#include "rsa.h"
 +#include "splay_tree.h"
  
  #define OPTION_INDIRECT               0x0001
  #define OPTION_TCPONLY                0x0002
  #define OPTION_PMTU_DISCOVERY 0x0004
+ #define OPTION_CLAMP_MSS      0x0008
  
  typedef struct connection_status_t {
 -      int pinged:1;                           /* sent ping */
 -      int active:1;                           /* 1 if active.. */
 -      int connecting:1;                       /* 1 if we are waiting for a non-blocking connect() to finish */
 -      int termreq:1;                          /* the termination of this connection was requested */
 -      int remove:1;                           /* Set to 1 if you want this connection removed */
 -      int timeout:1;                          /* 1 if gotten timeout */
 -      int encryptout:1;                       /* 1 if we can encrypt outgoing traffic */
 -      int decryptin:1;                        /* 1 if we have to decrypt incoming traffic */
 -      int mst:1;                              /* 1 if this connection is part of a minimum spanning tree */
 -      int unused:23;
 +              int pinged:1;                           /* sent ping */
 +              int active:1;                           /* 1 if active.. */
 +              int connecting:1;                       /* 1 if we are waiting for a non-blocking connect() to finish */
 +              int termreq:1;                          /* the termination of this connection was requested */
 +              int remove_unused:1;                            /* Set to 1 if you want this connection removed */
 +              int timeout_unused:1;                           /* 1 if gotten timeout */
 +              int encryptout:1;                       /* 1 if we can encrypt outgoing traffic */
 +              int decryptin:1;                        /* 1 if we have to decrypt incoming traffic */
 +              int mst:1;                              /* 1 if this connection is part of a minimum spanning tree */
 +              int control:1;
 +              int unused:22;
  } connection_status_t;
  
  #include "edge.h"
@@@ -66,30 -66,42 +67,30 @@@ typedef struct connection_t 
        struct node_t *node;            /* node associated with the other end */
        struct edge_t *edge;            /* edge associated with this connection */
  
 -      RSA *rsa_key;                           /* his public/private key */
 -      const EVP_CIPHER *incipher;     /* Cipher he will use to send data to us */
 -      const EVP_CIPHER *outcipher;    /* Cipher we will use to send data to him */
 -      EVP_CIPHER_CTX *inctx;          /* Context of encrypted meta data that will come from him to us */
 -      EVP_CIPHER_CTX *outctx;         /* Context of encrypted meta data that will be sent from us to him */
 -      char *inkey;                            /* His symmetric meta key + iv */
 -      char *outkey;                           /* Our symmetric meta key + iv */
 -      int inkeylength;                        /* Length of his key + iv */
 -      int outkeylength;                       /* Length of our key + iv */
 -      const EVP_MD *indigest;
 -      const EVP_MD *outdigest;
 +      rsa_t rsa;                      /* his public/private key */
 +      cipher_t incipher;              /* Cipher he will use to send data to us */
 +      cipher_t outcipher;             /* Cipher we will use to send data to him */
 +      digest_t indigest;
 +      digest_t outdigest;
 +
        int inmaclength;
        int outmaclength;
        int incompression;
        int outcompression;
 -      char *mychallenge;                      /* challenge we received from him */
 -      char *hischallenge;                     /* challenge we sent to him */
  
 -      char buffer[MAXBUFSIZE];        /* metadata input buffer */
 -      int buflen;                                     /* bytes read into buffer */
 -      int reqlen;                                     /* length of incoming request */
 +      char *hischallenge;             /* The challenge we sent to him */
 +
 +      struct bufferevent *buffer;                     /* buffer events on this metadata connection */
 +      struct event inevent;                           /* input event on this metadata connection */
        int tcplen;                                     /* length of incoming TCPpacket */
        int allow_request;                      /* defined if there's only one request possible */
  
 -      char *outbuf;                           /* metadata output buffer */
 -      int outbufstart;                        /* index of first meaningful byte in output buffer */
 -      int outbuflen;                          /* number of meaningful bytes in output buffer */
 -      int outbufsize;                         /* number of bytes allocated to output buffer */
 -
        time_t last_ping_time;          /* last time we saw some activity from the other end or pinged them */
 -      time_t last_flushed_time;       /* last time buffer was empty. Only meaningful if outbuflen > 0 */
  
 -      avl_tree_t *config_tree;        /* Pointer to configuration tree belonging to him */
 +      splay_tree_t *config_tree;      /* Pointer to configuration tree belonging to him */
  } connection_t;
  
 -extern avl_tree_t *connection_tree;
 +extern splay_tree_t *connection_tree;
  extern connection_t *broadcast;
  
  extern void init_connections(void);
@@@ -98,7 -110,7 +99,7 @@@ extern connection_t *new_connection(voi
  extern void free_connection(connection_t *);
  extern void connection_add(connection_t *);
  extern void connection_del(connection_t *);
 -extern void dump_connections(void);
 +extern bool dump_connections(struct connection_t *);
  extern bool read_connection_config(connection_t *);
  
  #endif                                                        /* __TINC_CONNECTION_H__ */
diff --combined src/graph.c
@@@ -1,6 -1,6 +1,6 @@@
  /*
      graph.c -- graph algorithms
-     Copyright (C) 2001-2009 Guus Sliepen <guus@tinc-vpn.org>,
+     Copyright (C) 2001-2010 Guus Sliepen <guus@tinc-vpn.org>,
                    2001-2005 Ivo Timmermans
  
      This program is free software; you can redistribute it and/or modify
@@@ -44,7 -44,7 +44,7 @@@
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "config.h"
  #include "connection.h"
  #include "device.h"
  #include "netutl.h"
  #include "node.h"
  #include "process.h"
+ #include "protocol.h"
  #include "subnet.h"
  #include "utils.h"
  #include "xalloc.h"
  
 -static bool graph_changed = true;
 -
  /* Implementation of Kruskal's algorithm.
 -   Running time: O(EN)
 +   Running time: O(E)
     Please note that sorting on weight is already done by add_edge().
  */
  
  void mst_kruskal(void) {
 -      avl_node_t *node, *next;
 +      splay_node_t *node, *next;
        edge_t *e;
        node_t *n;
        connection_t *c;
 -      int nodes = 0;
 -      int safe_edges = 0;
 -      bool skipped;
  
        /* Clear MST status on connections */
  
                c->status.mst = false;
        }
  
 -      /* Do we have something to do at all? */
 -
 -      if(!edge_weight_tree->head)
 -              return;
 -
        ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Running Kruskal's algorithm:");
  
        /* Clear visited status on nodes */
        for(node = node_tree->head; node; node = node->next) {
                n = node->data;
                n->status.visited = false;
 -              nodes++;
 -      }
 -
 -      /* Starting point */
 -
 -      for(node = edge_weight_tree->head; node; node = node->next) {
 -              e = node->data;
 -              if(e->from->status.reachable) {
 -                      e->from->status.visited = true;
 -                      break;
 -              }
        }
  
        /* Add safe edges */
  
 -      for(skipped = false, node = edge_weight_tree->head; node; node = next) {
 +      for(node = edge_weight_tree->head; node; node = next) {
                next = node->next;
                e = node->data;
  
 -              if(!e->reverse || e->from->status.visited == e->to->status.visited) {
 -                      skipped = true;
 +              if(!e->reverse || (e->from->status.visited && e->to->status.visited))
                        continue;
 -              }
  
                e->from->status.visited = true;
                e->to->status.visited = true;
                if(e->reverse->connection)
                        e->reverse->connection->status.mst = true;
  
 -              safe_edges++;
 -
                ifdebug(SCARY_THINGS) logger(LOG_DEBUG, " Adding edge %s - %s weight %d", e->from->name,
                                   e->to->name, e->weight);
 +      }
 +}
  
 -              if(skipped) {
 -                      skipped = false;
 -                      next = edge_weight_tree->head;
 -                      continue;
 +/* Implementation of Dijkstra's algorithm.
 +   Running time: O(N^2)
 +*/
 +
 +void sssp_dijkstra(void) {
 +      splay_node_t *node, *to;
 +      edge_t *e;
 +      node_t *n, *m;
 +      list_t *todo_list;
 +      list_node_t *lnode, *nnode;
 +      bool indirect;
 +
 +      todo_list = list_alloc(NULL);
 +
 +      ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Running Dijkstra's algorithm:");
 +
 +      /* Clear visited status on nodes */
 +
 +      for(node = node_tree->head; node; node = node->next) {
 +              n = node->data;
 +              n->status.visited = false;
 +              n->status.indirect = true;
 +              n->distance = -1;
 +      }
 +
 +      /* Begin with myself */
 +
 +      myself->status.indirect = false;
 +      myself->nexthop = myself;
 +      myself->via = myself;
 +      myself->distance = 0;
 +      list_insert_head(todo_list, myself);
 +
 +      /* Loop while todo_list is filled */
 +
 +      while(todo_list->head) {
 +              n = NULL;
 +              nnode = NULL;
 +
 +              /* Select node from todo_list with smallest distance */
 +
 +              for(lnode = todo_list->head; lnode; lnode = lnode->next) {
 +                      m = lnode->data;
 +                      if(!n || m->status.indirect < n->status.indirect || m->distance < n->distance) {
 +                              n = m;
 +                              nnode = lnode;
 +                      }
 +              }
 +
 +              /* Mark this node as visited and remove it from the todo_list */
 +
 +              n->status.visited = true;
 +              list_unlink_node(todo_list, nnode);
 +
 +              /* Update distance of neighbours and add them to the todo_list */
 +
 +              for(to = n->edge_tree->head; to; to = to->next) {       /* "to" is the edge connected to "from" */
 +                      e = to->data;
 +
 +                      if(e->to->status.visited || !e->reverse)
 +                              continue;
 +
 +                      /* Situation:
 +
 +                                 /
 +                                /
 +                         ----->(n)---e-->(e->to)
 +                                \
 +                                 \
 +
 +                         Where e is an edge, (n) and (e->to) are nodes.
 +                         n->address is set to the e->address of the edge left of n to n.
 +                         We are currently examining the edge e right of n from n:
 +
 +                         - If e->reverse->address != n->address, then e->to is probably
 +                           not reachable for the nodes left of n. We do as if the indirectdata
 +                           flag is set on edge e.
 +                         - If edge e provides for better reachability of e->to, update e->to.
 +                       */
 +
 +                      if(e->to->distance < 0)
 +                              list_insert_tail(todo_list, e->to);
 +
 +                      indirect = n->status.indirect || e->options & OPTION_INDIRECT || ((n != myself) && sockaddrcmp(&n->address, &e->reverse->address));
 +
 +                      if(e->to->distance >= 0 && (!e->to->status.indirect || indirect) && e->to->distance <= n->distance + e->weight)
 +                              continue;
 +
 +                      e->to->distance = n->distance + e->weight;
 +                      e->to->status.indirect = indirect;
 +                      e->to->nexthop = (n->nexthop == myself) ? e->to : n->nexthop;
 +                      e->to->via = indirect ? n->via : e->to;
 +                      e->to->options = e->options;
 +
 +                      if(sockaddrcmp(&e->to->address, &e->address)) {
 +                              node = splay_unlink(node_udp_tree, e->to);
 +                              sockaddrfree(&e->to->address);
 +                              sockaddrcpy(&e->to->address, &e->address);
 +
 +                              if(e->to->hostname)
 +                                      free(e->to->hostname);
 +
 +                              e->to->hostname = sockaddr2hostname(&e->to->address);
 +
 +                              if(node)
 +                                      splay_insert_node(node_udp_tree, node);
 +
 +                              if(e->to->options & OPTION_PMTU_DISCOVERY) {
 +                                      e->to->mtuprobes = 0;
 +                                      e->to->minmtu = 0;
 +                                      e->to->maxmtu = MTU;
 +                                      if(e->to->status.validkey)
 +                                              send_mtu_probe(e->to);
 +                              }
 +                      }
 +
 +                      ifdebug(SCARY_THINGS) logger(LOG_DEBUG, " Updating edge %s - %s weight %d distance %d", e->from->name,
 +                                         e->to->name, e->weight, e->to->distance);
                }
        }
  
 -      ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Done, counted %d nodes and %d safe edges.", nodes,
 -                         safe_edges);
 +      list_free(todo_list);
  }
  
  /* Implementation of a simple breadth-first search algorithm.
  */
  
  void sssp_bfs(void) {
 -      avl_node_t *node, *next, *to;
 +      splay_node_t *node, *to;
        edge_t *e;
        node_t *n;
        list_t *todo_list;
        list_node_t *from, *todonext;
        bool indirect;
 -      char *name;
 -      char *address, *port;
 -      char *envp[7];
 -      int i;
  
        todo_list = list_alloc(NULL);
  
        }
  
        list_free(todo_list);
 +}
 +
 +void check_reachability() {
 +      splay_node_t *node, *next;
 +      node_t *n;
 +      char *name;
 +      char *address, *port;
 +      char *envp[7];
 +      int i;
  
        /* Check reachability status. */
  
                        /* TODO: only clear status.validkey if node is unreachable? */
  
                        n->status.validkey = false;
-                       n->status.waitingforkey = false;
+                       n->last_req_key = 0;
  
                        n->maxmtu = MTU;
                        n->minmtu = 0;
                        n->mtuprobes = 0;
  
 -                      if(n->mtuevent) {
 -                              event_del(n->mtuevent);
 -                              n->mtuevent = NULL;
 -                      }
 +                      event_del(&n->mtuevent);
  
                        xasprintf(&envp[0], "NETNAME=%s", netname ? : "");
                        xasprintf(&envp[1], "DEVICE=%s", device ? : "");
  
                        if(!n->status.reachable)
                                update_node_udp(n, NULL);
+                       else if(n->connection)
+                               send_ans_key(n);
                }
        }
  }
  
  void graph(void) {
        subnet_cache_flush();
 -      sssp_bfs();
 +      sssp_dijkstra();
 +      check_reachability();
        mst_kruskal();
 -      graph_changed = true;
 -}
 -
 -
 -
 -/* Dump nodes and edges to a graphviz file.
 -         
 -   The file can be converted to an image with
 -   dot -Tpng graph_filename -o image_filename.png -Gconcentrate=true
 -*/
 -
 -void dump_graph(void) {
 -      avl_node_t *node;
 -      node_t *n;
 -      edge_t *e;
 -      char *filename = NULL, *tmpname = NULL;
 -      FILE *file;
 -      
 -      if(!graph_changed || !get_config_string(lookup_config(config_tree, "GraphDumpFile"), &filename))
 -              return;
 -
 -      graph_changed = false;
 -
 -      ifdebug(PROTOCOL) logger(LOG_NOTICE, "Dumping graph");
 -      
 -      if(filename[0] == '|') {
 -              file = popen(filename + 1, "w");
 -      } else {
 -              xasprintf(&tmpname, "%s.new", filename);
 -              file = fopen(tmpname, "w");
 -      }
 -
 -      if(!file) {
 -              logger(LOG_ERR, "Unable to open graph dump file %s: %s", filename, strerror(errno));
 -              free(tmpname);
 -              return;
 -      }
 -
 -      fprintf(file, "digraph {\n");
 -      
 -      /* dump all nodes first */
 -      for(node = node_tree->head; node; node = node->next) {
 -              n = node->data;
 -              fprintf(file, " %s [label = \"%s\"];\n", n->name, n->name);
 -      }
 -
 -      /* now dump all edges */
 -      for(node = edge_weight_tree->head; node; node = node->next) {
 -              e = node->data;
 -              fprintf(file, " %s -> %s;\n", e->from->name, e->to->name);
 -      }
 -
 -      fprintf(file, "}\n");   
 -      
 -      if(filename[0] == '|') {
 -              pclose(file);
 -      } else {
 -              fclose(file);
 -#ifdef HAVE_MINGW
 -              unlink(filename);
 -#endif
 -              rename(tmpname, filename);
 -              free(tmpname);
 -      }
  }
diff --combined src/net.c
+++ b/src/net.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      net.c -- most of the network code
      Copyright (C) 1998-2005 Ivo Timmermans,
-                   2000-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2010 Guus Sliepen <guus@tinc-vpn.org>
                    2006      Scott Lamb <slamb@slamb.org>
  
      This program is free software; you can redistribute it and/or modify
  
  #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;
@@@ -59,9 -68,9 +59,9 @@@
                        for(snode = n->subnet_tree->head; snode; snode = snext) {
                                snext = snode->next;
                                s = snode->data;
-                               if(!tunnelserver)
-                                       send_del_subnet(broadcast, s);
-                               subnet_del(n, s);
+                               send_del_subnet(broadcast, s);
+                               if(!strictsubnets)
+                                       subnet_del(n, s);
                        }
  
                        for(enode = n->edge_tree->head; enode; enode = enext) {
                                        break;
                        }
  
-                       if(!enode)
+                       if(!enode && (!strictsubnets || !n->subnet_tree->head))
+                               /* in strictsubnets mode do not delete nodes with subnets */
                                node_del(n);
                }
        }
  }
  
 -/*
 -  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);
 -                      }
 -              }
        }
 +
 +      event_add(event, &(struct timeval){pingtimeout, 0});
  }
  
 -/*
 -  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);
 +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;
                }
        }
  
 -      /* check meta connections */
 -      for(node = connection_tree->head; node; node = node->next) {
 -              c = node->data;
 +      if (!receive_meta(c)) {
 +              terminate_connection(c, c->status.active);
 +              return;
 +      }
 +}
  
 -              if(c->status.remove)
 -                      continue;
 +static void sigterm_handler(int signal, short events, void *data) {
 +      logger(LOG_NOTICE, "Got %s signal", strsignal(signal));
 +      event_loopexit(NULL);
 +}
  
 -              if(FD_ISSET(c->socket, readset)) {
 -                      if(c->status.connecting) {
 -                              c->status.connecting = false;
 -                              getsockopt(c->socket, SOL_SOCKET, SO_ERROR, &result, &len);
 +static void sighup_handler(int signal, short events, void *data) {
 +      logger(LOG_NOTICE, "Got %s signal", strsignal(signal));
 +      reload_configuration();
 +}
  
 -                              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;
 -                              }
 -                      }
 +int reload_configuration(void) {
 +      connection_t *c;
 +      splay_node_t *node, *next;
 +      char *fname;
 +      struct stat s;
 +      static time_t last_config_check = 0;
  
 -                      if(!receive_meta(c)) {
 -                              terminate_connection(c, c->status.active);
 -                              continue;
 -                      }
 -              }
 +      /* Reread our own configuration file */
  
 -              if(FD_ISSET(c->socket, writeset)) {
 -                      if(!flush_meta(c)) {
 -                              terminate_connection(c, c->status.active);
 -                              continue;
 -                      }
 +      exit_configuration(&config_tree);
 +      init_configuration(&config_tree);
 +
 +      if(!read_server_config()) {
 +              logger(LOG_ERR, "Unable to reread configuration file, exitting.");
 +              event_loopexit(NULL);
 +              return EINVAL;
 +      }
 +
 +      /* 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);
        }
  
 -      for(i = 0; i < listen_sockets; i++) {
 -              if(FD_ISSET(listen_socket[i].udp, readset))
 -                      handle_incoming_vpn_data(listen_socket[i].udp);
 +      last_config_check = time(NULL);
  
 -              if(FD_ISSET(listen_socket[i].tcp, readset))
 -                      handle_new_meta_connection(listen_socket[i].tcp);
 +      /* Try to make outgoing connections */
 +      
 +      try_outgoing_connections();
 +
 +      return 0;
 +}
 +
 +void retry(void) {
 +      connection_t *c;
 +      splay_node_t *node;
 +
 +      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);
 +              }
        }
  }
  
    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;
 +      struct event timeout_event;
 +      struct event sighup_event;
 +      struct event sigterm_event;
 +      struct event sigquit_event;
  
 -      while(running) {
 -              now = time(NULL);
 +      timeout_set(&timeout_event, timeout_handler, &timeout_event);
 +      event_add(&timeout_event, &(struct timeval){pingtimeout, 0});
  
 -      //      tv.tv_sec = 1 + (rand() & 7);   /* Approx. 5 seconds, randomized to prevent global synchronisation effects */
 -              tv.tv_sec = 1;
 -              tv.tv_usec = 0;
 -
 -              maxfd = build_fdset(&readset, &writeset);
 -
 -#ifdef HAVE_MINGW
 -              LeaveCriticalSection(&mutex);
 +#ifdef SIGHUP
 +      signal_set(&sighup_event, SIGHUP, sighup_handler, NULL);
 +      signal_add(&sighup_event, NULL);
  #endif
 -              r = select(maxfd + 1, &readset, &writeset, NULL, &tv);
 -#ifdef HAVE_MINGW
 -              EnterCriticalSection(&mutex);
 +#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(r < 0) {
 -                      if(!sockwouldblock(sockerrno)) {
 -                              logger(LOG_ERR, "Error while waiting for input: %s", sockstrerror(sockerrno));
 -                              dump_connections();
 -                              return 1;
 -                      }
 -              }
 -
 -              if(r > 0)
 -                      check_network_activity(&readset, &writeset);
 -
 -              if(do_purge) {
 -                      purge();
 -                      do_purge = false;
 -              }
 -
 -              /* Let's check if everybody is still alive */
 -
 -              if(last_ping_check + pingtimeout < now) {
 -                      check_dead_connections();
 -                      last_ping_check = now;
 -
 -                      if(routing_mode == RMODE_SWITCH)
 -                              age_subnets();
 -
 -                      age_past_requests();
 -
 -                      /* Should we regenerate our key? */
 -
 -                      if(keyexpires < now) {
 -                              avl_node_t *node;
 -                              node_t *n;
 -
 -                              ifdebug(STATUS) logger(LOG_INFO, "Expiring symmetric keys");
 -
 -                              for(node = node_tree->head; node; node = node->next) {
 -                                      n = node->data;
 -                                      if(n->inkey) {
 -                                              free(n->inkey);
 -                                              n->inkey = NULL;
 -                                      }
 -                              }
 -
 -                              send_key_changed(broadcast, myself);
 -                              keyexpires = now + keylifetime;
 -                      }
 -              }
 -
 -              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);
 -                      }
 -                      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);
 -                      }
 -
 -                      last_config_check = now;
 -
 -                      /* Try to make outgoing connections */
 -                      
 -                      try_outgoing_connections();
 -              }
 -              
 -              /* Dump graph if wanted every 60 seconds*/
 -
 -              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_packet.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      net_packet.c -- Handles in- and outgoing VPN packets
      Copyright (C) 1998-2005 Ivo Timmermans,
-                   2000-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2010 Guus Sliepen <guus@tinc-vpn.org>
  
      This program is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
  
  #include "system.h"
  
+ #include <openssl/rand.h>
+ #include <openssl/err.h>
+ #include <openssl/evp.h>
+ #include <openssl/pem.h>
+ #include <openssl/hmac.h>
+ #ifdef HAVE_ZLIB
  #include <zlib.h>
+ #endif
+ #ifdef HAVE_LZO
  #include LZO1X_H
+ #endif
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
 +#include "cipher.h"
  #include "conf.h"
  #include "connection.h"
 +#include "crypto.h"
 +#include "digest.h"
  #include "device.h"
  #include "ethernet.h"
 -#include "event.h"
  #include "graph.h"
  #include "list.h"
  #include "logger.h"
@@@ -44,7 -53,9 +55,9 @@@
  
  int keylifetime = 0;
  int keyexpires = 0;
+ #ifdef HAVE_LZO
  static char lzo_wrkmem[LZO1X_999_MEM_COMPRESS > LZO1X_1_MEM_COMPRESS ? LZO1X_999_MEM_COMPRESS : LZO1X_1_MEM_COMPRESS];
+ #endif
  
  static void send_udppacket(node_t *, vpn_packet_t *);
  
  // mtuprobes ==    32: send 1 burst, sleep pingtimeout second
  // mtuprobes ==    33: no response from other side, restart PMTU discovery process
  
 -void send_mtu_probe(node_t *n) {
 +static void send_mtu_probe_handler(int fd, short events, void *data) {
 +      node_t *n = data;
        vpn_packet_t packet;
        int len, i;
        int timeout = 1;
        
        n->mtuprobes++;
 -      n->mtuevent = NULL;
  
        if(!n->status.reachable || !n->status.validkey) {
                ifdebug(TRAFFIC) logger(LOG_INFO, "Trying to send MTU probe to unreachable or rekeying node %s (%s)", n->name, n->hostname);
                        len = 64;
                
                memset(packet.data, 0, 14);
 -              RAND_pseudo_bytes(packet.data + 14, len - 14);
 +              randomize(packet.data + 14, len - 14);
                packet.len = len;
                packet.priority = 0;
  
        }
  
  end:
 -      n->mtuevent = new_event();
 -      n->mtuevent->handler = (event_handler_t)send_mtu_probe;
 -      n->mtuevent->data = n;
 -      n->mtuevent->time = now + timeout;
 -      event_add(n->mtuevent);
 +      event_add(&n->mtuevent, &(struct timeval){timeout, 0});
 +}
 +
 +void send_mtu_probe(node_t *n) {
 +      if(!timeout_initialized(&n->mtuevent))
 +              timeout_set(&n->mtuevent, send_mtu_probe_handler, n);
 +      send_mtu_probe_handler(0, 0, n);
  }
  
  void mtu_probe_h(node_t *n, vpn_packet_t *packet, length_t len) {
  }
  
  static length_t compress_packet(uint8_t *dest, const uint8_t *source, length_t len, int level) {
-       if(level == 10) {
+       if(level == 0) {
+               memcpy(dest, source, len);
+               return len;
+       } else if(level == 10) {
+ #ifdef HAVE_LZO
                lzo_uint lzolen = MAXSIZE;
                lzo1x_1_compress(source, len, dest, &lzolen, lzo_wrkmem);
                return lzolen;
+ #else
+               return -1;
+ #endif
        } else if(level < 10) {
+ #ifdef HAVE_ZLIB
                unsigned long destlen = MAXSIZE;
                if(compress2(dest, &destlen, source, len, level) == Z_OK)
                        return destlen;
                else
+ #endif
                        return -1;
        } else {
+ #ifdef HAVE_LZO
                lzo_uint lzolen = MAXSIZE;
                lzo1x_999_compress(source, len, dest, &lzolen, lzo_wrkmem);
                return lzolen;
+ #else
+               return -1;
+ #endif
        }
        
        return -1;
  }
  
  static length_t uncompress_packet(uint8_t *dest, const uint8_t *source, length_t len, int level) {
-       if(level > 9) {
+       if(level == 0) {
+               memcpy(dest, source, len);
+               return len;
+       } else if(level > 9) {
+ #ifdef HAVE_LZO
                lzo_uint lzolen = MAXSIZE;
                if(lzo1x_decompress_safe(source, len, dest, &lzolen, NULL) == LZO_E_OK)
                        return lzolen;
                else
+ #endif
                        return -1;
-       } else {
+       }
+ #ifdef HAVE_ZLIB
+       else {
                unsigned long destlen = MAXSIZE;
                if(uncompress(dest, &destlen, source, len) == Z_OK)
                        return destlen;
                else
                        return -1;
        }
-       
+ #endif
        return -1;
  }
  
@@@ -191,11 -221,15 +225,11 @@@ static void receive_packet(node_t *n, v
        route(n, packet);
  }
  
 -static bool try_mac(const node_t *n, const vpn_packet_t *inpkt) {
 -      unsigned char hmac[EVP_MAX_MD_SIZE];
 -
 -      if(!n->indigest || !n->inmaclength || !n->inkey || inpkt->len < sizeof inpkt->seqno + n->inmaclength)
 +static bool try_mac(node_t *n, const vpn_packet_t *inpkt) {
 +      if(!digest_active(&n->indigest) || inpkt->len < sizeof inpkt->seqno + digest_length(&n->indigest))
                return false;
  
 -      HMAC(n->indigest, n->inkey, n->inkeylength, (unsigned char *) &inpkt->seqno, inpkt->len - n->inmaclength, (unsigned char *)hmac, NULL);
 -
 -      return !memcmp(hmac, (char *) &inpkt->seqno + inpkt->len - n->inmaclength, n->inmaclength);
 +      return digest_verify(&n->indigest, &inpkt->seqno, inpkt->len - n->indigest.maclength, (const char *)&inpkt->seqno + inpkt->len - n->indigest.maclength);
  }
  
  static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) {
        vpn_packet_t *pkt[] = { &pkt1, &pkt2, &pkt1, &pkt2 };
        int nextpkt = 0;
        vpn_packet_t *outpkt = pkt[0];
 -      int outlen, outpad;
 -      unsigned char hmac[EVP_MAX_MD_SIZE];
 +      size_t outlen;
        int i;
  
 -      if(!n->inkey) {
 +      if(!cipher_active(&n->incipher)) {
                ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got packet from %s (%s) but he hasn't got our key yet",
                                        n->name, n->hostname);
                return;
  
        /* Check packet length */
  
 -      if(inpkt->len < sizeof(inpkt->seqno) + n->inmaclength) {
 +      if(inpkt->len < sizeof inpkt->seqno + digest_length(&n->indigest)) {
                ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got too short packet from %s (%s)",
                                        n->name, n->hostname);
                return;
  
        /* Check the message authentication code */
  
 -      if(n->indigest && n->inmaclength) {
 -              inpkt->len -= n->inmaclength;
 -              HMAC(n->indigest, n->inkey, n->inkeylength,
 -                       (unsigned char *) &inpkt->seqno, inpkt->len, (unsigned char *)hmac, NULL);
 -
 -              if(memcmp(hmac, (char *) &inpkt->seqno + inpkt->len, n->inmaclength)) {
 -                      ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got unauthenticated packet from %s (%s)",
 -                                         n->name, n->hostname);
 +      if(digest_active(&n->indigest)) {
 +              inpkt->len -= n->indigest.maclength;
 +              if(!digest_verify(&n->indigest, &inpkt->seqno, inpkt->len, (const char *)&inpkt->seqno + inpkt->len)) {
 +                      ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got unauthenticated packet from %s (%s)", n->name, n->hostname);
                        return;
                }
        }
 -
        /* Decrypt the packet */
  
 -      if(n->incipher) {
 +      if(cipher_active(&n->incipher)) {
                outpkt = pkt[nextpkt++];
 +              outlen = MAXSIZE;
  
 -              if(!EVP_DecryptInit_ex(&n->inctx, NULL, NULL, NULL, NULL)
 -                              || !EVP_DecryptUpdate(&n->inctx, (unsigned char *) &outpkt->seqno, &outlen,
 -                                      (unsigned char *) &inpkt->seqno, inpkt->len)
 -                              || !EVP_DecryptFinal_ex(&n->inctx, (unsigned char *) &outpkt->seqno + outlen, &outpad)) {
 -                      ifdebug(TRAFFIC) logger(LOG_DEBUG, "Error decrypting packet from %s (%s): %s",
 -                                              n->name, n->hostname, ERR_error_string(ERR_get_error(), NULL));
 +              if(!cipher_decrypt(&n->incipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
 +                      ifdebug(TRAFFIC) logger(LOG_DEBUG, "Error decrypting packet from %s (%s)", n->name, n->hostname);
                        return;
                }
                
 -              outpkt->len = outlen + outpad;
 +              outpkt->len = outlen;
                inpkt = outpkt;
        }
  
        /* Check the sequence number */
  
 -      inpkt->len -= sizeof(inpkt->seqno);
 +      inpkt->len -= sizeof inpkt->seqno;
        inpkt->seqno = ntohl(inpkt->seqno);
  
        if(inpkt->seqno != n->received_seqno + 1) {
 -              if(inpkt->seqno >= n->received_seqno + sizeof(n->late) * 8) {
 +              if(inpkt->seqno >= n->received_seqno + sizeof n->late * 8) {
                        logger(LOG_WARNING, "Lost %d packets from %s (%s)",
                                           inpkt->seqno - n->received_seqno - 1, n->name, n->hostname);
                        
 -                      memset(n->late, 0, sizeof(n->late));
 +                      memset(n->late, 0, sizeof n->late);
                } else if (inpkt->seqno <= n->received_seqno) {
 -                      if((n->received_seqno >= sizeof(n->late) * 8 && inpkt->seqno <= n->received_seqno - sizeof(n->late) * 8) || !(n->late[(inpkt->seqno / 8) % sizeof(n->late)] & (1 << inpkt->seqno % 8))) {
 +                      if((n->received_seqno >= sizeof n->late * 8 && inpkt->seqno <= n->received_seqno - sizeof n->late * 8) || !(n->late[(inpkt->seqno / 8) % sizeof n->late] & (1 << inpkt->seqno % 8))) {
                                logger(LOG_WARNING, "Got late or replayed packet from %s (%s), seqno %d, last received %d",
                                           n->name, n->hostname, inpkt->seqno, n->received_seqno);
                                return;
                        }
                } else {
                        for(i = n->received_seqno + 1; i < inpkt->seqno; i++)
 -                              n->late[(i / 8) % sizeof(n->late)] |= 1 << i % 8;
 +                              n->late[(i / 8) % sizeof n->late] |= 1 << i % 8;
                }
        }
        
 -      n->late[(inpkt->seqno / 8) % sizeof(n->late)] &= ~(1 << inpkt->seqno % 8);
 +      n->late[(inpkt->seqno / 8) % sizeof n->late] &= ~(1 << inpkt->seqno % 8);
  
        if(inpkt->seqno > n->received_seqno)
                n->received_seqno = inpkt->seqno;
                        
        if(n->received_seqno > MAX_SEQNO)
 -              keyexpires = 0;
 +              regenerate_key();
  
        /* Decompress the packet */
  
@@@ -321,7 -364,7 +355,7 @@@ static void send_udppacket(node_t *n, v
        int nextpkt = 0;
        vpn_packet_t *outpkt;
        int origlen;
 -      int outlen, outpad;
 +      size_t outlen;
        static int priority = 0;
        int origpriority;
        int sock;
                                   "No valid key known yet for %s (%s), forwarding via TCP",
                                   n->name, n->hostname);
  
-               if(!n->status.waitingforkey)
+               if(n->last_req_key + 10 < now) {
                        send_req_key(n);
-               n->status.waitingforkey = true;
+                       n->last_req_key = now;
+               }
  
                send_tcppacket(n->nexthop->connection, origpkt);
  
        /* Add sequence number */
  
        inpkt->seqno = htonl(++(n->sent_seqno));
 -      inpkt->len += sizeof(inpkt->seqno);
 +      inpkt->len += sizeof inpkt->seqno;
  
        /* Encrypt the packet */
  
 -      if(n->outcipher) {
 +      if(cipher_active(&n->outcipher)) {
                outpkt = pkt[nextpkt++];
 +              outlen = MAXSIZE;
  
 -              if(!EVP_EncryptInit_ex(&n->outctx, NULL, NULL, NULL, NULL)
 -                              || !EVP_EncryptUpdate(&n->outctx, (unsigned char *) &outpkt->seqno, &outlen,
 -                                      (unsigned char *) &inpkt->seqno, inpkt->len)
 -                              || !EVP_EncryptFinal_ex(&n->outctx, (unsigned char *) &outpkt->seqno + outlen, &outpad)) {
 -                      ifdebug(TRAFFIC) logger(LOG_ERR, "Error while encrypting packet to %s (%s): %s",
 -                                              n->name, n->hostname, ERR_error_string(ERR_get_error(), NULL));
 +              if(!cipher_encrypt(&n->outcipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
 +                      ifdebug(TRAFFIC) logger(LOG_ERR, "Error while encrypting packet to %s (%s)", n->name, n->hostname);
                        goto end;
                }
  
 -              outpkt->len = outlen + outpad;
 +              outpkt->len = outlen;
                inpkt = outpkt;
        }
  
        /* Add the message authentication code */
  
 -      if(n->outdigest && n->outmaclength) {
 -              HMAC(n->outdigest, n->outkey, n->outkeylength, (unsigned char *) &inpkt->seqno,
 -                       inpkt->len, (unsigned char *) &inpkt->seqno + inpkt->len, NULL);
 -              inpkt->len += n->outmaclength;
 +      if(digest_active(&n->outdigest)) {
 +              digest_create(&n->outdigest, &inpkt->seqno, inpkt->len, (char *)&inpkt->seqno + inpkt->len);
 +              inpkt->len += digest_length(&n->outdigest);
        }
  
        /* Determine which socket we have to use */
           && listen_socket[sock].sa.sa.sa_family == AF_INET) {
                priority = origpriority;
                ifdebug(TRAFFIC) logger(LOG_DEBUG, "Setting outgoing packet priority to %d", priority);
 -              if(setsockopt(listen_socket[sock].udp, SOL_IP, IP_TOS, &priority, sizeof(priority)))    /* SO_PRIORITY doesn't seem to work */
 +              if(setsockopt(listen_socket[sock].udp, SOL_IP, IP_TOS, &priority, sizeof priority))     /* SO_PRIORITY doesn't seem to work */
                        logger(LOG_ERR, "System call `%s' failed: %s", "setsockopt", strerror(errno));
        }
  #endif
@@@ -478,7 -525,7 +512,7 @@@ void send_packet(const node_t *n, vpn_p
  /* Broadcast a packet using the minimum spanning tree */
  
  void broadcast_packet(const node_t *from, vpn_packet_t *packet) {
 -      avl_node_t *node;
 +      splay_node_t *node;
        connection_t *c;
  
        ifdebug(TRAFFIC) logger(LOG_INFO, "Broadcasting packet of %d bytes from %s (%s)",
  }
  
  static node_t *try_harder(const sockaddr_t *from, const vpn_packet_t *pkt) {
 -      avl_node_t *node;
 -      edge_t *e;
 -      node_t *n = NULL;
 +      splay_node_t *node;
 +      node_t *n, *found = NULL;
        static time_t last_hard_try = 0;
 +      time_t now = time(NULL);
  
 -      for(node = edge_weight_tree->head; node; node = node->next) {
 -              e = node->data;
 -
 -              if(sockaddrcmp_noport(from, &e->address)) {
 -                      if(last_hard_try == now)
 -                              continue;
 -                      last_hard_try = now;
 -              }
 +      if(last_hard_try == now)
 +              return NULL;
 +      else
 +              last_hard_try = now;
  
 -              if(!n)
 -                      n = e->to;
 +      for(node = node_tree->head; node; node = node->next) {
 +              n = node->data;
  
 -              if(!try_mac(e->to, pkt))
 +              if(n == myself || !n->status.reachable || !digest_active(&n->indigest))
                        continue;
  
 -              n = e->to;
 -              break;
 +              if(try_mac(n, pkt)) {
 +                      found = n;
 +                      break;
 +              }
        }
  
 -      return n;
 +      return found;
  }
  
 -void handle_incoming_vpn_data(int sock) {
 +void handle_incoming_vpn_data(int sock, short events, void *data) {
        vpn_packet_t pkt;
        char *hostname;
        sockaddr_t from;
 -      socklen_t fromlen = sizeof(from);
 +      socklen_t fromlen = sizeof from;
        node_t *n;
 +      int len;
  
 -      pkt.len = recvfrom(sock, (char *) &pkt.seqno, MAXSIZE, 0, &from.sa, &fromlen);
 +      len = recvfrom(sock, (char *) &pkt.seqno, MAXSIZE, 0, &from.sa, &fromlen);
  
 -      if(pkt.len < 0) {
 +      if(len <= 0 || len > MAXSIZE) {
                if(!sockwouldblock(sockerrno))
                        logger(LOG_ERR, "Receiving packet failed: %s", sockstrerror(sockerrno));
                return;
        }
  
 +      pkt.len = len;
 +
        sockaddrunmap(&from);           /* Some braindead IPv6 implementations do stupid things. */
  
        n = lookup_node_udp(&from);
  
        receive_udppacket(n, &pkt);
  }
 +
 +void handle_device_data(int sock, short events, void *data) {
 +      vpn_packet_t packet;
 +
 +      if(read_packet(&packet))
 +              route(myself, &packet);
 +}
diff --combined src/net_setup.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      net_setup.c -- Setup.
      Copyright (C) 1998-2005 Ivo Timmermans,
-                   2000-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2010 Guus Sliepen <guus@tinc-vpn.org>
                    2006      Scott Lamb <slamb@slamb.org>
  
      This program is free software; you can redistribute it and/or modify
  
  #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(!get_config_string(lookup_config(c->config_tree, "PublicKeyFile"), &fname))
 +              xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
  
 -              /* If it fails, try PEM_read_RSA_PUBKEY. */
 -              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_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;
  
 -      if(get_config_string(lookup_config(config_tree, "PrivateKey"), &key)) {
 -              if(!get_config_string(lookup_config(myself->connection->config_tree, "PublicKey"), &pubkey)) {
 +      /* First, check for simple PrivateKey statement */
 +
 +      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) {
+       DIR *dir;
+       struct dirent *ent;
+       char *dname;
+       char *fname;
+       avl_tree_t *config_tree;
+       config_t *cfg;
+       subnet_t *s;
+       node_t *n;
+       bool result;
+       xasprintf(&dname, "%s/hosts", confbase);
+       dir = opendir(dname);
+       if(!dir) {
+               logger(LOG_ERR, "Could not open %s: %s", dname, strerror(errno));
+               free(dname);
+               return;
+       }
+       while((ent = readdir(dir))) {
+               if(!check_id(ent->d_name))
+                       continue;
+               n = lookup_node(ent->d_name);
+               if(n)
+                       continue;
+               #ifdef _DIRENT_HAVE_D_TYPE
+               //if(ent->d_type != DT_REG)
+               //      continue;
+               #endif
+               xasprintf(&fname, "%s/hosts/%s", confbase, ent->d_name);
+               init_configuration(&config_tree);
+               result = read_config_file(config_tree, fname);
+               free(fname);
+               if(!result)
+                       continue;
+               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);
+               }
+               exit_configuration(&config_tree);
+       }
+       closedir(dir);
+ }
  /*
    Configure node_t myself and set up the local sockets (listen only)
  */
@@@ -171,8 -277,8 +230,8 @@@ bool setup_myself(void) 
        myself->connection = new_connection();
        init_configuration(&myself->connection->config_tree);
  
-       xasprintf(&myself->hostname, "MYSELF");
-       xasprintf(&myself->connection->hostname, "MYSELF");
+       myself->hostname = xstrdup("MYSELF");
+       myself->connection->hostname = xstrdup("MYSELF");
  
        myself->connection->options = 0;
        myself->connection->protocol_version = PROT_CURRENT;
        if(!read_rsa_private_key())
                return false;
  
-       if(!get_config_string(lookup_config(myself->connection->config_tree, "Port"), &myport))
-               xasprintf(&myport, "655");
+       if(!get_config_string(lookup_config(config_tree, "Port"), &myport)
+                       && !get_config_string(lookup_config(myself->connection->config_tree, "Port"), &myport))
+               myport = xstrdup("655");
  
        /* Read in all the subnets specified in the host configuration file */
  
        if(myself->options & OPTION_TCPONLY)
                myself->options |= OPTION_INDIRECT;
  
+       get_config_bool(lookup_config(config_tree, "DirectOnly"), &directonly);
+       get_config_bool(lookup_config(config_tree, "StrictSubnets"), &strictsubnets);
        get_config_bool(lookup_config(config_tree, "TunnelServer"), &tunnelserver);
+       strictsubnets |= tunnelserver;
  
        if(get_config_string(lookup_config(config_tree, "Mode"), &mode)) {
                if(!strcasecmp(mode, "router"))
                        return false;
                }
                free(mode);
-       } else
-               routing_mode = RMODE_ROUTER;
+       }
  
-       // Enable PMTUDiscovery by default if we are in router mode.
+       if(get_config_string(lookup_config(config_tree, "Forwarding"), &mode)) {
+               if(!strcasecmp(mode, "off"))
+                       forwarding_mode = FMODE_OFF;
+               else if(!strcasecmp(mode, "internal"))
+                       forwarding_mode = FMODE_INTERNAL;
+               else if(!strcasecmp(mode, "kernel"))
+                       forwarding_mode = FMODE_KERNEL;
+               else {
+                       logger(LOG_ERR, "Invalid forwarding mode!");
+                       return false;
+               }
+               free(mode);
+       }
  
-       choice = routing_mode == RMODE_ROUTER;
+       choice = true;
        get_config_bool(lookup_config(myself->connection->config_tree, "PMTUDiscovery"), &choice);
-       if(choice)      
+       get_config_bool(lookup_config(config_tree, "PMTUDiscovery"), &choice);
+       if(choice)
                myself->options |= OPTION_PMTU_DISCOVERY;
  
+       choice = true;
+       get_config_bool(lookup_config(config_tree, "ClampMSS"), &choice);
+       get_config_bool(lookup_config(myself->connection->config_tree, "ClampMSS"), &choice);
+       if(choice)
+               myself->options |= OPTION_CLAMP_MSS;
        get_config_bool(lookup_config(config_tree, "PriorityInheritance"), &priorityinheritance);
  
  #if !defined(SOL_IP) || !defined(IP_TOS)
  
        /* 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(!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;
 +      if(!get_config_string(lookup_config(myself->connection->config_tree, "Cipher"), &cipher))
 +              cipher = xstrdup("blowfish");
  
 -      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 */
  
  
        graph();
  
+       if(strictsubnets)
+               load_all_subnets();
        /* Open device */
  
        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_EMERG, "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_EMERG, "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/net_socket.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      net_socket.c -- Handle various kinds of sockets.
      Copyright (C) 1998-2005 Ivo Timmermans,
-                   2000-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2010 Guus Sliepen <guus@tinc-vpn.org>
                    2006      Scott Lamb <slamb@slamb.org>
                    2009      Florian Forster <octo@verplant.org>
  
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "conf.h"
  #include "connection.h"
 -#include "event.h"
  #include "logger.h"
  #include "meta.h"
  #include "net.h"
@@@ -69,12 -70,12 +69,12 @@@ static void configure_tcp(connection_t 
  
  #if defined(SOL_TCP) && defined(TCP_NODELAY)
        option = 1;
 -      setsockopt(c->socket, SOL_TCP, TCP_NODELAY, &option, sizeof(option));
 +      setsockopt(c->socket, SOL_TCP, TCP_NODELAY, &option, sizeof option);
  #endif
  
  #if defined(SOL_IP) && defined(IP_TOS) && defined(IPTOS_LOWDELAY)
        option = IPTOS_LOWDELAY;
 -      setsockopt(c->socket, SOL_IP, IP_TOS, &option, sizeof(option));
 +      setsockopt(c->socket, SOL_IP, IP_TOS, &option, sizeof option);
  #endif
  }
  
@@@ -180,7 -181,7 +180,7 @@@ int setup_listen_socket(const sockaddr_
        /* Optimize TCP settings */
  
        option = 1;
 -      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
 +      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof option);
  
  #if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
        if(sa->sa.sa_family == AF_INET6)
  #if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE)
                struct ifreq ifr;
  
 -              memset(&ifr, 0, sizeof(ifr));
 +              memset(&ifr, 0, sizeof ifr);
                strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ);
  
 -              if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr))) {
 +              if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof ifr)) {
                        closesocket(nfd);
                        logger(LOG_ERR, "Can't bind to interface %s: %s", iface,
                                   strerror(sockerrno));
@@@ -258,11 -259,15 +258,15 @@@ int setup_vpn_in_socket(const sockaddr_
  #endif
  
        option = 1;
 -      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
 +      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof option);
  
- #if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
+ #if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY)
        if(sa->sa.sa_family == AF_INET6)
-               setsockopt(nfd, SOL_IPV6, IPV6_V6ONLY, &option, sizeof option);
+               setsockopt(nfd, IPPROTO_IPV6, IPV6_V6ONLY, &option, sizeof option);
+ #endif
+ #if defined(IP_DONTFRAG) && !defined(IP_DONTFRAGMENT)
+ #define IP_DONTFRAGMENT IP_DONTFRAG
  #endif
  
  #if defined(SOL_IP) && defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
                option = 1;
                setsockopt(nfd, IPPROTO_IP, IP_DONTFRAGMENT, &option, sizeof(option));
        }
+ #else
+ #warning No way to disable IPv4 fragmentation
  #endif
  
  #if defined(SOL_IPV6) && defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO)
                option = IPV6_PMTUDISC_DO;
                setsockopt(nfd, SOL_IPV6, IPV6_MTU_DISCOVER, &option, sizeof(option));
        }
+ #elif defined(IPPROTO_IPV6) && defined(IPV6_DONTFRAG)
+       if(myself->options & OPTION_PMTU_DISCOVERY) {
+               option = 1;
+               setsockopt(nfd, IPPROTO_IPV6, IPV6_DONTFRAG, &option, sizeof(option));
+       }
+ #else
+ #warning No way to disable IPv6 fragmentation
  #endif
  
        if (!bind_to_interface(nfd)) {
        return nfd;
  } /* int setup_vpn_in_socket */
  
 +static void retry_outgoing_handler(int fd, short events, void *data) {
 +      setup_outgoing_connection(data);
 +}
 +
  void retry_outgoing(outgoing_t *outgoing) {
        outgoing->timeout += 5;
  
        if(outgoing->timeout > maxtimeout)
                outgoing->timeout = maxtimeout;
  
 -      if(outgoing->event)
 -              event_del(outgoing->event);
 -      outgoing->event = new_event();
 -      outgoing->event->handler = (event_handler_t) setup_outgoing_connection;
 -      outgoing->event->time = now + outgoing->timeout;
 -      outgoing->event->data = outgoing;
 -      event_add(outgoing->event);
 +      timeout_set(&outgoing->ev, retry_outgoing_handler, outgoing);
 +      event_add(&outgoing->ev, &(struct timeval){outgoing->timeout, 0});
  
        ifdebug(CONNECTIONS) logger(LOG_NOTICE,
                           "Trying to re-establish outgoing connection in %d seconds",
@@@ -323,14 -338,13 +336,14 @@@ void finish_connecting(connection_t *c
  
        configure_tcp(c);
  
 -      c->last_ping_time = now;
 +      c->last_ping_time = time(NULL);
 +      c->status.connecting = false;
  
        send_id(c);
  }
  
  void do_outgoing_connection(connection_t *c) {
-       char *address, *port;
+       char *address, *port, *space;
        int result;
  
        if(!c->outgoing) {
@@@ -343,16 -357,22 +356,22 @@@ begin
                if(!c->outgoing->cfg) {
                        ifdebug(CONNECTIONS) logger(LOG_ERR, "Could not set up a meta connection to %s",
                                           c->name);
 -                      c->status.remove = true;
                        retry_outgoing(c->outgoing);
                        c->outgoing = NULL;
 +                      connection_del(c);
                        return;
                }
  
                get_config_string(c->outgoing->cfg, &address);
  
-               if(!get_config_string(lookup_config(c->config_tree, "Port"), &port))
-                       xasprintf(&port, "655");
+               space = strchr(address, ' ');
+               if(space) {
+                       port = xstrdup(space + 1);
+                       *space = 0;
+               } else {
+                       if(!get_config_string(lookup_config(c->config_tree, "Port"), &port))
+                               port = xstrdup("655");
+               }
  
                c->outgoing->ai = str2addrinfo(address, port, SOCK_STREAM);
                free(address);
        return;
  }
  
 +void handle_meta_read(struct bufferevent *event, void *data) {
 +      logger(LOG_EMERG, "handle_meta_read() called");
 +      abort();
 +}
 +
 +void handle_meta_write(struct bufferevent *event, void *data) {
 +      ifdebug(META) logger(LOG_DEBUG, "handle_meta_write() called");
 +}
 +
 +void handle_meta_connection_error(struct bufferevent *event, short what, void *data) {
 +      connection_t *c = data;
 +      logger(LOG_EMERG, "handle_meta_connection_error() called: %d: %s", what, strerror(errno));
 +      terminate_connection(c, c->status.active);
 +}
 +
  void setup_outgoing_connection(outgoing_t *outgoing) {
        connection_t *c;
        node_t *n;
  
 -      outgoing->event = NULL;
 +      event_del(&outgoing->ev);
  
        n = lookup_node(outgoing->name);
  
        }
  
        c->outgoing = outgoing;
 -      c->last_ping_time = now;
 +      c->last_ping_time = time(NULL);
  
        connection_add(c);
  
        do_outgoing_connection(c);
 +
 +      event_set(&c->inevent, c->socket, EV_READ | EV_PERSIST, handle_meta_connection_data, c);
 +      event_add(&c->inevent, NULL);
 +      c->buffer = bufferevent_new(c->socket, handle_meta_read, handle_meta_write, handle_meta_connection_error, c);
 +      if(!c->buffer) {
 +              logger(LOG_EMERG, "bufferevent_new() failed: %s", strerror(errno));
 +              abort();
 +      }
 +      bufferevent_disable(c->buffer, EV_READ);
  }
  
  /*
    accept a new tcp connect and create a
    new connection
  */
 -bool handle_new_meta_connection(int sock) {
 +void handle_new_meta_connection(int sock, short events, void *data) {
        connection_t *c;
        sockaddr_t sa;
        int fd;
 -      socklen_t len = sizeof(sa);
 +      socklen_t len = sizeof sa;
  
        fd = accept(sock, &sa.sa, &len);
  
        if(fd < 0) {
                logger(LOG_ERR, "Accepting a new connection failed: %s", sockstrerror(sockerrno));
 -              return false;
 +              return;
        }
  
        sockaddrunmap(&sa);
        c->address = sa;
        c->hostname = sockaddr2hostname(&sa);
        c->socket = fd;
 -      c->last_ping_time = now;
 +      c->last_ping_time = time(NULL);
  
        ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Connection from %s", c->hostname);
  
 +      event_set(&c->inevent, c->socket, EV_READ | EV_PERSIST, handle_meta_connection_data, c);
 +      event_add(&c->inevent, NULL);
 +      c->buffer = bufferevent_new(c->socket, NULL, handle_meta_write, handle_meta_connection_error, c);
 +      if(!c->buffer) {
 +              logger(LOG_EMERG, "bufferevent_new() failed: %s", strerror(errno));
 +              abort();
 +      }
 +      bufferevent_disable(c->buffer, EV_READ);
 +              
        configure_tcp(c);
  
        connection_add(c);
  
        c->allow_request = ID;
        send_id(c);
 -
 -      return true;
  }
  
  void free_outgoing(outgoing_t *outgoing) {
@@@ -566,7 -555,7 +585,7 @@@ void try_outgoing_connections(void) 
                        continue;
                }
  
 -              outgoing = xmalloc_and_zero(sizeof(*outgoing));
 +              outgoing = xmalloc_and_zero(sizeof *outgoing);
                outgoing->name = name;
                list_insert_tail(outgoing_list, outgoing);
                setup_outgoing_connection(outgoing);
diff --combined src/node.h
@@@ -1,6 -1,6 +1,6 @@@
  /*
      node.h -- header for node.c
-     Copyright (C) 2001-2009 Guus Sliepen <guus@tinc-vpn.org>,
+     Copyright (C) 2001-2010 Guus Sliepen <guus@tinc-vpn.org>,
                    2001-2005 Ivo Timmermans
  
      This program is free software; you can redistribute it and/or modify
  #ifndef __TINC_NODE_H__
  #define __TINC_NODE_H__
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
 +#include "cipher.h"
  #include "connection.h"
 -#include "event.h"
 +#include "digest.h"
  #include "list.h"
  #include "subnet.h"
  
  typedef struct node_status_t {
        int unused_active:1;                    /* 1 if active (not used for nodes) */
        int validkey:1;                         /* 1 if we currently have a valid key for him */
-       int waitingforkey:1;                    /* 1 if we already sent out a request */
+       int unused_waitingforkey:1;             /* 1 if we already sent out a request */
        int visited:1;                          /* 1 if this node has been visited by one of the graph algorithms */
        int reachable:1;                        /* 1 if this node is reachable in the graph */
        int indirect:1;                         /* 1 if this node is not directly reachable by us */
@@@ -46,23 -45,33 +46,24 @@@ typedef struct node_t 
        char *hostname;                         /* the hostname of its real ip */
  
        node_status_t status;
+       time_t last_req_key;
  
 -      const EVP_CIPHER *incipher;             /* Cipher type for UDP packets received from him */
 -      char *inkey;                            /* Cipher key and iv */
 -      int inkeylength;                        /* Cipher key and iv length */
 -      EVP_CIPHER_CTX inctx;                   /* Cipher context */
 -      
 -      const EVP_CIPHER *outcipher;            /* Cipher type for UDP packets sent to him*/
 -      char *outkey;                           /* Cipher key and iv */
 -      int outkeylength;                       /* Cipher key and iv length */
 -      EVP_CIPHER_CTX outctx;                  /* Cipher context */
 -      
 -      const EVP_MD *indigest;                 /* Digest type for MAC of packets received from him */
 -      int inmaclength;                        /* Length of MAC */
 -
 -      const EVP_MD *outdigest;                /* Digest type for MAC of packets sent to him*/
 -      int outmaclength;                       /* Length of MAC */
 +      cipher_t incipher;                        /* Cipher for UDP packets */
 +      digest_t indigest;                        /* Digest for UDP packets */  
 +
 +      cipher_t outcipher;                        /* Cipher for UDP packets */
 +      digest_t outdigest;                        /* Digest for UDP packets */ 
  
        int incompression;                      /* Compressionlevel, 0 = no compression */
        int outcompression;                     /* Compressionlevel, 0 = no compression */
  
 +      int distance;
        struct node_t *nexthop;                 /* nearest node from us to him */
        struct node_t *via;                     /* next hop for UDP packets */
  
 -      avl_tree_t *subnet_tree;                /* Pointer to a tree of subnets belonging to this node */
 +      splay_tree_t *subnet_tree;              /* Pointer to a tree of subnets belonging to this node */
  
 -      avl_tree_t *edge_tree;                  /* Edges with this node as one of the endpoints */
 +      splay_tree_t *edge_tree;                        /* Edges with this node as one of the endpoints */
  
        struct connection_t *connection;        /* Connection associated with this node (if a direct connection exists) */
  
        length_t minmtu;                        /* Probed minimum MTU */
        length_t maxmtu;                        /* Probed maximum MTU */
        int mtuprobes;                          /* Number of probes */
 -      event_t *mtuevent;                      /* Probe event */
 +      struct event mtuevent;                  /* Probe event */
  } node_t;
  
  extern struct node_t *myself;
 -extern avl_tree_t *node_tree;
 -extern avl_tree_t *node_udp_tree;
 +extern splay_tree_t *node_tree;
 +extern splay_tree_t *node_udp_tree;
  
  extern void init_nodes(void);
  extern void exit_nodes(void);
@@@ -89,7 -98,7 +90,7 @@@ extern void node_add(node_t *)
  extern void node_del(node_t *);
  extern node_t *lookup_node(char *);
  extern node_t *lookup_node_udp(const sockaddr_t *);
 +extern bool dump_nodes(struct connection_t *);
  extern void update_node_udp(node_t *, const sockaddr_t *);
 -extern void dump_nodes(void);
  
  #endif                                                        /* __TINC_NODE_H__ */
diff --combined src/protocol.c
  #include "xalloc.h"
  
  bool tunnelserver = false;
+ bool strictsubnets = false;
  
  /* Jumptable for the request handlers */
  
 -static bool (*request_handlers[])(connection_t *) = {
 +static bool (*request_handlers[])(connection_t *, char *) = {
                id_h, metakey_h, challenge_h, chal_reply_h, ack_h,
                status_h, error_h, termreq_h,
                ping_h, pong_h,
                add_subnet_h, del_subnet_h,
                add_edge_h, del_edge_h,
 -              key_changed_h, req_key_h, ans_key_h, tcppacket_h,
 +              key_changed_h, req_key_h, ans_key_h, tcppacket_h, control_h,
  };
  
  /* Request names */
@@@ -48,10 -49,10 +49,10 @@@ static char (*request_name[]) = 
                "STATUS", "ERROR", "TERMREQ",
                "PING", "PONG",
                "ADD_SUBNET", "DEL_SUBNET",
 -              "ADD_EDGE", "DEL_EDGE", "KEY_CHANGED", "REQ_KEY", "ANS_KEY", "PACKET",
 +              "ADD_EDGE", "DEL_EDGE", "KEY_CHANGED", "REQ_KEY", "ANS_KEY", "PACKET", "CONTROL",
  };
  
 -static avl_tree_t *past_request_tree;
 +static splay_tree_t *past_request_tree;
  
  bool check_id(const char *id) {
        for(; *id; id++)
  
  bool send_request(connection_t *c, const char *format, ...) {
        va_list args;
 -      char buffer[MAXBUFSIZE];
 -      int len, request;
 +      char request[MAXBUFSIZE];
 +      int len;
  
        /* Use vsnprintf instead of vxasprintf: faster, no memory
           fragmentation, cleanup is automatic, and there is a limit on the
           input buffer anyway */
  
        va_start(args, format);
 -      len = vsnprintf(buffer, MAXBUFSIZE, format, args);
 +      len = vsnprintf(request, MAXBUFSIZE, format, args);
        va_end(args);
  
        if(len < 0 || len > MAXBUFSIZE - 1) {
        }
  
        ifdebug(PROTOCOL) {
 -              sscanf(buffer, "%d", &request);
                ifdebug(META)
                        logger(LOG_DEBUG, "Sending %s to %s (%s): %s",
 -                                 request_name[request], c->name, c->hostname, buffer);
 +                                 request_name[atoi(request)], c->name, c->hostname, request);
                else
 -                      logger(LOG_DEBUG, "Sending %s to %s (%s)", request_name[request],
 +                      logger(LOG_DEBUG, "Sending %s to %s (%s)", request_name[atoi(request)],
                                   c->name, c->hostname);
        }
  
 -      buffer[len++] = '\n';
 +      request[len++] = '\n';
  
        if(c == broadcast) {
 -              broadcast_meta(NULL, buffer, len);
 +              broadcast_meta(NULL, request, len);
                return true;
        } else
 -              return send_meta(c, buffer, len);
 +              return send_meta(c, request, len);
  }
  
 -void forward_request(connection_t *from) {
 -      int request;
 -
 +void forward_request(connection_t *from, char *request) {
        ifdebug(PROTOCOL) {
 -              sscanf(from->buffer, "%d", &request);
                ifdebug(META)
                        logger(LOG_DEBUG, "Forwarding %s from %s (%s): %s",
 -                                 request_name[request], from->name, from->hostname,
 -                                 from->buffer);
 +                                 request_name[atoi(request)], from->name, from->hostname, request);
                else
                        logger(LOG_DEBUG, "Forwarding %s from %s (%s)",
 -                                 request_name[request], from->name, from->hostname);
 +                                 request_name[atoi(request)], from->name, from->hostname);
        }
  
 -      from->buffer[from->reqlen - 1] = '\n';
 -
 -      broadcast_meta(from, from->buffer, from->reqlen);
 +      int len = strlen(request);
 +      request[len] = '\n';
 +      broadcast_meta(from, request, len);
  }
  
 -bool receive_request(connection_t *c) {
 -      int request;
 +bool receive_request(connection_t *c, char *request) {
 +      int reqno = atoi(request);
  
 -      if(sscanf(c->buffer, "%d", &request) == 1) {
 -              if((request < 0) || (request >= LAST) || !request_handlers[request]) {
 +      if(reqno || *request == '0') {
 +              if((reqno < 0) || (reqno >= LAST) || !request_handlers[reqno]) {
                        ifdebug(META)
                                logger(LOG_DEBUG, "Unknown request from %s (%s): %s",
 -                                         c->name, c->hostname, c->buffer);
 +                                         c->name, c->hostname, request);
                        else
                                logger(LOG_ERR, "Unknown request from %s (%s)",
                                           c->name, c->hostname);
                        ifdebug(PROTOCOL) {
                                ifdebug(META)
                                        logger(LOG_DEBUG, "Got %s from %s (%s): %s",
 -                                                 request_name[request], c->name, c->hostname,
 -                                                 c->buffer);
 +                                                 request_name[reqno], c->name, c->hostname, request);
                                else
                                        logger(LOG_DEBUG, "Got %s from %s (%s)",
 -                                                 request_name[request], c->name, c->hostname);
 +                                                 request_name[reqno], c->name, c->hostname);
                        }
                }
  
 -              if((c->allow_request != ALL) && (c->allow_request != request)) {
 +              if((c->allow_request != ALL) && (c->allow_request != reqno)) {
                        logger(LOG_ERR, "Unauthorized request from %s (%s)", c->name,
                                   c->hostname);
                        return false;
                }
  
 -              if(!request_handlers[request](c)) {
 +              if(!request_handlers[reqno](c, request)) {
                        /* Something went wrong. Probably scriptkiddies. Terminate. */
  
                        logger(LOG_ERR, "Error while processing %s from %s (%s)",
 -                                 request_name[request], c->name, c->hostname);
 +                                 request_name[reqno], c->name, c->hostname);
                        return false;
                }
        } else {
@@@ -173,38 -180,42 +174,38 @@@ static void free_past_request(past_requ
        free(r);
  }
  
 -void init_requests(void) {
 -      past_request_tree = avl_alloc_tree((avl_compare_t) past_request_compare, (avl_action_t) free_past_request);
 -}
 -
 -void exit_requests(void) {
 -      avl_delete_tree(past_request_tree);
 -}
 +static struct event past_request_event;
  
  bool seen_request(char *request) {
        past_request_t *new, p = {0};
  
        p.request = request;
  
 -      if(avl_search(past_request_tree, &p)) {
 +      if(splay_search(past_request_tree, &p)) {
                ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Already seen request");
                return true;
        } else {
 -              new = xmalloc(sizeof(*new));
 +              new = xmalloc(sizeof *new);
                new->request = xstrdup(request);
 -              new->firstseen = now;
 -              avl_insert(past_request_tree, new);
 +              new->firstseen = time(NULL);
 +              splay_insert(past_request_tree, new);
 +              event_add(&past_request_event, &(struct timeval){10, 0});
                return false;
        }
  }
  
 -void age_past_requests(void) {
 -      avl_node_t *node, *next;
 +void age_past_requests(int fd, short events, void *data) {
 +      splay_node_t *node, *next;
        past_request_t *p;
        int left = 0, deleted = 0;
 +      time_t now = time(NULL);
  
        for(node = past_request_tree->head; node; node = next) {
                next = node->next;
                p = node->data;
  
                if(p->firstseen + pinginterval < now)
 -                      avl_delete_node(past_request_tree, node), deleted++;
 +                      splay_delete_node(past_request_tree, node), deleted++;
                else
                        left++;
        }
        if(left || deleted)
                ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Aging past requests: deleted %d, left %d",
                           deleted, left);
 +
 +      if(left)
 +              event_add(&past_request_event, &(struct timeval){10, 0});
 +}
 +
 +void init_requests(void) {
 +      past_request_tree = splay_alloc_tree((splay_compare_t) past_request_compare, (splay_action_t) free_past_request);
 +
 +      timeout_set(&past_request_event, age_past_requests, NULL);
 +}
 +
 +void exit_requests(void) {
 +      splay_delete_tree(past_request_tree);
 +
 +      event_del(&past_request_event);
  }
diff --combined src/protocol.h
@@@ -44,7 -44,6 +44,7 @@@ typedef enum request_t 
        ADD_EDGE, DEL_EDGE,
        KEY_CHANGED, REQ_KEY, ANS_KEY,
        PACKET,
 +      CONTROL,
        LAST                                            /* Guardian for the highest request number */
  } request_t;
  
@@@ -54,6 -53,7 +54,7 @@@ typedef struct past_request_t 
  } past_request_t;
  
  extern bool tunnelserver;
+ extern bool strictsubnets;
  
  /* Maximum size of strings in a request.
   * scanf terminates %2048s with a NUL character,
  /* Basic functions */
  
  extern bool send_request(struct connection_t *, const char *, ...) __attribute__ ((__format__(printf, 2, 3)));
 -extern void forward_request(struct connection_t *);
 -extern bool receive_request(struct connection_t *);
 +extern void forward_request(struct connection_t *, char *);
 +extern bool receive_request(struct connection_t *, char *);
  extern bool check_id(const char *);
  
  extern void init_requests(void);
  extern void exit_requests(void);
  extern bool seen_request(char *);
 -extern void age_past_requests(void);
  
  /* Requests */
  
@@@ -95,31 -96,30 +96,31 @@@ extern bool send_add_subnet(struct conn
  extern bool send_del_subnet(struct connection_t *, const struct subnet_t *);
  extern bool send_add_edge(struct connection_t *, const struct edge_t *);
  extern bool send_del_edge(struct connection_t *, const struct edge_t *);
- extern bool send_key_changed();
+ extern void send_key_changed();
  extern bool send_req_key(struct node_t *);
  extern bool send_ans_key(struct node_t *);
  extern bool send_tcppacket(struct connection_t *, struct vpn_packet_t *);
  
  /* Request handlers  */
  
 -extern bool id_h(struct connection_t *);
 -extern bool metakey_h(struct connection_t *);
 -extern bool challenge_h(struct connection_t *);
 -extern bool chal_reply_h(struct connection_t *);
 -extern bool ack_h(struct connection_t *);
 -extern bool status_h(struct connection_t *);
 -extern bool error_h(struct connection_t *);
 -extern bool termreq_h(struct connection_t *);
 -extern bool ping_h(struct connection_t *);
 -extern bool pong_h(struct connection_t *);
 -extern bool add_subnet_h(struct connection_t *);
 -extern bool del_subnet_h(struct connection_t *);
 -extern bool add_edge_h(struct connection_t *);
 -extern bool del_edge_h(struct connection_t *);
 -extern bool key_changed_h(struct connection_t *);
 -extern bool req_key_h(struct connection_t *);
 -extern bool ans_key_h(struct connection_t *);
 -extern bool tcppacket_h(struct connection_t *);
 +extern bool id_h(struct connection_t *, char *);
 +extern bool metakey_h(struct connection_t *, char *);
 +extern bool challenge_h(struct connection_t *, char *);
 +extern bool chal_reply_h(struct connection_t *, char *);
 +extern bool ack_h(struct connection_t *, char *);
 +extern bool status_h(struct connection_t *, char *);
 +extern bool error_h(struct connection_t *, char *);
 +extern bool termreq_h(struct connection_t *, char *);
 +extern bool ping_h(struct connection_t *, char *);
 +extern bool pong_h(struct connection_t *, char *);
 +extern bool add_subnet_h(struct connection_t *, char *);
 +extern bool del_subnet_h(struct connection_t *, char *);
 +extern bool add_edge_h(struct connection_t *, char *);
 +extern bool del_edge_h(struct connection_t *, char *);
 +extern bool key_changed_h(struct connection_t *, char *);
 +extern bool req_key_h(struct connection_t *, char *);
 +extern bool ans_key_h(struct connection_t *, char *);
 +extern bool tcppacket_h(struct connection_t *, char *);
 +extern bool control_h(struct connection_t *, char *);
  
  #endif                                                        /* __TINC_PROTOCOL_H__ */
diff --combined src/protocol_auth.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      protocol_auth.c -- handle the meta-protocol, authentication
      Copyright (C) 1999-2005 Ivo Timmermans,
-                   2000-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2010 Guus Sliepen <guus@tinc-vpn.org>
  
      This program is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
  
  #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);
@@@ -357,13 -453,18 +357,18 @@@ bool send_ack(connection_t *c) 
        if(myself->options & OPTION_PMTU_DISCOVERY)
                c->options |= OPTION_PMTU_DISCOVERY;
  
+       choice = myself->options & OPTION_CLAMP_MSS;
+       get_config_bool(lookup_config(c->config_tree, "ClampMSS"), &choice);
+       if(choice)
+               c->options |= OPTION_CLAMP_MSS;
        get_config_int(lookup_config(c->config_tree, "Weight"), &c->estimated_weight);
  
        return send_request(c, "%d %s %d %x", ACK, myport, c->estimated_weight, c->options);
  }
  
  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;
        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();
        if(get_config_int(lookup_config(myself->connection->config_tree, "PMTU"), &mtu) && mtu < n->mtu)
                n->mtu = mtu;
  
+       if(get_config_bool(lookup_config(c->config_tree, "ClampMSS"), &choice)) {
+               if(choice)
+                       c->options |= OPTION_CLAMP_MSS;
+               else
+                       c->options &= ~OPTION_CLAMP_MSS;
+       }
        /* Activate this connection */
  
        c->allow_request = ALL;
diff --combined src/protocol_key.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      protocol_key.c -- handle the meta-protocol, key exchange
      Copyright (C) 1999-2005 Ivo Timmermans,
-                   2000-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2010 Guus Sliepen <guus@tinc-vpn.org>
  
      This program is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
  
  #include "system.h"
  
 -#include <openssl/evp.h>
 -#include <openssl/err.h>
 -#include <openssl/rand.h>
 -
 -#include "avl_tree.h"
 +#include "splay_tree.h"
 +#include "cipher.h"
  #include "connection.h"
 +#include "crypto.h"
  #include "logger.h"
  #include "net.h"
  #include "netutl.h"
  #include "utils.h"
  #include "xalloc.h"
  
 -bool mykeyused = false;
 +static bool mykeyused = false;
  
- bool send_key_changed() {
-       /* Only send this message if some other daemon requested our key previously.
-          This reduces unnecessary key_changed broadcasts.
-        */
+ void send_key_changed() {
+       avl_node_t *node;
+       connection_t *c;
  
-       if(!mykeyused)
-               return true;
+       send_request(broadcast, "%d %x %s", KEY_CHANGED, rand(), myself->name);
+       /* Immediately send new keys to directly connected nodes to keep UDP mappings alive */
  
-       return send_request(broadcast, "%d %x %s", KEY_CHANGED, rand(), myself->name);
+       for(node = connection_tree->head; node; node = node->next) {
+               c = node->data;
+               if(c->status.active && c->node && c->node->status.reachable)
+                       send_ans_key(c->node);
+       }
  }
  
 -bool key_changed_h(connection_t *c) {
 +bool key_changed_h(connection_t *c, char *request) {
        char name[MAX_STRING_SIZE];
        node_t *n;
  
 -      if(sscanf(c->buffer, "%*d %*x " MAX_STRING, name) != 1) {
 +      if(sscanf(request, "%*d %*x " MAX_STRING, name) != 1) {
                logger(LOG_ERR, "Got bad %s from %s (%s)", "KEY_CHANGED",
                           c->name, c->hostname);
                return false;
        }
  
 -      if(!check_id(name)) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s): %s", "KEY_CHANGED", c->name, c->hostname, "invalid name");
 -              return false;
 -      }
 -
 -      if(seen_request(c->buffer))
 +      if(seen_request(request))
                return true;
  
        n = lookup_node(name);
        if(!n) {
                logger(LOG_ERR, "Got %s from %s (%s) origin %s which does not exist",
                           "KEY_CHANGED", c->name, c->hostname, name);
-               return false;
+               return true;
        }
  
        n->status.validkey = false;
-       n->status.waitingforkey = false;
+       n->last_req_key = 0;
  
        /* Tell the others */
  
        if(!tunnelserver)
 -              forward_request(c);
 +              forward_request(c, request);
  
        return true;
  }
@@@ -81,23 -92,28 +85,28 @@@ bool send_req_key(node_t *to) 
        return send_request(to->nexthop->connection, "%d %s %s", REQ_KEY, myself->name, to->name);
  }
  
 -bool req_key_h(connection_t *c) {
 +bool req_key_h(connection_t *c, char *request) {
        char from_name[MAX_STRING_SIZE];
        char to_name[MAX_STRING_SIZE];
        node_t *from, *to;
  
 -      if(sscanf(c->buffer, "%*d " MAX_STRING " " MAX_STRING, from_name, to_name) != 2) {
 +      if(sscanf(request, "%*d " MAX_STRING " " MAX_STRING, from_name, to_name) != 2) {
                logger(LOG_ERR, "Got bad %s from %s (%s)", "REQ_KEY", c->name,
                           c->hostname);
                return false;
        }
  
+       if(!check_id(from_name) || !check_id(to_name)) {
+               logger(LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_KEY", c->name, c->hostname, "invalid name");
+               return false;
+       }
        from = lookup_node(from_name);
  
        if(!from) {
                logger(LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list",
                           "REQ_KEY", c->name, c->hostname, from_name);
-               return false;
+               return true;
        }
  
        to = lookup_node(to_name);
        if(!to) {
                logger(LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list",
                           "REQ_KEY", c->name, c->hostname, to_name);
-               return false;
+               return true;
        }
  
        /* Check if this key request is for us */
  
        if(to == myself) {                      /* Yes, send our own key back */
 +
                send_ans_key(from);
        } else {
                if(tunnelserver)
-                       return false;
+                       return true;
  
                if(!to->status.reachable) {
                        logger(LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable",
                        return true;
                }
  
 -              send_request(to->nexthop->connection, "%s", c->buffer);
 +              send_request(to->nexthop->connection, "%s", request);
        }
  
        return true;
  }
  
  bool send_ans_key(node_t *to) {
 -      char *key;
 +      size_t keylen = cipher_keylength(&myself->incipher);
 +      char key[keylen * 2 + 1];
  
 -      // Set key parameters
 -      to->incipher = myself->incipher;
 -      to->inkeylength = myself->inkeylength;
 -      to->indigest = myself->indigest;
 -      to->inmaclength = myself->inmaclength;
 +      cipher_open_by_nid(&to->incipher, cipher_get_nid(&myself->incipher));
 +      digest_open_by_nid(&to->indigest, digest_get_nid(&myself->indigest), digest_length(&myself->indigest));
        to->incompression = myself->incompression;
  
 -      // Allocate memory for key
 -      to->inkey = xrealloc(to->inkey, to->inkeylength);
 +      randomize(key, keylen);
 +      cipher_set_key(&to->incipher, key, true);
 +      digest_set_key(&to->indigest, key, keylen);
  
 -      // Create a new key
 -      RAND_pseudo_bytes((unsigned char *)to->inkey, to->inkeylength);
 -      if(to->incipher)
 -              EVP_DecryptInit_ex(&to->inctx, to->incipher, NULL, (unsigned char *)to->inkey, (unsigned char *)to->inkey + to->incipher->key_len);
 +      bin2hex(key, key, keylen);
 +      key[keylen * 2] = '\0';
  
        // Reset sequence number and late packet window
        mykeyused = true;
        to->received_seqno = 0;
        memset(to->late, 0, sizeof(to->late));
  
 -      // Convert to hexadecimal and send
 -      key = alloca(2 * to->inkeylength + 1);
 -      bin2hex(to->inkey, key, to->inkeylength);
 -      key[to->inkeylength * 2] = '\0';
 -
 -      return send_request(to->nexthop->connection, "%d %s %s %s %d %d %d %d", ANS_KEY,
 -                      myself->name, to->name, key,
 -                      to->incipher ? to->incipher->nid : 0,
 -                      to->indigest ? to->indigest->type : 0, to->inmaclength,
 -                      to->incompression);
 +      return send_request(to->nexthop->connection, "%d %s %s %s %d %d %zu %d", ANS_KEY,
 +                                              myself->name, to->name, key,
 +                                              cipher_get_nid(&to->incipher),
 +                                              digest_get_nid(&to->indigest),
 +                                              digest_length(&to->indigest),
 +                                              to->incompression);
  }
  
 -bool ans_key_h(connection_t *c) {
 +bool ans_key_h(connection_t *c, char *request) {
        char from_name[MAX_STRING_SIZE];
        char to_name[MAX_STRING_SIZE];
        char key[MAX_STRING_SIZE];
 -      char address[MAX_STRING_SIZE] = "";
 -      char port[MAX_STRING_SIZE] = "";
 -      int cipher, digest, maclength, compression;
++        char address[MAX_STRING_SIZE] = "";
++        char port[MAX_STRING_SIZE] = "";
 +      int cipher, digest, maclength, compression, keylen;
        node_t *from, *to;
  
 -      if(sscanf(c->buffer, "%*d "MAX_STRING" "MAX_STRING" "MAX_STRING" %d %d %d %d "MAX_STRING" "MAX_STRING,
 +      if(sscanf(request, "%*d "MAX_STRING" "MAX_STRING" "MAX_STRING" %d %d %d %d",
                from_name, to_name, key, &cipher, &digest, &maclength,
-               &compression) != 7) {
+               &compression, address, port) < 7) {
                logger(LOG_ERR, "Got bad %s from %s (%s)", "ANS_KEY", c->name,
                           c->hostname);
                return false;
        }
  
+       if(!check_id(from_name) || !check_id(to_name)) {
+               logger(LOG_ERR, "Got bad %s from %s (%s): %s", "ANS_KEY", c->name, c->hostname, "invalid name");
+               return false;
+       }
        from = lookup_node(from_name);
  
        if(!from) {
                logger(LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list",
                           "ANS_KEY", c->name, c->hostname, from_name);
-               return false;
+               return true;
        }
  
        to = lookup_node(to_name);
        if(!to) {
                logger(LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list",
                           "ANS_KEY", c->name, c->hostname, to_name);
-               return false;
+               return true;
        }
  
        /* Forward it if necessary */
  
        if(to != myself) {
                if(tunnelserver)
-                       return false;
+                       return true;
  
                if(!to->status.reachable) {
                        logger(LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable",
 -                              "ANS_KEY", c->name, c->hostname, to_name);
 -                      return true;
 -              }
 -
 -              if(!*address) {
 -                      char *address, *port;
 -                      ifdebug(PROTOCOL) logger(LOG_DEBUG, "Appending reflexive UDP address to ANS_KEY from %s to %s", from->name, to->name);
 -                      sockaddr2str(&from->address, &address, &port);
 -                      send_request(to->nexthop->connection, "%s %s %s", c->buffer, address, port);
 -                      free(address);
 -                      free(port);
 +                                 "ANS_KEY", c->name, c->hostname, to_name);
                        return true;
                }
  
 -              return send_request(to->nexthop->connection, "%s", c->buffer);
 +              return send_request(to->nexthop->connection, "%s", request);
        }
  
 -      /* Update our copy of the origin's packet key */
 -      from->outkey = xrealloc(from->outkey, strlen(key) / 2);
 -
 -      from->outkey = xstrdup(key);
 -      from->outkeylength = strlen(key) / 2;
 -      hex2bin(key, from->outkey, from->outkeylength);
 -
        /* Check and lookup cipher and digest algorithms */
  
 -      if(cipher) {
 -              from->outcipher = EVP_get_cipherbynid(cipher);
 -
 -              if(!from->outcipher) {
 -                      logger(LOG_ERR, "Node %s (%s) uses unknown cipher!", from->name,
 -                                 from->hostname);
 -                      return true;
 -              }
 -
 -              if(from->outkeylength != from->outcipher->key_len + from->outcipher->iv_len) {
 -                      logger(LOG_ERR, "Node %s (%s) uses wrong keylength!", from->name,
 -                                 from->hostname);
 -                      return true;
 -              }
 -      } else {
 -              from->outcipher = NULL;
 +      if(!cipher_open_by_nid(&from->outcipher, cipher)) {
 +              logger(LOG_ERR, "Node %s (%s) uses unknown cipher!", from->name, from->hostname);
 +              return false;
        }
  
 -      from->outmaclength = maclength;
 +      keylen = strlen(key) / 2;
  
 -      if(digest) {
 -              from->outdigest = EVP_get_digestbynid(digest);
 +      if(keylen != cipher_keylength(&from->outcipher)) {
 +              logger(LOG_ERR, "Node %s (%s) uses wrong keylength!", from->name, from->hostname);
 +              return false;
 +      }
  
 -              if(!from->outdigest) {
 -                      logger(LOG_ERR, "Node %s (%s) uses unknown digest!", from->name,
 -                                 from->hostname);
 -                      return true;
 -              }
 +      if(!digest_open_by_nid(&from->outdigest, digest, maclength)) {
 +              logger(LOG_ERR, "Node %s (%s) uses unknown digest!", from->name, from->hostname);
 +              return false;
 +      }
  
 -              if(from->outmaclength > from->outdigest->md_size || from->outmaclength < 0) {
 -                      logger(LOG_ERR, "Node %s (%s) uses bogus MAC length!",
 -                                 from->name, from->hostname);
 -                      return true;
 -              }
 -      } else {
 -              from->outdigest = NULL;
 +      if(maclength != digest_length(&from->outdigest)) {
 +              logger(LOG_ERR, "Node %s (%s) uses bogus MAC length!", from->name, from->hostname);
 +              return false;
        }
  
        if(compression < 0 || compression > 11) {
                logger(LOG_ERR, "Node %s (%s) uses bogus compression level!", from->name, from->hostname);
-               return false;
+               return true;
        }
        
        from->outcompression = compression;
  
 -      if(from->outcipher)
 -              if(!EVP_EncryptInit_ex(&from->outctx, from->outcipher, NULL, (unsigned char *)from->outkey, (unsigned char *)from->outkey + from->outcipher->key_len)) {
 -                      logger(LOG_ERR, "Error during initialisation of key from %s (%s): %s",
 -                                      from->name, from->hostname, ERR_error_string(ERR_get_error(), NULL));
 -                      return true;
 -              }
 +      /* Update our copy of the origin's packet key */
 +
 +      hex2bin(key, key, keylen);
 +      cipher_set_key(&from->outcipher, key, false);
 +      digest_set_key(&from->outdigest, key, keylen);
  
        from->status.validkey = true;
 +      from->status.waitingforkey = false;
        from->sent_seqno = 0;
  
+       if(*address && *port) {
+               ifdebug(PROTOCOL) logger(LOG_DEBUG, "Using reflexive UDP address from %s: %s port %s", from->name, address, port);
+               sockaddr_t sa = str2sockaddr(address, port);
+               update_node_udp(from, &sa);
+       }
        if(from->options & OPTION_PMTU_DISCOVERY && !from->mtuprobes)
                send_mtu_probe(from);
  
diff --combined src/protocol_subnet.c
@@@ -41,13 -41,13 +41,13 @@@ bool send_add_subnet(connection_t *c, c
        return send_request(c, "%d %x %s %s", ADD_SUBNET, rand(), subnet->owner->name, netstr);
  }
  
 -bool add_subnet_h(connection_t *c) {
 +bool add_subnet_h(connection_t *c, char *request) {
        char subnetstr[MAX_STRING_SIZE];
        char name[MAX_STRING_SIZE];
        node_t *owner;
        subnet_t s = {0}, *new, *old;
  
 -      if(sscanf(c->buffer, "%*d %*x " MAX_STRING " " MAX_STRING, name, subnetstr) != 2) {
 +      if(sscanf(request, "%*d %*x " MAX_STRING " " MAX_STRING, name, subnetstr) != 2) {
                logger(LOG_ERR, "Got bad %s from %s (%s)", "ADD_SUBNET", c->name,
                           c->hostname);
                return false;
@@@ -69,7 -69,7 +69,7 @@@
                return false;
        }
  
 -      if(seen_request(c->buffer))
 +      if(seen_request(request))
                return true;
  
        /* Check if the owner of the new subnet is in the connection list */
                return true;
        }
  
-       /* In tunnel server mode, check if the subnet matches one in the config file of this node */
+       /* In tunnel server mode, we should already know all allowed subnets */
  
        if(tunnelserver) {
-               config_t *cfg;
-               subnet_t *allowed;
-               for(cfg = lookup_config(c->config_tree, "Subnet"); cfg; cfg = lookup_config_next(c->config_tree, cfg)) {
-                       if(!get_config_subnet(cfg, &allowed))
-                               continue;
-                       if(!subnet_compare(&s, allowed))
-                               break;
-                       free_subnet(allowed);
-               }
+               logger(LOG_WARNING, "Ignoring unauthorized %s from %s (%s): %s",
+                               "ADD_SUBNET", c->name, c->hostname, subnetstr);
+               return true;
+       }
  
-               if(!cfg) {
-                       logger(LOG_WARNING, "Ignoring unauthorized %s from %s (%s): %s",
-                                       "ADD_SUBNET", c->name, c->hostname, subnetstr);
-                       return true;
-               }
+       /* Ignore if strictsubnets is true, but forward it to others */
  
-               free_subnet(allowed);
+       if(strictsubnets) {
+               logger(LOG_WARNING, "Ignoring unauthorized %s from %s (%s): %s",
+                               "ADD_SUBNET", c->name, c->hostname, subnetstr);
+               forward_request(c);
+               return true;
        }
  
        /* If everything is correct, add the subnet to the list of the owner */
  
        /* Tell the rest */
  
 -      forward_request(c);
 +      if(!tunnelserver)
 +              forward_request(c, request);
  
        /* Fast handoff of roaming MAC addresses */
  
        if(s.type == SUBNET_MAC && owner != myself && (old = lookup_subnet(myself, &s)) && old->expires)
 -              old->expires = now;
 +              old->expires = 1;
  
        return true;
  }
@@@ -159,13 -150,13 +151,13 @@@ bool send_del_subnet(connection_t *c, c
        return send_request(c, "%d %x %s %s", DEL_SUBNET, rand(), s->owner->name, netstr);
  }
  
 -bool del_subnet_h(connection_t *c) {
 +bool del_subnet_h(connection_t *c, char *request) {
        char subnetstr[MAX_STRING_SIZE];
        char name[MAX_STRING_SIZE];
        node_t *owner;
        subnet_t s = {0}, *find;
  
 -      if(sscanf(c->buffer, "%*d %*x " MAX_STRING " " MAX_STRING, name, subnetstr) != 2) {
 +      if(sscanf(request, "%*d %*x " MAX_STRING " " MAX_STRING, name, subnetstr) != 2) {
                logger(LOG_ERR, "Got bad %s from %s (%s)", "DEL_SUBNET", c->name,
                           c->hostname);
                return false;
                return false;
        }
  
 -      if(seen_request(c->buffer))
 +      if(seen_request(request))
                return true;
  
        /* Check if the owner of the subnet being deleted is in the connection list */
        if(!find) {
                ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for %s which does not appear in his subnet tree",
                                   "DEL_SUBNET", c->name, c->hostname, name);
+               if(strictsubnets)
+                       forward_request(c);
                return true;
        }
  
                return true;
        }
  
+       if(tunnelserver)
+               return true;
        /* Tell the rest */
  
 -      forward_request(c);
 +      if(!tunnelserver)
 +              forward_request(c, request);
+       if(strictsubnets)
+               return true;
  
        /* Finally, delete it. */
  
diff --combined src/route.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      route.c -- routing
      Copyright (C) 2000-2005 Ivo Timmermans,
-                   2000-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2010 Guus Sliepen <guus@tinc-vpn.org>
  
      This program is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
@@@ -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"
@@@ -33,6 -33,8 +33,8 @@@
  #include "utils.h"
  
  rmode_t routing_mode = RMODE_ROUTER;
+ fmode_t forwarding_mode = FMODE_INTERNAL;
+ bool directonly = false;
  bool priorityinheritance = false;
  int macexpire = 600;
  bool overwrite_mac = false;
@@@ -48,9 -50,8 +50,10 @@@ 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))
  
 +static struct event age_subnets_event;
 +
  /* RFC 1071 */
  
  static uint16_t inet_checksum(void *data, int len, uint16_t prevsum) {
@@@ -74,7 -75,6 +77,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)
@@@ -95,6 -95,78 +98,78 @@@ static bool checklength(node_t *source
                return true;
  }
  
+ static void clamp_mss(const node_t *source, const node_t *via, vpn_packet_t *packet) {
+       if(!source || !via || !(via->options & OPTION_CLAMP_MSS))
+               return;
+       uint16_t mtu = source->mtu;
+       if(via != myself && via->mtu < mtu)
+               mtu = via->mtu;
+       /* Find TCP header */
+       int start = 0;
+       uint16_t type = packet->data[12] << 8 | packet->data[13];
+       if(type == ETH_P_IP && packet->data[23] == 6)
+               start = 14 + (packet->data[14] & 0xf) * 4;
+       else if(type == ETH_P_IPV6 && packet->data[20] == 6)
+               start = 14 + 40;
+       if(!start || packet->len <= start + 20)
+               return;
+       /* Use data offset field to calculate length of options field */
+       int len = ((packet->data[start + 12] >> 4) - 5) * 4;
+       if(packet->len < start + 20 + len)
+               return;
+       /* Search for MSS option header */
+       for(int i = 0; i < len;) {
+               if(packet->data[start + 20 + i] == 0)
+                       break;
+               if(packet->data[start + 20 + i] == 1) {
+                       i++;
+                       continue;
+               }
+               if(i > len - 2 || i > len - packet->data[start + 21 + i])
+                       break;
+               if(packet->data[start + 20 + i] != 2) {
+                       if(packet->data[start + 21 + i] < 2)
+                               break;
+                       i += packet->data[start + 21 + i];
+                       continue;
+               }
+               if(packet->data[start + 21] != 4)
+                       break;
+               /* Found it */
+               uint16_t oldmss = packet->data[start + 22 + i] << 8 | packet->data[start + 23 + i];
+               uint16_t newmss = mtu - start - 20;
+               uint16_t csum = packet->data[start + 16] << 8 | packet->data[start + 17];
+               if(oldmss <= newmss)
+                       break;
+               
+               ifdebug(TRAFFIC) logger(LOG_INFO, "Clamping MSS of packet from %s to %s to %d", source->name, via->name, newmss);
+               /* Update the MSS value and the checksum */
+               packet->data[start + 22 + i] = newmss >> 8;
+               packet->data[start + 23 + i] = newmss & 0xff;
+               csum ^= 0xffff;
+               csum -= oldmss;
+               csum += newmss;
+               csum ^= 0xffff;
+               packet->data[start + 16] = csum >> 8;
+               packet->data[start + 17] = csum & 0xff;
+               break;
+       }
+ }
  static void swap_mac_addresses(vpn_packet_t *packet) {
        mac_t tmp;
        memcpy(&tmp, &packet->data[0], sizeof tmp);
        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);
+               subnet_update(myself, subnet, true);
  
                /* And tell all other tinc daemons it's our MAC */
  
                        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;
        }
  }
  
@@@ -323,17 -385,23 +399,23 @@@ static void route_ipv4_unicast(node_t *
        }
  
        if(!subnet->owner->status.reachable)
-               route_ipv4_unreachable(source, packet, ICMP_DEST_UNREACH, ICMP_NET_UNREACH);
+               return route_ipv4_unreachable(source, packet, ICMP_DEST_UNREACH, ICMP_NET_UNREACH);
+       if(forwarding_mode == FMODE_OFF && source != myself && subnet->owner != myself)
+               return route_ipv4_unreachable(source, packet, ICMP_DEST_UNREACH, ICMP_NET_ANO);
  
        if(priorityinheritance)
                packet->priority = packet->data[15];
  
        via = (subnet->owner->via == myself) ? subnet->owner->nexthop : subnet->owner->via;
        
-       if(via && packet->len > via->mtu && via != myself) {
+       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) {
                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 = via->mtu;
+                       packet->len = max(via->mtu, 590);
                        route_ipv4_unreachable(source, packet, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED);
                } else {
                        fragment_ipv4_packet(via, packet);
                return;
        }
  
+       clamp_mss(source, via, packet);
+  
        send_packet(subnet->owner, packet);
  }
  
@@@ -423,7 -493,7 +507,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);
  
@@@ -469,17 -539,25 +553,25 @@@ static void route_ipv6_unicast(node_t *
        }
  
        if(!subnet->owner->status.reachable)
-               route_ipv6_unreachable(source, packet, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE);
+               return route_ipv6_unreachable(source, packet, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE);
+       if(forwarding_mode == FMODE_OFF && source != myself && subnet->owner != myself)
+               return route_ipv6_unreachable(source, packet, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN);
  
        via = (subnet->owner->via == myself) ? subnet->owner->nexthop : subnet->owner->via;
        
-       if(via && packet->len > via->mtu && via != myself) {
+       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) {
                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 = via->mtu;
+               packet->len = max(via->mtu, 1294);
                route_ipv6_unreachable(source, packet, ICMP6_PACKET_TOO_BIG, 0);
                return;
        }
  
+       clamp_mss(source, via, packet);
+  
        send_packet(subnet->owner, packet);
  }
  
@@@ -542,7 -620,7 +634,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);
@@@ -666,7 -744,7 +758,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 */
@@@ -732,14 -810,20 +824,20 @@@ static void route_mac(node_t *source, v
                return;
        }
  
+       if(forwarding_mode == FMODE_OFF && source != myself && subnet->owner != myself)
+               return;
        // Handle packets larger than PMTU
  
        node_t *via = (subnet->owner->via == myself) ? subnet->owner->nexthop : subnet->owner->via;
+       if(directonly && subnet->owner != via)
+               return;
        
        if(via && packet->len > via->mtu && 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);
                uint16_t type = packet->data[12] << 8 | packet->data[13];
-               if(type == ETH_P_IP) {
+               if(type == ETH_P_IP && packet->len > 590) {
                        if(packet->data[20] & 0x40) {
                                packet->len = via->mtu;
                                route_ipv4_unreachable(source, packet, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED);
                                fragment_ipv4_packet(via, packet);
                        }
                        return;
-               } else if(type == ETH_P_IPV6) {
+               } else if(type == ETH_P_IPV6 && packet->len > 1294) {
                        packet->len = via->mtu;
                        route_ipv6_unreachable(source, packet, ICMP6_PACKET_TOO_BIG, 0);
                        return;
                }
        }
  
+       clamp_mss(source, via, packet);
+  
        send_packet(subnet->owner, packet);
  }
  
  void route(node_t *source, vpn_packet_t *packet) {
+       if(forwarding_mode == FMODE_KERNEL && source != myself) {
+               send_packet(myself, packet);
+               return;
+       }
        if(!checklength(source, packet, ether_size))
                return;
  
diff --combined src/route.h
@@@ -30,13 -30,22 +30,21 @@@ typedef enum rmode_t 
        RMODE_ROUTER,
  } rmode_t;
  
+ typedef enum fmode_t {
+       FMODE_OFF = 0,
+       FMODE_INTERNAL,
+       FMODE_KERNEL,
+ } fmode_t;
  extern rmode_t routing_mode;
+ extern fmode_t forwarding_mode;
+ extern bool directonly;
  extern bool overwrite_mac;
  extern bool priorityinheritance;
  extern int macexpire;
  
  extern mac_t mymac;
  
 -extern void age_subnets(void);
  extern void route(struct node_t *, struct vpn_packet_t *);
  
  #endif                                                        /* __TINC_ROUTE_H__ */
diff --combined src/subnet.c
@@@ -1,6 -1,6 +1,6 @@@
  /*
      subnet.c -- handle subnet lookups and lists
-     Copyright (C) 2000-2009 Guus Sliepen <guus@tinc-vpn.org>,
+     Copyright (C) 2000-2010 Guus Sliepen <guus@tinc-vpn.org>,
                    2000-2005 Ivo Timmermans
  
      This program is free software; you can redistribute it and/or modify
@@@ -20,8 -20,7 +20,8 @@@
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
 +#include "control_common.h"
  #include "device.h"
  #include "logger.h"
  #include "net.h"
@@@ -34,7 -33,7 +34,7 @@@
  
  /* lists type of subnet */
  
 -avl_tree_t *subnet_tree;
 +splay_tree_t *subnet_tree;
  
  /* Subnet lookup cache */
  
@@@ -64,7 -63,7 +64,7 @@@ void subnet_cache_flush() 
  static int subnet_compare_mac(const subnet_t *a, const subnet_t *b) {
        int result;
  
 -      result = memcmp(&a->net.mac.address, &b->net.mac.address, sizeof(mac_t));
 +      result = memcmp(&a->net.mac.address, &b->net.mac.address, sizeof a->net.mac.address);
  
        if(result)
                return result;
@@@ -146,21 -145,21 +146,21 @@@ int subnet_compare(const subnet_t *a, c
  /* Initialising trees */
  
  void init_subnets(void) {
 -      subnet_tree = avl_alloc_tree((avl_compare_t) subnet_compare, (avl_action_t) free_subnet);
 +      subnet_tree = splay_alloc_tree((splay_compare_t) subnet_compare, (splay_action_t) free_subnet);
  
        subnet_cache_flush();
  }
  
  void exit_subnets(void) {
 -      avl_delete_tree(subnet_tree);
 +      splay_delete_tree(subnet_tree);
  }
  
 -avl_tree_t *new_subnet_tree(void) {
 -      return avl_alloc_tree((avl_compare_t) subnet_compare, NULL);
 +splay_tree_t *new_subnet_tree(void) {
 +      return splay_alloc_tree((splay_compare_t) subnet_compare, NULL);
  }
  
 -void free_subnet_tree(avl_tree_t *subnet_tree) {
 -      avl_delete_tree(subnet_tree);
 +void free_subnet_tree(splay_tree_t *subnet_tree) {
 +      splay_delete_tree(subnet_tree);
  }
  
  /* Allocating and freeing space for subnets */
@@@ -178,15 -177,15 +178,15 @@@ void free_subnet(subnet_t *subnet) 
  void subnet_add(node_t *n, subnet_t *subnet) {
        subnet->owner = n;
  
 -      avl_insert(subnet_tree, subnet);
 -      avl_insert(n->subnet_tree, subnet);
 +      splay_insert(subnet_tree, subnet);
 +      splay_insert(n->subnet_tree, subnet);
  
        subnet_cache_flush();
  }
  
  void subnet_del(node_t *n, subnet_t *subnet) {
 -      avl_delete(n->subnet_tree, subnet);
 -      avl_delete(subnet_tree, subnet);
 +      splay_delete(n->subnet_tree, subnet);
 +      splay_delete(subnet_tree, subnet);
  
        subnet_cache_flush();
  }
@@@ -274,7 -273,7 +274,7 @@@ bool str2net(subnet_t *subnet, const ch
  
  bool net2str(char *netstr, int len, const subnet_t *subnet) {
        if(!netstr || !subnet) {
 -              logger(LOG_ERR, "net2str() was called with netstr=%p, subnet=%p!\n", netstr, subnet);
 +              logger(LOG_ERR, "net2str() was called with netstr=%p, subnet=%p!", netstr, subnet);
                return false;
        }
  
  /* Subnet lookup routines */
  
  subnet_t *lookup_subnet(const node_t *owner, const subnet_t *subnet) {
 -      return avl_search(owner->subnet_tree, subnet);
 +      return splay_search(owner->subnet_tree, subnet);
  }
  
  subnet_t *lookup_subnet_mac(const node_t *owner, const mac_t *address) {
        subnet_t *p, *r = NULL;
 -      avl_node_t *n;
 +      splay_node_t *n;
        int i;
  
        // Check if this address is cached
  
  subnet_t *lookup_subnet_ipv4(const ipv4_t *address) {
        subnet_t *p, *r = NULL;
 -      avl_node_t *n;
 +      splay_node_t *n;
        int i;
  
        // Check if this address is cached
  
  subnet_t *lookup_subnet_ipv6(const ipv6_t *address) {
        subnet_t *p, *r = NULL;
 -      avl_node_t *n;
 +      splay_node_t *n;
        int i;
  
        // Check if this address is cached
  }
  
  void subnet_update(node_t *owner, subnet_t *subnet, bool up) {
 -      avl_node_t *node;
 +      splay_node_t *node;
        int i;
        char *envp[9] = {0};
        char netstr[MAXNETSTR];
                        if(!net2str(netstr, sizeof netstr, subnet))
                                continue;
                        // Strip the weight from the subnet, and put it in its own environment variable
-                       char *weight = strchr(netstr + 7, '#');
+                       char *weight = strchr(netstr, '#');
                        if(weight)
                                *weight++ = 0;
                        else
                        execute_script(name, envp);
                }
        } else {
-               if(net2str(netstr + 7, sizeof netstr - 7, subnet)) {
+               if(net2str(netstr, sizeof netstr, subnet)) {
                        // Strip the weight from the subnet, and put it in its own environment variable
-                       char *weight = strchr(netstr + 7, '#');
+                       char *weight = strchr(netstr, '#');
                        if(weight)
                                *weight++ = 0;
                        else
                free(envp[i]);
  }
  
 -void dump_subnets(void) {
 +bool dump_subnets(connection_t *c) {
        char netstr[MAXNETSTR];
        subnet_t *subnet;
 -      avl_node_t *node;
 -
 -      logger(LOG_DEBUG, "Subnet list:");
 +      splay_node_t *node;
  
        for(node = subnet_tree->head; node; node = node->next) {
                subnet = node->data;
                if(!net2str(netstr, sizeof netstr, subnet))
                        continue;
 -              logger(LOG_DEBUG, " %s owner %s", netstr, subnet->owner->name);
 +              send_request(c, "%d %d %s owner %s",
 +                              CONTROL, REQ_DUMP_SUBNETS,
 +                              netstr, subnet->owner->name);
        }
  
 -      logger(LOG_DEBUG, "End of subnet list.");
 +      return send_request(c, "%d %d", CONTROL, REQ_DUMP_SUBNETS);
  }
diff --combined src/tincd.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      tincd.c -- the main file for tincd
      Copyright (C) 1998-2005 Ivo Timmermans
-                   2000-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2010 Guus Sliepen <guus@tinc-vpn.org>
                    2008      Max Rijevski <maksuf@gmail.com>
                    2009      Michael Tokarev <mjt@tls.msk.ru>
  
  #include <sys/mman.h>
  #endif
  
+ #include <openssl/rand.h>
+ #include <openssl/rsa.h>
+ #include <openssl/pem.h>
+ #include <openssl/evp.h>
+ #include <openssl/engine.h>
+ #ifdef HAVE_LZO
  #include LZO1X_H
+ #endif
  
  #ifndef HAVE_MINGW
  #include <pwd.h>
  #endif
  
  #include <getopt.h>
 -#include "pidfile.h"
  
  #include "conf.h"
 +#include "control.h"
 +#include "crypto.h"
  #include "device.h"
  #include "logger.h"
  #include "net.h"
@@@ -62,6 -69,12 +70,6 @@@ bool show_help = false
  /* If nonzero, print the version on standard output and exit.  */
  bool show_version = false;
  
 -/* If nonzero, it will attempt to kill a running tincd and exit. */
 -int kill_tincd = 0;
 -
 -/* If nonzero, generate public/private keypair for this host/net. */
 -int generate_keys = 0;
 -
  /* If nonzero, use null ciphers and skip all key exchanges. */
  bool bypass_security = false;
  
@@@ -78,25 -91,27 +86,25 @@@ static const char *switchuser = NULL
  bool use_logfile = false;
  
  char *identname = NULL;                               /* program name for syslog */
 -char *pidfilename = NULL;                     /* pid file location */
  char *logfilename = NULL;                     /* log file location */
 +char *controlcookiename = NULL;
  char **g_argv;                                        /* a copy of the cmdline arguments */
  
  static int status;
  
  static struct option const long_options[] = {
        {"config", required_argument, NULL, 'c'},
 -      {"kill", optional_argument, NULL, 'k'},
        {"net", required_argument, NULL, 'n'},
        {"help", no_argument, NULL, 1},
        {"version", no_argument, NULL, 2},
        {"no-detach", no_argument, NULL, 'D'},
 -      {"generate-keys", optional_argument, NULL, 'K'},
        {"debug", optional_argument, NULL, 'd'},
        {"bypass-security", no_argument, NULL, 3},
        {"mlock", no_argument, NULL, 'L'},
        {"chroot", no_argument, NULL, 'R'},
        {"user", required_argument, NULL, 'U'},
        {"logfile", optional_argument, NULL, 4},
 -      {"pidfile", required_argument, NULL, 5},
 +      {"controlcookie", required_argument, NULL, 5},
        {NULL, 0, NULL, 0}
  };
  
@@@ -111,17 -126,19 +119,17 @@@ static void usage(bool status) 
                                program_name);
        else {
                printf("Usage: %s [option]...\n\n", program_name);
 -              printf("  -c, --config=DIR           Read configuration options from DIR.\n"
 -                              "  -D, --no-detach            Don't fork and detach.\n"
 -                              "  -d, --debug[=LEVEL]        Increase debug level or set it to LEVEL.\n"
 -                              "  -k, --kill[=SIGNAL]        Attempt to kill a running tincd and exit.\n"
 -                              "  -n, --net=NETNAME          Connect to net NETNAME.\n"
 -                              "  -K, --generate-keys[=BITS] Generate public/private RSA keypair.\n"
 -                              "  -L, --mlock                Lock tinc into main memory.\n"
 -                              "      --logfile[=FILENAME]   Write log entries to a logfile.\n"
 -                              "      --pidfile=FILENAME     Write PID to FILENAME.\n"
 -                              "  -R, --chroot               chroot to NET dir at startup.\n"
 -                              "  -U, --user=USER            setuid to given USER at startup.\n"
 -                              "      --help                 Display this help and exit.\n"
 -                              "      --version              Output version information and exit.\n\n");
 +              printf( "  -c, --config=DIR              Read configuration options from DIR.\n"
 +                              "  -D, --no-detach               Don't fork and detach.\n"
 +                              "  -d, --debug[=LEVEL]           Increase debug level or set it to LEVEL.\n"
 +                              "  -n, --net=NETNAME             Connect to net NETNAME.\n"
 +                              "  -L, --mlock                   Lock tinc into main memory.\n"
 +                              "      --logfile[=FILENAME]      Write log entries to a logfile.\n"
 +                              "      --controlcookie=FILENAME  Write control socket cookie to FILENAME.\n"
 +                              "      --bypass-security         Disables meta protocol security, for debugging.\n"
 +                              "  -R, --chroot                  chroot to NET dir at startup.\n"
 +                              "  -U, --user=USER               setuid to given USER at startup.\n"                            "      --help                    Display this help and exit.\n"
 +                              "      --version                 Output version information and exit.\n\n");
                printf("Report bugs to tinc@tinc-vpn.org.\n");
        }
  }
@@@ -130,7 -147,7 +138,7 @@@ static bool parse_options(int argc, cha
        int r;
        int option_index = 0;
  
 -      while((r = getopt_long(argc, argv, "c:DLd::k::n:K::RU:", long_options, &option_index)) != EOF) {
 +      while((r = getopt_long(argc, argv, "c:DLd::n:RU:", long_options, &option_index)) != EOF) {
                switch (r) {
                        case 0:                         /* long option */
                                break;
                                        debug_level++;
                                break;
  
 -                      case 'k':                               /* kill old tincds */
 -#ifndef HAVE_MINGW
 -                              if(optarg) {
 -                                      if(!strcasecmp(optarg, "HUP"))
 -                                              kill_tincd = SIGHUP;
 -                                      else if(!strcasecmp(optarg, "TERM"))
 -                                              kill_tincd = SIGTERM;
 -                                      else if(!strcasecmp(optarg, "KILL"))
 -                                              kill_tincd = SIGKILL;
 -                                      else if(!strcasecmp(optarg, "USR1"))
 -                                              kill_tincd = SIGUSR1;
 -                                      else if(!strcasecmp(optarg, "USR2"))
 -                                              kill_tincd = SIGUSR2;
 -                                      else if(!strcasecmp(optarg, "WINCH"))
 -                                              kill_tincd = SIGWINCH;
 -                                      else if(!strcasecmp(optarg, "INT"))
 -                                              kill_tincd = SIGINT;
 -                                      else if(!strcasecmp(optarg, "ALRM"))
 -                                              kill_tincd = SIGALRM;
 -                                      else {
 -                                              kill_tincd = atoi(optarg);
 -
 -                                              if(!kill_tincd) {
 -                                                      fprintf(stderr, "Invalid argument `%s'; SIGNAL must be a number or one of HUP, TERM, KILL, USR1, USR2, WINCH, INT or ALRM.\n",
 -                                                                      optarg);
 -                                                      usage(true);
 -                                                      return false;
 -                                              }
 -                                      }
 -                              } else
 -                                      kill_tincd = SIGTERM;
 -#else
 -                                      kill_tincd = 1;
 -#endif
 -                              break;
 -
                        case 'n':                               /* net name given */
                                netname = xstrdup(optarg);
                                break;
  
 -                      case 'K':                               /* generate public/private keypair */
 -                              if(optarg) {
 -                                      generate_keys = atoi(optarg);
 -
 -                                      if(generate_keys < 512) {
 -                                              fprintf(stderr, "Invalid argument `%s'; BITS must be a number equal to or greater than 512.\n",
 -                                                              optarg);
 -                                              usage(true);
 -                                              return false;
 -                                      }
 -
 -                                      generate_keys &= ~7;    /* Round it to bytes */
 -                              } else
 -                                      generate_keys = 2048;
 -                              break;
 -
                        case 'R':                               /* chroot to NETNAME dir */
                                do_chroot = true;
                                break;
                                        logfilename = xstrdup(optarg);
                                break;
  
 -                      case 5:                                 /* write PID to a file */
 -                              pidfilename = xstrdup(optarg);
 +                      case 5:                                 /* open control socket here */
 +                              controlcookiename = xstrdup(optarg);
                                break;
  
                        case '?':
        return true;
  }
  
 -/* This function prettyprints the key generation process */
 -
 -static void indicator(int a, int b, void *p) {
 -      switch (a) {
 -              case 0:
 -                      fprintf(stderr, ".");
 -                      break;
 -
 -              case 1:
 -                      fprintf(stderr, "+");
 -                      break;
 -
 -              case 2:
 -                      fprintf(stderr, "-");
 -                      break;
 -
 -              case 3:
 -                      switch (b) {
 -                              case 0:
 -                                      fprintf(stderr, " p\n");
 -                                      break;
 -
 -                              case 1:
 -                                      fprintf(stderr, " q\n");
 -                                      break;
 -
 -                              default:
 -                                      fprintf(stderr, "?");
 -                      }
 -                      break;
 -
 -              default:
 -                      fprintf(stderr, "?");
 -      }
 -}
 -
 -/*
 -  Generate a public/private RSA keypair, and ask for a file to store
 -  them in.
 -*/
 -static bool keygen(int bits) {
 -      RSA *rsa_key;
 -      FILE *f;
 -      char *name = NULL;
 -      char *filename;
 -
 -      get_config_string(lookup_config(config_tree, "Name"), &name);
 -
 -      if(name && !check_id(name)) {
 -              fprintf(stderr, "Invalid name for myself!\n");
 -              return false;
 -      }
 -
 -      fprintf(stderr, "Generating %d bits keys:\n", bits);
 -      rsa_key = RSA_generate_key(bits, 0x10001, indicator, NULL);
 -
 -      if(!rsa_key) {
 -              fprintf(stderr, "Error during key generation!\n");
 -              return false;
 -      } else
 -              fprintf(stderr, "Done.\n");
 -
 -      xasprintf(&filename, "%s/rsa_key.priv", confbase);
 -      f = ask_and_open(filename, "private RSA key");
 -
 -      if(!f)
 -              return false;
 -
 -      if(disable_old_keys(f))
 -              fprintf(stderr, "Warning: old key(s) found and disabled.\n");
 -  
 -#ifdef HAVE_FCHMOD
 -      /* Make it unreadable for others. */
 -      fchmod(fileno(f), 0600);
 -#endif
 -              
 -      PEM_write_RSAPrivateKey(f, rsa_key, NULL, NULL, 0, NULL, NULL);
 -      fclose(f);
 -      free(filename);
 -
 -      if(name)
 -              xasprintf(&filename, "%s/hosts/%s", confbase, name);
 -      else
 -              xasprintf(&filename, "%s/rsa_key.pub", confbase);
 -
 -      f = ask_and_open(filename, "public RSA key");
 -
 -      if(!f)
 -              return false;
 -
 -      if(disable_old_keys(f))
 -              fprintf(stderr, "Warning: old key(s) found and disabled.\n");
 -
 -      PEM_write_RSAPublicKey(f, rsa_key);
 -      fclose(f);
 -      free(filename);
 -      if(name)
 -              free(name);
 -
 -      return true;
 -}
 -
  /*
    Set all files and paths according to netname
  */
@@@ -212,7 -383,7 +220,7 @@@ static void make_names(void) 
  #ifdef HAVE_MINGW
        HKEY key;
        char installdir[1024] = "";
 -      long len = sizeof(installdir);
 +      long len = sizeof installdir;
  #endif
  
        if(netname)
                                else
                                        xasprintf(&confbase, "%s", installdir);
                        }
 +                      if(!controlcookiename)
 +                              xasprintf(&controlcookiename, "%s/cookie", confbase);
                }
                RegCloseKey(key);
                if(*installdir)
        }
  #endif
  
 -      if(!pidfilename)
 -              xasprintf(&pidfilename, LOCALSTATEDIR "/run/%s.pid", identname);
 -
        if(!logfilename)
                xasprintf(&logfilename, LOCALSTATEDIR "/log/%s.log", identname);
  
 +      if(!controlcookiename)
 +              xasprintf(&controlcookiename, LOCALSTATEDIR "/run/%s.cookie", identname);
 +
        if(netname) {
                if(!confbase)
                        xasprintf(&confbase, CONFDIR "/tinc/%s", netname);
  static void free_names() {
        if (identname) free(identname);
        if (netname) free(netname);
 -      if (pidfilename) free(pidfilename);
 +      if (controlcookiename) free(controlcookiename);
        if (logfilename) free(logfilename);
        if (confbase) free(confbase);
  }
@@@ -333,7 -502,7 +341,7 @@@ int main(int argc, char **argv) 
        if(show_version) {
                printf("%s version %s (built %s %s, protocol %d)\n", PACKAGE,
                           VERSION, __DATE__, __TIME__, PROT_CURRENT);
-               printf("Copyright (C) 1998-2009 Ivo Timmermans, Guus Sliepen and others.\n"
+               printf("Copyright (C) 1998-2010 Ivo Timmermans, Guus Sliepen and others.\n"
                                "See the AUTHORS file for a complete list.\n\n"
                                "tinc comes with ABSOLUTELY NO WARRANTY.  This is free software,\n"
                                "and you are welcome to redistribute it under certain conditions;\n"
                return 0;
        }
  
 -      if(kill_tincd)
 -              return !kill_other(kill_tincd);
 +#ifdef HAVE_MINGW
 +      if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) {
 +              logger(LOG_ERR, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError()));
 +              return 1;
 +      }
 +#endif
  
        openlogger("tinc", use_logfile?LOGMODE_FILE:LOGMODE_STDERR);
  
 +      if(!event_init()) {
 +              logger(LOG_ERR, "Error initializing libevent!");
 +              return 1;
 +      }
 +
        g_argv = argv;
  
        init_configuration(&config_tree);
  
        /* Slllluuuuuuurrrrp! */
  
 -      RAND_load_file("/dev/urandom", 1024);
 -
 -      ENGINE_load_builtin_engines();
 -      ENGINE_register_all_complete();
 -
 -      OpenSSL_add_all_algorithms();
 -
 -      if(generate_keys) {
 -              read_server_config();
 -              return !keygen(generate_keys);
 -      }
 +      srand(time(NULL));
 +      crypto_init();
  
        if(!read_server_config())
                return 1;
  
+ #ifdef HAVE_LZO
        if(lzo_init() != LZO_E_OK) {
                logger(LOG_ERR, "Error initializing LZO compressor!");
                return 1;
        }
+ #endif
  
  #ifdef HAVE_MINGW
 -      if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) {
 -              logger(LOG_ERR, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError()));
 -              return 1;
 -      }
 -
        if(!do_detach || !init_service())
                return main2(argc, argv);
        else
@@@ -409,9 -585,6 +419,9 @@@ int main2(int argc, char **argv) 
        if(!setup_network())
                goto end;
  
 +      if(!init_control())
 +              return 1;
 +
        /* Initiate all outgoing connections. */
  
        try_outgoing_connections();
  end:
        logger(LOG_NOTICE, "Terminating");
  
 -#ifndef HAVE_MINGW
 -      remove_pid(pidfilename);
 -#endif
 +      exit_control();
  
 -      EVP_cleanup();
 -      ENGINE_cleanup();
 -      CRYPTO_cleanup_all_ex_data();
 -      ERR_remove_state(0);
 -      ERR_free_strings();
 +      crypto_exit();
  
        exit_configuration(&config_tree);
        free_names();