diff options
Diffstat (limited to 'src/pulsecore/socket-client.c')
-rw-r--r-- | src/pulsecore/socket-client.c | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/src/pulsecore/socket-client.c b/src/pulsecore/socket-client.c new file mode 100644 index 0000000..c87406d --- /dev/null +++ b/src/pulsecore/socket-client.c @@ -0,0 +1,563 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio 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. + + PulseAudio 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 PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/* #undef HAVE_LIBASYNCNS */ + +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +#ifdef HAVE_LIBASYNCNS +#include <asyncns.h> +#endif + +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/socket.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-rtclock.h> +#include <pulsecore/core-util.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/log.h> +#include <pulsecore/parseaddr.h> +#include <pulsecore/macro.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/arpa-inet.h> + +#include "socket-client.h" + +#define CONNECT_TIMEOUT 5 + +struct pa_socket_client { + PA_REFCNT_DECLARE; + int fd; + + pa_mainloop_api *mainloop; + pa_io_event *io_event; + pa_time_event *timeout_event; + pa_defer_event *defer_event; + + pa_socket_client_cb_t callback; + void *userdata; + + bool local; + +#ifdef HAVE_LIBASYNCNS + asyncns_t *asyncns; + asyncns_query_t * asyncns_query; + pa_io_event *asyncns_io_event; +#endif +}; + +static pa_socket_client* socket_client_new(pa_mainloop_api *m) { + pa_socket_client *c; + pa_assert(m); + + c = pa_xnew0(pa_socket_client, 1); + PA_REFCNT_INIT(c); + c->mainloop = m; + c->fd = -1; + + return c; +} + +static void free_events(pa_socket_client *c) { + pa_assert(c); + + if (c->io_event) { + c->mainloop->io_free(c->io_event); + c->io_event = NULL; + } + + if (c->timeout_event) { + c->mainloop->time_free(c->timeout_event); + c->timeout_event = NULL; + } + + if (c->defer_event) { + c->mainloop->defer_free(c->defer_event); + c->defer_event = NULL; + } +} + +static void do_call(pa_socket_client *c) { + pa_iochannel *io = NULL; + int error; + socklen_t lerror; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->callback); + + pa_socket_client_ref(c); + + if (c->fd < 0) + goto finish; + + lerror = sizeof(error); + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void*)&error, &lerror) < 0) { + pa_log("getsockopt(): %s", pa_cstrerror(errno)); + goto finish; + } + + if (lerror != sizeof(error)) { + pa_log("getsockopt() returned invalid size."); + goto finish; + } + + if (error != 0) { + pa_log_debug("connect(): %s", pa_cstrerror(error)); + errno = error; + goto finish; + } + + io = pa_iochannel_new(c->mainloop, c->fd, c->fd); + +finish: + if (!io && c->fd >= 0) + pa_close(c->fd); + c->fd = -1; + + free_events(c); + + c->callback(c, io, c->userdata); + + pa_socket_client_unref(c); +} + +static void connect_defer_cb(pa_mainloop_api *m, pa_defer_event *e, void *userdata) { + pa_socket_client *c = userdata; + + pa_assert(m); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->defer_event == e); + + do_call(c); +} + +static void connect_io_cb(pa_mainloop_api*m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { + pa_socket_client *c = userdata; + + pa_assert(m); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->io_event == e); + pa_assert(fd >= 0); + + do_call(c); +} + +static int do_connect(pa_socket_client *c, const struct sockaddr *sa, socklen_t len) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(sa); + pa_assert(len > 0); + + pa_make_fd_nonblock(c->fd); + + if (connect(c->fd, sa, len) < 0) { +#ifdef OS_IS_WIN32 + if (WSAGetLastError() != EWOULDBLOCK) { + pa_log_debug("connect(): %d", WSAGetLastError()); +#else + if (errno != EINPROGRESS) { + pa_log_debug("connect(): %s (%d)", pa_cstrerror(errno), errno); +#endif + return -1; + } + + c->io_event = c->mainloop->io_new(c->mainloop, c->fd, PA_IO_EVENT_OUTPUT, connect_io_cb, c); + } else + c->defer_event = c->mainloop->defer_new(c->mainloop, connect_defer_cb, c); + + return 0; +} + +pa_socket_client* pa_socket_client_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port) { + struct sockaddr_in sa; + + pa_assert(m); + pa_assert(port > 0); + + pa_zero(sa); + sa.sin_family = AF_INET; + sa.sin_port = htons(port); + sa.sin_addr.s_addr = htonl(address); + + return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa)); +} + +pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename) { +#ifdef HAVE_SYS_UN_H + struct sockaddr_un sa; + + pa_assert(m); + pa_assert(filename); + + pa_zero(sa); + sa.sun_family = AF_UNIX; + pa_strlcpy(sa.sun_path, filename, sizeof(sa.sun_path)); + + return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa)); +#else /* HAVE_SYS_UN_H */ + + return NULL; +#endif /* HAVE_SYS_UN_H */ +} + +static int sockaddr_prepare(pa_socket_client *c, const struct sockaddr *sa, size_t salen) { + pa_assert(c); + pa_assert(sa); + pa_assert(salen); + + c->local = pa_socket_address_is_local(sa); + + if ((c->fd = pa_socket_cloexec(sa->sa_family, SOCK_STREAM, 0)) < 0) { + pa_log("socket(): %s", pa_cstrerror(errno)); + return -1; + } + +#ifdef HAVE_IPV6 + if (sa->sa_family == AF_INET || sa->sa_family == AF_INET6) +#else + if (sa->sa_family == AF_INET) +#endif + pa_make_tcp_socket_low_delay(c->fd); + else + pa_make_socket_low_delay(c->fd); + + if (do_connect(c, sa, (socklen_t) salen) < 0) + return -1; + + return 0; +} + +pa_socket_client* pa_socket_client_new_sockaddr(pa_mainloop_api *m, const struct sockaddr *sa, size_t salen) { + pa_socket_client *c; + + pa_assert(m); + pa_assert(sa); + pa_assert(salen > 0); + + c = socket_client_new(m); + + if (sockaddr_prepare(c, sa, salen) < 0) + goto fail; + + return c; + +fail: + pa_socket_client_unref(c); + return NULL; +} + +static void socket_client_free(pa_socket_client *c) { + pa_assert(c); + pa_assert(c->mainloop); + + free_events(c); + + if (c->fd >= 0) + pa_close(c->fd); + +#ifdef HAVE_LIBASYNCNS + if (c->asyncns_query) + asyncns_cancel(c->asyncns, c->asyncns_query); + if (c->asyncns) + asyncns_free(c->asyncns); + if (c->asyncns_io_event) + c->mainloop->io_free(c->asyncns_io_event); +#endif + + pa_xfree(c); +} + +void pa_socket_client_unref(pa_socket_client *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (PA_REFCNT_DEC(c) <= 0) + socket_client_free(c); +} + +pa_socket_client* pa_socket_client_ref(pa_socket_client *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_REFCNT_INC(c); + return c; +} + +void pa_socket_client_set_callback(pa_socket_client *c, pa_socket_client_cb_t on_connection, void *userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + c->callback = on_connection; + c->userdata = userdata; +} + +pa_socket_client* pa_socket_client_new_ipv6(pa_mainloop_api *m, uint8_t address[16], uint16_t port) { +#ifdef HAVE_IPV6 + struct sockaddr_in6 sa; + + pa_assert(m); + pa_assert(address); + pa_assert(port > 0); + + pa_zero(sa); + sa.sin6_family = AF_INET6; + sa.sin6_port = htons(port); + memcpy(&sa.sin6_addr, address, sizeof(sa.sin6_addr)); + + return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa)); + +#else + return NULL; +#endif +} + +#ifdef HAVE_LIBASYNCNS + +static void asyncns_cb(pa_mainloop_api*m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { + pa_socket_client *c = userdata; + struct addrinfo *res = NULL; + int ret; + + pa_assert(m); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->asyncns_io_event == e); + pa_assert(fd >= 0); + + if (asyncns_wait(c->asyncns, 0) < 0) + goto fail; + + if (!asyncns_isdone(c->asyncns, c->asyncns_query)) + return; + + ret = asyncns_getaddrinfo_done(c->asyncns, c->asyncns_query, &res); + c->asyncns_query = NULL; + + if (ret != 0 || !res) + goto fail; + + if (res->ai_addr) + if (sockaddr_prepare(c, res->ai_addr, res->ai_addrlen) < 0) + goto fail; + + asyncns_freeaddrinfo(res); + + m->io_free(c->asyncns_io_event); + c->asyncns_io_event = NULL; + return; + +fail: + m->io_free(c->asyncns_io_event); + c->asyncns_io_event = NULL; + + errno = EHOSTUNREACH; + do_call(c); + return; + +} + +#endif + +static void timeout_cb(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) { + pa_socket_client *c = userdata; + + pa_assert(m); + pa_assert(e); + pa_assert(c); + + if (c->fd >= 0) { + pa_close(c->fd); + c->fd = -1; + } + + errno = ETIMEDOUT; + do_call(c); +} + +static void start_timeout(pa_socket_client *c, bool use_rtclock) { + struct timeval tv; + + pa_assert(c); + pa_assert(!c->timeout_event); + + c->timeout_event = c->mainloop->time_new(c->mainloop, pa_timeval_rtstore(&tv, pa_rtclock_now() + CONNECT_TIMEOUT * PA_USEC_PER_SEC, use_rtclock), timeout_cb, c); +} + +pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, bool use_rtclock, const char*name, uint16_t default_port) { + pa_socket_client *c = NULL; + pa_parsed_address a; + char *name_buf; + + pa_assert(m); + pa_assert(name); + + a.path_or_host = NULL; + + if (pa_is_ip6_address(name)) { + size_t len = strlen(name); + name_buf = pa_xmalloc(len + 3); + memcpy(name_buf + 1, name, len); + name_buf[0] = '['; + name_buf[len + 1] = ']'; + name_buf[len + 2] = '\0'; + } else { + name_buf = pa_xstrdup(name); + } + + if (pa_parse_address(name_buf, &a) < 0) { + pa_log_warn("parsing address failed: %s", name_buf); + goto finish; + } + + if (!a.port) + a.port = default_port; + + switch (a.type) { + case PA_PARSED_ADDRESS_UNIX: + if ((c = pa_socket_client_new_unix(m, a.path_or_host))) + start_timeout(c, use_rtclock); + break; + + case PA_PARSED_ADDRESS_TCP4: /* Fallthrough */ + case PA_PARSED_ADDRESS_TCP6: /* Fallthrough */ + case PA_PARSED_ADDRESS_TCP_AUTO: { + struct addrinfo hints; + char port[12]; + + pa_snprintf(port, sizeof(port), "%u", (unsigned) a.port); + + pa_zero(hints); + if (a.type == PA_PARSED_ADDRESS_TCP4) + hints.ai_family = PF_INET; +#ifdef HAVE_IPV6 + else if (a.type == PA_PARSED_ADDRESS_TCP6) + hints.ai_family = PF_INET6; +#endif + else + hints.ai_family = PF_UNSPEC; + + hints.ai_socktype = SOCK_STREAM; + +#if defined(HAVE_LIBASYNCNS) + { + asyncns_t *asyncns; + + if (!(asyncns = asyncns_new(1))) + goto finish; + + c = socket_client_new(m); + c->asyncns = asyncns; + c->asyncns_io_event = m->io_new(m, asyncns_fd(c->asyncns), PA_IO_EVENT_INPUT, asyncns_cb, c); + pa_assert_se(c->asyncns_query = asyncns_getaddrinfo(c->asyncns, a.path_or_host, port, &hints)); + start_timeout(c, use_rtclock); + } +#elif defined(HAVE_GETADDRINFO) + { + int ret; + struct addrinfo *res = NULL; + + ret = getaddrinfo(a.path_or_host, port, &hints, &res); + + if (ret < 0 || !res) + goto finish; + + if (res->ai_addr) { + if ((c = pa_socket_client_new_sockaddr(m, res->ai_addr, res->ai_addrlen))) + start_timeout(c, use_rtclock); + } + + freeaddrinfo(res); + } +#else + { + struct hostent *host = NULL; + struct sockaddr_in s; + +#ifdef HAVE_IPV6 + /* FIXME: PF_INET6 support */ + if (hints.ai_family == PF_INET6) { + pa_log_error("IPv6 is not supported on Windows"); + goto finish; + } +#endif + + host = gethostbyname(a.path_or_host); + if (!host) { + unsigned int addr = inet_addr(a.path_or_host); + if (addr != INADDR_NONE) + host = gethostbyaddr((char*)&addr, 4, AF_INET); + } + + if (!host) + goto finish; + + pa_zero(s); + s.sin_family = AF_INET; + memcpy(&s.sin_addr, host->h_addr, sizeof(struct in_addr)); + s.sin_port = htons(a.port); + + if ((c = pa_socket_client_new_sockaddr(m, (struct sockaddr*)&s, sizeof(s)))) + start_timeout(c, use_rtclock); + } +#endif /* HAVE_LIBASYNCNS */ + } + } + +finish: + pa_xfree(name_buf); + pa_xfree(a.path_or_host); + return c; + +} + +/* Return non-zero when the target sockaddr is considered + local. "local" means UNIX socket or TCP socket on localhost. Other + local IP addresses are not considered local. */ +bool pa_socket_client_is_local(pa_socket_client *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + return c->local; +} |