unify object freeing
[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("name",                string,   redirect,     name),
24         FW3_OPT("family",              family,   redirect,     family),
25
26         FW3_OPT("src",                 device,   redirect,     src),
27         FW3_OPT("dest",                device,   redirect,     dest),
28
29         FW3_OPT("ipset",               device,   redirect,     ipset),
30
31         FW3_LIST("proto",              protocol, redirect,     proto),
32
33         FW3_OPT("src_ip",              address,  redirect,     ip_src),
34         FW3_LIST("src_mac",            mac,      redirect,     mac_src),
35         FW3_OPT("src_port",            port,     redirect,     port_src),
36
37         FW3_OPT("src_dip",             address,  redirect,     ip_dest),
38         FW3_OPT("src_dport",           port,     redirect,     port_dest),
39
40         FW3_OPT("dest_ip",             address,  redirect,     ip_redir),
41         FW3_OPT("dest_port",           port,     redirect,     port_redir),
42
43         FW3_OPT("extra",               string,   redirect,     extra),
44
45         FW3_OPT("reflection",          bool,     redirect,     reflection),
46
47         FW3_OPT("target",              target,   redirect,     target),
48
49         { }
50 };
51
52
53 static bool
54 check_families(struct uci_element *e, struct fw3_redirect *r)
55 {
56         if (r->family == FW3_FAMILY_ANY)
57                 return true;
58
59         if (r->_src && r->_src->family && r->_src->family != r->family)
60         {
61                 warn_elem(e, "refers to source zone with different family");
62                 return false;
63         }
64
65         if (r->_dest && r->_dest->family && r->_dest->family != r->family)
66         {
67                 warn_elem(e, "refers to destination zone with different family");
68                 return false;
69         }
70
71         if (r->_ipset && r->_ipset->family && r->_ipset->family != r->family)
72         {
73                 warn_elem(e, "refers to ipset with different family");
74                 return false;
75         }
76
77         if (r->ip_src.family && r->ip_src.family != r->family)
78         {
79                 warn_elem(e, "uses source ip with different family");
80                 return false;
81         }
82
83         if (r->ip_dest.family && r->ip_dest.family != r->family)
84         {
85                 warn_elem(e, "uses destination ip with different family");
86                 return false;
87         }
88
89         if (r->ip_redir.family && r->ip_redir.family != r->family)
90         {
91                 warn_elem(e, "uses redirect ip with different family");
92                 return false;
93         }
94
95         return true;
96 }
97
98 void
99 fw3_load_redirects(struct fw3_state *state, struct uci_package *p)
100 {
101         struct uci_section *s;
102         struct uci_element *e;
103         struct fw3_redirect *redir;
104
105         bool valid = false;
106
107         INIT_LIST_HEAD(&state->redirects);
108
109         uci_foreach_element(&p->sections, e)
110         {
111                 s = uci_to_section(e);
112
113                 if (strcmp(s->type, "redirect"))
114                         continue;
115
116                 redir = malloc(sizeof(*redir));
117
118                 if (!redir)
119                         continue;
120
121                 memset(redir, 0, sizeof(*redir));
122
123                 INIT_LIST_HEAD(&redir->proto);
124                 INIT_LIST_HEAD(&redir->mac_src);
125
126                 redir->reflection = true;
127
128                 fw3_parse_options(redir, fw3_redirect_opts, s);
129
130                 if (redir->src.invert)
131                 {
132                         warn_elem(e, "must not have an inverted source");
133                         fw3_free_redirect(redir);
134                         continue;
135                 }
136                 else if (redir->src.set && !redir->src.any &&
137                          !(redir->_src = fw3_lookup_zone(state, redir->src.name, false)))
138                 {
139                         warn_elem(e, "refers to not existing zone '%s'", redir->src.name);
140                         fw3_free_redirect(redir);
141                         continue;
142                 }
143                 else if (redir->dest.set && !redir->dest.any &&
144                          !(redir->_dest = fw3_lookup_zone(state, redir->dest.name, false)))
145                 {
146                         warn_elem(e, "refers to not existing zone '%s'", redir->dest.name);
147                         fw3_free_redirect(redir);
148                         continue;
149                 }
150                 else if (redir->ipset.set && state->disable_ipsets)
151                 {
152                         warn_elem(e, "skipped due to disabled ipset support");
153                         fw3_free_redirect(redir);
154                         continue;
155                 }
156                 else if (redir->ipset.set && !redir->ipset.any &&
157                          !(redir->_ipset = fw3_lookup_ipset(state, redir->ipset.name, false)))
158                 {
159                         warn_elem(e, "refers to unknown ipset '%s'", redir->ipset.name);
160                         fw3_free_redirect(redir);
161                         continue;
162                 }
163
164                 if (!check_families(e, redir))
165                 {
166                         fw3_free_redirect(redir);
167                         continue;
168                 }
169
170                 if (redir->target == FW3_TARGET_UNSPEC)
171                 {
172                         warn_elem(e, "has no target specified, defaulting to DNAT");
173                         redir->target = FW3_TARGET_DNAT;
174                 }
175                 else if (redir->target < FW3_TARGET_DNAT)
176                 {
177                         warn_elem(e, "has invalid target specified, defaulting to DNAT");
178                         redir->target = FW3_TARGET_DNAT;
179                 }
180
181                 if (redir->target == FW3_TARGET_DNAT)
182                 {
183                         if (redir->src.any)
184                                 warn_elem(e, "must not have source '*' for DNAT target");
185                         else if (!redir->_src)
186                                 warn_elem(e, "has no source specified");
187                         else
188                         {
189                                 setbit(redir->_src->dst_flags, redir->target);
190                                 redir->_src->conntrack = true;
191                                 valid = true;
192                         }
193
194                         if (redir->reflection && redir->_dest && redir->_src->masq)
195                         {
196                                 setbit(redir->_dest->dst_flags, FW3_TARGET_ACCEPT);
197                                 setbit(redir->_dest->dst_flags, FW3_TARGET_DNAT);
198                                 setbit(redir->_dest->dst_flags, FW3_TARGET_SNAT);
199                         }
200                 }
201                 else
202                 {
203                         if (redir->dest.any)
204                                 warn_elem(e, "must not have destination '*' for SNAT target");
205                         else if (!redir->_dest)
206                                 warn_elem(e, "has no destination specified");
207                         else if (!redir->ip_dest.set)
208                                 warn_elem(e, "has no src_dip option specified");
209                         else
210                         {
211                                 setbit(redir->_dest->dst_flags, redir->target);
212                                 redir->_dest->conntrack = true;
213                                 valid = true;
214                         }
215                 }
216
217                 if (!valid)
218                 {
219                         fw3_free_redirect(redir);
220                         continue;
221                 }
222
223                 if (!redir->port_redir.set)
224                         redir->port_redir = redir->port_dest;
225
226                 list_add_tail(&redir->list, &state->redirects);
227         }
228 }
229
230 static void
231 print_chain_nat(struct fw3_redirect *redir)
232 {
233         if (redir->target == FW3_TARGET_DNAT)
234                 fw3_pr("-A zone_%s_prerouting", redir->src.name);
235         else
236                 fw3_pr("-A zone_%s_postrouting", redir->dest.name);
237 }
238
239 static void
240 print_snat_dnat(enum fw3_target target,
241                 struct fw3_address *addr, struct fw3_port *port)
242 {
243         const char *t;
244         char s[sizeof("255.255.255.255 ")];
245
246         if (target == FW3_TARGET_DNAT)
247                 t = "DNAT --to-destination";
248         else
249                 t = "SNAT --to-source";
250
251         inet_ntop(AF_INET, &addr->address.v4, s, sizeof(s));
252
253         fw3_pr(" -j %s %s", t, s);
254
255         if (port && port->set)
256         {
257                 if (port->port_min == port->port_max)
258                         fw3_pr(":%u", port->port_min);
259                 else
260                         fw3_pr(":%u-%u", port->port_min, port->port_max);
261         }
262
263         fw3_pr("\n");
264 }
265
266 static void
267 print_target_nat(struct fw3_redirect *redir)
268 {
269         if (redir->target == FW3_TARGET_DNAT)
270                 print_snat_dnat(redir->target, &redir->ip_redir, &redir->port_redir);
271         else
272                 print_snat_dnat(redir->target, &redir->ip_dest, &redir->port_dest);
273 }
274
275 static void
276 print_chain_filter(struct fw3_redirect *redir)
277 {
278         if (redir->target == FW3_TARGET_DNAT)
279         {
280                 /* XXX: check for local ip */
281                 if (!redir->ip_redir.set)
282                         fw3_pr("-A zone_%s_input", redir->src.name);
283                 else
284                         fw3_pr("-A zone_%s_forward", redir->src.name);
285         }
286         else
287         {
288                 if (redir->src.set && !redir->src.any)
289                         fw3_pr("-A zone_%s_forward", redir->src.name);
290                 else
291                         fw3_pr("-A delegate_forward");
292         }
293 }
294
295 static void
296 print_target_filter(struct fw3_redirect *redir)
297 {
298         /* XXX: check for local ip */
299         if (redir->target == FW3_TARGET_DNAT && !redir->ip_redir.set)
300                 fw3_pr(" -m conntrack --ctstate DNAT -j ACCEPT\n");
301         else
302                 fw3_pr(" -j ACCEPT\n");
303 }
304
305 static void
306 print_redirect(enum fw3_table table, enum fw3_family family,
307                struct fw3_redirect *redir, int num)
308 {
309         struct list_head *ext_addrs, *int_addrs;
310         struct fw3_address *ext_addr, *int_addr;
311         struct fw3_device *ext_net, *int_net;
312         struct fw3_protocol *proto;
313         struct fw3_mac *mac;
314
315         if (redir->name)
316                 info("   * Redirect '%s'", redir->name);
317         else
318                 info("   * Redirect #%u", num);
319
320         if (!fw3_is_family(redir->_src, family) ||
321                 !fw3_is_family(redir->_dest, family))
322         {
323                 info("     ! Skipping due to different family of zone");
324                 return;
325         }
326
327         if (!fw3_is_family(&redir->ip_src, family) ||
328             !fw3_is_family(&redir->ip_dest, family) ||
329                 !fw3_is_family(&redir->ip_redir, family))
330         {
331                 info("     ! Skipping due to different family of ip address");
332                 return;
333         }
334
335         if (redir->_ipset)
336         {
337                 if (!fw3_is_family(redir->_ipset, family))
338                 {
339                         info("     ! Skipping due to different family in ipset");
340                         return;
341                 }
342
343                 setbit(redir->_ipset->flags, family);
344         }
345
346         fw3_foreach(proto, &redir->proto)
347         fw3_foreach(mac, &redir->mac_src)
348         {
349                 if (table == FW3_TABLE_NAT)
350                 {
351                         print_chain_nat(redir);
352                         fw3_format_ipset(redir->_ipset, redir->ipset.invert);
353                         fw3_format_protocol(proto, family);
354
355                         if (redir->target == FW3_TARGET_DNAT)
356                         {
357                                 fw3_format_src_dest(&redir->ip_src, &redir->ip_dest);
358                                 fw3_format_sport_dport(&redir->port_src, &redir->port_dest);
359                         }
360                         else
361                         {
362                                 fw3_format_src_dest(&redir->ip_src, &redir->ip_redir);
363                                 fw3_format_sport_dport(&redir->port_src, &redir->port_redir);
364                         }
365
366                         fw3_format_mac(mac);
367                         fw3_format_extra(redir->extra);
368                         fw3_format_comment(redir->name);
369                         print_target_nat(redir);
370                 }
371                 else if (table == FW3_TABLE_FILTER)
372                 {
373                         print_chain_filter(redir);
374                         fw3_format_ipset(redir->_ipset, redir->ipset.invert);
375                         fw3_format_protocol(proto, family);
376                         fw3_format_src_dest(&redir->ip_src, &redir->ip_redir);
377                         fw3_format_sport_dport(&redir->port_src, &redir->port_redir);
378                         fw3_format_mac(mac);
379                         fw3_format_extra(redir->extra);
380                         fw3_format_comment(redir->name);
381                         print_target_filter(redir);
382                 }
383         }
384
385         /* reflection rules */
386         if (redir->target != FW3_TARGET_DNAT || !redir->reflection)
387                 return;
388
389         if (!redir->_dest || !redir->_src->masq)
390                 return;
391
392         list_for_each_entry(ext_net, &redir->_src->networks, list)
393         {
394                 ext_addrs = fw3_ubus_address(ext_net->name);
395
396                 if (!ext_addrs || list_empty(ext_addrs))
397                         continue;
398
399                 list_for_each_entry(int_net, &redir->_dest->networks, list)
400                 {
401                         int_addrs = fw3_ubus_address(int_net->name);
402
403                         if (!int_addrs || list_empty(int_addrs))
404                                 continue;
405
406                         fw3_foreach(ext_addr, ext_addrs)
407                         fw3_foreach(int_addr, int_addrs)
408                         fw3_foreach(proto, &redir->proto)
409                         {
410                                 if (!fw3_is_family(int_addr, family) ||
411                                     !fw3_is_family(ext_addr, family))
412                                         continue;
413
414                                 if (!proto || (proto->protocol != 6 && proto->protocol != 17))
415                                         continue;
416
417                                 ext_addr->mask = 32;
418
419                                 if (table == FW3_TABLE_NAT)
420                                 {
421                                         fw3_pr("-A zone_%s_prerouting", redir->dest.name);
422                                         fw3_format_protocol(proto, family);
423                                         fw3_format_src_dest(int_addr, ext_addr);
424                                         fw3_format_sport_dport(NULL, &redir->port_dest);
425                                         fw3_format_comment(redir->name, " (reflection)");
426                                         print_snat_dnat(FW3_TARGET_DNAT,
427                                                         &redir->ip_redir, &redir->port_redir);
428
429                                         fw3_pr("-A zone_%s_postrouting", redir->dest.name);
430                                         fw3_format_protocol(proto, family);
431                                         fw3_format_src_dest(int_addr, &redir->ip_redir);
432                                         fw3_format_sport_dport(NULL, &redir->port_redir);
433                                         fw3_format_comment(redir->name, " (reflection)");
434                                         print_snat_dnat(FW3_TARGET_SNAT, ext_addr, NULL);
435                                 }
436                                 else if (table == FW3_TABLE_FILTER)
437                                 {
438                                         fw3_pr("-A zone_%s_forward", redir->dest.name);
439                                         fw3_format_protocol(proto, family);
440                                         fw3_format_src_dest(int_addr, &redir->ip_redir);
441                                         fw3_format_sport_dport(NULL, &redir->port_redir);
442                                         fw3_format_comment(redir->name, " (reflection)");
443                                         fw3_pr(" -j zone_%s_dest_ACCEPT\n", redir->dest.name);
444                                 }
445                         }
446
447                         fw3_ubus_address_free(int_addrs);
448                 }
449
450                 fw3_ubus_address_free(ext_addrs);
451         }
452 }
453
454 void
455 fw3_print_redirects(enum fw3_table table, enum fw3_family family,
456                     struct fw3_state *state)
457 {
458         int num = 0;
459         struct fw3_redirect *redir;
460
461         if (family == FW3_FAMILY_V6)
462                 return;
463
464         if (table != FW3_TABLE_FILTER && table != FW3_TABLE_NAT)
465                 return;
466
467         list_for_each_entry(redir, &state->redirects, list)
468                 print_redirect(table, family, redir, num++);
469 }