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