/*
* firewall3 - 3rd OpenWrt UCI firewall implementation
*
- * Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ * Copyright (C) 2013 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
#include "ipsets.h"
-static struct fw3_option ipset_opts[] = {
+const struct fw3_option fw3_ipset_opts[] = {
+ FW3_OPT("enabled", bool, ipset, enabled),
+
FW3_OPT("name", string, ipset, name),
FW3_OPT("family", family, ipset, family),
FW3_OPT("storage", ipset_method, ipset, method),
FW3_LIST("match", ipset_datatype, ipset, datatypes),
- FW3_LIST("iprange", address, ipset, iprange),
+ FW3_OPT("iprange", address, ipset, iprange),
FW3_OPT("portrange", port, ipset, portrange),
FW3_OPT("netmask", int, ipset, netmask),
FW3_OPT("timeout", int, ipset, timeout),
FW3_OPT("external", string, ipset, external),
+
+ { }
};
#define T(m, t1, t2, t3, r, o) \
FW3_IPSET_TYPE_##t1 | (FW3_IPSET_TYPE_##t2 << 8) | (FW3_IPSET_TYPE_##t3 << 16), \
r, o }
-static struct fw3_ipset_settype ipset_types[] = {
- T(BITMAP, IP, UNSPEC, UNSPEC, FW3_IPSET_OPT_IPRANGE,
- FW3_IPSET_OPT_NETMASK),
- T(BITMAP, IP, MAC, UNSPEC, FW3_IPSET_OPT_IPRANGE, 0),
- T(BITMAP, PORT, UNSPEC, UNSPEC, FW3_IPSET_OPT_PORTRANGE, 0),
+enum ipset_optflag {
+ OPT_IPRANGE = (1 << 0),
+ OPT_PORTRANGE = (1 << 1),
+ OPT_NETMASK = (1 << 2),
+ OPT_HASHSIZE = (1 << 3),
+ OPT_MAXELEM = (1 << 4),
+ OPT_FAMILY = (1 << 5),
+};
+
+struct ipset_type {
+ enum fw3_ipset_method method;
+ uint32_t types;
+ uint8_t required;
+ uint8_t optional;
+};
+
+static struct ipset_type ipset_types[] = {
+ T(BITMAP, IP, UNSPEC, UNSPEC, OPT_IPRANGE, OPT_NETMASK),
+ T(BITMAP, IP, MAC, UNSPEC, OPT_IPRANGE, 0),
+ T(BITMAP, PORT, UNSPEC, UNSPEC, OPT_PORTRANGE, 0),
T(HASH, IP, UNSPEC, UNSPEC, 0,
- FW3_IPSET_OPT_FAMILY | FW3_IPSET_OPT_HASHSIZE | FW3_IPSET_OPT_MAXELEM |
- FW3_IPSET_OPT_NETMASK),
+ OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM | OPT_NETMASK),
T(HASH, NET, UNSPEC, UNSPEC, 0,
- FW3_IPSET_OPT_FAMILY | FW3_IPSET_OPT_HASHSIZE | FW3_IPSET_OPT_MAXELEM),
+ OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
T(HASH, IP, PORT, UNSPEC, 0,
- FW3_IPSET_OPT_FAMILY | FW3_IPSET_OPT_HASHSIZE | FW3_IPSET_OPT_MAXELEM),
+ OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
T(HASH, NET, PORT, UNSPEC, 0,
- FW3_IPSET_OPT_FAMILY | FW3_IPSET_OPT_HASHSIZE | FW3_IPSET_OPT_MAXELEM),
+ OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
T(HASH, IP, PORT, IP, 0,
- FW3_IPSET_OPT_FAMILY | FW3_IPSET_OPT_HASHSIZE | FW3_IPSET_OPT_MAXELEM),
+ OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
T(HASH, IP, PORT, NET, 0,
- FW3_IPSET_OPT_FAMILY | FW3_IPSET_OPT_HASHSIZE | FW3_IPSET_OPT_MAXELEM),
+ OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
- T(LIST, SET, UNSPEC, UNSPEC, 0, FW3_IPSET_OPT_MAXELEM),
+ T(LIST, SET, UNSPEC, UNSPEC, 0, OPT_MAXELEM),
};
uint32_t typelist = 0;
struct fw3_ipset_datatype *type;
- const char *methods[] = {
- "(bug)",
- "bitmap",
- "hash",
- "list",
- };
-
- typelist = 0;
-
list_for_each_entry(type, &ipset->datatypes, list)
{
if (i >= 3)
{
- warn_elem(e, "must not have more than 3 datatypes assigned");
+ warn_section("ipset", ipset, e, "must not have more than 3 datatypes assigned");
return false;
}
{
for (i = 0; i < ARRAY_SIZE(ipset_types); i++)
{
+ /* skip type for v6 if it does not support family */
+ if (ipset->family != FW3_FAMILY_V4 &&
+ !(ipset_types[i].optional & OPT_FAMILY))
+ continue;
+
if (ipset_types[i].types == typelist)
{
ipset->method = ipset_types[i].method;
- warn_elem(e, "defines no storage method, assuming '%s'",
- methods[ipset->method]);
+ warn_section("ipset", ipset, e, "defines no storage method, assuming '%s'",
+ fw3_ipset_method_names[ipset->method]);
break;
}
if (ipset_types[i].method == ipset->method &&
ipset_types[i].types == typelist)
{
- if (!ipset->external || !*ipset->external)
+ if (!ipset->external)
{
- if ((ipset_types[i].required & FW3_IPSET_OPT_IPRANGE) &&
- list_empty(&ipset->iprange))
+ if ((ipset_types[i].required & OPT_IPRANGE) &&
+ !ipset->iprange.set)
{
- warn_elem(e, "requires an ip range");
+ warn_section("ipset", ipset, e, "requires an ip range");
return false;
}
- if ((ipset_types[i].required & FW3_IPSET_OPT_PORTRANGE) &&
+ if ((ipset_types[i].required & OPT_PORTRANGE) &&
!ipset->portrange.set)
{
- warn_elem(e, "requires a port range");
+ warn_section("ipset", ipset, e, "requires a port range");
return false;
}
- if (!(ipset_types[i].required & FW3_IPSET_OPT_IPRANGE) &&
- !list_empty(&ipset->iprange))
+ if (!(ipset_types[i].required & OPT_IPRANGE) &&
+ ipset->iprange.set)
{
- warn_elem(e, "iprange ignored");
- fw3_free_list(&ipset->iprange);
+ warn_section("ipset", ipset, e, "iprange ignored");
+ ipset->iprange.set = false;
}
- if (!(ipset_types[i].required & FW3_IPSET_OPT_PORTRANGE) &&
+ if (!(ipset_types[i].required & OPT_PORTRANGE) &&
ipset->portrange.set)
{
- warn_elem(e, "portrange ignored");
- memset(&ipset->portrange, 0, sizeof(ipset->portrange));
+ warn_section("ipset", ipset, e, "portrange ignored");
+ ipset->portrange.set = false;
}
- if (!(ipset_types[i].optional & FW3_IPSET_OPT_NETMASK) &&
+ if (!(ipset_types[i].optional & OPT_NETMASK) &&
ipset->netmask > 0)
{
- warn_elem(e, "netmask ignored");
+ warn_section("ipset", ipset, e, "netmask ignored");
ipset->netmask = 0;
}
- if (!(ipset_types[i].optional & FW3_IPSET_OPT_HASHSIZE) &&
+ if (!(ipset_types[i].optional & OPT_HASHSIZE) &&
ipset->hashsize > 0)
{
- warn_elem(e, "hashsize ignored");
+ warn_section("ipset", ipset, e, "hashsize ignored");
ipset->hashsize = 0;
}
- if (!(ipset_types[i].optional & FW3_IPSET_OPT_MAXELEM) &&
+ if (!(ipset_types[i].optional & OPT_MAXELEM) &&
ipset->maxelem > 0)
{
- warn_elem(e, "maxelem ignored");
+ warn_section("ipset", ipset, e, "maxelem ignored");
ipset->maxelem = 0;
}
- if (!(ipset_types[i].optional & FW3_IPSET_OPT_FAMILY) &&
- ipset->family != FW3_FAMILY_ANY)
+ if (!(ipset_types[i].optional & OPT_FAMILY) &&
+ ipset->family != FW3_FAMILY_V4)
{
- warn_elem(e, "family ignored");
- ipset->family = FW3_FAMILY_ANY;
+ warn_section("ipset", ipset, e, "family ignored");
+ ipset->family = FW3_FAMILY_V4;
}
}
}
}
- warn_elem(e, "has an invalid combination of storage method and matches");
+ warn_section("ipset", ipset, e, "has an invalid combination of storage method and matches");
+ return false;
+}
+
+static bool
+check_ipset(struct fw3_state *state, struct fw3_ipset *ipset, struct uci_element *e)
+{
+ if (ipset->external)
+ {
+ if (!*ipset->external)
+ ipset->external = NULL;
+ else if (!ipset->name)
+ ipset->name = ipset->external;
+ }
+
+ if (!ipset->name || !*ipset->name)
+ {
+ warn_section("ipset", ipset, e, "ipset must have a name assigned");
+ }
+ //else if (fw3_lookup_ipset(state, ipset->name) != NULL)
+ //{
+ // warn_section("ipset", ipset, e, "has duplicated set name", ipset->name);
+ //}
+ else if (ipset->family == FW3_FAMILY_ANY)
+ {
+ warn_section("ipset", ipset, e, "must not have family 'any'");
+ }
+ else if (ipset->iprange.set && ipset->family != ipset->iprange.family)
+ {
+ warn_section("ipset", ipset, e, "has iprange of wrong address family");
+ }
+ else if (list_empty(&ipset->datatypes))
+ {
+ warn_section("ipset", ipset, e, "has no datatypes assigned");
+ }
+ else if (check_types(e, ipset))
+ {
+ return true;
+ }
+
return false;
}
+static struct fw3_ipset *
+fw3_alloc_ipset(struct fw3_state *state)
+{
+ struct fw3_ipset *ipset;
+
+ ipset = calloc(1, sizeof(*ipset));
+ if (!ipset)
+ return NULL;
+
+ INIT_LIST_HEAD(&ipset->datatypes);
+
+ ipset->enabled = true;
+ ipset->family = FW3_FAMILY_V4;
+
+ list_add_tail(&ipset->list, &state->ipsets);
+
+ return ipset;
+}
+
void
-fw3_load_ipsets(struct fw3_state *state, struct uci_package *p)
+fw3_load_ipsets(struct fw3_state *state, struct uci_package *p,
+ struct blob_attr *a)
{
struct uci_section *s;
struct uci_element *e;
struct fw3_ipset *ipset;
+ struct blob_attr *entry;
+ unsigned rem;
INIT_LIST_HEAD(&state->ipsets);
if (state->disable_ipsets)
return;
- uci_foreach_element(&p->sections, e)
+ blob_for_each_attr(entry, a, rem)
{
- s = uci_to_section(e);
+ const char *type;
+ const char *name = "ubus ipset";
- if (strcmp(s->type, "ipset"))
+ if (!fw3_attr_parse_name_type(entry, &name, &type))
continue;
- ipset = malloc(sizeof(*ipset));
+ if (strcmp(type, "ipset"))
+ continue;
+ ipset = fw3_alloc_ipset(state);
if (!ipset)
continue;
- memset(ipset, 0, sizeof(*ipset));
+ if (!fw3_parse_blob_options(ipset, fw3_ipset_opts, entry, name))
+ {
+ warn_section("ipset", ipset, NULL, "skipped due to invalid options");
+ fw3_free_ipset(ipset);
+ continue;
+ }
- INIT_LIST_HEAD(&ipset->datatypes);
- INIT_LIST_HEAD(&ipset->iprange);
+ if (!check_ipset(state, ipset, NULL))
+ fw3_free_ipset(ipset);
+ }
- fw3_parse_options(ipset, ipset_opts, ARRAY_SIZE(ipset_opts), s);
+ uci_foreach_element(&p->sections, e)
+ {
+ s = uci_to_section(e);
- if (!ipset->name || !*ipset->name)
- {
- warn_elem(e, "must have a name assigned");
- }
- //else if (fw3_lookup_ipset(state, ipset->name) != NULL)
- //{
- // warn_elem(e, "has duplicated set name '%s'", ipset->name);
- //}
- else if (list_empty(&ipset->datatypes))
- {
- warn_elem(e, "has no datatypes assigned");
- }
- else if (check_types(e, ipset))
- {
- list_add_tail(&ipset->list, &state->ipsets);
+ if (strcmp(s->type, "ipset"))
continue;
- }
- fw3_free_ipset(ipset);
+ ipset = fw3_alloc_ipset(state);
+
+ if (!ipset)
+ continue;
+
+ if (!fw3_parse_options(ipset, fw3_ipset_opts, s))
+ warn_elem(e, "has invalid options");
+
+ if (!check_ipset(state, ipset, e))
+ fw3_free_ipset(ipset);
}
}
static void
-create_ipset(struct fw3_ipset *ipset)
+create_ipset(struct fw3_ipset *ipset, struct fw3_state *state)
{
bool first = true;
- char s[INET6_ADDRSTRLEN];
struct fw3_ipset_datatype *type;
- struct fw3_address *a1, *a2;
-
- const char *methods[] = {
- "(bug)",
- "bitmap",
- "hash",
- "list",
- };
-
- const char *types[] = {
- "(bug)",
- "ip",
- "port",
- "mac",
- "net",
- "set",
- };
-
- if (ipset->external && *ipset->external)
- return;
- info(" * %s", ipset->name);
+ info(" * Creating ipset %s", ipset->name);
first = true;
- fw3_pr("create %s %s", ipset->name, methods[ipset->method]);
+ fw3_pr("create %s %s", ipset->name, fw3_ipset_method_names[ipset->method]);
list_for_each_entry(type, &ipset->datatypes, list)
{
- fw3_pr("%c%s", first ? ':' : ',', types[type->type]);
+ fw3_pr("%c%s", first ? ':' : ',', fw3_ipset_type_names[type->type]);
first = false;
}
- if (!list_empty(&ipset->iprange))
- {
- a1 = list_first_entry(&ipset->iprange, struct fw3_address, list);
- a2 = list_last_entry(&ipset->iprange, struct fw3_address, list);
-
- if (a1 == a2)
- {
- inet_ntop(a1->family == FW3_FAMILY_V4 ? AF_INET : AF_INET6,
- &a1->address.v6, s, sizeof(s));
-
- fw3_pr(" range %s/%u", s, a1->mask);
- }
- else if (a1->family == a2->family &&
- fw3_is_family(ipset, a1->family) &&
- fw3_is_family(ipset, a2->family))
- {
- inet_ntop(a1->family == FW3_FAMILY_V4 ? AF_INET : AF_INET6,
- &a1->address.v6, s, sizeof(s));
-
- fw3_pr(" range %s", s);
-
- inet_ntop(a2->family == FW3_FAMILY_V4 ? AF_INET : AF_INET6,
- &a2->address.v6, s, sizeof(s));
+ if (ipset->method == FW3_IPSET_METHOD_HASH)
+ fw3_pr(" family inet%s", (ipset->family == FW3_FAMILY_V4) ? "" : "6");
- fw3_pr("-%s", s);
- }
+ if (ipset->iprange.set)
+ {
+ fw3_pr(" range %s", fw3_address_to_string(&ipset->iprange, false, true));
}
else if (ipset->portrange.set)
{
ipset->portrange.port_min, ipset->portrange.port_max);
}
- if (ipset->family != FW3_FAMILY_ANY)
- fw3_pr(" family inet%s", (ipset->family == FW3_FAMILY_V4) ? "" : "6");
-
if (ipset->timeout > 0)
fw3_pr(" timeout %u", ipset->timeout);
void
fw3_create_ipsets(struct fw3_state *state)
{
+ int tries;
+ bool exec = false;
struct fw3_ipset *ipset;
if (state->disable_ipsets)
return;
- info("Initializing ipsets ...");
+ /* spawn ipsets */
+ list_for_each_entry(ipset, &state->ipsets, list)
+ {
+ if (ipset->external)
+ continue;
+
+ if (!exec)
+ {
+ exec = fw3_command_pipe(false, "ipset", "-exist", "-");
+
+ if (!exec)
+ return;
+ }
+
+ create_ipset(ipset, state);
+ }
+
+ if (exec)
+ {
+ fw3_pr("quit\n");
+ fw3_command_close();
+ }
+ /* wait for ipsets to appear */
list_for_each_entry(ipset, &state->ipsets, list)
- create_ipset(ipset);
+ {
+ if (ipset->external)
+ continue;
- fw3_pr("quit\n");
+ for (tries = 0; !fw3_check_ipset(ipset) && tries < 10; tries++)
+ usleep(50000);
+ }
}
void
-fw3_destroy_ipsets(struct list_head *statefile)
+fw3_destroy_ipsets(struct fw3_state *state)
{
- struct fw3_statefile_entry *e;
+ int tries;
+ bool exec = false;
+ struct fw3_ipset *ipset;
- if (statefile)
+ /* destroy ipsets */
+ list_for_each_entry(ipset, &state->ipsets, list)
{
- info("Destroying ipsets ...");
-
- list_for_each_entry(e, statefile, list)
+ if (!exec)
{
- if (e->type != FW3_TYPE_IPSET)
- continue;
+ exec = fw3_command_pipe(false, "ipset", "-exist", "-");
- info(" * %s", e->name);
-
- fw3_pr("flush %s\n", e->name);
- fw3_pr("destroy %s\n", e->name);
+ if (!exec)
+ return;
}
+
+ info(" * Deleting ipset %s", ipset->name);
+
+ fw3_pr("flush %s\n", ipset->name);
+ fw3_pr("destroy %s\n", ipset->name);
}
-}
-void
-fw3_free_ipset(struct fw3_ipset *ipset)
-{
- fw3_free_list(&ipset->datatypes);
- fw3_free_list(&ipset->iprange);
+ if (exec)
+ {
+ fw3_pr("quit\n");
+ fw3_command_close();
+ }
- free(ipset);
+ /* wait for ipsets to disappear */
+ list_for_each_entry(ipset, &state->ipsets, list)
+ {
+ if (ipset->external)
+ continue;
+
+ for (tries = 0; fw3_check_ipset(ipset) && tries < 10; tries++)
+ usleep(50000);
+ }
}
struct fw3_ipset *
fw3_lookup_ipset(struct fw3_state *state, const char *name)
{
- struct fw3_ipset *ipset;
+ struct fw3_ipset *s;
if (list_empty(&state->ipsets))
return NULL;
- list_for_each_entry(ipset, &state->ipsets, list)
- if (!strcmp(ipset->name, name))
- return ipset;
+ list_for_each_entry(s, &state->ipsets, list)
+ {
+ if (strcmp(s->name, name))
+ continue;
+
+ return s;
+ }
return NULL;
}
+
+bool
+fw3_check_ipset(struct fw3_ipset *set)
+{
+ bool rv = false;
+
+ socklen_t sz;
+ int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+ struct ip_set_req_version req_ver;
+ struct ip_set_req_get_set req_name;
+
+ if (s < 0 || fcntl(s, F_SETFD, FD_CLOEXEC))
+ goto out;
+
+ sz = sizeof(req_ver);
+ req_ver.op = IP_SET_OP_VERSION;
+
+ if (getsockopt(s, SOL_IP, SO_IP_SET, &req_ver, &sz))
+ goto out;
+
+ sz = sizeof(req_name);
+ req_name.op = IP_SET_OP_GET_BYNAME;
+ req_name.version = req_ver.version;
+ snprintf(req_name.set.name, IPSET_MAXNAMELEN - 1, "%s",
+ set->external ? set->external : set->name);
+
+ if (getsockopt(s, SOL_IP, SO_IP_SET, &req_name, &sz))
+ goto out;
+
+ rv = ((sz == sizeof(req_name)) && (req_name.set.index != IPSET_INVALID_ID));
+
+out:
+ if (s >= 0)
+ close(s);
+
+ return rv;
+}