Merge "Fix SIGSEV on freeing server domains list" into tizen
[platform/upstream/connman.git] / src / nat.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2012  Intel Corporation. All rights reserved.
6  *  Copyright (C) 2012-2014  BMW Car IT GmbH.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License version 2 as
10  *  published by the Free Software Foundation.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  *
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <errno.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32
33 #include "connman.h"
34
35 static char *default_interface;
36 static GHashTable *nat_hash;
37
38 struct connman_nat {
39         char *address;
40         unsigned char prefixlen;
41         struct firewall_context *fw;
42
43         char *interface;
44 };
45
46 static int enable_ip_forward(bool enable)
47 {
48         static char value = 0;
49         int f, err = 0;
50
51         if ((f = open("/proc/sys/net/ipv4/ip_forward", O_CLOEXEC | O_RDWR)) < 0)
52                 return -errno;
53
54         if (!value) {
55                 if (read(f, &value, sizeof(value)) < 0)
56                         value = 0;
57
58                 if (lseek(f, 0, SEEK_SET) < 0) {
59                         close(f);
60                         return -errno;
61                 }
62         }
63
64         if (enable) {
65                 char allow = '1';
66
67                 if (write (f, &allow, sizeof(allow)) < 0)
68                         err = -errno;
69         } else {
70                 char deny = '0';
71
72                 if (value)
73                         deny = value;
74
75                 if (write(f, &deny, sizeof(deny)) < 0)
76                         err = -errno;
77
78                 value = 0;
79         }
80
81         close(f);
82
83         return err;
84 }
85
86 static int enable_nat(struct connman_nat *nat)
87 {
88         g_free(nat->interface);
89         nat->interface = g_strdup(default_interface);
90
91         if (!nat->interface)
92                 return 0;
93
94         return __connman_firewall_enable_nat(nat->fw, nat->address,
95                                         nat->prefixlen, nat->interface);
96 }
97
98 static void disable_nat(struct connman_nat *nat)
99 {
100         if (!nat->interface)
101                 return;
102
103         __connman_firewall_disable_nat(nat->fw);
104 }
105
106 int __connman_nat_enable(const char *name, const char *address,
107                                 unsigned char prefixlen)
108 {
109         struct connman_nat *nat;
110         int err;
111
112         if (g_hash_table_size(nat_hash) == 0) {
113                 err = enable_ip_forward(true);
114                 if (err < 0)
115                         return err;
116         }
117
118         nat = g_try_new0(struct connman_nat, 1);
119         if (!nat)
120                 goto err;
121
122         nat->fw = __connman_firewall_create();
123         if (!nat->fw)
124                 goto err;
125
126         nat->address = g_strdup(address);
127         nat->prefixlen = prefixlen;
128
129         g_hash_table_replace(nat_hash, g_strdup(name), nat);
130
131         return enable_nat(nat);
132
133 err:
134         if (nat) {
135                 if (nat->fw)
136                         __connman_firewall_destroy(nat->fw);
137                 g_free(nat);
138         }
139
140         if (g_hash_table_size(nat_hash) == 0)
141                 enable_ip_forward(false);
142
143         return -ENOMEM;
144 }
145
146 void __connman_nat_disable(const char *name)
147 {
148         struct connman_nat *nat;
149
150         nat = g_hash_table_lookup(nat_hash, name);
151         if (!nat)
152                 return;
153
154         disable_nat(nat);
155
156         g_hash_table_remove(nat_hash, name);
157
158         if (g_hash_table_size(nat_hash) == 0)
159                 enable_ip_forward(false);
160 }
161
162 static void update_default_interface(struct connman_service *service)
163 {
164         GHashTableIter iter;
165         gpointer key, value;
166         char *interface;
167         int err;
168
169         interface = connman_service_get_interface(service);
170
171         DBG("interface %s", interface);
172
173         g_free(default_interface);
174         default_interface = interface;
175
176         g_hash_table_iter_init(&iter, nat_hash);
177
178         while (g_hash_table_iter_next(&iter, &key, &value)) {
179                 const char *name = key;
180                 struct connman_nat *nat = value;
181
182                 disable_nat(nat);
183                 err = enable_nat(nat);
184                 if (err < 0)
185                         DBG("Failed to enable nat for %s", name);
186         }
187 }
188
189 static void shutdown_nat(gpointer key, gpointer value, gpointer user_data)
190 {
191         const char *name = key;
192
193         __connman_nat_disable(name);
194 }
195
196 static void cleanup_nat(gpointer data)
197 {
198         struct connman_nat *nat = data;
199
200         __connman_firewall_destroy(nat->fw);
201         g_free(nat->address);
202         g_free(nat->interface);
203         g_free(nat);
204 }
205
206 static const struct connman_notifier nat_notifier = {
207         .name                   = "nat",
208         .default_changed        = update_default_interface,
209 };
210
211 int __connman_nat_init(void)
212 {
213         int err;
214
215         DBG("");
216
217         err = connman_notifier_register(&nat_notifier);
218         if (err < 0)
219                 return err;
220
221         nat_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
222                                                 g_free, cleanup_nat);
223
224         return 0;
225 }
226
227 void __connman_nat_cleanup(void)
228 {
229         DBG("");
230
231         g_hash_table_foreach(nat_hash, shutdown_nat, NULL);
232         g_hash_table_destroy(nat_hash);
233         nat_hash = NULL;
234
235         connman_notifier_unregister(&nat_notifier);
236 }