@@ -81,6 +81,8 @@ static struct ipset_type ipset_types[] = {
OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
T(HASH, NET, PORT, UNSPEC, 0,
OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
+ T(HASH, NET, IFACE, UNSPEC, 0,
+ OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
T(HASH, IP, PORT, IP, 0,
OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
T(HASH, IP, PORT, NET, 0,
@@ -247,7 +249,7 @@ check_ipset(struct fw3_state *state, struct fw3_ipset *ipset, struct uci_element
return false;
}
-static struct fw3_ipset *
+struct fw3_ipset *
fw3_alloc_ipset(struct fw3_state *state)
{
struct fw3_ipset *ipset;
@@ -611,3 +613,37 @@ fw3_ipsets_update_run_state(enum fw3_family family, struct fw3_state *run_state,
ipset_run->reload_set = ipset_cfg->reload_set;
}
}
+
+void
+fw3_ipset_add_devices(struct list_head *devices, enum fw3_family family,
+ const char *set_name)
+{
+ struct fw3_device *dev;
+ bool exec = false;
+ const char *addr;
+
+ fw3_foreach(dev, devices) {
+ if (!dev)
+ continue;
+
+ if (!exec) {
+ exec = fw3_command_pipe(false, "ipset", "-exist", "-");
+
+ if (!exec)
+ return;
+ }
+
+ if (family == FW3_FAMILY_V4) {
+ addr = "0.0.0.0/0";
+ } else {
+ addr = "::/0";
+ }
+
+ fw3_pr("add %s %s,%s\n", set_name, addr, dev->name);
+ }
+
+ if (exec) {
+ fw3_pr("quit\n");
+ fw3_command_close();
+ }
+}
@@ -41,6 +41,13 @@ void
fw3_ipsets_update_run_state(enum fw3_family family, struct fw3_state *run_state,
struct fw3_state *cfg_state);
+struct fw3_ipset *
+fw3_alloc_ipset(struct fw3_state *state);
+
+void
+fw3_ipset_add_devices(struct list_head *devices, enum fw3_family family,
+ const char *set_name);
+
static inline void fw3_free_ipset(struct fw3_ipset *ipset)
{
list_del(&ipset->list);
@@ -266,6 +266,9 @@ start(void)
continue;
}
+ if (!print_family)
+ fw3_fill_zone_ipsets(family, cfg_state);
+
for (table = FW3_TABLE_FILTER; table <= FW3_TABLE_RAW; table++)
{
if (!fw3_has_table(family == FW3_FAMILY_V6, fw3_flag_names[table]))
@@ -364,7 +367,10 @@ start:
if (family == FW3_FAMILY_V6 && cfg_state->defaults.disable_ipv6)
continue;
- fw3_create_ipsets(cfg_state, family, true);
+ if (!print_family) {
+ fw3_create_ipsets(cfg_state, family, true);
+ fw3_fill_zone_ipsets(family, cfg_state);
+ }
for (table = FW3_TABLE_FILTER; table <= FW3_TABLE_RAW; table++)
{
@@ -114,6 +114,7 @@ const char *fw3_ipset_type_names[__FW3_IPSET_TYPE_MAX] = {
"mac",
"net",
"set",
+ "iface",
};
static const char *weekdays[] = {
@@ -666,7 +667,7 @@ fw3_parse_ipset_datatype(void *ptr, const char *val, bool is_list)
}
if (parse_enum(&type.type, val, &fw3_ipset_type_names[FW3_IPSET_TYPE_IP],
- FW3_IPSET_TYPE_IP, FW3_IPSET_TYPE_SET))
+ FW3_IPSET_TYPE_IP, FW3_IPSET_TYPE_IFACE))
{
put_value(ptr, &type, sizeof(type), is_list);
return true;
@@ -132,6 +132,7 @@ enum fw3_ipset_type
FW3_IPSET_TYPE_MAC = 3,
FW3_IPSET_TYPE_NET = 4,
FW3_IPSET_TYPE_SET = 5,
+ FW3_IPSET_TYPE_IFACE = 6,
__FW3_IPSET_TYPE_MAX
};
@@ -336,6 +337,9 @@ struct fw3_zone
const char *extra_src;
const char *extra_dest;
+ char *set_name_4;
+ char *set_name_6;
+
bool masq;
bool masq_allow_invalid;
struct list_head masq_src;
@@ -19,6 +19,7 @@
#include "zones.h"
#include "ubus.h"
#include "helpers.h"
+#include "ipsets.h"
#define C(f, tbl, tgt, fmt) \
@@ -220,6 +221,67 @@ fw3_alloc_zone(void)
return zone;
}
+static struct fw3_ipset*
+add_zone_ipset(struct fw3_state *state, struct fw3_zone *zone,
+ enum fw3_family family)
+{
+ struct fw3_ipset *ipset_opt;
+ char *set_name;
+
+ if (!(set_name = calloc(1, 32)))
+ return NULL;
+
+ if (family == FW3_FAMILY_V4) {
+ snprintf(set_name, 32, "zone_%s_4_set", zone->name);
+ } else {
+ snprintf(set_name, 32, "zone_%s_6_set", zone->name);
+ }
+
+ if (!(ipset_opt = fw3_alloc_ipset(state)))
+ return NULL;
+
+ ipset_opt->name = set_name;
+ ipset_opt->reload_set = true;
+
+ if (family == FW3_FAMILY_V6) {
+ ipset_opt->family = FW3_FAMILY_V6;
+ zone->set_name_6 = set_name;
+ } else {
+ zone->set_name_4 = set_name;
+ }
+
+ fw3_parse_ipset_method(&(ipset_opt->method), "hash", false);
+ fw3_parse_ipset_datatype(&(ipset_opt->datatypes), "net", true);
+ fw3_parse_ipset_datatype(&(ipset_opt->datatypes), "iface", true);
+
+ return ipset_opt;
+}
+
+static struct fw3_ipset*
+add_zone_ipsets(struct fw3_state *state, struct fw3_zone *zone)
+{
+ struct fw3_ipset *ipset_v4 = NULL, *ipset_v6 = NULL;
+
+ if (zone->family == FW3_FAMILY_ANY || zone->family == FW3_FAMILY_V4) {
+ ipset_v4 = add_zone_ipset(state, zone, FW3_FAMILY_V4);
+
+ if (!ipset_v4)
+ return NULL;
+ }
+
+ if (zone->family == FW3_FAMILY_ANY || zone->family == FW3_FAMILY_V6) {
+ ipset_v6 = add_zone_ipset(state, zone, FW3_FAMILY_V6);
+
+ if (!ipset_v6) {
+ if (ipset_v4)
+ fw3_free_ipset(ipset_v4);
+ return NULL;
+ }
+ }
+
+ return ipset_v4 ? ipset_v4 : ipset_v6;
+}
+
void
fw3_load_zones(struct fw3_state *state, struct uci_package *p)
{
@@ -322,6 +384,14 @@ fw3_load_zones(struct fw3_state *state, struct uci_package *p)
fw3_setbit(zone->flags[1], zone->policy_forward);
fw3_setbit(zone->flags[1], zone->policy_output);
+ if (!state->statefile && !state->disable_ipsets) {
+ if (!add_zone_ipsets(state, zone)) {
+ warn_elem(e, "creating zone ipsets failed");
+ fw3_free_zone(zone);
+ continue;
+ }
+ }
+
list_add_tail(&zone->list, &state->zones);
}
}
@@ -400,16 +470,31 @@ print_zone_chain(struct fw3_ipt_handle *handle, struct fw3_state *state,
set(zone->flags, handle->family, handle->table);
}
+static void
+interface_rule_add_set_match(struct fw3_ipt_rule *r, const char *ipset_name,
+ bool match_src)
+{
+ fw3_ipt_rule_addarg(r, false, "-m", "set");
+ fw3_ipt_rule_addarg(r, false, "--match-set", ipset_name);
+
+ if (match_src)
+ fw3_ipt_rule_addarg(r, false, "src,src", NULL);
+ else
+ fw3_ipt_rule_addarg(r, false, "dst,dst", NULL);
+}
+
static void
print_interface_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
- bool reload, struct fw3_zone *zone,
- struct fw3_device *dev, struct fw3_address *sub)
+ bool reload, struct fw3_zone *zone, struct fw3_device *dev,
+ struct fw3_address *sub, bool loopback_dev,
+ const char *raw_chain)
{
struct fw3_protocol tcp = { .protocol = 6 };
struct fw3_ipt_rule *r;
enum fw3_flag t;
char buf[32];
+ char ipset_name[32];
int i;
@@ -419,6 +504,16 @@ print_interface_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
"forward", "FORWARD",
};
+ if (!state->disable_ipsets)
+ {
+ if (handle->family == FW3_FAMILY_V4)
+ snprintf(ipset_name, sizeof(ipset_name),
+ "zone_%s_4_set", zone->name);
+ else
+ snprintf(ipset_name, sizeof(ipset_name),
+ "zone_%s_6_set", zone->name);
+ }
+
#define jump_target(t) \
((t == FW3_FLAG_REJECT) ? "reject" : fw3_flag_names[t])
@@ -432,6 +527,11 @@ print_interface_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
{
r = fw3_ipt_rule_create(handle, NULL, dev, NULL, sub, NULL);
+ if (!state->disable_ipsets)
+ interface_rule_add_set_match(r,
+ ipset_name,
+ true);
+
snprintf(buf, sizeof(buf) - 1, "%s %s in: ",
fw3_flag_names[t], zone->name);
@@ -446,6 +546,11 @@ print_interface_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
{
r = fw3_ipt_rule_create(handle, NULL, NULL, dev, NULL, sub);
+ if (!state->disable_ipsets)
+ interface_rule_add_set_match(r,
+ ipset_name,
+ false);
+
snprintf(buf, sizeof(buf) - 1, "%s %s out: ",
fw3_flag_names[t], zone->name);
@@ -460,6 +565,12 @@ print_interface_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
if (has(zone->flags, handle->family, fw3_to_src_target(t)))
{
r = fw3_ipt_rule_create(handle, NULL, dev, NULL, sub, NULL);
+
+ if (!state->disable_ipsets)
+ interface_rule_add_set_match(r,
+ ipset_name,
+ true);
+
fw3_ipt_rule_target(r, jump_target(t));
fw3_ipt_rule_extra(r, zone->extra_src);
@@ -477,6 +588,12 @@ print_interface_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
zone->masq && !zone->masq_allow_invalid)
{
r = fw3_ipt_rule_create(handle, NULL, NULL, dev, NULL, sub);
+
+ if (!state->disable_ipsets)
+ interface_rule_add_set_match(r,
+ ipset_name,
+ false);
+
fw3_ipt_rule_extra(r, "-m conntrack --ctstate INVALID");
fw3_ipt_rule_comment(r, "Prevent NAT leakage");
fw3_ipt_rule_target(r, fw3_flag_names[FW3_FLAG_DROP]);
@@ -485,6 +602,12 @@ print_interface_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
}
r = fw3_ipt_rule_create(handle, NULL, NULL, dev, NULL, sub);
+
+ if (!state->disable_ipsets)
+ interface_rule_add_set_match(r,
+ ipset_name,
+ false);
+
fw3_ipt_rule_target(r, jump_target(t));
fw3_ipt_rule_extra(r, zone->extra_dest);
fw3_ipt_rule_replace(r, "zone_%s_dest_%s", zone->name,
@@ -494,11 +617,22 @@ print_interface_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
for (i = 0; i < sizeof(chains)/sizeof(chains[0]); i += 2)
{
- if (*chains[i] == 'o')
+ if (*chains[i] == 'o') {
r = fw3_ipt_rule_create(handle, NULL, NULL, dev, NULL, sub);
- else
+
+ if (!state->disable_ipsets)
+ interface_rule_add_set_match(r,
+ ipset_name,
+ false);
+ } else {
r = fw3_ipt_rule_create(handle, NULL, dev, NULL, sub, NULL);
+ if (!state->disable_ipsets)
+ interface_rule_add_set_match(r,
+ ipset_name,
+ true);
+ }
+
fw3_ipt_rule_target(r, "zone_%s_%s", zone->name, chains[i]);
if (*chains[i] == 'o')
@@ -514,6 +648,11 @@ print_interface_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
if (has(zone->flags, handle->family, FW3_FLAG_DNAT))
{
r = fw3_ipt_rule_create(handle, NULL, dev, NULL, sub, NULL);
+
+ if (!state->disable_ipsets)
+ interface_rule_add_set_match(r, ipset_name,
+ true);
+
fw3_ipt_rule_target(r, "zone_%s_prerouting", zone->name);
fw3_ipt_rule_extra(r, zone->extra_src);
fw3_ipt_rule_replace(r, "PREROUTING");
@@ -522,6 +661,11 @@ print_interface_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
if (has(zone->flags, handle->family, FW3_FLAG_SNAT))
{
r = fw3_ipt_rule_create(handle, NULL, NULL, dev, NULL, sub);
+
+ if (!state->disable_ipsets)
+ interface_rule_add_set_match(r, ipset_name,
+ false);
+
fw3_ipt_rule_target(r, "zone_%s_postrouting", zone->name);
fw3_ipt_rule_extra(r, zone->extra_dest);
fw3_ipt_rule_replace(r, "POSTROUTING");
@@ -536,6 +680,12 @@ print_interface_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
snprintf(buf, sizeof(buf) - 1, "MSSFIX %s out: ", zone->name);
r = fw3_ipt_rule_create(handle, &tcp, NULL, dev, NULL, sub);
+
+ if (!state->disable_ipsets)
+ interface_rule_add_set_match(r,
+ ipset_name,
+ true);
+
fw3_ipt_rule_addarg(r, false, "--tcp-flags", "SYN,RST");
fw3_ipt_rule_addarg(r, false, "SYN", NULL);
fw3_ipt_rule_limit(r, &zone->log_limit);
@@ -546,6 +696,11 @@ print_interface_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
}
r = fw3_ipt_rule_create(handle, &tcp, NULL, dev, NULL, sub);
+
+ if (!state->disable_ipsets)
+ interface_rule_add_set_match(r, ipset_name,
+ true);
+
fw3_ipt_rule_addarg(r, false, "--tcp-flags", "SYN,RST");
fw3_ipt_rule_addarg(r, false, "SYN", NULL);
fw3_ipt_rule_comment(r, "Zone %s MTU fixing", zone->name);
@@ -556,37 +711,45 @@ print_interface_rule(struct fw3_ipt_handle *handle, struct fw3_state *state,
}
else if (handle->table == FW3_TABLE_RAW)
{
- bool loopback_dev = (dev != NULL && !dev->any &&
- !dev->invert && fw3_check_loopback_dev(dev->name));
- char *chain = loopback_dev || (sub != NULL && !sub->invert && fw3_check_loopback_addr(sub)) ?
- "OUTPUT" : "PREROUTING";
-
if (has(zone->flags, handle->family, FW3_FLAG_HELPER))
{
r = fw3_ipt_rule_create(handle, NULL, loopback_dev ? NULL : dev, NULL, sub, NULL);
+
+ if (!state->disable_ipsets)
+ interface_rule_add_set_match(r, ipset_name,
+ true);
+
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, chain);
+ fw3_ipt_rule_replace(r, raw_chain);
}
if (has(zone->flags, handle->family, FW3_FLAG_NOTRACK))
{
r = fw3_ipt_rule_create(handle, NULL, loopback_dev ? NULL : dev, NULL, sub, NULL);
+
+ if (!state->disable_ipsets)
+ interface_rule_add_set_match(r, ipset_name,
+ true);
+
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, chain);
+ fw3_ipt_rule_replace(r, raw_chain);
}
}
}
static void
-print_interface_rules(struct fw3_ipt_handle *handle, struct fw3_state *state,
- bool reload, struct fw3_zone *zone)
+print_interface_rules_default(struct fw3_ipt_handle *handle,
+ struct fw3_state *state, bool reload,
+ struct fw3_zone *zone)
{
struct fw3_device *dev;
struct fw3_address *sub;
+ bool loopback_dev = false;
+ char *raw_chain_name;
fw3_foreach(dev, &zone->devices)
fw3_foreach(sub, &zone->subnets)
@@ -597,8 +760,89 @@ print_interface_rules(struct fw3_ipt_handle *handle, struct fw3_state *state,
if (!dev && !sub)
continue;
- print_interface_rule(handle, state, reload, zone, dev, sub);
+ if (handle->table == FW3_TABLE_RAW)
+ {
+ loopback_dev = (dev != NULL && !dev->any &&
+ !dev->invert &&
+ fw3_check_loopback_dev(dev->name));
+ raw_chain_name = loopback_dev || (sub != NULL &&
+ !sub->invert &&
+ fw3_check_loopback_addr(sub)) ?
+ "OUTPUT" : "PREROUTING";
+ }
+
+ print_interface_rule(handle, state, reload, zone, dev, sub,
+ loopback_dev, raw_chain_name);
+ }
+}
+
+static void
+print_interface_rules_set(struct fw3_ipt_handle *handle,
+ struct fw3_state *state, bool reload,
+ struct fw3_zone *zone)
+{
+ struct fw3_device *dev;
+ struct fw3_address *sub;
+ bool loopback_dev = false, other_dev = false,
+ loopback_addr_seen = false;
+
+ if (handle->table == FW3_TABLE_RAW)
+ {
+ fw3_foreach(dev, &zone->devices)
+ {
+ if (!dev)
+ continue;
+
+ if (!dev->any && !dev->invert &&
+ fw3_check_loopback_dev(dev->name))
+ loopback_dev = true;
+ else
+ other_dev = true;
+ }
+ }
+
+ fw3_foreach(sub, &zone->subnets)
+ {
+ if (!fw3_is_family(sub, handle->family))
+ continue;
+
+ if (handle->table == FW3_TABLE_RAW)
+ {
+ if (sub && !sub->invert && fw3_check_loopback_addr(sub))
+ {
+ print_interface_rule(handle, state, reload,
+ zone, NULL, sub, false,
+ "OUTPUT");
+ loopback_addr_seen = true;
+ }
+
+ if (other_dev)
+ {
+ print_interface_rule(handle, state, reload,
+ zone, NULL, sub, false,
+ "PREROUTING");
+ }
+ }
+ else
+ {
+ print_interface_rule(handle, state, reload, zone, NULL,
+ sub, false, NULL);
+ }
}
+
+ if (loopback_dev && !loopback_addr_seen)
+ print_interface_rule(handle, state, reload, zone, NULL, NULL,
+ false, "OUTPUT");
+}
+
+static void
+print_interface_rules(struct fw3_ipt_handle *handle, struct fw3_state *state,
+ bool reload, struct fw3_zone *zone)
+{
+ if (state->disable_ipsets)
+ print_interface_rules_default(handle, state, reload, zone);
+ else
+ print_interface_rules_set(handle, state, reload, zone);
}
static struct fw3_address *
@@ -875,3 +1119,22 @@ fw3_resolve_zone_addresses(struct fw3_zone *zone, struct fw3_address *addr)
return all;
}
+
+void
+fw3_fill_zone_ipsets(enum fw3_family family, struct fw3_state *state)
+{
+ struct fw3_zone *zone;
+ const char *set_name;
+
+ if (state->disable_ipsets)
+ return;
+
+ list_for_each_entry(zone, &state->zones, list) {
+ if (family == FW3_FAMILY_V4)
+ set_name = zone->set_name_4;
+ else
+ set_name = zone->set_name_6;
+
+ fw3_ipset_add_devices(&zone->devices, family, set_name);
+ }
+}
@@ -47,8 +47,19 @@ struct fw3_zone * fw3_lookup_zone(struct fw3_state *state, const char *name);
struct list_head * fw3_resolve_zone_addresses(struct fw3_zone *zone,
struct fw3_address *addr);
-#define fw3_free_zone(zone) \
- fw3_free_object(zone, fw3_zone_opts)
+void
+fw3_fill_zone_ipsets(enum fw3_family family, struct fw3_state *state);
+
+static inline void fw3_free_zone(struct fw3_zone *zone)
+{
+ if (zone->set_name_4)
+ free(zone->set_name_4);
+
+ if (zone->set_name_6)
+ free(zone->set_name_6);
+
+ fw3_free_object(zone, fw3_zone_opts);
+}
#define fw3_to_src_target(t) \
(FW3_FLAG_SRC_ACCEPT - FW3_FLAG_ACCEPT + t)
firewall3 currently creates one rule for each interface that is a member of a zone. On for example devices with multiple interfaces, the current firewall3 behavior quickly leads to a lot of rules. In order to reduce the number of rules, this patch replaces the per-interface rules with ipset matches (if ipset is available). Since 2011, ipset has supported the set type "hash:net,iface". By adding (and matching on) on pairs consiting of the v4/v6 any-address and an interface name, we get the same behavior as the current interface-rules. After applying this patch (and assuming ipset is available), the following actions are performed when a zone is created: * We creates (allocate) an ipset of type "hash:net,iface" for each zone. The name follows the following format: zone_<zone name>_<4/6>_set. * If creating a set fails, then we ignore the zone. This is something we can change, but my reason for this behavior is to have consistent firewall rules. I.e., zone-rules either match on ipset or interface names, and not a mix. * Each set is populated with pairs consisting of the IPv4/IPv6 any-address and an interface name, for example "0.0.0.0/0, eth0.2". * Instead of one rule per device, a single rule is created matching on the ipset. * The check used to select the OUTPUT/PREROUTING-chain when adding rules to the raw-table has been moved to print_interface_rules_{default,set}. The motivation behind this move was to avoid changing print_interface_rule() too much. As far as I can see (and have tested), the logic for selecting chain/creating the rules is the same as before. Because the change introduced by this patch is quite intrusive and I am sure there will be comments/disagreements/suggestions, I have sent this patch as an RFC. One thing that I am aware of and will fix before the final submission, is to add support for printing ipsets. Right now "fw3 print" prints per-interface rules. Signed-off-by: Kristian Evensen <kristian.evensen@gmail.com> --- ipsets.c | 38 ++++++- ipsets.h | 7 ++ main.c | 8 +- options.c | 3 +- options.h | 4 + zones.c | 291 +++++++++++++++++++++++++++++++++++++++++++++++++++--- zones.h | 15 ++- 7 files changed, 347 insertions(+), 19 deletions(-)