add osmo-pfcp-tool

Related: SYS#5599
Change-Id: I34a80d43a14c7b68952c7d337d8042d6f28ceae7
This commit is contained in:
Neels Hofmeyr
2022-01-12 02:50:21 +01:00
parent 5a93480b57
commit 972810e00d
18 changed files with 1625 additions and 0 deletions

View File

@@ -207,6 +207,7 @@ AC_OUTPUT(
src/libosmo-gtlv/Makefile
src/libosmo-pfcp/Makefile
src/osmo-upf/Makefile
src/osmo-pfcp-tool/Makefile
tests/Makefile
tests/atlocal
tests/libosmo-gtlv/Makefile

View File

@@ -0,0 +1,3 @@
pfcp-peer 127.0.0.1
tx assoc-setup-req
sleep 1

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,5 @@
log stderr
logging level set-all info
local-addr 127.0.0.2
listen

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,4 @@
timer pfcp x23 0
pfcp-peer 127.0.0.1
session endecaps
tx session-est-req

View File

@@ -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

View File

@@ -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

View File

@@ -2,4 +2,5 @@ SUBDIRS = \
libosmo-gtlv \
libosmo-pfcp \
osmo-upf \
osmo-pfcp-tool \
$(NULL)

View File

@@ -0,0 +1,41 @@
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) \
$(COVERAGE_CFLAGS) \
$(NULL)
AM_LDFLAGS = \
$(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)
osmo_pfcp_tool_LDADD = \
$(top_builddir)/src/libosmo-pfcp/libosmo-pfcp.a \
$(top_builddir)/src/libosmo-gtlv/libosmo-gtlv.a \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOVTY_LIBS) \
$(LIBOSMOCTRL_LIBS) \
$(COVERAGE_LDFLAGS) \
$(NULL)

View File

@@ -0,0 +1,371 @@
/*
* (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->retrans_queue));
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;
}

View File

@@ -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)
{
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++;
}

View File

@@ -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);
int peer_tx(struct pfcp_tool_peer *peer, struct osmo_pfcp_msg *m);
uint64_t peer_new_seid(struct pfcp_tool_peer *peer);

View File

@@ -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);
}