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