/* The set of characters that may separate one action or one key attribute
* from another. */
static const char *delimiters = ", \t\r\n";
+static const char *delimiters_end = ", \t\r\n)";
struct attr_len_tbl {
int len;
.min_len = sizeof(uint32_t) * 2 },
[OVS_CT_ATTR_LABELS] = { .type = NL_A_UNSPEC, .optional = true,
.min_len = sizeof(struct ovs_key_ct_labels) * 2 },
+ [OVS_CT_ATTR_HELPER] = { .type = NL_A_STRING, .optional = true,
+ .min_len = 1, .max_len = 16 },
};
static void
struct nlattr *a[ARRAY_SIZE(ovs_conntrack_policy)];
const ovs_u128 *label;
const uint32_t *mark;
+ const char *helper;
uint16_t zone;
bool commit;
zone = a[OVS_CT_ATTR_ZONE] ? nl_attr_get_u16(a[OVS_CT_ATTR_ZONE]) : 0;
mark = a[OVS_CT_ATTR_MARK] ? nl_attr_get(a[OVS_CT_ATTR_MARK]) : NULL;
label = a[OVS_CT_ATTR_LABELS] ? nl_attr_get(a[OVS_CT_ATTR_LABELS]): NULL;
+ helper = a[OVS_CT_ATTR_HELPER] ? nl_attr_get(a[OVS_CT_ATTR_HELPER]) : NULL;
ds_put_format(ds, "ct");
- if (commit || zone || mark || label) {
+ if (commit || zone || mark || label || helper) {
ds_put_cstr(ds, "(");
if (commit) {
ds_put_format(ds, "commit,");
ds_put_format(ds, "label=");
format_u128(ds, label, label + 1, true);
}
+ if (helper) {
+ ds_put_format(ds, "helper=%s,", helper);
+ }
ds_chomp(ds, ',');
ds_put_cstr(ds, ")");
}
const char *s = s_;
if (ovs_scan(s, "ct")) {
+ const char *helper = NULL;
+ size_t helper_len = 0;
bool commit = false;
uint16_t zone = 0;
struct {
s += retval;
continue;
}
+ if (ovs_scan(s, "helper=%n", &n)) {
+ s += n;
+ helper_len = strcspn(s, delimiters_end);
+ if (!helper_len || helper_len > 15) {
+ return -EINVAL;
+ }
+ helper = s;
+ s += helper_len;
+ continue;
+ }
return -EINVAL;
}
nl_msg_put_unspec(actions, OVS_CT_ATTR_LABELS, &ct_label,
sizeof ct_label);
}
+ if (helper) {
+ nl_msg_put_string__(actions, OVS_CT_ATTR_HELPER, helper,
+ helper_len);
+ }
nl_msg_end_nested(actions, start);
}
*/
#include <config.h>
+#include <netinet/in.h>
+
#include "ofp-actions.h"
#include "bundle.h"
#include "byte-order.h"
* It is strongly recommended that this table is later than the current
* table, to prevent loops.
*
+ * The "alg" attaches protocol-specific behaviour to this action:
+ *
+ * The ALG is a 16-bit number which specifies that additional
+ * processing should be applied to this traffic.
+ *
+ * Protocol | Value | Meaning
+ * --------------------------------------------------------------------
+ * None | 0 | No protocol-specific behaviour.
+ * FTP | 21 | Parse FTP control connections and observe the
+ * | | negotiation of related data connections.
+ * Other | Other | Unsupported protocols.
+ *
+ * By way of example, if FTP control connections have this action applied
+ * with the ALG set to FTP (21), then the connection tracker will observe
+ * the negotiation of data connections. This allows the connection
+ * tracker to identify subsequent data connections as "related" to this
+ * existing connection. The "related" flag will be populated in the
+ * NXM_NX_CT_STATE field for such connections if the 'recirc_table' is
+ * specified.
+ *
* Zero or more actions may immediately follow this action. These actions will
* be executed within the context of the connection tracker, and they require
* the NX_CT_F_COMMIT flag to be set.
};
uint8_t recirc_table; /* Recirculate to a specific table, or
NX_CT_RECIRC_NONE for no recirculation. */
- uint8_t pad[5]; /* Zeroes */
+ uint8_t pad[3]; /* Zeroes */
+ ovs_be16 alg; /* Well-known port number for the protocol.
+ * 0 indicates no ALG is required. */
/* Followed by a sequence of zero or more OpenFlow actions. The length of
* these is included in 'len'. */
};
goto out;
}
conntrack->recirc_table = nac->recirc_table;
+ conntrack->alg = ntohs(nac->alg);
ofpbuf_pull(out, sizeof(*conntrack));
nac->zone_imm = htons(conntrack->zone_imm);
}
nac->recirc_table = conntrack->recirc_table;
+ nac->alg = htons(conntrack->alg);
len = ofpacts_put_openflow_actions(conntrack->actions,
ofpact_ct_get_action_len(conntrack),
return error;
}
}
+ } else if (!strcmp(key, "alg")) {
+ error = str_to_connhelper(value, &oc->alg);
} else if (!strcmp(key, "exec")) {
/* Hide existing actions from ofpacts_parse_copy(), so the
* nesting can be handled transparently. */
return error;
}
+static void
+format_alg(int port, struct ds *s)
+{
+ if (port == IPPORT_FTP) {
+ ds_put_format(s, "alg=ftp,");
+ } else if (port) {
+ ds_put_format(s, "alg=%d,", port);
+ }
+}
+
static void
format_CT(const struct ofpact_conntrack *a, struct ds *s)
{
ofpacts_format(a->actions, ofpact_ct_get_action_len(a), s);
ds_put_format(s, "),");
}
+ format_alg(a->alg, s);
ds_chomp(s, ',');
ds_put_char(s, ')');
}
OVS_TRAFFIC_VSWITCHD_STOP
AT_CLEANUP
+
+AT_SETUP([conntrack - FTP])
+AT_SKIP_IF([test $HAVE_PYFTPDLIB = no])
+CHECK_CONNTRACK()
+OVS_TRAFFIC_VSWITCHD_START(
+ [set-fail-mode br0 standalone -- ])
+
+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
+priority=1,action=drop
+priority=10,arp,action=normal
+priority=10,icmp,action=normal
+priority=100,in_port=1,tcp,action=ct(alg=ftp,commit),2
+priority=100,in_port=2,tcp,ct_state=-trk,action=ct(table=0)
+priority=100,in_port=2,tcp,ct_state=+trk+est,action=1
+priority=100,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
+priority=1,action=drop
+priority=10,arp,action=normal
+priority=10,icmp,action=normal
+priority=100,in_port=1,tcp,ct_state=-trk,action=ct(table=0)
+priority=100,in_port=1,tcp,ct_state=+trk+new,action=ct(commit,alg=ftp),2
+priority=100,in_port=1,tcp,ct_state=+trk+est,action=2
+priority=100,in_port=2,tcp,ct_state=-trk,action=ct(table=0)
+priority=100,in_port=2,tcp,ct_state=+trk+new+rel,action=ct(commit),1
+priority=100,in_port=2,tcp,ct_state=+trk+est,action=1
+priority=100,in_port=2,tcp,ct_state=+trk-new+rel,action=1
+])
+
+AT_CHECK([ovs-ofctl add-flows br0 flows1.txt])
+
+NETNS_DAEMONIZE([at_ns0], [[$PYTHON $srcdir/test-l7.py ftp]], [ftp1.pid])
+NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-l7.py ftp]], [ftp0.pid])
+
+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([conntrack -L 2>&1 | 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([conntrack -L 2>&1 | FORMAT_CT(10.1.1.2) | grep -v "FIN"], [0], [dnl
+TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 helper=ftp use=1
+])
+
+dnl Try the second set of flows.
+conntrack -F
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_CHECK([ovs-ofctl add-flows br0 flows2.txt])
+
+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([conntrack -L 2>&1 | 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.log])
+AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.2) | grep -v "FIN"], [0], [dnl
+TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 helper=ftp use=2
+TIME_WAIT src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 use=1
+])
+
+AT_CHECK([conntrack -F 2>/dev/null])
+
+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.log])
+AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.2) | grep -v "FIN"], [0], [dnl
+TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 helper=ftp use=2
+TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 use=1
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([conntrack - FTP with multiple expectations])
+AT_SKIP_IF([test $HAVE_PYFTPDLIB = no])
+CHECK_CONNTRACK()
+OVS_TRAFFIC_VSWITCHD_START(
+ [set-fail-mode br0 standalone -- ])
+
+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 Dual-firewall, allow all from ns1->ns2, allow established and ftp ns2->ns1.
+AT_DATA([flows.txt], [dnl
+priority=1,action=drop
+priority=10,arp,action=normal
+priority=10,icmp,action=normal
+priority=100,in_port=1,tcp,ct_state=-trk,action=ct(table=0,zone=1)
+priority=100,in_port=1,tcp,ct_zone=1,ct_state=+trk+new,action=ct(commit,alg=ftp,zone=1),ct(commit,alg=ftp,zone=2),2
+priority=100,in_port=1,tcp,ct_zone=1,ct_state=+trk+est,action=ct(table=0,zone=2)
+priority=100,in_port=1,tcp,ct_zone=2,ct_state=+trk+new,action=ct(commit,alg=ftp,zone=2)
+priority=100,in_port=1,tcp,ct_zone=2,ct_state=+trk+est,action=2
+priority=100,in_port=2,tcp,ct_state=-trk,action=ct(table=0,zone=2)
+priority=100,in_port=2,tcp,ct_zone=2,ct_state=+trk+rel,action=ct(commit,zone=2),ct(commit,zone=1),1
+priority=100,in_port=2,tcp,ct_zone=2,ct_state=+trk+est,action=ct(table=0,zone=1)
+priority=100,in_port=2,tcp,ct_zone=1,ct_state=+trk+rel,action=ct(commit,zone=2),ct(commit,zone=1),1
+priority=100,in_port=2,tcp,ct_zone=1,ct_state=+trk+est,action=1
+])
+
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+NETNS_DAEMONIZE([at_ns0], [[$PYTHON $srcdir/test-l7.py ftp]], [ftp1.pid])
+NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-l7.py ftp]], [ftp0.pid])
+
+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([conntrack -L 2>&1 | 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.log])
+AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.2) | grep -v "FIN"], [0], [dnl
+TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 zone=1 helper=ftp use=2
+TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 zone=2 helper=ftp use=2
+TIME_WAIT src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 zone=1 use=1
+TIME_WAIT src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 zone=2 use=1
+])
+
+AT_CHECK([conntrack -F 2>/dev/null])
+
+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.log])
+AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.2) | grep -v "FIN"], [0], [dnl
+TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 zone=1 helper=ftp use=2
+TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 zone=1 use=1
+TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 zone=2 helper=ftp use=2
+TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 zone=2 use=1
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP