ip_fw_pfil.c revision 172467
1/*- 2 * Copyright (c) 2004 Andre Oppermann, Internet Business Solutions AG 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27#include <sys/cdefs.h> 28__FBSDID("$FreeBSD: head/sys/netinet/ip_fw_pfil.c 172467 2007-10-07 20:44:24Z silby $"); 29 30#if !defined(KLD_MODULE) 31#include "opt_ipfw.h" 32#include "opt_ipdn.h" 33#include "opt_inet.h" 34#ifndef INET 35#error IPFIREWALL requires INET. 36#endif /* INET */ 37#endif /* KLD_MODULE */ 38#include "opt_inet6.h" 39 40#include <sys/param.h> 41#include <sys/systm.h> 42#include <sys/malloc.h> 43#include <sys/mbuf.h> 44#include <sys/module.h> 45#include <sys/kernel.h> 46#include <sys/socket.h> 47#include <sys/socketvar.h> 48#include <sys/sysctl.h> 49#include <sys/ucred.h> 50 51#include <net/if.h> 52#include <net/route.h> 53#include <net/pfil.h> 54 55#include <netinet/in.h> 56#include <netinet/in_systm.h> 57#include <netinet/in_var.h> 58#include <netinet/ip.h> 59#include <netinet/ip_var.h> 60#include <netinet/ip_fw.h> 61#include <netinet/ip_divert.h> 62#include <netinet/ip_dummynet.h> 63 64#include <netgraph/ng_ipfw.h> 65 66#include <machine/in_cksum.h> 67 68int fw_enable = 1; 69#ifdef INET6 70int fw6_enable = 1; 71#endif 72 73int ipfw_chg_hook(SYSCTL_HANDLER_ARGS); 74 75/* Dummynet hooks. */ 76ip_dn_ruledel_t *ip_dn_ruledel_ptr = NULL; 77 78/* Divert hooks. */ 79ip_divert_packet_t *ip_divert_ptr = NULL; 80 81/* ng_ipfw hooks. */ 82ng_ipfw_input_t *ng_ipfw_input_p = NULL; 83 84/* Forward declarations. */ 85static int ipfw_divert(struct mbuf **, int, int); 86#define DIV_DIR_IN 1 87#define DIV_DIR_OUT 0 88 89int 90ipfw_check_in(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, 91 struct inpcb *inp) 92{ 93 struct ip_fw_args args; 94 struct ng_ipfw_tag *ng_tag; 95 struct m_tag *dn_tag; 96 int ipfw = 0; 97 int divert; 98 int tee; 99#ifdef IPFIREWALL_FORWARD 100 struct m_tag *fwd_tag; 101#endif 102 103 KASSERT(dir == PFIL_IN, ("ipfw_check_in wrong direction!")); 104 105 bzero(&args, sizeof(args)); 106 107 dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL); 108 if (dn_tag != NULL){ 109 struct dn_pkt_tag *dt; 110 111 dt = (struct dn_pkt_tag *)(dn_tag+1); 112 args.rule = dt->rule; 113 114 m_tag_delete(*m0, dn_tag); 115 } 116 117 ng_tag = (struct ng_ipfw_tag *)m_tag_locate(*m0, NGM_IPFW_COOKIE, 0, 118 NULL); 119 if (ng_tag != NULL) { 120 KASSERT(ng_tag->dir == NG_IPFW_IN, 121 ("ng_ipfw tag with wrong direction")); 122 args.rule = ng_tag->rule; 123 m_tag_delete(*m0, (struct m_tag *)ng_tag); 124 } 125 126again: 127 args.m = *m0; 128 args.inp = inp; 129 ipfw = ipfw_chk(&args); 130 *m0 = args.m; 131 tee = 0; 132 133 KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL", 134 __func__)); 135 136 switch (ipfw) { 137 case IP_FW_PASS: 138 if (args.next_hop == NULL) 139 goto pass; 140 141#ifdef IPFIREWALL_FORWARD 142 fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD, 143 sizeof(struct sockaddr_in), M_NOWAIT); 144 if (fwd_tag == NULL) 145 goto drop; 146 bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in)); 147 m_tag_prepend(*m0, fwd_tag); 148 149 if (in_localip(args.next_hop->sin_addr)) 150 (*m0)->m_flags |= M_FASTFWD_OURS; 151 goto pass; 152#endif 153 break; /* not reached */ 154 155 case IP_FW_DENY: 156 goto drop; 157 break; /* not reached */ 158 159 case IP_FW_DUMMYNET: 160 if (!DUMMYNET_LOADED) 161 goto drop; 162 if (mtod(*m0, struct ip *)->ip_v == 4) 163 ip_dn_io_ptr(*m0, DN_TO_IP_IN, &args); 164 else if (mtod(*m0, struct ip *)->ip_v == 6) 165 ip_dn_io_ptr(*m0, DN_TO_IP6_IN, &args); 166 *m0 = NULL; 167 return 0; /* packet consumed */ 168 169 case IP_FW_TEE: 170 tee = 1; 171 /* fall through */ 172 173 case IP_FW_DIVERT: 174 divert = ipfw_divert(m0, DIV_DIR_IN, tee); 175 if (divert) { 176 *m0 = NULL; 177 return 0; /* packet consumed */ 178 } else { 179 args.rule = NULL; 180 goto again; /* continue with packet */ 181 } 182 183 case IP_FW_NGTEE: 184 if (!NG_IPFW_LOADED) 185 goto drop; 186 (void)ng_ipfw_input_p(m0, NG_IPFW_IN, &args, 1); 187 goto again; /* continue with packet */ 188 189 case IP_FW_NETGRAPH: 190 if (!NG_IPFW_LOADED) 191 goto drop; 192 return ng_ipfw_input_p(m0, NG_IPFW_IN, &args, 0); 193 194 case IP_FW_NAT: 195 goto again; /* continue with packet */ 196 197 default: 198 KASSERT(0, ("%s: unknown retval", __func__)); 199 } 200 201drop: 202 if (*m0) 203 m_freem(*m0); 204 *m0 = NULL; 205 return (EACCES); 206pass: 207 return 0; /* not filtered */ 208} 209 210int 211ipfw_check_out(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, 212 struct inpcb *inp) 213{ 214 struct ip_fw_args args; 215 struct ng_ipfw_tag *ng_tag; 216 struct m_tag *dn_tag; 217 int ipfw = 0; 218 int divert; 219 int tee; 220#ifdef IPFIREWALL_FORWARD 221 struct m_tag *fwd_tag; 222#endif 223 224 KASSERT(dir == PFIL_OUT, ("ipfw_check_out wrong direction!")); 225 226 bzero(&args, sizeof(args)); 227 228 dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL); 229 if (dn_tag != NULL) { 230 struct dn_pkt_tag *dt; 231 232 dt = (struct dn_pkt_tag *)(dn_tag+1); 233 args.rule = dt->rule; 234 235 m_tag_delete(*m0, dn_tag); 236 } 237 238 ng_tag = (struct ng_ipfw_tag *)m_tag_locate(*m0, NGM_IPFW_COOKIE, 0, 239 NULL); 240 if (ng_tag != NULL) { 241 KASSERT(ng_tag->dir == NG_IPFW_OUT, 242 ("ng_ipfw tag with wrong direction")); 243 args.rule = ng_tag->rule; 244 m_tag_delete(*m0, (struct m_tag *)ng_tag); 245 } 246 247again: 248 args.m = *m0; 249 args.oif = ifp; 250 args.inp = inp; 251 ipfw = ipfw_chk(&args); 252 *m0 = args.m; 253 tee = 0; 254 255 KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL", 256 __func__)); 257 258 switch (ipfw) { 259 case IP_FW_PASS: 260 if (args.next_hop == NULL) 261 goto pass; 262#ifdef IPFIREWALL_FORWARD 263 /* Overwrite existing tag. */ 264 fwd_tag = m_tag_find(*m0, PACKET_TAG_IPFORWARD, NULL); 265 if (fwd_tag == NULL) { 266 fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD, 267 sizeof(struct sockaddr_in), M_NOWAIT); 268 if (fwd_tag == NULL) 269 goto drop; 270 } else 271 m_tag_unlink(*m0, fwd_tag); 272 bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in)); 273 m_tag_prepend(*m0, fwd_tag); 274 275 if (in_localip(args.next_hop->sin_addr)) 276 (*m0)->m_flags |= M_FASTFWD_OURS; 277 goto pass; 278#endif 279 break; /* not reached */ 280 281 case IP_FW_DENY: 282 goto drop; 283 break; /* not reached */ 284 285 case IP_FW_DUMMYNET: 286 if (!DUMMYNET_LOADED) 287 break; 288 if (mtod(*m0, struct ip *)->ip_v == 4) 289 ip_dn_io_ptr(*m0, DN_TO_IP_OUT, &args); 290 else if (mtod(*m0, struct ip *)->ip_v == 6) 291 ip_dn_io_ptr(*m0, DN_TO_IP6_OUT, &args); 292 *m0 = NULL; 293 return 0; /* packet consumed */ 294 295 break; 296 297 case IP_FW_TEE: 298 tee = 1; 299 /* fall through */ 300 301 case IP_FW_DIVERT: 302 divert = ipfw_divert(m0, DIV_DIR_OUT, tee); 303 if (divert) { 304 *m0 = NULL; 305 return 0; /* packet consumed */ 306 } else { 307 args.rule = NULL; 308 goto again; /* continue with packet */ 309 } 310 311 case IP_FW_NGTEE: 312 if (!NG_IPFW_LOADED) 313 goto drop; 314 (void)ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 1); 315 goto again; /* continue with packet */ 316 317 case IP_FW_NETGRAPH: 318 if (!NG_IPFW_LOADED) 319 goto drop; 320 return ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 0); 321 322 case IP_FW_NAT: 323 goto again; /* continue with packet */ 324 325 default: 326 KASSERT(0, ("%s: unknown retval", __func__)); 327 } 328 329drop: 330 if (*m0) 331 m_freem(*m0); 332 *m0 = NULL; 333 return (EACCES); 334pass: 335 return 0; /* not filtered */ 336} 337 338static int 339ipfw_divert(struct mbuf **m, int incoming, int tee) 340{ 341 /* 342 * ipfw_chk() has already tagged the packet with the divert tag. 343 * If tee is set, copy packet and return original. 344 * If not tee, consume packet and send it to divert socket. 345 */ 346 struct mbuf *clone, *reass; 347 struct ip *ip; 348 int hlen; 349 350 reass = NULL; 351 352 /* Is divert module loaded? */ 353 if (ip_divert_ptr == NULL) 354 goto nodivert; 355 356 /* Cloning needed for tee? */ 357 if (tee) 358 clone = m_dup(*m, M_DONTWAIT); 359 else 360 clone = *m; 361 362 /* In case m_dup was unable to allocate mbufs. */ 363 if (clone == NULL) 364 goto teeout; 365 366 /* 367 * Divert listeners can only handle non-fragmented packets. 368 * However when tee is set we will *not* de-fragment the packets; 369 * Doing do would put the reassembly into double-jeopardy. On top 370 * of that someone doing a tee will probably want to get the packet 371 * in its original form. 372 */ 373 ip = mtod(clone, struct ip *); 374 if (!tee && ip->ip_off & (IP_MF | IP_OFFMASK)) { 375 376 /* Reassemble packet. */ 377 reass = ip_reass(clone); 378 379 /* 380 * IP header checksum fixup after reassembly and leave header 381 * in network byte order. 382 */ 383 if (reass != NULL) { 384 ip = mtod(reass, struct ip *); 385 hlen = ip->ip_hl << 2; 386 ip->ip_len = htons(ip->ip_len); 387 ip->ip_off = htons(ip->ip_off); 388 ip->ip_sum = 0; 389 if (hlen == sizeof(struct ip)) 390 ip->ip_sum = in_cksum_hdr(ip); 391 else 392 ip->ip_sum = in_cksum(reass, hlen); 393 clone = reass; 394 } else 395 clone = NULL; 396 } else { 397 /* Convert header to network byte order. */ 398 ip->ip_len = htons(ip->ip_len); 399 ip->ip_off = htons(ip->ip_off); 400 } 401 402 /* Do the dirty job... */ 403 if (clone && ip_divert_ptr != NULL) 404 ip_divert_ptr(clone, incoming); 405 406teeout: 407 /* 408 * For tee we leave the divert tag attached to original packet. 409 * It will then continue rule evaluation after the tee rule. 410 */ 411 if (tee) 412 return 0; 413 414 /* Packet diverted and consumed */ 415 return 1; 416 417nodivert: 418 m_freem(*m); 419 return 1; 420} 421 422static int 423ipfw_hook(void) 424{ 425 struct pfil_head *pfh_inet; 426 427 pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); 428 if (pfh_inet == NULL) 429 return ENOENT; 430 431 pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet); 432 pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet); 433 434 return 0; 435} 436 437static int 438ipfw_unhook(void) 439{ 440 struct pfil_head *pfh_inet; 441 442 pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); 443 if (pfh_inet == NULL) 444 return ENOENT; 445 446 pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet); 447 pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet); 448 449 return 0; 450} 451 452#ifdef INET6 453static int 454ipfw6_hook(void) 455{ 456 struct pfil_head *pfh_inet6; 457 458 pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); 459 if (pfh_inet6 == NULL) 460 return ENOENT; 461 462 pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6); 463 pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6); 464 465 return 0; 466} 467 468static int 469ipfw6_unhook(void) 470{ 471 struct pfil_head *pfh_inet6; 472 473 pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); 474 if (pfh_inet6 == NULL) 475 return ENOENT; 476 477 pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6); 478 pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6); 479 480 return 0; 481} 482#endif /* INET6 */ 483 484int 485ipfw_chg_hook(SYSCTL_HANDLER_ARGS) 486{ 487 int enable = *(int *)arg1; 488 int error; 489 490 error = sysctl_handle_int(oidp, &enable, 0, req); 491 if (error) 492 return (error); 493 494 enable = (enable) ? 1 : 0; 495 496 if (enable == *(int *)arg1) 497 return (0); 498 499 if (arg1 == &fw_enable) { 500 if (enable) 501 error = ipfw_hook(); 502 else 503 error = ipfw_unhook(); 504 } 505#ifdef INET6 506 if (arg1 == &fw6_enable) { 507 if (enable) 508 error = ipfw6_hook(); 509 else 510 error = ipfw6_unhook(); 511 } 512#endif 513 514 if (error) 515 return (error); 516 517 *(int *)arg1 = enable; 518 519 return (0); 520} 521 522static int 523ipfw_modevent(module_t mod, int type, void *unused) 524{ 525 int err = 0; 526 527 switch (type) { 528 case MOD_LOAD: 529 if ((err = ipfw_init()) != 0) { 530 printf("ipfw_init() error\n"); 531 break; 532 } 533 if ((err = ipfw_hook()) != 0) { 534 printf("ipfw_hook() error\n"); 535 break; 536 } 537#ifdef INET6 538 if ((err = ipfw6_hook()) != 0) { 539 printf("ipfw_hook() error\n"); 540 break; 541 } 542#endif 543 break; 544 545 case MOD_UNLOAD: 546 if ((err = ipfw_unhook()) > 0) 547 break; 548#ifdef INET6 549 if ((err = ipfw6_unhook()) > 0) 550 break; 551#endif 552 ipfw_destroy(); 553 break; 554 555 default: 556 return EOPNOTSUPP; 557 break; 558 } 559 return err; 560} 561 562static moduledata_t ipfwmod = { 563 "ipfw", 564 ipfw_modevent, 565 0 566}; 567DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY); 568MODULE_VERSION(ipfw, 2); 569