/* osmo-upf-load-gen interface to quagga VTY */ /* * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH * All Rights Reserved. * * Author: Neels Janosch Hofmeyr * * 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 . * */ #include #include #include #include #include #include #include #include #include DEFUN(c_local_addr, c_local_addr_cmd, "local-addr IP_ADDR", "Set the local IP address to bind on for PFCP; see also 'listen'\n" "IP address\n") { if (g_pfcp_tool->ep != NULL) { vty_out(vty, "Already listening on %s%s", osmo_sockaddr_to_str_c(OTC_SELECT, osmo_pfcp_endpoint_get_local_addr(g_pfcp_tool->ep)), VTY_NEWLINE); return CMD_WARNING; } osmo_talloc_replace_string(g_pfcp_tool, &g_pfcp_tool->vty_cfg.local_ip, argv[0]); return CMD_SUCCESS; } DEFUN(c_listen, c_listen_cmd, "listen", "Bind local PFCP port and listen; see also 'local-addr'\n") { struct osmo_sockaddr_str local_addr; struct osmo_pfcp_endpoint_cfg cfg; int rc; OSMO_ASSERT(g_pfcp_tool); if (g_pfcp_tool->ep != NULL) { vty_out(vty, "Already listening on %s%s", osmo_sockaddr_to_str_c(OTC_SELECT, osmo_pfcp_endpoint_get_local_addr(g_pfcp_tool->ep)), VTY_NEWLINE); return CMD_WARNING; } cfg = (struct osmo_pfcp_endpoint_cfg){ .rx_msg_cb = pfcp_tool_rx_msg, }; /* Translate address string from VTY config to osmo_sockaddr: first read into osmo_sockaddr_str, then write to * osmo_sockaddr. */ osmo_sockaddr_str_from_str(&local_addr, g_pfcp_tool->vty_cfg.local_ip, g_pfcp_tool->vty_cfg.local_port); osmo_sockaddr_str_to_sockaddr(&local_addr, &cfg.local_addr.u.sas); /* Also use this address as the local PFCP Node Id */ osmo_pfcp_ie_node_id_from_osmo_sockaddr(&cfg.local_node_id, &cfg.local_addr); g_pfcp_tool->ep = osmo_pfcp_endpoint_create(g_pfcp_tool, &cfg); if (!g_pfcp_tool->ep) { vty_out(vty, "Failed to allocate PFCP endpoint.%s", VTY_NEWLINE); return CMD_WARNING; } osmo_pfcp_endpoint_set_seq_nr_state(g_pfcp_tool->ep, rand()); rc = osmo_pfcp_endpoint_bind(g_pfcp_tool->ep); if (rc) { vty_out(vty, "Failed to bind PFCP endpoint on %s: %s%s", osmo_sockaddr_to_str_c(OTC_SELECT, osmo_pfcp_endpoint_get_local_addr(g_pfcp_tool->ep)), strerror(-rc), VTY_NEWLINE); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN(c_sleep, c_sleep_cmd, "sleep <0-999999> [<0-999>]", "Let some time pass\n" "Seconds to wait\n" "Additional milliseconds to wait\n") { int secs = atoi(argv[0]); int msecs = 0; struct osmo_timer_list t = {}; if (argc > 1) msecs = atoi(argv[1]); vty_out(vty, "zzZ %d.%03ds...%s", secs, msecs, VTY_NEWLINE); vty_flush(vty); osmo_timer_setup(&t, NULL, NULL); osmo_timer_schedule(&t, secs, msecs * 1000); /* Still operate the message pump while waiting for time to pass */ while (t.active && !osmo_select_shutdown_done()) { if (pfcp_tool_mainloop(0) == -1) break; } osmo_timer_del(&t); vty_out(vty, "...zzZ %d.%03ds%s", secs, msecs, VTY_NEWLINE); vty_flush(vty); 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_NOT_NULL(&addr_str), VTY_NEWLINE); p = pfcp_tool_have_local_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; } DEFUN(c_gtp_core, c_gtp_core_cmd, "gtp core IP_ADDR [<1-65535>]", GTP_STR "Add a core 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, "core GTP port: " OSMO_SOCKADDR_STR_FMT "%s", OSMO_SOCKADDR_STR_FMT_ARGS_NOT_NULL(&addr_str), VTY_NEWLINE); p = pfcp_tool_have_core_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 (Rx and Tx) to launch, to emit GTP traffic from\n" "Number\n") { g_pfcp_tool->gtp.flood.cfg.num_rx_workers = atoi(argv[0]); g_pfcp_tool->gtp.flood.cfg.num_tx_workers = atoi(argv[0]); return CMD_SUCCESS; } DEFUN(gtpf_rx_workers, gtpf_rx_workers_cmd, "rx-workers <1-999>", "Number of Rx worker threads to launch, to receive GTP traffic\n" "Number\n") { g_pfcp_tool->gtp.flood.cfg.num_rx_workers = atoi(argv[0]); return CMD_SUCCESS; } DEFUN(gtpf_tx_workers, gtpf_tx_workers_cmd, "tx-workers <1-999>", "Number of Tx worker threads to launch, to emit GTP traffic from\n" "Number\n") { g_pfcp_tool->gtp.flood.cfg.num_tx_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_payload_append_payload_info, gtpf_payload_append_payload_info_cmd, "payload append-info", PAYLOAD_STR "Append info about the data stream to the emitted payload." " In a tunmap scenario, append the UPF's core side's TEID to the end of the generated payload," " to allow the remote receiver to echo back payload using the correct TEID, without the need" " for out-of-band communication (see osmo-udp-responder)\n") { g_pfcp_tool->gtp.payload.append_info = true; 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 (1000000 == 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)# ", 1, }; DEFUN(peer, peer_cmd, "pfcp-peer REMOTE_ADDR", "Enter the 'peer' node for the given remote address\n" "Remote PFCP peer's IP address\n") { struct pfcp_tool_peer *peer; struct osmo_sockaddr_str remote_addr_str; struct osmo_sockaddr remote_addr; osmo_sockaddr_str_from_str(&remote_addr_str, argv[0], OSMO_PFCP_PORT); osmo_sockaddr_str_to_sockaddr(&remote_addr_str, (struct sockaddr_storage *)&remote_addr); peer = pfcp_tool_peer_find_or_create(&remote_addr); vty->index = peer; vty->node = PEER_NODE; return CMD_SUCCESS; } #define TX_STR "Send a PFCP message to a peer\n" DEFUN(peer_tx_heartbeat, peer_tx_heartbeat_cmd, "tx heartbeat", TX_STR "Send a Heartbeat Request\n") { struct pfcp_tool_peer *peer = vty->index; int rc; if (!g_pfcp_tool->ep) { vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); return CMD_WARNING; } vty_out(vty, "Tx Heartbeat Request to %s%s", osmo_sockaddr_to_str_c(OTC_SELECT, &peer->remote_addr), VTY_NEWLINE); rc = osmo_pfcp_endpoint_tx_heartbeat_req(g_pfcp_tool->ep, &peer->remote_addr); if (rc) { vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN(peer_tx_assoc_setup_req, peer_tx_assoc_setup_req_cmd, "tx assoc-setup-req", TX_STR "Send an Association Setup Request\n") { struct pfcp_tool_peer *peer = vty->index; int rc; struct osmo_pfcp_msg *m; if (!g_pfcp_tool->ep) { vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); return CMD_WARNING; } m = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, OSMO_PFCP_MSGT_ASSOC_SETUP_REQ); m->ies.assoc_setup_req.recovery_time_stamp = osmo_pfcp_endpoint_get_recovery_timestamp(g_pfcp_tool->ep); m->ies.assoc_setup_req.cp_function_features_present = true; osmo_pfcp_bits_set(m->ies.assoc_setup_req.cp_function_features.bits, OSMO_PFCP_CP_FEAT_BUNDL, true); rc = peer_tx(peer, m); if (rc) { vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN(peer_retrans_req, peer_retrans_req_cmd, "retrans (req|resp)", "Retransmit the last sent message\n" "Retransmit the last sent PFCP Request\n" "Retransmit the last sent PFCP Response\n") { struct pfcp_tool_peer *peer = vty->index; int rc; struct osmo_pfcp_msg *m; if (!g_pfcp_tool->ep) { vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); return CMD_WARNING; } /* Allocate using API function to have the usual talloc destructor set up, then copy the last request or * response over it. */ m = osmo_pfcp_msg_alloc_rx(OTC_SELECT, &peer->remote_addr); if (strcmp(argv[0], "req") == 0) *m = peer->last_req; else *m = peer->last_resp; OSMO_LOG_PFCP_MSG(m, LOGL_INFO, "retrans %s\n", argv[0]); rc = osmo_pfcp_endpoint_tx_data(g_pfcp_tool->ep, m); if (rc) { vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); return CMD_WARNING; } return CMD_SUCCESS; } static struct cmd_node session_node = { SESSION_NODE, "%s(session)# ", 1, }; #define SESSION_STR "Enter the 'session' node for the given SEID\n" #define TUNEND_STR "Set up GTP tunnel encapsulation/decapsulation (default)\n" #define TUNMAP_STR "Set up GTP tunnel mapping\n" #define SEID_STR "local Session Endpoint ID\n" DEFUN(session, session_cmd, "session [(tunend|tunmap)] [<0-18446744073709551615>]", SESSION_STR TUNEND_STR TUNMAP_STR SEID_STR) { struct pfcp_tool_peer *peer = vty->index; struct pfcp_tool_session *session; enum up_gtp_action_kind kind = UP_GTP_U_TUNEND; if (argc > 0 && !strcmp(argv[0], "tunmap")) kind = UP_GTP_U_TUNMAP; if (argc > 1) session = pfcp_tool_session_find_or_create(peer, atoll(argv[1]), kind); else session = pfcp_tool_session_find_or_create(peer, peer_new_seid(peer), kind); vty->index = session; vty->node = SESSION_NODE; return CMD_SUCCESS; } /* legacy compat: "tunend" was originally named "endecaps" */ DEFUN_CMD_ELEMENT(session, session_endecaps_cmd, "session (endecaps) [<0-18446744073709551615>]", SESSION_STR TUNEND_STR SEID_STR, CMD_ATTR_HIDDEN, 0); DEFUN(s_ue, s_ue_cmd, "ue ip A.B.C.D", "Setup the UE as it appears towards the Core network in plain IP traffic\n" "IP address assigned to the UE\n") { struct pfcp_tool_session *session = vty->index; if (session->kind != UP_GTP_U_TUNEND) { vty_out(vty, "%% Error: 'ue ip' makes no sense in a 'tunmap' session%s", VTY_NEWLINE); return CMD_WARNING; } if (osmo_sockaddr_str_from_str2(&session->tunend.core.ue_local_addr, argv[0])) { vty_out(vty, "Error setting UE IP address%s", VTY_NEWLINE); return CMD_WARNING; } return CMD_SUCCESS; } #define GTP_ACCESS_CORE_STRS \ "Setup GTP\n" \ "Setup GTP towards ACCESS (towards the radio network and the actual UE)\n" \ "Setup GTP towards CORE (towards the internet)\n" #define GTP_LOCAL_STR "Setup GTP on the local side (UPF's local GTP endpoint)\n" #define GTP_REMOTE_STR "Setup GTP on the remote side (UPF's remote GTP peer)\n" #define F_TEID_STR "Set the fully-qualified TEID, i.e. GTP IP address and TEID\n" DEFUN(s_f_teid, s_f_teid_cmd, "gtp (access|core) (local|remote) f-teid A.B.C.D <0-4294967295>", GTP_ACCESS_CORE_STRS GTP_LOCAL_STR GTP_REMOTE_STR F_TEID_STR "GTP peer IP address\n" "GTP TEID\n") { struct pfcp_tool_session *session = vty->index; const char *tun_side = argv[0]; const char *local_remote = argv[1]; const char *addr_str = argv[2]; const char *teid_str = argv[3]; struct pfcp_tool_gtp_tun_ep *dst; switch (session->kind) { case UP_GTP_U_TUNEND: if (!strcmp(tun_side, "access")) { if (!strcmp(local_remote, "local")) dst = &session->tunend.access.local; else dst = &session->tunend.access.remote; } else { vty_out(vty, "%% Error: 'gtp core (local|remote) f-teid': 'tunend' only has GTP on" " the 'access' side%s", VTY_NEWLINE); return CMD_WARNING; } break; case UP_GTP_U_TUNMAP: if (!strcmp(tun_side, "access")) { if (!strcmp(local_remote, "local")) dst = &session->tunmap.access.local; else dst = &session->tunmap.access.remote; } else { if (!strcmp(local_remote, "local")) dst = &session->tunmap.core.local; else dst = &session->tunmap.core.remote; } break; default: OSMO_ASSERT(0); } if (osmo_sockaddr_str_from_str2(&dst->addr, addr_str)) { vty_out(vty, "Error setting GTP IP address from %s%s", osmo_quote_cstr_c(OTC_SELECT, addr_str, -1), VTY_NEWLINE); return CMD_WARNING; } dst->teid = atoi(teid_str); return CMD_SUCCESS; } DEFUN(s_f_teid_choose, s_f_teid_choose_cmd, "gtp (access|core) local f-teid choose", GTP_ACCESS_CORE_STRS GTP_LOCAL_STR F_TEID_STR "Send F-TEID with CHOOSE=1, i.e. the UPF shall return the local F-TEID in a PFCP Created PDR IE\n") { struct pfcp_tool_session *session = vty->index; const char *tun_side = argv[0]; struct pfcp_tool_gtp_tun_ep *dst; switch (session->kind) { case UP_GTP_U_TUNEND: if (!strcmp(tun_side, "access")) { dst = &session->tunend.access.local; } else { vty_out(vty, "%% Error: 'gtp core local choose': 'tunend' only has GTP on" " the 'access' side%s", VTY_NEWLINE); return CMD_WARNING; } break; case UP_GTP_U_TUNMAP: if (!strcmp(tun_side, "access")) dst = &session->tunmap.access.local; else dst = &session->tunmap.core.local; break; default: OSMO_ASSERT(0); } *dst = (struct pfcp_tool_gtp_tun_ep){}; return CMD_SUCCESS; } 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_peer *peer = session->peer; int rc; struct osmo_pfcp_msg *m; struct osmo_pfcp_ie_f_teid f_teid_access_local; struct osmo_pfcp_ie_outer_header_creation ohc_access; struct osmo_pfcp_ie_apply_action aa = {}; struct osmo_sockaddr ue_addr; struct osmo_pfcp_ie_f_seid cp_f_seid; OSMO_ASSERT(session->kind == UP_GTP_U_TUNEND); if (!g_pfcp_tool->ep) { LOGP(DLGLOBAL, LOGL_ERROR, "PFCP endpoint not configured\n"); return CMD_WARNING; } 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)) { \ LOGP(DLGLOBAL, LOGL_ERROR, "Error in " #SRC ": " OSMO_SOCKADDR_STR_FMT "\n", \ OSMO_SOCKADDR_STR_FMT_ARGS(&SRC)); \ return CMD_WARNING; \ } \ } while (0) STR_TO_ADDR(ue_addr, session->tunend.core.ue_local_addr); if (session->tunend.access.local.teid == 0) { f_teid_access_local = (struct osmo_pfcp_ie_f_teid){ .choose_flag = true, .choose = { .ipv4_addr = true, }, }; } else { f_teid_access_local = (struct osmo_pfcp_ie_f_teid){ .fixed = { .teid = session->tunend.access.local.teid, .ip_addr = { .v4_present = true, }, }, }; STR_TO_ADDR(f_teid_access_local.fixed.ip_addr.v4, session->tunend.access.local.addr); } ohc_access = (struct osmo_pfcp_ie_outer_header_creation){ .teid_present = true, .teid = session->tunend.access.remote.teid, .ip_addr.v4_present = true, }; osmo_pfcp_bits_set(ohc_access.desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true); STR_TO_ADDR(ohc_access.ip_addr.v4, session->tunend.access.remote.addr); cp_f_seid = (struct osmo_pfcp_ie_f_seid){ .seid = session->cp_seid, }; 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; /* GTP encapsulation decapsulation: remove header from ACCESS to CORE, add header from CORE towards ACCESS */ m->ies.session_est_req = (struct osmo_pfcp_msg_session_est_req){ .node_id = m->ies.session_est_req.node_id, .cp_f_seid_present = true, .cp_f_seid = cp_f_seid, .create_pdr_count = 2, .create_pdr = { { .pdr_id = PDR_ID_CORE, .precedence = 255, .pdi = { .source_iface = OSMO_PFCP_SOURCE_IFACE_CORE, .ue_ip_address_present = true, .ue_ip_address = { .ip_is_destination = true, .ip_addr = { .v4_present = true, .v4 = ue_addr, }, }, }, .far_id_present = true, .far_id = 1, }, { .pdr_id = PDR_ID_ACCESS, .precedence = 255, .pdi = { .source_iface = OSMO_PFCP_SOURCE_IFACE_ACCESS, .local_f_teid_present = true, .local_f_teid = f_teid_access_local, }, .outer_header_removal_present = true, .outer_header_removal = { .desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4, }, .far_id_present = true, .far_id = 2, }, }, .create_far_count = 2, .create_far = { { .far_id = 1, .forw_params_present = true, .forw_params = { .destination_iface = OSMO_PFCP_DEST_IFACE_ACCESS, .outer_header_creation_present = true, .outer_header_creation = ohc_access, }, .apply_action = aa, }, { .far_id = 2, .forw_params_present = true, .forw_params = { .destination_iface = OSMO_PFCP_DEST_IFACE_CORE, }, .apply_action = aa, }, }, }; rc = peer_tx(peer, m); if (rc) return CMD_WARNING; return CMD_SUCCESS; } int session_tunmap_tx_est_req(struct pfcp_tool_session *session, bool forw, osmo_pfcp_resp_cb resp_cb) { struct pfcp_tool_peer *peer = session->peer; int rc; struct osmo_pfcp_msg *m; struct osmo_pfcp_ie_f_seid cp_f_seid; struct osmo_pfcp_ie_f_teid f_teid_access_local; struct osmo_pfcp_ie_outer_header_creation ohc_access; struct osmo_pfcp_ie_f_teid f_teid_core_local; struct osmo_pfcp_ie_outer_header_creation ohc_core; struct osmo_pfcp_ie_apply_action aa = {}; if (!g_pfcp_tool->ep) { LOGP(DLGLOBAL, LOGL_ERROR, "PFCP endpoint not configured\n"); return CMD_WARNING; } 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){ .choose_flag = true, .choose = { .ipv4_addr = true, }, }; } else { f_teid_access_local = (struct osmo_pfcp_ie_f_teid){ .fixed = { .teid = session->tunmap.access.local.teid, .ip_addr = { .v4_present = true, .v4 = osmo_pfcp_endpoint_get_cfg(g_pfcp_tool->ep)->local_addr, }, }, }; STR_TO_ADDR(f_teid_access_local.fixed.ip_addr.v4, session->tunmap.access.local.addr); } ohc_access = (struct osmo_pfcp_ie_outer_header_creation){ .teid_present = true, .teid = session->tunmap.access.remote.teid, .ip_addr.v4_present = true, }; osmo_pfcp_bits_set(ohc_access.desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true); STR_TO_ADDR(ohc_access.ip_addr.v4, session->tunmap.access.remote.addr); if (session->tunmap.core.local.teid == 0) { f_teid_core_local = (struct osmo_pfcp_ie_f_teid){ .choose_flag = true, .choose = { .ipv4_addr = true, }, }; } else { f_teid_core_local = (struct osmo_pfcp_ie_f_teid){ .fixed = { .teid = session->tunmap.core.local.teid, .ip_addr = { .v4_present = true, }, }, }; STR_TO_ADDR(f_teid_core_local.fixed.ip_addr.v4, session->tunmap.core.local.addr); } ohc_core = (struct osmo_pfcp_ie_outer_header_creation){ .teid_present = true, .teid = session->tunmap.core.remote.teid, .ip_addr.v4_present = true, }; osmo_pfcp_bits_set(ohc_core.desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true); STR_TO_ADDR(ohc_core.ip_addr.v4, session->tunmap.core.remote.addr); cp_f_seid = (struct osmo_pfcp_ie_f_seid){ .seid = session->cp_seid, }; 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 */ m->ies.session_est_req = (struct osmo_pfcp_msg_session_est_req){ .node_id = m->ies.session_est_req.node_id, .cp_f_seid_present = true, .cp_f_seid = cp_f_seid, .create_pdr_count = 2, .create_pdr = { { .pdr_id = PDR_ID_CORE, .precedence = 255, .pdi = { .source_iface = OSMO_PFCP_SOURCE_IFACE_CORE, .local_f_teid_present = true, .local_f_teid = f_teid_core_local, }, .outer_header_removal_present = true, .outer_header_removal = { .desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4, }, .far_id_present = true, .far_id = 1, }, { .pdr_id = PDR_ID_ACCESS, .precedence = 255, .pdi = { .source_iface = OSMO_PFCP_SOURCE_IFACE_ACCESS, .local_f_teid_present = true, .local_f_teid = f_teid_access_local, }, .outer_header_removal_present = true, .outer_header_removal = { .desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4, }, .far_id_present = true, .far_id = 2, }, }, .create_far_count = 2, .create_far = { { .far_id = 1, .forw_params_present = true, .forw_params = { .destination_iface = OSMO_PFCP_DEST_IFACE_ACCESS, .outer_header_creation_present = true, .outer_header_creation = ohc_access, }, .apply_action = aa, }, { .far_id = 2, .forw_params_present = true, .forw_params = { .destination_iface = OSMO_PFCP_DEST_IFACE_CORE, .outer_header_creation_present = true, .outer_header_creation = ohc_core, }, .apply_action = aa, }, }, }; rc = peer_tx(peer, m); if (rc) return CMD_WARNING; return CMD_SUCCESS; } DEFUN(session_tx_est_req, session_tx_est_req_cmd, "tx session-est-req [(forw|drop)]", TX_STR "Send a Session Establishment Request\n" "Set FAR to FORW = 1 (default)\n" "Set FAR to DROP = 1\n") { struct pfcp_tool_session *session = vty->index; bool forw = (argc == 0 || !strcmp("forw", argv[0])); switch (session->kind) { case UP_GTP_U_TUNEND: return session_tunend_tx_est_req(session, forw, NULL); case UP_GTP_U_TUNMAP: return session_tunmap_tx_est_req(session, forw, NULL); default: vty_out(vty, "unknown gtp action%s", VTY_NEWLINE); return CMD_WARNING; } } int session_tunmap_tx_mod_req(struct pfcp_tool_session *session, bool forw, osmo_pfcp_resp_cb resp_cb) { struct pfcp_tool_peer *peer = session->peer; int rc; struct osmo_pfcp_msg *m; struct osmo_pfcp_ie_apply_action aa = {}; struct osmo_pfcp_ie_f_seid cp_f_seid; if (!g_pfcp_tool->ep) { LOGP(DLGLOBAL, LOGL_ERROR, "PFCP endpoint not configured\n"); return CMD_WARNING; } 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, }; 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){ .cp_f_seid_present = true, .cp_f_seid = cp_f_seid, .upd_far_count = 2, .upd_far = { { .far_id = 1, .apply_action_present = true, .apply_action = aa, }, { .far_id = 2, .apply_action_present = true, .apply_action = aa, }, }, }; rc = peer_tx(peer, m); if (rc) 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") { struct pfcp_tool_session *session = vty->index; struct pfcp_tool_peer *peer = session->peer; int rc; struct osmo_pfcp_msg *m; if (!g_pfcp_tool->ep) { vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); return CMD_WARNING; } m = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, OSMO_PFCP_MSGT_SESSION_DEL_REQ); m->h.seid_present = true; m->h.seid = session->up_f_seid.seid; rc = peer_tx(peer, m); if (rc) { vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); return CMD_WARNING; } return CMD_SUCCESS; } /* 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 set_access_ran_tunend(struct pfcp_tool_gtp_tun_ep *dst) { 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; } return CMD_SUCCESS; } /* 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; } dst->teid = pfcp_tool_new_teid(); LOGP(DLGLOBAL, LOGL_DEBUG, "session picked gtp local " OSMO_SOCKADDR_STR_FMT " TEID 0x%x\n", OSMO_SOCKADDR_STR_FMT_ARGS(&dst->addr), dst->teid); return CMD_SUCCESS; } static int set_core_cn_tunend(struct pfcp_tool_gtp_tun_ep *dst) { if (llist_empty(&g_pfcp_tool->gtp.gtp_core_addrs)) { /* No core 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; } return CMD_SUCCESS; } /* next core GTP address to emit from */ struct udp_port *next; next = llist_round_robin(&g_pfcp_tool->gtp.gtp_core_addrs, &g_pfcp_tool->gtp.gtp_core_addrs_next, struct udp_port, entry); 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; } dst->teid = pfcp_tool_new_teid(); LOGP(DLGLOBAL, LOGL_DEBUG, "session picked gtp core " OSMO_SOCKADDR_STR_FMT " TEID 0x%x\n", OSMO_SOCKADDR_STR_FMT_ARGS(&dst->addr), dst->teid); return CMD_SUCCESS; } 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-upf-load-gen's point of view. */ dst = &te->access.remote; rc = set_access_ran_tunend(dst); if (rc != CMD_SUCCESS) return rc; /* 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 from UPF's point of view. * A local GTP port from osmo-upf-load-gen's point of view. */ dst = &tm->access.remote; rc = set_access_ran_tunend(dst); if (rc != CMD_SUCCESS) return rc; /* Core: set remote GTP address */ dst = &tm->core.remote; rc = set_core_cn_tunend(dst); if (rc != CMD_SUCCESS) return rc; /* 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; } #define MASK_WAIT_BATCH_RESPONSES 0x7f 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 & MASK_WAIT_BATCH_RESPONSES) && 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); install_element(CONFIG_NODE, cmd); } void pfcp_tool_vty_init_cfg() { OSMO_ASSERT(g_pfcp_tool != NULL); install_ve_and_config(&c_local_addr_cmd); install_ve_and_config(&c_listen_cmd); } void pfcp_tool_vty_init_cmds() { OSMO_ASSERT(g_pfcp_tool != NULL); install_ve_and_config(&c_sleep_cmd); install_ve_and_config(&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_gtp_core_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_rx_workers_cmd); install_element(GTP_FLOOD_NODE, >pf_tx_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_payload_append_payload_info_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; }