diff mbox

[ovs-dev,09/15] tunneling: extend flow_tnl with ipv6 addresses

Message ID 1445534948-10538-10-git-send-email-cascardo@redhat.com
State Changes Requested
Headers show

Commit Message

Thadeu Lima de Souza Cascardo Oct. 22, 2015, 5:29 p.m. UTC
From: Jiri Benc <jbenc-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>

Note that because there's been no prerequisite on the outer protocol,
we cannot add it now. Instead, treat the ipv4 and ipv6 dst fields in the way
that either both are null, or at most one of them is non-null.

[cascardo: abstract testing either dst with flow_tnl_dst_is_set]

Signed-off-by: Jiri Benc <jbenc@redhat.com>
Signed-off-by: Thadeu Lima de Souza Cascardo <cascardo@redhat.com>
Co-authored-by: Thadeu Lima de Souza Cascardo <cascardo@redhat.com>
---
 lib/dpif.c                 |  6 ++++--
 lib/flow.c                 | 18 +++++++++++++-----
 lib/match.c                | 34 ++++++++++++++++++++++++++++++++++
 lib/match.h                |  6 ++++++
 lib/odp-util.c             | 39 +++++++++++++++++++++++++++++++++++----
 lib/odp-util.h             |  6 ++++--
 lib/packets.c              | 22 ++++++++++++++++++++++
 lib/packets.h              | 18 ++++++++++++++++--
 ofproto/ofproto-dpif-rid.c |  3 ++-
 ofproto/ofproto-dpif-rid.h |  2 +-
 ofproto/tunnel.c           | 23 +++++++++++++++++------
 ofproto/tunnel.h           |  2 +-
 12 files changed, 155 insertions(+), 24 deletions(-)

Comments

Ben Pfaff Nov. 10, 2015, 10:34 p.m. UTC | #1
On Thu, Oct 22, 2015 at 03:29:02PM -0200, Thadeu Lima de Souza Cascardo wrote:
> From: Jiri Benc <jbenc-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> 
> Note that because there's been no prerequisite on the outer protocol,
> we cannot add it now. Instead, treat the ipv4 and ipv6 dst fields in the way
> that either both are null, or at most one of them is non-null.
> 
> [cascardo: abstract testing either dst with flow_tnl_dst_is_set]
> 
> Signed-off-by: Jiri Benc <jbenc@redhat.com>
> Signed-off-by: Thadeu Lima de Souza Cascardo <cascardo@redhat.com>
> Co-authored-by: Thadeu Lima de Souza Cascardo <cascardo@redhat.com>

One wonders whether it would be worth merging the ip_dst and ipv6_dst
fields in struct flow_tnl, using IPv6-mapped IPv4 addresses for the IPv4
case.  Did you consider that?
Thadeu Lima de Souza Cascardo Nov. 11, 2015, 1:49 p.m. UTC | #2
On Tue, Nov 10, 2015 at 02:34:02PM -0800, Ben Pfaff wrote:
> On Thu, Oct 22, 2015 at 03:29:02PM -0200, Thadeu Lima de Souza Cascardo wrote:
> > From: Jiri Benc <jbenc-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> > 
> > Note that because there's been no prerequisite on the outer protocol,
> > we cannot add it now. Instead, treat the ipv4 and ipv6 dst fields in the way
> > that either both are null, or at most one of them is non-null.
> > 
> > [cascardo: abstract testing either dst with flow_tnl_dst_is_set]
> > 
> > Signed-off-by: Jiri Benc <jbenc@redhat.com>
> > Signed-off-by: Thadeu Lima de Souza Cascardo <cascardo@redhat.com>
> > Co-authored-by: Thadeu Lima de Souza Cascardo <cascardo@redhat.com>
> 
> One wonders whether it would be worth merging the ip_dst and ipv6_dst
> fields in struct flow_tnl, using IPv6-mapped IPv4 addresses for the IPv4
> case.  Did you consider that?

Yes, I tried that, and had to go back to the way Jiri implemented it. The main
problem was meta-flow and match. I tried using a union, so meta-flow.inc auto
generated code would still work. However, when setting IPv4 tunnel addresses,
the prefix would be all zeroes, not 0:0:0:0:0:0:ffff::. I didn't manage to make
that work. Maybe now that I have put my head a little more into meta-flow and
match code, I may find a way to do that. I will take a new look and see if I can
come back with new code or a better commit message, explaining why that would
not be the best course in this case.

When I went back to the original implementation, I knew an explanation about not
using IPv4-mapped addresses was due, but I must have forgotten after working on
the testsuite and many rebases.

Cascardo.
Ben Pfaff Nov. 23, 2015, 6:16 p.m. UTC | #3
On Wed, Nov 11, 2015 at 11:49:49AM -0200, Thadeu Lima de Souza Cascardo wrote:
> On Tue, Nov 10, 2015 at 02:34:02PM -0800, Ben Pfaff wrote:
> > On Thu, Oct 22, 2015 at 03:29:02PM -0200, Thadeu Lima de Souza Cascardo wrote:
> > > From: Jiri Benc <jbenc-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> > > 
> > > Note that because there's been no prerequisite on the outer protocol,
> > > we cannot add it now. Instead, treat the ipv4 and ipv6 dst fields in the way
> > > that either both are null, or at most one of them is non-null.
> > > 
> > > [cascardo: abstract testing either dst with flow_tnl_dst_is_set]
> > > 
> > > Signed-off-by: Jiri Benc <jbenc@redhat.com>
> > > Signed-off-by: Thadeu Lima de Souza Cascardo <cascardo@redhat.com>
> > > Co-authored-by: Thadeu Lima de Souza Cascardo <cascardo@redhat.com>
> > 
> > One wonders whether it would be worth merging the ip_dst and ipv6_dst
> > fields in struct flow_tnl, using IPv6-mapped IPv4 addresses for the IPv4
> > case.  Did you consider that?
> 
> Yes, I tried that, and had to go back to the way Jiri implemented it. The main
> problem was meta-flow and match. I tried using a union, so meta-flow.inc auto
> generated code would still work. However, when setting IPv4 tunnel addresses,
> the prefix would be all zeroes, not 0:0:0:0:0:0:ffff::. I didn't manage to make
> that work. Maybe now that I have put my head a little more into meta-flow and
> match code, I may find a way to do that. I will take a new look and see if I can
> come back with new code or a better commit message, explaining why that would
> not be the best course in this case.
> 
> When I went back to the original implementation, I knew an explanation about not
> using IPv4-mapped addresses was due, but I must have forgotten after working on
> the testsuite and many rebases.

That all seems reasonable.  I'll look forward to the next revision (I
don't think I've seen one yet but I may just be behind, as usual).
diff mbox

Patch

diff --git a/lib/dpif.c b/lib/dpif.c
index 3d6ac6e..31ace47 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1107,8 +1107,10 @@  dpif_execute_helper_cb(void *aux_, struct dp_packet **packets, int cnt,
         struct ofpbuf execute_actions;
         uint64_t stub[256 / 8];
         struct pkt_metadata *md = &packet->md;
+        bool dst_set;
 
-        if (md->tunnel.ip_dst) {
+        dst_set = flow_tnl_dst_is_set(&md->tunnel);
+        if (dst_set) {
             /* The Linux kernel datapath throws away the tunnel information
              * that we supply as metadata.  We have to use a "set" action to
              * supply it. */
@@ -1130,7 +1132,7 @@  dpif_execute_helper_cb(void *aux_, struct dp_packet **packets, int cnt,
         aux->error = dpif_execute(aux->dpif, &execute);
         log_execute_message(aux->dpif, &execute, true, aux->error);
 
-        if (md->tunnel.ip_dst) {
+        if (dst_set) {
             ofpbuf_uninit(&execute_actions);
         }
         break;
diff --git a/lib/flow.c b/lib/flow.c
index d5dcb92..3404bb0 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -449,7 +449,7 @@  miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
     uint8_t nw_frag, nw_tos, nw_ttl, nw_proto;
 
     /* Metadata. */
-    if (md->tunnel.ip_dst) {
+    if (flow_tnl_dst_is_set(&md->tunnel)) {
         miniflow_push_words(mf, tunnel, &md->tunnel,
                             offsetof(struct flow_tnl, metadata) /
                             sizeof(uint64_t));
@@ -815,12 +815,18 @@  flow_get_metadata(const struct flow *flow, struct match *flow_metadata)
         match_set_tun_flags(flow_metadata,
                             flow->tunnel.flags & FLOW_TNL_PUB_F_MASK);
     }
-    if (flow->tunnel.ip_src != htonl(0)) {
+    if (flow->tunnel.ip_src) {
         match_set_tun_src(flow_metadata, flow->tunnel.ip_src);
     }
-    if (flow->tunnel.ip_dst != htonl(0)) {
+    if (flow->tunnel.ip_dst) {
         match_set_tun_dst(flow_metadata, flow->tunnel.ip_dst);
     }
+    if (ipv6_addr_is_set(&flow->tunnel.ipv6_src)) {
+        match_set_tun_ipv6_src(flow_metadata, &flow->tunnel.ipv6_src);
+    }
+    if (ipv6_addr_is_set(&flow->tunnel.ipv6_dst)) {
+        match_set_tun_ipv6_dst(flow_metadata, &flow->tunnel.ipv6_dst);
+    }
     if (flow->tunnel.gbp_id != htons(0)) {
         match_set_tun_gbp_id(flow_metadata, flow->tunnel.gbp_id);
     }
@@ -1203,12 +1209,14 @@  void flow_wildcards_init_for_packet(struct flow_wildcards *wc,
     /* Update this function whenever struct flow changes. */
     BUILD_ASSERT_DECL(FLOW_WC_SEQ == 34);
 
-    if (flow->tunnel.ip_dst) {
+    if (flow_tnl_dst_is_set(&flow->tunnel)) {
         if (flow->tunnel.flags & FLOW_TNL_F_KEY) {
             WC_MASK_FIELD(wc, tunnel.tun_id);
         }
         WC_MASK_FIELD(wc, tunnel.ip_src);
         WC_MASK_FIELD(wc, tunnel.ip_dst);
+        WC_MASK_FIELD(wc, tunnel.ipv6_src);
+        WC_MASK_FIELD(wc, tunnel.ipv6_dst);
         WC_MASK_FIELD(wc, tunnel.flags);
         WC_MASK_FIELD(wc, tunnel.ip_tos);
         WC_MASK_FIELD(wc, tunnel.ip_ttl);
@@ -1320,7 +1328,7 @@  flow_wc_map(const struct flow *flow, struct flowmap *map)
 
     flowmap_init(map);
 
-    if (flow->tunnel.ip_dst) {
+    if (flow_tnl_dst_is_set(&flow->tunnel)) {
         FLOWMAP_SET__(map, tunnel, offsetof(struct flow_tnl, metadata));
         if (!(flow->tunnel.flags & FLOW_TNL_F_UDPIF)) {
             if (flow->tunnel.metadata.present.map) {
diff --git a/lib/match.c b/lib/match.c
index d22772b..327a4dc 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -191,6 +191,36 @@  match_set_tun_dst_masked(struct match *match, ovs_be32 dst, ovs_be32 mask)
 }
 
 void
+match_set_tun_ipv6_src(struct match *match, const struct in6_addr *src)
+{
+    match->flow.tunnel.ipv6_src = *src;
+    match->wc.masks.tunnel.ipv6_src = in6addr_exact;
+}
+
+void
+match_set_tun_ipv6_src_masked(struct match *match, const struct in6_addr *src,
+                              const struct in6_addr *mask)
+{
+    match->flow.tunnel.ipv6_src = ipv6_addr_bitand(src, mask);
+    match->wc.masks.tunnel.ipv6_src = *mask;
+}
+
+void
+match_set_tun_ipv6_dst(struct match *match, const struct in6_addr *dst)
+{
+    match->flow.tunnel.ipv6_dst = *dst;
+    match->wc.masks.tunnel.ipv6_dst = in6addr_exact;
+}
+
+void
+match_set_tun_ipv6_dst_masked(struct match *match, const struct in6_addr *dst,
+                              const struct in6_addr *mask)
+{
+    match->flow.tunnel.ipv6_dst = ipv6_addr_bitand(dst, mask);
+    match->wc.masks.tunnel.ipv6_dst = *mask;
+}
+
+void
 match_set_tun_ttl(struct match *match, uint8_t ttl)
 {
     match_set_tun_ttl_masked(match, ttl, UINT8_MAX);
@@ -949,6 +979,10 @@  format_flow_tunnel(struct ds *s, const struct match *match)
     format_be64_masked(s, "tun_id", tnl->tun_id, wc->masks.tunnel.tun_id);
     format_ip_netmask(s, "tun_src", tnl->ip_src, wc->masks.tunnel.ip_src);
     format_ip_netmask(s, "tun_dst", tnl->ip_dst, wc->masks.tunnel.ip_dst);
+    format_ipv6_netmask(s, "tun_ipv6_src", &tnl->ipv6_src,
+                        &wc->masks.tunnel.ipv6_src);
+    format_ipv6_netmask(s, "tun_ipv6_dst", &tnl->ipv6_dst,
+                        &wc->masks.tunnel.ipv6_dst);
 
     if (wc->masks.tunnel.gbp_id) {
         format_be16_masked(s, "tun_gbp_id", tnl->gbp_id,
diff --git a/lib/match.h b/lib/match.h
index 75e5925..650a203 100644
--- a/lib/match.h
+++ b/lib/match.h
@@ -70,6 +70,12 @@  void match_set_tun_src(struct match *match, ovs_be32 src);
 void match_set_tun_src_masked(struct match *match, ovs_be32 src, ovs_be32 mask);
 void match_set_tun_dst(struct match *match, ovs_be32 dst);
 void match_set_tun_dst_masked(struct match *match, ovs_be32 dst, ovs_be32 mask);
+void match_set_tun_ipv6_src(struct match *, const struct in6_addr *);
+void match_set_tun_ipv6_src_masked(struct match *, const struct in6_addr *,
+                                   const struct in6_addr *);
+void match_set_tun_ipv6_dst(struct match *, const struct in6_addr *);
+void match_set_tun_ipv6_dst_masked(struct match *, const struct in6_addr *,
+                                   const struct in6_addr *);
 void match_set_tun_ttl(struct match *match, uint8_t ttl);
 void match_set_tun_ttl_masked(struct match *match, uint8_t ttl, uint8_t mask);
 void match_set_tun_tos(struct match *match, uint8_t tos);
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 752e2ea..870e9a2 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -1370,6 +1370,8 @@  static const struct attr_len_tbl ovs_tun_key_attr_lens[OVS_TUNNEL_KEY_ATTR_MAX +
     [OVS_TUNNEL_KEY_ATTR_VXLAN_OPTS]    = { .len = ATTR_LEN_NESTED,
                                             .next = ovs_vxlan_ext_attr_lens ,
                                             .next_max = OVS_VXLAN_EXT_MAX},
+    [OVS_TUNNEL_KEY_ATTR_IPV6_SRC]      = { .len = 16 },
+    [OVS_TUNNEL_KEY_ATTR_IPV6_DST]      = { .len = 16 },
 };
 
 static const struct attr_len_tbl ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] = {
@@ -1482,6 +1484,12 @@  odp_tun_key_from_attr__(const struct nlattr *attr,
         case OVS_TUNNEL_KEY_ATTR_IPV4_DST:
             tun->ip_dst = nl_attr_get_be32(a);
             break;
+        case OVS_TUNNEL_KEY_ATTR_IPV6_SRC:
+            tun->ipv6_src = nl_attr_get_in6_addr(a);
+            break;
+        case OVS_TUNNEL_KEY_ATTR_IPV6_DST:
+            tun->ipv6_dst = nl_attr_get_in6_addr(a);
+            break;
         case OVS_TUNNEL_KEY_ATTR_TOS:
             tun->ip_tos = nl_attr_get_u8(a);
             break;
@@ -1574,6 +1582,12 @@  tun_key_to_attr(struct ofpbuf *a, const struct flow_tnl *tun_key,
     if (tun_key->ip_dst) {
         nl_msg_put_be32(a, OVS_TUNNEL_KEY_ATTR_IPV4_DST, tun_key->ip_dst);
     }
+    if (ipv6_addr_is_set(&tun_key->ipv6_src)) {
+        nl_msg_put_in6_addr(a, OVS_TUNNEL_KEY_ATTR_IPV6_SRC, &tun_key->ipv6_src);
+    }
+    if (ipv6_addr_is_set(&tun_key->ipv6_dst)) {
+        nl_msg_put_in6_addr(a, OVS_TUNNEL_KEY_ATTR_IPV6_DST, &tun_key->ipv6_dst);
+    }
     if (tun_key->ip_tos) {
         nl_msg_put_u8(a, OVS_TUNNEL_KEY_ATTR_TOS, tun_key->ip_tos);
     }
@@ -2129,6 +2143,20 @@  format_odp_tun_attr(const struct nlattr *attr, const struct nlattr *mask_attr,
             format_ipv4(ds, "dst", nl_attr_get_be32(a),
                         ma ? nl_attr_get(ma) : NULL, verbose);
             break;
+        case OVS_TUNNEL_KEY_ATTR_IPV6_SRC: {
+            struct in6_addr ipv6_src;
+            ipv6_src = nl_attr_get_in6_addr(a);
+            format_in6_addr(ds, "ipv6_src", &ipv6_src,
+                            ma ? nl_attr_get(ma) : NULL, verbose);
+            break;
+        }
+        case OVS_TUNNEL_KEY_ATTR_IPV6_DST: {
+            struct in6_addr ipv6_dst;
+            ipv6_dst = nl_attr_get_in6_addr(a);
+            format_in6_addr(ds, "ipv6_dst", &ipv6_dst,
+                            ma ? nl_attr_get(ma) : NULL, verbose);
+            break;
+        }
         case OVS_TUNNEL_KEY_ATTR_TOS:
             format_u8x(ds, "tos", nl_attr_get_u8(a),
                        ma ? nl_attr_get(ma) : NULL, verbose);
@@ -3564,6 +3592,8 @@  parse_odp_key_mask_attr(const char *s, const struct simap *port_names,
         SCAN_FIELD_NESTED("tun_id=", ovs_be64, be64, OVS_TUNNEL_KEY_ATTR_ID);
         SCAN_FIELD_NESTED("src=", ovs_be32, ipv4, OVS_TUNNEL_KEY_ATTR_IPV4_SRC);
         SCAN_FIELD_NESTED("dst=", ovs_be32, ipv4, OVS_TUNNEL_KEY_ATTR_IPV4_DST);
+        SCAN_FIELD_NESTED("ipv6_src=", struct in6_addr, in6_addr, OVS_TUNNEL_KEY_ATTR_IPV6_SRC);
+        SCAN_FIELD_NESTED("ipv6_dst=", struct in6_addr, in6_addr, OVS_TUNNEL_KEY_ATTR_IPV6_DST);
         SCAN_FIELD_NESTED("tos=", uint8_t, u8, OVS_TUNNEL_KEY_ATTR_TOS);
         SCAN_FIELD_NESTED("ttl=", uint8_t, u8, OVS_TUNNEL_KEY_ATTR_TTL);
         SCAN_FIELD_NESTED("tp_src=", ovs_be16, be16, OVS_TUNNEL_KEY_ATTR_TP_SRC);
@@ -3787,7 +3817,7 @@  odp_flow_key_from_flow__(const struct odp_flow_key_parms *parms,
 
     nl_msg_put_u32(buf, OVS_KEY_ATTR_PRIORITY, data->skb_priority);
 
-    if (flow->tunnel.ip_dst || export_mask) {
+    if (flow_tnl_dst_is_set(&flow->tunnel) || export_mask) {
         tun_key_to_attr(buf, &data->tunnel, &parms->flow->tunnel,
                         parms->key_buf);
     }
@@ -3984,7 +4014,7 @@  odp_key_from_pkt_metadata(struct ofpbuf *buf, const struct pkt_metadata *md)
 {
     nl_msg_put_u32(buf, OVS_KEY_ATTR_PRIORITY, md->skb_priority);
 
-    if (md->tunnel.ip_dst) {
+    if (flow_tnl_dst_is_set(&md->tunnel)) {
         tun_key_to_attr(buf, &md->tunnel, &md->tunnel, NULL);
     }
 
@@ -4895,8 +4925,9 @@  void
 commit_odp_tunnel_action(const struct flow *flow, struct flow *base,
                          struct ofpbuf *odp_actions)
 {
-    /* A valid IPV4_TUNNEL must have non-zero ip_dst. */
-    if (flow->tunnel.ip_dst) {
+    /* A valid IPV4_TUNNEL must have non-zero ip_dst; a valid IPv6 tunnel
+     * must have non-zero ipv6_dst. */
+    if (flow_tnl_dst_is_set(&flow->tunnel)) {
         if (!memcmp(&base->tunnel, &flow->tunnel, sizeof base->tunnel)) {
             return;
         }
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 1e49893..8d60d6e 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -109,6 +109,8 @@  void odp_portno_names_destroy(struct hmap *portno_names);
  *  - OVS_TUNNEL_KEY_ATTR_ID             8    --     4     12
  *  - OVS_TUNNEL_KEY_ATTR_IPV4_SRC       4    --     4      8
  *  - OVS_TUNNEL_KEY_ATTR_IPV4_DST       4    --     4      8
+ *  - OVS_TUNNEL_KEY_ATTR_IPV6_SRC       16   --     4     20
+ *  - OVS_TUNNEL_KEY_ATTR_IPV6_DST       16   --     4     20
  *  - OVS_TUNNEL_KEY_ATTR_TOS            1    3      4      8
  *  - OVS_TUNNEL_KEY_ATTR_TTL            1    3      4      8
  *  - OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT  0    --     4      4
@@ -133,12 +135,12 @@  void odp_portno_names_destroy(struct hmap *portno_names);
  *  OVS_KEY_ATTR_ICMPV6                  2     2     4      8
  *  OVS_KEY_ATTR_ND                     28    --     4     32
  *  ----------------------------------------------------------
- *  total                                                 532
+ *  total                                                 572
  *
  * We include some slack space in case the calculation isn't quite right or we
  * add another field and forget to adjust this value.
  */
-#define ODPUTIL_FLOW_KEY_BYTES 576
+#define ODPUTIL_FLOW_KEY_BYTES 640
 BUILD_ASSERT_DECL(FLOW_WC_SEQ == 34);
 
 /* A buffer with sufficient size and alignment to hold an nlattr-formatted flow
diff --git a/lib/packets.c b/lib/packets.c
index 342d8b7..6056250 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -36,6 +36,28 @@ 
 const struct in6_addr in6addr_exact = IN6ADDR_EXACT_INIT;
 const struct in6_addr in6addr_all_hosts = IN6ADDR_ALL_HOSTS_INIT;
 
+struct in6_addr
+flow_tnl_dst(const struct flow_tnl *tnl)
+{
+    struct in6_addr addr;
+    if (tnl->ip_dst) {
+        in6_addr_set_mapped_ipv4(&addr, tnl->ip_dst);
+        return addr;
+    }
+    return tnl->ipv6_dst;
+}
+
+struct in6_addr
+flow_tnl_src(const struct flow_tnl *tnl)
+{
+    struct in6_addr addr;
+    if (tnl->ip_src) {
+        in6_addr_set_mapped_ipv4(&addr, tnl->ip_src);
+        return addr;
+    }
+    return tnl->ipv6_src;
+}
+
 /* Parses 's' as a 16-digit hexadecimal number representing a datapath ID.  On
  * success stores the dpid into '*dpidp' and returns true, on failure stores 0
  * into '*dpidp' and returns false.
diff --git a/lib/packets.h b/lib/packets.h
index 67f635e..e03bf60 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -38,7 +38,9 @@  struct ds;
 /* Tunnel information used in flow key and metadata. */
 struct flow_tnl {
     ovs_be32 ip_dst;
+    struct in6_addr ipv6_dst;
     ovs_be32 ip_src;
+    struct in6_addr ipv6_src;
     ovs_be64 tun_id;
     uint16_t flags;
     uint8_t ip_tos;
@@ -72,12 +74,23 @@  struct flow_tnl {
 /* Tunnel information is in userspace datapath format. */
 #define FLOW_TNL_F_UDPIF (1 << 4)
 
+static inline bool ipv6_addr_is_set(const struct in6_addr *addr);
+
+static inline bool
+flow_tnl_dst_is_set(const struct flow_tnl *tnl)
+{
+    return tnl->ip_dst || ipv6_addr_is_set(&tnl->ipv6_dst);
+}
+
+struct in6_addr flow_tnl_dst(const struct flow_tnl *tnl);
+struct in6_addr flow_tnl_src(const struct flow_tnl *tnl);
+
 /* Returns an offset to 'src' covering all the meaningful fields in 'src'. */
 static inline size_t
 flow_tnl_size(const struct flow_tnl *src)
 {
-    if (!src->ip_dst) {
-        /* Covers ip_dst only. */
+    if (!flow_tnl_dst_is_set(src)) {
+        /* Covers ip_dst and ipv6_dst only. */
         return offsetof(struct flow_tnl, ip_src);
     }
     if (src->flags & FLOW_TNL_F_UDPIF) {
@@ -145,6 +158,7 @@  pkt_metadata_init(struct pkt_metadata *md, odp_port_t port)
      * looked at. */
     memset(md, 0, offsetof(struct pkt_metadata, in_port));
     md->tunnel.ip_dst = 0;
+    md->tunnel.ipv6_dst = in6addr_any;
 
     md->in_port.odp_port = port;
 }
diff --git a/ofproto/ofproto-dpif-rid.c b/ofproto/ofproto-dpif-rid.c
index 3e99128..30c1c94 100644
--- a/ofproto/ofproto-dpif-rid.c
+++ b/ofproto/ofproto-dpif-rid.c
@@ -130,7 +130,7 @@  recirc_metadata_hash(const struct recirc_state *state)
 
     hash = hash_pointer(state->ofproto, 0);
     hash = hash_int(state->table_id, hash);
-    if (state->metadata.tunnel->ip_dst) {
+    if (flow_tnl_dst_is_set(state->metadata.tunnel)) {
         /* We may leave remainder bytes unhashed, but that is unlikely as
          * the tunnel is not in the datapath format. */
         hash = hash_words64((const uint64_t *) state->metadata.tunnel,
@@ -289,6 +289,7 @@  recirc_alloc_id(struct ofproto_dpif *ofproto)
 {
     struct flow_tnl tunnel;
     tunnel.ip_dst = htonl(0);
+    tunnel.ipv6_dst = in6addr_any;
     struct recirc_state state = {
         .table_id = TBL_INTERNAL,
         .ofproto = ofproto,
diff --git a/ofproto/ofproto-dpif-rid.h b/ofproto/ofproto-dpif-rid.h
index 6ac5fef..dda9ec8 100644
--- a/ofproto/ofproto-dpif-rid.h
+++ b/ofproto/ofproto-dpif-rid.h
@@ -120,7 +120,7 @@  static inline void
 recirc_metadata_to_flow(const struct recirc_metadata *md,
                         struct flow *flow)
 {
-    if (md->tunnel && md->tunnel->ip_dst) {
+    if (md->tunnel && flow_tnl_dst_is_set(md->tunnel)) {
         flow->tunnel = *md->tunnel;
     } else {
         memset(&flow->tunnel, 0, sizeof flow->tunnel);
diff --git a/ofproto/tunnel.c b/ofproto/tunnel.c
index d293c16..4996bda 100644
--- a/ofproto/tunnel.c
+++ b/ofproto/tunnel.c
@@ -363,8 +363,13 @@  tnl_wc_init(struct flow *flow, struct flow_wildcards *wc)
 {
     if (tnl_port_should_receive(flow)) {
         wc->masks.tunnel.tun_id = OVS_BE64_MAX;
-        wc->masks.tunnel.ip_src = OVS_BE32_MAX;
-        wc->masks.tunnel.ip_dst = OVS_BE32_MAX;
+        if (flow->tunnel.ip_dst) {
+            wc->masks.tunnel.ip_src = OVS_BE32_MAX;
+            wc->masks.tunnel.ip_dst = OVS_BE32_MAX;
+        } else {
+            wc->masks.tunnel.ipv6_src = in6addr_exact;
+            wc->masks.tunnel.ipv6_dst = in6addr_exact;
+        }
         wc->masks.tunnel.flags = (FLOW_TNL_F_DONT_FRAGMENT |
                                   FLOW_TNL_F_CSUM |
                                   FLOW_TNL_F_KEY);
@@ -413,9 +418,15 @@  tnl_port_send(const struct ofport_dpif *ofport, struct flow *flow,
 
     if (!cfg->ip_src_flow) {
         flow->tunnel.ip_src = in6_addr_get_mapped_ipv4(&tnl_port->match.ipv6_src);
+        if (!flow->tunnel.ip_src) {
+            flow->tunnel.ipv6_src = tnl_port->match.ipv6_src;
+        }
     }
     if (!cfg->ip_dst_flow) {
         flow->tunnel.ip_dst = in6_addr_get_mapped_ipv4(&tnl_port->match.ipv6_dst);
+        if (!flow->tunnel.ip_dst) {
+            flow->tunnel.ipv6_dst = tnl_port->match.ipv6_dst;
+        }
     }
     flow->pkt_mark = tnl_port->match.pkt_mark;
 
@@ -535,11 +546,11 @@  tnl_find(const struct flow *flow) OVS_REQ_RDLOCK(rwlock)
                      * here as a description of how to treat received
                      * packets. */
                     match.in_key = in_key_flow ? 0 : flow->tunnel.tun_id;
-                    if (ip_src == IP_SRC_CFG && flow->tunnel.ip_dst) {
-                        in6_addr_set_mapped_ipv4(&match.ipv6_src, flow->tunnel.ip_dst);
+                    if (ip_src == IP_SRC_CFG) {
+                        match.ipv6_src = flow_tnl_dst(&flow->tunnel);
                     }
-                    if (!ip_dst_flow && flow->tunnel.ip_src) {
-                        in6_addr_set_mapped_ipv4(&match.ipv6_dst, flow->tunnel.ip_src);
+                    if (!ip_dst_flow) {
+                        match.ipv6_dst = flow_tnl_src(&flow->tunnel);
                     }
                     match.odp_port = flow->in_port.odp_port;
                     match.pkt_mark = flow->pkt_mark;
diff --git a/ofproto/tunnel.h b/ofproto/tunnel.h
index 3bb76c5..b2fc57c 100644
--- a/ofproto/tunnel.h
+++ b/ofproto/tunnel.h
@@ -47,7 +47,7 @@  odp_port_t tnl_port_send(const struct ofport_dpif *, struct flow *,
 static inline bool
 tnl_port_should_receive(const struct flow *flow)
 {
-    return flow->tunnel.ip_dst != 0;
+    return flow_tnl_dst_is_set(&flow->tunnel);
 }
 
 int tnl_port_build_header(const struct ofport_dpif *ofport,