| /* |
| * (C) 2017 by Harald Welte <laforge@gnumonks.org> |
| * All Rights Reserved |
| * |
| * 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 <string.h> |
| #include <stdint.h> |
| #include <inttypes.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| |
| #include <osmocom/core/talloc.h> |
| #include <osmocom/core/utils.h> |
| #include <osmocom/core/rate_ctr.h> |
| #include <osmocom/gsm/protocol/gsm_04_08_gprs.h> |
| |
| #include <osmocom/vty/command.h> |
| #include <osmocom/vty/vty.h> |
| #include <osmocom/vty/misc.h> |
| |
| #include "../gtp/gtp.h" |
| #include "../gtp/pdp.h" |
| |
| #include "ggsn.h" |
| |
| #define PREFIX_STR "Prefix (Network/Netmask)\n" |
| #define IFCONFIG_STR "GGSN-based interface configuration\n" |
| #define GGSN_STR "Gateway GPRS Support NODE (GGSN)\n" |
| |
| LLIST_HEAD(g_ggsn_list); |
| |
| enum ggsn_vty_node { |
| GGSN_NODE = _LAST_OSMOVTY_NODE + 1, |
| APN_NODE, |
| }; |
| |
| struct ggsn_ctx *ggsn_find(const char *name) |
| { |
| struct ggsn_ctx *ggsn; |
| |
| llist_for_each_entry(ggsn, &g_ggsn_list, list) { |
| if (!strcmp(ggsn->cfg.name, name)) |
| return ggsn; |
| } |
| return NULL; |
| } |
| |
| struct ggsn_ctx *ggsn_find_or_create(void *ctx, const char *name) |
| { |
| struct ggsn_ctx *ggsn; |
| |
| ggsn = ggsn_find(name); |
| if (ggsn) |
| return ggsn; |
| |
| ggsn = talloc_zero(ctx, struct ggsn_ctx); |
| if (!ggsn) |
| return NULL; |
| |
| ggsn->cfg.name = talloc_strdup(ggsn, name); |
| ggsn->cfg.state_dir = talloc_strdup(ggsn, "/tmp"); |
| ggsn->cfg.shutdown = true; |
| INIT_LLIST_HEAD(&ggsn->apn_list); |
| |
| llist_add_tail(&ggsn->list, &g_ggsn_list); |
| return ggsn; |
| } |
| |
| struct apn_ctx *ggsn_find_apn(struct ggsn_ctx *ggsn, const char *name) |
| { |
| struct apn_ctx *apn; |
| |
| llist_for_each_entry(apn, &ggsn->apn_list, list) { |
| if (!strcmp(apn->cfg.name, name)) |
| return apn; |
| } |
| return NULL; |
| } |
| |
| struct apn_ctx *ggsn_find_or_create_apn(struct ggsn_ctx *ggsn, const char *name) |
| { |
| struct apn_ctx *apn = ggsn_find_apn(ggsn, name); |
| if (apn) |
| return apn; |
| |
| apn = talloc_zero(ggsn, struct apn_ctx); |
| if (!apn) |
| return NULL; |
| apn->ggsn = ggsn; |
| apn->cfg.name = talloc_strdup(apn, name); |
| apn->cfg.shutdown = true; |
| INIT_LLIST_HEAD(&apn->cfg.name_list); |
| |
| llist_add_tail(&apn->list, &ggsn->apn_list); |
| return apn; |
| } |
| |
| /* GGSN Node */ |
| |
| static struct cmd_node ggsn_node = { |
| GGSN_NODE, |
| "%s(config-ggsn)# ", |
| 1, |
| }; |
| |
| DEFUN(cfg_ggsn, cfg_ggsn_cmd, |
| "ggsn NAME", |
| "Configure the Gateway GPRS Support Node\n" "GGSN Name (has only local significance)\n") |
| { |
| struct ggsn_ctx *ggsn; |
| |
| ggsn = ggsn_find_or_create(tall_ggsn_ctx, argv[0]); |
| if (!ggsn) |
| return CMD_WARNING; |
| |
| vty->node = GGSN_NODE; |
| vty->index = ggsn; |
| vty->index_sub = &ggsn->cfg.description; |
| |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_no_ggsn, cfg_no_ggsn_cmd, |
| "no ggsn NAME", |
| NO_STR "Remove the named Gateway GPRS Support Node\n" |
| "GGSN Name (has only local significance)\n") |
| { |
| struct ggsn_ctx *ggsn; |
| |
| ggsn = ggsn_find(argv[0]); |
| if (!ggsn) { |
| vty_out(vty, "%% No such GGSN '%s'%s", argv[0], VTY_NEWLINE); |
| return CMD_WARNING; |
| } |
| |
| if (!ggsn->cfg.shutdown) { |
| vty_out(vty, "%% GGSN %s is still active, please shutdown first%s", |
| ggsn->cfg.name, VTY_NEWLINE); |
| return CMD_WARNING; |
| } |
| |
| if (!llist_empty(&ggsn->apn_list)) { |
| vty_out(vty, "%% GGSN %s still has APNs configured, please remove first%s", |
| ggsn->cfg.name, VTY_NEWLINE); |
| return CMD_WARNING; |
| } |
| |
| llist_del(&ggsn->list); |
| talloc_free(ggsn); |
| |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_ggsn_bind_ip, cfg_ggsn_bind_ip_cmd, |
| "gtp bind-ip A.B.C.D", |
| "GTP Parameters\n" |
| "Set the IP address for the local GTP bind\n" |
| "IPv4 Address\n") |
| { |
| struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index; |
| size_t t; |
| |
| ippool_aton(&ggsn->cfg.listen_addr, &t, argv[0], 0); |
| |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_ggsn_gtpc_ip, cfg_ggsn_gtpc_ip_cmd, |
| "gtp control-ip A.B.C.D", |
| "GTP Parameters\n" |
| "Set the IP address states as local IP in GTP-C messages\n" |
| "IPv4 Address\n") |
| { |
| struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index; |
| size_t t; |
| |
| ippool_aton(&ggsn->cfg.gtpc_addr, &t, argv[0], 0); |
| |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_ggsn_gtpu_ip, cfg_ggsn_gtpu_ip_cmd, |
| "gtp user-ip A.B.C.D", |
| "GTP Parameters\n" |
| "Set the IP address states as local IP in GTP-U messages\n" |
| "IPv4 Address\n") |
| { |
| struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index; |
| size_t t; |
| |
| ippool_aton(&ggsn->cfg.gtpu_addr, &t, argv[0], 0); |
| |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_ggsn_state_dir, cfg_ggsn_state_dir_cmd, |
| "gtp state-dir PATH", |
| "GTP Parameters\n" |
| "Set the directory for the GTP State file\n" |
| "Local Directory\n") |
| { |
| struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index; |
| |
| osmo_talloc_replace_string(ggsn, &ggsn->cfg.state_dir, argv[0]); |
| |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_ggsn_apn, cfg_ggsn_apn_cmd, |
| "apn NAME", "APN Configuration\n" "APN Name\n") |
| { |
| struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index; |
| struct apn_ctx *apn; |
| |
| apn = ggsn_find_or_create_apn(ggsn, argv[0]); |
| if (!apn) |
| return CMD_WARNING; |
| |
| vty->node = APN_NODE; |
| vty->index = apn; |
| vty->index_sub = &ggsn->cfg.description; |
| |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_ggsn_no_apn, cfg_ggsn_no_apn_cmd, |
| "no apn NAME", |
| NO_STR "Remove APN Configuration\n" "APN Name\n") |
| { |
| struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index; |
| struct apn_ctx *apn; |
| |
| apn = ggsn_find_apn(ggsn, argv[0]); |
| if (!apn) { |
| vty_out(vty, "%% No such APN '%s'%s", argv[0], VTY_NEWLINE); |
| return CMD_WARNING; |
| } |
| |
| if (!apn->cfg.shutdown) { |
| vty_out(vty, "%% APN %s still active, please shutdown first%s", |
| apn->cfg.name, VTY_NEWLINE); |
| return CMD_WARNING; |
| } |
| |
| llist_del(&apn->list); |
| talloc_free(apn); |
| |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_ggsn_default_apn, cfg_ggsn_default_apn_cmd, |
| "default-apn NAME", |
| "Set a default-APN to be used if no other APN matches\n" |
| "APN Name\n") |
| { |
| struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index; |
| struct apn_ctx *apn; |
| |
| apn = ggsn_find_apn(ggsn, argv[0]); |
| if (!apn) { |
| vty_out(vty, "%% No APN of name '%s' found%s", argv[0], VTY_NEWLINE); |
| return CMD_WARNING; |
| } |
| |
| ggsn->cfg.default_apn = apn; |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_ggsn_no_default_apn, cfg_ggsn_no_default_apn_cmd, |
| "no default-apn", |
| NO_STR "Remove default-APN to be used if no other APN matches\n") |
| { |
| struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index; |
| ggsn->cfg.default_apn = NULL; |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_ggsn_shutdown, cfg_ggsn_shutdown_cmd, |
| "shutdown ggsn", |
| "Put the GGSN in administrative shut-down\n" GGSN_STR) |
| { |
| struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index; |
| |
| if (!ggsn->cfg.shutdown) { |
| if (ggsn_stop(ggsn)) { |
| vty_out(vty, "%% Failed to shutdown GGSN%s", VTY_NEWLINE); |
| return CMD_WARNING; |
| } |
| ggsn->cfg.shutdown = true; |
| } |
| |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_ggsn_no_shutdown, cfg_ggsn_no_shutdown_cmd, |
| "no shutdown ggsn", |
| NO_STR GGSN_STR "Remove the GGSN from administrative shut-down\n") |
| { |
| struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index; |
| |
| if (ggsn->cfg.shutdown) { |
| if (ggsn_start(ggsn) < 0) { |
| vty_out(vty, "%% Failed to start GGSN, check log for details%s", VTY_NEWLINE); |
| return CMD_WARNING; |
| } |
| ggsn->cfg.shutdown = false; |
| } |
| |
| return CMD_SUCCESS; |
| } |
| |
| /* APN Node */ |
| |
| static struct cmd_node apn_node = { |
| APN_NODE, |
| "%s(config-ggsn-apn)# ", |
| 1, |
| }; |
| |
| static const struct value_string pdp_type_names[] = { |
| { APN_TYPE_IPv4, "v4" }, |
| { APN_TYPE_IPv6, "v6" }, |
| { APN_TYPE_IPv4v6, "v4v6" }, |
| { 0, NULL } |
| }; |
| |
| static const struct value_string apn_gtpu_mode_names[] = { |
| { APN_GTPU_MODE_TUN, "tun" }, |
| { APN_GTPU_MODE_KERNEL_GTP, "kernel-gtp" }, |
| { 0, NULL } |
| }; |
| |
| |
| #define V4V6V46_STRING "IPv4(-only) PDP Type\n" \ |
| "IPv6(-only) PDP Type\n" \ |
| "IPv4v6 (dual-stack) PDP Type\n" |
| |
| DEFUN(cfg_apn_type_support, cfg_apn_type_support_cmd, |
| "type-support (v4|v6|v4v6)", |
| "Enable support for PDP Type\n" |
| V4V6V46_STRING) |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| uint32_t type = get_string_value(pdp_type_names, argv[0]); |
| |
| apn->cfg.apn_type_mask |= type; |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_no_type_support, cfg_apn_no_type_support_cmd, |
| "no type-support (v4|v6|v4v6)", |
| NO_STR "Disable support for PDP Type\n" |
| V4V6V46_STRING) |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| uint32_t type = get_string_value(pdp_type_names, argv[0]); |
| |
| apn->cfg.apn_type_mask &= ~type; |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_gtpu_mode, cfg_apn_gtpu_mode_cmd, |
| "gtpu-mode (tun|kernel-gtp)", |
| "Set the Mode for this APN (tun or Linux Kernel GTP)\n" |
| "GTP-U in userspace using TUN device\n" |
| "GTP-U in kernel using Linux Kernel GTP\n") |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| |
| apn->cfg.gtpu_mode = get_string_value(apn_gtpu_mode_names, argv[0]); |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_tun_dev_name, cfg_apn_tun_dev_name_cmd, |
| "tun-device NAME", |
| "Configure tun device name\n" |
| "TUN device name") |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| osmo_talloc_replace_string(apn, &apn->tun.cfg.dev_name, argv[0]); |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_ipup_script, cfg_apn_ipup_script_cmd, |
| "ipup-script PATH", |
| "Configure name/path of ip-up script\n" |
| "File/Path name of ip-up script\n") |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| osmo_talloc_replace_string(apn, &apn->tun.cfg.ipup_script, argv[0]); |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_no_ipup_script, cfg_apn_no_ipup_script_cmd, |
| "no ipup-script", |
| NO_STR "Disable ip-up script\n") |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| talloc_free(apn->tun.cfg.ipup_script); |
| apn->tun.cfg.ipup_script = NULL; |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_ipdown_script, cfg_apn_ipdown_script_cmd, |
| "ipdown-script PATH", |
| "Configure name/path of ip-down script\n" |
| "File/Path name of ip-down script\n") |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| osmo_talloc_replace_string(apn, &apn->tun.cfg.ipdown_script, argv[0]); |
| return CMD_SUCCESS; |
| } |
| |
| /* convert prefix from "A.B.C.D/M" notation to in46_prefix */ |
| static void str2prefix(struct in46_prefix *pfx, const char *in) |
| { |
| size_t t; |
| |
| ippool_aton(&pfx->addr, &t, in, 0); |
| pfx->prefixlen = t; |
| } |
| |
| DEFUN(cfg_apn_no_ipdown_script, cfg_apn_no_ipdown_script_cmd, |
| "no ipdown-script", |
| NO_STR "Disable ip-down script\n") |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| talloc_free(apn->tun.cfg.ipdown_script); |
| apn->tun.cfg.ipdown_script = NULL; |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_ip_prefix, cfg_apn_ip_prefix_cmd, |
| "ip prefix (static|dynamic) A.B.C.D/M", |
| IP_STR PREFIX_STR "IPv4 Adress/Prefix-Length\n") |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| struct in46_prefix *pfx; |
| |
| /* first update our parsed prefix */ |
| if (!strcmp(argv[0], "static")) |
| pfx = &apn->v4.cfg.static_prefix; |
| else |
| pfx = &apn->v4.cfg.dynamic_prefix; |
| str2prefix(pfx, argv[1]); |
| |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_ip_ifconfig, cfg_apn_ip_ifconfig_cmd, |
| "ip ifconfig A.B.C.D/M", |
| IP_STR IFCONFIG_STR "IPv4 Adress/Prefix-Length\n") |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| str2prefix(&apn->v4.cfg.ifconfig_prefix, argv[0]); |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_no_ip_ifconfig, cfg_apn_no_ip_ifconfig_cmd, |
| "no ip ifconfig", |
| NO_STR IP_STR IFCONFIG_STR) |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| memset(&apn->v4.cfg.ifconfig_prefix, 0, sizeof(apn->v4.cfg.ifconfig_prefix)); |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_ipv6_prefix, cfg_apn_ipv6_prefix_cmd, |
| "ipv6 prefix (static|dynamic) X:X::X:X/M", |
| IP6_STR PREFIX_STR "IPv6 Address/Prefix-Length\n") |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| struct in46_prefix *pfx; |
| |
| if (!strcmp(argv[0], "static")) |
| pfx = &apn->v6.cfg.static_prefix; |
| else |
| pfx = &apn->v6.cfg.dynamic_prefix; |
| str2prefix(pfx, argv[1]); |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_ipv6_ifconfig, cfg_apn_ipv6_ifconfig_cmd, |
| "ipv6 ifconfig X:X::X:X/M", |
| IP6_STR IFCONFIG_STR "IPv6 Adress/Prefix-Length\n") |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| str2prefix(&apn->v6.cfg.ifconfig_prefix, argv[0]); |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_no_ipv6_ifconfig, cfg_apn_no_ipv6_ifconfig_cmd, |
| "no ipv6 ifconfig", |
| NO_STR IP6_STR IFCONFIG_STR) |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| memset(&apn->v6.cfg.ifconfig_prefix, 0, sizeof(apn->v6.cfg.ifconfig_prefix)); |
| return CMD_SUCCESS; |
| } |
| |
| #define DNS_STRINGS "Configure DNS Server\n" "primary/secondary DNS\n" "IP address of DNS Sever\n" |
| |
| DEFUN(cfg_apn_ip_dns, cfg_apn_ip_dns_cmd, |
| "ip dns <0-1> A.B.C.D", |
| IP_STR DNS_STRINGS) |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| int idx = atoi(argv[0]); |
| size_t dummy; |
| |
| ippool_aton(&apn->v4.cfg.dns[idx], &dummy, argv[1], 0); |
| |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_ipv6_dns, cfg_apn_ipv6_dns_cmd, |
| "ipv6 dns <0-1> X:X::X:X", |
| IP6_STR DNS_STRINGS) |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| int idx = atoi(argv[0]); |
| size_t dummy; |
| |
| ippool_aton(&apn->v6.cfg.dns[idx], &dummy, argv[1], 0); |
| |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_no_dns, cfg_apn_no_dns_cmd, |
| "no (ip|ipv6) dns <0-1>", |
| NO_STR IP_STR IP6_STR "Disable DNS Server\n" "primary/secondary DNS\n") |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| struct in46_addr *a; |
| int idx = atoi(argv[1]); |
| |
| if (!strcmp(argv[0], "ip")) |
| a = &apn->v4.cfg.dns[idx]; |
| else |
| a = &apn->v6.cfg.dns[idx]; |
| |
| memset(a, 0, sizeof(*a)); |
| |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_shutdown, cfg_apn_shutdown_cmd, |
| "shutdown", |
| "Put the APN in administrative shut-down\n") |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| |
| if (!apn->cfg.shutdown) { |
| if (apn_stop(apn, false)) { |
| vty_out(vty, "%% Failed to Stop APN%s", VTY_NEWLINE); |
| return CMD_WARNING; |
| } |
| apn->cfg.shutdown = true; |
| } |
| |
| return CMD_SUCCESS; |
| } |
| |
| DEFUN(cfg_apn_no_shutdown, cfg_apn_no_shutdown_cmd, |
| "no shutdown", |
| NO_STR "Remove the APN from administrative shut-down\n") |
| { |
| struct apn_ctx *apn = (struct apn_ctx *) vty->index; |
| |
| if (apn->cfg.shutdown) { |
| if (apn_start(apn) < 0) { |
| vty_out(vty, "%% Failed to start APN, check log for details%s", VTY_NEWLINE); |
| return CMD_WARNING; |
| } |
| apn->cfg.shutdown = false; |
| } |
| |
| return CMD_SUCCESS; |
| } |
| |
| |
| static void vty_dump_prefix(struct vty *vty, const char *pre, const struct in46_prefix *pfx) |
| { |
| vty_out(vty, "%s %s%s", pre, in46p_ntoa(pfx), VTY_NEWLINE); |
| } |
| |
| static void config_write_apn(struct vty *vty, struct apn_ctx *apn) |
| { |
| unsigned int i; |
| |
| vty_out(vty, " apn %s%s", apn->cfg.name, VTY_NEWLINE); |
| if (apn->cfg.description) |
| vty_out(vty, " description %s%s", apn->cfg.description, VTY_NEWLINE); |
| vty_out(vty, " gtpu-mode %s%s", get_value_string(apn_gtpu_mode_names, apn->cfg.gtpu_mode), |
| VTY_NEWLINE); |
| if (apn->tun.cfg.dev_name) |
| vty_out(vty, " tun-device %s%s", apn->tun.cfg.dev_name, VTY_NEWLINE); |
| if (apn->tun.cfg.ipup_script) |
| vty_out(vty, " ipup-script %s%s", apn->tun.cfg.ipup_script, VTY_NEWLINE); |
| if (apn->tun.cfg.ipdown_script) |
| vty_out(vty, " ipdown-script %s%s", apn->tun.cfg.ipdown_script, VTY_NEWLINE); |
| |
| for (i = 0; i < 32; i++) { |
| if (!(apn->cfg.apn_type_mask & (1 << i))) |
| continue; |
| vty_out(vty, " type-support %s%s", get_value_string(pdp_type_names, (1 << i)), |
| VTY_NEWLINE); |
| } |
| |
| /* IPv4 prefixes + DNS */ |
| if (apn->v4.cfg.static_prefix.addr.len) |
| vty_dump_prefix(vty, " ip prefix static", &apn->v4.cfg.static_prefix); |
| if (apn->v4.cfg.dynamic_prefix.addr.len) |
| vty_dump_prefix(vty, " ip prefix dynamic", &apn->v4.cfg.dynamic_prefix); |
| for (i = 0; i < ARRAY_SIZE(apn->v4.cfg.dns); i++) { |
| if (!apn->v4.cfg.dns[i].len) |
| continue; |
| vty_out(vty, " ip dns %u %s%s", i, in46a_ntoa(&apn->v4.cfg.dns[i]), VTY_NEWLINE); |
| } |
| if (apn->v4.cfg.ifconfig_prefix.addr.len) |
| vty_dump_prefix(vty, " ip ifconfig", &apn->v4.cfg.ifconfig_prefix); |
| |
| /* IPv6 prefixes + DNS */ |
| if (apn->v6.cfg.static_prefix.addr.len) |
| vty_dump_prefix(vty, " ipv6 prefix static", &apn->v6.cfg.static_prefix); |
| if (apn->v6.cfg.dynamic_prefix.addr.len) |
| vty_dump_prefix(vty, " ipv6 prefix dynamic", &apn->v6.cfg.dynamic_prefix); |
| for (i = 0; i < ARRAY_SIZE(apn->v6.cfg.dns); i++) { |
| if (!apn->v6.cfg.dns[i].len) |
| continue; |
| vty_out(vty, " ipv6 dns %u %s%s", i, in46a_ntoa(&apn->v6.cfg.dns[i]), VTY_NEWLINE); |
| } |
| if (apn->v6.cfg.ifconfig_prefix.addr.len) |
| vty_dump_prefix(vty, " ipv6 ifconfig", &apn->v6.cfg.ifconfig_prefix); |
| |
| /* must be last */ |
| vty_out(vty, " %sshutdown%s", apn->cfg.shutdown ? "" : "no ", VTY_NEWLINE); |
| } |
| |
| static int config_write_ggsn(struct vty *vty) |
| { |
| struct ggsn_ctx *ggsn; |
| |
| llist_for_each_entry(ggsn, &g_ggsn_list, list) { |
| struct apn_ctx *apn; |
| vty_out(vty, "ggsn %s%s", ggsn->cfg.name, VTY_NEWLINE); |
| if (ggsn->cfg.description) |
| vty_out(vty, " description %s%s", ggsn->cfg.description, VTY_NEWLINE); |
| vty_out(vty, " gtp state-dir %s%s", ggsn->cfg.state_dir, VTY_NEWLINE); |
| vty_out(vty, " gtp bind-ip %s%s", in46a_ntoa(&ggsn->cfg.listen_addr), VTY_NEWLINE); |
| if (ggsn->cfg.gtpc_addr.v4.s_addr) |
| vty_out(vty, " gtp control-ip %s%s", in46a_ntoa(&ggsn->cfg.gtpc_addr), VTY_NEWLINE); |
| if (ggsn->cfg.gtpu_addr.v4.s_addr) |
| vty_out(vty, " gtp user-ip %s%s", in46a_ntoa(&ggsn->cfg.gtpu_addr), VTY_NEWLINE); |
| llist_for_each_entry(apn, &ggsn->apn_list, list) |
| config_write_apn(vty, apn); |
| if (ggsn->cfg.default_apn) |
| vty_out(vty, " default-apn %s%s", ggsn->cfg.default_apn->cfg.name, VTY_NEWLINE); |
| /* must be last */ |
| vty_out(vty, " %sshutdown ggsn%s", ggsn->cfg.shutdown ? "" : "no ", VTY_NEWLINE); |
| } |
| |
| return 0; |
| } |
| |
| static const char *print_gsnaddr(const struct ul16_t *in) |
| { |
| struct in46_addr in46; |
| |
| in46.len = in->l; |
| OSMO_ASSERT(in->l <= sizeof(in46.v6)); |
| memcpy(&in46.v6, in->v, in->l); |
| |
| return in46a_ntoa(&in46); |
| } |
| |
| static void show_one_pdp(struct vty *vty, struct pdp_t *pdp) |
| { |
| struct in46_addr eua46; |
| |
| vty_out(vty, "IMSI: %s, NSAPI: %u, MSISDN: %s%s", imsi_gtp2str(&pdp->imsi), pdp->nsapi, |
| osmo_hexdump_nospc(pdp->msisdn.v, pdp->msisdn.l), VTY_NEWLINE); |
| |
| vty_out(vty, " Control: %s:%08x ", print_gsnaddr(&pdp->gsnlc), pdp->teic_own); |
| vty_out(vty, "<-> %s:%08x%s", print_gsnaddr(&pdp->gsnrc), pdp->teic_gn, VTY_NEWLINE); |
| |
| vty_out(vty, " Data: %s:%08x ", print_gsnaddr(&pdp->gsnlu), pdp->teid_own); |
| vty_out(vty, "<-> %s:%08x%s", print_gsnaddr(&pdp->gsnru), pdp->teid_gn, VTY_NEWLINE); |
| |
| in46a_from_eua(&pdp->eua, &eua46); |
| vty_out(vty, " End-User Address: %s%s", in46a_ntoa(&eua46), VTY_NEWLINE); |
| } |
| |
| DEFUN(show_pdpctx_imsi, show_pdpctx_imsi_cmd, |
| "show pdp-context imsi IMSI [<0-15>]", |
| SHOW_STR "Display information on PDP Context\n" |
| "PDP contexts for given IMSI\n" |
| "PDP context for given NSAPI\n") |
| { |
| uint64_t imsi = strtoull(argv[0], NULL, 10); |
| unsigned int nsapi; |
| struct pdp_t *pdp; |
| int num_found = 0; |
| |
| if (argc > 1) { |
| nsapi = atoi(argv[1]); |
| if (pdp_getimsi(&pdp, imsi, nsapi)) { |
| show_one_pdp(vty, pdp); |
| num_found++; |
| } |
| } else { |
| for (nsapi = 0; nsapi < PDP_MAXNSAPI; nsapi++) { |
| if (pdp_getimsi(&pdp, imsi, nsapi)) |
| continue; |
| show_one_pdp(vty, pdp); |
| num_found++; |
| } |
| } |
| if (num_found == 0) { |
| vty_out(vty, "%% No such PDP context found%s", VTY_NEWLINE); |
| return CMD_WARNING; |
| } else |
| return CMD_SUCCESS; |
| } |
| |
| /* show all (active) PDP contexts within a pool */ |
| static void ippool_show_pdp_contexts(struct vty *vty, struct ippool_t *pool) |
| { |
| unsigned int i; |
| |
| if (!pool) |
| return; |
| |
| for (i = 0; i < pool->listsize; i++) { |
| struct ippoolm_t *member = &pool->member[i]; |
| if (member->inuse == 0) |
| continue; |
| show_one_pdp(vty, member->peer); |
| } |
| } |
| |
| /* show all (active) PDP contexts within an APN */ |
| static void apn_show_pdp_contexts(struct vty *vty, struct apn_ctx *apn) |
| { |
| ippool_show_pdp_contexts(vty, apn->v4.pool); |
| ippool_show_pdp_contexts(vty, apn->v6.pool); |
| } |
| |
| DEFUN(show_pdpctx, show_pdpctx_cmd, |
| "show pdp-context ggsn NAME [apn APN]", |
| SHOW_STR "Show PDP Context Information\n" |
| GGSN_STR "GGSN Name\n") // FIXME |
| { |
| struct ggsn_ctx *ggsn; |
| struct apn_ctx *apn; |
| |
| ggsn = ggsn_find(argv[0]); |
| if (!ggsn) { |
| vty_out(vty, "%% No such GGSN '%s'%s", argv[0], VTY_NEWLINE); |
| return CMD_WARNING; |
| } |
| if (argc < 2) { |
| llist_for_each_entry(apn, &ggsn->apn_list, list) |
| apn_show_pdp_contexts(vty, apn); |
| } else { |
| apn = ggsn_find_apn(ggsn, argv[1]); |
| if (!apn) { |
| vty_out(vty, "%% No such APN '%s'%s", argv[1], VTY_NEWLINE); |
| return CMD_WARNING; |
| } |
| apn_show_pdp_contexts(vty, apn); |
| } |
| |
| return CMD_SUCCESS; |
| } |
| |
| static void show_apn(struct vty *vty, struct apn_ctx *apn) |
| { |
| vty_out(vty, " APN: %s%s", apn->cfg.name, VTY_NEWLINE); |
| /* FIXME */ |
| } |
| |
| static void show_one_ggsn(struct vty *vty, struct ggsn_ctx *ggsn) |
| { |
| struct apn_ctx *apn; |
| vty_out(vty, "GGSN %s: Bound to %s%s", ggsn->cfg.name, in46a_ntoa(&ggsn->cfg.listen_addr), |
| VTY_NEWLINE); |
| /* FIXME */ |
| |
| llist_for_each_entry(apn, &ggsn->apn_list, list) |
| show_apn(vty, apn); |
| } |
| |
| DEFUN(show_ggsn, show_ggsn_cmd, |
| "show ggsn [NAME]", |
| SHOW_STR "Display information on the GGSN\n") |
| { |
| struct ggsn_ctx *ggsn; |
| |
| if (argc == 0) { |
| llist_for_each_entry(ggsn, &g_ggsn_list, list) |
| show_one_ggsn(vty, ggsn); |
| } else { |
| ggsn = ggsn_find(argv[0]); |
| if (!ggsn) |
| return CMD_WARNING; |
| show_one_ggsn(vty, ggsn); |
| } |
| |
| return CMD_SUCCESS; |
| } |
| |
| int ggsn_vty_init(void) |
| { |
| install_element_ve(&show_pdpctx_cmd); |
| install_element_ve(&show_pdpctx_imsi_cmd); |
| install_element_ve(&show_ggsn_cmd); |
| |
| install_element(CONFIG_NODE, &cfg_ggsn_cmd); |
| install_element(CONFIG_NODE, &cfg_no_ggsn_cmd); |
| install_node(&ggsn_node, config_write_ggsn); |
| vty_install_default(GGSN_NODE); |
| install_element(GGSN_NODE, &cfg_description_cmd); |
| install_element(GGSN_NODE, &cfg_no_description_cmd); |
| install_element(GGSN_NODE, &cfg_ggsn_shutdown_cmd); |
| install_element(GGSN_NODE, &cfg_ggsn_no_shutdown_cmd); |
| install_element(GGSN_NODE, &cfg_ggsn_bind_ip_cmd); |
| install_element(GGSN_NODE, &cfg_ggsn_gtpc_ip_cmd); |
| install_element(GGSN_NODE, &cfg_ggsn_gtpu_ip_cmd); |
| install_element(GGSN_NODE, &cfg_ggsn_state_dir_cmd); |
| install_element(GGSN_NODE, &cfg_ggsn_apn_cmd); |
| install_element(GGSN_NODE, &cfg_ggsn_no_apn_cmd); |
| install_element(GGSN_NODE, &cfg_ggsn_default_apn_cmd); |
| install_element(GGSN_NODE, &cfg_ggsn_no_default_apn_cmd); |
| |
| install_node(&apn_node, NULL); |
| vty_install_default(APN_NODE); |
| install_element(APN_NODE, &cfg_description_cmd); |
| install_element(APN_NODE, &cfg_no_description_cmd); |
| install_element(APN_NODE, &cfg_apn_shutdown_cmd); |
| install_element(APN_NODE, &cfg_apn_no_shutdown_cmd); |
| install_element(APN_NODE, &cfg_apn_gtpu_mode_cmd); |
| install_element(APN_NODE, &cfg_apn_type_support_cmd); |
| install_element(APN_NODE, &cfg_apn_no_type_support_cmd); |
| install_element(APN_NODE, &cfg_apn_tun_dev_name_cmd); |
| install_element(APN_NODE, &cfg_apn_ipup_script_cmd); |
| install_element(APN_NODE, &cfg_apn_no_ipup_script_cmd); |
| install_element(APN_NODE, &cfg_apn_ipdown_script_cmd); |
| install_element(APN_NODE, &cfg_apn_no_ipdown_script_cmd); |
| install_element(APN_NODE, &cfg_apn_ip_prefix_cmd); |
| install_element(APN_NODE, &cfg_apn_ipv6_prefix_cmd); |
| install_element(APN_NODE, &cfg_apn_ip_dns_cmd); |
| install_element(APN_NODE, &cfg_apn_ipv6_dns_cmd); |
| install_element(APN_NODE, &cfg_apn_no_dns_cmd); |
| install_element(APN_NODE, &cfg_apn_ip_ifconfig_cmd); |
| install_element(APN_NODE, &cfg_apn_no_ip_ifconfig_cmd); |
| install_element(APN_NODE, &cfg_apn_ipv6_ifconfig_cmd); |
| install_element(APN_NODE, &cfg_apn_no_ipv6_ifconfig_cmd); |
| |
| return 0; |
| } |
| |
| static int ggsn_vty_is_config_node(struct vty *vty, int node) |
| { |
| switch (node) { |
| case GGSN_NODE: |
| case APN_NODE: |
| return 1; |
| default: |
| return 0; |
| } |
| } |
| |
| static int ggsn_vty_go_parent(struct vty *vty) |
| { |
| switch (vty->node) { |
| case GGSN_NODE: |
| vty->node = CONFIG_NODE; |
| vty->index = NULL; |
| vty->index_sub = NULL; |
| break; |
| case APN_NODE: |
| vty->node = GGSN_NODE; |
| { |
| struct apn_ctx *apn = vty->index; |
| vty->index = apn->ggsn; |
| vty->index_sub = &apn->ggsn->cfg.description; |
| } |
| break; |
| } |
| |
| return vty->node; |
| } |
| |
| static const char ggsn_copyright[] = |
| "Copyright (C) 2011-2017 Harald Welte <laforge@gnumonks.org>\r\n" |
| "Copyright (C) 2012-2017 Holger Hans Peter Freyther <holger@moiji-mobile.com>\r\n" |
| "Copyright (C) 2012-2017 sysmocom - s.f.m.c. GmbH\r\n" |
| "Copyright (C) 2002-2005 Mondru AB\r\n" |
| "License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl-2.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"; |
| |
| struct vty_app_info g_vty_info = { |
| .name = "OsmoGGSN", |
| .version = PACKAGE_VERSION, |
| .copyright = ggsn_copyright, |
| .go_parent_cb = ggsn_vty_go_parent, |
| .is_config_node = ggsn_vty_is_config_node, |
| }; |