service: Disconnect the connecting service when needed
[platform/upstream/connman.git] / src / dhcp.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2012  Intel Corporation. All rights reserved.
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 #include <stdio.h>
26 #include <errno.h>
27 #include <string.h>
28 #include <stdlib.h>
29
30 #include <connman/ipconfig.h>
31 #include <include/setting.h>
32
33 #include <gdhcp/gdhcp.h>
34
35 #include <glib.h>
36
37 #include "connman.h"
38
39 struct connman_dhcp {
40         struct connman_network *network;
41         dhcp_cb callback;
42
43         char **nameservers;
44         char **timeservers;
45         char *pac;
46
47         GDHCPClient *dhcp_client;
48 };
49
50 static GHashTable *network_table;
51
52 static void dhcp_free(struct connman_dhcp *dhcp)
53 {
54         g_strfreev(dhcp->nameservers);
55         g_strfreev(dhcp->timeservers);
56         g_free(dhcp->pac);
57
58         dhcp->nameservers = NULL;
59         dhcp->timeservers = NULL;
60         dhcp->pac = NULL;
61 }
62
63 /**
64  * dhcp_invalidate: Invalidate an existing DHCP lease
65  * @dhcp: pointer to the DHCP lease to invalidate.
66  * @callback: flag indicating whether or not to invoke the client callback
67  *            if present.
68  *
69  * Invalidates an existing DHCP lease, optionally invoking the client
70  * callback. The caller may wish to avoid the client callback invocation
71  * when the invocation of that callback might otherwise unnecessarily upset
72  * service state due to the IP configuration change implied by this
73  * invalidation.
74  */
75 static void dhcp_invalidate(struct connman_dhcp *dhcp, connman_bool_t callback)
76 {
77         struct connman_service *service;
78         struct connman_ipconfig *ipconfig;
79         int i;
80
81         DBG("dhcp %p callback %u", dhcp, callback);
82
83         if (dhcp == NULL)
84                 return;
85
86         service = connman_service_lookup_from_network(dhcp->network);
87         if (service == NULL)
88                 goto out;
89
90         ipconfig = __connman_service_get_ip4config(service);
91         if (ipconfig == NULL)
92                 goto out;
93
94         __connman_6to4_remove(ipconfig);
95
96         __connman_service_set_domainname(service, NULL);
97         __connman_service_set_pac(service, NULL);
98
99         if (dhcp->timeservers != NULL) {
100                 for (i = 0; dhcp->timeservers[i] != NULL; i++) {
101                         __connman_service_timeserver_remove(service,
102                                                         dhcp->timeservers[i]);
103                 }
104         }
105
106         if (dhcp->nameservers != NULL) {
107                 for (i = 0; dhcp->nameservers[i] != NULL; i++) {
108                         __connman_service_nameserver_remove(service,
109                                                 dhcp->nameservers[i], FALSE);
110                 }
111         }
112
113         __connman_ipconfig_set_dhcp_address(ipconfig,
114                                 __connman_ipconfig_get_local(ipconfig));
115         DBG("last address %s", __connman_ipconfig_get_dhcp_address(ipconfig));
116
117         __connman_ipconfig_address_remove(ipconfig);
118
119         __connman_ipconfig_set_local(ipconfig, NULL);
120         __connman_ipconfig_set_broadcast(ipconfig, NULL);
121         __connman_ipconfig_set_gateway(ipconfig, NULL);
122         __connman_ipconfig_set_prefixlen(ipconfig, 0);
123
124         if (dhcp->callback != NULL && callback)
125                 dhcp->callback(dhcp->network, FALSE);
126
127 out:
128         dhcp_free(dhcp);
129 }
130
131 static void dhcp_valid(struct connman_dhcp *dhcp)
132 {
133         if (dhcp->callback != NULL)
134                 dhcp->callback(dhcp->network, TRUE);
135 }
136
137 static void no_lease_cb(GDHCPClient *dhcp_client, gpointer user_data)
138 {
139         struct connman_dhcp *dhcp = user_data;
140
141         DBG("No lease available");
142
143         dhcp_invalidate(dhcp, TRUE);
144 }
145
146 static void lease_lost_cb(GDHCPClient *dhcp_client, gpointer user_data)
147 {
148         struct connman_dhcp *dhcp = user_data;
149
150         DBG("Lease lost");
151
152         dhcp_invalidate(dhcp, TRUE);
153 }
154
155 static void ipv4ll_lost_cb(GDHCPClient *dhcp_client, gpointer user_data)
156 {
157         struct connman_dhcp *dhcp = user_data;
158
159         DBG("Lease lost");
160
161         dhcp_invalidate(dhcp, TRUE);
162 }
163
164
165 static gboolean compare_string_arrays(char **array_a, char **array_b)
166 {
167         int i;
168
169         if (array_a == NULL || array_b == NULL)
170                 return FALSE;
171
172         if (g_strv_length(array_a) != g_strv_length(array_b))
173                 return FALSE;
174
175         for (i = 0; array_a[i] != NULL &&
176                              array_b[i] != NULL; i++) {
177                 if (g_strcmp0(array_a[i], array_b[i]) != 0)
178                         return FALSE;
179         }
180
181         return TRUE;
182 }
183
184 static void lease_available_cb(GDHCPClient *dhcp_client, gpointer user_data)
185 {
186         struct connman_dhcp *dhcp = user_data;
187         GList *list, *option = NULL;
188         char *address, *netmask = NULL, *gateway = NULL;
189         const char *c_address, *c_gateway;
190         char *domainname = NULL, *hostname = NULL;
191         char **nameservers, **timeservers, *pac = NULL;
192         int ns_entries;
193         struct connman_ipconfig *ipconfig;
194         struct connman_service *service;
195         unsigned char prefixlen, c_prefixlen;
196         gboolean ip_change;
197         int i;
198
199         DBG("Lease available");
200
201         service = connman_service_lookup_from_network(dhcp->network);
202         if (service == NULL) {
203                 connman_error("Can not lookup service");
204                 return;
205         }
206
207         ipconfig = __connman_service_get_ip4config(service);
208         if (ipconfig == NULL) {
209                 connman_error("Could not lookup ipconfig");
210                 return;
211         }
212
213         c_address = __connman_ipconfig_get_local(ipconfig);
214         c_gateway = __connman_ipconfig_get_gateway(ipconfig);
215         c_prefixlen = __connman_ipconfig_get_prefixlen(ipconfig);
216
217         address = g_dhcp_client_get_address(dhcp_client);
218
219         __connman_ipconfig_set_dhcp_address(ipconfig, address);
220         DBG("last address %s", address);
221
222         option = g_dhcp_client_get_option(dhcp_client, G_DHCP_SUBNET);
223         if (option != NULL)
224                 netmask = g_strdup(option->data);
225
226         option = g_dhcp_client_get_option(dhcp_client, G_DHCP_ROUTER);
227         if (option != NULL)
228                 gateway = g_strdup(option->data);
229
230         prefixlen = __connman_ipaddress_netmask_prefix_len(netmask);
231         if (prefixlen == 255)
232                 connman_warn("netmask: %s is invalid", netmask);
233
234         DBG("c_address %s", c_address);
235
236         if (address != NULL && c_address != NULL &&
237                                         g_strcmp0(address, c_address) != 0)
238                 ip_change = TRUE;
239         else if (gateway != NULL && c_gateway != NULL &&
240                                         g_strcmp0(gateway, c_gateway) != 0)
241                 ip_change = TRUE;
242         else if (prefixlen != c_prefixlen)
243                 ip_change = TRUE;
244         else if (c_address == NULL || c_gateway == NULL)
245                 ip_change = TRUE;
246         else
247                 ip_change = FALSE;
248
249         option = g_dhcp_client_get_option(dhcp_client, G_DHCP_DNS_SERVER);
250         ns_entries = g_list_length(option);
251         nameservers = g_try_new0(char *, ns_entries + 1);
252         if (nameservers != NULL) {
253                 for (i = 0, list = option; list; list = list->next, i++)
254                         nameservers[i] = g_strdup(list->data);
255                 nameservers[ns_entries] = NULL;
256         }
257
258         option = g_dhcp_client_get_option(dhcp_client, G_DHCP_DOMAIN_NAME);
259         if (option != NULL)
260                 domainname = g_strdup(option->data);
261
262         if (connman_setting_get_bool("AllowHostnameUpdates") == TRUE) {
263                 option = g_dhcp_client_get_option(dhcp_client,
264                                                 G_DHCP_HOST_NAME);
265                 if (option != NULL)
266                         hostname = g_strdup(option->data);
267         }
268
269         option = g_dhcp_client_get_option(dhcp_client, G_DHCP_NTP_SERVER);
270         ns_entries = g_list_length(option);
271         timeservers = g_try_new0(char *, ns_entries + 1);
272         if (timeservers != NULL) {
273                 for (i = 0, list = option; list; list = list->next, i++)
274                         timeservers[i] = g_strdup(list->data);
275                 timeservers[ns_entries] = NULL;
276         }
277
278         option = g_dhcp_client_get_option(dhcp_client, 252);
279         if (option != NULL)
280                 pac = g_strdup(option->data);
281
282         __connman_ipconfig_set_method(ipconfig, CONNMAN_IPCONFIG_METHOD_DHCP);
283
284         if (ip_change == TRUE) {
285                 __connman_ipconfig_set_local(ipconfig, address);
286                 __connman_ipconfig_set_prefixlen(ipconfig, prefixlen);
287                 __connman_ipconfig_set_gateway(ipconfig, gateway);
288         }
289
290         if (compare_string_arrays(nameservers, dhcp->nameservers) == FALSE) {
291                 if (dhcp->nameservers != NULL) {
292                         for (i = 0; dhcp->nameservers[i] != NULL; i++) {
293                                 __connman_service_nameserver_remove(service,
294                                                 dhcp->nameservers[i], FALSE);
295                         }
296                         g_strfreev(dhcp->nameservers);
297                 }
298
299                 dhcp->nameservers = nameservers;
300
301                 for (i = 0; dhcp->nameservers != NULL &&
302                                         dhcp->nameservers[i] != NULL; i++) {
303                         __connman_service_nameserver_append(service,
304                                                 dhcp->nameservers[i], FALSE);
305                 }
306         } else {
307                 g_strfreev(nameservers);
308         }
309
310         if (compare_string_arrays(timeservers, dhcp->timeservers) == FALSE) {
311                 if (dhcp->timeservers != NULL) {
312                         for (i = 0; dhcp->timeservers[i] != NULL; i++) {
313                                 __connman_service_timeserver_remove(service,
314                                                         dhcp->timeservers[i]);
315                         }
316                         g_strfreev(dhcp->timeservers);
317                 }
318
319                 dhcp->timeservers = timeservers;
320
321                 for (i = 0; dhcp->timeservers != NULL &&
322                                          dhcp->timeservers[i] != NULL; i++) {
323                         __connman_service_timeserver_append(service,
324                                                         dhcp->timeservers[i]);
325                 }
326         } else {
327                 g_strfreev(timeservers);
328         }
329
330         if (g_strcmp0(pac, dhcp->pac) != 0) {
331                 g_free(dhcp->pac);
332                 dhcp->pac = pac;
333
334                 __connman_service_set_pac(service, dhcp->pac);
335         }
336
337         __connman_service_set_domainname(service, domainname);
338
339         if (domainname != NULL)
340                 __connman_utsname_set_domainname(domainname);
341
342         if (hostname != NULL)
343                 __connman_utsname_set_hostname(hostname);
344
345         if (ip_change == TRUE)
346                 dhcp_valid(dhcp);
347
348         __connman_6to4_probe(service);
349
350         g_free(address);
351         g_free(netmask);
352         g_free(gateway);
353         g_free(domainname);
354         g_free(hostname);
355 }
356
357 static void ipv4ll_available_cb(GDHCPClient *dhcp_client, gpointer user_data)
358 {
359         struct connman_dhcp *dhcp = user_data;
360         char *address, *netmask;
361         struct connman_service *service;
362         struct connman_ipconfig *ipconfig;
363         unsigned char prefixlen;
364
365         DBG("IPV4LL available");
366
367         service = connman_service_lookup_from_network(dhcp->network);
368         if (service == NULL)
369                 return;
370
371         ipconfig = __connman_service_get_ip4config(service);
372         if (ipconfig == NULL)
373                 return;
374
375         address = g_dhcp_client_get_address(dhcp_client);
376         netmask = g_dhcp_client_get_netmask(dhcp_client);
377
378         prefixlen = __connman_ipaddress_netmask_prefix_len(netmask);
379
380         __connman_ipconfig_set_method(ipconfig, CONNMAN_IPCONFIG_METHOD_DHCP);
381         __connman_ipconfig_set_local(ipconfig, address);
382         __connman_ipconfig_set_prefixlen(ipconfig, prefixlen);
383         __connman_ipconfig_set_gateway(ipconfig, NULL);
384
385         dhcp_valid(dhcp);
386
387         g_free(address);
388         g_free(netmask);
389 }
390
391 static void dhcp_debug(const char *str, void *data)
392 {
393         connman_info("%s: %s\n", (const char *) data, str);
394 }
395
396 static int dhcp_request(struct connman_dhcp *dhcp)
397 {
398         struct connman_service *service;
399         struct connman_ipconfig *ipconfig;
400         GDHCPClient *dhcp_client;
401         GDHCPClientError error;
402         const char *hostname;
403         int index;
404
405         DBG("dhcp %p", dhcp);
406
407         index = connman_network_get_index(dhcp->network);
408
409         dhcp_client = g_dhcp_client_new(G_DHCP_IPV4, index, &error);
410         if (error != G_DHCP_CLIENT_ERROR_NONE)
411                 return -EINVAL;
412
413         if (getenv("CONNMAN_DHCP_DEBUG"))
414                 g_dhcp_client_set_debug(dhcp_client, dhcp_debug, "DHCP");
415
416         g_dhcp_client_set_id(dhcp_client);
417
418         hostname = connman_utsname_get_hostname();
419         if (hostname != NULL)
420                 g_dhcp_client_set_send(dhcp_client, G_DHCP_HOST_NAME, hostname);
421
422         g_dhcp_client_set_request(dhcp_client, G_DHCP_HOST_NAME);
423         g_dhcp_client_set_request(dhcp_client, G_DHCP_SUBNET);
424         g_dhcp_client_set_request(dhcp_client, G_DHCP_DNS_SERVER);
425         g_dhcp_client_set_request(dhcp_client, G_DHCP_DOMAIN_NAME);
426         g_dhcp_client_set_request(dhcp_client, G_DHCP_NTP_SERVER);
427         g_dhcp_client_set_request(dhcp_client, G_DHCP_ROUTER);
428         g_dhcp_client_set_request(dhcp_client, 252);
429
430         g_dhcp_client_register_event(dhcp_client,
431                         G_DHCP_CLIENT_EVENT_LEASE_AVAILABLE,
432                                                 lease_available_cb, dhcp);
433
434         g_dhcp_client_register_event(dhcp_client,
435                         G_DHCP_CLIENT_EVENT_IPV4LL_AVAILABLE,
436                                                 ipv4ll_available_cb, dhcp);
437
438         g_dhcp_client_register_event(dhcp_client,
439                         G_DHCP_CLIENT_EVENT_LEASE_LOST, lease_lost_cb, dhcp);
440
441         g_dhcp_client_register_event(dhcp_client,
442                         G_DHCP_CLIENT_EVENT_IPV4LL_LOST, ipv4ll_lost_cb, dhcp);
443
444         g_dhcp_client_register_event(dhcp_client,
445                         G_DHCP_CLIENT_EVENT_NO_LEASE, no_lease_cb, dhcp);
446
447         dhcp->dhcp_client = dhcp_client;
448
449         service = connman_service_lookup_from_network(dhcp->network);
450         ipconfig = __connman_service_get_ip4config(service);
451
452         /*
453          * Clear the addresses at startup so that lease callback will
454          * take the lease and set ip address properly.
455          */
456         __connman_ipconfig_clear_address(ipconfig);
457
458         return g_dhcp_client_start(dhcp_client,
459                                 __connman_ipconfig_get_dhcp_address(ipconfig));
460 }
461
462 static int dhcp_release(struct connman_dhcp *dhcp)
463 {
464         DBG("dhcp %p", dhcp);
465
466         if (dhcp->dhcp_client == NULL)
467                 return 0;
468
469         g_dhcp_client_stop(dhcp->dhcp_client);
470         g_dhcp_client_unref(dhcp->dhcp_client);
471
472         dhcp->dhcp_client = NULL;
473
474         return 0;
475 }
476
477 static void remove_network(gpointer user_data)
478 {
479         struct connman_dhcp *dhcp = user_data;
480
481         DBG("dhcp %p", dhcp);
482
483         dhcp_invalidate(dhcp, FALSE);
484         dhcp_release(dhcp);
485
486         g_free(dhcp);
487 }
488
489 int __connman_dhcp_start(struct connman_network *network, dhcp_cb callback)
490 {
491         struct connman_dhcp *dhcp;
492
493         DBG("");
494
495         dhcp = g_try_new0(struct connman_dhcp, 1);
496         if (dhcp == NULL)
497                 return -ENOMEM;
498
499         dhcp->network = network;
500         dhcp->callback = callback;
501
502         connman_network_ref(network);
503
504         g_hash_table_replace(network_table, network, dhcp);
505
506         return dhcp_request(dhcp);
507 }
508
509 void __connman_dhcp_stop(struct connman_network *network)
510 {
511         DBG("");
512
513         if (network_table == NULL)
514                 return;
515
516         if (g_hash_table_remove(network_table, network) == TRUE)
517                 connman_network_unref(network);
518 }
519
520 int __connman_dhcp_init(void)
521 {
522         DBG("");
523
524         network_table = g_hash_table_new_full(g_direct_hash, g_direct_equal,
525                                                         NULL, remove_network);
526
527         return 0;
528 }
529
530 void __connman_dhcp_cleanup(void)
531 {
532         DBG("");
533
534         g_hash_table_destroy(network_table);
535         network_table = NULL;
536 }