diff --git a/doc/manuals/vty/ggsn_vty_reference.xml b/doc/manuals/vty/ggsn_vty_reference.xml index 15128ed..64bd07a 100644 --- a/doc/manuals/vty/ggsn_vty_reference.xml +++ b/doc/manuals/vty/ggsn_vty_reference.xml @@ -1380,6 +1380,18 @@ + + + + + + + + + + + + config-ggsn-apn diff --git a/ggsn/Makefile.am b/ggsn/Makefile.am index a8ddf1e..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_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 diff --git a/ggsn/ggsn.c b/ggsn/ggsn.c index 94f47e3..89b183f 100644 --- a/ggsn/ggsn.c +++ b/ggsn/ggsn.c @@ -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; diff --git a/ggsn/ggsn.h b/ggsn/ggsn.h index 6155b30..f23df54 100644 --- a/ggsn/ggsn.h +++ b/ggsn/ggsn.h @@ -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 ? */ + 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) diff --git a/ggsn/ggsn_vty.c b/ggsn/ggsn_vty.c index 5684f5a..cb92a8a 100644 --- a/ggsn/ggsn_vty.c +++ b/ggsn/ggsn_vty.c @@ -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); 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 +#include +#include +#include + +#include +#include + +#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)