|
@@ -12,9 +12,7 @@
|
|
|
* General Public License for more details.
|
|
|
*
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
- * along with this program; if not, write to the Free Software
|
|
|
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
- * 02110-1301, USA
|
|
|
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
*/
|
|
|
|
|
|
#include "main.h"
|
|
@@ -30,11 +28,17 @@
|
|
|
#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)
|
|
|
{
|
|
@@ -105,7 +109,18 @@ static void batadv_gw_select(struct batadv_priv *bat_priv,
|
|
|
spin_unlock_bh(&bat_priv->gw.list_lock);
|
|
|
}
|
|
|
|
|
|
-void batadv_gw_deselect(struct batadv_priv *bat_priv)
|
|
|
+/**
|
|
|
+ * batadv_gw_reselect - force a gateway reselection
|
|
|
+ * @bat_priv: the bat priv with all the soft interface information
|
|
|
+ *
|
|
|
+ * Set a flag to remind the GW component to perform a new gateway reselection.
|
|
|
+ * However this function does not ensure that the current gateway is going to be
|
|
|
+ * deselected. The reselection mechanism may elect the same gateway once again.
|
|
|
+ *
|
|
|
+ * This means that invoking batadv_gw_reselect() does not guarantee a gateway
|
|
|
+ * change and therefore a uevent is not necessarily expected.
|
|
|
+ */
|
|
|
+void batadv_gw_reselect(struct batadv_priv *bat_priv)
|
|
|
{
|
|
|
atomic_set(&bat_priv->gw.reselect, 1);
|
|
|
}
|
|
@@ -207,6 +222,11 @@ void batadv_gw_check_client_stop(struct batadv_priv *bat_priv)
|
|
|
if (!curr_gw)
|
|
|
return;
|
|
|
|
|
|
+ /* deselect the current gateway so that next time that client mode is
|
|
|
+ * enabled a proper GW_ADD event can be sent
|
|
|
+ */
|
|
|
+ batadv_gw_select(bat_priv, NULL);
|
|
|
+
|
|
|
/* if batman-adv is switching the gw client mode off and a gateway was
|
|
|
* already selected, send a DEL uevent
|
|
|
*/
|
|
@@ -239,7 +259,7 @@ void batadv_gw_election(struct batadv_priv *bat_priv)
|
|
|
|
|
|
router = batadv_orig_node_get_router(next_gw->orig_node);
|
|
|
if (!router) {
|
|
|
- batadv_gw_deselect(bat_priv);
|
|
|
+ batadv_gw_reselect(bat_priv);
|
|
|
goto out;
|
|
|
}
|
|
|
}
|
|
@@ -291,11 +311,11 @@ void batadv_gw_check_election(struct batadv_priv *bat_priv,
|
|
|
|
|
|
curr_gw_orig = batadv_gw_get_selected_orig(bat_priv);
|
|
|
if (!curr_gw_orig)
|
|
|
- goto deselect;
|
|
|
+ goto reselect;
|
|
|
|
|
|
router_gw = batadv_orig_node_get_router(curr_gw_orig);
|
|
|
if (!router_gw)
|
|
|
- goto deselect;
|
|
|
+ goto reselect;
|
|
|
|
|
|
/* this node already is the gateway */
|
|
|
if (curr_gw_orig == orig_node)
|
|
@@ -323,8 +343,8 @@ void batadv_gw_check_election(struct batadv_priv *bat_priv,
|
|
|
"Restarting gateway selection: better gateway found (tq curr: %i, tq new: %i)\n",
|
|
|
gw_tq_avg, orig_tq_avg);
|
|
|
|
|
|
-deselect:
|
|
|
- batadv_gw_deselect(bat_priv);
|
|
|
+reselect:
|
|
|
+ batadv_gw_reselect(bat_priv);
|
|
|
out:
|
|
|
if (curr_gw_orig)
|
|
|
batadv_orig_node_free_ref(curr_gw_orig);
|
|
@@ -454,7 +474,7 @@ void batadv_gw_node_update(struct batadv_priv *bat_priv,
|
|
|
*/
|
|
|
curr_gw = batadv_gw_get_selected_gw_node(bat_priv);
|
|
|
if (gw_node == curr_gw)
|
|
|
- batadv_gw_deselect(bat_priv);
|
|
|
+ batadv_gw_reselect(bat_priv);
|
|
|
}
|
|
|
|
|
|
out:
|
|
@@ -480,7 +500,7 @@ void batadv_gw_node_purge(struct batadv_priv *bat_priv)
|
|
|
struct batadv_gw_node *gw_node, *curr_gw;
|
|
|
struct hlist_node *node_tmp;
|
|
|
unsigned long timeout = msecs_to_jiffies(2 * BATADV_PURGE_TIMEOUT);
|
|
|
- int do_deselect = 0;
|
|
|
+ int do_reselect = 0;
|
|
|
|
|
|
curr_gw = batadv_gw_get_selected_gw_node(bat_priv);
|
|
|
|
|
@@ -494,7 +514,7 @@ void batadv_gw_node_purge(struct batadv_priv *bat_priv)
|
|
|
continue;
|
|
|
|
|
|
if (curr_gw == gw_node)
|
|
|
- do_deselect = 1;
|
|
|
+ do_reselect = 1;
|
|
|
|
|
|
hlist_del_rcu(&gw_node->list);
|
|
|
batadv_gw_node_free_ref(gw_node);
|
|
@@ -502,9 +522,9 @@ void batadv_gw_node_purge(struct batadv_priv *bat_priv)
|
|
|
|
|
|
spin_unlock_bh(&bat_priv->gw.list_lock);
|
|
|
|
|
|
- /* gw_deselect() needs to acquire the gw_list_lock */
|
|
|
- if (do_deselect)
|
|
|
- batadv_gw_deselect(bat_priv);
|
|
|
+ /* gw_reselect() needs to acquire the gw_list_lock */
|
|
|
+ if (do_reselect)
|
|
|
+ batadv_gw_reselect(bat_priv);
|
|
|
|
|
|
if (curr_gw)
|
|
|
batadv_gw_node_free_ref(curr_gw);
|
|
@@ -582,80 +602,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;
|
|
@@ -663,7 +642,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;
|
|
@@ -674,32 +653,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;
|
|
@@ -710,17 +691,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
|
|
@@ -734,6 +738,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)
|
|
@@ -741,19 +746,13 @@ 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 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)
|
|
@@ -763,10 +762,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
|