ip_fw_pfil.c revision 201122
1255219Spjd/*- 2255219Spjd * Copyright (c) 2004 Andre Oppermann, Internet Business Solutions AG 3255219Spjd * All rights reserved. 4255219Spjd * 5255219Spjd * Redistribution and use in source and binary forms, with or without 6255219Spjd * modification, are permitted provided that the following conditions 7255219Spjd * are met: 8255219Spjd * 1. Redistributions of source code must retain the above copyright 9255219Spjd * notice, this list of conditions and the following disclaimer. 10255219Spjd * 2. Redistributions in binary form must reproduce the above copyright 11255219Spjd * notice, this list of conditions and the following disclaimer in the 12255219Spjd * documentation and/or other materials provided with the distribution. 13255219Spjd * 14255219Spjd * 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 201122 2009-12-28 10:47:04Z luigi $"); 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/sysctl.h> 50 51#include <net/if.h> 52#include <net/route.h> 53#include <net/pfil.h> 54#include <net/vnet.h> 55 56#include <netinet/in.h> 57#include <netinet/in_systm.h> 58#include <netinet/ip.h> 59#include <netinet/ip_var.h> 60#include <netinet/ip_fw.h> 61#include <netinet/ipfw/ip_fw_private.h> 62#include <netinet/ip_divert.h> 63#include <netinet/ip_dummynet.h> 64 65#include <machine/in_cksum.h> 66 67static VNET_DEFINE(int, fw_enable) = 1; 68#define V_fw_enable VNET(fw_enable) 69 70#ifdef INET6 71static VNET_DEFINE(int, fw6_enable) = 1; 72#define V_fw6_enable VNET(fw6_enable) 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 void ipfw_divert(struct mbuf **, int, int); 85 86#ifdef SYSCTL_NODE 87SYSCTL_DECL(_net_inet_ip_fw); 88SYSCTL_VNET_PROC(_net_inet_ip_fw, OID_AUTO, enable, 89 CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE3, &VNET_NAME(fw_enable), 0, 90 ipfw_chg_hook, "I", "Enable ipfw"); 91#ifdef INET6 92SYSCTL_DECL(_net_inet6_ip6_fw); 93SYSCTL_VNET_PROC(_net_inet6_ip6_fw, OID_AUTO, enable, 94 CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE3, &VNET_NAME(fw6_enable), 0, 95 ipfw_chg_hook, "I", "Enable ipfw+6"); 96#endif /* INET6 */ 97#endif /* SYSCTL_NODE */ 98 99/* 100 * The pfilter hook to pass packets to ipfw_chk and then to 101 * dummynet, divert, netgraph or other modules. 102 * The packet may be consumed. 103 */ 104static int 105ipfw_check_hook(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, 106 struct inpcb *inp) 107{ 108 struct ip_fw_args args; 109 struct ng_ipfw_tag *ng_tag; 110 struct m_tag *dn_tag; 111 int ipfw; 112 int ret; 113#ifdef IPFIREWALL_FORWARD 114 struct m_tag *fwd_tag; 115#endif 116 117 /* convert dir to IPFW values */ 118 dir = (dir == PFIL_IN) ? DIR_IN : DIR_OUT; 119 bzero(&args, sizeof(args)); 120 121 ng_tag = (struct ng_ipfw_tag *)m_tag_locate(*m0, NGM_IPFW_COOKIE, 0, 122 NULL); 123 if (ng_tag != NULL) { 124 KASSERT(ng_tag->dir == dir, 125 ("ng_ipfw tag with wrong direction")); 126 args.slot = ng_tag->slot; 127 args.rulenum = ng_tag->rulenum; 128 args.rule_id = ng_tag->rule_id; 129 args.chain_id = ng_tag->chain_id; 130 m_tag_delete(*m0, (struct m_tag *)ng_tag); 131 } 132 133again: 134 dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL); 135 if (dn_tag != NULL) { 136 struct dn_pkt_tag *dt; 137 138 dt = (struct dn_pkt_tag *)(dn_tag+1); 139 args.slot = dt->slot; 140 args.rulenum = dt->rulenum; 141 args.rule_id = dt->rule_id; 142 args.chain_id = dt->chain_id; 143 m_tag_delete(*m0, dn_tag); 144 } 145 146 args.m = *m0; 147 args.oif = dir == DIR_OUT ? ifp : NULL; 148 args.inp = inp; 149 150 if (V_fw_one_pass == 0 || args.slot == 0) { 151 ipfw = ipfw_chk(&args); 152 *m0 = args.m; 153 } else 154 ipfw = IP_FW_PASS; 155 156 KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL", 157 __func__)); 158 159 /* breaking out of the switch means drop */ 160 ret = 0; /* default return value for pass */ 161 switch (ipfw) { 162 case IP_FW_PASS: 163 /* next_hop may be set by ipfw_chk */ 164 if (args.next_hop == NULL) 165 break; /* pass */ 166#ifndef IPFIREWALL_FORWARD 167 ret = EACCES; 168#else 169 /* Incoming packets should not be tagged so we do not 170 * m_tag_find. Outgoing packets may be tagged, so we 171 * reuse the tag if present. 172 */ 173 fwd_tag = (dir == DIR_IN) ? NULL : 174 m_tag_find(*m0, PACKET_TAG_IPFORWARD, NULL); 175 if (fwd_tag != NULL) { 176 m_tag_unlink(*m0, fwd_tag); 177 } else { 178 fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD, 179 sizeof(struct sockaddr_in), M_NOWAIT); 180 if (fwd_tag == NULL) { 181 ret = EACCES; 182 break; /* i.e. drop */ 183 } 184 } 185 bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in)); 186 m_tag_prepend(*m0, fwd_tag); 187 188 if (in_localip(args.next_hop->sin_addr)) 189 (*m0)->m_flags |= M_FASTFWD_OURS; 190#endif 191 break; 192 193 case IP_FW_DENY: 194 ret = EACCES; 195 break; /* i.e. drop */ 196 197 case IP_FW_DUMMYNET: 198 ret = EACCES; 199 if (ip_dn_io_ptr == NULL) 200 break; /* i.e. drop */ 201 if (mtod(*m0, struct ip *)->ip_v == 4) 202 ret = ip_dn_io_ptr(m0, dir, &args); 203 else if (mtod(*m0, struct ip *)->ip_v == 6) 204 ret = ip_dn_io_ptr(m0, dir | PROTO_IPV6, &args); 205 else 206 break; /* drop it */ 207 /* 208 * XXX should read the return value. 209 * dummynet normally eats the packet and sets *m0=NULL 210 * unless the packet can be sent immediately. In this 211 * case args is updated and we should re-run the 212 * check without clearing args. 213 */ 214 if (*m0 != NULL) 215 goto again; 216 break; 217 218 case IP_FW_TEE: 219 case IP_FW_DIVERT: 220 if (ip_divert_ptr == NULL) { 221 ret = EACCES; 222 break; /* i.e. drop */ 223 } 224 ipfw_divert(m0, dir, (ipfw == IP_FW_TEE) ? 1 : 0); 225 if (*m0) { 226 /* continue processing for this one. We set 227 * args.slot=0, but the divert tag is processed 228 * in ipfw_chk to jump to the right place. 229 */ 230 args.slot = 0; 231 goto again; /* continue with packet */ 232 } 233 break; 234 235 case IP_FW_NGTEE: 236 case IP_FW_NETGRAPH: 237 if (!NG_IPFW_LOADED) { 238 ret = EACCES; 239 break; /* i.e. drop */ 240 } 241 ret = ng_ipfw_input_p(m0, dir, &args, 242 (ipfw == IP_FW_NGTEE) ? 1 : 0); 243 if (ipfw == IP_FW_NGTEE) /* ignore errors for NGTEE */ 244 goto again; /* continue with packet */ 245 break; 246 247 case IP_FW_NAT: 248 case IP_FW_REASS: 249 goto again; /* continue with packet */ 250 251 default: 252 KASSERT(0, ("%s: unknown retval", __func__)); 253 } 254 255 if (ret != 0) { 256 if (*m0) 257 m_freem(*m0); 258 *m0 = NULL; 259 } 260 return ret; 261} 262 263static void 264ipfw_divert(struct mbuf **m0, int incoming, int tee) 265{ 266 /* 267 * ipfw_chk() has already tagged the packet with the divert tag. 268 * If tee is set, copy packet and return original. 269 * If not tee, consume packet and send it to divert socket. 270 */ 271 struct mbuf *clone; 272 struct ip *ip; 273 274 /* Cloning needed for tee? */ 275 if (tee == 0) { 276 clone = *m0; /* use the original mbuf */ 277 *m0 = NULL; 278 } else { 279 clone = m_dup(*m0, M_DONTWAIT); 280 /* If we cannot duplicate the mbuf, we sacrifice the divert 281 * chain and continue with the tee-ed packet. 282 */ 283 if (clone == NULL) 284 return; 285 } 286 287 /* 288 * Divert listeners can normally handle non-fragmented packets, 289 * but we can only reass in the non-tee case. 290 * This means that listeners on a tee rule may get fragments, 291 * and have to live with that. 292 * Note that we now have the 'reass' ipfw option so if we care 293 * we can do it before a 'tee'. 294 */ 295 ip = mtod(clone, struct ip *); 296 if (!tee && ip->ip_off & (IP_MF | IP_OFFMASK)) { 297 int hlen; 298 struct mbuf *reass; 299 300 reass = ip_reass(clone); /* Reassemble packet. */ 301 if (reass == NULL) 302 return; 303 /* if reass = NULL then it was consumed by ip_reass */ 304 /* 305 * IP header checksum fixup after reassembly and leave header 306 * in network byte order. 307 */ 308 ip = mtod(reass, struct ip *); 309 hlen = ip->ip_hl << 2; 310 SET_NET_IPLEN(ip); 311 ip->ip_sum = 0; 312 if (hlen == sizeof(struct ip)) 313 ip->ip_sum = in_cksum_hdr(ip); 314 else 315 ip->ip_sum = in_cksum(reass, hlen); 316 clone = reass; 317 } else { 318 /* Convert header to network byte order. */ 319 SET_NET_IPLEN(ip); 320 } 321 322 /* Do the dirty job... */ 323 ip_divert_ptr(clone, incoming); 324} 325 326/* 327 * attach or detach hooks for a given protocol family 328 */ 329static int 330ipfw_hook(int onoff, int pf) 331{ 332 const int arg = PFIL_IN | PFIL_OUT | PFIL_WAITOK; 333 struct pfil_head *pfh; 334 335 pfh = pfil_head_get(PFIL_TYPE_AF, pf); 336 if (pfh == NULL) 337 return ENOENT; 338 339 if (onoff) 340 (void)pfil_add_hook(ipfw_check_hook, NULL, arg, pfh); 341 else 342 (void)pfil_remove_hook(ipfw_check_hook, NULL, arg, pfh); 343 344 return 0; 345} 346 347int 348ipfw_attach_hooks(int arg) 349{ 350 int error = 0; 351 352 if (arg == 0) /* detach */ 353 ipfw_hook(0, AF_INET); 354 else if (V_fw_enable && ipfw_hook(1, AF_INET) != 0) { 355 error = ENOENT; /* see ip_fw_pfil.c::ipfw_hook() */ 356 printf("ipfw_hook() error\n"); 357 } 358#ifdef INET6 359 if (arg == 0) /* detach */ 360 ipfw_hook(0, AF_INET6); 361 else if (V_fw6_enable && ipfw_hook(1, AF_INET6) != 0) { 362 error = ENOENT; 363 printf("ipfw6_hook() error\n"); 364 } 365#endif 366 return error; 367} 368 369int 370ipfw_chg_hook(SYSCTL_HANDLER_ARGS) 371{ 372 int enable; 373 int oldenable; 374 int error; 375 int af; 376 377 if (arg1 == &VNET_NAME(fw_enable)) { 378 enable = V_fw_enable; 379 af = AF_INET; 380 } 381#ifdef INET6 382 else if (arg1 == &VNET_NAME(fw6_enable)) { 383 enable = V_fw6_enable; 384 af = AF_INET6; 385 } 386#endif 387 else 388 return (EINVAL); 389 390 oldenable = enable; 391 392 error = sysctl_handle_int(oidp, &enable, 0, req); 393 394 if (error) 395 return (error); 396 397 enable = (enable) ? 1 : 0; 398 399 if (enable == oldenable) 400 return (0); 401 402 error = ipfw_hook(enable, af); 403 if (error) 404 return (error); 405 if (af == AF_INET) 406 V_fw_enable = enable; 407#ifdef INET6 408 else if (af == AF_INET6) 409 V_fw6_enable = enable; 410#endif 411 412 return (0); 413} 414/* end of file */ 415