aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TODO-RELEASE2
-rw-r--r--configure.ac2
-rw-r--r--doc/manuals/vty/ggsn_vty_reference.xml82
-rw-r--r--ggsn/Makefile.am2
-rw-r--r--ggsn/ggsn.c663
-rw-r--r--ggsn/ggsn.h35
-rw-r--r--ggsn/ggsn_main.c208
-rw-r--r--ggsn/ggsn_vty.c134
-rw-r--r--ggsn/pco.c252
-rw-r--r--ggsn/pco.h81
-rw-r--r--ggsn/sgsn.c160
-rw-r--r--ggsn/sgsn.h46
-rw-r--r--gtp/gtp.c268
-rw-r--r--gtp/gtp.h13
-rw-r--r--gtp/pdp.c17
-rw-r--r--gtp/pdp.h5
-rw-r--r--gtp/queue.c3
-rw-r--r--gtp/queue.h3
-rw-r--r--lib/Makefile.am4
-rw-r--r--lib/gtp-kernel.c17
-rw-r--r--lib/in46_addr.c6
-rw-r--r--lib/in46_addr.h8
-rw-r--r--lib/util.c35
-rw-r--r--lib/util.h18
-rw-r--r--sgsnemu/sgsnemu.c6
25 files changed, 1373 insertions, 697 deletions
diff --git a/TODO-RELEASE b/TODO-RELEASE
index d0852fc..73e3189 100644
--- a/TODO-RELEASE
+++ b/TODO-RELEASE
@@ -7,3 +7,5 @@
# If any interfaces have been added since the last public release: c:r:a + 1.
# If any interfaces have been removed or changed since the last public release: c:r:0.
#library what description / commit summary line
+libgtp queue.h struct qmsg_t got a new field: entry
+libgtp pdp.h struct pdp_t got a new field: qmsg_list_req
diff --git a/configure.ac b/configure.ac
index 4c39681..c8bfae9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -7,7 +7,7 @@ dnl *This* is the root dir, even if an install-sh exists in ../ or ../../
AC_CONFIG_AUX_DIR([.])
AC_CONFIG_TESTDIR(tests)
-AC_CANONICAL_TARGET
+AC_CANONICAL_HOST
dnl kernel style compile messages
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
diff --git a/doc/manuals/vty/ggsn_vty_reference.xml b/doc/manuals/vty/ggsn_vty_reference.xml
index a395b23..64bd07a 100644
--- a/doc/manuals/vty/ggsn_vty_reference.xml
+++ b/doc/manuals/vty/ggsn_vty_reference.xml
@@ -254,6 +254,41 @@
<param name='force-all' doc='Release any globally forced log level set with &apos;logging level force-all &lt;level&gt;&apos;' />
</params>
</command>
+ <command id='logp (ip|tun|ggsn|sgsn|icmp6|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal) .LOGMESSAGE'>
+ <params>
+ <param name='logp' doc='Print a message on all log outputs; useful for placing markers in test logs' />
+ <param name='ip' doc='IP Pool and other groups' />
+ <param name='tun' doc='Tunnel interface' />
+ <param name='ggsn' doc='GGSN' />
+ <param name='sgsn' doc='SGSN Emulator' />
+ <param name='icmp6' doc='ICMPv6' />
+ <param name='lglobal' doc='Library-internal global log family' />
+ <param name='llapd' doc='LAPD in libosmogsm' />
+ <param name='linp' doc='A-bis Intput Subsystem' />
+ <param name='lmux' doc='A-bis B-Subchannel TRAU Frame Multiplex' />
+ <param name='lmi' doc='A-bis Input Driver for Signalling' />
+ <param name='lmib' doc='A-bis Input Driver for B-Channels (voice)' />
+ <param name='lsms' doc='Layer3 Short Message Service (SMS)' />
+ <param name='lctrl' doc='Control Interface' />
+ <param name='lgtp' doc='GPRS GTP library' />
+ <param name='lstats' doc='Statistics messages and logging' />
+ <param name='lgsup' doc='Generic Subscriber Update Protocol' />
+ <param name='loap' doc='Osmocom Authentication Protocol' />
+ <param name='lss7' doc='libosmo-sigtran Signalling System 7' />
+ <param name='lsccp' doc='libosmo-sigtran SCCP Implementation' />
+ <param name='lsua' doc='libosmo-sigtran SCCP User Adaptation' />
+ <param name='lm3ua' doc='libosmo-sigtran MTP3 User Adaptation' />
+ <param name='lmgcp' doc='libosmo-mgcp Media Gateway Control Protocol' />
+ <param name='ljibuf' doc='libosmo-netif Jitter Buffer' />
+ <param name='lrspro' doc='Remote SIM protocol' />
+ <param name='debug' doc='Log debug messages and higher levels' />
+ <param name='info' doc='Log informational messages and higher levels' />
+ <param name='notice' doc='Log noticeable messages and higher levels' />
+ <param name='error' doc='Log error messages and higher levels' />
+ <param name='fatal' doc='Log only fatal messages' />
+ <param name='.LOGMESSAGE' doc='Arbitrary message to log on given category and log level' />
+ </params>
+ </command>
<command id='show logging vty'>
<params>
<param name='show' doc='Show running system information' />
@@ -614,6 +649,41 @@
<param name='force-all' doc='Release any globally forced log level set with &apos;logging level force-all &lt;level&gt;&apos;' />
</params>
</command>
+ <command id='logp (ip|tun|ggsn|sgsn|icmp6|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal) .LOGMESSAGE'>
+ <params>
+ <param name='logp' doc='Print a message on all log outputs; useful for placing markers in test logs' />
+ <param name='ip' doc='IP Pool and other groups' />
+ <param name='tun' doc='Tunnel interface' />
+ <param name='ggsn' doc='GGSN' />
+ <param name='sgsn' doc='SGSN Emulator' />
+ <param name='icmp6' doc='ICMPv6' />
+ <param name='lglobal' doc='Library-internal global log family' />
+ <param name='llapd' doc='LAPD in libosmogsm' />
+ <param name='linp' doc='A-bis Intput Subsystem' />
+ <param name='lmux' doc='A-bis B-Subchannel TRAU Frame Multiplex' />
+ <param name='lmi' doc='A-bis Input Driver for Signalling' />
+ <param name='lmib' doc='A-bis Input Driver for B-Channels (voice)' />
+ <param name='lsms' doc='Layer3 Short Message Service (SMS)' />
+ <param name='lctrl' doc='Control Interface' />
+ <param name='lgtp' doc='GPRS GTP library' />
+ <param name='lstats' doc='Statistics messages and logging' />
+ <param name='lgsup' doc='Generic Subscriber Update Protocol' />
+ <param name='loap' doc='Osmocom Authentication Protocol' />
+ <param name='lss7' doc='libosmo-sigtran Signalling System 7' />
+ <param name='lsccp' doc='libosmo-sigtran SCCP Implementation' />
+ <param name='lsua' doc='libosmo-sigtran SCCP User Adaptation' />
+ <param name='lm3ua' doc='libosmo-sigtran MTP3 User Adaptation' />
+ <param name='lmgcp' doc='libosmo-mgcp Media Gateway Control Protocol' />
+ <param name='ljibuf' doc='libosmo-netif Jitter Buffer' />
+ <param name='lrspro' doc='Remote SIM protocol' />
+ <param name='debug' doc='Log debug messages and higher levels' />
+ <param name='info' doc='Log informational messages and higher levels' />
+ <param name='notice' doc='Log noticeable messages and higher levels' />
+ <param name='error' doc='Log error messages and higher levels' />
+ <param name='fatal' doc='Log only fatal messages' />
+ <param name='.LOGMESSAGE' doc='Arbitrary message to log on given category and log level' />
+ </params>
+ </command>
<command id='show logging vty'>
<params>
<param name='show' doc='Show running system information' />
@@ -1310,6 +1380,18 @@
<param name='default-apn' doc='Remove default-APN to be used if no other APN matches' />
</params>
</command>
+ <command id='show sgsn'>
+ <params>
+ <param name='show' doc='Negate a command or set its defaults' />
+ <param name='sgsn' doc='Gateway GPRS Support NODE (GGSN)' />
+ </params>
+ </command>
+ <command id='echo-interval &lt;0-36000&gt;'>
+ <params>
+ <param name='echo-interval' doc='Gateway GPRS Support NODE (GGSN)' />
+ <param name='&lt;0-36000&gt;' doc='GGSN Number' />
+ </params>
+ </command>
</node>
<node id='config-ggsn-apn'>
<name>config-ggsn-apn</name>
diff --git a/ggsn/Makefile.am b/ggsn/Makefile.am
index eca385f..ca389f0 100644
--- a/ggsn/Makefile.am
+++ b/ggsn/Makefile.am
@@ -12,4 +12,4 @@ osmo_ggsn_LDADD += $(LIBGTPNL_LIBS)
endif
osmo_ggsn_DEPENDENCIES = ../gtp/libgtp.la ../lib/libmisc.a
-osmo_ggsn_SOURCES = ggsn_vty.c ggsn.c ggsn.h icmpv6.c icmpv6.h checksum.c checksum.h
+osmo_ggsn_SOURCES = ggsn_main.c ggsn_vty.c ggsn.c ggsn.h sgsn.c sgsn.h icmpv6.c icmpv6.h checksum.c checksum.h pco.c pco.h
diff --git a/ggsn/ggsn.c b/ggsn/ggsn.c
index 95371a2..c710984 100644
--- a/ggsn/ggsn.c
+++ b/ggsn/ggsn.c
@@ -42,22 +42,8 @@
#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/core/utils.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/vty/misc.h>
#include <osmocom/gsm/apn.h>
#include "../lib/tun.h"
@@ -65,31 +51,26 @@
#include "../lib/syserr.h"
#include "../lib/in46_addr.h"
#include "../lib/gtp-kernel.h"
+#include "../lib/util.h"
#include "../gtp/pdp.h"
#include "../gtp/gtp.h"
#include "icmpv6.h"
+#include "pco.h"
#include "ggsn.h"
-void *tall_ggsn_ctx;
-
-static int end = 0;
-static int daemonize = 0;
-static struct ctrl_handle *g_ctrlh;
-
-struct ul255_t qos;
-struct ul255_t apn;
-
-#define LOGPAPN(level, apn, fmt, args...) \
- LOGP(DGGSN, level, "APN(%s): " fmt, (apn)->cfg.name, ## args)
-
-#define LOGPGGSN(level, ggsn, fmt, args...) \
- LOGP(DGGSN, level, "GGSN(%s): " fmt, (ggsn)->cfg.name, ## args)
-
-#define LOGPPDP(level, pdp, fmt, args...) LOGPDPX(DGGSN, level, pdp, fmt, ## 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);
+void ggsn_close_one_pdp(struct pdp_t *pdp)
+{
+ LOGPPDP(LOGL_DEBUG, pdp, "Sending DELETE PDP CTX due to shutdown\n");
+ gtp_delete_context_req2(pdp->gsn, pdp, NULL, 1);
+ /* We have nothing more to do with pdp ctx, free it. Upon cb_delete_context
+ called during this call we'll clean up ggsn related stuff attached to this
+ pdp context. After this call, ippool member is cleared so
+ data is no longer valid and should not be accessed anymore. */
+ gtp_freepdp_teardown(pdp->gsn, pdp);
+}
static void pool_close_all_pdp(struct ippool_t *pool)
{
@@ -107,13 +88,7 @@ static void pool_close_all_pdp(struct ippool_t *pool)
pdp = member->peer;
if (!pdp)
continue;
- LOGPPDP(LOGL_DEBUG, pdp, "Sending DELETE PDP CTX due to shutdown\n");
- gtp_delete_context_req2(pdp->gsn, pdp, NULL, 1);
- /* We have nothing more to do with pdp ctx, free it. Upon cb_delete_context
- called during this call we'll clean up ggsn related stuff attached to this
- pdp context. After this call, ippool member is cleared so
- data is no longer valid and should not be accessed anymore. */
- gtp_freepdp_teardown(pdp->gsn, pdp);
+ ggsn_close_one_pdp(pdp);
}
}
@@ -371,7 +346,8 @@ static bool send_trap(const struct gsn_t *gsn, const struct pdp_t *pdp, const st
static int delete_context(struct pdp_t *pdp)
{
struct gsn_t *gsn = pdp->gsn;
- struct apn_ctx *apn = pdp->priv;
+ struct pdp_priv_t *pdp_priv = pdp->priv;
+ struct apn_ctx *apn;
struct ippoolm_t *member;
int i;
@@ -382,358 +358,74 @@ static int delete_context(struct pdp_t *pdp)
member = pdp->peer[i];
send_trap(gsn, pdp, member, "imsi-rem-ip"); /* TRAP with IP removal */
ippool_freeip(member->pool, member);
- } else if(i == 0)
+ } else if (i == 0) {
LOGPPDP(LOGL_ERROR, pdp, "Cannot find/free IP Pool member\n");
- }
-
- if (apn->cfg.gtpu_mode == APN_GTPU_MODE_KERNEL_GTP) {
- if (gtp_kernel_tunnel_del(pdp, apn->tun.cfg.dev_name)) {
- LOGPPDP(LOGL_ERROR, pdp, "Cannot delete tunnel from kernel:%s\n",
- strerror(errno));
}
}
- return 0;
-}
-
-#include <osmocom/gsm/tlv.h>
-
-/* RFC 1332 */
-enum ipcp_options {
- IPCP_OPT_IPADDR = 3,
- IPCP_OPT_PRIMARY_DNS = 129,
- IPCP_OPT_SECONDARY_DNS = 131,
-};
-
-struct ipcp_option_hdr {
- uint8_t type;
- uint8_t len;
- uint8_t data[0];
-} __attribute__ ((packed));
-
-struct ipcp_hdr {
- uint8_t code;
- uint8_t id;
- uint16_t len;
- uint8_t options[0];
-} __attribute__ ((packed));
-
-/* determine if IPCP contains given option */
-static const uint8_t *ipcp_contains_option(const struct ipcp_hdr *ipcp, size_t ipcp_len,
- enum ipcp_options opt, size_t opt_minlen)
-{
- const uint8_t *cur_opt = ipcp->options;
-
- /* iterate over Options and check if protocol contained */
- while (cur_opt + sizeof(struct ipcp_option_hdr) <= (uint8_t*)ipcp + ipcp_len) {
- const struct ipcp_option_hdr *cur_opt_hdr = (const struct ipcp_option_hdr *)cur_opt;
- /* length value includes 2 bytes type/length */
- if (cur_opt_hdr->len < sizeof(struct ipcp_option_hdr))
- return NULL;
- if (cur_opt_hdr->type == opt &&
- cur_opt_hdr->len >= sizeof(struct ipcp_option_hdr) + opt_minlen)
- return cur_opt;
- cur_opt += cur_opt_hdr->len;
- }
- return NULL;
-}
-
-/* 3GPP TS 24.008 10.6.5.3 */
-enum pco_protocols {
- PCO_P_LCP = 0xC021,
- PCO_P_PAP = 0xC023,
- PCO_P_CHAP = 0xC223,
- PCO_P_IPCP = 0x8021,
- PCO_P_PCSCF_ADDR = 0x0001,
- PCO_P_IM_CN_SS_F = 0x0002,
- PCO_P_DNS_IPv6_ADDR = 0x0003,
- PCO_P_POLICY_CTRL_REJ = 0x0004, /* only in Network->MS */
- PCO_P_MS_SUP_NETREQ_BCI = 0x0005,
- /* reserved */
- PCO_P_DSMIPv6_HA_ADDR = 0x0007,
- PCO_P_DSMIPv6_HN_PREF = 0x0008,
- PCO_P_DSMIPv6_v4_HA_ADDR= 0x0009,
- PCO_P_IP_ADDR_VIA_NAS = 0x000a, /* only MS->Network */
- PCO_P_IPv4_ADDR_VIA_DHCP= 0x000b, /* only MS->Netowrk */
- PCO_P_PCSCF_IPv4_ADDR = 0x000c,
- PCO_P_DNS_IPv4_ADDR = 0x000d,
- PCO_P_MSISDN = 0x000e,
- PCO_P_IFOM_SUPPORT = 0x000f,
- PCO_P_IPv4_LINK_MTU = 0x0010,
- PCO_P_MS_SUPP_LOC_A_TFT = 0x0011,
- PCO_P_PCSCF_RESEL_SUP = 0x0012, /* only MS->Network */
- PCO_P_NBIFOM_REQ = 0x0013,
- PCO_P_NBIFOM_MODE = 0x0014,
- PCO_P_NONIP_LINK_MTU = 0x0015,
- PCO_P_APN_RATE_CTRL_SUP = 0x0016,
- PCO_P_PS_DATA_OFF_UE = 0x0017,
- PCO_P_REL_DATA_SVC = 0x0018,
-};
-
-struct pco_element {
- uint16_t protocol_id; /* network byte order */
- uint8_t length; /* length of data below */
- uint8_t data[0];
-} __attribute__((packed));
-
-/*! Get the peer of pdp based on IP version used.
- * \param[in] pdp PDP context to select the peer from.
- * \param[in] v4v6 IP version to select. Valid values are 4 and 6.
- * \returns The selected peer matching the given IP version. NULL if not present.
- */
-static struct ippoolm_t *pdp_get_peer_ipv(struct pdp_t *pdp, bool is_ipv6) {
- uint8_t len1, len2, i;
-
- if (is_ipv6) {
- len1 = 8;
- len2 = 16;
- } else {
- len1 = sizeof(struct in_addr);
- len2 = len1;
+ if (!pdp_priv) {
+ LOGPPDP(LOGL_NOTICE, pdp, "Deleting PDP context: without private structure!\n");
+ return 0;
}
- for (i = 0; i < 2; i++) {
- struct ippoolm_t * ippool = pdp->peer[i];
- if (ippool && (ippool->addr.len == len1 || ippool->addr.len == len2))
- return ippool;
- }
- return NULL;
-}
-
-/* RFC 1334, section 3.2. Packet Format */
-struct pap_element {
- uint8_t code;
- uint8_t id;
- uint16_t len; /* length including header */
- uint8_t data[0];
-} __attribute__((packed));
-
-enum pap_code {
- PAP_CODE_AUTH_REQ = 1,
- PAP_CODE_AUTH_ACK = 2,
- PAP_CODE_AUTH_NAK = 3,
-};
+ /* Remove from SGSN */
+ sgsn_peer_remove_pdp_priv(pdp_priv);
-static const char *pap_welcome = "Welcome to OsmoGGSN " PACKAGE_VERSION;
-
-/* Handle PAP protocol according to RFC 1334 */
-static void process_pco_element_pap(const struct pco_element *pco_in, struct msgb *resp,
- const struct apn_ctx *apn, struct pdp_t *pdp)
-{
- const struct pap_element *pap_in = (const struct pap_element *) pco_in->data;
- uint16_t pap_in_len;
- uint8_t peer_id_len;
- const uint8_t *peer_id;
- unsigned int pap_welcome_len;
- uint8_t pap_out_size;
- struct pap_element *pap_out;
-
- if (pco_in->length < sizeof(struct pap_element))
- goto ret_broken;
-
- pap_in_len = osmo_load16be(&pap_in->len);
- if (pco_in->length < pap_in_len)
- goto ret_broken;
- /* "pco_in->length > pap_in_len" is allowed: RFC1334 2.2 states:
- "Octets outside the range of the Length field should be treated as
- Data Link Layer padding and should be ignored on reception."
- */
-
- switch (pap_in->code) {
- case PAP_CODE_AUTH_REQ:
- if (pap_in_len < sizeof(struct pap_element) + 1)
- goto ret_broken_auth;
- peer_id_len = pap_in->data[0];
- if (pap_in_len < sizeof(struct pap_element) + 1 + peer_id_len)
- goto ret_broken_auth;
- peer_id = &pap_in->data[1];
- LOGPPDP(LOGL_DEBUG, pdp, "PCO PAP PeerId = %s, ACKing\n",
- osmo_quote_str((const char *)peer_id, peer_id_len));
- /* Password-Length + Password following here, but we don't care */
-
- /* Prepare response, we ACK all of them: */
- pap_welcome_len = strlen(pap_welcome);
- /* +1: Length field of pap_welcome Message */
- pap_out_size = sizeof(struct pap_element) + 1 + pap_welcome_len;
- pap_out = alloca(pap_out_size);
- pap_out->code = PAP_CODE_AUTH_ACK;
- pap_out->id = pap_in->id;
- pap_out->len = htons(pap_out_size);
- pap_out->data[0] = pap_welcome_len;
- memcpy(pap_out->data+1, pap_welcome, pap_welcome_len);
- msgb_t16lv_put(resp, PCO_P_PAP, pap_out_size, (uint8_t *) pap_out);
- break;
- case PAP_CODE_AUTH_ACK:
- case PAP_CODE_AUTH_NAK:
- default:
- LOGPPDP(LOGL_NOTICE, pdp, "Unsupported PAP PCO Code %u, ignoring\n", pap_in->code);
- break;
+ apn = pdp_priv->apn;
+ if (apn && apn->cfg.gtpu_mode == APN_GTPU_MODE_KERNEL_GTP) {
+ if (gtp_kernel_tunnel_del(pdp, apn->tun.cfg.dev_name)) {
+ LOGPPDP(LOGL_ERROR, pdp, "Cannot delete tunnel from kernel:%s\n",
+ strerror(errno));
+ }
}
- return;
-ret_broken_auth:
- LOGPPDP(LOGL_NOTICE, pdp, "Invalid PAP AuthenticateReq: %s, ignoring\n",
- osmo_hexdump_nospc((const uint8_t *)pco_in, pco_in->length));
- return;
+ talloc_free(pdp_priv);
-ret_broken:
- LOGPPDP(LOGL_NOTICE, pdp, "Invalid PAP PCO Length: %s, ignoring\n",
- osmo_hexdump_nospc((const uint8_t *)pco_in, pco_in->length));
+ return 0;
}
-static void process_pco_element_ipcp(const struct pco_element *pco_elem, struct msgb *resp,
- const struct apn_ctx *apn, struct pdp_t *pdp)
+static bool apn_supports_ipv4(const struct apn_ctx *apn)
{
- struct ippoolm_t *peer_v4 = pdp_get_peer_ipv(pdp, false);
- const struct in46_addr *dns1 = &apn->v4.cfg.dns[0];
- const struct in46_addr *dns2 = &apn->v4.cfg.dns[1];
- uint8_t *start = resp->tail;
- const struct ipcp_hdr *ipcp;
- uint16_t ipcp_len;
- uint8_t *len1, *len2;
- unsigned int len_appended;
- ptrdiff_t consumed;
- size_t remain;
-
- if (!peer_v4) {
- LOGPPDP(LOGL_ERROR, pdp, "IPCP but no IPv4 type ?!?\n");
- return;
- }
-
- ipcp = (const struct ipcp_hdr *)pco_elem->data;
- consumed = (pco_elem->data - &pdp->pco_req.v[0]);
- remain = sizeof(pdp->pco_req.v) - consumed;
- ipcp_len = osmo_load16be(&ipcp->len);
- if (remain < 0 || remain < ipcp_len) {
- LOGPPDP(LOGL_ERROR, pdp, "Malformed IPCP, ignoring\n");
- return;
- }
-
- /* Three byte T16L header */
- msgb_put_u16(resp, 0x8021); /* IPCP */
- len1 = msgb_put(resp, 1); /* Length of contents: delay */
-
- msgb_put_u8(resp, 0x02); /* ACK */
- msgb_put_u8(resp, ipcp->id); /* ID: Needs to match request */
- msgb_put_u8(resp, 0x00); /* Length MSB */
- len2 = msgb_put(resp, 1); /* Length LSB: delay */
-
- if (dns1->len == 4 && ipcp_contains_option(ipcp, ipcp_len, IPCP_OPT_PRIMARY_DNS, 4)) {
- msgb_put_u8(resp, 0x81); /* DNS1 Tag */
- msgb_put_u8(resp, 2 + dns1->len); /* DNS1 Length, incl. TL */
- msgb_put_u32(resp, ntohl(dns1->v4.s_addr));
- }
-
- if (dns2->len == 4 && ipcp_contains_option(ipcp, ipcp_len, IPCP_OPT_SECONDARY_DNS, 4)) {
- msgb_put_u8(resp, 0x83); /* DNS2 Tag */
- msgb_put_u8(resp, 2 + dns2->len); /* DNS2 Length, incl. TL */
- msgb_put_u32(resp, ntohl(dns2->v4.s_addr));
- }
-
- /* patch in length values */
- len_appended = resp->tail - start;
- *len1 = len_appended - 3;
- *len2 = len_appended - 3;
+ if (apn->v4.cfg.static_prefix.addr.len || apn->v4.cfg.dynamic_prefix.addr.len)
+ return true;
+ return false;
}
-static void process_pco_element_dns_ipv6(const struct pco_element *pco_elem, struct msgb *resp,
- const struct apn_ctx *apn, struct pdp_t *pdp)
+static bool apn_supports_ipv6(const struct apn_ctx *apn)
{
- unsigned int i;
- const uint8_t *tail = resp->tail;
-
- for (i = 0; i < ARRAY_SIZE(apn->v6.cfg.dns); i++) {
- const struct in46_addr *i46a = &apn->v6.cfg.dns[i];
- if (i46a->len != 16)
- continue;
- msgb_t16lv_put(resp, PCO_P_DNS_IPv6_ADDR, i46a->len, i46a->v6.s6_addr);
- }
- if (resp->tail == tail)
- LOGPPDP(LOGL_NOTICE, pdp, "MS requested IPv6 DNS, but APN has none configured\n");
+ if (apn->v6.cfg.static_prefix.addr.len || apn->v6.cfg.dynamic_prefix.addr.len)
+ return true;
+ return false;
}
-static void process_pco_element_dns_ipv4(const struct pco_element *pco_elem, struct msgb *resp,
- const struct apn_ctx *apn, struct pdp_t *pdp)
+static struct sgsn_peer* ggsn_find_sgsn(struct ggsn_ctx *ggsn, struct in_addr *peer_addr)
{
- unsigned int i;
- const uint8_t *tail = resp->tail;
+ struct sgsn_peer *sgsn;
- for (i = 0; i < ARRAY_SIZE(apn->v4.cfg.dns); i++) {
- const struct in46_addr *i46a = &apn->v4.cfg.dns[i];
- if (i46a->len != 4)
- continue;
- msgb_t16lv_put(resp, PCO_P_DNS_IPv4_ADDR, i46a->len, (uint8_t *)&i46a->v4);
+ llist_for_each_entry(sgsn, &ggsn->sgsn_list, entry) {
+ if (memcmp(&sgsn->addr, peer_addr, sizeof(*peer_addr)) == 0)
+ return sgsn;
}
- if (resp->tail == tail)
- LOGPPDP(LOGL_NOTICE, pdp, "MS requested IPv4 DNS, but APN has none configured\n");
+ return NULL;
}
-static void process_pco_element(const struct pco_element *pco_elem, struct msgb *resp,
- const struct apn_ctx *apn, struct pdp_t *pdp)
+static struct sgsn_peer* ggsn_find_or_create_sgsn(struct ggsn_ctx *ggsn, struct pdp_t *pdp)
{
- uint16_t protocol_id = osmo_load16be(&pco_elem->protocol_id);
+ struct sgsn_peer *sgsn;
+ struct in_addr ia;
- LOGPPDP(LOGL_DEBUG, pdp, "PCO Protocol 0x%04x\n", protocol_id);
- switch (protocol_id) {
- case PCO_P_PAP:
- process_pco_element_pap(pco_elem, resp, apn, pdp);
- break;
- case PCO_P_IPCP:
- process_pco_element_ipcp(pco_elem, resp, apn, pdp);
- break;
- case PCO_P_DNS_IPv6_ADDR:
- process_pco_element_dns_ipv6(pco_elem, resp, apn, pdp);
- break;
- case PCO_P_DNS_IPv4_ADDR:
- process_pco_element_dns_ipv4(pco_elem, resp, apn, pdp);
- break;
- default:
- LOGPPDP(LOGL_INFO, pdp, "Unknown/Unimplemented PCO Protocol 0x%04x: %s\n",
- protocol_id, osmo_hexdump_nospc(pco_elem->data, pco_elem->length));
- break;
- }
-}
-
-/* process one PCO request from a MS/UE, putting together the proper responses */
-static void process_pco(const struct apn_ctx *apn, struct pdp_t *pdp)
-{
- struct msgb *resp = msgb_alloc(256, "PCO.resp");
- const struct ul255_t *pco = &pdp->pco_req;
- const struct pco_element *pco_elem;
- const uint8_t *cur;
-
- /* build the header of the PCO response */
- OSMO_ASSERT(resp);
- msgb_put_u8(resp, 0x80); /* ext-bit + configuration protocol byte */
-
- /* iterate over the PCO elements in the request; call process_pco_element() for each */
- for (cur = pco->v + 1, pco_elem = (const struct pco_element *) cur;
- cur + sizeof(struct pco_element) <= pco->v + pco->l;
- cur += pco_elem->length + sizeof(*pco_elem), pco_elem = (const struct pco_element *) cur) {
- process_pco_element(pco_elem, resp, apn, pdp);
+ if (gsna2in_addr(&ia, &pdp->gsnrc)) {
+ LOGPPDP(LOGL_ERROR, pdp, "Failed parsing gsnrc (len=%u) to discover SGSN\n",
+ pdp->gsnrc.l);
+ return NULL;
}
- /* copy the PCO response msgb and copy its contents over to the PDP context */
- if (msgb_length(resp) > 1) {
- memcpy(pdp->pco_neg.v, msgb_data(resp), msgb_length(resp));
- pdp->pco_neg.l = msgb_length(resp);
- } else
- pdp->pco_neg.l = 0;
- msgb_free(resp);
-}
-
-static bool apn_supports_ipv4(const struct apn_ctx *apn)
-{
- if (apn->v4.cfg.static_prefix.addr.len || apn->v4.cfg.dynamic_prefix.addr.len)
- return true;
- return false;
-}
+ if ((sgsn = ggsn_find_sgsn(ggsn, &ia)))
+ return sgsn;
-static bool apn_supports_ipv6(const struct apn_ctx *apn)
-{
- if (apn->v6.cfg.static_prefix.addr.len || apn->v6.cfg.dynamic_prefix.addr.len)
- return true;
- return false;
+ sgsn = sgsn_peer_allocate(ggsn, &ia, pdp->version);
+ llist_add(&sgsn->entry, &ggsn->sgsn_list);
+ return sgsn;
}
int create_context_ind(struct pdp_t *pdp)
@@ -747,6 +439,8 @@ int create_context_ind(struct pdp_t *pdp)
struct apn_ctx *apn = NULL;
int rc, num_addr, i;
char *apn_name;
+ struct sgsn_peer *sgsn;
+ struct pdp_priv_t *pdp_priv;
apn_name = 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",
@@ -798,7 +492,7 @@ int create_context_ind(struct pdp_t *pdp)
/* Allocate dynamic addresses from the pool */
for (i = 0; i < num_addr; i++) {
- if (addr[i].len == sizeof(struct in_addr)) {
+ if (in46a_is_v4(&addr[i])) {
/* does this APN actually have an IPv4 pool? */
if (!apn_supports_ipv4(apn))
goto err_wrong_af;
@@ -811,7 +505,7 @@ int create_context_ind(struct pdp_t *pdp)
addrv4 = member;
- } else if (addr[i].len == sizeof(struct in6_addr)) {
+ } else if (in46a_is_v6(&addr[i])) {
/* does this APN actually have an IPv6 pool? */
if (!apn_supports_ipv6(apn))
@@ -848,7 +542,14 @@ int create_context_ind(struct pdp_t *pdp)
}
pdp->ipif = apn->tun.tun; /* TODO */
- pdp->priv = apn;
+
+ pdp_priv = talloc_zero(ggsn, struct pdp_priv_t);
+ pdp->priv = pdp_priv;
+ pdp_priv->lib = pdp;
+ /* Create sgsn and assign pdp to it */
+ sgsn = ggsn_find_or_create_sgsn(ggsn, pdp);
+ sgsn_peer_add_pdp_priv(sgsn, pdp_priv);
+ pdp_priv->apn = apn;
/* TODO: change trap to send 2 IPs */
if (!send_trap(gsn, pdp, member, "imsi-ass-ip")) { /* TRAP with IP assignment */
@@ -912,7 +613,7 @@ static int cb_tun_ind(struct tun_t *tun, void *pack, unsigned len)
pool = apn->v6.pool;
break;
default:
- LOGP(DTUN, LOGL_NOTICE, "non-IPv%u packet received from tun\n", iph->version);
+ LOGTUN(LOGL_NOTICE, tun, "non-IPv%u packet received\n", iph->version);
return -1;
}
@@ -920,15 +621,15 @@ static int cb_tun_ind(struct tun_t *tun, void *pack, unsigned len)
if (!pool)
return 0;
- DEBUGP(DTUN, "Received packet for APN(%s) from tun %s", apn->cfg.name, tun->devname);
-
if (ippool_getip(pool, &ipm, &dst)) {
- DEBUGPC(DTUN, " with no PDP contex! (%s)\n", iph->version == 4 ?
+ LOGTUN(LOGL_DEBUG, tun, "Received packet for APN(%s) with no PDP contex! (%s)\n",
+ apn->cfg.name,
+ iph->version == 4 ?
inet_ntop(AF_INET, &iph->saddr, straddr, sizeof(straddr)) :
inet_ntop(AF_INET6, &ip6h->ip6_src, straddr, sizeof(straddr)));
return 0;
}
- DEBUGPC(DTUN, "\n");
+ LOGTUN(LOGL_DEBUG, tun, "Received packet for APN(%s)\n", apn->cfg.name);
if (ipm->peer) /* Check if a peer protocol is defined */
gtp_data_req(apn->ggsn->gsn, (struct pdp_t *)ipm->peer, pack, len);
@@ -1003,8 +704,6 @@ static int encaps_tun(struct pdp_t *pdp, void *pack, unsigned len)
return tun_encaps((struct tun_t *)pdp->ipif, pack, len);
}
-static char *config_file = "osmo-ggsn.cfg";
-
/* callback for tun device osmocom select loop integration */
static int ggsn_tun_fd_cb(struct osmo_fd *fd, unsigned int what)
{
@@ -1040,54 +739,10 @@ static int ggsn_gtp_fd_cb(struct osmo_fd *fd, unsigned int what)
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:
- case SIGTERM:
- 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;
- }
-}
-
/* libgtp callback for confirmations */
static int cb_conf(int type, int cause, struct pdp_t *pdp, void *cbp)
{
+ struct sgsn_peer *sgsn;
int rc = 0;
if (cause == EOF)
@@ -1098,20 +753,40 @@ static int cb_conf(int type, int cause, struct pdp_t *pdp, void *cbp)
case GTP_DELETE_PDP_REQ:
/* Remark: We actually never reach this path nowadays because
only place where we call gtp_delete_context_req2() is during
- apn_stop()->pool_close_all_pdp() path, and in that case we
- free all pdp contexts immediatelly without waiting for
- confirmation since we want to tear down the whole APN
- anyways. As a result, DeleteCtxResponse will never reach here
- since it will be dropped at some point in lower layers in the
- Rx path. This code is nevertheless left here in order to ease
- future developent and avoid possible future memleaks once more
- scenarios where GGSN sends a DeleteCtxRequest are introduced. */
- if (pdp)
- rc = pdp_freepdp(pdp);
+ ggsn_close_one_pdp() path, and in that case we free all pdp
+ contexts immediatelly without waiting for confirmation
+ (through gtp_freepdp_teardown()) since we want to tear down
+ the whole APN anyways. As a result, DeleteCtxResponse will
+ never reach here since it will be dropped at some point in
+ lower layers in the Rx path. This code is nevertheless left
+ here in order to ease future developent and avoid possible
+ future memleaks once more scenarios where GGSN sends a
+ DeleteCtxRequest are introduced. */
+ if (pdp)
+ rc = gtp_freepdp(pdp->gsn, pdp);
+ break;
+ case GTP_ECHO_REQ:
+ sgsn = (struct sgsn_peer *)cbp;
+ sgsn_peer_echo_resp(sgsn, cause == EOF);
+ break;
}
return rc;
}
+static int cb_recovery3(struct gsn_t *gsn, struct sockaddr_in *peer, struct pdp_t *pdp, uint8_t recovery)
+{
+ struct ggsn_ctx *ggsn = (struct ggsn_ctx *)gsn->priv;
+ struct sgsn_peer *sgsn;
+
+ sgsn = ggsn_find_sgsn(ggsn, &peer->sin_addr);
+ if (!sgsn) {
+ LOGPGGSN(LOGL_NOTICE, ggsn, "Received Recovery IE for unknown SGSN (no PDP contexts active)\n");
+ return -EINVAL;
+ }
+
+ return sgsn_peer_handle_recovery(sgsn, pdp, recovery);
+}
+
/* Start a given GGSN */
int ggsn_start(struct ggsn_ctx *ggsn)
{
@@ -1151,14 +826,11 @@ int ggsn_start(struct ggsn_ctx *ggsn)
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);
- ggsn_gtp_tmr_start(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);
gtp_set_cb_conf(ggsn->gsn, cb_conf);
+ gtp_set_cb_recovery3(ggsn->gsn, cb_recovery3);
LOGPGGSN(LOGL_NOTICE, ggsn, "Successfully started\n");
ggsn->started = true;
@@ -1181,8 +853,6 @@ int ggsn_stop(struct ggsn_ctx *ggsn)
llist_for_each_entry(apn, &ggsn->apn_list, list)
apn_stop(apn);
- osmo_timer_del(&ggsn->gtp_timer);
-
osmo_fd_unregister(&ggsn->gtp_fd1u);
osmo_fd_unregister(&ggsn->gtp_fd1c);
osmo_fd_unregister(&ggsn->gtp_fd0);
@@ -1195,128 +865,3 @@ int ggsn_stop(struct ggsn_ctx *ggsn)
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)
-{
- struct ggsn_ctx *ggsn;
- int rc;
-
- tall_ggsn_ctx = talloc_named_const(NULL, 0, "OsmoGGSN");
- msgb_talloc_ctx_init(tall_ggsn_ctx, 0);
- g_vty_info.tall_ctx = tall_ggsn_ctx;
-
- /* Handle keyboard interrupt SIGINT */
- signal(SIGINT, &signal_handler);
- signal(SIGTERM, &signal_handler);
- signal(SIGABRT, &signal_handler);
- signal(SIGUSR1, &signal_handler);
- signal(SIGUSR2, &signal_handler);
-
- osmo_init_ignore_signals();
- osmo_init_logging2(tall_ggsn_ctx, &log_info);
- osmo_stats_init(tall_ggsn_ctx);
-
- vty_init(&g_vty_info);
- logging_vty_add_cmds();
- osmo_talloc_vty_add_cmds();
- osmo_stats_vty_add_cmds();
- 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);
- }
-
- rc = telnet_init_dynif(tall_ggsn_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_GGSN);
- if (rc < 0)
- exit(1);
-
- g_ctrlh = ctrl_interface_setup_dynip(NULL, ctrl_vty_get_bind_addr(),
- OSMO_CTRL_PORT_GGSN, NULL);
- if (!g_ctrlh) {
- LOGP(DGGSN, LOGL_ERROR, "Failed to create CTRL interface.\n");
- exit(1);
- }
-
- if (daemonize) {
- rc = osmo_daemonize();
- if (rc < 0) {
- perror("Error during daemonize");
- exit(1);
- }
- }
-
-#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
-
- /* Main select loop */
- while (!end) {
- osmo_select_main(0);
- }
-
- llist_for_each_entry(ggsn, &g_ggsn_list, list)
- ggsn_stop(ggsn);
-
- return 0;
-}
diff --git a/ggsn/ggsn.h b/ggsn/ggsn.h
index 09cd0f6..82984a0 100644
--- a/ggsn/ggsn.h
+++ b/ggsn/ggsn.h
@@ -6,6 +6,7 @@
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
+#include <osmocom/ctrl/control_if.h>
#include "../lib/tun.h"
#include "../lib/ippool.h"
@@ -13,6 +14,8 @@
#include "../lib/in46_addr.h"
#include "../gtp/gtp.h"
+#include "sgsn.h"
+
#define APN_TYPE_IPv4 0x01 /* v4-only */
#define APN_TYPE_IPv6 0x02 /* v6-only */
#define APN_TYPE_IPv4v6 0x04 /* v4v6 dual-stack */
@@ -88,6 +91,14 @@ struct apn_ctx {
struct apn_ctx_ip v6;
};
+struct pdp_priv_t {
+ struct pdp_t *lib; /* pointer to libgtp associated pdp_t instance */
+ struct sgsn_peer *sgsn;
+ struct apn_ctx *apn;
+ struct llist_head entry; /* to be included into sgsn_peer */
+ /* struct ggsn_ctx can be reached through lib->gsn->priv, or through sgsn->ggsn */
+};
+
struct ggsn_ctx {
/* global list of GGSNs */
struct llist_head list;
@@ -95,6 +106,9 @@ struct ggsn_ctx {
/* list of APNs in this GGSN */
struct llist_head apn_list;
+ /* list of SGSN peers (struct sgsn_peer) in this GGSN. TODO: hash table with key <ip+port>? */
+ struct llist_head sgsn_list;
+
bool started;
struct {
@@ -111,6 +125,8 @@ struct ggsn_ctx {
struct in46_addr gtpu_addr;
/* directory for state file */
char *state_dir;
+ /* Time between Echo requests on each SGSN */
+ unsigned int echo_interval;
/* administratively shut-down (true) or not (false) */
bool shutdown;
} cfg;
@@ -122,8 +138,6 @@ struct ggsn_ctx {
struct osmo_fd gtp_fd0;
struct osmo_fd gtp_fd1c;
struct osmo_fd gtp_fd1u;
-
- struct osmo_timer_list gtp_timer;
};
/* ggsn_vty.c */
@@ -135,9 +149,24 @@ 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 */
+/* ggsn_main.c */
+extern struct ctrl_handle *g_ctrlh;
extern void *tall_ggsn_ctx;
+
+/* ggsn.c */
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);
+void ggsn_close_one_pdp(struct pdp_t *pdp);
+
+#define LOGPAPN(level, apn, fmt, args...) \
+ LOGP(DGGSN, level, "APN(%s): " fmt, (apn)->cfg.name, ## args)
+
+#define LOGPGGSN(level, ggsn, fmt, args...) \
+ LOGP(DGGSN, level, "GGSN(%s): " fmt, (ggsn)->cfg.name, ## args)
+
+#define LOGPPDP(level, pdp, fmt, args...) LOGPDPX(DGGSN, level, pdp, fmt, ## args)
+
+#define LOGTUN(level, tun, fmt, args...) \
+ LOGP(DTUN, level, "TUN(%s): " fmt, (tun)->devname, ## args)
diff --git a/ggsn/ggsn_main.c b/ggsn/ggsn_main.c
new file mode 100644
index 0000000..81a8ab1
--- /dev/null
+++ b/ggsn/ggsn_main.c
@@ -0,0 +1,208 @@
+/*
+ * OsmoGGSN - Gateway GPRS Support Node
+ * Copyright (C) 2002, 2003, 2004 Mondru AB.
+ * Copyright (C) 2017-2019 by Harald Welte <laforge@gnumonks.org>
+ * Copyright (C) 2019 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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.
+ *
+ */
+
+#include "../config.h"
+
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+
+#include <getopt.h>
+#include <ctype.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/ioctl.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/core/utils.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/vty/misc.h>
+
+#include "ggsn.h"
+
+void *tall_ggsn_ctx;
+
+static int end = 0;
+static int daemonize = 0;
+struct ctrl_handle *g_ctrlh;
+
+struct ul255_t qos;
+struct ul255_t apn;
+
+static char *config_file = "osmo-ggsn.cfg";
+
+/* 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:
+ case SIGTERM:
+ 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;
+ }
+}
+
+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)
+{
+ struct ggsn_ctx *ggsn;
+ int rc;
+
+ tall_ggsn_ctx = talloc_named_const(NULL, 0, "OsmoGGSN");
+ msgb_talloc_ctx_init(tall_ggsn_ctx, 0);
+ g_vty_info.tall_ctx = tall_ggsn_ctx;
+
+ /* Handle keyboard interrupt SIGINT */
+ signal(SIGINT, &signal_handler);
+ signal(SIGTERM, &signal_handler);
+ signal(SIGABRT, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+
+ osmo_init_ignore_signals();
+ osmo_init_logging2(tall_ggsn_ctx, &log_info);
+ osmo_stats_init(tall_ggsn_ctx);
+
+ vty_init(&g_vty_info);
+ logging_vty_add_cmds();
+ osmo_talloc_vty_add_cmds();
+ osmo_stats_vty_add_cmds();
+ 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);
+ }
+
+ rc = telnet_init_dynif(tall_ggsn_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_GGSN);
+ if (rc < 0)
+ exit(1);
+
+ g_ctrlh = ctrl_interface_setup_dynip(NULL, ctrl_vty_get_bind_addr(),
+ OSMO_CTRL_PORT_GGSN, NULL);
+ if (!g_ctrlh) {
+ LOGP(DGGSN, LOGL_ERROR, "Failed to create CTRL interface.\n");
+ exit(1);
+ }
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+#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
+
+ /* Main select loop */
+ while (!end) {
+ osmo_select_main(0);
+ }
+
+ llist_for_each_entry(ggsn, &g_ggsn_list, list)
+ ggsn_stop(ggsn);
+
+ return 0;
+}
diff --git a/ggsn/ggsn_vty.c b/ggsn/ggsn_vty.c
index eb7cca7..cb92a8a 100644
--- a/ggsn/ggsn_vty.c
+++ b/ggsn/ggsn_vty.c
@@ -37,7 +37,10 @@
#include "../gtp/gtp.h"
#include "../gtp/pdp.h"
+#include "../lib/util.h"
+
#include "ggsn.h"
+#include "sgsn.h"
#define PREFIX_STR "Prefix (Network/Netmask)\n"
#define IFCONFIG_STR "GGSN-based interface configuration\n"
@@ -77,6 +80,7 @@ struct ggsn_ctx *ggsn_find_or_create(void *ctx, const char *name)
ggsn->cfg.state_dir = talloc_strdup(ggsn, "/tmp");
ggsn->cfg.shutdown = true;
INIT_LLIST_HEAD(&ggsn->apn_list);
+ INIT_LLIST_HEAD(&ggsn->sgsn_list);
llist_add_tail(&ggsn->list, &g_ggsn_list);
return ggsn;
@@ -326,6 +330,80 @@ DEFUN(cfg_ggsn_no_shutdown, cfg_ggsn_no_shutdown_cmd,
return CMD_SUCCESS;
}
+static void show_one_sgsn(struct vty *vty, const struct sgsn_peer *sgsn, const char* prefix)
+{
+ char buf[INET_ADDRSTRLEN];
+
+ inet_ntop(AF_INET, &sgsn->addr, buf, sizeof(buf));
+ vty_out(vty, "%s(S)GSN %s%s", prefix, buf, VTY_NEWLINE);
+ vty_out(vty, "%s Restart Counter: %d%s", prefix, sgsn->remote_restart_ctr, VTY_NEWLINE);
+ vty_out(vty, "%s PDP contexts: %d%s", prefix, llist_count(&sgsn->pdp_list), VTY_NEWLINE);
+ vty_out(vty, "%s Echo Requests in-flight: %u%s", prefix, sgsn->tx_msgs_queued, VTY_NEWLINE);
+}
+
+DEFUN(cfg_ggsn_show_sgsn, cfg_ggsn_show_sgsn_cmd,
+ "show sgsn",
+ NO_STR GGSN_STR "Remove the GGSN from administrative shut-down\n")
+{
+ struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
+ struct sgsn_peer *sgsn;
+
+ llist_for_each_entry(sgsn, &ggsn->sgsn_list, entry) {
+ show_one_sgsn(vty, sgsn, "");
+ }
+
+ return CMD_SUCCESS;
+}
+
+/* Seee 3GPP TS 29.060 section 7.2.1 */
+DEFUN(cfg_ggsn_echo_interval, cfg_ggsn_echo_interval_cmd,
+ "echo-interval <1-36000>",
+ GGSN_STR "GGSN Number\n"
+ "Send an echo request to this static GGSN every interval\n"
+ "Interval between echo requests in seconds\n")
+{
+ struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
+ int prev_interval = ggsn->cfg.echo_interval;
+ struct sgsn_peer *sgsn;
+
+ ggsn->cfg.echo_interval = atoi(argv[0]);
+
+ if (ggsn->cfg.echo_interval < 60)
+ vty_out(vty, "%% 3GPP TS 29.060 section states interval should " \
+ "not be lower than 60 seconds, use this value for " \
+ "testing purposes only!%s", VTY_NEWLINE);
+
+ if (prev_interval == ggsn->cfg.echo_interval)
+ return CMD_SUCCESS;
+
+ /* Re-enable echo timer for all sgsn */
+ llist_for_each_entry(sgsn, &ggsn->sgsn_list, entry)
+ sgsn_echo_timer_start(sgsn);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ggsn_no_echo_interval, cfg_ggsn_no_echo_interval_cmd,
+ "no echo-interval",
+ GGSN_STR "GGSN Number\n"
+ NO_STR "Send an echo request to this static GGSN every interval.\n")
+{
+ struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
+ int prev_interval = ggsn->cfg.echo_interval;
+ struct sgsn_peer *sgsn;
+
+ if (prev_interval == ggsn->cfg.echo_interval)
+ return CMD_SUCCESS;
+
+ ggsn->cfg.echo_interval = 0;
+
+ /* Disable echo timer for all sgsn */
+ llist_for_each_entry(sgsn, &ggsn->sgsn_list, entry)
+ sgsn_echo_timer_stop(sgsn);
+
+ return CMD_SUCCESS;
+}
+
/* APN Node */
static struct cmd_node apn_node = {
@@ -714,6 +792,8 @@ static int config_write_ggsn(struct vty *vty)
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);
+ if (ggsn->cfg.echo_interval)
+ vty_out(vty, " echo-interval %u%s", ggsn->cfg.echo_interval, VTY_NEWLINE);
/* must be last */
vty_out(vty, " %sshutdown ggsn%s", ggsn->cfg.shutdown ? "" : "no ", VTY_NEWLINE);
}
@@ -732,13 +812,22 @@ static const char *print_gsnaddr(const struct ul16_t *in)
return in46a_ntoa(&in46);
}
-static void show_one_pdp(struct vty *vty, struct pdp_t *pdp)
+/* Useful for v4v6 APNs, where we first iterate over v4 pool and then over v6
+ pool. param v4only can be used to avoid printing duplicates for pdp context
+ containing both IPv4 and IPv6 addresses. */
+static void show_one_pdp_v4only(struct vty *vty, struct pdp_t *pdp, bool v4only)
{
- struct in46_addr eua46;
+ struct ippoolm_t *peer4, *peer6;
char name_buf[256];
char *apn_name;
int rc;
+ peer4 = pdp_get_peer_ipv(pdp, false);
+ peer6 = pdp_get_peer_ipv(pdp, true);
+
+ if (v4only && peer6)
+ return;
+
/* Attempt to decode MSISDN */
rc = gsm48_decode_bcd_number2(name_buf, sizeof(name_buf),
pdp->msisdn.v, pdp->msisdn.l, 0);
@@ -746,6 +835,20 @@ static void show_one_pdp(struct vty *vty, struct pdp_t *pdp)
vty_out(vty, "IMSI: %s, NSAPI: %u, MSISDN: %s%s", imsi_gtp2str(&pdp->imsi), pdp->nsapi,
rc ? "(NONE)" : name_buf, VTY_NEWLINE);
+ vty_out(vty, " Version: %d", pdp->version);
+ if (pdp->version == 1) {
+ if (!pdp->secondary) {
+ vty_out(vty, ", Primary, Num Secondaries: %d%s%s",
+ pdp_count_secondary(pdp) - 1, /* primary included in count */
+ pdp->nodata ? ", No User Plane": "",
+ VTY_NEWLINE);
+ } else {
+ vty_out(vty, ", Secondary%s", VTY_NEWLINE);
+ }
+ } else {
+ vty_out(vty, "%s", 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);
@@ -757,12 +860,21 @@ static void show_one_pdp(struct vty *vty, struct pdp_t *pdp)
apn_name = osmo_apn_to_str(name_buf, pdp->apn_use.v, pdp->apn_use.l);
vty_out(vty, " APN in use: %s%s", apn_name ? name_buf : "(NONE)", VTY_NEWLINE);
- in46a_from_eua(&pdp->eua, &eua46);
- vty_out(vty, " End-User Address: %s%s", in46a_ntoa(&eua46), VTY_NEWLINE);
+ if (peer4)
+ vty_out(vty, " End-User Address (IPv4): %s%s",
+ in46a_ntop(&peer4->addr, name_buf, sizeof(name_buf)), VTY_NEWLINE);
+ if (peer6)
+ vty_out(vty, " End-User Address (IPv6): %s%s",
+ in46a_ntop(&peer6->addr, name_buf, sizeof(name_buf)), VTY_NEWLINE);
vty_out(vty, " Transmit GTP Sequence Number for G-PDU: %s%s",
pdp->tx_gpdu_seq ? "Yes" : "No", VTY_NEWLINE);
}
+static void show_one_pdp(struct vty *vty, struct pdp_t *pdp)
+{
+ show_one_pdp_v4only(vty, pdp, false);
+}
+
DEFUN(show_pdpctx_imsi, show_pdpctx_imsi_cmd,
"show pdp-context ggsn NAME imsi IMSI [<0-15>]",
SHOW_STR "Display information on PDP Context\n"
@@ -847,7 +959,7 @@ DEFUN(show_pdpctx_ip, show_pdpctx_ip_cmd,
}
/* show all (active) PDP contexts within a pool */
-static void ippool_show_pdp_contexts(struct vty *vty, struct ippool_t *pool)
+static void ippool_show_pdp_contexts(struct vty *vty, struct ippool_t *pool, bool pdp_v4only)
{
unsigned int i;
@@ -858,15 +970,15 @@ static void ippool_show_pdp_contexts(struct vty *vty, struct ippool_t *pool)
struct ippoolm_t *member = &pool->member[i];
if (member->inuse == 0)
continue;
- show_one_pdp(vty, member->peer);
+ show_one_pdp_v4only(vty, member->peer, pdp_v4only);
}
}
/* 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);
+ ippool_show_pdp_contexts(vty, apn->v4.pool, true);
+ ippool_show_pdp_contexts(vty, apn->v6.pool, false);
}
DEFUN(show_pdpctx, show_pdpctx_cmd,
@@ -930,12 +1042,15 @@ static void show_apn(struct vty *vty, struct apn_ctx *apn)
static void show_one_ggsn(struct vty *vty, struct ggsn_ctx *ggsn)
{
struct apn_ctx *apn;
+ struct sgsn_peer *sgsn;
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);
+ llist_for_each_entry(sgsn, &ggsn->sgsn_list, entry)
+ show_one_sgsn(vty, sgsn, " ");
}
DEFUN(show_ggsn, show_ggsn_cmd,
@@ -982,6 +1097,9 @@ int ggsn_vty_init(void)
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_element(GGSN_NODE, &cfg_ggsn_show_sgsn_cmd);
+ install_element(GGSN_NODE, &cfg_ggsn_echo_interval_cmd);
+ install_element(GGSN_NODE, &cfg_ggsn_no_echo_interval_cmd);
install_node(&apn_node, NULL);
install_element(APN_NODE, &cfg_description_cmd);
diff --git a/ggsn/pco.c b/ggsn/pco.c
new file mode 100644
index 0000000..e2181e1
--- /dev/null
+++ b/ggsn/pco.c
@@ -0,0 +1,252 @@
+/*
+ * PCO parsing related code
+ * Copyright (C) 2002, 2003, 2004 Mondru AB.
+ * Copyright (C) 2017-2019 by Harald Welte <laforge@gnumonks.org>
+ * Copyright (C) 2019 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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.
+ *
+ */
+
+#include <unistd.h>
+#include <string.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+
+#include "../lib/util.h"
+
+#include "pco.h"
+#include "ggsn.h"
+
+/* determine if IPCP contains given option */
+static const uint8_t *ipcp_contains_option(const struct ipcp_hdr *ipcp, size_t ipcp_len,
+ enum ipcp_options opt, size_t opt_minlen)
+{
+ const uint8_t *cur_opt = ipcp->options;
+
+ /* iterate over Options and check if protocol contained */
+ while (cur_opt + sizeof(struct ipcp_option_hdr) <= (uint8_t*)ipcp + ipcp_len) {
+ const struct ipcp_option_hdr *cur_opt_hdr = (const struct ipcp_option_hdr *)cur_opt;
+ /* length value includes 2 bytes type/length */
+ if (cur_opt_hdr->len < sizeof(struct ipcp_option_hdr))
+ return NULL;
+ if (cur_opt_hdr->type == opt &&
+ cur_opt_hdr->len >= sizeof(struct ipcp_option_hdr) + opt_minlen)
+ return cur_opt;
+ cur_opt += cur_opt_hdr->len;
+ }
+ return NULL;
+}
+
+
+static const char *pap_welcome = "Welcome to OsmoGGSN " PACKAGE_VERSION;
+
+/* Handle PAP protocol according to RFC 1334 */
+static void process_pco_element_pap(const struct pco_element *pco_in, struct msgb *resp,
+ const struct apn_ctx *apn, struct pdp_t *pdp)
+{
+ const struct pap_element *pap_in = (const struct pap_element *) pco_in->data;
+ uint16_t pap_in_len;
+ uint8_t peer_id_len;
+ const uint8_t *peer_id;
+ unsigned int pap_welcome_len;
+ uint8_t pap_out_size;
+ struct pap_element *pap_out;
+
+ if (pco_in->length < sizeof(struct pap_element))
+ goto ret_broken;
+
+ pap_in_len = osmo_load16be(&pap_in->len);
+ if (pco_in->length < pap_in_len)
+ goto ret_broken;
+ /* "pco_in->length > pap_in_len" is allowed: RFC1334 2.2 states:
+ "Octets outside the range of the Length field should be treated as
+ Data Link Layer padding and should be ignored on reception."
+ */
+
+ switch (pap_in->code) {
+ case PAP_CODE_AUTH_REQ:
+ if (pap_in_len < sizeof(struct pap_element) + 1)
+ goto ret_broken_auth;
+ peer_id_len = pap_in->data[0];
+ if (pap_in_len < sizeof(struct pap_element) + 1 + peer_id_len)
+ goto ret_broken_auth;
+ peer_id = &pap_in->data[1];
+ LOGPPDP(LOGL_DEBUG, pdp, "PCO PAP PeerId = %s, ACKing\n",
+ osmo_quote_str((const char *)peer_id, peer_id_len));
+ /* Password-Length + Password following here, but we don't care */
+
+ /* Prepare response, we ACK all of them: */
+ pap_welcome_len = strlen(pap_welcome);
+ /* +1: Length field of pap_welcome Message */
+ pap_out_size = sizeof(struct pap_element) + 1 + pap_welcome_len;
+ pap_out = alloca(pap_out_size);
+ pap_out->code = PAP_CODE_AUTH_ACK;
+ pap_out->id = pap_in->id;
+ pap_out->len = htons(pap_out_size);
+ pap_out->data[0] = pap_welcome_len;
+ memcpy(pap_out->data+1, pap_welcome, pap_welcome_len);
+ msgb_t16lv_put(resp, PCO_P_PAP, pap_out_size, (uint8_t *) pap_out);
+ break;
+ case PAP_CODE_AUTH_ACK:
+ case PAP_CODE_AUTH_NAK:
+ default:
+ LOGPPDP(LOGL_NOTICE, pdp, "Unsupported PAP PCO Code %u, ignoring\n", pap_in->code);
+ break;
+ }
+ return;
+
+ret_broken_auth:
+ LOGPPDP(LOGL_NOTICE, pdp, "Invalid PAP AuthenticateReq: %s, ignoring\n",
+ osmo_hexdump_nospc((const uint8_t *)pco_in, pco_in->length));
+ return;
+
+ret_broken:
+ LOGPPDP(LOGL_NOTICE, pdp, "Invalid PAP PCO Length: %s, ignoring\n",
+ osmo_hexdump_nospc((const uint8_t *)pco_in, pco_in->length));
+}
+
+static void process_pco_element_ipcp(const struct pco_element *pco_elem, struct msgb *resp,
+ const struct apn_ctx *apn, struct pdp_t *pdp)
+{
+ struct ippoolm_t *peer_v4 = pdp_get_peer_ipv(pdp, false);
+ const struct in46_addr *dns1 = &apn->v4.cfg.dns[0];
+ const struct in46_addr *dns2 = &apn->v4.cfg.dns[1];
+ uint8_t *start = resp->tail;
+ const struct ipcp_hdr *ipcp;
+ uint16_t ipcp_len;
+ uint8_t *len1, *len2;
+ unsigned int len_appended;
+ ptrdiff_t consumed;
+ size_t remain;
+
+ if (!peer_v4) {
+ LOGPPDP(LOGL_ERROR, pdp, "IPCP but no IPv4 type ?!?\n");
+ return;
+ }
+
+ ipcp = (const struct ipcp_hdr *)pco_elem->data;
+ consumed = (pco_elem->data - &pdp->pco_req.v[0]);
+ remain = sizeof(pdp->pco_req.v) - consumed;
+ ipcp_len = osmo_load16be(&ipcp->len);
+ if (remain < 0 || remain < ipcp_len) {
+ LOGPPDP(LOGL_ERROR, pdp, "Malformed IPCP, ignoring\n");
+ return;
+ }
+
+ /* Three byte T16L header */
+ msgb_put_u16(resp, 0x8021); /* IPCP */
+ len1 = msgb_put(resp, 1); /* Length of contents: delay */
+
+ msgb_put_u8(resp, 0x02); /* ACK */
+ msgb_put_u8(resp, ipcp->id); /* ID: Needs to match request */
+ msgb_put_u8(resp, 0x00); /* Length MSB */
+ len2 = msgb_put(resp, 1); /* Length LSB: delay */
+
+ if (dns1->len == 4 && ipcp_contains_option(ipcp, ipcp_len, IPCP_OPT_PRIMARY_DNS, 4)) {
+ msgb_put_u8(resp, 0x81); /* DNS1 Tag */
+ msgb_put_u8(resp, 2 + dns1->len); /* DNS1 Length, incl. TL */
+ msgb_put_u32(resp, ntohl(dns1->v4.s_addr));
+ }
+
+ if (dns2->len == 4 && ipcp_contains_option(ipcp, ipcp_len, IPCP_OPT_SECONDARY_DNS, 4)) {
+ msgb_put_u8(resp, 0x83); /* DNS2 Tag */
+ msgb_put_u8(resp, 2 + dns2->len); /* DNS2 Length, incl. TL */
+ msgb_put_u32(resp, ntohl(dns2->v4.s_addr));
+ }
+
+ /* patch in length values */
+ len_appended = resp->tail - start;
+ *len1 = len_appended - 3;
+ *len2 = len_appended - 3;
+}
+
+static void process_pco_element_dns_ipv6(const struct pco_element *pco_elem, struct msgb *resp,
+ const struct apn_ctx *apn, struct pdp_t *pdp)
+{
+ unsigned int i;
+ const uint8_t *tail = resp->tail;
+
+ for (i = 0; i < ARRAY_SIZE(apn->v6.cfg.dns); i++) {
+ const struct in46_addr *i46a = &apn->v6.cfg.dns[i];
+ if (i46a->len != 16)
+ continue;
+ msgb_t16lv_put(resp, PCO_P_DNS_IPv6_ADDR, i46a->len, i46a->v6.s6_addr);
+ }
+ if (resp->tail == tail)
+ LOGPPDP(LOGL_NOTICE, pdp, "MS requested IPv6 DNS, but APN has none configured\n");
+}
+
+static void process_pco_element_dns_ipv4(const struct pco_element *pco_elem, struct msgb *resp,
+ const struct apn_ctx *apn, struct pdp_t *pdp)
+{
+ unsigned int i;
+ const uint8_t *tail = resp->tail;
+
+ for (i = 0; i < ARRAY_SIZE(apn->v4.cfg.dns); i++) {
+ const struct in46_addr *i46a = &apn->v4.cfg.dns[i];
+ if (i46a->len != 4)
+ continue;
+ msgb_t16lv_put(resp, PCO_P_DNS_IPv4_ADDR, i46a->len, (uint8_t *)&i46a->v4);
+ }
+ if (resp->tail == tail)
+ LOGPPDP(LOGL_NOTICE, pdp, "MS requested IPv4 DNS, but APN has none configured\n");
+}
+
+static void process_pco_element(const struct pco_element *pco_elem, struct msgb *resp,
+ const struct apn_ctx *apn, struct pdp_t *pdp)
+{
+ uint16_t protocol_id = osmo_load16be(&pco_elem->protocol_id);
+
+ LOGPPDP(LOGL_DEBUG, pdp, "PCO Protocol 0x%04x\n", protocol_id);
+ switch (protocol_id) {
+ case PCO_P_PAP:
+ process_pco_element_pap(pco_elem, resp, apn, pdp);
+ break;
+ case PCO_P_IPCP:
+ process_pco_element_ipcp(pco_elem, resp, apn, pdp);
+ break;
+ case PCO_P_DNS_IPv6_ADDR:
+ process_pco_element_dns_ipv6(pco_elem, resp, apn, pdp);
+ break;
+ case PCO_P_DNS_IPv4_ADDR:
+ process_pco_element_dns_ipv4(pco_elem, resp, apn, pdp);
+ break;
+ default:
+ LOGPPDP(LOGL_INFO, pdp, "Unknown/Unimplemented PCO Protocol 0x%04x: %s\n",
+ protocol_id, osmo_hexdump_nospc(pco_elem->data, pco_elem->length));
+ break;
+ }
+}
+
+/* process one PCO request from a MS/UE, putting together the proper responses */
+void process_pco(const struct apn_ctx *apn, struct pdp_t *pdp)
+{
+ struct msgb *resp = msgb_alloc(256, "PCO.resp");
+ const struct ul255_t *pco = &pdp->pco_req;
+ const struct pco_element *pco_elem;
+ const uint8_t *cur;
+
+ /* build the header of the PCO response */
+ OSMO_ASSERT(resp);
+ msgb_put_u8(resp, 0x80); /* ext-bit + configuration protocol byte */
+
+ /* iterate over the PCO elements in the request; call process_pco_element() for each */
+ for (cur = pco->v + 1, pco_elem = (const struct pco_element *) cur;
+ cur + sizeof(struct pco_element) <= pco->v + pco->l;
+ cur += pco_elem->length + sizeof(*pco_elem), pco_elem = (const struct pco_element *) cur) {
+ process_pco_element(pco_elem, resp, apn, pdp);
+ }
+
+ /* copy the PCO response msgb and copy its contents over to the PDP context */
+ if (msgb_length(resp) > 1) {
+ memcpy(pdp->pco_neg.v, msgb_data(resp), msgb_length(resp));
+ pdp->pco_neg.l = msgb_length(resp);
+ } else
+ pdp->pco_neg.l = 0;
+ msgb_free(resp);
+}
diff --git a/ggsn/pco.h b/ggsn/pco.h
new file mode 100644
index 0000000..7ebe05a
--- /dev/null
+++ b/ggsn/pco.h
@@ -0,0 +1,81 @@
+#pragma once
+
+#include <stdint.h>
+
+#include "../gtp/pdp.h"
+
+/* 3GPP TS 24.008 10.6.5.3 */
+enum pco_protocols {
+ PCO_P_LCP = 0xC021,
+ PCO_P_PAP = 0xC023,
+ PCO_P_CHAP = 0xC223,
+ PCO_P_IPCP = 0x8021,
+ PCO_P_PCSCF_ADDR = 0x0001,
+ PCO_P_IM_CN_SS_F = 0x0002,
+ PCO_P_DNS_IPv6_ADDR = 0x0003,
+ PCO_P_POLICY_CTRL_REJ = 0x0004, /* only in Network->MS */
+ PCO_P_MS_SUP_NETREQ_BCI = 0x0005,
+ /* reserved */
+ PCO_P_DSMIPv6_HA_ADDR = 0x0007,
+ PCO_P_DSMIPv6_HN_PREF = 0x0008,
+ PCO_P_DSMIPv6_v4_HA_ADDR= 0x0009,
+ PCO_P_IP_ADDR_VIA_NAS = 0x000a, /* only MS->Network */
+ PCO_P_IPv4_ADDR_VIA_DHCP= 0x000b, /* only MS->Netowrk */
+ PCO_P_PCSCF_IPv4_ADDR = 0x000c,
+ PCO_P_DNS_IPv4_ADDR = 0x000d,
+ PCO_P_MSISDN = 0x000e,
+ PCO_P_IFOM_SUPPORT = 0x000f,
+ PCO_P_IPv4_LINK_MTU = 0x0010,
+ PCO_P_MS_SUPP_LOC_A_TFT = 0x0011,
+ PCO_P_PCSCF_RESEL_SUP = 0x0012, /* only MS->Network */
+ PCO_P_NBIFOM_REQ = 0x0013,
+ PCO_P_NBIFOM_MODE = 0x0014,
+ PCO_P_NONIP_LINK_MTU = 0x0015,
+ PCO_P_APN_RATE_CTRL_SUP = 0x0016,
+ PCO_P_PS_DATA_OFF_UE = 0x0017,
+ PCO_P_REL_DATA_SVC = 0x0018,
+};
+
+struct pco_element {
+ uint16_t protocol_id; /* network byte order */
+ uint8_t length; /* length of data below */
+ uint8_t data[0];
+} __attribute__((packed));
+
+
+/* RFC 1332 */
+enum ipcp_options {
+ IPCP_OPT_IPADDR = 3,
+ IPCP_OPT_PRIMARY_DNS = 129,
+ IPCP_OPT_SECONDARY_DNS = 131,
+};
+
+struct ipcp_option_hdr {
+ uint8_t type;
+ uint8_t len;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+struct ipcp_hdr {
+ uint8_t code;
+ uint8_t id;
+ uint16_t len;
+ uint8_t options[0];
+} __attribute__ ((packed));
+
+/* RFC 1334, section 3.2. Packet Format */
+struct pap_element {
+ uint8_t code;
+ uint8_t id;
+ uint16_t len; /* length including header */
+ uint8_t data[0];
+} __attribute__((packed));
+
+enum pap_code {
+ PAP_CODE_AUTH_REQ = 1,
+ PAP_CODE_AUTH_ACK = 2,
+ PAP_CODE_AUTH_NAK = 3,
+};
+
+struct apn_ctx;
+void process_pco(const struct apn_ctx *apn, struct pdp_t *pdp);
diff --git a/ggsn/sgsn.c b/ggsn/sgsn.c
new file mode 100644
index 0000000..8360439
--- /dev/null
+++ b/ggsn/sgsn.c
@@ -0,0 +1,160 @@
+#include "sgsn.h"
+#include "ggsn.h"
+
+
+static bool sgsn_peer_attempt_free(struct sgsn_peer *sgsn)
+{
+ /* We have to be careful here, since if all pdp ctx for that sgsn were
+ deactivated in-between we sent the Echo Req and receivied the timeout
+ indication, the sgsn (cbp) may be already gone. We need to add some
+ counter reference of echo requets in flight and only free sgsn
+ structures when it goes to zero decreased for all Echo Resp. We do it
+ this way because currently in libgtp there's no understanding of "gsn
+ peer" for which messages are grouped and hence we cannot request
+ libgtp to drop all queued messages for a specific peer. */
+ if (sgsn->tx_msgs_queued) {
+ LOGSGSN(LOGL_INFO, sgsn, "Delaying delete, still %u echo messages queued\n",
+ sgsn->tx_msgs_queued);
+ return false;
+ }
+ llist_del(&sgsn->entry);
+ LOGSGSN(LOGL_INFO, sgsn, "Deleting SGSN\n");
+ talloc_free(sgsn);
+ return true;
+}
+
+static void sgsn_peer_echo_req(struct sgsn_peer *sgsn)
+{
+ struct ggsn_ctx *ggsn = sgsn->ggsn;
+ LOGSGSN(LOGL_INFO, sgsn, "Tx Echo Request\n");
+ gtp_echo_req(ggsn->gsn, sgsn->gtp_version, sgsn, &sgsn->addr);
+ sgsn->tx_msgs_queued++;
+}
+
+void sgsn_peer_echo_resp(struct sgsn_peer *sgsn, bool timeout)
+{
+ if (timeout) {
+ LOGSGSN(LOGL_NOTICE, sgsn, "Rx Echo Request timed out!\n");
+ sgsn_peer_drop_all_pdp(sgsn);
+ } else {
+ LOGSGSN(LOGL_INFO, sgsn, "Rx Echo Response\n");
+ }
+
+ /* We decrement it here after dropping all pdps to make sure sgsn was
+ not freed upon last pdp ctx deleted and is still alive now */
+ sgsn->tx_msgs_queued--;
+ if (llist_empty(&sgsn->pdp_list))
+ sgsn_peer_attempt_free(sgsn);
+}
+
+void sgsn_echo_timer_start(struct sgsn_peer *sgsn)
+{
+ if (sgsn->ggsn->cfg.echo_interval == 0)
+ return;
+ sgsn_peer_echo_req(sgsn);
+ osmo_timer_schedule(&sgsn->echo_timer, sgsn->ggsn->cfg.echo_interval, 0);
+}
+
+void sgsn_echo_timer_stop(struct sgsn_peer *sgsn)
+{
+ osmo_timer_del(&sgsn->echo_timer);
+}
+
+static void sgsn_echo_timer_cb(void *data)
+{
+ struct sgsn_peer *sgsn = (struct sgsn_peer *) data;
+ sgsn_echo_timer_start(sgsn);
+}
+
+struct sgsn_peer *sgsn_peer_allocate(struct ggsn_ctx *ggsn, struct in_addr *ia, unsigned int gtp_version)
+{
+ struct sgsn_peer *sgsn;
+
+ sgsn = talloc_zero_size(ggsn, sizeof(struct sgsn_peer));
+ sgsn->ggsn = ggsn;
+ sgsn->addr = *ia;
+ sgsn->gtp_version = gtp_version;
+ sgsn->remote_restart_ctr = -1;
+ INIT_LLIST_HEAD(&sgsn->pdp_list);
+ INIT_LLIST_HEAD(&sgsn->entry);
+
+ osmo_timer_setup(&sgsn->echo_timer, sgsn_echo_timer_cb, sgsn);
+
+ LOGSGSN(LOGL_INFO, sgsn, "Discovered\n");
+ return sgsn;
+}
+
+void sgsn_peer_add_pdp_priv(struct sgsn_peer *sgsn, struct pdp_priv_t *pdp_priv)
+{
+ bool was_empty = llist_empty(&sgsn->pdp_list);
+ pdp_priv->sgsn = sgsn;
+ llist_add(&pdp_priv->entry, &sgsn->pdp_list);
+ if (was_empty)
+ sgsn_echo_timer_start(sgsn);
+}
+
+void sgsn_peer_remove_pdp_priv(struct pdp_priv_t* pdp_priv)
+{
+ struct sgsn_peer *sgsn = pdp_priv->sgsn;
+ llist_del(&pdp_priv->entry);
+ if (sgsn && llist_empty(&sgsn->pdp_list)) {
+ /* No PDP contexts associated to this SGSN, no need to keep it */
+ sgsn_echo_timer_stop(sgsn);
+ /* sgsn may not be freed if there are some messages still queued
+ in libgtp which could return a pointer to it */
+ sgsn_peer_attempt_free(sgsn);
+ }
+
+ pdp_priv->sgsn = NULL;
+}
+
+/* High-level function to be called in case a GGSN has disappeared or
+ * otherwise lost state (recovery procedure). It will detach all related pdp ctx
+ * from a ggsn and communicate deact to MS. Optionally (!NULL), one pdp ctx can
+ * be kept alive to allow handling later message which contained the Recovery IE. */
+static unsigned int sgsn_peer_drop_all_pdp_except(struct sgsn_peer *sgsn, struct pdp_priv_t *except)
+{
+ unsigned int num = 0;
+ char buf[INET_ADDRSTRLEN];
+
+ inet_ntop(AF_INET, &sgsn->addr, buf, sizeof(buf));
+
+ struct pdp_priv_t *pdp, *pdp2;
+ llist_for_each_entry_safe(pdp, pdp2, &sgsn->pdp_list, entry) {
+ if (pdp == except)
+ continue;
+ ggsn_close_one_pdp(pdp->lib);
+ num++;
+ }
+
+ /* Note: if except is NULL, all pdp contexts are freed and sgsn is
+ already freed at this point */
+ LOGP(DGGSN, LOGL_INFO, "SGSN(%s) Dropped %u PDP contexts\n", buf, num);
+
+ return num;
+}
+
+unsigned int sgsn_peer_drop_all_pdp(struct sgsn_peer *sgsn)
+{
+ return sgsn_peer_drop_all_pdp_except(sgsn, NULL);
+}
+
+int sgsn_peer_handle_recovery(struct sgsn_peer *sgsn, struct pdp_t *pdp, uint8_t recovery)
+{
+ struct pdp_priv_t *pdp_priv = NULL;
+
+ if (sgsn->remote_restart_ctr == -1) {
+ /* First received ECHO RESPONSE, note the restart ctr */
+ sgsn->remote_restart_ctr = recovery;
+ } else if (sgsn->remote_restart_ctr != recovery) {
+ /* counter has changed (SGSN restart): release all PDP */
+ LOGSGSN(LOGL_NOTICE, sgsn, "SGSN recovery (%u->%u) pdp=%p, "
+ "releasing all%s PDP contexts\n",
+ sgsn->remote_restart_ctr, recovery, pdp, pdp ? " other" : "");
+ sgsn->remote_restart_ctr = recovery;
+ if (pdp)
+ pdp_priv = pdp->priv;
+ sgsn_peer_drop_all_pdp_except(sgsn, pdp_priv);
+ }
+ return 0;
+}
diff --git a/ggsn/sgsn.h b/ggsn/sgsn.h
new file mode 100644
index 0000000..d2c3c0c
--- /dev/null
+++ b/ggsn/sgsn.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/timer.h>
+
+#include "../gtp/pdp.h"
+
+struct ggsn_ctx;
+struct pdp_priv_t;
+
+struct sgsn_peer {
+ struct llist_head entry; /* to be included into ggsn_ctx */
+ struct ggsn_ctx *ggsn; /* backpointer to ggsn_ctx */
+ struct in_addr addr; /* Addr of the sgsn peer */
+ unsigned int gtp_version; /* GTP version */
+ int remote_restart_ctr; /* Last received Restart Ctr from sgsn peer, -1 == unknown */
+ /* list of pdp contexts associated with this sgsn */
+ struct llist_head pdp_list;
+ /* Sends echo request towards SGSN on expiration. Echo Resp is received
+ through cb_recovery2(), and echo Req timeout through
+ cb_conf(GTP_ECHO_REQ, EOF, NULL, cbp); */
+ struct osmo_timer_list echo_timer;
+ /* Number of GTP messages in libgtp transmit queue */
+ unsigned int tx_msgs_queued;
+};
+
+struct sgsn_peer *sgsn_peer_allocate(struct ggsn_ctx *ggsn, struct in_addr *ia, unsigned int gtp_version);
+void sgsn_peer_add_pdp_priv(struct sgsn_peer *sgsn, struct pdp_priv_t *pdp_priv);
+void sgsn_peer_remove_pdp_priv(struct pdp_priv_t *pdp_priv);
+
+void sgsn_echo_timer_start(struct sgsn_peer *sgsn);
+void sgsn_echo_timer_stop(struct sgsn_peer *sgsn);
+
+void sgsn_peer_echo_resp(struct sgsn_peer *sgsn, bool timeout);
+unsigned int sgsn_peer_drop_all_pdp(struct sgsn_peer *sgsn);
+int sgsn_peer_handle_recovery(struct sgsn_peer *sgsn, struct pdp_t *pdp, uint8_t recovery);
+
+#define LOGSGSN(level, sgsn, fmt, args...) { \
+ char _buf[INET_ADDRSTRLEN]; \
+ LOGP(DGGSN, level, "SGSN(%s): " fmt, inet_ntop(AF_INET, &sgsn->addr, _buf, sizeof(_buf)), ## args); \
+ } while (0)
diff --git a/gtp/gtp.c b/gtp/gtp.c
index 94c3245..ec83041 100644
--- a/gtp/gtp.c
+++ b/gtp/gtp.c
@@ -219,6 +219,8 @@ static void emit_cb_recovery(struct gsn_t *gsn, struct sockaddr_in * peer,
gsn->cb_recovery(peer, recovery);
if (gsn->cb_recovery2)
gsn->cb_recovery2(peer, pdp, recovery);
+ if (gsn->cb_recovery3)
+ gsn->cb_recovery3(gsn, peer, pdp, recovery);
}
int gtp_set_cb_recovery(struct gsn_t *gsn,
@@ -242,6 +244,21 @@ int gtp_set_cb_recovery2(struct gsn_t *gsn,
return 0;
}
+/* cb_recovery()
+ * pdp may be NULL if Recovery IE was received from a message independent
+ * of any PDP ctx (such as Echo Response), or because pdp ctx is unknown to the
+ * local setup. In case pdp is known, caller may want to keep that pdp alive to
+ * handle subsequent msg cb as this specific pdp ctx is still valid according to
+ * specs.
+ */
+int gtp_set_cb_recovery3(struct gsn_t *gsn,
+ int (*cb_recovery3) (struct gsn_t *gsn, struct sockaddr_in *peer,
+ struct pdp_t *pdp, uint8_t recovery))
+{
+ gsn->cb_recovery3 = cb_recovery3;
+ return 0;
+}
+
int gtp_set_cb_data_ind(struct gsn_t *gsn,
int (*cb_data_ind) (struct pdp_t * pdp,
void *pack, unsigned len))
@@ -369,6 +386,109 @@ static uint32_t get_tei(void *pack)
}
}
+static int queue_timer_retrans(struct gsn_t *gsn)
+{
+ /* Retransmit any outstanding packets */
+ /* Remove from queue if maxretrans exceeded */
+ time_t now;
+ struct qmsg_t *qmsg;
+ now = time(NULL);
+ /*printf("Retrans: New beginning %d\n", (int) now); */
+
+ /* get first element in queue, as long as the timeout of that
+ * element has expired */
+ while ((!queue_getfirst(gsn->queue_req, &qmsg)) &&
+ (qmsg->timeout <= now)) {
+ /*printf("Retrans timeout found: %d\n", (int) time(NULL)); */
+ if (qmsg->retrans > N3_REQUESTS) { /* To many retrans */
+ LOGP(DLGTP, LOGL_NOTICE, "Timeout of seq %" PRIu16 "\n",
+ qmsg->seq);
+ if (gsn->cb_conf)
+ gsn->cb_conf(qmsg->type, EOF, NULL, qmsg->cbp);
+ queue_freemsg(gsn->queue_req, qmsg);
+ } else {
+ LOGP(DLGTP, LOGL_INFO, "Retransmit (%d) of seq %" PRIu16 "\n",
+ qmsg->retrans, qmsg->seq);
+ if (sendto(qmsg->fd, &qmsg->p, qmsg->l, 0,
+ (struct sockaddr *)&qmsg->peer,
+ sizeof(struct sockaddr_in)) < 0) {
+ gsn->err_sendto++;
+ LOGP(DLGTP, LOGL_ERROR,
+ "Sendto(fd0=%d, msg=%lx, len=%d) failed: Error = %s\n",
+ gsn->fd0, (unsigned long)&qmsg->p,
+ qmsg->l, strerror(errno));
+ }
+ queue_back(gsn->queue_req, qmsg);
+ qmsg->timeout = now + T3_REQUEST;
+ qmsg->retrans++;
+ }
+ }
+
+ /* Also clean up reply timeouts */
+ while ((!queue_getfirst(gsn->queue_resp, &qmsg)) &&
+ (qmsg->timeout < now)) {
+ /*printf("Retrans (reply) timeout found: %d\n", (int) time(NULL)); */
+ queue_freemsg(gsn->queue_resp, qmsg);
+ }
+
+ return 0;
+}
+
+static int queue_timer_retranstimeout(struct gsn_t *gsn, struct timeval *timeout)
+{
+ time_t now, later, diff;
+ struct qmsg_t *qmsg;
+ timeout->tv_usec = 0;
+
+ if (queue_getfirst(gsn->queue_req, &qmsg)) {
+ timeout->tv_sec = 10;
+ } else {
+ now = time(NULL);
+ later = qmsg->timeout;
+ timeout->tv_sec = later - now;
+ if (timeout->tv_sec < 0)
+ timeout->tv_sec = 0; /* No negative allowed */
+ if (timeout->tv_sec > 10)
+ timeout->tv_sec = 10; /* Max sleep for 10 sec */
+ }
+
+ if (queue_getfirst(gsn->queue_resp, &qmsg)) {
+ /* already set by queue_req, do nothing */
+ } else { /* trigger faster if earlier timeout exists in queue_resp */
+ now = time(NULL);
+ later = qmsg->timeout;
+ diff = later - now;
+ if (diff < 0)
+ diff = 0;
+ if (diff < timeout->tv_sec)
+ timeout->tv_sec = diff;
+ }
+
+ return 0;
+}
+
+static void queue_timer_start(struct gsn_t *gsn)
+{
+ struct timeval next;
+
+ /* Retrieve next retransmission as timeval */
+ queue_timer_retranstimeout(gsn, &next);
+
+ /* re-schedule the timer */
+ osmo_timer_schedule(&gsn->queue_timer, next.tv_sec, next.tv_usec/1000);
+}
+
+/* timer callback for libgtp retransmission and ping */
+static void queue_timer_cb(void *data)
+{
+ struct gsn_t *gsn = data;
+
+ /* do all the retransmissions as needed */
+ queue_timer_retrans(gsn);
+
+ queue_timer_start(gsn);
+}
+
/* ***********************************************************
* Reliable delivery of signalling messages
*
@@ -513,6 +633,12 @@ static int gtp_req(struct gsn_t *gsn, uint8_t version, struct pdp_t *pdp,
qmsg->cbp = cbp;
qmsg->type = ntoh8(packet->gtp0.h.type);
qmsg->fd = fd;
+ if (pdp) /* echo requests are not pdp-bound */
+ llist_add(&qmsg->entry, &pdp->qmsg_list_req);
+
+ /* Rearm timer: Retrans time for qmsg just queued may be required
+ before an existing one (for instance a gtp echo req) */
+ queue_timer_start(gsn);
}
gsn->seq_next++; /* Count up this time */
return 0;
@@ -568,78 +694,15 @@ static int gtp_conf(struct gsn_t *gsn, uint8_t version, struct sockaddr_in *peer
int gtp_retrans(struct gsn_t *gsn)
{
- /* Retransmit any outstanding packets */
- /* Remove from queue if maxretrans exceeded */
- time_t now;
- struct qmsg_t *qmsg;
- now = time(NULL);
- /*printf("Retrans: New beginning %d\n", (int) now); */
-
- /* get first element in queue, as long as the timeout of that
- * element has expired */
- while ((!queue_getfirst(gsn->queue_req, &qmsg)) &&
- (qmsg->timeout <= now)) {
- /*printf("Retrans timeout found: %d\n", (int) time(NULL)); */
- if (qmsg->retrans > N3_REQUESTS) { /* To many retrans */
- if (gsn->cb_conf)
- gsn->cb_conf(qmsg->type, EOF, NULL, qmsg->cbp);
- queue_freemsg(gsn->queue_req, qmsg);
- } else {
- if (sendto(qmsg->fd, &qmsg->p, qmsg->l, 0,
- (struct sockaddr *)&qmsg->peer,
- sizeof(struct sockaddr_in)) < 0) {
- gsn->err_sendto++;
- LOGP(DLGTP, LOGL_ERROR,
- "Sendto(fd0=%d, msg=%lx, len=%d) failed: Error = %s\n",
- gsn->fd0, (unsigned long)&qmsg->p,
- qmsg->l, strerror(errno));
- }
- queue_back(gsn->queue_req, qmsg);
- qmsg->timeout = now + T3_REQUEST;
- qmsg->retrans++;
- }
- }
-
- /* Also clean up reply timeouts */
- while ((!queue_getfirst(gsn->queue_resp, &qmsg)) &&
- (qmsg->timeout < now)) {
- /*printf("Retrans (reply) timeout found: %d\n", (int) time(NULL)); */
- queue_freemsg(gsn->queue_resp, qmsg);
- }
-
+ /* dummy API, deprecated. */
return 0;
}
int gtp_retranstimeout(struct gsn_t *gsn, struct timeval *timeout)
{
- time_t now, later, diff;
- struct qmsg_t *qmsg;
+ timeout->tv_sec = 24*60*60;
timeout->tv_usec = 0;
-
- if (queue_getfirst(gsn->queue_req, &qmsg)) {
- timeout->tv_sec = 10;
- } else {
- now = time(NULL);
- later = qmsg->timeout;
- timeout->tv_sec = later - now;
- if (timeout->tv_sec < 0)
- timeout->tv_sec = 0; /* No negative allowed */
- if (timeout->tv_sec > 10)
- timeout->tv_sec = 10; /* Max sleep for 10 sec */
- }
-
- if (queue_getfirst(gsn->queue_resp, &qmsg)) {
- /* already set by queue_req, do nothing */
- } else { /* trigger faster if earlier timeout exists in queue_resp */
- now = time(NULL);
- later = qmsg->timeout;
- diff = later - now;
- if (diff < 0)
- diff = 0;
- if (diff < timeout->tv_sec)
- timeout->tv_sec = diff;
- }
-
+ /* dummy API, deprecated. Return a huge timer to do nothing */
return 0;
}
@@ -697,6 +760,13 @@ static int gtp_resp(uint8_t version, struct gsn_t *gsn, struct pdp_t *pdp,
qmsg->cbp = NULL;
qmsg->type = 0;
qmsg->fd = fd;
+ /* No need to add to pdp list here, because even on pdp ctx free
+ we want to leave messages in queue_resp until timeout to
+ detect duplicates */
+
+ /* Rearm timer: Retrans time for qmsg just queued may be required
+ before an existing one (for instance a gtp echo req) */
+ queue_timer_start(gsn);
}
return 0;
}
@@ -846,6 +916,9 @@ int gtp_new(struct gsn_t **gsn, char *statedir, struct in_addr *listen,
/* Initialise pdp table */
pdp_init(*gsn);
+ /* Initialize internal queue timer */
+ osmo_timer_setup(&(*gsn)->queue_timer, queue_timer_cb, *gsn);
+
/* Initialise call back functions */
(*gsn)->cb_create_context_ind = 0;
(*gsn)->cb_delete_context = 0;
@@ -933,12 +1006,18 @@ int gtp_new(struct gsn_t **gsn, char *statedir, struct in_addr *listen,
return -errno;
}
+ /* Start internal queue timer */
+ queue_timer_start(*gsn);
+
return 0;
}
int gtp_free(struct gsn_t *gsn)
{
+ /* Cleanup internal queue timer */
+ osmo_timer_del(&gsn->queue_timer);
+
/* Clean up retransmit queues */
queue_free(gsn->queue_req);
queue_free(gsn->queue_resp);
@@ -1296,9 +1375,8 @@ int gtp_create_context_resp(struct gsn_t *gsn, struct pdp_t *pdp, int cause)
/* Now send off a reply to the peer */
gtp_create_pdp_resp(gsn, pdp->version, pdp, cause);
- if (cause != GTPCAUSE_ACC_REQ) {
- pdp_freepdp(pdp);
- }
+ if (cause != GTPCAUSE_ACC_REQ)
+ gtp_freepdp(gsn, pdp);
return 0;
}
@@ -1398,11 +1476,8 @@ int gtp_create_pdp_ind(struct gsn_t *gsn, int version,
pdp = &pdp_buf;
memset(pdp, 0, sizeof(struct pdp_t));
- if (version == 0) {
- uint64_t tid = be64toh(((union gtp_packet *)pack)->gtp0.h.tid);
-
- pdp_set_imsi_nsapi(pdp, tid);
- }
+ if (version == 0)
+ pdp_set_imsi_nsapi(pdp, get_tid(pack));
pdp->seq = seq;
pdp->sa_peer = *peer;
@@ -1727,8 +1802,6 @@ int gtp_create_pdp_conf(struct gsn_t *gsn, int version,
"Invalid message format\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, pdp, cbp);
- /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp);
- pdp_freepdp(pdp); */
return EOF;
}
@@ -1739,8 +1812,6 @@ int gtp_create_pdp_conf(struct gsn_t *gsn, int version,
"Missing mandatory information field\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, pdp, cbp);
- /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp);
- pdp_freepdp(pdp); */
return EOF;
}
@@ -1767,8 +1838,6 @@ int gtp_create_pdp_conf(struct gsn_t *gsn, int version,
"Missing conditional information field\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, pdp, cbp);
- /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp);
- pdp_freepdp(pdp); */
return EOF;
}
}
@@ -1780,8 +1849,6 @@ int gtp_create_pdp_conf(struct gsn_t *gsn, int version,
"Missing conditional information field\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, pdp, cbp);
- /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp);
- pdp_freepdp(pdp); */
return EOF;
}
@@ -1793,8 +1860,6 @@ int gtp_create_pdp_conf(struct gsn_t *gsn, int version,
"Missing conditional information field\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, pdp, cbp);
- /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp);
- pdp_freepdp(pdp); */
return EOF;
}
@@ -1805,8 +1870,6 @@ int gtp_create_pdp_conf(struct gsn_t *gsn, int version,
"Missing conditional information field\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, pdp, cbp);
- /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp);
- pdp_freepdp(pdp); */
return EOF;
}
}
@@ -1819,8 +1882,6 @@ int gtp_create_pdp_conf(struct gsn_t *gsn, int version,
"Missing conditional information field\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, pdp, cbp);
- /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp);
- pdp_freepdp(pdp); */
return EOF;
}
@@ -1831,8 +1892,6 @@ int gtp_create_pdp_conf(struct gsn_t *gsn, int version,
"Missing conditional information field\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, pdp, cbp);
- /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp);
- pdp_freepdp(pdp); */
return EOF;
}
}
@@ -1844,8 +1903,6 @@ int gtp_create_pdp_conf(struct gsn_t *gsn, int version,
"Missing conditional information field\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, pdp, cbp);
- /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp);
- pdp_freepdp(pdp); */
}
if (gtpie_gettlv(ie, GTPIE_EUA, 0, &pdp->eua.l,
@@ -1856,8 +1913,6 @@ int gtp_create_pdp_conf(struct gsn_t *gsn, int version,
"Missing conditional information field\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, pdp, cbp);
- /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp);
- pdp_freepdp(pdp); */
return EOF;
}
@@ -1869,8 +1924,6 @@ int gtp_create_pdp_conf(struct gsn_t *gsn, int version,
"Missing conditional information field\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, pdp, cbp);
- /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp);
- pdp_freepdp(pdp); */
return EOF;
}
@@ -1882,8 +1935,6 @@ int gtp_create_pdp_conf(struct gsn_t *gsn, int version,
"Missing conditional information field\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, pdp, cbp);
- /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp);
- pdp_freepdp(pdp); */
return EOF;
}
@@ -1897,8 +1948,6 @@ int gtp_create_pdp_conf(struct gsn_t *gsn, int version,
"Missing conditional information field\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, pdp, cbp);
- /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp);
- pdp_freepdp(pdp); */
return EOF;
}
}
@@ -2085,12 +2134,8 @@ static int gtp_update_pdp_ind(struct gsn_t *gsn, uint8_t version,
/* For GTP1 we must use imsi and nsapi if imsi is present. Otherwise */
/* we have to use the tunnel endpoint identifier */
if (version == 0) {
- uint64_t tid = be64toh(((union gtp_packet *)pack)->gtp0.h.tid);
-
- pdp_set_imsi_nsapi(pdp, tid);
-
/* Find the context in question */
- if (gtp_pdp_getimsi(gsn, &pdp, imsi, nsapi)) {
+ if (gtp_pdp_tidget(gsn, &pdp, get_tid(pack))) {
gsn->err_unknownpdp++;
GTP_LOGPKG(LOGL_ERROR, peer, pack,
len, "Unknown PDP context\n");
@@ -2098,6 +2143,9 @@ static int gtp_update_pdp_ind(struct gsn_t *gsn, uint8_t version,
len, NULL,
GTPCAUSE_NON_EXIST);
}
+
+ /* Update IMSI and NSAPI */
+ pdp_set_imsi_nsapi(pdp, get_tid(pack));
} else if (version == 1) {
/* NSAPI (mandatory) */
if (gtpie_gettv1(ie, GTPIE_NSAPI, 0, &nsapi)) {
@@ -2428,8 +2476,9 @@ int gtp_delete_context_req(struct gsn_t *gsn, struct pdp_t *pdp, void *cbp,
}
/* API: Send Delete PDP Context Request. PDP CTX shall be free'd by user at any
- point in time later than this function through a call to pdp_freepdp(pdp), but
- it must be freed no later than during cb_conf(GTP_DELETE_PDP_REQ, pdp) */
+ point in time later than this function through a call to pdp_freepdp(pdp) (or
+ through gtp_freepdp() if willing to receive cb_delete_context() callback),
+ but it must be freed no later than during cb_conf(GTP_DELETE_PDP_REQ, pdp) */
int gtp_delete_context_req2(struct gsn_t *gsn, struct pdp_t *pdp, void *cbp,
int teardown)
{
@@ -2713,7 +2762,7 @@ static int gtp_error_ind_conf(struct gsn_t *gsn, uint8_t version,
/* Find the context in question */
if (version == 0) {
- if (gtp_pdp_tidget(gsn, &pdp, be64toh(((union gtp_packet *)pack)->gtp0.h.tid))) {
+ if (gtp_pdp_tidget(gsn, &pdp, get_tid(pack))) {
gsn->err_unknownpdp++;
GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
"Unknown PDP context\n");
@@ -2744,6 +2793,9 @@ static int gtp_error_ind_conf(struct gsn_t *gsn, uint8_t version,
GTP_LOGPKG(LOGL_ERROR, peer, pack, len, "Unknown PDP context\n");
return EOF;
}
+ } else {
+ LOGP(DLGTP, LOGL_ERROR, "Unknown version: %d\n", version);
+ return EOF;
}
GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
diff --git a/gtp/gtp.h b/gtp/gtp.h
index c2c5122..e03d77d 100644
--- a/gtp/gtp.h
+++ b/gtp/gtp.h
@@ -14,6 +14,7 @@
#include <osmocom/core/utils.h>
#include <osmocom/core/defs.h>
+#include <osmocom/core/timer.h>
#include "pdp.h"
@@ -268,6 +269,8 @@ struct gsn_t {
struct pdp_t pdpa[PDP_MAX]; /* PDP storage */
struct pdp_t *hashtid[PDP_MAX]; /* Hash table for IMSI + NSAPI */
+ struct osmo_timer_list queue_timer; /* internal queue_{req,resp} timer */
+
/* Call back functions */
int (*cb_delete_context) (struct pdp_t *);
int (*cb_create_context_ind) (struct pdp_t *);
@@ -277,6 +280,7 @@ struct gsn_t {
int (*cb_data_ind) (struct pdp_t * pdp, void *pack, unsigned len);
int (*cb_recovery) (struct sockaddr_in * peer, uint8_t recovery);
int (*cb_recovery2) (struct sockaddr_in * peer, struct pdp_t * pdp, uint8_t recovery);
+ int (*cb_recovery3) (struct gsn_t *gsn, struct sockaddr_in *peer, struct pdp_t *pdp, uint8_t recovery);
/* Counters */
@@ -347,8 +351,8 @@ extern int gtp_fd(struct gsn_t *gsn);
extern int gtp_decaps0(struct gsn_t *gsn);
extern int gtp_decaps1c(struct gsn_t *gsn);
extern int gtp_decaps1u(struct gsn_t *gsn);
-extern int gtp_retrans(struct gsn_t *gsn);
-extern int gtp_retranstimeout(struct gsn_t *gsn, struct timeval *timeout);
+extern int gtp_retrans(struct gsn_t *gsn) OSMO_DEPRECATED("This API is a no-op, libgtp already does the job internally");
+extern int gtp_retranstimeout(struct gsn_t *gsn, struct timeval *timeout) OSMO_DEPRECATED("This API is a no-op and will return a 1 day timeout");
extern int gtp_set_cb_delete_context(struct gsn_t *gsn,
int (*cb_delete_context) (struct pdp_t *
@@ -373,6 +377,11 @@ int gtp_set_cb_recovery(struct gsn_t *gsn,
int gtp_set_cb_recovery2(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer,
struct pdp_t * pdp,
+ uint8_t recovery))
+ OSMO_DEPRECATED("Use gtp_set_cb_recovery3() instead, to obtain gsn handling the recovery");;
+int gtp_set_cb_recovery3(struct gsn_t *gsn,
+ int (*cb) (struct gsn_t * gsn, struct sockaddr_in * peer,
+ struct pdp_t * pdp,
uint8_t recovery));
void gtp_clear_queues(struct gsn_t *gsn);
diff --git a/gtp/pdp.c b/gtp/pdp.c
index d745916..518f5d4 100644
--- a/gtp/pdp.c
+++ b/gtp/pdp.c
@@ -31,6 +31,7 @@
#include "pdp.h"
#include "gtp.h"
#include "lookupa.h"
+#include "queue.h"
/* ***********************************************************
* Functions related to PDP storage
@@ -108,7 +109,7 @@ static struct gsn_t *g_gsn;
int pdp_init(struct gsn_t *gsn)
{
- if(!g_gsn) {
+ if (!g_gsn) {
g_gsn = gsn;
} else {
LOGP(DLGTP, LOGL_FATAL, "This interface is depreacted and doesn't support multiple GGSN!");
@@ -156,7 +157,7 @@ int gtp_pdp_newpdp(struct gsn_t *gsn, struct pdp_t **pdp, uint64_t imsi, uint8_t
}
/* Default: Generate G-PDU sequence numbers on Tx */
(*pdp)->tx_gpdu_seq = true;
-
+ INIT_LLIST_HEAD(&(*pdp)->qmsg_list_req);
return 0;
}
}
@@ -165,7 +166,17 @@ int gtp_pdp_newpdp(struct gsn_t *gsn, struct pdp_t **pdp, uint64_t imsi, uint8_t
int pdp_freepdp(struct pdp_t *pdp)
{
+ struct qmsg_t *qmsg, *qmsg2;
struct pdp_t *pdpa = pdp->gsn->pdpa;
+ int rc;
+
+ /* Remove all enqueued messages belonging to this pdp from req tx transmit
+ queue. queue_freemsg will call llist_del(). */
+ llist_for_each_entry_safe(qmsg, qmsg2, &pdp->qmsg_list_req, entry) {
+ if ((rc = queue_freemsg(pdp->gsn->queue_req, qmsg)))
+ LOGP(DLGTP, LOGL_ERROR,
+ "Failed freeing qmsg from qmsg_list_req during pdp_freepdp()! %d\n", rc);
+ }
pdp_tiddel(pdp);
@@ -348,7 +359,7 @@ void pdp_set_imsi_nsapi(struct pdp_t *pdp, uint64_t teid)
/* Count amount of secondary PDP contexts linked to this primary PDP context
* (itself included). Must be called on a primary PDP context. */
-unsigned int pdp_count_secondary(struct pdp_t *pdp)
+unsigned int pdp_count_secondary(const struct pdp_t *pdp)
{
unsigned int n;
unsigned int count = 0;
diff --git a/gtp/pdp.h b/gtp/pdp.h
index d64d394..4dcdde4 100644
--- a/gtp/pdp.h
+++ b/gtp/pdp.h
@@ -17,6 +17,7 @@
#include <netinet/in.h>
#include <osmocom/core/defs.h>
+#include <osmocom/core/linuxlist.h>
struct gsn_t;
@@ -241,6 +242,8 @@ struct pdp_t {
struct gsn_t *gsn; /* Back pointer to GSN where this pdp ctx belongs to */
bool tx_gpdu_seq; /* Transmit (true) or suppress G-PDU sequence numbers */
+
+ struct llist_head qmsg_list_req; /* list of req qmsg_t in retrans queue belonging this pdp ctx */
};
/* functions related to pdp_t management */
@@ -260,7 +263,7 @@ int pdp_tiddel(struct pdp_t *pdp);
uint64_t pdp_gettid(uint64_t imsi, uint8_t nsapi);
void pdp_set_imsi_nsapi(struct pdp_t *pdp, uint64_t teid);
-unsigned int pdp_count_secondary(struct pdp_t *pdp);
+unsigned int pdp_count_secondary(const struct pdp_t *pdp);
/* Deprecated APIs (support for only 1 GSN per process). Must be used only after first call to gtp_new() and until it is freed. */
int pdp_init(struct gsn_t *gsn); /* Use only allowed inside libgtp to keep compatiblity with deprecated APIs defined here. */
diff --git a/gtp/queue.c b/gtp/queue.c
index ce4713e..4c25913 100644
--- a/gtp/queue.c
+++ b/gtp/queue.c
@@ -172,6 +172,7 @@ int queue_newmsg(struct queue_t *queue, struct qmsg_t **qmsg,
} else {
*qmsg = &queue->qmsga[queue->next];
queue_seqset(queue, *qmsg, peer, seq);
+ INIT_LLIST_HEAD(&(*qmsg)->entry);
(*qmsg)->state = 1; /* Space taken */
(*qmsg)->this = queue->next;
(*qmsg)->next = -1; /* End of the queue */
@@ -206,6 +207,8 @@ int queue_freemsg(struct queue_t *queue, struct qmsg_t *qmsg)
return EOF; /* Not in queue */
}
+ llist_del(&qmsg->entry);
+
queue_seqdel(queue, qmsg);
if (qmsg->next == -1) /* Are we the last in queue? */
diff --git a/gtp/queue.h b/gtp/queue.h
index 76cb7be..9b0367b 100644
--- a/gtp/queue.h
+++ b/gtp/queue.h
@@ -17,6 +17,8 @@
#ifndef _QUEUE_H
#define _QUEUE_H
+#include <osmocom/core/linuxlist.h>
+
#include "gtp.h"
#define QUEUE_DEBUG 0 /* Print debug information */
@@ -39,6 +41,7 @@ struct qmsg_t { /* Holder for queued packets */
int this; /* Pointer to myself */
time_t timeout; /* When do we retransmit this packet? */
int retrans; /* How many times did we retransmit this? */
+ struct llist_head entry; /* Listed with other qmsg_t belonging to a pdp_t->qmsg_list_req */
};
struct queue_t {
diff --git a/lib/Makefile.am b/lib/Makefile.am
index b6e7aba..533d777 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -1,10 +1,10 @@
noinst_LIBRARIES = libmisc.a
-noinst_HEADERS = gnugetopt.h ippool.h lookup.h syserr.h tun.h in46_addr.h netdev.h gtp-kernel.h
+noinst_HEADERS = gnugetopt.h ippool.h lookup.h syserr.h tun.h in46_addr.h netdev.h gtp-kernel.h util.h
AM_CFLAGS = -O2 -fno-builtin -Wall -DSBINDIR='"$(sbindir)"' -ggdb $(LIBOSMOCORE_CFLAGS)
-libmisc_a_SOURCES = getopt1.c getopt.c ippool.c lookup.c tun.c debug.c in46_addr.c netdev.c
+libmisc_a_SOURCES = getopt1.c getopt.c ippool.c lookup.c tun.c debug.c in46_addr.c netdev.c util.c
if ENABLE_GTP_KERNEL
AM_CFLAGS += -DGTP_KERNEL $(LIBGTPNL_CFLAGS)
diff --git a/lib/gtp-kernel.c b/lib/gtp-kernel.c
index 48811bc..f6df408 100644
--- a/lib/gtp-kernel.c
+++ b/lib/gtp-kernel.c
@@ -26,6 +26,8 @@
#include "../lib/tun.h"
#include "../lib/syserr.h"
+#include "../lib/util.h"
+#include "../lib/ippool.h"
#include "../gtp/pdp.h"
#include "../gtp/gtp.h"
@@ -37,16 +39,23 @@
static void pdp_debug(const char *prefix, const char *devname, struct pdp_t *pdp)
{
- struct in46_addr ia46;
+ char buf4[INET_ADDRSTRLEN], buf6[INET6_ADDRSTRLEN];
+ struct ippoolm_t *peer;
struct in_addr ia;
- in46a_from_eua(&pdp->eua, &ia46);
+ buf4[0] = '\0';
+ if ((peer = pdp_get_peer_ipv(pdp, false)))
+ in46a_ntop(&peer->addr, buf4, sizeof(buf4));
+ buf6[0] = '\0';
+ if ((peer = pdp_get_peer_ipv(pdp, true)))
+ in46a_ntop(&peer->addr, buf6, sizeof(buf6));
+
gsna2in_addr(&ia, &pdp->gsnrc);
- LOGPDPX(DGGSN, LOGL_DEBUG, pdp, "%s %s v%u TEID %"PRIx64" EUA=%s SGSN=%s\n", prefix,
+ LOGPDPX(DGGSN, LOGL_DEBUG, pdp, "%s %s v%u TEID %"PRIx64" EUA=(%s,%s) SGSN=%s\n", prefix,
devname, pdp->version,
pdp->version == 0 ? pdp_gettid(pdp->imsi, pdp->nsapi) : pdp->teid_gn,
- in46a_ntoa(&ia46), inet_ntoa(ia));
+ buf4, buf6, inet_ntoa(ia));
}
static struct {
diff --git a/lib/in46_addr.c b/lib/in46_addr.c
index f4bb8a2..2562c71 100644
--- a/lib/in46_addr.c
+++ b/lib/in46_addr.c
@@ -60,7 +60,11 @@ int in46a_to_sas(struct sockaddr_storage *out, const struct in46_addr *in)
return 0;
}
-/*! Convenience wrapper around inet_ntop() for \ref in46_addr */
+/*! Convenience wrapper around inet_ntop() for in46_addr.
+ * \param[in] in the in46_addr to print
+ * \param[out] dst destination buffer where string representation of the address is stored
+ * \param[out] dst_size size dst. Usually it should be at least INET6_ADDRSTRLEN.
+ * \return address of dst on success, NULL on error */
const char *in46a_ntop(const struct in46_addr *in, char *dst, socklen_t dst_size)
{
int af;
diff --git a/lib/in46_addr.h b/lib/in46_addr.h
index e4654cc..153df00 100644
--- a/lib/in46_addr.h
+++ b/lib/in46_addr.h
@@ -31,3 +31,11 @@ unsigned int in46a_netmasklen(const struct in46_addr *netmask);
int in46a_to_eua(const struct in46_addr *src, unsigned int size, struct ul66_t *eua);
int in46a_from_eua(const struct ul66_t *eua, struct in46_addr *dst);
+
+static inline bool in46a_is_v6(const struct in46_addr *addr) {
+ return addr->len == 8 || addr->len == 16;
+}
+
+static inline bool in46a_is_v4(const struct in46_addr *addr) {
+ return addr->len == sizeof(struct in_addr);
+}
diff --git a/lib/util.c b/lib/util.c
new file mode 100644
index 0000000..6bb0d85
--- /dev/null
+++ b/lib/util.c
@@ -0,0 +1,35 @@
+/*
+ * misc helpers
+ * Copyright 2019 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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.
+ *
+ */
+
+#include "../gtp/pdp.h"
+
+#include "ippool.h"
+#include "in46_addr.h"
+
+/*! Get the peer of pdp based on IP version used.
+* \param[in] pdp PDP context to select the peer from.
+* \param[in] v4v6 IP version to select. Valid values are 4 and 6.
+* \returns The selected peer matching the given IP version. NULL if not present.
+*/
+struct ippoolm_t *pdp_get_peer_ipv(struct pdp_t *pdp, bool is_ipv6) {
+ uint8_t i;
+
+ for (i = 0; i < 2; i++) {
+ struct ippoolm_t * ippool = pdp->peer[i];
+ if (!ippool)
+ continue;
+ if (is_ipv6 && in46a_is_v6(&ippool->addr))
+ return ippool;
+ else if (!is_ipv6 && in46a_is_v4(&ippool->addr))
+ return ippool;
+ }
+ return NULL;
+}
diff --git a/lib/util.h b/lib/util.h
new file mode 100644
index 0000000..bc9674d
--- /dev/null
+++ b/lib/util.h
@@ -0,0 +1,18 @@
+#pragma once
+/*
+ * misc helpers
+ * Copyright 2019 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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.
+ *
+ */
+
+#include <stdbool.h>
+
+struct ippoolm_t;
+struct pdp_t;
+
+struct ippoolm_t *pdp_get_peer_ipv(struct pdp_t *pdp, bool is_ipv6);
diff --git a/sgsnemu/sgsnemu.c b/sgsnemu/sgsnemu.c
index 4f1f844..863ea51 100644
--- a/sgsnemu/sgsnemu.c
+++ b/sgsnemu/sgsnemu.c
@@ -1801,7 +1801,8 @@ int main(int argc, char **argv)
FD_SET(gsn->fd1c, &fds);
FD_SET(gsn->fd1u, &fds);
- gtp_retranstimeout(gsn, &idleTime);
+ idleTime.tv_sec = 10;
+ idleTime.tv_usec = 0;
ping_timeout(&idleTime);
if (options.debug)
@@ -1817,9 +1818,6 @@ int main(int argc, char **argv)
SYS_ERR(DSGSN, LOGL_ERROR, 0,
"Select returned -1");
break;
- case 0:
- gtp_retrans(gsn); /* Only retransmit if nothing else */
- break;
default:
break;
}