]> git.meshlink.io Git - meshlink/commitdiff
Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1
authorGuus Sliepen <guus@tinc-vpn.org>
Sun, 25 Mar 2012 22:35:31 +0000 (23:35 +0100)
committerGuus Sliepen <guus@tinc-vpn.org>
Sun, 25 Mar 2012 22:35:31 +0000 (23:35 +0100)
Conflicts:
NEWS
README
configure.in
src/Makefile.am
src/conf.c
src/conf.h
src/connection.c
src/net.c
src/tincd.c

29 files changed:
1  2 
NEWS
README
doc/tinc.conf.5.in
doc/tinc.texi
src/Makefile.am
src/conf.c
src/conf.h
src/connection.c
src/connection.h
src/device.h
src/graph.c
src/ipv4.h
src/ipv6.h
src/multicast_device.c
src/net.c
src/net.h
src/net_packet.c
src/net_setup.c
src/node.h
src/protocol.c
src/protocol_auth.c
src/protocol_edge.c
src/protocol_key.c
src/protocol_misc.c
src/route.c
src/route.h
src/subnet.c
src/tincd.c
src/vde_device.c

diff --combined NEWS
index 36f50606cd4bddad0c8cf063143dadd8b734560d,e2215fce0dde1ed47cfdbfa16d751a193cc0d28f..a3850477a70ce6b8df7a2d00c1273bbf6e98e09c
--- 1/NEWS
--- 2/NEWS
+++ b/NEWS
@@@ -1,33 -1,29 +1,59 @@@
 +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.18               March 25 2012
+  * Fixed IPv6 in switch mode by turning off DecrementTTL by default.
+  * Allow a port number to be specified in BindToAddress, which also allows tinc
+    to listen on multiple ports.
+  * Add support for multicast communication with UML/QEMU/KVM.
+ Version 1.0.17               March 10 2012
+  * The DeviceType option can now be used to select dummy, raw socket, UML and
+    VDE devices without needing to recompile tinc.
+  * Allow multiple BindToAddress statements.
+  * Decrement TTL value of IPv4 and IPv6 packets.
+  * Add LocalDiscovery option allowing tinc to detect peers that are behind the
+    same NAT.
+  * Accept Subnets passed with the -o option when StrictSubnets = yes.
+  * Disabling old RSA keys when generating new ones now also works properly on
+    Windows.
  Version 1.0.16               July 23 2011
  
   * Fixed a performance issue with TCP communication under Windows.
@@@ -43,8 -39,6 +69,8 @@@ Version 1.0.15               June 24 20
  
   * 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 README
index 09f6e6e9e60cbaee09d7ed5b7cefb58f226e6a74,ed1d2b42c84a4768de4ab0d5de8a3096a8b005ce..f978028b806d5644a1ba3a20df06cc3ab31fa326
--- 1/README
--- 2/README
+++ b/README
@@@ -1,7 -1,7 +1,7 @@@
 -This is the README file for tinc version 1.0.18. Installation
 +This is the README file for tinc version 1.1pre2. Installation
  instructions may be found in the INSTALL file.
  
- tinc is Copyright (C) 1998-2011 by:
+ tinc is Copyright (C) 1998-2012 by:
  
  Ivo Timmermans,
  Guus Sliepen <guus@tinc-vpn.org>,
@@@ -15,51 -15,68 +15,51 @@@ the Free Software Foundation; either ve
  your option) any later version. See the file COPYING for more details.
  
  
 -Security statement
 -------------------
 +This is a pre-release
 +---------------------
  
 -In August 2000, we discovered the existence of a security hole in all versions
 -of tinc up to and including 1.0pre2. This had to do with the way we exchanged
 -keys. Since then, we have been working on a new authentication scheme to make
 -tinc as secure as possible. The current version uses the OpenSSL library and
 -uses strong authentication with RSA keys.
 +Please note that this is NOT a stable release. Until version 1.1.0 is released,
 +please use one of the 1.0.x versions if you need a stable version of tinc.
  
 -On the 29th of December 2001, Jerome Etienne posted a security analysis of tinc
 -1.0pre4. Due to a lack of sequence numbers and a message authentication code
 -for each packet, an attacker could possibly disrupt certain network services or
 -launch a denial of service attack by replaying intercepted packets. The current
 -version adds sequence numbers and message authentication codes to prevent such
 -attacks.
 +Although tinc 1.1 will be protocol compatible with tinc 1.0.x, the
 +functionality of the tincctl program may still change, and the control socket
 +protocol is not fixed yet.
  
 -On September the 15th of 2003, Peter Gutmann contacted us and showed us a
 -writeup describing various security issues in several VPN daemons. He showed
 -that tinc lacks perfect forward security, the connection authentication could
 -be done more properly, that the sequence number we use as an IV is not the best
 -practice and that the default length of the HMAC for packets is too short in
 -his opinion. We do not know of a way to exploit these weaknesses, but we will
 -address these issues in tinc 2.0.
 -
 -Cryptography is a hard thing to get right. We cannot make any
 -guarantees. Time, review and feedback are the only things that can
 -prove the security of any cryptographic product. If you wish to review
 -tinc or give us feedback, you are stronly encouraged to do so.
  
 +Security statement
 +------------------
  
 -Changes to configuration file format since 1.0pre5
 ---------------------------------------------------
 +This version uses an experimental and unfinished cryptographic protocol. Use
 +it at your own risk.
  
 -Some configuration variables have different names now. Most notably "TapDevice"
 -should be changed into "Device", and "Device" should be changed into
 -"BindToDevice".
  
  Compatibility
  -------------
  
 -Version 1.0.18 is compatible with 1.0pre8, 1.0 and later, but not with older
 +Version 1.1pre2 is compatible with 1.0pre8, 1.0 and later, but not with older
  versions of tinc.
  
 +When the ExperimentalProtocol option is used, tinc is still compatible with
 +1.0.X and 1.1pre2 itself, but not with any other 1.1preX version.
 +
  
  Requirements
  ------------
  
 -Since 1.0pre3, we use OpenSSL for all cryptographic functions.  So you
 -need to install this library first; grab it from
 -http://www.openssl.org/.  You will need version 0.9.7 or later.  If
 -this library is not installed on you system, configure will fail.  The
 -manual in doc/tinc.texi contains more detailed information on how to
 -install this library.
 +Either OpenSSL (http://www.openssl.org/) or libgcrypt
 +(http://www.gnupg.org/download/#libgcrypt).
 +
 +The zlib library is used for optional compression. You can find it at
 +http://www.gzip.org/zlib/.
  
 -Since 1.0pre6, the zlib library is used for optional compression. You can
 -find it at http://www.gzip.org/zlib/. Because of a possible exploit in
 -earlier versions we recommend that you download version 1.1.4 or later.
 +The lzo library is also used for optional compression. You can find it at
 +http://www.oberhumer.com/opensource/lzo/.
  
 -Since 1.0, the lzo library is also used for optional compression. You can
 -find it at http://www.oberhumer.com/opensource/lzo/.
 +Since 1.1, the libevent library is used for the main event loop. You can find
 +it at http://monkey.org/~provos/libevent/.
  
 -In order to compile tinc, you will need a GNU C compiler environment.
 +In order to compile tinc, you will need a GNU C compiler environment. Please
 +ensure you have the latest stable versions of all the required libraries.
  
  
  Features
diff --combined doc/tinc.conf.5.in
index 99b987772ad0157ded52790323888eb1c1bd1000,f6b0da465658341540b0c25337d61190c37c3953..8f19de068ba90d4caaa29d09eab5356f836bccb4
@@@ -129,7 -129,7 +129,7 @@@ I
  is selected, then depending on the operating system both IPv4 and IPv6 or just
  IPv6 listening sockets will be created.
  
- .It Va BindToAddress Li = Ar address Bq experimental
+ .It Va BindToAddress Li = Ar address Oo Ar port Oc Bq experimental
  If your computer has more than one IPv4 or IPv6 address,
  .Nm tinc
  will by default listen on all of them for incoming connections.
@@@ -137,7 -137,16 +137,16 @@@ Multipl
  .Va BindToAddress
  variables may be specified,
  in which case listening sockets for each specified address are made.
+ .Pp
+ If no
+ .Ar port
+ is specified, the socket will be bound to the port specified by the
+ .Va Port
+ option, or to port 655 if neither is given.
+ To only bind to a specific port but not to a specific address, use
+ .Li *
+ for the
+ .Ar address .
  .Pp
  This option may not work on all platforms.
  
@@@ -171,13 -180,15 +180,15 @@@ 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
+ .It Va DecrementTTL Li = yes | no Po no Pc Bq experimental
  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.
+ .Pp
+ Do not use this option if you use switch mode and want to use IPv6.
  
  .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.
@@@ -210,6 -221,16 +221,16 @@@ All packets are read from this interfac
  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 multicast
+ Open a multicast UDP socket and bind it to the address and port (separated by spaces) and optionally a TTL value specified using
+ .Va Device .
+ Packets are read from and written to this multicast socket.
+ This can be used to connect to UML, QEMU or KVM instances listening on the same multicast address.
+ Do NOT connect multiple
+ .Nm tinc 
+ daemons to the same multicast address, this will very likely cause routing loops.
+ Also note that this can cause decrypted VPN packets to be sent out on a real network if misconfigured.
  .It uml Pq not compiled in by default
  Create a UNIX socket with the filename specified by
  .Va Device ,
@@@ -261,21 -282,6 +282,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
@@@ -467,7 -473,7 +488,7 @@@ Since host configuration files only con
  no secrets are revealed by sending out this information.
  .Bl -tag -width indent
  
- .It Va Address Li = Ar address Oo port Oc Bq recommended
+ .It Va Address Li = Ar address Oo Ar port Oc Bq recommended
  The IP address or hostname of this tinc daemon on the real network.
  This will only be used when trying to make an outgoing connection to this tinc daemon.
  Optionally, a port can be specified to use for this address.
@@@ -558,12 -564,11 +579,11 @@@ variables can be specified
  Subnets can either be single MAC, IPv4 or IPv6 addresses,
  in which case a subnet consisting of only that single address is assumed,
  or they can be a IPv4 or IPv6 network address with a prefixlength.
- Shorthand notations are not supported.
  For example, IPv4 subnets must be in a form like 192.168.1.0/24,
  where 192.168.1.0 is the network address and 24 is the number of bits set in the netmask.
  Note that subnets like 192.168.1.1/24 are invalid!
  Read a networking HOWTO/FAQ/guide if you don't understand this.
- IPv6 subnets are notated like fec0:0:0:1:0:0:0:0/64.
+ IPv6 subnets are notated like fec0:0:0:1::/64.
  MAC addresses are notated like 0:1a:2b:3c:4d:5e.
  
  .Pp
diff --combined doc/tinc.texi
index 526a6c96e5ced1ec8fc3b5561b734752d99f9615,d0fb70dec4556f849430c010a08f56ea5e9fa795..b4fb1f130ca02040308295e007b962a1f7060996
@@@ -15,7 -15,7 +15,7 @@@
  
  This is the info manual for @value{PACKAGE} version @value{VERSION}, a Virtual Private Network daemon.
  
- Copyright @copyright{} 1998-2011 Ivo Timmermans,
+ Copyright @copyright{} 1998-2012 Ivo Timmermans,
  Guus Sliepen <guus@@tinc-vpn.org> and
  Wessel Dankers <wsl@@tinc-vpn.org>.
  
@@@ -39,7 -39,7 +39,7 @@@ permission notice identical to this one
  @vskip 0pt plus 1filll
  This is the info manual for @value{PACKAGE} version @value{VERSION}, a Virtual Private Network daemon.
  
- Copyright @copyright{} 1998-2011 Ivo Timmermans,
+ Copyright @copyright{} 1998-2012 Ivo Timmermans,
  Guus Sliepen <guus@@tinc-vpn.org> and
  Wessel Dankers <wsl@@tinc-vpn.org>.
  
@@@ -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
@@@ -779,12 -756,16 +779,16 @@@ If any is selected, then depending on t
  both IPv4 and IPv6 or just IPv6 listening sockets will be created.
  
  @cindex BindToAddress
- @item BindToAddress = <@var{address}> [experimental]
+ @item BindToAddress = <@var{address}> [<@var{port}>] [experimental]
  If your computer has more than one IPv4 or IPv6 address, tinc
  will by default listen on all of them for incoming connections.
  Multiple BindToAddress variables may be specified,
  in which case listening sockets for each specified address are made.
  
+ If no @var{port} is specified, the socket will be bound to the port specified by the Port option,
+ or to port 655 if neither is given.
+ To only bind to a specific port but not to a specific address, use "*" for the @var{address}.
  This option may not work on all platforms.
  
  @cindex BindToInterface
@@@ -813,12 -794,14 +817,14 @@@ tinc won't try to connect to other daem
  and will instead just listen for incoming connections.
  
  @cindex DecrementTTL
- @item DecrementTTL = <yes | no> (yes)
+ @item DecrementTTL = <yes | no> (no) [experimental]
  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.
  
+ Do not use this option if you use switch mode and want to use IPv6.
  @cindex Device
  @item Device = <@var{device}> (@file{/dev/tap0}, @file{/dev/net/tun} or other depending on platform)
  The virtual network device to use.
@@@ -849,6 -832,14 +855,14 @@@ All packets are read from this interfac
  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 multicast
+ @item multicast
+ Open a multicast UDP socket and bind it to the address and port (separated by spaces) and optionally a TTL value specified using @var{Device}.
+ Packets are read from and written to this multicast socket.
+ This can be used to connect to UML, QEMU or KVM instances listening on the same multicast address.
+ Do NOT connect multiple tinc daemons to the same multicast address, this will very likely cause routing loops.
+ Also note that this can cause decrypted VPN packets to be sent out on a real network if misconfigured.
  @cindex UML
  @item uml (not compiled in by default)
  Create a UNIX socket with the filename specified by
@@@ -899,21 -890,6 +913,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.
@@@ -1042,7 -1018,7 +1056,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
@@@ -1164,7 -1140,7 +1178,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
@@@ -1186,12 -1162,11 +1200,11 @@@ Multiple subnet lines can be specified 
  Subnets can either be single MAC, IPv4 or IPv6 addresses,
  in which case a subnet consisting of only that single address is assumed,
  or they can be a IPv4 or IPv6 network address with a prefixlength.
- Shorthand notations are not supported.
  For example, IPv4 subnets must be in a form like 192.168.1.0/24,
  where 192.168.1.0 is the network address and 24 is the number of bits set in the netmask.
  Note that subnets like 192.168.1.1/24 are invalid!
  Read a networking HOWTO/FAQ/guide if you don't understand this.
- IPv6 subnets are notated like fec0:0:0:1:0:0:0:0/64.
+ IPv6 subnets are notated like fec0:0:0:1::/64.
  MAC addresses are notated like 0:1a:2b:3c:4d:5e.
  
  @cindex CIDR notation
@@@ -1200,6 -1175,7 +1213,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,
@@@ -1207,12 -1183,15 +1220,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
  
  
@@@ -1301,6 -1280,10 +1314,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
  
  
@@@ -1347,7 -1330,7 +1360,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.
@@@ -1576,7 -1559,7 +1589,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},
@@@ -1642,6 -1625,12 +1655,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}.
@@@ -1668,6 -1658,9 +1681,6 @@@ This will prevent sensitive data like s
  Write log entries to a file instead of to the system logging facility.
  If @var{file} is omitted, the default is @file{@value{localstatedir}/log/tinc.@var{netname}.log}.
  
 -@item --pidfile=@var{file}
 -Write PID to @var{file} instead of @file{@value{localstatedir}/run/tinc.@var{netname}.pid}.
 -
  @item --bypass-security
  Disables encryption and authentication.
  Only useful for debugging.
@@@ -1721,6 -1714,19 +1734,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 ==================================================================
@@@ -1784,7 -1790,7 +1797,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
  
@@@ -1883,8 -1889,6 +1896,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)
@@@ -1915,213 -1919,6 +1928,213 @@@ 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 environment variables::
 +* 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 environment variables
 +@section tincctl environment variables
 +
 +@table @env
 +@cindex NETNAME
 +@item NETNAME
 +If no netname is specified on the command line with the @option{-n} option,
 +the value of this environment variable is used.
 +@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 log [@var{level}]
 +Capture log messages from a running tinc daemon.
 +An optional debug level can be given that will be applied only for log messages sent to tincctl.
 +
 +@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
@@@ -2606,7 -2403,6 +2619,6 @@@ For IPv4 addresses
  @tab @code{netsh interface ip set address} @var{interface} @code{static} @var{address} @var{netmask}
  @end multitable
  
  For IPv6 addresses:
  
  @multitable {Darwin (MacOS/X)} {ifconfig route add -bla network address netmask netmask prefixlength interface}
  @tab @code{netsh interface ipv6 add address} @var{interface} @code{static} @var{address}/@var{prefixlength}
  @end multitable
  
+ On some platforms, when running tinc in switch mode, the VPN interface must be set to tap mode with an ifconfig command:
+ @multitable {Darwin (MacOS/X)} {ifconfig route add -bla network address netmask netmask prefixlength interface}
+ @item OpenBSD
+ @tab @code{ifconfig} @var{interface} @code{link0}
+ @end multitable
+ On Linux, it is possible to create a persistent tun/tap interface which will
+ continue to exist even if tinc quit, although this is normally not required.
+ It can be useful to set up a tun/tap interface owned by a non-root user, so
+ tinc can be started without needing any root privileges at all.
+ @multitable {Darwin (MacOS/X)} {ifconfig route add -bla network address netmask netmask prefixlength interface}
+ @item Linux
+ @tab @code{ip tuntap add dev} @var{interface} @code{mode} @var{tun|tap} @code{user} @var{username}
+ @end multitable
  
  @c ==================================================================
  @node    Routes
diff --combined src/Makefile.am
index 255ce3407f154f38e9021c0e4e6f4e63e3152081,cd44eb64adce99dc2a493613ff1670450c237962..e383e1f48eea7ed9e5ac3e8149e37398d046a206
@@@ -1,15 -1,13 +1,15 @@@
  ## Produce this file with automake to get Makefile.in
  
 -sbin_PROGRAMS = tincd
 +sbin_PROGRAMS = tincd tincctl sptps_test
  
 -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       \
 -      protocol_key.c protocol_subnet.c route.c subnet.c tincd.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 sptps.c subnet.c tincd.c \
-       dummy_device.c raw_socket_device.c
+       dummy_device.c raw_socket_device.c multicast_device.c
        
  if UML
  tincd_SOURCES += uml_device.c
@@@ -19,45 -17,29 +19,45 @@@ if VD
  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 utils.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/conf.c
index d7df4e96893e240a9ae4616dfa53177be5989dd0,0fc9d8f46cc559a16fdca1075ffd3db6db987627..f580a203d9275ce99e057deca263f4d1ea25ff29
@@@ -2,7 -2,7 +2,7 @@@
      conf.c -- configuration code
      Copyright (C) 1998 Robert van der Meulen
                    1998-2005 Ivo Timmermans
-                   2000-2010 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2012 Guus Sliepen <guus@tinc-vpn.org>
                    2010-2011 Julien Muchembled <jm@jmuchemb.eu>
                  2000 Cris van Pelt
  
@@@ -23,7 -23,7 +23,7 @@@
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "connection.h"
  #include "conf.h"
  #include "list.h"
@@@ -33,7 -33,7 +33,7 @@@
  #include "utils.h"                            /* for cp */
  #include "xalloc.h"
  
 -avl_tree_t *config_tree;
 +splay_tree_t *config_tree;
  
  int pinginterval = 0;                 /* seconds between pings */
  int pingtimeout = 0;                  /* seconds to wait for response */
@@@ -63,12 -63,12 +63,12 @@@ static int config_compare(const config_
                return a->file ? strcmp(a->file, b->file) : 0;
  }
  
 -void init_configuration(avl_tree_t ** config_tree) {
 -      *config_tree = avl_alloc_tree((avl_compare_t) config_compare, (avl_action_t) free_config);
 +void init_configuration(splay_tree_t ** config_tree) {
 +      *config_tree = splay_alloc_tree((splay_compare_t) config_compare, (splay_action_t) free_config);
  }
  
 -void exit_configuration(avl_tree_t ** config_tree) {
 -      avl_delete_tree(*config_tree);
 +void exit_configuration(splay_tree_t ** config_tree) {
 +      splay_delete_tree(*config_tree);
        *config_tree = NULL;
  }
  
@@@ -89,18 -89,18 +89,18 @@@ void free_config(config_t *cfg) 
        free(cfg);
  }
  
 -void config_add(avl_tree_t *config_tree, config_t *cfg) {
 -      avl_insert(config_tree, cfg);
 +void config_add(splay_tree_t *config_tree, config_t *cfg) {
 +      splay_insert(config_tree, cfg);
  }
  
 -config_t *lookup_config(const avl_tree_t *config_tree, char *variable) {
 +config_t *lookup_config(splay_tree_t *config_tree, char *variable) {
        config_t cfg, *found;
  
        cfg.variable = variable;
        cfg.file = NULL;
        cfg.line = 0;
  
 -      found = avl_search_closest_greater(config_tree, &cfg);
 +      found = splay_search_closest_greater(config_tree, &cfg);
  
        if(!found)
                return NULL;
        return found;
  }
  
 -config_t *lookup_config_next(const avl_tree_t *config_tree, const config_t *cfg) {
 -      avl_node_t *node;
 +config_t *lookup_config_next(splay_tree_t *config_tree, const config_t *cfg) {
 +      splay_node_t *node;
        config_t *found;
  
 -      node = avl_search_node(config_tree, cfg);
 +      node = splay_search_node(config_tree, cfg);
  
        if(node) {
                if(node->next) {
@@@ -141,7 -141,7 +141,7 @@@ bool get_config_bool(const config_t *cf
                return true;
        }
  
 -      logger(LOG_ERR, "\"yes\" or \"no\" expected for configuration variable %s in %s line %d",
 +      logger(DEBUG_ALWAYS, LOG_ERR, "\"yes\" or \"no\" expected for configuration variable %s in %s line %d",
                   cfg->variable, cfg->file, cfg->line);
  
        return false;
@@@ -154,7 -154,7 +154,7 @@@ bool get_config_int(const config_t *cfg
        if(sscanf(cfg->value, "%d", result) == 1)
                return true;
  
 -      logger(LOG_ERR, "Integer expected for configuration variable %s in %s line %d",
 +      logger(DEBUG_ALWAYS, LOG_ERR, "Integer expected for configuration variable %s in %s line %d",
                   cfg->variable, cfg->file, cfg->line);
  
        return false;
@@@ -182,7 -182,7 +182,7 @@@ bool get_config_address(const config_t 
                return true;
        }
  
 -      logger(LOG_ERR, "Hostname or IP address expected for configuration variable %s in %s line %d",
 +      logger(DEBUG_ALWAYS, LOG_ERR, "Hostname or IP address expected for configuration variable %s in %s line %d",
                   cfg->variable, cfg->file, cfg->line);
  
        return false;
@@@ -195,7 -195,7 +195,7 @@@ bool get_config_subnet(const config_t *
                return false;
  
        if(!str2net(&subnet, cfg->value)) {
 -              logger(LOG_ERR, "Subnet expected for configuration variable %s in %s line %d",
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Subnet expected for configuration variable %s in %s line %d",
                           cfg->variable, cfg->file, cfg->line);
                return false;
        }
        /* Teach newbies what subnets are... */
  
        if(((subnet.type == SUBNET_IPV4)
 -              && !maskcheck(&subnet.net.ipv4.address, subnet.net.ipv4.prefixlength, sizeof(ipv4_t)))
 +              && !maskcheck(&subnet.net.ipv4.address, subnet.net.ipv4.prefixlength, sizeof subnet.net.ipv4.address))
                || ((subnet.type == SUBNET_IPV6)
 -              && !maskcheck(&subnet.net.ipv6.address, subnet.net.ipv6.prefixlength, sizeof(ipv6_t)))) {
 -              logger(LOG_ERR, "Network address and prefix length do not match for configuration variable %s in %s line %d",
 +              && !maskcheck(&subnet.net.ipv6.address, subnet.net.ipv6.prefixlength, sizeof subnet.net.ipv6.address))) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Network address and prefix length do not match for configuration variable %s in %s line %d",
                           cfg->variable, cfg->file, cfg->line);
                return false;
        }
@@@ -265,10 -265,10 +265,10 @@@ config_t *parse_config_line(char *line
        if(!*value) {
                const char err[] = "No value for variable";
                if (fname)
 -                      logger(LOG_ERR, "%s `%s' on line %d while reading config file %s",
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "%s `%s' on line %d while reading config file %s",
                                err, variable, lineno, fname);
                else
 -                      logger(LOG_ERR, "%s `%s' in command line option %d",
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "%s `%s' in command line option %d",
                                err, variable, lineno);
                return NULL;
        }
    Parse a configuration file and put the results in the configuration tree
    starting at *base.
  */
 -bool read_config_file(avl_tree_t *config_tree, const char *fname) {
 +bool read_config_file(splay_tree_t *config_tree, const char *fname) {
        FILE *fp;
        char buffer[MAX_STRING_SIZE];
        char *line;
        fp = fopen(fname, "r");
  
        if(!fp) {
 -              logger(LOG_ERR, "Cannot open config file %s: %s", fname, strerror(errno));
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Cannot open config file %s: %s", fname, strerror(errno));
                return false;
        }
  
        return result;
  }
  
 -void read_config_options(avl_tree_t *config_tree, const char *prefix) {
 +void read_config_options(splay_tree_t *config_tree, const char *prefix) {
        list_node_t *node, *next;
        size_t prefix_len = prefix ? strlen(prefix) : 0;
  
@@@ -379,7 -379,7 +379,7 @@@ bool read_server_config(void) 
        x = read_config_file(config_tree, fname);
  
        if(!x) {                                /* System error: complain */
 -              logger(LOG_ERR, "Failed to read `%s': %s", fname, strerror(errno));
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Failed to read `%s': %s", fname, strerror(errno));
        }
  
        free(fname);
@@@ -400,60 -400,130 +400,21 @@@ bool read_connection_config(connection_
        return x;
  }
  
 -static void disable_old_keys(const char *filename) {
 -      char tmpfile[PATH_MAX] = "";
 -      char buf[1024];
 -      bool disabled = false;
 -      FILE *r, *w;
 -
 -      r = fopen(filename, "r");
 -      if(!r)
 -              return;
 -
 -      snprintf(tmpfile, sizeof tmpfile, "%s.tmp", filename);
 -
 -      w = fopen(tmpfile, "w");
 -
 -      while(fgets(buf, sizeof buf, r)) {
 -              if(!strncmp(buf, "-----BEGIN RSA", 14)) {       
 -                      buf[11] = 'O';
 -                      buf[12] = 'L';
 -                      buf[13] = 'D';
 -                      disabled = true;
 -              }
 -              else if(!strncmp(buf, "-----END RSA", 12)) {    
 -                      buf[ 9] = 'O';
 -                      buf[10] = 'L';
 -                      buf[11] = 'D';
 -                      disabled = true;
 -              }
 -              if(w && fputs(buf, w) < 0) {
 -                      disabled = false;
 -                      break;
 -              }
 -      }
 -
 -      if(w)
 -              fclose(w);
 -      fclose(r);
 -
 -      if(!w && disabled) {
 -              fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n");
 -              return;
 -      }
 -
 -      if(disabled) {
 -#ifdef HAVE_MINGW
 -              // We cannot atomically replace files on Windows.
 -              char bakfile[PATH_MAX] = "";
 -              snprintf(bakfile, sizeof bakfile, "%s.bak", filename);
 -              if(rename(filename, bakfile) || rename(tmpfile, filename)) {
 -                      rename(bakfile, filename);
 -#else
 -              if(rename(tmpfile, filename)) {
 -#endif
 -                      fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n");
 -              } else  {
 -#ifdef HAVE_MINGW
 -                      unlink(bakfile);
 -#endif
 -                      fprintf(stderr, "Warning: old key(s) found and disabled.\n");
 -              }
 -      }
 +bool append_config_file(const char *name, const char *key, const char *value) {
 +      char *fname;
 +      xasprintf(&fname, "%s/hosts/%s", confbase, name);
  
 -      unlink(tmpfile);
 -}
 +      FILE *fp = fopen(fname, "a");
  
 -FILE *ask_and_open(const char *filename, const char *what) {
 -      FILE *r;
 -      char *directory;
 -      char line[PATH_MAX];
 -      const char *fn;
 -
 -      /* Check stdin and stdout */
 -      if(!isatty(0) || !isatty(1)) {
 -              /* Argh, they are running us from a script or something.  Write
 -                 the files to the current directory and let them burn in hell
 -                 for ever. */
 -              fn = filename;
 +      if(!fp) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Cannot open config file %s: %s", fname, strerror(errno));
        } else {
 -              /* Ask for a file and/or directory name. */
 -              fprintf(stdout, "Please enter a file to save %s to [%s]: ",
 -                              what, filename);
 -              fflush(stdout);
 -
 -              fn = readline(stdin, line, sizeof line);
 -
 -              if(!fn) {
 -                      fprintf(stderr, "Error while reading stdin: %s\n",
 -                                      strerror(errno));
 -                      return NULL;
 -              }
 -
 -              if(!strlen(fn))
 -                      /* User just pressed enter. */
 -                      fn = filename;
 -      }
 -
 -#ifdef HAVE_MINGW
 -      if(fn[0] != '\\' && fn[0] != '/' && !strchr(fn, ':')) {
 -#else
 -      if(fn[0] != '/') {
 -#endif
 -              /* The directory is a relative path or a filename. */
 -              char *p;
 -
 -              directory = get_current_dir_name();
 -              xasprintf(&p, "%s/%s", directory, fn);
 -              free(directory);
 -              fn = p;
 +              fprintf(fp, "\n# The following line was automatically added by tinc\n%s = %s\n", key, value);
 +              fclose(fp);
        }
  
 -      umask(0077);                            /* Disallow everything for group and other */
 -      disable_old_keys(fn);
 -
 -      /* Open it first to keep the inode busy */
 -
 -      r = fopen(fn, "a");
 -
 -      if(!r) {
 -              fprintf(stderr, "Error opening file `%s': %s\n",
 -                              fn, strerror(errno));
 -              return NULL;
 -      }
 +      free(fname);
  
 -      return r;
 +      return fp;
  }
- bool disable_old_keys(FILE *f) {
-       char buf[100];
-       long pos;
-       bool disabled = false;
-       rewind(f);
-       pos = ftell(f);
-       if(pos < 0)
-               return false;
--
-       while(fgets(buf, sizeof buf, f)) {
-               if(!strncmp(buf, "-----BEGIN RSA", 14)) {       
-                       buf[11] = 'O';
-                       buf[12] = 'L';
-                       buf[13] = 'D';
-                       if(fseek(f, pos, SEEK_SET))
-                               break;
-                       if(fputs(buf, f) <= 0)
-                               break;
-                       disabled = true;
-               }
-               else if(!strncmp(buf, "-----END RSA", 12)) {    
-                       buf[ 9] = 'O';
-                       buf[10] = 'L';
-                       buf[11] = 'D';
-                       if(fseek(f, pos, SEEK_SET))
-                               break;
-                       if(fputs(buf, f) <= 0)
-                               break;
-                       disabled = true;
-               }
-               pos = ftell(f);
-               if(pos < 0)
-                       break;
-       }
--
-       return disabled;
- }
diff --combined src/conf.h
index 1ae5b73541c4b1a4ff9ffb8b9c2e480355ed2850,3a040c7b74cdb9b5502210e764b59d6e13ecc059..743851a70b9b7cade29f0802c23393c08c4b4af3
@@@ -1,7 -1,7 +1,7 @@@
  /*
      conf.h -- header for conf.c
      Copyright (C) 1998-2005 Ivo Timmermans
-                   2000-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-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
@@@ -21,7 -21,7 +21,7 @@@
  #ifndef __TINC_CONF_H__
  #define __TINC_CONF_H__
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "list.h"
  
  typedef struct config_t {
@@@ -33,7 -33,7 +33,7 @@@
  
  #include "subnet.h"
  
 -extern avl_tree_t *config_tree;
 +extern splay_tree_t *config_tree;
  
  extern int pinginterval;
  extern int pingtimeout;
@@@ -43,13 -43,13 +43,13 @@@ extern char *confbase
  extern char *netname;
  extern list_t *cmdline_conf;
  
 -extern void init_configuration(avl_tree_t **);
 -extern void exit_configuration(avl_tree_t **);
 +extern void init_configuration(splay_tree_t **);
 +extern void exit_configuration(splay_tree_t **);
  extern config_t *new_config(void) __attribute__ ((__malloc__));
  extern void free_config(config_t *);
 -extern void config_add(avl_tree_t *, config_t *);
 -extern config_t *lookup_config(const avl_tree_t *, char *);
 -extern config_t *lookup_config_next(const avl_tree_t *, const config_t *);
 +extern void config_add(splay_tree_t *, config_t *);
 +extern config_t *lookup_config(splay_tree_t *, char *);
 +extern config_t *lookup_config_next(splay_tree_t *, const config_t *);
  extern bool get_config_bool(const config_t *, bool *);
  extern bool get_config_int(const config_t *, int *);
  extern bool get_config_string(const config_t *, char **);
@@@ -57,11 -57,11 +57,10 @@@ extern bool get_config_address(const co
  extern bool get_config_subnet(const config_t *, struct subnet_t **);
  
  extern config_t *parse_config_line(char *, const char *, int);
 -extern bool read_config_file(avl_tree_t *, const char *);
 -extern void read_config_options(avl_tree_t *, const char *);
 +extern bool read_config_file(splay_tree_t *, const char *);
 +extern void read_config_options(splay_tree_t *, const char *);
  extern bool read_server_config(void);
  extern bool read_connection_config(struct connection_t *);
 -extern FILE *ask_and_open(const char *, const char *);
 -extern bool is_safe_path(const char *);
 +extern bool append_config_file(const char *, const char *, const char *);
- extern bool disable_old_keys(FILE *);
  
  #endif                                                        /* __TINC_CONF_H__ */
diff --combined src/connection.c
index ee44e539acabadccf7a5818519a593eaec0b5495,9b752fadaa3d1b292e36702c933ffbcf08897bf5..dd5244860bfea4df1ff31d0f8cb9711b8409f027
@@@ -1,6 -1,6 +1,6 @@@
  /*
      connection.c -- connection list management
-     Copyright (C) 2000-2009 Guus Sliepen <guus@tinc-vpn.org>,
+     Copyright (C) 2000-2012 Guus Sliepen <guus@tinc-vpn.org>,
                    2000-2005 Ivo Timmermans
                    2008      Max Rijevski <maksuf@gmail.com>
  
  
  #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 *everyone;
  
  static int connection_compare(const connection_t *a, const connection_t *b) {
  }
  
  void init_connections(void) {
 -      connection_tree = avl_alloc_tree((avl_compare_t) connection_compare, (avl_action_t) free_connection);
 +      connection_tree = splay_alloc_tree((splay_compare_t) connection_compare, (splay_action_t) free_connection);
        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(everyone);
  }
  
  connection_t *new_connection(void) {
 -      connection_t *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);
 -      c = xmalloc_and_zero(sizeof(connection_t));
++void free_connection_partially(connection_t *c) {
 +      cipher_close(&c->incipher);
 +      digest_close(&c->indigest);
 +      cipher_close(&c->outcipher);
 +      digest_close(&c->outdigest);
  
 -      if(!c)
 -              return NULL;
 +      sptps_stop(&c->sptps);
 +      ecdsa_free(&c->ecdsa);
 +      rsa_free(&c->rsa);
  
 -      gettimeofday(&c->start, NULL);
 +      if(c->hischallenge)
 +              free(c->hischallenge);
  
-       if(c->config_tree)
-               exit_configuration(&c->config_tree);
 -      return c;
 -}
 +      buffer_clear(&c->inbuf);
 +      buffer_clear(&c->outbuf);
 +      
 +      if(event_initialized(&c->inevent))
 +              event_del(&c->inevent);
  
 -void free_connection_partially(connection_t *c) {
 -      free(c->inkey);
 -      free(c->outkey);
 -      free(c->mychallenge);
 -      free(c->hischallenge);
 -      free(c->outbuf);
 -
 -      c->inkey = NULL;
 -      c->outkey = NULL;
 -      c->mychallenge = NULL;
 -      c->hischallenge = NULL;
 -      c->outbuf = NULL;
 -
 -      c->buflen = 0;
 -      c->reqlen = 0;
 -      c->tcplen = 0;
 -      c->allow_request = 0;
 -      c->outbuflen = 0;
 -      c->outbufsize = 0;
 -      c->outbufstart = 0;
 -
 -      if(c->inctx) {
 -              EVP_CIPHER_CTX_cleanup(c->inctx);
 -              free(c->inctx);
 -              c->inctx = NULL;
 -      }
 +      if(event_initialized(&c->outevent))
 +              event_del(&c->outevent);
  
 -      if(c->outctx) {
 -              EVP_CIPHER_CTX_cleanup(c->outctx);
 -              free(c->outctx);
 -              c->outctx = NULL;
 -      }
 +      if(c->socket > 0)
 +              closesocket(c->socket);
  
 -      if(c->rsa_key) {
 -              RSA_free(c->rsa_key);
 -              c->rsa_key = NULL;
 -      }
++      c->socket = -1;
+ }
+ void free_connection(connection_t *c) {
++      if(!c)
++              return;
++
+       free_connection_partially(c);
+       free(c->name);
+       free(c->hostname);
+       if(c->config_tree)
+               exit_configuration(&c->config_tree);
        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
index 58eea4e694742ab26d0af94b39177493bbb41bbb,fbe4e02ca68a551728e57169c641d48e8eedd0b2..2b6870e41c60d90260c0f2e561f237268c9fdd89
  #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"
 +#include "sptps.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;                 /* 1 if this is a control connection */
 +              unsigned int pcap:1;                    /* 1 if this is a control connection requesting packet capture */
 +              unsigned int log:1;                     /* 1 if this is a control connection requesting log dump */
 +              unsigned int unused:20;
  } connection_status_t;
  
 +#include "ecdh.h"
 +#include "ecdsa.h"
  #include "edge.h"
  #include "net.h"
  #include "node.h"
@@@ -60,8 -53,7 +60,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 */
 +      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;
 +      sptps_t sptps;
 +
        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 *everyone;
  
  extern void init_connections(void);
  extern void exit_connections(void);
  extern connection_t *new_connection(void) __attribute__ ((__malloc__));
  extern void free_connection(connection_t *);
+ extern void free_connection_partially(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/device.h
index 993d4ce82698a49bba569b3c03bd024ab9790422,a3c29146542e77c0445ab5ec249338e3000cff4d..e28647db8b83427ee2b828330b31795c2f3d6284
@@@ -1,7 -1,7 +1,7 @@@
  /*
      device.h -- generic header for device.c
      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
  
  extern int device_fd;
  extern char *device;
 -
  extern char *iface;
  
 +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);
@@@ -43,6 -39,7 +43,7 @@@
  extern const devops_t os_devops;
  extern const devops_t dummy_devops;
  extern const devops_t raw_socket_devops;
+ extern const devops_t multicast_devops;
  extern const devops_t uml_devops;
  extern const devops_t vde_devops;
  extern devops_t devops;
diff --combined src/graph.c
index 4a9af9bc3cd5fa080fdb7ac184c73465163d02a4,d68a973ba854f33a94814060af9fbabef649076a..d432023e496f4eb790804d545262a91ddfc07034
@@@ -1,6 -1,6 +1,6 @@@
  /*
      graph.c -- graph algorithms
-     Copyright (C) 2001-2011 Guus Sliepen <guus@tinc-vpn.org>,
+     Copyright (C) 2001-2012 Guus Sliepen <guus@tinc-vpn.org>,
                    2001-2005 Ivo Timmermans
  
      This program is free software; you can redistribute it and/or modify
@@@ -44,7 -44,7 +44,7 @@@
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "config.h"
  #include "connection.h"
  #include "device.h"
  #include "subnet.h"
  #include "utils.h"
  #include "xalloc.h"
 -
 -static bool graph_changed = true;
 +#include "graph.h"
  
  /* Implementation of Kruskal's algorithm.
 -   Running time: O(EN)
 +   Running time: O(E)
     Please note that sorting on weight is already done by add_edge().
  */
  
  static void mst_kruskal(void) {
 -      avl_node_t *node, *next;
 +      splay_node_t *node, *next;
        edge_t *e;
        node_t *n;
        connection_t *c;
 -      int nodes = 0;
 -      int safe_edges = 0;
 -      bool skipped;
  
        /* Clear MST status on connections */
  
                c->status.mst = false;
        }
  
 -      /* Do we have something to do at all? */
 -
 -      if(!edge_weight_tree->head)
 -              return;
 -
 -      ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Running Kruskal's algorithm:");
 +      logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Running Kruskal's algorithm:");
  
        /* Clear visited status on nodes */
  
        for(node = node_tree->head; node; node = node->next) {
                n = node->data;
                n->status.visited = false;
 -              nodes++;
 -      }
 -
 -      /* Starting point */
 -
 -      for(node = edge_weight_tree->head; node; node = node->next) {
 -              e = node->data;
 -              if(e->from->status.reachable) {
 -                      e->from->status.visited = true;
 -                      break;
 -              }
        }
  
        /* Add safe edges */
  
 -      for(skipped = false, node = edge_weight_tree->head; node; node = next) {
 +      for(node = edge_weight_tree->head; node; node = next) {
                next = node->next;
                e = node->data;
  
 -              if(!e->reverse || e->from->status.visited == e->to->status.visited) {
 -                      skipped = true;
 +              if(!e->reverse || (e->from->status.visited && e->to->status.visited))
                        continue;
 -              }
  
                e->from->status.visited = true;
                e->to->status.visited = true;
                if(e->reverse->connection)
                        e->reverse->connection->status.mst = true;
  
 -              safe_edges++;
 -
 -              ifdebug(SCARY_THINGS) logger(LOG_DEBUG, " Adding edge %s - %s weight %d", e->from->name,
 +              logger(DEBUG_SCARY_THINGS, LOG_DEBUG, " Adding edge %s - %s weight %d", e->from->name,
                                   e->to->name, e->weight);
 +      }
 +}
  
 -              if(skipped) {
 -                      skipped = false;
 -                      next = edge_weight_tree->head;
 -                      continue;
 +/* Implementation of Dijkstra's algorithm.
 +   Running time: O(N^2)
 +*/
 +
 +static void sssp_dijkstra(void) {
 +      splay_node_t *node, *to;
 +      edge_t *e;
 +      node_t *n, *m;
 +      list_t *todo_list;
 +      list_node_t *lnode, *nnode;
 +      bool indirect;
 +
 +      todo_list = list_alloc(NULL);
 +
 +      logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Running Dijkstra's algorithm:");
 +
 +      /* Clear visited status on nodes */
 +
 +      for(node = node_tree->head; node; node = node->next) {
 +              n = node->data;
 +              n->status.visited = false;
 +              n->status.indirect = true;
 +              n->distance = -1;
 +      }
 +
 +      /* Begin with myself */
 +
 +      myself->status.indirect = false;
 +      myself->nexthop = myself;
 +      myself->via = myself;
 +      myself->distance = 0;
 +      list_insert_head(todo_list, myself);
 +
 +      /* Loop while todo_list is filled */
 +
 +      while(todo_list->head) {
 +              n = NULL;
 +              nnode = NULL;
 +
 +              /* Select node from todo_list with smallest distance */
 +
 +              for(lnode = todo_list->head; lnode; lnode = lnode->next) {
 +                      m = lnode->data;
 +                      if(!n || m->status.indirect < n->status.indirect || m->distance < n->distance) {
 +                              n = m;
 +                              nnode = lnode;
 +                      }
 +              }
 +
 +              /* Mark this node as visited and remove it from the todo_list */
 +
 +              n->status.visited = true;
 +              list_unlink_node(todo_list, nnode);
 +
 +              /* Update distance of neighbours and add them to the todo_list */
 +
 +              for(to = n->edge_tree->head; to; to = to->next) {       /* "to" is the edge connected to "from" */
 +                      e = to->data;
 +
 +                      if(e->to->status.visited || !e->reverse)
 +                              continue;
 +
 +                      /* Situation:
 +
 +                                 /
 +                                /
 +                         ----->(n)---e-->(e->to)
 +                                \
 +                                 \
 +
 +                         Where e is an edge, (n) and (e->to) are nodes.
 +                         n->address is set to the e->address of the edge left of n to n.
 +                         We are currently examining the edge e right of n from n:
 +
 +                         - If edge e provides for better reachability of e->to, update e->to.
 +                       */
 +
 +                      if(e->to->distance < 0)
 +                              list_insert_tail(todo_list, e->to);
 +
 +                      indirect = n->status.indirect || e->options & OPTION_INDIRECT || ((n != myself) && sockaddrcmp(&n->address, &e->reverse->address));
 +
 +                      if(e->to->distance >= 0 && (!e->to->status.indirect || indirect) && e->to->distance <= n->distance + e->weight)
 +                              continue;
 +
 +                      e->to->distance = n->distance + e->weight;
 +                      e->to->status.indirect = indirect;
 +                      e->to->nexthop = (n->nexthop == myself) ? e->to : n->nexthop;
 +                      e->to->via = indirect ? n->via : e->to;
 +                      e->to->options = e->options;
 +
 +                      if(e->to->address.sa.sa_family == AF_UNSPEC && e->address.sa.sa_family != AF_UNKNOWN)
 +                              update_node_udp(e->to, &e->address);
 +
 +                      logger(DEBUG_SCARY_THINGS, LOG_DEBUG, " Updating edge %s - %s weight %d distance %d", e->from->name,
 +                                         e->to->name, e->weight, e->to->distance);
                }
        }
  
 -      ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Done, counted %d nodes and %d safe edges.", nodes,
 -                         safe_edges);
 +      list_free(todo_list);
  }
  
  /* Implementation of a simple breadth-first search algorithm.
  */
  
  static void sssp_bfs(void) {
 -      avl_node_t *node, *next, *to;
 +      splay_node_t *node, *to;
        edge_t *e;
        node_t *n;
        list_t *todo_list;
        list_node_t *from, *todonext;
        bool indirect;
 -      char *name;
 -      char *address, *port;
 -      char *envp[7];
 -      int i;
  
        todo_list = list_alloc(NULL);
  
        }
  
        list_free(todo_list);
 +}
 +
 +static void check_reachability(void) {
 +      splay_node_t *node, *next;
 +      node_t *n;
 +      char *name;
 +      char *address, *port;
 +      char *envp[7];
 +      int i;
  
        /* Check reachability status. */
  
                        n->status.reachable = !n->status.reachable;
  
                        if(n->status.reachable) {
 -                              ifdebug(TRAFFIC) logger(LOG_DEBUG, "Node %s (%s) became reachable",
 +                              logger(DEBUG_TRAFFIC, LOG_DEBUG, "Node %s (%s) became reachable",
                                           n->name, n->hostname);
                        } else {
 -                              ifdebug(TRAFFIC) logger(LOG_DEBUG, "Node %s (%s) became unreachable",
 +                              logger(DEBUG_TRAFFIC, LOG_DEBUG, "Node %s (%s) became unreachable",
                                           n->name, n->hostname);
                        }
  
                        n->minmtu = 0;
                        n->mtuprobes = 0;
  
 -                      if(n->mtuevent) {
 -                              event_del(n->mtuevent);
 -                              n->mtuevent = NULL;
 -                      }
 +                      if(timeout_initialized(&n->mtuevent))
 +                              event_del(&n->mtuevent);
  
                        xasprintf(&envp[0], "NETNAME=%s", netname ? : "");
                        xasprintf(&envp[1], "DEVICE=%s", device ? : "");
  void graph(void) {
        subnet_cache_flush();
        sssp_bfs();
 +      check_reachability();
        mst_kruskal();
 -      graph_changed = true;
 -}
 -
 -
 -
 -/* Dump nodes and edges to a graphviz file.
 -         
 -   The file can be converted to an image with
 -   dot -Tpng graph_filename -o image_filename.png -Gconcentrate=true
 -*/
 -
 -void dump_graph(void) {
 -      avl_node_t *node;
 -      node_t *n;
 -      edge_t *e;
 -      char *filename = NULL, *tmpname = NULL;
 -      FILE *file;
 -      
 -      if(!graph_changed || !get_config_string(lookup_config(config_tree, "GraphDumpFile"), &filename))
 -              return;
 -
 -      graph_changed = false;
 -
 -      ifdebug(PROTOCOL) logger(LOG_NOTICE, "Dumping graph");
 -      
 -      if(filename[0] == '|') {
 -              file = popen(filename + 1, "w");
 -      } else {
 -              xasprintf(&tmpname, "%s.new", filename);
 -              file = fopen(tmpname, "w");
 -      }
 -
 -      if(!file) {
 -              logger(LOG_ERR, "Unable to open graph dump file %s: %s", filename, strerror(errno));
 -              free(tmpname);
 -              return;
 -      }
 -
 -      fprintf(file, "digraph {\n");
 -      
 -      /* dump all nodes first */
 -      for(node = node_tree->head; node; node = node->next) {
 -              n = node->data;
 -              fprintf(file, " %s [label = \"%s\"];\n", n->name, n->name);
 -      }
 -
 -      /* now dump all edges */
 -      for(node = edge_weight_tree->head; node; node = node->next) {
 -              e = node->data;
 -              fprintf(file, " %s -> %s;\n", e->from->name, e->to->name);
 -      }
 -
 -      fprintf(file, "}\n");   
 -      
 -      if(filename[0] == '|') {
 -              pclose(file);
 -      } else {
 -              fclose(file);
 -#ifdef HAVE_MINGW
 -              unlink(filename);
 -#endif
 -              rename(tmpname, filename);
 -              free(tmpname);
 -      }
  }
diff --combined src/ipv4.h
index 57d236d33115f901ce2087834917b4214bf42826,0000000000000000000000000000000000000000..bd63ad04785e559c93eb18b53ed6fc0482ff3643
mode 100644,000000..100644
--- /dev/null
@@@ -1,149 -1,0 +1,149 @@@
-                   2006 Guus Sliepen <guus@tinc-vpn.org>
 +/*
 +    ipv4.h -- missing IPv4 related definitions
 +    Copyright (C) 2005 Ivo Timmermans
++                  2006-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
 +    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.
 +*/
 +
 +#ifndef __TINC_IPV4_H__
 +#define __TINC_IPV4_H__
 +
 +#ifndef AF_INET
 +#define AF_INET 2
 +#endif
 +
 +#ifndef IPPROTO_ICMP
 +#define IPPROTO_ICMP 1
 +#endif
 +
 +#ifndef ICMP_DEST_UNREACH
 +#define ICMP_DEST_UNREACH 3
 +#endif
 +
 +#ifndef ICMP_FRAG_NEEDED
 +#define ICMP_FRAG_NEEDED 4
 +#endif
 +
 +#ifndef ICMP_NET_UNKNOWN
 +#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
 +
 +#ifndef ICMP_NET_ANO
 +#define ICMP_NET_ANO 9
 +#endif
 +
 +#ifndef IP_MSS
 +#define       IP_MSS          576
 +#endif
 +
 +#ifndef HAVE_STRUCT_IP
 +struct ip {
 +#if __BYTE_ORDER == __LITTLE_ENDIAN
 +      unsigned int ip_hl:4;
 +      unsigned int ip_v:4;
 +#else
 +      unsigned int ip_v:4;
 +      unsigned int ip_hl:4;
 +#endif
 +      uint8_t ip_tos;
 +      uint16_t ip_len;
 +      uint16_t ip_id; 
 +      uint16_t ip_off;
 +#define IP_RF 0x8000
 +#define IP_DF 0x4000
 +#define IP_MF 0x2000
 +      uint8_t ip_ttl;
 +      uint8_t ip_p;
 +      uint16_t ip_sum;
 +      struct in_addr ip_src, ip_dst;
 +} __attribute__ ((__packed__));
 +#endif
 +
 +#ifndef IP_OFFMASK
 +#define IP_OFFMASK 0x1fff
 +#endif
 +
 +#ifndef HAVE_STRUCT_ICMP
 +struct icmp {
 +      uint8_t icmp_type;
 +      uint8_t icmp_code;
 +      uint16_t icmp_cksum;
 +      union {
 +              uint8_t ih_pptr;
 +              struct in_addr ih_gwaddr;
 +              struct ih_idseq {
 +                      uint16_t icd_id;
 +                      uint16_t icd_seq;
 +              } ih_idseq;
 +              uint32_t ih_void;
 +
 +
 +              struct ih_pmtu {
 +                      uint16_t ipm_void;
 +                      uint16_t ipm_nextmtu;
 +              } ih_pmtu;
 +
 +              struct ih_rtradv {
 +                      uint8_t irt_num_addrs;
 +                      uint8_t irt_wpa;
 +                      uint16_t irt_lifetime;
 +              } ih_rtradv;
 +      } icmp_hun;
 +#define icmp_pptr icmp_hun.ih_pptr
 +#define icmp_gwaddr icmp_hun.ih_gwaddr
 +#define icmp_id icmp_hun.ih_idseq.icd_id
 +#define icmp_seq icmp_hun.ih_idseq.icd_seq
 +#define icmp_void icmp_hun.ih_void
 +#define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void
 +#define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu
 +#define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs
 +#define icmp_wpa icmp_hun.ih_rtradv.irt_wpa
 +#define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime
 +      union {
 +              struct {
 +                      uint32_t its_otime;
 +                      uint32_t its_rtime;
 +                      uint32_t its_ttime;
 +              } id_ts;
 +              struct {
 +                      struct ip idi_ip;
 +              } id_ip;
 +              uint32_t id_mask;
 +              uint8_t id_data[1];
 +      } icmp_dun;
 +#define icmp_otime icmp_dun.id_ts.its_otime
 +#define icmp_rtime icmp_dun.id_ts.its_rtime
 +#define icmp_ttime icmp_dun.id_ts.its_ttime
 +#define icmp_ip icmp_dun.id_ip.idi_ip
 +#define icmp_radv icmp_dun.id_radv
 +#define icmp_mask icmp_dun.id_mask
 +#define icmp_data icmp_dun.id_data
 +} __attribute__ ((__packed__));
 +#endif
 +
 +#endif /* __TINC_IPV4_H__ */
diff --combined src/ipv6.h
index d98001d6a0c2569ad26358a0d43fd5f0f561eec2,0000000000000000000000000000000000000000..6a4466f11d37e652121a11037e11eab4b09d1c77
mode 100644,000000..100644
--- /dev/null
@@@ -1,128 -1,0 +1,130 @@@
-                   2006 Guus Sliepen <guus@tinc-vpn.org>
 +/*
 +    ipv6.h -- missing IPv6 related definitions
 +    Copyright (C) 2005 Ivo Timmermans
++                  2006-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
 +    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.
 +*/
 +
 +#ifndef __TINC_IPV6_H__
 +#define __TINC_IPV6_H__
 +
 +#ifndef AF_INET6
 +#define AF_INET6 10
 +#endif
 +
 +#ifndef IPPROTO_ICMPV6
 +#define IPPROTO_ICMPV6 58
 +#endif
 +
 +#ifndef HAVE_STRUCT_IN6_ADDR
 +struct in6_addr {
 +      union {
 +              uint8_t u6_addr8[16];
 +              uint16_t u6_addr16[8];
 +              uint32_t u6_addr32[4];
 +      } in6_u;
 +} __attribute__ ((__packed__));
 +#define s6_addr in6_u.u6_addr8
 +#define s6_addr16 in6_u.u6_addr16
 +#define s6_addr32 in6_u.u6_addr32
 +#endif
 +
 +#ifndef HAVE_STRUCT_SOCKADDR_IN6
 +struct sockaddr_in6 {
 +      uint16_t sin6_family;
 +      uint16_t sin6_port;
 +      uint32_t sin6_flowinfo;
 +      struct in6_addr sin6_addr;
 +      uint32_t sin6_scope_id;
 +} __attribute__ ((__packed__));
 +#endif
 +
 +#ifndef IN6_IS_ADDR_V4MAPPED
 +#define IN6_IS_ADDR_V4MAPPED(a) \
 +        ((((__const uint32_t *) (a))[0] == 0) \
 +        && (((__const uint32_t *) (a))[1] == 0) \
 +        && (((__const uint32_t *) (a))[2] == htonl (0xffff)))
 +#endif
 +
 +#ifndef HAVE_STRUCT_IP6_HDR
 +struct ip6_hdr {
 +      union {
 +              struct ip6_hdrctl {
 +                      uint32_t ip6_un1_flow;
 +                      uint16_t ip6_un1_plen;
 +                      uint8_t ip6_un1_nxt;
 +                      uint8_t ip6_un1_hlim;
 +              } ip6_un1;
 +              uint8_t ip6_un2_vfc;
 +      } ip6_ctlun;
 +      struct in6_addr ip6_src;
 +      struct in6_addr ip6_dst;
 +} __attribute__ ((__packed__));
 +#define ip6_vfc ip6_ctlun.ip6_un2_vfc
 +#define ip6_flow ip6_ctlun.ip6_un1.ip6_un1_flow
 +#define ip6_plen ip6_ctlun.ip6_un1.ip6_un1_plen
 +#define ip6_nxt ip6_ctlun.ip6_un1.ip6_un1_nxt
 +#define ip6_hlim ip6_ctlun.ip6_un1.ip6_un1_hlim
 +#define ip6_hops ip6_ctlun.ip6_un1.ip6_un1_hlim
 +#endif
 +
 +#ifndef HAVE_STRUCT_ICMP6_HDR
 +struct icmp6_hdr {
 +      uint8_t icmp6_type;
 +      uint8_t icmp6_code;
 +      uint16_t icmp6_cksum;
 +      union {
 +              uint32_t icmp6_un_data32[1];
 +              uint16_t icmp6_un_data16[2];
 +              uint8_t icmp6_un_data8[4];
 +      } icmp6_dataun;
 +} __attribute__ ((__packed__));
 +#define ICMP6_DST_UNREACH_NOROUTE 0
 +#define ICMP6_DST_UNREACH 1
 +#define ICMP6_PACKET_TOO_BIG 2
++#define ICMP6_TIME_EXCEEDED 3
 +#define ICMP6_DST_UNREACH_ADMIN 1
 +#define ICMP6_DST_UNREACH_ADDR 3
++#define ICMP6_TIME_EXCEED_TRANSIT 0
 +#define ND_NEIGHBOR_SOLICIT 135
 +#define ND_NEIGHBOR_ADVERT 136
 +#define icmp6_data32 icmp6_dataun.icmp6_un_data32
 +#define icmp6_data16 icmp6_dataun.icmp6_un_data16
 +#define icmp6_data8 icmp6_dataun.icmp6_un_data8
 +#define icmp6_mtu icmp6_data32[0]
 +#endif
 +
 +#ifndef HAVE_STRUCT_ND_NEIGHBOR_SOLICIT
 +struct nd_neighbor_solicit {
 +      struct icmp6_hdr nd_ns_hdr;
 +      struct in6_addr nd_ns_target;
 +} __attribute__ ((__packed__));
 +#define ND_OPT_SOURCE_LINKADDR 1
 +#define ND_OPT_TARGET_LINKADDR 2
 +#define nd_ns_type nd_ns_hdr.icmp6_type
 +#define nd_ns_code nd_ns_hdr.icmp6_code
 +#define nd_ns_cksum nd_ns_hdr.icmp6_cksum
 +#define nd_ns_reserved nd_ns_hdr.icmp6_data32[0]
 +#endif
 +
 +#ifndef HAVE_STRUCT_ND_OPT_HDR
 +struct nd_opt_hdr {
 +      uint8_t nd_opt_type;
 +      uint8_t nd_opt_len;
 +} __attribute__ ((__packed__));
 +#endif
 +
 +#endif /* __TINC_IPV6_H__ */
diff --combined src/multicast_device.c
index 0000000000000000000000000000000000000000,0b232dbb6f0238d260a8f658f6c7a69d19374b9b..e5e9a3fbd4fa7a009ec0b4cd7f11bc9d09341671
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,228 +1,228 @@@
 -              logger(LOG_ERR, "Device variable required for %s", device_info);
+ /*
+     device.c -- multicast socket
+     Copyright (C) 2002-2005 Ivo Timmermans,
+                   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
+     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 "conf.h"
+ #include "device.h"
+ #include "net.h"
+ #include "logger.h"
+ #include "netutl.h"
+ #include "utils.h"
+ #include "route.h"
+ #include "xalloc.h"
+ static char *device_info;
+ static uint64_t device_total_in = 0;
+ static uint64_t device_total_out = 0;
+ static struct addrinfo *ai = NULL;
+ static mac_t ignore_src = {{0}};
+ static bool setup_device(void) {
+       char *host;
+       char *port;
+       char *space;
+       int ttl = 1;
+       device_info = "multicast socket";
+       get_config_string(lookup_config(config_tree, "Interface"), &iface);
+       if(!get_config_string(lookup_config(config_tree, "Device"), &device)) {
 -              logger(LOG_ERR, "Port number required for %s", device_info);
++              logger(DEBUG_ALWAYS, LOG_ERR, "Device variable required for %s", device_info);
+               return false;
+       }
+       host = xstrdup(device);
+       space = strchr(host, ' ');
+       if(!space) {
 -              logger(LOG_ERR, "Creating socket failed: %s", sockstrerror(sockerrno));
++              logger(DEBUG_ALWAYS, LOG_ERR, "Port number required for %s", device_info);
+               return false;
+       }
+       *space++ = 0;
+       port = space;
+       space = strchr(port, ' ');
+       if(space) {
+               *space++ = 0;
+               ttl = atoi(space);
+       }
+       ai = str2addrinfo(host, port, SOCK_DGRAM);
+       if(!ai)
+               return false;
+       device_fd = socket(ai->ai_family, SOCK_DGRAM, IPPROTO_UDP);
+       if(device_fd < 0) {
 -              logger(LOG_ERR, "Can't bind to %s %s: %s", host, port, sockstrerror(sockerrno));
++              logger(DEBUG_ALWAYS, LOG_ERR, "Creating socket failed: %s", sockstrerror(sockerrno));
+               return false;
+       }
+ #ifdef FD_CLOEXEC
+       fcntl(device_fd, F_SETFD, FD_CLOEXEC);
+ #endif
+       static const int one = 1;
+       setsockopt(device_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof one);
+       if(bind(device_fd, ai->ai_addr, ai->ai_addrlen)) {
+               closesocket(device_fd);
 -                              logger(LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno));
++              logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to %s %s: %s", host, port, sockstrerror(sockerrno));
+               return false;
+       }
+       switch(ai->ai_family) {
+ #ifdef IP_ADD_MEMBERSHIP
+               case AF_INET: {
+                       struct ip_mreq mreq;
+                       struct sockaddr_in in;
+                       memcpy(&in, ai->ai_addr, sizeof in);
+                       mreq.imr_multiaddr.s_addr = in.sin_addr.s_addr;
+                       mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+                       if(setsockopt(device_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&mreq, sizeof mreq)) {
 -                              logger(LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno));
++                              logger(DEBUG_ALWAYS, LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno));
+                               closesocket(device_fd);
+                               return false;
+                       }
+ #ifdef IP_MULTICAST_LOOP
+                       setsockopt(device_fd, IPPROTO_IP, IP_MULTICAST_LOOP, (const void *)&one, sizeof one);
+ #endif
+ #ifdef IP_MULTICAST_TTL
+                       setsockopt(device_fd, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&ttl, sizeof ttl);
+ #endif
+               } break;
+ #endif
+ #ifdef IPV6_JOIN_GROUP
+               case AF_INET6: {
+                       struct ipv6_mreq mreq;
+                       struct sockaddr_in6 in6;
+                       memcpy(&in6, ai->ai_addr, sizeof in6);
+                       memcpy(&mreq.ipv6mr_multiaddr, &in6.sin6_addr, sizeof mreq.ipv6mr_multiaddr);
+                       mreq.ipv6mr_interface = in6.sin6_scope_id;
+                       if(setsockopt(device_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (void *)&mreq, sizeof mreq)) {
 -                      logger(LOG_ERR, "Multicast for address family %hx unsupported", ai->ai_family);
++                              logger(DEBUG_ALWAYS, LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno));
+                               closesocket(device_fd);
+                               return false;
+                       }
+ #ifdef IPV6_MULTICAST_LOOP
+                       setsockopt(device_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (const void *)&one, sizeof one);
+ #endif
+ #ifdef IPV6_MULTICAST_HOPS
+                       setsockopt(device_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (void *)&ttl, sizeof ttl);
+ #endif
+               } break;
+ #endif
+       
+               default:
 -      logger(LOG_INFO, "%s is a %s", device, device_info);
++                      logger(DEBUG_ALWAYS, LOG_ERR, "Multicast for address family %hx unsupported", ai->ai_family);
+                       closesocket(device_fd);
+                       return false;
+       }
 -              logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
++      logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info);
+       return true;
+ }
+ static void close_device(void) {
+       close(device_fd);
+       free(device);
+       free(iface);
+       if(ai)
+               freeaddrinfo(ai);
+ }
+ static bool read_packet(vpn_packet_t *packet) {
+       int lenin;
+       if((lenin = recv(device_fd, packet->data, MTU, 0)) <= 0) {
 -              ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Ignoring loopback packet of %d bytes from %s", lenin, device_info);
++              logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
+                          device, strerror(errno));
+               return false;
+       }
+       if(!memcmp(&ignore_src, packet->data + 6, sizeof ignore_src)) {
 -      ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len,
++              logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Ignoring loopback packet of %d bytes from %s", lenin, device_info);
+               packet->len = 0;
+               return true;
+       }
+       packet->len = lenin;
+       device_total_in += packet->len;
 -      ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s",
++      logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len,
+                          device_info);
+       return true;
+ }
+ static bool write_packet(vpn_packet_t *packet) {
 -              logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device,
++      logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
+                          packet->len, device_info);
+       if(sendto(device_fd, packet->data, packet->len, 0, ai->ai_addr, ai->ai_addrlen) < 0) {
 -      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(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device,
+                          strerror(errno));
+               return false;
+       }
+       device_total_out += packet->len;
+       memcpy(&ignore_src, packet->data + 6, sizeof ignore_src);
+       return true;
+ }
+ static void dump_device_stats(void) {
 -      logger(LOG_ERR, "Raw socket device not supported on this platform");
++      logger(DEBUG_ALWAYS, LOG_DEBUG, "Statistics for %s %s:", device_info, device);
++      logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes in:  %10"PRIu64, device_total_in);
++      logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out);
+ }
+ const devops_t multicast_devops = {
+       .setup = setup_device,
+       .close = close_device,
+       .read = read_packet,
+       .write = write_packet,
+       .dump_stats = dump_device_stats,
+ };
+ #if 0
+ static bool not_supported(void) {
++      logger(DEBUG_ALWAYS, LOG_ERR, "Raw socket device not supported on this platform");
+       return false;
+ }
+ const devops_t multicast_devops = {
+       .setup = not_supported,
+       .close = NULL,
+       .read = NULL,
+       .write = NULL,
+       .dump_stats = NULL,
+ };
+ #endif
diff --combined src/net.c
index 10a2d20645951081fef4c68e70fdf4e97f8df111,9799feabdf73952f3dc709434210f6014dee31d4..db5743af2e9179915aca7686ac5c7fa497050746
+++ b/src/net.c
@@@ -1,7 -1,7 +1,7 @@@
  /*
      net.c -- most of the network code
      Copyright (C) 1998-2005 Ivo Timmermans,
-                   2000-2011 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2012 Guus Sliepen <guus@tinc-vpn.org>
                    2006      Scott Lamb <slamb@slamb.org>
                  2011      Loïc Grenié <loic.grenie@gmail.com>
  
  
  #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;
  
 -      ifdebug(PROTOCOL) logger(LOG_DEBUG, "Purging unreachable nodes");
 +      logger(DEBUG_PROTOCOL, LOG_DEBUG, "Purging unreachable nodes");
  
        /* Remove all edges and subnets owned by unreachable nodes. */
  
@@@ -58,7 -69,8 +58,7 @@@
                n = nnode->data;
  
                if(!n->status.reachable) {
 -                      ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Purging node %s (%s)", n->name,
 -                                         n->hostname);
 +                      logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Purging node %s (%s)", n->name, n->hostname);
  
                        for(snode = n->subnet_tree->head; snode; snode = snext) {
                                snext = snode->next;
        }
  }
  
 -/*
 -  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)",
 +      logger(DEBUG_CONNECTIONS, 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(everyone, c->edge);
                }
        }
  
-       /* Check if this was our outgoing connection */
+       free_connection_partially(c);
  
-       if(c->outgoing)
-               retry_outgoing(c->outgoing);
+       /* Check if this was our outgoing connection */
  
-       connection_del(c);
+       if(c->outgoing) {
 -              c->status.remove = false;
+               do_outgoing_connection(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;
                c = node->data;
  
 +              if(c->status.control)
 +                      continue;
 +
                if(c->last_ping_time + pingtimeout <= now) {
                        if(c->status.active) {
                                if(c->status.pinged) {
 -                                      ifdebug(CONNECTIONS) logger(LOG_INFO, "%s (%s) didn't respond to PING in %ld seconds",
 +                                      logger(DEBUG_CONNECTIONS, LOG_INFO, "%s (%s) didn't respond to PING in %ld seconds",
-                                                          c->name, c->hostname, now - c->last_ping_time);
+                                                          c->name, c->hostname, (long)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) {
 +                                      logger(DEBUG_CONNECTIONS, LOG_WARNING, "Timeout while connecting to %s (%s)", c->name, c->hostname);
                                        c->status.connecting = false;
                                        closesocket(c->socket);
                                        do_outgoing_connection(c);
                                } else {
 +                                      logger(DEBUG_CONNECTIONS, 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, (long)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)) {
 -                      if(packet.len) {
 -                              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(DEBUG_ALWAYS, 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;
 -                              }
 -                      }
 +      event_add(event, &(struct timeval){pingtimeout, 0});
 +}
  
 -                      if(!receive_meta(c)) {
 -                              terminate_connection(c, c->status.active);
 -                              continue;
 -                      }
 -              }
 -
 -              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 {
 +                      logger(DEBUG_CONNECTIONS, 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(i);
 -
 -              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;
 -
 -      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
 -
 -      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
 -
 -              maxfd = build_fdset(&readset, &writeset);
 -
 -#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
 -
 -              if(r < 0) {
 -                      if(!sockwouldblock(sockerrno)) {
 -                              logger(LOG_ERR, "Error while waiting for input: %s", sockstrerror(sockerrno));
 -                              dump_connections();
 -                              return 1;
 -                      }
 -              }
 +static void sigterm_handler(int signal, short events, void *data) {
 +      logger(DEBUG_ALWAYS, LOG_NOTICE, "Got %s signal", strsignal(signal));
 +      event_loopexit(NULL);
 +}
  
 -              if(r > 0)
 -                      check_network_activity(&readset, &writeset);
 +static void sighup_handler(int signal, short events, void *data) {
 +      logger(DEBUG_ALWAYS, LOG_NOTICE, "Got %s signal", strsignal(signal));
 +      reopenlogger();
 +      reload_configuration();
 +}
  
 -              if(do_purge) {
 -                      purge();
 -                      do_purge = false;
 -              }
 +static void sigalrm_handler(int signal, short events, void *data) {
 +      logger(DEBUG_ALWAYS, LOG_NOTICE, "Got %s signal", strsignal(signal));
 +      retry();
 +}
  
 -              /* Let's check if everybody is still alive */
 +int reload_configuration(void) {
 +      connection_t *c;
 +      splay_node_t *node, *next;
 +      char *fname;
 +      struct stat s;
 +      static time_t last_config_check = 0;
  
 -              if(last_ping_check + pingtimeout <= now) {
 -                      check_dead_connections();
 -                      last_ping_check = now;
 +      /* Reread our own configuration file */
  
 -                      if(routing_mode == RMODE_SWITCH)
 -                              age_subnets();
 +      exit_configuration(&config_tree);
 +      init_configuration(&config_tree);
  
 -                      age_past_requests();
 +      if(!read_server_config()) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Unable to reread configuration file, exitting.");
 +              event_loopexit(NULL);
 +              return EINVAL;
 +      }
  
 -                      /* Should we regenerate our key? */
 +      /* 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(keyexpires <= now) {
 -                              avl_node_t *node;
 -                              node_t *n;
 +              if(c->status.control)
 +                      continue;
 +              
 +              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);
 +      }
  
 -                              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(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);
                        }
 -                      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);
                }
        }
 +}
  
 -#ifdef HAVE_PSELECT
 -      /* Restore SIGHUP & SIGALARM mask */
 -      sigprocmask(SIG_SETMASK, &omask, NULL);
 +/*
 +  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(DEBUG_ALWAYS, LOG_ERR, "Error while waiting for input: %s", strerror(errno));
 +              return 1;
 +      }
 +
 +#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.h
index 6ca13d488a307cdea812521c88c4568009fcdfa8,b6f54f2fad964849c8595f934abb0e93c9a8e2ba..27b5eb5b5a8e31ed5c53acbb643b5960fbb6e535
+++ b/src/net.h
@@@ -1,7 -1,7 +1,7 @@@
  /*
      net.h -- header for net.c
      Copyright (C) 1998-2005 Ivo Timmermans
-                   2000-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-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
@@@ -21,9 -21,9 +21,9 @@@
  #ifndef __TINC_NET_H__
  #define __TINC_NET_H__
  
 -#include <openssl/evp.h>
 -
  #include "ipv6.h"
 +#include "cipher.h"
 +#include "digest.h"
  
  #ifdef ENABLE_JUMBOGRAMS
  #define MTU 9018                              /* 9000 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */
  #define MTU 1518                              /* 1500 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */
  #endif
  
 -#define MAXSIZE (MTU + 4 + EVP_MAX_BLOCK_LENGTH + EVP_MAX_MD_SIZE + MTU/64 + 20)      /* MTU + seqno + padding + HMAC + compressor overhead */
 +#define MAXSIZE (MTU + 4 + CIPHER_MAX_BLOCK_SIZE + DIGEST_MAX_SIZE + MTU/64 + 20)     /* MTU + seqno + padding + HMAC + compressor overhead */
  #define MAXBUFSIZE ((MAXSIZE > 2048 ? MAXSIZE : 2048) + 128)  /* Enough room for a request with a MAXSIZEd packet or a 8192 bits RSA key */
  
 -#define MAXSOCKETS 128                        /* Overkill... */
 +#define MAXSOCKETS 8                  /* Probably overkill... */
  
  typedef struct mac_t {
        uint8_t x[6];
@@@ -84,8 -84,6 +84,8 @@@ typedef struct vpn_packet_t 
  } vpn_packet_t;
  
  typedef struct listen_socket_t {
 +      struct event ev_tcp;
 +      struct event ev_udp;
        int tcp;
        int udp;
        sockaddr_t sa;
@@@ -100,7 -98,7 +100,7 @@@ typedef struct outgoing_t 
        struct config_t *cfg;
        struct addrinfo *ai;
        struct addrinfo *aip;
 -      struct event *event;
 +      struct event ev;
  } outgoing_t;
  
  extern list_t *outgoing_list;
@@@ -113,26 -111,31 +113,26 @@@ extern bool localdiscovery
  
  extern listen_socket_t listen_socket[MAXSOCKETS];
  extern int listen_sockets;
 -extern int keyexpires;
  extern int keylifetime;
  extern int udp_rcvbuf;
  extern int udp_sndbuf;
  extern bool do_prune;
 -extern bool do_purge;
  extern char *myport;
 -extern time_t now;
  extern int contradicting_add_edge;
  extern int contradicting_del_edge;
  
 -extern volatile bool running;
 -
  /* Yes, very strange placement indeed, but otherwise the typedefs get all tangled up */
  #include "connection.h"
  #include "node.h"
  
  extern void retry_outgoing(outgoing_t *);
 -extern void handle_incoming_vpn_data(int);
 +extern void handle_incoming_vpn_data(int, short, void *);
  extern void finish_connecting(struct connection_t *);
 -extern void do_outgoing_connection(struct connection_t *);
 -extern bool handle_new_meta_connection(int);
 +extern bool do_outgoing_connection(struct connection_t *);
 +extern void handle_new_meta_connection(int, short, void *);
  extern int setup_listen_socket(const sockaddr_t *);
  extern int setup_vpn_in_socket(const sockaddr_t *);
 -extern void send_packet(const struct node_t *, vpn_packet_t *);
 +extern void send_packet(struct node_t *, vpn_packet_t *);
  extern void receive_tcppacket(struct connection_t *, const char *, int);
  extern void broadcast_packet(const struct node_t *, vpn_packet_t *);
  extern bool setup_network(void);
@@@ -142,16 -145,8 +142,16 @@@ extern void close_network_connections(v
  extern int main_loop(void);
  extern void terminate_connection(struct connection_t *, bool);
  extern void flush_queue(struct node_t *);
 +extern bool node_read_ecdsa_public_key(struct node_t *);
 +extern bool read_ecdsa_public_key(struct connection_t *);
  extern bool read_rsa_public_key(struct connection_t *);
  extern void send_mtu_probe(struct node_t *);
 +extern void handle_device_data(int, short, void *);
 +extern void handle_meta_connection_data(int, short, void *);
 +extern void regenerate_key(void);
 +extern void purge(void);
 +extern void retry(void);
 +extern int reload_configuration(void);
  extern void load_all_subnets(void);
  
  #ifndef HAVE_MINGW
diff --combined src/net_packet.c
index ac3b0cc59d1db6139cf5d33e10658783dc8dd67f,b11949a2048309b9006f47b41b33b26618a9d798..ca6aff3d9890e66051c227a8dcccb251bb420524
@@@ -1,7 -1,7 +1,7 @@@
  /*
      net_packet.c -- Handles in- and outgoing VPN packets
      Copyright (C) 1998-2005 Ivo Timmermans,
-                   2000-2011 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2012 Guus Sliepen <guus@tinc-vpn.org>
                    2010      Timothy Redaelli <timothy@redaelli.eu>
                    2010      Brandon Black <blblack@gmail.com>
  
  #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
@@@ -78,16 -77,16 +78,16 @@@ bool localdiscovery = false
     which will be broadcast to the local network.
  */
  
 -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);
 +              logger(DEBUG_TRAFFIC, LOG_INFO, "Trying to send MTU probe to unreachable or rekeying node %s (%s)", n->name, n->hostname);
                n->mtuprobes = 0;
                return;
        }
                        goto end;
                }
  
 -              ifdebug(TRAFFIC) logger(LOG_INFO, "%s (%s) did not respond to UDP ping, restarting PMTU discovery", n->name, n->hostname);
 +              logger(DEBUG_TRAFFIC, LOG_INFO, "%s (%s) did not respond to UDP ping, restarting PMTU discovery", n->name, n->hostname);
                n->mtuprobes = 1;
                n->minmtu = 0;
                n->maxmtu = MTU;
        }
  
        if(n->mtuprobes >= 10 && n->mtuprobes < 32 && !n->minmtu) {
 -              ifdebug(TRAFFIC) logger(LOG_INFO, "No response to MTU probes from %s (%s)", n->name, n->hostname);
 +              logger(DEBUG_TRAFFIC, LOG_INFO, "No response to MTU probes from %s (%s)", n->name, n->hostname);
                n->mtuprobes = 31;
        }
  
                else
                        n->maxmtu = n->minmtu;
                n->mtu = n->minmtu;
 -              ifdebug(TRAFFIC) logger(LOG_INFO, "Fixing MTU of %s (%s) to %d after %d probes", n->name, n->hostname, n->mtu, n->mtuprobes);
 +              logger(DEBUG_TRAFFIC, LOG_INFO, "Fixing MTU of %s (%s) to %d after %d probes", n->name, n->hostname, n->mtu, n->mtuprobes);
                n->mtuprobes = 31;
        }
  
                        len = 64;
                
                memset(packet.data, 0, 14);
 -              RAND_pseudo_bytes(packet.data + 14, len - 14);
 +              randomize(packet.data + 14, len - 14);
                packet.len = len;
                if(i >= 3 && n->mtuprobes <= 10)
                        packet.priority = -1;
                else
                        packet.priority = 0;
  
 -              ifdebug(TRAFFIC) logger(LOG_INFO, "Sending MTU probe length %d to %s (%s)", len, n->name, n->hostname);
 +              logger(DEBUG_TRAFFIC, LOG_INFO, "Sending MTU probe length %d to %s (%s)", len, n->name, n->hostname);
  
                send_udppacket(n, &packet);
        }
  
  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) {
 -      ifdebug(TRAFFIC) logger(LOG_INFO, "Got MTU probe length %d from %s (%s)", packet->len, n->name, n->hostname);
 +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) {
 +      logger(DEBUG_TRAFFIC, LOG_INFO, "Got MTU probe length %d from %s (%s)", packet->len, n->name, n->hostname);
  
        if(!packet->data[0]) {
                packet->data[0] = 1;
@@@ -242,20 -239,21 +242,20 @@@ static length_t uncompress_packet(uint8
  /* VPN packet I/O */
  
  static void receive_packet(node_t *n, vpn_packet_t *packet) {
 -      ifdebug(TRAFFIC) logger(LOG_DEBUG, "Received packet of %d bytes from %s (%s)",
 +      logger(DEBUG_TRAFFIC, 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) {
 -              ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got packet from %s (%s) but he hasn't got our key yet",
 +      if(!cipher_active(&n->incipher)) {
 +              logger(DEBUG_TRAFFIC, 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) {
 -              ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got too short packet from %s (%s)",
 +      if(inpkt->len < sizeof inpkt->seqno + digest_length(&n->indigest)) {
 +              logger(DEBUG_TRAFFIC, 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)) {
 +                      logger(DEBUG_TRAFFIC, 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)) {
 +                      logger(DEBUG_TRAFFIC, 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) {
                if(inpkt->seqno != n->received_seqno + 1) {
                        if(inpkt->seqno >= n->received_seqno + replaywin * 8) {
                                if(n->farfuture++ < replaywin >> 2) {
 -                                      logger(LOG_WARNING, "Packet from %s (%s) is %d seqs in the future, dropped (%u)",
 +                                      logger(DEBUG_ALWAYS, LOG_WARNING, "Packet from %s (%s) is %d seqs in the future, dropped (%u)",
                                                n->name, n->hostname, inpkt->seqno - n->received_seqno - 1, n->farfuture);
                                        return;
                                }
 -                              logger(LOG_WARNING, "Lost %d packets from %s (%s)",
 +                              logger(DEBUG_ALWAYS, LOG_WARNING, "Lost %d packets from %s (%s)",
                                                inpkt->seqno - n->received_seqno - 1, n->name, n->hostname);
                                memset(n->late, 0, replaywin);
                        } else if (inpkt->seqno <= n->received_seqno) {
                                if((n->received_seqno >= replaywin * 8 && inpkt->seqno <= n->received_seqno - replaywin * 8) || !(n->late[(inpkt->seqno / 8) % replaywin] & (1 << inpkt->seqno % 8))) {
 -                                      logger(LOG_WARNING, "Got late or replayed packet from %s (%s), seqno %d, last received %d",
 +                                      logger(DEBUG_ALWAYS, LOG_WARNING, "Got late or replayed packet from %s (%s), seqno %d, last received %d",
                                                n->name, n->hostname, inpkt->seqno, n->received_seqno);
                                        return;
                                }
                        } else {
 -                              for(i = n->received_seqno + 1; i < inpkt->seqno; i++)
 +                              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 */
  
                outpkt = pkt[nextpkt++];
  
                if((outpkt->len = uncompress_packet(outpkt->data, inpkt->data, inpkt->len, n->incompression)) < 0) {
 -                      ifdebug(TRAFFIC) logger(LOG_ERR, "Error while uncompressing packet from %s (%s)",
 +                      logger(DEBUG_TRAFFIC, LOG_ERR, "Error while uncompressing packet from %s (%s)",
                                                 n->name, n->hostname);
                        return;
                }
@@@ -386,24 -394,22 +386,24 @@@ 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 origpriority;
  
        if(!n->status.reachable) {
 -              ifdebug(TRAFFIC) logger(LOG_INFO, "Trying to send UDP packet to unreachable node %s (%s)", n->name, n->hostname);
 +              logger(DEBUG_TRAFFIC, LOG_INFO, "Trying to send UDP packet to unreachable node %s (%s)", n->name, n->hostname);
                return;
        }
  
        /* Make sure we have a valid key */
  
        if(!n->status.validkey) {
 -              ifdebug(TRAFFIC) logger(LOG_INFO,
 +              time_t now = time(NULL);
 +
 +              logger(DEBUG_TRAFFIC, LOG_INFO,
                                   "No valid key known yet for %s (%s), forwarding via TCP",
                                   n->name, n->hostname);
  
        }
  
        if(n->options & OPTION_PMTU_DISCOVERY && inpkt->len > n->minmtu && (inpkt->data[12] | inpkt->data[13])) {
 -              ifdebug(TRAFFIC) logger(LOG_INFO,
 +              logger(DEBUG_TRAFFIC, LOG_INFO,
                                "Packet for %s (%s) larger than minimum MTU, forwarding via %s",
                                n->name, n->hostname, n != n->nexthop ? n->nexthop->name : "TCP");
  
                return;
        }
  
 -      origlen = inpkt->len;
 -      origpriority = inpkt->priority;
 -
        /* Compress the packet */
  
        if(n->outcompression) {
                outpkt = pkt[nextpkt++];
  
                if((outpkt->len = compress_packet(outpkt->data, inpkt->data, inpkt->len, n->outcompression)) < 0) {
 -                      ifdebug(TRAFFIC) logger(LOG_ERR, "Error while compressing packet to %s (%s)",
 +                      logger(DEBUG_TRAFFIC, LOG_ERR, "Error while compressing packet to %s (%s)",
                                   n->name, n->hostname);
                        return;
                }
        /* 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)) {
 +                      logger(DEBUG_TRAFFIC, 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 */
        if(priorityinheritance && origpriority != priority
           && listen_socket[n->sock].sa.sa.sa_family == AF_INET) {
                priority = origpriority;
 -              ifdebug(TRAFFIC) logger(LOG_DEBUG, "Setting outgoing packet priority to %d", priority);
 +              logger(DEBUG_TRAFFIC, LOG_DEBUG, "Setting outgoing packet priority to %d", priority);
                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));
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setsockopt", strerror(errno));
        }
  #endif
  
                        if(n->mtu >= origlen)
                                n->mtu = origlen - 1;
                } else
 -                      ifdebug(TRAFFIC) logger(LOG_WARNING, "Error sending packet to %s (%s): %s", n->name, n->hostname, sockstrerror(sockerrno));
 +                      logger(DEBUG_TRAFFIC, LOG_WARNING, "Error sending packet to %s (%s): %s", n->name, n->hostname, sockstrerror(sockerrno));
        }
  
  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);
 +              n->out_packets++;
 +              n->out_bytes += packet->len;
                devops.write(packet);
                return;
        }
  
 -      ifdebug(TRAFFIC) logger(LOG_ERR, "Sending packet of %d bytes to %s (%s)",
 +      logger(DEBUG_TRAFFIC, LOG_ERR, "Sending packet of %d bytes to %s (%s)",
                           packet->len, n->name, n->hostname);
  
        if(!n->status.reachable) {
 -              ifdebug(TRAFFIC) logger(LOG_INFO, "Node %s (%s) is not reachable",
 +              logger(DEBUG_TRAFFIC, LOG_INFO, "Node %s (%s) is not reachable",
                                   n->name, n->hostname);
                return;
        }
  
 +      n->out_packets++;
 +      n->out_bytes += packet->len;
 +
        via = (packet->priority == -1 || n->via == myself) ? n->nexthop : n->via;
  
        if(via != n)
 -              ifdebug(TRAFFIC) logger(LOG_INFO, "Sending packet to %s via %s (%s)",
 +              logger(DEBUG_TRAFFIC, LOG_INFO, "Sending packet to %s via %s (%s)",
                           n->name, via->name, n->via->hostname);
  
        if(packet->priority == -1 || ((myself->options | via->options) & OPTION_TCPONLY)) {
  /* 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)",
 +      logger(DEBUG_TRAFFIC, LOG_INFO, "Broadcasting packet of %d bytes from %s (%s)",
                           packet->len, from->name, from->hostname);
  
        if(from != myself) {
  }
  
  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(listen_socket[sock].udp, (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));
 +                      logger(DEBUG_ALWAYS, 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);
                n = try_harder(&from, &pkt);
                if(n)
                        update_node_udp(n, &from);
 -              else ifdebug(PROTOCOL) {
 +              else if(debug_level >= DEBUG_PROTOCOL) {
                        hostname = sockaddr2hostname(&from);
 -                      logger(LOG_WARNING, "Received UDP packet from unknown source %s", hostname);
 +                      logger(DEBUG_PROTOCOL, LOG_WARNING, "Received UDP packet from unknown source %s", hostname);
                        free(hostname);
                        return;
                }
                        return;
        }
  
 -      n->sock = sock;
 +      n->sock = (intptr_t)data;
  
        receive_udppacket(n, &pkt);
  }
 +
 +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
index 28e086440ee168c1a305cd217f262e3e577e9cf8,4b90737f3c68d4e8d43baf473e110f27ab2c4bf8..c181df9472e78c07f0e1ea6efae2d27259d9d574
  
  #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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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;
 +
 +      /* 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 */
  
 -      /* Try again with PEM_read_RSA_PUBKEY. */
 +      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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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;
 +
 +      /* 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, LOG_ERR, "Could not stat ECDSA private key file `%s': %s'", fname, strerror(errno));
 +              free(fname);
 +              return false;
 +      }
  
 -      logger(LOG_ERR, "No public key for %s specified!", c->name);
 +      if(s.st_mode & ~0100700)
 +              logger(DEBUG_ALWAYS, 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);
  
 -      return false;
 +      if(!result) 
 +              logger(DEBUG_ALWAYS, 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;
 +
 +      /* First, check for simple PrivateKey statement */
  
 -      if(get_config_string(lookup_config(config_tree, "PrivateKey"), &key)) {
 -              if(!get_config_string(lookup_config(config_tree, "PublicKey"), &pubkey)) {
 -                      logger(LOG_ERR, "PrivateKey used but no PublicKey found!");
 +      if(get_config_string(lookup_config(config_tree, "PrivateKey"), &d)) {
 +              if(!get_config_string(lookup_config(config_tree, "PublicKey"), &n)) {
 +                      logger(DEBUG_ALWAYS, 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);
  
        fp = fopen(fname, "r");
  
        if(!fp) {
 -              logger(LOG_ERR, "Error reading RSA private key file `%s': %s",
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Error reading RSA 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 RSA private key file `%s': %s'",
 -                              fname, strerror(errno));
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Could not stat RSA private key file `%s': %s'", fname, strerror(errno));
                free(fname);
                return false;
        }
  
        if(s.st_mode & ~0100700)
 -              logger(LOG_WARNING, "Warning: insecure file permissions for RSA private key file `%s'!", fname);
 +              logger(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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)) {
 +              logger(DEBUG_STATUS, 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});
  }
  
  /*
@@@ -288,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;
        xasprintf(&dname, "%s/hosts", confbase);
        dir = opendir(dname);
        if(!dir) {
 -              logger(LOG_ERR, "Could not open %s: %s", dname, strerror(errno));
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", dname, strerror(errno));
                free(dname);
                return;
        }
@@@ -362,16 -291,15 +362,16 @@@ static bool setup_myself(void) 
        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!");
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Name for tinc daemon required!");
                return false;
        }
  
        if(!check_id(name)) {
 -              logger(LOG_ERR, "Invalid name for myself!");
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Invalid name for myself!");
                free(name);
                return false;
        }
        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;
  
                else if(!strcasecmp(mode, "hub"))
                        routing_mode = RMODE_HUB;
                else {
 -                      logger(LOG_ERR, "Invalid routing mode!");
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "Invalid routing mode!");
                        return false;
                }
                free(mode);
                else if(!strcasecmp(mode, "kernel"))
                        forwarding_mode = FMODE_KERNEL;
                else {
 -                      logger(LOG_ERR, "Invalid forwarding mode!");
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "Invalid forwarding mode!");
                        return false;
                }
                free(mode);
  
  #if !defined(SOL_IP) || !defined(IP_TOS)
        if(priorityinheritance)
 -              logger(LOG_WARNING, "%s not supported on this platform", "PriorityInheritance");
 +              logger(DEBUG_ALWAYS, LOG_WARNING, "%s not supported on this platform", "PriorityInheritance");
  #endif
  
        if(!get_config_int(lookup_config(config_tree, "MACExpire"), &macexpire))
  
        if(get_config_int(lookup_config(config_tree, "MaxTimeout"), &maxtimeout)) {
                if(maxtimeout <= 0) {
 -                      logger(LOG_ERR, "Bogus maximum timeout!");
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "Bogus maximum timeout!");
                        return false;
                }
        } else
  
        if(get_config_int(lookup_config(config_tree, "UDPRcvBuf"), &udp_rcvbuf)) {
                if(udp_rcvbuf <= 0) {
 -                      logger(LOG_ERR, "UDPRcvBuf cannot be negative!");
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "UDPRcvBuf cannot be negative!");
                        return false;
                }
        }
  
        if(get_config_int(lookup_config(config_tree, "UDPSndBuf"), &udp_sndbuf)) {
                if(udp_sndbuf <= 0) {
 -                      logger(LOG_ERR, "UDPSndBuf cannot be negative!");
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "UDPSndBuf cannot be negative!");
                        return false;
                }
        }
  
        if(get_config_int(lookup_config(config_tree, "ReplayWindow"), &replaywin_int)) {
                if(replaywin_int < 0) {
 -                      logger(LOG_ERR, "ReplayWindow cannot be negative!");
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "ReplayWindow cannot be negative!");
                        return false;
                }
                replaywin = (unsigned)replaywin_int;
                else if(!strcasecmp(afname, "any"))
                        addressfamily = AF_UNSPEC;
                else {
 -                      logger(LOG_ERR, "Invalid address family!");
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "Invalid address family!");
                        return false;
                }
                free(afname);
  
        /* 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, LOG_ERR, "Unrecognized digest type!");
 +              return false;
 +      }
  
        /* Compression */
  
        if(get_config_int(lookup_config(config_tree, "Compression"), &myself->incompression)) {
                if(myself->incompression < 0 || myself->incompression > 11) {
 -                      logger(LOG_ERR, "Bogus compression level!");
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "Bogus compression level!");
                        return false;
                }
        } else
                        devops = dummy_devops;
                else if(!strcasecmp(type, "raw_socket"))
                        devops = raw_socket_devops;
+               else if(!strcasecmp(type, "multicast"))
+                       devops = multicast_devops;
  #ifdef ENABLE_UML
                else if(!strcasecmp(type, "uml"))
                        devops = uml_devops;
        if(!devops.setup())
                return false;
  
 +      if(device_fd >= 0) {
 +              event_set(&device_ev, device_fd, EV_READ|EV_PERSIST, handle_device_data, NULL);
 +
 +              if (event_add(&device_ev, NULL) < 0) {
 +                      logger(DEBUG_ALWAYS, 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 */
                if(cfg)
                        cfg = lookup_config_next(config_tree, cfg);
  
+               char *port = myport;
+               if(address) {
+                       char *space = strchr(address, ' ');
+                       if(space) {
+                               *space++ = 0;
+                               port = space;
+                       }
+                       if(!strcmp(address, "*"))
+                               *address = 0;
+               }
                hint.ai_family = addressfamily;
                hint.ai_socktype = SOCK_STREAM;
                hint.ai_protocol = IPPROTO_TCP;
                hint.ai_flags = AI_PASSIVE;
  
-               err = getaddrinfo(address, myport, &hint, &ai);
+               err = getaddrinfo(address && *address ? address : NULL, port, &hint, &ai);
                free(address);
  
                if(err || !ai) {
 -                      logger(LOG_ERR, "System call `%s' failed: %s", "getaddrinfo",
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "getaddrinfo",
                                   gai_strerror(err));
                        return false;
                }
  
                for(aip = ai; aip; aip = aip->ai_next) {
                        if(listen_sockets >= MAXSOCKETS) {
 -                              logger(LOG_ERR, "Too many listening sockets");
 +                              logger(DEBUG_ALWAYS, LOG_ERR, "Too many listening sockets");
                                return false;
                        }
  
                        listen_socket[listen_sockets].udp =
                                setup_vpn_in_socket((sockaddr_t *) aip->ai_addr);
  
 -                      if(listen_socket[listen_sockets].udp < 0)
 +                      if(listen_socket[listen_sockets].udp < 0) {
 +                              close(listen_socket[listen_sockets].tcp);
                                continue;
 +                      }
  
 -                      ifdebug(CONNECTIONS) {
 +                      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(DEBUG_ALWAYS, LOG_ERR, "event_add failed: %s", strerror(errno));
 +                              abort();
 +                      }
 +
 +                      event_set(&listen_socket[listen_sockets].ev_udp,
 +                                        listen_socket[listen_sockets].udp,
 +                                        EV_READ|EV_PERSIST,
 +                                        handle_incoming_vpn_data, (void *)(intptr_t)listen_sockets);
 +                      if(event_add(&listen_socket[listen_sockets].ev_udp, NULL) < 0) {
 +                              logger(DEBUG_ALWAYS, LOG_ERR, "event_add failed: %s", strerror(errno));
 +                              abort();
 +                      }
 +
 +                      if(debug_level >= DEBUG_CONNECTIONS) {
                                hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr);
 -                              logger(LOG_NOTICE, "Listening on %s", hostname);
 +                              logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Listening on %s", hostname);
                                free(hostname);
                        }
  
        } while(cfg);
  
        if(listen_sockets)
 -              logger(LOG_NOTICE, "Ready");
 +              logger(DEBUG_ALWAYS, LOG_NOTICE, "Ready");
        else {
 -              logger(LOG_ERR, "Unable to create any listening socket!");
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Unable to create any listening socket!");
                return false;
        }
  
    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);
  
diff --combined src/node.h
index e86aa6a012e58523c4b9c74b7f71c5024d9d602d,f9ef3c15b81d442fce54a5f22406cc630cd2ca3b..62ed3acd575fe66d92c8a46b8f2738286c7e882e
@@@ -1,6 -1,6 +1,6 @@@
  /*
      node.h -- header for node.c
-     Copyright (C) 2001-2010 Guus Sliepen <guus@tinc-vpn.org>,
+     Copyright (C) 2001-2012 Guus Sliepen <guus@tinc-vpn.org>,
                    2001-2005 Ivo Timmermans
  
      This program is free software; you can redistribute it and/or modify
  #ifndef __TINC_NODE_H__
  #define __TINC_NODE_H__
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
 +#include "cipher.h"
  #include "connection.h"
 -#include "event.h"
 +#include "digest.h"
 +#include "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 {
        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 edge_t *prevedge;                /* nearest node from him to us */
        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);
@@@ -102,8 -100,7 +102,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/protocol.c
index 8b62916b523505cebb6c4f10a06c0eef11bbfc8f,1d91d088ff1cc932bda325ff7f30fcf6be5f0847..52ea69029b18286a96a19473018b0bee780dfaec
@@@ -1,7 -1,7 +1,7 @@@
  /*
      protocol.c -- handle the meta-protocol, basic functions
      Copyright (C) 1999-2005 Ivo Timmermans,
-                   2000-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-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
  
  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) {
 -              logger(LOG_ERR, "Output buffer overflow while sending request to %s (%s)",
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Output buffer overflow while sending request to %s (%s)",
                           c->name, c->hostname);
                return false;
        }
  
 -      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);
 -              else
 -                      logger(LOG_DEBUG, "Sending %s to %s (%s)", request_name[request],
 -                                 c->name, c->hostname);
 -      }
 +      logger(DEBUG_META, LOG_DEBUG, "Sending %s to %s (%s): %s", request_name[atoi(request)], c->name, c->hostname, request);
  
 -      buffer[len++] = '\n';
 +      request[len++] = '\n';
  
        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;
 -
 -      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);
 -              else
 -                      logger(LOG_DEBUG, "Forwarding %s from %s (%s)",
 -                                 request_name[request], from->name, from->hostname);
 -      }
 -
 -      from->buffer[from->reqlen - 1] = '\n';
 +void forward_request(connection_t *from, char *request) {
 +      /* Note: request is not zero terminated anymore after a call to this function! */
 +      logger(DEBUG_META, LOG_DEBUG, "Forwarding %s from %s (%s): %s", request_name[atoi(request)], from->name, from->hostname, request);
  
 -      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;
 -
 -      if(sscanf(c->buffer, "%d", &request) == 1) {
 -              if((request < 0) || (request >= LAST) || !request_handlers[request]) {
 -                      ifdebug(META)
 -                              logger(LOG_DEBUG, "Unknown request from %s (%s): %s",
 -                                         c->name, c->hostname, c->buffer);
 -                      else
 -                              logger(LOG_ERR, "Unknown request from %s (%s)",
 -                                         c->name, c->hostname);
 +bool receive_request(connection_t *c, char *request) {
 +      int reqno = atoi(request);
  
 +      if(reqno || *request == '0') {
 +              if((reqno < 0) || (reqno >= LAST) || !request_handlers[reqno]) {
 +                      logger(DEBUG_META, LOG_DEBUG, "Unknown request from %s (%s): %s", c->name, c->hostname, request);
                        return false;
                } else {
 -                      ifdebug(PROTOCOL) {
 -                              ifdebug(META)
 -                                      logger(LOG_DEBUG, "Got %s from %s (%s): %s",
 -                                                 request_name[request], c->name, c->hostname,
 -                                                 c->buffer);
 -                              else
 -                                      logger(LOG_DEBUG, "Got %s from %s (%s)",
 -                                                 request_name[request], c->name, c->hostname);
 -                      }
 +                      logger(DEBUG_META, LOG_DEBUG, "Got %s from %s (%s): %s", request_name[reqno], c->name, c->hostname, request);
                }
  
 -              if((c->allow_request != ALL) && (c->allow_request != request)) {
 -                      logger(LOG_ERR, "Unauthorized request from %s (%s)", c->name,
 -                                 c->hostname);
 +              if((c->allow_request != ALL) && (c->allow_request != reqno)) {
 +                      logger(DEBUG_ALWAYS, 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);
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "Error while processing %s from %s (%s)", request_name[reqno], c->name, c->hostname);
                        return false;
                }
        } else {
 -              logger(LOG_ERR, "Bogus data received from %s (%s)",
 -                         c->name, c->hostname);
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Bogus data received from %s (%s)", c->name, c->hostname);
                return false;
        }
  
@@@ -146,59 -180,47 +146,59 @@@ 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)) {
 -              ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Already seen request");
 +      if(splay_search(past_request_tree, &p)) {
 +              logger(DEBUG_SCARY_THINGS, 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",
 +              logger(DEBUG_SCARY_THINGS, 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
index 3bf18b21cb2dbf4747b038b4ef52106fa1eedd22,698806886ba363cfdd51984f5b2e5d44f7cc2642..406ec4f9baffdf7593b052a1b5a2b598e036b987
@@@ -1,7 -1,7 +1,7 @@@
  /*
      protocol_auth.c -- handle the meta-protocol, authentication
      Copyright (C) 1999-2005 Ivo Timmermans,
-                   2000-2010 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-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"
  
 -#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 "meta.h"
  #include "net.h"
  #include "netutl.h"
  #include "node.h"
 +#include "prf.h"
  #include "protocol.h"
 +#include "rsa.h"
 +#include "sptps.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) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s)", "ID", c->name,
 +      if(sscanf(request, "%*d " MAX_STRING " %d.%d", name, &c->protocol_major, &c->protocol_minor) < 2) {
 +              logger(DEBUG_ALWAYS, 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)) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s): %s", "ID", c->name,
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ID", c->name,
                           c->hostname, "invalid name");
                return false;
        }
@@@ -87,7 -64,7 +87,7 @@@
  
        if(c->outgoing) {
                if(strcmp(c->name, name)) {
 -                      logger(LOG_ERR, "Peer %s is %s instead of %s", c->hostname, name,
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s is %s instead of %s", c->hostname, name,
                                   c->name);
                        return false;
                }
@@@ -99,9 -76,9 +99,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(DEBUG_ALWAYS, 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);
  
                if(!read_connection_config(c)) {
 -                      logger(LOG_ERR, "Peer %s had unknown identity (%s)", c->hostname,
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s had unknown identity (%s)", c->hostname,
                                   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) {
 +              c->allow_request = ACK;
 +              char label[25 + strlen(myself->name) + strlen(c->name)];
  
 -bool send_metakey(connection_t *c) {
 -      bool x;
 +              if(c->outgoing)
 +                      snprintf(label, sizeof label, "tinc TCP key expansion %s %s", myself->name, c->name);
 +              else
 +                      snprintf(label, sizeof label, "tinc TCP key expansion %s %s", c->name, myself->name);
  
 -      int len = RSA_size(c->rsa_key);
 +              return sptps_start(&c->sptps, c, c->outgoing, false, myself->connection->ecdsa, c->ecdsa, label, sizeof label, send_meta_sptps, receive_meta_sptps);
 +      } else {
 +              return send_metakey(c);
 +      }
 +}
  
 -      /* Allocate buffers for the meta key */
 +bool send_metakey(connection_t *c) {
 +      if(!read_rsa_public_key(c))
 +              return false;
  
 -      char buffer[2 * len + 1];
 +      if(!cipher_open_blowfish_ofb(&c->outcipher))
 +              return false;
        
 -      c->outkey = xrealloc(c->outkey, len);
 +      if(!digest_open_sha1(&c->outdigest, -1))
 +              return false;
  
 -      if(!c->outctx)
 -              c->outctx = xmalloc_and_zero(sizeof(*c->outctx));
 +      size_t len = rsa_size(&c->rsa);
 +      char key[len];
 +      char enckey[len];
 +      char hexkey[2 * len + 1];
  
 -      /* Copy random data to the buffer */
 +      /* Create a random key */
  
 -      RAND_pseudo_bytes((unsigned char *)c->outkey, len);
 +      randomize(key, len);
  
        /* The message we send must be smaller than the modulus of the RSA key.
           By definition, for a key of k bits, the following formula holds:
           This can be done by setting the most significant bit to zero.
         */
  
 -      c->outkey[0] &= 0x7F;
 +      key[0] &= 0x7F;
 +
 +      cipher_set_key_from_rsa(&c->outcipher, key, len, true);
  
 -      ifdebug(SCARY_THINGS) {
 -              bin2hex(c->outkey, buffer, len);
 -              buffer[len * 2] = '\0';
 -              logger(LOG_DEBUG, "Generated random meta key (unencrypted): %s",
 -                         buffer);
 +      if(debug_level >= DEBUG_SCARY_THINGS) {
 +              bin2hex(key, hexkey, len);
 +              logger(DEBUG_SCARY_THINGS, 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(DEBUG_ALWAYS, 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);
 -
 -      /* Further outgoing requests are encrypted with the key we just generated */
 -
 -      if(c->outcipher) {
 -              if(!EVP_EncryptInit(c->outctx, c->outcipher,
 -                                      (unsigned char *)c->outkey + len - c->outcipher->key_len,
 -                                      (unsigned char *)c->outkey + len - c->outcipher->key_len -
 -                                      c->outcipher->iv_len)) {
 -                      logger(LOG_ERR, "Error during initialisation of cipher for %s (%s): %s",
 -                                      c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL));
 -                      return false;
 -              }
 -
 -              c->status.encryptout = true;
 -      }
 -
 -      return x;
 +      bool result = send_request(c, "%d %d %d %d %d %s", METAKEY,
 +                       cipher_get_nid(&c->outcipher),
 +                       digest_get_nid(&c->outdigest), c->outmaclength,
 +                       c->outcompression, hexkey);
 +      
 +      c->status.encryptout = true;
 +      return result;
  }
  
 -bool metakey_h(connection_t *c) {
 -      char buffer[MAX_STRING_SIZE];
 +bool metakey_h(connection_t *c, char *request) {
 +      char hexkey[MAX_STRING_SIZE];
        int cipher, digest, maclength, compression;
 -      int len;
 +      size_t len = rsa_size(&myself->connection->rsa);
 +      char enckey[len];
 +      char key[len];
  
 -      if(sscanf(c->buffer, "%*d %d %d %d %d " MAX_STRING, &cipher, &digest, &maclength, &compression, buffer) != 5) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s)", "METAKEY", c->name,
 -                         c->hostname);
 +      if(sscanf(request, "%*d %d %d %d %d " MAX_STRING, &cipher, &digest, &maclength, &compression, hexkey) != 5) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "METAKEY", c->name, c->hostname);
                return false;
        }
  
 -      len = RSA_size(myself->connection->rsa_key);
 +      /* Convert the challenge from hexadecimal back to binary */
 +
 +      int inlen = hex2bin(hexkey, enckey, sizeof enckey);
  
        /* Check if the length of the meta key is all right */
  
 -      if(strlen(buffer) != len * 2) {
 -              logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong keylength");
 +      if(inlen != len) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong keylength");
                return false;
        }
  
 -      /* Allocate buffers for the meta key */
 -
 -      c->inkey = xrealloc(c->inkey, len);
 -
 -      if(!c->inctx)
 -              c->inctx = xmalloc_and_zero(sizeof(*c->inctx));
 -
 -      /* Convert the challenge from hexadecimal back to binary */
 -
 -      hex2bin(buffer, buffer, len);
 -
        /* Decrypt the meta key */
  
 -      if(RSA_private_decrypt(len, (unsigned char *)buffer, (unsigned char *)c->inkey, myself->connection->rsa_key, RSA_NO_PADDING) != len) {  /* See challenge() */
 -              logger(LOG_ERR, "Error during decryption of meta key for %s (%s)",
 -                         c->name, c->hostname);
 +      if(!rsa_private_decrypt(&myself->connection->rsa, enckey, len, key)) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Error during decryption of meta key for %s (%s)", c->name, c->hostname);
                return false;
        }
  
 -      ifdebug(SCARY_THINGS) {
 -              bin2hex(c->inkey, buffer, len);
 -              buffer[len * 2] = '\0';
 -              logger(LOG_DEBUG, "Received random meta key (unencrypted): %s", buffer);
 +      if(debug_level >= DEBUG_SCARY_THINGS) {
 +              bin2hex(key, hexkey, len);
 +              logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Received random meta key (unencrypted): %s", hexkey);
        }
  
 -      /* All incoming requests will now be encrypted. */
 -
        /* Check and lookup cipher and digest algorithms */
  
 -      if(cipher) {
 -              c->incipher = EVP_get_cipherbynid(cipher);
 -              
 -              if(!c->incipher) {
 -                      logger(LOG_ERR, "%s (%s) uses unknown cipher!", c->name, c->hostname);
 -                      return false;
 -              }
 -
 -              if(!EVP_DecryptInit(c->inctx, c->incipher,
 -                                      (unsigned char *)c->inkey + len - c->incipher->key_len,
 -                                      (unsigned char *)c->inkey + len - c->incipher->key_len -
 -                                      c->incipher->iv_len)) {
 -                      logger(LOG_ERR, "Error during initialisation of cipher from %s (%s): %s",
 -                                      c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL));
 -                      return false;
 -              }
 -
 -              c->status.decryptin = true;
 -      } else {
 -              c->incipher = NULL;
 +      if(!cipher_open_by_nid(&c->incipher, cipher) || !cipher_set_key_from_rsa(&c->incipher, key, len, false)) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Error during initialisation of cipher from %s (%s)", c->name, c->hostname);
 +              return false;
        }
  
 -      c->inmaclength = maclength;
 -
 -      if(digest) {
 -              c->indigest = EVP_get_digestbynid(digest);
 -
 -              if(!c->indigest) {
 -                      logger(LOG_ERR, "Node %s (%s) uses unknown digest!", c->name, c->hostname);
 -                      return false;
 -              }
 -
 -              if(c->inmaclength > c->indigest->md_size || c->inmaclength < 0) {
 -                      logger(LOG_ERR, "%s (%s) uses bogus MAC length!", c->name, c->hostname);
 -                      return false;
 -              }
 -      } else {
 -              c->indigest = NULL;
 +      if(!digest_open_by_nid(&c->indigest, digest, -1)) {
 +              logger(DEBUG_ALWAYS, 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);
 -
 -      /* Allocate buffers for the challenge */
 -
 -      char buffer[2 * len + 1];
 +      size_t len = rsa_size(&c->rsa);
 +      char buffer[len * 2 + 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 = 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s)", "CHAL_REPLY", c->name,
 +      if(sscanf(request, "%*d " MAX_STRING, hishash) != 1) {
 +              logger(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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, rsa_size(&c->rsa), hishash)) {
 +              logger(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, LOG_INFO, "Already have ECDSA public key from %s (%s), not upgrading.", c->name, c->hostname);
 +              return false;
 +      }
 +
 +      logger(DEBUG_ALWAYS, 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) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s)", "ACK", c->name,
 +      if(sscanf(request, "%*d " MAX_STRING " %d %x", hisport, &weight, &options) != 3) {
 +              logger(DEBUG_ALWAYS, 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);
 +                      logger(DEBUG_CONNECTIONS, 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(DEBUG_ALWAYS, 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;
        c->status.active = true;
  
 -      ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Connection with %s (%s) activated", c->name,
 +      logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection with %s (%s) activated", c->name,
                           c->hostname);
  
        /* Send him everything we know */
diff --combined src/protocol_edge.c
index bf283dd667949caad12e90f79c7f22ba4e9ae039,3dfff05e1845eb4120febad7df96ba1d08255269..e8b3326b752b29f6077044943850421353692553
@@@ -1,7 -1,7 +1,7 @@@
  /*
      protocol_edge.c -- handle the meta-protocol, edges
      Copyright (C) 1999-2005 Ivo Timmermans,
-                   2000-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2012 Guus Sliepen <guus@tinc-vpn.org>
                    2009      Michael Tokarev <mjt@corpit.ru>
  
      This program is free software; you can redistribute it and/or modify
@@@ -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,9 -61,9 +61,9 @@@
        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,
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ADD_EDGE", c->name,
                           c->hostname);
                return false;
        }
        /* Check if names are valid */
  
        if(!check_id(from_name) || !check_id(to_name)) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_EDGE", c->name,
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_EDGE", c->name,
                           c->hostname, "invalid name");
                return false;
        }
  
 -      if(seen_request(c->buffer))
 +      if(seen_request(request))
                return true;
  
        /* Lookup nodes */
@@@ -88,7 -88,7 +88,7 @@@
           from != myself && from != c->node &&
           to != myself && to != c->node) {
                /* ignore indirect edge registrations for tunnelserver */
 -              ifdebug(PROTOCOL) logger(LOG_WARNING,
 +              logger(DEBUG_PROTOCOL, LOG_WARNING,
                   "Ignoring indirect %s from %s (%s)",
                   "ADD_EDGE", c->name, c->hostname);
                return true;
        if(e) {
                if(e->weight != weight || e->options != options || sockaddrcmp(&e->address, &address)) {
                        if(from == myself) {
 -                              ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for ourself which does not match existing entry",
 +                              logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself which does not match existing entry",
                                                   "ADD_EDGE", c->name, c->hostname);
                                send_add_edge(c, e);
                                return true;
                        } else {
 -                              ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) which does not match existing entry",
 +                              logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) which does not match existing entry",
                                                   "ADD_EDGE", c->name, c->hostname);
                                edge_del(e);
                                graph();
                } else
                        return true;
        } else if(from == myself) {
 -              ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for ourself which does not exist",
 +              logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself which does not exist",
                                   "ADD_EDGE", c->name, c->hostname);
                contradicting_add_edge++;
                e = new_edge();
        /* 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,14 -167,14 +167,14 @@@ 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) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s)", "DEL_EDGE", c->name,
 +      if(sscanf(request, "%*d %*x "MAX_STRING" "MAX_STRING, from_name, to_name) != 2) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "DEL_EDGE", c->name,
                           c->hostname);
                return false;
        }
        /* Check if names are valid */
  
        if(!check_id(from_name) || !check_id(to_name)) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_EDGE", c->name,
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_EDGE", c->name,
                           c->hostname, "invalid name");
                return false;
        }
  
 -      if(seen_request(c->buffer))
 +      if(seen_request(request))
                return true;
  
        /* Lookup nodes */
           from != myself && from != c->node &&
           to != myself && to != c->node) {
                /* ignore indirect edge registrations for tunnelserver */
 -              ifdebug(PROTOCOL) logger(LOG_WARNING,
 +              logger(DEBUG_PROTOCOL, LOG_WARNING,
                   "Ignoring indirect %s from %s (%s)",
                   "DEL_EDGE", c->name, c->hostname);
                return true;
        }
  
        if(!from) {
 -              ifdebug(PROTOCOL) logger(LOG_ERR, "Got %s from %s (%s) which does not appear in the edge tree",
 +              logger(DEBUG_PROTOCOL, LOG_ERR, "Got %s from %s (%s) which does not appear in the edge tree",
                                   "DEL_EDGE", c->name, c->hostname);
                return true;
        }
  
        if(!to) {
 -              ifdebug(PROTOCOL) logger(LOG_ERR, "Got %s from %s (%s) which does not appear in the edge tree",
 +              logger(DEBUG_PROTOCOL, LOG_ERR, "Got %s from %s (%s) which does not appear in the edge tree",
                                   "DEL_EDGE", c->name, c->hostname);
                return true;
        }
        e = lookup_edge(from, to);
  
        if(!e) {
 -              ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) which does not appear in the edge tree",
 +              logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) which does not appear in the edge tree",
                                   "DEL_EDGE", c->name, c->hostname);
                return true;
        }
  
        if(e->from == myself) {
 -              ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for ourself",
 +              logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself",
                                   "DEL_EDGE", c->name, c->hostname);
                contradicting_del_edge++;
                send_add_edge(c, e);    /* Send back a correction */
        /* Tell the rest about the deleted edge */
  
        if(!tunnelserver)
 -              forward_request(c);
 +              forward_request(c, request);
  
        /* Delete the edge */
  
diff --combined src/protocol_key.c
index c62c04814412774d60a8182ff03dda5b4fd3e064,1a184fb8f3ba817dccb4553a051361a4bf4b61a9..7e645d487e8dc85a271f464701a492191acb28a4
@@@ -1,7 -1,7 +1,7 @@@
  /*
      protocol_key.c -- handle the meta-protocol, key exchange
      Copyright (C) 1999-2005 Ivo Timmermans,
-                   2000-2011 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-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"
  
 -#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"
@@@ -37,7 -37,7 +37,7 @@@
  static bool mykeyused = false;
  
  void send_key_changed(void) {
 -      avl_node_t *node;
 +      splay_node_t *node;
        connection_t *c;
  
        send_request(everyone, "%d %x %s", KEY_CHANGED, rand(), myself->name);
        }
  }
  
 -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) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s)", "KEY_CHANGED",
 +      if(sscanf(request, "%*d %*x " MAX_STRING, name) != 1) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "KEY_CHANGED",
                           c->name, c->hostname);
                return false;
        }
  
 -      if(!check_id(name)) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s): %s", "KEY_CHANGED", c->name, c->hostname, "invalid name");
 -              return false;
 -      }
 -
 -      if(seen_request(c->buffer))
 +      if(seen_request(request))
                return true;
  
        n = lookup_node(name);
  
        if(!n) {
 -              logger(LOG_ERR, "Got %s from %s (%s) origin %s which does not exist",
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) origin %s which does not exist",
                           "KEY_CHANGED", c->name, c->hostname, name);
                return true;
        }
        /* 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) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s)", "REQ_KEY", c->name,
 +      if(sscanf(request, "%*d " MAX_STRING " " MAX_STRING " %d", from_name, to_name, &kx_version) < 2) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "REQ_KEY", c->name,
                           c->hostname);
                return false;
        }
  
        if(!check_id(from_name) || !check_id(to_name)) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_KEY", c->name, c->hostname, "invalid name");
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_KEY", c->name, c->hostname, "invalid name");
                return false;
        }
  
        from = lookup_node(from_name);
  
        if(!from) {
 -              logger(LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list",
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list",
                           "REQ_KEY", c->name, c->hostname, from_name);
                return true;
        }
        to = lookup_node(to_name);
  
        if(!to) {
 -              logger(LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list",
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list",
                           "REQ_KEY", c->name, c->hostname, to_name);
                return true;
        }
        /* Check if this key request is for us */
  
        if(to == myself) {                      /* Yes, send our own key back */
 +              if(experimental && kx_version >= 1) {
 +                      logger(DEBUG_ALWAYS, LOG_DEBUG, "Got ECDH key request from %s", from->name);
 +                      from->status.ecdh = true;
 +              }
                send_ans_key(from);
        } else {
                if(tunnelserver)
                        return true;
  
                if(!to->status.reachable) {
 -                      logger(LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable",
 +                      logger(DEBUG_ALWAYS, LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable",
                                "REQ_KEY", c->name, c->hostname, to_name);
                        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,
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ANS_KEY", c->name,
                           c->hostname);
                return false;
        }
  
        if(!check_id(from_name) || !check_id(to_name)) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s): %s", "ANS_KEY", c->name, c->hostname, "invalid name");
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ANS_KEY", c->name, c->hostname, "invalid name");
                return false;
        }
  
        from = lookup_node(from_name);
  
        if(!from) {
 -              logger(LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list",
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list",
                           "ANS_KEY", c->name, c->hostname, from_name);
                return true;
        }
        to = lookup_node(to_name);
  
        if(!to) {
 -              logger(LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list",
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list",
                           "ANS_KEY", c->name, c->hostname, to_name);
                return true;
        }
                        return true;
  
                if(!to->status.reachable) {
 -                      logger(LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable",
 -                              "ANS_KEY", c->name, c->hostname, to_name);
 +                      logger(DEBUG_ALWAYS, LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable",
 +                                 "ANS_KEY", c->name, c->hostname, to_name);
                        return true;
                }
  
                if(!*address && from->address.sa.sa_family != AF_UNSPEC) {
                        char *address, *port;
 -                      ifdebug(PROTOCOL) logger(LOG_DEBUG, "Appending reflexive UDP address to ANS_KEY from %s to %s", from->name, to->name);
 +                      logger(DEBUG_PROTOCOL, 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(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses unknown cipher!", from->name, from->hostname);
 +              return false;
 +      }
 +
 +      if(!digest_open_by_nid(&from->outdigest, digest, maclength)) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses unknown digest!", from->name, from->hostname);
 +              return false;
 +      }
 +
 +      if(maclength != digest_length(&from->outdigest)) {
 +              logger(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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;
 +
 +              cipher_set_key(&from->incipher, mykey, false);
 +              digest_set_key(&from->indigest, mykey + mykeylen, mykeylen);
  
 -      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->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(DEBUG_ALWAYS, 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;
  
        if(*address && *port) {
 -              ifdebug(PROTOCOL) logger(LOG_DEBUG, "Using reflexive UDP address from %s: %s port %s", from->name, address, port);
 +              logger(DEBUG_PROTOCOL, LOG_DEBUG, "Using reflexive UDP address from %s: %s port %s", from->name, address, port);
                sockaddr_t sa = str2sockaddr(address, port);
                update_node_udp(from, &sa);
        }
  
 -      if(from->options & OPTION_PMTU_DISCOVERY && !from->mtuevent)
 +      if(from->options & OPTION_PMTU_DISCOVERY)
                send_mtu_probe(from);
  
        return true;
diff --combined src/protocol_misc.c
index 37bc110aaba0cb69ece85cc316acd2afd0ee2de4,05afbce2894983bd27444e6d6e3567bafb235ff9..0f95b0beeb4d0ba60df147ac8390b745e68af3f7
@@@ -1,7 -1,7 +1,7 @@@
  /*
      protocol_misc.c -- handle the meta-protocol, miscellaneous functions
      Copyright (C) 1999-2005 Ivo Timmermans,
-                   2000-2009 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-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
@@@ -40,17 -40,17 +40,17 @@@ bool send_status(connection_t *c, int s
        return send_request(c, "%d %d %s", STATUS, statusno, statusstring);
  }
  
 -bool status_h(connection_t *c) {
 +bool status_h(connection_t *c, char *request) {
        int statusno;
        char statusstring[MAX_STRING_SIZE];
  
 -      if(sscanf(c->buffer, "%*d %d " MAX_STRING, &statusno, statusstring) != 2) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s)", "STATUS",
 +      if(sscanf(request, "%*d %d " MAX_STRING, &statusno, statusstring) != 2) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "STATUS",
                           c->name, c->hostname);
                return false;
        }
  
 -      ifdebug(STATUS) logger(LOG_NOTICE, "Status message from %s (%s): %d: %s",
 +      logger(DEBUG_STATUS, LOG_NOTICE, "Status message from %s (%s): %d: %s",
                           c->name, c->hostname, statusno, statusstring);
  
        return true;
@@@ -63,38 -63,42 +63,38 @@@ bool send_error(connection_t *c, int er
        return send_request(c, "%d %d %s", ERROR, err, errstring);
  }
  
 -bool error_h(connection_t *c) {
 +bool error_h(connection_t *c, char *request) {
        int err;
        char errorstring[MAX_STRING_SIZE];
  
 -      if(sscanf(c->buffer, "%*d %d " MAX_STRING, &err, errorstring) != 2) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s)", "ERROR",
 +      if(sscanf(request, "%*d %d " MAX_STRING, &err, errorstring) != 2) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ERROR",
                           c->name, c->hostname);
                return false;
        }
  
 -      ifdebug(ERROR) logger(LOG_NOTICE, "Error message from %s (%s): %d: %s",
 +      logger(DEBUG_ERROR, LOG_NOTICE, "Error message from %s (%s): %d: %s",
                           c->name, c->hostname, err, errorstring);
  
 -      terminate_connection(c, c->status.active);
 -
 -      return true;
 +      return false;
  }
  
  bool send_termreq(connection_t *c) {
        return send_request(c, "%d", TERMREQ);
  }
  
 -bool termreq_h(connection_t *c) {
 -      terminate_connection(c, c->status.active);
 -
 -      return true;
 +bool termreq_h(connection_t *c, char *request) {
 +      return false;
  }
  
  bool send_ping(connection_t *c) {
        c->status.pinged = true;
 -      c->last_ping_time = now;
 +      c->last_ping_time = time(NULL);
  
        return send_request(c, "%d", PING);
  }
  
 -bool ping_h(connection_t *c) {
 +bool ping_h(connection_t *c, char *request) {
        return send_pong(c);
  }
  
@@@ -102,13 -106,19 +102,19 @@@ bool send_pong(connection_t *c) 
        return send_request(c, "%d", PONG);
  }
  
 -bool pong_h(connection_t *c) {
 +bool pong_h(connection_t *c, char *request) {
        c->status.pinged = false;
  
        /* Succesful connection, reset timeout if this is an outgoing connection. */
  
-       if(c->outgoing)
+       if(c->outgoing) {
                c->outgoing->timeout = 0;
+               c->outgoing->cfg = NULL;
+               if(c->outgoing->ai)
+                       freeaddrinfo(c->outgoing->ai);
+               c->outgoing->ai = NULL;
+               c->outgoing->aip = NULL;
+       }
  
        return true;
  }
@@@ -119,20 -129,20 +125,20 @@@ bool send_tcppacket(connection_t *c, co
        /* If there already is a lot of data in the outbuf buffer, discard this packet.
             We use a very simple Random Early Drop algorithm. */
  
 -      if(2.0 * c->outbuflen / (float)maxoutbufsize - 1 > (float)rand()/(float)RAND_MAX)
 +      if(2.0 * c->outbuf.len / (float)maxoutbufsize - 1 > (float)rand()/(float)RAND_MAX)
                return true;
  
        if(!send_request(c, "%d %hd", PACKET, packet->len))
                return false;
  
 -      return send_meta(c, (char *)packet->data, packet->len) && flush_meta(c);
 +      return send_meta(c, (char *)packet->data, packet->len);
  }
  
 -bool tcppacket_h(connection_t *c) {
 +bool tcppacket_h(connection_t *c, char *request) {
        short int len;
  
 -      if(sscanf(c->buffer, "%*d %hd", &len) != 1) {
 -              logger(LOG_ERR, "Got bad %s from %s (%s)", "PACKET", c->name,
 +      if(sscanf(request, "%*d %hd", &len) != 1) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "PACKET", c->name,
                           c->hostname);
                return false;
        }
diff --combined src/route.c
index c76046689352dbdf72700844ff77dad5bc22d34c,6eadb888162980633a2813e0da199092c61987ff..5bf6e926724a664618b9fe0707125dc3318ddd87
@@@ -1,7 -1,7 +1,7 @@@
  /*
      route.c -- routing
      Copyright (C) 2000-2005 Ivo Timmermans,
-                   2000-2010 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-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"
  
 -#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 decrement_ttl = false;
  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 */
  
@@@ -60,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) {
@@@ -85,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)
  
  static bool checklength(node_t *source, vpn_packet_t *packet, length_t length) {
        if(packet->len < length) {
 -              ifdebug(TRAFFIC) logger(LOG_WARNING, "Got too short packet from %s (%s)", source->name, source->hostname);
 +              logger(DEBUG_TRAFFIC, LOG_WARNING, "Got too short packet from %s (%s)", source->name, source->hostname);
                return false;
        } else
                return true;
@@@ -164,7 -158,7 +164,7 @@@ static void clamp_mss(const node_t *sou
                if(oldmss <= newmss)
                        break;
                
 -              ifdebug(TRAFFIC) logger(LOG_INFO, "Clamping MSS of packet from %s to %s to %d", source->name, via->name, newmss);
 +              logger(DEBUG_TRAFFIC, LOG_INFO, "Clamping MSS of packet from %s to %s to %d", source->name, via->name, newmss);
  
                /* Update the MSS value and the checksum */
                packet->data[start + 22 + i] = newmss >> 8;
@@@ -186,43 -180,9 +186,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) {
 +                      if(debug_level >= DEBUG_TRAFFIC) {
 +                              char netstr[MAXNETSTR];
 +                              if(net2str(netstr, sizeof netstr, s))
 +                                      logger(DEBUG_TRAFFIC, 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);
        /* If we don't know this MAC address yet, store it */
  
        if(!subnet) {
 -              ifdebug(TRAFFIC) logger(LOG_INFO, "Learned new MAC address %hx:%hx:%hx:%hx:%hx:%hx",
 +              logger(DEBUG_TRAFFIC, LOG_INFO, "Learned new MAC address %hx:%hx:%hx:%hx:%hx:%hx",
                                   address->x[0], address->x[1], address->x[2], address->x[3],
                                   address->x[4], address->x[5]);
  
                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;
        }
  }
  
@@@ -350,11 -333,11 +350,11 @@@ 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);
 +              logger(DEBUG_TRAFFIC, LOG_WARNING, "Length of packet (%d) doesn't match length in IPv4 header (%d)", packet->len, (int)(ether_size + ip_size + todo));
                return;
        }
  
 -      ifdebug(TRAFFIC) logger(LOG_INFO, "Fragmenting packet of %d bytes to %s (%s)", packet->len, dest->name, dest->hostname);
 +      logger(DEBUG_TRAFFIC, LOG_INFO, "Fragmenting packet of %d bytes to %s (%s)", packet->len, dest->name, dest->hostname);
  
        offset = packet->data + ether_size + ip_size;
        maxlen = (dest->mtu - ether_size - ip_size) & ~0x7;
@@@ -391,7 -374,7 +391,7 @@@ static void route_ipv4_unicast(node_t *
        subnet = lookup_subnet_ipv4(&dest);
  
        if(!subnet) {
 -              ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet from %s (%s): unknown IPv4 destination address %d.%d.%d.%d",
 +              logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet from %s (%s): unknown IPv4 destination address %d.%d.%d.%d",
                                source->name, source->hostname,
                                dest.x[0],
                                dest.x[1],
        }
        
        if(subnet->owner == source) {
 -              ifdebug(TRAFFIC) logger(LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname);
 +              logger(DEBUG_TRAFFIC, LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname);
                return;
        }
  
        via = (subnet->owner->via == myself) ? subnet->owner->nexthop : subnet->owner->via;
  
        if(via == source) {
 -              ifdebug(TRAFFIC) logger(LOG_ERR, "Routing loop for packet from %s (%s)!", source->name, source->hostname);
 +              logger(DEBUG_TRAFFIC, LOG_ERR, "Routing loop for packet from %s (%s)!", source->name, source->hostname);
                return;
        }
        
                return route_ipv4_unreachable(source, packet, ICMP_DEST_UNREACH, ICMP_NET_ANO);
  
        if(via && packet->len > MAX(via->mtu, 590) && via != myself) {
 -              ifdebug(TRAFFIC) logger(LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu);
 +              logger(DEBUG_TRAFFIC, LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu);
                if(packet->data[20] & 0x40) {
                        packet->len = MAX(via->mtu, 590);
                        route_ipv4_unreachable(source, packet, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED);
@@@ -521,7 -504,7 +521,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);
  
@@@ -546,7 -529,7 +546,7 @@@ static void route_ipv6_unicast(node_t *
        subnet = lookup_subnet_ipv6(&dest);
  
        if(!subnet) {
 -              ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet from %s (%s): unknown IPv6 destination address %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx",
 +              logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet from %s (%s): unknown IPv6 destination address %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx",
                                source->name, source->hostname,
                                ntohs(dest.x[0]),
                                ntohs(dest.x[1]),
        }
  
        if(subnet->owner == source) {
 -              ifdebug(TRAFFIC) logger(LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname);
 +              logger(DEBUG_TRAFFIC, LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname);
                return;
        }
  
        via = (subnet->owner->via == myself) ? subnet->owner->nexthop : subnet->owner->via;
        
        if(via == source) {
 -              ifdebug(TRAFFIC) logger(LOG_ERR, "Routing loop for packet from %s (%s)!", source->name, source->hostname);
 +              logger(DEBUG_TRAFFIC, LOG_ERR, "Routing loop for packet from %s (%s)!", source->name, source->hostname);
                return;
        }
        
                return route_ipv6_unreachable(source, packet, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN);
  
        if(via && packet->len > MAX(via->mtu, 1294) && via != myself) {
 -              ifdebug(TRAFFIC) logger(LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu);
 +              logger(DEBUG_TRAFFIC, LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu);
                packet->len = MAX(via->mtu, 1294);
                route_ipv6_unreachable(source, packet, ICMP6_PACKET_TOO_BIG, 0);
                return;
@@@ -617,7 -600,7 +617,7 @@@ static void route_neighborsol(node_t *s
        has_opt = packet->len >= ether_size + ip6_size + ns_size + opt_size + ETH_ALEN;
        
        if(source != myself) {
 -              ifdebug(TRAFFIC) logger(LOG_WARNING, "Got neighbor solicitation request from %s (%s) while in router mode!", source->name, source->hostname);
 +              logger(DEBUG_TRAFFIC, LOG_WARNING, "Got neighbor solicitation request from %s (%s) while in router mode!", source->name, source->hostname);
                return;
        }
  
  
        if(ns.nd_ns_hdr.icmp6_type != ND_NEIGHBOR_SOLICIT ||
           (has_opt && opt.nd_opt_type != ND_OPT_SOURCE_LINKADDR)) {
 -              ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: received unknown type neighbor solicitation request");
 +              logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: received unknown type neighbor solicitation request");
                return;
        }
  
  
        /* 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);
        }
  
        if(checksum) {
 -              ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: checksum error for neighbor solicitation request");
 +              logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: checksum error for neighbor solicitation request");
                return;
        }
  
        subnet = lookup_subnet_ipv6((ipv6_t *) &ns.nd_ns_target);
  
        if(!subnet) {
 -              ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: neighbor solicitation request for unknown address %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx",
 +              logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: neighbor solicitation request for unknown address %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx",
                                   ntohs(((uint16_t *) &ns.nd_ns_target)[0]),
                                   ntohs(((uint16_t *) &ns.nd_ns_target)[1]),
                                   ntohs(((uint16_t *) &ns.nd_ns_target)[2]),
  
        /* 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);
@@@ -761,7 -744,7 +761,7 @@@ static void route_arp(node_t *source, v
                return;
  
        if(source != myself) {
 -              ifdebug(TRAFFIC) logger(LOG_WARNING, "Got ARP request from %s (%s) while in router mode!", source->name, source->hostname);
 +              logger(DEBUG_TRAFFIC, LOG_WARNING, "Got ARP request from %s (%s) while in router mode!", source->name, source->hostname);
                return;
        }
  
        /* 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) {
 -              ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: received unknown type ARP request");
 +         arp.arp_hln != ETH_ALEN || arp.arp_pln != sizeof addr || ntohs(arp.arp_op) != ARPOP_REQUEST) {
 +              logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: received unknown type ARP request");
                return;
        }
  
        subnet = lookup_subnet_ipv4((ipv4_t *) &arp.arp_tpa);
  
        if(!subnet) {
 -              ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: ARP request for unknown address %d.%d.%d.%d",
 +              logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: ARP request for unknown address %d.%d.%d.%d",
                                   arp.arp_tpa[0], arp.arp_tpa[1], arp.arp_tpa[2],
                                   arp.arp_tpa[3]);
                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 */
@@@ -840,7 -823,7 +840,7 @@@ static void route_mac(node_t *source, v
        }
  
        if(subnet->owner == source) {
 -              ifdebug(TRAFFIC) logger(LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname);
 +              logger(DEBUG_TRAFFIC, LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname);
                return;
        }
  
                return;
        
        if(via && packet->len > via->mtu && via != myself) {
 -              ifdebug(TRAFFIC) logger(LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu);
 +              logger(DEBUG_TRAFFIC, LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu);
                uint16_t type = packet->data[12] << 8 | packet->data[13];
                if(type == ETH_P_IP && packet->len > 590) {
                        if(packet->data[20] & 0x40) {
        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;
 +
 +              pcap = true;
 +              int len = packet->len;
 +              if(c->outmaclength && c->outmaclength < len)
 +                      len = c->outmaclength;
 +
 +              if(send_request(c, "%d %d %d", CONTROL, REQ_PCAP, len))
 +                      send_meta(c, (char *)packet->data, len);
 +      }
 +}
 +
  static bool do_decrement_ttl(node_t *source, vpn_packet_t *packet) {
        uint16_t type = packet->data[12] << 8 | packet->data[13];
  
  }
  
  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;
                                                break;
  
                                        default:
 -                                              ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet from %s (%s): unknown type %hx", source->name, source->hostname, type);
 +                                              logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet from %s (%s): unknown type %hx", source->name, source->hostname, type);
                                                break;
                                }
                        }
diff --combined src/route.h
index 44023fdb3efff73dfd9dd474fa41a9c3c8527cd4,5622feb2fb40ea6c2df8abed9bd1c68d27e363de..46dc3bdb44e2cfe1cb39d96a72d125c9892652c0
@@@ -1,7 -1,7 +1,7 @@@
  /*
      route.h -- header file for route.c
      Copyright (C) 2000-2005 Ivo Timmermans
-                   2000-2006 Guus Sliepen <guus@tinc-vpn.org>         
+                   2000-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
@@@ -44,10 -44,10 +44,10 @@@ 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/subnet.c
index e1de1012337987eb2760f41b169ec8d7366dc479,d7b9f470bffe3d68c2e3c2383504d008a716badd..63d169fe2e4abac6ec263827b6e11b536b401be4
@@@ -20,8 -20,7 +20,8 @@@
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
 +#include "control_common.h"
  #include "device.h"
  #include "logger.h"
  #include "net.h"
@@@ -34,7 -33,7 +34,7 @@@
  
  /* lists type of subnet */
  
 -avl_tree_t *subnet_tree;
 +splay_tree_t *subnet_tree;
  
  /* Subnet lookup cache */
  
@@@ -64,7 -63,7 +64,7 @@@ void subnet_cache_flush(void) 
  static int subnet_compare_mac(const subnet_t *a, const subnet_t *b) {
        int result;
  
 -      result = memcmp(&a->net.mac.address, &b->net.mac.address, sizeof(mac_t));
 +      result = memcmp(&a->net.mac.address, &b->net.mac.address, sizeof a->net.mac.address);
  
        if(result)
                return result;
@@@ -135,7 -134,7 +135,7 @@@ int subnet_compare(const subnet_t *a, c
        case SUBNET_IPV6:
                return subnet_compare_ipv6(a, b);
        default:
 -              logger(LOG_ERR, "subnet_compare() was called with unknown subnet type %d, exitting!",
 +              logger(DEBUG_ALWAYS, LOG_ERR, "subnet_compare() was called with unknown subnet type %d, exitting!",
                           a->type);
                exit(0);
        }
  /* Initialising trees */
  
  void init_subnets(void) {
 -      subnet_tree = avl_alloc_tree((avl_compare_t) subnet_compare, (avl_action_t) free_subnet);
 +      subnet_tree = splay_alloc_tree((splay_compare_t) subnet_compare, (splay_action_t) free_subnet);
  
        subnet_cache_flush();
  }
  
  void exit_subnets(void) {
 -      avl_delete_tree(subnet_tree);
 +      splay_delete_tree(subnet_tree);
  }
  
 -avl_tree_t *new_subnet_tree(void) {
 -      return avl_alloc_tree((avl_compare_t) subnet_compare, NULL);
 +splay_tree_t *new_subnet_tree(void) {
 +      return splay_alloc_tree((splay_compare_t) subnet_compare, NULL);
  }
  
 -void free_subnet_tree(avl_tree_t *subnet_tree) {
 -      avl_delete_tree(subnet_tree);
 +void free_subnet_tree(splay_tree_t *subnet_tree) {
 +      splay_delete_tree(subnet_tree);
  }
  
  /* Allocating and freeing space for subnets */
@@@ -178,15 -177,15 +178,15 @@@ void free_subnet(subnet_t *subnet) 
  void subnet_add(node_t *n, subnet_t *subnet) {
        subnet->owner = n;
  
 -      avl_insert(subnet_tree, subnet);
 -      avl_insert(n->subnet_tree, subnet);
 +      splay_insert(subnet_tree, subnet);
 +      splay_insert(n->subnet_tree, subnet);
  
        subnet_cache_flush();
  }
  
  void subnet_del(node_t *n, subnet_t *subnet) {
 -      avl_delete(n->subnet_tree, subnet);
 -      avl_delete(subnet_tree, subnet);
 +      splay_delete(n->subnet_tree, subnet);
 +      splay_delete(subnet_tree, subnet);
  
        subnet_cache_flush();
  }
@@@ -269,12 -268,84 +269,84 @@@ bool str2net(subnet_t *subnet, const ch
                return true;
        }
  
+       // IPv6 short form
+       if(strstr(subnetstr, "::")) {
+               const char *p;
+               char *q;
+               int colons = 0;
+               // Count number of colons
+               for(p = subnetstr; *p; p++)
+                       if(*p == ':')
+                               colons++;
+               if(colons > 7)
+                       return false;
+               // Scan numbers before the double colon
+               p = subnetstr;
+               for(i = 0; i < colons; i++) {
+                       if(*p == ':')
+                               break;
+                       x[i] = strtoul(p, &q, 0x10);
+                       if(!q || p == q || *q != ':')
+                               return false;
+                       p = ++q;
+               }
+               p++;
+               colons -= i;
+               if(!i) {
+                       p++;
+                       colons--;
+               }
+               if(!*p || *p == '/' || *p == '#')
+                       colons--;
+               // Fill in the blanks
+               for(; i < 8 - colons; i++)
+                       x[i] = 0;
+               // Scan the remaining numbers
+               for(; i < 8; i++) {
+                       x[i] = strtoul(p, &q, 0x10);
+                       if(!q || p == q)
+                               return false;
+                       if(i == 7) {
+                               p = q;
+                               break;
+                       }
+                       if(*q != ':')
+                               return false;
+                       p = ++q;
+               }
+               l = 128;
+               if(*p == '/')
+                       sscanf(p, "/%d#%d", &l, &weight);
+               else if(*p == '#')
+                       sscanf(p, "#%d", &weight);
+               if(l < 0 || l > 128)
+                       return false;
+               subnet->type = SUBNET_IPV6;
+               subnet->net.ipv6.prefixlength = l;
+               subnet->weight = weight;
+               for(i = 0; i < 8; i++)
+                       subnet->net.ipv6.address.x[i] = htons(x[i]);
+               return true;
+       }
        return false;
  }
  
  bool net2str(char *netstr, int len, const subnet_t *subnet) {
        if(!netstr || !subnet) {
 -              logger(LOG_ERR, "net2str() was called with netstr=%p, subnet=%p!", netstr, subnet);
 +              logger(DEBUG_ALWAYS, LOG_ERR, "net2str() was called with netstr=%p, subnet=%p!", netstr, subnet);
                return false;
        }
  
                        break;
  
                default:
 -                      logger(LOG_ERR,
 +                      logger(DEBUG_ALWAYS, LOG_ERR,
                                   "net2str() was called with unknown subnet type %d, exiting!",
                                   subnet->type);
                        exit(0);
  /* Subnet lookup routines */
  
  subnet_t *lookup_subnet(const node_t *owner, const subnet_t *subnet) {
 -      return avl_search(owner->subnet_tree, subnet);
 +      return splay_search(owner->subnet_tree, subnet);
  }
  
  subnet_t *lookup_subnet_mac(const node_t *owner, const mac_t *address) {
        subnet_t *p, *r = NULL;
 -      avl_node_t *n;
 +      splay_node_t *n;
        int i;
  
        // Check if this address is cached
  
  subnet_t *lookup_subnet_ipv4(const ipv4_t *address) {
        subnet_t *p, *r = NULL;
 -      avl_node_t *n;
 +      splay_node_t *n;
        int i;
  
        // Check if this address is cached
  
  subnet_t *lookup_subnet_ipv6(const ipv6_t *address) {
        subnet_t *p, *r = NULL;
 -      avl_node_t *n;
 +      splay_node_t *n;
        int i;
  
        // Check if this address is cached
  }
  
  void subnet_update(node_t *owner, subnet_t *subnet, bool up) {
 -      avl_node_t *node;
 +      splay_node_t *node;
        int i;
        char *envp[9] = {NULL};
        char netstr[MAXNETSTR];
                }
        }
  
 -      for(i = 0; envp[i] && i < 9; i++)
 +      for(i = 0; envp[i] && i < 8; i++)
                free(envp[i]);
  }
  
 -void dump_subnets(void) {
 +bool dump_subnets(connection_t *c) {
        char netstr[MAXNETSTR];
        subnet_t *subnet;
 -      avl_node_t *node;
 -
 -      logger(LOG_DEBUG, "Subnet list:");
 +      splay_node_t *node;
  
        for(node = subnet_tree->head; node; node = node->next) {
                subnet = node->data;
                if(!net2str(netstr, sizeof netstr, subnet))
                        continue;
 -              logger(LOG_DEBUG, " %s owner %s", netstr, subnet->owner->name);
 +              send_request(c, "%d %d %s owner %s",
 +                              CONTROL, REQ_DUMP_SUBNETS,
 +                              netstr, subnet->owner->name);
        }
  
 -      logger(LOG_DEBUG, "End of subnet list.");
 +      return send_request(c, "%d %d", CONTROL, REQ_DUMP_SUBNETS);
  }
diff --combined src/tincd.c
index a78ca0c4a7e1bdc5bae92c7353a397efcf8085ee,443301e083f178a501926dd0d383315463c2409b..1008f88aaec0865a5c604594f586f277a83f9f60
@@@ -1,7 -1,7 +1,7 @@@
  /*
      tincd.c -- the main file for tincd
      Copyright (C) 1998-2005 Ivo Timmermans
-                   2000-2011 Guus Sliepen <guus@tinc-vpn.org>
+                   2000-2012 Guus Sliepen <guus@tinc-vpn.org>
                    2008      Max Rijevski <maksuf@gmail.com>
                    2009      Michael Tokarev <mjt@tls.msk.ru>
                    2010      Julien Muchembled <jm@jmuchemb.eu>
  #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'},
@@@ -123,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"
 -                              "  -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");
        }
  }
@@@ -147,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;
  
                        case 'L':                               /* no detach */
  #ifndef HAVE_MLOCKALL
 -                              logger(LOG_ERR, "%s not supported on this platform", "mlockall()");
 +                              logger(DEBUG_ALWAYS, LOG_ERR, "%s not supported on this platform", "mlockall()");
                                return false;
  #else
                                do_mlock = true;
                                        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;
 -
 -#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;
 -
 -      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
  */
@@@ -238,7 -399,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);
                else
 -                      logger(LOG_INFO, "Both netname and configuration directory given, using the latter...");
 +                      logger(DEBUG_ALWAYS, LOG_INFO, "Both netname and configuration directory given, using the latter...");
        } else {
                if(!confbase)
                        xasprintf(&confbase, CONFDIR "/tinc");
        }
  }
  
 -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");
 +              logger(DEBUG_ALWAYS, LOG_ERR, "%s not supported on this platform", "-U");
                return false;
        }
        if (do_chroot) {
 -              logger(LOG_ERR, "%s not supported on this platform", "-R");
 +              logger(DEBUG_ALWAYS, LOG_ERR, "%s not supported on this platform", "-R");
                return false;
        }
  #else
        if (switchuser) {
                struct passwd *pw = getpwnam(switchuser);
                if (!pw) {
 -                      logger(LOG_ERR, "unknown user `%s'", switchuser);
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "unknown user `%s'", switchuser);
                        return false;
                }
                uid = pw->pw_uid;
                if (initgroups(switchuser, pw->pw_gid) != 0 ||
                    setgid(pw->pw_gid) != 0) {
 -                      logger(LOG_ERR, "System call `%s' failed: %s",
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s",
                               "initgroups", strerror(errno));
                        return false;
                }
        if (do_chroot) {
                tzset();        /* for proper timestamps in logs */
                if (chroot(confbase) != 0 || chdir("/") != 0) {
 -                      logger(LOG_ERR, "System call `%s' failed: %s",
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s",
                               "chroot", strerror(errno));
                        return false;
                }
        }
        if (switchuser)
                if (setuid(uid) != 0) {
 -                      logger(LOG_ERR, "System call `%s' failed: %s",
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s",
                               "setuid", strerror(errno));
                        return false;
                }
@@@ -357,9 -516,9 +357,9 @@@ 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"
+               printf("Copyright (C) 1998-2012 Ivo Timmermans, Guus Sliepen and others.\n"
                                "See the AUTHORS file for a complete list.\n\n"
                                "tinc comes with ABSOLUTELY NO WARRANTY.  This is free software,\n"
                                "and you are welcome to redistribute it under certain conditions;\n"
                return 0;
        }
  
 -      if(kill_tincd)
 -              return !kill_other(kill_tincd);
 +#ifdef HAVE_MINGW
 +      if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) {
 +              logger(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, LOG_ERR, "Error initializing libevent!");
 +              return 1;
 +      }
 +
        g_argv = argv;
  
        init_configuration(&config_tree);
  
        /* Slllluuuuuuurrrrp! */
  
 -      RAND_load_file("/dev/urandom", 1024);
 -
 -      ENGINE_load_builtin_engines();
 -      ENGINE_register_all_complete();
 -
 -      OpenSSL_add_all_algorithms();
 -
 -      if(generate_keys) {
 -              read_server_config();
 -              return !keygen(generate_keys);
 -      }
 +      srand(time(NULL));
 +      crypto_init();
  
        if(!read_server_config())
                return 1;
  
  #ifdef HAVE_LZO
        if(lzo_init() != LZO_E_OK) {
 -              logger(LOG_ERR, "Error initializing LZO compressor!");
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Error initializing LZO compressor!");
                return 1;
        }
  #endif
  
  #ifdef HAVE_MINGW
 -      if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) {
 -              logger(LOG_ERR, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError()));
 -              return 1;
 -      }
 -
        if(!do_detach || !init_service())
                return main2(argc, argv);
        else
@@@ -427,7 -591,7 +427,7 @@@ int main2(int argc, char **argv) 
         * This has to be done after daemon()/fork() so it works for child.
         * No need to do that in parent as it's very short-lived. */
        if(do_mlock && mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
 -              logger(LOG_ERR, "System call `%s' failed: %s", "mlockall",
 +              logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "mlockall",
                   strerror(errno));
                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. */
  
          if(get_config_string(lookup_config(config_tree, "ProcessPriority"), &priority)) {
                  if(!strcasecmp(priority, "Normal")) {
                          if (setpriority(NORMAL_PRIORITY_CLASS) != 0) {
 -                                logger(LOG_ERR, "System call `%s' failed: %s",
 +                                logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s",
                                         "setpriority", strerror(errno));
                                  goto end;
                          }
                  } else if(!strcasecmp(priority, "Low")) {
                          if (setpriority(BELOW_NORMAL_PRIORITY_CLASS) != 0) {
 -                                       logger(LOG_ERR, "System call `%s' failed: %s",
 +                                       logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s",
                                         "setpriority", strerror(errno));
                                  goto end;
                          }
                  } else if(!strcasecmp(priority, "High")) {
                          if (setpriority(HIGH_PRIORITY_CLASS) != 0) {
 -                                logger(LOG_ERR, "System call `%s' failed: %s",
 +                                logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s",
                                         "setpriority", strerror(errno));
                                  goto end;
                          }
                  } else {
 -                        logger(LOG_ERR, "Invalid priority `%s`!", priority);
 +                        logger(DEBUG_ALWAYS, LOG_ERR, "Invalid priority `%s`!", priority);
                          goto end;
                  }
          }
  
        /* Shutdown properly. */
  
 -      ifdebug(CONNECTIONS)
 +      if(debug_level >= DEBUG_CONNECTIONS)
                devops.dump_stats();
  
        close_network_connections();
  
  end:
 -      logger(LOG_NOTICE, "Terminating");
 +      exit_control();
  
 -#ifndef HAVE_MINGW
 -      remove_pid(pidfilename);
 -#endif
 +end_nonet:
 +      logger(DEBUG_ALWAYS, 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/vde_device.c
index ab2ffdcd70617bc174dddb490f1982cd1f13eb3d,e69ae80236457cf973bf9fb5c0efe1e3e8c32ae4..815b956fbeae12f417fde3633f3f870196a484be
@@@ -45,7 -45,7 +45,7 @@@ static bool setup_device(void) 
        libvdeplug_dynopen(plug);
  
        if(!plug.dl_handle) {
 -              logger(LOG_ERR, "Could not open libvdeplug library!");
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Could not open libvdeplug library!");
                return false;
        }
  
@@@ -68,7 -68,7 +68,7 @@@
  
        conn = plug.vde_open(device, identname, &args);
        if(!conn) {
 -              logger(LOG_ERR, "Could not open VDE socket %s", device);
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Could not open VDE socket %s", device);
                return false;
        }
  
@@@ -78,7 -78,7 +78,7 @@@
        fcntl(device_fd, F_SETFD, FD_CLOEXEC);
  #endif
  
 -      logger(LOG_INFO, "%s is a %s", device, device_info);
 +      logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info);
  
        if(routing_mode == RMODE_ROUTER)
                overwrite_mac = true;
@@@ -99,24 -99,24 +99,24 @@@ static void close_device(void) 
  }
  
  static bool read_packet(vpn_packet_t *packet) {
-       int lenin = plug.vde_recv(conn, packet->data, MTU, 0);
+       int lenin = (ssize_t)plug.vde_recv(conn, packet->data, MTU, 0);
        if(lenin <= 0) {
 -              logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno));
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno));
                running = false;
                return false;
        }
  
        packet->len = lenin;
        device_total_in += packet->len;
 -      ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, device_info);
 +      logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, device_info);
  
        return true;
  }
  
  static bool write_packet(vpn_packet_t *packet) {
-       if(plug.vde_send(conn, packet->data, packet->len, 0) < 0) {
+       if((ssize_t)plug.vde_send(conn, packet->data, packet->len, 0) < 0) {
                if(errno != EINTR && errno != EAGAIN) {
 -                      logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno));
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno));
                        running = false;
                }
  
  }
  
  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(DEBUG_ALWAYS, LOG_DEBUG, "Statistics for %s %s:", device_info, device);
 +      logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes in:  %10"PRIu64, device_total_in);
 +      logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out);
  }
  
  const devops_t vde_devops = {