/*- * Copyright (c) 1999-2003 Andrey Simonenko * 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. */ #include "compat.h" #ifndef lint static const char rcsid[] ATTR_UNUSED = "@(#)$Id: kipfw.c,v 1.3 2006/04/24 22:03:26 simon Exp $"; #endif /* !lint */ #include #include #include #include #include #include #include #include #include #include "ipa_ipfw.h" #include "kipfw.h" /* This is logically wrong for IPFW2, see below. */ #define IPFW_INCREASE_ENTRIES 20 /* How many entries allocate in fw_buf. */ #define IPFW_FREE_ENTRIES 30 /* Max allowed free entries in fw_buf. */ /* * List of active kipfw_rules. Since all kipfw_rules use * marray, then they are placed in one chunk of memory, let's * keep active rules list sorted by rules addresses. * I suppose that this speedups linear searches. */ struct kipfw_rules_list kipfw_rules_active; uint64_t kipfw_bcnt_max; /* Max value of IP Firewall byte counter. */ int debug_ipfw; /* ipfw:debug_ipfw */ static struct ip_fw *fw_buf = NULL; /* Buffer for read kernel IPFW rules. */ static int sd = -1; /* Descriptor for socket. */ static size_t nbytesalloc; /* * Init IPFW support. */ int kipfw_init(void) { struct ip_fw tmp; if ( (sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) { logmsg(IPA_LOG_ERR, "kipfw_init: socket(AF_INET, SOCK_RAW, IPPROTO_RAW)"); return -1; } /* * This is logically wrong for IPFW2, but we should start * from some buffer, instead of checking kipfw_buf each * time in kipfw_read_table(). */ nbytesalloc = IPFW_INCREASE_ENTRIES * sizeof tmp; if ( (fw_buf = memfunc->mem_malloc(nbytesalloc, m_buf)) == NULL) { logmsgx(IPA_LOG_ERR, "kipfw_init mem_malloc failed"); return -1; } /* Assume, that IPFW_BCNT is unsigned. */ switch (sizeof(IPFW_BCNT(tmp))) { case 4: kipfw_bcnt_max = UINT32_MAX; break; case 8: kipfw_bcnt_max = UINT64_MAX; break; default: logmsgx(IPA_LOG_ERR, "kipfw_init: unsupported size of fw_bcnt (%lu bytes) in struct ip_fw", (u_long)sizeof(IPFW_BCNT(tmp))); return -1; } if (debug_ipfw) { logmsgx(IPA_LOG_INFO, "kipfw_init: raw socket opened, FD %d", sd); logmsgx(IPA_LOG_INFO, "kipfw_init: sizeof(struct ip_fw) = %lu, sizeof(fw_bcnt) = %lu", (u_long)sizeof(tmp), (u_long)sizeof(IPFW_BCNT(tmp))); } return 0; } /* * Dump IPFW rules to log. */ static void kipfw_dump_table(void) { #define TXT_BUF_LEN 60 char txt_buf[TXT_BUF_LEN + 1]; int len, total_len = 0; u_int subnumber = 0; int prevnum = -1; /* Actual number is u_short. */ char one_rule[1 + 5 + 1 + 10 + 1]; struct ip_fw *fw; /* * 1 - ' ' * 5 - length of rule number (u_short) * 1 - '.' * 10 - length of subnumber (u_int) * 1 - '\0' */ logmsgx(IPA_LOG_INFO, "kipfw_dump_table: dump IPFW table:"); for (fw = fw_buf;; IPFWP_NEXT_RULE(fw)) { if (prevnum == IPFWP_NUMBER(fw)) ++subnumber; else { prevnum = IPFWP_NUMBER(fw); subnumber = 0; } if (subnumber == 0) len = snprintf(one_rule, sizeof one_rule, " %hu", IPFWP_NUMBER(fw)); else len = snprintf(one_rule, sizeof one_rule, " %hu.%u", IPFWP_NUMBER(fw), subnumber); if (len < 0) logmsg(IPA_LOG_ERR, "kipfw_dump_table: snprintf failed"); else if (len + 1 > (int)sizeof(one_rule)) logmsg(IPA_LOG_WARNING, "kipfw_dump_table: not enough space (%d chars) in one_rule", len + 1); else { len = strlen(one_rule); if (total_len + len > TXT_BUF_LEN) { logmsgx(IPA_LOG_INFO, " **%s", txt_buf); total_len = 0; } strcpy(txt_buf + total_len, one_rule); total_len += len; } if (IPFWP_NUMBER(fw) == IPFW_NUMBER_MAX) break; } if (total_len != 0) /* Log the rest from txt_buf. */ logmsgx(IPA_LOG_INFO, " **%s", txt_buf); #undef TXT_BUF_LEN } /* * Read whole IPFW rules table from the kernel and find needed * rules in this table. */ int kipfw_get_stat(void) { u_short number; u_int subnumber; socklen_t nbytes; struct ip_fw *fw; struct kipfw_rule *kipfw_rule; nbytes = nbytesalloc; if (getsockopt(sd, IPPROTO_IP, IP_FW_GET, fw_buf, &nbytes) < 0) { logmsg(IPA_LOG_ERR, "kipfw_get_stat: getsockopt(IP_FW_GET)"); logmsgx(IPA_LOG_ERR, "kipfw_get_stat: IP Firewall is not configured in the kernel or does not work properly"); return -1; } if (debug_ipfw) logmsgx(IPA_LOG_INFO, "kipfw_get_stat: nbytes = %lu, nbytesalloc = %lu", (u_long)nbytes, (u_long)nbytesalloc); if (nbytes >= nbytesalloc) { /* There are some data more in the IPFW kernel table. */ while (nbytes >= nbytesalloc) { nbytes = nbytesalloc += IPFW_INCREASE_ENTRIES * sizeof *fw_buf; if ( (fw_buf = mem_realloc(fw_buf, nbytesalloc, m_buf)) == NULL) { logmsgx(IPA_LOG_ERR, "kipfw_get_stat: mem_realloc failed"); return -1; } if (getsockopt(sd, IPPROTO_IP, IP_FW_GET, fw_buf, &nbytes) < 0) { logmsg(IPA_LOG_ERR, "kipfw_get_stat: getsockopt(IP_FW_GET)"); logmsgx(IPA_LOG_ERR, "kipfw_get_stat: IP Firewall is not configured in the kernel or does not work properly"); return -1; } if (debug_ipfw) logmsgx(IPA_LOG_INFO, "kipfw_read_table: increase table: nbytes = %lu, nbytesalloc = %lu", (u_long)nbytes, (u_long)nbytesalloc); } } else if (nbytesalloc - nbytes > IPFW_FREE_ENTRIES * sizeof *fw_buf) { /* * Reallocate memory if we have too much. * This is not logically correct for IPFW2, because IPFW2 rules * don't have fixed size, nevertheless this code is correct. */ nbytesalloc = nbytes + (IPFW_FREE_ENTRIES - 1) * sizeof *fw_buf; if ( (fw_buf = mem_realloc(fw_buf, nbytesalloc, m_buf)) == NULL) { logmsgx(IPA_LOG_ERR, "kipfw_get_stat: mem_realloc failed"); return -1; } if (debug_ipfw) logmsgx(IPA_LOG_INFO, "kipfw_get_stat: decrease table: nbytesalloc = %lu", (u_long)nbytesalloc); } if (debug_ipfw) kipfw_dump_table(); /* * fw_buf is sorted by numbers by the kernel, * kipfw_rules_active is kept sorted by numbers and subnumbers. * We need only two loops to find needed IPFW rules. */ fw = fw_buf; for (kipfw_rule = TAILQ_FIRST(&kipfw_rules_active); kipfw_rule != NULL;) { if (IPFWP_NUMBER(fw) > kipfw_rule->number) { /* * No such number in fw_buf, all subnumbers will be * caught by this if() as well. */ kipfw_rule->ip_fw = NULL; kipfw_rule = TAILQ_NEXT(kipfw_rule, link); continue; } if (IPFWP_NUMBER(fw) < kipfw_rule->number) { /* * Our number is greater than current number in fw_buf, * skip some rules in fw_buf util equal or greater one * is found. */ for (IPFWP_NEXT_RULE(fw); IPFWP_NUMBER(fw) != IPFW_NUMBER_MAX; IPFWP_NEXT_RULE(fw)) if (IPFWP_NUMBER(fw) >= kipfw_rule->number) break; if (IPFWP_NUMBER(fw) > kipfw_rule->number) { /* * Greater number was found, all subnumbers * will be caught by the first if(). */ kipfw_rule->ip_fw = NULL; kipfw_rule = TAILQ_NEXT(kipfw_rule, link); continue; } } /* At this point we found matched number in fw_buf. */ /* Save current number in sense of the same subnumbers. */ number = IPFWP_NUMBER(fw); if (kipfw_rule->subnumber == 0) { /* * Common case for rule number.0 * (this if() is for optimization). */ kipfw_rule->ip_fw = fw; kipfw_rule = TAILQ_NEXT(kipfw_rule, link); if (kipfw_rule != NULL) if (kipfw_rule->number > number) /* Next rule isn't number.y. */ continue; } /* * Get next rule in fw_buf, this is correct for last IPFW rule, * because IPFW_NUMBER_MAX rule cannot have subnumbers, so we * will not be in this point for the last rule in fw_buf. */ IPFWP_NEXT_RULE(fw); /* Start from the number.1, because number.0 was checked above. */ for (subnumber = 1; kipfw_rule != NULL;) { if (kipfw_rule->number > number) /* * Out of subnumbers. Return to the * first if(). */ break; if (IPFWP_NUMBER(fw) == number) { /* There is rule with the same number. */ if (kipfw_rule->subnumber == subnumber) { /* Subnumber is found. */ kipfw_rule->ip_fw = fw; kipfw_rule = TAILQ_NEXT(kipfw_rule, link); } else { /* * We don't check if kipfw_rule->subnumber * is greater than current subnumber, because * everything is sorted and it is greater * than current subnumber. */ } /* Get next kernel rule. */ IPFWP_NEXT_RULE(fw); /* * Get next subnumber, it will be used only * if next kernel rule has the same number. */ ++subnumber; } else { /* IPFWP_NUMBER(fw) > number */ /* * Can't find subnumber. Let's handle * the rest of subnumbers in the first if(). */ kipfw_rule->ip_fw = NULL; kipfw_rule = TAILQ_NEXT(kipfw_rule, link); break; } } /* At this point kipfw_rule is NULL or has next number. */ } return 0; } /* * Deinit IPFW support. */ int kipfw_deinit(void) { mem_free(fw_buf, m_buf); if (sd >= 0) if (close(sd) < 0) { logmsg(IPA_LOG_ERR, "kipfw_deinit: close(FD %d)", sd); return -1; } return 0; }