blob: 3f012d2b3c37ed52f6424433acf451c30b2642aa [file] [log] [blame]
Harald Weltedda21ed2017-08-12 15:07:02 +02001/*
2 * (C) 2017 by Harald Welte <laforge@gnumonks.org>
3 * All Rights Reserved
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20#include <string.h>
21#include <stdint.h>
22#include <inttypes.h>
23#include <netinet/in.h>
24#include <arpa/inet.h>
25
26#include <osmocom/core/talloc.h>
27#include <osmocom/core/utils.h>
28#include <osmocom/core/rate_ctr.h>
Vadim Yanitskiyd7030d22019-05-13 22:10:24 +070029#include <osmocom/gsm/apn.h>
Vadim Yanitskiyfb625042019-05-19 02:00:31 +070030#include <osmocom/gsm/gsm48_ie.h>
Harald Weltedda21ed2017-08-12 15:07:02 +020031#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
32
33#include <osmocom/vty/command.h>
34#include <osmocom/vty/vty.h>
35#include <osmocom/vty/misc.h>
36
37#include "../gtp/gtp.h"
38#include "../gtp/pdp.h"
39
40#include "ggsn.h"
41
42#define PREFIX_STR "Prefix (Network/Netmask)\n"
43#define IFCONFIG_STR "GGSN-based interface configuration\n"
44#define GGSN_STR "Gateway GPRS Support NODE (GGSN)\n"
45
46LLIST_HEAD(g_ggsn_list);
47
48enum ggsn_vty_node {
49 GGSN_NODE = _LAST_OSMOVTY_NODE + 1,
50 APN_NODE,
51};
52
53struct ggsn_ctx *ggsn_find(const char *name)
54{
55 struct ggsn_ctx *ggsn;
56
57 llist_for_each_entry(ggsn, &g_ggsn_list, list) {
58 if (!strcmp(ggsn->cfg.name, name))
59 return ggsn;
60 }
61 return NULL;
62}
63
64struct ggsn_ctx *ggsn_find_or_create(void *ctx, const char *name)
65{
66 struct ggsn_ctx *ggsn;
67
68 ggsn = ggsn_find(name);
69 if (ggsn)
70 return ggsn;
71
72 ggsn = talloc_zero(ctx, struct ggsn_ctx);
73 if (!ggsn)
74 return NULL;
75
76 ggsn->cfg.name = talloc_strdup(ggsn, name);
77 ggsn->cfg.state_dir = talloc_strdup(ggsn, "/tmp");
78 ggsn->cfg.shutdown = true;
79 INIT_LLIST_HEAD(&ggsn->apn_list);
80
81 llist_add_tail(&ggsn->list, &g_ggsn_list);
82 return ggsn;
83}
84
85struct apn_ctx *ggsn_find_apn(struct ggsn_ctx *ggsn, const char *name)
86{
87 struct apn_ctx *apn;
88
89 llist_for_each_entry(apn, &ggsn->apn_list, list) {
90 if (!strcmp(apn->cfg.name, name))
91 return apn;
92 }
93 return NULL;
94}
95
96struct apn_ctx *ggsn_find_or_create_apn(struct ggsn_ctx *ggsn, const char *name)
97{
98 struct apn_ctx *apn = ggsn_find_apn(ggsn, name);
99 if (apn)
100 return apn;
101
102 apn = talloc_zero(ggsn, struct apn_ctx);
103 if (!apn)
104 return NULL;
105 apn->ggsn = ggsn;
106 apn->cfg.name = talloc_strdup(apn, name);
107 apn->cfg.shutdown = true;
Harald Welte93fed3b2017-09-24 11:43:17 +0800108 apn->cfg.tx_gpdu_seq = true;
Harald Weltedda21ed2017-08-12 15:07:02 +0200109 INIT_LLIST_HEAD(&apn->cfg.name_list);
110
111 llist_add_tail(&apn->list, &ggsn->apn_list);
112 return apn;
113}
114
115/* GGSN Node */
116
117static struct cmd_node ggsn_node = {
118 GGSN_NODE,
119 "%s(config-ggsn)# ",
120 1,
121};
122
123DEFUN(cfg_ggsn, cfg_ggsn_cmd,
124 "ggsn NAME",
125 "Configure the Gateway GPRS Support Node\n" "GGSN Name (has only local significance)\n")
126{
127 struct ggsn_ctx *ggsn;
128
129 ggsn = ggsn_find_or_create(tall_ggsn_ctx, argv[0]);
130 if (!ggsn)
131 return CMD_WARNING;
132
133 vty->node = GGSN_NODE;
134 vty->index = ggsn;
135 vty->index_sub = &ggsn->cfg.description;
136
137 return CMD_SUCCESS;
138}
139
140DEFUN(cfg_no_ggsn, cfg_no_ggsn_cmd,
141 "no ggsn NAME",
142 NO_STR "Remove the named Gateway GPRS Support Node\n"
143 "GGSN Name (has only local significance)\n")
144{
145 struct ggsn_ctx *ggsn;
146
147 ggsn = ggsn_find(argv[0]);
148 if (!ggsn) {
149 vty_out(vty, "%% No such GGSN '%s'%s", argv[0], VTY_NEWLINE);
150 return CMD_WARNING;
151 }
152
153 if (!ggsn->cfg.shutdown) {
154 vty_out(vty, "%% GGSN %s is still active, please shutdown first%s",
155 ggsn->cfg.name, VTY_NEWLINE);
156 return CMD_WARNING;
157 }
158
159 if (!llist_empty(&ggsn->apn_list)) {
160 vty_out(vty, "%% GGSN %s still has APNs configured, please remove first%s",
161 ggsn->cfg.name, VTY_NEWLINE);
162 return CMD_WARNING;
163 }
164
165 llist_del(&ggsn->list);
166 talloc_free(ggsn);
167
168 return CMD_SUCCESS;
169}
170
Harald Welte98146772017-09-05 17:41:20 +0200171DEFUN(cfg_ggsn_bind_ip, cfg_ggsn_bind_ip_cmd,
172 "gtp bind-ip A.B.C.D",
Harald Weltedda21ed2017-08-12 15:07:02 +0200173 "GTP Parameters\n"
174 "Set the IP address for the local GTP bind\n"
175 "IPv4 Address\n")
176{
177 struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
178 size_t t;
179
180 ippool_aton(&ggsn->cfg.listen_addr, &t, argv[0], 0);
181
182 return CMD_SUCCESS;
183}
184
Harald Welte98146772017-09-05 17:41:20 +0200185DEFUN(cfg_ggsn_gtpc_ip, cfg_ggsn_gtpc_ip_cmd,
186 "gtp control-ip A.B.C.D",
187 "GTP Parameters\n"
188 "Set the IP address states as local IP in GTP-C messages\n"
189 "IPv4 Address\n")
190{
191 struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
192 size_t t;
193
194 ippool_aton(&ggsn->cfg.gtpc_addr, &t, argv[0], 0);
195
196 return CMD_SUCCESS;
197}
198
199DEFUN(cfg_ggsn_gtpu_ip, cfg_ggsn_gtpu_ip_cmd,
200 "gtp user-ip A.B.C.D",
201 "GTP Parameters\n"
202 "Set the IP address states as local IP in GTP-U messages\n"
203 "IPv4 Address\n")
204{
205 struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
206 size_t t;
207
208 ippool_aton(&ggsn->cfg.gtpu_addr, &t, argv[0], 0);
209
210 return CMD_SUCCESS;
211}
212
Harald Weltedda21ed2017-08-12 15:07:02 +0200213DEFUN(cfg_ggsn_state_dir, cfg_ggsn_state_dir_cmd,
214 "gtp state-dir PATH",
215 "GTP Parameters\n"
216 "Set the directory for the GTP State file\n"
217 "Local Directory\n")
218{
219 struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
220
221 osmo_talloc_replace_string(ggsn, &ggsn->cfg.state_dir, argv[0]);
222
223 return CMD_SUCCESS;
224}
225
226DEFUN(cfg_ggsn_apn, cfg_ggsn_apn_cmd,
227 "apn NAME", "APN Configuration\n" "APN Name\n")
228{
229 struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
230 struct apn_ctx *apn;
231
232 apn = ggsn_find_or_create_apn(ggsn, argv[0]);
233 if (!apn)
234 return CMD_WARNING;
235
236 vty->node = APN_NODE;
237 vty->index = apn;
238 vty->index_sub = &ggsn->cfg.description;
239
240 return CMD_SUCCESS;
241}
242
243DEFUN(cfg_ggsn_no_apn, cfg_ggsn_no_apn_cmd,
244 "no apn NAME",
245 NO_STR "Remove APN Configuration\n" "APN Name\n")
246{
247 struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
248 struct apn_ctx *apn;
249
250 apn = ggsn_find_apn(ggsn, argv[0]);
251 if (!apn) {
252 vty_out(vty, "%% No such APN '%s'%s", argv[0], VTY_NEWLINE);
253 return CMD_WARNING;
254 }
255
256 if (!apn->cfg.shutdown) {
257 vty_out(vty, "%% APN %s still active, please shutdown first%s",
258 apn->cfg.name, VTY_NEWLINE);
259 return CMD_WARNING;
260 }
261
262 llist_del(&apn->list);
263 talloc_free(apn);
264
265 return CMD_SUCCESS;
266}
267
268DEFUN(cfg_ggsn_default_apn, cfg_ggsn_default_apn_cmd,
269 "default-apn NAME",
270 "Set a default-APN to be used if no other APN matches\n"
271 "APN Name\n")
272{
273 struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
274 struct apn_ctx *apn;
275
276 apn = ggsn_find_apn(ggsn, argv[0]);
277 if (!apn) {
278 vty_out(vty, "%% No APN of name '%s' found%s", argv[0], VTY_NEWLINE);
279 return CMD_WARNING;
280 }
281
282 ggsn->cfg.default_apn = apn;
283 return CMD_SUCCESS;
284}
285
286DEFUN(cfg_ggsn_no_default_apn, cfg_ggsn_no_default_apn_cmd,
287 "no default-apn",
288 NO_STR "Remove default-APN to be used if no other APN matches\n")
289{
290 struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
291 ggsn->cfg.default_apn = NULL;
292 return CMD_SUCCESS;
293}
294
295DEFUN(cfg_ggsn_shutdown, cfg_ggsn_shutdown_cmd,
296 "shutdown ggsn",
297 "Put the GGSN in administrative shut-down\n" GGSN_STR)
298{
299 struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
300
301 if (!ggsn->cfg.shutdown) {
302 if (ggsn_stop(ggsn)) {
303 vty_out(vty, "%% Failed to shutdown GGSN%s", VTY_NEWLINE);
304 return CMD_WARNING;
305 }
306 ggsn->cfg.shutdown = true;
307 }
308
309 return CMD_SUCCESS;
310}
311
312DEFUN(cfg_ggsn_no_shutdown, cfg_ggsn_no_shutdown_cmd,
313 "no shutdown ggsn",
314 NO_STR GGSN_STR "Remove the GGSN from administrative shut-down\n")
315{
316 struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
317
318 if (ggsn->cfg.shutdown) {
319 if (ggsn_start(ggsn) < 0) {
320 vty_out(vty, "%% Failed to start GGSN, check log for details%s", VTY_NEWLINE);
321 return CMD_WARNING;
322 }
323 ggsn->cfg.shutdown = false;
324 }
325
326 return CMD_SUCCESS;
327}
328
329/* APN Node */
330
331static struct cmd_node apn_node = {
332 APN_NODE,
333 "%s(config-ggsn-apn)# ",
334 1,
335};
336
337static const struct value_string pdp_type_names[] = {
338 { APN_TYPE_IPv4, "v4" },
339 { APN_TYPE_IPv6, "v6" },
340 { APN_TYPE_IPv4v6, "v4v6" },
341 { 0, NULL }
342};
343
344static const struct value_string apn_gtpu_mode_names[] = {
345 { APN_GTPU_MODE_TUN, "tun" },
346 { APN_GTPU_MODE_KERNEL_GTP, "kernel-gtp" },
347 { 0, NULL }
348};
349
350
351#define V4V6V46_STRING "IPv4(-only) PDP Type\n" \
352 "IPv6(-only) PDP Type\n" \
353 "IPv4v6 (dual-stack) PDP Type\n"
354
355DEFUN(cfg_apn_type_support, cfg_apn_type_support_cmd,
356 "type-support (v4|v6|v4v6)",
357 "Enable support for PDP Type\n"
358 V4V6V46_STRING)
359{
360 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
361 uint32_t type = get_string_value(pdp_type_names, argv[0]);
362
363 apn->cfg.apn_type_mask |= type;
364 return CMD_SUCCESS;
365}
366
367DEFUN(cfg_apn_no_type_support, cfg_apn_no_type_support_cmd,
368 "no type-support (v4|v6|v4v6)",
369 NO_STR "Disable support for PDP Type\n"
370 V4V6V46_STRING)
371{
372 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
373 uint32_t type = get_string_value(pdp_type_names, argv[0]);
374
375 apn->cfg.apn_type_mask &= ~type;
376 return CMD_SUCCESS;
377}
378
379DEFUN(cfg_apn_gtpu_mode, cfg_apn_gtpu_mode_cmd,
380 "gtpu-mode (tun|kernel-gtp)",
381 "Set the Mode for this APN (tun or Linux Kernel GTP)\n"
382 "GTP-U in userspace using TUN device\n"
383 "GTP-U in kernel using Linux Kernel GTP\n")
384{
385 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
386
387 apn->cfg.gtpu_mode = get_string_value(apn_gtpu_mode_names, argv[0]);
388 return CMD_SUCCESS;
389}
390
391DEFUN(cfg_apn_tun_dev_name, cfg_apn_tun_dev_name_cmd,
392 "tun-device NAME",
393 "Configure tun device name\n"
394 "TUN device name")
395{
396 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
397 osmo_talloc_replace_string(apn, &apn->tun.cfg.dev_name, argv[0]);
398 return CMD_SUCCESS;
399}
400
401DEFUN(cfg_apn_ipup_script, cfg_apn_ipup_script_cmd,
402 "ipup-script PATH",
403 "Configure name/path of ip-up script\n"
404 "File/Path name of ip-up script\n")
405{
406 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
407 osmo_talloc_replace_string(apn, &apn->tun.cfg.ipup_script, argv[0]);
408 return CMD_SUCCESS;
409}
410
411DEFUN(cfg_apn_no_ipup_script, cfg_apn_no_ipup_script_cmd,
412 "no ipup-script",
413 NO_STR "Disable ip-up script\n")
414{
415 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
416 talloc_free(apn->tun.cfg.ipup_script);
417 apn->tun.cfg.ipup_script = NULL;
418 return CMD_SUCCESS;
419}
420
421DEFUN(cfg_apn_ipdown_script, cfg_apn_ipdown_script_cmd,
422 "ipdown-script PATH",
423 "Configure name/path of ip-down script\n"
424 "File/Path name of ip-down script\n")
425{
426 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
427 osmo_talloc_replace_string(apn, &apn->tun.cfg.ipdown_script, argv[0]);
428 return CMD_SUCCESS;
429}
430
431/* convert prefix from "A.B.C.D/M" notation to in46_prefix */
432static void str2prefix(struct in46_prefix *pfx, const char *in)
433{
434 size_t t;
435
436 ippool_aton(&pfx->addr, &t, in, 0);
437 pfx->prefixlen = t;
438}
439
440DEFUN(cfg_apn_no_ipdown_script, cfg_apn_no_ipdown_script_cmd,
441 "no ipdown-script",
442 NO_STR "Disable ip-down script\n")
443{
444 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
445 talloc_free(apn->tun.cfg.ipdown_script);
446 apn->tun.cfg.ipdown_script = NULL;
447 return CMD_SUCCESS;
448}
449
450DEFUN(cfg_apn_ip_prefix, cfg_apn_ip_prefix_cmd,
451 "ip prefix (static|dynamic) A.B.C.D/M",
452 IP_STR PREFIX_STR "IPv4 Adress/Prefix-Length\n")
453{
454 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
455 struct in46_prefix *pfx;
456
457 /* first update our parsed prefix */
458 if (!strcmp(argv[0], "static"))
459 pfx = &apn->v4.cfg.static_prefix;
460 else
461 pfx = &apn->v4.cfg.dynamic_prefix;
462 str2prefix(pfx, argv[1]);
463
464 return CMD_SUCCESS;
465}
466
467DEFUN(cfg_apn_ip_ifconfig, cfg_apn_ip_ifconfig_cmd,
468 "ip ifconfig A.B.C.D/M",
469 IP_STR IFCONFIG_STR "IPv4 Adress/Prefix-Length\n")
470{
471 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
472 str2prefix(&apn->v4.cfg.ifconfig_prefix, argv[0]);
473 return CMD_SUCCESS;
474}
475
476DEFUN(cfg_apn_no_ip_ifconfig, cfg_apn_no_ip_ifconfig_cmd,
477 "no ip ifconfig",
478 NO_STR IP_STR IFCONFIG_STR)
479{
480 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
481 memset(&apn->v4.cfg.ifconfig_prefix, 0, sizeof(apn->v4.cfg.ifconfig_prefix));
482 return CMD_SUCCESS;
483}
484
485DEFUN(cfg_apn_ipv6_prefix, cfg_apn_ipv6_prefix_cmd,
486 "ipv6 prefix (static|dynamic) X:X::X:X/M",
487 IP6_STR PREFIX_STR "IPv6 Address/Prefix-Length\n")
488{
489 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
490 struct in46_prefix *pfx;
491
492 if (!strcmp(argv[0], "static"))
493 pfx = &apn->v6.cfg.static_prefix;
494 else
495 pfx = &apn->v6.cfg.dynamic_prefix;
496 str2prefix(pfx, argv[1]);
497 return CMD_SUCCESS;
498}
499
500DEFUN(cfg_apn_ipv6_ifconfig, cfg_apn_ipv6_ifconfig_cmd,
501 "ipv6 ifconfig X:X::X:X/M",
502 IP6_STR IFCONFIG_STR "IPv6 Adress/Prefix-Length\n")
503{
504 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
505 str2prefix(&apn->v6.cfg.ifconfig_prefix, argv[0]);
506 return CMD_SUCCESS;
507}
508
509DEFUN(cfg_apn_no_ipv6_ifconfig, cfg_apn_no_ipv6_ifconfig_cmd,
510 "no ipv6 ifconfig",
511 NO_STR IP6_STR IFCONFIG_STR)
512{
513 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
514 memset(&apn->v6.cfg.ifconfig_prefix, 0, sizeof(apn->v6.cfg.ifconfig_prefix));
515 return CMD_SUCCESS;
516}
517
Pau Espin Pedrol37c45e32017-12-14 14:09:13 +0100518DEFUN(cfg_apn_ipv6_linklocal, cfg_apn_ipv6_linklocal_cmd,
519 "ipv6 link-local X:X::X:X/M",
520 IP6_STR IFCONFIG_STR "IPv6 Link-local Adress/Prefix-Length\n")
521{
522 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
523 str2prefix(&apn->v6.cfg.ll_prefix, argv[0]);
524 return CMD_SUCCESS;
525}
526
527DEFUN(cfg_apn_no_ipv6_linklocal, cfg_apn_no_ipv6_linklocal_cmd,
528 "no ipv6 link-local",
529 NO_STR IP6_STR IFCONFIG_STR)
530{
531 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
532 memset(&apn->v6.cfg.ll_prefix, 0, sizeof(apn->v6.cfg.ll_prefix));
533 return CMD_SUCCESS;
534}
535
Harald Weltedda21ed2017-08-12 15:07:02 +0200536#define DNS_STRINGS "Configure DNS Server\n" "primary/secondary DNS\n" "IP address of DNS Sever\n"
537
538DEFUN(cfg_apn_ip_dns, cfg_apn_ip_dns_cmd,
539 "ip dns <0-1> A.B.C.D",
540 IP_STR DNS_STRINGS)
541{
542 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
543 int idx = atoi(argv[0]);
544 size_t dummy;
545
546 ippool_aton(&apn->v4.cfg.dns[idx], &dummy, argv[1], 0);
547
548 return CMD_SUCCESS;
549}
550
551DEFUN(cfg_apn_ipv6_dns, cfg_apn_ipv6_dns_cmd,
552 "ipv6 dns <0-1> X:X::X:X",
553 IP6_STR DNS_STRINGS)
554{
555 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
556 int idx = atoi(argv[0]);
557 size_t dummy;
558
559 ippool_aton(&apn->v6.cfg.dns[idx], &dummy, argv[1], 0);
560
561 return CMD_SUCCESS;
562}
563
564DEFUN(cfg_apn_no_dns, cfg_apn_no_dns_cmd,
565 "no (ip|ipv6) dns <0-1>",
566 NO_STR IP_STR IP6_STR "Disable DNS Server\n" "primary/secondary DNS\n")
567{
568 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
569 struct in46_addr *a;
570 int idx = atoi(argv[1]);
571
572 if (!strcmp(argv[0], "ip"))
573 a = &apn->v4.cfg.dns[idx];
574 else
575 a = &apn->v6.cfg.dns[idx];
576
577 memset(a, 0, sizeof(*a));
578
579 return CMD_SUCCESS;
580}
581
Harald Welte93fed3b2017-09-24 11:43:17 +0800582DEFUN(cfg_apn_gpdu_seq, cfg_apn_gpdu_seq_cmd,
583 "g-pdu tx-sequence-numbers",
584 "G-PDU Configuration\n" "Enable transmission of G-PDU sequence numbers\n")
585{
586 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
587 apn->cfg.tx_gpdu_seq = true;
588 return CMD_SUCCESS;
589}
590
591DEFUN(cfg_apn_no_gpdu_seq, cfg_apn_no_gpdu_seq_cmd,
592 "no g-pdu tx-sequence-numbers",
593 NO_STR "G-PDU Configuration\n" "Disable transmission of G-PDU sequence numbers\n")
594{
595 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
596 apn->cfg.tx_gpdu_seq = false;
597 return CMD_SUCCESS;
598}
599
Harald Weltedda21ed2017-08-12 15:07:02 +0200600DEFUN(cfg_apn_shutdown, cfg_apn_shutdown_cmd,
601 "shutdown",
602 "Put the APN in administrative shut-down\n")
603{
604 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
605
606 if (!apn->cfg.shutdown) {
Pau Espin Pedrol72ab4bc2019-05-29 19:08:26 +0200607 if (apn_stop(apn)) {
Harald Weltedda21ed2017-08-12 15:07:02 +0200608 vty_out(vty, "%% Failed to Stop APN%s", VTY_NEWLINE);
609 return CMD_WARNING;
610 }
611 apn->cfg.shutdown = true;
612 }
613
614 return CMD_SUCCESS;
615}
616
617DEFUN(cfg_apn_no_shutdown, cfg_apn_no_shutdown_cmd,
618 "no shutdown",
619 NO_STR "Remove the APN from administrative shut-down\n")
620{
621 struct apn_ctx *apn = (struct apn_ctx *) vty->index;
622
623 if (apn->cfg.shutdown) {
624 if (apn_start(apn) < 0) {
625 vty_out(vty, "%% Failed to start APN, check log for details%s", VTY_NEWLINE);
626 return CMD_WARNING;
627 }
628 apn->cfg.shutdown = false;
629 }
630
631 return CMD_SUCCESS;
632}
633
634
635static void vty_dump_prefix(struct vty *vty, const char *pre, const struct in46_prefix *pfx)
636{
637 vty_out(vty, "%s %s%s", pre, in46p_ntoa(pfx), VTY_NEWLINE);
638}
639
640static void config_write_apn(struct vty *vty, struct apn_ctx *apn)
641{
642 unsigned int i;
643
644 vty_out(vty, " apn %s%s", apn->cfg.name, VTY_NEWLINE);
645 if (apn->cfg.description)
646 vty_out(vty, " description %s%s", apn->cfg.description, VTY_NEWLINE);
647 vty_out(vty, " gtpu-mode %s%s", get_value_string(apn_gtpu_mode_names, apn->cfg.gtpu_mode),
648 VTY_NEWLINE);
649 if (apn->tun.cfg.dev_name)
650 vty_out(vty, " tun-device %s%s", apn->tun.cfg.dev_name, VTY_NEWLINE);
651 if (apn->tun.cfg.ipup_script)
652 vty_out(vty, " ipup-script %s%s", apn->tun.cfg.ipup_script, VTY_NEWLINE);
653 if (apn->tun.cfg.ipdown_script)
654 vty_out(vty, " ipdown-script %s%s", apn->tun.cfg.ipdown_script, VTY_NEWLINE);
655
656 for (i = 0; i < 32; i++) {
657 if (!(apn->cfg.apn_type_mask & (1 << i)))
658 continue;
659 vty_out(vty, " type-support %s%s", get_value_string(pdp_type_names, (1 << i)),
660 VTY_NEWLINE);
661 }
662
Harald Welte93fed3b2017-09-24 11:43:17 +0800663 if (!apn->cfg.tx_gpdu_seq)
664 vty_out(vty, " no g-pdu tx-sequence-numbers%s", VTY_NEWLINE);
665
Harald Weltedda21ed2017-08-12 15:07:02 +0200666 /* IPv4 prefixes + DNS */
667 if (apn->v4.cfg.static_prefix.addr.len)
668 vty_dump_prefix(vty, " ip prefix static", &apn->v4.cfg.static_prefix);
669 if (apn->v4.cfg.dynamic_prefix.addr.len)
670 vty_dump_prefix(vty, " ip prefix dynamic", &apn->v4.cfg.dynamic_prefix);
671 for (i = 0; i < ARRAY_SIZE(apn->v4.cfg.dns); i++) {
672 if (!apn->v4.cfg.dns[i].len)
673 continue;
674 vty_out(vty, " ip dns %u %s%s", i, in46a_ntoa(&apn->v4.cfg.dns[i]), VTY_NEWLINE);
675 }
676 if (apn->v4.cfg.ifconfig_prefix.addr.len)
Harald Welteff438172017-09-24 22:48:46 +0800677 vty_dump_prefix(vty, " ip ifconfig", &apn->v4.cfg.ifconfig_prefix);
Harald Weltedda21ed2017-08-12 15:07:02 +0200678
679 /* IPv6 prefixes + DNS */
680 if (apn->v6.cfg.static_prefix.addr.len)
681 vty_dump_prefix(vty, " ipv6 prefix static", &apn->v6.cfg.static_prefix);
682 if (apn->v6.cfg.dynamic_prefix.addr.len)
683 vty_dump_prefix(vty, " ipv6 prefix dynamic", &apn->v6.cfg.dynamic_prefix);
684 for (i = 0; i < ARRAY_SIZE(apn->v6.cfg.dns); i++) {
685 if (!apn->v6.cfg.dns[i].len)
686 continue;
Harald Welte3ca419a2017-09-24 22:47:51 +0800687 vty_out(vty, " ipv6 dns %u %s%s", i, in46a_ntoa(&apn->v6.cfg.dns[i]), VTY_NEWLINE);
Harald Weltedda21ed2017-08-12 15:07:02 +0200688 }
689 if (apn->v6.cfg.ifconfig_prefix.addr.len)
Harald Welteff438172017-09-24 22:48:46 +0800690 vty_dump_prefix(vty, " ipv6 ifconfig", &apn->v6.cfg.ifconfig_prefix);
Pau Espin Pedrole5a082d2017-12-15 15:55:04 +0100691 if (apn->v6.cfg.ll_prefix.addr.len)
692 vty_dump_prefix(vty, " ipv6 link-local", &apn->v6.cfg.ll_prefix);
Harald Weltedda21ed2017-08-12 15:07:02 +0200693
694 /* must be last */
695 vty_out(vty, " %sshutdown%s", apn->cfg.shutdown ? "" : "no ", VTY_NEWLINE);
696}
697
698static int config_write_ggsn(struct vty *vty)
699{
700 struct ggsn_ctx *ggsn;
701
702 llist_for_each_entry(ggsn, &g_ggsn_list, list) {
703 struct apn_ctx *apn;
704 vty_out(vty, "ggsn %s%s", ggsn->cfg.name, VTY_NEWLINE);
705 if (ggsn->cfg.description)
706 vty_out(vty, " description %s%s", ggsn->cfg.description, VTY_NEWLINE);
707 vty_out(vty, " gtp state-dir %s%s", ggsn->cfg.state_dir, VTY_NEWLINE);
Harald Welte98146772017-09-05 17:41:20 +0200708 vty_out(vty, " gtp bind-ip %s%s", in46a_ntoa(&ggsn->cfg.listen_addr), VTY_NEWLINE);
709 if (ggsn->cfg.gtpc_addr.v4.s_addr)
710 vty_out(vty, " gtp control-ip %s%s", in46a_ntoa(&ggsn->cfg.gtpc_addr), VTY_NEWLINE);
711 if (ggsn->cfg.gtpu_addr.v4.s_addr)
712 vty_out(vty, " gtp user-ip %s%s", in46a_ntoa(&ggsn->cfg.gtpu_addr), VTY_NEWLINE);
Harald Weltedda21ed2017-08-12 15:07:02 +0200713 llist_for_each_entry(apn, &ggsn->apn_list, list)
714 config_write_apn(vty, apn);
715 if (ggsn->cfg.default_apn)
716 vty_out(vty, " default-apn %s%s", ggsn->cfg.default_apn->cfg.name, VTY_NEWLINE);
717 /* must be last */
718 vty_out(vty, " %sshutdown ggsn%s", ggsn->cfg.shutdown ? "" : "no ", VTY_NEWLINE);
719 }
720
721 return 0;
722}
723
724static const char *print_gsnaddr(const struct ul16_t *in)
725{
726 struct in46_addr in46;
727
728 in46.len = in->l;
729 OSMO_ASSERT(in->l <= sizeof(in46.v6));
730 memcpy(&in46.v6, in->v, in->l);
731
732 return in46a_ntoa(&in46);
733}
734
735static void show_one_pdp(struct vty *vty, struct pdp_t *pdp)
736{
737 struct in46_addr eua46;
Vadim Yanitskiyd7030d22019-05-13 22:10:24 +0700738 char name_buf[256];
739 char *apn_name;
Vadim Yanitskiyfb625042019-05-19 02:00:31 +0700740 int rc;
741
742 /* Attempt to decode MSISDN */
743 rc = gsm48_decode_bcd_number2(name_buf, sizeof(name_buf),
744 pdp->msisdn.v, pdp->msisdn.l, 0);
Harald Weltedda21ed2017-08-12 15:07:02 +0200745
746 vty_out(vty, "IMSI: %s, NSAPI: %u, MSISDN: %s%s", imsi_gtp2str(&pdp->imsi), pdp->nsapi,
Vadim Yanitskiyfb625042019-05-19 02:00:31 +0700747 rc ? "(NONE)" : name_buf, VTY_NEWLINE);
Harald Weltedda21ed2017-08-12 15:07:02 +0200748
749 vty_out(vty, " Control: %s:%08x ", print_gsnaddr(&pdp->gsnlc), pdp->teic_own);
750 vty_out(vty, "<-> %s:%08x%s", print_gsnaddr(&pdp->gsnrc), pdp->teic_gn, VTY_NEWLINE);
751
752 vty_out(vty, " Data: %s:%08x ", print_gsnaddr(&pdp->gsnlu), pdp->teid_own);
753 vty_out(vty, "<-> %s:%08x%s", print_gsnaddr(&pdp->gsnru), pdp->teid_gn, VTY_NEWLINE);
754
Vadim Yanitskiyd7030d22019-05-13 22:10:24 +0700755 apn_name = osmo_apn_to_str(name_buf, pdp->apn_req.v, pdp->apn_req.l);
756 vty_out(vty, " APN requested: %s%s", apn_name ? name_buf : "(NONE)", VTY_NEWLINE);
757 apn_name = osmo_apn_to_str(name_buf, pdp->apn_use.v, pdp->apn_use.l);
758 vty_out(vty, " APN in use: %s%s", apn_name ? name_buf : "(NONE)", VTY_NEWLINE);
759
Harald Weltedda21ed2017-08-12 15:07:02 +0200760 in46a_from_eua(&pdp->eua, &eua46);
761 vty_out(vty, " End-User Address: %s%s", in46a_ntoa(&eua46), VTY_NEWLINE);
Harald Welte93fed3b2017-09-24 11:43:17 +0800762 vty_out(vty, " Transmit GTP Sequence Number for G-PDU: %s%s",
763 pdp->tx_gpdu_seq ? "Yes" : "No", VTY_NEWLINE);
Harald Weltedda21ed2017-08-12 15:07:02 +0200764}
765
766DEFUN(show_pdpctx_imsi, show_pdpctx_imsi_cmd,
767 "show pdp-context imsi IMSI [<0-15>]",
768 SHOW_STR "Display information on PDP Context\n"
769 "PDP contexts for given IMSI\n"
770 "PDP context for given NSAPI\n")
771{
772 uint64_t imsi = strtoull(argv[0], NULL, 10);
773 unsigned int nsapi;
774 struct pdp_t *pdp;
775 int num_found = 0;
776
777 if (argc > 1) {
778 nsapi = atoi(argv[1]);
779 if (pdp_getimsi(&pdp, imsi, nsapi)) {
780 show_one_pdp(vty, pdp);
781 num_found++;
782 }
783 } else {
784 for (nsapi = 0; nsapi < PDP_MAXNSAPI; nsapi++) {
785 if (pdp_getimsi(&pdp, imsi, nsapi))
786 continue;
787 show_one_pdp(vty, pdp);
788 num_found++;
789 }
790 }
791 if (num_found == 0) {
792 vty_out(vty, "%% No such PDP context found%s", VTY_NEWLINE);
793 return CMD_WARNING;
794 } else
795 return CMD_SUCCESS;
796}
797
Vadim Yanitskiyca276e02019-05-13 13:06:51 +0700798DEFUN(show_pdpctx_ip, show_pdpctx_ip_cmd,
799 "show pdp-context ggsn NAME ipv4 A.B.C.D",
800 SHOW_STR "Display information on PDP Context\n"
801 GGSN_STR "GGSN Name\n" "IPv4 address type\n" "IP address\n")
802{
803 struct ggsn_ctx *ggsn;
804 struct apn_ctx *apn;
805 unsigned int i;
806
807 ggsn = ggsn_find(argv[0]);
808 if (!ggsn) {
809 vty_out(vty, "%% No such GGSN '%s'%s", argv[0], VTY_NEWLINE);
810 return CMD_WARNING;
811 }
812
813 /* Iterate over all APNs of a given GGSN */
814 llist_for_each_entry(apn, &ggsn->apn_list, list) {
815 struct ippool_t *pool = apn->v4.pool;
816
817 /* In some rare cases, if GGSN fails to init TUN/TAP interfaces
818 * (e.g. due to insufficient permissions), it will continue to
819 * work in such broken state, and pool would be NULL. */
820 if (!pool)
821 continue;
822
823 /* Iterate over all IPv4 pool members */
824 for (i = 0; i < pool->listsize; i++) {
825 struct ippoolm_t *member = &pool->member[i];
826 if (member->inuse == 0)
827 continue;
828 if (strcmp(argv[1], in46a_ntoa(&member->addr)) == 0) {
829 show_one_pdp(vty, member->peer);
830 return CMD_SUCCESS;
831 }
832 }
833 }
834
835 vty_out(vty, "%% No PDP context found for IP '%s'%s", argv[1], VTY_NEWLINE);
836 return CMD_WARNING;
837}
838
Harald Weltedda21ed2017-08-12 15:07:02 +0200839/* show all (active) PDP contexts within a pool */
840static void ippool_show_pdp_contexts(struct vty *vty, struct ippool_t *pool)
841{
842 unsigned int i;
843
844 if (!pool)
845 return;
846
847 for (i = 0; i < pool->listsize; i++) {
848 struct ippoolm_t *member = &pool->member[i];
849 if (member->inuse == 0)
850 continue;
851 show_one_pdp(vty, member->peer);
852 }
853}
854
855/* show all (active) PDP contexts within an APN */
856static void apn_show_pdp_contexts(struct vty *vty, struct apn_ctx *apn)
857{
858 ippool_show_pdp_contexts(vty, apn->v4.pool);
859 ippool_show_pdp_contexts(vty, apn->v6.pool);
860}
861
862DEFUN(show_pdpctx, show_pdpctx_cmd,
Vadim Yanitskiy977b3392019-05-13 15:32:21 +0700863 "show pdp-context ggsn NAME",
Harald Weltedda21ed2017-08-12 15:07:02 +0200864 SHOW_STR "Show PDP Context Information\n"
Vadim Yanitskiy977b3392019-05-13 15:32:21 +0700865 GGSN_STR "GGSN Name\n")
Harald Weltedda21ed2017-08-12 15:07:02 +0200866{
867 struct ggsn_ctx *ggsn;
868 struct apn_ctx *apn;
869
870 ggsn = ggsn_find(argv[0]);
871 if (!ggsn) {
872 vty_out(vty, "%% No such GGSN '%s'%s", argv[0], VTY_NEWLINE);
873 return CMD_WARNING;
874 }
Vadim Yanitskiy977b3392019-05-13 15:32:21 +0700875
876 llist_for_each_entry(apn, &ggsn->apn_list, list)
Harald Weltedda21ed2017-08-12 15:07:02 +0200877 apn_show_pdp_contexts(vty, apn);
Harald Weltedda21ed2017-08-12 15:07:02 +0200878
879 return CMD_SUCCESS;
880}
881
Vadim Yanitskiy977b3392019-05-13 15:32:21 +0700882DEFUN(show_pdpctx_apn, show_pdpctx_apn_cmd,
883 "show pdp-context ggsn NAME apn APN",
884 SHOW_STR "Show PDP Context Information\n"
885 GGSN_STR "GGSN Name\n" "Filter by APN\n" "APN name\n")
886{
887 struct ggsn_ctx *ggsn;
888 struct apn_ctx *apn;
889
890 ggsn = ggsn_find(argv[0]);
891 if (!ggsn) {
892 vty_out(vty, "%% No such GGSN '%s'%s", argv[0], VTY_NEWLINE);
893 return CMD_WARNING;
894 }
895
896 apn = ggsn_find_apn(ggsn, argv[1]);
897 if (!apn) {
898 vty_out(vty, "%% No such APN '%s'%s", argv[1], VTY_NEWLINE);
899 return CMD_WARNING;
900 }
901
902 apn_show_pdp_contexts(vty, apn);
903 return CMD_SUCCESS;
904}
905
906/* Backwards compatibility: the VTY parser is (mis)interpreting
907 * "[apn APN]" as two separate elements: "[apn" and "APN]",
908 * but the first part somehow turns into command "ap". */
909ALIAS_DEPRECATED(show_pdpctx_apn, show_deprecated_pdpctx_apn_cmd,
910 "show pdp-context ggsn NAME ap APN",
911 SHOW_STR "Show PDP Context Information\n"
912 GGSN_STR "GGSN Name\n" "Filter by APN\n" "APN name\n");
913
Harald Weltedda21ed2017-08-12 15:07:02 +0200914static void show_apn(struct vty *vty, struct apn_ctx *apn)
915{
916 vty_out(vty, " APN: %s%s", apn->cfg.name, VTY_NEWLINE);
917 /* FIXME */
918}
919
920static void show_one_ggsn(struct vty *vty, struct ggsn_ctx *ggsn)
921{
922 struct apn_ctx *apn;
923 vty_out(vty, "GGSN %s: Bound to %s%s", ggsn->cfg.name, in46a_ntoa(&ggsn->cfg.listen_addr),
924 VTY_NEWLINE);
925 /* FIXME */
926
927 llist_for_each_entry(apn, &ggsn->apn_list, list)
928 show_apn(vty, apn);
929}
930
931DEFUN(show_ggsn, show_ggsn_cmd,
932 "show ggsn [NAME]",
933 SHOW_STR "Display information on the GGSN\n")
934{
935 struct ggsn_ctx *ggsn;
936
937 if (argc == 0) {
938 llist_for_each_entry(ggsn, &g_ggsn_list, list)
939 show_one_ggsn(vty, ggsn);
940 } else {
941 ggsn = ggsn_find(argv[0]);
942 if (!ggsn)
943 return CMD_WARNING;
944 show_one_ggsn(vty, ggsn);
945 }
946
947 return CMD_SUCCESS;
948}
949
950int ggsn_vty_init(void)
951{
952 install_element_ve(&show_pdpctx_cmd);
Vadim Yanitskiy977b3392019-05-13 15:32:21 +0700953 install_element_ve(&show_pdpctx_apn_cmd);
954 install_element_ve(&show_deprecated_pdpctx_apn_cmd);
Harald Weltedda21ed2017-08-12 15:07:02 +0200955 install_element_ve(&show_pdpctx_imsi_cmd);
Vadim Yanitskiyca276e02019-05-13 13:06:51 +0700956 install_element_ve(&show_pdpctx_ip_cmd);
Harald Weltedda21ed2017-08-12 15:07:02 +0200957 install_element_ve(&show_ggsn_cmd);
958
959 install_element(CONFIG_NODE, &cfg_ggsn_cmd);
960 install_element(CONFIG_NODE, &cfg_no_ggsn_cmd);
Pau Espin Pedrol840ce8a2017-11-16 17:01:44 +0100961
Harald Weltedda21ed2017-08-12 15:07:02 +0200962 install_node(&ggsn_node, config_write_ggsn);
Harald Weltedda21ed2017-08-12 15:07:02 +0200963 install_element(GGSN_NODE, &cfg_description_cmd);
964 install_element(GGSN_NODE, &cfg_no_description_cmd);
965 install_element(GGSN_NODE, &cfg_ggsn_shutdown_cmd);
966 install_element(GGSN_NODE, &cfg_ggsn_no_shutdown_cmd);
Harald Welte98146772017-09-05 17:41:20 +0200967 install_element(GGSN_NODE, &cfg_ggsn_bind_ip_cmd);
968 install_element(GGSN_NODE, &cfg_ggsn_gtpc_ip_cmd);
969 install_element(GGSN_NODE, &cfg_ggsn_gtpu_ip_cmd);
Harald Weltedda21ed2017-08-12 15:07:02 +0200970 install_element(GGSN_NODE, &cfg_ggsn_state_dir_cmd);
971 install_element(GGSN_NODE, &cfg_ggsn_apn_cmd);
972 install_element(GGSN_NODE, &cfg_ggsn_no_apn_cmd);
973 install_element(GGSN_NODE, &cfg_ggsn_default_apn_cmd);
974 install_element(GGSN_NODE, &cfg_ggsn_no_default_apn_cmd);
975
976 install_node(&apn_node, NULL);
Harald Weltedda21ed2017-08-12 15:07:02 +0200977 install_element(APN_NODE, &cfg_description_cmd);
978 install_element(APN_NODE, &cfg_no_description_cmd);
979 install_element(APN_NODE, &cfg_apn_shutdown_cmd);
980 install_element(APN_NODE, &cfg_apn_no_shutdown_cmd);
981 install_element(APN_NODE, &cfg_apn_gtpu_mode_cmd);
982 install_element(APN_NODE, &cfg_apn_type_support_cmd);
983 install_element(APN_NODE, &cfg_apn_no_type_support_cmd);
984 install_element(APN_NODE, &cfg_apn_tun_dev_name_cmd);
985 install_element(APN_NODE, &cfg_apn_ipup_script_cmd);
986 install_element(APN_NODE, &cfg_apn_no_ipup_script_cmd);
987 install_element(APN_NODE, &cfg_apn_ipdown_script_cmd);
988 install_element(APN_NODE, &cfg_apn_no_ipdown_script_cmd);
989 install_element(APN_NODE, &cfg_apn_ip_prefix_cmd);
990 install_element(APN_NODE, &cfg_apn_ipv6_prefix_cmd);
991 install_element(APN_NODE, &cfg_apn_ip_dns_cmd);
992 install_element(APN_NODE, &cfg_apn_ipv6_dns_cmd);
993 install_element(APN_NODE, &cfg_apn_no_dns_cmd);
994 install_element(APN_NODE, &cfg_apn_ip_ifconfig_cmd);
995 install_element(APN_NODE, &cfg_apn_no_ip_ifconfig_cmd);
996 install_element(APN_NODE, &cfg_apn_ipv6_ifconfig_cmd);
997 install_element(APN_NODE, &cfg_apn_no_ipv6_ifconfig_cmd);
Pau Espin Pedrol37c45e32017-12-14 14:09:13 +0100998 install_element(APN_NODE, &cfg_apn_ipv6_linklocal_cmd);
999 install_element(APN_NODE, &cfg_apn_no_ipv6_linklocal_cmd);
Harald Welte93fed3b2017-09-24 11:43:17 +08001000 install_element(APN_NODE, &cfg_apn_gpdu_seq_cmd);
1001 install_element(APN_NODE, &cfg_apn_no_gpdu_seq_cmd);
Harald Weltedda21ed2017-08-12 15:07:02 +02001002
1003 return 0;
1004}
1005
1006static int ggsn_vty_is_config_node(struct vty *vty, int node)
1007{
1008 switch (node) {
1009 case GGSN_NODE:
1010 case APN_NODE:
1011 return 1;
1012 default:
1013 return 0;
1014 }
1015}
1016
1017static int ggsn_vty_go_parent(struct vty *vty)
1018{
1019 switch (vty->node) {
1020 case GGSN_NODE:
1021 vty->node = CONFIG_NODE;
1022 vty->index = NULL;
1023 vty->index_sub = NULL;
1024 break;
1025 case APN_NODE:
1026 vty->node = GGSN_NODE;
1027 {
1028 struct apn_ctx *apn = vty->index;
1029 vty->index = apn->ggsn;
1030 vty->index_sub = &apn->ggsn->cfg.description;
1031 }
1032 break;
Vadim Yanitskiy906c2092018-05-09 23:11:27 +07001033 default:
1034 vty->node = CONFIG_NODE;
1035 vty->index = NULL;
1036 vty->index_sub = NULL;
Harald Weltedda21ed2017-08-12 15:07:02 +02001037 }
1038
1039 return vty->node;
1040}
1041
1042static const char ggsn_copyright[] =
1043 "Copyright (C) 2011-2017 Harald Welte <laforge@gnumonks.org>\r\n"
1044 "Copyright (C) 2012-2017 Holger Hans Peter Freyther <holger@moiji-mobile.com>\r\n"
1045 "Copyright (C) 2012-2017 sysmocom - s.f.m.c. GmbH\r\n"
1046 "Copyright (C) 2002-2005 Mondru AB\r\n"
1047 "License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl-2.0.html>\r\n"
1048 "This is free software: you are free to change and redistribute it.\r\n"
1049 "There is NO WARRANTY, to the extent permitted by law.\r\n";
1050
1051struct vty_app_info g_vty_info = {
Harald Welte632e8432017-09-05 18:12:14 +02001052 .name = "OsmoGGSN",
Harald Weltedda21ed2017-08-12 15:07:02 +02001053 .version = PACKAGE_VERSION,
1054 .copyright = ggsn_copyright,
1055 .go_parent_cb = ggsn_vty_go_parent,
1056 .is_config_node = ggsn_vty_is_config_node,
1057};