`rc` must be reset in order to make the `do-while` loop work in case
there's no `sockopt_cb` set.
Fixup of f3460460e9
Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
446 lines
11 KiB
C
446 lines
11 KiB
C
/* SPDX-License-Identifier: MIT OR GPL-3.0-only */
|
|
/* sock.c
|
|
** strophe XMPP client library -- socket abstraction implementation
|
|
**
|
|
** Copyright (C) 2005-2009 Collecta, Inc.
|
|
**
|
|
** This software is provided AS-IS with no warranty, either express
|
|
** or implied.
|
|
**
|
|
** This program is dual licensed under the MIT or GPLv3 licenses.
|
|
*/
|
|
|
|
/** @file
|
|
* Socket abstraction.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#include <iphlpapi.h>
|
|
#include <mstcpip.h> /* tcp_keepalive */
|
|
#else
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <netdb.h>
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#include "common.h"
|
|
#include "resolver.h"
|
|
|
|
const struct conn_interface sock_intf = {
|
|
sock_read,
|
|
sock_write,
|
|
/* no flush */
|
|
conn_int_nop,
|
|
/* no pending */
|
|
conn_int_nop,
|
|
sock_error,
|
|
sock_is_recoverable,
|
|
NULL,
|
|
};
|
|
|
|
struct _xmpp_sock_t {
|
|
xmpp_ctx_t *ctx;
|
|
xmpp_conn_t *conn;
|
|
struct addrinfo *ainfo_list;
|
|
struct addrinfo *ainfo_cur;
|
|
resolver_srv_rr_t *srv_rr_list;
|
|
resolver_srv_rr_t *srv_rr_cur;
|
|
const char *host;
|
|
unsigned short port;
|
|
};
|
|
|
|
void sock_initialize(void)
|
|
{
|
|
#ifdef _WIN32
|
|
WSADATA wsad;
|
|
WSAStartup(0x0101, &wsad);
|
|
#endif
|
|
}
|
|
|
|
void sock_shutdown(void)
|
|
{
|
|
#ifdef _WIN32
|
|
WSACleanup();
|
|
#endif
|
|
}
|
|
|
|
int sock_error(struct conn_interface *intf)
|
|
{
|
|
UNUSED(intf);
|
|
#ifdef _WIN32
|
|
return WSAGetLastError();
|
|
#else
|
|
return errno;
|
|
#endif
|
|
}
|
|
|
|
static int _in_progress(int error)
|
|
{
|
|
#ifdef _WIN32
|
|
return (error == WSAEWOULDBLOCK || error == WSAEINPROGRESS);
|
|
#else
|
|
return (error == EINPROGRESS);
|
|
#endif
|
|
}
|
|
|
|
static void sock_getaddrinfo(xmpp_sock_t *xsock)
|
|
{
|
|
char service[6];
|
|
struct addrinfo hints;
|
|
int rc;
|
|
|
|
if (xsock->ainfo_list) {
|
|
freeaddrinfo(xsock->ainfo_list);
|
|
xsock->ainfo_list = NULL;
|
|
}
|
|
|
|
if (xsock->srv_rr_cur) {
|
|
/* Cache host and port for debug logs. */
|
|
xsock->host = xsock->srv_rr_cur->target;
|
|
xsock->port = xsock->srv_rr_cur->port;
|
|
|
|
strophe_snprintf(service, 6, "%u", xsock->srv_rr_cur->port);
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
hints.ai_family = AF_UNSPEC;
|
|
#ifdef AI_ADDRCONFIG
|
|
hints.ai_flags = AI_ADDRCONFIG;
|
|
#endif /* AI_ADDRCONFIG */
|
|
hints.ai_protocol = IPPROTO_TCP;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
rc = getaddrinfo(xsock->srv_rr_cur->target, service, &hints,
|
|
&xsock->ainfo_list);
|
|
if (rc != 0) {
|
|
strophe_debug(xsock->ctx, "sock", "getaddrinfo() failed with %d",
|
|
rc);
|
|
xsock->ainfo_list = NULL;
|
|
}
|
|
}
|
|
|
|
xsock->ainfo_cur = xsock->ainfo_list;
|
|
}
|
|
|
|
xmpp_sock_t *sock_new(xmpp_conn_t *conn,
|
|
const char *domain,
|
|
const char *host,
|
|
unsigned short port)
|
|
{
|
|
xmpp_ctx_t *ctx = conn->ctx;
|
|
xmpp_sock_t *xsock;
|
|
int found = XMPP_DOMAIN_NOT_FOUND;
|
|
|
|
xsock = strophe_alloc(ctx, sizeof(*xsock));
|
|
if (!xsock) {
|
|
return NULL;
|
|
}
|
|
|
|
xsock->ctx = ctx;
|
|
xsock->conn = conn;
|
|
xsock->host = NULL;
|
|
xsock->port = 0;
|
|
|
|
if (!host) {
|
|
found = resolver_srv_lookup(ctx, "xmpp-client", "tcp", domain,
|
|
&xsock->srv_rr_list);
|
|
if (XMPP_DOMAIN_NOT_FOUND == found)
|
|
strophe_debug(ctx, "sock",
|
|
"SRV lookup failed, connecting via domain.");
|
|
}
|
|
if (XMPP_DOMAIN_NOT_FOUND == found) {
|
|
/* Resolution failed or the host is provided explicitly. */
|
|
xsock->srv_rr_list =
|
|
resolver_srv_rr_new(ctx, host ? host : domain, port, 0, 0);
|
|
}
|
|
xsock->srv_rr_cur = xsock->srv_rr_list;
|
|
|
|
xsock->ainfo_list = NULL;
|
|
sock_getaddrinfo(xsock);
|
|
if (xsock->srv_rr_cur)
|
|
xsock->srv_rr_cur = xsock->srv_rr_cur->next;
|
|
|
|
return xsock;
|
|
}
|
|
|
|
void sock_free(xmpp_sock_t *xsock)
|
|
{
|
|
if (!xsock)
|
|
return;
|
|
|
|
if (xsock->ainfo_list)
|
|
freeaddrinfo(xsock->ainfo_list);
|
|
if (xsock->srv_rr_list)
|
|
resolver_srv_free(xsock->ctx, xsock->srv_rr_list);
|
|
strophe_free(xsock->ctx, xsock);
|
|
}
|
|
|
|
static const char *_sockaddr2str(struct sockaddr *sa, char *buf, size_t buflen)
|
|
{
|
|
buf[0] = '\0';
|
|
|
|
switch (sa->sa_family) {
|
|
case AF_INET:
|
|
inet_ntop(AF_INET, &((struct sockaddr_in *)sa)->sin_addr, buf, buflen);
|
|
break;
|
|
case AF_INET6:
|
|
inet_ntop(AF_INET6, &((struct sockaddr_in6 *)sa)->sin6_addr, buf,
|
|
buflen);
|
|
break;
|
|
default:
|
|
strophe_snprintf(buf, buflen, "<Unknown>");
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
sock_t sock_connect(xmpp_sock_t *xsock)
|
|
{
|
|
struct addrinfo *ainfo;
|
|
sock_t sock;
|
|
int rc;
|
|
char buf[64];
|
|
|
|
do {
|
|
if (!xsock->ainfo_cur) {
|
|
sock_getaddrinfo(xsock);
|
|
if (xsock->srv_rr_cur)
|
|
xsock->srv_rr_cur = xsock->srv_rr_cur->next;
|
|
}
|
|
if (!xsock->ainfo_cur) {
|
|
/* We tried all available addresses. */
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
ainfo = xsock->ainfo_cur;
|
|
strophe_debug(xsock->ctx, "sock", "Connecting to %s:%u via %s",
|
|
xsock->host, xsock->port,
|
|
_sockaddr2str(ainfo->ai_addr, buf, sizeof(buf)));
|
|
|
|
sock = socket(ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol);
|
|
if (sock != INVALID_SOCKET) {
|
|
rc = 0;
|
|
if (xsock->conn->sockopt_cb) {
|
|
/* Don't allow user to overwrite sockfd value. */
|
|
sock_t sock_copy = sock;
|
|
rc = xsock->conn->sockopt_cb(xsock->conn, &sock_copy);
|
|
if (rc != 0) {
|
|
strophe_debug(xsock->ctx, "sock",
|
|
"User's setsockopt callback"
|
|
"failed with %d (errno=%d)",
|
|
rc, errno);
|
|
}
|
|
}
|
|
if (rc == 0)
|
|
rc = sock_set_nonblocking(sock);
|
|
if (rc == 0)
|
|
rc = connect(sock, ainfo->ai_addr, ainfo->ai_addrlen);
|
|
/* Assume only connect() can cause "in progress" error. */
|
|
if (rc != 0 && !_in_progress(sock_error(NULL))) {
|
|
sock_close(sock);
|
|
sock = INVALID_SOCKET;
|
|
}
|
|
}
|
|
strophe_debug(xsock->ctx, "sock", "sock_connect() result %d", sock);
|
|
|
|
xsock->ainfo_cur = xsock->ainfo_cur->ai_next;
|
|
} while (sock == INVALID_SOCKET);
|
|
|
|
return sock;
|
|
}
|
|
|
|
int sock_set_keepalive(sock_t sock,
|
|
int timeout,
|
|
int interval,
|
|
int count,
|
|
unsigned int user_timeout)
|
|
{
|
|
int ret;
|
|
int optval = (timeout && interval) ? 1 : 0;
|
|
|
|
UNUSED(count);
|
|
UNUSED(user_timeout);
|
|
|
|
#ifdef _WIN32
|
|
struct tcp_keepalive ka;
|
|
DWORD dw = 0;
|
|
|
|
ka.onoff = optval;
|
|
ka.keepalivetime = timeout * 1000;
|
|
ka.keepaliveinterval = interval * 1000;
|
|
ret = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &ka, sizeof(ka), NULL, 0, &dw,
|
|
NULL, NULL);
|
|
#else
|
|
ret = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (optval) {
|
|
#ifdef TCP_KEEPIDLE
|
|
ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &timeout,
|
|
sizeof(timeout));
|
|
#elif defined(TCP_KEEPALIVE)
|
|
/* QNX receives `struct timeval' as argument, but it seems OSX does int
|
|
*/
|
|
ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, &timeout,
|
|
sizeof(timeout));
|
|
#endif /* TCP_KEEPIDLE */
|
|
if (ret < 0)
|
|
return ret;
|
|
#ifdef TCP_KEEPINTVL
|
|
ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &interval,
|
|
sizeof(interval));
|
|
if (ret < 0)
|
|
return ret;
|
|
#endif /* TCP_KEEPINTVL */
|
|
}
|
|
|
|
if (count) {
|
|
#ifdef TCP_KEEPCNT
|
|
ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count));
|
|
if (ret < 0)
|
|
return ret;
|
|
#endif /* TCP_KEEPCNT */
|
|
}
|
|
|
|
if (user_timeout) {
|
|
#ifdef TCP_USER_TIMEOUT
|
|
ret = setsockopt(sock, IPPROTO_TCP, TCP_USER_TIMEOUT, &user_timeout,
|
|
sizeof(user_timeout));
|
|
if (ret < 0)
|
|
return ret;
|
|
#elif defined(TCP_RXT_CONNDROPTIME)
|
|
int rxt = user_timeout / 1000;
|
|
ret = setsockopt(sock, IPPROTO_TCP, TCP_RXT_CONNDROPTIME, &rxt,
|
|
sizeof(rxt));
|
|
if (ret < 0)
|
|
return ret;
|
|
#endif /* TCP_USER_TIMEOUT */
|
|
}
|
|
|
|
#endif /* _WIN32 */
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** Example sockopt callback function
|
|
* An example function that can be used to set reasonable default keepalive
|
|
* options on sockets when registered for a connection with
|
|
* xmpp_conn_set_sockopt_callback()
|
|
*
|
|
* @param conn a Strophe connection object
|
|
* @param socket pointer to a socket descriptor
|
|
*
|
|
* @see xmpp_sockopt_callback for details on the `socket` parameter
|
|
* @ingroup Connections
|
|
*/
|
|
int xmpp_sockopt_cb_keepalive(xmpp_conn_t *conn, void *socket)
|
|
{
|
|
sock_t sock = *((sock_t *)socket);
|
|
|
|
return sock_set_keepalive(
|
|
sock, conn->ka_timeout, conn->ka_interval, conn->ka_count,
|
|
conn->ka_count
|
|
? (conn->ka_timeout + conn->ka_interval * conn->ka_count) * 1000
|
|
: 0);
|
|
}
|
|
|
|
int sock_close(sock_t sock)
|
|
{
|
|
#ifdef _WIN32
|
|
return closesocket(sock);
|
|
#else
|
|
return close(sock);
|
|
#endif
|
|
}
|
|
|
|
static int _sock_set_blocking_mode(sock_t sock, int blocking)
|
|
{
|
|
#ifdef _WIN32
|
|
u_long nonblock = blocking ? 0 : 1;
|
|
return ioctlsocket(sock, FIONBIO, &nonblock);
|
|
#else
|
|
int rc;
|
|
|
|
rc = fcntl(sock, F_GETFL, NULL);
|
|
if (rc >= 0) {
|
|
rc = blocking ? rc & (~O_NONBLOCK) : rc | O_NONBLOCK;
|
|
rc = fcntl(sock, F_SETFL, rc);
|
|
}
|
|
return rc;
|
|
#endif
|
|
}
|
|
|
|
int sock_set_blocking(sock_t sock)
|
|
{
|
|
return _sock_set_blocking_mode(sock, 1);
|
|
}
|
|
|
|
int sock_set_nonblocking(sock_t sock)
|
|
{
|
|
return _sock_set_blocking_mode(sock, 0);
|
|
}
|
|
|
|
int sock_read(struct conn_interface *intf, void *buff, size_t len)
|
|
{
|
|
return recv(intf->conn->sock, buff, len, 0);
|
|
}
|
|
|
|
int sock_write(struct conn_interface *intf, const void *buff, size_t len)
|
|
{
|
|
return send(intf->conn->sock, buff, len, 0);
|
|
}
|
|
|
|
int sock_is_recoverable(struct conn_interface *intf, int error)
|
|
{
|
|
UNUSED(intf);
|
|
#ifdef _WIN32
|
|
return (error == WSAEINTR || error == WSAEWOULDBLOCK ||
|
|
error == WSAEINPROGRESS);
|
|
#else
|
|
return (error == EAGAIN || error == EINTR);
|
|
#endif
|
|
}
|
|
|
|
int sock_connect_error(sock_t sock)
|
|
{
|
|
struct sockaddr_storage ss;
|
|
struct sockaddr *sa = (struct sockaddr *)&ss;
|
|
socklen_t len;
|
|
char temp;
|
|
|
|
memset(&ss, 0, sizeof(ss));
|
|
len = sizeof(ss);
|
|
sa->sa_family = AF_UNSPEC;
|
|
|
|
/* we don't actually care about the peer name, we're just checking if
|
|
* we're connected or not */
|
|
if (getpeername(sock, sa, &len) == 0) {
|
|
return 0;
|
|
}
|
|
|
|
/* it's possible that the error wasn't ENOTCONN, so if it wasn't,
|
|
* return that */
|
|
#ifdef _WIN32
|
|
if (sock_error(NULL) != WSAENOTCONN)
|
|
return sock_error(NULL);
|
|
#else
|
|
if (sock_error(NULL) != ENOTCONN)
|
|
return sock_error(NULL);
|
|
#endif
|
|
|
|
/* load the correct error into errno through error slippage */
|
|
recv(sock, &temp, 1, 0);
|
|
|
|
return sock_error(NULL);
|
|
}
|