356 lines
12 KiB
C
356 lines
12 KiB
C
/* GPRS LLC LL SAP as per 3GPP TS 44.064 7.2.2 */
|
|
/*
|
|
* (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_ll_prim_type_names[] = {
|
|
{ OSMO_GPRS_LLC_LL_RESET, "RESET" },
|
|
{ OSMO_GPRS_LLC_LL_ESTABLISH, "ESTABLISH" },
|
|
{ OSMO_GPRS_LLC_LL_RELEASE, "RELEASE" },
|
|
{ OSMO_GPRS_LLC_LL_XID, "XID" },
|
|
{ OSMO_GPRS_LLC_LL_DATA, "DATA" },
|
|
{ OSMO_GPRS_LLC_LL_UNITDATA, "UNITDATA" },
|
|
{ OSMO_GPRS_LLC_LL_STATUS, "STATUS" },
|
|
{ OSMO_GPRS_LLC_LL_ASSIGN, "ASSIGN" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/********************************
|
|
* Primitive allocation:
|
|
********************************/
|
|
|
|
static inline struct osmo_gprs_llc_prim *llc_prim_ll_alloc(enum osmo_gprs_llc_ll_prim_type type,
|
|
enum osmo_prim_operation operation,
|
|
unsigned int l3_len)
|
|
{
|
|
return gprs_llc_prim_alloc(OSMO_GPRS_LLC_SAP_LL, type, operation, l3_len);
|
|
}
|
|
|
|
/* 7.2.2.2 LL-ESTABLISH.req (MS/SGSN) */
|
|
struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_ll_establish_req(uint32_t tlli, enum osmo_gprs_llc_sapi ll_sapi, uint8_t *l3_par, unsigned int l3_par_len)
|
|
{
|
|
struct osmo_gprs_llc_prim *llc_prim;
|
|
llc_prim = llc_prim_ll_alloc(OSMO_GPRS_LLC_LL_ESTABLISH, PRIM_OP_REQUEST, l3_par_len);
|
|
llc_prim->ll.tlli = tlli;
|
|
llc_prim->ll.sapi = ll_sapi;
|
|
llc_prim->ll.l3_pdu = l3_par;
|
|
llc_prim->ll.l3_pdu_len = l3_par_len;
|
|
return llc_prim;
|
|
}
|
|
|
|
/* 7.2.2.2 LL-ESTABLISH.cnf (MS/SGSN) */
|
|
struct osmo_gprs_llc_prim *gprs_llc_prim_alloc_ll_establish_cnf(uint32_t tlli, enum osmo_gprs_llc_sapi ll_sapi, uint8_t *l3_par, unsigned int l3_par_len)
|
|
{
|
|
struct osmo_gprs_llc_prim *llc_prim;
|
|
llc_prim = llc_prim_ll_alloc(OSMO_GPRS_LLC_LL_ESTABLISH, PRIM_OP_CONFIRM, l3_par_len);
|
|
llc_prim->ll.tlli = tlli;
|
|
llc_prim->ll.sapi = ll_sapi;
|
|
llc_prim->ll.l3_pdu = l3_par;
|
|
llc_prim->ll.l3_pdu_len = l3_par_len;
|
|
return llc_prim;
|
|
}
|
|
|
|
/* 7.2.2.4 LL-XID.req (MS/SGSN) */
|
|
struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_ll_xid_req(uint32_t tlli, enum osmo_gprs_llc_sapi ll_sapi, uint8_t *l3_par, unsigned int l3_par_len)
|
|
{
|
|
struct osmo_gprs_llc_prim *llc_prim;
|
|
llc_prim = llc_prim_ll_alloc(OSMO_GPRS_LLC_LL_XID, PRIM_OP_REQUEST, l3_par_len);
|
|
llc_prim->ll.tlli = tlli;
|
|
llc_prim->ll.sapi = ll_sapi;
|
|
llc_prim->ll.l3_pdu = l3_par;
|
|
llc_prim->ll.l3_pdu_len = l3_par_len;
|
|
return llc_prim;
|
|
}
|
|
|
|
/* 7.2.2.4 LL-XID.ind (MS/SGSN) */
|
|
struct osmo_gprs_llc_prim *gprs_llc_prim_alloc_ll_xid_ind(uint32_t tlli, enum osmo_gprs_llc_sapi ll_sapi, uint8_t *l3_par, unsigned int l3_par_len)
|
|
{
|
|
struct osmo_gprs_llc_prim *llc_prim;
|
|
llc_prim = llc_prim_ll_alloc(OSMO_GPRS_LLC_LL_XID, PRIM_OP_INDICATION, l3_par_len);
|
|
llc_prim->ll.tlli = tlli;
|
|
llc_prim->ll.sapi = ll_sapi;
|
|
llc_prim->ll.l3_pdu = l3_par;
|
|
llc_prim->ll.l3_pdu_len = l3_par_len;
|
|
return llc_prim;
|
|
}
|
|
|
|
/* 7.2.2.4 LL-XID.resp (MS/SGSN) */
|
|
struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_ll_xid_resp(uint32_t tlli, enum osmo_gprs_llc_sapi ll_sapi, uint8_t *l3_par, unsigned int l3_par_len)
|
|
{
|
|
struct osmo_gprs_llc_prim *llc_prim;
|
|
llc_prim = llc_prim_ll_alloc(OSMO_GPRS_LLC_LL_XID, PRIM_OP_RESPONSE, l3_par_len);
|
|
llc_prim->ll.tlli = tlli;
|
|
llc_prim->ll.sapi = ll_sapi;
|
|
llc_prim->ll.l3_pdu = l3_par;
|
|
llc_prim->ll.l3_pdu_len = l3_par_len;
|
|
return llc_prim;
|
|
}
|
|
|
|
/* 7.2.2.4 LL-XID.cnf (MS/SGSN) */
|
|
struct osmo_gprs_llc_prim *gprs_llc_prim_alloc_ll_xid_cnf(uint32_t tlli, enum osmo_gprs_llc_sapi ll_sapi, uint8_t *l3_par, unsigned int l3_par_len)
|
|
{
|
|
struct osmo_gprs_llc_prim *llc_prim;
|
|
llc_prim = llc_prim_ll_alloc(OSMO_GPRS_LLC_LL_XID, PRIM_OP_CONFIRM, l3_par_len);
|
|
llc_prim->ll.tlli = tlli;
|
|
llc_prim->ll.sapi = ll_sapi;
|
|
llc_prim->ll.l3_pdu = l3_par;
|
|
llc_prim->ll.l3_pdu_len = l3_par_len;
|
|
return llc_prim;
|
|
}
|
|
|
|
/* 7.2.2.6 LL-UNITDATA.req (MS/SGSN):*/
|
|
struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_ll_unitdata_req(
|
|
uint32_t tlli, enum osmo_gprs_llc_sapi ll_sapi, uint8_t *l3_pdu, size_t l3_pdu_len)
|
|
{
|
|
struct osmo_gprs_llc_prim *llc_prim;
|
|
llc_prim = llc_prim_ll_alloc(OSMO_GPRS_LLC_LL_UNITDATA, PRIM_OP_REQUEST, l3_pdu_len);
|
|
llc_prim->ll.tlli = tlli;
|
|
llc_prim->ll.sapi = ll_sapi;
|
|
llc_prim->ll.l3_pdu = l3_pdu;
|
|
llc_prim->ll.l3_pdu_len = l3_pdu_len;
|
|
return llc_prim;
|
|
}
|
|
|
|
/* 7.2.2.6 LL-UNITDATA.ind (MS/SGSN):*/
|
|
struct osmo_gprs_llc_prim *gprs_llc_prim_alloc_ll_unitdata_ind(
|
|
uint32_t tlli, enum osmo_gprs_llc_sapi ll_sapi, uint8_t *l3_pdu, size_t l3_pdu_len)
|
|
{
|
|
struct osmo_gprs_llc_prim *llc_prim;
|
|
llc_prim = llc_prim_ll_alloc(OSMO_GPRS_LLC_LL_UNITDATA, PRIM_OP_INDICATION, l3_pdu_len);
|
|
llc_prim->ll.tlli = tlli;
|
|
llc_prim->ll.sapi = ll_sapi;
|
|
llc_prim->ll.l3_pdu = l3_pdu;
|
|
llc_prim->ll.l3_pdu_len = l3_pdu_len;
|
|
return llc_prim;
|
|
}
|
|
|
|
/* LL-ASSIGN.ind (MS/SGSN): Osmocom specific, used to inform TLLI update LLC->SNDCP */
|
|
struct osmo_gprs_llc_prim *gprs_llc_prim_alloc_ll_assign_ind(uint32_t old_tlli, uint32_t new_tlli)
|
|
{
|
|
struct osmo_gprs_llc_prim *llc_prim;
|
|
llc_prim = llc_prim_ll_alloc(OSMO_GPRS_LLC_LL_ASSIGN, PRIM_OP_INDICATION, 0);
|
|
llc_prim->ll.tlli = old_tlli;
|
|
llc_prim->ll.sapi = OSMO_GPRS_LLC_SAPI_SNDCP3; /* any SNDCP SAPI is good */
|
|
llc_prim->ll.assign_ind.tlli_new = new_tlli;
|
|
return llc_prim;
|
|
}
|
|
|
|
/********************************
|
|
* Handling to upper layers:
|
|
********************************/
|
|
/* Submit LL-XID.ind to upper layers (Figure 17) */
|
|
int gprs_llc_lle_submit_prim_ll_xid_ind(struct gprs_llc_lle *lle,
|
|
const struct gprs_llc_xid_field *xid_field_request_l3)
|
|
{
|
|
struct osmo_gprs_llc_prim *llc_prim_tx;
|
|
|
|
if (xid_field_request_l3) {
|
|
llc_prim_tx = gprs_llc_prim_alloc_ll_xid_ind(lle->llme->tlli, lle->sapi, NULL, xid_field_request_l3->var.val_len);
|
|
OSMO_ASSERT(llc_prim_tx);
|
|
llc_prim_tx->ll.l3_pdu_len = xid_field_request_l3->var.val_len;
|
|
llc_prim_tx->ll.l3_pdu = msgb_put(llc_prim_tx->oph.msg, llc_prim_tx->ll.l3_pdu_len);
|
|
if (llc_prim_tx->ll.l3_pdu_len > 0)
|
|
memcpy(llc_prim_tx->ll.l3_pdu, xid_field_request_l3->var.val,
|
|
llc_prim_tx->ll.l3_pdu_len);
|
|
} else {
|
|
llc_prim_tx = gprs_llc_prim_alloc_ll_xid_ind(lle->llme->tlli, lle->sapi, NULL, 0);
|
|
OSMO_ASSERT(llc_prim_tx);
|
|
}
|
|
llc_prim_tx->ll.xid.n201_i = lle->params.n201_i;
|
|
llc_prim_tx->ll.xid.n201_u = lle->params.n201_u;
|
|
|
|
return gprs_llc_prim_call_up_cb(llc_prim_tx);
|
|
}
|
|
|
|
/* Submit LL-XID.cnf to upper layers */
|
|
int gprs_llc_lle_submit_prim_ll_xid_cnf(struct gprs_llc_lle *lle,
|
|
const struct gprs_llc_xid_field *xid_field_response_l3,
|
|
const struct gprs_llc_xid_field *xid_field_request_l3)
|
|
{
|
|
struct osmo_gprs_llc_prim *llc_prim_tx;
|
|
OSMO_ASSERT(xid_field_response_l3);
|
|
llc_prim_tx = gprs_llc_prim_alloc_ll_xid_cnf(lle->llme->tlli, lle->sapi, NULL, xid_field_response_l3->var.val_len);
|
|
OSMO_ASSERT(llc_prim_tx);
|
|
llc_prim_tx->ll.l3_pdu_len = xid_field_response_l3->var.val_len;
|
|
llc_prim_tx->ll.l3_pdu = msgb_put(llc_prim_tx->oph.msg, llc_prim_tx->ll.l3_pdu_len);
|
|
if (llc_prim_tx->ll.l3_pdu_len > 0)
|
|
memcpy(llc_prim_tx->ll.l3_pdu, xid_field_response_l3->var.val,
|
|
llc_prim_tx->ll.l3_pdu_len);
|
|
llc_prim_tx->ll.xid.n201_i = lle->params.n201_i;
|
|
llc_prim_tx->ll.xid.n201_u = lle->params.n201_u;
|
|
|
|
/* TODO: do something with following. Is it actually needed? */
|
|
(void)xid_field_request_l3;
|
|
|
|
return gprs_llc_prim_call_up_cb(llc_prim_tx);
|
|
}
|
|
|
|
int gprs_llc_llme_submit_prim_ll_assign_ind(uint32_t old_tlli, uint32_t new_tlli)
|
|
{
|
|
struct osmo_gprs_llc_prim *llc_prim_tx;
|
|
llc_prim_tx = gprs_llc_prim_alloc_ll_assign_ind(old_tlli, new_tlli);
|
|
OSMO_ASSERT(llc_prim_tx);
|
|
|
|
return gprs_llc_prim_call_up_cb(llc_prim_tx);
|
|
}
|
|
|
|
/********************************
|
|
* Handling from upper layers:
|
|
********************************/
|
|
|
|
/* 7.2.2.2 LL-ESTABLISH.req (MS/SGSN):*/
|
|
static int llc_prim_handle_ll_establish_req(struct osmo_gprs_llc_prim *llc_prim)
|
|
{
|
|
int rc = 0;
|
|
struct gprs_llc_lle *lle;
|
|
|
|
lle = gprs_llc_find_lle_by_tlli_sapi(llc_prim->ll.tlli, llc_prim->ll.sapi);
|
|
if (!lle) {
|
|
LOGLLC(LOGL_NOTICE, "Rx %s: Unknown TLLI 0x%08x SAPI 0x%02x\n",
|
|
osmo_gprs_llc_prim_name(llc_prim), llc_prim->ll.tlli,
|
|
llc_prim->ll.sapi);
|
|
rc = -ENOKEY;
|
|
goto ret_free;
|
|
}
|
|
|
|
rc = gprs_llc_lle_tx_sabm(lle, llc_prim->ll.l3_pdu, llc_prim->ll.l3_pdu_len);
|
|
|
|
ret_free:
|
|
msgb_free(llc_prim->oph.msg);
|
|
return rc;
|
|
}
|
|
|
|
/* 7.2.2.4 LL-XID.req (MS/SGSN):*/
|
|
static int llc_prim_handle_ll_xid_req(struct osmo_gprs_llc_prim *llc_prim)
|
|
{
|
|
int rc = 0;
|
|
struct gprs_llc_lle *lle;
|
|
|
|
lle = gprs_llc_find_lle_by_tlli_sapi(llc_prim->ll.tlli, llc_prim->ll.sapi);
|
|
if (!lle) {
|
|
LOGLLC(LOGL_NOTICE, "Rx %s: Unknown TLLI 0x%08x SAPI 0x%02x\n",
|
|
osmo_gprs_llc_prim_name(llc_prim), llc_prim->ll.tlli,
|
|
llc_prim->ll.sapi);
|
|
rc = -ENOKEY;
|
|
goto ret_free;
|
|
}
|
|
|
|
rc = gprs_llc_lle_tx_xid_req(lle, llc_prim->ll.l3_pdu, llc_prim->ll.l3_pdu_len);
|
|
|
|
ret_free:
|
|
msgb_free(llc_prim->oph.msg);
|
|
return rc;
|
|
}
|
|
|
|
/* 7.2.2.4 LL-XID.resp (MS/SGSN):*/
|
|
static int llc_prim_handle_ll_xid_resp(struct osmo_gprs_llc_prim *llc_prim)
|
|
{
|
|
int rc = 0;
|
|
struct gprs_llc_lle *lle;
|
|
|
|
lle = gprs_llc_find_lle_by_tlli_sapi(llc_prim->ll.tlli, llc_prim->ll.sapi);
|
|
if (!lle) {
|
|
LOGLLC(LOGL_NOTICE, "Rx %s: Unknown TLLI 0x%08x SAPI 0x%02x\n",
|
|
osmo_gprs_llc_prim_name(llc_prim), llc_prim->ll.tlli,
|
|
llc_prim->ll.sapi);
|
|
rc = -ENOKEY;
|
|
goto ret_free;
|
|
}
|
|
|
|
rc = gprs_llc_lle_tx_xid_resp(lle, llc_prim->ll.l3_pdu, llc_prim->ll.l3_pdu_len);
|
|
|
|
ret_free:
|
|
msgb_free(llc_prim->oph.msg);
|
|
return rc;
|
|
}
|
|
|
|
/* 7.2.2.6 LL-UNITDATA.req (MS/SGSN):*/
|
|
static int llc_prim_handle_ll_unitdata_req(struct osmo_gprs_llc_prim *llc_prim)
|
|
{
|
|
int rc = 0;
|
|
struct gprs_llc_lle *lle;
|
|
|
|
lle = gprs_llc_find_lle_by_tlli_sapi(llc_prim->ll.tlli, llc_prim->ll.sapi);
|
|
if (!lle) {
|
|
struct gprs_llc_llme *llme;
|
|
LOGLLC(LOGL_NOTICE, "Rx %s: unknown TLLI 0x%08x, creating LLME on the fly\n",
|
|
osmo_gprs_llc_prim_name(llc_prim), llc_prim->ll.tlli);
|
|
llme = gprs_llc_llme_alloc(llc_prim->ll.tlli);
|
|
lle = gprs_llc_llme_get_lle(llme, llc_prim->ll.sapi);
|
|
}
|
|
|
|
if (lle->llme->suspended && llc_prim->ll.sapi != OSMO_GPRS_LLC_SAPI_GMM) {
|
|
LOGLLE(lle, LOGL_NOTICE, "Dropping frame to transmit, LLME is suspended\n");
|
|
goto ret_free;
|
|
}
|
|
|
|
rc = gprs_llc_lle_tx_ui(lle, llc_prim->ll.l3_pdu, llc_prim->ll.l3_pdu_len,
|
|
llc_prim->ll.unitdata_req.apply_gea,
|
|
llc_prim->ll.unitdata_req.radio_prio);
|
|
|
|
ret_free:
|
|
msgb_free(llc_prim->oph.msg);
|
|
return rc;
|
|
}
|
|
|
|
/* LLC higher layers push LLC primitive down to LLC layer: */
|
|
int gprs_llc_prim_ll_upper_down(struct osmo_gprs_llc_prim *llc_prim)
|
|
{
|
|
int rc;
|
|
switch (OSMO_PRIM_HDR(&llc_prim->oph)) {
|
|
case OSMO_PRIM(OSMO_GPRS_LLC_LL_ESTABLISH, 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_ll_establish_req(llc_prim);
|
|
break;
|
|
case OSMO_PRIM(OSMO_GPRS_LLC_LL_XID, 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_ll_xid_req(llc_prim);
|
|
break;
|
|
case OSMO_PRIM(OSMO_GPRS_LLC_LL_XID, PRIM_OP_RESPONSE):
|
|
OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS ||
|
|
g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN);
|
|
rc = llc_prim_handle_ll_xid_resp(llc_prim);
|
|
break;
|
|
case OSMO_PRIM(OSMO_GPRS_LLC_LL_UNITDATA, 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_ll_unitdata_req(llc_prim);
|
|
break;
|
|
/* TODO: others */
|
|
default:
|
|
rc = gprs_llc_prim_handle_unsupported(llc_prim);
|
|
}
|
|
return rc;
|
|
}
|