Add Nicira extension to OpenFlow for dropping spoofed ARP packets.
[sliver-openvswitch.git] / datapath / actions.c
index a6771b6..943d757 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/tcp.h>
 #include <linux/udp.h>
 #include <linux/in6.h>
+#include <linux/if_arp.h>
 #include <linux/if_vlan.h>
 #include <net/inet_ecn.h>
 #include <net/ip.h>
@@ -318,6 +319,35 @@ static struct sk_buff *set_tp_port(struct sk_buff *skb,
        return skb;
 }
 
+/**
+ * is_spoofed_arp - check for invalid ARP packet
+ *
+ * @skb: skbuff containing an Ethernet packet, with network header pointing
+ * just past the Ethernet and optional 802.1Q header.
+ * @key: flow key extracted from @skb by flow_extract()
+ *
+ * Returns true if @skb is an invalid Ethernet+IPv4 ARP packet: one with screwy
+ * or truncated header fields or one whose inner and outer Ethernet address
+ * differ.
+ */
+static bool is_spoofed_arp(struct sk_buff *skb, const struct odp_flow_key *key)
+{
+       struct arp_eth_header *arp;
+
+       if (key->dl_type != htons(ETH_P_ARP))
+               return false;
+
+       if (skb_network_offset(skb) + sizeof(struct arp_eth_header) > skb->len)
+               return true;
+
+       arp = (struct arp_eth_header *)skb_network_header(skb);
+       return (arp->ar_hrd != htons(ARPHRD_ETHER) ||
+               arp->ar_pro != htons(ETH_P_IP) ||
+               arp->ar_hln != ETH_ALEN ||
+               arp->ar_pln != 4 ||
+               compare_ether_addr(arp->ar_sha, eth_hdr(skb)->h_source));
+}
+
 static void do_output(struct datapath *dp, struct sk_buff *skb, int out_port)
 {
        struct dp_port *p;
@@ -484,10 +514,16 @@ int execute_actions(struct datapath *dp, struct sk_buff *skb,
                case ODPAT_POP_PRIORITY:
                        skb->priority = priority;
                        break;
+
+               case ODPAT_DROP_SPOOFED_ARP:
+                       if (unlikely(is_spoofed_arp(skb, key)))
+                               goto exit;
+                       break;
                }
                if (!skb)
                        return -ENOMEM;
        }
+exit:
        if (prev_port != -1)
                do_output(dp, skb, prev_port);
        else