[PATCH 3/7] netfilter: xtables2: chain creation and deletion

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Notes: xt2_core will have two independent users: xt2_nfnetlink and the
xt1 translator, and they shall be separate modules, thereby
necessiting the use of EXPORT_SYMBOLs.

Signed-off-by: Jan Engelhardt <jengelh@xxxxxxxxxx>
---
 include/linux/netfilter/nfnetlink_xtables.h |   18 ++++
 include/net/netfilter/x_tables2.h           |   31 ++++++-
 net/netfilter/xt2_core.c                    |   81 +++++++++++++++-
 net/netfilter/xt2_nfnetlink.c               |  146 ++++++++++++++++++++++++++-
 4 files changed, 273 insertions(+), 3 deletions(-)

diff --git a/include/linux/netfilter/nfnetlink_xtables.h b/include/linux/netfilter/nfnetlink_xtables.h
index 4c53042..fe1b6ce 100644
--- a/include/linux/netfilter/nfnetlink_xtables.h
+++ b/include/linux/netfilter/nfnetlink_xtables.h
@@ -3,18 +3,36 @@
 
 enum nfxt_msg_type {
 	NFXTM_IDENTIFY = 1,
+	NFXTM_CHAIN_NEW,
+	NFXTM_CHAIN_DEL,
 };
 
 /**
  * %NFXTA_NAME:			name of the object being operated on
+ * %NFXTA_ERRNO:		system error code (%Exxx)
+ * %NFXTA_XTERRNO:		NFXT-specific error code (cf. enum nfxt_errno)
  */
 enum nfxt_attr_type {
 	NFXTA_UNSPEC = 0,
 	NFXTA_NAME,
+	NFXTA_ERRNO,
+	NFXTA_XTERRNO,
 };
 
+/**
+ * %NFXTE_ATTRSET_INCOMPLETE:	Not all required attributes are present in nlmsg
+ * %NFXTE_CHAIN_INVALID_NAME:	Chain name is not acceptable
+ * %NFXTE_CHAIN_EXIST:		Chain already exists
+ * %NFXTE_CHAIN_NOENT:		Chain does not exist
+ * %NFXTE_CHAIN_NAMETOOLONG:	New chain name is too long
+ */
 enum nfxt_errno {
 	NFXTE_SUCCESS = 0,
+	NFXTE_ATTRSET_INCOMPLETE,
+	NFXTE_CHAIN_INVALID_NAME,
+	NFXTE_CHAIN_EXISTS,
+	NFXTE_CHAIN_NOENT,
+	NFXTE_CHAIN_NAMETOOLONG,
 };
 
 #endif /* _LINUX_NFNETLINK_XTABLES_H */
diff --git a/include/net/netfilter/x_tables2.h b/include/net/netfilter/x_tables2.h
index a219952..b13eab7 100644
--- a/include/net/netfilter/x_tables2.h
+++ b/include/net/netfilter/x_tables2.h
@@ -1,17 +1,46 @@
 #ifndef _NET_NETFILTER_XTABLES2_H
 #define _NET_NETFILTER_XTABLES2_H 1
 
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/rcupdate.h>
+
 #define XTABLES2_VTAG "Xtables2 A8"
 
 /**
  * @master:	the master table
+ * @master_lock:	protecting changes to @master
  */
 struct xt2_pernet_data {
 	struct xt2_table __rcu *master;
+	struct mutex master_lock;
 };
 
+/**
+ * @chain_list:		list of chains (struct xt2_chain)
+ * @lock:		protecting changes to @chain_list
+ */
 struct xt2_table {
-	int _dummy;
+	struct list_head chain_list;
+	struct mutex lock;
 };
 
+/**
+ * @anchor:	list anchor for parent (struct xt2_table.chain_list)
+ * @name:	name of chain; its large size is for the xt1 translator
+ * @rcu:	rcu head for delayed deletion
+ */
+struct xt2_chain {
+	struct list_head anchor;
+	char name[48];
+	struct rcu_head rcu;
+};
+
+struct net;
+
+extern struct xt2_pernet_data *xtables2_pernet(struct net *);
+extern struct xt2_chain *xt2_chain_new(struct xt2_table *, const char *);
+extern struct xt2_chain *xt2_chain_lookup(struct xt2_table *, const char *);
+extern void xt2_chain_free(struct xt2_chain *);
+
 #endif /* _NET_NETFILTER_XTABLES2_H */
diff --git a/net/netfilter/xt2_core.c b/net/netfilter/xt2_core.c
index ab73c4d..5e7426d 100644
--- a/net/netfilter/xt2_core.c
+++ b/net/netfilter/xt2_core.c
@@ -9,9 +9,12 @@
  */
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 #include <linux/err.h>
+#include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/rculist.h>
 #include <linux/slab.h>
+#include <linux/string.h>
 #include <net/net_namespace.h>
 #include <net/netns/generic.h>
 #include <net/netfilter/x_tables2.h>
@@ -22,10 +25,79 @@ MODULE_LICENSE("GPL");
 
 static int xtables2_net_id __read_mostly;
 
-static inline struct xt2_pernet_data *xtables2_pernet(struct net *net)
+struct xt2_pernet_data *xtables2_pernet(struct net *net)
 {
 	return net_generic(net, xtables2_net_id);
 }
+EXPORT_SYMBOL_GPL(xtables2_pernet);
+
+/**
+ * @table:	table to add the new chain to
+ * @name:	name for the chain; may be %NULL
+ *
+ * Creates a new chain for @table using the given @name. @name may be NULL or
+ * the empty string, in which case an anonymous chain is created.
+ *
+ * Caller should hold @table->lock and verify chain uniqueness.
+ */
+struct xt2_chain *xt2_chain_new(struct xt2_table *table, const char *name)
+{
+	struct xt2_chain *chain;
+
+	if (strlen(name) >= ARRAY_SIZE(chain->name))
+		return ERR_PTR(-ENAMETOOLONG);
+	chain = kmalloc(sizeof(*chain), GFP_KERNEL);
+	if (chain == NULL)
+		return ERR_PTR(-ENOMEM);
+	INIT_LIST_HEAD(&chain->anchor);
+	if (name != NULL)
+		strncpy(chain->name, name, sizeof(chain->name));
+	else
+		*chain->name = '\0';
+	chain->name[sizeof(chain->name)-1] = '\0';
+	list_add_tail_rcu(&chain->anchor, &table->chain_list);
+	return chain;
+}
+EXPORT_SYMBOL_GPL(xt2_chain_new);
+
+/**
+ * @table:	table to search chain in
+ * @name:	name of desired chain
+ *
+ * Looks for a chain by its name in the given table.
+ * Caller should hold RCU if the chain is supposed to not go away.
+ * (= Caller can ignore RCU if it just wants an existence test.)
+ */
+struct xt2_chain *xt2_chain_lookup(struct xt2_table *table, const char *name)
+{
+	/* Future patch: Use better-suited data structure. */
+	struct xt2_chain *chain;
+
+	list_for_each_entry_rcu(chain, &table->chain_list, anchor)
+		if (strcmp(chain->name, name) == 0)
+			return chain;
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(xt2_chain_lookup);
+
+/**
+ * Frees the chain and all its associated memory.
+ */
+static void xt2_chain_free_rcu(struct rcu_head *rcu)
+{
+	kfree(container_of(rcu, struct xt2_chain, rcu));
+}
+
+void xt2_chain_free(struct xt2_chain *chain)
+{
+	/*
+	 * Not using kfree_rcu here, as we may want to free more,
+	 * in xt2_chain_free_rcu, soon.
+	 */
+	list_del_rcu(&chain->anchor);
+	call_rcu(&chain->rcu, xt2_chain_free_rcu);
+}
+EXPORT_SYMBOL_GPL(xt2_chain_free);
 
 /**
  * Create a new table with no chains and no rules.
@@ -38,11 +110,17 @@ static struct xt2_table *xt2_table_new(void)
 	if (table == NULL)
 		return NULL;
 
+	mutex_init(&table->lock);
+	INIT_LIST_HEAD(&table->chain_list);
 	return table;
 }
 
 static void xt2_table_free(struct xt2_table *table)
 {
+	struct xt2_chain *chain, *next;
+
+	list_for_each_entry_safe(chain, next, &table->chain_list, anchor)
+		xt2_chain_free(chain);
 	kfree(table);
 }
 
@@ -50,6 +128,7 @@ static int __net_init xtables2_net_init(struct net *net)
 {
 	struct xt2_pernet_data *pnet = xtables2_pernet(net);
 
+	mutex_init(&pnet->master_lock);
 	pnet->master = xt2_table_new();
 	if (IS_ERR(pnet->master))
 		return PTR_ERR(pnet->master);
diff --git a/net/netfilter/xt2_nfnetlink.c b/net/netfilter/xt2_nfnetlink.c
index 3dc241f..b50e468d 100644
--- a/net/netfilter/xt2_nfnetlink.c
+++ b/net/netfilter/xt2_nfnetlink.c
@@ -17,6 +17,7 @@
 #include <linux/netfilter/nfnetlink.h>
 #include <linux/netfilter/nfnetlink_xtables.h>
 #include <net/netlink.h>
+#include <net/sock.h>
 #include <net/netfilter/x_tables2.h>
 
 MODULE_DESCRIPTION("Xtables2 nfnetlink interface");
@@ -69,6 +70,66 @@ xtnetlink_fill(struct sk_buff *skb, const struct xtnetlink_pktref *old,
 }
 
 /**
+ * @ref:	skb/nl pointers that will be filled in (secondary return values)
+ * @old:	pointers to the original incoming skb/nl headers
+ *
+ * xtnetlink_fill can be used when the outgoing skb already exists (e.g. in
+ * case of a dump operation), but for non-dump responses, we have to create it
+ * ourselves.
+ */
+static int
+xtnetlink_new_fill(struct xtnetlink_pktref *ref,
+		   const struct xtnetlink_pktref *old)
+{
+	ref->skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (ref->skb == NULL)
+		return -ENOMEM;
+	ref->msg = xtnetlink_fill(ref->skb, old, 0);
+	if (IS_ERR(ref->msg)) {
+		kfree_skb(ref->skb);
+		return PTR_ERR(ref->msg);
+	}
+	return 0;
+}
+
+/**
+ * @xtnl:	socket to send the error packet out on
+ * @old:	pointers to the original incoming skb/nl headers
+ * @errcode:	last error code
+ *
+ * Create and send out an NFXT error packet. If @errcode is < 0, it indicates
+ * a system-level error (such as %-ENOMEM), reported back using %NFXTA_ERRNO.
+ * If @errcode is >= 0, it indicates an NFXT-specific error codes (%NFXTE_*),
+ * which is more fine grained than the dreaded %EINVAL, and which is reported
+ * back using %NFXTA_XTERRNO.
+ */
+static int
+xtnetlink_error(struct sock *xtnl, const struct xtnetlink_pktref *old,
+		int errcode)
+{
+	struct xtnetlink_pktref ref;
+	int ret;
+
+	ret = xtnetlink_new_fill(&ref, old);
+	if (ret < 0)
+		return ret;
+	if (errcode < 0)
+		/* Prefer positive numbers on the wire */
+		NLA_PUT_U32(ref.skb, NFXTA_ERRNO, -errcode);
+	else
+		NLA_PUT_U32(ref.skb, NFXTA_XTERRNO, errcode);
+	nlmsg_end(ref.skb, ref.msg);
+	ret = netlink_unicast(xtnl, ref.skb, NETLINK_CB(old->skb).pid,
+			      MSG_DONTWAIT);
+	if (ret < 0)
+		return ret;
+	/* ret is skb->len, but values >0 mean error to the caller -.- */
+	return 0;
+ nla_put_failure:
+	return -ENOBUFS;
+}
+
+/**
  * Ran too often into NULL derefs. Now there is a dummy function for unused
  * message type 0.
  */
@@ -113,14 +174,95 @@ xtnetlink_identify(struct sock *xtnl, struct sk_buff *iskb,
 				  NULL, 0);
 }
 
+static int
+xtnetlink_chain_new(struct sock *xtnl, struct sk_buff *iskb,
+		    const struct nlmsghdr *imsg, const struct nlattr *const *ad)
+{
+	struct xt2_pernet_data *pnet = xtables2_pernet(sock_net(xtnl));
+	struct xtnetlink_pktref ref = {.c_skb = iskb, .c_msg = imsg};
+	const struct nlattr *attr;
+	struct xt2_table *table;
+	struct xt2_chain *chain;
+	const char *name;
+	int ret = 0;
+
+	attr = nlmsg_find_attr(imsg, sizeof(struct nfgenmsg), NFXTA_NAME);
+	if (attr == NULL)
+		return xtnetlink_error(xtnl, &ref, NFXTE_ATTRSET_INCOMPLETE);
+	name = nla_data(attr);
+	if (*name == '\0')
+		/* Anonymous chains are internal. */
+		return xtnetlink_error(xtnl, &ref, NFXTE_CHAIN_INVALID_NAME);
+	/*
+	 * The table needs to stay, but note that rcu_read_lock cannot be used,
+	 * since we might sleep.
+	 */
+	mutex_lock(&pnet->master_lock);
+	table = pnet->master;
+	mutex_lock(&table->lock);
+	if (xt2_chain_lookup(table, name) != NULL) {
+		ret = xtnetlink_error(xtnl, &ref, NFXTE_CHAIN_EXISTS);
+	} else {
+		chain = xt2_chain_new(table, name);
+		if (IS_ERR(chain))
+			ret = PTR_ERR(chain);
+		/* Use NFXTE error codes whenever possible. */
+		if (ret == -ENAMETOOLONG)
+			ret = NFXTE_CHAIN_NAMETOOLONG;
+		ret = xtnetlink_error(xtnl, &ref, ret);
+	}
+	mutex_unlock(&table->lock);
+	mutex_unlock(&pnet->master_lock);
+	return ret;
+}
+
+/**
+ * Act on a %NFXTM_CHAIN_DEL message.
+ */
+static int
+xtnetlink_chain_del(struct sock *xtnl, struct sk_buff *iskb,
+		    const struct nlmsghdr *imsg,
+		    const struct nlattr *const *ad)
+{
+	struct xt2_pernet_data *pnet = xtables2_pernet(sock_net(xtnl));
+	struct xtnetlink_pktref ref = {.c_skb = iskb, .c_msg = imsg};
+	const struct nlattr *name_attr;
+	struct xt2_table *table;
+	struct xt2_chain *chain;
+	const char *name;
+	int ret = 0;
+
+	name_attr = nlmsg_find_attr(imsg, sizeof(struct nfgenmsg), NFXTA_NAME);
+	if (name_attr == NULL)
+		return xtnetlink_error(xtnl, &ref, NFXTE_ATTRSET_INCOMPLETE);
+	name = nla_data(name_attr);
+	if (*name == '\0')
+		return xtnetlink_error(xtnl, &ref, NFXTE_CHAIN_NOENT);
+
+	mutex_lock(&pnet->master_lock);
+	table = pnet->master;
+	mutex_lock(&table->lock);
+	chain = xt2_chain_lookup(table, name);
+	if (chain != NULL)
+		xt2_chain_free(chain);
+	else
+		ret = NFXTE_CHAIN_NOENT;
+	ret = xtnetlink_error(xtnl, &ref, ret);
+	mutex_unlock(&table->lock);
+	mutex_unlock(&pnet->master_lock);
+	return ret;
+}
+
 static const struct nla_policy xtnetlink_policy[] = {
 	[NFXTA_NAME] = {.type = NLA_NUL_STRING},
+	[NFXTA_ERRNO] = {.type = NLA_U32},
+	[NFXTA_XTERRNO] = {.type = NLA_U32},
 };
 
 /*
  * Use the same policy for all messages. I do not want to see EINVAL anytime
  * soon again just because I forgot sending an attribute from userspace.
- * (If such occurs, it will be dealt with %NFXTE_ATTRSET_INCOMPLETE, tbd.)
+ * (If such occurs, it will be dealt with %NFXTE_ATTRSET_INCOMPLETE.)
  */
 #define pol \
 	.policy = xtnetlink_policy, \
@@ -128,6 +270,8 @@ static const struct nla_policy xtnetlink_policy[] = {
 static const struct nfnl_callback xtnetlink_callback[] = {
 	[0] = {.call = xtnetlink_ignore},
 	[NFXTM_IDENTIFY] = {.call = xtnetlink_identify, pol},
+	[NFXTM_CHAIN_NEW] = {.call = xtnetlink_chain_new, pol},
+	[NFXTM_CHAIN_DEL] = {.call = xtnetlink_chain_del, pol},
 };
 #undef pol
 
-- 
1.7.7

--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Netfitler Users]     [LARTC]     [Bugtraq]     [Yosemite Forum]

  Powered by Linux