]> git.meshlink.io Git - meshlink/blobdiff - src/net.c
Apply UTCP fix for handling callbacks when the receive buffer wraps around.
[meshlink] / src / net.c
index 58f3e02fbedba61472c062e890b7f9d9f262d1c8..6fd90e194442e9e96298825fa28083bf6cb444be 100644 (file)
--- a/src/net.c
+++ b/src/net.c
@@ -1,6 +1,6 @@
 /*
     net.c -- most of the network code
-    Copyright (C) 1998,99 Ivo Timmermans <zarq@iname.com>
+    Copyright (C) 2014-2017 Guus Sliepen <guus@meshlink.io>
 
     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
     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., 675 Mass Ave, Cambridge, MA 02139, USA.
+    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 "config.h"
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/signal.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <syslog.h>
-#include <unistd.h>
-
-#include <cipher.h>
-#include <utils.h>
-#include <xalloc.h>
+#include "system.h"
 
+#include "utils.h"
 #include "conf.h"
-#include "encr.h"
+#include "connection.h"
+#include "devtools.h"
+#include "graph.h"
+#include "logger.h"
+#include "meshlink_internal.h"
+#include "meta.h"
 #include "net.h"
 #include "netutl.h"
 #include "protocol.h"
+#include "sptps.h"
+#include "xalloc.h"
 
-int tap_fd = -1;
+#include <assert.h>
 
-int total_tap_in = 0;
-int total_tap_out = 0;
-int total_socket_in = 0;
-int total_socket_out = 0;
-
-time_t last_ping_time = 0;
+#if !defined(min)
+static inline int min(int a, int b) {
+       return a < b ? a : b;
+}
+#endif
 
-/* The global list of existing connections */
-conn_list_t *conn_list = NULL;
-conn_list_t *myself = NULL;
+static const int default_timeout = 5;
+static const int default_interval = 60;
 
 /*
-  strip off the MAC adresses of an ethernet frame
+  Terminate a connection:
+  - Mark it as inactive
+  - Remove the edge representing this connection
+  - Kill it with fire
+  - Check if we need to retry making an outgoing connection
 */
-void strip_mac_addresses(vpn_packet_t *p)
-{
-  unsigned char tmp[MAXSIZE];
-cp
-  memcpy(tmp, p->data, p->len);
-  p->len -= 12;
-  memcpy(p->data, &tmp[12], p->len);
-cp
-}
+void terminate_connection(meshlink_handle_t *mesh, connection_t *c, bool report) {
+       logger(mesh, MESHLINK_INFO, "Closing connection with %s", c->name);
 
-/*
-  reassemble MAC addresses
-*/
-void add_mac_addresses(vpn_packet_t *p)
-{
-  unsigned char tmp[MAXSIZE];
-cp
-  memcpy(&tmp[12], p->data, p->len);
-  p->len += 12;
-  tmp[0] = tmp[6] = 0xfe;
-  tmp[1] = tmp[7] = 0xfd;
-  *((ip_t*)(&tmp[2])) = (ip_t)(htonl(myself->vpn_ip));
-  *((ip_t*)(&tmp[8])) = *((ip_t*)(&tmp[26]));
-  memcpy(p->data, &tmp[0], p->len);
-cp
-}
+       c->status.active = false;
 
-int xsend(conn_list_t *cl, void *packet)
-{
-  int r;
-  real_packet_t rp;
-cp
-  do_encrypt((vpn_packet_t*)packet, &rp, cl->key);
-  rp.from = htonl(myself->vpn_ip);
-  rp.data.len = htons(rp.data.len);
-  rp.len = htons(rp.len);
-
-  if(debug_lvl > 3)
-    syslog(LOG_ERR, "Sent %d bytes to %lx", ntohs(rp.len), cl->vpn_ip);
-
-  if((r = send(cl->socket, (char*)&rp, ntohs(rp.len), 0)) < 0)
-    {
-      syslog(LOG_ERR, "Error sending data: %m");
-      return -1;
-    }
-
-  total_socket_out += r;
-cp
-  return 0;
-}
+       if(c->node && c->node->connection == c) {
+               c->node->connection = NULL;
+       }
 
-int xrecv(conn_list_t *cl, void *packet)
-{
-  vpn_packet_t vp;
-  int lenin;
-cp
-  do_decrypt((real_packet_t*)packet, &vp, cl->key);
-  add_mac_addresses(&vp);
-
-  if((lenin = write(tap_fd, &vp, vp.len + sizeof(vp.len))) < 0)
-    syslog(LOG_ERR, "Can't write to tap device: %m");
-  else
-    total_tap_out += lenin;
-cp
-  return 0;
-}
+       if(c->edge) {
+               if(report) {
+                       send_del_edge(mesh, mesh->everyone, c->edge, 0);
+               }
 
-/*
-  add the given packet of size s to the
-  queue q, be it the send or receive queue
-*/
-void add_queue(packet_queue_t **q, void *packet, size_t s)
-{
-  queue_element_t *e;
-cp
-  if(debug_lvl > 3)
-    syslog(LOG_DEBUG, "packet to queue: %d", s);
-
-  e = xmalloc(sizeof(*e));
-  e->packet = xmalloc(s);
-  memcpy(e->packet, packet, s);
-
-  if(!*q)
-    {
-      *q = xmalloc(sizeof(**q));
-      (*q)->head = (*q)->tail = NULL;
-    }
-
-  e->next = NULL;                      /* We insert at the tail */
-
-  if((*q)->tail)                       /* Do we have a tail? */
-    {
-      (*q)->tail->next = e;
-      e->prev = (*q)->tail;
-    }
-  else                                 /* No tail -> no head too */
-    {
-      (*q)->head = e;
-      e->prev = NULL;
-    }
-
-  (*q)->tail = e;
-cp
-}
+               edge_del(mesh, c->edge);
+               c->edge = NULL;
 
-/* Remove a queue element */
-void del_queue(packet_queue_t **q, queue_element_t *e)
-{
-cp
-  free(e->packet);
-
-  if(e->next)                          /* There is a successor, so we are not tail */
-    {
-      if(e->prev)                      /* There is a predecessor, so we are not head */
-        {
-          e->next->prev = e->prev;
-          e->prev->next = e->next;
-        }
-      else                             /* We are head */
-        {
-          e->next->prev = NULL;
-          (*q)->head = e->next;
-        }
-    }
-  else                                 /* We are tail (or all alone!) */
-    {          
-      if(e->prev)                      /* We are not alone :) */
-        {
-          e->prev->next = NULL;
-          (*q)->tail = e->prev;
-        }
-      else                             /* Adieu */
-        {
-          free(*q);
-          *q = NULL;
-        }
-    }
-    
-  free(e);
-cp
-}
+               /* Run MST and SSSP algorithms */
 
-/*
-  flush a queue by calling function for
-  each packet, and removing it when that
-  returned a zero exit code
-*/
-void flush_queue(conn_list_t *cl, packet_queue_t **pq,
-                int (*function)(conn_list_t*,void*))
-{
-  queue_element_t *p, *next = NULL;
-cp
-  for(p = (*pq)->head; p != NULL; )
-    {
-      next = p->next;
-
-      if(!function(cl, p->packet))
-        del_queue(pq, p);
-        
-      p = next;
-    }
-
-  if(debug_lvl > 3)
-    syslog(LOG_DEBUG, "queue flushed");
-cp
-}
+               graph(mesh);
 
-/*
-  flush the send&recv queues
-  void because nothing goes wrong here, packets
-  remain in the queue if something goes wrong
-*/
-void flush_queues(conn_list_t *cl)
-{
-cp
-  if(cl->sq)
-    {
-      if(debug_lvl > 1)
-       syslog(LOG_DEBUG, "Flushing send queue for " IP_ADDR_S,
-              IP_ADDR_V(cl->vpn_ip));
-      flush_queue(cl, &(cl->sq), xsend);
-    }
-
-  if(cl->rq)
-    {
-      if(debug_lvl > 1)
-       syslog(LOG_DEBUG, "Flushing receive queue for " IP_ADDR_S,
-              IP_ADDR_V(cl->vpn_ip));
-      flush_queue(cl, &(cl->rq), xrecv);
-    }
-cp
-}
+               /* If the node is not reachable anymore but we remember it had an edge to us, clean it up */
 
-/*
-  send a packet to the given vpn ip.
-*/
-int send_packet(ip_t to, vpn_packet_t *packet)
-{
-  config_t const *cfg;
-  conn_list_t *cl;
-cp
-  if(!(cfg = get_config_val(proxymode)))               /* In case we are in proxy mode, just send everything to our uplink. */
-    cl = conn_list;
-  else
-    cl = lookup_conn(to);
-
-  if((cl = lookup_conn(to)) == NULL)
-    {
-      if(debug_lvl > 2)
-        {
-          syslog(LOG_NOTICE, "trying to look up " IP_ADDR_S " in connection list failed.",
-                IP_ADDR_V(to));
-        }
-      for(cl = conn_list; cl != NULL && !cl->status.outgoing; cl = cl->next);
-      if(!cl)
-        { /* No open outgoing connection has been found. */
-         if(debug_lvl > 2)
-           syslog(LOG_NOTICE, "There is no remote host I can send this packet to.");
-          return -1;
-        }
-    }
-
-  if(my_key_expiry <= time(NULL))
-    regenerate_keys();
-
-  if(!cl->status.dataopen)
-    if(setup_vpn_connection(cl) < 0)
-      return -1;
-
-  if(!cl->status.validkey)
-    {
-      add_queue(&(cl->sq), packet, packet->len + 2);
-      if(!cl->status.waitingforkey)
-       send_key_request(cl->vpn_ip);                   /* Keys should be sent to the host running the tincd */
-      return 0;
-    }
-
-  if(!cl->status.active)
-    {
-      add_queue(&(cl->sq), packet, packet->len + 2);
-      if(debug_lvl > 1)
-       syslog(LOG_INFO, IP_ADDR_S " is not ready, queueing packet.", IP_ADDR_V(cl->vpn_ip));
-      return 0; /* We don't want to mess up, do we? */
-    }
-
-  /* can we send it? can we? can we? huh? */
-cp
-  return xsend(cl, packet);
-}
+               if(report && c->node && !c->node->status.reachable) {
+                       edge_t *e;
+                       e = lookup_edge(c->node, mesh->self);
 
-int send_broadcast(conn_list_t *cl, vpn_packet_t *packet)
-{
-  conn_list_t *p;
-cp
-  for(p = cl; p != NULL; p = p->next)
-    if(send_packet(p->real_ip, packet) < 0)
-      {
-       syslog(LOG_ERR, "Could not send a broadcast packet to %08lx (%08lx): %m",
-              p->vpn_ip, p->real_ip);
-       break; /* FIXME: should retry later, and send a ping over the metaconnection. */
-      }
-cp
-  return 0;
-}
+                       if(e) {
+                               send_del_edge(mesh, mesh->everyone, e, 0);
+                               edge_del(mesh, e);
+                       }
+               }
+       }
 
-/*
-  open the local ethertap device
-*/
-int setup_tap_fd(void)
-{
-  int nfd;
-  const char *tapfname;
-  config_t const *cfg;
-cp  
-  if((cfg = get_config_val(tapdevice)) == NULL)
-    tapfname = "/dev/tap0";
-  else
-    tapfname = cfg->data.ptr;
-
-  if((nfd = open(tapfname, O_RDWR | O_NONBLOCK)) < 0)
-    {
-      syslog(LOG_ERR, "Could not open %s: %m", tapfname);
-      return -1;
-    }
-
-  tap_fd = nfd;
-cp
-  return 0;
-}
+       outgoing_t *outgoing = c->outgoing;
+       connection_del(mesh, c);
 
-/*
-  set up the socket that we listen on for incoming
-  (tcp) connections
-*/
-int setup_listen_meta_socket(int port)
-{
-  int nfd, flags;
-  struct sockaddr_in a;
-  const int one = 1;
-cp
-  if((nfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
-    {
-      syslog(LOG_ERR, "Creating metasocket failed: %m");
-      return -1;
-    }
-
-  if(setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)))
-    {
-      syslog(LOG_ERR, "setsockopt: %m");
-      return -1;
-    }
-
-  flags = fcntl(nfd, F_GETFL);
-  if(fcntl(nfd, F_SETFL, flags | O_NONBLOCK) < 0)
-    {
-      syslog(LOG_ERR, "fcntl: %m");
-      return -1;
-    }
-
-  memset(&a, 0, sizeof(a));
-  a.sin_family = AF_INET;
-  a.sin_port = htons(port);
-  a.sin_addr.s_addr = htonl(INADDR_ANY);
-
-  if(bind(nfd, (struct sockaddr *)&a, sizeof(struct sockaddr)))
-    {
-      syslog(LOG_ERR, "Can't bind to port %hd/tcp: %m", port);
-      return -1;
-    }
-
-  if(listen(nfd, 3))
-    {
-      syslog(LOG_ERR, "listen: %m");
-      return -1;
-    }
-cp
-  return nfd;
-}
+       /* Check if this was our outgoing connection */
 
-/*
-  setup the socket for incoming encrypted
-  data (the udp part)
-*/
-int setup_vpn_in_socket(int port)
-{
-  int nfd, flags;
-  struct sockaddr_in a;
-  const int one = 1;
-cp
-  if((nfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
-    {
-      syslog(LOG_ERR, "Creating socket failed: %m");
-      return -1;
-    }
-
-  if(setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)))
-    {
-      syslog(LOG_ERR, "setsockopt: %m");
-      return -1;
-    }
-
-  flags = fcntl(nfd, F_GETFL);
-  if(fcntl(nfd, F_SETFL, flags | O_NONBLOCK) < 0)
-    {
-      syslog(LOG_ERR, "fcntl: %m");
-      return -1;
-    }
-
-  memset(&a, 0, sizeof(a));
-  a.sin_family = AF_INET;
-  a.sin_port = htons(port);
-  a.sin_addr.s_addr = htonl(INADDR_ANY);
-
-  if(bind(nfd, (struct sockaddr *)&a, sizeof(struct sockaddr)))
-    {
-      syslog(LOG_ERR, "Can't bind to port %hd/udp: %m", port);
-      return -1;
-    }
-cp
-  return nfd;
+       if(outgoing) {
+               do_outgoing_connection(mesh, outgoing);
+       }
 }
 
 /*
-  setup an outgoing meta (tcp) socket
+  Check if the other end is active.
+  If we have sent packets, but didn't receive any,
+  then possibly the other end is dead. We send a
+  PING request over the meta connection. If the other
+  end does not reply in time, we consider them dead
+  and close the connection.
 */
-int setup_outgoing_meta_socket(conn_list_t *cl)
-{
-  int flags;
-  struct sockaddr_in a;
-  config_t const *cfg;
-cp
-  if((cfg = get_config_val(upstreamport)) == NULL)
-    cl->port = 655;
-  else
-    cl->port = cfg->data.val;
-
-  cl->meta_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
-  if(cl->meta_socket == -1)
-    {
-      syslog(LOG_ERR, "Creating socket failed: %m");
-      return -1;
-    }
-
-  a.sin_family = AF_INET;
-  a.sin_port = htons(cl->port);
-  a.sin_addr.s_addr = htonl(cl->real_ip);
-
-  if(connect(cl->meta_socket, (struct sockaddr *)&a, sizeof(a)) == -1)
-    {
-      syslog(LOG_ERR, IP_ADDR_S ":%d: %m", IP_ADDR_V(cl->real_ip), cl->port);
-      return -1;
-    }
-
-  flags = fcntl(cl->meta_socket, F_GETFL);
-  if(fcntl(cl->meta_socket, F_SETFL, flags | O_NONBLOCK) < 0)
-    {
-      syslog(LOG_ERR, "fcntl: %m");
-      return -1;
-    }
-
-  cl->hostname = hostlookup(htonl(cl->real_ip));
-
-  syslog(LOG_INFO, "Connected to %s:%hd" , cl->hostname, cl->port);
-cp
-  return 0;
-}
+static void timeout_handler(event_loop_t *loop, void *data) {
+       assert(data);
+
+       meshlink_handle_t *mesh = loop->data;
+       logger(mesh, MESHLINK_DEBUG, "timeout_handler()");
+
+       for list_each(connection_t, c, mesh->connections) {
+               int pingtimeout = c->node ? mesh->dev_class_traits[c->node->devclass].pingtimeout : default_timeout;
+               int pinginterval = c->node ? mesh->dev_class_traits[c->node->devclass].pinginterval : default_interval;
+
+               if(c->outgoing && c->outgoing->timeout < 5) {
+                       pingtimeout = 1;
+               }
+
+               // Also make sure that if outstanding key requests for the UDP counterpart of a connection has timed out, we restart it.
+               if(c->node) {
+                       if(c->node->status.waitingforkey && c->node->last_req_key + pingtimeout <= mesh->loop.now.tv_sec) {
+                               send_req_key(mesh, c->node);
+                       }
+               }
+
+               if(c->status.active && c->last_key_renewal + 3600 < mesh->loop.now.tv_sec) {
+                       devtool_sptps_renewal_probe((meshlink_node_t *)c->node);
+
+                       if(!sptps_force_kex(&c->sptps)) {
+                               logger(mesh, MESHLINK_ERROR, "SPTPS key renewal for connection with %s failed", c->name);
+                               terminate_connection(mesh, c, true);
+                               continue;
+                       } else {
+                               c->last_key_renewal = mesh->loop.now.tv_sec;
+                       }
+               }
+
+               if(c->last_ping_time + pingtimeout <= mesh->loop.now.tv_sec) {
+                       if(c->status.active) {
+                               if(c->status.pinged) {
+                                       logger(mesh, MESHLINK_INFO, "%s didn't respond to PING in %ld seconds", c->name, (long)mesh->loop.now.tv_sec - c->last_ping_time);
+                               } else if(c->last_ping_time + pinginterval <= mesh->loop.now.tv_sec) {
+                                       send_ping(mesh, c);
+                                       continue;
+                               } else {
+                                       continue;
+                               }
+                       } else {
+                               if(c->status.connecting) {
+                                       logger(mesh, MESHLINK_WARNING, "Timeout while connecting to %s", c->name);
+                               } else {
+                                       logger(mesh, MESHLINK_WARNING, "Timeout from %s during authentication", c->name);
+                               }
+                       }
+
+                       terminate_connection(mesh, c, c->status.active);
+               }
+       }
 
-/*
-  setup an outgoing connection. It's not
-  necessary to also open an udp socket as
-  well, because the other host will initiate
-  an authentication sequence during which
-  we will do just that.
-*/
-int setup_outgoing_connection(ip_t ip)
-{
-  conn_list_t *ncn;
-cp
-  ncn = new_conn_list();
-  ncn->real_ip = ip;
-
-  if(setup_outgoing_meta_socket(ncn) < 0)
-    {
-      syslog(LOG_ERR, "Could not set up a meta connection.");
-      free_conn_element(ncn);
-      return -1;
-    }
-
-  ncn->status.meta = 1;
-  ncn->status.outgoing = 1;
-  ncn->next = conn_list;
-  conn_list = ncn;
-cp
-  return 0;
+       timeout_set(&mesh->loop, data, &(struct timespec) {
+               1, prng(mesh, TIMER_FUDGE)
+       });
 }
 
-/*
-  set up the local sockets (listen only)
-*/
-int setup_myself(void)
-{
-  config_t const *cfg;
-cp
-  myself = new_conn_list();
-
-  if(!(cfg = get_config_val(myvpnip)))
-    {
-      syslog(LOG_ERR, "No value for my VPN IP given");
-      return -1;
-    }
-
-  myself->vpn_ip = cfg->data.ip->ip;
-  myself->vpn_mask = cfg->data.ip->mask;
-
-  if(!(cfg = get_config_val(listenport)))
-    myself->port = 655;
-  else
-    myself->port = cfg->data.val;
-
-  if((myself->meta_socket = setup_listen_meta_socket(myself->port)) < 0)
-    {
-      syslog(LOG_ERR, "Unable to set up a listening socket");
-      return -1;
-    }
-
-  if((myself->socket = setup_vpn_in_socket(myself->port)) < 0)
-    {
-      syslog(LOG_ERR, "Unable to set up an incoming vpn data socket");
-      close(myself->meta_socket);
-      return -1;
-    }
-
-  myself->status.active = 1;
-
-  syslog(LOG_NOTICE, "Ready: listening on port %d.", myself->port);
-cp
-  return 0;
-}
+// devclass asc, last_successfull_connection desc
+static int node_compare_devclass_asc_lsc_desc(const void *a, const void *b) {
+       const node_t *na = a, *nb = b;
 
-/*
-  setup all initial network connections
-*/
-int setup_network_connections(void)
-{
-  config_t const *cfg;
-cp
-  if((cfg = get_config_val(pingtimeout)) == NULL)
-    timeout = 10;
-  else
-    timeout = cfg->data.val;
-
-  if(setup_tap_fd() < 0)
-    return -1;
-
-  if(setup_myself() < 0)
-    return -1;
-
-  if((cfg = get_config_val(upstreamip)) == NULL)
-    /* No upstream IP given, we're listen only. */
-    return 0;
-
-  if(setup_outgoing_connection(cfg->data.ip->ip))
-    return -1;
-cp
-  return 0;
-}
+       if(na->devclass < nb->devclass) {
+               return -1;
+       }
 
-RETSIGTYPE
-sigalrm_handler(int a)
-{
-  config_t const *cfg;
-  static int seconds_till_retry;
-cp
-  cfg = get_config_val(upstreamip);
-
-  if(!setup_outgoing_connection(cfg->data.ip->ip))
-    {
-      signal(SIGALRM, SIG_IGN);
-      seconds_till_retry = 5;
-    }
-  else
-    {
-      signal(SIGALRM, sigalrm_handler);
-      seconds_till_retry += 5;
-      alarm(seconds_till_retry);
-      syslog(LOG_ERR, "Still failed to connect to other. Will retry in %d seconds.",
-            seconds_till_retry);
-    }
-cp
-}
+       if(na->devclass > nb->devclass) {
+               return 1;
+       }
 
-/*
-  close all open network connections
-*/
-void close_network_connections(void)
-{
-  conn_list_t *p;
-cp
-  for(p = conn_list; p != NULL; p = p->next)
-    {
-      if(p->status.dataopen)
-       {
-         shutdown(p->socket, 0); /* No more receptions */
-         close(p->socket);
+       if(na->last_successfull_connection == nb->last_successfull_connection) {
+               return 0;
        }
-      if(p->status.meta)
-       {
-         send_termreq(p);
-         shutdown(p->meta_socket, 0); /* No more receptions */
-          close(p->meta_socket);
-        }
-    }
-
-  if(myself)
-    if(myself->status.active)
-      {
-       close(myself->meta_socket);
-       close(myself->socket);
-      }
-
-  close(tap_fd);
-  destroy_conn_list();
-
-  syslog(LOG_NOTICE, "Terminating.");
-cp
-  return;
-}
 
-/*
-  create a data (udp) socket
-*/
-int setup_vpn_connection(conn_list_t *cl)
-{
-  int nfd, flags;
-  struct sockaddr_in a;
-cp
-  if(debug_lvl > 1)
-    syslog(LOG_DEBUG, "Opening UDP socket to " IP_ADDR_S, IP_ADDR_V(cl->real_ip));
-
-  nfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
-  if(nfd == -1)
-    {
-      syslog(LOG_ERR, "Creating data socket failed: %m");
-      return -1;
-    }
-
-  a.sin_family = AF_INET;
-  a.sin_port = htons(cl->port);
-  a.sin_addr.s_addr = htonl(cl->real_ip);
-
-  if(connect(nfd, (struct sockaddr *)&a, sizeof(a)) == -1)
-    {
-      syslog(LOG_ERR, "Create connection to %08lx:%d failed: %m", ntohs(cl->real_ip),
-            cl->port);
-      return -1;
-    }
-
-  flags = fcntl(nfd, F_GETFL);
-  if(fcntl(nfd, F_SETFL, flags | O_NONBLOCK) < 0)
-    {
-      syslog(LOG_ERR, "This is a bug: %s:%d: %d:%m", __FILE__, __LINE__, nfd);
-      return -1;
-    }
-
-  cl->socket = nfd;
-  cl->status.dataopen = 1;
-cp
-  return 0;
-}
+       if(na->last_successfull_connection == 0 || na->last_successfull_connection > nb->last_successfull_connection) {
+               return -1;
+       }
 
-/*
-  handle an incoming tcp connect call and open
-  a connection to it.
-*/
-conn_list_t *create_new_connection(int sfd)
-{
-  conn_list_t *p;
-  struct sockaddr_in ci;
-  int len = sizeof(ci);
-cp
-  p = new_conn_list();
-
-  if(getpeername(sfd, &ci, &len) < 0)
-    {
-      syslog(LOG_ERR, "Error: getpeername: %m");
-      return NULL;
-    }
-
-  p->hostname = hostlookup(ci.sin_addr.s_addr);
-  p->real_ip = ntohl(ci.sin_addr.s_addr);
-  p->meta_socket = sfd;
-  p->status.meta = 1;
-  p->buflen = 0;
-  
-  syslog(LOG_NOTICE, "Connection from %s:%d", p->hostname, htons(ci.sin_port));
-
-  if(send_basic_info(p) < 0)
-    {
-      free(p);
-      return NULL;
-    }
-cp
-  return p;
-}
+       if(nb->last_successfull_connection == 0 || na->last_successfull_connection < nb->last_successfull_connection) {
+               return 1;
+       }
 
-/*
-  put all file descriptors in an fd_set array
-*/
-void build_fdset(fd_set *fs)
-{
-  conn_list_t *p;
-cp
-  FD_ZERO(fs);
-
-  for(p = conn_list; p != NULL; p = p->next)
-    {
-      if(p->status.meta)
-       FD_SET(p->meta_socket, fs);
-      if(p->status.dataopen)
-       FD_SET(p->socket, fs);
-    }
-
-  FD_SET(myself->meta_socket, fs);
-  FD_SET(myself->socket, fs);
-  FD_SET(tap_fd, fs);
-cp
+       if(na < nb) {
+               return -1;
+       }
+
+       if(na > nb) {
+               return 1;
+       }
+
+       return 0;
 }
 
-/*
-  receive incoming data from the listening
-  udp socket and write it to the ethertap
-  device after being decrypted
-*/
-int handle_incoming_vpn_data(conn_list_t *cl)
-{
-  real_packet_t rp;
-  int lenin;
-  int x, l = sizeof(x);
-  conn_list_t *f;
-cp
-  if(getsockopt(cl->socket, SOL_SOCKET, SO_ERROR, &x, &l) < 0)
-    {
-      syslog(LOG_ERR, "This is a bug: %s:%d: %d:%m", __FILE__, __LINE__, cl->socket);
-      return -1;
-    }
-  if(x)
-    {
-      syslog(LOG_ERR, "Incoming data socket error: %s", sys_errlist[x]);
-      return -1;
-    }
-
-  rp.len = -1;
-  lenin = recvfrom(cl->socket, &rp, MTU, 0, NULL, NULL);
-  if(lenin <= 0)
-    {
-      syslog(LOG_ERR, "Receiving data failed: %m");
-      return -1;
-    }
-  total_socket_in += lenin;
-
-  rp.data.len = ntohs(rp.data.len);
-  rp.len = ntohs(rp.len);
-  rp.from = ntohl(rp.from);
-
-  if(rp.len >= 0)
-    {
-      f = lookup_conn(rp.from);
-      if(debug_lvl > 3)
-       syslog(LOG_DEBUG, "packet from " IP_ADDR_S " (len %d)",
-              IP_ADDR_V(rp.from), rp.len);
-      if(!f)
-       {
-         syslog(LOG_ERR, "Got packet from unknown source " IP_ADDR_S,
-                IP_ADDR_V(rp.from));
-         return -1;
+// last_successfull_connection desc
+static int node_compare_lsc_desc(const void *a, const void *b) {
+       const node_t *na = a, *nb = b;
+
+       if(na->last_successfull_connection == nb->last_successfull_connection) {
+               return 0;
        }
 
-      if(f->status.validkey)
-       xrecv(f, &rp);
-      else
-       {
-         add_queue(&(f->rq), &rp, rp.len);
-         if(!cl->status.waitingforkey)
-           send_key_request(rp.from);
+       if(na->last_successfull_connection == 0 || na->last_successfull_connection > nb->last_successfull_connection) {
+               return -1;
        }
 
-      if(my_key_expiry <= time(NULL))
-       regenerate_keys();
-    }
-cp
-  return 0;
-}
+       if(nb->last_successfull_connection == 0 || na->last_successfull_connection < nb->last_successfull_connection) {
+               return 1;
+       }
 
-/*
-  terminate a connection and notify the other
-  end before closing the sockets
-*/
-void terminate_connection(conn_list_t *cl)
-{
-cp
-  if(cl->status.remove)
-    return;
-
-  if(debug_lvl > 0)
-    syslog(LOG_NOTICE, "Closing connection with %s.", cl->hostname);
-
-  if(cl->status.timeout)
-    send_timeout(cl);
-  else if(!cl->status.termreq)
-    send_termreq(cl);
-
-  close(cl->socket);
-  if(cl->status.meta)
-    close(cl->meta_socket);
-
-  if(cl->status.outgoing)
-    {
-      alarm(5);
-      signal(SIGALRM, sigalrm_handler);
-      syslog(LOG_NOTICE, "Try to re-establish outgoing connection in 5 seconds.");
-    }
-  
-  cl->status.remove = 1;
-cp
-}
+       if(na < nb) {
+               return -1;
+       }
 
-/*
-  send out a ping request to all active
-  connections
-*/
-int send_broadcast_ping(void)
-{
-  conn_list_t *p;
-cp
-  for(p = conn_list; p != NULL; p = p->next)
-    {
-      if(p->status.remove)
-       continue;
-      if(p->status.active && p->status.meta)
-       {
-         if(send_ping(p))
-           terminate_connection(p);
-         else
-           {
-             p->status.pinged = 1;
-             p->status.got_pong = 0;
-           }
+       if(na > nb) {
+               return 1;
        }
-    }
 
-  last_ping_time = time(NULL);
-cp
-  return 0;
+       return 0;
 }
 
-/*
-  end all connections that did not respond
-  to the ping probe in time
-*/
-int check_dead_connections(void)
-{
-  conn_list_t *p;
-cp
-  for(p = conn_list; p != NULL; p = p->next)
-    {
-      if(p->status.remove)
-       continue;
-      if(p->status.active && p->status.meta && p->status.pinged && !p->status.got_pong)
-       {
-         syslog(LOG_INFO, "%s (" IP_ADDR_S ") didn't respond to ping",
-                p->hostname, IP_ADDR_V(p->vpn_ip));
-         p->status.timeout = 1;
-         terminate_connection(p);
+// devclass desc
+static int node_compare_devclass_desc(const void *a, const void *b) {
+       const node_t *na = a, *nb = b;
+
+       if(na->devclass < nb->devclass) {
+               return -1;
+       }
+
+       if(na->devclass > nb->devclass) {
+               return 1;
+       }
+
+       if(na < nb) {
+               return -1;
+       }
+
+       if(na > nb) {
+               return 1;
        }
-    }
-cp
-  return 0;
+
+       return 0;
 }
 
+
 /*
-  accept a new tcp connect and create a
-  new connection
-*/
-int handle_new_meta_connection(conn_list_t *cl)
+
+autoconnect()
 {
-  conn_list_t *ncn;
-  struct sockaddr client;
-  int nfd, len = sizeof(client);
-cp
-  if((nfd = accept(cl->meta_socket, &client, &len)) < 0)
-    {
-      syslog(LOG_ERR, "Accepting a new connection failed: %m");
-      return -1;
-    }
-
-  if((ncn = create_new_connection(nfd)) == NULL)
-    {
-      shutdown(nfd, 2);
-      close(nfd);
-      syslog(LOG_NOTICE, "Closed attempted connection.");
-      return 0;
-    }
-
-  ncn->status.meta = 1;
-  ncn->next = conn_list;
-  conn_list = ncn;
-cp
-  return 0;
+        timeout = 5
+
+        // find the best one for initial connect
+
+        if cur < min
+                newcon =
+                        first from nodes
+                                where dclass <= my.dclass and !connection and (timestamp - last_retry) > retry_timeout
+                                order by dclass asc, last_connection desc
+                if newcon
+                        timeout = 0
+                        goto connect
+
+
+        // find better nodes to connect to: in case we have less than min connections within [BACKBONE, i] and there are nodes which we are not connected to within the range
+
+        if min <= cur < max
+                j = 0
+                for i = BACKBONE to my.dclass
+                        j += count(from connections where node.dclass = i)
+                        if j < min
+                                newcon =
+                                        first from nodes
+                                                where dclass = i and !connection and (timestamp - last_retry) > retry_timeout
+                                                order by last_connection desc
+                                if newcon
+                                        goto connect
+                        else
+                                break
+
+
+        // heal partitions
+
+        if min <= cur < max
+                newcon =
+                        first from nodes
+                                where dclass <= my.dclass and !reachable and (timestamp - last_retry) > retry_timeout
+                                order by dclass asc, last_connection desc
+                if newcon
+                        goto connect
+
+
+        // connect
+
+connect:
+        if newcon
+                connect newcon
+
+
+        // disconnect outgoing connections in case we have more than min connections within [BACKBONE, i] and there are nodes which we are connected to within the range [i, PORTABLE]
+
+        if min < cur <= max
+                j = 0
+                for i = BACKBONE to my.dclass
+                        j += count(from connections where node.dclass = i)
+                        if min < j
+                                delcon =
+                                        first from nodes
+                                                where dclass >= i and outgoing_connection
+                                                order by dclass desc
+                                if disconnect
+                                        goto disconnect
+                                else
+                                        break
+
+
+        // disconnect connections in case we have more than enough connections
+
+        if max < cur
+                delcon =
+                        first from nodes
+                                where outgoing_connection
+                                order by dclass desc
+                goto disconnect
+
+        // disconnect
+
+disconnect
+        if delcon
+                disconnect delcon
+
+
+        // next iteration
+        next (timeout, autoconnect)
+
 }
 
-/*
-  dispatch any incoming meta requests
 */
-int handle_incoming_meta_data(conn_list_t *cl)
-{
-  int x, l = sizeof(x);
-  int request, oldlen, i;
-  int lenin = 0;
-cp
-  if(getsockopt(cl->meta_socket, SOL_SOCKET, SO_ERROR, &x, &l) < 0)
-    {
-      syslog(LOG_ERR, "This is a bug: %s:%d: %d:%m", __FILE__, __LINE__, cl->meta_socket);
-      return -1;
-    }
-  if(x)
-    {
-      syslog(LOG_ERR, "Metadata socket error: %s", sys_errlist[x]);
-      return -1;
-    }
-
-  if(cl->buflen >= MAXBUFSIZE)
-    {
-      syslog(LOG_ERR, "Metadata read buffer full! Discarding contents.");
-      cl->buflen = 0;
-    }
-
-  lenin = read(cl->meta_socket, cl->buffer, MAXBUFSIZE-cl->buflen);
-
-  if(lenin<=0)
-    {
-      syslog(LOG_ERR, "Metadata socket read error: %m");
-      return -1;
-    }
-
-  oldlen = cl->buflen;
-  cl->buflen += lenin;
-
-  for(;;)
-    {
-      cl->reqlen = 0;
-
-      for(i = oldlen; i < cl->buflen; i++)
-        {
-          if(cl->buffer[i] == '\n')
-            {
-              cl->buffer[i] = 0;  /* replace end-of-line by end-of-string so we can use sscanf */
-              cl->reqlen = i + 1;
-              break;
-            }
-        }
-
-      if(cl->reqlen)
-        {
-          if(sscanf(cl->buffer, "%d", &request) == 1)
-            {
-              if(request_handlers[request] == NULL)
-                {
-                  syslog(LOG_ERR, "Unknown request: %s", cl->buffer);
-                  return 0;
-                }
-
-              if(debug_lvl > 3)
-                syslog(LOG_DEBUG, "Got request: %s", cl->buffer);                             
-
-              request_handlers[request](cl);
-            }
-          else
-            {
-              syslog(LOG_ERR, "Bogus data received: %s", cl->buffer);
-            }
-
-          cl->buflen -= cl->reqlen;
-          memmove(cl->buffer, cl->buffer + cl->reqlen, cl->buflen);
-          oldlen = 0;
-        }
-      else
-        {
-          break;
-        }
-    }
-cp  
-  return 0;
+
+
+static void periodic_handler(event_loop_t *loop, void *data) {
+       meshlink_handle_t *mesh = loop->data;
+
+       /* Check if there are too many contradicting ADD_EDGE and DEL_EDGE messages.
+          This usually only happens when another node has the same Name as this node.
+          If so, sleep for a short while to prevent a storm of contradicting messages.
+       */
+
+       if(mesh->contradicting_del_edge > 100 && mesh->contradicting_add_edge > 100) {
+               logger(mesh, MESHLINK_WARNING, "Possible node with same Name as us! Sleeping %d seconds.", mesh->sleeptime);
+               struct timespec ts = {mesh->sleeptime, 0};
+               nanosleep(&ts, NULL);
+               mesh->sleeptime *= 2;
+
+               if(mesh->sleeptime < 0) {
+                       mesh->sleeptime = 3600;
+               }
+       } else {
+               mesh->sleeptime /= 2;
+
+               if(mesh->sleeptime < 10) {
+                       mesh->sleeptime = 10;
+               }
+       }
+
+       mesh->contradicting_add_edge = 0;
+       mesh->contradicting_del_edge = 0;
+
+       int timeout = default_timeout;
+
+       /* Check if we need to make or break connections. */
+
+       if(mesh->nodes->count > 1) {
+
+               logger(mesh, MESHLINK_DEBUG, "--- autoconnect begin ---");
+
+               int retry_timeout = min(mesh->nodes->count * default_timeout, 60);
+
+               logger(mesh, MESHLINK_DEBUG, "* devclass = %d", mesh->devclass);
+               logger(mesh, MESHLINK_DEBUG, "* nodes = %d", mesh->nodes->count);
+               logger(mesh, MESHLINK_DEBUG, "* retry_timeout = %d", retry_timeout);
+
+
+               // connect disconnect nodes
+
+               node_t *connect_to = NULL;
+               node_t *disconnect_from = NULL;
+
+
+               // get cur_connects
+
+               unsigned int cur_connects = 0;
+
+               for list_each(connection_t, c, mesh->connections) {
+                       if(c->status.active) {
+                               cur_connects += 1;
+                       }
+               }
+
+               logger(mesh, MESHLINK_DEBUG, "* cur_connects = %d", cur_connects);
+               logger(mesh, MESHLINK_DEBUG, "* outgoings = %d", mesh->outgoings->count);
+
+               // get min_connects and max_connects
+
+               unsigned int min_connects = mesh->dev_class_traits[mesh->devclass].min_connects;
+               unsigned int max_connects = mesh->dev_class_traits[mesh->devclass].max_connects;
+
+               logger(mesh, MESHLINK_DEBUG, "* min_connects = %d", min_connects);
+               logger(mesh, MESHLINK_DEBUG, "* max_connects = %d", max_connects);
+
+               // find the best one for initial connect
+
+               if(cur_connects < min_connects) {
+                       splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_asc_lsc_desc, NULL);
+
+                       for splay_each(node_t, n, mesh->nodes) {
+                               logger(mesh, MESHLINK_DEBUG, "* %s->devclass = %d", n->name, n->devclass);
+
+                               if(n != mesh->self && n->devclass <= mesh->devclass && !n->connection && !n->status.blacklisted && (n->last_connect_try == 0 || (mesh->loop.now.tv_sec - n->last_connect_try) > retry_timeout)) {
+                                       splay_insert(nodes, n);
+                               }
+                       }
+
+                       if(nodes->head) {
+                               //timeout = 0;
+                               connect_to = (node_t *)nodes->head->data;
+
+                               logger(mesh, MESHLINK_DEBUG, "* found best one for initial connect: %s", connect_to->name);
+                       } else {
+                               logger(mesh, MESHLINK_DEBUG, "* could not find node for initial connect");
+                       }
+
+                       splay_delete_tree(nodes);
+               }
+
+
+               // find better nodes to connect to
+
+               if(!connect_to && min_connects <= cur_connects && cur_connects < max_connects) {
+                       unsigned int connects = 0;
+
+                       for(dev_class_t devclass = 0; devclass <= mesh->devclass; ++devclass) {
+                               for list_each(connection_t, c, mesh->connections) {
+                                       if(c->status.active && c->node && c->node->devclass == devclass) {
+                                               connects += 1;
+                                       }
+                               }
+
+                               if(connects < min_connects) {
+                                       splay_tree_t *nodes = splay_alloc_tree(node_compare_lsc_desc, NULL);
+
+                                       for splay_each(node_t, n, mesh->nodes) {
+                                               if(n != mesh->self && n->devclass == devclass && !n->connection && !n->status.blacklisted && (n->last_connect_try == 0 || (mesh->loop.now.tv_sec - n->last_connect_try) > retry_timeout)) {
+                                                       splay_insert(nodes, n);
+                                               }
+                                       }
+
+                                       if(nodes->head) {
+                                               logger(mesh, MESHLINK_DEBUG, "* found better node");
+                                               connect_to = (node_t *)nodes->head->data;
+
+                                               splay_delete_tree(nodes);
+                                               break;
+                                       }
+
+                                       splay_delete_tree(nodes);
+                               } else {
+                                       break;
+                               }
+                       }
+
+                       if(!connect_to) {
+                               logger(mesh, MESHLINK_DEBUG, "* could not find better nodes");
+                       }
+               }
+
+
+               // heal partitions
+
+               if(!connect_to && min_connects <= cur_connects && cur_connects < max_connects) {
+                       splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_asc_lsc_desc, NULL);
+
+                       for splay_each(node_t, n, mesh->nodes) {
+                               if(n != mesh->self && n->devclass <= mesh->devclass && !n->status.reachable && !n->status.blacklisted && (n->last_connect_try == 0 || (mesh->loop.now.tv_sec - n->last_connect_try) > retry_timeout)) {
+                                       splay_insert(nodes, n);
+                               }
+                       }
+
+                       if(nodes->head) {
+                               logger(mesh, MESHLINK_DEBUG, "* try to heal partition");
+                               connect_to = (node_t *)nodes->head->data;
+                       } else {
+                               logger(mesh, MESHLINK_DEBUG, "* could not find nodes for partition healing");
+                       }
+
+                       splay_delete_tree(nodes);
+               }
+
+
+               // perform connect
+
+               if(connect_to && !connect_to->connection) {
+                       connect_to->last_connect_try = mesh->loop.now.tv_sec;
+                       logger(mesh, MESHLINK_DEBUG, "Autoconnect trying to connect to %s", connect_to->name);
+
+                       /* check if there is already a connection attempt to this node */
+                       bool skip = false;
+
+                       for list_each(outgoing_t, outgoing, mesh->outgoings) {
+                               if(outgoing->node == connect_to) {
+                                       logger(mesh, MESHLINK_DEBUG, "* skip autoconnect since it is an outgoing connection already");
+                                       skip = true;
+                                       break;
+                               }
+                       }
+
+                       if(!connect_to->status.reachable && !node_read_public_key(mesh, connect_to)) {
+                               logger(mesh, MESHLINK_DEBUG, "* skip autoconnect since we don't know this node's public key");
+                               skip = true;
+                       }
+
+                       if(!skip) {
+                               logger(mesh, MESHLINK_DEBUG, "Autoconnecting to %s", connect_to->name);
+                               outgoing_t *outgoing = xzalloc(sizeof(outgoing_t));
+                               outgoing->node = connect_to;
+                               list_insert_tail(mesh->outgoings, outgoing);
+                               setup_outgoing_connection(mesh, outgoing);
+                       }
+               }
+
+
+               // disconnect suboptimal outgoing connections
+
+               if(min_connects < cur_connects /*&& cur_connects <= max_connects*/) {
+                       unsigned int connects = 0;
+
+                       for(dev_class_t devclass = 0; devclass <= mesh->devclass; ++devclass) {
+                               for list_each(connection_t, c, mesh->connections) {
+                                       if(c->status.active && c->node && c->node->devclass == devclass) {
+                                               connects += 1;
+                                       }
+                               }
+
+                               if(min_connects < connects) {
+                                       splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_desc, NULL);
+
+                                       for list_each(connection_t, c, mesh->connections) {
+                                               if(c->outgoing && c->node && c->node->devclass >= devclass) {
+                                                       splay_insert(nodes, c->node);
+                                               }
+                                       }
+
+                                       if(nodes->head) {
+                                               logger(mesh, MESHLINK_DEBUG, "* disconnect suboptimal outgoing connection");
+                                               disconnect_from = (node_t *)nodes->head->data;
+                                       }
+
+                                       splay_delete_tree(nodes);
+                                       break;
+                               }
+                       }
+
+                       if(!disconnect_from) {
+                               logger(mesh, MESHLINK_DEBUG, "* no suboptimal outgoing connections");
+                       }
+               }
+
+
+               // disconnect connections (too many connections)
+
+               if(!disconnect_from && max_connects < cur_connects) {
+                       splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_desc, NULL);
+
+                       for list_each(connection_t, c, mesh->connections) {
+                               if(c->status.active && c->node) {
+                                       splay_insert(nodes, c->node);
+                               }
+                       }
+
+                       if(nodes->head) {
+                               logger(mesh, MESHLINK_DEBUG, "* disconnect connection (too many connections)");
+
+                               //timeout = 0;
+                               disconnect_from = (node_t *)nodes->head->data;
+                       } else {
+                               logger(mesh, MESHLINK_DEBUG, "* no node we want to disconnect, even though we have too many connections");
+                       }
+
+                       splay_delete_tree(nodes);
+               }
+
+
+               // perform disconnect
+
+               if(disconnect_from && disconnect_from->connection) {
+                       logger(mesh, MESHLINK_DEBUG, "Autodisconnecting from %s", disconnect_from->connection->name);
+                       list_delete(mesh->outgoings, disconnect_from->connection->outgoing);
+                       disconnect_from->connection->outgoing = NULL;
+                       terminate_connection(mesh, disconnect_from->connection, disconnect_from->connection->status.active);
+               }
+
+               // reduce timeout if we don't have enough connections + outgoings
+               if(cur_connects + mesh->outgoings->count < 3) {
+                       timeout = 1;
+               }
+
+               // done!
+
+               logger(mesh, MESHLINK_DEBUG, "--- autoconnect end ---");
+       }
+
+       for splay_each(node_t, n, mesh->nodes) {
+               if(n->status.dirty) {
+                       if(!node_write_config(mesh, n)) {
+                               logger(mesh, MESHLINK_DEBUG, "Could not update %s", n->name);
+                       }
+
+                       n->status.dirty = false;
+               }
+
+               if(n->status.validkey && n->last_req_key + 3600 < mesh->loop.now.tv_sec) {
+                       logger(mesh, MESHLINK_DEBUG, "SPTPS key renewal for node %s", n->name);
+                       devtool_sptps_renewal_probe((meshlink_node_t *)n);
+
+                       if(!sptps_force_kex(&n->sptps)) {
+                               logger(mesh, MESHLINK_ERROR, "SPTPS key renewal for node %s failed", n->name);
+                               n->status.validkey = false;
+                               sptps_stop(&n->sptps);
+                               n->status.waitingforkey = false;
+                               n->last_req_key = -3600;
+                       } else {
+                               n->last_req_key = mesh->loop.now.tv_sec;
+                       }
+               }
+       }
+
+       timeout_set(&mesh->loop, data, &(struct timespec) {
+               timeout, prng(mesh, TIMER_FUDGE)
+       });
 }
 
-/*
-  check all connections to see if anything
-  happened on their sockets
-*/
-void check_network_activity(fd_set *f)
-{
-  conn_list_t *p;
-  int x, l = sizeof(x);
-cp
-  for(p = conn_list; p != NULL; p = p->next)
-    {
-      if(p->status.remove)
-       continue;
-
-      if(p->status.dataopen)
-       if(FD_ISSET(p->socket, f))
-         {
-           /*
-             The only thing that can happen to get us here is apparently an
-             error on this outgoing(!) UDP socket that isn't immediate (i.e.
-             something that will not trigger an error directly on send()).
-             I've once got here when it said `No route to host'.
-           */
-           getsockopt(p->socket, SOL_SOCKET, SO_ERROR, &x, &l);
-           syslog(LOG_ERR, "Outgoing data socket error: %s", sys_errlist[x]);
-           terminate_connection(p);
-           return;
-         }  
-
-      if(p->status.meta)
-       if(FD_ISSET(p->meta_socket, f))
-         if(handle_incoming_meta_data(p) < 0)
-           {
-             terminate_connection(p);
-             return;
-           } 
-    }
-  
-  if(FD_ISSET(myself->socket, f))
-    handle_incoming_vpn_data(myself);
-
-  if(FD_ISSET(myself->meta_socket, f))
-    handle_new_meta_connection(myself);
-cp
+void handle_meta_connection_data(meshlink_handle_t *mesh, connection_t *c) {
+       if(!receive_meta(mesh, c)) {
+               terminate_connection(mesh, c, c->status.active);
+               return;
+       }
 }
 
-/*
-  read, encrypt and send data that is
-  available through the ethertap device
-*/
-void handle_tap_input(void)
-{
-  vpn_packet_t vp;
-  ip_t from, to;
-  int ether_type, lenin;
-cp  
-  memset(&vp, 0, sizeof(vp));
-  if((lenin = read(tap_fd, &vp, MTU)) <= 0)
-    {
-      syslog(LOG_ERR, "Error while reading from tapdevice: %m");
-      return;
-    }
-
-  total_tap_in += lenin;
-
-  ether_type = ntohs(*((unsigned short*)(&vp.data[12])));
-  if(ether_type != 0x0800)
-    {
-      if(debug_lvl > 0)
-       syslog(LOG_INFO, "Non-IP ethernet frame %04x from " MAC_ADDR_S,
-              ether_type, MAC_ADDR_V(vp.data[6]));
-      return;
-    }
-  
-  if(lenin < 32)
-    {
-      if(debug_lvl > 0)
-       syslog(LOG_INFO, "Dropping short packet");
-      return;
-    }
-
-  from = ntohl(*((unsigned long*)(&vp.data[26])));
-  to = ntohl(*((unsigned long*)(&vp.data[30])));
-
-  if(debug_lvl > 3)
-    syslog(LOG_DEBUG, "An IP packet (%04x) for " IP_ADDR_S " from " IP_ADDR_S,
-          ether_type, IP_ADDR_V(to), IP_ADDR_V(from));
-  if(debug_lvl > 4)
-    syslog(LOG_DEBUG, MAC_ADDR_S " to " MAC_ADDR_S,
-          MAC_ADDR_V(vp.data[0]), MAC_ADDR_V(vp.data[6]));
-  
-  vp.len = (length_t)lenin - 2;
-
-  strip_mac_addresses(&vp);
-
-  send_packet(to, &vp);
-cp
+void retry(meshlink_handle_t *mesh) {
+       /* Reset the reconnection timers for all outgoing connections */
+       for list_each(outgoing_t, outgoing, mesh->outgoings) {
+               outgoing->timeout = 0;
+
+               if(outgoing->ev.cb) {
+                       timeout_set(&mesh->loop, &outgoing->ev, &(struct timespec) {
+                               0, 0
+                       });
+               }
+       }
+
+       /* For active connections, check if their addresses are still valid.
+        * If yes, reset their ping timers, otherwise terminate them. */
+       for list_each(connection_t, c, mesh->connections) {
+               if(!c->status.active) {
+                       continue;
+               }
+
+               if(!c->status.pinged) {
+                       c->last_ping_time = -3600;
+               }
+
+               sockaddr_t sa;
+               socklen_t salen = sizeof(sa);
+
+               if(getsockname(c->socket, &sa.sa, &salen)) {
+                       continue;
+               }
+
+               switch(sa.sa.sa_family) {
+               case AF_INET:
+                       sa.in.sin_port = 0;
+                       break;
+
+               case AF_INET6:
+                       sa.in6.sin6_port = 0;
+                       break;
+
+               default:
+                       continue;
+               }
+
+               int sock = socket(sa.sa.sa_family, SOCK_STREAM, IPPROTO_TCP);
+
+               if(sock != -1) {
+                       continue;
+               }
+
+               if(bind(sock, &sa.sa, salen) && errno == EADDRNOTAVAIL) {
+                       logger(mesh, MESHLINK_DEBUG, "Local address for connection to %s no longer valid, terminating", c->name);
+                       terminate_connection(mesh, c, c->status.active);
+               }
+
+               closesocket(sock);
+       }
+
+       /* Kick the ping timeout handler */
+       timeout_set(&mesh->loop, &mesh->pingtimer, &(struct timespec) {
+               0, 0
+       });
 }
 
 /*
   this is where it all happens...
 */
-void main_loop(void)
-{
-  fd_set fset;
-  struct timeval tv;
-  int r;
-cp
-  last_ping_time = time(NULL);
-
-  for(;;)
-    {
-      tv.tv_sec = timeout;
-      tv.tv_usec = 0;
-
-      prune_conn_list();
-      build_fdset(&fset);
-
-      if((r = select(FD_SETSIZE, &fset, NULL, NULL, &tv)) < 0)
-        {
-         if(errno == EINTR) /* because of alarm */
-           continue;
-          syslog(LOG_ERR, "Error while waiting for input: %m");
-          return;
-        }
-
-      if(r == 0 || last_ping_time + timeout < time(NULL))
-       /* Timeout... hm... something might be wrong. */
-       {
-         check_dead_connections();
-         send_broadcast_ping();
-         continue;
+void main_loop(meshlink_handle_t *mesh) {
+       timeout_add(&mesh->loop, &mesh->pingtimer, timeout_handler, &mesh->pingtimer, &(struct timespec) {
+               1, prng(mesh, TIMER_FUDGE)
+       });
+       timeout_add(&mesh->loop, &mesh->periodictimer, periodic_handler, &mesh->periodictimer, &(struct timespec) {
+               0, 0
+       });
+
+       //Add signal handler
+       mesh->datafromapp.signum = 0;
+       signal_add(&mesh->loop, &mesh->datafromapp, meshlink_send_from_queue, mesh, mesh->datafromapp.signum);
+
+       if(!event_loop_run(&mesh->loop, &mesh->mutex)) {
+               logger(mesh, MESHLINK_ERROR, "Error while waiting for input: %s", strerror(errno));
+               call_error_cb(mesh, MESHLINK_ENETWORK);
        }
 
-      check_network_activity(&fset);
-
-      /* local tap data */
-      if(FD_ISSET(tap_fd, &fset))
-       handle_tap_input();
-    }
-cp
+       signal_del(&mesh->loop, &mesh->datafromapp);
+       timeout_del(&mesh->loop, &mesh->periodictimer);
+       timeout_del(&mesh->loop, &mesh->pingtimer);
 }