summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--defaults.c2
-rw-r--r--helpers.c277
-rw-r--r--helpers.conf87
-rw-r--r--helpers.h50
-rw-r--r--iptables.c10
-rw-r--r--iptables.h2
-rw-r--r--main.c5
-rw-r--r--options.c23
-rw-r--r--options.h58
-rw-r--r--redirects.c134
-rw-r--r--redirects.h1
-rw-r--r--rules.c94
-rw-r--r--rules.h1
-rw-r--r--utils.c21
-rw-r--r--utils.h4
-rw-r--r--zones.c69
17 files changed, 809 insertions, 31 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 81928a6..0ba72ff 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -26,7 +26,7 @@ ENDIF()
FIND_PATH(uci_include_dir uci.h)
INCLUDE_DIRECTORIES(${uci_include_dir})
-ADD_EXECUTABLE(firewall3 main.c options.c defaults.c zones.c forwards.c rules.c redirects.c snats.c utils.c ubus.c ipsets.c includes.c iptables.c)
+ADD_EXECUTABLE(firewall3 main.c options.c defaults.c zones.c forwards.c rules.c redirects.c snats.c utils.c ubus.c ipsets.c includes.c iptables.c helpers.c)
TARGET_LINK_LIBRARIES(firewall3 uci ubox ubus xtables m dl ${iptc_libs} ${ext_libs})
SET(CMAKE_INSTALL_PREFIX /usr)
diff --git a/defaults.c b/defaults.c
index 85a3750..7b2d9e6 100644
--- a/defaults.c
+++ b/defaults.c
@@ -54,6 +54,7 @@ const struct fw3_option fw3_flag_opts[] = {
FW3_OPT("accept_redirects", bool, defaults, accept_redirects),
FW3_OPT("accept_source_route", bool, defaults, accept_source_route),
+ FW3_OPT("auto_helper", bool, defaults, auto_helper),
FW3_OPT("custom_chains", bool, defaults, custom_chains),
FW3_OPT("disable_ipv6", bool, defaults, disable_ipv6),
@@ -93,6 +94,7 @@ fw3_load_defaults(struct fw3_state *state, struct uci_package *p)
defs->tcp_syncookies = true;
defs->tcp_window_scaling = true;
defs->custom_chains = true;
+ defs->auto_helper = true;
uci_foreach_element(&p->sections, e)
{
diff --git a/helpers.c b/helpers.c
new file mode 100644
index 0000000..8cad0b3
--- /dev/null
+++ b/helpers.c
@@ -0,0 +1,277 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ * Copyright (C) 2018 Jo-Philipp Wich <jo@mein.io>
+ *
+ * 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 "helpers.h"
+
+
+const struct fw3_option fw3_cthelper_opts[] = {
+ FW3_OPT("enabled", bool, cthelper, enabled),
+ FW3_OPT("name", string, cthelper, name),
+ FW3_OPT("module", string, cthelper, module),
+ FW3_OPT("description", string, cthelper, description),
+ FW3_OPT("family", family, cthelper, family),
+ FW3_OPT("proto", protocol, cthelper, proto),
+ FW3_OPT("port", port, cthelper, port),
+
+ { }
+};
+
+
+static bool
+test_module(struct fw3_cthelper *helper)
+{
+ struct stat s;
+ char path[sizeof("/sys/module/nf_conntrack_xxxxxxxxxxxxxxxx")];
+
+ snprintf(path, sizeof(path), "/sys/module/%s", helper->module);
+
+ if (stat(path, &s) || !S_ISDIR(s.st_mode))
+ return false;
+
+ return true;
+}
+
+static bool
+check_cthelper(struct fw3_state *state, struct fw3_cthelper *helper, struct uci_element *e)
+{
+ if (!helper->name || !*helper->name)
+ {
+ warn_section("helper", helper, e, "must have a name assigned");
+ }
+ else if (!helper->module || !*helper->module)
+ {
+ warn_section("helper", helper, e, "must have a module assigned");
+ }
+ else if (!helper->proto.protocol || helper->proto.any || helper->proto.invert)
+ {
+ warn_section("helper", helper, e, "must specify a protocol");
+ }
+ else if (helper->port.set && helper->port.invert)
+ {
+ warn_section("helper", helper, e, "must not specify negated ports");
+ }
+ else
+ {
+ return true;
+ }
+
+ return false;
+}
+
+static struct fw3_cthelper *
+fw3_alloc_cthelper(struct fw3_state *state)
+{
+ struct fw3_cthelper *helper;
+
+ helper = calloc(1, sizeof(*helper));
+ if (!helper)
+ return NULL;
+
+ helper->enabled = true;
+ helper->family = FW3_FAMILY_ANY;
+
+ list_add_tail(&helper->list, &state->cthelpers);
+
+ return helper;
+}
+
+static void
+load_cthelpers(struct fw3_state *state, struct uci_package *p)
+{
+ struct fw3_cthelper *helper;
+ struct uci_section *s;
+ struct uci_element *e;
+
+ uci_foreach_element(&p->sections, e)
+ {
+ s = uci_to_section(e);
+
+ if (strcmp(s->type, "helper"))
+ continue;
+
+ helper = fw3_alloc_cthelper(state);
+
+ if (!helper)
+ continue;
+
+ if (!fw3_parse_options(helper, fw3_cthelper_opts, s))
+ warn_elem(e, "has invalid options");
+
+ if (!check_cthelper(state, helper, e))
+ fw3_free_cthelper(helper);
+ }
+}
+
+void
+fw3_load_cthelpers(struct fw3_state *state, struct uci_package *p)
+{
+ struct uci_package *hp = NULL;
+ FILE *fp;
+
+ INIT_LIST_HEAD(&state->cthelpers);
+
+ fp = fopen(FW3_HELPERCONF, "r");
+
+ if (fp) {
+ uci_import(state->uci, fp, "fw3_ct_helpers", &hp, true);
+ fclose(fp);
+
+ if (hp)
+ load_cthelpers(state, hp);
+ }
+
+ load_cthelpers(state, p);
+}
+
+struct fw3_cthelper *
+fw3_lookup_cthelper(struct fw3_state *state, const char *name)
+{
+ struct fw3_cthelper *h;
+
+ if (list_empty(&state->cthelpers))
+ return NULL;
+
+ list_for_each_entry(h, &state->cthelpers, list)
+ {
+ if (strcasecmp(h->name, name))
+ continue;
+
+ return h;
+ }
+
+ return NULL;
+}
+
+struct fw3_cthelper *
+fw3_lookup_cthelper_by_proto_port(struct fw3_state *state,
+ struct fw3_protocol *proto,
+ struct fw3_port *port)
+{
+ struct fw3_cthelper *h;
+
+ if (list_empty(&state->cthelpers))
+ return NULL;
+
+ if (!proto || !proto->protocol || proto->any || proto->invert)
+ return NULL;
+
+ if (port && port->invert)
+ return NULL;
+
+ list_for_each_entry(h, &state->cthelpers, list)
+ {
+ if (!h->enabled)
+ continue;
+
+ if (h->proto.protocol != proto->protocol)
+ continue;
+
+ if (h->port.set && (!port || !port->set))
+ continue;
+
+ if (!h->port.set && (!port || !port->set))
+ return h;
+
+ if (h->port.set && port && port->set &&
+ h->port.port_min <= port->port_min &&
+ h->port.port_max >= port->port_max)
+ return h;
+ }
+
+ return NULL;
+}
+
+static void
+print_helper_rule(struct fw3_ipt_handle *handle, struct fw3_cthelper *helper,
+ struct fw3_zone *zone)
+{
+ struct fw3_ipt_rule *r;
+
+ r = fw3_ipt_rule_create(handle, &helper->proto, NULL, NULL, NULL, NULL);
+
+ if (helper->description && *helper->description)
+ fw3_ipt_rule_comment(r, helper->description);
+ else
+ fw3_ipt_rule_comment(r, helper->name);
+
+ fw3_ipt_rule_sport_dport(r, NULL, &helper->port);
+ fw3_ipt_rule_target(r, "CT");
+ fw3_ipt_rule_addarg(r, false, "--helper", helper->name);
+ fw3_ipt_rule_replace(r, "zone_%s_helper", zone->name);
+}
+
+void
+fw3_print_cthelpers(struct fw3_ipt_handle *handle, struct fw3_state *state,
+ struct fw3_zone *zone)
+{
+ struct fw3_cthelper *helper;
+ struct fw3_cthelpermatch *match;
+
+ if (handle->table != FW3_TABLE_RAW)
+ return;
+
+ if (!fw3_is_family(zone, handle->family))
+ return;
+
+ if (list_empty(&zone->cthelpers))
+ {
+ if (zone->masq || !zone->auto_helper)
+ return;
+
+ if (list_empty(&state->cthelpers))
+ return;
+
+ info(" - Using automatic conntrack helper attachment");
+
+ list_for_each_entry(helper, &state->cthelpers, list)
+ {
+ if (!helper || !helper->enabled)
+ continue;
+
+ if (!fw3_is_family(helper, handle->family))
+ continue;
+
+ if (!test_module(helper))
+ continue;
+
+ print_helper_rule(handle, helper, zone);
+ }
+ }
+ else
+ {
+ list_for_each_entry(match, &zone->cthelpers, list)
+ {
+ helper = match->ptr;
+
+ if (!helper || !helper->enabled)
+ continue;
+
+ if (!fw3_is_family(helper, handle->family))
+ continue;
+
+ if (!test_module(helper))
+ {
+ info(" ! Conntrack module '%s' for helper '%s' is not loaded",
+ helper->module, helper->name);
+ continue;
+ }
+
+ print_helper_rule(handle, helper, zone);
+ }
+ }
+}
diff --git a/helpers.conf b/helpers.conf
new file mode 100644
index 0000000..55aa19d
--- /dev/null
+++ b/helpers.conf
@@ -0,0 +1,87 @@
+config helper
+ option name 'amanda'
+ option description 'Amanda backup and archiving proto'
+ option module 'nf_conntrack_amanda'
+ option family 'any'
+ option proto 'udp'
+ option port '10080'
+
+config helper
+ option name 'ftp'
+ option description 'FTP passive connection tracking'
+ option module 'nf_conntrack_ftp'
+ option family 'any'
+ option proto 'tcp'
+ option port '21'
+
+config helper
+ option name 'RAS'
+ option description 'RAS proto tracking'
+ option module 'nf_conntrack_h323'
+ option family 'any'
+ option proto 'udp'
+ option port '1719'
+
+config helper
+ option name 'Q.931'
+ option description 'Q.931 proto tracking'
+ option module 'nf_conntrack_h323'
+ option family 'any'
+ option proto 'tcp'
+ option port '1720'
+
+config helper
+ option name 'irc'
+ option description 'IRC DCC connection tracking'
+ option module 'nf_conntrack_irc'
+ option family 'ipv4'
+ option proto 'tcp'
+ option port '6667'
+
+config helper
+ option name 'netbios-ns'
+ option description 'NetBIOS name service broadcast tracking'
+ option module 'nf_conntrack_netbios_ns'
+ option family 'ipv4'
+ option proto 'udp'
+ option port '137'
+
+config helper
+ option name 'pptp'
+ option description 'PPTP VPN connection tracking'
+ option module 'nf_conntrack_pptp'
+ option family 'ipv4'
+ option proto 'tcp'
+ option port '1723'
+
+config helper
+ option name 'sane'
+ option description 'SANE scanner connection tracking'
+ option module 'nf_conntrack_sane'
+ option family 'any'
+ option proto 'tcp'
+ option port '6566'
+
+config helper
+ option name 'sip'
+ option description 'SIP VoIP connection tracking'
+ option module 'nf_conntrack_sip'
+ option family 'any'
+ option proto 'udp'
+ option port '5060'
+
+config helper
+ option name 'snmp'
+ option description 'SNMP monitoring connection tracking'
+ option module 'nf_conntrack_snmp'
+ option family 'ipv4'
+ option proto 'udp'
+ option port '161'
+
+config helper
+ option name 'tftp'
+ option description 'TFTP connection tracking'
+ option module 'nf_conntrack_tftp'
+ option family 'any'
+ option proto 'udp'
+ option port '69'
diff --git a/helpers.h b/helpers.h
new file mode 100644
index 0000000..450428e
--- /dev/null
+++ b/helpers.h
@@ -0,0 +1,50 @@
+/*
+ * firewall3 - 3rd OpenWrt UCI firewall implementation
+ *
+ * Copyright (C) 2018 Jo-Philipp Wich <jo@mein.io>
+ *
+ * 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_HELPERS_H
+#define __FW3_HELPERS_H
+
+#include "options.h"
+#include "utils.h"
+#include "iptables.h"
+
+
+extern const struct fw3_option fw3_cthelper_opts[];
+
+void
+fw3_load_cthelpers(struct fw3_state *state, struct uci_package *p);
+
+struct fw3_cthelper *
+fw3_lookup_cthelper(struct fw3_state *state, const char *name);
+
+struct fw3_cthelper *
+fw3_lookup_cthelper_by_proto_port(struct fw3_state *state,
+ struct fw3_protocol *proto,
+ struct fw3_port *port);
+
+void
+fw3_print_cthelpers(struct fw3_ipt_handle *handle, struct fw3_state *state,
+ struct fw3_zone *zone);
+
+static inline void fw3_free_cthelper(struct fw3_cthelper *helper)
+{
+ list_del(&helper->list);
+ fw3_free_object(helper, fw3_cthelper_opts);
+}
+
+#endif
diff --git a/iptables.c b/iptables.c
index d848239..a48a8b6 100644
--- a/iptables.c
+++ b/iptables.c
@@ -1026,6 +1026,16 @@ fw3_ipt_rule_ipset(struct fw3_ipt_rule *r, struct fw3_setmatch *match)
}
void
+fw3_ipt_rule_helper(struct fw3_ipt_rule *r, struct fw3_cthelpermatch *match)
+{
+ if (!match || !match->set || !match->ptr)
+ return;
+
+ fw3_ipt_rule_addarg(r, false, "-m", "helper");
+ fw3_ipt_rule_addarg(r, match->invert, "--helper", match->ptr->name);
+}
+
+void
fw3_ipt_rule_time(struct fw3_ipt_rule *r, struct fw3_time *time)
{
int i;
diff --git a/iptables.h b/iptables.h
index 8a4ce8f..8ec7add 100644
--- a/iptables.h
+++ b/iptables.h
@@ -87,6 +87,8 @@ void fw3_ipt_rule_limit(struct fw3_ipt_rule *r, struct fw3_limit *limit);
void fw3_ipt_rule_ipset(struct fw3_ipt_rule *r, struct fw3_setmatch *match);
+void fw3_ipt_rule_helper(struct fw3_ipt_rule *r, struct fw3_cthelpermatch *match);
+
void fw3_ipt_rule_time(struct fw3_ipt_rule *r, struct fw3_time *time);
void fw3_ipt_rule_mark(struct fw3_ipt_rule *r, struct fw3_mark *mark);
diff --git a/main.c b/main.c
index c4b8228..1410fef 100644
--- a/main.c
+++ b/main.c
@@ -30,6 +30,7 @@
#include "includes.h"
#include "ubus.h"
#include "iptables.h"
+#include "helpers.h"
static enum fw3_family print_family = FW3_FAMILY_ANY;
@@ -101,6 +102,7 @@ build_state(bool runtime)
fw3_ubus_rules(&b);
fw3_load_defaults(state, p);
+ fw3_load_cthelpers(state, p);
fw3_load_ipsets(state, p, b.head);
fw3_load_zones(state, p);
fw3_load_rules(state, p, b.head);
@@ -138,6 +140,9 @@ free_state(struct fw3_state *state)
list_for_each_safe(cur, tmp, &state->includes)
fw3_free_include((struct fw3_include *)cur);
+ list_for_each_safe(cur, tmp, &state->cthelpers)
+ fw3_free_cthelper((struct fw3_cthelper *)cur);
+
uci_free_context(state->uci);
free(state);
diff --git a/options.c b/options.c
index 6d2a283..d990cad 100644
--- a/options.c
+++ b/options.c
@@ -75,6 +75,7 @@ const char *fw3_flag_names[__FW3_FLAG_MAX] = {
"REJECT",
"DROP",
"NOTRACK",
+ "HELPER",
"MARK",
"DNAT",
"SNAT",
@@ -897,6 +898,28 @@ fw3_parse_direction(void *ptr, const char *val, bool is_list)
return valid;
}
+bool
+fw3_parse_cthelper(void *ptr, const char *val, bool is_list)
+{
+ struct fw3_cthelpermatch m = { };
+
+ if (*val == '!')
+ {
+ m.invert = true;
+ while (isspace(*++val));
+ }
+
+ if (*val)
+ {
+ m.set = true;
+ strncpy(m.name, val, sizeof(m.name) - 1);
+ put_value(ptr, &m, sizeof(m), is_list);
+ return true;
+ }
+
+ return false;
+}
+
bool
fw3_parse_options(void *s, const struct fw3_option *opts,
diff --git a/options.h b/options.h
index 6edd174..84bafed 100644
--- a/options.h
+++ b/options.h
@@ -71,18 +71,19 @@ enum fw3_flag
FW3_FLAG_REJECT = 7,
FW3_FLAG_DROP = 8,
FW3_FLAG_NOTRACK = 9,
- FW3_FLAG_MARK = 10,
- FW3_FLAG_DNAT = 11,
- FW3_FLAG_SNAT = 12,
- FW3_FLAG_MASQUERADE = 13,
- FW3_FLAG_SRC_ACCEPT = 14,
- FW3_FLAG_SRC_REJECT = 15,
- FW3_FLAG_SRC_DROP = 16,
- FW3_FLAG_CUSTOM_CHAINS = 17,
- FW3_FLAG_SYN_FLOOD = 18,
- FW3_FLAG_MTU_FIX = 19,
- FW3_FLAG_DROP_INVALID = 20,
- FW3_FLAG_HOTPLUG = 21,
+ FW3_FLAG_HELPER = 10,
+ FW3_FLAG_MARK = 11,
+ FW3_FLAG_DNAT = 12,
+ FW3_FLAG_SNAT = 13,
+ FW3_FLAG_MASQUERADE = 14,
+ FW3_FLAG_SRC_ACCEPT = 15,
+ FW3_FLAG_SRC_REJECT = 16,
+ FW3_FLAG_SRC_DROP = 17,
+ FW3_FLAG_CUSTOM_CHAINS = 18,
+ FW3_FLAG_SYN_FLOOD = 19,
+ FW3_FLAG_MTU_FIX = 20,
+ FW3_FLAG_DROP_INVALID = 21,
+ FW3_FLAG_HOTPLUG = 22,
__FW3_FLAG_MAX
};
@@ -258,6 +259,16 @@ struct fw3_mark
uint32_t mask;
};
+struct fw3_cthelpermatch
+{
+ struct list_head list;
+
+ bool set;
+ bool invert;
+ char name[32];
+ struct fw3_cthelper *ptr;
+};
+
struct fw3_defaults
{
enum fw3_flag policy_input;
@@ -277,6 +288,7 @@ struct fw3_defaults
bool accept_source_route;
bool custom_chains;
+ bool auto_helper;
bool disable_ipv6;
@@ -310,10 +322,13 @@ struct fw3_zone
bool mtu_fix;
+ struct list_head cthelpers;
+
bool log;
struct fw3_limit log_limit;
bool custom_chains;
+ bool auto_helper;
uint32_t flags[2];
@@ -338,6 +353,7 @@ struct fw3_rule
struct fw3_device src;
struct fw3_device dest;
struct fw3_setmatch ipset;
+ struct fw3_cthelpermatch helper;
struct list_head proto;
@@ -357,6 +373,7 @@ struct fw3_rule
enum fw3_flag target;
struct fw3_mark set_mark;
struct fw3_mark set_xmark;
+ struct fw3_cthelpermatch set_helper;
const char *extra;
};
@@ -376,6 +393,7 @@ struct fw3_redirect
struct fw3_device src;
struct fw3_device dest;
struct fw3_setmatch ipset;
+ struct fw3_cthelpermatch helper;
struct list_head proto;
@@ -415,6 +433,7 @@ struct fw3_snat
struct fw3_device src;
struct fw3_setmatch ipset;
+ struct fw3_cthelpermatch helper;
const char *device;
struct list_head proto;
@@ -493,6 +512,19 @@ struct fw3_include
bool reload;
};
+struct fw3_cthelper
+{
+ struct list_head list;
+
+ bool enabled;
+ const char *name;
+ const char *module;
+ const char *description;
+ enum fw3_family family;
+ struct fw3_protocol proto;
+ struct fw3_port port;
+};
+
struct fw3_state
{
struct uci_context *uci;
@@ -504,6 +536,7 @@ struct fw3_state
struct list_head forwards;
struct list_head ipsets;
struct list_head includes;
+ struct list_head cthelpers;
bool disable_ipsets;
bool statefile;
@@ -559,6 +592,7 @@ bool fw3_parse_monthdays(void *ptr, const char *val, bool is_list);
bool fw3_parse_mark(void *ptr, const char *val, bool is_list);
bool fw3_parse_setmatch(void *ptr, const char *val, bool is_list);
bool fw3_parse_direction(void *ptr, const char *val, bool is_list);
+bool fw3_parse_cthelper(void *ptr, const char *val, bool is_list);
bool fw3_parse_options(void *s, const struct fw3_option *opts,
struct uci_section *section);
diff --git a/redirects.c b/redirects.c
index e651ddd..660cdd2 100644
--- a/redirects.c
+++ b/redirects.c
@@ -1,7 +1,7 @@
/*
* firewall3 - 3rd OpenWrt UCI firewall implementation
*
- * Copyright (C) 2013-2014 Jo-Philipp Wich <jo@mein.io>
+ * Copyright (C) 2013-2018 Jo-Philipp Wich <jo@mein.io>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -29,6 +29,7 @@ const struct fw3_option fw3_redirect_opts[] = {
FW3_OPT("dest", device, redirect, dest),
FW3_OPT("ipset", setmatch, redirect, ipset),
+ FW3_OPT("helper", cthelper, redirect, helper),
FW3_LIST("proto", protocol, redirect, proto),
@@ -92,6 +93,13 @@ check_families(struct uci_element *e, struct fw3_redirect *r)
return false;
}
+ if (r->helper.ptr && r->helper.ptr->family &&
+ r->helper.ptr->family != r->family)
+ {
+ warn_elem(e, "refers to CT helper not supporting family");
+ return false;
+ }
+
if (r->ip_src.family && r->ip_src.family != r->family)
{
warn_elem(e, "uses source ip with different family");
@@ -175,6 +183,48 @@ check_local(struct uci_element *e, struct fw3_redirect *redir,
return redir->local;
}
+static void
+select_helper(struct fw3_state *state, struct fw3_redirect *redir)
+{
+ struct fw3_protocol *proto;
+ struct fw3_cthelper *helper;
+ int n_matches = 0;
+
+ if (!state->defaults.auto_helper)
+ return;
+
+ if (!redir->_src || redir->target != FW3_FLAG_DNAT)
+ return;
+
+ if (!redir->port_redir.set || redir->port_redir.invert)
+ return;
+
+ if (redir->helper.set || redir->helper.ptr)
+ return;
+
+ if (list_empty(&redir->proto))
+ return;
+
+ list_for_each_entry(proto, &redir->proto, list)
+ {
+ helper = fw3_lookup_cthelper_by_proto_port(state, proto, &redir->port_redir);
+
+ if (helper)
+ n_matches++;
+ }
+
+ if (n_matches != 1)
+ return;
+
+ /* store pointer to auto-selected helper but set ".set" flag to false,
+ * to allow later code to decide between configured or auto-selected
+ * helpers */
+ redir->helper.set = false;
+ redir->helper.ptr = helper;
+
+ set(redir->_src->flags, FW3_FAMILY_V4, FW3_FLAG_HELPER);
+}
+
static bool
check_redirect(struct fw3_state *state, struct fw3_redirect *redir, struct uci_element *e)
{
@@ -215,6 +265,13 @@ check_redirect(struct fw3_state *state, struct fw3_redirect *redir, struct uci_e
redir->ipset.name);
return false;
}
+ else if (redir->helper.set &&
+ !(redir->helper.ptr = fw3_lookup_cthelper(state, redir->helper.name)))
+ {
+ warn_section("redirect", redir, e, "refers to unknown CT helper '%s'",
+ redir->helper.name);
+ return false;
+ }
if (!check_families(e, redir))
return false;
@@ -238,6 +295,8 @@ check_redirect(struct fw3_state *state, struct fw3_redirect *redir, struct uci_e
warn_section("redirect", redir, e, "must not have source '*' for DNAT target");
else if (!redir->_src)
warn_section("redirect", redir, e, "has no source specified");
+ else if (redir->helper.invert)
+ warn_section("redirect", redir, e, "must not use a negated helper match");
else
{
set(redir->_src->flags, FW3_FAMILY_V4, redir->target);
@@ -257,6 +316,9 @@ check_redirect(struct fw3_state *state, struct fw3_redirect *redir, struct uci_e
set(redir->_dest->flags, FW3_FAMILY_V4, FW3_FLAG_DNAT);
set(redir->_dest->flags, FW3_FAMILY_V4, FW3_FLAG_SNAT);
}
+
+ if (redir->helper.ptr)
+ set(redir->_src->flags, FW3_FAMILY_V4, FW3_FLAG_HELPER);
}
}
else
@@ -270,6 +332,8 @@ check_redirect(struct fw3_state *state, struct fw3_redirect *redir, struct uci_e
warn_section("redirect", redir, e, "has no src_dip option specified");
else if (!list_empty(&redir->mac_src))
warn_section("redirect", redir, e, "must not use 'src_mac' option for SNAT target");
+ else if (redir->helper.set)
+ warn_section("redirect", redir, e, "must not use 'helper' option for SNAT target");
else
{
set(redir->_dest->flags, FW3_FAMILY_V4, redir->target);
@@ -346,8 +410,12 @@ fw3_load_redirects(struct fw3_state *state, struct uci_package *p,
continue;
}
- if (!check_redirect(state, redir, NULL))
+ if (!check_redirect(state, redir, NULL)) {
fw3_free_redirect(redir);
+ continue;
+ }
+
+ select_helper(state, redir);
}
uci_foreach_element(&p->sections, e)
@@ -368,8 +436,12 @@ fw3_load_redirects(struct fw3_state *state, struct uci_package *p,
continue;
}
- if (!check_redirect(state, redir, e))
+ if (!check_redirect(state, redir, e)) {
fw3_free_redirect(redir);
+ continue;
+ }
+
+ select_helper(state, redir);
}
}
@@ -446,19 +518,19 @@ set_target_nat(struct fw3_ipt_rule *r, struct fw3_redirect *redir)
}
static void
-set_comment(struct fw3_ipt_rule *r, const char *name, int num, bool ref)
+set_comment(struct fw3_ipt_rule *r, const char *name, int num, const char *suffix)
{
if (name)
{
- if (ref)
- fw3_ipt_rule_comment(r, "%s (reflection)", name);
+ if (suffix)
+ fw3_ipt_rule_comment(r, "%s (%s)", name, suffix);
else
fw3_ipt_rule_comment(r, name);
}
else
{
- if (ref)
- fw3_ipt_rule_comment(r, "@redirect[%u] (reflection)", num);
+ if (suffix)
+ fw3_ipt_rule_comment(r, "@redirect[%u] (%s)", num, suffix);
else
fw3_ipt_rule_comment(r, "@redirect[%u]", num);
}
@@ -491,15 +563,46 @@ print_redirect(struct fw3_ipt_handle *h, struct fw3_state *state,
fw3_ipt_rule_sport_dport(r, spt, dpt);
fw3_ipt_rule_mac(r, mac);
fw3_ipt_rule_ipset(r, &redir->ipset);
+ fw3_ipt_rule_helper(r, &redir->helper);
fw3_ipt_rule_limit(r, &redir->limit);
fw3_ipt_rule_time(r, &redir->time);
fw3_ipt_rule_mark(r, &redir->mark);
set_target_nat(r, redir);
fw3_ipt_rule_extra(r, redir->extra);
- set_comment(r, redir->name, num, false);
+ set_comment(r, redir->name, num, NULL);
append_chain_nat(r, redir);
break;
+ case FW3_TABLE_RAW:
+ if (redir->target == FW3_FLAG_DNAT && redir->helper.ptr)
+ {
+ if (redir->helper.ptr->proto.protocol != proto->protocol)
+ {
+ info(" ! Skipping protocol %s since helper '%s' does not support it",
+ fw3_protoname(proto), redir->helper.ptr->name);
+ return;
+ }
+
+ if (!redir->helper.set)
+ info(" - Auto-selected conntrack helper '%s' based on proto/port",
+ redir->helper.ptr->name);
+
+ r = fw3_ipt_rule_create(h, proto, NULL, NULL, &redir->ip_src, &redir->ip_redir);
+ fw3_ipt_rule_sport_dport(r, &redir->port_src, &redir->port_redir);
+ fw3_ipt_rule_mac(r, mac);
+ fw3_ipt_rule_ipset(r, &redir->ipset);
+ fw3_ipt_rule_limit(r, &redir->limit);
+ fw3_ipt_rule_time(r, &redir->time);
+ fw3_ipt_rule_mark(r, &redir->mark);
+ fw3_ipt_rule_addarg(r, false, "-m", "conntrack");
+ fw3_ipt_rule_addarg(r, false, "--ctstate", "DNAT");
+ fw3_ipt_rule_target(r, "CT");
+ fw3_ipt_rule_addarg(r, false, "--helper", redir->helper.ptr->name);
+ set_comment(r, redir->name, num, "CT helper");
+ fw3_ipt_rule_append(r, "zone_%s_helper", redir->_src->name);
+ }
+ break;
+
default:
break;
}
@@ -520,7 +623,7 @@ print_reflection(struct fw3_ipt_handle *h, struct fw3_state *state,
fw3_ipt_rule_sport_dport(r, NULL, &redir->port_dest);
fw3_ipt_rule_limit(r, &redir->limit);
fw3_ipt_rule_time(r, &redir->time);
- set_comment(r, redir->name, num, true);
+ set_comment(r, redir->name, num, "reflection");
set_snat_dnat(r, FW3_FLAG_DNAT, &redir->ip_redir, &redir->port_redir);
fw3_ipt_rule_replace(r, "zone_%s_prerouting", redir->dest.name);
@@ -528,7 +631,7 @@ print_reflection(struct fw3_ipt_handle *h, struct fw3_state *state,
fw3_ipt_rule_sport_dport(r, NULL, &redir->port_redir);
fw3_ipt_rule_limit(r, &redir->limit);
fw3_ipt_rule_time(r, &redir->time);
- set_comment(r, redir->name, num, true);
+ set_comment(r, redir->name, num, "reflection");
set_snat_dnat(r, FW3_FLAG_SNAT, ra, NULL);
fw3_ipt_rule_replace(r, "zone_%s_postrouting", redir->dest.name);
break;
@@ -650,9 +753,16 @@ fw3_print_redirects(struct fw3_ipt_handle *handle, struct fw3_state *state)
if (handle->family == FW3_FAMILY_V6)
return;
- if (handle->table != FW3_TABLE_FILTER && handle->table != FW3_TABLE_NAT)
+ if (handle->table != FW3_TABLE_FILTER &&
+ handle->table != FW3_TABLE_NAT &&
+ handle->table != FW3_TABLE_RAW)
return;
list_for_each_entry(redir, &state->redirects, list)
+ {
+ if (handle->table == FW3_TABLE_RAW && !redir->helper.ptr)
+ continue;
+
expand_redirect(handle, state, redir, num++);
+ }
}
diff --git a/redirects.h b/redirects.h
index ac625f4..0d46bd2 100644
--- a/redirects.h
+++ b/redirects.h
@@ -22,6 +22,7 @@
#include "options.h"
#include "zones.h"
#include "ipsets.h"
+#include "helpers.h"
#include "ubus.h"
#include "iptables.h"
diff --git a/rules.c b/rules.c
index 9867082..ea66771 100644
--- a/rules.c
+++ b/rules.c
@@ -1,7 +1,7 @@
/*
* firewall3 - 3rd OpenWrt UCI firewall implementation
*
- * Copyright (C) 2013 Jo-Philipp Wich <jo@mein.io>
+ * Copyright (C) 2013-2018 Jo-Philipp Wich <jo@mein.io>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -32,6 +32,8 @@ const struct fw3_option fw3_rule_opts[] = {
FW3_OPT("direction", direction, rule, direction_out),
FW3_OPT("ipset", setmatch, rule, ipset),
+ FW3_OPT("helper", cthelper, rule, helper),
+ FW3_OPT("set_helper", cthelper, rule, helper),
FW3_LIST("proto", protocol, rule, proto),
@@ -130,10 +132,23 @@ check_rule(struct fw3_state *state, struct fw3_rule *r, struct uci_element *e)
warn_section("rule", r, e, "refers to unknown ipset '%s'", r->ipset.name);
return false;
}
+ else if (r->helper.set &&
+ !(r->helper.ptr = fw3_lookup_cthelper(state, r->helper.name)))
+ {
+ warn_section("rule", r, e, "refers to unknown CT helper '%s'", r->helper.name);
+ return false;
+ }
+ else if (r->set_helper.set &&
+ !(r->set_helper.ptr = fw3_lookup_cthelper(state, r->set_helper.name)))
+ {
+ warn_section("rule", r, e, "refers to unknown CT helper '%s'", r->set_helper.name);
+ return false;
+ }
- if (!r->_src && r->target == FW3_FLAG_NOTRACK)
+ if (!r->_src && (r->target == FW3_FLAG_NOTRACK || r->target == FW3_FLAG_HELPER))
{
- warn_section("rule", r, e, "is set to target NOTRACK but has no source assigned");
+ warn_section("rule", r, e, "is set to target %s but has no source assigned",
+ fw3_flag_names[r->target]);
return false;
}
@@ -157,6 +172,19 @@ check_rule(struct fw3_state *state, struct fw3_rule *r, struct uci_element *e)
return false;
}
+ if (!r->set_helper.set && r->target == FW3_FLAG_HELPER)
+ {
+ warn_section("rule", r, e, "is set to target HELPER but specifies "
+ "no 'set_helper' option");
+ return false;
+ }
+
+ if (r->set_helper.invert && r->target == FW3_FLAG_HELPER)
+ {
+ warn_section("rule", r, e, "must not have inverted 'set_helper' option");
+ return false;
+ }
+
if (!r->_src && !r->_dest && !r->src.any && !r->dest.any)
{
warn_section("rule", r, e, "has neither a source nor a destination zone assigned "
@@ -265,6 +293,10 @@ append_chain(struct fw3_ipt_rule *r, struct fw3_rule *rule)
{
snprintf(chain, sizeof(chain), "zone_%s_notrack", rule->src.name);
}
+ else if (rule->target == FW3_FLAG_HELPER)
+ {
+ snprintf(chain, sizeof(chain), "zone_%s_helper", rule->src.name);
+ }
else if (rule->target == FW3_FLAG_MARK && (rule->_src || rule->src.any))
{
snprintf(chain, sizeof(chain), "PREROUTING");
@@ -326,6 +358,11 @@ static void set_target(struct fw3_ipt_rule *r, struct fw3_rule *rule)
fw3_ipt_rule_addarg(r, false, "--notrack", NULL);
return;
+ case FW3_FLAG_HELPER:
+ fw3_ipt_rule_target(r, "CT");
+ fw3_ipt_rule_addarg(r, false, "--helper", rule->set_helper.ptr->name);
+ return;
+
case FW3_FLAG_ACCEPT:
case FW3_FLAG_DROP:
name = fw3_flag_names[rule->target];
@@ -373,9 +410,44 @@ print_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
return;
}
+ if (!fw3_is_family(sip, handle->family) ||
+ !fw3_is_family(dip, handle->family))
+ {
+ if ((sip && !sip->resolved) || (dip && !dip->resolved))
+ info(" ! Skipping due to different family of ip address");
+
+ return;
+ }
+
+ if (!fw3_is_family(sip, handle->family) ||
+ !fw3_is_family(dip, handle->family))
+ {
+ if ((sip && !sip->resolved) || (dip && !dip->resolved))
+ info(" ! Skipping due to different family of ip address");
+
+ return;
+ }
+
if (proto->protocol == 58 && handle->family == FW3_FAMILY_V4)
{
- info(" ! Skipping due to different family of protocol");
+ info(" ! Skipping protocol %s due to different family",
+ fw3_protoname(proto));
+ return;
+ }
+
+ if (rule->helper.ptr &&
+ rule->helper.ptr->proto.protocol != proto->protocol)
+ {
+ info(" ! Skipping protocol %s since helper '%s' does not support it",
+ fw3_protoname(proto), rule->helper.ptr->name);
+ return;
+ }
+
+ if (rule->set_helper.ptr &&
+ rule->set_helper.ptr->proto.protocol != proto->protocol)
+ {
+ info(" ! Skipping protocol %s since helper '%s' does not support it",
+ fw3_protoname(proto), rule->helper.ptr->name);
return;
}
@@ -385,6 +457,7 @@ print_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
fw3_ipt_rule_icmptype(r, icmptype);
fw3_ipt_rule_mac(r, mac);
fw3_ipt_rule_ipset(r, &rule->ipset);
+ fw3_ipt_rule_helper(r, &rule->helper);
fw3_ipt_rule_limit(r, &rule->limit);
fw3_ipt_rule_time(r, &rule->time);
fw3_ipt_rule_mark(r, &rule->mark);
@@ -417,6 +490,7 @@ expand_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
return;
if ((rule->target == FW3_FLAG_NOTRACK && handle->table != FW3_TABLE_RAW) ||
+ (rule->target == FW3_FLAG_HELPER && handle->table != FW3_TABLE_RAW) ||
(rule->target == FW3_FLAG_MARK && handle->table != FW3_TABLE_MANGLE) ||
(rule->target < FW3_FLAG_NOTRACK && handle->table != FW3_TABLE_FILTER))
return;
@@ -452,6 +526,18 @@ expand_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
set(rule->ipset.ptr->flags, handle->family, handle->family);
}
+ if (rule->helper.ptr && !fw3_is_family(rule->helper.ptr, handle->family))
+ {
+ info(" ! Skipping due to unsupported family of CT helper");
+ return;
+ }
+
+ if (rule->set_helper.ptr && !fw3_is_family(rule->set_helper.ptr, handle->family))
+ {
+ info(" ! Skipping due to unsupported family of CT helper");
+ return;
+ }
+
list_for_each_entry(proto, &rule->proto, list)
{
/* icmp / ipv6-icmp */
diff --git a/rules.h b/rules.h
index 7cf8524..be7c8bd 100644
--- a/rules.h
+++ b/rules.h
@@ -22,6 +22,7 @@
#include "options.h"
#include "zones.h"
#include "ipsets.h"
+#include "helpers.h"
#include "utils.h"
#include "iptables.h"
diff --git a/utils.c b/utils.c
index 024f95e..4f892a7 100644
--- a/utils.c
+++ b/utils.c
@@ -912,3 +912,24 @@ bool fw3_attr_parse_name_type(struct blob_attr *entry, const char **name, const
return *type != NULL ? true : false;
}
+
+const char *
+fw3_protoname(void *proto)
+{
+ static char buf[sizeof("4294967295")];
+ struct fw3_protocol *p = proto;
+ struct protoent *pe;
+
+ if (!p)
+ return "?";
+
+ pe = getprotobynumber(p->protocol);
+
+ if (!pe)
+ {
+ snprintf(buf, sizeof(buf), "%u", p->protocol);
+ return buf;
+ }
+
+ return pe->p_name;
+}
diff --git a/utils.h b/utils.h
index 9a716ae..c8ab0e5 100644
--- a/utils.h
+++ b/utils.h
@@ -30,6 +30,7 @@
#include <sys/file.h>
#include <sys/types.h>
#include <ifaddrs.h>
+#include <netdb.h>
#include <libubox/list.h>
#include <libubox/blob.h>
@@ -38,6 +39,7 @@
#define FW3_STATEFILE "/var/run/fw3.state"
#define FW3_LOCKFILE "/var/run/fw3.lock"
+#define FW3_HELPERCONF "/usr/share/fw3/helpers.conf"
#define FW3_HOTPLUG "/sbin/hotplug-call"
extern bool fw3_pr_debug;
@@ -116,4 +118,6 @@ void fw3_flush_conntrack(void *zone);
bool fw3_attr_parse_name_type(struct blob_attr *entry, const char **name, const char **type);
+const char * fw3_protoname(void *proto);
+
#endif
diff --git a/zones.c b/zones.c
index 514d203..7638443 100644
--- a/zones.c
+++ b/zones.c
@@ -18,6 +18,7 @@
#include "zones.h"
#include "ubus.h"
+#include "helpers.h"
#define C(f, tbl, tgt, fmt) \
@@ -39,6 +40,7 @@ static const struct fw3_chain_spec zone_chains[] = {
C(V4, NAT, SNAT, "zone_%s_postrouting"),
C(V4, NAT, DNAT, "zone_%s_prerouting"),
+ C(ANY, RAW, HELPER, "zone_%s_helper"),
C(ANY, RAW, NOTRACK, "zone_%s_notrack"),
C(ANY, FILTER, CUSTOM_CHAINS, "input_%s_rule"),
@@ -80,6 +82,9 @@ const struct fw3_option fw3_zone_opts[] = {
FW3_OPT("log", bool, zone, log),
FW3_OPT("log_limit", limit, zone, log_limit),
+ FW3_OPT("auto_helper", bool, zone, auto_helper),
+ FW3_LIST("helper", cthelper, zone, cthelpers),
+
FW3_OPT("__flags_v4", int, zone, flags[0]),
FW3_OPT("__flags_v6", int, zone, flags[1]),
@@ -145,6 +150,46 @@ resolve_networks(struct uci_element *e, struct fw3_zone *zone)
}
}
+static void
+resolve_cthelpers(struct fw3_state *s, struct uci_element *e, struct fw3_zone *zone)
+{
+ struct fw3_cthelpermatch *match;
+
+ if (list_empty(&zone->cthelpers))
+ {
+ if (!zone->masq && zone->auto_helper)
+ {
+ fw3_setbit(zone->flags[0], FW3_FLAG_HELPER);
+ fw3_setbit(zone->flags[1], FW3_FLAG_HELPER);
+ }
+
+ return;
+ }
+
+ list_for_each_entry(match, &zone->cthelpers, list)
+ {
+ if (match->invert)
+ {
+ warn_elem(e, "must not use a negated helper match");
+ continue;
+ }
+
+ match->ptr = fw3_lookup_cthelper(s, match->name);
+
+ if (!match->ptr)
+ {
+ warn_elem(e, "refers to not existing helper '%s'", match->name);
+ continue;
+ }
+
+ if (fw3_is_family(match->ptr, FW3_FAMILY_V4))
+ fw3_setbit(zone->flags[0], FW3_FLAG_HELPER);
+
+ if (fw3_is_family(match->ptr, FW3_FAMILY_V6))
+ fw3_setbit(zone->flags[1], FW3_FLAG_HELPER);
+ }
+}
+
struct fw3_zone *
fw3_alloc_zone(void)
{
@@ -159,10 +204,12 @@ fw3_alloc_zone(void)
INIT_LIST_HEAD(&zone->subnets);
INIT_LIST_HEAD(&zone->masq_src);
INIT_LIST_HEAD(&zone->masq_dest);
+ INIT_LIST_HEAD(&zone->cthelpers);
INIT_LIST_HEAD(&zone->old_addrs);
zone->enabled = true;
+ zone->auto_helper = true;
zone->custom_chains = true;
zone->log_limit.rate = 10;
@@ -206,6 +253,9 @@ fw3_load_zones(struct fw3_state *state, struct uci_package *p)
if (!defs->custom_chains && zone->custom_chains)
zone->custom_chains = false;
+ if (!defs->auto_helper && zone->auto_helper)
+ zone->auto_helper = false;
+
if (!zone->name || !*zone->name)
{
warn_elem(e, "has no name - ignoring");
@@ -258,6 +308,8 @@ fw3_load_zones(struct fw3_state *state, struct uci_package *p)
fw3_setbit(zone->flags[0], FW3_FLAG_DNAT);
}
+ resolve_cthelpers(state, e, zone);
+
fw3_setbit(zone->flags[0], fw3_to_src_target(zone->policy_input));
fw3_setbit(zone->flags[0], zone->policy_forward);
fw3_setbit(zone->flags[0], zone->policy_output);
@@ -293,8 +345,6 @@ print_zone_chain(struct fw3_ipt_handle *handle, struct fw3_state *state,
if (!fw3_is_family(zone, handle->family))
return;
- info(" * Zone '%s'", zone->name);
-
set(zone->flags, handle->family, handle->table);
if (zone->custom_chains)
@@ -471,9 +521,19 @@ print_interface_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
}
else if (handle->table == FW3_TABLE_RAW)
{
+ if (has(zone->flags, handle->family, FW3_FLAG_HELPER))
+ {
+ r = fw3_ipt_rule_create(handle, NULL, dev, NULL, sub, NULL);
+ fw3_ipt_rule_comment(r, "%s CT helper assignment", zone->name);
+ fw3_ipt_rule_target(r, "zone_%s_helper", zone->name);
+ fw3_ipt_rule_extra(r, zone->extra_src);
+ fw3_ipt_rule_replace(r, "PREROUTING");
+ }
+
if (has(zone->flags, handle->family, FW3_FLAG_NOTRACK))
{
r = fw3_ipt_rule_create(handle, NULL, dev, NULL, sub, NULL);
+ fw3_ipt_rule_comment(r, "%s CT bypass", zone->name);
fw3_ipt_rule_target(r, "zone_%s_notrack", zone->name);
fw3_ipt_rule_extra(r, zone->extra_src);
fw3_ipt_rule_replace(r, "PREROUTING");
@@ -534,6 +594,8 @@ print_zone_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
if (!fw3_is_family(zone, handle->family))
return;
+ info(" * Zone '%s'", zone->name);
+
switch (handle->table)
{
case FW3_TABLE_FILTER:
@@ -654,6 +716,9 @@ print_zone_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
break;
case FW3_TABLE_RAW:
+ fw3_print_cthelpers(handle, state, zone);
+ break;
+
case FW3_TABLE_MANGLE:
break;
}