helpers.conf: support also tcp in the CT sip helper
[project/firewall3.git] / ipsets.c
index 3d659e2..b73c3d2 100644 (file)
--- a/ipsets.c
+++ b/ipsets.c
@@ -1,7 +1,7 @@
 /*
  * 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),
@@ -35,6 +37,11 @@ static struct fw3_option ipset_opts[] = {
        FW3_OPT("timeout",       int,            ipset,     timeout),
 
        FW3_OPT("external",      string,         ipset,     external),
+
+       FW3_LIST("entry",        setentry,       ipset,     entries),
+       FW3_OPT("loadfile",      string,         ipset,     loadfile),
+
+       { }
 };
 
 #define T(m, t1, t2, t3, r, o) \
@@ -42,27 +49,41 @@ static struct fw3_option ipset_opts[] = {
          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),
 };
 
 
@@ -73,20 +94,11 @@ check_types(struct uci_element *e, struct fw3_ipset *ipset)
        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;
                }
 
@@ -98,12 +110,17 @@ check_types(struct uci_element *e, struct fw3_ipset *ipset)
        {
                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;
                        }
@@ -117,62 +134,62 @@ check_types(struct uci_element *e, struct fw3_ipset *ipset)
                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;
                                }
                        }
 
@@ -180,22 +197,110 @@ check_types(struct uci_element *e, struct fw3_ipset *ipset)
                }
        }
 
-       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);
+       INIT_LIST_HEAD(&ipset->entries);
+
+       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;
 
+       blob_for_each_attr(entry, a, rem)
+       {
+               const char *type;
+               const char *name = "ubus ipset";
+
+               if (!fw3_attr_parse_name_type(entry, &name, &type))
+                       continue;
+
+               if (strcmp(type, "ipset"))
+                       continue;
+
+               ipset = fw3_alloc_ipset(state);
+               if (!ipset)
+                       continue;
+
+               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;
+               }
+
+               if (!check_ipset(state, ipset, NULL))
+                       fw3_free_ipset(ipset);
+       }
+
        uci_foreach_element(&p->sections, e)
        {
                s = uci_to_section(e);
@@ -203,112 +308,68 @@ fw3_load_ipsets(struct fw3_state *state, struct uci_package *p)
                if (strcmp(s->type, "ipset"))
                        continue;
 
-               ipset = malloc(sizeof(*ipset));
+               ipset = fw3_alloc_ipset(state);
 
                if (!ipset)
                        continue;
 
-               memset(ipset, 0, sizeof(*ipset));
+               if (!fw3_parse_options(ipset, fw3_ipset_opts, s))
+                       warn_elem(e, "has invalid options");
 
-               INIT_LIST_HEAD(&ipset->datatypes);
-               INIT_LIST_HEAD(&ipset->iprange);
+               if (!check_ipset(state, ipset, e))
+                       fw3_free_ipset(ipset);
+       }
+}
 
-               fw3_parse_options(ipset, ipset_opts, ARRAY_SIZE(ipset_opts), s);
 
-               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);
-                       continue;
-               }
+static void
+load_file(struct fw3_ipset *ipset)
+{
+       FILE *f;
+       char line[128];
+
+       if (!ipset->loadfile)
+               return;
+
+       info("   * Loading file %s", ipset->loadfile);
+
+       f = fopen(ipset->loadfile, "r");
 
-               fw3_free_ipset(ipset);
+       if (!f) {
+               info("     ! Skipping due to open error: %s", strerror(errno));
+               return;
        }
-}
 
+       while (fgets(line, sizeof(line), f))
+               fw3_pr("add %s %s", ipset->name, line);
+
+       fclose(f);
+}
 
 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_setentry *entry;
        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",
-       };
-
-       const char *families[] = {
-               "(bug)",
-               "inet",
-               "inet6",
-       };
-
-       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)
        {
@@ -316,9 +377,6 @@ create_ipset(struct fw3_ipset *ipset)
                       ipset->portrange.port_min, ipset->portrange.port_max);
        }
 
-       if (ipset->family != FW3_FAMILY_ANY)
-               fw3_pr(" family %s", families[ipset->family]);
-
        if (ipset->timeout > 0)
                fw3_pr(" timeout %u", ipset->timeout);
 
@@ -332,68 +390,150 @@ create_ipset(struct fw3_ipset *ipset)
                fw3_pr(" hashsize %u", ipset->hashsize);
 
        fw3_pr("\n");
+
+       list_for_each_entry(entry, &ipset->entries, list)
+               fw3_pr("add %s %s\n", ipset->name, entry->value);
+
+       load_file(ipset);
 }
 
 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 fw3_state *state)
 {
+       int tries;
+       bool exec = false;
        struct fw3_ipset *ipset;
 
-       if (state->disable_ipsets)
-               return;
-
-       info("Destroying ipsets ...");
-
+       /* destroy ipsets */
        list_for_each_entry(ipset, &state->ipsets, list)
        {
-               if (ipset->external && *ipset->external)
-                       continue;
+               if (!exec)
+               {
+                       exec = fw3_command_pipe(false, "ipset", "-exist", "-");
 
-               info(" * %s", ipset->name);
+                       if (!exec)
+                               return;
+               }
+
+               info(" * Deleting ipset %s", ipset->name);
 
                fw3_pr("flush %s\n", ipset->name);
                fw3_pr("destroy %s\n", ipset->name);
        }
 
-       fw3_pr("quit\n");
-}
+       if (exec)
+       {
+               fw3_pr("quit\n");
+               fw3_command_close();
+       }
 
-void
-fw3_free_ipset(struct fw3_ipset *ipset)
-{
-       fw3_free_list(&ipset->datatypes);
-       fw3_free_list(&ipset->iprange);
+       /* wait for ipsets to disappear */
+       list_for_each_entry(ipset, &state->ipsets, list)
+       {
+               if (ipset->external)
+                       continue;
 
-       free(ipset);
+               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;
+}