Message ID | 1503701479-43894-7-git-send-email-yihung.wei@gmail.com |
---|---|
State | Deferred |
Headers | show |
On 08/25/2017 03:51 PM, Yi-Hung Wei wrote: > 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 <yihung.wei@gmail.com> > --- > 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) These are duplicates of enums from /usr/include/linux/netfilter/nfnetlink_conntrack.h: ----- enum ctattr_expect { CTA_EXPECT_UNSPEC, CTA_EXPECT_MASTER, CTA_EXPECT_TUPLE, CTA_EXPECT_MASK, CTA_EXPECT_TIMEOUT, CTA_EXPECT_ID, CTA_EXPECT_HELP_NAME, CTA_EXPECT_ZONE, CTA_EXPECT_FLAGS, CTA_EXPECT_CLASS, CTA_EXPECT_NAT, CTA_EXPECT_FN, __CTA_EXPECT_MAX }; #define CTA_EXPECT_MAX (__CTA_EXPECT_MAX - 1) > + > +#define CTA_EXPECT_NAT_DIR 1 > +#define CTA_EXPECT_NAT_TUPLE 2 > + These are also duplicates from /usr/include/linux/netfilter/nfnetlink_conntrack.h. > #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); I feel like nl_ct_get_expect() should clean up after itself and not expect the caller to free this. > + 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; > + } I would not call nl_msg_start_nested() until after the check above. > + 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; > +} > + I think in general you need to provide more comments for your new functions and struct declarations. http://docs.openvswitch.org/en/latest/internals/contributing/coding-style/#functions Thanks, - Greg > 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() >
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()
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 <yihung.wei@gmail.com> --- 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(+)