ggsn: Implement echo req/resp and recovery

This patch is quite big because implementing echo req/resp and recovery
requires having knowledge and managing differentiated state for each GSN
peer attached/connected to osmo-ggsn. This kind of information was not
available in osmo-ggsn nor in libgtp.

So osmo-ggsn is now able to track GSN peers connected to a
ggsn_ctx (associated gsn_t from libgtp) by means of "sgsn_peer" data
structure, and accessible from the ggsn through a list. The instances of
sgsn_peer are currently allocated and destroyed dynamically based on
discovered peer who have at least a pdp context attached to us (we are
not interested in peers without pdp contexts because we don't need to
send echo requests/responses and maintain state in that case).

A new private pointer (pdp_t->priv) data structure struct pdp_priv_t is
added to be able to relate a pdp_t to an sgsn as well as the already
existing pointer to an apn.

An "echo-interval <0-36000>" VTY command is added which allows
configuring time wait between echo requests being sent to each
sgsn_peer. Transmission of echo requests is disabled by default.

Finally, a new "show sgsn" VTY command is introduced, and its output is
also printed during "show ggsn".

Related: OS#4165
Change-Id: Id2c84165dc59dff495106758146a701ca488834f
This commit is contained in:
Pau Espin 2019-08-21 18:49:44 +02:00
parent 5d8b226597
commit f5fbb419ef
7 changed files with 406 additions and 11 deletions

View File

@ -1380,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>

View File

@ -12,4 +12,4 @@ osmo_ggsn_LDADD += $(LIBGTPNL_LIBS)
endif
osmo_ggsn_DEPENDENCIES = ../gtp/libgtp.la ../lib/libmisc.a
osmo_ggsn_SOURCES = ggsn_main.c ggsn_vty.c ggsn.c ggsn.h icmpv6.c icmpv6.h checksum.c checksum.h pco.c pco.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

View File

@ -61,6 +61,17 @@
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)
{
unsigned int i;
@ -77,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);
}
}
@ -341,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;
@ -356,6 +362,15 @@ static int delete_context(struct pdp_t *pdp)
LOGPPDP(LOGL_ERROR, pdp, "Cannot find/free IP Pool member\n");
}
if (!pdp_priv) {
LOGPPDP(LOGL_NOTICE, pdp, "Deleting PDP context: without private structure!\n");
return 0;
}
/* Remove from SGSN */
sgsn_peer_remove_pdp_priv(pdp_priv);
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",
@ -363,6 +378,8 @@ static int delete_context(struct pdp_t *pdp)
}
}
talloc_free(pdp_priv);
return 0;
}
@ -380,6 +397,36 @@ static bool apn_supports_ipv6(const struct apn_ctx *apn)
return false;
}
static struct sgsn_peer* ggsn_find_sgsn(struct ggsn_ctx *ggsn, struct in_addr *peer_addr)
{
struct sgsn_peer *sgsn;
llist_for_each_entry(sgsn, &ggsn->sgsn_list, entry) {
if (memcmp(&sgsn->addr, peer_addr, sizeof(*peer_addr)) == 0)
return sgsn;
}
return NULL;
}
static struct sgsn_peer* ggsn_find_or_create_sgsn(struct ggsn_ctx *ggsn, struct pdp_t *pdp)
{
struct sgsn_peer *sgsn;
struct in_addr ia;
if (gsna2in_addr(&ia, &pdp->gsnrc)) {
LOGPPDP(LOGL_ERROR, pdp, "Failed parsing gsnrc (len=%u) to discover SGSN\n",
pdp->gsnrc.l);
return NULL;
}
if ((sgsn = ggsn_find_sgsn(ggsn, &ia)))
return sgsn;
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)
{
static char name_buf[256];
@ -391,6 +438,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",
@ -492,7 +541,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 */
@ -707,6 +763,7 @@ static void ggsn_gtp_tmr_cb(void *data)
/* 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)
@ -725,12 +782,31 @@ static int cb_conf(int type, int cause, struct pdp_t *pdp, void *cbp)
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)
if (pdp)
rc = pdp_freepdp(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)
{
@ -778,6 +854,7 @@ int ggsn_start(struct ggsn_ctx *ggsn)
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;

View File

@ -14,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 */
@ -89,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;
@ -96,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 {
@ -112,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;
@ -145,6 +160,7 @@ 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)

View File

@ -40,6 +40,7 @@
#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"
@ -79,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;
@ -328,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 = {
@ -716,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);
}
@ -964,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,
@ -1016,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);

160
ggsn/sgsn.c Normal file
View File

@ -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;
}

46
ggsn/sgsn.h Normal file
View File

@ -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)