UPnPsdk 0.1
Universal Plug and Play +, Software Development Kit
 
Loading...
Searching...
No Matches
sock.cpp
Go to the documentation of this file.
1/**************************************************************************
2 *
3 * Copyright (c) 2000-2003 Intel Corporation
4 * All rights reserved.
5 * Copyright (c) 2012 France Telecom All rights reserved.
6 * Copyright (C) 2021+ GPL 3 and higher by Ingo Höft, <Ingo@Hoeft-online.de>
7 * Redistribution only with this Copyright remark. Last modified: 2025-04-16
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions are met:
11 *
12 * - Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution.
17 * - Neither name of Intel Corporation nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR
25 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
29 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 *
33 **************************************************************************/
34// Last compare with ./pupnp source file on 2023-09-13, ver 1.14.18
41#include <sock.hpp>
42#include <upnp.hpp>
43
45#include <UPnPsdk/socket.hpp> // needed for compiling on win32.
46
47#include <umock/sys_socket.hpp>
48#include <umock/ssl.hpp>
49
51#include <fcntl.h> /* for F_GETFL, F_SETFL, O_NONBLOCK */
52#include <cstring>
54
55
56namespace {
57
58#ifdef UPnPsdk_HAVE_OPENSSL
63SSL_CTX* gSslCtx{nullptr};
64#endif
65
81 const SOCKINFO* a_info,
83 char* a_readbuf,
85 [[maybe_unused]] const char* a_writebuf,
87 const size_t a_bufsize,
90 int* a_timeoutSecs) {
91 time_t start_time{time(NULL)};
92 TRACE("Executing sock_read_unprotected()")
93
94 // Also restrict a_bufsize to integer for save later use despite type cast.
95 if (a_info == nullptr || a_readbuf == nullptr || a_bufsize > INT_MAX)
97 if (a_bufsize == 0)
98 return 0;
99
100 SOCKET sockfd{a_info->socket};
101
102 ::fd_set readSet;
103 ::fd_set writeSet;
104 FD_ZERO(&readSet);
105 FD_ZERO(&writeSet);
106 FD_SET(sockfd, &readSet);
107
108 // a_timeoutSecs == nullptr means default timeout to use.
109 int timeout_secs = (a_timeoutSecs == nullptr) ? UPnPsdk::g_response_timeout
110 : *a_timeoutSecs;
111
112 UPnPsdk::CSocketErr sockerrObj;
113 while (true) {
114 // Due to 'man select' timeout should be considered to be undefined
115 // after select() returns so we must set it on every loop.
116 ::timeval timeout;
117 timeout.tv_sec = timeout_secs;
118 timeout.tv_usec = 0;
119
120 // select() monitors only one socket file descriptor.
121 int retCode = umock::sys_socket_h.select(
122 static_cast<int>(sockfd + 1), &readSet, &writeSet, nullptr,
123 (timeout_secs < 0) ? nullptr : &timeout);
124
125 if (retCode == 0)
126 return UPNP_E_TIMEDOUT;
127 if (retCode == SOCKET_ERROR) {
128 sockerrObj.catch_error();
129 if (sockerrObj == EINTRP)
130 // Signal catched by select(). It is not for us so we
131 continue;
132 return UPNP_E_SOCKET_ERROR;
133 } else
134 /* read */
135 break;
136 }
137
138 TRACE("Read data with syscall ::recv().")
139 SSIZEP_T numBytes = umock::sys_socket_h.recv(
140 sockfd, a_readbuf, static_cast<SIZEP_T>(a_bufsize), 0);
141
142 // Also protect type cast
143 if (numBytes < 0 || numBytes > INT_MAX)
144 return UPNP_E_SOCKET_ERROR;
145
146 /* subtract time used for reading/writing. */
147 if (a_timeoutSecs != nullptr && timeout_secs != 0)
148 *a_timeoutSecs -= static_cast<int>(time(NULL) - start_time);
149
150 return static_cast<int>(numBytes);
151}
152
153
166 const SOCKINFO* a_info,
168 [[maybe_unused]] char* a_readbuf,
170 const char* a_writebuf,
172 const size_t a_bufsize,
175 int* a_timeoutSecs) {
176 time_t start_time{time(NULL)};
177 TRACE("Executing sock_write_unprotected()")
178
179 // Also restrict a_bufsize to integer for save later use despite type cast.
180 if (a_info == nullptr || a_writebuf == nullptr || a_bufsize > INT_MAX)
181 return UPNP_E_SOCKET_ERROR;
182 if (a_bufsize == 0)
183 return 0;
184
185 SOCKET sockfd{a_info->socket};
186
187 ::fd_set readSet;
188 ::fd_set writeSet;
189 FD_ZERO(&readSet);
190 FD_ZERO(&writeSet);
191 FD_SET(sockfd, &writeSet);
192
193 // a_timeoutSecs == nullptr means default timeout to use.
194 int timeout_secs = (a_timeoutSecs == nullptr) ? UPnPsdk::g_response_timeout
195 : *a_timeoutSecs;
196
197 UPnPsdk::CSocketErr sockerrObj;
198 while (true) {
199 // Due to 'man select' timeout should be considered to be undefined
200 // after select() returns so we must set it on every loop.
201 ::timeval timeout;
202 timeout.tv_sec = timeout_secs;
203 timeout.tv_usec = 0;
204
205 // select monitors only one socket file descriptor.
206 int retCode = umock::sys_socket_h.select(
207 static_cast<int>(sockfd + 1), &readSet, &writeSet, nullptr,
208 (timeout_secs < 0) ? nullptr : &timeout);
209
210 if (retCode == 0)
211 return UPNP_E_TIMEDOUT;
212 if (retCode == SOCKET_ERROR) {
213 sockerrObj.catch_error();
214 if (sockerrObj == EINTRP)
215 // Signal catched by select(). It is not for us so we
216 continue;
217 return UPNP_E_SOCKET_ERROR;
218 } else
219 /* write. */
220 break;
221 }
222
223 // a_bufsize is restricted from 0 to INT_MAX.
224 ssize_t byte_left{static_cast<ssize_t>(a_bufsize)};
225 ssize_t bytes_sent{};
226
227 TRACE("Write data with syscall ::send().")
228 UPNPLIB_SCOPED_NO_SIGPIPE
229 while (byte_left != 0) {
230 ssize_t num_written = umock::sys_socket_h.send(
231 sockfd, a_writebuf + bytes_sent, static_cast<SIZEP_T>(byte_left),
232 MSG_DONTROUTE);
233 if (num_written == -1 || num_written > INT_MAX) {
234 return UPNP_E_SOCKET_WRITE;
235 }
236 byte_left -= num_written;
237 bytes_sent += num_written;
238 }
239
240 // Also protect type cast
241 if (bytes_sent < 0 || bytes_sent > INT_MAX)
242 return UPNP_E_SOCKET_ERROR;
243
244 /* subtract time used for writing. */
245 if (a_timeoutSecs != nullptr && timeout_secs != 0)
246 *a_timeoutSecs -= static_cast<int>(time(NULL) - start_time);
247
248 return static_cast<int>(bytes_sent);
249}
250
251
252#ifdef UPnPsdk_HAVE_OPENSSL
266 const SOCKINFO* a_info,
268 char* a_readbuf,
270 [[maybe_unused]] const char* a_writebuf,
272 const size_t a_bufsize,
275 int* a_timeoutSecs) {
276 time_t start_time{time(NULL)};
277 TRACE("Executing sock_read_ssl()")
278
279 // Also restrict a_bufsize to integer for save later use despite type cast.
280 if (a_info == nullptr || a_readbuf == nullptr || a_bufsize > INT_MAX)
281 return UPNP_E_SOCKET_ERROR;
282 if (a_bufsize == 0)
283 return 0;
284
285 SOCKET sockfd{a_info->socket};
286
287 ::fd_set readSet;
288 ::fd_set writeSet;
289 FD_ZERO(&readSet);
290 FD_ZERO(&writeSet);
291 FD_SET(sockfd, &readSet);
292
293 // a_timeoutSecs == nullptr means default timeout to use.
294 int timeout_secs = (a_timeoutSecs == nullptr) ? UPnPsdk::g_response_timeout
295 : *a_timeoutSecs;
296
297 UPnPsdk::CSocketErr sockerrObj;
298 while (true) {
299 // Due to 'man select' timeout should be considered to be undefined
300 // after select() returns so we must set it on every loop.
301 ::timeval timeout;
302 timeout.tv_sec = timeout_secs;
303 timeout.tv_usec = 0;
304
305 // select() monitors only one socket file descriptor.
306 int retCode = umock::sys_socket_h.select(
307 static_cast<int>(sockfd + 1), &readSet, &writeSet, nullptr,
308 (timeout_secs < 0) ? nullptr : &timeout);
309
310 if (retCode == 0)
311 return UPNP_E_TIMEDOUT;
312 if (retCode == SOCKET_ERROR) {
313 sockerrObj.catch_error();
314 if (sockerrObj == EINTRP)
315 // Signal catched by select(). It is not for us so we
316 continue;
317 return UPNP_E_SOCKET_ERROR;
318 } else
319 /* read */
320 break;
321 }
322
323 TRACE("Read data with syscall ::SSL_read().")
324 // Type cast a_bufsize is protected above to only contain 0 to INT_MAX.
325 int numBytes = umock::ssl_h.SSL_read(a_info->ssl, a_readbuf,
326 static_cast<int>(a_bufsize));
327
328 if (numBytes < 0)
329 return UPNP_E_SOCKET_ERROR;
330
331 /* subtract time used for reading/writing. */
332 if (a_timeoutSecs != nullptr && timeout_secs != 0)
333 *a_timeoutSecs -= static_cast<int>(time(NULL) - start_time);
334
335 return numBytes;
336}
337
338
353 const SOCKINFO* a_info,
355 [[maybe_unused]] char* a_readbuf,
357 const char* a_writebuf,
359 const size_t a_bufsize,
362 int* a_timeoutSecs) {
363 time_t start_time{time(NULL)};
364 TRACE("Executing sock_write_ssl()")
365
366 // Also restrict a_bufsize to integer for save later use despite type cast.
367 if (a_info == nullptr || a_writebuf == nullptr || a_bufsize > INT_MAX)
368 return UPNP_E_SOCKET_ERROR;
369 if (a_bufsize == 0)
370 return 0;
371
372 SOCKET sockfd{a_info->socket};
373
374 ::fd_set readSet;
375 ::fd_set writeSet;
376 FD_ZERO(&readSet);
377 FD_ZERO(&writeSet);
378 FD_SET(sockfd, &writeSet);
379
380 // a_timeoutSecs == nullptr means default timeout to use.
381 int timeout_secs = (a_timeoutSecs == nullptr) ? UPnPsdk::g_response_timeout
382 : *a_timeoutSecs;
383
384 UPnPsdk::CSocketErr sockerrObj;
385 while (true) {
386 // Due to 'man select' timeout should be considered to be undefined
387 // after select() returns so we must set it on every loop.
388 ::timeval timeout;
389 timeout.tv_sec = timeout_secs;
390 timeout.tv_usec = 0;
391
392 // select monitors only one socket file descriptor.
393 int retCode = umock::sys_socket_h.select(
394 static_cast<int>(sockfd + 1), &readSet, &writeSet, nullptr,
395 (timeout_secs < 0) ? nullptr : &timeout);
396
397 if (retCode == 0)
398 return UPNP_E_TIMEDOUT;
399 if (retCode == SOCKET_ERROR) {
400 sockerrObj.catch_error();
401 if (sockerrObj == EINTRP)
402 // Signal catched by select(). It is not for us so we
403 continue;
404 return UPNP_E_SOCKET_ERROR;
405 } else
406 /* write. */
407 break;
408 }
409
410 int byte_left{static_cast<int>(a_bufsize)};
411 int bytes_sent{};
412
413 UPNPLIB_SCOPED_NO_SIGPIPE
414 while (byte_left != 0) {
415 TRACE("Write data with syscall ::SSL_write().")
416 int num_written = umock::ssl_h.SSL_write(
417 a_info->ssl, a_writebuf + bytes_sent, byte_left);
418 if (num_written == -1) {
419 return UPNP_E_SOCKET_WRITE;
420 }
421 byte_left -= num_written;
422 bytes_sent += num_written;
423 }
424
425 if (bytes_sent < 0)
426 return UPNP_E_SOCKET_ERROR;
427
428 /* subtract time used for writing. */
429 if (a_timeoutSecs != nullptr && timeout_secs != 0)
430 *a_timeoutSecs -= static_cast<int>(time(NULL) - start_time);
431
432 return bytes_sent;
433}
434#endif
435
437} // anonymous namespace
438
439
440int sock_init(SOCKINFO* info, SOCKET sockfd) {
441 TRACE("Executing sock_init()")
442 if (info == nullptr)
444
445 memset(info, 0, sizeof(SOCKINFO));
446 info->socket = sockfd;
447
448 return UPNP_E_SUCCESS;
449}
450
451int sock_init_with_ip(SOCKINFO* info, SOCKET sockfd,
452 sockaddr* foreign_sockaddr) {
453 TRACE("Executing sock_init_with_ip()")
454
455 if (foreign_sockaddr == nullptr)
457
458 int ret = sock_init(info, sockfd);
459 if (ret != UPNP_E_SUCCESS) {
460 return ret;
461 }
462 memcpy(&info->foreign_sockaddr, foreign_sockaddr,
463 sizeof(info->foreign_sockaddr));
464
465 return UPNP_E_SUCCESS;
466}
467
468#ifdef UPnPsdk_HAVE_OPENSSL
470 TRACE("Executing sock_ssl_connect()");
471 info->ssl = SSL_new(gSslCtx);
472 if (!info->ssl) {
473 return UPNP_E_SOCKET_ERROR;
474 }
475 // Due to man page there is no problem with type cast (int)
476 int status = SSL_set_fd(info->ssl, static_cast<int>(info->socket));
477 if (status == 0)
478 return UPNP_E_SOCKET_ERROR;
479
480 UPNPLIB_SCOPED_NO_SIGPIPE;
481 status = SSL_connect(info->ssl);
482 if (status != 1)
483 return UPNP_E_SOCKET_ERROR;
484
485 return UPNP_E_SUCCESS;
486}
487#endif
488
489int sock_destroy(SOCKINFO* info, int ShutdownMethod) {
490 TRACE("Executing sock_destroy()")
491 int ret{UPNP_E_SUCCESS};
492
493 if (info->socket != INVALID_SOCKET) {
494#ifdef UPnPsdk_HAVE_OPENSSL
495 if (info->ssl) {
496 SSL_shutdown(info->ssl);
497 SSL_free(info->ssl);
498 info->ssl = NULL;
499 }
500#endif
501 UPnPsdk::CSocketErr sockerrObj;
502 if (umock::sys_socket_h.shutdown(info->socket, ShutdownMethod) ==
503 SOCKET_ERROR) {
504 sockerrObj.catch_error();
505 std::string msg = "syscall ::shutdown() returned \"" +
506 sockerrObj.error_str() + "\".\n";
507 if (sockerrObj == ENOTCONNP) {
508 // shutdown a not connected connection is not an error.
509 UPnPsdk_LOGINFO("MSG1010") << msg;
510 } else {
511 UPnPsdk_LOGERR("MSG1089") << msg;
513 }
514 }
515 if (sock_close(info->socket) != 0) {
517 }
518 info->socket = INVALID_SOCKET;
519 }
520
521 return ret;
522}
523
524
525int sock_read(SOCKINFO* info, char* buffer, size_t bufsize, int* timeoutSecs) {
526 TRACE("Executing sock_read()")
527 if (info == nullptr) {
528 return UPNP_E_SOCKET_ERROR;
529 }
530#ifdef UPnPsdk_HAVE_OPENSSL
531 if (info->ssl) {
532 return sock_read_ssl(info, buffer /*read_buffer*/,
533 nullptr /*write_buffer*/, bufsize, timeoutSecs);
534 } else
535#endif
536 return sock_read_unprotected(info, buffer /*read_buffer*/,
537 nullptr /*write_buffer*/, bufsize,
538 timeoutSecs);
539}
540
541int sock_write(SOCKINFO* info, const char* buffer, size_t bufsize,
542 int* timeoutSecs) {
543 TRACE("Executing sock_write()")
544 if (info == nullptr)
545 return UPNP_E_SOCKET_ERROR;
546#ifdef UPnPsdk_HAVE_OPENSSL
547 if (info->ssl)
548 return sock_write_ssl(info, nullptr /*read_buffer*/,
549 buffer /*write_buffer*/, bufsize, timeoutSecs);
550 else
551#endif
552 return sock_write_unprotected(info, nullptr /*read_buffer*/,
553 buffer /*write_buffer*/, bufsize,
554 timeoutSecs);
555}
556
557int sock_make_blocking(SOCKET sock) {
558 // returns 0 if successful, else SOCKET_ERROR.
559 TRACE("Executing sock_make_blocking()")
560#ifdef _WIN32
561 u_long val = 0;
562 // returns 0 if successful, else SOCKET_ERROR.
563 return ioctlsocket(sock, FIONBIO, &val);
564#else
565 int val = fcntl(sock, F_GETFL, 0);
566 if (fcntl(sock, F_SETFL, val & ~O_NONBLOCK) == -1) {
567 return SOCKET_ERROR;
568 }
569 return 0;
570#endif
571}
572
573int sock_make_no_blocking(SOCKET sock) {
574 // returns 0 if successful, else SOCKET_ERROR.
575 TRACE("Executing sock_make_no_blocking()")
576#ifdef _WIN32
577 u_long val = 1;
578 // returns 0 if successful, else SOCKET_ERROR.
579 return ioctlsocket(sock, FIONBIO, &val);
580#else /* _WIN32 */
581 int val = fcntl(sock, F_GETFL, 0);
582 if (fcntl(sock, F_SETFL, val | O_NONBLOCK) == -1) {
583 return SOCKET_ERROR;
584 }
585 return 0;
586#endif /* _WIN32 */
587}
588
589#ifdef UPnPsdk_HAVE_OPENSSL
590int UpnpInitSslContext([[maybe_unused]] int initOpenSslLib,
591 const SSL_METHOD* sslMethod) {
592 if (gSslCtx)
593 return UPNP_E_INIT;
594#if 0 // next three OpenSSL functions are deprecated due to its man pages and
595 // should not be used since OpenSSL 1.1.0. Initialization is done
596 // automatically now. --Ingo
597 if (initOpenSslLib) {
598 SSL_load_error_strings();
599 SSL_library_init();
600 OpenSSL_add_all_algorithms();
601 }
602#endif
603 gSslCtx = SSL_CTX_new(sslMethod);
604 if (!gSslCtx) {
605 return UPNP_E_INIT_FAILED;
606 }
607 return UPNP_E_SUCCESS;
608}
609
611 if (gSslCtx) {
612 SSL_CTX_free(gSslCtx);
613 gSslCtx = nullptr;
614 }
615}
616#endif
Class for portable handling of network socket errors.
Definition socket.hpp:537
void catch_error()
Catch error for later use.
Definition socket.cpp:609
std::string error_str() const
Get human readable error description of the catched error.
Definition socket.cpp:618
Declaration of common used classes and free functions for network connections.
int UpnpInitSslContext(int initOpenSslLib, const SSL_METHOD *sslMethod)
Initializes the OpenSSL library, and the OpenSSL context.
Definition sock.cpp:590
#define UPNP_E_SOCKET_ERROR
Generic socket error code for conditions not covered by other error codes.
Definition messages.hpp:243
#define UPNP_E_TIMEDOUT
Too much time elapsed before the required number of bytes were sent or received over a socket.
Definition messages.hpp:235
#define UPNP_E_SUCCESS
The operation completed successfully.
Definition messages.hpp:27
#define UPNP_E_SOCKET_WRITE
An error happened while writing to a socket.
Definition messages.hpp:179
#define UPNP_E_INIT_FAILED
UpnpInit2 cannot complete.
Definition messages.hpp:128
#define UPNP_E_INVALID_PARAM
One or more of the parameters passed to the function is not valid.
Definition messages.hpp:40
#define UPNP_E_INIT
The SDK has already been initialized.
Definition messages.hpp:65
int sock_write_ssl(const SOCKINFO *a_info, char *a_readbuf, const char *a_writebuf, const size_t a_bufsize, int *a_timeoutSecs)
Write to an SSL protected socket.
Definition sock.cpp:351
int sock_write_unprotected(const SOCKINFO *a_info, char *a_readbuf, const char *a_writebuf, const size_t a_bufsize, int *a_timeoutSecs)
Write to a not SSL protected socket.
Definition sock.cpp:164
int sock_read_ssl(const SOCKINFO *a_info, char *a_readbuf, const char *a_writebuf, const size_t a_bufsize, int *a_timeoutSecs)
Read from an SSL protected socket.
Definition sock.cpp:264
int sock_read_unprotected(const SOCKINFO *a_info, char *a_readbuf, const char *a_writebuf, const size_t a_bufsize, int *a_timeoutSecs)
Read from a not SSL protected socket.
Definition sock.cpp:79
SSL_CTX * gSslCtx
Pointer to an SSL Context.
Definition sock.cpp:63
int sock_make_blocking(SOCKET sock)
Make socket blocking.
Definition sock.cpp:557
int sock_ssl_connect(SOCKINFO *info)
Associates an SSL object with the socket and begins the client-side SSL/TLS handshake.
Definition sock.cpp:469
int sock_make_no_blocking(SOCKET sock)
Make socket non-blocking.
Definition sock.cpp:573
int sock_destroy(SOCKINFO *info, int ShutdownMethod)
Shutsdown the socket using the ShutdownMethod to indicate whether sends and receives on the socket wi...
Definition sock.cpp:489
void freeSslCtx()
Free the OpenSSL context.
Definition sock.cpp:610
int sock_read(SOCKINFO *info, char *buffer, size_t bufsize, int *timeoutSecs)
Reads data on socket in sockinfo.
Definition sock.cpp:525
int sock_write(SOCKINFO *info, const char *buffer, size_t bufsize, int *timeoutSecs)
Writes data on the socket in sockinfo.
Definition sock.cpp:541
int sock_init_with_ip(SOCKINFO *info, SOCKET sockfd, sockaddr *foreign_sockaddr)
Calls the sock_init function and assigns the passed in IP address and port to the IP address and port...
Definition sock.cpp:451
int sock_init(SOCKINFO *info, SOCKET sockfd)
Assign the passed in socket descriptor to socket descriptor in the SOCKINFO structure.
Definition sock.cpp:440
Manage network sockets and connections.
SOCKET socket
Handle/descriptor to a socket.
Definition sock.hpp:67
SSL * ssl
Information about an ssl connection only filled in incoming requests.
Definition sock.hpp:72
sockaddr_storage foreign_sockaddr
Socket address of the remote node only filled in incoming requests.
Definition sock.hpp:69
int sock_close(SOCKET sock)
Closes the socket if it is different from -1.
Definition sock.hpp:85
Additional socket information for connections and ssl.
Definition sock.hpp:65
Socket Module: manage properties and methods but not connections of ONE network socket to handle IPv4...