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 81928a6..0ba72ff 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 85a3750..7b2d9e6 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 d848239..a48a8b6 100644 (file)
@@ -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;
index 8a4ce8f..8ec7add 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 c4b8228..1410fef 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 6d2a283..d990cad 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 6edd174..84bafed 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 e651ddd..660cdd2 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 ac625f4..0d46bd2 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 9867082..ea66771 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 7cf8524..be7c8bd 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 024f95e..4f892a7 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 9a716ae..c8ab0e5 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 514d203..7638443 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;
        }