diff options
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | defaults.c | 2 | ||||
-rw-r--r-- | helpers.c | 277 | ||||
-rw-r--r-- | helpers.conf | 87 | ||||
-rw-r--r-- | helpers.h | 50 | ||||
-rw-r--r-- | iptables.c | 10 | ||||
-rw-r--r-- | iptables.h | 2 | ||||
-rw-r--r-- | main.c | 5 | ||||
-rw-r--r-- | options.c | 23 | ||||
-rw-r--r-- | options.h | 58 | ||||
-rw-r--r-- | redirects.c | 134 | ||||
-rw-r--r-- | redirects.h | 1 | ||||
-rw-r--r-- | rules.c | 94 | ||||
-rw-r--r-- | rules.h | 1 | ||||
-rw-r--r-- | utils.c | 21 | ||||
-rw-r--r-- | utils.h | 4 | ||||
-rw-r--r-- | zones.c | 69 |
17 files changed, 809 insertions, 31 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 81928a6..0ba72ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ ENDIF() FIND_PATH(uci_include_dir uci.h) INCLUDE_DIRECTORIES(${uci_include_dir}) -ADD_EXECUTABLE(firewall3 main.c options.c defaults.c zones.c forwards.c rules.c redirects.c snats.c utils.c ubus.c ipsets.c includes.c iptables.c) +ADD_EXECUTABLE(firewall3 main.c options.c defaults.c zones.c forwards.c rules.c redirects.c snats.c utils.c ubus.c ipsets.c includes.c iptables.c helpers.c) TARGET_LINK_LIBRARIES(firewall3 uci ubox ubus xtables m dl ${iptc_libs} ${ext_libs}) SET(CMAKE_INSTALL_PREFIX /usr) @@ -54,6 +54,7 @@ const struct fw3_option fw3_flag_opts[] = { FW3_OPT("accept_redirects", bool, defaults, accept_redirects), FW3_OPT("accept_source_route", bool, defaults, accept_source_route), + FW3_OPT("auto_helper", bool, defaults, auto_helper), FW3_OPT("custom_chains", bool, defaults, custom_chains), FW3_OPT("disable_ipv6", bool, defaults, disable_ipv6), @@ -93,6 +94,7 @@ fw3_load_defaults(struct fw3_state *state, struct uci_package *p) defs->tcp_syncookies = true; defs->tcp_window_scaling = true; defs->custom_chains = true; + defs->auto_helper = true; uci_foreach_element(&p->sections, e) { diff --git a/helpers.c b/helpers.c new file mode 100644 index 0000000..8cad0b3 --- /dev/null +++ b/helpers.c @@ -0,0 +1,277 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2018 Jo-Philipp Wich <jo@mein.io> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "helpers.h" + + +const struct fw3_option fw3_cthelper_opts[] = { + FW3_OPT("enabled", bool, cthelper, enabled), + FW3_OPT("name", string, cthelper, name), + FW3_OPT("module", string, cthelper, module), + FW3_OPT("description", string, cthelper, description), + FW3_OPT("family", family, cthelper, family), + FW3_OPT("proto", protocol, cthelper, proto), + FW3_OPT("port", port, cthelper, port), + + { } +}; + + +static bool +test_module(struct fw3_cthelper *helper) +{ + struct stat s; + char path[sizeof("/sys/module/nf_conntrack_xxxxxxxxxxxxxxxx")]; + + snprintf(path, sizeof(path), "/sys/module/%s", helper->module); + + if (stat(path, &s) || !S_ISDIR(s.st_mode)) + return false; + + return true; +} + +static bool +check_cthelper(struct fw3_state *state, struct fw3_cthelper *helper, struct uci_element *e) +{ + if (!helper->name || !*helper->name) + { + warn_section("helper", helper, e, "must have a name assigned"); + } + else if (!helper->module || !*helper->module) + { + warn_section("helper", helper, e, "must have a module assigned"); + } + else if (!helper->proto.protocol || helper->proto.any || helper->proto.invert) + { + warn_section("helper", helper, e, "must specify a protocol"); + } + else if (helper->port.set && helper->port.invert) + { + warn_section("helper", helper, e, "must not specify negated ports"); + } + else + { + return true; + } + + return false; +} + +static struct fw3_cthelper * +fw3_alloc_cthelper(struct fw3_state *state) +{ + struct fw3_cthelper *helper; + + helper = calloc(1, sizeof(*helper)); + if (!helper) + return NULL; + + helper->enabled = true; + helper->family = FW3_FAMILY_ANY; + + list_add_tail(&helper->list, &state->cthelpers); + + return helper; +} + +static void +load_cthelpers(struct fw3_state *state, struct uci_package *p) +{ + struct fw3_cthelper *helper; + struct uci_section *s; + struct uci_element *e; + + uci_foreach_element(&p->sections, e) + { + s = uci_to_section(e); + + if (strcmp(s->type, "helper")) + continue; + + helper = fw3_alloc_cthelper(state); + + if (!helper) + continue; + + if (!fw3_parse_options(helper, fw3_cthelper_opts, s)) + warn_elem(e, "has invalid options"); + + if (!check_cthelper(state, helper, e)) + fw3_free_cthelper(helper); + } +} + +void +fw3_load_cthelpers(struct fw3_state *state, struct uci_package *p) +{ + struct uci_package *hp = NULL; + FILE *fp; + + INIT_LIST_HEAD(&state->cthelpers); + + fp = fopen(FW3_HELPERCONF, "r"); + + if (fp) { + uci_import(state->uci, fp, "fw3_ct_helpers", &hp, true); + fclose(fp); + + if (hp) + load_cthelpers(state, hp); + } + + load_cthelpers(state, p); +} + +struct fw3_cthelper * +fw3_lookup_cthelper(struct fw3_state *state, const char *name) +{ + struct fw3_cthelper *h; + + if (list_empty(&state->cthelpers)) + return NULL; + + list_for_each_entry(h, &state->cthelpers, list) + { + if (strcasecmp(h->name, name)) + continue; + + return h; + } + + return NULL; +} + +struct fw3_cthelper * +fw3_lookup_cthelper_by_proto_port(struct fw3_state *state, + struct fw3_protocol *proto, + struct fw3_port *port) +{ + struct fw3_cthelper *h; + + if (list_empty(&state->cthelpers)) + return NULL; + + if (!proto || !proto->protocol || proto->any || proto->invert) + return NULL; + + if (port && port->invert) + return NULL; + + list_for_each_entry(h, &state->cthelpers, list) + { + if (!h->enabled) + continue; + + if (h->proto.protocol != proto->protocol) + continue; + + if (h->port.set && (!port || !port->set)) + continue; + + if (!h->port.set && (!port || !port->set)) + return h; + + if (h->port.set && port && port->set && + h->port.port_min <= port->port_min && + h->port.port_max >= port->port_max) + return h; + } + + return NULL; +} + +static void +print_helper_rule(struct fw3_ipt_handle *handle, struct fw3_cthelper *helper, + struct fw3_zone *zone) +{ + struct fw3_ipt_rule *r; + + r = fw3_ipt_rule_create(handle, &helper->proto, NULL, NULL, NULL, NULL); + + if (helper->description && *helper->description) + fw3_ipt_rule_comment(r, helper->description); + else + fw3_ipt_rule_comment(r, helper->name); + + fw3_ipt_rule_sport_dport(r, NULL, &helper->port); + fw3_ipt_rule_target(r, "CT"); + fw3_ipt_rule_addarg(r, false, "--helper", helper->name); + fw3_ipt_rule_replace(r, "zone_%s_helper", zone->name); +} + +void +fw3_print_cthelpers(struct fw3_ipt_handle *handle, struct fw3_state *state, + struct fw3_zone *zone) +{ + struct fw3_cthelper *helper; + struct fw3_cthelpermatch *match; + + if (handle->table != FW3_TABLE_RAW) + return; + + if (!fw3_is_family(zone, handle->family)) + return; + + if (list_empty(&zone->cthelpers)) + { + if (zone->masq || !zone->auto_helper) + return; + + if (list_empty(&state->cthelpers)) + return; + + info(" - Using automatic conntrack helper attachment"); + + list_for_each_entry(helper, &state->cthelpers, list) + { + if (!helper || !helper->enabled) + continue; + + if (!fw3_is_family(helper, handle->family)) + continue; + + if (!test_module(helper)) + continue; + + print_helper_rule(handle, helper, zone); + } + } + else + { + list_for_each_entry(match, &zone->cthelpers, list) + { + helper = match->ptr; + + if (!helper || !helper->enabled) + continue; + + if (!fw3_is_family(helper, handle->family)) + continue; + + if (!test_module(helper)) + { + info(" ! Conntrack module '%s' for helper '%s' is not loaded", + helper->module, helper->name); + continue; + } + + print_helper_rule(handle, helper, zone); + } + } +} diff --git a/helpers.conf b/helpers.conf new file mode 100644 index 0000000..55aa19d --- /dev/null +++ b/helpers.conf @@ -0,0 +1,87 @@ +config helper + option name 'amanda' + option description 'Amanda backup and archiving proto' + option module 'nf_conntrack_amanda' + option family 'any' + option proto 'udp' + option port '10080' + +config helper + option name 'ftp' + option description 'FTP passive connection tracking' + option module 'nf_conntrack_ftp' + option family 'any' + option proto 'tcp' + option port '21' + +config helper + option name 'RAS' + option description 'RAS proto tracking' + option module 'nf_conntrack_h323' + option family 'any' + option proto 'udp' + option port '1719' + +config helper + option name 'Q.931' + option description 'Q.931 proto tracking' + option module 'nf_conntrack_h323' + option family 'any' + option proto 'tcp' + option port '1720' + +config helper + option name 'irc' + option description 'IRC DCC connection tracking' + option module 'nf_conntrack_irc' + option family 'ipv4' + option proto 'tcp' + option port '6667' + +config helper + option name 'netbios-ns' + option description 'NetBIOS name service broadcast tracking' + option module 'nf_conntrack_netbios_ns' + option family 'ipv4' + option proto 'udp' + option port '137' + +config helper + option name 'pptp' + option description 'PPTP VPN connection tracking' + option module 'nf_conntrack_pptp' + option family 'ipv4' + option proto 'tcp' + option port '1723' + +config helper + option name 'sane' + option description 'SANE scanner connection tracking' + option module 'nf_conntrack_sane' + option family 'any' + option proto 'tcp' + option port '6566' + +config helper + option name 'sip' + option description 'SIP VoIP connection tracking' + option module 'nf_conntrack_sip' + option family 'any' + option proto 'udp' + option port '5060' + +config helper + option name 'snmp' + option description 'SNMP monitoring connection tracking' + option module 'nf_conntrack_snmp' + option family 'ipv4' + option proto 'udp' + option port '161' + +config helper + option name 'tftp' + option description 'TFTP connection tracking' + option module 'nf_conntrack_tftp' + option family 'any' + option proto 'udp' + option port '69' diff --git a/helpers.h b/helpers.h new file mode 100644 index 0000000..450428e --- /dev/null +++ b/helpers.h @@ -0,0 +1,50 @@ +/* + * firewall3 - 3rd OpenWrt UCI firewall implementation + * + * Copyright (C) 2018 Jo-Philipp Wich <jo@mein.io> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __FW3_HELPERS_H +#define __FW3_HELPERS_H + +#include "options.h" +#include "utils.h" +#include "iptables.h" + + +extern const struct fw3_option fw3_cthelper_opts[]; + +void +fw3_load_cthelpers(struct fw3_state *state, struct uci_package *p); + +struct fw3_cthelper * +fw3_lookup_cthelper(struct fw3_state *state, const char *name); + +struct fw3_cthelper * +fw3_lookup_cthelper_by_proto_port(struct fw3_state *state, + struct fw3_protocol *proto, + struct fw3_port *port); + +void +fw3_print_cthelpers(struct fw3_ipt_handle *handle, struct fw3_state *state, + struct fw3_zone *zone); + +static inline void fw3_free_cthelper(struct fw3_cthelper *helper) +{ + list_del(&helper->list); + fw3_free_object(helper, fw3_cthelper_opts); +} + +#endif @@ -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; @@ -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); @@ -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); @@ -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, @@ -71,18 +71,19 @@ enum fw3_flag FW3_FLAG_REJECT = 7, FW3_FLAG_DROP = 8, FW3_FLAG_NOTRACK = 9, - FW3_FLAG_MARK = 10, - FW3_FLAG_DNAT = 11, - FW3_FLAG_SNAT = 12, - FW3_FLAG_MASQUERADE = 13, - FW3_FLAG_SRC_ACCEPT = 14, - FW3_FLAG_SRC_REJECT = 15, - FW3_FLAG_SRC_DROP = 16, - FW3_FLAG_CUSTOM_CHAINS = 17, - FW3_FLAG_SYN_FLOOD = 18, - FW3_FLAG_MTU_FIX = 19, - FW3_FLAG_DROP_INVALID = 20, - FW3_FLAG_HOTPLUG = 21, + FW3_FLAG_HELPER = 10, + FW3_FLAG_MARK = 11, + FW3_FLAG_DNAT = 12, + FW3_FLAG_SNAT = 13, + FW3_FLAG_MASQUERADE = 14, + FW3_FLAG_SRC_ACCEPT = 15, + FW3_FLAG_SRC_REJECT = 16, + FW3_FLAG_SRC_DROP = 17, + FW3_FLAG_CUSTOM_CHAINS = 18, + FW3_FLAG_SYN_FLOOD = 19, + FW3_FLAG_MTU_FIX = 20, + FW3_FLAG_DROP_INVALID = 21, + FW3_FLAG_HOTPLUG = 22, __FW3_FLAG_MAX }; @@ -258,6 +259,16 @@ struct fw3_mark uint32_t mask; }; +struct fw3_cthelpermatch +{ + struct list_head list; + + bool set; + bool invert; + char name[32]; + struct fw3_cthelper *ptr; +}; + struct fw3_defaults { enum fw3_flag policy_input; @@ -277,6 +288,7 @@ struct fw3_defaults bool accept_source_route; bool custom_chains; + bool auto_helper; bool disable_ipv6; @@ -310,10 +322,13 @@ struct fw3_zone bool mtu_fix; + struct list_head cthelpers; + bool log; struct fw3_limit log_limit; bool custom_chains; + bool auto_helper; uint32_t flags[2]; @@ -338,6 +353,7 @@ struct fw3_rule struct fw3_device src; struct fw3_device dest; struct fw3_setmatch ipset; + struct fw3_cthelpermatch helper; struct list_head proto; @@ -357,6 +373,7 @@ struct fw3_rule enum fw3_flag target; struct fw3_mark set_mark; struct fw3_mark set_xmark; + struct fw3_cthelpermatch set_helper; const char *extra; }; @@ -376,6 +393,7 @@ struct fw3_redirect struct fw3_device src; struct fw3_device dest; struct fw3_setmatch ipset; + struct fw3_cthelpermatch helper; struct list_head proto; @@ -415,6 +433,7 @@ struct fw3_snat struct fw3_device src; struct fw3_setmatch ipset; + struct fw3_cthelpermatch helper; const char *device; struct list_head proto; @@ -493,6 +512,19 @@ struct fw3_include bool reload; }; +struct fw3_cthelper +{ + struct list_head list; + + bool enabled; + const char *name; + const char *module; + const char *description; + enum fw3_family family; + struct fw3_protocol proto; + struct fw3_port port; +}; + struct fw3_state { struct uci_context *uci; @@ -504,6 +536,7 @@ struct fw3_state struct list_head forwards; struct list_head ipsets; struct list_head includes; + struct list_head cthelpers; bool disable_ipsets; bool statefile; @@ -559,6 +592,7 @@ bool fw3_parse_monthdays(void *ptr, const char *val, bool is_list); bool fw3_parse_mark(void *ptr, const char *val, bool is_list); bool fw3_parse_setmatch(void *ptr, const char *val, bool is_list); bool fw3_parse_direction(void *ptr, const char *val, bool is_list); +bool fw3_parse_cthelper(void *ptr, const char *val, bool is_list); bool fw3_parse_options(void *s, const struct fw3_option *opts, struct uci_section *section); diff --git a/redirects.c b/redirects.c index e651ddd..660cdd2 100644 --- a/redirects.c +++ b/redirects.c @@ -1,7 +1,7 @@ /* * firewall3 - 3rd OpenWrt UCI firewall implementation * - * Copyright (C) 2013-2014 Jo-Philipp Wich <jo@mein.io> + * Copyright (C) 2013-2018 Jo-Philipp Wich <jo@mein.io> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -29,6 +29,7 @@ const struct fw3_option fw3_redirect_opts[] = { FW3_OPT("dest", device, redirect, dest), FW3_OPT("ipset", setmatch, redirect, ipset), + FW3_OPT("helper", cthelper, redirect, helper), FW3_LIST("proto", protocol, redirect, proto), @@ -92,6 +93,13 @@ check_families(struct uci_element *e, struct fw3_redirect *r) return false; } + if (r->helper.ptr && r->helper.ptr->family && + r->helper.ptr->family != r->family) + { + warn_elem(e, "refers to CT helper not supporting family"); + return false; + } + if (r->ip_src.family && r->ip_src.family != r->family) { warn_elem(e, "uses source ip with different family"); @@ -175,6 +183,48 @@ check_local(struct uci_element *e, struct fw3_redirect *redir, return redir->local; } +static void +select_helper(struct fw3_state *state, struct fw3_redirect *redir) +{ + struct fw3_protocol *proto; + struct fw3_cthelper *helper; + int n_matches = 0; + + if (!state->defaults.auto_helper) + return; + + if (!redir->_src || redir->target != FW3_FLAG_DNAT) + return; + + if (!redir->port_redir.set || redir->port_redir.invert) + return; + + if (redir->helper.set || redir->helper.ptr) + return; + + if (list_empty(&redir->proto)) + return; + + list_for_each_entry(proto, &redir->proto, list) + { + helper = fw3_lookup_cthelper_by_proto_port(state, proto, &redir->port_redir); + + if (helper) + n_matches++; + } + + if (n_matches != 1) + return; + + /* store pointer to auto-selected helper but set ".set" flag to false, + * to allow later code to decide between configured or auto-selected + * helpers */ + redir->helper.set = false; + redir->helper.ptr = helper; + + set(redir->_src->flags, FW3_FAMILY_V4, FW3_FLAG_HELPER); +} + static bool check_redirect(struct fw3_state *state, struct fw3_redirect *redir, struct uci_element *e) { @@ -215,6 +265,13 @@ check_redirect(struct fw3_state *state, struct fw3_redirect *redir, struct uci_e redir->ipset.name); return false; } + else if (redir->helper.set && + !(redir->helper.ptr = fw3_lookup_cthelper(state, redir->helper.name))) + { + warn_section("redirect", redir, e, "refers to unknown CT helper '%s'", + redir->helper.name); + return false; + } if (!check_families(e, redir)) return false; @@ -238,6 +295,8 @@ check_redirect(struct fw3_state *state, struct fw3_redirect *redir, struct uci_e warn_section("redirect", redir, e, "must not have source '*' for DNAT target"); else if (!redir->_src) warn_section("redirect", redir, e, "has no source specified"); + else if (redir->helper.invert) + warn_section("redirect", redir, e, "must not use a negated helper match"); else { set(redir->_src->flags, FW3_FAMILY_V4, redir->target); @@ -257,6 +316,9 @@ check_redirect(struct fw3_state *state, struct fw3_redirect *redir, struct uci_e set(redir->_dest->flags, FW3_FAMILY_V4, FW3_FLAG_DNAT); set(redir->_dest->flags, FW3_FAMILY_V4, FW3_FLAG_SNAT); } + + if (redir->helper.ptr) + set(redir->_src->flags, FW3_FAMILY_V4, FW3_FLAG_HELPER); } } else @@ -270,6 +332,8 @@ check_redirect(struct fw3_state *state, struct fw3_redirect *redir, struct uci_e warn_section("redirect", redir, e, "has no src_dip option specified"); else if (!list_empty(&redir->mac_src)) warn_section("redirect", redir, e, "must not use 'src_mac' option for SNAT target"); + else if (redir->helper.set) + warn_section("redirect", redir, e, "must not use 'helper' option for SNAT target"); else { set(redir->_dest->flags, FW3_FAMILY_V4, redir->target); @@ -346,8 +410,12 @@ fw3_load_redirects(struct fw3_state *state, struct uci_package *p, continue; } - if (!check_redirect(state, redir, NULL)) + if (!check_redirect(state, redir, NULL)) { fw3_free_redirect(redir); + continue; + } + + select_helper(state, redir); } uci_foreach_element(&p->sections, e) @@ -368,8 +436,12 @@ fw3_load_redirects(struct fw3_state *state, struct uci_package *p, continue; } - if (!check_redirect(state, redir, e)) + if (!check_redirect(state, redir, e)) { fw3_free_redirect(redir); + continue; + } + + select_helper(state, redir); } } @@ -446,19 +518,19 @@ set_target_nat(struct fw3_ipt_rule *r, struct fw3_redirect *redir) } static void -set_comment(struct fw3_ipt_rule *r, const char *name, int num, bool ref) +set_comment(struct fw3_ipt_rule *r, const char *name, int num, const char *suffix) { if (name) { - if (ref) - fw3_ipt_rule_comment(r, "%s (reflection)", name); + if (suffix) + fw3_ipt_rule_comment(r, "%s (%s)", name, suffix); else fw3_ipt_rule_comment(r, name); } else { - if (ref) - fw3_ipt_rule_comment(r, "@redirect[%u] (reflection)", num); + if (suffix) + fw3_ipt_rule_comment(r, "@redirect[%u] (%s)", num, suffix); else fw3_ipt_rule_comment(r, "@redirect[%u]", num); } @@ -491,15 +563,46 @@ print_redirect(struct fw3_ipt_handle *h, struct fw3_state *state, fw3_ipt_rule_sport_dport(r, spt, dpt); fw3_ipt_rule_mac(r, mac); fw3_ipt_rule_ipset(r, &redir->ipset); + fw3_ipt_rule_helper(r, &redir->helper); fw3_ipt_rule_limit(r, &redir->limit); fw3_ipt_rule_time(r, &redir->time); fw3_ipt_rule_mark(r, &redir->mark); set_target_nat(r, redir); fw3_ipt_rule_extra(r, redir->extra); - set_comment(r, redir->name, num, false); + set_comment(r, redir->name, num, NULL); append_chain_nat(r, redir); break; + case FW3_TABLE_RAW: + if (redir->target == FW3_FLAG_DNAT && redir->helper.ptr) + { + if (redir->helper.ptr->proto.protocol != proto->protocol) + { + info(" ! Skipping protocol %s since helper '%s' does not support it", + fw3_protoname(proto), redir->helper.ptr->name); + return; + } + + if (!redir->helper.set) + info(" - Auto-selected conntrack helper '%s' based on proto/port", + redir->helper.ptr->name); + + r = fw3_ipt_rule_create(h, proto, NULL, NULL, &redir->ip_src, &redir->ip_redir); + fw3_ipt_rule_sport_dport(r, &redir->port_src, &redir->port_redir); + fw3_ipt_rule_mac(r, mac); + fw3_ipt_rule_ipset(r, &redir->ipset); + fw3_ipt_rule_limit(r, &redir->limit); + fw3_ipt_rule_time(r, &redir->time); + fw3_ipt_rule_mark(r, &redir->mark); + fw3_ipt_rule_addarg(r, false, "-m", "conntrack"); + fw3_ipt_rule_addarg(r, false, "--ctstate", "DNAT"); + fw3_ipt_rule_target(r, "CT"); + fw3_ipt_rule_addarg(r, false, "--helper", redir->helper.ptr->name); + set_comment(r, redir->name, num, "CT helper"); + fw3_ipt_rule_append(r, "zone_%s_helper", redir->_src->name); + } + break; + default: break; } @@ -520,7 +623,7 @@ print_reflection(struct fw3_ipt_handle *h, struct fw3_state *state, fw3_ipt_rule_sport_dport(r, NULL, &redir->port_dest); fw3_ipt_rule_limit(r, &redir->limit); fw3_ipt_rule_time(r, &redir->time); - set_comment(r, redir->name, num, true); + set_comment(r, redir->name, num, "reflection"); set_snat_dnat(r, FW3_FLAG_DNAT, &redir->ip_redir, &redir->port_redir); fw3_ipt_rule_replace(r, "zone_%s_prerouting", redir->dest.name); @@ -528,7 +631,7 @@ print_reflection(struct fw3_ipt_handle *h, struct fw3_state *state, fw3_ipt_rule_sport_dport(r, NULL, &redir->port_redir); fw3_ipt_rule_limit(r, &redir->limit); fw3_ipt_rule_time(r, &redir->time); - set_comment(r, redir->name, num, true); + set_comment(r, redir->name, num, "reflection"); set_snat_dnat(r, FW3_FLAG_SNAT, ra, NULL); fw3_ipt_rule_replace(r, "zone_%s_postrouting", redir->dest.name); break; @@ -650,9 +753,16 @@ fw3_print_redirects(struct fw3_ipt_handle *handle, struct fw3_state *state) if (handle->family == FW3_FAMILY_V6) return; - if (handle->table != FW3_TABLE_FILTER && handle->table != FW3_TABLE_NAT) + if (handle->table != FW3_TABLE_FILTER && + handle->table != FW3_TABLE_NAT && + handle->table != FW3_TABLE_RAW) return; list_for_each_entry(redir, &state->redirects, list) + { + if (handle->table == FW3_TABLE_RAW && !redir->helper.ptr) + continue; + expand_redirect(handle, state, redir, num++); + } } diff --git a/redirects.h b/redirects.h index ac625f4..0d46bd2 100644 --- a/redirects.h +++ b/redirects.h @@ -22,6 +22,7 @@ #include "options.h" #include "zones.h" #include "ipsets.h" +#include "helpers.h" #include "ubus.h" #include "iptables.h" @@ -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 */ @@ -22,6 +22,7 @@ #include "options.h" #include "zones.h" #include "ipsets.h" +#include "helpers.h" #include "utils.h" #include "iptables.h" @@ -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; +} @@ -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 @@ -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; } |