mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-ggsn.git
synced 2025-10-23 00:12:08 +00:00
To handle both incoming and outgoing SGSN Context via GTPv1. Introduce a FSM which handles the state of SGSN Context to simplify the API for the SGSN which uses libgtp as well as the external SGSN which communicates via GTP. Change-Id: Idb8ed0bb87200a68bb8caedd734fc070df9179c0
513 lines
13 KiB
C
513 lines
13 KiB
C
/*
|
|
* OsmoGGSN - Gateway GPRS Support Node
|
|
* Copyright (C) 2024 sysmocom s.f.m.c. GmbH
|
|
*
|
|
* Author: Alexander Couzens <lynxis@fe80.eu>
|
|
*
|
|
* 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 <osmocom/core/fsm.h>
|
|
|
|
#include <osmocom/core/logging.h>
|
|
#include <osmocom/core/tdef.h>
|
|
|
|
#include <osmocom/gtp/gsn.h>
|
|
#include <osmocom/gtp/gtp.h>
|
|
#include <osmocom/gtp/gtpie.h>
|
|
|
|
#include "gtp_sgsn_ctx.h"
|
|
|
|
#define X(s) (1 << (s))
|
|
|
|
/* Handle the logic part of the SGSN Context Req/Resp/Ack */
|
|
|
|
const struct value_string sgsn_ctx_event_names[] = {
|
|
OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_REQ),
|
|
OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_RESP),
|
|
OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_RESP_FAIL),
|
|
OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_ACK),
|
|
OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_NACK),
|
|
|
|
OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_REQ),
|
|
OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_RESP),
|
|
OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_RESP_FAIL),
|
|
OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_ACK),
|
|
OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_NACK),
|
|
{ 0, NULL }
|
|
};
|
|
|
|
static const struct osmo_tdef_state_timeout sgsn_ctx_fsm_timeouts[32] = {
|
|
[SGSN_CTX_REQ_ST_START] = { },
|
|
|
|
[SGSN_CTX_REQ_ST_WAIT_LOCAL_RESP] = { },
|
|
[SGSN_CTX_REQ_ST_WAIT_REMOTE_ACK] = { },
|
|
|
|
[SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP] = { },
|
|
[SGSN_CTX_REQ_ST_WAIT_LOCAL_ACK] = { },
|
|
};
|
|
|
|
struct osmo_tdef gtp_tdefs_sgsn_ctx[] = {
|
|
{ /* terminator */ }
|
|
};
|
|
|
|
/* Used to pass all relevant data to the fsm via data */
|
|
struct ctx_msg {
|
|
/* Message type */
|
|
uint8_t msg;
|
|
union gtpie_member * const *ie;
|
|
unsigned int ie_size;
|
|
};
|
|
|
|
#define sgsn_ctx_fsm_state_chg(fi, NEXT_STATE) \
|
|
osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, sgsn_ctx_fsm_timeouts, gtp_tdefs_sgsn_ctx, 10)
|
|
|
|
/* Direction of the SGSN context.
|
|
* Outgoing Ctx: From local to remote.
|
|
* Incoming Ctx: From remote to local.
|
|
*/
|
|
static void sgsn_ctx_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct sgsn_ctx_req *req = fi->priv;
|
|
struct gsn_t *gsn = req->gsn;
|
|
struct ctx_msg *msg = data;
|
|
|
|
switch (event) {
|
|
case SGSN_CTX_REQ_E_RX_REQ:
|
|
/* remote SGSN ask this peer */
|
|
sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_LOCAL_RESP);
|
|
if (gsn->cb_sgsn_context_request_ind)
|
|
gsn->cb_sgsn_context_request_ind(gsn, &req->peer, req->local_teic, msg->ie, msg->ie_size);
|
|
break;
|
|
case SGSN_CTX_REQ_E_TX_REQ:
|
|
/* local SGSN ask remote peer */
|
|
sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
/* Outgoing Ctx: remote SGSN ask this peer, waiting for a resp from this node (SGSN) */
|
|
static void sgsn_ctx_wait_local_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
switch (event) {
|
|
case SGSN_CTX_REQ_E_TX_RESP:
|
|
sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_REMOTE_ACK);
|
|
break;
|
|
case SGSN_CTX_REQ_E_TX_RESP_FAIL:
|
|
sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
/* Outgoing Ctx: This node (SGSN) answered with a Ctx Resp, waiting for a remote Ack or Nack */
|
|
static void sgsn_ctx_wait_remote_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct sgsn_ctx_req *req = fi->priv;
|
|
struct gsn_t *gsn = req->gsn;
|
|
struct ctx_msg *msg = data;
|
|
|
|
switch (event) {
|
|
case SGSN_CTX_REQ_E_RX_ACK:
|
|
if (gsn->cb_sgsn_context_ack_ind)
|
|
gsn->cb_sgsn_context_ack_ind(gsn, &req->peer, req->local_teic, msg->ie, msg->ie_size);
|
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, NULL);
|
|
break;
|
|
case SGSN_CTX_REQ_E_RX_NACK:
|
|
if (gsn->cb_sgsn_context_ack_ind)
|
|
gsn->cb_sgsn_context_ack_ind(gsn, &req->peer, req->local_teic, msg->ie, msg->ie_size);
|
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
/* Incoming Ctx: This node requested a remote SGSN. Waiting for a remote response */
|
|
static void sgsn_ctx_wait_remote_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct sgsn_ctx_req *req = fi->priv;
|
|
struct gsn_t *gsn = req->gsn;
|
|
struct ctx_msg *msg = data;
|
|
|
|
switch (event) {
|
|
case SGSN_CTX_REQ_E_RX_RESP:
|
|
sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_LOCAL_ACK);
|
|
if (gsn->cb_sgsn_context_response_ind)
|
|
gsn->cb_sgsn_context_response_ind(gsn, &req->peer, req->local_teic, msg->ie, msg->ie_size);
|
|
break;
|
|
case SGSN_CTX_REQ_E_RX_RESP_FAIL:
|
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
/* Incoming Ctx: Received a Response, waiting for this node to respond with an Ack or Nack */
|
|
static void sgsn_ctx_local_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
switch (event) {
|
|
case SGSN_CTX_REQ_E_TX_ACK:
|
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
|
|
break;
|
|
case SGSN_CTX_REQ_E_TX_NACK:
|
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
int sgsn_ctx_timer_cb(struct osmo_fsm_inst *fi)
|
|
{
|
|
/* Terminating the FSM when a timeout happens */
|
|
return 1;
|
|
}
|
|
|
|
static struct osmo_fsm_state sgsn_ctx_req_states[] = {
|
|
[SGSN_CTX_REQ_ST_START] = {
|
|
.in_event_mask =
|
|
X(SGSN_CTX_REQ_E_RX_REQ) |
|
|
X(SGSN_CTX_REQ_E_TX_REQ),
|
|
.out_state_mask =
|
|
X(SGSN_CTX_REQ_ST_WAIT_LOCAL_RESP) |
|
|
X(SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP),
|
|
.name = "Init",
|
|
.action = sgsn_ctx_init,
|
|
},
|
|
|
|
[SGSN_CTX_REQ_ST_WAIT_LOCAL_RESP] = {
|
|
.in_event_mask =
|
|
X(SGSN_CTX_REQ_E_TX_RESP) |
|
|
X(SGSN_CTX_REQ_E_TX_RESP_FAIL),
|
|
.out_state_mask =
|
|
X(SGSN_CTX_REQ_ST_WAIT_REMOTE_ACK),
|
|
.name = "Wait Local Response",
|
|
.action = sgsn_ctx_wait_local_resp,
|
|
},
|
|
[SGSN_CTX_REQ_ST_WAIT_REMOTE_ACK] = {
|
|
.in_event_mask =
|
|
X(SGSN_CTX_REQ_E_RX_ACK) |
|
|
X(SGSN_CTX_REQ_E_RX_NACK),
|
|
.out_state_mask = 0,
|
|
.name = "Wait Remote Ack",
|
|
.action = sgsn_ctx_wait_remote_ack,
|
|
},
|
|
|
|
[SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP] = {
|
|
.in_event_mask =
|
|
X(SGSN_CTX_REQ_E_RX_RESP) |
|
|
X(SGSN_CTX_REQ_E_RX_RESP_FAIL),
|
|
.out_state_mask =
|
|
X(SGSN_CTX_REQ_ST_WAIT_LOCAL_ACK),
|
|
.name = "Wait Remote Response",
|
|
.action = sgsn_ctx_wait_remote_resp,
|
|
},
|
|
[SGSN_CTX_REQ_ST_WAIT_LOCAL_ACK] = {
|
|
.in_event_mask =
|
|
X(SGSN_CTX_REQ_E_TX_ACK) |
|
|
X(SGSN_CTX_REQ_E_TX_NACK),
|
|
.out_state_mask = 0,
|
|
.name = "Wait Local Ack",
|
|
.action = sgsn_ctx_local_ack,
|
|
},
|
|
};
|
|
|
|
struct osmo_fsm sgsn_ctx_req_fsm = {
|
|
.name = "SGSNCtxReq",
|
|
.states = sgsn_ctx_req_states,
|
|
.num_states = ARRAY_SIZE(sgsn_ctx_req_states),
|
|
.event_names = sgsn_ctx_event_names,
|
|
.log_subsys = DLGTP,
|
|
.timer_cb = sgsn_ctx_timer_cb,
|
|
};
|
|
|
|
static __attribute__((constructor)) void sgsn_ctx_register(void)
|
|
{
|
|
OSMO_ASSERT(osmo_fsm_register(&sgsn_ctx_req_fsm) == 0);
|
|
}
|
|
|
|
struct sgsn_ctx_reqs *sgsn_ctx_reqs_init(void *ctx, uint32_t lowest_teic, uint32_t highest_teic)
|
|
{
|
|
struct sgsn_ctx_reqs *reqs;
|
|
|
|
if (lowest_teic >= highest_teic)
|
|
return NULL;
|
|
|
|
reqs = talloc_zero(ctx, struct sgsn_ctx_reqs);
|
|
if (!reqs)
|
|
return reqs;
|
|
|
|
reqs->lowest_teic = reqs->next_teic = lowest_teic;
|
|
reqs->high_teic = highest_teic;
|
|
|
|
INIT_LLIST_HEAD(&reqs->local_reqs);
|
|
INIT_LLIST_HEAD(&reqs->remote_reqs);
|
|
|
|
return reqs;
|
|
}
|
|
|
|
static uint32_t get_next_teic(struct gsn_t *gsn)
|
|
{
|
|
uint32_t teic = gsn->sgsn_ctx->next_teic++;
|
|
|
|
if (gsn->sgsn_ctx->next_teic > gsn->sgsn_ctx->high_teic)
|
|
gsn->sgsn_ctx->next_teic = gsn->sgsn_ctx->lowest_teic;
|
|
|
|
/* FIXME: check if this is already assigned! */
|
|
|
|
return teic;
|
|
}
|
|
|
|
static struct sgsn_ctx_req *_sgsn_ctx_req_alloc(struct gsn_t *gsn)
|
|
{
|
|
struct sgsn_ctx_req *req = talloc_zero(gsn, struct sgsn_ctx_req);
|
|
|
|
if (!req)
|
|
return NULL;
|
|
|
|
req->fi = osmo_fsm_inst_alloc(&sgsn_ctx_req_fsm, req, req, LOGL_INFO, NULL);
|
|
if (!req->fi)
|
|
goto out;
|
|
|
|
req->gsn = gsn;
|
|
req->local_teic = get_next_teic(gsn);
|
|
return req;
|
|
|
|
out:
|
|
osmo_fsm_inst_free(req->fi);
|
|
talloc_free(req);
|
|
return NULL;
|
|
}
|
|
|
|
/* Alloc a SGSN_CTX for an outgoing Context (Remote SGSN requested) */
|
|
static struct sgsn_ctx_req *sgsn_ctx_req_alloc_outgoing(struct gsn_t *gsn, const struct sockaddr_in *peer, uint16_t seq)
|
|
{
|
|
struct sgsn_ctx_req *req = _sgsn_ctx_req_alloc(gsn);
|
|
if (!req)
|
|
return req;
|
|
|
|
req->seq = seq;
|
|
req->peer = *peer;
|
|
llist_add_tail(&req->list, &gsn->sgsn_ctx->remote_reqs);
|
|
|
|
return req;
|
|
}
|
|
|
|
/* Alloc a SGSN_CTX for an incoming Context (Local SGSN requested) */
|
|
static struct sgsn_ctx_req *sgsn_ctx_req_alloc_incoming(struct gsn_t *gsn, const struct sockaddr_in *peer, uint16_t seq)
|
|
{
|
|
struct sgsn_ctx_req *req = _sgsn_ctx_req_alloc(gsn);
|
|
if (!req)
|
|
return req;
|
|
|
|
req->seq = seq;
|
|
req->peer = *peer;
|
|
llist_add_tail(&req->list, &gsn->sgsn_ctx->local_reqs);
|
|
|
|
return req;
|
|
}
|
|
|
|
static void sgsn_ctx_free(struct sgsn_ctx_req *req)
|
|
{
|
|
if (!req)
|
|
return;
|
|
|
|
osmo_fsm_inst_free(req->fi);
|
|
llist_del(&req->list);
|
|
talloc_free(req);
|
|
}
|
|
|
|
static struct sgsn_ctx_req *sgsn_ctx_by_teic(struct gsn_t *gsn, uint32_t teic, bool local_req)
|
|
{
|
|
struct sgsn_ctx_req *req;
|
|
struct llist_head *head;
|
|
|
|
if (local_req)
|
|
head = &gsn->sgsn_ctx->local_reqs;
|
|
else
|
|
head = &gsn->sgsn_ctx->remote_reqs;
|
|
|
|
llist_for_each_entry(req, head, list) {
|
|
if (req->local_teic == teic)
|
|
return req;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int sgsn_ctx_req_fsm_rx_req(struct gsn_t *gsn, const struct sockaddr_in *peer,
|
|
uint16_t seq,
|
|
union gtpie_member * const *ie, unsigned int ie_size)
|
|
{
|
|
struct sgsn_ctx_req *req = sgsn_ctx_req_alloc_outgoing(gsn, peer, seq);
|
|
struct ctx_msg ctx_msg = {
|
|
.msg = GTP_SGSN_CONTEXT_REQ,
|
|
.ie = ie,
|
|
.ie_size = ie_size,
|
|
};
|
|
|
|
if (!req)
|
|
return -ENOMEM;
|
|
|
|
if (!gsn->cb_sgsn_context_request_ind)
|
|
goto err;
|
|
|
|
if (gtpie_gettv4(ie, GTPIE_TEI_C, 0, &req->remote_teic))
|
|
goto err;
|
|
|
|
if (osmo_fsm_inst_dispatch(req->fi, SGSN_CTX_REQ_E_RX_REQ, &ctx_msg))
|
|
goto err;
|
|
|
|
return 0;
|
|
err:
|
|
sgsn_ctx_free(req);
|
|
return -EINVAL;
|
|
}
|
|
|
|
int sgsn_ctx_req_fsm_rx_resp(struct gsn_t *gsn, const struct sockaddr_in *peer,
|
|
uint16_t seq, uint32_t local_teic,
|
|
union gtpie_member * const *ie, unsigned int ie_size)
|
|
{
|
|
struct sgsn_ctx_req *req = sgsn_ctx_by_teic(gsn, local_teic, true);
|
|
uint8_t cause;
|
|
struct ctx_msg ctx_msg = {
|
|
.msg = GTP_SGSN_CONTEXT_RSP,
|
|
.ie = ie,
|
|
.ie_size = ie_size,
|
|
};
|
|
|
|
if (!req)
|
|
return -ENOENT;
|
|
|
|
if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause))
|
|
return -EINVAL;
|
|
|
|
if (gtpie_gettv4(ie, GTPIE_TEI_C, 0, &req->remote_teic) && cause == GTPCAUSE_ACC_REQ)
|
|
return -EINVAL;
|
|
|
|
if (osmo_fsm_inst_dispatch(req->fi,
|
|
cause == GTPCAUSE_ACC_REQ ? SGSN_CTX_REQ_E_RX_RESP : SGSN_CTX_REQ_E_RX_RESP_FAIL,
|
|
&ctx_msg))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sgsn_ctx_req_fsm_rx_ack(struct gsn_t *gsn, const struct sockaddr_in *peer,
|
|
uint16_t seq, uint32_t local_teic,
|
|
union gtpie_member * const *ie, unsigned int ie_size)
|
|
{
|
|
struct sgsn_ctx_req *req = sgsn_ctx_by_teic(gsn, local_teic, false);
|
|
struct ctx_msg ctx_msg = {
|
|
.msg = GTP_SGSN_CONTEXT_ACK,
|
|
.ie = ie,
|
|
.ie_size = ie_size,
|
|
};
|
|
|
|
if (!req)
|
|
return -ENOENT;
|
|
|
|
if (osmo_fsm_inst_dispatch(req->fi, SGSN_CTX_REQ_E_RX_ACK, &ctx_msg))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sgsn_ctx_req_fsm_tx_req(struct gsn_t *gsn, const struct sockaddr_in *peer,
|
|
union gtpie_member * const *ie, unsigned int ie_size,
|
|
uint32_t *local_teic, uint16_t seq)
|
|
{
|
|
struct sgsn_ctx_req *req = sgsn_ctx_req_alloc_incoming(gsn, peer, seq);
|
|
struct ctx_msg ctx_msg = {
|
|
.msg = GTP_SGSN_CONTEXT_REQ,
|
|
.ie = ie,
|
|
.ie_size = ie_size,
|
|
};
|
|
|
|
if (!req)
|
|
return -ENOMEM;
|
|
|
|
/* TODO: move tx GTPv1 into the fsm */
|
|
*local_teic = req->local_teic;
|
|
|
|
if (osmo_fsm_inst_dispatch(req->fi, SGSN_CTX_REQ_E_TX_REQ, &ctx_msg))
|
|
goto err;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
sgsn_ctx_free(req);
|
|
return -EINVAL;
|
|
}
|
|
|
|
int sgsn_ctx_req_fsm_tx_resp(struct gsn_t *gsn, struct sockaddr_in *peer,
|
|
uint16_t *seq, uint32_t local_teic, uint32_t *remote_teic,
|
|
union gtpie_member * const *ie, unsigned int ie_size)
|
|
{
|
|
struct sgsn_ctx_req *req = sgsn_ctx_by_teic(gsn, local_teic, false);
|
|
uint8_t cause;
|
|
struct ctx_msg ctx_msg = {
|
|
.msg = GTP_SGSN_CONTEXT_RSP,
|
|
.ie = ie,
|
|
.ie_size = ie_size,
|
|
};
|
|
|
|
if (!req)
|
|
return -ENOENT;
|
|
|
|
/* TODO: move tx GTPv1 into the fsm */
|
|
*seq = req->seq;
|
|
*peer = req->peer;
|
|
*remote_teic = req->remote_teic;
|
|
|
|
if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause))
|
|
return -EINVAL;
|
|
|
|
if (osmo_fsm_inst_dispatch(req->fi,
|
|
cause == GTPCAUSE_ACC_REQ ? SGSN_CTX_REQ_E_TX_RESP : SGSN_CTX_REQ_E_TX_RESP_FAIL,
|
|
&ctx_msg))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sgsn_ctx_req_fsm_tx_ack(struct gsn_t *gsn, struct sockaddr_in *peer,
|
|
uint16_t *seq, uint32_t local_teic, uint32_t *remote_teic,
|
|
union gtpie_member * const *ie, unsigned int ie_size)
|
|
{
|
|
struct sgsn_ctx_req *req = sgsn_ctx_by_teic(gsn, local_teic, true);
|
|
uint8_t cause;
|
|
struct ctx_msg ctx_msg = {
|
|
.msg = GTP_SGSN_CONTEXT_ACK,
|
|
.ie = ie,
|
|
.ie_size = ie_size,
|
|
};
|
|
|
|
if (!req)
|
|
return -ENOENT;
|
|
|
|
/* TODO: move tx GTPv1 into the fsm */
|
|
*seq = req->seq;
|
|
*peer = req->peer;
|
|
*remote_teic = req->remote_teic;
|
|
|
|
if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause))
|
|
return -EINVAL;
|
|
|
|
if (osmo_fsm_inst_dispatch(req->fi,
|
|
cause == GTPCAUSE_ACC_REQ ? SGSN_CTX_REQ_E_TX_ACK : SGSN_CTX_REQ_E_TX_NACK,
|
|
&ctx_msg))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|