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:
parent
5d8b226597
commit
f5fbb419ef
|
@ -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 <0-36000>'>
|
||||
<params>
|
||||
<param name='echo-interval' doc='Gateway GPRS Support NODE (GGSN)' />
|
||||
<param name='<0-36000>' doc='GGSN Number' />
|
||||
</params>
|
||||
</command>
|
||||
</node>
|
||||
<node id='config-ggsn-apn'>
|
||||
<name>config-ggsn-apn</name>
|
||||
|
|
|
@ -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
|
||||
|
|
97
ggsn/ggsn.c
97
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;
|
||||
|
|
16
ggsn/ggsn.h
16
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 <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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
Loading…
Reference in New Issue