chiark / gitweb /
dd812150cfc6554da47a98a1e5c02fc26f990ef9
[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                 dns_query_transaction_set_state(t, DNS_QUERY_SUCCESS);
112         else
113                 dns_query_transaction_set_state(t, DNS_QUERY_FAILURE);
114
115         return 0;
116 }
117
118 static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) {
119         DnsQueryTransaction *t = userdata;
120         int r;
121
122         assert(s);
123         assert(t);
124
125         /* Timeout reached? Try again, with a new server */
126         dns_scope_next_dns_server(t->scope);
127
128         r = dns_query_transaction_start(t);
129         if (r < 0)
130                 dns_query_transaction_set_state(t, DNS_QUERY_FAILURE);
131
132         return 0;
133 }
134
135 int dns_query_transaction_start(DnsQueryTransaction *t) {
136         _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
137         unsigned n;
138         int r;
139
140         assert(t);
141
142         t->timeout_event_source = sd_event_source_unref(t->timeout_event_source);
143
144         if (t->n_attempts >= ATTEMPTS_MAX) {
145                 dns_query_transaction_set_state(t, DNS_QUERY_ATTEMPTS_MAX);
146                 return 0;
147         }
148
149         t->n_attempts++;
150
151         r = dns_packet_new_query(&p, 0);
152         if (r < 0)
153                 return r;
154
155         for (n = 0; n < t->query->n_keys; n++) {
156                 r = dns_packet_append_key(p, &t->query->keys[n], NULL);
157                 if (r < 0)
158                         return r;
159         }
160
161         DNS_PACKET_HEADER(p)->qdcount = htobe16(t->query->n_keys);
162         DNS_PACKET_HEADER(p)->id = t->id;
163
164         r = dns_scope_send(t->scope, p);
165         if (r < 0) {
166                 /* Couldn't send? Try immediately again, with a new server */
167                 dns_scope_next_dns_server(t->scope);
168
169                 return dns_query_transaction_start(t);
170         }
171
172         if (r > 0) {
173                 int q;
174
175                 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);
176                 if (q < 0)
177                         return q;
178
179                 dns_query_transaction_set_state(t, DNS_QUERY_SENT);
180         } else
181                 dns_query_transaction_set_state(t, DNS_QUERY_SKIPPED);
182
183         return r;
184 }
185
186 DnsQuery *dns_query_free(DnsQuery *q) {
187         unsigned n;
188
189         if (!q)
190                 return NULL;
191
192         sd_bus_message_unref(q->request);
193         dns_packet_unref(q->packet);
194         sd_event_source_unref(q->timeout_event_source);
195
196         while (q->transactions)
197                 dns_query_transaction_free(q->transactions);
198
199         if (q->manager)
200                 LIST_REMOVE(queries, q->manager->dns_queries, q);
201
202         for (n = 0; n < q->n_keys; n++)
203                 free(q->keys[n].name);
204         free(q->keys);
205         free(q);
206
207         return NULL;
208 }
209
210 int dns_query_new(Manager *m, DnsQuery **ret, DnsResourceKey *keys, unsigned n_keys) {
211         _cleanup_(dns_query_freep) DnsQuery *q = NULL;
212         DnsScope *s, *first = NULL;
213         DnsScopeMatch found = DNS_SCOPE_NO;
214         const char *name = NULL;
215         int n, r;
216
217         assert(m);
218
219         if (n_keys <= 0 || n_keys >= 65535)
220                 return -EINVAL;
221
222         assert(keys);
223
224         q = new0(DnsQuery, 1);
225         if (!q)
226                 return -ENOMEM;
227
228         q->keys = new(DnsResourceKey, n_keys);
229         if (!q->keys)
230                 return -ENOMEM;
231
232         for (q->n_keys = 0; q->n_keys < n_keys; q->n_keys++) {
233                 q->keys[q->n_keys].class = keys[q->n_keys].class;
234                 q->keys[q->n_keys].type = keys[q->n_keys].type;
235                 q->keys[q->n_keys].name = strdup(keys[q->n_keys].name);
236                 if (!q->keys[q->n_keys].name)
237                         return -ENOMEM;
238
239                 if (!name)
240                         name = q->keys[q->n_keys].name;
241                 else if (!dns_name_equal(name, q->keys[q->n_keys].name))
242                         return -EINVAL;
243         }
244
245         LIST_PREPEND(queries, m->dns_queries, q);
246         q->manager = m;
247
248         LIST_FOREACH(scopes, s, m->dns_scopes) {
249                 DnsScopeMatch match;
250
251                 match = dns_scope_test(s, name);
252                 if (match < 0)
253                         return match;
254
255                 if (match == DNS_SCOPE_NO)
256                         continue;
257
258                 found = match;
259
260                 if (match == DNS_SCOPE_YES) {
261                         first = s;
262                         break;
263                 } else {
264                         assert(match == DNS_SCOPE_MAYBE);
265
266                         if (!first)
267                                 first = s;
268                 }
269         }
270
271         if (found == DNS_SCOPE_NO)
272                 return -ENETDOWN;
273
274         r = dns_query_transaction_new(q, NULL, first);
275         if (r < 0)
276                 return r;
277
278         n = 1;
279         LIST_FOREACH(scopes, s, first->scopes_next) {
280                 DnsScopeMatch match;
281
282                 match = dns_scope_test(s, name);
283                 if (match < 0)
284                         return match;
285
286                 if (match != found)
287                         continue;
288
289                 r = dns_query_transaction_new(q, NULL, s);
290                 if (r < 0)
291                         return r;
292
293                 n++;
294         }
295
296         if (ret)
297                 *ret = q;
298         q = NULL;
299
300         return n;
301 }
302
303 static void dns_query_set_state(DnsQuery *q, DnsQueryState state) {
304         assert(q);
305
306         if (q->state == state)
307                 return;
308
309         q->state = state;
310
311         if (state == DNS_QUERY_SENT)
312                 return;
313
314         q->timeout_event_source = sd_event_source_unref(q->timeout_event_source);
315
316         while (q->transactions)
317                 dns_query_transaction_free(q->transactions);
318
319         if (q->complete)
320                 q->complete(q);
321 }
322
323 static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) {
324         DnsQuery *q = userdata;
325
326         assert(s);
327         assert(q);
328
329         dns_query_set_state(q, DNS_QUERY_TIMEOUT);
330         return 0;
331 }
332
333 int dns_query_start(DnsQuery *q) {
334         DnsQueryTransaction *t;
335         int r;
336
337         assert(q);
338         assert(q->state == DNS_QUERY_NULL);
339
340         if (!q->transactions)
341                 return -ENETDOWN;
342
343         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);
344         if (r < 0)
345                 goto fail;
346
347         dns_query_set_state(q, DNS_QUERY_SENT);
348
349         LIST_FOREACH(transactions_by_query, t, q->transactions) {
350
351                 r = dns_query_transaction_start(t);
352                 if (r < 0)
353                         goto fail;
354
355                 if (q->state != DNS_QUERY_SENT)
356                         break;
357         }
358
359         return 0;
360
361 fail:
362         while (q->transactions)
363                 dns_query_transaction_free(q->transactions);
364
365         return r;
366 }
367
368 void dns_query_finish(DnsQuery *q) {
369         DnsQueryTransaction *t;
370         DnsQueryState state = DNS_QUERY_SKIPPED;
371         uint16_t rcode = 0;
372
373         assert(q);
374
375         if (q->state != DNS_QUERY_SENT)
376                 return;
377
378         LIST_FOREACH(transactions_by_query, t, q->transactions) {
379
380                 /* One of the transactions is still going on, let's wait for it */
381                 if (t->state == DNS_QUERY_SENT || t->state == DNS_QUERY_NULL)
382                         return;
383
384                 /* One of the transactions is sucecssful, let's use it */
385                 if (t->state == DNS_QUERY_SUCCESS) {
386                         q->packet = dns_packet_ref(t->packet);
387                         dns_query_set_state(q, DNS_QUERY_SUCCESS);
388                         return;
389                 }
390
391                 if (t->state == DNS_QUERY_FAILURE) {
392                         state = DNS_QUERY_FAILURE;
393
394                         if (rcode == 0 && t->packet)
395                                 rcode = DNS_PACKET_RCODE(t->packet);
396
397                 } else if (state == DNS_QUERY_SKIPPED && t->state != DNS_QUERY_SKIPPED)
398                         state = t->state;
399         }
400
401         if (state == DNS_QUERY_FAILURE)
402                 q->rcode = rcode;
403
404         dns_query_set_state(q, state);
405 }