/* osmo-pfcp-tool 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 "pfcp_tool.h" enum pfcp_tool_vty_node { PEER_NODE = _LAST_OSMOVTY_NODE + 1, SESSION_NODE, }; DEFUN(c_local_addr, c_local_addr_cmd, "local-addr IP_ADDR", "Set the local IP address to bind on for PFCP; see also 'listen'\n" "IP address\n") { if (g_pfcp_tool->ep != NULL) { vty_out(vty, "Already listening on %s%s", osmo_sockaddr_to_str_c(OTC_SELECT, 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()) break; } osmo_timer_del(&t); vty_out(vty, "...zzZ %d.%03ds%s", secs, msecs, VTY_NEWLINE); vty_flush(vty); return CMD_SUCCESS; } static struct cmd_node peer_node = { PEER_NODE, "%s(peer)# ", 1, }; DEFUN(peer, peer_cmd, "pfcp-peer REMOTE_ADDR", "Enter the 'peer' node for the given remote address\n" "Remote PFCP peer's IP address\n") { struct pfcp_tool_peer *peer; struct osmo_sockaddr_str remote_addr_str; struct osmo_sockaddr remote_addr; osmo_sockaddr_str_from_str(&remote_addr_str, argv[0], OSMO_PFCP_PORT); osmo_sockaddr_str_to_sockaddr(&remote_addr_str, (struct sockaddr_storage *)&remote_addr); peer = pfcp_tool_peer_find_or_create(&remote_addr); vty->index = peer; vty->node = PEER_NODE; return CMD_SUCCESS; } #define TX_STR "Send a PFCP message to a peer\n" DEFUN(peer_tx_heartbeat, peer_tx_heartbeat_cmd, "tx heartbeat", TX_STR "Send a Heartbeat Request\n") { struct pfcp_tool_peer *peer = vty->index; int rc; if (!g_pfcp_tool->ep) { vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); return CMD_WARNING; } vty_out(vty, "Tx Heartbeat Request to %s%s", osmo_sockaddr_to_str_c(OTC_SELECT, &peer->remote_addr), VTY_NEWLINE); rc = osmo_pfcp_endpoint_tx_heartbeat_req(g_pfcp_tool->ep, &peer->remote_addr); if (rc) { vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN(peer_tx_assoc_setup_req, peer_tx_assoc_setup_req_cmd, "tx assoc-setup-req", TX_STR "Send an Association Setup Request\n") { struct pfcp_tool_peer *peer = vty->index; int rc; struct osmo_pfcp_msg *m; if (!g_pfcp_tool->ep) { vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); return CMD_WARNING; } m = osmo_pfcp_msg_alloc_tx_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, }; int session_tunend_tx_est_req(struct vty *vty, const char **argv, int argc) { struct pfcp_tool_session *session = vty->index; struct pfcp_tool_peer *peer = session->peer; int rc; struct osmo_pfcp_msg *m; struct osmo_pfcp_ie_f_teid f_teid_access_local; struct osmo_pfcp_ie_outer_header_creation ohc_access; struct osmo_pfcp_ie_apply_action aa = {}; struct osmo_sockaddr ue_addr; struct osmo_pfcp_ie_f_seid cp_f_seid; OSMO_ASSERT(session->kind == UP_GTP_U_TUNEND); if (!g_pfcp_tool->ep) { vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); return CMD_WARNING; } if (argc > 0 && !strcmp("drop", argv[0])) osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true); else osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_FORW, true); #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); \ 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->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) { vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); return CMD_WARNING; } return CMD_SUCCESS; } int session_tunmap_tx_est_req(struct vty *vty, const char **argv, int argc) { struct pfcp_tool_session *session = vty->index; struct pfcp_tool_peer *peer = session->peer; int rc; struct osmo_pfcp_msg *m; struct osmo_pfcp_ie_f_seid cp_f_seid; struct osmo_pfcp_ie_f_teid f_teid_access_local; struct osmo_pfcp_ie_outer_header_creation ohc_access; struct osmo_pfcp_ie_f_teid f_teid_core_local; struct osmo_pfcp_ie_outer_header_creation ohc_core; struct osmo_pfcp_ie_apply_action aa = {}; if (!g_pfcp_tool->ep) { vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); return CMD_WARNING; } if (argc > 0 && !strcmp("drop", argv[0])) osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true); else osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_FORW, true); if (session->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->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) { vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN(session_tx_est_req, session_tx_est_req_cmd, "tx session-est-req [(forw|drop)]", TX_STR "Send a Session Establishment Request\n" "Set FAR to FORW = 1 (default)\n" "Set FAR to DROP = 1\n") { struct pfcp_tool_session *session = vty->index; switch (session->kind) { case UP_GTP_U_TUNEND: return session_tunend_tx_est_req(vty, argv, argc); case UP_GTP_U_TUNMAP: return session_tunmap_tx_est_req(vty, argv, argc); default: vty_out(vty, "unknown gtp action%s", VTY_NEWLINE); return CMD_WARNING; } } DEFUN(session_tx_mod_req, session_tx_mod_req_cmd, "tx session-mod-req far [(forw|drop)]", TX_STR "Send a Session Modification Request\n" "Set FAR to FORW = 1\n" "Set FAR to DROP = 1\n") { struct pfcp_tool_session *session = vty->index; struct pfcp_tool_peer *peer = session->peer; int rc; struct osmo_pfcp_msg *m; struct osmo_pfcp_ie_apply_action aa = {}; struct osmo_pfcp_ie_f_seid cp_f_seid; if (!g_pfcp_tool->ep) { vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); return CMD_WARNING; } if (argc > 0 && !strcmp("drop", argv[0])) osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true); else osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_FORW, true); cp_f_seid = (struct osmo_pfcp_ie_f_seid){ .seid = session->cp_seid, }; osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, 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->h.seid_present = true; m->h.seid = session->up_f_seid.seid; m->ies.session_mod_req = (struct osmo_pfcp_msg_session_mod_req){ .cp_f_seid_present = true, .cp_f_seid = cp_f_seid, .upd_far_count = 2, .upd_far = { { .far_id = 1, .apply_action_present = true, .apply_action = aa, }, { .far_id = 2, .apply_action_present = true, .apply_action = aa, }, }, }; rc = peer_tx(peer, m); if (rc) { vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN(session_tx_del_req, session_tx_del_req_cmd, "tx session-del-req", TX_STR "Send a Session Deletion Request\n") { struct pfcp_tool_session *session = vty->index; struct pfcp_tool_peer *peer = session->peer; int rc; struct osmo_pfcp_msg *m; if (!g_pfcp_tool->ep) { vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); return CMD_WARNING; } m = osmo_pfcp_msg_alloc_tx_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; } static void install_ve_and_config(struct cmd_element *cmd) { install_element_ve(cmd); install_element(CONFIG_NODE, cmd); } void pfcp_tool_vty_init_cfg() { OSMO_ASSERT(g_pfcp_tool != NULL); install_ve_and_config(&c_local_addr_cmd); install_ve_and_config(&c_listen_cmd); } void pfcp_tool_vty_init_cmds() { OSMO_ASSERT(g_pfcp_tool != NULL); install_ve_and_config(&c_sleep_cmd); install_ve_and_config(&peer_cmd); install_node(&peer_node, NULL); install_element(PEER_NODE, &c_sleep_cmd); install_element(PEER_NODE, &peer_tx_heartbeat_cmd); install_element(PEER_NODE, &peer_tx_assoc_setup_req_cmd); install_element(PEER_NODE, &peer_retrans_req_cmd); install_element(PEER_NODE, &session_cmd); install_element(PEER_NODE, &session_endecaps_cmd); install_node(&session_node, NULL); install_element(SESSION_NODE, &c_sleep_cmd); install_element(SESSION_NODE, &session_tx_est_req_cmd); install_element(SESSION_NODE, &session_tx_mod_req_cmd); install_element(SESSION_NODE, &session_tx_del_req_cmd); install_element(SESSION_NODE, &s_ue_cmd); install_element(SESSION_NODE, &s_f_teid_cmd); install_element(SESSION_NODE, &s_f_teid_choose_cmd); }