/*******************************************************************************
    OpenAirInterface
    Copyright(c) 1999 - 2014 Eurecom

    OpenAirInterface 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 3 of the License, or
    (at your option) any later version.


    OpenAirInterface 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 OpenAirInterface.The full GNU General Public License is
   included in this distribution in the file called "COPYING". If not,
   see <http://www.gnu.org/licenses/>.

  Contact Information
  OpenAirInterface Admin: openair_admin@eurecom.fr
  OpenAirInterface Tech : openair_tech@eurecom.fr
  OpenAirInterface Dev  : openair4g-devel@eurecom.fr

  Address      : Eurecom, Compus SophiaTech 450, route des chappes, 06451 Biot, France.

 *******************************************************************************/
/*! \file spgw_config.c
* \brief
* \author Lionel Gauthier
* \company Eurecom
* \email: lionel.gauthier@eurecom.fr
*/
#define SGW_LITE
#define SPGW_CONFIG_C

#include <string.h>
#include <libconfig.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>

#include "log.h"
#include "assertions.h"
#include "spgw_config.h"
#include "sgw_lite_defs.h"
#include "intertask_interface.h"

#ifdef LIBCONFIG_LONG
#define libconfig_int long
#else
#define libconfig_int int
#endif

#define NIPADDR(addr) \
        (uint8_t)(addr & 0x000000FF), \
        (uint8_t)((addr & 0x0000FF00) >> 8), \
        (uint8_t)((addr & 0x00FF0000) >> 16), \
        (uint8_t)((addr & 0xFF000000) >> 24)

#define HIPADDR(addr) \
        (uint8_t)((addr & 0xFF000000) >> 24),\
        (uint8_t)((addr & 0x00FF0000) >> 16),\
        (uint8_t)((addr & 0x0000FF00) >> 8), \
        (uint8_t)(addr & 0x000000FF)

#define NIP6ADDR(addr) \
        ntohs((addr)->s6_addr16[0]), \
        ntohs((addr)->s6_addr16[1]), \
        ntohs((addr)->s6_addr16[2]), \
        ntohs((addr)->s6_addr16[3]), \
        ntohs((addr)->s6_addr16[4]), \
        ntohs((addr)->s6_addr16[5]), \
        ntohs((addr)->s6_addr16[6]), \
        ntohs((addr)->s6_addr16[7])

#define IN6_ARE_ADDR_MASKED_EQUAL(a,b,m) \
           (((((__const uint32_t *) (a))[0] & (((__const uint32_t *) (m))[0])) == (((__const uint32_t *) (b))[0] & (((__const uint32_t *) (m))[0])))  \
         && ((((__const uint32_t *) (a))[1] & (((__const uint32_t *) (m))[1])) == (((__const uint32_t *) (b))[1] & (((__const uint32_t *) (m))[1])))  \
         && ((((__const uint32_t *) (a))[2] & (((__const uint32_t *) (m))[2])) == (((__const uint32_t *) (b))[2] & (((__const uint32_t *) (m))[2])))  \
         && ((((__const uint32_t *) (a))[3] & (((__const uint32_t *) (m))[3])) == (((__const uint32_t *) (b))[3] & (((__const uint32_t *) (m))[3]))))

void trim(char* srcP, int sizeP);
void sgw_ipv6_mask_in6_addr( struct in6_addr *addr6_pP, int maskP);


void
trim(
  char* srcP,
  int sizeP)
{
  if(srcP == NULL)
    return;

  const char* current = srcP;
  unsigned int i = 0;

  while((*current) != '\0' && (i < (sizeP-1))) {
    if((*current != ' ') && (*current != '\t')) {
      srcP[i++] = *current;
    }

    ++current;
  }

  srcP[i] = '\0';
}



void
sgw_ipv6_mask_in6_addr(
  struct in6_addr *addr6_pP,
  int maskP)
{
  int      addr8_idx;

  addr8_idx = maskP / 8;
  maskP     = maskP % 8;

  if (maskP > 0) {
    addr6_pP->s6_addr[addr8_idx] = addr6_pP->s6_addr[addr8_idx] & (0xFF << (8 - maskP));
    addr8_idx += 1;
  }

  while (addr8_idx < 16) {
    addr6_pP->s6_addr[addr8_idx++] = 0;
  }
}


int spgw_system(char *command_pP, spgw_system_abort_control_e abort_on_errorP, const char * const file_nameP, const int line_numberP)
{
  int ret = -1;

  if (command_pP) {
    SPGW_APP_INFO("system command: %s\n",command_pP);
    ret = system(command_pP);

    if (ret != 0) {
      SPGW_APP_ERROR("ERROR in system command %s: %d at %s:%u\n",
                     command_pP,ret, file_nameP, line_numberP);

      if (abort_on_errorP) {
        exit(-1); // may be not exit
      }
    }
  }

  return ret;
}

int spgw_config_process(spgw_config_t* config_pP)
{
  char              system_cmd[256];
  struct in_addr    inaddr;
  int               ret = 0;

  if (strncasecmp("tun",config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up, strlen("tun")) == 0) {
	config_pP->sgw_config.local_to_eNB = TRUE;
    if (snprintf(system_cmd, 256,
                 "ip link set %s down ;sync;openvpn --rmtun --dev %s;sync",
                 config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up,
                 config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up
                ) > 0) {
      ret += spgw_system(system_cmd, SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
    } else {
      SPGW_APP_ERROR("Del %s\n", config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up);
      ret = -1;
    }

    if (snprintf(system_cmd, 256,
                 "openvpn --mktun --dev %s;sync",
                 config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up) > 0) {
      ret += spgw_system(system_cmd, SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
    } else {
      SPGW_APP_ERROR("Create %s\n", config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up);
      ret = -1;
    }

    inaddr.s_addr = config_pP->sgw_config.ipv4.sgw_ipv4_address_for_S1u_S12_S4_up;

    if (snprintf(system_cmd, 256,
                 "ip -4 addr add %s/%d  dev %s;sync",
                 inet_ntoa(inaddr),
                 config_pP->sgw_config.ipv4.sgw_ip_netmask_for_S1u_S12_S4_up,
                 config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up) > 0) {
      ret += spgw_system(system_cmd, SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
    } else {
      SPGW_APP_ERROR("Set IPv4 address on %s\n", config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up);
      ret = -1;
    }

    if (snprintf(system_cmd, 256,
                 "sync;ifconfig  %s up;sync",
                 config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up) > 0) {
      ret += spgw_system(system_cmd, SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
    } else {
      SPGW_APP_ERROR("ifconfig up %s\n", config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up);
      ret = -1;
    }

    if (snprintf(system_cmd, 256,
                 "iptables -t filter -I INPUT -i lo -d %s --protocol sctp -j DROP",
                 inet_ntoa(inaddr)) > 0) {
      ret += spgw_system(system_cmd, SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
    } else {
      SPGW_APP_ERROR("Drop SCTP traffic on S1U\n");
      ret = -1;
    }

    if (snprintf(system_cmd, 256,
                 "iptables -t filter -I INPUT -i lo -s %s --protocol sctp -j DROP",
                 inet_ntoa(inaddr)) > 0) {
      ret += spgw_system(system_cmd, SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
    } else {
      SPGW_APP_ERROR("Drop SCTP traffic on S1U\n");
      ret = -1;
    }

    if (snprintf(system_cmd, 256,
                 "insmod $OPENAIR_TARGETS/bin/xt_GTPUAH.ko tunnel_local=1 gtpu_port=%u mtu=%u",
                 config_pP->sgw_config.sgw_udp_port_for_S1u_S12_S4_up,
                 config_pP->sgw_config.sgw_interface_mtu_for_S1u_S12_S4_up) > 0) {
      ret += spgw_system(system_cmd, SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
    } else {
      SPGW_APP_ERROR("GTPUAH kernel module\n");
      ret = -1;
    }
  } else {
	config_pP->sgw_config.local_to_eNB = FALSE;
    if (snprintf(system_cmd, 256,
                 "insmod $OPENAIR_TARGETS/bin/xt_GTPUAH.ko tunnel_local=0 gtpu_port=%u mtu=%u",
                 config_pP->sgw_config.sgw_udp_port_for_S1u_S12_S4_up,
                 config_pP->sgw_config.sgw_interface_mtu_for_S1u_S12_S4_up) > 0) {
      ret += spgw_system(system_cmd, SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
    } else {
      SPGW_APP_ERROR("GTPUAH kernel module\n");
      ret = -1;
    }
  }

  spgw_system("insmod $OPENAIR_TARGETS/bin/xt_GTPURH.ko", SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);

#if defined (ENABLE_USE_GTPU_IN_KERNEL)
  ret += spgw_system("echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects", SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
#endif

  if (snprintf(system_cmd, 256,
               "ip link set dev %s mtu %u",
               config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up,
               config_pP->sgw_config.sgw_interface_mtu_for_S1u_S12_S4_up) > 0) {
    SPGW_APP_INFO("Set S1U interface MTU: %s\n",system_cmd);
    ret += spgw_system(system_cmd, SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
  } else {
    SPGW_APP_ERROR("Set S1U interface MTU\n");
    ret = -1;
  }



  if (snprintf(system_cmd, 256,
               "ethtool -K %s tso off gso off gro off",
               config_pP->pgw_config.ipv4.pgw_interface_name_for_SGI) > 0) {
    SPGW_APP_INFO("Disable tcp segmentation offload, generic segmentation offload: %s\n",system_cmd);
    ret += spgw_system(system_cmd, SPGW_WARN_ON_ERROR, __FILE__, __LINE__);
  } else {
    SPGW_APP_ERROR("Disable tcp segmentation offload, generic segmentation offload\n");
    ret = -1;
  }



#if defined (ENABLE_USE_GTPU_IN_KERNEL)

  if (config_pP->sgw_config.local_to_eNB) {
	if (snprintf(system_cmd, 256,
	             "iptables -I OUTPUT -t mangle -m mark ! --mark 0 -j CONNMARK --save-mark") > 0) {
	  ret += spgw_system(system_cmd, SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
	} else {
	  SPGW_APP_ERROR("Save mark\n");
	  ret = -1;
	}
  } else {
    if (snprintf(system_cmd, 256,
               "iptables -I POSTROUTING -t mangle -o %s -m mark ! --mark 0   -j CONNMARK --save-mark",
               config_pP->pgw_config.ipv4.pgw_interface_name_for_SGI) > 0) {
      ret += spgw_system(system_cmd, SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
    } else {
      SPGW_APP_ERROR("Save mark\n");
      ret = -1;
    }
  }
  if (snprintf(system_cmd, 256,
	               "iptables -I INPUT -t mangle -i %s ! --protocol sctp   -j CONNMARK --restore-mark",
	               config_pP->pgw_config.ipv4.pgw_interface_name_for_SGI) > 0) {
    ret += spgw_system(system_cmd, SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
  } else {
	SPGW_APP_ERROR("Restore mark\n");
	ret = -1;
  }
  if (snprintf(system_cmd, 256,
               "iptables -I PREROUTING -t mangle -i %s ! --protocol sctp   -j CONNMARK --restore-mark",
               config_pP->pgw_config.ipv4.pgw_interface_name_for_SGI) > 0) {
    ret += spgw_system(system_cmd, SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
  } else {
    SPGW_APP_ERROR("Restore mark\n");
    ret = -1;
  }

#endif
  return ret;
}


int spgw_config_init(char* lib_config_file_name_pP, spgw_config_t* config_pP)
{

  config_t          cfg;
  config_setting_t *setting_sgw                          = NULL;
  char             *sgw_interface_name_for_S1u_S12_S4_up = NULL;
  char             *sgw_ipv4_address_for_S1u_S12_S4_up   = NULL;
  char             *sgw_interface_name_for_S5_S8_up      = NULL;
  char             *sgw_ipv4_address_for_S5_S8_up        = NULL;
  char             *sgw_interface_name_for_S11           = NULL;
  char             *sgw_ipv4_address_for_S11             = NULL;
  char             *sgw_drop_uplink_s1u_traffic          = NULL;
  char             *sgw_drop_downlink_s1u_traffic        = NULL;
  libconfig_int     sgw_interface_mtu_for_S1u_S12_S4_up  = 1500;
  libconfig_int     sgw_udp_port_for_S1u_S12_S4_up       = 2152;

  config_setting_t *setting_pgw                  = NULL;
  config_setting_t *subsetting                   = NULL;
  config_setting_t *sub2setting                  = NULL;
  char             *pgw_interface_name_for_S5_S8 = NULL;
  char             *pgw_ipv4_address_for_S5_S8   = NULL;
  char             *pgw_interface_name_for_SGI   = NULL;
  char             *pgw_ipv4_address_for_SGI     = NULL;
  char             *pgw_masquerade_SGI           = NULL;
  char             *pgw_default_dns_ipv4_address = NULL;
  char             *pgw_default_dns_sec_ipv4_address = NULL;

  char             *astring  = NULL;
  char             *atoken   = NULL;
  char             *atoken2  = NULL;
  char             *address  = NULL;
  char             *cidr     = NULL;
  char             *mask     = NULL;
  int               num      = 0;
  int               i        = 0;
  unsigned char     buf_in6_addr[sizeof(struct in6_addr)];
  struct in6_addr   addr6_start;
  struct in6_addr   addr6_mask;
  int               prefix_mask = 0;
  uint64_t          counter64   = 0;
  unsigned char     buf_in_addr[sizeof(struct in_addr)];
  struct in_addr    addr_start,in_addr_var;
  struct in_addr    addr_mask;
  pgw_lite_conf_ipv4_list_elm_t *ip4_ref = NULL;
  pgw_lite_conf_ipv6_list_elm_t *ip6_ref = NULL;
  char              system_cmd[256];
#if defined (ENABLE_USE_GTPU_IN_KERNEL)
  int               tun_id               = 21;
#endif

  memset((char*)config_pP, 0 , sizeof(spgw_config_t));
  STAILQ_INIT(&config_pP->pgw_config.pgw_lite_ipv4_pool_list);
  STAILQ_INIT(&config_pP->pgw_config.pgw_lite_ipv6_pool_list);

  config_init(&cfg);

  if(lib_config_file_name_pP != NULL) {
    /* Read the file. If there is an error, report it and exit. */
    if(! config_read_file(&cfg, lib_config_file_name_pP)) {
      SPGW_APP_ERROR("%s:%d - %s\n", lib_config_file_name_pP, config_error_line(&cfg), config_error_text(&cfg));
      config_destroy(&cfg);
      AssertFatal (1 == 0, "Failed to parse SP-GW configuration file %s!\n", lib_config_file_name_pP);
    }
  } else {
    SPGW_APP_ERROR("No SP-GW configuration file provided!\n");
    config_destroy(&cfg);
    AssertFatal (0, "No SP-GW configuration file provided!\n");
  }

  SPGW_APP_INFO("Parsing configuration file provided %s\n", lib_config_file_name_pP);

  setting_sgw = config_lookup(&cfg, SGW_CONFIG_STRING_SGW_CONFIG);

  if(setting_sgw != NULL) {
    subsetting = config_setting_get_member (setting_sgw, SGW_CONFIG_STRING_NETWORK_INTERFACES_CONFIG);

    if(subsetting != NULL) {
      if(  (
             config_setting_lookup_string( subsetting, SGW_CONFIG_STRING_SGW_INTERFACE_NAME_FOR_S1U_S12_S4_UP, (const char **)&sgw_interface_name_for_S1u_S12_S4_up)
             && config_setting_lookup_string( subsetting, SGW_CONFIG_STRING_SGW_IPV4_ADDRESS_FOR_S1U_S12_S4_UP,   (const char **)&sgw_ipv4_address_for_S1u_S12_S4_up)
             && config_setting_lookup_string( subsetting, SGW_CONFIG_STRING_SGW_INTERFACE_NAME_FOR_S5_S8_UP,      (const char **)&sgw_interface_name_for_S5_S8_up)
             && config_setting_lookup_string( subsetting, SGW_CONFIG_STRING_SGW_IPV4_ADDRESS_FOR_S5_S8_UP,        (const char **)&sgw_ipv4_address_for_S5_S8_up)
             && config_setting_lookup_string( subsetting, SGW_CONFIG_STRING_SGW_INTERFACE_NAME_FOR_S11,           (const char **)&sgw_interface_name_for_S11)
             && config_setting_lookup_string( subsetting, SGW_CONFIG_STRING_SGW_IPV4_ADDRESS_FOR_S11,             (const char **)&sgw_ipv4_address_for_S11)
           )
        ) {
        config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up = strdup(sgw_interface_name_for_S1u_S12_S4_up);
        cidr = strdup(sgw_ipv4_address_for_S1u_S12_S4_up);
        address = strtok(cidr, "/");
        mask    = strtok(NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO ( address, config_pP->sgw_config.ipv4.sgw_ipv4_address_for_S1u_S12_S4_up, "BAD IP ADDRESS FORMAT FOR S1u_S12_S4 !\n" )
        config_pP->sgw_config.ipv4.sgw_ip_netmask_for_S1u_S12_S4_up = atoi(mask);
        free(cidr);

        in_addr_var.s_addr = config_pP->sgw_config.ipv4.sgw_ipv4_address_for_S1u_S12_S4_up;
        SPGW_APP_INFO("Parsing configuration file found sgw_ipv4_address_for_S1u_S12_S4_up: %s/%d on %s\n",
                      inet_ntoa(in_addr_var),
                      config_pP->sgw_config.ipv4.sgw_ip_netmask_for_S1u_S12_S4_up,
                      config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up);


        config_pP->sgw_config.ipv4.sgw_interface_name_for_S5_S8_up = strdup(sgw_interface_name_for_S5_S8_up);
        cidr = strdup(sgw_ipv4_address_for_S5_S8_up);
        address = strtok(cidr, "/");
        mask    = strtok(NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO ( address, config_pP->sgw_config.ipv4.sgw_ipv4_address_for_S5_S8_up, "BAD IP ADDRESS FORMAT FOR S5_S8 !\n" )
        config_pP->sgw_config.ipv4.sgw_ip_netmask_for_S5_S8_up = atoi(mask);
        free(cidr);
        in_addr_var.s_addr = config_pP->sgw_config.ipv4.sgw_ipv4_address_for_S5_S8_up;
        SPGW_APP_INFO("Parsing configuration file found sgw_ipv4_address_for_S5_S8_up: %s/%d on %s\n",
                      inet_ntoa(in_addr_var),
                      config_pP->sgw_config.ipv4.sgw_ip_netmask_for_S5_S8_up,
                      config_pP->sgw_config.ipv4.sgw_interface_name_for_S5_S8_up);

        config_pP->sgw_config.ipv4.sgw_interface_name_for_S11 = strdup(sgw_interface_name_for_S11);
        cidr = strdup(sgw_ipv4_address_for_S11);
        address = strtok(cidr, "/");
        mask    = strtok(NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO ( address, config_pP->sgw_config.ipv4.sgw_ipv4_address_for_S11, "BAD IP ADDRESS FORMAT FOR S11 !\n" )
        config_pP->sgw_config.ipv4.sgw_ip_netmask_for_S11 = atoi(mask);
        free(cidr);
        in_addr_var.s_addr = config_pP->sgw_config.ipv4.sgw_ipv4_address_for_S11;
        SPGW_APP_INFO("Parsing configuration file found sgw_ipv4_address_for_S11: %s/%d on %s\n",
                      inet_ntoa(in_addr_var),
                      config_pP->sgw_config.ipv4.sgw_ip_netmask_for_S11,
                      config_pP->sgw_config.ipv4.sgw_interface_name_for_S11);
      }

      // optional
      if(config_setting_lookup_int(
            subsetting,
            SGW_CONFIG_STRING_SGW_INTERFACE_MTU_FOR_S1U_S12_S4_UP,
            &sgw_interface_mtu_for_S1u_S12_S4_up)
        ) {
        config_pP->sgw_config.sgw_interface_mtu_for_S1u_S12_S4_up = sgw_interface_mtu_for_S1u_S12_S4_up;
      } else {
        config_pP->sgw_config.sgw_interface_mtu_for_S1u_S12_S4_up = sgw_interface_mtu_for_S1u_S12_S4_up;
      }

      if(config_setting_lookup_int(
            subsetting,
            SGW_CONFIG_STRING_SGW_PORT_FOR_S1U_S12_S4_UP,
            &sgw_udp_port_for_S1u_S12_S4_up)
        ) {
        config_pP->sgw_config.sgw_udp_port_for_S1u_S12_S4_up = sgw_udp_port_for_S1u_S12_S4_up;
      } else {
        config_pP->sgw_config.sgw_udp_port_for_S1u_S12_S4_up = sgw_udp_port_for_S1u_S12_S4_up;
      }

    }

    if(  (
           config_setting_lookup_string( setting_sgw, SGW_CONFIG_STRING_SGW_DROP_UPLINK_S1U_TRAFFIC,
                                         (const char **)&sgw_drop_uplink_s1u_traffic)
           && config_setting_lookup_string( setting_sgw, SGW_CONFIG_STRING_SGW_DROP_DOWNLINK_S1U_TRAFFIC,
                                            (const char **)&sgw_drop_downlink_s1u_traffic)
         )
      ) {
      if (strcasecmp(sgw_drop_uplink_s1u_traffic, "yes") == 0) {
        config_pP->sgw_config.sgw_drop_uplink_traffic=1;
      } else {
        config_pP->sgw_config.sgw_drop_uplink_traffic=0;
      }

      if (strcasecmp(sgw_drop_downlink_s1u_traffic, "yes") == 0) {
        config_pP->sgw_config.sgw_drop_downlink_traffic=1;
      } else {
        config_pP->sgw_config.sgw_drop_downlink_traffic=0;
      }
    }
  }

  setting_pgw = config_lookup(&cfg, PGW_CONFIG_STRING_PGW_CONFIG);

  if(setting_pgw != NULL) {
    subsetting = config_setting_get_member (setting_pgw, PGW_CONFIG_STRING_NETWORK_INTERFACES_CONFIG);

    if(subsetting != NULL) {
      if(  (
             config_setting_lookup_string(subsetting,
                                          PGW_CONFIG_STRING_PGW_INTERFACE_NAME_FOR_S5_S8,
                                          (const char **)&pgw_interface_name_for_S5_S8)
             && config_setting_lookup_string(subsetting,
                                             PGW_CONFIG_STRING_PGW_IPV4_ADDRESS_FOR_S5_S8,
                                             (const char **)&pgw_ipv4_address_for_S5_S8)
             && config_setting_lookup_string(subsetting,
                                             PGW_CONFIG_STRING_PGW_INTERFACE_NAME_FOR_SGI,
                                             (const char **)&pgw_interface_name_for_SGI)
             && config_setting_lookup_string(subsetting,
                                             PGW_CONFIG_STRING_PGW_IPV4_ADDR_FOR_SGI,
                                             (const char **)&pgw_ipv4_address_for_SGI)
             &&  config_setting_lookup_string(subsetting,
                                              PGW_CONFIG_STRING_PGW_MASQUERADE_SGI,
                                              (const char **)&pgw_masquerade_SGI)
           )
        ) {
        config_pP->pgw_config.ipv4.pgw_interface_name_for_S5_S8 = strdup(pgw_interface_name_for_S5_S8);
        cidr = strdup(pgw_ipv4_address_for_S5_S8);
        address = strtok(cidr, "/");
        mask    = strtok(NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO ( address, config_pP->pgw_config.ipv4.pgw_ipv4_address_for_S5_S8, "BAD IP ADDRESS FORMAT FOR S5_S8 !\n" )
        config_pP->pgw_config.ipv4.pgw_ip_netmask_for_S5_S8 = atoi(mask);
        free(cidr);
        in_addr_var.s_addr = config_pP->pgw_config.ipv4.pgw_ipv4_address_for_S5_S8;
        SPGW_APP_INFO("Parsing configuration file found pgw_ipv4_address_for_S5_S8: %s/%d on %s\n",
                      inet_ntoa(in_addr_var),
                      config_pP->pgw_config.ipv4.pgw_ip_netmask_for_S5_S8,
                      config_pP->pgw_config.ipv4.pgw_interface_name_for_S5_S8);

        config_pP->pgw_config.ipv4.pgw_interface_name_for_SGI = strdup(pgw_interface_name_for_SGI);
        cidr = strdup(pgw_ipv4_address_for_SGI);
        address = strtok(cidr, "/");
        mask    = strtok(NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO ( address, config_pP->pgw_config.ipv4.pgw_ipv4_address_for_SGI, "BAD IP ADDRESS FORMAT FOR SGI !\n" )
        config_pP->pgw_config.ipv4.pgw_ip_netmask_for_SGI = atoi(mask);
        free(cidr);
        in_addr_var.s_addr = config_pP->pgw_config.ipv4.pgw_ipv4_address_for_SGI;
        SPGW_APP_INFO("Parsing configuration file found pgw_ipv4_address_for_SGI: %s/%d on %s\n",
                      inet_ntoa(in_addr_var),
                      config_pP->pgw_config.ipv4.pgw_ip_netmask_for_SGI,
                      config_pP->pgw_config.ipv4.pgw_interface_name_for_SGI);

        if (strcasecmp(pgw_masquerade_SGI, "yes") == 0) {
          config_pP->pgw_config.pgw_masquerade_SGI=1;
        } else {
          config_pP->pgw_config.pgw_masquerade_SGI=0;
          SPGW_APP_INFO("No masquerading for SGI\n");
        }
      } else {
        SPGW_APP_WARN("CONFIG P-GW / NETWORK INTERFACES parsing failed\n");
      }
    } else {
      SPGW_APP_WARN("CONFIG P-GW / NETWORK INTERFACES not found\n");
    }

    //!!!------------------------------------!!!
    spgw_config_process(config_pP);
    //!!!------------------------------------!!!

    subsetting = config_setting_get_member (setting_pgw, PGW_CONFIG_STRING_IP_ADDRESS_POOL);

    if(subsetting != NULL) {
      sub2setting = config_setting_get_member (subsetting, PGW_CONFIG_STRING_IPV4_ADDRESS_LIST);

      if(sub2setting != NULL) {
        num     = config_setting_length(sub2setting);

        for (i = 0; i < num; i++) {
          astring = config_setting_get_string_elem(sub2setting,i);

          if (astring != NULL) {
            trim(astring, strlen(astring)+1);
            // failure, test if there is a range specified in the string
            atoken = strtok(astring, PGW_CONFIG_STRING_IPV4_PREFIX_DELIMITER);

            if (inet_pton(AF_INET, atoken, buf_in_addr) == 1) {
              memcpy (&addr_start, buf_in_addr, sizeof(struct in_addr));
              // valid address
              atoken2 = strtok(NULL, PGW_CONFIG_STRING_IPV4_PREFIX_DELIMITER);
#if defined (ENABLE_USE_GTPU_IN_KERNEL)
              in_addr_var.s_addr = config_pP->sgw_config.ipv4.sgw_ipv4_address_for_S1u_S12_S4_up;

              //                          if (snprintf(system_cmd, 128, "ip route add %s/%s via %s dev %s",
              //                                       astring,
              //                                       atoken2,
              //                                       inet_ntoa(in_addr_var),
              //                                       config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up) > 0) {
              if (snprintf(system_cmd, 128, "ip route add %s/%s dev %s",
                           astring,
                           atoken2,
                           config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up) > 0) {
                spgw_system(system_cmd, SPGW_WARN_ON_ERROR, __FILE__, __LINE__);
              } else {
                SPGW_APP_ERROR("Add route: for %s\n", astring);
              }

#endif
              prefix_mask = atoi(atoken2);

              if ((prefix_mask >= 2)&&(prefix_mask < 32)) {
                memcpy (&addr_start, buf_in_addr, sizeof(struct in_addr));
                memcpy (&addr_mask,  buf_in_addr, sizeof(struct in_addr));

                addr_mask.s_addr = addr_mask.s_addr & htonl(0xFFFFFFFF << (32 - prefix_mask));

                if (memcmp(&addr_start, &addr_mask, sizeof(struct in_addr)) != 0) {
                  AssertFatal(0, "BAD IPV4 ADDR CONFIG/MASK PAIRING %s/%d addr %X mask %X\n",
                              astring, prefix_mask, addr_start.s_addr, addr_mask.s_addr);
                }

                counter64 = 0x00000000FFFFFFFF >> prefix_mask; // address Prefix_mask/0..0 not valid
                counter64 = counter64 - 2;

                do {
                  addr_start.s_addr = addr_start.s_addr + htonl(2);
                  ip4_ref = calloc(1, sizeof(pgw_lite_conf_ipv4_list_elm_t));
                  ip4_ref->addr       = addr_start;

                  STAILQ_INSERT_TAIL(&config_pP->pgw_config.pgw_lite_ipv4_pool_list, ip4_ref, ipv4_entries);
                  counter64 = counter64 - 1;
                } while (counter64 > 0);

                //---------------
                if (config_pP->pgw_config.pgw_masquerade_SGI) {
                  in_addr_var.s_addr = config_pP->pgw_config.ipv4.pgw_ipv4_address_for_SGI;

                  if (snprintf(system_cmd, 256,
                               //"iptables -t nat -I POSTROUTING -s %s/%s -o %s  ! --protocol sctp -j SNAT --to-source %s",
                               "iptables -t nat -I POSTROUTING -s %s/%s  ! --protocol sctp -j SNAT --to-source %s",
                               astring,
                               atoken2,
                               //config_pP->pgw_config.ipv4.pgw_interface_name_for_SGI,
                               inet_ntoa(in_addr_var)) > 0) {
                    SPGW_APP_INFO("Masquerade SGI: %s\n",system_cmd);
                    spgw_system(system_cmd, SPGW_ABORT_ON_ERROR, __FILE__, __LINE__);
                  } else {
                    SPGW_APP_ERROR("Masquerade SGI\n");
                  }
                }
              } else {
                SPGW_APP_ERROR("CONFIG POOL ADDR IPV4: BAD MASQ: %s\n", atoken2);
              }
            }
          }
        }
      } else {
        SPGW_APP_WARN("CONFIG POOL ADDR IPV4: NO IPV4 ADDRESS FOUND\n");
      }

      sub2setting = config_setting_get_member (subsetting, PGW_CONFIG_STRING_IPV6_ADDRESS_LIST);

      if(sub2setting != NULL) {
        num     = config_setting_length(sub2setting);

        for (i = 0; i < num; i++) {
          astring = config_setting_get_string_elem(sub2setting,i);

          if (astring != NULL) {
            trim(astring, strlen(astring)+1);

            if (inet_pton(AF_INET6, astring, buf_in6_addr) < 1) {
              // failure, test if there is a range specified in the string
              atoken = strtok(astring, PGW_CONFIG_STRING_IPV6_PREFIX_DELIMITER);

              if (inet_pton(AF_INET6, astring, buf_in6_addr) == 1) {
                atoken2 = strtok(NULL, PGW_CONFIG_STRING_IPV6_PREFIX_DELIMITER);
                prefix_mask = atoi(atoken2);
                // arbitrary values
                DevAssert((prefix_mask < 128) && (prefix_mask >= 64));

                memcpy (&addr6_start, buf_in6_addr, sizeof(struct in6_addr));
                memcpy (&addr6_mask,  buf_in6_addr, sizeof(struct in6_addr));
                sgw_ipv6_mask_in6_addr(&addr6_mask, prefix_mask);

                if (memcmp(&addr6_start, &addr6_mask, sizeof(struct in6_addr)) != 0) {
                  AssertFatal(0, "BAD IPV6 ADDR CONFIG/MASK PAIRING %s/%d\n", astring, prefix_mask);
                }


                ip6_ref = calloc(1, sizeof(pgw_lite_conf_ipv6_list_elm_t));
                ip6_ref->addr       = addr6_start;
                ip6_ref->prefix_len = prefix_mask;
                STAILQ_INSERT_TAIL(&config_pP->pgw_config.pgw_lite_ipv6_pool_list, ip6_ref, ipv6_entries);

              }
            } else {
              SPGW_APP_WARN("CONFIG POOL ADDR IPV6: FAILED WHILE PARSING %s\n", astring);
            }
          }
        }
      }

      if(
        config_setting_lookup_string(subsetting,
                                     PGW_CONFIG_STRING_DEFAULT_DNS_IPV4_ADDRESS,
                                     (const char **)&pgw_default_dns_ipv4_address)
        && config_setting_lookup_string(subsetting,
                                        PGW_CONFIG_STRING_DEFAULT_DNS_SEC_IPV4_ADDRESS,
                                        (const char **)&pgw_default_dns_sec_ipv4_address)) {
        config_pP->pgw_config.ipv4.pgw_interface_name_for_S5_S8 = strdup(pgw_interface_name_for_S5_S8);
        IPV4_STR_ADDR_TO_INT_NWBO ( pgw_default_dns_ipv4_address,     config_pP->pgw_config.ipv4.default_dns_v4, "BAD IPv4 ADDRESS FORMAT FOR DEFAULT DNS !\n" )
        IPV4_STR_ADDR_TO_INT_NWBO ( pgw_default_dns_sec_ipv4_address, config_pP->pgw_config.ipv4.default_dns_sec_v4, "BAD IPv4 ADDRESS FORMAT FOR DEFAULT DNS SEC!\n" )
      } else {
        SPGW_APP_WARN("NO DNS CONFIGURATION FOUND\n");
      }
    }
  } else {
    SPGW_APP_WARN("CONFIG P-GW not found\n");
  }

  return 0;
}