2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-31 14:25:26 +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

@@ -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,15 +4925,365 @@ 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. */
@@ -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);