/*** This file is part of PulseAudio. Copyright 2004-2006 Lennart Poettering Copyright 2006-2007 Pierre Ossman 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. ***/ #ifdef HAVE_CONFIG_H #include #endif /* #undef HAVE_LIBASYNCNS */ #include #include #include #include #include #ifdef HAVE_SYS_UN_H #include #endif #ifdef HAVE_NETINET_IN_H #include #endif #ifdef HAVE_NETDB_H #include #endif #ifdef HAVE_LIBASYNCNS #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; pa_bool_t 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, pa_bool_t 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, pa_bool_t use_rtclock, const char*name, uint16_t default_port) { pa_socket_client *c = NULL; pa_parsed_address a; pa_assert(m); pa_assert(name); if (pa_parse_address(name, &a) < 0) return NULL; 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(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. */ pa_bool_t pa_socket_client_is_local(pa_socket_client *c) { pa_assert(c); pa_assert(PA_REFCNT_VALUE(c) >= 1); return c->local; }