resolver: Make sure we do not use already removed timer
[platform/upstream/connman.git] / src / resolver.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
26 #define _GNU_SOURCE
27 #include <stdio.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <unistd.h>
31 #include <string.h>
32 #include <sys/stat.h>
33 #include <resolv.h>
34 #include <netdb.h>
35
36 #include "connman.h"
37
38 #define RESOLVER_FLAG_PUBLIC (1 << 0)
39
40 /*
41  * Threshold for RDNSS lifetime. Will be used to trigger RS
42  * before RDNSS entries actually expire
43  */
44 #define RESOLVER_LIFETIME_REFRESH_THRESHOLD 0.8
45
46 struct entry_data {
47         char *interface;
48         char *domain;
49         char *server;
50         int family;
51         unsigned int flags;
52         unsigned int lifetime;
53         guint timeout;
54 };
55
56 static GSList *entry_list = NULL;
57 static connman_bool_t dnsproxy_enabled = FALSE;
58
59 struct resolvfile_entry {
60         char *interface;
61         char *domain;
62         char *server;
63 };
64
65 static GList *resolvfile_list = NULL;
66
67 static void resolvfile_remove_entries(GList *entries)
68 {
69         GList *list;
70
71         for (list = entries; list; list = list->next) {
72                 struct resolvfile_entry *entry = list->data;
73
74                 resolvfile_list = g_list_remove(resolvfile_list, entry);
75
76                 g_free(entry->server);
77                 g_free(entry->domain);
78                 g_free(entry->interface);
79                 g_free(entry);
80         }
81
82         g_list_free(entries);
83 }
84
85 static int resolvfile_export(void)
86 {
87         GList *list;
88         GString *content;
89         int fd, err;
90         unsigned int count;
91         mode_t old_umask;
92
93         content = g_string_new("# Generated by Connection Manager\n");
94
95         /*
96          * Domains and nameservers are added in reverse so that the most
97          * recently appended entry is the primary one. No more than
98          * MAXDNSRCH/MAXNS entries are used.
99          */
100
101         for (count = 0, list = g_list_last(resolvfile_list);
102                                                 list && (count < MAXDNSRCH);
103                                                 list = g_list_previous(list)) {
104                 struct resolvfile_entry *entry = list->data;
105
106                 if (!entry->domain)
107                         continue;
108
109                 if (count == 0)
110                         g_string_append_printf(content, "search ");
111
112                 g_string_append_printf(content, "%s ", entry->domain);
113                 count++;
114         }
115
116         if (count)
117                 g_string_append_printf(content, "\n");
118
119         for (count = 0, list = g_list_last(resolvfile_list);
120                                                 list && (count < MAXNS);
121                                                 list = g_list_previous(list)) {
122                 struct resolvfile_entry *entry = list->data;
123
124                 if (!entry->server)
125                         continue;
126
127                 g_string_append_printf(content, "nameserver %s\n",
128                                                                 entry->server);
129                 count++;
130         }
131
132         old_umask = umask(022);
133
134         fd = open("/etc/resolv.conf", O_RDWR | O_CREAT | O_CLOEXEC,
135                                         S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
136         if (fd < 0) {
137                 err = -errno;
138                 goto done;
139         }
140
141         if (ftruncate(fd, 0) < 0) {
142                 err = -errno;
143                 goto failed;
144         }
145
146         err = 0;
147
148         if (write(fd, content->str, content->len) < 0)
149                 err = -errno;
150
151 failed:
152         close(fd);
153
154 done:
155         g_string_free(content, TRUE);
156         umask(old_umask);
157
158         return err;
159 }
160
161 int __connman_resolvfile_append(const char *interface, const char *domain,
162                                                         const char *server)
163 {
164         struct resolvfile_entry *entry;
165
166         DBG("interface %s server %s", interface, server);
167
168         if (interface == NULL)
169                 return -ENOENT;
170
171         entry = g_try_new0(struct resolvfile_entry, 1);
172         if (entry == NULL)
173                 return -ENOMEM;
174
175         entry->interface = g_strdup(interface);
176         entry->domain = g_strdup(domain);
177         entry->server = g_strdup(server);
178
179         resolvfile_list = g_list_append(resolvfile_list, entry);
180
181         return resolvfile_export();
182 }
183
184 int __connman_resolvfile_remove(const char *interface, const char *domain,
185                                                         const char *server)
186 {
187         GList *list, *matches = NULL;
188
189         DBG("interface %s server %s", interface, server);
190
191         for (list = resolvfile_list; list; list = g_list_next(list)) {
192                 struct resolvfile_entry *entry = list->data;
193
194                 if (interface != NULL &&
195                                 g_strcmp0(entry->interface, interface) != 0)
196                         continue;
197
198                 if (domain != NULL && g_strcmp0(entry->domain, domain) != 0)
199                         continue;
200
201                 if (g_strcmp0(entry->server, server) != 0)
202                         continue;
203
204                 matches = g_list_append(matches, entry);
205         }
206
207         resolvfile_remove_entries(matches);
208
209         return resolvfile_export();
210 }
211
212 static void remove_entries(GSList *entries)
213 {
214         GSList *list;
215
216         for (list = entries; list; list = list->next) {
217                 struct entry_data *entry = list->data;
218
219                 entry_list = g_slist_remove(entry_list, entry);
220
221                 if (dnsproxy_enabled == TRUE) {
222                         __connman_dnsproxy_remove(entry->interface, entry->domain,
223                                                         entry->server);
224                 } else {
225                         __connman_resolvfile_remove(entry->interface, entry->domain,
226                                                         entry->server);
227                 }
228
229                 if (entry->timeout)
230                         g_source_remove(entry->timeout);
231                 g_free(entry->server);
232                 g_free(entry->domain);
233                 g_free(entry->interface);
234                 g_free(entry);
235         }
236
237         g_slist_free(entries);
238 }
239
240 static gboolean resolver_expire_cb(gpointer user_data)
241 {
242         struct entry_data *entry = user_data;
243         GSList *list;
244         int index;
245
246         DBG("interface %s domain %s server %s",
247                         entry->interface, entry->domain, entry->server);
248
249         list = g_slist_prepend(NULL, entry);
250
251         index = connman_inet_ifindex(entry->interface);
252         if (index >= 0) {
253                 struct connman_service *service;
254                 service = __connman_service_lookup_from_index(index);
255                 if (service != NULL)
256                         __connman_service_nameserver_remove(service,
257                                                         entry->server, TRUE);
258         }
259
260         remove_entries(list);
261
262         return FALSE;
263 }
264
265 static gboolean resolver_refresh_cb(gpointer user_data)
266 {
267         struct entry_data *entry = user_data;
268         int index;
269         unsigned int interval;
270         struct connman_service *service = NULL;
271
272         /* Round up what we have left from lifetime */
273         interval = entry->lifetime *
274                 (1 - RESOLVER_LIFETIME_REFRESH_THRESHOLD) + 1.0;
275
276         DBG("RDNSS start interface %s domain %s "
277                         "server %s remaining lifetime %d",
278                         entry->interface, entry->domain,
279                         entry->server, interval);
280
281         entry->timeout = g_timeout_add_seconds(interval,
282                         resolver_expire_cb, entry);
283
284         index = connman_inet_ifindex(entry->interface);
285         if (index >= 0) {
286                 service = __connman_service_lookup_from_index(index);
287                 if (service != NULL) {
288                         /*
289                          * Send Router Solicitation to refresh RDNSS entries
290                          * before their lifetime expires
291                          */
292                         __connman_refresh_rs_ipv6(
293                                         __connman_service_get_network(service),
294                                         index);
295                 }
296         }
297         return FALSE;
298 }
299
300 static int append_resolver(const char *interface, const char *domain,
301                                 const char *server, unsigned int lifetime,
302                                                         unsigned int flags)
303 {
304         struct entry_data *entry;
305         unsigned int interval;
306
307         DBG("interface %s domain %s server %s lifetime %d flags %d",
308                                 interface, domain, server, lifetime, flags);
309
310         if (server == NULL && domain == NULL)
311                 return -EINVAL;
312
313         entry = g_try_new0(struct entry_data, 1);
314         if (entry == NULL)
315                 return -ENOMEM;
316
317         entry->interface = g_strdup(interface);
318         entry->domain = g_strdup(domain);
319         entry->server = g_strdup(server);
320         entry->flags = flags;
321         entry->lifetime = lifetime;
322
323         if (server != NULL)
324                 entry->family = connman_inet_check_ipaddress(server);
325
326         if (lifetime) {
327                 int index;
328                 interval = lifetime * RESOLVER_LIFETIME_REFRESH_THRESHOLD;
329
330                 DBG("RDNSS start interface %s domain %s "
331                                 "server %s lifetime threshold %d",
332                                 interface, domain, server, interval);
333
334                 entry->timeout = g_timeout_add_seconds(interval,
335                                 resolver_refresh_cb, entry);
336
337                 /*
338                  * We update the service only for those nameservers
339                  * that are automagically added via netlink (lifetime > 0)
340                  */
341                 index = connman_inet_ifindex(interface);
342                 if (server != NULL && index >= 0) {
343                         struct connman_service *service;
344                         service = __connman_service_lookup_from_index(index);
345                         if (service != NULL)
346                                 __connman_service_nameserver_append(service,
347                                                                 server, TRUE);
348                 }
349         }
350         entry_list = g_slist_append(entry_list, entry);
351
352         if (dnsproxy_enabled == TRUE)
353                 __connman_dnsproxy_append(interface, domain, server);
354         else
355                 __connman_resolvfile_append(interface, domain, server);
356
357         return 0;
358 }
359
360 /**
361  * connman_resolver_append:
362  * @interface: network interface
363  * @domain: domain limitation
364  * @server: server address
365  *
366  * Append resolver server address to current list
367  */
368 int connman_resolver_append(const char *interface, const char *domain,
369                                                 const char *server)
370 {
371         GSList *list;
372
373         DBG("interface %s domain %s server %s", interface, domain, server);
374
375         if (server == NULL && domain == NULL)
376                 return -EINVAL;
377
378         for (list = entry_list; list; list = list->next) {
379                 struct entry_data *entry = list->data;
380
381                 if (entry->timeout > 0)
382                         continue;
383
384                 if (g_strcmp0(entry->interface, interface) == 0 &&
385                                 g_strcmp0(entry->domain, domain) == 0 &&
386                                 g_strcmp0(entry->server, server) == 0)
387                         return -EEXIST;
388         }
389
390         return append_resolver(interface, domain, server, 0, 0);
391 }
392
393 /**
394  * connman_resolver_append_lifetime:
395  * @interface: network interface
396  * @domain: domain limitation
397  * @server: server address
398  * @timeout: server lifetime in seconds
399  *
400  * Append resolver server address to current list
401  */
402 int connman_resolver_append_lifetime(const char *interface, const char *domain,
403                                 const char *server, unsigned int lifetime)
404 {
405         GSList *list;
406         unsigned int interval;
407
408         DBG("interface %s domain %s server %s lifetime %d",
409                                 interface, domain, server, lifetime);
410
411         if (server == NULL && domain == NULL)
412                 return -EINVAL;
413
414         for (list = entry_list; list; list = list->next) {
415                 struct entry_data *entry = list->data;
416
417                 if (entry->timeout == 0 ||
418                                 g_strcmp0(entry->interface, interface) != 0 ||
419                                 g_strcmp0(entry->domain, domain) != 0 ||
420                                 g_strcmp0(entry->server, server) != 0)
421                         continue;
422
423                 g_source_remove(entry->timeout);
424
425                 if (lifetime == 0) {
426                         resolver_expire_cb(entry);
427                         return 0;
428                 }
429
430                 interval = lifetime * RESOLVER_LIFETIME_REFRESH_THRESHOLD;
431
432                 DBG("RDNSS start interface %s domain %s "
433                                 "server %s lifetime threshold %d",
434                                 interface, domain, server, interval);
435
436                 entry->timeout = g_timeout_add_seconds(interval,
437                                 resolver_refresh_cb, entry);
438                 return 0;
439         }
440
441         return append_resolver(interface, domain, server, lifetime, 0);
442 }
443
444 /**
445  * connman_resolver_remove:
446  * @interface: network interface
447  * @domain: domain limitation
448  * @server: server address
449  *
450  * Remover resolver server address from current list
451  */
452 int connman_resolver_remove(const char *interface, const char *domain,
453                                                         const char *server)
454 {
455         GSList *list, *matches = NULL;
456
457         DBG("interface %s domain %s server %s", interface, domain, server);
458
459         for (list = entry_list; list; list = list->next) {
460                 struct entry_data *entry = list->data;
461
462                 if (g_strcmp0(entry->interface, interface) != 0)
463                         continue;
464
465                 if (g_strcmp0(entry->domain, domain) != 0)
466                         continue;
467
468                 if (g_strcmp0(entry->server, server) != 0)
469                         continue;
470
471                 matches = g_slist_prepend(matches, entry);
472                 break;
473         }
474
475         if (matches == NULL)
476                 return -ENOENT;
477
478         remove_entries(matches);
479
480         return 0;
481 }
482
483 /**
484  * connman_resolver_remove_all:
485  * @interface: network interface
486  *
487  * Remove all resolver server address for the specified interface
488  */
489 int connman_resolver_remove_all(const char *interface)
490 {
491         GSList *list, *matches = NULL;
492
493         DBG("interface %s", interface);
494
495         if (interface == NULL)
496                 return -EINVAL;
497
498         for (list = entry_list; list; list = list->next) {
499                 struct entry_data *entry = list->data;
500
501                 if (g_strcmp0(entry->interface, interface) != 0)
502                         continue;
503
504                 matches = g_slist_prepend(matches, entry);
505         }
506
507         if (matches == NULL)
508                 return -ENOENT;
509
510         remove_entries(matches);
511
512         return 0;
513 }
514
515 /**
516  * connman_resolver_flush:
517  *
518  * Flush pending resolver requests
519  */
520 void connman_resolver_flush(void)
521 {
522         if (dnsproxy_enabled == TRUE)
523                 __connman_dnsproxy_flush();
524
525         return;
526 }
527
528 int __connman_resolver_redo_servers(const char *interface)
529 {
530         GSList *list;
531
532         if (dnsproxy_enabled == FALSE)
533                 return 0;
534
535         DBG("interface %s", interface);
536
537         if (interface == NULL)
538                 return -EINVAL;
539
540         for (list = entry_list; list; list = list->next) {
541                 struct entry_data *entry = list->data;
542
543                 if (entry->timeout == 0 ||
544                                 g_strcmp0(entry->interface, interface) != 0)
545                         continue;
546
547                 /*
548                  * This function must only check IPv6 server addresses so
549                  * do not remove IPv4 name servers unnecessarily.
550                  */
551                 if (entry->family != AF_INET6)
552                         continue;
553
554                 /*
555                  * We remove the server, and then re-create so that it will
556                  * use proper source addresses when sending DNS queries.
557                  */
558                 __connman_dnsproxy_remove(entry->interface, entry->domain,
559                                         entry->server);
560                 /*
561                  * Remove also the resolver timer for the old server entry.
562                  * A new timer will be set for the new server entry
563                  * when the next Router Advertisement message arrives
564                  * with RDNSS/DNSSL settings.
565                  */
566                 g_source_remove(entry->timeout);
567                 entry->timeout = 0;
568
569                 __connman_dnsproxy_append(entry->interface, entry->domain,
570                                         entry->server);
571         }
572
573         return 0;
574 }
575
576 static void free_entry(gpointer data)
577 {
578         struct entry_data *entry = data;
579         g_free(entry->interface);
580         g_free(entry->domain);
581         g_free(entry->server);
582         g_free(entry);
583 }
584
585 static void free_resolvfile(gpointer data)
586 {
587         struct resolvfile_entry *entry = data;
588         g_free(entry->interface);
589         g_free(entry->domain);
590         g_free(entry->server);
591         g_free(entry);
592 }
593
594 int __connman_resolver_init(connman_bool_t dnsproxy)
595 {
596         int i;
597         char **ns;
598
599         DBG("dnsproxy %d", dnsproxy);
600
601         if (dnsproxy == FALSE)
602                 return 0;
603
604         if (__connman_dnsproxy_init() < 0) {
605                 /* Fall back to resolv.conf */
606                 return 0;
607         }
608
609         dnsproxy_enabled = TRUE;
610
611         ns = connman_setting_get_string_list("FallbackNameservers");
612         for (i = 0; ns != NULL && ns[i] != NULL; i += 1) {
613                 DBG("server %s", ns[i]);
614                 append_resolver(NULL, NULL, ns[i], 0, RESOLVER_FLAG_PUBLIC);
615         }
616
617         return 0;
618 }
619
620 void __connman_resolver_cleanup(void)
621 {
622         DBG("");
623
624         if (dnsproxy_enabled == TRUE)
625                 __connman_dnsproxy_cleanup();
626         else {
627                 GList *list;
628                 GSList *slist;
629
630                 for (list = resolvfile_list; list; list = g_list_next(list))
631                         free_resolvfile(list->data);
632                 g_list_free(resolvfile_list);
633                 resolvfile_list = NULL;
634
635                 for (slist = entry_list; slist; slist = g_slist_next(slist))
636                         free_entry(slist->data);
637                 g_slist_free(entry_list);
638                 entry_list = NULL;
639         }
640 }