summaryrefslogtreecommitdiffstats
path: root/sys/netinet/ip_fw_pfil.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/netinet/ip_fw_pfil.c')
-rw-r--r--sys/netinet/ip_fw_pfil.c402
1 files changed, 402 insertions, 0 deletions
diff --git a/sys/netinet/ip_fw_pfil.c b/sys/netinet/ip_fw_pfil.c
new file mode 100644
index 0000000..9056ab6
--- /dev/null
+++ b/sys/netinet/ip_fw_pfil.c
@@ -0,0 +1,402 @@
+/*
+ * Copyright (c) 2004 Andre Oppermann, Internet Business Solutions AG
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#if !defined(KLD_MODULE)
+#include "opt_ipfw.h"
+#include "opt_ipdn.h"
+#include "opt_ipdivert.h"
+#include "opt_inet.h"
+#ifndef INET
+#error IPFIREWALL requires INET.
+#endif /* INET */
+#endif
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/module.h>
+#include <sys/kernel.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+#include <sys/ucred.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <net/pfil.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/in_var.h>
+#include <netinet/ip.h>
+#include <netinet/ip_var.h>
+#include <netinet/ip_fw.h>
+#include <netinet/ip_divert.h>
+#include <netinet/ip_dummynet.h>
+
+#include <machine/in_cksum.h>
+
+static int ipfw_pfil_hooked = 0;
+
+/* Dummynet hooks. */
+ip_dn_ruledel_t *ip_dn_ruledel_ptr = NULL;
+
+#define DIV_DIR_IN 1
+#define DIV_DIR_OUT 0
+
+static int ipfw_divert(struct mbuf **, int, int);
+
+int
+ipfw_check_in(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir)
+{
+ struct ip_fw_args args;
+ struct m_tag *dn_tag;
+ int ipfw = 0;
+ int divert;
+#ifdef IPFIREWALL_FORWARD
+ struct m_tag *fwd_tag;
+#endif
+
+ KASSERT(dir == PFIL_IN, ("ipfw_check_in wrong direction!"));
+
+ bzero(&args, sizeof(args));
+
+ dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL);
+ if (dn_tag != NULL){
+ struct dn_pkt_tag *dt;
+
+ dt = (struct dn_pkt_tag *)(dn_tag+1);
+ args.rule = dt->rule;
+
+ m_tag_delete(*m0, dn_tag);
+ }
+
+ args.m = *m0;
+ ipfw = ipfw_chk(&args);
+ *m0 = args.m;
+
+ if ((ipfw & IP_FW_PORT_DENY_FLAG) || *m0 == NULL)
+ goto drop;
+
+ if (ipfw == 0 && args.next_hop == NULL)
+ goto pass;
+
+ if (DUMMYNET_LOADED && (ipfw & IP_FW_PORT_DYNT_FLAG) != 0) {
+ ip_dn_io_ptr(*m0, ipfw & 0xffff, DN_TO_IP_IN, &args);
+ *m0 = NULL;
+ return 0; /* packet consumed */
+ }
+
+ if (ipfw != 0 && (ipfw & IP_FW_PORT_DYNT_FLAG) == 0) {
+ if ((ipfw & IP_FW_PORT_TEE_FLAG) != 0)
+ divert = ipfw_divert(m0, DIV_DIR_IN, 1);
+ else
+ divert = ipfw_divert(m0, DIV_DIR_IN, 0);
+
+ /* tee should continue again with the firewall. */
+ if (divert) {
+ *m0 = NULL;
+ return 0; /* packet consumed */
+ } else
+ goto pass; /* continue with packet */
+ }
+
+#ifdef IPFIREWALL_FORWARD
+ if (ipfw == 0 && args.next_hop != NULL) {
+ fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD,
+ sizeof(struct sockaddr_in), M_NOWAIT);
+ if (fwd_tag == NULL)
+ goto drop;
+ bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in));
+ m_tag_prepend(*m0, fwd_tag);
+
+ if (in_localip(args.next_hop->sin_addr))
+ (*m0)->m_flags |= M_FASTFWD_OURS;
+ goto pass;
+ }
+#endif
+
+drop:
+ if (*m0)
+ m_freem(*m0);
+ *m0 = NULL;
+ return (EACCES);
+pass:
+ return 0; /* not filtered */
+}
+
+int
+ipfw_check_out(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir)
+{
+ struct ip_fw_args args;
+ struct m_tag *dn_tag;
+ int ipfw = 0;
+ int divert;
+#ifdef IPFIREWALL_FORWARD
+ struct m_tag *fwd_tag;
+#endif
+
+ KASSERT(dir == PFIL_OUT, ("ipfw_check_out wrong direction!"));
+
+ bzero(&args, sizeof(args));
+
+ dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL);
+ if (dn_tag != NULL) {
+ struct dn_pkt_tag *dt;
+
+ dt = (struct dn_pkt_tag *)(dn_tag+1);
+ args.rule = dt->rule;
+
+ m_tag_delete(*m0, dn_tag);
+ }
+
+ args.m = *m0;
+ args.oif = ifp;
+ ipfw = ipfw_chk(&args);
+ *m0 = args.m;
+
+ if ((ipfw & IP_FW_PORT_DENY_FLAG) || *m0 == NULL)
+ goto drop;
+
+ if (ipfw == 0 && args.next_hop == NULL)
+ goto pass;
+
+ if (DUMMYNET_LOADED && (ipfw & IP_FW_PORT_DYNT_FLAG) != 0) {
+ ip_dn_io_ptr(*m0, ipfw & 0xffff, DN_TO_IP_OUT, &args);
+ *m0 = NULL;
+ return 0; /* packet consumed */
+ }
+
+ if (ipfw != 0 && (ipfw & IP_FW_PORT_DYNT_FLAG) == 0) {
+ if ((ipfw & IP_FW_PORT_TEE_FLAG) != 0)
+ divert = ipfw_divert(m0, DIV_DIR_OUT, 1);
+ else
+ divert = ipfw_divert(m0, DIV_DIR_OUT, 0);
+
+ if (divert) {
+ *m0 = NULL;
+ return 0; /* packet consumed */
+ } else
+ goto pass; /* continue with packet */
+ }
+
+#ifdef IPFIREWALL_FORWARD
+ if (ipfw == 0 && args.next_hop != NULL) {
+ /* Overwrite existing tag. */
+ fwd_tag = m_tag_find(*m0, PACKET_TAG_IPFORWARD, NULL);
+ if (fwd_tag == NULL)
+ fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD,
+ sizeof(struct sockaddr_in), M_NOWAIT);
+ if (fwd_tag == NULL)
+ goto drop;
+ bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in));
+ m_tag_prepend(*m0, fwd_tag);
+
+ if (in_localip(args.next_hop->sin_addr))
+ (*m0)->m_flags |= M_FASTFWD_OURS;
+ goto pass;
+ }
+#endif
+
+drop:
+ if (*m0)
+ m_freem(*m0);
+ *m0 = NULL;
+ return (EACCES);
+pass:
+ return 0; /* not filtered */
+}
+
+static int
+ipfw_divert(struct mbuf **m, int incoming, int tee)
+{
+ /*
+ * ipfw_chk() has already tagged the packet with the divert
+ * tag. For tee we need to remove the tag.
+ * If tee is set, copy packet and return original.
+ * If not tee, consume packet and send it to divert socket.
+ */
+#ifdef IPDIVERT
+ struct mbuf *clone, *reass;
+ struct m_tag *mtag;
+ struct ip *ip;
+ int hlen;
+
+ reass = NULL;
+
+ /* Cloning needed for tee? */
+ if (tee)
+ clone = m_dup(*m, M_DONTWAIT);
+ else
+ clone = *m;
+
+ /* In case m_dup was unable to allocate mbufs. */
+ if (clone == NULL)
+ goto teeout;
+
+ /*
+ * Divert listeners can only handle non-fragmented packets.
+ * However when tee is set we will *not* de-fragment the packets;
+ * Doing do would put the reassembly into double-jeopardy. On top
+ * of that someone doing a tee will probably want to get the packet
+ * in its original form.
+ */
+ ip = mtod(clone, struct ip *);
+ if (!tee && ip->ip_off & (IP_MF | IP_OFFMASK)) {
+
+ /* Reassemble packet. */
+ reass = ip_reass(clone);
+
+ /*
+ * IP header checksum fixup after reassembly and leave header
+ * in network byte order.
+ */
+ if (reass != NULL) {
+ ip = mtod(reass, struct ip *);
+ hlen = ip->ip_hl << 2;
+ ip->ip_len = htons(ip->ip_len);
+ ip->ip_off = htons(ip->ip_off);
+ ip->ip_sum = 0;
+ if (hlen == sizeof(struct ip))
+ ip->ip_sum = in_cksum_hdr(ip);
+ else
+ ip->ip_sum = in_cksum(reass, hlen);
+ clone = reass;
+ } else
+ clone = NULL;
+ } else {
+ /* Convert header to network byte order. */
+ ip->ip_len = htons(ip->ip_len);
+ ip->ip_off = htons(ip->ip_off);
+ }
+
+ /* Do the dirty job... */
+ if (clone)
+ divert_packet(clone, incoming);
+
+teeout:
+ if (tee) {
+ mtag = m_tag_find(*m, PACKET_TAG_DIVERT, NULL);
+ if (mtag != NULL)
+ m_tag_delete(*m, mtag);
+ return 0; /* continue with original packet. */
+ }
+
+ /* Packet diverted and consumed */
+ return 1;
+#else
+ m_freem(*m);
+ return 1;
+#endif /* ipdivert */
+}
+
+static int
+ipfw_hook(void)
+{
+ struct pfil_head *pfh_inet;
+
+ if (ipfw_pfil_hooked)
+ return EEXIST;
+
+ pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
+ if (pfh_inet == NULL)
+ return ENOENT;
+
+ pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
+ pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
+
+ return 0;
+}
+
+static int
+ipfw_unhook(void)
+{
+ struct pfil_head *pfh_inet;
+
+ if (!ipfw_pfil_hooked)
+ return ENOENT;
+
+ pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
+ if (pfh_inet == NULL)
+ return ENOENT;
+
+ pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
+ pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
+
+ return 0;
+}
+
+static int
+ipfw_modevent(module_t mod, int type, void *unused)
+{
+ int err = 0;
+
+ switch (type) {
+ case MOD_LOAD:
+ if (ipfw_pfil_hooked) {
+ printf("IP firewall already loaded\n");
+ err = EEXIST;
+ } else {
+ if ((err = ipfw_init()) != 0) {
+ printf("ipfw_init() error\n");
+ break;
+ }
+ if ((err = ipfw_hook()) != 0) {
+ printf("ipfw_hook() error\n");
+ break;
+ }
+ ipfw_pfil_hooked = 1;
+ }
+ break;
+
+ case MOD_UNLOAD:
+ if (ipfw_pfil_hooked) {
+ if ((err = ipfw_unhook()) > 0);
+ break;
+ ipfw_destroy();
+ ipfw_pfil_hooked = 0;
+ } else {
+ printf("IP firewall already unloaded\n");
+ }
+ break;
+
+ default:
+ return EOPNOTSUPP;
+ break;
+ }
+ return err;
+}
+
+static moduledata_t ipfwmod = {
+ "ipfw",
+ ipfw_modevent,
+ 0
+};
+DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY);
+MODULE_VERSION(ipfw, 2);
OpenPOWER on IntegriCloud