Review at  https://gerrit.osmocom.org/3848

Introduce a VTY, factually turning OpenGGSN into an Osmocom program

Change-Id: I9613ca3436e77ea132c47f0096df7c5050d7e826
---
M doc/ggsn.8
D examples/ggsn.conf
A examples/osmo-ggsn.cfg
M ggsn/Makefile.am
D ggsn/cmdline.c
D ggsn/cmdline.ggo
D ggsn/cmdline.h
M ggsn/ggsn.c
A ggsn/ggsn.h
A ggsn/ggsn_vty.c
M lib/tun.c
M lib/tun.h
M sgsnemu/sgsnemu.c
13 files changed, 1,673 insertions(+), 2,177 deletions(-)


  git pull ssh://gerrit.osmocom.org:29418/openggsn refs/changes/48/3848/1

diff --git a/doc/ggsn.8 b/doc/ggsn.8
index 62d467a..89dcb61b 100644
--- a/doc/ggsn.8
+++ b/doc/ggsn.8
@@ -31,33 +31,13 @@
 
 .B ggsn
 [
-.BI \-\-fg
+.BI \-\-help
 ] [
-.BI \-\-debug
+.BI \-\-daemonize
 ] [
-.BI \-\-conf " file"
+.BI \-\-config-file " file"
 ] [
-.BI \-\-pidfile " file"
-] [
-.BI \-\-statedir " file" 
-] [ 
-.BI \-\-listen " host" 
-] [
-.BI \-\-net " net" 
-] [
-.BI \-\-ipup " script" 
-] [
-.BI \-\-ipdown " script" 
-] [
-.BI \-\-dynip " net" 
-] [
-.BI \-\-statip " net" 
-] [
-.BI \-\-pcodns1 " host" 
-] [
-.BI \-\-pcodns2 " host" 
-] [
-.BI \-\-timelimit " seconds" 
+.BI \-\-version
 ]
 .SH DESCRIPTION
 .B ggsn
@@ -115,86 +95,17 @@
 Print version and exit.
 
 .TP
-.BI --fg
-Run in foreground (default = off)
+.BI --daemonize
+Run in background as a daemon (default = off)
 
 .TP
-.BI --debug
-Run in debug mode (default = off)
-
-.TP
-.BI --conf " file"
+.BI --config-file " file"
 Read configuration 
 .I file
-(default = /etc/ggsn.conf) where each line corresponds to one command
-line option, but with the leading '--' removed. Command line options
-override the options given in the configuration file.
-
-.TP
-.BI --pidfile " file"
-Filename of process id 
-.I file
-(default = /var/run/ggsn.pid)
-
-.TP
-.BI --statedir " path"
-.I path
-to directory of nonvolatile data (default = /var/lib/ggsn/)
-
-.TP
-.BI --listen " host"
-Local interface IP address to use for the Gn/Gp interface. This option
-must be specified. For security issues it is not possible to use
-INADDR_ANY.
-
-.TP
-.BI --net " net"
-Network address of the Gi interface (default = 192.168.0.0/24). The
-network address is set during initialisation when
-.B ggsn
-establishes a tun device for the Gi interface.
-
-.TP
-.BI --ipup " script"
-Script executed after the Gi tun network interface has been brought
-up.  Executed with the following parameters: <devicename> <ip address>
-
-.TP
-.BI --ipdown " script"
-Script executed after the Gi tun network interface has been taken
-down.  Executed with the following parameters: <devicename> <ip
-address>
-
-.TP
-.BI --dynip " net"
-Dynamic IP address pool. Specifies a pool of dynamic IP addresses. If
-this option is omitted the network address specified by the
-.BI --net
-option is used for dynamic IP address allocation.
-
-.TP
-.BI --pcodns1 " host"
-PCO DNS Server 1 (default = 0.0.0.0). PCO stands for Protocol
-Configuration options, and is part of the GPRS protocols. It is used
-to inform the mobile station about the DNS address to use for host
-name resolution.
-
-.TP
-.BI --pcodns2 " host"
-PCO DNS Server 2 (default = 0.0.0.0). PCO stands for Protocol
-Configuration options, and is part of the GPRS protocols. It is used
-to inform the mobile station about the DNS address to use for host
-name resolution.
-
-.TP
-.BI --timelimit " seconds"
-Exit 
-.B ggsn
-after \fIseconds\fP. Used for debugging.
-
+(default = ./openggsn.cfg)
 
 .SH FILES
-.I /etc/ggsn.conf
+.I ./openggsn.cfg
 .RS
 The configuration file for
 .B ggsn.
@@ -210,7 +121,7 @@
 
 .SH BUGS
 Report all bugs to the OpenGGSN bug tracking list at 
-.I http://sourceforge.net/projects/ggsn/
+.I https://osmocom.org/projects/openggsn
 
 .B ggsn
 has very limited management support. Currently both SNMP as well as
@@ -250,6 +161,7 @@
 .SH COPYRIGHT
 
 Copyright (C) 2002, 2003 by Mondru AB.
+Copyright (C) 2017 Harald Welte
 
 The contents of this file may be used under the terms of the GNU
 General Public License Version 2, provided that the above copyright
@@ -258,3 +170,4 @@
 
 .SH AUTHORS
 Jens Jakobsen <[email protected]>
+Harald Welte <[email protected]>
diff --git a/examples/ggsn.conf b/examples/ggsn.conf
deleted file mode 100644
index 6dcfe44..0000000
--- a/examples/ggsn.conf
+++ /dev/null
@@ -1,92 +0,0 @@
-##############################################################################
-#
-# Sample ggsn configuration file
-#
-##############################################################################
-
-# TAG: fg
-# Include this flag if process is to run in the foreground
-#
-#fg
-
-# TAG: debug
-# Include this flag to include debug information.
-#debug
-
-
-# TAG: conf
-# Configuration file to use. This file is the configuration file, 
-# so changing this parameter in the configuration file does not make
-# sense. Use it on the command line instead.
-
-# TAG: pidfile
-# File to store information about the process id of the program.
-# The program must have write access to this file/directory.
-#pidfile /var/run/ggsn.pid
-
-# TAG: statedir
-# Directory to use for nonvolatile storage. 
-# The program must have write access to this directory.
-#statedir /var/lib/ggsn/
-
-
-# TAG: listen
-# Specifies the local IP address to listen to
-#listen 10.0.0.240
-
-# TAG: net
-# IP network address of external packet data network
-# Used to set up network interface.
-#net 192.168.0.0/24
-
-# TAG: ipup
-# Script executed after network interface has been brought up.
-# Executed with the following parameters: <devicename> <ip address>
-#ipup /etc/ggsn/ip-up
-
-# TAG: ipdown
-# Script executed after network interface has been taken down.
-# Executed with the following parameters: <devicename> <ip address>
-#ipdown /etc/ggsn/ip-down
-
-# TAG: dynip
-# Dynamic IP address pool.
-# Used for allocation of dynamic IP address when address is not given
-# by HLR.
-# If this option is not given then the net option is used as a substitute.
-#dynip 192.168.0.0/24
-
-# TAG: statip
-# Use of this tag is currently UNSUPPORTED
-# Static IP address pool.
-# Used for allocation of static IP address by means of HLR.
-#statip 192.168.1.0/24
-
-# TAG: pcodns1
-# Protocol configuration option domain name system server 1.
-#pcodns1 0.0.0.0
-
-# TAG: pcodns2
-# Protocol configuration option domain name system server 2.
-#pcodns2 0.0.0.0
-
-# TAG: timelimit
-# Exit after timelimit seconds.
-# Setting timelimit to zero will cause the program not to exit.
-#timelimit 0
-
-# TAG: apn
-# Use of this tag is EXPERIMENTAL
-# Access point name to connect to when run in client mode.
-#apn internet
-
-# TAG: qos
-# Use of this tag is EXPERIMENTAL
-# Requested Quality of Service used when run in client mode.
-# 3 bytes corresponding to ????
-#qos 0x0b921f
-
-# TAG: qos
-# Enable GTP datapath through Linux kernel driver gtp.ko (since 4.7).
-#gtp-linux
-
diff --git a/examples/osmo-ggsn.cfg b/examples/osmo-ggsn.cfg
new file mode 100644
index 0000000..763e561
--- /dev/null
+++ b/examples/osmo-ggsn.cfg
@@ -0,0 +1,71 @@
+!
+! OpenGGSN (0.94.1-adac) configuration saved from vty
+!!
+!
+log stderr
+  logging filter all 1
+  logging color 1
+  logging print category 0
+  logging timestamp 0
+  logging level ip info
+  logging level tun info
+  logging level ggsn info
+  logging level sgsn notice
+  logging level icmp6 notice
+  logging level lglobal notice
+  logging level llapd notice
+  logging level linp notice
+  logging level lmux notice
+  logging level lmi notice
+  logging level lmib notice
+  logging level lsms notice
+  logging level lctrl notice
+  logging level lgtp info
+  logging level lstats notice
+  logging level lgsup notice
+  logging level loap notice
+  logging level lss7 notice
+  logging level lsccp notice
+  logging level lsua notice
+  logging level lm3ua notice
+  logging level lmgcp notice
+!
+stats interval 5
+!
+line vty
+ no login
+!
+ggsn ggsn0
+ gtp state-dir /tmp
+ gtp bind-ip 127.0.0.6
+ apn internet
+  gtpu-mode tun
+  tun-device tun4
+  type-support v4
+  ip prefix dynamic 176.16.222.0/24
+  ip dns 0 192.168.100.1
+  ip dns 1 8.8.8.8
+  ip ifconfig 176.16.222.0/24
+  no shutdown
+ apn inet6
+  gtpu-mode tun
+  tun-device tun6
+  type-support v6
+  ipv6 prefix dynamic 2001:780:44:2000:0:0:0:0/56
+  ipv6 dns 0 2001:4860:4860::8888
+  ipv6 ifconfig 2001:780:44:2000:0:0:0:0/56
+  no shutdown
+ apn inet46
+  gtpu-mode tun
+  tun-device tun46
+  type-support v4v6
+  ip prefix dynamic 176.16.46.0/24
+  ip dns 0 192.168.100.1
+  ip dns 1 8.8.8.8
+  ip ifconfig 176.16.46.0/24
+  ipv6 prefix dynamic 2001:780:44:2100:0:0:0:0/56
+  ipv6 dns 0 2001:4860:4860::8888
+  ipv6 ifconfig 2001:780:44:2100:0:0:0:0/56
+  no shutdown
+ default-apn internet
+ no shutdown ggsn
diff --git a/ggsn/Makefile.am b/ggsn/Makefile.am
index c945f0b..fef12d3 100644
--- a/ggsn/Makefile.am
+++ b/ggsn/Makefile.am
@@ -2,17 +2,17 @@
 
 AM_LDFLAGS = @EXEC_LDFLAGS@
 
-AM_CFLAGS = -O2 -D_GNU_SOURCE -fno-builtin -Wall -DSBINDIR='"$(sbindir)"' 
-ggdb $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS)
+AM_CFLAGS = -O2 -D_GNU_SOURCE -fno-builtin -Wall -DSBINDIR='"$(sbindir)"' 
-ggdb $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
+
+ggsn_LDADD = @EXEC_LDADD@ -lgtp -L../gtp ../lib/libmisc.a $(LIBOSMOCORE_LIBS) 
$(LIBOSMOCTRL_LIBS) $(LIBOSMOVTY_LIBS)
 
 if ENABLE_GTP_KERNEL
 AM_CFLAGS += -DGTP_KERNEL
-ggsn_LDADD = @EXEC_LDADD@ -lgtp -lgtpnl -L../gtp ../lib/libmisc.a 
$(LIBOSMOCORE_LIBS) $(LIBOSMOCTRL_LIBS)
-else
-ggsn_LDADD = @EXEC_LDADD@ -lgtp -L../gtp ../lib/libmisc.a $(LIBOSMOCORE_LIBS) 
$(LIBOSMOCTRL_LIBS)
+ggsn_LDADD += -lgtpnl
 endif
 
 ggsn_DEPENDENCIES = ../gtp/libgtp.la ../lib/libmisc.a
-ggsn_SOURCES = ggsn.c cmdline.c cmdline.h gtp-kernel.h icmpv6.c icmpv6.h 
checksum.c checksum.h
+ggsn_SOURCES = ggsn_vty.c ggsn.c ggsn.h gtp-kernel.h icmpv6.c icmpv6.h 
checksum.c checksum.h
 
 if ENABLE_GTP_KERNEL
 ggsn_SOURCES += gtp-kernel.c
diff --git a/ggsn/cmdline.c b/ggsn/cmdline.c
deleted file mode 100644
index 31c0744..0000000
--- a/ggsn/cmdline.c
+++ /dev/null
@@ -1,1162 +0,0 @@
-/*
-  File autogenerated by gengetopt version 2.22.6
-  generated with the following command:
-  gengetopt -i cmdline.ggo --conf-parser 
-
-  The developers of gengetopt consider the fixed text that goes in all
-  gengetopt output files to be in the public domain:
-  we make no copyright claims on it.
-*/
-
-/* If we use autoconf.  */
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#ifndef FIX_UNUSED
-#define FIX_UNUSED(X) (void) (X) /* avoid warnings for unused params */
-#endif
-
-#include <getopt.h>
-
-#include "cmdline.h"
-
-const char *gengetopt_args_info_purpose = "";
-
-const char *gengetopt_args_info_usage = "Usage: " CMDLINE_PARSER_PACKAGE " 
[OPTIONS]...";
-
-const char *gengetopt_args_info_versiontext = "";
-
-const char *gengetopt_args_info_description = "";
-
-const char *gengetopt_args_info_help[] = {
-  "  -h, --help             Print help and exit",
-  "  -V, --version          Print version and exit",
-  "  -f, --fg               Run in foreground  (default=off)",
-  "  -d, --debug            Run in debug mode  (default=off)",
-  "  -c, --conf=STRING      Read configuration file  
(default=`/etc/ggsn.conf')",
-  "      --pidfile=STRING   Filename of process id file\n                      
     (default=`/var/run/ggsn.pid')",
-  "      --statedir=STRING  Directory of nonvolatile data\n                    
       (default=`/var/lib/ggsn/')",
-  "  -l, --listen=STRING    Local interface",
-  "  -n, --net=STRING       Network  (default=`192.168.0.0/24')",
-  "      --ipup=STRING      Script to run after link-up",
-  "      --ipdown=STRING    Script to run after link-down",
-  "      --dynip=STRING     Dynamic IP address pool",
-  "      --statip=STRING    Static IP address pool",
-  "      --pcodns1=STRING   PCO DNS Server 1  (default=`0.0.0.0')",
-  "      --pcodns2=STRING   PCO DNS Server 2  (default=`0.0.0.0')",
-  "      --timelimit=INT    Exit after timelimit seconds  (default=`0')",
-  "  -a, --apn=STRING       Access point name  (default=`internet')",
-  "  -q, --qos=INT          Requested quality of service  
(default=`0x0b921f')",
-  "      --logfile=STRING   Logfile for errors",
-  "      --loglevel=STRING  Global log ldevel  (default=`error')",
-  "  -g, --gtp-linux        GTP linux kernel support  (default=off)",
-    0
-};
-
-typedef enum {ARG_NO
-  , ARG_FLAG
-  , ARG_STRING
-  , ARG_INT
-} cmdline_parser_arg_type;
-
-static
-void clear_given (struct gengetopt_args_info *args_info);
-static
-void clear_args (struct gengetopt_args_info *args_info);
-
-static int
-cmdline_parser_internal (int argc, char **argv, struct gengetopt_args_info 
*args_info,
-                        struct cmdline_parser_params *params, const char 
*additional_error);
-
-struct line_list
-{
-  char * string_arg;
-  struct line_list * next;
-};
-
-static struct line_list *cmd_line_list = 0;
-static struct line_list *cmd_line_list_tmp = 0;
-
-static void
-free_cmd_list(void)
-{
-  /* free the list of a previous call */
-  if (cmd_line_list)
-    {
-      while (cmd_line_list) {
-        cmd_line_list_tmp = cmd_line_list;
-        cmd_line_list = cmd_line_list->next;
-        free (cmd_line_list_tmp->string_arg);
-        free (cmd_line_list_tmp);
-      }
-    }
-}
-
-
-static char *
-gengetopt_strdup (const char *s);
-
-static
-void clear_given (struct gengetopt_args_info *args_info)
-{
-  args_info->help_given = 0 ;
-  args_info->version_given = 0 ;
-  args_info->fg_given = 0 ;
-  args_info->debug_given = 0 ;
-  args_info->conf_given = 0 ;
-  args_info->pidfile_given = 0 ;
-  args_info->statedir_given = 0 ;
-  args_info->listen_given = 0 ;
-  args_info->net_given = 0 ;
-  args_info->ipup_given = 0 ;
-  args_info->ipdown_given = 0 ;
-  args_info->dynip_given = 0 ;
-  args_info->statip_given = 0 ;
-  args_info->pcodns1_given = 0 ;
-  args_info->pcodns2_given = 0 ;
-  args_info->timelimit_given = 0 ;
-  args_info->apn_given = 0 ;
-  args_info->qos_given = 0 ;
-  args_info->logfile_given = 0 ;
-  args_info->loglevel_given = 0 ;
-  args_info->gtp_linux_given = 0 ;
-}
-
-static
-void clear_args (struct gengetopt_args_info *args_info)
-{
-  FIX_UNUSED (args_info);
-  args_info->fg_flag = 0;
-  args_info->debug_flag = 0;
-  args_info->conf_arg = gengetopt_strdup ("/etc/ggsn.conf");
-  args_info->conf_orig = NULL;
-  args_info->pidfile_arg = gengetopt_strdup ("/var/run/ggsn.pid");
-  args_info->pidfile_orig = NULL;
-  args_info->statedir_arg = gengetopt_strdup ("/var/lib/ggsn/");
-  args_info->statedir_orig = NULL;
-  args_info->listen_arg = NULL;
-  args_info->listen_orig = NULL;
-  args_info->net_arg = gengetopt_strdup ("192.168.0.0/24");
-  args_info->net_orig = NULL;
-  args_info->ipup_arg = NULL;
-  args_info->ipup_orig = NULL;
-  args_info->ipdown_arg = NULL;
-  args_info->ipdown_orig = NULL;
-  args_info->dynip_arg = NULL;
-  args_info->dynip_orig = NULL;
-  args_info->statip_arg = NULL;
-  args_info->statip_orig = NULL;
-  args_info->pcodns1_arg = gengetopt_strdup ("0.0.0.0");
-  args_info->pcodns1_orig = NULL;
-  args_info->pcodns2_arg = gengetopt_strdup ("0.0.0.0");
-  args_info->pcodns2_orig = NULL;
-  args_info->timelimit_arg = 0;
-  args_info->timelimit_orig = NULL;
-  args_info->apn_arg = gengetopt_strdup ("internet");
-  args_info->apn_orig = NULL;
-  args_info->qos_arg = 0x0b921f;
-  args_info->qos_orig = NULL;
-  args_info->logfile_arg = NULL;
-  args_info->logfile_orig = NULL;
-  args_info->loglevel_arg = gengetopt_strdup ("error");
-  args_info->loglevel_orig = NULL;
-  args_info->gtp_linux_flag = 0;
-  
-}
-
-static
-void init_args_info(struct gengetopt_args_info *args_info)
-{
-
-
-  args_info->help_help = gengetopt_args_info_help[0] ;
-  args_info->version_help = gengetopt_args_info_help[1] ;
-  args_info->fg_help = gengetopt_args_info_help[2] ;
-  args_info->debug_help = gengetopt_args_info_help[3] ;
-  args_info->conf_help = gengetopt_args_info_help[4] ;
-  args_info->pidfile_help = gengetopt_args_info_help[5] ;
-  args_info->statedir_help = gengetopt_args_info_help[6] ;
-  args_info->listen_help = gengetopt_args_info_help[7] ;
-  args_info->net_help = gengetopt_args_info_help[8] ;
-  args_info->ipup_help = gengetopt_args_info_help[9] ;
-  args_info->ipdown_help = gengetopt_args_info_help[10] ;
-  args_info->dynip_help = gengetopt_args_info_help[11] ;
-  args_info->statip_help = gengetopt_args_info_help[12] ;
-  args_info->pcodns1_help = gengetopt_args_info_help[13] ;
-  args_info->pcodns2_help = gengetopt_args_info_help[14] ;
-  args_info->timelimit_help = gengetopt_args_info_help[15] ;
-  args_info->apn_help = gengetopt_args_info_help[16] ;
-  args_info->qos_help = gengetopt_args_info_help[17] ;
-  args_info->logfile_help = gengetopt_args_info_help[18] ;
-  args_info->loglevel_help = gengetopt_args_info_help[19] ;
-  args_info->gtp_linux_help = gengetopt_args_info_help[20] ;
-  
-}
-
-void
-cmdline_parser_print_version (void)
-{
-  printf ("%s %s\n",
-     (strlen(CMDLINE_PARSER_PACKAGE_NAME) ? CMDLINE_PARSER_PACKAGE_NAME : 
CMDLINE_PARSER_PACKAGE),
-     CMDLINE_PARSER_VERSION);
-
-  if (strlen(gengetopt_args_info_versiontext) > 0)
-    printf("\n%s\n", gengetopt_args_info_versiontext);
-}
-
-static void print_help_common(void) {
-  cmdline_parser_print_version ();
-
-  if (strlen(gengetopt_args_info_purpose) > 0)
-    printf("\n%s\n", gengetopt_args_info_purpose);
-
-  if (strlen(gengetopt_args_info_usage) > 0)
-    printf("\n%s\n", gengetopt_args_info_usage);
-
-  printf("\n");
-
-  if (strlen(gengetopt_args_info_description) > 0)
-    printf("%s\n\n", gengetopt_args_info_description);
-}
-
-void
-cmdline_parser_print_help (void)
-{
-  int i = 0;
-  print_help_common();
-  while (gengetopt_args_info_help[i])
-    printf("%s\n", gengetopt_args_info_help[i++]);
-}
-
-void
-cmdline_parser_init (struct gengetopt_args_info *args_info)
-{
-  clear_given (args_info);
-  clear_args (args_info);
-  init_args_info (args_info);
-}
-
-void
-cmdline_parser_params_init(struct cmdline_parser_params *params)
-{
-  if (params)
-    { 
-      params->override = 0;
-      params->initialize = 1;
-      params->check_required = 1;
-      params->check_ambiguity = 0;
-      params->print_errors = 1;
-    }
-}
-
-struct cmdline_parser_params *
-cmdline_parser_params_create(void)
-{
-  struct cmdline_parser_params *params = 
-    (struct cmdline_parser_params *)malloc(sizeof(struct 
cmdline_parser_params));
-  cmdline_parser_params_init(params);  
-  return params;
-}
-
-static void
-free_string_field (char **s)
-{
-  if (*s)
-    {
-      free (*s);
-      *s = 0;
-    }
-}
-
-
-static void
-cmdline_parser_release (struct gengetopt_args_info *args_info)
-{
-
-  free_string_field (&(args_info->conf_arg));
-  free_string_field (&(args_info->conf_orig));
-  free_string_field (&(args_info->pidfile_arg));
-  free_string_field (&(args_info->pidfile_orig));
-  free_string_field (&(args_info->statedir_arg));
-  free_string_field (&(args_info->statedir_orig));
-  free_string_field (&(args_info->listen_arg));
-  free_string_field (&(args_info->listen_orig));
-  free_string_field (&(args_info->net_arg));
-  free_string_field (&(args_info->net_orig));
-  free_string_field (&(args_info->ipup_arg));
-  free_string_field (&(args_info->ipup_orig));
-  free_string_field (&(args_info->ipdown_arg));
-  free_string_field (&(args_info->ipdown_orig));
-  free_string_field (&(args_info->dynip_arg));
-  free_string_field (&(args_info->dynip_orig));
-  free_string_field (&(args_info->statip_arg));
-  free_string_field (&(args_info->statip_orig));
-  free_string_field (&(args_info->pcodns1_arg));
-  free_string_field (&(args_info->pcodns1_orig));
-  free_string_field (&(args_info->pcodns2_arg));
-  free_string_field (&(args_info->pcodns2_orig));
-  free_string_field (&(args_info->timelimit_orig));
-  free_string_field (&(args_info->apn_arg));
-  free_string_field (&(args_info->apn_orig));
-  free_string_field (&(args_info->qos_orig));
-  free_string_field (&(args_info->logfile_arg));
-  free_string_field (&(args_info->logfile_orig));
-  free_string_field (&(args_info->loglevel_arg));
-  free_string_field (&(args_info->loglevel_orig));
-  
-  
-
-  clear_given (args_info);
-}
-
-
-static void
-write_into_file(FILE *outfile, const char *opt, const char *arg, const char 
*values[])
-{
-  FIX_UNUSED (values);
-  if (arg) {
-    fprintf(outfile, "%s=\"%s\"\n", opt, arg);
-  } else {
-    fprintf(outfile, "%s\n", opt);
-  }
-}
-
-
-int
-cmdline_parser_dump(FILE *outfile, struct gengetopt_args_info *args_info)
-{
-  int i = 0;
-
-  if (!outfile)
-    {
-      fprintf (stderr, "%s: cannot dump options to stream\n", 
CMDLINE_PARSER_PACKAGE);
-      return EXIT_FAILURE;
-    }
-
-  if (args_info->help_given)
-    write_into_file(outfile, "help", 0, 0 );
-  if (args_info->version_given)
-    write_into_file(outfile, "version", 0, 0 );
-  if (args_info->fg_given)
-    write_into_file(outfile, "fg", 0, 0 );
-  if (args_info->debug_given)
-    write_into_file(outfile, "debug", 0, 0 );
-  if (args_info->conf_given)
-    write_into_file(outfile, "conf", args_info->conf_orig, 0);
-  if (args_info->pidfile_given)
-    write_into_file(outfile, "pidfile", args_info->pidfile_orig, 0);
-  if (args_info->statedir_given)
-    write_into_file(outfile, "statedir", args_info->statedir_orig, 0);
-  if (args_info->listen_given)
-    write_into_file(outfile, "listen", args_info->listen_orig, 0);
-  if (args_info->net_given)
-    write_into_file(outfile, "net", args_info->net_orig, 0);
-  if (args_info->ipup_given)
-    write_into_file(outfile, "ipup", args_info->ipup_orig, 0);
-  if (args_info->ipdown_given)
-    write_into_file(outfile, "ipdown", args_info->ipdown_orig, 0);
-  if (args_info->dynip_given)
-    write_into_file(outfile, "dynip", args_info->dynip_orig, 0);
-  if (args_info->statip_given)
-    write_into_file(outfile, "statip", args_info->statip_orig, 0);
-  if (args_info->pcodns1_given)
-    write_into_file(outfile, "pcodns1", args_info->pcodns1_orig, 0);
-  if (args_info->pcodns2_given)
-    write_into_file(outfile, "pcodns2", args_info->pcodns2_orig, 0);
-  if (args_info->timelimit_given)
-    write_into_file(outfile, "timelimit", args_info->timelimit_orig, 0);
-  if (args_info->apn_given)
-    write_into_file(outfile, "apn", args_info->apn_orig, 0);
-  if (args_info->qos_given)
-    write_into_file(outfile, "qos", args_info->qos_orig, 0);
-  if (args_info->logfile_given)
-    write_into_file(outfile, "logfile", args_info->logfile_orig, 0);
-  if (args_info->loglevel_given)
-    write_into_file(outfile, "loglevel", args_info->loglevel_orig, 0);
-  if (args_info->gtp_linux_given)
-    write_into_file(outfile, "gtp-linux", 0, 0 );
-  
-
-  i = EXIT_SUCCESS;
-  return i;
-}
-
-int
-cmdline_parser_file_save(const char *filename, struct gengetopt_args_info 
*args_info)
-{
-  FILE *outfile;
-  int i = 0;
-
-  outfile = fopen(filename, "w");
-
-  if (!outfile)
-    {
-      fprintf (stderr, "%s: cannot open file for writing: %s\n", 
CMDLINE_PARSER_PACKAGE, filename);
-      return EXIT_FAILURE;
-    }
-
-  i = cmdline_parser_dump(outfile, args_info);
-  fclose (outfile);
-
-  return i;
-}
-
-void
-cmdline_parser_free (struct gengetopt_args_info *args_info)
-{
-  cmdline_parser_release (args_info);
-}
-
-/** @brief replacement of strdup, which is not standard */
-char *
-gengetopt_strdup (const char *s)
-{
-  char *result = 0;
-  if (!s)
-    return result;
-
-  result = (char*)malloc(strlen(s) + 1);
-  if (result == (char*)0)
-    return (char*)0;
-  strcpy(result, s);
-  return result;
-}
-
-int
-cmdline_parser (int argc, char **argv, struct gengetopt_args_info *args_info)
-{
-  return cmdline_parser2 (argc, argv, args_info, 0, 1, 1);
-}
-
-int
-cmdline_parser_ext (int argc, char **argv, struct gengetopt_args_info 
*args_info,
-                   struct cmdline_parser_params *params)
-{
-  int result;
-  result = cmdline_parser_internal (argc, argv, args_info, params, 0);
-
-  if (result == EXIT_FAILURE)
-    {
-      cmdline_parser_free (args_info);
-      exit (EXIT_FAILURE);
-    }
-  
-  return result;
-}
-
-int
-cmdline_parser2 (int argc, char **argv, struct gengetopt_args_info *args_info, 
int override, int initialize, int check_required)
-{
-  int result;
-  struct cmdline_parser_params params;
-  
-  params.override = override;
-  params.initialize = initialize;
-  params.check_required = check_required;
-  params.check_ambiguity = 0;
-  params.print_errors = 1;
-
-  result = cmdline_parser_internal (argc, argv, args_info, &params, 0);
-
-  if (result == EXIT_FAILURE)
-    {
-      cmdline_parser_free (args_info);
-      exit (EXIT_FAILURE);
-    }
-  
-  return result;
-}
-
-int
-cmdline_parser_required (struct gengetopt_args_info *args_info, const char 
*prog_name)
-{
-  FIX_UNUSED (args_info);
-  FIX_UNUSED (prog_name);
-  return EXIT_SUCCESS;
-}
-
-
-static char *package_name = 0;
-
-/**
- * @brief updates an option
- * @param field the generic pointer to the field to update
- * @param orig_field the pointer to the orig field
- * @param field_given the pointer to the number of occurrence of this option
- * @param prev_given the pointer to the number of occurrence already seen
- * @param value the argument for this option (if null no arg was specified)
- * @param possible_values the possible values for this option (if specified)
- * @param default_value the default value (in case the option only accepts 
fixed values)
- * @param arg_type the type of this option
- * @param check_ambiguity @see cmdline_parser_params.check_ambiguity
- * @param override @see cmdline_parser_params.override
- * @param no_free whether to free a possible previous value
- * @param multiple_option whether this is a multiple option
- * @param long_opt the corresponding long option
- * @param short_opt the corresponding short option (or '-' if none)
- * @param additional_error possible further error specification
- */
-static
-int update_arg(void *field, char **orig_field,
-               unsigned int *field_given, unsigned int *prev_given, 
-               char *value, const char *possible_values[],
-               const char *default_value,
-               cmdline_parser_arg_type arg_type,
-               int check_ambiguity, int override,
-               int no_free, int multiple_option,
-               const char *long_opt, char short_opt,
-               const char *additional_error)
-{
-  char *stop_char = 0;
-  const char *val = value;
-  int found;
-  char **string_field;
-  FIX_UNUSED (field);
-
-  stop_char = 0;
-  found = 0;
-
-  if (!multiple_option && prev_given && (*prev_given || (check_ambiguity && 
*field_given)))
-    {
-      if (short_opt != '-')
-        fprintf (stderr, "%s: `--%s' (`-%c') option given more than once%s\n", 
-               package_name, long_opt, short_opt,
-               (additional_error ? additional_error : ""));
-      else
-        fprintf (stderr, "%s: `--%s' option given more than once%s\n", 
-               package_name, long_opt,
-               (additional_error ? additional_error : ""));
-      return 1; /* failure */
-    }
-
-  FIX_UNUSED (default_value);
-    
-  if (field_given && *field_given && ! override)
-    return 0;
-  if (prev_given)
-    (*prev_given)++;
-  if (field_given)
-    (*field_given)++;
-  if (possible_values)
-    val = possible_values[found];
-
-  switch(arg_type) {
-  case ARG_FLAG:
-    *((int *)field) = !*((int *)field);
-    break;
-  case ARG_INT:
-    if (val) *((int *)field) = strtol (val, &stop_char, 0);
-    break;
-  case ARG_STRING:
-    if (val) {
-      string_field = (char **)field;
-      if (!no_free && *string_field)
-        free (*string_field); /* free previous string */
-      *string_field = gengetopt_strdup (val);
-    }
-    break;
-  default:
-    break;
-  };
-
-  /* check numeric conversion */
-  switch(arg_type) {
-  case ARG_INT:
-    if (val && !(stop_char && *stop_char == '\0')) {
-      fprintf(stderr, "%s: invalid numeric value: %s\n", package_name, val);
-      return 1; /* failure */
-    }
-    break;
-  default:
-    ;
-  };
-
-  /* store the original value */
-  switch(arg_type) {
-  case ARG_NO:
-  case ARG_FLAG:
-    break;
-  default:
-    if (value && orig_field) {
-      if (no_free) {
-        *orig_field = value;
-      } else {
-        if (*orig_field)
-          free (*orig_field); /* free previous string */
-        *orig_field = gengetopt_strdup (value);
-      }
-    }
-  };
-
-  return 0; /* OK */
-}
-
-
-int
-cmdline_parser_internal (
-  int argc, char **argv, struct gengetopt_args_info *args_info,
-                        struct cmdline_parser_params *params, const char 
*additional_error)
-{
-  int c;       /* Character of the parsed option.  */
-
-  int error_occurred = 0;
-  struct gengetopt_args_info local_args_info;
-  
-  int override;
-  int initialize;
-  int check_required;
-  int check_ambiguity;
-  
-  package_name = argv[0];
-  
-  override = params->override;
-  initialize = params->initialize;
-  check_required = params->check_required;
-  check_ambiguity = params->check_ambiguity;
-
-  if (initialize)
-    cmdline_parser_init (args_info);
-
-  cmdline_parser_init (&local_args_info);
-
-  optarg = 0;
-  optind = 0;
-  opterr = params->print_errors;
-  optopt = '?';
-
-  while (1)
-    {
-      int option_index = 0;
-
-      static struct option long_options[] = {
-        { "help",      0, NULL, 'h' },
-        { "version",   0, NULL, 'V' },
-        { "fg",        0, NULL, 'f' },
-        { "debug",     0, NULL, 'd' },
-        { "conf",      1, NULL, 'c' },
-        { "pidfile",   1, NULL, 0 },
-        { "statedir",  1, NULL, 0 },
-        { "listen",    1, NULL, 'l' },
-        { "net",       1, NULL, 'n' },
-        { "ipup",      1, NULL, 0 },
-        { "ipdown",    1, NULL, 0 },
-        { "dynip",     1, NULL, 0 },
-        { "statip",    1, NULL, 0 },
-        { "pcodns1",   1, NULL, 0 },
-        { "pcodns2",   1, NULL, 0 },
-        { "timelimit", 1, NULL, 0 },
-        { "apn",       1, NULL, 'a' },
-        { "qos",       1, NULL, 'q' },
-        { "logfile",   1, NULL, 0 },
-        { "loglevel",  1, NULL, 0 },
-        { "gtp-linux", 0, NULL, 'g' },
-        { 0,  0, 0, 0 }
-      };
-
-      c = getopt_long (argc, argv, "hVfdc:l:n:a:q:g", long_options, 
&option_index);
-
-      if (c == -1) break;      /* Exit from `while (1)' loop.  */
-
-      switch (c)
-        {
-        case 'h':      /* Print help and exit.  */
-          cmdline_parser_print_help ();
-          cmdline_parser_free (&local_args_info);
-          exit (EXIT_SUCCESS);
-
-        case 'V':      /* Print version and exit.  */
-          cmdline_parser_print_version ();
-          cmdline_parser_free (&local_args_info);
-          exit (EXIT_SUCCESS);
-
-        case 'f':      /* Run in foreground.  */
-        
-        
-          if (update_arg((void *)&(args_info->fg_flag), 0, 
&(args_info->fg_given),
-              &(local_args_info.fg_given), optarg, 0, 0, ARG_FLAG,
-              check_ambiguity, override, 1, 0, "fg", 'f',
-              additional_error))
-            goto failure;
-        
-          break;
-        case 'd':      /* Run in debug mode.  */
-        
-        
-          if (update_arg((void *)&(args_info->debug_flag), 0, 
&(args_info->debug_given),
-              &(local_args_info.debug_given), optarg, 0, 0, ARG_FLAG,
-              check_ambiguity, override, 1, 0, "debug", 'd',
-              additional_error))
-            goto failure;
-        
-          break;
-        case 'c':      /* Read configuration file.  */
-        
-        
-          if (update_arg( (void *)&(args_info->conf_arg), 
-               &(args_info->conf_orig), &(args_info->conf_given),
-              &(local_args_info.conf_given), optarg, 0, "/etc/ggsn.conf", 
ARG_STRING,
-              check_ambiguity, override, 0, 0,
-              "conf", 'c',
-              additional_error))
-            goto failure;
-        
-          break;
-        case 'l':      /* Local interface.  */
-        
-        
-          if (update_arg( (void *)&(args_info->listen_arg), 
-               &(args_info->listen_orig), &(args_info->listen_given),
-              &(local_args_info.listen_given), optarg, 0, 0, ARG_STRING,
-              check_ambiguity, override, 0, 0,
-              "listen", 'l',
-              additional_error))
-            goto failure;
-        
-          break;
-        case 'n':      /* Network.  */
-        
-        
-          if (update_arg( (void *)&(args_info->net_arg), 
-               &(args_info->net_orig), &(args_info->net_given),
-              &(local_args_info.net_given), optarg, 0, "192.168.0.0/24", 
ARG_STRING,
-              check_ambiguity, override, 0, 0,
-              "net", 'n',
-              additional_error))
-            goto failure;
-        
-          break;
-        case 'a':      /* Access point name.  */
-        
-        
-          if (update_arg( (void *)&(args_info->apn_arg), 
-               &(args_info->apn_orig), &(args_info->apn_given),
-              &(local_args_info.apn_given), optarg, 0, "internet", ARG_STRING,
-              check_ambiguity, override, 0, 0,
-              "apn", 'a',
-              additional_error))
-            goto failure;
-        
-          break;
-        case 'q':      /* Requested quality of service.  */
-        
-        
-          if (update_arg( (void *)&(args_info->qos_arg), 
-               &(args_info->qos_orig), &(args_info->qos_given),
-              &(local_args_info.qos_given), optarg, 0, "0x0b921f", ARG_INT,
-              check_ambiguity, override, 0, 0,
-              "qos", 'q',
-              additional_error))
-            goto failure;
-        
-          break;
-        case 'g':      /* GTP linux kernel support.  */
-        
-        
-          if (update_arg((void *)&(args_info->gtp_linux_flag), 0, 
&(args_info->gtp_linux_given),
-              &(local_args_info.gtp_linux_given), optarg, 0, 0, ARG_FLAG,
-              check_ambiguity, override, 1, 0, "gtp-linux", 'g',
-              additional_error))
-            goto failure;
-        
-          break;
-
-        case 0:        /* Long option with no short option */
-          /* Filename of process id file.  */
-          if (strcmp (long_options[option_index].name, "pidfile") == 0)
-          {
-          
-          
-            if (update_arg( (void *)&(args_info->pidfile_arg), 
-                 &(args_info->pidfile_orig), &(args_info->pidfile_given),
-                &(local_args_info.pidfile_given), optarg, 0, 
"/var/run/ggsn.pid", ARG_STRING,
-                check_ambiguity, override, 0, 0,
-                "pidfile", '-',
-                additional_error))
-              goto failure;
-          
-          }
-          /* Directory of nonvolatile data.  */
-          else if (strcmp (long_options[option_index].name, "statedir") == 0)
-          {
-          
-          
-            if (update_arg( (void *)&(args_info->statedir_arg), 
-                 &(args_info->statedir_orig), &(args_info->statedir_given),
-                &(local_args_info.statedir_given), optarg, 0, 
"/var/lib/ggsn/", ARG_STRING,
-                check_ambiguity, override, 0, 0,
-                "statedir", '-',
-                additional_error))
-              goto failure;
-          
-          }
-          /* Script to run after link-up.  */
-          else if (strcmp (long_options[option_index].name, "ipup") == 0)
-          {
-          
-          
-            if (update_arg( (void *)&(args_info->ipup_arg), 
-                 &(args_info->ipup_orig), &(args_info->ipup_given),
-                &(local_args_info.ipup_given), optarg, 0, 0, ARG_STRING,
-                check_ambiguity, override, 0, 0,
-                "ipup", '-',
-                additional_error))
-              goto failure;
-          
-          }
-          /* Script to run after link-down.  */
-          else if (strcmp (long_options[option_index].name, "ipdown") == 0)
-          {
-          
-          
-            if (update_arg( (void *)&(args_info->ipdown_arg), 
-                 &(args_info->ipdown_orig), &(args_info->ipdown_given),
-                &(local_args_info.ipdown_given), optarg, 0, 0, ARG_STRING,
-                check_ambiguity, override, 0, 0,
-                "ipdown", '-',
-                additional_error))
-              goto failure;
-          
-          }
-          /* Dynamic IP address pool.  */
-          else if (strcmp (long_options[option_index].name, "dynip") == 0)
-          {
-          
-          
-            if (update_arg( (void *)&(args_info->dynip_arg), 
-                 &(args_info->dynip_orig), &(args_info->dynip_given),
-                &(local_args_info.dynip_given), optarg, 0, 0, ARG_STRING,
-                check_ambiguity, override, 0, 0,
-                "dynip", '-',
-                additional_error))
-              goto failure;
-          
-          }
-          /* Static IP address pool.  */
-          else if (strcmp (long_options[option_index].name, "statip") == 0)
-          {
-          
-          
-            if (update_arg( (void *)&(args_info->statip_arg), 
-                 &(args_info->statip_orig), &(args_info->statip_given),
-                &(local_args_info.statip_given), optarg, 0, 0, ARG_STRING,
-                check_ambiguity, override, 0, 0,
-                "statip", '-',
-                additional_error))
-              goto failure;
-          
-          }
-          /* PCO DNS Server 1.  */
-          else if (strcmp (long_options[option_index].name, "pcodns1") == 0)
-          {
-          
-          
-            if (update_arg( (void *)&(args_info->pcodns1_arg), 
-                 &(args_info->pcodns1_orig), &(args_info->pcodns1_given),
-                &(local_args_info.pcodns1_given), optarg, 0, "0.0.0.0", 
ARG_STRING,
-                check_ambiguity, override, 0, 0,
-                "pcodns1", '-',
-                additional_error))
-              goto failure;
-          
-          }
-          /* PCO DNS Server 2.  */
-          else if (strcmp (long_options[option_index].name, "pcodns2") == 0)
-          {
-          
-          
-            if (update_arg( (void *)&(args_info->pcodns2_arg), 
-                 &(args_info->pcodns2_orig), &(args_info->pcodns2_given),
-                &(local_args_info.pcodns2_given), optarg, 0, "0.0.0.0", 
ARG_STRING,
-                check_ambiguity, override, 0, 0,
-                "pcodns2", '-',
-                additional_error))
-              goto failure;
-          
-          }
-          /* Exit after timelimit seconds.  */
-          else if (strcmp (long_options[option_index].name, "timelimit") == 0)
-          {
-          
-          
-            if (update_arg( (void *)&(args_info->timelimit_arg), 
-                 &(args_info->timelimit_orig), &(args_info->timelimit_given),
-                &(local_args_info.timelimit_given), optarg, 0, "0", ARG_INT,
-                check_ambiguity, override, 0, 0,
-                "timelimit", '-',
-                additional_error))
-              goto failure;
-          
-          }
-          /* Logfile for errors.  */
-          else if (strcmp (long_options[option_index].name, "logfile") == 0)
-          {
-          
-          
-            if (update_arg( (void *)&(args_info->logfile_arg), 
-                 &(args_info->logfile_orig), &(args_info->logfile_given),
-                &(local_args_info.logfile_given), optarg, 0, 0, ARG_STRING,
-                check_ambiguity, override, 0, 0,
-                "logfile", '-',
-                additional_error))
-              goto failure;
-          
-          }
-          /* Global log ldevel.  */
-          else if (strcmp (long_options[option_index].name, "loglevel") == 0)
-          {
-          
-          
-            if (update_arg( (void *)&(args_info->loglevel_arg), 
-                 &(args_info->loglevel_orig), &(args_info->loglevel_given),
-                &(local_args_info.loglevel_given), optarg, 0, "error", 
ARG_STRING,
-                check_ambiguity, override, 0, 0,
-                "loglevel", '-',
-                additional_error))
-              goto failure;
-          
-          }
-          
-          break;
-        case '?':      /* Invalid option.  */
-          /* `getopt_long' already printed an error message.  */
-          goto failure;
-
-        default:       /* bug: option not considered.  */
-          fprintf (stderr, "%s: option unknown: %c%s\n", 
CMDLINE_PARSER_PACKAGE, c, (additional_error ? additional_error : ""));
-          abort ();
-        } /* switch */
-    } /* while */
-
-
-
-
-  cmdline_parser_release (&local_args_info);
-
-  if ( error_occurred )
-    return (EXIT_FAILURE);
-
-  return 0;
-
-failure:
-  
-  cmdline_parser_release (&local_args_info);
-  return (EXIT_FAILURE);
-}
-
-#ifndef CONFIG_FILE_LINE_SIZE
-#define CONFIG_FILE_LINE_SIZE 2048
-#endif
-#define ADDITIONAL_ERROR " in configuration file "
-
-#define CONFIG_FILE_LINE_BUFFER_SIZE (CONFIG_FILE_LINE_SIZE+3)
-/* 3 is for "--" and "=" */
-
-static int
-_cmdline_parser_configfile (const char *filename, int *my_argc)
-{
-  FILE* file;
-  char my_argv[CONFIG_FILE_LINE_BUFFER_SIZE+1];
-  char linebuf[CONFIG_FILE_LINE_SIZE];
-  int line_num = 0;
-  int result = 0, equal;
-  char *fopt, *farg;
-  char *str_index;
-  size_t len, next_token;
-  char delimiter;
-
-  if ((file = fopen(filename, "r")) == 0)
-    {
-      fprintf (stderr, "%s: Error opening configuration file '%s'\n",
-               CMDLINE_PARSER_PACKAGE, filename);
-      return EXIT_FAILURE;
-    }
-
-  while ((fgets(linebuf, CONFIG_FILE_LINE_SIZE, file)) != 0)
-    {
-      ++line_num;
-      my_argv[0] = '\0';
-      len = strlen(linebuf);
-      if (len > (CONFIG_FILE_LINE_BUFFER_SIZE-1))
-        {
-          fprintf (stderr, "%s:%s:%d: Line too long in configuration file\n",
-                   CMDLINE_PARSER_PACKAGE, filename, line_num);
-          result = EXIT_FAILURE;
-          break;
-        }
-
-      /* find first non-whitespace character in the line */
-      next_token = strspn (linebuf, " \t\r\n");
-      str_index  = linebuf + next_token;
-
-      if ( str_index[0] == '\0' || str_index[0] == '#')
-        continue; /* empty line or comment line is skipped */
-
-      fopt = str_index;
-
-      /* truncate fopt at the end of the first non-valid character */
-      next_token = strcspn (fopt, " \t\r\n=");
-
-      if (fopt[next_token] == '\0') /* the line is over */
-        {
-          farg  = 0;
-          equal = 0;
-          goto noarg;
-        }
-
-      /* remember if equal sign is present */
-      equal = (fopt[next_token] == '=');
-      fopt[next_token++] = '\0';
-
-      /* advance pointers to the next token after the end of fopt */
-      next_token += strspn (fopt + next_token, " \t\r\n");
-
-      /* check for the presence of equal sign, and if so, skip it */
-      if ( !equal )
-        if ((equal = (fopt[next_token] == '=')))
-          {
-            next_token++;
-            next_token += strspn (fopt + next_token, " \t\r\n");
-          }
-      str_index  += next_token;
-
-      /* find argument */
-      farg = str_index;
-      if ( farg[0] == '\"' || farg[0] == '\'' )
-        { /* quoted argument */
-          str_index = strchr (++farg, str_index[0] ); /* skip opening quote */
-          if (! str_index)
-            {
-              fprintf
-                (stderr,
-                 "%s:%s:%d: unterminated string in configuration file\n",
-                 CMDLINE_PARSER_PACKAGE, filename, line_num);
-              result = EXIT_FAILURE;
-              break;
-            }
-        }
-      else
-        { /* read up the remaining part up to a delimiter */
-          next_token = strcspn (farg, " \t\r\n#\'\"");
-          str_index += next_token;
-        }
-
-      /* truncate farg at the delimiter and store it for further check */
-      delimiter = *str_index, *str_index++ = '\0';
-
-      /* everything but comment is illegal at the end of line */
-      if (delimiter != '\0' && delimiter != '#')
-        {
-          str_index += strspn(str_index, " \t\r\n");
-          if (*str_index != '\0' && *str_index != '#')
-            {
-              fprintf
-                (stderr,
-                 "%s:%s:%d: malformed string in configuration file\n",
-                 CMDLINE_PARSER_PACKAGE, filename, line_num);
-              result = EXIT_FAILURE;
-              break;
-            }
-        }
-
-    noarg:
-      if (!strcmp(fopt,"include")) {
-        if (farg && *farg) {
-          result = _cmdline_parser_configfile(farg, my_argc);
-        } else {
-          fprintf(stderr, "%s:%s:%d: include requires a filename argument.\n",
-                  CMDLINE_PARSER_PACKAGE, filename, line_num);
-        }
-        continue;
-      }
-      len = strlen(fopt);
-      strcat (my_argv, len > 1 ? "--" : "-");
-      strcat (my_argv, fopt);
-      if (len > 1 && ((farg && *farg) || equal))
-        strcat (my_argv, "=");
-      if (farg && *farg)
-        strcat (my_argv, farg);
-      ++(*my_argc);
-
-      cmd_line_list_tmp = (struct line_list *) malloc (sizeof (struct 
line_list));
-      cmd_line_list_tmp->next = cmd_line_list;
-      cmd_line_list = cmd_line_list_tmp;
-      cmd_line_list->string_arg = gengetopt_strdup(my_argv);
-    } /* while */
-
-  if (file)
-    fclose(file);
-  return result;
-}
-
-int
-cmdline_parser_configfile (
-  const char *filename,
-                           struct gengetopt_args_info *args_info,
-                           int override, int initialize, int check_required)
-{
-  struct cmdline_parser_params params;
-
-  params.override = override;
-  params.initialize = initialize;
-  params.check_required = check_required;
-  params.check_ambiguity = 0;
-  params.print_errors = 1;
-  
-  return cmdline_parser_config_file (filename, args_info, &params);
-}
-
-int
-cmdline_parser_config_file (const char *filename,
-                           struct gengetopt_args_info *args_info,
-                           struct cmdline_parser_params *params)
-{
-  int i, result;
-  int my_argc = 1;
-  char **my_argv_arg;
-  char *additional_error;
-
-  /* store the program name */
-  cmd_line_list_tmp = (struct line_list *) malloc (sizeof (struct line_list));
-  cmd_line_list_tmp->next = cmd_line_list;
-  cmd_line_list = cmd_line_list_tmp;
-  cmd_line_list->string_arg = gengetopt_strdup (CMDLINE_PARSER_PACKAGE);
-
-  result = _cmdline_parser_configfile(filename, &my_argc);
-
-  if (result != EXIT_FAILURE) {
-    my_argv_arg = (char **) malloc((my_argc+1) * sizeof(char *));
-    cmd_line_list_tmp = cmd_line_list;
-
-    for (i = my_argc - 1; i >= 0; --i) {
-      my_argv_arg[i] = cmd_line_list_tmp->string_arg;
-      cmd_line_list_tmp = cmd_line_list_tmp->next;
-    }
-
-    my_argv_arg[my_argc] = 0;
-
-    additional_error = (char *)malloc(strlen(filename) + 
strlen(ADDITIONAL_ERROR) + 1);
-    strcpy (additional_error, ADDITIONAL_ERROR);
-    strcat (additional_error, filename);
-    result =
-      cmdline_parser_internal (my_argc, my_argv_arg, args_info,
-                              params,
-                              additional_error);
-
-    free (additional_error);
-    free (my_argv_arg);
-  }
-
-  free_cmd_list();
-  if (result == EXIT_FAILURE)
-    {
-      cmdline_parser_free (args_info);
-      exit (EXIT_FAILURE);
-    }
-  
-  return result;
-}
diff --git a/ggsn/cmdline.ggo b/ggsn/cmdline.ggo
deleted file mode 100644
index 7426707..0000000
--- a/ggsn/cmdline.ggo
+++ /dev/null
@@ -1,38 +0,0 @@
-# OpenGGSN - Gateway GPRS Support Node
-# Copyright (C) 2002, 2003, 2004 Mondru AB.
-#  
-# The contents of this file may be used under the terms of the GNU
-# General Public License Version 2, provided that the above copyright
-# notice and this permission notice is included in all copies or
-# substantial portions of the software.
-#
-# Use "gengetopt --conf-parser < cmdline.ggo" 
-# to generate cmdline.c and cmdline.h
-
-option  "fg"          f "Run in foreground"             flag   off
-option  "debug"       d "Run in debug mode"             flag   off
-
-option  "conf"        c "Read configuration file"       string 
default="/etc/ggsn.conf" no
-option  "pidfile"     - "Filename of process id file"   string 
default="/var/run/ggsn.pid" no
-option  "statedir"    - "Directory of nonvolatile data" string 
default="/var/lib/ggsn/" no
-
-option  "listen"      l "Local interface"               string no
-option  "net"         n "Network"                       string 
default="192.168.0.0/24" no
-option  "ipup"        - "Script to run after link-up"    string no
-option  "ipdown"      - "Script to run after link-down"  string no
-
-option  "dynip"       - "Dynamic IP address pool"       string no
-option  "statip"      - "Static IP address pool"        string no
-
-option  "pcodns1"     - "PCO DNS Server 1"              string 
default="0.0.0.0" no
-option  "pcodns2"     - "PCO DNS Server 2"              string 
default="0.0.0.0" no
-
-option  "timelimit"   - "Exit after timelimit seconds"  int default="0" no
-
-option  "apn"         a "Access point name"             string 
default="internet" no
-option  "qos"         q "Requested quality of service"  int    
default="0x0b921f" no
-option  "logfile"     - "Logfile for errors"            string no
-option  "loglevel"    - "Global log ldevel"            string default="error" 
no
-
-option  "gtp-linux"   g "GTP linux kernel support"      flag   off
-
diff --git a/ggsn/cmdline.h b/ggsn/cmdline.h
deleted file mode 100644
index 57f5cae..0000000
--- a/ggsn/cmdline.h
+++ /dev/null
@@ -1,275 +0,0 @@
-/** @file cmdline.h
- *  @brief The header file for the command line option parser
- *  generated by GNU Gengetopt version 2.22.6
- *  http://www.gnu.org/software/gengetopt.
- *  DO NOT modify this file, since it can be overwritten
- *  @author GNU Gengetopt by Lorenzo Bettini */
-
-#ifndef CMDLINE_H
-#define CMDLINE_H
-
-/* If we use autoconf.  */
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <stdio.h> /* for FILE */
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-#ifndef CMDLINE_PARSER_PACKAGE
-/** @brief the program name (used for printing errors) */
-#define CMDLINE_PARSER_PACKAGE PACKAGE
-#endif
-
-#ifndef CMDLINE_PARSER_PACKAGE_NAME
-/** @brief the complete program name (used for help and version) */
-#ifdef PACKAGE_NAME
-#define CMDLINE_PARSER_PACKAGE_NAME PACKAGE_NAME
-#else
-#define CMDLINE_PARSER_PACKAGE_NAME PACKAGE
-#endif
-#endif
-
-#ifndef CMDLINE_PARSER_VERSION
-/** @brief the program version */
-#define CMDLINE_PARSER_VERSION VERSION
-#endif
-
-/** @brief Where the command line options are stored */
-struct gengetopt_args_info
-{
-  const char *help_help; /**< @brief Print help and exit help description.  */
-  const char *version_help; /**< @brief Print version and exit help 
description.  */
-  int fg_flag; /**< @brief Run in foreground (default=off).  */
-  const char *fg_help; /**< @brief Run in foreground help description.  */
-  int debug_flag;      /**< @brief Run in debug mode (default=off).  */
-  const char *debug_help; /**< @brief Run in debug mode help description.  */
-  char * conf_arg;     /**< @brief Read configuration file 
(default='/etc/ggsn.conf').  */
-  char * conf_orig;    /**< @brief Read configuration file original value 
given at command line.  */
-  const char *conf_help; /**< @brief Read configuration file help description. 
 */
-  char * pidfile_arg;  /**< @brief Filename of process id file 
(default='/var/run/ggsn.pid').  */
-  char * pidfile_orig; /**< @brief Filename of process id file original value 
given at command line.  */
-  const char *pidfile_help; /**< @brief Filename of process id file help 
description.  */
-  char * statedir_arg; /**< @brief Directory of nonvolatile data 
(default='/var/lib/ggsn/').  */
-  char * statedir_orig;        /**< @brief Directory of nonvolatile data 
original value given at command line.  */
-  const char *statedir_help; /**< @brief Directory of nonvolatile data help 
description.  */
-  char * listen_arg;   /**< @brief Local interface.  */
-  char * listen_orig;  /**< @brief Local interface original value given at 
command line.  */
-  const char *listen_help; /**< @brief Local interface help description.  */
-  char * net_arg;      /**< @brief Network (default='192.168.0.0/24').  */
-  char * net_orig;     /**< @brief Network original value given at command 
line.  */
-  const char *net_help; /**< @brief Network help description.  */
-  char * ipup_arg;     /**< @brief Script to run after link-up.  */
-  char * ipup_orig;    /**< @brief Script to run after link-up original value 
given at command line.  */
-  const char *ipup_help; /**< @brief Script to run after link-up help 
description.  */
-  char * ipdown_arg;   /**< @brief Script to run after link-down.  */
-  char * ipdown_orig;  /**< @brief Script to run after link-down original 
value given at command line.  */
-  const char *ipdown_help; /**< @brief Script to run after link-down help 
description.  */
-  char * dynip_arg;    /**< @brief Dynamic IP address pool.  */
-  char * dynip_orig;   /**< @brief Dynamic IP address pool original value 
given at command line.  */
-  const char *dynip_help; /**< @brief Dynamic IP address pool help 
description.  */
-  char * statip_arg;   /**< @brief Static IP address pool.  */
-  char * statip_orig;  /**< @brief Static IP address pool original value given 
at command line.  */
-  const char *statip_help; /**< @brief Static IP address pool help 
description.  */
-  char * pcodns1_arg;  /**< @brief PCO DNS Server 1 (default='0.0.0.0').  */
-  char * pcodns1_orig; /**< @brief PCO DNS Server 1 original value given at 
command line.  */
-  const char *pcodns1_help; /**< @brief PCO DNS Server 1 help description.  */
-  char * pcodns2_arg;  /**< @brief PCO DNS Server 2 (default='0.0.0.0').  */
-  char * pcodns2_orig; /**< @brief PCO DNS Server 2 original value given at 
command line.  */
-  const char *pcodns2_help; /**< @brief PCO DNS Server 2 help description.  */
-  int timelimit_arg;   /**< @brief Exit after timelimit seconds (default='0'). 
 */
-  char * timelimit_orig;       /**< @brief Exit after timelimit seconds 
original value given at command line.  */
-  const char *timelimit_help; /**< @brief Exit after timelimit seconds help 
description.  */
-  char * apn_arg;      /**< @brief Access point name (default='internet').  */
-  char * apn_orig;     /**< @brief Access point name original value given at 
command line.  */
-  const char *apn_help; /**< @brief Access point name help description.  */
-  int qos_arg; /**< @brief Requested quality of service (default='0x0b921f').  
*/
-  char * qos_orig;     /**< @brief Requested quality of service original value 
given at command line.  */
-  const char *qos_help; /**< @brief Requested quality of service help 
description.  */
-  char * logfile_arg;  /**< @brief Logfile for errors.  */
-  char * logfile_orig; /**< @brief Logfile for errors original value given at 
command line.  */
-  const char *logfile_help; /**< @brief Logfile for errors help description.  
*/
-  char * loglevel_arg; /**< @brief Global log ldevel (default='error').  */
-  char * loglevel_orig;        /**< @brief Global log ldevel original value 
given at command line.  */
-  const char *loglevel_help; /**< @brief Global log ldevel help description.  
*/
-  int gtp_linux_flag;  /**< @brief GTP linux kernel support (default=off).  */
-  const char *gtp_linux_help; /**< @brief GTP linux kernel support help 
description.  */
-  
-  unsigned int help_given ;    /**< @brief Whether help was given.  */
-  unsigned int version_given ; /**< @brief Whether version was given.  */
-  unsigned int fg_given ;      /**< @brief Whether fg was given.  */
-  unsigned int debug_given ;   /**< @brief Whether debug was given.  */
-  unsigned int conf_given ;    /**< @brief Whether conf was given.  */
-  unsigned int pidfile_given ; /**< @brief Whether pidfile was given.  */
-  unsigned int statedir_given ;        /**< @brief Whether statedir was given. 
 */
-  unsigned int listen_given ;  /**< @brief Whether listen was given.  */
-  unsigned int net_given ;     /**< @brief Whether net was given.  */
-  unsigned int ipup_given ;    /**< @brief Whether ipup was given.  */
-  unsigned int ipdown_given ;  /**< @brief Whether ipdown was given.  */
-  unsigned int dynip_given ;   /**< @brief Whether dynip was given.  */
-  unsigned int statip_given ;  /**< @brief Whether statip was given.  */
-  unsigned int pcodns1_given ; /**< @brief Whether pcodns1 was given.  */
-  unsigned int pcodns2_given ; /**< @brief Whether pcodns2 was given.  */
-  unsigned int timelimit_given ;       /**< @brief Whether timelimit was 
given.  */
-  unsigned int apn_given ;     /**< @brief Whether apn was given.  */
-  unsigned int qos_given ;     /**< @brief Whether qos was given.  */
-  unsigned int logfile_given ; /**< @brief Whether logfile was given.  */
-  unsigned int loglevel_given ;        /**< @brief Whether loglevel was given. 
 */
-  unsigned int gtp_linux_given ;       /**< @brief Whether gtp-linux was 
given.  */
-
-} ;
-
-/** @brief The additional parameters to pass to parser functions */
-struct cmdline_parser_params
-{
-  int override; /**< @brief whether to override possibly already present 
options (default 0) */
-  int initialize; /**< @brief whether to initialize the option structure 
gengetopt_args_info (default 1) */
-  int check_required; /**< @brief whether to check that all required options 
were provided (default 1) */
-  int check_ambiguity; /**< @brief whether to check for options already 
specified in the option structure gengetopt_args_info (default 0) */
-  int print_errors; /**< @brief whether getopt_long should print an error 
message for a bad option (default 1) */
-} ;
-
-/** @brief the purpose string of the program */
-extern const char *gengetopt_args_info_purpose;
-/** @brief the usage string of the program */
-extern const char *gengetopt_args_info_usage;
-/** @brief the description string of the program */
-extern const char *gengetopt_args_info_description;
-/** @brief all the lines making the help output */
-extern const char *gengetopt_args_info_help[];
-
-/**
- * The command line parser
- * @param argc the number of command line options
- * @param argv the command line options
- * @param args_info the structure where option information will be stored
- * @return 0 if everything went fine, NON 0 if an error took place
- */
-int cmdline_parser (int argc, char **argv,
-  struct gengetopt_args_info *args_info);
-
-/**
- * The command line parser (version with additional parameters - deprecated)
- * @param argc the number of command line options
- * @param argv the command line options
- * @param args_info the structure where option information will be stored
- * @param override whether to override possibly already present options
- * @param initialize whether to initialize the option structure my_args_info
- * @param check_required whether to check that all required options were 
provided
- * @return 0 if everything went fine, NON 0 if an error took place
- * @deprecated use cmdline_parser_ext() instead
- */
-int cmdline_parser2 (int argc, char **argv,
-  struct gengetopt_args_info *args_info,
-  int override, int initialize, int check_required);
-
-/**
- * The command line parser (version with additional parameters)
- * @param argc the number of command line options
- * @param argv the command line options
- * @param args_info the structure where option information will be stored
- * @param params additional parameters for the parser
- * @return 0 if everything went fine, NON 0 if an error took place
- */
-int cmdline_parser_ext (int argc, char **argv,
-  struct gengetopt_args_info *args_info,
-  struct cmdline_parser_params *params);
-
-/**
- * Save the contents of the option struct into an already open FILE stream.
- * @param outfile the stream where to dump options
- * @param args_info the option struct to dump
- * @return 0 if everything went fine, NON 0 if an error took place
- */
-int cmdline_parser_dump(FILE *outfile,
-  struct gengetopt_args_info *args_info);
-
-/**
- * Save the contents of the option struct into a (text) file.
- * This file can be read by the config file parser (if generated by gengetopt)
- * @param filename the file where to save
- * @param args_info the option struct to save
- * @return 0 if everything went fine, NON 0 if an error took place
- */
-int cmdline_parser_file_save(const char *filename,
-  struct gengetopt_args_info *args_info);
-
-/**
- * Print the help
- */
-void cmdline_parser_print_help(void);
-/**
- * Print the version
- */
-void cmdline_parser_print_version(void);
-
-/**
- * Initializes all the fields a cmdline_parser_params structure 
- * to their default values
- * @param params the structure to initialize
- */
-void cmdline_parser_params_init(struct cmdline_parser_params *params);
-
-/**
- * Allocates dynamically a cmdline_parser_params structure and initializes
- * all its fields to their default values
- * @return the created and initialized cmdline_parser_params structure
- */
-struct cmdline_parser_params *cmdline_parser_params_create(void);
-
-/**
- * Initializes the passed gengetopt_args_info structure's fields
- * (also set default values for options that have a default)
- * @param args_info the structure to initialize
- */
-void cmdline_parser_init (struct gengetopt_args_info *args_info);
-/**
- * Deallocates the string fields of the gengetopt_args_info structure
- * (but does not deallocate the structure itself)
- * @param args_info the structure to deallocate
- */
-void cmdline_parser_free (struct gengetopt_args_info *args_info);
-
-/**
- * The config file parser (deprecated version)
- * @param filename the name of the config file
- * @param args_info the structure where option information will be stored
- * @param override whether to override possibly already present options
- * @param initialize whether to initialize the option structure my_args_info
- * @param check_required whether to check that all required options were 
provided
- * @return 0 if everything went fine, NON 0 if an error took place
- * @deprecated use cmdline_parser_config_file() instead
- */
-int cmdline_parser_configfile (const char *filename,
-  struct gengetopt_args_info *args_info,
-  int override, int initialize, int check_required);
-
-/**
- * The config file parser
- * @param filename the name of the config file
- * @param args_info the structure where option information will be stored
- * @param params additional parameters for the parser
- * @return 0 if everything went fine, NON 0 if an error took place
- */
-int cmdline_parser_config_file (const char *filename,
-  struct gengetopt_args_info *args_info,
-  struct cmdline_parser_params *params);
-
-/**
- * Checks that all the required options were specified
- * @param args_info the structure to check
- * @param prog_name the name of the program that will be used to print
- *   possible errors
- * @return
- */
-int cmdline_parser_required (struct gengetopt_args_info *args_info,
-  const char *prog_name);
-
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-#endif /* CMDLINE_H */
diff --git a/ggsn/ggsn.c b/ggsn/ggsn.c
index 2ab0e43..da46a62 100644
--- a/ggsn/ggsn.c
+++ b/ggsn/ggsn.c
@@ -1,6 +1,7 @@
 /* 
  * OpenGGSN - Gateway GPRS Support Node
  * Copyright (C) 2002, 2003, 2004 Mondru AB.
+ * Copyright (C) 2017 by Harald Welte <[email protected]>
  * 
  * The contents of this file may be used under the terms of the GNU
  * General Public License Version 2, provided that the above copyright
@@ -19,42 +20,41 @@
 
 #include "../config.h"
 
-#include <osmocom/core/application.h>
-
 #ifdef HAVE_STDINT_H
 #include <stdint.h>
 #endif
 
+#include <getopt.h>
 #include <ctype.h>
-#include <netdb.h>
 #include <signal.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/ip.h>
-#include <netinet/ip6.h>
-#include <arpa/inet.h>
-#include <sys/wait.h>
-#include <sys/stat.h>
-#include <fcntl.h>
 #include <unistd.h>
 #include <inttypes.h>
-#include <sys/socket.h>
-#include <sys/ioctl.h>
-#include <net/if.h>
-#include <net/if.h>
-
 #include <errno.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
 
-#include <time.h>
+#include <net/if.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
 
+#include <osmocom/core/application.h>
 #include <osmocom/core/select.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/timer.h>
 #include <osmocom/ctrl/control_if.h>
 #include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/control_vty.h>
 #include <osmocom/ctrl/ports.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/gsm/apn.h>
 
 #include "../lib/tun.h"
 #include "../lib/ippool.h"
@@ -62,82 +62,188 @@
 #include "../lib/in46_addr.h"
 #include "../gtp/pdp.h"
 #include "../gtp/gtp.h"
-#include "cmdline.h"
 #include "gtp-kernel.h"
 #include "icmpv6.h"
+#include "ggsn.h"
 
-int end = 0;
-int maxfd = 0;                 /* For select()            */
+void *tall_ggsn_ctx;
 
-struct in_addr listen_;
-struct in46_addr netaddr, destaddr, net;       /* Network interface       */
-size_t prefixlen;
-struct in46_addr dns1, dns2;   /* PCO DNS address         */
-char *ipup, *ipdown;           /* Filename of scripts     */
-int debug;                     /* Print debug output      */
-struct ul255_t pco;
+static int end = 0;
+static int daemonize = 0;
+static struct ctrl_handle *g_ctrlh;
+
 struct ul255_t qos;
 struct ul255_t apn;
 
-struct gsn_t *gsn;             /* GSN instance            */
-struct tun_t *tun;             /* TUN instance            */
-struct ippool_t *ippool;       /* Pool of IP addresses    */
+#define LOGPAPN(level, apn, fmt, args...)                      \
+       LOGP(DGGSN, level, "APN(%s): " fmt, (apn)->cfg.name, ## args)
 
-/* To exit gracefully. Used with GCC compilation flag -pg and gprof */
-void signal_handler(int s)
+#define LOGPGGSN(level, ggsn, fmt, args...)                    \
+       LOGP(DGGSN, level, "GGSN(%s): " fmt, (ggsn)->cfg.name, ## args)
+
+#define LOGPPDP(level, pdp, fmt, args...)                      \
+       LOGP(DGGSN, level, "PDP(%s:%u): " fmt, imsi_gtp2str(&(pdp)->imsi), 
(pdp)->nsapi, ## args)
+
+static int ggsn_tun_fd_cb(struct osmo_fd *fd, unsigned int what);
+static int cb_tun_ind(struct tun_t *tun, void *pack, unsigned len);
+
+
+static void pool_close_all_pdp(struct ippool_t *pool)
 {
-       DEBUGP(DGGSN, "Received signal %d, exiting.\n", s);
-       end = 1;
-}
+       unsigned int i;
 
-/* Used to write process ID to file. Assume someone else will delete */
-void log_pid(char *pidfile)
-{
-       FILE *file;
-       mode_t oldmask;
-
-       oldmask = umask(022);
-       file = fopen(pidfile, "w");
-       umask(oldmask);
-       if (!file) {
-               SYS_ERR(DGGSN, LOGL_ERROR, 0,
-                       "Failed to create process ID file: %s!", pidfile);
+       if (!pool)
                return;
+
+       for (i = 0; i < pool->listsize; i++) {
+               struct ippoolm_t *member = &pool->member[i];
+               struct pdp_t *pdp;
+
+               if (!member->inuse)
+                       continue;
+               pdp = member->peer;
+               if (!pdp)
+                       continue;
+               LOGPPDP(LOGL_DEBUG, pdp, "Sending DELETE PDP CTX due to 
shutdown\n");
+               gtp_delete_context_req(pdp->gsn, pdp, NULL, 1);
        }
-       fprintf(file, "%d\n", (int)getpid());
-       fclose(file);
 }
 
-#if defined(__sun__)
-int daemon(int nochdir, int noclose)
+int apn_stop(struct apn_ctx *apn, bool force)
 {
-       int fd;
+       if (!apn->started)
+               return 0;
 
-       switch (fork()) {
-       case -1:
-               return (-1);
-       case 0:
+       LOGPAPN(LOGL_NOTICE, apn, "%sStopping\n", force ? "FORCED " : "");
+       /* check if pools have any active PDP contexts and bail out */
+       pool_close_all_pdp(apn->v4.pool);
+       pool_close_all_pdp(apn->v6.pool);
+
+       /* shutdown whatever old state might be left */
+       if (apn->tun.tun) {
+               /* run ip-down script */
+               if (apn->tun.cfg.ipdown_script) {
+                       LOGPAPN( LOGL_INFO, apn, "Running %s\n", 
apn->tun.cfg.ipdown_script);
+                       tun_runscript(apn->tun.tun, apn->tun.cfg.ipdown_script);
+               }
+               /* release tun device */
+               LOGPAPN(LOGL_INFO, apn, "Closing TUN device\n");
+               osmo_fd_unregister(&apn->tun.fd);
+               tun_free(apn->tun.tun);
+               apn->tun.tun = NULL;
+       }
+
+       if (apn->v4.pool) {
+               LOGPAPN(LOGL_INFO, apn, "Releasing IPv4 pool\n");
+               ippool_free(apn->v4.pool);
+               apn->v4.pool = NULL;
+       }
+       if (apn->v6.pool) {
+               LOGPAPN(LOGL_INFO, apn, "Releasing IPv6 pool\n");
+               ippool_free(apn->v6.pool);
+               apn->v6.pool = NULL;
+       }
+
+       apn->started = false;
+       return 0;
+}
+
+/* actually start the APN with its current config */
+int apn_start(struct apn_ctx *apn)
+{
+       if (apn->started)
+               return 0;
+
+       LOGPAPN(LOGL_INFO, apn, "Starting\n");
+       switch (apn->cfg.gtpu_mode) {
+       case APN_GTPU_MODE_TUN:
+               LOGPAPN(LOGL_INFO, apn, "Opening TUN device %s\n", 
apn->tun.cfg.dev_name);
+               if (tun_new(&apn->tun.tun, apn->tun.cfg.dev_name)) {
+                       LOGPAPN(LOGL_ERROR, apn, "Failed to configure tun 
device\n");
+                       return -1;
+               }
+               LOGPAPN(LOGL_INFO, apn, "Opened TUN device %s\n", 
apn->tun.tun->devname);
+
+               /* Register with libosmcoore */
+               osmo_fd_setup(&apn->tun.fd, apn->tun.tun->fd, BSC_FD_READ, 
ggsn_tun_fd_cb, apn, 0);
+               osmo_fd_register(&apn->tun.fd);
+
+               /* Set TUN library callback */
+               tun_set_cb_ind(apn->tun.tun, cb_tun_ind);
+
+               if (apn->v4.cfg.ifconfig_prefix.addr.len) {
+                       LOGPAPN(LOGL_INFO, apn, "Setting tun IP address %s\n",
+                               in46p_ntoa(&apn->v4.cfg.ifconfig_prefix));
+                       if (tun_setaddr(apn->tun.tun, 
&apn->v4.cfg.ifconfig_prefix.addr, NULL,
+                                       apn->v4.cfg.ifconfig_prefix.prefixlen)) 
{
+                               LOGPAPN(LOGL_ERROR, apn, "Failed to set tun 
IPv4 address %s: %s\n",
+                                       
in46p_ntoa(&apn->v4.cfg.ifconfig_prefix), strerror(errno));
+                               apn_stop(apn, false);
+                               return -1;
+                       }
+               }
+
+               if (apn->v6.cfg.ifconfig_prefix.addr.len) {
+                       LOGPAPN(LOGL_INFO, apn, "Setting tun IPv6 address %s\n",
+                               in46p_ntoa(&apn->v6.cfg.ifconfig_prefix));
+                       if (tun_setaddr(apn->tun.tun, 
&apn->v6.cfg.ifconfig_prefix.addr, NULL,
+                                       apn->v6.cfg.ifconfig_prefix.prefixlen)) 
{
+                               LOGPAPN(LOGL_ERROR, apn, "Failed to set tun 
IPv6 address %s: %s\n",
+                                       
in46p_ntoa(&apn->v6.cfg.ifconfig_prefix), strerror(errno));
+                               apn_stop(apn, false);
+                               return -1;
+                       }
+               }
+
+               if (apn->tun.cfg.ipup_script) {
+                       LOGPAPN(LOGL_INFO, apn, "Running ip-up script %s\n",
+                               apn->tun.cfg.ipup_script);
+                       tun_runscript(apn->tun.tun, apn->tun.cfg.ipup_script);
+               }
+               /* set back-pointer from TUN device to APN */
+               apn->tun.tun->priv = apn;
+               break;
+       case APN_GTPU_MODE_KERNEL_GTP:
+               LOGPAPN(LOGL_ERROR, apn, "FIXME: Kernel GTP\n");
+#if 0
+               /* use GTP kernel module for data packet encapsulation */
+               if (gtp_kernel_init(gsn, &net.v4, prefixlen, net_arg) < 0)
+                       goto err;
+#endif
                break;
        default:
-               _exit(0);
+               LOGPAPN(LOGL_ERROR, apn, "Unknown GTPU Mode %d\n", 
apn->cfg.gtpu_mode);
+               return -1;
        }
 
-       if (setsid() == -1)
-               return (-1);
-
-       if (!nochdir)
-               chdir("/");
-
-       if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
-               dup2(fd, STDIN_FILENO);
-               dup2(fd, STDOUT_FILENO);
-               dup2(fd, STDERR_FILENO);
-               if (fd > 2)
-                       close(fd);
+       /* Create IPv4 pool */
+       if (apn->v4.cfg.dynamic_prefix.addr.len) {
+               LOGPAPN(LOGL_INFO, apn, "Creating IPv4 pool %s\n",
+                       in46p_ntoa(&apn->v4.cfg.dynamic_prefix));
+               if (ippool_new(&apn->v4.pool, &apn->v4.cfg.dynamic_prefix,
+                               &apn->v4.cfg.static_prefix, 0)) {
+                       LOGPAPN(LOGL_ERROR, apn, "Failed to create IPv4 
pool\n");
+                       apn_stop(apn, false);
+                       return -1;
+               }
        }
-       return (0);
+
+       /* Create IPv6 pool */
+       if (apn->v6.cfg.dynamic_prefix.addr.len) {
+               LOGPAPN(LOGL_INFO, apn, "Creating IPv6 pool %s\n",
+                       in46p_ntoa(&apn->v6.cfg.dynamic_prefix));
+               if (ippool_new(&apn->v6.pool, &apn->v6.cfg.dynamic_prefix,
+                               &apn->v6.cfg.static_prefix, 0)) {
+                       LOGPAPN(LOGL_ERROR, apn, "Failed to create IPv6 
pool\n");
+                       apn_stop(apn, false);
+                       return -1;
+               }
+       }
+
+       LOGPAPN(LOGL_NOTICE, apn, "Successfully started\n");
+       apn->started = true;
+       return 0;
 }
-#endif
 
 static bool send_trap(const struct gsn_t *gsn, const struct pdp_t *pdp, const 
struct ippoolm_t *member, const char *var)
 {
@@ -148,27 +254,29 @@
 
        snprintf(val, sizeof(val), "%s,%s", imsi_gtp2str(&pdp->imsi), addrstr);
 
-       if (ctrl_cmd_send_trap(gsn->ctrl, var, val) < 0) {
-               LOGP(DGGSN, LOGL_ERROR, "Failed to create and send TRAP for 
IMSI %" PRIu64 " [%s].\n", pdp->imsi, var);
+       if (ctrl_cmd_send_trap(g_ctrlh, var, val) < 0) {
+               LOGPPDP(LOGL_ERROR, pdp, "Failed to create and send TRAP %s\n", 
var);
                return false;
        }
        return true;
 }
 
-int delete_context(struct pdp_t *pdp)
+static int delete_context(struct pdp_t *pdp)
 {
-       DEBUGP(DGGSN, "Deleting PDP context\n");
+       struct gsn_t *gsn = pdp->gsn;
+       struct ippoolm_t *ipp = (struct ippoolm_t *)pdp->peer;
+
+       LOGPPDP(LOGL_INFO, pdp, "Deleting PDP context\n");
        struct ippoolm_t *member = pdp->peer;
 
        if (pdp->peer) {
                send_trap(gsn, pdp, member, "imsi-rem-ip"); /* TRAP with IP 
removal */
-               ippool_freeip(ippool, (struct ippoolm_t *)pdp->peer);
+               ippool_freeip(ipp->pool, ipp);
        } else
-               SYS_ERR(DGGSN, LOGL_ERROR, 0, "Peer not defined!");
+               LOGPPDP(LOGL_ERROR, pdp, "Cannot find/free IP Pool member\n");
 
        if (gtp_kernel_tunnel_del(pdp)) {
-               SYS_ERR(DGGSN, LOGL_ERROR, 0,
-                       "Cannot delete tunnel from kernel: %s\n",
+               LOGPPDP(LOGL_ERROR, pdp, "Cannot delete tunnel from 
kernel:%s\n",
                        strerror(errno));
        }
 
@@ -236,31 +344,74 @@
                return false;
 }
 
+/* construct an IPCP PCO from up to two given DNS addreses */
+static int build_ipcp_pco(struct msgb *msg, uint8_t id, const struct in46_addr 
*dns1,
+                         const struct in46_addr *dns2)
+{
+       uint8_t *len1, *len2;
+       uint8_t *start = msg->tail;
+       unsigned int len_appended;
+
+       /* Three byte T16L header */
+       msgb_put_u16(msg, 0x8021);      /* IPCP */
+       len1 = msgb_put(msg, 1);        /* Length of contents: delay */
+
+       msgb_put_u8(msg, 0x02);         /* ACK */
+       msgb_put_u8(msg, id);           /* ID: Needs to match request */
+       msgb_put_u8(msg, 0x00);         /* Length MSB */
+       len2 = msgb_put(msg, 1);        /* Length LSB: delay */
+
+       if (dns1 && dns1->len == 4) {
+               msgb_put_u8(msg, 0x81);         /* DNS1 Tag */
+               msgb_put_u8(msg, 2 + dns1->len);/* DNS1 Length, incl. TL */
+               msgb_put_u32(msg, dns1->v4.s_addr);
+       }
+
+       if (dns2 && dns2->len == 4) {
+               msgb_put_u8(msg, 0x83);         /* DNS2 Tag */
+               msgb_put_u8(msg, 2 + dns2->len);/* DNS2 Length, incl. TL */
+               msgb_put_u32(msg, dns2->v4.s_addr);
+       }
+
+       /* patch in length values */
+       len_appended = msg->tail - start;
+       *len1 = len_appended - 3;
+       *len2 = len_appended - 3;
+
+       return 0;
+}
+
 /* process one PCO request from a MS/UE, putting together the proper responses 
*/
-static void process_pco(struct pdp_t *pdp)
+static void process_pco(struct apn_ctx *apn, struct pdp_t *pdp)
 {
        struct msgb *msg = msgb_alloc(256, "PCO");
+       unsigned int i;
+
+       OSMO_ASSERT(msg);
        msgb_put_u8(msg, 0x80); /* ext-bit + configuration protocol byte */
 
        /* FIXME: also check if primary / secondary DNS was requested */
        if (pdp_has_v4(pdp) && pco_contains_proto(&pdp->pco_req, PCO_P_IPCP)) {
                /* FIXME: properly implement this for IPCP */
-               uint8_t *cur = msgb_put(msg, pco.l-1);
-               memcpy(cur, pco.v+1, pco.l-1);
+               build_ipcp_pco(msg, 0, &apn->v4.cfg.dns[0], 
&apn->v4.cfg.dns[1]);
        }
 
        if (pco_contains_proto(&pdp->pco_req, PCO_P_DNS_IPv6_ADDR)) {
-               if (dns1.len == 16)
-                       msgb_t16lv_put(msg, PCO_P_DNS_IPv6_ADDR, dns1.len, 
dns1.v6.s6_addr);
-               if (dns2.len == 16)
-                       msgb_t16lv_put(msg, PCO_P_DNS_IPv6_ADDR, dns2.len, 
dns2.v6.s6_addr);
+               for (i = 0; i < ARRAY_SIZE(apn->v6.cfg.dns); i++) {
+                       struct in46_addr *i46a = &apn->v6.cfg.dns[i];
+                       if (i46a->len != 16)
+                               continue;
+                       msgb_t16lv_put(msg, PCO_P_DNS_IPv6_ADDR, i46a->len, 
i46a->v6.s6_addr);
+               }
        }
 
        if (pco_contains_proto(&pdp->pco_req, PCO_P_DNS_IPv4_ADDR)) {
-               if (dns1.len == 4)
-                       msgb_t16lv_put(msg, PCO_P_DNS_IPv4_ADDR, dns1.len, 
(uint8_t *)&dns1.v4);
-               if (dns2.len == 4)
-                       msgb_t16lv_put(msg, PCO_P_DNS_IPv4_ADDR, dns2.len, 
(uint8_t *)&dns2.v4);
+               for (i = 0; i < ARRAY_SIZE(apn->v4.cfg.dns); i++) {
+                       struct in46_addr *i46a = &apn->v4.cfg.dns[i];
+                       if (i46a->len != 4)
+                               continue;
+                       msgb_t16lv_put(msg, PCO_P_DNS_IPv4_ADDR, i46a->len, 
(uint8_t *)&i46a->v4);
+               }
        }
 
        if (msgb_length(msg) > 1) {
@@ -274,11 +425,29 @@
 
 int create_context_ind(struct pdp_t *pdp)
 {
+       static char name_buf[256];
+       struct gsn_t *gsn = pdp->gsn;
+       struct ggsn_ctx *ggsn = gsn->priv;
        struct in46_addr addr;
        struct ippoolm_t *member;
+       struct apn_ctx *apn;
        int rc;
 
-       DEBUGP(DGGSN, "Received create PDP context request\n");
+       osmo_apn_to_str(name_buf, pdp->apn_req.v, pdp->apn_req.l);
+
+       LOGPPDP(LOGL_DEBUG, pdp, "Processing create PDP context request for APN 
'%s'\n", name_buf);
+
+       /* First find an exact APN name match */
+       apn = ggsn_find_apn(ggsn, name_buf);
+       /* then try default (if any) */
+       if (!apn)
+               apn = ggsn->cfg.default_apn;
+       if (!apn) {
+               /* no APN found for what user requested */
+               LOGPPDP(LOGL_NOTICE, pdp, "Unknown APN '%s', rejecting\n", 
name_buf);
+               gtp_create_context_resp(gsn, pdp, GTPCAUSE_MISSING_APN);
+               return 0;
+       }
 
        /* FIXME: we manually force all context requests to dynamic here! */
        if (pdp->eua.l > 2)
@@ -290,21 +459,30 @@
        pdp->qos_neg.l = pdp->qos_req.l;
 
        if (in46a_from_eua(&pdp->eua, &addr)) {
-               SYS_ERR(DGGSN, LOGL_ERROR, 0, "Cannot decode EUA from MS/SGSN: 
%s",
+               LOGPPDP(LOGL_ERROR, pdp, "Cannot decode EUA from MS/SGSN: %s\n",
                        osmo_hexdump(pdp->eua.v, pdp->eua.l));
                gtp_create_context_resp(gsn, pdp, GTPCAUSE_UNKNOWN_PDP);
                return 0;
        }
 
-       rc = ippool_newip(ippool, &member, &addr, 0);
-       if (rc < 0) {
-               SYS_ERR(DGGSN, LOGL_ERROR, 0, "Cannot allocate IP address in 
pool\n");
-               gtp_create_context_resp(gsn, pdp, -rc);
-               return 0;       /* Allready in use, or no more available */
-       }
+       if (addr.len == sizeof(struct in_addr)) {
+               rc = ippool_newip(apn->v4.pool, &member, &addr, 0);
+               if (rc < 0)
+                       goto err_pool_full;
+               in46a_to_eua(&member->addr, &pdp->eua);
 
-       if (addr.len == sizeof(struct in6_addr)) {
+               /* TODO: In IPv6, EUA doesn't contain the actual IP 
addr/prefix! */
+               if (gtp_kernel_tunnel_add(pdp) < 0) {
+                       LOGPPDP(LOGL_ERROR, pdp, "Cannot add tunnel to kernel: 
%s\n", strerror(errno));
+                       gtp_create_context_resp(gsn, pdp, GTPCAUSE_SYS_FAIL);
+                       return 0;
+               }
+       } else if (addr.len == sizeof(struct in6_addr)) {
                struct in46_addr tmp;
+               rc = ippool_newip(apn->v6.pool, &member, &addr, 0);
+               if (rc < 0)
+                       goto err_pool_full;
+
                /* IPv6 doesn't really send the real/allocated address at this 
point, but just
                 * the link-identifier which the MS shall use for router 
solicitation */
                tmp.len = addr.len;
@@ -314,43 +492,46 @@
                memcpy(tmp.v6.s6_addr+8, &member->addr.v6, 8);
                in46a_to_eua(&tmp, &pdp->eua);
        } else
-               in46a_to_eua(&member->addr, &pdp->eua);
-       pdp->peer = member;
-       pdp->ipif = tun;        /* TODO */
-       member->peer = pdp;
+               OSMO_ASSERT(0);
 
-       /* TODO: In IPv6, EUA doesn't contain the actual IP addr/prefix! */
-       if (gtp_kernel_tunnel_add(pdp) < 0) {
-               SYS_ERR(DGGSN, LOGL_ERROR, 0,
-                       "Cannot add tunnel to kernel: %s\n", strerror(errno));
-               gtp_create_context_resp(gsn, pdp, GTPCAUSE_SYS_FAIL);
-               return 0;
-       }
+       pdp->peer = member;
+       pdp->ipif = apn->tun.tun;       /* TODO */
+       member->peer = pdp;
 
        if (!send_trap(gsn, pdp, member, "imsi-ass-ip")) { /* TRAP with IP 
assignment */
                gtp_create_context_resp(gsn, pdp, GTPCAUSE_NO_RESOURCES);
                return 0;
        }
 
-       process_pco(pdp);
+       process_pco(apn, pdp);
 
+       LOGPPDP(LOGL_INFO, pdp, "Successful PDP Context Creation: APN=%s(%s), 
TEIC=%u, IP=%s\n",
+               name_buf, apn->cfg.name, pdp->teic_own, 
in46a_ntoa(&member->addr));
        gtp_create_context_resp(gsn, pdp, GTPCAUSE_ACC_REQ);
        return 0;               /* Success */
+
+err_pool_full:
+       LOGPPDP(LOGL_ERROR, pdp, "Cannot allocate IP address from pool 
(full!)\n");
+       gtp_create_context_resp(gsn, pdp, -rc);
+       return 0;       /* Already in use, or no more available */
 }
 
-/* Callback for receiving messages from tun */
-int cb_tun_ind(struct tun_t *tun, void *pack, unsigned len)
+/* Internet-originated IP packet, needs to be sent via GTP towards MS */
+static int cb_tun_ind(struct tun_t *tun, void *pack, unsigned len)
 {
+       struct apn_ctx *apn = tun->priv;
        struct ippoolm_t *ipm;
        struct in46_addr dst;
        struct iphdr *iph = (struct iphdr *)pack;
        struct ip6_hdr *ip6h = (struct ip6_hdr *)pack;
+       struct ippool_t *pool;
 
        if (iph->version == 4) {
                if (len < sizeof(*iph) || len < 4*iph->ihl)
                        return -1;
                dst.len = 4;
                dst.v4.s_addr = iph->daddr;
+               pool = apn->v4.pool;
        } else if (iph->version == 6) {
                /* Due to the fact that 3GPP requires an allocation of a
                 * /64 prefix to each MS, we must instruct
@@ -358,20 +539,25 @@
                 * prefix, i.e. the first 8 bytes of the address */
                dst.len = 8;
                dst.v6 = ip6h->ip6_dst;
+               pool = apn->v6.pool;
        } else {
-               LOGP(DGGSN, LOGL_NOTICE, "non-IPv packet received from tun\n");
+               LOGP(DTUN, LOGL_NOTICE, "non-IPv packet received from tun\n");
                return -1;
        }
 
-       DEBUGP(DGGSN, "Received packet from tun!\n");
+       /* IPv6 packet but no IPv6 pool, or IPv4 packet with no IPv4 pool */
+       if (!pool)
+               return 0;
 
-       if (ippool_getip(ippool, &ipm, &dst)) {
-               DEBUGP(DGGSN, "Received packet with no destination!!!\n");
+       DEBUGP(DTUN, "Received packet from tun!\n");
+
+       if (ippool_getip(pool, &ipm, &dst)) {
+               DEBUGP(DTUN, "Received packet with no PDP contex!!\n");
                return 0;
        }
 
        if (ipm->peer)          /* Check if a peer protocol is defined */
-               gtp_data_req(gsn, (struct pdp_t *)ipm->peer, pack, len);
+               gtp_data_req(apn->ggsn->gsn, (struct pdp_t *)ipm->peer, pack, 
len);
        return 0;
 }
 
@@ -380,435 +566,303 @@
        .s6_addr = { 0xff,0x02,0,0,  0,0,0,0, 0,0,0,0,  0,0,0,2 }
 };
 
-int encaps_tun(struct pdp_t *pdp, void *pack, unsigned len)
+/* MS-originated GTP1-U packet, needs to be sent via TUN device */
+static int encaps_tun(struct pdp_t *pdp, void *pack, unsigned len)
 {
        struct iphdr *iph = (struct iphdr *)pack;
        struct ip6_hdr *ip6h = (struct ip6_hdr *)pack;
 
-       DEBUGP(DGGSN, "encaps_tun. Packet received: forwarding to tun\n");
+       LOGPPDP(LOGL_DEBUG, pdp, "Packet received: forwarding to tun\n");
 
        switch (iph->version) {
        case 6:
                /* daddr: all-routers multicast addr */
                if (IN6_ARE_ADDR_EQUAL(&ip6h->ip6_dst, &all_router_mcast_addr))
-                       return handle_router_mcast(gsn, pdp, pack, len);
+                       return handle_router_mcast(pdp->gsn, pdp, pack, len);
                break;
        case 4:
                break;
        default:
-               LOGP(DGGSN, LOGL_ERROR, "Packet from MS is neither IPv4 nor 
IPv6\n");
+               LOGPPDP(LOGL_ERROR, pdp, "Packet from MS is neither IPv4 nor 
IPv6: %s\n",
+                       osmo_hexdump(pack, len));
                return -1;
        }
        return tun_encaps((struct tun_t *)pdp->ipif, pack, len);
 }
 
+static char *config_file = "openggsn.cfg";
+
+/* callback for tun device osmocom select loop integration */
+static int ggsn_tun_fd_cb(struct osmo_fd *fd, unsigned int what)
+{
+       struct apn_ctx *apn = fd->data;
+
+       OSMO_ASSERT(what & BSC_FD_READ);
+
+       return tun_decaps(apn->tun.tun);
+}
+
+/* callback for libgtp osmocom select loop integration */
+static int ggsn_gtp_fd_cb(struct osmo_fd *fd, unsigned int what)
+{
+       struct ggsn_ctx *ggsn = fd->data;
+       int rc;
+
+       OSMO_ASSERT(what & BSC_FD_READ);
+
+       switch (fd->priv_nr) {
+       case 0:
+               rc = gtp_decaps0(ggsn->gsn);
+               break;
+       case 1:
+               rc = gtp_decaps1c(ggsn->gsn);
+               break;
+       case 2:
+               rc = gtp_decaps1u(ggsn->gsn);
+               break;
+       default:
+               OSMO_ASSERT(0);
+               break;
+       }
+       return rc;
+}
+
+static void ggsn_gtp_tmr_start(struct ggsn_ctx *ggsn)
+{
+       struct timeval next;
+
+       /* Retrieve next retransmission as timeval */
+       gtp_retranstimeout(ggsn->gsn, &next);
+
+       /* re-schedule the timer */
+       osmo_timer_schedule(&ggsn->gtp_timer, next.tv_sec, next.tv_usec/1000);
+}
+
+/* timer callback for libgtp retransmission and ping */
+static void ggsn_gtp_tmr_cb(void *data)
+{
+       struct ggsn_ctx *ggsn = data;
+
+       /* do all the retransmissions as needed */
+       gtp_retrans(ggsn->gsn);
+
+       ggsn_gtp_tmr_start(ggsn);
+}
+
+/* To exit gracefully. Used with GCC compilation flag -pg and gprof */
+static void signal_handler(int s)
+{
+       LOGP(DGGSN, LOGL_NOTICE, "signal %d received\n", s);
+       switch (s) {
+       case SIGINT:
+               LOGP(DGGSN, LOGL_NOTICE, "SIGINT received, shutting down\n");
+               end = 1;
+               break;
+       case SIGABRT:
+       case SIGUSR1:
+               talloc_report(tall_vty_ctx, stderr);
+               talloc_report_full(tall_ggsn_ctx, stderr);
+               break;
+       case SIGUSR2:
+               talloc_report_full(tall_vty_ctx, stderr);
+               break;
+       default:
+               break;
+       }
+}
+
+
+/* Start a given GGSN */
+int ggsn_start(struct ggsn_ctx *ggsn)
+{
+       struct apn_ctx *apn;
+       int rc;
+
+       if (ggsn->started)
+               return 0;
+
+       LOGPGGSN(LOGL_INFO, ggsn, "Starting GGSN\n");
+
+       /* Start libgtp listener */
+       if (gtp_new(&ggsn->gsn, ggsn->cfg.state_dir, &ggsn->cfg.listen_addr.v4, 
GTP_MODE_GGSN)) {
+               LOGPGGSN(LOGL_ERROR, ggsn, "Failed to create GTP: %s\n", 
strerror(errno));
+               return -1;
+       }
+       ggsn->gsn->priv = ggsn;
+
+       /* Register File Descriptors */
+       osmo_fd_setup(&ggsn->gtp_fd0, ggsn->gsn->fd0, BSC_FD_READ, 
ggsn_gtp_fd_cb, ggsn, 0);
+       rc = osmo_fd_register(&ggsn->gtp_fd0);
+       OSMO_ASSERT(rc == 0);
+
+       osmo_fd_setup(&ggsn->gtp_fd1c, ggsn->gsn->fd1c, BSC_FD_READ, 
ggsn_gtp_fd_cb, ggsn, 1);
+       rc = osmo_fd_register(&ggsn->gtp_fd1c);
+       OSMO_ASSERT(rc == 0);
+
+       osmo_fd_setup(&ggsn->gtp_fd1u, ggsn->gsn->fd1u, BSC_FD_READ, 
ggsn_gtp_fd_cb, ggsn, 2);
+       rc = osmo_fd_register(&ggsn->gtp_fd1u);
+       OSMO_ASSERT(rc == 0);
+
+       /* Start GTP re-transmission timer */
+       osmo_timer_setup(&ggsn->gtp_timer, ggsn_gtp_tmr_cb, ggsn);
+
+       gtp_set_cb_data_ind(ggsn->gsn, encaps_tun);
+       gtp_set_cb_delete_context(ggsn->gsn, delete_context);
+       gtp_set_cb_create_context_ind(ggsn->gsn, create_context_ind);
+
+       LOGPGGSN(LOGL_NOTICE, ggsn, "Successfully started\n");
+       ggsn->started = true;
+
+       llist_for_each_entry(apn, &ggsn->apn_list, list)
+               apn_start(apn);
+
+       return 0;
+}
+
+/* Stop a given GGSN */
+int ggsn_stop(struct ggsn_ctx *ggsn)
+{
+       struct apn_ctx *apn;
+
+       if (!ggsn->started)
+               return 0;
+
+       /* iterate over all APNs and stop them */
+       llist_for_each_entry(apn, &ggsn->apn_list, list)
+               apn_stop(apn, true);
+
+       osmo_timer_del(&ggsn->gtp_timer);
+
+       osmo_fd_unregister(&ggsn->gtp_fd1u);
+       osmo_fd_unregister(&ggsn->gtp_fd1c);
+       osmo_fd_unregister(&ggsn->gtp_fd0);
+
+       if (ggsn->gsn) {
+               gtp_free(ggsn->gsn);
+               ggsn->gsn = NULL;
+       }
+
+       ggsn->started = false;
+       return 0;
+}
+
+static void print_usage()
+{
+       printf("Usage: osmo-ggsn [-h] [-D] [-c configfile] [-V]\n");
+}
+
+static void print_help()
+{
+       printf( "  Some useful help...\n"
+               "  -h --help            This help text\n"
+               "  -D --daemonize       Fork the process into a background 
daemon\n"
+               "  -c --config-file     filename The config file to use\n"
+               "  -V --version         Print the version of OsmoGGSN\n"
+               );
+}
+
+static void handle_options(int argc, char **argv)
+{
+       while (1) {
+               int option_index = 0, c;
+               static struct option long_options[] = {
+                       { "help", 0, 0, 'h' },
+                       { "daemonize", 0, 0, 'D' },
+                       { "config-file", 1, 0, 'c' },
+                       { "version", 0, 0, 'V' },
+                       { 0, 0, 0, 0 }
+               };
+
+               c = getopt_long(argc, argv, "hdc:V", long_options, 
&option_index);
+               if (c == -1)
+                       break;
+
+               switch (c) {
+               case 'h':
+                       print_usage();
+                       print_help();
+                       exit(0);
+               case 'D':
+                       daemonize = 1;
+                       break;
+               case 'c':
+                       config_file = optarg;
+                       break;
+               case 'V':
+                       print_version(1);
+                       exit(0);
+                       break;
+               }
+       }
+}
+
 int main(int argc, char **argv)
 {
-       /* gengeopt declarations */
-       struct gengetopt_args_info args_info;
-
-       struct hostent *host;
+       struct ggsn_ctx *ggsn;
+       int rc;
 
        /* Handle keyboard interrupt SIGINT */
-       struct sigaction s;
-       s.sa_handler = (void *)signal_handler;
-       if ((0 != sigemptyset(&s.sa_mask)) && debug)
-               printf("sigemptyset failed.\n");
-       s.sa_flags = SA_RESETHAND;
-       if ((sigaction(SIGINT, &s, NULL) != 0) && debug)
-               printf("Could not register SIGINT signal handler.\n");
+       tall_ggsn_ctx = talloc_named_const(NULL, 0, "openggsn");
+       msgb_talloc_ctx_init(tall_ggsn_ctx, 0);
 
-       fd_set fds;             /* For select() */
-       struct timeval idleTime;        /* How long to select() */
+       signal(SIGINT, &signal_handler);
+       signal(SIGABRT, &signal_handler);
+       signal(SIGUSR1, &signal_handler);
+       signal(SIGUSR2, &signal_handler);
 
-       int timelimit;          /* Number of seconds to be connected */
-       int starttime;          /* Time program was started */
-
+       osmo_init_ignore_signals();
        osmo_init_logging(&log_info);
+       osmo_stats_init(tall_ggsn_ctx);
 
-       if (cmdline_parser(argc, argv, &args_info) != 0)
-               exit(1);
-       if (args_info.debug_flag) {
-               printf("listen: %s\n", args_info.listen_arg);
-               if (args_info.conf_arg)
-                       printf("conf: %s\n", args_info.conf_arg);
-               printf("fg: %d\n", args_info.fg_flag);
-               printf("debug: %d\n", args_info.debug_flag);
-               printf("qos: %#08x\n", args_info.qos_arg);
-               if (args_info.apn_arg)
-                       printf("apn: %s\n", args_info.apn_arg);
-               if (args_info.net_arg)
-                       printf("net: %s\n", args_info.net_arg);
-               if (args_info.dynip_arg)
-                       printf("dynip: %s\n", args_info.dynip_arg);
-               if (args_info.statip_arg)
-                       printf("statip: %s\n", args_info.statip_arg);
-               if (args_info.ipup_arg)
-                       printf("ipup: %s\n", args_info.ipup_arg);
-               if (args_info.ipdown_arg)
-                       printf("ipdown: %s\n", args_info.ipdown_arg);
-               if (args_info.pidfile_arg)
-                       printf("pidfile: %s\n", args_info.pidfile_arg);
-               if (args_info.statedir_arg)
-                       printf("statedir: %s\n", args_info.statedir_arg);
-               if (args_info.gtp_linux_flag)
-                       printf("gtp_linux: %d\n", args_info.gtp_linux_flag);
-               printf("timelimit: %d\n", args_info.timelimit_arg);
+       vty_init(&g_vty_info);
+       logging_vty_add_cmds(NULL);
+       osmo_stats_vty_add_cmds(&log_info);
+       ggsn_vty_init();
+       ctrl_vty_init(tall_ggsn_ctx);
+
+       handle_options(argc, argv);
+
+       rate_ctr_init(tall_ggsn_ctx);
+
+       rc = vty_read_config_file(config_file, NULL);
+       if (rc < 0) {
+               fprintf(stderr, "Failed to open config file: '%s'\n", 
config_file);
+               exit(2);
        }
 
-       /* Try out our new parser */
-
-       if (cmdline_parser_configfile(args_info.conf_arg, &args_info, 0, 0, 0)
-           != 0)
+       rc = telnet_init_dynif(tall_ggsn_ctx, NULL, vty_get_bind_addr(), 
OSMO_VTY_PORT_GGSN);
+       if (rc < 0)
                exit(1);
 
-       /* Open a log file */
-       if (args_info.logfile_arg) {
-               struct log_target *tgt;
-               int lvl;
-
-               tgt = log_target_find(LOG_TGT_TYPE_FILE, args_info.logfile_arg);
-               if (!tgt) {
-                       tgt = log_target_create_file(args_info.logfile_arg);
-                       if (!tgt) {
-                               LOGP(DGGSN, LOGL_ERROR,
-                                       "Failed to create logfile: %s\n",
-                                       args_info.logfile_arg);
-                               exit(1);
-                       }
-                       log_add_target(tgt);
-               }
-               log_set_all_filter(tgt, 1);
-               log_set_use_color(tgt, 0);
-
-               if (args_info.loglevel_arg) {
-                       lvl = log_parse_level(args_info.loglevel_arg);
-                       log_set_log_level(tgt, lvl);
-                       LOGP(DGGSN, LOGL_NOTICE,
-                               "Set file log level to %s\n",
-                               log_level_str(lvl));
-               }
-       }
-
-       if (args_info.debug_flag) {
-               printf("cmdline_parser_configfile\n");
-               printf("listen: %s\n", args_info.listen_arg);
-               printf("conf: %s\n", args_info.conf_arg);
-               printf("fg: %d\n", args_info.fg_flag);
-               printf("debug: %d\n", args_info.debug_flag);
-               printf("qos: %#08x\n", args_info.qos_arg);
-               if (args_info.apn_arg)
-                       printf("apn: %s\n", args_info.apn_arg);
-               if (args_info.net_arg)
-                       printf("net: %s\n", args_info.net_arg);
-               if (args_info.dynip_arg)
-                       printf("dynip: %s\n", args_info.dynip_arg);
-               if (args_info.statip_arg)
-                       printf("statip: %s\n", args_info.statip_arg);
-               if (args_info.ipup_arg)
-                       printf("ipup: %s\n", args_info.ipup_arg);
-               if (args_info.ipdown_arg)
-                       printf("ipdown: %s\n", args_info.ipdown_arg);
-               if (args_info.pidfile_arg)
-                       printf("pidfile: %s\n", args_info.pidfile_arg);
-               if (args_info.statedir_arg)
-                       printf("statedir: %s\n", args_info.statedir_arg);
-               if (args_info.gtp_linux_flag)
-                       printf("gtp-linux: %d\n", args_info.gtp_linux_flag);
-               printf("timelimit: %d\n", args_info.timelimit_arg);
-       }
-
-       /* Handle each option */
-
-       /* debug                                                        */
-       debug = args_info.debug_flag;
-
-       /* listen                                                       */
-       /* Do hostname lookup to translate hostname to IP address       */
-       /* Any port listening is not possible as a valid address is     */
-       /* required for create_pdp_context_response messages            */
-       if (args_info.listen_arg) {
-               if (!(host = gethostbyname(args_info.listen_arg))) {
-                       SYS_ERR(DGGSN, LOGL_ERROR, 0,
-                               "Invalid listening address: %s!",
-                               args_info.listen_arg);
-                       exit(1);
-               } else {
-                       memcpy(&listen_.s_addr, host->h_addr, host->h_length);
-               }
-       } else {
-               SYS_ERR(DGGSN, LOGL_ERROR, 0,
-                       "Listening address must be specified! "
-                       "Please use command line option --listen or "
-                       "edit %s configuration file\n", args_info.conf_arg);
+       g_ctrlh = ctrl_interface_setup(NULL, OSMO_CTRL_PORT_GGSN, NULL);
+       if (!g_ctrlh) {
+               LOGP(DGGSN, LOGL_ERROR, "Failed to create CTRL interface.\n");
                exit(1);
        }
 
-       /* net                                                          */
-       /* Store net as in_addr net and mask                            */
-       if (args_info.net_arg) {
-               if (ippool_aton(&net, &prefixlen, args_info.net_arg, 0)) {
-                       SYS_ERR(DGGSN, LOGL_ERROR, 0,
-                               "Invalid network address: %s!",
-                               args_info.net_arg);
-                       exit(1);
-               }
-               /* default for network + destination address = net + 1 */
-               netaddr = net;
-               in46a_inc(&netaddr);
-               destaddr = netaddr;
-       } else {
-               SYS_ERR(DGGSN, LOGL_ERROR, 0,
-                       "Network address must be specified: %s!",
-                       args_info.net_arg);
-               exit(1);
-       }
-
-       /* dynip                                                        */
-       struct in46_prefix i46p;
-       size_t prefixlen;
-       if (!args_info.dynip_arg) {
-               if (ippool_aton(&i46p.addr, &prefixlen, args_info.net_arg, 0)) {
-                       SYS_ERR(DIP, LOGL_ERROR, 0, "Failed to parse dynamic 
pool");
-                       exit(1);
-               }
-       } else {
-               if (ippool_aton(&i46p.addr, &prefixlen, args_info.dynip_arg, 
0)) {
-                       SYS_ERR(DIP, LOGL_ERROR, 0, "Failed to parse dynamic 
pool");
-                       exit(1);
-               }
-       }
-       i46p.prefixlen = prefixlen;
-       if (ippool_new(&ippool, &i46p, NULL, IPPOOL_NONETWORK | 
IPPOOL_NOGATEWAY | IPPOOL_NOBROADCAST)) {
-               SYS_ERR(DGGSN, LOGL_ERROR, 0, "Failed to allocate IP pool!");
-               exit(1);
-       }
-
-       /* DNS1 and DNS2 */
-       memset(&dns1, 0, sizeof(dns1));
-       if (args_info.pcodns1_arg) {
-               size_t tmp;
-               if (ippool_aton(&dns1, &tmp, args_info.pcodns1_arg, 0) != 0) {
-                       SYS_ERR(DGGSN, LOGL_ERROR, 0,
-                               "Failed to convert pcodns1!");
-                       exit(1);
-               }
-       }
-       memset(&dns2, 0, sizeof(dns2));
-       if (args_info.pcodns2_arg) {
-               size_t tmp;
-               if (ippool_aton(&dns2, &tmp, args_info.pcodns2_arg, 0) != 0) {
-                       SYS_ERR(DGGSN, LOGL_ERROR, 0,
-                               "Failed to convert pcodns2!");
+       if (daemonize) {
+               rc = osmo_daemonize();
+               if (rc < 0) {
+                       perror("Error during daemonize");
                        exit(1);
                }
        }
 
-       unsigned int cur = 0;
-       pco.v[cur++] = 0x80;    /* x0000yyy x=1, yyy=000: PPP */
-       pco.v[cur++] = 0x80;    /* IPCP */
-       pco.v[cur++] = 0x21;
-       pco.v[cur++] = 0xFF;    /* Length of contents */
-       pco.v[cur++] = 0x02;    /* ACK */
-       pco.v[cur++] = 0x00;    /* ID: Need to match request */
-       pco.v[cur++] = 0x00;    /* Length */
-       pco.v[cur++] = 0xFF;    /* overwritten  */
-       if (dns1.len == 4) {
-               pco.v[cur++] = 0x81;    /* DNS 1 */
-               pco.v[cur++] = 2 + dns1.len;
-               if (dns1.len == 4)
-                       memcpy(&pco.v[cur], &dns1.v4, dns1.len);
-               else
-                       memcpy(&pco.v[cur], &dns1.v6, dns1.len);
-               cur += dns1.len;
-       }
-       if (dns2.len == 4) {
-               pco.v[cur++] = 0x83;
-               pco.v[cur++] = 2 + dns2.len;    /* DNS 2 */
-               if (dns2.len == 4)
-                       memcpy(&pco.v[cur], &dns2.v4, dns2.len);
-               else
-                       memcpy(&pco.v[cur], &dns2.v6, dns2.len);
-               cur += dns2.len;
-       }
-       pco.l = cur;
-       /* patch in length values */
-       pco.v[3] = pco.l - 4;
-       pco.v[7] = pco.l - 4;
-
-       /* ipup */
-       ipup = args_info.ipup_arg;
-
-       /* ipdown */
-       ipdown = args_info.ipdown_arg;
-
-       /* Timelimit                                                       */
-       timelimit = args_info.timelimit_arg;
-       starttime = time(NULL);
-
+#if 0
        /* qos                                                             */
        qos.l = 3;
        qos.v[2] = (args_info.qos_arg) & 0xff;
        qos.v[1] = ((args_info.qos_arg) >> 8) & 0xff;
        qos.v[0] = ((args_info.qos_arg) >> 16) & 0xff;
+#endif
 
-       /* apn                                                             */
-       if (strlen(args_info.apn_arg) > (sizeof(apn.v) - 1)) {
-               LOGP(DGGSN, LOGL_ERROR, "Invalid APN\n");
-               return -1;
-       }
-       apn.l = strlen(args_info.apn_arg) + 1;
-       apn.v[0] = (char)strlen(args_info.apn_arg);
-       strncpy((char *)&apn.v[1], args_info.apn_arg, sizeof(apn.v) - 1);
-
-       /* foreground                                                   */
-       /* If flag not given run as a daemon                            */
-       if (!args_info.fg_flag) {
-               FILE *f;
-               int rc;
-               /* Close the standard file descriptors. */
-               /* Is this really needed ? */
-               f = freopen("/dev/null", "w", stdout);
-               if (f == NULL) {
-                       SYS_ERR(DGGSN, LOGL_NOTICE, 0,
-                               "Could not redirect stdout to /dev/null");
-               }
-               f = freopen("/dev/null", "w", stderr);
-               if (f == NULL) {
-                       SYS_ERR(DGGSN, LOGL_NOTICE, 0,
-                               "Could not redirect stderr to /dev/null");
-               }
-               f = freopen("/dev/null", "r", stdin);
-               if (f == NULL) {
-                       SYS_ERR(DGGSN, LOGL_NOTICE, 0,
-                               "Could not redirect stdin to /dev/null");
-               }
-               rc = daemon(0, 0);
-               if (rc != 0) {
-                       SYS_ERR(DGGSN, LOGL_ERROR, rc,
-                               "Could not daemonize");
-                       exit(1);
-               }
+       /* Main select loop */
+       while (!end) {
+               osmo_select_main(0);
        }
 
-       /* pidfile */
-       /* This has to be done after we have our final pid */
-       if (args_info.pidfile_arg) {
-               log_pid(args_info.pidfile_arg);
-       }
-
-       DEBUGP(DGGSN, "gtpclient: Initialising GTP tunnel\n");
-
-       if (gtp_new(&gsn, args_info.statedir_arg, &listen_, GTP_MODE_GGSN)) {
-               SYS_ERR(DGGSN, LOGL_ERROR, 0, "Failed to create gtp");
-               exit(1);
-       }
-       if (gsn->fd0 > maxfd)
-               maxfd = gsn->fd0;
-       if (gsn->fd1c > maxfd)
-               maxfd = gsn->fd1c;
-       if (gsn->fd1u > maxfd)
-               maxfd = gsn->fd1u;
-
-       /* use GTP kernel module for data packet encapsulation */
-       if (args_info.gtp_linux_given) {
-               if (gtp_kernel_init(gsn, &net.v4, prefixlen, args_info.net_arg) 
< 0) {
-                       SYS_ERR(DGGSN, LOGL_ERROR, 0, "Failed to initialize 
kernel GTP\n");
-                       goto err;
-               }
-       }
-
-       gtp_set_cb_data_ind(gsn, encaps_tun);
-       gtp_set_cb_delete_context(gsn, delete_context);
-       gtp_set_cb_create_context_ind(gsn, create_context_ind);
-
-       gsn->ctrl = ctrl_interface_setup(NULL, OSMO_CTRL_PORT_GGSN, NULL);
-       if (!gsn->ctrl) {
-               LOGP(DGGSN, LOGL_ERROR, "Failed to create CTRL interface.\n");
-               exit(1);
-       }
-
-       /* skip the configuration of the tun0 if we're using the gtp0 device */
-       if (gtp_kernel_enabled())
-               goto skip_tun;
-
-       /* Create a tunnel interface */
-       DEBUGP(DGGSN, "Creating tun interface\n");
-       if (tun_new((struct tun_t **)&tun)) {
-               SYS_ERR(DGGSN, LOGL_ERROR, 0, "Failed to create tun");
-               exit(1);
-       }
-
-       DEBUGP(DGGSN, "Setting tun IP address\n");
-       if (tun_setaddr(tun, &netaddr, &destaddr, prefixlen)) {
-               SYS_ERR(DGGSN, LOGL_ERROR, 0, "Failed to set tun IP address");
-               exit(1);
-       }
-
-       tun_set_cb_ind(tun, cb_tun_ind);
-       if (tun->fd > maxfd)
-               maxfd = tun->fd;
-
-       if (ipup)
-               tun_runscript(tun, ipup);
-
-skip_tun:
-
-  /******************************************************************/
-       /* Main select loop                                               */
-  /******************************************************************/
-
-       while ((((starttime + timelimit) > time(NULL)) || (0 == timelimit))
-              && (!end)) {
-
-               FD_ZERO(&fds);
-               if (tun)
-                       FD_SET(tun->fd, &fds);
-               FD_SET(gsn->fd0, &fds);
-               FD_SET(gsn->fd1c, &fds);
-               FD_SET(gsn->fd1u, &fds);
-
-               gtp_retranstimeout(gsn, &idleTime);
-               switch (select(maxfd + 1, &fds, NULL, NULL, &idleTime)) {
-               case -1:        /* errno == EINTR : unblocked signal */
-                       SYS_ERR(DGGSN, LOGL_ERROR, 0,
-                               "select() returned -1");
-                       /* On error, select returns without modifying fds */
-                       FD_ZERO(&fds);
-                       break;
-               case 0:
-                       /* printf("Select returned 0\n"); */
-                       gtp_retrans(gsn);       /* Only retransmit if nothing 
else */
-                       break;
-               default:
-                       break;
-               }
-
-               if (tun && tun->fd != -1 && FD_ISSET(tun->fd, &fds) &&
-                   tun_decaps(tun) < 0) {
-                       SYS_ERR(DGGSN, LOGL_ERROR, 0,
-                               "TUN read failed (fd)=(%d)", tun->fd);
-               }
-
-               if (FD_ISSET(gsn->fd0, &fds))
-                       gtp_decaps0(gsn);
-
-               if (FD_ISSET(gsn->fd1c, &fds))
-                       gtp_decaps1c(gsn);
-
-               if (FD_ISSET(gsn->fd1u, &fds))
-                       gtp_decaps1u(gsn);
-
-               osmo_select_main(1);
-       }
-err:
-       gtp_kernel_stop();
-       cmdline_parser_free(&args_info);
-       ippool_free(ippool);
-       gtp_free(gsn);
-       if (tun)
-               tun_free(tun);
+       llist_for_each_entry(ggsn, &g_ggsn_list, list)
+               ggsn_stop(ggsn);
 
        return 1;
-
 }
diff --git a/ggsn/ggsn.h b/ggsn/ggsn.h
new file mode 100644
index 0000000..bab6cf7
--- /dev/null
+++ b/ggsn/ggsn.h
@@ -0,0 +1,133 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+
+#include "../lib/tun.h"
+#include "../lib/ippool.h"
+#include "../lib/syserr.h"
+#include "../lib/in46_addr.h"
+#include "../gtp/gtp.h"
+
+#define APN_TYPE_IPv4  0x01    /* v4-only */
+#define APN_TYPE_IPv6  0x02    /* v6-only */
+#define APN_TYPE_IPv4v6        0x04    /* v4v6 dual-stack */
+
+struct ggsn_ctx;
+
+struct apn_ctx_ip {
+       struct {
+               struct in46_prefix ifconfig_prefix;
+               struct in46_prefix static_prefix;
+               struct in46_prefix dynamic_prefix;
+               /* v4 DNS server names */
+               struct in46_addr dns[2];
+       } cfg;
+
+       /* v4 address pool */
+       struct ippool_t *pool;
+};
+
+struct apn_name {
+       struct llist_head list;
+       char *name;
+};
+
+enum apn_gtpu_mode {
+       APN_GTPU_MODE_TUN = 0,          /* default */
+       APN_GTPU_MODE_KERNEL_GTP,
+};
+
+struct apn_ctx {
+       /* list of APNs inside GGSN */
+       struct llist_head list;
+       /* back-pointer to GGSN */
+       struct ggsn_ctx *ggsn;
+
+       bool started;
+
+       struct {
+               /* Primary name */
+               char *name;
+               /* Description string */
+               char *description;
+               /* List of secondary APN names */
+               struct llist_head name_list;
+               /* types supported address types on this APN */
+               uint32_t apn_type_mask;
+               /* GTP-U via TUN device or in Linux kernel */
+               enum apn_gtpu_mode gtpu_mode;
+               /* administratively shut-down (true) or not (false) */
+               bool shutdown;
+       } cfg;
+
+       /* corresponding tun device */
+       struct {
+               struct {
+                       /* name of the network device */
+                       char *dev_name;
+                       /* ip-up and ip-down script names/paths */
+                       char *ipup_script;
+                       char *ipdown_script;
+               } cfg;
+               struct tun_t *tun;
+               struct osmo_fd fd;
+       } tun;
+
+       struct apn_ctx_ip v4;
+       struct apn_ctx_ip v6;
+};
+
+struct ggsn_ctx {
+       /* global list of GGSNs */
+       struct llist_head list;
+
+       /* list of APNs in this GGSN */
+       struct llist_head apn_list;
+
+       bool started;
+
+       struct {
+               char *name;
+               /* Description string */
+               char *description;
+               /* an APN that shall be used as default for any non-matching 
APN */
+               struct apn_ctx *default_apn;
+               /* ADdress to which we listen for GTP */
+               struct in46_addr listen_addr;
+               /* directory for state file */
+               char *state_dir;
+               /* administratively shut-down (true) or not (false) */
+               bool shutdown;
+       } cfg;
+
+       /* The libgtp (G)GSN instance, i.e. what listens to GTP */
+       struct gsn_t *gsn;
+
+       /* osmo-fd for gsn */
+       struct osmo_fd gtp_fd0;
+       struct osmo_fd gtp_fd1c;
+       struct osmo_fd gtp_fd1u;
+
+       struct osmo_timer_list gtp_timer;
+};
+
+/* ggsn_vty.c */
+extern struct llist_head g_ggsn_list;
+extern struct vty_app_info g_vty_info;
+extern int ggsn_vty_init(void);
+struct ggsn_ctx *ggsn_find(const char *name);
+struct ggsn_ctx *ggsn_find_or_create(void *ctx, const char *name);
+struct apn_ctx *ggsn_find_apn(struct ggsn_ctx *ggsn, const char *name);
+struct apn_ctx *ggsn_find_or_create_apn(struct ggsn_ctx *ggsn, const char 
*name);
+
+/* ggsn.c */
+extern void *tall_ggsn_ctx;
+extern int ggsn_start(struct ggsn_ctx *ggsn);
+extern int ggsn_stop(struct ggsn_ctx *ggsn);
+extern int apn_start(struct apn_ctx *apn);
+extern int apn_stop(struct apn_ctx *apn, bool force);
diff --git a/ggsn/ggsn_vty.c b/ggsn/ggsn_vty.c
new file mode 100644
index 0000000..f23a298
--- /dev/null
+++ b/ggsn/ggsn_vty.c
@@ -0,0 +1,890 @@
+/*
+ * (C) 2017 by Harald Welte <[email protected]>
+ * 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_local_ip, cfg_ggsn_local_ip_cmd,
+       "gtp local-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_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, "  ip 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 local-ip %s%s", 
in46a_ntoa(&ggsn->cfg.listen_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_local_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 <[email protected]>\r\n"
+       "Copyright (C) 2012-2017 Holger Hans Peter Freyther 
<[email protected]>\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           = "OpenGGSN",
+       .version        = PACKAGE_VERSION,
+       .copyright      = ggsn_copyright,
+       .go_parent_cb   = ggsn_vty_go_parent,
+       .is_config_node = ggsn_vty_is_config_node,
+};
diff --git a/lib/tun.c b/lib/tun.c
index e8368e5..76ac379 100644
--- a/lib/tun.c
+++ b/lib/tun.c
@@ -583,7 +583,7 @@
        return tun_route(this, dst, gateway, mask, 1);
 }
 
-int tun_new(struct tun_t **tun)
+int tun_new(struct tun_t **tun, const char *dev_name)
 {
 
 #if defined(__linux__)
@@ -615,6 +615,8 @@
        /* Set device flags. For some weird reason this is also the method
           used to obtain the network interface name */
        memset(&ifr, 0, sizeof(ifr));
+       if (dev_name)
+               strcpy(ifr.ifr_name, dev_name);
        ifr.ifr_flags = IFF_TUN | IFF_NO_PI;    /* Tun device, no packet info */
        if (ioctl((*tun)->fd, TUNSETIFF, (void *)&ifr) < 0) {
                SYS_ERR(DTUN, LOGL_ERROR, errno, "ioctl() failed");
diff --git a/lib/tun.h b/lib/tun.h
index 67ef39d..50ac806 100644
--- a/lib/tun.h
+++ b/lib/tun.h
@@ -65,7 +65,7 @@
        void *priv;
 };
 
-extern int tun_new(struct tun_t **tun);
+extern int tun_new(struct tun_t **tun, const char *dev_name);
 extern int tun_free(struct tun_t *tun);
 extern int tun_decaps(struct tun_t *this);
 extern int tun_encaps(struct tun_t *tun, void *pack, unsigned len);
diff --git a/sgsnemu/sgsnemu.c b/sgsnemu/sgsnemu.c
index 1f0b3f9..ed1fb93 100644
--- a/sgsnemu/sgsnemu.c
+++ b/sgsnemu/sgsnemu.c
@@ -1461,7 +1461,7 @@
        if (options.createif) {
                printf("Setting up interface\n");
                /* Create a tunnel interface */
-               if (tun_new((struct tun_t **)&tun)) {
+               if (tun_new((struct tun_t **)&tun, NULL)) {
                        SYS_ERR(DSGSN, LOGL_ERROR, 0,
                                "Failed to create tun");
                        exit(1);

-- 
To view, visit https://gerrit.osmocom.org/3848
To unsubscribe, visit https://gerrit.osmocom.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I9613ca3436e77ea132c47f0096df7c5050d7e826
Gerrit-PatchSet: 1
Gerrit-Project: openggsn
Gerrit-Branch: master
Gerrit-Owner: Harald Welte <[email protected]>

Reply via email to