X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/8be7e7e4c72c048b90e3482557954a24bba43ba7..7e480893cc3eb9f307199517f0e1d5c8e72a030f:/qemu-sockets.c diff --git a/qemu-sockets.c b/qemu-sockets.c index 2ae715db76..2b1ed2f0e9 100644 --- a/qemu-sockets.c +++ b/qemu-sockets.c @@ -11,6 +11,9 @@ * 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 #include @@ -21,6 +24,7 @@ #include "qemu_socket.h" #include "qemu-common.h" /* for qemu_isdigit */ +#include "main-loop.h" #ifndef AI_ADDRCONFIG # define AI_ADDRCONFIG 0 @@ -51,9 +55,6 @@ static QemuOptsList dummy_opts = { },{ .name = "ipv6", .type = QEMU_OPT_BOOL, - },{ - .name = "block", - .type = QEMU_OPT_BOOL, }, { /* end if list */ } }, @@ -206,90 +207,200 @@ listen: return slisten; } -int inet_connect_opts(QemuOpts *opts, Error **errp) +#ifdef _WIN32 +#define QEMU_SOCKET_RC_INPROGRESS(rc) \ + ((rc) == -EINPROGRESS || (rc) == -EWOULDBLOCK || (rc) == -WSAEALREADY) +#else +#define QEMU_SOCKET_RC_INPROGRESS(rc) \ + ((rc) == -EINPROGRESS) +#endif + +/* Struct to store connect state for non blocking connect */ +typedef struct ConnectState { + int fd; + struct addrinfo *addr_list; + struct addrinfo *current_addr; + NonBlockingConnectHandler *callback; + void *opaque; +} ConnectState; + +static int inet_connect_addr(struct addrinfo *addr, bool *in_progress, + ConnectState *connect_state); + +static void wait_for_connect(void *opaque) { - struct addrinfo ai,*res,*e; + ConnectState *s = opaque; + int val = 0, rc = 0; + socklen_t valsize = sizeof(val); + bool in_progress; + + qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL); + + do { + rc = getsockopt(s->fd, SOL_SOCKET, SO_ERROR, (void *) &val, &valsize); + } while (rc == -1 && socket_error() == EINTR); + + /* update rc to contain error */ + if (!rc && val) { + rc = -1; + } + + /* connect error */ + if (rc < 0) { + closesocket(s->fd); + s->fd = rc; + } + + /* try to connect to the next address on the list */ + while (s->current_addr->ai_next != NULL && s->fd < 0) { + s->current_addr = s->current_addr->ai_next; + s->fd = inet_connect_addr(s->current_addr, &in_progress, s); + /* connect in progress */ + if (in_progress) { + return; + } + } + + freeaddrinfo(s->addr_list); + if (s->callback) { + s->callback(s->fd, s->opaque); + } + g_free(s); +} + +static int inet_connect_addr(struct addrinfo *addr, bool *in_progress, + ConnectState *connect_state) +{ + int sock, rc; + + *in_progress = false; + + sock = qemu_socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (sock < 0) { + fprintf(stderr, "%s: socket(%s): %s\n", __func__, + inet_strfamily(addr->ai_family), strerror(errno)); + return -1; + } + qemu_setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (connect_state != NULL) { + socket_set_nonblock(sock); + } + /* connect to peer */ + do { + rc = 0; + if (connect(sock, addr->ai_addr, addr->ai_addrlen) < 0) { + rc = -socket_error(); + } + } while (rc == -EINTR); + + if (connect_state != NULL && QEMU_SOCKET_RC_INPROGRESS(rc)) { + connect_state->fd = sock; + qemu_set_fd_handler2(sock, NULL, NULL, wait_for_connect, + connect_state); + *in_progress = true; + } else if (rc < 0) { + closesocket(sock); + return -1; + } + return sock; +} + +static struct addrinfo *inet_parse_connect_opts(QemuOpts *opts, Error **errp) +{ + struct addrinfo ai, *res; + int rc; const char *addr; const char *port; - char uaddr[INET6_ADDRSTRLEN+1]; - char uport[33]; - int sock,rc; - bool block; - memset(&ai,0, sizeof(ai)); + memset(&ai, 0, sizeof(ai)); + ai.ai_flags = AI_CANONNAME | AI_ADDRCONFIG; ai.ai_family = PF_UNSPEC; ai.ai_socktype = SOCK_STREAM; addr = qemu_opt_get(opts, "host"); port = qemu_opt_get(opts, "port"); - block = qemu_opt_get_bool(opts, "block", 0); if (addr == NULL || port == NULL) { - fprintf(stderr, "inet_connect: host and/or port not specified\n"); + fprintf(stderr, + "inet_parse_connect_opts: host and/or port not specified\n"); error_set(errp, QERR_SOCKET_CREATE_FAILED); - return -1; + return NULL; } - if (qemu_opt_get_bool(opts, "ipv4", 0)) + if (qemu_opt_get_bool(opts, "ipv4", 0)) { ai.ai_family = PF_INET; - if (qemu_opt_get_bool(opts, "ipv6", 0)) + } + if (qemu_opt_get_bool(opts, "ipv6", 0)) { ai.ai_family = PF_INET6; + } /* lookup */ - if (0 != (rc = getaddrinfo(addr, port, &ai, &res))) { - fprintf(stderr,"getaddrinfo(%s,%s): %s\n", addr, port, + rc = getaddrinfo(addr, port, &ai, &res); + if (rc != 0) { + fprintf(stderr, "getaddrinfo(%s,%s): %s\n", addr, port, gai_strerror(rc)); error_set(errp, QERR_SOCKET_CREATE_FAILED); - return -1; + return NULL; + } + return res; +} + +/** + * Create a socket and connect it to an address. + * + * @opts: QEMU options, recognized parameters strings "host" and "port", + * bools "ipv4" and "ipv6". + * @errp: set on error + * @callback: callback function for non-blocking connect + * @opaque: opaque for callback function + * + * Returns: -1 on error, file descriptor on success. + * + * If @callback is non-null, the connect is non-blocking. If this + * function succeeds, callback will be called when the connection + * completes, with the file descriptor on success, or -1 on error. + */ +int inet_connect_opts(QemuOpts *opts, Error **errp, + NonBlockingConnectHandler *callback, void *opaque) +{ + struct addrinfo *res, *e; + int sock = -1; + bool in_progress; + ConnectState *connect_state = NULL; + + res = inet_parse_connect_opts(opts, errp); + if (!res) { + return -1; + } + + if (callback != NULL) { + connect_state = g_malloc0(sizeof(*connect_state)); + connect_state->addr_list = res; + connect_state->callback = callback; + connect_state->opaque = opaque; } for (e = res; e != NULL; e = e->ai_next) { - if (getnameinfo((struct sockaddr*)e->ai_addr,e->ai_addrlen, - uaddr,INET6_ADDRSTRLEN,uport,32, - NI_NUMERICHOST | NI_NUMERICSERV) != 0) { - fprintf(stderr,"%s: getnameinfo: oops\n", __FUNCTION__); - continue; - } - sock = qemu_socket(e->ai_family, e->ai_socktype, e->ai_protocol); - if (sock < 0) { - fprintf(stderr,"%s: socket(%s): %s\n", __FUNCTION__, - inet_strfamily(e->ai_family), strerror(errno)); - continue; - } - setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(void*)&on,sizeof(on)); - if (!block) { - socket_set_nonblock(sock); + if (connect_state != NULL) { + connect_state->current_addr = e; } - /* connect to peer */ - do { - rc = 0; - if (connect(sock, e->ai_addr, e->ai_addrlen) < 0) { - rc = -socket_error(); + sock = inet_connect_addr(e, &in_progress, connect_state); + if (in_progress) { + return sock; + } else if (sock >= 0) { + /* non blocking socket immediate success, call callback */ + if (callback != NULL) { + callback(sock, opaque); } - } while (rc == -EINTR); - - #ifdef _WIN32 - if (!block && (rc == -EINPROGRESS || rc == -EWOULDBLOCK - || rc == -WSAEALREADY)) { - #else - if (!block && (rc == -EINPROGRESS)) { - #endif - error_set(errp, QERR_SOCKET_CONNECT_IN_PROGRESS); - } else if (rc < 0) { - if (NULL == e->ai_next) - fprintf(stderr, "%s: connect(%s,%s,%s,%s): %s\n", __FUNCTION__, - inet_strfamily(e->ai_family), - e->ai_canonname, uaddr, uport, strerror(errno)); - closesocket(sock); - sock = -1; - continue; + break; } - freeaddrinfo(res); - return sock; } - error_set(errp, QERR_SOCKET_CONNECT_FAILED); + if (sock < 0) { + error_set(errp, QERR_SOCKET_CONNECT_FAILED); + } + g_free(connect_state); freeaddrinfo(res); - return -1; + return sock; } int inet_dgram_opts(QemuOpts *opts) @@ -345,7 +456,7 @@ int inet_dgram_opts(QemuOpts *opts) if (0 != (rc = getaddrinfo(addr, port, &ai, &local))) { fprintf(stderr,"getaddrinfo(%s,%s): %s\n", addr, port, gai_strerror(rc)); - return -1; + goto err; } /* create socket */ @@ -485,17 +596,54 @@ int inet_listen(const char *str, char *ostr, int olen, return sock; } -int inet_connect(const char *str, bool block, Error **errp) +/** + * Create a blocking socket and connect it to an address. + * + * @str: address string + * @errp: set in case of an error + * + * Returns -1 in case of error, file descriptor on success + **/ +int inet_connect(const char *str, Error **errp) { QemuOpts *opts; int sock = -1; opts = qemu_opts_create(&dummy_opts, NULL, 0, NULL); if (inet_parse(opts, str) == 0) { - if (block) { - qemu_opt_set(opts, "block", "on"); - } - sock = inet_connect_opts(opts, errp); + sock = inet_connect_opts(opts, errp, NULL, NULL); + } else { + error_set(errp, QERR_SOCKET_CREATE_FAILED); + } + qemu_opts_del(opts); + return sock; +} + +/** + * Create a non-blocking socket and connect it to an address. + * Calls the callback function with fd in case of success or -1 in case of + * error. + * + * @str: address string + * @callback: callback function that is called when connect completes, + * cannot be NULL. + * @opaque: opaque for callback function + * @errp: set in case of an error + * + * Returns: -1 on immediate error, file descriptor on success. + **/ +int inet_nonblocking_connect(const char *str, + NonBlockingConnectHandler *callback, + void *opaque, Error **errp) +{ + QemuOpts *opts; + int sock = -1; + + g_assert(callback != NULL); + + opts = qemu_opts_create(&dummy_opts, NULL, 0, NULL); + if (inet_parse(opts, str) == 0) { + sock = inet_connect_opts(opts, errp, callback, opaque); } else { error_set(errp, QERR_SOCKET_CREATE_FAILED); }