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