1 /* $NetBSD: npf_alg_icmp.c,v 1.1 2010/08/22 18:56:22 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 #ifdef _KERNEL 37 #include <sys/cdefs.h> 38 __KERNEL_RCSID(0, "$NetBSD: npf_alg_icmp.c,v 1.1 2010/08/22 18:56:22 rmind Exp $"); 39 40 #include <sys/param.h> 41 #include <sys/kernel.h> 42 #endif 43 #include <sys/module.h> 44 #include <sys/pool.h> 45 46 #include <netinet/in_systm.h> 47 #include <netinet/in.h> 48 #include <netinet/ip.h> 49 #include <netinet/tcp.h> 50 #include <netinet/udp.h> 51 #include <netinet/ip_icmp.h> 52 #include <net/pfil.h> 53 54 #include "npf_impl.h" 55 56 MODULE(MODULE_CLASS_MISC, npf_alg_icmp, "npf"); 57 58 /* 59 * Traceroute criteria. 60 * 61 * IANA assigned base port: 33434. However, common practice is to increase 62 * the port, thus monitor [33434-33484] range. Additional filter is TTL < 50. 63 */ 64 65 #define TR_BASE_PORT 33434 66 #define TR_PORT_RANGE 33484 67 #define TR_MAX_TTL 50 68 69 static npf_alg_t * alg_icmp; 70 71 static bool npfa_icmp_match(npf_cache_t *, nbuf_t *, void *); 72 static bool npfa_icmp_natin(npf_cache_t *, nbuf_t *, void *); 73 static bool npfa_icmp_session(npf_cache_t *, nbuf_t *, void *); 74 75 /* 76 * npf_alg_icmp_{init,fini,modcmd}: ICMP ALG initialization, destruction 77 * and module interface. 78 */ 79 80 static int 81 npf_alg_icmp_init(void) 82 { 83 84 alg_icmp = npf_alg_register(npfa_icmp_match, NULL, 85 npfa_icmp_natin, npfa_icmp_session); 86 KASSERT(alg_icmp != NULL); 87 return 0; 88 } 89 90 static int 91 npf_alg_icmp_fini(void) 92 { 93 94 KASSERT(alg_icmp != NULL); 95 return npf_alg_unregister(alg_icmp); 96 } 97 98 static int 99 npf_alg_icmp_modcmd(modcmd_t cmd, void *arg) 100 { 101 102 switch (cmd) { 103 case MODULE_CMD_INIT: 104 return npf_alg_icmp_init(); 105 case MODULE_CMD_FINI: 106 return npf_alg_icmp_fini(); 107 default: 108 return ENOTTY; 109 } 110 return 0; 111 } 112 113 /* 114 * npfa_icmp_match: ALG matching inspector, determines ALG case and 115 * establishes a session for "backwards" stream. 116 */ 117 static bool 118 npfa_icmp_match(npf_cache_t *npc, nbuf_t *nbuf, void *ntptr) 119 { 120 const int proto = npc->npc_proto; 121 void *n_ptr = nbuf_dataptr(nbuf); 122 123 /* Handle TCP/UDP traceroute - check for port range. */ 124 if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) { 125 return false; 126 } 127 KASSERT(npf_iscached(npc, NPC_PORTS)); 128 in_port_t dport = ntohs(npc->npc_dport); 129 if (dport < TR_BASE_PORT || dport > TR_PORT_RANGE) { 130 return false; 131 } 132 133 /* Check for low TTL. */ 134 const u_int offby = offsetof(struct ip, ip_ttl); 135 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL) { 136 return false; 137 } 138 uint8_t ttl; 139 if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(uint8_t), &ttl)) { 140 return false; 141 } 142 if (ttl > TR_MAX_TTL) { 143 return false; 144 } 145 146 /* Associate ALG with translation entry. */ 147 npf_nat_t *nt = ntptr; 148 npf_nat_setalg(nt, alg_icmp, 0); 149 return true; 150 } 151 152 /* 153 * npf_icmp_uniqid: retrieve unique identifiers - either ICMP query ID 154 * or TCP/UDP ports of the original packet, which is embedded. 155 */ 156 static inline bool 157 npf_icmp_uniqid(const int type, npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr) 158 { 159 u_int offby; 160 161 /* Per RFC 792. */ 162 switch (type) { 163 case ICMP_UNREACH: 164 case ICMP_SOURCEQUENCH: 165 case ICMP_REDIRECT: 166 case ICMP_TIMXCEED: 167 case ICMP_PARAMPROB: 168 /* Should contain original IP header. */ 169 offby = offsetof(struct icmp, icmp_ip); 170 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL) { 171 return false; 172 } 173 /* Fetch into the cache. */ 174 if (!npf_ip4_proto(npc, nbuf, n_ptr)) { 175 return false; 176 } 177 const int proto = npc->npc_proto; 178 if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) { 179 return false; 180 } 181 if (!npf_fetch_ip4addrs(npc, nbuf, n_ptr)) { 182 return false; 183 } 184 if (!npf_fetch_ports(npc, nbuf, n_ptr, proto)) { 185 return false; 186 } 187 return true; 188 189 case ICMP_ECHOREPLY: 190 case ICMP_ECHO: 191 case ICMP_TSTAMP: 192 case ICMP_TSTAMPREPLY: 193 case ICMP_IREQ: 194 case ICMP_IREQREPLY: 195 /* Should contain ICMP query ID. */ 196 offby = offsetof(struct icmp, icmp_id); 197 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL) { 198 return false; 199 } 200 if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(uint16_t), 201 &npc->npc_icmp_id)) { 202 return false; 203 } 204 npc->npc_info |= NPC_ICMP_ID; 205 return true; 206 default: 207 break; 208 } 209 /* No unique IDs. */ 210 return false; 211 } 212 213 /* 214 * npfa_icmp_session: ALG session inspector, determines unique identifiers. 215 */ 216 static bool 217 npfa_icmp_session(npf_cache_t *npc, nbuf_t *nbuf, void *keyptr) 218 { 219 npf_cache_t *key = keyptr; 220 void *n_ptr; 221 222 /* ICMP? Get unique identifiers from ICMP packet. */ 223 if (npc->npc_proto != IPPROTO_ICMP) { 224 return false; 225 } 226 KASSERT(npf_iscached(npc, NPC_IP46 | NPC_ICMP)); 227 key->npc_info = NPC_ICMP; 228 229 /* Advance to ICMP header. */ 230 n_ptr = nbuf_dataptr(nbuf); 231 #ifdef _NPF_TESTING 232 if (npc->npc_elen && /* XXX */ 233 (n_ptr = nbuf_advance(&nbuf, n_ptr, npc->npc_elen)) == NULL) 234 return false; 235 #endif 236 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, npc->npc_hlen)) == NULL) { 237 return false; 238 } 239 240 /* Fetch into the separate (key) cache. */ 241 if (!npf_icmp_uniqid(npc->npc_icmp_type, key, nbuf, n_ptr)) { 242 return false; 243 } 244 245 if (npf_iscached(key, NPC_ICMP_ID)) { 246 /* Construct the key. */ 247 key->npc_proto = npc->npc_proto; 248 key->npc_dir = npc->npc_dir; 249 /* Save IP addresses. */ 250 key->npc_srcip = npc->npc_srcip; 251 key->npc_dstip = npc->npc_dstip; 252 key->npc_info |= NPC_IP46 | NPC_ADDRS | NPC_PORTS; 253 /* Fake ports with ICMP query IDs. */ 254 key->npc_sport = key->npc_icmp_id; 255 key->npc_dport = key->npc_icmp_id; 256 } else { 257 in_addr_t addr; 258 in_port_t port; 259 /* 260 * Embedded IP packet is the original of "forwards" stream. 261 * We should imitate the "backwards" stream for inspection. 262 */ 263 KASSERT(npf_iscached(key, NPC_IP46 | NPC_ADDRS | NPC_PORTS)); 264 addr = key->npc_srcip; 265 port = key->npc_sport; 266 key->npc_srcip = key->npc_dstip; 267 key->npc_dstip = addr; 268 key->npc_sport = key->npc_dport; 269 key->npc_dport = port; 270 } 271 return true; 272 } 273 274 /* 275 * npfa_icmp_natin: ALG inbound translation inspector, rewrite IP address 276 * in the IP header, which is embedded in ICMP packet. 277 */ 278 static bool 279 npfa_icmp_natin(npf_cache_t *npc, nbuf_t *nbuf, void *ntptr) 280 { 281 void *n_ptr = nbuf_dataptr(nbuf); 282 npf_cache_t enpc; 283 u_int offby; 284 uint16_t cksum; 285 286 /* XXX: Duplicated work. */ 287 if (!npfa_icmp_session(npc, nbuf, &enpc)) { 288 return false; 289 } 290 KASSERT(npf_iscached(&enpc, NPC_IP46 | NPC_ADDRS | NPC_PORTS)); 291 292 /* Advance to ICMP checksum and fetch it. */ 293 offby = npc->npc_hlen + offsetof(struct icmp, icmp_cksum); 294 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL) { 295 return false; 296 } 297 if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(uint16_t), &cksum)) { 298 return false; 299 } 300 301 /* Save the data for checksum update later. */ 302 void *cnbuf = nbuf, *cnptr = n_ptr; 303 uint16_t ecksum = enpc.npc_ipsum; 304 305 /* Advance to the original IP header, which is embedded after ICMP. */ 306 offby = offsetof(struct icmp, icmp_ip) - 307 offsetof(struct icmp, icmp_cksum); 308 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL) { 309 return false; 310 } 311 312 /* 313 * Rewrite source IP address and port of the embedded IP header, 314 * which represents original packet - therefore passing PFIL_OUT. 315 */ 316 npf_nat_t *nt = ntptr; 317 in_addr_t addr; 318 in_port_t port; 319 320 npf_nat_getlocal(nt, &addr, &port); 321 322 if (!npf_rwrip(&enpc, nbuf, n_ptr, PFIL_OUT, addr)) { 323 return false; 324 } 325 if (!npf_rwrport(&enpc, nbuf, n_ptr, PFIL_OUT, port, addr)) { 326 return false; 327 } 328 329 /* 330 * Fixup and update ICMP checksum. 331 * Note: npf_rwrip() has updated the IP checksum. 332 */ 333 cksum = npf_fixup32_cksum(cksum, enpc.npc_srcip, addr); 334 cksum = npf_fixup16_cksum(cksum, enpc.npc_sport, port); 335 cksum = npf_fixup16_cksum(cksum, ecksum, enpc.npc_ipsum); 336 /* FIXME: Updated UDP/TCP checksum joins-in too., when != 0, sigh. */ 337 if (nbuf_store_datum(cnbuf, cnptr, sizeof(uint16_t), &cksum)){ 338 return false; 339 } 340 return true; 341 } 342