@@ -20,6 +20,7 @@ struct nft_ctx;
struct location;
struct output_ctx;
struct list_head;
+struct nftnl_device;
#ifdef HAVE_LIBJANSSON
@@ -118,6 +119,8 @@ void monitor_print_flowtable_json(struct netlink_mon_handler *monh,
const char *cmd, struct flowtable *ft);
void monitor_print_rule_json(struct netlink_mon_handler *monh,
const char *cmd, struct rule *r);
+void monitor_print_device_json(struct netlink_mon_handler *monh,
+ const char *cmd, struct nftnl_device *nld);
int json_events_cb(const struct nlmsghdr *nlh,
struct netlink_mon_handler *monh);
@@ -270,6 +273,13 @@ static inline void monitor_print_rule_json(struct netlink_mon_handler *monh,
/* empty */
}
+static inline void
+monitor_print_device_json(struct netlink_mon_handler *monh,
+ const char *cmd, struct nftnl_device *nld)
+{
+ /* empty */
+}
+
static inline int json_events_cb(const struct nlmsghdr *nlh,
struct netlink_mon_handler *monh)
{
@@ -142,6 +142,8 @@ enum nf_tables_msg_types {
NFT_MSG_DESTROYOBJ,
NFT_MSG_DESTROYFLOWTABLE,
NFT_MSG_GETSETELEM_RESET,
+ NFT_MSG_NEWDEV,
+ NFT_MSG_DELDEV,
NFT_MSG_MAX,
};
@@ -1761,10 +1763,18 @@ enum nft_synproxy_attributes {
* enum nft_device_attributes - nf_tables device netlink attributes
*
* @NFTA_DEVICE_NAME: name of this device (NLA_STRING)
+ * @NFTA_DEVICE_TABLE: table containing the flowtable or chain hooking into the device (NLA_STRING)
+ * @NFTA_DEVICE_FLOWTABLE: flowtable hooking into the device (NLA_STRING)
+ * @NFTA_DEVICE_CHAIN: chain hooking into the device (NLA_STRING)
+ * @NFTA_DEVICE_SPEC: hook spec matching the device (NLA_STRING)
*/
enum nft_devices_attributes {
NFTA_DEVICE_UNSPEC,
NFTA_DEVICE_NAME,
+ NFTA_DEVICE_TABLE,
+ NFTA_DEVICE_FLOWTABLE,
+ NFTA_DEVICE_CHAIN,
+ NFTA_DEVICE_SPEC,
__NFTA_DEVICE_MAX
};
#define NFTA_DEVICE_MAX (__NFTA_DEVICE_MAX - 1)
@@ -17,6 +17,8 @@
#include <rt.h>
#include "nftutils.h"
+#include <libnftnl/device.h>
+
#include <netdb.h>
#include <netinet/icmp6.h>
#include <netinet/in.h>
@@ -2122,6 +2124,31 @@ void monitor_print_rule_json(struct netlink_mon_handler *monh,
monitor_print_json(monh, cmd, rule_print_json(octx, r));
}
+void monitor_print_device_json(struct netlink_mon_handler *monh,
+ const char *cmd, struct nftnl_device *nld)
+{
+ int32_t family = nftnl_device_get_s32(nld, NFTNL_DEVICE_FAMILY);
+ const char *key, *val;
+ json_t *root;
+
+ if (nftnl_device_is_set(nld, NFTNL_DEVICE_CHAIN)) {
+ key = "chain";
+ val = nftnl_device_get_str(nld, NFTNL_DEVICE_CHAIN);
+ } else if (nftnl_device_is_set(nld, NFTNL_DEVICE_FLOWTABLE)) {
+ key = "flowtable";
+ val = nftnl_device_get_str(nld, NFTNL_DEVICE_FLOWTABLE);
+ } else {
+ return;
+ }
+ root = json_pack("{s:{s:s, s:s, s:s, s:s, s:s}}", "device",
+ "family", family2str(family),
+ "table", nftnl_device_get_str(nld, NFTNL_DEVICE_TABLE),
+ key, val,
+ "name", nftnl_device_get_str(nld, NFTNL_DEVICE_NAME),
+ "spec", nftnl_device_get_str(nld, NFTNL_DEVICE_SPEC));
+ monitor_print_json(monh, cmd, root);
+}
+
void json_alloc_echo(struct nft_ctx *nft)
{
nft->json_echo = json_array();
@@ -25,6 +25,7 @@
#include <libnftnl/udata.h>
#include <libnftnl/ruleset.h>
#include <libnftnl/common.h>
+#include <libnftnl/device.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/netfilter.h>
@@ -154,6 +155,7 @@ static uint32_t netlink_msg2nftnl_of(uint32_t type, uint16_t flags)
case NFT_MSG_NEWSETELEM:
case NFT_MSG_NEWOBJ:
case NFT_MSG_NEWFLOWTABLE:
+ case NFT_MSG_NEWDEV:
if (flags & NLM_F_EXCL)
return NFT_OF_EVENT_CREATE;
else
@@ -165,6 +167,7 @@ static uint32_t netlink_msg2nftnl_of(uint32_t type, uint16_t flags)
case NFT_MSG_DELRULE:
case NFT_MSG_DELOBJ:
case NFT_MSG_DELFLOWTABLE:
+ case NFT_MSG_DELDEV:
return NFTNL_OF_EVENT_DEL;
}
@@ -599,6 +602,61 @@ static int netlink_events_flowtable_cb(const struct nlmsghdr *nlh, int type,
return MNL_CB_OK;
}
+static struct nftnl_device *netlink_device_alloc(const struct nlmsghdr *nlh)
+{
+ struct nftnl_device *nld;
+
+ nld = nftnl_device_alloc();
+ if (nld == NULL)
+ memory_allocation_error();
+ if (nftnl_device_nlmsg_parse(nlh, nld) < 0)
+ netlink_abi_error();
+
+ return nld;
+}
+
+static int netlink_events_dev_cb(const struct nlmsghdr *nlh, int type,
+ struct netlink_mon_handler *monh)
+{
+ struct nftnl_device *nld = netlink_device_alloc(nlh);
+ const char *cmd, *obj;
+ uint32_t objattr;
+ int32_t family;
+
+ if (nftnl_device_is_set(nld, NFTNL_DEVICE_CHAIN)) {
+ objattr = NFTNL_DEVICE_CHAIN;
+ obj = "chain";
+ } else if (nftnl_device_is_set(nld, NFTNL_DEVICE_FLOWTABLE)) {
+ objattr = NFTNL_DEVICE_FLOWTABLE;
+ obj = "flowtable";
+ } else {
+ return MNL_CB_ERROR;
+ }
+
+ cmd = netlink_msg2cmd(type, nlh->nlmsg_flags);
+ family = nftnl_device_get_s32(nld, NFTNL_DEVICE_FAMILY);
+
+ switch (monh->format) {
+ case NFTNL_OUTPUT_DEFAULT:
+ nft_mon_print(monh, "%s device %s %s %s %s hook %s { %s }",
+ cmd, obj, family2str(family),
+ nftnl_device_get_str(nld, NFTNL_DEVICE_TABLE),
+ nftnl_device_get_str(nld, objattr),
+ nftnl_device_get_str(nld, NFTNL_DEVICE_SPEC),
+ nftnl_device_get_str(nld, NFTNL_DEVICE_NAME));
+ nft_mon_print(monh, "\n");
+ break;
+ case NFTNL_OUTPUT_JSON:
+ monitor_print_device_json(monh, cmd, nld);
+ if (!nft_output_echo(&monh->ctx->nft->output))
+ nft_mon_print(monh, "\n");
+ break;
+ }
+
+ nftnl_device_free(nld);
+ return MNL_CB_OK;
+}
+
static void rule_map_decompose_cb(struct set *s, void *data)
{
if (!set_is_anonymous(s->flags))
@@ -921,6 +979,8 @@ static const char *const nftnl_msg_types[NFT_MSG_MAX] = {
[NFT_MSG_NEWGEN] = "NFT_MSG_NEWGEN",
[NFT_MSG_NEWOBJ] = "NFT_MSG_NEWOBJ",
[NFT_MSG_DELOBJ] = "NFT_MSG_DELOBJ",
+ [NFT_MSG_NEWDEV] = "NFT_MSG_NEWDEV",
+ [NFT_MSG_DELDEV] = "NFT_MSG_DELDEV",
};
static const char *nftnl_msgtype2str(uint16_t type)
@@ -1026,6 +1086,10 @@ static int netlink_events_cb(const struct nlmsghdr *nlh, void *data)
case NFT_MSG_NEWGEN:
ret = netlink_events_newgen_cb(nlh, type, monh);
break;
+ case NFT_MSG_NEWDEV:
+ case NFT_MSG_DELDEV:
+ ret = netlink_events_dev_cb(nlh, type, monh);
+ break;
}
return ret;
new file mode 100644
@@ -0,0 +1,66 @@
+# setup first
+I add table netdev t
+I add chain netdev t c { type filter hook ingress devices = { lo } priority 0; policy accept; }
+O -
+J {"add": {"table": {"family": "netdev", "name": "t", "handle": 0}}}
+J {"add": {"chain": {"family": "netdev", "table": "t", "name": "c", "handle": 0, "dev": "lo", "type": "filter", "hook": "ingress", "prio": 0, "policy": "accept"}}}
+
+I delete chain netdev t c
+O delete chain netdev t c { type filter hook ingress devices = { lo } priority 0; policy accept; }
+J {"delete": {"chain": {"family": "netdev", "table": "t", "name": "c", "handle": 0, "dev": "lo", "type": "filter", "hook": "ingress", "prio": 0, "policy": "accept"}}}
+
+I add chain netdev t c { type filter hook ingress devices = { eth1337, lo } priority 0; policy accept; }
+O -
+J {"add": {"chain": {"family": "netdev", "table": "t", "name": "c", "handle": 0, "dev": ["eth1337", "lo"], "type": "filter", "hook": "ingress", "prio": 0, "policy": "accept"}}}
+
+@ ip link add eth1337 type dummy
+O add device chain netdev t c hook eth1337 { eth1337 }
+J {"add": {"device": {"family": "netdev", "table": "t", "chain": "c", "name": "eth1337", "spec": "eth1337"}}}
+
+@ ip link del eth1337
+O delete device chain netdev t c hook eth1337 { eth1337 }
+J {"delete": {"device": {"family": "netdev", "table": "t", "chain": "c", "name": "eth1337", "spec": "eth1337"}}}
+
+I delete chain netdev t c
+O delete chain netdev t c { type filter hook ingress devices = { eth1337, lo } priority 0; policy accept; }
+J {"delete": {"chain": {"family": "netdev", "table": "t", "name": "c", "handle": 0, "dev": ["eth1337", "lo"], "type": "filter", "hook": "ingress", "prio": 0, "policy": "accept"}}}
+
+I add chain netdev t c { type filter hook ingress devices = { wild* } priority 0; }
+@ ip link add wild23 type dummy
+@ ip link add wild42 type dummy
+@ ip link del wild23
+I delete chain netdev t c
+O add chain netdev t c { type filter hook ingress devices = { wild* } priority 0; policy accept; }
+O add device chain netdev t c hook wild* { wild23 }
+O add device chain netdev t c hook wild* { wild42 }
+O delete device chain netdev t c hook wild* { wild23 }
+O delete chain netdev t c { type filter hook ingress devices = { wild* } priority 0; policy accept; }
+J {"add": {"chain": {"family": "netdev", "table": "t", "name": "c", "handle": 0, "dev": "wild*", "type": "filter", "hook": "ingress", "prio": 0, "policy": "accept"}}}
+J {"add": {"device": {"family": "netdev", "table": "t", "chain": "c", "name": "wild23", "spec": "wild*"}}}
+J {"add": {"device": {"family": "netdev", "table": "t", "chain": "c", "name": "wild42", "spec": "wild*"}}}
+J {"delete": {"device": {"family": "netdev", "table": "t", "chain": "c", "name": "wild23", "spec": "wild*"}}}
+J {"delete": {"chain": {"family": "netdev", "table": "t", "name": "c", "handle": 0, "dev": "wild*", "type": "filter", "hook": "ingress", "prio": 0, "policy": "accept"}}}
+
+I add chain netdev t c { type filter hook ingress devices = { wild* } priority 0; }
+I add chain netdev t c2 { type filter hook ingress devices = { wald* } priority 0; }
+@ ip link add wild23 type dummy
+@ ip link set wild42 name wald42
+@ ip link del wild23
+I delete chain netdev t c
+I delete chain netdev t c2
+O add chain netdev t c { type filter hook ingress devices = { wild* } priority 0; policy accept; }
+O add chain netdev t c2 { type filter hook ingress devices = { wald* } priority 0; policy accept; }
+O add device chain netdev t c hook wild* { wild23 }
+O add device chain netdev t c2 hook wald* { wald42 }
+O delete device chain netdev t c hook wild* { wald42 }
+O delete device chain netdev t c hook wild* { wild23 }
+O delete chain netdev t c { type filter hook ingress devices = { wild* } priority 0; policy accept; }
+O delete chain netdev t c2 { type filter hook ingress devices = { wald* } priority 0; policy accept; }
+J {"add": {"chain": {"family": "netdev", "table": "t", "name": "c", "handle": 0, "dev": "wild*", "type": "filter", "hook": "ingress", "prio": 0, "policy": "accept"}}}
+J {"add": {"chain": {"family": "netdev", "table": "t", "name": "c2", "handle": 0, "dev": "wald*", "type": "filter", "hook": "ingress", "prio": 0, "policy": "accept"}}}
+J {"add": {"device": {"family": "netdev", "table": "t", "chain": "c", "name": "wild23", "spec": "wild*"}}}
+J {"add": {"device": {"family": "netdev", "table": "t", "chain": "c2", "name": "wald42", "spec": "wald*"}}}
+J {"delete": {"device": {"family": "netdev", "table": "t", "chain": "c", "name": "wald42", "spec": "wild*"}}}
+J {"delete": {"device": {"family": "netdev", "table": "t", "chain": "c", "name": "wild23", "spec": "wild*"}}}
+J {"delete": {"chain": {"family": "netdev", "table": "t", "name": "c", "handle": 0, "dev": "wild*", "type": "filter", "hook": "ingress", "prio": 0, "policy": "accept"}}}
+J {"delete": {"chain": {"family": "netdev", "table": "t", "name": "c2", "handle": 0, "dev": "wald*", "type": "filter", "hook": "ingress", "prio": 0, "policy": "accept"}}}
@@ -8,3 +8,59 @@ J {"add": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0
I delete flowtable ip t ft
O -
J {"delete": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": "lo"}}}
+
+I add flowtable ip t ft { hook ingress priority 0; devices = { eth1337, lo }; }
+O -
+J {"add": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": ["eth1337", "lo"]}}}
+
+@ ip link add eth1337 type dummy
+O add device flowtable ip t ft hook eth1337 { eth1337 }
+J {"add": {"device": {"family": "ip", "table": "t", "flowtable": "ft", "name": "eth1337", "spec": "eth1337"}}}
+
+@ ip link del eth1337
+O delete device flowtable ip t ft hook eth1337 { eth1337 }
+J {"delete": {"device": {"family": "ip", "table": "t", "flowtable": "ft", "name": "eth1337", "spec": "eth1337"}}}
+
+I delete flowtable ip t ft
+O -
+J {"delete": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": ["eth1337", "lo"]}}}
+
+I add flowtable ip t ft { hook ingress priority 0; devices = { wild* }; }
+@ ip link add wild23 type dummy
+@ ip link add wild42 type dummy
+@ ip link del wild23
+I delete flowtable ip t ft
+O add flowtable ip t ft { hook ingress priority 0; devices = { wild* }; }
+O add device flowtable ip t ft hook wild* { wild23 }
+O add device flowtable ip t ft hook wild* { wild42 }
+O delete device flowtable ip t ft hook wild* { wild23 }
+O delete flowtable ip t ft
+J {"add": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": "wild*"}}}
+J {"add": {"device": {"family": "ip", "table": "t", "flowtable": "ft", "name": "wild23", "spec": "wild*"}}}
+J {"add": {"device": {"family": "ip", "table": "t", "flowtable": "ft", "name": "wild42", "spec": "wild*"}}}
+J {"delete": {"device": {"family": "ip", "table": "t", "flowtable": "ft", "name": "wild23", "spec": "wild*"}}}
+J {"delete": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": "wild*"}}}
+
+I add flowtable ip t ft { hook ingress priority 0; devices = { wild* }; }
+I add flowtable ip t ft2 { hook ingress priority 0; devices = { wald* }; }
+@ ip link add wild23 type dummy
+@ ip link set wild42 name wald42
+@ ip link del wild23
+I delete flowtable ip t ft
+I delete flowtable ip t ft2
+O add flowtable ip t ft { hook ingress priority 0; devices = { wild* }; }
+O add flowtable ip t ft2 { hook ingress priority 0; devices = { wald* }; }
+O add device flowtable ip t ft hook wild* { wild23 }
+O add device flowtable ip t ft2 hook wald* { wald42 }
+O delete device flowtable ip t ft hook wild* { wald42 }
+O delete device flowtable ip t ft hook wild* { wild23 }
+O delete flowtable ip t ft
+O delete flowtable ip t ft2
+J {"add": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": "wild*"}}}
+J {"add": {"flowtable": {"family": "ip", "name": "ft2", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": "wald*"}}}
+J {"add": {"device": {"family": "ip", "table": "t", "flowtable": "ft", "name": "wild23", "spec": "wild*"}}}
+J {"add": {"device": {"family": "ip", "table": "t", "flowtable": "ft2", "name": "wald42", "spec": "wald*"}}}
+J {"delete": {"device": {"family": "ip", "table": "t", "flowtable": "ft", "name": "wald42", "spec": "wild*"}}}
+J {"delete": {"device": {"family": "ip", "table": "t", "flowtable": "ft", "name": "wild23", "spec": "wild*"}}}
+J {"delete": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": "wild*"}}}
+J {"delete": {"flowtable": {"family": "ip", "name": "ft2", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": "wald*"}}}
Kernels with name-based netdev hooks emit these messages when a device is added to or removed from an existing flowtable or netdev-family base-chain. This patch depends on respective support code in libnftnl. Signed-off-by: Phil Sutter <phil@nwl.cc> --- include/json.h | 10 ++++ include/linux/netfilter/nf_tables.h | 10 ++++ src/json.c | 27 +++++++++ src/monitor.c | 64 +++++++++++++++++++++ tests/monitor/testcases/chain-netdev.t | 66 ++++++++++++++++++++++ tests/monitor/testcases/flowtable-simple.t | 56 ++++++++++++++++++ 6 files changed, 233 insertions(+) create mode 100644 tests/monitor/testcases/chain-netdev.t