libosmo-gprs/src/llc/llc.c

854 lines
25 KiB
C

/* GPRS LLC as per 3GPP TS 44.064 */
/*
* (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 <arpa/inet.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/logging.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>
struct gprs_llc_ctx *g_llc_ctx;
const struct value_string gprs_llc_llme_state_names[] = {
{ OSMO_GPRS_LLC_LLMS_UNASSIGNED, "UNASSIGNED" },
{ OSMO_GPRS_LLC_LLMS_ASSIGNED, "ASSIGNED" },
{ 0, NULL }
};
const struct value_string gprs_llc_lle_state_names[] = {
{ OSMO_GPRS_LLC_LLES_UNASSIGNED, "UNASSIGNED" },
{ OSMO_GPRS_LLC_LLES_ASSIGNED_ADM, "ASSIGNED_ADM" },
{ OSMO_GPRS_LLC_LLES_LOCAL_EST, "LOCAL_EST" },
{ OSMO_GPRS_LLC_LLES_REMOTE_EST, "REMOTE_EST" },
{ OSMO_GPRS_LLC_LLES_ABM, "ABM" },
{ OSMO_GPRS_LLC_LLES_LOCAL_REL, "LOCAL_REL" },
{ OSMO_GPRS_LLC_LLES_TIMER_REC, "TIMER_REC" },
{ 0, NULL }
};
/* Section 8.9.9 LLC layer parameter default values */
static const struct gprs_llc_params llc_default_params[NUM_SAPIS] = {
[1] = {
.t200_201 = 5,
.n200 = 3,
.n201_u = 400,
},
[2] = {
.t200_201 = 5,
.n200 = 3,
.n201_u = 270,
},
[3] = {
.iov_i_exp = 27,
.t200_201 = 5,
.n200 = 3,
.n201_u = 500,
.n201_i = 1503,
.mD = 1520,
.mU = 1520,
.kD = 16,
.kU = 16,
},
[5] = {
.iov_i_exp = 27,
.t200_201 = 10,
.n200 = 3,
.n201_u = 500,
.n201_i = 1503,
.mD = 760,
.mU = 760,
.kD = 8,
.kU = 8,
},
[7] = {
.t200_201 = 20,
.n200 = 3,
.n201_u = 270,
},
[8] = {
.t200_201 = 20,
.n200 = 3,
.n201_u = 270,
},
[9] = {
.iov_i_exp = 27,
.t200_201 = 20,
.n200 = 3,
.n201_u = 500,
.n201_i = 1503,
.mD = 380,
.mU = 380,
.kD = 4,
.kU = 4,
},
[11] = {
.iov_i_exp = 27,
.t200_201 = 40,
.n200 = 3,
.n201_u = 500,
.n201_i = 1503,
.mD = 190,
.mU = 190,
.kD = 2,
.kU = 2,
},
};
static void gprs_llc_ctx_free(void)
{
struct gprs_llc_llme *llme;
while ((llme = llist_first_entry_or_null(&g_llc_ctx->llme_list, struct gprs_llc_llme, list)))
gprs_llc_llme_free(llme);
talloc_free(g_llc_ctx);
}
int osmo_gprs_llc_init(enum osmo_gprs_llc_location location, const char *cipher_plugin_path)
{
int rc;
OSMO_ASSERT(location == OSMO_GPRS_LLC_LOCATION_MS || location == OSMO_GPRS_LLC_LOCATION_SGSN)
if ((rc = gprs_cipher_load(cipher_plugin_path)) != 0) {
LOGLLC(LOGL_NOTICE, "Failed loading GPRS cipher plugins from %s\n", cipher_plugin_path);
return rc;
}
if (g_llc_ctx)
gprs_llc_ctx_free();
g_llc_ctx = talloc_zero(NULL, struct gprs_llc_ctx);
g_llc_ctx->location = location;
INIT_LLIST_HEAD(&g_llc_ctx->llme_list);
return 0;
}
static void lle_init(struct gprs_llc_llme *llme, uint8_t sapi)
{
struct gprs_llc_lle *lle = &llme->lle[sapi];
lle->llme = llme;
lle->sapi = sapi;
lle->state = OSMO_GPRS_LLC_LLES_UNASSIGNED;
/* Initialize according to parameters */
memcpy(&lle->params, &llc_default_params[sapi], sizeof(lle->params));
}
struct gprs_llc_llme *gprs_llc_llme_alloc(uint32_t tlli)
{
struct gprs_llc_llme *llme;
uint32_t i;
llme = talloc_zero(g_llc_ctx, struct gprs_llc_llme);
if (!llme)
return NULL;
llme->tlli = tlli;
llme->old_tlli = TLLI_UNASSIGNED;
llme->state = OSMO_GPRS_LLC_LLMS_UNASSIGNED;
llme->age_timestamp = GPRS_LLME_RESET_AGE;
llme->cksn = GSM_KEY_SEQ_INVAL;
for (i = 0; i < ARRAY_SIZE(llme->lle); i++)
lle_init(llme, i);
llist_add(&llme->list, &g_llc_ctx->llme_list);
//llme->comp.proto = gprs_sndcp_comp_alloc(llme);
//llme->comp.data = gprs_sndcp_comp_alloc(llme);
return llme;
}
void gprs_llc_llme_free(struct gprs_llc_llme *llme)
{
/* TODO: here we probably need to trigger LLGMM-RESET or LLGMM-SUSPEND towards upper layers! */
//gprs_sndcp_sm_deactivate_ind_by_llme(llme);
//gprs_sndcp_comp_free(llme->comp.proto);
//gprs_sndcp_comp_free(llme->comp.data);
llist_del(&llme->list);
talloc_free(llme);
}
/* lookup LLC Entity based on TLLI */
struct gprs_llc_llme *gprs_llc_find_llme_by_tlli(uint32_t tlli)
{
struct gprs_llc_llme *llme;
llist_for_each_entry(llme, &g_llc_ctx->llme_list, list) {
if (llme->tlli == tlli || llme->old_tlli == tlli)
return llme;
}
return NULL;
}
/* lookup LLC Entity based on DLCI (TLLI+SAPI tuple) */
struct gprs_llc_lle *gprs_llc_find_lle_by_tlli_sapi(uint32_t tlli, uint8_t sapi)
{
struct gprs_llc_llme *llme = gprs_llc_find_llme_by_tlli(tlli);
if (!llme)
return NULL;
return &llme->lle[sapi];
}
static int gprs_llc_lle_tx_dm(const struct gprs_llc_lle *lle)
{
int rc;
struct msgb *msg;
struct gprs_llc_pdu_decoded pdu_dec = {
.sapi = lle->sapi,
.fmt = OSMO_GPRS_LLC_FMT_U,
.func = OSMO_GPRS_LLC_FUNC_DM,
.flags = OSMO_GPRS_LLC_PDU_F_FOLL_FIN,
};
struct osmo_gprs_llc_prim *llc_prim;
/* LLC payload is put directly below: */
llc_prim = gprs_llc_prim_alloc_bssgp_dl_unitdata_req(lle->llme->tlli, NULL, 4096 - sizeof(llc_prim));
msg = llc_prim->oph.msg;
msg->l3h = msg->tail;
rc = gprs_llc_pdu_encode(msg, &pdu_dec);
if (rc < 0) {
LOGLLC(LOGL_NOTICE, "Failed to encode U DM\n");
msgb_free(msg);
return rc;
}
llc_prim->bssgp.ll_pdu = msgb_l3(msg);
llc_prim->bssgp.ll_pdu_len = msgb_l3len(msg);
/* Send BSSGP-DL-UNITDATA.req */
gprs_llc_prim_call_down_cb(llc_prim);
return 0;
}
int gprs_llc_lle_tx_xid(const struct gprs_llc_lle *lle, uint8_t *xid_payload, unsigned int xid_payload_len, bool is_cmd)
{
int rc;
struct msgb *msg;
struct osmo_gprs_llc_prim *llc_prim;
struct gprs_llc_pdu_decoded pdu_dec = {
.sapi = lle->sapi,
.fmt = OSMO_GPRS_LLC_FMT_U,
.func = OSMO_GPRS_LLC_FUNC_XID,
.flags = OSMO_GPRS_LLC_PDU_F_FOLL_FIN,
.data_len = xid_payload_len,
.data = xid_payload,
};
gprs_llc_encode_is_cmd_as_cr(is_cmd, &pdu_dec.flags);
/* LLC payload is put directly below: */
if (g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN)
llc_prim = gprs_llc_prim_alloc_bssgp_dl_unitdata_req(lle->llme->tlli, NULL, 4096 - sizeof(llc_prim));
else
llc_prim = gprs_llc_prim_alloc_grr_unitdata_req(lle->llme->tlli, NULL, 4096 - sizeof(llc_prim));
msg = llc_prim->oph.msg;
msg->l3h = msg->tail;
rc = gprs_llc_pdu_encode(msg, &pdu_dec);
if (rc < 0) {
LOGLLC(LOGL_NOTICE, "Failed to encode U DM\n");
msgb_free(msg);
return rc;
}
if (g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS) {
llc_prim->bssgp.ll_pdu = msgb_l3(msg);
llc_prim->bssgp.ll_pdu_len = msgb_l3len(msg);
} else {
llc_prim->grr.ll_pdu = msgb_l3(msg);
llc_prim->grr.ll_pdu_len = msgb_l3len(msg);
}
/* Send GRR-UNITDATA.req */
gprs_llc_prim_call_down_cb(llc_prim);
return 0;
}
/* 6.4.1.7 NULL command */
int gprs_llc_lle_tx_null(const struct gprs_llc_lle *lle)
{
int rc;
struct msgb *msg;
struct gprs_llc_pdu_decoded pdu_dec = {
.sapi = lle->sapi,
.fmt = OSMO_GPRS_LLC_FMT_U,
.func = OSMO_GPRS_LLC_FUNC_NULL,
.flags = 0 /* P=0 */,
};
struct osmo_gprs_llc_prim *llc_prim;
/* LLC payload is put directly below: */
if (g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN)
llc_prim = gprs_llc_prim_alloc_bssgp_dl_unitdata_req(lle->llme->tlli, NULL, 4096 - sizeof(llc_prim));
else
llc_prim = gprs_llc_prim_alloc_grr_unitdata_req(lle->llme->tlli, NULL, 4096 - sizeof(llc_prim));
msg = llc_prim->oph.msg;
msg->l3h = msg->tail;
rc = gprs_llc_pdu_encode(msg, &pdu_dec);
if (rc < 0) {
LOGLLC(LOGL_NOTICE, "Failed to encode U DM\n");
msgb_free(msg);
return rc;
}
if (g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS) {
llc_prim->bssgp.ll_pdu = msgb_l3(msg);
llc_prim->bssgp.ll_pdu_len = msgb_l3len(msg);
} else {
llc_prim->grr.ll_pdu = msgb_l3(msg);
llc_prim->grr.ll_pdu_len = msgb_l3len(msg);
}
/* Send BSSGP-DL-UNITDATA.req (SGSN) / GRR-UNITDATA.req (MS) */
gprs_llc_prim_call_down_cb(llc_prim);
return 0;
}
/* Transmit a UI frame over the given SAPI:
'encryptable' indicates whether particular message can be encrypted according
to 3GPP TS 24.008 § 4.7.1.2
*/
int gprs_llc_lle_tx_ui(struct gprs_llc_lle *lle, uint8_t *l3_pdu, size_t l3_pdu_len, bool encryptable)
{
struct osmo_gprs_llc_prim *llc_prim;
struct msgb *msg;
int rc;
struct gprs_llc_pdu_decoded pdu_dec = {
.sapi = lle->sapi,
.fmt = OSMO_GPRS_LLC_FMT_UI,
.func = 0,
.flags = OSMO_GPRS_LLC_PDU_F_PROT_MODE,
.seq_tx = lle->vu_send,
.data_len = l3_pdu_len,
.data = l3_pdu,
};
gprs_llc_encode_is_cmd_as_cr(false, &pdu_dec.flags);
//TODO: what to do with:
// oc = lle->oc_ui_send;
if (pdu_dec.data_len > lle->params.n201_u) {
LOGLLE(lle, LOGL_ERROR, "Cannot Tx %zu bytes (N201-U=%u)\n",
pdu_dec.data_len, lle->params.n201_u);
return -EFBIG;
}
if (lle->llme->algo != GPRS_ALGO_GEA0 && encryptable)
pdu_dec.flags |= OSMO_GPRS_LLC_PDU_F_ENC_MODE;
/* TODO: we are probably missing the ciphering enc part, see osmo-sgsn apply_gea() */
/* LLC payload is put directly below: */
if (g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN)
llc_prim = gprs_llc_prim_alloc_bssgp_dl_unitdata_req(lle->llme->tlli, NULL, 4096 - sizeof(llc_prim));
else
llc_prim = gprs_llc_prim_alloc_grr_unitdata_req(lle->llme->tlli, NULL, 4096 - sizeof(llc_prim));
msg = llc_prim->oph.msg;
msg->l3h = msg->tail;
rc = gprs_llc_pdu_encode(msg, &pdu_dec);
if (rc < 0) {
LOGLLE(lle, LOGL_NOTICE, "Failed to encode U DM\n");
msgb_free(msg);
return rc;
}
if (g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN) {
llc_prim->bssgp.ll_pdu = msgb_l3(msg);
llc_prim->bssgp.ll_pdu_len = msgb_l3len(msg);
} else {
llc_prim->grr.ll_pdu = msgb_l3(msg);
llc_prim->grr.ll_pdu_len = msgb_l3len(msg);
llc_prim->grr.unitdata_req.sapi = lle->sapi;
}
/* Increment V(U) */
lle->vu_send = (lle->vu_send + 1) % 512;
/* Increment Overflow Counter, if needed */
if ((lle->vu_send + 1) / 512)
lle->oc_ui_send += 512;
/* Send BSSGP-DL-UNITDATA.req (SGSN) / GRR-UNITDATA.req (MS) */
rc = gprs_llc_prim_call_down_cb(llc_prim);
return rc;
}
/* lookup LLC Entity for RX based on DLCI (TLLI+SAPI tuple) */
struct gprs_llc_lle *gprs_llc_lle_for_rx_by_tlli_sapi(const uint32_t tlli,
uint8_t sapi, enum gprs_llc_frame_func cmd)
{
struct gprs_llc_lle *lle;
/* We already know about this TLLI */
lle = gprs_llc_find_lle_by_tlli_sapi(tlli, sapi);
if (lle)
return lle;
/* Maybe it is a routing area update but we already know this sapi? */
if (gprs_tlli_type(tlli) == TLLI_FOREIGN) {
lle = gprs_llc_find_lle_by_tlli_sapi(tlli, sapi);
if (lle) {
LOGLLC(LOGL_NOTICE,
"LLC RX: Found a local entry for TLLI 0x%08x\n", tlli);
return lle;
}
}
/* 7.2.1.1 LLC belonging to unassigned TLLI+SAPI shall be discarded,
* except UID and XID frames with SAPI=1 */
if (sapi == OSMO_GPRS_LLC_SAPI_GMM &&
(cmd == OSMO_GPRS_LLC_FUNC_XID || cmd == OSMO_GPRS_LLC_FUNC_UI)) {
struct gprs_llc_llme *llme;
/* FIXME: don't use the TLLI but the 0xFFFF unassigned? */
llme = gprs_llc_llme_alloc(tlli);
LOGLLME(llme, LOGL_NOTICE, "LLC RX: unknown TLLI 0x%08x, "
"creating LLME on the fly\n", tlli);
lle = &llme->lle[sapi];
return lle;
}
LOGLLC(LOGL_NOTICE, "unknown TLLI(0x%08x)/SAPI(%d): Silently dropping\n",
tlli, sapi);
return NULL;
}
/* Generate XID message */
static int gprs_llc_lle_generate_xid(struct gprs_llc_lle *lle, uint8_t *bytes, int bytes_len,
const uint8_t *l3par, unsigned int l3par_len)
{
unsigned int xid_fields_len = 3 + (l3par ? 1 : 0);
struct gprs_llc_xid_field *xid_fields =
(struct gprs_llc_xid_field *)talloc_zero_size(lle->llme, sizeof(*xid_fields) * xid_fields_len);
int rc;
OSMO_ASSERT(xid_fields);
xid_fields[0].type = OSMO_GPRS_LLC_XID_T_VERSION;
xid_fields[0].val = 0;
xid_fields[1].type = OSMO_GPRS_LLC_XID_T_N201_U;
xid_fields[1].val = lle->params.n201_u;
xid_fields[2].type = OSMO_GPRS_LLC_XID_T_N201_I;
xid_fields[2].val = lle->params.n201_i;
if (l3par_len > 0) {
xid_fields[3].type = OSMO_GPRS_LLC_XID_T_L3_PAR;
xid_fields[3].var.val_len = l3par_len;
if (l3par_len > 0) {
xid_fields[3].var.val = talloc_size(xid_fields, l3par_len);
memcpy(xid_fields[3].var.val, l3par, l3par_len);
}
} else {
xid_fields_len--;
}
/* Store generated XID for later reference */
talloc_free(lle->xid);
lle->xid = xid_fields;
lle->xid_len = xid_fields_len;
rc = gprs_llc_xid_encode(bytes, bytes_len, lle->xid, lle->xid_len);
return rc;
}
/* LL-ESTABLISH negotiation (See also: TS 101 351, Section 7.2.2.2) */
int gprs_llc_lle_tx_sabm(struct gprs_llc_lle *lle, uint8_t *l3par, unsigned int l3par_len)
{
LOGLLE(lle, LOGL_ERROR, "Tx SABM: ABM mode not supported yet!\n");
return -ENOTSUP;
}
/* Set of LL-XID negotiation (See also: TS 101 351, Section 7.2.2.4) */
int gprs_llc_lle_tx_xid_req(struct gprs_llc_lle *lle, uint8_t *l3par, unsigned int l3par_len)
{
uint8_t xid_bytes[1024];
int xid_bytes_len;
int rc;
/* Generate XID */
xid_bytes_len =
gprs_llc_lle_generate_xid(lle, xid_bytes, sizeof(xid_bytes), l3par, l3par_len);
/* Only perform XID sending if the XID message contains something */
if (xid_bytes_len > 0) {
/* Transmit XID bytes */
LOGLLE(lle, LOGL_NOTICE, "Sending XID type %s (%d bytes) request to MS...\n",
l3par ? "L3-Params" : "NULL", xid_bytes_len);
rc = gprs_llc_lle_tx_xid(lle, xid_bytes, xid_bytes_len, true);
} else {
LOGLLE(lle, LOGL_ERROR,
"XID-Message generation failed, XID not sent!\n");
rc = -EINVAL;
}
return rc;
}
/* Generate XID response from XID-Ind received from peer */
int gprs_llc_lle_tx_xid_resp(struct gprs_llc_lle *lle, uint8_t *l3par, unsigned int l3par_len)
{
uint8_t bytes_response[1024];
unsigned int rc, i;
/* Replace the SNDCP L3 xid_field with response from our upper layer: */
for (i = 0; i < lle->rx_xid_len; i++) {
struct gprs_llc_xid_field *xid_field_l3;
if (lle->rx_xid[i].type != OSMO_GPRS_LLC_XID_T_L3_PAR)
continue;
xid_field_l3 = &lle->rx_xid[i];
xid_field_l3->var.val = l3par;
xid_field_l3->var.val_len = l3par_len;
break;
}
rc = gprs_llc_xid_encode(bytes_response, sizeof(bytes_response),
lle->rx_xid, lle->rx_xid_len);
TALLOC_FREE(lle->rx_xid);
lle->rx_xid_len = 0;
if (rc < 0)
return rc;
rc = gprs_llc_lle_tx_xid(lle, bytes_response, rc, false);
return rc;
}
/* Process an incoming XID indication and generate an appropriate response */
static int gprs_llc_lle_process_xid_ind(struct gprs_llc_lle *lle,
uint8_t *bytes_request,
int bytes_request_len)
{
/* Note: This function computes the response that is sent back to the
* MS when a mobile originated XID is received. */
struct gprs_llc_xid_field xid_fields[16] = { 0 };
unsigned int xid_fields_len;
int rc, i;
struct gprs_llc_xid_field *xid_field_request_l3 = NULL;
/* Parse and analyze XID-Request */
rc = gprs_llc_xid_decode(xid_fields, ARRAY_SIZE(xid_fields),
bytes_request, bytes_request_len);
if (rc < 0) {
LOGLLE(lle, LOGL_ERROR, "Failed decoding XID Fields\n");
return rc;
}
xid_fields_len = rc;
/* FIXME: Check the incoming XID parameters for
* for validity. Currently we just blindly
* accept all XID fields by just echoing them.
* There is a remaining risk of malfunction
* when a MS submits values which defer from
* the default! */
/* Store last received XID-Ind from peer: */
lle->rx_xid = gprs_llc_xid_deepcopy(lle->llme, xid_fields, xid_fields_len);
OSMO_ASSERT(lle->rx_xid);
lle->rx_xid_len = xid_fields_len;
/* Forward SNDCP-XID fields to Layer 3 (SNDCP) */
for (i = 0; i < xid_fields_len; i++) {
if (xid_fields[i].type == OSMO_GPRS_LLC_XID_T_L3_PAR) {
xid_field_request_l3 = &xid_fields[i];
gprs_llc_lle_submit_prim_ll_xid_ind(lle, xid_field_request_l3);
/* delay answer until we get LL-XID.resp from SNDCP: */
return rc;
}
}
rc = gprs_llc_lle_tx_xid_resp(lle, NULL, 0);
return rc;
}
/* Process an incoming XID confirmation. 8.5.3.0 */
static int gprs_llc_lle_process_xid_conf(struct gprs_llc_lle *lle, uint8_t *bytes, int bytes_len)
{
/* Note: This function handles the response of a network originated
* XID-Request. There XID messages reflected by the MS are analyzed
* and processed here. The caller is called by rx_llc_xid(). */
struct gprs_llc_xid_field xid_fields[16] = { 0 };
unsigned int xid_fields_len;
struct gprs_llc_xid_field *xid_field;
struct gprs_llc_xid_field *xid_field_request_l3 = NULL;
unsigned int i;
int rc;
/* Pick layer3 XID from the XID request we have sent last */
if (lle->xid) {
for (i = 0; i < lle->xid_len; i++) {
if (lle->xid[i].type == OSMO_GPRS_LLC_XID_T_L3_PAR)
xid_field_request_l3 = &lle->xid[i];
}
}
/* Parse and analyze XID-Response */
rc = gprs_llc_xid_decode(xid_fields, ARRAY_SIZE(xid_fields),
bytes, bytes_len);
if (rc < 0) {
LOGLLE(lle, LOGL_ERROR, "Failed decoding XID Fields\n");
return rc;
}
xid_fields_len = rc;
for (i = 0; i < xid_fields_len; i++) {
xid_field = &xid_fields[i];
/* Forward SNDCP-XID fields to Layer 3 (SNDCP) */
if (xid_field->type == OSMO_GPRS_LLC_XID_T_L3_PAR && xid_field_request_l3) {
gprs_llc_lle_submit_prim_ll_xid_cnf(lle, xid_field, xid_field_request_l3);
/* TODO: sndcp_sn_xid_conf is basically primitive LL-XID.cnf. See 8.5.3.0 */
} else { /* Process LLC-XID fields: */
/* FIXME: Do something more useful with the
* echoed XID-Information. Currently we
* just ignore the response completely and
* by doing so we blindly accept any changes
* the MS might have done to the our XID
* inquiry. There is a remainig risk of
* malfunction! */
LOGLLE(lle, LOGL_NOTICE,
"Ignoring XID-Field: XID: type %s\n",
gprs_llc_xid_type_name(xid_field->type));
}
}
/* Flush pending XID fields */
TALLOC_FREE(lle->xid);
lle->xid_len = 0;
return 0;
}
/* Dispatch XID indications and responses coming from the MS */
static int gprs_llc_lle_rx_llc_xid(struct gprs_llc_lle *lle, struct gprs_llc_pdu_decoded *pdu_dec)
{
int rc = 0;
/* FIXME: 8.5.3.3: check if XID is invalid */
if (gprs_llc_received_cr_is_cmd(pdu_dec->flags & OSMO_GPRS_LLC_PDU_F_CMD_RSP)) {
LOGLLE(lle, LOGL_NOTICE, "Received XID indication from MS.\n");
rc = gprs_llc_lle_process_xid_ind(lle, pdu_dec->data, pdu_dec->data_len);
} else {
LOGLLE(lle, LOGL_NOTICE, "Received XID confirmation from MS\n");
rc = gprs_llc_lle_process_xid_conf(lle, pdu_dec->data, pdu_dec->data_len);
/* FIXME: if we had sent a XID reset, send
* LLGMM-RESET.conf to GMM */
}
return rc;
}
/* Receive and process decoded LLC PDU from lower layer (GRR/BSSGP): */
static int gprs_llc_lle_hdr_rx(struct gprs_llc_lle *lle, struct gprs_llc_pdu_decoded *pdu_dec)
{
const char *llc_pdu_name = gprs_llc_pdu_hdr_dump(pdu_dec);
LOGLLE(lle, LOGL_DEBUG, "Rx %s\n", llc_pdu_name);
switch (pdu_dec->func) {
case OSMO_GPRS_LLC_FUNC_SABM:
case OSMO_GPRS_LLC_FUNC_DISC:
/* send DM to properly signal we don't do ABM */
gprs_llc_lle_tx_dm(lle);
break;
case OSMO_GPRS_LLC_FUNC_XID: /* Section 6.4.1.6 */
gprs_llc_lle_rx_llc_xid(lle, pdu_dec);
break;
case OSMO_GPRS_LLC_FUNC_UI:
if (gprs_llc_is_retransmit(pdu_dec->seq_tx, lle->vu_recv)) {
LOGLLE(lle, LOGL_NOTICE,
"TLLI=%08x dropping UI, N(U=%d) not in window V(URV(UR:%d).\n",
lle->llme ? lle->llme->tlli : -1,
pdu_dec->seq_tx, lle->vu_recv);
/* HACK: non-standard recovery handling. If remote LLE
* is re-transmitting the same sequence number for
* three times, don't discard the frame but pass it on
* and 'learn' the new sequence number */
if (pdu_dec->seq_tx != lle->vu_recv_last) {
lle->vu_recv_last = pdu_dec->seq_tx;
lle->vu_recv_duplicates = 0;
} else {
lle->vu_recv_duplicates++;
if (lle->vu_recv_duplicates < 3)
return -EIO;
LOGLLE(lle, LOGL_NOTICE, "TLLI=%08x recovering "
"N(U=%d) after receiving %u duplicates\n",
lle->llme ? lle->llme->tlli : -1,
pdu_dec->seq_tx, lle->vu_recv_duplicates);
}
}
/* Increment the sequence number that we expect in the next frame */
lle->vu_recv = (pdu_dec->seq_tx + 1) % 512;
/* Increment Overflow Counter */
if ((pdu_dec->seq_tx + 1) / 512)
lle->oc_ui_recv += 512;
break;
case OSMO_GPRS_LLC_FUNC_NULL:
LOGLLE(lle, LOGL_DEBUG, "TLLI=%08x sends us LLC NULL\n", lle->llme ? lle->llme->tlli : -1);
break;
default:
LOGLLE(lle, LOGL_NOTICE, "Unhandled command: %s\n",
gprs_llc_frame_func_name(pdu_dec->func));
break;
}
return 0;
}
/* encrypt information field + FCS, if needed! */
static int apply_gea(const struct gprs_llc_lle *lle, uint16_t crypt_len, uint16_t nu,
uint32_t oc, uint8_t sapi, uint8_t *fcs, uint8_t *data)
{
uint8_t cipher_out[GSM0464_CIPH_MAX_BLOCK];
if (lle->llme->algo == GPRS_ALGO_GEA0)
return -EINVAL;
/* Compute the 'Input' Paraemeter */
uint32_t fcs_calc, iv = gprs_cipher_gen_input_ui(lle->llme->iov_ui, sapi,
nu, oc);
/* Compute gamma that we need to XOR with the data */
int r = gprs_cipher_run(cipher_out, crypt_len, lle->llme->algo,
lle->llme->kc, iv,
fcs ? GPRS_CIPH_SGSN2MS : GPRS_CIPH_MS2SGSN);
if (r < 0) {
LOGLLC(LOGL_ERROR, "Error producing %s gamma for UI "
"frame: %d\n", get_value_string(gprs_cipher_names,
lle->llme->algo), r);
return -ENOMSG;
}
if (fcs) {
/* Mark frame as encrypted and update FCS */
data[2] |= 0x02;
fcs_calc = gprs_llc_fcs(data, fcs - data);
fcs[0] = fcs_calc & 0xff;
fcs[1] = (fcs_calc >> 8) & 0xff;
fcs[2] = (fcs_calc >> 16) & 0xff;
data += 3;
}
/* XOR the cipher output with the data */
for (r = 0; r < crypt_len; r++)
*(data + r) ^= cipher_out[r];
return 0;
}
/* Shared upper part handling of BSSGP-UNITDATA.ind and GRR-UNITDATA.ind */
int gprs_llc_lle_rx_unitdata_ind(struct gprs_llc_lle *lle, uint8_t *ll_pdu, size_t ll_pdu_len, struct gprs_llc_pdu_decoded *pdu_dec)
{
struct osmo_gprs_llc_prim *llc_prim_tx;
bool drop_cipherable = false;
int rc;
/* reset age computation */
lle->llme->age_timestamp = GPRS_LLME_RESET_AGE;
/* decrypt information field + FCS, if needed! */
if (pdu_dec->flags & OSMO_GPRS_LLC_PDU_F_ENC_MODE) {
if (lle->llme->algo != GPRS_ALGO_GEA0) {
rc = apply_gea(lle, pdu_dec->data_len + 3, pdu_dec->seq_tx,
lle->oc_ui_recv, lle->sapi, NULL,
(uint8_t *)pdu_dec->data); /*TODO: either copy buffer or remove "const" from pdu_dec field "data" */
if (rc < 0)
return rc;
pdu_dec->fcs = *(pdu_dec->data + pdu_dec->data_len);
pdu_dec->fcs |= *(pdu_dec->data + pdu_dec->data_len + 1) << 8;
pdu_dec->fcs |= *(pdu_dec->data + pdu_dec->data_len + 2) << 16;
} else {
LOGLLME(lle->llme, LOGL_NOTICE, "encrypted frame for LLC that "
"has no KC/Algo! Dropping.\n");
return 0;
}
} else {
if (lle->llme->algo != GPRS_ALGO_GEA0 &&
lle->llme->cksn != GSM_KEY_SEQ_INVAL)
drop_cipherable = true;
}
/* We have to do the FCS check _after_ decryption */
uint16_t crc_length = ll_pdu_len - CRC24_LENGTH;
if (~pdu_dec->flags & OSMO_GPRS_LLC_PDU_F_PROT_MODE)
crc_length = OSMO_MIN(crc_length, UI_HDR_LEN + N202);
if (pdu_dec->fcs != gprs_llc_fcs(ll_pdu, crc_length)) {
LOGLLE(lle, LOGL_NOTICE, "Dropping frame with invalid FCS 0x%06x vs exp 0x%06x\n",
pdu_dec->fcs, gprs_llc_fcs(ll_pdu, crc_length));
return -EIO;
}
/* Receive and Process the actual LLC frame */
rc = gprs_llc_lle_hdr_rx(lle, pdu_dec);
if (rc < 0)
return rc;
/* pdu_dec->data is only set when we need to send LL_[UNIT]DATA_IND up */
if (pdu_dec->func == OSMO_GPRS_LLC_FUNC_UI && pdu_dec->data && pdu_dec->data_len) {
switch (pdu_dec->sapi) {
case OSMO_GPRS_LLC_SAPI_GMM:
/* send LL-UNITDATA-IND to GMM */
llc_prim_tx = gprs_llc_prim_alloc_ll_unitdata_ind(lle->llme->tlli,
pdu_dec->sapi,
pdu_dec->data,
pdu_dec->data_len);
llc_prim_tx->ll.unitdata_ind.apply_gea = !drop_cipherable; /* TODO: is this correct? */
llc_prim_tx->ll.unitdata_ind.apply_gia = false; /* TODO: how to set this? */
gprs_llc_prim_call_up_cb(llc_prim_tx);
break;
case OSMO_GPRS_LLC_SAPI_SNDCP3:
case OSMO_GPRS_LLC_SAPI_SNDCP5:
case OSMO_GPRS_LLC_SAPI_SNDCP9:
case OSMO_GPRS_LLC_SAPI_SNDCP11:
/* send LL_DATA_IND/LL_UNITDATA_IND to SNDCP */
llc_prim_tx = gprs_llc_prim_alloc_ll_unitdata_ind(lle->llme->tlli,
pdu_dec->sapi,
pdu_dec->data,
pdu_dec->data_len);
llc_prim_tx->ll.unitdata_ind.apply_gea = !drop_cipherable; /* TODO: is this correct? */
llc_prim_tx->ll.unitdata_ind.apply_gia = false; /* TODO: how to set this? */
gprs_llc_prim_call_up_cb(llc_prim_tx);
break;
case OSMO_GPRS_LLC_SAPI_SMS:
/* FIXME */
case OSMO_GPRS_LLC_SAPI_TOM2:
case OSMO_GPRS_LLC_SAPI_TOM8:
/* FIXME: send LL_DATA_IND/LL_UNITDATA_IND to TOM */
default:
LOGLLC(LOGL_NOTICE, "Unsupported SAPI %u\n", pdu_dec->sapi);
rc = -EINVAL;
break;
}
}
return rc;
}