initial commit
authorJo-Philipp Wich <jow@openwrt.org>
Sun, 17 Feb 2013 13:31:47 +0000 (14:31 +0100)
committerJo-Philipp Wich <jow@openwrt.org>
Sun, 17 Feb 2013 13:31:47 +0000 (14:31 +0100)
21 files changed:
CMakeLists.txt [new file with mode: 0644]
defaults.c [new file with mode: 0644]
defaults.h [new file with mode: 0644]
forwards.c [new file with mode: 0644]
forwards.h [new file with mode: 0644]
icmp_codes.h [new file with mode: 0644]
ipsets.c [new file with mode: 0644]
ipsets.h [new file with mode: 0644]
main.c [new file with mode: 0644]
options.c [new file with mode: 0644]
options.h [new file with mode: 0644]
redirects.c [new file with mode: 0644]
redirects.h [new file with mode: 0644]
rules.c [new file with mode: 0644]
rules.h [new file with mode: 0644]
ubus.c [new file with mode: 0644]
ubus.h [new file with mode: 0644]
utils.c [new file with mode: 0644]
utils.h [new file with mode: 0644]
zones.c [new file with mode: 0644]
zones.h [new file with mode: 0644]

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e944949
--- /dev/null
@@ -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 (file)
index 0000000..498e5d5
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 (file)
index 0000000..10ac68c
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 (file)
index 0000000..c212c8c
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 (file)
index 0000000..c3caff9
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 (file)
index 0000000..4edfbb9
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 (file)
index 0000000..3d659e2
--- /dev/null
+++ b/ipsets.c
@@ -0,0 +1,399 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 (file)
index 0000000..5ad6d99
--- /dev/null
+++ b/ipsets.h
@@ -0,0 +1,49 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 (file)
index 0000000..ddbd24d
--- /dev/null
+++ b/main.c
@@ -0,0 +1,378 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..5d325fc
--- /dev/null
+++ b/options.c
@@ -0,0 +1,800 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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(&section->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 (file)
index 0000000..0d9fb99
--- /dev/null
+++ b/options.h
@@ -0,0 +1,410 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 <errno.h>
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+
+#include <ctype.h>
+#include <string.h>
+
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ether.h>
+
+#include <uci.h>
+
+#include <libubox/list.h>
+#include <libubox/utils.h>
+
+#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 (file)
index 0000000..2bf2c37
--- /dev/null
@@ -0,0 +1,400 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 (file)
index 0000000..e8da040
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 (file)
index 0000000..3ad2a6a
--- /dev/null
+++ b/rules.c
@@ -0,0 +1,332 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 = &empty;
+                       dports = &empty;
+                       icmptypes = &rule->icmp_type;
+               }
+               else
+               {
+                       sports = &rule->port_src;
+                       dports = &rule->port_dest;
+                       icmptypes = &empty;
+               }
+
+               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 (file)
index 0000000..db93220
--- /dev/null
+++ b/rules.h
@@ -0,0 +1,33 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 (file)
index 0000000..0647bc3
--- /dev/null
+++ b/ubus.c
@@ -0,0 +1,197 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 (file)
index 0000000..0b4c01a
--- /dev/null
+++ b/ubus.h
@@ -0,0 +1,36 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 <libubus.h>
+#include <libubox/blobmsg.h>
+
+#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 (file)
index 0000000..9899d4d
--- /dev/null
+++ b/utils.c
@@ -0,0 +1,364 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 (file)
index 0000000..efce382
--- /dev/null
+++ b/utils.h
@@ -0,0 +1,78 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/file.h>
+
+#include <libubox/list.h>
+#include <uci.h>
+
+
+#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 (file)
index 0000000..2820348
--- /dev/null
+++ b/zones.c
@@ -0,0 +1,472 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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 (file)
index 0000000..8537af7
--- /dev/null
+++ b/zones.h
@@ -0,0 +1,36 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * 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