+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, ')');
+ }
+}
+