Files
libstrophe-gh/src/sock.c
Stu Tomlinson 06afe3d1c2 Add API to register callback to set socket options
The registered callback function will be called before connect() is
called on the socket, allowing tweaking of much more than just keepalive
settings.

This deprecates xmpp_conn_set_keepalive().

Includes helper/example callback function to set default keepalive
parameters that some library users may find useful.
2022-03-17 22:59:07 +00:00

303 lines
6.9 KiB
C

/* 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 and 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 <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"
void sock_initialize(void)
{
#ifdef _WIN32
WSADATA wsad;
WSAStartup(0x0101, &wsad);
#endif
}
void sock_shutdown(void)
{
#ifdef _WIN32
WSACleanup();
#endif
}
int sock_error(void)
{
#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
}
sock_t sock_connect(xmpp_conn_t *conn, const char *host, unsigned short port)
{
sock_t sock;
char service[6];
struct addrinfo *res, *ainfo, hints;
int err;
strophe_snprintf(service, 6, "%u", 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;
err = getaddrinfo(host, service, &hints, &res);
if (err != 0)
return -1;
for (ainfo = res; ainfo != NULL; ainfo = ainfo->ai_next) {
sock = socket(ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol);
if (sock < 0)
continue;
if (conn->sockopt_cb != NULL)
err = (conn->sockopt_cb)(conn, &sock);
if (err != 0) {
sock_close(sock);
continue;
}
err = sock_set_nonblocking(sock);
if (err == 0) {
err = connect(sock, ainfo->ai_addr, ainfo->ai_addrlen);
if (err == 0 || _in_progress(sock_error()))
break;
}
sock_close(sock);
}
freeaddrinfo(res);
sock = ainfo == NULL ? -1 : sock;
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(sock_t sock, void *buff, size_t len)
{
return recv(sock, buff, len, 0);
}
int sock_write(sock_t sock, const void *buff, size_t len)
{
return send(sock, buff, len, 0);
}
int sock_is_recoverable(int error)
{
#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() != WSAENOTCONN)
return sock_error();
#else
if (sock_error() != ENOTCONN)
return sock_error();
#endif
/* load the correct error into errno through error slippage */
recv(sock, &temp, 1, 0);
return sock_error();
}