diff options
Diffstat (limited to 'net/netfilter/xt_HMARK.c')
| -rw-r--r-- | net/netfilter/xt_HMARK.c | 372 | 
1 files changed, 372 insertions, 0 deletions
diff --git a/net/netfilter/xt_HMARK.c b/net/netfilter/xt_HMARK.c new file mode 100644 index 00000000000..73b73f687c5 --- /dev/null +++ b/net/netfilter/xt_HMARK.c @@ -0,0 +1,372 @@ +/* + * xt_HMARK - Netfilter module to set mark by means of hashing + * + * (C) 2012 by Hans Schillstrom <hans.schillstrom@ericsson.com> + * (C) 2012 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 version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/skbuff.h> +#include <linux/icmp.h> + +#include <linux/netfilter/x_tables.h> +#include <linux/netfilter/xt_HMARK.h> + +#include <net/ip.h> +#if IS_ENABLED(CONFIG_NF_CONNTRACK) +#include <net/netfilter/nf_conntrack.h> +#endif +#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) +#include <net/ipv6.h> +#include <linux/netfilter_ipv6/ip6_tables.h> +#endif + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Hans Schillstrom <hans.schillstrom@ericsson.com>"); +MODULE_DESCRIPTION("Xtables: packet marking using hash calculation"); +MODULE_ALIAS("ipt_HMARK"); +MODULE_ALIAS("ip6t_HMARK"); + +struct hmark_tuple { +	__be32			src; +	__be32			dst; +	union hmark_ports	uports; +	u8			proto; +}; + +static inline __be32 hmark_addr6_mask(const __be32 *addr32, const __be32 *mask) +{ +	return (addr32[0] & mask[0]) ^ +	       (addr32[1] & mask[1]) ^ +	       (addr32[2] & mask[2]) ^ +	       (addr32[3] & mask[3]); +} + +static inline __be32 +hmark_addr_mask(int l3num, const __be32 *addr32, const __be32 *mask) +{ +	switch (l3num) { +	case AF_INET: +		return *addr32 & *mask; +	case AF_INET6: +		return hmark_addr6_mask(addr32, mask); +	} +	return 0; +} + +static inline void hmark_swap_ports(union hmark_ports *uports, +				    const struct xt_hmark_info *info) +{ +	union hmark_ports hp; +	u16 src, dst; + +	hp.b32 = (uports->b32 & info->port_mask.b32) | info->port_set.b32; +	src = ntohs(hp.b16.src); +	dst = ntohs(hp.b16.dst); + +	if (dst > src) +		uports->v32 = (dst << 16) | src; +	else +		uports->v32 = (src << 16) | dst; +} + +static int +hmark_ct_set_htuple(const struct sk_buff *skb, struct hmark_tuple *t, +		    const struct xt_hmark_info *info) +{ +#if IS_ENABLED(CONFIG_NF_CONNTRACK) +	enum ip_conntrack_info ctinfo; +	struct nf_conn *ct = nf_ct_get(skb, &ctinfo); +	struct nf_conntrack_tuple *otuple; +	struct nf_conntrack_tuple *rtuple; + +	if (ct == NULL || nf_ct_is_untracked(ct)) +		return -1; + +	otuple = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; +	rtuple = &ct->tuplehash[IP_CT_DIR_REPLY].tuple; + +	t->src = hmark_addr_mask(otuple->src.l3num, otuple->src.u3.ip6, +				 info->src_mask.ip6); +	t->dst = hmark_addr_mask(otuple->src.l3num, rtuple->src.u3.ip6, +				 info->dst_mask.ip6); + +	if (info->flags & XT_HMARK_FLAG(XT_HMARK_METHOD_L3)) +		return 0; + +	t->proto = nf_ct_protonum(ct); +	if (t->proto != IPPROTO_ICMP) { +		t->uports.b16.src = otuple->src.u.all; +		t->uports.b16.dst = rtuple->src.u.all; +		hmark_swap_ports(&t->uports, info); +	} + +	return 0; +#else +	return -1; +#endif +} + +/* This hash function is endian independent, to ensure consistent hashing if + * the cluster is composed of big and little endian systems. */ +static inline u32 +hmark_hash(struct hmark_tuple *t, const struct xt_hmark_info *info) +{ +	u32 hash; +	u32 src = ntohl(t->src); +	u32 dst = ntohl(t->dst); + +	if (dst < src) +		swap(src, dst); + +	hash = jhash_3words(src, dst, t->uports.v32, info->hashrnd); +	hash = hash ^ (t->proto & info->proto_mask); + +	return (((u64)hash * info->hmodulus) >> 32) + info->hoffset; +} + +static void +hmark_set_tuple_ports(const struct sk_buff *skb, unsigned int nhoff, +		      struct hmark_tuple *t, const struct xt_hmark_info *info) +{ +	int protoff; + +	protoff = proto_ports_offset(t->proto); +	if (protoff < 0) +		return; + +	nhoff += protoff; +	if (skb_copy_bits(skb, nhoff, &t->uports, sizeof(t->uports)) < 0) +		return; + +	hmark_swap_ports(&t->uports, info); +} + +#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) +static int get_inner6_hdr(const struct sk_buff *skb, int *offset) +{ +	struct icmp6hdr *icmp6h, _ih6; + +	icmp6h = skb_header_pointer(skb, *offset, sizeof(_ih6), &_ih6); +	if (icmp6h == NULL) +		return 0; + +	if (icmp6h->icmp6_type && icmp6h->icmp6_type < 128) { +		*offset += sizeof(struct icmp6hdr); +		return 1; +	} +	return 0; +} + +static int +hmark_pkt_set_htuple_ipv6(const struct sk_buff *skb, struct hmark_tuple *t, +			  const struct xt_hmark_info *info) +{ +	struct ipv6hdr *ip6, _ip6; +	int flag = IP6_FH_F_AUTH; +	unsigned int nhoff = 0; +	u16 fragoff = 0; +	int nexthdr; + +	ip6 = (struct ipv6hdr *) (skb->data + skb_network_offset(skb)); +	nexthdr = ipv6_find_hdr(skb, &nhoff, -1, &fragoff, &flag); +	if (nexthdr < 0) +		return 0; +	/* No need to check for icmp errors on fragments */ +	if ((flag & IP6_FH_F_FRAG) || (nexthdr != IPPROTO_ICMPV6)) +		goto noicmp; +	/* Use inner header in case of ICMP errors */ +	if (get_inner6_hdr(skb, &nhoff)) { +		ip6 = skb_header_pointer(skb, nhoff, sizeof(_ip6), &_ip6); +		if (ip6 == NULL) +			return -1; +		/* If AH present, use SPI like in ESP. */ +		flag = IP6_FH_F_AUTH; +		nexthdr = ipv6_find_hdr(skb, &nhoff, -1, &fragoff, &flag); +		if (nexthdr < 0) +			return -1; +	} +noicmp: +	t->src = hmark_addr6_mask(ip6->saddr.s6_addr32, info->src_mask.ip6); +	t->dst = hmark_addr6_mask(ip6->daddr.s6_addr32, info->dst_mask.ip6); + +	if (info->flags & XT_HMARK_FLAG(XT_HMARK_METHOD_L3)) +		return 0; + +	t->proto = nexthdr; +	if (t->proto == IPPROTO_ICMPV6) +		return 0; + +	if (flag & IP6_FH_F_FRAG) +		return 0; + +	hmark_set_tuple_ports(skb, nhoff, t, info); +	return 0; +} + +static unsigned int +hmark_tg_v6(struct sk_buff *skb, const struct xt_action_param *par) +{ +	const struct xt_hmark_info *info = par->targinfo; +	struct hmark_tuple t; + +	memset(&t, 0, sizeof(struct hmark_tuple)); + +	if (info->flags & XT_HMARK_FLAG(XT_HMARK_CT)) { +		if (hmark_ct_set_htuple(skb, &t, info) < 0) +			return XT_CONTINUE; +	} else { +		if (hmark_pkt_set_htuple_ipv6(skb, &t, info) < 0) +			return XT_CONTINUE; +	} + +	skb->mark = hmark_hash(&t, info); +	return XT_CONTINUE; +} +#endif + +static int get_inner_hdr(const struct sk_buff *skb, int iphsz, int *nhoff) +{ +	const struct icmphdr *icmph; +	struct icmphdr _ih; + +	/* Not enough header? */ +	icmph = skb_header_pointer(skb, *nhoff + iphsz, sizeof(_ih), &_ih); +	if (icmph == NULL || icmph->type > NR_ICMP_TYPES) +		return 0; + +	/* Error message? */ +	if (icmph->type != ICMP_DEST_UNREACH && +	    icmph->type != ICMP_SOURCE_QUENCH && +	    icmph->type != ICMP_TIME_EXCEEDED && +	    icmph->type != ICMP_PARAMETERPROB && +	    icmph->type != ICMP_REDIRECT) +		return 0; + +	*nhoff += iphsz + sizeof(_ih); +	return 1; +} + +static int +hmark_pkt_set_htuple_ipv4(const struct sk_buff *skb, struct hmark_tuple *t, +			  const struct xt_hmark_info *info) +{ +	struct iphdr *ip, _ip; +	int nhoff = skb_network_offset(skb); + +	ip = (struct iphdr *) (skb->data + nhoff); +	if (ip->protocol == IPPROTO_ICMP) { +		/* Use inner header in case of ICMP errors */ +		if (get_inner_hdr(skb, ip->ihl * 4, &nhoff)) { +			ip = skb_header_pointer(skb, nhoff, sizeof(_ip), &_ip); +			if (ip == NULL) +				return -1; +		} +	} + +	t->src = ip->saddr & info->src_mask.ip; +	t->dst = ip->daddr & info->dst_mask.ip; + +	if (info->flags & XT_HMARK_FLAG(XT_HMARK_METHOD_L3)) +		return 0; + +	t->proto = ip->protocol; + +	/* ICMP has no ports, skip */ +	if (t->proto == IPPROTO_ICMP) +		return 0; + +	/* follow-up fragments don't contain ports, skip all fragments */ +	if (ip->frag_off & htons(IP_MF | IP_OFFSET)) +		return 0; + +	hmark_set_tuple_ports(skb, (ip->ihl * 4) + nhoff, t, info); + +	return 0; +} + +static unsigned int +hmark_tg_v4(struct sk_buff *skb, const struct xt_action_param *par) +{ +	const struct xt_hmark_info *info = par->targinfo; +	struct hmark_tuple t; + +	memset(&t, 0, sizeof(struct hmark_tuple)); + +	if (info->flags & XT_HMARK_FLAG(XT_HMARK_CT)) { +		if (hmark_ct_set_htuple(skb, &t, info) < 0) +			return XT_CONTINUE; +	} else { +		if (hmark_pkt_set_htuple_ipv4(skb, &t, info) < 0) +			return XT_CONTINUE; +	} + +	skb->mark = hmark_hash(&t, info); +	return XT_CONTINUE; +} + +static int hmark_tg_check(const struct xt_tgchk_param *par) +{ +	const struct xt_hmark_info *info = par->targinfo; + +	if (!info->hmodulus) { +		pr_info("xt_HMARK: hash modulus can't be zero\n"); +		return -EINVAL; +	} +	if (info->proto_mask && +	    (info->flags & XT_HMARK_FLAG(XT_HMARK_METHOD_L3))) { +		pr_info("xt_HMARK: proto mask must be zero with L3 mode\n"); +		return -EINVAL; +	} +	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPI_MASK) && +	    (info->flags & (XT_HMARK_FLAG(XT_HMARK_SPORT_MASK) | +			     XT_HMARK_FLAG(XT_HMARK_DPORT_MASK)))) { +		pr_info("xt_HMARK: spi-mask and port-mask can't be combined\n"); +		return -EINVAL; +	} +	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPI) && +	    (info->flags & (XT_HMARK_FLAG(XT_HMARK_SPORT) | +			     XT_HMARK_FLAG(XT_HMARK_DPORT)))) { +		pr_info("xt_HMARK: spi-set and port-set can't be combined\n"); +		return -EINVAL; +	} +	return 0; +} + +static struct xt_target hmark_tg_reg[] __read_mostly = { +	{ +		.name		= "HMARK", +		.family		= NFPROTO_IPV4, +		.target		= hmark_tg_v4, +		.targetsize	= sizeof(struct xt_hmark_info), +		.checkentry	= hmark_tg_check, +		.me		= THIS_MODULE, +	}, +#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) +	{ +		.name		= "HMARK", +		.family		= NFPROTO_IPV6, +		.target		= hmark_tg_v6, +		.targetsize	= sizeof(struct xt_hmark_info), +		.checkentry	= hmark_tg_check, +		.me		= THIS_MODULE, +	}, +#endif +}; + +static int __init hmark_tg_init(void) +{ +	return xt_register_targets(hmark_tg_reg, ARRAY_SIZE(hmark_tg_reg)); +} + +static void __exit hmark_tg_exit(void) +{ +	xt_unregister_targets(hmark_tg_reg, ARRAY_SIZE(hmark_tg_reg)); +} + +module_init(hmark_tg_init); +module_exit(hmark_tg_exit);  | 
