@@ -116,7 +116,7 @@ integer
|===================
The IPv4 address type is used for IPv4 addresses. Addresses are specified in
-either dotted decimal, dotted hexadecimal, dotted octal, decimal, hexadecimal,
+either dotted decimal, IPv4-mapped (::ffff:0:0/96), decimal, hexadecimal,
octal notation or as a host name. A host name will be resolved using the
standard system resolver.
@@ -125,6 +125,9 @@ standard system resolver.
# dotted decimal notation
filter output ip daddr 127.0.0.1
+# IPv4-mapped notation
+filter output ip daddr ::ffff:127.0.0.1
+
# host name
filter output ip daddr localhost
----------------------------
@@ -141,6 +141,10 @@ gro_ipv4_max_size and gso_ipv4_max_size), then *ip length* might be 0 for such
jumbo packets. *meta length* allows you to match on the packet length including
the IP header size.
+If needed, *ip saddr* and *ip daddr* expressions are converted to produce
+IPv4-mapped IPv6 addresses (e.g. ::ffff:1.2.3.4). This way, a common set or map
+with key type *ipv6_addr* may be used for both IPv4 and IPv6 packets.
+
ICMP HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~
[verse]
@@ -107,6 +107,7 @@ enum ops {
OP_LTE,
OP_GTE,
OP_NEG,
+ OP_V6MAP,
__OP_MAX
};
#define OP_MAX (__OP_MAX - 1)
@@ -991,6 +991,7 @@ static bool payload_needs_adjustment(const struct expr *expr)
static int expr_evaluate_payload(struct eval_ctx *ctx, struct expr **exprp)
{
+ const struct datatype *dtype = ctx->ectx.dtype;
const struct expr *key = ctx->ectx.key;
struct expr *expr = *exprp;
@@ -1012,6 +1013,14 @@ static int expr_evaluate_payload(struct eval_ctx *ctx, struct expr **exprp)
return err;
}
+ if (expr->dtype == &ipaddr_type && dtype == &ip6addr_type) {
+ *exprp = binop_expr_alloc(&expr->location,
+ OP_V6MAP, expr, NULL);
+ datatype_set(*exprp, &ip6addr_type);
+ (*exprp)->len = ip6addr_type.size;
+ return 0;
+ }
+
expr->payload.evaluated = true;
return 0;
@@ -854,13 +854,17 @@ bool must_print_eq_op(const struct expr *expr)
expr->right->etype == EXPR_VALUE)
return true;
- return expr->left->etype == EXPR_BINOP;
+ return expr->left->etype == EXPR_BINOP &&
+ expr->left->op != OP_V6MAP;
}
static void binop_expr_print(const struct expr *expr, struct output_ctx *octx)
{
binop_arg_print(expr, expr->left, octx);
+ if (expr->op == OP_V6MAP)
+ return;
+
if (expr_op_symbols[expr->op] &&
(expr->op != OP_EQ || must_print_eq_op(expr)))
nft_print(octx, " %s ", expr_op_symbols[expr->op]);
@@ -873,7 +877,8 @@ static void binop_expr_print(const struct expr *expr, struct output_ctx *octx)
static void binop_expr_clone(struct expr *new, const struct expr *expr)
{
new->left = expr_clone(expr->left);
- new->right = expr_clone(expr->right);
+ if (expr->right)
+ new->right = expr_clone(expr->right);
}
static void binop_expr_destroy(struct expr *expr)
@@ -668,6 +668,9 @@ __binop_expr_json(int op, const struct expr *expr, struct output_ctx *octx)
json_t *binop_expr_json(const struct expr *expr, struct output_ctx *octx)
{
+ if (expr->op == OP_V6MAP)
+ return payload_expr_json(expr->left, octx);
+
return nft_json_pack("{s:o}", expr_op_symbols[expr->op],
__binop_expr_json(expr->op, expr, octx));
}
@@ -17,6 +17,7 @@
#include <linux/netfilter/nf_nat.h>
#include <linux/netfilter.h>
#include <net/ethernet.h>
+#include <netinet/ip.h>
#include <netlink.h>
#include <rule.h>
#include <statement.h>
@@ -649,10 +650,31 @@ static void netlink_parse_byteorder(struct netlink_parse_ctx *ctx,
netlink_set_register(ctx, dreg, expr);
}
+static bool payload_is_ip_addr(const struct expr *expr)
+{
+ size_t saddr_offset = offsetof(struct iphdr, saddr) * BITS_PER_BYTE;
+ size_t daddr_offset = offsetof(struct iphdr, daddr) * BITS_PER_BYTE;
+
+ return expr->payload.base == PROTO_BASE_NETWORK_HDR &&
+ expr->len == ipaddr_type.size &&
+ (expr->payload.offset == saddr_offset ||
+ expr->payload.offset == daddr_offset);
+}
+
+static bool immediate_is_v6map_pfx(const struct expr *expr)
+{
+ return expr &&
+ expr->etype == EXPR_VALUE &&
+ expr->len == ip6addr_type.size - ipaddr_type.size &&
+ !mpz_cmp_ui(expr->value, 0xffff);
+}
+
static void netlink_parse_payload_expr(struct netlink_parse_ctx *ctx,
const struct location *loc,
const struct nftnl_expr *nle)
{
+ size_t v6map_reg_off = (sizeof(struct in6_addr) -
+ sizeof(struct in_addr)) / NFT_REG32_SIZE;
enum nft_registers dreg;
uint32_t base, offset, len;
struct expr *expr;
@@ -670,6 +692,15 @@ static void netlink_parse_payload_expr(struct netlink_parse_ctx *ctx,
dreg = netlink_parse_register(nle, NFTNL_EXPR_PAYLOAD_DREG);
+ if (dreg >= v6map_reg_off &&
+ payload_is_ip_addr(expr) &&
+ immediate_is_v6map_pfx(ctx->registers[dreg - v6map_reg_off])) {
+ expr = binop_expr_alloc(loc, OP_V6MAP, expr, NULL);
+ datatype_set(expr, &ip6addr_type);
+ expr->len = ip6addr_type.size;
+ dreg -= v6map_reg_off;
+ }
+
if (ctx->inner)
ctx->inner_reg = dreg;
@@ -2925,6 +2956,8 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp)
return;
}
break;
+ case OP_V6MAP:
+ return;
default:
if (expr->right->len > expr->left->len) {
expr_set_type(expr->right, expr->left->dtype,
@@ -798,6 +798,21 @@ static void netlink_gen_bitwise_bool(struct netlink_linearize_ctx *ctx,
nftnl_rule_add_expr(ctx->nlr, nle);
}
+static void netlink_gen_v6map(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr, enum nft_registers dreg)
+{
+ const uint16_t pfx[6] = { 0, 0, 0, 0, 0, 0xffff };
+ struct nftnl_expr *nle;
+
+ nle = alloc_nft_expr("immediate");
+ netlink_put_register(nle, NFTNL_EXPR_IMM_DREG, dreg);
+ nftnl_expr_set(nle, NFTNL_EXPR_IMM_DATA, pfx, sizeof(pfx));
+ nftnl_rule_add_expr(ctx->nlr, nle);
+
+ dreg += netlink_register_space(sizeof(pfx) * BITS_PER_BYTE);
+ netlink_gen_expr(ctx, expr->left, dreg);
+}
+
static void netlink_gen_binop(struct netlink_linearize_ctx *ctx,
const struct expr *expr,
enum nft_registers dreg)
@@ -807,6 +822,9 @@ static void netlink_gen_binop(struct netlink_linearize_ctx *ctx,
case OP_RSHIFT:
netlink_gen_bitwise_shift(ctx, expr, dreg);
break;
+ case OP_V6MAP:
+ netlink_gen_v6map(ctx, expr, dreg);
+ break;
default:
if (expr_is_constant(expr->right))
netlink_gen_bitwise_mask_xor(ctx, expr, dreg);
@@ -7,6 +7,13 @@
*bridge;test-bridge;input
*netdev;test-netdev;ingress,egress
+!ip6addrset type ipv6_addr;ok
+?ip6addrset feed::c0:ffee, ::ffff:1.2.3.4;ok
+!v4v6set type ipv4_addr . ipv6_addr;ok
+?v4v6set 1.2.3.4 . ::ffff:5.6.7.8;ok
+!v6v4set type ipv6_addr . ipv4_addr;ok
+?v6v4set ::ffff:5.6.7.8 . 1.2.3.4;ok
+
- ip version 2;ok
# bug ip hdrlength
@@ -110,6 +117,15 @@ ip saddr & 0.0.0.255 < 0.0.0.127;ok
ip saddr & 0xffff0000 == 0xffff0000;ok;ip saddr 255.255.0.0/16
+ip saddr ::ffff:1.2.3.4;ok;ip saddr 1.2.3.4
+ip daddr ::ffff:1.2.3.4;ok;ip daddr 1.2.3.4
+ip saddr { ::ffff:1.2.3.4, feed::c0:ff:ee };fail
+ip daddr { ::ffff:1.2.3.4, feed::c0:ff:ee };fail
+ip saddr ::ffff:1.2.3.4 ip daddr 5.6.7.8;ok;ip saddr 1.2.3.4 ip daddr 5.6.7.8
+ip saddr @ip6addrset;ok
+ip saddr . ip daddr @v4v6set;ok
+ip saddr . ip daddr @v6v4set;ok
+
ip version 4 ip hdrlength 5;ok
ip hdrlength 0;ok
ip hdrlength 15;ok
@@ -1350,6 +1350,134 @@
}
]
+# ip saddr ::ffff:1.2.3.4
+[
+ {
+ "match": {
+ "left": {
+ "payload": {
+ "field": "saddr",
+ "protocol": "ip"
+ }
+ },
+ "op": "==",
+ "right": "::ffff:1.2.3.4"
+ }
+ }
+]
+
+# ip daddr ::ffff:1.2.3.4
+[
+ {
+ "match": {
+ "left": {
+ "payload": {
+ "field": "daddr",
+ "protocol": "ip"
+ }
+ },
+ "op": "==",
+ "right": "::ffff:1.2.3.4"
+ }
+ }
+]
+
+# ip saddr ::ffff:1.2.3.4 ip daddr 5.6.7.8
+[
+ {
+ "match": {
+ "left": {
+ "payload": {
+ "field": "saddr",
+ "protocol": "ip"
+ }
+ },
+ "op": "==",
+ "right": "::ffff:1.2.3.4"
+ }
+ },
+ {
+ "match": {
+ "left": {
+ "payload": {
+ "field": "daddr",
+ "protocol": "ip"
+ }
+ },
+ "op": "==",
+ "right": "5.6.7.8"
+ }
+ }
+]
+
+# ip saddr @ip6addrset
+[
+ {
+ "match": {
+ "left": {
+ "payload": {
+ "field": "saddr",
+ "protocol": "ip"
+ }
+ },
+ "op": "==",
+ "right": "@ip6addrset"
+ }
+ }
+]
+
+# ip saddr . ip daddr @v4v6set
+[
+ {
+ "match": {
+ "left": {
+ "concat": [
+ {
+ "payload": {
+ "field": "saddr",
+ "protocol": "ip"
+ }
+ },
+ {
+ "payload": {
+ "field": "daddr",
+ "protocol": "ip"
+ }
+ }
+ ]
+ },
+ "op": "==",
+ "right": "@v4v6set"
+ }
+ }
+]
+
+# ip saddr . ip daddr @v6v4set
+[
+ {
+ "match": {
+ "left": {
+ "concat": [
+ {
+ "payload": {
+ "field": "saddr",
+ "protocol": "ip"
+ }
+ },
+ {
+ "payload": {
+ "field": "daddr",
+ "protocol": "ip"
+ }
+ }
+ ]
+ },
+ "op": "==",
+ "right": "@v6v4set"
+ }
+ }
+]
+
# ip version 4 ip hdrlength 5
[
{
@@ -206,6 +206,66 @@
}
]
+# ip saddr ::ffff:1.2.3.4
+[
+ {
+ "match": {
+ "left": {
+ "payload": {
+ "field": "saddr",
+ "protocol": "ip"
+ }
+ },
+ "op": "==",
+ "right": "1.2.3.4"
+ }
+ }
+]
+
+# ip daddr ::ffff:1.2.3.4
+[
+ {
+ "match": {
+ "left": {
+ "payload": {
+ "field": "daddr",
+ "protocol": "ip"
+ }
+ },
+ "op": "==",
+ "right": "1.2.3.4"
+ }
+ }
+]
+
+# ip saddr ::ffff:1.2.3.4 ip daddr 5.6.7.8
+[
+ {
+ "match": {
+ "left": {
+ "payload": {
+ "field": "saddr",
+ "protocol": "ip"
+ }
+ },
+ "op": "==",
+ "right": "1.2.3.4"
+ }
+ },
+ {
+ "match": {
+ "left": {
+ "payload": {
+ "field": "daddr",
+ "protocol": "ip"
+ }
+ },
+ "op": "==",
+ "right": "5.6.7.8"
+ }
+ }
+]
+
# iif "lo" ip ecn set 1
[
{
@@ -413,6 +413,43 @@ ip test-ip4 input
[ bitwise reg 1 = ( reg 1 & 0x0000ffff ) ^ 0x00000000 ]
[ cmp eq reg 1 0x0000ffff ]
+# ip saddr ::ffff:1.2.3.4
+ip test-ip4 input
+ [ payload load 4b @ network header + 12 => reg 1 ]
+ [ cmp eq reg 1 0x04030201 ]
+
+# ip daddr ::ffff:1.2.3.4
+ip test-ip4 input
+ [ payload load 4b @ network header + 16 => reg 1 ]
+ [ cmp eq reg 1 0x04030201 ]
+
+# ip saddr ::ffff:1.2.3.4 ip daddr 5.6.7.8
+ip test-ip4 input
+ [ payload load 4b @ network header + 12 => reg 1 ]
+ [ cmp eq reg 1 0x04030201 ]
+ [ payload load 4b @ network header + 16 => reg 1 ]
+ [ cmp eq reg 1 0x08070605 ]
+
+# ip saddr @ip6addrset
+ip test-ip4 input
+ [ immediate reg 1 0x00000000 0x00000000 0xffff0000 ]
+ [ payload load 4b @ network header + 12 => reg 11 ]
+ [ lookup reg 1 set ip6addrset ]
+
+# ip saddr . ip daddr @v4v6set
+ip test-ip4 input
+ [ payload load 4b @ network header + 12 => reg 1 ]
+ [ immediate reg 9 0x00000000 0x00000000 0xffff0000 ]
+ [ payload load 4b @ network header + 16 => reg 2 ]
+ [ lookup reg 1 set v4v6set ]
+
+# ip saddr . ip daddr @v6v4set
+ip test-ip4 input
+ [ immediate reg 1 0x00000000 0x00000000 0xffff0000 ]
+ [ payload load 4b @ network header + 12 => reg 11 ]
+ [ payload load 4b @ network header + 16 => reg 2 ]
+ [ lookup reg 1 set v6v4set ]
+
# ip version 4 ip hdrlength 5
ip test-ip4 input
[ payload load 1b @ network header + 0 => reg 1 ]
@@ -549,6 +549,55 @@ bridge test-bridge input
[ bitwise reg 1 = ( reg 1 & 0x0000ffff ) ^ 0x00000000 ]
[ cmp eq reg 1 0x0000ffff ]
+# ip saddr ::ffff:1.2.3.4
+bridge test-bridge input
+ [ meta load protocol => reg 1 ]
+ [ cmp eq reg 1 0x00000008 ]
+ [ payload load 4b @ network header + 12 => reg 1 ]
+ [ cmp eq reg 1 0x04030201 ]
+
+# ip daddr ::ffff:1.2.3.4
+bridge test-bridge input
+ [ meta load protocol => reg 1 ]
+ [ cmp eq reg 1 0x00000008 ]
+ [ payload load 4b @ network header + 16 => reg 1 ]
+ [ cmp eq reg 1 0x04030201 ]
+
+# ip saddr ::ffff:1.2.3.4 ip daddr 5.6.7.8
+bridge test-bridge input
+ [ meta load protocol => reg 1 ]
+ [ cmp eq reg 1 0x00000008 ]
+ [ payload load 4b @ network header + 12 => reg 1 ]
+ [ cmp eq reg 1 0x04030201 ]
+ [ payload load 4b @ network header + 16 => reg 1 ]
+ [ cmp eq reg 1 0x08070605 ]
+
+# ip saddr @ip6addrset
+bridge test-bridge input
+ [ meta load protocol => reg 1 ]
+ [ cmp eq reg 1 0x00000008 ]
+ [ immediate reg 1 0x00000000 0x00000000 0xffff0000 ]
+ [ payload load 4b @ network header + 12 => reg 11 ]
+ [ lookup reg 1 set ip6addrset ]
+
+# ip saddr . ip daddr @v4v6set
+bridge test-bridge input
+ [ meta load protocol => reg 1 ]
+ [ cmp eq reg 1 0x00000008 ]
+ [ payload load 4b @ network header + 12 => reg 1 ]
+ [ immediate reg 9 0x00000000 0x00000000 0xffff0000 ]
+ [ payload load 4b @ network header + 16 => reg 2 ]
+ [ lookup reg 1 set v4v6set ]
+
+# ip saddr . ip daddr @v6v4set
+bridge test-bridge input
+ [ meta load protocol => reg 1 ]
+ [ cmp eq reg 1 0x00000008 ]
+ [ immediate reg 1 0x00000000 0x00000000 0xffff0000 ]
+ [ payload load 4b @ network header + 12 => reg 11 ]
+ [ payload load 4b @ network header + 16 => reg 2 ]
+ [ lookup reg 1 set v6v4set ]
+
# ip version 4 ip hdrlength 5
bridge test-bridge input
[ meta load protocol => reg 1 ]
@@ -549,6 +549,55 @@ inet test-inet input
[ bitwise reg 1 = ( reg 1 & 0x0000ffff ) ^ 0x00000000 ]
[ cmp eq reg 1 0x0000ffff ]
+# ip saddr ::ffff:1.2.3.4
+inet test-inet input
+ [ meta load nfproto => reg 1 ]
+ [ cmp eq reg 1 0x00000002 ]
+ [ payload load 4b @ network header + 12 => reg 1 ]
+ [ cmp eq reg 1 0x04030201 ]
+
+# ip daddr ::ffff:1.2.3.4
+inet test-inet input
+ [ meta load nfproto => reg 1 ]
+ [ cmp eq reg 1 0x00000002 ]
+ [ payload load 4b @ network header + 16 => reg 1 ]
+ [ cmp eq reg 1 0x04030201 ]
+
+# ip saddr ::ffff:1.2.3.4 ip daddr 5.6.7.8
+inet test-inet input
+ [ meta load nfproto => reg 1 ]
+ [ cmp eq reg 1 0x00000002 ]
+ [ payload load 4b @ network header + 12 => reg 1 ]
+ [ cmp eq reg 1 0x04030201 ]
+ [ payload load 4b @ network header + 16 => reg 1 ]
+ [ cmp eq reg 1 0x08070605 ]
+
+# ip saddr @ip6addrset
+inet test-inet input
+ [ meta load nfproto => reg 1 ]
+ [ cmp eq reg 1 0x00000002 ]
+ [ immediate reg 1 0x00000000 0x00000000 0xffff0000 ]
+ [ payload load 4b @ network header + 12 => reg 11 ]
+ [ lookup reg 1 set ip6addrset ]
+
+# ip saddr . ip daddr @v4v6set
+inet test-inet input
+ [ meta load nfproto => reg 1 ]
+ [ cmp eq reg 1 0x00000002 ]
+ [ payload load 4b @ network header + 12 => reg 1 ]
+ [ immediate reg 9 0x00000000 0x00000000 0xffff0000 ]
+ [ payload load 4b @ network header + 16 => reg 2 ]
+ [ lookup reg 1 set v4v6set ]
+
+# ip saddr . ip daddr @v6v4set
+inet test-inet input
+ [ meta load nfproto => reg 1 ]
+ [ cmp eq reg 1 0x00000002 ]
+ [ immediate reg 1 0x00000000 0x00000000 0xffff0000 ]
+ [ payload load 4b @ network header + 12 => reg 11 ]
+ [ payload load 4b @ network header + 16 => reg 2 ]
+ [ lookup reg 1 set v6v4set ]
+
# ip version 4 ip hdrlength 5
inet test-inet input
[ meta load nfproto => reg 1 ]
@@ -448,6 +448,55 @@ netdev test-netdev ingress
[ bitwise reg 1 = ( reg 1 & 0x0000ffff ) ^ 0x00000000 ]
[ cmp eq reg 1 0x0000ffff ]
+# ip saddr ::ffff:1.2.3.4
+netdev test-netdev ingress
+ [ meta load protocol => reg 1 ]
+ [ cmp eq reg 1 0x00000008 ]
+ [ payload load 4b @ network header + 12 => reg 1 ]
+ [ cmp eq reg 1 0x04030201 ]
+
+# ip daddr ::ffff:1.2.3.4
+netdev test-netdev ingress
+ [ meta load protocol => reg 1 ]
+ [ cmp eq reg 1 0x00000008 ]
+ [ payload load 4b @ network header + 16 => reg 1 ]
+ [ cmp eq reg 1 0x04030201 ]
+
+# ip saddr ::ffff:1.2.3.4 ip daddr 5.6.7.8
+netdev test-netdev egress
+ [ meta load protocol => reg 1 ]
+ [ cmp eq reg 1 0x00000008 ]
+ [ payload load 4b @ network header + 12 => reg 1 ]
+ [ cmp eq reg 1 0x04030201 ]
+ [ payload load 4b @ network header + 16 => reg 1 ]
+ [ cmp eq reg 1 0x08070605 ]
+
+# ip saddr @ip6addrset
+netdev test-netdev egress
+ [ meta load protocol => reg 1 ]
+ [ cmp eq reg 1 0x00000008 ]
+ [ immediate reg 1 0x00000000 0x00000000 0xffff0000 ]
+ [ payload load 4b @ network header + 12 => reg 11 ]
+ [ lookup reg 1 set ip6addrset ]
+
+# ip saddr . ip daddr @v4v6set
+netdev test-netdev ingress
+ [ meta load protocol => reg 1 ]
+ [ cmp eq reg 1 0x00000008 ]
+ [ payload load 4b @ network header + 12 => reg 1 ]
+ [ immediate reg 9 0x00000000 0x00000000 0xffff0000 ]
+ [ payload load 4b @ network header + 16 => reg 2 ]
+ [ lookup reg 1 set v4v6set ]
+
+# ip saddr . ip daddr @v6v4set
+netdev test-netdev egress
+ [ meta load protocol => reg 1 ]
+ [ cmp eq reg 1 0x00000008 ]
+ [ immediate reg 1 0x00000000 0x00000000 0xffff0000 ]
+ [ payload load 4b @ network header + 12 => reg 11 ]
+ [ payload load 4b @ network header + 16 => reg 2 ]
+ [ lookup reg 1 set v6v4set ]
+
# ip version 4 ip hdrlength 5
netdev test-netdev ingress
[ meta load protocol => reg 1 ]
In order to reuse IPv6 address-based data structures like sets and maps with IPv4 packets, if a relational's RHS data type is ip6addr_type, turn these payload expressions into binop expressions with new OP_V6MAP internally to have them produce IPv4-mapped IPv6 addresses from the packet-extracted data as described in RFC4291 section 2.5.5.2[1]. Since the adjustment merely consists of injecting an immediate expression, no specific kernel support is required for this feature. Upon deserialization of an ip {s,d}addr expression, the new construct is detected by probing for a previous immediate expression loading the expected value into preceeding registers. Special casing is needed in binop_expr_clone() and expr_postprocess() to avoid expr->right dereference and to maintain payload expression's behaviour regarding OP_EQ printing. While at it, fix documentation of accepted IPv4 address notations: While dotted hexadecimal is not accepted at all, dotted octal works with two-digit numbers only (the scanner expects at most three digits). Therefore drop both from the list. [1] https://www.rfc-editor.org/rfc/rfc4291#section-2.5.5.2 Signed-off-by: Phil Sutter <phil@nwl.cc> --- Changes since v1: - This is v2 of "src: Implement ip {s,d}addr6 expressions" avoiding the introduction of new keywords. Note that adding IPv4 addresses to sets of type ip6addr_type is still rejected although easily enabled by passing AI_V4MAPPED flag to getaddrinfo(): Since this also resolves DNS names, users may unexpectedly add a host's IPv4 address in mapped notation if the name doesn't resolve to an IPv6 address for whatever reason. If we really want to open this can of worms, it deserves a separate patch anyway. --- doc/data-types.txt | 5 +- doc/payload-expression.txt | 4 + include/expression.h | 1 + src/evaluate.c | 9 +++ src/expression.c | 9 ++- src/json.c | 3 + src/netlink_delinearize.c | 33 ++++++++ src/netlink_linearize.c | 18 +++++ tests/py/ip/ip.t | 16 ++++ tests/py/ip/ip.t.json | 128 ++++++++++++++++++++++++++++++++ tests/py/ip/ip.t.json.output | 60 +++++++++++++++ tests/py/ip/ip.t.payload | 37 +++++++++ tests/py/ip/ip.t.payload.bridge | 49 ++++++++++++ tests/py/ip/ip.t.payload.inet | 49 ++++++++++++ tests/py/ip/ip.t.payload.netdev | 49 ++++++++++++ 15 files changed, 467 insertions(+), 3 deletions(-)