@@ -43,6 +43,7 @@ extern struct xt2_chain *xt2_chain_lookup(struct xt2_table *, const char *);
extern void xt2_chain_free(struct xt2_chain *);
extern struct xt2_chain *xt2_chain_move(struct xt2_table *, const char *,
const char *);
+extern struct xt2_chain *xt2_chain_dup(const struct xt2_chain *);
extern struct xt2_table *xt2_table_new(void);
extern void xt2_table_free(struct xt2_table *);
@@ -12,6 +12,7 @@
* %NFXTM_COMMIT: finalize and commit a transaction
* %NFXTM_TABLE_REPLACE:start a table replace transaction
* %NFXTM_ABORT: abort an active transaction
+ * %NFXTM_CHAIN_DUMP: retrieve chain properties and rules in the chain
*/
enum nfxt_msg_type {
NFXTM_IDENTIFY = 1,
@@ -22,6 +23,7 @@ enum nfxt_msg_type {
NFXTM_COMMIT,
NFXTM_TABLE_REPLACE,
NFXTM_ABORT,
+ NFXTM_CHAIN_DUMP,
};
/**
@@ -129,6 +129,14 @@ struct xt2_chain *xt2_chain_move(struct xt2_table *table, const char *old_name,
return new_chain;
}
+struct xt2_chain *xt2_chain_dup(const struct xt2_chain *old)
+{
+ WARN_ON(old == NULL);
+ if (old == NULL)
+ return ERR_PTR(-EINVAL);
+ return xt2_chain_new(NULL, old->name);
+}
+
/**
* Create a new table with no chains and no rules.
*/
@@ -68,6 +68,14 @@ struct xtnetlink_transact {
};
/**
+ * struct netlink_callback->args[x] holds, for different x:
+ * %NLCBA_CHAIN_PTR: a pointer to the currently traversed chain
+ */
+enum {
+ NLCBA_CHAIN_PTR = 1,
+};
+
+/**
* Write-locked: the one user may add/delete entries to/from transact_list
* Read-locked: users only touch transaction entries' content
*/
@@ -342,6 +350,21 @@ xtnetlink_table_wput(struct xtnetlink_transact *xa, struct net *net,
}
/**
+ * Return a pointer to the live table, and only for a reading consumer.
+ * Symmetric call to xtnetlink_table_rput is required.
+ */
+static struct xt2_table *xtnetlink_table_rget(struct net *net)
+{
+ rcu_read_lock();
+ return rcu_dereference(xtables2_pernet(net)->master);
+}
+
+static void xtnetlink_table_rput(void)
+{
+ rcu_read_unlock();
+}
+
+/**
* Ran too often into NULL derefs. Now there is a dummy function for unused
* message type 0.
*/
@@ -590,6 +613,134 @@ xtnetlink_abort(struct sock *xtnl, struct sk_buff *iskb,
return xtnetlink_error(&ref, NFXTE_SUCCESS);
}
+/*
+ * Table dumping is iterating over all chains it contains and simply calling
+ * their dump functions. Since only a single message can be emitted at a time,
+ * we have to remember where we left off. nl_cb->args[NLCBA_*] take care of
+ * that, but more importantly, so does nl_cb->dump, which essentially holds
+ * the state (in a Turing sense).
+ *
+ * digraph {
+ * emit_table -> emit_final; -- no chains at all
+ * emit_table -> emit_chain; -- chains exist
+ * emit_chain -> emit_final; -- no further chains
+ * emit_chain -> emit_chain; -- when this chain is empty and next one exists
+ * emit_chain -> emit_rule; -- when chain has rules
+ * emit_rule -> emit_final; -- no further rules, no further chains
+ * emit_rule -> emit_chain; -- no further rules, more chains
+ * emit_rule -> emit_rule; -- more rules
+ * };
+ */
+
+static int
+xtnetlink_emit_final(struct sk_buff *skb, struct netlink_callback *nl_cb)
+{
+ /* Cause emission of NLMSG_DONE. */
+ return 0;
+}
+
+static int
+xtnetlink_emit_chain(struct sk_buff *skb, struct netlink_callback *nl_cb)
+{
+ struct xtnetlink_pktref oldref =
+ {.c_skb = nl_cb->skb, .c_msg = nl_cb->nlh};
+ struct xt2_chain *chain = (void *)nl_cb->args[NLCBA_CHAIN_PTR];
+ struct nlmsghdr *msg = NULL;
+
+ msg = xtnetlink_fill(skb, &oldref, NLM_F_MULTI);
+ msg->nlmsg_type = MAKE_TAGGED_TYPE(NFXTM_CHAIN_DUMP);
+ if (nla_put_string(skb, NFXTA_NAME, chain->name) != 0)
+ goto nla_put_failure;
+ nlmsg_end(skb, msg);
+
+ nl_cb->dump = xtnetlink_emit_final;
+ return skb->len;
+ nla_put_failure:
+ return 0; /* -ENOBUFS; but caller only checks for != 0 */
+}
+
+static int
+xtnetlink_dump_done(struct netlink_callback *nl_cb)
+{
+ struct xt2_chain *chain = (void *)nl_cb->args[NLCBA_CHAIN_PTR];
+
+ if (chain != NULL)
+ xt2_chain_free(chain);
+ return 0; /* caller does not care */
+}
+
+/**
+ * To get an atomic snapshot of the chain, it needs to be duplicated at the
+ * start of the CHAIN_DUMP operation. This is done here.
+ */
+static int xtnetlink_chain_dump_init(struct sock *xtnl, struct sk_buff *iskb,
+ const struct nlmsghdr *imsg,
+ struct xt2_chain **chain)
+{
+ struct xtnetlink_pktref ref =
+ {.c_skb = iskb, .c_msg = imsg, .sock = xtnl};
+ struct xt2_table *table;
+ struct xt2_chain *old_chain;
+ const struct nlattr *name_attr;
+ const char *name;
+
+ name_attr = nlmsg_find_attr(imsg, sizeof(struct nfgenmsg), NFXTA_NAME);
+ if (name_attr == NULL)
+ return xtnetlink_error(&ref, NFXTE_ATTRSET_INCOMPLETE);
+ name = nla_data(name_attr);
+ if (*name == '\0')
+ return xtnetlink_error(&ref, NFXTE_CHAIN_INVALID_NAME);
+
+ table = xtnetlink_table_rget(sock_net(xtnl));
+ old_chain = xt2_chain_lookup(table, name);
+ if (old_chain == NULL) {
+ xtnetlink_table_rput();
+ return xtnetlink_error(&ref, NFXTE_CHAIN_NOENT);
+ }
+
+ *chain = xt2_chain_dup(old_chain);
+ xtnetlink_table_rput();
+ if (IS_ERR(*chain))
+ return xtnetlink_error(&ref, PTR_ERR(*chain));
+ /*
+ * Because xtnetlink_error returns 0 when it emitted an error packet,
+ * >0 shall be used here to denote success.
+ */
+ return 1;
+}
+
+static int
+xtnetlink_chain_dump_begin(struct sk_buff *skb, struct netlink_callback *nl_cb)
+{
+ nl_cb->args[NLCBA_CHAIN_PTR] = (uintptr_t)nl_cb->data;
+ nl_cb->dump = xtnetlink_emit_chain;
+ return nl_cb->dump(skb, nl_cb);
+}
+
+/**
+ * Handle a CHAIN_DUMP request. This will, in response, emit a CHAIN_DUMP
+ * (with chain properties like NFXTA_NAME etc.), [and later: zero or more
+ * RULE_ENTRY].
+ */
+static int xtnetlink_chain_dump(struct sock *xtnl, struct sk_buff *iskb,
+ const struct nlmsghdr *imsg,
+ const struct nlattr *const *ad)
+{
+ struct netlink_dump_control ctl = {
+ .dump = xtnetlink_chain_dump_begin,
+ .done = xtnetlink_dump_done,
+ .module = THIS_MODULE,
+ };
+ struct xt2_chain *chain = NULL;
+ int ret;
+
+ ret = xtnetlink_chain_dump_init(xtnl, iskb, imsg, &chain);
+ if (ret <= 0)
+ return ret;
+ ctl.data = chain;
+ return netlink_dump_start(xtnl, iskb, imsg, &ctl);
+}
+
static const struct nla_policy xtnetlink_policy[] = {
[NFXTA_NAME] = {.type = NLA_NUL_STRING},
[NFXTA_ERRNO] = {.type = NLA_U32},
@@ -616,6 +767,7 @@ static const struct nfnl_callback xtnetlink_callback[] = {
[NFXTM_COMMIT] = {.call = xtnetlink_commit, pol},
[NFXTM_TABLE_REPLACE] = {.call = xtnetlink_table_replace, pol},
[NFXTM_ABORT] = {.call = xtnetlink_abort, pol},
+ [NFXTM_CHAIN_DUMP] = {.call = xtnetlink_chain_dump, pol},
};
#undef pol
This adds recognition for the NFXTM_CHAIN_DUMP request. A chain snapshot is taken and then used to spool the contents (of which there are not any yet, but still) to userspace in small Netlink bites. Signed-off-by: Jan Engelhardt <jengelh@inai.de> --- include/net/netfilter/xt_core.h | 1 + include/uapi/linux/netfilter/nfnetlink_xtables.h | 2 + net/netfilter/xt_core.c | 8 ++ net/netfilter/xt_nfnetlink.c | 152 ++++++++++++++++++++++ 4 files changed, 163 insertions(+)