- netinet/* include files depend on netinet/in_systm.h.
[tinc] / src / route.c
index 2c3cc0d..a552eaf 100644 (file)
@@ -1,7 +1,7 @@
 /*
     route.c -- routing
-    Copyright (C) 2000,2001 Ivo Timmermans <itimmermans@bigfoot.com>,
-                  2000,2001 Guus Sliepen <guus@sliepen.warande.net>
+    Copyright (C) 2000-2002 Ivo Timmermans <itimmermans@bigfoot.com>,
+                  2000-2002 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
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-    $Id: route.c,v 1.1.2.11 2001/06/05 16:09:55 guus Exp $
+    $Id: route.c,v 1.1.2.42 2002/06/08 14:08:57 guus Exp $
 */
 
 #include "config.h"
 
-#ifdef HAVE_FREEBSD
+#ifdef HAVE_SYS_PARAM_H
  #include <sys/param.h>
 #endif
 #include <sys/socket.h>
 #include <netinet/in.h>
-#include <net/ethernet.h>
+#ifdef HAVE_NET_IF_H
+ #include <net/if.h>
+#endif
+#ifdef HAVE_NET_ETHERNET_H
+ #include <net/ethernet.h>
+#endif
+#ifdef HAVE_NETINET_IN_SYSTM_H
+ #include <netinet/in_systm.h>
+#endif
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
 #include <netinet/if_ether.h>
 #include <utils.h>
 #include <xalloc.h>
 #include <syslog.h>
+#include <string.h>
+#ifdef HAVE_INTTYPES_H
+ #include <inttypes.h>
+#endif
+
+#include <avl_tree.h>
 
 #include "net.h"
-#include "net/ethernet.h"
-#include "netinet/if_ether.h"
 #include "connection.h"
 #include "subnet.h"
 #include "route.h"
+#include "protocol.h"
+#include "device.h"
 
 #include "system.h"
 
+#ifndef ETHER_ADDR_LEN
+ #define ETHER_ADDR_LEN 6
+#endif
+
 int routing_mode = RMODE_ROUTER;
+int priorityinheritance = 0;
+int macexpire = 600;
 subnet_t mymac;
 
-void learn_mac(connection_t *source, mac_t *address)
+void learn_mac(mac_t *address)
 {
   subnet_t *subnet;
+  avl_node_t *node;
+  connection_t *c;
 cp
   subnet = lookup_subnet_mac(address);
+
+  /* If we don't know this MAC address yet, store it */
   
-  if(!subnet)
+  if(!subnet || subnet->owner!=myself)
     {
+      if(debug_lvl >= DEBUG_TRAFFIC)
+        syslog(LOG_INFO, _("Learned new MAC address %hx:%hx:%hx:%hx:%hx:%hx"),
+               address->x[0], address->x[1], address->x[2], address->x[3],  address->x[4], address->x[5]);
+               
       subnet = new_subnet();
       subnet->type = SUBNET_MAC;
-//      subnet->lasttime = gettimeofday();
       memcpy(&subnet->net.mac.address, address, sizeof(mac_t));
-      subnet_add(source, subnet);
+      subnet_add(myself, subnet);
 
-      if(debug_lvl >= DEBUG_TRAFFIC)
+      /* And tell all other tinc daemons it's our MAC */
+      
+      for(node = connection_tree->head; node; node = node->next)
         {
-          syslog(LOG_DEBUG, _("Learned new MAC address %x:%x:%x:%x:%x:%x from %s (%s)"),
-               address->x[0],
-               address->x[1],
-               address->x[2],
-               address->x[3],
-               address->x[4],
-               address->x[5],
-               source->name, source->hostname);
+          c = (connection_t *)node->data;
+          if(c->status.active)
+            send_add_subnet(c, subnet);
         }
     }
+
+  subnet->net.mac.lastseen = now;
 }
 
-connection_t *route_mac(connection_t *source, vpn_packet_t *packet)
+void age_mac(void)
+{
+  subnet_t *s;
+  connection_t *c;
+  avl_node_t *node, *next, *node2;
+cp
+  for(node = myself->subnet_tree->head; node; node = next)
+    {
+      next = node->next;
+      s = (subnet_t *)node->data;
+      if(s->type == SUBNET_MAC && s->net.mac.lastseen && s->net.mac.lastseen + macexpire < now)
+        {
+         if(debug_lvl >= DEBUG_TRAFFIC)
+            syslog(LOG_INFO, _("MAC address %hx:%hx:%hx:%hx:%hx:%hx expired"),
+                  s->net.mac.address.x[0], s->net.mac.address.x[1], s->net.mac.address.x[2], s->net.mac.address.x[3],  s->net.mac.address.x[4], s->net.mac.address.x[5]);
+         for(node2 = connection_tree->head; node2; node2 = node2->next)
+            {
+              c = (connection_t *)node2->data;
+              if(c->status.active)
+               send_del_subnet(c, s);
+            }
+          subnet_del(myself, s);
+       }
+    }
+cp
+}
+
+node_t *route_mac(vpn_packet_t *packet)
 {
   subnet_t *subnet;
 cp
   /* Learn source address */
 
-  learn_mac(source, (mac_t *)(&packet->data[6]));
+  learn_mac((mac_t *)(&packet->data[6]));
   
   /* Lookup destination address */
     
@@ -91,20 +146,20 @@ cp
     return NULL;
 }
 
-connection_t *route_ipv4(vpn_packet_t *packet)
+node_t *route_ipv4(vpn_packet_t *packet)
 {
-  ipv4_t dest;
   subnet_t *subnet;
 cp
-  dest = ntohl(*((unsigned long*)(&packet->data[30])));
-  
-  subnet = lookup_subnet_ipv4(&dest);
+  if(priorityinheritance)
+    packet->priority = packet->data[15];
 
+  subnet = lookup_subnet_ipv4((ipv4_t *)&packet->data[30]);
+cp
   if(!subnet)
     {
       if(debug_lvl >= DEBUG_TRAFFIC)
         {
-          syslog(LOG_WARNING, _("Cannot route packet: unknown destination address %d.%d.%d.%d"),
+          syslog(LOG_WARNING, _("Cannot route packet: unknown IPv4 destination address %d.%d.%d.%d"),
                  packet->data[30], packet->data[31], packet->data[32], packet->data[33]);
         }
 
@@ -114,24 +169,168 @@ cp
   return subnet->owner;  
 }
 
-connection_t *route_ipv6(vpn_packet_t *packet)
+node_t *route_ipv6(vpn_packet_t *packet)
+{
+  subnet_t *subnet;
+cp
+  subnet = lookup_subnet_ipv6((ipv6_t *)&packet->data[38]);
+cp
+  if(!subnet)
+    {
+      if(debug_lvl >= DEBUG_TRAFFIC)
+        {
+          syslog(LOG_WARNING, _("Cannot route packet: unknown IPv6 destination address %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx"),
+           ntohs(*(uint16_t *)&packet->data[38]),
+           ntohs(*(uint16_t *)&packet->data[40]),
+           ntohs(*(uint16_t *)&packet->data[42]),
+           ntohs(*(uint16_t *)&packet->data[44]),
+           ntohs(*(uint16_t *)&packet->data[46]),
+           ntohs(*(uint16_t *)&packet->data[48]),
+           ntohs(*(uint16_t *)&packet->data[50]),
+           ntohs(*(uint16_t *)&packet->data[52]));
+        }
+
+      return NULL;
+    }
+cp
+  return subnet->owner;  
+}
+
+uint16_t inet_checksum(uint16_t *data, int len, uint16_t prevsum)
 {
+  uint32_t checksum = prevsum ^ 0xFFFF;
+
+  while(len--)
+    checksum += ntohs(*data++);
+
+  while(checksum >> 16)
+    checksum = (checksum & 0xFFFF) + (checksum >> 16);
+
+  return checksum ^ 0xFFFF;
+}
+
+void route_neighborsol(vpn_packet_t *packet)
+{
+  struct ip6_hdr *hdr;
+  struct nd_neighbor_solicit *ns;
+  struct nd_opt_hdr *opt;
+  subnet_t *subnet;
+  uint16_t checksum;
+  
+  struct {
+    struct in6_addr ip6_src;      /* source address */
+    struct in6_addr ip6_dst;      /* destination address */
+    uint32_t length;
+    uint8_t junk[4];
+  } pseudo;
+
 cp
-  if(debug_lvl > DEBUG_NOTHING)
+  hdr = (struct ip6_hdr *)(packet->data + 14);
+  ns = (struct nd_neighbor_solicit *)(packet->data + 14 + sizeof(*hdr));
+  opt = (struct nd_opt_hdr *)(packet->data + 14 + sizeof(*hdr) + sizeof(*ns));
+
+  /* First, snatch the source address from the neighbor solicitation packet */
+
+  memcpy(mymac.net.mac.address.x, packet->data + 6, 6);
+
+  /* Check if this is a valid neighbor solicitation request */
+  
+  if(ns->nd_ns_hdr.icmp6_type != ND_NEIGHBOR_SOLICIT ||
+     opt->nd_opt_type != ND_OPT_SOURCE_LINKADDR)
     {
-      syslog(LOG_WARNING, _("Cannot route packet: IPv6 routing not yet implemented"));
-    } 
+      if(debug_lvl > DEBUG_TRAFFIC)
+        {
+          syslog(LOG_WARNING, _("Cannot route packet: received unknown type neighbor solicitation request"));
+        } 
+      return;
+    }
+
+  /* Create pseudo header */
+
+  memcpy(&pseudo.ip6_src, &hdr->ip6_src, 16);
+  memcpy(&pseudo.ip6_dst, &hdr->ip6_dst, 16);
+  pseudo.length = htonl(sizeof(*ns) + sizeof(*opt) + 6);
+  pseudo.junk[0] = pseudo.junk[1] = pseudo.junk[2] = 0;
+  pseudo.junk[3] = IPPROTO_ICMPV6;
+  
+  /* Generate checksum */
+  
+  checksum = inet_checksum((uint16_t *)&pseudo, sizeof(pseudo)/2, ~0);
+  checksum = inet_checksum((uint16_t *)ns, sizeof(*ns)/2 + 4, checksum);
+
+  if(checksum)
+    {
+      if(debug_lvl >= DEBUG_TRAFFIC)
+          syslog(LOG_WARNING, _("Cannot route packet: checksum error for neighbor solicitation request"));
+      return;
+    }
+
+  /* Check if the IPv6 address exists on the VPN */
+
+  subnet = lookup_subnet_ipv6((ipv6_t *)&ns->nd_ns_target);
+
+  if(!subnet)
+    {
+      if(debug_lvl >= DEBUG_TRAFFIC)
+        {
+          syslog(LOG_WARNING, _("Cannot route packet: neighbor solicitation request for unknown address %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx"),
+                 ntohs(((uint16_t *)&ns->nd_ns_target)[0]), ntohs(((uint16_t *)&ns->nd_ns_target)[1]), ntohs(((uint16_t *)&ns->nd_ns_target)[2]), ntohs(((uint16_t *)&ns->nd_ns_target)[3]),
+                 ntohs(((uint16_t *)&ns->nd_ns_target)[4]), ntohs(((uint16_t *)&ns->nd_ns_target)[5]), ntohs(((uint16_t *)&ns->nd_ns_target)[6]), ntohs(((uint16_t *)&ns->nd_ns_target)[7]));
+        }
+
+      return;
+    }
+
+  /* Check if it is for our own subnet */
+  
+  if(subnet->owner == myself)
+    return;    /* silently ignore */
+
+  /* Create neighbor advertation reply */
+
+  memcpy(packet->data, packet->data + ETHER_ADDR_LEN, ETHER_ADDR_LEN); /* copy destination address */
+  packet->data[ETHER_ADDR_LEN*2 - 1] ^= 0xFF;                          /* mangle source address so it looks like it's not from us */
+
+  memcpy(&hdr->ip6_dst, &hdr->ip6_src, 16);                            /* swap destination and source protocol address */
+  memcpy(&hdr->ip6_src, &ns->nd_ns_target, 16);                                /* ... */
+
+  memcpy((char *)opt + sizeof(*opt), packet->data + ETHER_ADDR_LEN, 6);        /* add fake source hard addr */
+
+  ns->nd_ns_hdr.icmp6_cksum = 0;
+  ns->nd_ns_hdr.icmp6_type = ND_NEIGHBOR_ADVERT;
+  ns->nd_ns_hdr.icmp6_dataun.icmp6_un_data8[0] = 0x40;                 /* Set solicited flag */
+  ns->nd_ns_hdr.icmp6_dataun.icmp6_un_data8[1] = ns->nd_ns_hdr.icmp6_dataun.icmp6_un_data8[2] = ns->nd_ns_hdr.icmp6_dataun.icmp6_un_data8[3] = 0;
+  opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
+
+  /* Create pseudo header */
+  
+  memcpy(&pseudo.ip6_src, &hdr->ip6_src, 16);
+  memcpy(&pseudo.ip6_dst, &hdr->ip6_dst, 16);
+  pseudo.length = htonl(sizeof(*ns) + sizeof(*opt) + 6);
+  pseudo.junk[0] = pseudo.junk[1] = pseudo.junk[2] = 0;
+  pseudo.junk[3] = IPPROTO_ICMPV6;
+  
+  /* Generate checksum */
+  
+  checksum = inet_checksum((uint16_t *)&pseudo, sizeof(pseudo)/2, ~0);
+  checksum = inet_checksum((uint16_t *)ns, sizeof(*ns)/2 + 4, checksum);
+
+  ns->nd_ns_hdr.icmp6_cksum = htons(checksum);
+
+  write_packet(packet);
 cp
-  return NULL;
 }
 
 void route_arp(vpn_packet_t *packet)
 {
   struct ether_arp *arp;
   subnet_t *subnet;
-  unsigned char ipbuf[4];
-  ipv4_t dest;
+  uint8_t ipbuf[4];
 cp
+  /* First, snatch the source address from the ARP packet */
+
+  memcpy(mymac.net.mac.address.x, packet->data + 6, 6);
+
   /* This routine generates replies to ARP requests.
      You don't need to set NOARP flag on the interface anymore (which is broken on FreeBSD).
      Most of the code here is taken from choparp.c by Takamichi Tateoka (tree@mma.club.uec.ac.jp)
@@ -143,8 +342,8 @@ cp
 
   if(ntohs(arp->arp_hrd) != ARPHRD_ETHER ||
      ntohs(arp->arp_pro) != ETHERTYPE_IP ||
-     (int) (arp->arp_hln) != ETHER_ADDR_LEN ||
-     (int) (arp->arp_pln) != 4 ||
+     arp->arp_hln != ETHER_ADDR_LEN ||
+     arp->arp_pln != 4 ||
      ntohs(arp->arp_op) != ARPOP_REQUEST )
     {
       if(debug_lvl > DEBUG_TRAFFIC)
@@ -154,10 +353,9 @@ cp
       return;
     }
 
-  /* Check if the IP address exists on the VPN */
+  /* Check if the IPv4 address exists on the VPN */
 
-  dest = ntohl(*((unsigned long*)(arp->arp_tpa)));
-  subnet = lookup_subnet_ipv4(&dest);
+  subnet = lookup_subnet_ipv4((ipv4_t *)arp->arp_tpa);
 
   if(!subnet)
     {
@@ -186,28 +384,33 @@ cp
   memcpy(arp->arp_sha, packet->data + ETHER_ADDR_LEN, ETHER_ADDR_LEN); /* add fake source hard addr */
   arp->arp_op = htons(ARPOP_REPLY);
   
-  accept_packet(packet);
+  write_packet(packet);
 cp
 }
 
 void route_outgoing(vpn_packet_t *packet)
 {
-  unsigned short int type;
-  connection_t *cl;
+  uint16_t type;
+  node_t *n = NULL;
 cp
   /* FIXME: multicast? */
 
   switch(routing_mode)
     {
       case RMODE_ROUTER:
-        type = ntohs(*((unsigned short*)(&packet->data[12])));
+        type = ntohs(*((uint16_t *)(&packet->data[12])));
         switch(type)
           {
             case 0x0800:
-              cl = route_ipv4(packet);
+              n = route_ipv4(packet);
               break;
             case 0x86DD:
-              cl = route_ipv6(packet);
+              if(packet->data[20] == IPPROTO_ICMPV6 && packet->data[54] == ND_NEIGHBOR_SOLICIT)
+               {
+                 route_neighborsol(packet);
+                 return;
+               }
+              n = route_ipv6(packet);
               break;
             case 0x0806:
               route_arp(packet);
@@ -219,14 +422,14 @@ cp
                 }
               return;
            }
-         if(cl)
-           send_packet(cl, packet);
+         if(n)
+           send_packet(n, packet);
          break;
         
       case RMODE_SWITCH:
-        cl = route_mac(myself, packet);
-        if(cl)
-          send_packet(cl, packet);
+        n = route_mac(packet);
+        if(n)
+          send_packet(n, packet);
         else
           broadcast_packet(myself, packet);
         break;
@@ -237,23 +440,64 @@ cp
     }
 }
 
-void route_incoming(connection_t *source, vpn_packet_t *packet)
+void route_incoming(node_t *source, vpn_packet_t *packet)
 {
   switch(routing_mode)
     {
       case RMODE_ROUTER:
-        memcpy(packet->data, mymac.net.mac.address.x, 6);      /* Override destination address to make the kernel accept it */
+        {
+          node_t *n = NULL;
+         uint16_t type;
+
+          type = ntohs(*((uint16_t *)(&packet->data[12])));
+          switch(type)
+            {
+              case 0x0800:
+               n = route_ipv4(packet);
+               break;
+              case 0x86DD:
+               n = route_ipv6(packet);
+               break;
+              default:
+                n = myself;
+               break;
+             }
+
+          if(n)
+            {
+              if(n == myself)
+               {
+                  memcpy(packet->data, mymac.net.mac.address.x, 6);
+                  write_packet(packet);
+               }
+              else
+                send_packet(n, packet);
+            }
+          }
         break;
       case RMODE_SWITCH:
-        if((packet->data[0] & packet->data[1]) == 0xFF)                /* Broadcast? */
-          broadcast_packet(source, packet);                    /* If yes, spread it on */
-        else
-          learn_mac(source, (mac_t *)(&packet->data[6]));
+        {
+          subnet_t *subnet;
+
+          subnet = lookup_subnet_mac((mac_t *)(&packet->data[0]));
+
+          if(subnet)
+            {
+              if(subnet->owner == myself)
+                write_packet(packet);
+              else
+                send_packet(subnet->owner, packet);
+            }
+          else
+            {
+              broadcast_packet(source, packet);
+              write_packet(packet);
+            }
+          }
         break;
       case RMODE_HUB:
-        broadcast_packet(source,packet);                       /* Spread it on */
+        broadcast_packet(source, packet);                      /* Spread it on */
+        write_packet(packet);
         break;
     }
-  
-  accept_packet(packet);
 }