diff options
-rw-r--r-- | datapath/actions.c | 36 | ||||
-rw-r--r-- | datapath/flow.c | 15 | ||||
-rw-r--r-- | datapath/flow.h | 16 | ||||
-rw-r--r-- | include/openflow/nicira-ext.h | 13 | ||||
-rw-r--r-- | include/openvswitch/datapath-protocol.h | 3 | ||||
-rw-r--r-- | lib/dpif-netdev.c | 33 | ||||
-rw-r--r-- | ofproto/ofproto.c | 6 |
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. */ |