From: Zong Kai LI <zealo...@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 <nusid...@redhat.com> Signed-off-by: Zongkai LI <zealo...@gmail.com> Signed-off-by: Numan Siddique <nusid...@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 | 246 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 378 insertions(+), 11 deletions(-) diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c index 5c0abba..86ba68d 100644 --- a/ovn/controller/pinctrl.c +++ b/ovn/controller/pinctrl.c @@ -1802,7 +1802,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 96aad9a..e52705d 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -133,10 +133,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") \ @@ -5004,7 +5005,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 @@ -5046,7 +5124,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 @@ -5241,7 +5319,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 @@ -5281,7 +5359,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 86efe86..57681a3 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "5.6.0", - "cksum": "1358108512 15019", + "version": "5.6.1", + "cksum": "282603683 15182", "tables": { "NB_Global": { "columns": { @@ -207,6 +207,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 deff965..e8aef28 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -1204,6 +1204,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 8e51edf..b40d4fc 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -7321,6 +7321,252 @@ OVN_CLEANUP([hv1],[hv2],[hv3]) 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 -- /32 router IP address]) AT_SKIP_IF([test $HAVE_PYTHON = no]) ovn_start -- 2.9.3 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev