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