[v4,5/7] batman-adv: Distributed ARP Table - add snooping functions for ARP messages

Message ID 1322173279-18338-6-git-send-email-ordex@autistici.org (mailing list archive)
State Superseded, archived
Headers

Commit Message

Antonio Quartulli Nov. 24, 2011, 10:21 p.m. UTC
  In case of an ARP message going in or out the soft_iface, it is intercepted and
a special action is performed. In particular the DHT helper functions previously
implemented are used to store all the ARP entries belonging to the network in
order to provide a fast and unicast lookup instead of the classic broadcast flooding
mechanism.
Each node stores the entries it is responsible for (following the DHT rules) in
its soft_iface ARP table. This makes it possible to reuse the kernel data
structures and functions for ARP management.

Signed-off-by: Antonio Quartulli <ordex@autistici.org>
---
 distributed-arp-table.c |  226 +++++++++++++++++++++++++++++++++++++++++++++++
 distributed-arp-table.h |   10 ++
 main.h                  |    2 +
 send.c                  |    6 ++
 soft-interface.c        |   16 +++-
 5 files changed, 259 insertions(+), 1 deletions(-)
  

Comments

Marek Lindner Nov. 25, 2011, 1:18 a.m. UTC | #1
On Friday, November 25, 2011 06:21:17 Antonio Quartulli wrote:
> @@ -30,6 +31,8 @@
>  #include "originator.h"
>  #include "bat_ogm.h"
>  
> +#include <net/arp.h>
> +
>  static void send_outstanding_bcast_packet(struct work_struct *work);

What is this include doing here ? If I am not mistaken you removed it from 
your patchset already. Is this really the latest version ? 

Regards,
Marek
  
Andrew Lunn Nov. 25, 2011, 8:45 a.m. UTC | #2
Hi Antonio

General question. In the Linux ARP decode code is:

/*
 *      Check for bad requests for 127.x.x.x and requests for multicast
 *      addresses.  If this is one such, delete it.
 */
        if (ipv4_is_loopback(tip) || ipv4_is_multicast(tip))
                goto out;

I don't see the same filtering here. What would happen if you did
receiver and cached such a bad request?

In a similar direction, how does duplicate address detection work?
i.e. i ARP my own address to see if somebody else is using it? 

Or do i just need to RTFM your GSOC documentation :-)

   Andrew
  
Antonio Quartulli Nov. 25, 2011, 11:17 a.m. UTC | #3
On Fri, Nov 25, 2011 at 09:45:56AM +0100, Andrew Lunn wrote:
> Hi Antonio
> 
> General question. In the Linux ARP decode code is:
> 
> /*
>  *      Check for bad requests for 127.x.x.x and requests for multicast
>  *      addresses.  If this is one such, delete it.
>  */
>         if (ipv4_is_loopback(tip) || ipv4_is_multicast(tip))
>                 goto out;
> 
> I don't see the same filtering here. What would happen if you did
> receiver and cached such a bad request?

atually there isnot such control over the arp message content. In case
of, let's say, a malicious ARP message of this type, it is stored like
any other one.

> 
> In a similar direction, how does duplicate address detection work?
> i.e. i ARP my own address to see if somebody else is using it? 
> 
> Or do i just need to RTFM your GSOC documentation :-)
> 

Don't think so. Actually I/we didn't think too much about this kind of
cases. Well, a duplicate entry is simply overwritten: I mean, if we
already have the entry [IPa,MACa] in the table, any other ARP reply containing
[IPa,MACb] will update the older one and MACa will be lost.

Do you expect a different behaviour? Can I do it better?

Cheers,
  
Andrew Lunn Nov. 25, 2011, 9:09 p.m. UTC | #4
On Fri, Nov 25, 2011 at 12:17:08PM +0100, Antonio Quartulli wrote:
> On Fri, Nov 25, 2011 at 09:45:56AM +0100, Andrew Lunn wrote:
> > Hi Antonio
> > 
> > General question. In the Linux ARP decode code is:
> > 
> > /*
> >  *      Check for bad requests for 127.x.x.x and requests for multicast
> >  *      addresses.  If this is one such, delete it.
> >  */
> >         if (ipv4_is_loopback(tip) || ipv4_is_multicast(tip))
> >                 goto out;
> > 
> > I don't see the same filtering here. What would happen if you did
> > receiver and cached such a bad request?
> 
> atually there isnot such control over the arp message content. In case
> of, let's say, a malicious ARP message of this type, it is stored like
> any other one.

It might make sense to drop such messages, since they are
invalid. However, nothing obvious comes to mind which would go wrong
if you did cache them, other than somebody could DOS you by sending
lots of ARP entries for multicast addresses.

> > In a similar direction, how does duplicate address detection work?
> > i.e. i ARP my own address to see if somebody else is using it? 
 
> Don't think so. Actually I/we didn't think too much about this kind of
> cases. Well, a duplicate entry is simply overwritten: I mean, if we
> already have the entry [IPa,MACa] in the table, any other ARP reply containing
> [IPa,MACb] will update the older one and MACa will be lost.

The basic idea with duplicate address detection is to send out an ARP
request for your own address. If you get an answer, you know somebody
is using the address. I think Windoz then shuts the interface down, or
at least gives a warning. So in the case of duplicate address
detection, you want to fallback to broadcasting the ARP request and
see if anybody answers. You can detect if a node is performing
aduplicate detection, if the ARP requests source MAC address is the
same as the answer in the cache. If so, fall back to broadcasting
rather than answering from the cache.

Looking at RFC 3927 might also be interesting, since it uses ARP
messages in a different way.

Also, i know some dhcp servers try to ping an IP address before giving
it out, just to be sure it is not in use. Answering the ARP request
from what could be an out of date cache entry doesn't i think causes a
problem, so long as the ping that follows it does not get
answered. But maybe some DHCP servers just perform an ARP request?

Some things to think about...

	  Andrew
  
Antonio Quartulli Nov. 26, 2011, 9:09 a.m. UTC | #5
On Fri, Nov 25, 2011 at 10:09:11 +0100, Andrew Lunn wrote:
> It might make sense to drop such messages, since they are
> invalid. However, nothing obvious comes to mind which would go wrong
> if you did cache them, other than somebody could DOS you by sending
> lots of ARP entries for multicast addresses.

Yes, I was thinking the same. A DOS is the major threat we could face without
this validity check. However, since we use the kernel function to add such
entries into the table it might be that the kernel already does this checks for
us. I'll look towards it

> 
> > > In a similar direction, how does duplicate address detection work?
> > > i.e. i ARP my own address to see if somebody else is using it? 
>  
> > Don't think so. Actually I/we didn't think too much about this kind of
> > cases. Well, a duplicate entry is simply overwritten: I mean, if we
> > already have the entry [IPa,MACa] in the table, any other ARP reply containing
> > [IPa,MACb] will update the older one and MACa will be lost.
> 
> The basic idea with duplicate address detection is to send out an ARP
> request for your own address. If you get an answer, you know somebody
> is using the address. I think Windoz then shuts the interface down, or
> at least gives a warning. So in the case of duplicate address
> detection, you want to fallback to broadcasting the ARP request and
> see if anybody answers. You can detect if a node is performing
> aduplicate detection, if the ARP requests source MAC address is the
> same as the answer in the cache. If so, fall back to broadcasting
> rather than answering from the cache.
> 
> Looking at RFC 3927 might also be interesting, since it uses ARP
> messages in a different way.
> 
> Also, i know some dhcp servers try to ping an IP address before giving
> it out, just to be sure it is not in use. Answering the ARP request
> from what could be an out of date cache entry doesn't i think causes a
> problem, so long as the ping that follows it does not get
> answered. But maybe some DHCP servers just perform an ARP request?
> 

Mh...but I think this behaviour is somehow left untouched. The mechanisms you
are describing are higher-level matters. Since we only limit to fill tables and
get answer, all the other mechanisms/procedures which use the tables should
still continue to work as they are. If a windows client will issue an ARP req to
see is someone else is using the same IP, DAT will simply answer as a normal
table would do. Therefore I think that this kind of worries are not matter of
DAT.

I hope I clearly explained my thought.

Then we could always add more feature in order to "facilitate" such mechanisms,
but up to now I think that DAT simply provides the same behaviour a normal table
would do.

Thank you for your comments!
Cheers,
  

Patch

diff --git a/distributed-arp-table.c b/distributed-arp-table.c
index 6cb60b0..a1deb79 100644
--- a/distributed-arp-table.c
+++ b/distributed-arp-table.c
@@ -21,6 +21,8 @@ 
 
 #include <linux/if_ether.h>
 #include <linux/if_arp.h>
+/* needed to use arp_tbl */
+#include <net/arp.h>
 
 #include "main.h"
 #include "distributed-arp-table.h"
@@ -197,6 +199,31 @@  out:
 	return ret;
 }
 
+/* Update the neighbour entry corresponding to the IP passed as parameter with
+ * the hw address hw. If the neighbour entry doesn't exists, then it will be
+ * created */
+static void arp_neigh_update(struct bat_priv *bat_priv, uint32_t ip,
+			     uint8_t *hw)
+{
+	struct neighbour *n = NULL;
+	struct hard_iface *primary_if = primary_if_get_selected(bat_priv);
+	if (!primary_if)
+		goto out;
+
+	n = __neigh_lookup(&arp_tbl, &ip, primary_if->soft_iface, 1);
+	if (!n)
+		goto out;
+
+	bat_dbg(DBG_ARP, bat_priv, "Updating neighbour: %pI4 - %pM\n", &ip, hw);
+
+	neigh_update(n, hw, NUD_CONNECTED, NEIGH_UPDATE_F_OVERRIDE);
+out:
+	if (n && !IS_ERR(n))
+		neigh_release(n);
+	if (primary_if)
+		hardif_free_ref(primary_if);
+}
+
 /* Returns arphdr->ar_op if the skb contains a valid ARP packet, otherwise
  * returns 0 */
 static uint16_t arp_get_type(struct bat_priv *bat_priv, struct sk_buff *skb)
@@ -239,3 +266,202 @@  static uint16_t arp_get_type(struct bat_priv *bat_priv, struct sk_buff *skb)
 out:
 	return type;
 }
+
+/* return true if the message has been sent to the dht candidates, false
+ * otherwise. In case of true the message has to be enqueued to permit the
+ * fallback */
+bool arp_snoop_outgoing_request(struct bat_priv *bat_priv, struct sk_buff *skb)
+{
+	uint16_t type = 0;
+	uint32_t ip_dst, ip_src;
+	uint8_t *hw_src;
+	bool ret = false;
+	struct neighbour *n = NULL;
+	struct hard_iface *primary_if = NULL;
+	struct sk_buff *skb_new;
+
+	type = arp_get_type(bat_priv, skb);
+	/* If we get an ARP_REQUEST we have to send the unicast message to the
+	 * selected DHT candidates */
+	if (type != ARPOP_REQUEST)
+		goto out;
+
+	bat_dbg(DBG_ARP, bat_priv, "Snooped outgoing ARP request\n");
+
+	ip_src = ARP_IP_SRC(skb);
+	hw_src = ARP_HW_SRC(skb);
+	ip_dst = ARP_IP_DST(skb);
+
+	primary_if = primary_if_get_selected(bat_priv);
+	if (!primary_if)
+		goto out;
+
+	arp_neigh_update(bat_priv, ip_src, hw_src);
+
+	n = neigh_lookup(&arp_tbl, &ip_dst, primary_if->soft_iface);
+	/* check if it is a valid neigh entry */
+	if (n && (n->nud_state & NUD_CONNECTED)) {
+		skb_new = arp_create(ARPOP_REPLY, ETH_P_ARP, ip_src,
+				     primary_if->soft_iface, ip_dst, hw_src,
+				     n->ha, hw_src);
+		if (!skb_new)
+			goto out;
+
+		skb_reset_mac_header(skb_new);
+		skb_new->protocol = eth_type_trans(skb_new,
+						   primary_if->soft_iface);
+		bat_priv->stats.rx_packets++;
+		bat_priv->stats.rx_bytes += skb->len + sizeof(struct ethhdr);
+		primary_if->soft_iface->last_rx = jiffies;
+
+		netif_rx(skb_new);
+		bat_dbg(DBG_ARP, bat_priv, "ARP request replied locally\n");
+	} else
+		/* Send the request on the DHT */
+		ret = dht_send_data(bat_priv, skb, ip_dst);
+out:
+	if (n)
+		neigh_release(n);
+	if (primary_if)
+		hardif_free_ref(primary_if);
+	return ret;
+}
+
+/* This function is meant to be invoked for an ARP request which is coming into
+ * the bat0 interfaces from the mesh network. It will check for the needed data
+ * into the local table. If found, an ARP reply is sent immediately, otherwise
+ * the caller has to deliver the ARP request to the upper layer */
+bool arp_snoop_incoming_request(struct bat_priv *bat_priv, struct sk_buff *skb)
+{
+	uint16_t type;
+	uint32_t ip_src, ip_dst;
+	uint8_t *hw_src;
+	struct hard_iface *primary_if = NULL;
+	struct sk_buff *skb_new;
+	struct neighbour *n = NULL;
+	bool ret = false;
+
+	type = arp_get_type(bat_priv, skb);
+	if (type != ARPOP_REQUEST)
+		goto out;
+
+	hw_src = ARP_HW_SRC(skb);
+	ip_src = ARP_IP_SRC(skb);
+	ip_dst = ARP_IP_DST(skb);
+
+	bat_dbg(DBG_ARP, bat_priv, "Snooped incoming ARP request\n");
+
+	primary_if = primary_if_get_selected(bat_priv);
+	if (!primary_if)
+		goto out;
+
+	arp_neigh_update(bat_priv, ip_src, hw_src);
+
+	n = neigh_lookup(&arp_tbl, &ip_dst, primary_if->soft_iface);
+	/* check if it is a valid neigh entry */
+	if (!n || !(n->nud_state & NUD_CONNECTED))
+		goto out;
+
+	skb_new = arp_create(ARPOP_REPLY, ETH_P_ARP, ip_src,
+			     primary_if->soft_iface, ip_dst, hw_src, n->ha,
+			     hw_src);
+
+	if (!skb_new)
+		goto out;
+
+	unicast_send_skb(skb_new, bat_priv);
+
+	ret = true;
+out:
+	if (n)
+		neigh_release(n);
+	if (primary_if)
+		hardif_free_ref(primary_if);
+	if (ret)
+		kfree_skb(skb);
+	return ret;
+}
+
+/* This function is meant to be invoked on an ARP reply packet going into the
+ * soft interface. The related neighbour entry has to be updated and the DHT has
+ * to be populated as well */
+bool arp_snoop_outgoing_reply(struct bat_priv *bat_priv, struct sk_buff *skb)
+{
+	uint16_t type;
+	uint32_t ip_src, ip_dst;
+	uint8_t *hw_src, *hw_dst;
+	bool ret = false;
+
+	type = arp_get_type(bat_priv, skb);
+	if (type != ARPOP_REPLY)
+		goto out;
+
+	bat_dbg(DBG_ARP, bat_priv, "Snooped outgoing ARP reply\n");
+
+	hw_src = ARP_HW_SRC(skb);
+	ip_src = ARP_IP_SRC(skb);
+	hw_dst = ARP_HW_DST(skb);
+	ip_dst = ARP_IP_DST(skb);
+
+	arp_neigh_update(bat_priv, ip_src, hw_src);
+	arp_neigh_update(bat_priv, ip_dst, hw_dst);
+
+	/* Send the ARP reply to the candidates for both the IP addresses we
+	 * fetched from the ARP reply */
+	dht_send_data(bat_priv, skb, ip_src);
+	dht_send_data(bat_priv, skb, ip_dst);
+	ret = true;
+out:
+	return ret;
+}
+
+/* This function has to be invoked on an ARP reply coming into the soft
+ * interface from the mesh network. The local table has to be updated */
+bool arp_snoop_incoming_reply(struct bat_priv *bat_priv, struct sk_buff *skb)
+{
+	uint16_t type;
+	uint32_t ip_src, ip_dst;
+	uint8_t *hw_src, *hw_dst;
+	bool ret = false;
+
+	type = arp_get_type(bat_priv, skb);
+	if (type != ARPOP_REPLY)
+		goto out;
+
+	bat_dbg(DBG_ARP, bat_priv, "Snooped incoming ARP reply\n");
+
+	hw_src = ARP_HW_SRC(skb);
+	ip_src = ARP_IP_SRC(skb);
+	hw_dst = ARP_HW_DST(skb);
+	ip_dst = ARP_IP_DST(skb);
+
+	/* Update our internal cache with both the IP addresses we fetched from
+	 * the ARP reply */
+	arp_neigh_update(bat_priv, ip_src, hw_src);
+	arp_neigh_update(bat_priv, ip_dst, hw_dst);
+
+	ret = true;
+out:
+	return ret;
+}
+
+bool arp_drop_broadcast_packet(struct bat_priv *bat_priv,
+			       struct forw_packet *forw_packet)
+{
+	struct neighbour *n;
+
+	/* If this packet is an ARP_REQUEST and we already have the information
+	 * that it is going to ask, we can drop the packet */
+	if (!forw_packet->num_packets &&
+			(arp_get_type(bat_priv, forw_packet->skb) ==
+							ARPOP_REQUEST)) {
+		n = neigh_lookup(&arp_tbl, &ARP_IP_DST(forw_packet->skb),
+				 forw_packet->if_incoming->soft_iface);
+		/* check if we already know this neigh */
+		if (n && (n->nud_state & NUD_CONNECTED))
+			return true;
+
+		bat_dbg(DBG_ARP, bat_priv, "ARP request: fallback\n");
+	}
+	return false;
+}
diff --git a/distributed-arp-table.h b/distributed-arp-table.h
index 3e0f5c6..3747aad 100644
--- a/distributed-arp-table.h
+++ b/distributed-arp-table.h
@@ -27,6 +27,7 @@ 
 #include <linux/if_arp.h>
 
 struct bat_priv;
+struct forw_packet;
 
 /*
  * dat_addr_t is the type used for all DHT indexes. If it is changed,
@@ -43,6 +44,15 @@  struct bat_priv;
 #define ARP_HW_DST(skb) (ARP_HW_SRC(skb) + ETH_ALEN + 4)
 #define ARP_IP_DST(skb) (*(uint32_t *)(ARP_HW_SRC(skb) + ETH_ALEN * 2 + 4))
 
+bool arp_snoop_outgoing_request(struct bat_priv *bat_priv,
+				struct sk_buff *skb);
+bool arp_snoop_incoming_request(struct bat_priv *bat_priv,
+				struct sk_buff *skb);
+bool arp_snoop_outgoing_reply(struct bat_priv *bat_priv, struct sk_buff *skb);
+bool arp_snoop_incoming_reply(struct bat_priv *bat_priv, struct sk_buff *skb);
+bool arp_drop_broadcast_packet(struct bat_priv *bat_priv,
+			       struct forw_packet *forw_packet);
+
 /* hash function to choose an entry in a hash table of given size */
 /* hash algorithm from http://en.wikipedia.org/wiki/Hash_table */
 static inline uint32_t hash_ipv4(const void *data, uint32_t size)
diff --git a/main.h b/main.h
index e129b01..a915db6 100644
--- a/main.h
+++ b/main.h
@@ -64,6 +64,8 @@ 
 
 #define NULL_IFINDEX 0 /* dummy ifindex used to avoid iface checks */
 
+/* msecs after which an ARP_REQUEST is sent in broadcast as fallback */
+#define ARP_REQ_DELAY 250
 /* numbers of originator to contact for any STORE/GET DHT operation */
 #define DHT_CANDIDATES_NUM 3
 
diff --git a/send.c b/send.c
index b00a0f5..d625998 100644
--- a/send.c
+++ b/send.c
@@ -20,6 +20,7 @@ 
  */
 
 #include "main.h"
+#include "distributed-arp-table.h"
 #include "send.h"
 #include "routing.h"
 #include "translation-table.h"
@@ -30,6 +31,8 @@ 
 #include "originator.h"
 #include "bat_ogm.h"
 
+#include <net/arp.h>
+
 static void send_outstanding_bcast_packet(struct work_struct *work);
 
 /* send out an already prepared packet to the given address via the
@@ -275,6 +278,9 @@  static void send_outstanding_bcast_packet(struct work_struct *work)
 	if (atomic_read(&bat_priv->mesh_state) == MESH_DEACTIVATING)
 		goto out;
 
+	if (arp_drop_broadcast_packet(bat_priv, forw_packet))
+		goto out;
+
 	/* rebroadcast packet */
 	rcu_read_lock();
 	list_for_each_entry_rcu(hard_iface, &hardif_list, list) {
diff --git a/soft-interface.c b/soft-interface.c
index 962ee8d..21991e1 100644
--- a/soft-interface.c
+++ b/soft-interface.c
@@ -22,6 +22,7 @@ 
 #include "main.h"
 #include "soft-interface.h"
 #include "hard-interface.h"
+#include "distributed-arp-table.h"
 #include "routing.h"
 #include "send.h"
 #include "bat_debugfs.h"
@@ -567,6 +568,7 @@  static int interface_tx(struct sk_buff *skb, struct net_device *soft_iface)
 	int data_len = skb->len, ret;
 	short vid = -1;
 	bool do_bcast = false;
+	unsigned long brd_delay = 1;
 
 	if (atomic_read(&bat_priv->mesh_state) != MESH_ACTIVE)
 		goto dropped;
@@ -587,6 +589,8 @@  static int interface_tx(struct sk_buff *skb, struct net_device *soft_iface)
 		goto end;
 	}
 
+	skb_reset_mac_header(skb);
+
 	/**
 	 * if we have a another chosen mesh exit node in range
 	 * it will transport the packets to the mesh
@@ -628,6 +632,9 @@  static int interface_tx(struct sk_buff *skb, struct net_device *soft_iface)
 		if (!primary_if)
 			goto dropped;
 
+		if (arp_snoop_outgoing_request(bat_priv, skb))
+			brd_delay = msecs_to_jiffies(ARP_REQ_DELAY);
+
 		if (my_skb_head_push(skb, sizeof(*bcast_packet)) < 0)
 			goto dropped;
 
@@ -647,7 +654,7 @@  static int interface_tx(struct sk_buff *skb, struct net_device *soft_iface)
 		bcast_packet->seqno =
 			htonl(atomic_inc_return(&bat_priv->bcast_seqno));
 
-		add_bcast_packet_to_list(bat_priv, skb, 1);
+		add_bcast_packet_to_list(bat_priv, skb, brd_delay);
 
 		/* a copy is stored in the bcast list, therefore removing
 		 * the original skb. */
@@ -661,6 +668,8 @@  static int interface_tx(struct sk_buff *skb, struct net_device *soft_iface)
 				goto dropped;
 		}
 
+		arp_snoop_outgoing_reply(bat_priv, skb);
+
 		ret = unicast_send_skb(skb, bat_priv);
 		if (ret != 0)
 			goto dropped_freed;
@@ -716,6 +725,11 @@  void interface_rx(struct net_device *soft_iface,
 		goto dropped;
 	}
 
+	if (arp_snoop_incoming_request(bat_priv, skb))
+		goto out;
+
+	arp_snoop_incoming_reply(bat_priv, skb);
+
 	/**
 	 * if we have a another chosen mesh exit node in range
 	 * it will transport the packets to the non-mesh network