firewall3: ipset: Handle reload_set properly
[oweals/firewall3.git] / ipsets.c
1 /*
2  * firewall3 - 3rd OpenWrt UCI firewall implementation
3  *
4  *   Copyright (C) 2013 Jo-Philipp Wich <jo@mein.io>
5  *
6  * Permission to use, copy, modify, and/or distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18
19 #include "ipsets.h"
20
21
22 const struct fw3_option fw3_ipset_opts[] = {
23         FW3_OPT("enabled",       bool,           ipset,     enabled),
24         FW3_OPT("reload_set",    bool,           ipset,     reload_set),
25         FW3_OPT("counters",      bool,           ipset,     counters),
26         FW3_OPT("comment",       bool,           ipset,     comment),
27
28         FW3_OPT("name",          string,         ipset,     name),
29         FW3_OPT("family",        family,         ipset,     family),
30
31         FW3_OPT("storage",       ipset_method,   ipset,     method),
32         FW3_LIST("match",        ipset_datatype, ipset,     datatypes),
33
34         FW3_OPT("iprange",       address,        ipset,     iprange),
35         FW3_OPT("portrange",     port,           ipset,     portrange),
36
37         FW3_OPT("netmask",       int,            ipset,     netmask),
38         FW3_OPT("maxelem",       int,            ipset,     maxelem),
39         FW3_OPT("hashsize",      int,            ipset,     hashsize),
40         FW3_OPT("timeout",       int,            ipset,     timeout),
41
42         FW3_OPT("external",      string,         ipset,     external),
43
44         FW3_LIST("entry",        setentry,       ipset,     entries),
45         FW3_OPT("loadfile",      string,         ipset,     loadfile),
46
47         { }
48 };
49
50 #define T(m, t1, t2, t3, r, o) \
51         { FW3_IPSET_METHOD_##m, \
52           FW3_IPSET_TYPE_##t1 | (FW3_IPSET_TYPE_##t2 << 8) | (FW3_IPSET_TYPE_##t3 << 16), \
53           r, o }
54
55 enum ipset_optflag {
56         OPT_IPRANGE   = (1 << 0),
57         OPT_PORTRANGE = (1 << 1),
58         OPT_NETMASK   = (1 << 2),
59         OPT_HASHSIZE  = (1 << 3),
60         OPT_MAXELEM   = (1 << 4),
61         OPT_FAMILY    = (1 << 5),
62 };
63
64 struct ipset_type {
65         enum fw3_ipset_method method;
66         uint32_t types;
67         uint8_t required;
68         uint8_t optional;
69 };
70
71 static struct ipset_type ipset_types[] = {
72         T(BITMAP, IP,   UNSPEC, UNSPEC, OPT_IPRANGE, OPT_NETMASK),
73         T(BITMAP, IP,   MAC,    UNSPEC, OPT_IPRANGE, 0),
74         T(BITMAP, PORT, UNSPEC, UNSPEC, OPT_PORTRANGE, 0),
75
76         T(HASH,   IP,   UNSPEC, UNSPEC, 0,
77           OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM | OPT_NETMASK),
78         T(HASH,   NET,  UNSPEC, UNSPEC, 0,
79           OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
80         T(HASH,   IP,   PORT,   UNSPEC, 0,
81           OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
82         T(HASH,   NET,  PORT,   UNSPEC, 0,
83           OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
84         T(HASH,   IP,   PORT,   IP,     0,
85           OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
86         T(HASH,   IP,   PORT,   NET,    0,
87           OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
88
89         T(LIST,   SET,  UNSPEC, UNSPEC, 0, OPT_MAXELEM),
90 };
91
92
93 static bool
94 check_types(struct uci_element *e, struct fw3_ipset *ipset)
95 {
96         int i = 0;
97         uint32_t typelist = 0;
98         struct fw3_ipset_datatype *type;
99
100         list_for_each_entry(type, &ipset->datatypes, list)
101         {
102                 if (i >= 3)
103                 {
104                         warn_section("ipset", ipset, e, "must not have more than 3 datatypes assigned");
105                         return false;
106                 }
107
108                 typelist |= (type->type << (i++ * 8));
109         }
110
111         /* find a suitable storage method if none specified */
112         if (ipset->method == FW3_IPSET_METHOD_UNSPEC)
113         {
114                 for (i = 0; i < ARRAY_SIZE(ipset_types); i++)
115                 {
116                         /* skip type for v6 if it does not support family */
117                         if (ipset->family != FW3_FAMILY_V4 &&
118                             !(ipset_types[i].optional & OPT_FAMILY))
119                                 continue;
120
121                         if (ipset_types[i].types == typelist)
122                         {
123                                 ipset->method = ipset_types[i].method;
124
125                                 warn_section("ipset", ipset, e, "defines no storage method, assuming '%s'",
126                                         fw3_ipset_method_names[ipset->method]);
127
128                                 break;
129                         }
130                 }
131         }
132
133         //typelist |= ipset->method;
134
135         for (i = 0; i < ARRAY_SIZE(ipset_types); i++)
136         {
137                 if (ipset_types[i].method == ipset->method &&
138                     ipset_types[i].types == typelist)
139                 {
140                         if (!ipset->external)
141                         {
142                                 if ((ipset_types[i].required & OPT_IPRANGE) &&
143                                         !ipset->iprange.set)
144                                 {
145                                         warn_section("ipset", ipset, e, "requires an ip range");
146                                         return false;
147                                 }
148
149                                 if ((ipset_types[i].required & OPT_PORTRANGE) &&
150                                     !ipset->portrange.set)
151                                 {
152                                         warn_section("ipset", ipset, e, "requires a port range");
153                                         return false;
154                                 }
155
156                                 if (!(ipset_types[i].required & OPT_IPRANGE) &&
157                                     ipset->iprange.set)
158                                 {
159                                         warn_section("ipset", ipset, e, "iprange ignored");
160                                         ipset->iprange.set = false;
161                                 }
162
163                                 if (!(ipset_types[i].required & OPT_PORTRANGE) &&
164                                     ipset->portrange.set)
165                                 {
166                                         warn_section("ipset", ipset, e, "portrange ignored");
167                                         ipset->portrange.set = false;
168                                 }
169
170                                 if (!(ipset_types[i].optional & OPT_NETMASK) &&
171                                     ipset->netmask > 0)
172                                 {
173                                         warn_section("ipset", ipset, e, "netmask ignored");
174                                         ipset->netmask = 0;
175                                 }
176
177                                 if (!(ipset_types[i].optional & OPT_HASHSIZE) &&
178                                     ipset->hashsize > 0)
179                                 {
180                                         warn_section("ipset", ipset, e, "hashsize ignored");
181                                         ipset->hashsize = 0;
182                                 }
183
184                                 if (!(ipset_types[i].optional & OPT_MAXELEM) &&
185                                     ipset->maxelem > 0)
186                                 {
187                                         warn_section("ipset", ipset, e, "maxelem ignored");
188                                         ipset->maxelem = 0;
189                                 }
190
191                                 if (!(ipset_types[i].optional & OPT_FAMILY) &&
192                                     ipset->family != FW3_FAMILY_V4)
193                                 {
194                                         warn_section("ipset", ipset, e, "family ignored");
195                                         ipset->family = FW3_FAMILY_V4;
196                                 }
197                         }
198
199                         return true;
200                 }
201         }
202
203         warn_section("ipset", ipset, e, "has an invalid combination of storage method and matches");
204         return false;
205 }
206
207 static bool
208 check_ipset(struct fw3_state *state, struct fw3_ipset *ipset, struct uci_element *e)
209 {
210         if (!ipset->enabled) {
211                 return false;
212         }
213
214         if (ipset->external)
215         {
216                 if (!*ipset->external)
217                         ipset->external = NULL;
218                 else if (!ipset->name)
219                         ipset->name = ipset->external;
220         }
221
222         if (!ipset->name || !*ipset->name)
223         {
224                 warn_section("ipset", ipset, e, "ipset must have a name assigned");
225         }
226         //else if (fw3_lookup_ipset(state, ipset->name) != NULL)
227         //{
228         //      warn_section("ipset", ipset, e, "has duplicated set name", ipset->name);
229         //}
230         else if (ipset->family == FW3_FAMILY_ANY)
231         {
232                 warn_section("ipset", ipset, e, "must not have family 'any'");
233         }
234         else if (ipset->iprange.set && ipset->family != ipset->iprange.family)
235         {
236                 warn_section("ipset", ipset, e, "has iprange of wrong address family");
237         }
238         else if (list_empty(&ipset->datatypes))
239         {
240                 warn_section("ipset", ipset, e, "has no datatypes assigned");
241         }
242         else if (check_types(e, ipset))
243         {
244                 return true;
245         }
246
247         return false;
248 }
249
250 static struct fw3_ipset *
251 fw3_alloc_ipset(struct fw3_state *state)
252 {
253         struct fw3_ipset *ipset;
254
255         ipset = calloc(1, sizeof(*ipset));
256         if (!ipset)
257                 return NULL;
258
259         INIT_LIST_HEAD(&ipset->datatypes);
260         INIT_LIST_HEAD(&ipset->entries);
261
262         ipset->comment    = false;
263         ipset->counters   = false;
264         ipset->enabled    = true;
265         ipset->family     = FW3_FAMILY_V4;
266         ipset->reload_set = false;
267
268         list_add_tail(&ipset->list, &state->ipsets);
269
270         return ipset;
271 }
272
273 void
274 fw3_load_ipsets(struct fw3_state *state, struct uci_package *p,
275                 struct blob_attr *a)
276 {
277         struct uci_section *s;
278         struct uci_element *e;
279         struct fw3_ipset *ipset;
280         struct blob_attr *entry;
281         unsigned rem;
282
283         INIT_LIST_HEAD(&state->ipsets);
284
285         if (state->disable_ipsets)
286                 return;
287
288         blob_for_each_attr(entry, a, rem)
289         {
290                 const char *type;
291                 const char *name = "ubus ipset";
292
293                 if (!fw3_attr_parse_name_type(entry, &name, &type))
294                         continue;
295
296                 if (strcmp(type, "ipset"))
297                         continue;
298
299                 ipset = fw3_alloc_ipset(state);
300                 if (!ipset)
301                         continue;
302
303                 if (!fw3_parse_blob_options(ipset, fw3_ipset_opts, entry, name))
304                 {
305                         warn_section("ipset", ipset, NULL, "skipped due to invalid options");
306                         fw3_free_ipset(ipset);
307                         continue;
308                 }
309
310                 if (!check_ipset(state, ipset, NULL))
311                         fw3_free_ipset(ipset);
312         }
313
314         uci_foreach_element(&p->sections, e)
315         {
316                 s = uci_to_section(e);
317
318                 if (strcmp(s->type, "ipset"))
319                         continue;
320
321                 ipset = fw3_alloc_ipset(state);
322
323                 if (!ipset)
324                         continue;
325
326                 if (!fw3_parse_options(ipset, fw3_ipset_opts, s))
327                         warn_elem(e, "has invalid options");
328
329                 if (!check_ipset(state, ipset, e))
330                         fw3_free_ipset(ipset);
331         }
332 }
333
334
335 static void
336 load_file(struct fw3_ipset *ipset)
337 {
338         FILE *f;
339         char line[128];
340
341         if (!ipset->loadfile)
342                 return;
343
344         info("   * Loading file %s", ipset->loadfile);
345
346         f = fopen(ipset->loadfile, "r");
347
348         if (!f) {
349                 info("     ! Skipping due to open error: %s", strerror(errno));
350                 return;
351         }
352
353         while (fgets(line, sizeof(line), f))
354                 fw3_pr("add %s %s", ipset->name, line);
355
356         fclose(f);
357 }
358
359 static void
360 create_ipset(struct fw3_ipset *ipset, struct fw3_state *state)
361 {
362         bool first = true;
363         struct fw3_setentry *entry;
364         struct fw3_ipset_datatype *type;
365
366         info(" * Creating ipset %s", ipset->name);
367
368         first = true;
369         fw3_pr("create %s %s", ipset->name, fw3_ipset_method_names[ipset->method]);
370
371         list_for_each_entry(type, &ipset->datatypes, list)
372         {
373                 fw3_pr("%c%s", first ? ':' : ',', fw3_ipset_type_names[type->type]);
374                 first = false;
375         }
376
377         if (ipset->method == FW3_IPSET_METHOD_HASH)
378                 fw3_pr(" family inet%s", (ipset->family == FW3_FAMILY_V4) ? "" : "6");
379
380         if (ipset->iprange.set)
381         {
382                 fw3_pr(" range %s", fw3_address_to_string(&ipset->iprange, false, true));
383         }
384         else if (ipset->portrange.set)
385         {
386                 fw3_pr(" range %u-%u",
387                        ipset->portrange.port_min, ipset->portrange.port_max);
388         }
389
390         if (ipset->timeout > 0)
391                 fw3_pr(" timeout %u", ipset->timeout);
392
393         if (ipset->maxelem > 0)
394                 fw3_pr(" maxelem %u", ipset->maxelem);
395
396         if (ipset->netmask > 0)
397                 fw3_pr(" netmask %u", ipset->netmask);
398
399         if (ipset->hashsize > 0)
400                 fw3_pr(" hashsize %u", ipset->hashsize);
401
402         if (ipset->counters)
403                 fw3_pr(" counters");
404
405         if (ipset->comment)
406                 fw3_pr(" comment");
407
408         fw3_pr("\n");
409
410         list_for_each_entry(entry, &ipset->entries, list)
411                 fw3_pr("add %s %s\n", ipset->name, entry->value);
412
413         load_file(ipset);
414 }
415
416 void
417 fw3_create_ipsets(struct fw3_state *state, enum fw3_family family,
418                   bool reload_set)
419 {
420         unsigned int delay, tries;
421         bool exec = false;
422         struct fw3_ipset *ipset;
423
424         if (state->disable_ipsets)
425                 return;
426
427         /* spawn ipsets */
428         list_for_each_entry(ipset, &state->ipsets, list)
429         {
430                 if (ipset->family != family)
431                         continue;
432
433                 if (ipset->external)
434                         continue;
435
436                 if (fw3_check_ipset(ipset) &&
437                     (reload_set && !ipset->reload_set))
438                         continue;
439
440                 if (!exec)
441                 {
442                         exec = fw3_command_pipe(false, "ipset", "-exist", "-");
443
444                         if (!exec)
445                                 return;
446                 }
447
448                 create_ipset(ipset, state);
449         }
450
451         if (exec)
452         {
453                 fw3_pr("quit\n");
454                 fw3_command_close();
455         }
456
457         /* wait a little expontially for ipsets to appear */
458         list_for_each_entry(ipset, &state->ipsets, list)
459         {
460                 if (ipset->external)
461                         continue;
462
463                 delay = 5;
464                 for (tries = 0; !fw3_check_ipset(ipset) && tries < 10; tries++)
465                         usleep(delay<<1);
466         }
467 }
468
469 void
470 fw3_destroy_ipsets(struct fw3_state *state, enum fw3_family family,
471                    bool reload_set)
472 {
473         unsigned int delay, tries;
474         bool exec = false;
475         struct fw3_ipset *ipset;
476
477         if (state->disable_ipsets)
478                 return;
479
480         /* destroy ipsets */
481         list_for_each_entry(ipset, &state->ipsets, list)
482         {
483                 if (ipset->family != family ||
484                     (reload_set && !ipset->reload_set))
485                         continue;
486
487                 if (!exec)
488                 {
489                         exec = fw3_command_pipe(false, "ipset", "-exist", "-");
490
491                         if (!exec)
492                                 return;
493                 }
494
495                 info(" * Deleting ipset %s", ipset->name);
496
497                 fw3_pr("flush %s\n", ipset->name);
498                 fw3_pr("destroy %s\n", ipset->name);
499         }
500
501         if (exec)
502         {
503                 fw3_pr("quit\n");
504                 fw3_command_close();
505         }
506
507         /* wait for ipsets to disappear */
508         list_for_each_entry(ipset, &state->ipsets, list)
509         {
510                 if (ipset->external)
511                         continue;
512
513                 delay = 5;
514                 for (tries = 0; fw3_check_ipset(ipset) && tries < 10; tries++)
515                         usleep(delay<<1);
516         }
517 }
518
519 struct fw3_ipset *
520 fw3_lookup_ipset(struct fw3_state *state, const char *name)
521 {
522         struct fw3_ipset *s;
523
524         if (list_empty(&state->ipsets))
525                 return NULL;
526
527         list_for_each_entry(s, &state->ipsets, list)
528         {
529                 if (strcmp(s->name, name))
530                         continue;
531
532                 return s;
533         }
534
535         return NULL;
536 }
537
538 bool
539 fw3_check_ipset(struct fw3_ipset *set)
540 {
541         bool rv = false;
542
543         socklen_t sz;
544         int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
545         struct ip_set_req_version req_ver;
546         struct ip_set_req_get_set req_name;
547
548         if (s < 0 || fcntl(s, F_SETFD, FD_CLOEXEC))
549                 goto out;
550
551         sz = sizeof(req_ver);
552         req_ver.op = IP_SET_OP_VERSION;
553
554         if (getsockopt(s, SOL_IP, SO_IP_SET, &req_ver, &sz))
555                 goto out;
556
557         sz = sizeof(req_name);
558         req_name.op = IP_SET_OP_GET_BYNAME;
559         req_name.version = req_ver.version;
560         snprintf(req_name.set.name, IPSET_MAXNAMELEN - 1, "%s",
561                  set->external ? set->external : set->name);
562
563         if (getsockopt(s, SOL_IP, SO_IP_SET, &req_name, &sz))
564                 goto out;
565
566         rv = ((sz == sizeof(req_name)) && (req_name.set.index != IPSET_INVALID_ID));
567
568 out:
569         if (s >= 0)
570                 close(s);
571
572         return rv;
573 }
574
575 void
576 fw3_ipsets_update_run_state(enum fw3_family family, struct fw3_state *run_state,
577                             struct fw3_state *cfg_state)
578 {
579         struct fw3_ipset *ipset_run, *ipset_cfg;
580         bool in_cfg;
581
582         list_for_each_entry(ipset_run, &run_state->ipsets, list) {
583                 if (ipset_run->family != family)
584                         continue;
585
586                 in_cfg = false;
587
588                 list_for_each_entry(ipset_cfg, &cfg_state->ipsets, list) {
589                         if (ipset_cfg->family != family)
590                                 continue;
591
592                         if (strlen(ipset_run->name) ==
593                             strlen(ipset_cfg->name) &&
594                             !strcmp(ipset_run->name, ipset_cfg->name)) {
595                                 in_cfg = true;
596                                 break;
597                         }
598                 }
599
600                 /* If a set is found in run_state, but not in cfg_state then the
601                  * set has been deleted/renamed. Set reload_set to true to force
602                  * the old set to be destroyed in the "stop" fase of the reload.
603                  * If the set is found, then copy the reload_set value from the
604                  * configuration state. This ensures that the elements are
605                  * always updated according to the configuration, and not the
606                  * runtime state (which the user might have forgotten).
607                  */
608                 if (!in_cfg)
609                         ipset_run->reload_set = true;
610                 else
611                         ipset_run->reload_set = ipset_cfg->reload_set;
612         }
613 }