neels submitted this change.

View Change

Approvals: Jenkins Builder: Verified fixeria: Looks good to me, but someone else must approve neels: Looks good to me, approved
add osmo-pfcp-tool

A tool for quick testing of PFCP interaction with a UPF, based on VTY
scripts / interaction.

The main motivation to create this tool was to test both the CPF and UPF
sides of the new PFCP protocol encoding and decoding, and then to test
interaction of osmo-upf with the kernel modules. It may also come in
handy as a fast way to verify basic operation in a production
environment.

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(-)

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..2943c76
--- /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);
+}

To view, visit change 28310. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: osmo-upf
Gerrit-Branch: master
Gerrit-Change-Id: I34a80d43a14c7b68952c7d337d8042d6f28ceae7
Gerrit-Change-Number: 28310
Gerrit-PatchSet: 3
Gerrit-Owner: neels <nhofmeyr@sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanitskiy@sysmocom.de>
Gerrit-Reviewer: neels <nhofmeyr@sysmocom.de>
Gerrit-Reviewer: pespin <pespin@sysmocom.de>
Gerrit-CC: laforge <laforge@osmocom.org>
Gerrit-MessageType: merged