diff options
Diffstat (limited to 'net/ipv6/netfilter.c')
| -rw-r--r-- | net/ipv6/netfilter.c | 79 | 
1 files changed, 60 insertions, 19 deletions
diff --git a/net/ipv6/netfilter.c b/net/ipv6/netfilter.c index 35915e8617f..d38e6a8d8b9 100644 --- a/net/ipv6/netfilter.c +++ b/net/ipv6/netfilter.c @@ -1,8 +1,16 @@ +/* + * IPv6 specific functions of netfilter core + * + * Rusty Russell (C) 2000 -- This code is GPL. + * Patrick McHardy (C) 2006-2012 + */  #include <linux/kernel.h>  #include <linux/init.h>  #include <linux/ipv6.h>  #include <linux/netfilter.h>  #include <linux/netfilter_ipv6.h> +#include <linux/export.h> +#include <net/addrconf.h>  #include <net/dst.h>  #include <net/ipv6.h>  #include <net/ip6_route.h> @@ -13,21 +21,24 @@  int ip6_route_me_harder(struct sk_buff *skb)  {  	struct net *net = dev_net(skb_dst(skb)->dev); -	struct ipv6hdr *iph = ipv6_hdr(skb); +	const struct ipv6hdr *iph = ipv6_hdr(skb); +	unsigned int hh_len;  	struct dst_entry *dst; -	struct flowi fl = { -		.oif = skb->sk ? skb->sk->sk_bound_dev_if : 0, -		.mark = skb->mark, -		.fl6_dst = iph->daddr, -		.fl6_src = iph->saddr, +	struct flowi6 fl6 = { +		.flowi6_oif = skb->sk ? skb->sk->sk_bound_dev_if : 0, +		.flowi6_mark = skb->mark, +		.daddr = iph->daddr, +		.saddr = iph->saddr,  	}; +	int err; -	dst = ip6_route_output(net, skb->sk, &fl); -	if (dst->error) { +	dst = ip6_route_output(net, skb->sk, &fl6); +	err = dst->error; +	if (err) {  		IP6_INC_STATS(net, ip6_dst_idev(dst), IPSTATS_MIB_OUTNOROUTES);  		LIMIT_NETDEBUG(KERN_DEBUG "ip6_route_me_harder: No more route.\n");  		dst_release(dst); -		return -EINVAL; +		return err;  	}  	/* Drop old route. */ @@ -37,14 +48,22 @@ int ip6_route_me_harder(struct sk_buff *skb)  #ifdef CONFIG_XFRM  	if (!(IP6CB(skb)->flags & IP6SKB_XFRM_TRANSFORMED) && -	    xfrm_decode_session(skb, &fl, AF_INET6) == 0) { +	    xfrm_decode_session(skb, flowi6_to_flowi(&fl6), AF_INET6) == 0) {  		skb_dst_set(skb, NULL); -		if (xfrm_lookup(net, &dst, &fl, skb->sk, 0)) -			return -1; +		dst = xfrm_lookup(net, dst, flowi6_to_flowi(&fl6), skb->sk, 0); +		if (IS_ERR(dst)) +			return PTR_ERR(dst);  		skb_dst_set(skb, dst);  	}  #endif +	/* Change in oif may mean change in hh_len. */ +	hh_len = skb_dst(skb)->dev->hard_header_len; +	if (skb_headroom(skb) < hh_len && +	    pskb_expand_head(skb, HH_DATA_ALIGN(hh_len - skb_headroom(skb)), +			     0, GFP_ATOMIC)) +		return -ENOMEM; +  	return 0;  }  EXPORT_SYMBOL(ip6_route_me_harder); @@ -66,7 +85,7 @@ static void nf_ip6_saveroute(const struct sk_buff *skb,  	struct ip6_rt_info *rt_info = nf_queue_entry_reroute(entry);  	if (entry->hook == NF_INET_LOCAL_OUT) { -		struct ipv6hdr *iph = ipv6_hdr(skb); +		const struct ipv6hdr *iph = ipv6_hdr(skb);  		rt_info->daddr = iph->daddr;  		rt_info->saddr = iph->saddr; @@ -80,7 +99,7 @@ static int nf_ip6_reroute(struct sk_buff *skb,  	struct ip6_rt_info *rt_info = nf_queue_entry_reroute(entry);  	if (entry->hook == NF_INET_LOCAL_OUT) { -		struct ipv6hdr *iph = ipv6_hdr(skb); +		const struct ipv6hdr *iph = ipv6_hdr(skb);  		if (!ipv6_addr_equal(&iph->daddr, &rt_info->daddr) ||  		    !ipv6_addr_equal(&iph->saddr, &rt_info->saddr) ||  		    skb->mark != rt_info->mark) @@ -89,16 +108,32 @@ static int nf_ip6_reroute(struct sk_buff *skb,  	return 0;  } -static int nf_ip6_route(struct dst_entry **dst, struct flowi *fl) +static int nf_ip6_route(struct net *net, struct dst_entry **dst, +			struct flowi *fl, bool strict)  { -	*dst = ip6_route_output(&init_net, NULL, fl); -	return (*dst)->error; +	static const struct ipv6_pinfo fake_pinfo; +	static const struct inet_sock fake_sk = { +		/* makes ip6_route_output set RT6_LOOKUP_F_IFACE: */ +		.sk.sk_bound_dev_if = 1, +		.pinet6 = (struct ipv6_pinfo *) &fake_pinfo, +	}; +	const void *sk = strict ? &fake_sk : NULL; +	struct dst_entry *result; +	int err; + +	result = ip6_route_output(net, sk, &fl->u.ip6); +	err = result->error; +	if (err) +		dst_release(result); +	else +		*dst = result; +	return err;  }  __sum16 nf_ip6_checksum(struct sk_buff *skb, unsigned int hook,  			     unsigned int dataoff, u_int8_t protocol)  { -	struct ipv6hdr *ip6h = ipv6_hdr(skb); +	const struct ipv6hdr *ip6h = ipv6_hdr(skb);  	__sum16 csum = 0;  	switch (skb->ip_summed) { @@ -132,7 +167,7 @@ static __sum16 nf_ip6_checksum_partial(struct sk_buff *skb, unsigned int hook,  				       unsigned int dataoff, unsigned int len,  				       u_int8_t protocol)  { -	struct ipv6hdr *ip6h = ipv6_hdr(skb); +	const struct ipv6hdr *ip6h = ipv6_hdr(skb);  	__wsum hsum;  	__sum16 csum = 0; @@ -154,6 +189,10 @@ static __sum16 nf_ip6_checksum_partial(struct sk_buff *skb, unsigned int hook,  	return csum;  }; +static const struct nf_ipv6_ops ipv6ops = { +	.chk_addr	= ipv6_chk_addr, +}; +  static const struct nf_afinfo nf_ip6_afinfo = {  	.family			= AF_INET6,  	.checksum		= nf_ip6_checksum, @@ -166,6 +205,7 @@ static const struct nf_afinfo nf_ip6_afinfo = {  int __init ipv6_netfilter_init(void)  { +	RCU_INIT_POINTER(nf_ipv6_ops, &ipv6ops);  	return nf_register_afinfo(&nf_ip6_afinfo);  } @@ -174,5 +214,6 @@ int __init ipv6_netfilter_init(void)   */  void ipv6_netfilter_fini(void)  { +	RCU_INIT_POINTER(nf_ipv6_ops, NULL);  	nf_unregister_afinfo(&nf_ip6_afinfo);  }  | 
