This patch add support for building Router Solicitation responder flows, which will be used to reply Router Advertisement packet from ovn-controller for VM/VIF port.
It adds table Logical_Router_RA_Prefix_Config to define a set of ND Prefix informations configurations(such as valid_lifetime, preferred_lifetime) for a IPv6 prefix. It adds table Logical_Router_Port new columns: - ipv6_ra_prefix_configs: to refer records in Logical_Router_RA_Prefix_Config for lrouter port to use for its networks. - ipv6_ra_configs: to define a set of RA configurations, such as "managed_address" flag, router_lifetime. v1 -> v2 introduces new table and new columns to define configurations for RS responder. --- ovn/northd/ovn-northd.c | 140 ++++++++++++++++++++++++++++++++++++++++++++-- ovn/ovn-nb.ovsschema | 21 ++++++- ovn/ovn-nb.xml | 103 ++++++++++++++++++++++++++++++++++ ovn/utilities/ovn-nbctl.c | 5 ++ tests/ovn.at | 134 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 395 insertions(+), 8 deletions(-) diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index ab5e2f0..d3b7282 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -123,9 +123,10 @@ enum ovn_stage { PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 1, "lr_in_ip_input") \ PIPELINE_STAGE(ROUTER, IN, UNSNAT, 2, "lr_in_unsnat") \ PIPELINE_STAGE(ROUTER, IN, DNAT, 3, "lr_in_dnat") \ - PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 4, "lr_in_ip_routing") \ - PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 5, "lr_in_arp_resolve") \ - PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 6, "lr_in_arp_request") \ + PIPELINE_STAGE(ROUTER, IN, RS_RSP, 4, "lr_in_rs_rsp") \ + 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, ARP_REQUEST, 7, "lr_in_arp_request") \ \ /* Logical router egress stages. */ \ PIPELINE_STAGE(ROUTER, OUT, SNAT, 0, "lr_out_snat") \ @@ -3592,7 +3593,134 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, "ip", "flags.loopback = 1; ct_dnat;"); } - /* Logical router ingress table 4: IP Routing. + /* Logical router ingress table 4: 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 *hop_lmt = smap_get( + &op->nbrp->ipv6_ra_configs, "cur_hop_limit"); + const char *mgd_addr = smap_get( + &op->nbrp->ipv6_ra_configs, "managed_address"); + const char *other_cfg = smap_get( + &op->nbrp->ipv6_ra_configs, "other_config"); + const char *rtr_lt = smap_get( + &op->nbrp->ipv6_ra_configs, "router_lifetime"); + const char *rch_t = smap_get( + &op->nbrp->ipv6_ra_configs, "reachable_time"); + const char *rts_t = smap_get( + &op->nbrp->ipv6_ra_configs, "retrans_timer"); + const char *mtu_s = smap_get( + &op->nbrp->ipv6_ra_configs, "mtu"); + + uint8_t managed_address_flag = ND_ROUTER_ADV_MANAGED_ADDRESS; + if (mgd_addr && !strcmp(mgd_addr, "0")) { + managed_address_flag = 0; + } + uint8_t other_config_flag = ND_ROUTER_ADV_OTHER_CONFIG; + if (other_cfg && !strcmp(other_cfg, "0")) { + other_config_flag = 0; + } + uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0; + + ds_put_format(&actions, "nd_ra{put_nd_ra(%u, %u, %u, %u, %u); " + "put_nd_opt_sll(%s); ", + (hop_lmt && atoi(hop_lmt) > 0) ? atoi(hop_lmt) : 64, + managed_address_flag | other_config_flag, + (rtr_lt && atoi(rtr_lt) > 0) ? atoi(rtr_lt) : 10800, + (rch_t && atoi(rch_t) > 0) ? atoi(rch_t) : 0, + (rts_t && atoi(rts_t) > 0) ? atoi(rts_t) : 0, + op->lrp_networks.ea_s); + if (mtu > 0) { + ds_put_format(&actions, "put_nd_opt_mtu(%u); ", + mtu); + } + size_t prev_actions_len = actions.length; + struct ds lrp_prefix = DS_EMPTY_INITIALIZER; + 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; + } + uint8_t onlink_flag = ND_PREFIX_ON_LINK; + uint8_t autonomous_address_flag = ND_PREFIX_AUTONOMOUS_ADDRESS; + const char *v_lt = NULL; + const char *p_lt = NULL; + ds_put_format(&lrp_prefix, "%s/%u", + op->lrp_networks.ipv6_addrs[i].network_s, + op->lrp_networks.ipv6_addrs[i].plen); + for (size_t j = 0; j != op->nbrp->n_ipv6_ra_prefix_configs; j++) { + if (!strcmp(ds_cstr(&lrp_prefix), + op->nbrp->ipv6_ra_prefix_configs[j]->prefix)) { + const char *ol = smap_get( + &op->nbrp->ipv6_ra_prefix_configs[j]->options, + "onlink"); + const char *aa = smap_get( + &op->nbrp->ipv6_ra_prefix_configs[j]->options, + "autonomous_address"); + v_lt = smap_get( + &op->nbrp->ipv6_ra_prefix_configs[j]->options, + "valid_lifetime"); + p_lt = smap_get( + &op->nbrp->ipv6_ra_prefix_configs[j]->options, + "preferred_lifetime"); + if (ol && !strcmp(ol, "0")) { + onlink_flag = 0; + } + if (aa && !strcmp(aa, "0")) { + autonomous_address_flag = 0; + } + break; + } + } + ds_put_format(&actions, + "put_nd_opt_prefix(%u, %u, %u, %u, %s); ", + op->lrp_networks.ipv6_addrs[i].plen, + (onlink_flag | autonomous_address_flag), + (v_lt && atoi(v_lt) > 0) ? atoi(v_lt) : 10800, + (p_lt && atoi(p_lt) > 0) ? atoi(p_lt) : 10800, + op->lrp_networks.ipv6_addrs[i].network_s); + ds_clear(&lrp_prefix); + } + ds_destroy(&lrp_prefix); + + if (actions.length != prev_actions_len) { + 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 4: 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 5: 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 @@ -3634,7 +3762,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, /* XXX destination unreachable */ - /* Local router ingress table 5: ARP Resolution. + /* Local router ingress table 6: 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 @@ -3831,7 +3959,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, "get_nd(outport, xxreg0); next;"); } - /* Local router ingress table 6: ARP request. + /* Local router ingress table 7: 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 456ae98..7d4a9c0 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "5.3.1", - "cksum": "1921908091 9353", + "version": "5.4.0", + "cksum": "306455825 10329", "tables": { "NB_Global": { "columns": { @@ -155,6 +155,14 @@ "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"}}, + "ipv6_ra_prefix_configs": {"type": {"key": {"type": "uuid", + "refTable": "Logical_Router_RA_Prefix_Config", + "refType": "weak"}, + "min": 0, + "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, @@ -184,6 +192,15 @@ "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, + "isRoot": true}, + "Logical_Router_RA_Prefix_Config": { + "columns": { + "prefix": {"type": "string"}, + "options": {"type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}, + "external_ids": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}}, "isRoot": true} } } diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index 5719e74..f462293 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -913,6 +913,54 @@ The Ethernet address that belongs to this router port. </column> + <group title="ipv6_ra_configs"> + <p> + This column defines the IPv6 RA configurations and ND MTU Option to be + used by <code>ovn-northd</code> when it generates logical flows for + Router Solicitaion responder. + Please refer to RFC 4861 for more details on RA message fields and + ND MTU Option. + </p> + + <column name="ipv6_ra_configs" key="cur_hop_limit"> + Current hop limit. Default is 64. + </column> + + <column name="ipv6_ra_configs" key="managed_address"> + 1-bit "Managed address configuration" flag. Default is 1. + </column> + + <column name="ipv6_ra_configs" key="other_config"> + 1-bit "Other configuration" flag. Default is 1. + </column> + + <column name="ipv6_ra_configs" key="router_lifetime"> + Router lifetime. Default is 10800(seconds). + </column> + + <column name="ipv6_ra_configs" key="reachable_time"> + Reachable time. Default is 0. + </column> + + <column name="ipv6_ra_configs" key="retrans_timer"> + Retrans timer. Default is 0. + </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 considerred as no MTU Option. + </column> + </group> + + <column name="ipv6_ra_prefix_configs"> + This column defines the IPv6 ND Prefix Information Option configurations + to be used by <code>ovn-northd</code> when it generates logical flows for + Router Solicitaion responder. + Please see the <ref table="Logical_Router_RA_Prefix_Config"/> table. + </column> + <column name="enabled"> This column is used to administratively set port state. If this column is empty or is set to <code>true</code>, the port is enabled. If this @@ -1324,4 +1372,59 @@ </column> </group> </table> + + <table name="Logical_Router_RA_Prefix_Config" title="Logical Router RA Prefix Config"> + <p> + Each record represents a set of RA configurations for + Logical_Router_Port to use. + </p> + + <column name="prefix"> + <p> + The RA Prefix Information Option configurations will be used if the + logical router port has its network in this <ref column="prefix"/>. + </p> + </column> + + <group title="configs"> + <p> + The CMS should define the set of ND Prefix Information Option + configurations as key/value pairs in the <ref column="configs"/> column + of this table. + Records in <ref table="Logical_Router_Port"/> can refer record in this + table in their <ref column="ipv6_ra_prefix_configs"/> to configure Router + Solicitation responder. Then <code>ovn-northd</code> will build logical + flows per ND Prefix Information Option configurations defined in the + <ref column="configs"/> column. + </p> + + <p> + Below are the supported ND Prefix Information Option configurations. + Please refer to RFC 4861 for more details. + </p> + + <column name="options" key="onlink"> + 1-bit on-link flag. Default is 1. + </column> + + <column name="options" key="autonomous"> + 1-bit autonomous address-configuration flag. Default is 1. + </column> + + <column name="options" key="valid_lifetime"> + Valid lifetime for on-link prefix. Default is 10800(seconds). + </column> + + <column name="options" key="preferred_lifetime"> + Preferred lifetime for on-link prefix. Default is 10800(seconds). + </column> + </group> + + <group title="Common Columns"> + <column name="external_ids"> + See <em>External IDs</em> at the beginning of this document. + </column> + </group> + </table> + </database> diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c index d6d64ea..d241661 100644 --- a/ovn/utilities/ovn-nbctl.c +++ b/ovn/utilities/ovn-nbctl.c @@ -2161,6 +2161,11 @@ static const struct ctl_table_class tables[] = { NULL}, {NULL, NULL, NULL}}}, + {&nbrec_table_logical_router_ra_prefix_config, + {{&nbrec_table_logical_router_ra_prefix_config, NULL, + NULL}, + {NULL, NULL, NULL}}}, + {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}} }; diff --git a/tests/ovn.at b/tests/ovn.at index 53528ac..387ff0b 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -5071,3 +5071,137 @@ OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=0 | grep REG13 | wc -l` OVN_CLEANUP([hv1]) 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. +# Router solicitation packet we test, sent from VIF port, will be replied +# by local ovn-controller. + +# Create hypervisors and logical switch lsw0, logical router lr0, attach lsw0 +# onto lr0, set Logical_Router_Port.slaac column to 'true' 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 slaac="true" +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 + +# Add vif1 to hv1 and lsw0, turn on l2 port security on vif1. +ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap +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" + +# Add vif2 to hv1 and lsw0, turn on l2 port security on vif2. +ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv1/vif2-tx.pcap options:rxq_pcap=hv1/vif2-rx.pcap +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" + +# Add vif3 to hv1 and lsw0, turn on l2 port security on vif3. +ovs-vsctl add-port br-int vif3 -- set Interface vif3 external-ids:iface-id=lp3 options:tx_pcap=hv1/vif3-tx.pcap options:rxq_pcap=hv1/vif3-rx.pcap +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 + +# 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 + +# This shell function sends a Router Solicitation packet and receives +# Router Solicitation packet from vif. +# test_ipv6_ra INPORT SRC_MAC SRC_LLA RA_CONFIG MTU RA_PREFIX_CONFIG +test_ipv6_ra() { + local inport=$1 src_mac=$2 src_lla=$3 ra_config=$4 mtu=$5 ra_prefix_config=$6 + local request=333300000002${src_mac}86dd6000000000103aff${src_lla}ff02000000000000000000000000000285000efc000000000101${src_mac} + + local len=40 + if test $mtu == 0; then + len=38 + unset mtu + fi + local lrp_mac=fa163e000001 + local lrp_lla=fe80000000000000f8163efffe000001 + local lrp_prefix=fdad1234567800000000000000000000 + local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600${ra_config}0101${lrp_mac}${mtu}0304${ra_prefix_config}00000000${lrp_prefix} + + as hv1 ovs-appctl netdev-dummy/receive vif${inport} $request + echo $reply | trim_zeros >> $inport.expected +} + +# No setting to lrp0 ipv6_ra_configs and ipv6_ra_prefix_configs, vif1 will get +# a RA packet with default configs. +default_ra_config=40c02a300000000000000000 +default_prefix_option_config=40c000002a3000002a30 +src_mac=fa163e000002 +src_lla=fe80000000000000f8163efffe000002 +test_ipv6_ra 1 $src_mac $src_lla $default_ra_config 0 $default_prefix_option_config + +# Set lrp0 ipv6_ra_configs, vif2 will get a configured RA packet +ovn-nbctl add Logical_Router_Port lrp0 ipv6_ra_config managed_address="0" other_config="0" mtu="1450" +ra_config=40002a300000000000000000 +mtu_opt=05010000000005aa +src_mac=fa163e000003 +src_lla=fe80000000000000f8163efffe000003 +test_ipv6_ra 2 $src_mac $src_lla $ra_config $mtu_opt $default_prefix_option_config + +# Create a Logical_Router_RA_Prefix_Config, and set lrp0.ipv6_ra_prefix_configs +# to refer it, vif3 will get a configured RA packet with configured Prefix +# Information Option. +ovn-nbctl -- --id=@d1 create Logical_Router_RA_Prefix_Config prefix=\"fdad:1234:5678::/64\" \ +options="\"valid_lifetime\"="21600" \"preferred_lifetime\"="21600" \ +\"autonomous_address\"="0"" \ +-- add Logical_Router_Port lrp0 ipv6_ra_prefix_configs @d1 +prefix_option_config=40800000546000005460 +src_mac=fa163e000004 +src_lla=fe80000000000000f8163efffe000004 +test_ipv6_ra 3 $src_mac $src_lla $ra_config $mtu_opt $prefix_option_config + +sleep 1 + +echo "------ hv1 dump ------" +as hv1 ovs-vsctl show +as hv1 ovs-ofctl -O OpenFlow13 show br-int +as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int + +for i in 1 2 3; do + file=hv1/vif$i-tx.pcap + echo $file + # Remove checksum to compare. + $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $file | trim_zeros | cut -b 1-112,117- > $i.packets + cat $i.expected > expout + AT_CHECK([cat $i.packets], [0], [expout]) +done + +OVN_CLEANUP([hv1]) + +AT_CLEANUP -- 2.7.4 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev