/* * QEMU DNS resolver * * Copyright (c) 2016 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see <http://www.gnu.org/licenses/>. * */ #include "qemu/osdep.h" #include "io/dns-resolver.h" #include "qapi/clone-visitor.h" #include "qapi/qapi-visit-sockets.h" #include "qemu/sockets.h" #include "qapi/error.h" #include "qemu/cutils.h" #ifndef AI_NUMERICSERV # define AI_NUMERICSERV 0 #endif static QIODNSResolver *instance; static GOnce instance_init = G_ONCE_INIT; static gpointer qio_dns_resolve_init_instance(gpointer unused G_GNUC_UNUSED) { instance = QIO_DNS_RESOLVER(object_new(TYPE_QIO_DNS_RESOLVER)); return NULL; } QIODNSResolver *qio_dns_resolver_get_instance(void) { g_once(&instance_init, qio_dns_resolve_init_instance, NULL); return instance; } static int qio_dns_resolver_lookup_sync_inet(QIODNSResolver *resolver, SocketAddress *addr, size_t *naddrs, SocketAddress ***addrs, Error **errp) { struct addrinfo ai, *res, *e; InetSocketAddress *iaddr = &addr->u.inet; char port[33]; char uaddr[INET6_ADDRSTRLEN + 1]; char uport[33]; int rc; Error *err = NULL; size_t i; *naddrs = 0; *addrs = NULL; memset(&ai, 0, sizeof(ai)); ai.ai_flags = AI_PASSIVE; if (iaddr->has_numeric && iaddr->numeric) { ai.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV; } ai.ai_family = inet_ai_family_from_address(iaddr, &err); ai.ai_socktype = SOCK_STREAM; if (err) { error_propagate(errp, err); return -1; } if (iaddr->host == NULL) { error_setg(errp, "host not specified"); return -1; } if (iaddr->port != NULL) { pstrcpy(port, sizeof(port), iaddr->port); } else { port[0] = '\0'; } rc = getaddrinfo(strlen(iaddr->host) ? iaddr->host : NULL, strlen(port) ? port : NULL, &ai, &res); if (rc != 0) { error_setg(errp, "address resolution failed for %s:%s: %s", iaddr->host, port, gai_strerror(rc)); return -1; } for (e = res; e != NULL; e = e->ai_next) { (*naddrs)++; } *addrs = g_new0(SocketAddress *, *naddrs); /* create socket + bind */ for (i = 0, e = res; e != NULL; i++, e = e->ai_next) { SocketAddress *newaddr = g_new0(SocketAddress, 1); newaddr->type = SOCKET_ADDRESS_TYPE_INET; getnameinfo((struct sockaddr *)e->ai_addr, e->ai_addrlen, uaddr, INET6_ADDRSTRLEN, uport, 32, NI_NUMERICHOST | NI_NUMERICSERV); newaddr->u.inet = (InetSocketAddress){ .host = g_strdup(uaddr), .port = g_strdup(uport), .has_numeric = true, .numeric = true, .has_to = iaddr->has_to, .to = iaddr->to, .has_ipv4 = iaddr->has_ipv4, .ipv4 = iaddr->ipv4, .has_ipv6 = iaddr->has_ipv6, .ipv6 = iaddr->ipv6, }; (*addrs)[i] = newaddr; } freeaddrinfo(res); return 0; } static int qio_dns_resolver_lookup_sync_nop(QIODNSResolver *resolver, SocketAddress *addr, size_t *naddrs, SocketAddress ***addrs, Error **errp) { *naddrs = 1; *addrs = g_new0(SocketAddress *, 1); (*addrs)[0] = QAPI_CLONE(SocketAddress, addr); return 0; } int qio_dns_resolver_lookup_sync(QIODNSResolver *resolver, SocketAddress *addr, size_t *naddrs, SocketAddress ***addrs, Error **errp) { switch (addr->type) { case SOCKET_ADDRESS_TYPE_INET: return qio_dns_resolver_lookup_sync_inet(resolver, addr, naddrs, addrs, errp); case SOCKET_ADDRESS_TYPE_UNIX: case SOCKET_ADDRESS_TYPE_VSOCK: case SOCKET_ADDRESS_TYPE_FD: return qio_dns_resolver_lookup_sync_nop(resolver, addr, naddrs, addrs, errp); default: abort(); } } struct QIODNSResolverLookupData { SocketAddress *addr; SocketAddress **addrs; size_t naddrs; }; static void qio_dns_resolver_lookup_data_free(gpointer opaque) { struct QIODNSResolverLookupData *data = opaque; size_t i; qapi_free_SocketAddress(data->addr); for (i = 0; i < data->naddrs; i++) { qapi_free_SocketAddress(data->addrs[i]); } g_free(data->addrs); g_free(data); } static void qio_dns_resolver_lookup_worker(QIOTask *task, gpointer opaque) { QIODNSResolver *resolver = QIO_DNS_RESOLVER(qio_task_get_source(task)); struct QIODNSResolverLookupData *data = opaque; Error *err = NULL; qio_dns_resolver_lookup_sync(resolver, data->addr, &data->naddrs, &data->addrs, &err); if (err) { qio_task_set_error(task, err); } else { qio_task_set_result_pointer(task, opaque, NULL); } object_unref(OBJECT(resolver)); } void qio_dns_resolver_lookup_async(QIODNSResolver *resolver, SocketAddress *addr, QIOTaskFunc func, gpointer opaque, GDestroyNotify notify) { QIOTask *task; struct QIODNSResolverLookupData *data = g_new0(struct QIODNSResolverLookupData, 1); data->addr = QAPI_CLONE(SocketAddress, addr); task = qio_task_new(OBJECT(resolver), func, opaque, notify); qio_task_run_in_thread(task, qio_dns_resolver_lookup_worker, data, qio_dns_resolver_lookup_data_free, NULL); } void qio_dns_resolver_lookup_result(QIODNSResolver *resolver, QIOTask *task, size_t *naddrs, SocketAddress ***addrs) { struct QIODNSResolverLookupData *data = qio_task_get_result_pointer(task); size_t i; *naddrs = 0; *addrs = NULL; if (!data) { return; } *naddrs = data->naddrs; *addrs = g_new0(SocketAddress *, data->naddrs); for (i = 0; i < data->naddrs; i++) { (*addrs)[i] = QAPI_CLONE(SocketAddress, data->addrs[i]); } } static const TypeInfo qio_dns_resolver_info = { .parent = TYPE_OBJECT, .name = TYPE_QIO_DNS_RESOLVER, .instance_size = sizeof(QIODNSResolver), .class_size = sizeof(QIODNSResolverClass), }; static void qio_dns_resolver_register_types(void) { type_register_static(&qio_dns_resolver_info); } type_init(qio_dns_resolver_register_types);