]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - net/batman-adv/gateway_client.c
Merge tag 'for-usb-linus-2014-02-04' of git://git.kernel.org/pub/scm/linux/kernel...
[karo-tx-linux.git] / net / batman-adv / gateway_client.c
index a5602ef0f262acd87d6cb3021cc03e7eebbfce07..55cf2260d295b6f4002d438ed34b52382ed7eeea 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2009-2013 B.A.T.M.A.N. contributors:
+/* Copyright (C) 2009-2014 B.A.T.M.A.N. contributors:
  *
  * Marek Lindner
  *
 #include <linux/udp.h>
 #include <linux/if_vlan.h>
 
-/* This is the offset of the options field in a dhcp packet starting at
- * the beginning of the dhcp header
+/* These are the offsets of the "hw type" and "hw address length" in the dhcp
+ * packet starting at the beginning of the dhcp header
  */
-#define BATADV_DHCP_OPTIONS_OFFSET 240
-#define BATADV_DHCP_REQUEST 3
+#define BATADV_DHCP_HTYPE_OFFSET       1
+#define BATADV_DHCP_HLEN_OFFSET                2
+/* Value of htype representing Ethernet */
+#define BATADV_DHCP_HTYPE_ETHERNET     0x01
+/* This is the offset of the "chaddr" field in the dhcp packet starting at the
+ * beginning of the dhcp header
+ */
+#define BATADV_DHCP_CHADDR_OFFSET      28
 
 static void batadv_gw_node_free_ref(struct batadv_gw_node *gw_node)
 {
@@ -123,6 +129,7 @@ static struct batadv_gw_node *
 batadv_gw_get_best_gw_node(struct batadv_priv *bat_priv)
 {
        struct batadv_neigh_node *router;
+       struct batadv_neigh_ifinfo *router_ifinfo;
        struct batadv_gw_node *gw_node, *curr_gw = NULL;
        uint32_t max_gw_factor = 0, tmp_gw_factor = 0;
        uint32_t gw_divisor;
@@ -139,14 +146,19 @@ batadv_gw_get_best_gw_node(struct batadv_priv *bat_priv)
                        continue;
 
                orig_node = gw_node->orig_node;
-               router = batadv_orig_node_get_router(orig_node);
+               router = batadv_orig_router_get(orig_node, BATADV_IF_DEFAULT);
                if (!router)
                        continue;
 
+               router_ifinfo = batadv_neigh_ifinfo_get(router,
+                                                       BATADV_IF_DEFAULT);
+               if (!router_ifinfo)
+                       goto next;
+
                if (!atomic_inc_not_zero(&gw_node->refcount))
                        goto next;
 
-               tq_avg = router->bat_iv.tq_avg;
+               tq_avg = router_ifinfo->bat_iv.tq_avg;
 
                switch (atomic_read(&bat_priv->gw_sel_class)) {
                case 1: /* fast connection */
@@ -191,6 +203,8 @@ batadv_gw_get_best_gw_node(struct batadv_priv *bat_priv)
 
 next:
                batadv_neigh_node_free_ref(router);
+               if (router_ifinfo)
+                       batadv_neigh_ifinfo_free_ref(router_ifinfo);
        }
        rcu_read_unlock();
 
@@ -233,6 +247,7 @@ void batadv_gw_election(struct batadv_priv *bat_priv)
 {
        struct batadv_gw_node *curr_gw = NULL, *next_gw = NULL;
        struct batadv_neigh_node *router = NULL;
+       struct batadv_neigh_ifinfo *router_ifinfo = NULL;
        char gw_addr[18] = { '\0' };
 
        if (atomic_read(&bat_priv->gw_mode) != BATADV_GW_MODE_CLIENT)
@@ -251,11 +266,19 @@ void batadv_gw_election(struct batadv_priv *bat_priv)
        if (next_gw) {
                sprintf(gw_addr, "%pM", next_gw->orig_node->orig);
 
-               router = batadv_orig_node_get_router(next_gw->orig_node);
+               router = batadv_orig_router_get(next_gw->orig_node,
+                                               BATADV_IF_DEFAULT);
                if (!router) {
                        batadv_gw_reselect(bat_priv);
                        goto out;
                }
+
+               router_ifinfo = batadv_neigh_ifinfo_get(router,
+                                                       BATADV_IF_DEFAULT);
+               if (!router_ifinfo) {
+                       batadv_gw_reselect(bat_priv);
+                       goto out;
+               }
        }
 
        if ((curr_gw) && (!next_gw)) {
@@ -270,7 +293,8 @@ void batadv_gw_election(struct batadv_priv *bat_priv)
                           next_gw->bandwidth_down / 10,
                           next_gw->bandwidth_down % 10,
                           next_gw->bandwidth_up / 10,
-                          next_gw->bandwidth_up % 10, router->bat_iv.tq_avg);
+                          next_gw->bandwidth_up % 10,
+                          router_ifinfo->bat_iv.tq_avg);
                batadv_throw_uevent(bat_priv, BATADV_UEV_GW, BATADV_UEV_ADD,
                                    gw_addr);
        } else {
@@ -280,7 +304,8 @@ void batadv_gw_election(struct batadv_priv *bat_priv)
                           next_gw->bandwidth_down / 10,
                           next_gw->bandwidth_down % 10,
                           next_gw->bandwidth_up / 10,
-                          next_gw->bandwidth_up % 10, router->bat_iv.tq_avg);
+                          next_gw->bandwidth_up % 10,
+                          router_ifinfo->bat_iv.tq_avg);
                batadv_throw_uevent(bat_priv, BATADV_UEV_GW, BATADV_UEV_CHANGE,
                                    gw_addr);
        }
@@ -294,11 +319,15 @@ out:
                batadv_gw_node_free_ref(next_gw);
        if (router)
                batadv_neigh_node_free_ref(router);
+       if (router_ifinfo)
+               batadv_neigh_ifinfo_free_ref(router_ifinfo);
 }
 
 void batadv_gw_check_election(struct batadv_priv *bat_priv,
                              struct batadv_orig_node *orig_node)
 {
+       struct batadv_neigh_ifinfo *router_orig_tq = NULL;
+       struct batadv_neigh_ifinfo *router_gw_tq = NULL;
        struct batadv_orig_node *curr_gw_orig;
        struct batadv_neigh_node *router_gw = NULL, *router_orig = NULL;
        uint8_t gw_tq_avg, orig_tq_avg;
@@ -307,20 +336,30 @@ void batadv_gw_check_election(struct batadv_priv *bat_priv,
        if (!curr_gw_orig)
                goto reselect;
 
-       router_gw = batadv_orig_node_get_router(curr_gw_orig);
+       router_gw = batadv_orig_router_get(curr_gw_orig, BATADV_IF_DEFAULT);
        if (!router_gw)
                goto reselect;
 
+       router_gw_tq = batadv_neigh_ifinfo_get(router_gw,
+                                              BATADV_IF_DEFAULT);
+       if (!router_gw_tq)
+               goto reselect;
+
        /* this node already is the gateway */
        if (curr_gw_orig == orig_node)
                goto out;
 
-       router_orig = batadv_orig_node_get_router(orig_node);
+       router_orig = batadv_orig_router_get(orig_node, BATADV_IF_DEFAULT);
        if (!router_orig)
                goto out;
 
-       gw_tq_avg = router_gw->bat_iv.tq_avg;
-       orig_tq_avg = router_orig->bat_iv.tq_avg;
+       router_orig_tq = batadv_neigh_ifinfo_get(router_orig,
+                                                BATADV_IF_DEFAULT);
+       if (!router_orig_tq)
+               goto out;
+
+       gw_tq_avg = router_gw_tq->bat_iv.tq_avg;
+       orig_tq_avg = router_orig_tq->bat_iv.tq_avg;
 
        /* the TQ value has to be better */
        if (orig_tq_avg < gw_tq_avg)
@@ -346,6 +385,10 @@ out:
                batadv_neigh_node_free_ref(router_gw);
        if (router_orig)
                batadv_neigh_node_free_ref(router_orig);
+       if (router_gw_tq)
+               batadv_neigh_ifinfo_free_ref(router_gw_tq);
+       if (router_orig_tq)
+               batadv_neigh_ifinfo_free_ref(router_orig_tq);
 
        return;
 }
@@ -531,28 +574,36 @@ static int batadv_write_buffer_text(struct batadv_priv *bat_priv,
 {
        struct batadv_gw_node *curr_gw;
        struct batadv_neigh_node *router;
+       struct batadv_neigh_ifinfo *router_ifinfo = NULL;
        int ret = -1;
 
-       router = batadv_orig_node_get_router(gw_node->orig_node);
+       router = batadv_orig_router_get(gw_node->orig_node, BATADV_IF_DEFAULT);
        if (!router)
                goto out;
 
+       router_ifinfo = batadv_neigh_ifinfo_get(router, BATADV_IF_DEFAULT);
+       if (!router_ifinfo)
+               goto out;
+
        curr_gw = batadv_gw_get_selected_gw_node(bat_priv);
 
        ret = seq_printf(seq, "%s %pM (%3i) %pM [%10s]: %u.%u/%u.%u MBit\n",
                         (curr_gw == gw_node ? "=>" : "  "),
                         gw_node->orig_node->orig,
-                        router->bat_iv.tq_avg, router->addr,
+                        router_ifinfo->bat_iv.tq_avg, router->addr,
                         router->if_incoming->net_dev->name,
                         gw_node->bandwidth_down / 10,
                         gw_node->bandwidth_down % 10,
                         gw_node->bandwidth_up / 10,
                         gw_node->bandwidth_up % 10);
 
-       batadv_neigh_node_free_ref(router);
        if (curr_gw)
                batadv_gw_node_free_ref(curr_gw);
 out:
+       if (router_ifinfo)
+               batadv_neigh_ifinfo_free_ref(router_ifinfo);
+       if (router)
+               batadv_neigh_node_free_ref(router);
        return ret;
 }
 
@@ -596,80 +647,39 @@ out:
        return 0;
 }
 
-/* this call might reallocate skb data */
-static bool batadv_is_type_dhcprequest(struct sk_buff *skb, int header_len)
-{
-       int ret = false;
-       unsigned char *p;
-       int pkt_len;
-
-       if (skb_linearize(skb) < 0)
-               goto out;
-
-       pkt_len = skb_headlen(skb);
-
-       if (pkt_len < header_len + BATADV_DHCP_OPTIONS_OFFSET + 1)
-               goto out;
-
-       p = skb->data + header_len + BATADV_DHCP_OPTIONS_OFFSET;
-       pkt_len -= header_len + BATADV_DHCP_OPTIONS_OFFSET + 1;
-
-       /* Access the dhcp option lists. Each entry is made up by:
-        * - octet 1: option type
-        * - octet 2: option data len (only if type != 255 and 0)
-        * - octet 3: option data
-        */
-       while (*p != 255 && !ret) {
-               /* p now points to the first octet: option type */
-               if (*p == 53) {
-                       /* type 53 is the message type option.
-                        * Jump the len octet and go to the data octet
-                        */
-                       if (pkt_len < 2)
-                               goto out;
-                       p += 2;
-
-                       /* check if the message type is what we need */
-                       if (*p == BATADV_DHCP_REQUEST)
-                               ret = true;
-                       break;
-               } else if (*p == 0) {
-                       /* option type 0 (padding), just go forward */
-                       if (pkt_len < 1)
-                               goto out;
-                       pkt_len--;
-                       p++;
-               } else {
-                       /* This is any other option. So we get the length... */
-                       if (pkt_len < 1)
-                               goto out;
-                       pkt_len--;
-                       p++;
-
-                       /* ...and then we jump over the data */
-                       if (pkt_len < 1 + (*p))
-                               goto out;
-                       pkt_len -= 1 + (*p);
-                       p += 1 + (*p);
-               }
-       }
-out:
-       return ret;
-}
-
-/* this call might reallocate skb data */
-bool batadv_gw_is_dhcp_target(struct sk_buff *skb, unsigned int *header_len)
+/**
+ * batadv_gw_dhcp_recipient_get - check if a packet is a DHCP message
+ * @skb: the packet to check
+ * @header_len: a pointer to the batman-adv header size
+ * @chaddr: buffer where the client address will be stored. Valid
+ *  only if the function returns BATADV_DHCP_TO_CLIENT
+ *
+ * Returns:
+ * - BATADV_DHCP_NO if the packet is not a dhcp message or if there was an error
+ *   while parsing it
+ * - BATADV_DHCP_TO_SERVER if this is a message going to the DHCP server
+ * - BATADV_DHCP_TO_CLIENT if this is a message going to a DHCP client
+ *
+ * This function may re-allocate the data buffer of the skb passed as argument.
+ */
+enum batadv_dhcp_recipient
+batadv_gw_dhcp_recipient_get(struct sk_buff *skb, unsigned int *header_len,
+                            uint8_t *chaddr)
 {
+       enum batadv_dhcp_recipient ret = BATADV_DHCP_NO;
        struct ethhdr *ethhdr;
        struct iphdr *iphdr;
        struct ipv6hdr *ipv6hdr;
        struct udphdr *udphdr;
        struct vlan_ethhdr *vhdr;
+       int chaddr_offset;
        __be16 proto;
+       uint8_t *p;
 
        /* check for ethernet header */
        if (!pskb_may_pull(skb, *header_len + ETH_HLEN))
-               return false;
+               return BATADV_DHCP_NO;
+
        ethhdr = (struct ethhdr *)skb->data;
        proto = ethhdr->h_proto;
        *header_len += ETH_HLEN;
@@ -677,7 +687,7 @@ bool batadv_gw_is_dhcp_target(struct sk_buff *skb, unsigned int *header_len)
        /* check for initial vlan header */
        if (proto == htons(ETH_P_8021Q)) {
                if (!pskb_may_pull(skb, *header_len + VLAN_HLEN))
-                       return false;
+                       return BATADV_DHCP_NO;
 
                vhdr = (struct vlan_ethhdr *)skb->data;
                proto = vhdr->h_vlan_encapsulated_proto;
@@ -688,32 +698,34 @@ bool batadv_gw_is_dhcp_target(struct sk_buff *skb, unsigned int *header_len)
        switch (proto) {
        case htons(ETH_P_IP):
                if (!pskb_may_pull(skb, *header_len + sizeof(*iphdr)))
-                       return false;
+                       return BATADV_DHCP_NO;
+
                iphdr = (struct iphdr *)(skb->data + *header_len);
                *header_len += iphdr->ihl * 4;
 
                /* check for udp header */
                if (iphdr->protocol != IPPROTO_UDP)
-                       return false;
+                       return BATADV_DHCP_NO;
 
                break;
        case htons(ETH_P_IPV6):
                if (!pskb_may_pull(skb, *header_len + sizeof(*ipv6hdr)))
-                       return false;
+                       return BATADV_DHCP_NO;
+
                ipv6hdr = (struct ipv6hdr *)(skb->data + *header_len);
                *header_len += sizeof(*ipv6hdr);
 
                /* check for udp header */
                if (ipv6hdr->nexthdr != IPPROTO_UDP)
-                       return false;
+                       return BATADV_DHCP_NO;
 
                break;
        default:
-               return false;
+               return BATADV_DHCP_NO;
        }
 
        if (!pskb_may_pull(skb, *header_len + sizeof(*udphdr)))
-               return false;
+               return BATADV_DHCP_NO;
 
        /* skb->data might have been reallocated by pskb_may_pull() */
        ethhdr = (struct ethhdr *)skb->data;
@@ -724,17 +736,40 @@ bool batadv_gw_is_dhcp_target(struct sk_buff *skb, unsigned int *header_len)
        *header_len += sizeof(*udphdr);
 
        /* check for bootp port */
-       if ((proto == htons(ETH_P_IP)) &&
-           (udphdr->dest != htons(67)))
-               return false;
+       switch (proto) {
+       case htons(ETH_P_IP):
+               if (udphdr->dest == htons(67))
+                       ret = BATADV_DHCP_TO_SERVER;
+               else if (udphdr->source == htons(67))
+                       ret = BATADV_DHCP_TO_CLIENT;
+               break;
+       case htons(ETH_P_IPV6):
+               if (udphdr->dest == htons(547))
+                       ret = BATADV_DHCP_TO_SERVER;
+               else if (udphdr->source == htons(547))
+                       ret = BATADV_DHCP_TO_CLIENT;
+               break;
+       }
 
-       if ((proto == htons(ETH_P_IPV6)) &&
-           (udphdr->dest != htons(547)))
-               return false;
+       chaddr_offset = *header_len + BATADV_DHCP_CHADDR_OFFSET;
+       /* store the client address if the message is going to a client */
+       if (ret == BATADV_DHCP_TO_CLIENT &&
+           pskb_may_pull(skb, chaddr_offset + ETH_ALEN)) {
+               /* check if the DHCP packet carries an Ethernet DHCP */
+               p = skb->data + *header_len + BATADV_DHCP_HTYPE_OFFSET;
+               if (*p != BATADV_DHCP_HTYPE_ETHERNET)
+                       return BATADV_DHCP_NO;
+
+               /* check if the DHCP packet carries a valid Ethernet address */
+               p = skb->data + *header_len + BATADV_DHCP_HLEN_OFFSET;
+               if (*p != ETH_ALEN)
+                       return BATADV_DHCP_NO;
+
+               memcpy(chaddr, skb->data + chaddr_offset, ETH_ALEN);
+       }
 
-       return true;
+       return ret;
 }
-
 /**
  * batadv_gw_out_of_range - check if the dhcp request destination is the best gw
  * @bat_priv: the bat priv with all the soft interface information
@@ -748,6 +783,7 @@ bool batadv_gw_is_dhcp_target(struct sk_buff *skb, unsigned int *header_len)
  * false otherwise.
  *
  * This call might reallocate skb data.
+ * Must be invoked only when the DHCP packet is going TO a DHCP SERVER.
  */
 bool batadv_gw_out_of_range(struct batadv_priv *bat_priv,
                            struct sk_buff *skb)
@@ -755,19 +791,14 @@ bool batadv_gw_out_of_range(struct batadv_priv *bat_priv,
        struct batadv_neigh_node *neigh_curr = NULL, *neigh_old = NULL;
        struct batadv_orig_node *orig_dst_node = NULL;
        struct batadv_gw_node *gw_node = NULL, *curr_gw = NULL;
-       struct ethhdr *ethhdr;
-       bool ret, out_of_range = false;
-       unsigned int header_len = 0;
+       struct batadv_neigh_ifinfo *curr_ifinfo, *old_ifinfo;
+       struct ethhdr *ethhdr = (struct ethhdr *)skb->data;
+       bool out_of_range = false;
        uint8_t curr_tq_avg;
        unsigned short vid;
 
        vid = batadv_get_vid(skb, 0);
 
-       ret = batadv_gw_is_dhcp_target(skb, &header_len);
-       if (!ret)
-               goto out;
-
-       ethhdr = (struct ethhdr *)skb->data;
        orig_dst_node = batadv_transtable_search(bat_priv, ethhdr->h_source,
                                                 ethhdr->h_dest, vid);
        if (!orig_dst_node)
@@ -777,10 +808,6 @@ bool batadv_gw_out_of_range(struct batadv_priv *bat_priv,
        if (!gw_node->bandwidth_down == 0)
                goto out;
 
-       ret = batadv_is_type_dhcprequest(skb, header_len);
-       if (!ret)
-               goto out;
-
        switch (atomic_read(&bat_priv->gw_mode)) {
        case BATADV_GW_MODE_SERVER:
                /* If we are a GW then we are our best GW. We can artificially
@@ -806,7 +833,14 @@ bool batadv_gw_out_of_range(struct batadv_priv *bat_priv,
                if (!neigh_curr)
                        goto out;
 
-               curr_tq_avg = neigh_curr->bat_iv.tq_avg;
+               curr_ifinfo = batadv_neigh_ifinfo_get(neigh_curr,
+                                                     BATADV_IF_DEFAULT);
+               if (!curr_ifinfo)
+                       goto out;
+
+               curr_tq_avg = curr_ifinfo->bat_iv.tq_avg;
+               batadv_neigh_ifinfo_free_ref(curr_ifinfo);
+
                break;
        case BATADV_GW_MODE_OFF:
        default:
@@ -817,8 +851,13 @@ bool batadv_gw_out_of_range(struct batadv_priv *bat_priv,
        if (!neigh_old)
                goto out;
 
-       if (curr_tq_avg - neigh_old->bat_iv.tq_avg > BATADV_GW_THRESHOLD)
+       old_ifinfo = batadv_neigh_ifinfo_get(neigh_old, BATADV_IF_DEFAULT);
+       if (!old_ifinfo)
+               goto out;
+
+       if ((curr_tq_avg - old_ifinfo->bat_iv.tq_avg) > BATADV_GW_THRESHOLD)
                out_of_range = true;
+       batadv_neigh_ifinfo_free_ref(old_ifinfo);
 
 out:
        if (orig_dst_node)