helpers: implement explicit CT helper assignment support
authorJo-Philipp Wich <jo@mein.io>
Mon, 19 Feb 2018 17:16:26 +0000 (18:16 +0100)
committerJo-Philipp Wich <jo@mein.io>
Tue, 20 Feb 2018 16:01:27 +0000 (17:01 +0100)
Implement support for explicit per-zone conntrack helper assignment in
the raw table in order to compensate for the now disabled automatic
helper assignment in recent Linux kernels.

This commit adds, along with the required infrastructure, a new per-
zone uci option "helper" which can be used to tie one or more CT helpers
to a given zone.

For example the following configuration:

    config zone
      option name lan
      option network lan
      list helper ftp
      list helper sip

... will assign the FTP and SIP conntrack helpers as specified in
/usr/share/fw3/helpers.conf to traffic originating from the LAN zone.

Additionally, a new boolean option "auto_helper" has been defined for
both "config defaults" and "config zone" sections, with the former
option overruling the latter.

When the default true "option auto_helper" is set, all available helpers
are automatically attached to each non-masq zone (i.e. "lan" by default).

When one or more "list helper" options are specified, the zone has
masquerading enabled or "auto_helper" is set to false, then the automatic
helper attachment is disabled for the corresponding zone.

Furthermore, this commit introduces support for a new 'HELPER' target in
"config rule" sections, along with "option helper" to match helper traffic
and "option set_helper" to assign CT helpers to a stream.

Finally, "config redirect" sections support "option helper" too now,
which causes fw3 to emit helper setting rules for forwarded DNAT traffic.

When "option helper" is not defined for a redirect and when the global
option "auto_helper" is not disabled, fw3 will pick a suitable helper
based on the destination protocol and port and assign it to DNATed traffic.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
17 files changed:
CMakeLists.txt
defaults.c
helpers.c [new file with mode: 0644]
helpers.conf [new file with mode: 0644]
helpers.h [new file with mode: 0644]
iptables.c
iptables.h
main.c
options.c
options.h
redirects.c
redirects.h
rules.c
rules.h
utils.c
utils.h
zones.c

index 81928a622e8b63cb91d7c3f7cfc31f53b00e166c..0ba72ff19ee108af0e4a868d704aa8dd699ca940 100644 (file)
@@ -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)
index 85a3750a52c6e6bcfc9ee12b424d44ac946f8041..7b2d9e6f357f2aecdbbc12904882c7651d8b9173 100644 (file)
@@ -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 (file)
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 (file)
index 0000000..55aa19d
--- /dev/null
@@ -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 (file)
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
index d8482399dbaa85f611f6720aaae1d29727b0bf17..a48a8b6ec1c465e57d92931f2aa39aa378373834 100644 (file)
@@ -1025,6 +1025,16 @@ fw3_ipt_rule_ipset(struct fw3_ipt_rule *r, struct fw3_setmatch *match)
        fw3_ipt_rule_addarg(r, false, buf, NULL);
 }
 
+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)
 {
index 8a4ce8f64780b87476fce1a45127fe56f1e9ae09..8ec7add695cbdfc012cc9b3d4e8cd1ba2e42a653 100644 (file)
@@ -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 c4b82282b6c416112c51f3fe1f2efb9e37e73dcc..1410fef31f3c477363b01f7bf9537b4e80e089f8 100644 (file)
--- 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);
index 6d2a283fdd8a95f03cdfb3295cb54db8720bed9b..d990cada687277c12a92efb26817785b21001975 100644 (file)
--- 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,
index 6edd174819b59aa8ef82b1907892b823a7e4dc97..84bafed59d374a7722adfd7e8d46b6a35f039f75 100644 (file)
--- 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);
index e651dddef381375a912d4d3882e49b3ef0cdcf21..660cdd2f3e94875b80499a8094cdb90558201ce6 100644 (file)
@@ -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++);
+       }
 }
index ac625f47ec6d35035f95b4b643c6acd7305bad81..0d46bd218a2944921fff062fbb1be95611f7ca5c 100644 (file)
@@ -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 98670821dc336fe4c8bac7ada5add517ce8ef7e1..ea66771457511b37b7bc7af3fb8248b16f0906d4 100644 (file)
--- 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 7cf852444f0789c4a018dcb4ceac75bb47c84ef5..be7c8bdd1044da92f0829880da20cfa1880d04bd 100644 (file)
--- 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 024f95e16d5df1e1d2cc2adb399f42e589c91000..4f892a7aa465bae393f7266a9773971d2bb5703c 100644 (file)
--- 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 9a716aec1a9dd8dc03fff00aeaf4cb9d304a1c5c..c8ab0e5b0b91d82884880a6f8e528647279a08a1 100644 (file)
--- 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 514d20310dfb93312e6cfb2d0c0355633a335f16..7638443c5b320b65125fdb6e9b1688bc4f6f06eb 100644 (file)
--- 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;
        }