summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJarno Rajahalme <jarno@ovn.org>2017-03-08 17:18:23 -0800
committerJarno Rajahalme <jarno@ovn.org>2017-03-08 17:23:15 -0800
commitdaf4d3c18da4356ea7469ea0b467278651c91baa (patch)
treec9e4e60ef890ce46a208aeacee98d35e919ef03c
parent7befb20d0f70b1e695d3b14def9fef803ec3ae8d (diff)
downloadopenvswitch-daf4d3c18da4356ea7469ea0b467278651c91baa.tar.gz
odp: Support conntrack orig tuple key.
Userspace support for datapath original direction conntrack tuple. Signed-off-by: Jarno Rajahalme <jarno@ovn.org> Acked-by: Joe Stringer <joe@ovn.org>
-rwxr-xr-xbuild-aux/extract-ofp-fields3
-rw-r--r--include/openvswitch/flow.h15
-rw-r--r--include/openvswitch/match.h16
-rw-r--r--include/openvswitch/meta-flow.h136
-rw-r--r--lib/conntrack.c43
-rw-r--r--lib/flow.c228
-rw-r--r--lib/flow.h50
-rw-r--r--lib/match.c110
-rw-r--r--lib/meta-flow.c157
-rw-r--r--lib/meta-flow.xml92
-rw-r--r--lib/nx-match.c18
-rw-r--r--lib/odp-util.c86
-rw-r--r--lib/odp-util.h8
-rw-r--r--lib/ofp-util.c2
-rw-r--r--lib/packets.h5
-rw-r--r--ofproto/ofproto-dpif-rid.h2
-rw-r--r--ofproto/ofproto-dpif-xlate.c13
-rw-r--r--ofproto/ofproto-dpif.c2
-rw-r--r--tests/odp.at2
-rw-r--r--tests/ofproto-dpif.at30
-rw-r--r--tests/ofproto.at7
-rw-r--r--tests/system-traffic.at142
22 files changed, 1058 insertions, 109 deletions
diff --git a/build-aux/extract-ofp-fields b/build-aux/extract-ofp-fields
index 91cd75612..d64ee7923 100755
--- a/build-aux/extract-ofp-fields
+++ b/build-aux/extract-ofp-fields
@@ -44,6 +44,9 @@ PREREQS = {"none": "MFP_NONE",
"IPv4": "MFP_IPV4",
"IPv6": "MFP_IPV6",
"IPv4/IPv6": "MFP_IP_ANY",
+ "CT": "MFP_CT_VALID",
+ "CTv4": "MFP_CTV4_VALID",
+ "CTv6": "MFP_CTV6_VALID",
"MPLS": "MFP_MPLS",
"TCP": "MFP_TCP",
"UDP": "MFP_UDP",
diff --git a/include/openvswitch/flow.h b/include/openvswitch/flow.h
index 91692723f..5cd78e4df 100644
--- a/include/openvswitch/flow.h
+++ b/include/openvswitch/flow.h
@@ -23,7 +23,7 @@
/* This sequence number should be incremented whenever anything involving flows
* or the wildcarding of flows changes. This will cause build assertion
* failures in places which likely need to be updated. */
-#define FLOW_WC_SEQ 36
+#define FLOW_WC_SEQ 37
/* Number of Open vSwitch extension 32-bit registers. */
#define FLOW_N_REGS 16
@@ -92,7 +92,7 @@ struct flow {
union flow_in_port in_port; /* Input port.*/
uint32_t recirc_id; /* Must be exact match. */
uint8_t ct_state; /* Connection tracking state. */
- uint8_t pad0;
+ uint8_t ct_nw_proto; /* CT orig tuple IP protocol. */
uint16_t ct_zone; /* Connection tracking zone. */
uint32_t ct_mark; /* Connection mark.*/
uint8_t pad1[4]; /* Pad to 64 bits. */
@@ -110,8 +110,12 @@ struct flow {
/* L3 (64-bit aligned) */
ovs_be32 nw_src; /* IPv4 source address or ARP SPA. */
ovs_be32 nw_dst; /* IPv4 destination address or ARP TPA. */
+ ovs_be32 ct_nw_src; /* CT orig tuple IPv4 source address. */
+ ovs_be32 ct_nw_dst; /* CT orig tuple IPv4 destination address. */
struct in6_addr ipv6_src; /* IPv6 source address. */
struct in6_addr ipv6_dst; /* IPv6 destination address. */
+ struct in6_addr ct_ipv6_src; /* CT orig tuple IPv6 source address. */
+ struct in6_addr ct_ipv6_dst; /* CT orig tuple IPv6 destination address. */
ovs_be32 ipv6_label; /* IPv6 flow label. */
uint8_t nw_frag; /* FLOW_FRAG_* flags. */
uint8_t nw_tos; /* IP ToS (including DSCP and ECN). */
@@ -126,8 +130,11 @@ struct flow {
/* L4 (64-bit aligned) */
ovs_be16 tp_src; /* TCP/UDP/SCTP source port/ICMP type. */
ovs_be16 tp_dst; /* TCP/UDP/SCTP destination port/ICMP code. */
+ ovs_be16 ct_tp_src; /* CT original tuple source port/ICMP type. */
+ ovs_be16 ct_tp_dst; /* CT original tuple dst port/ICMP code. */
ovs_be32 igmp_group_ip4; /* IGMP group IPv4 address.
* Keep last for BUILD_ASSERT_DECL below. */
+ ovs_be32 pad4; /* Pad to 64 bits. */
};
BUILD_ASSERT_DECL(sizeof(struct flow) % sizeof(uint64_t) == 0);
BUILD_ASSERT_DECL(sizeof(struct flow_tnl) % sizeof(uint64_t) == 0);
@@ -136,8 +143,8 @@ BUILD_ASSERT_DECL(sizeof(struct flow_tnl) % sizeof(uint64_t) == 0);
/* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */
BUILD_ASSERT_DECL(offsetof(struct flow, igmp_group_ip4) + sizeof(uint32_t)
- == sizeof(struct flow_tnl) + 248
- && FLOW_WC_SEQ == 36);
+ == sizeof(struct flow_tnl) + 292
+ && FLOW_WC_SEQ == 37);
/* Incremental points at which flow classification may be performed in
* segments.
diff --git a/include/openvswitch/match.h b/include/openvswitch/match.h
index 0b5f05052..06fa04c43 100644
--- a/include/openvswitch/match.h
+++ b/include/openvswitch/match.h
@@ -99,6 +99,22 @@ void match_set_ct_mark(struct match *, uint32_t ct_mark);
void match_set_ct_mark_masked(struct match *, uint32_t ct_mark, uint32_t mask);
void match_set_ct_label(struct match *, ovs_u128 ct_label);
void match_set_ct_label_masked(struct match *, ovs_u128 ct_label, ovs_u128 mask);
+void match_set_ct_nw_src(struct match *, ovs_be32);
+void match_set_ct_nw_src_masked(struct match *, ovs_be32, ovs_be32 mask);
+void match_set_ct_nw_dst(struct match *, ovs_be32);
+void match_set_ct_nw_dst_masked(struct match *, ovs_be32, ovs_be32 mask);
+void match_set_ct_nw_proto(struct match *, uint8_t);
+void match_set_ct_tp_src(struct match *, ovs_be16);
+void match_set_ct_tp_src_masked(struct match *, ovs_be16, ovs_be16 mask);
+void match_set_ct_tp_dst(struct match *, ovs_be16);
+void match_set_ct_tp_dst_masked(struct match *, ovs_be16, ovs_be16 mask);
+void match_set_ct_ipv6_src(struct match *, const struct in6_addr *);
+void match_set_ct_ipv6_src_masked(struct match *, const struct in6_addr *,
+ const struct in6_addr *);
+void match_set_ct_ipv6_dst(struct match *, const struct in6_addr *);
+void match_set_ct_ipv6_dst_masked(struct match *, const struct in6_addr *,
+ const struct in6_addr *);
+
void match_set_skb_priority(struct match *, uint32_t skb_priority);
void match_set_dl_type(struct match *, ovs_be16);
void match_set_dl_src(struct match *, const struct eth_addr );
diff --git a/include/openvswitch/meta-flow.h b/include/openvswitch/meta-flow.h
index 2986c82b3..450e5297d 100644
--- a/include/openvswitch/meta-flow.h
+++ b/include/openvswitch/meta-flow.h
@@ -740,6 +740,139 @@ enum OVS_PACKED_ENUM mf_field_id {
*/
MFF_CT_LABEL,
+ /* "ct_nw_proto".
+ *
+ * The "protocol" byte in the IPv4 or IPv6 header for the original
+ * direction conntrack tuple, or of the master conntrack entry, if the
+ * current connection is a related connection.
+ *
+ * The value is initially zero and populated by the CT action. The value
+ * remains zero after the CT action only if the packet can not be
+ * associated with a valid connection, in which case the prerequisites
+ * for matching this field ("CT") are not met.
+ *
+ * Type: u8.
+ * Maskable: no.
+ * Formatting: decimal.
+ * Prerequisites: CT.
+ * Access: read-only.
+ * NXM: NXM_NX_CT_NW_PROTO(119) since v2.7.
+ * OXM: none.
+ */
+ MFF_CT_NW_PROTO,
+
+ /* "ct_nw_src".
+ *
+ * IPv4 source address of the original direction tuple of the conntrack
+ * entry, or of the master conntrack entry, if the current connection is a
+ * related connection.
+ *
+ * The value is populated by the CT action.
+ *
+ * Type: be32.
+ * Maskable: bitwise.
+ * Formatting: IPv4.
+ * Prerequisites: CTv4.
+ * Access: read-only.
+ * NXM: NXM_NX_CT_NW_SRC(120) since v2.7.
+ * OXM: none.
+ * Prefix lookup member: ct_nw_src.
+ */
+ MFF_CT_NW_SRC,
+
+ /* "ct_nw_dst".
+ *
+ * IPv4 destination address of the original direction tuple of the
+ * conntrack entry, or of the master conntrack entry, if the current
+ * connection is a related connection.
+ *
+ * The value is populated by the CT action.
+ *
+ * Type: be32.
+ * Maskable: bitwise.
+ * Formatting: IPv4.
+ * Prerequisites: CTv4.
+ * Access: read-only.
+ * NXM: NXM_NX_CT_NW_DST(121) since v2.7.
+ * OXM: none.
+ * Prefix lookup member: ct_nw_dst.
+ */
+ MFF_CT_NW_DST,
+
+ /* "ct_ipv6_src".
+ *
+ * IPv6 source address of the original direction tuple of the conntrack
+ * entry, or of the master conntrack entry, if the current connection is a
+ * related connection.
+ *
+ * The value is populated by the CT action.
+ *
+ * Type: be128.
+ * Maskable: bitwise.
+ * Formatting: IPv6.
+ * Prerequisites: CTv6.
+ * Access: read-only.
+ * NXM: NXM_NX_CT_IPV6_SRC(122) since v2.7.
+ * OXM: none.
+ * Prefix lookup member: ct_ipv6_src.
+ */
+ MFF_CT_IPV6_SRC,
+
+ /* "ct_ipv6_dst".
+ *
+ * IPv6 destination address of the original direction tuple of the
+ * conntrack entry, or of the master conntrack entry, if the current
+ * connection is a related connection.
+ *
+ * The value is populated by the CT action.
+ *
+ * Type: be128.
+ * Maskable: bitwise.
+ * Formatting: IPv6.
+ * Prerequisites: CTv6.
+ * Access: read-only.
+ * NXM: NXM_NX_CT_IPV6_DST(123) since v2.7.
+ * OXM: none.
+ * Prefix lookup member: ct_ipv6_dst.
+ */
+ MFF_CT_IPV6_DST,
+
+ /* "ct_tp_src".
+ *
+ * Transport layer source port of the original direction tuple of the
+ * conntrack entry, or of the master conntrack entry, if the current
+ * connection is a related connection.
+ *
+ * The value is populated by the CT action.
+ *
+ * Type: be16.
+ * Maskable: bitwise.
+ * Formatting: decimal.
+ * Prerequisites: CT.
+ * Access: read-only.
+ * NXM: NXM_NX_CT_TP_SRC(124) since v2.7.
+ * OXM: none.
+ */
+ MFF_CT_TP_SRC,
+
+ /* "ct_tp_dst".
+ *
+ * Transport layer destination port of the original direction tuple of the
+ * conntrack entry, or of the master conntrack entry, if the current
+ * connection is a related connection.
+ *
+ * The value is populated by the CT action.
+ *
+ * Type: be16.
+ * Maskable: bitwise.
+ * Formatting: decimal.
+ * Prerequisites: CT.
+ * Access: read-only.
+ * NXM: NXM_NX_CT_TP_DST(125) since v2.7.
+ * OXM: none.
+ */
+ MFF_CT_TP_DST,
+
#if FLOW_N_REGS == 16
/* "reg<N>".
*
@@ -1690,6 +1823,9 @@ enum OVS_PACKED_ENUM mf_prereqs {
MFP_SCTP, /* On IPv4 or IPv6. */
MFP_ICMPV4,
MFP_ICMPV6,
+ MFP_CT_VALID, /* Implies IPv4 or IPv6. */
+ MFP_CTV4_VALID, /* MFP_CT_VALID and IPv4. */
+ MFP_CTV6_VALID, /* MFP_CT_VALID and IPv6. */
/* L2+L3+L4 requirements. */
MFP_ND,
diff --git a/lib/conntrack.c b/lib/conntrack.c
index 677c0d2a3..8ae501b9e 100644
--- a/lib/conntrack.c
+++ b/lib/conntrack.c
@@ -159,12 +159,44 @@ static unsigned hash_to_bucket(uint32_t hash)
static void
write_ct_md(struct dp_packet *pkt, uint16_t state, uint16_t zone,
- uint32_t mark, ovs_u128 label)
+ const struct conn *conn, const struct conn_key *key)
{
pkt->md.ct_state = state | CS_TRACKED;
pkt->md.ct_zone = zone;
- pkt->md.ct_mark = mark;
- pkt->md.ct_label = label;
+ pkt->md.ct_mark = conn ? conn->mark : 0;
+ pkt->md.ct_label = conn ? conn->label : OVS_U128_ZERO;
+
+ /* Use the original direction tuple if we have it. */
+ if (conn) {
+ key = &conn->key;
+ }
+ pkt->md.ct_orig_tuple_ipv6 = false;
+ if (key) {
+ if (key->dl_type == htons(ETH_TYPE_IP)) {
+ pkt->md.ct_orig_tuple.ipv4 = (struct ovs_key_ct_tuple_ipv4) {
+ key->src.addr.ipv4_aligned,
+ key->dst.addr.ipv4_aligned,
+ key->nw_proto != IPPROTO_ICMP
+ ? key->src.port : htons(key->src.icmp_type),
+ key->nw_proto != IPPROTO_ICMP
+ ? key->dst.port : htons(key->src.icmp_code),
+ key->nw_proto,
+ };
+ } else if (key->dl_type == htons(ETH_TYPE_IPV6)) {
+ pkt->md.ct_orig_tuple_ipv6 = true;
+ pkt->md.ct_orig_tuple.ipv6 = (struct ovs_key_ct_tuple_ipv6) {
+ key->src.addr.ipv6_aligned,
+ key->dst.addr.ipv6_aligned,
+ key->nw_proto != IPPROTO_ICMPV6
+ ? key->src.port : htons(key->src.icmp_type),
+ key->nw_proto != IPPROTO_ICMPV6
+ ? key->dst.port : htons(key->src.icmp_code),
+ key->nw_proto,
+ };
+ }
+ } else {
+ memset(&pkt->md.ct_orig_tuple, 0, sizeof pkt->md.ct_orig_tuple);
+ }
}
static struct conn *
@@ -254,8 +286,7 @@ process_one(struct conntrack *ct, struct dp_packet *pkt,
}
}
- write_ct_md(pkt, state, zone, conn ? conn->mark : 0,
- conn ? conn->label : OVS_U128_ZERO);
+ write_ct_md(pkt, state, zone, conn, &ctx->key);
return conn;
}
@@ -306,7 +337,7 @@ conntrack_execute(struct conntrack *ct, struct dp_packet_batch *pkt_batch,
unsigned bucket;
if (!conn_key_extract(ct, pkts[i], dl_type, &ctxs[i], zone)) {
- write_ct_md(pkts[i], CS_INVALID, zone, 0, OVS_U128_ZERO);
+ write_ct_md(pkts[i], CS_INVALID, zone, NULL, NULL);
continue;
}
diff --git a/lib/flow.c b/lib/flow.c
index 0c95b7507..b476fce49 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -125,7 +125,7 @@ struct mf_ctx {
* away. Some GCC versions gave warnings on ALWAYS_INLINE, so these are
* defined as macros. */
-#if (FLOW_WC_SEQ != 36)
+#if (FLOW_WC_SEQ != 37)
#define MINIFLOW_ASSERT(X) ovs_assert(X)
BUILD_MESSAGE("FLOW_WC_SEQ changed: miniflow_extract() will have runtime "
"assertions enabled. Consider updating FLOW_WC_SEQ after "
@@ -312,6 +312,11 @@ BUILD_MESSAGE("FLOW_WC_SEQ changed: miniflow_extract() will have runtime "
#define miniflow_push_macs(MF, FIELD, VALUEP) \
miniflow_push_macs_(MF, offsetof(struct flow, FIELD), VALUEP)
+/* Return the pointer to the miniflow data when called BEFORE the corresponding
+ * push. */
+#define miniflow_pointer(MF, FIELD) \
+ (void *)((uint8_t *)MF.data + ((offsetof(struct flow, FIELD)) % 8))
+
/* Pulls the MPLS headers at '*datap' and returns the count of them. */
static inline int
parse_mpls(const void **datap, size_t *sizep)
@@ -383,61 +388,67 @@ parse_ethertype(const void **datap, size_t *sizep)
return htons(FLOW_DL_TYPE_NONE);
}
-static inline void
+/* Returns 'true' if the packet is an ND packet. In that case the '*nd_target'
+ * and 'arp_buf[]' are filled in. If the packet is not an ND pacet, 'false' is
+ * returned and no values are filled in on '*nd_target' or 'arp_buf[]'. */
+static inline bool
parse_icmpv6(const void **datap, size_t *sizep, const struct icmp6_hdr *icmp,
const struct in6_addr **nd_target,
struct eth_addr arp_buf[2])
{
- if (icmp->icmp6_code == 0 &&
- (icmp->icmp6_type == ND_NEIGHBOR_SOLICIT ||
- icmp->icmp6_type == ND_NEIGHBOR_ADVERT)) {
+ if (icmp->icmp6_code != 0 ||
+ (icmp->icmp6_type != ND_NEIGHBOR_SOLICIT &&
+ icmp->icmp6_type != ND_NEIGHBOR_ADVERT)) {
+ return false;
+ }
- *nd_target = data_try_pull(datap, sizep, sizeof **nd_target);
- if (OVS_UNLIKELY(!*nd_target)) {
- return;
- }
+ arp_buf[0] = eth_addr_zero;
+ arp_buf[1] = eth_addr_zero;
+ *nd_target = data_try_pull(datap, sizep, sizeof **nd_target);
+ if (OVS_UNLIKELY(!*nd_target)) {
+ return true;
+ }
- while (*sizep >= 8) {
- /* The minimum size of an option is 8 bytes, which also is
- * the size of Ethernet link-layer options. */
- const struct ovs_nd_opt *nd_opt = *datap;
- int opt_len = nd_opt->nd_opt_len * ND_OPT_LEN;
+ while (*sizep >= 8) {
+ /* The minimum size of an option is 8 bytes, which also is
+ * the size of Ethernet link-layer options. */
+ const struct ovs_nd_opt *nd_opt = *datap;
+ int opt_len = nd_opt->nd_opt_len * ND_OPT_LEN;
- if (!opt_len || opt_len > *sizep) {
- return;
- }
+ if (!opt_len || opt_len > *sizep) {
+ return true;
+ }
- /* Store the link layer address if the appropriate option is
- * provided. It is considered an error if the same link
- * layer option is specified twice. */
- if (nd_opt->nd_opt_type == ND_OPT_SOURCE_LINKADDR
- && opt_len == 8) {
- if (OVS_LIKELY(eth_addr_is_zero(arp_buf[0]))) {
- arp_buf[0] = nd_opt->nd_opt_mac;
- } else {
- goto invalid;
- }
- } else if (nd_opt->nd_opt_type == ND_OPT_TARGET_LINKADDR
- && opt_len == 8) {
- if (OVS_LIKELY(eth_addr_is_zero(arp_buf[1]))) {
- arp_buf[1] = nd_opt->nd_opt_mac;
- } else {
- goto invalid;
- }
+ /* Store the link layer address if the appropriate option is
+ * provided. It is considered an error if the same link
+ * layer option is specified twice. */
+ if (nd_opt->nd_opt_type == ND_OPT_SOURCE_LINKADDR
+ && opt_len == 8) {
+ if (OVS_LIKELY(eth_addr_is_zero(arp_buf[0]))) {
+ arp_buf[0] = nd_opt->nd_opt_mac;
+ } else {
+ goto invalid;
}
-
- if (OVS_UNLIKELY(!data_try_pull(datap, sizep, opt_len))) {
- return;
+ } else if (nd_opt->nd_opt_type == ND_OPT_TARGET_LINKADDR
+ && opt_len == 8) {
+ if (OVS_LIKELY(eth_addr_is_zero(arp_buf[1]))) {
+ arp_buf[1] = nd_opt->nd_opt_mac;
+ } else {
+ goto invalid;
}
}
- }
- return;
+ if (OVS_UNLIKELY(!data_try_pull(datap, sizep, opt_len))) {
+ return true;
+ }
+ }
+ return true;
invalid:
*nd_target = NULL;
arp_buf[0] = eth_addr_zero;
arp_buf[1] = eth_addr_zero;
+ return true;
}
static inline bool
@@ -561,6 +572,8 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
const char *l2;
ovs_be16 dl_type;
uint8_t nw_frag, nw_tos, nw_ttl, nw_proto;
+ uint8_t *ct_nw_proto_p = NULL;
+ ovs_be16 ct_tp_src = 0, ct_tp_dst = 0;
/* Metadata. */
if (flow_tnl_dst_is_set(&md->tunnel)) {
@@ -594,7 +607,8 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
if (md->recirc_id || md->ct_state) {
miniflow_push_uint32(mf, recirc_id, md->recirc_id);
miniflow_push_uint8(mf, ct_state, md->ct_state);
- miniflow_push_uint8(mf, pad0, 0);
+ ct_nw_proto_p = miniflow_pointer(mf, ct_nw_proto);
+ miniflow_push_uint8(mf, ct_nw_proto, 0);
miniflow_push_uint16(mf, ct_zone, md->ct_zone);
}
@@ -670,6 +684,15 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
/* Push both source and destination address at once. */
miniflow_push_words(mf, nw_src, &nh->ip_src, 1);
+ if (ct_nw_proto_p && !md->ct_orig_tuple_ipv6) {
+ *ct_nw_proto_p = md->ct_orig_tuple.ipv4.ipv4_proto;
+ if (*ct_nw_proto_p) {
+ miniflow_push_words(mf, ct_nw_src,
+ &md->ct_orig_tuple.ipv4.ipv4_src, 1);
+ ct_tp_src = md->ct_orig_tuple.ipv4.src_port;
+ ct_tp_dst = md->ct_orig_tuple.ipv4.dst_port;
+ }
+ }
miniflow_push_be32(mf, ipv6_label, 0); /* Padding for IPv4. */
@@ -708,6 +731,17 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
sizeof nh->ip6_src / 8);
miniflow_push_words(mf, ipv6_dst, &nh->ip6_dst,
sizeof nh->ip6_dst / 8);
+ if (ct_nw_proto_p && md->ct_orig_tuple_ipv6) {
+ *ct_nw_proto_p = md->ct_orig_tuple.ipv6.ipv6_proto;
+ if (*ct_nw_proto_p) {
+ miniflow_push_words(mf, ct_ipv6_src,
+ &md->ct_orig_tuple.ipv6.ipv6_src,
+ 2 *
+ sizeof md->ct_orig_tuple.ipv6.ipv6_src / 8);
+ ct_tp_src = md->ct_orig_tuple.ipv6.src_port;
+ ct_tp_dst = md->ct_orig_tuple.ipv6.dst_port;
+ }
+ }
tc_flow = get_16aligned_be32(&nh->ip6_flow);
{
@@ -770,7 +804,8 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
TCP_FLAGS_BE32(tcp->tcp_ctl));
miniflow_push_be16(mf, tp_src, tcp->tcp_src);
miniflow_push_be16(mf, tp_dst, tcp->tcp_dst);
- miniflow_pad_to_64(mf, tp_dst);
+ miniflow_push_be16(mf, ct_tp_src, ct_tp_src);
+ miniflow_push_be16(mf, ct_tp_dst, ct_tp_dst);
}
} else if (OVS_LIKELY(nw_proto == IPPROTO_UDP)) {
if (OVS_LIKELY(size >= UDP_HEADER_LEN)) {
@@ -778,7 +813,8 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
miniflow_push_be16(mf, tp_src, udp->udp_src);
miniflow_push_be16(mf, tp_dst, udp->udp_dst);
- miniflow_pad_to_64(mf, tp_dst);
+ miniflow_push_be16(mf, ct_tp_src, ct_tp_src);
+ miniflow_push_be16(mf, ct_tp_dst, ct_tp_dst);
}
} else if (OVS_LIKELY(nw_proto == IPPROTO_SCTP)) {
if (OVS_LIKELY(size >= SCTP_HEADER_LEN)) {
@@ -786,7 +822,8 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
miniflow_push_be16(mf, tp_src, sctp->sctp_src);
miniflow_push_be16(mf, tp_dst, sctp->sctp_dst);
- miniflow_pad_to_64(mf, tp_dst);
+ miniflow_push_be16(mf, ct_tp_src, ct_tp_src);
+ miniflow_push_be16(mf, ct_tp_dst, ct_tp_dst);
}
} else if (OVS_LIKELY(nw_proto == IPPROTO_ICMP)) {
if (OVS_LIKELY(size >= ICMP_HEADER_LEN)) {
@@ -794,7 +831,8 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
miniflow_push_be16(mf, tp_src, htons(icmp->icmp_type));
miniflow_push_be16(mf, tp_dst, htons(icmp->icmp_code));
- miniflow_pad_to_64(mf, tp_dst);
+ miniflow_push_be16(mf, ct_tp_src, ct_tp_src);
+ miniflow_push_be16(mf, ct_tp_dst, ct_tp_dst);
}
} else if (OVS_LIKELY(nw_proto == IPPROTO_IGMP)) {
if (OVS_LIKELY(size >= IGMP_HEADER_LEN)) {
@@ -802,25 +840,35 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
miniflow_push_be16(mf, tp_src, htons(igmp->igmp_type));
miniflow_push_be16(mf, tp_dst, htons(igmp->igmp_code));
+ miniflow_push_be16(mf, ct_tp_src, ct_tp_src);
+ miniflow_push_be16(mf, ct_tp_dst, ct_tp_dst);
miniflow_push_be32(mf, igmp_group_ip4,
get_16aligned_be32(&igmp->group));
+ miniflow_pad_to_64(mf, igmp_group_ip4);
}
} else if (OVS_LIKELY(nw_proto == IPPROTO_ICMPV6)) {
if (OVS_LIKELY(size >= sizeof(struct icmp6_hdr))) {
- const struct in6_addr *nd_target = NULL;
- struct eth_addr arp_buf[2] = { { { { 0 } } } };
+ const struct in6_addr *nd_target;
+ struct eth_addr arp_buf[2];
const struct icmp6_hdr *icmp = data_pull(&data, &size,
sizeof *icmp);
- parse_icmpv6(&data, &size, icmp, &nd_target, arp_buf);
- if (nd_target) {
- miniflow_push_words(mf, nd_target, nd_target,
- sizeof *nd_target / sizeof(uint64_t));
+ if (parse_icmpv6(&data, &size, icmp, &nd_target, arp_buf)) {
+ if (nd_target) {
+ miniflow_push_words(mf, nd_target, nd_target,
+ sizeof *nd_target / sizeof(uint64_t));
+ }
+ miniflow_push_macs(mf, arp_sha, arp_buf);
+ miniflow_pad_to_64(mf, arp_tha);
+ miniflow_push_be16(mf, tp_src, htons(icmp->icmp6_type));
+ miniflow_push_be16(mf, tp_dst, htons(icmp->icmp6_code));
+ miniflow_pad_to_64(mf, tp_dst);
+ } else {
+ /* ICMPv6 but not ND. */
+ miniflow_push_be16(mf, tp_src, htons(icmp->icmp6_type));
+ miniflow_push_be16(mf, tp_dst, htons(icmp->icmp6_code));
+ miniflow_push_be16(mf, ct_tp_src, ct_tp_src);
+ miniflow_push_be16(mf, ct_tp_dst, ct_tp_dst);
}
- miniflow_push_macs(mf, arp_sha, arp_buf);
- miniflow_pad_to_64(mf, arp_tha);
- miniflow_push_be16(mf, tp_src, htons(icmp->icmp6_type));
- miniflow_push_be16(mf, tp_dst, htons(icmp->icmp6_code));
- miniflow_pad_to_64(mf, tp_dst);
}
}
}
@@ -870,7 +918,7 @@ flow_get_metadata(const struct flow *flow, struct match *flow_metadata)
{
int i;
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 36);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 37);
match_init_catchall(flow_metadata);
if (flow->tunnel.tun_id != htonll(0)) {
@@ -916,6 +964,21 @@ flow_get_metadata(const struct flow *flow, struct match *flow_metadata)
match_set_in_port(flow_metadata, flow->in_port.ofp_port);
if (flow->ct_state != 0) {
match_set_ct_state(flow_metadata, flow->ct_state);
+ if (is_ct_valid(flow, NULL, NULL) && flow->ct_nw_proto != 0) {
+ if (flow->dl_type == htons(ETH_TYPE_IP)) {
+ match_set_ct_nw_src(flow_metadata, flow->ct_nw_src);
+ match_set_ct_nw_dst(flow_metadata, flow->ct_nw_dst);
+ match_set_ct_nw_proto(flow_metadata, flow->ct_nw_proto);
+ match_set_ct_tp_src(flow_metadata, flow->ct_tp_src);
+ match_set_ct_tp_dst(flow_metadata, flow->ct_tp_dst);
+ } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
+ match_set_ct_ipv6_src(flow_metadata, &flow->ct_ipv6_src);
+ match_set_ct_ipv6_dst(flow_metadata, &flow->ct_ipv6_dst);
+ match_set_ct_nw_proto(flow_metadata, flow->ct_nw_proto);
+ match_set_ct_tp_src(flow_metadata, flow->ct_tp_src);
+ match_set_ct_tp_dst(flow_metadata, flow->ct_tp_dst);
+ }
+ }
}
if (flow->ct_zone != 0) {
match_set_ct_zone(flow_metadata, flow->ct_zone);
@@ -1237,6 +1300,18 @@ flow_format(struct ds *ds, const struct flow *flow)
if (ovs_u128_is_zero(flow->ct_label)) {
WC_UNMASK_FIELD(wc, ct_label);
}
+ if (!is_ct_valid(flow, &match.wc, NULL) || !flow->ct_nw_proto) {
+ WC_UNMASK_FIELD(wc, ct_nw_proto);
+ WC_UNMASK_FIELD(wc, ct_tp_src);
+ WC_UNMASK_FIELD(wc, ct_tp_dst);
+ if (flow->dl_type == htons(ETH_TYPE_IP)) {
+ WC_UNMASK_FIELD(wc, ct_nw_src);
+ WC_UNMASK_FIELD(wc, ct_nw_dst);
+ } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
+ WC_UNMASK_FIELD(wc, ct_ipv6_src);
+ WC_UNMASK_FIELD(wc, ct_ipv6_dst);
+ }
+ }
for (int i = 0; i < FLOW_N_REGS; i++) {
if (!flow->regs[i]) {
WC_UNMASK_FIELD(wc, regs[i]);
@@ -1276,7 +1351,7 @@ void flow_wildcards_init_for_packet(struct flow_wildcards *wc,
memset(&wc->masks, 0x0, sizeof wc->masks);
/* Update this function whenever struct flow changes. */
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 36);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 37);
if (flow_tnl_dst_is_set(&flow->tunnel)) {
if (flow->tunnel.flags & FLOW_TNL_F_KEY) {
@@ -1332,10 +1407,20 @@ void flow_wildcards_init_for_packet(struct flow_wildcards *wc,
if (flow->dl_type == htons(ETH_TYPE_IP)) {
WC_MASK_FIELD(wc, nw_src);
WC_MASK_FIELD(wc, nw_dst);
+ WC_MASK_FIELD(wc, ct_nw_src);
+ WC_MASK_FIELD(wc, ct_nw_dst);
} else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
WC_MASK_FIELD(wc, ipv6_src);
WC_MASK_FIELD(wc, ipv6_dst);
WC_MASK_FIELD(wc, ipv6_label);
+ if (is_nd(flow, wc)) {
+ WC_MASK_FIELD(wc, arp_sha);
+ WC_MASK_FIELD(wc, arp_tha);
+ WC_MASK_FIELD(wc, nd_target);
+ } else {
+ WC_MASK_FIELD(wc, ct_ipv6_src);
+ WC_MASK_FIELD(wc, ct_ipv6_dst);
+ }
} else if (flow->dl_type == htons(ETH_TYPE_ARP) ||
flow->dl_type == htons(ETH_TYPE_RARP)) {
WC_MASK_FIELD(wc, nw_src);
@@ -1361,6 +1446,9 @@ void flow_wildcards_init_for_packet(struct flow_wildcards *wc,
WC_MASK_FIELD(wc, nw_tos);
WC_MASK_FIELD(wc, nw_ttl);
WC_MASK_FIELD(wc, nw_proto);
+ WC_MASK_FIELD(wc, ct_nw_proto);
+ WC_MASK_FIELD(wc, ct_tp_src);
+ WC_MASK_FIELD(wc, ct_tp_dst);
/* No transport layer header in later fragments. */
if (!(flow->nw_frag & FLOW_NW_FRAG_LATER) &&
@@ -1375,10 +1463,6 @@ void flow_wildcards_init_for_packet(struct flow_wildcards *wc,
if (flow->nw_proto == IPPROTO_TCP) {
WC_MASK_FIELD(wc, tcp_flags);
- } else if (flow->nw_proto == IPPROTO_ICMPV6) {
- WC_MASK_FIELD(wc, arp_sha);
- WC_MASK_FIELD(wc, arp_tha);
- WC_MASK_FIELD(wc, nd_target);
} else if (flow->nw_proto == IPPROTO_IGMP) {
WC_MASK_FIELD(wc, igmp_group_ip4);
}
@@ -1394,7 +1478,7 @@ void
flow_wc_map(const struct flow *flow, struct flowmap *map)
{
/* Update this function whenever struct flow changes. */
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 36);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 37);
flowmap_init(map);
@@ -1436,6 +1520,11 @@ flow_wc_map(const struct flow *flow, struct flowmap *map)
FLOWMAP_SET(map, nw_ttl);
FLOWMAP_SET(map, tp_src);
FLOWMAP_SET(map, tp_dst);
+ FLOWMAP_SET(map, ct_nw_proto);
+ FLOWMAP_SET(map, ct_nw_src);
+ FLOWMAP_SET(map, ct_nw_dst);
+ FLOWMAP_SET(map, ct_tp_src);
+ FLOWMAP_SET(map, ct_tp_dst);
if (OVS_UNLIKELY(flow->nw_proto == IPPROTO_IGMP)) {
FLOWMAP_SET(map, igmp_group_ip4);
@@ -1453,11 +1542,16 @@ flow_wc_map(const struct flow *flow, struct flowmap *map)
FLOWMAP_SET(map, tp_src);
FLOWMAP_SET(map, tp_dst);
- if (OVS_UNLIKELY(flow->nw_proto == IPPROTO_ICMPV6)) {
+ if (OVS_UNLIKELY(is_nd(flow, NULL))) {
FLOWMAP_SET(map, nd_target);
FLOWMAP_SET(map, arp_sha);
FLOWMAP_SET(map, arp_tha);
} else {
+ FLOWMAP_SET(map, ct_nw_proto);
+ FLOWMAP_SET(map, ct_ipv6_src);
+ FLOWMAP_SET(map, ct_ipv6_dst);
+ FLOWMAP_SET(map, ct_tp_src);
+ FLOWMAP_SET(map, ct_tp_dst);
FLOWMAP_SET(map, tcp_flags);
}
} else if (eth_type_mpls(flow->dl_type)) {
@@ -1478,7 +1572,7 @@ void
flow_wildcards_clear_non_packet_fields(struct flow_wildcards *wc)
{
/* Update this function whenever struct flow changes. */
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 36);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 37);
memset(&wc->masks.metadata, 0, sizeof wc->masks.metadata);
memset(&wc->masks.regs, 0, sizeof wc->masks.regs);
@@ -1622,7 +1716,7 @@ flow_wildcards_set_xxreg_mask(struct flow_wildcards *wc, int idx,
uint32_t
miniflow_hash_5tuple(const struct miniflow *flow, uint32_t basis)
{
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 36);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 37);
uint32_t hash = basis;
if (flow) {
@@ -1669,7 +1763,7 @@ ASSERT_SEQUENTIAL(ipv6_src, ipv6_dst);
uint32_t
flow_hash_5tuple(const struct flow *flow, uint32_t basis)
{
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 36);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 37);
uint32_t hash = basis;
if (flow) {
@@ -2137,7 +2231,7 @@ flow_push_mpls(struct flow *flow, int n, ovs_be16 mpls_eth_type,
if (clear_flow_L3) {
/* Clear all L3 and L4 fields and dp_hash. */
- BUILD_ASSERT(FLOW_WC_SEQ == 36);
+ BUILD_ASSERT(FLOW_WC_SEQ == 37);
memset((char *) flow + FLOW_SEGMENT_2_ENDS_AT, 0,
sizeof(struct flow) - FLOW_SEGMENT_2_ENDS_AT);
flow->dp_hash = 0;
diff --git a/lib/flow.h b/lib/flow.h
index 62315bc49..14a30041e 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -862,9 +862,35 @@ flow_union_with_miniflow(struct flow *dst, const struct miniflow *src)
flow_union_with_miniflow_subset(dst, src, src->map);
}
+static inline bool is_ct_valid(const struct flow *flow,
+ const struct flow_wildcards *mask,
+ struct flow_wildcards *wc)
+{
+ /* Matches are checked with 'mask' and without 'wc'. */
+ if (mask && !wc) {
+ /* Must match at least one of the bits that implies a valid
+ * conntrack entry, or an explicit not-invalid. */
+ return flow->ct_state & (CS_NEW | CS_ESTABLISHED | CS_RELATED
+ | CS_REPLY_DIR | CS_SRC_NAT | CS_DST_NAT)
+ || (flow->ct_state & CS_TRACKED
+ && mask->masks.ct_state & CS_INVALID
+ && !(flow->ct_state & CS_INVALID));
+ }
+ /* Else we are checking a fully extracted flow, where valid CT state always
+ * has either 'new', 'established', or 'reply_dir' bit set. */
+#define CS_VALID_MASK (CS_NEW | CS_ESTABLISHED | CS_REPLY_DIR)
+ if (wc) {
+ wc->masks.ct_state |= CS_VALID_MASK;
+ }
+ return flow->ct_state & CS_VALID_MASK;
+}
+
static inline void
pkt_metadata_from_flow(struct pkt_metadata *md, const struct flow *flow)
{
+ /* Update this function whenever struct flow changes. */
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 37);
+
md->recirc_id = flow->recirc_id;
md->dp_hash = flow->dp_hash;
flow_tnl_copy__(&md->tunnel, &flow->tunnel);
@@ -875,6 +901,30 @@ pkt_metadata_from_flow(struct pkt_metadata *md, const struct flow *flow)
md->ct_zone = flow->ct_zone;
md->ct_mark = flow->ct_mark;
md->ct_label = flow->ct_label;
+
+ md->ct_orig_tuple_ipv6 = false;
+ if (is_ct_valid(flow, NULL, NULL)) {
+ if (flow->dl_type == htons(ETH_TYPE_IP)) {
+ md->ct_orig_tuple.ipv4 = (struct ovs_key_ct_tuple_ipv4) {
+ flow->ct_nw_src,
+ flow->ct_nw_dst,
+ flow->ct_tp_src,
+ flow->ct_tp_dst,
+ flow->ct_nw_proto,
+ };
+ } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
+ md->ct_orig_tuple_ipv6 = true;
+ md->ct_orig_tuple.ipv6 = (struct ovs_key_ct_tuple_ipv6) {
+ flow->ct_ipv6_src,
+ flow->ct_ipv6_dst,
+ flow->ct_tp_src,
+ flow->ct_tp_dst,
+ flow->ct_nw_proto,
+ };
+ }
+ } else {
+ memset(&md->ct_orig_tuple, 0, sizeof md->ct_orig_tuple);
+ }
}
/* Often, during translation we need to read a value from a flow('FLOW') and
diff --git a/lib/match.c b/lib/match.c
index 882bf0c7c..1a5b4bae7 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -384,6 +384,99 @@ match_set_ct_label_masked(struct match *match, ovs_u128 value, ovs_u128 mask)
}
void
+match_set_ct_nw_src(struct match *match, ovs_be32 ct_nw_src)
+{
+ match->flow.ct_nw_src = ct_nw_src;
+ match->wc.masks.ct_nw_src = OVS_BE32_MAX;
+}
+
+void
+match_set_ct_nw_src_masked(struct match *match, ovs_be32 ct_nw_src,
+ ovs_be32 mask)
+{
+ match->flow.ct_nw_src = ct_nw_src & mask;
+ match->wc.masks.ct_nw_src = mask;
+}
+
+void
+match_set_ct_nw_dst(struct match *match, ovs_be32 ct_nw_dst)
+{
+ match->flow.ct_nw_dst = ct_nw_dst;
+ match->wc.masks.ct_nw_dst = OVS_BE32_MAX;
+}
+
+void
+match_set_ct_nw_dst_masked(struct match *match, ovs_be32 ct_nw_dst,
+ ovs_be32 mask)
+{
+ match->flow.ct_nw_dst = ct_nw_dst & mask;
+ match->wc.masks.ct_nw_dst = mask;
+}
+
+void
+match_set_ct_nw_proto(struct match *match, uint8_t ct_nw_proto)
+{
+ match->flow.ct_nw_proto = ct_nw_proto;
+ match->wc.masks.ct_nw_proto = UINT8_MAX;
+}
+
+void
+match_set_ct_tp_src(struct match *match, ovs_be16 ct_tp_src)
+{
+ match_set_ct_tp_src_masked(match, ct_tp_src, OVS_BE16_MAX);
+}
+
+void
+match_set_ct_tp_src_masked(struct match *match, ovs_be16 port, ovs_be16 mask)
+{
+ match->flow.ct_tp_src = port & mask;
+ match->wc.masks.ct_tp_src = mask;
+}
+
+void
+match_set_ct_tp_dst(struct match *match, ovs_be16 ct_tp_dst)
+{
+ match_set_ct_tp_dst_masked(match, ct_tp_dst, OVS_BE16_MAX);
+}
+
+void
+match_set_ct_tp_dst_masked(struct match *match, ovs_be16 port, ovs_be16 mask)
+{
+ match->flow.ct_tp_dst = port & mask;
+ match->wc.masks.ct_tp_dst = mask;
+}
+
+void
+match_set_ct_ipv6_src(struct match *match, const struct in6_addr *src)
+{
+ match->flow.ct_ipv6_src = *src;
+ match->wc.masks.ct_ipv6_src = in6addr_exact;
+}
+
+void
+match_set_ct_ipv6_src_masked(struct match *match, const struct in6_addr *src,
+ const struct in6_addr *mask)
+{
+ match->flow.ct_ipv6_src = ipv6_addr_bitand(src, mask);
+ match->wc.masks.ct_ipv6_src = *mask;
+}
+
+void
+match_set_ct_ipv6_dst(struct match *match, const struct in6_addr *dst)
+{
+ match->flow.ct_ipv6_dst = *dst;
+ match->wc.masks.ct_ipv6_dst = in6addr_exact;
+}
+
+void
+match_set_ct_ipv6_dst_masked(struct match *match, const struct in6_addr *dst,
+ const struct in6_addr *mask)
+{
+ match->flow.ct_ipv6_dst = ipv6_addr_bitand(dst, mask);
+ match->wc.masks.ct_ipv6_dst = *mask;
+}
+
+void
match_set_dl_type(struct match *match, ovs_be16 dl_type)
{
match->wc.masks.dl_type = OVS_BE16_MAX;
@@ -1075,7 +1168,7 @@ match_format(const struct match *match, struct ds *s, int priority)
int i;
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 36);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 37);
if (priority != OFP_DEFAULT_PRIORITY) {
ds_put_format(s, "%spriority=%s%d,",
@@ -1137,6 +1230,21 @@ match_format(const struct match *match, struct ds *s, int priority)
format_ct_label_masked(s, &f->ct_label, &wc->masks.ct_label);
}
+ format_ip_netmask(s, "ct_nw_src", f->ct_nw_src,
+ wc->masks.ct_nw_src);
+ format_ipv6_netmask(s, "ct_ipv6_src", &f->ct_ipv6_src,
+ &wc->masks.ct_ipv6_src);
+ format_ip_netmask(s, "ct_nw_dst", f->ct_nw_dst,
+ wc->masks.ct_nw_dst);
+ format_ipv6_netmask(s, "ct_ipv6_dst", &f->ct_ipv6_dst,
+ &wc->masks.ct_ipv6_dst);
+ if (wc->masks.ct_nw_proto) {
+ ds_put_format(s, "%sct_nw_proto=%s%"PRIu8",",
+ colors.param, colors.end, f->ct_nw_proto);
+ format_be16_masked(s, "ct_tp_src", f->ct_tp_src, wc->masks.ct_tp_src);
+ format_be16_masked(s, "ct_tp_dst", f->ct_tp_dst, wc->masks.ct_tp_dst);
+ }
+
if (wc->masks.dl_type) {
skip_type = true;
if (f->dl_type == htons(ETH_TYPE_IP)) {
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index 9d635a368..38d051eb0 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -246,6 +246,20 @@ mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)
return !wc->masks.ct_mark;
case MFF_CT_LABEL:
return ovs_u128_is_zero(wc->masks.ct_label);
+ case MFF_CT_NW_PROTO:
+ return !wc->masks.ct_nw_proto;
+ case MFF_CT_NW_SRC:
+ return !wc->masks.ct_nw_src;
+ case MFF_CT_NW_DST:
+ return !wc->masks.ct_nw_dst;
+ case MFF_CT_TP_SRC:
+ return !wc->masks.ct_tp_src;
+ case MFF_CT_TP_DST:
+ return !wc->masks.ct_tp_dst;
+ case MFF_CT_IPV6_SRC:
+ return ipv6_mask_is_any(&wc->masks.ct_ipv6_src);
+ case MFF_CT_IPV6_DST:
+ return ipv6_mask_is_any(&wc->masks.ct_ipv6_dst);
CASE_MFF_REGS:
return !wc->masks.regs[mf->id - MFF_REG0];
CASE_MFF_XREGS:
@@ -383,7 +397,7 @@ mf_is_mask_valid(const struct mf_field *mf, const union mf_value *mask)
* Sets inspected bits in 'wc', if non-NULL. */
static bool
mf_are_prereqs_ok__(const struct mf_field *mf, const struct flow *flow,
- const struct flow_wildcards *mask OVS_UNUSED,
+ const struct flow_wildcards *mask,
struct flow_wildcards *wc)
{
switch (mf->prereqs) {
@@ -402,6 +416,14 @@ mf_are_prereqs_ok__(const struct mf_field *mf, const struct flow *flow,
return eth_type_mpls(flow->dl_type);
case MFP_IP_ANY:
return is_ip_any(flow);
+ case MFP_CT_VALID:
+ return is_ct_valid(flow, mask, wc);
+ case MFP_CTV4_VALID:
+ return flow->dl_type == htons(ETH_TYPE_IP)
+ && is_ct_valid(flow, mask, wc);
+ case MFP_CTV6_VALID:
+ return flow->dl_type == htons(ETH_TYPE_IPV6)
+ && is_ct_valid(flow, mask, wc);
case MFP_TCP:
/* Matching !FRAG_LATER is not enforced (mask is not checked). */
return is_tcp(flow, wc) && !(flow->nw_frag & FLOW_NW_FRAG_LATER);
@@ -475,6 +497,13 @@ mf_is_value_valid(const struct mf_field *mf, const union mf_value *value)
case MFF_CT_ZONE:
case MFF_CT_MARK:
case MFF_CT_LABEL:
+ case MFF_CT_NW_PROTO:
+ case MFF_CT_NW_SRC:
+ case MFF_CT_NW_DST:
+ case MFF_CT_IPV6_SRC:
+ case MFF_CT_IPV6_DST:
+ case MFF_CT_TP_SRC:
+ case MFF_CT_TP_DST:
CASE_MFF_REGS:
CASE_MFF_XREGS:
CASE_MFF_XXREGS:
@@ -649,6 +678,34 @@ mf_get_value(const struct mf_field *mf, const struct flow *flow,
value->be128 = hton128(flow->ct_label);
break;
+ case MFF_CT_NW_PROTO:
+ value->u8 = flow->ct_nw_proto;
+ break;
+
+ case MFF_CT_NW_SRC:
+ value->be32 = flow->ct_nw_src;
+ break;
+
+ case MFF_CT_NW_DST:
+ value->be32 = flow->ct_nw_dst;
+ break;
+
+ case MFF_CT_IPV6_SRC:
+ value->ipv6 = flow->ct_ipv6_src;
+ break;
+
+ case MFF_CT_IPV6_DST:
+ value->ipv6 = flow->ct_ipv6_dst;
+ break;
+
+ case MFF_CT_TP_SRC:
+ value->be16 = flow->ct_tp_src;
+ break;
+
+ case MFF_CT_TP_DST:
+ value->be16 = flow->ct_tp_dst;
+ break;
+
CASE_MFF_REGS:
value->be32 = htonl(flow->regs[mf->id - MFF_REG0]);
break;
@@ -911,6 +968,34 @@ mf_set_value(const struct mf_field *mf,
match_set_ct_label(match, ntoh128(value->be128));
break;
+ case MFF_CT_NW_PROTO:
+ match_set_ct_nw_proto(match, value->u8);
+ break;
+
+ case MFF_CT_NW_SRC:
+ match_set_ct_nw_src(match, value->be32);
+ break;
+
+ case MFF_CT_NW_DST:
+ match_set_ct_nw_dst(match, value->be32);
+ break;
+
+ case MFF_CT_IPV6_SRC:
+ match_set_ct_ipv6_src(match, &value->ipv6);
+ break;
+
+ case MFF_CT_IPV6_DST:
+ match_set_ct_ipv6_dst(match, &value->ipv6);
+ break;
+
+ case MFF_CT_TP_SRC:
+ match_set_ct_tp_src(match, value->be16);
+ break;
+
+ case MFF_CT_TP_DST:
+ match_set_ct_tp_dst(match, value->be16);
+ break;
+
CASE_MFF_REGS:
match_set_reg(match, mf->id - MFF_REG0, ntohl(value->be32));
break;
@@ -1242,6 +1327,34 @@ mf_set_flow_value(const struct mf_field *mf,
flow->ct_label = ntoh128(value->be128);
break;
+ case MFF_CT_NW_PROTO:
+ flow->ct_nw_proto = value->u8;
+ break;
+
+ case MFF_CT_NW_SRC:
+ flow->ct_nw_src = value->be32;
+ break;
+
+ case MFF_CT_NW_DST:
+ flow->ct_nw_dst = value->be32;
+ break;
+
+ case MFF_CT_IPV6_SRC:
+ flow->ct_ipv6_src = value->ipv6;
+ break;
+
+ case MFF_CT_IPV6_DST:
+ flow->ct_ipv6_dst = value->ipv6;
+ break;
+
+ case MFF_CT_TP_SRC:
+ flow->ct_tp_src = value->be16;
+ break;
+
+ case MFF_CT_TP_DST:
+ flow->ct_tp_dst = value->be16;
+ break;
+
CASE_MFF_REGS:
flow->regs[mf->id - MFF_REG0] = ntohl(value->be32);
break;
@@ -1571,6 +1684,41 @@ mf_set_wild(const struct mf_field *mf, struct match *match, char **err_str)
memset(&match->wc.masks.ct_label, 0, sizeof(match->wc.masks.ct_label));
break;
+ case MFF_CT_NW_PROTO:
+ match->flow.ct_nw_proto = 0;
+ match->wc.masks.ct_nw_proto = 0;
+ break;
+
+ case MFF_CT_NW_SRC:
+ match->flow.ct_nw_src = 0;
+ match->wc.masks.ct_nw_src = 0;
+ break;
+
+ case MFF_CT_NW_DST:
+ match->flow.ct_nw_dst = 0;
+ match->wc.masks.ct_nw_dst = 0;
+ break;
+
+ case MFF_CT_IPV6_SRC:
+ memset(&match->flow.ct_ipv6_src, 0, sizeof(match->flow.ct_ipv6_src));
+ WC_UNMASK_FIELD(&match->wc, ct_ipv6_src);
+ break;
+
+ case MFF_CT_IPV6_DST:
+ memset(&match->flow.ct_ipv6_dst, 0, sizeof(match->flow.ct_ipv6_dst));
+ WC_UNMASK_FIELD(&match->wc, ct_ipv6_dst);
+ break;
+
+ case MFF_CT_TP_SRC:
+ match->flow.ct_tp_src = 0;
+ match->wc.masks.ct_tp_src = 0;
+ break;
+
+ case MFF_CT_TP_DST:
+ match->flow.ct_tp_dst = 0;
+ match->wc.masks.ct_tp_dst = 0;
+ break;
+
CASE_MFF_REGS:
match_set_reg_masked(match, mf->id - MFF_REG0, 0, 0);
break;
@@ -1773,6 +1921,13 @@ mf_set(const struct mf_field *mf,
switch (mf->id) {
case MFF_CT_ZONE:
+ case MFF_CT_NW_PROTO:
+ case MFF_CT_NW_SRC:
+ case MFF_CT_NW_DST:
+ case MFF_CT_IPV6_SRC:
+ case MFF_CT_IPV6_DST:
+ case MFF_CT_TP_SRC:
+ case MFF_CT_TP_DST:
case MFF_RECIRC_ID:
case MFF_CONJ_ID:
case MFF_IN_PORT:
diff --git a/lib/meta-flow.xml b/lib/meta-flow.xml
index 3db0f825e..5efd43100 100644
--- a/lib/meta-flow.xml
+++ b/lib/meta-flow.xml
@@ -2479,6 +2479,98 @@ actions=clone(load:0->NXM_OF_IN_PORT[],output:123)
parameter to the <code>ct</code> action, to the connection to which the
current packet belongs.
</field>
+
+ <p>
+ Open vSwitch 2.8 introduced the matching support for connection
+ tracker original direction 5-tuple fields.
+ </p>
+
+ <p>
+ For non-committed non-related connections the conntrack original
+ direction tuple fields always have the same values as the
+ corresponding headers in the packet itself. For any other packets of
+ a committed connection the conntrack original direction tuple fields
+ reflect the values from that initial non-committed non-related packet,
+ and thus may be different from the actual packet headers, as the
+ actual packet headers may be in reverse direction (for reply packets),
+ transformed by NAT (when \fBnat\fR option was applied to the
+ connection), or be of different protocol (i.e., when an ICMP response
+ is sent to an UDP packet). In case of related connections, e.g., an
+ FTP data connection, the original direction tuple contains the
+ original direction headers from the master connection, e.g., an FTP
+ control connection.
+ </p>
+
+ <p>
+ The following fields are populated by the ct action, and require a
+ match to a valid connection tracking state as a prerequisite, in
+ addition to the IP or IPv6 ethertype match. Examples of valid
+ connection tracking state matches include \fBct_state=+new\fR,
+ \fBct_state=+est\fR, \fBct_state=+rel\fR, and \fBct_state=+trk-inv\fR.
+ </p>
+
+ <field id="MFF_CT_NW_SRC" title="Connection Tracking Original Direction IPv4 Source Address">
+ Matches IPv4 conntrack original direction tuple source address.
+ See the paragraphs above for general description to the
+ conntrack original direction tuple. Introduced in Open vSwitch
+ 2.8.
+ </field>
+
+ <field id="MFF_CT_NW_DST" title="Connection Tracking Original Direction IPv4 Destination Address">
+ Matches IPv4 conntrack original direction tuple destination address.
+ See the paragraphs above for general description to the
+ conntrack original direction tuple. Introduced in Open vSwitch
+ 2.8.
+ </field>
+
+ <field id="MFF_CT_IPV6_SRC" title="Connection Tracking Original Direction IPv6 Source Address">
+ Matches IPv6 conntrack original direction tuple source address.
+ See the paragraphs above for general description to the
+ conntrack original direction tuple. Introduced in Open vSwitch
+ 2.8.
+ </field>
+
+ <field id="MFF_CT_IPV6_DST" title="Connection Tracking Original Direction IPv6 Destination Address">
+ Matches IPv6 conntrack original direction tuple destination address.
+ See the paragraphs above for general description to the
+ conntrack original direction tuple. Introduced in Open vSwitch
+ 2.8.
+ </field>
+
+ <field id="MFF_CT_NW_PROTO" title="Connection Tracking Original Direction IP Protocol">
+ Matches conntrack original direction tuple IP protocol type,
+ which is specified as a decimal number between 0 and 255,
+ inclusive (e.g. 1 to match ICMP packets or 6 to match TCP
+ packets). In case of, for example, an ICMP response to an UDP
+ packet, this may be different from the IP protocol type of the
+ packet itself. See the paragraphs above for general description
+ to the conntrack original direction tuple. Introduced in Open
+ vSwitch 2.8.
+ </field>
+
+ <field id="MFF_CT_TP_SRC" title="Connection Tracking Original Direction Transport Layer Source Port">
+ Bitwise match on the conntrack original direction tuple
+ transport source, when
+ <code>MFF_CT_NW_PROTO</code> has value 6 for TCP, 17 for UDP, or
+ 132 for SCTP. When <code>MFF_CT_NW_PROTO</code> has value 1 for
+ ICMP, or 58 for ICMPv6, the lower 8 bits of
+ <code>MFF_CT_TP_SRC</code> matches the conntrack original
+ direction ICMP type. See the paragraphs above for general
+ description to the conntrack original direction
+ tuple. Introduced in Open vSwitch 2.8.
+ </field>
+
+ <field id="MFF_CT_TP_DST" title="Connection Tracking Original Direction Transport Layer Source Port">
+ Bitwise match on the conntrack original direction tuple
+ transport destination port, when
+ <code>MFF_CT_NW_PROTO</code> has value 6 for TCP, 17 for UDP, or
+ 132 for SCTP. When <code>MFF_CT_NW_PROTO</code> has value 1 for
+ ICMP, or 58 for ICMPv6, the lower 8 bits of
+ <code>MFF_CT_TP_DST</code> matches the conntrack original
+ direction ICMP code. See the paragraphs above for general
+ description to the conntrack original direction
+ tuple. Introduced in Open vSwitch 2.8.
+ </field>
</group>
<group title="Register">
diff --git a/lib/nx-match.c b/lib/nx-match.c
index bcc134746..35a312f0e 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -970,7 +970,7 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,
int match_len;
int i;
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 36);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 37);
/* Metadata. */
if (match->wc.masks.dp_hash) {
@@ -1118,7 +1118,21 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,
htonl(match->wc.masks.ct_mark));
nxm_put_128m(b, MFF_CT_LABEL, oxm, hton128(flow->ct_label),
hton128(match->wc.masks.ct_label));
-
+ nxm_put_32m(b, MFF_CT_NW_SRC, oxm,
+ flow->ct_nw_src, match->wc.masks.ct_nw_src);
+ nxm_put_ipv6(b, MFF_CT_IPV6_SRC, oxm,
+ &flow->ct_ipv6_src, &match->wc.masks.ct_ipv6_src);
+ nxm_put_32m(b, MFF_CT_NW_DST, oxm,
+ flow->ct_nw_dst, match->wc.masks.ct_nw_dst);
+ nxm_put_ipv6(b, MFF_CT_IPV6_DST, oxm,
+ &flow->ct_ipv6_dst, &match->wc.masks.ct_ipv6_dst);
+ if (flow->ct_nw_proto) {
+ nxm_put_8(b, MFF_CT_NW_PROTO, oxm, flow->ct_nw_proto);
+ nxm_put_16m(b, MFF_CT_TP_SRC, oxm,
+ flow->ct_tp_src, match->wc.masks.ct_tp_src);
+ nxm_put_16m(b, MFF_CT_TP_DST, oxm,
+ flow->ct_tp_dst, match->wc.masks.ct_tp_dst);
+ }
/* OpenFlow 1.1+ Metadata. */
nxm_put_64m(b, MFF_METADATA, oxm,
flow->metadata, match->wc.masks.metadata);
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 743b4ab3a..d73ecab54 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -4168,6 +4168,22 @@ parse_odp_key_mask_attr(const char *s, const struct simap *port_names,
SCAN_SINGLE("ct_mark(", uint32_t, u32, OVS_KEY_ATTR_CT_MARK);
SCAN_SINGLE("ct_label(", ovs_u128, u128, OVS_KEY_ATTR_CT_LABELS);
+ SCAN_BEGIN("ct_tuple4(", struct ovs_key_ct_tuple_ipv4) {
+ SCAN_FIELD("src=", ipv4, ipv4_src);
+ SCAN_FIELD("dst=", ipv4, ipv4_dst);
+ SCAN_FIELD("proto=", u8, ipv4_proto);
+ SCAN_FIELD("tp_src=", be16, src_port);
+ SCAN_FIELD("tp_dst=", be16, dst_port);
+ } SCAN_END(OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4);
+
+ SCAN_BEGIN("ct_tuple6(", struct ovs_key_ct_tuple_ipv6) {
+ SCAN_FIELD("src=", in6_addr, ipv6_src);
+ SCAN_FIELD("dst=", in6_addr, ipv6_dst);
+ SCAN_FIELD("proto=", u8, ipv6_proto);
+ SCAN_FIELD("tp_src=", be16, src_port);
+ SCAN_FIELD("tp_dst=", be16, dst_port);
+ } SCAN_END(OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6);
+
SCAN_BEGIN_NESTED("tunnel(", OVS_KEY_ATTR_TUNNEL) {
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);
@@ -4418,6 +4434,29 @@ odp_flow_key_from_flow__(const struct odp_flow_key_parms *parms,
nl_msg_put_unspec(buf, OVS_KEY_ATTR_CT_LABELS, &data->ct_label,
sizeof(data->ct_label));
}
+ if (parms->support.ct_orig_tuple && flow->ct_nw_proto) {
+ if (flow->dl_type == htons(ETH_TYPE_IP)) {
+ struct ovs_key_ct_tuple_ipv4 ct = {
+ data->ct_nw_src,
+ data->ct_nw_dst,
+ data->ct_tp_src,
+ data->ct_tp_dst,
+ data->ct_nw_proto,
+ };
+ nl_msg_put_unspec(buf, OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4, &ct,
+ sizeof ct);
+ } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
+ struct ovs_key_ct_tuple_ipv6 ct = {
+ data->ct_ipv6_src,
+ data->ct_ipv6_dst,
+ data->ct_tp_src,
+ data->ct_tp_dst,
+ data->ct_nw_proto,
+ };
+ nl_msg_put_unspec(buf, OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6, &ct,
+ sizeof ct);
+ }
+ }
if (parms->support.recirc) {
nl_msg_put_u32(buf, OVS_KEY_ATTR_RECIRC_ID, data->recirc_id);
nl_msg_put_u32(buf, OVS_KEY_ATTR_DP_HASH, data->dp_hash);
@@ -4614,6 +4653,19 @@ odp_key_from_pkt_metadata(struct ofpbuf *buf, const struct pkt_metadata *md)
nl_msg_put_unspec(buf, OVS_KEY_ATTR_CT_LABELS, &md->ct_label,
sizeof(md->ct_label));
}
+ if (md->ct_orig_tuple_ipv6) {
+ if (md->ct_orig_tuple.ipv6.ipv6_proto) {
+ nl_msg_put_unspec(buf, OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6,
+ &md->ct_orig_tuple.ipv6,
+ sizeof md->ct_orig_tuple.ipv6);
+ }
+ } else {
+ if (md->ct_orig_tuple.ipv4.ipv4_proto) {
+ nl_msg_put_unspec(buf, OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4,
+ &md->ct_orig_tuple.ipv4,
+ sizeof md->ct_orig_tuple.ipv4);
+ }
+ }
}
/* Add an ingress port attribute if 'odp_in_port' is not the magical
@@ -4682,6 +4734,21 @@ odp_key_to_pkt_metadata(const struct nlattr *key, size_t key_len,
wanted_attrs &= ~(1u << OVS_KEY_ATTR_CT_LABELS);
break;
}
+ case OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4: {
+ const struct ovs_key_ct_tuple_ipv4 *ct = nl_attr_get(nla);
+ md->ct_orig_tuple.ipv4 = *ct;
+ md->ct_orig_tuple_ipv6 = false;
+ wanted_attrs &= ~(1u << OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4);
+ break;
+ }
+ case OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6: {
+ const struct ovs_key_ct_tuple_ipv6 *ct = nl_attr_get(nla);
+
+ md->ct_orig_tuple.ipv6 = *ct;
+ md->ct_orig_tuple_ipv6 = true;
+ wanted_attrs &= ~(1u << OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6);
+ break;
+ }
case OVS_KEY_ATTR_TUNNEL: {
enum odp_key_fitness res;
@@ -5255,6 +5322,25 @@ odp_flow_key_to_flow__(const struct nlattr *key, size_t key_len,
flow->ct_label = *cl;
expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_CT_LABELS;
}
+ if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4)) {
+ const struct ovs_key_ct_tuple_ipv4 *ct = nl_attr_get(attrs[OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4]);
+ flow->ct_nw_src = ct->ipv4_src;
+ flow->ct_nw_dst = ct->ipv4_dst;
+ flow->ct_nw_proto = ct->ipv4_proto;
+ flow->ct_tp_src = ct->src_port;
+ flow->ct_tp_dst = ct->dst_port;
+ expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4;
+ }
+ if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6)) {
+ const struct ovs_key_ct_tuple_ipv6 *ct = nl_attr_get(attrs[OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6]);
+
+ flow->ct_ipv6_src = ct->ipv6_src;
+ flow->ct_ipv6_dst = ct->ipv6_dst;
+ flow->ct_nw_proto = ct->ipv6_proto;
+ flow->ct_tp_src = ct->src_port;
+ flow->ct_tp_dst = ct->dst_port;
+ expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6;
+ }
if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_TUNNEL)) {
enum odp_key_fitness res;
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 42011bccd..2d0081575 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -127,6 +127,7 @@ void odp_portno_names_destroy(struct hmap *portno_names);
* OVS_KEY_ATTR_CT_ZONE 2 2 4 8
* OVS_KEY_ATTR_CT_MARK 4 -- 4 8
* OVS_KEY_ATTR_CT_LABEL 16 -- 4 20
+ * OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6 40 -- 4 44
* OVS_KEY_ATTR_ETHERNET 12 -- 4 16
* OVS_KEY_ATTR_ETHERTYPE 2 2 4 8 (outer VLAN ethertype)
* OVS_KEY_ATTR_VLAN 2 2 4 8
@@ -136,13 +137,13 @@ 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 572
+ * total 616
*
* 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 640
-BUILD_ASSERT_DECL(FLOW_WC_SEQ == 36);
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 37);
/* A buffer with sufficient size and alignment to hold an nlattr-formatted flow
* key. An array of "struct nlattr" might not, in theory, be sufficiently
@@ -185,6 +186,9 @@ struct odp_support {
* 'ct_state'. The above 'ct_state' member must be true for this
* to make sense */
bool ct_state_nat;
+
+ bool ct_orig_tuple; /* Conntrack original direction tuple matching
+ * supported. */
};
struct odp_flow_key_parms {
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index ed66dd19f..78814806d 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -101,7 +101,7 @@ ofputil_netmask_to_wcbits(ovs_be32 netmask)
void
ofputil_wildcard_from_ofpfw10(uint32_t ofpfw, struct flow_wildcards *wc)
{
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 36);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 37);
/* Initialize most of wc. */
flow_wildcards_init_catchall(wc);
diff --git a/lib/packets.h b/lib/packets.h
index cbf049651..a5a483bc8 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -100,9 +100,14 @@ struct pkt_metadata {
uint32_t skb_priority; /* Packet priority for QoS. */
uint32_t pkt_mark; /* Packet mark. */
uint8_t ct_state; /* Connection state. */
+ bool ct_orig_tuple_ipv6;
uint16_t ct_zone; /* Connection zone. */
uint32_t ct_mark; /* Connection mark. */
ovs_u128 ct_label; /* Connection label. */
+ union { /* Populated only for non-zero 'ct_state'. */
+ struct ovs_key_ct_tuple_ipv4 ipv4;
+ struct ovs_key_ct_tuple_ipv6 ipv6; /* Used only if */
+ } ct_orig_tuple; /* 'ct_orig_tuple_ipv6' is set */
union flow_in_port in_port; /* Input port. */
struct flow_tnl tunnel; /* Encapsulating tunnel parameters. Note that
* if 'ip_dst' == 0, the rest of the fields may
diff --git a/ofproto/ofproto-dpif-rid.h b/ofproto/ofproto-dpif-rid.h
index c3575911f..dfe54ff32 100644
--- a/ofproto/ofproto-dpif-rid.h
+++ b/ofproto/ofproto-dpif-rid.h
@@ -99,7 +99,7 @@ struct rule;
/* Metadata for restoring pipeline context after recirculation. Helpers
* are inlined below to keep them together with the definition for easier
* updates. */
-BUILD_ASSERT_DECL(FLOW_WC_SEQ == 36);
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 37);
struct frozen_metadata {
/* Metadata in struct flow. */
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 94ba25799..677789d50 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -3061,6 +3061,17 @@ clear_conntrack(struct xlate_ctx *ctx)
flow->ct_zone = 0;
flow->ct_mark = 0;
flow->ct_label = OVS_U128_ZERO;
+
+ flow->ct_nw_proto = 0;
+ flow->ct_tp_src = 0;
+ flow->ct_tp_dst = 0;
+ if (flow->dl_type == htons(ETH_TYPE_IP)) {
+ flow->ct_nw_src = 0;
+ flow->ct_nw_dst = 0;
+ } if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
+ memset(&flow->ct_ipv6_src, 0, sizeof flow->ct_ipv6_src);
+ memset(&flow->ct_ipv6_dst, 0, sizeof flow->ct_ipv6_dst);
+ }
}
static bool
@@ -3095,7 +3106,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
/* If 'struct flow' gets additional metadata, we'll need to zero it out
* before traversing a patch port. */
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 36);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 37);
memset(&flow_tnl, 0, sizeof flow_tnl);
if (!xport) {
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index ca07d8c7e..4ef71661e 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -1184,6 +1184,7 @@ CHECK_FEATURE(ct_zone)
CHECK_FEATURE(ct_mark)
CHECK_FEATURE__(ct_label, ct_label, ct_label.u64.lo, 1)
CHECK_FEATURE__(ct_state_nat, ct_state, ct_state, CS_TRACKED|CS_SRC_NAT)
+CHECK_FEATURE__(ct_orig_tuple, ct_orig_tuple, ct_nw_proto, 1)
#undef CHECK_FEATURE
#undef CHECK_FEATURE__
@@ -1210,6 +1211,7 @@ check_support(struct dpif_backer *backer)
backer->support.odp.ct_label = check_ct_label(backer);
backer->support.odp.ct_state_nat = check_ct_state_nat(backer);
+ backer->support.odp.ct_orig_tuple = check_ct_orig_tuple(backer);
}
static int
diff --git a/tests/odp.at b/tests/odp.at
index db1e82780..459ff355d 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -163,7 +163,7 @@ s/$/)/' odp-base.txt
echo
echo '# Valid forms with conntrack fields.'
- sed 's/\(eth([[^)]]*)\),/\1,ct_state(+trk),ct_zone(0x5\/0xff),ct_mark(0x10305070\/0xf0f0f0f0),ct_label(0x1234567890abcdef1234567890abcdef\/0x102030405060708090a0b0c0d0e0f0),/' odp-base.txt
+ sed 's/\(eth([[^)]]*)\),/\1,ct_state(+trk),ct_zone(0x5\/0xff),ct_mark(0x10305070\/0xf0f0f0f0),ct_label(0x1234567890abcdef1234567890abcdef\/0x102030405060708090a0b0c0d0e0f0),ct_tuple4(src=10.10.10.10,dst=20.20.20.20,proto=17,tp_src=1,tp_dst=2),/' odp-base.txt
echo
echo '# Valid forms with IP first fragment.'
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index f1415e49b..6f55d433b 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -8447,7 +8447,7 @@ AT_CHECK([cat ofctl_monitor.log], [0], [dnl
NXT_PACKET_IN (xid=0x0): cookie=0x0 total_len=42 in_port=1 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=1,tp_dst=2 udp_csum:e9d6
dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,in_port=2 (via action) data_len=42 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2,in_port=2 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:0a,dl_dst=50:54:00:00:00:09,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=2,tp_dst=1 udp_csum:e9d6
])
@@ -8470,7 +8470,7 @@ AT_CHECK([cat ofctl_monitor.log], [0], [dnl
NXT_PACKET_IN (xid=0x0): cookie=0x0 total_len=42 in_port=1 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=3,tp_dst=4 udp_csum:e9d2
dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,in_port=2 (via action) data_len=42 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=3,ct_tp_dst=4,in_port=2 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:0a,dl_dst=50:54:00:00:00:09,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4,tp_dst=3 udp_csum:e9d2
])
@@ -8520,7 +8520,7 @@ dnl happens because the ct_state field is available only after recirc.
AT_CHECK([cat ofctl_monitor.log], [0], [dnl
NXT_PACKET_IN (xid=0x0): cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
udp6,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,ipv6_src=2001:db8::1,ipv6_dst=2001:db8::2,ipv6_label=0x00000,nw_tos=112,nw_ecn=0,nw_ttl=128,tp_src=1,tp_dst=2 udp_csum:a466
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=62 ct_state=est|rpl|trk,in_port=2 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=62 ct_state=est|rpl|trk,ct_ipv6_src=2001:db8::1,ct_ipv6_dst=2001:db8::2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2,in_port=2 (via action) data_len=62 (unbuffered)
udp6,vlan_tci=0x0000,dl_src=50:54:00:00:00:0a,dl_dst=50:54:00:00:00:09,ipv6_src=2001:db8::2,ipv6_dst=2001:db8::1,ipv6_label=0x00000,nw_tos=112,nw_ecn=0,nw_ttl=128,tp_src=2,tp_dst=1 udp_csum:a466
])
@@ -8631,7 +8631,7 @@ OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
dnl Check this output. Only one reply must be there
AT_CHECK([cat ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,in_port=2 (via action) data_len=42 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2,in_port=2 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:0a,dl_dst=50:54:00:00:00:09,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=2,tp_dst=1 udp_csum:e9d6
dnl
OFPT_ECHO_REQUEST (xid=0x0): 0 bytes of payload
@@ -8724,13 +8724,13 @@ AT_CHECK([cat ofctl_monitor.log], [0], [dnl
NXT_PACKET_IN (xid=0x0): cookie=0x0 total_len=42 in_port=1 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=1,tp_dst=2 udp_csum:e9d6
dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,in_port=2 (via action) data_len=42 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2,in_port=2 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:0a,dl_dst=50:54:00:00:00:09,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=2,tp_dst=1 udp_csum:e9d6
dnl
NXT_PACKET_IN (xid=0x0): cookie=0x0 total_len=42 in_port=3 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=1,tp_dst=2 udp_csum:e9d6
dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_zone=1,in_port=4 (via action) data_len=42 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_zone=1,ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2,in_port=4 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:0a,dl_dst=50:54:00:00:00:09,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=2,tp_dst=1 udp_csum:e9d6
])
@@ -8777,10 +8777,10 @@ OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
dnl Check this output. We only see the latter two packets, not the first.
AT_CHECK([cat ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=new|trk,in_port=1 (via action) data_len=42 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=new|trk,ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2,in_port=1 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=1,tp_dst=2 udp_csum:e9d6
dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,in_port=2 (via action) data_len=42 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2,in_port=2 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:0a,dl_dst=50:54:00:00:00:09,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=2,tp_dst=1 udp_csum:e9d6
])
@@ -8827,10 +8827,10 @@ OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
dnl Check this output. We only see the first and the last packet
AT_CHECK([cat ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=47 ct_state=new|trk,in_port=1 (via action) data_len=47 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=47 ct_state=new|trk,ct_nw_src=172.16.0.1,ct_nw_dst=172.16.0.2,ct_nw_proto=17,ct_tp_src=41614,ct_tp_dst=5555,in_port=1 (via action) data_len=47 (unbuffered)
udp,vlan_tci=0x0000,dl_src=e6:4c:47:35:28:c9,dl_dst=c6:f9:4e:cb:72:db,nw_src=172.16.0.1,nw_dst=172.16.0.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=41614,tp_dst=5555 udp_csum:2096
dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=75 ct_state=rel|rpl|trk,in_port=2 (via action) data_len=75 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=75 ct_state=rel|rpl|trk,ct_nw_src=172.16.0.1,ct_nw_dst=172.16.0.2,ct_nw_proto=17,ct_tp_src=41614,ct_tp_dst=5555,in_port=2 (via action) data_len=75 (unbuffered)
icmp,vlan_tci=0x0000,dl_src=c6:f9:4e:cb:72:db,dl_dst=e6:4c:47:35:28:c9,nw_src=172.16.0.2,nw_dst=172.16.0.1,nw_tos=192,nw_ecn=0,nw_ttl=64,icmp_type=3,icmp_code=3 icmp_csum:553f
])
@@ -8888,10 +8888,10 @@ dnl
NXT_PACKET_IN (xid=0x0): cookie=0x0 total_len=42 ct_mark=0x5,in_port=1 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=5,tp_dst=6 udp_csum:e9ce
dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_mark=0x1,in_port=2 (via action) data_len=42 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_mark=0x1,ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2,in_port=2 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:0a,dl_dst=50:54:00:00:00:09,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=2,tp_dst=1 udp_csum:e9d6
dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_mark=0x3,in_port=2 (via action) data_len=42 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_mark=0x3,ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=3,ct_tp_dst=4,in_port=2 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:0a,dl_dst=50:54:00:00:00:09,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4,tp_dst=3 udp_csum:e9d2
])
@@ -8936,10 +8936,10 @@ OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
dnl Check this output.
AT_CHECK([cat ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_label=0x1,in_port=2 (via action) data_len=42 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_label=0x1,ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2,in_port=2 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:0a,dl_dst=50:54:00:00:00:09,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=2,tp_dst=1 udp_csum:e9d6
dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_label=0x2,in_port=2 (via action) data_len=42 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_label=0x2,ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=3,ct_tp_dst=4,in_port=2 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:0a,dl_dst=50:54:00:00:00:09,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4,tp_dst=3 udp_csum:e9d2
])
@@ -9215,7 +9215,7 @@ dnl
NXT_PACKET_IN (xid=0x0): cookie=0x0 total_len=42 in_port=1 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=1,tp_dst=2 udp_csum:e9d6
dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,in_port=2 (via action) data_len=42 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2,in_port=2 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:0a,dl_dst=50:54:00:00:00:09,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=2,tp_dst=1 udp_csum:e9d6
dnl
NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 in_port=2 (via action) data_len=42 (unbuffered)
diff --git a/tests/ofproto.at b/tests/ofproto.at
index c899ec80c..94b1149fb 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -2406,6 +2406,13 @@ metadata in_port in_port_oxm pkt_mark ct_mark ct_label reg0 reg1 reg2 reg3 reg4
ct_zone: exact match or wildcard
ct_mark: arbitrary mask
ct_label: arbitrary mask
+ ct_nw_proto: exact match or wildcard
+ ct_nw_src: arbitrary mask
+ ct_nw_dst: arbitrary mask
+ ct_ipv6_src: arbitrary mask
+ ct_ipv6_dst: arbitrary mask
+ ct_tp_src: arbitrary mask
+ ct_tp_dst: arbitrary mask
reg0: arbitrary mask
reg1: arbitrary mask
reg2: arbitrary mask
diff --git a/tests/system-traffic.at b/tests/system-traffic.at
index bb7a49760..4ba4b089b 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -612,6 +612,7 @@ AT_BANNER([conntrack])
AT_SETUP([conntrack - controller])
CHECK_CONNTRACK()
OVS_TRAFFIC_VSWITCHD_START()
+AT_CHECK([ovs-appctl vlog/set dpif:dbg dpif_netdev:dbg ofproto_dpif_upcall:dbg])
ADD_NAMESPACES(at_ns0, at_ns1)
@@ -645,7 +646,7 @@ dnl Check this output. We only see the latter two packets, not the first.
AT_CHECK([cat ofctl_monitor.log], [0], [dnl
NXT_PACKET_IN2 (xid=0x0): total_len=42 in_port=1 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_tos=0,nw_ecn=0,nw_ttl=0,tp_src=1,tp_dst=2 udp_csum:0
-NXT_PACKET_IN2 (xid=0x0): cookie=0x0 total_len=42 ct_state=est|rpl|trk,in_port=2 (via action) data_len=42 (unbuffered)
+NXT_PACKET_IN2 (xid=0x0): cookie=0x0 total_len=42 ct_state=est|rpl|trk,ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2,in_port=2 (via action) data_len=42 (unbuffered)
udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_tos=0,nw_ecn=0,nw_ttl=0,tp_src=2,tp_dst=1 udp_csum:0
])
@@ -1353,9 +1354,9 @@ dnl Check this output. We only see the latter two packets, not the first.
AT_CHECK([cat ofctl_monitor.log], [0], [dnl
NXT_PACKET_IN2 (xid=0x0): table_id=1 cookie=0x0 total_len=75 ct_state=inv|trk,in_port=2 (via action) data_len=75 (unbuffered)
icmp,vlan_tci=0x0000,dl_src=c6:f5:4e:cb:72:db,dl_dst=f6:4c:47:35:28:c9,nw_src=172.16.0.4,nw_dst=172.16.0.3,nw_tos=192,nw_ecn=0,nw_ttl=64,icmp_type=3,icmp_code=3 icmp_csum:da49
-NXT_PACKET_IN2 (xid=0x0): table_id=1 cookie=0x0 total_len=47 ct_state=new|trk,in_port=1 (via action) data_len=47 (unbuffered)
+NXT_PACKET_IN2 (xid=0x0): table_id=1 cookie=0x0 total_len=47 ct_state=new|trk,ct_nw_src=172.16.0.1,ct_nw_dst=172.16.0.2,ct_nw_proto=17,ct_tp_src=41614,ct_tp_dst=5555,in_port=1 (via action) data_len=47 (unbuffered)
udp,vlan_tci=0x0000,dl_src=e6:4c:47:35:28:c9,dl_dst=c6:f9:4e:cb:72:db,nw_src=172.16.0.1,nw_dst=172.16.0.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=41614,tp_dst=5555 udp_csum:2096
-NXT_PACKET_IN2 (xid=0x0): table_id=1 cookie=0x0 total_len=75 ct_state=rel|rpl|trk,in_port=2 (via action) data_len=75 (unbuffered)
+NXT_PACKET_IN2 (xid=0x0): table_id=1 cookie=0x0 total_len=75 ct_state=rel|rpl|trk,ct_nw_src=172.16.0.1,ct_nw_dst=172.16.0.2,ct_nw_proto=17,ct_tp_src=41614,ct_tp_dst=5555,in_port=2 (via action) data_len=75 (unbuffered)
icmp,vlan_tci=0x0000,dl_src=c6:f9:4e:cb:72:db,dl_dst=e6:4c:47:35:28:c9,nw_src=172.16.0.2,nw_dst=172.16.0.1,nw_tos=192,nw_ecn=0,nw_ttl=64,icmp_type=3,icmp_code=3 icmp_csum:553f
])
@@ -1369,7 +1370,7 @@ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.0.3)], [0], [dnl
OVS_TRAFFIC_VSWITCHD_STOP
AT_CLEANUP
-AT_SETUP([conntrack - IPv4 fragmentation ])
+AT_SETUP([conntrack - IPv4 fragmentation])
CHECK_CONNTRACK()
CHECK_CONNTRACK_FRAG()
OVS_TRAFFIC_VSWITCHD_START()
@@ -2818,6 +2819,72 @@ dnl separate from the above to easier identify issues in this code on different
dnl kernels.
CHECK_FTP_NAT_POST_RECIRC([seqadj], [10.1.1.240], [0x0a0101f0])
+
+dnl CHECK_FTP_NAT_ORIG_TUPLE(TITLE, IP_ADDR, IP_ADDR_AS_HEX)
+dnl
+dnl Checks the implementation of conntrack original direction tuple matching
+dnl with FTP ALGs in combination with NAT, with flow tables that implement
+dnl the NATing before the first round of recirculation - that is, the first
+dnl flow ct(nat, table=foo) then a subsequent flow will implement the
+dnl commiting of NATed and other connections with ct(nat..),output:foo.
+dnl
+dnl IP_ADDR must specify the NAT address in standard "10.1.1.x" format,
+dnl and IP_ADDR_AS_HEX must specify the same address as hex, eg 0x0a0101xx.
+m4_define([CHECK_FTP_NAT_ORIG_TUPLE], [dnl
+ CHECK_FTP_NAT([orig tuple $1], [$2], [dnl
+dnl track all IP traffic (includes nat and helper calls to non-NEW packets.)
+table=0 ip, action=ct(nat,table=1)
+dnl
+dnl ARP
+dnl
+table=0 priority=100 arp arp_op=1 action=move:OXM_OF_ARP_TPA[[]]->NXM_NX_REG2[[]],resubmit(,8),goto_table:10
+table=0 priority=10 arp action=normal
+table=0 priority=0 action=drop
+dnl
+dnl "ACL table"
+dnl
+dnl Allow all IP traffic with conntrack original direction IP source address
+dnl '10.1.1.1'. This should allow also reply packets and related packets in
+dnl both directions.
+table=1 ip, ct_state=+trk-inv, ct_nw_src=10.1.1.1 action=goto_table:2
+dnl Drop everything else.
+table=1 priority=0, action=drop
+dnl
+dnl "Conntrack commit table"
+dnl
+dnl Commit new outgoing FTP control connections. Must match on 'tcp' when
+dnl setting 'alg=ftp'.
+table=2 in_port=1 priority=100 ct_state=+new, tcp, tp_dst=21, action=ct(alg=ftp,commit,nat(src=$2)),2
+dnl Commit other new outgoing IP connections.
+table=2 in_port=1 priority=20 ct_state=+new, ip, action=ct(commit,nat(src=$2)),2
+dnl Commit incoming new IP connections. 'nat' may be needed for related
+dnl connections, and is harmless for connections that do not need it.
+table=2 in_port=2 priority=10 ct_state=+new, ip, action=ct(commit,nat),1
+dnl Just forward all the rest.
+table=2 priority=0 in_port=1 action=2
+table=2 priority=0 in_port=2 action=1
+dnl
+dnl MAC resolution table for IP in reg2, stores mac in OXM_OF_PKT_REG0
+dnl
+table=8,reg2=$3/0xffffffff,action=load:0x808888888888->OXM_OF_PKT_REG0[[]]
+table=8,priority=0,action=load:0->OXM_OF_PKT_REG0[[]]
+dnl ARP responder mac filled in at OXM_OF_PKT_REG0, or 0 for normal action.
+dnl TPA IP in reg2.
+dnl Swaps the fields of the ARP message to turn a query to a response.
+table=10 priority=100 arp xreg0=0 action=normal
+table=10 priority=10,arp,arp_op=1,action=load:2->OXM_OF_ARP_OP[[]],move:OXM_OF_ARP_SHA[[]]->OXM_OF_ARP_THA[[]],move:OXM_OF_PKT_REG0[[0..47]]->OXM_OF_ARP_SHA[[]],move:OXM_OF_ARP_SPA[[]]->OXM_OF_ARP_TPA[[]],move:NXM_NX_REG2[[]]->OXM_OF_ARP_SPA[[]],move:NXM_OF_ETH_SRC[[]]->NXM_OF_ETH_DST[[]],move:OXM_OF_PKT_REG0[[0..47]]->NXM_OF_ETH_SRC[[]],move:NXM_OF_IN_PORT[[]]->NXM_NX_REG3[[0..15]],load:0->NXM_OF_IN_PORT[[]],output:NXM_NX_REG3[[0..15]]
+table=10 priority=0 action=drop
+ ])
+])
+
+dnl Check that ct(nat,table=foo) works without TCP sequence adjustment with
+dnl an ACL table based on matching on conntrack original direction tuple only.
+CHECK_FTP_NAT_ORIG_TUPLE([], [10.1.1.9], [0x0a010109])
+
+dnl Check that ct(nat,table=foo) works with TCP sequence adjustment with
+dnl an ACL table based on matching on conntrack original direction tuple only.
+CHECK_FTP_NAT_ORIG_TUPLE([seqadj], [10.1.1.240], [0x0a0101f0])
+
AT_SETUP([conntrack - IPv6 HTTP with NAT])
CHECK_CONNTRACK()
CHECK_CONNTRACK_NAT()
@@ -2878,9 +2945,6 @@ NS_CHECK_EXEC([at_ns1], [ip -6 neigh add fc00::240 lladdr 80:88:88:88:88:88 dev
dnl Allow any traffic from ns0->ns1.
dnl Only allow nd, return traffic from ns1->ns0.
AT_DATA([flows.txt], [dnl
-dnl Allow other ICMPv6 both ways (without commit).
-table=1 priority=100 in_port=1 icmp6, action=2
-table=1 priority=100 in_port=2 icmp6, action=1
dnl track all IPv6 traffic (this includes NAT & help to non-NEW packets.)
table=0 priority=10 ip6, action=ct(nat,table=1)
table=0 priority=0 action=drop
@@ -2894,6 +2958,9 @@ table=1 in_port=2 ct_state=+new+rel tcp6 ipv6_dst=fc00::240 action=ct(commit,nat
dnl Allow established TCPv6 connections both ways, enforce NATting
table=1 in_port=1 ct_state=+est tcp6 ipv6_src=fc00::240 action=2
table=1 in_port=2 ct_state=+est tcp6 ipv6_dst=fc00::1 action=1
+dnl Allow other ICMPv6 both ways (without commit).
+table=1 priority=100 in_port=1 icmp6, action=2
+table=1 priority=100 in_port=2 icmp6, action=1
dnl Drop everything else.
table=1 priority=0, action=drop
])
@@ -2919,6 +2986,67 @@ tcp,orig=(src=fc00::2,dst=fc00::240,sport=<cleared>,dport=<cleared>),reply=(src=
OVS_TRAFFIC_VSWITCHD_STOP
AT_CLEANUP
+
+AT_SETUP([conntrack - IPv6 FTP with NAT - orig tuple])
+AT_SKIP_IF([test $HAVE_FTP = no])
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "fc00::1/96")
+NS_CHECK_EXEC([at_ns0], [ip link set dev p0 address 80:88:88:88:88:88])
+ADD_VETH(p1, at_ns1, br0, "fc00::2/96")
+dnl Would be nice if NAT could translate neighbor discovery messages, too.
+NS_CHECK_EXEC([at_ns1], [ip -6 neigh add fc00::240 lladdr 80:88:88:88:88:88 dev p1])
+
+dnl Allow any traffic from ns0->ns1.
+dnl Only allow nd, return traffic from ns1->ns0.
+AT_DATA([flows.txt], [dnl
+dnl track all IPv6 traffic (this includes NAT & help to non-NEW packets.)
+table=0 priority=10 ip6, action=ct(nat,table=1)
+table=0 priority=0 action=drop
+dnl
+dnl Table 1
+dnl
+dnl Allow other ICMPv6 both ways (without commit).
+table=1 priority=100 in_port=1 icmp6, action=2
+table=1 priority=100 in_port=2 icmp6, action=1
+dnl Allow new TCPv6 FTP control connections.
+table=1 priority=10 in_port=1 ct_state=+new+trk-inv tcp6 ct_nw_proto=6 ct_ipv6_src=fc00::1 ct_tp_dst=21 action=ct(alg=ftp,commit,nat(src=fc00::240)),2
+dnl Allow related TCPv6 connections from port 2 to the NATted address.
+table=1 priority=10 in_port=2 ct_state=+new+rel+trk-inv ipv6 ct_nw_proto=6 ct_ipv6_src=fc00::1 ct_tp_dst=21 action=ct(commit,nat),1
+dnl Allow established TCPv6 connections both ways, enforce NATting
+table=1 priority=10 in_port=1 ct_state=+est+trk-inv ipv6 ct_nw_proto=6 ct_ipv6_src=fc00::1 ct_tp_dst=21 action=2
+table=1 priority=10 in_port=2 ct_state=+est+trk-inv ipv6 ct_nw_proto=6 ct_ipv6_src=fc00::1 ct_tp_dst=21 action=1
+dnl Drop everything else.
+table=1 priority=0, action=drop
+])
+
+AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
+
+dnl Linux seems to take a little time to get its IPv6 stack in order. Without
+dnl waiting, we get occasional failures due to the following error:
+dnl "connect: Cannot assign requested address"
+OVS_WAIT_UNTIL([ip netns exec at_ns0 ping6 -c 1 fc00::2 >/dev/null])
+
+NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-l7.py ftp]], [ftp0.pid])
+OVS_WAIT_UNTIL([ip netns exec at_ns1 netstat -l | grep ftp])
+
+dnl FTP requests from p0->p1 should work fine.
+NS_CHECK_EXEC([at_ns0], [wget ftp://[[fc00::2]] -6 --no-passive-ftp -t 3 -T 1 --retry-connrefused -v --server-response --no-remove-listing -o wget0.log -d])
+
+dnl Discards CLOSE_WAIT and CLOSING
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fc00::2)], [0], [dnl
+tcp,orig=(src=fc00::1,dst=fc00::2,sport=<cleared>,dport=<cleared>),reply=(src=fc00::2,dst=fc00::240,sport=<cleared>,dport=<cleared>),protoinfo=(state=<cleared>),helper=ftp
+tcp,orig=(src=fc00::2,dst=fc00::240,sport=<cleared>,dport=<cleared>),reply=(src=fc00::1,dst=fc00::2,sport=<cleared>,dport=<cleared>),protoinfo=(state=<cleared>)
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+
AT_SETUP([conntrack - DNAT load balancing])
CHECK_CONNTRACK()
CHECK_CONNTRACK_NAT()