1 /* $NetBSD: npf_alg_icmp.c,v 1.21 2014/06/08 12:12:56 spz 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.21 2014/06/08 12:12:56 spz Exp $"); 38 39 #include <sys/param.h> 40 #include <sys/module.h> 41 42 #include <netinet/in_systm.h> 43 #include <netinet/in.h> 44 #include <netinet/ip.h> 45 #include <netinet/tcp.h> 46 #include <netinet/udp.h> 47 #include <netinet/ip_icmp.h> 48 #include <netinet/icmp6.h> 49 #include <net/pfil.h> 50 51 #include "npf_impl.h" 52 53 MODULE(MODULE_CLASS_MISC, npf_alg_icmp, "npf"); 54 55 /* 56 * Traceroute criteria. 57 * 58 * IANA assigned base port: 33434. However, common practice is to increase 59 * the port, thus monitor [33434-33484] range. Additional filter is low TTL. 60 */ 61 62 #define TR_BASE_PORT 33434 63 #define TR_PORT_RANGE 33484 64 #define TR_MAX_TTL 48 65 66 static npf_alg_t * alg_icmp __read_mostly; 67 68 /* 69 * npfa_icmp_match: matching inspector determines ALG case and associates 70 * our ALG with the NAT entry. 71 */ 72 static bool 73 npfa_icmp_match(npf_cache_t *npc, nbuf_t *nbuf, npf_nat_t *nt, int di) 74 { 75 const int proto = npc->npc_proto; 76 const struct ip *ip = npc->npc_ip.v4; 77 in_port_t dport; 78 79 KASSERT(npf_iscached(npc, NPC_IP46)); 80 KASSERT(npf_iscached(npc, NPC_LAYER4)); 81 82 /* Check for low TTL. Also, we support outbound NAT only. */ 83 if (ip->ip_ttl > TR_MAX_TTL || di != PFIL_OUT) { 84 return false; 85 } 86 87 switch (proto) { 88 case IPPROTO_TCP: { 89 const struct tcphdr *th = npc->npc_l4.tcp; 90 dport = ntohs(th->th_dport); 91 break; 92 } 93 case IPPROTO_UDP: { 94 const struct udphdr *uh = npc->npc_l4.udp; 95 dport = ntohs(uh->uh_dport); 96 break; 97 } 98 case IPPROTO_ICMP: 99 case IPPROTO_ICMPV6: 100 /* Just to pass the test below. */ 101 dport = TR_BASE_PORT; 102 break; 103 default: 104 return false; 105 } 106 107 /* Handle TCP/UDP traceroute - check for port range. */ 108 if (dport < TR_BASE_PORT || dport > TR_PORT_RANGE) { 109 return false; 110 } 111 112 /* Associate ALG with translation entry. */ 113 npf_nat_setalg(nt, alg_icmp, 0); 114 return true; 115 } 116 117 /* 118 * npfa_icmp{4,6}_inspect: retrieve unique identifiers - either ICMP query 119 * ID or TCP/UDP ports of the original packet, which is embedded. 120 */ 121 122 static bool 123 npfa_icmp4_inspect(const int type, npf_cache_t *npc, nbuf_t *nbuf) 124 { 125 u_int offby; 126 127 /* Per RFC 792. */ 128 switch (type) { 129 case ICMP_UNREACH: 130 case ICMP_SOURCEQUENCH: 131 case ICMP_REDIRECT: 132 case ICMP_TIMXCEED: 133 case ICMP_PARAMPROB: 134 if (npc == NULL) { 135 return false; 136 } 137 /* Should contain original IP header. */ 138 if (!nbuf_advance(nbuf, offsetof(struct icmp, icmp_ip), 0)) { 139 return false; 140 } 141 return (npf_cache_all(npc, nbuf) & NPC_LAYER4) != 0; 142 143 case ICMP_ECHOREPLY: 144 case ICMP_ECHO: 145 case ICMP_TSTAMP: 146 case ICMP_TSTAMPREPLY: 147 case ICMP_IREQ: 148 case ICMP_IREQREPLY: 149 /* Should contain ICMP query ID - ensure. */ 150 offby = offsetof(struct icmp, icmp_id); 151 if (!nbuf_advance(nbuf, offby, sizeof(uint16_t))) { 152 return false; 153 } 154 npc->npc_info |= NPC_ICMP_ID; 155 return true; 156 default: 157 break; 158 } 159 return false; 160 } 161 162 static bool 163 npfa_icmp6_inspect(const int type, npf_cache_t *npc, nbuf_t *nbuf) 164 { 165 u_int offby; 166 167 /* Per RFC 4443. */ 168 switch (type) { 169 case ICMP6_DST_UNREACH: 170 case ICMP6_PACKET_TOO_BIG: 171 case ICMP6_TIME_EXCEEDED: 172 case ICMP6_PARAM_PROB: 173 if (npc == NULL) { 174 return false; 175 } 176 /* Should contain original IP header. */ 177 if (!nbuf_advance(nbuf, sizeof(struct icmp6_hdr), 0)) { 178 return false; 179 } 180 return (npf_cache_all(npc, nbuf) & NPC_LAYER4) != 0; 181 182 case ICMP6_ECHO_REQUEST: 183 case ICMP6_ECHO_REPLY: 184 /* Should contain ICMP query ID - ensure. */ 185 offby = offsetof(struct icmp6_hdr, icmp6_id); 186 if (!nbuf_advance(nbuf, offby, sizeof(uint16_t))) { 187 return false; 188 } 189 npc->npc_info |= NPC_ICMP_ID; 190 return true; 191 default: 192 break; 193 } 194 return false; 195 } 196 197 /* 198 * npfa_icmp_session: ALG ICMP inspector. 199 * 200 * => Returns true if "enpc" is filled. 201 */ 202 static bool 203 npfa_icmp_inspect(npf_cache_t *npc, nbuf_t *nbuf, npf_cache_t *enpc) 204 { 205 bool ret; 206 207 KASSERT(npf_iscached(npc, NPC_IP46)); 208 KASSERT(npf_iscached(npc, NPC_ICMP)); 209 210 /* Advance to ICMP header. */ 211 nbuf_reset(nbuf); 212 if (!nbuf_advance(nbuf, npc->npc_hlen, 0)) { 213 return false; 214 } 215 enpc->npc_info = 0; 216 217 /* 218 * Inspect the ICMP packet. The relevant data might be in the 219 * embedded packet. Fill the "enpc" cache, if so. 220 */ 221 if (npf_iscached(npc, NPC_IP4)) { 222 const struct icmp *ic = npc->npc_l4.icmp; 223 ret = npfa_icmp4_inspect(ic->icmp_type, enpc, nbuf); 224 } else if (npf_iscached(npc, NPC_IP6)) { 225 const struct icmp6_hdr *ic6 = npc->npc_l4.icmp6; 226 ret = npfa_icmp6_inspect(ic6->icmp6_type, enpc, nbuf); 227 } else { 228 ret = false; 229 } 230 if (!ret) { 231 return false; 232 } 233 234 /* ICMP ID is the original packet, just indicate it. */ 235 if (npf_iscached(enpc, NPC_ICMP_ID)) { 236 npc->npc_info |= NPC_ICMP_ID; 237 return false; 238 } 239 240 /* Indicate that embedded packet is in the cache. */ 241 return true; 242 } 243 244 static npf_session_t * 245 npfa_icmp_session(npf_cache_t *npc, nbuf_t *nbuf, int di) 246 { 247 npf_cache_t enpc; 248 249 /* Inspect ICMP packet for an embedded packet. */ 250 if (!npf_iscached(npc, NPC_ICMP)) 251 return NULL; 252 if (!npfa_icmp_inspect(npc, nbuf, &enpc)) 253 return NULL; 254 255 /* 256 * Invert the identifiers of the embedded packet. 257 * If it is ICMP, then ensure ICMP ID. 258 */ 259 union l4 { 260 struct tcphdr th; 261 struct udphdr uh; 262 } l4; 263 bool ret, forw; 264 265 #define SWAP(type, x, y) { type tmp = x; x = y; y = tmp; } 266 SWAP(npf_addr_t *, enpc.npc_ips[NPF_SRC], enpc.npc_ips[NPF_DST]); 267 268 switch (enpc.npc_proto) { 269 case IPPROTO_TCP: 270 l4.th.th_sport = enpc.npc_l4.tcp->th_dport; 271 l4.th.th_dport = enpc.npc_l4.tcp->th_sport; 272 enpc.npc_l4.tcp = &l4.th; 273 break; 274 case IPPROTO_UDP: 275 l4.uh.uh_sport = enpc.npc_l4.udp->uh_dport; 276 l4.uh.uh_dport = enpc.npc_l4.udp->uh_sport; 277 enpc.npc_l4.udp = &l4.uh; 278 break; 279 case IPPROTO_ICMP: { 280 const struct icmp *ic = enpc.npc_l4.icmp; 281 ret = npfa_icmp4_inspect(ic->icmp_type, &enpc, nbuf); 282 if (!ret || !npf_iscached(&enpc, NPC_ICMP_ID)) 283 return false; 284 break; 285 } 286 case IPPROTO_ICMPV6: { 287 const struct icmp6_hdr *ic6 = enpc.npc_l4.icmp6; 288 ret = npfa_icmp6_inspect(ic6->icmp6_type, &enpc, nbuf); 289 if (!ret || !npf_iscached(&enpc, NPC_ICMP_ID)) 290 return false; 291 break; 292 } 293 default: 294 return false; 295 } 296 297 /* Lookup for a session using embedded packet. */ 298 return npf_session_lookup(&enpc, nbuf, di, &forw); 299 } 300 301 /* 302 * npfa_icmp_nat: ALG translator - rewrites IP address in the IP header 303 * which is embedded in ICMP packet. Note: backwards stream only. 304 */ 305 static bool 306 npfa_icmp_nat(npf_cache_t *npc, nbuf_t *nbuf, npf_nat_t *nt, bool forw) 307 { 308 const u_int which = NPF_SRC; 309 npf_cache_t enpc; 310 311 if (forw || !npf_iscached(npc, NPC_ICMP)) 312 return false; 313 if (!npfa_icmp_inspect(npc, nbuf, &enpc)) 314 return false; 315 316 KASSERT(npf_iscached(&enpc, NPC_IP46)); 317 KASSERT(npf_iscached(&enpc, NPC_LAYER4)); 318 319 /* 320 * ICMP: fetch the current checksum we are going to fixup. 321 */ 322 struct icmp *ic = npc->npc_l4.icmp; 323 uint16_t cksum = ic->icmp_cksum; 324 325 CTASSERT(offsetof(struct icmp, icmp_cksum) == 326 offsetof(struct icmp6_hdr, icmp6_cksum)); 327 328 /* 329 * Fetch the IP and port in the _embedded_ packet. Also, fetch 330 * the IPv4 and TCP/UDP checksums before they are rewritten. 331 * Calculate the part of the ICMP checksum fixup. 332 */ 333 const int proto = enpc.npc_proto; 334 uint16_t ipcksum = 0, l4cksum = 0; 335 npf_addr_t *addr; 336 in_port_t port; 337 338 npf_nat_getorig(nt, &addr, &port); 339 340 if (npf_iscached(&enpc, NPC_IP4)) { 341 const struct ip *eip = enpc.npc_ip.v4; 342 ipcksum = eip->ip_sum; 343 } 344 cksum = npf_addr_cksum(cksum, enpc.npc_alen, enpc.npc_ips[which], addr); 345 346 switch (proto) { 347 case IPPROTO_TCP: { 348 const struct tcphdr *th = enpc.npc_l4.tcp; 349 cksum = npf_fixup16_cksum(cksum, th->th_sport, port); 350 l4cksum = th->th_sum; 351 break; 352 } 353 case IPPROTO_UDP: { 354 const struct udphdr *uh = enpc.npc_l4.udp; 355 cksum = npf_fixup16_cksum(cksum, uh->uh_sport, port); 356 l4cksum = uh->uh_sum; 357 break; 358 } 359 case IPPROTO_ICMP: 360 case IPPROTO_ICMPV6: 361 break; 362 default: 363 return false; 364 } 365 366 /* 367 * Translate the embedded packet. The following changes will 368 * be performed by npf_napt_rwr(): 369 * 370 * 1) Rewrite the IP address and, if not ICMP, port. 371 * 2) Rewrite the TCP/UDP checksum (if not ICMP). 372 * 3) Rewrite the IPv4 checksum for (1) and (2). 373 * 374 * XXX: Assumes NPF_NATOUT (source address/port). Currently, 375 * npfa_icmp_match() matches only for the PFIL_OUT traffic. 376 */ 377 if (npf_napt_rwr(&enpc, which, addr, port)) { 378 return false; 379 } 380 381 /* 382 * Finally, finish the ICMP checksum fixup: include the checksum 383 * changes in the embedded packet. 384 */ 385 if (npf_iscached(&enpc, NPC_IP4)) { 386 const struct ip *eip = enpc.npc_ip.v4; 387 cksum = npf_fixup16_cksum(cksum, ipcksum, eip->ip_sum); 388 } 389 switch (proto) { 390 case IPPROTO_TCP: { 391 const struct tcphdr *th = enpc.npc_l4.tcp; 392 cksum = npf_fixup16_cksum(cksum, l4cksum, th->th_sum); 393 break; 394 } 395 case IPPROTO_UDP: 396 if (l4cksum) { 397 const struct udphdr *uh = enpc.npc_l4.udp; 398 cksum = npf_fixup16_cksum(cksum, l4cksum, uh->uh_sum); 399 } 400 break; 401 } 402 ic->icmp_cksum = cksum; 403 return true; 404 } 405 406 /* 407 * npf_alg_icmp_{init,fini,modcmd}: ICMP ALG initialization, destruction 408 * and module interface. 409 */ 410 411 static int 412 npf_alg_icmp_init(void) 413 { 414 static const npfa_funcs_t icmp = { 415 .match = npfa_icmp_match, 416 .translate = npfa_icmp_nat, 417 .inspect = npfa_icmp_session, 418 }; 419 alg_icmp = npf_alg_register("icmp", &icmp); 420 return alg_icmp ? 0 : ENOMEM; 421 } 422 423 static int 424 npf_alg_icmp_fini(void) 425 { 426 KASSERT(alg_icmp != NULL); 427 return npf_alg_unregister(alg_icmp); 428 } 429 430 static int 431 npf_alg_icmp_modcmd(modcmd_t cmd, void *arg) 432 { 433 switch (cmd) { 434 case MODULE_CMD_INIT: 435 return npf_alg_icmp_init(); 436 case MODULE_CMD_FINI: 437 return npf_alg_icmp_fini(); 438 case MODULE_CMD_AUTOUNLOAD: 439 return EBUSY; 440 default: 441 return ENOTTY; 442 } 443 return 0; 444 } 445