New iptunnel system to allow multiple users of the same iptunnel
authorHenning Rogge <hrogge@googlemail.com>
Fri, 19 Feb 2010 18:49:23 +0000 (19:49 +0100)
committerHenning Rogge <hrogge@googlemail.com>
Fri, 19 Feb 2010 18:49:23 +0000 (19:49 +0100)
src/gateway.c
src/kernel_tunnel.h
src/linux/kernel_routes_nl.c
src/linux/kernel_tunnel.c

index 7334d5f..3f868b1 100644 (file)
@@ -30,10 +30,7 @@ static uint8_t smart_gateway_netmask[sizeof(union olsr_ip_addr)];
 static struct gateway_entry *current_ipv4_gw, *current_ipv6_gw;
 static struct olsr_gw_handler *gw_handler;
 
-static const char v4gw_tunnel[] = "olsr_ipv4_inet";
-static const char v6gw_tunnel[] = "olsr_ipv6_inet";
-
-static uint32_t v4gw_index, v6gw_index;
+static struct olsr_iptunnel_entry *v4gw_tunnel, *v6gw_tunnel;
 
 /**
  * Reconstructs an uplink/downlink speed value from the encoded
@@ -87,8 +84,8 @@ olsr_init_gateways(void) {
   current_ipv4_gw = NULL;
   current_ipv6_gw = NULL;
 
-  v4gw_index = 0;
-  v6gw_index = 0;
+  v4gw_tunnel = NULL;
+  v6gw_tunnel = NULL;
 
   memset(&smart_gateway_netmask, 0, sizeof(smart_gateway_netmask));
 
@@ -125,10 +122,10 @@ olsr_init_gateways(void) {
  */
 void olsr_cleanup_gateways(void) {
   if (current_ipv4_gw) {
-    olsr_os_del_ipip_tunnel(v4gw_tunnel, true);
+    olsr_os_del_ipip_tunnel(v4gw_tunnel);
   }
   if (current_ipv6_gw) {
-    olsr_os_del_ipip_tunnel(v6gw_tunnel, false);
+    olsr_os_del_ipip_tunnel(v6gw_tunnel);
   }
   olsr_os_cleanup_iptunnel();
 }
@@ -179,8 +176,12 @@ olsr_set_inetgw_handler(struct olsr_gw_handler *h) {
 bool
 olsr_set_inet_gateway(union olsr_ip_addr *originator, bool ipv4, bool ipv6, bool external) {
   struct gateway_entry *entry, *oldV4, *oldV6;
+  struct olsr_iptunnel_entry *tunnelV4, *tunnelV6;
+
   oldV4 = current_ipv4_gw;
   oldV6 = current_ipv6_gw;
+  tunnelV4 = v4gw_tunnel;
+  tunnelV6 = v6gw_tunnel;
 
   ipv4 = ipv4 && (olsr_cnf->ip_version == AF_INET || olsr_cnf->use_niit);
   ipv6 = ipv6 && (olsr_cnf->ip_version == AF_INET6);
@@ -213,32 +214,28 @@ olsr_set_inet_gateway(union olsr_ip_addr *originator, bool ipv4, bool ipv6, bool
 
   /* handle IPv4 */
   if (oldV4 != current_ipv4_gw) {
-    if (oldV4 == NULL) {
-      olsr_os_add_ipip_tunnel(v4gw_tunnel, &current_ipv4_gw->originator, true);
-      olsr_if_set_state(v4gw_tunnel, true);
-
-      v4gw_index = if_nametoindex(v4gw_tunnel);
-fprintf(stderr, "add tunnel: %s %d\n", v4gw_tunnel, v4gw_index);
-      olsr_os_inetgw_tunnel_route(v4gw_index, true, true);
+    if ((v4gw_tunnel = olsr_os_add_ipip_tunnel(&current_ipv4_gw->originator, true)) != NULL) {
+      olsr_os_inetgw_tunnel_route(v4gw_tunnel->if_index, true, true);
     }
     else {
-      olsr_os_change_ipip_tunnel(v4gw_tunnel, &current_ipv4_gw->originator, true);
-      v4gw_index = if_nametoindex(v4gw_tunnel);
+      // TODO handle error
+fprintf(stderr, "add tunnel failed !\n");
+    }
+    if (oldV4 != NULL) {
+      olsr_os_del_ipip_tunnel(tunnelV4);
     }
   }
   /* handle IPv6 */
   if (oldV6 != current_ipv6_gw) {
-    if (oldV6 == NULL) {
-      olsr_os_add_ipip_tunnel(v6gw_tunnel, &current_ipv6_gw->originator, false);
-      olsr_if_set_state(v6gw_tunnel, true);
-
-      v6gw_index = if_nametoindex(v6gw_tunnel);
-fprintf(stderr, "add tunnel6: %s %d\n", v6gw_tunnel, v6gw_index);
-      olsr_os_inetgw_tunnel_route(v6gw_index, false, true);
+    if ((v6gw_tunnel = olsr_os_add_ipip_tunnel(&current_ipv6_gw->originator, false)) != NULL) {
+      olsr_os_inetgw_tunnel_route(v6gw_tunnel->if_index, false, true);
     }
     else {
-      olsr_os_change_ipip_tunnel(v6gw_tunnel, &current_ipv6_gw->originator, false);
-      v6gw_index = if_nametoindex(v6gw_tunnel);
+      // TODO handle error
+fprintf(stderr, "add tunnel failed !\n");
+    }
+    if (oldV6 != NULL) {
+      olsr_os_del_ipip_tunnel(tunnelV6);
     }
   }
   return (ipv4 && current_ipv4_gw == NULL) || (ipv6 && current_ipv6_gw == NULL);
@@ -344,6 +341,7 @@ static void cleanup_gateway_handler(void *ptr) {
   avl_delete(&gateway_tree, &gw->node);
   olsr_cookie_free(gw_mem_cookie, gw);
 }
+
 /**
  * Delete a gateway based on the originator IP and the prefixlength of a HNA.
  * Should only be called if prefix is a smart_gw prefix or if node is removed
@@ -382,18 +380,16 @@ olsr_delete_gateway_entry(union olsr_ip_addr *originator, uint8_t prefixlen) {
       gw_handler->handle_delete_gw(gw);
       /* cleanup gateway if necessary */
       if (current_ipv4_gw == gw) {
-        olsr_os_inetgw_tunnel_route(v4gw_index, true, false);
-        olsr_if_set_state(v4gw_tunnel, false);
-        olsr_os_del_ipip_tunnel(v4gw_tunnel, true);
+        olsr_os_inetgw_tunnel_route(v4gw_tunnel->if_index, true, false);
+        olsr_os_del_ipip_tunnel(v4gw_tunnel);
         current_ipv4_gw = NULL;
-        v4gw_index = 0;
+        v4gw_tunnel = NULL;
       }
       if (current_ipv6_gw == gw) {
-        olsr_os_inetgw_tunnel_route(v6gw_index, false, false);
-        olsr_if_set_state(v6gw_tunnel, false);
-        olsr_os_del_ipip_tunnel(v6gw_tunnel, false);
+        olsr_os_inetgw_tunnel_route(v6gw_tunnel->if_index, false, false);
+        olsr_os_del_ipip_tunnel(v6gw_tunnel);
         current_ipv6_gw = NULL;
-        v6gw_index = 0;
+        v6gw_tunnel = NULL;
       }
 
       /* remove gateway entry */
index a09abf1..23fefae 100644 (file)
@@ -8,14 +8,26 @@
 #ifndef KERNEL_TUNNEL_H_
 #define KERNEL_TUNNEL_H_
 
+#include <net/if.h>
+
 #include "defs.h"
 #include "olsr_types.h"
+#include "common/avl.h"
+
+struct olsr_iptunnel_entry {
+  struct avl_node node;
+  union olsr_ip_addr target;
+
+  char if_name[IFNAMSIZ];
+  int if_index;
+
+  int usage;
+};
 
 int olsr_os_init_iptunnel(void);
 void olsr_os_cleanup_iptunnel(void);
 
-int olsr_os_add_ipip_tunnel(const char *name, union olsr_ip_addr *target, bool transportV4);
-int olsr_os_change_ipip_tunnel(const char *name, union olsr_ip_addr *target, bool transportV4);
-int olsr_os_del_ipip_tunnel(const char *name, bool transportV4);
+struct olsr_iptunnel_entry *olsr_os_add_ipip_tunnel(union olsr_ip_addr *target, bool transportV4);
+void olsr_os_del_ipip_tunnel(struct olsr_iptunnel_entry *);
 
 #endif /* KERNEL_TUNNEL_H_ */
index 4fede8a..809514a 100644 (file)
@@ -413,14 +413,16 @@ static int olsr_new_netlink_route(int family, int rttable, int if_index, int met
   if (err) {
     if (gw) {
       struct ipaddr_str buf;
-      olsr_syslog(OLSR_LOG_ERR, ". error: %s route to %s via %s dev %s",
+      olsr_syslog(OLSR_LOG_ERR, ". error: %s route to %s via %s dev %s (%s %d)",
           set ? "add" : "del",
-          olsr_ip_prefix_to_string(dst), olsr_ip_to_string(&buf, gw), if_ifwithindex_name(if_index));
+          olsr_ip_prefix_to_string(dst), olsr_ip_to_string(&buf, gw),
+          if_ifwithindex_name(if_index), strerror(errno), errno);
     }
     else {
-      olsr_syslog(OLSR_LOG_ERR, ". error: %s route to %s dev %s",
+      olsr_syslog(OLSR_LOG_ERR, ". error: %s route to %s dev %s (%s %d)",
           set ? "add" : "del",
-          olsr_ip_prefix_to_string(dst), if_ifwithindex_name(if_index));
+          olsr_ip_prefix_to_string(dst), if_ifwithindex_name(if_index),
+          strerror(errno), errno);
     }
   }
 
@@ -448,22 +450,14 @@ void olsr_os_niit_4to6_route(const struct olsr_ip_prefix *dst_v4, bool set) {
 void olsr_os_inetgw_tunnel_route(uint32_t if_idx, bool ipv4, bool set) {
   const struct olsr_ip_prefix *dst;
 
-  if (olsr_cnf->ip_version == AF_INET) {
-    assert(ipv4);
+  assert(olsr_cnf->ip_version == AF_INET6 || ipv4);
 
-    dst = &ipv4_internet_route;
-  }
-  else if (ipv4) {
-    dst = &ipv6_mappedv4_route;
-  }
-  else {
-    dst = &ipv6_internet_route;
-  }
+  dst = ipv4 ? &ipv4_internet_route : &ipv6_internet_route;
 
   if (olsr_new_netlink_route(ipv4 ? AF_INET : AF_INET6, olsr_cnf->rttable,
       if_idx, RT_METRIC_DEFAULT, olsr_cnf->rtproto,NULL, NULL, dst, set, false)) {
-    olsr_syslog(OLSR_LOG_ERR, ". error while %s inetgw tunnel route to %s",
-        set ? "setting" : "removing", olsr_ip_prefix_to_string(dst));
+    olsr_syslog(OLSR_LOG_ERR, ". error while %s inetgw tunnel route to %s for if %d",
+        set ? "setting" : "removing", olsr_ip_prefix_to_string(dst), if_idx);
   }
 }
 
index cf3ebec..85cc854 100644 (file)
 #include "log.h"
 #include "olsr_types.h"
 #include "net_os.h"
+#include "olsr_cookie.h"
+#include "ipcalc.h"
 
 #include <assert.h>
 
 //ipip includes
+#include <arpa/inet.h>
 #include <netinet/in.h>
 #include <sys/ioctl.h>
 #include <net/if.h>
@@ -64,10 +67,16 @@ static const char DEV_IPV4_TUNNEL[IFNAMSIZ] = "tunl0";
 static const char DEV_IPV6_TUNNEL[IFNAMSIZ] = "ip6tnl0";
 
 static bool store_iptunnel_state;
+static struct olsr_cookie_info *tunnel_cookie;
+static struct avl_tree tunnel_tree;
 
 int olsr_os_init_iptunnel(void) {
   const char *dev = olsr_cnf->ip_version == AF_INET ? DEV_IPV4_TUNNEL : DEV_IPV6_TUNNEL;
 
+  tunnel_cookie = olsr_alloc_cookie("iptunnel", OLSR_COOKIE_TYPE_MEMORY);
+  olsr_cookie_set_memory_size(tunnel_cookie, sizeof(struct olsr_iptunnel_entry));
+  avl_init(&tunnel_tree, avl_comp_default);
+
   store_iptunnel_state = olsr_if_isup(dev);
   if (store_iptunnel_state) {
     return 0;
@@ -76,29 +85,30 @@ int olsr_os_init_iptunnel(void) {
 }
 
 void olsr_os_cleanup_iptunnel(void) {
+  while (tunnel_tree.count > 0) {
+    struct olsr_iptunnel_entry *t;
+
+    /* kill tunnel */
+    t = (struct olsr_iptunnel_entry *)avl_walk_first(&tunnel_tree);
+    t->usage = 1;
+
+    olsr_os_del_ipip_tunnel(t);
+  }
   if (!store_iptunnel_state) {
     olsr_if_set_state(olsr_cnf->ip_version == AF_INET ? DEV_IPV4_TUNNEL : DEV_IPV6_TUNNEL, false);
   }
-}
 
-static const char *get_tunnelcmd_name(uint32_t cmd) {
-  static const char ADD[] = "add";
-  static const char CHANGE[] = "change";
-  static const char DELETE[] = "delete";
-
-  switch (cmd) {
-    case SIOCADDTUNNEL:
-      return ADD;
-    case SIOCCHGTUNNEL:
-      return CHANGE;
-    case SIOCDELTUNNEL:
-      return DELETE;
-    default:
-      return NULL;
-  }
+  olsr_free_cookie(tunnel_cookie);
 }
 
-static int os_ip4_tunnel(const char *name, in_addr_t *target, uint32_t cmd)
+/**
+ * creates an ipip tunnel (for ipv4)
+ * @param name interface name
+ * @param target pointer to tunnel target IP, NULL if tunnel should be removed
+ * @return 0 if an error happened,
+ *   if_index for successful created tunnel, 1 for successful deleted tunnel
+ */
+static int os_ip4_tunnel(const char *name, in_addr_t *target)
 {
   struct ifreq ifr;
   int err;
@@ -116,17 +126,29 @@ static int os_ip4_tunnel(const char *name, in_addr_t *target, uint32_t cmd)
   strncpy(p.name, name, IFNAMSIZ);
 
   memset(&ifr, 0, sizeof(ifr));
-  strncpy(ifr.ifr_name, cmd == SIOCADDTUNNEL ? DEV_IPV4_TUNNEL : name, IFNAMSIZ);
+  strncpy(ifr.ifr_name, target != NULL ? DEV_IPV4_TUNNEL : name, IFNAMSIZ);
   ifr.ifr_ifru.ifru_data = (void *) &p;
 
-  if ((err = ioctl(olsr_cnf->ioctl_s, cmd, &ifr))) {
-    olsr_syslog(OLSR_LOG_ERR, "Cannot %s a tunnel %s: %s (%d)\n",
-        get_tunnelcmd_name(cmd), name, strerror(errno), errno);
+  if ((err = ioctl(olsr_cnf->ioctl_s, target != NULL ? SIOCADDTUNNEL : SIOCDELTUNNEL, &ifr))) {
+    char buffer[INET6_ADDRSTRLEN];
+
+    olsr_syslog(OLSR_LOG_ERR, "Cannot %s a tunnel %s to %s: %s (%d)\n",
+        target != NULL ? "add" : "remove", name,
+        target != NULL ? inet_ntop(olsr_cnf->ip_version, target, buffer, sizeof(buffer)) : "-",
+        strerror(errno), errno);
+    return 0;
   }
-  return err;
+  return target != NULL ? if_nametoindex(name) : 1;
 }
 
-static int os_ip6_tunnel(const char *name, struct in6_addr *target, uint32_t cmd, uint8_t proto)
+/**
+ * creates an ipip tunnel (for ipv6)
+ * @param name interface name
+ * @param target pointer to tunnel target IP, NULL if tunnel should be removed
+ * @return 0 if an error happened,
+ *   if_index for successful created tunnel, 1 for successful deleted tunnel
+ */
+static int os_ip6_tunnel(const char *name, struct in6_addr *target)
 {
   struct ifreq ifr;
   int err;
@@ -135,46 +157,121 @@ static int os_ip6_tunnel(const char *name, struct in6_addr *target, uint32_t cmd
   /* only IP6 tunnel if OLSR runs with IPv6 */
   assert (olsr_cnf->ip_version == AF_INET6);
   memset(&p, 0, sizeof(p));
-  p.proto = proto;
+  p.proto = 0; /* any protocol */
   if (target) {
     p.raddr = *target;
   }
   strncpy(p.name, name, IFNAMSIZ);
 
   memset(&ifr, 0, sizeof(ifr));
-  strncpy(ifr.ifr_name, cmd == SIOCADDTUNNEL ? DEV_IPV6_TUNNEL : name, IFNAMSIZ);
+  strncpy(ifr.ifr_name, target != NULL ? DEV_IPV6_TUNNEL : name, IFNAMSIZ);
   ifr.ifr_ifru.ifru_data = (void *) &p;
 
-  if ((err = ioctl(olsr_cnf->ioctl_s, cmd, &ifr))) {
-    olsr_syslog(OLSR_LOG_ERR, "Cannot %s a tunnel %s: %s (%d)\n",
-        get_tunnelcmd_name(cmd), name, strerror(errno), errno);
+  if ((err = ioctl(olsr_cnf->ioctl_s, target != NULL ? SIOCADDTUNNEL : SIOCDELTUNNEL, &ifr))) {
+    char buffer[INET6_ADDRSTRLEN];
+
+    olsr_syslog(OLSR_LOG_ERR, "Cannot %s a tunnel %s to %s: %s (%d)\n",
+        target != NULL ? "add" : "remove", name,
+        target != NULL ? inet_ntop(olsr_cnf->ip_version, target, buffer, sizeof(buffer)) : "-",
+        strerror(errno), errno);
+    return 0;
   }
-  return err;
+  return target != NULL ? if_nametoindex(name) : 1;
 }
 
-int olsr_os_add_ipip_tunnel(const char *name, union olsr_ip_addr *target, bool transportV4) {
-  if (olsr_cnf->ip_version == AF_INET) {
-    assert(transportV4);
+/**
+ * Dummy for generating an interface name for an olsr ipip tunnel
+ * @param target IP destination of the tunnel
+ * @param name pointer to output buffer (length IFNAMSIZ)
+ */
+static void generate_iptunnel_name(union olsr_ip_addr *target, char *name) {
+  static char PREFIX[] = "tnl_";
+  static uint32_t counter = 0;
 
-    return os_ip4_tunnel(name, &target->v4.s_addr, SIOCADDTUNNEL);
-  }
-  return os_ip6_tunnel(name, &target->v6, SIOCADDTUNNEL, transportV4 ? IPPROTO_IPIP : IPPROTO_IPV6);
+  snprintf(name, IFNAMSIZ, "%s%08x", PREFIX,
+      olsr_cnf->ip_version == AF_INET ? target->v4.s_addr : ++counter);
 }
 
-int olsr_os_change_ipip_tunnel(const char *name, union olsr_ip_addr *target, bool transportV4) {
-  if (olsr_cnf->ip_version == AF_INET) {
-    assert(transportV4);
+/**
+ * demands an ipip tunnel to a certain target. If no tunnel exists it will be created
+ * @param target ip address of the target
+ * @param transportV4 true if IPv4 traffic is used, false for IPv6 traffic
+ * @return NULL if an error happened, pointer to olsr_iptunnel_entry otherwise
+ */
+struct olsr_iptunnel_entry *olsr_os_add_ipip_tunnel(union olsr_ip_addr *target, bool transportV4) {
+  struct olsr_iptunnel_entry *t;
 
-    return os_ip4_tunnel(name, &target->v4.s_addr, SIOCCHGTUNNEL);
+  assert(olsr_cnf->ip_version == AF_INET6 || transportV4);
+
+  t = (struct olsr_iptunnel_entry *)avl_find(&tunnel_tree, target);
+  if (t == NULL) {
+    char name[IFNAMSIZ];
+    int if_idx;
+    struct ipaddr_str buf;
+
+    memset(name, 0, sizeof(name));
+    generate_iptunnel_name(target, name);
+
+    if (olsr_cnf->ip_version == AF_INET) {
+      if_idx = os_ip4_tunnel(name, &target->v4.s_addr);
+    }
+    else {
+      if_idx = os_ip6_tunnel(name, &target->v6);
+    }
+
+    if (if_idx == 0) {
+      // cannot create tunnel
+fprintf(stderr, "Cannot create tunnel %s to %s\n", name, olsr_ip_to_string(&buf, target));
+      return NULL;
+    }
+    // TODO: fehler bei setstate abfangen ?
+    olsr_if_set_state(name, true);
+
+    t = olsr_cookie_malloc(tunnel_cookie);
+    memcpy(&t->target, target, sizeof(*target));
+    t->node.key = &t->target;
+
+    strncpy(t->if_name, name, IFNAMSIZ);
+    t->if_index = if_idx;
+
+    avl_insert(&tunnel_tree, &t->node, AVL_DUP_NO);
   }
-  return os_ip6_tunnel(name, &target->v6, SIOCCHGTUNNEL, transportV4 ? IPPROTO_IPIP : IPPROTO_IPV6);
+
+  t->usage++;
+  return t;
 }
 
-int olsr_os_del_ipip_tunnel(const char *name, bool transportV4) {
+/**
+ * Release an olsr ipip tunnel. Tunnel will be deleted
+ * if this was the last user
+ * @param t pointer to olsr_iptunnel_entry
+ */
+static void internal_olsr_os_del_ipip_tunnel(struct olsr_iptunnel_entry *t, bool cleanup) {
+  if (!cleanup) {
+    if (t->usage == 0) {
+      return;
+    }
+    t->usage--;
+
+    if (t->usage > 0) {
+      return;
+    }
+  }
+
+  olsr_if_set_state(t->if_name, false);
   if (olsr_cnf->ip_version == AF_INET) {
-    assert(transportV4);
+    os_ip4_tunnel(t->if_name, NULL);
+  }
+  else {
+    os_ip6_tunnel(t->if_name, NULL);
+  }
 
-    return os_ip4_tunnel(name, NULL, SIOCDELTUNNEL);
+  avl_delete(&tunnel_tree, &t->node);
+  if (!cleanup) {
+    olsr_cookie_free(tunnel_cookie, t);
   }
-  return os_ip6_tunnel(name, NULL, SIOCDELTUNNEL, transportV4 ? IPPROTO_IPIP : IPPROTO_IPV6);
+}
+
+void olsr_os_del_ipip_tunnel(struct olsr_iptunnel_entry *t) {
+  internal_olsr_os_del_ipip_tunnel(t, false);
 }