summaryrefslogtreecommitdiffstats
path: root/libfreerdp/core/tcp.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
commita9bcc81f821d7c66f623779fa5147e728eb3c388 (patch)
tree98676963bcdd537ae5908a067a8eb110b93486a6 /libfreerdp/core/tcp.c
parentInitial commit. (diff)
downloadfreerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz
freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libfreerdp/core/tcp.c')
-rw-r--r--libfreerdp/core/tcp.c1294
1 files changed, 1294 insertions, 0 deletions
diff --git a/libfreerdp/core/tcp.c b/libfreerdp/core/tcp.c
new file mode 100644
index 0000000..f9e216f
--- /dev/null
+++ b/libfreerdp/core/tcp.c
@@ -0,0 +1,1294 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Transmission Control Protocol (TCP)
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include "settings.h"
+
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <winpr/crt.h>
+#include <winpr/platform.h>
+#include <winpr/winsock.h>
+
+#include "rdp.h"
+#include "utils.h"
+
+#if !defined(_WIN32)
+
+#include <netdb.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#ifdef WINPR_HAVE_POLL_H
+#include <poll.h>
+#else
+#include <time.h>
+#include <sys/select.h>
+#endif
+
+#if defined(__FreeBSD__) || defined(__OpenBSD__)
+#ifndef SOL_TCP
+#define SOL_TCP IPPROTO_TCP
+#endif
+#endif
+
+#ifdef __APPLE__
+#ifndef SOL_TCP
+#define SOL_TCP IPPROTO_TCP
+#endif
+#ifndef TCP_KEEPIDLE
+#define TCP_KEEPIDLE TCP_KEEPALIVE
+#endif
+#endif
+
+#else
+
+#include <winpr/windows.h>
+
+#include <winpr/crt.h>
+
+#define SHUT_RDWR SD_BOTH
+#define close(_fd) closesocket(_fd)
+
+#endif
+
+#include <freerdp/log.h>
+
+#include <winpr/stream.h>
+
+#include "tcp.h"
+#include "../crypto/opensslcompat.h"
+
+#if defined(HAVE_AF_VSOCK_H)
+#include <ctype.h>
+#include <linux/vm_sockets.h>
+#endif
+
+#define TAG FREERDP_TAG("core")
+
+/* Simple Socket BIO */
+
+typedef struct
+{
+ SOCKET socket;
+ HANDLE hEvent;
+} WINPR_BIO_SIMPLE_SOCKET;
+
+static int transport_bio_simple_init(BIO* bio, SOCKET socket, int shutdown);
+static int transport_bio_simple_uninit(BIO* bio);
+
+static long transport_bio_simple_callback(BIO* bio, int mode, const char* argp, int argi, long argl,
+ long ret)
+{
+ return 1;
+}
+
+static int transport_bio_simple_write(BIO* bio, const char* buf, int size)
+{
+ int error = 0;
+ int status = 0;
+ WINPR_BIO_SIMPLE_SOCKET* ptr = (WINPR_BIO_SIMPLE_SOCKET*)BIO_get_data(bio);
+
+ if (!buf)
+ return 0;
+
+ BIO_clear_flags(bio, BIO_FLAGS_WRITE);
+ status = _send(ptr->socket, buf, size, 0);
+
+ if (status <= 0)
+ {
+ error = WSAGetLastError();
+
+ if ((error == WSAEWOULDBLOCK) || (error == WSAEINTR) || (error == WSAEINPROGRESS) ||
+ (error == WSAEALREADY))
+ {
+ BIO_set_flags(bio, (BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY));
+ }
+ else
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ }
+ }
+
+ return status;
+}
+
+static int transport_bio_simple_read(BIO* bio, char* buf, int size)
+{
+ int error = 0;
+ int status = 0;
+ WINPR_BIO_SIMPLE_SOCKET* ptr = (WINPR_BIO_SIMPLE_SOCKET*)BIO_get_data(bio);
+
+ if (!buf)
+ return 0;
+
+ BIO_clear_flags(bio, BIO_FLAGS_READ);
+ WSAResetEvent(ptr->hEvent);
+ status = _recv(ptr->socket, buf, size, 0);
+
+ if (status > 0)
+ {
+ return status;
+ }
+
+ if (status == 0)
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ return 0;
+ }
+
+ error = WSAGetLastError();
+
+ if ((error == WSAEWOULDBLOCK) || (error == WSAEINTR) || (error == WSAEINPROGRESS) ||
+ (error == WSAEALREADY))
+ {
+ BIO_set_flags(bio, (BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY));
+ }
+ else
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ }
+
+ return -1;
+}
+
+static int transport_bio_simple_puts(BIO* bio, const char* str)
+{
+ return 1;
+}
+
+static int transport_bio_simple_gets(BIO* bio, char* str, int size)
+{
+ return 1;
+}
+
+static long transport_bio_simple_ctrl(BIO* bio, int cmd, long arg1, void* arg2)
+{
+ int status = -1;
+ WINPR_BIO_SIMPLE_SOCKET* ptr = (WINPR_BIO_SIMPLE_SOCKET*)BIO_get_data(bio);
+
+ switch (cmd)
+ {
+ case BIO_C_SET_SOCKET:
+ transport_bio_simple_uninit(bio);
+ transport_bio_simple_init(bio, (SOCKET)arg2, (int)arg1);
+ return 1;
+ case BIO_C_GET_SOCKET:
+ if (!BIO_get_init(bio) || !arg2)
+ return 0;
+
+ *((SOCKET*)arg2) = ptr->socket;
+ return 1;
+ case BIO_C_GET_EVENT:
+ if (!BIO_get_init(bio) || !arg2)
+ return 0;
+
+ *((HANDLE*)arg2) = ptr->hEvent;
+ return 1;
+ case BIO_C_SET_NONBLOCK:
+ {
+#ifndef _WIN32
+ int flags = 0;
+ flags = fcntl((int)ptr->socket, F_GETFL);
+
+ if (flags == -1)
+ return 0;
+
+ if (arg1)
+ fcntl((int)ptr->socket, F_SETFL, flags | O_NONBLOCK);
+ else
+ fcntl((int)ptr->socket, F_SETFL, flags & ~(O_NONBLOCK));
+
+#else
+ /* the internal socket is always non-blocking */
+#endif
+ return 1;
+ }
+ case BIO_C_WAIT_READ:
+ {
+ int timeout = (int)arg1;
+ int sockfd = (int)ptr->socket;
+#ifdef WINPR_HAVE_POLL_H
+ struct pollfd pollset;
+ pollset.fd = sockfd;
+ pollset.events = POLLIN;
+ pollset.revents = 0;
+
+ do
+ {
+ status = poll(&pollset, 1, timeout);
+ } while ((status < 0) && (errno == EINTR));
+
+#else
+ fd_set rset;
+ struct timeval tv;
+ FD_ZERO(&rset);
+ FD_SET(sockfd, &rset);
+
+ if (timeout)
+ {
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+ }
+
+ do
+ {
+ status = select(sockfd + 1, &rset, NULL, NULL, timeout ? &tv : NULL);
+ } while ((status < 0) && (errno == EINTR));
+
+#endif
+ }
+ break;
+ case BIO_C_WAIT_WRITE:
+ {
+ int timeout = (int)arg1;
+ int sockfd = (int)ptr->socket;
+#ifdef WINPR_HAVE_POLL_H
+ struct pollfd pollset;
+ pollset.fd = sockfd;
+ pollset.events = POLLOUT;
+ pollset.revents = 0;
+
+ do
+ {
+ status = poll(&pollset, 1, timeout);
+ } while ((status < 0) && (errno == EINTR));
+
+#else
+ fd_set rset;
+ struct timeval tv;
+ FD_ZERO(&rset);
+ FD_SET(sockfd, &rset);
+
+ if (timeout)
+ {
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+ }
+
+ do
+ {
+ status = select(sockfd + 1, NULL, &rset, NULL, timeout ? &tv : NULL);
+ } while ((status < 0) && (errno == EINTR));
+
+#endif
+ }
+ break;
+ default:
+ break;
+ }
+
+ switch (cmd)
+ {
+ case BIO_C_SET_FD:
+ if (arg2)
+ {
+ transport_bio_simple_uninit(bio);
+ transport_bio_simple_init(bio, (SOCKET) * ((int*)arg2), (int)arg1);
+ status = 1;
+ }
+
+ break;
+
+ case BIO_C_GET_FD:
+ if (BIO_get_init(bio))
+ {
+ if (arg2)
+ *((int*)arg2) = (int)ptr->socket;
+
+ status = (int)ptr->socket;
+ }
+
+ break;
+
+ case BIO_CTRL_GET_CLOSE:
+ status = BIO_get_shutdown(bio);
+ break;
+
+ case BIO_CTRL_SET_CLOSE:
+ BIO_set_shutdown(bio, (int)arg1);
+ status = 1;
+ break;
+
+ case BIO_CTRL_DUP:
+ status = 1;
+ break;
+
+ case BIO_CTRL_FLUSH:
+ status = 1;
+ break;
+
+ default:
+ status = 0;
+ break;
+ }
+
+ return status;
+}
+
+static int transport_bio_simple_init(BIO* bio, SOCKET socket, int shutdown)
+{
+ WINPR_BIO_SIMPLE_SOCKET* ptr = (WINPR_BIO_SIMPLE_SOCKET*)BIO_get_data(bio);
+ ptr->socket = socket;
+ BIO_set_shutdown(bio, shutdown);
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ BIO_set_init(bio, 1);
+ ptr->hEvent = WSACreateEvent();
+
+ if (!ptr->hEvent)
+ return 0;
+
+ /* WSAEventSelect automatically sets the socket in non-blocking mode */
+ if (WSAEventSelect(ptr->socket, ptr->hEvent, FD_READ | FD_ACCEPT | FD_CLOSE))
+ {
+ WLog_ERR(TAG, "WSAEventSelect returned 0x%08X", WSAGetLastError());
+ return 0;
+ }
+
+ return 1;
+}
+
+static int transport_bio_simple_uninit(BIO* bio)
+{
+ WINPR_BIO_SIMPLE_SOCKET* ptr = (WINPR_BIO_SIMPLE_SOCKET*)BIO_get_data(bio);
+
+ if (BIO_get_shutdown(bio))
+ {
+ if (BIO_get_init(bio) && ptr)
+ {
+ _shutdown(ptr->socket, SD_BOTH);
+ closesocket(ptr->socket);
+ ptr->socket = 0;
+ }
+ }
+
+ if (ptr && ptr->hEvent)
+ {
+ CloseHandle(ptr->hEvent);
+ ptr->hEvent = NULL;
+ }
+
+ BIO_set_init(bio, 0);
+ BIO_set_flags(bio, 0);
+ return 1;
+}
+
+static int transport_bio_simple_new(BIO* bio)
+{
+ WINPR_BIO_SIMPLE_SOCKET* ptr = NULL;
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ ptr = (WINPR_BIO_SIMPLE_SOCKET*)calloc(1, sizeof(WINPR_BIO_SIMPLE_SOCKET));
+
+ if (!ptr)
+ return 0;
+
+ BIO_set_data(bio, ptr);
+ return 1;
+}
+
+static int transport_bio_simple_free(BIO* bio)
+{
+ WINPR_BIO_SIMPLE_SOCKET* ptr = NULL;
+
+ if (!bio)
+ return 0;
+
+ transport_bio_simple_uninit(bio);
+ ptr = (WINPR_BIO_SIMPLE_SOCKET*)BIO_get_data(bio);
+
+ if (ptr)
+ {
+ BIO_set_data(bio, NULL);
+ free(ptr);
+ }
+
+ return 1;
+}
+
+BIO_METHOD* BIO_s_simple_socket(void)
+{
+ static BIO_METHOD* bio_methods = NULL;
+
+ if (bio_methods == NULL)
+ {
+ if (!(bio_methods = BIO_meth_new(BIO_TYPE_SIMPLE, "SimpleSocket")))
+ return NULL;
+
+ BIO_meth_set_write(bio_methods, transport_bio_simple_write);
+ BIO_meth_set_read(bio_methods, transport_bio_simple_read);
+ BIO_meth_set_puts(bio_methods, transport_bio_simple_puts);
+ BIO_meth_set_gets(bio_methods, transport_bio_simple_gets);
+ BIO_meth_set_ctrl(bio_methods, transport_bio_simple_ctrl);
+ BIO_meth_set_create(bio_methods, transport_bio_simple_new);
+ BIO_meth_set_destroy(bio_methods, transport_bio_simple_free);
+ }
+
+ return bio_methods;
+}
+
+/* Buffered Socket BIO */
+
+typedef struct
+{
+ BIO* bufferedBio;
+ BOOL readBlocked;
+ BOOL writeBlocked;
+ RingBuffer xmitBuffer;
+} WINPR_BIO_BUFFERED_SOCKET;
+
+static long transport_bio_buffered_callback(BIO* bio, int mode, const char* argp, int argi,
+ long argl, long ret)
+{
+ return 1;
+}
+
+static int transport_bio_buffered_write(BIO* bio, const char* buf, int num)
+{
+ int ret = num;
+ int nchunks = 0;
+ size_t committedBytes = 0;
+ DataChunk chunks[2] = { 0 };
+ WINPR_BIO_BUFFERED_SOCKET* ptr = (WINPR_BIO_BUFFERED_SOCKET*)BIO_get_data(bio);
+ BIO* next_bio = NULL;
+
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(ptr);
+
+ ptr->writeBlocked = FALSE;
+ BIO_clear_flags(bio, BIO_FLAGS_WRITE);
+
+ /* we directly append extra bytes in the xmit buffer, this could be prevented
+ * but for now it makes the code more simple.
+ */
+ if (buf && num && !ringbuffer_write(&ptr->xmitBuffer, (const BYTE*)buf, num))
+ {
+ WLog_ERR(TAG, "an error occurred when writing (num: %d)", num);
+ return -1;
+ }
+
+ nchunks = ringbuffer_peek(&ptr->xmitBuffer, chunks, ringbuffer_used(&ptr->xmitBuffer));
+ next_bio = BIO_next(bio);
+
+ for (int i = 0; i < nchunks; i++)
+ {
+ while (chunks[i].size)
+ {
+ ERR_clear_error();
+ const int status = BIO_write(next_bio, chunks[i].data, chunks[i].size);
+
+ if (status <= 0)
+ {
+ if (!BIO_should_retry(next_bio))
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ ret = -1; /* fatal error */
+ goto out;
+ }
+
+ if (BIO_should_write(next_bio))
+ {
+ BIO_set_flags(bio, BIO_FLAGS_WRITE);
+ ptr->writeBlocked = TRUE;
+ goto out; /* EWOULDBLOCK */
+ }
+ }
+ else
+ {
+ committedBytes += (size_t)status;
+ chunks[i].size -= (size_t)status;
+ chunks[i].data += status;
+ }
+ }
+ }
+
+out:
+ ringbuffer_commit_read_bytes(&ptr->xmitBuffer, committedBytes);
+ return ret;
+}
+
+static int transport_bio_buffered_read(BIO* bio, char* buf, int size)
+{
+ int status = 0;
+ WINPR_BIO_BUFFERED_SOCKET* ptr = (WINPR_BIO_BUFFERED_SOCKET*)BIO_get_data(bio);
+ BIO* next_bio = BIO_next(bio);
+ ptr->readBlocked = FALSE;
+ BIO_clear_flags(bio, BIO_FLAGS_READ);
+ ERR_clear_error();
+ status = BIO_read(next_bio, buf, size);
+
+ if (status <= 0)
+ {
+ if (!BIO_should_retry(next_bio))
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ goto out;
+ }
+
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+
+ if (BIO_should_read(next_bio))
+ {
+ BIO_set_flags(bio, BIO_FLAGS_READ);
+ ptr->readBlocked = TRUE;
+ goto out;
+ }
+ }
+
+out:
+ return status;
+}
+
+static int transport_bio_buffered_puts(BIO* bio, const char* str)
+{
+ return 1;
+}
+
+static int transport_bio_buffered_gets(BIO* bio, char* str, int size)
+{
+ return 1;
+}
+
+static long transport_bio_buffered_ctrl(BIO* bio, int cmd, long arg1, void* arg2)
+{
+ long status = -1;
+ WINPR_BIO_BUFFERED_SOCKET* ptr = (WINPR_BIO_BUFFERED_SOCKET*)BIO_get_data(bio);
+
+ switch (cmd)
+ {
+ case BIO_CTRL_FLUSH:
+ if (!ringbuffer_used(&ptr->xmitBuffer))
+ status = 1;
+ else
+ status = (transport_bio_buffered_write(bio, NULL, 0) >= 0) ? 1 : -1;
+
+ break;
+
+ case BIO_CTRL_WPENDING:
+ status = ringbuffer_used(&ptr->xmitBuffer);
+ break;
+
+ case BIO_CTRL_PENDING:
+ status = 0;
+ break;
+
+ case BIO_C_READ_BLOCKED:
+ status = (int)ptr->readBlocked;
+ break;
+
+ case BIO_C_WRITE_BLOCKED:
+ status = (int)ptr->writeBlocked;
+ break;
+
+ default:
+ status = BIO_ctrl(BIO_next(bio), cmd, arg1, arg2);
+ break;
+ }
+
+ return status;
+}
+
+static int transport_bio_buffered_new(BIO* bio)
+{
+ WINPR_BIO_BUFFERED_SOCKET* ptr = NULL;
+ BIO_set_init(bio, 1);
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ ptr = (WINPR_BIO_BUFFERED_SOCKET*)calloc(1, sizeof(WINPR_BIO_BUFFERED_SOCKET));
+
+ if (!ptr)
+ return -1;
+
+ BIO_set_data(bio, (void*)ptr);
+
+ if (!ringbuffer_init(&ptr->xmitBuffer, 0x10000))
+ return -1;
+
+ return 1;
+}
+
+/* Free the buffered BIO.
+ * Do not free other elements in the BIO stack,
+ * let BIO_free_all handle that. */
+static int transport_bio_buffered_free(BIO* bio)
+{
+ WINPR_BIO_BUFFERED_SOCKET* ptr = (WINPR_BIO_BUFFERED_SOCKET*)BIO_get_data(bio);
+
+ if (!ptr)
+ return 0;
+
+ ringbuffer_destroy(&ptr->xmitBuffer);
+ free(ptr);
+ return 1;
+}
+
+BIO_METHOD* BIO_s_buffered_socket(void)
+{
+ static BIO_METHOD* bio_methods = NULL;
+
+ if (bio_methods == NULL)
+ {
+ if (!(bio_methods = BIO_meth_new(BIO_TYPE_BUFFERED, "BufferedSocket")))
+ return NULL;
+
+ BIO_meth_set_write(bio_methods, transport_bio_buffered_write);
+ BIO_meth_set_read(bio_methods, transport_bio_buffered_read);
+ BIO_meth_set_puts(bio_methods, transport_bio_buffered_puts);
+ BIO_meth_set_gets(bio_methods, transport_bio_buffered_gets);
+ BIO_meth_set_ctrl(bio_methods, transport_bio_buffered_ctrl);
+ BIO_meth_set_create(bio_methods, transport_bio_buffered_new);
+ BIO_meth_set_destroy(bio_methods, transport_bio_buffered_free);
+ }
+
+ return bio_methods;
+}
+
+char* freerdp_tcp_address_to_string(const struct sockaddr_storage* addr, BOOL* pIPv6)
+{
+ char ipAddress[INET6_ADDRSTRLEN + 1] = { 0 };
+ const struct sockaddr_in6* sockaddr_ipv6 = (const struct sockaddr_in6*)addr;
+ const struct sockaddr_in* sockaddr_ipv4 = (const struct sockaddr_in*)addr;
+
+ if (addr == NULL)
+ {
+ return NULL;
+ }
+
+ switch (sockaddr_ipv4->sin_family)
+ {
+ case AF_INET:
+ if (!inet_ntop(sockaddr_ipv4->sin_family, &sockaddr_ipv4->sin_addr, ipAddress,
+ sizeof(ipAddress)))
+ return NULL;
+
+ break;
+
+ case AF_INET6:
+ if (!inet_ntop(sockaddr_ipv6->sin6_family, &sockaddr_ipv6->sin6_addr, ipAddress,
+ sizeof(ipAddress)))
+ return NULL;
+
+ break;
+
+ case AF_UNIX:
+ sprintf_s(ipAddress, ARRAYSIZE(ipAddress), "127.0.0.1");
+ break;
+
+ default:
+ return NULL;
+ }
+
+ if (pIPv6 != NULL)
+ {
+ *pIPv6 = (sockaddr_ipv4->sin_family == AF_INET6);
+ }
+
+ return _strdup(ipAddress);
+}
+
+static char* freerdp_tcp_get_ip_address(int sockfd, BOOL* pIPv6)
+{
+ struct sockaddr_storage saddr = { 0 };
+ socklen_t length = sizeof(struct sockaddr_storage);
+
+ if (getsockname(sockfd, (struct sockaddr*)&saddr, &length) != 0)
+ {
+ return NULL;
+ }
+
+ return freerdp_tcp_address_to_string(&saddr, pIPv6);
+}
+
+char* freerdp_tcp_get_peer_address(SOCKET sockfd)
+{
+ struct sockaddr_storage saddr = { 0 };
+ socklen_t length = sizeof(struct sockaddr_storage);
+
+ if (getpeername(sockfd, (struct sockaddr*)&saddr, &length) != 0)
+ {
+ return NULL;
+ }
+
+ return freerdp_tcp_address_to_string(&saddr, NULL);
+}
+
+static int freerdp_uds_connect(const char* path)
+{
+#ifndef _WIN32
+ int status = 0;
+ int sockfd = 0;
+ struct sockaddr_un addr = { 0 };
+ sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ if (sockfd == -1)
+ {
+ WLog_ERR(TAG, "socket");
+ return -1;
+ }
+
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
+ status = connect(sockfd, (struct sockaddr*)&addr, sizeof(addr));
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "connect");
+ close(sockfd);
+ return -1;
+ }
+
+ return sockfd;
+#else /* ifndef _WIN32 */
+ return -1;
+#endif
+}
+
+struct addrinfo* freerdp_tcp_resolve_host(const char* hostname, int port, int ai_flags)
+{
+ char* service = NULL;
+ char port_str[16];
+ int status = 0;
+ struct addrinfo hints = { 0 };
+ struct addrinfo* result = NULL;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = ai_flags;
+
+ if (port >= 0)
+ {
+ sprintf_s(port_str, sizeof(port_str) - 1, "%d", port);
+ service = port_str;
+ }
+
+ status = getaddrinfo(hostname, service, &hints, &result);
+
+ if (status)
+ return NULL;
+
+ return result;
+}
+
+static BOOL freerdp_tcp_is_hostname_resolvable(rdpContext* context, const char* hostname)
+{
+ struct addrinfo* result = freerdp_tcp_resolve_host(hostname, -1, 0);
+
+ if (!result)
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_DNS_NAME_NOT_FOUND);
+
+ return FALSE;
+ }
+
+ freerdp_set_last_error_log(context, 0);
+ freeaddrinfo(result);
+ return TRUE;
+}
+
+static BOOL freerdp_tcp_connect_timeout(rdpContext* context, int sockfd, struct sockaddr* addr,
+ socklen_t addrlen, UINT32 timeout)
+{
+ BOOL rc = FALSE;
+ HANDLE handles[2];
+ int status = 0;
+ int count = 0;
+ u_long arg = 0;
+ DWORD tout = (timeout > 0) ? timeout : INFINITE;
+
+ handles[count] = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!handles[count])
+ return FALSE;
+
+ status = WSAEventSelect(sockfd, handles[count++], FD_READ | FD_WRITE | FD_CONNECT | FD_CLOSE);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "WSAEventSelect failed with %d", WSAGetLastError());
+ goto fail;
+ }
+
+ handles[count++] = utils_get_abort_event(context->rdp);
+ status = _connect(sockfd, addr, addrlen);
+
+ if (status < 0)
+ {
+ status = WSAGetLastError();
+
+ switch (status)
+ {
+ case WSAEINPROGRESS:
+ case WSAEWOULDBLOCK:
+ break;
+
+ default:
+ goto fail;
+ }
+ }
+
+ status = WaitForMultipleObjects(count, handles, FALSE, tout);
+
+ if (WAIT_OBJECT_0 != status)
+ goto fail;
+
+ status = recv(sockfd, NULL, 0, 0);
+
+ if (status == SOCKET_ERROR)
+ {
+ if (WSAGetLastError() == WSAECONNRESET)
+ goto fail;
+ }
+
+ status = WSAEventSelect(sockfd, handles[0], 0);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "WSAEventSelect failed with %d", WSAGetLastError());
+ goto fail;
+ }
+
+ if (_ioctlsocket(sockfd, FIONBIO, &arg) != 0)
+ goto fail;
+
+ rc = TRUE;
+fail:
+ CloseHandle(handles[0]);
+ return rc;
+}
+
+typedef struct
+{
+ SOCKET s;
+ struct addrinfo* addr;
+ struct addrinfo* result;
+} t_peer;
+
+static void peer_free(t_peer* peer)
+{
+ if (peer->s != INVALID_SOCKET)
+ closesocket(peer->s);
+
+ freeaddrinfo(peer->addr);
+ memset(peer, 0, sizeof(t_peer));
+ peer->s = INVALID_SOCKET;
+}
+
+static int freerdp_tcp_connect_multi(rdpContext* context, char** hostnames, UINT32* ports,
+ UINT32 count, UINT16 port, UINT32 timeout)
+{
+ UINT32 sindex = count;
+ int status = -1;
+ SOCKET sockfd = INVALID_SOCKET;
+ HANDLE* events = NULL;
+ struct addrinfo* addr = NULL;
+ struct addrinfo* result = NULL;
+ t_peer* peers = NULL;
+ events = (HANDLE*)calloc(count + 1, sizeof(HANDLE));
+ peers = (t_peer*)calloc(count, sizeof(t_peer));
+
+ if (!peers || !events || (count < 1))
+ {
+ free(peers);
+ free(events);
+ return -1;
+ }
+
+ for (UINT32 index = 0; index < count; index++)
+ {
+ int curPort = port;
+
+ if (ports)
+ curPort = ports[index];
+
+ result = freerdp_tcp_resolve_host(hostnames[index], curPort, 0);
+
+ if (!result)
+ continue;
+
+ addr = result;
+
+ if ((addr->ai_family == AF_INET6) && (addr->ai_next != 0))
+ {
+ while ((addr = addr->ai_next))
+ {
+ if (addr->ai_family == AF_INET)
+ break;
+ }
+
+ if (!addr)
+ addr = result;
+ }
+
+ peers[index].s = _socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
+
+ if (peers[index].s == INVALID_SOCKET)
+ {
+ freeaddrinfo(result);
+ continue;
+ }
+
+ peers[index].addr = addr;
+ peers[index].result = result;
+ }
+
+ for (UINT32 index = 0; index < count; index++)
+ {
+ sockfd = peers[index].s;
+ addr = peers[index].addr;
+
+ if ((sockfd == INVALID_SOCKET) || (!addr))
+ continue;
+
+ /* blocking tcp connect */
+ status = _connect(sockfd, addr->ai_addr, addr->ai_addrlen);
+
+ if (status >= 0)
+ {
+ /* connection success */
+ sindex = index;
+ break;
+ }
+ }
+
+ if (sindex < count)
+ {
+ sockfd = peers[sindex].s;
+ peers[sindex].s = INVALID_SOCKET;
+ }
+ else
+ freerdp_set_last_error_log(context, FREERDP_ERROR_CONNECT_CANCELLED);
+
+ for (UINT32 index = 0; index < count; index++)
+ peer_free(&peers[index]);
+
+ free(peers);
+ free(events);
+ return sockfd;
+}
+
+BOOL freerdp_tcp_set_keep_alive_mode(const rdpSettings* settings, int sockfd)
+{
+ const BOOL keepalive = (freerdp_settings_get_bool(settings, FreeRDP_TcpKeepAlive));
+ UINT32 optval = 0;
+ socklen_t optlen = 0;
+ optval = keepalive ? 1 : 0;
+ optlen = sizeof(optval);
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&optval, optlen) < 0)
+ {
+ WLog_WARN(TAG, "setsockopt() SOL_SOCKET, SO_KEEPALIVE");
+ }
+
+#ifndef _WIN32
+#ifdef TCP_KEEPIDLE
+ optval = keepalive ? freerdp_settings_get_uint32(settings, FreeRDP_TcpKeepAliveDelay) : 0;
+ optlen = sizeof(optval);
+
+ if (setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, (void*)&optval, optlen) < 0)
+ {
+ WLog_WARN(TAG, "setsockopt() IPPROTO_TCP, TCP_KEEPIDLE");
+ }
+
+#endif
+#ifndef SOL_TCP
+ /* "tcp" from /etc/protocols as getprotobyname(3C) */
+#define SOL_TCP 6
+#endif
+#ifdef TCP_KEEPCNT
+ optval = keepalive ? freerdp_settings_get_uint32(settings, FreeRDP_TcpKeepAliveRetries) : 0;
+ optlen = sizeof(optval);
+
+ if (setsockopt(sockfd, SOL_TCP, TCP_KEEPCNT, (void*)&optval, optlen) < 0)
+ {
+ WLog_WARN(TAG, "setsockopt() SOL_TCP, TCP_KEEPCNT");
+ }
+
+#endif
+#ifdef TCP_KEEPINTVL
+ optval = keepalive ? freerdp_settings_get_uint32(settings, FreeRDP_TcpKeepAliveInterval) : 0;
+ optlen = sizeof(optval);
+
+ if (setsockopt(sockfd, SOL_TCP, TCP_KEEPINTVL, (void*)&optval, optlen) < 0)
+ {
+ WLog_WARN(TAG, "setsockopt() SOL_TCP, TCP_KEEPINTVL");
+ }
+
+#endif
+#endif
+#if defined(__MACOSX__) || defined(__IOS__)
+ optval = 1;
+ optlen = sizeof(optval);
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void*)&optval, optlen) < 0)
+ {
+ WLog_WARN(TAG, "setsockopt() SOL_SOCKET, SO_NOSIGPIPE");
+ }
+
+#endif
+#ifdef TCP_USER_TIMEOUT
+ optval = freerdp_settings_get_uint32(settings, FreeRDP_TcpAckTimeout);
+ optlen = sizeof(optval);
+
+ if (setsockopt(sockfd, SOL_TCP, TCP_USER_TIMEOUT, (void*)&optval, optlen) < 0)
+ {
+ WLog_WARN(TAG, "setsockopt() SOL_TCP, TCP_USER_TIMEOUT");
+ }
+
+#endif
+ return TRUE;
+}
+
+int freerdp_tcp_connect(rdpContext* context, const char* hostname, int port, DWORD timeout)
+{
+ rdpTransport* transport = NULL;
+ if (!context || !context->rdp)
+ return -1;
+ transport = context->rdp->transport;
+ if (!transport)
+ return -1;
+ return transport_tcp_connect(context->rdp->transport, hostname, port, timeout);
+}
+
+int freerdp_tcp_default_connect(rdpContext* context, rdpSettings* settings, const char* hostname,
+ int port, DWORD timeout)
+{
+ int sockfd = 0;
+ UINT32 optval = 0;
+ socklen_t optlen = 0;
+ BOOL ipcSocket = FALSE;
+ BOOL useExternalDefinedSocket = FALSE;
+
+ if (!hostname)
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_FAILED);
+
+ return -1;
+ }
+
+ if (hostname[0] == '/')
+ ipcSocket = TRUE;
+
+ if (hostname[0] == '|')
+ useExternalDefinedSocket = TRUE;
+
+ const char* vsock = utils_is_vsock(hostname);
+ if (ipcSocket)
+ {
+ sockfd = freerdp_uds_connect(hostname);
+
+ if (sockfd < 0)
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_FAILED);
+
+ return -1;
+ }
+ }
+ else if (useExternalDefinedSocket)
+ sockfd = port;
+ else if (vsock)
+ {
+#if defined(HAVE_AF_VSOCK_H)
+ hostname = vsock;
+ sockfd = socket(AF_VSOCK, SOCK_STREAM, 0);
+ struct sockaddr_vm addr = { 0 };
+
+ addr.svm_family = AF_VSOCK;
+ addr.svm_port = port;
+
+ errno = 0;
+ char* ptr = NULL;
+ unsigned long val = strtoul(hostname, &ptr, 10);
+ if (errno || (val > UINT32_MAX))
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "could not extract port from '%s', value=%ul, error=%s", hostname, val,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return -1;
+ }
+ addr.svm_cid = val;
+ if (addr.svm_cid == 2)
+ {
+ addr.svm_flags = VMADDR_FLAG_TO_HOST;
+ }
+ if ((connect(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_vm))) == -1)
+ {
+ WLog_ERR(TAG, "failed to connect to %s", hostname);
+ return -1;
+ }
+#else
+ WLog_ERR(TAG, "Compiled without AF_VSOCK, '%s' not supported", hostname);
+ return -1;
+#endif
+ }
+ else
+ {
+ sockfd = -1;
+
+ if (!settings->GatewayEnabled)
+ {
+ if (!freerdp_tcp_is_hostname_resolvable(context, hostname) ||
+ settings->RemoteAssistanceMode)
+ {
+ if (settings->TargetNetAddressCount > 0)
+ {
+ sockfd = freerdp_tcp_connect_multi(
+ context, settings->TargetNetAddresses, settings->TargetNetPorts,
+ settings->TargetNetAddressCount, port, timeout);
+ }
+ }
+ }
+
+ if (sockfd <= 0)
+ {
+ char* peerAddress = NULL;
+ struct addrinfo* addr = NULL;
+ struct addrinfo* result = NULL;
+
+ result = freerdp_tcp_resolve_host(hostname, port, 0);
+
+ if (!result)
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_DNS_NAME_NOT_FOUND);
+
+ return -1;
+ }
+ freerdp_set_last_error_log(context, 0);
+
+ addr = result;
+
+ if ((addr->ai_family == AF_INET6) && (addr->ai_next != 0) &&
+ !settings->PreferIPv6OverIPv4)
+ {
+ while ((addr = addr->ai_next))
+ {
+ if (addr->ai_family == AF_INET)
+ break;
+ }
+
+ if (!addr)
+ addr = result;
+ }
+
+ sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
+
+ if (sockfd < 0)
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_FAILED);
+
+ freeaddrinfo(result);
+ return -1;
+ }
+
+ if ((peerAddress = freerdp_tcp_address_to_string(
+ (const struct sockaddr_storage*)addr->ai_addr, NULL)) != NULL)
+ {
+ WLog_DBG(TAG, "connecting to peer %s", peerAddress);
+ free(peerAddress);
+ }
+
+ if (!freerdp_tcp_connect_timeout(context, sockfd, addr->ai_addr, addr->ai_addrlen,
+ timeout))
+ {
+ freeaddrinfo(result);
+ close(sockfd);
+
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_FAILED);
+
+ WLog_ERR(TAG, "failed to connect to %s", hostname);
+ return -1;
+ }
+
+ freeaddrinfo(result);
+ }
+ }
+
+ if (!vsock)
+ {
+ free(settings->ClientAddress);
+ settings->ClientAddress = freerdp_tcp_get_ip_address(sockfd, &settings->IPv6Enabled);
+
+ if (!settings->ClientAddress)
+ {
+ if (!useExternalDefinedSocket)
+ close(sockfd);
+
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_FAILED);
+
+ WLog_ERR(TAG, "Couldn't get socket ip address");
+ return -1;
+ }
+ }
+
+ optval = 1;
+ optlen = sizeof(optval);
+
+ if (!ipcSocket && !useExternalDefinedSocket)
+ {
+ if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (void*)&optval, optlen) < 0)
+ WLog_ERR(TAG, "unable to set TCP_NODELAY");
+ }
+
+ /* receive buffer must be a least 32 K */
+ if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (void*)&optval, &optlen) == 0)
+ {
+ if (optval < (1024 * 32))
+ {
+ optval = 1024 * 32;
+ optlen = sizeof(optval);
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (void*)&optval, optlen) < 0)
+ {
+ close(sockfd);
+
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_FAILED);
+
+ WLog_ERR(TAG, "unable to set receive buffer len");
+ return -1;
+ }
+ }
+ }
+
+ if (!ipcSocket && !useExternalDefinedSocket)
+ {
+ if (!freerdp_tcp_set_keep_alive_mode(settings, sockfd))
+ {
+ close(sockfd);
+
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_FAILED);
+
+ WLog_ERR(TAG, "Couldn't set keep alive mode.");
+ return -1;
+ }
+ }
+
+ if (WaitForSingleObject(utils_get_abort_event(context->rdp), 0) == WAIT_OBJECT_0)
+ {
+ close(sockfd);
+ return -1;
+ }
+
+ return sockfd;
+}