Skip to contentSkip to conversation

add userspace dump filter

RFC PATCH In review Ken-ichirou MATSUZAWA 2 participants 4 messages Jun 17, 2014 – Jun 23, 2014

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

RFC PATCH 1/1 add userspace dump filter

Hello,

I tried to filter dump by zone in userspace. But it seems for me that CTA_ZONE is classified larger class than CTA_MARK, then I think it allows to be filtered in kernel like CTA_MARK/CTA_MARK_MASK.

Which is preferable way?

--------

This patch adds more type to filter_dump, working in userspace, just only for nf_callback_register, not nf_callback_register2.

---
 include/internal/object.h                          |  5 +++
 include/internal/types.h                           |  4 ++-
 .../libnetfilter_conntrack.h                       |  6 ++++
 src/callback.c                                     |  4 +--
 src/conntrack/api.c                                | 41 ++++++++++++++++++++--
 src/conntrack/filter_dump.c                        | 17 +++++++++
 6 files changed, 72 insertions(+), 5 deletions(-)
include/internal/object.h +5 -0
index 83d9010..c1515ae 100644
@@ -49,6 +49,10 @@ struct nfct_handle {
 struct __data_container {
 	struct nfct_handle *h;
 	enum nf_conntrack_msg_type type;
+	int (*cb)(enum nf_conntrack_msg_type type,
+		  struct nf_conntrack *ct,
+		  void *data);
+	struct nf_conntrack *filter_ct;
 	void *data;
 };
 
@@ -284,6 +288,7 @@ struct nfct_filter {
 struct nfct_filter_dump {
 	struct nfct_filter_dump_mark	mark;
 	u_int8_t			l3num;
+	struct nf_conntrack		*ct;
 	u_int32_t			set;
 };
 
include/internal/types.h +3 -1
index 49bac2e..0520ff4 100644
@@ -15,7 +15,9 @@ typedef int (*getobjopt)(const struct nf_conntrack *ct);
 typedef void (*setobjopt)(struct nf_conntrack *ct);
 typedef void (*set_attr_grp)(struct nf_conntrack *ct, const void *value);
 typedef void (*get_attr_grp)(const struct nf_conntrack *ct, void *data);
-typedef void (*set_filter_dump_attr)(struct nfct_filter_dump *filter_dump, const void *value);
+typedef void (*set_filter_dump_attr)(struct nfct_filter_dump *filter_dump,
+				     const enum nfct_filter_dump_attr type,
+				     const void *value);
 
 /*
  * expectation types
include/libnetfilter_conntrack/libnetfilter_conntrack.h +6 -0
index 52fee85..d5a18db 100644
@@ -536,9 +536,15 @@ struct nfct_filter_dump_mark {
 enum nfct_filter_dump_attr {
 	NFCT_FILTER_DUMP_MARK = 0,	/* struct nfct_filter_dump_mark */
 	NFCT_FILTER_DUMP_L3NUM,		/* u_int8_t */
+	NFCT_FILTER_DUMP_L4PROTO,	/* u_int8_t */
+	NFCT_FILTER_DUMP_ZONE,		/* u_int16_t */
 	NFCT_FILTER_DUMP_MAX
 };
 
+#define NFCT_FILTER_DUMP_USER \
+  (1 << NFCT_FILTER_DUMP_ZONE | \
+   1 << NFCT_FILTER_DUMP_L4PROTO)
+
 struct nfct_filter_dump *nfct_filter_dump_create(void);
 
 void nfct_filter_dump_destroy(struct nfct_filter_dump *filter);
src/callback.c +2 -2
index 19cc663..e7db2bb 100644
@@ -54,8 +54,8 @@ int __callback(struct nlmsghdr *nlh, struct nfattr *nfa[], void *data)
 
 		__parse_conntrack(nlh, nfa, ct);
 
-		if (container->h->cb) {
-			ret = container->h->cb(type, ct, container->data);
+		if (container->cb) {
+			ret = container->cb(type, ct, container->data);
 		} else if (container->h->cb2) {
 			ret = container->h->cb2(nlh, type, ct,
 						container->data);
src/conntrack/api.c +39 -2

77 lines not on this page · raw message

src/conntrack/filter_dump.c +17 -0
index 4819759..0da4794 100644
@@ -11,6 +11,7 @@
 
 static void
 set_filter_dump_attr_mark(struct nfct_filter_dump *filter_dump,
+			  const enum nfct_filter_dump_attr type,
 			  const void *value)
 {
 	const struct nfct_filter_dump_mark *this = value;
@@ -21,14 +22,30 @@ set_filter_dump_attr_mark(struct nfct_filter_dump *filter_dump,
 
 static void
 set_filter_dump_attr_family(struct nfct_filter_dump *filter_dump,
+			    const enum nfct_filter_dump_attr type,
 			    const void *value)
 {
 	filter_dump->l3num = *((u_int8_t *)value);
 }
 
+const enum nf_conntrack_attr simple_dump_attr_array[] = {
+	[NFCT_FILTER_DUMP_ZONE]		= ATTR_ZONE,
+	[NFCT_FILTER_DUMP_L4PROTO]	= ATTR_L4PROTO,
+};
+
+static void
+set_filter_dump_attr_simple(struct nfct_filter_dump *filter_dump,
+			    const enum nfct_filter_dump_attr type,
+			    const void *value)
+{
+	nfct_set_attr(filter_dump->ct, simple_dump_attr_array[type], value);
+}
+
 const set_filter_dump_attr set_filter_dump_attr_array[NFCT_FILTER_DUMP_MAX] = {
 	[NFCT_FILTER_DUMP_MARK]		= set_filter_dump_attr_mark,
 	[NFCT_FILTER_DUMP_L3NUM]	= set_filter_dump_attr_family,
+	[NFCT_FILTER_DUMP_ZONE]		= set_filter_dump_attr_simple,
+	[NFCT_FILTER_DUMP_L4PROTO]	= set_filter_dump_attr_simple,
 };
 
 void __build_filter_dump(struct nfnlhdr *req, size_t size,
Signature · 1 line
1.9.1
#1 lore raw
Quoted 11 lines · Ken-ichirou MATSUZAWA

Hello,

I tried to filter dump by zone in userspace. But it seems for me that CTA_ZONE is classified larger class than CTA_MARK, then I think it allows to be filtered in kernel like CTA_MARK/CTA_MARK_MASK.

Which is preferable way?

--------

This patch adds more type to filter_dump, working in userspace,

If this works from userspace, then you can just filter out the conntracks from the callback handler itself. So I don't think this patch is the way to go.

The kernel side filtering aims to reduce the time to dump conntrack tables with lots of entries. If you want to add filtering by zone in the ctnetlink_dump_table path, you have to generalize the existing ctnetlink code.

My suggestion is to rework ctnetlink_dump_filter to make it look like (just quick code, I didn't even compile test it):

struct ctnetlink_dump_filter {
        enum ctnetlink_filter_key       key;
        u_int32_t                       data;
        u_int32_t                       mask;
};

The keys can be:

enum ctnetlink_filter_key {
CTNL_FILTER_MARK = 0,
CTNL_FILTER_ZONE,
};

Then, from ctnetlink_dump_table() you'll need to do something like:

        if (filter) {
                data = ctnetlink_fetch_key(ct, filter->key);
                if (!(data & filter->mask == filter->data))
                        continue;
        }

The ctnetlink_fetch_key() should look like:

static u_int32_t ctnetlink_fetch_key(const struct nf_conn *ct, u_int32_t key)

{
        u_int32_t ret;

        switch (key) {
        case CTNL_FILTER_MARK:
                ret = ct->mark;
        case CTNL_FILTER_ZONE:
                ret = ct->zone;
        }

        return ret;
}

Please, if you work on this, first send us a patch to generalize the filtering "framework" for ctnetlink dumps and then add the filtering by zone.

#2 lore raw

Thank you for your understandable explanation.

2014-06-18 17:59 GMT+09:00 Pablo Neira Ayuso <[email protected]>:

Quoted 4 lines · Ken-ichirou MATSUZAWA

Please, if you work on this, first send us a patch to generalize the filtering "framework" for ctnetlink dumps and then add the filtering by zone.

How about using sk_filter? I could have understood it's not efficient than the way you told me but BPF seems more versatile and can work on the socket which both dumping and listening event.

# I think your nfct-daemon.c example in libmnl

Also I know this changes dump behavior, I need to include an indication in nla or somewhere which distinguishes from normal dump, but it's not included.

--------

This patch enables dump filtering by bpf. It is not efficient since every nf_conn needs to be translated into skb, but it can be used both event and dump socket.

---
 net/netfilter/nf_conntrack_netlink.c |   20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)
net/netfilter/nf_conntrack_netlink.c +18 -2
index f77024d..189f19d 100644
@@ -831,10 +831,26 @@ restart:
 					    cb->nlh->nlmsg_seq,
 					    NFNL_MSG_TYPE(cb->nlh->nlmsg_type),
 					    ct);
-			rcu_read_unlock();
-			if (res < 0) {
+			if (res >= 0) {
+				struct sk_filter *skfilter
+					= rcu_dereference(skb->sk->sk_filter);
+				int ret = 0;
+
+				if (skfilter != NULL) {
+					skb_pull(skb, cb->args[2]);
+					ret = SK_RUN_FILTER(skfilter, skb);
+					skb_push(skb, cb->args[2]);
+					if (ret)
+						cb->args[2] = res;
+					else
+						skb_trim(skb, cb->args[2]);
+				}
+				rcu_read_unlock();
+			} else {
+				rcu_read_unlock();
 				nf_conntrack_get(&ct->ct_general);
 				cb->args[1] = (unsigned long)ct;
+				cb->args[2] = 0;
 				spin_unlock(lockp);
 				goto out;
 			}
Signature · 1 line
1.7.10.4
#3 lore raw
Quoted 9 lines · Ken-ichirou MATSUZAWA

2014-06-18 17:59 GMT+09:00 Pablo Neira Ayuso <[email protected]>:

Quoted 4 lines · Ken-ichirou MATSUZAWA

Please, if you work on this, first send us a patch to generalize the filtering "framework" for ctnetlink dumps and then add the filtering by zone.

How about using sk_filter? I could have understood it's not efficient than the way you told me but BPF seems more versatile and can work on the socket which both dumping and listening event.

The main reason for bpf in the event path was ENOBUFS. You can overrun the socket buffer easily with a high rate of events coming from interrupt context.

In the dump path, we have quite a lot more bandwidth since everything is running from user context and the table is dumped in chunks of one memory page. The motivation for the filtering was to reduce the time to dump large table.

Quoted 38 lines
net/netfilter/nf_conntrack_netlink.c +18 -2
index f77024d..189f19d 100644
@@ -831,10 +831,26 @@ restart:
 					    cb->nlh->nlmsg_seq,
 					    NFNL_MSG_TYPE(cb->nlh->nlmsg_type),
 					    ct);
-			rcu_read_unlock();
-			if (res < 0) {
+			if (res >= 0) {
+				struct sk_filter *skfilter
+					= rcu_dereference(skb->sk->sk_filter);
+				int ret = 0;
+
+				if (skfilter != NULL) {
+					skb_pull(skb, cb->args[2]);
+					ret = SK_RUN_FILTER(skfilter, skb);
+					skb_push(skb, cb->args[2]);
+					if (ret)
+						cb->args[2] = res;
+					else
+						skb_trim(skb, cb->args[2]);
+				}
 
 
+				rcu_read_unlock();
+			} else {
+				rcu_read_unlock();
 				nf_conntrack_get(&ct->ct_general);
 				cb->args[1] = (unsigned long)ct;
+				cb->args[2] = 0;
 				spin_unlock(lockp);
net/netfilter/nf_conntrack_netlink.c +0 -0
 				goto out;
 			}
Signature · 1 line
1.7.10.4