summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPablo Neira Ayuso <pablo@netfilter.org>2008-07-17 17:20:10 +0200
committerPablo Neira Ayuso <pablo@netfilter.org>2008-07-17 17:20:10 +0200
commit563114a47ae03c988ca0e66eddda33d485e35f6b (patch)
tree0e2db0e2a52b283991c96b25e6386be05fb2ed70 /src
parentd073c52600a052db2822b4f284a0b2c546ec1ea4 (diff)
add berkeley socket filtering high-level API
This patch adds an abstraction level to berkeley sockets filter (BSF) for Netlink sockets available since Linux kernel 2.6.26. This provides an easy way to attach filters without knowing about BSF at all. Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Diffstat (limited to 'src')
-rw-r--r--src/conntrack/Makefile.am3
-rw-r--r--src/conntrack/api.c93
-rw-r--r--src/conntrack/bsf.c416
-rw-r--r--src/conntrack/filter.c39
4 files changed, 550 insertions, 1 deletions
diff --git a/src/conntrack/Makefile.am b/src/conntrack/Makefile.am
index 6440dd7..94009ea 100644
--- a/src/conntrack/Makefile.am
+++ b/src/conntrack/Makefile.am
@@ -12,4 +12,5 @@ libnfconntrack_la_SOURCES = api.c callback.c \
snprintf_default.c snprintf_xml.c \
objopt.c \
compare.c \
- copy.c
+ copy.c \
+ filter.c bsf.c
diff --git a/src/conntrack/api.c b/src/conntrack/api.c
index 58efd32..3bd96a8 100644
--- a/src/conntrack/api.c
+++ b/src/conntrack/api.c
@@ -842,3 +842,96 @@ void nfct_copy_attr(struct nf_conntrack *ct1,
set_bit(type, ct1->set);
}
}
+
+/**
+ * nfct_filter_create - create a filter
+ *
+ * This function returns a valid pointer on success, otherwise NULL is
+ * returned and errno is appropriately set.
+ */
+struct nfct_filter *nfct_filter_create(void)
+{
+ return calloc(sizeof(struct nfct_filter), 1);
+}
+
+/**
+ * nfct_filter_destroy - destroy a filter
+ * @filter: filter that we want to destroy
+ *
+ * This function releases the memory that is used by the filter object.
+ * However, please note that this function does *not* detach an already
+ * attached filter.
+ */
+void nfct_filter_destroy(struct nfct_filter *filter)
+{
+ assert(filter != NULL);
+ free(filter);
+ filter = NULL;
+}
+
+/**
+ * nfct_filter_add_attr - add a filter attribute of the filter object
+ * @filter: filter object that we want to modify
+ * @type: filter attribute type
+ * @value: pointer to the value of the filter attribute
+ *
+ * Limitations: You can add up to 256 IPv4 addresses and masks for
+ * NFCT_FILTER_SRC_IPV4 and, similarly, 256 for NFCT_FILTER_DST_IPV4.
+ */
+void nfct_filter_add_attr(struct nfct_filter *filter,
+ const enum nfct_filter_attr type,
+ const void *value)
+{
+ assert(filter != NULL);
+ assert(value != NULL);
+
+ if (type >= NFCT_FILTER_MAX)
+ return;
+
+ if (filter_attr_array[type]) {
+ filter_attr_array[type](filter, value);
+ set_bit(type, filter->set);
+ }
+}
+
+/**
+ * nfct_filter_add_attr_u32 - add an u32 filter attribute of the filter object
+ * @filter: filter object that we want to modify
+ * @type: filter attribute type
+ * @value: value of the filter attribute using unsigned int (32 bits).
+ */
+void nfct_filter_add_attr_u32(struct nfct_filter *filter,
+ const enum nfct_filter_attr type,
+ u_int32_t value)
+{
+ nfct_filter_add_attr(filter, type, &value);
+}
+
+/**
+ * nfct_filter_attach - attach a filter to a socket descriptor
+ * @fd: socket descriptor
+ * @filter: filter that we want to attach to the socket
+ *
+ * This function returns -1 on error and set errno appropriately. If the
+ * function returns EINVAL probably you have found a bug in it. Please,
+ * report this.
+ */
+int nfct_filter_attach(int fd, struct nfct_filter *filter)
+{
+ assert(filter != NULL);
+
+ return __setup_netlink_socket_filter(fd, filter);
+}
+
+/**
+ * nfct_filter_detach - detach an existing filter
+ * @fd: socket descriptor
+ *
+ * This function returns -1 on error and set errno appropriately.
+ */
+int nfct_filter_detach(int fd)
+{
+ int val = 0;
+
+ return setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val));
+}
diff --git a/src/conntrack/bsf.c b/src/conntrack/bsf.c
new file mode 100644
index 0000000..2ac5fe1
--- /dev/null
+++ b/src/conntrack/bsf.c
@@ -0,0 +1,416 @@
+/*
+ * (C) 2008 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ */
+
+#include "internal.h"
+#include <linux/filter.h>
+
+#ifndef SKF_AD_NLATTR
+#define SKF_AD_NLATTR 12
+#endif
+
+#define NFCT_FILTER_REJECT 0U
+#define NFCT_FILTER_ACCEPT ~0U
+
+#if 0
+static void show_filter(struct sock_filter *this, int size)
+{
+ int i;
+
+ for(i=0; i<size; i++)
+ printf("(%.4x) code=%.4x jt=%.2x jf=%.2x k=%.8x\n",
+ i,
+ this[i].code & 0xFFFF,
+ this[i].jt & 0xFF,
+ this[i].jf & 0xFF,
+ this[i].k & 0xFFFFFFFF);
+}
+#else
+static inline void show_filter(struct sock_filter *this, int size) {}
+#endif
+
+static void set_basic_filter(struct sock_filter *this,
+ unsigned int first_type,
+ unsigned int second_type,
+ unsigned int third_type,
+ unsigned int label,
+ unsigned int word_size)
+{
+ struct sock_filter filter[] = {
+ [0] = {
+ /* A = sizeof(struct nlmsghdr) + sizeof(struct nfgenmsg) */
+ .code = BPF_LD|BPF_IMM,
+ .k = sizeof(struct nlmsghdr) + sizeof(struct nfgenmsg),
+ },
+ [1] = {
+ /* X = first_type */
+ .code = BPF_LDX|BPF_IMM,
+ .k = first_type,
+ },
+ [2] = {
+ /* A = netlink attribute offset */
+ .code = BPF_LD|BPF_B|BPF_ABS,
+ .k = SKF_AD_OFF + SKF_AD_NLATTR,
+ },
+ [3] = {
+ /* Reject if not found (A == 0) */
+ .code = BPF_JMP|BPF_JEQ|BPF_K,
+ .k = 0,
+ .jt = label - 3 - 1,
+ },
+ [4] = {
+ /* A += sizeof(struct nfattr) */
+ .code = BPF_ALU|BPF_ADD|BPF_K,
+ .k = sizeof(struct nfattr),
+ },
+ [5] = {
+ /* X = second_type */
+ .code = BPF_LDX|BPF_IMM,
+ .k = second_type,
+ },
+ [6] = {
+ /* A = netlink attribute offset */
+ .code = BPF_LD|BPF_B|BPF_ABS,
+ .k = SKF_AD_OFF + SKF_AD_NLATTR,
+ },
+ [7] = {
+ /* Reject if not found (A == 0) */
+ .code = BPF_JMP|BPF_JEQ|BPF_K,
+ .k = 0,
+ .jt = label - 7 - 1,
+ },
+ [8] = {
+ /* A += sizeof(struct nfattr) */
+ .code = BPF_ALU|BPF_ADD|BPF_K,
+ .k = sizeof(struct nfattr),
+ },
+ [9] = {
+ /* X = third_type */
+ .code = BPF_LDX|BPF_IMM,
+ .k = third_type,
+ },
+ [10] = {
+ /* A = netlink attribute offset */
+ .code = BPF_LD|BPF_B|BPF_ABS,
+ .k = SKF_AD_OFF + SKF_AD_NLATTR,
+ },
+ [11] = {
+ /* Reject if not found (A == 0) */
+ .code = BPF_JMP|BPF_JEQ|BPF_K,
+ .k = 0,
+ .jt = label - 11 - 1,
+ },
+ [12] = {
+ /* X = A */
+ .code = BPF_MISC|BPF_TAX,
+ },
+ [13] = {
+ /* A = skb->data[X + k:word_size] */
+ .code = BPF_LD|word_size|BPF_IND,
+ .k = sizeof(struct nfattr),
+ },
+ };
+
+ memcpy(this, filter, sizeof(filter));
+}
+
+static int
+add_state_filter_cta(struct sock_filter *this,
+ unsigned int cta_protoinfo_proto,
+ unsigned int cta_protoinfo_state,
+ u_int16_t state_flags,
+ size_t remain)
+{
+ struct sock_filter filter[14 + __FILTER_PROTO_MAX];
+ struct sock_filter verdict = {
+ /* Reject */
+ .code = BPF_RET|BPF_K,
+ .k = NFCT_FILTER_REJECT,
+ };
+ unsigned int i, j;
+ unsigned int label_continue;
+
+ /* calculate the number of filter lines */
+ for (i = 0, j = 0; i < sizeof(state_flags) * 8; i++) {
+ if (state_flags & (1 << i)) {
+ j++;
+ }
+ }
+
+ /* nothing to filter, skip */
+ if (j == 0)
+ return 0;
+
+ if (j + 14 >= __FILTER_PROTO_MAX + 14 || j + 14 > remain) {
+ errno = ENOSPC;
+ return -1;
+ }
+
+ memset(filter, 0, sizeof(filter));
+
+ label_continue = j + 1;
+
+ set_basic_filter(filter,
+ CTA_PROTOINFO,
+ cta_protoinfo_proto,
+ cta_protoinfo_state,
+ 14 + label_continue,
+ BPF_B);
+
+ for (i = 0, j = 0; i < sizeof(state_flags) * 8; i++) {
+ struct sock_filter cmp = {
+ .code = BPF_JMP|BPF_JEQ|BPF_K,
+ .k = i,
+ .jt = label_continue - j - 1,
+ };
+
+ if (state_flags & (1 << i)) {
+ memcpy(&filter[j + 14], &cmp, sizeof(cmp));
+ j++;
+ }
+ }
+
+ memcpy(this, filter, sizeof(struct sock_filter) * (j + 14));
+ memcpy(&this[j + 14], &verdict, sizeof(verdict));
+
+ return j + 14 + 1;
+}
+
+static int
+add_state_filter(struct sock_filter *this,
+ int proto,
+ u_int16_t flags,
+ size_t remain)
+{
+ struct {
+ unsigned int cta_protoinfo;
+ unsigned int cta_state;
+ } cta[IPPROTO_MAX] = {
+ [IPPROTO_TCP] = {
+ .cta_protoinfo = CTA_PROTOINFO_TCP,
+ .cta_state = CTA_PROTOINFO_TCP_STATE,
+ },
+ [IPPROTO_SCTP] = {
+ .cta_protoinfo = CTA_PROTOINFO_SCTP,
+ .cta_state = CTA_PROTOINFO_SCTP_STATE,
+ },
+ [IPPROTO_DCCP] = {
+ .cta_protoinfo = CTA_PROTOINFO_DCCP,
+ .cta_state = CTA_PROTOINFO_DCCP_STATE,
+ },
+ };
+
+ if (cta[proto].cta_protoinfo == 0 && cta[proto].cta_state == 0) {
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ return add_state_filter_cta(this,
+ cta[proto].cta_protoinfo,
+ cta[proto].cta_state,
+ flags,
+ remain);
+}
+
+static int
+bsf_add_state_filter(const struct nfct_filter *filter,
+ struct sock_filter *this,
+ size_t remain)
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; i < IPPROTO_MAX; i++) {
+ if (test_bit(i, filter->l4proto_map) &&
+ filter->l4proto_state[i].map) {
+ j += add_state_filter(this,
+ i,
+ filter->l4proto_state[i].map,
+ remain);
+ }
+ }
+
+ return j;
+}
+
+static int
+bsf_add_proto_filter(const struct nfct_filter *f,
+ struct sock_filter *this,
+ size_t remain)
+{
+ struct sock_filter filter[14 + IPPROTO_MAX];
+ struct sock_filter verdict = {
+ /* Reject */
+ .code = BPF_RET|BPF_K,
+ .k = NFCT_FILTER_REJECT,
+ };
+ unsigned int i, j;
+ unsigned int label_continue;
+
+ for (i = 0, j = 0; i < IPPROTO_MAX; i++) {
+ if (test_bit(i, f->l4proto_map)) {
+ j++;
+ }
+ }
+
+ /* nothing to filter, skip */
+ if (j == 0)
+ return 0;
+
+ if (j + 14 >= IPPROTO_MAX + 14 || j + 14 > remain) {
+ errno = ENOSPC;
+ return -1;
+ }
+
+ label_continue = j + 1;
+
+ memset(filter, 0, sizeof(filter));
+
+ set_basic_filter(filter,
+ CTA_TUPLE_ORIG,
+ CTA_TUPLE_PROTO,
+ CTA_PROTO_NUM,
+ 14 + label_continue,
+ BPF_B);
+
+ for (i = 0, j = 0; i < IPPROTO_MAX; i++) {
+ struct sock_filter cmp = {
+ .code = BPF_JMP|BPF_JEQ|BPF_K,
+ .k = i,
+ .jt = label_continue - j - 1,
+ };
+
+ if (test_bit(i, f->l4proto_map)) {
+ memcpy(&filter[j + 14], &cmp, sizeof(cmp));
+ j++;
+ }
+ }
+
+ memcpy(this, filter, sizeof(struct sock_filter) * (j + 14));
+ memcpy(&this[j + 14], &verdict, sizeof(verdict));
+
+ return j + 14 + 1;
+}
+
+static int
+bsf_add_addr_ipv4_filter(const struct nfct_filter *f,
+ struct sock_filter *this,
+ unsigned int type,
+ size_t remain)
+{
+ struct sock_filter filter[14 + __FILTER_ADDR_MAX];
+ struct sock_filter verdict = {
+ /* Reject */
+ .code = BPF_RET|BPF_K,
+ .k = NFCT_FILTER_REJECT,
+ };
+ unsigned int i, j, dir;
+ unsigned int label_continue;
+
+ switch(type) {
+ case CTA_IP_V4_SRC:
+ dir = __FILTER_ADDR_SRC;
+ break;
+ case CTA_IP_V4_DST:
+ dir = __FILTER_ADDR_DST;
+ break;
+ default:
+ return 0;
+ }
+
+ /* nothing to filter, skip */
+ if (f->l3proto_elems[dir] == 0)
+ return 0;
+
+ if (f->l3proto_elems[dir] + 14 >= __FILTER_ADDR_MAX + 14 ||
+ f->l3proto_elems[dir] + 14 > remain) {
+ errno = ENOSPC;
+ return -1;
+ }
+
+ label_continue = (f->l3proto_elems[dir] * 2) + 1;
+
+ memset(filter, 0, sizeof(filter));
+
+ set_basic_filter(filter,
+ CTA_TUPLE_ORIG,
+ CTA_TUPLE_IP,
+ type,
+ 14 + label_continue,
+ BPF_W);
+
+ for (i = 0, j = 0; i < f->l3proto_elems[dir]; i++) {
+ struct sock_filter cmp[] = {
+ [0] = {
+ .code = BPF_ALU|BPF_AND|BPF_K,
+ .k = f->l3proto[dir][i].mask,
+ },
+ [1] = {
+ .code = BPF_JMP|BPF_JEQ|BPF_K,
+ .k = f->l3proto[dir][i].addr &
+ f->l3proto[dir][i].mask,
+ .jt = label_continue - j - 2,
+ },
+ };
+ memcpy(&filter[j + 14], cmp, sizeof(cmp));
+ j+=2;
+ }
+
+ memcpy(this, filter, sizeof(struct sock_filter) * (j + 14));
+ memcpy(&this[j + 14], &verdict, sizeof(verdict));
+
+ return j + 14 + 1;
+}
+
+static int
+bsf_add_saddr_ipv4_filter(const struct nfct_filter *f,
+ struct sock_filter *this,
+ size_t remain)
+{
+ return bsf_add_addr_ipv4_filter(f, this, CTA_IP_V4_SRC, remain);
+}
+
+static int
+bsf_add_daddr_ipv4_filter(const struct nfct_filter *f,
+ struct sock_filter *this,
+ size_t remain)
+{
+ return bsf_add_addr_ipv4_filter(f, this, CTA_IP_V4_DST, remain);
+}
+
+/* this buffer must be big enough to store all the autogenerated lines */
+#define BSF_BUFFER_SIZE 1024
+
+int __setup_netlink_socket_filter(int fd, struct nfct_filter *f)
+{
+ struct sock_filter bsf[BSF_BUFFER_SIZE];
+ struct sock_filter bsf_accept = {
+ /* Accept */
+ .code = BPF_RET|BPF_K,
+ .k = NFCT_FILTER_ACCEPT,
+ };
+ struct sock_fprog sf;
+ unsigned int j = 0;
+
+ memset(bsf, 0, sizeof(bsf));
+
+ j += bsf_add_proto_filter(f, &bsf[j], BSF_BUFFER_SIZE-j);
+ j += bsf_add_saddr_ipv4_filter(f, &bsf[j], BSF_BUFFER_SIZE-j);
+ j += bsf_add_daddr_ipv4_filter(f, &bsf[j], BSF_BUFFER_SIZE-j);
+ j += bsf_add_state_filter(f, &bsf[j], BSF_BUFFER_SIZE-j);
+
+ /* nothing to filter, skip */
+ if (j == 0)
+ return 0;
+
+ memcpy(&bsf[j], &bsf_accept, sizeof(struct sock_filter));
+
+ show_filter(bsf, j+1);
+
+ sf.len = (sizeof(struct sock_filter) * (j + 1)) / sizeof(bsf[0]);
+ sf.filter = bsf;
+
+ return setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &sf, sizeof(sf));
+}
diff --git a/src/conntrack/filter.c b/src/conntrack/filter.c
new file mode 100644
index 0000000..5ea7d5e
--- /dev/null
+++ b/src/conntrack/filter.c
@@ -0,0 +1,39 @@
+#include "internal.h"
+
+static void filter_attr_l4proto(struct nfct_filter *filter, const void *value)
+{
+ set_bit(*((int *) value), filter->l4proto_map);
+}
+
+static void
+filter_attr_l4proto_state(struct nfct_filter *filter, const void *value)
+{
+ const struct nfct_filter_proto *this = value;
+
+ set_bit_u16(this->state, &filter->l4proto_state[this->proto].map);
+}
+
+static void filter_attr_src_ipv4(struct nfct_filter *filter, const void *value)
+{
+ const struct nfct_filter_ipv4 *this = value;
+
+ filter->l3proto[0][filter->l3proto_elems[0]].addr = this->addr;
+ filter->l3proto[0][filter->l3proto_elems[0]].mask = this->mask;
+ filter->l3proto_elems[0]++;
+}
+
+static void filter_attr_dst_ipv4(struct nfct_filter *filter, const void *value)
+{
+ const struct nfct_filter_ipv4 *this = value;
+
+ filter->l3proto[1][filter->l3proto_elems[1]].addr = this->addr;
+ filter->l3proto[1][filter->l3proto_elems[1]].mask = this->mask;
+ filter->l3proto_elems[1]++;
+}
+
+filter_attr filter_attr_array[] = {
+ [NFCT_FILTER_L4PROTO] = filter_attr_l4proto,
+ [NFCT_FILTER_L4PROTO_STATE] = filter_attr_l4proto_state,
+ [NFCT_FILTER_SRC_IPV4] = filter_attr_src_ipv4,
+ [NFCT_FILTER_DST_IPV4] = filter_attr_dst_ipv4,
+};