Added support of WPA3-SAE security mode.
[platform/upstream/connman.git] / src / firewall-iptables.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2013,2015  BMW Car IT GmbH.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27
28 #include <xtables.h>
29 #include <linux/netfilter_ipv4/ip_tables.h>
30
31 #include "connman.h"
32
33 #define CHAIN_PREFIX "connman-"
34
35 static const char *builtin_chains[] = {
36         [NF_IP_PRE_ROUTING]     = "PREROUTING",
37         [NF_IP_LOCAL_IN]        = "INPUT",
38         [NF_IP_FORWARD]         = "FORWARD",
39         [NF_IP_LOCAL_OUT]       = "OUTPUT",
40         [NF_IP_POST_ROUTING]    = "POSTROUTING",
41 };
42
43 struct connman_managed_table {
44         char *name;
45         unsigned int chains[NF_INET_NUMHOOKS];
46 };
47
48 struct fw_rule {
49         bool enabled;
50         char *table;
51         char *chain;
52         char *rule_spec;
53 };
54
55 struct firewall_context {
56         GList *rules;
57 };
58
59 static GSList *managed_tables;
60 static struct firewall_context *connmark_ctx;
61 static unsigned int connmark_ref;
62
63 static int chain_to_index(const char *chain_name)
64 {
65         if (!g_strcmp0(builtin_chains[NF_IP_PRE_ROUTING], chain_name))
66                 return NF_IP_PRE_ROUTING;
67         if (!g_strcmp0(builtin_chains[NF_IP_LOCAL_IN], chain_name))
68                 return NF_IP_LOCAL_IN;
69         if (!g_strcmp0(builtin_chains[NF_IP_FORWARD], chain_name))
70                 return NF_IP_FORWARD;
71         if (!g_strcmp0(builtin_chains[NF_IP_LOCAL_OUT], chain_name))
72                 return NF_IP_LOCAL_OUT;
73         if (!g_strcmp0(builtin_chains[NF_IP_POST_ROUTING], chain_name))
74                 return NF_IP_POST_ROUTING;
75
76         return -1;
77 }
78
79 static int managed_chain_to_index(const char *chain_name)
80 {
81         if (!g_str_has_prefix(chain_name, CHAIN_PREFIX))
82                 return -1;
83
84         return chain_to_index(chain_name + strlen(CHAIN_PREFIX));
85 }
86
87 static int insert_managed_chain(const char *table_name, int id)
88 {
89         char *rule, *managed_chain;
90         int err;
91
92         managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX,
93                                         builtin_chains[id]);
94
95         err = __connman_iptables_new_chain(table_name, managed_chain);
96         if (err < 0)
97                 goto out;
98
99         rule = g_strdup_printf("-j %s", managed_chain);
100         err = __connman_iptables_insert(table_name, builtin_chains[id], rule);
101         g_free(rule);
102         if (err < 0) {
103                 __connman_iptables_delete_chain(table_name, managed_chain);
104                 goto out;
105         }
106
107 out:
108         g_free(managed_chain);
109
110         return err;
111 }
112
113 static int delete_managed_chain(const char *table_name, int id)
114 {
115         char *rule, *managed_chain;
116         int err;
117
118         managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX,
119                                         builtin_chains[id]);
120
121         rule = g_strdup_printf("-j %s", managed_chain);
122         err = __connman_iptables_delete(table_name, builtin_chains[id], rule);
123         g_free(rule);
124
125         if (err < 0)
126                 goto out;
127
128         err =  __connman_iptables_delete_chain(table_name, managed_chain);
129
130 out:
131         g_free(managed_chain);
132
133         return err;
134 }
135
136 static int insert_managed_rule(const char *table_name,
137                                 const char *chain_name,
138                                 const char *rule_spec)
139 {
140         struct connman_managed_table *mtable = NULL;
141         GSList *list;
142         char *chain;
143         int id, err;
144
145         id = chain_to_index(chain_name);
146         if (id < 0) {
147                 /* This chain is not managed */
148                 chain = g_strdup(chain_name);
149                 goto out;
150         }
151
152         for (list = managed_tables; list; list = list->next) {
153                 mtable = list->data;
154
155                 if (g_strcmp0(mtable->name, table_name) == 0)
156                         break;
157
158                 mtable = NULL;
159         }
160
161         if (!mtable) {
162                 mtable = g_new0(struct connman_managed_table, 1);
163                 mtable->name = g_strdup(table_name);
164
165                 managed_tables = g_slist_prepend(managed_tables, mtable);
166         }
167
168         if (mtable->chains[id] == 0) {
169                 DBG("table %s add managed chain for %s",
170                         table_name, chain_name);
171
172                 err = insert_managed_chain(table_name, id);
173                 if (err < 0)
174                         return err;
175         }
176
177         mtable->chains[id]++;
178         chain = g_strdup_printf("%s%s", CHAIN_PREFIX, chain_name);
179
180 out:
181         err = __connman_iptables_append(table_name, chain, rule_spec);
182
183         g_free(chain);
184
185         return err;
186  }
187
188 static int delete_managed_rule(const char *table_name,
189                                 const char *chain_name,
190                                 const char *rule_spec)
191  {
192         struct connman_managed_table *mtable = NULL;
193         GSList *list;
194         int id, err;
195         char *managed_chain;
196
197         id = chain_to_index(chain_name);
198         if (id < 0) {
199                 /* This chain is not managed */
200                 return __connman_iptables_delete(table_name, chain_name,
201                                                         rule_spec);
202         }
203
204         managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX, chain_name);
205
206         err = __connman_iptables_delete(table_name, managed_chain,
207                                         rule_spec);
208
209         for (list = managed_tables; list; list = list->next) {
210                 mtable = list->data;
211
212                 if (g_strcmp0(mtable->name, table_name) == 0)
213                         break;
214
215                 mtable = NULL;
216         }
217
218         if (!mtable) {
219                 err = -ENOENT;
220                 goto out;
221         }
222
223         mtable->chains[id]--;
224         if (mtable->chains[id] > 0)
225                 goto out;
226
227         DBG("table %s remove managed chain for %s",
228                         table_name, chain_name);
229
230         err = delete_managed_chain(table_name, id);
231
232  out:
233         g_free(managed_chain);
234
235         return err;
236 }
237
238 static void cleanup_managed_table(gpointer user_data)
239 {
240         struct connman_managed_table *table = user_data;
241
242         g_free(table->name);
243         g_free(table);
244 }
245
246 static void cleanup_fw_rule(gpointer user_data)
247 {
248         struct fw_rule *rule = user_data;
249
250         g_free(rule->rule_spec);
251         g_free(rule->chain);
252         g_free(rule->table);
253         g_free(rule);
254 }
255
256 struct firewall_context *__connman_firewall_create(void)
257 {
258         struct firewall_context *ctx;
259
260         ctx = g_new0(struct firewall_context, 1);
261
262         return ctx;
263 }
264
265 void __connman_firewall_destroy(struct firewall_context *ctx)
266 {
267         g_list_free_full(ctx->rules, cleanup_fw_rule);
268         g_free(ctx);
269 }
270
271 static int enable_rule(struct fw_rule *rule)
272 {
273         int err;
274
275         if (rule->enabled)
276                 return -EALREADY;
277
278         DBG("%s %s %s", rule->table, rule->chain, rule->rule_spec);
279
280         err = insert_managed_rule(rule->table, rule->chain, rule->rule_spec);
281         if (err < 0)
282                 return err;
283
284         err = __connman_iptables_commit(rule->table);
285         if (err < 0)
286                 return err;
287
288         rule->enabled = true;
289
290         return 0;
291 }
292
293 static int disable_rule(struct fw_rule *rule)
294 {
295         int err;
296
297         if (!rule->enabled)
298                 return -EALREADY;
299
300         err = delete_managed_rule(rule->table, rule->chain, rule->rule_spec);
301         if (err < 0) {
302                 connman_error("Cannot remove previously installed "
303                         "iptables rules: %s", strerror(-err));
304                 return err;
305         }
306
307         err = __connman_iptables_commit(rule->table);
308         if (err < 0) {
309                 connman_error("Cannot remove previously installed "
310                         "iptables rules: %s", strerror(-err));
311                 return err;
312         }
313
314         rule->enabled = false;
315
316         return 0;
317 }
318
319 static void firewall_add_rule(struct firewall_context *ctx,
320                                 const char *table,
321                                 const char *chain,
322                                 const char *rule_fmt, ...)
323 {
324         va_list args;
325         char *rule_spec;
326         struct fw_rule *rule;
327
328         va_start(args, rule_fmt);
329
330         rule_spec = g_strdup_vprintf(rule_fmt, args);
331
332         va_end(args);
333
334         rule = g_new0(struct fw_rule, 1);
335
336         rule->enabled = false;
337         rule->table = g_strdup(table);
338         rule->chain = g_strdup(chain);
339         rule->rule_spec = rule_spec;
340
341         ctx->rules = g_list_append(ctx->rules, rule);
342 }
343
344 static void firewall_remove_rules(struct firewall_context *ctx)
345 {
346         struct fw_rule *rule;
347         GList *list;
348
349         for (list = g_list_last(ctx->rules); list;
350                         list = g_list_previous(list)) {
351                 rule = list->data;
352
353                 ctx->rules = g_list_remove(ctx->rules, rule);
354                 cleanup_fw_rule(rule);
355         }
356 }
357
358 static int firewall_enable_rules(struct firewall_context *ctx)
359 {
360         struct fw_rule *rule;
361         GList *list;
362         int err = -ENOENT;
363
364         for (list = g_list_first(ctx->rules); list; list = g_list_next(list)) {
365                 rule = list->data;
366
367                 err = enable_rule(rule);
368                 if (err < 0)
369                         break;
370         }
371
372         return err;
373 }
374
375 static int firewall_disable_rules(struct firewall_context *ctx)
376 {
377         struct fw_rule *rule;
378         GList *list;
379         int e;
380         int err = -ENOENT;
381
382         for (list = g_list_last(ctx->rules); list;
383                         list = g_list_previous(list)) {
384                 rule = list->data;
385
386                 e = disable_rule(rule);
387
388                 /* Report last error back */
389                 if (e == 0 && err == -ENOENT)
390                         err = 0;
391                 else if (e < 0)
392                         err = e;
393         }
394
395         return err;
396 }
397
398 int __connman_firewall_enable_nat(struct firewall_context *ctx,
399                                 char *address, unsigned char prefixlen,
400                                 char *interface)
401 {
402         char *cmd;
403         int err;
404
405         cmd = g_strdup_printf("-s %s/%d -o %s -j MASQUERADE",
406                                         address, prefixlen, interface);
407
408         firewall_add_rule(ctx, "nat", "POSTROUTING", cmd);
409         g_free(cmd);
410         err = firewall_enable_rules(ctx);
411         if (err)
412                 firewall_remove_rules(ctx);
413         return err;
414 }
415
416 int __connman_firewall_disable_nat(struct firewall_context *ctx)
417 {
418         int err;
419
420         err = firewall_disable_rules(ctx);
421         if (err < 0) {
422                 DBG("could not disable NAT rule");
423                 return err;
424         }
425
426         firewall_remove_rules(ctx);
427         return 0;
428 }
429
430 int __connman_firewall_enable_snat(struct firewall_context *ctx,
431                                 int index, const char *ifname,
432                                 const char *addr)
433 {
434         int err;
435
436         firewall_add_rule(ctx, "nat", "POSTROUTING",
437                                 "-o %s -j SNAT --to-source %s",
438                                 ifname, addr);
439
440         err = firewall_enable_rules(ctx);
441         if (err)
442                 firewall_remove_rules(ctx);
443         return err;
444 }
445
446 int __connman_firewall_disable_snat(struct firewall_context *ctx)
447 {
448         int err;
449
450         err = firewall_disable_rules(ctx);
451         if (err < 0) {
452                 DBG("could not disable SNAT rule");
453                 return err;
454         }
455
456         firewall_remove_rules(ctx);
457         return 0;
458 }
459
460 static int firewall_enable_connmark(void)
461 {
462         int err;
463
464         if (connmark_ref > 0) {
465                 connmark_ref++;
466                 return 0;
467         }
468
469         connmark_ctx = __connman_firewall_create();
470
471         firewall_add_rule(connmark_ctx, "mangle", "INPUT",
472                                         "-j CONNMARK --restore-mark");
473         firewall_add_rule(connmark_ctx, "mangle", "POSTROUTING",
474                                         "-j CONNMARK --save-mark");
475         err = firewall_enable_rules(connmark_ctx);
476         if (err) {
477                 __connman_firewall_destroy(connmark_ctx);
478                 connmark_ctx = NULL;
479                 return err;
480         }
481         connmark_ref++;
482         return 0;
483 }
484
485 static void firewall_disable_connmark(void)
486 {
487         connmark_ref--;
488         if (connmark_ref > 0)
489                 return;
490
491         firewall_disable_rules(connmark_ctx);
492         __connman_firewall_destroy(connmark_ctx);
493         connmark_ctx = NULL;
494 }
495
496 int __connman_firewall_enable_marking(struct firewall_context *ctx,
497                                         enum connman_session_id_type id_type,
498                                         char *id, const char *src_ip,
499                                         uint32_t mark)
500 {
501         int err;
502
503         err = firewall_enable_connmark();
504         if (err)
505                 return err;
506
507         switch (id_type) {
508         case CONNMAN_SESSION_ID_TYPE_UID:
509                 firewall_add_rule(ctx, "mangle", "OUTPUT",
510                                 "-m owner --uid-owner %s -j MARK --set-mark %d",
511                                         id, mark);
512                 break;
513         case CONNMAN_SESSION_ID_TYPE_GID:
514                 firewall_add_rule(ctx, "mangle", "OUTPUT",
515                                 "-m owner --gid-owner %s -j MARK --set-mark %d",
516                                         id, mark);
517                 break;
518         case CONNMAN_SESSION_ID_TYPE_UNKNOWN:
519                 break;
520         case CONNMAN_SESSION_ID_TYPE_LSM:
521         default:
522                 return -EINVAL;
523         }
524
525         if (src_ip) {
526                 firewall_add_rule(ctx, "mangle", "OUTPUT",
527                                 "-s %s -j MARK --set-mark %d",
528                                         src_ip, mark);
529         }
530
531         return firewall_enable_rules(ctx);
532 }
533
534 int __connman_firewall_disable_marking(struct firewall_context *ctx)
535 {
536         firewall_disable_connmark();
537         return firewall_disable_rules(ctx);
538 }
539
540 static void iterate_chains_cb(const char *chain_name, void *user_data)
541 {
542         GSList **chains = user_data;
543         int id;
544
545         id = managed_chain_to_index(chain_name);
546         if (id < 0)
547                 return;
548
549         *chains = g_slist_prepend(*chains, GINT_TO_POINTER(id));
550 }
551
552 static void flush_table(const char *table_name)
553 {
554         GSList *chains = NULL, *list;
555         char *rule, *managed_chain;
556         int id, err;
557
558         __connman_iptables_iterate_chains(table_name, iterate_chains_cb,
559                                                 &chains);
560
561         for (list = chains; list; list = list->next) {
562                 id = GPOINTER_TO_INT(list->data);
563
564                 managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX,
565                                                 builtin_chains[id]);
566
567                 rule = g_strdup_printf("-j %s", managed_chain);
568                 err = __connman_iptables_delete(table_name,
569                                                 builtin_chains[id], rule);
570                 if (err < 0) {
571                         connman_warn("Failed to delete jump rule '%s': %s",
572                                 rule, strerror(-err));
573                 }
574                 g_free(rule);
575
576                 err = __connman_iptables_flush_chain(table_name, managed_chain);
577                 if (err < 0) {
578                         connman_warn("Failed to flush chain '%s': %s",
579                                 managed_chain, strerror(-err));
580                 }
581                 err = __connman_iptables_delete_chain(table_name, managed_chain);
582                 if (err < 0) {
583                         connman_warn("Failed to delete chain '%s': %s",
584                                 managed_chain, strerror(-err));
585                 }
586
587                 g_free(managed_chain);
588         }
589
590         err = __connman_iptables_commit(table_name);
591         if (err < 0) {
592                 connman_warn("Failed to flush table '%s': %s",
593                         table_name, strerror(-err));
594         }
595
596         g_slist_free(chains);
597 }
598
599 static void flush_all_tables(void)
600 {
601         /* Flush the tables ConnMan might have modified
602          * But do so if only ConnMan has done something with
603          * iptables */
604
605         if (!g_file_test("/proc/net/ip_tables_names",
606                         G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
607                 return;
608         }
609
610         flush_table("filter");
611         flush_table("mangle");
612         flush_table("nat");
613 }
614
615 int __connman_firewall_init(void)
616 {
617         DBG("");
618
619         __connman_iptables_init();
620         flush_all_tables();
621
622         return 0;
623 }
624
625 void __connman_firewall_cleanup(void)
626 {
627         DBG("");
628
629         g_slist_free_full(managed_tables, cleanup_managed_table);
630         __connman_iptables_cleanup();
631 }