xref: /openbmc/linux/net/ipv6/netfilter.c (revision fd589a8f)
1 #include <linux/kernel.h>
2 #include <linux/init.h>
3 #include <linux/ipv6.h>
4 #include <linux/netfilter.h>
5 #include <linux/netfilter_ipv6.h>
6 #include <net/dst.h>
7 #include <net/ipv6.h>
8 #include <net/ip6_route.h>
9 #include <net/xfrm.h>
10 #include <net/ip6_checksum.h>
11 #include <net/netfilter/nf_queue.h>
12 
13 int ip6_route_me_harder(struct sk_buff *skb)
14 {
15 	struct net *net = dev_net(skb_dst(skb)->dev);
16 	struct ipv6hdr *iph = ipv6_hdr(skb);
17 	struct dst_entry *dst;
18 	struct flowi fl = {
19 		.oif = skb->sk ? skb->sk->sk_bound_dev_if : 0,
20 		.mark = skb->mark,
21 		.nl_u =
22 		{ .ip6_u =
23 		  { .daddr = iph->daddr,
24 		    .saddr = iph->saddr, } },
25 	};
26 
27 	dst = ip6_route_output(net, skb->sk, &fl);
28 
29 #ifdef CONFIG_XFRM
30 	if (!(IP6CB(skb)->flags & IP6SKB_XFRM_TRANSFORMED) &&
31 	    xfrm_decode_session(skb, &fl, AF_INET6) == 0) {
32 		struct dst_entry *dst2 = skb_dst(skb);
33 
34 		if (xfrm_lookup(net, &dst2, &fl, skb->sk, 0)) {
35 			skb_dst_set(skb, NULL);
36 			return -1;
37 		}
38 		skb_dst_set(skb, dst2);
39 	}
40 #endif
41 
42 	if (dst->error) {
43 		IP6_INC_STATS(net, ip6_dst_idev(dst), IPSTATS_MIB_OUTNOROUTES);
44 		LIMIT_NETDEBUG(KERN_DEBUG "ip6_route_me_harder: No more route.\n");
45 		dst_release(dst);
46 		return -EINVAL;
47 	}
48 
49 	/* Drop old route. */
50 	skb_dst_drop(skb);
51 
52 	skb_dst_set(skb, dst);
53 	return 0;
54 }
55 EXPORT_SYMBOL(ip6_route_me_harder);
56 
57 /*
58  * Extra routing may needed on local out, as the QUEUE target never
59  * returns control to the table.
60  */
61 
62 struct ip6_rt_info {
63 	struct in6_addr daddr;
64 	struct in6_addr saddr;
65 	u_int32_t mark;
66 };
67 
68 static void nf_ip6_saveroute(const struct sk_buff *skb,
69 			     struct nf_queue_entry *entry)
70 {
71 	struct ip6_rt_info *rt_info = nf_queue_entry_reroute(entry);
72 
73 	if (entry->hook == NF_INET_LOCAL_OUT) {
74 		struct ipv6hdr *iph = ipv6_hdr(skb);
75 
76 		rt_info->daddr = iph->daddr;
77 		rt_info->saddr = iph->saddr;
78 		rt_info->mark = skb->mark;
79 	}
80 }
81 
82 static int nf_ip6_reroute(struct sk_buff *skb,
83 			  const struct nf_queue_entry *entry)
84 {
85 	struct ip6_rt_info *rt_info = nf_queue_entry_reroute(entry);
86 
87 	if (entry->hook == NF_INET_LOCAL_OUT) {
88 		struct ipv6hdr *iph = ipv6_hdr(skb);
89 		if (!ipv6_addr_equal(&iph->daddr, &rt_info->daddr) ||
90 		    !ipv6_addr_equal(&iph->saddr, &rt_info->saddr) ||
91 		    skb->mark != rt_info->mark)
92 			return ip6_route_me_harder(skb);
93 	}
94 	return 0;
95 }
96 
97 static int nf_ip6_route(struct dst_entry **dst, struct flowi *fl)
98 {
99 	*dst = ip6_route_output(&init_net, NULL, fl);
100 	return (*dst)->error;
101 }
102 
103 __sum16 nf_ip6_checksum(struct sk_buff *skb, unsigned int hook,
104 			     unsigned int dataoff, u_int8_t protocol)
105 {
106 	struct ipv6hdr *ip6h = ipv6_hdr(skb);
107 	__sum16 csum = 0;
108 
109 	switch (skb->ip_summed) {
110 	case CHECKSUM_COMPLETE:
111 		if (hook != NF_INET_PRE_ROUTING && hook != NF_INET_LOCAL_IN)
112 			break;
113 		if (!csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
114 				     skb->len - dataoff, protocol,
115 				     csum_sub(skb->csum,
116 					      skb_checksum(skb, 0,
117 							   dataoff, 0)))) {
118 			skb->ip_summed = CHECKSUM_UNNECESSARY;
119 			break;
120 		}
121 		/* fall through */
122 	case CHECKSUM_NONE:
123 		skb->csum = ~csum_unfold(
124 				csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
125 					     skb->len - dataoff,
126 					     protocol,
127 					     csum_sub(0,
128 						      skb_checksum(skb, 0,
129 								   dataoff, 0))));
130 		csum = __skb_checksum_complete(skb);
131 	}
132 	return csum;
133 }
134 EXPORT_SYMBOL(nf_ip6_checksum);
135 
136 static __sum16 nf_ip6_checksum_partial(struct sk_buff *skb, unsigned int hook,
137 				       unsigned int dataoff, unsigned int len,
138 				       u_int8_t protocol)
139 {
140 	struct ipv6hdr *ip6h = ipv6_hdr(skb);
141 	__wsum hsum;
142 	__sum16 csum = 0;
143 
144 	switch (skb->ip_summed) {
145 	case CHECKSUM_COMPLETE:
146 		if (len == skb->len - dataoff)
147 			return nf_ip6_checksum(skb, hook, dataoff, protocol);
148 		/* fall through */
149 	case CHECKSUM_NONE:
150 		hsum = skb_checksum(skb, 0, dataoff, 0);
151 		skb->csum = ~csum_unfold(csum_ipv6_magic(&ip6h->saddr,
152 							 &ip6h->daddr,
153 							 skb->len - dataoff,
154 							 protocol,
155 							 csum_sub(0, hsum)));
156 		skb->ip_summed = CHECKSUM_NONE;
157 		csum = __skb_checksum_complete_head(skb, dataoff + len);
158 		if (!csum)
159 			skb->ip_summed = CHECKSUM_UNNECESSARY;
160 	}
161 	return csum;
162 };
163 
164 static const struct nf_afinfo nf_ip6_afinfo = {
165 	.family			= AF_INET6,
166 	.checksum		= nf_ip6_checksum,
167 	.checksum_partial	= nf_ip6_checksum_partial,
168 	.route			= nf_ip6_route,
169 	.saveroute		= nf_ip6_saveroute,
170 	.reroute		= nf_ip6_reroute,
171 	.route_key_size		= sizeof(struct ip6_rt_info),
172 };
173 
174 int __init ipv6_netfilter_init(void)
175 {
176 	return nf_register_afinfo(&nf_ip6_afinfo);
177 }
178 
179 /* This can be called from inet6_init() on errors, so it cannot
180  * be marked __exit. -DaveM
181  */
182 void ipv6_netfilter_fini(void)
183 {
184 	nf_unregister_afinfo(&nf_ip6_afinfo);
185 }
186