2
0
mirror of https://github.com/openvswitch/ovs synced 2025-09-03 07:45:30 +00:00

conntrack: 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 <jarno@ovn.org>
Acked-by: Ben Pfaff <blp@ovn.org>
This commit is contained in:
Jarno Rajahalme
2015-11-24 15:47:56 -08:00
parent 2fa3e06d35
commit 9ac0aadab9
12 changed files with 1579 additions and 39 deletions

View File

@@ -478,6 +478,12 @@ struct ovs_key_ct_labels {
#define OVS_CS_F_REPLY_DIR 0x08 /* Flow is in the reply direction. */ #define OVS_CS_F_REPLY_DIR 0x08 /* Flow is in the reply direction. */
#define OVS_CS_F_INVALID 0x10 /* Could not track connection. */ #define OVS_CS_F_INVALID 0x10 /* Could not track connection. */
#define OVS_CS_F_TRACKED 0x20 /* Conntrack has occurred. */ #define OVS_CS_F_TRACKED 0x20 /* Conntrack has occurred. */
#define OVS_CS_F_SRC_NAT 0x40 /* Packet's source address/port was
mangled by NAT. */
#define OVS_CS_F_DST_NAT 0x80 /* Packet's destination address/port
was mangled by NAT. */
#define OVS_CS_F_NAT_MASK (OVS_CS_F_SRC_NAT | OVS_CS_F_DST_NAT)
/** /**
* enum ovs_flow_attr - attributes for %OVS_FLOW_* commands. * enum ovs_flow_attr - attributes for %OVS_FLOW_* commands.
@@ -686,11 +692,49 @@ enum ovs_ct_attr {
OVS_CT_ATTR_LABELS, /* label to associate with this connection. */ OVS_CT_ATTR_LABELS, /* label to associate with this connection. */
OVS_CT_ATTR_HELPER, /* netlink helper to assist detection of OVS_CT_ATTR_HELPER, /* netlink helper to assist detection of
related connections. */ related connections. */
OVS_CT_ATTR_NAT, /* Nested OVS_NAT_ATTR_* */
__OVS_CT_ATTR_MAX __OVS_CT_ATTR_MAX
}; };
#define OVS_CT_ATTR_MAX (__OVS_CT_ATTR_MAX - 1) #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. * enum ovs_action_attr - Action types.
* *

View File

@@ -872,6 +872,10 @@ const char *ct_state_to_string(uint32_t state)
return "rel"; return "rel";
case CS_INVALID: case CS_INVALID:
return "inv"; return "inv";
case CS_SRC_NAT:
return "snat";
case CS_DST_NAT:
return "dnat";
default: default:
return NULL; return NULL;
} }

View File

@@ -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); 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;
uint16_t 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;
ip_min = addr_len == sizeof(ovs_be32) && a[OVS_NAT_ATTR_IP_MIN]
? nl_attr_get_be32(a[OVS_NAT_ATTR_IP_MIN]) : 0;
ip_max = addr_len == sizeof(ovs_be32) && a[OVS_NAT_ATTR_IP_MAX]
? nl_attr_get_be32(a[OVS_NAT_ATTR_IP_MAX]) : 0;
if (addr_len == sizeof ip6_min) {
ip6_min = a[OVS_NAT_ATTR_IP_MIN]
? *(struct in6_addr *)nl_attr_get(a[OVS_NAT_ATTR_IP_MIN])
: in6addr_any;
ip6_max = a[OVS_NAT_ATTR_IP_MAX]
? *(struct in6_addr *)nl_attr_get(a[OVS_NAT_ATTR_IP_MAX])
: in6addr_any;
}
proto_min = a[OVS_NAT_ATTR_PROTO_MIN]
? nl_attr_get_u16(a[OVS_NAT_ATTR_PROTO_MIN]) : 0;
proto_max = a[OVS_NAT_ATTR_PROTO_MAX]
? nl_attr_get_u16(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)
&& !ipv6_mask_is_any(&ip6_max)
&& memcmp(&ip6_min, &ip6_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) {
ipv6_format_addr_bracket(&ip6_min, ds, proto_min);
if (!ipv6_mask_is_any(&ip6_max) &&
memcmp(&ip6_max, &ip6_min, sizeof ip6_max) != 0) {
ds_put_char(ds, '-');
ipv6_format_addr_bracket(&ip6_max, ds, proto_min);
}
}
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[] = { static const struct nl_policy ovs_conntrack_policy[] = {
[OVS_CT_ATTR_COMMIT] = { .type = NL_A_FLAG, .optional = true, }, [OVS_CT_ATTR_COMMIT] = { .type = NL_A_FLAG, .optional = true, },
[OVS_CT_ATTR_ZONE] = { .type = NL_A_U16, .optional = true, }, [OVS_CT_ATTR_ZONE] = { .type = NL_A_U16, .optional = true, },
@@ -551,6 +695,7 @@ static const struct nl_policy ovs_conntrack_policy[] = {
.min_len = sizeof(struct ovs_key_ct_labels) * 2 }, .min_len = sizeof(struct ovs_key_ct_labels) * 2 },
[OVS_CT_ATTR_HELPER] = { .type = NL_A_STRING, .optional = true, [OVS_CT_ATTR_HELPER] = { .type = NL_A_STRING, .optional = true,
.min_len = 1, .max_len = 16 }, .min_len = 1, .max_len = 16 },
[OVS_CT_ATTR_NAT] = { .type = NL_A_UNSPEC, .optional = true },
}; };
static void static void
@@ -562,6 +707,7 @@ format_odp_conntrack_action(struct ds *ds, const struct nlattr *attr)
const char *helper; const char *helper;
uint16_t zone; uint16_t zone;
bool commit; bool commit;
const struct nlattr *nat;
if (!nl_parse_nested(attr, ovs_conntrack_policy, a, ARRAY_SIZE(a))) { if (!nl_parse_nested(attr, ovs_conntrack_policy, a, ARRAY_SIZE(a))) {
ds_put_cstr(ds, "ct(error)"); ds_put_cstr(ds, "ct(error)");
@@ -573,9 +719,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; 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; 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; 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"); ds_put_format(ds, "ct");
if (commit || zone || mark || label || helper) { if (commit || zone || mark || label || helper || nat) {
ds_put_cstr(ds, "("); ds_put_cstr(ds, "(");
if (commit) { if (commit) {
ds_put_format(ds, "commit,"); ds_put_format(ds, "commit,");
@@ -595,6 +742,9 @@ format_odp_conntrack_action(struct ds *ds, const struct nlattr *attr)
if (helper) { if (helper) {
ds_put_format(ds, "helper=%s,", helper); ds_put_format(ds, "helper=%s,", helper);
} }
if (nat) {
format_odp_ct_nat(ds, nat);
}
ds_chomp(ds, ','); ds_chomp(ds, ',');
ds_put_cstr(ds, ")"); ds_put_cstr(ds, ")");
} }
@@ -876,15 +1026,15 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
l4 = ((uint8_t *) l3 + sizeof (struct ip_header)); l4 = ((uint8_t *) l3 + sizeof (struct ip_header));
ip = (struct ip_header *) l3; ip = (struct ip_header *) l3;
if (!ovs_scan_len(s, &n, "header(size=%"SCNi32",type=%"SCNi32"," if (!ovs_scan_len(s, &n, "header(size=%"SCNi32",type=%"SCNi32","
"eth(dst="ETH_ADDR_SCAN_FMT",", "eth(dst="ETH_ADDR_SCAN_FMT",",
&data->header_len, &data->header_len,
&data->tnl_type, &data->tnl_type,
ETH_ADDR_SCAN_ARGS(eth->eth_dst))) { ETH_ADDR_SCAN_ARGS(eth->eth_dst))) {
return -EINVAL; return -EINVAL;
} }
if (!ovs_scan_len(s, &n, "src="ETH_ADDR_SCAN_FMT",", 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; return -EINVAL;
} }
if (!ovs_scan_len(s, &n, "dl_type=0x%"SCNx16"),", &dl_type)) { if (!ovs_scan_len(s, &n, "dl_type=0x%"SCNx16"),", &dl_type)) {
@@ -894,11 +1044,11 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
/* IPv4 */ /* IPv4 */
if (!ovs_scan_len(s, &n, "ipv4(src="IP_SCAN_FMT",dst="IP_SCAN_FMT",proto=%"SCNi8 if (!ovs_scan_len(s, &n, "ipv4(src="IP_SCAN_FMT",dst="IP_SCAN_FMT",proto=%"SCNi8
",tos=%"SCNi8",ttl=%"SCNi8",frag=0x%"SCNx16"),", ",tos=%"SCNi8",ttl=%"SCNi8",frag=0x%"SCNx16"),",
IP_SCAN_ARGS(&sip), IP_SCAN_ARGS(&sip),
IP_SCAN_ARGS(&dip), IP_SCAN_ARGS(&dip),
&ip->ip_proto, &ip->ip_tos, &ip->ip_proto, &ip->ip_tos,
&ip->ip_ttl, &ip->ip_frag_off)) { &ip->ip_ttl, &ip->ip_frag_off)) {
return -EINVAL; return -EINVAL;
} }
put_16aligned_be32(&ip->ip_src, sip); put_16aligned_be32(&ip->ip_src, sip);
@@ -908,7 +1058,7 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
udp = (struct udp_header *) l4; udp = (struct udp_header *) l4;
greh = (struct gre_base_hdr *) l4; greh = (struct gre_base_hdr *) l4;
if (ovs_scan_len(s, &n, "udp(src=%"SCNi16",dst=%"SCNi16",csum=0x%"SCNx16"),", 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; uint32_t vx_flags, vni;
udp->udp_src = htons(udp_src); udp->udp_src = htons(udp_src);
@@ -917,7 +1067,7 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
udp->udp_csum = htons(csum); udp->udp_csum = htons(csum);
if (ovs_scan_len(s, &n, "vxlan(flags=0x%"SCNx32",vni=0x%"SCNx32"))", 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); struct vxlanhdr *vxh = (struct vxlanhdr *) (udp + 1);
put_16aligned_be32(&vxh->vx_flags, htonl(vx_flags)); put_16aligned_be32(&vxh->vx_flags, htonl(vx_flags));
@@ -968,7 +1118,7 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
return -EINVAL; return -EINVAL;
} }
} else if (ovs_scan_len(s, &n, "gre((flags=0x%"SCNx16",proto=0x%"SCNx16")", } 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; tnl_type = OVS_VPORT_TYPE_GRE;
greh->flags = htons(gre_flags); greh->flags = htons(gre_flags);
@@ -1030,6 +1180,176 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
return n; 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;
}
}
} else if ((ovs_scan_len(s, n, IPV6_SCAN_FMT, ipv6_s)
|| 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)
|| 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;
}
}
} 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;
}
}
}
}
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 static int
parse_conntrack_action(const char *s_, struct ofpbuf *actions) parse_conntrack_action(const char *s_, struct ofpbuf *actions)
{ {
@@ -1048,6 +1368,8 @@ parse_conntrack_action(const char *s_, struct ofpbuf *actions)
ovs_u128 value; ovs_u128 value;
ovs_u128 mask; ovs_u128 mask;
} ct_label; } ct_label;
struct ct_nat_params nat_params;
bool have_nat = false;
size_t start; size_t start;
char *end; char *end;
@@ -1056,13 +1378,14 @@ parse_conntrack_action(const char *s_, struct ofpbuf *actions)
s += 2; s += 2;
if (ovs_scan(s, "(")) { if (ovs_scan(s, "(")) {
s++; s++;
find_end:
end = strchr(s, ')'); end = strchr(s, ')');
if (!end) { if (!end) {
return -EINVAL; return -EINVAL;
} }
while (s != end) { while (s != end) {
int n = -1; int n;
s += strspn(s, delimiters); s += strspn(s, delimiters);
if (ovs_scan(s, "commit%n", &n)) { if (ovs_scan(s, "commit%n", &n)) {
@@ -1106,6 +1429,16 @@ parse_conntrack_action(const char *s_, struct ofpbuf *actions)
continue; 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; return -EINVAL;
} }
s++; s++;
@@ -1130,6 +1463,9 @@ parse_conntrack_action(const char *s_, struct ofpbuf *actions)
nl_msg_put_string__(actions, OVS_CT_ATTR_HELPER, helper, nl_msg_put_string__(actions, OVS_CT_ATTR_HELPER, helper,
helper_len); helper_len);
} }
if (have_nat) {
nl_msg_put_ct_nat(&nat_params, actions);
}
nl_msg_end_nested(actions, start); nl_msg_end_nested(actions, start);
} }

View File

@@ -28,6 +28,7 @@
#include "meta-flow.h" #include "meta-flow.h"
#include "multipath.h" #include "multipath.h"
#include "nx-match.h" #include "nx-match.h"
#include "odp-netlink.h"
#include "ofp-parse.h" #include "ofp-parse.h"
#include "ofp-util.h" #include "ofp-util.h"
#include "ofpbuf.h" #include "ofpbuf.h"
@@ -291,6 +292,9 @@ enum ofp_raw_action_type {
/* NX1.0+(35): struct nx_action_conntrack, ... */ /* NX1.0+(35): struct nx_action_conntrack, ... */
NXAST_RAW_CT, NXAST_RAW_CT,
/* NX1.0+(36): struct nx_action_nat, ... */
NXAST_RAW_NAT,
/* ## ------------------ ## */ /* ## ------------------ ## */
/* ## Debugging actions. ## */ /* ## Debugging actions. ## */
/* ## ------------------ ## */ /* ## ------------------ ## */
@@ -4320,21 +4324,12 @@ encode_NOTE(const struct ofpact_note *note,
{ {
size_t start_ofs = out->size; size_t start_ofs = out->size;
struct nx_action_note *nan; struct nx_action_note *nan;
unsigned int remainder;
unsigned int len;
put_NXAST_NOTE(out); put_NXAST_NOTE(out);
out->size = out->size - sizeof nan->note; out->size = out->size - sizeof nan->note;
ofpbuf_put(out, note->data, note->length); ofpbuf_put(out, note->data, note->length);
pad_ofpat(out, start_ofs);
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);
} }
static char * OVS_WARN_UNUSED_RESULT static char * OVS_WARN_UNUSED_RESULT
@@ -4777,9 +4772,18 @@ decode_NXAST_RAW_CT(const struct nx_action_conntrack *nac,
if (conntrack->ofpact.len > sizeof(*conntrack) if (conntrack->ofpact.len > sizeof(*conntrack)
&& !(conntrack->flags & NX_CT_F_COMMIT)) { && !(conntrack->flags & NX_CT_F_COMMIT)) {
VLOG_WARN_RL(&rl, "CT action requires commit flag if actions are " const struct ofpact *a;
"specified."); size_t ofpacts_len = conntrack->ofpact.len - sizeof(*conntrack);
error = OFPERR_OFPBAC_BAD_ARGUMENT;
OFPACT_FOR_EACH (a, conntrack->actions, ofpacts_len) {
if (a->type != OFPACT_NAT || ofpact_get_NAT(a)->flags
|| ofpact_get_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: out:
@@ -4816,6 +4820,9 @@ encode_CT(const struct ofpact_conntrack *conntrack,
nac->len = htons(len); 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 /* Parses 'arg' as the argument to a "ct" action, and appends such an
* action to 'ofpacts'. * action to 'ofpacts'.
* *
@@ -4853,12 +4860,20 @@ parse_CT(char *arg, struct ofpbuf *ofpacts,
} }
} else if (!strcmp(key, "alg")) { } else if (!strcmp(key, "alg")) {
error = str_to_connhelper(value, &oc->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")) { } else if (!strcmp(key, "exec")) {
/* Hide existing actions from ofpacts_parse_copy(), so the /* Hide existing actions from ofpacts_parse_copy(), so the
* nesting can be handled transparently. */ * nesting can be handled transparently. */
enum ofputil_protocol usable_protocols2; enum ofputil_protocol usable_protocols2;
const size_t exec_offset = ofpacts_pull(ofpacts);
ofpbuf_pull(ofpacts, sizeof(*oc));
/* Initializes 'usable_protocol2', fold it back to /* Initializes 'usable_protocol2', fold it back to
* '*usable_protocols' afterwards, so that we do not lose * '*usable_protocols' afterwards, so that we do not lose
* restrictions already in there. */ * restrictions already in there. */
@@ -4866,7 +4881,7 @@ parse_CT(char *arg, struct ofpbuf *ofpacts,
false, OFPACT_CT); false, OFPACT_CT);
*usable_protocols &= usable_protocols2; *usable_protocols &= usable_protocols2;
ofpact_pad(ofpacts); ofpact_pad(ofpacts);
ofpacts->header = ofpbuf_push_uninit(ofpacts, sizeof(*oc)); ofpacts->header = ofpbuf_push_uninit(ofpacts, exec_offset);
oc = ofpacts->header; oc = ofpacts->header;
} else { } else {
error = xasprintf("invalid argument to \"ct\" action: `%s'", key); error = xasprintf("invalid argument to \"ct\" action: `%s'", key);
@@ -4891,6 +4906,8 @@ format_alg(int port, struct ds *s)
} }
} }
static void format_NAT(const struct ofpact_nat *a, struct ds *ds);
static void static void
format_CT(const struct ofpact_conntrack *a, struct ds *s) format_CT(const struct ofpact_conntrack *a, struct ds *s)
{ {
@@ -4908,15 +4925,365 @@ format_CT(const struct ofpact_conntrack *a, struct ds *s)
} else if (a->zone_imm) { } else if (a->zone_imm) {
ds_put_format(s, "zone=%"PRIu16",", 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(ofpact_get_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("); ds_put_cstr(s, "exec(");
ofpacts_format(a->actions, ofpact_ct_get_action_len(a), s); ofpacts_format(action, actions_len, s);
ds_put_format(s, "),"); ds_put_cstr(s, "),");
} }
format_alg(a->alg, s); format_alg(a->alg, s);
ds_chomp(s, ','); ds_chomp(s, ',');
ds_put_char(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) {
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) {
ipv6_format_addr_bracket(&a->range.addr.ipv6.min, ds,
a->range.proto.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, '-');
ipv6_format_addr_bracket(&a->range.addr.ipv6.max, ds,
a->range.proto.min);
}
}
if (a->range.proto.min) {
ds_put_char(ds, ':');
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];
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)
|| 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)
|| 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;
}
}
}
if (on->range_af != AF_UNSPEC && s[n] == ':') {
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. */ /* Meter instruction. */
@@ -5298,6 +5665,7 @@ ofpact_is_set_or_move_action(const struct ofpact *a)
case OFPACT_BUNDLE: case OFPACT_BUNDLE:
case OFPACT_CLEAR_ACTIONS: case OFPACT_CLEAR_ACTIONS:
case OFPACT_CT: case OFPACT_CT:
case OFPACT_NAT:
case OFPACT_CONTROLLER: case OFPACT_CONTROLLER:
case OFPACT_DEC_MPLS_TTL: case OFPACT_DEC_MPLS_TTL:
case OFPACT_DEC_TTL: case OFPACT_DEC_TTL:
@@ -5373,6 +5741,7 @@ ofpact_is_allowed_in_actions_set(const struct ofpact *a)
case OFPACT_BUNDLE: case OFPACT_BUNDLE:
case OFPACT_CONTROLLER: case OFPACT_CONTROLLER:
case OFPACT_CT: case OFPACT_CT:
case OFPACT_NAT:
case OFPACT_ENQUEUE: case OFPACT_ENQUEUE:
case OFPACT_EXIT: case OFPACT_EXIT:
case OFPACT_UNROLL_XLATE: case OFPACT_UNROLL_XLATE:
@@ -5603,6 +5972,7 @@ ovs_instruction_type_from_ofpact_type(enum ofpact_type type)
case OFPACT_SAMPLE: case OFPACT_SAMPLE:
case OFPACT_DEBUG_RECIRC: case OFPACT_DEBUG_RECIRC:
case OFPACT_CT: case OFPACT_CT:
case OFPACT_NAT:
default: default:
return OVSINST_OFPIT11_APPLY_ACTIONS; return OVSINST_OFPIT11_APPLY_ACTIONS;
} }
@@ -6178,6 +6548,18 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
return err; return err;
} }
case OFPACT_NAT: {
struct ofpact_nat *on = ofpact_get_NAT(a);
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: case OFPACT_CLEAR_ACTIONS:
return 0; return 0;
@@ -6322,6 +6704,13 @@ ofpacts_verify_nested(const struct ofpact *a, enum ofpact_type outer_action)
VLOG_WARN("cannot set CT fields outside of ct action"); VLOG_WARN("cannot set CT fields outside of ct action");
return OFPERR_OFPBAC_BAD_SET_ARGUMENT; 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) { if (outer_action) {
ovs_assert(outer_action == OFPACT_WRITE_ACTIONS ovs_assert(outer_action == OFPACT_WRITE_ACTIONS
@@ -6693,6 +7082,7 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port)
case OFPACT_GROUP: case OFPACT_GROUP:
case OFPACT_DEBUG_RECIRC: case OFPACT_DEBUG_RECIRC:
case OFPACT_CT: case OFPACT_CT:
case OFPACT_NAT:
default: default:
return false; return false;
} }
@@ -7359,7 +7749,8 @@ pad_ofpat(struct ofpbuf *openflow, size_t start_ofs)
{ {
struct ofp_action_header *oah; 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 = ofpbuf_at_assert(openflow, start_ofs, sizeof *oah);
oah->len = htons(openflow->size - start_ofs); oah->len = htons(openflow->size - start_ofs);

View File

@@ -107,6 +107,7 @@
OFPACT(SAMPLE, ofpact_sample, ofpact, "sample") \ OFPACT(SAMPLE, ofpact_sample, ofpact, "sample") \
OFPACT(UNROLL_XLATE, ofpact_unroll_xlate, ofpact, "unroll_xlate") \ OFPACT(UNROLL_XLATE, ofpact_unroll_xlate, ofpact, "unroll_xlate") \
OFPACT(CT, ofpact_conntrack, ofpact, "ct") \ OFPACT(CT, ofpact_conntrack, ofpact, "ct") \
OFPACT(NAT, ofpact_nat, ofpact, "nat") \
\ \
/* Debugging actions. \ /* 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, void ofpacts_execute_action_set(struct ofpbuf *action_list,
const struct ofpbuf *action_set); 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. /* OFPACT_RESUBMIT.
* *
* Used for NXAST_RESUBMIT, NXAST_RESUBMIT_TABLE. */ * 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', * Appends a new 'ofpact', of length OFPACT_<ENUM>_RAW_SIZE, to 'ofpacts',
* initializes it with ofpact_init_<ENUM>(), and returns it. Also sets * 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 * After using this function to add a variable-length action, add the
* elements of the flexible array (e.g. with ofpbuf_put()), then use * elements of the flexible array (e.g. with ofpbuf_put()), then use

View File

@@ -447,6 +447,21 @@ ipv6_format_addr(const struct in6_addr *addr, struct ds *s)
s->length += strlen(dst); s->length += strlen(dst);
} }
/* Same as print_ipv6_addr, but optionally encloses the address in square
* brackets. */
void
ipv6_format_addr_bracket(const struct in6_addr *addr, struct ds *s,
bool bracket)
{
if (bracket) {
ds_put_char(s, '[');
}
ipv6_format_addr(addr, s);
if (bracket) {
ds_put_char(s, ']');
}
}
void void
ipv6_format_mapped(const struct in6_addr *addr, struct ds *s) ipv6_format_mapped(const struct in6_addr *addr, struct ds *s)
{ {

View File

@@ -725,10 +725,13 @@ BUILD_ASSERT_DECL(TCP_HEADER_LEN == sizeof(struct tcp_header));
#define CS_REPLY_DIR 0x08 #define CS_REPLY_DIR 0x08
#define CS_INVALID 0x10 #define CS_INVALID 0x10
#define CS_TRACKED 0x20 #define CS_TRACKED 0x20
#define CS_SRC_NAT 0x40
#define CS_DST_NAT 0x80
/* Undefined connection state bits. */ /* Undefined connection state bits. */
#define CS_SUPPORTED_MASK (CS_NEW | CS_ESTABLISHED | CS_RELATED \ #define CS_SUPPORTED_MASK (CS_NEW | CS_ESTABLISHED | CS_RELATED \
| CS_INVALID | CS_REPLY_DIR | CS_TRACKED) | CS_INVALID | CS_REPLY_DIR | CS_TRACKED \
| CS_SRC_NAT | CS_DST_NAT)
#define CS_UNSUPPORTED_MASK (~(uint32_t)CS_SUPPORTED_MASK) #define CS_UNSUPPORTED_MASK (~(uint32_t)CS_SUPPORTED_MASK)
#define ARP_HRD_ETHERNET 1 #define ARP_HRD_ETHERNET 1
@@ -951,6 +954,8 @@ struct vxlanhdr {
#define VXLAN_FLAGS 0x08000000 /* struct vxlanhdr.vx_flags required value. */ #define VXLAN_FLAGS 0x08000000 /* struct vxlanhdr.vx_flags required value. */
void ipv6_format_addr(const struct in6_addr *addr, struct ds *); void ipv6_format_addr(const struct in6_addr *addr, struct ds *);
void ipv6_format_addr_bracket(const struct in6_addr *addr, struct ds *,
bool bracket);
void ipv6_format_mapped(const struct in6_addr *addr, struct ds *); void ipv6_format_mapped(const struct in6_addr *addr, struct ds *);
void ipv6_format_masked(const struct in6_addr *addr, void ipv6_format_masked(const struct in6_addr *addr,
const struct in6_addr *mask, struct ds *); const struct in6_addr *mask, struct ds *);

View File

@@ -305,6 +305,9 @@ struct xlate_ctx {
* state from the datapath should be honored after recirculation. */ * state from the datapath should be honored after recirculation. */
bool conntracked; 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. /* OpenFlow 1.1+ action set.
* *
* 'action_set' accumulates "struct ofpact"s added by OFPACT_WRITE_ACTIONS. * 'action_set' accumulates "struct ofpact"s added by OFPACT_WRITE_ACTIONS.
@@ -4164,6 +4167,7 @@ recirc_unroll_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
case OFPACT_SAMPLE: case OFPACT_SAMPLE:
case OFPACT_DEBUG_RECIRC: case OFPACT_DEBUG_RECIRC:
case OFPACT_CT: case OFPACT_CT:
case OFPACT_NAT:
break; break;
/* These need not be copied for restoration. */ /* These need not be copied for restoration. */
@@ -4235,6 +4239,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 static void
compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc) compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc)
{ {
@@ -4248,6 +4307,7 @@ compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc)
xlate_commit_actions(ctx); xlate_commit_actions(ctx);
/* Process nested actions first, to populate the key. */ /* 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); do_xlate_actions(ofc->actions, ofpact_ct_get_action_len(ofc), ctx);
if (ofc->zone_src.field) { if (ofc->zone_src.field) {
@@ -4264,6 +4324,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_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_label(&ctx->xin->flow, &ctx->base_flow, ctx->odp_actions, ctx->wc);
put_ct_helper(ctx->odp_actions, ofc); 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); nl_msg_end_nested(ctx->odp_actions, ct_offset);
/* Restore the original ct fields in the key. These should only be exposed /* Restore the original ct fields in the key. These should only be exposed
@@ -4656,6 +4718,11 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
compose_conntrack_action(ctx, ofpact_get_CT(a)); compose_conntrack_action(ctx, ofpact_get_CT(a));
break; 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: case OFPACT_DEBUG_RECIRC:
ctx_trigger_recirculation(ctx); ctx_trigger_recirculation(ctx);
a = ofpact_next(a); a = ofpact_next(a);
@@ -4976,6 +5043,8 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
.was_mpls = false, .was_mpls = false,
.conntracked = false, .conntracked = false,
.ct_nat_action = NULL,
.action_set_has_group = false, .action_set_has_group = false,
.action_set = OFPBUF_STUB_INITIALIZER(action_set_stub), .action_set = OFPBUF_STUB_INITIALIZER(action_set_stub),
}; };

View File

@@ -312,6 +312,17 @@ ct(commit,zone=5)
ct(commit,mark=0xa0a0a0a0/0xfefefefe) ct(commit,mark=0xa0a0a0a0/0xfefefefe)
ct(commit,label=0x1234567890abcdef1234567890abcdef/0xf1f2f3f4f5f6f7f8f9f0fafbfcfdfeff) ct(commit,label=0x1234567890abcdef1234567890abcdef/0xf1f2f3f4f5f6f7f8f9f0fafbfcfdfeff)
ct(commit,helper=ftp) 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))
ct(commit,nat(src=fe80::20c:29ff:fe88:a18b,random))
ct(commit,nat(src=fe80::20c:29ff:fe88:1-fe80::20c:29ff:fe88:a18b,random))
ct(commit,nat(src=[[fe80::20c:29ff:fe88:1]]-[[fe80::20c:29ff:fe88:a18b]]:255-4096,random))
ct(commit,helper=ftp,nat(src=10.1.1.240-10.1.1.255))
]) ])
AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0], AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0],
[`cat actions.txt` [`cat actions.txt`

View File

@@ -187,6 +187,53 @@ ffff 0018 00002320 0007 001f 00010004 000000000000f009
# actions=ct(alg=ftp) # actions=ct(alg=ftp)
ffff 0018 00002320 0023 0000 00000000 0000 FF 000000 0015 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 '/^[[#&]]/d' < test-data > input.txt
sed -n 's/^# //p; /^$/p' < test-data > expout sed -n 's/^# //p; /^$/p' < test-data > expout

View File

@@ -160,12 +160,23 @@ sctp actions=drop
sctp actions=drop sctp actions=drop
in_port=0 actions=resubmit:0 in_port=0 actions=resubmit:0
actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678) 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 AT_CHECK([ovs-ofctl parse-flows flows.txt
], [0], [stdout]) ], [0], [stdout])
AT_CHECK([[sed 's/ (xid=0x[0-9a-fA-F]*)//' stdout]], [0], AT_CHECK([[sed 's/ (xid=0x[0-9a-fA-F]*)//' stdout]], [0],
[[usable protocols: any [[usable protocols: OpenFlow10,NXM
chosen protocol: OpenFlow10-table_id chosen protocol: OpenFlow10-table_id
OFPT_FLOW_MOD: ADD tcp,tp_src=123 actions=FLOOD 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 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 sctp actions=drop
OFPT_FLOW_MOD: ADD in_port=0 actions=resubmit:0 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=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 AT_CLEANUP

View File

@@ -1005,7 +1005,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. 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 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 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 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
@@ -1014,7 +1014,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]) AT_CHECK([conntrack -F 2>/dev/null])
dnl Passive FTP requests from p0->p1 should work fine. 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 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 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 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
@@ -1352,6 +1352,7 @@ 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 OVS_TRAFFIC_VSWITCHD_STOP
AT_CLEANUP AT_CLEANUP
AT_SETUP([conntrack - resubmit to ct multiple times]) AT_SETUP([conntrack - resubmit to ct multiple times])
CHECK_CONNTRACK() CHECK_CONNTRACK()
@@ -1390,3 +1391,561 @@ NXST_FLOW reply:
OVS_TRAFFIC_VSWITCHD_STOP OVS_TRAFFIC_VSWITCHD_STOP
AT_CLEANUP AT_CLEANUP
AT_SETUP([conntrack - simple SNAT])
CHECK_CONNTRACK()
OVS_TRAFFIC_VSWITCHD_START()
ADD_NAMESPACES(at_ns0, at_ns1)
ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
NS_CHECK_EXEC([at_ns0], [ip link set dev p0 address 80:88:88:88:88:88])
ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from ns1->ns0.
AT_DATA([flows.txt], [dnl
in_port=1,ip,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 --bundle add-flows br0 flows.txt])
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 - SNAT with port range])
CHECK_CONNTRACK()
OVS_TRAFFIC_VSWITCHD_START()
ADD_NAMESPACES(at_ns0, at_ns1)
ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
NS_CHECK_EXEC([at_ns0], [ip link set dev p0 address 80:88:88:88:88:88])
ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from ns1->ns0.
AT_DATA([flows.txt], [dnl
in_port=1,tcp,action=ct(commit,zone=1,nat(src=10.1.1.240-10.1.1.255:34567-34568,random)),2
in_port=2,ct_state=-trk,tcp,tp_dst=34567,action=ct(table=0,zone=1,nat)
in_port=2,ct_state=-trk,tcp,tp_dst=34568,action=ct(table=0,zone=1,nat)
in_port=2,ct_state=+trk,ct_zone=1,tcp,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 --bundle add-flows br0 flows.txt])
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()
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 --bundle add-flows br0 flows.txt])
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()
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 --bundle add-flows br0 flows.txt])
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()
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 --bundle add-flows br0 flows.txt])
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()
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 --bundle add-flows br0 flows.txt])
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()
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, nw_src=10.1.1.1, action=ct(alg=ftp,commit,nat(src=10.1.1.240)),2
dnl Allow established TCP connections, make sure they are NATted already.
table=1 ct_state=+est, tcp, nw_src=10.1.1.240, 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, make sure they are reverse NATted
table=2 ct_state=+est, tcp, nw_dst=10.1.1.1, action=1
dnl Allow (new) related (data) connections. These need to be commited.
table=2 ct_state=+new+rel, tcp, nw_dst=10.1.1.240, 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 --bundle add-flows br0 flows.txt])
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()
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 --bundle add-flows br0 flows.txt])
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
AT_SETUP([conntrack - IPv6 HTTP with NAT])
CHECK_CONNTRACK()
OVS_TRAFFIC_VSWITCHD_START()
ADD_NAMESPACES(at_ns0, at_ns1)
ADD_VETH(p0, at_ns0, br0, "fc00::1/96")
NS_CHECK_EXEC([at_ns0], [ip link set dev p0 address 80:88:88:88:88:88])
ADD_VETH(p1, at_ns1, br0, "fc00::2/96")
NS_CHECK_EXEC([at_ns1], [ip -6 neigh add fc00::240 lladdr 80:88:88:88:88:88 dev p1])
dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from ns1->ns0.
AT_DATA([flows.txt], [dnl
priority=1,action=drop
priority=10,icmp6,action=normal
priority=100,in_port=1,ip6,action=ct(commit,nat(src=fc00::240)),2
priority=100,in_port=2,ct_state=-trk,ip6,action=ct(nat,table=0)
priority=100,in_port=2,ct_state=+trk+est,ip6,action=1
priority=200,in_port=2,ct_state=+trk+new,icmp6,icmpv6_code=0,icmpv6_type=135,nd_target=fc00::240,action=ct(commit,nat(dst=fc00::1)),1
])
AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
dnl Without this sleep, we get occasional failures due to the following error:
dnl "connect: Cannot assign requested address"
sleep 2;
dnl HTTP requests from ns0->ns1 should work fine.
NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-l7.py http6]], [http0.pid])
NS_CHECK_EXEC([at_ns0], [wget http://[[fc00::2]] -t 3 -T 1 --retry-connrefused -v -o wget0.log])
dnl HTTP requests from ns1->ns0 should fail due to network failure.
dnl Try 3 times, in 1 second intervals.
NETNS_DAEMONIZE([at_ns0], [[$PYTHON $srcdir/test-l7.py http6]], [http1.pid])
NS_CHECK_EXEC([at_ns1], [wget http://[[fc00::1]] -t 3 -T 1 -v -o wget1.log], [4])
OVS_TRAFFIC_VSWITCHD_STOP
AT_CLEANUP
AT_SETUP([conntrack - IPv6 FTP with NAT])
AT_SKIP_IF([test $HAVE_PYFTPDLIB = no])
CHECK_CONNTRACK()
OVS_TRAFFIC_VSWITCHD_START()
ADD_NAMESPACES(at_ns0, at_ns1)
ADD_VETH(p0, at_ns0, br0, "fc00::1/96")
NS_CHECK_EXEC([at_ns0], [ip link set dev p0 address 80:88:88:88:88:88])
ADD_VETH(p1, at_ns1, br0, "fc00::2/96")
dnl Would be nice if NAT could translate neighbor discovery messages, too.
NS_CHECK_EXEC([at_ns1], [ip -6 neigh add fc00::240 lladdr 80:88:88:88:88:88 dev p1])
dnl Allow any traffic from ns0->ns1.
dnl Only allow nd, return traffic from ns1->ns0.
AT_DATA([flows.txt], [dnl
dnl Allow other ICMPv6 both ways (without commit).
table=1 priority=100 in_port=1 icmp6, action=2
table=1 priority=100 in_port=2 icmp6, action=1
dnl track all IPv6 traffic (this includes NAT & help to non-NEW packets.)
table=0 priority=10 ip6, action=ct(nat,table=1)
table=0 priority=0 action=drop
dnl
dnl Table 1
dnl
dnl Allow new TCPv6 FTP control connections.
table=1 in_port=1 ct_state=+new tcp6 ipv6_src=fc00::1 tp_dst=21 action=ct(alg=ftp,commit,nat(src=fc00::240)),2
dnl Allow related TCPv6 connections from port 2 to the NATted address.
table=1 in_port=2 ct_state=+new+rel tcp6 ipv6_dst=fc00::240 action=ct(commit,nat),1
dnl Allow established TCPv6 connections both ways, enforce NATting
table=1 in_port=1 ct_state=+est tcp6 ipv6_src=fc00::240 action=2
table=1 in_port=2 ct_state=+est tcp6 ipv6_dst=fc00::1 action=1
dnl Drop everything else.
table=1 priority=0, action=drop
])
AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
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://[[fc00::2]] -6 --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 -f ipv6 2>&1 | FORMAT_CT(fc00::2) | grep -v "FIN" | grep -v "CLOSE"], [0], [dnl
TIME_WAIT src=fc00::1 dst=fc00::2 sport=<cleared> dport=<cleared> src=fc00::2 dst=fc00::240 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 helper=ftp use=2
TIME_WAIT src=fc00::2 dst=fc00::240 sport=<cleared> dport=<cleared> src=fc00::1 dst=fc00::2 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 use=1
])
OVS_TRAFFIC_VSWITCHD_STOP
AT_CLEANUP