2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-31 06:15:47 +00:00

conntrack: Add support for NAT.

Extend OVS conntrack interface to cover NAT.  New nested NAT action
may be included with a CT action.  A bare NAT action only mangles
existing connections.  If a NAT action with src or dst range attribute
is included, new (non-committed) connections are mangled according to
the NAT attributes.

This work extends on a branch by Thomas Graf at
https://github.com/tgraf/ovs/tree/nat.

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

View File

@@ -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);
}