chiark / gitweb /
resolved: add a DNS client stub resolver
[elogind.git] / src / resolve / resolved-dns-query.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2014 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include "resolved-dns-query.h"
23 #include "resolved-dns-domain.h"
24
25 #define TRANSACTION_TIMEOUT_USEC (5 * USEC_PER_SEC)
26 #define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC)
27 #define ATTEMPTS_MAX 8
28
29 DnsQueryTransaction* dns_query_transaction_free(DnsQueryTransaction *t) {
30         if (!t)
31                 return NULL;
32
33         sd_event_source_unref(t->timeout_event_source);
34         dns_packet_unref(t->packet);
35
36         if (t->query) {
37                 LIST_REMOVE(transactions_by_query, t->query->transactions, t);
38                 hashmap_remove(t->query->manager->dns_query_transactions, UINT_TO_PTR(t->id));
39         }
40
41         if (t->scope)
42                 LIST_REMOVE(transactions_by_scope, t->scope->transactions, t);
43
44         free(t);
45         return NULL;
46 }
47
48 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQueryTransaction*, dns_query_transaction_free);
49
50 static int dns_query_transaction_new(DnsQuery *q, DnsQueryTransaction **ret, DnsScope *s) {
51         _cleanup_(dns_query_transaction_freep) DnsQueryTransaction *t = NULL;
52         int r;
53
54         assert(q);
55         assert(s);
56
57         r = hashmap_ensure_allocated(&q->manager->dns_query_transactions, NULL, NULL);
58         if (r < 0)
59                 return r;
60
61         t = new0(DnsQueryTransaction, 1);
62         if (!t)
63                 return -ENOMEM;
64
65         do
66                 random_bytes(&t->id, sizeof(t->id));
67         while (t->id == 0 ||
68                hashmap_get(q->manager->dns_query_transactions, UINT_TO_PTR(t->id)));
69
70         r = hashmap_put(q->manager->dns_query_transactions, UINT_TO_PTR(t->id), t);
71         if (r < 0) {
72                 t->id = 0;
73                 return r;
74         }
75
76         LIST_PREPEND(transactions_by_query, q->transactions, t);
77         t->query = q;
78
79         LIST_PREPEND(transactions_by_scope, s->transactions, t);
80         t->scope = s;
81
82         if (ret)
83                 *ret = t;
84
85         t = NULL;
86
87         return 0;
88 }
89
90 static void dns_query_transaction_set_state(DnsQueryTransaction *t, DnsQueryState state) {
91         assert(t);
92
93         if (t->state == state)
94                 return;
95
96         t->state = state;
97
98         if (state != DNS_QUERY_SENT)
99                 t->timeout_event_source = sd_event_source_unref(t->timeout_event_source);
100
101         dns_query_finish(t->query);
102 }
103
104 int dns_query_transaction_reply(DnsQueryTransaction *t, DnsPacket *p) {
105         assert(t);
106         assert(p);
107
108         t->packet = dns_packet_ref(p);
109
110         if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS) {
111                 if( be16toh(DNS_PACKET_HEADER(p)->ancount) > 0)
112                         dns_query_transaction_set_state(t, DNS_QUERY_SUCCESS);
113                 else
114                         dns_query_transaction_set_state(t, DNS_QUERY_INVALID_REPLY);
115         } else
116                 dns_query_transaction_set_state(t, DNS_QUERY_FAILURE);
117
118         return 0;
119 }
120
121 static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) {
122         DnsQueryTransaction *t = userdata;
123         int r;
124
125         assert(s);
126         assert(t);
127
128         /* Timeout reached? Try again, with a new server */
129         dns_scope_next_dns_server(t->scope);
130
131         r = dns_query_transaction_start(t);
132         if (r < 0)
133                 dns_query_transaction_set_state(t, DNS_QUERY_FAILURE);
134
135         return 0;
136 }
137
138 int dns_query_transaction_start(DnsQueryTransaction *t) {
139         _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
140         unsigned n;
141         int r;
142
143         assert(t);
144
145         t->timeout_event_source = sd_event_source_unref(t->timeout_event_source);
146
147         if (t->n_attempts >= ATTEMPTS_MAX) {
148                 dns_query_transaction_set_state(t, DNS_QUERY_ATTEMPTS_MAX);
149                 return 0;
150         }
151
152         t->n_attempts++;
153
154         r = dns_packet_new_query(&p, 0);
155         if (r < 0)
156                 return r;
157
158         for (n = 0; n < t->query->n_keys; n++) {
159                 r = dns_packet_append_key(p, &t->query->keys[n], NULL);
160                 if (r < 0)
161                         return r;
162         }
163
164         DNS_PACKET_HEADER(p)->qdcount = htobe16(t->query->n_keys);
165         DNS_PACKET_HEADER(p)->id = t->id;
166
167         r = dns_scope_send(t->scope, p);
168         if (r < 0) {
169                 /* Couldn't send? Try immediately again, with a new server */
170                 dns_scope_next_dns_server(t->scope);
171
172                 return dns_query_transaction_start(t);
173         }
174
175         if (r > 0) {
176                 int q;
177
178                 q = sd_event_add_time(t->query->manager->event, &t->timeout_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + TRANSACTION_TIMEOUT_USEC, 0, on_transaction_timeout, t);
179                 if (q < 0)
180                         return q;
181
182                 dns_query_transaction_set_state(t, DNS_QUERY_SENT);
183         } else
184                 dns_query_transaction_set_state(t, DNS_QUERY_SKIPPED);
185
186         return r;
187 }
188
189 DnsQuery *dns_query_free(DnsQuery *q) {
190         unsigned n;
191
192         if (!q)
193                 return NULL;
194
195         sd_bus_message_unref(q->request);
196         dns_packet_unref(q->packet);
197         sd_event_source_unref(q->timeout_event_source);
198
199         while (q->transactions)
200                 dns_query_transaction_free(q->transactions);
201
202         if (q->manager)
203                 LIST_REMOVE(queries, q->manager->dns_queries, q);
204
205         for (n = 0; n < q->n_keys; n++)
206                 free(q->keys[n].name);
207         free(q->keys);
208         free(q);
209
210         return NULL;
211 }
212
213 int dns_query_new(Manager *m, DnsQuery **ret, DnsResourceKey *keys, unsigned n_keys) {
214         _cleanup_(dns_query_freep) DnsQuery *q = NULL;
215         DnsScope *s, *first = NULL;
216         DnsScopeMatch found = DNS_SCOPE_NO;
217         const char *name = NULL;
218         int n, r;
219
220         assert(m);
221
222         if (n_keys <= 0 || n_keys >= 65535)
223                 return -EINVAL;
224
225         assert(keys);
226
227         q = new0(DnsQuery, 1);
228         if (!q)
229                 return -ENOMEM;
230
231         q->keys = new(DnsResourceKey, n_keys);
232         if (!q->keys)
233                 return -ENOMEM;
234
235         for (q->n_keys = 0; q->n_keys < n_keys; q->n_keys++) {
236                 q->keys[q->n_keys].class = keys[q->n_keys].class;
237                 q->keys[q->n_keys].type = keys[q->n_keys].type;
238                 q->keys[q->n_keys].name = strdup(keys[q->n_keys].name);
239                 if (!q->keys[q->n_keys].name)
240                         return -ENOMEM;
241
242                 if (!name)
243                         name = q->keys[q->n_keys].name;
244                 else if (!dns_name_equal(name, q->keys[q->n_keys].name))
245                         return -EINVAL;
246         }
247
248         LIST_PREPEND(queries, m->dns_queries, q);
249         q->manager = m;
250
251         LIST_FOREACH(scopes, s, m->dns_scopes) {
252                 DnsScopeMatch match;
253
254                 match = dns_scope_test(s, name);
255                 if (match < 0)
256                         return match;
257
258                 if (match == DNS_SCOPE_NO)
259                         continue;
260
261                 found = match;
262
263                 if (match == DNS_SCOPE_YES) {
264                         first = s;
265                         break;
266                 } else {
267                         assert(match == DNS_SCOPE_MAYBE);
268
269                         if (!first)
270                                 first = s;
271                 }
272         }
273
274         if (found == DNS_SCOPE_NO)
275                 return -ENETDOWN;
276
277         r = dns_query_transaction_new(q, NULL, first);
278         if (r < 0)
279                 return r;
280
281         n = 1;
282         LIST_FOREACH(scopes, s, first->scopes_next) {
283                 DnsScopeMatch match;
284
285                 match = dns_scope_test(s, name);
286                 if (match < 0)
287                         return match;
288
289                 if (match != found)
290                         continue;
291
292                 r = dns_query_transaction_new(q, NULL, s);
293                 if (r < 0)
294                         return r;
295
296                 n++;
297         }
298
299         if (ret)
300                 *ret = q;
301         q = NULL;
302
303         return n;
304 }
305
306 static void dns_query_set_state(DnsQuery *q, DnsQueryState state) {
307         assert(q);
308
309         if (q->state == state)
310                 return;
311
312         q->state = state;
313
314         if (state == DNS_QUERY_SENT)
315                 return;
316
317         q->timeout_event_source = sd_event_source_unref(q->timeout_event_source);
318
319         while (q->transactions)
320                 dns_query_transaction_free(q->transactions);
321
322         if (q->complete)
323                 q->complete(q);
324 }
325
326 static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) {
327         DnsQuery *q = userdata;
328
329         assert(s);
330         assert(q);
331
332         dns_query_set_state(q, DNS_QUERY_TIMEOUT);
333         return 0;
334 }
335
336 int dns_query_start(DnsQuery *q) {
337         DnsQueryTransaction *t;
338         int r;
339
340         assert(q);
341         assert(q->state == DNS_QUERY_NULL);
342
343         if (!q->transactions)
344                 return -ENETDOWN;
345
346         r = sd_event_add_time(q->manager->event, &q->timeout_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + QUERY_TIMEOUT_USEC, 0, on_query_timeout, q);
347         if (r < 0)
348                 goto fail;
349
350         dns_query_set_state(q, DNS_QUERY_SENT);
351
352         LIST_FOREACH(transactions_by_query, t, q->transactions) {
353
354                 r = dns_query_transaction_start(t);
355                 if (r < 0)
356                         goto fail;
357
358                 if (q->state != DNS_QUERY_SENT)
359                         break;
360         }
361
362         return 0;
363
364 fail:
365         while (q->transactions)
366                 dns_query_transaction_free(q->transactions);
367
368         return r;
369 }
370
371 void dns_query_finish(DnsQuery *q) {
372         DnsQueryTransaction *t;
373         DnsQueryState state = DNS_QUERY_SKIPPED;
374         uint16_t rcode = 0;
375
376         assert(q);
377
378         if (q->state != DNS_QUERY_SENT)
379                 return;
380
381         LIST_FOREACH(transactions_by_query, t, q->transactions) {
382
383                 /* One of the transactions is still going on, let's wait for it */
384                 if (t->state == DNS_QUERY_SENT || t->state == DNS_QUERY_NULL)
385                         return;
386
387                 /* One of the transactions is sucecssful, let's use it */
388                 if (t->state == DNS_QUERY_SUCCESS) {
389                         q->packet = dns_packet_ref(t->packet);
390                         dns_query_set_state(q, DNS_QUERY_SUCCESS);
391                         return;
392                 }
393
394                 if (t->state == DNS_QUERY_FAILURE) {
395                         state = DNS_QUERY_FAILURE;
396
397                         if (rcode == 0 && t->packet)
398                                 rcode = DNS_PACKET_RCODE(t->packet);
399
400                 } else if (state == DNS_QUERY_SKIPPED && t->state != DNS_QUERY_SKIPPED)
401                         state = t->state;
402         }
403
404         if (state == DNS_QUERY_FAILURE)
405                 q->rcode = rcode;
406
407         dns_query_set_state(q, state);
408 }