VRTSIM: allow client reconnection via a connection descriptor file

Replace the unwieldy unix socket implementation of peer information
exchange with an authorative connection descriptor file that
informs the client of the connection method an number of antennas
on the server.

This allows the server to run without the client and allows the
client to reconnect as long as the server is running, as many times
as needed.
This commit is contained in:
Bartosz Podrygajlo
2025-08-20 15:57:34 +02:00
parent 54fdd3547b
commit ea3a4b991c
+78 -108
View File
@@ -35,6 +35,7 @@
#include <pthread.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <linux/limits.h>
#include <common/utils/assertions.h>
#include <common/utils/LOG/log.h>
@@ -62,15 +63,20 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role;
#define TIME_SCALE_HLP \
"sample time scale. 1.0 means realtime. Values > 1 mean faster than realtime. Values < 1 mean slower than realtime\n"
#define TAPS_SOCKET_HLP "Socket to connect to the channel emulation server\n"
#define CLIENT_NUM_RX_HLP "Number of RX antennas of the client, specified on the server\n"
#define CONNECTION_DESCRIPTOR_HLP "Path to the file written by the server that the client can use to connect."
#define DEFAULT_CHANNEL_NAME "vrtsim_channel"
#define DEFAULT_DESCRIPTOR "/tmp/vrtsim_connection"
// clang-format off
#define VRTSIM_PARAMS_DESC \
{ \
{"channel_name", "shared memory channel name\n", 0, .strptr = &channel_name, .defstrval = "vrtsim_channel", TYPE_STRING, 0}, \
{"role", "either client or server\n", 0, .strptr = &role, .defstrval = ROLE_CLIENT_STRING, TYPE_STRING, 0}, \
{"timescale", TIME_SCALE_HLP, 0, .dblptr = &vrtsim_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \
{"chanmod", "Enable channel modelling", 0, .iptr = &vrtsim_state->chanmod, .defintval = 0, TYPE_INT, 0}, \
{"taps-socket", TAPS_SOCKET_HLP, 0, .strptr = &vrtsim_state->taps_socket, .defstrval = NULL, TYPE_STRING, 0}, \
{"connection_descriptor", CONNECTION_DESCRIPTOR_HLP, 0, .strptr = &vrtsim_state->connection_descriptor, .defstrval = DEFAULT_DESCRIPTOR, TYPE_STRING, 0}, \
{"role", "either client or server\n", 0, .strptr = &role, .defstrval = ROLE_CLIENT_STRING, TYPE_STRING, 0}, \
{"timescale", TIME_SCALE_HLP, 0, .dblptr = &vrtsim_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \
{"chanmod", "Enable channel modelling", 0, .iptr = &vrtsim_state->chanmod, .defintval = 0, TYPE_INT, 0}, \
{"taps-socket", TAPS_SOCKET_HLP, 0, .strptr = &vrtsim_state->taps_socket, .defstrval = NULL, TYPE_STRING, 0}, \
{"client-num-rx-antennas", CLIENT_NUM_RX_HLP, 0, .iptr = &vrtsim_state->client_num_rx_antennas, .defintval = 1, TYPE_INT, 0}, \
};
// clang-format on
@@ -96,7 +102,7 @@ typedef struct tx_timing_s {
typedef struct {
int role;
char channel_name[40];
char *connection_descriptor;
ShmTDIQChannel *channel;
uint64_t last_received_sample;
pthread_t timing_thread;
@@ -116,6 +122,7 @@ typedef struct {
channel_desc_t *channel_desc;
Actor_t *channel_modelling_actors;
char *taps_socket;
int client_num_rx_antennas;
} vrtsim_state_t;
// Sample history for channel impulse response
@@ -174,12 +181,10 @@ static void load_channel_model(vrtsim_state_t *vrtsim_state)
static void vrtsim_readconfig(vrtsim_state_t *vrtsim_state)
{
char *channel_name = NULL;
char *role = NULL;
paramdef_t vrtsim_params[] = VRTSIM_PARAMS_DESC;
int ret = config_get(config_get_if(), vrtsim_params, sizeofArray(vrtsim_params), VRTSIM_SECTION);
AssertFatal(ret >= 0, "configuration couldn't be performed\n");
strncpy(vrtsim_state->channel_name, channel_name, sizeof(vrtsim_state->channel_name) - 1);
if (strncmp(role, ROLE_CLIENT_STRING, strlen(ROLE_CLIENT_STRING)) == 0) {
vrtsim_state->role = ROLE_CLIENT;
} else if (strncmp(role, ROLE_SERVER_STRING, strlen(ROLE_SERVER_STRING)) == 0) {
@@ -230,112 +235,80 @@ static void *vrtsim_timing_job(void *arg)
return 0;
}
typedef struct client_info_s {
int server_num_rx_antennas;
int client_num_rx_antennas;
} client_info_t;
/**
* @brief Exchanges peer information between the server and the client.
* @brief Publishes the client information information to a file for the client to read.
*
* This function sets up a Unix domain socket server, waits for a client to connect,
* and exchanges peer information (number of RX antennas) with the client.
* The server writes its client_info (number of RX antennas) to a file, which the client reads.
* The server does not wait for the client to write back; the client can connect at any point.
*
* @param peer_info The peer information to send to the client.
* @return The peer information received from the client.
*
* @note This function will block until a client connects and the information is exchanged.
* @param client_info The client information to publish.
* @return The peer information (same as input, server is authoritative).
*/
static peer_info_t server_exchange_peer_info(peer_info_t peer_info)
static void server_publish_client_info(client_info_t client_info, char *descriptor_file)
{
int server_fd, client_fd;
// Create a Unix domain socket
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
AssertFatal(server_fd >= 0, "socket() failed: errno: %d, %s\n", errno, strerror(errno));
// Bind the socket to a file path
struct sockaddr_un addr = {.sun_family = AF_UNIX};
strncpy(addr.sun_path, "/tmp/vrtsim_socket", sizeof(addr.sun_path) - 1);
unlink(addr.sun_path); // Ensure the path does not already exist
int ret = bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
AssertFatal(ret == 0, "bind() failed: errno: %d, %s\n", errno, strerror(errno));
// Listen for incoming connections
ret = listen(server_fd, 1);
AssertFatal(ret == 0, "listen() failed: errno: %d, %s\n", errno, strerror(errno));
// Accept a connection from a client
socklen_t addr_len = sizeof(addr);
client_fd = accept(server_fd, (struct sockaddr *)&addr, &addr_len);
AssertFatal(client_fd >= 0, "accept() failed: errno: %d, %s\n", errno, strerror(errno));
// Send the antenna info to the server
ssize_t bytes_sent = send(client_fd, &peer_info, sizeof(peer_info), 0);
AssertFatal(bytes_sent == sizeof(peer_info), "send() failed: errno: %d, %s\n", errno, strerror(errno));
ssize_t bytes_received = recv(client_fd, &peer_info, sizeof(peer_info), 0);
AssertFatal(bytes_received == sizeof(peer_info), "recv() failed: errno: %d, %s\n", errno, strerror(errno));
LOG_I(HW, "Received client info: RX %d\n", peer_info.num_rx_antennas);
// Close the client and server sockets
close(client_fd);
close(server_fd);
return peer_info;
FILE *fp = fopen(descriptor_file, "wb");
AssertFatal(fp != NULL, "Failed to open client info file for writing: %s\n", strerror(errno));
size_t written = fwrite(&client_info, sizeof(client_info), 1, fp);
AssertFatal(written == 1, "Failed to write client info to file\n");
fclose(fp);
}
/**
* @brief Exchanges peer information between the client and the server.
*
* This function connects to a Unix domain socket server, exchanges peer information
* (number of RX antennas) with the server, and returns the peer information received
* from the server.
*
* @param peer_info The peer information to send to the server.
* @return The peer information received from the server.
*
* @note This function will block until the information is exchanged.
*/
static peer_info_t client_exchange_peer_info(peer_info_t peer_info)
static client_info_t client_read_info(char *descriptor_file)
{
int client_fd;
// Create a Unix domain socket
client_fd = socket(AF_UNIX, SOCK_STREAM, 0);
AssertFatal(client_fd >= 0, "socket() failed: errno: %d, %s\n", errno, strerror(errno));
// Connect to the server
struct sockaddr_un addr = {.sun_family = AF_UNIX};
strncpy(addr.sun_path, "/tmp/vrtsim_socket", sizeof(addr.sun_path) - 1);
int ret = -1;
client_info_t client_info;
int tries = 0;
while (ret != 0 && tries < 10) {
ret = connect(client_fd, (struct sockaddr *)&addr, sizeof(addr));
tries++;
while (tries < 10) {
FILE *fp = fopen(descriptor_file, "rb");
if (fp) {
size_t read = fread(&client_info, sizeof(client_info), 1, fp);
fclose(fp);
if (read == 1) {
return client_info;
}
}
sleep(1);
tries++;
}
AssertFatal(ret == 0, "connect() failed: errno: %d, %s\n", errno, strerror(errno));
peer_info_t peer_info_received;
ssize_t bytes_received = recv(client_fd, &peer_info_received, sizeof(peer_info_received), 0);
AssertFatal(bytes_received == sizeof(peer_info_received), "recv() failed: errno: %d, %s\n", errno, strerror(errno));
// Send the antenna info to the server
ssize_t bytes_sent = send(client_fd, &peer_info, sizeof(peer_info), 0);
AssertFatal(bytes_sent == sizeof(peer_info), "send() failed: errno: %d, %s\n", errno, strerror(errno));
LOG_I(HW, "Received peer info: RX %d\n", peer_info_received.num_rx_antennas);
// Close the client socket
close(client_fd);
return peer_info_received;
AssertFatal(0, "Timeout waiting for client info\n");
return client_info;
}
static int vrtsim_connect(openair0_device *device)
{
vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv;
// Exchange peer info
peer_info_t peer_info = {.num_rx_antennas = device->openair0_cfg[0].rx_num_channels};
// Setup a shared memory channel
if (vrtsim_state->role == ROLE_SERVER) {
vrtsim_state->peer_info = server_exchange_peer_info(peer_info);
vrtsim_state->peer_info.num_rx_antennas = vrtsim_state->client_num_rx_antennas;
vrtsim_state->channel = shm_td_iq_channel_create(DEFAULT_CHANNEL_NAME,
vrtsim_state->peer_info.num_rx_antennas,
device->openair0_cfg[0].rx_num_channels);
// Exchange peer info
client_info_t client_info = {
.server_num_rx_antennas = device->openair0_cfg[0].rx_num_channels,
.client_num_rx_antennas = vrtsim_state->client_num_rx_antennas,
};
server_publish_client_info(client_info, vrtsim_state->connection_descriptor);
vrtsim_state->run_timing_thread = true;
int ret = pthread_create(&vrtsim_state->timing_thread, NULL, vrtsim_timing_job, vrtsim_state);
AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno));
} else {
vrtsim_state->peer_info = client_exchange_peer_info(peer_info);
client_info_t client_info = client_read_info(vrtsim_state->connection_descriptor);
AssertFatal(client_info.server_num_rx_antennas > 0, "Server did not publish valid client info, aborting client connection\n");
AssertFatal(
client_info.client_num_rx_antennas == device->openair0_cfg[0].rx_num_channels,
"Server expects different number of RX antennas. %d != %d. Use server command line option --client-num-rx-antennas\n",
client_info.client_num_rx_antennas,
device->openair0_cfg[0].rx_num_channels);
vrtsim_state->channel = shm_td_iq_channel_connect(DEFAULT_CHANNEL_NAME, 10);
vrtsim_state->peer_info.num_rx_antennas = client_info.server_num_rx_antennas;
vrtsim_state->last_received_sample = shm_td_iq_channel_get_current_sample(vrtsim_state->channel);
}
// Handle channel modelling after number of RX antennas are known
@@ -363,18 +336,6 @@ static int vrtsim_connect(openair0_device *device)
vrtsim_state->tx_timing[i].tx_histogram.range = 3000.0;
}
// Setup a shared memory channel
if (vrtsim_state->role == ROLE_SERVER) {
vrtsim_state->channel = shm_td_iq_channel_create(vrtsim_state->channel_name,
vrtsim_state->peer_info.num_rx_antennas,
device->openair0_cfg[0].rx_num_channels);
vrtsim_state->run_timing_thread = true;
int ret = pthread_create(&vrtsim_state->timing_thread, NULL, vrtsim_timing_job, vrtsim_state);
AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno));
} else {
vrtsim_state->channel = shm_td_iq_channel_connect(vrtsim_state->channel_name, 10);
}
return 0;
}
@@ -640,6 +601,15 @@ static void vrtsim_end(openair0_device *device)
LOG_I(HW, "VRTSIM: Average TX budget %.3lf uS (more is better)\n", tx_timing->average_tx_budget);
histogram_print(&tx_timing->tx_histogram);
free(vrtsim_state->tx_timing);
if (vrtsim_state->role == ROLE_SERVER) {
int ret = remove(vrtsim_state->connection_descriptor);
if (ret != 0) {
LOG_E(HW, "Failed to remove connection descriptor file %s: %s\n", vrtsim_state->connection_descriptor, strerror(errno));
} else {
LOG_A(HW, "Removed connection descriptor file %s\n", vrtsim_state->connection_descriptor);
}
}
}
static int vrtsim_stub(openair0_device *device)