]> git.meshlink.io Git - meshlink/commitdiff
Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1
authorGuus Sliepen <guus@tinc-vpn.org>
Tue, 26 Jun 2012 11:24:20 +0000 (13:24 +0200)
committerGuus Sliepen <guus@tinc-vpn.org>
Tue, 26 Jun 2012 11:24:20 +0000 (13:24 +0200)
Conflicts:
NEWS
README
configure.in
lib/utils.c
src/linux/device.c
src/meta.c
src/net.h
src/net_setup.c
src/net_socket.c
src/protocol.c
src/protocol_auth.c
src/tincd.c

16 files changed:
1  2 
NEWS
THANKS
doc/tinc.conf.5.in
doc/tinc.texi
have.h
src/meta.c
src/net.h
src/net_packet.c
src/net_setup.c
src/net_socket.c
src/netutl.c
src/protocol.c
src/protocol_auth.c
src/route.c
src/route.h
src/utils.c

diff --combined NEWS
index a3850477a70ce6b8df7a2d00c1273bbf6e98e09c,4887ee4c575505bca046f24d8253816151a321a3..191c2f217e6f4efa76f4765758dfe554d5bb4b66
--- 1/NEWS
--- 2/NEWS
+++ b/NEWS
@@@ -1,33 -1,14 +1,44 @@@
 +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.19               June 25 2012
+  * Allow :: notation in IPv6 Subnets.
+  * Add support for systemd style socket activation.
+  * Allow environment variables to be used for the Name option.
+  * Add basic support for SOCKS proxies, HTTP proxies, and proxying through an
+    external command.
  Version 1.0.18               March 25 2012
  
   * Fixed IPv6 in switch mode by turning off DecrementTTL by default.
@@@ -69,8 -50,6 +80,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 THANKS
index 521f1a20990c26d8bf5e496085a220258ac227cd,0698c47d653db5f8cccff50167bd7b9876f3a9d9..6fa8f9c869d32c6a4d7009c8c1a3f7bf73e8be08
--- 1/THANKS
--- 2/THANKS
+++ b/THANKS
@@@ -3,13 -3,13 +3,14 @@@ We would like to thank the following pe
  * Alexander Reil and Gemeinde Berg
  * Allesandro Gatti
  * Andreas van Cranenburgh
+ * Anthony G. Basile
  * Armijn Hemel
  * Brandon Black
  * Cris van Pelt
  * Delf Eldkraft
  * dnk
  * Enrique Zanardi
 +* Erik Tews
  * Flynn Marquardt
  * Grzegorz Dymarek
  * Hans Bayle
diff --combined doc/tinc.conf.5.in
index 8f19de068ba90d4caaa29d09eab5356f836bccb4,df74c5b17639ea76cefaf1b62f34265aab160437..40b013b607e2c2e3f4667e71afab93d1f51eb7eb
@@@ -159,8 -159,25 +159,25 @@@ It is possible to bind only to a singl
  .Pp
  This option may not work on all platforms.
  
- .It Va Broadcast Li = yes | no Po yes Pc Bq experimental
- When disabled, tinc will drop all broadcast and multicast packets, in both router and switch mode.
+ .It Va Broadcast Li = no | mst | direct Po mst Pc Bq experimental
+ This option selects the way broadcast packets are sent to other daemons.
+ NOTE: all nodes in a VPN must use the same
+ .Va Broadcast
+ mode, otherwise routing loops can form.
+ .Bl -tag -width indent
+ .It no
+ Broadcast packets are never sent to other nodes.
+ .It mst
+ Broadcast packets are sent and forwarded via the VPN's Minimum Spanning Tree.
+ This ensures broadcast packets reach all nodes.
+ .It direct
+ Broadcast packets are sent directly to all nodes that can be reached directly.
+ Broadcast packets received from other nodes are never forwarded.
+ If the IndirectData option is also set, broadcast packets will only be sent to nodes which we have a meta connection to.
+ .El
  
  .It Va ConnectTo Li = Ar name
  Specifies which other tinc daemon to connect to on startup.
@@@ -282,21 -299,6 +299,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
@@@ -409,6 -411,19 +426,19 @@@ while no routing table is managed
  .It Va Name Li = Ar name Bq required
  This is the name which identifies this tinc daemon.
  It must be unique for the virtual private network this daemon will connect to.
+ The Name may only consist of alphanumeric and underscore characters.
+ If 
+ .Va Name
+ starts with a
+ .Li $ ,
+ then the contents of the environment variable that follows will be used.
+ In that case, invalid characters will be converted to underscores.
+ If
+ .Va Name
+ is
+ .Li $HOST ,
+ but no such environment variable exist, the hostname will be read using the gethostnname() system call.
  
  .It Va PingInterval Li = Ar seconds Pq 60
  The number of seconds of inactivity that
@@@ -441,8 -456,41 +471,41 @@@ specified in the configuration file
  When this option is used the priority of the tincd process will be adjusted.
  Increasing the priority may help to reduce latency and packet loss on the VPN.
  
+ .It Va Proxy Li = socks4 | socks5 | http | exec Ar ... Bq experimental
+ Use a proxy when making outgoing connections.
+ The following proxy types are currently supported:
+ .Bl -tag -width indent
+ .It socks4 Ar address Ar port Op Ar username
+ Connects to the proxy using the SOCKS version 4 protocol.
+ Optionally, a
+ .Ar username
+ can be supplied which will be passed on to the proxy server.
+ Only IPv4 connections can be proxied using SOCKS 4.
+ .It socks5 Ar address Ar port Op Ar username Ar password
+ Connect to the proxy using the SOCKS version 5 protocol.
+ If a
+ .Ar username
+ and
+ .Ar password
+ are given, basic username/password authentication will be used,
+ otherwise no authentication will be used.
+ .It http Ar address Ar port
+ Connects to the proxy and sends a HTTP CONNECT request.
+ .It exec Ar command
+ Executes the given
+ .Ar command
+ which should set up the outgoing connection.
+ The environment variables
+ .Ev NAME ,
+ .Ev NODE ,
+ .Ev REMOTEADDRES
+ and
+ .Ev REMOTEPORT
+ are available.
+ .El
  .It Va ReplayWindow Li = Ar bytes Pq 16
This is the size of the replay tracking window for each remote node, in bytes.
vhis is the size of the replay tracking window for each remote node, in bytes.
  The window is a bitfield which tracks 1 packet per bit, so for example
  the default setting of 16 will track up to 128 packets in the window.  In high
  bandwidth scenarios, setting this to a higher value can reduce packet loss from
diff --combined doc/tinc.texi
index b4fb1f130ca02040308295e007b962a1f7060996,8cf157fe9ce0564e9ded904f96b6c9b9c51b55f0..b595aec1741f0b6ffaac91519065238513e97ab0
@@@ -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
@@@ -801,8 -778,23 +801,23 @@@ variable
  This option may not work on all platforms.
  
  @cindex Broadcast
- @item Broadcast = <yes | no> (yes) [experimental]
- When disabled, tinc will drop all broadcast and multicast packets, in both router and switch mode.
+ @item Broadcast = <no | mst | direct> (mst) [experimental]
+ This option selects the way broadcast packets are sent to other daemons.
+ @emph{NOTE: all nodes in a VPN must use the same Broadcast mode, otherwise routing loops can form.}
+ @table @asis
+ @item no
+ Broadcast packets are never sent to other nodes.
+ @item mst
+ Broadcast packets are sent and forwarded via the VPN's Minimum Spanning Tree.
+ This ensures broadcast packets reach all nodes.
+ @item direct
+ Broadcast packets are sent directly to all nodes that can be reached directly.
+ Broadcast packets received from other nodes are never forwarded.
+ If the IndirectData option is also set, broadcast packets will only be sent to nodes which we have a meta connection to.
+ @end table
  
  @cindex ConnectTo
  @item ConnectTo = <@var{name}>
@@@ -913,21 -905,6 +928,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.
@@@ -1031,6 -1008,11 +1046,11 @@@ This only has effect when Mode is set t
  This is a symbolic name for this connection.
  The name should consist only of alfanumeric and underscore characters (a-z, A-Z, 0-9 and _).
  
+ If Name starts with a $, then the contents of the environment variable that follows will be used.
+ In that case, invalid characters will be converted to underscores.
+ If Name is $HOST, but no such environment variable exist,
+ the hostname will be read using the gethostnname() system call.
  @cindex PingInterval
  @item PingInterval = <@var{seconds}> (60)
  The number of seconds of inactivity that tinc will wait before sending a
@@@ -1056,7 -1038,7 +1076,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
@@@ -1068,6 -1050,33 +1088,33 @@@ specified in the configuration file
  When this option is used the priority of the tincd process will be adjusted.
  Increasing the priority may help to reduce latency and packet loss on the VPN.
  
+ @cindex Proxy
+ @item Proxy = socks4 | socks4 | http | exec @var{...} [experimental]
+ Use a proxy when making outgoing connections.
+ The following proxy types are currently supported:
+ @table @asis
+ @cindex socks4
+ @item socks4 <@var{address}> <@var{port}> [<@var{username}>]
+ Connects to the proxy using the SOCKS version 4 protocol.
+ Optionally, a @var{username} can be supplied which will be passed on to the proxy server.
+ @cindex socks5
+ @item socks4 <@var{address}> <@var{port}> [<@var{username}> <@var{password}>]
+ Connect to the proxy using the SOCKS version 5 protocol.
+ If a @var{username} and @var{password} are given, basic username/password authentication will be used,
+ otherwise no authentication will be used.
+ @cindex http
+ @item http <@var{address}> <@var{port}>
+ Connects to the proxy and sends a HTTP CONNECT request.
+ @cindex exec
+ @item exec <@var{command}>
+ Executes the given command which should set up the outgoing connection.
+ The environment variables @env{NAME}, @env{NODE}, @env{REMOTEADDRES} and @env{REMOTEPORT} are available.
+ @end table
  @cindex ReplayWindow
  @item ReplayWindow = <bytes> (16)
  This is the size of the replay tracking window for each remote node, in bytes.
@@@ -1178,7 -1187,7 +1225,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
@@@ -1213,6 -1222,7 +1260,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,
@@@ -1220,12 -1230,15 +1267,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
  
  
@@@ -1314,6 -1327,10 +1361,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
  
  
@@@ -1360,7 -1377,7 +1407,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.
@@@ -1589,7 -1606,7 +1636,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},
@@@ -1655,6 -1672,12 +1702,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}.
@@@ -1681,6 -1705,9 +1728,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.
@@@ -1734,6 -1761,19 +1781,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 ==================================================================
@@@ -1797,7 -1837,7 +1844,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
  
@@@ -1896,8 -1936,6 +1943,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)
@@@ -1928,213 -1966,6 +1975,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
diff --combined have.h
index 0ab813461c41bcf54bd8d397cfa18d334066feb8,e00c7f7194f046f57d8f2d447893ead48417f319..d47f204cdc1bb45f0dbbd6fc28550d701080e6c2
--- 1/have.h
--- 2/have.h
+++ b/have.h
@@@ -27,7 -27,6 +27,7 @@@
  #else
  #define WINVER WindowsXP
  #endif
 +#define WIN32_LEAN_AND_MEAN
  #endif
  
  #include <stdio.h>
@@@ -42,6 -41,7 +42,7 @@@
  
  #ifdef HAVE_MINGW
  #include <w32api.h>
+ #include <winsock2.h>
  #include <windows.h>
  #include <ws2tcpip.h>
  #endif
  #include <inttypes.h>
  #endif
  
 -#ifdef HAVE_ALLOCA_H
 -#include <alloca.h>
 -#endif
 -
  /* Include system specific headers */
  
  #ifdef HAVE_SYSLOG_H
  #include <sys/uio.h>
  #endif
  
 +#ifdef HAVE_SYS_UN_H
 +#include <sys/un.h>
 +#endif
 +
  #ifdef HAVE_DIRENT_H
  #include <dirent.h>
  #endif
  #include <netinet/if_ether.h>
  #endif
  
 +#ifdef HAVE_EVENT_H
 +#include <event.h>
 +#endif
 +
  #endif /* __TINC_SYSTEM_H__ */
diff --combined src/meta.c
index 13c8495a6e3fbd5c5a9e02cd13a1f3e6074e0cbd,1b3424606771b813aa3a15c096a8ad751eaafbcc..a272baf57e10da72687fed8c7aa91223e626bccc
  
  #include "system.h"
  
 -#include <openssl/err.h>
 -#include <openssl/evp.h>
 -
 -#include "avl_tree.h"
 +#include "splay_tree.h"
 +#include "cipher.h"
  #include "connection.h"
  #include "logger.h"
  #include "meta.h"
  #include "utils.h"
  #include "xalloc.h"
  
 -bool send_meta(connection_t *c, const char *buffer, int length) {
 -      int outlen;
 -      int result;
 +bool send_meta_sptps(void *handle, const char *buffer, size_t length) {
 +      connection_t *c = handle;
  
        if(!c) {
 -              logger(LOG_ERR, "send_meta() called with NULL pointer!");
 +              logger(DEBUG_ALWAYS, LOG_ERR, "send_meta_sptps() called with NULL pointer!");
                abort();
        }
  
 -      ifdebug(META) logger(LOG_DEBUG, "Sending %d bytes of metadata to %s (%s)", length,
 -                         c->name, c->hostname);
 +      logger(DEBUG_META, LOG_DEBUG, "send_meta_sptps(%s, %p, %zu)", c->name, buffer, length);
  
 -      if(!c->outbuflen)
 -              c->last_flushed_time = now;
 +      buffer_add(&c->outbuf, buffer, length);
 +      event_add(&c->outevent, NULL);
  
 -      /* Find room in connection's buffer */
 -      if(length + c->outbuflen > c->outbufsize) {
 -              c->outbufsize = length + c->outbuflen;
 -              c->outbuf = xrealloc(c->outbuf, c->outbufsize);
 -      }
 +      return true;
 +}
  
 -      if(length + c->outbuflen + c->outbufstart > c->outbufsize) {
 -              memmove(c->outbuf, c->outbuf + c->outbufstart, c->outbuflen);
 -              c->outbufstart = 0;
 +bool send_meta(connection_t *c, const char *buffer, int length) {
 +      if(!c) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "send_meta() called with NULL pointer!");
 +              abort();
        }
  
 -      /* Add our data to buffer */
 -      if(c->status.encryptout) {
 -              result = EVP_EncryptUpdate(c->outctx, (unsigned char *)c->outbuf + c->outbufstart + c->outbuflen,
 -                              &outlen, (unsigned char *)buffer, length);
 -              if(!result || outlen < length) {
 -                      logger(LOG_ERR, "Error while encrypting metadata to %s (%s): %s",
 -                                      c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL));
 -                      return false;
 -              } else if(outlen > length) {
 -                      logger(LOG_EMERG, "Encrypted data too long! Heap corrupted!");
 -                      abort();
 -              }
 -              c->outbuflen += outlen;
 -      } else {
 -              memcpy(c->outbuf + c->outbufstart + c->outbuflen, buffer, length);
 -              c->outbuflen += length;
 -      }
 +      logger(DEBUG_META, LOG_DEBUG, "Sending %d bytes of metadata to %s (%s)", length,
 +                         c->name, c->hostname);
  
 -      return true;
 -}
 +      if(c->protocol_minor >= 2)
 +              return sptps_send_record(&c->sptps, 0, buffer, length);
  
 -bool flush_meta(connection_t *c) {
 -      int result;
 -      
 -      ifdebug(META) logger(LOG_DEBUG, "Flushing %d bytes to %s (%s)",
 -                       c->outbuflen, c->name, c->hostname);
 -
 -      while(c->outbuflen) {
 -              result = send(c->socket, c->outbuf + c->outbufstart, c->outbuflen, 0);
 -              if(result <= 0) {
 -                      if(!errno || errno == EPIPE) {
 -                              ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Connection closed by %s (%s)",
 -                                                 c->name, c->hostname);
 -                      } else if(errno == EINTR) {
 -                              continue;
 -                      } else if(sockwouldblock(sockerrno)) {
 -                              ifdebug(CONNECTIONS) logger(LOG_DEBUG, "Flushing %d bytes to %s (%s) would block",
 -                                              c->outbuflen, c->name, c->hostname);
 -                              return true;
 -                      } else {
 -                              logger(LOG_ERR, "Flushing meta data to %s (%s) failed: %s", c->name,
 -                                         c->hostname, sockstrerror(sockerrno));
 -                      }
 +      /* Add our data to buffer */
 +      if(c->status.encryptout) {
 +              size_t outlen = length;
  
 +              if(!cipher_encrypt(&c->outcipher, buffer, length, buffer_prepare(&c->outbuf, length), &outlen, false) || outlen != length) {
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting metadata to %s (%s)",
 +                                      c->name, c->hostname);
                        return false;
                }
  
 -              c->outbufstart += result;
 -              c->outbuflen -= result;
 +      } else {
 +              buffer_add(&c->outbuf, buffer, length);
        }
  
 -      c->outbufstart = 0; /* avoid unnecessary memmoves */
 +      event_add(&c->outevent, NULL);
 +
        return true;
  }
  
  void broadcast_meta(connection_t *from, const char *buffer, int length) {
 -      avl_node_t *node;
 +      splay_node_t *node;
        connection_t *c;
  
        for(node = connection_tree->head; node; node = node->next) {
        }
  }
  
 +bool receive_meta_sptps(void *handle, uint8_t type, const char *data, uint16_t length) {
 +      connection_t *c = handle;
 +
 +      if(!c) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "receive_meta_sptps() called with NULL pointer!");
 +              abort();
 +      }
 +
 +      logger(DEBUG_META, LOG_DEBUG, "receive_meta_sptps(%s, %d, %p, %hu)", c->name, type, data, length);
 +
 +      if(type == SPTPS_HANDSHAKE) {
 +              if(c->allow_request == ACK)
 +                      return send_ack(c);
 +              else
 +                      return true;
 +      }
 +
 +      if(!data)
 +              return true;
 +
 +      /* Are we receiving a TCPpacket? */
 +
 +      if(c->tcplen) {
 +              if(length != c->tcplen)
 +                      return false;
 +              receive_tcppacket(c, data, length);
 +              c->tcplen = 0;
 +              return true;
 +      }
 +
 +      /* Otherwise we are waiting for a request */
 +
 +      return receive_request(c, data);
 +}
 +
  bool receive_meta(connection_t *c) {
 -      int oldlen, i, result;
 -      int lenin, lenout, reqlen;
 -      bool decrypted = false;
 +      int inlen;
        char inbuf[MAXBUFSIZE];
 +      char *bufp = inbuf, *endp;
  
        /* Strategy:
           - Read as much as possible from the TCP socket in one go.
           - If not, keep stuff in buffer and exit.
         */
  
 -      lenin = recv(c->socket, c->buffer + c->buflen, MAXBUFSIZE - c->buflen, 0);
 +      buffer_compact(&c->inbuf, MAXBUFSIZE);
 +
 +      if(sizeof inbuf <= c->inbuf.len) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Input buffer full for %s (%s)", c->name, c->hostname);
 +              return false;
 +      }
 +
 +      inlen = recv(c->socket, inbuf, sizeof inbuf - c->inbuf.len, 0);
  
 -      if(lenin <= 0) {
 -              if(!lenin || !errno) {
 -                      ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Connection closed by %s (%s)",
 +      if(inlen <= 0) {
 +              if(!inlen || !errno) {
 +                      logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection closed by %s (%s)",
                                           c->name, c->hostname);
                } else if(sockwouldblock(sockerrno))
                        return true;
                else
 -                      logger(LOG_ERR, "Metadata socket read error for %s (%s): %s",
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "Metadata socket read error for %s (%s): %s",
                                   c->name, c->hostname, sockstrerror(sockerrno));
 -
                return false;
        }
  
 -      oldlen = c->buflen;
 -      c->buflen += lenin;
 +      do {
 +              if(c->protocol_minor >= 2) {
 +                      logger(DEBUG_META, LOG_DEBUG, "Receiving %d bytes of SPTPS data", inlen);
 +                      return sptps_receive_data(&c->sptps, bufp, inlen);
 +              }
 +
 +              if(!c->status.decryptin) {
 +                      endp = memchr(bufp, '\n', inlen);
 +                      if(endp)
 +                              endp++;
 +                      else
 +                              endp = bufp + inlen;
  
 -      while(lenin > 0) {
 -              /* Decrypt */
 +                      buffer_add(&c->inbuf, bufp, endp - bufp);
  
 -              if(c->status.decryptin && !decrypted) {
 -                      result = EVP_DecryptUpdate(c->inctx, (unsigned char *)inbuf, &lenout, (unsigned char *)c->buffer + oldlen, lenin);
 -                      if(!result || lenout != lenin) {
 -                              logger(LOG_ERR, "Error while decrypting metadata from %s (%s): %s",
 -                                              c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL));
 +                      inlen -= endp - bufp;
 +                      bufp = endp;
 +              } else {
 +                      size_t outlen = inlen;
 +                      logger(DEBUG_META, LOG_DEBUG, "Received encrypted %d bytes", inlen);
 +
 +                      if(!cipher_decrypt(&c->incipher, bufp, inlen, buffer_prepare(&c->inbuf, inlen), &outlen, false) || inlen != outlen) {
 +                              logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting metadata from %s (%s)",
 +                                         c->name, c->hostname);
                                return false;
                        }
 -                      memcpy(c->buffer + oldlen, inbuf, lenin);
 -                      decrypted = true;
 -              }
  
 -              /* Are we receiving a TCPpacket? */
 -
 -              if(c->tcplen) {
 -                      if(c->tcplen <= c->buflen) {
 -                              if(proxytype == PROXY_SOCKS4 && c->allow_request == ID) {
 -                                      if(c->buffer[0] == 0 && c->buffer[1] == 0x5a) {
 -                                              logger(LOG_DEBUG, "Proxy request granted");
 -                                      } else {
 -                                              logger(LOG_ERR, "Proxy request rejected");
 -                                              return false;
 -                                      }
 -                              } else 
 -                                      receive_tcppacket(c, c->buffer, c->tcplen);
 -
 -                              c->buflen -= c->tcplen;
 -                              lenin -= c->tcplen - oldlen;
 -                              memmove(c->buffer, c->buffer + c->tcplen, c->buflen);
 -                              oldlen = 0;
 -                              c->tcplen = 0;
 -                              continue;
 -                      } else {
 -                              break;
 -                      }
 +                      inlen = 0;
                }
  
 -              /* Otherwise we are waiting for a request */
 +              while(c->inbuf.len) {
 +                      /* Are we receiving a TCPpacket? */
 +
 +                      if(c->tcplen) {
 +                              char *tcpbuffer = buffer_read(&c->inbuf, c->tcplen);
 +                              if(tcpbuffer) {
-                                       receive_tcppacket(c, tcpbuffer, c->tcplen);
++                                      if(proxytype == PROXY_SOCKS4 && c->allow_request == ID) {
++                                              if(tcpbuffer[0] == 0 && tcpbuffer[1] == 0x5a) {
++                                                      logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Proxy request granted");
++                                              } else {
++                                                      logger(DEBUG_CONNECTIONS, LOG_ERR, "Proxy request rejected");
++                                                      return false;
++                                              }
++                                      } else 
++                                              receive_tcppacket(c, tcpbuffer, c->tcplen);
 +                                      c->tcplen = 0;
 +                                      continue;
 +                              } else {
 +                                      break;
 +                              }
 +                      }
  
 -              reqlen = 0;
 +                      /* Otherwise we are waiting for a request */
  
 -              for(i = oldlen; i < c->buflen; i++) {
 -                      if(c->buffer[i] == '\n') {
 -                              c->buffer[i] = '\0';    /* replace end-of-line by end-of-string so we can use sscanf */
 -                              reqlen = i + 1;
 +                      char *request = buffer_readline(&c->inbuf);
 +                      if(request) {
 +                              bool result = receive_request(c, request);
 +                              if(!result)
 +                                      return false;
 +                              continue;
 +                      } else {
                                break;
                        }
                }
 -
 -              if(reqlen) {
 -                      c->reqlen = reqlen;
 -                      if(!receive_request(c))
 -                              return false;
 -
 -                      c->buflen -= reqlen;
 -                      lenin -= reqlen - oldlen;
 -                      memmove(c->buffer, c->buffer + reqlen, c->buflen);
 -                      oldlen = 0;
 -                      continue;
 -              } else {
 -                      break;
 -              }
 -      }
 -
 -      if(c->buflen >= MAXBUFSIZE) {
 -              logger(LOG_ERR, "Metadata read buffer overflow for %s (%s)",
 -                         c->name, c->hostname);
 -              return false;
 -      }
 +      } while(inlen);
  
        return true;
  }
diff --combined src/net.h
index 27b5eb5b5a8e31ed5c53acbb643b5960fbb6e535,2b50c5a2fab629965455db8b2205fe556055c782..d1dde61481101b71ba5873c4138199ba975d2feb
+++ b/src/net.h
@@@ -21,9 -21,9 +21,9 @@@
  #ifndef __TINC_NET_H__
  #define __TINC_NET_H__
  
 -#include <openssl/evp.h>
 -
  #include "ipv6.h"
 +#include "cipher.h"
 +#include "digest.h"
  
  #ifdef ENABLE_JUMBOGRAMS
  #define MTU 9018                              /* 9000 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */
  #define MTU 1518                              /* 1500 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */
  #endif
  
 -#define MAXSIZE (MTU + 4 + EVP_MAX_BLOCK_LENGTH + EVP_MAX_MD_SIZE + MTU/64 + 20)      /* MTU + seqno + padding + HMAC + compressor overhead */
 +#define MAXSIZE (MTU + 4 + CIPHER_MAX_BLOCK_SIZE + DIGEST_MAX_SIZE + MTU/64 + 20)     /* MTU + seqno + padding + HMAC + compressor overhead */
  #define MAXBUFSIZE ((MAXSIZE > 2048 ? MAXSIZE : 2048) + 128)  /* Enough room for a request with a MAXSIZEd packet or a 8192 bits RSA key */
  
 -#define MAXSOCKETS 128                        /* Overkill... */
 +#define MAXSOCKETS 8                  /* Probably overkill... */
  
  typedef struct mac_t {
        uint8_t x[6];
@@@ -84,8 -84,6 +84,8 @@@ typedef struct vpn_packet_t 
  } vpn_packet_t;
  
  typedef struct listen_socket_t {
 +      struct event ev_tcp;
 +      struct event ev_udp;
        int tcp;
        int udp;
        sockaddr_t sa;
@@@ -100,7 -98,7 +100,7 @@@ typedef struct outgoing_t 
        struct config_t *cfg;
        struct addrinfo *ai;
        struct addrinfo *aip;
 -      struct event *event;
 +      struct event ev;
  } outgoing_t;
  
  extern list_t *outgoing_list;
@@@ -113,28 -111,48 +113,43 @@@ 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;
 -
+ extern char *proxyhost;
+ extern char *proxyport;
+ extern char *proxyuser;
+ extern char *proxypass;
+ typedef enum proxytype_t {
+       PROXY_NONE = 0,
+       PROXY_SOCKS4,
+       PROXY_SOCKS4A,
+       PROXY_SOCKS5,
+       PROXY_HTTP,
+       PROXY_EXEC,
+ } proxytype_t;
+ extern proxytype_t proxytype;
  /* 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 char *get_name(void);
  extern bool setup_network(void);
  extern void setup_outgoing_connection(struct outgoing_t *);
  extern void try_outgoing_connections(void);
@@@ -142,16 -160,8 +157,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 ca6aff3d9890e66051c227a8dcccb251bb420524,cd8d98ac0b94cb602a738752f771cfa1d6102af2..cbdc15cfebc98ea77156017b65db2c4beb3d472a
  #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;
+       node_t *n;
+       // Always give ourself a copy of the packet.
+       if(from != myself)
+               send_packet(myself, packet);
+       // In TunnelServer mode, do not forward broadcast packets.
+         // The MST might not be valid and create loops.
+       if(tunnelserver || broadcast_mode == BMODE_NONE)
+               return;
  
 -      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) {
-               send_packet(myself, packet);
+       switch(broadcast_mode) {
+               // In MST mode, broadcast packets travel via the Minimum Spanning Tree.
+               // This guarantees all nodes receive the broadcast packet, and
+               // usually distributes the sending of broadcast packets over all nodes.
+               case BMODE_MST:
+                       for(node = connection_tree->head; node; node = node->next) {
+                               c = node->data;
  
-               // In TunnelServer mode, do not forward broadcast packets.
-                 // The MST might not be valid and create loops.
-               if(tunnelserver)
-                       return;
-       }
+                               if(c->status.active && c->status.mst && c != from->nexthop->connection)
+                                       send_packet(c->node, packet);
+                       }
+                       break;
+               // In direct mode, we send copies to each node we know of.
+               // However, this only reaches nodes that can be reached in a single hop.
+               // We don't have enough information to forward broadcast packets in this case.
+               case BMODE_DIRECT:
+                       if(from != myself)
+                               break;
+                       for(node = node_udp_tree->head; node; node = node->next) {
+                               n = node->data;
  
-       for(node = connection_tree->head; node; node = node->next) {
-               c = node->data;
+                               if(n->status.reachable && ((n->via == myself && n->nexthop == n) || n->via == n))
+                                       send_packet(n, packet);
+                       }
+                       break;
  
-               if(c->status.active && c->status.mst && c != from->nexthop->connection)
-                       send_packet(c->node, packet);
+               default:
+                       break;
        }
  }
  
  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 4afb31b9c3ec5819e904762d600ba43cb7454086,eec438a80879831b1a2416fbdeefa0a9cebf6328..3285a32981805815ac9effd1b8323eae270ab397
  
  #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) {
+ char *proxyhost;
+ char *proxyport;
+ char *proxyuser;
+ char *proxypass;
+ proxytype_t proxytype;
 +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, "Parsing ECDSA public key file `%s' failed.", fname);
        free(fname);
 +      return result;
 +}
  
 -      if(c->rsa_key)
 -              return true;
 +bool read_rsa_public_key(connection_t *c) {
 +      FILE *fp;
 +      char *fname;
 +      char *n;
 +      bool result;
  
 -      /* Try again with PEM_read_RSA_PUBKEY. */
 +      /* First, check for simple PublicKey statement */
 +
 +      if(get_config_string(lookup_config(c->config_tree, "PublicKey"), &n)) {
 +              result = rsa_set_hex_public_key(&c->rsa, n, "FFFF");
 +              free(n);
 +              return result;
 +      }
 +
 +      /* Else, check for PublicKeyFile statement and read it */
 +
 +      if(!get_config_string(lookup_config(c->config_tree, "PublicKeyFile"), &fname))
 +              xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
  
 -      xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
        fp = fopen(fname, "r");
  
        if(!fp) {
 -              logger(LOG_ERR, "Error reading RSA public key file `%s': %s", fname, strerror(errno));
 +              logger(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 */
  
 -      logger(LOG_ERR, "No public key for %s specified!", c->name);
 +      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;
 +      }
 +
 +      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;
  
 -      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!");
 +      /* First, check for simple PrivateKey statement */
 +
 +      if(get_config_string(lookup_config(config_tree, "PrivateKey"), &d)) {
 +              if(!get_config_string(lookup_config(config_tree, "PublicKey"), &n)) {
 +                      logger(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 -223,7 +294,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;
        }
        closedir(dir);
  }
  
 -              logger(LOG_ERR, "Invalid name for myself!");
+ char *get_name(void) {
+       char *name = NULL;
+       get_config_string(lookup_config(config_tree, "Name"), &name);
+       if(!name)
+               return NULL;
+       if(*name == '$') {
+               char *envname = getenv(name + 1);
+               if(!envname) {
+                       if(strcmp(name + 1, "HOST")) {
+                               fprintf(stderr, "Invalid Name: environment variable %s does not exist\n", name + 1);
+                               return false;
+                       }
+                       envname = alloca(32);
+                       if(gethostname(envname, 32)) {
+                               fprintf(stderr, "Could not get hostname: %s\n", strerror(errno));
+                               return false;
+                       }
+                       envname[31] = 0;
+               }
+               free(name);
+               name = xstrdup(envname);
+               for(char *c = name; *c; c++)
+                       if(!isalnum(*c))
+                               *c = '_';
+       }
+       if(!check_id(name)) {
++              logger(DEBUG_ALWAYS, LOG_ERR, "Invalid name for myself!");
+               free(name);
+               return false;
+       }
+       return name;
+ }
  /*
    Configure node_t myself and set up the local sockets (listen only)
  */
@@@ -349,6 -322,8 +393,8 @@@ static bool setup_myself(void) 
        char *name, *hostname, *mode, *afname, *cipher, *digest, *type;
        char *fname = NULL;
        char *address = NULL;
+       char *proxy = NULL;
+       char *space;
        char *envp[5];
        struct addrinfo *ai, *aip, hint = {0};
        bool choice;
        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 */
+       if(!(name = get_name())) {
 -              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(DEBUG_ALWAYS, LOG_ERR, "Invalid name for myself!");
-               free(name);
-               return false;
-       }
        myself->name = name;
        myself->connection->name = xstrdup(name);
        xasprintf(&fname, "%s/hosts/%s", confbase, name);
        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;
  
                sockaddr2str(&sa, NULL, &myport);
        }
  
 -                      logger(LOG_ERR, "Unknown proxy type %s!", proxy);
+       get_config_string(lookup_config(config_tree, "Proxy"), &proxy);
+       if(proxy) {
+               if((space = strchr(proxy, ' ')))
+                       *space++ = 0;
+               if(!strcasecmp(proxy, "none")) {
+                       proxytype = PROXY_NONE;
+               } else if(!strcasecmp(proxy, "socks4")) {
+                       proxytype = PROXY_SOCKS4;
+               } else if(!strcasecmp(proxy, "socks4a")) {
+                       proxytype = PROXY_SOCKS4A;
+               } else if(!strcasecmp(proxy, "socks5")) {
+                       proxytype = PROXY_SOCKS5;
+               } else if(!strcasecmp(proxy, "http")) {
+                       proxytype = PROXY_HTTP;
+               } else if(!strcasecmp(proxy, "exec")) {
+                       proxytype = PROXY_EXEC;
+               } else {
 -                                      logger(LOG_ERR, "Argument expected for proxy type exec!");
++                      logger(DEBUG_ALWAYS, LOG_ERR, "Unknown proxy type %s!", proxy);
+                       return false;
+               }
+               switch(proxytype) {
+                       case PROXY_NONE:
+                       default:
+                               break;
+                       case PROXY_EXEC:
+                               if(!space || !*space) {
 -                                      logger(LOG_ERR, "Host and port argument expected for proxy!");
++                                      logger(DEBUG_ALWAYS, LOG_ERR, "Argument expected for proxy type exec!");
+                                       return false;
+                               }
+                               proxyhost =  xstrdup(space);
+                               break;
+                       case PROXY_SOCKS4:
+                       case PROXY_SOCKS4A:
+                       case PROXY_SOCKS5:
+                       case PROXY_HTTP:
+                               proxyhost = space;
+                               if(space && (space = strchr(space, ' ')))
+                                       *space++ = 0, proxyport = space;
+                               if(space && (space = strchr(space, ' ')))
+                                       *space++ = 0, proxyuser = space;
+                               if(space && (space = strchr(space, ' ')))
+                                       *space++ = 0, proxypass = space;
+                               if(!proxyhost || !*proxyhost || !proxyport || !*proxyport) {
++                                      logger(DEBUG_ALWAYS, LOG_ERR, "Host and port argument expected for proxy!");
+                                       return false;
+                               }
+                               proxyhost = xstrdup(proxyhost);
+                               proxyport = xstrdup(proxyport);
+                               if(proxyuser && *proxyuser)
+                                       proxyuser = xstrdup(proxyuser);
+                               if(proxypass && *proxypass)
+                                       proxypass = xstrdup(proxypass);
+                               break;
+               }
+               free(proxy);
+       }
        /* Read in all the subnets specified in the host configuration file */
  
        cfg = lookup_config(config_tree, "Subnet");
                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);
  
        get_config_bool(lookup_config(config_tree, "PriorityInheritance"), &priorityinheritance);
        get_config_bool(lookup_config(config_tree, "DecrementTTL"), &decrement_ttl);
-       get_config_bool(lookup_config(config_tree, "Broadcast"), &broadcast);
+       if(get_config_string(lookup_config(config_tree, "Broadcast"), &mode)) {
+               if(!strcasecmp(mode, "no"))
+                       broadcast_mode = BMODE_NONE;
+               else if(!strcasecmp(mode, "yes") || !strcasecmp(mode, "mst"))
+                       broadcast_mode = BMODE_MST;
+               else if(!strcasecmp(mode, "direct"))
+                       broadcast_mode = BMODE_DIRECT;
+               else {
 -                      logger(LOG_ERR, "Invalid broadcast mode!");
++                      logger(DEBUG_ALWAYS, LOG_ERR, "Invalid broadcast 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
        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 */
  #endif
  
                if(listen_sockets > MAXSOCKETS) {
 -                      logger(LOG_ERR, "Too many listening sockets");
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "Too many listening sockets");
                        return false;
                }
  
                for(i = 0; i < listen_sockets; i++) {
                        salen = sizeof sa;
                        if(getsockname(i + 3, &sa.sa, &salen) < 0) {
 -                              logger(LOG_ERR, "Could not get address of listen fd %d: %s", i + 3, sockstrerror(errno));
 +                              logger(DEBUG_ALWAYS, LOG_ERR, "Could not get address of listen fd %d: %s", i + 3, sockstrerror(errno));
                                return false;
                        }
  
                        if(listen_socket[i].udp < 0)
                                return false;
  
 -                      ifdebug(CONNECTIONS) {
 +                      event_set(&listen_socket[i].ev_tcp, listen_socket[i].tcp, EV_READ|EV_PERSIST, handle_new_meta_connection, NULL);
 +                      if(event_add(&listen_socket[i].ev_tcp, NULL) < 0) {
 +                              logger(DEBUG_ALWAYS, LOG_ERR, "event_add failed: %s", strerror(errno));
 +                              abort();
 +                      }
 +
 +                      event_set(&listen_socket[i].ev_udp, listen_socket[i].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(&sa);
 -                              logger(LOG_NOTICE, "Listening on %s", hostname);
 +                              logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Listening on %s", hostname);
                                free(hostname);
                        }
  
                        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);
                                }
  
        }
  
        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/net_socket.c
index af3f2fe662eb70cf94b3adbe8edf20d4c5ce0e65,2d1ecc50e83f40ed5d03590e49a8a7a6e6e13182..7f3db5c8bf59b499ebd4aced678498af1719bc81
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "conf.h"
  #include "connection.h"
 -#include "event.h"
  #include "logger.h"
  #include "meta.h"
  #include "net.h"
@@@ -57,24 -58,24 +57,24 @@@ static void configure_tcp(connection_t 
        int flags = fcntl(c->socket, F_GETFL);
  
        if(fcntl(c->socket, F_SETFL, flags | O_NONBLOCK) < 0) {
 -              logger(LOG_ERR, "fcntl for %s: %s", c->hostname, strerror(errno));
 +              logger(DEBUG_ALWAYS, LOG_ERR, "fcntl for %s: %s", c->hostname, strerror(errno));
        }
  #elif defined(WIN32)
        unsigned long arg = 1;
  
        if(ioctlsocket(c->socket, FIONBIO, &arg) != 0) {
 -              logger(LOG_ERR, "ioctlsocket for %s: %d", c->hostname, sockstrerror(sockerrno));
 +              logger(DEBUG_ALWAYS, LOG_ERR, "ioctlsocket for %s: %d", c->hostname, sockstrerror(sockerrno));
        }
  #endif
  
  #if defined(SOL_TCP) && defined(TCP_NODELAY)
        option = 1;
 -      setsockopt(c->socket, SOL_TCP, TCP_NODELAY, (void *)&option, sizeof(option));
 +      setsockopt(c->socket, SOL_TCP, TCP_NODELAY, (void *)&option, sizeof option);
  #endif
  
  #if defined(SOL_IP) && defined(IP_TOS) && defined(IPTOS_LOWDELAY)
        option = IPTOS_LOWDELAY;
 -      setsockopt(c->socket, SOL_IP, IP_TOS, (void *)&option, sizeof(option));
 +      setsockopt(c->socket, SOL_IP, IP_TOS, (void *)&option, sizeof option);
  #endif
  }
  
@@@ -96,12 -97,12 +96,12 @@@ static bool bind_to_interface(int sd) 
  
        status = setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr));
        if(status) {
 -              logger(LOG_ERR, "Can't bind to interface %s: %s", iface,
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to interface %s: %s", iface,
                                strerror(errno));
                return false;
        }
  #else /* if !defined(SOL_SOCKET) || !defined(SO_BINDTODEVICE) */
 -      logger(LOG_WARNING, "%s not supported on this platform", "BindToInterface");
 +      logger(DEBUG_ALWAYS, LOG_WARNING, "%s not supported on this platform", "BindToInterface");
  #endif
  
        return true;
@@@ -116,7 -117,7 +116,7 @@@ int setup_listen_socket(const sockaddr_
        nfd = socket(sa->sa.sa_family, SOCK_STREAM, IPPROTO_TCP);
  
        if(nfd < 0) {
 -              ifdebug(STATUS) logger(LOG_ERR, "Creating metasocket failed: %s", sockstrerror(sockerrno));
 +              logger(DEBUG_STATUS, LOG_ERR, "Creating metasocket failed: %s", sockstrerror(sockerrno));
                return -1;
        }
  
        /* Optimize TCP settings */
  
        option = 1;
 -      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof(option));
 +      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof option);
  
  #if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
        if(sa->sa.sa_family == AF_INET6)
  #if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE)
                struct ifreq ifr;
  
 -              memset(&ifr, 0, sizeof(ifr));
 +              memset(&ifr, 0, sizeof ifr);
                strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ);
  
 -              if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr))) {
 +              if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof ifr)) {
                        closesocket(nfd);
 -                      logger(LOG_ERR, "Can't bind to interface %s: %s", iface,
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to interface %s: %s", iface,
                                   strerror(sockerrno));
                        return -1;
                }
  #else
 -              logger(LOG_WARNING, "%s not supported on this platform", "BindToInterface");
 +              logger(DEBUG_ALWAYS, LOG_WARNING, "%s not supported on this platform", "BindToInterface");
  #endif
        }
  
        if(bind(nfd, &sa->sa, SALEN(sa->sa))) {
                closesocket(nfd);
                addrstr = sockaddr2hostname(sa);
 -              logger(LOG_ERR, "Can't bind to %s/tcp: %s", addrstr, sockstrerror(sockerrno));
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to %s/tcp: %s", addrstr, sockstrerror(sockerrno));
                free(addrstr);
                return -1;
        }
  
        if(listen(nfd, 3)) {
                closesocket(nfd);
 -              logger(LOG_ERR, "System call `%s' failed: %s", "listen", sockstrerror(sockerrno));
 +              logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "listen", sockstrerror(sockerrno));
                return -1;
        }
  
@@@ -178,7 -179,7 +178,7 @@@ int setup_vpn_in_socket(const sockaddr_
        nfd = socket(sa->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP);
  
        if(nfd < 0) {
 -              logger(LOG_ERR, "Creating UDP socket failed: %s", sockstrerror(sockerrno));
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Creating UDP socket failed: %s", sockstrerror(sockerrno));
                return -1;
        }
  
  
                if(fcntl(nfd, F_SETFL, flags | O_NONBLOCK) < 0) {
                        closesocket(nfd);
 -                      logger(LOG_ERR, "System call `%s' failed: %s", "fcntl",
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl",
                                   strerror(errno));
                        return -1;
                }
                unsigned long arg = 1;
                if(ioctlsocket(nfd, FIONBIO, &arg) != 0) {
                        closesocket(nfd);
 -                      logger(LOG_ERR, "Call to `%s' failed: %s", "ioctlsocket", sockstrerror(sockerrno));
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "Call to `%s' failed: %s", "ioctlsocket", sockstrerror(sockerrno));
                        return -1;
                }
        }
  #endif
  
        option = 1;
 -      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof(option));
 -      setsockopt(nfd, SOL_SOCKET, SO_BROADCAST, (void *)&option, sizeof(option));
 +      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof option);
 +      setsockopt(nfd, SOL_SOCKET, SO_BROADCAST, (void *)&option, sizeof option);
  
        if(udp_rcvbuf && setsockopt(nfd, SOL_SOCKET, SO_RCVBUF, (void *)&udp_rcvbuf, sizeof(udp_rcvbuf)))
 -              logger(LOG_WARNING, "Can't set UDP SO_RCVBUF to %i: %s", udp_rcvbuf, strerror(errno));
 +              logger(DEBUG_ALWAYS, LOG_WARNING, "Can't set UDP SO_RCVBUF to %i: %s", udp_rcvbuf, strerror(errno));
  
        if(udp_sndbuf && setsockopt(nfd, SOL_SOCKET, SO_SNDBUF, (void *)&udp_sndbuf, sizeof(udp_sndbuf)))
 -              logger(LOG_WARNING, "Can't set UDP SO_SNDBUF to %i: %s", udp_sndbuf, strerror(errno));
 +              logger(DEBUG_ALWAYS, LOG_WARNING, "Can't set UDP SO_SNDBUF to %i: %s", udp_sndbuf, strerror(errno));
  
  #if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY)
        if(sa->sa.sa_family == AF_INET6)
        if(bind(nfd, &sa->sa, SALEN(sa->sa))) {
                closesocket(nfd);
                addrstr = sockaddr2hostname(sa);
 -              logger(LOG_ERR, "Can't bind to %s/udp: %s", addrstr, sockstrerror(sockerrno));
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to %s/udp: %s", addrstr, sockstrerror(sockerrno));
                free(addrstr);
                return -1;
        }
        return nfd;
  } /* int setup_vpn_in_socket */
  
 +static void retry_outgoing_handler(int fd, short events, void *data) {
 +      setup_outgoing_connection(data);
 +}
 +
  void retry_outgoing(outgoing_t *outgoing) {
        outgoing->timeout += 5;
  
        if(outgoing->timeout > maxtimeout)
                outgoing->timeout = maxtimeout;
  
 -      if(outgoing->event)
 -              event_del(outgoing->event);
 -      outgoing->event = new_event();
 -      outgoing->event->handler = (event_handler_t) setup_outgoing_connection;
 -      outgoing->event->time = now + outgoing->timeout;
 -      outgoing->event->data = outgoing;
 -      event_add(outgoing->event);
 +      timeout_set(&outgoing->ev, retry_outgoing_handler, outgoing);
 +      event_add(&outgoing->ev, &(struct timeval){outgoing->timeout, 0});
  
 -      ifdebug(CONNECTIONS) logger(LOG_NOTICE,
 +      logger(DEBUG_CONNECTIONS, LOG_NOTICE,
                           "Trying to re-establish outgoing connection in %d seconds",
                           outgoing->timeout);
  }
  
  void finish_connecting(connection_t *c) {
 -      ifdebug(CONNECTIONS) logger(LOG_INFO, "Connected to %s (%s)", c->name, c->hostname);
 +      logger(DEBUG_CONNECTIONS, LOG_INFO, "Connected to %s (%s)", c->name, c->hostname);
  
-       configure_tcp(c);
+       if(proxytype != PROXY_EXEC)
+               configure_tcp(c);
  
 -      c->last_ping_time = now;
 +      c->last_ping_time = time(NULL);
 +      c->status.connecting = false;
  
        send_id(c);
  }
  
 -              logger(LOG_ERR, "Could not create socketpair: %s\n", strerror(errno));
+ static void do_outgoing_pipe(connection_t *c, char *command) {
+ #ifndef HAVE_MINGW
+       int fd[2];
+       if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) {
 -              logger(LOG_DEBUG, "Using proxy %s", command);
++              logger(DEBUG_ALWAYS, LOG_ERR, "Could not create socketpair: %s\n", strerror(errno));
+               return;
+       }
+       if(fork()) {
+               c->socket = fd[0];
+               close(fd[1]);
 -              logger(LOG_ERR, "Could not execute %s: %s\n", command, strerror(errno));
++              logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Using proxy %s", command);
+               return;
+       }
+       close(0);
+       close(1);
+       close(fd[0]);
+       dup2(fd[1], 0);
+       dup2(fd[1], 1);
+       close(fd[1]);
+       // Other filedescriptors should be closed automatically by CLOEXEC
+       char *host = NULL;
+       char *port = NULL;
+       sockaddr2str(&c->address, &host, &port);
+       setenv("REMOTEADDRESS", host, true);
+       setenv("REMOTEPORT", port, true);
+       setenv("NODE", c->name, true);
+       setenv("NAME", myself->name, true);
+       if(netname)
+               setenv("NETNAME", netname, true);
+       int result = system(command);
+       if(result < 0)
 -              logger(LOG_ERR, "%s exited with non-zero status %d", command, result);
++              logger(DEBUG_ALWAYS, LOG_ERR, "Could not execute %s: %s\n", command, strerror(errno));
+       else if(result)
 -      logger(LOG_ERR, "Proxy type exec not supported on this platform!");
++              logger(DEBUG_ALWAYS, LOG_ERR, "%s exited with non-zero status %d", command, result);
+       exit(result);
+ #else
 -void do_outgoing_connection(connection_t *c) {
++      logger(DEBUG_ALWAYS, LOG_ERR, "Proxy type exec not supported on this platform!");
+       return;
+ #endif
+ }
 +bool do_outgoing_connection(connection_t *c) {
        char *address, *port, *space;
+       struct addrinfo *proxyai = NULL;
        int result;
  
        if(!c->outgoing) {
 -              logger(LOG_ERR, "do_outgoing_connection() for %s called without c->outgoing", c->name);
 +              logger(DEBUG_ALWAYS, LOG_ERR, "do_outgoing_connection() for %s called without c->outgoing", c->name);
                abort();
        }
  
  begin:
        if(!c->outgoing->ai) {
                if(!c->outgoing->cfg) {
 -                      ifdebug(CONNECTIONS) logger(LOG_ERR, "Could not set up a meta connection to %s",
 +                      logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not set up a meta connection to %s",
                                           c->name);
 -                      c->status.remove = true;
                        retry_outgoing(c->outgoing);
                        c->outgoing = NULL;
 -                      return;
 +                      connection_del(c);
 +                      return false;
                }
  
                get_config_string(c->outgoing->cfg, &address);
  
        c->hostname = sockaddr2hostname(&c->address);
  
 -      ifdebug(CONNECTIONS) logger(LOG_INFO, "Trying to connect to %s (%s)", c->name,
 +      logger(DEBUG_CONNECTIONS, LOG_INFO, "Trying to connect to %s (%s)", c->name,
                           c->hostname);
  
-       c->socket = socket(c->address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP);
- #ifdef FD_CLOEXEC
-       fcntl(c->socket, F_SETFD, FD_CLOEXEC);
- #endif
+       if(!proxytype) {
+               c->socket = socket(c->address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP);
+               configure_tcp(c);
+       } else if(proxytype == PROXY_EXEC) {
+               do_outgoing_pipe(c, proxyhost);
+       } else {
+               proxyai = str2addrinfo(proxyhost, proxyport, SOCK_STREAM);
+               if(!proxyai)
+                       goto begin;
 -              ifdebug(CONNECTIONS) logger(LOG_INFO, "Using proxy at %s port %s", proxyhost, proxyport);
++              logger(DEBUG_CONNECTIONS, LOG_INFO, "Using proxy at %s port %s", proxyhost, proxyport);
+               c->socket = socket(proxyai->ai_family, SOCK_STREAM, IPPROTO_TCP);
+       }
  
        if(c->socket == -1) {
 -              ifdebug(CONNECTIONS) logger(LOG_ERR, "Creating socket for %s failed: %s", c->hostname, sockstrerror(sockerrno));
 +              logger(DEBUG_CONNECTIONS, LOG_ERR, "Creating socket for %s failed: %s", c->hostname, sockstrerror(sockerrno));
                goto begin;
        }
  
- #if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
-       int option = 1;
-       if(c->address.sa.sa_family == AF_INET6)
-               setsockopt(c->socket, SOL_IPV6, IPV6_V6ONLY, (void *)&option, sizeof option);
+ #ifdef FD_CLOEXEC
+       fcntl(c->socket, F_SETFD, FD_CLOEXEC);
  #endif
  
-       bind_to_interface(c->socket);
-       /* Optimize TCP settings */
+       if(proxytype != PROXY_EXEC) {
+ #if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
+               int option = 1;
+               if(c->address.sa.sa_family == AF_INET6)
+                       setsockopt(c->socket, SOL_IPV6, IPV6_V6ONLY, (void *)&option, sizeof option);
+ #endif
  
-       configure_tcp(c);
+               bind_to_interface(c->socket);
+       }
  
        /* Connect */
  
-       result = connect(c->socket, &c->address.sa, SALEN(c->address.sa));
+       if(!proxytype) {
+               result = connect(c->socket, &c->address.sa, SALEN(c->address.sa));
+       } else if(proxytype == PROXY_EXEC) {
+               result = 0;
+       } else {
+               result = connect(c->socket, proxyai->ai_addr, proxyai->ai_addrlen);
+               freeaddrinfo(proxyai);
+       }
  
        if(result == -1) {
                if(sockinprogress(sockerrno)) {
                        c->status.connecting = true;
 -                      return;
 +                      return true;
                }
  
                closesocket(c->socket);
  
 -              ifdebug(CONNECTIONS) logger(LOG_ERR, "%s: %s", c->hostname, sockstrerror(sockerrno));
 +              logger(DEBUG_CONNECTIONS, LOG_ERR, "%s: %s", c->hostname, sockstrerror(sockerrno));
  
                goto begin;
        }
  
        finish_connecting(c);
  
 -      return;
 +      return true;
 +}
 +
 +static void handle_meta_write(int sock, short events, void *data) {
 +      connection_t *c = data;
 +
 +      ssize_t outlen = send(c->socket, c->outbuf.data + c->outbuf.offset, c->outbuf.len - c->outbuf.offset, 0);
 +      if(outlen <= 0) {
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Onoes, outlen = %d (%s)", (int)outlen, strerror(errno));
 +              terminate_connection(c, c->status.active);
 +              return;
 +      }
 +
 +      buffer_read(&c->outbuf, outlen);
 +      if(!c->outbuf.len && event_initialized(&c->outevent))
 +              event_del(&c->outevent);
  }
  
  void setup_outgoing_connection(outgoing_t *outgoing) {
        connection_t *c;
        node_t *n;
  
 -      outgoing->event = NULL;
 +      if(event_initialized(&outgoing->ev))
 +              event_del(&outgoing->ev);
  
        n = lookup_node(outgoing->name);
  
        if(n)
                if(n->connection) {
 -                      ifdebug(CONNECTIONS) logger(LOG_INFO, "Already connected to %s", outgoing->name);
 +                      logger(DEBUG_CONNECTIONS, LOG_INFO, "Already connected to %s", outgoing->name);
  
                        n->connection->outgoing = outgoing;
                        return;
        outgoing->cfg = lookup_config(c->config_tree, "Address");
  
        if(!outgoing->cfg) {
 -              logger(LOG_ERR, "No address specified for %s", c->name);
 +              logger(DEBUG_ALWAYS, LOG_ERR, "No address specified for %s", c->name);
                free_connection(c);
                return;
        }
  
        c->outgoing = outgoing;
 -      c->last_ping_time = now;
 +      c->last_ping_time = time(NULL);
  
        connection_add(c);
  
 -      do_outgoing_connection(c);
 +      if (do_outgoing_connection(c)) {
 +              event_set(&c->inevent, c->socket, EV_READ | EV_PERSIST, handle_meta_connection_data, c);
 +              event_set(&c->outevent, c->socket, EV_WRITE | EV_PERSIST, handle_meta_write, c);
 +              event_add(&c->inevent, NULL);
 +      }
  }
  
  /*
    accept a new tcp connect and create a
    new connection
  */
 -bool handle_new_meta_connection(int sock) {
 +void handle_new_meta_connection(int sock, short events, void *data) {
        connection_t *c;
        sockaddr_t sa;
        int fd;
 -      socklen_t len = sizeof(sa);
 +      socklen_t len = sizeof sa;
  
        fd = accept(sock, &sa.sa, &len);
  
        if(fd < 0) {
 -              logger(LOG_ERR, "Accepting a new connection failed: %s", sockstrerror(sockerrno));
 -              return false;
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Accepting a new connection failed: %s", sockstrerror(sockerrno));
 +              return;
        }
  
        sockaddrunmap(&sa);
        c->address = sa;
        c->hostname = sockaddr2hostname(&sa);
        c->socket = fd;
 -      c->last_ping_time = now;
 +      c->last_ping_time = time(NULL);
  
 -      ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Connection from %s", c->hostname);
 +      logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection from %s", c->hostname);
  
 +      event_set(&c->inevent, c->socket, EV_READ | EV_PERSIST, handle_meta_connection_data, c);
 +      event_set(&c->outevent, c->socket, EV_WRITE | EV_PERSIST, handle_meta_write, c);
 +      event_add(&c->inevent, NULL);
 +              
        configure_tcp(c);
  
        connection_add(c);
  
        c->allow_request = ID;
        send_id(c);
 -
 -      return true;
  }
  
  static void free_outgoing(outgoing_t *outgoing) {
@@@ -530,14 -575,14 +596,14 @@@ void try_outgoing_connections(void) 
                get_config_string(cfg, &name);
  
                if(!check_id(name)) {
 -                      logger(LOG_ERR,
 +                      logger(DEBUG_ALWAYS, LOG_ERR,
                                   "Invalid name for outgoing connection in %s line %d",
                                   cfg->file, cfg->line);
                        free(name);
                        continue;
                }
  
 -              outgoing = xmalloc_and_zero(sizeof(*outgoing));
 +              outgoing = xmalloc_and_zero(sizeof *outgoing);
                outgoing->name = name;
                list_insert_tail(outgoing_list, outgoing);
                setup_outgoing_connection(outgoing);
diff --combined src/netutl.c
index 9d848dc49b172980748aee965774e752e9964ec9,c57b24ff1e2dd86a554b24e63187b3885ca0a164..340a2da9641d706ee2152c5b1c681e4a05f6bcdf
@@@ -42,7 -42,7 +42,7 @@@ struct addrinfo *str2addrinfo(const cha
        err = getaddrinfo(address, service, &hint, &ai);
  
        if(err) {
 -              logger(LOG_WARNING, "Error looking up %s port %s: %s", address,
 +              logger(DEBUG_ALWAYS, LOG_WARNING, "Error looking up %s port %s: %s", address,
                                   service, gai_strerror(err));
                return NULL;
        }
@@@ -62,7 -62,8 +62,7 @@@ sockaddr_t str2sockaddr(const char *add
        err = getaddrinfo(address, port, &hint, &ai);
  
        if(err || !ai) {
 -              ifdebug(SCARY_THINGS)
 -                      logger(LOG_DEBUG, "Unknown type address %s port %s", address, port);
 +              logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Unknown type address %s port %s", address, port);
                result.sa.sa_family = AF_UNKNOWN;
                result.unknown.address = xstrdup(address);
                result.unknown.port = xstrdup(port);
@@@ -82,15 -83,17 +82,17 @@@ void sockaddr2str(const sockaddr_t *sa
        int err;
  
        if(sa->sa.sa_family == AF_UNKNOWN) {
-               *addrstr = xstrdup(sa->unknown.address);
-               *portstr = xstrdup(sa->unknown.port);
+               if(addrstr)
+                       *addrstr = xstrdup(sa->unknown.address);
+               if(portstr)
+                       *portstr = xstrdup(sa->unknown.port);
                return;
        }
  
 -      err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof(address), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV);
 +      err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof address, port, sizeof port, NI_NUMERICHOST | NI_NUMERICSERV);
  
        if(err) {
 -              logger(LOG_ERR, "Error while translating addresses: %s",
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Error while translating addresses: %s",
                           gai_strerror(err));
                abort();
        }
@@@ -117,10 -120,10 +119,10 @@@ char *sockaddr2hostname(const sockaddr_
                return str;
        }
  
 -      err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof(address), port, sizeof(port),
 +      err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof address, port, sizeof port,
                                        hostnames ? 0 : (NI_NUMERICHOST | NI_NUMERICSERV));
        if(err) {
 -              logger(LOG_ERR, "Error while looking up hostname: %s",
 +              logger(DEBUG_ALWAYS, LOG_ERR, "Error while looking up hostname: %s",
                           gai_strerror(err));
        }
  
@@@ -151,7 -154,7 +153,7 @@@ int sockaddrcmp_noport(const sockaddr_
                        return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr));
  
                default:
 -                      logger(LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!",
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!",
                                   a->sa.sa_family);
                        abort();
        }
@@@ -178,23 -181,23 +180,23 @@@ int sockaddrcmp(const sockaddr_t *a, co
                        return strcmp(a->unknown.port, b->unknown.port);
  
                case AF_INET:
 -                      result = memcmp(&a->in.sin_addr, &b->in.sin_addr, sizeof(a->in.sin_addr));
 +                      result = memcmp(&a->in.sin_addr, &b->in.sin_addr, sizeof a->in.sin_addr);
  
                        if(result)
                                return result;
  
 -                      return memcmp(&a->in.sin_port, &b->in.sin_port, sizeof(a->in.sin_port));
 +                      return memcmp(&a->in.sin_port, &b->in.sin_port, sizeof a->in.sin_port);
  
                case AF_INET6:
 -                      result = memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr));
 +                      result = memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof a->in6.sin6_addr);
  
                        if(result)
                                return result;
  
 -                      return memcmp(&a->in6.sin6_port, &b->in6.sin6_port, sizeof(a->in6.sin6_port));
 +                      return memcmp(&a->in6.sin6_port, &b->in6.sin6_port, sizeof a->in6.sin6_port);
  
                default:
 -                      logger(LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!",
 +                      logger(DEBUG_ALWAYS, LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!",
                                   a->sa.sa_family);
                        abort();
        }
diff --combined src/protocol.c
index 1e63f2ea2e2ee1e28a4f81673f6fc8d15222aa14,f36538e382899ca4f4d3a27ff4267899fb64f185..1c5b6cfd9c1d43dba0c6a2e21defabdb4c431209
  
  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 *, const 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 = 0;
 +      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, const char *request) {
 +      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);
 +      // Create a temporary newline-terminated copy of the request
 +      int len = strlen(request);
 +      char tmp[len + 1];
 +      memcpy(tmp, request, len);
 +      tmp[len] = '\n';
 +      broadcast_meta(from, tmp, sizeof tmp);
  }
  
 -bool receive_request(connection_t *c) {
 -      int request;
 -
 +bool receive_request(connection_t *c, const char *request) {
+       if(proxytype == PROXY_HTTP && c->allow_request == ID) {
 -              if(!c->buffer[0] || c->buffer[0] == '\r')
++              if(!request[0] || request[0] == '\r')
+                       return true;
 -              if(!strncasecmp(c->buffer, "HTTP/1.1 ", 9)) {
 -                      if(!strncmp(c->buffer + 9, "200", 3)) {
 -                              logger(LOG_DEBUG, "Proxy request granted");
++              if(!strncasecmp(request, "HTTP/1.1 ", 9)) {
++                      if(!strncmp(request + 9, "200", 3)) {
++                              logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Proxy request granted");
+                               return true;
+                       } else {
 -                              logger(LOG_DEBUG, "Proxy request rejected: %s", c->buffer + 9);
++                              logger(DEBUG_ALWAYS, LOG_DEBUG, "Proxy request rejected: %s", request + 9);
+                               return false;
+                       }
+               }
+       }
 -      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);
 +      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;
        }
  
@@@ -143,64 -189,52 +157,64 @@@ static int past_request_compare(const p
  
  static void free_past_request(past_request_t *r) {
        if(r->request)
 -              free(r->request);
 +              free((char *)r->request);
  
        free(r);
  }
  
 -void init_requests(void) {
 -      past_request_tree = avl_alloc_tree((avl_compare_t) past_request_compare, (avl_action_t) free_past_request);
 -}
 +static struct event past_request_event;
  
 -void exit_requests(void) {
 -      avl_delete_tree(past_request_tree);
 -}
 -
 -bool seen_request(char *request) {
 +bool seen_request(const 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 057b88e545939e2b27c6946567768ee54f968807,4c721a44982f3b52ef75a016f56563202d779eab..ccb7976c2700813131a62206f8cd7d3d1443db75
  
  #include "system.h"
  
 -#include <openssl/sha.h>
 -#include <openssl/rand.h>
 -#include <openssl/err.h>
 -#include <openssl/evp.h>
 -
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "conf.h"
  #include "connection.h"
 +#include "control.h"
 +#include "control_common.h"
 +#include "cipher.h"
 +#include "crypto.h"
 +#include "digest.h"
  #include "edge.h"
  #include "graph.h"
  #include "logger.h"
  #include "net.h"
  #include "netutl.h"
  #include "node.h"
 +#include "prf.h"
  #include "protocol.h"
 +#include "rsa.h"
 +#include "sptps.h"
  #include "utils.h"
  #include "xalloc.h"
  
 -                              logger(LOG_ERR, "Cannot connect to an IPv6 host through a SOCKS 4 proxy!");
+ static bool send_proxyrequest(connection_t *c) {
+       switch(proxytype) {
+               case PROXY_HTTP: {
+                       char *host;
+                       char *port;
+                       sockaddr2str(&c->address, &host, &port);
+                       send_request(c, "CONNECT %s:%s HTTP/1.1\r\n\r", host, port);
+                       free(host);
+                       free(port);
+                       return true;
+               }
+               case PROXY_SOCKS4: {
+                       if(c->address.sa.sa_family != AF_INET) {
 -                              logger(LOG_ERR, "Address family %hx not supported for SOCKS 5 proxies!", c->address.sa.sa_family);
++                              logger(DEBUG_ALWAYS, LOG_ERR, "Cannot connect to an IPv6 host through a SOCKS 4 proxy!");
+                               return false;
+                       }
+                       char s4req[9 + (proxyuser ? strlen(proxyuser) : 0)];
+                       s4req[0] = 4;
+                       s4req[1] = 1;
+                       memcpy(s4req + 2, &c->address.in.sin_port, 2);
+                       memcpy(s4req + 4, &c->address.in.sin_addr, 4);
+                       if(proxyuser)
+                               strcpy(s4req + 8, proxyuser);
+                       s4req[sizeof s4req - 1] = 0;
+                       c->tcplen = 8;
+                       return send_meta(c, s4req, sizeof s4req);
+               }
+               case PROXY_SOCKS5: {
+                       int len = 3 + 6 + (c->address.sa.sa_family == AF_INET ? 4 : 16);
+                       c->tcplen = 2;
+                       if(proxypass)
+                               len += 3 + strlen(proxyuser) + strlen(proxypass);
+                       char s5req[len];
+                       int i = 0;
+                       s5req[i++] = 5;
+                       s5req[i++] = 1;
+                       if(proxypass) {
+                               s5req[i++] = 2;
+                               s5req[i++] = 1;
+                               s5req[i++] = strlen(proxyuser);
+                               strcpy(s5req + i, proxyuser);
+                               i += strlen(proxyuser);
+                               s5req[i++] = strlen(proxypass);
+                               strcpy(s5req + i, proxypass);
+                               i += strlen(proxypass);
+                               c->tcplen += 2;
+                       } else {
+                               s5req[i++] = 0;
+                       }
+                       s5req[i++] = 5;
+                       s5req[i++] = 1;
+                       s5req[i++] = 0;
+                       if(c->address.sa.sa_family == AF_INET) {
+                               s5req[i++] = 1;
+                               memcpy(s5req + i, &c->address.in.sin_addr, 4);
+                               i += 4;
+                               memcpy(s5req + i, &c->address.in.sin_port, 2);
+                               i += 2;
+                               c->tcplen += 10;
+                       } else if(c->address.sa.sa_family == AF_INET6) {
+                               s5req[i++] = 3;
+                               memcpy(s5req + i, &c->address.in6.sin6_addr, 16);
+                               i += 16;
+                               memcpy(s5req + i, &c->address.in6.sin6_port, 2);
+                               i += 2;
+                               c->tcplen += 22;
+                       } else {
 -                      logger(LOG_ERR, "Proxy type not implemented yet");
++                              logger(DEBUG_ALWAYS, LOG_ERR, "Address family %hx not supported for SOCKS 5 proxies!", c->address.sa.sa_family);
+                               return false;
+                       }
+                       if(i > len)
+                               abort();
+                       return send_meta(c, s5req, sizeof s5req);
+               }
+               case PROXY_SOCKS4A:
 -                      logger(LOG_ERR, "Unknown proxy type");
++                      logger(DEBUG_ALWAYS, LOG_ERR, "Proxy type not implemented yet");
+                       return false;
+               case PROXY_EXEC:
+                       return true;
+               default:
++                      logger(DEBUG_ALWAYS, LOG_ERR, "Unknown proxy type");
+                       return false;
+       }
+ }
  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;
 +      }
 +
+       if(proxytype)
+               if(!send_proxyrequest(c))
+                       return false;
 +      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, const 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;
 +
 +              free(c->name);
 +                c->name = xstrdup("<control>");
 +
 +              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;
        }
  
        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;
                }
  
        /* 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;
  
 -      ifdebug(SCARY_THINGS) {
 -              bin2hex(c->outkey, buffer, len);
 -              buffer[len * 2] = '\0';
 -              logger(LOG_DEBUG, "Generated random meta key (unencrypted): %s",
 -                         buffer);
 +      cipher_set_key_from_rsa(&c->outcipher, key, len, true);
 +
 +      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, const 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 */
 +      size_t len = rsa_size(&c->rsa);
 +      char buffer[len * 2 + 1];
  
 -      char buffer[2 * len + 1];
 -
 -      c->hischallenge = xrealloc(c->hischallenge, len);
 +      if(!c->hischallenge)
 +              c->hischallenge = xrealloc(c->hischallenge, len);
  
        /* Copy random data to the buffer */
  
 -      RAND_pseudo_bytes((unsigned char *)c->hischallenge, len);
 +      randomize(c->hischallenge, len);
  
        /* Convert to hex */
  
        bin2hex(c->hischallenge, buffer, len);
 -      buffer[len * 2] = '\0';
  
        /* Send the challenge */
  
        return send_request(c, "%d %s", CHALLENGE, buffer);
  }
  
 -bool challenge_h(connection_t *c) {
 +bool challenge_h(connection_t *c, const 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, const 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, const 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, const 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/route.c
index 5bf6e926724a664618b9fe0707125dc3318ddd87,74ad9a3469d335fa13e7472c0d3c5b13ef734834..4c4312cd01a7729b3fa56fff7f65b224cd650d94
  
  #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;
+ bmode_t broadcast_mode = BMODE_MST;
  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);
@@@ -447,7 -430,7 +447,7 @@@ static void route_ipv4(node_t *source, 
        if(!checklength(source, packet, ether_size + ip_size))
                return;
  
-       if(broadcast && (((packet->data[30] & 0xf0) == 0xe0) || (
+       if(broadcast_mode && (((packet->data[30] & 0xf0) == 0xe0) || (
                        packet->data[30] == 255 &&
                        packet->data[31] == 255 &&
                        packet->data[32] == 255 &&
@@@ -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);
@@@ -744,7 -727,7 +744,7 @@@ static void route_ipv6(node_t *source, 
                return;
        }
  
-       if(broadcast && packet->data[38] == 255)
+       if(broadcast_mode && packet->data[38] == 255)
                broadcast_packet(source, packet);
        else
                route_ipv6_unicast(source, packet);
@@@ -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 */
@@@ -834,13 -817,12 +834,12 @@@ static void route_mac(node_t *source, v
        subnet = lookup_subnet_mac(NULL, &dest);
  
        if(!subnet) {
-               if(broadcast)
-                       broadcast_packet(source, packet);
+               broadcast_packet(source, packet);
                return;
        }
  
        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 46dc3bdb44e2cfe1cb39d96a72d125c9892652c0,7b45e76a686df0ba90a93543c601dbd9b5e81eca..6f4a4e58317daecc04e9b6182f3f0c77a88a6cc9
@@@ -36,18 -36,24 +36,24 @@@ typedef enum fmode_t 
        FMODE_KERNEL,
  } fmode_t;
  
+ typedef enum bmode_t {
+       BMODE_NONE = 0,
+       BMODE_MST,
+       BMODE_DIRECT,
+ } bmode_t;
  extern rmode_t routing_mode;
  extern fmode_t forwarding_mode;
+ extern bmode_t broadcast_mode;
  extern bool decrement_ttl;
  extern bool directonly;
  extern bool overwrite_mac;
- extern bool broadcast;
  extern bool priorityinheritance;
  extern int macexpire;
 +extern bool pcap;
  
  extern mac_t mymac;
  
 -extern void age_subnets(void);
  extern void route(struct node_t *, struct vpn_packet_t *);
  
  #endif                                                        /* __TINC_ROUTE_H__ */
diff --combined src/utils.c
index cf46221280cd42113a265e2e17dcad2abf5bf61b,0000000000000000000000000000000000000000..e750450e8bc3cf203843378e99f123a7681f86f3
mode 100644,000000..100644
--- /dev/null
@@@ -1,160 -1,0 +1,162 @@@
-       static char buf[1024], *newline;
 +/*
 +    utils.c -- gathering of some stupid small functions
 +    Copyright (C) 1999-2005 Ivo Timmermans
 +                  2000-2009 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 "../src/logger.h"
 +#include "utils.h"
 +
 +static const char hexadecimals[] = "0123456789ABCDEF";
 +static const char base64imals[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 +
 +static int charhex2bin(char c) {
 +      if(isdigit(c))
 +              return c - '0';
 +      else
 +              return toupper(c) - 'A' + 10;
 +}
 +
 +static int charb64decode(char c) {
 +      if(c >= 'a')
 +              return c - 'a' + 26;
 +      else if(c >= 'A')
 +              return c - 'A';
 +      else if(c >= '0') 
 +              return c - '0' + 52;
 +      else if(c == '+')
 +              return 62;
 +      else
 +              return 63;
 +}
 +
 +int hex2bin(const char *src, char *dst, int length) {
 +      int i;
 +      for(i = 0; i < length && src[i * 2] && src[i * 2 + 1]; i++)
 +              dst[i] = charhex2bin(src[i * 2]) * 16 + charhex2bin(src[i * 2 + 1]);
 +      return i;
 +}
 +
 +int bin2hex(const char *src, char *dst, int length) {
 +      int i;
 +      for(i = length - 1; i >= 0; i--) {
 +              dst[i * 2 + 1] = hexadecimals[(unsigned char) src[i] & 15];
 +              dst[i * 2] = hexadecimals[(unsigned char) src[i] >> 4];
 +      }
 +      dst[length * 2] = 0;
 +      return length * 2;
 +}
 +
 +int b64decode(const char *src, char *dst, int length) {
 +      int i;
 +      uint32_t triplet = 0;
 +      unsigned char *udst = (unsigned char *)dst;
 +
 +      for(i = 0; i < length / 3 * 4 && src[i]; i++) {
 +              triplet |= charb64decode(src[i]) << (6 * (i & 3));
 +              if((i & 3) == 3) {
 +                      udst[0] = triplet & 0xff; triplet >>= 8;
 +                      udst[1] = triplet & 0xff; triplet >>= 8;
 +                      udst[2] = triplet;
 +                      triplet = 0;
 +                      udst += 3;
 +              }
 +      }
 +      if((i & 3) == 3) {
 +              udst[0] = triplet & 0xff; triplet >>= 8;
 +              udst[1] = triplet & 0xff;
 +              return i / 4 * 3 + 2;
 +      } else if((i & 3) == 2) {
 +              udst[0] = triplet & 0xff;
 +              return i / 4 * 3 + 1;
 +      } else {
 +              return i / 4 * 3;
 +      }
 +}
 +
 +int b64encode(const char *src, char *dst, int length) {
 +      uint32_t triplet;
 +      const unsigned char *usrc = (unsigned char *)src;
 +      int si = length / 3 * 3;
 +      int di = length / 3 * 4;
 +
 +      switch(length % 3) {
 +              case 2: 
 +                      triplet = usrc[si] | usrc[si + 1] << 8;
 +                      dst[di] = base64imals[triplet & 63]; triplet >>= 6;
 +                      dst[di + 1] = base64imals[triplet & 63]; triplet >>= 6;
 +                      dst[di + 2] = base64imals[triplet];
 +                      dst[di + 3] = 0;
 +                      length = di + 2;
 +                      break;
 +              case 1:
 +                      triplet = usrc[si];
 +                      dst[di] = base64imals[triplet & 63]; triplet >>= 6;
 +                      dst[di + 1] = base64imals[triplet];
 +                      dst[di + 2] = 0;
 +                      length = di + 1;
 +                      break;
 +              default:
 +                      dst[di] = 0;
 +                      length = di;
 +                      break;
 +      }
 +
 +      while(si > 0) {
 +              di -= 4;
 +              si -= 3;
 +              triplet = usrc[si] | usrc[si + 1] << 8 | usrc[si + 2] << 16;
 +              dst[di] = base64imals[triplet & 63]; triplet >>= 6;
 +              dst[di + 1] = base64imals[triplet & 63]; triplet >>= 6;
 +              dst[di + 2] = base64imals[triplet & 63]; triplet >>= 6;
 +              dst[di + 3] = base64imals[triplet];
 +      }
 +
 +      return length;
 +}
 +
 +#if defined(HAVE_MINGW) || defined(HAVE_CYGWIN)
 +#ifdef HAVE_CYGWIN
 +#include <w32api/windows.h>
 +#endif
 +
 +const char *winerror(int err) {
-               NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, sizeof(buf), NULL)) {
++      static char buf[1024], *ptr;
++
++      ptr = buf + sprintf(buf, "(%d) ", err);
 +
 +      if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
-       if((newline = strchr(buf, '\r')))
-               *newline = '\0';
++              NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), ptr, sizeof(buf) - (ptr - buf), NULL)) {
 +              strncpy(buf, "(unable to format errormessage)", sizeof(buf));
 +      };
 +
++      if((ptr = strchr(buf, '\r')))
++              *ptr = '\0';
 +
 +      return buf;
 +}
 +#endif
 +
 +unsigned int bitfield_to_int(const void *bitfield, size_t size) {
 +      unsigned int value = 0;
 +      if(size > sizeof value)
 +              size = sizeof value;
 +      memcpy(&value, bitfield, size);
 +      return value;
 +}