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:
+75
-105
@@ -35,6 +35,7 @@
|
|||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <linux/limits.h>
|
||||||
|
|
||||||
#include <common/utils/assertions.h>
|
#include <common/utils/assertions.h>
|
||||||
#include <common/utils/LOG/log.h>
|
#include <common/utils/LOG/log.h>
|
||||||
@@ -62,15 +63,20 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role;
|
|||||||
#define TIME_SCALE_HLP \
|
#define TIME_SCALE_HLP \
|
||||||
"sample time scale. 1.0 means realtime. Values > 1 mean faster than realtime. Values < 1 mean slower than realtime\n"
|
"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 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
|
// clang-format off
|
||||||
#define VRTSIM_PARAMS_DESC \
|
#define VRTSIM_PARAMS_DESC \
|
||||||
{ \
|
{ \
|
||||||
{"channel_name", "shared memory channel name\n", 0, .strptr = &channel_name, .defstrval = "vrtsim_channel", 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}, \
|
{"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}, \
|
{"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}, \
|
{"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}, \
|
{"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
|
// clang-format on
|
||||||
|
|
||||||
@@ -96,7 +102,7 @@ typedef struct tx_timing_s {
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int role;
|
int role;
|
||||||
char channel_name[40];
|
char *connection_descriptor;
|
||||||
ShmTDIQChannel *channel;
|
ShmTDIQChannel *channel;
|
||||||
uint64_t last_received_sample;
|
uint64_t last_received_sample;
|
||||||
pthread_t timing_thread;
|
pthread_t timing_thread;
|
||||||
@@ -116,6 +122,7 @@ typedef struct {
|
|||||||
channel_desc_t *channel_desc;
|
channel_desc_t *channel_desc;
|
||||||
Actor_t *channel_modelling_actors;
|
Actor_t *channel_modelling_actors;
|
||||||
char *taps_socket;
|
char *taps_socket;
|
||||||
|
int client_num_rx_antennas;
|
||||||
} vrtsim_state_t;
|
} vrtsim_state_t;
|
||||||
|
|
||||||
// Sample history for channel impulse response
|
// 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)
|
static void vrtsim_readconfig(vrtsim_state_t *vrtsim_state)
|
||||||
{
|
{
|
||||||
char *channel_name = NULL;
|
|
||||||
char *role = NULL;
|
char *role = NULL;
|
||||||
paramdef_t vrtsim_params[] = VRTSIM_PARAMS_DESC;
|
paramdef_t vrtsim_params[] = VRTSIM_PARAMS_DESC;
|
||||||
int ret = config_get(config_get_if(), vrtsim_params, sizeofArray(vrtsim_params), VRTSIM_SECTION);
|
int ret = config_get(config_get_if(), vrtsim_params, sizeofArray(vrtsim_params), VRTSIM_SECTION);
|
||||||
AssertFatal(ret >= 0, "configuration couldn't be performed\n");
|
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) {
|
if (strncmp(role, ROLE_CLIENT_STRING, strlen(ROLE_CLIENT_STRING)) == 0) {
|
||||||
vrtsim_state->role = ROLE_CLIENT;
|
vrtsim_state->role = ROLE_CLIENT;
|
||||||
} else if (strncmp(role, ROLE_SERVER_STRING, strlen(ROLE_SERVER_STRING)) == 0) {
|
} 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;
|
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,
|
* The server writes its client_info (number of RX antennas) to a file, which the client reads.
|
||||||
* and exchanges peer information (number of RX antennas) with the client.
|
* 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.
|
* @param client_info The client information to publish.
|
||||||
* @return The peer information received from the client.
|
* @return The peer information (same as input, server is authoritative).
|
||||||
*
|
|
||||||
* @note This function will block until a client connects and the information is exchanged.
|
|
||||||
*/
|
*/
|
||||||
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;
|
FILE *fp = fopen(descriptor_file, "wb");
|
||||||
|
AssertFatal(fp != NULL, "Failed to open client info file for writing: %s\n", strerror(errno));
|
||||||
// Create a Unix domain socket
|
size_t written = fwrite(&client_info, sizeof(client_info), 1, fp);
|
||||||
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
AssertFatal(written == 1, "Failed to write client info to file\n");
|
||||||
AssertFatal(server_fd >= 0, "socket() failed: errno: %d, %s\n", errno, strerror(errno));
|
fclose(fp);
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static client_info_t client_read_info(char *descriptor_file)
|
||||||
* @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)
|
|
||||||
{
|
{
|
||||||
int client_fd;
|
client_info_t client_info;
|
||||||
|
|
||||||
// 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;
|
|
||||||
int tries = 0;
|
int tries = 0;
|
||||||
while (ret != 0 && tries < 10) {
|
while (tries < 10) {
|
||||||
ret = connect(client_fd, (struct sockaddr *)&addr, sizeof(addr));
|
FILE *fp = fopen(descriptor_file, "rb");
|
||||||
tries++;
|
if (fp) {
|
||||||
sleep(1);
|
size_t read = fread(&client_info, sizeof(client_info), 1, fp);
|
||||||
|
fclose(fp);
|
||||||
|
if (read == 1) {
|
||||||
|
return client_info;
|
||||||
}
|
}
|
||||||
AssertFatal(ret == 0, "connect() failed: errno: %d, %s\n", errno, strerror(errno));
|
}
|
||||||
|
sleep(1);
|
||||||
peer_info_t peer_info_received;
|
tries++;
|
||||||
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));
|
AssertFatal(0, "Timeout waiting for client info\n");
|
||||||
|
return client_info;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int vrtsim_connect(openair0_device *device)
|
static int vrtsim_connect(openair0_device *device)
|
||||||
{
|
{
|
||||||
vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv;
|
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) {
|
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 {
|
} 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
|
// 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;
|
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;
|
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);
|
LOG_I(HW, "VRTSIM: Average TX budget %.3lf uS (more is better)\n", tx_timing->average_tx_budget);
|
||||||
histogram_print(&tx_timing->tx_histogram);
|
histogram_print(&tx_timing->tx_histogram);
|
||||||
free(vrtsim_state->tx_timing);
|
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)
|
static int vrtsim_stub(openair0_device *device)
|
||||||
|
|||||||
Reference in New Issue
Block a user