]>
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 | ||
17 | #include "vector.h" | |
18 | #include "thread.h" | |
aed07011 | 19 | #include "lib_errors.h" |
fe9e7b71 DL |
20 | #include "resolver.h" |
21 | #include "command.h" | |
2fb975da TT |
22 | |
23 | struct resolver_state { | |
24 | ares_channel channel; | |
fe9e7b71 | 25 | struct thread_master *master; |
2fb975da TT |
26 | struct thread *timeout; |
27 | vector read_threads, write_threads; | |
28 | }; | |
29 | ||
30 | static struct resolver_state state; | |
fe9e7b71 | 31 | static bool resolver_debug; |
2fb975da TT |
32 | |
33 | #define THREAD_RUNNING ((struct thread *)-1) | |
34 | ||
35 | static void resolver_update_timeouts(struct resolver_state *r); | |
36 | ||
37 | static int resolver_cb_timeout(struct thread *t) | |
38 | { | |
39 | struct resolver_state *r = THREAD_ARG(t); | |
40 | ||
41 | r->timeout = THREAD_RUNNING; | |
42 | ares_process(r->channel, NULL, NULL); | |
43 | r->timeout = NULL; | |
44 | resolver_update_timeouts(r); | |
45 | ||
46 | return 0; | |
47 | } | |
48 | ||
49 | static int resolver_cb_socket_readable(struct thread *t) | |
50 | { | |
51 | struct resolver_state *r = THREAD_ARG(t); | |
52 | int fd = THREAD_FD(t); | |
53 | ||
54 | vector_set_index(r->read_threads, fd, THREAD_RUNNING); | |
55 | ares_process_fd(r->channel, fd, ARES_SOCKET_BAD); | |
56 | if (vector_lookup(r->read_threads, fd) == THREAD_RUNNING) { | |
57 | t = NULL; | |
fe9e7b71 DL |
58 | thread_add_read(r->master, resolver_cb_socket_readable, r, fd, |
59 | &t); | |
2fb975da TT |
60 | vector_set_index(r->read_threads, fd, t); |
61 | } | |
62 | resolver_update_timeouts(r); | |
63 | ||
64 | return 0; | |
65 | } | |
66 | ||
67 | static int resolver_cb_socket_writable(struct thread *t) | |
68 | { | |
69 | struct resolver_state *r = THREAD_ARG(t); | |
70 | int fd = THREAD_FD(t); | |
71 | ||
72 | vector_set_index(r->write_threads, fd, THREAD_RUNNING); | |
73 | ares_process_fd(r->channel, ARES_SOCKET_BAD, fd); | |
74 | if (vector_lookup(r->write_threads, fd) == THREAD_RUNNING) { | |
75 | t = NULL; | |
fe9e7b71 | 76 | thread_add_write(r->master, resolver_cb_socket_writable, r, fd, |
ffa2c898 | 77 | &t); |
2fb975da TT |
78 | vector_set_index(r->write_threads, fd, t); |
79 | } | |
80 | resolver_update_timeouts(r); | |
81 | ||
82 | return 0; | |
83 | } | |
84 | ||
85 | static void resolver_update_timeouts(struct resolver_state *r) | |
86 | { | |
87 | struct timeval *tv, tvbuf; | |
88 | ||
996c9314 LB |
89 | if (r->timeout == THREAD_RUNNING) |
90 | return; | |
2fb975da TT |
91 | |
92 | THREAD_OFF(r->timeout); | |
93 | tv = ares_timeout(r->channel, NULL, &tvbuf); | |
94 | if (tv) { | |
95 | unsigned int timeoutms = tv->tv_sec * 1000 + tv->tv_usec / 1000; | |
fe9e7b71 DL |
96 | thread_add_timer_msec(r->master, resolver_cb_timeout, r, |
97 | timeoutms, &r->timeout); | |
2fb975da TT |
98 | } |
99 | } | |
100 | ||
996c9314 LB |
101 | static void ares_socket_cb(void *data, ares_socket_t fd, int readable, |
102 | int writable) | |
2fb975da | 103 | { |
996c9314 | 104 | struct resolver_state *r = (struct resolver_state *)data; |
2fb975da TT |
105 | struct thread *t; |
106 | ||
107 | if (readable) { | |
108 | t = vector_lookup_ensure(r->read_threads, fd); | |
109 | if (!t) { | |
fe9e7b71 DL |
110 | thread_add_read(r->master, resolver_cb_socket_readable, |
111 | r, fd, &t); | |
2fb975da TT |
112 | vector_set_index(r->read_threads, fd, t); |
113 | } | |
114 | } else { | |
115 | t = vector_lookup(r->read_threads, fd); | |
116 | if (t) { | |
117 | if (t != THREAD_RUNNING) { | |
118 | THREAD_OFF(t); | |
119 | } | |
120 | vector_unset(r->read_threads, fd); | |
121 | } | |
122 | } | |
123 | ||
124 | if (writable) { | |
125 | t = vector_lookup_ensure(r->write_threads, fd); | |
126 | if (!t) { | |
fe9e7b71 DL |
127 | thread_add_read(r->master, resolver_cb_socket_writable, |
128 | r, fd, &t); | |
2fb975da TT |
129 | vector_set_index(r->write_threads, fd, t); |
130 | } | |
131 | } else { | |
132 | t = vector_lookup(r->write_threads, fd); | |
133 | if (t) { | |
134 | if (t != THREAD_RUNNING) { | |
135 | THREAD_OFF(t); | |
136 | } | |
137 | vector_unset(r->write_threads, fd); | |
138 | } | |
139 | } | |
140 | } | |
141 | ||
2fb975da | 142 | |
996c9314 LB |
143 | static void ares_address_cb(void *arg, int status, int timeouts, |
144 | struct hostent *he) | |
2fb975da | 145 | { |
996c9314 | 146 | struct resolver_query *query = (struct resolver_query *)arg; |
2fb975da | 147 | union sockunion addr[16]; |
50cdb6cf | 148 | void (*callback)(struct resolver_query *, int, union sockunion *); |
2fb975da TT |
149 | size_t i; |
150 | ||
50cdb6cf DL |
151 | callback = query->callback; |
152 | query->callback = NULL; | |
153 | ||
2fb975da | 154 | if (status != ARES_SUCCESS) { |
fe9e7b71 DL |
155 | if (resolver_debug) |
156 | zlog_debug("[%p] Resolving failed", query); | |
157 | ||
50cdb6cf | 158 | callback(query, -1, NULL); |
2fb975da TT |
159 | return; |
160 | } | |
161 | ||
7e3a1ec7 | 162 | for (i = 0; i < array_size(addr) && he->h_addr_list[i] != NULL; i++) { |
2fb975da TT |
163 | memset(&addr[i], 0, sizeof(addr[i])); |
164 | addr[i].sa.sa_family = he->h_addrtype; | |
165 | switch (he->h_addrtype) { | |
166 | case AF_INET: | |
996c9314 LB |
167 | memcpy(&addr[i].sin.sin_addr, |
168 | (uint8_t *)he->h_addr_list[i], he->h_length); | |
2fb975da TT |
169 | break; |
170 | case AF_INET6: | |
996c9314 LB |
171 | memcpy(&addr[i].sin6.sin6_addr, |
172 | (uint8_t *)he->h_addr_list[i], he->h_length); | |
2fb975da TT |
173 | break; |
174 | } | |
175 | } | |
176 | ||
fe9e7b71 DL |
177 | if (resolver_debug) |
178 | zlog_debug("[%p] Resolved with %d results", query, (int)i); | |
179 | ||
50cdb6cf | 180 | callback(query, i, &addr[0]); |
2fb975da TT |
181 | } |
182 | ||
996c9314 LB |
183 | void resolver_resolve(struct resolver_query *query, int af, |
184 | const char *hostname, | |
185 | void (*callback)(struct resolver_query *, int, | |
186 | union sockunion *)) | |
2fb975da TT |
187 | { |
188 | if (query->callback != NULL) { | |
1c50c1c0 | 189 | flog_err( |
fe9e7b71 | 190 | EC_LIB_RESOLVER, |
1c50c1c0 QY |
191 | "Trying to resolve '%s', but previous query was not finished yet", |
192 | hostname); | |
2fb975da TT |
193 | return; |
194 | } | |
195 | ||
fe9e7b71 DL |
196 | if (resolver_debug) |
197 | zlog_debug("[%p] Resolving '%s'", query, hostname); | |
2fb975da TT |
198 | |
199 | query->callback = callback; | |
200 | ares_gethostbyname(state.channel, hostname, af, ares_address_cb, query); | |
201 | resolver_update_timeouts(&state); | |
202 | } | |
fe9e7b71 DL |
203 | |
204 | DEFUN(debug_resolver, | |
205 | debug_resolver_cmd, | |
206 | "[no] debug resolver", | |
207 | NO_STR | |
208 | DEBUG_STR | |
209 | "Debug DNS resolver actions\n") | |
210 | { | |
211 | resolver_debug = (argc == 2); | |
212 | return CMD_SUCCESS; | |
213 | } | |
214 | ||
215 | static struct cmd_node resolver_debug_node = {RESOLVER_DEBUG_NODE, "", 1}; | |
216 | ||
217 | static int resolver_config_write_debug(struct vty *vty) | |
218 | { | |
219 | if (resolver_debug) | |
220 | vty_out(vty, "debug resolver\n"); | |
221 | return 1; | |
222 | } | |
223 | ||
224 | ||
225 | void resolver_init(struct thread_master *tm) | |
226 | { | |
227 | struct ares_options ares_opts; | |
228 | ||
229 | state.master = tm; | |
230 | state.read_threads = vector_init(1); | |
231 | state.write_threads = vector_init(1); | |
232 | ||
233 | ares_opts = (struct ares_options){ | |
234 | .sock_state_cb = &ares_socket_cb, | |
235 | .sock_state_cb_data = &state, | |
236 | .timeout = 2, | |
237 | .tries = 3, | |
238 | }; | |
239 | ||
240 | ares_init_options(&state.channel, &ares_opts, | |
241 | ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUT | |
242 | | ARES_OPT_TRIES); | |
243 | ||
244 | install_node(&resolver_debug_node, resolver_config_write_debug); | |
245 | install_element(CONFIG_NODE, &debug_resolver_cmd); | |
246 | install_element(ENABLE_NODE, &debug_resolver_cmd); | |
247 | } |