/*
 * (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 <endian.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,
#if BYTE_ORDER == BIG_ENDIAN
			.k	= 0,
#else
			.k	= sizeof(uint8_t),
#endif
		},
		[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 0;
	}

	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 0;
	}

	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 0;
	}

	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 0;
	}

	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 0;
	}

	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,
							     (3 - k) * 3 + 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;
		}
	}

	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 0;
	}

	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;
}

static int
bsf_add_zone_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->zone_elems == 0)
		return 0;

	/* 127 max filterable zones. One JMP instruction per zone. */
	s = stack_create(sizeof(struct jump), 127);
	if (s == NULL) {
		errno = ENOMEM;
		return 0;
	}

	jt = 1;
	j = 0;
	j += nfct_bsf_load_payload_offset(this, j);	/* A = nla header offset 		*/
	j += nfct_bsf_find_attr(this, CTA_ZONE, j);	/* A = CTA_ZONE 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_ZONE offset>		*/
	j += nfct_bsf_load_attr(this, BPF_H, j);	/* A = skb->data[X:X + BPF_H]		*/

	for (i = 0; i < f->zone_elems; i++) {
		j += nfct_bsf_cmp_k_stack(this, f->zone[i], jt - j, j, s);
	}

	while (stack_pop(s, &jmp) != -1)
		this[jmp.line].jt += jmp.jt + j;

	if (f->logic[NFCT_FILTER_ZONE] == 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;
	j += bsf_add_zone_filter(f, &bsf[j]);
	show_filter(bsf, from, j, "---- check zone ----");
	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 = j;
	sf.filter = bsf;

	return setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &sf, sizeof(sf));
}
