| /* | |
| * inet and unix socket functions for qemu | |
| * | |
| * (c) 2008 Gerd Hoffmann <kraxel@redhat.com> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; under version 2 of the License. | |
| * | |
| * This program 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 General Public License for more details. | |
| * | |
| * Contributions after 2012-01-13 are licensed under the terms of the | |
| * GNU GPL, version 2 or (at your option) any later version. | |
| */ | |
| #include "qemu/osdep.h" | |
| #ifdef CONFIG_AF_VSOCK | |
| #include <linux/vm_sockets.h> | |
| #endif /* CONFIG_AF_VSOCK */ | |
| #include "monitor/monitor.h" | |
| #include "qapi/clone-visitor.h" | |
| #include "qapi/error.h" | |
| #include "qapi/qapi-visit-sockets.h" | |
| #include "qemu/sockets.h" | |
| #include "qemu/main-loop.h" | |
| #include "qapi/qobject-input-visitor.h" | |
| #include "qapi/qobject-output-visitor.h" | |
| #include "qemu/cutils.h" | |
| #include "qemu/option.h" | |
| #include "trace.h" | |
| #ifndef AI_ADDRCONFIG | |
| # define AI_ADDRCONFIG 0 | |
| #endif | |
| #ifndef AI_V4MAPPED | |
| # define AI_V4MAPPED 0 | |
| #endif | |
| #ifndef AI_NUMERICSERV | |
| # define AI_NUMERICSERV 0 | |
| #endif | |
| /* | |
| * On macOS TCP_KEEPIDLE is available under a different name, TCP_KEEPALIVE. | |
| * https://github.com/apple/darwin-xnu/blob/xnu-4570.1.46/bsd/man/man4/tcp.4#L172 | |
| */ | |
| #if defined(TCP_KEEPALIVE) && !defined(TCP_KEEPIDLE) | |
| # define TCP_KEEPIDLE TCP_KEEPALIVE | |
| #endif | |
| static int inet_getport(struct addrinfo *e) | |
| { | |
| struct sockaddr_in *i4; | |
| struct sockaddr_in6 *i6; | |
| switch (e->ai_family) { | |
| case PF_INET6: | |
| i6 = (void*)e->ai_addr; | |
| return ntohs(i6->sin6_port); | |
| case PF_INET: | |
| i4 = (void*)e->ai_addr; | |
| return ntohs(i4->sin_port); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static void inet_setport(struct addrinfo *e, int port) | |
| { | |
| struct sockaddr_in *i4; | |
| struct sockaddr_in6 *i6; | |
| switch (e->ai_family) { | |
| case PF_INET6: | |
| i6 = (void*)e->ai_addr; | |
| i6->sin6_port = htons(port); | |
| break; | |
| case PF_INET: | |
| i4 = (void*)e->ai_addr; | |
| i4->sin_port = htons(port); | |
| break; | |
| } | |
| } | |
| NetworkAddressFamily inet_netfamily(int family) | |
| { | |
| switch (family) { | |
| case PF_INET6: return NETWORK_ADDRESS_FAMILY_IPV6; | |
| case PF_INET: return NETWORK_ADDRESS_FAMILY_IPV4; | |
| case PF_UNIX: return NETWORK_ADDRESS_FAMILY_UNIX; | |
| #ifdef CONFIG_AF_VSOCK | |
| case PF_VSOCK: return NETWORK_ADDRESS_FAMILY_VSOCK; | |
| #endif /* CONFIG_AF_VSOCK */ | |
| } | |
| return NETWORK_ADDRESS_FAMILY_UNKNOWN; | |
| } | |
| bool fd_is_socket(int fd) | |
| { | |
| int optval; | |
| socklen_t optlen = sizeof(optval); | |
| return !getsockopt(fd, SOL_SOCKET, SO_TYPE, &optval, &optlen); | |
| } | |
| /* | |
| * Matrix we're trying to apply | |
| * | |
| * ipv4 ipv6 family | |
| * - - PF_UNSPEC | |
| * - f PF_INET | |
| * - t PF_INET6 | |
| * f - PF_INET6 | |
| * f f <error> | |
| * f t PF_INET6 | |
| * t - PF_INET | |
| * t f PF_INET | |
| * t t PF_INET6/PF_UNSPEC | |
| * | |
| * NB, this matrix is only about getting the necessary results | |
| * from getaddrinfo(). Some of the cases require further work | |
| * after reading results from getaddrinfo in order to fully | |
| * apply the logic the end user wants. | |
| * | |
| * In the first and last cases, we must set IPV6_V6ONLY=0 | |
| * when binding, to allow a single listener to potentially | |
| * accept both IPv4+6 addresses. | |
| */ | |
| int inet_ai_family_from_address(InetSocketAddress *addr, | |
| Error **errp) | |
| { | |
| if (addr->has_ipv6 && addr->has_ipv4 && | |
| !addr->ipv6 && !addr->ipv4) { | |
| error_setg(errp, "Cannot disable IPv4 and IPv6 at same time"); | |
| return PF_UNSPEC; | |
| } | |
| if ((addr->has_ipv6 && addr->ipv6) && (addr->has_ipv4 && addr->ipv4)) { | |
| /* | |
| * Some backends can only do a single listener. In that case | |
| * we want empty hostname to resolve to "::" and then use the | |
| * flag IPV6_V6ONLY==0 to get both protocols on 1 socket. This | |
| * doesn't work for addresses other than "", so they're just | |
| * inevitably broken until multiple listeners can be used, | |
| * and thus we honour getaddrinfo automatic protocol detection | |
| * Once all backends do multi-listener, remove the PF_INET6 | |
| * branch entirely. | |
| */ | |
| if (!addr->host || g_str_equal(addr->host, "")) { | |
| return PF_INET6; | |
| } else { | |
| return PF_UNSPEC; | |
| } | |
| } | |
| if ((addr->has_ipv6 && addr->ipv6) || (addr->has_ipv4 && !addr->ipv4)) { | |
| return PF_INET6; | |
| } | |
| if ((addr->has_ipv4 && addr->ipv4) || (addr->has_ipv6 && !addr->ipv6)) { | |
| return PF_INET; | |
| } | |
| return PF_UNSPEC; | |
| } | |
| static int create_fast_reuse_socket(struct addrinfo *e) | |
| { | |
| int slisten = qemu_socket(e->ai_family, e->ai_socktype, e->ai_protocol); | |
| if (slisten < 0) { | |
| return -1; | |
| } | |
| socket_set_fast_reuse(slisten); | |
| return slisten; | |
| } | |
| static int try_bind(int socket, InetSocketAddress *saddr, struct addrinfo *e) | |
| { | |
| #ifndef IPV6_V6ONLY | |
| return bind(socket, e->ai_addr, e->ai_addrlen); | |
| #else | |
| /* | |
| * Deals with first & last cases in matrix in comment | |
| * for inet_ai_family_from_address(). | |
| */ | |
| int v6only = | |
| ((!saddr->has_ipv4 && !saddr->has_ipv6) || | |
| (saddr->has_ipv4 && saddr->ipv4 && | |
| saddr->has_ipv6 && saddr->ipv6)) ? 0 : 1; | |
| int stat; | |
| rebind: | |
| if (e->ai_family == PF_INET6) { | |
| setsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, | |
| sizeof(v6only)); | |
| } | |
| stat = bind(socket, e->ai_addr, e->ai_addrlen); | |
| if (!stat) { | |
| return 0; | |
| } | |
| /* If we got EADDRINUSE from an IPv6 bind & v6only is unset, | |
| * it could be that the IPv4 port is already claimed, so retry | |
| * with v6only set | |
| */ | |
| if (e->ai_family == PF_INET6 && errno == EADDRINUSE && !v6only) { | |
| v6only = 1; | |
| goto rebind; | |
| } | |
| return stat; | |
| #endif | |
| } | |
| static int inet_set_sockopts(int sock, InetSocketAddress *saddr, Error **errp) | |
| { | |
| if (saddr->keep_alive) { | |
| int keep_alive = 1; | |
| int ret = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, | |
| &keep_alive, sizeof(keep_alive)); | |
| if (ret < 0) { | |
| error_setg_errno(errp, errno, | |
| "Unable to set keep-alive option on socket"); | |
| return -1; | |
| } | |
| #ifdef HAVE_TCP_KEEPCNT | |
| if (saddr->has_keep_alive_count && saddr->keep_alive_count) { | |
| int keep_count = saddr->keep_alive_count; | |
| ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keep_count, | |
| sizeof(keep_count)); | |
| if (ret < 0) { | |
| error_setg_errno(errp, errno, | |
| "Unable to set TCP keep-alive count option on socket"); | |
| return -1; | |
| } | |
| } | |
| #endif | |
| #ifdef HAVE_TCP_KEEPIDLE | |
| if (saddr->has_keep_alive_idle && saddr->keep_alive_idle) { | |
| int keep_idle = saddr->keep_alive_idle; | |
| ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keep_idle, | |
| sizeof(keep_idle)); | |
| if (ret < 0) { | |
| error_setg_errno(errp, errno, | |
| "Unable to set TCP keep-alive idle option on socket"); | |
| return -1; | |
| } | |
| } | |
| #endif | |
| #ifdef HAVE_TCP_KEEPINTVL | |
| if (saddr->has_keep_alive_interval && saddr->keep_alive_interval) { | |
| int keep_interval = saddr->keep_alive_interval; | |
| ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keep_interval, | |
| sizeof(keep_interval)); | |
| if (ret < 0) { | |
| error_setg_errno(errp, errno, | |
| "Unable to set TCP keep-alive interval option on socket"); | |
| return -1; | |
| } | |
| } | |
| #endif | |
| } | |
| return 0; | |
| } | |
| static int inet_listen_saddr(InetSocketAddress *saddr, | |
| int port_offset, | |
| int num, | |
| Error **errp) | |
| { | |
| ERRP_GUARD(); | |
| struct addrinfo ai, *res, *e; | |
| char port[33]; | |
| char uaddr[INET6_ADDRSTRLEN+1]; | |
| char uport[33]; | |
| int rc, port_min, port_max, p; | |
| int slisten = -1; | |
| int saved_errno = 0; | |
| bool socket_created = false; | |
| memset(&ai,0, sizeof(ai)); | |
| ai.ai_flags = AI_PASSIVE; | |
| if (saddr->has_numeric && saddr->numeric) { | |
| ai.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV; | |
| } | |
| ai.ai_socktype = SOCK_STREAM; | |
| ai.ai_family = inet_ai_family_from_address(saddr, errp); | |
| if (*errp) { | |
| return -1; | |
| } | |
| if (saddr->host == NULL) { | |
| error_setg(errp, "host not specified"); | |
| return -1; | |
| } | |
| if (saddr->port != NULL) { | |
| pstrcpy(port, sizeof(port), saddr->port); | |
| } else { | |
| port[0] = '\0'; | |
| } | |
| /* lookup */ | |
| if (port_offset) { | |
| uint64_t baseport; | |
| if (strlen(port) == 0) { | |
| error_setg(errp, "port not specified"); | |
| return -1; | |
| } | |
| if (parse_uint_full(port, 10, &baseport) < 0) { | |
| error_setg(errp, "can't convert to a number: %s", port); | |
| return -1; | |
| } | |
| if (baseport > 65535 || | |
| baseport + port_offset > 65535) { | |
| error_setg(errp, "port %s out of range", port); | |
| return -1; | |
| } | |
| snprintf(port, sizeof(port), "%d", (int)baseport + port_offset); | |
| } | |
| rc = getaddrinfo(strlen(saddr->host) ? saddr->host : NULL, | |
| strlen(port) ? port : NULL, &ai, &res); | |
| if (rc != 0) { | |
| error_setg(errp, "address resolution failed for %s:%s: %s", | |
| saddr->host, port, gai_strerror(rc)); | |
| return -1; | |
| } | |
| /* create socket + bind/listen */ | |
| for (e = res; e != NULL; e = e->ai_next) { | |
| #ifdef HAVE_IPPROTO_MPTCP | |
| if (saddr->has_mptcp && saddr->mptcp) { | |
| e->ai_protocol = IPPROTO_MPTCP; | |
| } | |
| #endif | |
| getnameinfo((struct sockaddr*)e->ai_addr,e->ai_addrlen, | |
| uaddr,INET6_ADDRSTRLEN,uport,32, | |
| NI_NUMERICHOST | NI_NUMERICSERV); | |
| port_min = inet_getport(e); | |
| port_max = saddr->has_to ? saddr->to + port_offset : port_min; | |
| for (p = port_min; p <= port_max; p++) { | |
| if (slisten >= 0) { | |
| /* | |
| * We have a socket we tried with the previous port. It cannot | |
| * be rebound, we need to close it and create a new one. | |
| */ | |
| close(slisten); | |
| slisten = -1; | |
| } | |
| inet_setport(e, p); | |
| slisten = create_fast_reuse_socket(e); | |
| if (slisten < 0) { | |
| /* | |
| * First time we expect we might fail to create the socket | |
| * eg if 'e' has AF_INET6 but ipv6 kmod is not loaded. | |
| * Later iterations should always succeed if first iteration | |
| * worked though, so treat that as fatal. | |
| */ | |
| if (p == port_min) { | |
| continue; | |
| } else { | |
| error_setg_errno(errp, errno, | |
| "Failed to recreate failed listening socket"); | |
| goto fail; | |
| } | |
| } | |
| socket_created = true; | |
| rc = try_bind(slisten, saddr, e); | |
| if (rc < 0) { | |
| if (errno == EADDRINUSE) { | |
| /* This port is already used, try the next one */ | |
| continue; | |
| } | |
| error_setg_errno(errp, errno, "Failed to bind socket"); | |
| goto fail; | |
| } | |
| if (listen(slisten, num)) { | |
| if (errno == EADDRINUSE) { | |
| /* This port is already used, try the next one */ | |
| continue; | |
| } | |
| error_setg_errno(errp, errno, "Failed to listen on socket"); | |
| goto fail; | |
| } | |
| /* We have a listening socket */ | |
| if (inet_set_sockopts(slisten, saddr, errp) < 0) { | |
| goto fail; | |
| } | |
| freeaddrinfo(res); | |
| return slisten; | |
| } | |
| } | |
| error_setg_errno(errp, errno, | |
| socket_created ? | |
| "Failed to find an available port" : | |
| "Failed to create a socket"); | |
| fail: | |
| saved_errno = errno; | |
| if (slisten >= 0) { | |
| close(slisten); | |
| } | |
| freeaddrinfo(res); | |
| errno = saved_errno; | |
| return -1; | |
| } | |
| #ifdef _WIN32 | |
| #define QEMU_SOCKET_RC_INPROGRESS(rc) \ | |
| ((rc) == -EINPROGRESS || (rc) == -EWOULDBLOCK || (rc) == -WSAEALREADY) | |
| #else | |
| #define QEMU_SOCKET_RC_INPROGRESS(rc) \ | |
| ((rc) == -EINPROGRESS) | |
| #endif | |
| static int inet_connect_addr(const InetSocketAddress *saddr, | |
| struct addrinfo *addr, Error **errp) | |
| { | |
| int sock, rc; | |
| sock = qemu_socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); | |
| if (sock < 0) { | |
| error_setg_errno(errp, errno, "Failed to create socket family %d", | |
| addr->ai_family); | |
| return -1; | |
| } | |
| /* connect to peer */ | |
| do { | |
| rc = 0; | |
| if (connect(sock, addr->ai_addr, addr->ai_addrlen) < 0) { | |
| rc = -errno; | |
| } | |
| } while (rc == -EINTR); | |
| if (rc < 0) { | |
| error_setg_errno(errp, errno, "Failed to connect to '%s:%s'", | |
| saddr->host, saddr->port); | |
| close(sock); | |
| return -1; | |
| } | |
| return sock; | |
| } | |
| static struct addrinfo *inet_parse_connect_saddr(InetSocketAddress *saddr, | |
| Error **errp) | |
| { | |
| ERRP_GUARD(); | |
| struct addrinfo ai, *res; | |
| int rc; | |
| static int useV4Mapped = 1; | |
| memset(&ai, 0, sizeof(ai)); | |
| ai.ai_flags = AI_CANONNAME | AI_ADDRCONFIG; | |
| if (qatomic_read(&useV4Mapped)) { | |
| ai.ai_flags |= AI_V4MAPPED; | |
| } | |
| ai.ai_socktype = SOCK_STREAM; | |
| ai.ai_family = inet_ai_family_from_address(saddr, errp); | |
| if (*errp) { | |
| return NULL; | |
| } | |
| if (saddr->host == NULL || saddr->port == NULL) { | |
| error_setg(errp, "host and/or port not specified"); | |
| return NULL; | |
| } | |
| /* lookup */ | |
| rc = getaddrinfo(saddr->host, saddr->port, &ai, &res); | |
| /* At least FreeBSD and OS-X 10.6 declare AI_V4MAPPED but | |
| * then don't implement it in their getaddrinfo(). Detect | |
| * this and retry without the flag since that's preferable | |
| * to a fatal error | |
| */ | |
| if (rc == EAI_BADFLAGS && | |
| (ai.ai_flags & AI_V4MAPPED)) { | |
| qatomic_set(&useV4Mapped, 0); | |
| ai.ai_flags &= ~AI_V4MAPPED; | |
| rc = getaddrinfo(saddr->host, saddr->port, &ai, &res); | |
| } | |
| if (rc != 0) { | |
| error_setg(errp, "address resolution failed for %s:%s: %s", | |
| saddr->host, saddr->port, gai_strerror(rc)); | |
| return NULL; | |
| } | |
| return res; | |
| } | |
| /** | |
| * Create a socket and connect it to an address. | |
| * | |
| * @saddr: Inet socket address specification | |
| * @errp: set on error | |
| * | |
| * Returns: -1 on error, file descriptor on success. | |
| */ | |
| int inet_connect_saddr(InetSocketAddress *saddr, Error **errp) | |
| { | |
| Error *local_err = NULL; | |
| struct addrinfo *res, *e; | |
| int sock = -1; | |
| res = inet_parse_connect_saddr(saddr, errp); | |
| if (!res) { | |
| return -1; | |
| } | |
| for (e = res; e != NULL; e = e->ai_next) { | |
| error_free(local_err); | |
| local_err = NULL; | |
| #ifdef HAVE_IPPROTO_MPTCP | |
| if (saddr->has_mptcp && saddr->mptcp) { | |
| e->ai_protocol = IPPROTO_MPTCP; | |
| } | |
| #endif | |
| sock = inet_connect_addr(saddr, e, &local_err); | |
| if (sock >= 0) { | |
| break; | |
| } | |
| } | |
| freeaddrinfo(res); | |
| if (sock < 0) { | |
| error_propagate(errp, local_err); | |
| return sock; | |
| } | |
| if (inet_set_sockopts(sock, saddr, errp) < 0) { | |
| close(sock); | |
| return -1; | |
| } | |
| return sock; | |
| } | |
| static int inet_dgram_saddr(InetSocketAddress *sraddr, | |
| InetSocketAddress *sladdr, | |
| Error **errp) | |
| { | |
| ERRP_GUARD(); | |
| struct addrinfo ai, *peer = NULL, *local = NULL; | |
| const char *addr; | |
| const char *port; | |
| int sock = -1, rc; | |
| /* lookup peer addr */ | |
| memset(&ai,0, sizeof(ai)); | |
| ai.ai_flags = AI_CANONNAME | AI_V4MAPPED | AI_ADDRCONFIG; | |
| ai.ai_socktype = SOCK_DGRAM; | |
| ai.ai_family = inet_ai_family_from_address(sraddr, errp); | |
| if (*errp) { | |
| goto err; | |
| } | |
| addr = sraddr->host; | |
| port = sraddr->port; | |
| if (addr == NULL || strlen(addr) == 0) { | |
| addr = "localhost"; | |
| } | |
| if (port == NULL || strlen(port) == 0) { | |
| error_setg(errp, "remote port not specified"); | |
| goto err; | |
| } | |
| if ((rc = getaddrinfo(addr, port, &ai, &peer)) != 0) { | |
| error_setg(errp, "address resolution failed for %s:%s: %s", addr, port, | |
| gai_strerror(rc)); | |
| goto err; | |
| } | |
| /* lookup local addr */ | |
| memset(&ai,0, sizeof(ai)); | |
| ai.ai_flags = AI_PASSIVE; | |
| ai.ai_family = peer->ai_family; | |
| ai.ai_socktype = SOCK_DGRAM; | |
| if (sladdr) { | |
| addr = sladdr->host; | |
| port = sladdr->port; | |
| if (addr == NULL || strlen(addr) == 0) { | |
| addr = NULL; | |
| } | |
| if (!port || strlen(port) == 0) { | |
| port = "0"; | |
| } | |
| } else { | |
| addr = NULL; | |
| port = "0"; | |
| } | |
| if ((rc = getaddrinfo(addr, port, &ai, &local)) != 0) { | |
| error_setg(errp, "address resolution failed for %s:%s: %s", addr, port, | |
| gai_strerror(rc)); | |
| goto err; | |
| } | |
| /* create socket */ | |
| sock = qemu_socket(peer->ai_family, peer->ai_socktype, peer->ai_protocol); | |
| if (sock < 0) { | |
| error_setg_errno(errp, errno, "Failed to create socket family %d", | |
| peer->ai_family); | |
| goto err; | |
| } | |
| socket_set_fast_reuse(sock); | |
| /* bind socket */ | |
| if (bind(sock, local->ai_addr, local->ai_addrlen) < 0) { | |
| error_setg_errno(errp, errno, "Failed to bind socket"); | |
| goto err; | |
| } | |
| /* connect to peer */ | |
| if (connect(sock,peer->ai_addr,peer->ai_addrlen) < 0) { | |
| error_setg_errno(errp, errno, "Failed to connect to '%s:%s'", | |
| addr, port); | |
| goto err; | |
| } | |
| freeaddrinfo(local); | |
| freeaddrinfo(peer); | |
| return sock; | |
| err: | |
| if (sock != -1) { | |
| close(sock); | |
| } | |
| if (local) { | |
| freeaddrinfo(local); | |
| } | |
| if (peer) { | |
| freeaddrinfo(peer); | |
| } | |
| return -1; | |
| } | |
| static QemuOptsList inet_opts = { | |
| .name = "InetSocketAddress", | |
| .head = QTAILQ_HEAD_INITIALIZER(inet_opts.head), | |
| .implied_opt_name = "addr", | |
| .desc = { | |
| { | |
| .name = "addr", | |
| .type = QEMU_OPT_STRING, | |
| }, | |
| { | |
| .name = "numeric", | |
| .type = QEMU_OPT_BOOL, | |
| }, | |
| { | |
| .name = "to", | |
| .type = QEMU_OPT_NUMBER, | |
| }, | |
| { | |
| .name = "ipv4", | |
| .type = QEMU_OPT_BOOL, | |
| }, | |
| { | |
| .name = "ipv6", | |
| .type = QEMU_OPT_BOOL, | |
| }, | |
| { | |
| .name = "keep-alive", | |
| .type = QEMU_OPT_BOOL, | |
| }, | |
| #ifdef HAVE_TCP_KEEPCNT | |
| { | |
| .name = "keep-alive-count", | |
| .type = QEMU_OPT_NUMBER, | |
| }, | |
| #endif | |
| #ifdef HAVE_TCP_KEEPIDLE | |
| { | |
| .name = "keep-alive-idle", | |
| .type = QEMU_OPT_NUMBER, | |
| }, | |
| #endif | |
| #ifdef HAVE_TCP_KEEPINTVL | |
| { | |
| .name = "keep-alive-interval", | |
| .type = QEMU_OPT_NUMBER, | |
| }, | |
| #endif | |
| #ifdef HAVE_IPPROTO_MPTCP | |
| { | |
| .name = "mptcp", | |
| .type = QEMU_OPT_BOOL, | |
| }, | |
| #endif | |
| { /* end of list */ } | |
| }, | |
| }; | |
| int inet_parse(InetSocketAddress *addr, const char *str, Error **errp) | |
| { | |
| QemuOpts *opts = qemu_opts_parse(&inet_opts, str, true, errp); | |
| if (!opts) { | |
| return -1; | |
| } | |
| memset(addr, 0, sizeof(*addr)); | |
| /* parse address */ | |
| const char *addr_str = qemu_opt_get(opts, "addr"); | |
| if (!addr_str) { | |
| error_setg(errp, "error parsing address ''"); | |
| return -1; | |
| } | |
| if (str[0] == '[') { | |
| /* IPv6 addr */ | |
| const char *ip_end = strstr(addr_str, "]:"); | |
| if (!ip_end || ip_end - addr_str < 2 || strlen(ip_end) < 3) { | |
| error_setg(errp, "error parsing IPv6 address '%s'", addr_str); | |
| return -1; | |
| } | |
| addr->host = g_strndup(addr_str + 1, ip_end - addr_str - 1); | |
| addr->port = g_strdup(ip_end + 2); | |
| } else { | |
| /* no host, hostname or IPv4 addr */ | |
| const char *port = strchr(addr_str, ':'); | |
| if (!port || strlen(port) < 2) { | |
| error_setg(errp, "error parsing address '%s'", addr_str); | |
| return -1; | |
| } | |
| addr->host = g_strndup(addr_str, port - addr_str); | |
| addr->port = g_strdup(port + 1); | |
| } | |
| /* parse options */ | |
| if (qemu_opt_find(opts, "numeric")) { | |
| addr->has_numeric = true, | |
| addr->numeric = qemu_opt_get_bool(opts, "numeric", false); | |
| } | |
| if (qemu_opt_find(opts, "to")) { | |
| addr->has_to = true; | |
| addr->to = qemu_opt_get_number(opts, "to", 0); | |
| } | |
| if (qemu_opt_find(opts, "ipv4")) { | |
| addr->has_ipv4 = true; | |
| addr->ipv4 = qemu_opt_get_bool(opts, "ipv4", false); | |
| } | |
| if (qemu_opt_find(opts, "ipv6")) { | |
| addr->has_ipv6 = true; | |
| addr->ipv6 = qemu_opt_get_bool(opts, "ipv6", false); | |
| } | |
| if (qemu_opt_find(opts, "keep-alive")) { | |
| addr->has_keep_alive = true; | |
| addr->keep_alive = qemu_opt_get_bool(opts, "keep-alive", false); | |
| } | |
| #ifdef HAVE_TCP_KEEPCNT | |
| if (qemu_opt_find(opts, "keep-alive-count")) { | |
| addr->has_keep_alive_count = true; | |
| addr->keep_alive_count = qemu_opt_get_number(opts, "keep-alive-count", 0); | |
| } | |
| #endif | |
| #ifdef HAVE_TCP_KEEPIDLE | |
| if (qemu_opt_find(opts, "keep-alive-idle")) { | |
| addr->has_keep_alive_idle = true; | |
| addr->keep_alive_idle = qemu_opt_get_number(opts, "keep-alive-idle", 0); | |
| } | |
| #endif | |
| #ifdef HAVE_TCP_KEEPINTVL | |
| if (qemu_opt_find(opts, "keep-alive-interval")) { | |
| addr->has_keep_alive_interval = true; | |
| addr->keep_alive_interval = qemu_opt_get_number(opts, "keep-alive-interval", 0); | |
| } | |
| #endif | |
| #ifdef HAVE_IPPROTO_MPTCP | |
| if (qemu_opt_find(opts, "mptcp")) { | |
| addr->has_mptcp = true; | |
| addr->mptcp = qemu_opt_get_bool(opts, "mptcp", 0); | |
| } | |
| #endif | |
| return 0; | |
| } | |
| #ifdef CONFIG_AF_VSOCK | |
| static bool vsock_parse_vaddr_to_sockaddr(const VsockSocketAddress *vaddr, | |
| struct sockaddr_vm *svm, | |
| Error **errp) | |
| { | |
| uint64_t val; | |
| memset(svm, 0, sizeof(*svm)); | |
| svm->svm_family = AF_VSOCK; | |
| if (parse_uint_full(vaddr->cid, 10, &val) < 0 || | |
| val > UINT32_MAX) { | |
| error_setg(errp, "Failed to parse cid '%s'", vaddr->cid); | |
| return false; | |
| } | |
| svm->svm_cid = val; | |
| if (parse_uint_full(vaddr->port, 10, &val) < 0 || | |
| val > UINT32_MAX) { | |
| error_setg(errp, "Failed to parse port '%s'", vaddr->port); | |
| return false; | |
| } | |
| svm->svm_port = val; | |
| return true; | |
| } | |
| static int vsock_connect_addr(const VsockSocketAddress *vaddr, | |
| const struct sockaddr_vm *svm, Error **errp) | |
| { | |
| int sock, rc; | |
| sock = qemu_socket(AF_VSOCK, SOCK_STREAM, 0); | |
| if (sock < 0) { | |
| error_setg_errno(errp, errno, "Failed to create socket family %d", | |
| AF_VSOCK); | |
| return -1; | |
| } | |
| /* connect to peer */ | |
| do { | |
| rc = 0; | |
| if (connect(sock, (const struct sockaddr *)svm, sizeof(*svm)) < 0) { | |
| rc = -errno; | |
| } | |
| } while (rc == -EINTR); | |
| if (rc < 0) { | |
| error_setg_errno(errp, errno, "Failed to connect to '%s:%s'", | |
| vaddr->cid, vaddr->port); | |
| close(sock); | |
| return -1; | |
| } | |
| return sock; | |
| } | |
| static int vsock_connect_saddr(VsockSocketAddress *vaddr, Error **errp) | |
| { | |
| struct sockaddr_vm svm; | |
| if (!vsock_parse_vaddr_to_sockaddr(vaddr, &svm, errp)) { | |
| return -1; | |
| } | |
| return vsock_connect_addr(vaddr, &svm, errp); | |
| } | |
| static int vsock_listen_saddr(VsockSocketAddress *vaddr, | |
| int num, | |
| Error **errp) | |
| { | |
| struct sockaddr_vm svm; | |
| int slisten; | |
| if (!vsock_parse_vaddr_to_sockaddr(vaddr, &svm, errp)) { | |
| return -1; | |
| } | |
| slisten = qemu_socket(AF_VSOCK, SOCK_STREAM, 0); | |
| if (slisten < 0) { | |
| error_setg_errno(errp, errno, "Failed to create socket"); | |
| return -1; | |
| } | |
| if (bind(slisten, (const struct sockaddr *)&svm, sizeof(svm)) != 0) { | |
| error_setg_errno(errp, errno, "Failed to bind socket"); | |
| close(slisten); | |
| return -1; | |
| } | |
| if (listen(slisten, num) != 0) { | |
| error_setg_errno(errp, errno, "Failed to listen on socket"); | |
| close(slisten); | |
| return -1; | |
| } | |
| return slisten; | |
| } | |
| static int vsock_parse(VsockSocketAddress *addr, const char *str, | |
| Error **errp) | |
| { | |
| char cid[33]; | |
| char port[33]; | |
| int n; | |
| if (sscanf(str, "%32[^:]:%32[^,]%n", cid, port, &n) != 2) { | |
| error_setg(errp, "error parsing address '%s'", str); | |
| return -1; | |
| } | |
| if (str[n] != '\0') { | |
| error_setg(errp, "trailing characters in address '%s'", str); | |
| return -1; | |
| } | |
| addr->cid = g_strdup(cid); | |
| addr->port = g_strdup(port); | |
| return 0; | |
| } | |
| #else | |
| static void vsock_unsupported(Error **errp) | |
| { | |
| error_setg(errp, "socket family AF_VSOCK unsupported"); | |
| } | |
| static int vsock_connect_saddr(VsockSocketAddress *vaddr, Error **errp) | |
| { | |
| vsock_unsupported(errp); | |
| return -1; | |
| } | |
| static int vsock_listen_saddr(VsockSocketAddress *vaddr, | |
| int num, | |
| Error **errp) | |
| { | |
| vsock_unsupported(errp); | |
| return -1; | |
| } | |
| static int vsock_parse(VsockSocketAddress *addr, const char *str, | |
| Error **errp) | |
| { | |
| vsock_unsupported(errp); | |
| return -1; | |
| } | |
| #endif /* CONFIG_AF_VSOCK */ | |
| static bool saddr_is_abstract(UnixSocketAddress *saddr) | |
| { | |
| #ifdef CONFIG_LINUX | |
| return saddr->abstract; | |
| #else | |
| return false; | |
| #endif | |
| } | |
| static bool saddr_is_tight(UnixSocketAddress *saddr) | |
| { | |
| #ifdef CONFIG_LINUX | |
| return !saddr->has_tight || saddr->tight; | |
| #else | |
| return false; | |
| #endif | |
| } | |
| static int unix_listen_saddr(UnixSocketAddress *saddr, | |
| int num, | |
| Error **errp) | |
| { | |
| bool abstract = saddr_is_abstract(saddr); | |
| struct sockaddr_un un; | |
| int sock, fd; | |
| char *pathbuf = NULL; | |
| const char *path; | |
| size_t pathlen; | |
| size_t addrlen; | |
| sock = qemu_socket(PF_UNIX, SOCK_STREAM, 0); | |
| if (sock < 0) { | |
| error_setg_errno(errp, errno, "Failed to create Unix socket"); | |
| return -1; | |
| } | |
| if (saddr->path[0] || abstract) { | |
| path = saddr->path; | |
| } else { | |
| path = pathbuf = g_strdup_printf("%s/qemu-socket-XXXXXX", | |
| g_get_tmp_dir()); | |
| } | |
| pathlen = strlen(path); | |
| if (pathlen > sizeof(un.sun_path) || | |
| (abstract && pathlen > (sizeof(un.sun_path) - 1))) { | |
| error_setg(errp, "UNIX socket path '%s' is too long", path); | |
| error_append_hint(errp, "Path must be less than %zu bytes\n", | |
| abstract ? sizeof(un.sun_path) - 1 : | |
| sizeof(un.sun_path)); | |
| goto err; | |
| } | |
| if (pathbuf != NULL) { | |
| /* | |
| * This dummy fd usage silences the mktemp() insecure warning. | |
| * Using mkstemp() doesn't make things more secure here | |
| * though. bind() complains about existing files, so we have | |
| * to unlink first and thus re-open the race window. The | |
| * worst case possible is bind() failing, i.e. a DoS attack. | |
| */ | |
| fd = mkstemp(pathbuf); | |
| if (fd < 0) { | |
| error_setg_errno(errp, errno, | |
| "Failed to make a temporary socket %s", pathbuf); | |
| goto err; | |
| } | |
| close(fd); | |
| } | |
| if (!abstract && unlink(path) < 0 && errno != ENOENT) { | |
| error_setg_errno(errp, errno, | |
| "Failed to unlink socket %s", path); | |
| goto err; | |
| } | |
| memset(&un, 0, sizeof(un)); | |
| un.sun_family = AF_UNIX; | |
| addrlen = sizeof(un); | |
| if (abstract) { | |
| un.sun_path[0] = '\0'; | |
| memcpy(&un.sun_path[1], path, pathlen); | |
| if (saddr_is_tight(saddr)) { | |
| addrlen = offsetof(struct sockaddr_un, sun_path) + 1 + pathlen; | |
| } | |
| } else { | |
| memcpy(un.sun_path, path, pathlen); | |
| } | |
| if (bind(sock, (struct sockaddr *) &un, addrlen) < 0) { | |
| error_setg_errno(errp, errno, "Failed to bind socket to %s", path); | |
| goto err; | |
| } | |
| if (listen(sock, num) < 0) { | |
| error_setg_errno(errp, errno, "Failed to listen on socket"); | |
| goto err; | |
| } | |
| g_free(pathbuf); | |
| return sock; | |
| err: | |
| g_free(pathbuf); | |
| close(sock); | |
| return -1; | |
| } | |
| static int unix_connect_saddr(UnixSocketAddress *saddr, Error **errp) | |
| { | |
| bool abstract = saddr_is_abstract(saddr); | |
| struct sockaddr_un un; | |
| int sock, rc; | |
| size_t pathlen; | |
| size_t addrlen; | |
| if (saddr->path == NULL) { | |
| error_setg(errp, "unix connect: no path specified"); | |
| return -1; | |
| } | |
| sock = qemu_socket(PF_UNIX, SOCK_STREAM, 0); | |
| if (sock < 0) { | |
| error_setg_errno(errp, errno, "Failed to create socket"); | |
| return -1; | |
| } | |
| pathlen = strlen(saddr->path); | |
| if (pathlen > sizeof(un.sun_path) || | |
| (abstract && pathlen > (sizeof(un.sun_path) - 1))) { | |
| error_setg(errp, "UNIX socket path '%s' is too long", saddr->path); | |
| error_append_hint(errp, "Path must be less than %zu bytes\n", | |
| abstract ? sizeof(un.sun_path) - 1 : | |
| sizeof(un.sun_path)); | |
| goto err; | |
| } | |
| memset(&un, 0, sizeof(un)); | |
| un.sun_family = AF_UNIX; | |
| addrlen = sizeof(un); | |
| if (abstract) { | |
| un.sun_path[0] = '\0'; | |
| memcpy(&un.sun_path[1], saddr->path, pathlen); | |
| if (saddr_is_tight(saddr)) { | |
| addrlen = offsetof(struct sockaddr_un, sun_path) + 1 + pathlen; | |
| } | |
| } else { | |
| memcpy(un.sun_path, saddr->path, pathlen); | |
| } | |
| /* connect to peer */ | |
| do { | |
| rc = 0; | |
| if (connect(sock, (struct sockaddr *) &un, addrlen) < 0) { | |
| rc = -errno; | |
| } | |
| } while (rc == -EINTR); | |
| if (rc < 0) { | |
| error_setg_errno(errp, -rc, "Failed to connect to '%s'", | |
| saddr->path); | |
| goto err; | |
| } | |
| return sock; | |
| err: | |
| close(sock); | |
| return -1; | |
| } | |
| /* compatibility wrapper */ | |
| int unix_listen(const char *str, Error **errp) | |
| { | |
| UnixSocketAddress *saddr; | |
| int sock; | |
| saddr = g_new0(UnixSocketAddress, 1); | |
| saddr->path = g_strdup(str); | |
| sock = unix_listen_saddr(saddr, 1, errp); | |
| qapi_free_UnixSocketAddress(saddr); | |
| return sock; | |
| } | |
| int unix_connect(const char *path, Error **errp) | |
| { | |
| UnixSocketAddress *saddr; | |
| int sock; | |
| saddr = g_new0(UnixSocketAddress, 1); | |
| saddr->path = g_strdup(path); | |
| sock = unix_connect_saddr(saddr, errp); | |
| qapi_free_UnixSocketAddress(saddr); | |
| return sock; | |
| } | |
| char *socket_uri(SocketAddress *addr) | |
| { | |
| switch (addr->type) { | |
| case SOCKET_ADDRESS_TYPE_INET: | |
| return g_strdup_printf("tcp:%s:%s", | |
| addr->u.inet.host, | |
| addr->u.inet.port); | |
| case SOCKET_ADDRESS_TYPE_UNIX: | |
| return g_strdup_printf("unix:%s", | |
| addr->u.q_unix.path); | |
| case SOCKET_ADDRESS_TYPE_FD: | |
| return g_strdup_printf("fd:%s", addr->u.fd.str); | |
| case SOCKET_ADDRESS_TYPE_VSOCK: | |
| return g_strdup_printf("vsock:%s:%s", | |
| addr->u.vsock.cid, | |
| addr->u.vsock.port); | |
| default: | |
| return g_strdup("unknown address type"); | |
| } | |
| } | |
| SocketAddress *socket_parse(const char *str, Error **errp) | |
| { | |
| SocketAddress *addr; | |
| addr = g_new0(SocketAddress, 1); | |
| if (strstart(str, "unix:", NULL)) { | |
| if (str[5] == '\0') { | |
| error_setg(errp, "invalid Unix socket address"); | |
| goto fail; | |
| } else { | |
| addr->type = SOCKET_ADDRESS_TYPE_UNIX; | |
| addr->u.q_unix.path = g_strdup(str + 5); | |
| } | |
| } else if (strstart(str, "fd:", NULL)) { | |
| if (str[3] == '\0') { | |
| error_setg(errp, "invalid file descriptor address"); | |
| goto fail; | |
| } else { | |
| addr->type = SOCKET_ADDRESS_TYPE_FD; | |
| addr->u.fd.str = g_strdup(str + 3); | |
| } | |
| } else if (strstart(str, "vsock:", NULL)) { | |
| addr->type = SOCKET_ADDRESS_TYPE_VSOCK; | |
| if (vsock_parse(&addr->u.vsock, str + strlen("vsock:"), errp)) { | |
| goto fail; | |
| } | |
| } else if (strstart(str, "tcp:", NULL)) { | |
| addr->type = SOCKET_ADDRESS_TYPE_INET; | |
| if (inet_parse(&addr->u.inet, str + strlen("tcp:"), errp)) { | |
| goto fail; | |
| } | |
| } else { | |
| addr->type = SOCKET_ADDRESS_TYPE_INET; | |
| if (inet_parse(&addr->u.inet, str, errp)) { | |
| goto fail; | |
| } | |
| } | |
| return addr; | |
| fail: | |
| qapi_free_SocketAddress(addr); | |
| return NULL; | |
| } | |
| static int socket_get_fd(const char *fdstr, Error **errp) | |
| { | |
| Monitor *cur_mon = monitor_cur(); | |
| int fd; | |
| if (cur_mon) { | |
| fd = monitor_get_fd(cur_mon, fdstr, errp); | |
| if (fd < 0) { | |
| return -1; | |
| } | |
| } else { | |
| if (qemu_strtoi(fdstr, NULL, 10, &fd) < 0) { | |
| error_setg_errno(errp, errno, | |
| "Unable to parse FD number %s", | |
| fdstr); | |
| return -1; | |
| } | |
| } | |
| if (!fd_is_socket(fd)) { | |
| error_setg(errp, "File descriptor '%s' is not a socket", fdstr); | |
| close(fd); | |
| return -1; | |
| } | |
| return fd; | |
| } | |
| int socket_address_parse_named_fd(SocketAddress *addr, Error **errp) | |
| { | |
| int fd; | |
| if (addr->type != SOCKET_ADDRESS_TYPE_FD) { | |
| return 0; | |
| } | |
| fd = socket_get_fd(addr->u.fd.str, errp); | |
| if (fd < 0) { | |
| return fd; | |
| } | |
| g_free(addr->u.fd.str); | |
| addr->u.fd.str = g_strdup_printf("%d", fd); | |
| return 0; | |
| } | |
| int socket_connect(SocketAddress *addr, Error **errp) | |
| { | |
| int fd; | |
| switch (addr->type) { | |
| case SOCKET_ADDRESS_TYPE_INET: | |
| fd = inet_connect_saddr(&addr->u.inet, errp); | |
| break; | |
| case SOCKET_ADDRESS_TYPE_UNIX: | |
| fd = unix_connect_saddr(&addr->u.q_unix, errp); | |
| break; | |
| case SOCKET_ADDRESS_TYPE_FD: | |
| fd = socket_get_fd(addr->u.fd.str, errp); | |
| break; | |
| case SOCKET_ADDRESS_TYPE_VSOCK: | |
| fd = vsock_connect_saddr(&addr->u.vsock, errp); | |
| break; | |
| default: | |
| abort(); | |
| } | |
| return fd; | |
| } | |
| int socket_listen(SocketAddress *addr, int num, Error **errp) | |
| { | |
| int fd; | |
| trace_socket_listen(num); | |
| switch (addr->type) { | |
| case SOCKET_ADDRESS_TYPE_INET: | |
| fd = inet_listen_saddr(&addr->u.inet, 0, num, errp); | |
| break; | |
| case SOCKET_ADDRESS_TYPE_UNIX: | |
| fd = unix_listen_saddr(&addr->u.q_unix, num, errp); | |
| break; | |
| case SOCKET_ADDRESS_TYPE_FD: | |
| fd = socket_get_fd(addr->u.fd.str, errp); | |
| if (fd < 0) { | |
| return -1; | |
| } | |
| /* | |
| * If the socket is not yet in the listen state, then transition it to | |
| * the listen state now. | |
| * | |
| * If it's already listening then this updates the backlog value as | |
| * requested. | |
| * | |
| * If this socket cannot listen because it's already in another state | |
| * (e.g. unbound or connected) then we'll catch the error here. | |
| */ | |
| if (listen(fd, num) != 0) { | |
| error_setg_errno(errp, errno, "Failed to listen on fd socket"); | |
| close(fd); | |
| return -1; | |
| } | |
| break; | |
| case SOCKET_ADDRESS_TYPE_VSOCK: | |
| fd = vsock_listen_saddr(&addr->u.vsock, num, errp); | |
| break; | |
| default: | |
| abort(); | |
| } | |
| return fd; | |
| } | |
| void socket_listen_cleanup(int fd, Error **errp) | |
| { | |
| SocketAddress *addr; | |
| addr = socket_local_address(fd, errp); | |
| if (!addr) { | |
| return; | |
| } | |
| if (addr->type == SOCKET_ADDRESS_TYPE_UNIX | |
| && addr->u.q_unix.path) { | |
| if (unlink(addr->u.q_unix.path) < 0 && errno != ENOENT) { | |
| error_setg_errno(errp, errno, | |
| "Failed to unlink socket %s", | |
| addr->u.q_unix.path); | |
| } | |
| } | |
| qapi_free_SocketAddress(addr); | |
| } | |
| int socket_dgram(SocketAddress *remote, SocketAddress *local, Error **errp) | |
| { | |
| int fd; | |
| /* | |
| * TODO SOCKET_ADDRESS_TYPE_FD when fd is AF_INET or AF_INET6 | |
| * (although other address families can do SOCK_DGRAM, too) | |
| */ | |
| switch (remote->type) { | |
| case SOCKET_ADDRESS_TYPE_INET: | |
| fd = inet_dgram_saddr(&remote->u.inet, | |
| local ? &local->u.inet : NULL, errp); | |
| break; | |
| default: | |
| error_setg(errp, "socket type unsupported for datagram"); | |
| fd = -1; | |
| } | |
| return fd; | |
| } | |
| static SocketAddress * | |
| socket_sockaddr_to_address_inet(struct sockaddr_storage *sa, | |
| socklen_t salen, | |
| Error **errp) | |
| { | |
| char host[NI_MAXHOST]; | |
| char serv[NI_MAXSERV]; | |
| SocketAddress *addr; | |
| InetSocketAddress *inet; | |
| int ret; | |
| ret = getnameinfo((struct sockaddr *)sa, salen, | |
| host, sizeof(host), | |
| serv, sizeof(serv), | |
| NI_NUMERICHOST | NI_NUMERICSERV); | |
| if (ret != 0) { | |
| error_setg(errp, "Cannot format numeric socket address: %s", | |
| gai_strerror(ret)); | |
| return NULL; | |
| } | |
| addr = g_new0(SocketAddress, 1); | |
| addr->type = SOCKET_ADDRESS_TYPE_INET; | |
| inet = &addr->u.inet; | |
| inet->host = g_strdup(host); | |
| inet->port = g_strdup(serv); | |
| if (sa->ss_family == AF_INET) { | |
| inet->has_ipv4 = inet->ipv4 = true; | |
| } else { | |
| inet->has_ipv6 = inet->ipv6 = true; | |
| } | |
| return addr; | |
| } | |
| static SocketAddress * | |
| socket_sockaddr_to_address_unix(struct sockaddr_storage *sa, | |
| socklen_t salen, | |
| Error **errp) | |
| { | |
| SocketAddress *addr; | |
| struct sockaddr_un *su = (struct sockaddr_un *)sa; | |
| addr = g_new0(SocketAddress, 1); | |
| addr->type = SOCKET_ADDRESS_TYPE_UNIX; | |
| salen -= offsetof(struct sockaddr_un, sun_path); | |
| #ifdef CONFIG_LINUX | |
| if (salen > 0 && !su->sun_path[0]) { | |
| /* Linux abstract socket */ | |
| addr->u.q_unix.path = g_strndup(su->sun_path + 1, salen - 1); | |
| addr->u.q_unix.has_abstract = true; | |
| addr->u.q_unix.abstract = true; | |
| addr->u.q_unix.has_tight = true; | |
| addr->u.q_unix.tight = salen < sizeof(su->sun_path); | |
| return addr; | |
| } | |
| #endif | |
| addr->u.q_unix.path = g_strndup(su->sun_path, salen); | |
| return addr; | |
| } | |
| #ifdef CONFIG_AF_VSOCK | |
| static SocketAddress * | |
| socket_sockaddr_to_address_vsock(struct sockaddr_storage *sa, | |
| socklen_t salen, | |
| Error **errp) | |
| { | |
| SocketAddress *addr; | |
| VsockSocketAddress *vaddr; | |
| struct sockaddr_vm *svm = (struct sockaddr_vm *)sa; | |
| addr = g_new0(SocketAddress, 1); | |
| addr->type = SOCKET_ADDRESS_TYPE_VSOCK; | |
| vaddr = &addr->u.vsock; | |
| vaddr->cid = g_strdup_printf("%u", svm->svm_cid); | |
| vaddr->port = g_strdup_printf("%u", svm->svm_port); | |
| return addr; | |
| } | |
| #endif /* CONFIG_AF_VSOCK */ | |
| SocketAddress * | |
| socket_sockaddr_to_address(struct sockaddr_storage *sa, | |
| socklen_t salen, | |
| Error **errp) | |
| { | |
| switch (sa->ss_family) { | |
| case AF_INET: | |
| case AF_INET6: | |
| return socket_sockaddr_to_address_inet(sa, salen, errp); | |
| case AF_UNIX: | |
| return socket_sockaddr_to_address_unix(sa, salen, errp); | |
| #ifdef CONFIG_AF_VSOCK | |
| case AF_VSOCK: | |
| return socket_sockaddr_to_address_vsock(sa, salen, errp); | |
| #endif | |
| default: | |
| error_setg(errp, "socket family %d unsupported", | |
| sa->ss_family); | |
| return NULL; | |
| } | |
| return 0; | |
| } | |
| SocketAddress *socket_local_address(int fd, Error **errp) | |
| { | |
| struct sockaddr_storage ss; | |
| socklen_t sslen = sizeof(ss); | |
| if (getsockname(fd, (struct sockaddr *)&ss, &sslen) < 0) { | |
| error_setg_errno(errp, errno, "%s", | |
| "Unable to query local socket address"); | |
| return NULL; | |
| } | |
| return socket_sockaddr_to_address(&ss, sslen, errp); | |
| } | |
| SocketAddress *socket_address_flatten(SocketAddressLegacy *addr_legacy) | |
| { | |
| SocketAddress *addr; | |
| if (!addr_legacy) { | |
| return NULL; | |
| } | |
| addr = g_new(SocketAddress, 1); | |
| switch (addr_legacy->type) { | |
| case SOCKET_ADDRESS_TYPE_INET: | |
| addr->type = SOCKET_ADDRESS_TYPE_INET; | |
| QAPI_CLONE_MEMBERS(InetSocketAddress, &addr->u.inet, | |
| addr_legacy->u.inet.data); | |
| break; | |
| case SOCKET_ADDRESS_TYPE_UNIX: | |
| addr->type = SOCKET_ADDRESS_TYPE_UNIX; | |
| QAPI_CLONE_MEMBERS(UnixSocketAddress, &addr->u.q_unix, | |
| addr_legacy->u.q_unix.data); | |
| break; | |
| case SOCKET_ADDRESS_TYPE_VSOCK: | |
| addr->type = SOCKET_ADDRESS_TYPE_VSOCK; | |
| QAPI_CLONE_MEMBERS(VsockSocketAddress, &addr->u.vsock, | |
| addr_legacy->u.vsock.data); | |
| break; | |
| case SOCKET_ADDRESS_TYPE_FD: | |
| addr->type = SOCKET_ADDRESS_TYPE_FD; | |
| QAPI_CLONE_MEMBERS(FdSocketAddress, &addr->u.fd, | |
| addr_legacy->u.fd.data); | |
| break; | |
| default: | |
| abort(); | |
| } | |
| return addr; | |
| } |