From: Jo-Philipp Wich Date: Sun, 17 Feb 2013 13:31:47 +0000 (+0100) Subject: initial commit X-Git-Url: http://git.openwrt.org/?p=project%2Ffirewall3.git;a=commitdiff_plain;h=8fee8f9c520c58d07772cc6bd8f65d9eb1776a56 initial commit --- 8fee8f9c520c58d07772cc6bd8f65d9eb1776a56 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e944949 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(firewall3 C) +ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +IF(APPLE) + INCLUDE_DIRECTORIES(/opt/local/include) + LINK_DIRECTORIES(/opt/local/lib) +ENDIF() + +ADD_EXECUTABLE(firewall3 main.c options.c defaults.c zones.c forwards.c rules.c redirects.c utils.c ubus.c ipsets.c) +TARGET_LINK_LIBRARIES(firewall3 uci ubox ubus) + +SET(CMAKE_INSTALL_PREFIX /usr) + +INSTALL(TARGETS firewall3 + RUNTIME DESTINATION sbin +) diff --git a/defaults.c b/defaults.c new file mode 100644 index 0000000..498e5d5 --- /dev/null +++ b/defaults.c @@ -0,0 +1,283 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "defaults.h" + + +static struct fw3_option default_opts[] = { + FW3_OPT("input", target, defaults, policy_input), + FW3_OPT("forward", target, defaults, policy_forward), + FW3_OPT("output", target, defaults, policy_output), + + FW3_OPT("drop_invalid", bool, defaults, drop_invalid), + + FW3_OPT("syn_flood", bool, defaults, syn_flood), + FW3_OPT("synflood_protect", bool, defaults, syn_flood), + FW3_OPT("synflood_rate", limit, defaults, syn_flood_rate), + FW3_OPT("synflood_burst", int, defaults, syn_flood_rate.burst), + + FW3_OPT("tcp_syncookies", bool, defaults, tcp_syncookies), + FW3_OPT("tcp_ecn", bool, defaults, tcp_ecn), + FW3_OPT("tcp_westwood", bool, defaults, tcp_westwood), + FW3_OPT("tcp_window_scaling", bool, defaults, tcp_window_scaling), + + FW3_OPT("accept_redirects", bool, defaults, accept_redirects), + FW3_OPT("accept_source_route", bool, defaults, accept_source_route), + + FW3_OPT("custom_chains", bool, defaults, custom_chains), + FW3_OPT("disable_ipv6", bool, defaults, disable_ipv6), +}; + + +static void +check_policy(struct uci_element *e, enum fw3_target *pol, const char *name) +{ + if (*pol == FW3_TARGET_UNSPEC) + { + warn_elem(e, "has no %s policy specified, defaulting to DROP", name); + *pol = FW3_TARGET_DROP; + } + else if (*pol > FW3_TARGET_DROP) + { + warn_elem(e, "has invalid %s policy, defaulting to DROP", name); + *pol = FW3_TARGET_DROP; + } +} + +void +fw3_load_defaults(struct fw3_state *state, struct uci_package *p) +{ + struct uci_section *s; + struct uci_element *e; + struct fw3_defaults *defs = &state->defaults; + + bool seen = false; + + defs->syn_flood_rate.rate = 25; + defs->syn_flood_rate.burst = 50; + defs->tcp_syncookies = true; + defs->tcp_window_scaling = true; + defs->custom_chains = true; + + uci_foreach_element(&p->sections, e) + { + s = uci_to_section(e); + + if (strcmp(s->type, "defaults")) + continue; + + if (seen) + { + warn_elem(e, "ignoring duplicate section"); + continue; + } + + fw3_parse_options(&state->defaults, + default_opts, ARRAY_SIZE(default_opts), s); + + check_policy(e, &defs->policy_input, "input"); + check_policy(e, &defs->policy_output, "output"); + check_policy(e, &defs->policy_forward, "forward"); + } +} + +void +fw3_print_default_chains(enum fw3_table table, enum fw3_family family, + struct fw3_state *state) +{ + struct fw3_defaults *defs = &state->defaults; + const char *policy[] = { + "(bug)", + "ACCEPT", + "DROP", + "DROP", + "(bug)", + "(bug)", + "(bug)", + }; + + switch (table) + { + case FW3_TABLE_FILTER: + fw3_pr(":INPUT %s [0:0]\n", policy[defs->policy_input]); + fw3_pr(":FORWARD %s [0:0]\n", policy[defs->policy_forward]); + fw3_pr(":OUTPUT %s [0:0]\n", policy[defs->policy_output]); + + if (defs->custom_chains) + { + fw3_pr(":input_rule - [0:0]\n"); + fw3_pr(":output_rule - [0:0]\n"); + fw3_pr(":forwarding_rule - [0:0]\n"); + } + + fw3_pr(":delegate_input - [0:0]\n"); + fw3_pr(":delegate_output - [0:0]\n"); + fw3_pr(":delegate_forward - [0:0]\n"); + fw3_pr(":reject - [0:0]\n"); + fw3_pr(":syn_flood - [0:0]\n"); + break; + + case FW3_TABLE_NAT: + if (defs->custom_chains) + { + fw3_pr(":prerouting_rule - [0:0]\n"); + fw3_pr(":postrouting_rule - [0:0]\n"); + } + break; + + case FW3_TABLE_MANGLE: + fw3_pr(":mssfix - [0:0]\n"); + break; + + case FW3_TABLE_RAW: + if (!defs->drop_invalid) + fw3_pr(":notrack - [0:0]\n"); + break; + } +} + +void +fw3_print_default_rules(enum fw3_table table, enum fw3_family family, + struct fw3_state *state) +{ + int i; + struct fw3_defaults *defs = &state->defaults; + const char *chains[] = { + "INPUT", + "OUTPUT", + "FORWARD", + }; + + switch (table) + { + case FW3_TABLE_FILTER: + fw3_pr("-A INPUT -i lo -j ACCEPT\n"); + fw3_pr("-A OUTPUT -o lo -j ACCEPT\n"); + + for (i = 0; i < ARRAY_SIZE(chains); i++) + { + fw3_pr("-A %s -m conntrack --ctstate RELATED,ESTABLISHED " + "-j ACCEPT\n", chains[i]); + + if (defs->drop_invalid) + { + fw3_pr("-A %s -m conntrack --ctstate INVALID -j DROP\n", + chains[i]); + } + } + + if (defs->syn_flood) + { + fw3_pr("-A syn_flood -p tcp --syn"); + fw3_format_limit(&defs->syn_flood_rate); + fw3_pr(" -j RETURN\n"); + + fw3_pr("-A syn_flood -j DROP\n"); + fw3_pr("-A INPUT -p tcp --syn -j syn_flood\n"); + } + + if (defs->custom_chains) + { + fw3_pr("-A INPUT -j input_rule\n"); + fw3_pr("-A OUTPUT -j output_rule\n"); + fw3_pr("-A FORWARD -j forwarding_rule\n"); + } + + fw3_pr("-A INPUT -j delegate_input\n"); + fw3_pr("-A OUTPUT -j delegate_output\n"); + fw3_pr("-A FORWARD -j delegate_forward\n"); + + fw3_pr("-A reject -p tcp -j REJECT --reject-with tcp-reset\n"); + fw3_pr("-A reject -j REJECT --reject-with port-unreach\n"); + + if (defs->policy_input == FW3_TARGET_REJECT) + fw3_pr("-A INPUT -j reject\n"); + + if (defs->policy_output == FW3_TARGET_REJECT) + fw3_pr("-A OUTPUT -j reject\n"); + + if (defs->policy_forward == FW3_TARGET_REJECT) + fw3_pr("-A FORWARD -j reject\n"); + + break; + + case FW3_TABLE_NAT: + if (defs->custom_chains) + { + fw3_pr("-A PREROUTING -j prerouting_rule\n"); + fw3_pr("-A POSTROUTING -j postrouting_rule\n"); + } + break; + + case FW3_TABLE_MANGLE: + fw3_pr("-A FORWARD -j mssfix\n"); + break; + + case FW3_TABLE_RAW: + if (!defs->drop_invalid) + fw3_pr("-A PREROUTING -j notrack\n"); + break; + } +} + +void +fw3_print_flush_rules(enum fw3_table table, enum fw3_family family, + struct fw3_state *state, bool complete) +{ + switch (table) + { + case FW3_TABLE_FILTER: + fw3_pr(":INPUT ACCEPT [0:0]\n"); + fw3_pr(":OUTPUT ACCEPT [0:0]\n"); + fw3_pr(":FORWARD ACCEPT [0:0]\n"); + /* fall through */ + + case FW3_TABLE_NAT: + fw3_pr("-F\n"); + fw3_pr("-X\n"); + break; + + case FW3_TABLE_MANGLE: + if (complete) + { + fw3_pr("-F\n"); + fw3_pr("-X\n"); + } + else + { + fw3_pr("-D FORWARD -j mssfix\n"); + fw3_pr("-F mssfix\n"); + fw3_pr("-X mssfix\n"); + } + break; + + case FW3_TABLE_RAW: + if (complete) + { + fw3_pr("-F\n"); + fw3_pr("-X\n"); + } + else + { + fw3_pr("-D PREROUTING -j notrack\n"); + fw3_pr("-F notrack\n"); + fw3_pr("-X notrack\n"); + } + break; + } +} diff --git a/defaults.h b/defaults.h new file mode 100644 index 0000000..10ac68c --- /dev/null +++ b/defaults.h @@ -0,0 +1,35 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __FW3_DEFAULTS_H +#define __FW3_DEFAULTS_H + +#include "options.h" + +void fw3_load_defaults(struct fw3_state *state, struct uci_package *p); + +void fw3_print_default_chains(enum fw3_table table, enum fw3_family family, + struct fw3_state *state); + +void fw3_print_default_rules(enum fw3_table table, enum fw3_family family, + struct fw3_state *state); + +void fw3_print_flush_rules(enum fw3_table table, enum fw3_family family, + struct fw3_state *state, bool complete); + +#endif diff --git a/forwards.c b/forwards.c new file mode 100644 index 0000000..c212c8c --- /dev/null +++ b/forwards.c @@ -0,0 +1,146 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "forwards.h" + + +static struct fw3_option forward_opts[] = { + FW3_OPT("name", string, forward, name), + FW3_OPT("family", family, forward, family), + + FW3_OPT("src", device, forward, src), + FW3_OPT("dest", device, forward, dest), +}; + + +void +fw3_load_forwards(struct fw3_state *state, struct uci_package *p) +{ + struct uci_section *s; + struct uci_element *e; + struct fw3_forward *forward; + + INIT_LIST_HEAD(&state->forwards); + + uci_foreach_element(&p->sections, e) + { + s = uci_to_section(e); + + if (strcmp(s->type, "forwarding")) + continue; + + forward = malloc(sizeof(*forward)); + + if (!forward) + continue; + + memset(forward, 0, sizeof(*forward)); + + fw3_parse_options(forward, forward_opts, ARRAY_SIZE(forward_opts), s); + + if (forward->src.invert || forward->dest.invert) + { + warn_elem(e, "must not have inverted 'src' or 'dest' options"); + fw3_free_forward(forward); + continue; + } + else if (forward->src.set && !forward->src.any && + !(forward->_src = fw3_lookup_zone(state, forward->src.name))) + { + warn_elem(e, "refers to not existing zone '%s'", forward->src.name); + fw3_free_forward(forward); + continue; + } + else if (forward->dest.set && !forward->dest.any && + !(forward->_dest = fw3_lookup_zone(state, forward->dest.name))) + { + warn_elem(e, "refers to not existing zone '%s'", forward->dest.name); + fw3_free_forward(forward); + continue; + } + + if (forward->_dest) + { + forward->_dest->has_dest_target[FW3_TARGET_ACCEPT] = true; + + if (forward->_src && + (forward->_src->conntrack || forward->_dest->conntrack)) + { + forward->_src->conntrack = forward->_dest->conntrack = true; + } + } + + list_add_tail(&forward->list, &state->forwards); + continue; + } +} + + +static void +print_chain(struct fw3_forward *forward) +{ + if (forward->src.any || !forward->src.set) + fw3_pr("-A delegate_forward"); + else + fw3_pr("-A zone_%s_forward", forward->src.name); +} + +static void print_target(struct fw3_forward *forward) +{ + if (forward->dest.any || !forward->dest.set) + fw3_pr(" -j ACCEPT\n"); + else + fw3_pr(" -j zone_%s_dest_ACCEPT\n", forward->dest.name); +} + +static void +print_forward(enum fw3_table table, enum fw3_family family, + struct fw3_forward *forward) +{ + const char *s, *d; + + if (table != FW3_TABLE_FILTER) + return; + + if (!fw3_is_family(forward, family) || + (forward->_src && !fw3_is_family(forward->_src, family)) || + (forward->_dest && !fw3_is_family(forward->_dest, family))) + return; + + s = forward->_src ? forward->_src->name : "*"; + d = forward->_dest ? forward->_dest->name : "*"; + + if (forward->name) + info(" * Forward '%s'", forward->name); + else + info(" * Forward %s->%s", s, d); + + print_chain(forward); + fw3_format_comment("forwarding ", s, "->", d); + print_target(forward); +} + +void +fw3_print_forwards(enum fw3_table table, enum fw3_family family, + struct fw3_state *state) +{ + struct fw3_forward *forward; + + list_for_each_entry(forward, &state->forwards, list) + print_forward(table, family, forward); +} diff --git a/forwards.h b/forwards.h new file mode 100644 index 0000000..c3caff9 --- /dev/null +++ b/forwards.h @@ -0,0 +1,32 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __FW3_FORWARDS_H +#define __FW3_FORWARDS_H + +#include "options.h" +#include "zones.h" +#include "utils.h" + +void fw3_load_forwards(struct fw3_state *state, struct uci_package *p); +void fw3_print_forwards(enum fw3_table table, enum fw3_family family, + struct fw3_state *state); + +#define fw3_free_forward(forward) free(forward) + +#endif diff --git a/icmp_codes.h b/icmp_codes.h new file mode 100644 index 0000000..4edfbb9 --- /dev/null +++ b/icmp_codes.h @@ -0,0 +1,125 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __FW3_ICMP_CODES_H +#define __FW3_ICMP_CODES_H + + +struct fw3_icmptype_entry { + const char *name; + uint8_t type; + uint8_t code_min; + uint8_t code_max; +}; + +/* taken from iptables extensions/libipt_icmp.c */ +static const struct fw3_icmptype_entry fw3_icmptype_list_v4[] = { + { "any", 0xFF, 0, 0xFF }, + { "echo-reply", 0, 0, 0xFF }, + /* Alias */ { "pong", 0, 0, 0xFF }, + + { "destination-unreachable", 3, 0, 0xFF }, + { "network-unreachable", 3, 0, 0 }, + { "host-unreachable", 3, 1, 1 }, + { "protocol-unreachable", 3, 2, 2 }, + { "port-unreachable", 3, 3, 3 }, + { "fragmentation-needed", 3, 4, 4 }, + { "source-route-failed", 3, 5, 5 }, + { "network-unknown", 3, 6, 6 }, + { "host-unknown", 3, 7, 7 }, + { "network-prohibited", 3, 9, 9 }, + { "host-prohibited", 3, 10, 10 }, + { "TOS-network-unreachable", 3, 11, 11 }, + { "TOS-host-unreachable", 3, 12, 12 }, + { "communication-prohibited", 3, 13, 13 }, + { "host-precedence-violation", 3, 14, 14 }, + { "precedence-cutoff", 3, 15, 15 }, + + { "source-quench", 4, 0, 0xFF }, + + { "redirect", 5, 0, 0xFF }, + { "network-redirect", 5, 0, 0 }, + { "host-redirect", 5, 1, 1 }, + { "TOS-network-redirect", 5, 2, 2 }, + { "TOS-host-redirect", 5, 3, 3 }, + + { "echo-request", 8, 0, 0xFF }, + /* Alias */ { "ping", 8, 0, 0xFF }, + + { "router-advertisement", 9, 0, 0xFF }, + + { "router-solicitation", 10, 0, 0xFF }, + + { "time-exceeded", 11, 0, 0xFF }, + /* Alias */ { "ttl-exceeded", 11, 0, 0xFF }, + { "ttl-zero-during-transit", 11, 0, 0 }, + { "ttl-zero-during-reassembly", 11, 1, 1 }, + + { "parameter-problem", 12, 0, 0xFF }, + { "ip-header-bad", 12, 0, 0 }, + { "required-option-missing", 12, 1, 1 }, + + { "timestamp-request", 13, 0, 0xFF }, + + { "timestamp-reply", 14, 0, 0xFF }, + + { "address-mask-request", 17, 0, 0xFF }, + + { "address-mask-reply", 18, 0, 0xFF } +}; + +/* taken from iptables extensions/libip6t_icmp6.c */ +static const struct fw3_icmptype_entry fw3_icmptype_list_v6[] = { + { "destination-unreachable", 1, 0, 0xFF }, + { "no-route", 1, 0, 0 }, + { "communication-prohibited", 1, 1, 1 }, + { "address-unreachable", 1, 3, 3 }, + { "port-unreachable", 1, 4, 4 }, + + { "packet-too-big", 2, 0, 0xFF }, + + { "time-exceeded", 3, 0, 0xFF }, + /* Alias */ { "ttl-exceeded", 3, 0, 0xFF }, + { "ttl-zero-during-transit", 3, 0, 0 }, + { "ttl-zero-during-reassembly", 3, 1, 1 }, + + { "parameter-problem", 4, 0, 0xFF }, + { "bad-header", 4, 0, 0 }, + { "unknown-header-type", 4, 1, 1 }, + { "unknown-option", 4, 2, 2 }, + + { "echo-request", 128, 0, 0xFF }, + /* Alias */ { "ping", 128, 0, 0xFF }, + + { "echo-reply", 129, 0, 0xFF }, + /* Alias */ { "pong", 129, 0, 0xFF }, + + { "router-solicitation", 133, 0, 0xFF }, + + { "router-advertisement", 134, 0, 0xFF }, + + { "neighbour-solicitation", 135, 0, 0xFF }, + /* Alias */ { "neighbor-solicitation", 135, 0, 0xFF }, + + { "neighbour-advertisement", 136, 0, 0xFF }, + /* Alias */ { "neighbor-advertisement", 136, 0, 0xFF }, + + { "redirect", 137, 0, 0xFF }, +}; + +#endif diff --git a/ipsets.c b/ipsets.c new file mode 100644 index 0000000..3d659e2 --- /dev/null +++ b/ipsets.c @@ -0,0 +1,399 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ipsets.h" + + +static struct fw3_option ipset_opts[] = { + FW3_OPT("name", string, ipset, name), + FW3_OPT("family", family, ipset, family), + + FW3_OPT("storage", ipset_method, ipset, method), + FW3_LIST("match", ipset_datatype, ipset, datatypes), + + FW3_LIST("iprange", address, ipset, iprange), + FW3_OPT("portrange", port, ipset, portrange), + + FW3_OPT("netmask", int, ipset, netmask), + FW3_OPT("maxelem", int, ipset, maxelem), + FW3_OPT("hashsize", int, ipset, hashsize), + FW3_OPT("timeout", int, ipset, timeout), + + FW3_OPT("external", string, ipset, external), +}; + +#define T(m, t1, t2, t3, r, o) \ + { FW3_IPSET_METHOD_##m, \ + FW3_IPSET_TYPE_##t1 | (FW3_IPSET_TYPE_##t2 << 8) | (FW3_IPSET_TYPE_##t3 << 16), \ + r, o } + +static struct fw3_ipset_settype ipset_types[] = { + T(BITMAP, IP, UNSPEC, UNSPEC, FW3_IPSET_OPT_IPRANGE, + FW3_IPSET_OPT_NETMASK), + T(BITMAP, IP, MAC, UNSPEC, FW3_IPSET_OPT_IPRANGE, 0), + T(BITMAP, PORT, UNSPEC, UNSPEC, FW3_IPSET_OPT_PORTRANGE, 0), + + T(HASH, IP, UNSPEC, UNSPEC, 0, + FW3_IPSET_OPT_FAMILY | FW3_IPSET_OPT_HASHSIZE | FW3_IPSET_OPT_MAXELEM | + FW3_IPSET_OPT_NETMASK), + T(HASH, NET, UNSPEC, UNSPEC, 0, + FW3_IPSET_OPT_FAMILY | FW3_IPSET_OPT_HASHSIZE | FW3_IPSET_OPT_MAXELEM), + T(HASH, IP, PORT, UNSPEC, 0, + FW3_IPSET_OPT_FAMILY | FW3_IPSET_OPT_HASHSIZE | FW3_IPSET_OPT_MAXELEM), + T(HASH, NET, PORT, UNSPEC, 0, + FW3_IPSET_OPT_FAMILY | FW3_IPSET_OPT_HASHSIZE | FW3_IPSET_OPT_MAXELEM), + T(HASH, IP, PORT, IP, 0, + FW3_IPSET_OPT_FAMILY | FW3_IPSET_OPT_HASHSIZE | FW3_IPSET_OPT_MAXELEM), + T(HASH, IP, PORT, NET, 0, + FW3_IPSET_OPT_FAMILY | FW3_IPSET_OPT_HASHSIZE | FW3_IPSET_OPT_MAXELEM), + + T(LIST, SET, UNSPEC, UNSPEC, 0, FW3_IPSET_OPT_MAXELEM), +}; + + +static bool +check_types(struct uci_element *e, struct fw3_ipset *ipset) +{ + int i = 0; + uint32_t typelist = 0; + struct fw3_ipset_datatype *type; + + const char *methods[] = { + "(bug)", + "bitmap", + "hash", + "list", + }; + + typelist = 0; + + list_for_each_entry(type, &ipset->datatypes, list) + { + if (i >= 3) + { + warn_elem(e, "must not have more than 3 datatypes assigned"); + return false; + } + + typelist |= (type->type << (i++ * 8)); + } + + /* find a suitable storage method if none specified */ + if (ipset->method == FW3_IPSET_METHOD_UNSPEC) + { + for (i = 0; i < ARRAY_SIZE(ipset_types); i++) + { + if (ipset_types[i].types == typelist) + { + ipset->method = ipset_types[i].method; + + warn_elem(e, "defines no storage method, assuming '%s'", + methods[ipset->method]); + + break; + } + } + } + + //typelist |= ipset->method; + + for (i = 0; i < ARRAY_SIZE(ipset_types); i++) + { + if (ipset_types[i].method == ipset->method && + ipset_types[i].types == typelist) + { + if (!ipset->external || !*ipset->external) + { + if ((ipset_types[i].required & FW3_IPSET_OPT_IPRANGE) && + list_empty(&ipset->iprange)) + { + warn_elem(e, "requires an ip range"); + return false; + } + + if ((ipset_types[i].required & FW3_IPSET_OPT_PORTRANGE) && + !ipset->portrange.set) + { + warn_elem(e, "requires a port range"); + return false; + } + + if (!(ipset_types[i].required & FW3_IPSET_OPT_IPRANGE) && + !list_empty(&ipset->iprange)) + { + warn_elem(e, "iprange ignored"); + fw3_free_list(&ipset->iprange); + } + + if (!(ipset_types[i].required & FW3_IPSET_OPT_PORTRANGE) && + ipset->portrange.set) + { + warn_elem(e, "portrange ignored"); + memset(&ipset->portrange, 0, sizeof(ipset->portrange)); + } + + if (!(ipset_types[i].optional & FW3_IPSET_OPT_NETMASK) && + ipset->netmask > 0) + { + warn_elem(e, "netmask ignored"); + ipset->netmask = 0; + } + + if (!(ipset_types[i].optional & FW3_IPSET_OPT_HASHSIZE) && + ipset->hashsize > 0) + { + warn_elem(e, "hashsize ignored"); + ipset->hashsize = 0; + } + + if (!(ipset_types[i].optional & FW3_IPSET_OPT_MAXELEM) && + ipset->maxelem > 0) + { + warn_elem(e, "maxelem ignored"); + ipset->maxelem = 0; + } + + if (!(ipset_types[i].optional & FW3_IPSET_OPT_FAMILY) && + ipset->family != FW3_FAMILY_ANY) + { + warn_elem(e, "family ignored"); + ipset->family = FW3_FAMILY_ANY; + } + } + + return true; + } + } + + warn_elem(e, "has an invalid combination of storage method and matches"); + return false; +} + +void +fw3_load_ipsets(struct fw3_state *state, struct uci_package *p) +{ + struct uci_section *s; + struct uci_element *e; + struct fw3_ipset *ipset; + + INIT_LIST_HEAD(&state->ipsets); + + if (state->disable_ipsets) + return; + + uci_foreach_element(&p->sections, e) + { + s = uci_to_section(e); + + if (strcmp(s->type, "ipset")) + continue; + + ipset = malloc(sizeof(*ipset)); + + if (!ipset) + continue; + + memset(ipset, 0, sizeof(*ipset)); + + INIT_LIST_HEAD(&ipset->datatypes); + INIT_LIST_HEAD(&ipset->iprange); + + fw3_parse_options(ipset, ipset_opts, ARRAY_SIZE(ipset_opts), s); + + if (!ipset->name || !*ipset->name) + { + warn_elem(e, "must have a name assigned"); + } + //else if (fw3_lookup_ipset(state, ipset->name) != NULL) + //{ + // warn_elem(e, "has duplicated set name '%s'", ipset->name); + //} + else if (list_empty(&ipset->datatypes)) + { + warn_elem(e, "has no datatypes assigned"); + } + else if (check_types(e, ipset)) + { + list_add_tail(&ipset->list, &state->ipsets); + continue; + } + + fw3_free_ipset(ipset); + } +} + + +static void +create_ipset(struct fw3_ipset *ipset) +{ + bool first = true; + char s[INET6_ADDRSTRLEN]; + + struct fw3_ipset_datatype *type; + struct fw3_address *a1, *a2; + + const char *methods[] = { + "(bug)", + "bitmap", + "hash", + "list", + }; + + const char *types[] = { + "(bug)", + "ip", + "port", + "mac", + "net", + "set", + }; + + const char *families[] = { + "(bug)", + "inet", + "inet6", + }; + + if (ipset->external && *ipset->external) + return; + + info(" * %s", ipset->name); + + first = true; + fw3_pr("create %s %s", ipset->name, methods[ipset->method]); + + list_for_each_entry(type, &ipset->datatypes, list) + { + fw3_pr("%c%s", first ? ':' : ',', types[type->type]); + first = false; + } + + if (!list_empty(&ipset->iprange)) + { + a1 = list_first_entry(&ipset->iprange, struct fw3_address, list); + a2 = list_last_entry(&ipset->iprange, struct fw3_address, list); + + if (a1 == a2) + { + inet_ntop(a1->family == FW3_FAMILY_V4 ? AF_INET : AF_INET6, + &a1->address.v6, s, sizeof(s)); + + fw3_pr(" range %s/%u", s, a1->mask); + } + else if (a1->family == a2->family && + fw3_is_family(ipset, a1->family) && + fw3_is_family(ipset, a2->family)) + { + inet_ntop(a1->family == FW3_FAMILY_V4 ? AF_INET : AF_INET6, + &a1->address.v6, s, sizeof(s)); + + fw3_pr(" range %s", s); + + inet_ntop(a2->family == FW3_FAMILY_V4 ? AF_INET : AF_INET6, + &a2->address.v6, s, sizeof(s)); + + fw3_pr("-%s", s); + } + } + else if (ipset->portrange.set) + { + fw3_pr(" range %u-%u", + ipset->portrange.port_min, ipset->portrange.port_max); + } + + if (ipset->family != FW3_FAMILY_ANY) + fw3_pr(" family %s", families[ipset->family]); + + if (ipset->timeout > 0) + fw3_pr(" timeout %u", ipset->timeout); + + if (ipset->maxelem > 0) + fw3_pr(" maxelem %u", ipset->maxelem); + + if (ipset->netmask > 0) + fw3_pr(" netmask %u", ipset->netmask); + + if (ipset->hashsize > 0) + fw3_pr(" hashsize %u", ipset->hashsize); + + fw3_pr("\n"); +} + +void +fw3_create_ipsets(struct fw3_state *state) +{ + struct fw3_ipset *ipset; + + if (state->disable_ipsets) + return; + + info("Initializing ipsets ..."); + + list_for_each_entry(ipset, &state->ipsets, list) + create_ipset(ipset); + + fw3_pr("quit\n"); +} + +void +fw3_destroy_ipsets(struct fw3_state *state) +{ + struct fw3_ipset *ipset; + + if (state->disable_ipsets) + return; + + info("Destroying ipsets ..."); + + list_for_each_entry(ipset, &state->ipsets, list) + { + if (ipset->external && *ipset->external) + continue; + + info(" * %s", ipset->name); + + fw3_pr("flush %s\n", ipset->name); + fw3_pr("destroy %s\n", ipset->name); + } + + fw3_pr("quit\n"); +} + +void +fw3_free_ipset(struct fw3_ipset *ipset) +{ + fw3_free_list(&ipset->datatypes); + fw3_free_list(&ipset->iprange); + + free(ipset); +} + +struct fw3_ipset * +fw3_lookup_ipset(struct fw3_state *state, const char *name) +{ + struct fw3_ipset *ipset; + + if (list_empty(&state->ipsets)) + return NULL; + + list_for_each_entry(ipset, &state->ipsets, list) + if (!strcmp(ipset->name, name)) + return ipset; + + return NULL; +} diff --git a/ipsets.h b/ipsets.h new file mode 100644 index 0000000..5ad6d99 --- /dev/null +++ b/ipsets.h @@ -0,0 +1,49 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __FW3_IPSETS_H +#define __FW3_IPSETS_H + +#include "options.h" +#include "utils.h" + +enum fw3_ipset_opts { + FW3_IPSET_OPT_IPRANGE = (1 << 0), + FW3_IPSET_OPT_PORTRANGE = (1 << 1), + FW3_IPSET_OPT_NETMASK = (1 << 2), + FW3_IPSET_OPT_HASHSIZE = (1 << 3), + FW3_IPSET_OPT_MAXELEM = (1 << 4), + FW3_IPSET_OPT_FAMILY = (1 << 5), +}; + +struct fw3_ipset_settype { + enum fw3_ipset_method method; + uint32_t types; + uint8_t required; + uint8_t optional; +}; + +void fw3_load_ipsets(struct fw3_state *state, struct uci_package *p); +void fw3_create_ipsets(struct fw3_state *state); +void fw3_destroy_ipsets(struct fw3_state *state); + +void fw3_free_ipset(struct fw3_ipset *ipset); + +struct fw3_ipset * fw3_lookup_ipset(struct fw3_state *state, const char *name); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..ddbd24d --- /dev/null +++ b/main.c @@ -0,0 +1,378 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "options.h" +#include "defaults.h" +#include "zones.h" +#include "rules.h" +#include "redirects.h" +#include "forwards.h" +#include "ipsets.h" +#include "ubus.h" + + +static bool print_rules = false; +static bool skip_family[FW3_FAMILY_V6 + 1] = { false }; + + +static struct fw3_state * +build_state(void) +{ + struct fw3_state *state = NULL; + struct uci_package *p = NULL; + + state = malloc(sizeof(*state)); + + if (!state) + error("Out of memory"); + + memset(state, 0, sizeof(*state)); + state->uci = uci_alloc_context(); + + if (!state->uci) + error("Out of memory"); + + if (uci_load(state->uci, "firewall", &p)) + { + uci_perror(state->uci, NULL); + error("Failed to load /etc/config/firewall"); + } + + if (!fw3_find_command("ipset")) + { + warn("Unable to locate ipset utility, disabling ipset support"); + state->disable_ipsets = true; + } + + fw3_load_defaults(state, p); + fw3_load_ipsets(state, p); + fw3_load_zones(state, p); + fw3_load_rules(state, p); + fw3_load_redirects(state, p); + fw3_load_forwards(state, p); + + if (state->defaults.disable_ipv6 && !skip_family[FW3_FAMILY_V6]) + { + warn("IPv6 rules globally disabled in configuration"); + skip_family[FW3_FAMILY_V6] = true; + } + + return state; +} + +static void +free_state(struct fw3_state *state) +{ + struct list_head *cur, *tmp; + + list_for_each_safe(cur, tmp, &state->zones) + fw3_free_zone((struct fw3_zone *)cur); + + list_for_each_safe(cur, tmp, &state->rules) + fw3_free_rule((struct fw3_rule *)cur); + + list_for_each_safe(cur, tmp, &state->redirects) + fw3_free_redirect((struct fw3_redirect *)cur); + + list_for_each_safe(cur, tmp, &state->forwards) + fw3_free_forward((struct fw3_forward *)cur); + + uci_free_context(state->uci); + + free(state); + + fw3_ubus_disconnect(); +} + + +static bool +restore_pipe(enum fw3_family family, bool silent) +{ + const char *cmd[] = { + "(bug)", + "iptables-restore", + "ip6tables-restore", + }; + + if (print_rules) + return fw3_stdout_pipe(); + + if (!fw3_command_pipe(silent, cmd[family], "--lenient", "--noflush")) + { + warn("Unable to execute %s", cmd[family]); + return false; + } + + return true; +} + +static int +stop(struct fw3_state *state, bool complete) +{ + enum fw3_family family; + enum fw3_table table; + + const char *tables[] = { + "filter", + "nat", + "mangle", + "raw", + }; + + for (family = FW3_FAMILY_V4; family <= FW3_FAMILY_V6; family++) + { + if (skip_family[family] || !restore_pipe(family, true)) + continue; + + info("Removing IPv%d rules ...", family == FW3_FAMILY_V4 ? 4 : 6); + + for (table = FW3_TABLE_FILTER; table <= FW3_TABLE_RAW; table++) + { + if (!fw3_has_table(family == FW3_FAMILY_V6, tables[table])) + continue; + + info(" * %sing %s table", + complete ? "Flush" : "Clear", tables[table]); + + fw3_pr("*%s\n", tables[table]); + fw3_print_flush_rules(table, family, state, complete); + fw3_pr("COMMIT\n"); + } + + fw3_command_close(); + } + + if (complete && fw3_command_pipe(false, "ipset", "-exist", "-")) + { + fw3_destroy_ipsets(state); + fw3_command_close(); + } + + return 0; +} + +static int +start(struct fw3_state *state) +{ + enum fw3_family family; + enum fw3_table table; + + const char *tables[] = { + "filter", + "nat", + "mangle", + "raw", + }; + + if (!print_rules && fw3_command_pipe(false, "ipset", "-exist", "-")) + { + fw3_create_ipsets(state); + fw3_command_close(); + } + + for (family = FW3_FAMILY_V4; family <= FW3_FAMILY_V6; family++) + { + if (skip_family[family] || !restore_pipe(family, false)) + continue; + + info("Constructing IPv%d rules ...", family == FW3_FAMILY_V4 ? 4 : 6); + + for (table = FW3_TABLE_FILTER; table <= FW3_TABLE_RAW; table++) + { + if (!fw3_has_table(family == FW3_FAMILY_V6, tables[table])) + continue; + + info(" * Populating %s table", tables[table]); + + fw3_pr("*%s\n", tables[table]); + fw3_print_default_chains(table, family, state); + fw3_print_zone_chains(table, family, state); + fw3_print_default_rules(table, family, state); + fw3_print_rules(table, family, state); + fw3_print_redirects(table, family, state); + fw3_print_forwards(table, family, state); + fw3_print_zone_rules(table, family, state); + fw3_pr("COMMIT\n"); + } + + fw3_command_close(); + } + + return 0; +} + +static int +lookup_network(struct fw3_state *state, const char *net) +{ + struct fw3_zone *z; + struct fw3_device *d; + + list_for_each_entry(z, &state->zones, list) + { + list_for_each_entry(d, &z->networks, list) + { + if (!strcmp(d->name, net)) + { + printf("%s\n", z->name); + return 0; + } + } + } + + return 1; +} + +static int +lookup_device(struct fw3_state *state, const char *dev) +{ + struct fw3_zone *z; + struct fw3_device *d; + + list_for_each_entry(z, &state->zones, list) + { + list_for_each_entry(d, &z->devices, list) + { + if (!strcmp(d->name, dev)) + { + printf("%s\n", z->name); + return 0; + } + } + } + + return 1; +} + +static int +usage(void) +{ + fprintf(stderr, "fw3 [-4] [-6] [-q] {start|stop|flush|restart|print}\n"); + fprintf(stderr, "fw3 [-q] network {net}\n"); + fprintf(stderr, "fw3 [-q] device {dev}\n"); + + return 1; +} + + +int main(int argc, char **argv) +{ + int ch, rv = 1; + struct fw3_state *state = NULL; + + while ((ch = getopt(argc, argv, "46qh")) != -1) + { + switch (ch) + { + case '4': + skip_family[FW3_FAMILY_V4] = false; + skip_family[FW3_FAMILY_V6] = true; + break; + + case '6': + skip_family[FW3_FAMILY_V4] = true; + skip_family[FW3_FAMILY_V6] = false; + break; + + case 'q': + freopen("/dev/null", "w", stderr); + break; + + case 'h': + rv = usage(); + goto out; + } + } + + if (!fw3_ubus_connect()) + error("Failed to connect to ubus"); + + state = build_state(); + + if (optind >= argc) + { + rv = usage(); + goto out; + } + + if (!strcmp(argv[optind], "print")) + { + state->disable_ipsets = true; + print_rules = true; + + if (!skip_family[FW3_FAMILY_V4] && !skip_family[FW3_FAMILY_V6]) + skip_family[FW3_FAMILY_V6] = true; + + rv = start(state); + } + else if (!strcmp(argv[optind], "start")) + { + if (!fw3_check_statefile(false)) + goto out; + + rv = start(state); + fw3_close_statefile(); + } + else if (!strcmp(argv[optind], "stop")) + { + if (!fw3_check_statefile(true)) + goto out; + + rv = stop(state, false); + fw3_remove_statefile(); + } + else if (!strcmp(argv[optind], "flush")) + { + rv = stop(state, true); + fw3_remove_statefile(); + } + else if (!strcmp(argv[optind], "restart")) + { + if (fw3_check_statefile(true)) + { + stop(state, false); + fw3_remove_statefile(); + } + + if (!fw3_check_statefile(false)) + goto out; + + rv = start(state); + fw3_close_statefile(); + } + else if (!strcmp(argv[optind], "network") && (optind + 1) < argc) + { + rv = lookup_network(state, argv[optind + 1]); + } + else if (!strcmp(argv[optind], "device") && (optind + 1) < argc) + { + rv = lookup_device(state, argv[optind + 1]); + } + else + { + rv = usage(); + } + +out: + if (state) + free_state(state); + + return rv; +} diff --git a/options.c b/options.c new file mode 100644 index 0000000..5d325fc --- /dev/null +++ b/options.c @@ -0,0 +1,800 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "options.h" + +bool +fw3_parse_bool(void *ptr, const char *val) +{ + if (!strcmp(val, "true") || !strcmp(val, "yes") || !strcmp(val, "1")) + *((bool *)ptr) = true; + else + *((bool *)ptr) = false; + + return true; +} + +bool +fw3_parse_int(void *ptr, const char *val) +{ + int n = strtol(val, NULL, 10); + + if (errno == ERANGE || errno == EINVAL) + return false; + + *((int *)ptr) = n; + + return true; +} + +bool +fw3_parse_string(void *ptr, const char *val) +{ + *((char **)ptr) = (char *)val; + return true; +} + +bool +fw3_parse_target(void *ptr, const char *val) +{ + if (!strcmp(val, "ACCEPT")) + { + *((enum fw3_target *)ptr) = FW3_TARGET_ACCEPT; + return true; + } + else if (!strcmp(val, "REJECT")) + { + *((enum fw3_target *)ptr) = FW3_TARGET_REJECT; + return true; + } + else if (!strcmp(val, "DROP")) + { + *((enum fw3_target *)ptr) = FW3_TARGET_DROP; + return true; + } + else if (!strcmp(val, "NOTRACK")) + { + *((enum fw3_target *)ptr) = FW3_TARGET_NOTRACK; + return true; + } + else if (!strcmp(val, "DNAT")) + { + *((enum fw3_target *)ptr) = FW3_TARGET_DNAT; + return true; + } + else if (!strcmp(val, "SNAT")) + { + *((enum fw3_target *)ptr) = FW3_TARGET_SNAT; + return true; + } + + return false; +} + +bool +fw3_parse_limit(void *ptr, const char *val) +{ + struct fw3_limit *limit = ptr; + enum fw3_limit_unit u = FW3_LIMIT_UNIT_SECOND; + char *e; + int n; + + if (*val == '!') + { + limit->invert = true; + while (isspace(*++val)); + } + + n = strtol(val, &e, 10); + + if (errno == ERANGE || errno == EINVAL) + return false; + + if (*e && *e++ != '/') + return false; + + if (!strlen(e)) + return false; + + if (!strncmp(e, "second", strlen(e))) + u = FW3_LIMIT_UNIT_SECOND; + else if (!strncmp(e, "minute", strlen(e))) + u = FW3_LIMIT_UNIT_MINUTE; + else if (!strncmp(e, "hour", strlen(e))) + u = FW3_LIMIT_UNIT_HOUR; + else if (!strncmp(e, "day", strlen(e))) + u = FW3_LIMIT_UNIT_DAY; + else + return false; + + limit->rate = n; + limit->unit = u; + + return true; +} + +bool +fw3_parse_device(void *ptr, const char *val) +{ + struct fw3_device *dev = ptr; + + if (*val == '*') + { + dev->set = true; + dev->any = true; + return true; + } + + if (*val == '!') + { + dev->invert = true; + while (isspace(*++val)); + } + + if (*val) + snprintf(dev->name, sizeof(dev->name), "%s", val); + else + return false; + + dev->set = true; + return true; +} + +bool +fw3_parse_address(void *ptr, const char *val) +{ + struct fw3_address *addr = ptr; + struct in_addr v4; + struct in6_addr v6; + char *p, *s, *e; + int i, m = -1; + + if (*val == '!') + { + addr->invert = true; + while (isspace(*++val)); + } + + s = strdup(val); + + if (!s) + return false; + + if ((p = strchr(s, '/')) != NULL) + { + *p++ = 0; + m = strtoul(p, &e, 10); + + if ((e == p) || (*e != 0)) + { + if (strchr(s, ':') || !inet_pton(AF_INET, p, &v4)) + { + free(s); + return false; + } + + for (i = 0, m = 32; !(v4.s_addr & 1) && (i < 32); i++) + { + m--; + v4.s_addr >>= 1; + } + } + } + + if (inet_pton(AF_INET6, s, &v6)) + { + addr->family = FW3_FAMILY_V6; + addr->address.v6 = v6; + addr->mask = (m >= 0) ? m : 128; + } + else if (inet_pton(AF_INET, s, &v4)) + { + addr->family = FW3_FAMILY_V4; + addr->address.v4 = v4; + addr->mask = (m >= 0) ? m : 32; + } + else + { + free(s); + return false; + } + + free(s); + addr->set = true; + return true; +} + +bool +fw3_parse_mac(void *ptr, const char *val) +{ + struct fw3_mac *addr = ptr; + struct ether_addr *mac; + + if (*val == '!') + { + addr->invert = true; + while (isspace(*++val)); + } + + if ((mac = ether_aton(val)) != NULL) + { + addr->mac = *mac; + addr->set = true; + return true; + } + + return false; +} + +bool +fw3_parse_port(void *ptr, const char *val) +{ + struct fw3_port *range = ptr; + uint16_t n; + uint16_t m; + char *p; + + if (*val == '!') + { + range->invert = true; + while (isspace(*++val)); + } + + n = strtoul(val, &p, 10); + + if (errno == ERANGE || errno == EINVAL) + return false; + + if (*p && *p != '-' && *p != ':') + return false; + + if (*p) + { + m = strtoul(++p, NULL, 10); + + if (errno == ERANGE || errno == EINVAL || m < n) + return false; + + range->port_min = n; + range->port_max = m; + } + else + { + range->port_min = n; + range->port_max = n; + } + + range->set = true; + return true; +} + +bool +fw3_parse_family(void *ptr, const char *val) +{ + if (!strcmp(val, "any")) + *((enum fw3_family *)ptr) = FW3_FAMILY_ANY; + else if (!strcmp(val, "inet") || strrchr(val, '4')) + *((enum fw3_family *)ptr) = FW3_FAMILY_V4; + else if (!strcmp(val, "inet6") || strrchr(val, '6')) + *((enum fw3_family *)ptr) = FW3_FAMILY_V6; + else + return false; + + return true; +} + +bool +fw3_parse_icmptype(void *ptr, const char *val) +{ + struct fw3_icmptype *icmp = ptr; + bool v4 = false; + bool v6 = false; + char *p; + int i; + + for (i = 0; i < ARRAY_SIZE(fw3_icmptype_list_v4); i++) + { + if (!strcmp(val, fw3_icmptype_list_v4[i].name)) + { + icmp->type = fw3_icmptype_list_v4[i].type; + icmp->code_min = fw3_icmptype_list_v4[i].code_min; + icmp->code_max = fw3_icmptype_list_v4[i].code_max; + + v4 = true; + break; + } + } + + for (i = 0; i < ARRAY_SIZE(fw3_icmptype_list_v6); i++) + { + if (!strcmp(val, fw3_icmptype_list_v6[i].name)) + { + icmp->type6 = fw3_icmptype_list_v6[i].type; + icmp->code6_min = fw3_icmptype_list_v6[i].code_min; + icmp->code6_max = fw3_icmptype_list_v6[i].code_max; + + v6 = true; + break; + } + } + + if (!v4 && !v6) + { + i = strtoul(val, &p, 10); + + if ((p == val) || (*p != '/' && *p != 0) || (i > 0xFF)) + return false; + + icmp->type = i; + + if (*p == '/') + { + val = ++p; + i = strtoul(val, &p, 10); + + if ((p == val) || (*p != 0) || (i > 0xFF)) + return false; + + icmp->code_min = i; + icmp->code_max = i; + } + else + { + icmp->code_min = 0; + icmp->code_max = 0xFF; + } + + icmp->type6 = icmp->type; + icmp->code6_min = icmp->code_max; + icmp->code6_max = icmp->code_max; + + v4 = true; + v6 = true; + } + + icmp->family = (v4 && v6) ? FW3_FAMILY_ANY + : (v6 ? FW3_FAMILY_V6 : FW3_FAMILY_V4); + + return true; +} + +bool +fw3_parse_protocol(void *ptr, const char *val) +{ + struct fw3_protocol *proto = ptr; + struct protoent *ent; + + if (*val == '!') + { + proto->invert = true; + while (isspace(*++val)); + } + + if (!strcmp(val, "all")) + { + proto->any = true; + return true; + } + else if (!strcmp(val, "icmpv6")) + { + val = "ipv6-icmp"; + } + + ent = getprotobyname(val); + + if (ent) + { + proto->protocol = ent->p_proto; + return true; + } + + proto->protocol = strtoul(val, NULL, 10); + return (errno != ERANGE && errno != EINVAL); +} + +bool +fw3_parse_ipset_method(void *ptr, const char *val) +{ + if (!strncmp(val, "bitmap", strlen(val))) + { + *((enum fw3_ipset_method *)ptr) = FW3_IPSET_METHOD_BITMAP; + return true; + } + else if (!strncmp(val, "hash", strlen(val))) + { + *((enum fw3_ipset_method *)ptr) = FW3_IPSET_METHOD_HASH; + return true; + } + else if (!strncmp(val, "list", strlen(val))) + { + *((enum fw3_ipset_method *)ptr) = FW3_IPSET_METHOD_LIST; + return true; + } + + return false; +} + +bool +fw3_parse_ipset_datatype(void *ptr, const char *val) +{ + struct fw3_ipset_datatype *type = ptr; + + if (!strncmp(val, "dest_", 5)) + { + val += 5; + type->dest = true; + } + else if (!strncmp(val, "dst_", 4)) + { + val += 4; + type->dest = true; + } + else if (!strncmp(val, "src_", 4)) + { + val += 4; + type->dest = false; + } + + if (!strncmp(val, "ip", strlen(val))) + { + type->type = FW3_IPSET_TYPE_IP; + return true; + } + else if (!strncmp(val, "port", strlen(val))) + { + type->type = FW3_IPSET_TYPE_PORT; + return true; + } + else if (!strncmp(val, "mac", strlen(val))) + { + type->type = FW3_IPSET_TYPE_MAC; + return true; + } + else if (!strncmp(val, "net", strlen(val))) + { + type->type = FW3_IPSET_TYPE_NET; + return true; + } + else if (!strncmp(val, "set", strlen(val))) + { + type->type = FW3_IPSET_TYPE_SET; + return true; + } + + return false; +} + + +void +fw3_parse_options(void *s, + struct fw3_option *opts, int n, + struct uci_section *section) +{ + int i; + char *p; + bool known; + struct uci_element *e, *l; + struct uci_option *o; + struct fw3_option *opt; + struct list_head *item; + struct list_head *dest; + + uci_foreach_element(§ion->options, e) + { + o = uci_to_option(e); + known = false; + + for (i = 0; i < n; i++) + { + opt = &opts[i]; + + if (!opt->parse || !opt->name) + continue; + + if (strcmp(opt->name, e->name)) + continue; + + if (o->type == UCI_TYPE_LIST) + { + if (!opt->elem_size) + { + warn_elem(e, "must not be a list"); + } + else + { + uci_foreach_element(&o->v.list, l) + { + if (!l->name) + continue; + + item = malloc(opt->elem_size); + + if (!item) + continue; + + memset(item, 0, opt->elem_size); + + if (!opt->parse(item, l->name)) + { + warn_elem(e, "has invalid value '%s'", l->name); + free(item); + continue; + } + + dest = (struct list_head *)((char *)s + opt->offset); + list_add_tail(item, dest); + } + } + } + else + { + if (!o->v.string) + continue; + + if (!opt->elem_size) + { + if (!opt->parse((char *)s + opt->offset, o->v.string)) + warn_elem(e, "has invalid value '%s'", o->v.string); + } + else + { + for (p = strtok(o->v.string, " \t"); + p != NULL; + p = strtok(NULL, " \t")) + { + item = malloc(opt->elem_size); + + if (!item) + continue; + + memset(item, 0, opt->elem_size); + + if (!opt->parse(item, p)) + { + warn_elem(e, "has invalid value '%s'", p); + free(item); + continue; + } + + dest = (struct list_head *)((char *)s + opt->offset); + list_add_tail(item, dest); + } + } + } + + known = true; + break; + } + + if (!known) + warn_elem(e, "is unknown"); + } +} + + +void +fw3_format_in_out(struct fw3_device *in, struct fw3_device *out) +{ + if (in && !in->any) + fw3_pr(" %s-i %s", in->invert ? "! " : "", in->name); + + if (out && !out->any) + fw3_pr(" %s-o %s", out->invert ? "! " : "", out->name); +} + +void +fw3_format_src_dest(struct fw3_address *src, struct fw3_address *dest) +{ + char s[INET6_ADDRSTRLEN]; + + if (src && src->set) + { + inet_ntop(src->family == FW3_FAMILY_V4 ? AF_INET : AF_INET6, + &src->address.v4, s, sizeof(s)); + + fw3_pr(" %s-s %s/%u", src->invert ? "! " : "", s, src->mask); + } + + if (dest && dest->set) + { + inet_ntop(dest->family == FW3_FAMILY_V4 ? AF_INET : AF_INET6, + &dest->address.v4, s, sizeof(s)); + + fw3_pr(" %s-d %s/%u", dest->invert ? "! " : "", s, dest->mask); + } +} + +void +fw3_format_sport_dport(struct fw3_port *sp, struct fw3_port *dp) +{ + if (sp && sp->set) + { + if (sp->port_min == sp->port_max) + fw3_pr(" %s--sport %u", sp->invert ? "! " : "", sp->port_min); + else + fw3_pr(" %s--sport %u:%u", + sp->invert ? "! " : "", sp->port_min, sp->port_max); + } + + if (dp && dp->set) + { + if (dp->port_min == dp->port_max) + fw3_pr(" %s--dport %u", dp->invert ? "! " : "", dp->port_min); + else + fw3_pr(" %s--dport %u:%u", + dp->invert ? "! " : "", dp->port_min, dp->port_max); + } +} + +void +fw3_format_mac(struct fw3_mac *mac) +{ + if (!mac) + return; + + fw3_pr(" -m mac %s--mac-source %s", + mac->invert ? "! " : "", ether_ntoa(&mac->mac)); +} + +void +fw3_format_protocol(struct fw3_protocol *proto, enum fw3_family family) +{ + uint16_t pr; + + if (!proto) + return; + + pr = proto->protocol; + + if (pr == 1 && family == FW3_FAMILY_V6) + pr = 58; + + if (proto->any) + fw3_pr(" -p all"); + else + fw3_pr(" %s-p %u", proto->invert ? "! " : "", pr); +} + +void +fw3_format_icmptype(struct fw3_icmptype *icmp, enum fw3_family family) +{ + if (!icmp) + return; + + if (family != FW3_FAMILY_V6) + { + if (icmp->code_min == 0 && icmp->code_max == 0xFF) + fw3_pr(" %s--icmp-type %u", icmp->invert ? "! " : "", icmp->type); + else + fw3_pr(" %s--icmp-type %u/%u", + icmp->invert ? "! " : "", icmp->type, icmp->code_min); + } + else + { + if (icmp->code6_min == 0 && icmp->code6_max == 0xFF) + fw3_pr(" %s--icmpv6-type %u", icmp->invert ? "! " : "", icmp->type6); + else + fw3_pr(" %s--icmpv6-type %u/%u", + icmp->invert ? "! " : "", icmp->type6, icmp->code6_min); + } +} + +void +fw3_format_limit(struct fw3_limit *limit) +{ + if (!limit) + return; + + const char *units[] = { + [FW3_LIMIT_UNIT_SECOND] = "second", + [FW3_LIMIT_UNIT_MINUTE] = "minute", + [FW3_LIMIT_UNIT_HOUR] = "hour", + [FW3_LIMIT_UNIT_DAY] = "day", + }; + + if (limit->rate > 0) + { + fw3_pr(" -m limit %s--limit %u/%s", + limit->invert ? "! " : "", limit->rate, units[limit->unit]); + + if (limit->burst > 0) + fw3_pr(" --limit-burst %u", limit->burst); + } +} + +void +fw3_format_ipset(struct fw3_ipset *ipset, bool invert) +{ + bool first = true; + const char *name = NULL; + struct fw3_ipset_datatype *type; + + if (!ipset) + return; + + if (ipset->external && *ipset->external) + name = ipset->external; + else + name = ipset->name; + + fw3_pr(" -m set %s--match-set %s", invert ? "! " : "", name); + + list_for_each_entry(type, &ipset->datatypes, list) + { + fw3_pr("%c%s", first ? ' ' : ',', type->dest ? "dst" : "src"); + first = false; + } +} + +void +__fw3_format_comment(const char *comment, ...) +{ + va_list ap; + int len = 0; + const char *c; + + if (!comment || !*comment) + return; + + fw3_pr(" -m comment --comment \""); + + c = comment; + + va_start(ap, comment); + + do + { + while (*c) + { + switch (*c) + { + case '"': + case '$': + case '`': + case '\\': + fw3_pr("\\"); + /* fall through */ + + default: + fw3_pr("%c", *c); + break; + } + + c++; + + if (len++ >= 255) + goto end; + } + + c = va_arg(ap, const char *); + } + while (c); + +end: + va_end(ap); + fw3_pr("\""); +} + +void +fw3_format_extra(const char *extra) +{ + if (!extra || !*extra) + return; + + fw3_pr(" %s", extra); +} diff --git a/options.h b/options.h new file mode 100644 index 0000000..0d9fb99 --- /dev/null +++ b/options.h @@ -0,0 +1,410 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __FW3_OPTIONS_H +#define __FW3_OPTIONS_H + + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "icmp_codes.h" +#include "utils.h" + + +enum fw3_table +{ + FW3_TABLE_FILTER, + FW3_TABLE_NAT, + FW3_TABLE_MANGLE, + FW3_TABLE_RAW, +}; + +enum fw3_family +{ + FW3_FAMILY_ANY = 0, + FW3_FAMILY_V4 = 1, + FW3_FAMILY_V6 = 2, +}; + +enum fw3_target +{ + FW3_TARGET_UNSPEC = 0, + FW3_TARGET_ACCEPT = 1, + FW3_TARGET_REJECT = 2, + FW3_TARGET_DROP = 3, + FW3_TARGET_NOTRACK = 4, + FW3_TARGET_DNAT = 5, + FW3_TARGET_SNAT = 6, +}; + +enum fw3_limit_unit +{ + FW3_LIMIT_UNIT_SECOND = 0, + FW3_LIMIT_UNIT_MINUTE = 1, + FW3_LIMIT_UNIT_HOUR = 2, + FW3_LIMIT_UNIT_DAY = 3, +}; + +enum fw3_ipset_method +{ + FW3_IPSET_METHOD_UNSPEC = 0, + FW3_IPSET_METHOD_BITMAP = 1, + FW3_IPSET_METHOD_HASH = 2, + FW3_IPSET_METHOD_LIST = 3, +}; + +enum fw3_ipset_type +{ + FW3_IPSET_TYPE_UNSPEC = 0, + FW3_IPSET_TYPE_IP = 1, + FW3_IPSET_TYPE_PORT = 2, + FW3_IPSET_TYPE_MAC = 3, + FW3_IPSET_TYPE_NET = 4, + FW3_IPSET_TYPE_SET = 5, +}; + +struct fw3_ipset_datatype +{ + struct list_head list; + enum fw3_ipset_type type; + bool dest; +}; + +struct fw3_device +{ + struct list_head list; + + bool set; + bool any; + bool invert; + char name[32]; +}; + +struct fw3_address +{ + struct list_head list; + + bool set; + bool invert; + enum fw3_family family; + int mask; + union { + struct in_addr v4; + struct in6_addr v6; + struct ether_addr mac; + } address; +}; + +struct fw3_mac +{ + struct list_head list; + + bool set; + bool invert; + struct ether_addr mac; +}; + +struct fw3_protocol +{ + struct list_head list; + + bool any; + bool invert; + uint16_t protocol; +}; + +struct fw3_port +{ + struct list_head list; + + bool set; + bool invert; + uint16_t port_min; + uint16_t port_max; +}; + +struct fw3_icmptype +{ + struct list_head list; + + bool invert; + enum fw3_family family; + uint8_t type; + uint8_t code_min; + uint8_t code_max; + uint8_t type6; + uint8_t code6_min; + uint8_t code6_max; +}; + +struct fw3_limit +{ + bool invert; + int rate; + int burst; + enum fw3_limit_unit unit; +}; + +struct fw3_defaults +{ + enum fw3_target policy_input; + enum fw3_target policy_output; + enum fw3_target policy_forward; + + bool drop_invalid; + + bool syn_flood; + struct fw3_limit syn_flood_rate; + + bool tcp_syncookies; + bool tcp_ecn; + bool tcp_westwood; + bool tcp_window_scaling; + + bool accept_redirects; + bool accept_source_route; + + bool custom_chains; + + bool disable_ipv6; +}; + +struct fw3_zone +{ + struct list_head list; + + const char *name; + + enum fw3_family family; + + enum fw3_target policy_input; + enum fw3_target policy_output; + enum fw3_target policy_forward; + + struct list_head networks; + struct list_head devices; + struct list_head subnets; + + const char *extra_src; + const char *extra_dest; + + bool masq; + struct list_head masq_src; + struct list_head masq_dest; + + bool conntrack; + bool mtu_fix; + + bool log; + struct fw3_limit log_limit; + + bool custom_chains; + + bool has_src_target[FW3_TARGET_SNAT + 1]; + bool has_dest_target[FW3_TARGET_SNAT + 1]; +}; + +struct fw3_rule +{ + struct list_head list; + + const char *name; + + enum fw3_family family; + + struct fw3_zone *_src; + struct fw3_zone *_dest; + + struct fw3_device src; + struct fw3_device dest; + + struct fw3_ipset *_ipset; + struct fw3_device ipset; + + struct list_head proto; + + struct list_head ip_src; + struct list_head mac_src; + struct list_head port_src; + + struct list_head ip_dest; + struct list_head port_dest; + + struct list_head icmp_type; + + enum fw3_target target; + + struct fw3_limit limit; + + const char *extra; +}; + +struct fw3_redirect +{ + struct list_head list; + + const char *name; + + enum fw3_family family; + + struct fw3_zone *_src; + struct fw3_zone *_dest; + + struct fw3_device src; + struct fw3_device dest; + + struct fw3_ipset *_ipset; + struct fw3_device ipset; + + struct list_head proto; + + struct fw3_address ip_src; + struct list_head mac_src; + struct fw3_port port_src; + + struct fw3_address ip_dest; + struct fw3_port port_dest; + + struct fw3_address ip_redir; + struct fw3_port port_redir; + + enum fw3_target target; + + const char *extra; + + bool reflection; +}; + +struct fw3_forward +{ + struct list_head list; + + const char *name; + + enum fw3_family family; + + struct fw3_zone *_src; + struct fw3_zone *_dest; + + struct fw3_device src; + struct fw3_device dest; +}; + +struct fw3_ipset +{ + struct list_head list; + + const char *name; + enum fw3_family family; + + enum fw3_ipset_method method; + struct list_head datatypes; + + struct list_head iprange; + struct fw3_port portrange; + + int netmask; + int maxelem; + int hashsize; + + int timeout; + + const char *external; +}; + +struct fw3_state +{ + struct uci_context *uci; + struct fw3_defaults defaults; + struct list_head zones; + struct list_head rules; + struct list_head redirects; + struct list_head forwards; + struct list_head ipsets; + + bool disable_ipsets; +}; + + +struct fw3_option +{ + const char *name; + bool (*parse)(void *, const char *); + uintptr_t offset; + size_t elem_size; +}; + +#define FW3_OPT(name, parse, structure, member) \ + { name, fw3_parse_##parse, offsetof(struct fw3_##structure, member) } + +#define FW3_LIST(name, parse, structure, member) \ + { name, fw3_parse_##parse, offsetof(struct fw3_##structure, member), \ + sizeof(struct fw3_##structure) } + + +bool fw3_parse_bool(void *ptr, const char *val); +bool fw3_parse_int(void *ptr, const char *val); +bool fw3_parse_string(void *ptr, const char *val); +bool fw3_parse_target(void *ptr, const char *val); +bool fw3_parse_limit(void *ptr, const char *val); +bool fw3_parse_device(void *ptr, const char *val); +bool fw3_parse_address(void *ptr, const char *val); +bool fw3_parse_mac(void *ptr, const char *val); +bool fw3_parse_port(void *ptr, const char *val); +bool fw3_parse_family(void *ptr, const char *val); +bool fw3_parse_icmptype(void *ptr, const char *val); +bool fw3_parse_protocol(void *ptr, const char *val); +bool fw3_parse_ipset_method(void *ptr, const char *val); +bool fw3_parse_ipset_datatype(void *ptr, const char *val); + +void fw3_parse_options(void *s, struct fw3_option *opts, int n, + struct uci_section *section); + +void fw3_format_in_out(struct fw3_device *in, struct fw3_device *out); +void fw3_format_src_dest(struct fw3_address *src, struct fw3_address *dest); +void fw3_format_sport_dport(struct fw3_port *sp, struct fw3_port *dp); +void fw3_format_mac(struct fw3_mac *mac); +void fw3_format_protocol(struct fw3_protocol *proto, enum fw3_family family); +void fw3_format_icmptype(struct fw3_icmptype *icmp, enum fw3_family family); +void fw3_format_limit(struct fw3_limit *limit); +void fw3_format_ipset(struct fw3_ipset *ipset, bool invert); + +void __fw3_format_comment(const char *comment, ...); +#define fw3_format_comment(...) __fw3_format_comment(__VA_ARGS__, NULL) + +void fw3_format_extra(const char *extra); + +#endif diff --git a/redirects.c b/redirects.c new file mode 100644 index 0000000..2bf2c37 --- /dev/null +++ b/redirects.c @@ -0,0 +1,400 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "redirects.h" + + +static struct fw3_option redirect_opts[] = { + FW3_OPT("name", string, redirect, name), + FW3_OPT("family", family, redirect, family), + + FW3_OPT("src", device, redirect, src), + FW3_OPT("dest", device, redirect, dest), + + FW3_OPT("ipset", device, redirect, ipset), + + FW3_LIST("proto", protocol, redirect, proto), + + FW3_OPT("src_ip", address, redirect, ip_src), + FW3_LIST("src_mac", mac, redirect, mac_src), + FW3_OPT("src_port", port, redirect, port_src), + + FW3_OPT("src_dip", address, redirect, ip_dest), + FW3_OPT("src_dport", port, redirect, port_dest), + + FW3_OPT("dest_ip", address, redirect, ip_redir), + FW3_OPT("dest_port", port, redirect, port_redir), + + FW3_OPT("extra", string, redirect, extra), + + FW3_OPT("reflection", bool, redirect, reflection), + + FW3_OPT("target", target, redirect, target), +}; + + +void +fw3_load_redirects(struct fw3_state *state, struct uci_package *p) +{ + struct uci_section *s; + struct uci_element *e; + struct fw3_redirect *redir; + + bool valid = false; + + INIT_LIST_HEAD(&state->redirects); + + uci_foreach_element(&p->sections, e) + { + s = uci_to_section(e); + + if (strcmp(s->type, "redirect")) + continue; + + redir = malloc(sizeof(*redir)); + + if (!redir) + continue; + + memset(redir, 0, sizeof(*redir)); + + INIT_LIST_HEAD(&redir->proto); + INIT_LIST_HEAD(&redir->mac_src); + + redir->reflection = true; + + fw3_parse_options(redir, redirect_opts, ARRAY_SIZE(redirect_opts), s); + + if (redir->src.invert) + { + warn_elem(e, "must not have an inverted source"); + fw3_free_redirect(redir); + continue; + } + else if (redir->src.set && !redir->src.any && + !(redir->_src = fw3_lookup_zone(state, redir->src.name))) + { + warn_elem(e, "refers to not existing zone '%s'", redir->src.name); + fw3_free_redirect(redir); + continue; + } + else if (redir->dest.set && !redir->dest.any && + !(redir->_dest = fw3_lookup_zone(state, redir->dest.name))) + { + warn_elem(e, "refers to not existing zone '%s'", redir->dest.name); + fw3_free_redirect(redir); + continue; + } + else if (redir->ipset.set && state->disable_ipsets) + { + warn_elem(e, "skipped due to disabled ipset support"); + fw3_free_redirect(redir); + continue; + } + else if (redir->ipset.set && !redir->ipset.any && + !(redir->_ipset = fw3_lookup_ipset(state, redir->ipset.name))) + { + warn_elem(e, "refers to not declared ipset '%s'", redir->ipset.name); + fw3_free_redirect(redir); + continue; + } + + if (redir->target == FW3_TARGET_UNSPEC) + { + warn_elem(e, "has no target specified, defaulting to DNAT"); + redir->target = FW3_TARGET_DNAT; + } + else if (redir->target < FW3_TARGET_DNAT) + { + warn_elem(e, "has invalid target specified, defaulting to DNAT"); + redir->target = FW3_TARGET_DNAT; + } + + if (redir->target == FW3_TARGET_DNAT) + { + if (redir->src.any) + warn_elem(e, "must not have source '*' for DNAT target"); + else if (!redir->_src) + warn_elem(e, "has no source specified"); + else + { + redir->_src->has_dest_target[redir->target] = true; + redir->_src->conntrack = true; + valid = true; + } + + if (redir->reflection && redir->_dest && redir->_src->masq) + { + redir->_dest->has_dest_target[FW3_TARGET_ACCEPT] = true; + redir->_dest->has_dest_target[FW3_TARGET_DNAT] = true; + redir->_dest->has_dest_target[FW3_TARGET_SNAT] = true; + } + } + else + { + if (redir->dest.any) + warn_elem(e, "must not have destination '*' for SNAT target"); + else if (!redir->_dest) + warn_elem(e, "has no destination specified"); + else if (!redir->ip_dest.set) + warn_elem(e, "has no src_dip option specified"); + else + { + redir->_dest->has_dest_target[redir->target] = true; + redir->_dest->conntrack = true; + valid = true; + } + } + + if (!valid) + { + fw3_free_redirect(redir); + continue; + } + + if (!redir->port_redir.set) + redir->port_redir = redir->port_dest; + + list_add_tail(&redir->list, &state->redirects); + } +} + +static void +print_chain_nat(struct fw3_redirect *redir) +{ + if (redir->target == FW3_TARGET_DNAT) + fw3_pr("-A zone_%s_prerouting", redir->src.name); + else + fw3_pr("-A zone_%s_postrouting", redir->dest.name); +} + +static void +print_snat_dnat(enum fw3_target target, + struct fw3_address *addr, struct fw3_port *port) +{ + const char *t; + char s[sizeof("255.255.255.255 ")]; + + if (target == FW3_TARGET_DNAT) + t = "DNAT --to-destination"; + else + t = "SNAT --to-source"; + + inet_ntop(AF_INET, &addr->address.v4, s, sizeof(s)); + + fw3_pr(" -j %s %s", t, s); + + if (port && port->set) + { + if (port->port_min == port->port_max) + fw3_pr(":%u", port->port_min); + else + fw3_pr(":%u-%u", port->port_min, port->port_max); + } + + fw3_pr("\n"); +} + +static void +print_target_nat(struct fw3_redirect *redir) +{ + if (redir->target == FW3_TARGET_DNAT) + print_snat_dnat(redir->target, &redir->ip_redir, &redir->port_redir); + else + print_snat_dnat(redir->target, &redir->ip_dest, &redir->port_dest); +} + +static void +print_chain_filter(struct fw3_redirect *redir) +{ + if (redir->target == FW3_TARGET_DNAT) + { + /* XXX: check for local ip */ + if (!redir->ip_redir.set) + fw3_pr("-A zone_%s_input", redir->src.name); + else + fw3_pr("-A zone_%s_forward", redir->src.name); + } + else + { + if (redir->src.set && !redir->src.any) + fw3_pr("-A zone_%s_forward", redir->src.name); + else + fw3_pr("-A delegate_forward"); + } +} + +static void +print_target_filter(struct fw3_redirect *redir) +{ + /* XXX: check for local ip */ + if (redir->target == FW3_TARGET_DNAT && !redir->ip_redir.set) + fw3_pr(" -m conntrack --ctstate DNAT -j ACCEPT\n"); + else + fw3_pr(" -j ACCEPT\n"); +} + +static void +print_redirect(enum fw3_table table, enum fw3_family family, + struct fw3_redirect *redir, int num) +{ + struct list_head *ext_addrs, *int_addrs; + struct fw3_address *ext_addr, *int_addr; + struct fw3_device *ext_net, *int_net; + struct fw3_protocol *proto; + struct fw3_mac *mac; + + fw3_foreach(proto, &redir->proto) + fw3_foreach(mac, &redir->mac_src) + { + if (table == FW3_TABLE_NAT) + { + if (redir->name) + info(" * Redirect '%s'", redir->name); + else + info(" * Redirect #%u", num); + + print_chain_nat(redir); + fw3_format_ipset(redir->_ipset, redir->ipset.invert); + fw3_format_protocol(proto, family); + + if (redir->target == FW3_TARGET_DNAT) + { + fw3_format_src_dest(&redir->ip_src, &redir->ip_dest); + fw3_format_sport_dport(&redir->port_src, &redir->port_dest); + } + else + { + fw3_format_src_dest(&redir->ip_src, &redir->ip_redir); + fw3_format_sport_dport(&redir->port_src, &redir->port_redir); + } + + fw3_format_mac(mac); + fw3_format_extra(redir->extra); + fw3_format_comment(redir->name); + print_target_nat(redir); + } + else if (table == FW3_TABLE_FILTER) + { + if (redir->name) + info(" * Redirect '%s'", redir->name); + else + info(" * Redirect #%u", num); + + print_chain_filter(redir); + fw3_format_ipset(redir->_ipset, redir->ipset.invert); + fw3_format_protocol(proto, family); + fw3_format_src_dest(&redir->ip_src, &redir->ip_redir); + fw3_format_sport_dport(&redir->port_src, &redir->port_redir); + fw3_format_mac(mac); + fw3_format_extra(redir->extra); + fw3_format_comment(redir->name); + print_target_filter(redir); + } + } + + /* reflection rules */ + if (redir->target != FW3_TARGET_DNAT || !redir->reflection) + return; + + if (!redir->_dest || !redir->_src->masq) + return; + + list_for_each_entry(ext_net, &redir->_src->networks, list) + { + ext_addrs = fw3_ubus_address(ext_net->name); + + if (!ext_addrs || list_empty(ext_addrs)) + continue; + + list_for_each_entry(int_net, &redir->_dest->networks, list) + { + int_addrs = fw3_ubus_address(int_net->name); + + if (!int_addrs || list_empty(int_addrs)) + continue; + + fw3_foreach(ext_addr, ext_addrs) + fw3_foreach(int_addr, int_addrs) + fw3_foreach(proto, &redir->proto) + { + if (!fw3_is_family(int_addr, family) || + !fw3_is_family(ext_addr, family)) + continue; + + if (!proto || (proto->protocol != 6 && proto->protocol != 17)) + continue; + + ext_addr->mask = 32; + + if (table == FW3_TABLE_NAT) + { + fw3_pr("-A zone_%s_prerouting", redir->dest.name); + fw3_format_protocol(proto, family); + fw3_format_src_dest(int_addr, ext_addr); + fw3_format_sport_dport(NULL, &redir->port_dest); + fw3_format_comment(redir->name, " (reflection)"); + print_snat_dnat(FW3_TARGET_DNAT, + &redir->ip_redir, &redir->port_redir); + + fw3_pr("-A zone_%s_postrouting", redir->dest.name); + fw3_format_protocol(proto, family); + fw3_format_src_dest(int_addr, &redir->ip_redir); + fw3_format_sport_dport(NULL, &redir->port_redir); + fw3_format_comment(redir->name, " (reflection)"); + print_snat_dnat(FW3_TARGET_SNAT, ext_addr, NULL); + } + else if (table == FW3_TABLE_FILTER) + { + fw3_pr("-A zone_%s_forward", redir->dest.name); + fw3_format_protocol(proto, family); + fw3_format_src_dest(int_addr, &redir->ip_redir); + fw3_format_sport_dport(NULL, &redir->port_redir); + fw3_format_comment(redir->name, " (reflection)"); + fw3_pr(" -j zone_%s_dest_ACCEPT\n", redir->dest.name); + } + } + + fw3_ubus_address_free(int_addrs); + } + + fw3_ubus_address_free(ext_addrs); + } +} + +void +fw3_print_redirects(enum fw3_table table, enum fw3_family family, + struct fw3_state *state) +{ + int num = 0; + struct fw3_redirect *redir; + + if (family == FW3_FAMILY_V6) + return; + + list_for_each_entry(redir, &state->redirects, list) + print_redirect(table, family, redir, num++); +} + +void +fw3_free_redirect(struct fw3_redirect *redir) +{ + fw3_free_list(&redir->proto); + fw3_free_list(&redir->mac_src); + free(redir); +} diff --git a/redirects.h b/redirects.h new file mode 100644 index 0000000..e8da040 --- /dev/null +++ b/redirects.h @@ -0,0 +1,33 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __FW3_REDIRECTS_H +#define __FW3_REDIRECTS_H + +#include "options.h" +#include "zones.h" +#include "ipsets.h" +#include "ubus.h" + +void fw3_load_redirects(struct fw3_state *state, struct uci_package *p); +void fw3_print_redirects(enum fw3_table table, enum fw3_family family, + struct fw3_state *state); + +void fw3_free_redirect(struct fw3_redirect *redir); + +#endif diff --git a/rules.c b/rules.c new file mode 100644 index 0000000..3ad2a6a --- /dev/null +++ b/rules.c @@ -0,0 +1,332 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "rules.h" + + +static struct fw3_option rule_opts[] = { + FW3_OPT("name", string, rule, name), + FW3_OPT("family", family, rule, family), + + FW3_OPT("src", device, rule, src), + FW3_OPT("dest", device, rule, dest), + + FW3_OPT("ipset", device, rule, ipset), + + FW3_LIST("proto", protocol, rule, proto), + + FW3_LIST("src_ip", address, rule, ip_src), + FW3_LIST("src_mac", mac, rule, mac_src), + FW3_LIST("src_port", port, rule, port_src), + + FW3_LIST("dest_ip", address, rule, ip_dest), + FW3_LIST("dest_port", port, rule, port_dest), + + FW3_LIST("icmp_type", icmptype, rule, icmp_type), + FW3_OPT("extra", string, rule, extra), + + FW3_OPT("limit", limit, rule, limit), + FW3_OPT("limit_burst", int, rule, limit.burst), + + FW3_OPT("target", target, rule, target), +}; + + +void +fw3_load_rules(struct fw3_state *state, struct uci_package *p) +{ + struct uci_section *s; + struct uci_element *e; + struct fw3_rule *rule; + + INIT_LIST_HEAD(&state->rules); + + uci_foreach_element(&p->sections, e) + { + s = uci_to_section(e); + + if (strcmp(s->type, "rule")) + continue; + + rule = malloc(sizeof(*rule)); + + if (!rule) + continue; + + memset(rule, 0, sizeof(*rule)); + + INIT_LIST_HEAD(&rule->proto); + + INIT_LIST_HEAD(&rule->ip_src); + INIT_LIST_HEAD(&rule->mac_src); + INIT_LIST_HEAD(&rule->port_src); + + INIT_LIST_HEAD(&rule->ip_dest); + INIT_LIST_HEAD(&rule->port_dest); + + INIT_LIST_HEAD(&rule->icmp_type); + + fw3_parse_options(rule, rule_opts, ARRAY_SIZE(rule_opts), s); + + if (rule->src.invert || rule->dest.invert) + { + warn_elem(e, "must not have inverted 'src' or 'dest' options"); + fw3_free_rule(rule); + continue; + } + else if (rule->src.set && !rule->src.any && + !(rule->_src = fw3_lookup_zone(state, rule->src.name))) + { + warn_elem(e, "refers to not existing zone '%s'", rule->src.name); + fw3_free_rule(rule); + continue; + } + else if (rule->dest.set && !rule->dest.any && + !(rule->_dest = fw3_lookup_zone(state, rule->dest.name))) + { + warn_elem(e, "refers to not existing zone '%s'", rule->dest.name); + fw3_free_rule(rule); + continue; + } + else if (rule->ipset.set && state->disable_ipsets) + { + warn_elem(e, "skipped due to disabled ipset support"); + fw3_free_rule(rule); + continue; + } + else if (rule->ipset.set && !rule->ipset.any && + !(rule->_ipset = fw3_lookup_ipset(state, rule->ipset.name))) + { + warn_elem(e, "refers to not declared ipset '%s'", rule->ipset.name); + fw3_free_rule(rule); + continue; + } + + if (!rule->_src && rule->target == FW3_TARGET_NOTRACK) + { + warn_elem(e, "is set to target NOTRACK but has no source assigned"); + fw3_free_rule(rule); + continue; + } + + if (!rule->_src && !rule->_dest && !rule->src.any && !rule->dest.any) + { + warn_elem(e, "has neither a source nor a destination zone assigned " + "- assuming an output rule"); + } + + if (rule->target == FW3_TARGET_UNSPEC) + { + warn_elem(e, "has no target specified, defaulting to REJECT"); + rule->target = FW3_TARGET_REJECT; + } + else if (rule->target > FW3_TARGET_NOTRACK) + { + warn_elem(e, "has invalid target specified, defaulting to REJECT"); + rule->target = FW3_TARGET_REJECT; + } + + if (rule->_dest) + rule->_dest->has_dest_target[rule->target] = true; + + list_add_tail(&rule->list, &state->rules); + continue; + } +} + + +static void +print_chain(struct fw3_rule *rule) +{ + char chain[256]; + + sprintf(chain, "delegate_output"); + + if (rule->target == FW3_TARGET_NOTRACK) + { + sprintf(chain, "zone_%s_notrack", rule->src.name); + } + else + { + if (rule->src.set) + { + if (!rule->src.any) + { + if (rule->dest.set) + sprintf(chain, "zone_%s_forward", rule->src.name); + else + sprintf(chain, "zone_%s_input", rule->src.name); + } + else + { + if (rule->dest.set) + sprintf(chain, "delegate_forward"); + else + sprintf(chain, "delegate_input"); + } + } + + if (rule->dest.set && !rule->src.set) + sprintf(chain, "zone_%s_output", rule->dest.name); + } + + fw3_pr("-A %s", chain); +} + +static void print_target(struct fw3_rule *rule) +{ + char target[256]; + + switch(rule->target) + { + case FW3_TARGET_ACCEPT: + sprintf(target, "ACCEPT"); + break; + + case FW3_TARGET_DROP: + sprintf(target, "DROP"); + break; + + case FW3_TARGET_NOTRACK: + sprintf(target, "NOTRACK"); + break; + + default: + sprintf(target, "REJECT"); + break; + } + + if (rule->dest.set && !rule->dest.any) + fw3_pr(" -j zone_%s_dest_%s\n", rule->dest.name, target); + else if (!strcmp(target, "REJECT")) + fw3_pr(" -j reject\n"); + else + fw3_pr(" -j %s\n", target); +} + +static void +print_rule(enum fw3_table table, enum fw3_family family, + struct fw3_rule *rule, struct fw3_protocol *proto, + struct fw3_address *sip, struct fw3_address *dip, + struct fw3_port *sport, struct fw3_port *dport, + struct fw3_mac *mac, struct fw3_icmptype *icmptype) +{ + if (!fw3_is_family(sip, family) || !fw3_is_family(dip, family)) + return; + + if (proto->protocol == 58 && family == FW3_FAMILY_V4) + return; + + print_chain(rule); + fw3_format_ipset(rule->_ipset, rule->ipset.invert); + fw3_format_protocol(proto, family); + fw3_format_src_dest(sip, dip); + fw3_format_sport_dport(sport, dport); + fw3_format_icmptype(icmptype, family); + fw3_format_mac(mac); + fw3_format_limit(&rule->limit); + fw3_format_extra(rule->extra); + fw3_format_comment(rule->name); + print_target(rule); +} + +static void +expand_rule(enum fw3_table table, enum fw3_family family, + struct fw3_rule *rule, int num) +{ + struct fw3_protocol *proto; + struct fw3_address *sip; + struct fw3_address *dip; + struct fw3_port *sport; + struct fw3_port *dport; + struct fw3_mac *mac; + struct fw3_icmptype *icmptype; + + struct list_head *sports = NULL; + struct list_head *dports = NULL; + struct list_head *icmptypes = NULL; + + struct list_head empty; + INIT_LIST_HEAD(&empty); + + if (!fw3_is_family(rule, family)) + return; + + if ((table == FW3_TABLE_RAW && rule->target != FW3_TARGET_NOTRACK) || + (table != FW3_TABLE_FILTER)) + return; + + if (rule->name) + info(" * Rule '%s'", rule->name); + else + info(" * Rule #%u", num); + + list_for_each_entry(proto, &rule->proto, list) + { + /* icmp / ipv6-icmp */ + if (proto->protocol == 1 || proto->protocol == 58) + { + sports = ∅ + dports = ∅ + icmptypes = &rule->icmp_type; + } + else + { + sports = &rule->port_src; + dports = &rule->port_dest; + icmptypes = ∅ + } + + fw3_foreach(sip, &rule->ip_src) + fw3_foreach(dip, &rule->ip_dest) + fw3_foreach(sport, sports) + fw3_foreach(dport, dports) + fw3_foreach(mac, &rule->mac_src) + fw3_foreach(icmptype, icmptypes) + print_rule(table, family, rule, proto, sip, dip, sport, dport, + mac, icmptype); + } +} + +void +fw3_print_rules(enum fw3_table table, enum fw3_family family, + struct fw3_state *state) +{ + int num = 0; + struct fw3_rule *rule; + + list_for_each_entry(rule, &state->rules, list) + expand_rule(table, family, rule, num++); +} + +void +fw3_free_rule(struct fw3_rule *rule) +{ + fw3_free_list(&rule->proto); + + fw3_free_list(&rule->ip_src); + fw3_free_list(&rule->mac_src); + fw3_free_list(&rule->port_dest); + + fw3_free_list(&rule->ip_dest); + fw3_free_list(&rule->port_dest); + + fw3_free_list(&rule->icmp_type); + + free(rule); +} diff --git a/rules.h b/rules.h new file mode 100644 index 0000000..db93220 --- /dev/null +++ b/rules.h @@ -0,0 +1,33 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __FW3_RULES_H +#define __FW3_RULES_H + +#include "options.h" +#include "zones.h" +#include "ipsets.h" +#include "utils.h" + +void fw3_load_rules(struct fw3_state *state, struct uci_package *p); +void fw3_print_rules(enum fw3_table table, enum fw3_family family, + struct fw3_state *state); + +void fw3_free_rule(struct fw3_rule *rule); + +#endif diff --git a/ubus.c b/ubus.c new file mode 100644 index 0000000..0647bc3 --- /dev/null +++ b/ubus.c @@ -0,0 +1,197 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ubus.h" + + +static struct ubus_context *ctx = NULL; + +bool +fw3_ubus_connect(void) +{ + ctx = ubus_connect(NULL); + return !!ctx; +} + +void +fw3_ubus_disconnect(void) +{ + if (!ctx) + return; + + ubus_free(ctx); + ctx = NULL; +} + +static struct fw3_address * +parse_subnet(enum fw3_family family, struct blob_attr *dict, int rem) +{ + struct blob_attr *cur; + struct fw3_address *addr; + + addr = malloc(sizeof(*addr)); + + if (!addr) + return NULL; + + memset(addr, 0, sizeof(*addr)); + + addr->set = true; + addr->family = family; + + __blob_for_each_attr(cur, dict, rem) + { + if (!strcmp(blobmsg_name(cur), "address")) + inet_pton(family == FW3_FAMILY_V4 ? AF_INET : AF_INET6, + blobmsg_data(cur), &addr->address.v6); + + else if (!strcmp(blobmsg_name(cur), "mask")) + addr->mask = be32_to_cpu(*(uint32_t *)blobmsg_data(cur)); + } + + return addr; +} + +static void +parse_subnets(struct list_head *head, enum fw3_family family, + struct blob_attr *list, int rem) +{ + struct blob_attr *cur; + struct fw3_address *addr; + + __blob_for_each_attr(cur, list, rem) + { + addr = parse_subnet(family, blobmsg_data(cur), blobmsg_data_len(cur)); + + if (addr) + list_add_tail(&addr->list, head); + } +} + +struct dev_addr +{ + struct fw3_device *dev; + struct list_head *addr; +}; + +static void +invoke_cb(struct ubus_request *req, int type, struct blob_attr *msg) +{ + int rem; + char *data; + struct blob_attr *cur; + struct dev_addr *da = (struct dev_addr *)req->priv; + struct fw3_device *dev = da->dev; + + if (!msg) + return; + + rem = blob_len(msg); + __blob_for_each_attr(cur, blob_data(msg), rem) + { + data = blobmsg_data(cur); + + if (dev && !strcmp(blobmsg_name(cur), "device") && !dev->name[0]) + snprintf(dev->name, sizeof(dev->name), "%s", data); + else if (dev && !strcmp(blobmsg_name(cur), "l3_device")) + snprintf(dev->name, sizeof(dev->name), "%s", data); + else if (!dev && !strcmp(blobmsg_name(cur), "ipv4-address")) + parse_subnets(da->addr, FW3_FAMILY_V4, + blobmsg_data(cur), blobmsg_data_len(cur)); + else if (!dev && !strcmp(blobmsg_name(cur), "ipv6-address")) + parse_subnets(da->addr, FW3_FAMILY_V6, + blobmsg_data(cur), blobmsg_data_len(cur)); + } + + if (dev) + dev->set = !!dev->name[0]; +} + +static void * +invoke_common(const char *net, bool dev) +{ + uint32_t id; + char path[128]; + static struct dev_addr da; + + if (!net) + return NULL; + + memset(&da, 0, sizeof(da)); + + if (dev) + da.dev = malloc(sizeof(*da.dev)); + else + da.addr = malloc(sizeof(*da.addr)); + + if ((dev && !da.dev) || (!dev && !da.addr)) + goto fail; + + if (dev) + memset(da.dev, 0, sizeof(*da.dev)); + else + INIT_LIST_HEAD(da.addr); + + snprintf(path, sizeof(path), "network.interface.%s", net); + + if (ubus_lookup_id(ctx, path, &id)) + goto fail; + + if (ubus_invoke(ctx, id, "status", NULL, invoke_cb, &da, 500)) + goto fail; + + if (dev && da.dev->set) + return da.dev; + else if (!dev && !list_empty(da.addr)) + return da.addr; + +fail: + if (da.dev) + free(da.dev); + + if (da.addr) + free(da.addr); + + return NULL; +} + +struct fw3_device * +fw3_ubus_device(const char *net) +{ + return invoke_common(net, true); +} + +struct list_head * +fw3_ubus_address(const char *net) +{ + return invoke_common(net, false); +} + +void +fw3_ubus_address_free(struct list_head *list) +{ + struct fw3_address *addr, *tmp; + + list_for_each_entry_safe(addr, tmp, list, list) + { + list_del(&addr->list); + free(addr); + } + + free(list); +} diff --git a/ubus.h b/ubus.h new file mode 100644 index 0000000..0b4c01a --- /dev/null +++ b/ubus.h @@ -0,0 +1,36 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __FW3_UBUS_H +#define __FW3_UBUS_H + +#include +#include + +#include "options.h" + + +bool fw3_ubus_connect(void); +void fw3_ubus_disconnect(void); + +struct fw3_device * fw3_ubus_device(const char *net); + +struct list_head * fw3_ubus_address(const char *net); +void fw3_ubus_address_free(struct list_head *list); + +#endif diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..9899d4d --- /dev/null +++ b/utils.c @@ -0,0 +1,364 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "utils.h" +#include "options.h" + +static int lock_fd = -1; +static pid_t pipe_pid = -1; +static FILE *pipe_fd = NULL; + +static void +warn_elem_section_name(struct uci_section *s, bool find_name) +{ + int i = 0; + struct uci_option *o; + struct uci_element *tmp; + + if (s->anonymous) + { + uci_foreach_element(&s->package->sections, tmp) + { + if (strcmp(uci_to_section(tmp)->type, s->type)) + continue; + + if (&s->e == tmp) + break; + + i++; + } + + fprintf(stderr, "@%s[%d]", s->type, i); + + if (find_name) + { + uci_foreach_element(&s->options, tmp) + { + o = uci_to_option(tmp); + + if (!strcmp(tmp->name, "name") && (o->type == UCI_TYPE_STRING)) + { + fprintf(stderr, " (%s)", o->v.string); + break; + } + } + } + } + else + { + fprintf(stderr, "'%s'", s->e.name); + } + + if (find_name) + fprintf(stderr, " "); +} + +void +warn_elem(struct uci_element *e, const char *format, ...) +{ + if (e->type == UCI_TYPE_SECTION) + { + fprintf(stderr, "Warning: Section "); + warn_elem_section_name(uci_to_section(e), true); + } + else if (e->type == UCI_TYPE_OPTION) + { + fprintf(stderr, "Warning: Option "); + warn_elem_section_name(uci_to_option(e)->section, false); + fprintf(stderr, ".%s ", e->name); + } + + va_list argptr; + va_start(argptr, format); + vfprintf(stderr, format, argptr); + va_end(argptr); + + fprintf(stderr, "\n"); +} + +void +warn(const char* format, ...) +{ + fprintf(stderr, "Warning: "); + va_list argptr; + va_start(argptr, format); + vfprintf(stderr, format, argptr); + va_end(argptr); + fprintf(stderr, "\n"); +} + +void +error(const char* format, ...) +{ + fprintf(stderr, "Error: "); + va_list argptr; + va_start(argptr, format); + vfprintf(stderr, format, argptr); + va_end(argptr); + fprintf(stderr, "\n"); + + exit(1); +} + +void +info(const char* format, ...) +{ + va_list argptr; + va_start(argptr, format); + vfprintf(stderr, format, argptr); + va_end(argptr); + fprintf(stderr, "\n"); +} + +const char * +fw3_find_command(const char *cmd) +{ + struct stat s; + int plen = 0, clen = strlen(cmd) + 1; + char *search, *p; + static char path[PATH_MAX]; + + if (!stat(cmd, &s) && S_ISREG(s.st_mode)) + return cmd; + + search = getenv("PATH"); + + if (!search) + search = "/bin:/usr/bin:/sbin:/usr/sbin"; + + p = search; + + do + { + if (*p != ':' && *p != '\0') + continue; + + plen = p - search; + + if ((plen + clen) >= sizeof(path)) + continue; + + strncpy(path, search, plen); + sprintf(path + plen, "/%s", cmd); + + if (!stat(path, &s) && S_ISREG(s.st_mode)) + return path; + + search = p + 1; + } + while (*p++); + + return NULL; +} + +bool +fw3_stdout_pipe(void) +{ + pipe_fd = stdout; + return true; +} + +bool +__fw3_command_pipe(bool silent, const char *command, ...) +{ + pid_t pid; + va_list argp; + int pfds[2]; + int argn; + char *arg, **args, **tmp; + + command = fw3_find_command(command); + + if (!command) + return false; + + if (pipe(pfds)) + return false; + + argn = 2; + args = malloc(argn * sizeof(arg)); + + if (!args) + return false; + + args[0] = (char *)command; + args[1] = NULL; + + va_start(argp, command); + + while ((arg = va_arg(argp, char *)) != NULL) + { + tmp = realloc(args, ++argn * sizeof(arg)); + + if (!tmp) + break; + + args = tmp; + args[argn-2] = arg; + args[argn-1] = NULL; + } + + va_end(argp); + + switch ((pid = fork())) + { + case -1: + return false; + + case 0: + dup2(pfds[0], 0); + + close(pfds[0]); + close(pfds[1]); + + close(1); + + if (silent) + close(2); + + execv(command, args); + + default: + signal(SIGPIPE, SIG_IGN); + pipe_pid = pid; + close(pfds[0]); + } + + pipe_fd = fdopen(pfds[1], "w"); + return true; +} + +void +fw3_pr(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(pipe_fd, fmt, args); + va_end(args); +} + +void +fw3_command_close(void) +{ + if (pipe_fd && pipe_fd != stdout) + fclose(pipe_fd); + + if (pipe_pid > -1) + waitpid(pipe_pid, NULL, 0); + + signal(SIGPIPE, SIG_DFL); + + pipe_fd = NULL; + pipe_pid = -1; +} + +bool +fw3_has_table(bool ipv6, const char *table) +{ + FILE *f; + + char line[12]; + bool seen = false; + + const char *path = ipv6 + ? "/proc/net/ip6_tables_names" : "/proc/net/ip_tables_names"; + + if (!(f = fopen(path, "r"))) + return false; + + while (fgets(line, sizeof(line), f)) + { + if (!strncmp(line, table, strlen(table))) + { + seen = true; + break; + } + } + + fclose(f); + + return seen; +} + +bool +fw3_check_statefile(bool test_exists) +{ + struct stat s; + + if (!stat(FW3_STATEFILE, &s)) + { + if (test_exists) + return true; + + warn("The firewall appears to be started already. " + "If it is indeed empty, remove the %s file and retry.", + FW3_STATEFILE); + + return false; + } + else if (test_exists) + { + warn("The firewall appears to stopped already."); + return false; + } + + lock_fd = open(FW3_STATEFILE, O_CREAT | O_RDWR); + + if (lock_fd < 0) + { + warn("Unable to create %s file", FW3_STATEFILE); + goto fail; + } + + if (flock(lock_fd, LOCK_EX)) + { + warn("Unable to acquire exclusive lock on %s file", FW3_STATEFILE); + goto fail; + + } + + return true; + +fail: + if (lock_fd > -1) + { + close(lock_fd); + lock_fd = -1; + } + + return false; +} + +void +fw3_remove_statefile(void) +{ + if (lock_fd > -1) + fw3_close_statefile(); + + if (unlink(FW3_STATEFILE)) + warn("Unable to delete %s file", FW3_STATEFILE); +} + +void +fw3_close_statefile(void) +{ + flock(lock_fd, LOCK_UN); + close(lock_fd); + + lock_fd = -1; +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..efce382 --- /dev/null +++ b/utils.h @@ -0,0 +1,78 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __FW3_UTILS_H +#define __FW3_UTILS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +#define FW3_STATEFILE "/var/run/fw3.lock" + +void warn_elem(struct uci_element *e, const char *format, ...); +void warn(const char *format, ...); +void error(const char *format, ...); +void info(const char *format, ...); + +#define fw3_foreach(p, h) \ + for (p = list_empty(h) ? NULL : list_first_entry(h, typeof(*p), list); \ + list_empty(h) ? (p == NULL) : (&p->list != (h)); \ + p = list_empty(h) ? list_first_entry(h, typeof(*p), list) \ + : list_entry(p->list.next, typeof(*p), list)) + +static inline void +fw3_free_list(struct list_head *list) +{ + struct list_head *cur, *tmp; + + list_for_each_safe(cur, tmp, list) + { + list_del(cur); + free(cur); + } +} + +#define fw3_is_family(p, f) \ + (!p || p->family == FW3_FAMILY_ANY || p->family == f) + +const char * fw3_find_command(const char *cmd); + +bool fw3_stdout_pipe(void); +bool __fw3_command_pipe(bool silent, const char *command, ...); +#define fw3_command_pipe(...) __fw3_command_pipe(__VA_ARGS__, NULL) + +void fw3_command_close(void); +void fw3_pr(const char *fmt, ...); + +bool fw3_has_table(bool ipv6, const char *table); + +bool fw3_check_statefile(bool test_exists); +void fw3_remove_statefile(void); +void fw3_close_statefile(void); + +#endif diff --git a/zones.c b/zones.c new file mode 100644 index 0000000..2820348 --- /dev/null +++ b/zones.c @@ -0,0 +1,472 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "zones.h" +#include "ubus.h" + + +static struct fw3_option zone_opts[] = { + FW3_OPT("name", string, zone, name), + + FW3_LIST("network", device, zone, networks), + FW3_LIST("device", device, zone, devices), + FW3_LIST("subnet", address, zone, subnets), + + FW3_OPT("input", target, zone, policy_input), + FW3_OPT("forward", target, zone, policy_forward), + FW3_OPT("output", target, zone, policy_output), + + FW3_OPT("masq", bool, zone, masq), + FW3_LIST("masq_src", address, zone, masq_src), + FW3_LIST("masq_dest", address, zone, masq_dest), + + FW3_OPT("extra", string, zone, extra_src), + FW3_OPT("extra_src", string, zone, extra_src), + FW3_OPT("extra_dest", string, zone, extra_dest), + + FW3_OPT("conntrack", bool, zone, conntrack), + FW3_OPT("mtu_fix", bool, zone, mtu_fix), + FW3_OPT("custom_chains", bool, zone, custom_chains), + + FW3_OPT("log", bool, zone, log), + FW3_OPT("log_limit", limit, zone, log_limit), +}; + + +static void +check_policy(struct uci_element *e, enum fw3_target *pol, enum fw3_target def, + const char *name) +{ + if (*pol == FW3_TARGET_UNSPEC) + { + warn_elem(e, "has no %s policy specified, using default", name); + *pol = def; + } + else if (*pol > FW3_TARGET_DROP) + { + warn_elem(e, "has invalid %s policy, using default", name); + *pol = def; + } +} + +static void +resolve_networks(struct uci_element *e, struct fw3_zone *zone) +{ + struct fw3_device *net, *tmp; + + list_for_each_entry(net, &zone->networks, list) + { + tmp = fw3_ubus_device(net->name); + + if (!tmp) + { + warn_elem(e, "cannot resolve device of network '%s'", net->name); + continue; + } + + list_add_tail(&tmp->list, &zone->devices); + } +} + +void +fw3_load_zones(struct fw3_state *state, struct uci_package *p) +{ + struct uci_section *s; + struct uci_element *e; + struct fw3_zone *zone; + struct fw3_defaults *defs = &state->defaults; + + INIT_LIST_HEAD(&state->zones); + + uci_foreach_element(&p->sections, e) + { + s = uci_to_section(e); + + if (strcmp(s->type, "zone")) + continue; + + zone = malloc(sizeof(*zone)); + + if (!zone) + continue; + + memset(zone, 0, sizeof(*zone)); + + INIT_LIST_HEAD(&zone->networks); + INIT_LIST_HEAD(&zone->devices); + INIT_LIST_HEAD(&zone->subnets); + INIT_LIST_HEAD(&zone->masq_src); + INIT_LIST_HEAD(&zone->masq_dest); + + zone->log_limit.rate = 10; + + fw3_parse_options(zone, zone_opts, ARRAY_SIZE(zone_opts), s); + + if (!zone->extra_dest) + zone->extra_dest = zone->extra_src; + + if (!zone->name || !*zone->name) + { + warn_elem(e, "has no name - ignoring"); + fw3_free_zone(zone); + continue; + } + + if (list_empty(&zone->networks) && list_empty(&zone->devices) && + list_empty(&zone->subnets) && !zone->extra_src) + { + warn_elem(e, "has no device, network, subnet or extra options"); + } + + check_policy(e, &zone->policy_input, defs->policy_input, "input"); + check_policy(e, &zone->policy_output, defs->policy_output, "output"); + check_policy(e, &zone->policy_forward, defs->policy_forward, "forward"); + + resolve_networks(e, zone); + + if (zone->masq) + { + zone->has_dest_target[FW3_TARGET_SNAT] = true; + zone->conntrack = true; + } + + zone->has_src_target[zone->policy_input] = true; + zone->has_dest_target[zone->policy_output] = true; + zone->has_dest_target[zone->policy_forward] = true; + + list_add_tail(&zone->list, &state->zones); + } +} + + +static void +print_zone_chain(enum fw3_table table, enum fw3_family family, + struct fw3_zone *zone, bool disable_notrack) +{ + enum fw3_target t; + const char *targets[] = { + "(bug)", + "ACCEPT", + "REJECT", + "DROP", + }; + + if (!fw3_is_family(zone, family)) + return; + + switch (table) + { + case FW3_TABLE_FILTER: + info(" * Zone '%s'", zone->name); + + for (t = FW3_TARGET_ACCEPT; t <= FW3_TARGET_DROP; t++) + { + if (zone->has_src_target[t]) + fw3_pr(":zone_%s_src_%s - [0:0]\n", zone->name, targets[t]); + + if (zone->has_dest_target[t]) + fw3_pr(":zone_%s_dest_%s - [0:0]\n", zone->name, targets[t]); + } + + fw3_pr(":zone_%s_forward - [0:0]\n", zone->name); + fw3_pr(":zone_%s_input - [0:0]\n", zone->name); + fw3_pr(":zone_%s_output - [0:0]\n", zone->name); + break; + + case FW3_TABLE_NAT: + if (family == FW3_FAMILY_V4) + { + info(" * Zone '%s'", zone->name); + + if (zone->has_dest_target[FW3_TARGET_SNAT]) + fw3_pr(":zone_%s_postrouting - [0:0]\n", zone->name); + + if (zone->has_dest_target[FW3_TARGET_DNAT]) + fw3_pr(":zone_%s_prerouting - [0:0]\n", zone->name); + } + break; + + case FW3_TABLE_RAW: + if (!zone->conntrack && !disable_notrack) + { + info(" * Zone '%s'", zone->name); + fw3_pr(":zone_%s_notrack - [0:0]\n", zone->name); + } + break; + + case FW3_TABLE_MANGLE: + break; + } +} + +static void +print_interface_rule(enum fw3_table table, enum fw3_family family, + struct fw3_zone *zone, struct fw3_device *dev, + struct fw3_address *sub, bool disable_notrack) +{ + enum fw3_target t; + const char *targets[] = { + "(bug)", "(bug)", + "ACCEPT", "ACCEPT", + "REJECT", "reject", + "DROP", "DROP", + }; + + if (table == FW3_TABLE_FILTER) + { + for (t = FW3_TARGET_ACCEPT; t <= FW3_TARGET_DROP; t++) + { + if (zone->has_src_target[t]) + { + fw3_pr("-A zone_%s_src_%s", zone->name, targets[t*2]); + fw3_format_in_out(dev, NULL); + fw3_format_src_dest(sub, NULL); + fw3_format_extra(zone->extra_src); + fw3_pr(" -j %s\n", targets[t*2+1]); + } + + if (zone->has_dest_target[t]) + { + fw3_pr("-A zone_%s_dest_%s", zone->name, targets[t*2]); + fw3_format_in_out(NULL, dev); + fw3_format_src_dest(NULL, sub); + fw3_format_extra(zone->extra_dest); + fw3_pr(" -j %s\n", targets[t*2+1]); + } + } + + fw3_pr("-A delegate_input"); + fw3_format_in_out(dev, NULL); + fw3_format_src_dest(sub, NULL); + fw3_format_extra(zone->extra_src); + fw3_pr(" -j zone_%s_input\n", zone->name); + + fw3_pr("-A delegate_forward"); + fw3_format_in_out(dev, NULL); + fw3_format_src_dest(sub, NULL); + fw3_format_extra(zone->extra_src); + fw3_pr(" -j zone_%s_forward\n", zone->name); + + fw3_pr("-A delegate_output"); + fw3_format_in_out(NULL, dev); + fw3_format_src_dest(NULL, sub); + fw3_format_extra(zone->extra_dest); + fw3_pr(" -j zone_%s_output\n", zone->name); + } + else if (table == FW3_TABLE_NAT) + { + if (zone->has_dest_target[FW3_TARGET_DNAT]) + { + fw3_pr("-A PREROUTING"); + fw3_format_in_out(dev, NULL); + fw3_format_src_dest(sub, NULL); + fw3_format_extra(zone->extra_src); + fw3_pr(" -j zone_%s_prerouting\n", zone->name); + } + + if (zone->has_dest_target[FW3_TARGET_SNAT]) + { + fw3_pr("-A POSTROUTING"); + fw3_format_in_out(NULL, dev); + fw3_format_src_dest(NULL, sub); + fw3_format_extra(zone->extra_dest); + fw3_pr(" -j zone_%s_postrouting\n", zone->name); + } + } + else if (table == FW3_TABLE_MANGLE) + { + if (zone->mtu_fix) + { + if (zone->log) + { + fw3_pr("-A mssfix"); + fw3_format_in_out(NULL, dev); + fw3_format_src_dest(NULL, sub); + fw3_pr(" -p tcp --tcp-flags SYN,RST SYN"); + fw3_format_limit(&zone->log_limit); + fw3_format_comment(zone->name, " (mtu_fix logging)"); + fw3_pr(" -j LOG --log-prefix \"MSSFIX(%s): \"\n", zone->name); + } + + fw3_pr("-A mssfix"); + fw3_format_in_out(NULL, dev); + fw3_format_src_dest(NULL, sub); + fw3_pr(" -p tcp --tcp-flags SYN,RST SYN"); + fw3_format_comment(zone->name, " (mtu_fix)"); + fw3_pr(" -j TCPMSS --clamp-mss-to-pmtu\n"); + } + } + else if (table == FW3_TABLE_RAW) + { + if (!zone->conntrack && !disable_notrack) + { + fw3_pr("-A notrack"); + fw3_format_in_out(dev, NULL); + fw3_format_src_dest(sub, NULL); + fw3_format_extra(zone->extra_src); + fw3_format_comment(zone->name, " (notrack)"); + fw3_pr(" -j CT --notrack\n", zone->name); + } + } +} + +static void +print_interface_rules(enum fw3_table table, enum fw3_family family, + struct fw3_zone *zone, bool disable_notrack) +{ + struct fw3_device *dev; + struct fw3_address *sub; + + fw3_foreach(dev, &zone->devices) + fw3_foreach(sub, &zone->subnets) + { + if (!fw3_is_family(sub, family)) + continue; + + if (!dev && !sub) + continue; + + print_interface_rule(table, family, zone, dev, sub, disable_notrack); + } +} + +static void +print_zone_rule(enum fw3_table table, enum fw3_family family, + struct fw3_zone *zone, bool disable_notrack) +{ + struct fw3_address *msrc; + struct fw3_address *mdest; + + enum fw3_target t; + const char *targets[] = { + "(bug)", + "ACCEPT", + "REJECT", + "DROP", + "(bug)", + "(bug)", + "(bug)", + }; + + if (!fw3_is_family(zone, family)) + return; + + switch (table) + { + case FW3_TABLE_FILTER: + fw3_pr("-A zone_%s_input -j zone_%s_src_%s\n", + zone->name, zone->name, targets[zone->policy_input]); + + fw3_pr("-A zone_%s_forward -j zone_%s_dest_%s\n", + zone->name, zone->name, targets[zone->policy_forward]); + + fw3_pr("-A zone_%s_output -j zone_%s_dest_%s\n", + zone->name, zone->name, targets[zone->policy_output]); + + if (zone->log) + { + for (t = FW3_TARGET_REJECT; t <= FW3_TARGET_DROP; t++) + { + if (zone->has_src_target[t]) + { + fw3_pr("-A zone_%s_src_%s", zone->name, targets[t]); + fw3_format_limit(&zone->log_limit); + fw3_pr(" -j LOG --log-prefix \"%s(src %s)\"\n", + targets[t], zone->name); + } + + if (zone->has_dest_target[t]) + { + fw3_pr("-A zone_%s_dest_%s", zone->name, targets[t]); + fw3_format_limit(&zone->log_limit); + fw3_pr(" -j LOG --log-prefix \"%s(dest %s)\"\n", + targets[t], zone->name); + } + } + } + break; + + case FW3_TABLE_NAT: + if (zone->masq && family == FW3_FAMILY_V4) + { + fw3_foreach(msrc, &zone->masq_src) + fw3_foreach(mdest, &zone->masq_dest) + { + fw3_pr("-A zone_%s_postrouting ", zone->name); + fw3_format_src_dest(msrc, mdest); + fw3_pr("-j MASQUERADE\n"); + } + } + break; + + case FW3_TABLE_RAW: + case FW3_TABLE_MANGLE: + break; + } + + print_interface_rules(table, family, zone, disable_notrack); +} + +void +fw3_print_zone_chains(enum fw3_table table, enum fw3_family family, + struct fw3_state *state) +{ + struct fw3_zone *zone; + + list_for_each_entry(zone, &state->zones, list) + print_zone_chain(table, family, zone, state->defaults.drop_invalid); +} + +void +fw3_print_zone_rules(enum fw3_table table, enum fw3_family family, + struct fw3_state *state) +{ + struct fw3_zone *zone; + + list_for_each_entry(zone, &state->zones, list) + print_zone_rule(table, family, zone, state->defaults.drop_invalid); +} + + +struct fw3_zone * +fw3_lookup_zone(struct fw3_state *state, const char *name) +{ + struct fw3_zone *z; + + if (list_empty(&state->zones)) + return NULL; + + list_for_each_entry(z, &state->zones, list) + if (!strcmp(z->name, name)) + return z; + + return NULL; +} + +void +fw3_free_zone(struct fw3_zone *zone) +{ + fw3_free_list(&zone->networks); + fw3_free_list(&zone->devices); + fw3_free_list(&zone->subnets); + + fw3_free_list(&zone->masq_src); + fw3_free_list(&zone->masq_dest); + + free(zone); +} diff --git a/zones.h b/zones.h new file mode 100644 index 0000000..8537af7 --- /dev/null +++ b/zones.h @@ -0,0 +1,36 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __FW3_ZONES_H +#define __FW3_ZONES_H + +#include "options.h" + +void fw3_load_zones(struct fw3_state *state, struct uci_package *p); + +void fw3_print_zone_chains(enum fw3_table table, enum fw3_family family, + struct fw3_state *state); + +void fw3_print_zone_rules(enum fw3_table table, enum fw3_family family, + struct fw3_state *state); + +struct fw3_zone * fw3_lookup_zone(struct fw3_state *state, const char *name); + +void fw3_free_zone(struct fw3_zone *zone); + +#endif