diff mbox

[ovs-dev,v4,6/6] ovn-northd: Add IPv6 RS responder support

Message ID 20170323060750.6525-1-nusiddiq@redhat.com
State Superseded
Delegated to: Justin Pettit
Headers show

Commit Message

Numan Siddique March 23, 2017, 6:07 a.m. UTC
From: Zong Kai LI <zealokii@gmail.com>

This patch adds logical flows for IPv6 Router Advertisements in
reply to the IPv6 Router Solicitation requests. It uses the
actions "nd_ra", "put_nd_ra_mode", "put_nd_opt_mtu" and
"put_nd_opt_prefix". These logical flows are added in the
new ingress stage "lr_in_rs_rsp" in the logical router pipeline.

A new column "ipv6_ra_configs" is added in the Logical_Router_Port
table, which the CMS is expected to configure IPv6 RA
configurations - "address_mode" and "mtu" for adding these flows.

Co-authored-by: Numan Siddique <nusiddiq@redhat.com>
Signed-off-by: Zongkai LI <zealokii@gmail.com>
Signed-off-by: Numan Siddique <nusiddiq@redhat.com>
---
 ovn/controller/pinctrl.c |   3 +-
 ovn/northd/ovn-northd.c  |  94 ++++++++++++++++--
 ovn/ovn-nb.ovsschema     |   7 +-
 ovn/ovn-nb.xml           |  39 ++++++++
 tests/ovn.at             | 247 +++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 379 insertions(+), 11 deletions(-)
diff mbox

Patch

diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index be5dd61..8eb73ae 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -1434,7 +1434,8 @@  pinctrl_handle_nd(const struct flow *ip_flow, const struct match *md,
         struct prefix_data *prefixes = NULL;
         int num_prefixes = 0;
 
-        while (userdata->size - args_len > 0) {
+        uint32_t remaining_len = userdata->size - args_len;
+        while (userdata->size > remaining_len) {
             struct ipv6_nd_ra_opt_header *opt =
                 ofpbuf_try_pull(userdata, sizeof(*opt));
 
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 8c8f16b..3d36ee6 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -131,10 +131,11 @@  enum ovn_stage {
     PIPELINE_STAGE(ROUTER, IN,  DEFRAG,      2, "lr_in_defrag")       \
     PIPELINE_STAGE(ROUTER, IN,  UNSNAT,      3, "lr_in_unsnat")       \
     PIPELINE_STAGE(ROUTER, IN,  DNAT,        4, "lr_in_dnat")         \
-    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,  5, "lr_in_ip_routing")   \
-    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE, 6, "lr_in_arp_resolve")  \
-    PIPELINE_STAGE(ROUTER, IN,  GW_REDIRECT, 7, "lr_in_gw_redirect")  \
-    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST, 8, "lr_in_arp_request")  \
+    PIPELINE_STAGE(ROUTER, IN,  RS_RSP,      5, "lr_in_rs_rsp")       \
+    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,  6, "lr_in_ip_routing")   \
+    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE, 7, "lr_in_arp_resolve")  \
+    PIPELINE_STAGE(ROUTER, IN,  GW_REDIRECT, 8, "lr_in_gw_redirect")  \
+    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST, 9, "lr_in_arp_request")  \
                                                                       \
     /* Logical router egress stages. */                               \
     PIPELINE_STAGE(ROUTER, OUT, UNDNAT,    0, "lr_out_undnat")        \
@@ -4773,7 +4774,84 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
         sset_destroy(&all_ips);
     }
 
-    /* Logical router ingress table 5: IP Routing.
+    /* Logical router ingress table 5: RS responder, reply for router port
+     * with IPv6 networks configured. (priority 50)*/
+    HMAP_FOR_EACH (op, key_node, ports) {
+        if (!op->nbrp || op->nbrp->peer || !op->peer) {
+            continue;
+        }
+
+        if (!op->lrp_networks.n_ipv6_addrs) {
+            continue;
+        }
+
+        ds_clear(&match);
+        ds_put_format(&match, "inport == %s && ip6.dst == ff02::2 && nd_rs",
+                              op->json_key);
+        ds_clear(&actions);
+
+        const char *address_mode = smap_get(
+            &op->nbrp->ipv6_ra_configs, "address_mode");
+
+        if (!address_mode || (strcmp(address_mode, "slaac") &&
+                              strcmp(address_mode, "dhcpv6_stateful") &&
+                              strcmp(address_mode, "dhcpv6_stateless"))) {
+            continue;
+        }
+
+        const char *mtu_s = smap_get(
+            &op->nbrp->ipv6_ra_configs, "mtu");
+
+        uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0;
+
+        ds_put_format(&actions,
+                      "nd_ra{put_nd_ra_addr_mode(\"%s\"); put_nd_opt_sll(%s); ",
+                      address_mode, op->lrp_networks.ea_s);
+        if (mtu > 0) {
+            ds_put_format(&actions, "put_nd_opt_mtu(%u); ",
+                                     mtu);
+        }
+
+        bool add_rs_response_flow = false;
+
+        for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
+            if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) {
+                continue;
+            }
+
+            if (!strcmp(address_mode, "slaac")) {
+                ds_put_format(&actions, "put_nd_opt_prefix(%s/%u); ",
+                              op->lrp_networks.ipv6_addrs[i].network_s,
+                              op->lrp_networks.ipv6_addrs[i].plen);
+            }
+            add_rs_response_flow = true;
+        }
+
+        if (add_rs_response_flow) {
+            char ip6_str[INET6_ADDRSTRLEN + 1];
+            struct in6_addr lla;
+            in6_generate_lla(op->lrp_networks.ea, &lla);
+            memset(ip6_str, 0, sizeof(ip6_str));
+            ipv6_string_mapped(ip6_str, &lla);
+            ds_put_format(&actions, "eth.src = %s; ip6.src = %s; "
+                          "outport = inport; flags.loopback = 1; "
+                           "output;};", op->lrp_networks.ea_s, ip6_str);
+            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_RS_RSP, 50,
+                          ds_cstr(&match), ds_cstr(&actions));
+        }
+    }
+
+    /* Logical router ingress table 5: RS responder, by default goto next.
+     * (priority 0)*/
+    HMAP_FOR_EACH (od, key_node, datapaths) {
+        if (!od->nbr) {
+            continue;
+        }
+
+        ovn_lflow_add(lflows, od, S_ROUTER_IN_RS_RSP, 0, "1", "next;");
+    }
+
+    /* Logical router ingress table 6: IP Routing.
      *
      * A packet that arrives at this table is an IP packet that should be
      * routed to the address in 'ip[46].dst'. This table sets outport to
@@ -4815,7 +4893,7 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
 
     /* XXX destination unreachable */
 
-    /* Local router ingress table 6: ARP Resolution.
+    /* Local router ingress table 7: ARP Resolution.
      *
      * Any packet that reaches this table is an IP packet whose next-hop IP
      * address is in reg0. (ip4.dst is the final destination.) This table
@@ -5010,7 +5088,7 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                       "get_nd(outport, xxreg0); next;");
     }
 
-    /* Logical router ingress table 7: Gateway redirect.
+    /* Logical router ingress table 8: Gateway redirect.
      *
      * For traffic with outport equal to the l3dgw_port
      * on a distributed router, this table redirects a subset
@@ -5050,7 +5128,7 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
         ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;");
     }
 
-    /* Local router ingress table 8: ARP request.
+    /* Local router ingress table 9: ARP request.
      *
      * In the common case where the Ethernet destination has been resolved,
      * this table outputs the packet (priority 0).  Otherwise, it composes
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index dd0ac3d..5fb282f 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "5.5.0",
-    "cksum": "2099428463 14236",
+    "version": "5.6.0",
+    "cksum": "2423943180 14399",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -202,6 +202,9 @@ 
                 "mac": {"type": "string"},
                 "peer": {"type": {"key": "string", "min": 0, "max": 1}},
                 "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
+                "ipv6_ra_configs": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 46a25f6..fed5e00 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -1131,6 +1131,45 @@ 
       port has all ingress and egress traffic dropped.
     </column>
 
+    <group title="ipv6_ra_configs">
+      <p>
+        This column defines the IPv6 RA address mode and ND MTU Option to be
+        used by <code>ovn-northd</code> when it generates logical flows for
+        Router Solicitaion responder.
+      </p>
+
+      <column name="ipv6_ra_configs" key="address_mode">
+        The address mode to be used for IPv6 address configuration.
+        The supported values are:
+        <ul>
+          <li>
+            <code>slaac</code>: Address configuration using Router
+            Advertisement (RA) packet. The IPv6 prefixes defined in the
+            <ref table="Logical_Router_Port"/> table's <ref table="Logical_Router_Port"
+            column="networks"/> column will be included in the RA's ICMPv6
+            option - Prefix information.
+          </li>
+
+          <li>
+            <code>dhcpv6_stateful</code>: Address configuration using DHCPv6.
+          </li>
+
+          <li>
+            <code>dhcpv6_stateless</code>: Address configuration using Router
+            Advertisement (RA) packet. Other IPv6 options are provided by
+            DHCPv6.
+          </li>
+        </ul>
+      </column>
+
+      <column name="ipv6_ra_configs" key="mtu">
+        The recommended MTU for the link. Default is 0, which means no MTU
+        Option will be included in RA packet replied by ovn-controller.
+        Per RFC 2460, the mtu value is recommended no less than 1280, so
+        any mtu value less than 1280 will be considered as no MTU Option.
+      </column>
+    </group>
+
     <group title="Options">
       <p>
         Additional options for the logical router port.
diff --git a/tests/ovn.at b/tests/ovn.at
index 6ae1b7a..1d2ffcd 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -6369,6 +6369,253 @@  OVS_APP_EXIT_AND_WAIT([ovsdb-server])
 
 AT_CLEANUP
 
+
+AT_SETUP([ovn -- nd_ra ])
+AT_KEYWORDS([ovn-nd_ra])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+# In this test case we create 1 lswitch with 3 VIF ports attached,
+# and a lrouter connected to the lswitch.
+# We generate the Router solicitation packet and verify the Router Advertisement
+# reply packet from the ovn-controller.
+
+# Create hypervisor and logical switch lsw0, logical router lr0, attach lsw0
+# onto lr0, set Logical_Router_Port.ipv6_ra_configs:address_mode column to
+# 'slaac' to allow lrp0 send RA for SLAAC mode.
+ovn-nbctl ls-add lsw0
+ovn-nbctl lr-add lr0
+ovn-nbctl lrp-add lr0 lrp0 fa:16:3e:00:00:01 fdad:1234:5678::1/64
+ovn-nbctl set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode="slaac"
+ovn-nbctl \
+    -- lsp-add lsw0 lsp0 \
+    -- set Logical_Switch_Port lsp0 type=router \
+                     options:router-port=lrp0 \
+                     addresses='"fa:16:3e:00:00:01 fdad:1234:5678::1"'
+net_add n1
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+
+ovn-nbctl lsp-add lsw0 lp1
+ovn-nbctl lsp-set-addresses lp1 "fa:16:3e:00:00:02 10.0.0.12 fdad:1234:5678:0:f816:3eff:fe:2"
+ovn-nbctl lsp-set-port-security lp1 "fa:16:3e:00:00:02 10.0.0.12 fdad:1234:5678:0:f816:3eff:fe:2"
+
+ovn-nbctl lsp-add lsw0 lp2
+ovn-nbctl lsp-set-addresses lp2 "fa:16:3e:00:00:03 10.0.0.13 fdad:1234:5678:0:f816:3eff:fe:3"
+ovn-nbctl lsp-set-port-security lp2 "fa:16:3e:00:00:03 10.0.0.13 fdad:1234:5678:0:f816:3eff:fe:3"
+
+ovn-nbctl lsp-add lsw0 lp3
+ovn-nbctl lsp-set-addresses lp3 "fa:16:3e:00:00:04 10.0.0.14 fdad:1234:5678:0:f816:3eff:fe:4"
+ovn-nbctl lsp-set-port-security lp3 "fa:16:3e:00:00:04 10.0.0.14 fdad:1234:5678:0:f816:3eff:fe:4"
+
+# Add ACL rule for ICMPv6 on lsw0
+ovn-nbctl acl-add lsw0 from-lport 1002 'ip6 && icmp6'  allow-related
+ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp1" && ip6 && icmp6'  allow-related
+ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp2" && ip6 && icmp6'  allow-related
+ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp3" && ip6 && icmp6'  allow-related
+
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=lp1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=1
+
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
+    set interface hv1-vif2 external-ids:iface-id=lp2 \
+    options:tx_pcap=hv1/vif2-tx.pcap \
+    options:rxq_pcap=hv1/vif2-rx.pcap \
+    ofport-request=2
+
+ovs-vsctl -- add-port br-int hv1-vif3 -- \
+    set interface hv1-vif3 external-ids:iface-id=lp3 \
+    options:tx_pcap=hv1/vif3-tx.pcap \
+    options:rxq_pcap=hv1/vif3-rx.pcap \
+    ofport-request=3
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+# XXX This should be more systematic.
+sleep 1
+
+# Given the name of a logical port, prints the name of the hypervisor
+# on which it is located.
+trim_zeros() {
+    sed 's/\(00\)\{1,\}$//'
+}
+for i in 1 2 3; do
+    : > $i.expected
+done
+
+reset_pcap_file() {
+    local iface=$1
+    local pcap_file=$2
+    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
+options:rxq_pcap=dummy-rx.pcap
+    rm -f ${pcap_file}*.pcap
+    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
+options:rxq_pcap=${pcap_file}-rx.pcap
+}
+
+# This shell function sends a Router Solicitation packet.
+# test_ipv6_ra INPORT SRC_MAC SRC_LLA ADDR_MODE MTU RA_PREFIX_OPT
+test_ipv6_ra() {
+    local inport=$1 src_mac=$2 src_lla=$3 addr_mode=$4 mtu=$5 prefix_opt=$6
+    local request=333300000002${src_mac}86dd6000000000103aff${src_lla}ff02000000000000000000000000000285000efc000000000101${src_mac}
+
+    local len=24
+    local mtu_opt=""
+    if test $mtu != 0; then
+        len=`expr $len + 8`
+        mtu_opt=05010000${mtu}
+    fi
+
+    if test ${#prefix_opt} != 0; then
+        prefix_opt=${prefix_opt}fdad1234567800000000000000000000
+        len=`expr $len + ${#prefix_opt} / 2`
+    fi
+
+    len=$(printf "%x" $len)
+    local lrp_mac=fa163e000001
+    local lrp_lla=fe80000000000000f8163efffe000001
+    local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600XXXXff${addr_mode}ffff00000000000000000101${lrp_mac}${mtu_opt}${prefix_opt}
+    echo $reply >> $inport.expected
+
+    as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $request
+}
+
+AT_CAPTURE_FILE([ofctl_monitor0.log])
+as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
+--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log
+
+# MTU is not set and the address mode is set to slaac
+addr_mode=00
+default_prefix_option_config=030440c0ffffffffffffffff00000000
+src_mac=fa163e000002
+src_lla=fe80000000000000f8163efffe000002
+test_ipv6_ra 1 $src_mac $src_lla $addr_mode 0 $default_prefix_option_config
+
+# NXT_PACKET_IN2 should be 1.
+OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_PACKET_IN2`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap  > 1.packets
+
+cat 1.expected | cut -c -112 > expout
+AT_CHECK([cat 1.packets | cut -c -112], [0], [expout])
+
+# Skipping the ICMPv6 checksum.
+cat 1.expected | cut -c 117- > expout
+AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout])
+
+rm -f *.expected
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+reset_pcap_file hv1-vif3 hv1/vif3
+
+# Set the MTU to 1500
+ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:mtu=1500
+
+addr_mode=00
+default_prefix_option_config=030440c0ffffffffffffffff00000000
+src_mac=fa163e000003
+src_lla=fe80000000000000f8163efffe000003
+mtu=000005dc
+
+test_ipv6_ra 2 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
+
+# NXT_PACKET_IN2 should be 2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_PACKET_IN2`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap  > 2.packets
+
+cat 2.expected | cut -c -112 > expout
+AT_CHECK([cat 2.packets | cut -c -112], [0], [expout])
+
+# Skipping the ICMPv6 checksum.
+cat 2.expected | cut -c 117- > expout
+AT_CHECK([cat 2.packets | cut -c 117-], [0], [expout])
+
+rm -f *.expected
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+reset_pcap_file hv1-vif3 hv1/vif3
+
+# Set the address mode to dhcpv6_stateful
+ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateful
+
+addr_mode=80
+default_prefix_option_config=""
+src_mac=fa163e000004
+src_lla=fe80000000000000f8163efffe000004
+mtu=000005dc
+
+test_ipv6_ra 3 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
+
+# NXT_PACKET_IN2 should be 3.
+OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_PACKET_IN2`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap  > 3.packets
+
+cat 3.expected | cut -c -112 > expout
+AT_CHECK([cat 3.packets | cut -c -112], [0], [expout])
+
+# Skipping the ICMPv6 checksum.
+cat 3.expected | cut -c 117- > expout
+AT_CHECK([cat 3.packets | cut -c 117-], [0], [expout])
+
+rm -f *.expected
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+reset_pcap_file hv1-vif3 hv1/vif3
+
+# Set the address mode to dhcpv6_stateless
+ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateless
+
+addr_mode=40
+default_prefix_option_config=""
+src_mac=fa163e000002
+src_lla=fe80000000000000f8163efffe000002
+mtu=000005dc
+
+test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
+
+# NXT_PACKET_IN2 should be 4.
+OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_PACKET_IN2`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap  > 1.packets
+
+cat 1.expected | cut -c -112 > expout
+AT_CHECK([cat 1.packets | cut -c -112], [0], [expout])
+
+# Skipping the ICMPv6 checksum.
+cat 1.expected | cut -c 117- > expout
+AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout])
+
+rm -f *.expected
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+reset_pcap_file hv1-vif3 hv1/vif3
+
+# Set the address mode to invalid.
+ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=invalid
+
+addr_mode=40
+default_prefix_option_config=""
+src_mac=fa163e000002
+src_lla=fe80000000000000f8163efffe000002
+mtu=000005dc
+
+test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
+
+# NXT_PACKET_IN2 should be 4 only.
+OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_PACKET_IN2`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap  > 1.packets
+AT_CHECK([cat 1.packets], [0], [])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+
 AT_SETUP([ovn -- 1 LR with distributed router gateway port])
 AT_SKIP_IF([test $HAVE_PYTHON = no])
 ovn_start