mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-upf.git
synced 2025-10-23 00:02:03 +00:00
Compare commits
12 Commits
7d2c272047
...
neels/gtpf
Author | SHA1 | Date | |
---|---|---|---|
|
6c1fd09bdb | ||
|
0eed0079e0 | ||
|
cb1a3ea0da | ||
|
9ee5959151 | ||
|
0ad6aaeda3 | ||
|
c7119ea26c | ||
|
f31d604eb9 | ||
|
09de12b6dc | ||
|
61d4aea56a | ||
|
54169ccddf | ||
|
a8629aa2f1 | ||
|
a91f4ec88e |
@@ -7,3 +7,4 @@
|
||||
# If any interfaces have been added since the last public release: c:r:a + 1.
|
||||
# If any interfaces have been removed or changed since the last public release: c:r:0.
|
||||
#library what description / commit summary line
|
||||
osmo-pfcp-tool liburing new dependency to support GTP flooding
|
||||
|
15
configure.ac
15
configure.ac
@@ -43,6 +43,20 @@ PKG_CHECK_MODULES(LIBOSMOPFCP, libosmo-pfcp >= 0.1.0)
|
||||
PKG_CHECK_MODULES(LIBGTPNL, libgtpnl >= 1.2.0)
|
||||
PKG_CHECK_MODULES(LIBNFTABLES, libnftables >= 1.0.2)
|
||||
|
||||
AC_ARG_ENABLE([uring], [AS_HELP_STRING([--disable-uring], [Build without io_uring support])],
|
||||
[
|
||||
ENABLE_URING=$enableval
|
||||
],
|
||||
[
|
||||
ENABLE_URING="yes"
|
||||
])
|
||||
AS_IF([test "x$ENABLE_URING" = "xyes"], [
|
||||
PKG_CHECK_MODULES(LIBURING, [liburing >= 0.7])
|
||||
AC_DEFINE([HAVE_URING],[1],[Build with io_uring support for GTP flood commands])
|
||||
])
|
||||
AM_CONDITIONAL(ENABLE_URING, test "x$ENABLE_URING" = "xyes")
|
||||
AC_SUBST(ENABLE_URING)
|
||||
|
||||
dnl checks for header files
|
||||
AC_HEADER_STDC
|
||||
|
||||
@@ -198,6 +212,7 @@ AC_OUTPUT(
|
||||
include/Makefile
|
||||
include/osmocom/Makefile
|
||||
include/osmocom/upf/Makefile
|
||||
include/osmocom/pfcptool/Makefile
|
||||
src/Makefile
|
||||
src/osmo-upf/Makefile
|
||||
src/osmo-pfcp-tool/Makefile
|
||||
|
@@ -1 +1,29 @@
|
||||
SUBDIRS = systemd
|
||||
|
||||
AM_CPPFLAGS = \
|
||||
$(all_includes) \
|
||||
-I$(top_srcdir)/include \
|
||||
-I$(top_builddir)/include \
|
||||
-I$(top_builddir) \
|
||||
$(NULL)
|
||||
|
||||
AM_CFLAGS = \
|
||||
-Wall \
|
||||
$(LIBOSMOCORE_CFLAGS) \
|
||||
$(LIBURING_CFLAGS) \
|
||||
$(COVERAGE_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
AM_LDFLAGS = \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBURING_LIBS) \
|
||||
$(COVERAGE_LDFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
osmo-udp-responder \
|
||||
$(NULL)
|
||||
|
||||
osmo_udp_responder_SOURCES = \
|
||||
udp_responder.c \
|
||||
$(NULL)
|
||||
|
53
contrib/osmo-pfcp-tool-scripts/gtp_flood.vty
Normal file
53
contrib/osmo-pfcp-tool-scripts/gtp_flood.vty
Normal file
@@ -0,0 +1,53 @@
|
||||
# Establish N PFCP sessions for tunend, and emit massive GTP traffic to the UPF
|
||||
# to each established tunnel.
|
||||
#
|
||||
# osmo-pfcp-tool UPF "internet host"
|
||||
# |GTP-ep -------GTP-----> GTP-ep|UE-IP-addr -------IP------> arbitrary-IP|
|
||||
# |10.0.1.1 10.0.2.1|192.168.10.23 123.234.42.23|
|
||||
# |10.0.1.2
|
||||
# ^ ^ ^
|
||||
# ^ | | |
|
||||
# | | configure by configure by
|
||||
# configure by from UPF 'ue ip' 'target ip',
|
||||
# 'gtp ip' ("F-TEID=choose") 'target port'
|
||||
|
||||
# Configure one or more local GTP endpoints to emit GTP packets from.
|
||||
# Established sessions will use these round-robin.
|
||||
# These need to be local IP addresses for 'gtp flood' to work.
|
||||
gtp local 127.0.1.1
|
||||
gtp local 127.0.1.2
|
||||
|
||||
# use UE IP addresses from this range, +1 for each new UE:
|
||||
# 192.168.0.1, 192.168.0.2, ...
|
||||
ue ip range 192.168.23.2 192.168.23.254
|
||||
|
||||
# now associate with UPF and start N sessions.
|
||||
pfcp-peer 127.0.0.11
|
||||
tx assoc-setup-req
|
||||
sleep 1
|
||||
date
|
||||
|
||||
n 10 session create tunend
|
||||
wait responses
|
||||
# All sessions established
|
||||
date
|
||||
|
||||
# For each established PFCP session, emit GTP packets
|
||||
gtp flood
|
||||
workers 1
|
||||
io-uring queue-size 4
|
||||
flows-per-session 1
|
||||
packets-per-flow 1000
|
||||
slew 0
|
||||
|
||||
# configure the generated GTP payload: send UDP packets from the UE address
|
||||
# and these source UDP ports to these target addresses and target UDP ports.
|
||||
# They are used round-robin.
|
||||
# Source IP is the UE IP address.
|
||||
payload source port udp range 10000 10010
|
||||
payload target ip range 192.168.10.154 192.168.10.154
|
||||
payload target port udp range 23000 23000
|
||||
|
||||
# All GTP is flowing.
|
||||
# osmo-pfcp-tool will keep this up for as long as there still are active GTP flows,
|
||||
# or until receiving a signal interrupt (ctrl-C).
|
523
contrib/udp_responder.c
Normal file
523
contrib/udp_responder.c
Normal file
@@ -0,0 +1,523 @@
|
||||
/* UDP responder: listen on a UDP port, and respond to each received UDP packet back to the sender. */
|
||||
/*
|
||||
* (C) 2024 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 "config.h"
|
||||
|
||||
#include <osmocom/core/application.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
|
||||
#if HAVE_URING
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <getopt.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <liburing.h>
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
|
||||
struct cmdline_cmd {
|
||||
const char *short_option;
|
||||
const char *long_option;
|
||||
const char *arg_name;
|
||||
const char *doc;
|
||||
|
||||
const char *value;
|
||||
};
|
||||
|
||||
#define cmdline_foreach(ITER, CMDS) \
|
||||
for (const struct cmdline_cmd *ITER = (CMDS); \
|
||||
ITER->short_option || ITER->long_option || ITER->arg_name; \
|
||||
ITER++)
|
||||
|
||||
int cmdline_doc_str_buf(char *buf, size_t buflen, const struct cmdline_cmd *cmds)
|
||||
{
|
||||
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
|
||||
/* First find the longest options part */
|
||||
int w = 0;
|
||||
cmdline_foreach (cmd, cmds) {
|
||||
int cmd_w = 0;
|
||||
if (cmd->short_option)
|
||||
cmd_w += 2 + strlen(cmd->short_option);
|
||||
if (cmd->long_option)
|
||||
cmd_w += 3 + strlen(cmd->long_option);
|
||||
if (cmd->arg_name)
|
||||
cmd_w += 1 + strlen(cmd->arg_name);
|
||||
w = OSMO_MAX(w, cmd_w);
|
||||
}
|
||||
/* vertical gap */
|
||||
w += 2;
|
||||
|
||||
OSMO_STRBUF_PRINTF(sb, "Options:\n");
|
||||
cmdline_foreach (cmd, cmds) {
|
||||
char *line_start = sb.pos;
|
||||
if (cmd->short_option)
|
||||
OSMO_STRBUF_PRINTF(sb, " -%s", cmd->short_option);
|
||||
if (cmd->long_option)
|
||||
OSMO_STRBUF_PRINTF(sb, " --%s", cmd->long_option);
|
||||
if (cmd->arg_name)
|
||||
OSMO_STRBUF_PRINTF(sb, " %s", cmd->arg_name);
|
||||
if (cmd->doc) {
|
||||
int have = sb.pos - line_start;
|
||||
int spaces = OSMO_MAX(1, w - have);
|
||||
OSMO_STRBUF_PRINTF(sb, "%*s", spaces, "");
|
||||
OSMO_STRBUF_PRINTF(sb, "%s", cmd->doc);
|
||||
}
|
||||
OSMO_STRBUF_PRINTF(sb, "\n");
|
||||
}
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
void cmdline_print_help(const struct cmdline_cmd *cmds)
|
||||
{
|
||||
char buf[8192];
|
||||
cmdline_doc_str_buf(buf, sizeof(buf), cmds);
|
||||
printf("%s", buf);
|
||||
}
|
||||
|
||||
void cmdline_cmd_store_optarg(struct cmdline_cmd *cmd)
|
||||
{
|
||||
if (cmd->arg_name)
|
||||
cmd->value = optarg;
|
||||
else
|
||||
cmd->value = (cmd->short_option ? : cmd->long_option);
|
||||
}
|
||||
|
||||
int cmdline_read(struct cmdline_cmd *cmds, int argc, char **argv)
|
||||
{
|
||||
char short_options[256] = {};
|
||||
struct option long_options[128] = {};
|
||||
int long_options_i = 0;
|
||||
int long_option_val = 0;
|
||||
struct osmo_strbuf short_sb = { .buf = short_options, .len = sizeof(short_options) };
|
||||
|
||||
cmdline_foreach (cmd, cmds) {
|
||||
if (cmd->short_option) {
|
||||
OSMO_STRBUF_PRINTF(short_sb, "%s", cmd->short_option);
|
||||
if (cmd->arg_name)
|
||||
OSMO_STRBUF_PRINTF(short_sb, ":");
|
||||
}
|
||||
if (cmd->long_option) {
|
||||
long_options[long_options_i] = (struct option){
|
||||
cmd->long_option,
|
||||
cmd->arg_name ? 1 : 0,
|
||||
&long_option_val,
|
||||
long_options_i,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
while (1) {
|
||||
int option_index = 0;
|
||||
char c = getopt_long(argc, argv, short_options, long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
if (c == 0) {
|
||||
struct cmdline_cmd *long_cmd = &cmds[long_option_val];
|
||||
cmdline_cmd_store_optarg(long_cmd);
|
||||
} else {
|
||||
bool found = false;
|
||||
cmdline_foreach (cc, cmds) {
|
||||
if (strchr(cc->short_option, c)) {
|
||||
cmdline_cmd_store_optarg((struct cmdline_cmd *)cc);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
fprintf(stderr, "%s: Error in command line options. Exiting.\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* positional args */
|
||||
cmdline_foreach (cmd, cmds) {
|
||||
if (optind >= argc)
|
||||
break;
|
||||
if (cmd->short_option || cmd->long_option)
|
||||
continue;
|
||||
if (!cmd->arg_name)
|
||||
continue;
|
||||
((struct cmdline_cmd *)cmd)->value = argv[optind];
|
||||
optind++;
|
||||
}
|
||||
|
||||
if (optind < argc) {
|
||||
cmdline_print_help(cmds);
|
||||
fprintf(stderr, "%s: Unsupported positional argument on command line\n", argv[optind]);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *cmdline_get(const struct cmdline_cmd *cmds, const char *option_name, const char *default_val)
|
||||
{
|
||||
cmdline_foreach (cmd, cmds) {
|
||||
if (cmd->long_option && !strcmp(cmd->long_option, option_name))
|
||||
return cmd->value;
|
||||
if (cmd->short_option && !strcmp(cmd->short_option, option_name))
|
||||
return cmd->value;
|
||||
if (cmd->arg_name && !strcmp(cmd->arg_name, option_name))
|
||||
return cmd->value;
|
||||
}
|
||||
return default_val;
|
||||
}
|
||||
|
||||
bool cmdline_get_int(int *dst, int minval, int maxval, int default_val,
|
||||
const struct cmdline_cmd *cmds, const char *option_name)
|
||||
{
|
||||
const char *str = cmdline_get(cmds, option_name, NULL);
|
||||
if (!str) {
|
||||
*dst = default_val;
|
||||
return true;
|
||||
}
|
||||
if (osmo_str_to_int(dst, str, 10, minval, maxval)) {
|
||||
cmdline_print_help(cmds);
|
||||
printf("ERROR: invalid integer number: %s\n", str);
|
||||
return false;
|
||||
}
|
||||
if (*dst < minval || *dst > maxval) {
|
||||
cmdline_print_help(cmds);
|
||||
printf("ERROR: number out of range: %d <= %d <= %d\n", minval, *dst, maxval);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct udp_port {
|
||||
struct llist_head entry;
|
||||
|
||||
/* IP address and UDP port from user input */
|
||||
struct osmo_sockaddr osa;
|
||||
|
||||
/* locally bound socket */
|
||||
int fd;
|
||||
};
|
||||
|
||||
enum data_io_type {
|
||||
IO_UNUSED = 0,
|
||||
IO_RECV,
|
||||
IO_SEND,
|
||||
};
|
||||
|
||||
struct data_io {
|
||||
enum data_io_type type;
|
||||
struct osmo_sockaddr osa;
|
||||
struct iovec iov;
|
||||
struct msghdr msgh;
|
||||
uint8_t *data;
|
||||
size_t data_size;
|
||||
int n;
|
||||
};
|
||||
|
||||
struct io_queue {
|
||||
size_t d_size;
|
||||
struct data_io d[0];
|
||||
};
|
||||
|
||||
static void data_io_prep_recv(struct io_uring *ring, struct udp_port *port, struct data_io *d)
|
||||
{
|
||||
struct io_uring_sqe *sqe;
|
||||
*d = (struct data_io){
|
||||
.type = IO_RECV,
|
||||
.iov = {
|
||||
.iov_base = d->data,
|
||||
.iov_len = d->data_size,
|
||||
},
|
||||
.msgh = {
|
||||
.msg_name = &d->osa,
|
||||
.msg_namelen = sizeof(d->osa),
|
||||
.msg_iov = &d->iov,
|
||||
.msg_iovlen = 1,
|
||||
},
|
||||
.data_size = d->data_size,
|
||||
.data = d->data,
|
||||
};
|
||||
|
||||
sqe = io_uring_get_sqe(ring);
|
||||
OSMO_ASSERT(sqe);
|
||||
io_uring_prep_recvmsg(sqe, port->fd, &d->msgh, 0);
|
||||
io_uring_sqe_set_data(sqe, d);
|
||||
}
|
||||
|
||||
static void data_io_prep_send(struct io_uring *ring, struct udp_port *port, struct data_io *d)
|
||||
{
|
||||
struct io_uring_sqe *sqe;
|
||||
d->type = IO_SEND;
|
||||
sqe = io_uring_get_sqe(ring);
|
||||
OSMO_ASSERT(sqe);
|
||||
io_uring_prep_sendmsg(sqe, port->fd, &d->msgh, 0);
|
||||
io_uring_sqe_set_data(sqe, d);
|
||||
}
|
||||
|
||||
uint32_t g_total_rx = 0;
|
||||
uint32_t g_total_tx = 0;
|
||||
|
||||
static void data_io_handle_completion(struct io_uring *ring, struct udp_port *port, struct io_uring_cqe *cqe,
|
||||
int response_size, int response_n)
|
||||
{
|
||||
struct data_io *d;
|
||||
struct osmo_sockaddr *osa = NULL;
|
||||
int rc;
|
||||
|
||||
d = io_uring_cqe_get_data(cqe);
|
||||
|
||||
osa = &d->osa;
|
||||
rc = cqe->res;
|
||||
if (rc < 0) {
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "%s -> rx error rc=%d flags=0x%x\n",
|
||||
osa ? osmo_sockaddr_to_str(osa) : "NULL",
|
||||
rc, cqe->flags);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (d->type) {
|
||||
case IO_RECV:
|
||||
/* done reading */
|
||||
d->iov.iov_len = rc;
|
||||
LOGP(DLGLOBAL, LOGL_DEBUG, "%s -> rx rc=%d flags=0x%x: %s\n",
|
||||
osa ? osmo_sockaddr_to_str(osa) : "NULL",
|
||||
rc, cqe->flags,
|
||||
osmo_quote_str(d->iov.iov_base, d->iov.iov_len));
|
||||
io_uring_cqe_seen(ring, cqe);
|
||||
g_total_rx++;
|
||||
|
||||
if (response_n < 1)
|
||||
break;
|
||||
|
||||
/* resubmit back to sender */
|
||||
if (response_size > 0)
|
||||
d->iov.iov_len = response_size;
|
||||
data_io_prep_send(ring, port, d);
|
||||
break;
|
||||
|
||||
case IO_SEND:
|
||||
/* done writing. */
|
||||
LOGP(DLGLOBAL, LOGL_DEBUG, "%s <- tx rc=%d flags=0x%x: %s\n",
|
||||
osa ? osmo_sockaddr_to_str(osa) : "NULL",
|
||||
rc, cqe->flags,
|
||||
osmo_quote_str(d->iov.iov_base, rc));
|
||||
io_uring_cqe_seen(ring, cqe);
|
||||
g_total_tx++;
|
||||
|
||||
d->n++;
|
||||
|
||||
/* Send again? If not, re-submit open slot for reading. */
|
||||
if (d->n < response_n)
|
||||
data_io_prep_send(ring, port, d);
|
||||
else
|
||||
data_io_prep_recv(ring, port, d);
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
struct cmdline_cmd cmds[] = {
|
||||
{
|
||||
.short_option = "h",
|
||||
.long_option = "help",
|
||||
.doc = "Show this help",
|
||||
},
|
||||
{
|
||||
.short_option = "l",
|
||||
.long_option = "local-addr",
|
||||
.arg_name = "IP-ADDR",
|
||||
.doc = "Listen on local IP address (default is 0.0.0.0).",
|
||||
},
|
||||
{
|
||||
.short_option = "p",
|
||||
.long_option = "port",
|
||||
.arg_name = "UDP-PORT",
|
||||
.doc = "Listen on local UDP port.",
|
||||
},
|
||||
/*
|
||||
{
|
||||
.short_option = "P",
|
||||
.long_option = "port-range-to",
|
||||
.arg_name = "UDP-PORT-TO",
|
||||
.doc = "Listen on a range of ports, from --port to --port-range-to, inclusive.",
|
||||
},
|
||||
*/
|
||||
{
|
||||
.short_option = "s",
|
||||
.long_option = "response-size",
|
||||
.arg_name = "BYTES",
|
||||
.doc = "When responding, enlarge or shorten the payload to this size.",
|
||||
},
|
||||
{
|
||||
.short_option = "n",
|
||||
.long_option = "response-repeat",
|
||||
.arg_name = "N",
|
||||
.doc = "Respond N times, i.e. multiply the returned traffic.",
|
||||
},
|
||||
{
|
||||
.long_option = "io-uring-queue",
|
||||
.arg_name = "SIZE",
|
||||
.doc = "I/O tuning: queue size to use for io_uring, default is 4000.",
|
||||
},
|
||||
{
|
||||
.long_option = "io-uring-buf",
|
||||
.arg_name = "SIZE",
|
||||
.doc = "I/O tuning: maximum payload size, default is 2048.",
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct log_info_cat categories[] = {
|
||||
};
|
||||
|
||||
const struct log_info udp_responder_log_info = {
|
||||
.cat = categories,
|
||||
.num_cat = ARRAY_SIZE(categories),
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct udp_port port = {};
|
||||
struct osmo_sockaddr_str addr = {};
|
||||
struct osmo_sockaddr osa = {};
|
||||
|
||||
osmo_init_logging2(OTC_GLOBAL, &udp_responder_log_info);
|
||||
log_set_log_level(osmo_stderr_target, LOGL_ERROR);
|
||||
|
||||
if (cmdline_read(cmds, argc, argv)
|
||||
|| cmdline_get(cmds, "help", NULL)) {
|
||||
cmdline_print_help(cmds);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int port_nr;
|
||||
if (!cmdline_get_int(&port_nr, 1, 65525, 23000, cmds, "port"))
|
||||
return -1;
|
||||
|
||||
const char *local_addr = cmdline_get(cmds, "local-addr", "0.0.0.0");
|
||||
if (osmo_sockaddr_str_from_str(&addr, local_addr, port_nr)
|
||||
|| osmo_sockaddr_str_to_osa(&addr, &osa)) {
|
||||
printf("ERROR: invalid interface or port number: %s:%d\n", local_addr, port_nr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int queue_size;
|
||||
if (!cmdline_get_int(&queue_size, 1, 65536, 4000, cmds, "io-uring-queue"))
|
||||
return -1;
|
||||
int buf_size;
|
||||
if (!cmdline_get_int(&buf_size, 1, 65536, 2048, cmds, "io-uring-buf"))
|
||||
return -1;
|
||||
|
||||
int response_size = 0;
|
||||
if (!cmdline_get_int(&response_size, 0, buf_size, 0, cmds, "response-size"))
|
||||
return -1;
|
||||
|
||||
int response_n = 1;
|
||||
if (!cmdline_get_int(&response_n, 0, INT_MAX, 1, cmds, "response-repeat"))
|
||||
return -1;
|
||||
|
||||
|
||||
port.osa = osa;
|
||||
|
||||
/* create and bind socket */
|
||||
int rc;
|
||||
rc = osmo_sock_init_osa(SOCK_DGRAM, IPPROTO_UDP, &port.osa, NULL, OSMO_SOCK_F_BIND);
|
||||
/* (logging of errors already happens in osmo_sock_init_osa() */
|
||||
if (rc < 0)
|
||||
return -1;
|
||||
port.fd = rc;
|
||||
LOGP(DLGLOBAL, LOGL_NOTICE, "bound UDP %s fd=%d\n", osmo_sock_get_name2(port.fd), port.fd);
|
||||
|
||||
struct io_uring ring = {};
|
||||
rc = io_uring_queue_init(queue_size, &ring, 0);
|
||||
OSMO_ASSERT(rc >= 0);
|
||||
|
||||
struct io_queue *q = talloc_size(OTC_GLOBAL, sizeof(struct io_queue) + queue_size * sizeof(struct data_io));
|
||||
OSMO_ASSERT(q);
|
||||
*q = (struct io_queue){
|
||||
.d_size = queue_size,
|
||||
};
|
||||
|
||||
for (int i = 0; i < q->d_size; i++) {
|
||||
struct data_io *d = &q->d[i];
|
||||
*d = (struct data_io){
|
||||
.data = talloc_size(q, buf_size),
|
||||
.data_size = buf_size,
|
||||
};
|
||||
|
||||
/* fill once with random printable data */
|
||||
for (int j = 0; j < d->data_size; j++)
|
||||
d->data[j] = 32 + random() % (126 - 32 + 1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < q->d_size; i++) {
|
||||
data_io_prep_recv(&ring, &port, &q->d[i]);
|
||||
}
|
||||
|
||||
struct __kernel_timespec ts = {};
|
||||
struct timespec next = {};
|
||||
|
||||
while (1) {
|
||||
uint32_t submitted;
|
||||
uint32_t completed = 0;
|
||||
struct io_uring_cqe *cqe;
|
||||
bool show = false;
|
||||
|
||||
/* submit any requests from previous loop */
|
||||
submitted = io_uring_submit(&ring);
|
||||
|
||||
/* process all pending completions */
|
||||
while (io_uring_wait_cqe_timeout(&ring, &cqe, &ts) == 0) {
|
||||
data_io_handle_completion(&ring, &port, cqe, response_size, response_n);
|
||||
completed++;
|
||||
}
|
||||
|
||||
{
|
||||
struct timespec now;
|
||||
osmo_clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
if (timespeccmp(&now, &next, >=)) {
|
||||
next = now;
|
||||
next.tv_sec++;
|
||||
show = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (show || (!submitted && !completed)) {
|
||||
printf("%6u rx %6u tx \r", g_total_rx, g_total_tx);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
if (!submitted && !completed) {
|
||||
io_uring_wait_cqe(&ring, &cqe);
|
||||
data_io_handle_completion(&ring, &port, cqe, response_size, response_n);
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(q);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* HAVE_URING */
|
||||
|
4
debian/control
vendored
4
debian/control
vendored
@@ -2,6 +2,7 @@ Source: osmo-upf
|
||||
Section: net
|
||||
Priority: extra
|
||||
Maintainer: Osmocom team <openbsc@lists.osmocom.org>
|
||||
# liburing-dev: don't try to install it on debian 10 and ubuntu 20.04
|
||||
Build-Depends: debhelper (>= 10),
|
||||
dh-autoreconf,
|
||||
autotools-dev,
|
||||
@@ -16,7 +17,8 @@ Build-Depends: debhelper (>= 10),
|
||||
libnftables-dev (>= 1.0.2),
|
||||
libosmocore-dev (>= 1.6.0),
|
||||
libosmo-pfcp-dev (>= 0.1.0),
|
||||
osmo-gsm-manuals-dev (>= 1.2.0)
|
||||
osmo-gsm-manuals-dev (>= 1.2.0),
|
||||
liburing-dev | base-files (<< 11) | ubuntu-keyring (<< 2021),
|
||||
Standards-Version: 3.9.8
|
||||
Vcs-Git: https://gitea.osmocom.org/cellular-infrastructure/osmo-upf
|
||||
Vcs-Browser: https://gitea.osmocom.org/cellular-infrastructure/osmo-upf
|
||||
|
@@ -1,3 +1,4 @@
|
||||
SUBDIRS = \
|
||||
upf \
|
||||
pfcptool \
|
||||
$(NULL)
|
||||
|
6
include/osmocom/pfcptool/Makefile.am
Normal file
6
include/osmocom/pfcptool/Makefile.am
Normal file
@@ -0,0 +1,6 @@
|
||||
noinst_HEADERS = \
|
||||
checksum.h \
|
||||
gtp_flood.h \
|
||||
pfcp_tool.h \
|
||||
range.h \
|
||||
$(NULL)
|
13
include/osmocom/pfcptool/checksum.h
Normal file
13
include/osmocom/pfcptool/checksum.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
uint16_t ip_fast_csum(const void *iph, unsigned int ihl);
|
||||
uint32_t csum_partial(const void *buff, int len, uint32_t wsum);
|
||||
uint16_t ip_compute_csum(const void *buff, int len);
|
||||
|
||||
uint16_t csum_ipv6_magic(const struct in6_addr *saddr,
|
||||
const struct in6_addr *daddr,
|
||||
uint32_t len, uint8_t proto, uint32_t csum);
|
||||
|
||||
uint16_t csum_fold(uint32_t csum);
|
39
include/osmocom/pfcptool/gtp_flood.h
Normal file
39
include/osmocom/pfcptool/gtp_flood.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/core/socket.h>
|
||||
|
||||
/* According to 3GPP TS 29.060. */
|
||||
struct gtp1u_hdr {
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
uint8_t pn:1, s:1, e:1, spare:1, pt:1, version:3;
|
||||
#else
|
||||
uint8_t version:3, pt:1, spare:1, e:1, s:1, pn:1;
|
||||
#endif
|
||||
uint8_t type;
|
||||
uint16_t length;
|
||||
uint32_t tei;
|
||||
};
|
||||
|
||||
struct gtp_flood;
|
||||
struct udp_port;
|
||||
|
||||
struct gtp_flood_cfg {
|
||||
unsigned int workers;
|
||||
unsigned int queue_size;
|
||||
unsigned int slew_us;
|
||||
};
|
||||
struct gtp_flood *gtp_flood_alloc(void *ctx, const struct gtp_flood_cfg *cfg);
|
||||
|
||||
struct gtp_flood_flow_cfg {
|
||||
struct udp_port *gtp_local;
|
||||
struct osmo_sockaddr gtp_remote;
|
||||
uint32_t gtp_remote_teid;
|
||||
struct osmo_sockaddr payload_src;
|
||||
struct osmo_sockaddr payload_dst;
|
||||
unsigned int num_packets;
|
||||
};
|
||||
void gtp_flood_add_flow(struct gtp_flood *gtp_flood,
|
||||
const struct gtp_flood_flow_cfg *flow_cfg);
|
||||
|
||||
void gtp_flood_start(struct gtp_flood *gtp_flood);
|
||||
bool gtp_flood_is_busy(struct gtp_flood *gtp_flood);
|
@@ -28,14 +28,23 @@
|
||||
#include <osmocom/core/tdef.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
#include <osmocom/vty/command.h>
|
||||
|
||||
#include <osmocom/pfcp/pfcp_msg.h>
|
||||
|
||||
#include <osmocom/upf/up_gtp_action.h>
|
||||
#include <osmocom/pfcptool/range.h>
|
||||
#include <osmocom/pfcptool/gtp_flood.h>
|
||||
|
||||
struct osmo_tdef;
|
||||
struct ctrl_handle;
|
||||
|
||||
enum pfcp_tool_vty_node {
|
||||
PEER_NODE = _LAST_OSMOVTY_NODE + 1,
|
||||
SESSION_NODE,
|
||||
GTP_FLOOD_NODE,
|
||||
};
|
||||
|
||||
extern struct osmo_tdef g_pfcp_tool_tdefs[];
|
||||
extern struct osmo_tdef_group g_pfcp_tool_tdef_groups[];
|
||||
|
||||
@@ -90,6 +99,16 @@ struct pfcp_tool_session {
|
||||
};
|
||||
};
|
||||
|
||||
struct udp_port {
|
||||
struct llist_head entry;
|
||||
|
||||
/* IP address and UDP port from user input */
|
||||
struct osmo_sockaddr osa;
|
||||
|
||||
/* In case this is a locally bound port, this is the fd for the socket. */
|
||||
struct osmo_fd ofd;
|
||||
};
|
||||
|
||||
struct g_pfcp_tool {
|
||||
struct ctrl_handle *ctrl;
|
||||
|
||||
@@ -100,6 +119,38 @@ struct g_pfcp_tool {
|
||||
|
||||
struct osmo_pfcp_endpoint *ep;
|
||||
struct llist_head peers;
|
||||
|
||||
uint32_t next_teid_state;
|
||||
|
||||
struct {
|
||||
struct range ip_range;
|
||||
} ue;
|
||||
|
||||
struct {
|
||||
/* list of struct udp_port */
|
||||
struct llist_head gtp_local_addrs;
|
||||
struct udp_port *gtp_local_addrs_next;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
/* source address is always the UE IP address */
|
||||
struct range udp_port_range;
|
||||
} source;
|
||||
struct {
|
||||
struct range ip_range;
|
||||
struct range udp_port_range;
|
||||
} target;
|
||||
} payload;
|
||||
|
||||
struct {
|
||||
struct gtp_flood_cfg cfg;
|
||||
|
||||
unsigned int flows_per_session;
|
||||
unsigned int packets_per_flow;
|
||||
|
||||
struct gtp_flood *state;
|
||||
} flood;
|
||||
} gtp;
|
||||
};
|
||||
|
||||
extern struct g_pfcp_tool *g_pfcp_tool;
|
||||
@@ -108,7 +159,7 @@ void g_pfcp_tool_alloc(void *ctx);
|
||||
void pfcp_tool_vty_init_cfg();
|
||||
void pfcp_tool_vty_init_cmds();
|
||||
|
||||
int pfcp_tool_mainloop();
|
||||
int pfcp_tool_mainloop(int poll);
|
||||
|
||||
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,
|
||||
@@ -117,3 +168,12 @@ void pfcp_tool_rx_msg(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, st
|
||||
|
||||
int peer_tx(struct pfcp_tool_peer *peer, struct osmo_pfcp_msg *m);
|
||||
uint64_t peer_new_seid(struct pfcp_tool_peer *peer);
|
||||
|
||||
uint32_t pfcp_tool_new_teid(void);
|
||||
int pfcp_tool_next_ue_addr(struct osmo_sockaddr *dst);
|
||||
|
||||
struct udp_port *pfcp_tool_have_udp_port_by_str(const struct osmo_sockaddr_str *addr, uint16_t fallback_port);
|
||||
|
||||
void pfcp_tool_gtp_flood_start(void);
|
||||
|
||||
int pfcp_tool_vty_go_parent(struct vty *vty);
|
38
include/osmocom/pfcptool/range.h
Normal file
38
include/osmocom/pfcptool/range.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct osmo_sockaddr;
|
||||
|
||||
/* A value large enough for an IPv6 address (128bit) */
|
||||
struct range_val {
|
||||
uint64_t buf[2];
|
||||
size_t size;
|
||||
};
|
||||
|
||||
/* Manage stepping through a defined range of values large enough to handle IPv6 addresses. */
|
||||
struct range {
|
||||
struct range_val first;
|
||||
struct range_val last;
|
||||
struct range_val next;
|
||||
bool wrapped;
|
||||
};
|
||||
|
||||
/* struct range r = {};
|
||||
* range_val_set_int(&r.first, 23);
|
||||
* range_val_set_int(&r.last, 42);
|
||||
* while (1) {
|
||||
* range_next();
|
||||
* unsigned int val = range_val_get_int(&r.next);
|
||||
* printf("%u\n", val);
|
||||
* }
|
||||
*/
|
||||
void range_next(struct range *r);
|
||||
void range_val_set_int(struct range_val *rv, uint32_t val);
|
||||
uint32_t range_val_get_int(const struct range_val *rv);
|
||||
void range_val_set_addr(struct range_val *rv, const struct osmo_sockaddr *val);
|
||||
void range_val_get_addr(struct osmo_sockaddr *dst, const struct range_val *rv);
|
||||
void range_val_inc(struct range_val *rv);
|
||||
int range_val_cmp(const struct range_val *a, const struct range_val *b);
|
@@ -11,6 +11,7 @@ AM_CFLAGS = \
|
||||
$(LIBOSMOVTY_CFLAGS) \
|
||||
$(LIBOSMOCTRL_CFLAGS) \
|
||||
$(LIBOSMOPFCP_CFLAGS) \
|
||||
$(LIBURING_CFLAGS) \
|
||||
$(COVERAGE_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
@@ -19,19 +20,19 @@ AM_LDFLAGS = \
|
||||
$(LIBOSMOVTY_LIBS) \
|
||||
$(LIBOSMOCTRL_LIBS) \
|
||||
$(LIBOSMOPFCP_LIBS) \
|
||||
$(LIBURING_LIBS) \
|
||||
$(COVERAGE_LDFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
noinst_HEADERS = \
|
||||
pfcp_tool.h \
|
||||
$(NULL)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
osmo-pfcp-tool \
|
||||
$(NULL)
|
||||
|
||||
osmo_pfcp_tool_SOURCES = \
|
||||
checksum.c \
|
||||
gtp_flood.c \
|
||||
osmo_pfcp_tool_main.c \
|
||||
pfcp_tool.c \
|
||||
pfcp_tool_vty.c \
|
||||
range.c \
|
||||
$(NULL)
|
||||
|
211
src/osmo-pfcp-tool/checksum.c
Normal file
211
src/osmo-pfcp-tool/checksum.c
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
*
|
||||
* INET An implementation of the TCP/IP protocol suite for the LINUX
|
||||
* operating system. INET is implemented using the BSD Socket
|
||||
* interface as the means of communication with the user level.
|
||||
*
|
||||
* IP/TCP/UDP checksumming routines
|
||||
*
|
||||
* Authors: Jorge Cwik, <jorge@laser.satlink.net>
|
||||
* Arnt Gulbrandsen, <agulbra@nvg.unit.no>
|
||||
* Tom May, <ftom@netcom.com>
|
||||
* Andreas Schwab, <schwab@issan.informatik.uni-dortmund.de>
|
||||
* Lots of code moved from tcp.c and ip.c; see those files
|
||||
* for more names.
|
||||
*
|
||||
* 03/02/96 Jes Sorensen, Andreas Schwab, Roman Hodek:
|
||||
* Fixed some nasty bugs, causing some horrible crashes.
|
||||
* A: At some points, the sum (%0) was used as
|
||||
* length-counter instead of the length counter
|
||||
* (%1). Thanks to Roman Hodek for pointing this out.
|
||||
* B: GCC seems to mess up if one uses too many
|
||||
* data-registers to hold input values and one tries to
|
||||
* specify d0 and d1 as scratch registers. Letting gcc
|
||||
* choose these registers itself solves the problem.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* Revised by Kenneth Albanowski for m68knommu. Basic problem: unaligned access
|
||||
kills, so most of the assembly has to go. */
|
||||
|
||||
#if defined(__FreeBSD__)
|
||||
#define _KERNEL /* needed on FreeBSD 10.x for s6_addr32 */
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/endian.h>
|
||||
#endif
|
||||
|
||||
#include <osmocom/pfcptool/checksum.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
static inline unsigned short from32to16(unsigned int x)
|
||||
{
|
||||
/* add up 16-bit and 16-bit for 16+c bit */
|
||||
x = (x & 0xffff) + (x >> 16);
|
||||
/* add up carry.. */
|
||||
x = (x & 0xffff) + (x >> 16);
|
||||
return x;
|
||||
}
|
||||
|
||||
static unsigned int do_csum(const unsigned char *buff, int len)
|
||||
{
|
||||
int odd;
|
||||
unsigned int result = 0;
|
||||
|
||||
if (len <= 0)
|
||||
goto out;
|
||||
odd = 1 & (unsigned long) buff;
|
||||
if (odd) {
|
||||
#if BYTE_ORDER == LITTLE_ENDIAN
|
||||
result += (*buff << 8);
|
||||
#else
|
||||
result = *buff;
|
||||
#endif
|
||||
len--;
|
||||
buff++;
|
||||
}
|
||||
if (len >= 2) {
|
||||
if (2 & (unsigned long) buff) {
|
||||
result += *(unsigned short *) buff;
|
||||
len -= 2;
|
||||
buff += 2;
|
||||
}
|
||||
if (len >= 4) {
|
||||
const unsigned char *end = buff + ((unsigned)len & ~3);
|
||||
unsigned int carry = 0;
|
||||
do {
|
||||
unsigned int w = *(unsigned int *) buff;
|
||||
buff += 4;
|
||||
result += carry;
|
||||
result += w;
|
||||
carry = (w > result);
|
||||
} while (buff < end);
|
||||
result += carry;
|
||||
result = (result & 0xffff) + (result >> 16);
|
||||
}
|
||||
if (len & 2) {
|
||||
result += *(unsigned short *) buff;
|
||||
buff += 2;
|
||||
}
|
||||
}
|
||||
if (len & 1)
|
||||
#if BYTE_ORDER == LITTLE_ENDIAN
|
||||
result += *buff;
|
||||
#else
|
||||
result += (*buff << 8);
|
||||
#endif
|
||||
result = from32to16(result);
|
||||
if (odd)
|
||||
result = ((result >> 8) & 0xff) | ((result & 0xff) << 8);
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a version of ip_compute_csum() optimized for IP headers,
|
||||
* which always checksum on 4 octet boundaries.
|
||||
*/
|
||||
uint16_t ip_fast_csum(const void *iph, unsigned int ihl)
|
||||
{
|
||||
return (uint16_t)~do_csum(iph, ihl*4);
|
||||
}
|
||||
|
||||
/*
|
||||
* computes the checksum of a memory block at buff, length len,
|
||||
* and adds in "sum" (32-bit)
|
||||
*
|
||||
* returns a 32-bit number suitable for feeding into itself
|
||||
* or csum_tcpudp_magic
|
||||
*
|
||||
* this function must be called with even lengths, except
|
||||
* for the last fragment, which may be odd
|
||||
*
|
||||
* it's best to have buff aligned on a 32-bit boundary
|
||||
*/
|
||||
uint32_t csum_partial(const void *buff, int len, uint32_t wsum)
|
||||
{
|
||||
unsigned int sum = (unsigned int)wsum;
|
||||
unsigned int result = do_csum(buff, len);
|
||||
|
||||
/* add in old sum, and carry.. */
|
||||
result += sum;
|
||||
if (sum > result)
|
||||
result += 1;
|
||||
return (uint32_t)result;
|
||||
}
|
||||
|
||||
/*
|
||||
* this routine is used for miscellaneous IP-like checksums, mainly
|
||||
* in icmp.c
|
||||
*/
|
||||
uint16_t ip_compute_csum(const void *buff, int len)
|
||||
{
|
||||
return (uint16_t)~do_csum(buff, len);
|
||||
}
|
||||
|
||||
uint16_t csum_ipv6_magic(const struct in6_addr *saddr,
|
||||
const struct in6_addr *daddr,
|
||||
uint32_t len, uint8_t proto, uint32_t csum)
|
||||
{
|
||||
int carry;
|
||||
uint32_t ulen;
|
||||
uint32_t uproto;
|
||||
uint32_t sum = (uint32_t)csum;
|
||||
|
||||
sum += (uint32_t)saddr->s6_addr32[0];
|
||||
carry = (sum < (uint32_t)saddr->s6_addr32[0]);
|
||||
sum += carry;
|
||||
|
||||
sum += (uint32_t)saddr->s6_addr32[1];
|
||||
carry = (sum < (uint32_t)saddr->s6_addr32[1]);
|
||||
sum += carry;
|
||||
|
||||
sum += (uint32_t)saddr->s6_addr32[2];
|
||||
carry = (sum < (uint32_t)saddr->s6_addr32[2]);
|
||||
sum += carry;
|
||||
|
||||
sum += (uint32_t)saddr->s6_addr32[3];
|
||||
carry = (sum < (uint32_t)saddr->s6_addr32[3]);
|
||||
sum += carry;
|
||||
|
||||
sum += (uint32_t)daddr->s6_addr32[0];
|
||||
carry = (sum < (uint32_t)daddr->s6_addr32[0]);
|
||||
sum += carry;
|
||||
|
||||
sum += (uint32_t)daddr->s6_addr32[1];
|
||||
carry = (sum < (uint32_t)daddr->s6_addr32[1]);
|
||||
sum += carry;
|
||||
|
||||
sum += (uint32_t)daddr->s6_addr32[2];
|
||||
carry = (sum < (uint32_t)daddr->s6_addr32[2]);
|
||||
sum += carry;
|
||||
|
||||
sum += (uint32_t)daddr->s6_addr32[3];
|
||||
carry = (sum < (uint32_t)daddr->s6_addr32[3]);
|
||||
sum += carry;
|
||||
|
||||
ulen = (uint32_t)htonl((uint32_t) len);
|
||||
sum += ulen;
|
||||
carry = (sum < ulen);
|
||||
sum += carry;
|
||||
|
||||
uproto = (uint32_t)htonl(proto);
|
||||
sum += uproto;
|
||||
carry = (sum < uproto);
|
||||
sum += carry;
|
||||
|
||||
return csum_fold((uint32_t)sum);
|
||||
}
|
||||
|
||||
/* fold a partial checksum */
|
||||
uint16_t csum_fold(uint32_t csum)
|
||||
{
|
||||
uint32_t sum = (uint32_t)csum;
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
return (uint16_t)~sum;
|
||||
}
|
388
src/osmo-pfcp-tool/gtp_flood.c
Normal file
388
src/osmo-pfcp-tool/gtp_flood.c
Normal file
@@ -0,0 +1,388 @@
|
||||
/* GTP-U traffic/load generator. Generates a configurable amount of UDP/IP flows using io_uring.
|
||||
*
|
||||
* Based on gtp-load-gen.c from https://gitea.osmocom.org/cellular-infrastructure/gtp-load-gen
|
||||
* which is marked (C) 2021 by Harald Welte <laforge@osmocom.org>
|
||||
*/
|
||||
/*
|
||||
* (C) 2024 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 "config.h"
|
||||
|
||||
#include <osmocom/pfcptool/gtp_flood.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
|
||||
#if HAVE_URING
|
||||
|
||||
#include <unistd.h>
|
||||
#include <liburing.h>
|
||||
#include <pthread.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/ip6.h>
|
||||
#include <netinet/udp.h>
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/pfcptool/pfcp_tool.h>
|
||||
#include <osmocom/pfcptool/checksum.h>
|
||||
|
||||
#define BUF_SIZE 1024
|
||||
|
||||
struct gtp_flood_worker;
|
||||
|
||||
struct gtp_flood {
|
||||
struct gtp_flood_cfg cfg;
|
||||
|
||||
/* allocated array */
|
||||
struct gtp_flood_worker *workers;
|
||||
unsigned int workers_count;
|
||||
unsigned int next_worker;
|
||||
|
||||
int workers_started;
|
||||
int workers_running;
|
||||
};
|
||||
|
||||
struct gtp_flood_worker {
|
||||
/* backpointer */
|
||||
struct gtp_flood *gtp_flood;
|
||||
|
||||
/* list of struct gtp_flood_flow */
|
||||
struct llist_head flows;
|
||||
|
||||
struct io_uring ring;
|
||||
int fd;
|
||||
|
||||
pthread_t worker;
|
||||
};
|
||||
|
||||
struct gtp_flood_flow {
|
||||
struct llist_head entry;
|
||||
|
||||
/* backpointer */
|
||||
struct gtp_flood_worker *worker;
|
||||
|
||||
struct gtp_flood_flow_cfg cfg;
|
||||
|
||||
/* for logging and included in generated payloads */
|
||||
unsigned int id;
|
||||
|
||||
/* must live until completion */
|
||||
struct iovec iov[1];
|
||||
struct msghdr msgh;
|
||||
|
||||
/* flow-private packet buffer */
|
||||
uint8_t *pkt_buf;
|
||||
|
||||
unsigned int submitted_gtp_packets;
|
||||
unsigned int sent_gtp_packets;
|
||||
|
||||
bool stop;
|
||||
};
|
||||
|
||||
static void gtp_flood_worker_init(struct gtp_flood *gtp_flood, struct gtp_flood_worker *worker)
|
||||
{
|
||||
worker->gtp_flood = gtp_flood;
|
||||
INIT_LLIST_HEAD(&worker->flows);
|
||||
}
|
||||
|
||||
struct gtp_flood *gtp_flood_alloc(void *ctx, const struct gtp_flood_cfg *cfg)
|
||||
{
|
||||
struct gtp_flood *gtp_flood;
|
||||
int i;
|
||||
|
||||
gtp_flood = talloc_zero(ctx, struct gtp_flood);
|
||||
*gtp_flood = (struct gtp_flood){
|
||||
.cfg = *cfg,
|
||||
.workers = talloc_array(gtp_flood, struct gtp_flood_worker, cfg->workers),
|
||||
.workers_count = cfg->workers,
|
||||
};
|
||||
cfg = >p_flood->cfg;
|
||||
gtp_flood->cfg.workers = OSMO_MAX(1, gtp_flood->cfg.workers);
|
||||
for (i = 0; i < gtp_flood->workers_count; i++)
|
||||
gtp_flood_worker_init(gtp_flood, >p_flood->workers[i]);
|
||||
LOGP(DLGLOBAL, LOGL_NOTICE, "workers: %u\n", gtp_flood->workers_count);
|
||||
return gtp_flood;
|
||||
}
|
||||
|
||||
static void gtp_flood_flow_init_payload(struct gtp_flood_flow *flow)
|
||||
{
|
||||
struct gtp1u_hdr *gtp_hdr = (void *)flow->pkt_buf;
|
||||
|
||||
*gtp_hdr = (struct gtp1u_hdr) {
|
||||
.pn = 0,
|
||||
.s = 0,
|
||||
.e = 0,
|
||||
.spare = 0,
|
||||
.pt = 1,
|
||||
.version = 1,
|
||||
.type = 0xff, /* G-PDU */
|
||||
.length = 0, /* filled in later */
|
||||
.tei = htonl(flow->cfg.gtp_remote_teid),
|
||||
};
|
||||
|
||||
uint8_t *cur = flow->pkt_buf + sizeof(*gtp_hdr);
|
||||
|
||||
/* FIXME: randomize this */
|
||||
unsigned int udp_len = 1024;
|
||||
|
||||
struct iphdr *iph;
|
||||
struct ip6_hdr *ip6h;
|
||||
struct udphdr *uh;
|
||||
struct osmo_sockaddr *src = &flow->cfg.payload_src;
|
||||
struct osmo_sockaddr *dst = &flow->cfg.payload_dst;
|
||||
|
||||
if (src->u.sa.sa_family == AF_INET) {
|
||||
iph = (struct iphdr *) cur;
|
||||
cur += sizeof(*iph);
|
||||
|
||||
iph->ihl = 5;
|
||||
iph->version = 4;
|
||||
iph->tos = 0;
|
||||
iph->tot_len = htons(udp_len + sizeof(struct udphdr) + sizeof(*iph));
|
||||
iph->id = 0;
|
||||
iph->frag_off = 0;
|
||||
iph->ttl = 32;
|
||||
iph->protocol = IPPROTO_UDP;
|
||||
iph->saddr = src->u.sin.sin_addr.s_addr;
|
||||
iph->daddr = dst->u.sin.sin_addr.s_addr;
|
||||
iph->check = ip_fast_csum(iph, iph->ihl);
|
||||
} else {
|
||||
ip6h = (struct ip6_hdr *) cur;
|
||||
cur += sizeof(*ip6h);
|
||||
|
||||
ip6h->ip6_flow = htonl((6 << 28));
|
||||
ip6h->ip6_plen = htons(udp_len + sizeof(struct udphdr));
|
||||
ip6h->ip6_nxt = IPPROTO_UDP;
|
||||
ip6h->ip6_hlim = 32;
|
||||
ip6h->ip6_src = src->u.sin6.sin6_addr;
|
||||
ip6h->ip6_dst = dst->u.sin6.sin6_addr;
|
||||
}
|
||||
|
||||
uh = (struct udphdr *) cur;
|
||||
cur += sizeof(*uh);
|
||||
|
||||
uh->source = htons(osmo_sockaddr_port(&src->u.sa));
|
||||
uh->dest = htons(osmo_sockaddr_port(&dst->u.sa));
|
||||
uh->len = htons(udp_len);
|
||||
uh->check = 0; // TODO
|
||||
|
||||
gtp_hdr->length = htons(udp_len + (cur - flow->pkt_buf) - sizeof(*gtp_hdr));
|
||||
|
||||
/* initialize this once, so we have it ready for each transmit */
|
||||
flow->msgh.msg_name = &flow->cfg.gtp_remote.u.sa;
|
||||
flow->msgh.msg_namelen = sizeof(flow->cfg.gtp_remote.u.sa);
|
||||
flow->msgh.msg_iov = flow->iov;
|
||||
flow->msgh.msg_iovlen = ARRAY_SIZE(flow->iov);
|
||||
flow->msgh.msg_control = NULL;
|
||||
flow->msgh.msg_controllen = 0;
|
||||
flow->msgh.msg_flags = 0;
|
||||
|
||||
flow->iov[0].iov_base = flow->pkt_buf;
|
||||
flow->iov[0].iov_len = udp_len + (cur - flow->pkt_buf);
|
||||
|
||||
/* write some payload */
|
||||
struct osmo_strbuf sb = { .buf = (void *)cur, .len = udp_len };
|
||||
OSMO_STRBUF_PRINTF(sb, "osmo-pfcp-tool gtp flood, emitted from %s to %s teid 0x%08x flow %u\n",
|
||||
osmo_sockaddr_to_str_c(OTC_SELECT, &flow->cfg.gtp_local->osa),
|
||||
osmo_sockaddr_to_str_c(OTC_SELECT, &flow->cfg.gtp_remote),
|
||||
flow->cfg.gtp_remote_teid,
|
||||
flow->id);
|
||||
}
|
||||
|
||||
void gtp_flood_add_flow(struct gtp_flood *gtp_flood,
|
||||
const struct gtp_flood_flow_cfg *flow_cfg)
|
||||
{
|
||||
static unsigned int next_flow_id = 0;
|
||||
|
||||
struct gtp_flood_worker *worker;
|
||||
gtp_flood->next_worker %= gtp_flood->workers_count;
|
||||
worker = >p_flood->workers[gtp_flood->next_worker];
|
||||
gtp_flood->next_worker++;
|
||||
|
||||
struct gtp_flood_flow *flow = talloc_zero(gtp_flood, struct gtp_flood_flow);
|
||||
flow->cfg = *flow_cfg;
|
||||
flow->id = next_flow_id++;
|
||||
|
||||
flow->pkt_buf = talloc_zero_size(flow, BUF_SIZE);
|
||||
OSMO_ASSERT(flow->pkt_buf);
|
||||
|
||||
flow->worker = worker;
|
||||
llist_add_tail(&flow->entry, &worker->flows);
|
||||
|
||||
gtp_flood_flow_init_payload(flow);
|
||||
}
|
||||
|
||||
/* transmit one packet for a given flow */
|
||||
static bool gtp_flow_tx_one(struct gtp_flood_flow *flow)
|
||||
{
|
||||
struct gtp_flood_worker *worker = flow->worker;
|
||||
struct io_uring_sqe *sqe;
|
||||
|
||||
sqe = io_uring_get_sqe(&worker->ring);
|
||||
if (!sqe)
|
||||
return false;
|
||||
io_uring_prep_sendmsg(sqe, flow->cfg.gtp_local->ofd.fd, &flow->msgh, 0);
|
||||
io_uring_sqe_set_data(sqe, flow);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void *gtp_flood_worker_thread(void *_worker)
|
||||
{
|
||||
struct gtp_flood_worker *worker = (struct gtp_flood_worker *)_worker;
|
||||
struct gtp_flood *gtp_flood = worker->gtp_flood;
|
||||
|
||||
osmo_ctx_init(__func__);
|
||||
|
||||
gtp_flood->workers_started++;
|
||||
gtp_flood->workers_running++;
|
||||
|
||||
LOGP(DLGLOBAL, LOGL_INFO, "gtp flood worker starting (%u started, %u running)\n",
|
||||
gtp_flood->workers_started,
|
||||
gtp_flood->workers_running);
|
||||
|
||||
int flows_count = llist_count(&worker->flows);
|
||||
int flows_ended = 0;
|
||||
|
||||
|
||||
int num_submitted_total = 0;
|
||||
int num_pending_total = 0;
|
||||
|
||||
while (flows_ended < flows_count) {
|
||||
uint32_t num_submitted = 0;
|
||||
int num_pending;
|
||||
usleep(gtp_flood->cfg.slew_us);
|
||||
|
||||
/* fill up sqe with transmit submissions */
|
||||
bool keep_submitting = true;
|
||||
while (keep_submitting) {
|
||||
int submitted_was = num_submitted;
|
||||
|
||||
struct gtp_flood_flow *flow;
|
||||
llist_for_each_entry (flow, &worker->flows, entry) {
|
||||
if (flow->stop)
|
||||
continue;
|
||||
if (flow->cfg.num_packets
|
||||
&& flow->submitted_gtp_packets >= flow->cfg.num_packets)
|
||||
continue;
|
||||
|
||||
if (gtp_flow_tx_one(flow)) {
|
||||
flow->submitted_gtp_packets++;
|
||||
num_submitted++;
|
||||
} else {
|
||||
/* out of sqe. */
|
||||
keep_submitting = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* No change in number of submitted PDUs, all flows are done submitting. */
|
||||
if (submitted_was == num_submitted)
|
||||
keep_submitting = false;
|
||||
}
|
||||
|
||||
/* actually submit; determines completions */
|
||||
num_pending = io_uring_submit(&worker->ring);
|
||||
|
||||
/* process all completions */
|
||||
for (int j = 0; j < num_pending; j++) {
|
||||
struct io_uring_cqe *cqe;
|
||||
struct gtp_flood_flow *flow;
|
||||
int rc;
|
||||
|
||||
rc = io_uring_wait_cqe(&worker->ring, &cqe);
|
||||
OSMO_ASSERT(rc >= 0);
|
||||
if (cqe->res != 1060) printf(" %d", cqe->res);
|
||||
|
||||
if (cqe->res >= 0) {
|
||||
flow = io_uring_cqe_get_data(cqe);
|
||||
flow->sent_gtp_packets++;
|
||||
if (flow->cfg.num_packets
|
||||
&& flow->sent_gtp_packets >= flow->cfg.num_packets) {
|
||||
flow->stop = true;
|
||||
flows_ended++;
|
||||
}
|
||||
} else {
|
||||
flow->submitted_gtp_packets--;
|
||||
}
|
||||
io_uring_cqe_seen(&worker->ring, cqe);
|
||||
}
|
||||
|
||||
printf("\nnum_submitted=%5d/%5d num_pending=%5d/%5d flows_ended=%5d/%5d\n",
|
||||
num_submitted, num_submitted_total, num_pending, num_pending_total, flows_ended, flows_count);
|
||||
num_submitted_total += num_submitted;
|
||||
if (num_pending > 0)
|
||||
num_pending_total += num_pending;
|
||||
}
|
||||
|
||||
gtp_flood->workers_running--;
|
||||
LOGP(DLGLOBAL, LOGL_INFO, "gtp flood worker done (%u started, %u running)\n",
|
||||
gtp_flood->workers_started,
|
||||
gtp_flood->workers_running);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void gtp_flood_worker_start(struct gtp_flood_worker *worker)
|
||||
{
|
||||
int rc;
|
||||
rc = io_uring_queue_init(worker->gtp_flood->cfg.queue_size, &worker->ring, 0);
|
||||
OSMO_ASSERT(rc >= 0);
|
||||
rc = pthread_create(&worker->worker, NULL, gtp_flood_worker_thread, worker);
|
||||
OSMO_ASSERT(rc >= 0);
|
||||
}
|
||||
|
||||
void gtp_flood_start(struct gtp_flood *gtp_flood)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < gtp_flood->workers_count; i++)
|
||||
gtp_flood_worker_start(>p_flood->workers[i]);
|
||||
}
|
||||
|
||||
bool gtp_flood_is_busy(struct gtp_flood *gtp_flood)
|
||||
{
|
||||
if (!gtp_flood)
|
||||
return false;
|
||||
return gtp_flood->workers_started && gtp_flood->workers_running;
|
||||
}
|
||||
|
||||
#else /* HAVE_URING */
|
||||
|
||||
struct gtp_flood *gtp_flood_alloc(void *ctx, unsigned int workers)
|
||||
{
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "Cannot start GTP flood: built without liburing support\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void gtp_flood_add_flow(struct gtp_flood *gtp_flood,
|
||||
const struct gtp_flood_flow_cfg *flow_cfg)
|
||||
{
|
||||
}
|
||||
|
||||
void gtp_flood_start(struct gtp_flood *gtp_flood)
|
||||
{
|
||||
}
|
||||
|
||||
bool gtp_flood_is_busy(struct gtp_flood *gtp_flood)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif /* HAVE_URING */
|
@@ -42,7 +42,8 @@
|
||||
|
||||
#include <osmocom/pfcp/pfcp_endpoint.h>
|
||||
|
||||
#include "pfcp_tool.h"
|
||||
#include <osmocom/pfcptool/pfcp_tool.h>
|
||||
#include <osmocom/pfcptool/gtp_flood.h>
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <getopt.h>
|
||||
@@ -206,7 +207,6 @@ static void signal_handler(int signum)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static struct vty_app_info pfcp_tool_vty_app_info = {
|
||||
.name = "osmo-pfcp-tool",
|
||||
.version = PACKAGE_VERSION,
|
||||
@@ -216,6 +216,7 @@ static struct vty_app_info pfcp_tool_vty_app_info = {
|
||||
"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
|
||||
"This is free software: you are free to change and redistribute it.\r\n"
|
||||
"There is NO WARRANTY, to the extent permitted by law.\r\n",
|
||||
.go_parent_cb = pfcp_tool_vty_go_parent,
|
||||
};
|
||||
|
||||
static const struct log_info_cat pfcp_tool_default_categories[] = {
|
||||
@@ -226,14 +227,15 @@ const struct log_info log_info = {
|
||||
.num_cat = ARRAY_SIZE(pfcp_tool_default_categories),
|
||||
};
|
||||
|
||||
int pfcp_tool_mainloop()
|
||||
int pfcp_tool_mainloop(int poll)
|
||||
{
|
||||
int rc;
|
||||
log_reset_context();
|
||||
osmo_select_main_ctx(0);
|
||||
rc = osmo_select_main_ctx(poll);
|
||||
|
||||
/* If the user hits Ctrl-C the third time, just terminate immediately. */
|
||||
if (quit >= 3)
|
||||
return 1;
|
||||
return -1;
|
||||
|
||||
/* Has SIGTERM been received (and not yet been handled)? */
|
||||
if (quit && !osmo_select_shutdown_requested()) {
|
||||
@@ -243,7 +245,7 @@ int pfcp_tool_mainloop()
|
||||
osmo_select_shutdown_request();
|
||||
/* continue the main select loop until all write queues are serviced. */
|
||||
}
|
||||
return 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
@@ -326,7 +328,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
pfcp_tool_mainloop();
|
||||
pfcp_tool_mainloop(1);
|
||||
|
||||
pfcp_tool_vty_init_cmds();
|
||||
|
||||
@@ -338,17 +340,18 @@ int main(int argc, char **argv)
|
||||
pfcp_tool_cmdline_config.command_file);
|
||||
return 1;
|
||||
}
|
||||
printf("Done reading '%s', waiting for retransmission queue...\n",
|
||||
printf("Done reading '%s', waiting for tasks to conclude...\n",
|
||||
pfcp_tool_cmdline_config.command_file);
|
||||
do {
|
||||
if (pfcp_tool_mainloop())
|
||||
if (pfcp_tool_mainloop(0) == -1)
|
||||
break;
|
||||
} while (osmo_pfcp_endpoint_retrans_queue_is_busy(g_pfcp_tool->ep));
|
||||
printf("Done\n");
|
||||
} while (osmo_pfcp_endpoint_retrans_queue_is_busy(g_pfcp_tool->ep)
|
||||
|| gtp_flood_is_busy(g_pfcp_tool->gtp.flood.state));
|
||||
LOGP(DLGLOBAL, LOGL_NOTICE, "Done\n");
|
||||
} else {
|
||||
printf("Listening for commands on VTY...\n");
|
||||
do {
|
||||
if (pfcp_tool_mainloop())
|
||||
if (pfcp_tool_mainloop(0) == -1)
|
||||
break;
|
||||
} while (!osmo_select_shutdown_done());
|
||||
}
|
||||
|
@@ -26,7 +26,8 @@
|
||||
|
||||
#include <osmocom/pfcp/pfcp_endpoint.h>
|
||||
|
||||
#include "pfcp_tool.h"
|
||||
#include <osmocom/pfcptool/pfcp_tool.h>
|
||||
#include <osmocom/pfcptool/gtp_flood.h>
|
||||
|
||||
struct g_pfcp_tool *g_pfcp_tool = NULL;
|
||||
|
||||
@@ -45,9 +46,20 @@ void g_pfcp_tool_alloc(void *ctx)
|
||||
.local_ip = talloc_strdup(g_pfcp_tool, "0.0.0.0"),
|
||||
.local_port = OSMO_PFCP_PORT,
|
||||
},
|
||||
.next_teid_state = 23,
|
||||
.gtp.flood = {
|
||||
.cfg = {
|
||||
.workers = 1,
|
||||
.queue_size = 4096,
|
||||
.slew_us = 0,
|
||||
},
|
||||
.flows_per_session = 1,
|
||||
.packets_per_flow = 0,
|
||||
},
|
||||
};
|
||||
|
||||
INIT_LLIST_HEAD(&g_pfcp_tool->peers);
|
||||
INIT_LLIST_HEAD(&g_pfcp_tool->gtp.gtp_local_addrs);
|
||||
}
|
||||
|
||||
struct pfcp_tool_peer *pfcp_tool_peer_find(const struct osmo_sockaddr *remote_addr)
|
||||
@@ -176,6 +188,8 @@ int peer_tx(struct pfcp_tool_peer *peer, struct osmo_pfcp_msg *m)
|
||||
else
|
||||
copy_msg(&peer->last_req, m);
|
||||
rc = osmo_pfcp_endpoint_tx(g_pfcp_tool->ep, m);
|
||||
if (rc)
|
||||
LOGP(DLPFCP, LOGL_ERROR, "Failed to transmit PFCP: %s\n", strerror(-rc));
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -183,3 +197,157 @@ uint64_t peer_new_seid(struct pfcp_tool_peer *peer)
|
||||
{
|
||||
return peer->next_seid_state++;
|
||||
}
|
||||
|
||||
uint32_t pfcp_tool_new_teid(void)
|
||||
{
|
||||
return g_pfcp_tool->next_teid_state++;
|
||||
}
|
||||
|
||||
int pfcp_tool_next_ue_addr(struct osmo_sockaddr *dst)
|
||||
{
|
||||
range_next(&g_pfcp_tool->ue.ip_range);
|
||||
if (g_pfcp_tool->ue.ip_range.wrapped) {
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "insufficient UE IP addresses, wrapped back to first\n");
|
||||
g_pfcp_tool->ue.ip_range.wrapped = false;
|
||||
}
|
||||
range_val_get_addr(dst, &g_pfcp_tool->ue.ip_range.next);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct udp_port *pfcp_tool_have_udp_port_by_osa(const struct osmo_sockaddr *_osa, uint16_t fallback_port)
|
||||
{
|
||||
struct udp_port *port;
|
||||
/* copy osa and have a non-const pointer */
|
||||
struct osmo_sockaddr osa_mutable = *_osa;
|
||||
struct osmo_sockaddr *osa = &osa_mutable;
|
||||
|
||||
if (!osmo_sockaddr_port(&osa->u.sa))
|
||||
osmo_sockaddr_set_port(&osa->u.sa, fallback_port);
|
||||
OSMO_ASSERT(osmo_sockaddr_port(&osa->u.sa));
|
||||
|
||||
llist_for_each_entry (port, &g_pfcp_tool->gtp.gtp_local_addrs, entry) {
|
||||
if (osmo_sockaddr_cmp(&port->osa, osa) == 0)
|
||||
return port;
|
||||
}
|
||||
|
||||
LOGP(DLGLOBAL, LOGL_NOTICE, "new port UDP %s\n", osmo_sockaddr_to_str_c(OTC_SELECT, osa));
|
||||
|
||||
port = talloc_zero(g_pfcp_tool, struct udp_port);
|
||||
port->osa = *osa;
|
||||
|
||||
/* create and bind socket */
|
||||
int rc;
|
||||
rc = osmo_sock_init_osa(SOCK_DGRAM, IPPROTO_UDP, &port->osa, NULL, OSMO_SOCK_F_BIND);
|
||||
if (rc < 0) {
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "Failed to bind socket on UDP %s\n",
|
||||
osmo_sockaddr_to_str_c(OTC_SELECT, &port->osa));
|
||||
exit(1);
|
||||
}
|
||||
port->ofd.fd = rc;
|
||||
LOGP(DLGLOBAL, LOGL_NOTICE, "bound UDP %s fd=%d\n",
|
||||
osmo_sockaddr_to_str_c(OTC_SELECT, &port->osa), rc);
|
||||
|
||||
llist_add_tail(&port->entry, &g_pfcp_tool->gtp.gtp_local_addrs);
|
||||
return port;
|
||||
}
|
||||
|
||||
struct udp_port *pfcp_tool_have_udp_port_by_str(const struct osmo_sockaddr_str *addr, uint16_t fallback_port)
|
||||
{
|
||||
struct osmo_sockaddr osa;
|
||||
if (osmo_sockaddr_str_to_osa(addr, &osa))
|
||||
return NULL;
|
||||
return pfcp_tool_have_udp_port_by_osa(&osa, fallback_port);
|
||||
}
|
||||
|
||||
static bool add_session_to_gtp_flow(struct gtp_flood *gf, struct pfcp_tool_session *session)
|
||||
{
|
||||
struct range *r;
|
||||
struct gtp_flood_flow_cfg cfg = {};
|
||||
const struct pfcp_tool_gtp_tun *tun_access;
|
||||
const struct osmo_sockaddr_str *ue_local_addr = NULL;
|
||||
|
||||
switch (session->kind) {
|
||||
case UP_GTP_U_TUNEND:
|
||||
tun_access = &session->tunend.access;
|
||||
ue_local_addr = &session->tunend.core.ue_local_addr;
|
||||
break;
|
||||
case UP_GTP_U_TUNMAP:
|
||||
tun_access = &session->tunmap.access;
|
||||
break;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
|
||||
/* The 'local' and 'remote' naming clashes here. struct pfcp_tool_gtp_tun names its items from the UPF's point
|
||||
* of view: so the GTP port of osmo-pfcp-tool is 'remote'.
|
||||
* Here we are setting up a port to emit GTP from osmo-pfcp-tool, the same port is called 'gtp_local'.
|
||||
*/
|
||||
cfg.gtp_local = pfcp_tool_have_udp_port_by_str(&tun_access->remote.addr, 2152);
|
||||
if (!cfg.gtp_local) {
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "Cannot set up GTP flow for session, failed to setup local GTP port\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The 'local' and 'remote' naming clashes here. struct pfcp_tool_gtp_tun names its items from the UPF's point
|
||||
* of view: so the GTP port of UPF is 'local'.
|
||||
* Here we are reading the GTP port that the UPF has opened to receive GTP traffic, the same port is called
|
||||
* 'gtp_remote' in cfg.
|
||||
*/
|
||||
if (osmo_sockaddr_str_to_osa(&tun_access->local.addr, &cfg.gtp_remote)) {
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "Cannot set up GTP flow for session, failed to identify UPF's GTP port\n");
|
||||
return false;
|
||||
}
|
||||
if (!osmo_sockaddr_port(&cfg.gtp_remote.u.sa))
|
||||
osmo_sockaddr_set_port(&cfg.gtp_remote.u.sa, 2152);
|
||||
cfg.gtp_remote_teid = tun_access->local.teid;
|
||||
|
||||
if (ue_local_addr) {
|
||||
osmo_sockaddr_str_to_osa(ue_local_addr, &cfg.payload_src);
|
||||
} else if (pfcp_tool_next_ue_addr(&cfg.payload_src)) {
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "Cannot set up GTP flow for session, failed to identify UE's IP address\n");
|
||||
return false;
|
||||
}
|
||||
r = &g_pfcp_tool->gtp.payload.source.udp_port_range;
|
||||
range_next(r);
|
||||
osmo_sockaddr_set_port(&cfg.payload_src.u.sa, range_val_get_int(&r->next));
|
||||
|
||||
r = &g_pfcp_tool->gtp.payload.target.ip_range;
|
||||
range_next(r);
|
||||
range_val_get_addr(&cfg.payload_dst, &r->next);
|
||||
r = &g_pfcp_tool->gtp.payload.target.udp_port_range;
|
||||
range_next(r);
|
||||
osmo_sockaddr_set_port(&cfg.payload_dst.u.sa, range_val_get_int(&r->next));
|
||||
|
||||
cfg.num_packets = g_pfcp_tool->gtp.flood.packets_per_flow;
|
||||
|
||||
for (int i = 0; i < g_pfcp_tool->gtp.flood.flows_per_session; i++)
|
||||
gtp_flood_add_flow(gf, &cfg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void pfcp_tool_gtp_flood_start(void)
|
||||
{
|
||||
struct gtp_flood *gf;
|
||||
struct pfcp_tool_peer *peer;
|
||||
|
||||
|
||||
if (g_pfcp_tool->gtp.flood.state) {
|
||||
LOGP(DLGLOBAL, LOGL_ERROR,
|
||||
"another 'gtp flood' is still running; currently we can run only one gtp flood at a time\n");
|
||||
return;
|
||||
}
|
||||
|
||||
gf = g_pfcp_tool->gtp.flood.state = gtp_flood_alloc(g_pfcp_tool, &g_pfcp_tool->gtp.flood.cfg);
|
||||
if (!gf)
|
||||
return;
|
||||
|
||||
llist_for_each_entry (peer, &g_pfcp_tool->peers, entry) {
|
||||
struct pfcp_tool_session *session;
|
||||
llist_for_each_entry (session, &peer->sessions, entry) {
|
||||
add_session_to_gtp_flow(gf, session);
|
||||
}
|
||||
}
|
||||
|
||||
gtp_flood_start(gf);
|
||||
}
|
||||
|
@@ -34,12 +34,7 @@
|
||||
#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,
|
||||
};
|
||||
#include <osmocom/pfcptool/pfcp_tool.h>
|
||||
|
||||
DEFUN(c_local_addr, c_local_addr_cmd,
|
||||
"local-addr IP_ADDR",
|
||||
@@ -124,7 +119,7 @@ DEFUN(c_sleep, c_sleep_cmd,
|
||||
|
||||
/* Still operate the message pump while waiting for time to pass */
|
||||
while (t.active && !osmo_select_shutdown_done()) {
|
||||
if (pfcp_tool_mainloop())
|
||||
if (pfcp_tool_mainloop(0) == -1)
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -134,6 +129,209 @@ DEFUN(c_sleep, c_sleep_cmd,
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(c_date, c_date_cmd,
|
||||
"date",
|
||||
"print a timestamp\n")
|
||||
{
|
||||
struct timeval tv = {};
|
||||
struct tm tm = {};
|
||||
|
||||
osmo_gettimeofday(&tv, NULL);
|
||||
localtime_r(&tv.tv_sec, &tm);
|
||||
|
||||
vty_out(vty, "%04d-%02d-%02d,%02d:%02d:%02d.%03d%s",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||||
(int)(tv.tv_usec / 1000), VTY_NEWLINE);
|
||||
vty_flush(vty);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
static bool parse_ip_to_range_val(struct vty *vty, struct range_val *val, const char *ip_addr_str)
|
||||
{
|
||||
struct osmo_sockaddr_str a;
|
||||
struct osmo_sockaddr osa;
|
||||
if (osmo_sockaddr_str_from_str(&a, ip_addr_str, 0)
|
||||
|| osmo_sockaddr_str_to_osa(&a, &osa)) {
|
||||
vty_out(vty, "%% Error: invalid IP address: '%s'%s", ip_addr_str, VTY_NEWLINE);
|
||||
return false;
|
||||
}
|
||||
range_val_set_addr(val, &osa);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_ip_range(struct vty *vty, struct range *dst, const char *argv[])
|
||||
{
|
||||
return parse_ip_to_range_val(vty, &dst->first, argv[0])
|
||||
&& parse_ip_to_range_val(vty, &dst->last, argv[1]);
|
||||
}
|
||||
|
||||
#define IP_RANGE_STR \
|
||||
"Set a range with first and last address\n" \
|
||||
"First IP address in the range, 1.2.3.4 or 1:2:3::4\n" \
|
||||
"Last IP address in the range, 1.2.3.4 or 1:2:3::4\n"
|
||||
|
||||
DEFUN(c_ue_ip_range, c_ue_ip_range_cmd,
|
||||
"ue ip range IP_ADDR_FIRST IP_ADDR_LAST",
|
||||
"UE\n"
|
||||
"Set the IP address range used for the UE IP addresses\n"
|
||||
IP_RANGE_STR)
|
||||
{
|
||||
if (!parse_ip_range(vty, &g_pfcp_tool->ue.ip_range, &argv[0]))
|
||||
return CMD_WARNING;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#define GTP_STR "Configure GTP\n"
|
||||
#define GTP_IP_STR "Add a GTP IP address\n"
|
||||
#define IP_ADDR_STR "IP address, 1.2.3.4 or 1:2:3:4::1\n"
|
||||
|
||||
DEFUN(c_gtp_local, c_gtp_local_cmd,
|
||||
"gtp local IP_ADDR [<1-65535>]",
|
||||
GTP_STR
|
||||
"Add a local GTP port, to emit GTP traffic from\n"
|
||||
IP_ADDR_STR
|
||||
"Specific UDP port to use, 2152 if omitted\n")
|
||||
{
|
||||
const char *ip_str = argv[0];
|
||||
uint16_t port_nr;
|
||||
struct osmo_sockaddr_str addr_str;
|
||||
struct udp_port *p;
|
||||
|
||||
if (argc > 1)
|
||||
port_nr = atoi(argv[1]);
|
||||
else
|
||||
port_nr = 2152;
|
||||
|
||||
if (osmo_sockaddr_str_from_str(&addr_str, ip_str, port_nr))
|
||||
goto invalid_ip;
|
||||
vty_out(vty, "local GTP port: " OSMO_SOCKADDR_STR_FMT "%s", OSMO_SOCKADDR_STR_FMT_ARGS(&addr_str), VTY_NEWLINE);
|
||||
p = pfcp_tool_have_udp_port_by_str(&addr_str, port_nr);
|
||||
if (!p)
|
||||
goto invalid_ip;
|
||||
return CMD_SUCCESS;
|
||||
|
||||
invalid_ip:
|
||||
vty_out(vty, "%% Error: invalid IP address: '%s'%s", ip_str, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
static struct cmd_node gtp_flood_node = {
|
||||
GTP_FLOOD_NODE,
|
||||
"%s(gtp-flood)# ",
|
||||
1,
|
||||
};
|
||||
|
||||
DEFUN(gtp_flood, gtp_flood_cmd,
|
||||
"gtp flood",
|
||||
GTP_STR
|
||||
"Setup GTP traffic\n")
|
||||
{
|
||||
vty->node = GTP_FLOOD_NODE;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(gtpf_workers, gtpf_workers_cmd,
|
||||
"workers <1-999>",
|
||||
"Number of worker threads to launch, to emit GTP traffic from\n"
|
||||
"Number\n")
|
||||
{
|
||||
g_pfcp_tool->gtp.flood.cfg.workers = atoi(argv[0]);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(gtpf_io_uring_queue_size, gtpf_io_uring_queue_size_cmd,
|
||||
"io-uring queue-size <1-65536>",
|
||||
"Fine-tune io-uring usage for optimal GTP packet flooding\n"
|
||||
"Maximum number of packets to submit for sending simultaneously.\n"
|
||||
"Number\n")
|
||||
{
|
||||
g_pfcp_tool->gtp.flood.cfg.queue_size = atoi(argv[0]);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(gtpf_flows, gtpf_flows_cmd,
|
||||
"flows-per-session <1-999999>",
|
||||
"Set number of GTP packet flows emitted per PFCP session's GTP tunnel\n"
|
||||
"Number\n")
|
||||
{
|
||||
g_pfcp_tool->gtp.flood.flows_per_session = atoi(argv[0]);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(gtpf_packets, gtpf_packets_cmd,
|
||||
"packets-per-flow (<1-2000000000>|infinite)",
|
||||
"Set number of GTP packet flows emitted per PFCP session's GTP tunnel\n"
|
||||
"Number\n")
|
||||
{
|
||||
unsigned int val;
|
||||
if (!strcmp("infinite", argv[0]))
|
||||
val = 0;
|
||||
else
|
||||
val = atoi(argv[0]);
|
||||
g_pfcp_tool->gtp.flood.packets_per_flow = val;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#define PAYLOAD_STR "Configure GTP payload\n"
|
||||
|
||||
#define RANGE_STR \
|
||||
"Set a range to cycle through\n" \
|
||||
"First value of the range\n" \
|
||||
"Last value of the range\n"
|
||||
|
||||
DEFUN(gtpf_payload_src_port, gtpf_payload_src_port_cmd,
|
||||
"payload source port udp range <1-65535> <1-65535>",
|
||||
PAYLOAD_STR
|
||||
"Source port of payload packets. Note: the source IP will always be the UE IP for that session.\n"
|
||||
"Port\n"
|
||||
"UDP port\n"
|
||||
RANGE_STR)
|
||||
{
|
||||
range_val_set_int(&g_pfcp_tool->gtp.payload.source.udp_port_range.first,
|
||||
atoi(argv[0]));
|
||||
range_val_set_int(&g_pfcp_tool->gtp.payload.source.udp_port_range.last,
|
||||
atoi(argv[1]));
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#define PAYLOAD_TARGET_STR PAYLOAD_STR "Target of payload packets\n"
|
||||
|
||||
DEFUN(gtpf_payload_target_ip, gtpf_payload_target_ip_cmd,
|
||||
"payload target ip range IP_ADDR_FIRST IP_ADDR_LAST",
|
||||
PAYLOAD_TARGET_STR
|
||||
"IP address\n"
|
||||
RANGE_STR)
|
||||
{
|
||||
if (!parse_ip_range(vty, &g_pfcp_tool->gtp.payload.target.ip_range, &argv[0]))
|
||||
return CMD_WARNING;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(gtpf_payload_target_port, gtpf_payload_target_port_cmd,
|
||||
"payload target port udp range <1-65535> <1-65535>",
|
||||
PAYLOAD_STR
|
||||
"Target port of payload packets\n"
|
||||
"Port\n"
|
||||
"UDP port\n"
|
||||
RANGE_STR)
|
||||
{
|
||||
range_val_set_int(&g_pfcp_tool->gtp.payload.target.udp_port_range.first,
|
||||
atoi(argv[0]));
|
||||
range_val_set_int(&g_pfcp_tool->gtp.payload.target.udp_port_range.last,
|
||||
atoi(argv[1]));
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(gtpf_slew, gtpf_slew_cmd,
|
||||
"slew <0-1000000000>",
|
||||
"Wait N microseconds after each io_uring transmission submission\n"
|
||||
"microseconds to wait (1000000000 == 1 second)\n")
|
||||
{
|
||||
g_pfcp_tool->gtp.flood.cfg.slew_us = atoi(argv[0]);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
static struct cmd_node peer_node = {
|
||||
PEER_NODE,
|
||||
"%s(peer)# ",
|
||||
@@ -397,9 +595,16 @@ DEFUN(s_f_teid_choose, s_f_teid_choose_cmd,
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
int session_tunend_tx_est_req(struct vty *vty, const char **argv, int argc)
|
||||
enum pdr_id_fixed {
|
||||
PDR_ID_CORE = 1,
|
||||
PDR_ID_ACCESS = 2,
|
||||
};
|
||||
|
||||
const char * const gtp_ip_core = "10.99.0.1";
|
||||
const char * const fallback_gtp_ip_access = "10.99.0.2";
|
||||
|
||||
int session_tunend_tx_est_req(struct pfcp_tool_session *session, bool forw, osmo_pfcp_resp_cb resp_cb)
|
||||
{
|
||||
struct pfcp_tool_session *session = vty->index;
|
||||
struct pfcp_tool_peer *peer = session->peer;
|
||||
int rc;
|
||||
struct osmo_pfcp_msg *m;
|
||||
@@ -412,19 +617,19 @@ int session_tunend_tx_est_req(struct vty *vty, const char **argv, int argc)
|
||||
OSMO_ASSERT(session->kind == UP_GTP_U_TUNEND);
|
||||
|
||||
if (!g_pfcp_tool->ep) {
|
||||
vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE);
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "PFCP endpoint not configured\n");
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (argc > 0 && !strcmp("drop", argv[0]))
|
||||
osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true);
|
||||
else
|
||||
if (forw)
|
||||
osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_FORW, true);
|
||||
else
|
||||
osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true);
|
||||
|
||||
#define STR_TO_ADDR(DST, SRC) do { \
|
||||
if (osmo_sockaddr_str_to_sockaddr(&SRC, &DST.u.sas)) { \
|
||||
vty_out(vty, "Error in " #SRC ": " OSMO_SOCKADDR_STR_FMT "%s", \
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&SRC), VTY_NEWLINE); \
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "Error in " #SRC ": " OSMO_SOCKADDR_STR_FMT "\n", \
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&SRC)); \
|
||||
return CMD_WARNING; \
|
||||
} \
|
||||
} while (0)
|
||||
@@ -464,6 +669,10 @@ int session_tunend_tx_est_req(struct vty *vty, const char **argv, int argc)
|
||||
osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, osmo_pfcp_endpoint_get_local_addr(g_pfcp_tool->ep));
|
||||
|
||||
m = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, OSMO_PFCP_MSGT_SESSION_EST_REQ);
|
||||
|
||||
m->ctx.resp_cb = resp_cb;
|
||||
m->ctx.priv = session;
|
||||
|
||||
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;
|
||||
@@ -475,7 +684,7 @@ int session_tunend_tx_est_req(struct vty *vty, const char **argv, int argc)
|
||||
.create_pdr_count = 2,
|
||||
.create_pdr = {
|
||||
{
|
||||
.pdr_id = 1,
|
||||
.pdr_id = PDR_ID_CORE,
|
||||
.precedence = 255,
|
||||
.pdi = {
|
||||
.source_iface = OSMO_PFCP_SOURCE_IFACE_CORE,
|
||||
@@ -492,7 +701,7 @@ int session_tunend_tx_est_req(struct vty *vty, const char **argv, int argc)
|
||||
.far_id = 1,
|
||||
},
|
||||
{
|
||||
.pdr_id = 2,
|
||||
.pdr_id = PDR_ID_ACCESS,
|
||||
.precedence = 255,
|
||||
.pdi = {
|
||||
.source_iface = OSMO_PFCP_SOURCE_IFACE_ACCESS,
|
||||
@@ -531,16 +740,13 @@ int session_tunend_tx_est_req(struct vty *vty, const char **argv, int argc)
|
||||
};
|
||||
|
||||
rc = peer_tx(peer, m);
|
||||
if (rc) {
|
||||
vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE);
|
||||
if (rc)
|
||||
return CMD_WARNING;
|
||||
}
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
int session_tunmap_tx_est_req(struct vty *vty, const char **argv, int argc)
|
||||
int session_tunmap_tx_est_req(struct pfcp_tool_session *session, bool forw, osmo_pfcp_resp_cb resp_cb)
|
||||
{
|
||||
struct pfcp_tool_session *session = vty->index;
|
||||
struct pfcp_tool_peer *peer = session->peer;
|
||||
int rc;
|
||||
struct osmo_pfcp_msg *m;
|
||||
@@ -556,14 +762,14 @@ int session_tunmap_tx_est_req(struct vty *vty, const char **argv, int argc)
|
||||
struct osmo_pfcp_ie_apply_action aa = {};
|
||||
|
||||
if (!g_pfcp_tool->ep) {
|
||||
vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE);
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "PFCP endpoint not configured\n");
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (argc > 0 && !strcmp("drop", argv[0]))
|
||||
osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true);
|
||||
else
|
||||
if (forw)
|
||||
osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_FORW, true);
|
||||
else
|
||||
osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true);
|
||||
|
||||
if (session->tunmap.access.local.teid == 0) {
|
||||
f_teid_access_local = (struct osmo_pfcp_ie_f_teid){
|
||||
@@ -625,6 +831,10 @@ int session_tunmap_tx_est_req(struct vty *vty, const char **argv, int argc)
|
||||
osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, osmo_pfcp_endpoint_get_local_addr(g_pfcp_tool->ep));
|
||||
|
||||
m = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, OSMO_PFCP_MSGT_SESSION_EST_REQ);
|
||||
|
||||
m->ctx.resp_cb = resp_cb;
|
||||
m->ctx.priv = session;
|
||||
|
||||
m->h.seid_present = true;
|
||||
m->h.seid = 0;
|
||||
/* GTP tunmap: remove header from both directions, and add header in both directions */
|
||||
@@ -635,7 +845,7 @@ int session_tunmap_tx_est_req(struct vty *vty, const char **argv, int argc)
|
||||
.create_pdr_count = 2,
|
||||
.create_pdr = {
|
||||
{
|
||||
.pdr_id = 1,
|
||||
.pdr_id = PDR_ID_CORE,
|
||||
.precedence = 255,
|
||||
.pdi = {
|
||||
.source_iface = OSMO_PFCP_SOURCE_IFACE_CORE,
|
||||
@@ -650,7 +860,7 @@ int session_tunmap_tx_est_req(struct vty *vty, const char **argv, int argc)
|
||||
.far_id = 1,
|
||||
},
|
||||
{
|
||||
.pdr_id = 2,
|
||||
.pdr_id = PDR_ID_ACCESS,
|
||||
.precedence = 255,
|
||||
.pdi = {
|
||||
.source_iface = OSMO_PFCP_SOURCE_IFACE_ACCESS,
|
||||
@@ -691,10 +901,8 @@ int session_tunmap_tx_est_req(struct vty *vty, const char **argv, int argc)
|
||||
};
|
||||
|
||||
rc = peer_tx(peer, m);
|
||||
if (rc) {
|
||||
vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE);
|
||||
if (rc)
|
||||
return CMD_WARNING;
|
||||
}
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -705,24 +913,20 @@ DEFUN(session_tx_est_req, session_tx_est_req_cmd,
|
||||
"Set FAR to DROP = 1\n")
|
||||
{
|
||||
struct pfcp_tool_session *session = vty->index;
|
||||
bool forw = (argc == 0 || !strcmp("forw", argv[0]));
|
||||
switch (session->kind) {
|
||||
case UP_GTP_U_TUNEND:
|
||||
return session_tunend_tx_est_req(vty, argv, argc);
|
||||
return session_tunend_tx_est_req(session, forw, NULL);
|
||||
case UP_GTP_U_TUNMAP:
|
||||
return session_tunmap_tx_est_req(vty, argv, argc);
|
||||
return session_tunmap_tx_est_req(session, forw, NULL);
|
||||
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")
|
||||
int session_tunmap_tx_mod_req(struct pfcp_tool_session *session, bool forw, osmo_pfcp_resp_cb resp_cb)
|
||||
{
|
||||
struct pfcp_tool_session *session = vty->index;
|
||||
struct pfcp_tool_peer *peer = session->peer;
|
||||
int rc;
|
||||
struct osmo_pfcp_msg *m;
|
||||
@@ -730,14 +934,14 @@ DEFUN(session_tx_mod_req, session_tx_mod_req_cmd,
|
||||
struct osmo_pfcp_ie_f_seid cp_f_seid;
|
||||
|
||||
if (!g_pfcp_tool->ep) {
|
||||
vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE);
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "PFCP endpoint not configured\n");
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (argc > 0 && !strcmp("drop", argv[0]))
|
||||
osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true);
|
||||
else
|
||||
if (forw)
|
||||
osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_FORW, true);
|
||||
else
|
||||
osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true);
|
||||
|
||||
cp_f_seid = (struct osmo_pfcp_ie_f_seid){
|
||||
.seid = session->cp_seid,
|
||||
@@ -745,6 +949,10 @@ DEFUN(session_tx_mod_req, session_tx_mod_req_cmd,
|
||||
osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, osmo_pfcp_endpoint_get_local_addr(g_pfcp_tool->ep));
|
||||
|
||||
m = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, OSMO_PFCP_MSGT_SESSION_MOD_REQ);
|
||||
|
||||
m->ctx.resp_cb = resp_cb;
|
||||
m->ctx.priv = session;
|
||||
|
||||
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){
|
||||
@@ -766,13 +974,25 @@ DEFUN(session_tx_mod_req, session_tx_mod_req_cmd,
|
||||
};
|
||||
|
||||
rc = peer_tx(peer, m);
|
||||
if (rc) {
|
||||
vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE);
|
||||
if (rc)
|
||||
return CMD_WARNING;
|
||||
}
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
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;
|
||||
bool forw = (argc == 0 || !strcmp("forw", argv[0]));
|
||||
int rc = session_tunmap_tx_mod_req(session, forw, NULL);
|
||||
if (rc != CMD_SUCCESS)
|
||||
vty_out(vty, "Failed to send Session Modification Request%s", VTY_NEWLINE);
|
||||
return rc;
|
||||
}
|
||||
|
||||
DEFUN(session_tx_del_req, session_tx_del_req_cmd,
|
||||
"tx session-del-req",
|
||||
TX_STR "Send a Session Deletion Request\n")
|
||||
@@ -799,6 +1019,327 @@ DEFUN(session_tx_del_req, session_tx_del_req_cmd,
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
/* N SESSIONS */
|
||||
|
||||
static int responses_pending = 0;
|
||||
|
||||
DEFUN(wait_responses, wait_responses_cmd,
|
||||
"wait responses",
|
||||
"Let some time pass until events have occurred\n"
|
||||
"Wait for all PFCP responses for pending PFCP requests\n")
|
||||
{
|
||||
if (!responses_pending) {
|
||||
vty_out(vty, "no responses pending, not waiting.%s", VTY_NEWLINE);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
vty_out(vty, "waiting for %d responses...%s", responses_pending, VTY_NEWLINE);
|
||||
vty_flush(vty);
|
||||
|
||||
/* Still operate the message pump while waiting for time to pass */
|
||||
while (!osmo_select_shutdown_done()) {
|
||||
if (pfcp_tool_mainloop(0) == -1)
|
||||
break;
|
||||
if (responses_pending == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
vty_out(vty, "...done waiting for responses%s", VTY_NEWLINE);
|
||||
vty_flush(vty);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
/* N SESSIONS TUNEND */
|
||||
|
||||
int one_session_mod_tunend_resp_cb(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg)
|
||||
{
|
||||
responses_pending--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int one_session_mod_tunend(struct pfcp_tool_session *session)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = session_tunmap_tx_mod_req(session, true, one_session_mod_tunend_resp_cb);
|
||||
if (rc == CMD_SUCCESS)
|
||||
responses_pending++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
void est_resp_get_created_f_teid(struct pfcp_tool_gtp_tun_ep *dst, const struct osmo_pfcp_msg *rx_resp, uint16_t pdr_id)
|
||||
{
|
||||
int i;
|
||||
const struct osmo_pfcp_msg_session_est_resp *r;
|
||||
if (rx_resp->h.message_type != OSMO_PFCP_MSGT_SESSION_EST_RESP)
|
||||
return;
|
||||
|
||||
r = &rx_resp->ies.session_est_resp;
|
||||
|
||||
for (i = 0; i < r->created_pdr_count; i++) {
|
||||
const struct osmo_pfcp_ie_created_pdr *p = &r->created_pdr[i];
|
||||
if (p->pdr_id != pdr_id)
|
||||
continue;
|
||||
if (!p->local_f_teid_present)
|
||||
continue;
|
||||
osmo_sockaddr_str_from_sockaddr(&dst->addr,
|
||||
&p->local_f_teid.fixed.ip_addr.v4.u.sas);
|
||||
dst->teid = p->local_f_teid.fixed.teid;
|
||||
}
|
||||
}
|
||||
|
||||
int one_session_create_tunend_resp_cb(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg)
|
||||
{
|
||||
struct pfcp_tool_session *session = req->ctx.priv;
|
||||
enum osmo_pfcp_cause *cause;
|
||||
const struct osmo_pfcp_msg_session_est_resp *r = NULL;
|
||||
if (rx_resp)
|
||||
r = &rx_resp->ies.session_est_resp;
|
||||
|
||||
responses_pending--;
|
||||
|
||||
if (errmsg)
|
||||
LOGP(DLPFCP, LOGL_ERROR, "%s\n", errmsg);
|
||||
|
||||
cause = rx_resp ? osmo_pfcp_msg_cause(rx_resp) : NULL;
|
||||
if (!cause || *cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED)
|
||||
return 0;
|
||||
|
||||
/* store SEID */
|
||||
if (r && r->up_f_seid_present)
|
||||
session->up_f_seid = r->up_f_seid;
|
||||
/* store access local F-TEID */
|
||||
est_resp_get_created_f_teid(&session->tunend.access.local, rx_resp, PDR_ID_ACCESS);
|
||||
|
||||
/* Success response, now continue with second step: Session Mod to set the CORE's remote side GTP */
|
||||
one_session_mod_tunend(session);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int one_session_create_tunend(struct pfcp_tool_peer *peer)
|
||||
{
|
||||
struct pfcp_tool_session *session;
|
||||
struct pfcp_tool_tunend *te;
|
||||
struct pfcp_tool_gtp_tun_ep *dst;
|
||||
struct osmo_sockaddr osa;
|
||||
int rc;
|
||||
|
||||
session = pfcp_tool_session_find_or_create(peer, peer_new_seid(peer), UP_GTP_U_TUNEND);
|
||||
te = &session->tunend;
|
||||
|
||||
/* Access: set remote GTP address from UPF's point of view.
|
||||
* A local GTP port from osmo-pfcp-tool's point of view. */
|
||||
dst = &te->access.remote;
|
||||
if (llist_empty(&g_pfcp_tool->gtp.gtp_local_addrs)) {
|
||||
/* No local GTP ports configured, just set an arbitrary address. */
|
||||
if (osmo_sockaddr_str_from_str2(&dst->addr, fallback_gtp_ip_access)) {
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "Error setting Access side GTP IP address from %s\n",
|
||||
osmo_quote_cstr_c(OTC_SELECT, fallback_gtp_ip_access, -1));
|
||||
return CMD_WARNING;
|
||||
}
|
||||
} else {
|
||||
/* next local GTP address to emit from */
|
||||
struct udp_port *next;
|
||||
next = g_pfcp_tool->gtp.gtp_local_addrs_next;
|
||||
if (next) {
|
||||
/* Move on by one gtp_local_addr. If past the end of the list, wrap back to the start below. */
|
||||
if (next->entry.next == &g_pfcp_tool->gtp.gtp_local_addrs)
|
||||
next = NULL;
|
||||
else
|
||||
next = container_of(next->entry.next, struct udp_port, entry);
|
||||
}
|
||||
if (!next)
|
||||
next = llist_first_entry_or_null(&g_pfcp_tool->gtp.gtp_local_addrs, struct udp_port,
|
||||
entry);
|
||||
OSMO_ASSERT(next);
|
||||
g_pfcp_tool->gtp.gtp_local_addrs_next = next;
|
||||
|
||||
if (osmo_sockaddr_str_from_osa(&dst->addr, &next->osa)) {
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "Error setting Access side GTP IP address from %s\n",
|
||||
osmo_sockaddr_to_str_c(OTC_SELECT, &next->osa));
|
||||
return CMD_WARNING;
|
||||
}
|
||||
}
|
||||
LOGP(DLGLOBAL, LOGL_NOTICE, "session picked gtp local " OSMO_SOCKADDR_STR_FMT "\n", OSMO_SOCKADDR_STR_FMT_ARGS(&dst->addr));
|
||||
dst->teid = pfcp_tool_new_teid();
|
||||
|
||||
/* Set UE address */
|
||||
te->access.local = (struct pfcp_tool_gtp_tun_ep){};
|
||||
pfcp_tool_next_ue_addr(&osa);
|
||||
if (osmo_sockaddr_str_from_osa(&te->core.ue_local_addr, &osa)) {
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "Error setting UE address from %s\n",
|
||||
osmo_sockaddr_to_str_c(OTC_SELECT, &osa));
|
||||
return CMD_WARNING;
|
||||
};
|
||||
|
||||
/* Send initial Session Establishment Request */
|
||||
rc = session_tunend_tx_est_req(session, false, one_session_create_tunend_resp_cb);
|
||||
if (rc == CMD_SUCCESS)
|
||||
responses_pending++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* N SESSIONS TUNMAP */
|
||||
|
||||
int one_session_mod_tunmap_resp_cb(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg)
|
||||
{
|
||||
responses_pending--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int one_session_mod_tunmap(struct pfcp_tool_session *session)
|
||||
{
|
||||
struct pfcp_tool_gtp_tun_ep *dst;
|
||||
int rc;
|
||||
|
||||
dst = &session->tunmap.core.remote;
|
||||
if (osmo_sockaddr_str_from_str2(&dst->addr, gtp_ip_core)) {
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "Error setting GTP IP address from %s\n",
|
||||
osmo_quote_cstr_c(OTC_SELECT, gtp_ip_core, -1));
|
||||
return CMD_WARNING;
|
||||
}
|
||||
dst->teid = pfcp_tool_new_teid();
|
||||
|
||||
rc = session_tunmap_tx_mod_req(session, true, one_session_mod_tunmap_resp_cb);
|
||||
if (rc == CMD_SUCCESS)
|
||||
responses_pending++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int one_session_create_tunmap_resp_cb(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg)
|
||||
{
|
||||
struct pfcp_tool_session *session = req->ctx.priv;
|
||||
enum osmo_pfcp_cause *cause;
|
||||
|
||||
responses_pending--;
|
||||
|
||||
if (errmsg)
|
||||
LOGP(DLPFCP, LOGL_ERROR, "%s\n", errmsg);
|
||||
|
||||
cause = rx_resp ? osmo_pfcp_msg_cause(rx_resp) : NULL;
|
||||
if (!cause || *cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED)
|
||||
return 0;
|
||||
|
||||
/* store SEID */
|
||||
if (rx_resp->ies.session_est_resp.up_f_seid_present)
|
||||
session->up_f_seid = rx_resp->ies.session_est_resp.up_f_seid;
|
||||
|
||||
/* store local F-TEIDs */
|
||||
est_resp_get_created_f_teid(&session->tunmap.access.local, rx_resp, PDR_ID_ACCESS);
|
||||
est_resp_get_created_f_teid(&session->tunmap.core.local, rx_resp, PDR_ID_CORE);
|
||||
|
||||
/* Success response, now continue with second step: Session Mod to set the CORE's remote side GTP */
|
||||
one_session_mod_tunmap(session);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int one_session_create_tunmap(struct pfcp_tool_peer *peer)
|
||||
{
|
||||
struct pfcp_tool_session *session;
|
||||
struct pfcp_tool_tunmap *tm;
|
||||
struct pfcp_tool_gtp_tun_ep *dst;
|
||||
int rc;
|
||||
|
||||
session = pfcp_tool_session_find_or_create(peer, peer_new_seid(peer), UP_GTP_U_TUNMAP);
|
||||
tm = &session->tunmap;
|
||||
|
||||
/* Access: set remote GTP address */
|
||||
dst = &tm->access.remote;
|
||||
if (osmo_sockaddr_str_from_str2(&dst->addr, fallback_gtp_ip_access)) {
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "Error setting GTP IP address from %s\n",
|
||||
osmo_quote_cstr_c(OTC_SELECT, fallback_gtp_ip_access, -1));
|
||||
return CMD_WARNING;
|
||||
}
|
||||
dst->teid = pfcp_tool_new_teid();
|
||||
|
||||
/* Core: set remote GTP address */
|
||||
dst = &tm->core.remote;
|
||||
if (osmo_sockaddr_str_from_str2(&dst->addr, gtp_ip_core)) {
|
||||
LOGP(DLGLOBAL, LOGL_ERROR, "Error setting GTP IP address from %s\n",
|
||||
osmo_quote_cstr_c(OTC_SELECT, gtp_ip_core, -1));
|
||||
return CMD_WARNING;
|
||||
}
|
||||
dst->teid = pfcp_tool_new_teid();
|
||||
|
||||
/* Set local F-TEIDs == CHOOSE */
|
||||
tm->access.local = (struct pfcp_tool_gtp_tun_ep){};
|
||||
tm->core.local = (struct pfcp_tool_gtp_tun_ep){};
|
||||
|
||||
/* Send initial Session Establishment Request */
|
||||
rc = session_tunmap_tx_est_req(session, false, one_session_create_tunmap_resp_cb);
|
||||
if (rc == CMD_SUCCESS)
|
||||
responses_pending++;
|
||||
return rc;
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
static void n_sessions_create(struct vty *vty, struct pfcp_tool_peer *peer, int n, enum up_gtp_action_kind kind)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < n; i++) {
|
||||
int rc;
|
||||
if (kind == UP_GTP_U_TUNMAP)
|
||||
rc = one_session_create_tunmap(peer);
|
||||
else
|
||||
rc = one_session_create_tunend(peer);
|
||||
if (rc != CMD_SUCCESS)
|
||||
break;
|
||||
|
||||
/* handle any pending select work */
|
||||
while (!osmo_select_shutdown_done()) {
|
||||
rc = pfcp_tool_mainloop(1);
|
||||
/* quit requested */
|
||||
if (rc < 0)
|
||||
return;
|
||||
/* no fd needed service */
|
||||
if (rc == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Every N created sessions, wait for pending responses */
|
||||
if (!(i & 0x3f) && responses_pending) {
|
||||
vty_out(vty, "waiting for %d responses...%s", responses_pending, VTY_NEWLINE);
|
||||
vty_flush(vty);
|
||||
while (!osmo_select_shutdown_done()) {
|
||||
if (pfcp_tool_mainloop(0) == -1)
|
||||
break;
|
||||
if (responses_pending == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void n_sessions_delete(struct pfcp_tool_peer *peer, int n, enum up_gtp_action_kind kind)
|
||||
{
|
||||
}
|
||||
|
||||
DEFUN(n_sessions, n_sessions_cmd,
|
||||
"n (<0-2147483647>|all) session (create|delete) (tunend|tunmap)",
|
||||
"Batch run\n"
|
||||
"Perform the action N times, or on all available entries\n"
|
||||
"In a batch run, create and later delete a number of sessions at once.\n"
|
||||
"Create N new sessions\n"
|
||||
"Delete N sessions created earlier\n"
|
||||
TUNEND_STR TUNMAP_STR)
|
||||
{
|
||||
struct pfcp_tool_peer *peer = vty->index;
|
||||
int n = ((strcmp("all", argv[0]) == 0) ? -1 : atoi(argv[0]));
|
||||
bool create = (strcmp("create", argv[1]) == 0);
|
||||
enum up_gtp_action_kind kind;
|
||||
if (!strcmp(argv[2], "tunmap"))
|
||||
kind = UP_GTP_U_TUNMAP;
|
||||
else
|
||||
kind = UP_GTP_U_TUNEND;
|
||||
|
||||
if (create)
|
||||
n_sessions_create(vty, peer, n, kind);
|
||||
else
|
||||
n_sessions_delete(peer, n, kind);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static void install_ve_and_config(struct cmd_element *cmd)
|
||||
{
|
||||
install_element_ve(cmd);
|
||||
@@ -818,23 +1359,56 @@ void pfcp_tool_vty_init_cmds()
|
||||
OSMO_ASSERT(g_pfcp_tool != NULL);
|
||||
|
||||
install_ve_and_config(&c_sleep_cmd);
|
||||
install_ve_and_config(&c_date_cmd);
|
||||
install_ve_and_config(&wait_responses_cmd);
|
||||
|
||||
install_ve_and_config(&peer_cmd);
|
||||
install_node(&peer_node, NULL);
|
||||
|
||||
install_element(PEER_NODE, &c_sleep_cmd);
|
||||
install_element(PEER_NODE, &c_date_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, &n_sessions_cmd);
|
||||
install_element(PEER_NODE, &wait_responses_cmd);
|
||||
|
||||
install_element(PEER_NODE, &session_cmd);
|
||||
install_element(PEER_NODE, &session_endecaps_cmd);
|
||||
install_node(&session_node, NULL);
|
||||
install_element(SESSION_NODE, &c_sleep_cmd);
|
||||
install_element(SESSION_NODE, &c_date_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_f_teid_cmd);
|
||||
install_element(SESSION_NODE, &s_f_teid_choose_cmd);
|
||||
|
||||
install_ve_and_config(&c_gtp_local_cmd);
|
||||
install_ve_and_config(&c_ue_ip_range_cmd);
|
||||
|
||||
install_ve_and_config(>p_flood_cmd);
|
||||
install_node(>p_flood_node, NULL);
|
||||
install_element(GTP_FLOOD_NODE, >pf_workers_cmd);
|
||||
install_element(GTP_FLOOD_NODE, >pf_io_uring_queue_size_cmd);
|
||||
install_element(GTP_FLOOD_NODE, >pf_flows_cmd);
|
||||
install_element(GTP_FLOOD_NODE, >pf_packets_cmd);
|
||||
install_element(GTP_FLOOD_NODE, >pf_payload_src_port_cmd);
|
||||
install_element(GTP_FLOOD_NODE, >pf_payload_target_ip_cmd);
|
||||
install_element(GTP_FLOOD_NODE, >pf_payload_target_port_cmd);
|
||||
install_element(GTP_FLOOD_NODE, >pf_slew_cmd);
|
||||
}
|
||||
|
||||
int pfcp_tool_vty_go_parent(struct vty *vty)
|
||||
{
|
||||
switch (vty->node) {
|
||||
case GTP_FLOOD_NODE:
|
||||
/* exiting a 'gtp flood' configuration, start the flooding */
|
||||
pfcp_tool_gtp_flood_start();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
111
src/osmo-pfcp-tool/range.c
Normal file
111
src/osmo-pfcp-tool/range.c
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* (C) 2024 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/socket.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include <osmocom/pfcptool/range.h>
|
||||
|
||||
void range_val_set_int(struct range_val *rv, uint32_t val)
|
||||
{
|
||||
*rv = (struct range_val){
|
||||
.buf = { (uint64_t)val, 0 },
|
||||
.size = sizeof(val),
|
||||
};
|
||||
}
|
||||
|
||||
uint32_t range_val_get_int(const struct range_val *rv)
|
||||
{
|
||||
return rv->buf[0];
|
||||
}
|
||||
|
||||
void range_val_set_addr(struct range_val *rv, const struct osmo_sockaddr *val)
|
||||
{
|
||||
*rv = (struct range_val){};
|
||||
rv->size = osmo_sockaddr_to_octets((void *)rv->buf, sizeof(rv->buf), val);
|
||||
|
||||
#if !OSMO_IS_BIG_ENDIAN
|
||||
for (int i = 0; i < rv->size / 2; i++) {
|
||||
uint8_t *rvbuf = (void *)rv->buf;
|
||||
uint8_t tmp = rvbuf[i];
|
||||
rvbuf[i] = rvbuf[rv->size - 1 - i];
|
||||
rvbuf[rv->size - 1 - i] = tmp;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void range_val_get_addr(struct osmo_sockaddr *dst, const struct range_val *rv)
|
||||
{
|
||||
void *buf;
|
||||
#if OSMO_IS_BIG_ENDIAN
|
||||
buf = rv->buf;
|
||||
#else
|
||||
int i;
|
||||
uint8_t rev[sizeof(rv->buf)];
|
||||
uint8_t *val = (void *)rv->buf;
|
||||
for (i = 0; i < rv->size; i++)
|
||||
rev[i] = val[rv->size - 1 - i];
|
||||
buf = rev;
|
||||
#endif
|
||||
osmo_sockaddr_from_octets(dst, buf, rv->size);
|
||||
}
|
||||
|
||||
void range_val_inc(struct range_val *rv)
|
||||
{
|
||||
uint64_t was = rv->buf[0];
|
||||
rv->buf[0]++;
|
||||
if (rv->buf[0] < was)
|
||||
rv->buf[1]++;
|
||||
}
|
||||
|
||||
int range_val_cmp(const struct range_val *a, const struct range_val *b)
|
||||
{
|
||||
int rc;
|
||||
if (a == b)
|
||||
return 0;
|
||||
if (!a)
|
||||
return -1;
|
||||
if (!b)
|
||||
return 1;
|
||||
rc = OSMO_CMP(a->buf[0], b->buf[0]);
|
||||
if (rc)
|
||||
return rc;
|
||||
rc = OSMO_CMP(a->buf[1], b->buf[1]);
|
||||
if (rc)
|
||||
return rc;
|
||||
return OSMO_CMP(a->size, b->size);
|
||||
}
|
||||
|
||||
void range_next(struct range *r)
|
||||
{
|
||||
if (range_val_cmp(&r->next, &r->first) < 0) {
|
||||
r->next = r->first;
|
||||
return;
|
||||
}
|
||||
if (range_val_cmp(&r->next, &r->last) >= 0) {
|
||||
r->next = r->first;
|
||||
r->wrapped = true;
|
||||
return;
|
||||
}
|
||||
range_val_inc(&r->next);
|
||||
}
|
Reference in New Issue
Block a user