ud3tn/components/cla/posix/cla_tcp_util.c
Maximilian Nitsch 4e125e3af7 fix: cppcheck warnings
Add command-line options for cppcheck to the CI job:

- `--suppress=constParameterCallback`

- `--check-level=exhaustive`

  information: Limiting analysis of branches.
  Use --check-level=exhaustive to analyze all branches.

- `--force`

  information: Too many #ifdef configurations - cppcheck only checks 12
  configurations. Use --force to check all configurations.

Signed-off-by: Maximilian Nitsch <maximilian.nitsch@d3tn.com>
2026-05-20 17:57:53 +02:00

367 lines
7.4 KiB
C

// SPDX-License-Identifier: BSD-3-Clause OR Apache-2.0
#include "cla/posix/cla_tcp_util.h"
#include "platform/hal_io.h"
#include "ud3tn/common.h"
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef NI_MAXHOST
#define NI_MAXHOST 1025
#endif // NI_MAXHOST
#ifndef NI_MAXSERV
#define NI_MAXSERV 32
#endif // NI_MAXSERV
char *cla_tcp_sockaddr_to_cla_addr(struct sockaddr *const sockaddr,
const socklen_t sockaddr_len)
{
char host_tmp[NI_MAXHOST];
char service_tmp[NI_MAXSERV];
int status = getnameinfo(
sockaddr,
sockaddr_len,
host_tmp,
NI_MAXHOST,
service_tmp,
NI_MAXSERV,
NI_NUMERICHOST | NI_NUMERICSERV
);
if (status != 0) {
LOGF_WARN(
"TCP: getnameinfo failed: %s\n",
gai_strerror(status)
);
return NULL;
}
if (sockaddr->sa_family != AF_INET && sockaddr->sa_family != AF_INET6) {
LOGF_WARN(
"TCP: getnameinfo returned invalid AF: %hu\n",
sockaddr->sa_family
);
return NULL;
}
const size_t host_len = strlen(host_tmp);
const size_t service_len = strlen(service_tmp);
const size_t result_len = (
host_len +
service_len +
1 + // ':'
1 + // '\0'
(sockaddr->sa_family == AF_INET6 ? 2 : 0) // "[...]:..."
);
char *const result = malloc(result_len);
if (!result) {
LOG_WARN("TCP: Memory allocation failed.");
return NULL;
}
snprintf(
result,
result_len,
(sockaddr->sa_family == AF_INET6 ? "[%s]:%s" : "%s:%s"),
host_tmp,
service_tmp
);
return result;
}
int create_tcp_socket(const char *const node, const char *const service,
const bool client, char **const addr_return)
{
const int enable = 1;
const int disable = 0;
const char *node_param = node;
ASSERT(node_param != NULL);
ASSERT(service != NULL);
// We support specifying "*" as node name to bind to all interfaces.
// Note that by default this only uses IPv4. To support IPv4 and v6
// at the same time, "::" should be specified instead.
if (strcmp(node_param, "*") == 0)
node_param = NULL;
struct addrinfo hints;
struct addrinfo *result, *e;
int sock = -1;
int status;
int error_code = 0;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; // support IPv4 + v6
hints.ai_socktype = SOCK_STREAM; // TCP
hints.ai_flags = AI_V4MAPPED; // enable IPv4 support via mapped addr
if (!client)
hints.ai_flags |= AI_PASSIVE; // node == NULL -> any interface
status = getaddrinfo(node_param, service, &hints, &result);
if (status != 0) {
LOGF_WARN(
"TCP: getaddrinfo() failed for %s:%s: %s",
node,
service,
gai_strerror(status)
);
return -1;
}
// Default behavior when using getaddrinfo: try one after another
for (e = result; e != NULL; e = e->ai_next) {
sock = socket(
e->ai_family,
e->ai_socktype,
e->ai_protocol
);
if (sock == -1) {
error_code = errno;
LOG_ERRNO("TCP", "socket()", error_code);
continue;
}
// Enable the immediate reuse of a previously closed socket.
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
&enable, sizeof(int)) < 0) {
error_code = errno;
LOG_ERRNO(
"TCP",
"setsockopt(SO_REUSEADDR, 1)",
error_code
);
close(sock);
continue;
}
#if defined(CLA_TCP_ALLOW_REUSE_PORT) && CLA_TCP_ALLOW_REUSE_PORT == 1
// NOTE: SO_REUSEPORT is Linux- and BSD-specific.
if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
&enable, sizeof(int)) < 0) {
error_code = errno;
LOG_ERRNO(
"TCP",
"setsockopt(SO_REUSEPORT, 1)",
error_code
);
close(sock);
continue;
}
#endif // CLA_TCP_ALLOW_REUSE_PORT
if (e->ai_family == AF_INET6) {
// Some systems may want to only listen for IPv6
// connections by default.
if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
&disable, sizeof(int)) < 0) {
error_code = errno;
LOG_ERRNO(
"TCP",
"setsockopt(IPV6_V6ONLY, 0)",
error_code
);
close(sock);
continue;
}
}
// Disable the nagle algorithm to prevent delays in responses.
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
&enable, sizeof(int)) < 0) {
error_code = errno;
LOG_ERRNO(
"TCP",
"setsockopt(TCP_NODELAY, 1)",
error_code
);
close(sock);
continue;
}
if (client && connect(sock, e->ai_addr, e->ai_addrlen) < 0) {
error_code = errno;
LOG_ERRNO_INFO("TCP", "connect()", error_code);
close(sock);
continue;
} else if (!client &&
bind(sock, e->ai_addr, e->ai_addrlen) < 0) {
error_code = errno;
LOG_ERRNO_INFO("TCP", "bind()", error_code);
close(sock);
continue;
}
// We have our socket!
break;
}
if (e == NULL) {
LOGF_WARN(
"TCP: Failed to %s to [%s]:%s",
client ? "connect" : "bind",
node,
service
);
if (sock != -1)
close(sock);
sock = -1;
goto done;
}
if (addr_return) {
*addr_return = cla_tcp_sockaddr_to_cla_addr(
e->ai_addr,
e->ai_addrlen
);
if (!*addr_return) {
close(sock);
sock = -1;
goto done;
}
}
done:
// Free the list allocated by getaddrinfo.
freeaddrinfo(result);
return sock;
}
int cla_tcp_connect_to_cla_addr(const char *const cla_addr,
const char *const default_service)
{
ASSERT(cla_addr != NULL && cla_addr[0] != 0);
char *const addr = strdup(cla_addr);
char *node, *service;
if (!addr)
return -1;
// Split CLA addr into node and service names
if (addr[0] == '[') {
// IPv6 port notation
service = strrchr(addr, ']');
if (!service || service[1] != ':' || service[2] == 0) {
if (!default_service) {
LOG_WARN("TCP: Service field empty and no default service/port specified, cannot connect");
free(addr);
return -1;
}
// no port / service given
if (service)
service[0] = 0; // zero-terminate node string
service = (char *)default_service; // use default port
} else {
service[0] = 0;
service = &service[2];
}
node = &addr[1];
} else {
service = strrchr(addr, ':');
if (!service || service[1] == 0) {
if (!default_service) {
LOG_WARN("TCP: Service field empty and no default service/port specified, cannot connect");
free(addr);
return -1;
}
// no port / service given
if (service)
service[0] = 0; // zero-terminate node string
service = (char *)default_service; // use default port
} else {
service[0] = 0;
service = &service[1];
}
node = addr;
}
const int socket = create_tcp_socket(node, service, true, NULL);
free(addr);
return socket;
}
ssize_t tcp_send_all(const int socket, const void *const buffer,
const size_t length)
{
size_t sent = 0;
while (sent < length) {
const ssize_t r = send(
socket,
buffer,
length - sent,
0
);
if (r == 0)
return r;
if (r < 0) {
if (errno == EINTR)
continue;
return r;
}
sent += r;
}
return sent;
}
ssize_t tcp_recv_all(const int socket, void *const buffer, const size_t length)
{
size_t recvd = 0;
while (recvd < length) {
const ssize_t r = recv(
socket,
buffer,
length - recvd,
MSG_WAITALL
);
if (r == 0)
return r;
if (r < 0) {
if (errno == EINTR)
continue;
return r;
}
recvd += r;
}
return recvd;
}
void tcp_write_to_socket(
void *const p, const void *const buffer, const size_t length)
{
struct tcp_write_to_socket_param *const wsp = p;
ssize_t result;
if (wsp->errno_)
return;
result = tcp_send_all(wsp->socket_fd, buffer, length);
if (result == -1)
wsp->errno_ = errno;
}