firewall3: Improve ipset support
[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                     (reload_set && !ipset->reload_set))
432                         continue;
433
434                 if (ipset->external)
435                         continue;
436
437                 if (!exec)
438                 {
439                         exec = fw3_command_pipe(false, "ipset", "-exist", "-");
440
441                         if (!exec)
442                                 return;
443                 }
444
445                 create_ipset(ipset, state);
446         }
447
448         if (exec)
449         {
450                 fw3_pr("quit\n");
451                 fw3_command_close();
452         }
453
454         /* wait a little expontially for ipsets to appear */
455         list_for_each_entry(ipset, &state->ipsets, list)
456         {
457                 if (ipset->external)
458                         continue;
459
460                 delay = 5;
461                 for (tries = 0; !fw3_check_ipset(ipset) && tries < 10; tries++)
462                         usleep(delay<<1);
463         }
464 }
465
466 void
467 fw3_destroy_ipsets(struct fw3_state *state, enum fw3_family family,
468                    bool reload_set)
469 {
470         unsigned int delay, tries;
471         bool exec = false;
472         struct fw3_ipset *ipset;
473
474         if (state->disable_ipsets)
475                 return;
476
477         /* destroy ipsets */
478         list_for_each_entry(ipset, &state->ipsets, list)
479         {
480                 if (ipset->family != family ||
481                     (reload_set && !ipset->reload_set))
482                         continue;
483
484                 if (!exec)
485                 {
486                         exec = fw3_command_pipe(false, "ipset", "-exist", "-");
487
488                         if (!exec)
489                                 return;
490                 }
491
492                 info(" * Deleting ipset %s", ipset->name);
493
494                 fw3_pr("flush %s\n", ipset->name);
495                 fw3_pr("destroy %s\n", ipset->name);
496         }
497
498         if (exec)
499         {
500                 fw3_pr("quit\n");
501                 fw3_command_close();
502         }
503
504         /* wait for ipsets to disappear */
505         list_for_each_entry(ipset, &state->ipsets, list)
506         {
507                 if (ipset->external)
508                         continue;
509
510                 delay = 5;
511                 for (tries = 0; fw3_check_ipset(ipset) && tries < 10; tries++)
512                         usleep(delay<<1);
513         }
514 }
515
516 struct fw3_ipset *
517 fw3_lookup_ipset(struct fw3_state *state, const char *name)
518 {
519         struct fw3_ipset *s;
520
521         if (list_empty(&state->ipsets))
522                 return NULL;
523
524         list_for_each_entry(s, &state->ipsets, list)
525         {
526                 if (strcmp(s->name, name))
527                         continue;
528
529                 return s;
530         }
531
532         return NULL;
533 }
534
535 bool
536 fw3_check_ipset(struct fw3_ipset *set)
537 {
538         bool rv = false;
539
540         socklen_t sz;
541         int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
542         struct ip_set_req_version req_ver;
543         struct ip_set_req_get_set req_name;
544
545         if (s < 0 || fcntl(s, F_SETFD, FD_CLOEXEC))
546                 goto out;
547
548         sz = sizeof(req_ver);
549         req_ver.op = IP_SET_OP_VERSION;
550
551         if (getsockopt(s, SOL_IP, SO_IP_SET, &req_ver, &sz))
552                 goto out;
553
554         sz = sizeof(req_name);
555         req_name.op = IP_SET_OP_GET_BYNAME;
556         req_name.version = req_ver.version;
557         snprintf(req_name.set.name, IPSET_MAXNAMELEN - 1, "%s",
558                  set->external ? set->external : set->name);
559
560         if (getsockopt(s, SOL_IP, SO_IP_SET, &req_name, &sz))
561                 goto out;
562
563         rv = ((sz == sizeof(req_name)) && (req_name.set.index != IPSET_INVALID_ID));
564
565 out:
566         if (s >= 0)
567                 close(s);
568
569         return rv;
570 }