libosmo-gprs/src/llc/llc_llgmm.c

367 lines
12 KiB
C

/* GPRS LLC LLGM SAP as per 3GPP TS 44.064 7.2.1 */
/*
* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
#include <errno.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/logging.h>
#include <osmocom/crypt/gprs_cipher.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gprs/llc/llc.h>
#include <osmocom/gprs/llc/llc_prim.h>
#include <osmocom/gprs/llc/llc_private.h>
const struct value_string osmo_gprs_llc_llgmm_prim_type_names[] = {
{ OSMO_GPRS_LLC_LLGMM_ASSIGN, "ASSIGN" },
{ OSMO_GPRS_LLC_LLGMM_RESET, "RESET" },
{ OSMO_GPRS_LLC_LLGMM_TRIGGER, "TRIGGER" },
{ OSMO_GPRS_LLC_LLGMM_SUSPEND, "SUSPEND" },
{ OSMO_GPRS_LLC_LLGMM_RESUME, "RESUME" },
{ OSMO_GPRS_LLC_LLGMM_PAGE, "PAGE" },
{ OSMO_GPRS_LLC_LLGMM_IOV, "IOV" },
{ OSMO_GPRS_LLC_LLGMM_STATUS, "STATUS" },
{ OSMO_GPRS_LLC_LLGMM_PSHO, "PSHO" },
{ OSMO_GPRS_LLC_LLGMM_ASSIGN_UP, "ASSIGN-USERPLANE" },
{ 0, NULL }
};
/* Generate XID message that will cause the GMM to reset */
static int gprs_llc_lle_generate_xid_for_gmm_reset(struct gprs_llc_lle *lle,
uint8_t *bytes,
int bytes_len, uint32_t iov_ui)
{
struct gprs_llc_xid_field *fields;
const unsigned int fields_len = 2;
fields = (struct gprs_llc_xid_field *)talloc_zero_size(lle->llme, sizeof(*fields) * fields_len);
OSMO_ASSERT(fields);
/* First XID component must be RESET */
fields[0].type = OSMO_GPRS_LLC_XID_T_RESET;
fields[1].type = OSMO_GPRS_LLC_XID_T_IOV_UI;
fields[1].val = iov_ui;
talloc_free(lle->xid);
lle->xid = fields;
lle->xid_len = fields_len;
return gprs_llc_xid_encode(bytes, bytes_len, fields, fields_len);
}
/********************************
* Primitive allocation:
********************************/
static inline struct osmo_gprs_llc_prim *llc_prim_llgmm_alloc(enum osmo_gprs_llc_llgmm_prim_type type,
enum osmo_prim_operation operation,
unsigned int l3_len)
{
return gprs_llc_prim_alloc(OSMO_GPRS_LLC_SAP_LLGMM, type, operation, l3_len);
}
/* 7.2.1.1 LLGMM-ASSIGN.req (MS/SGSN):*/
struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_llgm_assign_req(uint32_t tlli)
{
struct osmo_gprs_llc_prim *llc_prim;
llc_prim = llc_prim_llgmm_alloc(OSMO_GPRS_LLC_LLGMM_ASSIGN, PRIM_OP_REQUEST, 0);
llc_prim->llgmm.tlli = tlli;
return llc_prim;
}
/* 7.2.1.2 LLGMM-RESET.req (SGSN):*/
struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_llgm_reset_req(uint32_t tlli)
{
struct osmo_gprs_llc_prim *llc_prim;
llc_prim = llc_prim_llgmm_alloc(OSMO_GPRS_LLC_LLGMM_RESET, PRIM_OP_REQUEST, 0);
llc_prim->llgmm.tlli = tlli;
return llc_prim;
}
/* 7.2.1.2 LLGMM-RESET.cnf (SGSN): */
struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_llgm_reset_cnf(uint32_t tlli)
{
struct osmo_gprs_llc_prim *llc_prim;
llc_prim = llc_prim_llgmm_alloc(OSMO_GPRS_LLC_LLGMM_RESET, PRIM_OP_CONFIRM, 0);
llc_prim->llgmm.tlli = tlli;
return llc_prim;
}
/* 7.2.1.3 LLGMM-TRIGGER.req (MS): */
struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_llgm_trigger_req(uint32_t tlli, uint8_t cause)
{
struct osmo_gprs_llc_prim *llc_prim;
llc_prim = llc_prim_llgmm_alloc(OSMO_GPRS_LLC_LLGMM_TRIGGER, PRIM_OP_REQUEST, 0);
llc_prim->llgmm.tlli = tlli;
llc_prim->llgmm.trigger_req.cause = cause;
return llc_prim;
}
/* 7.2.1.4 LLGMM-SUSPEND.req (MS/SGSN): */
struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_llgm_suspend_req(uint32_t tlli)
{
struct osmo_gprs_llc_prim *llc_prim;
llc_prim = llc_prim_llgmm_alloc(OSMO_GPRS_LLC_LLGMM_SUSPEND, PRIM_OP_REQUEST, 0);
llc_prim->llgmm.tlli = tlli;
return llc_prim;
}
/* 7.2.1.5 LLGMM-RESUME.req (MS/SGSN):*/
struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_llgm_resume_req(uint32_t tlli)
{
struct osmo_gprs_llc_prim *llc_prim;
llc_prim = llc_prim_llgmm_alloc(OSMO_GPRS_LLC_LLGMM_RESUME, PRIM_OP_REQUEST, 0);
llc_prim->llgmm.tlli = tlli;
return llc_prim;
}
/********************************
* Handling from upper layers:
********************************/
/* 7.2.1.1 LLGMM-ASSIGN.req (MS/SGSN):*/
static int llc_prim_handle_llgm_assign_req(struct osmo_gprs_llc_prim *llc_prim)
{
uint32_t old_tlli = llc_prim->llgmm.tlli;
uint32_t new_tlli = llc_prim->llgmm.assign_req.tlli_new;
int rc = 0;
bool free = false;
struct gprs_llc_llme *llme;
unsigned int i;
if (old_tlli == TLLI_UNASSIGNED && new_tlli == TLLI_UNASSIGNED) {
LOGLLC(LOGL_NOTICE, "Rx %s: Wrong input: Both old and new TLLI are unassigned!\n",
osmo_gprs_llc_prim_name(llc_prim));
rc = -EINVAL;
goto ret_free;
}
llme = gprs_llc_find_llme_by_tlli(old_tlli != TLLI_UNASSIGNED ? old_tlli : new_tlli);
if (!llme) {
LOGLLC(LOGL_NOTICE, "Rx %s: Unknown TLLI 0x%08x\n",
osmo_gprs_llc_prim_name(llc_prim), old_tlli);
rc = -ENOKEY;
goto ret_free;
}
LOGLLME(llme, LOGL_NOTICE, "LLGM Assign pre (%08x => %08x)\n", old_tlli, new_tlli);
if (old_tlli == TLLI_UNASSIGNED && new_tlli != TLLI_UNASSIGNED) {
/* TLLI Assignment 8.3.1 */
/* New TLLI shall be assigned and used when (re)transmitting LLC frames */
/* If old TLLI != TLLI_UNASSIGNED was assigned to LLME, then TLLI
* old is unassigned. Only TLLI new shall be accepted when
* received from peer. */
if (llme->old_tlli != TLLI_UNASSIGNED) {
llme->old_tlli = TLLI_UNASSIGNED;
llme->tlli = new_tlli;
} else {
/* If TLLI old == TLLI_UNASSIGNED was assigned to LLME, then this is
* TLLI assignmemt according to 8.3.1 */
llme->old_tlli = TLLI_UNASSIGNED;
llme->state = OSMO_GPRS_LLC_LLMS_ASSIGNED;
/* 8.5.3.1 For all LLE's */
for (i = 0; i < ARRAY_SIZE(llme->lle); i++) {
struct gprs_llc_lle *l = &llme->lle[i];
l->state = OSMO_GPRS_LLC_LLES_ASSIGNED_ADM;
if (llme->tlli != new_tlli) {
/* Only reset state if the new tlli is really changing.
* During GMM attachment, the TLLI is already known and
* used by LLC (LLME allocated on the fly), and hence it
* is expected to keep using previous state with the
* same TLLI from here onwards.
*/
l->vu_send = l->vu_recv = 0;
l->retrans_ctr = 0;
}
/* FIXME Set parameters according to table 9 */
}
llme->tlli = new_tlli;
}
} else if (old_tlli != TLLI_UNASSIGNED && new_tlli != TLLI_UNASSIGNED) {
/* TLLI Change 8.3.2 */
/* Both TLLI Old and TLLI New are assigned; use New when
* (re)transmitting. Accept both Old and New on Rx */
llme->old_tlli = old_tlli;
llme->tlli = new_tlli;
llme->state = OSMO_GPRS_LLC_LLMS_ASSIGNED;
} else if (old_tlli != TLLI_UNASSIGNED && new_tlli == TLLI_UNASSIGNED) {
/* TLLI Unassignment 8.3.3) */
llme->tlli = llme->old_tlli = 0;
llme->state = OSMO_GPRS_LLC_LLMS_ASSIGNED;
for (i = 0; i < ARRAY_SIZE(llme->lle); i++) {
struct gprs_llc_lle *l = &llme->lle[i];
l->state = OSMO_GPRS_LLC_LLES_UNASSIGNED;
}
free = true;
}
LOGLLME(llme, LOGL_NOTICE, "LLGM Assign post (%08x => %08x)\n", old_tlli, new_tlli);
if (free)
gprs_llc_llme_free(llme);
ret_free:
msgb_free(llc_prim->oph.msg);
return rc;
}
/* 7.2.1.2 LLGMM-RESET.req (SGSN):*/
static int llc_prim_handle_llgm_reset_req(struct osmo_gprs_llc_prim *llc_prim)
{
struct gprs_llc_lle *lle;
uint8_t xid_bytes[1024];
int xid_bytes_len;
int rc = 0;
struct gprs_llc_llme *llme = gprs_llc_find_llme_by_tlli(llc_prim->llgmm.tlli);
if (!llme) {
LOGLLC(LOGL_NOTICE, "Rx %s: Unknown TLLI 0x%08x\n",
osmo_gprs_llc_prim_name(llc_prim), llc_prim->llgmm.tlli);
rc = -ENOKEY;
goto ret_free;
}
LOGLLME(llme, LOGL_INFO, "%s\n", osmo_gprs_llc_prim_name(llc_prim));
lle = gprs_llc_llme_get_lle(llme, OSMO_GPRS_LLC_SAPI_GMM);
rc = osmo_get_rand_id((uint8_t *) &llme->iov_ui, 4);
if (rc < 0) {
LOGLLME(llme, LOGL_ERROR, "osmo_get_rand_id() failed for LLC XID reset: %s\n",
strerror(-rc));
goto ret_free;
}
/* Generate XID message */
xid_bytes_len = gprs_llc_lle_generate_xid_for_gmm_reset(lle, xid_bytes, sizeof(xid_bytes),
llme->iov_ui);
if (xid_bytes_len < 0) {
rc = -EINVAL;
goto ret_free;
}
/* Reset some of the LLC parameters. See GSM 04.64, 8.5.3.1 */
lle->vu_recv = 0;
lle->vu_send = 0;
lle->oc_ui_send = 0;
lle->oc_ui_recv = 0;
rc = gprs_llc_lle_tx_xid(lle, xid_bytes, xid_bytes_len, true);
ret_free:
msgb_free(llc_prim->oph.msg);
return rc;
}
/* 7.2.1.4 LLGMM-SUSPEND.req (MS/SGSN):*/
static int llc_prim_handle_llgm_suspend_req(struct osmo_gprs_llc_prim *llc_prim)
{
int rc = 0;
struct gprs_llc_llme *llme = gprs_llc_find_llme_by_tlli(llc_prim->llgmm.tlli);
if (!llme) {
LOGLLC(LOGL_NOTICE, "Rx %s: Unknown TLLI 0x%08x\n",
osmo_gprs_llc_prim_name(llc_prim), llc_prim->llgmm.tlli);
rc = -ENOKEY;
goto ret_free;
}
LOGLLME(llme, LOGL_INFO, "%s\n", osmo_gprs_llc_prim_name(llc_prim));
llme->suspended = true;
ret_free:
msgb_free(llc_prim->oph.msg);
return rc;
}
/* 7.2.1.5 LLGMM-RESUME.req (MS/SGSN):*/
static int llc_prim_handle_llgm_resume_req(struct osmo_gprs_llc_prim *llc_prim)
{
int rc = 0;
struct gprs_llc_llme *llme = gprs_llc_find_llme_by_tlli(llc_prim->llgmm.tlli);
if (!llme) {
LOGLLC(LOGL_NOTICE, "Rx %s: Unknown TLLI 0x%08x\n",
osmo_gprs_llc_prim_name(llc_prim), llc_prim->llgmm.tlli);
rc = -ENOKEY;
goto ret_free;
}
LOGLLME(llme, LOGL_INFO, "%s\n", osmo_gprs_llc_prim_name(llc_prim));
llme->suspended = false;
ret_free:
msgb_free(llc_prim->oph.msg);
return rc;
}
/* LLC higher layers push LLC primitive down to LLC layer: */
int gprs_llc_prim_llgmm_upper_down(struct osmo_gprs_llc_prim *llc_prim)
{
int rc;
switch (OSMO_PRIM_HDR(&llc_prim->oph)) {
case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_ASSIGN, PRIM_OP_REQUEST):
OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS ||
g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN);
rc = llc_prim_handle_llgm_assign_req(llc_prim);
break;
case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_RESET, PRIM_OP_REQUEST):
OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN);
rc = llc_prim_handle_llgm_reset_req(llc_prim);
break;
case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_TRIGGER, PRIM_OP_REQUEST):
OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS);
rc = gprs_llc_prim_handle_unsupported(llc_prim);
break;
case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_SUSPEND, PRIM_OP_REQUEST):
OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS ||
g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN);
rc = llc_prim_handle_llgm_suspend_req(llc_prim);
break;
case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_RESUME, PRIM_OP_REQUEST):
OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS ||
g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN);
rc = llc_prim_handle_llgm_resume_req(llc_prim);
break;
case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_IOV, PRIM_OP_REQUEST):
OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN);
rc = gprs_llc_prim_handle_unsupported(llc_prim);
break;
case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_PSHO, PRIM_OP_REQUEST):
OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS ||
g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN);
rc = gprs_llc_prim_handle_unsupported(llc_prim);
break;
case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_ASSIGN_UP, PRIM_OP_REQUEST):
OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS ||
g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN);
rc = gprs_llc_prim_handle_unsupported(llc_prim);
break;
default:
rc = -ENOTSUP;
msgb_free(llc_prim->oph.msg);
}
return rc;
}