| /* iptables module for using new netfilter netlink queue | 
 |  * | 
 |  * (C) 2005 by Harald Welte <laforge@netfilter.org> | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License version 2 as | 
 |  * published by the Free Software Foundation. | 
 |  * | 
 |  */ | 
 |  | 
 | #include <linux/module.h> | 
 | #include <linux/skbuff.h> | 
 |  | 
 | #include <linux/ip.h> | 
 | #include <linux/ipv6.h> | 
 | #include <linux/jhash.h> | 
 |  | 
 | #include <linux/netfilter.h> | 
 | #include <linux/netfilter_arp.h> | 
 | #include <linux/netfilter/x_tables.h> | 
 | #include <linux/netfilter/xt_NFQUEUE.h> | 
 |  | 
 | MODULE_AUTHOR("Harald Welte <laforge@netfilter.org>"); | 
 | MODULE_DESCRIPTION("Xtables: packet forwarding to netlink"); | 
 | MODULE_LICENSE("GPL"); | 
 | MODULE_ALIAS("ipt_NFQUEUE"); | 
 | MODULE_ALIAS("ip6t_NFQUEUE"); | 
 | MODULE_ALIAS("arpt_NFQUEUE"); | 
 |  | 
 | static u32 jhash_initval __read_mostly; | 
 | static bool rnd_inited __read_mostly; | 
 |  | 
 | static unsigned int | 
 | nfqueue_tg(struct sk_buff *skb, const struct xt_action_param *par) | 
 | { | 
 | 	const struct xt_NFQ_info *tinfo = par->targinfo; | 
 |  | 
 | 	return NF_QUEUE_NR(tinfo->queuenum); | 
 | } | 
 |  | 
 | static u32 hash_v4(const struct sk_buff *skb) | 
 | { | 
 | 	const struct iphdr *iph = ip_hdr(skb); | 
 |  | 
 | 	/* packets in either direction go into same queue */ | 
 | 	if ((__force u32)iph->saddr < (__force u32)iph->daddr) | 
 | 		return jhash_3words((__force u32)iph->saddr, | 
 | 			(__force u32)iph->daddr, iph->protocol, jhash_initval); | 
 |  | 
 | 	return jhash_3words((__force u32)iph->daddr, | 
 | 			(__force u32)iph->saddr, iph->protocol, jhash_initval); | 
 | } | 
 |  | 
 | #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) | 
 | static u32 hash_v6(const struct sk_buff *skb) | 
 | { | 
 | 	const struct ipv6hdr *ip6h = ipv6_hdr(skb); | 
 | 	u32 a, b, c; | 
 |  | 
 | 	if ((__force u32)ip6h->saddr.s6_addr32[3] < | 
 | 	    (__force u32)ip6h->daddr.s6_addr32[3]) { | 
 | 		a = (__force u32) ip6h->saddr.s6_addr32[3]; | 
 | 		b = (__force u32) ip6h->daddr.s6_addr32[3]; | 
 | 	} else { | 
 | 		b = (__force u32) ip6h->saddr.s6_addr32[3]; | 
 | 		a = (__force u32) ip6h->daddr.s6_addr32[3]; | 
 | 	} | 
 |  | 
 | 	if ((__force u32)ip6h->saddr.s6_addr32[1] < | 
 | 	    (__force u32)ip6h->daddr.s6_addr32[1]) | 
 | 		c = (__force u32) ip6h->saddr.s6_addr32[1]; | 
 | 	else | 
 | 		c = (__force u32) ip6h->daddr.s6_addr32[1]; | 
 |  | 
 | 	return jhash_3words(a, b, c, jhash_initval); | 
 | } | 
 | #endif | 
 |  | 
 | static unsigned int | 
 | nfqueue_tg_v1(struct sk_buff *skb, const struct xt_action_param *par) | 
 | { | 
 | 	const struct xt_NFQ_info_v1 *info = par->targinfo; | 
 | 	u32 queue = info->queuenum; | 
 |  | 
 | 	if (info->queues_total > 1) { | 
 | 		if (par->family == NFPROTO_IPV4) | 
 | 			queue = (((u64) hash_v4(skb) * info->queues_total) >> | 
 | 				 32) + queue; | 
 | #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) | 
 | 		else if (par->family == NFPROTO_IPV6) | 
 | 			queue = (((u64) hash_v6(skb) * info->queues_total) >> | 
 | 				 32) + queue; | 
 | #endif | 
 | 	} | 
 | 	return NF_QUEUE_NR(queue); | 
 | } | 
 |  | 
 | static unsigned int | 
 | nfqueue_tg_v2(struct sk_buff *skb, const struct xt_action_param *par) | 
 | { | 
 | 	const struct xt_NFQ_info_v2 *info = par->targinfo; | 
 | 	unsigned int ret = nfqueue_tg_v1(skb, par); | 
 |  | 
 | 	if (info->bypass) | 
 | 		ret |= NF_VERDICT_FLAG_QUEUE_BYPASS; | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int nfqueue_tg_check(const struct xt_tgchk_param *par) | 
 | { | 
 | 	const struct xt_NFQ_info_v2 *info = par->targinfo; | 
 | 	u32 maxid; | 
 |  | 
 | 	if (unlikely(!rnd_inited)) { | 
 | 		get_random_bytes(&jhash_initval, sizeof(jhash_initval)); | 
 | 		rnd_inited = true; | 
 | 	} | 
 | 	if (info->queues_total == 0) { | 
 | 		pr_err("NFQUEUE: number of total queues is 0\n"); | 
 | 		return -EINVAL; | 
 | 	} | 
 | 	maxid = info->queues_total - 1 + info->queuenum; | 
 | 	if (maxid > 0xffff) { | 
 | 		pr_err("NFQUEUE: number of queues (%u) out of range (got %u)\n", | 
 | 		       info->queues_total, maxid); | 
 | 		return -ERANGE; | 
 | 	} | 
 | 	if (par->target->revision == 2 && info->bypass > 1) | 
 | 		return -EINVAL; | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct xt_target nfqueue_tg_reg[] __read_mostly = { | 
 | 	{ | 
 | 		.name		= "NFQUEUE", | 
 | 		.family		= NFPROTO_UNSPEC, | 
 | 		.target		= nfqueue_tg, | 
 | 		.targetsize	= sizeof(struct xt_NFQ_info), | 
 | 		.me		= THIS_MODULE, | 
 | 	}, | 
 | 	{ | 
 | 		.name		= "NFQUEUE", | 
 | 		.revision	= 1, | 
 | 		.family		= NFPROTO_UNSPEC, | 
 | 		.checkentry	= nfqueue_tg_check, | 
 | 		.target		= nfqueue_tg_v1, | 
 | 		.targetsize	= sizeof(struct xt_NFQ_info_v1), | 
 | 		.me		= THIS_MODULE, | 
 | 	}, | 
 | 	{ | 
 | 		.name		= "NFQUEUE", | 
 | 		.revision	= 2, | 
 | 		.family		= NFPROTO_UNSPEC, | 
 | 		.checkentry	= nfqueue_tg_check, | 
 | 		.target		= nfqueue_tg_v2, | 
 | 		.targetsize	= sizeof(struct xt_NFQ_info_v2), | 
 | 		.me		= THIS_MODULE, | 
 | 	}, | 
 | }; | 
 |  | 
 | static int __init nfqueue_tg_init(void) | 
 | { | 
 | 	return xt_register_targets(nfqueue_tg_reg, ARRAY_SIZE(nfqueue_tg_reg)); | 
 | } | 
 |  | 
 | static void __exit nfqueue_tg_exit(void) | 
 | { | 
 | 	xt_unregister_targets(nfqueue_tg_reg, ARRAY_SIZE(nfqueue_tg_reg)); | 
 | } | 
 |  | 
 | module_init(nfqueue_tg_init); | 
 | module_exit(nfqueue_tg_exit); |