Imported Upstream version 1.35
[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                         return -errno;
60         }
61
62         if (enable) {
63                 char allow = '1';
64
65                 if (write (f, &allow, sizeof(allow)) < 0)
66                         err = -errno;
67         } else {
68                 char deny = '0';
69
70                 if (value)
71                         deny = value;
72
73                 if (write(f, &deny, sizeof(deny)) < 0)
74                         err = -errno;
75
76                 value = 0;
77         }
78
79         close(f);
80
81         return err;
82 }
83
84 static int enable_nat(struct connman_nat *nat)
85 {
86         g_free(nat->interface);
87         nat->interface = g_strdup(default_interface);
88
89         if (!nat->interface)
90                 return 0;
91
92         return __connman_firewall_enable_nat(nat->fw, nat->address,
93                                         nat->prefixlen, nat->interface);
94 }
95
96 static void disable_nat(struct connman_nat *nat)
97 {
98         if (!nat->interface)
99                 return;
100
101         __connman_firewall_disable_nat(nat->fw);
102 }
103
104 int __connman_nat_enable(const char *name, const char *address,
105                                 unsigned char prefixlen)
106 {
107         struct connman_nat *nat;
108         int err;
109
110         if (g_hash_table_size(nat_hash) == 0) {
111                 err = enable_ip_forward(true);
112                 if (err < 0)
113                         return err;
114         }
115
116         nat = g_try_new0(struct connman_nat, 1);
117         if (!nat)
118                 goto err;
119
120         nat->fw = __connman_firewall_create();
121         if (!nat->fw)
122                 goto err;
123
124         nat->address = g_strdup(address);
125         nat->prefixlen = prefixlen;
126
127         g_hash_table_replace(nat_hash, g_strdup(name), nat);
128
129         return enable_nat(nat);
130
131 err:
132         if (nat) {
133                 if (nat->fw)
134                         __connman_firewall_destroy(nat->fw);
135                 g_free(nat);
136         }
137
138         if (g_hash_table_size(nat_hash) == 0)
139                 enable_ip_forward(false);
140
141         return -ENOMEM;
142 }
143
144 void __connman_nat_disable(const char *name)
145 {
146         struct connman_nat *nat;
147
148         nat = g_hash_table_lookup(nat_hash, name);
149         if (!nat)
150                 return;
151
152         disable_nat(nat);
153
154         g_hash_table_remove(nat_hash, name);
155
156         if (g_hash_table_size(nat_hash) == 0)
157                 enable_ip_forward(false);
158 }
159
160 static void update_default_interface(struct connman_service *service)
161 {
162         GHashTableIter iter;
163         gpointer key, value;
164         char *interface;
165         int err;
166
167         interface = connman_service_get_interface(service);
168
169         DBG("interface %s", interface);
170
171         g_free(default_interface);
172         default_interface = interface;
173
174         g_hash_table_iter_init(&iter, nat_hash);
175
176         while (g_hash_table_iter_next(&iter, &key, &value)) {
177                 const char *name = key;
178                 struct connman_nat *nat = value;
179
180                 disable_nat(nat);
181                 err = enable_nat(nat);
182                 if (err < 0)
183                         DBG("Failed to enable nat for %s", name);
184         }
185 }
186
187 static void shutdown_nat(gpointer key, gpointer value, gpointer user_data)
188 {
189         const char *name = key;
190
191         __connman_nat_disable(name);
192 }
193
194 static void cleanup_nat(gpointer data)
195 {
196         struct connman_nat *nat = data;
197
198         __connman_firewall_destroy(nat->fw);
199         g_free(nat->address);
200         g_free(nat->interface);
201         g_free(nat);
202 }
203
204 static struct connman_notifier nat_notifier = {
205         .name                   = "nat",
206         .default_changed        = update_default_interface,
207 };
208
209 int __connman_nat_init(void)
210 {
211         int err;
212
213         DBG("");
214
215         err = connman_notifier_register(&nat_notifier);
216         if (err < 0)
217                 return err;
218
219         nat_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
220                                                 g_free, cleanup_nat);
221
222         return 0;
223 }
224
225 void __connman_nat_cleanup(void)
226 {
227         DBG("");
228
229         g_hash_table_foreach(nat_hash, shutdown_nat, NULL);
230         g_hash_table_destroy(nat_hash);
231         nat_hash = NULL;
232
233         connman_notifier_unregister(&nat_notifier);
234 }