]> git.proxmox.com Git - mirror_frr.git/blob - lib/resolver.c
Merge pull request #9906 from patrasar/2553196
[mirror_frr.git] / lib / resolver.c
1 /* C-Ares integration to Quagga mainloop
2 * Copyright (c) 2014-2015 Timo Teräs
3 *
4 * This file is free software: you may copy, redistribute and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 */
9
10 #ifdef HAVE_CONFIG_H
11 #include "config.h"
12 #endif
13
14 #include <ares.h>
15 #include <ares_version.h>
16
17 #include "typesafe.h"
18 #include "jhash.h"
19 #include "thread.h"
20 #include "lib_errors.h"
21 #include "resolver.h"
22 #include "command.h"
23 #include "xref.h"
24
25 XREF_SETUP();
26
27 struct resolver_state {
28 ares_channel channel;
29 struct thread_master *master;
30 struct thread *timeout;
31 };
32
33 static struct resolver_state state;
34 static bool resolver_debug;
35
36 /* a FD doesn't necessarily map 1:1 to a request; we could be talking to
37 * multiple caches simultaneously, to see which responds fastest.
38 * Theoretically we could also be using the same fd for multiple lookups,
39 * but the c-ares API guarantees an n:1 mapping for fd => channel.
40 *
41 * Either way c-ares makes that decision and we just need to deal with
42 * whatever FDs it gives us.
43 */
44
45 DEFINE_MTYPE_STATIC(LIB, ARES_FD, "c-ares (DNS) file descriptor information");
46 PREDECL_HASH(resolver_fds);
47
48 struct resolver_fd {
49 struct resolver_fds_item itm;
50
51 int fd;
52 struct resolver_state *state;
53 struct thread *t_read, *t_write;
54 };
55
56 static int resolver_fd_cmp(const struct resolver_fd *a,
57 const struct resolver_fd *b)
58 {
59 return numcmp(a->fd, b->fd);
60 }
61
62 static uint32_t resolver_fd_hash(const struct resolver_fd *item)
63 {
64 return jhash_1word(item->fd, 0xacd04c9e);
65 }
66
67 DECLARE_HASH(resolver_fds, struct resolver_fd, itm, resolver_fd_cmp,
68 resolver_fd_hash);
69
70 static struct resolver_fds_head resfds[1] = {INIT_HASH(resfds[0])};
71
72 static struct resolver_fd *resolver_fd_get(int fd,
73 struct resolver_state *newstate)
74 {
75 struct resolver_fd ref = {.fd = fd}, *res;
76
77 res = resolver_fds_find(resfds, &ref);
78 if (!res && newstate) {
79 res = XCALLOC(MTYPE_ARES_FD, sizeof(*res));
80 res->fd = fd;
81 res->state = newstate;
82 resolver_fds_add(resfds, res);
83
84 if (resolver_debug)
85 zlog_debug("c-ares registered FD %d", fd);
86 }
87 return res;
88 }
89
90 static void resolver_fd_drop_maybe(struct resolver_fd *resfd)
91 {
92 if (resfd->t_read || resfd->t_write)
93 return;
94
95 if (resolver_debug)
96 zlog_debug("c-ares unregistered FD %d", resfd->fd);
97
98 resolver_fds_del(resfds, resfd);
99 XFREE(MTYPE_ARES_FD, resfd);
100 }
101
102 /* end of FD housekeeping */
103
104 static void resolver_update_timeouts(struct resolver_state *r);
105
106 static int resolver_cb_timeout(struct thread *t)
107 {
108 struct resolver_state *r = THREAD_ARG(t);
109
110 ares_process(r->channel, NULL, NULL);
111 resolver_update_timeouts(r);
112
113 return 0;
114 }
115
116 static int resolver_cb_socket_readable(struct thread *t)
117 {
118 struct resolver_fd *resfd = THREAD_ARG(t);
119 struct resolver_state *r = resfd->state;
120
121 thread_add_read(r->master, resolver_cb_socket_readable, resfd,
122 resfd->fd, &resfd->t_read);
123 /* ^ ordering important:
124 * ares_process_fd may transitively call THREAD_OFF(resfd->t_read)
125 * combined with resolver_fd_drop_maybe, so resfd may be free'd after!
126 */
127 ares_process_fd(r->channel, resfd->fd, ARES_SOCKET_BAD);
128 resolver_update_timeouts(r);
129
130 return 0;
131 }
132
133 static int resolver_cb_socket_writable(struct thread *t)
134 {
135 struct resolver_fd *resfd = THREAD_ARG(t);
136 struct resolver_state *r = resfd->state;
137
138 thread_add_write(r->master, resolver_cb_socket_writable, resfd,
139 resfd->fd, &resfd->t_write);
140 /* ^ ordering important:
141 * ares_process_fd may transitively call THREAD_OFF(resfd->t_write)
142 * combined with resolver_fd_drop_maybe, so resfd may be free'd after!
143 */
144 ares_process_fd(r->channel, ARES_SOCKET_BAD, resfd->fd);
145 resolver_update_timeouts(r);
146
147 return 0;
148 }
149
150 static void resolver_update_timeouts(struct resolver_state *r)
151 {
152 struct timeval *tv, tvbuf;
153
154 THREAD_OFF(r->timeout);
155 tv = ares_timeout(r->channel, NULL, &tvbuf);
156 if (tv) {
157 unsigned int timeoutms = tv->tv_sec * 1000 + tv->tv_usec / 1000;
158
159 thread_add_timer_msec(r->master, resolver_cb_timeout, r,
160 timeoutms, &r->timeout);
161 }
162 }
163
164 static void ares_socket_cb(void *data, ares_socket_t fd, int readable,
165 int writable)
166 {
167 struct resolver_state *r = (struct resolver_state *)data;
168 struct resolver_fd *resfd;
169
170 resfd = resolver_fd_get(fd, (readable || writable) ? r : NULL);
171 if (!resfd)
172 return;
173
174 assert(resfd->state == r);
175
176 if (!readable)
177 THREAD_OFF(resfd->t_read);
178 else if (!resfd->t_read)
179 thread_add_read(r->master, resolver_cb_socket_readable, resfd,
180 fd, &resfd->t_read);
181
182 if (!writable)
183 THREAD_OFF(resfd->t_write);
184 else if (!resfd->t_write)
185 thread_add_write(r->master, resolver_cb_socket_writable, resfd,
186 fd, &resfd->t_write);
187
188 resolver_fd_drop_maybe(resfd);
189 }
190
191
192 static void ares_address_cb(void *arg, int status, int timeouts,
193 struct hostent *he)
194 {
195 struct resolver_query *query = (struct resolver_query *)arg;
196 union sockunion addr[16];
197 void (*callback)(struct resolver_query *, const char *, int,
198 union sockunion *);
199 size_t i;
200
201 callback = query->callback;
202 query->callback = NULL;
203
204 if (status != ARES_SUCCESS) {
205 if (resolver_debug)
206 zlog_debug("[%p] Resolving failed (%s)",
207 query, ares_strerror(status));
208
209 callback(query, ares_strerror(status), -1, NULL);
210 return;
211 }
212
213 for (i = 0; i < array_size(addr) && he->h_addr_list[i] != NULL; i++) {
214 memset(&addr[i], 0, sizeof(addr[i]));
215 addr[i].sa.sa_family = he->h_addrtype;
216 switch (he->h_addrtype) {
217 case AF_INET:
218 memcpy(&addr[i].sin.sin_addr,
219 (uint8_t *)he->h_addr_list[i], he->h_length);
220 break;
221 case AF_INET6:
222 memcpy(&addr[i].sin6.sin6_addr,
223 (uint8_t *)he->h_addr_list[i], he->h_length);
224 break;
225 }
226 }
227
228 if (resolver_debug)
229 zlog_debug("[%p] Resolved with %d results", query, (int)i);
230
231 callback(query, NULL, i, &addr[0]);
232 }
233
234 static int resolver_cb_literal(struct thread *t)
235 {
236 struct resolver_query *query = THREAD_ARG(t);
237 void (*callback)(struct resolver_query *, const char *, int,
238 union sockunion *);
239
240 callback = query->callback;
241 query->callback = NULL;
242
243 callback(query, ARES_SUCCESS, 1, &query->literal_addr);
244 return 0;
245 }
246
247 void resolver_resolve(struct resolver_query *query, int af,
248 const char *hostname,
249 void (*callback)(struct resolver_query *, const char *,
250 int, union sockunion *))
251 {
252 int ret;
253
254 if (query->callback != NULL) {
255 flog_err(
256 EC_LIB_RESOLVER,
257 "Trying to resolve '%s', but previous query was not finished yet",
258 hostname);
259 return;
260 }
261
262 query->callback = callback;
263 query->literal_cb = NULL;
264
265 ret = str2sockunion(hostname, &query->literal_addr);
266 if (ret == 0) {
267 if (resolver_debug)
268 zlog_debug("[%p] Resolving '%s' (IP literal)",
269 query, hostname);
270
271 /* for consistency with proper name lookup, don't call the
272 * callback immediately; defer to thread loop
273 */
274 thread_add_timer_msec(state.master, resolver_cb_literal,
275 query, 0, &query->literal_cb);
276 return;
277 }
278
279 if (resolver_debug)
280 zlog_debug("[%p] Resolving '%s'", query, hostname);
281
282 ares_gethostbyname(state.channel, hostname, af, ares_address_cb, query);
283 resolver_update_timeouts(&state);
284 }
285
286 DEFUN(debug_resolver,
287 debug_resolver_cmd,
288 "[no] debug resolver",
289 NO_STR
290 DEBUG_STR
291 "Debug DNS resolver actions\n")
292 {
293 resolver_debug = (argc == 2);
294 return CMD_SUCCESS;
295 }
296
297 static int resolver_config_write_debug(struct vty *vty);
298 static struct cmd_node resolver_debug_node = {
299 .name = "resolver debug",
300 .node = RESOLVER_DEBUG_NODE,
301 .prompt = "",
302 .config_write = resolver_config_write_debug,
303 };
304
305 static int resolver_config_write_debug(struct vty *vty)
306 {
307 if (resolver_debug)
308 vty_out(vty, "debug resolver\n");
309 return 1;
310 }
311
312
313 void resolver_init(struct thread_master *tm)
314 {
315 struct ares_options ares_opts;
316
317 state.master = tm;
318
319 ares_opts = (struct ares_options){
320 .sock_state_cb = &ares_socket_cb,
321 .sock_state_cb_data = &state,
322 .timeout = 2,
323 .tries = 3,
324 };
325
326 ares_init_options(&state.channel, &ares_opts,
327 ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUT
328 | ARES_OPT_TRIES);
329
330 install_node(&resolver_debug_node);
331 install_element(CONFIG_NODE, &debug_resolver_cmd);
332 install_element(ENABLE_NODE, &debug_resolver_cmd);
333 }