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