/*
 * High performance packet classification module for netfilter
 *
 * Authors: Michael Bellion and Thomas Heinz
 * (c) 2002-2003 by the hipac core team <nf@hipac.org>:
 *      +-----------------------+----------------------+
 *      |   Michael Bellion     |     Thomas Heinz     |
 *      | <mbellion@hipac.org>  |  <creatix@hipac.org> |
 *      +-----------------------+----------------------+
 * Licenced under the GNU General Public Licence, version >= 2.
 */


#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kmod.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <net/sock.h>
#include <linux/types.h>
#include <linux/netfilter_ipv4.h>
#include <net/ip.h>
#include <linux/spinlock.h>
#include <linux/netfilter.h>
#include <linux/udp.h>
#include <linux/netfilter_ipv4/ip_conntrack.h>
#include "nfhp_mod.h"
#include "nfhp_com.h"
#include "nfhp_dev.h"
#include "nfhp_proc.h"
#include "hipac.h"

#define MAX(a, b)  ((a) >= (b) ? (a) : (b))
#define MIN(a, b)  ((a) >= (b) ? (b) : (a))
#define SKB_LEN(s) (((s)->end - (s)->tail) - NLMSG_LENGTH(0))

/* hook match functions */
static nf_hookfn input_match;
static nf_hookfn forward_match;
static nf_hookfn output_match;

/* hipac data structures for INPUT, FORWARD and OUPUT hook plus
   their corresponding netfilter ops and rwlocks */
void *hipac_input   = NULL;
void *hipac_forward = NULL;
void *hipac_output  = NULL;
struct nf_hook_ops input_op   = { LIST_HEAD_INIT(input_op.list),
				  input_match, PF_INET, NF_IP_LOCAL_IN,
				  NF_IP_PRI_FILTER + 1 };
struct nf_hook_ops forward_op = { LIST_HEAD_INIT(forward_op.list),
				  forward_match, PF_INET, NF_IP_FORWARD,
				  NF_IP_PRI_FILTER + 1 };
struct nf_hook_ops output_op  = { LIST_HEAD_INIT(output_op.list),
				  output_match, PF_INET, NF_IP_LOCAL_OUT,
				  NF_IP_PRI_FILTER + 1 };

/* used to serialize hipac modifications caused by netlink commands and
   the interface handling module */
DECLARE_MUTEX(nlhp_lock);

/* protects listing operations (callback based) */
DECLARE_MUTEX(nlhp_listing_lock);

static struct sock *nfhp_sock = NULL;
static int peerpid = 0;

/* connection tracking dependency helper module */
static DECLARE_MUTEX(cthelp_lock);
static struct module *nfhp_cthelp_module = NULL;

/* size of reply skb in bytes */
static const int reply_skb_size = 130048;

/* used to increment ip_tables' module counter */
static struct ipt_target nfhp_dummy_target =
{
	{ NULL, NULL }, "NF_HIPAC_DUMMY_TARGET", NULL, NULL, NULL, THIS_MODULE
};

/* latest hipac_get_chain_info snapshot */
struct list_info
{
	struct hipac_chain_info *inf;
	u32 len;
};
static struct list_info linfo = {NULL, 0};

struct packet
{
	int hook;
	struct sk_buff **skbuff;
	const struct net_device *indev, *outdev;
};



/*
 * dimension extractor functions
 */

static u32
get_src_ip(const void *pkt)
{
	const struct sk_buff *sk = *((struct packet *) pkt)->skbuff;
	return ntohl(sk->nh.iph->saddr);
}

static u32
get_dst_ip(const void *pkt)
{
	const struct sk_buff *sk = *((struct packet *) pkt)->skbuff;
	return ntohl(sk->nh.iph->daddr);
}

static u32
get_iniface(const void *pkt)
{
	return ((struct packet*) pkt)->indev->ifindex;
}

static u32
get_outiface(const void *pkt)
{
	return ((struct packet*) pkt)->outdev->ifindex;
}

static u32
get_proto(const void *pkt)
{
	const struct sk_buff *sk = *((struct packet *) pkt)->skbuff;
	return sk->nh.iph->protocol;
}

static u32
get_fragment(const void *pkt)
{
	const struct sk_buff *sk = *((struct packet *) pkt)->skbuff;
	return !!(ntohs(sk->nh.iph->frag_off) & IP_OFFSET);
}

static u32
get_tcp_dport(const void *pkt)
{
	const struct sk_buff *sk = *((struct packet *) pkt)->skbuff;
	return ntohs(((struct tcphdr *) 
		      (((u32 *) sk->nh.iph) + sk->nh.iph->ihl))->dest);
}

static u32
get_tcp_sport(const void *pkt)
{
	const struct sk_buff *sk = *((struct packet *) pkt)->skbuff;
	return ntohs(((struct tcphdr *) 
		      (((u32 *) sk->nh.iph) + sk->nh.iph->ihl))->source);
}

static u32
get_tcp_flags(const void *pkt)
{
	const struct sk_buff *sk = *((struct packet *) pkt)->skbuff;
	struct tcphdr *tcp = (struct tcphdr *)
		(((u32 *) sk->nh.iph) + sk->nh.iph->ihl);
	if (tcp->syn && !tcp->ack && !tcp->fin && !tcp->rst) {
		return 0;
	}
	return 1;
}

static u32
get_udp_dport(const void *pkt)
{
	const struct sk_buff *sk = *((struct packet *) pkt)->skbuff;
	return ntohs(((struct udphdr *) 
		      (((u32 *) sk->nh.iph) + sk->nh.iph->ihl))->dest);
}

static u32
get_udp_sport(const void *pkt)
{
	const struct sk_buff *sk = *((struct packet *) pkt)->skbuff;
	return ntohs(((struct udphdr *) 
		      (((u32 *) sk->nh.iph) + sk->nh.iph->ihl))->source);
}

static u32
get_icmptypes(const void *pkt)
{
	const struct sk_buff *sk = *((struct packet *) pkt)->skbuff;
	return (((struct icmphdr *) 
		 (((u32 *) sk->nh.iph) + sk->nh.iph->ihl))->type << 8) +
		(((struct icmphdr *) 
		  (((u32 *) sk->nh.iph) + sk->nh.iph->ihl))->code);
}

static u32
get_state(const void *pkt)
{
	const struct sk_buff *sk = *((struct packet *) pkt)->skbuff;
	if (sk->nfct == NULL) {
		return NFHP_STATE_INVALID;
	}
	return ((sk->nfct - 
		 ((struct ip_conntrack *) sk->nfct->master)->infos) %
		NFHP_STATE_NUM_VALID);
}

static u32
get_ttl(const void *pkt)
{
	const struct sk_buff *sk = *((struct packet *) pkt)->skbuff;
	return sk->nh.iph->ttl;
}


/*
 * conntrack dependency helper module counter management :-)
 */

int
nfhp_register_cthelp(struct module *cthelp)
{
	int ret;

	MOD_INC_USE_COUNT;
	ret = down_interruptible(&cthelp_lock);
	if (ret != 0) {
		MOD_DEC_USE_COUNT;
		return ret;
	}
	if (nfhp_cthelp_module != NULL) {
		printk(KERN_ERR "nfhp_register_cthelp: module already "
		       "registered\n");
		MOD_DEC_USE_COUNT;
		ret = -EINVAL;
	}
	nfhp_cthelp_module = cthelp;
	up(&cthelp_lock);
	return ret;
}

void
nfhp_unregister_cthelp(struct module *cthelp)
{
	 down(&cthelp_lock);
	 if (nfhp_cthelp_module != cthelp) {
		 printk(KERN_ERR "nfhp_unregister_cthelp: unregistered "
			"module tries to unregister\n");
		 up(&cthelp_lock);
		 return;
	 }
	 nfhp_cthelp_module = NULL;
	 up(&cthelp_lock);
	 MOD_DEC_USE_COUNT;
}

static inline int
cthelp_use(void)
{
	int ret;
	
	/* check whether the conntrack dependency helper is registered */
	ret = down_interruptible(&cthelp_lock);
	if (ret < 0) {
		return ERRNO_TO_NFHE(-ret);
	}
	if (nfhp_cthelp_module != NULL) {
		__MOD_INC_USE_COUNT(nfhp_cthelp_module);
		up(&cthelp_lock);
		return 0;
	}

	/* try to load the module */
	up(&cthelp_lock);
	request_module("nf_hipac_cthelp");
	
	/* did we succeed? */
	ret = down_interruptible(&cthelp_lock);
	if (ret < 0) {
		return ERRNO_TO_NFHE(-ret);
	}
	if (nfhp_cthelp_module != NULL) {
		__MOD_INC_USE_COUNT(nfhp_cthelp_module);
		up(&cthelp_lock);
		return 0;
	}
	up(&cthelp_lock);
	return NFHE_CTHELP;
}

static inline void
cthelp_unuse(void)
{
	if (nfhp_cthelp_module == NULL) {
		printk(KERN_ERR "%s: conntrack dependency helper "
		       "module not registered\n", __FUNCTION__);
		return;
	}
	__MOD_DEC_USE_COUNT(nfhp_cthelp_module);
}


/*
 * functions and data structures necessary for hipac initialization
 */

/* dimension id to bit type mapping */
static const u8 dim2btype[] =
{
	BIT_SRC_IP,    BIT_DEST_IP,   BIT_INIFACE,   BIT_OUTIFACE,
	BIT_PROTO,     BIT_FRAGMENT,  BIT_TCP_DPORT, BIT_TCP_SPORT,
	BIT_TCP_FLAGS, BIT_UDP_DPORT, BIT_UDP_SPORT, BIT_ICMP_TYPE,
	BIT_STATE,     BIT_TTL
};

/* dimension extractor functions */
static const hipac_extract_t extract[] =
{
	get_src_ip,    get_dst_ip,    get_iniface,   get_outiface,
	get_proto,     get_fragment,  get_tcp_dport, get_tcp_sport,
	get_tcp_flags, get_udp_dport, get_udp_sport, get_icmptypes,
	get_state,     get_ttl
};

/* iptables_match executor */
static hipac_match_t
hipac_match_exec(const void *packet, void *first_match, void *end)
{
	const struct packet *p = packet;
	hipac_match_t match = MATCH_YES;
	struct ipt_entry_match *m = first_match;
	int hotdrop = 0;
	struct iphdr *ip;
	u16 offset, datalen;
	void *protohdr;

	ip = (*p->skbuff)->nh.iph;
	protohdr = (u32 *) ip + ip->ihl;
	datalen = (*p->skbuff)->len - ip->ihl * 4;
	offset = ntohs(ip->frag_off) & IP_OFFSET;
	for (; m < (struct ipt_entry_match *) end; m = NEXT_IPT_MATCH(m)) {
		if (!m->u.kernel.match->match(*p->skbuff, p->indev,
					      p->outdev, m->data, offset,
					      protohdr, datalen, &hotdrop)) {
			match = MATCH_NO;
			break;
		}
	}
	if (hotdrop) {
		return MATCH_HOTDROP;
	}
	return match;
}

/* iptables_target executor */
static hipac_target_t
hipac_target_exec(const void *packet, void *target)
{
	const struct packet *p = packet;
	struct ipt_entry_target *t = target;

	switch (t->u.kernel.target->target(p->skbuff, p->hook, p->indev,
					   p->outdev, t->data, NULL)) {
	    case NF_ACCEPT:    return TARGET_ACCEPT;
	    case NF_DROP:      return TARGET_DROP;
	    case IPT_CONTINUE: return TARGET_NONE;
	}
	
	/* this should never be reached */
	printk(KERN_ERR "%s: unkown verdict => packet dropped\n",
	       __FUNCTION__);
	return TARGET_DROP;
}

/* equality test - rnl is the hipac_rule in netlink format which implies
   that it contains ipt_entry and cmp_len if the rule has an ipt_entry_match
   or ipt_entry_target or chain label; rhp is in hipac format which means
   that it does not contain ipt_entry and cmp_len */
static int
hipac_eq_exec(const struct hipac_rule *rnl, const struct hipac_rule *rhp)
{
	u32 cmp_len_ind = 0;
	u16 *cmp_len;

	if (rnl == rhp) {
		printk(KERN_ERR "%s: rnl == rhp error\n", __FUNCTION__);
		return 0;
	}
	if (rnl == NULL || rhp == NULL || rnl->size != rhp->size ||
	    rnl->native_mct != rhp->native_mct ||
	    memcmp(rnl->cmp_start, rhp->cmp_start,
		   sizeof(*rnl) - offsetof(struct hipac_rule, cmp_start) +
		   rnl->native_mct * sizeof(*rnl->first_match))) {
		return 0;
	}
	cmp_len = CMP_LEN(rnl);
	if (HAS_IPT_MATCH(rnl)) {
		struct ipt_entry_match *mnl, *mhp, *endhp;
		mnl = FIRST_IPT_MATCH_IE(rnl);
		mhp = FIRST_IPT_MATCH(rhp);
		endhp = IPT_MATCH_END(rhp);
		for (; mhp < endhp;
		     mnl = NEXT_IPT_MATCH(mnl), mhp = NEXT_IPT_MATCH(mhp),
		     cmp_len_ind++) {
			if (strncmp(mnl->u.user.name,
				    mhp->u.kernel.match->name,
				    sizeof(mnl->u.user.name)) ||
			    mnl->u.match_size != mhp->u.match_size ||
			    memcmp(&mnl->data, &mhp->data,
				   cmp_len[cmp_len_ind])) {
				return 0;
			}
		}
	}
	if (HAS_IPT_TARGET(rnl)) {
		struct ipt_entry_target *tnl, *thp;
		tnl = IPT_TARGET_IE(rnl);
		thp = IPT_TARGET(rhp);
		if (strncmp(tnl->u.user.name, thp->u.kernel.target->name,
			    sizeof(tnl->u.user.name)) ||
		    tnl->u.target_size != thp->u.target_size ||
		    memcmp(&tnl->data, &thp->data,
			   cmp_len[cmp_len_ind])) {
			return 0;
		}
	} else if (HAS_CHAIN_TARGET(rnl)) {
		char *tnl, *thp;
		tnl = CHAIN_TARGET_IE(rnl);
		thp = CHAIN_TARGET(rhp);
		/* strlen(tnl) < HIPAC_CHAIN_NAME_MAX_LEN */
		if (strcmp(tnl, thp)) {
			return 0;
		}
	}
	return 1;
}

/* r is constructed by copying rnl to the exclusion of ipt_entry and
   cmp_len (if present); rnl->size already states the size of r _but_
   rnl may be smaller than rnl->size if it has a chain target */
static void
hipac_copy_constructor(const struct hipac_rule *rnl, struct hipac_rule *r)
{
	if (HAS_IPT_ENTRY(rnl)) {
		u32 size = rnl->size;
		if (HAS_CHAIN_TARGET(rnl)) {
			size -= HIPAC_CHAIN_NAME_MAX_LEN -
				strlen(CHAIN_TARGET_IE(rnl)) - 1;
		}
		memcpy(r, rnl, sizeof(*rnl) + rnl->native_mct *
		       sizeof(*rnl->first_match));
		if (HAS_IPT_MATCH(rnl)) {
			memcpy(FIRST_IPT_MATCH(r), IPT_ENTRY_END(rnl),
			       size - rnl->match_offset);
		} else {
			memcpy(IPT_TARGET(r), IPT_ENTRY_END(rnl),
			       size - rnl->target_offset);
		}
	} else {
		memcpy(r, rnl, rnl->size);
	}
}

/* destructor for iptables matches/target */
static void
hipac_destroy_exec(struct hipac_rule *r)
{
	int i;

	if (r == NULL) {
		return;
	}
	for (i = 0; i < r->native_mct &&
		     r->first_match[i].dimid < DIMID_STATE; i++);
	if (i < r->native_mct && r->first_match[i].dimid == DIMID_STATE) {
		cthelp_unuse();
	}
	if (HAS_IPT_MATCH(r)) {
		struct ipt_entry_match *m, *end;
		m = FIRST_IPT_MATCH(r);
		end = IPT_MATCH_END(r);
		for (; m < end; m = NEXT_IPT_MATCH(m)) {
			if (m->u.kernel.match->destroy) {
				m->u.kernel.match->destroy(
					m->data, m->u.match_size - sizeof(*m));
			}
			if (m->u.kernel.match->me) {
				__MOD_DEC_USE_COUNT(m->u.kernel.match->me);
			}
		}
	}
	if (HAS_IPT_TARGET(r)) {
		struct ipt_entry_target *t;
		t = IPT_TARGET(r);
		if (t->u.kernel.target->destroy) {
			t->u.kernel.target->destroy(
				t->data, t->u.target_size - sizeof(*t));
		}
		if (t->u.kernel.target->me) {
			__MOD_DEC_USE_COUNT(t->u.kernel.target->me);
		}
	}
}

/* destructor for iptables matches/target (rnl is the hipac_rule in
   netlink format) */
static void
hipac_destroy_exec_nl(struct hipac_rule *rnl)
{
	int i;

	if (rnl == NULL) {
		return;
	}
	for (i = 0; i < rnl->native_mct &&
		     rnl->first_match[i].dimid < DIMID_STATE; i++);
	if (i < rnl->native_mct && rnl->first_match[i].dimid == DIMID_STATE) {
		cthelp_unuse();
	}
	if (HAS_IPT_MATCH(rnl)) {
		struct ipt_entry_match *m, *end;
		m = FIRST_IPT_MATCH_IE(rnl);
		end = IPT_MATCH_END_IE(rnl);
		for (; m < end; m = NEXT_IPT_MATCH(m)) {
			if (m->u.kernel.match->destroy) {
				m->u.kernel.match->destroy(
					m->data, m->u.match_size - sizeof(*m));
			}
			if (m->u.kernel.match->me) {
				__MOD_DEC_USE_COUNT(m->u.kernel.match->me);
			}
		}
	}
	if (HAS_IPT_TARGET(rnl)) {
		struct ipt_entry_target *t;
		t = IPT_TARGET_IE(rnl);
		if (t->u.kernel.target->destroy) {
			t->u.kernel.target->destroy(
				t->data, t->u.target_size - sizeof(*t));
		}
		if (t->u.kernel.target->me) {
			__MOD_DEC_USE_COUNT(t->u.kernel.target->me);
		}
	}
}

static inline int
skb_ok(const struct sk_buff *skb)
{
	const struct iphdr *ip = skb->nh.iph;
	u16 datalen = skb->len - ip->ihl * 4;
	
	/* make sure that tcp/udp/icmp headers can be accessed properly
	   (if available) within the dimid extractor functions */
	switch (ntohs(ip->frag_off) & IP_OFFSET) {
	    case 0:
		    switch (ip->protocol) {
			case IPPROTO_TCP:
				if (datalen < sizeof(struct tcphdr)) {
					printk(KERN_NOTICE "Dropping evil "
					       "TCP tinygram.\n");
					return 0;
				}
			case IPPROTO_UDP:
				if (datalen < sizeof(struct udphdr)) {
					printk(KERN_NOTICE "Dropping evil "
					       "UDP tinygram.\n");
					return 0;
				}
			case IPPROTO_ICMP:
				if (datalen < 2) {
					printk(KERN_NOTICE "Dropping evil "
					       "ICMP tinygram.\n");
					return 0;
				}
		    }
		    return 1;
	    case 1:
		    if (ip->protocol == IPPROTO_TCP) {
			    printk(KERN_NOTICE "Dropping evil TCP offset=1 "
				   "fragment.\n");
			    return 0;
		    }
	    default:
		    /* fragment => tcp/udp/icmp match never occurs */
		    return 1;
	}
}

static unsigned int
input_match(unsigned int hooknum,
	    struct sk_buff **skb,
	    const struct net_device *in,
	    const struct net_device *out,
	    int (*okfn) (struct sk_buff *))
{
	const struct packet pkt = {hooknum, skb, in, out};

	if (likely(skb_ok(*skb))) {
		switch (hipac_match(hipac_input, &pkt,
				    ntohs((*skb)->nh.iph->tot_len))) {
		    case TARGET_ACCEPT: return NF_ACCEPT;
		    case TARGET_DROP:   return NF_DROP;
		    default:           
			    printk(KERN_ERR "%s: unkown verdict (packet "
				   "dropped)\n", __FUNCTION__);
			    return NF_DROP;
		}
	}
	return NF_DROP;
}

static unsigned int
forward_match(unsigned int hooknum,
	      struct sk_buff **skb,
	      const struct net_device *in,
	      const struct net_device *out,
	      int (*okfn) (struct sk_buff *))
{
	const struct packet pkt = {hooknum, skb, in, out};

	if (likely(skb_ok(*skb))) {
		switch (hipac_match(hipac_forward, &pkt,
				    ntohs((*skb)->nh.iph->tot_len))) {
		    case TARGET_ACCEPT: return NF_ACCEPT;
		    case TARGET_DROP:   return NF_DROP;
		    default:           
			    printk(KERN_ERR "%s: unkown verdict (packet "
				   "dropped)\n", __FUNCTION__);
			    return NF_DROP;
		}
	}
	return NF_DROP;
}

static unsigned int
output_match(unsigned int hooknum,
	     struct sk_buff **skb,
	     const struct net_device *in,
	     const struct net_device *out,
	     int (*okfn) (struct sk_buff *))
{
	const struct packet pkt = {hooknum, skb, in, out};

	if (likely(skb_ok(*skb))) {
		switch (hipac_match(hipac_output, &pkt,
				    ntohs((*skb)->nh.iph->tot_len))) {
		    case TARGET_ACCEPT: return NF_ACCEPT;
		    case TARGET_DROP:   return NF_DROP;
		    default:           
			    printk(KERN_ERR "%s: unkown verdict (packet "
				   "dropped)\n", __FUNCTION__);
			    return NF_DROP;
		}
	}
	return NF_DROP;
}


/*
 * kernel-user netlink communication
 */

static struct sk_buff *
nlhp_new_reply(void)
{
	struct sk_buff *skb;

	skb = alloc_skb(reply_skb_size, GFP_KERNEL);
	if (skb == NULL) {
		/* maybe we can get a smaller skb */
		int sz = MAX(sizeof(int32_t), sizeof(struct nfhp_err));
		skb = alloc_skb(NLMSG_SPACE(sz), GFP_KERNEL);
		if (skb == NULL) {
			return NULL;
		}
		return skb;
	}
	return skb;
}

static inline void *
nlhp_reply_data(struct sk_buff *skb)
{
	if (skb == NULL) {
		return NULL;
	}
	return ((struct nfhp_err *) NLMSG_DATA(skb->data))->data;
}

static void
nlhp_send_list_err(struct sk_buff *reply, int err)
{
	struct nlmsghdr *nlh;

	nlh = NLMSG_PUT(reply, 0, 0, NLMSG_ERROR, sizeof(int32_t));
	*(int32_t *) NLMSG_DATA(nlh) = err;
	if (!NLMSG_OK(nlh, NLMSG_LENGTH(sizeof(int32_t)))) {
		printk(KERN_ERR "netlink message not ok\n");
		return;
	}
	if (netlink_unicast(nfhp_sock, reply, peerpid, MSG_DONTWAIT) <= 0) {
		printk(KERN_ERR "netlink_unicast failed\n");
		return;
	}
	return;
	
 nlmsg_failure:
	printk(KERN_ERR "NLMSG_PUT failed\n");
}

static void
nlhp_send_reply(struct sk_buff *reply, int err, const char *data)
{
	struct nlmsghdr *nlh;
	struct nfhp_err *hpr;
	u32 size;

	if (data != NULL) {
		size = sizeof(*hpr) + strlen(data);
		size = MIN(size, SKB_LEN(reply));
	} else {
		size = sizeof(*hpr);
	}
	nlh = NLMSG_PUT(reply, 0, 0, NLMSG_ERROR, size);
	hpr = NLMSG_DATA(nlh);
	hpr->err = err;
	hpr->size = size;
	if (data != NULL && hpr->size > sizeof(*hpr)) {
		strncpy(hpr->data, data, hpr->size - sizeof(*hpr));
	}
	if (!NLMSG_OK(nlh, NLMSG_LENGTH(hpr->size))) {
		printk(KERN_ERR "netlink message not ok\n");
		return;
	}
	if (netlink_unicast(nfhp_sock, reply, peerpid, MSG_DONTWAIT) <= 0) {
		printk(KERN_ERR "netlink_unicast failed\n");
		return;
	}
	return;

 nlmsg_failure:
	printk(KERN_ERR "NLMSG_PUT failed\n");
}

static inline void *
nlhp_list_rule(struct nfhp_list_rule *r, struct hipac_rule *rule, int *len)
{
	int size = IPT_ALIGN(offsetof(struct nfhp_list_rule, r) + rule->size);
	u32 i;

	if (*len < size) {
		return NULL;
	}
	r->indev[0] = '\0';
	r->outdev[0] = '\0';
	memcpy(&r->r, rule, rule->size);

	/* TEMPORARY FIX: the current implementation produces incorrect
	                  counter values for jump rules; therefore we set
	                  the counters to 0 here to avoid confusion */
	if (HAS_CHAIN_TARGET(&r->r)) {
		r->r.packet_ct = r->r.byte_ct = 0;
	}
	
	/* fill in interface names if necessary */
	for (i = 0; i < r->r.native_mct; i++) {
		switch (r->r.first_match[i].dimid) {
		    case DIMID_INIFACE:
			    if (hpdev_get_name(r->r.first_match[i].left,
					       r->indev) < 0) {
				    printk(KERN_ERR "%s: interface name look"
					   "up failed\n", __FUNCTION__);
			    }
			    break;
		    case DIMID_OUTIFACE:
			    if (hpdev_get_name(r->r.first_match[i].left,
					       r->outdev) < 0) {
				    printk(KERN_ERR "%s: interface name look"
					   "up failed\n", __FUNCTION__);
			    }
			    break;
		}
	}

	/* prepare iptables matches/target for userspace */
	if (HAS_IPT_MATCH(&r->r)) {
		struct ipt_entry_match *m, *end;
		m = FIRST_IPT_MATCH(&r->r);
		end = IPT_MATCH_END(&r->r);
		for (; m < end; m = NEXT_IPT_MATCH(m)) {
			strncpy(m->u.user.name, m->u.kernel.match->name,
				sizeof(m->u.user.name));
		}
	}
	if (HAS_IPT_TARGET(&r->r)) {
		struct ipt_entry_target *t;
		t = IPT_TARGET(&r->r);
		strncpy(t->u.user.name, t->u.kernel.target->name,
			sizeof(t->u.user.name));
	}

	*len -= size;
	return (char *) r + size;
}

static inline void *
nlhp_list_chain(struct nfhp_list_chain *c, int pos, int *len)
{
	if (*len < sizeof(*c)) {
		return NULL;
	}
	strncpy(c->label, linfo.inf[pos].label, sizeof(c->label));
	c->label[sizeof(c->label) - 1] = '\0';
	c->policy = linfo.inf[pos].policy;
	c->rule_num = linfo.inf[pos].rule_num;
	/* TEMPORARY FIX: the current implementation produces incorrect
	                  counter values for chains; therefore we set
	                  the counters to 0 here to avoid confusion;
			  once the issue is fixed we replace the lines
			  by:
			  c->packet_ct = linfo.inf[pos].packet_ct;
			  c->byte_ct = linfo.inf[pos].byte_ct; */
	c->packet_ct = 0;
	c->byte_ct = 0;
	*len -= sizeof(*c);
	return c + 1;
}

static inline int
nlhp_list_next_rule(struct sk_buff *skb, struct hipac_rule *prev,
		    struct hipac_rule **rule, int pos)
{
	int stat;

	stat = hipac_get_next_rule(&linfo.inf[pos], prev, rule);
	switch (stat) {
	case HE_OK:
		return 0;
	case HE_RULE_NOT_EXISTENT:
		*rule = NULL;
		return 0;
	default:
		if (unlikely(stat > 0)) {
			/* this should never happen */
			printk(KERN_ERR "%s: hipac_get_next_rule returned "
			       "status > 0\n", __FUNCTION__);
			stat = -stat;
		}
		return stat;
	}
}

/* callback function for CMD_LIST and CMD_LIST_COUNTER command */
static int
nlhp_list(struct sk_buff *skb, struct netlink_callback *cb)
{
	static u32 pos;
	static struct hipac_rule *rule;
	struct nlmsghdr *nlh;
	int len, total, stat;
	void *data;

	total = skb_tailroom(skb) - NLMSG_SPACE(0);
	switch (cb->args[0]) {
	    case 0:
		    /* first callback in the series */
		    down(&nlhp_listing_lock);
		    pos = 0;
		    rule = NULL;
		    data = NLMSG_DATA(skb->data);
		    len = total;
		    cb->args[0] = 1;
		    break;
	    case 1:
		    /* pos, rule represent the current state */
		    data = NLMSG_DATA(skb->data);
		    len = total;
		    break;
	    default:
		    return 0;
	}

	while (1) {
		if (rule == NULL) {
			/* send chain info */
			data = nlhp_list_chain(data, pos, &len);
			if (data == NULL) {
				/* skb full - chain sent next time */
				break;
			}
			stat = nlhp_list_next_rule(skb, NULL, &rule, pos);
			if (stat < 0) {
				/* rule listing aborted due to error */
				return stat;
			}
		} else {
			/* send next rule */
			data = nlhp_list_rule(data, rule, &len);
			if (data == NULL) {
				/* skb full - rule sent next time */
				break;
			}
			stat = nlhp_list_next_rule(skb, rule, &rule, pos);
			if (stat < 0) {
				/* rule listing aborted due to error */
				return stat;
			}
		}
		if (rule == NULL) {
			if (++pos == linfo.len) {
				/* we are done */
				cb->args[0] = 2;
				break;
			}
		}
	}
	nlh = NLMSG_PUT(skb, peerpid, 0, NLHP_TYPE, total - len);
	nlh->nlmsg_flags |= NLM_F_MULTI;
	return NLMSG_SPACE(total - len);

 nlmsg_failure:
	skb_trim(skb, skb->tail - skb->data);
	return NFHE_ILL;
}

static int
nlhp_done(struct netlink_callback *cb)
{
	if (hipac_free_chain_infos(linfo.inf) != HE_OK) {
		/* this should never happen */
		printk(KERN_ERR "%s: hipac_free_chain_info failed\n",
		       __FUNCTION__);
	}
	linfo.inf = NULL;
	linfo.len = 0;
	if (cb->args[0]) {
		up(&nlhp_listing_lock);
	}
	return 0;
}

static void
do_cmd(struct sk_buff *skb, int msg_len, struct sk_buff *reply);

static inline void
nlhp_chk_user_skb(struct sk_buff *skb, struct sk_buff *reply)
{
	struct nlmsghdr *nlh = (struct nlmsghdr *) skb->data;

	if (skb->len < sizeof(struct nlmsghdr) ||
	    nlh->nlmsg_len < sizeof(struct nlmsghdr) ||
	    skb->len < nlh->nlmsg_len ||
	    nlh->nlmsg_pid <= 0 ||
	    nlh->nlmsg_type != NLHP_TYPE ||
	    nlh->nlmsg_flags & NLM_F_MULTI ||
	    !(nlh->nlmsg_flags & NLM_F_REQUEST) ||
	    !(nlh->nlmsg_flags & NLM_F_ACK)) {
		nlhp_send_reply(reply, NFHE_NOMSG, NULL);
		return;
	}
	if (nlh->nlmsg_flags & MSG_TRUNC) {
		nlhp_send_reply(reply, ERRNO_TO_NFHE(ECOMM), NULL);
		return;
	}
	if(!cap_raised(NETLINK_CB(skb).eff_cap, CAP_NET_ADMIN)) {
		nlhp_send_reply(reply, ERRNO_TO_NFHE(EPERM), NULL);
		return;
	}
	peerpid = nlh->nlmsg_pid;
	do_cmd(skb, skb->len - NLMSG_LENGTH(0), reply);
}

static void
nlhp_recv_user_skb(struct sock *sk, int len)
{
	struct sk_buff *skb, *reply;
	
	down(&nlhp_lock);
	while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
		reply = nlhp_new_reply();
		if (reply == NULL) {
			printk(KERN_ERR "reply skb cannot be allocated\n");
		} else {
			down(&nlhp_listing_lock);
			up(&nlhp_listing_lock);
			/* now we can be sure that we don't compete with
			   an ongoing listing operation */
			nlhp_chk_user_skb(skb, reply);
		}
		kfree_skb(skb);
	}
	up(&nlhp_lock);
}


/*
 * request handling
 */

static int
cmd_check_init_native_matches(struct nfhp_rule *rule, int inc_modct,
			      int *inc_cthelp)
{
	u32 dimbitv = 0;
	u8 frag_match = 0;
	u16 proto_match = 0;
	struct ipt_entry *e = NULL;
	struct hipac_rule *r = &rule->r;
	int i, ifindex, stat;
	u8 dimid, inv, devbf;
	u32 left, right;
	
	devbf = (rule->indev[0] != '\0');
	devbf |= (rule->outdev[0] != '\0') << 1;
	if (HAS_IPT_ENTRY(r)) {
		/* as nf-hipac does not care about ipt_entry we just fill in
		   the info needed by the existing modules */
		e = IPT_ENTRY(r);
		memset(&e->ip, 0, sizeof(e->ip));
		if (devbf & 1) {
			strncpy(e->ip.iniface, rule->indev,
				sizeof(e->ip.iniface));
			memset(e->ip.iniface_mask, 0xff,
			       strlen(rule->indev) + 1);
		}
		if (devbf & 2) {
			strncpy(e->ip.outiface, rule->outdev,
				sizeof(e->ip.outiface));
			memset(e->ip.outiface_mask, 0xff,
			       strlen(rule->outdev) + 1);
		}
		e->nfcache = 0;
		e->comefrom = 0;
		memset(&e->counters, 0, sizeof(e->counters));
	}
	for (i = 0; i < r->native_mct; i++) {
		r->first_match[i].invert = !!r->first_match[i].invert;
		dimid = r->first_match[i].dimid;
		inv = r->first_match[i].invert;
		left = r->first_match[i].left;
		right = r->first_match[i].right;
		if (i > 0 && dimid <= r->first_match[i - 1].dimid) {
			return NFHE_SORT;
		}
		if (left > right || right > hipac_maxkey(dim2btype[dimid])) {
			return NFHE_MINT;
		}
		dimbitv |= 1 << dimid;
		switch (dimid) {
		    case DIMID_INIFACE:
			    if (!(devbf & 1)) {
				    return NFHE_DEVA;
			    }
			    ifindex = hpdev_get_index(rule->indev);
			    if (ifindex < 0) {
				    return ifindex;
			    }
			    if (e != NULL && inv) {
				    e->ip.invflags |= IPT_INV_VIA_IN;
			    }
			    devbf &= 0xfe;
			    r->origin &= NFHP_ORIGIN_INPUT |
				    NFHP_ORIGIN_FORWARD;
			    /* initialize interface match */
			    r->first_match[i].left = ifindex;
			    r->first_match[i].right = ifindex;
			    break;
		    case DIMID_OUTIFACE:
			    if (!(devbf & 2)) {
				    return NFHE_DEVA;
			    }
			    ifindex = hpdev_get_index(rule->outdev);
			    if (ifindex < 0) {
				    return ifindex;
			    }
			    if (e != NULL && inv) {
				    e->ip.invflags |= IPT_INV_VIA_OUT;
			    }
			    devbf &= 0xfd;
			    r->origin &= NFHP_ORIGIN_OUTPUT |
				    NFHP_ORIGIN_FORWARD;
			    /* initialize interface match */
			    r->first_match[i].left = ifindex;
			    r->first_match[i].right = ifindex;
			    break;
		    case DIMID_PROTO:
			    if (left != right) {
				    return NFHE_PROTO;
			    }
			    if (!inv) {
				    proto_match = left;
			    }
			    if (e != NULL) {
				    e->ip.proto = r->first_match[i].left;
				    if (inv) {
					    e->ip.invflags |= IPT_INV_PROTO;
				    }
			    }
			    break;
		    case DIMID_FRAGMENT:
			    if (inv || (left != right && left == 0)) {
				    return NFHE_FRAG;
			    }
			    if (e != NULL) {
				    e->ip.flags = IPT_F_FRAG;
			    }
			    if (left > 0) {
				    r->first_match[i].left = 1;
				    r->first_match[i].right =
					    hipac_maxkey(dim2btype[dimid]);
			    } else {
				    frag_match = 1;
				    if (e != NULL) {
					    e->ip.invflags |= IPT_INV_FRAG;
				    }
			    }
			    break;
		    case DIMID_TCP_FLAGS:
			    if (inv || (left != right && left == 0)) {
				    return NFHE_TFLAGS;
			    }
			    if (left > 0) {
				    r->first_match[i].left = 1;
				    r->first_match[i].right =
					    hipac_maxkey(dim2btype[dimid]);
			    }
			    break;
		    case DIMID_STATE:
			    if (left != right ||
				left > NFHP_STATE_NUM_VALID + 1) {
				    return NFHE_STATE;
			    }
			    if (inc_modct) {
				    stat = cthelp_use();
				    if (stat < 0) {
					    return stat;
				    }
				    (*inc_cthelp)++;
			    }
			    break;
		}
	}
	if (devbf != 0) {
		return NFHE_DEVB;
	}

	/* check inter-match dependencies */
	if (dimbitv & (1 << DIMID_TCP_DPORT) ||
	    dimbitv & (1 << DIMID_TCP_SPORT) ||
	    dimbitv & (1 << DIMID_TCP_FLAGS)) {
		if (proto_match != IPPROTO_TCP || !frag_match ||
		    dimbitv & (1 << DIMID_UDP_DPORT) || 
		    dimbitv & (1 << DIMID_UDP_SPORT) ||
		    dimbitv & (1 << DIMID_ICMP_TYPE)) {
			return NFHE_TCP;
		}
	} else if (dimbitv & (1 << DIMID_UDP_DPORT) ||
		   dimbitv & (1 << DIMID_UDP_SPORT)) {
		if (proto_match != IPPROTO_UDP || !frag_match ||
		    dimbitv & (1 << DIMID_ICMP_TYPE)) {
			return NFHE_UDP;
		}
	} else if (dimbitv & (1 << DIMID_ICMP_TYPE) &&
		   (proto_match != IPPROTO_ICMP || !frag_match)) {
		return NFHE_ICMP;
	}
	return 0;
}

static inline u32
origin_to_hookmask(u32 origin)
{
	return (origin & NFHP_ORIGIN_INPUT ? 1 << NF_IP_LOCAL_IN : 0) |
	       (origin & NFHP_ORIGIN_FORWARD ? 1 << NF_IP_FORWARD : 0) |
	       (origin & NFHP_ORIGIN_OUTPUT ? 1 << NF_IP_LOCAL_OUT : 0);
}

static int
cmd_check_init_ipt_matches(struct hipac_rule *r, int len, u16 **cmp,
			   int *cmp_len, int inc_modct, int *num_done)
{
	struct ipt_entry_match *match;
	int stat;
	
	match = FIRST_IPT_MATCH_IE(r);
	while (len >= sizeof(*match) && len >= match->u.match_size) {
		if (match->u.match_size != IPT_ALIGN(match->u.match_size)) {
			return NFHE_IPTMSZ;
		}
		if (*cmp != NULL) {
			if (*cmp_len < sizeof(**cmp)) {
				return NFHE_CMPSH;
			}
			if (**cmp > match->u.match_size - sizeof(*match)) {
				return NFHE_CMPLA;
			}
		}

		/* this is really ugly but we have to hardcode the maximum
		   allowed origin bit vector since the actual origin of this
		   rule might change after another rule has been inserted;
		   in order to dynamically handle this setting a module would
		   be required to be asked for its maximum allowed origin bit
		   vector */
		if (!strcmp(match->u.user.name, "mac")) {
			r->origin &= NFHP_ORIGIN_FORWARD | NFHP_ORIGIN_INPUT;
		} else if (!strcmp(match->u.user.name, "owner")) {
			r->origin &= NFHP_ORIGIN_OUTPUT;
		} else if (!strcmp(match->u.user.name, "owner-socketlookup")) {
			r->origin &= NFHP_ORIGIN_INPUT | NFHP_ORIGIN_OUTPUT;
		}
		if (r->origin == 0) {
			return NFHE_ORIGIN;
		}
		
		if (inc_modct) {
			if (r->action == TARGET_CHAIN) {
				/* TEMPORARY FIX: since hipac currently treats
				     jump rules in a way that leads to problems
				     with stateful ipt_matches we restrict
				     ourselves to certain known stateless
				     ipt_matches; if you absolutely need a jump
				     rule with another stateless ipt_match you
				     are free to add its name here after but
				     make sure that it is __really__
				     stateless */
				if (strcmp(match->u.user.name, "ah") &&
				    strcmp(match->u.user.name, "dscp") &&
				    strcmp(match->u.user.name, "ecn") &&
				    strcmp(match->u.user.name, "esp") &&
				    strcmp(match->u.user.name, "length") &&
				    strcmp(match->u.user.name, "mac") &&
				    strcmp(match->u.user.name, "owner") &&
				    strcmp(match->u.user.name, "pkttype") &&
				    strcmp(match->u.user.name, "tcpmss") &&
				    strcmp(match->u.user.name, "tos") &&
				    strcmp(match->u.user.name, "unclean")) {
					return NFHE_IMPL;
				}
			}
			stat = ipt_init_match(match);
			if (stat < 0) {
				return ERRNO_TO_NFHE(-stat);
			}
			if (match->u.kernel.match->checkentry &&
			    !match->u.kernel.match->checkentry(
				    "filter", &IPT_ENTRY(r)->ip, match->data,
				    match->u.match_size - sizeof(*match),
				    origin_to_hookmask(r->origin))) {
				if (match->u.kernel.match->me) {
					__MOD_DEC_USE_COUNT(
						match->u.kernel.match->me);
				}
				return NFHE_IPTMCH;
			}
		}
		(*num_done)++;
		len -= match->u.match_size;
		match = NEXT_IPT_MATCH(match);
		if (*cmp != NULL) {
			(*cmp_len) -= sizeof(**cmp);
			(*cmp)++;
		}
	}
	if (len > 0) {
		return NFHE_TOFF;
	}
	return 0;
}

static int
cmd_check_init_ipt_target(struct hipac_rule *r, int len, u16 *cmp,
			  int cmp_len, int inc_modct, int *done)
{
	struct ipt_entry_target *target;
	int stat;
	
	target = IPT_TARGET_IE(r);
	if (len < sizeof(*target) || len < target->u.target_size ||
	    target->u.target_size != IPT_ALIGN(target->u.target_size)) {
		return NFHE_IPTTSZ;
	}
	if (cmp != NULL) {
		if (cmp_len < sizeof(*cmp)) {
			return NFHE_CMPSH;
		}
		if (*cmp > target->u.target_size - sizeof(*target)) {
			return NFHE_CMPLA;
		}
	}

	/* this is really ugly but we have to hardcode the maximum allowed
	   origin bit vector since the actual origin of this rule might
	   change after another rule has been inserted;
	   in order to dynamically handle this setting a module would be 
	   required to be asked for its maximum allowed origin bit vector */
	if (!strcmp(target->u.user.name, "MIRROR")) {
		r->origin &= NFHP_ORIGIN_FORWARD | NFHP_ORIGIN_INPUT;
	} else if (!strcmp(target->u.user.name, "TCPMSS")) {
		r->origin &= NFHP_ORIGIN_FORWARD | NFHP_ORIGIN_OUTPUT;
	} else if (!strcmp(target->u.user.name, "TARPIT")) {
		r->origin &= NFHP_ORIGIN_FORWARD | NFHP_ORIGIN_INPUT;
	}
	if (r->origin == 0) {
		return NFHE_ORIGIN;
	}

	if (inc_modct) {
		stat = ipt_init_target(target);
		if (stat < 0) {
			return ERRNO_TO_NFHE(-stat);
		}
		if (target->u.kernel.target->checkentry &&
		    !target->u.kernel.target->checkentry(
			    "filter", IPT_ENTRY(r), target->data,
			    target->u.target_size - sizeof(*target),
			    origin_to_hookmask(r->origin))) {
			if (target->u.kernel.target->me) {
				__MOD_DEC_USE_COUNT(
					target->u.kernel.target->me);
			}
			return NFHE_IPTTCH;
		}
	}
	(*done)++;
	len -= target->u.target_size;
	if (len > 0) {
		/* netlink message contains unnecessary padding between the
		   end of the ipt target and the beginning of the cmp_len
		   array */
		r->size -= len;
	}
	return 0;
}

static void
cmd_cleanup(struct hipac_rule *r, int inc_cthelp, int num_matches, int target)
{
	struct ipt_entry_match *m;
	struct ipt_entry_target *t;
	int i;

	if (inc_cthelp) {
		cthelp_unuse();
	}
	for (m = FIRST_IPT_MATCH_IE(r), i = 0; i < num_matches;
	     m = NEXT_IPT_MATCH(m), i++) {
		if (m->u.kernel.match->destroy) {
			m->u.kernel.match->destroy(m->data, m->u.match_size -
						   sizeof(*m));
		}
		if (m->u.kernel.match->me) {
			__MOD_DEC_USE_COUNT(m->u.kernel.match->me);
		}
	}
	if (target) {
		t = IPT_TARGET_IE(r);
		if (t->u.kernel.target->destroy) {
			t->u.kernel.target->destroy(
				t->data, t->u.target_size - sizeof(*t));
		}
		if (t->u.kernel.target->me) {
			__MOD_DEC_USE_COUNT(t->u.kernel.target->me);
		}
	}
}

static int
cmd_check_init(struct nfhp_cmd *cmd, int msg_len)
{
	u16 *cmp = NULL;
	int inc_cthelp = 0, num_matches = 0, target = 0, cmp_len = 0;
	int stat, len, inc_modct;
	struct hipac_rule *r;
	u32 c;
	
	if (msg_len < sizeof(*cmd)) {
		return NFHE_NOMSG;
	}

	/* basic checks */
	c = cmd->cmd;
	inc_modct = (c == CMD_APPEND || c == CMD_INSERT || c == CMD_REPLACE);
	if (c < CMD_MIN || c > CMD_MAX) {
		return NFHE_CMD;
	}
	cmd->chain.label[HIPAC_CHAIN_NAME_MAX_LEN - 1] = '\0';
	cmd->chain.newlabel[HIPAC_CHAIN_NAME_MAX_LEN - 1] = '\0';
	if (cmd->chain.label[0] == '\0' &&
	    !(c == CMD_ZERO_COUNTERS || c == CMD_FLUSH ||
	      c == CMD_DELETE_CHAIN || c == CMD_LIST ||
	      c == CMD_LIST_COUNTER)) {
		return NFHE_LABEL;
	}
	if (c == CMD_RENAME_CHAIN && cmd->chain.newlabel[0] == '\0') {
		return NFHE_NLABEL;
	}
	if (c == CMD_SET_POLICY && cmd->chain.policy != TARGET_ACCEPT &&
	    cmd->chain.policy != TARGET_DROP) {
		return NFHE_POLICY;
	}
	if (!(c == CMD_APPEND || c == CMD_INSERT || c == CMD_DELETE_RULE ||
	      c == CMD_DELETE_POS || c == CMD_REPLACE)) {
		/* we are finished since cmd->rule is irrelevant */
		return 0;
	}

	/* rule checks */
	r = &cmd->rule.r;
	cmd->rule.indev[IFNAMSIZ - 1] = '\0';
	cmd->rule.outdev[IFNAMSIZ - 1] = '\0';
	/* r->packet_ct and r->byte_ct may be set freely */
	r->origin = NFHP_ORIGIN_INPUT | NFHP_ORIGIN_FORWARD |
		NFHP_ORIGIN_OUTPUT;
	/* TEMPORARY FIX: TARGET_RETURN is not yet implemented */
	if (r->action == TARGET_RETURN) {
		return NFHE_IMPL;
	}
	if (!(r->action == TARGET_ACCEPT || r->action == TARGET_DROP ||
	      r->action == TARGET_NONE || r->action == TARGET_RETURN ||
	      r->action == TARGET_EXEC || r->action == TARGET_CHAIN)) {
		return NFHE_ACTION;
	}
	if (r->native_mct > NUMBER_OF_DIM) {
		return NFHE_NMCT;
	}
	if (HAS_IPT_ENTRY(r)) {
		struct ipt_entry *e = IPT_ENTRY(r);
		if (e->target_offset > e->next_offset ||
		    e->target_offset < sizeof(*e) ||
		    offsetof(struct nfhp_cmd, rule.e) + 
		    e->next_offset > msg_len) {
			return NFHE_IEOFF;
		}
		/* assume target_offset/next_offset are correct; if they prove
		   to be wrong we reject the packet anyway;
		   the values are chosen to be correct for the target of 
		   hipac_copy_constructor */
		if (HAS_IPT_MATCH(r)) {
			r->match_offset = IPT_ALIGN(sizeof(*r) +
						    r->native_mct *
						    sizeof(*r->first_match));
			r->target_offset = (e->target_offset - sizeof(*e)) +
				r->match_offset;
		} else {
			if (e->target_offset != sizeof(*e)) {
				return NFHE_IEOFF;
			}
			r->match_offset = 0;
			if (HAS_CHAIN_TARGET(r)) {
				r->target_offset = sizeof(*r) + r->native_mct *
					sizeof(*r->first_match);
			} else {
				r->target_offset =
					IPT_ALIGN(sizeof(*r) +
						  r->native_mct *
						  sizeof(*r->first_match));
			}
		}
		r->size = (e->next_offset - sizeof(*e)) + r->target_offset;
	} else {
		/* no iptables matches/target, no chain target */
		r->size = sizeof(*r) + r->native_mct * sizeof(*r->first_match);
		r->match_offset = r->target_offset = 0;
	}

       	/* check the native matches */
	stat = cmd_check_init_native_matches(&cmd->rule, inc_modct,
					     &inc_cthelp);
	if (stat < 0) {
		goto error;
	}

	/* set maximum size of cmp_len based on r->size */
	if (HAS_CMP_LEN(c, r)) {
		cmp = CMP_LEN(r);
		cmp_len = msg_len - ((char *) cmp - (char *) cmd);
		if (cmp_len <= 0) {
			stat = NFHE_CMPMIS;
			goto error;
		}
	}

	/* check and initialize ipt matches */
	if (HAS_IPT_MATCH(r)) {
		len = r->target_offset - r->match_offset;
		stat = cmd_check_init_ipt_matches(r, len, &cmp, &cmp_len,
						  inc_modct, &num_matches);
		if (stat < 0) {
			goto error;
		}
	}

	/* check and initialize ipt target / chain target */
	if (HAS_IPT_TARGET(r)) {
		len = r->size - r->target_offset;
		if (len <= 0) {
			stat = NFHE_IPTTMI;
			goto error;
		}
		stat = cmd_check_init_ipt_target(r, len, cmp, cmp_len,
						 inc_modct, &target);
		if (stat < 0) {
			goto error;
		}
	} else if (HAS_CHAIN_TARGET(r)) {
		char *chain = CHAIN_TARGET_IE(r);
		u32 real_len;
		len = r->size - r->target_offset;
		if (len <= 0 || chain[0] == '\0') {
			stat = NFHE_CHAINE;
			goto error;
		}
		real_len = strnlen(chain, len);
		if (len > HIPAC_CHAIN_NAME_MAX_LEN || real_len == len) {
			stat = NFHE_CHAINL;
			goto error;
		}
		/* we have to reserve HIPAC_CHAIN_NAME_MAX_LEN bytes for
		   the chain label */
		r->size += HIPAC_CHAIN_NAME_MAX_LEN - len;
	}

	/* rule _syntactically_ correct; it might still be invalid because
	   of a violation of the hipac semantics */
	return 0;

 error:
	cmd_cleanup(r, inc_cthelp, num_matches, target);
	return stat;
}

static void
do_cmd(struct sk_buff *skb, int msg_len, struct sk_buff *reply)
{
	struct nlmsghdr *nlh = (struct nlmsghdr *) skb->data;
	struct nfhp_cmd *cmd = (struct nfhp_cmd *) NLMSG_DATA(nlh);
	char *chain_label;
	int stat;

	stat = cmd_check_init(cmd, msg_len);
	if (stat < 0) {
		nlhp_send_reply(reply, stat, NULL);
		return;
	}
	if (cmd->chain.label[0] == '\0') {
		chain_label = NULL;
	} else {
		chain_label = cmd->chain.label;
	}

	switch (cmd->cmd) {
	    case CMD_APPEND:
		    stat = hipac_append(chain_label, &cmd->rule.r);
		    if (stat != HE_OK) {
			    hipac_destroy_exec_nl(&cmd->rule.r);
		    }
		    nlhp_send_reply(reply, stat, hipac_get_error_message());
		    break;
	    case CMD_INSERT:
		    stat = hipac_insert(chain_label, &cmd->rule.r);
		    if (stat != HE_OK) {
			    hipac_destroy_exec_nl(&cmd->rule.r);
		    }
		    nlhp_send_reply(reply, stat, hipac_get_error_message());
		    break;
	    case CMD_DELETE_RULE:
		    stat = hipac_delete(chain_label, &cmd->rule.r);
		    nlhp_send_reply(reply, stat, hipac_get_error_message());
		    break;
	    case CMD_DELETE_POS:
		    stat = hipac_delete_pos(chain_label, cmd->rule.r.pos);
		    nlhp_send_reply(reply, stat, hipac_get_error_message());
		    break;
	    case CMD_REPLACE:
		    stat = hipac_replace(chain_label, &cmd->rule.r);
		    if (stat != HE_OK) {
			    hipac_destroy_exec_nl(&cmd->rule.r);
		    }
		    nlhp_send_reply(reply, stat, hipac_get_error_message());
		    break;
	    case CMD_ZERO_COUNTERS:
		    stat = hipac_zero_counters(chain_label);
		    nlhp_send_reply(reply, stat, hipac_get_error_message());
		    break;
	    case CMD_FLUSH:
		    stat = hipac_flush_chain(chain_label);
		    nlhp_send_reply(reply, stat, hipac_get_error_message());
		    break;
	    case CMD_NEW_CHAIN:
		    stat = hipac_new_chain(chain_label);
		    nlhp_send_reply(reply, stat, hipac_get_error_message());
		    break;
	    case CMD_DELETE_CHAIN:
		    stat = hipac_delete_chain(chain_label);
		    nlhp_send_reply(reply, stat, hipac_get_error_message());
		    break;
	    case CMD_RENAME_CHAIN:
		    stat = hipac_rename_chain(chain_label,
					      cmd->chain.newlabel);
		    nlhp_send_reply(reply, stat, hipac_get_error_message());
		    break;
	    case CMD_SET_POLICY:
		    stat = hipac_set_policy(chain_label, cmd->chain.policy);
		    nlhp_send_reply(reply, stat, hipac_get_error_message());
		    break;
	    case CMD_LIST:
	    case CMD_LIST_COUNTER: {
		    struct netlink_callback cb = {skb, nlh, nlhp_list,
						  nlhp_done, PF_NETLINK, {0},
						  reply, reply_skb_size};
		    stat = hipac_get_chain_infos(chain_label,
						 cmd->cmd == CMD_LIST_COUNTER,
						 &linfo.inf, &linfo.len);
		    if (stat < 0) {
			    nlhp_send_list_err(reply, stat);
			    return;
		    }
		    if (SKB_LEN(reply) < reply_skb_size - NLMSG_LENGTH(0)) {
			    cb.reply = NULL;
			    kfree_skb(reply);
		    }
		    if (netlink_dump_start_cb(nfhp_sock, &cb) < 0) {
			    printk(KERN_ERR "netlink_dump_start_cb failed\n");
			    kfree_skb(reply);
		    }
		    break;
	    }
	    default:
		    printk(KERN_ERR "invalid command type although "
			   "cmd_check_init reported a valid command\n");
		    nlhp_send_reply(reply, NFHE_NOMSG, NULL);
		    break;
	}
}


/*
 * initialization, finalization
 */

static int
__init init(void)
{
	struct sysinfo sys;
	u64 total_mem;
	int ret;

	si_meminfo(&sys);
	total_mem = (u64) sys.totalram << PAGE_SHIFT;

	/* initialize hipac layer */
	if (hipac_init(dim2btype, extract,
		       sizeof(dim2btype) / sizeof(*dim2btype),
		       hipac_copy_constructor, hipac_destroy_exec,
		       hipac_match_exec, hipac_target_exec, hipac_eq_exec,
		       total_mem >> 1) != HE_OK) {
		printk(KERN_ERR "nf_hipac: initialization failed: unable to "
		       "initialize hipac algorithm\n");
		return -ENOMEM;
	}
	if (hipac_new("INPUT", "__/INPUT_INTERN\\__", TARGET_ACCEPT,
		      NFHP_ORIGIN_INPUT, &hipac_input) != HE_OK) {
		printk(KERN_ERR "nf_hipac: initialization failed: unable to "
		       "create hipac data structure for input hook\n");
		ret = -ENOMEM;
		goto cleanup_hipac;
	}
	if (hipac_new("FORWARD", "__/FORWARD_INTERN\\__", TARGET_ACCEPT,
		      NFHP_ORIGIN_FORWARD, &hipac_forward) != HE_OK) {
		printk(KERN_ERR "nf_hipac: initialization failed: unable to "
		       "create hipac data structure for forward hook\n");
		ret = -ENOMEM;
		goto cleanup_hipac;
	}
	if (hipac_new("OUTPUT", "__/OUTPUT_INTERN\\__", TARGET_ACCEPT,
		      NFHP_ORIGIN_OUTPUT, &hipac_output) != HE_OK) {
		printk(KERN_ERR "nf_hipac: initialization failed: unable to "
		       "create hipac data structure for output hook\n");
		ret = -ENOMEM;
		goto cleanup_hipac;
	}

	/* register to netfilter */
	if ((ret = nf_register_hook(&input_op)) < 0) {
		printk(KERN_ERR "nf_hipac: initialization failed: unable to "
		       "register input hook\n");
		goto cleanup_hipac;
	}
	if ((ret = nf_register_hook(&forward_op)) < 0) {
		printk(KERN_ERR "nf_hipac: initialization failed: unable to "
		       "register forward hook\n");
		goto cleanup_input;
	}
	if ((ret = nf_register_hook(&output_op)) < 0) {
		printk(KERN_ERR "nf_hipac: initialization failed: unable to "
		       "register output hook\n");
		goto cleanup_forward;
	}

	/* initialize interface manager */
	if ((ret = hpdev_init()) != 0) {
		printk(KERN_ERR "nf_hipac: initialization failed: unable to "
		       "initialize device management\n");
		goto cleanup_output;
	}

	/* initialize proc interface */
	hpproc_init(total_mem);

	/* increment ip_tables' module counter */
	ret = ipt_register_target(&nfhp_dummy_target);
	if (ret < 0) {
		goto cleanup_hpproc;
	}

	/* enable netlink user communication */
	nfhp_sock = netlink_kernel_create(NLHP_PROTO, nlhp_recv_user_skb);
	if (nfhp_sock == NULL) {
		printk(KERN_ERR "nf_hipac: initialization failed: unable to "
		       "create kernel netlink socket\n");
		ret = -ENOMEM;
		goto cleanup_ipt;
	}

	printk(KERN_INFO "nf_hipac: (C) 2002-2003 HIPAC core team "
	       "(Michael Bellion, Thomas Heinz)\n");
	return 0;

 cleanup_ipt:
	ipt_unregister_target(&nfhp_dummy_target);
 cleanup_hpproc:
	hpproc_exit();
	hpdev_exit();
 cleanup_output:
	nf_unregister_hook(&output_op);
 cleanup_forward:
	nf_unregister_hook(&forward_op);
 cleanup_input:
	nf_unregister_hook(&input_op);
 cleanup_hipac:
	hipac_exit();
	return ret;	
}

static void
__exit fini(void)
{
	if (nfhp_sock == NULL ||
	    nfhp_sock->socket == NULL) {
		/* this should never happen but in fact it did once on a
		   sparc64 */
		printk(KERN_ERR "nfhp_sock is broken\n");
	} else {
		sock_release(nfhp_sock->socket);
	}
	ipt_unregister_target(&nfhp_dummy_target);
	hpproc_exit();
	hpdev_exit();
	nf_unregister_hook(&input_op);
	nf_unregister_hook(&forward_op);
	nf_unregister_hook(&output_op);
	hipac_exit();
}


module_init(init);
module_exit(fini);
MODULE_AUTHOR("Michael Bellion and Thomas Heinz");
MODULE_DESCRIPTION("NF-HIPAC - netfilter high performance "
		   "packet classification");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 10)
MODULE_LICENSE("GPL");
#endif

EXPORT_SYMBOL(nfhp_register_cthelp);
EXPORT_SYMBOL(nfhp_unregister_cthelp);
