aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--datapath/actions.c36
-rw-r--r--datapath/flow.c15
-rw-r--r--datapath/flow.h16
-rw-r--r--include/openflow/nicira-ext.h13
-rw-r--r--include/openvswitch/datapath-protocol.h3
-rw-r--r--lib/dpif-netdev.c33
-rw-r--r--ofproto/ofproto.c6
7 files changed, 105 insertions, 17 deletions
diff --git a/datapath/actions.c b/datapath/actions.c
index a6771b6c..943d7570 100644
--- a/datapath/actions.c
+++ b/datapath/actions.c
@@ -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
diff --git a/datapath/flow.c b/datapath/flow.c
index 5e362e30..c37c8e0f 100644
--- a/datapath/flow.c
+++ b/datapath/flow.c
@@ -34,21 +34,6 @@
struct kmem_cache *flow_cache;
static unsigned int hash_seed;
-struct arp_eth_header
-{
- __be16 ar_hrd; /* format of hardware address */
- __be16 ar_pro; /* format of protocol address */
- unsigned char ar_hln; /* length of hardware address */
- unsigned char ar_pln; /* length of protocol address */
- __be16 ar_op; /* ARP opcode (command) */
-
- /* Ethernet+IPv4 specific members. */
- unsigned char ar_sha[ETH_ALEN]; /* sender hardware address */
- unsigned char ar_sip[4]; /* sender IP address */
- unsigned char ar_tha[ETH_ALEN]; /* target hardware address */
- unsigned char ar_tip[4]; /* target IP address */
-} __attribute__((packed));
-
static inline bool arphdr_ok(struct sk_buff *skb)
{
int nh_ofs = skb_network_offset(skb);
diff --git a/datapath/flow.h b/datapath/flow.h
index 534c7af2..80a5b66b 100644
--- a/datapath/flow.h
+++ b/datapath/flow.h
@@ -14,6 +14,7 @@
#include <linux/types.h>
#include <linux/rcupdate.h>
#include <linux/gfp.h>
+#include <linux/if_ether.h>
#include <linux/jiffies.h>
#include <linux/time.h>
@@ -42,6 +43,21 @@ struct sw_flow {
u8 tcp_flags; /* Union of seen TCP flags. */
};
+struct arp_eth_header
+{
+ __be16 ar_hrd; /* format of hardware address */
+ __be16 ar_pro; /* format of protocol address */
+ unsigned char ar_hln; /* length of hardware address */
+ unsigned char ar_pln; /* length of protocol address */
+ __be16 ar_op; /* ARP opcode (command) */
+
+ /* Ethernet+IPv4 specific members. */
+ unsigned char ar_sha[ETH_ALEN]; /* sender hardware address */
+ unsigned char ar_sip[4]; /* sender IP address */
+ unsigned char ar_tha[ETH_ALEN]; /* target hardware address */
+ unsigned char ar_tip[4]; /* target IP address */
+} __attribute__((packed));
+
extern struct kmem_cache *flow_cache;
struct sw_flow_actions *flow_actions_alloc(size_t n_actions);
diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index a80439c5..885e01da 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -131,7 +131,18 @@ enum nx_action_subtype {
*/
NXAST_RESUBMIT,
- NXAST_SET_TUNNEL /* Set encapsulating tunnel ID. */
+ /* Set encapsulating tunnel ID. */
+ NXAST_SET_TUNNEL,
+
+ /* Stops processing further actions, if the packet being processed is an
+ * Ethernet+IPv4 ARP packet for which the source Ethernet address inside
+ * the ARP packet differs from the source Ethernet address in the Ethernet
+ * header.
+ *
+ * This is useful because OpenFlow does not provide a way to match on the
+ * Ethernet addresses inside ARP packets, so there is no other way to drop
+ * spoofed ARPs other than sending every packet up to the controller. */
+ NXAST_DROP_SPOOFED_ARP
};
/* Action structure for NXAST_RESUBMIT. */
diff --git a/include/openvswitch/datapath-protocol.h b/include/openvswitch/datapath-protocol.h
index c3ec4dcf..839c4846 100644
--- a/include/openvswitch/datapath-protocol.h
+++ b/include/openvswitch/datapath-protocol.h
@@ -281,7 +281,8 @@ struct odp_flowvec {
#define ODPAT_SET_TUNNEL 13 /* Set the encapsulating tunnel ID. */
#define ODPAT_SET_PRIORITY 14 /* Set skb->priority. */
#define ODPAT_POP_PRIORITY 15 /* Restore original skb->priority. */
-#define ODPAT_N_ACTIONS 16
+#define ODPAT_DROP_SPOOFED_ARP 16 /* Drop ARPs with spoofed source MAC. */
+#define ODPAT_N_ACTIONS 17
struct odp_action_output {
uint16_t type; /* ODPAT_OUTPUT. */
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index c17b5257..6c813299 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -1269,6 +1269,34 @@ dp_netdev_output_control(struct dp_netdev *dp, const struct ofpbuf *packet,
return 0;
}
+/* Returns true if 'packet' 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
+dp_netdev_is_spoofed_arp(struct ofpbuf *packet, const struct odp_flow_key *key)
+{
+ struct arp_eth_header *arp;
+ struct eth_header *eth;
+ ptrdiff_t l3_size;
+
+ if (key->dl_type != htons(ETH_TYPE_ARP)) {
+ return false;
+ }
+
+ l3_size = (char *) ofpbuf_end(packet) - (char *) packet->l3;
+ if (l3_size < sizeof(struct arp_eth_header)) {
+ return true;
+ }
+
+ eth = packet->l2;
+ arp = packet->l3;
+ return (arp->ar_hrd != htons(ARP_HRD_ETHERNET)
+ || arp->ar_pro != htons(ARP_PRO_IP)
+ || arp->ar_hln != ETH_HEADER_LEN
+ || arp->ar_pln != 4
+ || !eth_addr_equals(arp->ar_sha, eth->eth_src));
+}
+
static int
dp_netdev_execute_actions(struct dp_netdev *dp,
struct ofpbuf *packet, const flow_t *key,
@@ -1329,6 +1357,11 @@ dp_netdev_execute_actions(struct dp_netdev *dp,
case ODPAT_SET_TP_DST:
dp_netdev_set_tp_port(packet, key, &a->tp_port);
break;
+
+ case ODPAT_DROP_SPOOFED_ARP:
+ if (dp_netdev_is_spoofed_arp(packet, key)) {
+ return 0;
+ }
}
}
return 0;
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 91ff0238..2e1530a3 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -2631,6 +2631,12 @@ xlate_nicira_action(struct action_xlate_ctx *ctx,
ctx->flow.tun_id = oa->tunnel.tun_id = nast->tun_id;
break;
+ case NXAST_DROP_SPOOFED_ARP:
+ if (ctx->flow.dl_type == htons(ETH_TYPE_ARP)) {
+ odp_actions_add(ctx->out, ODPAT_DROP_SPOOFED_ARP);
+ }
+ break;
+
/* If you add a new action here that modifies flow data, don't forget to
* update the flow key in ctx->flow at the same time. */