ip_fw_pfil.c revision 140224
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 140224 2005-01-14 09:00:46Z glebius $ 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 38#include <sys/param.h> 39#include <sys/systm.h> 40#include <sys/malloc.h> 41#include <sys/mbuf.h> 42#include <sys/module.h> 43#include <sys/kernel.h> 44#include <sys/socket.h> 45#include <sys/socketvar.h> 46#include <sys/sysctl.h> 47#include <sys/ucred.h> 48 49#include <net/if.h> 50#include <net/route.h> 51#include <net/pfil.h> 52 53#include <netinet/in.h> 54#include <netinet/in_systm.h> 55#include <netinet/in_var.h> 56#include <netinet/ip.h> 57#include <netinet/ip_var.h> 58#include <netinet/ip_fw.h> 59#include <netinet/ip_divert.h> 60#include <netinet/ip_dummynet.h> 61 62#include <machine/in_cksum.h> 63 64static int ipfw_pfil_hooked = 0; 65 66/* Dummynet hooks. */ 67ip_dn_ruledel_t *ip_dn_ruledel_ptr = NULL; 68 69/* Divert hooks. */ 70ip_divert_packet_t *ip_divert_ptr = NULL; 71 72/* Forward declarations. */ 73static int ipfw_divert(struct mbuf **, int, int); 74#define DIV_DIR_IN 1 75#define DIV_DIR_OUT 0 76 77int 78ipfw_check_in(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, 79 struct inpcb *inp) 80{ 81 struct ip_fw_args args; 82 struct m_tag *dn_tag; 83 int ipfw = 0; 84 int divert; 85 int tee; 86#ifdef IPFIREWALL_FORWARD 87 struct m_tag *fwd_tag; 88#endif 89 90 KASSERT(dir == PFIL_IN, ("ipfw_check_in wrong direction!")); 91 92 if (!fw_enable) 93 goto pass; 94 95 bzero(&args, sizeof(args)); 96 97 dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL); 98 if (dn_tag != NULL){ 99 struct dn_pkt_tag *dt; 100 101 dt = (struct dn_pkt_tag *)(dn_tag+1); 102 args.rule = dt->rule; 103 104 m_tag_delete(*m0, dn_tag); 105 } 106 107again: 108 args.m = *m0; 109 args.inp = inp; 110 ipfw = ipfw_chk(&args); 111 *m0 = args.m; 112 tee = 0; 113 114 KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL", 115 __func__)); 116 117 switch (ipfw) { 118 case IP_FW_PASS: 119 if (args.next_hop == NULL) 120 goto pass; 121 122#ifdef IPFIREWALL_FORWARD 123 fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD, 124 sizeof(struct sockaddr_in), M_NOWAIT); 125 if (fwd_tag == NULL) 126 goto drop; 127 bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in)); 128 m_tag_prepend(*m0, fwd_tag); 129 130 if (in_localip(args.next_hop->sin_addr)) 131 (*m0)->m_flags |= M_FASTFWD_OURS; 132 goto pass; 133#endif 134 break; /* not reached */ 135 136 case IP_FW_DENY: 137 goto drop; 138 break; /* not reached */ 139 140 case IP_FW_DUMMYNET: 141 if (!DUMMYNET_LOADED) 142 goto drop; 143 ip_dn_io_ptr(*m0, args.cookie, DN_TO_IP_IN, &args); 144 *m0 = NULL; 145 return 0; /* packet consumed */ 146 147 case IP_FW_TEE: 148 tee = 1; 149 /* fall through */ 150 151 case IP_FW_DIVERT: 152 divert = ipfw_divert(m0, DIV_DIR_IN, tee); 153 if (divert) { 154 *m0 = NULL; 155 return 0; /* packet consumed */ 156 } else 157 goto again; /* continue with packet */ 158 159 default: 160 KASSERT(0, ("%s: unknown retval", __func__)); 161 } 162 163drop: 164 if (*m0) 165 m_freem(*m0); 166 *m0 = NULL; 167 return (EACCES); 168pass: 169 return 0; /* not filtered */ 170} 171 172int 173ipfw_check_out(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, 174 struct inpcb *inp) 175{ 176 struct ip_fw_args args; 177 struct m_tag *dn_tag; 178 int ipfw = 0; 179 int divert; 180 int tee; 181#ifdef IPFIREWALL_FORWARD 182 struct m_tag *fwd_tag; 183#endif 184 185 KASSERT(dir == PFIL_OUT, ("ipfw_check_out wrong direction!")); 186 187 if (!fw_enable) 188 goto pass; 189 190 bzero(&args, sizeof(args)); 191 192 dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL); 193 if (dn_tag != NULL) { 194 struct dn_pkt_tag *dt; 195 196 dt = (struct dn_pkt_tag *)(dn_tag+1); 197 args.rule = dt->rule; 198 199 m_tag_delete(*m0, dn_tag); 200 } 201 202again: 203 args.m = *m0; 204 args.oif = ifp; 205 args.inp = inp; 206 ipfw = ipfw_chk(&args); 207 *m0 = args.m; 208 tee = 0; 209 210 KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL", 211 __func__)); 212 213 switch (ipfw) { 214 case IP_FW_PASS: 215 if (args.next_hop == NULL) 216 goto pass; 217#ifdef IPFIREWALL_FORWARD 218 /* Overwrite existing tag. */ 219 fwd_tag = m_tag_find(*m0, PACKET_TAG_IPFORWARD, NULL); 220 if (fwd_tag == NULL) { 221 fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD, 222 sizeof(struct sockaddr_in), M_NOWAIT); 223 if (fwd_tag == NULL) 224 goto drop; 225 } else 226 m_tag_unlink(*m0, fwd_tag); 227 bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in)); 228 m_tag_prepend(*m0, fwd_tag); 229 230 if (in_localip(args.next_hop->sin_addr)) 231 (*m0)->m_flags |= M_FASTFWD_OURS; 232 goto pass; 233#endif 234 break; /* not reached */ 235 236 case IP_FW_DENY: 237 goto drop; 238 break; /* not reached */ 239 240 case IP_FW_DUMMYNET: 241 if (!DUMMYNET_LOADED) 242 break; 243 ip_dn_io_ptr(*m0, args.cookie, DN_TO_IP_OUT, &args); 244 *m0 = NULL; 245 return 0; /* packet consumed */ 246 247 break; 248 249 case IP_FW_TEE: 250 tee = 1; 251 /* fall through */ 252 253 case IP_FW_DIVERT: 254 divert = ipfw_divert(m0, DIV_DIR_OUT, tee); 255 if (divert) { 256 *m0 = NULL; 257 return 0; /* packet consumed */ 258 } else 259 goto again; /* continue with packet */ 260 261 default: 262 KASSERT(0, ("%s: unknown retval", __func__)); 263 } 264 265drop: 266 if (*m0) 267 m_freem(*m0); 268 *m0 = NULL; 269 return (EACCES); 270pass: 271 return 0; /* not filtered */ 272} 273 274static int 275ipfw_divert(struct mbuf **m, int incoming, int tee) 276{ 277 /* 278 * ipfw_chk() has already tagged the packet with the divert tag. 279 * If tee is set, copy packet and return original. 280 * If not tee, consume packet and send it to divert socket. 281 */ 282 struct mbuf *clone, *reass; 283 struct ip *ip; 284 int hlen; 285 286 reass = NULL; 287 288 /* Is divert module loaded? */ 289 if (ip_divert_ptr == NULL) 290 goto nodivert; 291 292 /* Cloning needed for tee? */ 293 if (tee) 294 clone = m_dup(*m, M_DONTWAIT); 295 else 296 clone = *m; 297 298 /* In case m_dup was unable to allocate mbufs. */ 299 if (clone == NULL) 300 goto teeout; 301 302 /* 303 * Divert listeners can only handle non-fragmented packets. 304 * However when tee is set we will *not* de-fragment the packets; 305 * Doing do would put the reassembly into double-jeopardy. On top 306 * of that someone doing a tee will probably want to get the packet 307 * in its original form. 308 */ 309 ip = mtod(clone, struct ip *); 310 if (!tee && ip->ip_off & (IP_MF | IP_OFFMASK)) { 311 312 /* Reassemble packet. */ 313 reass = ip_reass(clone); 314 315 /* 316 * IP header checksum fixup after reassembly and leave header 317 * in network byte order. 318 */ 319 if (reass != NULL) { 320 ip = mtod(reass, struct ip *); 321 hlen = ip->ip_hl << 2; 322 ip->ip_len = htons(ip->ip_len); 323 ip->ip_off = htons(ip->ip_off); 324 ip->ip_sum = 0; 325 if (hlen == sizeof(struct ip)) 326 ip->ip_sum = in_cksum_hdr(ip); 327 else 328 ip->ip_sum = in_cksum(reass, hlen); 329 clone = reass; 330 } else 331 clone = NULL; 332 } else { 333 /* Convert header to network byte order. */ 334 ip->ip_len = htons(ip->ip_len); 335 ip->ip_off = htons(ip->ip_off); 336 } 337 338 /* Do the dirty job... */ 339 if (clone && ip_divert_ptr != NULL) 340 ip_divert_ptr(clone, incoming); 341 342teeout: 343 /* 344 * For tee we leave the divert tag attached to original packet. 345 * It will then continue rule evaluation after the tee rule. 346 */ 347 if (tee) 348 return 0; 349 350 /* Packet diverted and consumed */ 351 return 1; 352 353nodivert: 354 m_freem(*m); 355 return 1; 356} 357 358static int 359ipfw_hook(void) 360{ 361 struct pfil_head *pfh_inet; 362 363 if (ipfw_pfil_hooked) 364 return EEXIST; 365 366 pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); 367 if (pfh_inet == NULL) 368 return ENOENT; 369 370 pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet); 371 pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet); 372 373 return 0; 374} 375 376static int 377ipfw_unhook(void) 378{ 379 struct pfil_head *pfh_inet; 380 381 if (!ipfw_pfil_hooked) 382 return ENOENT; 383 384 pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); 385 if (pfh_inet == NULL) 386 return ENOENT; 387 388 pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet); 389 pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet); 390 391 return 0; 392} 393 394static int 395ipfw_modevent(module_t mod, int type, void *unused) 396{ 397 int err = 0; 398 399 switch (type) { 400 case MOD_LOAD: 401 if (ipfw_pfil_hooked) { 402 printf("IP firewall already loaded\n"); 403 err = EEXIST; 404 } else { 405 if ((err = ipfw_init()) != 0) { 406 printf("ipfw_init() error\n"); 407 break; 408 } 409 if ((err = ipfw_hook()) != 0) { 410 printf("ipfw_hook() error\n"); 411 break; 412 } 413 ipfw_pfil_hooked = 1; 414 } 415 break; 416 417 case MOD_UNLOAD: 418 if (ipfw_pfil_hooked) { 419 if ((err = ipfw_unhook()) > 0) 420 break; 421 ipfw_destroy(); 422 ipfw_pfil_hooked = 0; 423 } else { 424 printf("IP firewall already unloaded\n"); 425 } 426 break; 427 428 default: 429 return EOPNOTSUPP; 430 break; 431 } 432 return err; 433} 434 435static moduledata_t ipfwmod = { 436 "ipfw", 437 ipfw_modevent, 438 0 439}; 440DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY); 441MODULE_VERSION(ipfw, 2); 442