blob: 1549815eedcc5a2bcfb9e78beb40bedb6c3d7257 [file] [log] [blame]
/*
* (C) 2005-2011 by Pablo Neira Ayuso <pablo@netfilter.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include "internal/internal.h"
#include "internal/stack.h"
#include <linux/filter.h>
#include <stddef.h> /* offsetof */
#ifndef SKF_AD_NLATTR
#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
#if 0
static char *code2str(uint16_t code)
{
switch(code) {
case BPF_LD|BPF_IMM:
return "BPF_LD|BPF_IMM";
break;
case BPF_LDX|BPF_IMM:
return "BPF_LDX|BPF_IMM";
break;
case BPF_LD|BPF_B|BPF_ABS:
return "BPF_LD|BPF_B|BPF_ABS";
break;
case BPF_JMP|BPF_JEQ|BPF_K:
return "BPF_JMP|BPF_JEQ|BPF_K";
break;
case BPF_ALU|BPF_AND|BPF_K:
return "BPF_ALU|BPF_AND|BPF_K";
break;
case BPF_JMP|BPF_JA:
return "BPF_JMP|BPF_JA";
break;
case BPF_RET|BPF_K:
return "BPF_RET|BPF_K";
break;
case BPF_ALU|BPF_ADD|BPF_K:
return "BPF_ALU|BPF_ADD|BPF_K";
break;
case BPF_MISC|BPF_TAX:
return "BPF_MISC|BPF_TAX";
break;
case BPF_MISC|BPF_TXA:
return "BPF_MISC|BPF_TXA";
break;
case BPF_LD|BPF_B|BPF_IND:
return "BPF_LD|BPF_B|BPF_IND";
break;
case BPF_LD|BPF_H|BPF_IND:
return "BPF_LD|BPF_H|BPF_IND";
break;
case BPF_LD|BPF_W|BPF_IND:
return "BPF_LD|BPF_W|BPF_IND";
break;
}
return NULL;
}
static void show_filter(struct sock_filter *this, int from, int to, char *str)
{
int i;
printf("%s\n", str);
for(i=from; i<to; i++) {
char *code_str = code2str(this[i].code & 0xFFFF);
if (!code_str) {
printf("(%.4x) code=%.4x\t\t\tjt=%.2x jf=%.2x k=%.8x\n",
i,
this[i].code & 0xFFFF,
this[i].jt & 0xFF,
this[i].jf & 0xFF,
this[i].k & 0xFFFFFFFF);
} else {
printf("(%.4x) code=%30s\tjt=%.2x jf=%.2x k=%.8x\n",
i,
code_str,
this[i].jt & 0xFF,
this[i].jf & 0xFF,
this[i].k & 0xFFFFFFFF);
}
}
}
#else
static inline void
show_filter(struct sock_filter *this, int from, int to, char *str) {}
#endif
#define NEW_POS(x) (sizeof(x)/sizeof(struct sock_filter))
static int
nfct_bsf_load_payload_offset(struct sock_filter *this, int pos)
{
struct sock_filter __code = {
.code = BPF_LD|BPF_IMM,
.k = sizeof(struct nlmsghdr) + sizeof(struct nfgenmsg),
};
memcpy(&this[pos], &__code, sizeof(__code));
return NEW_POS(__code);
}
static int
nfct_bsf_find_attr(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,
}
};
memcpy(&this[pos], __code, sizeof(__code));
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;
uint8_t jt;
uint8_t jf;
};
static int
nfct_bsf_cmp_k_stack(struct sock_filter *this, int k,
int jump_true, int pos, struct stack *s)
{
struct sock_filter __code = {
.code = BPF_JMP|BPF_JEQ|BPF_K,
.k = k,
};
struct jump jmp = {
.line = pos,
.jt = jump_true - 1,
.jf = 0,
};
stack_push(s, &jmp);
memcpy(&this[pos], &__code, sizeof(__code));
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)
{
struct sock_filter __code = {
.code = BPF_ALU|BPF_AND|BPF_K,
.k = k,
};
memcpy(&this[pos], &__code, sizeof(__code));
return NEW_POS(__code);
}
static int
nfct_bsf_add_attr_data_offset(struct sock_filter *this, int pos)
{
struct sock_filter __code = {
/* A += sizeof(struct nfattr) */
.code = BPF_ALU|BPF_ADD|BPF_K,
.k = sizeof(struct nfattr),
};
memcpy(&this[pos], &__code, sizeof(__code));
return NEW_POS(__code);
}
static int
nfct_bsf_x_equal_a(struct sock_filter *this, int pos)
{
struct sock_filter __code = {
.code = BPF_MISC|BPF_TAX,
};
memcpy(&this[pos], &__code, sizeof(__code));
return NEW_POS(__code);
}
static int
nfct_bsf_a_equal_x(struct sock_filter *this, int pos)
{
struct sock_filter __code = {
.code = BPF_MISC|BPF_TXA,
};
memcpy(&this[pos], &__code, sizeof(__code));
return NEW_POS(__code);
}
static int
nfct_bsf_load_attr(struct sock_filter *this, int word_size, int pos)
{
struct sock_filter __code = {
/* A = skb->data[X + k:word_size] */
.code = BPF_LD|word_size|BPF_IND,
.k = sizeof(struct nfattr),
};
memcpy(&this[pos], &__code, sizeof(__code));
return NEW_POS(__code);
}
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 = {
.code = BPF_RET|BPF_K,
.k = verdict,
};
memcpy(&this[pos], &__code, sizeof(__code));
return NEW_POS(__code);
}
static int
nfct_bsf_jump_to(struct sock_filter *this, int line, int pos)
{
struct sock_filter __code = {
.code = BPF_JMP|BPF_JA,
.k = line,
};
memcpy(&this[pos], &__code, sizeof(__code));
return NEW_POS(__code);
};
/* this helps to skip messages coming from the ctnetlink expectation subsys. */
static int
bsf_cmp_subsys(struct sock_filter *this, int pos, uint8_t subsys)
{
struct sock_filter __code[] = {
[0] = {
/* X = offset to nlh->nlmsg_type */
.code = BPF_LDX|BPF_IMM,
.k = offsetof(struct nlmsghdr, nlmsg_type),
},
[1] = {
/* A = skb->data[X+k:B] (subsys_id) */
.code = BPF_LD|BPF_B|BPF_IND,
.k = sizeof(uint8_t),
},
[2] = {
/* A == subsys ? jump +1 : accept */
.code = BPF_JMP|BPF_JEQ|BPF_K,
.k = subsys,
.jt = 1,
.jf = 0,
},
};
memcpy(&this[pos], &__code, sizeof(__code));
return NEW_POS(__code);
}
static int
add_state_filter_cta(struct sock_filter *this,
unsigned int cta_protoinfo_proto,
unsigned int cta_protoinfo_state,
uint16_t state_flags,
unsigned int logic)
{
unsigned int i, j;
unsigned int label_continue, jt;
struct stack *s;
struct jump jmp;
/* XXX: 32 maximum states + 3 jumps in the three-level iteration */
s = stack_create(sizeof(struct jump), 3 + 32);
if (s == NULL) {
errno = ENOMEM;
return -1;
}
jt = 1;
if (logic == 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_PROTOINFO, j);
j += nfct_bsf_cmp_k_stack(this, 0, label_continue - j, j, s);
j += nfct_bsf_add_attr_data_offset(this, j);
j += nfct_bsf_find_attr(this, cta_protoinfo_proto, j);
j += nfct_bsf_cmp_k_stack(this, 0, label_continue - j, j, s);
j += nfct_bsf_add_attr_data_offset(this, j);
j += nfct_bsf_find_attr(this, cta_protoinfo_state, j);
j += nfct_bsf_cmp_k_stack(this, 0, label_continue - j, j, s);
j += nfct_bsf_x_equal_a(this, j);
j += nfct_bsf_load_attr(this, BPF_B, j);
for (i = 0; i < sizeof(state_flags) * 8; i++) {
if (state_flags & (1 << i)) {
j += nfct_bsf_cmp_k_stack(this, i, jt - j, j, s);
}
}
while (stack_pop(s, &jmp) != -1)
this[jmp.line].jt += jmp.jt + j;
if (logic == 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
add_state_filter(struct sock_filter *this,
int proto,
uint16_t flags,
unsigned int logic)
{
static const 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,
logic);
}
static int
bsf_add_state_filter(const struct nfct_filter *filter, struct sock_filter *this)
{
unsigned int i, j;
for (i = 0, j = 0; i < IPPROTO_MAX; i++) {
if (filter->l4proto_state[i].map &&
filter->l4proto_state[i].len > 0) {
j += add_state_filter(
this,
i,
filter->l4proto_state[i].map,
filter->logic[NFCT_FILTER_L4PROTO_STATE]);
}
}
return j;
}
static int
bsf_add_proto_filter(const struct nfct_filter *f, struct sock_filter *this)
{
unsigned int i, j;
unsigned int label_continue, jt;
struct stack *s;
struct jump jmp;
/* nothing to filter, skip */
if (f->l4proto_len == 0)
return 0;
/* XXX: 255 maximum proto + 3 jumps in the three-level iteration */
s = stack_create(sizeof(struct jump), 3 + 255);
if (s == NULL) {
errno = ENOMEM;
return -1;
}
jt = 1;
if (f->logic[NFCT_FILTER_L4PROTO] == 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);
j += nfct_bsf_add_attr_data_offset(this, j);
j += nfct_bsf_find_attr(this, CTA_TUPLE_PROTO, j);
j += nfct_bsf_cmp_k_stack(this, 0, label_continue - j, j, s);
j += nfct_bsf_add_attr_data_offset(this, j);
j += nfct_bsf_find_attr(this, CTA_PROTO_NUM, j);
j += nfct_bsf_cmp_k_stack(this, 0, label_continue - j, j, s);
j += nfct_bsf_x_equal_a(this, j);
j += nfct_bsf_load_attr(this, BPF_B, j);
for (i = 0; i < IPPROTO_MAX; i++) {
if (test_bit(i, f->l4proto_map)) {
j += nfct_bsf_cmp_k_stack(this, i, jt - j, j, s);
}
}
while (stack_pop(s, &jmp) != -1)
this[jmp.line].jt += jmp.jt + j;
if (f->logic[NFCT_FILTER_L4PROTO] == 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_addr_ipv4_filter(const struct nfct_filter *f,
struct sock_filter *this,
unsigned int type)
{
unsigned int i, j, dir, attr;
unsigned int label_continue, jt;
struct stack *s;
struct jump jmp;
switch(type) {
case CTA_IP_V4_SRC:
dir = __FILTER_ADDR_SRC;
attr = NFCT_FILTER_SRC_IPV4;
break;
case CTA_IP_V4_DST:
dir = __FILTER_ADDR_DST;
attr = NFCT_FILTER_DST_IPV4;
break;
default:
return 0;
}
/* nothing to filter, skip */
if (f->l3proto_elems[dir] == 0)
return 0;
/* XXX: 127 maximum IPs + 3 jumps in the three-level iteration */
s = stack_create(sizeof(struct jump), 3 + 127);
if (s == NULL) {
errno = ENOMEM;
return -1;
}
jt = 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);
j += nfct_bsf_add_attr_data_offset(this, j);
j += nfct_bsf_find_attr(this, CTA_TUPLE_IP, j);
j += nfct_bsf_cmp_k_stack(this, 0, label_continue - j, j, s);
j += nfct_bsf_add_attr_data_offset(this, j);
j += nfct_bsf_find_attr(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[dir]; i++) {
int ip = f->l3proto[dir][i].addr & f->l3proto[dir][i].mask;
j += nfct_bsf_load_attr(this, BPF_W, j);
j += nfct_bsf_alu_and(this, f->l3proto[dir][i].mask, j);
j += nfct_bsf_cmp_k_stack(this, ip, jt - j, j, s);
}
while (stack_pop(s, &jmp) != -1)
this[jmp.line].jt += jmp.jt + 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_ipv4_filter(const struct nfct_filter *f, struct sock_filter *this)
{
return bsf_add_addr_ipv4_filter(f, this, CTA_IP_V4_SRC);
}
static int
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 - 1,
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);
}
static int
bsf_add_mark_filter(const struct nfct_filter *f, struct sock_filter *this)
{
unsigned int i, j;
unsigned int jt;
struct stack *s;
struct jump jmp;
struct sock_filter __code = {
/* if (A == 0) skip next two */
.code = BPF_JMP|BPF_JEQ|BPF_K,
.k = 0,
.jt = 2,
.jf = 0,
};
/* nothing to filter, skip */
if (f->mark_elems == 0)
return 0;
/* XXX: see bsf_add_addr_ipv4_filter() */
s = stack_create(sizeof(struct jump), 3 + 127);
if (s == NULL) {
errno = ENOMEM;
return -1;
}
jt = 1;
j = 0;
j += nfct_bsf_load_payload_offset(this, j); /* A = nla header offset */
j += nfct_bsf_find_attr(this, CTA_MARK, j); /* A = CTA_MARK offset, started from A */
memcpy(&this[j], &__code, sizeof(__code)); /* if A == 0 skip next two op */
j += NEW_POS(__code);
j += nfct_bsf_x_equal_a(this, j); /* X = A <CTA_MARK offset> */
j += nfct_bsf_load_attr(this, BPF_W, j); /* A = skb->data[X:X + BPF_W] */
j += nfct_bsf_x_equal_a(this, j); /* X = A <CTA_MARK value> */
for (i = 0; i < f->mark_elems; i++) {
int mark = f->mark[i].val & f->mark[i].mask;
j += nfct_bsf_alu_and(this, f->mark[i].mask, j);
j += nfct_bsf_cmp_k_stack(this, mark, jt - j, j, s);
j += nfct_bsf_a_equal_x(this, j);
}
while (stack_pop(s, &jmp) != -1)
this[jmp.line].jt += jmp.jt + j;
if (f->logic[NFCT_FILTER_MARK] == 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;
}
/* this buffer must be big enough to store all the autogenerated lines */
#define BSF_BUFFER_SIZE 2048
int __setup_netlink_socket_filter(int fd, struct nfct_filter *f)
{
struct sock_filter bsf[BSF_BUFFER_SIZE];
struct sock_fprog sf;
unsigned int j = 0, from = 0;
memset(bsf, 0, sizeof(bsf));
j += bsf_cmp_subsys(&bsf[j], j, NFNL_SUBSYS_CTNETLINK);
j += nfct_bsf_ret_verdict(bsf, NFCT_FILTER_ACCEPT, j);
show_filter(bsf, from, j, "--- check subsys ---");
from = j;
j += bsf_add_proto_filter(f, &bsf[j]);
show_filter(bsf, from, j, "---- check proto ----");
from = j;
j += bsf_add_saddr_ipv4_filter(f, &bsf[j]);
show_filter(bsf, from, j, "---- check src IPv4 ----");
from = j;
j += bsf_add_daddr_ipv4_filter(f, &bsf[j]);
show_filter(bsf, from, j, "---- check dst IPv4 ----");
from = j;
j += bsf_add_saddr_ipv6_filter(f, &bsf[j]);
show_filter(bsf, from, j, "---- check src IPv6 ----");
from = j;
j += bsf_add_daddr_ipv6_filter(f, &bsf[j]);
show_filter(bsf, from, j, "---- check dst IPv6 ----");
from = j;
j += bsf_add_state_filter(f, &bsf[j]);
show_filter(bsf, from, j, "---- check state ----");
from = j;
j += bsf_add_mark_filter(f, &bsf[j]);
show_filter(bsf, from, j, "---- check mark ----");
from = j;
/* nothing to filter, skip */
if (j == 0)
return 0;
j += nfct_bsf_ret_verdict(bsf, NFCT_FILTER_ACCEPT, j);
show_filter(bsf, from, j, "---- final verdict ----");
from = j;
sf.len = (sizeof(struct sock_filter) * j) / sizeof(bsf[0]);
sf.filter = bsf;
return setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &sf, sizeof(sf));
}