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