npf_alg_icmp.c revision 1.5 1 /* $NetBSD: npf_alg_icmp.c,v 1.5 2010/12/18 01:07:25 rmind Exp $ */
2
3 /*-
4 * Copyright (c) 2010 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This material is based upon work partially supported by The
8 * NetBSD Foundation under a contract with Mindaugas Rasiukevicius.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 /*
33 * NPF ALG for ICMP and traceroute translations.
34 */
35
36 #include <sys/cdefs.h>
37 __KERNEL_RCSID(0, "$NetBSD: npf_alg_icmp.c,v 1.5 2010/12/18 01:07:25 rmind Exp $");
38
39 #include <sys/param.h>
40 #include <sys/kernel.h>
41 #include <sys/module.h>
42 #include <sys/pool.h>
43
44 #include <netinet/in_systm.h>
45 #include <netinet/in.h>
46 #include <netinet/ip.h>
47 #include <netinet/tcp.h>
48 #include <netinet/udp.h>
49 #include <netinet/ip_icmp.h>
50 #include <net/pfil.h>
51
52 #include "npf_impl.h"
53
54 MODULE(MODULE_CLASS_MISC, npf_alg_icmp, "npf");
55
56 /*
57 * Traceroute criteria.
58 *
59 * IANA assigned base port: 33434. However, common practice is to increase
60 * the port, thus monitor [33434-33484] range. Additional filter is TTL < 50.
61 */
62
63 #define TR_BASE_PORT 33434
64 #define TR_PORT_RANGE 33484
65 #define TR_MAX_TTL 50
66
67 static npf_alg_t * alg_icmp;
68
69 static bool npfa_icmp_match(npf_cache_t *, nbuf_t *, void *);
70 static bool npfa_icmp_natin(npf_cache_t *, nbuf_t *, void *);
71 static bool npfa_icmp_session(npf_cache_t *, nbuf_t *, void *);
72
73 /*
74 * npf_alg_icmp_{init,fini,modcmd}: ICMP ALG initialization, destruction
75 * and module interface.
76 */
77
78 static int
79 npf_alg_icmp_init(void)
80 {
81
82 alg_icmp = npf_alg_register(npfa_icmp_match, NULL,
83 npfa_icmp_natin, npfa_icmp_session);
84 KASSERT(alg_icmp != NULL);
85 return 0;
86 }
87
88 static int
89 npf_alg_icmp_fini(void)
90 {
91
92 KASSERT(alg_icmp != NULL);
93 return npf_alg_unregister(alg_icmp);
94 }
95
96 static int
97 npf_alg_icmp_modcmd(modcmd_t cmd, void *arg)
98 {
99
100 switch (cmd) {
101 case MODULE_CMD_INIT:
102 return npf_alg_icmp_init();
103 case MODULE_CMD_FINI:
104 return npf_alg_icmp_fini();
105 default:
106 return ENOTTY;
107 }
108 return 0;
109 }
110
111 /*
112 * npfa_icmp_match: ALG matching inspector - determines ALG case and
113 * associates ALG with NAT entry.
114 */
115 static bool
116 npfa_icmp_match(npf_cache_t *npc, nbuf_t *nbuf, void *ntptr)
117 {
118 const int proto = npf_cache_ipproto(npc);
119 struct ip *ip = &npc->npc_ip.v4;
120 in_port_t dport;
121
122 KASSERT(npf_iscached(npc, NPC_IP46 | NPC_LAYER4));
123
124 if (proto == IPPROTO_TCP) {
125 struct tcphdr *th = &npc->npc_l4.tcp;
126 dport = ntohs(th->th_dport);
127 } else if (proto == IPPROTO_UDP) {
128 struct udphdr *uh = &npc->npc_l4.udp;
129 dport = ntohs(uh->uh_dport);
130 } else {
131 return false;
132 }
133
134 /* Handle TCP/UDP traceroute - check for port range. */
135 if (dport < TR_BASE_PORT || dport > TR_PORT_RANGE) {
136 return false;
137 }
138
139 /* Check for low TTL. */
140 if (ip->ip_ttl > TR_MAX_TTL) {
141 return false;
142 }
143
144 /* Associate ALG with translation entry. */
145 npf_nat_t *nt = ntptr;
146 npf_nat_setalg(nt, alg_icmp, 0);
147 return true;
148 }
149
150 /*
151 * npf_icmp_uniqid: retrieve unique identifiers - either ICMP query ID
152 * or TCP/UDP ports of the original packet, which is embedded.
153 */
154 static bool
155 npf_icmp_uniqid(const int type, npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr)
156 {
157 struct icmp *ic;
158 u_int offby;
159
160 /* Per RFC 792. */
161 switch (type) {
162 case ICMP_UNREACH:
163 case ICMP_SOURCEQUENCH:
164 case ICMP_REDIRECT:
165 case ICMP_TIMXCEED:
166 case ICMP_PARAMPROB:
167 /* Should contain original IP header. */
168 offby = offsetof(struct icmp, icmp_ip);
169 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL) {
170 return false;
171 }
172 /* Fetch into the cache. */
173 if (!npf_fetch_ip(npc, nbuf, n_ptr)) {
174 return false;
175 }
176 switch (npf_cache_ipproto(npc)) {
177 case IPPROTO_TCP:
178 return npf_fetch_tcp(npc, nbuf, n_ptr);
179 case IPPROTO_UDP:
180 return npf_fetch_udp(npc, nbuf, n_ptr);
181 default:
182 return false;
183 }
184 return true;
185
186 case ICMP_ECHOREPLY:
187 case ICMP_ECHO:
188 case ICMP_TSTAMP:
189 case ICMP_TSTAMPREPLY:
190 case ICMP_IREQ:
191 case ICMP_IREQREPLY:
192 /* Should contain ICMP query ID. */
193 ic = &npc->npc_l4.icmp;
194 offby = offsetof(struct icmp, icmp_id);
195 if (nbuf_advfetch(&nbuf, &n_ptr, offby,
196 sizeof(uint16_t), &ic->icmp_id)) {
197 return false;
198 }
199 npc->npc_info |= NPC_ICMP_ID;
200 return true;
201 default:
202 break;
203 }
204 /* No unique IDs. */
205 return false;
206 }
207
208 /*
209 * npfa_icmp_session: ALG session inspector, determines unique identifiers.
210 */
211 static bool
212 npfa_icmp_session(npf_cache_t *npc, nbuf_t *nbuf, void *keyptr)
213 {
214 npf_cache_t *key = keyptr;
215
216 /* ICMP? Get unique identifiers from ICMP packet. */
217 if (npf_cache_ipproto(npc) != IPPROTO_ICMP) {
218 return false;
219 }
220 KASSERT(npf_iscached(npc, NPC_IP46));
221 KASSERT(npf_iscached(npc, NPC_ICMP));
222 key->npc_info = NPC_ICMP;
223
224 /* Advance to ICMP header. */
225 struct ip *ip = &npc->npc_ip.v4;
226 void *n_ptr = nbuf_dataptr(nbuf);
227
228 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, ip->ip_hl << 2)) == NULL) {
229 return false;
230 }
231
232 /* Fetch relevant data into the separate ("key") cache. */
233 struct icmp *ic = &npc->npc_l4.icmp;
234 if (!npf_icmp_uniqid(ic->icmp_type, key, nbuf, n_ptr)) {
235 return false;
236 }
237
238 if (npf_iscached(key, NPC_ICMP_ID)) {
239 struct icmp *keyic = &key->npc_l4.icmp;
240
241 /* Copy ICMP ID to the cache and flag it. */
242 npc->npc_info |= NPC_ICMP_ID;
243 ic->icmp_id = keyic->icmp_id;
244
245 /* Note: return 'false', since key is the original cache. */
246 return false;
247 }
248
249 /*
250 * Embedded IP packet is the original of "forwards" stream.
251 * We should imitate the "backwards" stream for inspection.
252 */
253 KASSERT(npf_iscached(key, NPC_IP46));
254 KASSERT(npf_iscached(key, NPC_LAYER4));
255 key->npc_ipsz = npc->npc_ipsz;
256
257 return true;
258 }
259
260 /*
261 * npfa_icmp_natin: ALG inbound translation inspector, rewrite IP address
262 * in the IP header, which is embedded in ICMP packet.
263 */
264 static bool
265 npfa_icmp_natin(npf_cache_t *npc, nbuf_t *nbuf, void *ntptr)
266 {
267 npf_cache_t enpc;
268
269 /* XXX: Duplicated work. */
270 if (!npfa_icmp_session(npc, nbuf, &enpc)) {
271 return false;
272 }
273 KASSERT(npf_iscached(&enpc, NPC_IP46 | NPC_LAYER4));
274
275 const int proto = npf_cache_ipproto(&enpc);
276 void *n_ptr = nbuf_dataptr(nbuf);
277 void *cnbuf = nbuf, *cnptr = n_ptr;
278 struct icmp *ic = &npc->npc_l4.icmp;
279 uint16_t cksum = ic->icmp_cksum;
280 struct ip *ip = &enpc.npc_ip.v4;
281 uint16_t ecksum = ip->ip_sum, l4cksum;
282
283 /* Save TCP/UDP checksum for update. */
284 if (proto == IPPROTO_TCP) {
285 struct tcphdr *th = &enpc.npc_l4.tcp;
286 l4cksum = th->th_sum;
287 } else {
288 struct udphdr *uh = &enpc.npc_l4.udp;
289 l4cksum = uh->uh_sum;
290 }
291
292 /* Advance to the original IP header, which is embedded after ICMP. */
293 u_int offby = offsetof(struct icmp, icmp_ip);
294 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL) {
295 return false;
296 }
297
298 npf_nat_t *nt = ntptr;
299 npf_addr_t *addr;
300 in_port_t port;
301
302 npf_nat_getorig(nt, &addr, &port);
303
304 /*
305 * Rewrite source IP address and port of the embedded IP header,
306 * which represents original packet - therefore passing PFIL_OUT.
307 * Note: checksum is first, since it uses values from the cache.
308 */
309 if (!npf_rwrcksum(&enpc, nbuf, n_ptr, PFIL_OUT, addr, port)) {
310 return false;
311 }
312 if (!npf_rwrip(&enpc, nbuf, n_ptr, PFIL_OUT, addr)) {
313 return false;
314 }
315 if (!npf_rwrport(&enpc, nbuf, n_ptr, PFIL_OUT, port)) {
316 return false;
317 }
318
319 /*
320 * Calculate ICMP checksum.
321 */
322 if (proto == IPPROTO_TCP) {
323 struct tcphdr *th = &enpc.npc_l4.tcp;
324 cksum = npf_fixup16_cksum(cksum, th->th_sport, port);
325 cksum = npf_fixup16_cksum(cksum, l4cksum, th->th_sum);
326 } else {
327 struct udphdr *uh = &enpc.npc_l4.udp;
328 cksum = npf_fixup16_cksum(cksum, uh->uh_sport, port);
329 cksum = npf_fixup16_cksum(cksum, l4cksum, uh->uh_sum);
330 }
331 cksum = npf_addr_cksum(cksum, enpc.npc_ipsz, enpc.npc_srcip, addr);
332 cksum = npf_fixup16_cksum(cksum, ecksum, ip->ip_sum);
333
334 /* Rewrite ICMP checksum. */
335 return nbuf_store_datum(cnbuf, cnptr, sizeof(uint16_t), &cksum);
336 }
337