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