]> git.meshlink.io Git - meshlink/commitdiff
Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1
authorGuus Sliepen <guus@tinc-vpn.org>
Thu, 23 Feb 2012 12:26:01 +0000 (13:26 +0100)
committerGuus Sliepen <guus@tinc-vpn.org>
Thu, 23 Feb 2012 12:26:01 +0000 (13:26 +0100)
Conflicts:
src/net.c
src/net_packet.c
src/net_socket.c

1  2 
doc/tinc.conf.5.in
doc/tinc.texi
src/graph.c
src/net.h
src/net_packet.c
src/net_setup.c
src/net_socket.c
src/node.h

diff --combined doc/tinc.conf.5.in
index 47cf82e5c7c0d06817f8fffe9ba09f671bd60ef8,a6ae4f5ac3ee8d4355fff4d7814138506a908c1f..99b987772ad0157ded52790323888eb1c1bd1000
@@@ -261,21 -261,6 +261,21 @@@ but which would have to be forwarded b
  When combined with the IndirectData option,
  packets for nodes for which we do not have a meta connection with are also dropped.
  
 +.It Va ECDSAPrivateKeyFile Li = Ar filename Po Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /ecdsa_key.priv Pc
 +The file in which the private ECDSA key of this tinc daemon resides.
 +This is only used if
 +.Va ExperimentalProtocol
 +is enabled.
 +
 +.It Va ExperimentalProtocol Li = yes | no Po no Pc Bq experimental
 +When this option is enabled, experimental protocol enhancements will be used.
 +Ephemeral ECDH will be used for key exchanges,
 +and ECDSA will be used instead of RSA for authentication.
 +When enabled, an ECDSA key must have been generated before with
 +.Nm tincctl generate-ecdsa-keys .
 +The experimental protocol may change at any time,
 +and there is no guarantee that tinc will run stable when it is used.
 +
  .It Va Forwarding Li = off | internal | kernel Po internal Pc Bq experimental
  This option selects the way indirect packets are forwarded.
  .Bl -tag -width indent
@@@ -335,6 -320,18 +335,18 @@@ This option controls the period the enc
  It is common practice to change keys at regular intervals to make it even harder for crackers,
  even though it is thought to be nearly impossible to crack a single key.
  
+ .It Va LocalDiscovery Li = yes | no Po no Pc Bq experimental
+ When enabled,
+ .Nm tinc
+ will try to detect peers that are on the same local network.
+ This will allow direct communication using LAN addresses, even if both peers are behind a NAT
+ and they only ConnectTo a third node outside the NAT,
+ which normally would prevent the peers from learning each other's LAN address.
+ .Pp
+ Currently, local discovery is implemented by sending broadcast packets to the LAN during path MTU discovery.
+ This feature may not work in all possible situations.
  .It Va MACExpire Li = Ar seconds Pq 600
  This option controls the amount of time MAC addresses are kept before they are removed.
  This only has effect when
diff --combined doc/tinc.texi
index 4d9f233ac7a1c4e6ad6073ea3b9342dd2ce3f2a6,0697f103bedbbba1030079d33eaf6fb726e3152d..0c470123ae47168ba5d039d966791263114b1354
@@@ -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
@@@ -899,21 -876,6 +899,21 @@@ but which would have to be forwarded b
  When combined with the IndirectData option,
  packets for nodes for which we do not have a meta connection with are also dropped.
  
 +@cindex ECDSAPrivateKeyFile
 +@item ECDSAPrivateKeyFile = <@var{path}> (@file{@value{sysconfdir}/tinc/@var{netname}/ecdsa_key.priv})
 +The file in which the private ECDSA key of this tinc daemon resides.
 +This is only used if ExperimentalProtocol is enabled.
 +
 +@cindex ExperimentalProtocol
 +@item ExperimentalProtocol = <yes|no> (no) [experimental]
 +When this option is enabled, experimental protocol enhancements will be used.
 +Ephemeral ECDH will be used for key exchanges,
 +and ECDSA will be used instead of RSA for authentication.
 +When enabled, an ECDSA key must have been generated before with
 +@samp{tincctl generate-ecdsa-keys}.
 +The experimental protocol may change at any time,
 +and there is no guarantee that tinc will run stable when it is used.
 +
  @cindex Forwarding
  @item Forwarding = <off|internal|kernel> (internal) [experimental]
  This option selects the way indirect packets are forwarded.
@@@ -961,6 -923,16 +961,16 @@@ Depending on the operating system and t
  Under Windows, this variable is used to select which network interface will be used.
  If you specified a Device, this variable is almost always already correctly set.
  
+ @cindex LocalDiscovery
+ @item LocalDiscovery = <yes | no> (no) [experimental]
+ When enabled, tinc will try to detect peers that are on the same local network.
+ This will allow direct communication using LAN addresses, even if both peers are behind a NAT
+ and they only ConnectTo a third node outside the NAT,
+ which normally would prevent the peers from learning each other's LAN address.
+ Currently, local discovery is implemented by sending broadcast packets to the LAN during path MTU discovery.
+ This feature may not work in all possible situations.
  @cindex Mode
  @item Mode = <router|switch|hub> (router)
  This option selects the way packets are routed to other daemons.
@@@ -1032,7 -1004,7 +1042,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
@@@ -1154,7 -1126,7 +1164,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
@@@ -1190,6 -1162,7 +1200,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,
@@@ -1197,12 -1170,15 +1207,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
  
  
@@@ -1291,6 -1267,10 +1301,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
  
  
@@@ -1337,7 -1317,7 +1347,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.
@@@ -1566,7 -1546,7 +1576,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},
@@@ -1632,6 -1612,12 +1642,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}.
@@@ -1658,6 -1645,9 +1668,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.
@@@ -1711,6 -1701,19 +1721,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 ==================================================================
@@@ -1774,7 -1777,7 +1784,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
  
@@@ -1873,8 -1876,6 +1883,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)
@@@ -1905,198 -1906,6 +1915,198 @@@ Be sure to include the following inform
  @item The output of any command that fails to work as it should (like ping or traceroute).
  @end itemize
  
 +@c ==================================================================
 +@node    Controlling tinc
 +@chapter Controlling tinc
 +
 +You can control and inspect a running tincd through the tincctl
 +command. A quick example:
 +
 +@example
 +tincctl -n @var{netname} reload
 +@end example
 +
 +@menu
 +* tincctl runtime options::
 +* tincctl commands::
 +* tincctl examples::
 +* tincctl top::
 +@end menu
 +
 +
 +@c ==================================================================
 +@node    tincctl runtime options
 +@section tincctl runtime options
 +
 +@c from the manpage
 +@table @option
 +@item -c, --config=@var{path}
 +Read configuration options from the directory @var{path}.  The default is
 +@file{@value{sysconfdir}/tinc/@var{netname}/}.
 +
 +@item -n, --net=@var{netname}
 +Use configuration for net @var{netname}. @xref{Multiple networks}.
 +
 +@item --pidfile=@var{filename}
 +Use the cookie from @var{filename} to authenticate with a running tinc daemon.
 +If unspecified, the default is
 +@file{@value{localstatedir}/run/tinc.@var{netname}.pid}.
 +
 +@item --help
 +Display a short reminder of runtime options and commands, then terminate.
 +
 +@item --version
 +Output version information and exit.
 +
 +@end table
 +
 +
 +@c ==================================================================
 +@node    tincctl commands
 +@section tincctl commands
 +
 +@c from the manpage
 +@table @code
 +
 +@item start
 +Start @samp{tincd}.
 +
 +@item stop
 +Stop @samp{tincd}.
 +
 +@item restart
 +Restart @samp{tincd}.
 +
 +@item reload
 +Partially rereads configuration files. Connections to hosts whose host
 +config files are removed are closed. New outgoing connections specified
 +in @file{tinc.conf} will be made.
 +
 +@item pid
 +Shows the PID of the currently running @samp{tincd}.
 +
 +@item generate-keys [@var{bits}]
 +Generate public/private keypair of @var{bits} length. If @var{bits} is not specified,
 +1024 is the default. tinc will ask where you want to store the files,
 +but will default to the configuration directory (you can use the -c or -n
 +option).
 +
 +@item dump nodes
 +Dump a list of all known nodes in the VPN.
 +
 +@item dump edges
 +Dump a list of all known connections in the VPN.
 +
 +@item dump subnets
 +Dump a list of all known subnets in the VPN.
 +
 +@item dump connections
 +Dump a list of all meta connections with ourself.
 +
 +@item dump graph
 +Dump a graph of the VPN in dotty format.
 +
 +@item purge
 +Purges all information remembered about unreachable nodes.
 +
 +@item debug @var{level}
 +Sets debug level to @var{level}.
 +
 +@item retry
 +Forces tinc to try to connect to all uplinks immediately.
 +Usually tinc attempts to do this itself,
 +but increases the time it waits between the attempts each time it failed,
 +and if tinc didn't succeed to connect to an uplink the first time after it started,
 +it defaults to the maximum time of 15 minutes.
 +
 +@item disconnect @var{node}
 +Closes the meta connection with the given @var{node}.
 +
 +@item top
 +If tincctl is compiled with libcurses support, this will display live traffic statistics for all the known nodes,
 +similar to the UNIX top command.
 +See below for more information.
 +
 +@item pcap
 +Dump VPN traffic going through the local tinc node in pcap-savefile format to standard output,
 +from where it can be redirected to a file or piped through a program that can parse it directly,
 +such as tcpdump.
 +
 +@end table
 +
 +@c ==================================================================
 +@node    tincctl examples
 +@section tincctl examples
 +
 +Examples of some commands:
 +
 +@example
 +tincctl -n vpn dump graph | circo -Txlib
 +tincctl -n vpn pcap | tcpdump -r -
 +tincctl -n vpn top
 +@end example
 +
 +@c ==================================================================
 +@node    tincctl top
 +@section tincctl top
 +
 +The top command connects to a running tinc daemon and repeatedly queries its per-node traffic counters.
 +It displays a list of all the known nodes in the left-most column,
 +and the amount of bytes and packets read from and sent to each node in the other columns.
 +By default, the information is updated every second.
 +The behaviour of the top command can be changed using the following keys:
 +
 +@table @key
 +
 +@item s
 +Change the interval between updates.
 +After pressing the @key{s} key, enter the desired interval in seconds, followed by enter.
 +Fractional seconds are honored.
 +Intervals lower than 0.1 seconds are not allowed.
 +
 +@item c
 +Toggle between displaying current traffic rates (in packets and bytes per second)
 +and cummulative traffic (total packets and bytes since the tinc daemon started).
 +
 +@item n
 +Sort the list of nodes by name.
 +
 +@item i
 +Sort the list of nodes by incoming amount of bytes.
 +
 +@item I
 +Sort the list of nodes by incoming amount of packets.
 +
 +@item o
 +Sort the list of nodes by outgoing amount of bytes.
 +
 +@item O
 +Sort the list of nodes by outgoing amount of packets.
 +
 +@item t
 +Sort the list of nodes by sum of incoming and outgoing amount of bytes.
 +
 +@item T
 +Sort the list of nodes by sum of incoming and outgoing amount of packets.
 +
 +@item b
 +Show amount of traffic in bytes.
 +
 +@item k
 +Show amount of traffic in kilobytes.
 +
 +@item M
 +Show amount of traffic in megabytes.
 +
 +@item G
 +Show amount of traffic in gigabytes.
 +
 +@item q
 +Quit.
 +
 +@end table
 +
 +
  @c ==================================================================
  @node    Technical information
  @chapter Technical information
diff --combined src/graph.c
index bb55dfdcba1c821d25fcf1f27e4c81fc5478fb7f,297832cda5bb8f3eeabd8cf241c3eeacfc8601f0..42157b0a994b8cc29630eed36559dac9688e6cb1
@@@ -44,7 -44,7 +44,7 @@@
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "config.h"
  #include "connection.h"
  #include "device.h"
  #include "subnet.h"
  #include "utils.h"
  #include "xalloc.h"
 -
 -static bool graph_changed = true;
 +#include "graph.h"
  
  /* Implementation of Kruskal's algorithm.
 -   Running time: O(EN)
 +   Running time: O(E)
     Please note that sorting on weight is already done by add_edge().
  */
  
 -static void mst_kruskal(void) {
 -      avl_node_t *node, *next;
 +void mst_kruskal(void) {
 +      splay_node_t *node, *next;
        edge_t *e;
        node_t *n;
        connection_t *c;
 -      int nodes = 0;
 -      int safe_edges = 0;
 -      bool skipped;
  
        /* Clear MST status on connections */
  
                c->status.mst = false;
        }
  
 -      /* Do we have something to do at all? */
 -
 -      if(!edge_weight_tree->head)
 -              return;
 -
        ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Running Kruskal's algorithm:");
  
        /* Clear visited status on nodes */
        for(node = node_tree->head; node; node = node->next) {
                n = node->data;
                n->status.visited = false;
 -              nodes++;
 -      }
 -
 -      /* Starting point */
 -
 -      for(node = edge_weight_tree->head; node; node = node->next) {
 -              e = node->data;
 -              if(e->from->status.reachable) {
 -                      e->from->status.visited = true;
 -                      break;
 -              }
        }
  
        /* Add safe edges */
  
 -      for(skipped = false, node = edge_weight_tree->head; node; node = next) {
 +      for(node = edge_weight_tree->head; node; node = next) {
                next = node->next;
                e = node->data;
  
 -              if(!e->reverse || e->from->status.visited == e->to->status.visited) {
 -                      skipped = true;
 +              if(!e->reverse || (e->from->status.visited && e->to->status.visited))
                        continue;
 -              }
  
                e->from->status.visited = true;
                e->to->status.visited = true;
                if(e->reverse->connection)
                        e->reverse->connection->status.mst = true;
  
 -              safe_edges++;
 -
                ifdebug(SCARY_THINGS) logger(LOG_DEBUG, " Adding edge %s - %s weight %d", e->from->name,
                                   e->to->name, e->weight);
 +      }
 +}
  
 -              if(skipped) {
 -                      skipped = false;
 -                      next = edge_weight_tree->head;
 -                      continue;
 +/* Implementation of Dijkstra's algorithm.
 +   Running time: O(N^2)
 +*/
 +
 +static void sssp_dijkstra(void) {
 +      splay_node_t *node, *to;
 +      edge_t *e;
 +      node_t *n, *m;
 +      list_t *todo_list;
 +      list_node_t *lnode, *nnode;
 +      bool indirect;
 +
 +      todo_list = list_alloc(NULL);
 +
 +      ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Running Dijkstra's algorithm:");
 +
 +      /* Clear visited status on nodes */
 +
 +      for(node = node_tree->head; node; node = node->next) {
 +              n = node->data;
 +              n->status.visited = false;
 +              n->status.indirect = true;
 +              n->distance = -1;
 +      }
 +
 +      /* Begin with myself */
 +
 +      myself->status.indirect = false;
 +      myself->nexthop = myself;
 +      myself->via = myself;
 +      myself->distance = 0;
 +      list_insert_head(todo_list, myself);
 +
 +      /* Loop while todo_list is filled */
 +
 +      while(todo_list->head) {
 +              n = NULL;
 +              nnode = NULL;
 +
 +              /* Select node from todo_list with smallest distance */
 +
 +              for(lnode = todo_list->head; lnode; lnode = lnode->next) {
 +                      m = lnode->data;
 +                      if(!n || m->status.indirect < n->status.indirect || m->distance < n->distance) {
 +                              n = m;
 +                              nnode = lnode;
 +                      }
 +              }
 +
 +              /* Mark this node as visited and remove it from the todo_list */
 +
 +              n->status.visited = true;
 +              list_unlink_node(todo_list, nnode);
 +
 +              /* Update distance of neighbours and add them to the todo_list */
 +
 +              for(to = n->edge_tree->head; to; to = to->next) {       /* "to" is the edge connected to "from" */
 +                      e = to->data;
 +
 +                      if(e->to->status.visited || !e->reverse)
 +                              continue;
 +
 +                      /* Situation:
 +
 +                                 /
 +                                /
 +                         ----->(n)---e-->(e->to)
 +                                \
 +                                 \
 +
 +                         Where e is an edge, (n) and (e->to) are nodes.
 +                         n->address is set to the e->address of the edge left of n to n.
 +                         We are currently examining the edge e right of n from n:
 +
 +                         - If edge e provides for better reachability of e->to, update e->to.
 +                       */
 +
 +                      if(e->to->distance < 0)
 +                              list_insert_tail(todo_list, e->to);
 +
 +                      indirect = n->status.indirect || e->options & OPTION_INDIRECT || ((n != myself) && sockaddrcmp(&n->address, &e->reverse->address));
 +
 +                      if(e->to->distance >= 0 && (!e->to->status.indirect || indirect) && e->to->distance <= n->distance + e->weight)
 +                              continue;
 +
 +                      e->to->distance = n->distance + e->weight;
 +                      e->to->status.indirect = indirect;
 +                      e->to->nexthop = (n->nexthop == myself) ? e->to : n->nexthop;
 +                      e->to->via = indirect ? n->via : e->to;
 +                      e->to->options = e->options;
 +
 +                      if(e->to->address.sa.sa_family == AF_UNSPEC && e->address.sa.sa_family != AF_UNKNOWN)
 +                              update_node_udp(e->to, &e->address);
 +
 +                      ifdebug(SCARY_THINGS) logger(LOG_DEBUG, " Updating edge %s - %s weight %d distance %d", e->from->name,
 +                                         e->to->name, e->weight, e->to->distance);
                }
        }
  
 -      ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Done, counted %d nodes and %d safe edges.", nodes,
 -                         safe_edges);
 +      list_free(todo_list);
  }
  
  /* Implementation of a simple breadth-first search algorithm.
     Running time: O(E)
  */
  
 -static void sssp_bfs(void) {
 -      avl_node_t *node, *next, *to;
 +void sssp_bfs(void) {
 +      splay_node_t *node, *to;
        edge_t *e;
        node_t *n;
        list_t *todo_list;
        list_node_t *from, *todonext;
        bool indirect;
 -      char *name;
 -      char *address, *port;
 -      char *envp[7];
 -      int i;
  
        todo_list = list_alloc(NULL);
  
        myself->status.visited = true;
        myself->status.indirect = false;
        myself->nexthop = myself;
+       myself->prevedge = NULL;
        myself->via = myself;
        list_insert_head(todo_list, myself);
  
                        e->to->status.visited = true;
                        e->to->status.indirect = indirect;
                        e->to->nexthop = (n->nexthop == myself) ? e->to : n->nexthop;
+                       e->to->prevedge = e;
                        e->to->via = indirect ? n->via : e->to;
                        e->to->options = e->options;
  
        }
  
        list_free(todo_list);
 +}
 +
 +static void check_reachability(void) {
 +      splay_node_t *node, *next;
 +      node_t *n;
 +      char *name;
 +      char *address, *port;
 +      char *envp[7];
 +      int i;
  
        /* Check reachability status. */
  
                        n->minmtu = 0;
                        n->mtuprobes = 0;
  
 -                      if(n->mtuevent) {
 -                              event_del(n->mtuevent);
 -                              n->mtuevent = NULL;
 -                      }
 +                      if(timeout_initialized(&n->mtuevent))
 +                              event_del(&n->mtuevent);
  
                        xasprintf(&envp[0], "NETNAME=%s", netname ? : "");
                        xasprintf(&envp[1], "DEVICE=%s", device ? : "");
  
  void graph(void) {
        subnet_cache_flush();
 -      sssp_bfs();
 +      sssp_dijkstra();
 +      check_reachability();
        mst_kruskal();
 -      graph_changed = true;
 -}
 -
 -
 -
 -/* Dump nodes and edges to a graphviz file.
 -         
 -   The file can be converted to an image with
 -   dot -Tpng graph_filename -o image_filename.png -Gconcentrate=true
 -*/
 -
 -void dump_graph(void) {
 -      avl_node_t *node;
 -      node_t *n;
 -      edge_t *e;
 -      char *filename = NULL, *tmpname = NULL;
 -      FILE *file;
 -      
 -      if(!graph_changed || !get_config_string(lookup_config(config_tree, "GraphDumpFile"), &filename))
 -              return;
 -
 -      graph_changed = false;
 -
 -      ifdebug(PROTOCOL) logger(LOG_NOTICE, "Dumping graph");
 -      
 -      if(filename[0] == '|') {
 -              file = popen(filename + 1, "w");
 -      } else {
 -              xasprintf(&tmpname, "%s.new", filename);
 -              file = fopen(tmpname, "w");
 -      }
 -
 -      if(!file) {
 -              logger(LOG_ERR, "Unable to open graph dump file %s: %s", filename, strerror(errno));
 -              free(tmpname);
 -              return;
 -      }
 -
 -      fprintf(file, "digraph {\n");
 -      
 -      /* dump all nodes first */
 -      for(node = node_tree->head; node; node = node->next) {
 -              n = node->data;
 -              fprintf(file, " %s [label = \"%s\"];\n", n->name, n->name);
 -      }
 -
 -      /* now dump all edges */
 -      for(node = edge_weight_tree->head; node; node = node->next) {
 -              e = node->data;
 -              fprintf(file, " %s -> %s;\n", e->from->name, e->to->name);
 -      }
 -
 -      fprintf(file, "}\n");   
 -      
 -      if(filename[0] == '|') {
 -              pclose(file);
 -      } else {
 -              fclose(file);
 -#ifdef HAVE_MINGW
 -              unlink(filename);
 -#endif
 -              rename(tmpname, filename);
 -              free(tmpname);
 -      }
  }
diff --combined src/net.h
index c511a5fc8342f6084525bbbbe7300afb63c1ca85,03cbec75f96437c9aa93fec66a5f50907921ce0c..6ca13d488a307cdea812521c88c4568009fcdfa8
+++ 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;
@@@ -109,29 -107,35 +109,30 @@@ extern int maxoutbufsize
  extern int seconds_till_retry;
  extern int addressfamily;
  extern unsigned replaywin;
+ extern bool localdiscovery;
  
  extern listen_socket_t listen_socket[MAXSOCKETS];
  extern int listen_sockets;
 -extern int keyexpires;
  extern int keylifetime;
  extern int udp_rcvbuf;
  extern int udp_sndbuf;
  extern bool do_prune;
 -extern bool do_purge;
  extern char *myport;
 -extern time_t now;
  extern int contradicting_add_edge;
  extern int contradicting_del_edge;
  
 -extern volatile bool running;
 -
  /* Yes, very strange placement indeed, but otherwise the typedefs get all tangled up */
  #include "connection.h"
  #include "node.h"
  
  extern void retry_outgoing(outgoing_t *);
 -extern void handle_incoming_vpn_data(int);
 +extern void handle_incoming_vpn_data(int, short, void *);
  extern void finish_connecting(struct connection_t *);
 -extern void do_outgoing_connection(struct connection_t *);
 -extern bool handle_new_meta_connection(int);
 +extern bool do_outgoing_connection(struct connection_t *);
 +extern void handle_new_meta_connection(int, short, void *);
  extern int setup_listen_socket(const sockaddr_t *);
  extern int setup_vpn_in_socket(const sockaddr_t *);
 -extern void send_packet(const struct node_t *, vpn_packet_t *);
 +extern void send_packet(struct node_t *, vpn_packet_t *);
  extern void receive_tcppacket(struct connection_t *, const char *, int);
  extern void broadcast_packet(const struct node_t *, vpn_packet_t *);
  extern bool setup_network(void);
@@@ -141,16 -145,8 +142,16 @@@ extern void close_network_connections(v
  extern int main_loop(void);
  extern void terminate_connection(struct connection_t *, bool);
  extern void flush_queue(struct node_t *);
 +extern bool node_read_ecdsa_public_key(struct node_t *);
 +extern bool read_ecdsa_public_key(struct connection_t *);
  extern bool read_rsa_public_key(struct connection_t *);
  extern void send_mtu_probe(struct node_t *);
 +extern void handle_device_data(int, short, void *);
 +extern void handle_meta_connection_data(int, short, void *);
 +extern void regenerate_key(void);
 +extern void purge(void);
 +extern void retry(void);
 +extern int reload_configuration(void);
  extern void load_all_subnets(void);
  
  #ifndef HAVE_MINGW
diff --combined src/net_packet.c
index f7d8640300a6bc5065b572e0828446e9b3cb7c0a,18a898d30f3dba95fcc709cfb02981b2e9d3ef05..64b0f8e8490adb7d5f0a06c2c99f6fcf39db2e64
  #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
  static void send_udppacket(node_t *, vpn_packet_t *);
  
  unsigned replaywin = 16;
+ bool localdiscovery = false;
  
  #define MAX_SEQNO 1073741824
  
- // mtuprobes == 1..30: initial discovery, send bursts with 1 second interval
- // mtuprobes ==    31: sleep pinginterval seconds
- // mtuprobes ==    32: send 1 burst, sleep pingtimeout second
- // mtuprobes ==    33: no response from other side, restart PMTU discovery process
+ /* mtuprobes == 1..30: initial discovery, send bursts with 1 second interval
+    mtuprobes ==    31: sleep pinginterval seconds
+    mtuprobes ==    32: send 1 burst, sleep pingtimeout second
+    mtuprobes ==    33: no response from other side, restart PMTU discovery process
+    Probes are sent in batches of three, with random sizes between the lower and
+    upper boundaries for the MTU thus far discovered.
+    In case local discovery is enabled, a fourth packet is added to each batch,
+    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);
                timeout = pingtimeout;
        }
  
-       for(i = 0; i < 3; i++) {
+       for(i = 0; i < 3 + localdiscovery; i++) {
                if(n->maxmtu <= n->minmtu)
                        len = n->maxmtu;
                else
                        len = 64;
                
                memset(packet.data, 0, 14);
 -              RAND_pseudo_bytes(packet.data + 14, len - 14);
 +              randomize(packet.data + 14, len - 14);
                packet.len = len;
-               packet.priority = 0;
+               packet.priority = i < 3 ? 0 : -1;
  
                ifdebug(TRAFFIC) logger(LOG_INFO, "Sending MTU probe length %d to %s (%s)", len, n->name, n->hostname);
  
        }
  
  end:
 -      n->mtuevent = new_event();
 -      n->mtuevent->handler = (event_handler_t)send_mtu_probe;
 -      n->mtuevent->data = n;
 -      n->mtuevent->time = now + timeout;
 -      event_add(n->mtuevent);
 +      event_add(&n->mtuevent, &(struct timeval){timeout, 0});
  }
  
 -void mtu_probe_h(node_t *n, vpn_packet_t *packet, length_t len) {
 +void send_mtu_probe(node_t *n) {
 +      if(!timeout_initialized(&n->mtuevent))
 +              timeout_set(&n->mtuevent, send_mtu_probe_handler, n);
 +      send_mtu_probe_handler(0, 0, n);
 +}
 +
 +static void mtu_probe_h(node_t *n, vpn_packet_t *packet, length_t len) {
        ifdebug(TRAFFIC) logger(LOG_INFO, "Got MTU probe length %d from %s (%s)", packet->len, n->name, n->hostname);
  
        if(!packet->data[0]) {
@@@ -234,17 -239,18 +242,17 @@@ static void receive_packet(node_t *n, v
        ifdebug(TRAFFIC) logger(LOG_DEBUG, "Received packet of %d bytes from %s (%s)",
                           packet->len, n->name, n->hostname);
  
 +      n->in_packets++;
 +      n->in_bytes += packet->len;
 +
        route(n, packet);
  }
  
 -static bool try_mac(const node_t *n, const vpn_packet_t *inpkt) {
 -      unsigned char hmac[EVP_MAX_MD_SIZE];
 -
 -      if(!n->indigest || !n->inmaclength || !n->inkey || inpkt->len < sizeof inpkt->seqno + n->inmaclength)
 +static bool try_mac(node_t *n, const vpn_packet_t *inpkt) {
 +      if(!digest_active(&n->indigest) || inpkt->len < sizeof inpkt->seqno + digest_length(&n->indigest))
                return false;
  
 -      HMAC(n->indigest, n->inkey, n->inkeylength, (unsigned char *) &inpkt->seqno, inpkt->len - n->inmaclength, (unsigned char *)hmac, NULL);
 -
 -      return !memcmp(hmac, (char *) &inpkt->seqno + inpkt->len - n->inmaclength, n->inmaclength);
 +      return digest_verify(&n->indigest, &inpkt->seqno, inpkt->len - n->indigest.maclength, (const char *)&inpkt->seqno + inpkt->len - n->indigest.maclength);
  }
  
  static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) {
        vpn_packet_t *pkt[] = { &pkt1, &pkt2, &pkt1, &pkt2 };
        int nextpkt = 0;
        vpn_packet_t *outpkt = pkt[0];
 -      int outlen, outpad;
 -      unsigned char hmac[EVP_MAX_MD_SIZE];
 -      int i;
 +      size_t outlen;
  
 -      if(!n->inkey) {
 +      if(!cipher_active(&n->incipher)) {
                ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got packet from %s (%s) but he hasn't got our key yet",
                                        n->name, n->hostname);
                return;
  
        /* Check packet length */
  
 -      if(inpkt->len < sizeof(inpkt->seqno) + n->inmaclength) {
 +      if(inpkt->len < sizeof inpkt->seqno + digest_length(&n->indigest)) {
                ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got too short packet from %s (%s)",
                                        n->name, n->hostname);
                return;
  
        /* Check the message authentication code */
  
 -      if(n->indigest && n->inmaclength) {
 -              inpkt->len -= n->inmaclength;
 -              HMAC(n->indigest, n->inkey, n->inkeylength,
 -                       (unsigned char *) &inpkt->seqno, inpkt->len, (unsigned char *)hmac, NULL);
 -
 -              if(memcmp(hmac, (char *) &inpkt->seqno + inpkt->len, n->inmaclength)) {
 -                      ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got unauthenticated packet from %s (%s)",
 -                                         n->name, n->hostname);
 +      if(digest_active(&n->indigest)) {
 +              inpkt->len -= n->indigest.maclength;
 +              if(!digest_verify(&n->indigest, &inpkt->seqno, inpkt->len, (const char *)&inpkt->seqno + inpkt->len)) {
 +                      ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got unauthenticated packet from %s (%s)", n->name, n->hostname);
                        return;
                }
        }
 -
        /* Decrypt the packet */
  
 -      if(n->incipher) {
 +      if(cipher_active(&n->incipher)) {
                outpkt = pkt[nextpkt++];
 +              outlen = MAXSIZE;
  
 -              if(!EVP_DecryptInit_ex(&n->inctx, NULL, NULL, NULL, NULL)
 -                              || !EVP_DecryptUpdate(&n->inctx, (unsigned char *) &outpkt->seqno, &outlen,
 -                                      (unsigned char *) &inpkt->seqno, inpkt->len)
 -                              || !EVP_DecryptFinal_ex(&n->inctx, (unsigned char *) &outpkt->seqno + outlen, &outpad)) {
 -                      ifdebug(TRAFFIC) logger(LOG_DEBUG, "Error decrypting packet from %s (%s): %s",
 -                                              n->name, n->hostname, ERR_error_string(ERR_get_error(), NULL));
 +              if(!cipher_decrypt(&n->incipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
 +                      ifdebug(TRAFFIC) logger(LOG_DEBUG, "Error decrypting packet from %s (%s)", n->name, n->hostname);
                        return;
                }
                
 -              outpkt->len = outlen + outpad;
 +              outpkt->len = outlen;
                inpkt = outpkt;
        }
  
        /* Check the sequence number */
  
 -      inpkt->len -= sizeof(inpkt->seqno);
 +      inpkt->len -= sizeof inpkt->seqno;
        inpkt->seqno = ntohl(inpkt->seqno);
  
        if(replaywin) {
                                        return;
                                }
                        } else {
 -                              for(i = n->received_seqno + 1; i < inpkt->seqno; i++)
 +                              for(int i = n->received_seqno + 1; i < inpkt->seqno; i++)
                                        n->late[(i / 8) % replaywin] |= 1 << i % 8;
                        }
                }
                n->received_seqno = inpkt->seqno;
                        
        if(n->received_seqno > MAX_SEQNO)
 -              keyexpires = 0;
 +              regenerate_key();
  
        /* Decompress the packet */
  
@@@ -375,12 -391,12 +383,12 @@@ static void send_udppacket(node_t *n, v
        vpn_packet_t *inpkt = origpkt;
        int nextpkt = 0;
        vpn_packet_t *outpkt;
 -      int origlen;
 -      int outlen, outpad;
 +      int origlen = origpkt->len;
 +      size_t outlen;
  #if defined(SOL_IP) && defined(IP_TOS)
        static int priority = 0;
 +      int origpriority = origpkt->priority;
  #endif
 -      int origpriority;
  
        if(!n->status.reachable) {
                ifdebug(TRAFFIC) logger(LOG_INFO, "Trying to send UDP packet to unreachable node %s (%s)", n->name, n->hostname);
        /* Make sure we have a valid key */
  
        if(!n->status.validkey) {
 +              time_t now = time(NULL);
 +
                ifdebug(TRAFFIC) logger(LOG_INFO,
                                   "No valid key known yet for %s (%s), forwarding via TCP",
                                   n->name, n->hostname);
                return;
        }
  
 -      origlen = inpkt->len;
 -      origpriority = inpkt->priority;
 -
        /* Compress the packet */
  
        if(n->outcompression) {
        /* Add sequence number */
  
        inpkt->seqno = htonl(++(n->sent_seqno));
 -      inpkt->len += sizeof(inpkt->seqno);
 +      inpkt->len += sizeof inpkt->seqno;
  
        /* Encrypt the packet */
  
 -      if(n->outcipher) {
 +      if(cipher_active(&n->outcipher)) {
                outpkt = pkt[nextpkt++];
 +              outlen = MAXSIZE;
  
 -              if(!EVP_EncryptInit_ex(&n->outctx, NULL, NULL, NULL, NULL)
 -                              || !EVP_EncryptUpdate(&n->outctx, (unsigned char *) &outpkt->seqno, &outlen,
 -                                      (unsigned char *) &inpkt->seqno, inpkt->len)
 -                              || !EVP_EncryptFinal_ex(&n->outctx, (unsigned char *) &outpkt->seqno + outlen, &outpad)) {
 -                      ifdebug(TRAFFIC) logger(LOG_ERR, "Error while encrypting packet to %s (%s): %s",
 -                                              n->name, n->hostname, ERR_error_string(ERR_get_error(), NULL));
 +              if(!cipher_encrypt(&n->outcipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
 +                      ifdebug(TRAFFIC) logger(LOG_ERR, "Error while encrypting packet to %s (%s)", n->name, n->hostname);
                        goto end;
                }
  
 -              outpkt->len = outlen + outpad;
 +              outpkt->len = outlen;
                inpkt = outpkt;
        }
  
        /* Add the message authentication code */
  
 -      if(n->outdigest && n->outmaclength) {
 -              HMAC(n->outdigest, n->outkey, n->outkeylength, (unsigned char *) &inpkt->seqno,
 -                       inpkt->len, (unsigned char *) &inpkt->seqno + inpkt->len, NULL);
 -              inpkt->len += n->outmaclength;
 +      if(digest_active(&n->outdigest)) {
 +              digest_create(&n->outdigest, &inpkt->seqno, inpkt->len, (char *)&inpkt->seqno + inpkt->len);
 +              inpkt->len += digest_length(&n->outdigest);
        }
  
        /* Determine which socket we have to use */
  
        /* Send the packet */
  
+       struct sockaddr *sa;
+       socklen_t sl;
+       int sock;
+       /* Overloaded use of priority field: -1 means local broadcast */
+       if(origpriority == -1 && n->prevedge) {
+               struct sockaddr_in in;
+               in.sin_family = AF_INET;
+               in.sin_addr.s_addr = -1;
+               in.sin_port = n->prevedge->address.in.sin_port;
+               sa = (struct sockaddr *)&in;
+               sl = sizeof in;
+               sock = 0;
+       } else {
+               if(origpriority == -1)
+                       origpriority = 0;
+               sa = &(n->address.sa);
+               sl = SALEN(n->address.sa);
+               sock = n->sock;
+       }
  #if defined(SOL_IP) && defined(IP_TOS)
        if(priorityinheritance && origpriority != priority
           && listen_socket[n->sock].sa.sa.sa_family == AF_INET) {
        }
  #endif
  
-       if(sendto(listen_socket[n->sock].udp, (char *) &inpkt->seqno, inpkt->len, 0, &(n->address.sa), SALEN(n->address.sa)) < 0 && !sockwouldblock(sockerrno)) {
+       if(sendto(listen_socket[sock].udp, (char *) &inpkt->seqno, inpkt->len, 0, sa, sl) < 0 && !sockwouldblock(sockerrno)) {
                if(sockmsgsize(sockerrno)) {
                        if(n->maxmtu >= origlen)
                                n->maxmtu = origlen - 1;
@@@ -500,14 -544,12 +531,14 @@@ 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;
        }
                return;
        }
  
 +      n->out_packets++;
 +      n->out_bytes += packet->len;
 +
        via = (packet->priority == -1 || n->via == myself) ? n->nexthop : n->via;
  
        if(via != n)
  /* Broadcast a packet using the minimum spanning tree */
  
  void broadcast_packet(const node_t *from, vpn_packet_t *packet) {
 -      avl_node_t *node;
 +      splay_node_t *node;
        connection_t *c;
  
        ifdebug(TRAFFIC) logger(LOG_INFO, "Broadcasting packet of %d bytes from %s (%s)",
  }
  
  static node_t *try_harder(const sockaddr_t *from, const vpn_packet_t *pkt) {
 -      avl_node_t *node;
 +      splay_node_t *node;
        edge_t *e;
        node_t *n = NULL;
        bool hard = false;
        static time_t last_hard_try = 0;
 +      time_t now = time(NULL);
  
        for(node = edge_weight_tree->head; node; node = node->next) {
                e = node->data;
        if(hard)
                last_hard_try = now;
  
+       last_hard_try = now;
        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));
                return;
        }
  
 +      pkt.len = len;
 +
        sockaddrunmap(&from);           /* Some braindead IPv6 implementations do stupid things. */
  
        n = lookup_node_udp(&from);
                        return;
        }
  
 -      n->sock = sock;
 +      n->sock = (intptr_t)data;
  
        receive_udppacket(n, &pkt);
  }
 +
 +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 207ce42543b4c9da1c542aa4f7484b580953a425,bab50fe4389d36aa1b168543ad356c24635a3e38..d292d850ced2f43d3f187ace41d28a953b6e47fb
  
  #include "system.h"
  
 -#include <openssl/pem.h>
 -#include <openssl/rsa.h>
 -#include <openssl/rand.h>
 -#include <openssl/err.h>
 -#include <openssl/evp.h>
 -
 -#include "avl_tree.h"
 +#include "splay_tree.h"
 +#include "cipher.h"
  #include "conf.h"
  #include "connection.h"
 +#include "control.h"
  #include "device.h"
 -#include "event.h"
 +#include "digest.h"
 +#include "ecdsa.h"
  #include "graph.h"
  #include "logger.h"
  #include "net.h"
  #include "process.h"
  #include "protocol.h"
  #include "route.h"
 +#include "rsa.h"
  #include "subnet.h"
  #include "utils.h"
  #include "xalloc.h"
  
  char *myport;
 +static struct event device_ev;
  devops_t devops;
  
 -bool read_rsa_public_key(connection_t *c) {
 +bool node_read_ecdsa_public_key(node_t *n) {
 +      if(ecdsa_active(&n->ecdsa))
 +              return true;
 +
 +      splay_tree_t *config_tree;
        FILE *fp;
        char *fname;
 -      char *key;
 +      char *p;
 +      bool result = false;
  
 -      if(!c->rsa_key) {
 -              c->rsa_key = RSA_new();
 -//            RSA_blinding_on(c->rsa_key, NULL);
 -      }
 +      xasprintf(&fname, "%s/hosts/%s", confbase, n->name);
  
 -      /* First, check for simple PublicKey statement */
 +      init_configuration(&config_tree);
 +      if(!read_config_file(config_tree, fname))
 +              goto exit;
  
 -      if(get_config_string(lookup_config(c->config_tree, "PublicKey"), &key)) {
 -              BN_hex2bn(&c->rsa_key->n, key);
 -              BN_hex2bn(&c->rsa_key->e, "FFFF");
 -              free(key);
 -              return true;
 +      /* First, check for simple ECDSAPublicKey statement */
 +
 +      if(get_config_string(lookup_config(config_tree, "ECDSAPublicKey"), &p)) {
 +              result = ecdsa_set_base64_public_key(&n->ecdsa, p);
 +              free(p);
 +              goto exit;
        }
  
 -      /* Else, check for PublicKeyFile statement and read it */
 +      /* Else, check for ECDSAPublicKeyFile statement and read it */
  
 -      if(get_config_string(lookup_config(c->config_tree, "PublicKeyFile"), &fname)) {
 -              fp = fopen(fname, "r");
 +      free(fname);
  
 -              if(!fp) {
 -                      logger(LOG_ERR, "Error reading RSA public key file `%s': %s",
 -                                 fname, strerror(errno));
 -                      free(fname);
 -                      return false;
 -              }
 +      if(!get_config_string(lookup_config(config_tree, "ECDSAPublicKeyFile"), &fname))
 +              xasprintf(&fname, "%s/hosts/%s", confbase, n->name);
  
 -              free(fname);
 -              c->rsa_key = PEM_read_RSAPublicKey(fp, &c->rsa_key, NULL, NULL);
 -              fclose(fp);
 +      fp = fopen(fname, "r");
  
 -              if(c->rsa_key)
 -                      return true;            /* Woohoo. */
 +      if(!fp) {
 +              logger(LOG_ERR, "Error reading ECDSA public key file `%s': %s", fname, strerror(errno));
 +              goto exit;
 +      }
  
 -              /* If it fails, try PEM_read_RSA_PUBKEY. */
 -              fp = fopen(fname, "r");
 +      result = ecdsa_read_pem_public_key(&n->ecdsa, fp);
 +      fclose(fp);
  
 -              if(!fp) {
 -                      logger(LOG_ERR, "Error reading RSA public key file `%s': %s",
 -                                 fname, strerror(errno));
 -                      free(fname);
 -                      return false;
 -              }
 +exit:
 +      exit_configuration(&config_tree);
 +      free(fname);
 +      return result;
 +}
  
 -              free(fname);
 -              c->rsa_key = PEM_read_RSA_PUBKEY(fp, &c->rsa_key, NULL, NULL);
 -              fclose(fp);
 +bool read_ecdsa_public_key(connection_t *c) {
 +      FILE *fp;
 +      char *fname;
 +      char *p;
 +      bool result;
  
 -              if(c->rsa_key) {
 -//                            RSA_blinding_on(c->rsa_key, NULL);
 -                      return true;
 -              }
 +      /* First, check for simple ECDSAPublicKey statement */
  
 -              logger(LOG_ERR, "Reading RSA public key file `%s' failed: %s",
 -                         fname, strerror(errno));
 -              return false;
 +      if(get_config_string(lookup_config(c->config_tree, "ECDSAPublicKey"), &p)) {
 +              result = ecdsa_set_base64_public_key(&c->ecdsa, p);
 +              free(p);
 +              return result;
        }
  
 -      /* Else, check if a harnessed public key is in the config file */
 +      /* Else, check for ECDSAPublicKeyFile statement and read it */
 +
 +      if(!get_config_string(lookup_config(c->config_tree, "ECDSAPublicKeyFile"), &fname))
 +              xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
  
 -      xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
        fp = fopen(fname, "r");
  
        if(!fp) {
 -              logger(LOG_ERR, "Error reading RSA public key file `%s': %s", fname, strerror(errno));
 +              logger(LOG_ERR, "Error reading ECDSA public key file `%s': %s",
 +                         fname, strerror(errno));
                free(fname);
                return false;
        }
  
 -      c->rsa_key = PEM_read_RSAPublicKey(fp, &c->rsa_key, NULL, NULL);
 +      result = ecdsa_read_pem_public_key(&c->ecdsa, fp);
        fclose(fp);
 +
 +      if(!result) 
 +              logger(LOG_ERR, "Reading ECDSA public key file `%s' failed: %s", fname, strerror(errno));
        free(fname);
 +      return result;
 +}
  
 -      if(c->rsa_key)
 -              return true;
 +bool read_rsa_public_key(connection_t *c) {
 +      FILE *fp;
 +      char *fname;
 +      char *n;
 +      bool result;
  
 -      /* Try again with PEM_read_RSA_PUBKEY. */
 +      /* First, check for simple PublicKey statement */
 +
 +      if(get_config_string(lookup_config(c->config_tree, "PublicKey"), &n)) {
 +              result = rsa_set_hex_public_key(&c->rsa, n, "FFFF");
 +              free(n);
 +              return result;
 +      }
 +
 +      /* Else, check for PublicKeyFile statement and read it */
 +
 +      if(!get_config_string(lookup_config(c->config_tree, "PublicKeyFile"), &fname))
 +              xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
  
 -      xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
        fp = fopen(fname, "r");
  
        if(!fp) {
                return false;
        }
  
 -      c->rsa_key = PEM_read_RSA_PUBKEY(fp, &c->rsa_key, NULL, NULL);
 -//    RSA_blinding_on(c->rsa_key, NULL);
 +      result = rsa_read_pem_public_key(&c->rsa, fp);
        fclose(fp);
 +
 +      if(!result) 
 +              logger(LOG_ERR, "Reading RSA public key file `%s' failed: %s", fname, strerror(errno));
        free(fname);
 +      return result;
 +}
  
 -      if(c->rsa_key)
 -              return true;
 +static bool read_ecdsa_private_key(void) {
 +      FILE *fp;
 +      char *fname;
 +      bool result;
  
 -      logger(LOG_ERR, "No public key for %s specified!", c->name);
 +      /* Check for PrivateKeyFile statement and read it */
 +
 +      if(!get_config_string(lookup_config(config_tree, "ECDSAPrivateKeyFile"), &fname))
 +              xasprintf(&fname, "%s/ecdsa_key.priv", confbase);
 +
 +      fp = fopen(fname, "r");
 +
 +      if(!fp) {
 +              logger(LOG_ERR, "Error reading ECDSA private key file `%s': %s", fname, strerror(errno));
 +              free(fname);
 +              return false;
 +      }
 +
 +#if !defined(HAVE_MINGW) && !defined(HAVE_CYGWIN)
 +      struct stat s;
 +
 +      if(fstat(fileno(fp), &s)) {
 +              logger(LOG_ERR, "Could not stat ECDSA private key file `%s': %s'", fname, strerror(errno));
 +              free(fname);
 +              return false;
 +      }
  
 -      return false;
 +      if(s.st_mode & ~0100700)
 +              logger(LOG_WARNING, "Warning: insecure file permissions for ECDSA private key file `%s'!", fname);
 +#endif
 +
 +      result = ecdsa_read_pem_private_key(&myself->connection->ecdsa, fp);
 +      fclose(fp);
 +
 +      if(!result) 
 +              logger(LOG_ERR, "Reading ECDSA private key file `%s' failed: %s", fname, strerror(errno));
 +      free(fname);
 +      return result;
  }
  
  static bool read_rsa_private_key(void) {
        FILE *fp;
 -      char *fname, *key, *pubkey;
 -      struct stat s;
 +      char *fname;
 +      char *n, *d;
 +      bool result;
  
 -      if(get_config_string(lookup_config(config_tree, "PrivateKey"), &key)) {
 -              if(!get_config_string(lookup_config(config_tree, "PublicKey"), &pubkey)) {
 +      /* First, check for simple PrivateKey statement */
 +
 +      if(get_config_string(lookup_config(config_tree, "PrivateKey"), &d)) {
 +              if(!get_config_string(lookup_config(config_tree, "PublicKey"), &n)) {
                        logger(LOG_ERR, "PrivateKey used but no PublicKey found!");
 +                      free(d);
                        return false;
                }
 -              myself->connection->rsa_key = RSA_new();
 -//            RSA_blinding_on(myself->connection->rsa_key, NULL);
 -              BN_hex2bn(&myself->connection->rsa_key->d, key);
 -              BN_hex2bn(&myself->connection->rsa_key->n, pubkey);
 -              BN_hex2bn(&myself->connection->rsa_key->e, "FFFF");
 -              free(key);
 -              free(pubkey);
 +              result = rsa_set_hex_private_key(&myself->connection->rsa, n, "FFFF", d);
 +              free(n);
 +              free(d);
                return true;
        }
  
 +      /* Else, check for PrivateKeyFile statement and read it */
 +
        if(!get_config_string(lookup_config(config_tree, "PrivateKeyFile"), &fname))
                xasprintf(&fname, "%s/rsa_key.priv", confbase);
  
        }
  
  #if !defined(HAVE_MINGW) && !defined(HAVE_CYGWIN)
 +      struct stat s;
 +
        if(fstat(fileno(fp), &s)) {
 -              logger(LOG_ERR, "Could not stat RSA private key file `%s': %s'",
 -                              fname, strerror(errno));
 +              logger(LOG_ERR, "Could not stat RSA private key file `%s': %s'", fname, strerror(errno));
                free(fname);
                return false;
        }
                logger(LOG_WARNING, "Warning: insecure file permissions for RSA private key file `%s'!", fname);
  #endif
  
 -      myself->connection->rsa_key = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
 +      result = rsa_read_pem_private_key(&myself->connection->rsa, fp);
        fclose(fp);
  
 -      if(!myself->connection->rsa_key) {
 -              logger(LOG_ERR, "Reading RSA private key file `%s' failed: %s",
 -                         fname, strerror(errno));
 -              free(fname);
 -              return false;
 +      if(!result) 
 +              logger(LOG_ERR, "Reading RSA private key file `%s' failed: %s", fname, strerror(errno));
 +      free(fname);
 +      return result;
 +}
 +
 +static struct event keyexpire_event;
 +
 +static void keyexpire_handler(int fd, short events, void *data) {
 +      regenerate_key();
 +}
 +
 +void regenerate_key(void) {
 +      if(timeout_initialized(&keyexpire_event)) {
 +              ifdebug(STATUS) logger(LOG_INFO, "Expiring symmetric keys");
 +              event_del(&keyexpire_event);
 +              send_key_changed();
 +      } else {
 +              timeout_set(&keyexpire_event, keyexpire_handler, NULL);
        }
  
 -      free(fname);
 -      return true;
 +      event_add(&keyexpire_event, &(struct timeval){keylifetime, 0});
  }
  
  /*
@@@ -288,7 -217,7 +288,7 @@@ void load_all_subnets(void) 
        struct dirent *ent;
        char *dname;
        char *fname;
 -      avl_tree_t *config_tree;
 +      splay_tree_t *config_tree;
        config_t *cfg;
        subnet_t *s, *s2;
        node_t *n;
@@@ -364,8 -293,7 +364,8 @@@ static bool setup_myself(void) 
        myself->connection->hostname = xstrdup("MYSELF");
  
        myself->connection->options = 0;
 -      myself->connection->protocol_version = PROT_CURRENT;
 +      myself->connection->protocol_major = PROT_MAJOR;
 +      myself->connection->protocol_minor = PROT_MINOR;
  
        if(!get_config_string(lookup_config(config_tree, "Name"), &name)) {     /* Not acceptable */
                logger(LOG_ERR, "Name for tinc daemon required!");
        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;
  
        get_config_bool(lookup_config(config_tree, "DirectOnly"), &directonly);
        get_config_bool(lookup_config(config_tree, "StrictSubnets"), &strictsubnets);
        get_config_bool(lookup_config(config_tree, "TunnelServer"), &tunnelserver);
+       get_config_bool(lookup_config(config_tree, "LocalDiscovery"), &localdiscovery);
        strictsubnets |= tunnelserver;
  
        if(get_config_string(lookup_config(config_tree, "Mode"), &mode)) {
  
        /* Generate packet encryption key */
  
 -      if(get_config_string
 -         (lookup_config(config_tree, "Cipher"), &cipher)) {
 -              if(!strcasecmp(cipher, "none")) {
 -                      myself->incipher = NULL;
 -              } else {
 -                      myself->incipher = EVP_get_cipherbyname(cipher);
 -
 -                      if(!myself->incipher) {
 -                              logger(LOG_ERR, "Unrecognized cipher type!");
 -                              return false;
 -                      }
 -              }
 -      } else
 -              myself->incipher = EVP_bf_cbc();
 -
 -      if(myself->incipher)
 -              myself->inkeylength = myself->incipher->key_len + myself->incipher->iv_len;
 -      else
 -              myself->inkeylength = 1;
 +      if(!get_config_string(lookup_config(config_tree, "Cipher"), &cipher))
 +              cipher = xstrdup("blowfish");
  
 -      myself->connection->outcipher = EVP_bf_ofb();
 +      if(!cipher_open_by_name(&myself->incipher, cipher)) {
 +              logger(LOG_ERR, "Unrecognized cipher type!");
 +              return false;
 +      }
  
        if(!get_config_int(lookup_config(config_tree, "KeyExpire"), &keylifetime))
                keylifetime = 3600;
  
 -      keyexpires = now + keylifetime;
 -      
 -      /* Check if we want to use message authentication codes... */
 +      regenerate_key();
  
 -      if(get_config_string(lookup_config(config_tree, "Digest"), &digest)) {
 -              if(!strcasecmp(digest, "none")) {
 -                      myself->indigest = NULL;
 -              } else {
 -                      myself->indigest = EVP_get_digestbyname(digest);
 +      /* Check if we want to use message authentication codes... */
  
 -                      if(!myself->indigest) {
 -                              logger(LOG_ERR, "Unrecognized digest type!");
 -                              return false;
 -                      }
 -              }
 -      } else
 -              myself->indigest = EVP_sha1();
 +      int maclength = 4;
 +      get_config_int(lookup_config(config_tree, "MACLength"), &maclength);
  
 -      myself->connection->outdigest = EVP_sha1();
 +      if(maclength < 0) {
 +              logger(LOG_ERR, "Bogus MAC length!");
 +              return false;
 +      }
  
 -      if(get_config_int(lookup_config(config_tree, "MACLength"), &myself->inmaclength)) {
 -              if(myself->indigest) {
 -                      if(myself->inmaclength > myself->indigest->md_size) {
 -                              logger(LOG_ERR, "MAC length exceeds size of digest!");
 -                              return false;
 -                      } else if(myself->inmaclength < 0) {
 -                              logger(LOG_ERR, "Bogus MAC length!");
 -                              return false;
 -                      }
 -              }
 -      } else
 -              myself->inmaclength = 4;
 +      if(!get_config_string(lookup_config(config_tree, "Digest"), &digest))
 +              digest = xstrdup("sha1");
  
 -      myself->connection->outmaclength = 0;
 +      if(!digest_open_by_name(&myself->indigest, digest, maclength)) {
 +              logger(LOG_ERR, "Unrecognized digest type!");
 +              return false;
 +      }
  
        /* Compression */
  
        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(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 */
                        listen_socket[listen_sockets].udp =
                                setup_vpn_in_socket((sockaddr_t *) aip->ai_addr);
  
 -                      if(listen_socket[listen_sockets].udp < 0)
 +                      if(listen_socket[listen_sockets].udp < 0) {
 +                              close(listen_socket[listen_sockets].tcp);
                                continue;
 +                      }
 +
 +                      event_set(&listen_socket[listen_sockets].ev_tcp,
 +                                        listen_socket[listen_sockets].tcp,
 +                                        EV_READ|EV_PERSIST,
 +                                        handle_new_meta_connection, NULL);
 +                      if(event_add(&listen_socket[listen_sockets].ev_tcp, NULL) < 0) {
 +                              logger(LOG_ERR, "event_add failed: %s", strerror(errno));
 +                              abort();
 +                      }
 +
 +                      event_set(&listen_socket[listen_sockets].ev_udp,
 +                                        listen_socket[listen_sockets].udp,
 +                                        EV_READ|EV_PERSIST,
 +                                        handle_incoming_vpn_data, (void *)(intptr_t)listen_sockets);
 +                      if(event_add(&listen_socket[listen_sockets].ev_udp, NULL) < 0) {
 +                              logger(LOG_ERR, "event_add failed: %s", strerror(errno));
 +                              abort();
 +                      }
  
                        ifdebug(CONNECTIONS) {
                                hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr);
    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 730cf6b8e56f5f00ffce0fa12ec34e6a7334aedd,94db11c0fa650a457d4a43c5e694136a28510335..282066976b271c23e827e7f7149daa2486bf3917
  
  #include "system.h"
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
  #include "conf.h"
  #include "connection.h"
 -#include "event.h"
  #include "logger.h"
  #include "meta.h"
  #include "net.h"
@@@ -69,12 -70,12 +69,12 @@@ static void configure_tcp(connection_t 
  
  #if defined(SOL_TCP) && defined(TCP_NODELAY)
        option = 1;
 -      setsockopt(c->socket, SOL_TCP, TCP_NODELAY, (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
  }
  
@@@ -127,7 -128,7 +127,7 @@@ int setup_listen_socket(const sockaddr_
        /* Optimize TCP settings */
  
        option = 1;
 -      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof(option));
 +      setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof option);
  
  #if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
        if(sa->sa.sa_family == AF_INET6)
  #if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE)
                struct ifreq ifr;
  
 -              memset(&ifr, 0, sizeof(ifr));
 +              memset(&ifr, 0, sizeof ifr);
                strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ);
  
 -              if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr))) {
 +              if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof ifr)) {
                        closesocket(nfd);
                        logger(LOG_ERR, "Can't bind to interface %s: %s", iface,
                                   strerror(sockerrno));
@@@ -209,7 -210,8 +209,8 @@@ int setup_vpn_in_socket(const sockaddr_
  #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));
        return nfd;
  } /* int setup_vpn_in_socket */
  
 +static void retry_outgoing_handler(int fd, short events, void *data) {
 +      setup_outgoing_connection(data);
 +}
 +
  void retry_outgoing(outgoing_t *outgoing) {
        outgoing->timeout += 5;
  
        if(outgoing->timeout > maxtimeout)
                outgoing->timeout = maxtimeout;
  
 -      if(outgoing->event)
 -              event_del(outgoing->event);
 -      outgoing->event = new_event();
 -      outgoing->event->handler = (event_handler_t) setup_outgoing_connection;
 -      outgoing->event->time = now + outgoing->timeout;
 -      outgoing->event->data = outgoing;
 -      event_add(outgoing->event);
 +      timeout_set(&outgoing->ev, retry_outgoing_handler, outgoing);
 +      event_add(&outgoing->ev, &(struct timeval){outgoing->timeout, 0});
  
        ifdebug(CONNECTIONS) logger(LOG_NOTICE,
                           "Trying to re-establish outgoing connection in %d seconds",
@@@ -293,13 -296,12 +294,13 @@@ void finish_connecting(connection_t *c
  
        configure_tcp(c);
  
 -      c->last_ping_time = now;
 +      c->last_ping_time = time(NULL);
 +      c->status.connecting = false;
  
        send_id(c);
  }
  
 -void do_outgoing_connection(connection_t *c) {
 +bool do_outgoing_connection(connection_t *c) {
        char *address, *port, *space;
        int result;
  
@@@ -313,10 -315,10 +314,10 @@@ begin
                if(!c->outgoing->cfg) {
                        ifdebug(CONNECTIONS) logger(LOG_ERR, "Could not set up a meta connection to %s",
                                           c->name);
 -                      c->status.remove = true;
                        retry_outgoing(c->outgoing);
                        c->outgoing = NULL;
 -                      return;
 +                      connection_del(c);
 +                      return false;
                }
  
                get_config_string(c->outgoing->cfg, &address);
        if(result == -1) {
                if(sockinprogress(sockerrno)) {
                        c->status.connecting = true;
 -                      return;
 +                      return true;
                }
  
                closesocket(c->socket);
  
        finish_connecting(c);
  
 -      return;
 +      return true;
 +}
 +
 +static void handle_meta_write(int sock, short events, void *data) {
 +      ifdebug(META) logger(LOG_DEBUG, "handle_meta_write() called");
 +
 +      connection_t *c = data;
 +
 +      ssize_t outlen = send(c->socket, c->outbuf.data + c->outbuf.offset, c->outbuf.len - c->outbuf.offset, 0);
 +      if(outlen <= 0) {
 +              logger(LOG_ERR, "Onoes, outlen = %d (%s)", (int)outlen, strerror(errno));
 +              terminate_connection(c, c->status.active);
 +              return;
 +      }
 +
 +      buffer_read(&c->outbuf, outlen);
 +      if(!c->outbuf.len && event_initialized(&c->outevent))
 +              event_del(&c->outevent);
  }
  
  void setup_outgoing_connection(outgoing_t *outgoing) {
        connection_t *c;
        node_t *n;
  
 -      outgoing->event = NULL;
 +      if(event_initialized(&outgoing->ev))
 +              event_del(&outgoing->ev);
  
        n = lookup_node(outgoing->name);
  
        }
  
        c->outgoing = outgoing;
 -      c->last_ping_time = now;
 +      c->last_ping_time = time(NULL);
  
        connection_add(c);
  
 -      do_outgoing_connection(c);
 +      if (do_outgoing_connection(c)) {
 +              event_set(&c->inevent, c->socket, EV_READ | EV_PERSIST, handle_meta_connection_data, c);
 +              event_set(&c->outevent, c->socket, EV_WRITE | EV_PERSIST, handle_meta_write, c);
 +              event_add(&c->inevent, NULL);
 +      }
  }
  
  /*
    accept a new tcp connect and create a
    new connection
  */
 -bool handle_new_meta_connection(int sock) {
 +void handle_new_meta_connection(int sock, short events, void *data) {
        connection_t *c;
        sockaddr_t sa;
        int fd;
 -      socklen_t len = sizeof(sa);
 +      socklen_t len = sizeof sa;
  
        fd = accept(sock, &sa.sa, &len);
  
        if(fd < 0) {
                logger(LOG_ERR, "Accepting a new connection failed: %s", sockstrerror(sockerrno));
 -              return false;
 +              return;
        }
  
        sockaddrunmap(&sa);
        c->address = sa;
        c->hostname = sockaddr2hostname(&sa);
        c->socket = fd;
 -      c->last_ping_time = now;
 +      c->last_ping_time = time(NULL);
  
        ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Connection from %s", c->hostname);
  
 +      event_set(&c->inevent, c->socket, EV_READ | EV_PERSIST, handle_meta_connection_data, c);
 +      event_set(&c->outevent, c->socket, EV_WRITE | EV_PERSIST, handle_meta_write, c);
 +      event_add(&c->inevent, NULL);
 +              
        configure_tcp(c);
  
        connection_add(c);
  
        c->allow_request = ID;
        send_id(c);
 -
 -      return true;
  }
  
  static void free_outgoing(outgoing_t *outgoing) {
@@@ -538,7 -516,7 +539,7 @@@ void try_outgoing_connections(void) 
                        continue;
                }
  
 -              outgoing = xmalloc_and_zero(sizeof(*outgoing));
 +              outgoing = xmalloc_and_zero(sizeof *outgoing);
                outgoing->name = name;
                list_insert_tail(outgoing_list, outgoing);
                setup_outgoing_connection(outgoing);
diff --combined src/node.h
index 4464011fc1f60e3ad94a355510432cb6039b1b69,d1cb97f7e0c7a45bf6cf0fd13581192bd148d9d9..e86aa6a012e58523c4b9c74b7f71c5024d9d602d
  #ifndef __TINC_NODE_H__
  #define __TINC_NODE_H__
  
 -#include "avl_tree.h"
 +#include "splay_tree.h"
 +#include "cipher.h"
  #include "connection.h"
 -#include "event.h"
 +#include "digest.h"
 +#include "ecdh.h"
  #include "subnet.h"
  
  typedef struct node_status_t {
 -      unsigned int unused_active:1;                   /* 1 if active (not used for nodes) */
 -      unsigned int validkey:1;                                /* 1 if we currently have a valid key for him */
 -      unsigned int unused_waitingforkey:1;            /* 1 if we already sent out a request */
 -      unsigned int visited:1;                         /* 1 if this node has been visited by one of the graph algorithms */
 -      unsigned int reachable:1;                       /* 1 if this node is reachable in the graph */
 -      unsigned int indirect:1;                                /* 1 if this node is not directly reachable by us */
 -      unsigned int unused:26;
 +      unsigned int unused_active:1;           /* 1 if active (not used for nodes) */
 +      unsigned int validkey:1;                /* 1 if we currently have a valid key for him */
 +      unsigned int unused_waitingforkey:1;    /* 1 if we already sent out a request */
 +      unsigned int visited:1;                 /* 1 if this node has been visited by one of the graph algorithms */
 +      unsigned int reachable:1;               /* 1 if this node is reachable in the graph */
 +      unsigned int indirect:1;                /* 1 if this node is not directly reachable by us */
 +      unsigned int ecdh:1;                    /* 1 if this node supports ECDH key exchange */
 +      unsigned int unused:25;
  } node_status_t;
  
  typedef struct node_t {
        node_status_t status;
        time_t last_req_key;
  
 -      const EVP_CIPHER *incipher;             /* Cipher type for UDP packets received from him */
 -      char *inkey;                            /* Cipher key and iv */
 -      int inkeylength;                        /* Cipher key and iv length */
 -      EVP_CIPHER_CTX inctx;                   /* Cipher context */
 -      
 -      const EVP_CIPHER *outcipher;            /* Cipher type for UDP packets sent to him*/
 -      char *outkey;                           /* Cipher key and iv */
 -      int outkeylength;                       /* Cipher key and iv length */
 -      EVP_CIPHER_CTX outctx;                  /* Cipher context */
 -      
 -      const EVP_MD *indigest;                 /* Digest type for MAC of packets received from him */
 -      int inmaclength;                        /* Length of MAC */
 -
 -      const EVP_MD *outdigest;                /* Digest type for MAC of packets sent to him*/
 -      int outmaclength;                       /* Length of MAC */
 +      ecdsa_t ecdsa;                          /* His public ECDSA key */
 +      ecdh_t ecdh;                            /* State for ECDH key exchange */
 +
 +      cipher_t incipher;                        /* Cipher for UDP packets */
 +      digest_t indigest;                        /* Digest for UDP packets */  
 +
 +      cipher_t outcipher;                        /* Cipher for UDP packets */
 +      digest_t outdigest;                        /* Digest for UDP packets */ 
  
        int incompression;                      /* Compressionlevel, 0 = no compression */
        int outcompression;                     /* Compressionlevel, 0 = no compression */
  
 +      int distance;
        struct node_t *nexthop;                 /* nearest node from us to him */
+       struct edge_t *prevedge;                /* nearest node from him to us */
        struct node_t *via;                     /* next hop for UDP packets */
  
 -      avl_tree_t *subnet_tree;                /* Pointer to a tree of subnets belonging to this node */
 +      splay_tree_t *subnet_tree;              /* Pointer to a tree of subnets belonging to this node */
  
 -      avl_tree_t *edge_tree;                  /* Edges with this node as one of the endpoints */
 +      splay_tree_t *edge_tree;                        /* Edges with this node as one of the endpoints */
  
        struct connection_t *connection;        /* Connection associated with this node (if a direct connection exists) */
  
        length_t minmtu;                        /* Probed minimum MTU */
        length_t maxmtu;                        /* Probed maximum MTU */
        int mtuprobes;                          /* Number of probes */
 -      event_t *mtuevent;                      /* Probe event */
 +      struct event mtuevent;                  /* Probe event */
 +
 +      uint64_t in_packets;
 +      uint64_t in_bytes;
 +      uint64_t out_packets;
 +      uint64_t out_bytes;
  } node_t;
  
  extern struct node_t *myself;
 -extern avl_tree_t *node_tree;
 -extern avl_tree_t *node_udp_tree;
 +extern splay_tree_t *node_tree;
 +extern splay_tree_t *node_udp_tree;
  
  extern void init_nodes(void);
  extern void exit_nodes(void);
@@@ -101,8 -100,7 +102,8 @@@ extern void node_add(node_t *)
  extern void node_del(node_t *);
  extern node_t *lookup_node(char *);
  extern node_t *lookup_node_udp(const sockaddr_t *);
 +extern bool dump_nodes(struct connection_t *);
 +extern bool dump_traffic(struct connection_t *);
  extern void update_node_udp(node_t *, const sockaddr_t *);
 -extern void dump_nodes(void);
  
  #endif                                                        /* __TINC_NODE_H__ */