6d33f905ffbddc1c662dd689fb4e5c5a92ca8768
[project/firewall3.git] / redirects.c
1 /*
2  * firewall3 - 3rd OpenWrt UCI firewall implementation
3  *
4  *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
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 "redirects.h"
20
21
22 const struct fw3_option fw3_redirect_opts[] = {
23         FW3_OPT("enabled",             bool,      redirect,     enabled),
24
25         FW3_OPT("name",                string,    redirect,     name),
26         FW3_OPT("family",              family,    redirect,     family),
27
28         FW3_OPT("src",                 device,    redirect,     src),
29         FW3_OPT("dest",                device,    redirect,     dest),
30
31         FW3_OPT("ipset",               setmatch,  redirect,     ipset),
32
33         FW3_LIST("proto",              protocol,  redirect,     proto),
34
35         FW3_OPT("src_ip",              network,   redirect,     ip_src),
36         FW3_LIST("src_mac",            mac,       redirect,     mac_src),
37         FW3_OPT("src_port",            port,      redirect,     port_src),
38
39         FW3_OPT("src_dip",             network,   redirect,     ip_dest),
40         FW3_OPT("src_dport",           port,      redirect,     port_dest),
41
42         FW3_OPT("dest_ip",             network,   redirect,     ip_redir),
43         FW3_OPT("dest_port",           port,      redirect,     port_redir),
44
45         FW3_OPT("extra",               string,    redirect,     extra),
46
47         FW3_OPT("utc_time",            bool,      redirect,     time.utc),
48         FW3_OPT("start_date",          date,      redirect,     time.datestart),
49         FW3_OPT("stop_date",           date,      redirect,     time.datestop),
50         FW3_OPT("start_time",          time,      redirect,     time.timestart),
51         FW3_OPT("stop_time",           time,      redirect,     time.timestop),
52         FW3_OPT("weekdays",            weekdays,  redirect,     time.weekdays),
53         FW3_OPT("monthdays",           monthdays, redirect,     time.monthdays),
54
55         FW3_OPT("mark",                mark,      redirect,     mark),
56
57         FW3_OPT("reflection",          bool,      redirect,     reflection),
58         FW3_OPT("reflection_src",      reflection_source,
59                                                   redirect,     reflection_src),
60
61         FW3_OPT("target",              target,    redirect,     target),
62
63         { }
64 };
65
66
67 static bool
68 check_families(struct uci_element *e, struct fw3_redirect *r)
69 {
70         if (r->family == FW3_FAMILY_ANY)
71                 return true;
72
73         if (r->_src && r->_src->family && r->_src->family != r->family)
74         {
75                 warn_elem(e, "refers to source zone with different family");
76                 return false;
77         }
78
79         if (r->_dest && r->_dest->family && r->_dest->family != r->family)
80         {
81                 warn_elem(e, "refers to destination zone with different family");
82                 return false;
83         }
84
85         if (r->ipset.ptr && r->ipset.ptr->family &&
86             r->ipset.ptr->family != r->family)
87         {
88                 warn_elem(e, "refers to ipset with different family");
89                 return false;
90         }
91
92         if (r->ip_src.family && r->ip_src.family != r->family)
93         {
94                 warn_elem(e, "uses source ip with different family");
95                 return false;
96         }
97
98         if (r->ip_dest.family && r->ip_dest.family != r->family)
99         {
100                 warn_elem(e, "uses destination ip with different family");
101                 return false;
102         }
103
104         if (r->ip_redir.family && r->ip_redir.family != r->family)
105         {
106                 warn_elem(e, "uses redirect ip with different family");
107                 return false;
108         }
109
110         return true;
111 }
112
113 static bool
114 compare_addr(struct fw3_address *a, struct fw3_address *b)
115 {
116         uint32_t mask;
117
118         if (a->family != FW3_FAMILY_V4)
119                 return false;
120
121         mask = ~((1 << (32 - a->mask)) - 1);
122
123         return ((a->address.v4.s_addr & mask) == (b->address.v4.s_addr & mask));
124 }
125
126 static bool
127 resolve_dest(struct uci_element *e, struct fw3_redirect *redir,
128              struct fw3_state *state)
129 {
130         struct fw3_zone *zone;
131         struct fw3_address *addr;
132         struct list_head *addrs;
133
134         if (!redir->ip_redir.set)
135                 return false;
136
137         list_for_each_entry(zone, &state->zones, list)
138         {
139                 addrs = fw3_resolve_zone_addresses(zone);
140
141                 if (!addrs)
142                         continue;
143
144                 list_for_each_entry(addr, addrs, list)
145                 {
146                         if (!compare_addr(addr, &redir->ip_redir))
147                                 continue;
148
149                         strncpy(redir->dest.name, zone->name, sizeof(redir->dest.name));
150                         redir->dest.set = true;
151                         redir->_dest = zone;
152
153                         break;
154                 }
155
156                 fw3_free_list(addrs);
157
158                 if (redir->_dest)
159                         return true;
160         }
161
162         return false;
163 }
164
165 void
166 fw3_load_redirects(struct fw3_state *state, struct uci_package *p)
167 {
168         struct uci_section *s;
169         struct uci_element *e;
170         struct fw3_redirect *redir;
171
172         bool valid;
173
174         INIT_LIST_HEAD(&state->redirects);
175
176         uci_foreach_element(&p->sections, e)
177         {
178                 s = uci_to_section(e);
179
180                 if (strcmp(s->type, "redirect"))
181                         continue;
182
183                 redir = malloc(sizeof(*redir));
184
185                 if (!redir)
186                         continue;
187
188                 memset(redir, 0, sizeof(*redir));
189
190                 INIT_LIST_HEAD(&redir->proto);
191                 INIT_LIST_HEAD(&redir->mac_src);
192
193                 redir->enabled = true;
194                 redir->reflection = true;
195
196                 valid = false;
197
198                 fw3_parse_options(redir, fw3_redirect_opts, s);
199
200                 if (!redir->enabled)
201                 {
202                         fw3_free_redirect(redir);
203                         continue;
204                 }
205
206                 if (redir->src.invert)
207                 {
208                         warn_elem(e, "must not have an inverted source");
209                         fw3_free_redirect(redir);
210                         continue;
211                 }
212                 else if (redir->src.set && !redir->src.any &&
213                          !(redir->_src = fw3_lookup_zone(state, redir->src.name)))
214                 {
215                         warn_elem(e, "refers to not existing zone '%s'", redir->src.name);
216                         fw3_free_redirect(redir);
217                         continue;
218                 }
219                 else if (redir->dest.set && !redir->dest.any &&
220                          !(redir->_dest = fw3_lookup_zone(state, redir->dest.name)))
221                 {
222                         warn_elem(e, "refers to not existing zone '%s'", redir->dest.name);
223                         fw3_free_redirect(redir);
224                         continue;
225                 }
226                 else if (redir->ipset.set && state->disable_ipsets)
227                 {
228                         warn_elem(e, "skipped due to disabled ipset support");
229                         fw3_free_redirect(redir);
230                         continue;
231                 }
232                 else if (redir->ipset.set &&
233                          !(redir->ipset.ptr = fw3_lookup_ipset(state, redir->ipset.name)))
234                 {
235                         warn_elem(e, "refers to unknown ipset '%s'", redir->ipset.name);
236                         fw3_free_redirect(redir);
237                         continue;
238                 }
239
240                 if (!check_families(e, redir))
241                 {
242                         fw3_free_redirect(redir);
243                         continue;
244                 }
245
246                 if (redir->target == FW3_FLAG_UNSPEC)
247                 {
248                         warn_elem(e, "has no target specified, defaulting to DNAT");
249                         redir->target = FW3_FLAG_DNAT;
250                 }
251                 else if (redir->target < FW3_FLAG_DNAT)
252                 {
253                         warn_elem(e, "has invalid target specified, defaulting to DNAT");
254                         redir->target = FW3_FLAG_DNAT;
255                 }
256
257                 if (redir->target == FW3_FLAG_DNAT)
258                 {
259                         if (redir->src.any)
260                                 warn_elem(e, "must not have source '*' for DNAT target");
261                         else if (!redir->_src)
262                                 warn_elem(e, "has no source specified");
263                         else
264                         {
265                                 set(redir->_src->flags, FW3_FAMILY_V4, redir->target);
266                                 redir->_src->conntrack = true;
267                                 valid = true;
268                         }
269
270                         if (!redir->dest.set && resolve_dest(e, redir, state))
271                         {
272                                 warn_elem(e, "does not specify a destination, assuming '%s'",
273                                           redir->dest.name);
274                         }
275
276                         if (redir->reflection && redir->_dest && redir->_src->masq)
277                         {
278                                 set(redir->_dest->flags, FW3_FAMILY_V4, FW3_FLAG_ACCEPT);
279                                 set(redir->_dest->flags, FW3_FAMILY_V4, FW3_FLAG_DNAT);
280                                 set(redir->_dest->flags, FW3_FAMILY_V4, FW3_FLAG_SNAT);
281                         }
282                 }
283                 else
284                 {
285                         if (redir->dest.any)
286                                 warn_elem(e, "must not have destination '*' for SNAT target");
287                         else if (!redir->_dest)
288                                 warn_elem(e, "has no destination specified");
289                         else if (!redir->ip_dest.set)
290                                 warn_elem(e, "has no src_dip option specified");
291                         else if (!list_empty(&redir->mac_src))
292                                 warn_elem(e, "must not use 'src_mac' option for SNAT target");
293                         else
294                         {
295                                 set(redir->_dest->flags, FW3_FAMILY_V4, redir->target);
296                                 redir->_dest->conntrack = true;
297                                 valid = true;
298                         }
299                 }
300
301                 if (list_empty(&redir->proto))
302                 {
303                         warn_elem(e, "does not specify a protocol, assuming TCP+UDP");
304                         fw3_parse_protocol(&redir->proto, "tcpudp", true);
305                 }
306
307                 if (!valid)
308                 {
309                         fw3_free_redirect(redir);
310                         continue;
311                 }
312
313                 if (!redir->port_redir.set)
314                         redir->port_redir = redir->port_dest;
315
316                 list_add_tail(&redir->list, &state->redirects);
317         }
318 }
319
320 static void
321 append_chain_nat(struct fw3_ipt_rule *r, struct fw3_redirect *redir)
322 {
323         if (redir->target == FW3_FLAG_DNAT)
324                 fw3_ipt_rule_append(r, "zone_%s_prerouting", redir->src.name);
325         else
326                 fw3_ipt_rule_append(r, "zone_%s_postrouting", redir->dest.name);
327 }
328
329 static void
330 set_snat_dnat(struct fw3_ipt_rule *r, enum fw3_flag target,
331               struct fw3_address *addr, struct fw3_port *port)
332 {
333         char buf[sizeof("255.255.255.255:65535-65535\0")];
334
335         buf[0] = '\0';
336
337         if (addr && addr->set)
338         {
339                 inet_ntop(AF_INET, &addr->address.v4, buf, sizeof(buf));
340         }
341
342         if (port && port->set)
343         {
344                 if (port->port_min == port->port_max)
345                         sprintf(buf + strlen(buf), ":%u", port->port_min);
346                 else
347                         sprintf(buf + strlen(buf), ":%u-%u",
348                                 port->port_min, port->port_max);
349         }
350
351         if (target == FW3_FLAG_DNAT)
352         {
353                 fw3_ipt_rule_target(r, "DNAT");
354                 fw3_ipt_rule_addarg(r, false, "--to-destination", buf);
355         }
356         else
357         {
358                 fw3_ipt_rule_target(r, "SNAT");
359                 fw3_ipt_rule_addarg(r, false, "--to-source", buf);
360         }
361 }
362
363 static void
364 set_target_nat(struct fw3_ipt_rule *r, struct fw3_redirect *redir)
365 {
366         if (redir->target == FW3_FLAG_DNAT)
367                 set_snat_dnat(r, redir->target, &redir->ip_redir, &redir->port_redir);
368         else
369                 set_snat_dnat(r, redir->target, &redir->ip_dest, &redir->port_dest);
370 }
371
372 static void
373 append_chain_filter(struct fw3_ipt_rule *r, struct fw3_redirect *redir)
374 {
375         if (redir->target == FW3_FLAG_DNAT)
376         {
377                 /* XXX: check for local ip */
378                 if (!redir->ip_redir.set)
379                         fw3_ipt_rule_append(r, "zone_%s_input", redir->src.name);
380                 else
381                         fw3_ipt_rule_append(r, "zone_%s_forward", redir->src.name);
382         }
383         else
384         {
385                 if (redir->src.set && !redir->src.any)
386                         fw3_ipt_rule_append(r, "zone_%s_forward", redir->src.name);
387                 else
388                         fw3_ipt_rule_append(r, "delegate_forward");
389         }
390 }
391
392 static void
393 set_target_filter(struct fw3_ipt_rule *r, struct fw3_redirect *redir)
394 {
395         /* XXX: check for local ip */
396         if (redir->target == FW3_FLAG_DNAT && !redir->ip_redir.set)
397                 fw3_ipt_rule_extra(r, "-m conntrack --ctstate DNAT");
398
399         fw3_ipt_rule_target(r, "ACCEPT");
400 }
401
402 static void
403 set_comment(struct fw3_ipt_rule *r, const char *name, int num, bool ref)
404 {
405         if (name)
406         {
407                 if (ref)
408                         fw3_ipt_rule_comment(r, "%s (reflection)", name);
409                 else
410                         fw3_ipt_rule_comment(r, name);
411         }
412         else
413         {
414                 if (ref)
415                         fw3_ipt_rule_comment(r, "@redirect[%u] (reflection)", num);
416                 else
417                         fw3_ipt_rule_comment(r, "@redirect[%u]", num);
418         }
419 }
420
421 static void
422 print_redirect(struct fw3_ipt_handle *h, struct fw3_state *state,
423                struct fw3_redirect *redir, int num,
424                struct fw3_protocol *proto, struct fw3_mac *mac)
425 {
426         struct fw3_ipt_rule *r;
427         struct fw3_address *src, *dst;
428         struct fw3_port *spt, *dpt;
429
430         switch (h->table)
431         {
432         case FW3_TABLE_NAT:
433                 src = &redir->ip_src;
434                 dst = &redir->ip_dest;
435                 spt = &redir->port_src;
436                 dpt = &redir->port_dest;
437
438                 if (redir->target == FW3_FLAG_SNAT)
439                 {
440                         dst = &redir->ip_redir;
441                         dpt = &redir->port_redir;
442                 }
443
444                 r = fw3_ipt_rule_create(h, proto, NULL, NULL, src, dst);
445                 fw3_ipt_rule_sport_dport(r, spt, dpt);
446                 fw3_ipt_rule_mac(r, mac);
447                 fw3_ipt_rule_ipset(r, &redir->ipset);
448                 fw3_ipt_rule_time(r, &redir->time);
449                 fw3_ipt_rule_mark(r, &redir->mark);
450                 set_target_nat(r, redir);
451                 fw3_ipt_rule_extra(r, redir->extra);
452                 set_comment(r, redir->name, num, false);
453                 append_chain_nat(r, redir);
454                 break;
455
456         case FW3_TABLE_FILTER:
457                 src = &redir->ip_src;
458                 dst = &redir->ip_redir;
459                 spt = &redir->port_src;
460                 dpt = &redir->port_redir;
461
462                 r = fw3_ipt_rule_create(h, proto, NULL, NULL, src, dst);
463                 fw3_ipt_rule_sport_dport(r, spt, dpt);
464                 fw3_ipt_rule_mac(r, mac);
465                 fw3_ipt_rule_ipset(r, &redir->ipset);
466                 fw3_ipt_rule_time(r, &redir->time);
467                 fw3_ipt_rule_mark(r, &redir->mark);
468                 set_target_filter(r, redir);
469                 fw3_ipt_rule_extra(r, redir->extra);
470                 set_comment(r, redir->name, num, false);
471                 append_chain_filter(r, redir);
472                 break;
473
474         default:
475                 break;
476         }
477 }
478
479 static void
480 print_reflection(struct fw3_ipt_handle *h, struct fw3_state *state,
481                  struct fw3_redirect *redir, int num,
482                  struct fw3_protocol *proto, struct fw3_address *ra,
483                  struct fw3_address *ia, struct fw3_address *ea)
484 {
485         struct fw3_ipt_rule *r;
486
487         switch (h->table)
488         {
489         case FW3_TABLE_NAT:
490                 r = fw3_ipt_rule_create(h, proto, NULL, NULL, ia, ea);
491                 fw3_ipt_rule_sport_dport(r, NULL, &redir->port_dest);
492                 fw3_ipt_rule_time(r, &redir->time);
493                 set_comment(r, redir->name, num, true);
494                 set_snat_dnat(r, FW3_FLAG_DNAT, &redir->ip_redir, &redir->port_redir);
495                 fw3_ipt_rule_append(r, "zone_%s_prerouting", redir->dest.name);
496
497                 r = fw3_ipt_rule_create(h, proto, NULL, NULL, ia, &redir->ip_redir);
498                 fw3_ipt_rule_sport_dport(r, NULL, &redir->port_redir);
499                 fw3_ipt_rule_time(r, &redir->time);
500                 set_comment(r, redir->name, num, true);
501                 set_snat_dnat(r, FW3_FLAG_SNAT, ra, NULL);
502                 fw3_ipt_rule_append(r, "zone_%s_postrouting", redir->dest.name);
503                 break;
504
505         case FW3_TABLE_FILTER:
506                 r = fw3_ipt_rule_create(h, proto, NULL, NULL, ia, &redir->ip_redir);
507                 fw3_ipt_rule_sport_dport(r, NULL, &redir->port_redir);
508                 fw3_ipt_rule_time(r, &redir->time);
509                 set_comment(r, redir->name, num, true);
510                 fw3_ipt_rule_target(r, "zone_%s_dest_ACCEPT", redir->dest.name);
511                 fw3_ipt_rule_append(r, "zone_%s_forward", redir->dest.name);
512                 break;
513
514         default:
515                 break;
516         }
517 }
518
519 static void
520 expand_redirect(struct fw3_ipt_handle *handle, struct fw3_state *state,
521                 struct fw3_redirect *redir, int num)
522 {
523         struct list_head *ext_addrs, *int_addrs;
524         struct fw3_address *ext_addr, *int_addr, ref_addr;
525         struct fw3_protocol *proto;
526         struct fw3_mac *mac;
527
528         if (redir->name)
529                 info("   * Redirect '%s'", redir->name);
530         else
531                 info("   * Redirect #%u", num);
532
533         if (!fw3_is_family(redir->_src, handle->family) ||
534                 !fw3_is_family(redir->_dest, handle->family))
535         {
536                 info("     ! Skipping due to different family of zone");
537                 return;
538         }
539
540         if (!fw3_is_family(&redir->ip_src, handle->family) ||
541             !fw3_is_family(&redir->ip_dest, handle->family) ||
542                 !fw3_is_family(&redir->ip_redir, handle->family))
543         {
544                 if (!redir->ip_src.resolved ||
545                     !redir->ip_dest.resolved ||
546                     !redir->ip_redir.resolved)
547                         info("     ! Skipping due to different family of ip address");
548
549                 return;
550         }
551
552         if (redir->ipset.ptr)
553         {
554                 if (!fw3_is_family(redir->ipset.ptr, handle->family))
555                 {
556                         info("     ! Skipping due to different family in ipset");
557                         return;
558                 }
559
560                 if (!fw3_check_ipset(redir->ipset.ptr))
561                 {
562                         info("     ! Skipping due to missing ipset '%s'",
563                              redir->ipset.ptr->external ?
564                                         redir->ipset.ptr->external : redir->ipset.ptr->name);
565                         return;
566                 }
567
568                 set(redir->ipset.ptr->flags, handle->family, handle->family);
569         }
570
571         fw3_foreach(proto, &redir->proto)
572         fw3_foreach(mac, &redir->mac_src)
573                 print_redirect(handle, state, redir, num, proto, mac);
574
575         /* reflection rules */
576         if (redir->target != FW3_FLAG_DNAT || !redir->reflection)
577                 return;
578
579         if (!redir->_dest || !redir->_src->masq)
580                 return;
581
582         ext_addrs = fw3_resolve_zone_addresses(redir->_src);
583         int_addrs = fw3_resolve_zone_addresses(redir->_dest);
584
585         if (!ext_addrs || !int_addrs)
586                 goto out;
587
588         list_for_each_entry(ext_addr, ext_addrs, list)
589         {
590                 if (!fw3_is_family(ext_addr, handle->family))
591                         continue;
592
593                 list_for_each_entry(int_addr, int_addrs, list)
594                 {
595                         if (!fw3_is_family(int_addr, handle->family))
596                                 continue;
597
598                         fw3_foreach(proto, &redir->proto)
599                         {
600                                 if (!proto || (proto->protocol != 6 && proto->protocol != 17))
601                                         continue;
602
603                                 if (redir->reflection_src == FW3_REFLECTION_INTERNAL)
604                                         ref_addr = *int_addr;
605                                 else
606                                         ref_addr = *ext_addr;
607
608                                 ref_addr.mask = 32;
609                                 ext_addr->mask = 32;
610
611                                 print_reflection(handle, state, redir, num, proto,
612                                                                  &ref_addr, int_addr, ext_addr);
613                         }
614                 }
615         }
616
617 out:
618         fw3_free_list(ext_addrs);
619         fw3_free_list(int_addrs);
620 }
621
622 void
623 fw3_print_redirects(struct fw3_ipt_handle *handle, struct fw3_state *state)
624 {
625         int num = 0;
626         struct fw3_redirect *redir;
627
628         if (handle->family == FW3_FAMILY_V6)
629                 return;
630
631         if (handle->table != FW3_TABLE_FILTER && handle->table != FW3_TABLE_NAT)
632                 return;
633
634         list_for_each_entry(redir, &state->redirects, list)
635                 expand_redirect(handle, state, redir, num++);
636 }