1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <platform.h>
26 #include <cfnet.h> /* CF_BUFSIZE */
27 #include <net.h>
28 #include <classic.h>
29 #include <tls_generic.h>
30 #include <connection_info.h>
31 #include <logging.h>
32 #include <misc_lib.h>
33 #include <cf3.defs.h>
34 #include <protocol.h>
35
36
37 /* TODO remove libpromises dependency. */
38 extern char BINDINTERFACE[CF_MAXVARSIZE]; /* cf3globals.c, cf3.extern.h */
39
SetBindInterface(const char * ip)40 void SetBindInterface(const char *ip)
41 {
42 strlcpy(BINDINTERFACE, ip, sizeof(BINDINTERFACE));
43 Log(LOG_LEVEL_VERBOSE, "Setting bindtointerface to '%s'", BINDINTERFACE);
44 }
45
46
47 /**
48 * @param len is the number of bytes to send, or 0 if buffer is a
49 * '\0'-terminated string so strlen(buffer) can be used.
50 * @return -1 in case of error or connection closed
51 * (also currently returns 0 for success but don't count on it)
52 * @NOTE #buffer can't be of zero length, our protocol
53 * does not allow empty transactions!
54 * @NOTE (len <= CF_BUFSIZE - CF_INBAND_OFFSET)
55 *
56 * @TODO Currently only transactions up to CF_BUFSIZE-CF_INBAND_OFFSET are
57 * allowed to be sent. This function should be changed to allow up to
58 * CF_BUFSIZE-1 (since '\0' is not sent, but the receiver needs space to
59 * append it). So transaction length will be at most 4095!
60 */
SendTransaction(ConnectionInfo * conn_info,const char * buffer,int len,char status)61 int SendTransaction(ConnectionInfo *conn_info,
62 const char *buffer, int len, char status)
63 {
64 assert(status == CF_MORE || status == CF_DONE);
65
66 char work[CF_BUFSIZE] = { 0 };
67 int ret;
68
69 if (len == 0)
70 {
71 len = strlen(buffer);
72 }
73
74 /* Not allowed to send zero-payload packets */
75 assert(len > 0);
76
77 if (len > CF_BUFSIZE - CF_INBAND_OFFSET)
78 {
79 Log(LOG_LEVEL_ERR, "SendTransaction: len (%d) > %d - %d",
80 len, CF_BUFSIZE, CF_INBAND_OFFSET);
81 return -1;
82 }
83
84 snprintf(work, CF_INBAND_OFFSET, "%c %d", status, len);
85
86 memcpy(work + CF_INBAND_OFFSET, buffer, len);
87
88 Log(LOG_LEVEL_DEBUG, "SendTransaction header: %s", work);
89 LogRaw(LOG_LEVEL_DEBUG, "SendTransaction data: ",
90 work + CF_INBAND_OFFSET, len);
91
92 switch (ProtocolClassicOrTLS(conn_info->protocol))
93 {
94
95 case CF_PROTOCOL_CLASSIC:
96 ret = SendSocketStream(conn_info->sd, work,
97 len + CF_INBAND_OFFSET);
98 break;
99
100 case CF_PROTOCOL_TLS:
101 ret = TLSSend(conn_info->ssl, work, len + CF_INBAND_OFFSET);
102 if (ret <= 0)
103 {
104 ret = -1;
105 }
106 break;
107
108 default:
109 UnexpectedError("SendTransaction: ProtocolVersion %d!",
110 conn_info->protocol);
111 ret = -1;
112 }
113
114 if (ret == -1)
115 {
116 /* We are experiencing problems with sending data to server.
117 * This might lead to packages being not delivered in correct
118 * order and unexpected issues like directories being replaced
119 * with files.
120 * In order to make sure that file transfer is reliable we have to
121 * close connection to avoid broken packages being received. */
122 conn_info->status = CONNECTIONINFO_STATUS_BROKEN;
123 return -1;
124 }
125 else
126 {
127 /* SSL_MODE_AUTO_RETRY guarantees no partial writes. */
128 assert(ret == len + CF_INBAND_OFFSET);
129
130 return 0;
131 }
132 }
133
134 /*************************************************************************/
135
136 /**
137 * Receive a transaction packet of at most CF_BUFSIZE-1 bytes, and
138 * NULL-terminate it.
139 *
140 * @param #buffer must be of size at least CF_BUFSIZE.
141 *
142 * @return -1 in case of closed socket, other error or timeout.
143 * The connection MAY NOT BE FINALISED!
144 * >0 the number of bytes read, transaction was successfully received.
145 *
146 * @TODO shutdown() the connection in all cases were this function returns -1,
147 * in order to protect against future garbage reads.
148 */
ReceiveTransaction(ConnectionInfo * conn_info,char * buffer,int * more)149 int ReceiveTransaction(ConnectionInfo *conn_info, char *buffer, int *more)
150 {
151 char proto[CF_INBAND_OFFSET + 1] = { 0 };
152 int ret;
153
154 /* Get control channel. */
155 switch (ProtocolClassicOrTLS(conn_info->protocol))
156 {
157 case CF_PROTOCOL_CLASSIC:
158 ret = RecvSocketStream(conn_info->sd, proto, CF_INBAND_OFFSET);
159 break;
160 case CF_PROTOCOL_TLS:
161 ret = TLSRecv(conn_info->ssl, proto, CF_INBAND_OFFSET);
162 break;
163 default:
164 UnexpectedError("ReceiveTransaction: ProtocolVersion %d!",
165 conn_info->protocol);
166 ret = -1;
167 }
168
169 /* If error occurred or recv() timeout or if connection was gracefully
170 * closed. Connection has been finalised. */
171 if (ret <= 0)
172 {
173 /* We are experiencing problems with receiving data from server.
174 * This might lead to packages being not delivered in correct
175 * order and unexpected issues like directories being replaced
176 * with files.
177 * In order to make sure that file transfer is reliable we have to
178 * close connection to avoid broken packages being received. */
179 conn_info->status = CONNECTIONINFO_STATUS_BROKEN;
180 return -1;
181 }
182 else if (ret != CF_INBAND_OFFSET)
183 {
184 /* If we received less bytes than expected. Might happen
185 * with TLSRecv(). */
186 Log(LOG_LEVEL_ERR,
187 "ReceiveTransaction: bogus short header (%d bytes: '%s')",
188 ret, proto);
189 conn_info->status = CONNECTIONINFO_STATUS_BROKEN;
190 return -1;
191 }
192
193 LogRaw(LOG_LEVEL_DEBUG, "ReceiveTransaction header: ", proto, ret);
194
195 char status = 'x';
196 int len = 0;
197
198 ret = sscanf(proto, "%c %d", &status, &len);
199 if (ret != 2)
200 {
201 Log(LOG_LEVEL_ERR,
202 "ReceiveTransaction: bogus header: %s", proto);
203 conn_info->status = CONNECTIONINFO_STATUS_BROKEN;
204 return -1;
205 }
206
207 if (status != CF_MORE && status != CF_DONE)
208 {
209 Log(LOG_LEVEL_ERR,
210 "ReceiveTransaction: bogus header (more='%c')", status);
211 conn_info->status = CONNECTIONINFO_STATUS_BROKEN;
212 return -1;
213 }
214 if (len > CF_BUFSIZE - CF_INBAND_OFFSET)
215 {
216 Log(LOG_LEVEL_ERR,
217 "ReceiveTransaction: packet too long (len=%d)", len);
218 conn_info->status = CONNECTIONINFO_STATUS_BROKEN;
219 return -1;
220 }
221 else if (len <= 0)
222 {
223 /* Zero-length packets are disallowed, because
224 * ReceiveTransaction() == 0 currently means connection closed. */
225 Log(LOG_LEVEL_ERR,
226 "ReceiveTransaction: packet too short (len=%d)", len);
227 conn_info->status = CONNECTIONINFO_STATUS_BROKEN;
228 return -1;
229 }
230
231 if (more != NULL)
232 {
233 switch (status)
234 {
235 case CF_MORE:
236 *more = true;
237 break;
238 case CF_DONE:
239 *more = false;
240 break;
241 default:
242 ProgrammingError("Unreachable, "
243 "bogus headers have already been checked!");
244 }
245 }
246
247 /* Get data. */
248 switch (ProtocolClassicOrTLS(conn_info->protocol))
249 {
250 case CF_PROTOCOL_CLASSIC:
251 ret = RecvSocketStream(conn_info->sd, buffer, len);
252 break;
253 case CF_PROTOCOL_TLS:
254 ret = TLSRecv(conn_info->ssl, buffer, len);
255 break;
256 default:
257 UnexpectedError("ReceiveTransaction: ProtocolVersion %d!",
258 conn_info->protocol);
259 ret = -1;
260 }
261
262 /* Connection gracefully closed (ret==0) or connection error (ret==-1) or
263 * just partial receive of bytestream.*/
264 if (ret != len)
265 {
266 /*
267 * Should never happen except with TLS, given that we are using
268 * SSL_MODE_AUTO_RETRY and that transaction payload < CF_BUFSIZE < TLS
269 * record size, it can currently only happen if the other side does
270 * TLSSend(wrong_number) for the transaction.
271 *
272 * TODO IMPORTANT terminate TLS session in that case.
273 */
274 Log(LOG_LEVEL_ERR,
275 "Partial transaction read %d != %d bytes!",
276 ret, len);
277 conn_info->status = CONNECTIONINFO_STATUS_BROKEN;
278 return -1;
279 }
280
281 LogRaw(LOG_LEVEL_DEBUG, "ReceiveTransaction data: ", buffer, ret);
282
283 return ret;
284 }
285
286 /* BWlimit global variables
287
288 Throttling happens for all network interfaces, all traffic being sent for
289 any connection of this process (cf-agent or cf-serverd).
290 We need a lock, to avoid concurrent writes to "bwlimit_next".
291 Then, "bwlimit_next" is the absolute time (as of clock_gettime() ) that we
292 are clear to send, after. It is incremented with the delay for every packet
293 scheduled for sending. Thus, integer arithmetic will make sure we wait for
294 the correct amount of time, in total.
295 */
296
297 #ifndef _WIN32
298 static pthread_mutex_t bwlimit_lock = PTHREAD_MUTEX_INITIALIZER;
299 static struct timespec bwlimit_next = {0, 0L};
300 #endif
301
302 uint32_t bwlimit_kbytes = 0; /* desired limit, in kB/s */
303
304
305 /** Throttle traffic, if next packet happens too soon after the previous one
306 *
307 * This function is global, across all network operations (and interfaces, perhaps)
308 * @param tosend Length of current packet being sent out (in bytes)
309 */
310
311 #ifdef CLOCK_MONOTONIC
312 # define PREFERRED_CLOCK CLOCK_MONOTONIC
313 #else
314 /* Some OS-es don't have monotonic clock, but we can still use the
315 * next available one */
316 # define PREFERRED_CLOCK CLOCK_REALTIME
317 #endif
318
EnforceBwLimit(int tosend)319 void EnforceBwLimit(int tosend)
320 {
321 if (!bwlimit_kbytes)
322 {
323 /* early return, before any expensive syscalls */
324 return;
325 }
326
327 #ifdef _WIN32
328 Log(LOG_LEVEL_WARNING, "Bandwidth limiting with \"bwlimit\" is not supported on Windows.");
329 (void)tosend; // Avoid "unused" warning.
330 return;
331 #else
332
333 const uint32_t u_10e6 = 1000000L;
334 const uint32_t u_10e9 = 1000000000L;
335 struct timespec clock_now = {0, 0L};
336
337 if (pthread_mutex_lock(&bwlimit_lock) == 0)
338 {
339 clock_gettime(PREFERRED_CLOCK, &clock_now);
340
341 if ((bwlimit_next.tv_sec < clock_now.tv_sec) ||
342 ( (bwlimit_next.tv_sec == clock_now.tv_sec) &&
343 (bwlimit_next.tv_nsec < clock_now.tv_nsec) ) )
344 {
345 /* penalty has expired, we can immediately send data. But reset the timestamp */
346 bwlimit_next = clock_now;
347 clock_now.tv_sec = 0;
348 clock_now.tv_nsec = 0L;
349 }
350 else
351 {
352 clock_now.tv_sec = bwlimit_next.tv_sec - clock_now.tv_sec;
353 clock_now.tv_nsec = bwlimit_next.tv_nsec - clock_now.tv_nsec;
354 if (clock_now.tv_nsec < 0L)
355 {
356 clock_now.tv_sec --;
357 clock_now.tv_nsec += u_10e9;
358 }
359 }
360
361 uint64_t delay = ((uint64_t) tosend * u_10e6) / bwlimit_kbytes; /* in ns */
362
363 bwlimit_next.tv_sec += (delay / u_10e9);
364 bwlimit_next.tv_nsec += (long) (delay % u_10e9);
365 if (bwlimit_next.tv_nsec >= u_10e9)
366 {
367 bwlimit_next.tv_sec++;
368 bwlimit_next.tv_nsec -= u_10e9;
369 }
370
371 if (bwlimit_next.tv_sec > 20)
372 {
373 /* Upper limit of 20sec for penalty. This will avoid huge wait if
374 * our clock has jumped >minutes back in time. Still, assuming that
375 * most of our packets are <= 2048 bytes, the lower bwlimit is bound
376 * to 102.4 Bytes/sec. With 65k packets (rare) is 3.7kBytes/sec in
377 * that extreme case.
378 * With more clients hitting a single server, this lower bound is
379 * multiplied by num of clients, eg. 102.4kBytes/sec for 1000 reqs.
380 * simultaneously.
381 */
382 bwlimit_next.tv_sec = 20;
383 }
384 pthread_mutex_unlock(&bwlimit_lock);
385 }
386
387 /* Even if we push our data every few bytes to the network interface,
388 the software+hardware buffers will queue it and send it in bursts,
389 anyway. It is more likely that we will waste CPU sys-time calling
390 nanosleep() for such short delays.
391 So, sleep only if we have >1ms penalty
392 */
393 if (clock_now.tv_sec > 0 || ( (clock_now.tv_sec == 0) && (clock_now.tv_nsec >= u_10e6)) )
394 {
395 nanosleep(&clock_now, NULL);
396 }
397 #endif // !_WIN32
398 }
399
400
401 /*************************************************************************/
402
403
404 /**
405 Tries to connect() to server #host, returns the socket descriptor and the
406 IP address that succeeded in #txtaddr.
407
408 @param #connect_timeout how long to wait for connect(), zero blocks forever
409 @param #txtaddr If connected successfully return the IP connected in
410 textual representation
411 @return Connected socket descriptor or -1 in case of failure.
412 */
SocketConnect(const char * host,const char * port,unsigned int connect_timeout,bool force_ipv4,char * txtaddr,size_t txtaddr_size)413 int SocketConnect(const char *host, const char *port,
414 unsigned int connect_timeout, bool force_ipv4,
415 char *txtaddr, size_t txtaddr_size)
416 {
417 struct addrinfo *response = NULL, *ap;
418 bool connected = false;
419 int sd = -1;
420
421 struct addrinfo query = {
422 .ai_family = force_ipv4 ? AF_INET : AF_UNSPEC,
423 .ai_socktype = SOCK_STREAM
424 };
425
426 int ret = getaddrinfo(host, port, &query, &response);
427 if (ret != 0)
428 {
429 Log(LOG_LEVEL_INFO,
430 "Unable to find host '%s' service '%s' (%s)",
431 host, port, gai_strerror(ret));
432 if (response != NULL)
433 {
434 freeaddrinfo(response);
435 }
436 return -1;
437 }
438
439 for (ap = response; !connected && ap != NULL; ap = ap->ai_next)
440 {
441 /* Convert address to string. */
442 getnameinfo(ap->ai_addr, ap->ai_addrlen,
443 txtaddr, txtaddr_size,
444 NULL, 0, NI_NUMERICHOST);
445 Log(LOG_LEVEL_VERBOSE,
446 "Connecting to host %s, port %s as address %s",
447 host, port, txtaddr);
448
449 sd = socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol);
450 if (sd == -1)
451 {
452 Log(LOG_LEVEL_ERR, "Couldn't open a socket to '%s' (socket: %s)",
453 txtaddr, GetErrorStr());
454 }
455 else
456 {
457 /* Bind socket to specific interface, if requested. */
458 if (BINDINTERFACE[0] != '\0')
459 {
460 struct addrinfo query2 = {
461 .ai_family = force_ipv4 ? AF_INET : AF_UNSPEC,
462 .ai_socktype = SOCK_STREAM,
463 /* returned address is for bind() */
464 .ai_flags = AI_PASSIVE
465 };
466
467 struct addrinfo *response2 = NULL, *ap2;
468 int ret2 = getaddrinfo(BINDINTERFACE, NULL, &query2, &response2);
469 if (ret2 != 0)
470 {
471 Log(LOG_LEVEL_ERR,
472 "Unable to lookup interface '%s' to bind. (getaddrinfo: %s)",
473 BINDINTERFACE, gai_strerror(ret2));
474
475 if (response2 != NULL)
476 {
477 freeaddrinfo(response2);
478 }
479 assert(response); /* first getaddrinfo was successful */
480 freeaddrinfo(response);
481 cf_closesocket(sd);
482 return -1;
483 }
484
485 for (ap2 = response2; ap2 != NULL; ap2 = ap2->ai_next)
486 {
487 if (bind(sd, ap2->ai_addr, ap2->ai_addrlen) == 0)
488 {
489 break;
490 }
491 }
492 if (ap2 == NULL)
493 {
494 Log(LOG_LEVEL_ERR,
495 "Unable to bind to interface '%s'. (bind: %s)",
496 BINDINTERFACE, GetErrorStr());
497 }
498 assert(response2); /* second getaddrinfo was successful */
499 freeaddrinfo(response2);
500 }
501
502 connected = TryConnect(sd, connect_timeout * 1000,
503 ap->ai_addr, ap->ai_addrlen);
504 if (!connected)
505 {
506 Log(LOG_LEVEL_VERBOSE, "Unable to connect to address %s (%s)",
507 txtaddr, GetErrorStr());
508 cf_closesocket(sd);
509 sd = -1;
510 }
511 }
512 }
513
514 assert(response != NULL); /* first getaddrinfo was successful */
515 freeaddrinfo(response);
516
517 if (connected)
518 {
519 Log(LOG_LEVEL_VERBOSE,
520 "Connected to host %s address %s port %s (socket descriptor %d)",
521 host, txtaddr, port, sd);
522 }
523 else
524 {
525 Log(LOG_LEVEL_VERBOSE,
526 "Unable to connect to host %s port %s (socket descriptor %d)",
527 host, port, sd);
528 }
529
530 return sd;
531 }
532
533
534 #if !defined(__MINGW32__)
535
536 #if defined(__hpux) && defined(__GNUC__)
537 #pragma GCC diagnostic ignored "-Wstrict-aliasing"
538 // HP-UX GCC type-pun warning on FD_SET() macro:
539 // While the "fd_set" type is defined in /usr/include/sys/_fd_macros.h as a
540 // struct of an array of "long" values in accordance with the XPG4 standard's
541 // requirements, the macros for the FD operations "pretend it is an array of
542 // int32_t's so the binary layout is the same for both Narrow and Wide
543 // processes," as described in _fd_macros.h. In the FD_SET, FD_CLR, and
544 // FD_ISSET macros at line 101, the result is cast to an "__fd_mask *" type,
545 // which is defined as int32_t at _fd_macros.h:82.
546 //
547 // This conflict between the "long fds_bits[]" array in the XPG4-compliant
548 // fd_set structure, and the cast to an int32_t - not long - pointer in the
549 // macros, causes a type-pun warning if -Wstrict-aliasing is enabled.
550 // The warning is merely a side effect of HP-UX working as designed,
551 // so it can be ignored.
552 #endif
553
554 /**
555 * Tries to connect for #timeout_ms milliseconds. On success sets the recv()
556 * timeout to #timeout_ms as well.
557 *
558 * @param #timeout_ms How long to wait for connect(), if zero wait forever.
559 * @return true on success, false otherwise.
560 **/
TryConnect(int sd,unsigned long timeout_ms,const struct sockaddr * sa,socklen_t sa_len)561 bool TryConnect(int sd, unsigned long timeout_ms,
562 const struct sockaddr *sa, socklen_t sa_len)
563 {
564 assert(sd != -1);
565 assert(sa != NULL);
566
567 if (sd >= FD_SETSIZE)
568 {
569 Log(LOG_LEVEL_ERR,
570 "Open connections exceed FD_SETSIZE limit (%d >= %d)",
571 sd, FD_SETSIZE);
572 return false;
573 }
574
575 /* set non-blocking socket */
576 int arg = fcntl(sd, F_GETFL, NULL);
577 int ret = fcntl(sd, F_SETFL, arg | O_NONBLOCK);
578 if (ret == -1)
579 {
580 Log(LOG_LEVEL_ERR,
581 "Failed to set socket to non-blocking mode (fcntl: %s)",
582 GetErrorStr());
583 }
584
585 ret = connect(sd, sa, sa_len);
586 if (ret == -1)
587 {
588 if (errno != EINPROGRESS)
589 {
590 Log(LOG_LEVEL_INFO, "Failed to connect to server (connect: %s)",
591 GetErrorStr());
592 return false;
593 }
594
595 int errcode;
596 socklen_t opt_len = sizeof(errcode);
597 fd_set myset;
598 FD_ZERO(&myset);
599 FD_SET(sd, &myset);
600
601 Log(LOG_LEVEL_VERBOSE, "Waiting to connect...");
602
603 struct timeval tv, *tvp;
604 if (timeout_ms > 0)
605 {
606 tv.tv_sec = timeout_ms / 1000;
607 tv.tv_usec = (timeout_ms % 1000) * 1000;
608 tvp = &tv;
609 }
610 else
611 {
612 tvp = NULL; /* wait indefinitely */
613 }
614
615 ret = select(sd + 1, NULL, &myset, NULL, tvp);
616 if (ret == 0)
617 {
618 Log(LOG_LEVEL_INFO, "Timeout connecting to server");
619 return false;
620 }
621 if (ret == -1)
622 {
623 if (errno == EINTR)
624 {
625 Log(LOG_LEVEL_ERR,
626 "Socket connect was interrupted by signal");
627 }
628 else
629 {
630 Log(LOG_LEVEL_ERR,
631 "Failure while connecting (select: %s)",
632 GetErrorStr());
633 }
634 return false;
635 }
636
637 ret = getsockopt(sd, SOL_SOCKET, SO_ERROR,
638 (void *) &errcode, &opt_len);
639 if (ret == -1)
640 {
641 Log(LOG_LEVEL_ERR,
642 "Could not check connection status (getsockopt: %s)",
643 GetErrorStr());
644 return false;
645 }
646
647 if (errcode != 0)
648 {
649 Log(LOG_LEVEL_INFO, "Failed to connect to server: %s",
650 GetErrorStrFromCode(errcode));
651 return false;
652 }
653 }
654
655 /* Connection succeeded, return to blocking mode. */
656 ret = fcntl(sd, F_SETFL, arg);
657 if (ret == -1)
658 {
659 Log(LOG_LEVEL_ERR,
660 "Failed to set socket back to blocking mode (fcntl: %s)",
661 GetErrorStr());
662 }
663
664 if (timeout_ms > 0)
665 {
666 SetReceiveTimeout(sd, timeout_ms);
667 }
668
669 return true;
670 }
671
672 #if defined(__hpux) && defined(__GNUC__)
673 #pragma GCC diagnostic warning "-Wstrict-aliasing"
674 #endif
675
676 #endif /* !defined(__MINGW32__) */
677
678
679
680 /**
681 * Set timeout for recv(), in milliseconds.
682 * @param ms must be > 0.
683 */
SetReceiveTimeout(int fd,unsigned long ms)684 int SetReceiveTimeout(int fd, unsigned long ms)
685 {
686 assert(ms > 0);
687
688 Log(LOG_LEVEL_VERBOSE, "Setting socket timeout to %lu seconds.", ms/1000);
689
690 /* On windows SO_RCVTIMEO is set by a DWORD indicating the timeout in
691 * milliseconds, on UNIX it's a struct timeval. */
692
693 #if !defined(__MINGW32__)
694 struct timeval tv = {
695 .tv_sec = ms / 1000,
696 .tv_usec = (ms % 1000) * 1000
697 };
698 int ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
699 #else
700 int ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &ms, sizeof(ms));
701 #endif
702
703 if (ret != 0)
704 {
705 Log(LOG_LEVEL_VERBOSE,
706 "Failed to set socket timeout to %lu milliseconds.", ms);
707 return -1;
708 }
709
710 return 0;
711 }
712