firewall: Add firewall API
[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 <xtables.h>
27 #include <linux/netfilter_ipv4/ip_tables.h>
28
29 #include "connman.h"
30
31 #define CHAIN_PREFIX "connman-"
32
33 static const char *builtin_chains[] = {
34         [NF_IP_PRE_ROUTING]     = "PREROUTING",
35         [NF_IP_LOCAL_IN]        = "INPUT",
36         [NF_IP_FORWARD]         = "FORWARD",
37         [NF_IP_LOCAL_OUT]       = "OUTPUT",
38         [NF_IP_POST_ROUTING]    = "POSTROUTING",
39 };
40
41 struct fw_rule {
42         char *table;
43         char *chain;
44         char *rule_spec;
45 };
46
47 struct firewall_context {
48         GList *rules;
49 };
50
51 static int chain_to_index(const char *chain_name)
52 {
53         if (!g_strcmp0(builtin_chains[NF_IP_PRE_ROUTING], chain_name))
54                 return NF_IP_PRE_ROUTING;
55         if (!g_strcmp0(builtin_chains[NF_IP_LOCAL_IN], chain_name))
56                 return NF_IP_LOCAL_IN;
57         if (!g_strcmp0(builtin_chains[NF_IP_FORWARD], chain_name))
58                 return NF_IP_FORWARD;
59         if (!g_strcmp0(builtin_chains[NF_IP_LOCAL_OUT], chain_name))
60                 return NF_IP_LOCAL_OUT;
61         if (!g_strcmp0(builtin_chains[NF_IP_POST_ROUTING], chain_name))
62                 return NF_IP_POST_ROUTING;
63
64         return -1;
65 }
66
67 static int managed_chain_to_index(const char *chain_name)
68 {
69         if (g_str_has_prefix(chain_name, CHAIN_PREFIX) == FALSE)
70                 return -1;
71
72         return chain_to_index(chain_name + strlen(CHAIN_PREFIX));
73 }
74
75 static void cleanup_fw_rule(gpointer user_data)
76 {
77         struct fw_rule *rule = user_data;
78
79         g_free(rule->rule_spec);
80         g_free(rule->chain);
81         g_free(rule->table);
82         g_free(rule);
83 }
84
85 struct firewall_context *__connman_firewall_create(void)
86 {
87         struct firewall_context *ctx;
88
89         ctx = g_new0(struct firewall_context, 1);
90
91         return ctx;
92 }
93
94 void __connman_firewall_destroy(struct firewall_context *ctx)
95 {
96         g_list_free_full(ctx->rules, cleanup_fw_rule);
97         g_free(ctx);
98 }
99
100 int __connman_firewall_add_rule(struct firewall_context *ctx,
101                                 const char *table,
102                                 const char *chain,
103                                 const char *rule_fmt, ...)
104 {
105         va_list args;
106         char *rule_spec;
107         struct fw_rule *rule;
108
109         va_start(args, rule_fmt);
110
111         rule_spec = g_strdup_vprintf(rule_fmt, args);
112
113         va_end(args);
114
115         rule = g_new0(struct fw_rule, 1);
116
117         rule->table = g_strdup(table);
118         rule->chain = g_strdup(chain);
119         rule->rule_spec = rule_spec;
120
121         ctx->rules = g_list_append(ctx->rules, rule);
122
123         return 0;
124 }
125
126 static int firewall_disable(GList *rules)
127 {
128         struct fw_rule *rule;
129         GList *list;
130         int err;
131
132         for (list = rules; list != NULL; list = g_list_previous(list)) {
133                 rule = list->data;
134
135                 err = __connman_iptables_delete(rule->table,
136                                                 rule->chain,
137                                                 rule->rule_spec);
138                 if (err < 0) {
139                         connman_error("Cannot remove previously installed "
140                                 "iptables rules: %s", strerror(-err));
141                         return err;
142                 }
143
144                 err = __connman_iptables_commit(rule->table);
145                 if (err < 0) {
146                         connman_error("Cannot remove previously installed "
147                                 "iptables rules: %s", strerror(-err));
148                         return err;
149                 }
150         }
151
152         return 0;
153 }
154
155 int __connman_firewall_enable(struct firewall_context *ctx)
156 {
157         struct fw_rule *rule;
158         GList *list;
159         int err;
160
161         for (list = g_list_first(ctx->rules); list != NULL;
162                         list = g_list_next(list)) {
163                 rule = list->data;
164
165                 DBG("%s %s %s", rule->table, rule->chain, rule->rule_spec);
166
167                 err = __connman_iptables_append(rule->table,
168                                         rule->chain,
169                                         rule->rule_spec);
170                 if (err < 0)
171                         goto err;
172
173                 err = __connman_iptables_commit(rule->table);
174                 if (err < 0)
175                         goto err;
176         }
177
178         return 0;
179
180 err:
181         connman_warn("Failed to install iptables rules: %s", strerror(-err));
182
183         firewall_disable(g_list_previous(list));
184
185         return err;
186 }
187
188 int __connman_firewall_disable(struct firewall_context *ctx)
189 {
190         return firewall_disable(g_list_last(ctx->rules));
191 }
192
193 static void iterate_chains_cb(const char *chain_name, void *user_data)
194 {
195         GSList **chains = user_data;
196         int id;
197
198         id = managed_chain_to_index(chain_name);
199         if (id < 0)
200                 return;
201
202         *chains = g_slist_prepend(*chains, GINT_TO_POINTER(id));
203 }
204
205 static void flush_table(const char *table_name)
206 {
207         GSList *chains = NULL, *list;
208         char *rule, *managed_chain;
209         int id, err;
210
211         __connman_iptables_iterate_chains(table_name, iterate_chains_cb,
212                                                 &chains);
213
214         for (list = chains; list != NULL; list = list->next) {
215                 id = GPOINTER_TO_INT(list->data);
216
217                 managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX,
218                                                 builtin_chains[id]);
219
220                 rule = g_strdup_printf("-j %s", managed_chain);
221                 err = __connman_iptables_delete(table_name,
222                                                 builtin_chains[id], rule);
223                 if (err < 0) {
224                         connman_warn("Failed to delete jump rule '%s': %s",
225                                 rule, strerror(-err));
226                 }
227                 g_free(rule);
228
229                 err = __connman_iptables_flush_chain(table_name, managed_chain);
230                 if (err < 0) {
231                         connman_warn("Failed to flush chain '%s': %s",
232                                 managed_chain, strerror(-err));
233                 }
234                 err = __connman_iptables_delete_chain(table_name, managed_chain);
235                 if (err < 0) {
236                         connman_warn("Failed to delete chain '%s': %s",
237                                 managed_chain, strerror(-err));
238                 }
239
240                 g_free(managed_chain);
241         }
242
243         err = __connman_iptables_commit(table_name);
244         if (err < 0) {
245                 connman_warn("Failed to flush table '%s': %s",
246                         table_name, strerror(-err));
247         }
248
249         g_slist_free(chains);
250 }
251
252 static void flush_all_tables(void)
253 {
254         /* Flush the tables ConnMan might have modified */
255
256         flush_table("filter");
257         flush_table("mangle");
258         flush_table("nat");
259 }
260
261 int __connman_firewall_init(void)
262 {
263         DBG("");
264
265         flush_all_tables();
266
267         return 0;
268 }
269
270 void __connman_firewall_cleanup(void)
271 {
272         DBG("");
273 }