fixeria has submitted this change. ( https://gerrit.osmocom.org/c/osmo-trx/+/43603?usp=email )
(
3 is the latest approved patch-set. No files were changed between the latest approved patch-set and the submitted one. )Change subject: osmo-trx-proxy: initial app skeleton ......................................................................
osmo-trx-proxy: initial app skeleton
osmo-trx-proxy is a C rewrite of the fake_trx.py: a virtual TRX endpoint that BTS/MS-side L1 implementations (osmo-bts-trx, trxcon) can connect to over the TRXC/TRXD protocol instead of real RF hardware, useful for testing without SDR devices. This first commit adds just the daemon skeleton (config context, VTY, logging).
Change-Id: Id2b12e5e7d053eea53338fbabc48131b9c46c82a Related: OS#6672 --- M .gitignore M Makefile.am M configure.ac A proxy/Makefile.am A proxy/include/osmocom/proxy/logging.h A proxy/include/osmocom/proxy/proxy.h A proxy/include/osmocom/proxy/trx.h A proxy/include/osmocom/proxy/vty.h A proxy/src/Makefile.am A proxy/src/logging.c A proxy/src/main.c A proxy/src/proxy.c A proxy/src/trx.c A proxy/src/vty.c 14 files changed, 871 insertions(+), 0 deletions(-)
Approvals: pespin: Looks good to me, approved Jenkins Builder: Verified laforge: Looks good to me, but someone else must approve
diff --git a/.gitignore b/.gitignore index 164e863..8c51628 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ Transceiver52M/osmo-trx-ms-uhd Transceiver52M/osmo-trx-ms-ipc Transceiver52M/device/ipc/uhddev_ipc.cpp +proxy/src/osmo-trx-proxy
.clang-format
diff --git a/Makefile.am b/Makefile.am index 844d6e7..2585fdf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -35,6 +35,13 @@ # Order must be preserved SUBDIRS += \ libosmo-trx \ + $(NULL) + +if ENABLE_PROXY +SUBDIRS += proxy +endif + +SUBDIRS += \ CommonLibs \ GSM \ Transceiver52M \ diff --git a/configure.ac b/configure.ac index e18f943..fd71f47 100644 --- a/configure.ac +++ b/configure.ac @@ -162,6 +162,11 @@ [enable MS TRX]) ])
+AC_ARG_WITH(proxy, [ + AS_HELP_STRING([--with-proxy], + [enable osmo-trx-proxy [default=enabled]]) +]) + AC_ARG_WITH(neon, [ AS_HELP_STRING([--with-neon], [enable ARM NEON support]) @@ -288,6 +293,7 @@ AM_CONDITIONAL(ARCH_ARM, [test "x$with_neon" = "xyes" || test "x$with_neon_vfpv4" = "xyes"]) AM_CONDITIONAL(ARCH_ARM_A15, [test "x$with_neon_vfpv4" = "xyes"]) AM_CONDITIONAL(ENABLE_MS_TRX, [test "x$with_mstrx" = "xyes"]) +AM_CONDITIONAL(ENABLE_PROXY, [test "x$with_proxy" != "xno"])
# Disable Multi ARFCN support AC_ARG_ENABLE(multi-arfcn, @@ -375,6 +381,8 @@ libosmo-trx/src/Makefile \ libosmo-trx/libosmo-trx.pc \ libosmo-trx/Doxyfile.trx \ + proxy/Makefile \ + proxy/src/Makefile \ CommonLibs/Makefile \ GSM/Makefile \ Transceiver52M/Makefile \ diff --git a/proxy/Makefile.am b/proxy/Makefile.am new file mode 100644 index 0000000..23a1d3a --- /dev/null +++ b/proxy/Makefile.am @@ -0,0 +1,10 @@ +SUBDIRS = \ + src \ + $(NULL) + +noinst_HEADERS = \ + include/osmocom/proxy/logging.h \ + include/osmocom/proxy/proxy.h \ + include/osmocom/proxy/trx.h \ + include/osmocom/proxy/vty.h \ + $(NULL) diff --git a/proxy/include/osmocom/proxy/logging.h b/proxy/include/osmocom/proxy/logging.h new file mode 100644 index 0000000..6e94ba0 --- /dev/null +++ b/proxy/include/osmocom/proxy/logging.h @@ -0,0 +1,11 @@ +#pragma once + +#include <osmocom/core/logging.h> + +extern const struct log_info log_info; + +enum { + DPROXY, /* main/vty/config */ + DTRXC, /* TRXC (control) + clock handling */ + DTRXD, /* TRXD (burst data) handling / forwarding */ +}; diff --git a/proxy/include/osmocom/proxy/proxy.h b/proxy/include/osmocom/proxy/proxy.h new file mode 100644 index 0000000..080ff53 --- /dev/null +++ b/proxy/include/osmocom/proxy/proxy.h @@ -0,0 +1,25 @@ +#pragma once + +#include <stdbool.h> + +#include <osmocom/core/linuxlist.h> + +#define PROXY_DEFAULT_BIND_ADDR "0.0.0.0" + +/* Endpoints created when the config file defines none explicitly */ +#define PROXY_DEFAULT_REMOTE_ADDR "127.0.0.1" +#define PROXY_DEFAULT_BTS_NAME "bts" +#define PROXY_DEFAULT_BTS_PORT 5700 +#define PROXY_DEFAULT_MS_NAME "ms" +#define PROXY_DEFAULT_MS_PORT 6700 + +struct proxy_ctx { + char *bind_addr; + struct llist_head trx_list; /*!< struct proxy_trx::list */ +}; + +extern struct proxy_ctx *g_proxy_ctx; + +struct proxy_ctx *proxy_ctx_alloc(void *talloc_ctx); +void proxy_ctx_free(struct proxy_ctx *proxy); +int proxy_ctx_add_defaults_if_empty(struct proxy_ctx *proxy); diff --git a/proxy/include/osmocom/proxy/trx.h b/proxy/include/osmocom/proxy/trx.h new file mode 100644 index 0000000..e687825 --- /dev/null +++ b/proxy/include/osmocom/proxy/trx.h @@ -0,0 +1,24 @@ +#pragma once + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/logging.h> + +struct osmo_trx_ep; +struct proxy_ctx; + +/*! One virtual transceiver endpoint */ +struct proxy_trx { + struct llist_head list; + char *name; + struct osmo_trx_ep *ep; +}; + +#define LOGP_TRX(trx, ss, level, fmt, args...) \ + LOGP(ss, level, "(trx=%s) " fmt, (trx)->name, ##args) + +struct proxy_trx *proxy_trx_find(struct proxy_ctx *proxy, const char *name); +struct proxy_trx *proxy_trx_alloc(struct proxy_ctx *proxy, const char *name); +void proxy_trx_free(struct proxy_trx *trx); + +int proxy_trx_open(struct proxy_trx *trx); +void proxy_trx_close(struct proxy_trx *trx); diff --git a/proxy/include/osmocom/proxy/vty.h b/proxy/include/osmocom/proxy/vty.h new file mode 100644 index 0000000..e23b350 --- /dev/null +++ b/proxy/include/osmocom/proxy/vty.h @@ -0,0 +1,3 @@ +#pragma once + +int proxy_vty_init(void); diff --git a/proxy/src/Makefile.am b/proxy/src/Makefile.am new file mode 100644 index 0000000..0350474 --- /dev/null +++ b/proxy/src/Makefile.am @@ -0,0 +1,29 @@ +AM_CPPFLAGS = \ + -Wall \ + -I$(top_srcdir)/proxy/include \ + -I$(top_srcdir)/libosmo-trx/include \ + -I$(top_builddir)/libosmo-trx/include \ + $(NULL) + +AM_CFLAGS = \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(NULL) + +bin_PROGRAMS = osmo-trx-proxy + +osmo_trx_proxy_SOURCES = \ + main.c \ + proxy.c \ + vty.c \ + trx.c \ + logging.c \ + $(NULL) + +osmo_trx_proxy_LDADD = \ + $(top_builddir)/libosmo-trx/src/libosmo-trx.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) diff --git a/proxy/src/logging.c b/proxy/src/logging.c new file mode 100644 index 0000000..b1d3fbb --- /dev/null +++ b/proxy/src/logging.c @@ -0,0 +1,57 @@ +/*! \file src/logging.c + * osmo-trx-proxy: log category definitions. */ + +/* + * (C) 2026 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * Author: Vadim Yanitskiy vyanitskiy@sysmocom.de + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> + +#include <osmocom/proxy/logging.h> + +static const struct log_info_cat default_categories[] = { + [DPROXY] = { + .name = "DPROXY", + .description = "Main generic category", + .loglevel = LOGL_INFO, + .enabled = 1, + }, + [DTRXC] = { + .name = "DTRXC", + .description = "TRXC (control) interface + clock handling", + .color = "\033[1;33m", + .loglevel = LOGL_NOTICE, + .enabled = 1, + }, + [DTRXD] = { + .name = "DTRXD", + .description = "TRXD (burst data) interface handling / forwarding", + .color = "\033[1;33m", + .loglevel = LOGL_NOTICE, + .enabled = 1, + }, +}; + +const struct log_info log_info = { + .cat = default_categories, + .num_cat = ARRAY_SIZE(default_categories), +}; diff --git a/proxy/src/main.c b/proxy/src/main.c new file mode 100644 index 0000000..5ac023a --- /dev/null +++ b/proxy/src/main.c @@ -0,0 +1,150 @@ +/*! \file src/main.c + * osmo-trx-proxy: main program entry point. */ + +/* + * (C) 2026 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * Author: Vadim Yanitskiy vyanitskiy@sysmocom.de + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <getopt.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/application.h> +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/cpu_sched_vty.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/ports.h> + +#include <osmocom/proxy/proxy.h> +#include <osmocom/proxy/vty.h> +#include <osmocom/proxy/trx.h> +#include <osmocom/proxy/logging.h> + +void *g_talloc_ctx; +static char *g_config_file = "osmo-trx-proxy.cfg"; +static volatile sig_atomic_t g_quit = 0; + +static void print_help(void) +{ + printf("Usage: osmo-trx-proxy [options]\n"); + printf(" -h, --help This text\n"); + printf(" -c, --config-file FILE Specify the configuration file (default: %s)\n", + g_config_file); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0; + static const struct option long_options[] = { + { "help", 0, 0, 'h' }, + { "config-file", 1, 0, 'c' }, + { 0, 0, 0, 0 } + }; + + int c = getopt_long(argc, argv, "hc:", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(); + exit(0); + case 'c': + g_config_file = optarg; + break; + default: + exit(2); + } + } +} + +static void signal_handler(int signum) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + g_quit = 1; + break; + default: + break; + } +} + +int main(int argc, char **argv) +{ + int rc; + + g_talloc_ctx = talloc_named_const(NULL, 0, "osmo-trx-proxy"); + msgb_talloc_ctx_init(g_talloc_ctx, 0); + + signal(SIGINT, &signal_handler); + signal(SIGTERM, &signal_handler); + osmo_init_ignore_signals(); + + g_proxy_ctx = proxy_ctx_alloc(g_talloc_ctx); + OSMO_ASSERT(g_proxy_ctx != NULL); + + osmo_init_logging2(g_talloc_ctx, &log_info); + log_set_print_extended_timestamp(osmo_stderr_target, 1); + log_set_print_category_hex(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); + log_set_print_level(osmo_stderr_target, 1); + log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME); + log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END); + + proxy_vty_init(); + + handle_options(argc, argv); + + rc = vty_read_config_file(g_config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to open config file: '%s'\n", g_config_file); + return 2; + } + + rc = telnet_init_default(g_talloc_ctx, NULL, OSMO_VTY_PORT_TRX); + if (rc < 0) + return 1; + + osmo_cpu_sched_vty_apply_localthread(); + + /* Only apply the built-in BTS/MS defaults if the config file did not + * declare any 'ep' stanza of its own. */ + rc = proxy_ctx_add_defaults_if_empty(g_proxy_ctx); + if (rc < 0) + return 1; + + LOGP(DPROXY, LOGL_NOTICE, "osmo-trx-proxy started\n"); + + while (!g_quit) + osmo_select_main(0); + + LOGP(DPROXY, LOGL_NOTICE, "Shutting down...\n"); + proxy_ctx_free(g_proxy_ctx); + return 0; +} diff --git a/proxy/src/proxy.c b/proxy/src/proxy.c new file mode 100644 index 0000000..b194bc9 --- /dev/null +++ b/proxy/src/proxy.c @@ -0,0 +1,101 @@ +/*! \file src/proxy.c + * osmo-trx-proxy: top-level daemon context (struct proxy_ctx). */ + +/* + * (C) 2026 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * Author: Vadim Yanitskiy vyanitskiy@sysmocom.de + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <errno.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/linuxlist.h> + +#include <osmocom/trx/ep.h> + +#include <osmocom/proxy/proxy.h> +#include <osmocom/proxy/trx.h> +#include <osmocom/proxy/logging.h> + +struct proxy_ctx *g_proxy_ctx = NULL; + +struct proxy_ctx *proxy_ctx_alloc(void *talloc_ctx) +{ + struct proxy_ctx *proxy; + + proxy = talloc_zero(talloc_ctx, struct proxy_ctx); + if (proxy == NULL) + return NULL; + + proxy->bind_addr = talloc_strdup(proxy, PROXY_DEFAULT_BIND_ADDR); + INIT_LLIST_HEAD(&proxy->trx_list); + + return proxy; +} + +void proxy_ctx_free(struct proxy_ctx *proxy) +{ + struct proxy_trx *trx, *trx2; + + if (!proxy) + return; + + llist_for_each_entry_safe(trx, trx2, &proxy->trx_list, list) { + proxy_trx_close(trx); + proxy_trx_free(trx); + } + + talloc_free(proxy); +} + +/*! Populate the config with the default BTS/MS endpoints (base ports 5700 + * and 6700) and open them, iff the config file did not declare any + * endpoint explicitly. + * \returns 0 on success; negative on error (see proxy_trx_open()) */ +int proxy_ctx_add_defaults_if_empty(struct proxy_ctx *proxy) +{ + struct proxy_trx *trx; + int rc; + + if (!llist_empty(&proxy->trx_list)) + return 0; + + LOGP(DPROXY, LOGL_NOTICE, + "No endpoints configured, creating defaults ('%s' and '%s')\n", + PROXY_DEFAULT_BTS_NAME, PROXY_DEFAULT_MS_NAME); + + trx = proxy_trx_alloc(proxy, PROXY_DEFAULT_BTS_NAME); + osmo_trx_ep_set_raddr(trx->ep, PROXY_DEFAULT_REMOTE_ADDR); + osmo_trx_ep_set_base_port(trx->ep, PROXY_DEFAULT_BTS_PORT); + rc = proxy_trx_open(trx); + if (rc != 0) + return rc; + + trx = proxy_trx_alloc(proxy, PROXY_DEFAULT_MS_NAME); + osmo_trx_ep_set_raddr(trx->ep, PROXY_DEFAULT_REMOTE_ADDR); + osmo_trx_ep_set_base_port(trx->ep, PROXY_DEFAULT_MS_PORT); + /* MS (trxcon) side has no use for TRX clock indications */ + osmo_trx_ep_set_clock_socket(trx->ep, false); + rc = proxy_trx_open(trx); + if (rc != 0) + return rc; + + return 0; +} diff --git a/proxy/src/trx.c b/proxy/src/trx.c new file mode 100644 index 0000000..6eab496 --- /dev/null +++ b/proxy/src/trx.c @@ -0,0 +1,108 @@ +/*! \file src/trx.c + * osmo-trx-proxy: virtual transceiver management. */ + +/* + * (C) 2026 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * Author: Vadim Yanitskiy vyanitskiy@sysmocom.de + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <errno.h> +#include <string.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/linuxlist.h> + +#include <osmocom/trx/ep.h> + +#include <osmocom/proxy/proxy.h> +#include <osmocom/proxy/trx.h> +#include <osmocom/proxy/logging.h> + +struct proxy_trx *proxy_trx_alloc(struct proxy_ctx *proxy, const char *name) +{ + struct proxy_trx *trx; + + trx = talloc_zero(proxy, struct proxy_trx); + if (trx == NULL) + return NULL; + + trx->name = talloc_strdup(trx, name); + trx->ep = osmo_trx_ep_alloc(trx, 1); + if (trx->ep == NULL) { + talloc_free(trx); + return NULL; + } + + osmo_trx_ep_set_priv(trx->ep, trx); + osmo_trx_ep_set_name(trx->ep, "%s", name); + osmo_trx_ep_set_log_cat(trx->ep, DPROXY); + osmo_trx_ep_set_mode(trx->ep, OSMO_TRX_EP_MODE_TRX); + osmo_trx_ep_set_laddr(trx->ep, proxy->bind_addr); + osmo_trx_ep_set_clock_socket(trx->ep, true); + osmo_trx_ep_set_ctrl_promisc(trx->ep, true); + + llist_add_tail(&trx->list, &proxy->trx_list); + + return trx; +} + +struct proxy_trx *proxy_trx_find(struct proxy_ctx *proxy, const char *name) +{ + struct proxy_trx *trx; + + llist_for_each_entry(trx, &proxy->trx_list, list) { + if (strcmp(trx->name, name) == 0) + return trx; + } + + return NULL; +} + +void proxy_trx_close(struct proxy_trx *trx) +{ + if (!trx) + return; + osmo_trx_ep_close(trx->ep); +} + +void proxy_trx_free(struct proxy_trx *trx) +{ + if (!trx) + return; + osmo_trx_ep_free(trx->ep); + llist_del(&trx->list); + talloc_free(trx); +} + +int proxy_trx_open(struct proxy_trx *trx) +{ + int rc; + + rc = osmo_trx_ep_open(trx->ep); + if (rc < 0 && rc != -EALREADY) { + LOGP_TRX(trx, DPROXY, LOGL_ERROR, + "Failed to open TRX endpoint %s:%u\n", + osmo_trx_ep_get_raddr(trx->ep), + osmo_trx_ep_get_base_port(trx->ep)); + return rc; + } + + return 0; +} diff --git a/proxy/src/vty.c b/proxy/src/vty.c new file mode 100644 index 0000000..086fe50 --- /dev/null +++ b/proxy/src/vty.c @@ -0,0 +1,337 @@ +/*! \file src/vty.c + * osmo-trx-proxy: VTY configuration interface. */ + +/* + * (C) 2026 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * Author: Vadim Yanitskiy vyanitskiy@sysmocom.de + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include <osmocom/vty/command.h> +#include <osmocom/vty/vty.h> +#include <osmocom/vty/misc.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/cpu_sched_vty.h> + +#include <osmocom/trx/ep.h> + +#include <osmocom/proxy/proxy.h> +#include <osmocom/proxy/vty.h> +#include <osmocom/proxy/trx.h> + +extern void *g_talloc_ctx; + +enum proxy_vty_node { + PROXY_NODE = _LAST_OSMOVTY_NODE + 1, + EP_NODE, +}; + +static struct cmd_node proxy_node = { + PROXY_NODE, + "%s(config-proxy)# ", + 1, +}; + +static struct cmd_node ep_node = { + EP_NODE, + "%s(config-proxy-ep)# ", + 1, +}; + +static int dummy_config_write(struct vty *vty) +{ + return CMD_SUCCESS; +} + +DEFUN(cfg_proxy, + cfg_proxy_cmd, + "proxy", + "Configure osmo-trx-proxy\n") +{ + vty->node = PROXY_NODE; + vty->index = g_proxy_ctx; + + return CMD_SUCCESS; +} + +DEFUN(cfg_proxy_bind_addr, + cfg_proxy_bind_addr_cmd, + "bind-addr " VTY_IPV46_CMD, + "Set the default local bind address for endpoints without one of their own\n" + "IPv4 Address\n" + "IPv6 Address\n") +{ + struct proxy_ctx *proxy = vty->index; + + osmo_talloc_replace_string(proxy, &proxy->bind_addr, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_proxy_ep, + cfg_proxy_ep_cmd, + "ep NAME", + "Configure a virtual TRX endpoint\n" + "Endpoint name (used for logging)\n") +{ + struct proxy_ctx *proxy = vty->index; + struct proxy_trx *trx = proxy_trx_find(proxy, argv[0]); + + if (!trx) + trx = proxy_trx_alloc(proxy, argv[0]); + + vty->node = EP_NODE; + vty->index = trx; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_proxy_ep, + cfg_no_proxy_ep_cmd, + "no ep NAME", + NO_STR "Remove a virtual TRX endpoint, closing its sockets if currently open\n" + "Endpoint name\n") +{ + struct proxy_ctx *proxy = vty->index; + struct proxy_trx *trx = proxy_trx_find(proxy, argv[0]); + + if (!trx) { + vty_out(vty, "%% Endpoint '%s' does not exist%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + proxy_trx_free(trx); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ep_remote_addr, + cfg_ep_remote_addr_cmd, + "remote-addr " VTY_IPV46_CMD, + "Set the remote (L1 peer) IP address\n" + "IPv4 Address\n" + "IPv6 Address\n") +{ + struct proxy_trx *trx = vty->index; + + osmo_trx_ep_set_raddr(trx->ep, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ep_bind_addr, + cfg_ep_bind_addr_cmd, + "bind-addr " VTY_IPV46_CMD, + "Set the local bind address for this endpoint, overriding the 'proxy' default\n" + "IPv4 Address\n" + "IPv6 Address\n") +{ + struct proxy_trx *trx = vty->index; + + osmo_trx_ep_set_laddr(trx->ep, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ep_base_port, + cfg_ep_base_port_cmd, + "base-port <1-65535>", + "Set the TRX base port\n" + "Base port (clock: +0, per channel N: ctrl +2N+1, data +2N+2)\n") +{ + struct proxy_trx *trx = vty->index; + + osmo_trx_ep_set_base_port(trx->ep, atoi(argv[0])); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ep_num_chans, + cfg_ep_num_chans_cmd, + "num-chans <1-16>", + "Set the number of channels (1 + number of child transceivers)\n" + "Number of channels\n") +{ + struct proxy_trx *trx = vty->index; + int num_chans = atoi(argv[0]); + int rc; + + rc = osmo_trx_ep_set_num_chans(trx->ep, num_chans); + if (rc) { + vty_out(vty, "%% osmo_trx_ep_set_num_chans(%d) failed: rc=%d%s", + num_chans, rc, VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_ep_clock_socket, + cfg_ep_clock_socket_cmd, + "clock-socket", + "Enable the clock socket for this endpoint (default)\n") +{ + struct proxy_trx *trx = vty->index; + + osmo_trx_ep_set_clock_socket(trx->ep, true); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ep_no_clock_socket, + cfg_ep_no_clock_socket_cmd, + "no clock-socket", + NO_STR "Disable the clock socket for this endpoint\n") +{ + struct proxy_trx *trx = vty->index; + + osmo_trx_ep_set_clock_socket(trx->ep, false); + + return CMD_SUCCESS; +} + +static int config_write_proxy(struct vty *vty) +{ + struct proxy_trx *trx; + + vty_out(vty, "proxy%s", VTY_NEWLINE); + if (g_proxy_ctx->bind_addr) + vty_out(vty, " bind-addr %s%s", g_proxy_ctx->bind_addr, VTY_NEWLINE); + + llist_for_each_entry(trx, &g_proxy_ctx->trx_list, list) { + const char *raddr = osmo_trx_ep_get_raddr(trx->ep); + const char *laddr = osmo_trx_ep_get_laddr(trx->ep); + + vty_out(vty, " ep %s%s", trx->name, VTY_NEWLINE); + if (raddr) + vty_out(vty, " remote-addr %s%s", raddr, VTY_NEWLINE); + if (laddr && strcmp(laddr, g_proxy_ctx->bind_addr) != 0) + vty_out(vty, " bind-addr %s%s", laddr, VTY_NEWLINE); + vty_out(vty, " base-port %u%s", osmo_trx_ep_get_base_port(trx->ep), VTY_NEWLINE); + vty_out(vty, " num-chans %u%s", osmo_trx_ep_get_num_chans(trx->ep), VTY_NEWLINE); + + if (!osmo_trx_ep_get_clock_socket(trx->ep)) + vty_out(vty, " no clock-socket%s", VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(show_proxy, + show_proxy_cmd, + "show proxy", + SHOW_STR "Display configured virtual TRX endpoints\n") +{ + struct proxy_trx *trx; + + llist_for_each_entry(trx, &g_proxy_ctx->trx_list, list) { + vty_out(vty, "Endpoint '%s': %s:%u, %u channel(s), %s%s", + trx->name, + osmo_trx_ep_get_raddr(trx->ep), + osmo_trx_ep_get_base_port(trx->ep), + osmo_trx_ep_get_num_chans(trx->ep), + osmo_trx_ep_is_open(trx->ep) ? "open" : "closed", + VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +/*! Called when leaving EP_NODE (interactively via 'exit'/'end', or on dedent + * while reading a config file): validate the endpoint config and open it, + * unless it is already open. Returns 0 on success, non-zero if the node + * should not be left (so the user/config can fix the problem). */ +static int proxy_vty_ep_node_exit(struct vty *vty, struct proxy_trx *trx) +{ + int rc = proxy_trx_open(trx); + + if (rc < 0 && rc != -EALREADY) { + vty_out(vty, "%% Endpoint '%s': failed to open%s", + trx->name, VTY_NEWLINE); + return -1; + } + + return 0; +} + +static int proxy_vty_go_parent(struct vty *vty) +{ + switch (vty->node) { + case PROXY_NODE: + vty->node = CONFIG_NODE; + break; + case EP_NODE: + if (proxy_vty_ep_node_exit(vty, vty->index) != 0) + break; + vty->node = PROXY_NODE; + vty->index = g_proxy_ctx; + break; + default: + vty->node = CONFIG_NODE; + } + + return vty->node; +} + +static struct vty_app_info g_vty_info = { + .name = "OsmoTRXProxy", + .copyright = + "Copyright (C) 2026 by sysmocom - s.f.m.c. GmbH info@sysmocom.de\r\n" + "Author: Vadim Yanitskiy vyanitskiy@sysmocom.de\r\n" + "License AGPLv3+: GNU AGPL version 3 or later " + "http://gnu.org/licenses/agpl-3.0.html\r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n", + .go_parent_cb = proxy_vty_go_parent, +}; + +int proxy_vty_init(void) +{ + g_vty_info.tall_ctx = g_talloc_ctx; + vty_init(&g_vty_info); + + logging_vty_add_cmds(); + osmo_talloc_vty_add_cmds(); + osmo_cpu_sched_vty_init(g_talloc_ctx); + + install_element_ve(&show_proxy_cmd); + + install_element(CONFIG_NODE, &cfg_proxy_cmd); + install_node(&proxy_node, config_write_proxy); + install_element(PROXY_NODE, &cfg_proxy_bind_addr_cmd); + install_element(PROXY_NODE, &cfg_proxy_ep_cmd); + install_element(PROXY_NODE, &cfg_no_proxy_ep_cmd); + + install_node(&ep_node, dummy_config_write); + install_element(EP_NODE, &cfg_ep_remote_addr_cmd); + install_element(EP_NODE, &cfg_ep_bind_addr_cmd); + install_element(EP_NODE, &cfg_ep_base_port_cmd); + install_element(EP_NODE, &cfg_ep_num_chans_cmd); + install_element(EP_NODE, &cfg_ep_clock_socket_cmd); + install_element(EP_NODE, &cfg_ep_no_clock_socket_cmd); + + return 0; +}