ip_fw_pfil.c revision 193532
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/ipfw/ip_fw_pfil.c 193532 2009-06-05 19:22:47Z luigi $"); 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/lock.h> 47#include <sys/rwlock.h> 48#include <sys/socket.h> 49#include <sys/socketvar.h> 50#include <sys/sysctl.h> 51#include <sys/ucred.h> 52#include <sys/vimage.h> 53 54#include <net/if.h> 55#include <net/route.h> 56#include <net/pfil.h> 57 58#include <netinet/in.h> 59#include <netinet/in_systm.h> 60#include <netinet/ip.h> 61#include <netinet/ip_var.h> 62#include <netinet/ip_fw.h> 63#include <netinet/ip_divert.h> 64#include <netinet/ip_dummynet.h> 65#include <netinet/vinet.h> 66 67#include <netgraph/ng_ipfw.h> 68 69#include <machine/in_cksum.h> 70 71#ifdef VIMAGE_GLOBALS 72int fw_enable = 1; 73#ifdef INET6 74int fw6_enable = 1; 75#endif 76#endif 77 78int ipfw_chg_hook(SYSCTL_HANDLER_ARGS); 79 80/* Divert hooks. */ 81ip_divert_packet_t *ip_divert_ptr = NULL; 82 83/* ng_ipfw hooks. */ 84ng_ipfw_input_t *ng_ipfw_input_p = NULL; 85 86/* Forward declarations. */ 87static int ipfw_divert(struct mbuf **, int, int); 88#define DIV_DIR_IN 1 89#define DIV_DIR_OUT 0 90 91int 92ipfw_check_in(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, 93 struct inpcb *inp) 94{ 95 INIT_VNET_INET(curvnet); 96 struct ip_fw_args args; 97 struct ng_ipfw_tag *ng_tag; 98 struct m_tag *dn_tag; 99 int ipfw = 0; 100 int divert; 101 int tee; 102#ifdef IPFIREWALL_FORWARD 103 struct m_tag *fwd_tag; 104#endif 105 106 KASSERT(dir == PFIL_IN, ("ipfw_check_in wrong direction!")); 107 108 bzero(&args, sizeof(args)); 109 110 ng_tag = (struct ng_ipfw_tag *)m_tag_locate(*m0, NGM_IPFW_COOKIE, 0, 111 NULL); 112 if (ng_tag != NULL) { 113 KASSERT(ng_tag->dir == NG_IPFW_IN, 114 ("ng_ipfw tag with wrong direction")); 115 args.rule = ng_tag->rule; 116 m_tag_delete(*m0, (struct m_tag *)ng_tag); 117 } 118 119again: 120 dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL); 121 if (dn_tag != NULL){ 122 struct dn_pkt_tag *dt; 123 124 dt = (struct dn_pkt_tag *)(dn_tag+1); 125 args.rule = dt->rule; 126 127 m_tag_delete(*m0, dn_tag); 128 } 129 130 args.m = *m0; 131 args.inp = inp; 132 tee = 0; 133 134 if (V_fw_one_pass == 0 || args.rule == NULL) { 135 ipfw = ipfw_chk(&args); 136 *m0 = args.m; 137 } else 138 ipfw = IP_FW_PASS; 139 140 KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL", 141 __func__)); 142 143 switch (ipfw) { 144 case IP_FW_PASS: 145 if (args.next_hop == NULL) 146 goto pass; 147 148#ifdef IPFIREWALL_FORWARD 149 fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD, 150 sizeof(struct sockaddr_in), M_NOWAIT); 151 if (fwd_tag == NULL) 152 goto drop; 153 bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in)); 154 m_tag_prepend(*m0, fwd_tag); 155 156 if (in_localip(args.next_hop->sin_addr)) 157 (*m0)->m_flags |= M_FASTFWD_OURS; 158 goto pass; 159#endif 160 break; /* not reached */ 161 162 case IP_FW_DENY: 163 goto drop; 164 break; /* not reached */ 165 166 case IP_FW_DUMMYNET: 167 if (ip_dn_io_ptr == NULL) 168 goto drop; 169 if (mtod(*m0, struct ip *)->ip_v == 4) 170 ip_dn_io_ptr(m0, DN_TO_IP_IN, &args); 171 else if (mtod(*m0, struct ip *)->ip_v == 6) 172 ip_dn_io_ptr(m0, DN_TO_IP6_IN, &args); 173 if (*m0 != NULL) 174 goto again; 175 return 0; /* packet consumed */ 176 177 case IP_FW_TEE: 178 tee = 1; 179 /* fall through */ 180 181 case IP_FW_DIVERT: 182 divert = ipfw_divert(m0, DIV_DIR_IN, tee); 183 if (divert) { 184 *m0 = NULL; 185 return 0; /* packet consumed */ 186 } else { 187 args.rule = NULL; 188 goto again; /* continue with packet */ 189 } 190 191 case IP_FW_NGTEE: 192 if (!NG_IPFW_LOADED) 193 goto drop; 194 (void)ng_ipfw_input_p(m0, NG_IPFW_IN, &args, 1); 195 goto again; /* continue with packet */ 196 197 case IP_FW_NETGRAPH: 198 if (!NG_IPFW_LOADED) 199 goto drop; 200 return ng_ipfw_input_p(m0, NG_IPFW_IN, &args, 0); 201 202 case IP_FW_NAT: 203 goto again; /* continue with packet */ 204 205 case IP_FW_REASS: 206 goto again; 207 208 default: 209 KASSERT(0, ("%s: unknown retval", __func__)); 210 } 211 212drop: 213 if (*m0) 214 m_freem(*m0); 215 *m0 = NULL; 216 return (EACCES); 217pass: 218 return 0; /* not filtered */ 219} 220 221int 222ipfw_check_out(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, 223 struct inpcb *inp) 224{ 225 INIT_VNET_INET(curvnet); 226 struct ip_fw_args args; 227 struct ng_ipfw_tag *ng_tag; 228 struct m_tag *dn_tag; 229 int ipfw = 0; 230 int divert; 231 int tee; 232#ifdef IPFIREWALL_FORWARD 233 struct m_tag *fwd_tag; 234#endif 235 236 KASSERT(dir == PFIL_OUT, ("ipfw_check_out wrong direction!")); 237 238 bzero(&args, sizeof(args)); 239 240 ng_tag = (struct ng_ipfw_tag *)m_tag_locate(*m0, NGM_IPFW_COOKIE, 0, 241 NULL); 242 if (ng_tag != NULL) { 243 KASSERT(ng_tag->dir == NG_IPFW_OUT, 244 ("ng_ipfw tag with wrong direction")); 245 args.rule = ng_tag->rule; 246 m_tag_delete(*m0, (struct m_tag *)ng_tag); 247 } 248 249again: 250 dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL); 251 if (dn_tag != NULL) { 252 struct dn_pkt_tag *dt; 253 254 dt = (struct dn_pkt_tag *)(dn_tag+1); 255 args.rule = dt->rule; 256 257 m_tag_delete(*m0, dn_tag); 258 } 259 260 args.m = *m0; 261 args.oif = ifp; 262 args.inp = inp; 263 tee = 0; 264 265 if (V_fw_one_pass == 0 || args.rule == NULL) { 266 ipfw = ipfw_chk(&args); 267 *m0 = args.m; 268 } else 269 ipfw = IP_FW_PASS; 270 271 KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL", 272 __func__)); 273 274 switch (ipfw) { 275 case IP_FW_PASS: 276 if (args.next_hop == NULL) 277 goto pass; 278#ifdef IPFIREWALL_FORWARD 279 /* Overwrite existing tag. */ 280 fwd_tag = m_tag_find(*m0, PACKET_TAG_IPFORWARD, NULL); 281 if (fwd_tag == NULL) { 282 fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD, 283 sizeof(struct sockaddr_in), M_NOWAIT); 284 if (fwd_tag == NULL) 285 goto drop; 286 } else 287 m_tag_unlink(*m0, fwd_tag); 288 bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in)); 289 m_tag_prepend(*m0, fwd_tag); 290 291 if (in_localip(args.next_hop->sin_addr)) 292 (*m0)->m_flags |= M_FASTFWD_OURS; 293 goto pass; 294#endif 295 break; /* not reached */ 296 297 case IP_FW_DENY: 298 goto drop; 299 break; /* not reached */ 300 301 case IP_FW_DUMMYNET: 302 if (ip_dn_io_ptr == NULL) 303 break; 304 if (mtod(*m0, struct ip *)->ip_v == 4) 305 ip_dn_io_ptr(m0, DN_TO_IP_OUT, &args); 306 else if (mtod(*m0, struct ip *)->ip_v == 6) 307 ip_dn_io_ptr(m0, DN_TO_IP6_OUT, &args); 308 if (*m0 != NULL) 309 goto again; 310 return 0; /* packet consumed */ 311 312 break; 313 314 case IP_FW_TEE: 315 tee = 1; 316 /* fall through */ 317 318 case IP_FW_DIVERT: 319 divert = ipfw_divert(m0, DIV_DIR_OUT, tee); 320 if (divert) { 321 *m0 = NULL; 322 return 0; /* packet consumed */ 323 } else { 324 args.rule = NULL; 325 goto again; /* continue with packet */ 326 } 327 328 case IP_FW_NGTEE: 329 if (!NG_IPFW_LOADED) 330 goto drop; 331 (void)ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 1); 332 goto again; /* continue with packet */ 333 334 case IP_FW_NETGRAPH: 335 if (!NG_IPFW_LOADED) 336 goto drop; 337 return ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 0); 338 339 case IP_FW_NAT: 340 goto again; /* continue with packet */ 341 342 case IP_FW_REASS: 343 goto again; 344 345 default: 346 KASSERT(0, ("%s: unknown retval", __func__)); 347 } 348 349drop: 350 if (*m0) 351 m_freem(*m0); 352 *m0 = NULL; 353 return (EACCES); 354pass: 355 return 0; /* not filtered */ 356} 357 358static int 359ipfw_divert(struct mbuf **m, int incoming, int tee) 360{ 361 /* 362 * ipfw_chk() has already tagged the packet with the divert tag. 363 * If tee is set, copy packet and return original. 364 * If not tee, consume packet and send it to divert socket. 365 */ 366 struct mbuf *clone, *reass; 367 struct ip *ip; 368 int hlen; 369 370 reass = NULL; 371 372 /* Is divert module loaded? */ 373 if (ip_divert_ptr == NULL) 374 goto nodivert; 375 376 /* Cloning needed for tee? */ 377 if (tee) 378 clone = m_dup(*m, M_DONTWAIT); 379 else 380 clone = *m; 381 382 /* In case m_dup was unable to allocate mbufs. */ 383 if (clone == NULL) 384 goto teeout; 385 386 /* 387 * Divert listeners can only handle non-fragmented packets. 388 * However when tee is set we will *not* de-fragment the packets; 389 * Doing do would put the reassembly into double-jeopardy. On top 390 * of that someone doing a tee will probably want to get the packet 391 * in its original form. 392 */ 393 ip = mtod(clone, struct ip *); 394 if (!tee && ip->ip_off & (IP_MF | IP_OFFMASK)) { 395 396 /* Reassemble packet. */ 397 reass = ip_reass(clone); 398 399 /* 400 * IP header checksum fixup after reassembly and leave header 401 * in network byte order. 402 */ 403 if (reass != NULL) { 404 ip = mtod(reass, struct ip *); 405 hlen = ip->ip_hl << 2; 406 ip->ip_len = htons(ip->ip_len); 407 ip->ip_off = htons(ip->ip_off); 408 ip->ip_sum = 0; 409 if (hlen == sizeof(struct ip)) 410 ip->ip_sum = in_cksum_hdr(ip); 411 else 412 ip->ip_sum = in_cksum(reass, hlen); 413 clone = reass; 414 } else 415 clone = NULL; 416 } else { 417 /* Convert header to network byte order. */ 418 ip->ip_len = htons(ip->ip_len); 419 ip->ip_off = htons(ip->ip_off); 420 } 421 422 /* Do the dirty job... */ 423 if (clone && ip_divert_ptr != NULL) 424 ip_divert_ptr(clone, incoming); 425 426teeout: 427 /* 428 * For tee we leave the divert tag attached to original packet. 429 * It will then continue rule evaluation after the tee rule. 430 */ 431 if (tee) 432 return 0; 433 434 /* Packet diverted and consumed */ 435 return 1; 436 437nodivert: 438 m_freem(*m); 439 return 1; 440} 441 442static int 443ipfw_hook(void) 444{ 445 struct pfil_head *pfh_inet; 446 447 pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); 448 if (pfh_inet == NULL) 449 return ENOENT; 450 451 (void)pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, 452 pfh_inet); 453 (void)pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, 454 pfh_inet); 455 456 return 0; 457} 458 459static int 460ipfw_unhook(void) 461{ 462 struct pfil_head *pfh_inet; 463 464 pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); 465 if (pfh_inet == NULL) 466 return ENOENT; 467 468 (void)pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, 469 pfh_inet); 470 (void)pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, 471 pfh_inet); 472 473 return 0; 474} 475 476#ifdef INET6 477static int 478ipfw6_hook(void) 479{ 480 struct pfil_head *pfh_inet6; 481 482 pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); 483 if (pfh_inet6 == NULL) 484 return ENOENT; 485 486 (void)pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, 487 pfh_inet6); 488 (void)pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, 489 pfh_inet6); 490 491 return 0; 492} 493 494static int 495ipfw6_unhook(void) 496{ 497 struct pfil_head *pfh_inet6; 498 499 pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); 500 if (pfh_inet6 == NULL) 501 return ENOENT; 502 503 (void)pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, 504 pfh_inet6); 505 (void)pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, 506 pfh_inet6); 507 508 return 0; 509} 510#endif /* INET6 */ 511 512int 513ipfw_chg_hook(SYSCTL_HANDLER_ARGS) 514{ 515 INIT_VNET_IPFW(curvnet); 516 int enable = *(int *)arg1; 517 int error; 518 519 error = sysctl_handle_int(oidp, &enable, 0, req); 520 if (error) 521 return (error); 522 523 enable = (enable) ? 1 : 0; 524 525 if (enable == *(int *)arg1) 526 return (0); 527 528 if (arg1 == &V_fw_enable) { 529 if (enable) 530 error = ipfw_hook(); 531 else 532 error = ipfw_unhook(); 533 } 534#ifdef INET6 535 if (arg1 == &V_fw6_enable) { 536 if (enable) 537 error = ipfw6_hook(); 538 else 539 error = ipfw6_unhook(); 540 } 541#endif 542 543 if (error) 544 return (error); 545 546 *(int *)arg1 = enable; 547 548 return (0); 549} 550 551static int 552ipfw_modevent(module_t mod, int type, void *unused) 553{ 554 int err = 0; 555 556 switch (type) { 557 case MOD_LOAD: 558 if ((err = ipfw_init()) != 0) { 559 printf("ipfw_init() error\n"); 560 break; 561 } 562 if ((err = ipfw_hook()) != 0) { 563 printf("ipfw_hook() error\n"); 564 break; 565 } 566#ifdef INET6 567 if ((err = ipfw6_hook()) != 0) { 568 printf("ipfw_hook() error\n"); 569 break; 570 } 571#endif 572 break; 573 574 case MOD_UNLOAD: 575 if ((err = ipfw_unhook()) > 0) 576 break; 577#ifdef INET6 578 if ((err = ipfw6_unhook()) > 0) 579 break; 580#endif 581 ipfw_destroy(); 582 break; 583 584 default: 585 return EOPNOTSUPP; 586 break; 587 } 588 return err; 589} 590 591static moduledata_t ipfwmod = { 592 "ipfw", 593 ipfw_modevent, 594 0 595}; 596DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY - 256); 597MODULE_VERSION(ipfw, 2); 598