This patch implements ct_get_info() in dpif-netlink. It uses
NFNL_SUBSYS_CTNETLINK netlink subsystem to query conntrack info from
kernel datapath. Then, ofproto/trace can use the ct_get_info() to derive
the ct_state of the traced flow. System traffic tests are provided to verify
that ofproto/trace can get the correct ct_state for IPv4 TCP/UDP, and NAT
traffic.

Signed-off-by: Yi-Hung Wei <[email protected]>
---
 lib/dpif-netlink.c               |  10 +-
 lib/netlink-conntrack.c          | 204 +++++++++++++++++++++++++++++++++++++++
 lib/netlink-conntrack.h          |   3 +
 tests/system-kmod-macros.at      |   6 ++
 tests/system-traffic.at          | 181 ++++++++++++++++++++++++++++++++++
 tests/system-userspace-macros.at |   9 ++
 6 files changed, 412 insertions(+), 1 deletion(-)

diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
index 122c59614f5a..cc22cc6068c2 100644
--- a/lib/dpif-netlink.c
+++ b/lib/dpif-netlink.c
@@ -2901,6 +2901,14 @@ dpif_netlink_ct_flush(struct dpif *dpif OVS_UNUSED, 
const uint16_t *zone)
     }
 }
 
+static int
+dpif_netlink_ct_get_info(struct dpif *dpif OVS_UNUSED,
+                         struct ct_dpif_tuple *tuple, uint16_t zone,
+                         struct ct_dpif_info *info)
+{
+    return nl_ct_get_info(tuple, zone, info);
+}
+
 
 /* Meters */
 static void
@@ -2986,7 +2994,7 @@ const struct dpif_class dpif_netlink_class = {
     dpif_netlink_ct_dump_next,
     dpif_netlink_ct_dump_done,
     dpif_netlink_ct_flush,
-    NULL,                       /* ct_get_info */
+    dpif_netlink_ct_get_info,
     dpif_netlink_meter_get_features,
     dpif_netlink_meter_set,
     dpif_netlink_meter_get,
diff --git a/lib/netlink-conntrack.c b/lib/netlink-conntrack.c
index ac48b15bde90..93cd0ac2c34f 100644
--- a/lib/netlink-conntrack.c
+++ b/lib/netlink-conntrack.c
@@ -18,6 +18,7 @@
 
 #include "netlink-conntrack.h"
 
+#include <errno.h>
 #include <linux/netfilter/nfnetlink.h>
 #include <linux/netfilter/nfnetlink_conntrack.h>
 #include <linux/netfilter/nf_conntrack_common.h>
@@ -112,6 +113,9 @@ static bool nl_ct_attrs_to_ct_dpif_entry(struct 
ct_dpif_entry *entry,
         struct nlattr *attrs[ARRAY_SIZE(nfnlgrp_conntrack_policy)],
         uint8_t nfgen_family);
 
+static bool nl_ct_put_ct_tuple(struct ofpbuf *, struct ct_dpif_tuple *,
+                               enum ctattr_type);
+
 struct nl_ct_dump_state {
     struct nl_dump dump;
     struct ofpbuf buf;
@@ -325,6 +329,125 @@ nl_ct_flush_zone(uint16_t flush_zone)
 }
 #endif
 
+static bool
+nl_ct_cmp_ct_dpif_tuple(struct ct_dpif_tuple *t1, struct ct_dpif_tuple *t2)
+{
+    if (t1->l3_type != t2->l3_type || t1->ip_proto != t2->ip_proto) {
+        return false;
+    }
+
+    if (t1->l3_type == AF_INET) {
+        if (t1->src.ip != t2->src.ip || t1->dst.ip != t2->dst.ip) {
+            return false;
+        }
+    } else {
+        if (memcmp(&t1->src.in6, &t2->src.in6, sizeof t1->src.in6) ||
+            memcpy(&t1->dst.in6, &t2->dst.in6, sizeof t1->dst.in6)) {
+            return false;
+        }
+    }
+
+    if (t1->ip_proto == IPPROTO_ICMP || t1->ip_proto == IPPROTO_ICMPV6) {
+        if (t1->icmp_id != t2->icmp_id || t1->icmp_type != t2->icmp_type ||
+            t1->icmp_code != t2->icmp_code) {
+            return false;
+        }
+    } else {
+        if (t1->src_port != t2->src_port || t1->dst_port != t2->dst_port) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static void
+nl_ct_get_ct_state(struct ct_dpif_tuple *tuple, struct ct_dpif_entry *entry,
+                   uint32_t *ct_state)
+{
+    if (entry->status & IPS_SEEN_REPLY) {
+        *ct_state |= CS_ESTABLISHED;
+    }
+    if (!nl_ct_cmp_ct_dpif_tuple(tuple, &entry->tuple_orig)) {
+        *ct_state |= CS_REPLY_DIR;
+    }
+    if (entry->tuple_master.l3_type) {
+        *ct_state |= CS_RELATED;
+    }
+    if (entry->status & IPS_SRC_NAT_DONE) {
+        *ct_state |= *ct_state & CS_REPLY_DIR ? CS_DST_NAT : CS_SRC_NAT;
+    }
+    if (entry->status & IPS_DST_NAT_DONE) {
+        *ct_state |= *ct_state & CS_REPLY_DIR ? CS_SRC_NAT : CS_DST_NAT;
+    }
+    if (*ct_state == 0) {
+        *ct_state = CS_NEW;
+    }
+}
+
+int
+nl_ct_get_info(struct ct_dpif_tuple *tuple, uint16_t zone,
+               struct ct_dpif_info *info)
+{
+    struct nlattr *attrs[ARRAY_SIZE(nfnlgrp_conntrack_policy)];
+    struct ofpbuf request, *reply;
+    struct ct_dpif_entry entry;
+    enum nl_ct_event_type type;
+    uint8_t nfgen_family;
+    int err;
+
+    ofpbuf_init(&request, NL_DUMP_BUFSIZE);
+    nl_msg_put_nfgenmsg(&request, 0, tuple->l3_type, NFNL_SUBSYS_CTNETLINK,
+                        IPCTNL_MSG_CT_GET, NLM_F_REQUEST);
+    nl_msg_put_be16(&request, CTA_ZONE, htons(zone));
+    if (!nl_ct_put_ct_tuple(&request, tuple, CTA_TUPLE_ORIG)) {
+        ofpbuf_uninit(&request);
+        return EOPNOTSUPP;
+    }
+
+    err = nl_transact(NETLINK_NETFILTER, &request, &reply);
+    ofpbuf_uninit(&request);
+
+    if (err == ENOENT) {
+        info->ct_state = CS_TRACKED | CS_NEW;
+        return 0;
+    } else if (err) {
+        struct ds s = DS_EMPTY_INITIALIZER;
+
+        ct_dpif_format_tuple(&s, tuple);
+        VLOG_WARN_RL(&rl, "Could not get conntrack info for tuple %s, "
+                          "errono %d\n", ds_cstr(&s), err);
+        ds_destroy(&s);
+        return EOPNOTSUPP;
+    }
+
+    if (!nl_ct_parse_header_policy(reply, &type, &nfgen_family, attrs)) {
+        goto out;
+    }
+
+    uint16_t entry_zone = attrs[CTA_ZONE] ?
+                          ntohs(nl_attr_get_be16(attrs[CTA_ZONE])) : 0;
+    if (entry_zone != zone) {
+        VLOG_WARN_RL(&rl, "Received invalid zone number %d\n", entry_zone);
+        goto out;
+    }
+
+    memset(&entry, 0, sizeof(entry));
+    if (nl_ct_attrs_to_ct_dpif_entry(&entry, attrs, nfgen_family) == false) {
+        goto out;
+    }
+
+    nl_ct_get_ct_state(tuple, &entry, &info->ct_state);
+    info->ct_state |= CS_TRACKED;
+
+    ofpbuf_delete(reply);
+    return 0;
+
+out:
+    ofpbuf_delete(reply);
+    return EOPNOTSUPP;
+}
+
 /* Conntrack netlink parsing. */
 
 static bool
@@ -517,6 +640,87 @@ nl_ct_parse_tuple(struct nlattr *nla, struct ct_dpif_tuple 
*tuple,
     return parsed;
 }
 
+static bool
+nl_ct_put_tuple_ip(struct ofpbuf *buf, struct ct_dpif_tuple *tuple)
+{
+    size_t offset = nl_msg_start_nested(buf, CTA_TUPLE_IP);
+
+    if (tuple->l3_type == AF_INET) {
+        nl_msg_put_be32(buf, CTA_IP_V4_SRC, tuple->src.ip);
+        nl_msg_put_be32(buf, CTA_IP_V4_DST, tuple->dst.ip);
+    } else if (tuple->l3_type == AF_INET6) {
+        nl_msg_put_in6_addr(buf, CTA_IP_V6_SRC, &tuple->src.in6);
+        nl_msg_put_in6_addr(buf, CTA_IP_V6_DST, &tuple->dst.in6);
+    } else {
+        VLOG_WARN_RL(&rl, "Unsupported IP protocol: %"PRIu16".",
+                     tuple->l3_type);
+        return false;
+    }
+
+    nl_msg_end_nested(buf, offset);
+    return true;
+}
+
+static bool
+nl_ct_put_tuple_proto(struct ofpbuf *buf, struct ct_dpif_tuple *tuple)
+{
+    size_t offset = nl_msg_start_nested(buf, CTA_TUPLE_PROTO);
+
+    nl_msg_put_u8(buf, CTA_PROTO_NUM, tuple->ip_proto);
+
+    if (tuple->l3_type == AF_INET && tuple->ip_proto == IPPROTO_ICMP) {
+        nl_msg_put_be16(buf, CTA_PROTO_ICMP_ID, tuple->icmp_id);
+        nl_msg_put_u8(buf, CTA_PROTO_ICMP_TYPE, tuple->icmp_type);
+        nl_msg_put_u8(buf, CTA_PROTO_ICMP_CODE, tuple->icmp_code);
+    } else if (tuple->l3_type == AF_INET6 &&
+               tuple->ip_proto == IPPROTO_ICMPV6) {
+        nl_msg_put_be16(buf, CTA_PROTO_ICMPV6_ID, tuple->icmp_id);
+        nl_msg_put_u8(buf, CTA_PROTO_ICMPV6_TYPE, tuple->icmp_type);
+        nl_msg_put_u8(buf, CTA_PROTO_ICMPV6_CODE, tuple->icmp_code);
+    } else if (tuple->ip_proto == IPPROTO_TCP ||
+               tuple->ip_proto == IPPROTO_UDP) {
+        nl_msg_put_be16(buf, CTA_PROTO_SRC_PORT, tuple->src_port);
+        nl_msg_put_be16(buf, CTA_PROTO_DST_PORT, tuple->dst_port);
+    } else {
+        VLOG_WARN_RL(&rl, "Unsupported L4 protocol: %"PRIu8".",
+                     tuple->ip_proto);
+        return false;
+    }
+
+    nl_msg_end_nested(buf, offset);
+    return true;
+}
+
+static bool
+nl_ct_put_tuple(struct ofpbuf *buf, struct ct_dpif_tuple *tuple)
+{
+    if (!nl_ct_put_tuple_ip(buf, tuple)) {
+        return false;
+    }
+    if (!nl_ct_put_tuple_proto(buf, tuple)) {
+        return false;
+    }
+    return true;
+}
+
+static bool
+nl_ct_put_ct_tuple(struct ofpbuf *buf, struct ct_dpif_tuple *tuple,
+                   enum ctattr_type type)
+{
+    size_t offset = nl_msg_start_nested(buf, type);
+
+    if (type != CTA_TUPLE_ORIG && type != CTA_TUPLE_REPLY &&
+        type != CTA_TUPLE_MASTER) {
+        return false;
+    }
+    if (!nl_ct_put_tuple(buf, tuple)) {
+        return false;
+    }
+
+    nl_msg_end_nested(buf, offset);
+    return true;
+}
+
 /* Translate netlink TCP state to CT_DPIF_TCP state. */
 static uint8_t
 nl_ct_tcp_state_to_dpif(uint8_t state)
diff --git a/lib/netlink-conntrack.h b/lib/netlink-conntrack.h
index a95aa69a4cde..86d3adab0403 100644
--- a/lib/netlink-conntrack.h
+++ b/lib/netlink-conntrack.h
@@ -43,6 +43,9 @@ int nl_ct_dump_done(struct nl_ct_dump_state *);
 int nl_ct_flush(void);
 int nl_ct_flush_zone(uint16_t zone);
 
+int nl_ct_get_info(struct ct_dpif_tuple *, uint16_t zone,
+                   struct ct_dpif_info *info);
+
 bool nl_ct_parse_entry(struct ofpbuf *, struct ct_dpif_entry *,
                        enum nl_ct_event_type *);
 void nl_ct_format_event_entry(const struct ct_dpif_entry *,
diff --git a/tests/system-kmod-macros.at b/tests/system-kmod-macros.at
index 27498341a9f8..60751c215c3e 100644
--- a/tests/system-kmod-macros.at
+++ b/tests/system-kmod-macros.at
@@ -96,3 +96,9 @@ m4_define([CHECK_CONNTRACK_LOCAL_STACK])
 # always supports NAT, so no check is needed.
 #
 m4_define([CHECK_CONNTRACK_NAT])
+
+# CHECK_CT_DPIF_GET_INFO()
+#
+# Perform requirements checks for running ofproto/trace tests. The kernel
+# datapath does support ct_dpif_get_info(), so no check is needed.
+m4_define([CHECK_CT_DPIF_GET_INFO])
diff --git a/tests/system-traffic.at b/tests/system-traffic.at
index 522eaa615834..ccad3fab0ca4 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -3996,6 +3996,187 @@ ovs-ofctl -O OpenFlow15 dump-group-stats br0
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([conntrack - ofproto/trace])
+AT_SKIP_IF([test $HAVE_NC = no])
+CHECK_CONNTRACK()
+CHECK_CT_DPIF_GET_INFO()
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
+
+dnl Test IPv4 TCP traffic
+AT_DATA([flows.txt], [dnl
+table=0,priority=100,arp,action=normal
+table=0,priority=10,in_port=1,tcp,action=ct(table=1,zone=1)
+table=0,priority=10,in_port=2,tcp,ct_state=-trk,action=ct(table=1,zone=1)
+
+table=1,priority=10,in_port=1,tcp,ct_state=+trk+new,action=ct(commit,zone=1),2
+table=1,priority=10,in_port=1,tcp,ct_state=+trk+est,action=2
+table=1,priority=10,in_port=2,tcp,ct_state=+trk+new,action=drop
+table=1,priority=10,in_port=2,tcp,ct_state=+trk+est+rpl,action=1
+])
+AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
+
+dnl Start TCP server in ns1
+NETNS_DAEMONIZE([at_ns1], [nc -l 22222], [nc_tcp_server.pid])
+dnl use this file as payload file for ncat
+AT_CHECK([dd if=/dev/urandom of=payload100.bin bs=100 count=1 2> /dev/null])
+on_exit 'rm -f payload100.bin'
+
+dnl Run ofproto/trace before connection is established.
+AT_CHECK([ovs-appctl ofproto/trace br0 
"in_port=1,dl_type=0x800,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_proto=6,tcp_src=33333,tcp_dst=22222"],
 [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: ct(commit,zone=1),3
+])
+AT_CHECK([grep -c 'ct_state=new|trk' stdout], [0], [2
+])
+
+AT_CHECK([ovs-appctl ofproto/trace br0 
"in_port=2,dl_type=0x800,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_proto=6,tcp_src=22222,tcp_dst=33333"],
 [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: drop
+])
+AT_CHECK([grep -c 'ct_state=new|trk' stdout], [0], [2
+])
+
+dnl Establish TCP connection.
+NS_CHECK_EXEC([at_ns0], [nc $NC_EOF_OPT -p 33333 10.1.1.2 22222 < 
payload100.bin])
+
+AT_CHECK([ovs-appctl ofproto/trace br0 
"in_port=1,dl_type=0x800,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_proto=6,tcp_src=33333,tcp_dst=22222"],
 [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: 3
+])
+AT_CHECK([grep -c 'ct_state=est|trk' stdout], [0], [2
+])
+
+AT_CHECK([ovs-appctl ofproto/trace br0 
"in_port=2,dl_type=0x800,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_proto=6,tcp_src=22222,tcp_dst=33333"],
 [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: 2
+])
+AT_CHECK([grep -c 'ct_state=est|rpl|trk' stdout], [0], [2
+])
+
+
+dnl Test IPv4 UDP traffic
+AT_DATA([flows.txt], [dnl
+table=0,priority=100,arp,action=normal
+table=0,priority=10,in_port=1,udp,action=ct(table=1,zone=1)
+table=0,priority=10,in_port=2,udp,ct_state=-trk,action=ct(table=1,zone=1)
+table=0,priority=1,in_port=2,udp,action=drop
+
+table=1,priority=10,in_port=1,udp,ct_state=+trk+new,action=ct(commit,zone=1),2
+table=1,priority=10,in_port=1,udp,ct_state=+trk+est,action=2
+table=1,priority=10,in_port=2,udp,ct_state=+trk+new,action=drop
+table=1,priority=10,in_port=2,udp,ct_state=+trk+est+rpl,action=1
+])
+
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
+
+dnl Start UDP server in ns1
+NETNS_DAEMONIZE([at_ns1], [nc -ul 44444 < payload100.bin], [nc_udp_server.pid])
+
+dnl Run ofproto/trace before connection is established.
+AT_CHECK([ovs-appctl ofproto/trace br0 
"in_port=1,dl_type=0x800,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_proto=17,udp_src=33333,udp_dst=44444"],
 [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: ct(commit,zone=1),3
+])
+AT_CHECK([grep -c 'ct_state=new|trk' stdout], [0], [2
+])
+
+AT_CHECK([ovs-appctl ofproto/trace br0 
"in_port=2,dl_type=0x800,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_proto=17,udp_src=44444,udp_dst=33333"],
 [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: drop
+])
+AT_CHECK([grep -c 'ct_state=new|trk' stdout], [0], [2
+])
+
+dnl Establish UDP connection.
+NS_CHECK_EXEC([at_ns0], [nc $NC_EOF_OPT -u -p 33333 10.1.1.2 44444 < 
payload100.bin], [0], [stdout])
+
+AT_CHECK([ovs-appctl ofproto/trace br0 
"in_port=1,dl_type=0x800,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_proto=17,udp_src=33333,udp_dst=44444"],
 [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: 3
+])
+AT_CHECK([grep -c 'ct_state=est|trk' stdout], [0], [2
+])
+
+AT_CHECK([ovs-appctl ofproto/trace br0 
"in_port=2,dl_type=0x800,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_proto=17,udp_src=44444,udp_dst=33333"],
 [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: 2
+])
+AT_CHECK([grep -c 'ct_state=est|rpl|trk' stdout], [0], [2
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([conntrack - ofproto/trace SNAT])
+AT_SKIP_IF([test $HAVE_NC = no])
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+CHECK_CT_DPIF_GET_INFO()
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
+NS_CHECK_EXEC([at_ns0], [ip link set dev p0 address 80:88:88:88:88:88])
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
+
+dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from 
ns1->ns0.
+AT_DATA([flows.txt], [dnl
+in_port=1,ip,ct_state=-trk,action=ct(commit,table=1,zone=1,nat(src=10.1.1.240))
+in_port=2,ct_state=-trk,ip,action=ct(table=1,zone=1,nat)
+dnl
+dnl ARP
+priority=100 arp arp_op=1 
action=move:OXM_OF_ARP_TPA[[]]->NXM_NX_REG2[[]],resubmit(,8),goto_table:10
+priority=10 arp action=normal
+priority=0,action=drop
+dnl
+table=1,in_port=1,ip,ct_state=+trk+snat,action=2
+table=1,in_port=2,ct_state=+trk+rpl+est+dnat,ct_zone=1,ip,action=1
+dnl
+dnl MAC resolution table for IP in reg2, stores mac in OXM_OF_PKT_REG0
+table=8,reg2=0x0a0101f0/0xfffffff0,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
+])
+
+AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
+
+dnl Start TCP server in ns1
+NETNS_DAEMONIZE([at_ns1], [nc -l 22222], [nc_tcp_server.pid])
+dnl use this file as payload file for ncat
+AT_CHECK([dd if=/dev/urandom of=payload100.bin bs=100 count=1 2> /dev/null])
+on_exit 'rm -f payload100.bin'
+dnl Establish TCP connection.
+NS_CHECK_EXEC([at_ns0], [nc $NC_EOF_OPT -p 33333 10.1.1.2 22222 < 
payload100.bin])
+
+AT_CHECK([ovs-appctl ofproto/trace br0 
"in_port=1,dl_type=0x800,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_proto=6,tcp_src=33333,tcp_dst=22222"],
 [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: 3
+])
+AT_CHECK([grep -c 'ct_state=est|trk|snat' stdout], [0], [2
+])
+
+AT_CHECK([ovs-appctl ofproto/trace br0 
"in_port=2,dl_type=0x800,nw_src=10.1.1.2,nw_dst=10.1.1.240,nw_proto=6,tcp_src=22222,tcp_dst=33333"],
 [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: 2
+])
+AT_CHECK([grep -c 'ct_state=est|rpl|trk|dnat' stdout], [0], [2
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_BANNER([802.1ad])
 
 AT_SETUP([802.1ad - vlan_limit])
diff --git a/tests/system-userspace-macros.at b/tests/system-userspace-macros.at
index f3337f04499a..b2c47c7fea1b 100644
--- a/tests/system-userspace-macros.at
+++ b/tests/system-userspace-macros.at
@@ -99,3 +99,12 @@ m4_define([CHECK_CONNTRACK_LOCAL_STACK],
 # datapath supports NAT.
 #
 m4_define([CHECK_CONNTRACK_NAT])
+
+# CHECK_CT_DPIF_GET_INFO()
+#
+# Perform requirements checks for running ofproto/trace tests. The userspace
+# datapath does not support ct_dpif_get_info().
+m4_define([CHECK_CT_DPIF_GET_INFO],
+[
+    AT_SKIP_IF([:])
+])
-- 
2.7.4

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to