summaryrefslogtreecommitdiffstats
path: root/src/pulsecore/socket-client.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pulsecore/socket-client.c')
-rw-r--r--src/pulsecore/socket-client.c563
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;
+}