Bug #153 » 0001-batman-adv-gw-performance.patch
| gateway_client.c | ||
|---|---|---|
|
#include "gateway_common.h"
|
||
|
#include "hard-interface.h"
|
||
|
#include "originator.h"
|
||
|
#include "routing.h"
|
||
|
#include <linux/ip.h>
|
||
|
#include <linux/ipv6.h>
|
||
|
#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 */
|
||
|
#define DHCP_OPTIONS_OFFSET 240
|
||
|
#define DHCP_REQUEST 3
|
||
|
static void gw_node_free_ref(struct gw_node *gw_node)
|
||
|
{
|
||
|
if (atomic_dec_and_test(&gw_node->refcount))
|
||
|
kfree_rcu(gw_node, rcu);
|
||
|
}
|
||
|
static struct gw_node *gw_get_selected_gw_node(struct bat_priv *bat_priv)
|
||
|
{
|
||
|
struct gw_node *gw_node;
|
||
|
rcu_read_lock();
|
||
|
gw_node = rcu_dereference(bat_priv->curr_gw);
|
||
|
if (!gw_node)
|
||
|
goto out;
|
||
|
if (!atomic_inc_not_zero(&gw_node->refcount))
|
||
|
gw_node = NULL;
|
||
|
out:
|
||
|
rcu_read_unlock();
|
||
|
return gw_node;
|
||
|
}
|
||
|
struct orig_node *gw_get_selected_orig(struct bat_priv *bat_priv)
|
||
|
{
|
||
| ... | ... | |
|
hardif_free_ref(primary_if);
|
||
|
return ret;
|
||
|
}
|
||
|
static bool 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 + DHCP_OPTIONS_OFFSET + 1)
|
||
|
goto out;
|
||
|
p = skb->data + header_len + DHCP_OPTIONS_OFFSET;
|
||
|
pkt_len -= header_len + DHCP_OPTIONS_OFFSET + 1;
|
||
|
/* Access the dhcp option lists. Each entry is made up by:
|
||
|
* - octect 1: option type
|
||
|
* - octect 2: option data len (only if type != 255 and 0)
|
||
|
* - octect 3: option data */
|
||
|
while (*p != 255 && !ret) {
|
||
|
/* p now points to the first octect: option type */
|
||
|
if (*p == 53) {
|
||
|
/* type 53 is the message type option.
|
||
|
* Jump the len octect and go to the data octect */
|
||
|
if (pkt_len < 2)
|
||
|
goto out;
|
||
|
p += 2;
|
||
|
/* check if the message type is what we need */
|
||
|
if (*p == 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 < *p)
|
||
|
goto out;
|
||
|
pkt_len -= *p;
|
||
|
p += (*p);
|
||
|
}
|
||
|
}
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
int gw_is_target(struct bat_priv *bat_priv, struct sk_buff *skb,
|
||
|
struct orig_node *old_gw)
|
||
|
{
|
||
|
struct ethhdr *ethhdr;
|
||
|
struct iphdr *iphdr;
|
||
|
struct ipv6hdr *ipv6hdr;
|
||
|
struct udphdr *udphdr;
|
||
|
struct gw_node *curr_gw;
|
||
|
struct neigh_node *neigh_curr = NULL, *neigh_old = NULL;
|
||
|
unsigned int header_len = 0;
|
||
|
int ret = 1;
|
||
|
if (atomic_read(&bat_priv->gw_mode) == GW_MODE_OFF)
|
||
|
return 0;
|
||
|
/* check for ethernet header */
|
||
|
if (!pskb_may_pull(skb, header_len + ETH_HLEN))
|
||
|
return 0;
|
||
|
ethhdr = (struct ethhdr *)skb->data;
|
||
|
header_len += ETH_HLEN;
|
||
|
/* check for initial vlan header */
|
||
|
if (ntohs(ethhdr->h_proto) == ETH_P_8021Q) {
|
||
|
if (!pskb_may_pull(skb, header_len + VLAN_HLEN))
|
||
|
return 0;
|
||
|
ethhdr = (struct ethhdr *)(skb->data + VLAN_HLEN);
|
||
|
header_len += VLAN_HLEN;
|
||
|
}
|
||
|
/* check for ip header */
|
||
|
switch (ntohs(ethhdr->h_proto)) {
|
||
|
case ETH_P_IP:
|
||
|
if (!pskb_may_pull(skb, header_len + sizeof(*iphdr)))
|
||
|
return 0;
|
||
|
iphdr = (struct iphdr *)(skb->data + header_len);
|
||
|
header_len += iphdr->ihl * 4;
|
||
|
/* check for udp header */
|
||
|
if (iphdr->protocol != IPPROTO_UDP)
|
||
|
return 0;
|
||
|
break;
|
||
|
case ETH_P_IPV6:
|
||
|
if (!pskb_may_pull(skb, header_len + sizeof(*ipv6hdr)))
|
||
|
return 0;
|
||
|
ipv6hdr = (struct ipv6hdr *)(skb->data + header_len);
|
||
|
header_len += sizeof(*ipv6hdr);
|
||
|
/* check for udp header */
|
||
|
if (ipv6hdr->nexthdr != IPPROTO_UDP)
|
||
|
return 0;
|
||
|
break;
|
||
|
default:
|
||
|
return 0;
|
||
|
}
|
||
|
if (!pskb_may_pull(skb, header_len + sizeof(*udphdr)))
|
||
|
return 0;
|
||
|
udphdr = (struct udphdr *)(skb->data + header_len);
|
||
|
header_len += sizeof(*udphdr);
|
||
|
/* check for bootp port */
|
||
|
if ((ntohs(ethhdr->h_proto) == ETH_P_IP) &&
|
||
|
(ntohs(udphdr->dest) != 67))
|
||
|
return 0;
|
||
|
if ((ntohs(ethhdr->h_proto) == ETH_P_IPV6) &&
|
||
|
(ntohs(udphdr->dest) != 547))
|
||
|
return 0;
|
||
|
if (atomic_read(&bat_priv->gw_mode) == GW_MODE_SERVER)
|
||
|
return -1;
|
||
|
curr_gw = gw_get_selected_gw_node(bat_priv);
|
||
|
if (!curr_gw)
|
||
|
return 0;
|
||
|
/* If old_gw != NULL then this packet is unicast.
|
||
|
* So, at this point we have to check the message type: if it is a
|
||
|
* DHCPREQUEST we have to decide whether to drop it or not */
|
||
|
if (old_gw && curr_gw->orig_node != old_gw) {
|
||
|
if (is_type_dhcprequest(skb, header_len)) {
|
||
|
/* If the dhcp packet has been sent to a different gw,
|
||
|
* we have to evaluate whether the old gw is still
|
||
|
* reliable enough */
|
||
|
neigh_curr = find_router(bat_priv, curr_gw->orig_node,
|
||
|
NULL);
|
||
|
neigh_old = find_router(bat_priv, old_gw, NULL);
|
||
|
if (!neigh_curr || !neigh_old)
|
||
|
goto free_neigh;
|
||
|
if (neigh_curr->tq_avg - neigh_old->tq_avg <
|
||
|
GW_THRESHOLD)
|
||
|
ret = -1;
|
||
|
}
|
||
|
}
|
||
|
free_neigh:
|
||
|
if (neigh_old)
|
||
|
neigh_node_free_ref(neigh_old);
|
||
|
if (neigh_curr)
|
||
|
neigh_node_free_ref(neigh_curr);
|
||
|
if (curr_gw)
|
||
|
gw_node_free_ref(curr_gw);
|
||
|
return ret;
|
||
|
}
|
||
| gateway_client.h | ||
|---|---|---|
|
#ifndef _NET_BATMAN_ADV_GATEWAY_CLIENT_H_
|
||
|
#define _NET_BATMAN_ADV_GATEWAY_CLIENT_H_
|
||
|
/* This is the offset of the options field in a dhcp packet starting at
|
||
|
* the beginning of the dhcp header */
|
||
|
#define DHCP_OPTIONS_OFFSET 240
|
||
|
#define DHCP_REQUEST 3
|
||
|
#include "translation-table.h"
|
||
|
#include "originator.h"
|
||
|
#include "routing.h"
|
||
|
#include <linux/ip.h>
|
||
|
#include <linux/ipv6.h>
|
||
|
#include <linux/udp.h>
|
||
|
#include <linux/if_vlan.h>
|
||
|
void gw_deselect(struct bat_priv *bat_priv);
|
||
|
void gw_election(struct bat_priv *bat_priv);
|
||
|
struct orig_node *gw_get_selected_orig(struct bat_priv *bat_priv);
|
||
| ... | ... | |
|
void gw_node_delete(struct bat_priv *bat_priv, struct orig_node *orig_node);
|
||
|
void gw_node_purge(struct bat_priv *bat_priv);
|
||
|
int gw_client_seq_print_text(struct seq_file *seq, void *offset);
|
||
|
int gw_is_target(struct bat_priv *bat_priv, struct sk_buff *skb,
|
||
|
struct orig_node *old_gw);
|
||
|
static inline void gw_node_free_ref(struct gw_node *gw_node)
|
||
|
{
|
||
|
if (atomic_dec_and_test(&gw_node->refcount))
|
||
|
kfree_rcu(gw_node, rcu);
|
||
|
}
|
||
|
static inline struct gw_node *gw_get_selected_gw_node(struct bat_priv *bat_priv)
|
||
|
{
|
||
|
struct gw_node *gw_node;
|
||
|
rcu_read_lock();
|
||
|
gw_node = rcu_dereference(bat_priv->curr_gw);
|
||
|
if (!gw_node)
|
||
|
goto out;
|
||
|
if (!atomic_inc_not_zero(&gw_node->refcount))
|
||
|
gw_node = NULL;
|
||
|
out:
|
||
|
rcu_read_unlock();
|
||
|
return gw_node;
|
||
|
}
|
||
|
static inline bool 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 + DHCP_OPTIONS_OFFSET + 1)
|
||
|
goto out;
|
||
|
p = skb->data + header_len + DHCP_OPTIONS_OFFSET;
|
||
|
pkt_len -= header_len + DHCP_OPTIONS_OFFSET + 1;
|
||
|
/* Access the dhcp option lists. Each entry is made up by:
|
||
|
* - octect 1: option type
|
||
|
* - octect 2: option data len (only if type != 255 and 0)
|
||
|
* - octect 3: option data */
|
||
|
while (*p != 255 && !ret) {
|
||
|
/* p now points to the first octect: option type */
|
||
|
if (*p == 53) {
|
||
|
/* type 53 is the message type option.
|
||
|
* Jump the len octect and go to the data octect */
|
||
|
if (pkt_len < 2)
|
||
|
goto out;
|
||
|
p += 2;
|
||
|
/* check if the message type is what we need */
|
||
|
if (*p == 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 < *p)
|
||
|
goto out;
|
||
|
pkt_len -= *p;
|
||
|
p += (*p);
|
||
|
}
|
||
|
}
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
static inline bool gw_is_dhcp_target(struct sk_buff *skb,
|
||
|
unsigned int *header_len)
|
||
|
{
|
||
|
struct ethhdr *ethhdr;
|
||
|
struct iphdr *iphdr;
|
||
|
struct ipv6hdr *ipv6hdr;
|
||
|
struct udphdr *udphdr;
|
||
|
/* check for ethernet header */
|
||
|
if (!pskb_may_pull(skb, *header_len + ETH_HLEN))
|
||
|
return false;
|
||
|
ethhdr = (struct ethhdr *)skb->data;
|
||
|
*header_len += ETH_HLEN;
|
||
|
/* check for initial vlan header */
|
||
|
if (ntohs(ethhdr->h_proto) == ETH_P_8021Q) {
|
||
|
if (!pskb_may_pull(skb, *header_len + VLAN_HLEN))
|
||
|
return false;
|
||
|
ethhdr = (struct ethhdr *)(skb->data + VLAN_HLEN);
|
||
|
*header_len += VLAN_HLEN;
|
||
|
}
|
||
|
/* check for ip header */
|
||
|
switch (ntohs(ethhdr->h_proto)) {
|
||
|
case ETH_P_IP:
|
||
|
if (!pskb_may_pull(skb, *header_len + sizeof(*iphdr)))
|
||
|
return false;
|
||
|
iphdr = (struct iphdr *)(skb->data + *header_len);
|
||
|
*header_len += iphdr->ihl * 4;
|
||
|
/* check for udp header */
|
||
|
if (iphdr->protocol != IPPROTO_UDP)
|
||
|
return false;
|
||
|
break;
|
||
|
case ETH_P_IPV6:
|
||
|
if (!pskb_may_pull(skb, *header_len + sizeof(*ipv6hdr)))
|
||
|
return false;
|
||
|
ipv6hdr = (struct ipv6hdr *)(skb->data + *header_len);
|
||
|
*header_len += sizeof(*ipv6hdr);
|
||
|
/* check for udp header */
|
||
|
if (ipv6hdr->nexthdr != IPPROTO_UDP)
|
||
|
return false;
|
||
|
break;
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
if (!pskb_may_pull(skb, *header_len + sizeof(*udphdr)))
|
||
|
return false;
|
||
|
udphdr = (struct udphdr *)(skb->data + *header_len);
|
||
|
*header_len += sizeof(*udphdr);
|
||
|
/* check for bootp port */
|
||
|
if ((ntohs(ethhdr->h_proto) == ETH_P_IP) &&
|
||
|
(ntohs(udphdr->dest) != 67))
|
||
|
return false;
|
||
|
if ((ntohs(ethhdr->h_proto) == ETH_P_IPV6) &&
|
||
|
(ntohs(udphdr->dest) != 547))
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
static inline bool gw_out_of_range(struct bat_priv *bat_priv,
|
||
|
struct sk_buff *skb, struct ethhdr *ethhdr)
|
||
|
{
|
||
|
struct neigh_node *neigh_curr = NULL, *neigh_old = NULL;
|
||
|
struct orig_node *orig_dst_node = NULL;
|
||
|
struct gw_node *curr_gw = NULL;
|
||
|
bool ret, out_of_range = false;
|
||
|
unsigned int header_len = 0;
|
||
|
ret = gw_is_dhcp_target(skb, &header_len);
|
||
|
if (!ret)
|
||
|
goto out;
|
||
|
orig_dst_node = transtable_search(bat_priv, ethhdr->h_dest);
|
||
|
if (!orig_dst_node)
|
||
|
goto out;
|
||
|
if (!orig_dst_node->gw_flags)
|
||
|
goto out;
|
||
|
curr_gw = gw_get_selected_gw_node(bat_priv);
|
||
|
if (!curr_gw)
|
||
|
goto out;
|
||
|
/* packet is going to our gateway */
|
||
|
if (curr_gw->orig_node == orig_dst_node)
|
||
|
goto out;
|
||
|
/* TODO: what happens if the node itself is a gateway ? */
|
||
|
ret = is_type_dhcprequest(skb, header_len);
|
||
|
if (!ret)
|
||
|
goto out;
|
||
|
/* If the dhcp packet has been sent to a different gw,
|
||
|
* we have to evaluate whether the old gw is still
|
||
|
* reliable enough */
|
||
|
neigh_curr = find_router(bat_priv, curr_gw->orig_node, NULL);
|
||
|
if (!neigh_curr)
|
||
|
goto out;
|
||
|
neigh_old = find_router(bat_priv, orig_dst_node, NULL);
|
||
|
if (!!neigh_old)
|
||
|
goto out;
|
||
|
if (neigh_curr->tq_avg - neigh_old->tq_avg > GW_THRESHOLD)
|
||
|
out_of_range = true;
|
||
|
out:
|
||
|
if (orig_dst_node)
|
||
|
orig_node_free_ref(orig_dst_node);
|
||
|
if (curr_gw)
|
||
|
gw_node_free_ref(curr_gw);
|
||
|
if (neigh_old)
|
||
|
neigh_node_free_ref(neigh_old);
|
||
|
if (neigh_curr)
|
||
|
neigh_node_free_ref(neigh_curr);
|
||
|
return out_of_range;
|
||
|
}
|
||
|
#endif /* _NET_BATMAN_ADV_GATEWAY_CLIENT_H_ */
|
||
| soft-interface.c | ||
|---|---|---|
|
struct bcast_packet *bcast_packet;
|
||
|
struct vlan_ethhdr *vhdr;
|
||
|
struct softif_neigh *curr_softif_neigh = NULL;
|
||
|
struct orig_node *orig_node = NULL;
|
||
|
unsigned int header_len = 0;
|
||
|
int data_len = skb->len, ret;
|
||
|
short vid = -1;
|
||
|
bool do_bcast = false;
|
||
| ... | ... | |
|
/* Register the client MAC in the transtable */
|
||
|
tt_local_add(soft_iface, ethhdr->h_source);
|
||
|
orig_node = transtable_search(bat_priv, ethhdr->h_dest);
|
||
|
if (is_multicast_ether_addr(ethhdr->h_dest) ||
|
||
|
(orig_node && orig_node->gw_flags)) {
|
||
|
ret = gw_is_target(bat_priv, skb, orig_node);
|
||
|
if (is_multicast_ether_addr(ethhdr->h_dest)) {
|
||
|
do_bcast = true;
|
||
|
if (ret < 0)
|
||
|
goto dropped;
|
||
|
if (ret == 0)
|
||
|
do_bcast = true;
|
||
|
switch (atomic_read(&bat_priv->gw_mode)) {
|
||
|
case GW_MODE_SERVER:
|
||
|
/* gateway servers should not send dhcp
|
||
|
* requests into the mesh */
|
||
|
ret = gw_is_dhcp_target(skb, &header_len);
|
||
|
if (ret)
|
||
|
goto dropped;
|
||
|
break;
|
||
|
case GW_MODE_CLIENT:
|
||
|
/* gateway clients should send dhcp requests
|
||
|
* via unicast to their gateway */
|
||
|
ret = gw_is_dhcp_target(skb, &header_len);
|
||
|
if (ret)
|
||
|
do_bcast = false;
|
||
|
break;
|
||
|
case GW_MODE_OFF:
|
||
|
printk(KERN_INFO "interface_tx(): bcast check: gw mode off\n");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
/* ethernet packet should be broadcasted */
|
||
| ... | ... | |
|
/* unicast packet */
|
||
|
} else {
|
||
|
if (atomic_read(&bat_priv->gw_mode) != GW_MODE_OFF) {
|
||
|
ret = gw_out_of_range(bat_priv, skb, ethhdr);
|
||
|
if (ret)
|
||
|
goto dropped;
|
||
|
}
|
||
|
ret = unicast_send_skb(skb, bat_priv);
|
||
|
if (ret != 0)
|
||
|
goto dropped_freed;
|
||
| ... | ... | |
|
softif_neigh_free_ref(curr_softif_neigh);
|
||
|
if (primary_if)
|
||
|
hardif_free_ref(primary_if);
|
||
|
if (orig_node)
|
||
|
orig_node_free_ref(orig_node);
|
||
|
return NETDEV_TX_OK;
|
||
|
}
|
||