httpinfo: add smart gateway information tab
[olsrd.git] / src / gateway.c
index 154ea2f..e4db6e9 100644 (file)
@@ -59,10 +59,10 @@ static uint8_t smart_gateway_netmask[sizeof(union olsr_ip_addr)];
 static struct olsr_gw_handler *gw_handler;
 
 /** the IPv4 gateway list */
-static struct gw_list gw_list_ipv4;
+struct gw_list gw_list_ipv4;
 
 /** the IPv6 gateway list */
-static struct gw_list gw_list_ipv6;
+struct gw_list gw_list_ipv6;
 
 /** the current IPv4 gateway */
 static struct gw_container_entry *current_ipv4_gw;
@@ -79,6 +79,9 @@ struct interfaceName * sgwTunnel4InterfaceNames;
 /** interface names for smart gateway tunnel interfaces, IPv6 */
 struct interfaceName * sgwTunnel6InterfaceNames;
 
+/** the timer for proactive takedown */
+static struct timer_entry *gw_takedown_timer;
+
 /*
  * Forward Declarations
  */
@@ -177,6 +180,10 @@ static struct interfaceName * find_interfaceName(struct gateway_entry *gw) {
   struct interfaceName * sgwTunnelInterfaceNames;
   uint8_t i = 0;
 
+  if (!multi_gateway_mode()) {
+    return NULL;
+  }
+
   assert(sgwTunnel4InterfaceNames);
   assert(sgwTunnel6InterfaceNames);
 
@@ -206,20 +213,21 @@ static void get_unused_iptunnel_name(struct gateway_entry *gw, char * name, stru
   assert(name);
   assert(interfaceName);
 
+  memset(name, 0, IFNAMSIZ);
+
   if (multi_gateway_mode()) {
     struct interfaceName * ifn = find_interfaceName(NULL);
 
     if (ifn) {
-      ifn->gw = gw;
       strncpy(&name[0], &ifn->name[0], sizeof(ifn->name));
       *interfaceName = ifn;
+      ifn->gw = gw;
       return;
     }
 
     /* do not return, fall-through to classic naming as fallback */
   }
 
-  memset(name, 0, IFNAMSIZ);
   snprintf(name, IFNAMSIZ, "tnl_%08x", (olsr_cnf->ip_version == AF_INET) ? gw->originator.v4.s_addr : ++counter);
   *interfaceName = NULL;
 }
@@ -237,8 +245,6 @@ static void set_unused_iptunnel_name(struct gateway_entry *gw) {
   }
 
   assert(gw);
-  assert(sgwTunnel4InterfaceNames);
-  assert(sgwTunnel6InterfaceNames);
 
   ifn = find_interfaceName(gw);
   if (ifn) {
@@ -299,7 +305,7 @@ static bool multiGwRunScript(const char * mode, bool add, const char * ifname, u
 /**
  * Setup generic multi-gateway iptables and ip rules
  *
- * - generic (on olsrd up/down)
+ * - generic (on olsrd start/stop)
  * iptablesExecutable -t mangle -A OUTPUT -j CONNMARK --restore-mark
  *
  * @param add true to add policy routing, false to remove it
@@ -312,7 +318,7 @@ static bool multiGwRulesGeneric(bool add) {
 /**
  * Setup multi-gateway iptables and ip rules for all OLSR interfaces.
  *
- * - olsr interfaces (on olsrd up/down)
+ * - olsr interfaces (on olsrd start/stop)
  * iptablesExecutable -t mangle -A PREROUTING -i ${olsrInterface} -j CONNMARK --restore-mark
  *
  * @param add true to add policy routing, false to remove it
@@ -337,7 +343,7 @@ static bool multiGwRulesOlsrInterfaces(bool add) {
 /**
  * Setup multi-gateway iptables and ip rules for the smart gateway server tunnel.
  *
- * - sgw server tunnel interface (on olsrd up/down)
+ * - sgw server tunnel interface (on olsrd start/stop)
  * iptablesExecutable -t mangle -A PREROUTING  -i tunl0 -j CONNMARK --restore-mark
  *
  * @param add true to add policy routing, false to remove it
@@ -350,7 +356,7 @@ static bool multiGwRulesSgwServerTunnel(bool add) {
 /**
  * Setup multi-gateway iptables and ip rules for all egress interfaces.
  *
- * - egress interfaces (on interface up/down)
+ * - egress interfaces (on interface start/stop)
  * iptablesExecutable -t mangle -A POSTROUTING -m conntrack --ctstate NEW -o ${egressInterface} -j CONNMARK --set-mark ${egressInterfaceMark}
  * iptablesExecutable -t mangle -A INPUT       -m conntrack --ctstate NEW -i ${egressInterface} -j CONNMARK --set-mark ${egressInterfaceMark}
  * ip rule add fwmark ${egressInterfaceMark} table ${egressInterfaceMark} pref ${egressInterfaceMark}
@@ -382,7 +388,7 @@ static bool multiGwRulesEgressInterfaces(bool add) {
 /**
  * Setup multi-gateway iptables and ip rules for the smart gateway client tunnels.
  *
- * - sgw tunnels (on sgw tunnel up/down)
+ * - sgw tunnels (on sgw tunnel start/stop)
  * iptablesExecutable -t mangle -A POSTROUTING -m conntrack --ctstate NEW -o ${sgwTunnelInterface} -j CONNMARK --set-mark ${sgwTunnelInterfaceMark}
  * ip rule add fwmark ${sgwTunnelInterfaceMark} table ${sgwTunnelInterfaceMark} pref ${sgwTunnelInterfaceMark}
  *
@@ -450,6 +456,88 @@ static void cleanup_gateway_handler(void *ptr) {
   olsr_cookie_free(gateway_entry_mem_cookie, gw);
 }
 
+/**
+ * Remove a gateway from a gateway list.
+ *
+ * @param gw_list a pointer to the gateway list
+ * @param ipv4 true when dealing with an IPv4 gateway / gateway list
+ * @param gw a pointer to the gateway to remove from the list
+ */
+static void removeGatewayFromList(struct gw_list * gw_list, bool ipv4, struct gw_container_entry * gw) {
+  if (gw->tunnel) {
+    struct interfaceName * ifn = find_interfaceName(gw->gw);
+    if (ifn) {
+      olsr_os_inetgw_tunnel_route(gw->tunnel->if_index, ipv4, false, ifn->mark);
+    }
+    olsr_os_del_ipip_tunnel(gw->tunnel);
+    set_unused_iptunnel_name(gw->gw);
+    gw->tunnel = NULL;
+  }
+  gw->gw = NULL;
+  olsr_cookie_free(gw_container_entry_mem_cookie, olsr_gw_list_remove(gw_list, gw));
+}
+
+/**
+ * Remove expensive gateways from the gateway list.
+ * It uses the smart_gw_takedown_percentage configuration parameter
+ *
+ * @param gw_list a pointer to the gateway list
+ * @param ipv4 true when dealing with an IPv4 gateway / gateway list
+ * @param current_gw the current gateway
+ */
+static void takeDownExpensiveGateways(struct gw_list * gw_list, bool ipv4, struct gw_container_entry * current_gw) {
+  uint64_t current_gw_cost_boundary;
+
+  /*
+   * exit immediately when takedown is disabled, there is no current gateway, or
+   * when there is only a single gateway
+   */
+  if ((olsr_cnf->smart_gw_takedown_percentage == 0) || (current_gw == NULL ) || (gw_list->count <= 1)) {
+    return;
+  }
+
+  /* get the cost boundary */
+
+  /* scale down because otherwise the percentage calculation can overflow */
+  current_gw_cost_boundary = (current_gw->path_cost >> 2);
+
+  if (olsr_cnf->smart_gw_takedown_percentage < 100) {
+    current_gw_cost_boundary = (current_gw_cost_boundary * 100) / olsr_cnf->smart_gw_takedown_percentage;
+  }
+
+  /* loop while we still have gateways */
+  while (gw_list->count > 1) {
+    /* get the worst gateway */
+    struct gw_container_entry * worst_gw = olsr_gw_list_get_worst_entry(gw_list);
+
+    /* exit when it's the current gateway */
+    if (worst_gw == current_gw) {
+      return;
+    }
+
+    /*
+     * exit when it (and further ones; the list is sorted on costs) has lower
+     * costs than the boundary costs
+     */
+    if ((worst_gw->path_cost >> 2) < current_gw_cost_boundary) {
+      return;
+    }
+
+    /* it's is too expensive: take it down */
+    removeGatewayFromList(gw_list, ipv4, worst_gw);
+  }
+}
+
+/**
+ * Timer callback for proactive gateway takedown
+ *
+ * @param unused unused
+ */
+static void gw_takedown_timer_callback(void *unused __attribute__ ((unused))) {
+  takeDownExpensiveGateways(&gw_list_ipv4, true, current_ipv4_gw);
+  takeDownExpensiveGateways(&gw_list_ipv6, false, current_ipv6_gw);
+}
+
 /*
  * Main Interface
  */
@@ -458,6 +546,8 @@ static void cleanup_gateway_handler(void *ptr) {
  * Initialize gateway system
  */
 int olsr_init_gateways(void) {
+  int retries = 5;
+
   gateway_entry_mem_cookie = olsr_alloc_cookie("gateway_entry_mem_cookie", OLSR_COOKIE_TYPE_MEMORY);
   olsr_cookie_set_memory_size(gateway_entry_mem_cookie, sizeof(struct gateway_entry));
 
@@ -520,7 +610,22 @@ int olsr_init_gateways(void) {
   gw_handler = &gw_def_handler;
   gw_handler->init();
 
-  if (olsr_os_init_iptunnel(server_tunnel_name())) {
+
+  /*
+   * There appears to be a kernel bug in some kernels (at least in the 3.0
+   * Debian Squeeze kernel, but not in the Fedora 17 kernels) around
+   * initialising the IPIP server tunnel (loading the IPIP module), so we retry
+   * a few times before giving up
+   */
+  while (retries-- > 0) {
+    if (!olsr_os_init_iptunnel(server_tunnel_name())) {
+      retries = 5;
+      break;
+    }
+
+    olsr_printf(0, "Could not initialise the IPIP server tunnel, retrying %d more times\n", retries);
+  }
+  if (retries <= 0) {
     return 1;
   }
 
@@ -550,6 +655,11 @@ int olsr_startup_gateways(void) {
     return 1;
   }
 
+  if (olsr_cnf->smart_gw_takedown_percentage > 0) {
+    /* start gateway takedown timer */
+    olsr_set_timer(&gw_takedown_timer, olsr_cnf->smart_gw_period, 0, true, &gw_takedown_timer_callback, NULL, 0);
+  }
+
   return 0;
 }
 
@@ -561,6 +671,12 @@ void olsr_shutdown_gateways(void) {
     return;
   }
 
+  if (olsr_cnf->smart_gw_takedown_percentage > 0) {
+    /* stop gateway takedown timer */
+    olsr_stop_timer(gw_takedown_timer);
+    gw_takedown_timer = NULL;
+  }
+
   (void)multiGwRulesSgwTunnels(false);
   (void)multiGwRulesEgressInterfaces(false);
   (void)multiGwRulesOlsrInterfaces(false);
@@ -572,18 +688,17 @@ void olsr_shutdown_gateways(void) {
  * Cleanup gateway tunnel system
  */
 void olsr_cleanup_gateways(void) {
-  struct avl_node * avlnode = NULL;
+  struct gateway_entry * tree_gw;
   struct gw_container_entry * gw;
 
   olsr_remove_ifchange_handler(smartgw_tunnel_monitor);
 
   /* remove all gateways in the gateway tree that are not the active gateway */
-  while ((avlnode = avl_walk_first(&gateway_tree))) {
-    struct gateway_entry* tree_gw = node2gateway(avlnode);
+  OLSR_FOR_ALL_GATEWAY_ENTRIES(tree_gw) {
     if ((tree_gw != olsr_get_inet_gateway(false)) && (tree_gw != olsr_get_inet_gateway(true))) {
       olsr_delete_gateway_tree_entry(tree_gw, FORCE_DELETE_GW_ENTRY, true);
     }
-  }
+  } OLSR_FOR_ALL_GATEWAY_ENTRIES_END(gw)
 
   /* remove all active IPv4 gateways (should be at most 1 now) */
   OLSR_FOR_ALL_GWS(&gw_list_ipv4.head, gw) {
@@ -681,6 +796,7 @@ void olsr_print_gateway_entries(void) {
 void olsr_modifiy_inetgw_netmask(union olsr_ip_addr *mask, int prefixlen) {
   uint8_t *ptr = hna_mask_to_hna_pointer(mask, prefixlen);
 
+  /* copy the current settings for uplink/downlink into the mask */
   memcpy(ptr, &smart_gateway_netmask, sizeof(smart_gateway_netmask) - prefixlen / 8);
   if (olsr_cnf->has_ipv4_gateway) {
     ptr[GW_HNA_FLAGS] |= GW_HNA_FLAG_IPV4;
@@ -706,17 +822,17 @@ void olsr_modifiy_inetgw_netmask(union olsr_ip_addr *mask, int prefixlen) {
  */
 void refresh_smartgw_netmask(void) {
   uint8_t *ip;
+
+  /* clear the mask */
   memset(&smart_gateway_netmask, 0, sizeof(smart_gateway_netmask));
 
   if (olsr_cnf->smart_gw_active) {
     ip = (uint8_t *) &smart_gateway_netmask;
 
-    if (olsr_cnf->smart_gw_uplink > 0 && olsr_cnf->smart_gw_downlink > 0) {
-      /* the link is bi-directional with a non-zero bandwidth */
-      ip[GW_HNA_FLAGS] |= GW_HNA_FLAG_LINKSPEED;
-      ip[GW_HNA_DOWNLINK] = serialize_gw_speed(olsr_cnf->smart_gw_downlink);
-      ip[GW_HNA_UPLINK] = serialize_gw_speed(olsr_cnf->smart_gw_uplink);
-    }
+    ip[GW_HNA_FLAGS] |= GW_HNA_FLAG_LINKSPEED;
+    ip[GW_HNA_DOWNLINK] = serialize_gw_speed(olsr_cnf->smart_gw_downlink);
+    ip[GW_HNA_UPLINK] = serialize_gw_speed(olsr_cnf->smart_gw_uplink);
+
     if (olsr_cnf->ip_version == AF_INET6 && olsr_cnf->smart_gw_prefix.prefix_len > 0) {
       ip[GW_HNA_FLAGS] |= GW_HNA_FLAG_IPV6PREFIX;
       ip[GW_HNA_V6PREFIXLEN] = olsr_cnf->smart_gw_prefix.prefix_len;
@@ -1008,8 +1124,11 @@ bool olsr_set_inet_gateway(union olsr_ip_addr *originator, uint64_t path_cost, b
   }
 
   /* handle IPv4 */
-  if (ipv4 && new_gw->ipv4 && (!new_gw->ipv4nat || olsr_cnf->smart_gw_allow_nat) && (!current_ipv4_gw || current_ipv4_gw->gw != new_gw)) {
-    /* new gw is different than the current gw */
+  if (ipv4 &&
+      new_gw->ipv4 &&
+      (!new_gw->ipv4nat || olsr_cnf->smart_gw_allow_nat) &&
+      (!current_ipv4_gw || current_ipv4_gw->gw != new_gw || current_ipv4_gw->path_cost != path_cost)) {
+    /* new gw is different than the current gw, or costs have changed */
 
     struct gw_container_entry * new_gw_in_list = olsr_gw_list_find(&gw_list_ipv4, new_gw);
     if (new_gw_in_list) {
@@ -1028,17 +1147,7 @@ bool olsr_set_inet_gateway(union olsr_ip_addr *originator, uint64_t path_cost, b
         struct gw_container_entry* worst = olsr_gw_list_get_worst_entry(&gw_list_ipv4);
         assert(worst);
 
-        if (worst->tunnel) {
-          struct interfaceName * ifn = find_interfaceName(worst->gw);
-          if (ifn) {
-            olsr_os_inetgw_tunnel_route(worst->tunnel->if_index, true, false, ifn->mark);
-          }
-          olsr_os_del_ipip_tunnel(worst->tunnel);
-          set_unused_iptunnel_name(worst->gw);
-          worst->tunnel = NULL;
-        }
-        worst->gw = NULL;
-        olsr_cookie_free(gw_container_entry_mem_cookie, olsr_gw_list_remove(&gw_list_ipv4, worst));
+        removeGatewayFromList(&gw_list_ipv4, true, worst);
       }
 
       get_unused_iptunnel_name(new_gw, name, &interfaceName);
@@ -1063,8 +1172,10 @@ bool olsr_set_inet_gateway(union olsr_ip_addr *originator, uint64_t path_cost, b
   }
 
   /* handle IPv6 */
-  if (ipv6 && new_gw->ipv6 && (!current_ipv6_gw || current_ipv6_gw->gw != new_gw)) {
-    /* new gw is different than the current gw */
+  if (ipv6 &&
+      new_gw->ipv6 &&
+      (!current_ipv6_gw || current_ipv6_gw->gw != new_gw || current_ipv6_gw->path_cost != path_cost)) {
+    /* new gw is different than the current gw, or costs have changed */
 
        struct gw_container_entry * new_gw_in_list = olsr_gw_list_find(&gw_list_ipv6, new_gw);
     if (new_gw_in_list) {
@@ -1083,17 +1194,7 @@ bool olsr_set_inet_gateway(union olsr_ip_addr *originator, uint64_t path_cost, b
         struct gw_container_entry* worst = olsr_gw_list_get_worst_entry(&gw_list_ipv6);
         assert(worst);
 
-        if (worst->tunnel) {
-          struct interfaceName * ifn = find_interfaceName(worst->gw);
-          if (ifn) {
-            olsr_os_inetgw_tunnel_route(worst->tunnel->if_index, false, false, ifn->mark);
-          }
-          olsr_os_del_ipip_tunnel(worst->tunnel);
-          set_unused_iptunnel_name(worst->gw);
-          worst->tunnel = NULL;
-        }
-        worst->gw = NULL;
-        olsr_cookie_free(gw_container_entry_mem_cookie, olsr_gw_list_remove(&gw_list_ipv6, worst));
+        removeGatewayFromList(&gw_list_ipv6, false, worst);
       }
 
       get_unused_iptunnel_name(new_gw, name, &interfaceName);