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