From 9ac0aadab9f99c5f9cbe8b30cc095ce9be4be4e9 Mon Sep 17 00:00:00 2001 From: Jarno Rajahalme Date: Tue, 24 Nov 2015 15:47:56 -0800 Subject: [PATCH] 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 Acked-by: Ben Pfaff --- .../linux/compat/include/linux/openvswitch.h | 44 ++ lib/flow.c | 4 + lib/odp-util.c | 366 +++++++++++- lib/ofp-actions.c | 429 ++++++++++++- lib/ofp-actions.h | 39 +- lib/packets.c | 15 + lib/packets.h | 7 +- ofproto/ofproto-dpif-xlate.c | 69 +++ tests/odp.at | 11 + tests/ofp-actions.at | 47 ++ tests/ovs-ofctl.at | 24 +- tests/system-traffic.at | 563 +++++++++++++++++- 12 files changed, 1579 insertions(+), 39 deletions(-) diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h index dae2e5b2c..f8a56211a 100644 --- a/datapath/linux/compat/include/linux/openvswitch.h +++ b/datapath/linux/compat/include/linux/openvswitch.h @@ -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_INVALID 0x10 /* Could not track connection. */ #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. @@ -686,11 +692,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. * diff --git a/lib/flow.c b/lib/flow.c index d5dcb92be..2b0c6d2e3 100644 --- a/lib/flow.c +++ b/lib/flow.c @@ -872,6 +872,10 @@ const char *ct_state_to_string(uint32_t state) return "rel"; case CS_INVALID: return "inv"; + case CS_SRC_NAT: + return "snat"; + case CS_DST_NAT: + return "dnat"; default: return NULL; } diff --git a/lib/odp-util.c b/lib/odp-util.c index b7c58d32c..8733bc3ff 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; + 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[] = { [OVS_CT_ATTR_COMMIT] = { .type = NL_A_FLAG, .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 }, [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 }, }; static void @@ -562,6 +707,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 +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; 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 +742,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 +1026,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 +1044,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 +1058,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 +1067,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 +1118,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 +1180,176 @@ 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; + } + } + } 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 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 mask; } ct_label; + struct ct_nat_params nat_params; + bool have_nat = false; size_t start; char *end; @@ -1056,13 +1378,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 +1429,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 +1463,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 43d4b721e..2a71e7ac7 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. ## */ /* ## ------------------ ## */ @@ -4320,21 +4324,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 @@ -4777,9 +4772,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 || 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: @@ -4816,6 +4820,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'. * @@ -4853,12 +4860,20 @@ 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. */ 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 * '*usable_protocols' afterwards, so that we do not lose * restrictions already in there. */ @@ -4866,7 +4881,7 @@ parse_CT(char *arg, struct ofpbuf *ofpacts, false, OFPACT_CT); *usable_protocols &= usable_protocols2; 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); @@ -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 format_CT(const struct ofpact_conntrack *a, struct ds *s) { @@ -4908,16 +4925,366 @@ 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(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("); - 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) { + 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. */ static void @@ -5298,6 +5665,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: @@ -5373,6 +5741,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: @@ -5603,6 +5972,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; } @@ -6178,6 +6548,18 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a, 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: 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"); 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 @@ -6693,6 +7082,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; } @@ -7359,7 +7749,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 773b61704..d18076782 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__RAW_SIZE, to 'ofpacts', * initializes it with ofpact_init_(), 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/packets.c b/lib/packets.c index f6fd4803d..0f72891a5 100644 --- a/lib/packets.c +++ b/lib/packets.c @@ -447,6 +447,21 @@ ipv6_format_addr(const struct in6_addr *addr, struct ds *s) 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 ipv6_format_mapped(const struct in6_addr *addr, struct ds *s) { diff --git a/lib/packets.h b/lib/packets.h index 5b12df705..2ad268afc 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -725,10 +725,13 @@ BUILD_ASSERT_DECL(TCP_HEADER_LEN == sizeof(struct tcp_header)); #define CS_REPLY_DIR 0x08 #define CS_INVALID 0x10 #define CS_TRACKED 0x20 +#define CS_SRC_NAT 0x40 +#define CS_DST_NAT 0x80 /* Undefined connection state bits. */ #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 ARP_HRD_ETHERNET 1 @@ -951,6 +954,8 @@ struct vxlanhdr { #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_bracket(const struct in6_addr *addr, struct ds *, + bool bracket); void ipv6_format_mapped(const struct in6_addr *addr, struct ds *); void ipv6_format_masked(const struct in6_addr *addr, const struct in6_addr *mask, struct ds *); diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index 153b5804c..ab4d52fb3 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. @@ -4164,6 +4167,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. */ @@ -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 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); /* 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) { @@ -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_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 @@ -4656,6 +4718,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); @@ -4976,6 +5043,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 eaba0596b..3d699e12b 100644 --- a/tests/odp.at +++ b/tests/odp.at @@ -312,6 +312,17 @@ 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)) +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], [`cat actions.txt` diff --git a/tests/ofp-actions.at b/tests/ofp-actions.at index 01e5b67fb..f3e5277e3 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 247e3da09..6d5d4c99b 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 8df26511c..74d9b6fcf 100644 --- a/tests/system-traffic.at +++ b/tests/system-traffic.at @@ -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. -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= dport= src=10.1.1.2 dst=10.1.1.1 sport= dport= [[ASSURED]] mark=0 helper=ftp use=2 TIME_WAIT src=10.1.1.2 dst=10.1.1.1 sport= dport= src=10.1.1.1 dst=10.1.1.2 sport= dport= [[ASSURED]] mark=0 use=1 @@ -1014,7 +1014,7 @@ TIME_WAIT src=10.1.1.2 dst=10.1.1.1 sport= dport= 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= dport= src=10.1.1.2 dst=10.1.1.1 sport= dport= [[ASSURED]] mark=0 helper=ftp use=2 TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport= dport= src=10.1.1.2 dst=10.1.1.1 sport= dport= [[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 AT_CLEANUP + AT_SETUP([conntrack - resubmit to ct multiple times]) CHECK_CONNTRACK() @@ -1390,3 +1391,561 @@ NXST_FLOW reply: OVS_TRAFFIC_VSWITCHD_STOP 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= dport= src=10.1.1.2 dst=10.1.1.2XX sport= dport= [[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= dport= src=10.1.1.2 dst=10.1.1.2XX sport= dport= [[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= dport= src=10.1.1.2 dst=10.1.1.2XX sport= dport= [[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= dport= src=10.1.1.2 dst=10.1.1.1 sport= dport= [[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= dport= src=10.1.1.2 dst=10.1.1.1 sport= dport= [[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= dport= src=10.1.1.2 dst=10.1.1.1 sport= dport= [[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= dport= src=10.1.1.2 dst=10.1.1.1 sport= dport= [[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= dport= [[UNREPLIED]] src=10.1.1.2 dst=10.1.1.2XX sport= dport= 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= dport= src=10.1.1.2 dst=10.1.1.240 sport= dport= [[ASSURED]] mark=0 helper=ftp use=2 +TIME_WAIT src=10.1.1.2 dst=10.1.1.240 sport= dport= src=10.1.1.1 dst=10.1.1.2 sport= dport= [[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= dport= src=10.1.1.2 dst=10.1.1.240 sport= dport= [[ASSURED]] mark=0 helper=ftp use=2 +TIME_WAIT src=10.1.1.2 dst=10.1.1.240 sport= dport= src=10.1.1.1 dst=10.1.1.2 sport= dport= [[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= dport= src=fc00::2 dst=fc00::240 sport= dport= [[ASSURED]] mark=0 helper=ftp use=2 +TIME_WAIT src=fc00::2 dst=fc00::240 sport= dport= src=fc00::1 dst=fc00::2 sport= dport= [[ASSURED]] mark=0 use=1 +]) + +OVS_TRAFFIC_VSWITCHD_STOP +AT_CLEANUP -- 2.20.1