From 8fee8f9c520c58d07772cc6bd8f65d9eb1776a56 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Sun, 17 Feb 2013 14:31:47 +0100 Subject: [PATCH] initial commit --- CMakeLists.txt | 20 ++ defaults.c | 283 ++++++++++++++++++++ defaults.h | 35 +++ forwards.c | 146 +++++++++++ forwards.h | 32 +++ icmp_codes.h | 125 +++++++++ ipsets.c | 399 ++++++++++++++++++++++++++++ ipsets.h | 49 ++++ main.c | 378 +++++++++++++++++++++++++++ options.c | 800 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ options.h | 410 +++++++++++++++++++++++++++++ redirects.c | 400 +++++++++++++++++++++++++++++ redirects.h | 33 +++ rules.c | 332 ++++++++++++++++++++++++ rules.h | 33 +++ ubus.c | 197 ++++++++++++++ ubus.h | 36 +++ utils.c | 364 ++++++++++++++++++++++++++ utils.h | 78 ++++++ zones.c | 472 ++++++++++++++++++++++++++++++++++ zones.h | 36 +++ 21 files changed, 4658 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 defaults.c create mode 100644 defaults.h create mode 100644 forwards.c create mode 100644 forwards.h create mode 100644 icmp_codes.h create mode 100644 ipsets.c create mode 100644 ipsets.h create mode 100644 main.c create mode 100644 options.c create mode 100644 options.h create mode 100644 redirects.c create mode 100644 redirects.h create mode 100644 rules.c create mode 100644 rules.h create mode 100644 ubus.c create mode 100644 ubus.h create mode 100644 utils.c create mode 100644 utils.h create mode 100644 zones.c create mode 100644 zones.h 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 -- 2.11.0