Message ID | 20170714122629.18990-1-nusiddiq@redhat.com |
---|---|
State | Superseded |
Headers | show |
Looks good, just a tiny comment, but not actually very important. On Fri, Jul 14, 2017 at 2:26 PM, <nusiddiq@redhat.com> wrote: > From: Zongkai LI <zealokii@gmail.com> > > This patch adds logical flows which sends IPv6 Router Advertisement > packet in response to the IPv6 Router Solicitation request. It uses > the actions "put_nd_ra_opts" to transform the RS packet to RA packet > in the newly added ingress stage "lr_in_nd_ra_options" in router > pipeline. If the action "put_nd_ra_opts" is successful, it sends the > RA packet back to the originating port in the next ingress stage > "lr_in_nd_ra_response". > > 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/lib/logical-fields.c | 4 + > ovn/northd/ovn-northd.8.xml | 81 ++++++++++++++- > ovn/northd/ovn-northd.c | 129 ++++++++++++++++++++---- > ovn/ovn-nb.ovsschema | 7 +- > ovn/ovn-nb.xml | 39 ++++++++ > ovn/ovn-sb.xml | 4 + > tests/ovn.at | 237 ++++++++++++++++++++++++++++++ > ++++++++++++++ > 7 files changed, 478 insertions(+), 23 deletions(-) > > diff --git a/ovn/lib/logical-fields.c b/ovn/lib/logical-fields.c > index 26e336f5a..a8b5e3c51 100644 > --- a/ovn/lib/logical-fields.c > +++ b/ovn/lib/logical-fields.c > @@ -183,6 +183,10 @@ ovn_init_symtab(struct shash *symtab) > "icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255"); > expr_symtab_add_predicate(symtab, "nd_na", > "icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255"); > + expr_symtab_add_predicate(symtab, "nd_rs", > + "icmp6.type == 133 && icmp6.code == 0 && ip.ttl == 255"); > + expr_symtab_add_predicate(symtab, "nd_ra", > + "icmp6.type == 134 && icmp6.code == 0 && ip.ttl == 255"); > expr_symtab_add_field(symtab, "nd.target", MFF_ND_TARGET, "nd", > false); > expr_symtab_add_field(symtab, "nd.sll", MFF_ND_SLL, "nd_ns", false); > expr_symtab_add_field(symtab, "nd.tll", MFF_ND_TLL, "nd_na", false); > diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml > index 7ff524508..911f67b21 100644 > --- a/ovn/northd/ovn-northd.8.xml > +++ b/ovn/northd/ovn-northd.8.xml > @@ -1573,7 +1573,80 @@ icmp4 { > </li> > </ul> > > - <h3>Ingress Table 5: IP Routing</h3> > + <h3>Ingress Table 5: IPv6 ND RA option processing</h3> > + > + <ul> > + <li> > + <p> > + A priority-50 logical flow is added for each logical router port > + configured with IPv6 ND RA options which matches IPv6 ND Router > + Solicitation packet and applies the action > + <code>put_nd_ra_opts</code> and advances the packet to the next > + table. > + </p> > + > + <pre> > +reg0[5] = put_nd_ra_opts(<var>options</var>);next; > + </pre> > + > + <p> > + For a valid IPv6 ND RS packet, this transforms the packet into > an > + IPv6 ND RA reply and sets the RA options to the packet and > stores 1 > + into reg0[5]. For other kinds of packets, it just stores 0 into > + reg0[5]. Either way, it continues to the next table. > + </p> > + </li> > + > + <li> > + A priority-0 logical flow with match <code>1</code> has actions > + <code>next;</code>. > + </li> > + </ul> > + > + <h3>Ingress Table 6: IPv6 ND RA responder</h3> > + > + <p> > + This table implements IPv6 ND RA responder for the IPv6 ND RA > replies > + generated by the previous table. > + </p> > + > + <ul> > + <li> > + <p> > + A priority-50 logical flow is added for each logical router port > + configured with IPv6 ND RA options which matches IPv6 ND RA > + packets and <code>reg0[5] == 1</code> and responds back to the > + <code>inport</code> after applying these actions. > + If <code>reg0[5]</code> is set to 1, it means that the action > + <code>put_nd_ra_opts</code> was successful. > + </p> > + > + <pre> > +eth.src = <var>E</var>; > +ip6.src = <var>I</var>; > +outport = <var>P</var>; > +flags.loopback = 1; > +output; > + </pre> > + > + <p> > + where <var>E</var> is the MAC address and <var>I</var> is the > IPv6 > + link local address of the logical router port. > + </p> > + > + <p> > + (This terminates packet processing in ingress pipeline; the > packet > + does not go to the next ingress table.) > + </p> > + </li> > + > + <li> > + A priority-0 logical flow with match <code>1</code> has actions > + <code>next;</code>. > + </li> > + </ul> > + > + <h3>Ingress Table 7: IP Routing</h3> > > <p> > A packet that arrives at this table is an IP packet that should be > @@ -1675,7 +1748,7 @@ next; > </li> > </ul> > > - <h3>Ingress Table 6: ARP/ND Resolution</h3> > + <h3>Ingress Table 8: ARP/ND Resolution</h3> > > <p> > Any packet that reaches this table is an IP packet whose next-hop > @@ -1768,7 +1841,7 @@ next; > </li> > </ul> > > - <h3>Ingress Table 7: Gateway Redirect</h3> > + <h3>Ingress Table 9: Gateway Redirect</h3> > > <p> > For distributed logical routers where one of the logical router > @@ -1825,7 +1898,7 @@ next; > </li> > </ul> > > - <h3>Ingress Table 8: ARP Request</h3> > + <h3>Ingress Table 10: ARP Request</h3> > > <p> > In the common case where the Ethernet destination has been > resolved, this > diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c > index 2d7c2d86e..5e36eb9b1 100644 > --- a/ovn/northd/ovn-northd.c > +++ b/ovn/northd/ovn-northd.c > @@ -128,15 +128,17 @@ enum ovn_stage { > PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 8, "ls_out_port_sec_l2") > \ > \ > /* Logical router ingress stages. */ \ > - PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \ > - PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 1, "lr_in_ip_input") \ > - 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, ADMISSION, 0, "lr_in_admission") \ > + PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 1, "lr_in_ip_input") \ > + 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, ND_RA_OPTIONS, 5, "lr_in_nd_ra_options") > \ > + PIPELINE_STAGE(ROUTER, IN, ND_RA_RESPONSE, 6, > "lr_in_nd_ra_response") \ > + PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 7, "lr_in_ip_routing") \ > + PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 8, "lr_in_arp_resolve") \ > + PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 9, "lr_in_gw_redirect") \ > + PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 10, "lr_in_arp_request") > \ > \ > /* Logical router egress stages. */ \ > PIPELINE_STAGE(ROUTER, OUT, UNDNAT, 0, "lr_out_undnat") \ > @@ -158,11 +160,12 @@ enum ovn_stage { > #define OVN_ACL_PRI_OFFSET 1000 > > /* Register definitions specific to switches. */ > -#define REGBIT_CONNTRACK_DEFRAG "reg0[0]" > -#define REGBIT_CONNTRACK_COMMIT "reg0[1]" > -#define REGBIT_CONNTRACK_NAT "reg0[2]" > -#define REGBIT_DHCP_OPTS_RESULT "reg0[3]" > +#define REGBIT_CONNTRACK_DEFRAG "reg0[0]" > +#define REGBIT_CONNTRACK_COMMIT "reg0[1]" > +#define REGBIT_CONNTRACK_NAT "reg0[2]" > +#define REGBIT_DHCP_OPTS_RESULT "reg0[3]" > #define REGBIT_DNS_LOOKUP_RESULT "reg0[4]" > +#define REGBIT_ND_RA_OPTS_RESULT "reg0[5]" > > /* Register definitions for switches and routers. */ > #define REGBIT_NAT_REDIRECT "reg9[0]" > @@ -2634,7 +2637,11 @@ build_pre_acls(struct ovn_datapath *od, struct hmap > *lflows) > * > * Not to do conntrack on ND packets. */ > ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, "nd", > "next;"); > + ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, "(nd_rs || > nd_ra)", > + "next;"); > ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, "nd", > "next;"); > + ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, > + "(nd_rs || nd_ra)", "next;"); > > /* Ingress and Egress Pre-ACL Table (Priority 100). > * > @@ -5030,7 +5037,95 @@ 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 and 6: IPv6 Router Adv (RA) options > and > + * response. */ > + HMAP_FOR_EACH (op, key_node, ports) { > + if (!op->nbrp || op->nbrp->peer || !op->peer) { > + continue; > + } > + > + if (!op->lrp_networks.n_ipv6_addrs) { > + continue; > + } > + > + 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; > Would it be worth to log a rate limited warning? > + } > + > + ds_clear(&match); > + ds_put_format(&match, "inport == %s && ip6.dst == ff02::2 && > nd_rs", > + op->json_key); > + ds_clear(&actions); > + > + 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, REGBIT_ND_RA_OPTS_RESULT" = > put_nd_ra_opts(" > + "addr_mode = \"%s\", slla = %s", > + address_mode, op->lrp_networks.ea_s); > + if (mtu > 0) { > + ds_put_format(&actions, ", 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; > + } > + > + /* Add the prefix option if the address mode is slaac or > + * dhcpv6_stateless. */ > + if (strcmp(address_mode, "dhcpv6_stateful")) { > + ds_put_format(&actions, ", 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) { > + ds_put_cstr(&actions, "); next;"); > + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, 50, > + ds_cstr(&match), ds_cstr(&actions)); > + ds_clear(&actions); > + ds_clear(&match); > + ds_put_format(&match, "inport == %s && ip6.src == ff02::2 && " > + "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, > op->json_key); > + > + 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_ND_RA_RESPONSE, 50, > + ds_cstr(&match), ds_cstr(&actions)); > + } > + } > + > + /* Logical router ingress table 5, 6: 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_ND_RA_OPTIONS, 0, "1", > "next;"); > + ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", > "next;"); > + } > + > + /* Logical router ingress table 7: 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 > @@ -5072,7 +5167,7 @@ build_lrouter_flows(struct hmap *datapaths, struct > hmap *ports, > > /* XXX destination unreachable */ > > - /* Local router ingress table 6: ARP Resolution. > + /* Local router ingress table 8: 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 > @@ -5267,7 +5362,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 9: Gateway redirect. > * > * For traffic with outport equal to the l3dgw_port > * on a distributed router, this table redirects a subset > @@ -5307,7 +5402,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 10: 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 c6a1417ff..a15a24c27 100644 > --- a/ovn/ovn-nb.ovsschema > +++ b/ovn/ovn-nb.ovsschema > @@ -1,7 +1,7 @@ > { > "name": "OVN_Northbound", > - "version": "5.6.0", > - "cksum": "2552205612 15123", > + "version": "5.6.1", > + "cksum": "3970773262 15286", > "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 32c10c147..c9ed80cce 100644 > --- a/ovn/ovn-nb.xml > +++ b/ovn/ovn-nb.xml > @@ -1276,6 +1276,45 @@ > port has all ingress and egress traffic dropped. > </column> > > + <group title="ipv6_ra_configs"> > + <p> > + This column defines the IPv6 ND RA address mode and ND MTU Option > to be > + included by <code>ovn-controller</code> when it replies to the > IPv6 > + Router solicitation requests. > + </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/ovn/ovn-sb.xml b/ovn/ovn-sb.xml > index 824ff123a..43bcf380e 100644 > --- a/ovn/ovn-sb.xml > +++ b/ovn/ovn-sb.xml > @@ -908,6 +908,10 @@ > <li><code>nd</code> expands to <code>icmp6.type == {135, 136} > && icmp6.code == 0 && ip.ttl == 255</code></li> > <li><code>nd_ns</code> expands to <code>icmp6.type == 135 > && icmp6.code == 0 && ip.ttl == 255</code></li> > <li><code>nd_na</code> expands to <code>icmp6.type == 136 > && icmp6.code == 0 && ip.ttl == 255</code></li> > + <li><code>nd_rs</code> expands to <code>icmp6.type == 133 > && > + icmp6.code == 0 && ip.ttl == 255</code></li> > + <li><code>nd_ra</code> expands to <code>icmp6.type == 134 > && > + icmp6.code == 0 && ip.ttl == 255</code></li> > <li><code>tcp</code> expands to <code>ip.proto == 6</code></li> > <li><code>udp</code> expands to <code>ip.proto == 17</code></li> > <li><code>sctp</code> expands to <code>ip.proto == 132</code></li> > diff --git a/tests/ovn.at b/tests/ovn.at > index 8add532d7..f39481d5a 100644 > --- a/tests/ovn.at > +++ b/tests/ovn.at > @@ -7306,6 +7306,243 @@ OVN_CLEANUP([hv1],[hv2],[hv3]) > > AT_CLEANUP > > +AT_SETUP([ovn -- IPv6 ND Router Solicitation responder]) > +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 > + > +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_RESUME should be 1. > +OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) > + > +$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_RESUME should be 2. > +OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) > + > +$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_RESUME should be 3. > +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) > + > +$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=030440c0ffffffffffffffff00000000 > +src_mac=fa163e000002 > +src_lla=fe80000000000000f8163efffe000002 > +mtu=000005dc > + > +test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu > $default_prefix_option_config > + > +# NXT_RESUME should be 4. > +OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) > + > +$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_RESUME should be 4 only. > +OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) > + > +$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.13.0 > > _______________________________________________ > dev mailing list > dev@openvswitch.org > https://mail.openvswitch.org/mailman/listinfo/ovs-dev >
On Tue, Jul 25, 2017 at 6:14 PM, Miguel Angel Ajo Pelayo < majopela@redhat.com> wrote: > Looks good, just a tiny comment, but not actually very important. > > > On Fri, Jul 14, 2017 at 2:26 PM, <nusiddiq@redhat.com> wrote: > >> From: Zongkai LI <zealokii@gmail.com> >> >> This patch adds logical flows which sends IPv6 Router Advertisement >> packet in response to the IPv6 Router Solicitation request. It uses >> the actions "put_nd_ra_opts" to transform the RS packet to RA packet >> in the newly added ingress stage "lr_in_nd_ra_options" in router >> pipeline. If the action "put_nd_ra_opts" is successful, it sends the >> RA packet back to the originating port in the next ingress stage >> "lr_in_nd_ra_response". >> >> 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/lib/logical-fields.c | 4 + >> ovn/northd/ovn-northd.8.xml | 81 ++++++++++++++- >> ovn/northd/ovn-northd.c | 129 ++++++++++++++++++++---- >> ovn/ovn-nb.ovsschema | 7 +- >> ovn/ovn-nb.xml | 39 ++++++++ >> ovn/ovn-sb.xml | 4 + >> tests/ovn.at | 237 ++++++++++++++++++++++++++++++ >> ++++++++++++++ >> 7 files changed, 478 insertions(+), 23 deletions(-) >> >> diff --git a/ovn/lib/logical-fields.c b/ovn/lib/logical-fields.c >> index 26e336f5a..a8b5e3c51 100644 >> --- a/ovn/lib/logical-fields.c >> +++ b/ovn/lib/logical-fields.c >> @@ -183,6 +183,10 @@ ovn_init_symtab(struct shash *symtab) >> "icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255"); >> expr_symtab_add_predicate(symtab, "nd_na", >> "icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255"); >> + expr_symtab_add_predicate(symtab, "nd_rs", >> + "icmp6.type == 133 && icmp6.code == 0 && ip.ttl == 255"); >> + expr_symtab_add_predicate(symtab, "nd_ra", >> + "icmp6.type == 134 && icmp6.code == 0 && ip.ttl == 255"); >> expr_symtab_add_field(symtab, "nd.target", MFF_ND_TARGET, "nd", >> false); >> expr_symtab_add_field(symtab, "nd.sll", MFF_ND_SLL, "nd_ns", false); >> expr_symtab_add_field(symtab, "nd.tll", MFF_ND_TLL, "nd_na", false); >> diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml >> index 7ff524508..911f67b21 100644 >> --- a/ovn/northd/ovn-northd.8.xml >> +++ b/ovn/northd/ovn-northd.8.xml >> @@ -1573,7 +1573,80 @@ icmp4 { >> </li> >> </ul> >> >> - <h3>Ingress Table 5: IP Routing</h3> >> + <h3>Ingress Table 5: IPv6 ND RA option processing</h3> >> + >> + <ul> >> + <li> >> + <p> >> + A priority-50 logical flow is added for each logical router >> port >> + configured with IPv6 ND RA options which matches IPv6 ND Router >> + Solicitation packet and applies the action >> + <code>put_nd_ra_opts</code> and advances the packet to the next >> + table. >> + </p> >> + >> + <pre> >> +reg0[5] = put_nd_ra_opts(<var>options</var>);next; >> + </pre> >> + >> + <p> >> + For a valid IPv6 ND RS packet, this transforms the packet into >> an >> + IPv6 ND RA reply and sets the RA options to the packet and >> stores 1 >> + into reg0[5]. For other kinds of packets, it just stores 0 into >> + reg0[5]. Either way, it continues to the next table. >> + </p> >> + </li> >> + >> + <li> >> + A priority-0 logical flow with match <code>1</code> has actions >> + <code>next;</code>. >> + </li> >> + </ul> >> + >> + <h3>Ingress Table 6: IPv6 ND RA responder</h3> >> + >> + <p> >> + This table implements IPv6 ND RA responder for the IPv6 ND RA >> replies >> + generated by the previous table. >> + </p> >> + >> + <ul> >> + <li> >> + <p> >> + A priority-50 logical flow is added for each logical router >> port >> + configured with IPv6 ND RA options which matches IPv6 ND RA >> + packets and <code>reg0[5] == 1</code> and responds back to the >> + <code>inport</code> after applying these actions. >> + If <code>reg0[5]</code> is set to 1, it means that the action >> + <code>put_nd_ra_opts</code> was successful. >> + </p> >> + >> + <pre> >> +eth.src = <var>E</var>; >> +ip6.src = <var>I</var>; >> +outport = <var>P</var>; >> +flags.loopback = 1; >> +output; >> + </pre> >> + >> + <p> >> + where <var>E</var> is the MAC address and <var>I</var> is the >> IPv6 >> + link local address of the logical router port. >> + </p> >> + >> + <p> >> + (This terminates packet processing in ingress pipeline; the >> packet >> + does not go to the next ingress table.) >> + </p> >> + </li> >> + >> + <li> >> + A priority-0 logical flow with match <code>1</code> has actions >> + <code>next;</code>. >> + </li> >> + </ul> >> + >> + <h3>Ingress Table 7: IP Routing</h3> >> >> <p> >> A packet that arrives at this table is an IP packet that should be >> @@ -1675,7 +1748,7 @@ next; >> </li> >> </ul> >> >> - <h3>Ingress Table 6: ARP/ND Resolution</h3> >> + <h3>Ingress Table 8: ARP/ND Resolution</h3> >> >> <p> >> Any packet that reaches this table is an IP packet whose next-hop >> @@ -1768,7 +1841,7 @@ next; >> </li> >> </ul> >> >> - <h3>Ingress Table 7: Gateway Redirect</h3> >> + <h3>Ingress Table 9: Gateway Redirect</h3> >> >> <p> >> For distributed logical routers where one of the logical router >> @@ -1825,7 +1898,7 @@ next; >> </li> >> </ul> >> >> - <h3>Ingress Table 8: ARP Request</h3> >> + <h3>Ingress Table 10: ARP Request</h3> >> >> <p> >> In the common case where the Ethernet destination has been >> resolved, this >> diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c >> index 2d7c2d86e..5e36eb9b1 100644 >> --- a/ovn/northd/ovn-northd.c >> +++ b/ovn/northd/ovn-northd.c >> @@ -128,15 +128,17 @@ enum ovn_stage { >> PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 8, "ls_out_port_sec_l2") >> \ >> \ >> /* Logical router ingress stages. */ \ >> - PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \ >> - PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 1, "lr_in_ip_input") \ >> - 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, ADMISSION, 0, "lr_in_admission") >> \ >> + PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 1, "lr_in_ip_input") >> \ >> + 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, ND_RA_OPTIONS, 5, >> "lr_in_nd_ra_options") \ >> + PIPELINE_STAGE(ROUTER, IN, ND_RA_RESPONSE, 6, >> "lr_in_nd_ra_response") \ >> + PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 7, "lr_in_ip_routing") >> \ >> + PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 8, "lr_in_arp_resolve") >> \ >> + PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 9, "lr_in_gw_redirect") >> \ >> + PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 10, >> "lr_in_arp_request") \ >> \ >> /* Logical router egress stages. */ \ >> PIPELINE_STAGE(ROUTER, OUT, UNDNAT, 0, "lr_out_undnat") \ >> @@ -158,11 +160,12 @@ enum ovn_stage { >> #define OVN_ACL_PRI_OFFSET 1000 >> >> /* Register definitions specific to switches. */ >> -#define REGBIT_CONNTRACK_DEFRAG "reg0[0]" >> -#define REGBIT_CONNTRACK_COMMIT "reg0[1]" >> -#define REGBIT_CONNTRACK_NAT "reg0[2]" >> -#define REGBIT_DHCP_OPTS_RESULT "reg0[3]" >> +#define REGBIT_CONNTRACK_DEFRAG "reg0[0]" >> +#define REGBIT_CONNTRACK_COMMIT "reg0[1]" >> +#define REGBIT_CONNTRACK_NAT "reg0[2]" >> +#define REGBIT_DHCP_OPTS_RESULT "reg0[3]" >> #define REGBIT_DNS_LOOKUP_RESULT "reg0[4]" >> +#define REGBIT_ND_RA_OPTS_RESULT "reg0[5]" >> >> /* Register definitions for switches and routers. */ >> #define REGBIT_NAT_REDIRECT "reg9[0]" >> @@ -2634,7 +2637,11 @@ build_pre_acls(struct ovn_datapath *od, struct >> hmap *lflows) >> * >> * Not to do conntrack on ND packets. */ >> ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, "nd", >> "next;"); >> + ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, "(nd_rs || >> nd_ra)", >> + "next;"); >> ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, "nd", >> "next;"); >> + ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, >> + "(nd_rs || nd_ra)", "next;"); >> >> /* Ingress and Egress Pre-ACL Table (Priority 100). >> * >> @@ -5030,7 +5037,95 @@ 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 and 6: IPv6 Router Adv (RA) >> options and >> + * response. */ >> + HMAP_FOR_EACH (op, key_node, ports) { >> + if (!op->nbrp || op->nbrp->peer || !op->peer) { >> + continue; >> + } >> + >> + if (!op->lrp_networks.n_ipv6_addrs) { >> + continue; >> + } >> + >> + 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; >> > Would it be worth to log a rate limited warning? > Thanks for the review Miguel. I have addressed this comment, rebased the patch series to resolve the merge conflicts and submitted the v4. Numan > > > >> + } >> + >> + ds_clear(&match); >> + ds_put_format(&match, "inport == %s && ip6.dst == ff02::2 && >> nd_rs", >> + op->json_key); >> + ds_clear(&actions); >> + >> + 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, REGBIT_ND_RA_OPTS_RESULT" = >> put_nd_ra_opts(" >> + "addr_mode = \"%s\", slla = %s", >> + address_mode, op->lrp_networks.ea_s); >> + if (mtu > 0) { >> + ds_put_format(&actions, ", 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; >> + } >> + >> + /* Add the prefix option if the address mode is slaac or >> + * dhcpv6_stateless. */ >> + if (strcmp(address_mode, "dhcpv6_stateful")) { >> + ds_put_format(&actions, ", 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) { >> + ds_put_cstr(&actions, "); next;"); >> + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, 50, >> + ds_cstr(&match), ds_cstr(&actions)); >> + ds_clear(&actions); >> + ds_clear(&match); >> + ds_put_format(&match, "inport == %s && ip6.src == ff02::2 && >> " >> + "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, >> op->json_key); >> + >> + 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_ND_RA_RESPONSE, 50, >> + ds_cstr(&match), ds_cstr(&actions)); >> + } >> + } >> + >> + /* Logical router ingress table 5, 6: 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_ND_RA_OPTIONS, 0, "1", >> "next;"); >> + ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", >> "next;"); >> + } >> + >> + /* Logical router ingress table 7: 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 >> @@ -5072,7 +5167,7 @@ build_lrouter_flows(struct hmap *datapaths, struct >> hmap *ports, >> >> /* XXX destination unreachable */ >> >> - /* Local router ingress table 6: ARP Resolution. >> + /* Local router ingress table 8: 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 >> @@ -5267,7 +5362,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 9: Gateway redirect. >> * >> * For traffic with outport equal to the l3dgw_port >> * on a distributed router, this table redirects a subset >> @@ -5307,7 +5402,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 10: 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 c6a1417ff..a15a24c27 100644 >> --- a/ovn/ovn-nb.ovsschema >> +++ b/ovn/ovn-nb.ovsschema >> @@ -1,7 +1,7 @@ >> { >> "name": "OVN_Northbound", >> - "version": "5.6.0", >> - "cksum": "2552205612 15123", >> + "version": "5.6.1", >> + "cksum": "3970773262 15286", >> "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 32c10c147..c9ed80cce 100644 >> --- a/ovn/ovn-nb.xml >> +++ b/ovn/ovn-nb.xml >> @@ -1276,6 +1276,45 @@ >> port has all ingress and egress traffic dropped. >> </column> >> >> + <group title="ipv6_ra_configs"> >> + <p> >> + This column defines the IPv6 ND RA address mode and ND MTU >> Option to be >> + included by <code>ovn-controller</code> when it replies to the >> IPv6 >> + Router solicitation requests. >> + </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/ovn/ovn-sb.xml b/ovn/ovn-sb.xml >> index 824ff123a..43bcf380e 100644 >> --- a/ovn/ovn-sb.xml >> +++ b/ovn/ovn-sb.xml >> @@ -908,6 +908,10 @@ >> <li><code>nd</code> expands to <code>icmp6.type == {135, 136} >> && icmp6.code == 0 && ip.ttl == 255</code></li> >> <li><code>nd_ns</code> expands to <code>icmp6.type == 135 >> && icmp6.code == 0 && ip.ttl == 255</code></li> >> <li><code>nd_na</code> expands to <code>icmp6.type == 136 >> && icmp6.code == 0 && ip.ttl == 255</code></li> >> + <li><code>nd_rs</code> expands to <code>icmp6.type == 133 >> && >> + icmp6.code == 0 && ip.ttl == 255</code></li> >> + <li><code>nd_ra</code> expands to <code>icmp6.type == 134 >> && >> + icmp6.code == 0 && ip.ttl == 255</code></li> >> <li><code>tcp</code> expands to <code>ip.proto == 6</code></li> >> <li><code>udp</code> expands to <code>ip.proto == 17</code></li> >> <li><code>sctp</code> expands to <code>ip.proto == >> 132</code></li> >> diff --git a/tests/ovn.at b/tests/ovn.at >> index 8add532d7..f39481d5a 100644 >> --- a/tests/ovn.at >> +++ b/tests/ovn.at >> @@ -7306,6 +7306,243 @@ OVN_CLEANUP([hv1],[hv2],[hv3]) >> >> AT_CLEANUP >> >> +AT_SETUP([ovn -- IPv6 ND Router Solicitation responder]) >> +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 >> + >> +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_RESUME should be 1. >> +OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) >> + >> +$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_RESUME should be 2. >> +OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) >> + >> +$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_RESUME should be 3. >> +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) >> + >> +$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=030440c0ffffffffffffffff00000000 >> +src_mac=fa163e000002 >> +src_lla=fe80000000000000f8163efffe000002 >> +mtu=000005dc >> + >> +test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu >> $default_prefix_option_config >> + >> +# NXT_RESUME should be 4. >> +OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) >> + >> +$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_RESUME should be 4 only. >> +OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) >> + >> +$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.13.0 >> >> _______________________________________________ >> dev mailing list >> dev@openvswitch.org >> https://mail.openvswitch.org/mailman/listinfo/ovs-dev >> > >
diff --git a/ovn/lib/logical-fields.c b/ovn/lib/logical-fields.c index 26e336f5a..a8b5e3c51 100644 --- a/ovn/lib/logical-fields.c +++ b/ovn/lib/logical-fields.c @@ -183,6 +183,10 @@ ovn_init_symtab(struct shash *symtab) "icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255"); expr_symtab_add_predicate(symtab, "nd_na", "icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255"); + expr_symtab_add_predicate(symtab, "nd_rs", + "icmp6.type == 133 && icmp6.code == 0 && ip.ttl == 255"); + expr_symtab_add_predicate(symtab, "nd_ra", + "icmp6.type == 134 && icmp6.code == 0 && ip.ttl == 255"); expr_symtab_add_field(symtab, "nd.target", MFF_ND_TARGET, "nd", false); expr_symtab_add_field(symtab, "nd.sll", MFF_ND_SLL, "nd_ns", false); expr_symtab_add_field(symtab, "nd.tll", MFF_ND_TLL, "nd_na", false); diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index 7ff524508..911f67b21 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -1573,7 +1573,80 @@ icmp4 { </li> </ul> - <h3>Ingress Table 5: IP Routing</h3> + <h3>Ingress Table 5: IPv6 ND RA option processing</h3> + + <ul> + <li> + <p> + A priority-50 logical flow is added for each logical router port + configured with IPv6 ND RA options which matches IPv6 ND Router + Solicitation packet and applies the action + <code>put_nd_ra_opts</code> and advances the packet to the next + table. + </p> + + <pre> +reg0[5] = put_nd_ra_opts(<var>options</var>);next; + </pre> + + <p> + For a valid IPv6 ND RS packet, this transforms the packet into an + IPv6 ND RA reply and sets the RA options to the packet and stores 1 + into reg0[5]. For other kinds of packets, it just stores 0 into + reg0[5]. Either way, it continues to the next table. + </p> + </li> + + <li> + A priority-0 logical flow with match <code>1</code> has actions + <code>next;</code>. + </li> + </ul> + + <h3>Ingress Table 6: IPv6 ND RA responder</h3> + + <p> + This table implements IPv6 ND RA responder for the IPv6 ND RA replies + generated by the previous table. + </p> + + <ul> + <li> + <p> + A priority-50 logical flow is added for each logical router port + configured with IPv6 ND RA options which matches IPv6 ND RA + packets and <code>reg0[5] == 1</code> and responds back to the + <code>inport</code> after applying these actions. + If <code>reg0[5]</code> is set to 1, it means that the action + <code>put_nd_ra_opts</code> was successful. + </p> + + <pre> +eth.src = <var>E</var>; +ip6.src = <var>I</var>; +outport = <var>P</var>; +flags.loopback = 1; +output; + </pre> + + <p> + where <var>E</var> is the MAC address and <var>I</var> is the IPv6 + link local address of the logical router port. + </p> + + <p> + (This terminates packet processing in ingress pipeline; the packet + does not go to the next ingress table.) + </p> + </li> + + <li> + A priority-0 logical flow with match <code>1</code> has actions + <code>next;</code>. + </li> + </ul> + + <h3>Ingress Table 7: IP Routing</h3> <p> A packet that arrives at this table is an IP packet that should be @@ -1675,7 +1748,7 @@ next; </li> </ul> - <h3>Ingress Table 6: ARP/ND Resolution</h3> + <h3>Ingress Table 8: ARP/ND Resolution</h3> <p> Any packet that reaches this table is an IP packet whose next-hop @@ -1768,7 +1841,7 @@ next; </li> </ul> - <h3>Ingress Table 7: Gateway Redirect</h3> + <h3>Ingress Table 9: Gateway Redirect</h3> <p> For distributed logical routers where one of the logical router @@ -1825,7 +1898,7 @@ next; </li> </ul> - <h3>Ingress Table 8: ARP Request</h3> + <h3>Ingress Table 10: ARP Request</h3> <p> In the common case where the Ethernet destination has been resolved, this diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 2d7c2d86e..5e36eb9b1 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -128,15 +128,17 @@ enum ovn_stage { PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 8, "ls_out_port_sec_l2") \ \ /* Logical router ingress stages. */ \ - PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \ - PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 1, "lr_in_ip_input") \ - 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, ADMISSION, 0, "lr_in_admission") \ + PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 1, "lr_in_ip_input") \ + 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, ND_RA_OPTIONS, 5, "lr_in_nd_ra_options") \ + PIPELINE_STAGE(ROUTER, IN, ND_RA_RESPONSE, 6, "lr_in_nd_ra_response") \ + PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 7, "lr_in_ip_routing") \ + PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 8, "lr_in_arp_resolve") \ + PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 9, "lr_in_gw_redirect") \ + PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 10, "lr_in_arp_request") \ \ /* Logical router egress stages. */ \ PIPELINE_STAGE(ROUTER, OUT, UNDNAT, 0, "lr_out_undnat") \ @@ -158,11 +160,12 @@ enum ovn_stage { #define OVN_ACL_PRI_OFFSET 1000 /* Register definitions specific to switches. */ -#define REGBIT_CONNTRACK_DEFRAG "reg0[0]" -#define REGBIT_CONNTRACK_COMMIT "reg0[1]" -#define REGBIT_CONNTRACK_NAT "reg0[2]" -#define REGBIT_DHCP_OPTS_RESULT "reg0[3]" +#define REGBIT_CONNTRACK_DEFRAG "reg0[0]" +#define REGBIT_CONNTRACK_COMMIT "reg0[1]" +#define REGBIT_CONNTRACK_NAT "reg0[2]" +#define REGBIT_DHCP_OPTS_RESULT "reg0[3]" #define REGBIT_DNS_LOOKUP_RESULT "reg0[4]" +#define REGBIT_ND_RA_OPTS_RESULT "reg0[5]" /* Register definitions for switches and routers. */ #define REGBIT_NAT_REDIRECT "reg9[0]" @@ -2634,7 +2637,11 @@ build_pre_acls(struct ovn_datapath *od, struct hmap *lflows) * * Not to do conntrack on ND packets. */ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, "nd", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, "(nd_rs || nd_ra)", + "next;"); ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, "nd", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, + "(nd_rs || nd_ra)", "next;"); /* Ingress and Egress Pre-ACL Table (Priority 100). * @@ -5030,7 +5037,95 @@ 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 and 6: IPv6 Router Adv (RA) options and + * response. */ + HMAP_FOR_EACH (op, key_node, ports) { + if (!op->nbrp || op->nbrp->peer || !op->peer) { + continue; + } + + if (!op->lrp_networks.n_ipv6_addrs) { + continue; + } + + 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; + } + + ds_clear(&match); + ds_put_format(&match, "inport == %s && ip6.dst == ff02::2 && nd_rs", + op->json_key); + ds_clear(&actions); + + 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, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts(" + "addr_mode = \"%s\", slla = %s", + address_mode, op->lrp_networks.ea_s); + if (mtu > 0) { + ds_put_format(&actions, ", 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; + } + + /* Add the prefix option if the address mode is slaac or + * dhcpv6_stateless. */ + if (strcmp(address_mode, "dhcpv6_stateful")) { + ds_put_format(&actions, ", 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) { + ds_put_cstr(&actions, "); next;"); + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, 50, + ds_cstr(&match), ds_cstr(&actions)); + ds_clear(&actions); + ds_clear(&match); + ds_put_format(&match, "inport == %s && ip6.src == ff02::2 && " + "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key); + + 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_ND_RA_RESPONSE, 50, + ds_cstr(&match), ds_cstr(&actions)); + } + } + + /* Logical router ingress table 5, 6: 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_ND_RA_OPTIONS, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;"); + } + + /* Logical router ingress table 7: 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 @@ -5072,7 +5167,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, /* XXX destination unreachable */ - /* Local router ingress table 6: ARP Resolution. + /* Local router ingress table 8: 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 @@ -5267,7 +5362,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 9: Gateway redirect. * * For traffic with outport equal to the l3dgw_port * on a distributed router, this table redirects a subset @@ -5307,7 +5402,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 10: 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 c6a1417ff..a15a24c27 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "5.6.0", - "cksum": "2552205612 15123", + "version": "5.6.1", + "cksum": "3970773262 15286", "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 32c10c147..c9ed80cce 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -1276,6 +1276,45 @@ port has all ingress and egress traffic dropped. </column> + <group title="ipv6_ra_configs"> + <p> + This column defines the IPv6 ND RA address mode and ND MTU Option to be + included by <code>ovn-controller</code> when it replies to the IPv6 + Router solicitation requests. + </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/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index 824ff123a..43bcf380e 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -908,6 +908,10 @@ <li><code>nd</code> expands to <code>icmp6.type == {135, 136} && icmp6.code == 0 && ip.ttl == 255</code></li> <li><code>nd_ns</code> expands to <code>icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255</code></li> <li><code>nd_na</code> expands to <code>icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255</code></li> + <li><code>nd_rs</code> expands to <code>icmp6.type == 133 && + icmp6.code == 0 && ip.ttl == 255</code></li> + <li><code>nd_ra</code> expands to <code>icmp6.type == 134 && + icmp6.code == 0 && ip.ttl == 255</code></li> <li><code>tcp</code> expands to <code>ip.proto == 6</code></li> <li><code>udp</code> expands to <code>ip.proto == 17</code></li> <li><code>sctp</code> expands to <code>ip.proto == 132</code></li> diff --git a/tests/ovn.at b/tests/ovn.at index 8add532d7..f39481d5a 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -7306,6 +7306,243 @@ OVN_CLEANUP([hv1],[hv2],[hv3]) AT_CLEANUP +AT_SETUP([ovn -- IPv6 ND Router Solicitation responder]) +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 + +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_RESUME should be 1. +OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$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_RESUME should be 2. +OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$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_RESUME should be 3. +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$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=030440c0ffffffffffffffff00000000 +src_mac=fa163e000002 +src_lla=fe80000000000000f8163efffe000002 +mtu=000005dc + +test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config + +# NXT_RESUME should be 4. +OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$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_RESUME should be 4 only. +OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$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