]>
Commit | Line | Data |
---|---|---|
771680d9 YS |
1 | /* |
2 | * Copyright (c) 2017, 2018 Nicira, Inc. | |
3 | * | |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at: | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | ||
17 | #include <config.h> | |
18 | #include "dns-resolve.h" | |
19 | #include <sys/types.h> | |
20 | #include <netinet/in.h> | |
21 | #include <arpa/inet.h> | |
22 | #include <arpa/nameser.h> | |
23 | #include <errno.h> | |
24 | #include <string.h> | |
70c5afb0 | 25 | #include <sys/stat.h> |
771680d9 YS |
26 | #include <unbound.h> |
27 | #include "hash.h" | |
28 | #include "openvswitch/hmap.h" | |
29 | #include "openvswitch/vlog.h" | |
30 | #include "timeval.h" | |
31 | ||
32 | VLOG_DEFINE_THIS_MODULE(dns_resolve); | |
33 | ||
34 | /* Guard all_reqs__ and resolve_state of each request. */ | |
35 | static struct ovs_mutex dns_mutex__ = OVS_MUTEX_INITIALIZER; | |
36 | static struct hmap all_reqs__; | |
37 | static struct ub_ctx *ub_ctx__; | |
38 | ||
39 | static bool thread_is_daemon; | |
40 | ||
41 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); | |
42 | ||
43 | enum resolve_state { | |
44 | RESOLVE_INVALID, | |
45 | RESOLVE_PENDING, | |
46 | RESOLVE_GOOD, | |
47 | RESOLVE_ERROR | |
48 | }; | |
49 | ||
50 | struct resolve_request { | |
51 | struct hmap_node hmap_node; /* node for all_reqs__ */ | |
52 | char *name; /* the domain name to be resolved */ | |
53 | char *addr; /* the resolved ip address */ | |
54 | enum resolve_state state; /* state of this request */ | |
55 | time_t time; /* resolving time */ | |
56 | struct ub_result *ub_result; /* the stored unbound result */ | |
57 | }; | |
58 | ||
59 | static struct resolve_request *resolve_find_or_new__(const char *name) | |
60 | OVS_REQUIRES(dns_mutex__); | |
61 | static bool resolve_check_expire__(struct resolve_request *req) | |
62 | OVS_REQUIRES(dns_mutex__); | |
63 | static bool resolve_check_valid__(struct resolve_request *req) | |
64 | OVS_REQUIRES(dns_mutex__); | |
65 | static bool resolve_async__(struct resolve_request *req, int qtype) | |
66 | OVS_REQUIRES(dns_mutex__); | |
67 | static void resolve_callback__(void *req, int err, struct ub_result *) | |
68 | OVS_REQUIRES(dns_mutex__); | |
69 | static bool resolve_result_to_addr__(struct ub_result *result, char **addr); | |
70 | static bool dns_resolve_sync__(const char *name, char **addr); | |
71 | ||
72 | /* Pass a true 'is_daemon' if you don't want the DNS-resolving to block the | |
73 | * running thread. | |
74 | */ | |
75 | void | |
76 | dns_resolve_init(bool is_daemon) | |
77 | { | |
78 | ub_ctx__ = ub_ctx_create(); | |
79 | if (ub_ctx__ == NULL) { | |
80 | VLOG_ERR_RL(&rl, "Failed to create libunbound context, " | |
81 | "so asynchronous DNS resolving is disabled."); | |
82 | return; | |
83 | } | |
84 | ||
f00c47b8 TE |
85 | const char *ub_conf_filename = getenv("OVS_UNBOUND_CONF"); |
86 | if (ub_conf_filename != NULL) { | |
87 | int retval = ub_ctx_config(ub_ctx__, ub_conf_filename); | |
88 | if (retval != 0) { | |
89 | VLOG_WARN_RL(&rl, "Failed to set libunbound context config: %s", | |
90 | ub_strerror(retval)); | |
91 | ub_ctx_delete(ub_ctx__); | |
92 | ub_ctx__ = NULL; | |
93 | return; | |
94 | } | |
95 | } | |
96 | ||
9ce4aa8e YS |
97 | const char *filename = getenv("OVS_RESOLV_CONF"); |
98 | if (!filename) { | |
99 | #ifdef _WIN32 | |
100 | /* On Windows, NULL means to use the system default nameserver. */ | |
101 | #else | |
102 | filename = "/etc/resolv.conf"; | |
103 | #endif | |
104 | } | |
70c5afb0 | 105 | struct stat s; |
9ce4aa8e | 106 | if (!filename || !stat(filename, &s) || errno != ENOENT) { |
70c5afb0 BP |
107 | int retval = ub_ctx_resolvconf(ub_ctx__, filename); |
108 | if (retval != 0) { | |
109 | VLOG_WARN_RL(&rl, "Failed to read %s: %s", | |
9ce4aa8e YS |
110 | filename ? filename : "system default nameserver", |
111 | ub_strerror(retval)); | |
ba8eb43a YS |
112 | ub_ctx_delete(ub_ctx__); |
113 | ub_ctx__ = NULL; | |
114 | return; | |
70c5afb0 | 115 | } |
ba8eb43a YS |
116 | } else { |
117 | VLOG_WARN_RL(&rl, "Failed to read %s: %s", | |
118 | filename, ovs_strerror(errno)); | |
119 | ub_ctx_delete(ub_ctx__); | |
120 | ub_ctx__ = NULL; | |
121 | return; | |
771680d9 | 122 | } |
771680d9 YS |
123 | |
124 | /* Handles '/etc/hosts' on Linux and 'WINDIR/etc/hosts' on Windows. */ | |
70c5afb0 | 125 | int retval = ub_ctx_hosts(ub_ctx__, NULL); |
771680d9 YS |
126 | if (retval != 0) { |
127 | VLOG_WARN_RL(&rl, "Failed to read etc/hosts: %s", | |
128 | ub_strerror(retval)); | |
129 | } | |
130 | ||
131 | ub_ctx_async(ub_ctx__, true); | |
132 | hmap_init(&all_reqs__); | |
133 | thread_is_daemon = is_daemon; | |
134 | } | |
135 | ||
136 | /* Returns true on success. Otherwise, returns false and the error information | |
137 | * can be found in logs. If there is no error information, then the resolving | |
138 | * is in process and the caller should call again later. The value of '*addr' | |
139 | * is always nullified if false is returned. If this function is called under | |
140 | * daemon-context, the resolving will undergo asynchronously. Otherwise, a | |
141 | * synchronouse resolving will take place. | |
142 | * | |
143 | * This function is thread-safe. | |
144 | * | |
145 | * The caller is responsible for freeing the returned '*addr'. | |
146 | */ | |
147 | bool | |
148 | dns_resolve(const char *name, char **addr) | |
149 | OVS_EXCLUDED(dns_mutex__) | |
150 | { | |
151 | bool success = false; | |
152 | ||
153 | if (!thread_is_daemon) { | |
154 | return dns_resolve_sync__(name, addr); | |
155 | } | |
156 | ||
157 | *addr = NULL; | |
158 | ovs_mutex_lock(&dns_mutex__); | |
159 | ||
160 | if (ub_ctx__ == NULL) { | |
161 | goto unlock; | |
162 | } | |
163 | ||
164 | /* ub_process is inside lock as it invokes resolve_callback__. */ | |
165 | int retval = ub_process(ub_ctx__); | |
166 | if (retval != 0) { | |
167 | VLOG_ERR_RL(&rl, "dns-resolve error: %s", ub_strerror(retval)); | |
168 | goto unlock; | |
169 | } | |
170 | ||
171 | struct resolve_request *req; | |
172 | req = resolve_find_or_new__(name); | |
173 | if (resolve_check_valid__(req)) { | |
174 | *addr = xstrdup(req->addr); | |
175 | success = true; | |
176 | } else if (req->state != RESOLVE_PENDING) { | |
177 | success = resolve_async__(req, ns_t_a); | |
178 | } | |
179 | unlock: | |
180 | ovs_mutex_unlock(&dns_mutex__); | |
181 | return success; | |
182 | } | |
183 | ||
184 | void | |
185 | dns_resolve_destroy(void) | |
186 | { | |
187 | if (ub_ctx__ != NULL) { | |
188 | /* Outstanding requests will be killed. */ | |
189 | ub_ctx_delete(ub_ctx__); | |
190 | ub_ctx__ = NULL; | |
191 | ||
a84b8865 YS |
192 | struct resolve_request *req, *next; |
193 | HMAP_FOR_EACH_SAFE (req, next, hmap_node, &all_reqs__) { | |
771680d9 YS |
194 | ub_resolve_free(req->ub_result); |
195 | free(req->addr); | |
196 | free(req->name); | |
197 | free(req); | |
198 | } | |
199 | hmap_destroy(&all_reqs__); | |
200 | } | |
201 | } | |
202 | ||
203 | static struct resolve_request * | |
204 | resolve_find_or_new__(const char *name) | |
205 | OVS_REQUIRES(dns_mutex__) | |
206 | { | |
207 | struct resolve_request *req; | |
208 | ||
209 | HMAP_FOR_EACH_IN_BUCKET(req, hmap_node, hash_string(name, 0), | |
210 | &all_reqs__) { | |
211 | if (!strcmp(name, req->name)) { | |
212 | return req; | |
213 | } | |
214 | } | |
215 | ||
216 | req = xzalloc(sizeof *req); | |
217 | req->name = xstrdup(name); | |
218 | req->state = RESOLVE_INVALID; | |
219 | hmap_insert(&all_reqs__, &req->hmap_node, hash_string(req->name, 0)); | |
220 | return req; | |
221 | } | |
222 | ||
223 | static bool | |
224 | resolve_check_expire__(struct resolve_request *req) | |
225 | OVS_REQUIRES(dns_mutex__) | |
226 | { | |
227 | return time_now() > req->time + req->ub_result->ttl; | |
228 | } | |
229 | ||
230 | static bool | |
231 | resolve_check_valid__(struct resolve_request *req) | |
232 | OVS_REQUIRES(dns_mutex__) | |
233 | { | |
234 | return (req != NULL | |
235 | && req->state == RESOLVE_GOOD | |
236 | && !resolve_check_expire__(req)); | |
237 | } | |
238 | ||
239 | static bool | |
240 | resolve_async__(struct resolve_request *req, int qtype) | |
241 | OVS_REQUIRES(dns_mutex__) | |
242 | { | |
243 | if (qtype == ns_t_a || qtype == ns_t_aaaa) { | |
244 | int retval; | |
245 | retval = ub_resolve_async(ub_ctx__, req->name, | |
246 | qtype, ns_c_in, req, | |
247 | resolve_callback__, NULL); | |
248 | if (retval != 0) { | |
249 | req->state = RESOLVE_ERROR; | |
250 | return false; | |
251 | } else { | |
252 | req->state = RESOLVE_PENDING; | |
253 | return true; | |
254 | } | |
255 | } | |
256 | return false; | |
257 | } | |
258 | ||
259 | static void | |
260 | resolve_callback__(void *req_, int err, struct ub_result *result) | |
261 | OVS_REQUIRES(dns_mutex__) | |
262 | { | |
263 | struct resolve_request *req = req_; | |
264 | ||
265 | if (err != 0 || (result->qtype == ns_t_aaaa && !result->havedata)) { | |
9ff0b84c | 266 | ub_resolve_free(result); |
771680d9 YS |
267 | req->state = RESOLVE_ERROR; |
268 | VLOG_ERR_RL(&rl, "%s: failed to resolve", req->name); | |
269 | return; | |
270 | } | |
271 | ||
272 | /* IPv4 address is empty, try IPv6. */ | |
273 | if (result->qtype == ns_t_a && !result->havedata) { | |
274 | ub_resolve_free(result); | |
275 | resolve_async__(req, ns_t_aaaa); | |
276 | return; | |
277 | } | |
278 | ||
279 | char *addr; | |
280 | if (!resolve_result_to_addr__(result, &addr)) { | |
9ff0b84c | 281 | ub_resolve_free(result); |
771680d9 YS |
282 | req->state = RESOLVE_ERROR; |
283 | VLOG_ERR_RL(&rl, "%s: failed to resolve", req->name); | |
284 | return; | |
285 | } | |
286 | ||
287 | ub_resolve_free(req->ub_result); | |
288 | free(req->addr); | |
289 | ||
290 | req->ub_result = result; | |
291 | req->addr = addr; | |
292 | req->state = RESOLVE_GOOD; | |
293 | req->time = time_now(); | |
294 | } | |
295 | ||
296 | static bool | |
297 | resolve_result_to_addr__(struct ub_result *result, char **addr) | |
298 | { | |
299 | int af = result->qtype == ns_t_a ? AF_INET : AF_INET6; | |
300 | char buffer[INET6_ADDRSTRLEN]; | |
301 | ||
302 | /* XXX: only the first returned IP is used. */ | |
303 | if (inet_ntop(af, result->data[0], buffer, sizeof buffer)) { | |
304 | *addr = xstrdup(buffer); | |
305 | } else { | |
306 | *addr = NULL; | |
307 | } | |
308 | ||
309 | return (*addr != NULL); | |
310 | } | |
311 | ||
312 | static bool | |
313 | dns_resolve_sync__(const char *name, char **addr) | |
314 | { | |
315 | *addr = NULL; | |
316 | ||
317 | if (ub_ctx__ == NULL) { | |
318 | dns_resolve_init(false); | |
319 | if (ub_ctx__ == NULL) { | |
320 | return false; | |
321 | } | |
322 | } | |
323 | ||
324 | struct ub_result *result; | |
325 | int retval = ub_resolve(ub_ctx__, name, ns_t_a, ns_c_in, &result); | |
326 | if (retval != 0) { | |
327 | return false; | |
328 | } else if (!result->havedata) { | |
329 | ub_resolve_free(result); | |
330 | ||
331 | retval = ub_resolve(ub_ctx__, name, ns_t_aaaa, ns_c_in, &result); | |
332 | if (retval != 0) { | |
333 | return false; | |
334 | } else if (!result->havedata) { | |
335 | ub_resolve_free(result); | |
336 | return false; | |
337 | } | |
338 | } | |
339 | ||
340 | bool success = resolve_result_to_addr__(result, addr); | |
341 | ub_resolve_free(result); | |
342 | return success; | |
343 | } |