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