diff options
-rw-r--r-- | include/internal/object.h | 13 | ||||
-rw-r--r-- | include/libnetfilter_conntrack/libnetfilter_conntrack.h | 6 | ||||
-rw-r--r-- | src/conntrack/bsf.c | 164 | ||||
-rw-r--r-- | src/conntrack/filter.c | 30 | ||||
-rw-r--r-- | utils/conntrack_filter.c | 13 |
5 files changed, 226 insertions, 0 deletions
diff --git a/include/internal/object.h b/include/internal/object.h index ef49590..df002fd 100644 --- a/include/internal/object.h +++ b/include/internal/object.h @@ -222,6 +222,19 @@ struct nfct_filter { u_int32_t mask; } l3proto[2][__FILTER_ADDR_MAX]; + /* + * FIXME: For IPv6 filtering, up to 20 IPs/masks (12 BSF lines + * per comparison). I think that it is not worthy to try to support + * more than that for performance reasons. It seems that oprofile + * shows bad numbers for very large BSF code. + */ + u_int32_t l3proto_elems_ipv6[2]; + struct { +#define __FILTER_IPV6_MAX 20 + u_int32_t addr[4]; + u_int32_t mask[4]; + } l3proto_ipv6[2][__FILTER_IPV6_MAX]; + u_int32_t set[1]; }; diff --git a/include/libnetfilter_conntrack/libnetfilter_conntrack.h b/include/libnetfilter_conntrack/libnetfilter_conntrack.h index f77d273..766fb47 100644 --- a/include/libnetfilter_conntrack/libnetfilter_conntrack.h +++ b/include/libnetfilter_conntrack/libnetfilter_conntrack.h @@ -416,12 +416,18 @@ struct nfct_filter_ipv4 { u_int32_t addr; u_int32_t mask; }; +struct nfct_filter_ipv6 { + u_int32_t addr[4]; + u_int32_t mask[4]; +}; enum nfct_filter_attr { NFCT_FILTER_L4PROTO = 0, /* u_int32_t */ NFCT_FILTER_L4PROTO_STATE, /* struct nfct_filter_proto */ NFCT_FILTER_SRC_IPV4, /* struct nfct_filter_ipv4 */ NFCT_FILTER_DST_IPV4, /* struct nfct_filter_ipv4 */ + NFCT_FILTER_SRC_IPV6, /* struct nfct_filter_ipv6 */ + NFCT_FILTER_DST_IPV6, /* struct nfct_filter_ipv6 */ NFCT_FILTER_MAX }; diff --git a/src/conntrack/bsf.c b/src/conntrack/bsf.c index 9a0dfff..0b71e8e 100644 --- a/src/conntrack/bsf.c +++ b/src/conntrack/bsf.c @@ -13,6 +13,11 @@ #define SKF_AD_NLATTR 12 #endif +/* this requires a Linux kernel >= 2.6.29 */ +#ifndef SKF_AD_NLATTR_NEST +#define SKF_AD_NLATTR_NEST 16 +#endif + #define NFCT_FILTER_REJECT 0U #define NFCT_FILTER_ACCEPT ~0U @@ -65,6 +70,26 @@ nfct_bsf_find_attr(struct sock_filter *this, int attr, int pos) return NEW_POS(__code); } +/* like the previous, but limit the search to the bound of the nest */ +static int +nfct_bsf_find_attr_nest(struct sock_filter *this, int attr, int pos) +{ + struct sock_filter __code[] = { + [0] = { + /* X = attribute type */ + .code = BPF_LDX|BPF_IMM, + .k = attr, + }, + [1] = { + /* A = netlink attribute offset */ + .code = BPF_LD|BPF_B|BPF_ABS, + .k = SKF_AD_OFF + SKF_AD_NLATTR_NEST, + } + }; + memcpy(&this[pos], __code, sizeof(__code)); + return NEW_POS(__code); +} + struct jump { int line; u_int8_t jt; @@ -89,6 +114,25 @@ nfct_bsf_cmp_k_stack(struct sock_filter *this, int k, return NEW_POS(__code); } +/* like previous, but use jf instead of jt. We can merge both functions */ +static int +nfct_bsf_cmp_k_stack_jf(struct sock_filter *this, int k, + int jump_false, int pos, struct stack *s) +{ + struct sock_filter __code = { + .code = BPF_JMP|BPF_JEQ|BPF_K, + .k = k, + }; + struct jump jmp = { + .line = pos, + .jt = 0, + .jf = jump_false - 1, + }; + stack_push(s, &jmp); + memcpy(&this[pos], &__code, sizeof(__code)); + return NEW_POS(__code); +} + static int nfct_bsf_alu_and(struct sock_filter *this, int k, int pos) { @@ -136,6 +180,19 @@ nfct_bsf_load_attr(struct sock_filter *this, int word_size, int pos) } static int +nfct_bsf_load_attr_offset(struct sock_filter *this, int word_size, + int offset, int pos) +{ + struct sock_filter __code = { + /* A = skb->data[X + k:word_size] */ + .code = BPF_LD|word_size|BPF_IND, + .k = sizeof(struct nfattr) + offset, + }; + memcpy(&this[pos], &__code, sizeof(__code)); + return NEW_POS(__code); +} + +static int nfct_bsf_ret_verdict(struct sock_filter *this, int verdict, int pos) { struct sock_filter __code = { @@ -411,6 +468,111 @@ bsf_add_daddr_ipv4_filter(const struct nfct_filter *f, struct sock_filter *this) return bsf_add_addr_ipv4_filter(f, this, CTA_IP_V4_DST); } +static int +bsf_add_addr_ipv6_filter(const struct nfct_filter *f, + struct sock_filter *this, + unsigned int type) +{ + unsigned int i, j, dir, attr; + unsigned int label_continue, jf; + struct stack *s; + struct jump jmp; + + switch(type) { + case CTA_IP_V6_SRC: + dir = __FILTER_ADDR_SRC; + attr = NFCT_FILTER_SRC_IPV6; + break; + case CTA_IP_V6_DST: + dir = __FILTER_ADDR_DST; + attr = NFCT_FILTER_DST_IPV6; + break; + default: + return 0; + } + + /* nothing to filter, skip */ + if (f->l3proto_elems_ipv6[dir] == 0) + return 0; + + /* XXX: 80 jumps (4*20) + 3 jumps in the three-level iteration */ + s = stack_create(sizeof(struct jump), 3 + 80); + if (s == NULL) { + errno = ENOMEM; + return -1; + } + + jf = 1; + if (f->logic[attr] == NFCT_FILTER_LOGIC_POSITIVE) + label_continue = 1; + else + label_continue = 2; + + j = 0; + j += nfct_bsf_load_payload_offset(this, j); + j += nfct_bsf_find_attr(this, CTA_TUPLE_ORIG, j); + j += nfct_bsf_cmp_k_stack(this, 0, label_continue - j, j, s); + /* no need to access attribute payload, we are using nest-based finder + * j += nfct_bsf_add_attr_data_offset(this, j); */ + j += nfct_bsf_find_attr_nest(this, CTA_TUPLE_IP, j); + j += nfct_bsf_cmp_k_stack(this, 0, label_continue - j, j, s); + j += nfct_bsf_find_attr_nest(this, type, j); + j += nfct_bsf_cmp_k_stack(this, 0, label_continue - j, j, s); + j += nfct_bsf_x_equal_a(this, j); + + for (i = 0; i < f->l3proto_elems_ipv6[dir]; i++) { + int k, offset; + + for (k = 0, offset = 0; k < 4; k++, offset += 4) { + int ip = f->l3proto_ipv6[dir][i].addr[k] & + f->l3proto_ipv6[dir][i].mask[k]; + + j += nfct_bsf_load_attr_offset(this, BPF_W, offset, j); + j += nfct_bsf_alu_and(this, + f->l3proto_ipv6[dir][i].mask[k], + j); + if (k < 3) { + j += nfct_bsf_cmp_k_stack_jf(this, ip, + jf - j, j, s); + } else { + /* last word: jump if true */ + j += nfct_bsf_cmp_k_stack(this, ip, jf - j, + j, s); + } + } + } + + while (stack_pop(s, &jmp) != -1) { + if (jmp.jt) { + this[jmp.line].jt += jmp.jt + j; + } + if (jmp.jf) { + this[jmp.line].jf += jmp.jf + j; + } + } + + if (f->logic[attr] == NFCT_FILTER_LOGIC_NEGATIVE) + j += nfct_bsf_jump_to(this, 1, j); + + j += nfct_bsf_ret_verdict(this, NFCT_FILTER_REJECT, j); + + stack_destroy(s); + + return j; +} + +static int +bsf_add_saddr_ipv6_filter(const struct nfct_filter *f, struct sock_filter *this) +{ + return bsf_add_addr_ipv6_filter(f, this, CTA_IP_V6_SRC); +} + +static int +bsf_add_daddr_ipv6_filter(const struct nfct_filter *f, struct sock_filter *this) +{ + return bsf_add_addr_ipv6_filter(f, this, CTA_IP_V6_DST); +} + /* this buffer must be big enough to store all the autogenerated lines */ #define BSF_BUFFER_SIZE 2048 @@ -425,6 +587,8 @@ int __setup_netlink_socket_filter(int fd, struct nfct_filter *f) j += bsf_add_proto_filter(f, &bsf[j]); j += bsf_add_saddr_ipv4_filter(f, &bsf[j]); j += bsf_add_daddr_ipv4_filter(f, &bsf[j]); + j += bsf_add_saddr_ipv6_filter(f, &bsf[j]); + j += bsf_add_daddr_ipv6_filter(f, &bsf[j]); j += bsf_add_state_filter(f, &bsf[j]); /* nothing to filter, skip */ diff --git a/src/conntrack/filter.c b/src/conntrack/filter.c index 7cee673..bf29f96 100644 --- a/src/conntrack/filter.c +++ b/src/conntrack/filter.c @@ -49,9 +49,39 @@ static void filter_attr_dst_ipv4(struct nfct_filter *filter, const void *value) filter->l3proto_elems[1]++; } +static void filter_attr_src_ipv6(struct nfct_filter *filter, const void *value) +{ + const struct nfct_filter_ipv6 *this = value; + + if (filter->l3proto_elems_ipv6[0] >= __FILTER_IPV6_MAX) + return; + + memcpy(filter->l3proto_ipv6[0][filter->l3proto_elems_ipv6[0]].addr, + this->addr, sizeof(u_int32_t)*4); + memcpy(filter->l3proto_ipv6[0][filter->l3proto_elems_ipv6[0]].mask, + this->mask, sizeof(u_int32_t)*4); + filter->l3proto_elems_ipv6[0]++; +} + +static void filter_attr_dst_ipv6(struct nfct_filter *filter, const void *value) +{ + const struct nfct_filter_ipv6 *this = value; + + if (filter->l3proto_elems_ipv6[1] >= __FILTER_IPV6_MAX) + return; + + memcpy(filter->l3proto_ipv6[1][filter->l3proto_elems_ipv6[1]].addr, + this->addr, sizeof(u_int32_t)*4); + memcpy(filter->l3proto_ipv6[1][filter->l3proto_elems_ipv6[1]].mask, + this->mask, sizeof(u_int32_t)*4); + filter->l3proto_elems_ipv6[1]++; +} + filter_attr filter_attr_array[NFCT_FILTER_MAX] = { [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, + [NFCT_FILTER_SRC_IPV6] = filter_attr_src_ipv6, + [NFCT_FILTER_DST_IPV6] = filter_attr_dst_ipv6, }; diff --git a/utils/conntrack_filter.c b/utils/conntrack_filter.c index 7c44c50..0252fbf 100644 --- a/utils/conntrack_filter.c +++ b/utils/conntrack_filter.c @@ -66,6 +66,19 @@ int main() nfct_filter_add_attr(filter, NFCT_FILTER_SRC_IPV4, &filter_ipv4); + /* BSF always wants data in host-byte order */ + struct nfct_filter_ipv6 filter_ipv6 = { + .addr = { 0x0, 0x0, 0x0, 0x1 }, + .mask = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }, + }; + + /* ignore whatever that comes from ::1 (loopback) */ + nfct_filter_set_logic(filter, + NFCT_FILTER_SRC_IPV6, + NFCT_FILTER_LOGIC_NEGATIVE); + + nfct_filter_add_attr(filter, NFCT_FILTER_SRC_IPV6, &filter_ipv6); + if (nfct_filter_attach(nfct_fd(h), filter) == -1) { perror("nfct_filter_attach"); return 0; |