/* 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 #include #include #include #ifdef _WIN32 #include #include #include #include /* tcp_keepalive */ #else #include #include #include #include #include #include #include #include #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, ""); } 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); }