Skip to content

Commit

Permalink
nicira-ext: Support matching IPv6 Neighbor Discovery messages.
Browse files Browse the repository at this point in the history
IPv6 uses Neighbor Discovery messages in a similar manner to how IPv4
uses ARP.  This commit adds support for matching deeper into the
payloads of Neighbor Solicitation (NS) and Neighbor Advertisement (NA)
messages.  Currently, the matching fields include:

    - NS and NA Target (nd_target)
    - NS Source Link Layer Address (nd_sll)
    - NA Target Link Layer Address (nd_tll)

When defining IPv6 Neighbor Discovery rules, the Nicira Extensible Match
(NXM) extension to OVS must be used.

Signed-off-by: Justin Pettit <[email protected]>
Acked-by: Ben Pfaff <[email protected]>
  • Loading branch information
Justin Pettit committed Feb 2, 2011
1 parent d31f110 commit 685a51a
Show file tree
Hide file tree
Showing 18 changed files with 468 additions and 33 deletions.
3 changes: 2 additions & 1 deletion DESIGN
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ IPv6

Open vSwitch supports stateless handling of IPv6 packets. Flows can be
written to support matching TCP, UDP, and ICMPv6 headers within an IPv6
packet.
packet. Deeper matching of some Neighbor Discovery messages is also
supported.

IPv6 was not designed to interact well with middle-boxes. This,
combined with Open vSwitch's stateless nature, have affected the
Expand Down
116 changes: 108 additions & 8 deletions datapath/flow.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <net/inet_ecn.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include <net/ndisc.h>

static struct kmem_cache *flow_cache;
static unsigned int hash_seed __read_mostly;
Expand Down Expand Up @@ -314,6 +315,75 @@ static __be16 parse_ethertype(struct sk_buff *skb)
return llc->ethertype;
}

static int parse_icmpv6(struct sk_buff *skb, struct sw_flow_key *key,
int nh_len)
{
struct ipv6hdr *nh = ipv6_hdr(skb);
int icmp_len = ntohs(nh->payload_len) + sizeof(*nh) - nh_len;
struct icmp6hdr *icmp = icmp6_hdr(skb);

/* The ICMPv6 type and code fields use the 16-bit transport port
* fields, so we need to store them in 16-bit network byte order. */
key->tp_src = htons(icmp->icmp6_type);
key->tp_dst = htons(icmp->icmp6_code);

if (!icmp->icmp6_code
&& ((icmp->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION)
|| (icmp->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT))) {
struct nd_msg *nd;
int offset;

/* In order to process neighbor discovery options, we need the
* entire packet. */
if (icmp_len < sizeof(*nd))
goto invalid;
if (!pskb_may_pull(skb, skb_transport_offset(skb) + icmp_len))
return -ENOMEM;

nd = (struct nd_msg *)skb_transport_header(skb);
memcpy(key->nd_target, &nd->target, sizeof(key->nd_target));

icmp_len -= sizeof(*nd);
offset = 0;
while (icmp_len >= 8) {
struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)(nd->opt + offset);
int opt_len = nd_opt->nd_opt_len * 8;

if (!opt_len || (opt_len > icmp_len))
goto invalid;

/* Store the link layer address if the appropriate option is
* provided. It is considered an error if the same link
* layer option is specified twice. */
if (nd_opt->nd_opt_type == ND_OPT_SOURCE_LL_ADDR
&& opt_len == 8) {
if (!is_zero_ether_addr(key->arp_sha))
goto invalid;
memcpy(key->arp_sha,
&nd->opt[offset+sizeof(*nd_opt)], ETH_ALEN);
} else if (nd_opt->nd_opt_type == ND_OPT_TARGET_LL_ADDR
&& opt_len == 8) {
if (!is_zero_ether_addr(key->arp_tha))
goto invalid;
memcpy(key->arp_tha,
&nd->opt[offset+sizeof(*nd_opt)], ETH_ALEN);
}

icmp_len -= opt_len;
offset += opt_len;
}
}

return 0;

invalid:
memset(key->nd_target, 0, sizeof(key->nd_target));
memset(key->arp_sha, 0, sizeof(key->arp_sha));
memset(key->arp_tha, 0, sizeof(key->arp_tha));

return 0;
}

/**
* flow_extract - extracts a flow key from an Ethernet frame.
* @skb: sk_buff that contains the frame, with skb->data pointing to the
Expand Down Expand Up @@ -482,12 +552,9 @@ int flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key,
}
} else if (key->nw_proto == NEXTHDR_ICMP) {
if (icmp6hdr_ok(skb)) {
struct icmp6hdr *icmp = icmp6_hdr(skb);
/* The ICMPv6 type and code fields use the 16-bit
* transport port fields, so we need to store them
* in 16-bit network byte order. */
key->tp_src = htons(icmp->icmp6_type);
key->tp_dst = htons(icmp->icmp6_code);
int error = parse_icmpv6(skb, key, nh_len);
if (error < 0)
return error;
}
}
}
Expand Down Expand Up @@ -517,7 +584,7 @@ int flow_cmp(const struct tbl_node *node, void *key2_)
* elements and | for alternatives:
*
* [tun_id] in_port ethernet [8021q] [ethertype \
* [IPv4 [TCP|UDP|ICMP] | IPv6 [TCP|UDP|ICMPv6] | ARP]]
* [IPv4 [TCP|UDP|ICMP] | IPv6 [TCP|UDP|ICMPv6 [ND]] | ARP]]
*/
int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
{
Expand All @@ -543,6 +610,7 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
[ODP_KEY_ATTR_ICMP] = sizeof(struct odp_key_icmp),
[ODP_KEY_ATTR_ICMPV6] = sizeof(struct odp_key_icmpv6),
[ODP_KEY_ATTR_ARP] = sizeof(struct odp_key_arp),
[ODP_KEY_ATTR_ND] = sizeof(struct odp_key_nd),
};

const struct odp_key_ethernet *eth_key;
Expand All @@ -554,6 +622,7 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
const struct odp_key_icmp *icmp_key;
const struct odp_key_icmpv6 *icmpv6_key;
const struct odp_key_arp *arp_key;
const struct odp_key_nd *nd_key;

int type = nla_type(nla);

Expand Down Expand Up @@ -669,6 +738,17 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
memcpy(swkey->arp_tha, arp_key->arp_tha, ETH_ALEN);
break;

case TRANSITION(ODP_KEY_ATTR_ICMPV6, ODP_KEY_ATTR_ND):
if (swkey->tp_src != htons(NDISC_NEIGHBOUR_SOLICITATION)
&& swkey->tp_src != htons(NDISC_NEIGHBOUR_ADVERTISEMENT))
return -EINVAL;
nd_key = nla_data(nla);
memcpy(swkey->nd_target, nd_key->nd_target,
sizeof(swkey->nd_target));
memcpy(swkey->arp_sha, nd_key->nd_sll, ETH_ALEN);
memcpy(swkey->arp_tha, nd_key->nd_tll, ETH_ALEN);
break;

default:
return -EINVAL;
}
Expand Down Expand Up @@ -710,11 +790,17 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
return -EINVAL;
return 0;

case ODP_KEY_ATTR_ICMPV6:
if (swkey->tp_src == htons(NDISC_NEIGHBOUR_SOLICITATION) ||
swkey->tp_src == htons(NDISC_NEIGHBOUR_ADVERTISEMENT))
return -EINVAL;
return 0;

case ODP_KEY_ATTR_TCP:
case ODP_KEY_ATTR_UDP:
case ODP_KEY_ATTR_ICMP:
case ODP_KEY_ATTR_ICMPV6:
case ODP_KEY_ATTR_ARP:
case ODP_KEY_ATTR_ND:
return 0;
}

Expand Down Expand Up @@ -831,6 +917,20 @@ int flow_to_nlattrs(const struct sw_flow_key *swkey, struct sk_buff *skb)
icmpv6_key = nla_data(nla);
icmpv6_key->icmpv6_type = ntohs(swkey->tp_src);
icmpv6_key->icmpv6_code = ntohs(swkey->tp_dst);

if (icmpv6_key->icmpv6_type == NDISC_NEIGHBOUR_SOLICITATION
|| icmpv6_key->icmpv6_type == NDISC_NEIGHBOUR_ADVERTISEMENT) {
struct odp_key_nd *nd_key;

nla = nla_reserve(skb, ODP_KEY_ATTR_ND, sizeof(*nd_key));
if (!nla)
goto nla_put_failure;
nd_key = nla_data(nla);
memcpy(nd_key->nd_target, swkey->nd_target,
sizeof(nd_key->nd_target));
memcpy(nd_key->nd_sll, swkey->arp_sha, ETH_ALEN);
memcpy(nd_key->nd_tll, swkey->arp_tha, ETH_ALEN);
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions datapath/flow.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct sw_flow_key {
__be32 ipv6_dst[4]; /* IPv6 source address. */
};
};
__be32 nd_target[4]; /* IPv6 ND target address. */
u16 in_port; /* Input switch port. */
__be16 dl_tci; /* 0 if no VLAN, VLAN_TAG_PRESENT set otherwise. */
__be16 dl_type; /* Ethernet frame type. */
Expand All @@ -50,8 +51,8 @@ struct sw_flow_key {
u8 dl_dst[ETH_ALEN]; /* Ethernet destination address. */
u8 nw_proto; /* IP protocol or lower 8 bits of ARP opcode. */
u8 nw_tos; /* IP ToS (DSCP field, 6 bits). */
u8 arp_sha[ETH_ALEN]; /* ARP source hardware address. */
u8 arp_tha[ETH_ALEN]; /* ARP target hardware address. */
u8 arp_sha[ETH_ALEN]; /* ARP/ND source hardware address. */
u8 arp_tha[ETH_ALEN]; /* ARP/ND target hardware address. */
};

struct sw_flow {
Expand Down
40 changes: 40 additions & 0 deletions include/openflow/nicira-ext.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ OFP_ASSERT(sizeof(struct nx_action_pop_queue) == 16);
* - NXM_NX_ARP_THA
* - NXM_NX_ICMPV6_TYPE
* - NXM_NX_ICMPV6_CODE
* - NXM_NX_ND_SLL
* - NXM_NX_ND_TLL
* - NXM_NX_REG(idx) for idx in the switch's accepted range.
*
* The following nxm_header values are potentially acceptable as 'dst':
Expand Down Expand Up @@ -1078,6 +1080,44 @@ enum nx_mp_algorithm {
#define NXM_NX_ICMPV6_TYPE NXM_HEADER (0x0001, 21, 1)
#define NXM_NX_ICMPV6_CODE NXM_HEADER (0x0001, 22, 1)

/* The target address in an IPv6 Neighbor Discovery message.
*
* Prereqs:
* NXM_OF_ETH_TYPE must match 0x86dd exactly.
* NXM_OF_IP_PROTO must match 58 exactly.
* NXM_OF_ICMPV6_TYPE must be either 135 or 136.
*
* Format: 128-bit IPv6 address.
*
* Masking: Not maskable. */
#define NXM_NX_ND_TARGET NXM_HEADER (0x0001, 23, 16)

/* The source link-layer address option in an IPv6 Neighbor Discovery
* message.
*
* Prereqs:
* NXM_OF_ETH_TYPE must match 0x86dd exactly.
* NXM_OF_IP_PROTO must match 58 exactly.
* NXM_OF_ICMPV6_TYPE must be exactly 135.
*
* Format: 48-bit Ethernet MAC address.
*
* Masking: Not maskable. */
#define NXM_NX_ND_SLL NXM_HEADER (0x0001, 24, 6)

/* The target link-layer address option in an IPv6 Neighbor Discovery
* message.
*
* Prereqs:
* NXM_OF_ETH_TYPE must match 0x86dd exactly.
* NXM_OF_IP_PROTO must match 58 exactly.
* NXM_OF_ICMPV6_TYPE must be exactly 136.
*
* Format: 48-bit Ethernet MAC address.
*
* Masking: Not maskable. */
#define NXM_NX_ND_TLL NXM_HEADER (0x0001, 25, 6)


/* ## --------------------- ## */
/* ## Requests and replies. ## */
Expand Down
7 changes: 7 additions & 0 deletions include/openvswitch/datapath-protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ enum odp_key_type {
ODP_KEY_ATTR_ICMP, /* struct odp_key_icmp */
ODP_KEY_ATTR_ICMPV6, /* struct odp_key_icmpv6 */
ODP_KEY_ATTR_ARP, /* struct odp_key_arp */
ODP_KEY_ATTR_ND, /* struct odp_key_nd */
__ODP_KEY_ATTR_MAX
};

Expand Down Expand Up @@ -374,6 +375,12 @@ struct odp_key_arp {
uint8_t arp_tha[6];
};

struct odp_key_nd {
uint32_t nd_target[4];
uint8_t nd_sll[6];
uint8_t nd_tll[6];
};

/**
* enum odp_flow_attr - attributes for %ODP_FLOW_* commands.
* @ODP_FLOW_ATTR_KEY: Nested %ODP_KEY_ATTR_* attributes specifying the flow
Expand Down
33 changes: 29 additions & 4 deletions lib/classifier.c
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,13 @@ cls_rule_set_ipv6_dst_masked(struct cls_rule *rule, const struct in6_addr *dst,
}
}

void
cls_rule_set_nd_target(struct cls_rule *rule, const struct in6_addr target)
{
rule->wc.wildcards &= ~FWW_ND_TARGET;
rule->flow.nd_target = target;
}

/* Returns true if 'a' and 'b' have the same priority, wildcard the same
* fields, and have the same values for fixed fields, otherwise false. */
bool
Expand Down Expand Up @@ -586,7 +593,20 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s)
if (!(w & FWW_TP_DST)) {
ds_put_format(s, "icmp_code=%"PRIu16",", ntohs(f->tp_dst));
}
} else {
if (!(w & FWW_ND_TARGET)) {
ds_put_cstr(s, "nd_target=");
print_ipv6_addr(s, &f->nd_target);
ds_put_char(s, ',');
}
if (!(w & FWW_ARP_SHA)) {
ds_put_format(s, "nd_sll="ETH_ADDR_FMT",",
ETH_ADDR_ARGS(f->arp_sha));
}
if (!(w & FWW_ARP_THA)) {
ds_put_format(s, "nd_tll="ETH_ADDR_FMT",",
ETH_ADDR_ARGS(f->arp_tha));
}
} else {
if (!(w & FWW_TP_SRC)) {
ds_put_format(s, "tp_src=%"PRIu16",", ntohs(f->tp_src));
}
Expand Down Expand Up @@ -1080,7 +1100,7 @@ flow_equal_except(const struct flow *a, const struct flow *b,
const flow_wildcards_t wc = wildcards->wildcards;
int i;

BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 84 + FLOW_N_REGS * 4);
BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 100 + FLOW_N_REGS * 4);

for (i = 0; i < FLOW_N_REGS; i++) {
if ((a->regs[i] ^ b->regs[i]) & wildcards->reg_masks[i]) {
Expand Down Expand Up @@ -1113,7 +1133,9 @@ flow_equal_except(const struct flow *a, const struct flow *b,
&& ipv6_equal_except(&a->ipv6_src, &b->ipv6_src,
&wildcards->ipv6_src_mask)
&& ipv6_equal_except(&a->ipv6_dst, &b->ipv6_dst,
&wildcards->ipv6_dst_mask));
&wildcards->ipv6_dst_mask)
&& (wc & FWW_ND_TARGET
|| ipv6_addr_equals(&a->nd_target, &b->nd_target)));
}

static void
Expand All @@ -1122,7 +1144,7 @@ zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
const flow_wildcards_t wc = wildcards->wildcards;
int i;

BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 84 + 4 * FLOW_N_REGS);
BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 100 + 4 * FLOW_N_REGS);

for (i = 0; i < FLOW_N_REGS; i++) {
flow->regs[i] &= wildcards->reg_masks[i];
Expand Down Expand Up @@ -1169,4 +1191,7 @@ zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
&wildcards->ipv6_src_mask);
flow->ipv6_dst = ipv6_addr_bitand(&flow->ipv6_dst,
&wildcards->ipv6_dst_mask);
if (wc & FWW_ND_TARGET) {
memset(&flow->nd_target, 0, sizeof flow->nd_target);
}
}
1 change: 1 addition & 0 deletions lib/classifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ bool cls_rule_set_ipv6_src_masked(struct cls_rule *, const struct in6_addr *,
void cls_rule_set_ipv6_dst(struct cls_rule *, const struct in6_addr *);
bool cls_rule_set_ipv6_dst_masked(struct cls_rule *, const struct in6_addr *,
const struct in6_addr *);
void cls_rule_set_nd_target(struct cls_rule *, const struct in6_addr);

bool cls_rule_equal(const struct cls_rule *, const struct cls_rule *);

Expand Down
Loading

0 comments on commit 685a51a

Please sign in to comment.