| /* |
| * 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.1 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" |
| #include "qemu/module.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, |
| #ifdef HAVE_IPPROTO_MPTCP |
| .has_mptcp = iaddr->has_mptcp, |
| .mptcp = iaddr->mptcp, |
| #endif |
| }; |
| |
| (*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), |
| }; |
| |
| |
| static void qio_dns_resolver_register_types(void) |
| { |
| type_register_static(&qio_dns_resolver_info); |
| } |
| |
| |
| type_init(qio_dns_resolver_register_types); |