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