diff options
Diffstat (limited to 'sysdep/bsd/fw.c')
-rw-r--r-- | sysdep/bsd/fw.c | 412 |
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 + |