summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt20
-rw-r--r--defaults.c283
-rw-r--r--defaults.h35
-rw-r--r--forwards.c146
-rw-r--r--forwards.h32
-rw-r--r--icmp_codes.h125
-rw-r--r--ipsets.c399
-rw-r--r--ipsets.h49
-rw-r--r--main.c378
-rw-r--r--options.c800
-rw-r--r--options.h410
-rw-r--r--redirects.c400
-rw-r--r--redirects.h33
-rw-r--r--rules.c332
-rw-r--r--rules.h33
-rw-r--r--ubus.c197
-rw-r--r--ubus.h36
-rw-r--r--utils.c364
-rw-r--r--utils.h78
-rw-r--r--zones.c472
-rw-r--r--zones.h36
21 files changed, 4658 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..e944949
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 2.6)
+
+PROJECT(firewall3 C)
+ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations)
+
+SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
+
+IF(APPLE)
+ INCLUDE_DIRECTORIES(/opt/local/include)
+ LINK_DIRECTORIES(/opt/local/lib)
+ENDIF()
+
+ADD_EXECUTABLE(firewall3 main.c options.c defaults.c zones.c forwards.c rules.c redirects.c utils.c ubus.c ipsets.c)
+TARGET_LINK_LIBRARIES(firewall3 uci ubox ubus)
+
+SET(CMAKE_INSTALL_PREFIX /usr)
+
+INSTALL(TARGETS firewall3
+ RUNTIME DESTINATION sbin
+)
diff --git a/defaults.c b/defaults.c
new file mode 100644
index 0000000..498e5d5
--- /dev/null
+++ b/defaults.c
@@ -0,0 +1,283 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ * Copyright (C) 2013 Jo-Philipp Wich <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
index 0000000..10ac68c
--- /dev/null
+++ b/defaults.h
@@ -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
index 0000000..c212c8c
--- /dev/null
+++ b/forwards.c
@@ -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
index 0000000..c3caff9
--- /dev/null
+++ b/forwards.h
@@ -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
index 0000000..4edfbb9
--- /dev/null
+++ b/icmp_codes.h
@@ -0,0 +1,125 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ * Copyright (C) 2013 Jo-Philipp Wich <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
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
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
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
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
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
index 0000000..2bf2c37
--- /dev/null
+++ b/redirects.c
@@ -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
index 0000000..e8da040
--- /dev/null
+++ b/redirects.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_REDIRECTS_H
+#define __FW3_REDIRECTS_H
+
+#include "options.h"
+#include "zones.h"
+#include "ipsets.h"
+#include "ubus.h"
+
+void fw3_load_redirects(struct fw3_state *state, struct uci_package *p);
+void fw3_print_redirects(enum fw3_table table, enum fw3_family family,
+ struct fw3_state *state);
+
+void fw3_free_redirect(struct fw3_redirect *redir);
+
+#endif
diff --git a/rules.c b/rules.c
new file mode 100644
index 0000000..3ad2a6a
--- /dev/null
+++ b/rules.c
@@ -0,0 +1,332 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ * Copyright (C) 2013 Jo-Philipp Wich <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
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
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
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
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
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
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
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