[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1445379840-113042-3-git-send-email-jrajahalme@nicira.com>
Date: Tue, 20 Oct 2015 15:24:00 -0700
From: Jarno Rajahalme <jrajahalme@...ira.com>
To: dev@...nvswitch.org
Cc: netdev@...r.kernel.org, jrajahalme@...ira.com
Subject: [RFC PATCH 3/3] conntrack action: Add support for NAT.
Extend OVS conntrack interface to cover NAT. New nested nat action
may be included with a CT action. A bare nat action only mangles
existing connections. If a nat action with src or dst range attribute
is included, new (non-committed) connections are mangled according to
the nat attributes.
This work extends on a branch by Thomas Graf at
https://github.com/tgraf/ovs/tree/nat.
Signed-off-by: Jarno Rajahalme <jrajahalme@...ira.com>
---
datapath/linux/compat/include/linux/openvswitch.h | 37 ++
lib/odp-util.c | 372 ++++++++++++++++-
lib/ofp-actions.c | 435 +++++++++++++++++++-
lib/ofp-actions.h | 39 +-
lib/ofp-parse.c | 13 +
lib/ofp-parse.h | 1 +
ofproto/ofproto-dpif-xlate.c | 69 ++++
tests/odp.at | 7 +
tests/ofp-actions.at | 47 +++
tests/ovs-ofctl.at | 24 +-
tests/system-traffic.at | 460 +++++++++++++++++++++-
11 files changed, 1466 insertions(+), 38 deletions(-)
diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index dae2e5b..2c5b87d 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -686,12 +686,49 @@ enum ovs_ct_attr {
OVS_CT_ATTR_LABELS, /* label to associate with this connection. */
OVS_CT_ATTR_HELPER, /* netlink helper to assist detection of
related connections. */
+ OVS_CT_ATTR_NAT, /* Nested OVS_NAT_ATTR_* */
__OVS_CT_ATTR_MAX
};
#define OVS_CT_ATTR_MAX (__OVS_CT_ATTR_MAX - 1)
/**
+ * enum ovs_nat_attr - Attributes for %OVS_CT_ATTR_NAT.
+ * @OVS_NAT_ATTR_SRC: Flag for Source NAT (mangle source address/port).
+ * @OVS_NAT_ATTR_DST: Flag for Destination NAT (mangle destination
+ * address/port). Only one of (@OVS_NAT_ATTR_SRC, @OVS_NAT_ATTR_DST) may be
+ * specified. Effective only for packets for ct_state NEW connections.
+ * Committed connections are mangled by the NAT action according to the
+ * committed NAT type regardless of the flags specified. As a corollary, a NAT
+ * action without a NAT type flag will only mangle packets of committed
+ * connections. The following NAT attributes only apply for NEW connections,
+ * and they may be included only when the CT action has the @OVS_CT_ATTR_COMMIT
+ * flag and either @OVS_NAT_ATTR_SRC, @OVS_NAT_ATTR_DST is also included.
+ * @OVS_NAT_ATTR_IP_MIN: struct in_addr or struct in6_addr
+ * @OVS_NAT_ATTR_IP_MAX: struct in_addr or struct in6_addr
+ * @OVS_NAT_ATTR_PROTO_MIN: u16 L4 protocol specific lower boundary (port)
+ * @OVS_NAT_ATTR_PROTO_MAX: u16 L4 protocol specific upper boundary (port)
+ * @OVS_NAT_ATTR_PERSISTENT: Flag for persistent IP mapping across reboots
+ * @OVS_NAT_ATTR_PROTO_HASH: Flag for pseudo random L4 port mapping (MD5)
+ * @OVS_NAT_ATTR_PROTO_RANDOM: Flag for fully randomized L4 port mapping
+ */
+enum ovs_nat_attr {
+ OVS_NAT_ATTR_UNSPEC,
+ OVS_NAT_ATTR_SRC,
+ OVS_NAT_ATTR_DST,
+ OVS_NAT_ATTR_IP_MIN,
+ OVS_NAT_ATTR_IP_MAX,
+ OVS_NAT_ATTR_PROTO_MIN,
+ OVS_NAT_ATTR_PROTO_MAX,
+ OVS_NAT_ATTR_PERSISTENT,
+ OVS_NAT_ATTR_PROTO_HASH,
+ OVS_NAT_ATTR_PROTO_RANDOM,
+ __OVS_NAT_ATTR_MAX,
+};
+
+#define OVS_NAT_ATTR_MAX (__OVS_NAT_ATTR_MAX - 1)
+
+/**
* enum ovs_action_attr - Action types.
*
* @OVS_ACTION_ATTR_OUTPUT: Output packet to port.
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 8f0f39a..185dd5c 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -542,6 +542,150 @@ format_odp_tnl_push_action(struct ds *ds, const struct nlattr *attr)
ds_put_format(ds, ",out_port(%"PRIu32"))", data->out_port);
}
+static const struct nl_policy ovs_nat_policy[] = {
+ [OVS_NAT_ATTR_SRC] = { .type = NL_A_FLAG, .optional = true, },
+ [OVS_NAT_ATTR_DST] = { .type = NL_A_FLAG, .optional = true, },
+ [OVS_NAT_ATTR_IP_MIN] = { .type = NL_A_UNSPEC, .optional = true,
+ .min_len = sizeof(struct in_addr),
+ .max_len = sizeof(struct in6_addr)},
+ [OVS_NAT_ATTR_IP_MAX] = { .type = NL_A_UNSPEC, .optional = true,
+ .min_len = sizeof(struct in_addr),
+ .max_len = sizeof(struct in6_addr)},
+ [OVS_NAT_ATTR_PROTO_MIN] = { .type = NL_A_U16, .optional = true, },
+ [OVS_NAT_ATTR_PROTO_MAX] = { .type = NL_A_U16, .optional = true, },
+ [OVS_NAT_ATTR_PERSISTENT] = { .type = NL_A_FLAG, .optional = true, },
+ [OVS_NAT_ATTR_PROTO_HASH] = { .type = NL_A_FLAG, .optional = true, },
+ [OVS_NAT_ATTR_PROTO_RANDOM] = { .type = NL_A_FLAG, .optional = true, },
+};
+
+static void
+format_odp_ct_nat(struct ds *ds, const struct nlattr *attr)
+{
+ struct nlattr *a[ARRAY_SIZE(ovs_nat_policy)];
+ size_t addr_len;
+ ovs_be32 ip_min, ip_max;
+ struct in6_addr ip6_min, ip6_max;
+ ovs_be16 proto_min, proto_max;
+
+ if (!nl_parse_nested(attr, ovs_nat_policy, a, ARRAY_SIZE(a))) {
+ ds_put_cstr(ds, "nat(error: nl_parse_nested() failed.)");
+ return;
+ }
+ /* If no type, then nothing else either. */
+ if (!(a[OVS_NAT_ATTR_SRC] || a[OVS_NAT_ATTR_DST])
+ && (a[OVS_NAT_ATTR_IP_MIN] || a[OVS_NAT_ATTR_IP_MAX]
+ || a[OVS_NAT_ATTR_PROTO_MIN] || a[OVS_NAT_ATTR_PROTO_MAX]
+ || a[OVS_NAT_ATTR_PERSISTENT] || a[OVS_NAT_ATTR_PROTO_HASH]
+ || a[OVS_NAT_ATTR_PROTO_RANDOM])) {
+ ds_put_cstr(ds, "nat(error: options allowed only with \"src\" or \"dst\")");
+ return;
+ }
+ /* Both SNAT & DNAT may not be specified. */
+ if (a[OVS_NAT_ATTR_SRC] && a[OVS_NAT_ATTR_DST]) {
+ ds_put_cstr(ds, "nat(error: Only one of \"src\" or \"dst\" may be present.)");
+ return;
+ }
+ /* proto may not appear without ip. */
+ if (!a[OVS_NAT_ATTR_IP_MIN] && a[OVS_NAT_ATTR_PROTO_MIN]) {
+ ds_put_cstr(ds, "nat(error: proto but no IP.)");
+ return;
+ }
+ /* MAX may not appear without MIN. */
+ if ((!a[OVS_NAT_ATTR_IP_MIN] && a[OVS_NAT_ATTR_IP_MAX])
+ || (!a[OVS_NAT_ATTR_PROTO_MIN] && a[OVS_NAT_ATTR_PROTO_MAX])) {
+ ds_put_cstr(ds, "nat(error: range max without min.)");
+ return;
+ }
+ /* Address sizes must match. */
+ if ((a[OVS_NAT_ATTR_IP_MIN]
+ && (nl_attr_get_size(a[OVS_NAT_ATTR_IP_MIN]) != sizeof(ovs_be32) &&
+ nl_attr_get_size(a[OVS_NAT_ATTR_IP_MIN]) != sizeof(struct in6_addr)))
+ || (a[OVS_NAT_ATTR_IP_MIN] && a[OVS_NAT_ATTR_IP_MAX]
+ && (nl_attr_get_size(a[OVS_NAT_ATTR_IP_MIN])
+ != nl_attr_get_size(a[OVS_NAT_ATTR_IP_MAX])))) {
+ ds_put_cstr(ds, "nat(error: IP address sizes do not match)");
+ return;
+ }
+
+ addr_len = a[OVS_NAT_ATTR_IP_MIN]
+ ? nl_attr_get_size(a[OVS_NAT_ATTR_IP_MIN]) : 0;
+ if (addr_len == sizeof(ovs_be32)) {
+ ip_min = a[OVS_NAT_ATTR_IP_MIN]
+ ? nl_attr_get_be32(a[OVS_NAT_ATTR_IP_MIN]) : 0;
+ ip_max = a[OVS_NAT_ATTR_IP_MAX]
+ ? nl_attr_get_be32(a[OVS_NAT_ATTR_IP_MAX]) : 0;
+ } else {
+ memcpy(&ip6_min, a[OVS_NAT_ATTR_IP_MIN]
+ ? nl_attr_get(a[OVS_NAT_ATTR_IP_MIN]) : &in6addr_any,
+ sizeof ip6_min);
+ memcpy(&ip6_max, a[OVS_NAT_ATTR_IP_MAX]
+ ? nl_attr_get(a[OVS_NAT_ATTR_IP_MAX]) : &in6addr_any,
+ sizeof ip6_max);
+ }
+ proto_min = a[OVS_NAT_ATTR_PROTO_MIN]
+ ? nl_attr_get_be16(a[OVS_NAT_ATTR_PROTO_MIN]) : 0;
+ proto_max = a[OVS_NAT_ATTR_PROTO_MAX]
+ ? nl_attr_get_be16(a[OVS_NAT_ATTR_PROTO_MAX]) : 0;
+
+ if ((addr_len == sizeof(ovs_be32)
+ && ip_max && ntohl(ip_min) > ntohl(ip_max))
+ || (addr_len == sizeof(struct in6_addr)
+ && memcmp(&ip6_min, &ip_max, sizeof ip6_min) > 0)
+ || (proto_max && proto_min > proto_max)) {
+ ds_put_cstr(ds, "nat(range error)");
+ return;
+ }
+
+ ds_put_cstr(ds, "nat");
+ if (a[OVS_NAT_ATTR_SRC] || a[OVS_NAT_ATTR_DST]) {
+ ds_put_char(ds, '(');
+ if (a[OVS_NAT_ATTR_SRC]) {
+ ds_put_cstr(ds, "src");
+ } else if (a[OVS_NAT_ATTR_DST]) {
+ ds_put_cstr(ds, "dst");
+ }
+
+ if (addr_len > 0) {
+ ds_put_cstr(ds, "=");
+
+ if (addr_len == sizeof ip_min) {
+ ds_put_format(ds, IP_FMT, IP_ARGS(ip_min));
+
+ if (ip_max && ip_max != ip_min) {
+ ds_put_format(ds, "-"IP_FMT, IP_ARGS(ip_max));
+ }
+ } else if (addr_len == sizeof ip6_min) {
+ print_ipv6_addr(ds, &ip6_min);
+
+ if (ipv6_mask_is_any(&ip6_max) &&
+ memcmp(&ip6_max, &ip6_min, sizeof ip6_max) != 0) {
+ ds_put_char(ds, '-');
+ print_ipv6_addr(ds, &ip6_max);
+ }
+ }
+ if (proto_min) {
+ ds_put_format(ds, ":%"PRIu16, proto_min);
+
+ if (proto_max && proto_max != proto_min) {
+ ds_put_format(ds, "-%"PRIu16, proto_max);
+ }
+ }
+ }
+ ds_put_char(ds, ',');
+ if (a[OVS_NAT_ATTR_PERSISTENT]) {
+ ds_put_cstr(ds, "persistent,");
+ }
+ if (a[OVS_NAT_ATTR_PROTO_HASH]) {
+ ds_put_cstr(ds, "hash,");
+ }
+ if (a[OVS_NAT_ATTR_PROTO_RANDOM]) {
+ ds_put_cstr(ds, "random,");
+ }
+ ds_chomp(ds, ',');
+ ds_put_char(ds, ')');
+ }
+}
+
static const struct nl_policy ovs_conntrack_policy[] = {
[OVS_CT_ATTR_COMMIT] = { .type = NL_A_FLAG, .optional = true, },
[OVS_CT_ATTR_ZONE] = { .type = NL_A_U16, .optional = true, },
@@ -551,6 +695,8 @@ static const struct nl_policy ovs_conntrack_policy[] = {
.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 },
+ [OVS_CT_ATTR_NAT] = { .type = NL_A_UNSPEC, .optional = true,
+ .min_len = 0, .max_len = 96 },
};
static void
@@ -562,6 +708,7 @@ format_odp_conntrack_action(struct ds *ds, const struct nlattr *attr)
const char *helper;
uint16_t zone;
bool commit;
+ const struct nlattr *nat;
if (!nl_parse_nested(attr, ovs_conntrack_policy, a, ARRAY_SIZE(a))) {
ds_put_cstr(ds, "ct(error)");
@@ -573,9 +720,10 @@ format_odp_conntrack_action(struct ds *ds, const struct nlattr *attr)
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;
+ nat = a[OVS_CT_ATTR_NAT];
ds_put_format(ds, "ct");
- if (commit || zone || mark || label || helper) {
+ if (commit || zone || mark || label || helper || nat) {
ds_put_cstr(ds, "(");
if (commit) {
ds_put_format(ds, "commit,");
@@ -595,6 +743,9 @@ format_odp_conntrack_action(struct ds *ds, const struct nlattr *attr)
if (helper) {
ds_put_format(ds, "helper=%s,", helper);
}
+ if (nat) {
+ format_odp_ct_nat(ds, nat);
+ }
ds_chomp(ds, ',');
ds_put_cstr(ds, ")");
}
@@ -876,15 +1027,15 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
l4 = ((uint8_t *) l3 + sizeof (struct ip_header));
ip = (struct ip_header *) l3;
if (!ovs_scan_len(s, &n, "header(size=%"SCNi32",type=%"SCNi32","
- "eth(dst="ETH_ADDR_SCAN_FMT",",
- &data->header_len,
- &data->tnl_type,
- ETH_ADDR_SCAN_ARGS(eth->eth_dst))) {
+ "eth(dst="ETH_ADDR_SCAN_FMT",",
+ &data->header_len,
+ &data->tnl_type,
+ ETH_ADDR_SCAN_ARGS(eth->eth_dst))) {
return -EINVAL;
}
if (!ovs_scan_len(s, &n, "src="ETH_ADDR_SCAN_FMT",",
- ETH_ADDR_SCAN_ARGS(eth->eth_src))) {
+ ETH_ADDR_SCAN_ARGS(eth->eth_src))) {
return -EINVAL;
}
if (!ovs_scan_len(s, &n, "dl_type=0x%"SCNx16"),", &dl_type)) {
@@ -894,11 +1045,11 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
/* IPv4 */
if (!ovs_scan_len(s, &n, "ipv4(src="IP_SCAN_FMT",dst="IP_SCAN_FMT",proto=%"SCNi8
- ",tos=%"SCNi8",ttl=%"SCNi8",frag=0x%"SCNx16"),",
- IP_SCAN_ARGS(&sip),
- IP_SCAN_ARGS(&dip),
- &ip->ip_proto, &ip->ip_tos,
- &ip->ip_ttl, &ip->ip_frag_off)) {
+ ",tos=%"SCNi8",ttl=%"SCNi8",frag=0x%"SCNx16"),",
+ IP_SCAN_ARGS(&sip),
+ IP_SCAN_ARGS(&dip),
+ &ip->ip_proto, &ip->ip_tos,
+ &ip->ip_ttl, &ip->ip_frag_off)) {
return -EINVAL;
}
put_16aligned_be32(&ip->ip_src, sip);
@@ -908,7 +1059,7 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
udp = (struct udp_header *) l4;
greh = (struct gre_base_hdr *) l4;
if (ovs_scan_len(s, &n, "udp(src=%"SCNi16",dst=%"SCNi16",csum=0x%"SCNx16"),",
- &udp_src, &udp_dst, &csum)) {
+ &udp_src, &udp_dst, &csum)) {
uint32_t vx_flags, vni;
udp->udp_src = htons(udp_src);
@@ -917,7 +1068,7 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
udp->udp_csum = htons(csum);
if (ovs_scan_len(s, &n, "vxlan(flags=0x%"SCNx32",vni=0x%"SCNx32"))",
- &vx_flags, &vni)) {
+ &vx_flags, &vni)) {
struct vxlanhdr *vxh = (struct vxlanhdr *) (udp + 1);
put_16aligned_be32(&vxh->vx_flags, htonl(vx_flags));
@@ -968,7 +1119,7 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
return -EINVAL;
}
} else if (ovs_scan_len(s, &n, "gre((flags=0x%"SCNx16",proto=0x%"SCNx16")",
- &gre_flags, &gre_proto)){
+ &gre_flags, &gre_proto)){
tnl_type = OVS_VPORT_TYPE_GRE;
greh->flags = htons(gre_flags);
@@ -1030,6 +1181,181 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
return n;
}
+struct ct_nat_params {
+ bool snat;
+ bool dnat;
+ size_t addr_len;
+ union {
+ ovs_be32 ip;
+ struct in6_addr ip6;
+ } addr_min;
+ union {
+ ovs_be32 ip;
+ struct in6_addr ip6;
+ } addr_max;
+ uint16_t proto_min;
+ uint16_t proto_max;
+ bool persistent;
+ bool proto_hash;
+ bool proto_random;
+};
+
+static int
+scan_ct_nat_range(const char *s, int *n, struct ct_nat_params *p)
+{
+ if (ovs_scan_len(s, n, "=")) {
+ char ipv6_s[IPV6_SCAN_LEN + 1];
+ struct in6_addr ipv6;
+
+ if (ovs_scan_len(s, n, IP_SCAN_FMT, IP_SCAN_ARGS(&p->addr_min.ip))) {
+ p->addr_len = sizeof p->addr_min.ip;
+ if (ovs_scan_len(s, n, "-")) {
+ if (!ovs_scan_len(s, n, IP_SCAN_FMT,
+ IP_SCAN_ARGS(&p->addr_max.ip))) {
+ return -EINVAL;
+ }
+ }
+ if (ovs_scan_len(s, n, ":%"SCNu16, &p->proto_min)) {
+ if (ovs_scan_len(s, n, "-")) {
+ if (!ovs_scan_len(s, n, "%"SCNu16, &p->proto_max)) {
+ return -EINVAL;
+ }
+ }
+ }
+ } else if (ovs_scan_len(s, n, IPV6_SCAN_FMT, ipv6_s) &&
+ inet_pton(AF_INET6, ipv6_s, &ipv6) == 1) {
+ p->addr_len = sizeof p->addr_min.ip6;
+ p->addr_min.ip6 = ipv6;
+ if (ovs_scan_len(s, n, "-")) {
+ if (ovs_scan_len(s, n, IPV6_SCAN_FMT, ipv6_s) &&
+ inet_pton(AF_INET6, ipv6_s, &ipv6) == 1) {
+ p->addr_max.ip6 = ipv6;
+ } else {
+ return -EINVAL;
+ }
+ }
+ if (ovs_scan_len(s, n, "+%"SCNu16, &p->proto_min)) {
+ if (ovs_scan_len(s, n, "-")) {
+ if (!ovs_scan_len(s, n, "%"SCNu16, &p->proto_max)) {
+ return -EINVAL;
+ }
+ }
+ }
+ } else {
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static int
+scan_ct_nat(const char *s, struct ct_nat_params *p)
+{
+ int n = 0;
+
+ if (ovs_scan_len(s, &n, "nat")) {
+ memset(p, 0, sizeof *p);
+
+ if (ovs_scan_len(s, &n, "(")) {
+ char *end;
+ int end_n;
+
+ end = strchr(s + n, ')');
+ if (!end) {
+ return -EINVAL;
+ }
+ end_n = end - s;
+
+ while (n < end_n) {
+ n += strspn(s + n, delimiters);
+ if (ovs_scan_len(s, &n, "src")) {
+ int err = scan_ct_nat_range(s, &n, p);
+ if (err) {
+ return err;
+ }
+ p->snat = true;
+ continue;
+ }
+ if (ovs_scan_len(s, &n, "dst")) {
+ int err = scan_ct_nat_range(s, &n, p);
+ if (err) {
+ return err;
+ }
+ p->dnat = true;
+ continue;
+ }
+ if (ovs_scan_len(s, &n, "persistent")) {
+ p->persistent = true;
+ continue;
+ }
+ if (ovs_scan_len(s, &n, "hash")) {
+ p->proto_hash = true;
+ continue;
+ }
+ if (ovs_scan_len(s, &n, "random")) {
+ p->proto_random = true;
+ continue;
+ }
+ return -EINVAL;
+ }
+
+ if (p->snat && p->dnat) {
+ return -EINVAL;
+ }
+ if ((p->addr_len != 0 &&
+ memcmp(&p->addr_max, &in6addr_any, p->addr_len) &&
+ memcmp(&p->addr_max, &p->addr_min, p->addr_len) < 0) ||
+ (p->proto_max && p->proto_max < p->proto_min)) {
+ return -EINVAL;
+ }
+ if (p->proto_hash && p->proto_random) {
+ return -EINVAL;
+ }
+ n++;
+ }
+ }
+ return n;
+}
+
+static void
+nl_msg_put_ct_nat(struct ct_nat_params *p, struct ofpbuf *actions)
+{
+ size_t start = nl_msg_start_nested(actions, OVS_CT_ATTR_NAT);
+
+ if (p->snat) {
+ nl_msg_put_flag(actions, OVS_NAT_ATTR_SRC);
+ } else if (p->dnat) {
+ nl_msg_put_flag(actions, OVS_NAT_ATTR_DST);
+ } else {
+ goto out;
+ }
+ if (p->addr_len != 0) {
+ nl_msg_put_unspec(actions, OVS_NAT_ATTR_IP_MIN, &p->addr_min,
+ p->addr_len);
+ if (memcmp(&p->addr_max, &p->addr_min, p->addr_len) > 0) {
+ nl_msg_put_unspec(actions, OVS_NAT_ATTR_IP_MAX, &p->addr_max,
+ p->addr_len);
+ }
+ if (p->proto_min) {
+ nl_msg_put_u16(actions, OVS_NAT_ATTR_PROTO_MIN, p->proto_min);
+ if (p->proto_max && p->proto_max > p->proto_min) {
+ nl_msg_put_u16(actions, OVS_NAT_ATTR_PROTO_MAX, p->proto_max);
+ }
+ }
+ if (p->persistent) {
+ nl_msg_put_flag(actions, OVS_NAT_ATTR_PERSISTENT);
+ }
+ if (p->proto_hash) {
+ nl_msg_put_flag(actions, OVS_NAT_ATTR_PROTO_HASH);
+ }
+ if (p->proto_random) {
+ nl_msg_put_flag(actions, OVS_NAT_ATTR_PROTO_RANDOM);
+ }
+ }
+out:
+ nl_msg_end_nested(actions, start);
+}
+
static int
parse_conntrack_action(const char *s_, struct ofpbuf *actions)
{
@@ -1048,6 +1374,8 @@ parse_conntrack_action(const char *s_, struct ofpbuf *actions)
ovs_u128 value;
ovs_u128 mask;
} ct_label;
+ struct ct_nat_params nat_params;
+ bool have_nat = false;
size_t start;
char *end;
@@ -1056,13 +1384,14 @@ parse_conntrack_action(const char *s_, struct ofpbuf *actions)
s += 2;
if (ovs_scan(s, "(")) {
s++;
+find_end:
end = strchr(s, ')');
if (!end) {
return -EINVAL;
}
while (s != end) {
- int n = -1;
+ int n;
s += strspn(s, delimiters);
if (ovs_scan(s, "commit%n", &n)) {
@@ -1106,6 +1435,16 @@ parse_conntrack_action(const char *s_, struct ofpbuf *actions)
continue;
}
+ n = scan_ct_nat(s, &nat_params);
+ if (n > 0) {
+ s += n;
+ have_nat = true;
+
+ /* end points to the end of the nested, nat action.
+ * find the real end. */
+ goto find_end;
+ }
+ /* Nothing matched. */
return -EINVAL;
}
s++;
@@ -1130,6 +1469,9 @@ parse_conntrack_action(const char *s_, struct ofpbuf *actions)
nl_msg_put_string__(actions, OVS_CT_ATTR_HELPER, helper,
helper_len);
}
+ if (have_nat) {
+ nl_msg_put_ct_nat(&nat_params, actions);
+ }
nl_msg_end_nested(actions, start);
}
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 5f72fda..49caa3d 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -28,6 +28,7 @@
#include "meta-flow.h"
#include "multipath.h"
#include "nx-match.h"
+#include "odp-netlink.h"
#include "ofp-parse.h"
#include "ofp-util.h"
#include "ofpbuf.h"
@@ -291,6 +292,9 @@ enum ofp_raw_action_type {
/* NX1.0+(35): struct nx_action_conntrack, ... */
NXAST_RAW_CT,
+ /* NX1.0+(36): struct nx_action_nat, ... */
+ NXAST_RAW_NAT,
+
/* ## ------------------ ## */
/* ## Debugging actions. ## */
/* ## ------------------ ## */
@@ -4316,21 +4320,12 @@ encode_NOTE(const struct ofpact_note *note,
{
size_t start_ofs = out->size;
struct nx_action_note *nan;
- unsigned int remainder;
- unsigned int len;
put_NXAST_NOTE(out);
out->size = out->size - sizeof nan->note;
ofpbuf_put(out, note->data, note->length);
-
- len = out->size - start_ofs;
- remainder = len % OFP_ACTION_ALIGN;
- if (remainder) {
- ofpbuf_put_zeros(out, OFP_ACTION_ALIGN - remainder);
- }
- nan = ofpbuf_at(out, start_ofs, sizeof *nan);
- nan->len = htons(out->size - start_ofs);
+ pad_ofpat(out, start_ofs);
}
static char * OVS_WARN_UNUSED_RESULT
@@ -4773,9 +4768,18 @@ decode_NXAST_RAW_CT(const struct nx_action_conntrack *nac,
if (conntrack->ofpact.len > sizeof(*conntrack)
&& !(conntrack->flags & NX_CT_F_COMMIT)) {
- VLOG_WARN_RL(&rl, "CT action requires commit flag if actions are "
- "specified.");
- error = OFPERR_OFPBAC_BAD_ARGUMENT;
+ const struct ofpact *a;
+ size_t ofpacts_len = conntrack->ofpact.len - sizeof(*conntrack);
+
+ OFPACT_FOR_EACH (a, conntrack->actions, ofpacts_len) {
+ if (a->type != OFPACT_NAT || ((struct ofpact_nat *)a)->flags
+ || ((struct ofpact_nat *)a)->range_af != AF_UNSPEC) {
+ VLOG_WARN_RL(&rl, "CT action requires commit flag if actions "
+ "other than NAT without arguments are specified.");
+ error = OFPERR_OFPBAC_BAD_ARGUMENT;
+ goto out;
+ }
+ }
}
out:
@@ -4812,6 +4816,9 @@ encode_CT(const struct ofpact_conntrack *conntrack,
nac->len = htons(len);
}
+static char * OVS_WARN_UNUSED_RESULT parse_NAT(char *arg, struct ofpbuf *,
+ enum ofputil_protocol * OVS_UNUSED);
+
/* Parses 'arg' as the argument to a "ct" action, and appends such an
* action to 'ofpacts'.
*
@@ -4849,14 +4856,22 @@ parse_CT(char *arg, struct ofpbuf *ofpacts,
}
} else if (!strcmp(key, "alg")) {
error = str_to_connhelper(value, &oc->alg);
+ } else if (!strcmp(key, "nat")) {
+ const size_t nat_offset = ofpacts_pull(ofpacts);
+
+ error = parse_NAT(value, ofpacts, usable_protocols);
+ ofpact_pad(ofpacts);
+ /* Update CT action pointer and length. */
+ ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset);
+ oc = ofpacts->header;
} else if (!strcmp(key, "exec")) {
/* Hide existing actions from ofpacts_parse_copy(), so the
* nesting can be handled transparently. */
- ofpbuf_pull(ofpacts, sizeof(*oc));
+ const size_t exec_offset = ofpacts_pull(ofpacts);
error = ofpacts_parse_copy(value, ofpacts, usable_protocols, false,
OFPACT_CT);
ofpact_pad(ofpacts);
- ofpacts->header = ofpbuf_push_uninit(ofpacts, sizeof(*oc));
+ ofpacts->header = ofpbuf_push_uninit(ofpacts, exec_offset);
oc = ofpacts->header;
} else {
error = xasprintf("invalid argument to \"ct\" action: `%s'", key);
@@ -4881,6 +4896,8 @@ format_alg(int port, struct ds *s)
}
}
+static void format_NAT(const struct ofpact_nat *a, struct ds *ds);
+
static void
format_CT(const struct ofpact_conntrack *a, struct ds *s)
{
@@ -4898,16 +4915,371 @@ format_CT(const struct ofpact_conntrack *a, struct ds *s)
} else if (a->zone_imm) {
ds_put_format(s, "zone=%"PRIu16",", a->zone_imm);
}
- if (ofpact_ct_get_action_len(a)) {
+ /* If the first action is a NAT action, format it outside of the 'exec'
+ * envelope. */
+ const struct ofpact *action = a->actions;
+ size_t actions_len = ofpact_ct_get_action_len(a);
+ if (actions_len && action->type == OFPACT_NAT) {
+ format_NAT((const struct ofpact_nat *)action, s);
+ ds_put_char(s, ',');
+ actions_len -= OFPACT_ALIGN(action->len);
+ action = ofpact_next(action);
+ }
+ if (actions_len) {
ds_put_cstr(s, "exec(");
- ofpacts_format(a->actions, ofpact_ct_get_action_len(a), s);
- ds_put_format(s, "),");
+ ofpacts_format(action, actions_len, s);
+ ds_put_cstr(s, "),");
}
format_alg(a->alg, s);
ds_chomp(s, ',');
ds_put_char(s, ')');
}
.
+/* NAT action. */
+
+/* Which optional fields are present? */
+enum nx_nat_range {
+ NX_NAT_RANGE_IPV4_MIN = 1 << 0, /* ovs_be32 */
+ NX_NAT_RANGE_IPV4_MAX = 1 << 1, /* ovs_be32 */
+ NX_NAT_RANGE_IPV6_MIN = 1 << 2, /* struct in6_addr */
+ NX_NAT_RANGE_IPV6_MAX = 1 << 3, /* struct in6_addr */
+ NX_NAT_RANGE_PROTO_MIN = 1 << 4, /* ovs_be16 */
+ NX_NAT_RANGE_PROTO_MAX = 1 << 5, /* ovs_be16 */
+};
+
+/* Action structure for NXAST_NAT. */
+struct nx_action_nat {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* At least 16. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_NAT. */
+ uint8_t pad[2]; /* Must be zero. */
+ ovs_be16 flags; /* Zero or more NX_NAT_F_* flags.
+ * Unspecified flag bits must be zero. */
+ ovs_be16 range_present; /* NX_NAT_RANGE_* */
+ /* Followed by optional parameters as specified by 'range_present' */
+};
+OFP_ASSERT(sizeof(struct nx_action_nat) == 16);
+
+static void
+encode_NAT(const struct ofpact_nat *nat,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct nx_action_nat *nan;
+ const size_t ofs = out->size;
+ uint16_t range_present = 0;
+
+ nan = put_NXAST_NAT(out);
+ nan->flags = htons(nat->flags);
+ if (nat->range_af == AF_INET) {
+ if (nat->range.addr.ipv4.min) {
+ ovs_be32 *min = ofpbuf_put_uninit(out, sizeof *min);
+ *min = nat->range.addr.ipv4.min;
+ range_present |= NX_NAT_RANGE_IPV4_MIN;
+ }
+ if (nat->range.addr.ipv4.max) {
+ ovs_be32 *max = ofpbuf_put_uninit(out, sizeof *max);
+ *max = nat->range.addr.ipv4.max;
+ range_present |= NX_NAT_RANGE_IPV4_MAX;
+ }
+ }
+ else if (nat->range_af == AF_INET6) {
+ if (!ipv6_mask_is_any(&nat->range.addr.ipv6.min)) {
+ struct in6_addr *min = ofpbuf_put_uninit(out, sizeof *min);
+ *min = nat->range.addr.ipv6.min;
+ range_present |= NX_NAT_RANGE_IPV6_MIN;
+ }
+ if (!ipv6_mask_is_any(&nat->range.addr.ipv6.max)) {
+ struct in6_addr *max = ofpbuf_put_uninit(out, sizeof *max);
+ *max = nat->range.addr.ipv6.max;
+ range_present |= NX_NAT_RANGE_IPV6_MAX;
+ }
+ }
+ if (nat->range_af != AF_UNSPEC) {
+ if (nat->range.proto.min) {
+ ovs_be16 *min = ofpbuf_put_uninit(out, sizeof *min);
+ *min = htons(nat->range.proto.min);
+ range_present |= NX_NAT_RANGE_PROTO_MIN;
+ }
+ if (nat->range.proto.max) {
+ ovs_be16 *max = ofpbuf_put_uninit(out, sizeof *max);
+ *max = htons(nat->range.proto.max);
+ range_present |= NX_NAT_RANGE_PROTO_MAX;
+ }
+ }
+ pad_ofpat(out, ofs);
+ nan = ofpbuf_at(out, ofs, sizeof *nan);
+ nan->range_present = htons(range_present);
+}
+
+static enum ofperr
+decode_NXAST_RAW_NAT(const struct nx_action_nat *nan,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_nat *nat;
+ uint16_t range_present = ntohs(nan->range_present);
+ const char *opts = (char *)(nan + 1);
+ uint16_t len = ntohs(nan->len) - sizeof *nan;
+
+ nat = ofpact_put_NAT(out);
+ nat->flags = ntohs(nan->flags);
+
+#define NX_NAT_GET_OPT(DST, SRC, LEN, TYPE) \
+ (LEN >= sizeof(TYPE) \
+ ? (memcpy(DST, SRC, sizeof(TYPE)), LEN -= sizeof(TYPE), \
+ SRC += sizeof(TYPE)) \
+ : NULL)
+
+ nat->range_af = AF_UNSPEC;
+ if (range_present & NX_NAT_RANGE_IPV4_MIN) {
+ if (range_present & (NX_NAT_RANGE_IPV6_MIN | NX_NAT_RANGE_IPV6_MAX)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ if (!NX_NAT_GET_OPT(&nat->range.addr.ipv4.min, opts, len, ovs_be32)
+ || !nat->range.addr.ipv4.min) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ nat->range_af = AF_INET;
+
+ if (range_present & NX_NAT_RANGE_IPV4_MAX) {
+ if (!NX_NAT_GET_OPT(&nat->range.addr.ipv4.max, opts, len,
+ ovs_be32)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ if (ntohl(nat->range.addr.ipv4.max)
+ < ntohl(nat->range.addr.ipv4.min)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ }
+ } else if (range_present & NX_NAT_RANGE_IPV4_MAX) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ else if (range_present & NX_NAT_RANGE_IPV6_MIN) {
+ if (!NX_NAT_GET_OPT(&nat->range.addr.ipv6.min, opts, len,
+ struct in6_addr)
+ || ipv6_mask_is_any(&nat->range.addr.ipv6.min)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ nat->range_af = AF_INET6;
+
+ if (range_present & NX_NAT_RANGE_IPV6_MAX) {
+ if (!NX_NAT_GET_OPT(&nat->range.addr.ipv6.max, opts, len,
+ struct in6_addr)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ if (memcmp(&nat->range.addr.ipv6.max, &nat->range.addr.ipv6.min,
+ sizeof(struct in6_addr)) < 0) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ }
+ } else if (range_present & NX_NAT_RANGE_IPV6_MAX) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ if (range_present & NX_NAT_RANGE_PROTO_MIN) {
+ ovs_be16 proto;
+
+ if (nat->range_af == AF_UNSPEC) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ if (!NX_NAT_GET_OPT(&proto, opts, len, ovs_be16) || proto == 0) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ nat->range.proto.min = ntohs(proto);
+ if (range_present & NX_NAT_RANGE_PROTO_MAX) {
+ if (!NX_NAT_GET_OPT(&proto, opts, len, ovs_be16)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ nat->range.proto.max = ntohs(proto);
+ if (nat->range.proto.max < nat->range.proto.min) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ }
+ } else if (range_present & NX_NAT_RANGE_PROTO_MAX) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ return 0;
+}
+
+static void
+format_NAT(const struct ofpact_nat *a, struct ds *ds)
+{
+ ds_put_cstr(ds, "nat");
+
+ if (a->flags & (NX_NAT_F_SRC | NX_NAT_F_DST)) {
+ ds_put_char(ds, '(');
+ ds_put_cstr(ds, a->flags & NX_NAT_F_SRC ? "src" : "dst");
+
+ if (a->range_af != AF_UNSPEC) {
+ char port_delim = ':';
+
+ ds_put_cstr(ds, "=");
+
+ if (a->range_af == AF_INET) {
+ ds_put_format(ds, IP_FMT, IP_ARGS(a->range.addr.ipv4.min));
+
+ if (a->range.addr.ipv4.max
+ && a->range.addr.ipv4.max != a->range.addr.ipv4.min) {
+ ds_put_format(ds, "-"IP_FMT,
+ IP_ARGS(a->range.addr.ipv4.max));
+ }
+ } else if (a->range_af == AF_INET6) {
+ print_ipv6_addr(ds, &a->range.addr.ipv6.min);
+
+ if (!ipv6_mask_is_any(&a->range.addr.ipv6.max)
+ && memcmp(&a->range.addr.ipv6.max, &a->range.addr.ipv6.min,
+ sizeof(struct in6_addr)) != 0) {
+ ds_put_char(ds, '-');
+ print_ipv6_addr(ds, &a->range.addr.ipv6.max);
+ }
+ port_delim = '+';
+ }
+ if (a->range.proto.min) {
+ ds_put_char(ds, port_delim);
+ ds_put_format(ds, "%"PRIu16, a->range.proto.min);
+
+ if (a->range.proto.max
+ && a->range.proto.max != a->range.proto.min) {
+ ds_put_format(ds, "-%"PRIu16, a->range.proto.max);
+ }
+ }
+ ds_put_char(ds, ',');
+
+ if (a->flags & NX_NAT_F_PERSISTENT) {
+ ds_put_cstr(ds, "persistent,");
+ }
+ if (a->flags & NX_NAT_F_PROTO_HASH) {
+ ds_put_cstr(ds, "hash,");
+ }
+ if (a->flags & NX_NAT_F_PROTO_RANDOM) {
+ ds_put_cstr(ds, "random,");
+ }
+ }
+ ds_chomp(ds, ',');
+ ds_put_char(ds, ')');
+ }
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+str_to_nat_range(const char *s, struct ofpact_nat *on)
+{
+ char ipv6_s[IPV6_SCAN_LEN + 1];
+ char port_delim = ':';
+ int n = 0;
+
+ on->range_af = AF_UNSPEC;
+ if (ovs_scan_len(s, &n, IP_SCAN_FMT,
+ IP_SCAN_ARGS(&on->range.addr.ipv4.min))) {
+ on->range_af = AF_INET;
+
+ if (s[n] == '-') {
+ n++;
+ if (!ovs_scan_len(s, &n, IP_SCAN_FMT,
+ IP_SCAN_ARGS(&on->range.addr.ipv4.max))
+ || (ntohl(on->range.addr.ipv4.max)
+ < ntohl(on->range.addr.ipv4.min))) {
+ goto error;
+ }
+ }
+ } else if (ovs_scan_len(s, &n, IPV6_SCAN_FMT, ipv6_s)
+ && inet_pton(AF_INET6, ipv6_s, &on->range.addr.ipv6.min) == 1) {
+ on->range_af = AF_INET6;
+
+ if (s[n] == '-') {
+ n++;
+ if (!ovs_scan_len(s, &n, IPV6_SCAN_FMT, ipv6_s)
+ || inet_pton(AF_INET6, ipv6_s, &on->range.addr.ipv6.max) != 1
+ || memcmp(&on->range.addr.ipv6.max, &on->range.addr.ipv6.min,
+ sizeof on->range.addr.ipv6.max) < 0) {
+ goto error;
+ }
+ }
+ /* XXX: ':' as a delimiter seems difficult as it can be mixed with the
+ * delimiters in an IPv6 address. */
+ port_delim = '+';
+ }
+ if (on->range_af != AF_UNSPEC && s[n] == port_delim) {
+ n++;
+ if (!ovs_scan_len(s, &n, "%"SCNu16, &on->range.proto.min)) {
+ goto error;
+ }
+ if (s[n] == '-') {
+ n++;
+ if (!ovs_scan_len(s, &n, "%"SCNu16, &on->range.proto.max)
+ || on->range.proto.max < on->range.proto.min) {
+ goto error;
+ }
+ }
+ }
+ if (strlen(s) != n) {
+ return xasprintf("garbage (%s) after nat range \"%s\" (pos: %d)",
+ &s[n], s, n);
+ }
+ return NULL;
+error:
+ return xasprintf("invalid nat range \"%s\"", s);
+}
+
+
+/* Parses 'arg' as the argument to a "nat" action, and appends such an
+ * action to 'ofpacts'.
+ *
+ * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error. The caller is responsible for freeing the returned string. */
+static char * OVS_WARN_UNUSED_RESULT
+parse_NAT(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ struct ofpact_nat *on = ofpact_put_NAT(ofpacts);
+ char *key, *value;
+
+ on->flags = 0;
+ on->range_af = AF_UNSPEC;
+
+ while (ofputil_parse_key_value(&arg, &key, &value)) {
+ char *error = NULL;
+
+ if (!strcmp(key, "src")) {
+ on->flags |= NX_NAT_F_SRC;
+ error = str_to_nat_range(value, on);
+ } else if (!strcmp(key, "dst")) {
+ on->flags |= NX_NAT_F_DST;
+ error = str_to_nat_range(value, on);
+ } else if (!strcmp(key, "persistent")) {
+ on->flags |= NX_NAT_F_PERSISTENT;
+ } else if (!strcmp(key, "hash")) {
+ on->flags |= NX_NAT_F_PROTO_HASH;
+ } else if (!strcmp(key, "random")) {
+ on->flags |= NX_NAT_F_PROTO_RANDOM;
+ } else {
+ error = xasprintf("invalid key \"%s\" in \"nat\" argument",
+ key);
+ }
+ if (error) {
+ return error;
+ }
+ }
+ if (on->flags & NX_NAT_F_SRC && on->flags & NX_NAT_F_DST) {
+ return xasprintf("May only specify one of \"snat\" or \"dnat\".");
+ }
+ if (!(on->flags & NX_NAT_F_SRC || on->flags & NX_NAT_F_DST)) {
+ if (on->flags) {
+ return xasprintf("Flags allowed only with \"snat\" or \"dnat\".");
+ }
+ if (on->range_af != AF_UNSPEC) {
+ return xasprintf("Range allowed only with \"snat\" or \"dnat\".");
+ }
+ }
+ return NULL;
+}
+
+.
/* Meter instruction. */
static void
@@ -5288,6 +5660,7 @@ ofpact_is_set_or_move_action(const struct ofpact *a)
case OFPACT_BUNDLE:
case OFPACT_CLEAR_ACTIONS:
case OFPACT_CT:
+ case OFPACT_NAT:
case OFPACT_CONTROLLER:
case OFPACT_DEC_MPLS_TTL:
case OFPACT_DEC_TTL:
@@ -5363,6 +5736,7 @@ ofpact_is_allowed_in_actions_set(const struct ofpact *a)
case OFPACT_BUNDLE:
case OFPACT_CONTROLLER:
case OFPACT_CT:
+ case OFPACT_NAT:
case OFPACT_ENQUEUE:
case OFPACT_EXIT:
case OFPACT_UNROLL_XLATE:
@@ -5593,6 +5967,7 @@ ovs_instruction_type_from_ofpact_type(enum ofpact_type type)
case OFPACT_SAMPLE:
case OFPACT_DEBUG_RECIRC:
case OFPACT_CT:
+ case OFPACT_NAT:
default:
return OVSINST_OFPIT11_APPLY_ACTIONS;
}
@@ -6166,6 +6541,19 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
flow, max_ports, table_id, n_tables, &p);
}
+ case OFPACT_NAT: {
+ struct ofpact_nat *on = ofpact_get_NAT(a);
+
+ /* XXX: Check if any other criteria applies. */
+ if (!dl_type_is_ip_any(flow->dl_type) ||
+ (on->range_af == AF_INET && flow->dl_type != htons(ETH_TYPE_IP)) ||
+ (on->range_af == AF_INET6
+ && flow->dl_type != htons(ETH_TYPE_IPV6))) {
+ inconsistent_match(usable_protocols);
+ }
+ return 0;
+ }
+
case OFPACT_CLEAR_ACTIONS:
return 0;
@@ -6310,6 +6698,13 @@ ofpacts_verify_nested(const struct ofpact *a, enum ofpact_type outer_action)
VLOG_WARN("cannot set CT fields outside of ct action");
return OFPERR_OFPBAC_BAD_SET_ARGUMENT;
}
+ if (a->type == OFPACT_NAT) {
+ if (outer_action != OFPACT_CT) {
+ VLOG_WARN("Cannot have NAT action outside of \"ct\" action");
+ return OFPERR_OFPBAC_BAD_SET_ARGUMENT;
+ }
+ return 0;
+ }
if (outer_action) {
ovs_assert(outer_action == OFPACT_WRITE_ACTIONS
@@ -6681,6 +7076,7 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port)
case OFPACT_GROUP:
case OFPACT_DEBUG_RECIRC:
case OFPACT_CT:
+ case OFPACT_NAT:
default:
return false;
}
@@ -7347,7 +7743,8 @@ pad_ofpat(struct ofpbuf *openflow, size_t start_ofs)
{
struct ofp_action_header *oah;
- ofpbuf_put_zeros(openflow, PAD_SIZE(openflow->size - start_ofs, 8));
+ ofpbuf_put_zeros(openflow, PAD_SIZE(openflow->size - start_ofs,
+ OFP_ACTION_ALIGN));
oah = ofpbuf_at_assert(openflow, start_ofs, sizeof *oah);
oah->len = htons(openflow->size - start_ofs);
diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
index 773b617..d180767 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -107,6 +107,7 @@
OFPACT(SAMPLE, ofpact_sample, ofpact, "sample") \
OFPACT(UNROLL_XLATE, ofpact_unroll_xlate, ofpact, "unroll_xlate") \
OFPACT(CT, ofpact_conntrack, ofpact, "ct") \
+ OFPACT(NAT, ofpact_nat, ofpact, "nat") \
\
/* Debugging actions. \
* \
@@ -529,6 +530,42 @@ ofpact_nest_get_action_len(const struct ofpact_nest *on)
void ofpacts_execute_action_set(struct ofpbuf *action_list,
const struct ofpbuf *action_set);
+/* Bits for 'flags' in struct nx_action_nat.
+ */
+enum nx_nat_flags {
+ NX_NAT_F_SRC = 1 << 0,
+ NX_NAT_F_DST = 1 << 1,
+ NX_NAT_F_PERSISTENT = 1 << 2,
+ NX_NAT_F_PROTO_HASH = 1 << 3,
+ NX_NAT_F_PROTO_RANDOM = 1 << 4,
+};
+
+/* OFPACT_NAT.
+ *
+ * Used for NXAST_NAT. */
+struct ofpact_nat {
+ struct ofpact ofpact;
+ uint8_t range_af; /* AF_UNSPEC, AF_INET, or AF_INET6 */
+ uint16_t flags; /* NX_NAT_F_* */
+ struct {
+ struct {
+ uint16_t min;
+ uint16_t max;
+ } proto;
+ union {
+ struct {
+ ovs_be32 min;
+ ovs_be32 max;
+ } ipv4;
+ struct {
+ struct in6_addr min;
+ struct in6_addr max;
+ } ipv6;
+ } addr;
+ } range;
+};
+
+
/* OFPACT_RESUBMIT.
*
* Used for NXAST_RESUBMIT, NXAST_RESUBMIT_TABLE. */
@@ -844,7 +881,7 @@ void *ofpact_put(struct ofpbuf *, enum ofpact_type, size_t len);
*
* Appends a new 'ofpact', of length OFPACT_<ENUM>_RAW_SIZE, to 'ofpacts',
* initializes it with ofpact_init_<ENUM>(), and returns it. Also sets
- * 'ofpacts->l2' to the returned action.
+ * 'ofpacts->header' to the returned action.
*
* After using this function to add a variable-length action, add the
* elements of the flexible array (e.g. with ofpbuf_put()), then use
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index 8437656..b6e8d77 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -169,6 +169,19 @@ str_to_ip(const char *str, ovs_be32 *ip)
return NULL;
}
+/* Parses 'str' as an IP address into '*ip'.
+ *
+ * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error. The caller is responsible for freeing the returned string. */
+char * OVS_WARN_UNUSED_RESULT
+str_to_ipv6(const char *str, struct in6_addr *ip)
+{
+ if (lookup_ipv6(str, ip)) {
+ return xasprintf("%s: could not convert to IPv6 address", str);
+ }
+ return NULL;
+}
+
/* Parses 'str' as a conntrack helper into 'alg'.
*
* Returns NULL if successful, otherwise a malloc()'d string describing the
diff --git a/lib/ofp-parse.h b/lib/ofp-parse.h
index 36f9acc..9a803f8 100644
--- a/lib/ofp-parse.h
+++ b/lib/ofp-parse.h
@@ -99,6 +99,7 @@ char *str_to_u64(const char *str, uint64_t *valuep) OVS_WARN_UNUSED_RESULT;
char *str_to_be64(const char *str, ovs_be64 *valuep) OVS_WARN_UNUSED_RESULT;
char *str_to_mac(const char *str, struct eth_addr *mac) OVS_WARN_UNUSED_RESULT;
char *str_to_ip(const char *str, ovs_be32 *ip) OVS_WARN_UNUSED_RESULT;
+char *str_to_ipv6(const char *str, struct in6_addr *ip) OVS_WARN_UNUSED_RESULT;
char *str_to_connhelper(const char *str, uint16_t *alg) OVS_WARN_UNUSED_RESULT;
#endif /* ofp-parse.h */
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index a4007e3..9810c7b 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -305,6 +305,9 @@ struct xlate_ctx {
* state from the datapath should be honored after recirculation. */
bool conntracked;
+ /* Pointer to an embedded NAT action in a conntrack action, or NULL. */
+ struct ofpact_nat *ct_nat_action;
+
/* OpenFlow 1.1+ action set.
*
* 'action_set' accumulates "struct ofpact"s added by OFPACT_WRITE_ACTIONS.
@@ -4125,6 +4128,7 @@ recirc_unroll_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
case OFPACT_SAMPLE:
case OFPACT_DEBUG_RECIRC:
case OFPACT_CT:
+ case OFPACT_NAT:
break;
/* These need not be copied for restoration. */
@@ -4197,6 +4201,61 @@ put_ct_helper(struct ofpbuf *odp_actions, struct ofpact_conntrack *ofc)
}
static void
+put_ct_nat(struct xlate_ctx *ctx)
+{
+ struct ofpact_nat *ofn = ctx->ct_nat_action;
+ size_t nat_offset;
+
+ if (!ofn) {
+ return;
+ }
+
+ nat_offset = nl_msg_start_nested(ctx->odp_actions, OVS_CT_ATTR_NAT);
+ if (ofn->flags & NX_NAT_F_SRC || ofn->flags & NX_NAT_F_DST) {
+ nl_msg_put_flag(ctx->odp_actions, ofn->flags & NX_NAT_F_SRC
+ ? OVS_NAT_ATTR_SRC : OVS_NAT_ATTR_DST);
+ if (ofn->flags & NX_NAT_F_PERSISTENT) {
+ nl_msg_put_flag(ctx->odp_actions, OVS_NAT_ATTR_PERSISTENT);
+ }
+ if (ofn->flags & NX_NAT_F_PROTO_HASH) {
+ nl_msg_put_flag(ctx->odp_actions, OVS_NAT_ATTR_PROTO_HASH);
+ } else if (ofn->flags & NX_NAT_F_PROTO_RANDOM) {
+ nl_msg_put_flag(ctx->odp_actions, OVS_NAT_ATTR_PROTO_RANDOM);
+ }
+ if (ofn->range_af == AF_INET) {
+ nl_msg_put_u32(ctx->odp_actions, OVS_NAT_ATTR_IP_MIN,
+ ofn->range.addr.ipv4.min);
+ if (ofn->range.addr.ipv4.max &&
+ ofn->range.addr.ipv4.max > ofn->range.addr.ipv4.min) {
+ nl_msg_put_u32(ctx->odp_actions, OVS_NAT_ATTR_IP_MAX,
+ ofn->range.addr.ipv4.max);
+ }
+ } else if (ofn->range_af == AF_INET6) {
+ nl_msg_put_unspec(ctx->odp_actions, OVS_NAT_ATTR_IP_MIN,
+ &ofn->range.addr.ipv6.min,
+ sizeof ofn->range.addr.ipv6.min);
+ if (!ipv6_mask_is_any(&ofn->range.addr.ipv6.max) &&
+ memcmp(&ofn->range.addr.ipv6.max, &ofn->range.addr.ipv6.min,
+ sizeof ofn->range.addr.ipv6.max) > 0) {
+ nl_msg_put_unspec(ctx->odp_actions, OVS_NAT_ATTR_IP_MAX,
+ &ofn->range.addr.ipv6.max,
+ sizeof ofn->range.addr.ipv6.max);
+ }
+ }
+ if (ofn->range_af != AF_UNSPEC && ofn->range.proto.min) {
+ nl_msg_put_u16(ctx->odp_actions, OVS_NAT_ATTR_PROTO_MIN,
+ ofn->range.proto.min);
+ if (ofn->range.proto.max &&
+ ofn->range.proto.max > ofn->range.proto.min) {
+ nl_msg_put_u16(ctx->odp_actions, OVS_NAT_ATTR_PROTO_MAX,
+ ofn->range.proto.max);
+ }
+ }
+ }
+ nl_msg_end_nested(ctx->odp_actions, nat_offset);
+}
+
+static void
compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc)
{
ovs_u128 old_ct_label = ctx->base_flow.ct_label;
@@ -4209,6 +4268,7 @@ compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc)
xlate_commit_actions(ctx);
/* Process nested actions first, to populate the key. */
+ ctx->ct_nat_action = NULL;
do_xlate_actions(ofc->actions, ofpact_ct_get_action_len(ofc), ctx);
if (ofc->zone_src.field) {
@@ -4225,6 +4285,8 @@ compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc)
put_ct_mark(&ctx->xin->flow, &ctx->base_flow, ctx->odp_actions, ctx->wc);
put_ct_label(&ctx->xin->flow, &ctx->base_flow, ctx->odp_actions, ctx->wc);
put_ct_helper(ctx->odp_actions, ofc);
+ put_ct_nat(ctx);
+ ctx->ct_nat_action = NULL;
nl_msg_end_nested(ctx->odp_actions, ct_offset);
/* Restore the original ct fields in the key. These should only be exposed
@@ -4614,6 +4676,11 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
compose_conntrack_action(ctx, ofpact_get_CT(a));
break;
+ case OFPACT_NAT:
+ /* This will be processed by compose_conntrack_action(). */
+ ctx->ct_nat_action = ofpact_get_NAT(a);
+ break;
+
case OFPACT_DEBUG_RECIRC:
ctx_trigger_recirculation(ctx);
a = ofpact_next(a);
@@ -4922,6 +4989,8 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
.was_mpls = false,
.conntracked = false,
+ .ct_nat_action = NULL,
+
.action_set_has_group = false,
.action_set = OFPBUF_STUB_INITIALIZER(action_set_stub),
};
diff --git a/tests/odp.at b/tests/odp.at
index eaba059..7353de3 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -312,6 +312,13 @@ ct(commit,zone=5)
ct(commit,mark=0xa0a0a0a0/0xfefefefe)
ct(commit,label=0x1234567890abcdef1234567890abcdef/0xf1f2f3f4f5f6f7f8f9f0fafbfcfdfeff)
ct(commit,helper=ftp)
+ct(nat)
+ct(commit,nat(src))
+ct(commit,nat(dst))
+ct(commit,nat(src=10.0.0.240,random))
+ct(commit,nat(src=10.0.0.240:32768-65535,random))
+ct(commit,nat(dst=10.0.0.128-10.0.0.254,hash))
+ct(commit,nat(src=10.0.0.240-10.0.0.254:32768-65535,persistent))
])
AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0],
[`cat actions.txt`
diff --git a/tests/ofp-actions.at b/tests/ofp-actions.at
index 01e5b67..8ebd928 100644
--- a/tests/ofp-actions.at
+++ b/tests/ofp-actions.at
@@ -187,6 +187,53 @@ ffff 0018 00002320 0007 001f 00010004 000000000000f009
# actions=ct(alg=ftp)
ffff 0018 00002320 0023 0000 00000000 0000 FF 000000 0015
+# actions=ct(commit,nat(src))
+ffff 0028 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
+ffff 0010 00002320 0024 00 00 0001 0000
+
+# actions=ct(commit,nat(dst))
+ffff 0028 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
+ffff 0010 00002320 0024 00 00 0002 0000
+
+# actions=ct(nat)
+ffff 0028 00002320 0023 0000 00000000 0000 FF 000000 0000 dnl
+ffff 0010 00002320 0024 00 00 0000 0000
+
+# actions=ct(commit,nat(src=10.0.0.240,random))
+ffff 0030 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
+ffff 0018 00002320 0024 00 00 0011 0001 0a0000f0 00000000
+
+# actions=ct(commit,nat(src=10.0.0.240:32768-65535,random))
+ffff 0030 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
+ffff 0018 00002320 0024 00 00 0011 0031 0a0000f0 8000ffff
+
+# actions=ct(commit,nat(dst=10.0.0.128-10.0.0.254,hash))
+ffff 0030 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
+ffff 0018 00002320 0024 00 00 000a 0003 0a000080 0a0000fe
+
+# actions=ct(commit,nat(src=10.0.0.240-10.0.0.254:32768-65535,persistent))
+ffff 0038 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
+ffff 0020 00002320 0024 00 00 0005 0033 0a0000f0 0a0000fe 8000ffff 00000000
+
+# actions=ct(commit,nat(src=fe80::20c:29ff:fe88:a18b,random))
+ffff 0038 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
+ffff 0020 00002320 0024 00 00 0011 0004 fe800000 00000000 020c 29ff fe88 a18b
+
+# actions=ct(commit,nat(src=fe80::20c:29ff:fe88:1-fe80::20c:29ff:fe88:a18b,random))
+ffff 0048 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
+ffff 0030 00002320 0024 00 00 0011 000c fe800000 00000000 020c 29ff fe88 0001 fe800000 00000000 020c 29ff fe88 a18b
+
+# actions=ct(commit,nat(src=fe80::20c:29ff:fe88:1-fe80::20c:29ff:fe88:a18b+255-4096,random))
+ffff 0050 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
+ffff 0038 00002320 0024 00 00 0011 003c dnl
+fe800000 00000000 020c 29ff fe88 0001 dnl
+fe800000 00000000 020c 29ff fe88 a18b dnl
+00ff1000 00000000
+
+# bad OpenFlow10 actions: OFPBAC_BAD_ARGUMENT
+ffff 0048 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
+ffff 0030 00002320 0024 00 00 0011 000c fe800000 00000000 020c 29ff fe88 a18b fe800000 00000000 020c 29ff fe88 0001
+
])
sed '/^[[#&]]/d' < test-data > input.txt
sed -n 's/^# //p; /^$/p' < test-data > expout
diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
index 7375cad..48e6106 100644
--- a/tests/ovs-ofctl.at
+++ b/tests/ovs-ofctl.at
@@ -160,12 +160,23 @@ sctp actions=drop
sctp actions=drop
in_port=0 actions=resubmit:0
actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+actions=ct(nat)
+actions=ct(commit,nat(dst))
+actions=ct(commit,nat(src))
+actions=ct(commit,nat(src=10.0.0.240,random))
+actions=ct(commit,nat(src=10.0.0.240:32768-65535,random))
+actions=ct(commit,nat(dst=10.0.0.128-10.0.0.254,hash))
+actions=ct(commit,nat(src=10.0.0.240-10.0.0.254:32768-65535,persistent))
+actions=ct(commit,nat(src=fe80::20c:29ff:fe88:a18b,random))
+actions=ct(commit,nat(src=fe80::20c:29ff:fe88:1-fe80::20c:29ff:fe88:a18b,random))
+actions=ct(commit,nat(src=fe80::20c:29ff:fe88:1-fe80::20c:29ff:fe88:a18b+255-4096,random))
+actions=ct(commit,nat(src=10.1.1.240-10.1.1.255),alg=ftp)
]])
AT_CHECK([ovs-ofctl parse-flows flows.txt
], [0], [stdout])
AT_CHECK([[sed 's/ (xid=0x[0-9a-fA-F]*)//' stdout]], [0],
-[[usable protocols: any
+[[usable protocols: OpenFlow10,NXM
chosen protocol: OpenFlow10-table_id
OFPT_FLOW_MOD: ADD tcp,tp_src=123 actions=FLOOD
OFPT_FLOW_MOD: ADD in_port=LOCAL,dl_vlan=9,dl_src=00:0a:e4:25:6b:b0 actions=drop
@@ -179,6 +190,17 @@ OFPT_FLOW_MOD: ADD sctp actions=drop
OFPT_FLOW_MOD: ADD sctp actions=drop
OFPT_FLOW_MOD: ADD in_port=0 actions=resubmit:0
OFPT_FLOW_MOD: ADD actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+OFPT_FLOW_MOD: ADD actions=ct(nat)
+OFPT_FLOW_MOD: ADD actions=ct(commit,nat(dst))
+OFPT_FLOW_MOD: ADD actions=ct(commit,nat(src))
+OFPT_FLOW_MOD: ADD actions=ct(commit,nat(src=10.0.0.240,random))
+OFPT_FLOW_MOD: ADD actions=ct(commit,nat(src=10.0.0.240:32768-65535,random))
+OFPT_FLOW_MOD: ADD actions=ct(commit,nat(dst=10.0.0.128-10.0.0.254,hash))
+OFPT_FLOW_MOD: ADD actions=ct(commit,nat(src=10.0.0.240-10.0.0.254:32768-65535,persistent))
+OFPT_FLOW_MOD: ADD actions=ct(commit,nat(src=fe80::20c:29ff:fe88:a18b,random))
+OFPT_FLOW_MOD: ADD actions=ct(commit,nat(src=fe80::20c:29ff:fe88:1-fe80::20c:29ff:fe88:a18b,random))
+OFPT_FLOW_MOD: ADD actions=ct(commit,nat(src=fe80::20c:29ff:fe88:1-fe80::20c:29ff:fe88:a18b+255-4096,random))
+OFPT_FLOW_MOD: ADD actions=ct(commit,nat(src=10.1.1.240-10.1.1.255),alg=ftp)
]])
AT_CLEANUP
diff --git a/tests/system-traffic.at b/tests/system-traffic.at
index 3b2de83..5b27100 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -966,7 +966,7 @@ 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])
+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([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
@@ -975,7 +975,7 @@ TIME_WAIT src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> src=10.1.1.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])
+NS_CHECK_EXEC([at_ns0], [wget ftp://10.1.1.2 -t 3 -T 1 --retry-connrefused -v -o wget0-2.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
@@ -1266,3 +1266,459 @@ NS_CHECK_EXEC([at_ns0], [ping -s 3200 -q -c 3 -i 0.3 -w 2 10.1.1.100 | FORMAT_PI
OVS_TRAFFIC_VSWITCHD_STOP
AT_CLEANUP
+
+AT_SETUP([conntrack - simple SNAT])
+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")
+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,action=ct(commit,zone=1,nat(src=10.1.1.240-10.1.1.255)),2
+in_port=2,ct_state=-trk,ip,action=ct(table=0,zone=1,nat)
+in_port=2,ct_state=+trk,ct_zone=1,ip,action=1
+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
+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 -O OpenFlow15 add-flows br0 flows.txt])
+
+on_exit 'conntrack -L --output extended --any-nat'
+on_exit 'ovs-ofctl -O OpenFlow15 dump-flows br0'
+on_exit 'ovs-ofctl dump-ports br0'
+
+dnl HTTP requests from p0->p1 should work fine.
+NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-l7.py]], [http0.pid])
+NS_CHECK_EXEC([at_ns0], [wget 10.1.1.2 -t 5 -T 1 --retry-connrefused -v -o wget0.log])
+
+AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.2) | sed -e 's/dst=10.1.1.2[[45]][[0-9]]/dst=10.1.1.2XX/'], [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.2XX sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 zone=1 use=1
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([conntrack - more complex SNAT])
+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")
+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")
+
+AT_DATA([flows.txt], [dnl
+dnl Track all IP traffic, NAT existing connections.
+priority=100 ip action=ct(table=1,zone=1,nat)
+dnl
+dnl Allow ARP, but generate responses for NATed addresses
+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
+dnl Allow any traffic from ns0->ns1. SNAT ns0 to 10.1.1.240-10.1.1.255
+table=1 priority=100 in_port=1 ip ct_state=+trk+new-est action=ct(commit,zone=1,nat(src=10.1.1.240-10.1.1.255)),2
+table=1 priority=100 in_port=1 ip ct_state=+trk-new+est action=2
+dnl Only allow established traffic from ns1->ns0.
+table=1 priority=100 in_port=2 ip ct_state=+trk-new+est action=1
+table=1 priority=0 action=drop
+dnl
+dnl MAC resolution table for IP in reg2, stores mac in OXM_OF_PKT_REG0
+table=8 priority=100 reg2=0x0a0101f0/0xfffffff0 action=load:0x808888888888->OXM_OF_PKT_REG0[[]]
+dnl Zero result means not found.
+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 ARP TPA IP in reg2.
+table=10 priority=100 arp xreg0=0 action=normal
+dnl Swaps the fields of the ARP message to turn a query to a response.
+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 -O OpenFlow15 add-flows br0 flows.txt])
+
+on_exit 'conntrack -L --output extended'
+on_exit 'ovs-ofctl -O OpenFlow15 dump-flows br0'
+on_exit 'ovs-ofctl dump-ports br0'
+on_exit 'ovs-appctl dpctl/dump-flows'
+
+dnl HTTP requests from p0->p1 should work fine.
+NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-l7.py]], [http0.pid])
+NS_CHECK_EXEC([at_ns0], [wget 10.1.1.2 -t 5 -T 1 --retry-connrefused -v -o wget0.log])
+
+AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.2) | sed -e 's/dst=10.1.1.2[[45]][[0-9]]/dst=10.1.1.2XX/'], [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.2XX sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 zone=1 use=1
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([conntrack - simple DNAT])
+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")
+NS_CHECK_EXEC([at_ns1], [ip link set dev p1 address 80:88:88:88:88:88])
+
+dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from ns1->ns0.
+AT_DATA([flows.txt], [dnl
+priority=100 in_port=1,ip,nw_dst=10.1.1.64,action=ct(zone=1,nat(dst=10.1.1.2),commit),2
+priority=10 in_port=1,ip,action=ct(commit,zone=1),2
+priority=100 in_port=2,ct_state=-trk,ip,action=ct(table=0,nat,zone=1)
+priority=100 in_port=2,ct_state=+trk+est,ct_zone=1,ip,action=1
+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
+dnl MAC resolution table for IP in reg2, stores mac in OXM_OF_PKT_REG0
+table=8,reg2=0x0a010140,action=load:0x808888888888->OXM_OF_PKT_REG0[[]]
+dnl Zero result means not found.
+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.
+table=10 priority=100 arp xreg0=0 action=normal
+dnl Swaps the fields of the ARP message to turn a query to a response.
+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 -O OpenFlow15 add-flows br0 flows.txt])
+
+on_exit 'conntrack -L --output extended'
+on_exit 'ovs-ofctl -O OpenFlow15 dump-flows br0'
+on_exit 'ovs-ofctl dump-ports br0'
+on_exit 'ovs-appctl dpctl/dump-flows'
+
+dnl Should work with the virtual IP address through NAT
+NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-l7.py]], [http0.pid])
+NS_CHECK_EXEC([at_ns0], [wget 10.1.1.64 -t 5 -T 1 --retry-connrefused -v -o wget0.log])
+
+AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.64) ], [0], [dnl
+TIME_WAIT src=10.1.1.1 dst=10.1.1.64 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
+])
+
+dnl Should work with the assigned IP address as well
+NS_CHECK_EXEC([at_ns0], [wget 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) ], [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 use=1
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([conntrack - more complex DNAT])
+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")
+NS_CHECK_EXEC([at_ns1], [ip link set dev p1 address 80:88:88:88:88:88])
+
+dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from ns1->ns0.
+AT_DATA([flows.txt], [dnl
+dnl Track all IP traffic
+table=0 priority=100 ip action=ct(table=1,zone=1,nat)
+dnl
+dnl Allow ARP, but generate responses for NATed addresses
+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 Allow any IP traffic from ns0->ns1. DNAT ns0 from 10.1.1.64 to 10.1.1.2
+table=1 priority=100 in_port=1 ct_state=+new ip nw_dst=10.1.1.64 action=ct(zone=1,nat(dst=10.1.1.2),commit),2
+table=1 priority=10 in_port=1 ct_state=+new ip action=ct(commit,zone=1),2
+table=1 priority=100 in_port=1 ct_state=+est ct_zone=1 action=2
+dnl Only allow established traffic from ns1->ns0.
+table=1 priority=100 in_port=2 ct_state=+est ct_zone=1 action=1
+table=1 priority=0 action=drop
+dnl
+dnl MAC resolution table for IP in reg2, stores mac in OXM_OF_PKT_REG0
+table=8,reg2=0x0a010140,action=load:0x808888888888->OXM_OF_PKT_REG0[[]]
+dnl Zero result means not found.
+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.
+table=10 priority=100 arp xreg0=0 action=normal
+dnl Swaps the fields of the ARP message to turn a query to a response.
+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 -O OpenFlow15 add-flows br0 flows.txt])
+
+on_exit 'conntrack -L --output extended --any-nat'
+on_exit 'ovs-ofctl -O OpenFlow15 dump-flows br0'
+on_exit 'ovs-ofctl dump-ports br0'
+
+dnl Should work with the virtual IP address through NAT
+NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-l7.py]], [http0.pid])
+NS_CHECK_EXEC([at_ns0], [wget 10.1.1.64 -t 5 -T 1 --retry-connrefused -v -o wget0.log])
+
+AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.64) ], [0], [dnl
+TIME_WAIT src=10.1.1.1 dst=10.1.1.64 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
+])
+
+dnl Should work with the assigned IP address as well
+NS_CHECK_EXEC([at_ns0], [wget 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) ], [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 use=1
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([conntrack - ICMP related with NAT])
+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")
+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 UDP traffic from ns0->ns1. Only allow related ICMP responses back.
+dnl Make sure ICMP responses are reverse-NATted.
+AT_DATA([flows.txt], [dnl
+in_port=1,udp,action=ct(commit,nat(src=10.1.1.240-10.1.1.255),exec(set_field:1->ct_mark)),2
+in_port=2,icmp,ct_state=-trk,action=ct(table=0,nat)
+in_port=2,icmp,nw_dst=10.1.1.1,ct_state=+trk+rel,ct_mark=1,action=1
+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
+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 -O OpenFlow15 add-flows br0 flows.txt])
+
+on_exit 'conntrack -L --output extended --any-nat'
+on_exit 'ovs-ofctl -O OpenFlow15 dump-flows br0'
+on_exit 'ovs-ofctl dump-ports br0'
+on_exit 'ovs-appctl dpctl/dump-flows'
+
+dnl UDP packets from ns0->ns1 should solicit "destination unreachable" response.
+dnl We pass "-q 1" here to handle openbsd-style nc that can't quit immediately.
+NS_CHECK_EXEC([at_ns0], [bash -c "echo a | nc -q 1 -u 10.1.1.2 10000"])
+
+AT_CHECK([ovs-appctl revalidator/purge], [0])
+AT_CHECK([ovs-ofctl -O OpenFlow15 dump-flows br0 | ofctl_strip | sort | grep -v drop], [0], [dnl
+ n_packets=1, n_bytes=42, priority=10,arp actions=NORMAL
+ n_packets=1, n_bytes=44, udp,in_port=1 actions=ct(commit,nat(src=10.1.1.240-10.1.1.255),exec(set_field:0x1->ct_mark)),output:2
+ n_packets=1, n_bytes=72, ct_state=+rel+trk,ct_mark=0x1,icmp,in_port=2,nw_dst=10.1.1.1 actions=output:1
+ n_packets=1, n_bytes=72, ct_state=-trk,icmp,in_port=2 actions=ct(table=0,nat)
+ n_packets=2, n_bytes=84, priority=100,arp,arp_op=1 actions=move:NXM_OF_ARP_TPA[[]]->NXM_NX_REG2[[]],resubmit(,8),goto_table:10
+ table=10, n_packets=1, n_bytes=42, priority=10,arp,arp_op=1 actions=set_field:2->arp_op,move:NXM_NX_ARP_SHA[[]]->NXM_NX_ARP_THA[[]],move:OXM_OF_PKT_REG0[[0..47]]->NXM_NX_ARP_SHA[[]],move:NXM_OF_ARP_SPA[[]]->NXM_OF_ARP_TPA[[]],move:NXM_NX_REG2[[]]->NXM_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]],set_field:0->in_port,output:NXM_NX_REG3[[0..15]]
+ table=10, n_packets=1, n_bytes=42, priority=100,arp,reg0=0,reg1=0 actions=NORMAL
+ table=8, n_packets=1, n_bytes=42, priority=0 actions=set_field:0->xreg0
+ table=8, n_packets=1, n_bytes=42, reg2=0xa0101f0/0xfffffff0 actions=set_field:0x808888888888->xreg0
+OFPST_FLOW reply (OF1.5):
+])
+
+AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.2) | sed -e 's/dst=10.1.1.2[[45]][[0-9]]/dst=10.1.1.2XX/'], [0], [dnl
+src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> [[UNREPLIED]] src=10.1.1.2 dst=10.1.1.2XX sport=<cleared> dport=<cleared> mark=1 use=1
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+
+AT_SETUP([conntrack - FTP with NAT])
+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")
+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
+dnl track all IP traffic, de-mangle non-NEW connections
+table=0 in_port=1, ip, action=ct(table=1,nat)
+table=0 in_port=2, ip, action=ct(table=2,nat)
+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 Table 1: port 1 -> 2
+dnl
+dnl Allow new FTP connections. These need to be commited.
+table=1 ct_state=+new, tcp, tp_dst=21, action=ct(alg=ftp,commit,nat(src=10.1.1.240)),2
+dnl Allow established TCP connections
+table=1 ct_state=+est, tcp, action=2
+dnl
+dnl Table 1: droppers
+dnl
+table=1 priority=10, tcp, action=drop
+table=1 priority=0,action=drop
+dnl
+dnl Table 2: port 2 -> 1
+dnl
+dnl Allow established TCP connections
+table=2 ct_state=+est, tcp, action=1
+dnl Allow (new) related (data) connections. These need to be commited.
+table=2 ct_state=+new+rel, tcp, action=ct(commit,nat),1
+dnl Allow related ICMP packets, make sure they are reverse NATted
+table=2 ct_state=+rel, icmp, nw_dst=10.1.1.1, action=1
+dnl
+dnl Table 2: droppers
+dnl
+table=2 priority=10, tcp, action=drop
+table=2 priority=0, action=drop
+dnl
+dnl MAC resolution table for IP in reg2, stores mac in OXM_OF_PKT_REG0
+dnl
+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 -O OpenFlow15 add-flows br0 flows.txt])
+
+on_exit 'conntrack -L --output extended --any-nat'
+on_exit 'ovs-ofctl -O OpenFlow15 dump-flows br0'
+on_exit 'ovs-ofctl dump-ports br0'
+on_exit 'ovs-appctl dpctl/dump-flows'
+
+dnl 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 p0->p1 should work fine.
+NS_CHECK_EXEC([at_ns0], [wget ftp://10.1.1.2 -4 --no-passive-ftp -t 3 -T 1 --retry-connrefused -v --server-response --no-proxy --no-remove-listing -o wget0.log -d])
+
+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.240 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 helper=ftp use=2
+TIME_WAIT src=10.1.1.2 dst=10.1.1.240 sport=<cleared> dport=<cleared> src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 use=1
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+
+AT_SETUP([conntrack - FTP with NAT 2])
+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")
+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.
+dnl Only allow nd, return traffic from ns1->ns0.
+AT_DATA([flows.txt], [dnl
+dnl track all IP traffic (this includes a helper call to non-NEW packets.)
+table=0 ip, action=ct(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 Table 1
+dnl
+dnl Allow new FTP connections. These need to be commited.
+dnl This does helper for new packets.
+table=1 in_port=1 ct_state=+new, tcp, tp_dst=21, action=ct(alg=ftp,commit,nat(src=10.1.1.240)),2
+dnl Allow and NAT established TCP connections
+table=1 in_port=1 ct_state=+est, tcp, action=ct(nat),2
+table=1 in_port=2 ct_state=+est, tcp, action=ct(nat),1
+dnl Allow and NAT (new) related active (data) connections.
+dnl These need to be commited.
+table=1 in_port=2 ct_state=+new+rel, tcp, action=ct(commit,nat),1
+dnl Allow related ICMP packets.
+table=1 in_port=2 ct_state=+rel, icmp, action=ct(nat),1
+dnl Drop everything else.
+table=1 priority=0, action=drop
+dnl
+dnl MAC resolution table for IP in reg2, stores mac in OXM_OF_PKT_REG0
+dnl
+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 -O OpenFlow15 add-flows br0 flows.txt])
+
+on_exit 'conntrack -L --output extended --any-nat'
+on_exit 'ovs-ofctl -O OpenFlow15 dump-flows br0'
+on_exit 'ovs-ofctl dump-ports br0'
+on_exit 'ovs-appctl dpctl/dump-flows'
+
+NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-l7.py ftp]], [ftp0.pid])
+
+dnl FTP requests from p0->p1 should work fine.
+NS_CHECK_EXEC([at_ns0], [wget ftp://10.1.1.2 -4 --no-passive-ftp -t 3 -T 1 --retry-connrefused -v --server-response --no-proxy --no-remove-listing -o wget0.log -d])
+
+AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.2) | grep -v "FIN" | grep -v "CLOSE"], [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.240 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 helper=ftp use=2
+TIME_WAIT src=10.1.1.2 dst=10.1.1.240 sport=<cleared> dport=<cleared> src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 use=1
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
--
2.1.4
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Powered by blists - more mailing lists