83d7dfb3a20afeb50edfa7ce52a21f9a09466bac
[platform/upstream/connman.git] / src / dhcp.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2013  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 #define RATE_LIMIT_INTERVAL     60      /* delay between successive attempts */
40
41 struct connman_dhcp {
42         struct connman_network *network;
43         dhcp_cb callback;
44
45         char **nameservers;
46         char **timeservers;
47         char *pac;
48
49         unsigned int timeout;
50
51         GDHCPClient *ipv4ll_client;
52         GDHCPClient *dhcp_client;
53         char *ipv4ll_debug_prefix;
54         char *dhcp_debug_prefix;
55 };
56
57 static GHashTable *network_table;
58 static bool ipv4ll_running;
59
60 static void dhcp_free(struct connman_dhcp *dhcp)
61 {
62         g_strfreev(dhcp->nameservers);
63         g_strfreev(dhcp->timeservers);
64         g_free(dhcp->pac);
65
66         dhcp->nameservers = NULL;
67         dhcp->timeservers = NULL;
68         dhcp->pac = NULL;
69
70         g_free(dhcp);
71 }
72
73 /**
74  * dhcp_invalidate: Invalidate an existing DHCP lease
75  * @dhcp: pointer to the DHCP lease to invalidate.
76  * @callback: flag indicating whether or not to invoke the client callback
77  *            if present.
78  *
79  * Invalidates an existing DHCP lease, optionally invoking the client
80  * callback. The caller may wish to avoid the client callback invocation
81  * when the invocation of that callback might otherwise unnecessarily upset
82  * service state due to the IP configuration change implied by this
83  * invalidation.
84  */
85 static void dhcp_invalidate(struct connman_dhcp *dhcp, bool callback)
86 {
87         struct connman_service *service;
88         struct connman_ipconfig *ipconfig;
89         int i;
90
91         DBG("dhcp %p callback %u", dhcp, callback);
92
93         if (!dhcp)
94                 return;
95
96         service = connman_service_lookup_from_network(dhcp->network);
97         if (!service)
98                 return;
99
100         ipconfig = __connman_service_get_ip4config(service);
101         if (!ipconfig)
102                 return;
103
104         __connman_6to4_remove(ipconfig);
105
106         __connman_service_set_domainname(service, NULL);
107         __connman_service_set_pac(service, NULL);
108
109         if (dhcp->timeservers) {
110                 for (i = 0; dhcp->timeservers[i]; i++) {
111                         __connman_service_timeserver_remove(service,
112                                                         dhcp->timeservers[i]);
113                 }
114         }
115
116         if (dhcp->nameservers) {
117                 for (i = 0; dhcp->nameservers[i]; i++) {
118                         __connman_service_nameserver_remove(service,
119                                                 dhcp->nameservers[i], false);
120                 }
121         }
122
123         __connman_ipconfig_set_dhcp_address(ipconfig,
124                                 __connman_ipconfig_get_local(ipconfig));
125         DBG("last address %s", __connman_ipconfig_get_dhcp_address(ipconfig));
126
127         __connman_ipconfig_address_remove(ipconfig);
128
129         __connman_ipconfig_set_local(ipconfig, NULL);
130         __connman_ipconfig_set_broadcast(ipconfig, NULL);
131         __connman_ipconfig_set_gateway(ipconfig, NULL);
132         __connman_ipconfig_set_prefixlen(ipconfig, 0);
133
134         if (dhcp->callback && callback)
135                 dhcp->callback(dhcp->network, false, NULL);
136 }
137
138 static void dhcp_valid(struct connman_dhcp *dhcp)
139 {
140         if (dhcp->callback)
141                 dhcp->callback(dhcp->network, true, NULL);
142 }
143
144 static void dhcp_debug(const char *str, void *data)
145 {
146         connman_info("%s: %s", (const char *) data, str);
147 }
148
149 static void ipv4ll_stop_client(struct connman_dhcp *dhcp)
150 {
151         if (!dhcp->ipv4ll_client)
152                 return;
153
154         g_dhcp_client_stop(dhcp->ipv4ll_client);
155         g_dhcp_client_unref(dhcp->ipv4ll_client);
156         dhcp->ipv4ll_client = NULL;
157         ipv4ll_running = false;
158
159         g_free(dhcp->ipv4ll_debug_prefix);
160         dhcp->ipv4ll_debug_prefix = NULL;
161 }
162
163 static void ipv4ll_lost_cb(GDHCPClient *dhcp_client, gpointer user_data);
164 static void ipv4ll_available_cb(GDHCPClient *ipv4ll_client, gpointer user_data);
165
166 static int ipv4ll_start_client(struct connman_dhcp *dhcp)
167 {
168         GDHCPClient *ipv4ll_client;
169         GDHCPClientError error;
170         const char *hostname;
171         int index;
172         int err;
173
174         if (dhcp->ipv4ll_client)
175                 return -EALREADY;
176
177         index = connman_network_get_index(dhcp->network);
178
179         ipv4ll_client = g_dhcp_client_new(G_DHCP_IPV4LL, index, &error);
180         if (error != G_DHCP_CLIENT_ERROR_NONE)
181                 return -EINVAL;
182
183         if (getenv("CONNMAN_DHCP_DEBUG")) {
184                 dhcp->ipv4ll_debug_prefix = g_strdup_printf("IPv4LL index %d",
185                                                         index);
186                 g_dhcp_client_set_debug(ipv4ll_client, dhcp_debug,
187                                         dhcp->ipv4ll_debug_prefix);
188         }
189
190         g_dhcp_client_set_id(ipv4ll_client);
191
192         hostname = connman_utsname_get_hostname();
193         if (hostname)
194                 g_dhcp_client_set_send(ipv4ll_client, G_DHCP_HOST_NAME,
195                                         hostname);
196
197         g_dhcp_client_register_event(ipv4ll_client,
198                         G_DHCP_CLIENT_EVENT_IPV4LL_LOST, ipv4ll_lost_cb, dhcp);
199
200         g_dhcp_client_register_event(ipv4ll_client,
201                         G_DHCP_CLIENT_EVENT_IPV4LL_AVAILABLE,
202                                                 ipv4ll_available_cb, dhcp);
203
204         dhcp->ipv4ll_client = ipv4ll_client;
205
206         err = g_dhcp_client_start(dhcp->ipv4ll_client, NULL);
207         if (err < 0) {
208                 ipv4ll_stop_client(dhcp);
209                 return err;
210         }
211
212         ipv4ll_running = true;
213         return 0;
214 }
215
216 static gboolean dhcp_retry_cb(gpointer user_data)
217 {
218         struct connman_dhcp *dhcp = user_data;
219         struct connman_service *service;
220         struct connman_ipconfig *ipconfig;
221
222         dhcp->timeout = 0;
223
224         service = connman_service_lookup_from_network(dhcp->network);
225         ipconfig = __connman_service_get_ip4config(service);
226
227         g_dhcp_client_start(dhcp->dhcp_client,
228                                 __connman_ipconfig_get_dhcp_address(ipconfig));
229
230         return FALSE;
231 }
232
233 static void no_lease_cb(GDHCPClient *dhcp_client, gpointer user_data)
234 {
235         struct connman_dhcp *dhcp = user_data;
236         int err;
237
238         DBG("No lease available ipv4ll %d client %p", ipv4ll_running,
239                 dhcp->ipv4ll_client);
240
241         dhcp->timeout = g_timeout_add_seconds(RATE_LIMIT_INTERVAL,
242                                                 dhcp_retry_cb,
243                                                 dhcp);
244         if (ipv4ll_running)
245                 return;
246
247         err = ipv4ll_start_client(dhcp);
248         if (err < 0)
249                 DBG("Cannot start ipv4ll client (%d/%s)", err, strerror(-err));
250
251         /* Only notify upper layer if we have a problem */
252         dhcp_invalidate(dhcp, !ipv4ll_running);
253 }
254
255 static void lease_lost_cb(GDHCPClient *dhcp_client, gpointer user_data)
256 {
257         struct connman_dhcp *dhcp = user_data;
258
259         DBG("Lease lost");
260
261         /* Upper layer will decide what to do, e.g. nothing or retry. */
262         dhcp_invalidate(dhcp, true);
263 }
264
265 static void ipv4ll_lost_cb(GDHCPClient *dhcp_client, gpointer user_data)
266 {
267         struct connman_dhcp *dhcp = user_data;
268
269         DBG("Lease lost");
270
271         ipv4ll_stop_client(dhcp);
272
273         /*
274          * Since we lost our IPv4LL configuration we might as notify
275          * the upper layers.
276          */
277         dhcp_invalidate(dhcp, true);
278 }
279
280 static bool compare_string_arrays(char **array_a, char **array_b)
281 {
282         int i;
283
284         if (!array_a || !array_b)
285                 return false;
286
287         if (g_strv_length(array_a) != g_strv_length(array_b))
288                 return false;
289
290         for (i = 0; array_a[i] &&
291                              array_b[i]; i++) {
292                 if (g_strcmp0(array_a[i], array_b[i]) != 0)
293                         return false;
294         }
295
296         return true;
297 }
298
299 static void lease_available_cb(GDHCPClient *dhcp_client, gpointer user_data)
300 {
301         struct connman_dhcp *dhcp = user_data;
302         GList *list, *option = NULL;
303         char *address, *netmask = NULL, *gateway = NULL;
304         const char *c_address, *c_gateway;
305         char **nameservers, **timeservers, *pac = NULL;
306         int ns_entries;
307         struct connman_ipconfig *ipconfig;
308         struct connman_service *service;
309         unsigned char prefixlen, c_prefixlen;
310         bool ip_change;
311         int i;
312
313         DBG("Lease available");
314
315         if (dhcp->ipv4ll_client) {
316                 ipv4ll_stop_client(dhcp);
317                 dhcp_invalidate(dhcp, false);
318         }
319
320         service = connman_service_lookup_from_network(dhcp->network);
321         if (!service) {
322                 connman_error("Can not lookup service");
323                 return;
324         }
325
326         ipconfig = __connman_service_get_ip4config(service);
327         if (!ipconfig) {
328                 connman_error("Could not lookup ipconfig");
329                 return;
330         }
331
332         c_address = __connman_ipconfig_get_local(ipconfig);
333         c_gateway = __connman_ipconfig_get_gateway(ipconfig);
334         c_prefixlen = __connman_ipconfig_get_prefixlen(ipconfig);
335
336         address = g_dhcp_client_get_address(dhcp_client);
337
338         __connman_ipconfig_set_dhcp_address(ipconfig, address);
339         DBG("last address %s", address);
340
341         option = g_dhcp_client_get_option(dhcp_client, G_DHCP_SUBNET);
342         if (option)
343                 netmask = g_strdup(option->data);
344
345         option = g_dhcp_client_get_option(dhcp_client, G_DHCP_ROUTER);
346         if (option)
347                 gateway = g_strdup(option->data);
348
349         prefixlen = __connman_ipaddress_netmask_prefix_len(netmask);
350         if (prefixlen == 255)
351                 connman_warn("netmask: %s is invalid", netmask);
352
353         DBG("c_address %s", c_address);
354
355         if (address && c_address && g_strcmp0(address, c_address) != 0)
356                 ip_change = true;
357         else if (gateway && c_gateway && g_strcmp0(gateway, c_gateway) != 0)
358                 ip_change = true;
359         else if (prefixlen != c_prefixlen)
360                 ip_change = true;
361         else if (!c_address || !c_gateway)
362                 ip_change = true;
363         else
364                 ip_change = false;
365
366         option = g_dhcp_client_get_option(dhcp_client, G_DHCP_DNS_SERVER);
367         ns_entries = g_list_length(option);
368         nameservers = g_try_new0(char *, ns_entries + 1);
369         if (nameservers) {
370                 for (i = 0, list = option; list; list = list->next, i++)
371                         nameservers[i] = g_strdup(list->data);
372                 nameservers[ns_entries] = NULL;
373         }
374
375         option = g_dhcp_client_get_option(dhcp_client, G_DHCP_DOMAIN_NAME);
376         if (option)
377                 __connman_service_set_domainname(service, option->data);
378
379         option = g_dhcp_client_get_option(dhcp_client, G_DHCP_HOST_NAME);
380         if (option)
381                 __connman_service_set_hostname(service, option->data);
382
383         option = g_dhcp_client_get_option(dhcp_client, G_DHCP_NTP_SERVER);
384         ns_entries = g_list_length(option);
385         timeservers = g_try_new0(char *, ns_entries + 1);
386         if (timeservers) {
387                 for (i = 0, list = option; list; list = list->next, i++)
388                         timeservers[i] = g_strdup(list->data);
389                 timeservers[ns_entries] = NULL;
390         }
391
392         option = g_dhcp_client_get_option(dhcp_client, 252);
393         if (option)
394                 pac = g_strdup(option->data);
395
396         __connman_ipconfig_set_method(ipconfig, CONNMAN_IPCONFIG_METHOD_DHCP);
397
398         if (ip_change) {
399                 __connman_ipconfig_set_local(ipconfig, address);
400                 __connman_ipconfig_set_prefixlen(ipconfig, prefixlen);
401                 __connman_ipconfig_set_gateway(ipconfig, gateway);
402         }
403
404         if (!compare_string_arrays(nameservers, dhcp->nameservers)) {
405                 if (dhcp->nameservers) {
406                         for (i = 0; dhcp->nameservers[i]; i++) {
407                                 __connman_service_nameserver_remove(service,
408                                                 dhcp->nameservers[i], false);
409                         }
410                         g_strfreev(dhcp->nameservers);
411                 }
412
413                 dhcp->nameservers = nameservers;
414
415                 for (i = 0; dhcp->nameservers &&
416                                         dhcp->nameservers[i]; i++) {
417                         __connman_service_nameserver_append(service,
418                                                 dhcp->nameservers[i], false);
419                 }
420         } else {
421                 g_strfreev(nameservers);
422         }
423
424         if (!compare_string_arrays(timeservers, dhcp->timeservers)) {
425                 if (dhcp->timeservers) {
426                         for (i = 0; dhcp->timeservers[i]; i++) {
427                                 __connman_service_timeserver_remove(service,
428                                                         dhcp->timeservers[i]);
429                         }
430                         g_strfreev(dhcp->timeservers);
431                 }
432
433                 dhcp->timeservers = timeservers;
434
435                 for (i = 0; dhcp->timeservers &&
436                                          dhcp->timeservers[i]; i++) {
437                         __connman_service_timeserver_append(service,
438                                                         dhcp->timeservers[i]);
439                 }
440         } else {
441                 g_strfreev(timeservers);
442         }
443
444         if (g_strcmp0(pac, dhcp->pac) != 0) {
445                 g_free(dhcp->pac);
446                 dhcp->pac = pac;
447
448                 __connman_service_set_pac(service, dhcp->pac);
449         }
450
451         if (ip_change)
452                 dhcp_valid(dhcp);
453
454         __connman_6to4_probe(service);
455
456         g_free(address);
457         g_free(netmask);
458         g_free(gateway);
459 }
460
461 static void ipv4ll_available_cb(GDHCPClient *ipv4ll_client, gpointer user_data)
462 {
463         struct connman_dhcp *dhcp = user_data;
464         char *address, *netmask;
465         struct connman_service *service;
466         struct connman_ipconfig *ipconfig;
467         unsigned char prefixlen;
468
469         DBG("IPV4LL available");
470
471         service = connman_service_lookup_from_network(dhcp->network);
472         if (!service)
473                 return;
474
475         ipconfig = __connman_service_get_ip4config(service);
476         if (!ipconfig)
477                 return;
478
479         address = g_dhcp_client_get_address(ipv4ll_client);
480         netmask = g_dhcp_client_get_netmask(ipv4ll_client);
481
482         prefixlen = __connman_ipaddress_netmask_prefix_len(netmask);
483
484         __connman_ipconfig_set_method(ipconfig, CONNMAN_IPCONFIG_METHOD_DHCP);
485         __connman_ipconfig_set_local(ipconfig, address);
486         __connman_ipconfig_set_prefixlen(ipconfig, prefixlen);
487         __connman_ipconfig_set_gateway(ipconfig, NULL);
488
489         dhcp_valid(dhcp);
490
491         g_free(address);
492         g_free(netmask);
493 }
494
495 static int dhcp_initialize(struct connman_dhcp *dhcp)
496 {
497         struct connman_service *service;
498         GDHCPClient *dhcp_client;
499         GDHCPClientError error;
500         const char *hostname;
501         int index;
502
503         DBG("dhcp %p", dhcp);
504
505         index = connman_network_get_index(dhcp->network);
506
507         dhcp_client = g_dhcp_client_new(G_DHCP_IPV4, index, &error);
508         if (error != G_DHCP_CLIENT_ERROR_NONE)
509                 return -EINVAL;
510
511         if (getenv("CONNMAN_DHCP_DEBUG")) {
512                 dhcp->dhcp_debug_prefix = g_strdup_printf("DHCP index %d",
513                                                         index);
514                 g_dhcp_client_set_debug(dhcp_client, dhcp_debug,
515                                         dhcp->dhcp_debug_prefix);
516         }
517
518         g_dhcp_client_set_id(dhcp_client);
519
520         service = connman_service_lookup_from_network(dhcp->network);
521
522         hostname = __connman_service_get_hostname(service);
523         if (!hostname)
524                 hostname = connman_utsname_get_hostname();
525
526         if (hostname)
527                 g_dhcp_client_set_send(dhcp_client, G_DHCP_HOST_NAME, hostname);
528
529         g_dhcp_client_set_request(dhcp_client, G_DHCP_HOST_NAME);
530         g_dhcp_client_set_request(dhcp_client, G_DHCP_SUBNET);
531         g_dhcp_client_set_request(dhcp_client, G_DHCP_DNS_SERVER);
532         g_dhcp_client_set_request(dhcp_client, G_DHCP_DOMAIN_NAME);
533         g_dhcp_client_set_request(dhcp_client, G_DHCP_NTP_SERVER);
534         g_dhcp_client_set_request(dhcp_client, G_DHCP_ROUTER);
535         g_dhcp_client_set_request(dhcp_client, 252);
536
537         g_dhcp_client_register_event(dhcp_client,
538                         G_DHCP_CLIENT_EVENT_LEASE_AVAILABLE,
539                                                 lease_available_cb, dhcp);
540
541         g_dhcp_client_register_event(dhcp_client,
542                         G_DHCP_CLIENT_EVENT_LEASE_LOST, lease_lost_cb, dhcp);
543
544         g_dhcp_client_register_event(dhcp_client,
545                         G_DHCP_CLIENT_EVENT_NO_LEASE, no_lease_cb, dhcp);
546
547         dhcp->dhcp_client = dhcp_client;
548
549         return 0;
550 }
551
552 static int dhcp_release(struct connman_dhcp *dhcp)
553 {
554         DBG("dhcp %p", dhcp);
555
556         if (dhcp->timeout > 0)
557                 g_source_remove(dhcp->timeout);
558
559         if (dhcp->dhcp_client) {
560                 g_dhcp_client_stop(dhcp->dhcp_client);
561                 g_dhcp_client_unref(dhcp->dhcp_client);
562         }
563
564         dhcp->dhcp_client = NULL;
565
566         g_free(dhcp->dhcp_debug_prefix);
567         dhcp->dhcp_debug_prefix = NULL;
568
569         ipv4ll_stop_client(dhcp);
570
571         return 0;
572 }
573
574 int __connman_dhcp_start(struct connman_network *network, dhcp_cb callback)
575 {
576         struct connman_service *service;
577         struct connman_ipconfig *ipconfig;
578         const char *last_addr = NULL;
579         struct connman_dhcp *dhcp;
580
581         DBG("");
582
583         service = connman_service_lookup_from_network(network);
584         if (!service)
585                 return -EINVAL;
586
587         ipconfig = __connman_service_get_ip4config(service);
588         if (ipconfig)
589                 last_addr = __connman_ipconfig_get_dhcp_address(ipconfig);
590
591         dhcp = g_hash_table_lookup(network_table, network);
592         if (!dhcp) {
593
594                 dhcp = g_try_new0(struct connman_dhcp, 1);
595                 if (!dhcp)
596                         return -ENOMEM;
597
598                 dhcp->network = network;
599                 connman_network_ref(network);
600
601                 g_hash_table_insert(network_table, network, dhcp);
602
603                 dhcp_initialize(dhcp);
604         }
605
606         dhcp->callback = callback;
607
608         return g_dhcp_client_start(dhcp->dhcp_client, last_addr);
609 }
610
611 void __connman_dhcp_stop(struct connman_network *network)
612 {
613         struct connman_dhcp *dhcp;
614
615         DBG("network_table %p network %p", network_table, network);
616
617         if (!network_table)
618                 return;
619
620         dhcp = g_hash_table_lookup(network_table, network);
621         if (dhcp) {
622                 g_hash_table_remove(network_table, network);
623                 connman_network_unref(network);
624                 dhcp_release(dhcp);
625                 dhcp_invalidate(dhcp, false);
626                 dhcp_free(dhcp);
627         }
628 }
629
630 int __connman_dhcp_init(void)
631 {
632         DBG("");
633
634         network_table = g_hash_table_new_full(g_direct_hash, g_direct_equal,
635                                                         NULL, NULL);
636
637         return 0;
638 }
639
640 void __connman_dhcp_cleanup(void)
641 {
642         DBG("");
643
644         g_hash_table_destroy(network_table);
645         network_table = NULL;
646 }