]> git.proxmox.com Git - mirror_ovs.git/blob - lib/dns-resolve.c
DNS: Add basic support for asynchronous DNS resolving
[mirror_ovs.git] / lib / dns-resolve.c
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>
25 #include <unbound.h>
26 #include "hash.h"
27 #include "openvswitch/hmap.h"
28 #include "openvswitch/vlog.h"
29 #include "timeval.h"
30
31 VLOG_DEFINE_THIS_MODULE(dns_resolve);
32
33 /* Guard all_reqs__ and resolve_state of each request. */
34 static struct ovs_mutex dns_mutex__ = OVS_MUTEX_INITIALIZER;
35 static struct hmap all_reqs__;
36 static struct ub_ctx *ub_ctx__;
37
38 static bool thread_is_daemon;
39
40 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
41
42 enum resolve_state {
43 RESOLVE_INVALID,
44 RESOLVE_PENDING,
45 RESOLVE_GOOD,
46 RESOLVE_ERROR
47 };
48
49 struct resolve_request {
50 struct hmap_node hmap_node; /* node for all_reqs__ */
51 char *name; /* the domain name to be resolved */
52 char *addr; /* the resolved ip address */
53 enum resolve_state state; /* state of this request */
54 time_t time; /* resolving time */
55 struct ub_result *ub_result; /* the stored unbound result */
56 };
57
58 static struct resolve_request *resolve_find_or_new__(const char *name)
59 OVS_REQUIRES(dns_mutex__);
60 static bool resolve_check_expire__(struct resolve_request *req)
61 OVS_REQUIRES(dns_mutex__);
62 static bool resolve_check_valid__(struct resolve_request *req)
63 OVS_REQUIRES(dns_mutex__);
64 static bool resolve_async__(struct resolve_request *req, int qtype)
65 OVS_REQUIRES(dns_mutex__);
66 static void resolve_callback__(void *req, int err, struct ub_result *)
67 OVS_REQUIRES(dns_mutex__);
68 static bool resolve_result_to_addr__(struct ub_result *result, char **addr);
69 static bool dns_resolve_sync__(const char *name, char **addr);
70
71 /* Pass a true 'is_daemon' if you don't want the DNS-resolving to block the
72 * running thread.
73 */
74 void
75 dns_resolve_init(bool is_daemon)
76 {
77 ub_ctx__ = ub_ctx_create();
78 if (ub_ctx__ == NULL) {
79 VLOG_ERR_RL(&rl, "Failed to create libunbound context, "
80 "so asynchronous DNS resolving is disabled.");
81 return;
82 }
83
84 int retval;
85 #ifdef __linux__
86 retval = ub_ctx_resolvconf(ub_ctx__, "/etc/resolv.conf");
87 if (retval != 0) {
88 VLOG_WARN_RL(&rl, "Failed to read /etc/resolv.conf: %s",
89 ub_strerror(retval));
90 }
91 #endif
92
93 /* Handles '/etc/hosts' on Linux and 'WINDIR/etc/hosts' on Windows. */
94 retval = ub_ctx_hosts(ub_ctx__, NULL);
95 if (retval != 0) {
96 VLOG_WARN_RL(&rl, "Failed to read etc/hosts: %s",
97 ub_strerror(retval));
98 }
99
100 ub_ctx_async(ub_ctx__, true);
101 hmap_init(&all_reqs__);
102 thread_is_daemon = is_daemon;
103 }
104
105 /* Returns true on success. Otherwise, returns false and the error information
106 * can be found in logs. If there is no error information, then the resolving
107 * is in process and the caller should call again later. The value of '*addr'
108 * is always nullified if false is returned. If this function is called under
109 * daemon-context, the resolving will undergo asynchronously. Otherwise, a
110 * synchronouse resolving will take place.
111 *
112 * This function is thread-safe.
113 *
114 * The caller is responsible for freeing the returned '*addr'.
115 */
116 bool
117 dns_resolve(const char *name, char **addr)
118 OVS_EXCLUDED(dns_mutex__)
119 {
120 bool success = false;
121
122 if (!thread_is_daemon) {
123 return dns_resolve_sync__(name, addr);
124 }
125
126 *addr = NULL;
127 ovs_mutex_lock(&dns_mutex__);
128
129 if (ub_ctx__ == NULL) {
130 goto unlock;
131 }
132
133 /* ub_process is inside lock as it invokes resolve_callback__. */
134 int retval = ub_process(ub_ctx__);
135 if (retval != 0) {
136 VLOG_ERR_RL(&rl, "dns-resolve error: %s", ub_strerror(retval));
137 goto unlock;
138 }
139
140 struct resolve_request *req;
141 req = resolve_find_or_new__(name);
142 if (resolve_check_valid__(req)) {
143 *addr = xstrdup(req->addr);
144 success = true;
145 } else if (req->state != RESOLVE_PENDING) {
146 success = resolve_async__(req, ns_t_a);
147 }
148 unlock:
149 ovs_mutex_unlock(&dns_mutex__);
150 return success;
151 }
152
153 void
154 dns_resolve_destroy(void)
155 {
156 if (ub_ctx__ != NULL) {
157 /* Outstanding requests will be killed. */
158 ub_ctx_delete(ub_ctx__);
159 ub_ctx__ = NULL;
160
161 struct resolve_request *req;
162 HMAP_FOR_EACH(req, hmap_node, &all_reqs__) {
163 ub_resolve_free(req->ub_result);
164 free(req->addr);
165 free(req->name);
166 free(req);
167 }
168 hmap_destroy(&all_reqs__);
169 }
170 }
171
172 static struct resolve_request *
173 resolve_find_or_new__(const char *name)
174 OVS_REQUIRES(dns_mutex__)
175 {
176 struct resolve_request *req;
177
178 HMAP_FOR_EACH_IN_BUCKET(req, hmap_node, hash_string(name, 0),
179 &all_reqs__) {
180 if (!strcmp(name, req->name)) {
181 return req;
182 }
183 }
184
185 req = xzalloc(sizeof *req);
186 req->name = xstrdup(name);
187 req->state = RESOLVE_INVALID;
188 hmap_insert(&all_reqs__, &req->hmap_node, hash_string(req->name, 0));
189 return req;
190 }
191
192 static bool
193 resolve_check_expire__(struct resolve_request *req)
194 OVS_REQUIRES(dns_mutex__)
195 {
196 return time_now() > req->time + req->ub_result->ttl;
197 }
198
199 static bool
200 resolve_check_valid__(struct resolve_request *req)
201 OVS_REQUIRES(dns_mutex__)
202 {
203 return (req != NULL
204 && req->state == RESOLVE_GOOD
205 && !resolve_check_expire__(req));
206 }
207
208 static bool
209 resolve_async__(struct resolve_request *req, int qtype)
210 OVS_REQUIRES(dns_mutex__)
211 {
212 if (qtype == ns_t_a || qtype == ns_t_aaaa) {
213 int retval;
214 retval = ub_resolve_async(ub_ctx__, req->name,
215 qtype, ns_c_in, req,
216 resolve_callback__, NULL);
217 if (retval != 0) {
218 req->state = RESOLVE_ERROR;
219 return false;
220 } else {
221 req->state = RESOLVE_PENDING;
222 return true;
223 }
224 }
225 return false;
226 }
227
228 static void
229 resolve_callback__(void *req_, int err, struct ub_result *result)
230 OVS_REQUIRES(dns_mutex__)
231 {
232 struct resolve_request *req = req_;
233
234 if (err != 0 || (result->qtype == ns_t_aaaa && !result->havedata)) {
235 req->state = RESOLVE_ERROR;
236 VLOG_ERR_RL(&rl, "%s: failed to resolve", req->name);
237 return;
238 }
239
240 /* IPv4 address is empty, try IPv6. */
241 if (result->qtype == ns_t_a && !result->havedata) {
242 ub_resolve_free(result);
243 resolve_async__(req, ns_t_aaaa);
244 return;
245 }
246
247 char *addr;
248 if (!resolve_result_to_addr__(result, &addr)) {
249 req->state = RESOLVE_ERROR;
250 VLOG_ERR_RL(&rl, "%s: failed to resolve", req->name);
251 return;
252 }
253
254 ub_resolve_free(req->ub_result);
255 free(req->addr);
256
257 req->ub_result = result;
258 req->addr = addr;
259 req->state = RESOLVE_GOOD;
260 req->time = time_now();
261 }
262
263 static bool
264 resolve_result_to_addr__(struct ub_result *result, char **addr)
265 {
266 int af = result->qtype == ns_t_a ? AF_INET : AF_INET6;
267 char buffer[INET6_ADDRSTRLEN];
268
269 /* XXX: only the first returned IP is used. */
270 if (inet_ntop(af, result->data[0], buffer, sizeof buffer)) {
271 *addr = xstrdup(buffer);
272 } else {
273 *addr = NULL;
274 }
275
276 return (*addr != NULL);
277 }
278
279 static bool
280 dns_resolve_sync__(const char *name, char **addr)
281 {
282 *addr = NULL;
283
284 if (ub_ctx__ == NULL) {
285 dns_resolve_init(false);
286 if (ub_ctx__ == NULL) {
287 return false;
288 }
289 }
290
291 struct ub_result *result;
292 int retval = ub_resolve(ub_ctx__, name, ns_t_a, ns_c_in, &result);
293 if (retval != 0) {
294 return false;
295 } else if (!result->havedata) {
296 ub_resolve_free(result);
297
298 retval = ub_resolve(ub_ctx__, name, ns_t_aaaa, ns_c_in, &result);
299 if (retval != 0) {
300 return false;
301 } else if (!result->havedata) {
302 ub_resolve_free(result);
303 return false;
304 }
305 }
306
307 bool success = resolve_result_to_addr__(result, addr);
308 ub_resolve_free(result);
309 return success;
310 }