Line data Source code
1 : /* SPDX-License-Identifier: MIT OR GPL-3.0-only */
2 : /* sock.c
3 : ** strophe XMPP client library -- socket abstraction implementation
4 : **
5 : ** Copyright (C) 2005-2009 Collecta, Inc.
6 : **
7 : ** This software is provided AS-IS with no warranty, either express
8 : ** or implied.
9 : **
10 : ** This program is dual licensed under the MIT or GPLv3 licenses.
11 : */
12 :
13 : /** @file
14 : * Socket abstraction.
15 : */
16 :
17 : #include <stdio.h>
18 : #include <stdlib.h>
19 : #include <string.h>
20 : #include <sys/types.h>
21 :
22 : #ifdef _WIN32
23 : #include <winsock2.h>
24 : #include <ws2tcpip.h>
25 : #include <iphlpapi.h>
26 : #include <mstcpip.h> /* tcp_keepalive */
27 : #else
28 : #include <arpa/inet.h>
29 : #include <errno.h>
30 : #include <unistd.h>
31 : #include <sys/socket.h>
32 : #include <netinet/in.h>
33 : #include <netinet/tcp.h>
34 : #include <netdb.h>
35 : #include <fcntl.h>
36 : #endif
37 :
38 : #include "common.h"
39 : #include "resolver.h"
40 :
41 : const struct conn_interface sock_intf = {
42 : sock_read,
43 : sock_write,
44 : /* no flush */
45 : conn_int_nop,
46 : /* no pending */
47 : conn_int_nop,
48 : sock_error,
49 : sock_is_recoverable,
50 : NULL,
51 : };
52 :
53 : struct _xmpp_sock_t {
54 : xmpp_ctx_t *ctx;
55 : xmpp_conn_t *conn;
56 : struct addrinfo *ainfo_list;
57 : struct addrinfo *ainfo_cur;
58 : resolver_srv_rr_t *srv_rr_list;
59 : resolver_srv_rr_t *srv_rr_cur;
60 : const char *host;
61 : unsigned short port;
62 : };
63 :
64 2 : void sock_initialize(void)
65 : {
66 : #ifdef _WIN32
67 : WSADATA wsad;
68 : WSAStartup(0x0101, &wsad);
69 : #endif
70 2 : }
71 :
72 2 : void sock_shutdown(void)
73 : {
74 : #ifdef _WIN32
75 : WSACleanup();
76 : #endif
77 2 : }
78 :
79 0 : int sock_error(struct conn_interface *intf)
80 : {
81 0 : UNUSED(intf);
82 : #ifdef _WIN32
83 : return WSAGetLastError();
84 : #else
85 0 : return errno;
86 : #endif
87 : }
88 :
89 : static int _in_progress(int error)
90 : {
91 : #ifdef _WIN32
92 : return (error == WSAEWOULDBLOCK || error == WSAEINPROGRESS);
93 : #else
94 : return (error == EINPROGRESS);
95 : #endif
96 : }
97 :
98 0 : static void sock_getaddrinfo(xmpp_sock_t *xsock)
99 : {
100 0 : char service[6];
101 0 : struct addrinfo hints;
102 0 : int rc;
103 :
104 0 : if (xsock->ainfo_list) {
105 0 : freeaddrinfo(xsock->ainfo_list);
106 0 : xsock->ainfo_list = NULL;
107 : }
108 :
109 0 : if (xsock->srv_rr_cur) {
110 : /* Cache host and port for debug logs. */
111 0 : xsock->host = xsock->srv_rr_cur->target;
112 0 : xsock->port = xsock->srv_rr_cur->port;
113 :
114 0 : strophe_snprintf(service, 6, "%u", xsock->srv_rr_cur->port);
115 0 : memset(&hints, 0, sizeof(struct addrinfo));
116 0 : hints.ai_family = AF_UNSPEC;
117 : #ifdef AI_ADDRCONFIG
118 0 : hints.ai_flags = AI_ADDRCONFIG;
119 : #endif /* AI_ADDRCONFIG */
120 0 : hints.ai_protocol = IPPROTO_TCP;
121 0 : hints.ai_socktype = SOCK_STREAM;
122 :
123 0 : rc = getaddrinfo(xsock->srv_rr_cur->target, service, &hints,
124 : &xsock->ainfo_list);
125 0 : if (rc != 0) {
126 0 : strophe_debug(xsock->ctx, "sock", "getaddrinfo() failed with %d",
127 : rc);
128 0 : xsock->ainfo_list = NULL;
129 : }
130 : }
131 :
132 0 : xsock->ainfo_cur = xsock->ainfo_list;
133 0 : }
134 :
135 0 : xmpp_sock_t *sock_new(xmpp_conn_t *conn,
136 : const char *domain,
137 : const char *host,
138 : unsigned short port)
139 : {
140 0 : xmpp_ctx_t *ctx = conn->ctx;
141 0 : xmpp_sock_t *xsock;
142 0 : int found = XMPP_DOMAIN_NOT_FOUND;
143 :
144 0 : xsock = strophe_alloc(ctx, sizeof(*xsock));
145 0 : if (!xsock) {
146 : return NULL;
147 : }
148 :
149 0 : xsock->ctx = ctx;
150 0 : xsock->conn = conn;
151 0 : xsock->host = NULL;
152 0 : xsock->port = 0;
153 :
154 0 : if (!host) {
155 0 : found = resolver_srv_lookup(ctx, "xmpp-client", "tcp", domain,
156 : &xsock->srv_rr_list);
157 0 : if (XMPP_DOMAIN_NOT_FOUND == found)
158 0 : strophe_debug(ctx, "sock",
159 : "SRV lookup failed, connecting via domain.");
160 : }
161 0 : if (XMPP_DOMAIN_NOT_FOUND == found) {
162 : /* Resolution failed or the host is provided explicitly. */
163 0 : xsock->srv_rr_list =
164 0 : resolver_srv_rr_new(ctx, host ? host : domain, port, 0, 0);
165 : }
166 0 : xsock->srv_rr_cur = xsock->srv_rr_list;
167 :
168 0 : xsock->ainfo_list = NULL;
169 0 : sock_getaddrinfo(xsock);
170 0 : if (xsock->srv_rr_cur)
171 0 : xsock->srv_rr_cur = xsock->srv_rr_cur->next;
172 :
173 : return xsock;
174 : }
175 :
176 8 : void sock_free(xmpp_sock_t *xsock)
177 : {
178 8 : if (!xsock)
179 : return;
180 :
181 0 : if (xsock->ainfo_list)
182 0 : freeaddrinfo(xsock->ainfo_list);
183 0 : if (xsock->srv_rr_list)
184 0 : resolver_srv_free(xsock->ctx, xsock->srv_rr_list);
185 0 : strophe_free(xsock->ctx, xsock);
186 : }
187 :
188 0 : static const char *_sockaddr2str(struct sockaddr *sa, char *buf, size_t buflen)
189 : {
190 0 : buf[0] = '\0';
191 :
192 0 : switch (sa->sa_family) {
193 0 : case AF_INET:
194 0 : inet_ntop(AF_INET, &((struct sockaddr_in *)sa)->sin_addr, buf, buflen);
195 0 : break;
196 0 : case AF_INET6:
197 0 : inet_ntop(AF_INET6, &((struct sockaddr_in6 *)sa)->sin6_addr, buf,
198 : buflen);
199 0 : break;
200 : default:
201 0 : strophe_snprintf(buf, buflen, "<Unknown>");
202 : }
203 0 : return buf;
204 : }
205 :
206 0 : sock_t sock_connect(xmpp_sock_t *xsock)
207 : {
208 0 : struct addrinfo *ainfo;
209 0 : sock_t sock;
210 0 : int rc = 0;
211 0 : char buf[64];
212 :
213 0 : do {
214 0 : if (!xsock->ainfo_cur) {
215 0 : sock_getaddrinfo(xsock);
216 0 : if (xsock->srv_rr_cur)
217 0 : xsock->srv_rr_cur = xsock->srv_rr_cur->next;
218 : }
219 0 : if (!xsock->ainfo_cur) {
220 : /* We tried all available addresses. */
221 0 : return INVALID_SOCKET;
222 : }
223 :
224 0 : ainfo = xsock->ainfo_cur;
225 0 : strophe_debug(xsock->ctx, "sock", "Connecting to %s:%u via %s",
226 0 : xsock->host, xsock->port,
227 : _sockaddr2str(ainfo->ai_addr, buf, sizeof(buf)));
228 :
229 0 : sock = socket(ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol);
230 0 : if (sock != INVALID_SOCKET) {
231 0 : if (xsock->conn->sockopt_cb) {
232 : /* Don't allow user to overwrite sockfd value. */
233 0 : sock_t sock_copy = sock;
234 0 : rc = xsock->conn->sockopt_cb(xsock->conn, &sock_copy);
235 0 : if (rc != 0) {
236 0 : strophe_debug(xsock->ctx, "sock",
237 : "User's setsockopt callback"
238 : "failed with %d (errno=%d)",
239 0 : rc, errno);
240 : }
241 : }
242 0 : if (rc == 0)
243 0 : rc = sock_set_nonblocking(sock);
244 0 : if (rc == 0)
245 0 : rc = connect(sock, ainfo->ai_addr, ainfo->ai_addrlen);
246 : /* Assume only connect() can cause "in progress" error. */
247 0 : if (rc != 0 && !_in_progress(sock_error(NULL))) {
248 0 : sock_close(sock);
249 : sock = INVALID_SOCKET;
250 : }
251 : }
252 0 : strophe_debug(xsock->ctx, "sock", "sock_connect() result %d", sock);
253 :
254 0 : xsock->ainfo_cur = xsock->ainfo_cur->ai_next;
255 0 : } while (sock == INVALID_SOCKET);
256 :
257 : return sock;
258 : }
259 :
260 0 : int sock_set_keepalive(sock_t sock,
261 : int timeout,
262 : int interval,
263 : int count,
264 : unsigned int user_timeout)
265 : {
266 0 : int ret;
267 0 : int optval = (timeout && interval) ? 1 : 0;
268 :
269 0 : UNUSED(count);
270 0 : UNUSED(user_timeout);
271 :
272 : #ifdef _WIN32
273 : struct tcp_keepalive ka;
274 : DWORD dw = 0;
275 :
276 : ka.onoff = optval;
277 : ka.keepalivetime = timeout * 1000;
278 : ka.keepaliveinterval = interval * 1000;
279 : ret = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &ka, sizeof(ka), NULL, 0, &dw,
280 : NULL, NULL);
281 : #else
282 0 : ret = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));
283 0 : if (ret < 0)
284 : return ret;
285 :
286 0 : if (optval) {
287 : #ifdef TCP_KEEPIDLE
288 0 : ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &timeout,
289 : sizeof(timeout));
290 : #elif defined(TCP_KEEPALIVE)
291 : /* QNX receives `struct timeval' as argument, but it seems OSX does int
292 : */
293 : ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, &timeout,
294 : sizeof(timeout));
295 : #endif /* TCP_KEEPIDLE */
296 0 : if (ret < 0)
297 : return ret;
298 : #ifdef TCP_KEEPINTVL
299 0 : ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &interval,
300 : sizeof(interval));
301 0 : if (ret < 0)
302 : return ret;
303 : #endif /* TCP_KEEPINTVL */
304 : }
305 :
306 0 : if (count) {
307 : #ifdef TCP_KEEPCNT
308 0 : ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count));
309 0 : if (ret < 0)
310 : return ret;
311 : #endif /* TCP_KEEPCNT */
312 : }
313 :
314 0 : if (user_timeout) {
315 : #ifdef TCP_USER_TIMEOUT
316 0 : ret = setsockopt(sock, IPPROTO_TCP, TCP_USER_TIMEOUT, &user_timeout,
317 : sizeof(user_timeout));
318 0 : if (ret < 0)
319 : return ret;
320 : #elif defined(TCP_RXT_CONNDROPTIME)
321 : int rxt = user_timeout / 1000;
322 : ret = setsockopt(sock, IPPROTO_TCP, TCP_RXT_CONNDROPTIME, &rxt,
323 : sizeof(rxt));
324 : if (ret < 0)
325 : return ret;
326 : #endif /* TCP_USER_TIMEOUT */
327 : }
328 :
329 : #endif /* _WIN32 */
330 :
331 : return ret;
332 : }
333 :
334 : /** Example sockopt callback function
335 : * An example function that can be used to set reasonable default keepalive
336 : * options on sockets when registered for a connection with
337 : * xmpp_conn_set_sockopt_callback()
338 : *
339 : * @param conn a Strophe connection object
340 : * @param socket pointer to a socket descriptor
341 : *
342 : * @see xmpp_sockopt_callback for details on the `socket` parameter
343 : * @ingroup Connections
344 : */
345 0 : int xmpp_sockopt_cb_keepalive(xmpp_conn_t *conn, void *socket)
346 : {
347 0 : sock_t sock = *((sock_t *)socket);
348 :
349 0 : return sock_set_keepalive(
350 : sock, conn->ka_timeout, conn->ka_interval, conn->ka_count,
351 : conn->ka_count
352 0 : ? (conn->ka_timeout + conn->ka_interval * conn->ka_count) * 1000
353 : : 0);
354 : }
355 :
356 0 : int sock_close(sock_t sock)
357 : {
358 : #ifdef _WIN32
359 : return closesocket(sock);
360 : #else
361 0 : return close(sock);
362 : #endif
363 : }
364 :
365 0 : static int _sock_set_blocking_mode(sock_t sock, int blocking)
366 : {
367 : #ifdef _WIN32
368 : u_long nonblock = blocking ? 0 : 1;
369 : return ioctlsocket(sock, FIONBIO, &nonblock);
370 : #else
371 0 : int rc;
372 :
373 0 : rc = fcntl(sock, F_GETFL, NULL);
374 0 : if (rc >= 0) {
375 0 : rc = blocking ? rc & (~O_NONBLOCK) : rc | O_NONBLOCK;
376 0 : rc = fcntl(sock, F_SETFL, rc);
377 : }
378 0 : return rc;
379 : #endif
380 : }
381 :
382 0 : int sock_set_blocking(sock_t sock)
383 : {
384 0 : return _sock_set_blocking_mode(sock, 1);
385 : }
386 :
387 0 : int sock_set_nonblocking(sock_t sock)
388 : {
389 0 : return _sock_set_blocking_mode(sock, 0);
390 : }
391 :
392 0 : int sock_read(struct conn_interface *intf, void *buff, size_t len)
393 : {
394 0 : return recv(intf->conn->sock, buff, len, 0);
395 : }
396 :
397 0 : int sock_write(struct conn_interface *intf, const void *buff, size_t len)
398 : {
399 0 : return send(intf->conn->sock, buff, len, 0);
400 : }
401 :
402 0 : int sock_is_recoverable(struct conn_interface *intf, int error)
403 : {
404 0 : UNUSED(intf);
405 : #ifdef _WIN32
406 : return (error == WSAEINTR || error == WSAEWOULDBLOCK ||
407 : error == WSAEINPROGRESS);
408 : #else
409 0 : return (error == EAGAIN || error == EINTR);
410 : #endif
411 : }
412 :
413 0 : int sock_connect_error(sock_t sock)
414 : {
415 0 : struct sockaddr_storage ss;
416 0 : struct sockaddr *sa = (struct sockaddr *)&ss;
417 0 : socklen_t len;
418 0 : char temp;
419 :
420 0 : memset(&ss, 0, sizeof(ss));
421 0 : len = sizeof(ss);
422 0 : sa->sa_family = AF_UNSPEC;
423 :
424 : /* we don't actually care about the peer name, we're just checking if
425 : * we're connected or not */
426 0 : if (getpeername(sock, sa, &len) == 0) {
427 0 : return 0;
428 : }
429 :
430 : /* it's possible that the error wasn't ENOTCONN, so if it wasn't,
431 : * return that */
432 : #ifdef _WIN32
433 : if (sock_error(NULL) != WSAENOTCONN)
434 : return sock_error(NULL);
435 : #else
436 0 : if (sock_error(NULL) != ENOTCONN)
437 0 : return sock_error(NULL);
438 : #endif
439 :
440 : /* load the correct error into errno through error slippage */
441 0 : recv(sock, &temp, 1, 0);
442 :
443 0 : return sock_error(NULL);
444 : }
|