neels has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-upf/+/28310 )
Change subject: add osmo-pfcp-tool ......................................................................
add osmo-pfcp-tool
Related: SYS#5599 Change-Id: I34a80d43a14c7b68952c7d337d8042d6f28ceae7 --- M configure.ac A contrib/osmo-pfcp-tool-scripts/assoc_setup.vty A contrib/osmo-pfcp-tool-scripts/assoc_setup_retrans.vty A contrib/osmo-pfcp-tool-scripts/encaps_plus_tunmap.vty A contrib/osmo-pfcp-tool-scripts/endecaps_session_est.vty A contrib/osmo-pfcp-tool-scripts/heartbeat.vty A contrib/osmo-pfcp-tool-scripts/osmo-pfcp-tool.cfg A contrib/osmo-pfcp-tool-scripts/osmo-upf-11.cfg A contrib/osmo-pfcp-tool-scripts/osmo-upf-12.cfg A contrib/osmo-pfcp-tool-scripts/session_est_without_assoc.vty A contrib/osmo-pfcp-tool-scripts/session_mod.vty A contrib/osmo-pfcp-tool-scripts/tunmap_session_est.vty M src/Makefile.am A src/osmo-pfcp-tool/Makefile.am A src/osmo-pfcp-tool/osmo_pfcp_tool_main.c A src/osmo-pfcp-tool/pfcp_tool.c A src/osmo-pfcp-tool/pfcp_tool.h A src/osmo-pfcp-tool/pfcp_tool_vty.c 18 files changed, 1,624 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-upf refs/changes/10/28310/1
diff --git a/configure.ac b/configure.ac index fea27c6..3522af0 100644 --- a/configure.ac +++ b/configure.ac @@ -205,6 +205,7 @@ include/osmocom/upf/Makefile src/Makefile src/osmo-upf/Makefile + src/osmo-pfcp-tool/Makefile tests/Makefile tests/atlocal doc/Makefile diff --git a/contrib/osmo-pfcp-tool-scripts/assoc_setup.vty b/contrib/osmo-pfcp-tool-scripts/assoc_setup.vty new file mode 100644 index 0000000..9877acb --- /dev/null +++ b/contrib/osmo-pfcp-tool-scripts/assoc_setup.vty @@ -0,0 +1,3 @@ +pfcp-peer 127.0.0.1 + tx assoc-setup-req + sleep 1 diff --git a/contrib/osmo-pfcp-tool-scripts/assoc_setup_retrans.vty b/contrib/osmo-pfcp-tool-scripts/assoc_setup_retrans.vty new file mode 100644 index 0000000..c580abb --- /dev/null +++ b/contrib/osmo-pfcp-tool-scripts/assoc_setup_retrans.vty @@ -0,0 +1,8 @@ +pfcp-peer 127.0.0.1 + tx assoc-setup-req + sleep 3 + retrans req + sleep 5 + retrans req + sleep 1 + retrans req diff --git a/contrib/osmo-pfcp-tool-scripts/encaps_plus_tunmap.vty b/contrib/osmo-pfcp-tool-scripts/encaps_plus_tunmap.vty new file mode 100644 index 0000000..e84deee --- /dev/null +++ b/contrib/osmo-pfcp-tool-scripts/encaps_plus_tunmap.vty @@ -0,0 +1,30 @@ +# ACCESS HOP CORE +# session 23 = tunmap session 42 = encaps/decaps +# GTP 127.0.0.13 127.0.0.12 127.0.0.11 +# TEID l:23 r:123 <---> r:23 l:123 | l:142 r:42 <---> r:142 l:42 | 192.168.100.42 +# +# Run two UPF, one listening on / sending from 127.0.0.11, the other on 127.0.0.12. +# (Each has to match on the sender address of incoming GTP packets.) + +timer pfcp x23 0 + +pfcp-peer 127.0.0.11 + tx assoc-setup-req + sleep 1 + session endecaps 42 + ue ip 192.168.100.42 + gtp access ip 127.0.0.12 + gtp access teid local 42 remote 142 + tx session-est-req + sleep 1 + +pfcp-peer 127.0.0.12 + tx assoc-setup-req + sleep 1 + session tunmap 23 + gtp core ip 127.0.0.11 + gtp core teid local 142 remote 42 + gtp access ip 127.0.0.13 + gtp access teid local 123 remote 23 + tx session-est-req + sleep 1 diff --git a/contrib/osmo-pfcp-tool-scripts/endecaps_session_est.vty b/contrib/osmo-pfcp-tool-scripts/endecaps_session_est.vty new file mode 100644 index 0000000..9a19d8c --- /dev/null +++ b/contrib/osmo-pfcp-tool-scripts/endecaps_session_est.vty @@ -0,0 +1,8 @@ +timer pfcp x23 0 +pfcp-peer 127.0.0.1 + tx assoc-setup-req + sleep 1 + session endecaps + tx session-est-req forw + sleep 5 + tx session-del-req diff --git a/contrib/osmo-pfcp-tool-scripts/heartbeat.vty b/contrib/osmo-pfcp-tool-scripts/heartbeat.vty new file mode 100644 index 0000000..5ac4bf0 --- /dev/null +++ b/contrib/osmo-pfcp-tool-scripts/heartbeat.vty @@ -0,0 +1,10 @@ +pfcp-peer 127.0.0.1 + tx heartbeat + sleep 2 + tx heartbeat + sleep 2 + tx heartbeat + sleep 2 + tx heartbeat + sleep 2 + tx heartbeat diff --git a/contrib/osmo-pfcp-tool-scripts/osmo-pfcp-tool.cfg b/contrib/osmo-pfcp-tool-scripts/osmo-pfcp-tool.cfg new file mode 100644 index 0000000..a6d4635 --- /dev/null +++ b/contrib/osmo-pfcp-tool-scripts/osmo-pfcp-tool.cfg @@ -0,0 +1,5 @@ +log stderr + logging level set-all info + +local-addr 127.0.0.2 +listen diff --git a/contrib/osmo-pfcp-tool-scripts/osmo-upf-11.cfg b/contrib/osmo-pfcp-tool-scripts/osmo-upf-11.cfg new file mode 100644 index 0000000..0184d46 --- /dev/null +++ b/contrib/osmo-pfcp-tool-scripts/osmo-upf-11.cfg @@ -0,0 +1,27 @@ +log stderr + logging filter all 1 + logging color 1 + logging print level 1 + logging print category 1 + logging print category-hex 0 + logging print file basename last + logging print extended-timestamp 1 + logging level set-all notice + logging level set-all info + logging level session debug + logging level nft debug + logging level gtp debug +#logging level set-all debug + +line vty + bind 127.0.0.11 +ctrl + bind 127.0.0.11 + +timer pfcp x24 5000 +pfcp + local-addr 127.0.0.11 +gtp + dev create apn11 127.0.0.11 +nft + table-name osmo-upf-11 diff --git a/contrib/osmo-pfcp-tool-scripts/osmo-upf-12.cfg b/contrib/osmo-pfcp-tool-scripts/osmo-upf-12.cfg new file mode 100644 index 0000000..1fa620d --- /dev/null +++ b/contrib/osmo-pfcp-tool-scripts/osmo-upf-12.cfg @@ -0,0 +1,27 @@ +log stderr + logging filter all 1 + logging color 1 + logging print level 1 + logging print category 1 + logging print category-hex 0 + logging print file basename last + logging print extended-timestamp 1 + logging level set-all notice + logging level set-all info + logging level session debug + logging level nft debug + logging level gtp debug +#logging level set-all debug + +line vty + bind 127.0.0.12 +ctrl + bind 127.0.0.12 + +timer pfcp x24 5000 +pfcp + local-addr 127.0.0.12 +gtp + dev create apn12 127.0.0.12 +nft + table-name osmo-upf-12 diff --git a/contrib/osmo-pfcp-tool-scripts/session_est_without_assoc.vty b/contrib/osmo-pfcp-tool-scripts/session_est_without_assoc.vty new file mode 100644 index 0000000..2472292 --- /dev/null +++ b/contrib/osmo-pfcp-tool-scripts/session_est_without_assoc.vty @@ -0,0 +1,4 @@ +timer pfcp x23 0 +pfcp-peer 127.0.0.1 + session endecaps + tx session-est-req diff --git a/contrib/osmo-pfcp-tool-scripts/session_mod.vty b/contrib/osmo-pfcp-tool-scripts/session_mod.vty new file mode 100644 index 0000000..7645c05 --- /dev/null +++ b/contrib/osmo-pfcp-tool-scripts/session_mod.vty @@ -0,0 +1,14 @@ +timer pfcp x23 0 +pfcp-peer 127.0.0.1 + tx assoc-setup-req + sleep 1 + session + tx session-est-req drop + sleep 3 + tx session-mod-req forw + sleep 5 + tx session-mod-req drop + sleep 3 + tx session-mod-req forw + sleep 3 + tx session-del-req diff --git a/contrib/osmo-pfcp-tool-scripts/tunmap_session_est.vty b/contrib/osmo-pfcp-tool-scripts/tunmap_session_est.vty new file mode 100644 index 0000000..b01b088 --- /dev/null +++ b/contrib/osmo-pfcp-tool-scripts/tunmap_session_est.vty @@ -0,0 +1,8 @@ +timer pfcp x23 0 +pfcp-peer 127.0.0.1 + tx assoc-setup-req + sleep 1 + session tunmap + tx session-est-req + sleep 5 + tx session-del-req diff --git a/src/Makefile.am b/src/Makefile.am index a8ba763..08ceed7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,3 +1,4 @@ SUBDIRS = \ osmo-upf \ + osmo-pfcp-tool \ $(NULL) diff --git a/src/osmo-pfcp-tool/Makefile.am b/src/osmo-pfcp-tool/Makefile.am new file mode 100644 index 0000000..6c23d9f --- /dev/null +++ b/src/osmo-pfcp-tool/Makefile.am @@ -0,0 +1,39 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMOGTLV_CFLAGS) \ + $(LIBOSMOPFCP_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOGTLV_LIBS) \ + $(LIBOSMOPFCP_LIBS) \ + $(COVERAGE_LDFLAGS) \ + $(NULL) + +noinst_HEADERS = \ + pfcp_tool.h \ + $(NULL) + +bin_PROGRAMS = \ + osmo-pfcp-tool \ + $(NULL) + +osmo_pfcp_tool_SOURCES = \ + osmo_pfcp_tool_main.c \ + pfcp_tool.c \ + pfcp_tool_vty.c \ + $(NULL) diff --git a/src/osmo-pfcp-tool/osmo_pfcp_tool_main.c b/src/osmo-pfcp-tool/osmo_pfcp_tool_main.c new file mode 100644 index 0000000..9a4de1c --- /dev/null +++ b/src/osmo-pfcp-tool/osmo_pfcp_tool_main.c @@ -0,0 +1,372 @@ +/* + * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr nhofmeyr@sysmocom.de + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#include <osmocom/core/application.h> +#include <osmocom/core/signal.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/fsm.h> +#include <osmocom/core/stats.h> +#include <osmocom/core/msgb.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> +#include <osmocom/vty/cpu_sched_vty.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/ports.h> +#include <osmocom/vty/tdef_vty.h> +#include <osmocom/ctrl/control_if.h> +#include <osmocom/ctrl/control_vty.h> +#include <osmocom/ctrl/ports.h> +#include <osmocom/core/sockaddr_str.h> + +#include <osmocom/pfcp/pfcp_endpoint.h> + +#include "pfcp_tool.h" + +#define _GNU_SOURCE +#include <getopt.h> + +/* build switches from the configure script */ +#include "config.h" + +#include <signal.h> +#include <stdio.h> +#include <string.h> + +extern void *tall_vty_ctx; + +void *tall_pfcp_tool_ctx = NULL; +static int quit = 0; + +static struct { + const char *config_file; + int daemonize; + enum vty_ref_gen_mode vty_ref_gen_mode; + const char *command_file; +} pfcp_tool_cmdline_config = { + .config_file = "osmo-pfcp-tool.cfg", + .vty_ref_gen_mode = VTY_REF_GEN_MODE_DEFAULT, +}; + +static void print_usage() +{ + printf("Usage: osmo-pfcp-tool [command-file.vty]\n telnet localhost %d\n", OSMO_VTY_PORT_PFCP_TOOL); +} + +static void print_help() +{ + const struct value_string *vty_ref_gen_mode_name; + + printf("Some useful options:\n"); + printf(" -h --help This text.\n"); + printf(" -D --daemonize Fork the process into a background daemon.\n"); + printf(" -c --config-file filename The config file to use, for logging etc.\n"); + printf(" -V --version Print the version of OsmoMSC.\n"); + + printf("\nVTY reference generation:\n"); + printf(" --vty-ref-xml Generate the VTY reference XML output and exit.\n"); + printf(" --vty-ref-mode MODE Mode for --vty-ref-xml:\n"); + /* List all VTY ref gen modes */ + for (vty_ref_gen_mode_name = vty_ref_gen_mode_names; vty_ref_gen_mode_name->str; vty_ref_gen_mode_name++) + printf(" %s: %s\n", + vty_ref_gen_mode_name->str, + get_value_string(vty_ref_gen_mode_desc, vty_ref_gen_mode_name->value)); +} + +static void handle_long_options(const char *prog_name, const int long_option) +{ + switch (long_option) { + case 1: + pfcp_tool_cmdline_config.vty_ref_gen_mode = get_string_value(vty_ref_gen_mode_names, optarg); + if (pfcp_tool_cmdline_config.vty_ref_gen_mode < 0) { + fprintf(stderr, "%s: Unknown VTY reference generation mode: '%s'\n", prog_name, optarg); + exit(2); + } + break; + case 2: + fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n", + get_value_string(vty_ref_gen_mode_names, pfcp_tool_cmdline_config.vty_ref_gen_mode), + get_value_string(vty_ref_gen_mode_desc, pfcp_tool_cmdline_config.vty_ref_gen_mode)); + vty_dump_xml_ref_mode(stdout, pfcp_tool_cmdline_config.vty_ref_gen_mode); + exit(0); + default: + fprintf(stderr, "%s: error parsing cmdline options\n", prog_name); + exit(2); + } +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static int long_option = 0; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"daemonize", 0, 0, 'D'}, + {"config-file", 1, 0, 'c'}, + {"version", 0, 0, 'V' }, + {"vty-ref-mode", 1, &long_option, 1}, + {"vty-ref-xml", 0, &long_option, 2}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hDc:V", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage(); + print_help(); + exit(0); + case 0: + handle_long_options(argv[0], long_option); + break; + case 'D': + pfcp_tool_cmdline_config.daemonize = 1; + break; + case 'c': + pfcp_tool_cmdline_config.config_file = optarg; + break; + case 'V': + print_version(1); + exit(0); + break; + default: + /* catch unknown options *as well as* missing arguments. */ + fprintf(stderr, "%s: Error in command line options. Exiting.\n", argv[0]); + exit(-1); + } + } + + if (argc > optind) { + pfcp_tool_cmdline_config.command_file = argv[optind]; + optind++; + } + + if (argc > optind) { + fprintf(stderr, "%s: Unsupported positional arguments on command line\n", argv[optind]); + exit(2); + } +} + +static void signal_handler(int signum) +{ + fprintf(stdout, "signal %u received\n", signum); + + switch (signum) { + case SIGINT: + case SIGTERM: + LOGP(DLGLOBAL, LOGL_NOTICE, "Terminating due to signal %d\n", signum); + quit++; + break; + case SIGABRT: + osmo_generate_backtrace(); + /* in case of abort, we want to obtain a talloc report and + * then run default SIGABRT handler, who will generate coredump + * and abort the process. abort() should do this for us after we + * return, but program wouldn't exit if an external SIGABRT is + * received. + */ + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(tall_pfcp_tool_ctx, stderr); + signal(SIGABRT, SIG_DFL); + raise(SIGABRT); + break; + case SIGUSR1: + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(tall_pfcp_tool_ctx, stderr); + break; + case SIGUSR2: + talloc_report_full(tall_vty_ctx, stderr); + break; + default: + break; + } +} + +static const char * const osmo_pfcp_tool_copyright = + "OsmoPFCPTool - Osmocom Packet Forwarding Control Protocol tool for testing\r\n" + "Copyright (C) 2021-2022 by sysmocom - s.f.m.c. GmbH info@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"; + +static struct vty_app_info pfcp_tool_vty_app_info = { + .name = "osmo-pfcp-tool", + .version = PACKAGE_VERSION, + .copyright = osmo_pfcp_tool_copyright, +}; + +static const struct log_info_cat pfcp_tool_default_categories[] = { +}; + +const struct log_info log_info = { + .cat = pfcp_tool_default_categories, + .num_cat = ARRAY_SIZE(pfcp_tool_default_categories), +}; + +int pfcp_tool_mainloop() +{ + log_reset_context(); + osmo_select_main_ctx(0); + + /* If the user hits Ctrl-C the third time, just terminate immediately. */ + if (quit >= 3) + return 1; + + /* Has SIGTERM been received (and not yet been handled)? */ + if (quit && !osmo_select_shutdown_requested()) { + osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL); + + /* Request write-only mode in osmo_select_main_ctx() */ + osmo_select_shutdown_request(); + /* continue the main select loop until all write queues are serviced. */ + } + return 0; +} + +int main(int argc, char **argv) +{ + int rc; + + /* Track the use of talloc NULL memory contexts */ + talloc_enable_null_tracking(); + + osmo_fsm_set_dealloc_ctx(OTC_SELECT); + + tall_pfcp_tool_ctx = talloc_named_const(NULL, 1, "osmo-pfcp-tool"); + pfcp_tool_vty_app_info.tall_ctx = tall_pfcp_tool_ctx; + + msgb_talloc_ctx_init(tall_pfcp_tool_ctx, 0); + osmo_signal_talloc_ctx_init(tall_pfcp_tool_ctx); + + osmo_init_logging2(tall_pfcp_tool_ctx, &log_info); + 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); + log_set_print_extended_timestamp(osmo_stderr_target, 1); + + osmo_fsm_log_timeouts(true); + osmo_fsm_log_addr(true); + + osmo_stats_init(tall_pfcp_tool_ctx); + + g_pfcp_tool_alloc(tall_pfcp_tool_ctx); + + /* For --version, vty_init() must be called before handling options */ + vty_init(&pfcp_tool_vty_app_info); + + ctrl_vty_init(tall_pfcp_tool_ctx); + logging_vty_add_cmds(); + osmo_talloc_vty_add_cmds(); + osmo_cpu_sched_vty_init(tall_pfcp_tool_ctx); + osmo_fsm_vty_add_cmds(); + osmo_tdef_vty_groups_init(CONFIG_NODE, g_pfcp_tool_tdef_groups); + + pfcp_tool_vty_init_cfg(); + + /* Parse options */ + handle_options(argc, argv); + + if (pfcp_tool_cmdline_config.config_file) { + rc = vty_read_config_file(pfcp_tool_cmdline_config.config_file, NULL); + if (rc < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "Failed to parse the config file: '%s'\n", + pfcp_tool_cmdline_config.config_file); + } + } + + /* start telnet, after reading config for vty_get_bind_addr() */ + rc = telnet_init_dynif(tall_pfcp_tool_ctx, &g_pfcp_tool, vty_get_bind_addr(), OSMO_VTY_PORT_PFCP_TOOL); + if (rc < 0) + return 2; + + /* start control interface, after reading config for ctrl_vty_get_bind_addr() */ + g_pfcp_tool->ctrl = ctrl_interface_setup_dynip(g_pfcp_tool, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_PFCP_TOOL, NULL); + if (!g_pfcp_tool->ctrl) { + fprintf(stderr, "Failed to initialize control interface. Exiting.\n"); + return -1; + } + + signal(SIGINT, &signal_handler); + signal(SIGTERM, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + osmo_init_ignore_signals(); + + if (pfcp_tool_cmdline_config.daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + return 6; + } + } + + pfcp_tool_mainloop(); + + pfcp_tool_vty_init_cmds(); + + if (pfcp_tool_cmdline_config.command_file) { + printf("Reading '%s'\n", pfcp_tool_cmdline_config.command_file); + rc = vty_read_config_file(pfcp_tool_cmdline_config.command_file, NULL); + if (rc < 0) { + LOGP(DLGLOBAL, LOGL_FATAL, "Failed to parse the command file: '%s'\n", + pfcp_tool_cmdline_config.command_file); + return 1; + } + printf("Done reading '%s', waiting for retransmission queue...\n", + pfcp_tool_cmdline_config.command_file); + do { + if (pfcp_tool_mainloop()) + break; + } while (!llist_empty(&g_pfcp_tool->ep->sent_requests) + || !llist_empty(&g_pfcp_tool->ep->sent_responses)); + printf("Done\n"); + } else { + printf("Listening for commands on VTY...\n"); + do { + if (pfcp_tool_mainloop()) + break; + } while (!osmo_select_shutdown_done()); + } + + osmo_pfcp_endpoint_free(&g_pfcp_tool->ep); + + log_fini(); + + /* Report the heap state of talloc contexts, then free, so both ASAN and Valgrind are happy... */ + //talloc_report_full(tall_pfcp_tool_ctx, stderr); + talloc_free(tall_pfcp_tool_ctx); + + //talloc_report_full(tall_vty_ctx, stderr); + talloc_free(tall_vty_ctx); + + //talloc_report_full(NULL, stderr); + talloc_disable_null_tracking(); + return 0; +} diff --git a/src/osmo-pfcp-tool/pfcp_tool.c b/src/osmo-pfcp-tool/pfcp_tool.c new file mode 100644 index 0000000..aebb92c --- /dev/null +++ b/src/osmo-pfcp-tool/pfcp_tool.c @@ -0,0 +1,176 @@ +/* + * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr nhofmeyr@sysmocom.de + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#include <osmocom/core/utils.h> +#include <osmocom/core/talloc.h> + +#include <osmocom/pfcp/pfcp_endpoint.h> + +#include "pfcp_tool.h" + +struct g_pfcp_tool *g_pfcp_tool = NULL; + +struct osmo_tdef_group g_pfcp_tool_tdef_groups[] = { + { .name = "pfcp", .tdefs = osmo_pfcp_tdefs, .desc = "PFCP" }, + {} +}; + +void g_pfcp_tool_alloc(void *ctx) +{ + OSMO_ASSERT(g_pfcp_tool == NULL); + g_pfcp_tool = talloc_zero(ctx, struct g_pfcp_tool); + + *g_pfcp_tool = (struct g_pfcp_tool){ + .vty_cfg = { + .local_ip = talloc_strdup(g_pfcp_tool, "0.0.0.0"), + .local_port = OSMO_PFCP_PORT, + }, + }; + + INIT_LLIST_HEAD(&g_pfcp_tool->peers); +} + +struct pfcp_tool_peer *pfcp_tool_peer_find(const struct osmo_sockaddr *remote_addr) +{ + struct pfcp_tool_peer *peer; + llist_for_each_entry(peer, &g_pfcp_tool->peers, entry) { + if (osmo_sockaddr_cmp(&peer->remote_addr, remote_addr) == 0) + return peer; + } + return NULL; +} + +struct pfcp_tool_peer *pfcp_tool_peer_find_or_create(const struct osmo_sockaddr *remote_addr) +{ + struct pfcp_tool_peer *peer = pfcp_tool_peer_find(remote_addr); + if (peer) + return peer; + + peer = talloc_zero(g_pfcp_tool, struct pfcp_tool_peer); + peer->remote_addr = *remote_addr; + peer->next_seid_state = 0x1234567; + INIT_LLIST_HEAD(&peer->sessions); + llist_add(&peer->entry, &g_pfcp_tool->peers); + return peer; +} + +struct pfcp_tool_session *pfcp_tool_session_find(struct pfcp_tool_peer *peer, uint64_t cp_seid) +{ + struct pfcp_tool_session *session; + llist_for_each_entry(session, &peer->sessions, entry) { + if (session->cp_seid == cp_seid) + return session; + } + return NULL; +} + +struct pfcp_tool_session *pfcp_tool_session_find_or_create(struct pfcp_tool_peer *peer, uint64_t cp_seid, + enum up_gtp_action_kind gtp_action) +{ + struct pfcp_tool_session *session = pfcp_tool_session_find(peer, cp_seid); + if (session) + return session; + + session = talloc(peer, struct pfcp_tool_session); + *session = (struct pfcp_tool_session){ + .peer = peer, + .cp_seid = cp_seid, + .gtp_action = gtp_action, + }; + llist_add(&session->entry, &peer->sessions); + return session; +} + +static void rx_assoc_setup_resp(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m) +{ + if (m->ies.assoc_setup_resp.up_function_features_present) + OSMO_LOG_PFCP_MSG(m, LOGL_NOTICE, "Associated. UP Peer features: %s\n", + osmo_pfcp_bits_to_str_c(OTC_SELECT, + m->ies.assoc_setup_resp.up_function_features.bits, + osmo_pfcp_up_feature_strs)); + + if (m->ies.assoc_setup_resp.cp_function_features_present) + OSMO_LOG_PFCP_MSG(m, LOGL_NOTICE, "Associated. CP Peer features: %s\n", + osmo_pfcp_bits_to_str_c(OTC_SELECT, + m->ies.assoc_setup_resp.cp_function_features.bits, + osmo_pfcp_cp_feature_strs)); +} + +static void rx_session_est_resp(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m) +{ + struct pfcp_tool_peer *peer; + struct pfcp_tool_session *session; + enum osmo_pfcp_cause *cause = osmo_pfcp_msg_cause(m); + if (!cause) { + OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Session Establishment Response should contain a Cause\n"); + return; + } + if (*cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) { + OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer responds that Session Establishment failed\n"); + return; + } + if (!m->h.seid_present) { + OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Session Establishment Response should contain a SEID\n"); + return; + } + if (!m->ies.session_est_resp.up_f_seid_present) { + OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Session Establishment Response without UP F-SEID\n"); + return; + } + peer = pfcp_tool_peer_find(&m->remote_addr); + if (!peer) + return; + session = pfcp_tool_session_find(peer, m->h.seid); + if (!session) + return; + session->up_f_seid = m->ies.session_est_resp.up_f_seid; +} + +void pfcp_tool_rx_msg(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req) +{ + switch (m->h.message_type) { + case OSMO_PFCP_MSGT_ASSOC_SETUP_RESP: + rx_assoc_setup_resp(ep, m); + break; + case OSMO_PFCP_MSGT_SESSION_EST_RESP: + rx_session_est_resp(ep, m); + break; + default: break; + } +} + +int peer_tx(struct pfcp_tool_peer *peer, struct osmo_pfcp_msg *m) +{ + int rc; + rc = osmo_pfcp_endpoint_tx(g_pfcp_tool->ep, m); + if (m->is_response) + peer->last_resp = *m; + else + peer->last_req = *m; + return rc; +} + +uint64_t peer_new_seid(struct pfcp_tool_peer *peer) +{ + return peer->next_seid_state++; +} diff --git a/src/osmo-pfcp-tool/pfcp_tool.h b/src/osmo-pfcp-tool/pfcp_tool.h new file mode 100644 index 0000000..9f04efd --- /dev/null +++ b/src/osmo-pfcp-tool/pfcp_tool.h @@ -0,0 +1,106 @@ +/* Global definitions for osmo-pfcp-tool */ +/* + * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr nhofmeyr@sysmocom.de + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/tdef.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/sockaddr_str.h> + +#include <osmocom/pfcp/pfcp_msg.h> + +#include <osmocom/upf/up_gtp_action.h> + +struct osmo_tdef; +struct ctrl_handle; + +extern struct osmo_tdef g_pfcp_tool_tdefs[]; +extern struct osmo_tdef_group g_pfcp_tool_tdef_groups[]; + +struct pfcp_tool_peer { + struct llist_head entry; + + struct osmo_sockaddr remote_addr; + struct osmo_pfcp_msg last_req; + struct osmo_pfcp_msg last_resp; + + uint64_t next_seid_state; + + struct llist_head sessions; +}; + +struct pfcp_tool_teid_pair { + uint32_t local; + uint32_t remote; +}; + +struct pfcp_tool_session { + struct llist_head entry; + + enum up_gtp_action_kind gtp_action; + + struct pfcp_tool_peer *peer; + uint64_t cp_seid; + struct osmo_pfcp_ie_f_seid up_f_seid; + + struct { + struct pfcp_tool_teid_pair teid; + struct osmo_sockaddr_str gtp_ip; + } access; + + struct { + struct pfcp_tool_teid_pair teid; + struct osmo_sockaddr_str gtp_ip; + struct osmo_sockaddr_str ue_addr; + } core; +}; + +struct g_pfcp_tool { + struct ctrl_handle *ctrl; + + struct { + char *local_ip; + uint16_t local_port; + } vty_cfg; + + struct osmo_pfcp_endpoint *ep; + struct llist_head peers; +}; + +extern struct g_pfcp_tool *g_pfcp_tool; + +void g_pfcp_tool_alloc(void *ctx); +void pfcp_tool_vty_init_cfg(); +void pfcp_tool_vty_init_cmds(); + +int pfcp_tool_mainloop(); + +struct pfcp_tool_peer *pfcp_tool_peer_find_or_create(const struct osmo_sockaddr *remote_addr); +struct pfcp_tool_session *pfcp_tool_session_find_or_create(struct pfcp_tool_peer *peer, uint64_t cp_seid, + enum up_gtp_action_kind kind); +void pfcp_tool_rx_msg(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req); + +int peer_tx(struct pfcp_tool_peer *peer, struct osmo_pfcp_msg *m); +uint64_t peer_new_seid(struct pfcp_tool_peer *peer); diff --git a/src/osmo-pfcp-tool/pfcp_tool_vty.c b/src/osmo-pfcp-tool/pfcp_tool_vty.c new file mode 100644 index 0000000..988484e --- /dev/null +++ b/src/osmo-pfcp-tool/pfcp_tool_vty.c @@ -0,0 +1,785 @@ +/* osmo-pfcp-tool interface to quagga VTY */ +/* + * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr nhofmeyr@sysmocom.de + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#include <stdlib.h> +#include <unistd.h> + +#include <osmocom/core/sockaddr_str.h> +#include <osmocom/core/socket.h> + +#include <osmocom/pfcp/pfcp_endpoint.h> +#include <osmocom/pfcp/pfcp_msg.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> + +#include "pfcp_tool.h" + +enum pfcp_tool_vty_node { + PEER_NODE = _LAST_OSMOVTY_NODE + 1, + SESSION_NODE, +}; + +DEFUN(c_local_addr, c_local_addr_cmd, + "local-addr IP_ADDR", + "Set the local IP address to bind on for PFCP; see also 'listen'\n" + "IP address\n") +{ + if (g_pfcp_tool->ep != NULL) { + vty_out(vty, "Already listening on %s%s", + osmo_sockaddr_to_str_c(OTC_SELECT, &g_pfcp_tool->ep->cfg.local_addr), + VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_talloc_replace_string(g_pfcp_tool, &g_pfcp_tool->vty_cfg.local_ip, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(c_listen, c_listen_cmd, + "listen", + "Bind local PFCP port and listen; see also 'local-addr'\n") +{ + struct osmo_sockaddr_str local_addr; + int rc; + + OSMO_ASSERT(g_pfcp_tool); + if (g_pfcp_tool->ep != NULL) { + vty_out(vty, "Already listening on %s%s", + osmo_sockaddr_to_str_c(OTC_SELECT, &g_pfcp_tool->ep->cfg.local_addr), + VTY_NEWLINE); + return CMD_WARNING; + } + + g_pfcp_tool->ep = osmo_pfcp_endpoint_create(g_pfcp_tool, g_pfcp_tool); + if (!g_pfcp_tool->ep) { + vty_out(vty, "Failed to allocate PFCP endpoint.%s", VTY_NEWLINE); + return CMD_WARNING; + } + g_pfcp_tool->ep->rx_msg = pfcp_tool_rx_msg; + g_pfcp_tool->ep->seq_nr_state = rand(); + + /* Translate address string from VTY config to osmo_sockaddr: first read into osmo_sockaddr_str, then write to + * osmo_sockaddr. */ + osmo_sockaddr_str_from_str(&local_addr, g_pfcp_tool->vty_cfg.local_ip, + g_pfcp_tool->vty_cfg.local_port); + osmo_sockaddr_str_to_sockaddr(&local_addr, &g_pfcp_tool->ep->cfg.local_addr.u.sas); + + /* Store this address as the local PFCP Node Id */ + osmo_pfcp_ie_node_id_from_osmo_sockaddr(&g_pfcp_tool->ep->cfg.local_node_id, &g_pfcp_tool->ep->cfg.local_addr); + + rc = osmo_pfcp_endpoint_bind(g_pfcp_tool->ep); + if (rc) { + vty_out(vty, "Failed to bind PFCP endpoint on %s: %s%s\n", + osmo_sockaddr_to_str_c(OTC_SELECT, &g_pfcp_tool->ep->cfg.local_addr), strerror(rc), + VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(c_sleep, c_sleep_cmd, + "sleep <0-999999> [<0-999>]", + "Let some time pass\n" + "Seconds to wait\n") +{ + int secs = atoi(argv[0]); + int msecs = 0; + struct osmo_timer_list t = {}; + if (argc > 1) + msecs = atoi(argv[1]); + + vty_out(vty, "zzZ %d.%03ds...%s", secs, msecs, VTY_NEWLINE); + vty_flush(vty); + + osmo_timer_setup(&t, NULL, NULL); + osmo_timer_schedule(&t, secs, msecs * 1000); + + /* Still operate the message pump while waiting for time to pass */ + while (t.active && !osmo_select_shutdown_done()) { + if (pfcp_tool_mainloop()) + break; + } + + osmo_timer_del(&t); + vty_out(vty, "...zzZ %d.%03ds%s", secs, msecs, VTY_NEWLINE); + vty_flush(vty); + return CMD_SUCCESS; +} + +static struct cmd_node peer_node = { + PEER_NODE, + "%s(peer)# ", + 1, +}; + +DEFUN(peer, peer_cmd, + "pfcp-peer REMOTE_ADDR", + "Enter the 'peer' node for the given remote address\n" + "Remote PFCP peer's IP address\n") +{ + struct pfcp_tool_peer *peer; + struct osmo_sockaddr_str remote_addr_str; + struct osmo_sockaddr remote_addr; + + osmo_sockaddr_str_from_str(&remote_addr_str, argv[0], OSMO_PFCP_PORT); + osmo_sockaddr_str_to_sockaddr(&remote_addr_str, (struct sockaddr_storage*)&remote_addr); + + peer = pfcp_tool_peer_find_or_create(&remote_addr); + + vty->index = peer; + vty->node = PEER_NODE; + + return CMD_SUCCESS; +} + +#define TX_STR "Send a PFCP message to a peer\n" + +DEFUN(peer_tx_heartbeat, peer_tx_heartbeat_cmd, + "tx heartbeat", + TX_STR "Send a Heartbeat Request\n") +{ + struct pfcp_tool_peer *peer = vty->index; + int rc; + + if (!g_pfcp_tool->ep) { + vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); + return CMD_WARNING; + } + + vty_out(vty, "Tx Heartbeat Request to %s%s", + osmo_sockaddr_to_str_c(OTC_SELECT, &peer->remote_addr), VTY_NEWLINE); + + rc = osmo_pfcp_endpoint_tx_heartbeat_req(g_pfcp_tool->ep, &peer->remote_addr); + if (rc) { + vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(peer_tx_assoc_setup_req, peer_tx_assoc_setup_req_cmd, + "tx assoc-setup-req", + TX_STR "Send an Association Setup Request\n") +{ + struct pfcp_tool_peer *peer = vty->index; + int rc; + struct osmo_pfcp_msg *m; + + if (!g_pfcp_tool->ep) { + vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); + return CMD_WARNING; + } + + m = osmo_pfcp_msg_alloc_tx(OTC_SELECT, &peer->remote_addr, &g_pfcp_tool->ep->cfg.local_node_id, NULL, + OSMO_PFCP_MSGT_ASSOC_SETUP_REQ); + m->ies.assoc_setup_req.recovery_time_stamp = g_pfcp_tool->ep->recovery_time_stamp; + + m->ies.assoc_setup_req.cp_function_features_present = true; + osmo_pfcp_bits_set(m->ies.assoc_setup_req.cp_function_features.bits, OSMO_PFCP_CP_FEAT_BUNDL, true); + + rc = peer_tx(peer, m); + if (rc) { + vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(peer_retrans_req, peer_retrans_req_cmd, + "retrans (req|resp)", + "Retransmit the last sent message\n" "Retransmit the last sent PFCP Request\n" + "Retransmit the last sent PFCP Response\n") +{ + struct pfcp_tool_peer *peer = vty->index; + int rc; + struct osmo_pfcp_msg *m; + + if (!g_pfcp_tool->ep) { + vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); + return CMD_WARNING; + } + + m = osmo_pfcp_msg_alloc_tx(OTC_SELECT, &peer->remote_addr, &g_pfcp_tool->ep->cfg.local_node_id, NULL, 0); + if (strcmp(argv[0], "req") == 0) + *m = peer->last_req; + else + *m = peer->last_resp; + + OSMO_LOG_PFCP_MSG(m, LOGL_DEBUG, "retrans %s\n", argv[0]); + + rc = osmo_pfcp_endpoint_tx_data(g_pfcp_tool->ep, m); + if (rc) { + vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +static struct cmd_node session_node = { + SESSION_NODE, + "%s(session)# ", + 1, +}; + +DEFUN(session, session_cmd, + "session [(endecaps|tunmap)] [<0-18446744073709551615>]", + "Enter the 'session' node for the given SEID\n" + "Set up GTP tunnel encapsulation/decapsulation (default)\n" + "Set up GTP tunnel mapping\n" + "local Session Endpoint ID\n") +{ + struct pfcp_tool_peer *peer = vty->index; + struct pfcp_tool_session *session; + enum up_gtp_action_kind gtp_action = UP_GTP_U_ENDECAPS; + + if (argc > 0 && !strcmp(argv[0], "tunmap")) + gtp_action = UP_GTP_U_TUNMAP; + + if (argc > 1) + session = pfcp_tool_session_find_or_create(peer, atoll(argv[1]), gtp_action); + else + session = pfcp_tool_session_find_or_create(peer, peer_new_seid(peer), gtp_action); + + vty->index = session; + vty->node = SESSION_NODE; + + return CMD_SUCCESS; +} + +DEFUN(s_ue, s_ue_cmd, + "ue ip A.B.C.D", + "Setup the UE as it appears towards the Core network in plain IP traffic\n" + "IP address assigned to the UE\n") +{ + struct pfcp_tool_session *session = vty->index; + if (osmo_sockaddr_str_from_str2(&session->core.ue_addr, argv[0])) { + vty_out(vty, "Error setting UE IP address%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(s_teid, s_teid_cmd, + "gtp (access|core) teid local <0-4294967295> remote <0-4294967295>", + "Setup TEID used in GTP\n" + "Set the TEIDs towards the ACCESS network (towards the radio network and the actual UE)\n" + "Set the TEIDs towards the CORE network (towards the internet)\n" + "Local TEID, which the UPF expects to see in incoming GTP packets\n" + "Local TEID, when 0 tell the UPF to choose (PFCP: FAR F-TEID: CHOOSE=1)\n" + "Remote TEID, which the UPF sends out in GTP packets\n" + "Remote TEID, which the GTP peer has assigned for itself\n") +{ + struct pfcp_tool_session *session = vty->index; + struct pfcp_tool_teid_pair *dst; + if (!strcmp(argv[0], "access")) + dst = &session->access.teid; + else + dst = &session->core.teid; + *dst = (struct pfcp_tool_teid_pair){ + .local = atoi(argv[1]), + .remote = atoi(argv[2]), + }; + return CMD_SUCCESS; +} + +DEFUN(s_gtp, s_gtp_cmd, + "gtp (access|core) ip A.B.C.D", + "Setup GTP peer\n" + "Set the GTP peer towards the ACCESS network (towards the radio network and the actual UE)\n" + "Set the GTP peer towards the CORE network (towards the internet)\n" + "Set the GTP peer IP address, where to send GTP packets to / receive GTP packets from\n" + "GTP peer IP address\n") +{ + struct pfcp_tool_session *session = vty->index; + struct osmo_sockaddr_str *dst; + if (!strcmp(argv[0], "access")) + dst = &session->access.gtp_ip; + else + dst = &session->core.gtp_ip; + if (osmo_sockaddr_str_from_str2(dst, argv[1])) { + vty_out(vty, "Error setting GTP IP address%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +int session_endecaps_tx_est_req(struct vty *vty, const char **argv, int argc) +{ + struct pfcp_tool_session *session = vty->index; + struct pfcp_tool_peer *peer = session->peer; + int rc; + struct osmo_pfcp_msg *m; + struct osmo_pfcp_ie_f_teid f_teid_access_local; + struct osmo_pfcp_ie_outer_header_creation ohc_access; + struct osmo_pfcp_ie_apply_action aa = {}; + struct osmo_sockaddr ue_addr; + struct osmo_pfcp_ie_f_seid cp_f_seid; + + if (!g_pfcp_tool->ep) { + vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (argc > 0 && !strcmp("drop", argv[0])) + osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true); + else + osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_FORW, true); + + if (osmo_sockaddr_str_to_sockaddr(&session->core.ue_addr, &ue_addr.u.sas)) { + vty_out(vty, "Error in UE IP%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (session->access.teid.local == 0) { + f_teid_access_local = (struct osmo_pfcp_ie_f_teid){ + .choose_flag = true, + .choose = { + .ipv4_addr = true, + }, + }; + } else { + f_teid_access_local = (struct osmo_pfcp_ie_f_teid){ + .fixed = { + .teid = session->access.teid.local, + .ip_addr = { + .v4_present = true, + .v4 = g_pfcp_tool->ep->cfg.local_addr, + }, + }, + }; + if (osmo_sockaddr_str_to_sockaddr(&session->access.gtp_ip, &f_teid_access_local.fixed.ip_addr.v4.u.sas)) { + vty_out(vty, "Error in GTP IP towards Access%s", VTY_NEWLINE); + return CMD_WARNING; + } + } + + ohc_access = (struct osmo_pfcp_ie_outer_header_creation){ + .teid_present = true, + .teid = session->access.teid.remote, + .ip_addr.v4_present = true, + }; + osmo_pfcp_bits_set(ohc_access.desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true); + if (osmo_sockaddr_str_to_sockaddr(&session->access.gtp_ip, &ohc_access.ip_addr.v4.u.sas)) { + vty_out(vty, "Error in GTP IP towards Access%s", VTY_NEWLINE); + return CMD_WARNING; + } + + cp_f_seid = (struct osmo_pfcp_ie_f_seid){ + .seid = session->cp_seid, + }; + osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, &g_pfcp_tool->ep->cfg.local_addr); + + m = osmo_pfcp_msg_alloc_tx(OTC_SELECT, &peer->remote_addr, &g_pfcp_tool->ep->cfg.local_node_id, NULL, + OSMO_PFCP_MSGT_SESSION_EST_REQ); + m->h.seid_present = true; + /* the UPF has yet to assign a SEID for itself, no matter what SEID we (the CPF) use for this session */ + m->h.seid = 0; + /* GTP encapsulation decapsulation: remove header from ACCESS to CORE, add header from CORE towards ACCESS */ + m->ies.session_est_req = (struct osmo_pfcp_msg_session_est_req){ + .node_id = m->ies.session_est_req.node_id, + .cp_f_seid_present = true, + .cp_f_seid = cp_f_seid, + .create_pdr_count = 2, + .create_pdr = { + { + .pdr_id = 1, + .precedence = 255, + .pdi = { + .source_iface = OSMO_PFCP_SOURCE_IFACE_CORE, + .ue_ip_address_present = true, + .ue_ip_address = { + .ip_is_destination = true, + .ip_addr = { + .v4_present = true, + .v4 = ue_addr, + }, + }, + }, + .far_id_present = true, + .far_id = 1, + }, + { + .pdr_id = 2, + .precedence = 255, + .pdi = { + .source_iface = OSMO_PFCP_SOURCE_IFACE_ACCESS, + .local_f_teid_present = true, + .local_f_teid = f_teid_access_local, + }, + .outer_header_removal_present = true, + .outer_header_removal = { + .desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4, + }, + .far_id_present = true, + .far_id = 2, + }, + }, + .create_far_count = 2, + .create_far = { + { + .far_id = 1, + .forw_params_present = true, + .forw_params = { + .destination_iface = OSMO_PFCP_DEST_IFACE_ACCESS, + .outer_header_creation_present = true, + .outer_header_creation = ohc_access, + }, + .apply_action = aa, + }, + { + .far_id = 2, + .forw_params_present = true, + .forw_params = { + .destination_iface = OSMO_PFCP_DEST_IFACE_CORE, + }, + .apply_action = aa, + }, + }, + }; + + rc = peer_tx(peer, m); + if (rc) { + vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +int session_tunmap_tx_est_req(struct vty *vty, const char **argv, int argc) +{ + struct pfcp_tool_session *session = vty->index; + struct pfcp_tool_peer *peer = session->peer; + int rc; + struct osmo_pfcp_msg *m; + + struct osmo_pfcp_ie_f_seid cp_f_seid; + + struct osmo_pfcp_ie_f_teid f_teid_access_local; + struct osmo_pfcp_ie_outer_header_creation ohc_access; + + struct osmo_pfcp_ie_f_teid f_teid_core_local; + struct osmo_pfcp_ie_outer_header_creation ohc_core; + + struct osmo_pfcp_ie_apply_action aa = {}; + + if (!g_pfcp_tool->ep) { + vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (argc > 0 && !strcmp("drop", argv[0])) + osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true); + else + osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_FORW, true); + + if (session->access.teid.local == 0) { + f_teid_access_local = (struct osmo_pfcp_ie_f_teid){ + .choose_flag = true, + .choose = { + .ipv4_addr = true, + }, + }; + } else { + f_teid_access_local = (struct osmo_pfcp_ie_f_teid){ + .fixed = { + .teid = session->access.teid.local, + .ip_addr = { + .v4_present = true, + .v4 = g_pfcp_tool->ep->cfg.local_addr, + }, + }, + }; + if (osmo_sockaddr_str_to_sockaddr(&session->access.gtp_ip, &f_teid_access_local.fixed.ip_addr.v4.u.sas)) { + vty_out(vty, "Error in GTP IP towards Access%s", VTY_NEWLINE); + return CMD_WARNING; + } + } + + ohc_access = (struct osmo_pfcp_ie_outer_header_creation){ + .teid_present = true, + .teid = session->access.teid.remote, + .ip_addr.v4_present = true, + }; + osmo_pfcp_bits_set(ohc_access.desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true); + if (osmo_sockaddr_str_to_sockaddr(&session->access.gtp_ip, &ohc_access.ip_addr.v4.u.sas)) { + vty_out(vty, "Error in GTP IP towards Access%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (session->core.teid.local == 0) { + f_teid_core_local = (struct osmo_pfcp_ie_f_teid){ + .choose_flag = true, + .choose = { + .ipv4_addr = true, + }, + }; + } else { + f_teid_core_local = (struct osmo_pfcp_ie_f_teid){ + .fixed = { + .teid = session->core.teid.local, + .ip_addr = { + .v4_present = true, + .v4 = g_pfcp_tool->ep->cfg.local_addr, + }, + }, + }; + if (osmo_sockaddr_str_to_sockaddr(&session->core.gtp_ip, &f_teid_core_local.fixed.ip_addr.v4.u.sas)) { + vty_out(vty, "Error in GTP IP towards Core%s", VTY_NEWLINE); + return CMD_WARNING; + } + } + ohc_core = (struct osmo_pfcp_ie_outer_header_creation){ + .teid_present = true, + .teid = session->core.teid.remote, + .ip_addr.v4_present = true, + }; + osmo_pfcp_bits_set(ohc_core.desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true); + if (osmo_sockaddr_str_to_sockaddr(&session->core.gtp_ip, &ohc_core.ip_addr.v4.u.sas)) { + vty_out(vty, "Error in GTP IP towards Core%s", VTY_NEWLINE); + return CMD_WARNING; + } + + cp_f_seid = (struct osmo_pfcp_ie_f_seid){ + .seid = session->cp_seid, + }; + osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, &g_pfcp_tool->ep->cfg.local_addr); + + m = osmo_pfcp_msg_alloc_tx(OTC_SELECT, &peer->remote_addr, &g_pfcp_tool->ep->cfg.local_node_id, NULL, + OSMO_PFCP_MSGT_SESSION_EST_REQ); + m->h.seid_present = true; + m->h.seid = 0; + /* GTP tunmap: remove header from both directions, and add header in both directions */ + m->ies.session_est_req = (struct osmo_pfcp_msg_session_est_req){ + .node_id = m->ies.session_est_req.node_id, + .cp_f_seid_present = true, + .cp_f_seid = cp_f_seid, + .create_pdr_count = 2, + .create_pdr = { + { + .pdr_id = 1, + .precedence = 255, + .pdi = { + .source_iface = OSMO_PFCP_SOURCE_IFACE_CORE, + .local_f_teid_present = true, + .local_f_teid = f_teid_core_local, + }, + .outer_header_removal_present = true, + .outer_header_removal = { + .desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4, + }, + .far_id_present = true, + .far_id = 1, + }, + { + .pdr_id = 2, + .precedence = 255, + .pdi = { + .source_iface = OSMO_PFCP_SOURCE_IFACE_ACCESS, + .local_f_teid_present = true, + .local_f_teid = f_teid_access_local, + }, + .outer_header_removal_present = true, + .outer_header_removal = { + .desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4, + }, + .far_id_present = true, + .far_id = 2, + }, + }, + .create_far_count = 2, + .create_far = { + { + .far_id = 1, + .forw_params_present = true, + .forw_params = { + .destination_iface = OSMO_PFCP_DEST_IFACE_ACCESS, + .outer_header_creation_present = true, + .outer_header_creation = ohc_access, + }, + .apply_action = aa, + }, + { + .far_id = 2, + .forw_params_present = true, + .forw_params = { + .destination_iface = OSMO_PFCP_DEST_IFACE_CORE, + .outer_header_creation_present = true, + .outer_header_creation = ohc_core, + }, + .apply_action = aa, + }, + }, + }; + + rc = peer_tx(peer, m); + if (rc) { + vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(session_tx_est_req, session_tx_est_req_cmd, + "tx session-est-req [(forw|drop)]", + TX_STR "Send a Session Establishment Request\n" + "Set FAR to FORW = 1 (default)\n" + "Set FAR to DROP = 1\n") +{ + struct pfcp_tool_session *session = vty->index; + switch (session->gtp_action) { + case UP_GTP_U_ENDECAPS: + return session_endecaps_tx_est_req(vty, argv, argc); + case UP_GTP_U_TUNMAP: + return session_tunmap_tx_est_req(vty, argv, argc); + default: + vty_out(vty, "unknown gtp action%s", VTY_NEWLINE); + return CMD_WARNING; + } +} + +DEFUN(session_tx_mod_req, session_tx_mod_req_cmd, + "tx session-mod-req far [(forw|drop)]", + TX_STR "Send a Session Modification Request\n" + "Set FAR to FORW = 1\n" + "Set FAR to DROP = 1\n") +{ + struct pfcp_tool_session *session = vty->index; + struct pfcp_tool_peer *peer = session->peer; + int rc; + struct osmo_pfcp_msg *m; + struct osmo_pfcp_ie_apply_action aa = {}; + struct osmo_pfcp_ie_f_seid cp_f_seid; + + if (!g_pfcp_tool->ep) { + vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (argc > 0 && !strcmp("drop", argv[0])) + osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true); + else + osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_FORW, true); + + cp_f_seid = (struct osmo_pfcp_ie_f_seid){ + .seid = session->cp_seid, + }; + osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, &g_pfcp_tool->ep->cfg.local_addr); + + m = osmo_pfcp_msg_alloc_tx(OTC_SELECT, &peer->remote_addr, &g_pfcp_tool->ep->cfg.local_node_id, NULL, + OSMO_PFCP_MSGT_SESSION_MOD_REQ); + m->h.seid_present = true; + m->h.seid = session->up_f_seid.seid; + m->ies.session_mod_req = (struct osmo_pfcp_msg_session_mod_req){ + .cp_f_seid_present = true, + .cp_f_seid = cp_f_seid, + .upd_far_count = 2, + .upd_far = { + { + .far_id = 1, + .apply_action_present = true, + .apply_action = aa, + }, + { + .far_id = 2, + .apply_action_present = true, + .apply_action = aa, + }, + }, + }; + + rc = peer_tx(peer, m); + if (rc) { + vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(session_tx_del_req, session_tx_del_req_cmd, + "tx session-del-req", + TX_STR "Send a Session Deletion Request\n") +{ + struct pfcp_tool_session *session = vty->index; + struct pfcp_tool_peer *peer = session->peer; + int rc; + struct osmo_pfcp_msg *m; + + if (!g_pfcp_tool->ep) { + vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); + return CMD_WARNING; + } + + m = osmo_pfcp_msg_alloc_tx(OTC_SELECT, &peer->remote_addr, &g_pfcp_tool->ep->cfg.local_node_id, NULL, + OSMO_PFCP_MSGT_SESSION_DEL_REQ); + m->h.seid_present = true; + m->h.seid = session->up_f_seid.seid; + + rc = peer_tx(peer, m); + if (rc) { + vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +static void install_ve_and_config(struct cmd_element *cmd) +{ + install_element_ve(cmd); + install_element(CONFIG_NODE, cmd); +} + +void pfcp_tool_vty_init_cfg() +{ + OSMO_ASSERT(g_pfcp_tool != NULL); + + install_ve_and_config(&c_local_addr_cmd); + install_ve_and_config(&c_listen_cmd); +} + +void pfcp_tool_vty_init_cmds() +{ + OSMO_ASSERT(g_pfcp_tool != NULL); + + install_ve_and_config(&c_sleep_cmd); + + install_ve_and_config(&peer_cmd); + install_node(&peer_node, NULL); + + install_element(PEER_NODE, &c_sleep_cmd); + install_element(PEER_NODE, &peer_tx_heartbeat_cmd); + install_element(PEER_NODE, &peer_tx_assoc_setup_req_cmd); + install_element(PEER_NODE, &peer_retrans_req_cmd); + + install_element(PEER_NODE, &session_cmd); + install_node(&session_node, NULL); + install_element(SESSION_NODE, &c_sleep_cmd); + install_element(SESSION_NODE, &session_tx_est_req_cmd); + install_element(SESSION_NODE, &session_tx_mod_req_cmd); + install_element(SESSION_NODE, &session_tx_del_req_cmd); + install_element(SESSION_NODE, &s_ue_cmd); + install_element(SESSION_NODE, &s_gtp_cmd); + install_element(SESSION_NODE, &s_teid_cmd); +}