c235d86131e56f4c876b90511a067d0af431d0a2
[platform/upstream/connman.git] / src / firewall.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2013  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         char *table;
50         char *chain;
51         char *rule_spec;
52 };
53
54 struct firewall_context {
55         GList *rules;
56 };
57
58 static GSList *managed_tables;
59
60 static int chain_to_index(const char *chain_name)
61 {
62         if (!g_strcmp0(builtin_chains[NF_IP_PRE_ROUTING], chain_name))
63                 return NF_IP_PRE_ROUTING;
64         if (!g_strcmp0(builtin_chains[NF_IP_LOCAL_IN], chain_name))
65                 return NF_IP_LOCAL_IN;
66         if (!g_strcmp0(builtin_chains[NF_IP_FORWARD], chain_name))
67                 return NF_IP_FORWARD;
68         if (!g_strcmp0(builtin_chains[NF_IP_LOCAL_OUT], chain_name))
69                 return NF_IP_LOCAL_OUT;
70         if (!g_strcmp0(builtin_chains[NF_IP_POST_ROUTING], chain_name))
71                 return NF_IP_POST_ROUTING;
72
73         return -1;
74 }
75
76 static int managed_chain_to_index(const char *chain_name)
77 {
78         if (g_str_has_prefix(chain_name, CHAIN_PREFIX) == FALSE)
79                 return -1;
80
81         return chain_to_index(chain_name + strlen(CHAIN_PREFIX));
82 }
83
84 static int insert_managed_chain(const char *table_name, int id)
85 {
86         char *rule, *managed_chain;
87         int err;
88
89         managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX,
90                                         builtin_chains[id]);
91
92         err = __connman_iptables_new_chain(table_name, managed_chain);
93         if (err < 0)
94                 goto out;
95
96         rule = g_strdup_printf("-j %s", managed_chain);
97         err = __connman_iptables_insert(table_name, builtin_chains[id], rule);
98         g_free(rule);
99         if (err < 0) {
100                 __connman_iptables_delete_chain(table_name, managed_chain);
101                 goto out;
102         }
103
104 out:
105         g_free(managed_chain);
106
107         return err;
108 }
109
110 static int delete_managed_chain(const char *table_name, int id)
111 {
112         char *rule, *managed_chain;
113         int err;
114
115         managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX,
116                                         builtin_chains[id]);
117
118         rule = g_strdup_printf("-j %s", managed_chain);
119         err = __connman_iptables_delete(table_name, builtin_chains[id], rule);
120         g_free(rule);
121
122         if (err < 0)
123                 goto out;
124
125         err =  __connman_iptables_delete_chain(table_name, managed_chain);
126
127 out:
128         g_free(managed_chain);
129
130         return err;
131 }
132
133 static int insert_managed_rule(const char *table_name,
134                                 const char *chain_name,
135                                 const char *rule_spec)
136 {
137         struct connman_managed_table *mtable = NULL;
138         GSList *list;
139         char *chain;
140         int id, err;
141
142         id = chain_to_index(chain_name);
143         if (id < 0) {
144                 /* This chain is not managed */
145                 chain = g_strdup(chain_name);
146                 goto out;
147         }
148
149         for (list = managed_tables; list != NULL; list = list->next) {
150                 mtable = list->data;
151
152                 if (g_strcmp0(mtable->name, table_name) == 0)
153                         break;
154
155                 mtable = NULL;
156         }
157
158         if (mtable == NULL) {
159                 mtable = g_new0(struct connman_managed_table, 1);
160                 mtable->name = g_strdup(table_name);
161
162                 managed_tables = g_slist_prepend(managed_tables, mtable);
163         }
164
165         if (mtable->chains[id] == 0) {
166                 DBG("table %s add managed chain for %s",
167                         table_name, chain_name);
168
169                 err = insert_managed_chain(table_name, id);
170                 if (err < 0)
171                         return err;
172         }
173
174         mtable->chains[id]++;
175         chain = g_strdup_printf("%s%s", CHAIN_PREFIX, chain_name);
176
177 out:
178         err = __connman_iptables_append(table_name, chain, rule_spec);
179
180         g_free(chain);
181
182         return err;
183  }
184
185 static int delete_managed_rule(const char *table_name,
186                                 const char *chain_name,
187                                 const char *rule_spec)
188  {
189         struct connman_managed_table *mtable = NULL;
190         GSList *list;
191         int id, err;
192         char *managed_chain;
193
194         id = chain_to_index(chain_name);
195         if (id < 0) {
196                 /* This chain is not managed */
197                 return __connman_iptables_delete(table_name, chain_name,
198                                                         rule_spec);
199         }
200
201         managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX, chain_name);
202
203         err = __connman_iptables_delete(table_name, managed_chain,
204                                         rule_spec);
205
206         for (list = managed_tables; list != NULL; list = list->next) {
207                 mtable = list->data;
208
209                 if (g_strcmp0(mtable->name, table_name) == 0)
210                         break;
211
212                 mtable = NULL;
213         }
214
215         if (mtable == NULL) {
216                 err = -ENOENT;
217                 goto out;
218         }
219
220         mtable->chains[id]--;
221         if (mtable->chains[id] > 0)
222                 goto out;
223
224         DBG("table %s remove managed chain for %s",
225                         table_name, chain_name);
226
227         err = delete_managed_chain(table_name, id);
228
229  out:
230         g_free(managed_chain);
231
232         return err;
233 }
234
235 static void cleanup_managed_table(gpointer user_data)
236 {
237         struct connman_managed_table *table = user_data;
238
239         g_free(table->name);
240         g_free(table);
241 }
242
243 static void cleanup_fw_rule(gpointer user_data)
244 {
245         struct fw_rule *rule = user_data;
246
247         g_free(rule->rule_spec);
248         g_free(rule->chain);
249         g_free(rule->table);
250         g_free(rule);
251 }
252
253 struct firewall_context *__connman_firewall_create(void)
254 {
255         struct firewall_context *ctx;
256
257         ctx = g_new0(struct firewall_context, 1);
258
259         return ctx;
260 }
261
262 void __connman_firewall_destroy(struct firewall_context *ctx)
263 {
264         g_list_free_full(ctx->rules, cleanup_fw_rule);
265         g_free(ctx);
266 }
267
268 int __connman_firewall_add_rule(struct firewall_context *ctx,
269                                 const char *table,
270                                 const char *chain,
271                                 const char *rule_fmt, ...)
272 {
273         va_list args;
274         char *rule_spec;
275         struct fw_rule *rule;
276
277         va_start(args, rule_fmt);
278
279         rule_spec = g_strdup_vprintf(rule_fmt, args);
280
281         va_end(args);
282
283         rule = g_new0(struct fw_rule, 1);
284
285         rule->table = g_strdup(table);
286         rule->chain = g_strdup(chain);
287         rule->rule_spec = rule_spec;
288
289         ctx->rules = g_list_append(ctx->rules, rule);
290
291         return 0;
292 }
293
294 static int firewall_disable(GList *rules)
295 {
296         struct fw_rule *rule;
297         GList *list;
298         int err;
299
300         for (list = rules; list != NULL; list = g_list_previous(list)) {
301                 rule = list->data;
302
303                 err = delete_managed_rule(rule->table,
304                                                 rule->chain, rule->rule_spec);
305                 if (err < 0) {
306                         connman_error("Cannot remove previously installed "
307                                 "iptables rules: %s", strerror(-err));
308                         return err;
309                 }
310
311                 err = __connman_iptables_commit(rule->table);
312                 if (err < 0) {
313                         connman_error("Cannot remove previously installed "
314                                 "iptables rules: %s", strerror(-err));
315                         return err;
316                 }
317         }
318
319         return 0;
320 }
321
322 int __connman_firewall_enable(struct firewall_context *ctx)
323 {
324         struct fw_rule *rule;
325         GList *list;
326         int err;
327
328         for (list = g_list_first(ctx->rules); list != NULL;
329                         list = g_list_next(list)) {
330                 rule = list->data;
331
332                 DBG("%s %s %s", rule->table, rule->chain, rule->rule_spec);
333
334                 err = insert_managed_rule(rule->table,
335                                                 rule->chain, rule->rule_spec);
336                 if (err < 0)
337                         goto err;
338
339                 err = __connman_iptables_commit(rule->table);
340                 if (err < 0)
341                         goto err;
342         }
343
344         return 0;
345
346 err:
347         connman_warn("Failed to install iptables rules: %s", strerror(-err));
348
349         firewall_disable(g_list_previous(list));
350
351         return err;
352 }
353
354 int __connman_firewall_disable(struct firewall_context *ctx)
355 {
356         return firewall_disable(g_list_last(ctx->rules));
357 }
358
359 static void iterate_chains_cb(const char *chain_name, void *user_data)
360 {
361         GSList **chains = user_data;
362         int id;
363
364         id = managed_chain_to_index(chain_name);
365         if (id < 0)
366                 return;
367
368         *chains = g_slist_prepend(*chains, GINT_TO_POINTER(id));
369 }
370
371 static void flush_table(const char *table_name)
372 {
373         GSList *chains = NULL, *list;
374         char *rule, *managed_chain;
375         int id, err;
376
377         __connman_iptables_iterate_chains(table_name, iterate_chains_cb,
378                                                 &chains);
379
380         for (list = chains; list != NULL; list = list->next) {
381                 id = GPOINTER_TO_INT(list->data);
382
383                 managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX,
384                                                 builtin_chains[id]);
385
386                 rule = g_strdup_printf("-j %s", managed_chain);
387                 err = __connman_iptables_delete(table_name,
388                                                 builtin_chains[id], rule);
389                 if (err < 0) {
390                         connman_warn("Failed to delete jump rule '%s': %s",
391                                 rule, strerror(-err));
392                 }
393                 g_free(rule);
394
395                 err = __connman_iptables_flush_chain(table_name, managed_chain);
396                 if (err < 0) {
397                         connman_warn("Failed to flush chain '%s': %s",
398                                 managed_chain, strerror(-err));
399                 }
400                 err = __connman_iptables_delete_chain(table_name, managed_chain);
401                 if (err < 0) {
402                         connman_warn("Failed to delete chain '%s': %s",
403                                 managed_chain, strerror(-err));
404                 }
405
406                 g_free(managed_chain);
407         }
408
409         err = __connman_iptables_commit(table_name);
410         if (err < 0) {
411                 connman_warn("Failed to flush table '%s': %s",
412                         table_name, strerror(-err));
413         }
414
415         g_slist_free(chains);
416 }
417
418 static void flush_all_tables(void)
419 {
420         /* Flush the tables ConnMan might have modified */
421
422         flush_table("filter");
423         flush_table("mangle");
424         flush_table("nat");
425 }
426
427 int __connman_firewall_init(void)
428 {
429         DBG("");
430
431         flush_all_tables();
432
433         return 0;
434 }
435
436 void __connman_firewall_cleanup(void)
437 {
438         DBG("");
439
440         g_slist_free_full(managed_tables, cleanup_managed_table);
441 }