diff --git a/ovn/lib/logical-fields.c b/ovn/lib/logical-fields.c
index b906cf8..1152d9f 100644
--- a/ovn/lib/logical-fields.c
+++ b/ovn/lib/logical-fields.c
@@ -185,7 +185,9 @@ ovn_init_symtab(struct shash *symtab)
     expr_symtab_add_predicate(symtab, "nd_na",
               "icmp6.type == 136 && 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.sll", MFF_ND_SLL,
+              "icmp6.type == {133, 134, 135} && "
+              "icmp6.code == 0 && ip.ttl == 255", false);
     expr_symtab_add_field(symtab, "nd.tll", MFF_ND_TLL, "nd_na", false);
 
     expr_symtab_add_predicate(symtab, "tcp", "ip.proto == 6");
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 861f872..5db7ad3 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -126,9 +126,10 @@ enum ovn_stage {
     PIPELINE_STAGE(ROUTER, IN,  IP_INPUT,    1, "lr_in_ip_input")     \
     PIPELINE_STAGE(ROUTER, IN,  UNSNAT,      2, "lr_in_unsnat")       \
     PIPELINE_STAGE(ROUTER, IN,  DNAT,        3, "lr_in_dnat")         \
-    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,  4, "lr_in_ip_routing")   \
-    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE, 5, "lr_in_arp_resolve")  \
-    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST, 6, "lr_in_arp_request")  \
+    PIPELINE_STAGE(ROUTER, IN,  RS_RSP,      4, "lr_in_rs_rsp")         \
+    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,  5, "lr_in_ip_routing")   \
+    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE, 6, "lr_in_arp_resolve")  \
+    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST, 7, "lr_in_arp_request")  \
                                                                       \
     /* Logical router egress stages. */                               \
     PIPELINE_STAGE(ROUTER, OUT, SNAT,      0, "lr_out_snat")          \
@@ -2579,6 +2580,32 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;");
     }
 
+    /* Ingress table 12: Destination lookup, router solicitation handling
+     * (priority 110). */
+    HMAP_FOR_EACH (od, key_node, datapaths) {
+        if (!od->nbs) {
+            continue;
+        }
+
+        if (!od->n_router_ports) {
+            continue;
+        }
+
+        ds_clear(&actions);
+        for (size_t i = 0; i != od->n_router_ports; i++) {
+            op = od->router_ports[i];
+            if (!op->lsp_addrs || !op->lsp_addrs->n_ipv6_addrs) {
+                continue;
+            }
+            ds_put_format(&actions, "outport = %s; output; ", op->json_key);
+        }
+        if (actions.length != 0) {
+            ds_chomp(&actions, ' ');
+            ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 110, "nd_rs",
+                          ds_cstr(&actions));
+        }
+    }
+
     /* Ingress table 12: Destination lookup, broadcast and multicast handling
      * (priority 100). */
     HMAP_FOR_EACH (op, key_node, ports) {
@@ -3406,7 +3433,61 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                       "ip", "flags.loopback = 1; ct_dnat;");
     }
 
-    /* Logical router ingress table 4: IP Routing.
+    /* Logical router ingress table 4: RS responder, by default goto next.
+     * (priority 0)*/
+    HMAP_FOR_EACH (od, key_node, datapaths) {
+        if (!od->nbr) {
+            continue;
+        }
+
+        ovn_lflow_add(lflows, od, S_ROUTER_IN_RS_RSP, 0, "1", "next;");
+    }
+
+    /* Logical router ingress table 4: RS responder, reply for 'slaac' enabled
+     * router port. (priority 50)*/
+    HMAP_FOR_EACH (op, key_node, ports) {
+        if (!op->nbrp || op->nbrp->peer
+            || !op->peer
+            || !op->nbrp->slaac
+            || !*op->nbrp->slaac) {
+            continue;
+        }
+
+        const char *mtu_s = smap_get(&op->peer->od->nbs->other_config, "mtu");
+        if (!mtu_s) {
+            continue;
+        }
+        int mtu = atoi(mtu_s);
+        /* Per https://tools.ietf.org/html/rfc2460, consider 1280 as minimum
+         * valid MTU value for IPv6 on a logical switch. */
+        if (mtu < 1280) {
+            continue;
+        }
+        ds_clear(&match);
+        ds_put_format(&match, "inport == %s", op->json_key);
+        ds_put_cstr(&match, " && ip6.dst == ff02::2 && nd_rs");
+        ds_clear(&actions);
+        ds_put_format(&actions, "nd_ra{slaac(%s,", op->lrp_networks.ea_s);
+        ds_put_format(&actions, "%d,", mtu);
+        size_t actions_len = actions.length;
+        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;
+            }
+            ds_put_format(&actions, "%s/%u,",
+                          op->lrp_networks.ipv6_addrs[i].network_s,
+                          op->lrp_networks.ipv6_addrs[i].plen);
+        }
+        if (actions.length != actions_len) {
+            ds_chomp(&actions, ',');
+            ds_put_cstr(&actions, "); outport = inport; flags.loopback = 1;"
+                                  " output;};");
+            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_RS_RSP, 50,
+                          ds_cstr(&match), ds_cstr(&actions));
+        }
+    }
+
+    /* Logical router ingress table 5: IP Routing.
      *
      * A packet that arrives at this table is an IP packet that should be
      * routed to the address in 'ip[46].dst'. This table sets outport to
@@ -3448,7 +3529,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
 
     /* XXX destination unreachable */
 
-    /* Local router ingress table 5: ARP Resolution.
+    /* Local router ingress table 6: ARP Resolution.
      *
      * Any packet that reaches this table is an IP packet whose next-hop IP
      * address is in reg0. (ip4.dst is the final destination.) This table
@@ -3645,7 +3726,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                       "get_nd(outport, xxreg0); next;");
     }
 
-    /* Local router ingress table 6: ARP request.
+    /* Local router ingress table 7: ARP request.
      *
      * In the common case where the Ethernet destination has been resolved,
      * this table outputs the packet (priority 0).  Otherwise, it composes
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index 660db76..c802571 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Northbound",
-    "version": "5.3.0",
-    "cksum": "1305504870 9051",
+    "version": "5.4.0",
+    "cksum": "3777848460 9126",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -150,6 +150,7 @@
                 "mac": {"type": "string"},
                 "peer": {"type": {"key": "string", "min": 0, "max": 1}},
                 "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
+                "slaac": {"type": {"key": "boolean", "min": 0, "max": 1}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 4ce295a..c288f4a 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -132,6 +132,10 @@
         column="addresses"/> column to request dynamic address assignment for a
         particular port.
       </column>
+
+      <column name="other_config" key="mtu">
+        Logical Switch MTU.
+      </column>
     </group>
 
     <group title="Common Columns">
@@ -904,6 +908,13 @@
       port has all ingress and egress traffic dropped.
     </column>
 
+    <column name="slaac">
+      Setting <code>true</code> specifies the logical switch subnets behind
+      this router port IPv6 networks are going to use SLAAC as IPv6 address
+      configuration and RA mode. Otherwise set this column to
+      <code>false</code>, or this router port has no IPv6 networks.
+    </column>
+
     <group title="Attachment">
       <p>
         A given router port serves one of two purposes:
diff --git a/tests/ovn.at b/tests/ovn.at
index 33ace16..3352434 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -748,7 +748,7 @@ get_nd(inport, outport); => Cannot use string field outport where numeric field
 get_nd(xxreg0, ip6.dst); => Cannot use numeric field xxreg0 where string field is required.
 
 # put_nd
-put_nd(inport, nd.target, nd.sll); => actions=push:NXM_NX_XXREG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ND_SLL[],push:NXM_NX_ND_TARGET[],pop:NXM_NX_XXREG0[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.04.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_XXREG0[], prereqs=((icmp6.type == 0x85 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd)) || (icmp6.type == 0x86 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd)) || (icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd)) || (icmp6.type == 0x88 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd))) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd
 ) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd)
+put_nd(inport, nd.target, nd.sll); => actions=push:NXM_NX_XXREG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ND_SLL[],push:NXM_NX_ND_TARGET[],pop:NXM_NX_XXREG0[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.04.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_XXREG0[], prereqs=((icmp6.type == 0x85 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd)) || (icmp6.type == 0x86 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd)) || (icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd)) || (icmp6.type == 0x88 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd))) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd) && ((icmp6.type == 0x85 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86
 dd)) || (icmp6.type == 0x86 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd)) || (icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd))) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd)
 
