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