summaryrefslogtreecommitdiff
path: root/sysdep/bsd/fw.c
diff options
context:
space:
mode:
Diffstat (limited to 'sysdep/bsd/fw.c')
-rw-r--r--sysdep/bsd/fw.c412
1 files changed, 412 insertions, 0 deletions
diff --git a/sysdep/bsd/fw.c b/sysdep/bsd/fw.c
new file mode 100644
index 00000000..af3ebb46
--- /dev/null
+++ b/sysdep/bsd/fw.c
@@ -0,0 +1,412 @@
+/*
+ * BIRD -- IPFW/PF manipulations
+ *
+ * (c) 2011 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <err.h>
+#include <net/route.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+
+#undef LOCAL_DEBUG
+
+#include "nest/bird.h"
+#include "nest/iface.h"
+#include "nest/route.h"
+#include "nest/protocol.h"
+#include "nest/iface.h"
+#include "lib/timer.h"
+#include "lib/unix.h"
+#include "lib/krt.h"
+#include "lib/string.h"
+#include "lib/socket.h"
+#ifdef CONFIG_FIREWALL
+#include "proto/firewall/firewall.h"
+#ifdef CONFIG_FIREWALL_IPFW
+#include "netinet/ip_fw.h"
+#endif
+#ifdef CONFIG_FIREWALL_PF
+#include "net/pfvar.h"
+#endif
+
+#ifdef CONFIG_FIREWALL_IPFW
+
+int ipfw_fd = -1;
+int ipfw_instance_count = 0;
+
+struct ipfw_priv {
+ int table; /* Table number */
+ pool *pool; /* Protocol pool */
+};
+
+int
+ipfw_do_cmd(int optname, void *optval, uintptr_t optlen)
+{
+ return setsockopt(ipfw_fd, IPPROTO_IP, optname, optval, optlen);
+}
+
+void *
+ipfw_fw_init(struct proto *p, char *table)
+{
+ pool *fwpool = p->pool;
+ int table_num = strtol(table, NULL, 10);
+ int tables_max;
+ size_t len = sizeof(tables_max);
+
+ if (sysctlbyname("net.inet.ip.fw.tables_max", &tables_max, &len, NULL, 0) == -1)
+ {
+ log(L_ERR "Error getting maximum ipfw table count");
+ tables_max = IPFW_TABLES_MAX;
+ }
+ DBG("ipfw maximum table count set to %d\n", tables_max);
+
+ if ((table_num < 0) || (table_num >= tables_max))
+ {
+ log(L_ERR "ipfw table %d is not within possible range (0..%d)", table_num, tables_max);
+ return NULL;
+ }
+
+ if (ipfw_fd == -1)
+ {
+ if ((ipfw_fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1)
+ {
+ log(L_ERR "ipfw: error opering raw socket: %m");
+ return NULL;
+ }
+ DBG("Opened IPFW socked %d\n", ipfw_fd);
+ }
+
+ struct ipfw_priv *priv = mb_alloc(fwpool, sizeof(struct ipfw_priv));
+
+ priv->table = table_num;
+ priv->pool = fwpool;
+
+ ipfw_instance_count++;
+
+ return priv;
+}
+
+void
+ipfw_fw_shutdown(void *_priv)
+{
+ struct ipfw_priv *priv = _priv;
+
+ if (--ipfw_instance_count == 0)
+ {
+ DBG("Closing ipfw socket %d\n", ipfw_fd);
+ close(ipfw_fd);
+ ipfw_fd = -1;
+ }
+
+ mb_free(priv);
+}
+
+int
+ipfw_fw_flush(void *_priv)
+{
+ struct ipfw_priv *priv = _priv;
+ ipfw_table_entry ent;
+
+ memset(&ent, 0, sizeof(ent));
+ ent.tbl = priv->table;
+
+ log(L_DEBUG "Flushing ipfw table %d", priv->table);
+
+ if (ipfw_do_cmd(IP_FW_TABLE_FLUSH, &ent.tbl, sizeof(ent.tbl)) == -1)
+ {
+ log(L_ERR "Error flushing ipfw table %d: %m", priv->table);
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+ipfw_fw_add(void *_priv, net *n, char *prefixdata)
+{
+ struct ipfw_priv *priv = _priv;
+ ip_addr addr;
+ ipfw_table_entry ent;
+
+ addr = n->n.prefix;
+ ipa_hton(addr);
+
+ ent.masklen = n->n.pxlen;
+ memcpy(&ent.addr, &addr, sizeof(ip_addr));
+ ent.value = strtol(prefixdata, NULL, 0);
+ ent.tbl = priv->table;
+
+ DBG("Adding %I/%d to ipfw table %d with value %s\n", n->n.prefix, n->n.pxlen, priv->table, prefixdata);
+
+ if (ipfw_do_cmd(IP_FW_TABLE_ADD, &ent, sizeof(ent)) == -1)
+ {
+ log(L_ERR "Error adding %I/%d to ipfw table %d: %m", n->n.prefix, n->n.pxlen, priv->table);
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+ipfw_fw_del(void *_priv, net *n)
+{
+ struct ipfw_priv *priv = _priv;
+ ip_addr addr;
+ ipfw_table_entry ent;
+
+ addr = n->n.prefix;
+ ipa_hton(addr);
+
+ ent.masklen = n->n.pxlen;
+ memcpy(&ent.addr, &addr, sizeof(ip_addr));
+ ent.value = 0;
+ ent.tbl = priv->table;
+
+ DBG("Removing %I/%d from ipfw table %d\n", n->n.prefix, n->n.pxlen, priv->table);
+
+ if (ipfw_do_cmd(IP_FW_TABLE_DEL, &ent, sizeof(ent)) == -1)
+ {
+ log(L_ERR "Error removing %I/%d from ipfw table %d: %m", n->n.prefix, n->n.pxlen, priv->table);
+ return 0;
+ }
+
+ return 1;
+}
+
+struct firewall_control fw_ipfw = {
+ fwtype: FWTYPE_IPFW,
+ description: "IPFW",
+ fw_init: ipfw_fw_init,
+ fw_shutdown: ipfw_fw_shutdown,
+ fw_flush: ipfw_fw_flush,
+ fw_add: ipfw_fw_add,
+ fw_del: ipfw_fw_del,
+};
+#endif
+
+#ifdef CONFIG_FIREWALL_PF
+
+#define PF_DEVNAME "/dev/pf"
+int pf_fd = -1;
+int pf_instance_count = 0;
+
+struct pf_priv {
+ struct pfr_table table; /* PF table structure */
+ pool *pool; /* Protocol pool */
+};
+
+#define pf_tablename table.pfrt_name
+
+int
+pf_do_cmd(struct pfr_table *tbl, unsigned long cmd, void *buffer, int esize, int items, int *nadd, int *ndel, int flags)
+{
+ struct pfioc_table io;
+
+ bzero(&io, sizeof(io));
+ io.pfrio_flags = flags;
+ if (tbl)
+ io.pfrio_table = *tbl;
+ io.pfrio_buffer = buffer;
+ io.pfrio_esize = esize;
+ io.pfrio_size = items;
+
+ /* DBG("Doing PF ioctl %X for table %s on fd %d\n", cmd, tbl ? tbl->pfrt_name : "NULL", pf_fd); */
+ if (ioctl(pf_fd, cmd, &io))
+ return 0;
+
+ if (nadd)
+ *nadd = io.pfrio_nadd;
+ if (ndel)
+ *ndel = io.pfrio_ndel;
+
+ return 1;
+}
+
+void *
+pf_fw_init(struct proto *p, char *table)
+{
+ pool *fwpool = p->pool;
+ struct pfr_table pf_table;
+ int nadd = 0;
+
+ if (strlen(table) > PF_TABLE_NAME_SIZE)
+ {
+ log(L_ERR "PF table name too long, max %d", PF_TABLE_NAME_SIZE);
+ return NULL;
+ }
+
+ memset(&pf_table, 0, sizeof(pf_table));
+
+ if (pf_fd == -1)
+ {
+ if ((pf_fd = open(PF_DEVNAME, O_RDWR)) == -1)
+ {
+ log(L_ERR "pf: error opening %s: %m", PF_DEVNAME);
+ return NULL;
+ }
+
+ DBG("Opened PF socked %d\n", pf_fd);
+ }
+
+ strcpy(pf_table.pfrt_name, table);
+ pf_table.pfrt_flags |= PFR_TFLAG_PERSIST;
+ if (!pf_do_cmd(NULL, DIOCRADDTABLES, &pf_table, sizeof(pf_table), 1, &nadd, NULL, 0))
+ {
+ log(L_ERR "Error creating PF table %s: %m", table);
+ if (pf_instance_count == 0)
+ {
+ log(L_ERR "Closing PF socket");
+ close(pf_fd);
+ pf_fd = -1;
+ }
+ return NULL;
+ }
+ DBG("PF table %s created\n", table);
+ /* Remove persistent flag */
+ pf_table.pfrt_flags = 0;
+
+ struct pf_priv *priv = mb_alloc(fwpool, sizeof(struct pf_priv));
+
+ priv->table = pf_table;
+ priv->pool = fwpool;
+
+ pf_instance_count++;
+
+ return priv;
+}
+
+void
+pf_fw_shutdown(void *_priv)
+{
+ struct pf_priv *priv = _priv;
+
+ if (--pf_instance_count == 0)
+ {
+ DBG("Closing PF socket %d\n", pf_fd);
+ close(pf_fd);
+ pf_fd = -1;
+ }
+
+ mb_free(priv);
+}
+
+int
+pf_fw_flush(void *_priv)
+{
+ struct pf_priv *priv = _priv;
+ int ndel;
+
+ log(L_DEBUG "Flushing PF table %s", priv->pf_tablename);
+
+ if (!pf_do_cmd(&priv->table, DIOCRCLRADDRS, NULL, 0, 0, NULL, &ndel, 0))
+ {
+ log(L_ERR "Error flushing PF table %s: %m", priv->pf_tablename);
+ return 0;
+ }
+
+ DBG("Flushed %d record(s) from PF table %s\n", ndel, priv->pf_tablename);
+
+ return 1;
+}
+
+static int
+pf_put_addr(struct pfr_addr *pf_addr, net *n)
+{
+ int rt_family = AF_INET;
+ ip_addr addr;
+
+ memset(pf_addr, 0, sizeof(struct pfr_addr));
+ pf_addr->pfra_not = 0;
+ pf_addr->pfra_net = n->n.pxlen;
+ switch (rt_family)
+ {
+ case AF_INET:
+ addr = n->n.prefix;
+ ipa_hton(addr);
+ pf_addr->pfra_ip4addr.s_addr = addr;
+ pf_addr->pfra_af = rt_family;
+ break;
+ default:
+ log(L_ERR "Address family %d is not supported by pf, ignoring prefix", rt_family);
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+pf_fw_add(void *_priv, net *n, char *prefixdata)
+{
+ struct pf_priv *priv = _priv;
+ struct pfr_addr pf_addr;
+ int nadd = 0;
+
+ if (!pf_put_addr(&pf_addr, n))
+ {
+ log(L_ERR "Error adding %I/%d to PF table %s", n->n.prefix, n->n.pxlen, priv->pf_tablename);
+ return 0;
+ }
+
+ DBG("Adding %I/%d to PF table %s with value %s\n", n->n.prefix, n->n.pxlen, priv->pf_tablename, prefixdata);
+ if (!pf_do_cmd(&priv->table, DIOCRADDADDRS, &pf_addr, sizeof(pf_addr), 1, &nadd, NULL, 0))
+ {
+ log(L_ERR "Error adding %I/%d to PF table %s: %m", n->n.prefix, n->n.pxlen, priv->pf_tablename);
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+pf_fw_del(void *_priv, net *n)
+{
+ struct pf_priv *priv = _priv;
+ struct pfr_addr pf_addr;
+ int ndel = 0;
+
+ if (!pf_put_addr(&pf_addr, n))
+ {
+ log(L_ERR "Error deleting %I/%d from PF table %s", n->n.prefix, n->n.pxlen, priv->pf_tablename);
+ return 0;
+ }
+
+ DBG("Deleting %I/%d from PF table %s\n", n->n.prefix, n->n.pxlen, priv->pf_tablename);
+ if (!pf_do_cmd(&priv->table, DIOCRDELADDRS, &pf_addr, sizeof(pf_addr), 1, NULL, &ndel, 0))
+ {
+ log(L_ERR "Error deleting %I/%d from PF table %s: %m", n->n.prefix, n->n.pxlen, priv->pf_tablename);
+ return 0;
+ }
+
+ return 1;
+}
+
+struct firewall_control fw_pf = {
+ fwtype: FWTYPE_PF,
+ description: "PF",
+ fw_init: pf_fw_init,
+ fw_shutdown: pf_fw_shutdown,
+ fw_flush: pf_fw_flush,
+ fw_add: pf_fw_add,
+ fw_del: pf_fw_del,
+};
+#endif
+
+
+#endif
+