Copy packets before putting them in the queue.
[tinc] / src / net.c
index cb32cab..4a369ff 100644 (file)
--- a/src/net.c
+++ b/src/net.c
@@ -1,7 +1,7 @@
 /*
     net.c -- most of the network code
-    Copyright (C) 1998,1999,2000 Ivo Timmermans <itimmermans@bigfoot.com>,
-                            2000 Guus Sliepen <guus@sliepen.warande.net>
+    Copyright (C) 1998-2001 Ivo Timmermans <itimmermans@bigfoot.com>,
+                  2000,2001 Guus Sliepen <guus@sliepen.warande.net>
 
     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
@@ -17,7 +17,7 @@
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-    $Id: net.c,v 1.35.4.88 2000/12/22 21:34:20 guus Exp $
+    $Id: net.c,v 1.35.4.95 2001/02/25 16:04:00 guus Exp $
 */
 
 #include "config.h"
 
 #include <utils.h>
 #include <xalloc.h>
+#include <avl_tree.h>
+#include <list.h>
 
 #include "conf.h"
 #include "connection.h"
-#include "list.h"
 #include "meta.h"
 #include "net.h"
 #include "netutl.h"
@@ -111,7 +112,7 @@ int xsend(connection_t *cl, vpn_packet_t *inpkt)
 cp
   outpkt.len = inpkt->len;
   
-  /* Encrypt the packet */
+  /* Encrypt the packet. FIXME: we should use CBC, not CFB. */
   
   EVP_EncryptInit(&ctx, cl->cipher_pkttype, cl->cipher_pktkey, cl->cipher_pktkey + cl->cipher_pkttype->key_len);
   EVP_EncryptUpdate(&ctx, outpkt.data, &outlen, inpkt->data, inpkt->len);
@@ -162,156 +163,38 @@ cp
   outlen = outpkt.len+2;
   memcpy(&outpkt, inpkt, outlen);
 */
-     
+cp
+  return receive_packet(cl, &outpkt);
+}
+
+int receive_packet(connection_t *cl, vpn_packet_t *packet)
+{
   if(debug_lvl >= DEBUG_TRAFFIC)
     syslog(LOG_ERR, _("Writing packet of %d bytes to tap device"),
-           outpkt.len, outlen);
+           packet->len);
 
   /* Fix mac address */
 
-  memcpy(outpkt.data, mymac.net.mac.address.x, 6);
+  memcpy(packet->data, mymac.net.mac.address.x, 6);
 
   if(taptype == TAP_TYPE_TUNTAP)
     {
-      if(write(tap_fd, outpkt.data, outpkt.len) < 0)
+      if(write(tap_fd, packet->data, packet->len) < 0)
         syslog(LOG_ERR, _("Can't write to tun/tap device: %m"));
       else
-        total_tap_out += outpkt.len;
+        total_tap_out += packet->len;
     }
   else /* ethertap */
     {
-      if(write(tap_fd, outpkt.data - 2, outpkt.len + 2) < 0)
+      if(write(tap_fd, packet->data - 2, packet->len + 2) < 0)
         syslog(LOG_ERR, _("Can't write to ethertap device: %m"));
       else
-        total_tap_out += outpkt.len + 2;
+        total_tap_out += packet->len + 2;
     }
 cp
   return 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
-  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
-}
-
-/* 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
-}
-
-/*
-  flush a queue by calling function for
-  each packet, and removing it when that
-  returned a zero exit code
-*/
-void flush_queue(connection_t *cl, packet_queue_t **pq,
-                int (*function)(connection_t*,vpn_packet_t*))
-{
-  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 >= DEBUG_TRAFFIC)
-    syslog(LOG_DEBUG, _("Queue flushed"));
-cp
-}
-
-/*
-  flush the send&recv queues
-  void because nothing goes wrong here, packets
-  remain in the queue if something goes wrong
-*/
-void flush_queues(connection_t *cl)
-{
-cp
-  if(cl->sq)
-    {
-      if(debug_lvl >= DEBUG_TRAFFIC)
-       syslog(LOG_DEBUG, _("Flushing send queue for %s (%s)"),
-              cl->name, cl->hostname);
-      flush_queue(cl, &(cl->sq), xsend);
-    }
-
-  if(cl->rq)
-    {
-      if(debug_lvl >=  DEBUG_TRAFFIC)
-       syslog(LOG_DEBUG, _("Flushing receive queue for %s (%s)"),
-              cl->name, cl->hostname);
-      flush_queue(cl, &(cl->rq), xrecv);
-    }
-cp
-}
-
 /*
   send a packet to the given vpn ip.
 */
@@ -319,8 +202,9 @@ int send_packet(ip_t to, vpn_packet_t *packet)
 {
   connection_t *cl;
   subnet_t *subnet;
+  vpn_packet_t *copy;
 cp
-  if((subnet = lookup_subnet_ipv4(to)) == NULL)
+  if((subnet = lookup_subnet_ipv4(&to)) == NULL)
     {
       if(debug_lvl >= DEBUG_TRAFFIC)
         {
@@ -344,37 +228,56 @@ cp
       return -1;
     }
 
-  /* If we ourselves have indirectdata flag set, we should send only to our uplink! */
+  if(!cl->status.active)
+    {
+      if(debug_lvl >= DEBUG_TRAFFIC)
+       syslog(LOG_INFO, _("%s (%s) is not active, dropping packet"),
+              cl->name, cl->hostname);
+
+      return 0;
+    }
 
-  /* FIXME - check for indirection and reprogram it The Right Way(tm) this time. */
-  
   if(!cl->status.validkey)
     {
-/* FIXME: Don't queue until everything else is fixed.
       if(debug_lvl >= DEBUG_TRAFFIC)
        syslog(LOG_INFO, _("No valid key known yet for %s (%s), queueing packet"),
               cl->name, cl->hostname);
-      add_queue(&(cl->sq), packet, packet->len + 2);
-*/
+
+      /* Since packet is on the stack of handle_tap_input(),
+         we have to make a copy of it first. */
+
+      copy = xmalloc(sizeof(vpn_packet_t));
+      memcpy(copy, packet, sizeof(vpn_packet_t));
+
+      list_insert_tail(cl->queue, copy);
+
       if(!cl->status.waitingforkey)
        send_req_key(myself, cl);                       /* Keys should be sent to the host running the tincd */
       return 0;
     }
 
-  if(!cl->status.active)
+  /* Check if it has to go via UDP or TCP... */
+cp
+  if(cl->options & OPTION_TCPONLY)
+    return send_tcppacket(cl, packet);      
+  else
+    return xsend(cl, packet);
+}
+
+void flush_queue(connection_t *cl)
+{
+  list_node_t *node, *next;
+cp
+  if(debug_lvl >= DEBUG_TRAFFIC)
+    syslog(LOG_INFO, _("Flushing queue for %s (%s)"), cl->name, cl->hostname);
+  
+  for(node = cl->queue->head; node; node = next)
     {
-/* FIXME: Don't queue until everything else is fixed.
-      if(debug_lvl >= DEBUG_TRAFFIC)
-       syslog(LOG_INFO, _("%s (%s) is not ready, queueing packet"),
-              cl->name, cl->hostname);
-      add_queue(&(cl->sq), packet, packet->len + 2);
-*/
-      return 0; /* We don't want to mess up, do we? */
+      next = node->next;
+      xsend(cl, (vpn_packet_t *)node->data);
+      list_delete_node(cl->queue, node);
     }
-
-  /* can we send it? can we? can we? huh? */
 cp
-  return xsend(cl, packet);
 }
 
 /*
@@ -607,6 +510,19 @@ cp
       return -1;
     }
 
+  /* Bind first to get a fix on our source port */
+
+  a.sin_family = AF_INET;
+  a.sin_port = htons(0);
+  a.sin_addr.s_addr = htonl(INADDR_ANY);
+
+  if(bind(cl->meta_socket, (struct sockaddr *)&a, sizeof(struct sockaddr)))
+    {
+      close(cl->meta_socket);
+      syslog(LOG_ERR, _("System call `%s' failed: %m"), "bind");
+      return -1;
+    }
+  
   a.sin_family = AF_INET;
   a.sin_port = htons(cl->port);
   a.sin_addr.s_addr = htonl(cl->address);
@@ -656,14 +572,14 @@ cp
     
   if(read_host_config(ncn))
     {
-      syslog(LOG_ERR, _("Error reading host configuration file for %s"));
+      syslog(LOG_ERR, _("Error reading host configuration file for %s"), ncn->name);
       free_connection(ncn);
       return -1;
     }
     
   if(!(cfg = get_config_val(ncn->config, config_address)))
     {
-      syslog(LOG_ERR, _("No address specified for %s"));
+      syslog(LOG_ERR, _("No address specified for %s"), ncn->name);
       free_connection(ncn);
       return -1;
     }
@@ -702,17 +618,24 @@ int read_rsa_public_key(connection_t *cl)
 {
   config_t const *cfg;
   FILE *fp;
+  char *fname;
   void *result;
 cp
   if(!cl->rsa_key)
     cl->rsa_key = RSA_new();
 
+  /* First, check for simple PublicKey statement */
+
   if((cfg = get_config_val(cl->config, config_publickey)))
     {
       BN_hex2bn(&cl->rsa_key->n, cfg->data.ptr);
       BN_hex2bn(&cl->rsa_key->e, "FFFF");
+      return 0;
     }
-  else if((cfg = get_config_val(cl->config, config_publickeyfile)))
+
+  /* Else, check for PublicKeyFile statement and read it */
+
+  if((cfg = get_config_val(cl->config, config_publickeyfile)))
     {
       if(is_safe_path(cfg->data.ptr))
         {
@@ -730,17 +653,31 @@ cp
                     cfg->data.ptr);
               return -1;
             }
+          return 0;
         }
       else
         return -1;
     }    
-  else
+
+  /* Else, check if a harnessed public key is in the config file */
+  
+  asprintf(&fname, "%s/hosts/%s", confbase, cl->name);
+  if((fp = fopen(fname, "r")))
     {
-      syslog(LOG_ERR, _("No public key for %s specified!"), cl->name);
-      return -1;
+      result = PEM_read_RSAPublicKey(fp, &cl->rsa_key, NULL, NULL);
+      fclose(fp);
+      free(fname);
+      if(result)
+        return 0;
     }
+
+  free(fname);
+
+  /* Nothing worked. */
+
+  syslog(LOG_ERR, _("No public key for %s specified!"), cl->name);
 cp
-  return 0;
+  return -1;
 }
 
 int read_rsa_private_key(void)
@@ -794,7 +731,7 @@ int setup_myself(void)
 cp
   myself = new_connection();
 
-  asprintf(&myself->hostname, "MYSELF"); /* FIXME? Do hostlookup on ourselves? */
+  asprintf(&myself->hostname, "MYSELF");
   myself->flags = 0;
   myself->protocol_version = PROT_CURRENT;
 
@@ -876,7 +813,7 @@ cp
       syslog(LOG_ERR, _("Unable to set up a listening UDP socket!"));
       return -1;
     }
-
+cp
   /* Generate packet encryption key */
 
   myself->cipher_pkttype = EVP_bf_cfb();
@@ -892,9 +829,22 @@ cp
     keylifetime = cfg->data.val;
     
   keyexpires = time(NULL) + keylifetime;
+cp
+  /* Check some options */
+  
+  if((cfg = get_config_val(config, config_indirectdata)))
+    {
+      if(cfg->data.val == stupid_true)
+        myself->options |= OPTION_INDIRECT;
+    }
 
+  if((cfg = get_config_val(config, config_tcponly)))
+    {
+      if(cfg->data.val == stupid_true)
+        myself->options |= OPTION_TCPONLY;
+    }
   /* Activate ourselves */
-  
+
   myself->status.active = 1;
 
   syslog(LOG_NOTICE, _("Ready: listening on port %hd"), myself->port);
@@ -991,12 +941,12 @@ cp
 */
 void close_network_connections(void)
 {
-  rbl_t *rbl;
+  avl_node_t *node;
   connection_t *p;
 cp
-  RBL_FOREACH(connection_tree, rbl)
+  for(node = connection_tree->head; node; node = node->next)
     {
-      p = (connection_t *)rbl->data;
+      p = (connection_t *)node->data;
       p->status.active = 0;
       terminate_connection(p);
     }
@@ -1111,12 +1061,14 @@ cp
     {
       syslog(LOG_ERR, _("System call `%s' failed: %m"),
             "getpeername");
+      close(sfd);
       return NULL;
     }
 
   p->name = unknown;
   p->address = ntohl(ci.sin_addr.s_addr);
   p->hostname = hostlookup(ci.sin_addr.s_addr);
+  p->port = htons(ci.sin_port);                                /* This one will be overwritten later */
   p->meta_socket = sfd;
   p->status.meta = 1;
   p->buffer = xmalloc(MAXBUFSIZE);
@@ -1137,16 +1089,16 @@ cp
 */
 void build_fdset(fd_set *fs)
 {
-  rbl_t *rbl;
+  avl_node_t *node;
   connection_t *p;
 cp
   FD_ZERO(fs);
 
   FD_SET(myself->socket, fs);
 
-  RBL_FOREACH(connection_tree, rbl)
+  for(node = connection_tree->head; node; node = node->next)
     {
-      p = (connection_t *)rbl->data;
+      p = (connection_t *)node->data;
       if(p->status.meta)
         FD_SET(p->meta_socket, fs);
     }
@@ -1192,7 +1144,7 @@ cp
   
   if(!cl)
     {
-      syslog(LOG_WARNING, _("Received UDP packets on port %d from unknown source %lx:%d"), myself->port, ntohl(from.sin_addr.s_addr), ntohs(from.sin_port));
+      syslog(LOG_WARNING, _("Received UDP packets on port %hd from unknown source %x:%hd"), myself->port, ntohl(from.sin_addr.s_addr), ntohs(from.sin_port));
       return 0;
     }
 
@@ -1214,48 +1166,52 @@ void terminate_connection(connection_t *cl)
 {
   connection_t *p;
   subnet_t *subnet;
-  rbl_t *rbl;
+  avl_node_t *node, *next;
 cp
   if(cl->status.remove)
     return;
 
-  cl->status.remove = 1;
-
   if(debug_lvl >= DEBUG_CONNECTIONS)
     syslog(LOG_NOTICE, _("Closing connection with %s (%s)"),
            cl->name, cl->hostname);
  
+  cl->status.remove = 1;
+  
   if(cl->socket)
     close(cl->socket);
   if(cl->status.meta)
     close(cl->meta_socket);
 
-  /* Find all connections that were lost because they were behind cl
-     (the connection that was dropped). */
-
   if(cl->status.meta)
-    RBL_FOREACH(connection_tree, rbl)
-      {
-        p = (connection_t *)rbl->data;
-        if(p->nexthop == cl && p != cl)
-          terminate_connection(p);
-      }
+    {
+    
+      /* Find all connections that were lost because they were behind cl
+         (the connection that was dropped). */
 
-  /* Inform others of termination if it was still active */
+        for(node = connection_tree->head; node; node = node->next)
+          {
+            p = (connection_t *)node->data;
+            if(p->nexthop == cl && p != cl)
+              terminate_connection(p);
+          }
 
-  if(cl->status.active)
-    RBL_FOREACH(connection_tree, rbl)
-      {
-        p = (connection_t *)rbl->data;
-        if(p->status.meta && p->status.active && p!=cl)
-          send_del_host(p, cl);        /* Sounds like recursion, but p does not have a meta connection :) */
-      }
+      /* Inform others of termination if it was still active */
+
+      if(cl->status.active)
+        for(node = connection_tree->head; node; node = node->next)
+          {
+            p = (connection_t *)node->data;
+            if(p->status.meta && p->status.active && p != cl)
+              send_del_host(p, cl);    /* Sounds like recursion, but p does not have a meta connection :) */
+          }
+    }
 
   /* Remove the associated subnets */
 
-  RBL_FOREACH(cl->subnet_tree, rbl)
+  for(node = cl->subnet_tree->head; node; node = next)
     {
-      subnet = (subnet_t *)rbl->data;
+      next = node->next;
+      subnet = (subnet_t *)node->data;
       subnet_del(subnet);
     }
 
@@ -1286,14 +1242,14 @@ cp
 void check_dead_connections(void)
 {
   time_t now;
-  rbl_t *rbl;
+  avl_node_t *node;
   connection_t *cl;
 cp
   now = time(NULL);
 
-  RBL_FOREACH(connection_tree, rbl)
+  for(node = connection_tree->head; node; node = node->next)
     {
-      cl = (connection_t *)rbl->data;
+      cl = (connection_t *)node->data;
       if(cl->status.active && cl->status.meta)
         {
           if(cl->last_ping_time + timeout < now)
@@ -1352,14 +1308,14 @@ cp
 void check_network_activity(fd_set *f)
 {
   connection_t *p;
-  rbl_t *rbl;
+  avl_node_t *node;
 cp
   if(FD_ISSET(myself->socket, f))
     handle_incoming_vpn_data();
 
-  RBL_FOREACH(connection_tree, rbl)
+  for(node = connection_tree->head; node; node = node->next)
     {
-      p = (connection_t *)rbl->data;
+      p = (connection_t *)node->data;
 
       if(p->status.remove)
        return;