xref: /linux/net/netfilter/utils.c (revision bd628c1bed7902ec1f24ba0fe70758949146abbe)
1 // SPDX-License-Identifier: GPL-2.0
2 #include <linux/kernel.h>
3 #include <linux/netfilter.h>
4 #include <linux/netfilter_ipv4.h>
5 #include <linux/netfilter_ipv6.h>
6 #include <net/netfilter/nf_queue.h>
7 #include <net/ip6_checksum.h>
8 
9 #ifdef CONFIG_INET
10 __sum16 nf_ip_checksum(struct sk_buff *skb, unsigned int hook,
11 		       unsigned int dataoff, u8 protocol)
12 {
13 	const struct iphdr *iph = ip_hdr(skb);
14 	__sum16 csum = 0;
15 
16 	switch (skb->ip_summed) {
17 	case CHECKSUM_COMPLETE:
18 		if (hook != NF_INET_PRE_ROUTING && hook != NF_INET_LOCAL_IN)
19 			break;
20 		if ((protocol == 0 && !csum_fold(skb->csum)) ||
21 		    !csum_tcpudp_magic(iph->saddr, iph->daddr,
22 				       skb->len - dataoff, protocol,
23 				       skb->csum)) {
24 			skb->ip_summed = CHECKSUM_UNNECESSARY;
25 			break;
26 		}
27 		/* fall through */
28 	case CHECKSUM_NONE:
29 		if (protocol == 0)
30 			skb->csum = 0;
31 		else
32 			skb->csum = csum_tcpudp_nofold(iph->saddr, iph->daddr,
33 						       skb->len - dataoff,
34 						       protocol, 0);
35 		csum = __skb_checksum_complete(skb);
36 	}
37 	return csum;
38 }
39 EXPORT_SYMBOL(nf_ip_checksum);
40 #endif
41 
42 static __sum16 nf_ip_checksum_partial(struct sk_buff *skb, unsigned int hook,
43 				      unsigned int dataoff, unsigned int len,
44 				      u8 protocol)
45 {
46 	const struct iphdr *iph = ip_hdr(skb);
47 	__sum16 csum = 0;
48 
49 	switch (skb->ip_summed) {
50 	case CHECKSUM_COMPLETE:
51 		if (len == skb->len - dataoff)
52 			return nf_ip_checksum(skb, hook, dataoff, protocol);
53 		/* fall through */
54 	case CHECKSUM_NONE:
55 		skb->csum = csum_tcpudp_nofold(iph->saddr, iph->daddr, protocol,
56 					       skb->len - dataoff, 0);
57 		skb->ip_summed = CHECKSUM_NONE;
58 		return __skb_checksum_complete_head(skb, dataoff + len);
59 	}
60 	return csum;
61 }
62 
63 __sum16 nf_ip6_checksum(struct sk_buff *skb, unsigned int hook,
64 			unsigned int dataoff, u8 protocol)
65 {
66 	const struct ipv6hdr *ip6h = ipv6_hdr(skb);
67 	__sum16 csum = 0;
68 
69 	switch (skb->ip_summed) {
70 	case CHECKSUM_COMPLETE:
71 		if (hook != NF_INET_PRE_ROUTING && hook != NF_INET_LOCAL_IN)
72 			break;
73 		if (!csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
74 				     skb->len - dataoff, protocol,
75 				     csum_sub(skb->csum,
76 					      skb_checksum(skb, 0,
77 							   dataoff, 0)))) {
78 			skb->ip_summed = CHECKSUM_UNNECESSARY;
79 			break;
80 		}
81 		/* fall through */
82 	case CHECKSUM_NONE:
83 		skb->csum = ~csum_unfold(
84 				csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
85 					     skb->len - dataoff,
86 					     protocol,
87 					     csum_sub(0,
88 						      skb_checksum(skb, 0,
89 								   dataoff, 0))));
90 		csum = __skb_checksum_complete(skb);
91 	}
92 	return csum;
93 }
94 EXPORT_SYMBOL(nf_ip6_checksum);
95 
96 static __sum16 nf_ip6_checksum_partial(struct sk_buff *skb, unsigned int hook,
97 				       unsigned int dataoff, unsigned int len,
98 				       u8 protocol)
99 {
100 	const struct ipv6hdr *ip6h = ipv6_hdr(skb);
101 	__wsum hsum;
102 	__sum16 csum = 0;
103 
104 	switch (skb->ip_summed) {
105 	case CHECKSUM_COMPLETE:
106 		if (len == skb->len - dataoff)
107 			return nf_ip6_checksum(skb, hook, dataoff, protocol);
108 		/* fall through */
109 	case CHECKSUM_NONE:
110 		hsum = skb_checksum(skb, 0, dataoff, 0);
111 		skb->csum = ~csum_unfold(csum_ipv6_magic(&ip6h->saddr,
112 							 &ip6h->daddr,
113 							 skb->len - dataoff,
114 							 protocol,
115 							 csum_sub(0, hsum)));
116 		skb->ip_summed = CHECKSUM_NONE;
117 		return __skb_checksum_complete_head(skb, dataoff + len);
118 	}
119 	return csum;
120 };
121 
122 __sum16 nf_checksum(struct sk_buff *skb, unsigned int hook,
123 		    unsigned int dataoff, u8 protocol,
124 		    unsigned short family)
125 {
126 	__sum16 csum = 0;
127 
128 	switch (family) {
129 	case AF_INET:
130 		csum = nf_ip_checksum(skb, hook, dataoff, protocol);
131 		break;
132 	case AF_INET6:
133 		csum = nf_ip6_checksum(skb, hook, dataoff, protocol);
134 		break;
135 	}
136 
137 	return csum;
138 }
139 EXPORT_SYMBOL_GPL(nf_checksum);
140 
141 __sum16 nf_checksum_partial(struct sk_buff *skb, unsigned int hook,
142 			    unsigned int dataoff, unsigned int len,
143 			    u8 protocol, unsigned short family)
144 {
145 	__sum16 csum = 0;
146 
147 	switch (family) {
148 	case AF_INET:
149 		csum = nf_ip_checksum_partial(skb, hook, dataoff, len,
150 					      protocol);
151 		break;
152 	case AF_INET6:
153 		csum = nf_ip6_checksum_partial(skb, hook, dataoff, len,
154 					       protocol);
155 		break;
156 	}
157 
158 	return csum;
159 }
160 EXPORT_SYMBOL_GPL(nf_checksum_partial);
161 
162 int nf_route(struct net *net, struct dst_entry **dst, struct flowi *fl,
163 	     bool strict, unsigned short family)
164 {
165 	const struct nf_ipv6_ops *v6ops;
166 	int ret = 0;
167 
168 	switch (family) {
169 	case AF_INET:
170 		ret = nf_ip_route(net, dst, fl, strict);
171 		break;
172 	case AF_INET6:
173 		v6ops = rcu_dereference(nf_ipv6_ops);
174 		if (v6ops)
175 			ret = v6ops->route(net, dst, fl, strict);
176 		break;
177 	}
178 
179 	return ret;
180 }
181 EXPORT_SYMBOL_GPL(nf_route);
182 
183 int nf_reroute(struct sk_buff *skb, struct nf_queue_entry *entry)
184 {
185 	const struct nf_ipv6_ops *v6ops;
186 	int ret = 0;
187 
188 	switch (entry->state.pf) {
189 	case AF_INET:
190 		ret = nf_ip_reroute(skb, entry);
191 		break;
192 	case AF_INET6:
193 		v6ops = rcu_dereference(nf_ipv6_ops);
194 		if (v6ops)
195 			ret = v6ops->reroute(skb, entry);
196 		break;
197 	}
198 	return ret;
199 }
200