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(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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(a)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(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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
https://gerrit.osmocom.org/c/osmo-upf/+/28310
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings
Gerrit-Project: osmo-upf
Gerrit-Branch: master
Gerrit-Change-Id: I34a80d43a14c7b68952c7d337d8042d6f28ceae7
Gerrit-Change-Number: 28310
Gerrit-PatchSet: 1
Gerrit-Owner: neels <nhofmeyr(a)sysmocom.de>
Gerrit-MessageType: newchange