The ct_state of an uncommmited new flow is marked as related if the flow is in the conntrack expectation table. In order for ofproto/trace to identify the ct_state of a new related flow, this patch utilizes NFNL_SUBSYS_CTNETLINK_EXP netlink subsystem to query the conntrack expectation table, therefore, we can mark the ct_state of a related flow correctly.
Signed-off-by: Yi-Hung Wei <[email protected]> --- lib/ct-dpif.h | 13 ++++ lib/netlink-conntrack.c | 203 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/netlink-conntrack.h | 2 + tests/atlocal.in | 3 + tests/system-traffic.at | 137 ++++++++++++++++++++++++++++++++ 5 files changed, 358 insertions(+) diff --git a/lib/ct-dpif.h b/lib/ct-dpif.h index f4ca07b5e776..9122e8cd752b 100644 --- a/lib/ct-dpif.h +++ b/lib/ct-dpif.h @@ -177,6 +177,19 @@ struct ct_dpif_info { uint32_t ct_state; }; +struct ct_dpif_exp_entry { + struct ct_dpif_tuple tuple; + struct ct_dpif_tuple tuple_mask; + struct ct_dpif_tuple tuple_master; + struct ct_dpif_tuple tuple_nat; + struct ct_dpif_helper helper; + uint32_t timeout; + uint32_t id; + uint32_t nat_dir; + uint32_t exp_flags; + uint32_t exp_class; +}; + enum { CT_STATS_UDP, CT_STATS_TCP, diff --git a/lib/netlink-conntrack.c b/lib/netlink-conntrack.c index 93cd0ac2c34f..de1e467dc61a 100644 --- a/lib/netlink-conntrack.c +++ b/lib/netlink-conntrack.c @@ -70,6 +70,15 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); #define CTA_TIMESTAMP_START 1 #define CTA_TIMESTAMP_STOP 2 +#define CTA_EXPECT_ZONE (CTA_EXPECT_HELP_NAME + 1) +#define CTA_EXPECT_FLAGS (CTA_EXPECT_HELP_NAME + 2) +#define CTA_EXPECT_CLASS (CTA_EXPECT_HELP_NAME + 3) +#define CTA_EXPECT_NAT (CTA_EXPECT_HELP_NAME + 4) +#define CTA_EXPECT_FN (CTA_EXPECT_HELP_NAME + 5) + +#define CTA_EXPECT_NAT_DIR 1 +#define CTA_EXPECT_NAT_TUPLE 2 + #define IPS_TEMPLATE_BIT 11 #define IPS_TEMPLATE (1 << IPS_TEMPLATE_BIT) @@ -99,6 +108,20 @@ static const struct nl_policy nfnlgrp_conntrack_policy[] = { * CTA_LABELS_MASK are not received from kernel. */ }; +static const struct nl_policy nfnlgrp_conntrack_exp_policy[] = { + [CTA_EXPECT_MASTER] = { .type = NL_A_NESTED, .optional = false }, + [CTA_EXPECT_TUPLE] = { .type = NL_A_NESTED, .optional = false }, + [CTA_EXPECT_MASK] = { .type = NL_A_NESTED, .optional = false }, + [CTA_EXPECT_TIMEOUT] = { .type = NL_A_U32, .optional = false }, + [CTA_EXPECT_ID] = { .type = NL_A_U32, .optional = false }, + [CTA_EXPECT_HELP_NAME] = { .type = NL_A_STRING, .optional = true }, + [CTA_EXPECT_ZONE] = { .type = NL_A_U16, .optional = true }, + [CTA_EXPECT_FLAGS] = { .type = NL_A_U32, .optional = false }, + [CTA_EXPECT_CLASS] = { .type = NL_A_U32, .optional = false }, + [CTA_EXPECT_NAT] = { .type = NL_A_NESTED, .optional = true }, + [CTA_EXPECT_FN] = { .type = NL_A_STRING, .optional = true }, +}; + /* Declarations for conntrack netlink dumping. */ static void nl_msg_put_nfgenmsg(struct ofpbuf *msg, size_t expected_payload, int family, uint8_t subsystem, uint8_t cmd, @@ -108,13 +131,22 @@ static bool nl_ct_parse_header_policy(struct ofpbuf *buf, enum nl_ct_event_type *event_type, uint8_t *nfgen_family, struct nlattr *attrs[ARRAY_SIZE(nfnlgrp_conntrack_policy)]); +static bool nl_ct_parse_ct_exp_policy(struct ofpbuf *buf, + uint8_t *nfgen_family, + struct nlattr *attrs[ARRAY_SIZE(nfnlgrp_conntrack_exp_policy)]); 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_exp_attrs_to_ct_dpif_exp_entry( + struct ct_dpif_exp_entry *entry, + struct nlattr *attrs[ARRAY_SIZE(nfnlgrp_conntrack_exp_policy)], + uint8_t nfgen_family); static bool nl_ct_put_ct_tuple(struct ofpbuf *, struct ct_dpif_tuple *, enum ctattr_type); +static bool nl_ct_put_ct_exp_tuple(struct ofpbuf *, struct ct_dpif_tuple *, + enum ctattr_expect); struct nl_ct_dump_state { struct nl_dump dump; @@ -385,6 +417,51 @@ nl_ct_get_ct_state(struct ct_dpif_tuple *tuple, struct ct_dpif_entry *entry, } } +/* Searches nf_conntrack's expectation table for 'tuple' in 'zone'. + * Sets 'exp' and returns 0 if we successfully find a match. Otherwise, + * returns non-zero errno. */ +int +nl_ct_get_expect(struct ct_dpif_tuple *tuple, uint16_t zone, + struct ct_dpif_exp_entry *exp) +{ + struct nlattr *attrs[ARRAY_SIZE(nfnlgrp_conntrack_exp_policy)]; + struct ofpbuf request, *reply; + uint8_t nfgen_family; + int err; + + ofpbuf_init(&request, NL_DUMP_BUFSIZE); + + nl_msg_put_nfgenmsg(&request, 0, tuple->l3_type, NFNL_SUBSYS_CTNETLINK_EXP, + IPCTNL_MSG_EXP_GET, NLM_F_REQUEST); + nl_msg_put_be16(&request, CTA_EXPECT_ZONE, htons(zone)); + if (!nl_ct_put_ct_exp_tuple(&request, tuple, CTA_EXPECT_TUPLE)) { + ofpbuf_uninit(&request); + return EOPNOTSUPP; + } + + err = nl_transact(NETLINK_NETFILTER, &request, &reply); + ofpbuf_uninit(&request); + if (err) { + return err; + } + + if (!nl_ct_parse_ct_exp_policy(reply, &nfgen_family, attrs)) { + goto out; + } + + memset(exp, 0, sizeof(*exp)); + if (!nl_ct_exp_attrs_to_ct_dpif_exp_entry(exp, attrs, nfgen_family)) { + goto out; + } + + ofpbuf_delete(reply); + return 0; + +out: + ofpbuf_delete(reply); + return EOPNOTSUPP; +} + int nl_ct_get_info(struct ct_dpif_tuple *tuple, uint16_t zone, struct ct_dpif_info *info) @@ -392,10 +469,17 @@ nl_ct_get_info(struct ct_dpif_tuple *tuple, uint16_t zone, struct nlattr *attrs[ARRAY_SIZE(nfnlgrp_conntrack_policy)]; struct ofpbuf request, *reply; struct ct_dpif_entry entry; + struct ct_dpif_exp_entry exp; enum nl_ct_event_type type; uint8_t nfgen_family; int err; + if (nl_ct_get_expect(tuple, zone, &exp) == 0) { + info->ct_state = CS_TRACKED | CS_NEW | CS_RELATED; + free(exp.helper.name); + return 0; + } + 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); @@ -721,6 +805,23 @@ nl_ct_put_ct_tuple(struct ofpbuf *buf, struct ct_dpif_tuple *tuple, return true; } +static bool +nl_ct_put_ct_exp_tuple(struct ofpbuf *buf, struct ct_dpif_tuple *tuple, + enum ctattr_expect type) +{ + size_t offset = nl_msg_start_nested(buf, type); + + if (type != CTA_EXPECT_TUPLE && type != CTA_EXPECT_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) @@ -945,6 +1046,42 @@ nl_ct_parse_header_policy(struct ofpbuf *buf, } static bool +nl_ct_parse_ct_exp_policy(struct ofpbuf *buf, uint8_t *nfgen_family, + struct nlattr *attrs[ARRAY_SIZE(nfnlgrp_conntrack_exp_policy)]) +{ + struct nlmsghdr *nlh; + struct nfgenmsg *nfm; + + nlh = ofpbuf_at(buf, 0, NLMSG_HDRLEN); + nfm = ofpbuf_at(buf, NLMSG_HDRLEN, sizeof *nfm); + if (!nfm) { + VLOG_ERR_RL(&rl, "Received bad nfnl message (no nfgenmsg)."); + return false; + } + if (NFNL_SUBSYS_ID(nlh->nlmsg_type) != NFNL_SUBSYS_CTNETLINK_EXP) { + VLOG_ERR_RL(&rl, "Received non-conntrack message (subsystem: %u).", + NFNL_SUBSYS_ID(nlh->nlmsg_type)); + return false; + } + if (nfm->version != NFNETLINK_V0) { + VLOG_ERR_RL(&rl, "Received unsupported nfnetlink version (%u).", + NFNL_MSG_TYPE(nfm->version)); + return false; + } + + if (!nl_policy_parse(buf, NLMSG_HDRLEN + sizeof *nfm, + nfnlgrp_conntrack_exp_policy, attrs, + ARRAY_SIZE(nfnlgrp_conntrack_exp_policy))) { + VLOG_ERR_RL(&rl, "Received bad nfnl message (policy)."); + return false; + } + + *nfgen_family = nfm->nfgen_family; + + return true; +} + +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) @@ -1008,6 +1145,72 @@ nl_ct_attrs_to_ct_dpif_entry(struct ct_dpif_entry *entry, return true; } +static bool +nl_ct_parse_exp_nat(struct nlattr *nla, struct ct_dpif_exp_entry *entry, + uint16_t l3_type) +{ + static const struct nl_policy policy[] = { + [CTA_EXPECT_NAT_DIR] = { .type = NL_A_BE32, .optional = false }, + [CTA_EXPECT_NAT_TUPLE] = { .type = NL_A_NESTED, .optional = false }, + }; + struct nlattr *attrs[ARRAY_SIZE(policy)]; + bool parsed; + + parsed = nl_parse_nested(nla, policy, attrs, ARRAY_SIZE(policy)); + + if (parsed) { + entry->nat_dir = ntohl(nl_attr_get_be32(attrs[CTA_EXPECT_NAT_DIR])); + if (!nl_ct_parse_tuple(attrs[CTA_EXPECT_NAT_TUPLE], &entry->tuple_nat, + l3_type)) { + return false; + } + } else { + VLOG_ERR_RL(&rl, "Could not parse nested expect NAT options. " + "Possibly incompatible Linux kernel version."); + } + return parsed; +} + +static bool +nl_ct_exp_attrs_to_ct_dpif_exp_entry(struct ct_dpif_exp_entry *entry, + struct nlattr *attrs[ARRAY_SIZE(nfnlgrp_conntrack_exp_policy)], + uint8_t nfgen_family) +{ + if (!nl_ct_parse_tuple(attrs[CTA_EXPECT_TUPLE], &entry->tuple, + nfgen_family)) { + return false; + } + if (!nl_ct_parse_tuple(attrs[CTA_EXPECT_MASK], &entry->tuple_mask, + nfgen_family)) { + return false; + } + if (!nl_ct_parse_tuple(attrs[CTA_EXPECT_MASTER], &entry->tuple_master, + nfgen_family)) { + return false; + } + if (attrs[CTA_EXPECT_NAT] && + !nl_ct_parse_exp_nat(attrs[CTA_EXPECT_NAT], entry, nfgen_family)) { + return false; + } + if (attrs[CTA_EXPECT_TIMEOUT]) { + entry->timeout = ntohl(nl_attr_get_be32(attrs[CTA_EXPECT_TIMEOUT])); + } + if (attrs[CTA_EXPECT_ID]) { + entry->id = ntohl(nl_attr_get_be32(attrs[CTA_EXPECT_ID])); + } + if (attrs[CTA_EXPECT_FLAGS]) { + entry->exp_flags = ntohl(nl_attr_get_be32(attrs[CTA_EXPECT_FLAGS])); + } + if (attrs[CTA_EXPECT_CLASS]) { + entry->exp_class = ntohl(nl_attr_get_be32(attrs[CTA_EXPECT_CLASS])); + } + if (attrs[CTA_EXPECT_HELP_NAME]) { + entry->helper.name = xstrdup(nl_attr_get_string( + attrs[CTA_EXPECT_HELP_NAME])); + } + return true; +} + bool nl_ct_parse_entry(struct ofpbuf *buf, struct ct_dpif_entry *entry, enum nl_ct_event_type *event_type) diff --git a/lib/netlink-conntrack.h b/lib/netlink-conntrack.h index 86d3adab0403..4c5798bdefc8 100644 --- a/lib/netlink-conntrack.h +++ b/lib/netlink-conntrack.h @@ -45,6 +45,8 @@ 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); +int nl_ct_get_expect(struct ct_dpif_tuple *, uint16_t zone, + struct ct_dpif_exp_entry *); bool nl_ct_parse_entry(struct ofpbuf *, struct ct_dpif_entry *, enum nl_ct_event_type *); diff --git a/tests/atlocal.in b/tests/atlocal.in index 7c5e9e3357e5..35a1c7ebc7d0 100644 --- a/tests/atlocal.in +++ b/tests/atlocal.in @@ -162,6 +162,9 @@ fi # Set HAVE_TCPDUMP find_command tcpdump +# Set HAVE_CONNTRACK +find_command conntrack + CURL_OPT="-g -v --max-time 1 --retry 2 --retry-delay 1 --connect-timeout 1" # Turn off proxies. diff --git a/tests/system-traffic.at b/tests/system-traffic.at index ccad3fab0ca4..727f97f7a7dd 100644 --- a/tests/system-traffic.at +++ b/tests/system-traffic.at @@ -4113,6 +4113,143 @@ AT_CHECK([grep -c 'ct_state=est|rpl|trk' stdout], [0], [2 OVS_TRAFFIC_VSWITCHD_STOP AT_CLEANUP +AT_SETUP([conntrack - ofproto/trace FTP]) +AT_SKIP_IF([test $HAVE_FTP = no]) +AT_SKIP_IF([test $HAVE_CONNTRACK = no]) +CHECK_CONNTRACK() +CHECK_CONNTRACK_ALG() +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 Allow any traffic from ns0->ns1. Only allow nd, return traffic from ns1->ns0. +AT_DATA([flows1.txt], [dnl +table=0,priority=1,action=drop +table=0,priority=10,arp,action=normal +table=0,priority=10,icmp,action=normal +table=0,priority=100,in_port=1,tcp,action=ct(alg=ftp,commit),2 +table=0,priority=100,in_port=2,tcp,action=ct(table=1) +table=1,in_port=2,tcp,ct_state=+trk+est,action=1 +table=1,in_port=2,tcp,ct_state=+trk+rel,action=1 +]) + +dnl Similar policy but without allowing all traffic from ns0->ns1. +AT_DATA([flows2.txt], [dnl +table=0,priority=1,action=drop +table=0,priority=10,arp,action=normal +table=0,priority=10,icmp,action=normal + +dnl Allow outgoing TCP connections, and treat them as FTP +table=0,priority=100,in_port=1,tcp,action=ct(table=1) +table=1,in_port=1,tcp,ct_state=+trk+new,action=ct(commit,alg=ftp),2 +table=1,in_port=1,tcp,ct_state=+trk+est,action=2 + +dnl Allow incoming FTP data connections and responses to existing connections +table=0,priority=100,in_port=2,tcp,action=ct(table=1) +table=1,in_port=2,tcp,ct_state=+trk+new+rel,action=ct(commit),1 +table=1,in_port=2,tcp,ct_state=+trk+est,action=1 +table=1,in_port=2,tcp,ct_state=+trk-new+rel,action=1 +]) + +AT_CHECK([ovs-ofctl --bundle replace-flows br0 flows1.txt]) + +OVS_START_L7([at_ns0], [ftp]) +OVS_START_L7([at_ns1], [ftp]) + +dnl FTP requests from p1->p0 should fail due to network failure. +dnl Try 3 times, in 1 second intervals. +NS_CHECK_EXEC([at_ns1], [wget ftp://10.1.1.1 --no-passive-ftp -t 3 -T 1 -v -o wget1.log], [4]) +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.1)], [0], [dnl +]) + +dnl FTP requests from p0->p1 should work fine. +NS_CHECK_EXEC([at_ns0], [wget ftp://10.1.1.2 --no-passive-ftp -t 3 -T 1 --retry-connrefused -v -o wget0.log]) +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0], [dnl +tcp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=<cleared>,dport=<cleared>),reply=(src=10.1.1.2,dst=10.1.1.1,sport=<cleared>,dport=<cleared>),protoinfo=(state=<cleared>),helper=ftp +]) + +dnl Test ofproto/trace +DPORT=$(conntrack -L expect | grep 'src=10.1.1.2' | cut -d ' ' -f6 | cut -d '=' -f2) + +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=12345,tcp_dst=$DPORT"], [0], [stdout]) +AT_CHECK([tail -1 stdout], [0], + [Datapath actions: 2 +]) +AT_CHECK([grep -c 'ct_state=new|rel|trk' stdout], [0], [2 +]) + +dnl Try the second set of flows. +AT_CHECK([ovs-ofctl --bundle replace-flows br0 flows2.txt]) +AT_CHECK([ovs-appctl dpctl/flush-conntrack]) + +dnl FTP requests from p1->p0 should fail due to network failure. +dnl Try 3 times, in 1 second intervals. +NS_CHECK_EXEC([at_ns1], [wget ftp://10.1.1.1 --no-passive-ftp -t 3 -T 1 -v -o wget1.log], [4]) +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.1)], [0], [dnl +]) + +dnl Active FTP requests from p0->p1 should work fine. +NS_CHECK_EXEC([at_ns0], [wget ftp://10.1.1.2 --no-passive-ftp -t 3 -T 1 --retry-connrefused -v -o wget0-1.log]) +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0], [dnl +tcp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=<cleared>,dport=<cleared>),reply=(src=10.1.1.2,dst=10.1.1.1,sport=<cleared>,dport=<cleared>),protoinfo=(state=<cleared>),helper=ftp +tcp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=<cleared>,dport=<cleared>),reply=(src=10.1.1.1,dst=10.1.1.2,sport=<cleared>,dport=<cleared>),protoinfo=(state=<cleared>) +]) + +dnl Test ofproto/trace +ovs-appctl dpctl/dump-conntrack +CONNTRACK_INFO=$(ovs-appctl dpctl/dump-conntrack | grep -v 'dport=21)' | grep '(src=10.1.1.2,dst=10.1.1.1,') +SPORT=$(echo $CONNTRACK_INFO | cut -d ',' -f4 | cut -d '=' -f2) +DPORT=$(echo $CONNTRACK_INFO | cut -d ',' -f5 | cut -d ')' -f1 | cut -d '=' -f2) + +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=$SPORT,tcp_dst=$DPORT"], [0], [stdout]) +AT_CHECK([tail -1 stdout], [0], + [Datapath actions: 2 +]) +AT_CHECK([grep -c 'ct_state=est|rel|trk' stdout], [0], [2 +]) + +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=$DPORT,tcp_dst=$SPORT"], [0], [stdout]) +AT_CHECK([tail -1 stdout], [0], + [Datapath actions: 3 +]) +AT_CHECK([grep -c 'ct_state=est|rel|rpl|trk' stdout], [0], [2 +]) + +AT_CHECK([ovs-appctl dpctl/flush-conntrack]) + +dnl Passive FTP requests from p0->p1 should work fine. +NS_CHECK_EXEC([at_ns0], [wget ftp://10.1.1.2 -t 3 -T 1 --retry-connrefused -v -o wget0-2.log]) +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0], [dnl +tcp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=<cleared>,dport=<cleared>),reply=(src=10.1.1.2,dst=10.1.1.1,sport=<cleared>,dport=<cleared>),protoinfo=(state=<cleared>),helper=ftp +]) + +dnl Test ofproto/trace +ovs-appctl dpctl/dump-conntrack +CONNTRACK_INFO=$(ovs-appctl dpctl/dump-conntrack | grep -v 'dport=21)' | grep '(src=10.1.1.1,dst=10.1.1.2,') +SPORT=$(echo $CONNTRACK_INFO | cut -d ',' -f4 | cut -d '=' -f2) +DPORT=$(echo $CONNTRACK_INFO | cut -d ',' -f5 | cut -d ')' -f1 | cut -d '=' -f2) + +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=$DPORT,tcp_dst=$SPORT"], [0], [stdout]) +AT_CHECK([tail -1 stdout], [0], + [Datapath actions: 2 +]) +AT_CHECK([grep -c 'ct_state=est|rel|rpl|trk' stdout], [0], [2 +]) + +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=$SPORT,tcp_dst=$DPORT"], [0], [stdout]) +AT_CHECK([tail -1 stdout], [0], + [Datapath actions: 3 +]) +AT_CHECK([grep -c 'ct_state=est|rel|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() -- 2.7.4 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
