diff --git a/lib/packets.c b/lib/packets.c
index ed60891..b5ea5eb 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -1162,6 +1162,36 @@ compose_arp(struct dp_packet *b, uint16_t arp_op,
     dp_packet_set_l3(b, arp);
 }
 
+void
+compose_nd(struct dp_packet *b, const struct eth_addr eth_src,
+           struct in6_addr * ipv6_src, struct in6_addr * ipv6_dst)
+{
+    struct in6_addr sn_addr;
+    struct eth_addr eth_dst;
+    struct ovs_nd_msg *ns;
+    struct ovs_nd_opt *nd_opt;
+
+    in6_addr_solicited_node(&sn_addr, ipv6_dst);
+    ipv6_multicast_to_ethernet(&eth_dst, &sn_addr);
+
+    eth_compose(b, eth_dst, eth_src, ETH_TYPE_IPV6,
+                IPV6_HEADER_LEN + ICMP6_HEADER_LEN + ND_OPT_LEN);
+    packet_set_ipv6(b, IPPROTO_ICMPV6,
+                    ALIGNED_CAST(ovs_be32 *, ipv6_src->s6_addr),
+                    ALIGNED_CAST(ovs_be32 *, sn_addr.s6_addr),
+                    0, 0, 255);
+
+    ns = dp_packet_l4(b);
+    nd_opt = &ns->options[0];
+
+    ns->icmph.icmp6_type = ND_NEIGHBOR_SOLICIT;
+    ns->icmph.icmp6_code = 0;
+
+    nd_opt->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
+    packet_set_nd(b, ALIGNED_CAST(ovs_be32 *, ipv6_dst->s6_addr),
+                  eth_src, eth_addr_zero);
+}
+
 uint32_t
 packet_csum_pseudoheader(const struct ip_header *ip)
 {
diff --git a/lib/packets.h b/lib/packets.h
index 6d30c9b..9f39c05 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -918,6 +918,28 @@ static inline bool ipv6_addr_is_multicast(const struct in6_addr *ip) {
 }
 
 static inline void
+in6_addr_solicited_node(struct in6_addr *addr, const struct in6_addr *ip6)
+{
+    union ovs_16aligned_in6_addr *taddr = (void *) addr;
+    memset(taddr->be16, 0, sizeof(taddr->be16));
+    taddr->be16[0] = htons(0xff02);
+    taddr->be16[5] = htons(0x1);
+    taddr->be16[6] = htons(0xff00);
+    memcpy(&addr->s6_addr[13], &ip6->s6_addr[13], 3);
+}
+
+static inline void
+ipv6_multicast_to_ethernet(struct eth_addr *eth, const struct in6_addr *ip6)
+{
+    eth->ea[0] = 0x33;
+    eth->ea[1] = 0x33;
+    eth->ea[2] = ip6->s6_addr[12];
+    eth->ea[3] = ip6->s6_addr[13];
+    eth->ea[4] = ip6->s6_addr[14];
+    eth->ea[5] = ip6->s6_addr[15];
+}
+
+static inline void
 in6_addr_set_mapped_ipv4(struct in6_addr *addr, ovs_be32 ip4)
 {
     union ovs_16aligned_in6_addr *taddr = (void *) addr;
@@ -1005,6 +1027,8 @@ void compose_arp(struct dp_packet *, uint16_t arp_op,
                  const struct eth_addr arp_sha,
                  const struct eth_addr arp_tha, bool broadcast,
                  ovs_be32 arp_spa, ovs_be32 arp_tpa);
+void compose_nd(struct dp_packet *, const struct eth_addr eth_src,
+                struct in6_addr *, struct in6_addr *);
 uint32_t packet_csum_pseudoheader(const struct ip_header *);
 
 #endif /* packets.h */
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 8185d47..2abdd3a 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -54,6 +54,7 @@
 #include "ofproto/ofproto-dpif-sflow.h"
 #include "ofproto/ofproto-dpif.h"
 #include "ofproto/ofproto-provider.h"
+#include "packets.h"
 #include "ovs-router.h"
 #include "tnl-ports.h"
 #include "tunnel.h"
@@ -2659,21 +2660,24 @@ process_special(struct xlate_ctx *ctx, const struct xport *xport)
 
 static int
 tnl_route_lookup_flow(const struct flow *oflow,
-                      ovs_be32 *ip, struct xport **out_port)
+                      struct in6_addr *ip, struct xport **out_port)
 {
     char out_dev[IFNAMSIZ];
     struct xbridge *xbridge;
     struct xlate_cfg *xcfg;
-    ovs_be32 gw;
+    struct in6_addr gw;
+    struct in6_addr dst;
 
-    if (!ovs_router_lookup4(oflow->tunnel.ip_dst, out_dev, &gw)) {
+    dst = flow_tnl_dst(&oflow->tunnel);
+    if (!ovs_router_lookup(&dst, out_dev, &gw)) {
         return -ENOENT;
     }
 
-    if (gw) {
+    if (ipv6_addr_is_set(&gw) &&
+        (!IN6_IS_ADDR_V4MAPPED(&gw) || in6_addr_get_mapped_ipv4(&gw))) {
         *ip = gw;
     } else {
-        *ip = oflow->tunnel.ip_dst;
+        *ip = dst;
     }
 
     xcfg = ovsrcu_get(struct xlate_cfg *, &xcfgp);
@@ -2714,6 +2718,19 @@ compose_table_xlate(struct xlate_ctx *ctx, const struct xport *out_dev,
 }
 
 static void
+tnl_send_nd_request(struct xlate_ctx *ctx, const struct xport *out_dev,
+                     const struct eth_addr eth_src,
+                     struct in6_addr * ipv6_src, struct in6_addr * ipv6_dst)
+{
+    struct dp_packet packet;
+
+    dp_packet_init(&packet, 0);
+    compose_nd(&packet, eth_src, ipv6_src, ipv6_dst);
+    compose_table_xlate(ctx, out_dev, &packet);
+    dp_packet_uninit(&packet);
+}
+
+static void
 tnl_send_arp_request(struct xlate_ctx *ctx, const struct xport *out_dev,
                      const struct eth_addr eth_src,
                      ovs_be32 ip_src, ovs_be32 ip_dst)
@@ -2734,19 +2751,24 @@ build_tunnel_send(struct xlate_ctx *ctx, const struct xport *xport,
 {
     struct ovs_action_push_tnl tnl_push_data;
     struct xport *out_dev = NULL;
-    ovs_be32 s_ip, d_ip = 0;
-    struct in6_addr s_ip6;
+    ovs_be32 s_ip = 0, d_ip = 0;
+    struct in6_addr s_ip6 = in6addr_any;
+    struct in6_addr d_ip6 = in6addr_any;
     struct eth_addr smac;
     struct eth_addr dmac;
     int err;
+    char buf_sip6[INET6_ADDRSTRLEN];
+    char buf_dip6[INET6_ADDRSTRLEN];
 
-    err = tnl_route_lookup_flow(flow, &d_ip, &out_dev);
+    err = tnl_route_lookup_flow(flow, &d_ip6, &out_dev);
     if (err) {
         xlate_report(ctx, "native tunnel routing failed");
         return err;
     }
-    xlate_report(ctx, "tunneling to "IP_FMT" via %s",
-                 IP_ARGS(d_ip), netdev_get_name(out_dev->netdev));
+
+    xlate_report(ctx, "tunneling to %s via %s",
+                 ipv6_string_mapped(buf_dip6, &d_ip6),
+                 netdev_get_name(out_dev->netdev));
 
     /* Use mac addr of bridge port of the peer. */
     err = netdev_get_etheraddr(out_dev->netdev, &smac);
@@ -2755,34 +2777,49 @@ build_tunnel_send(struct xlate_ctx *ctx, const struct xport *xport,
         return err;
     }
 
-    err = netdev_get_in4(out_dev->netdev, (struct in_addr *) &s_ip, NULL);
-    if (err) {
-        xlate_report(ctx, "tunnel output device lacks IPv4 address");
-        return err;
+    d_ip = in6_addr_get_mapped_ipv4(&d_ip6);
+    if (d_ip) {
+        err = netdev_get_in4(out_dev->netdev, (struct in_addr *) &s_ip, NULL);
+        if (err) {
+            xlate_report(ctx, "tunnel output device lacks IPv4 address");
+            return err;
+        }
+        in6_addr_set_mapped_ipv4(&s_ip6, s_ip);
+    } else {
+        err = netdev_get_in6(out_dev->netdev, &s_ip6);
+        if (err) {
+            xlate_report(ctx, "tunnel output device lacks IPv6 address");
+            return err;
+        }
     }
 
-    err = tnl_arp_lookup(out_dev->xbridge->name, d_ip, &dmac);
+    err = tnl_neigh_lookup(out_dev->xbridge->name, &d_ip6, &dmac);
     if (err) {
-        xlate_report(ctx, "ARP cache miss for "IP_FMT" on bridge %s, "
-                     "sending ARP request",
-                     IP_ARGS(d_ip), out_dev->xbridge->name);
-        tnl_send_arp_request(ctx, out_dev, smac, s_ip, d_ip);
+        xlate_report(ctx, "neighbor cache miss for %s on bridge %s, "
+                     "sending %s request",
+                     buf_dip6, out_dev->xbridge->name, d_ip ? "ARP" : "ND");
+        if (d_ip) {
+            tnl_send_arp_request(ctx, out_dev, smac, s_ip, d_ip);
+        } else {
+            tnl_send_nd_request(ctx, out_dev, smac, &s_ip6, &d_ip6);
+        }
         return err;
     }
+
     if (ctx->xin->xcache) {
         struct xc_entry *entry;
 
         entry = xlate_cache_add_entry(ctx->xin->xcache, XC_TNL_NEIGH);
         ovs_strlcpy(entry->u.tnl_neigh_cache.br_name, out_dev->xbridge->name,
                     sizeof entry->u.tnl_neigh_cache.br_name);
-        in6_addr_set_mapped_ipv4(&entry->u.tnl_neigh_cache.d_ipv6, d_ip);
+        entry->u.tnl_neigh_cache.d_ipv6 = d_ip6;
     }
 
-    xlate_report(ctx, "tunneling from "ETH_ADDR_FMT" "IP_FMT
-                 " to "ETH_ADDR_FMT" "IP_FMT,
-                 ETH_ADDR_ARGS(smac), IP_ARGS(s_ip),
-                 ETH_ADDR_ARGS(dmac), IP_ARGS(d_ip));
-    in6_addr_set_mapped_ipv4(&s_ip6, s_ip);
+    xlate_report(ctx, "tunneling from "ETH_ADDR_FMT" %s"
+                 " to "ETH_ADDR_FMT" %s",
+                 ETH_ADDR_ARGS(smac), ipv6_string_mapped(buf_sip6, &s_ip6),
+                 ETH_ADDR_ARGS(dmac), buf_dip6);
+
     err = tnl_port_build_header(xport->ofport, flow,
                                 dmac, smac, &s_ip6, &tnl_push_data);
     if (err) {
@@ -2983,7 +3020,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
     }
 
     if (xport->is_tunnel) {
-        ovs_be32 dst;
+        struct in6_addr dst;
          /* Save tunnel metadata so that changes made due to
           * the Logical (tunnel) Port are not visible for any further
           * matches, while explicit set actions on tunnel metadata are.
@@ -2994,8 +3031,8 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
             xlate_report(ctx, "Tunneling decided against output");
             goto out; /* restore flow_nw_tos */
         }
-        dst = in6_addr_get_mapped_ipv4(&ctx->orig_tunnel_ipv6_dst);
-        if (flow->tunnel.ip_dst == dst) {
+        dst = flow_tnl_dst(&flow->tunnel);
+        if (ipv6_addr_equals(&dst, &ctx->orig_tunnel_ipv6_dst)) {
             xlate_report(ctx, "Not tunneling to our own address");
             goto out; /* restore flow_nw_tos */
         }
@@ -4908,6 +4945,7 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
         .xin = xin,
         .xout = xout,
         .base_flow = *flow,
+        .orig_tunnel_ipv6_dst = flow_tnl_dst(&flow->tunnel),
         .xbridge = xbridge,
         .stack = OFPBUF_STUB_INITIALIZER(stack_stub),
         .rule = xin->rule,
@@ -4937,7 +4975,6 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
         .action_set_has_group = false,
         .action_set = OFPBUF_STUB_INITIALIZER(action_set_stub),
     };
-    in6_addr_set_mapped_ipv4(&ctx.orig_tunnel_ipv6_dst, flow->tunnel.ip_dst);
 
     /* 'base_flow' reflects the packet as it came in, but we need it to reflect
      * the packet as the datapath will treat it for output actions:
