Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit be7af5cf authored by Marek Lindner's avatar Marek Lindner Committed by Sven Eckelmann
Browse files

batman-adv: refactoring gateway handling code

parent 25a92b13
Loading
Loading
Loading
Loading
+94 −59
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@
#include "gateway_common.h"
#include "hard-interface.h"
#include "originator.h"
#include "translation-table.h"
#include "routing.h"
#include <linux/ip.h>
#include <linux/ipv6.h>
@@ -572,108 +573,142 @@ static bool is_type_dhcprequest(struct sk_buff *skb, int header_len)
	return ret;
}

int gw_is_target(struct bat_priv *bat_priv, struct sk_buff *skb,
		 struct orig_node *old_gw)
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;
	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;
	if (!pskb_may_pull(skb, *header_len + ETH_HLEN))
		return false;
	ethhdr = (struct ethhdr *)skb->data;
	header_len += ETH_HLEN;
	*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;
		if (!pskb_may_pull(skb, *header_len + VLAN_HLEN))
			return false;
		ethhdr = (struct ethhdr *)(skb->data + VLAN_HLEN);
		header_len += 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;
		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 0;
			return false;

		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);
		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 0;
			return false;

		break;
	default:
		return 0;
		return false;
	}

	if (!pskb_may_pull(skb, header_len + sizeof(*udphdr)))
		return 0;
	udphdr = (struct udphdr *)(skb->data + header_len);
	header_len += sizeof(*udphdr);
	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 0;
		return false;

	if ((ntohs(ethhdr->h_proto) == ETH_P_IPV6) &&
	    (ntohs(udphdr->dest) != 547))
		return 0;
		return false;

	return true;
}

	if (atomic_read(&bat_priv->gw_mode) == GW_MODE_SERVER)
		return -1;
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;
	uint8_t curr_tq_avg;

	ret = gw_is_dhcp_target(skb, &header_len);
	if (!ret)
		goto out;

	orig_dst_node = transtable_search(bat_priv, ethhdr->h_source,
					  ethhdr->h_dest);
	if (!orig_dst_node)
		goto out;

	if (!orig_dst_node->gw_flags)
		goto out;

	ret = is_type_dhcprequest(skb, header_len);
	if (!ret)
		goto out;

	switch (atomic_read(&bat_priv->gw_mode)) {
	case GW_MODE_SERVER:
		/* If we are a GW then we are our best GW. We can artificially
		 * set the tq towards ourself as the maximum value */
		curr_tq_avg = TQ_MAX_VALUE;
		break;
	case GW_MODE_CLIENT:
		curr_gw = gw_get_selected_gw_node(bat_priv);
		if (!curr_gw)
		return 0;
			goto out;

		/* packet is going to our gateway */
		if (curr_gw->orig_node == orig_dst_node)
			goto out;

	/* 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;
		}
		neigh_curr = find_router(bat_priv, curr_gw->orig_node, NULL);
		if (!neigh_curr)
			goto out;

		curr_tq_avg = neigh_curr->tq_avg;
		break;
	case GW_MODE_OFF:
	default:
		goto out;
	}
free_neigh:

	neigh_old = find_router(bat_priv, orig_dst_node, NULL);
	if (!!neigh_old)
		goto out;

	if (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);
	if (curr_gw)
		gw_node_free_ref(curr_gw);
	return ret;
	return out_of_range;
}
+3 −2
Original line number Diff line number Diff line
@@ -31,7 +31,8 @@ void gw_node_update(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);
bool gw_is_dhcp_target(struct sk_buff *skb, unsigned int *header_len);
bool gw_out_of_range(struct bat_priv *bat_priv,
		     struct sk_buff *skb, struct ethhdr *ethhdr);

#endif /* _NET_BATMAN_ADV_GATEWAY_CLIENT_H_ */
+29 −14
Original line number Diff line number Diff line
@@ -563,10 +563,10 @@ static int interface_tx(struct sk_buff *skb, struct net_device *soft_iface)
	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;
	bool do_bcast = false;

	if (atomic_read(&bat_priv->mesh_state) != MESH_ACTIVE)
		goto dropped;
@@ -598,17 +598,28 @@ static int interface_tx(struct sk_buff *skb, struct net_device *soft_iface)
	/* Register the client MAC in the transtable */
	tt_local_add(soft_iface, ethhdr->h_source, skb->skb_iif);

	orig_node = transtable_search(bat_priv, ethhdr->h_source,
				      ethhdr->h_dest);
	do_bcast = is_multicast_ether_addr(ethhdr->h_dest);
	if (do_bcast || (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)
		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:
		default:
			break;
		}
	}

	/* ethernet packet should be broadcasted */
@@ -644,6 +655,12 @@ static int interface_tx(struct sk_buff *skb, struct net_device *soft_iface)

	/* 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;
@@ -662,8 +679,6 @@ static int interface_tx(struct sk_buff *skb, struct net_device *soft_iface)
		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;
}