summaryrefslogtreecommitdiff
path: root/lib/dns-resolve.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dns-resolve.c')
-rw-r--r--lib/dns-resolve.c310
1 files changed, 310 insertions, 0 deletions
diff --git a/lib/dns-resolve.c b/lib/dns-resolve.c
new file mode 100644
index 000000000..f1f911296
--- /dev/null
+++ b/lib/dns-resolve.c
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2017, 2018 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "dns-resolve.h"
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <errno.h>
+#include <string.h>
+#include <unbound.h>
+#include "hash.h"
+#include "openvswitch/hmap.h"
+#include "openvswitch/vlog.h"
+#include "timeval.h"
+
+VLOG_DEFINE_THIS_MODULE(dns_resolve);
+
+/* Guard all_reqs__ and resolve_state of each request. */
+static struct ovs_mutex dns_mutex__ = OVS_MUTEX_INITIALIZER;
+static struct hmap all_reqs__;
+static struct ub_ctx *ub_ctx__;
+
+static bool thread_is_daemon;
+
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+
+enum resolve_state {
+ RESOLVE_INVALID,
+ RESOLVE_PENDING,
+ RESOLVE_GOOD,
+ RESOLVE_ERROR
+};
+
+struct resolve_request {
+ struct hmap_node hmap_node; /* node for all_reqs__ */
+ char *name; /* the domain name to be resolved */
+ char *addr; /* the resolved ip address */
+ enum resolve_state state; /* state of this request */
+ time_t time; /* resolving time */
+ struct ub_result *ub_result; /* the stored unbound result */
+};
+
+static struct resolve_request *resolve_find_or_new__(const char *name)
+ OVS_REQUIRES(dns_mutex__);
+static bool resolve_check_expire__(struct resolve_request *req)
+ OVS_REQUIRES(dns_mutex__);
+static bool resolve_check_valid__(struct resolve_request *req)
+ OVS_REQUIRES(dns_mutex__);
+static bool resolve_async__(struct resolve_request *req, int qtype)
+ OVS_REQUIRES(dns_mutex__);
+static void resolve_callback__(void *req, int err, struct ub_result *)
+ OVS_REQUIRES(dns_mutex__);
+static bool resolve_result_to_addr__(struct ub_result *result, char **addr);
+static bool dns_resolve_sync__(const char *name, char **addr);
+
+/* Pass a true 'is_daemon' if you don't want the DNS-resolving to block the
+ * running thread.
+ */
+void
+dns_resolve_init(bool is_daemon)
+{
+ ub_ctx__ = ub_ctx_create();
+ if (ub_ctx__ == NULL) {
+ VLOG_ERR_RL(&rl, "Failed to create libunbound context, "
+ "so asynchronous DNS resolving is disabled.");
+ return;
+ }
+
+ int retval;
+#ifdef __linux__
+ retval = ub_ctx_resolvconf(ub_ctx__, "/etc/resolv.conf");
+ if (retval != 0) {
+ VLOG_WARN_RL(&rl, "Failed to read /etc/resolv.conf: %s",
+ ub_strerror(retval));
+ }
+#endif
+
+ /* Handles '/etc/hosts' on Linux and 'WINDIR/etc/hosts' on Windows. */
+ retval = ub_ctx_hosts(ub_ctx__, NULL);
+ if (retval != 0) {
+ VLOG_WARN_RL(&rl, "Failed to read etc/hosts: %s",
+ ub_strerror(retval));
+ }
+
+ ub_ctx_async(ub_ctx__, true);
+ hmap_init(&all_reqs__);
+ thread_is_daemon = is_daemon;
+}
+
+/* Returns true on success. Otherwise, returns false and the error information
+ * can be found in logs. If there is no error information, then the resolving
+ * is in process and the caller should call again later. The value of '*addr'
+ * is always nullified if false is returned. If this function is called under
+ * daemon-context, the resolving will undergo asynchronously. Otherwise, a
+ * synchronouse resolving will take place.
+ *
+ * This function is thread-safe.
+ *
+ * The caller is responsible for freeing the returned '*addr'.
+ */
+bool
+dns_resolve(const char *name, char **addr)
+ OVS_EXCLUDED(dns_mutex__)
+{
+ bool success = false;
+
+ if (!thread_is_daemon) {
+ return dns_resolve_sync__(name, addr);
+ }
+
+ *addr = NULL;
+ ovs_mutex_lock(&dns_mutex__);
+
+ if (ub_ctx__ == NULL) {
+ goto unlock;
+ }
+
+ /* ub_process is inside lock as it invokes resolve_callback__. */
+ int retval = ub_process(ub_ctx__);
+ if (retval != 0) {
+ VLOG_ERR_RL(&rl, "dns-resolve error: %s", ub_strerror(retval));
+ goto unlock;
+ }
+
+ struct resolve_request *req;
+ req = resolve_find_or_new__(name);
+ if (resolve_check_valid__(req)) {
+ *addr = xstrdup(req->addr);
+ success = true;
+ } else if (req->state != RESOLVE_PENDING) {
+ success = resolve_async__(req, ns_t_a);
+ }
+unlock:
+ ovs_mutex_unlock(&dns_mutex__);
+ return success;
+}
+
+void
+dns_resolve_destroy(void)
+{
+ if (ub_ctx__ != NULL) {
+ /* Outstanding requests will be killed. */
+ ub_ctx_delete(ub_ctx__);
+ ub_ctx__ = NULL;
+
+ struct resolve_request *req;
+ HMAP_FOR_EACH(req, hmap_node, &all_reqs__) {
+ ub_resolve_free(req->ub_result);
+ free(req->addr);
+ free(req->name);
+ free(req);
+ }
+ hmap_destroy(&all_reqs__);
+ }
+}
+
+static struct resolve_request *
+resolve_find_or_new__(const char *name)
+ OVS_REQUIRES(dns_mutex__)
+{
+ struct resolve_request *req;
+
+ HMAP_FOR_EACH_IN_BUCKET(req, hmap_node, hash_string(name, 0),
+ &all_reqs__) {
+ if (!strcmp(name, req->name)) {
+ return req;
+ }
+ }
+
+ req = xzalloc(sizeof *req);
+ req->name = xstrdup(name);
+ req->state = RESOLVE_INVALID;
+ hmap_insert(&all_reqs__, &req->hmap_node, hash_string(req->name, 0));
+ return req;
+}
+
+static bool
+resolve_check_expire__(struct resolve_request *req)
+ OVS_REQUIRES(dns_mutex__)
+{
+ return time_now() > req->time + req->ub_result->ttl;
+}
+
+static bool
+resolve_check_valid__(struct resolve_request *req)
+ OVS_REQUIRES(dns_mutex__)
+{
+ return (req != NULL
+ && req->state == RESOLVE_GOOD
+ && !resolve_check_expire__(req));
+}
+
+static bool
+resolve_async__(struct resolve_request *req, int qtype)
+ OVS_REQUIRES(dns_mutex__)
+{
+ if (qtype == ns_t_a || qtype == ns_t_aaaa) {
+ int retval;
+ retval = ub_resolve_async(ub_ctx__, req->name,
+ qtype, ns_c_in, req,
+ resolve_callback__, NULL);
+ if (retval != 0) {
+ req->state = RESOLVE_ERROR;
+ return false;
+ } else {
+ req->state = RESOLVE_PENDING;
+ return true;
+ }
+ }
+ return false;
+}
+
+static void
+resolve_callback__(void *req_, int err, struct ub_result *result)
+ OVS_REQUIRES(dns_mutex__)
+{
+ struct resolve_request *req = req_;
+
+ if (err != 0 || (result->qtype == ns_t_aaaa && !result->havedata)) {
+ req->state = RESOLVE_ERROR;
+ VLOG_ERR_RL(&rl, "%s: failed to resolve", req->name);
+ return;
+ }
+
+ /* IPv4 address is empty, try IPv6. */
+ if (result->qtype == ns_t_a && !result->havedata) {
+ ub_resolve_free(result);
+ resolve_async__(req, ns_t_aaaa);
+ return;
+ }
+
+ char *addr;
+ if (!resolve_result_to_addr__(result, &addr)) {
+ req->state = RESOLVE_ERROR;
+ VLOG_ERR_RL(&rl, "%s: failed to resolve", req->name);
+ return;
+ }
+
+ ub_resolve_free(req->ub_result);
+ free(req->addr);
+
+ req->ub_result = result;
+ req->addr = addr;
+ req->state = RESOLVE_GOOD;
+ req->time = time_now();
+}
+
+static bool
+resolve_result_to_addr__(struct ub_result *result, char **addr)
+{
+ int af = result->qtype == ns_t_a ? AF_INET : AF_INET6;
+ char buffer[INET6_ADDRSTRLEN];
+
+ /* XXX: only the first returned IP is used. */
+ if (inet_ntop(af, result->data[0], buffer, sizeof buffer)) {
+ *addr = xstrdup(buffer);
+ } else {
+ *addr = NULL;
+ }
+
+ return (*addr != NULL);
+}
+
+static bool
+dns_resolve_sync__(const char *name, char **addr)
+{
+ *addr = NULL;
+
+ if (ub_ctx__ == NULL) {
+ dns_resolve_init(false);
+ if (ub_ctx__ == NULL) {
+ return false;
+ }
+ }
+
+ struct ub_result *result;
+ int retval = ub_resolve(ub_ctx__, name, ns_t_a, ns_c_in, &result);
+ if (retval != 0) {
+ return false;
+ } else if (!result->havedata) {
+ ub_resolve_free(result);
+
+ retval = ub_resolve(ub_ctx__, name, ns_t_aaaa, ns_c_in, &result);
+ if (retval != 0) {
+ return false;
+ } else if (!result->havedata) {
+ ub_resolve_free(result);
+ return false;
+ }
+ }
+
+ bool success = resolve_result_to_addr__(result, addr);
+ ub_resolve_free(result);
+ return success;
+}