/* OsmoUpf 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 #include #include #include #include #include enum upf_vty_node { PFCP_NODE = _LAST_OSMOVTY_NODE + 1, TUNEND_NODE, TUNMAP_NODE, NETINST_NODE, }; static struct cmd_node cfg_pfcp_node = { PFCP_NODE, "%s(config-pfcp)# ", 1, }; #define pfcp_vty (g_upf->pfcp.vty_cfg) #define tunend_vty (g_upf->tunend.vty_cfg) DEFUN(cfg_pfcp, cfg_pfcp_cmd, "pfcp", "Enter the PFCP configuration node\n") { vty->node = PFCP_NODE; return CMD_SUCCESS; } static int config_write_pfcp(struct vty *vty) { vty_out(vty, "pfcp%s", VTY_NEWLINE); if (strcmp(UPF_PFCP_LISTEN_DEFAULT, pfcp_vty.local_addr)) vty_out(vty, " local-addr %s%s", pfcp_vty.local_addr, VTY_NEWLINE); return CMD_SUCCESS; } DEFUN(cfg_pfcp_local_addr, cfg_pfcp_local_addr_cmd, "local-addr IP_ADDR", "Set the local IP address to bind on for PFCP\n" "IP address\n") { osmo_talloc_replace_string(g_upf, &pfcp_vty.local_addr, argv[0]); return CMD_SUCCESS; } static struct cmd_node cfg_tunend_node = { TUNEND_NODE, "%s(config-tunend)# ", 1, }; #define TUNEND_NODE_STR "Enter the 'tunend' node to configure Linux GTP kernel module usage\n" DEFUN(cfg_tunend, cfg_tunend_cmd, "tunend", TUNEND_NODE_STR) { vty->node = TUNEND_NODE; return CMD_SUCCESS; } /* legacy compat: "tunend" was originally named "gtp" */ DEFUN_CMD_ELEMENT(cfg_tunend, cfg_gtp_cmd, "gtp", TUNEND_NODE_STR, CMD_ATTR_HIDDEN, 0); static int config_write_tunend(struct vty *vty) { struct tunend_vty_cfg_dev *d; vty_out(vty, "tunend%s", VTY_NEWLINE); if (g_upf->tunend.mockup) vty_out(vty, " mockup%s", VTY_NEWLINE); llist_for_each_entry(d, &tunend_vty.devs, entry) { if (d->create) { vty_out(vty, " dev create %s", d->dev_name); if (d->local_addr) vty_out(vty, " %s", d->local_addr); vty_out(vty, "%s", VTY_NEWLINE); } else { vty_out(vty, " dev use %s%s", d->dev_name, VTY_NEWLINE); } } return CMD_SUCCESS; } #define DEV_STR "Configure the GTP device to use for encaps/decaps.\n" DEFUN(cfg_tunend_mockup, cfg_tunend_mockup_cmd, "mockup", "don't actually send commands to the GTP kernel module, just return success\n") { g_upf->tunend.mockup = true; return CMD_SUCCESS; } DEFUN(cfg_tunend_no_mockup, cfg_tunend_no_mockup_cmd, "no mockup", NO_STR "operate GTP kernel module normally\n") { g_upf->tunend.mockup = false; return CMD_SUCCESS; } static struct tunend_vty_cfg_dev *tunend_dev_add(int argc, const char **argv, bool create) { struct tunend_vty_cfg_dev *d = talloc_zero(g_upf, struct tunend_vty_cfg_dev); d->create = create; d->dev_name = talloc_strdup(d, argv[0]); if (argc > 1) d->local_addr = talloc_strdup(d, argv[1]); llist_add(&d->entry, &tunend_vty.devs); return d; } DEFUN(cfg_tunend_dev_create, cfg_tunend_dev_create_cmd, "dev create DEVNAME [LISTEN_ADDR]", DEV_STR "Add GTP device, creating a new Linux kernel GTP device. Will listen on GTPv1 port " OSMO_STRINGIFY_VAL(PORT_GTP1_U) " and GTPv0 port " OSMO_STRINGIFY_VAL(PORT_GTP0_U) " on the specified LISTEN_ADDR\n" "device name, e.g. 'apn0'\n" "IPv4 or IPv6 address to listen on, omit for ANY. LISTEN_ADDR is used to pick a GTP device matching the local" " address for a PFCP Network Instance, which are configured in the 'netinst' node.\n") { struct tunend_vty_cfg_dev *d = tunend_dev_add(argc, argv, true); vty_out(vty, "Added GTP device %s on %s (create new)%s", d->dev_name, d->local_addr ? : "0.0.0.0", VTY_NEWLINE); return CMD_SUCCESS; } DEFUN(cfg_tunend_dev_use, cfg_tunend_dev_use_cmd, "dev use DEVNAME [LOCAL_ADDR]", DEV_STR "Add GTP device, using an existing Linux kernel GTP device, e.g. created by 'gtp-link'\n" "device name, e.g. 'apn0'\n" "The local GTP address this device listens on. It is assumed to be ANY when omitted." " LOCAL_ADDR is used to pick a GTP device matching the local address for a PFCP Network Instance," " which are configured in the 'netinst' node.\n") { struct tunend_vty_cfg_dev *d = tunend_dev_add(argc, argv, false); vty_out(vty, "Added GTP device %s on %s (use existing)%s", d->dev_name, d->local_addr ? : "0.0.0.0", VTY_NEWLINE); return CMD_SUCCESS; } DEFUN(cfg_tunend_dev_del, cfg_tunend_dev_del_cmd, "dev delete DEVNAME", DEV_STR "Remove a GTP device from the configuration, and delete the Linux kernel GTP device if it was created here.\n" "device name, e.g. 'apn0'\n") { const char *dev_name = argv[0]; struct tunend_vty_cfg_dev *d; struct upf_gtp_dev *dev; /* remove from VTY cfg */ llist_for_each_entry(d, &tunend_vty.devs, entry) { if (strcmp(d->dev_name, dev_name)) continue; llist_del(&d->entry); break; } /* close device (and possibly delete from system, via talloc destructor) */ dev = upf_gtp_dev_find_by_name(dev_name); if (dev) talloc_free(dev); return CMD_SUCCESS; } static struct cmd_node cfg_tunmap_node = { TUNMAP_NODE, "%s(config-tunmap)# ", 1, }; #define TUNMAP_NODE_STR "Enter the 'tunmap' node to configure nftables usage\n" DEFUN(cfg_tunmap, cfg_tunmap_cmd, "tunmap", TUNMAP_NODE_STR) { vty->node = TUNMAP_NODE; return CMD_SUCCESS; } /* legacy compat: "tunmap" was originally named "nft" */ DEFUN_CMD_ELEMENT(cfg_tunmap, cfg_nft_cmd, "nft", TUNMAP_NODE_STR, CMD_ATTR_HIDDEN, 0); static int config_write_tunmap(struct vty *vty) { vty_out(vty, "tunmap%s", VTY_NEWLINE); if (g_upf->tunmap.mockup) vty_out(vty, " mockup%s", VTY_NEWLINE); if (g_upf->tunmap.table_name && strcmp(g_upf->tunmap.table_name, "osmo-upf")) vty_out(vty, " table-name %s%s", g_upf->tunmap.table_name, VTY_NEWLINE); return CMD_SUCCESS; } DEFUN(cfg_tunmap_mockup, cfg_tunmap_mockup_cmd, "mockup", "don't actually send rulesets to nftables, just return success\n") { g_upf->tunmap.mockup = true; return CMD_SUCCESS; } DEFUN(cfg_tunmap_no_mockup, cfg_tunmap_no_mockup_cmd, "no mockup", NO_STR "operate nftables rulesets normally\n") { g_upf->tunmap.mockup = false; return CMD_SUCCESS; } DEFUN(cfg_tunmap_table_name, cfg_tunmap_table_name_cmd, "table-name TABLE_NAME", "Set the nft inet table name to create and place GTP tunnel forwarding chains in" " (as in 'nft add table inet foo'). If multiple instances of osmo-upf are running on the same system, each" " osmo-upf must have its own table name. Otherwise the names of created forwarding chains will collide." " The default table name is \"osmo-upf\".\n" "nft inet table name\n") { osmo_talloc_replace_string(g_upf, &g_upf->tunmap.table_name, argv[0]); return CMD_SUCCESS; } #define NFT_RULE_STR "nftables rule specifics\n" #define TUNMAP_STR "GTP tunmap use case (a.k.a. forwarding between two GTP tunnels)\n" #define TUNMAP_APPEND_STR "'tunmap append' feature is no longer available.\n" DEFUN_DEPRECATED(cfg_tunmap_nft_rule_append, cfg_tunmap_nft_rule_append_cmd, "nft-rule tunmap append .NFT_RULE", NFT_RULE_STR TUNMAP_STR TUNMAP_APPEND_STR TUNMAP_APPEND_STR) { vty_out(vty, "%% deprecated config option: 'nft-rule tunmap append'%s", VTY_NEWLINE); return CMD_SUCCESS; } DEFUN_DEPRECATED(cfg_tunmap_no_nft_rule_append, cfg_tunmap_no_nft_rule_append_cmd, "no nft-rule tunmap append", NO_STR NFT_RULE_STR TUNMAP_STR TUNMAP_APPEND_STR TUNMAP_APPEND_STR) { vty_out(vty, "%% deprecated config option: 'no nft-rule tunmap append'%s", VTY_NEWLINE); return CMD_SUCCESS; } DEFUN_DEPRECATED(show_nft_rule_append, show_nft_rule_append_cmd, "show nft-rule tunmap append", SHOW_STR NFT_RULE_STR TUNMAP_STR TUNMAP_APPEND_STR) { vty_out(vty, "%% deprecated config option: 'show nft-rule tunmap append'%s", VTY_NEWLINE); return CMD_SUCCESS; } DEFUN(show_nft_rule_tunmap_example, show_nft_rule_tunmap_example_cmd, "show nft-rule tunmap example", SHOW_STR NFT_RULE_STR TUNMAP_STR "Print a complete nftables ruleset for a tunmap filled with example IP addresses and TEIDs\n") { struct osmo_sockaddr_str str = {}; struct upf_tunmap tunmap = { .access = { .tun = { .local.teid = 0x201, .remote.teid = 0x102, }, .chain_id = 123, }, .core = { .tun = { .local.teid = 0x203, .remote.teid = 0x302, }, .chain_id = 321, }, }; osmo_sockaddr_str_from_str2(&str, "1.1.1.1"); osmo_sockaddr_str_to_sockaddr(&str, &tunmap.access.tun.remote.addr.u.sas); osmo_sockaddr_str_from_str2(&str, "2.2.2.1"); osmo_sockaddr_str_to_sockaddr(&str, &tunmap.access.tun.local.addr.u.sas); osmo_sockaddr_str_from_str2(&str, "2.2.2.3"); osmo_sockaddr_str_to_sockaddr(&str, &tunmap.core.tun.local.addr.u.sas); osmo_sockaddr_str_from_str2(&str, "3.3.3.3"); osmo_sockaddr_str_to_sockaddr(&str, &tunmap.core.tun.remote.addr.u.sas); vty_out(vty, "%% init verdict map:%s", VTY_NEWLINE); vty_out(vty, "%s%s", upf_nft_tunmap_get_table_init_str(OTC_SELECT), VTY_NEWLINE); vty_out(vty, "%s%s", upf_nft_tunmap_get_vmap_init_str(OTC_SELECT), VTY_NEWLINE); vty_out(vty, "%% add tunmap:%s", VTY_NEWLINE); vty_out(vty, "%% %s%s", upf_nft_tunmap_to_str_c(OTC_SELECT, &tunmap), VTY_NEWLINE); vty_out(vty, "%s%s", upf_nft_tunmap_get_ruleset_str(OTC_SELECT, &tunmap), VTY_NEWLINE); vty_out(vty, "%% delete tunmap:%s", VTY_NEWLINE); vty_out(vty, "%s%s", upf_nft_tunmap_get_ruleset_del_str(OTC_SELECT, &tunmap), VTY_NEWLINE); return CMD_SUCCESS; } static struct cmd_node cfg_netinst_node = { NETINST_NODE, "%s(config-netinst)# ", 1, }; DEFUN(cfg_netinst, cfg_netinst_cmd, "netinst", "Enter the Network Instance configuration node\n") { vty->node = NETINST_NODE; return CMD_SUCCESS; } static int config_write_netinst(struct vty *vty) { vty_out(vty, "netinst%s", VTY_NEWLINE); netinst_vty_write(vty, &g_upf->netinst, " ", NULL); return CMD_SUCCESS; } DEFUN(cfg_netinst_add, cfg_netinst_add_cmd, "add NAME ADDR", "add Network Instance: associate a PFCP Network Instance name with a local IP address\n" "Network Instance name as received in PFCP Network Instance IE\n" "IP address of a local interface\n") { const char *errmsg; if (!netinst_add(g_upf, &g_upf->netinst, argv[0], argv[1], &errmsg)) { vty_out(vty, "%% Error: netinst: cannot add %s %s: %s%s", argv[0], argv[1], errmsg ? : "(unknown error)", VTY_NEWLINE); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN(show_netinst, show_netinst_cmd, "show netinst [NAME]", SHOW_STR "List configured Network Instance entries\n" "Show the Network Instance with this name (show all when omitted)\n") { const char *name_or_null = argc > 0 ? argv[0] : NULL; if (!netinst_vty_write(vty, &g_upf->netinst, " ", name_or_null)) { if (name_or_null) vty_out(vty, "%% No such Network Instance entry%s", VTY_NEWLINE); else vty_out(vty, "%% No Network Instance entries configured%s", VTY_NEWLINE); } return CMD_SUCCESS; } DEFUN(cfg_netinst_clear, cfg_netinst_clear_cmd, "clear", "Remove all Network Instance entries\n") { int count = netinst_clear(&g_upf->netinst); vty_out(vty, "netinst entries removed: %d%s", count, VTY_NEWLINE); return CMD_SUCCESS; } DEFUN(show_pdr, show_pdr_cmd, "show pdr", SHOW_STR "List all sessions' PDR and FAR status\n") { struct up_peer *peer; int active_count = 0; int inactive_count = 0; llist_for_each_entry(peer, &g_upf->pfcp.ep->peers, entry) { struct up_session *session; int bkt; hash_for_each(peer->sessions_by_up_seid, bkt, session, node_by_up_seid) { struct pdr *pdr; llist_for_each_entry(pdr, &session->pdrs, entry) { if (!pdr->active) { vty_out(vty, "%s: inactive: %s%s%s%s", session->fi->id, pdr_to_str_c(OTC_SELECT, pdr), pdr->inactive_reason ? ": " : "", pdr->inactive_reason ? : "", VTY_NEWLINE); inactive_count++; } else { vty_out(vty, "%s: active: %s%s", session->fi->id, pdr_to_str_c(OTC_SELECT, pdr), VTY_NEWLINE); active_count++; } } } } vty_out(vty, "(%d of %d active)%s", active_count, active_count + inactive_count, VTY_NEWLINE); return CMD_SUCCESS; } DEFUN(show_gtp, show_gtp_cmd, "show gtp", SHOW_STR "Active GTP tunnels, both tunend and tunmap\n") { struct up_peer *peer; int count = 0; llist_for_each_entry(peer, &g_upf->pfcp.ep->peers, entry) { struct up_session *session; int bkt; hash_for_each(peer->sessions_by_up_seid, bkt, session, node_by_up_seid) { struct up_gtp_action *a; llist_for_each_entry(a, &session->active_gtp_actions, entry) { vty_out(vty, "%s%s", up_gtp_action_to_str_c(OTC_SELECT, a), VTY_NEWLINE); count++; } } } vty_out(vty, "(%d active)%s", count, VTY_NEWLINE); return CMD_SUCCESS; } DEFUN(show_session, show_session_cmd, "show session", SHOW_STR "PFCP Session status\n") { struct up_peer *peer; int inactive_count = 0; int active_count = 0; int fully_active_count = 0; llist_for_each_entry(peer, &g_upf->pfcp.ep->peers, entry) { struct up_session *session; int bkt; hash_for_each(peer->sessions_by_up_seid, bkt, session, node_by_up_seid) { vty_out(vty, "%s %s%s", up_session_to_str_c(OTC_SELECT, session), up_session_gtp_status(session), VTY_NEWLINE); if (up_session_is_active(session)) { if (up_session_is_fully_active(session, NULL, NULL)) fully_active_count++; else active_count++; } else { inactive_count++; } } } vty_out(vty, "(%d fully-active + %d active with some PDR/FAR ignored + %d inactive)%s", fully_active_count, active_count, inactive_count, VTY_NEWLINE); return CMD_SUCCESS; } /* variant: * 0 "gtp1u-echo send to (A.B.C.D|X:X::X:X)" * 1 "gtp1u-echo send to (A.B.C.D|X:X::X:X) local-ip (A.B.C.D|X:X::X:X)" * 2 "gtp1u-echo send to (A.B.C.D|X:X::X:X) local-dev DEV_NAME" */ static int _gtp_echo_tx(struct vty *vty, int variant, int argc, const char **argv) { struct osmo_sockaddr_str addr; struct osmo_sockaddr osa_remote; struct osmo_sockaddr osa_local; struct upf_gtp_dev *gtp_dev = NULL; const char *remote_str = argv[0]; const char *local_str = NULL; if (argc > 1) local_str = argv[1]; /* GTP can be received on port 2152 only, i.e. the remote port must be 2152. (The sending port is allowed to * differ). */ if (osmo_sockaddr_str_from_str(&addr, remote_str, 2152) || osmo_sockaddr_str_to_osa(&addr, &osa_remote)) { vty_out(vty, "%% Error: cannot send Echo: invalid IP address: %s%s", osmo_quote_str(remote_str, -1), VTY_NEWLINE); return CMD_WARNING; } switch (variant) { case 0: gtp_dev = llist_first_entry_or_null(&g_upf->tunend.devs, struct upf_gtp_dev, entry); if (!gtp_dev) { vty_out(vty, "%% Error: cannot send Echo: there is no GTP device%s", VTY_NEWLINE); return CMD_WARNING; } break; case 1: if (osmo_sockaddr_str_from_str(&addr, local_str, 2152) || osmo_sockaddr_str_to_osa(&addr, &osa_local)) { vty_out(vty, "%% Error: cannot send Echo: invalid IP address: %s%s", osmo_quote_str(local_str, -1), VTY_NEWLINE); return CMD_WARNING; } gtp_dev = upf_gtp_dev_find_by_local_addr(&osa_local); if (!gtp_dev) { vty_out(vty, "%% Error: cannot send Echo: this does not seem to be a locally bound GTP address: %s%s", osmo_sockaddr_to_str_c(OTC_SELECT, &osa_local), VTY_NEWLINE); return CMD_WARNING; } break; case 2: gtp_dev = upf_gtp_dev_find_by_name(local_str); if (!gtp_dev) { vty_out(vty, "%% Error: cannot send Echo: there is no GTP device by the name of '%s'%s", local_str, VTY_NEWLINE); return CMD_WARNING; } break; } OSMO_ASSERT(gtp_dev); if (upf_gtpu_echo_req_tx(gtp_dev, &osa_remote, g_upf->gtp.next_echo_seq_nr++)) { vty_out(vty, "%% Error: Failed to transmit Echo Request (see DGTP logging)%s", VTY_NEWLINE); return CMD_WARNING; } vty_out(vty, "%s -> %s tx Echo Request; for responses, see DGTP logging level INFO%s", gtp_dev->name, osmo_sockaddr_to_str_c(OTC_SELECT, &osa_remote), VTY_NEWLINE); return CMD_SUCCESS; } #define IP46_STR "IPv4 address\nIPv6 address\n" #define GTP_ECHO_TX_STR \ "GTP1-U Echo probing\n" \ "Send a GTP1-U Echo Request to a remote peer\n" \ "Send to remote peer's GTP address\n" IP46_STR DEFUN(gtp_echo_tx, gtp_echo_tx_cmd, "gtp1u-echo send to " VTY_IPV46_CMD, GTP_ECHO_TX_STR) { return _gtp_echo_tx(vty, 0, argc, argv); } DEFUN(gtp_echo_tx_local_ip, gtp_echo_tx_local_ip_cmd, "gtp1u-echo send to " VTY_IPV46_CMD " local-ip " VTY_IPV46_CMD, GTP_ECHO_TX_STR "Send from local GTP device, chosen by IP address\n" IP46_STR) { return _gtp_echo_tx(vty, 1, argc, argv); } DEFUN(gtp_echo_tx_local_dev, gtp_echo_tx_local_dev_cmd, "gtp1u-echo send to " VTY_IPV46_CMD " local-dev DEV_NAME", GTP_ECHO_TX_STR "Send from local GTP device, chosen by name as configured in 'dev create' or 'dev use'.\n" "A GTP device name as it appears in the cfg\n") { return _gtp_echo_tx(vty, 2, argc, argv); } void upf_vty_init() { OSMO_ASSERT(g_upf != NULL); install_element_ve(&show_pdr_cmd); install_element_ve(&show_gtp_cmd); install_element_ve(&show_session_cmd); install_element_ve(&show_netinst_cmd); install_element_ve(&show_nft_rule_append_cmd); install_element_ve(>p_echo_tx_cmd); install_element_ve(>p_echo_tx_local_ip_cmd); install_element_ve(>p_echo_tx_local_dev_cmd); install_node(&cfg_pfcp_node, config_write_pfcp); install_element(CONFIG_NODE, &cfg_pfcp_cmd); install_element(PFCP_NODE, &cfg_pfcp_local_addr_cmd); install_node(&cfg_tunend_node, config_write_tunend); install_element(CONFIG_NODE, &cfg_tunend_cmd); install_element(CONFIG_NODE, &cfg_gtp_cmd); install_element(TUNEND_NODE, &cfg_tunend_mockup_cmd); install_element(TUNEND_NODE, &cfg_tunend_no_mockup_cmd); install_element(TUNEND_NODE, &cfg_tunend_dev_create_cmd); install_element(TUNEND_NODE, &cfg_tunend_dev_use_cmd); install_element(TUNEND_NODE, &cfg_tunend_dev_del_cmd); install_node(&cfg_tunmap_node, config_write_tunmap); install_element(CONFIG_NODE, &cfg_tunmap_cmd); install_element(CONFIG_NODE, &cfg_nft_cmd); install_element(TUNMAP_NODE, &cfg_tunmap_mockup_cmd); install_element(TUNMAP_NODE, &cfg_tunmap_no_mockup_cmd); install_element(TUNMAP_NODE, &cfg_tunmap_table_name_cmd); install_element(TUNMAP_NODE, &cfg_tunmap_nft_rule_append_cmd); install_element(TUNMAP_NODE, &cfg_tunmap_no_nft_rule_append_cmd); install_element(TUNMAP_NODE, &show_nft_rule_append_cmd); install_element(TUNMAP_NODE, &show_nft_rule_tunmap_example_cmd); install_node(&cfg_netinst_node, config_write_netinst); install_element(CONFIG_NODE, &cfg_netinst_cmd); install_element(NETINST_NODE, &cfg_netinst_clear_cmd); install_element(NETINST_NODE, &cfg_netinst_add_cmd); install_element(NETINST_NODE, &show_netinst_cmd); }