[ovs-dev,v9,3/4] ovn-northd: Add logical flows to support native IPv6 RA

Message ID 20171002162542.1848-1-nusiddiq@redhat.com
State Superseded
Headers show
Series
  • ovn IPv6: Add Router Solicitation responder support and generate Neighbor Solicitation request for unknown
Related show

Commit Message

Numan Siddique Oct. 2, 2017, 4:25 p.m.
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>
Acked-by: Miguel Angel Ajo <majopela@redhat.com>
---
 ovn/lib/logical-fields.c    |   4 +
 ovn/northd/ovn-northd.8.xml |  83 ++++++++++++++-
 ovn/northd/ovn-northd.c     | 137 +++++++++++++++++++++---
 ovn/ovn-nb.ovsschema        |   7 +-
 ovn/ovn-nb.xml              |  39 +++++++
 ovn/ovn-sb.xml              |   4 +
 tests/ovn.at                | 249 ++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 500 insertions(+), 23 deletions(-)

Comments

Mark Michelson Oct. 4, 2017, 2:54 p.m. | #1
On Mon, Oct 2, 2017 at 11:28 AM <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>
> Acked-by: Miguel Angel Ajo <majopela@redhat.com>
>

Acked-by: Mark Michelson <mmichels@redhat.com>


> ---
>  ovn/lib/logical-fields.c    |   4 +
>  ovn/northd/ovn-northd.8.xml |  83 ++++++++++++++-
>  ovn/northd/ovn-northd.c     | 137 +++++++++++++++++++++---
>  ovn/ovn-nb.ovsschema        |   7 +-
>  ovn/ovn-nb.xml              |  39 +++++++
>  ovn/ovn-sb.xml              |   4 +
>  tests/ovn.at                | 249
> ++++++++++++++++++++++++++++++++++++++++++++
>  7 files changed, 500 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 0d85ec0d2..a994abf78 100644
> --- a/ovn/northd/ovn-northd.8.xml
> +++ b/ovn/northd/ovn-northd.8.xml
> @@ -1584,7 +1584,82 @@ 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.dst = eth.src;
> +eth.src = <var>E</var>;
> +ip6.dst = ip6.src;
> +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
> @@ -1686,7 +1761,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
> @@ -1779,7 +1854,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
> @@ -1836,7 +1911,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 75f2c6607..3da20d25b 100644
> --- a/ovn/northd/ovn-northd.c
> +++ b/ovn/northd/ovn-northd.c
> @@ -129,15 +129,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")        \
> @@ -159,11 +161,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]"
> @@ -2868,7 +2871,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).
>           *
> @@ -5319,7 +5326,103 @@ 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) {
> +            continue;
> +        }
> +        if (strcmp(address_mode, "slaac") &&
> +            strcmp(address_mode, "dhcpv6_stateful") &&
> +            strcmp(address_mode, "dhcpv6_stateless")) {
> +            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +            VLOG_WARN_RL(&rl, "Invalid address mode [%s] defined",
> +                         address_mode);
> +            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");
> +
> +        /* As per RFC 2460, 1280 is minimum IPv6 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.dst == 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.dst = eth.src; eth.src = %s; "
> +                          "ip6.dst = ip6.src; 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
> @@ -5361,7 +5464,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
> @@ -5556,7 +5659,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
> @@ -5596,7 +5699,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 a077bfb81..fcd878cf2 100644
> --- a/ovn/ovn-nb.ovsschema
> +++ b/ovn/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
> -    "version": "5.8.0",
> -    "cksum": "2812300190 <(281)%20230-0190> 16766",
> +    "version": "5.8.1",
> +    "cksum": "607160660 16929",
>      "tables": {
>          "NB_Global": {
>              "columns": {
> @@ -222,6 +222,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 9869d7ed7..8ad53cd7d 100644
> --- a/ovn/ovn-nb.xml
> +++ b/ovn/ovn-nb.xml
> @@ -1332,6 +1332,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 fab3f9de6..2e4f28b96 100644
> --- a/ovn/ovn-sb.xml
> +++ b/ovn/ovn-sb.xml
> @@ -911,6 +911,10 @@
>          <li><code>nd</code> expands to <code>icmp6.type == {135, 136}
> &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
>          <li><code>nd_ns</code> expands to <code>icmp6.type == 135
> &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
>          <li><code>nd_na</code> expands to <code>icmp6.type == 136
> &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
> +        <li><code>nd_rs</code> expands to <code>icmp6.type == 133
> &amp;&amp;
> +        icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
> +        <li><code>nd_ra</code> expands to <code>icmp6.type == 134
> &amp;&amp;
> +        icmp6.code == 0 &amp;&amp; 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 e56dc6232..3aa4e5e22 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -7847,6 +7847,255 @@ 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
> +}
> +
> +# Make sure that ovn-controller has installed the corresponding OF Flow.
> +OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c
> "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
> +
> +# 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
> +
> +# Make sure that ovn-controller has installed the corresponding OF Flow.
> +OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c
> "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
> +
> +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
> +# Make sure that ovn-controller has installed the corresponding OF Flow.
> +OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c
> "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
> +
> +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
> +# Make sure that ovn-controller has installed the corresponding OF Flow.
> +OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c
> "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
> +
> +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
> +# Make sure that ovn-controller has not installed any OF Flow for IPv6 ND
> RA.
> +OVS_WAIT_UNTIL([test 0 = `as hv1 ovs-ofctl dump-flows br-int | grep -c
> "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
> +
> +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.5
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>

Patch

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 0d85ec0d2..a994abf78 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -1584,7 +1584,82 @@  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.dst = eth.src;
+eth.src = <var>E</var>;
+ip6.dst = ip6.src;
+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
@@ -1686,7 +1761,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
@@ -1779,7 +1854,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
@@ -1836,7 +1911,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 75f2c6607..3da20d25b 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -129,15 +129,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")        \
@@ -159,11 +161,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]"
@@ -2868,7 +2871,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).
          *
@@ -5319,7 +5326,103 @@  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) {
+            continue;
+        }
+        if (strcmp(address_mode, "slaac") &&
+            strcmp(address_mode, "dhcpv6_stateful") &&
+            strcmp(address_mode, "dhcpv6_stateless")) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_WARN_RL(&rl, "Invalid address mode [%s] defined",
+                         address_mode);
+            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");
+
+        /* As per RFC 2460, 1280 is minimum IPv6 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.dst == 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.dst = eth.src; eth.src = %s; "
+                          "ip6.dst = ip6.src; 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
@@ -5361,7 +5464,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
@@ -5556,7 +5659,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
@@ -5596,7 +5699,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 a077bfb81..fcd878cf2 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "5.8.0",
-    "cksum": "2812300190 16766",
+    "version": "5.8.1",
+    "cksum": "607160660 16929",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -222,6 +222,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 9869d7ed7..8ad53cd7d 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -1332,6 +1332,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 fab3f9de6..2e4f28b96 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -911,6 +911,10 @@ 
         <li><code>nd</code> expands to <code>icmp6.type == {135, 136} &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
         <li><code>nd_ns</code> expands to <code>icmp6.type == 135 &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
         <li><code>nd_na</code> expands to <code>icmp6.type == 136 &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
+        <li><code>nd_rs</code> expands to <code>icmp6.type == 133 &amp;&amp;
+        icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
+        <li><code>nd_ra</code> expands to <code>icmp6.type == 134 &amp;&amp;
+        icmp6.code == 0 &amp;&amp; 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 e56dc6232..3aa4e5e22 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -7847,6 +7847,255 @@  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
+}
+
+# Make sure that ovn-controller has installed the corresponding OF Flow.
+OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+
+# 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
+
+# Make sure that ovn-controller has installed the corresponding OF Flow.
+OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+
+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
+# Make sure that ovn-controller has installed the corresponding OF Flow.
+OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+
+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
+# Make sure that ovn-controller has installed the corresponding OF Flow.
+OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+
+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
+# Make sure that ovn-controller has not installed any OF Flow for IPv6 ND RA.
+OVS_WAIT_UNTIL([test 0 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+
+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