This is my first attempt at patch submission, so please bear with me.  Mostly 
wanted to get this code out to the community for constructive criticism.

This patch adds set-field operations for nd_target, nd_sll, and nd_tll fields 
using nicra extensions and OpenFlow 1.2 protocol.  Unit tests are included.

Patch has been tested vs kernel version 3.10.0 (CentOS 7).  I plan to test it 
against 2.6.32 (CentOS 6.6) when I get access to a test platform (soon).  All 
ND set-field functions pass functional testing via fast path (kernel) 
processing -- checksums are correct as well.

I haven't yet figured out how to force slow path (userspace) processing, so I 
can't yet test packet_set_nd() in lib/packets.c -- any pointers on how to 
select slow path for ND fields only (not simply forcing slow for all fields) 
would be appreciated.

Signed-off-by: Randall A Sharo <randall.sharo at navy.mil>

---
--- datapath/actions.c.orig 2014-11-10 12:57:33.000000000 -0500
+++ datapath/actions.c 2014-11-14 14:29:00.000000000 -0500
@@ -38,6 +38,24 @@
 #include "vlan.h"
 #include "vport.h"

+static void inet_proto_csum_replace6(__sum16 *sum, struct sk_buff *skb,
+    const u8 *from, const u8 *to, int pseudohdr) {
+ u8 diff[] = {
+   ~from[0], ~from[1], ~from[2], ~from[3], ~from[4], ~from[5],
+   to[0], to[1], to[2], to[3], to[4], to[5]
+ };
+
+ if (skb->ip_summed != CHECKSUM_PARTIAL) {
+  *sum = csum_fold(csum_partial(diff, sizeof(diff),
+     ~csum_unfold(*sum)));
+  if (skb->ip_summed == CHECKSUM_COMPLETE && pseudohdr)
+   skb->csum = ~csum_partial(diff, sizeof(diff),
+        ~skb->csum);
+ } else if (pseudohdr)
+  *sum = ~csum_fold(csum_partial(diff, sizeof(diff),
+      csum_unfold(*sum)));
+}
+
 static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
          const struct nlattr *attr, int len);

@@ -295,6 +313,83 @@ static int set_ipv6(struct sk_buff *skb,
  return 0;
 }

+static int set_nd(struct sk_buff *skb, const struct ovs_key_nd *nd_key)
+{
+ struct nd_msg *nd;
+ int err;
+ const u8 *option_field;
+ u8 option_type;
+ unsigned int offset;
+ int bytes_remain = skb->len - skb_transport_offset(skb);
+
+ /* None of the other set_xxx() calls appear to check for
+  * undersized packet buffers.  Is this appropriate? */
+ if (unlikely(bytes_remain < sizeof(*nd))) {
+  OVS_NLERR("Truncated packet sent to set_nd()\n");
+  return -EINVAL;
+ }
+
+ /* Make the whole packet linear and writable
+  *  since sll/tll fields are at the end */
+ err = skb_linearize_cow(skb);
+
+ if (unlikely(err)) {
+  OVS_NLERR("skb_linearize_cow() failed with code 0x%x in set_nd()\n", err);
+  return err;
+ }
+
+ nd = (struct nd_msg *)skb_transport_header(skb);
+
+ if (nd->icmph.icmp6_type == NDISC_NEIGHBOUR_SOLICITATION) {
+  option_type = ND_OPT_SOURCE_LL_ADDR;
+  option_field = nd_key->nd_sll;
+ } else if (likely(nd->icmph.icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT)) {
+  option_type = ND_OPT_TARGET_LL_ADDR;
+  option_field = nd_key->nd_tll;
+ } else {
+  OVS_NLERR("ND set action applied to non-ND message (icmpv6_type = %d)\n",
+     nd->icmph.icmp6_type);
+  return -EINVAL;
+ }
+
+ if (memcmp(nd_key->nd_target, nd->target.s6_addr32,
+     sizeof(nd_key->nd_target)))
+  set_ipv6_addr(skb, NEXTHDR_ICMP, nd->target.s6_addr32,
+         nd_key->nd_target, true);
+
+ offset = 0;
+ bytes_remain -= sizeof (*nd);
+ while (bytes_remain >= 8) {
+  struct nd_opt_hdr *nd_opt =
+   (struct nd_opt_hdr *)&nd->opt[offset];
+  int opt_len_bytes = nd_opt->nd_opt_len * 8 - sizeof(*nd_opt);
+
+  offset += sizeof(*nd_opt);
+
+  if (likely(nd_opt->nd_opt_type == option_type
+      && opt_len_bytes == ETH_ALEN)) {
+   if (memcmp(option_field,
+       &nd->opt[offset],
+       ETH_ALEN)) {
+    inet_proto_csum_replace6(&nd->icmph.icmp6_cksum,
+        skb,
+        &nd->opt[offset],
+        option_field, 1);
+    ether_addr_copy(&nd->opt[offset],
+      option_field);
+   }
+
+   /* Only one SLL or TLL field allowed per message */
+   return 0;
+  }
+
+  offset += opt_len_bytes;
+  bytes_remain -= opt_len_bytes;
+ }
+
+ return 0;
+}
+
 /* Must follow make_writable() since that can move the skb data. */
 static void set_tp_port(struct sk_buff *skb, __be16 *port,
     __be16 new_port, __sum16 *check)
@@ -549,6 +644,10 @@ static int execute_set_action(struct sk_
  case OVS_KEY_ATTR_SCTP:
   err = set_sctp(skb, nla_data(nested_attr));
   break;
+
+ case OVS_KEY_ATTR_ND:
+  err = set_nd(skb, nla_data(nested_attr));
+  break;
  }

  return err;
--- datapath/flow_netlink.c.orig 2014-11-10 12:57:33.000000000 -0500
+++ datapath/flow_netlink.c 2014-11-13 16:36:31.000000000 -0500
@@ -1373,6 +1373,17 @@ static int validate_set(const struct nla

   return validate_tp_port(flow_key);

+ case OVS_KEY_ATTR_ND:
+  if (flow_key->eth.type != htons(ETH_P_IPV6))
+   return -EINVAL;
+
+  if (flow_key->ip.proto != IPPROTO_ICMPV6)
+   return -EINVAL;
+
+  /* I'd like to check for icmp6_type == 135 or 136 here
+   * but it isn't in sw_flow_key */
+  break;
+
  default:
   return -EINVAL;
  }
--- lib/meta-flow.c.orig 2014-11-10 12:57:34.000000000 -0500
+++ lib/meta-flow.c 2014-11-10 12:57:34.000000000 -0500
@@ -757,7 +757,7 @@ const struct mf_field mf_fields[MFF_N_ID
         MFM_FULLY,
         MFS_IPV6,
         MFP_ND,
-        false,
+        true,
         NXM_NX_ND_TARGET, "NXM_NX_ND_TARGET",
         OXM_OF_IPV6_ND_TARGET, "OXM_OF_IPV6_ND_TARGET", OFP12_VERSION,
         OFPUTIL_P_NXM_OXM_ANY,
@@ -769,7 +769,7 @@ const struct mf_field mf_fields[MFF_N_ID
         MFM_FULLY,
         MFS_ETHERNET,
         MFP_ND_SOLICIT,
-        false,
+        true,
         NXM_NX_ND_SLL, "NXM_NX_ND_SLL",
         OXM_OF_IPV6_ND_SLL, "OXM_OF_IPV6_ND_SLL", OFP12_VERSION,
         OFPUTIL_P_NXM_OXM_ANY,
@@ -781,7 +781,7 @@ const struct mf_field mf_fields[MFF_N_ID
         MFM_FULLY,
         MFS_ETHERNET,
         MFP_ND_ADVERT,
-        false,
+        true,
         NXM_NX_ND_TLL, "NXM_NX_ND_TLL",
         OXM_OF_IPV6_ND_TLL, "OXM_OF_IPV6_ND_TLL", OFP12_VERSION,
         OFPUTIL_P_NXM_OXM_ANY,
--- lib/odp-execute.c.orig 2014-11-10 12:57:34.000000000 -0500
+++ lib/odp-execute.c 2014-11-14 14:04:49.000000000 -0500
@@ -73,6 +73,7 @@ odp_execute_set_action(struct ofpbuf *pa
     const struct ovs_key_tcp *tcp_key;
     const struct ovs_key_udp *udp_key;
     const struct ovs_key_sctp *sctp_key;
+    const struct ovs_key_nd *nd_key;

     switch (type) {
     case OVS_KEY_ATTR_PRIORITY:
@@ -136,6 +137,12 @@ odp_execute_set_action(struct ofpbuf *pa
         md->recirc_id = nl_attr_get_u32(a);
         break;

+    case OVS_KEY_ATTR_ND:
+        nd_key = nl_attr_get_unspec(a, sizeof(struct ovs_key_nd));
+        packet_set_nd(packet, nd_key->nd_target, nd_key->nd_sll,
+                      nd_key->nd_tll);
+        break;
+
     case OVS_KEY_ATTR_UNSPEC:
     case OVS_KEY_ATTR_ENCAP:
     case OVS_KEY_ATTR_ETHERTYPE:
@@ -143,7 +150,6 @@ odp_execute_set_action(struct ofpbuf *pa
     case OVS_KEY_ATTR_VLAN:
     case OVS_KEY_ATTR_ICMP:
     case OVS_KEY_ATTR_ICMPV6:
-    case OVS_KEY_ATTR_ND:
     case OVS_KEY_ATTR_TCP_FLAGS:
     case __OVS_KEY_ATTR_MAX:
     default:
--- lib/odp-util.c.orig 2014-11-10 15:38:32.000000000 -0500
+++ lib/odp-util.c 2014-11-14 14:03:57.000000000 -0500
@@ -3823,6 +3823,36 @@ commit_set_port_action(const struct flow
 }

 static void
+commit_set_nd_action(const struct flow *flow, struct flow *base,
+                     struct ofpbuf *odp_actions,
+                     struct flow_wildcards *wc)
+{
+    struct ovs_key_nd nd_key;
+
+    /* nd_sll and nd_tll are stored in arp_sha and arp_tha, respectively */
+    if (ipv6_addr_equals(&base->nd_target, &flow->nd_target)
+      && eth_addr_equals(base->arp_sha, flow->arp_sha)
+      && eth_addr_equals(base->arp_tha, flow->arp_tha)) {
+     return;
+    }
+
+    memset(&wc->masks.nd_target, 0xff, sizeof wc->masks.nd_target);
+    memset(&wc->masks.arp_sha,   0xff, sizeof wc->masks.arp_sha);
+    memset(&wc->masks.arp_tha,   0xff, sizeof wc->masks.arp_tha);
+
+    base->nd_target = flow->nd_target;
+    memcpy(base->arp_sha, flow->arp_sha, ETH_ADDR_LEN);
+    memcpy(base->arp_tha, flow->arp_tha, ETH_ADDR_LEN);
+
+    memcpy(&nd_key.nd_target, &base->nd_target, sizeof(nd_key.nd_target));
+    memcpy(nd_key.nd_sll, flow->arp_sha, ETH_ADDR_LEN);
+    memcpy(nd_key.nd_tll, flow->arp_tha, ETH_ADDR_LEN);
+
+    commit_set_action(odp_actions, OVS_KEY_ATTR_ND,
+                &nd_key, sizeof(nd_key));
+}
+
+static void
 commit_set_priority_action(const struct flow *flow, struct flow *base,
                            struct ofpbuf *odp_actions,
                            struct flow_wildcards *wc)
@@ -3871,6 +3901,7 @@ commit_odp_actions(const struct flow *fl
     commit_set_ether_addr_action(flow, base, odp_actions, wc);
     slow = commit_set_nw_action(flow, base, odp_actions, wc);
     commit_set_port_action(flow, base, odp_actions, wc);
+    commit_set_nd_action(flow, base, odp_actions, wc);
     commit_mpls_action(flow, base, odp_actions, wc);
     commit_vlan_action(flow->vlan_tci, base, odp_actions, wc);
     commit_set_priority_action(flow, base, odp_actions, wc);
--- lib/packets.c.orig 2014-11-10 12:57:34.000000000 -0500
+++ lib/packets.c 2014-11-14 14:01:46.000000000 -0500
@@ -875,6 +875,67 @@ packet_set_sctp_port(struct ofpbuf *pack
     put_16aligned_be32(&sh->sctp_csum, old_csum ^ old_correct_csum ^ new_csum);
 }

+void
+packet_set_nd(struct ofpbuf *packet, const ovs_be32 target[4],
+              const uint8_t sll[ETH_ADDR_LEN],
+              const uint8_t tll[ETH_ADDR_LEN]) {
+    uint8_t *option                = ofpbuf_l4(packet);
+    size_t bytes_remain            = ofpbuf_l4_size(packet);
+    struct nd_neighbor_solicit *ns = (struct nd_neighbor_solicit *)option;
+
+    if (bytes_remain < sizeof(*ns))  {
+        return;
+    }
+
+    if (memcmp(&ns->nd_ns_target, target, sizeof(ovs_be32[4]))) {
+        packet_set_ipv6_addr(packet, IPPROTO_ICMPV6,
+                             (ovs_16aligned_be32 *)ns->nd_ns_target.s6_addr32, 
target, true);
+    }
+
+    option       += sizeof(*ns);
+    bytes_remain -= sizeof(*ns);
+
+    while (bytes_remain >= 8) {
+        struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *) option;
+        int opt_len_bytes = nd_opt->nd_opt_len * 8 - sizeof(*nd_opt);
+        option += sizeof(*nd_opt);
+
+        if (nd_opt->nd_opt_type == ND_OPT_SOURCE_LINKADDR
+            && opt_len_bytes == ETH_ADDR_LEN) {
+            if (memcmp(option, sll, ETH_ADDR_LEN)) {
+                ovs_be16 *csum = &(ns->nd_ns_hdr.icmp6_cksum);
+                *csum = recalc_csum32(*csum,
+                                      get_unaligned_be32((ovs_be32 *)(option)),
+                                      get_unaligned_be32((const ovs_be32 
*)sll));
+                *csum = recalc_csum16(*csum,
+                                      get_unaligned_be16((ovs_be16 
*)(option+4)),
+                                      get_unaligned_be16((const ovs_be16 
*)(sll+4)));
+                memcpy(option, sll, ETH_ADDR_LEN);
+
+                /* A packet can only contain one SLL or TLL option, so we can 
break here */
+                break;
+            }
+        } else if (nd_opt->nd_opt_type == ND_OPT_TARGET_LINKADDR && 
opt_len_bytes == ETH_ADDR_LEN) {
+            if (memcmp(option, tll, ETH_ADDR_LEN)) {
+                ovs_be16 *csum = &(ns->nd_ns_hdr.icmp6_cksum);
+                *csum = recalc_csum32(*csum,
+                                      get_unaligned_be32((ovs_be32 *)option),
+                                      get_unaligned_be32((const ovs_be32 
*)tll));
+                *csum = recalc_csum16(*csum,
+                                      get_unaligned_be16((ovs_be16 
*)(option+4)),
+                                      get_unaligned_be16((const ovs_be16 
*)(tll+4)));
+                memcpy(option, tll, ETH_ADDR_LEN);
+
+                /* A packet can only contain one SLL or TLL option, so we can 
break here */
+                break;
+            }
+        }
+
+        option       += opt_len_bytes;
+        bytes_remain -= opt_len_bytes;
+    }
+}
+
 const char *
 packet_tcp_flag_to_string(uint32_t flag)
 {
--- lib/packets.h.orig 2014-11-10 12:57:34.000000000 -0500
+++ lib/packets.h 2014-11-10 12:57:34.000000000 -0500
@@ -687,6 +687,8 @@ void packet_set_ipv6(struct ofpbuf *, ui
 void packet_set_tcp_port(struct ofpbuf *, ovs_be16 src, ovs_be16 dst);
 void packet_set_udp_port(struct ofpbuf *, ovs_be16 src, ovs_be16 dst);
 void packet_set_sctp_port(struct ofpbuf *, ovs_be16 src, ovs_be16 dst);
+void packet_set_nd(struct ofpbuf *, const ovs_be32 target[4],
+     const uint8_t sll[6], const uint8_t tll[6]);

 void packet_format_tcp_flags(struct ds *, uint16_t);
 const char *packet_tcp_flag_to_string(uint32_t flag);
--- tests/ofp-print.at.orig 2014-11-10 12:57:36.000000000 -0500
+++ tests/ofp-print.at 2014-11-12 14:44:52.000000000 -0500
@@ -890,6 +890,23 @@ OFPT_FLOW_MOD (OF1.2) (xid=0x52334507):
 ])
 AT_CLEANUP

+AT_SETUP([OFPT_FLOW_MOD - OF1.2 - set-field nd_target, nd_sll])
+AT_KEYWORDS([ofp-print])
+AT_CHECK([ovs-ofctl '-vPATTERN:console:%c|%p|%m' ofp-print "\
+03 0e 00 78 00 00 00 02 00 00 00 00 00 00 00 00 \
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff \
+ff ff ff ff ff ff ff ff ff ff ff ff 00 00 00 00 \
+00 01 00 14 80 00 0a 02 86 dd 80 00 14 01 3a 80 \
+00 3a 01 87 00 00 00 00 00 04 00 30 00 00 00 00 \
+00 19 00 18 80 00 3e 10 00 00 00 00 00 00 00 00 \
+00 00 00 00 00 00 00 01 00 19 00 10 80 00 40 06 \
+aa aa aa aa aa aa 00 00
+" 2], [0], [dnl
+OFPT_FLOW_MOD (OF1.2) (xid=0x2): ADD priority=255,icmp6,icmp_type=135 
actions=set_field:::1->nd_target,set_field:aa:aa:aa:aa:aa:aa->nd_sll
+], [dnl
+])
+AT_CLEANUP
+
 dnl This triggered a buggy "instructions out of order" message earlier.
 AT_SETUP([OFPT_FLOW_MOD - OF1.3 - meter])
 AT_KEYWORDS([ofp-print])
--- tests/ofproto.at.orig 2014-11-10 14:06:24.000000000 -0500
+++ tests/ofproto.at 2014-11-12 15:15:10.000000000 -0500
@@ -609,7 +609,9 @@ AT_SETUP([ofproto - set-field flow_mod c
 OVS_VSWITCHD_START
 AT_CHECK([ovs-ofctl add-flow br0 ipv6,table=1,in_port=3,actions=drop])
 AT_CHECK([ovs-ofctl add-flow br0 
ipv6,table=1,in_port=3,actions=set_field:fe80:0123:4567:890a:a6ba:dbff:fefe:59fa-\>ipv6_src])
+AT_CHECK([ovs-ofctl add-flow br0 
icmp6,icmp_type=136,table=1,in_port=3,actions=set_field:fe80:8675:3097:890a:a6ba:dbff:f00d:59fa-\>nd_target,set_field:cc:dd:ee:ff:00:11-\>nd_tll])
 AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
+ table=1, icmp6,in_port=3,icmp_type=136 
actions=load:0xa6badbfff00d59fa->NXM_NX_ND_TARGET[[0..63]],load:0xfe8086753097890a->NXM_NX_ND_TARGET[[64..127]],load:0xccddeeff0011->NXM_NX_ND_TLL[[]]
  table=1, ipv6,in_port=3 
actions=load:0xa6badbfffefe59fa->NXM_NX_IPV6_SRC[[0..63]],load:0xfe8001234567890a->NXM_NX_IPV6_SRC[[64..127]]
 NXST_FLOW reply:
 ])
@@ -634,6 +636,19 @@ AT_CHECK([ovs-ofctl -O OpenFlow12 dump-f
 ])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
+
+AT_SETUP([ofproto - set-field flow_mod commands (OF1.2)])
+OVS_VSWITCHD_START
+AT_CHECK([ovs-ofctl -O OpenFlow12 add-flow br0 
ipv6,table=1,in_port=3,actions=drop])
+AT_CHECK([ovs-ofctl -O OpenFlow12 add-flow br0 
ipv6,table=1,in_port=3,actions=set_field:fe80:0123:4567:890a:a6ba:dbff:fefe:59fa-\>ipv6_src])
+AT_CHECK([ovs-ofctl -O OpenFlow12 add-flow br0 
icmp6,icmp_type=136,table=1,in_port=3,actions=set_field:fe80:8675:3097:890a:a6ba:dbff:f00d:59fa-\>nd_target,set_field:cc:dd:ee:ff:00:11-\>nd_tll])
+AT_CHECK([ovs-ofctl -O OpenFlow12 dump-flows br0 | ofctl_strip | sort], [0], 
[dnl
+ table=1, icmp6,in_port=3,icmp_type=136 
actions=set_field:fe80:8675:3097:890a:a6ba:dbff:f00d:59fa->nd_target,set_field:cc:dd:ee:ff:00:11->nd_tll
+ table=1, ipv6,in_port=3 
actions=set_field:fe80:123:4567:890a:a6ba:dbff:fefe:59fa->ipv6_src
+OFPST_FLOW reply (OF1.2):
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP

 AT_SETUP([ofproto - dump flows with cookie])
 OVS_VSWITCHD_START
--- tests/ofproto-dpif.at.orig 2014-11-10 14:31:51.000000000 -0500
+++ tests/ofproto-dpif.at 2014-11-12 15:28:33.000000000 -0500
@@ -249,6 +249,23 @@ Datapath actions: 10,set(ipv4(src=192.16
 OVS_VSWITCHD_STOP
 AT_CLEANUP

+AT_SETUP([ofproto-dpif - modify IPv6 Neighbor Solitication (ND)])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [10], [11], [12], [13])
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1,icmp6,icmpv6_type=135 
actions=output(10),write_actions(set_field:fe80::3->nd_target,set_field:aa:aa:aa:aa:aa:aa->nd_sll,output(12)),goto_table(1)
+table=1 icmp6 actions=write_actions(output(13)),goto_table(2)
+table=2 in_port=1,icmp6,icmpv6_type=135 
actions=set_field:fe80::4->nd_target,set_field:cc:cc:cc:cc:cc:cc->nd_sll,output(11)
+])
+AT_CHECK([ovs-ofctl -O OpenFlow12 add-flows br0 flows.txt])
+AT_CHECK([ovs-appctl ofproto/trace br0 
'in_port=1,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,icmp6,ipv6_src=fe80::1,ipv6_dst=fe80::2,nw_tos=0,nw_ttl=128,icmpv6_type=135,nd_target=fe80::2020,nd_sll=66:55:44:33:22:11'],
 [0], [stdout])
+AT_CHECK([tail -2 stdout], [0],
+  [Megaflow: 
recirc_id=0,skb_priority=0,icmp6,in_port=1,nw_frag=no,icmp_type=135,icmp_code=0x0/0xff,nd_target=fe80::2020,nd_sll=66:55:44:33:22:11,nd_tll=00:00:00:00:00:00
+Datapath actions: 
10,set(nd(target=fe80::4,sll=cc:cc:cc:cc:cc:cc)),11,set(nd(target=fe80::3,sll=aa:aa:aa:aa:aa:aa)),13
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([ofproto-dpif - clear actions])
 OVS_VSWITCHD_START
 ADD_OF_PORTS([br0], [1], [10], [11], [12])
_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to