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

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

diff --combined NEWS
--- 1/NEWS
--- 2/NEWS
+++ b/NEWS
@@@ -1,33 -1,10 +1,40 @@@
 +Version 1.1pre2              Juli 17 2011
 +
 + * .cookie files are renamed to .pid files, which are compatible with 1.0.x.
 +
 + * Experimental protocol enhancements that can be enabled with the option
 +   ExperimentalProtocol = yes:
 +
 +   * Ephemeral ECDH key exchange will be used for both the meta protocol and
 +     UDP session keys.
 +   * Key exchanges are signed with ECDSA.
 +   * ECDSA public keys are automatically exchanged after RSA authentication if
 +     nodes do not know each other's ECDSA public key yet.
 +
 +Version 1.1pre1              June 25 2011
 +
 + * Control interface allows control of a running tinc daemon. Used by:
 +   * tincctl, a commandline utility
 +   * tinc-gui, a preliminary GUI implemented in Python/wxWidgets
 +
 + * Code cleanups and reorganization. 
 +
 + * Repleacable cryptography backend, currently supports OpenSSL and libgcrypt.
 +
 + * Use libevent to handle I/O events and timeouts.
 +
 + * Use splay trees instead of AVL trees to manage internal datastructures.
 +
 + Thanks to Scott Lamb and Sven-Haegar Koch for their contributions to this
 + version of tinc.
 +
+ Version 1.0.16               July 23 2011
+  * Fixed a performance issue with TCP communication under Windows.
+  * Fixed code that, during network outages, would cause tinc to exit when it
+    thought two nodes with identical Names were on the VPN.
  Version 1.0.15               June 24 2011
  
   * Improved logging to file.
@@@ -36,8 -13,6 +43,8 @@@
  
   * Fixed ProcessPriority option under Windows.
  
 +  Thanks to Loïc Grenié for his contribution to this version of tinc.
 +
  Version 1.0.14               May  8 2011
  
   * Fixed reading configuration files that do not end with a newline. Again.
diff --combined THANKS
--- 1/THANKS
--- 2/THANKS
+++ b/THANKS
@@@ -9,7 -9,6 +9,7 @@@ We would like to thank the following pe
  * Delf Eldkraft
  * dnk
  * Enrique Zanardi
 +* Erik Tews
  * Flynn Marquardt
  * Grzegorz Dymarek
  * Hans Bayle
@@@ -32,6 -31,7 +32,7 @@@
  * Menno Smits
  * Michael Tokarev
  * Miles Nordin
+ * Nick Hibma
  * Nick Patavalis
  * Paul Littlefield
  * Robert van der Meulen
diff --combined configure.in
@@@ -3,8 -3,7 +3,8 @@@ dnl Process this file with autoconf to 
  AC_PREREQ(2.61)
  AC_INIT
  AC_CONFIG_SRCDIR([src/tincd.c])
 -AM_INIT_AUTOMAKE(tinc, 1.0.16)
 +AC_GNU_SOURCE
 +AM_INIT_AUTOMAKE(tinc, 1.1pre2)
  AC_CONFIG_HEADERS([config.h])
  AM_MAINTAINER_MODE
  
@@@ -73,6 -72,21 +73,21 @@@ case $host_os i
    ;;
  esac
  
+ AC_ARG_ENABLE(uml,
+   AS_HELP_STRING([--enable-uml], [enable support for User Mode Linux]),
+   [ AC_DEFINE(ENABLE_UML, 1, [Support for UML])
+     uml=true
+   ]
+ )
+ AC_ARG_ENABLE(vde,
+   AS_HELP_STRING([--enable-vde], [enable support for Virtual Distributed Ethernet]),
+   [ AC_CHECK_HEADERS(libvdeplug_dyn.h, [], [AC_MSG_ERROR([VDE plug header files not found.]); break])
+     AC_DEFINE(ENABLE_VDE, 1, [Support for VDE])
+     vde=true
+   ]
+ )
  AC_ARG_ENABLE(tunemu,
    AS_HELP_STRING([--enable-tunemu], [enable support for the tunemu driver]),
    [ AC_DEFINE(ENABLE_TUNEMU, 1, [Support for tunemu])
@@@ -85,6 -99,8 +100,8 @@@ AC_ARG_WITH(windows2000
    [AC_DEFINE(WITH_WINDOWS2000, 1, [Compile with support for Windows 2000])]
  )
  
+ AM_CONDITIONAL(UML, test "$uml" = true)
+ AM_CONDITIONAL(VDE, test "$vde" = true)
  AM_CONDITIONAL(TUNEMU, test "$tunemu" = true)
  
  AC_CACHE_SAVE
@@@ -96,12 -112,14 +113,12 @@@ if test -d /sw/lib ; the
    LIBS="$LIBS -L/sw/lib"
  fi
  
 -dnl Checks for libraries.
 -
  dnl Checks for header files.
  dnl We do this in multiple stages, because unlike Linux all the other operating systems really suck and don't include their own dependencies.
  
  AC_HEADER_STDC
 -AC_CHECK_HEADERS([stdbool.h syslog.h sys/file.h sys/ioctl.h sys/mman.h sys/param.h sys/resource.h sys/socket.h sys/time.h time.h sys/uio.h sys/wait.h netdb.h arpa/inet.h dirent.h])
 -AC_CHECK_HEADERS([net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h netpacket/packet.h],
 +AC_CHECK_HEADERS([stdbool.h syslog.h sys/file.h sys/ioctl.h sys/mman.h sys/param.h sys/resource.h sys/socket.h sys/time.h sys/uio.h sys/un.h sys/wait.h netdb.h arpa/inet.h dirent.h])
- AC_CHECK_HEADERS([net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h time.h],
++AC_CHECK_HEADERS([net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h time.h netpacket/packet.h],
    [], [], [#include "have.h"]
  )
  AC_CHECK_HEADERS([netinet/if_ether.h netinet/ip.h netinet/ip6.h],
@@@ -126,10 -144,14 +143,10 @@@ AC_CHECK_TYPES([socklen_t, struct ether
  )
  
  dnl Checks for library functions.
 -AC_FUNC_MEMCMP
 -AC_FUNC_ALLOCA
  AC_TYPE_SIGNAL
 -AC_CHECK_FUNCS([asprintf daemon fchmod flock ftime fork get_current_dir_name gettimeofday mlockall pselect putenv random select strdup strerror strsignal strtol system unsetenv usleep vsyslog writev],
 +AC_CHECK_FUNCS([asprintf daemon fchmod flock ftime fork get_current_dir_name gettimeofday mlockall putenv random select strdup strerror strsignal strtol system time usleep unsetenv vsyslog writev],
    [], [], [#include "have.h"]
  )
 -AC_FUNC_MALLOC
 -AC_FUNC_REALLOC
  
  dnl Support for SunOS
  
@@@ -148,21 -170,9 +165,21 @@@ AC_CACHE_SAV
  
  dnl These are defined in files in m4/
  
 +AC_ARG_WITH(libgcrypt, AC_HELP_STRING([--with-libgcrypt], [enable use of libgcrypt instead of OpenSSL])], [])
 +
 +tinc_CURSES
 +tinc_LIBEVENT
  tinc_ZLIB
  tinc_LZO
 -tinc_OPENSSL
 +
 +if test "$with_libgcrypt" = yes; then
 +      AM_PATH_LIBGCRYPT([1.4.0], [], [])
 +      ln -sf gcrypt/cipher.c gcrypt/cipher.h gcrypt/crypto.c gcrypt/crypto.h gcrypt/digest.c gcrypt/digest.h gcrypt/ecdh.c gcrypt/ecdh.h gcrypt/ecdsa.c gcrypt/ecdsa.h gcrypt/ecdsagen.c gcrypt/ecdsagen.h gcrypt/prf.c gcrypt/prf.h gcrypt/rsa.c gcrypt/rsa.h gcrypt/rsagen.c gcrypt/rsagen.h src/
 +else
 +      tinc_OPENSSL
 +      ln -sf openssl/cipher.c openssl/cipher.h openssl/crypto.c openssl/crypto.h openssl/digest.c openssl/digest.h openssl/ecdh.c openssl/ecdh.h openssl/ecdsa.c openssl/ecdsa.h openssl/ecdsagen.c openssl/ecdsagen.h openssl/prf.c openssl/prf.h openssl/rsa.c openssl/rsa.h openssl/rsagen.c openssl/rsagen.h src/
 +fi
 +      
  
  dnl Check if support for jumbograms is requested 
  AC_ARG_ENABLE(jumbograms,
  
  AC_SUBST(INCLUDES)
  
 -AC_CONFIG_FILES([Makefile src/Makefile doc/Makefile lib/Makefile m4/Makefile])
 +AC_CONFIG_FILES([Makefile src/Makefile doc/Makefile m4/Makefile gui/Makefile])
  
  AC_OUTPUT
diff --combined doc/tinc.conf.5.in
@@@ -133,7 -133,10 +133,10 @@@ IPv6 listening sockets will be created
  If your computer has more than one IPv4 or IPv6 address,
  .Nm tinc
  will by default listen on all of them for incoming connections.
- It is possible to bind only to a single address with this variable.
+ Multiple
+ .Va BindToAddress
+ variables may be specified,
+ in which case listening sockets for each specified address are made.
  
  .Pp
  This option may not work on all platforms.
@@@ -147,6 -150,9 +150,9 @@@ It is possible to bind only to a singl
  .Pp
  This option may not work on all platforms.
  
+ .It Va Broadcast Li = yes | no Po yes Pc Bq experimental
+ When disabled, tinc will drop all broadcast and multicast packets, in both router and switch mode.
  .It Va ConnectTo Li = Ar name
  Specifies which other tinc daemon to connect to on startup.
  Multiple
@@@ -165,6 -171,14 +171,14 @@@ If you don't specify a host wit
  won't try to connect to other daemons at all,
  and will instead just listen for incoming connections.
  
+ .It Va DecrementTTL Li = yes | no Po yes Pc
+ When enabled,
+ .Nm tinc
+ will decrement the Time To Live field in IPv4 packets, or the Hop Limit field in IPv6 packets,
+ before forwarding a received packet to the virtual network device or to another node,
+ and will drop packets that have a TTL value of zero,
+ in which case it will send an ICMP Time Exceeded packet back.
  .It Va Device Li = Ar device Po Pa /dev/tap0 , Pa /dev/net/tun No or other depending on platform Pc
  The virtual network device to use.
  .Nm tinc
@@@ -177,30 -191,65 +191,65 @@@ instead o
  The info pages of the tinc package contain more information
  about configuring the virtual network device.
  
- .It Va DeviceType Li = tun | tunnohead | tunifhead | tap Po only supported on BSD platforms Pc
+ .It Va DeviceType Li = Ar type Pq platform dependent
  The type of the virtual network device.
- Tinc will normally automatically select the right type, and this option should not be used.
- However, in case tinc does not seem to correctly interpret packets received from the virtual network device,
- using this option might help.
+ Tinc will normally automatically select the right type of tun/tap interface, and this option should not be used.
+ However, this option can be used to select one of the special interface types, if support for them is compiled in.
+ .Bl -tag -width indent
+ .It dummy
+ Use a dummy interface.
+ No packets are ever read or written to a virtual network device.
+ Useful for testing, or when setting up a node that only forwards packets for other nodes.
+ .It raw_socket
+ Open a raw socket, and bind it to a pre-existing
+ .Va Interface
+ (eth0 by default).
+ All packets are read from this interface.
+ Packets received for the local node are written to the raw socket.
+ However, at least on Linux, the operating system does not process IP packets destined for the local host.
+ .It uml Pq not compiled in by default
+ Create a UNIX socket with the filename specified by
+ .Va Device ,
+ or
+ .Pa @localstatedir@/run/ Ns Ar NETNAME Ns Pa .umlsocket
+ if not specified.
+ .Nm tinc
+ will wait for a User Mode Linux instance to connect to this socket.
+ .It vde Pq not compiled in by default
+ Uses the libvdeplug library to connect to a Virtual Distributed Ethernet switch,
+ using the UNIX socket specified by
+ .Va Device ,
+ or
+ .Pa @localstatedir@/run/vde.ctl
+ if not specified.
+ .El
+ Also, in case tinc does not seem to correctly interpret packets received from the virtual network device,
+ it can be used to change the way packets are interpreted:
  .Bl -tag -width indent
  
- .It tun
+ .It tun Pq BSD and Linux
  Set type to tun.
  Depending on the platform, this can either be with or without an address family header (see below).
  
- .It tunnohead
+ .It tunnohead Pq BSD
  Set type to tun without an address family header.
  Tinc will expect packets read from the virtual network device to start with an IP header.
  On some platforms IPv6 packets cannot be read from or written to the device in this mode.
  
- .It tunifhead
+ .It tunifhead Pq BSD
  Set type to tun with an address family header.
  Tinc will expect packets read from the virtual network device
  to start with a four byte header containing the address family,
  followed by an IP header.
  This mode should support both IPv4 and IPv6 packets.
  
- .It tap
+ .It tap Pq BSD and Linux
  Set type to tap.
  Tinc will expect packets read from the virtual network device
  to start with an Ethernet header.
@@@ -212,21 -261,6 +261,21 @@@ but which would have to be forwarded b
  When combined with the IndirectData option,
  packets for nodes for which we do not have a meta connection with are also dropped.
  
 +.It Va ECDSAPrivateKeyFile Li = Ar filename Po Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /ecdsa_key.priv Pc
 +The file in which the private ECDSA key of this tinc daemon resides.
 +This is only used if
 +.Va ExperimentalProtocol
 +is enabled.
 +
 +.It Va ExperimentalProtocol Li = yes | no Po no Pc Bq experimental
 +When this option is enabled, experimental protocol enhancements will be used.
 +Ephemeral ECDH will be used for key exchanges,
 +and ECDSA will be used instead of RSA for authentication.
 +When enabled, an ECDSA key must have been generated before with
 +.Nm tincctl generate-ecdsa-keys .
 +The experimental protocol may change at any time,
 +and there is no guarantee that tinc will run stable when it is used.
 +
  .It Va Forwarding Li = off | internal | kernel Po internal Pc Bq experimental
  This option selects the way indirect packets are forwarded.
  .Bl -tag -width indent
diff --combined doc/tinc.texi
@@@ -65,7 -65,6 +65,7 @@@ permission notice identical to this one
  * Installation::
  * Configuration::
  * Running tinc::
 +* Controlling tinc::
  * Technical information::
  * Platform specific information::
  * About us::
@@@ -338,7 -337,6 +338,7 @@@ having them installed, configure will g
  * OpenSSL::
  * zlib::
  * lzo::
 +* libevent::
  @end menu
  
  
@@@ -451,27 -449,6 +451,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
@@@ -782,7 -759,8 +782,8 @@@ both IPv4 and IPv6 or just IPv6 listeni
  @item BindToAddress = <@var{address}> [experimental]
  If your computer has more than one IPv4 or IPv6 address, tinc
  will by default listen on all of them for incoming connections.
- It is possible to bind only to a single address with this variable.
+ Multiple BindToAddress variables may be specified,
+ in which case listening sockets for each specified address are made.
  
  This option may not work on all platforms.
  
@@@ -795,6 -773,10 +796,10 @@@ variable
  
  This option may not work on all platforms.
  
+ @cindex Broadcast
+ @item Broadcast = <yes | no> (yes) [experimental]
+ When disabled, tinc will drop all broadcast and multicast packets, in both router and switch mode.
  @cindex ConnectTo
  @item ConnectTo = <@var{name}>
  Specifies which other tinc daemon to connect to on startup.
@@@ -807,6 -789,13 +812,13 @@@ If you don't specify a host with Connec
  tinc won't try to connect to other daemons at all,
  and will instead just listen for incoming connections.
  
+ @cindex DecrementTTL
+ @item DecrementTTL = <yes | no> (yes)
+ When enabled, tinc will decrement the Time To Live field in IPv4 packets, or the Hop Limit field in IPv6 packets,
+ before forwarding a received packet to the virtual network device or to another node,
+ and will drop packets that have a TTL value of zero,
+ in which case it will send an ICMP Time Exceeded packet back.
  @cindex Device
  @item Device = <@var{device}> (@file{/dev/tap0}, @file{/dev/net/tun} or other depending on platform)
  The virtual network device to use.
@@@ -817,32 -806,64 +829,64 @@@ Note that you can only use one device p
  See also @ref{Device files}.
  
  @cindex DeviceType
- @item DeviceType = <tun|tunnohead|tunifhead|tap> (only supported on BSD platforms)
+ @item DeviceType = <@var{type}> (platform dependent)
  The type of the virtual network device.
- Tinc will normally automatically select the right type, and this option should not be used.
- However, in case tinc does not seem to correctly interpret packets received from the virtual network device,
- using this option might help.
+ Tinc will normally automatically select the right type of tun/tap interface, and this option should not be used.
+ However, this option can be used to select one of the special interface types, if support for them is compiled in.
  
  @table @asis
- @item tun
+ @cindex dummy
+ @item dummy
+ Use a dummy interface.
+ No packets are ever read or written to a virtual network device.
+ Useful for testing, or when setting up a node that only forwards packets for other nodes.
+ @cindex raw_socket
+ @item raw_socket
+ Open a raw socket, and bind it to a pre-existing
+ @var{Interface} (eth0 by default).
+ All packets are read from this interface.
+ Packets received for the local node are written to the raw socket.
+ However, at least on Linux, the operating system does not process IP packets destined for the local host.
+ @cindex UML
+ @item uml (not compiled in by default)
+ Create a UNIX socket with the filename specified by
+ @var{Device}, or @file{@value{localstatedir}/run/@var{netname}.umlsocket}
+ if not specified.
+ Tinc will wait for a User Mode Linux instance to connect to this socket.
+ @cindex VDE
+ @item vde (not compiled in by default)
+ Uses the libvdeplug library to connect to a Virtual Distributed Ethernet switch,
+ using the UNIX socket specified by
+ @var{Device}, or @file{@value{localstatedir}/run/vde.ctl}
+ if not specified.
+ @end table
+ Also, in case tinc does not seem to correctly interpret packets received from the virtual network device,
+ it can be used to change the way packets are interpreted:
+ @table @asis
+ @item tun (BSD and Linux)
  Set type to tun.
  Depending on the platform, this can either be with or without an address family header (see below).
  
  @cindex tunnohead
- @item tunnohead
+ @item tunnohead (BSD)
  Set type to tun without an address family header.
  Tinc will expect packets read from the virtual network device to start with an IP header.
  On some platforms IPv6 packets cannot be read from or written to the device in this mode.
  
  @cindex tunifhead
- @item tunifhead
+ @item tunifhead (BSD)
  Set type to tun with an address family header.
  Tinc will expect packets read from the virtual network device
  to start with a four byte header containing the address family,
  followed by an IP header.
  This mode should support both IPv4 and IPv6 packets.
  
- @item tap
+ @item tap (BSD and Linux)
  Set type to tap.
  Tinc will expect packets read from the virtual network device
  to start with an Ethernet header.
@@@ -855,21 -876,6 +899,21 @@@ but which would have to be forwarded b
  When combined with the IndirectData option,
  packets for nodes for which we do not have a meta connection with are also dropped.
  
 +@cindex ECDSAPrivateKeyFile
 +@item ECDSAPrivateKeyFile = <@var{path}> (@file{@value{sysconfdir}/tinc/@var{netname}/ecdsa_key.priv})
 +The file in which the private ECDSA key of this tinc daemon resides.
 +This is only used if ExperimentalProtocol is enabled.
 +
 +@cindex ExperimentalProtocol
 +@item ExperimentalProtocol = <yes|no> (no) [experimental]
 +When this option is enabled, experimental protocol enhancements will be used.
 +Ephemeral ECDH will be used for key exchanges,
 +and ECDSA will be used instead of RSA for authentication.
 +When enabled, an ECDSA key must have been generated before with
 +@samp{tincctl generate-ecdsa-keys}.
 +The experimental protocol may change at any time,
 +and there is no guarantee that tinc will run stable when it is used.
 +
  @cindex Forwarding
  @item Forwarding = <off|internal|kernel> (internal) [experimental]
  This option selects the way indirect packets are forwarded.
@@@ -988,7 -994,7 +1032,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
@@@ -1110,7 -1116,7 +1154,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
@@@ -1146,6 -1152,7 +1190,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,
@@@ -1153,12 -1160,15 +1197,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
  
  
@@@ -1247,6 -1257,10 +1291,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
  
  
@@@ -1293,7 -1307,7 +1337,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.
@@@ -1522,7 -1536,7 +1566,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},
@@@ -1588,6 -1602,12 +1632,6 @@@ 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}.
  This will let tinc read all configuration files from
  Specifying . for @var{netname} is the same as not specifying any @var{netname}.
  @xref{Multiple networks}.
  
 -@item -K, --generate-keys[=@var{bits}]
 -Generate public/private keypair of @var{bits} length. If @var{bits} is not specified,
 -2048 is the default. tinc will ask where you want to store the files,
 -but will default to the configuration directory (you can use the -c or -n option
 -in combination with -K). After that, tinc will quit.
 +@item --pidfile=@var{filename}
 +Store a cookie in @var{filename} which allows tincctl to authenticate.
 +If unspecified, the default is
 +@file{@value{localstatedir}/run/tinc.@var{netname}.pid}.
  
+ @item -o, --option=[@var{HOST}.]@var{KEY}=@var{VALUE}
+ Without specifying a @var{HOST}, this will set server configuration variable @var{KEY} to @var{VALUE}.
+ If specified as @var{HOST}.@var{KEY}=@var{VALUE},
+ this will set the host configuration variable @var{KEY} of the host named @var{HOST} to @var{VALUE}.
+ This option can be used more than once to specify multiple configuration variables.
  @item -L, --mlock
  Lock tinc into main memory.
  This will prevent sensitive data like shared private keys to be written to the system swap files/partitions.
  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.
@@@ -1661,6 -1691,19 +1711,6 @@@ New outgoing connections specified in @
  If the --logfile option is used, this will also close and reopen the log file,
  useful when log rotation is used.
  
 -@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 ==================================================================
@@@ -1724,7 -1767,7 +1774,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
  
@@@ -1823,8 -1866,6 +1873,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)
@@@ -1855,198 -1896,6 +1905,198 @@@ 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 tincd through the tincctl
 +command. A quick example:
 +
 +@example
 +tincctl -n @var{netname} reload
 +@end example
 +
 +@menu
 +* tincctl runtime options::
 +* tincctl commands::
 +* tincctl examples::
 +* tincctl top::
 +@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 --pidfile=@var{filename}
 +Use the cookie from @var{filename} to authenticate with a running tinc daemon.
 +If unspecified, the default is
 +@file{@value{localstatedir}/run/tinc.@var{netname}.pid}.
 +
 +@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.
 +
 +@item disconnect @var{node}
 +Closes the meta connection with the given @var{node}.
 +
 +@item top
 +If tincctl is compiled with libcurses support, this will display live traffic statistics for all the known nodes,
 +similar to the UNIX top command.
 +See below for more information.
 +
 +@item pcap
 +Dump VPN traffic going through the local tinc node in pcap-savefile format to standard output,
 +from where it can be redirected to a file or piped through a program that can parse it directly,
 +such as tcpdump.
 +
 +@end table
 +
 +@c ==================================================================
 +@node    tincctl examples
 +@section tincctl examples
 +
 +Examples of some commands:
 +
 +@example
 +tincctl -n vpn dump graph | circo -Txlib
 +tincctl -n vpn pcap | tcpdump -r -
 +tincctl -n vpn top
 +@end example
 +
 +@c ==================================================================
 +@node    tincctl top
 +@section tincctl top
 +
 +The top command connects to a running tinc daemon and repeatedly queries its per-node traffic counters.
 +It displays a list of all the known nodes in the left-most column,
 +and the amount of bytes and packets read from and sent to each node in the other columns.
 +By default, the information is updated every second.
 +The behaviour of the top command can be changed using the following keys:
 +
 +@table @key
 +
 +@item s
 +Change the interval between updates.
 +After pressing the @key{s} key, enter the desired interval in seconds, followed by enter.
 +Fractional seconds are honored.
 +Intervals lower than 0.1 seconds are not allowed.
 +
 +@item c
 +Toggle between displaying current traffic rates (in packets and bytes per second)
 +and cummulative traffic (total packets and bytes since the tinc daemon started).
 +
 +@item n
 +Sort the list of nodes by name.
 +
 +@item i
 +Sort the list of nodes by incoming amount of bytes.
 +
 +@item I
 +Sort the list of nodes by incoming amount of packets.
 +
 +@item o
 +Sort the list of nodes by outgoing amount of bytes.
 +
 +@item O
 +Sort the list of nodes by outgoing amount of packets.
 +
 +@item t
 +Sort the list of nodes by sum of incoming and outgoing amount of bytes.
 +
 +@item T
 +Sort the list of nodes by sum of incoming and outgoing amount of packets.
 +
 +@item b
 +Show amount of traffic in bytes.
 +
 +@item k
 +Show amount of traffic in kilobytes.
 +
 +@item M
 +Show amount of traffic in megabytes.
 +
 +@item G
 +Show amount of traffic in gigabytes.
 +
 +@item q
 +Quit.
 +
 +@end table
 +
 +
  @c ==================================================================
  @node    Technical information
  @chapter Technical information
diff --combined doc/tincd.8.in
@@@ -1,4 -1,4 +1,4 @@@
 -.Dd 2011-01-02
 +.Dd 2011-06-25
  .Dt TINCD 8
  .\" Manual page created by:
  .\" Ivo Timmermans
@@@ -8,13 -8,17 +8,14 @@@
  .Nd tinc VPN daemon
  .Sh SYNOPSIS
  .Nm
- .Op Fl cdDKnLRU
 -.Op Fl cdDkKnoLRU
++.Op Fl cdDKnoLRU
  .Op Fl -config Ns = Ns Ar DIR
  .Op Fl -no-detach
  .Op Fl -debug Ns Op = Ns Ar LEVEL
 -.Op Fl -kill Ns Op = Ns Ar SIGNAL
  .Op Fl -net Ns = Ns Ar NETNAME
 -.Op Fl -generate-keys Ns Op = Ns Ar BITS
+ .Op Fl -option Ns = Ns Ar [HOST.]KEY=VALUE
  .Op Fl -mlock
  .Op Fl -logfile Ns Op = Ns Ar FILE
 -.Op Fl -pidfile Ns = Ns Ar FILE
  .Op Fl -bypass-security
  .Op Fl -chroot
  .Op Fl -user Ns = Ns Ar USER
@@@ -50,6 -54,14 +51,6 @@@ If not mentioned otherwise, this will s
  Increase debug level or set it to
  .Ar LEVEL
  (see below).
 -.It Fl k, -kill Ns Op = Ns Ar SIGNAL
 -Attempt to kill a running
 -.Nm
 -(optionally with the specified
 -.Ar SIGNAL
 -instead of SIGTERM) and exit.
 -Under Windows (not Cygwin) the optional argument is ignored,
 -the service will always be stopped and removed.
  .It Fl n, -net Ns = Ns Ar NETNAME
  Connect to net
  .Ar NETNAME .
@@@ -61,6 -73,29 +62,22 @@@ fo
  .Ar NETNAME
  is the same as not specifying any
  .Ar NETNAME .
 -.It Fl K, -generate-keys Ns Op = Ns Ar BITS
 -Generate public/private RSA keypair and exit.
 -If
 -.Ar BITS
 -is omitted, the default length will be 2048 bits.
 -When saving keys to existing files, tinc will not delete the old keys,
 -you have to remove them manually.
+ .It Fl o, -option Ns = Ns Ar [HOST.]KEY=VALUE
+ Without specifying a
+ .Ar HOST ,
+ this will set server configuration variable
+ .Ar KEY 
+ to
+ .Ar VALUE .
+ If specified as
+ .Ar HOST.KEY=VALUE ,
+ this will set the host configuration variable 
+ .Ar KEY
+ of the host named
+ .Ar HOST
+ to
+ .Ar VALUE .
+ This option can be used more than once to specify multiple configuration variables.
  .It Fl L, -mlock
  Lock tinc into main memory.
  This will prevent sensitive data like shared private keys to be written to the system swap files/partitions.
@@@ -70,16 -105,12 +87,16 @@@ I
  .Ar FILE
  is omitted, the default is
  .Pa @localstatedir@/log/tinc. Ns Ar NETNAME Ns Pa .log.
 -.It Fl -pidfile Ns = Ns Ar FILE
 -Write PID to
 +.It Fl -pidfile Ns = Ns Ar FILENAME
 +Store a cookie in
 +.Ar FILENAME
 +which allows
 +.Xr tincctl 8
 +to authenticate.
 +If
  .Ar FILE
 -instead of
 +is omitted, the default is
  .Pa @localstatedir@/run/tinc. Ns Ar NETNAME Ns Pa .pid.
 -Under Windows this option will be ignored.
  .It Fl -bypass-security
  Disables encryption and authentication of the meta protocol.
  Only useful for debugging.
@@@ -120,6 -151,15 +137,6 @@@ If th
  .Fl -logfile
  option is used, this will also close and reopen the log file,
  useful when log rotation is used.
 -.It INT
 -Temporarily increases debug level to 5.
 -Send this signal again to revert to the original level.
 -.It USR1
 -Dumps the connection list to syslog.
 -.It USR2
 -Dumps virtual network device statistics, all known nodes, edges and subnets to syslog.
 -.It WINCH
 -Purges all information remembered about unreachable nodes.
  .El
  .Sh DEBUG LEVELS
  The tinc daemon can send a lot of messages to the syslog.
@@@ -166,7 -206,6 +183,7 @@@ If you find any bugs, report them to ti
  .Sh TODO
  A lot, especially security auditing.
  .Sh SEE ALSO
 +.Xr tincctl 8 ,
  .Xr tinc.conf 5 ,
  .Pa http://www.tinc-vpn.org/ ,
  .Pa http://www.cabal.org/ .
diff --combined src/Makefile.am
@@@ -1,54 -1,45 +1,63 @@@
  ## Produce this file with automake to get Makefile.in
  
 -sbin_PROGRAMS = tincd
 +sbin_PROGRAMS = tincd tincctl sptps_test
  
- EXTRA_DIST = linux bsd solaris cygwin mingw raw_socket uml_socket openssl gcrypt
 -EXTRA_DIST = linux/device.c bsd/device.c solaris/device.c cygwin/device.c mingw/device.c mingw/common.h
++EXTRA_DIST = linux bsd solaris cygwin mingw openssl gcrypt
  
 -tincd_SOURCES = conf.c connection.c edge.c event.c graph.c logger.c meta.c net.c net_packet.c net_setup.c     \
 -      net_socket.c netutl.c node.c process.c protocol.c protocol_auth.c protocol_edge.c protocol_misc.c       \
 +tincd_SOURCES = \
 +      utils.c getopt.c getopt1.c list.c splay_tree.c dropin.c fake-getaddrinfo.c fake-getnameinfo.c \
 +      buffer.c conf.c connection.c control.c edge.c graph.c logger.c meta.c net.c net_packet.c net_setup.c \
 +      net_socket.c netutl.c node.c process.c protocol.c protocol_auth.c protocol_edge.c protocol_misc.c \
-       protocol_key.c protocol_subnet.c route.c subnet.c tincd.c
+       protocol_key.c protocol_subnet.c route.c subnet.c tincd.c \
+       dummy_device.c raw_socket_device.c
+       
+ if UML
+ tincd_SOURCES += uml_device.c
+ endif
+ if VDE
+ tincd_SOURCES += vde_device.c
+ endif
  
 +nodist_tincd_SOURCES = \
 +      device.c cipher.c crypto.c ecdh.c ecdsa.c digest.c prf.c rsa.c
 +
 +tincctl_SOURCES = \
 +      utils.c getopt.c getopt1.c dropin.c \
 +      list.c tincctl.c top.c
 +
 +nodist_tincctl_SOURCES = \
 +      ecdsagen.c rsagen.c
 +
 +sptps_test_SOURCES = \
 +      logger.c cipher.c crypto.c ecdh.c ecdsa.c digest.c prf.c \
 +      sptps.c sptps_test.c
 +
  if TUNEMU
  tincd_SOURCES += bsd/tunemu.c
  endif
  
 -nodist_tincd_SOURCES = device.c
 +tincctl_LDADD = $(CURSES_LIBS)
  
  DEFAULT_INCLUDES =
  
 -INCLUDES = @INCLUDES@ -I$(top_builddir) -I$(top_srcdir)/lib
 +INCLUDES = @INCLUDES@ -I$(top_builddir)
 +
 +noinst_HEADERS = \
 +      xalloc.h utils.h getopt.h list.h splay_tree.h dropin.h fake-getaddrinfo.h fake-getnameinfo.h fake-gai-errnos.h ipv6.h ipv4.h ethernet.h \
 +      buffer.h conf.h connection.h control.h control_common.h device.h edge.h graph.h logger.h meta.h net.h netutl.h node.h process.h \
 +      protocol.h route.h subnet.h tincctl.h top.h bsd/tunemu.h
  
 -noinst_HEADERS = conf.h connection.h device.h edge.h event.h graph.h logger.h meta.h net.h netutl.h node.h process.h  \
 -      protocol.h route.h subnet.h bsd/tunemu.h
 +nodist_noinst_HEADERS = \
 +      cipher.h crypto.h ecdh.h ecdsa.h digest.h prf.h rsa.h ecdsagen.h rsagen.h
  
 -LIBS = @LIBS@
 +LIBS = @LIBS@ @LIBGCRYPT_LIBS@
  
  if TUNEMU
  LIBS += -lpcap
  endif
  
 -tincd_LDADD = \
 -      $(top_builddir)/lib/libvpn.a
 -
 -AM_CFLAGS = -DCONFDIR=\"$(sysconfdir)\" -DLOCALSTATEDIR=\"$(localstatedir)\"
 +AM_CFLAGS = -DCONFDIR=\"$(sysconfdir)\" -DLOCALSTATEDIR=\"$(localstatedir)\" -DSBINDIR=\"$(sbindir)\"
  
  dist-hook:
        rm -f `find . -type l`
diff --combined src/bsd/device.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      device.c -- Interaction BSD tun/tap device
      Copyright (C) 2001-2005 Ivo Timmermans,
-                   2001-2011 Guus Sliepen <guus@tinc-vpn.org>
+                   2001-2012 Guus Sliepen <guus@tinc-vpn.org>
                    2009      Grzegorz Dymarek <gregd72002@googlemail.com>
  
      This program is free software; you can redistribute it and/or modify
@@@ -58,7 -58,7 +58,7 @@@ static device_type_t device_type = DEVI
  static device_type_t device_type = DEVICE_TYPE_TUN;
  #endif
  
- bool setup_device(void) {
static bool setup_device(void) {
        char *type;
  
        if(!get_config_string(lookup_config(config_tree, "Device"), &device))
                return false;
        }
  
+ #ifdef FD_CLOEXEC
+       fcntl(device_fd, F_SETFD, FD_CLOEXEC);
+ #endif
        switch(device_type) {
                default:
                        device_type = DEVICE_TYPE_TUN;
        return true;
  }
  
- void close_device(void) {
static void close_device(void) {
        switch(device_type) {
  #ifdef HAVE_TUNEMU
                case DEVICE_TYPE_TUNEMU:
        free(iface);
  }
  
- bool read_packet(vpn_packet_t *packet) {
static bool read_packet(vpn_packet_t *packet) {
 -      int lenin;
 +      int inlen;
  
        switch(device_type) {
                case DEVICE_TYPE_TUN:
  #ifdef HAVE_TUNEMU
                case DEVICE_TYPE_TUNEMU:
                        if(device_type == DEVICE_TYPE_TUNEMU)
 -                              lenin = tunemu_read(device_fd, packet->data + 14, MTU - 14);
 +                              inlen = tunemu_read(device_fd, packet->data + 14, MTU - 14);
                        else
  #endif
 -                              lenin = read(device_fd, packet->data + 14, MTU - 14);
 +                              inlen = read(device_fd, packet->data + 14, MTU - 14);
  
 -                      if(lenin <= 0) {
 +                      if(inlen <= 0) {
                                logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
                                           device, strerror(errno));
                                return false;
                                        return false;
                        }
  
 -                      packet->len = lenin + 14;
 +                      packet->len = inlen + 14;
                        break;
  
                case DEVICE_TYPE_TUNIFHEAD: {
                        u_int32_t type;
 -                      struct iovec vector[2] = {{&type, sizeof(type)}, {packet->data + 14, MTU - 14}};
 +                      struct iovec vector[2] = {{&type, sizeof type}, {packet->data + 14, MTU - 14}};
  
 -                      if((lenin = readv(device_fd, vector, 2)) <= 0) {
 +                      if((inlen = readv(device_fd, vector, 2)) <= 0) {
                                logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
                                           device, strerror(errno));
                                return false;
                                        return false;
                        }
  
 -                      packet->len = lenin + 10;
 +                      packet->len = inlen + 10;
                        break;
                }
  
                case DEVICE_TYPE_TAP:
 -                      if((lenin = read(device_fd, packet->data, MTU)) <= 0) {
 +                      if((inlen = read(device_fd, packet->data, MTU)) <= 0) {
                                logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
                                           device, strerror(errno));
                                return false;
                        }
  
 -                      packet->len = lenin;
 +                      packet->len = inlen;
                        break;
  
                default:
        return true;
  }
  
- bool write_packet(vpn_packet_t *packet) {
static bool write_packet(vpn_packet_t *packet) {
        ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s",
                           packet->len, device_info);
  
  
                case DEVICE_TYPE_TUNIFHEAD: {
                        u_int32_t type;
 -                      struct iovec vector[2] = {{&type, sizeof(type)}, {packet->data + 14, packet->len - 14}};
 +                      struct iovec vector[2] = {{&type, sizeof type}, {packet->data + 14, packet->len - 14}};
                        int af;
                        
                        af = (packet->data[12] << 8) + packet->data[13];
        return true;
  }
  
- void dump_device_stats(void) {
static void dump_device_stats(void) {
        logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device);
        logger(LOG_DEBUG, " total bytes in:  %10"PRIu64, device_total_in);
        logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out);
  }
+ const devops_t os_devops = {
+       .setup = setup_device,
+       .close = close_device,
+       .read = read_packet,
+       .write = write_packet,
+       .dump_stats = dump_device_stats,
+ };
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 "subnet.h"
  #include "utils.h"
  #include "xalloc.h"
  
 -avl_tree_t *connection_tree;  /* Meta connections */
 +splay_tree_t *connection_tree;        /* Meta connections */
- connection_t *broadcast;
+ connection_t *everyone;
  
  static int connection_compare(const connection_t *a, const connection_t *b) {
        return a < b ? -1 : a == b ? 0 : 1;
  }
  
  void init_connections(void) {
 -      connection_tree = avl_alloc_tree((avl_compare_t) connection_compare, (avl_action_t) free_connection);
 +      connection_tree = splay_alloc_tree((splay_compare_t) connection_compare, (splay_action_t) free_connection);
-       broadcast = new_connection();
-       broadcast->name = xstrdup("everyone");
-       broadcast->hostname = xstrdup("BROADCAST");
+       everyone = new_connection();
+       everyone->name = xstrdup("everyone");
+       everyone->hostname = xstrdup("BROADCAST");
  }
  
  void exit_connections(void) {
 -      avl_delete_tree(connection_tree);
 +      splay_delete_tree(connection_tree);
-       free_connection(broadcast);
+       free_connection(everyone);
  }
  
  connection_t *new_connection(void) {
 -      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);
 +      cipher_close(&c->incipher);
 +      digest_close(&c->indigest);
 +      cipher_close(&c->outcipher);
 +      digest_close(&c->outdigest);
  
 -      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);
 +      ecdh_free(&c->ecdh);
 +      ecdsa_free(&c->ecdsa);
 +      rsa_free(&c->rsa);
  
        if(c->hischallenge)
                free(c->hischallenge);
        if(c->config_tree)
                exit_configuration(&c->config_tree);
  
 -      if(c->outbuf)
 -              free(c->outbuf);
 +      buffer_clear(&c->inbuf);
 +      buffer_clear(&c->outbuf);
 +      
 +      if(event_initialized(&c->inevent))
 +              event_del(&c->inevent);
 +
 +      if(event_initialized(&c->outevent))
 +              event_del(&c->outevent);
  
 -      if(c->rsa_key)
 -              RSA_free(c->rsa_key);
 +      if(c->socket > 0)
 +              closesocket(c->socket);
  
        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);
  }
diff --combined src/connection.h
  #ifndef __TINC_CONNECTION_H__
  #define __TINC_CONNECTION_H__
  
 -#include <openssl/rsa.h>
 -#include <openssl/evp.h>
 -
 -#include "avl_tree.h"
 +#include "buffer.h"
 +#include "cipher.h"
 +#include "digest.h"
 +#include "rsa.h"
 +#include "splay_tree.h"
  
  #define OPTION_INDIRECT               0x0001
  #define OPTION_TCPONLY                0x0002
  #define OPTION_CLAMP_MSS      0x0008
  
  typedef struct connection_status_t {
 -      unsigned int pinged:1;                          /* sent ping */
 -      unsigned int active:1;                          /* 1 if active.. */
 -      unsigned int connecting:1;                      /* 1 if we are waiting for a non-blocking connect() to finish */
 -      unsigned int termreq:1;                         /* the termination of this connection was requested */
 -      unsigned int remove:1;                          /* Set to 1 if you want this connection removed */
 -      unsigned int timeout:1;                         /* 1 if gotten timeout */
 -      unsigned int encryptout:1;                      /* 1 if we can encrypt outgoing traffic */
 -      unsigned int decryptin:1;                       /* 1 if we have to decrypt incoming traffic */
 -      unsigned int mst:1;                             /* 1 if this connection is part of a minimum spanning tree */
 -      unsigned int unused:23;
 +              unsigned int pinged:1;                  /* sent ping */
 +              unsigned int active:1;                  /* 1 if active.. */
 +              unsigned int connecting:1;              /* 1 if we are waiting for a non-blocking connect() to finish */
 +              unsigned int termreq:1;                 /* the termination of this connection was requested */
 +              unsigned int remove_unused:1;           /* Set to 1 if you want this connection removed */
 +              unsigned int timeout_unused:1;          /* 1 if gotten timeout */
 +              unsigned int encryptout:1;              /* 1 if we can encrypt outgoing traffic */
 +              unsigned int decryptin:1;               /* 1 if we have to decrypt incoming traffic */
 +              unsigned int mst:1;                     /* 1 if this connection is part of a minimum spanning tree */
 +              unsigned int control:1;
 +              unsigned int pcap:1;
 +              unsigned int unused:21;
  } connection_status_t;
  
 +#include "ecdh.h"
 +#include "ecdsa.h"
  #include "edge.h"
  #include "net.h"
  #include "node.h"
@@@ -58,8 -53,7 +58,8 @@@ typedef struct connection_t 
  
        union sockaddr_t address;                       /* his real (internet) ip */
        char *hostname;                         /* the hostname of its real ip */
 -      int protocol_version;           /* used protocol */
 +      int protocol_major;             /* used protocol */
 +      int protocol_minor;             /* used protocol */
  
        int socket;                                     /* socket used for this connection */
        uint32_t options;                       /* options for this connection */
        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 RSA key */
 +      ecdsa_t ecdsa;                  /* his public ECDSA key */
 +      ecdsa_t ecdh;                   /* state for ECDH key exchange */
 +      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 buffer_t inbuf;
 +      struct buffer_t outbuf;
 +      struct event inevent;                           /* input event on this metadata connection */
 +      struct event outevent;                          /* output 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 connection_t *everyone;
  
  extern void init_connections(void);
  extern void exit_connections(void);
@@@ -107,6 -109,6 +107,6 @@@ 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 *);
  
  #endif                                                        /* __TINC_CONNECTION_H__ */
diff --combined src/cygwin/device.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      device.c -- Interaction with Windows tap driver in a Cygwin environment
      Copyright (C) 2002-2005 Ivo Timmermans,
-                   2002-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2002-2011 Guus Sliepen <guus@tinc-vpn.org>
  
      This program is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
@@@ -45,7 -45,7 +45,7 @@@ static uint64_t device_total_out = 0
  static pid_t reader_pid;
  static int sp[2];
  
- bool setup_device(void) {
static bool setup_device(void) {
        HKEY key, key2;
        int i, err;
  
        }
  
        for (i = 0; ; i++) {
 -              len = sizeof(adapterid);
 +              len = sizeof adapterid;
                if(RegEnumKeyEx(key, i, adapterid, &len, 0, 0, 0, NULL))
                        break;
  
                /* Find out more about this adapter */
  
 -              snprintf(regpath, sizeof(regpath), "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid);
 +              snprintf(regpath, sizeof regpath, "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid);
  
                  if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2))
                        continue;
  
 -              len = sizeof(adaptername);
 +              len = sizeof adaptername;
                err = RegQueryValueEx(key2, "Name", 0, 0, adaptername, &len);
  
                RegCloseKey(key2);
                                continue;
                }
  
 -              snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, adapterid);
 +              snprintf(tapname, sizeof tapname, USERMODEDEVICEDIR "%s" TAPSUFFIX, adapterid);
                device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0);
                if(device_handle != INVALID_HANDLE_VALUE) {
                        CloseHandle(device_handle);
        if(!iface)
                iface = xstrdup(adaptername);
  
 -      snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, device);
 +      snprintf(tapname, sizeof tapname, USERMODEDEVICEDIR "%s" TAPSUFFIX, device);
        
        /* Now we are going to open this device twice: once for reading and once for writing.
           We do this because apparently it isn't possible to check for activity in the select() loop.
  
        /* Get MAC address from tap device */
  
 -      if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof(mymac.x), mymac.x, sizeof(mymac.x), &len, 0)) {
 +      if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof mymac.x, mymac.x, sizeof mymac.x, &len, 0)) {
                logger(LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
                return false;
        }
                   It passes everything it reads to the socket. */
        
                char buf[MTU];
 -              long lenin;
 +              long inlen;
  
                CloseHandle(device_handle);
  
                /* Pass packets */
  
                for(;;) {
 -                      ReadFile(device_handle, buf, MTU, &lenin, NULL);
 -                      write(sp[1], buf, lenin);
 +                      ReadFile(device_handle, buf, MTU, &inlen, NULL);
 +                      write(sp[1], buf, inlen);
                }
        }
  
        return true;
  }
  
- void close_device(void) {
static void close_device(void) {
        close(sp[0]);
        close(sp[1]);
        CloseHandle(device_handle);
        free(iface);
  }
  
- bool read_packet(vpn_packet_t *packet) {
static bool read_packet(vpn_packet_t *packet) {
 -      int lenin;
 +      int inlen;
  
 -      if((lenin = read(sp[0], packet->data, MTU)) <= 0) {
 +      if((inlen = read(sp[0], packet->data, MTU)) <= 0) {
                logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
                           device, strerror(errno));
                return false;
        }
        
 -      packet->len = lenin;
 +      packet->len = inlen;
  
        device_total_in += packet->len;
  
        return true;
  }
  
- bool write_packet(vpn_packet_t *packet) {
static bool write_packet(vpn_packet_t *packet) {
 -      long lenout;
 +      long outlen;
  
        ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s",
                           packet->len, device_info);
  
 -      if(!WriteFile (device_handle, packet->data, packet->len, &lenout, NULL)) {
 +      if(!WriteFile (device_handle, packet->data, packet->len, &outlen, NULL)) {
                logger(LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError()));
                return false;
        }
        return true;
  }
  
- void dump_device_stats(void) {
static void dump_device_stats(void) {
        logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device);
        logger(LOG_DEBUG, " total bytes in:  %10"PRIu64, device_total_in);
        logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out);
  }
+ const devops_t os_devops = {
+       .setup = setup_device,
+       .close = close_device,
+       .read = read_packet,
+       .write = write_packet,
+       .dump_stats = dump_device_stats,
+ };
diff --combined src/device.h
@@@ -1,7 -1,7 +1,7 @@@
  /*
-     net.h -- generic header for device.c
+     device.h -- generic header for device.c
      Copyright (C) 2001-2005 Ivo Timmermans
-                   2001-2006 Guus Sliepen <guus@tinc-vpn.org>
+                   2001-2011 Guus Sliepen <guus@tinc-vpn.org>
  
      This program is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
  
  extern int device_fd;
  extern char *device;
 -
  extern char *iface;
  
- extern bool setup_device(void);
- extern void close_device(void);
- extern bool read_packet(struct vpn_packet_t *);
- extern bool write_packet(struct vpn_packet_t *);
- extern void dump_device_stats(void);
 +extern uint64_t device_in_packets;
 +extern uint64_t device_in_bytes;
 +extern uint64_t device_out_packets;
 +extern uint64_t device_out_bytes;
 +
+ typedef struct devops_t {
+       bool (*setup)(void);
+       void (*close)(void);
+       bool (*read)(struct vpn_packet_t *);
+       bool (*write)(struct vpn_packet_t *);
+       void (*dump_stats)(void);
+ } devops_t;
+ extern const devops_t os_devops;
+ extern const devops_t dummy_devops;
+ extern const devops_t raw_socket_devops;
+ extern const devops_t uml_devops;
+ extern const devops_t vde_devops;
+ extern devops_t devops;
  
  #endif                                                        /* __TINC_DEVICE_H__ */
diff --combined src/ipv4.h
  #define ICMP_NET_UNKNOWN 6
  #endif
  
+ #ifndef ICMP_TIME_EXCEEDED
+ #define ICMP_TIME_EXCEEDED 11
+ #endif
+ #ifndef ICMP_EXC_TTL
+ #define ICMP_EXC_TTL 0
+ #endif
  #ifndef ICMP_NET_UNREACH
  #define ICMP_NET_UNREACH 0
  #endif
diff --combined src/linux/device.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      device.c -- Interaction with Linux ethertap and tun/tap device
      Copyright (C) 2001-2005 Ivo Timmermans,
-                   2001-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2001-2012 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"
  
 -#ifdef HAVE_LINUX_IF_TUN_H
  #include <linux/if_tun.h>
  #define DEFAULT_DEVICE "/dev/net/tun"
 -#else
 -#define DEFAULT_DEVICE "/dev/tap0"
 -#endif
  
  #include "conf.h"
  #include "device.h"
@@@ -30,9 -34,9 +30,9 @@@
  #include "route.h"
  #include "utils.h"
  #include "xalloc.h"
 +#include "device.h"
  
  typedef enum device_type_t {
 -      DEVICE_TYPE_ETHERTAP,
        DEVICE_TYPE_TUN,
        DEVICE_TYPE_TAP,
  } device_type_t;
@@@ -41,15 -45,17 +41,16 @@@ int device_fd = -1
  static device_type_t device_type;
  char *device = NULL;
  char *iface = NULL;
+ static char *type = NULL;
  static char ifrname[IFNAMSIZ];
  static char *device_info;
  
 -static uint64_t device_total_in = 0;
 -static uint64_t device_total_out = 0;
 +uint64_t device_in_packets = 0;
 +uint64_t device_in_bytes = 0;
 +uint64_t device_out_packets = 0;
 +uint64_t device_out_bytes = 0;
  
- bool setup_device(void) {
static bool setup_device(void) {
 -      struct ifreq ifr;
 -      bool t1q = false;
 -
        if(!get_config_string(lookup_config(config_tree, "Device"), &device))
                device = xstrdup(DEFAULT_DEVICE);
  
                return false;
        }
  
 -#ifdef HAVE_LINUX_IF_TUN_H
 -      /* Ok now check if this is an old ethertap or a new tun/tap thingie */
 -
 -      memset(&ifr, 0, sizeof(ifr));
+ #ifdef FD_CLOEXEC
+       fcntl(device_fd, F_SETFD, FD_CLOEXEC);
+ #endif
 +      struct ifreq ifr = {{{0}}};
  
-       if(routing_mode == RMODE_ROUTER) {
+       get_config_string(lookup_config(config_tree, "DeviceType"), &type);
+       if(type && strcasecmp(type, "tun") && strcasecmp(type, "tap")) {
+               logger(LOG_ERR, "Unknown device type %s!", type);
+               return false;
+       }
+       if((type && !strcasecmp(type, "tun")) || (!type && routing_mode == RMODE_ROUTER)) {
                ifr.ifr_flags = IFF_TUN;
                device_type = DEVICE_TYPE_TUN;
                device_info = "Linux tun/tap device (tun mode)";
  
  #ifdef IFF_ONE_QUEUE
        /* Set IFF_ONE_QUEUE flag... */
 +
 +      bool t1q = false;
        if(get_config_bool(lookup_config(config_tree, "IffOneQueue"), &t1q) && t1q)
                ifr.ifr_flags |= IFF_ONE_QUEUE;
  #endif
                strncpy(ifrname, ifr.ifr_name, IFNAMSIZ);
                if(iface) free(iface);
                iface = xstrdup(ifrname);
 -      } else
 -#endif
 -      {
 -              if(routing_mode == RMODE_ROUTER)
 -                      overwrite_mac = true;
 -              device_info = "Linux ethertap device";
 -              device_type = DEVICE_TYPE_ETHERTAP;
 -              if(iface)
 -                      free(iface);
 -              iface = xstrdup(strrchr(device, '/') ? strrchr(device, '/') + 1 : device);
        }
  
        logger(LOG_INFO, "%s is a %s", device, device_info);
        return true;
  }
  
- void close_device(void) {
static void close_device(void) {
        close(device_fd);
  
+       free(type);
        free(device);
        free(iface);
  }
  
- bool read_packet(vpn_packet_t *packet) {
static bool read_packet(vpn_packet_t *packet) {
 -      int lenin;
 +      int inlen;
        
        switch(device_type) {
                case DEVICE_TYPE_TUN:
 -                      lenin = read(device_fd, packet->data + 10, MTU - 10);
 +                      inlen = read(device_fd, packet->data + 10, MTU - 10);
  
 -                      if(lenin <= 0) {
 +                      if(inlen <= 0) {
                                logger(LOG_ERR, "Error while reading from %s %s: %s",
                                           device_info, device, strerror(errno));
                                return false;
                        }
  
 -                      packet->len = lenin + 10;
 +                      packet->len = inlen + 10;
                        break;
                case DEVICE_TYPE_TAP:
 -                      lenin = read(device_fd, packet->data, MTU);
 +                      inlen = read(device_fd, packet->data, MTU);
  
 -                      if(lenin <= 0) {
 +                      if(inlen <= 0) {
                                logger(LOG_ERR, "Error while reading from %s %s: %s",
                                           device_info, device, strerror(errno));
                                return false;
                        }
  
 -                      packet->len = lenin;
 -                      break;
 -              case DEVICE_TYPE_ETHERTAP:
 -                      lenin = read(device_fd, packet->data - 2, MTU + 2);
 -
 -                      if(lenin <= 0) {
 -                              logger(LOG_ERR, "Error while reading from %s %s: %s",
 -                                         device_info, device, strerror(errno));
 -                              return false;
 -                      }
 -
 -                      packet->len = lenin - 2;
 +                      packet->len = inlen;
                        break;
 +              default:
 +                      abort();
        }
  
 -      device_total_in += packet->len;
 +      device_in_packets++;
 +      device_in_bytes += packet->len;
  
        ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len,
                           device_info);
        return true;
  }
  
- bool write_packet(vpn_packet_t *packet) {
static bool write_packet(vpn_packet_t *packet) {
        ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s",
                           packet->len, device_info);
  
                                return false;
                        }
                        break;
 -              case DEVICE_TYPE_ETHERTAP:
 -                      *(short int *)(packet->data - 2) = packet->len;
 -
 -                      if(write(device_fd, packet->data - 2, packet->len + 2) < 0) {
 -                              logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device,
 -                                         strerror(errno));
 -                              return false;
 -                      }
 -                      break;
 +              default:
 +                      abort();
        }
  
 -      device_total_out += packet->len;
 +      device_out_packets++;
 +      device_out_bytes += packet->len;
  
        return true;
  }
  
- void dump_device_stats(void) {
static void dump_device_stats(void) {
        logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device);
 -      logger(LOG_DEBUG, " total bytes in:  %10"PRIu64, device_total_in);
 -      logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out);
 +      logger(LOG_DEBUG, " total bytes in:  %10"PRIu64, device_in_bytes);
 +      logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_out_bytes);
  }
+ const devops_t os_devops = {
+       .setup = setup_device,
+       .close = close_device,
+       .read = read_packet,
+       .write = write_packet,
+       .dump_stats = dump_device_stats,
+ };
diff --combined src/mingw/device.c
@@@ -83,7 -83,7 +83,7 @@@ static DWORD WINAPI tapreader(void *bla
        }
  }
  
- bool setup_device(void) {
static bool setup_device(void) {
        HKEY key, key2;
        int i;
  
        }
  
        for (i = 0; ; i++) {
 -              len = sizeof(adapterid);
 +              len = sizeof adapterid;
                if(RegEnumKeyEx(key, i, adapterid, &len, 0, 0, 0, NULL))
                        break;
  
                /* Find out more about this adapter */
  
 -              snprintf(regpath, sizeof(regpath), "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid);
 +              snprintf(regpath, sizeof regpath, "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid);
  
                  if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2))
                        continue;
  
 -              len = sizeof(adaptername);
 +              len = sizeof adaptername;
                err = RegQueryValueEx(key2, "Name", 0, 0, adaptername, &len);
  
                RegCloseKey(key2);
                                continue;
                }
  
 -              snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, adapterid);
 +              snprintf(tapname, sizeof tapname, USERMODEDEVICEDIR "%s" TAPSUFFIX, adapterid);
                device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
                if(device_handle != INVALID_HANDLE_VALUE) {
                        found = true;
        /* Try to open the corresponding tap device */
  
        if(device_handle == INVALID_HANDLE_VALUE) {
 -              snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, device);
 +              snprintf(tapname, sizeof tapname, USERMODEDEVICEDIR "%s" TAPSUFFIX, device);
                device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
        }
        
  
        /* Get MAC address from tap device */
  
 -      if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof(mymac.x), mymac.x, sizeof(mymac.x), &len, 0)) {
 +      if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof mymac.x, mymac.x, sizeof mymac.x, &len, 0)) {
                logger(LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError()));
                return false;
        }
        /* Set media status for newer TAP-Win32 devices */
  
        status = true;
 -      DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
 +      DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof status, &status, sizeof status, &len, NULL);
  
        device_info = "Windows tap device";
  
        return true;
  }
  
- void close_device(void) {
static void close_device(void) {
        CloseHandle(device_handle);
  
        free(device);
        free(iface);
  }
  
- bool read_packet(vpn_packet_t *packet) {
static bool read_packet(vpn_packet_t *packet) {
        return false;
  }
  
- bool write_packet(vpn_packet_t *packet) {
static bool write_packet(vpn_packet_t *packet) {
 -      long lenout;
 +      long outlen;
        OVERLAPPED overlapped = {0};
  
        ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s",
                           packet->len, device_info);
  
 -      if(!WriteFile(device_handle, packet->data, packet->len, &lenout, &overlapped)) {
 +      if(!WriteFile(device_handle, packet->data, packet->len, &outlen, &overlapped)) {
                logger(LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError()));
                return false;
        }
        return true;
  }
  
- void dump_device_stats(void) {
static void dump_device_stats(void) {
        logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device);
        logger(LOG_DEBUG, " total bytes in:  %10"PRIu64, device_total_in);
        logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out);
  }
+ const devops_t os_devops = {
+       .setup = setup_device,
+       .close = close_device,
+       .read = read_packet,
+       .write = write_packet,
+       .dump_stats = dump_device_stats,
+ };
diff --combined src/net.c
+++ b/src/net.c
  
  #include "system.h"
  
 -#include <openssl/rand.h>
 -
  #include "utils.h"
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "conf.h"
  #include "connection.h"
  #include "device.h"
 -#include "event.h"
  #include "graph.h"
  #include "logger.h"
  #include "meta.h"
  #include "netutl.h"
  #include "process.h"
  #include "protocol.h"
 -#include "route.h"
  #include "subnet.h"
  #include "xalloc.h"
  
 -bool do_purge = false;
 -volatile bool running = false;
 -#ifdef HAVE_PSELECT
 -bool graph_dump = false;
 -#endif
 -
 -time_t now = 0;
  int contradicting_add_edge = 0;
  int contradicting_del_edge = 0;
  static int sleeptime = 10;
  
  /* 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;
@@@ -64,7 -75,7 +64,7 @@@
                        for(snode = n->subnet_tree->head; snode; snode = snext) {
                                snext = snode->next;
                                s = snode->data;
-                               send_del_subnet(broadcast, s);
+                               send_del_subnet(everyone, s);
                                if(!strictsubnets)
                                        subnet_del(n, s);
                        }
@@@ -73,7 -84,7 +73,7 @@@
                                enext = enode->next;
                                e = enode->data;
                                if(!tunnelserver)
-                                       send_del_edge(broadcast, e);
+                                       send_del_edge(everyone, e);
                                edge_del(e);
                        }
                }
        }
  }
  
 -/*
 -  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)
                c->node->connection = NULL;
  
 -      if(c->socket)
 -              closesocket(c->socket);
 -
        if(c->edge) {
                if(report && !tunnelserver)
-                       send_del_edge(broadcast, c->edge);
+                       send_del_edge(everyone, c->edge);
  
                edge_del(c->edge);
  
                        e = lookup_edge(c->node, myself);
                        if(e) {
                                if(!tunnelserver)
-                                       send_del_edge(broadcast, e);
+                                       send_del_edge(everyone, e);
                                edge_del(e);
                        }
                }
  
        /* Check if this was our outgoing connection */
  
 -      if(c->outgoing) {
 +      if(c->outgoing)
                retry_outgoing(c->outgoing);
 -              c->outgoing = NULL;
 -      }
  
 -      free(c->outbuf);
 -      c->outbuf = NULL;
 -      c->outbuflen = 0;
 -      c->outbufsize = 0;
 -      c->outbufstart = 0;
 +      connection_del(c);
  }
  
  /*
    end does not reply in time, we consider them dead
    and close the connection.
  */
 -static void check_dead_connections(void) {
 -      avl_node_t *node, *next;
 +static void timeout_handler(int fd, short events, void *event) {
 +      splay_node_t *node, *next;
        connection_t *c;
 +      time_t now = time(NULL);
  
        for(node = connection_tree->head; node; node = next) {
                next = node->next;
                                if(c->status.pinged) {
                                        ifdebug(CONNECTIONS) logger(LOG_INFO, "%s (%s) didn't respond to PING in %ld seconds",
                                                           c->name, c->hostname, now - c->last_ping_time);
 -                                      c->status.timeout = true;
                                        terminate_connection(c, true);
 +                                      continue;
                                } else if(c->last_ping_time + pinginterval <= now) {
                                        send_ping(c);
                                }
                        } else {
 -                              if(c->status.remove) {
 -                                      logger(LOG_WARNING, "Old connection_t for %s (%s) status %04x still lingering, deleting...",
 -                                                 c->name, c->hostname, bitfield_to_int(&c->status, sizeof c->status));
 -                                      connection_del(c);
 -                                      continue;
 -                              }
 -                              ifdebug(CONNECTIONS) logger(LOG_WARNING, "Timeout from %s (%s) during authentication",
 -                                                 c->name, c->hostname);
                                if(c->status.connecting) {
 +                                      ifdebug(CONNECTIONS)
 +                                              logger(LOG_WARNING, "Timeout while connecting to %s (%s)", c->name, c->hostname);
                                        c->status.connecting = false;
                                        closesocket(c->socket);
                                        do_outgoing_connection(c);
                                } else {
 +                                      ifdebug(CONNECTIONS) logger(LOG_WARNING, "Timeout from %s (%s) during authentication", c->name, c->hostname);
                                        terminate_connection(c, false);
 +                                      continue;
                                }
                        }
                }
 -
 -              if(c->outbuflen > 0 && c->last_flushed_time + pingtimeout <= now) {
 -                      if(c->status.active) {
 -                              ifdebug(CONNECTIONS) logger(LOG_INFO,
 -                                              "%s (%s) could not flush for %ld seconds (%d bytes remaining)",
 -                                              c->name, c->hostname, now - c->last_flushed_time, c->outbuflen);
 -                              c->status.timeout = true;
 -                              terminate_connection(c, true);
 -                      }
 -              }
        }
 -}
  
 -/*
 -  check all connections to see if anything
 -  happened on their sockets
 -*/
 -static void check_network_activity(fd_set * readset, fd_set * writeset) {
 -      connection_t *c;
 -      avl_node_t *node;
 -      int result, i;
 -      socklen_t len = sizeof(result);
 -      vpn_packet_t packet;
 -      static int errors = 0;
 -
 -      /* check input from kernel */
 -      if(device_fd >= 0 && FD_ISSET(device_fd, readset)) {
 -              if(devops.read(&packet)) {
 -                      errors = 0;
 -                      packet.priority = 0;
 -                      route(myself, &packet);
 -              } else {
 -                      usleep(errors * 50000);
 -                      errors++;
 -                      if(errors > 10) {
 -                              logger(LOG_ERR, "Too many errors from %s, exiting!", device);
 -                              running = false;
 -                      }
 -              }
 +      if(contradicting_del_edge > 100 && contradicting_add_edge > 100) {
 +              logger(LOG_WARNING, "Possible node with same Name as us! Sleeping %d seconds.", sleeptime);
 +              usleep(sleeptime * 1000000LL);
 +              sleeptime *= 2;
 +              if(sleeptime < 0)
 +                      sleeptime = 3600;
 +      } else {
 +              sleeptime /= 2;
 +              if(sleeptime < 10)
 +                      sleeptime = 10;
        }
  
 -      /* check meta connections */
 -      for(node = connection_tree->head; node; node = node->next) {
 -              c = node->data;
 -
 -              if(c->status.remove)
 -                      continue;
 +      contradicting_add_edge = 0;
 +      contradicting_del_edge = 0;
  
 -              if(FD_ISSET(c->socket, readset)) {
 -                      if(c->status.connecting) {
 -                              c->status.connecting = false;
 -                              getsockopt(c->socket, SOL_SOCKET, SO_ERROR, (void *)&result, &len);
 -
 -                              if(!result)
 -                                      finish_connecting(c);
 -                              else {
 -                                      ifdebug(CONNECTIONS) logger(LOG_DEBUG,
 -                                                         "Error while connecting to %s (%s): %s",
 -                                                         c->name, c->hostname, sockstrerror(result));
 -                                      closesocket(c->socket);
 -                                      do_outgoing_connection(c);
 -                                      continue;
 -                              }
 -                      }
 -
 -                      if(!receive_meta(c)) {
 -                              terminate_connection(c, c->status.active);
 -                              continue;
 -                      }
 -              }
 +      event_add(event, &(struct timeval){pingtimeout, 0});
 +}
  
 -              if(FD_ISSET(c->socket, writeset)) {
 -                      if(!flush_meta(c)) {
 -                              terminate_connection(c, c->status.active);
 -                              continue;
 -                      }
 +void handle_meta_connection_data(int fd, short events, void *data) {
 +      connection_t *c = data;
 +      int result;
 +      socklen_t len = sizeof result;
 +
 +      if(c->status.connecting) {
 +              c->status.connecting = false;
 +
 +              getsockopt(c->socket, SOL_SOCKET, SO_ERROR, &result, &len);
 +
 +              if(!result)
 +                      finish_connecting(c);
 +              else {
 +                      ifdebug(CONNECTIONS) logger(LOG_DEBUG,
 +                                         "Error while connecting to %s (%s): %s",
 +                                         c->name, c->hostname, sockstrerror(result));
 +                      closesocket(c->socket);
 +                      do_outgoing_connection(c);
 +                      return;
                }
        }
  
 -      for(i = 0; i < listen_sockets; i++) {
 -              if(FD_ISSET(listen_socket[i].udp, readset))
 -                      handle_incoming_vpn_data(listen_socket[i].udp);
 -
 -              if(FD_ISSET(listen_socket[i].tcp, readset))
 -                      handle_new_meta_connection(listen_socket[i].tcp);
 +      if (!receive_meta(c)) {
 +              terminate_connection(c, c->status.active);
 +              return;
        }
  }
  
 -/*
 -  this is where it all happens...
 -*/
 -int main_loop(void) {
 -      fd_set readset, writeset;
 -#ifdef HAVE_PSELECT
 -      struct timespec tv;
 -      sigset_t omask, block_mask;
 -      time_t next_event;
 -#else
 -      struct timeval tv;
 -#endif
 -      int r, maxfd;
 -      time_t last_ping_check, last_config_check, last_graph_dump;
 -      event_t *event;
 +static void sigterm_handler(int signal, short events, void *data) {
 +      logger(LOG_NOTICE, "Got %s signal", strsignal(signal));
 +      event_loopexit(NULL);
 +}
  
 -      last_ping_check = now;
 -      last_config_check = now;
 -      last_graph_dump = now;
 -      
 -      srand(now);
 -
 -#ifdef HAVE_PSELECT
 -      if(lookup_config(config_tree, "GraphDumpFile"))
 -              graph_dump = true;
 -      /* Block SIGHUP & SIGALRM */
 -      sigemptyset(&block_mask);
 -      sigaddset(&block_mask, SIGHUP);
 -      sigaddset(&block_mask, SIGALRM);
 -      sigprocmask(SIG_BLOCK, &block_mask, &omask);
 -#endif
 +static void sighup_handler(int signal, short events, void *data) {
 +      logger(LOG_NOTICE, "Got %s signal", strsignal(signal));
 +      reopenlogger();
 +      reload_configuration();
 +}
  
 -      running = true;
 -
 -      while(running) {
 -#ifdef HAVE_PSELECT
 -              next_event = last_ping_check + pingtimeout;
 -              if(graph_dump && next_event > last_graph_dump + 60)
 -                      next_event = last_graph_dump + 60;
 -
 -              if((event = peek_next_event()) && next_event > event->time)
 -                      next_event = event->time;
 -
 -              if(next_event <= now)
 -                      tv.tv_sec = 0;
 -              else
 -                      tv.tv_sec = next_event - now;
 -              tv.tv_nsec = 0;
 -#else
 -              tv.tv_sec = 1;
 -              tv.tv_usec = 0;
 -#endif
 +static void sigalrm_handler(int signal, short events, void *data) {
 +      logger(LOG_NOTICE, "Got %s signal", strsignal(signal));
 +      retry();
 +}
  
 -              maxfd = build_fdset(&readset, &writeset);
 +int reload_configuration(void) {
 +      connection_t *c;
 +      splay_node_t *node, *next;
 +      char *fname;
 +      struct stat s;
 +      static time_t last_config_check = 0;
  
 -#ifdef HAVE_MINGW
 -              LeaveCriticalSection(&mutex);
 -#endif
 -#ifdef HAVE_PSELECT
 -              r = pselect(maxfd + 1, &readset, &writeset, NULL, &tv, &omask);
 -#else
 -              r = select(maxfd + 1, &readset, &writeset, NULL, &tv);
 -#endif
 -              now = time(NULL);
 -#ifdef HAVE_MINGW
 -              EnterCriticalSection(&mutex);
 -#endif
 +      /* Reread our own configuration file */
  
 -              if(r < 0) {
 -                      if(!sockwouldblock(sockerrno)) {
 -                              logger(LOG_ERR, "Error while waiting for input: %s", sockstrerror(sockerrno));
 -                              dump_connections();
 -                              return 1;
 -                      }
 -              }
 +      exit_configuration(&config_tree);
 +      init_configuration(&config_tree);
  
 -              if(r > 0)
 -                      check_network_activity(&readset, &writeset);
 +      if(!read_server_config()) {
 +              logger(LOG_ERR, "Unable to reread configuration file, exitting.");
 +              event_loopexit(NULL);
 +              return EINVAL;
 +      }
  
 -              if(do_purge) {
 -                      purge();
 -                      do_purge = false;
 +      /* Close connections to hosts that have a changed or deleted host config file */
 +      
 +      for(node = connection_tree->head; node; node = next) {
 +              c = node->data;
 +              next = node->next;
 +              
 +              if(c->outgoing) {
 +                      free(c->outgoing->name);
 +                      if(c->outgoing->ai)
 +                              freeaddrinfo(c->outgoing->ai);
 +                      free(c->outgoing);
 +                      c->outgoing = NULL;
                }
 +              
 +              xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
 +              if(stat(fname, &s) || s.st_mtime > last_config_check)
 +                      terminate_connection(c, c->status.active);
 +              free(fname);
 +      }
  
 -              /* Let's check if everybody is still alive */
 -
 -              if(last_ping_check + pingtimeout <= now) {
 -                      check_dead_connections();
 -                      last_ping_check = now;
 -
 -                      if(routing_mode == RMODE_SWITCH)
 -                              age_subnets();
 -
 -                      age_past_requests();
 -
 -                      /* Should we regenerate our key? */
 -
 -                      if(keyexpires <= now) {
 -                              avl_node_t *node;
 -                              node_t *n;
 -
 -                              ifdebug(STATUS) logger(LOG_INFO, "Expiring symmetric keys");
 +      last_config_check = time(NULL);
  
 -                              for(node = node_tree->head; node; node = node->next) {
 -                                      n = node->data;
 -                                      if(n->inkey) {
 -                                              free(n->inkey);
 -                                              n->inkey = NULL;
 -                                      }
 -                              }
 +      /* If StrictSubnet is set, expire deleted Subnets and read new ones in */
  
 -                              send_key_changed();
 -                              keyexpires = now + keylifetime;
 -                      }
 +      if(strictsubnets) {
 +              subnet_t *subnet;
  
 -                      /* Detect ADD_EDGE/DEL_EDGE storms that are caused when
 -                       * two tinc daemons with the same name are on the VPN.
 -                       * If so, sleep a while. If this happens multiple times
 -                       * in a row, sleep longer. */
 -
 -                      if(contradicting_del_edge > 100 && contradicting_add_edge > 100) {
 -                              logger(LOG_WARNING, "Possible node with same Name as us! Sleeping %d seconds.", sleeptime);
 -                              usleep(sleeptime * 1000000LL);
 -                              sleeptime *= 2;
 -                              if(sleeptime < 0)
 -                                      sleeptime = 3600;
 -                      } else {
 -                              sleeptime /= 2;
 -                              if(sleeptime < 10)
 -                                      sleeptime = 10;
 -                      }
  
 -                      contradicting_add_edge = 0;
 -                      contradicting_del_edge = 0;
 +              for(node = subnet_tree->head; node; node = node->next) {
 +                      subnet = node->data;
 +                      subnet->expires = 1;
                }
  
 -              if(sigalrm) {
 -                      avl_node_t *node;
 -                      logger(LOG_INFO, "Flushing event queue");
 -                      expire_events();
 -                      for(node = connection_tree->head; node; node = node->next) {
 -                              connection_t *c = node->data;
 -                              send_ping(c);
 +              load_all_subnets();
 +
 +              for(node = subnet_tree->head; node; node = next) {
 +                      next = node->next;
 +                      subnet = node->data;
 +                      if(subnet->expires == 1) {
-                               send_del_subnet(broadcast, subnet);
++                              send_del_subnet(everyone, subnet);
 +                              if(subnet->owner->status.reachable)
 +                                      subnet_update(subnet->owner, subnet, false);
 +                              subnet_del(subnet->owner, subnet);
 +                      } else if(subnet->expires == -1) {
 +                              subnet->expires = 0;
 +                      } else {
-                               send_add_subnet(broadcast, subnet);
++                              send_add_subnet(everyone, subnet);
 +                              if(subnet->owner->status.reachable)
 +                                      subnet_update(subnet->owner, subnet, true);
                        }
 -                      sigalrm = false;
                }
 +      }
  
 -              while((event = get_expired_event())) {
 -                      event->handler(event->data);
 -                      free_event(event);
 -              }
 -
 -              if(sighup) {
 -                      connection_t *c;
 -                      avl_node_t *node, *next;
 -                      char *fname;
 -                      struct stat s;
 -                      
 -                      sighup = false;
 -
 -                      reopenlogger();
 -                      
 -                      /* Reread our own configuration file */
 -
 -                      exit_configuration(&config_tree);
 -                      init_configuration(&config_tree);
 -
 -                      if(!read_server_config()) {
 -                              logger(LOG_ERR, "Unable to reread configuration file, exitting.");
 -                              return 1;
 -                      }
 -
 -                      /* Cancel non-active outgoing connections */
 -
 -                      for(node = connection_tree->head; node; node = next) {
 -                              next = node->next;
 -                              c = node->data;
 -
 -                              c->outgoing = NULL;
 -
 -                              if(c->status.connecting) {
 -                                      terminate_connection(c, false);
 -                                      connection_del(c);
 -                              }
 -                      }
 -
 -                      /* Wipe list of outgoing connections */
 -
 -                      for(list_node_t *node = outgoing_list->head; node; node = node->next) {
 -                              outgoing_t *outgoing = node->data;
 -
 -                              if(outgoing->event)
 -                                      event_del(outgoing->event);
 -                      }
 -
 -                      list_delete_list(outgoing_list);
 -
 -                      /* Close connections to hosts that have a changed or deleted host config file */
 -                      
 -                      for(node = connection_tree->head; node; node = node->next) {
 -                              c = node->data;
 -                              
 -                              xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
 -                              if(stat(fname, &s) || s.st_mtime > last_config_check)
 -                                      terminate_connection(c, c->status.active);
 -                              free(fname);
 -                      }
 -
 -                      last_config_check = now;
 -
 -                      /* If StrictSubnet is set, expire deleted Subnets and read new ones in */
 -
 -                      if(strictsubnets) {
 -                              subnet_t *subnet;
 +      /* Try to make outgoing connections */
 +      
 +      try_outgoing_connections();
  
 -                              for(node = subnet_tree->head; node; node = node->next) {
 -                                      subnet = node->data;
 -                                      subnet->expires = 1;
 -                              }
 +      return 0;
 +}
  
 -                              load_all_subnets();
 -
 -                              for(node = subnet_tree->head; node; node = next) {
 -                                      next = node->next;
 -                                      subnet = node->data;
 -                                      if(subnet->expires == 1) {
 -                                              send_del_subnet(everyone, subnet);
 -                                              if(subnet->owner->status.reachable)
 -                                                      subnet_update(subnet->owner, subnet, false);
 -                                              subnet_del(subnet->owner, subnet);
 -                                      } else if(subnet->expires == -1) {
 -                                              subnet->expires = 0;
 -                                      } else {
 -                                              send_add_subnet(everyone, subnet);
 -                                              if(subnet->owner->status.reachable)
 -                                                      subnet_update(subnet->owner, subnet, true);
 -                                      }
 -                              }
 -                      }
 +void retry(void) {
 +      connection_t *c;
 +      splay_node_t *node;
  
 -                      /* Try to make outgoing connections */
 -                      
 -                      try_outgoing_connections();
 -              }
 +      for(node = connection_tree->head; node; node = node->next) {
 +              c = node->data;
                
 -              /* Dump graph if wanted every 60 seconds*/
 -
 -              if(last_graph_dump + 60 <= now) {
 -                      dump_graph();
 -                      last_graph_dump = now;
 +              if(c->outgoing && !c->node) {
 +                      if(timeout_initialized(&c->outgoing->ev))
 +                              event_del(&c->outgoing->ev);
 +                      if(c->status.connecting)
 +                              close(c->socket);
 +                      c->outgoing->timeout = 0;
 +                      do_outgoing_connection(c);
                }
        }
 +}
 +
 +/*
 +  this is where it all happens...
 +*/
 +int main_loop(void) {
 +      struct event timeout_event;
 +
 +      timeout_set(&timeout_event, timeout_handler, &timeout_event);
 +      event_add(&timeout_event, &(struct timeval){pingtimeout, 0});
 +
 +#ifndef HAVE_MINGW
 +      struct event sighup_event;
 +      struct event sigterm_event;
 +      struct event sigquit_event;
 +      struct event sigalrm_event;
 +
 +      signal_set(&sighup_event, SIGHUP, sighup_handler, NULL);
 +      signal_add(&sighup_event, NULL);
 +      signal_set(&sigterm_event, SIGTERM, sigterm_handler, NULL);
 +      signal_add(&sigterm_event, NULL);
 +      signal_set(&sigquit_event, SIGQUIT, sigterm_handler, NULL);
 +      signal_add(&sigquit_event, NULL);
 +      signal_set(&sigalrm_event, SIGALRM, sigalrm_handler, NULL);
 +      signal_add(&sigalrm_event, NULL);
 +#endif
 +
 +      if(event_loop(0) < 0) {
 +              logger(LOG_ERR, "Error while waiting for input: %s", strerror(errno));
 +              return 1;
 +      }
  
 -#ifdef HAVE_PSELECT
 -      /* Restore SIGHUP & SIGALARM mask */
 -      sigprocmask(SIG_SETMASK, &omask, NULL);
 +#ifndef HAVE_MINGW
 +      signal_del(&sighup_event);
 +      signal_del(&sigterm_event);
 +      signal_del(&sigquit_event);
 +      signal_del(&sigalrm_event);
  #endif
  
 +      event_del(&timeout_event);
 +
        return 0;
  }
diff --combined src/net_packet.c
  #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 "logger.h"
  #include "net.h"
@@@ -55,6 -53,7 +55,6 @@@
  #include "xalloc.h"
  
  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
@@@ -70,13 -69,13 +70,13 @@@ unsigned replaywin = 16
  // 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 mtu_probe_h(node_t *n, vpn_packet_t *packet, length_t len) {
 +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);
 +}
 +
 +static void mtu_probe_h(node_t *n, vpn_packet_t *packet, length_t len) {
        ifdebug(TRAFFIC) logger(LOG_INFO, "Got MTU probe length %d from %s (%s)", packet->len, n->name, n->hostname);
  
        if(!packet->data[0]) {
@@@ -234,17 -231,18 +234,17 @@@ static void receive_packet(node_t *n, v
        ifdebug(TRAFFIC) logger(LOG_DEBUG, "Received packet of %d bytes from %s (%s)",
                           packet->len, n->name, n->hostname);
  
 +      n->in_packets++;
 +      n->in_bytes += packet->len;
 +
        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];
 -      int i;
 +      size_t outlen;
  
 -      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(replaywin) {
                                        return;
                                }
                        } else {
 -                              for(i = n->received_seqno + 1; i < inpkt->seqno; i++)
 +                              for(int i = n->received_seqno + 1; i < inpkt->seqno; i++)
                                        n->late[(i / 8) % replaywin] |= 1 << i % 8;
                        }
                }
                n->received_seqno = inpkt->seqno;
                        
        if(n->received_seqno > MAX_SEQNO)
 -              keyexpires = 0;
 +              regenerate_key();
  
        /* Decompress the packet */
  
@@@ -375,13 -383,12 +375,12 @@@ static void send_udppacket(node_t *n, v
        vpn_packet_t *inpkt = origpkt;
        int nextpkt = 0;
        vpn_packet_t *outpkt;
 -      int origlen;
 -      int outlen, outpad;
 +      int origlen = origpkt->len;
 +      size_t outlen;
  #if defined(SOL_IP) && defined(IP_TOS)
        static int priority = 0;
 +      int origpriority = origpkt->priority;
  #endif
-       int sock;
 -      int origpriority;
  
        if(!n->status.reachable) {
                ifdebug(TRAFFIC) logger(LOG_INFO, "Trying to send UDP packet to unreachable node %s (%s)", n->name, n->hostname);
        /* Make sure we have a valid key */
  
        if(!n->status.validkey) {
 +              time_t now = time(NULL);
 +
                ifdebug(TRAFFIC) logger(LOG_INFO,
                                   "No valid key known yet for %s (%s), forwarding via TCP",
                                   n->name, n->hostname);
                return;
        }
  
 -      origlen = inpkt->len;
 -      origpriority = inpkt->priority;
 -
        /* Compress the packet */
  
        if(n->outcompression) {
        /* 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 */
  
-       for(sock = 0; sock < listen_sockets; sock++)
-               if(n->address.sa.sa_family == listen_socket[sock].sa.sa.sa_family)
-                       break;
-       if(sock >= listen_sockets)
-               sock = 0;                               /* If none is available, just use the first and hope for the best. */
+       if(n->address.sa.sa_family != listen_socket[n->sock].sa.sa.sa_family) {
+               for(int sock = 0; sock < listen_sockets; sock++) {
+                       if(n->address.sa.sa_family == listen_socket[sock].sa.sa.sa_family) {
+                               n->sock = sock;
+                               break;
+                       }
+               }
+       }
  
        /* Send the packet */
  
  #if defined(SOL_IP) && defined(IP_TOS)
        if(priorityinheritance && origpriority != priority
-          && listen_socket[sock].sa.sa.sa_family == AF_INET) {
+          && listen_socket[n->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[n->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
  
-       if(sendto(listen_socket[sock].udp, (char *) &inpkt->seqno, inpkt->len, 0, &(n->address.sa), SALEN(n->address.sa)) < 0 && !sockwouldblock(sockerrno)) {
+       if(sendto(listen_socket[n->sock].udp, (char *) &inpkt->seqno, inpkt->len, 0, &(n->address.sa), SALEN(n->address.sa)) < 0 && !sockwouldblock(sockerrno)) {
                if(sockmsgsize(sockerrno)) {
                        if(n->maxmtu >= origlen)
                                n->maxmtu = origlen - 1;
@@@ -499,15 -513,13 +500,15 @@@ end
  /*
    send a packet to the given vpn ip.
  */
 -void send_packet(const node_t *n, vpn_packet_t *packet) {
 +void send_packet(node_t *n, vpn_packet_t *packet) {
        node_t *via;
  
        if(n == myself) {
                if(overwrite_mac)
                         memcpy(packet->data, mymac.x, ETH_ALEN);
-               write_packet(packet);
 +              n->out_packets++;
 +              n->out_bytes += packet->len;
+               devops.write(packet);
                return;
        }
  
                return;
        }
  
 +      n->out_packets++;
 +      n->out_bytes += packet->len;
 +
        via = (packet->priority == -1 || n->via == myself) ? n->nexthop : n->via;
  
        if(via != n)
  /* 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;
 +      splay_node_t *node;
        edge_t *e;
        node_t *n = NULL;
        bool hard = false;
        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;
        return n;
  }
  
 -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);
                        return;
        }
  
 -      n->sock = sock;
++      n->sock = (intptr_t)data;
        receive_udppacket(n, &pkt);
  }
-       if(read_packet(&packet)) {
 +
 +void handle_device_data(int sock, short events, void *data) {
 +      vpn_packet_t packet;
 +
 +      packet.priority = 0;
 +
++      if(devops.read(&packet)) {
 +              myself->in_packets++;
 +              myself->in_bytes += packet.len;
 +              route(myself, &packet);
 +      }
 +}
diff --combined src/net_setup.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      net_setup.c -- Setup.
      Copyright (C) 1998-2005 Ivo Timmermans,
-                   2000-2010 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2012 Guus Sliepen <guus@tinc-vpn.org>
                    2006      Scott Lamb <slamb@slamb.org>
                    2010      Brandon Black <blblack@gmail.com>
  
  
  #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 "ecdsa.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;
+ devops_t devops;
  
 -bool read_rsa_public_key(connection_t *c) {
 +bool node_read_ecdsa_public_key(node_t *n) {
 +      if(ecdsa_active(&n->ecdsa))
 +              return true;
 +
 +      splay_tree_t *config_tree;
        FILE *fp;
        char *fname;
 -      char *key;
 +      char *p;
 +      bool result = false;
  
 -      if(!c->rsa_key) {
 -              c->rsa_key = RSA_new();
 -//            RSA_blinding_on(c->rsa_key, NULL);
 -      }
 +      xasprintf(&fname, "%s/hosts/%s", confbase, n->name);
  
 -      /* First, check for simple PublicKey statement */
 +      init_configuration(&config_tree);
 +      if(!read_config_file(config_tree, fname))
 +              goto exit;
  
 -      if(get_config_string(lookup_config(c->config_tree, "PublicKey"), &key)) {
 -              BN_hex2bn(&c->rsa_key->n, key);
 -              BN_hex2bn(&c->rsa_key->e, "FFFF");
 -              free(key);
 -              return true;
 +      /* First, check for simple ECDSAPublicKey statement */
 +
 +      if(get_config_string(lookup_config(config_tree, "ECDSAPublicKey"), &p)) {
 +              result = ecdsa_set_base64_public_key(&n->ecdsa, p);
 +              free(p);
 +              goto exit;
        }
  
 -      /* Else, check for PublicKeyFile statement and read it */
 +      /* Else, check for ECDSAPublicKeyFile statement and read it */
  
 -      if(get_config_string(lookup_config(c->config_tree, "PublicKeyFile"), &fname)) {
 -              fp = fopen(fname, "r");
 +      free(fname);
  
 -              if(!fp) {
 -                      logger(LOG_ERR, "Error reading RSA public key file `%s': %s",
 -                                 fname, strerror(errno));
 -                      free(fname);
 -                      return false;
 -              }
 +      if(!get_config_string(lookup_config(config_tree, "ECDSAPublicKeyFile"), &fname))
 +              xasprintf(&fname, "%s/hosts/%s", confbase, n->name);
  
 -              free(fname);
 -              c->rsa_key = PEM_read_RSAPublicKey(fp, &c->rsa_key, NULL, NULL);
 -              fclose(fp);
 +      fp = fopen(fname, "r");
  
 -              if(c->rsa_key)
 -                      return true;            /* Woohoo. */
 +      if(!fp) {
 +              logger(LOG_ERR, "Error reading ECDSA public key file `%s': %s", fname, strerror(errno));
 +              goto exit;
 +      }
  
 -              /* If it fails, try PEM_read_RSA_PUBKEY. */
 -              fp = fopen(fname, "r");
 +      result = ecdsa_read_pem_public_key(&n->ecdsa, fp);
 +      fclose(fp);
  
 -              if(!fp) {
 -                      logger(LOG_ERR, "Error reading RSA public key file `%s': %s",
 -                                 fname, strerror(errno));
 -                      free(fname);
 -                      return false;
 -              }
 +exit:
 +      exit_configuration(&config_tree);
 +      free(fname);
 +      return result;
 +}
  
 -              free(fname);
 -              c->rsa_key = PEM_read_RSA_PUBKEY(fp, &c->rsa_key, NULL, NULL);
 -              fclose(fp);
 +bool read_ecdsa_public_key(connection_t *c) {
 +      FILE *fp;
 +      char *fname;
 +      char *p;
 +      bool result;
  
 -              if(c->rsa_key) {
 -//                            RSA_blinding_on(c->rsa_key, NULL);
 -                      return true;
 -              }
 +      /* First, check for simple ECDSAPublicKey statement */
  
 -              logger(LOG_ERR, "Reading RSA public key file `%s' failed: %s",
 -                         fname, strerror(errno));
 -              return false;
 +      if(get_config_string(lookup_config(c->config_tree, "ECDSAPublicKey"), &p)) {
 +              result = ecdsa_set_base64_public_key(&c->ecdsa, p);
 +              free(p);
 +              return result;
        }
  
 -      /* Else, check if a harnessed public key is in the config file */
 +      /* Else, check for ECDSAPublicKeyFile statement and read it */
 +
 +      if(!get_config_string(lookup_config(c->config_tree, "ECDSAPublicKeyFile"), &fname))
 +              xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
  
 -      xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
        fp = fopen(fname, "r");
  
        if(!fp) {
 -              logger(LOG_ERR, "Error reading RSA public key file `%s': %s", fname, strerror(errno));
 +              logger(LOG_ERR, "Error reading ECDSA public key file `%s': %s",
 +                         fname, strerror(errno));
                free(fname);
                return false;
        }
  
 -      c->rsa_key = PEM_read_RSAPublicKey(fp, &c->rsa_key, NULL, NULL);
 +      result = ecdsa_read_pem_public_key(&c->ecdsa, fp);
        fclose(fp);
 +
 +      if(!result) 
 +              logger(LOG_ERR, "Reading ECDSA public key file `%s' failed: %s", fname, strerror(errno));
        free(fname);
 +      return result;
 +}
  
 -      if(c->rsa_key)
 -              return true;
 +bool read_rsa_public_key(connection_t *c) {
 +      FILE *fp;
 +      char *fname;
 +      char *n;
 +      bool result;
  
 -      /* Try again with PEM_read_RSA_PUBKEY. */
 +      /* First, check for simple PublicKey statement */
 +
 +      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))
 +              xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
  
 -      xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
        fp = fopen(fname, "r");
  
        if(!fp) {
-               logger(LOG_ERR, "Error reading RSA public key file `%s': %s",
-                          fname, strerror(errno));
+               logger(LOG_ERR, "Error reading RSA public key file `%s': %s", fname, strerror(errno));
                free(fname);
                return false;
        }
  
 -      c->rsa_key = PEM_read_RSA_PUBKEY(fp, &c->rsa_key, NULL, NULL);
 -//    RSA_blinding_on(c->rsa_key, NULL);
 +      result = rsa_read_pem_public_key(&c->rsa, fp);
        fclose(fp);
 +
 +      if(!result) 
 +              logger(LOG_ERR, "Reading RSA public key file `%s' failed: %s", fname, strerror(errno));
        free(fname);
 +      return result;
 +}
  
 -      if(c->rsa_key)
 -              return true;
 +static bool read_ecdsa_private_key(void) {
 +      FILE *fp;
 +      char *fname;
 +      bool result;
  
 -      logger(LOG_ERR, "No public key for %s specified!", c->name);
 +      /* Check for PrivateKeyFile statement and read it */
 +
 +      if(!get_config_string(lookup_config(config_tree, "ECDSAPrivateKeyFile"), &fname))
 +              xasprintf(&fname, "%s/ecdsa_key.priv", confbase);
 +
 +      fp = fopen(fname, "r");
 +
 +      if(!fp) {
-               logger(LOG_ERR, "Error reading ECDSA private key file `%s': %s",
-                          fname, strerror(errno));
++              logger(LOG_ERR, "Error reading ECDSA private key file `%s': %s", fname, strerror(errno));
 +              free(fname);
 +              return false;
 +      }
 +
 +#if !defined(HAVE_MINGW) && !defined(HAVE_CYGWIN)
 +      struct stat s;
 +
 +      if(fstat(fileno(fp), &s)) {
 +              logger(LOG_ERR, "Could not stat ECDSA private key file `%s': %s'", fname, strerror(errno));
 +              free(fname);
 +              return false;
 +      }
  
 -      return false;
 +      if(s.st_mode & ~0100700)
 +              logger(LOG_WARNING, "Warning: insecure file permissions for ECDSA private key file `%s'!", fname);
 +#endif
 +
 +      result = ecdsa_read_pem_private_key(&myself->connection->ecdsa, fp);
 +      fclose(fp);
 +
 +      if(!result) 
 +              logger(LOG_ERR, "Reading ECDSA private key file `%s' failed: %s", fname, strerror(errno));
 +      free(fname);
 +      return result;
  }
  
  static bool read_rsa_private_key(void) {
        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(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(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(void) {
 +      if(timeout_initialized(&keyexpire_event)) {
 +              ifdebug(STATUS) logger(LOG_INFO, "Expiring symmetric keys");
 +              event_del(&keyexpire_event);
 +              send_key_changed();
 +      } else {
 +              timeout_set(&keyexpire_event, keyexpire_handler, NULL);
        }
  
 -      free(fname);
 -      return true;
 +      event_add(&keyexpire_event, &(struct timeval){keylifetime, 0});
  }
  
  /*
@@@ -289,7 -217,7 +288,7 @@@ void load_all_subnets(void) 
        struct dirent *ent;
        char *dname;
        char *fname;
 -      avl_tree_t *config_tree;
 +      splay_tree_t *config_tree;
        config_t *cfg;
        subnet_t *s, *s2;
        node_t *n;
  static bool setup_myself(void) {
        config_t *cfg;
        subnet_t *subnet;
-       char *name, *hostname, *mode, *afname, *cipher, *digest;
+       char *name, *hostname, *mode, *afname, *cipher, *digest, *type;
        char *fname = NULL;
        char *address = NULL;
        char *envp[5];
        myself->connection->hostname = xstrdup("MYSELF");
  
        myself->connection->options = 0;
 -      myself->connection->protocol_version = PROT_CURRENT;
 +      myself->connection->protocol_major = PROT_MAJOR;
 +      myself->connection->protocol_minor = PROT_MINOR;
  
        if(!get_config_string(lookup_config(config_tree, "Name"), &name)) {     /* Not acceptable */
                logger(LOG_ERR, "Name for tinc daemon required!");
        read_config_file(config_tree, fname);
        free(fname);
  
 +      get_config_bool(lookup_config(config_tree, "ExperimentalProtocol"), &experimental);
 +
 +      if(experimental && !read_ecdsa_private_key())
 +              return false;
 +
        if(!read_rsa_private_key())
                return false;
  
                myself->options |= OPTION_CLAMP_MSS;
  
        get_config_bool(lookup_config(config_tree, "PriorityInheritance"), &priorityinheritance);
+       get_config_bool(lookup_config(config_tree, "DecrementTTL"), &decrement_ttl);
+       get_config_bool(lookup_config(config_tree, "Broadcast"), &broadcast);
  
  #if !defined(SOL_IP) || !defined(IP_TOS)
        if(priorityinheritance)
  
        /* Generate packet encryption key */
  
 -      if(get_config_string
 -         (lookup_config(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(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;
 -      
 -      /* Check if we want to use message authentication codes... */
 +      regenerate_key();
  
 -      if(get_config_string(lookup_config(config_tree, "Digest"), &digest)) {
 -              if(!strcasecmp(digest, "none")) {
 -                      myself->indigest = NULL;
 -              } else {
 -                      myself->indigest = EVP_get_digestbyname(digest);
 +      /* Check if we want to use message authentication codes... */
  
 -                      if(!myself->indigest) {
 -                              logger(LOG_ERR, "Unrecognized digest type!");
 -                              return false;
 -                      }
 -              }
 -      } else
 -              myself->indigest = EVP_sha1();
 +      int maclength = 4;
 +      get_config_int(lookup_config(config_tree, "MACLength"), &maclength);
  
 -      myself->connection->outdigest = EVP_sha1();
 +      if(maclength < 0) {
 +              logger(LOG_ERR, "Bogus MAC length!");
 +              return false;
 +      }
  
 -      if(get_config_int(lookup_config(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;
 +      if(!get_config_string(lookup_config(config_tree, "Digest"), &digest))
 +              digest = xstrdup("sha1");
  
 -      myself->connection->outmaclength = 0;
 +      if(!digest_open_by_name(&myself->indigest, digest, maclength)) {
 +              logger(LOG_ERR, "Unrecognized digest type!");
 +              return false;
 +      }
  
        /* Compression */
  
  
        /* Open device */
  
-       if(!setup_device())
+       devops = os_devops;
+       if(get_config_string(lookup_config(config_tree, "DeviceType"), &type)) {
+               if(!strcasecmp(type, "dummy"))
+                       devops = dummy_devops;
+               else if(!strcasecmp(type, "raw_socket"))
+                       devops = raw_socket_devops;
+ #ifdef ENABLE_UML
+               else if(!strcasecmp(type, "uml"))
+                       devops = uml_devops;
+ #endif
+ #ifdef ENABLE_VDE
+               else if(!strcasecmp(type, "vde"))
+                       devops = vde_devops;
+ #endif
+       }
+       if(!devops.setup())
                return false;
  
-                       close_device();
 +      if(device_fd >= 0) {
 +              event_set(&device_ev, device_fd, EV_READ|EV_PERSIST, handle_device_data, NULL);
 +
 +              if (event_add(&device_ev, NULL) < 0) {
 +                      logger(LOG_ERR, "event_add failed: %s", strerror(errno));
++                      devops.close();
 +                      return false;
 +              }
 +      }
 +
        /* Run tinc-up script to further initialize the tap interface */
        xasprintf(&envp[0], "NETNAME=%s", netname ? : "");
        xasprintf(&envp[1], "DEVICE=%s", device ? : "");
  
        execute_script("tinc-up", envp);
  
 -      for(i = 0; i < 5; i++)
 +      for(i = 0; i < 4; i++)
                free(envp[i]);
  
        /* Run subnet-up scripts for our own subnets */
  
        /* Open sockets */
  
-       get_config_string(lookup_config(config_tree, "BindToAddress"), &address);
+       listen_sockets = 0;
+       cfg = lookup_config(config_tree, "BindToAddress");
  
-       hint.ai_family = addressfamily;
-       hint.ai_socktype = SOCK_STREAM;
-       hint.ai_protocol = IPPROTO_TCP;
-       hint.ai_flags = AI_PASSIVE;
+       do {
+               get_config_string(cfg, &address);
+               if(cfg)
+                       cfg = lookup_config_next(config_tree, cfg);
  
-       err = getaddrinfo(address, myport, &hint, &ai);
+               hint.ai_family = addressfamily;
+               hint.ai_socktype = SOCK_STREAM;
+               hint.ai_protocol = IPPROTO_TCP;
+               hint.ai_flags = AI_PASSIVE;
  
-       if(err || !ai) {
-               logger(LOG_ERR, "System call `%s' failed: %s", "getaddrinfo",
-                          gai_strerror(err));
-               return false;
-       }
+               err = getaddrinfo(address, myport, &hint, &ai);
+               free(address);
  
-       listen_sockets = 0;
+               if(err || !ai) {
+                       logger(LOG_ERR, "System call `%s' failed: %s", "getaddrinfo",
+                                  gai_strerror(err));
+                       return false;
+               }
  
-       for(aip = ai; aip; aip = aip->ai_next) {
-               listen_socket[listen_sockets].tcp =
-                       setup_listen_socket((sockaddr_t *) aip->ai_addr);
+               for(aip = ai; aip; aip = aip->ai_next) {
+                       if(listen_sockets >= MAXSOCKETS) {
+                               logger(LOG_ERR, "Too many listening sockets");
+                               return false;
+                       }
  
-               if(listen_socket[listen_sockets].tcp < 0)
-                       continue;
+                       listen_socket[listen_sockets].tcp =
+                               setup_listen_socket((sockaddr_t *) aip->ai_addr);
  
-               listen_socket[listen_sockets].udp =
-                       setup_vpn_in_socket((sockaddr_t *) aip->ai_addr);
+                       if(listen_socket[listen_sockets].tcp < 0)
+                               continue;
  
-               if(listen_socket[listen_sockets].udp < 0) {
-                       close(listen_socket[listen_sockets].tcp);
-                       continue;
-               }
+                       listen_socket[listen_sockets].udp =
+                               setup_vpn_in_socket((sockaddr_t *) aip->ai_addr);
  
-               event_set(&listen_socket[listen_sockets].ev_tcp,
-                                 listen_socket[listen_sockets].tcp,
-                                 EV_READ|EV_PERSIST,
-                                 handle_new_meta_connection, NULL);
-               if(event_add(&listen_socket[listen_sockets].ev_tcp, NULL) < 0) {
-                       logger(LOG_ERR, "event_add failed: %s", strerror(errno));
-                       abort();
-               }
 -                      if(listen_socket[listen_sockets].udp < 0)
++                      if(listen_socket[listen_sockets].udp < 0) {
++                              close(listen_socket[listen_sockets].tcp);
+                               continue;
++                      }
 +
-               event_set(&listen_socket[listen_sockets].ev_udp,
-                                 listen_socket[listen_sockets].udp,
-                                 EV_READ|EV_PERSIST,
-                                 handle_incoming_vpn_data, NULL);
-               if(event_add(&listen_socket[listen_sockets].ev_udp, NULL) < 0) {
-                       logger(LOG_ERR, "event_add failed: %s", strerror(errno));
-                       abort();
-               }
++                      event_set(&listen_socket[listen_sockets].ev_tcp,
++                                        listen_socket[listen_sockets].tcp,
++                                        EV_READ|EV_PERSIST,
++                                        handle_new_meta_connection, NULL);
++                      if(event_add(&listen_socket[listen_sockets].ev_tcp, NULL) < 0) {
++                              logger(LOG_ERR, "event_add failed: %s", strerror(errno));
++                              abort();
++                      }
 +
-               ifdebug(CONNECTIONS) {
-                       hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr);
-                       logger(LOG_NOTICE, "Listening on %s", hostname);
-                       free(hostname);
-               }
++                      event_set(&listen_socket[listen_sockets].ev_udp,
++                                        listen_socket[listen_sockets].udp,
++                                        EV_READ|EV_PERSIST,
++                                        handle_incoming_vpn_data, (void *)(intptr_t)listen_sockets);
++                      if(event_add(&listen_socket[listen_sockets].ev_udp, NULL) < 0) {
++                              logger(LOG_ERR, "event_add failed: %s", strerror(errno));
++                              abort();
++                      }
  
-               memcpy(&listen_socket[listen_sockets].sa, aip->ai_addr, aip->ai_addrlen);
-               listen_sockets++;
+                       ifdebug(CONNECTIONS) {
+                               hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr);
+                               logger(LOG_NOTICE, "Listening on %s", hostname);
+                               free(hostname);
+                       }
  
-               if(listen_sockets >= MAXSOCKETS) {
-                       logger(LOG_WARNING, "Maximum of %d listening sockets reached", MAXSOCKETS);
-                       break;
+                       memcpy(&listen_socket[listen_sockets].sa, aip->ai_addr, aip->ai_addrlen);
+                       listen_sockets++;
                }
-       }
  
-       freeaddrinfo(ai);
+               freeaddrinfo(ai);
+       } while(cfg);
  
        if(listen_sockets)
                logger(LOG_NOTICE, "Ready");
    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;
                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);
  
        for(i = 0; i < 4; i++)
                free(envp[i]);
  
-       close_device();
+       devops.close();
  
        return;
  }
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-2010 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2012 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"
@@@ -33,8 -34,6 +33,6 @@@
  #include "utils.h"
  #include "xalloc.h"
  
- #include <assert.h>
  /* Needed on Mac OS/X */
  #ifndef SOL_TCP
  #define SOL_TCP IPPROTO_TCP
@@@ -71,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, (void *)&option, sizeof(option));
 +      setsockopt(c->socket, SOL_TCP, TCP_NODELAY, (void *)&option, sizeof option);
  #endif
  
  #if defined(SOL_IP) && defined(IP_TOS) && defined(IPTOS_LOWDELAY)
        option = IPTOS_LOWDELAY;
 -      setsockopt(c->socket, SOL_IP, IP_TOS, (void *)&option, sizeof(option));
 +      setsockopt(c->socket, SOL_IP, IP_TOS, (void *)&option, sizeof option);
  #endif
  }
  
@@@ -109,63 -108,6 +107,6 @@@ static bool bind_to_interface(int sd) 
        return true;
  }
  
- static bool bind_to_address(connection_t *c) {
-       char *node;
-       struct addrinfo *ai_list;
-       struct addrinfo *ai_ptr;
-       struct addrinfo ai_hints;
-       int status;
-       assert(c != NULL);
-       assert(c->socket >= 0);
-       node = NULL;
-       if(!get_config_string(lookup_config(config_tree, "BindToAddress"),
-                               &node))
-               return true;
-       assert(node != NULL);
-       memset(&ai_hints, 0, sizeof(ai_hints));
-       ai_hints.ai_family = c->address.sa.sa_family;
-       /* We're called from `do_outgoing_connection' only. */
-       ai_hints.ai_socktype = SOCK_STREAM;
-       ai_hints.ai_protocol = IPPROTO_TCP;
-       ai_list = NULL;
-       status = getaddrinfo(node, /* service = */ NULL,
-                       &ai_hints, &ai_list);
-       if(status) {
-               logger(LOG_WARNING, "Error looking up %s port %s: %s",
-                               node, "any", gai_strerror(status));
-               free(node);
-               return false;
-       }
-       assert(ai_list != NULL);
-       status = -1;
-       for(ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
-               status = bind(c->socket,
-                               ai_list->ai_addr, ai_list->ai_addrlen);
-               if(!status)
-                       break;
-       }
-       if(status) {
-               logger(LOG_ERR, "Can't bind to %s/tcp: %s", node, sockstrerror(sockerrno));
-       } else ifdebug(CONNECTIONS) {
-               logger(LOG_DEBUG, "Successfully bound outgoing "
-                               "TCP socket to %s", node);
-       }
-       free(node);
-       freeaddrinfo(ai_list);
-       return status ? false : true;
- }
  int setup_listen_socket(const sockaddr_t *sa) {
        int nfd;
        char *addrstr;
                return -1;
        }
  
+ #ifdef FD_CLOEXEC
+       fcntl(nfd, F_SETFD, FD_CLOEXEC);
+ #endif
        /* Optimize TCP settings */
  
        option = 1;
 -      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof(option));
 +      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&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, (void *)&ifr, sizeof(ifr))) {
 +              if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof ifr)) {
                        closesocket(nfd);
                        logger(LOG_ERR, "Can't bind to interface %s: %s", iface,
                                   strerror(sockerrno));
@@@ -237,6 -183,10 +182,10 @@@ int setup_vpn_in_socket(const sockaddr_
                return -1;
        }
  
+ #ifdef FD_CLOEXEC
+       fcntl(nfd, F_SETFD, FD_CLOEXEC);
+ #endif
  #ifdef O_NONBLOCK
        {
                int flags = fcntl(nfd, F_GETFL);
  #endif
  
        option = 1;
 -      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof(option));
 +      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof option);
  
        if(udp_rcvbuf && setsockopt(nfd, SOL_SOCKET, SO_RCVBUF, (void *)&udp_rcvbuf, sizeof(udp_rcvbuf)))
                logger(LOG_WARNING, "Can't set UDP SO_RCVBUF to %i: %s", udp_rcvbuf, strerror(errno));
        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",
@@@ -344,13 -295,12 +293,13 @@@ 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) {
 +bool do_outgoing_connection(connection_t *c) {
        char *address, *port, *space;
        int result;
  
@@@ -364,10 -314,10 +313,10 @@@ 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;
 -                      return;
 +                      connection_del(c);
 +                      return false;
                }
  
                get_config_string(c->outgoing->cfg, &address);
  
        c->socket = socket(c->address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP);
  
+ #ifdef FD_CLOEXEC
+       fcntl(c->socket, F_SETFD, FD_CLOEXEC);
+ #endif
        if(c->socket == -1) {
                ifdebug(CONNECTIONS) logger(LOG_ERR, "Creating socket for %s failed: %s", c->hostname, sockstrerror(sockerrno));
                goto begin;
  #endif
  
        bind_to_interface(c->socket);
-       bind_to_address(c);
  
        /* Optimize TCP settings */
  
        if(result == -1) {
                if(sockinprogress(sockerrno)) {
                        c->status.connecting = true;
 -                      return;
 +                      return true;
                }
  
                closesocket(c->socket);
  
        finish_connecting(c);
  
 -      return;
 +      return true;
 +}
 +
 +static void handle_meta_write(int sock, short events, void *data) {
 +      ifdebug(META) logger(LOG_DEBUG, "handle_meta_write() called");
 +
 +      connection_t *c = data;
 +
 +      ssize_t outlen = send(c->socket, c->outbuf.data + c->outbuf.offset, c->outbuf.len - c->outbuf.offset, 0);
 +      if(outlen <= 0) {
 +              logger(LOG_ERR, "Onoes, outlen = %d (%s)", (int)outlen, strerror(errno));
 +              terminate_connection(c, c->status.active);
 +              return;
 +      }
 +
 +      buffer_read(&c->outbuf, outlen);
 +      if(!c->outbuf.len && event_initialized(&c->outevent))
 +              event_del(&c->outevent);
  }
  
  void setup_outgoing_connection(outgoing_t *outgoing) {
        connection_t *c;
        node_t *n;
  
 -      outgoing->event = NULL;
 +      if(event_initialized(&outgoing->ev))
 +              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);
 +      if (do_outgoing_connection(c)) {
 +              event_set(&c->inevent, c->socket, EV_READ | EV_PERSIST, handle_meta_connection_data, c);
 +              event_set(&c->outevent, c->socket, EV_WRITE | EV_PERSIST, handle_meta_write, c);
 +              event_add(&c->inevent, NULL);
 +      }
  }
  
  /*
    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_set(&c->outevent, c->socket, EV_WRITE | EV_PERSIST, handle_meta_write, c);
 +      event_add(&c->inevent, NULL);
 +              
        configure_tcp(c);
  
        connection_add(c);
  
        c->allow_request = ID;
        send_id(c);
 -
 -      return true;
  }
  
  static void free_outgoing(outgoing_t *outgoing) {
@@@ -586,7 -515,7 +538,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
  #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 "ecdh.h"
  #include "subnet.h"
  
  typedef struct node_status_t {
 -      unsigned int unused_active:1;                   /* 1 if active (not used for nodes) */
 -      unsigned int validkey:1;                                /* 1 if we currently have a valid key for him */
 -      unsigned int unused_waitingforkey:1;            /* 1 if we already sent out a request */
 -      unsigned int visited:1;                         /* 1 if this node has been visited by one of the graph algorithms */
 -      unsigned int reachable:1;                       /* 1 if this node is reachable in the graph */
 -      unsigned int indirect:1;                                /* 1 if this node is not directly reachable by us */
 -      unsigned int unused:26;
 +      unsigned int unused_active:1;           /* 1 if active (not used for nodes) */
 +      unsigned int validkey:1;                /* 1 if we currently have a valid key for him */
 +      unsigned int unused_waitingforkey:1;    /* 1 if we already sent out a request */
 +      unsigned int visited:1;                 /* 1 if this node has been visited by one of the graph algorithms */
 +      unsigned int reachable:1;               /* 1 if this node is reachable in the graph */
 +      unsigned int indirect:1;                /* 1 if this node is not directly reachable by us */
 +      unsigned int ecdh:1;                    /* 1 if this node supports ECDH key exchange */
 +      unsigned int unused:25;
  } node_status_t;
  
  typedef struct node_t {
        char *name;                             /* name of this node */
        uint32_t options;                       /* options turned on for this node */
  
+       int sock;                               /* Socket to use for outgoing UDP packets */
        sockaddr_t address;                     /* his real (internet) ip to send UDP packets to */
        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 */
 +      ecdsa_t ecdsa;                          /* His public ECDSA key */
 +      ecdh_t ecdh;                            /* State for ECDH key exchange */
 +
 +      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 */
 +
 +      uint64_t in_packets;
 +      uint64_t in_bytes;
 +      uint64_t out_packets;
 +      uint64_t out_bytes;
  } 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);
@@@ -100,8 -99,7 +101,8 @@@ 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 bool dump_traffic(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/openssl/prf.c
index 13841c4,0000000..1c432c7
mode 100644,000000..100644
--- /dev/null
@@@ -1,73 -1,0 +1,75 @@@
 +/*
 +    prf.c -- Pseudo-Random Function for key material generation
 +    Copyright (C) 2011 Guus Sliepen <guus@tinc-vpn.org>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License along
 +    with this program; if not, write to the Free Software Foundation, Inc.,
 +    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 +*/
 +
 +#include "system.h"
 +
++#include <openssl/obj_mac.h>
++
 +#include "digest.h"
 +#include "prf.h"
 +
 +/* Generate key material from a master secret and a seed, based on RFC 4346 section 5.
 +   We use SHA512 instead of MD5 and SHA1.
 + */
 +
 +static bool prf_xor(int nid, const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, ssize_t outlen) {
 +      digest_t digest;
 +      
 +      if(!digest_open_by_nid(&digest, nid, -1))
 +              return false;
 +
 +      if(!digest_set_key(&digest, secret, secretlen))
 +              return false;
 +
 +      size_t len = digest_length(&digest);
 +
 +      /* Data is what the "inner" HMAC function processes.
 +         It consists of the previous HMAC result plus the seed.
 +       */
 +
 +      char data[len + seedlen];
 +      memset(data, 0, len);
 +      memcpy(data + len, seed, seedlen);
 +
 +      char hash[len];
 +
 +      while(outlen > 0) {
 +              /* Inner HMAC */
 +              digest_create(&digest, data, len + seedlen, data);
 +
 +              /* Outer HMAC */
 +              digest_create(&digest, data, len + seedlen, hash);
 +
 +              /* XOR the results of the outer HMAC into the out buffer */
 +              for(int i = 0; i < len && i < outlen; i++)
 +                      *out++ ^= hash[i];
 +
 +              outlen -= len;
 +      }
 +
 +      digest_close(&digest);
 +      return true;
 +}
 +
 +bool prf(const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, size_t outlen) {
 +      /* This construction allows us to easily switch back to a scheme where the PRF is calculated using two different digest algorithms. */
 +      memset(out, 0, outlen);
 +
 +      return prf_xor(NID_sha512, secret, secretlen, seed, seedlen, out, outlen);
 +}
diff --combined src/protocol.c
  
  bool tunnelserver = false;
  bool strictsubnets = false;
 +bool experimental = 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 */
@@@ -50,10 -49,10 +50,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) {
+       if(c == everyone) {
 -              broadcast_meta(NULL, buffer, len);
 +              broadcast_meta(NULL, request, len);
                return true;
        } else
 -              return send_meta(c, buffer, len);
 +              return send_meta(c, request, len);
  }
  
 -void forward_request(connection_t *from) {
 -      int request;
 -
 +void forward_request(connection_t *from, char *request) {
 +      /* Note: request is not zero terminated anymore after a call to this function! */
        ifdebug(PROTOCOL) {
 -              sscanf(from->buffer, "%d", &request);
                ifdebug(META)
                        logger(LOG_DEBUG, "Forwarding %s from %s (%s): %s",
 -                                 request_name[request], from->name, from->hostname,
 -                                 from->buffer);
 +                                 request_name[atoi(request)], from->name, from->hostname, request);
                else
                        logger(LOG_DEBUG, "Forwarding %s from %s (%s)",
 -                                 request_name[request], from->name, from->hostname);
 +                                 request_name[atoi(request)], from->name, from->hostname);
        }
  
 -      from->buffer[from->reqlen - 1] = '\n';
 -
 -      broadcast_meta(from, from->buffer, from->reqlen);
 +      int len = strlen(request);
 +      request[len++] = '\n';
 +      broadcast_meta(from, request, len);
  }
  
 -bool receive_request(connection_t *c) {
 -      int request;
 +bool receive_request(connection_t *c, char *request) {
 +      int reqno = atoi(request);
  
 -      if(sscanf(c->buffer, "%d", &request) == 1) {
 -              if((request < 0) || (request >= LAST) || !request_handlers[request]) {
 +      if(reqno || *request == '0') {
 +              if((reqno < 0) || (reqno >= LAST) || !request_handlers[reqno]) {
                        ifdebug(META)
                                logger(LOG_DEBUG, "Unknown request from %s (%s): %s",
 -                                         c->name, c->hostname, c->buffer);
 +                                         c->name, c->hostname, request);
                        else
                                logger(LOG_ERR, "Unknown request from %s (%s)",
                                           c->name, c->hostname);
                        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 {
@@@ -176,38 -180,42 +176,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 = {NULL};
  
        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;
 +static 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);
 +
 +      if(timeout_initialized(&past_request_event))
 +              event_del(&past_request_event);
  }
diff --combined src/protocol_auth.c
  
  #include "system.h"
  
 -#include <openssl/sha.h>
 -#include <openssl/rand.h>
 -#include <openssl/err.h>
 -#include <openssl/evp.h>
 -
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "conf.h"
  #include "connection.h"
 +#include "control.h"
 +#include "control_common.h"
 +#include "cipher.h"
 +#include "crypto.h"
 +#include "digest.h"
  #include "edge.h"
  #include "graph.h"
  #include "logger.h"
  #include "net.h"
  #include "netutl.h"
  #include "node.h"
 +#include "prf.h"
  #include "protocol.h"
 +#include "rsa.h"
  #include "utils.h"
  #include "xalloc.h"
  
  bool send_id(connection_t *c) {
 -      return send_request(c, "%d %s %d", ID, myself->connection->name,
 -                                              myself->connection->protocol_version);
 +      gettimeofday(&c->start, NULL);
 +
 +      int minor = 0;
 +
 +      if(experimental) {
 +              if(c->config_tree && !read_ecdsa_public_key(c))
 +                      minor = 1;
 +              else
 +                      minor = myself->connection->protocol_minor;
 +      }
 +
 +      return send_request(c, "%d %s %d.%d", ID, myself->connection->name, myself->connection->protocol_major, minor);
  }
  
 -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.%d", name, &c->protocol_major, &c->protocol_minor) < 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)) {
@@@ -97,9 -76,9 +97,9 @@@
  
        /* Check if version matches */
  
 -      if(c->protocol_version != myself->connection->protocol_version) {
 -              logger(LOG_ERR, "Peer %s (%s) uses incompatible version %d",
 -                         c->name, c->hostname, c->protocol_version);
 +      if(c->protocol_major != myself->connection->protocol_major) {
 +              logger(LOG_ERR, "Peer %s (%s) uses incompatible version %d.%d",
 +                         c->name, c->hostname, c->protocol_major, c->protocol_minor);
                return false;
        }
  
                return send_ack(c);
        }
  
 +      if(!experimental)
 +              c->protocol_minor = 0;
 +
        if(!c->config_tree) {
                init_configuration(&c->config_tree);
  
                                   c->name);
                        return false;
                }
 -      }
  
 -      if(!read_rsa_public_key(c)) {
 -              return false;
 +              if(experimental && c->protocol_minor >= 2)
 +                      if(!read_ecdsa_public_key(c))
 +                              return false;
 +      } else {
 +              if(c->protocol_minor && !ecdsa_active(&c->ecdsa))
 +                      c->protocol_minor = 1;
        }
  
        c->allow_request = METAKEY;
  
 -      return send_metakey(c);
 +      if(c->protocol_minor >= 2)
 +              return send_metakey_ec(c);
 +      else
 +              return send_metakey(c);
  }
  
 -bool send_metakey(connection_t *c) {
 -      bool x;
 +bool send_metakey_ec(connection_t *c) {
 +      logger(LOG_DEBUG, "Sending ECDH metakey to %s", c->name);
 +
 +      size_t siglen = ecdsa_size(&myself->connection->ecdsa);
 +
 +      char key[(ECDH_SIZE + siglen) * 2 + 1];
  
 -      int len = RSA_size(c->rsa_key);
 +      // TODO: include nonce? Use relevant parts of SSH or TLS protocol
  
 -      /* Allocate buffers for the meta key */
 +      if(!ecdh_generate_public(&c->ecdh, key))
 +              return false;
 +
 +      if(!ecdsa_sign(&myself->connection->ecdsa, key, ECDH_SIZE, key + ECDH_SIZE))
 +              return false;
  
 -      char buffer[2 * len + 1];
 +      b64encode(key, key, ECDH_SIZE + siglen);
        
 -      c->outkey = xrealloc(c->outkey, len);
 +      return send_request(c, "%d %s", METAKEY, key);
 +}
  
 -      if(!c->outctx)
 -              c->outctx = xmalloc_and_zero(sizeof(*c->outctx));
 +bool send_metakey(connection_t *c) {
 +      if(!read_rsa_public_key(c))
 +              return false;
  
 -      /* Copy random data to the buffer */
 +      if(!cipher_open_blowfish_ofb(&c->outcipher))
 +              return false;
 +      
 +      if(!digest_open_sha1(&c->outdigest, -1))
 +              return false;
 +
 +      size_t len = rsa_size(&c->rsa);
 +      char key[len];
 +      char enckey[len];
 +      char hexkey[2 * len + 1];
  
 -      RAND_pseudo_bytes((unsigned char *)c->outkey, len);
 +      /* Create a random key */
 +
 +      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);
 +              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);
  
        /* 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);
 +      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;
 +}
  
 -      /* Further outgoing requests are encrypted with the key we just generated */
 +static bool metakey_ec_h(connection_t *c, const char *request) {
 +      size_t siglen = ecdsa_size(&c->ecdsa);
 +      char key[MAX_STRING_SIZE];
  
 -      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;
 -              }
 +      logger(LOG_DEBUG, "Got ECDH metakey from %s", c->name);
  
 -              c->status.encryptout = true;
 +      if(sscanf(request, "%*d " MAX_STRING, key) != 1) {
 +              logger(LOG_ERR, "Got bad %s from %s (%s)", "METAKEY", c->name, c->hostname);
 +              return false;
        }
  
 -      return x;
 -}
 +      int inlen = b64decode(key, key, sizeof key);
  
 -bool metakey_h(connection_t *c) {
 -      char buffer[MAX_STRING_SIZE];
 -      int cipher, digest, maclength, compression;
 -      int len;
 +      if(inlen != (ECDH_SIZE + siglen)) {
 +              logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong keylength");
 +              return false;
 +      }
  
 -      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(!ecdsa_verify(&c->ecdsa, key, ECDH_SIZE, key + ECDH_SIZE)) {
 +              logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "invalid ECDSA signature");
                return false;
        }
  
 -      len = RSA_size(myself->connection->rsa_key);
 +      char shared[ECDH_SHARED_SIZE];
  
 -      /* Check if the length of the meta key is all right */
 +      if(!ecdh_compute_shared(&c->ecdh, key, shared))
 +              return false;
  
 -      if(strlen(buffer) != len * 2) {
 -              logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong keylength");
 +      /* Update our crypto end */
 +
 +      if(!cipher_open_by_name(&c->incipher, "aes-256-ofb"))
 +              return false;
 +      if(!digest_open_by_name(&c->indigest, "sha512", -1))
                return false;
 +      if(!cipher_open_by_name(&c->outcipher, "aes-256-ofb"))
 +              return false;
 +      if(!digest_open_by_name(&c->outdigest, "sha512", -1))
 +              return false;
 +
 +      size_t mykeylen = cipher_keylength(&c->incipher);
 +      size_t hiskeylen = cipher_keylength(&c->outcipher);
 +
 +      char *mykey;
 +      char *hiskey;
 +      char *seed;
 +      
 +      if(strcmp(myself->name, c->name) < 0) {
 +              mykey = key;
 +              hiskey = key + mykeylen * 2;
 +              xasprintf(&seed, "tinc TCP key expansion %s %s", myself->name, c->name);
 +      } else {
 +              mykey = key + hiskeylen * 2;
 +              hiskey = key;
 +              xasprintf(&seed, "tinc TCP key expansion %s %s", c->name, myself->name);
        }
  
 -      /* Allocate buffers for the meta key */
 +      if(!prf(shared, ECDH_SHARED_SIZE, seed, strlen(seed), key, hiskeylen * 2 + mykeylen * 2))
 +              return false;
  
 -      c->inkey = xrealloc(c->inkey, len);
 +      free(seed);
  
 -      if(!c->inctx)
 -              c->inctx = xmalloc_and_zero(sizeof(*c->inctx));
 +      cipher_set_key(&c->incipher, mykey, false);
 +      digest_set_key(&c->indigest, mykey + mykeylen, mykeylen);
  
 -      /* Convert the challenge from hexadecimal back to binary */
 +      cipher_set_key(&c->outcipher, hiskey, true);
 +      digest_set_key(&c->outdigest, hiskey + hiskeylen, hiskeylen);
  
 -      hex2bin(buffer, buffer, len);
 +      c->status.decryptin = true;
 +      c->status.encryptout = true;
 +      c->allow_request = CHALLENGE;
  
 -      /* Decrypt the meta key */
 +      return send_challenge(c);
 +}
  
 -      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);
 +bool metakey_h(connection_t *c, char *request) {
 +      if(c->protocol_minor >= 2)
 +              return metakey_ec_h(c, request);
 +
 +      char hexkey[MAX_STRING_SIZE];
 +      int cipher, digest, maclength, compression;
 +      size_t len = rsa_size(&myself->connection->rsa);
 +      char enckey[len];
 +      char key[len];
 +
 +      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;
        }
  
 -      ifdebug(SCARY_THINGS) {
 -              bin2hex(c->inkey, buffer, len);
 -              buffer[len * 2] = '\0';
 -              logger(LOG_DEBUG, "Received random meta key (unencrypted): %s", buffer);
 -      }
 +      /* Convert the challenge from hexadecimal back to binary */
  
 -      /* All incoming requests will now be encrypted. */
 +      int inlen = hex2bin(hexkey, enckey, sizeof enckey);
  
 -      /* Check and lookup cipher and digest algorithms */
 +      /* Check if the length of the meta key is all right */
  
 -      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(inlen != len) {
 +              logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong keylength");
 +              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;
 -              }
 +      /* Decrypt the meta key */
  
 -              c->status.decryptin = true;
 -      } else {
 -              c->incipher = NULL;
 +      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;
        }
  
 -      c->inmaclength = maclength;
 +      ifdebug(SCARY_THINGS) {
 +              bin2hex(key, hexkey, len);
 +              logger(LOG_DEBUG, "Received random meta key (unencrypted): %s", hexkey);
 +      }
  
 -      if(digest) {
 -              c->indigest = EVP_get_digestbynid(digest);
 +      /* Check and lookup cipher and digest algorithms */
  
 -              if(!c->indigest) {
 -                      logger(LOG_ERR, "Node %s (%s) uses unknown digest!", c->name, c->hostname);
 -                      return false;
 -              }
 +      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;
 +      }
  
 -              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) {
 -      /* CHECKME: what is most reasonable value for len? */
 -
 -      int len = RSA_size(c->rsa_key);
 +      size_t len = c->protocol_minor >= 2 ? ECDH_SIZE : rsa_size(&c->rsa);
 +      char buffer[len * 2 + 1];
  
 -      /* Allocate buffers for the challenge */
 -
 -      char buffer[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 */
  
        bin2hex(c->hischallenge, buffer, len);
 -      buffer[len * 2] = '\0';
  
        /* Send the challenge */
  
        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 = c->protocol_minor >= 2 ? ECDH_SIZE : rsa_size(&myself->connection->rsa);
 +      size_t digestlen = digest_length(&c->indigest);
 +      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);
 +      /* Convert the challenge from hexadecimal back to binary */
 +
 +      int inlen = hex2bin(buffer, buffer, sizeof buffer);
  
        /* 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");
 +      if(inlen != len) {
 +              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);
 -
        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);
  
        /* 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");
 -              return false;
 -      }
 -
        /* Convert the hash to binary format */
  
 -      hex2bin(hishash, hishash, c->outdigest->md_size);
 +      int inlen = hex2bin(hishash, hishash, sizeof hishash);
  
 -      /* Calculate the hash from the challenge we sent */
 +      /* Check if the length of the hash is all right */
  
 -      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));
 +      if(inlen != digest_length(&c->outdigest)) {
 +              logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge reply length");
                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, c->protocol_minor >= 2 ? ECDH_SIZE : 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);
  }
  
 +static bool send_upgrade(connection_t *c) {
 +      /* Special case when protocol_minor is 1: the other end is ECDSA capable,
 +       * but doesn't know our key yet. So send it now. */
 +
 +      char *pubkey = ecdsa_get_base64_public_key(&myself->connection->ecdsa);
 +
 +      if(!pubkey)
 +              return false;
 +
 +      bool result = send_request(c, "%d %s", ACK, pubkey);
 +      free(pubkey);
 +      return result;
 +}
 +
  bool send_ack(connection_t *c) {
 +      if(c->protocol_minor == 1)
 +              return send_upgrade(c);
 +
        /* ACK message contains rest of the information the other end needs
           to create node_t and edge_t structures. */
  
  }
  
  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) {
 +static bool upgrade_h(connection_t *c, char *request) {
 +      char pubkey[MAX_STRING_SIZE];
 +
 +      if(sscanf(request, "%*d " MAX_STRING, pubkey) != 1) {
 +              logger(LOG_ERR, "Got bad %s from %s (%s)", "ACK", c->name, c->hostname);
 +              return false;
 +      }
 +
 +      if(ecdsa_active(&c->ecdsa) || read_ecdsa_public_key(c)) {
 +              logger(LOG_INFO, "Already have ECDSA public key from %s (%s), not upgrading.", c->name, c->hostname);
 +              return false;
 +      }
 +
 +      logger(LOG_INFO, "Got ECDSA public key from %s (%s), upgrading!", c->name, c->hostname);
 +      append_config_file(c->name, "ECDSAPublicKey", pubkey);
 +      c->allow_request = TERMREQ;
 +      return send_termreq(c);
 +}
 +
 +bool ack_h(connection_t *c, char *request) {
 +      if(c->protocol_minor == 1)
 +              return upgrade_h(c, request);
 +
        char hisport[MAX_STRING_SIZE];
        char *hisaddress;
        int weight, mtu;
        node_t *n;
        bool choice;
  
 -      if(sscanf(c->buffer, "%*d " MAX_STRING " %d %x", hisport, &weight, &options) != 3) {
 +      if(sscanf(request, "%*d " MAX_STRING " %d %x", hisport, &weight, &options) != 3) {
                logger(LOG_ERR, "Got bad %s from %s (%s)", "ACK", c->name,
                           c->hostname);
                return false;
        } else {
                if(n->connection) {
                        /* Oh dear, we already have a connection to this node. */
 -                      ifdebug(CONNECTIONS) logger(LOG_DEBUG, "Established a second connection with %s (%s), closing old connection",
 -                                         n->name, n->hostname);
 +                      ifdebug(CONNECTIONS) logger(LOG_DEBUG, "Established a second connection with %s (%s), closing old connection", n->connection->name, n->connection->hostname);
 +
 +                      if(n->connection->outgoing) {
 +                              if(c->outgoing)
 +                                      logger(LOG_WARNING, "Two outgoing connections to the same node!");
 +                              else
 +                                      c->outgoing = n->connection->outgoing;
 +
 +                              n->connection->outgoing = NULL;
 +                      }
 +
                        terminate_connection(n->connection, false);
                        /* Run graph algorithm to purge key and make sure up/down scripts are rerun with new IP addresses and stuff */
                        graph();
                        c->options &= ~OPTION_CLAMP_MSS;
        }
  
 +      if(c->protocol_minor > 0)
 +              c->node->status.ecdh = true;
 +
        /* Activate this connection */
  
        c->allow_request = ALL;
        if(tunnelserver)
                send_add_edge(c, c->edge);
        else
-               send_add_edge(broadcast, c->edge);
+               send_add_edge(everyone, c->edge);
  
        /* Run MST and SSSP algorithms */
  
diff --combined src/protocol_edge.c
@@@ -21,7 -21,7 +21,7 @@@
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "conf.h"
  #include "connection.h"
  #include "edge.h"
@@@ -50,7 -50,7 +50,7 @@@ bool send_add_edge(connection_t *c, con
        return x;
  }
  
 -bool add_edge_h(connection_t *c) {
 +bool add_edge_h(connection_t *c, char *request) {
        edge_t *e;
        node_t *from, *to;
        char from_name[MAX_STRING_SIZE];
@@@ -61,7 -61,7 +61,7 @@@
        uint32_t options;
        int weight;
  
 -      if(sscanf(c->buffer, "%*d %*x "MAX_STRING" "MAX_STRING" "MAX_STRING" "MAX_STRING" %x %d",
 +      if(sscanf(request, "%*d %*x "MAX_STRING" "MAX_STRING" "MAX_STRING" "MAX_STRING" %x %d",
                          from_name, to_name, to_address, to_port, &options, &weight) != 6) {
                logger(LOG_ERR, "Got bad %s from %s (%s)", "ADD_EDGE", c->name,
                           c->hostname);
@@@ -76,7 -76,7 +76,7 @@@
                return false;
        }
  
 -      if(seen_request(c->buffer))
 +      if(seen_request(request))
                return true;
  
        /* Lookup nodes */
        /* Tell the rest about the new edge */
  
        if(!tunnelserver)
 -              forward_request(c);
 +              forward_request(c, request);
  
        /* Run MST before or after we tell the rest? */
  
@@@ -167,13 -167,13 +167,13 @@@ bool send_del_edge(connection_t *c, con
                                                e->from->name, e->to->name);
  }
  
 -bool del_edge_h(connection_t *c) {
 +bool del_edge_h(connection_t *c, char *request) {
        edge_t *e;
        char from_name[MAX_STRING_SIZE];
        char to_name[MAX_STRING_SIZE];
        node_t *from, *to;
  
 -      if(sscanf(c->buffer, "%*d %*x "MAX_STRING" "MAX_STRING, from_name, to_name) != 2) {
 +      if(sscanf(request, "%*d %*x "MAX_STRING" "MAX_STRING, from_name, to_name) != 2) {
                logger(LOG_ERR, "Got bad %s from %s (%s)", "DEL_EDGE", c->name,
                           c->hostname);
                return false;
                return false;
        }
  
 -      if(seen_request(c->buffer))
 +      if(seen_request(request))
                return true;
  
        /* Lookup nodes */
        /* Tell the rest about the deleted edge */
  
        if(!tunnelserver)
 -              forward_request(c);
 +              forward_request(c, request);
  
        /* Delete the edge */
  
                e = lookup_edge(to, myself);
                if(e) {
                        if(!tunnelserver)
-                               send_del_edge(broadcast, e);
+                               send_del_edge(everyone, e);
                        edge_del(e);
                }
        }
diff --combined src/protocol_key.c
  
  #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 "ecdh.h"
  #include "logger.h"
  #include "net.h"
  #include "netutl.h"
  #include "node.h"
 +#include "prf.h"
  #include "protocol.h"
  #include "utils.h"
  #include "xalloc.h"
  static bool mykeyused = false;
  
  void send_key_changed(void) {
 -      avl_node_t *node;
 +      splay_node_t *node;
        connection_t *c;
  
-       send_request(broadcast, "%d %x %s", KEY_CHANGED, rand(), myself->name);
+       send_request(everyone, "%d %x %s", KEY_CHANGED, rand(), myself->name);
  
        /* Immediately send new keys to directly connected nodes to keep UDP mappings alive */
  
        }
  }
  
 -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);
        /* Tell the others */
  
        if(!tunnelserver)
 -              forward_request(c);
 +              forward_request(c, request);
  
        return true;
  }
  
  bool send_req_key(node_t *to) {
 -      return send_request(to->nexthop->connection, "%d %s %s", REQ_KEY, myself->name, to->name);
 +      return send_request(to->nexthop->connection, "%d %s %s %d", REQ_KEY, myself->name, to->name, experimental ? 1 : 0);
  }
  
 -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;
 +      int kx_version = 0;
  
 -      if(sscanf(c->buffer, "%*d " MAX_STRING " " MAX_STRING, from_name, to_name) != 2) {
 +      if(sscanf(request, "%*d " MAX_STRING " " MAX_STRING " %d", from_name, to_name, &kx_version) < 2) {
                logger(LOG_ERR, "Got bad %s from %s (%s)", "REQ_KEY", c->name,
                           c->hostname);
                return false;
        /* Check if this key request is for us */
  
        if(to == myself) {                      /* Yes, send our own key back */
 +              if(experimental && kx_version >= 1) {
 +                      logger(LOG_DEBUG, "Got ECDH key request from %s", from->name);
 +                      from->status.ecdh = true;
 +              }
                send_ans_key(from);
        } else {
                if(tunnelserver)
                        return true;
                }
  
 -              send_request(to->nexthop->connection, "%s", c->buffer);
 +              send_request(to->nexthop->connection, "%s", request);
        }
  
        return true;
  }
  
 +bool send_ans_key_ecdh(node_t *to) {
 +      int siglen = ecdsa_size(&myself->connection->ecdsa);
 +      char key[(ECDH_SIZE + siglen) * 2 + 1];
 +
 +      if(!ecdh_generate_public(&to->ecdh, key))
 +              return false;
 +
 +      if(!ecdsa_sign(&myself->connection->ecdsa, key, ECDH_SIZE, key + ECDH_SIZE))
 +              return false;
 +
 +      b64encode(key, key, ECDH_SIZE + siglen);
 +
 +      char *pubkey = ecdsa_get_base64_public_key(&myself->connection->ecdsa);
 +
 +      if(!pubkey)
 +              return false;
 +
 +      int result = send_request(to->nexthop->connection, "%d %s %s ECDH:%s:%s %d %d %zu %d", ANS_KEY,
 +                                              myself->name, to->name, key, pubkey,
 +                                              cipher_get_nid(&myself->incipher),
 +                                              digest_get_nid(&myself->indigest),
 +                                              digest_length(&myself->indigest),
 +                                              myself->incompression);
 +
 +      free(pubkey);
 +      return result;
 +}
 +
  bool send_ans_key(node_t *to) {
 -      // Set key parameters
 -      to->incipher = myself->incipher;
 -      to->inkeylength = myself->inkeylength;
 -      to->indigest = myself->indigest;
 -      to->inmaclength = myself->inmaclength;
 +      if(experimental && to->status.ecdh)
 +              return send_ans_key_ecdh(to);
 +
 +      size_t keylen = cipher_keylength(&myself->incipher);
 +      char key[keylen * 2 + 1];
 +
 +      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, false);
 +      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);
  
        // Reset sequence number and late packet window
        mykeyused = true;
        to->received_seqno = 0;
        if(replaywin) memset(to->late, 0, replaywin);
  
 -      // Convert to hexadecimal and send
 -      char key[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 "MAX_STRING" "MAX_STRING,
                from_name, to_name, key, &cipher, &digest, &maclength,
                &compression, address, port) < 7) {
                logger(LOG_ERR, "Got bad %s from %s (%s)", "ANS_KEY", c->name,
  
                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);
 +                                 "ANS_KEY", c->name, c->hostname, to_name);
                        return true;
                }
  
                        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);
 +                      send_request(to->nexthop->connection, "%s %s %s", request, address, port);
                        free(address);
                        free(port);
                        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->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(!cipher_open_by_nid(&from->outcipher, cipher)) {
 +              logger(LOG_ERR, "Node %s (%s) uses unknown cipher!", from->name, from->hostname);
 +              return false;
 +      }
 +
 +      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(maclength != digest_length(&from->outdigest)) {
 +              logger(LOG_ERR, "Node %s (%s) uses bogus MAC length!", from->name, from->hostname);
 +              return false;
 +      }
  
 -              if(!from->outcipher) {
 -                      logger(LOG_ERR, "Node %s (%s) uses unknown cipher!", from->name,
 -                                 from->hostname);
 +      if(compression < 0 || compression > 11) {
 +              logger(LOG_ERR, "Node %s (%s) uses bogus compression level!", from->name, from->hostname);
 +              return true;
 +      }
 +
 +      from->outcompression = compression;
 +
 +      /* ECDH or old-style key exchange? */
 +      
 +      if(experimental && !strncmp(key, "ECDH:", 5)) {
 +              char *pubkey = strchr(key + 5, ':');
 +              if(pubkey)
 +                      *pubkey++ = 0;
 +                      
 +              /* Check if we already have an ECDSA public key for this node.
 +               * If not, use the one from the key exchange, and store it. */
 +
 +              if(!node_read_ecdsa_public_key(from)) {
 +                      if(!pubkey) {
 +                              logger(LOG_ERR, "No ECDSA public key known for %s (%s), cannot verify ECDH key exchange!", from->name, from->hostname);
 +                              return true;
 +                      }
 +
 +                      if(!ecdsa_set_base64_public_key(&from->ecdsa, pubkey))
 +                              return true;
 +
 +                      append_config_file(from->name, "ECDSAPublicKey", pubkey);
 +              }
 +
 +              int siglen = ecdsa_size(&from->ecdsa);
 +              int keylen = b64decode(key + 5, key + 5, sizeof key - 5);
 +
 +              if(keylen != ECDH_SIZE + siglen) {
 +                      logger(LOG_ERR, "Node %s (%s) uses wrong keylength! %d != %d", from->name, from->hostname, keylen, ECDH_SIZE + siglen);
                        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);
 +              if(ECDH_SHARED_SIZE < cipher_keylength(&from->outcipher)) {
 +                      logger(LOG_ERR, "ECDH key too short for cipher of %s!", from->name);
 +                      return true;
 +              }
 +
 +              if(!ecdsa_verify(&from->ecdsa, key + 5, ECDH_SIZE, key + 5 + ECDH_SIZE)) {
 +                      logger(LOG_ERR, "Possible intruder %s (%s): %s", from->name, from->hostname, "invalid ECDSA signature");
                        return true;
                }
 -      } else {
 -              from->outcipher = NULL;
 -      }
  
 -      from->outmaclength = maclength;
 +              if(!from->ecdh) {
 +                      from->status.ecdh = true;
 +                      if(!send_ans_key(from))
 +                              return true;
 +              }
  
 -      if(digest) {
 -              from->outdigest = EVP_get_digestbynid(digest);
 +              char shared[ECDH_SHARED_SIZE * 2 + 1];
  
 -              if(!from->outdigest) {
 -                      logger(LOG_ERR, "Node %s (%s) uses unknown digest!", from->name,
 -                                 from->hostname);
 +              if(!ecdh_compute_shared(&from->ecdh, key + 5, shared))
                        return true;
 +
 +              /* Update our crypto end */
 +
 +              size_t mykeylen = cipher_keylength(&myself->incipher);
 +              size_t hiskeylen = cipher_keylength(&from->outcipher);
 +
 +              char *mykey;
 +              char *hiskey;
 +              char *seed;
 +              
 +              if(strcmp(myself->name, from->name) < 0) {
 +                      mykey = key;
 +                      hiskey = key + mykeylen * 2;
 +                      xasprintf(&seed, "tinc UDP key expansion %s %s", myself->name, from->name);
 +              } else {
 +                      mykey = key + hiskeylen * 2;
 +                      hiskey = key;
 +                      xasprintf(&seed, "tinc UDP key expansion %s %s", from->name, myself->name);
                }
  
 -              if(from->outmaclength > from->outdigest->md_size || from->outmaclength < 0) {
 -                      logger(LOG_ERR, "Node %s (%s) uses bogus MAC length!",
 -                                 from->name, from->hostname);
 +              if(!prf(shared, ECDH_SHARED_SIZE, seed, strlen(seed), key, hiskeylen * 2 + mykeylen * 2))
                        return true;
 -              }
 -      } else {
 -              from->outdigest = NULL;
 -      }
  
 -      if(compression < 0 || compression > 11) {
 -              logger(LOG_ERR, "Node %s (%s) uses bogus compression level!", from->name, from->hostname);
 -              return true;
 -      }
 -      
 -      from->outcompression = compression;
 +              free(seed);
 +
 +              cipher_open_by_nid(&from->incipher, cipher_get_nid(&myself->incipher));
 +              digest_open_by_nid(&from->indigest, digest_get_nid(&myself->indigest), digest_length(&myself->indigest));
 +              from->incompression = myself->incompression;
  
 -      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));
 +              cipher_set_key(&from->incipher, mykey, false);
 +              digest_set_key(&from->indigest, mykey + mykeylen, mykeylen);
 +
 +              cipher_set_key(&from->outcipher, hiskey, true);
 +              digest_set_key(&from->outdigest, hiskey + hiskeylen, hiskeylen);
 +
 +              // Reset sequence number and late packet window
 +              mykeyused = true;
 +              from->received_seqno = 0;
 +              if(replaywin)
 +                      memset(from->late, 0, replaywin);
 +
 +              if(strcmp(myself->name, from->name) < 0)
 +                      memmove(key, key + mykeylen * 2, hiskeylen * 2);
 +      } else {
 +              keylen = hex2bin(key, key, sizeof key);
 +
 +              if(keylen != cipher_keylength(&from->outcipher)) {
 +                      logger(LOG_ERR, "Node %s (%s) uses wrong keylength!", from->name, from->hostname);
                        return true;
                }
  
 +              /* Update our copy of the origin's packet key */
 +
 +              cipher_set_key(&from->outcipher, key, true);
 +              digest_set_key(&from->outdigest, key, keylen);
 +      }
 +
        from->status.validkey = true;
        from->sent_seqno = 0;
  
                update_node_udp(from, &sa);
        }
  
 -      if(from->options & OPTION_PMTU_DISCOVERY && !from->mtuevent)
 +      if(from->options & OPTION_PMTU_DISCOVERY)
                send_mtu_probe(from);
  
        return true;
diff --combined src/raw_socket_device.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      device.c -- raw socket
      Copyright (C) 2002-2005 Ivo Timmermans,
-                   2002-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2002-2012 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,9 +20,9 @@@
  
  #include "system.h"
  
+ #ifdef HAVE_NETPACKET_PACKET_H
  #include <netpacket/packet.h>
+ #endif
  
  #include "conf.h"
  #include "device.h"
  #include "route.h"
  #include "xalloc.h"
  
- int device_fd = -1;
- char *device = NULL;
- char *iface = NULL;
- static char ifrname[IFNAMSIZ];
+ #if defined(PF_PACKET) && defined(ETH_P_ALL) && defined(AF_PACKET)
  static char *device_info;
  
  static uint64_t device_total_in = 0;
  static uint64_t device_total_out = 0;
  
- bool setup_device(void) {
static bool setup_device(void) {
        struct ifreq ifr;
        struct sockaddr_ll sa;
  
                return false;
        }
  
 -      memset(&ifr, 0, sizeof(ifr));
 +      memset(&ifr, 0, sizeof ifr);
++
+ #ifdef FD_CLOEXEC
+       fcntl(device_fd, F_SETFD, FD_CLOEXEC);
+ #endif
        strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ);
        if(ioctl(device_fd, SIOCGIFINDEX, &ifr)) {
                close(device_fd);
                return false;
        }
  
 -      memset(&sa, '0', sizeof(sa));
 +      memset(&sa, '0', sizeof sa);
        sa.sll_family = AF_PACKET;
        sa.sll_protocol = htons(ETH_P_ALL);
        sa.sll_ifindex = ifr.ifr_ifindex;
  
 -      if(bind(device_fd, (struct sockaddr *) &sa, (socklen_t) sizeof(sa))) {
 +      if(bind(device_fd, (struct sockaddr *) &sa, (socklen_t) sizeof sa)) {
                logger(LOG_ERR, "Could not bind %s to %s: %s", device, iface, strerror(errno));
                return false;
        }
        return true;
  }
  
- void close_device(void) {
static void close_device(void) {
        close(device_fd);
  
        free(device);
        free(iface);
  }
  
- bool read_packet(vpn_packet_t *packet) {
static bool read_packet(vpn_packet_t *packet) {
 -      int lenin;
 +      int inlen;
  
 -      if((lenin = read(device_fd, packet->data, MTU)) <= 0) {
 +      if((inlen = read(device_fd, packet->data, MTU)) <= 0) {
                logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
                           device, strerror(errno));
                return false;
        }
  
 -      packet->len = lenin;
 +      packet->len = inlen;
  
        device_total_in += packet->len;
  
        return true;
  }
  
- bool write_packet(vpn_packet_t *packet) {
static bool write_packet(vpn_packet_t *packet) {
        ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s",
                           packet->len, device_info);
  
        return true;
  }
  
- void dump_device_stats(void) {
static void dump_device_stats(void) {
        logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device);
        logger(LOG_DEBUG, " total bytes in:  %10"PRIu64, device_total_in);
        logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out);
  }
+ const devops_t raw_socket_devops = {
+       .setup = setup_device,
+       .close = close_device,
+       .read = read_packet,
+       .write = write_packet,
+       .dump_stats = dump_device_stats,
+ };
+ #else
+ static bool not_supported(void) {
+       logger(LOG_ERR, "Raw socket device not supported on this platform");
+       return false;
+ }
+ const devops_t raw_socket_devops = {
+       .setup = not_supported,
+       .close = NULL,
+       .read = NULL,
+       .write = NULL,
+       .dump_stats = NULL,
+ };
+ #endif
diff --combined src/route.c
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "connection.h"
 +#include "control_common.h"
  #include "ethernet.h"
  #include "ipv4.h"
  #include "ipv6.h"
  #include "logger.h"
 +#include "meta.h"
  #include "net.h"
  #include "protocol.h"
  #include "route.h"
  
  rmode_t routing_mode = RMODE_ROUTER;
  fmode_t forwarding_mode = FMODE_INTERNAL;
+ bool decrement_ttl = true;
  bool directonly = false;
  bool priorityinheritance = false;
  int macexpire = 600;
  bool overwrite_mac = false;
+ bool broadcast = true;
  mac_t mymac = {{0xFE, 0xFD, 0, 0, 0, 0}};
 +bool pcap = false;
  
  /* Sizes of various headers */
  
@@@ -58,8 -57,6 +60,8 @@@ static const size_t opt_size = sizeof(s
  #define MAX(a, b) ((a) > (b) ? (a) : (b))
  #endif
  
 +static struct event age_subnets_event;
 +
  /* RFC 1071 */
  
  static uint16_t inet_checksum(void *data, int len, uint16_t prevsum) {
@@@ -83,7 -80,6 +85,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)
@@@ -183,43 -179,9 +185,43 @@@ static void swap_mac_addresses(vpn_pack
        memcpy(&packet->data[6], &tmp, sizeof tmp);
  }
        
 +static void age_subnets(int fd, short events, void *data) {
 +      subnet_t *s;
 +      connection_t *c;
 +      splay_node_t *node, *next, *node2;
 +      bool left = false;
 +      time_t now = time(NULL);
 +
 +      for(node = myself->subnet_tree->head; node; node = next) {
 +              next = node->next;
 +              s = node->data;
 +              if(s->expires && s->expires < now) {
 +                      ifdebug(TRAFFIC) {
 +                              char netstr[MAXNETSTR];
 +                              if(net2str(netstr, sizeof netstr, s))
 +                                      logger(LOG_INFO, "Subnet %s expired", netstr);
 +                      }
 +
 +                      for(node2 = connection_tree->head; node2; node2 = node2->next) {
 +                              c = node2->data;
 +                              if(c->status.active)
 +                                      send_del_subnet(c, s);
 +                      }
 +
 +                      subnet_del(myself, s);
 +              } else {
 +                      if(s->expires)
 +                              left = true;
 +              }
 +      }
 +
 +      if(left)
 +              event_add(&age_subnets_event, &(struct timeval){10, 0});
 +}
 +
  static void learn_mac(mac_t *address) {
        subnet_t *subnet;
 -      avl_node_t *node;
 +      splay_node_t *node;
        connection_t *c;
  
        subnet = lookup_subnet_mac(myself, address);
  
                subnet = new_subnet();
                subnet->type = SUBNET_MAC;
 -              subnet->expires = now + macexpire;
 +              subnet->expires = time(NULL) + macexpire;
                subnet->net.mac.address = *address;
                subnet->weight = 10;
                subnet_add(myself, subnet);
                        if(c->status.active)
                                send_add_subnet(c, subnet);
                }
 -      }
  
 -      if(subnet->expires)
 -              subnet->expires = now + macexpire;
 -}
 -
 -void age_subnets(void) {
 -      subnet_t *s;
 -      connection_t *c;
 -      avl_node_t *node, *next, *node2;
 -
 -      for(node = myself->subnet_tree->head; node; node = next) {
 -              next = node->next;
 -              s = node->data;
 -              if(s->expires && s->expires <= now) {
 -                      ifdebug(TRAFFIC) {
 -                              char netstr[MAXNETSTR];
 -                              if(net2str(netstr, sizeof netstr, s))
 -                                      logger(LOG_INFO, "Subnet %s expired", netstr);
 -                      }
 -
 -                      for(node2 = connection_tree->head; node2; node2 = node2->next) {
 -                              c = node2->data;
 -                              if(c->status.active)
 -                                      send_del_subnet(c, s);
 -                      }
 -
 -                      subnet_update(myself, s, false);
 -                      subnet_del(myself, s);
 -              }
 +              if(!timeout_initialized(&age_subnets_event))
 +                      timeout_set(&age_subnets_event, age_subnets, NULL);
 +              event_add(&age_subnets_event, &(struct timeval){10, 0});
 +      } else {
 +              if(subnet->expires)
 +                      subnet->expires = time(NULL) + macexpire;
        }
  }
  
@@@ -347,7 -332,7 +349,7 @@@ static void fragment_ipv4_packet(node_
        todo = ntohs(ip.ip_len) - ip_size;
  
        if(ether_size + ip_size + todo != packet->len) {
 -              ifdebug(TRAFFIC) logger(LOG_WARNING, "Length of packet (%d) doesn't match length in IPv4 header (%zd)", packet->len, ether_size + ip_size + todo);
 +              ifdebug(TRAFFIC) logger(LOG_WARNING, "Length of packet (%d) doesn't match length in IPv4 header (%d)", packet->len, (int)(ether_size + ip_size + todo));
                return;
        }
  
@@@ -439,11 -424,11 +441,11 @@@ static void route_ipv4(node_t *source, 
        if(!checklength(source, packet, ether_size + ip_size))
                return;
  
-       if(((packet->data[30] & 0xf0) == 0xe0) || (
+       if(broadcast && (((packet->data[30] & 0xf0) == 0xe0) || (
                        packet->data[30] == 255 &&
                        packet->data[31] == 255 &&
                        packet->data[32] == 255 &&
-                       packet->data[33] == 255))
+                       packet->data[33] == 255)))
                broadcast_packet(source, packet);
        else
                route_ipv4_unicast(source, packet);
@@@ -513,7 -498,7 +515,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);
  
@@@ -640,7 -625,7 +642,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);
@@@ -731,7 -716,7 +733,7 @@@ static void route_ipv6(node_t *source, 
                return;
        }
  
-       if(packet->data[38] == 255)
+       if(broadcast && packet->data[38] == 255)
                broadcast_packet(source, packet);
        else
                route_ipv6_unicast(source, packet);
@@@ -764,7 -749,7 +766,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 */
@@@ -821,7 -806,8 +823,8 @@@ static void route_mac(node_t *source, v
        subnet = lookup_subnet_mac(NULL, &dest);
  
        if(!subnet) {
-               broadcast_packet(source, packet);
+               if(broadcast)
+                       broadcast_packet(source, packet);
                return;
        }
  
        send_packet(subnet->owner, packet);
  }
  
 +static void send_pcap(vpn_packet_t *packet) {
 +      pcap = false;
 +      for(splay_node_t *node = connection_tree->head; node; node = node->next) {
 +              connection_t *c = node->data;
 +              if(!c->status.pcap)
 +                      continue;
 +              else
 +                      pcap = true;
 +              if(send_request(c, "%d %d %d", CONTROL, REQ_PCAP, packet->len))
 +                      send_meta(c, (char *)packet->data, packet->len);
 +      }
 +}
 +
+ static bool do_decrement_ttl(node_t *source, vpn_packet_t *packet) {
+       uint16_t type = packet->data[12] << 8 | packet->data[13];
+       switch (type) {
+               case ETH_P_IP:
+                       if(!checklength(source, packet, 14 + 32))
+                               return false;
+                       if(packet->data[22] < 1) {
+                               route_ipv4_unreachable(source, packet, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL);
+                               return false;
+                       }
+                       uint16_t old = packet->data[22] << 8 | packet->data[23];
+                       packet->data[22]--;
+                       uint16_t new = packet->data[22] << 8 | packet->data[23];
+                       uint32_t checksum = packet->data[24] << 8 | packet->data[25];
+                       checksum += old + (~new & 0xFFFF);
+                       while(checksum >> 16)
+                               checksum = (checksum & 0xFFFF) + (checksum >> 16);
+                       packet->data[24] = checksum >> 8;
+                       packet->data[25] = checksum & 0xff;
+                       return true;
+               case ETH_P_IPV6:
+                       if(!checklength(source, packet, 14 + 40))
+                               return false;
+                       if(packet->data[21] < 1) {
+                               route_ipv6_unreachable(source, packet, ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT);
+                               return false;
+                       }
+                       packet->data[21]--;
+                       return true;
+               default:
+                       return true;
+       }
+ }
  void route(node_t *source, vpn_packet_t *packet) {
 +      if(pcap)
 +              send_pcap(packet);
 +
        if(forwarding_mode == FMODE_KERNEL && source != myself) {
                send_packet(myself, packet);
                return;
        if(!checklength(source, packet, ether_size))
                return;
  
+       if(decrement_ttl && source != myself)
+               if(!do_decrement_ttl(source, packet))
+                       return;
        switch (routing_mode) {
                case RMODE_ROUTER:
                        {
diff --combined src/route.h
@@@ -38,14 -38,16 +38,16 @@@ typedef enum fmode_t 
  
  extern rmode_t routing_mode;
  extern fmode_t forwarding_mode;
+ extern bool decrement_ttl;
  extern bool directonly;
  extern bool overwrite_mac;
+ extern bool broadcast;
  extern bool priorityinheritance;
  extern int macexpire;
 +extern bool pcap;
  
  extern mac_t mymac;
  
 -extern void age_subnets(void);
  extern void route(struct node_t *, struct vpn_packet_t *);
  
  #endif                                                        /* __TINC_ROUTE_H__ */
diff --combined src/solaris/device.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      device.c -- Interaction with Solaris tun device
      Copyright (C) 2001-2005 Ivo Timmermans,
-                   2001-2011 Guus Sliepen <guus@tinc-vpn.org>
+                   2001-2012 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
@@@ -35,7 -35,7 +35,7 @@@
  #define DEFAULT_DEVICE "/dev/tun"
  
  int device_fd = -1;
- int ip_fd = -1, if_fd = -1;
static int ip_fd = -1, if_fd = -1;
  char *device = NULL;
  char *iface = NULL;
  static char *device_info = NULL;
@@@ -43,7 -43,7 +43,7 @@@
  static uint64_t device_total_in = 0;
  static uint64_t device_total_out = 0;
  
- bool setup_device(void) {
static bool setup_device(void) {
        int ppa;
        char *ptr;
  
                return false;
        }
  
+ #ifdef FD_CLOEXEC
+       fcntl(device_fd, F_SETFD, FD_CLOEXEC);
+ #endif
        ppa = 0;
  
        ptr = device;
                return false;
        }
  
+ #ifdef FD_CLOEXEC
+       fcntl(ip_fd, F_SETFD, FD_CLOEXEC);
+ #endif
        /* Assign a new PPA and get its unit number. */
        if((ppa = ioctl(device_fd, TUNNEWPPA, ppa)) < 0) {
                logger(LOG_ERR, "Can't assign new interface: %s", strerror(errno));
                return false;
        }
  
+ #ifdef FD_CLOEXEC
+       fcntl(if_fd, F_SETFD, FD_CLOEXEC);
+ #endif
        if(ioctl(if_fd, I_PUSH, "ip") < 0) {
                logger(LOG_ERR, "Can't push IP module: %s", strerror(errno));
                return false;
        return true;
  }
  
- void close_device(void) {
static void close_device(void) {
        close(if_fd);
        close(ip_fd);
        close(device_fd);
        free(iface);
  }
  
- bool read_packet(vpn_packet_t *packet) {
static bool read_packet(vpn_packet_t *packet) {
 -      int lenin;
 +      int inlen;
  
 -      if((lenin = read(device_fd, packet->data + 14, MTU - 14)) <= 0) {
 +      if((inlen = read(device_fd, packet->data + 14, MTU - 14)) <= 0) {
                logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
                           device, strerror(errno));
                return false;
                        return false;
        }
  
 -      packet->len = lenin + 14;
 +      packet->len = inlen + 14;
  
        device_total_in += packet->len;
  
        return true;
  }
  
- bool write_packet(vpn_packet_t *packet) {
static bool write_packet(vpn_packet_t *packet) {
        ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s",
                           packet->len, device_info);
  
        return true;
  }
  
- void dump_device_stats(void) {
static void dump_device_stats(void) {
        logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device);
        logger(LOG_DEBUG, " total bytes in:  %10"PRIu64, device_total_in);
        logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out);
  }
+ const devops_t os_devops = {
+       .setup = setup_device,
+       .close = close_device,
+       .read = read_packet,
+       .write = write_packet,
+       .dump_stats = dump_device_stats,
+ };
diff --combined src/tincd.c
  #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"
  char *program_name = NULL;
  
  /* If nonzero, display usage information and exit. */
 -bool show_help = false;
 +static 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;
 +static bool show_version = false;
  
  /* If nonzero, use null ciphers and skip all key exchanges. */
  bool bypass_security = false;
  
  /* If nonzero, disable swapping for this process. */
 -bool do_mlock = false;
 +static bool do_mlock = false;
  
  /* If nonzero, chroot to netdir after startup. */
  static bool do_chroot = false;
@@@ -88,18 -93,20 +88,18 @@@ 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 *pidfilename = NULL;
  char **g_argv;                                        /* a copy of the cmdline arguments */
  
 -static int status;
 +static int status = 1;
  
  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'},
        {"user", required_argument, NULL, 'U'},
        {"logfile", optional_argument, NULL, 4},
        {"pidfile", required_argument, NULL, 5},
+       {"option", required_argument, NULL, 'o'},
        {NULL, 0, NULL, 0}
  };
  
@@@ -122,18 -130,20 +123,18 @@@ static void usage(bool status) 
                                program_name);
        else {
                printf("Usage: %s [option]...\n\n", program_name);
-               printf( "  -c, --config=DIR          Read configuration options from DIR.\n"
-                               "  -D, --no-detach           Don't fork and detach.\n"
-                               "  -d, --debug[=LEVEL]       Increase debug level or set it to LEVEL.\n"
-                               "  -n, --net=NETNAME         Connect to net NETNAME.\n"
-                               "  -L, --mlock               Lock tinc into main memory.\n"
-                               "      --logfile[=FILENAME]  Write log entries to a logfile.\n"
-                               "      --pidfile=FILENAME    Write PID and control socket cookie to FILENAME.\n"
-                               "      --bypass-security     Disables meta protocol security, for debugging.\n"
-                               "  -o [HOST.]KEY=VALUE       Set global/host configuration value.\n"
-                               "  -R, --chroot              chroot to NET dir at startup.\n"
-                               "  -U, --user=USER           setuid to given USER at startup.\n"                                "      --help                    Display this help and exit.\n"
-                               "      --version             Output version information and exit.\n\n");
 -              printf("  -c, --config=DIR               Read configuration options from DIR.\n"
 -                              "  -D, --no-detach                Don't fork and detach.\n"
 -                              "  -d, --debug[=LEVEL]            Increase debug level or set it to LEVEL.\n"
 -                              "  -k, --kill[=SIGNAL]            Attempt to kill a running tincd and exit.\n"
 -                              "  -n, --net=NETNAME              Connect to net NETNAME.\n"
 -                              "  -K, --generate-keys[=BITS]     Generate public/private RSA keypair.\n"
 -                              "  -L, --mlock                    Lock tinc into main memory.\n"
 -                              "      --logfile[=FILENAME]       Write log entries to a logfile.\n"
 -                              "      --pidfile=FILENAME         Write PID to FILENAME.\n"
 -                              "  -o, --option=[HOST.]KEY=VALUE  Set global/host configuration value.\n"
 -                              "  -R, --chroot                   chroot to NET dir at startup.\n"
 -                              "  -U, --user=USER                setuid to given USER at startup.\n"
 -                              "      --help                     Display this help and exit.\n"
 -                              "      --version                  Output version information and exit.\n\n");
++              printf( "  -c, --config=DIR              Read configuration options from DIR.\n"
++                              "  -D, --no-detach               Don't fork and detach.\n"
++                              "  -d, --debug[=LEVEL]           Increase debug level or set it to LEVEL.\n"
++                              "  -n, --net=NETNAME             Connect to net NETNAME.\n"
++                              "  -L, --mlock                   Lock tinc into main memory.\n"
++                              "      --logfile[=FILENAME]      Write log entries to a logfile.\n"
++                              "      --pidfile=FILENAME        Write PID and control socket cookie to FILENAME.\n"
++                              "      --bypass-security         Disables meta protocol security, for debugging.\n"
++                              "  -o, --option[HOST.]KEY=VALUE  Set global/host configuration value.\n"
++                              "  -R, --chroot                  chroot to NET dir at startup.\n"
++                              "  -U, --user=USER               setuid to given USER at startup.\n"                            "      --help                    Display this help and exit.\n"
++                              "      --version                 Output version information and exit.\n\n");
                printf("Report bugs to tinc@tinc-vpn.org.\n");
        }
  }
@@@ -146,7 -156,7 +147,7 @@@ static bool parse_options(int argc, cha
  
        cmdline_conf = list_alloc((list_action_t)free_config);
  
 -      while((r = getopt_long(argc, argv, "c:DLd::k::n:o:K::RU:", long_options, &option_index)) != EOF) {
 +      while((r = getopt_long(argc, argv, "c:DLd::n:o: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 if(!strcasecmp(optarg, "ABRT"))
 -                                              kill_tincd = SIGABRT;
 -                                      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 "." is special: a "top-level name" */
                                netname = strcmp(optarg, ".") != 0 ?
                                list_insert_tail(cmdline_conf, cfg);
                                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 */
 +                      case 5:                                 /* open control socket here */
                                pidfilename = xstrdup(optarg);
                                break;
  
        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
 -              
 -      fputc('\n', f);
 -      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");
 -
 -      fputc('\n', f);
 -      PEM_write_RSAPublicKey(f, rsa_key);
 -      fclose(f);
 -      free(filename);
 -      if(name)
 -              free(name);
 -
 -      return true;
 -}
 -
  /*
    Set all files and paths according to netname
  */
@@@ -237,7 -405,7 +238,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(!pidfilename)
 +                              xasprintf(&pidfilename, "%s/pid", 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(!pidfilename)
 +              xasprintf(&pidfilename, LOCALSTATEDIR "/run/%s.pid", identname);
 +
        if(netname) {
                if(!confbase)
                        xasprintf(&confbase, CONFDIR "/tinc/%s", netname);
        }
  }
  
 -static void free_names() {
 +static void free_names(void) {
        if (identname) free(identname);
        if (netname) free(netname);
        if (pidfilename) free(pidfilename);
        if (confbase) free(confbase);
  }
  
 -static bool drop_privs() {
 +static bool drop_privs(void) {
  #ifdef HAVE_MINGW
        if (switchuser) {
                logger(LOG_ERR, "%s not supported on this platform", "-U");
@@@ -356,8 -522,8 +357,8 @@@ int main(int argc, char **argv) 
        make_names();
  
        if(show_version) {
 -              printf("%s version %s (built %s %s, protocol %d)\n", PACKAGE,
 -                         VERSION, __DATE__, __TIME__, PROT_CURRENT);
 +              printf("%s version %s (built %s %s, protocol %d.%d)\n", PACKAGE,
 +                         VERSION, __DATE__, __TIME__, PROT_MAJOR, PROT_MINOR);
                printf("Copyright (C) 1998-2011 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"
                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;
  #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
@@@ -416,6 -587,7 +417,7 @@@ int main2(int argc, char **argv) 
        InitializeCriticalSection(&mutex);
        EnterCriticalSection(&mutex);
  #endif
+         char *priority = NULL;
  
        if(!detach())
                return 1;
        /* Setup sockets and open device. */
  
        if(!setup_network())
 -              goto end;
 +              goto end_nonet;
 +
 +      if(!init_control())
 +              goto end_nonet;
  
        /* Initiate all outgoing connections. */
  
  
        /* Change process priority */
  
-         char *priority = NULL;
          if(get_config_string(lookup_config(config_tree, "ProcessPriority"), &priority)) {
                  if(!strcasecmp(priority, "Normal")) {
                          if (setpriority(NORMAL_PRIORITY_CLASS) != 0) {
        /* Shutdown properly. */
  
        ifdebug(CONNECTIONS)
-               dump_device_stats();
+               devops.dump_stats();
  
        close_network_connections();
  
  end:
 -      logger(LOG_NOTICE, "Terminating");
 +      exit_control();
  
 -#ifndef HAVE_MINGW
 -      remove_pid(pidfilename);
 -#endif
 +end_nonet:
 +      logger(LOG_NOTICE, "Terminating");
  
        free(priority);
  
 -      EVP_cleanup();
 -      ENGINE_cleanup();
 -      CRYPTO_cleanup_all_ex_data();
 -      ERR_remove_state(0);
 -      ERR_free_strings();
 +      crypto_exit();
  
        exit_configuration(&config_tree);
 -      list_free(cmdline_conf);
 +      free(cmdline_conf);
        free_names();
  
        return status;
diff --combined src/uml_device.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      device.c -- UML network socket
      Copyright (C) 2002-2005 Ivo Timmermans,
-                   2002-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2002-2012 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 "logger.h"
  #include "utils.h"
  #include "route.h"
+ #include "xalloc.h"
  
- int device_fd = -1;
  static int listen_fd = -1;
  static int request_fd = -1;
  static int data_fd = -1;
  static int write_fd = -1;
  static int state = 0;
- char *device = NULL;
- char *iface = NULL;
  static char *device_info;
  
  extern char *identname;
- extern bool running;
+ extern volatile bool running;
  
  static uint64_t device_total_in = 0;
  static uint64_t device_total_out = 0;
@@@ -56,7 -54,7 +54,7 @@@ static struct request 
  
  static struct sockaddr_un data_sun;
  
- bool setup_device(void) {
static bool setup_device(void) {
        struct sockaddr_un listen_sun;
        static const int one = 1;
        struct {
                return false;
        }
  
+ #ifdef FD_CLOEXEC
+       fcntl(write_fd, F_SETFD, FD_CLOEXEC);
+ #endif
        setsockopt(write_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
  
        if(fcntl(write_fd, F_SETFL, O_NONBLOCK) < 0) {
                return false;
        }
  
+ #ifdef FD_CLOEXEC
+       fcntl(data_fd, F_SETFD, FD_CLOEXEC);
+ #endif
        setsockopt(data_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
  
        if(fcntl(data_fd, F_SETFL, O_NONBLOCK) < 0) {
                return false;
        }
  
+ #ifdef FD_CLOEXEC
+       fcntl(device_fd, F_SETFD, FD_CLOEXEC);
+ #endif
        setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
  
        if(fcntl(listen_fd, F_SETFL, O_NONBLOCK) < 0) {
@@@ -169,13 -179,13 +179,13 @@@ void close_device(void) 
        if(iface) free(iface);
  }
  
- bool read_packet(vpn_packet_t *packet) {
static bool read_packet(vpn_packet_t *packet) {
 -      int lenin;
 +      int inlen;
  
        switch(state) {
                case 0: {
                        struct sockaddr sa;
-                       int salen = sizeof sa;
+                       socklen_t salen = sizeof sa;
  
                        request_fd = accept(listen_fd, &sa, &salen);
                        if(request_fd < 0) {
                                return false;
                        }
  
+ #ifdef FD_CLOEXEC
+                       fcntl(request_fd, F_SETFD, FD_CLOEXEC);
+ #endif
                        if(fcntl(listen_fd, F_SETFL, O_NONBLOCK) < 0) {
                                logger(LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno));
                                running = false;
                }
  
                case 1: {
 -                      if((lenin = read(request_fd, &request, sizeof request)) != sizeof request) {
 +                      if((inlen = read(request_fd, &request, sizeof request)) != sizeof request) {
                                logger(LOG_ERR, "Error while reading request from %s %s: %s", device_info,
                                           device, strerror(errno));
                                running = false;
                }
  
                case 2: {
 -                      if((lenin = read(data_fd, packet->data, MTU)) <= 0) {
 +                      if((inlen = read(data_fd, packet->data, MTU)) <= 0) {
                                logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
                                           device, strerror(errno));
                                running = false;
                                return false;
                        }
  
 -                      packet->len = lenin;
 +                      packet->len = inlen;
  
                        device_total_in += packet->len;
  
  
                        return true;
                }
+               default:
+                       logger(LOG_ERR, "Invalid value for state variable in " __FILE__);
+                       abort();
        }
  }
  
- bool write_packet(vpn_packet_t *packet) {
static bool write_packet(vpn_packet_t *packet) {
        if(state != 2) {
                ifdebug(TRAFFIC) logger(LOG_DEBUG, "Dropping packet of %d bytes to %s: not connected to UML yet",
                                packet->len, device_info);
        return true;
  }
  
- void dump_device_stats(void) {
static void dump_device_stats(void) {
        logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device);
        logger(LOG_DEBUG, " total bytes in:  %10"PRIu64, device_total_in);
        logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out);
  }
+ const devops_t uml_devops = {
+       .setup = setup_device,
+       .close = close_device,
+       .read = read_packet,
+       .write = write_packet,
+       .dump_stats = dump_device_stats,
+ };