From 762c39ccc65e0307a32b89e8bf7daaad2b6b66b4 Mon Sep 17 00:00:00 2001 From: Pau Espin Pedrol Date: Fri, 30 Aug 2019 20:47:02 +0200 Subject: Move out sgsn to its own subdir Change-Id: I16fccc0eadf588599b9e5578d0f4dbaf9df81737 --- src/Makefile.am | 1 + src/gprs/Makefile.am | 58 - src/gprs/gprs_gb.c | 75 - src/gprs/gprs_gmm.c | 2852 --------------------------------------- src/gprs/gprs_gmm_attach.c | 461 ------- src/gprs/gprs_llc.c | 1175 ---------------- src/gprs/gprs_llc_vty.c | 115 -- src/gprs/gprs_llc_xid.c | 281 ---- src/gprs/gprs_mm_state_gb_fsm.c | 112 -- src/gprs/gprs_mm_state_iu_fsm.c | 121 -- src/gprs/gprs_ranap.c | 231 ---- src/gprs/gprs_sgsn.c | 1017 -------------- src/gprs/gprs_sndcp.c | 1265 ----------------- src/gprs/gprs_sndcp_comp.c | 338 ----- src/gprs/gprs_sndcp_dcomp.c | 358 ----- src/gprs/gprs_sndcp_pcomp.c | 282 ---- src/gprs/gprs_sndcp_vty.c | 70 - src/gprs/gprs_sndcp_xid.c | 1901 -------------------------- src/gprs/gprs_subscriber.c | 949 ------------- src/gprs/sgsn_auth.c | 312 ----- src/gprs/sgsn_cdr.c | 301 ----- src/gprs/sgsn_ctrl.c | 62 - src/gprs/sgsn_libgtp.c | 842 ------------ src/gprs/sgsn_main.c | 536 -------- src/gprs/sgsn_vty.c | 1504 --------------------- src/gprs/slhc.c | 813 ----------- src/gprs/v42bis.c | 767 ----------- src/sgsn/Makefile.am | 88 ++ src/sgsn/gprs_gb.c | 75 + src/sgsn/gprs_gmm.c | 2852 +++++++++++++++++++++++++++++++++++++++ src/sgsn/gprs_gmm_attach.c | 461 +++++++ src/sgsn/gprs_llc.c | 1175 ++++++++++++++++ src/sgsn/gprs_llc_vty.c | 115 ++ src/sgsn/gprs_llc_xid.c | 281 ++++ src/sgsn/gprs_mm_state_gb_fsm.c | 112 ++ src/sgsn/gprs_mm_state_iu_fsm.c | 121 ++ src/sgsn/gprs_ranap.c | 231 ++++ src/sgsn/gprs_sgsn.c | 1017 ++++++++++++++ src/sgsn/gprs_sndcp.c | 1265 +++++++++++++++++ src/sgsn/gprs_sndcp_comp.c | 338 +++++ src/sgsn/gprs_sndcp_dcomp.c | 358 +++++ src/sgsn/gprs_sndcp_pcomp.c | 282 ++++ src/sgsn/gprs_sndcp_vty.c | 70 + src/sgsn/gprs_sndcp_xid.c | 1901 ++++++++++++++++++++++++++ src/sgsn/gprs_subscriber.c | 949 +++++++++++++ src/sgsn/sgsn_auth.c | 312 +++++ src/sgsn/sgsn_cdr.c | 301 +++++ src/sgsn/sgsn_ctrl.c | 62 + src/sgsn/sgsn_libgtp.c | 842 ++++++++++++ src/sgsn/sgsn_main.c | 536 ++++++++ src/sgsn/sgsn_vty.c | 1504 +++++++++++++++++++++ src/sgsn/slhc.c | 813 +++++++++++ src/sgsn/v42bis.c | 767 +++++++++++ 53 files changed, 16829 insertions(+), 16798 deletions(-) delete mode 100644 src/gprs/gprs_gb.c delete mode 100644 src/gprs/gprs_gmm.c delete mode 100644 src/gprs/gprs_gmm_attach.c delete mode 100644 src/gprs/gprs_llc.c delete mode 100644 src/gprs/gprs_llc_vty.c delete mode 100644 src/gprs/gprs_llc_xid.c delete mode 100644 src/gprs/gprs_mm_state_gb_fsm.c delete mode 100644 src/gprs/gprs_mm_state_iu_fsm.c delete mode 100644 src/gprs/gprs_ranap.c delete mode 100644 src/gprs/gprs_sgsn.c delete mode 100644 src/gprs/gprs_sndcp.c delete mode 100644 src/gprs/gprs_sndcp_comp.c delete mode 100644 src/gprs/gprs_sndcp_dcomp.c delete mode 100644 src/gprs/gprs_sndcp_pcomp.c delete mode 100644 src/gprs/gprs_sndcp_vty.c delete mode 100644 src/gprs/gprs_sndcp_xid.c delete mode 100644 src/gprs/gprs_subscriber.c delete mode 100644 src/gprs/sgsn_auth.c delete mode 100644 src/gprs/sgsn_cdr.c delete mode 100644 src/gprs/sgsn_ctrl.c delete mode 100644 src/gprs/sgsn_libgtp.c delete mode 100644 src/gprs/sgsn_main.c delete mode 100644 src/gprs/sgsn_vty.c delete mode 100644 src/gprs/slhc.c delete mode 100644 src/gprs/v42bis.c create mode 100644 src/sgsn/Makefile.am create mode 100644 src/sgsn/gprs_gb.c create mode 100644 src/sgsn/gprs_gmm.c create mode 100644 src/sgsn/gprs_gmm_attach.c create mode 100644 src/sgsn/gprs_llc.c create mode 100644 src/sgsn/gprs_llc_vty.c create mode 100644 src/sgsn/gprs_llc_xid.c create mode 100644 src/sgsn/gprs_mm_state_gb_fsm.c create mode 100644 src/sgsn/gprs_mm_state_iu_fsm.c create mode 100644 src/sgsn/gprs_ranap.c create mode 100644 src/sgsn/gprs_sgsn.c create mode 100644 src/sgsn/gprs_sndcp.c create mode 100644 src/sgsn/gprs_sndcp_comp.c create mode 100644 src/sgsn/gprs_sndcp_dcomp.c create mode 100644 src/sgsn/gprs_sndcp_pcomp.c create mode 100644 src/sgsn/gprs_sndcp_vty.c create mode 100644 src/sgsn/gprs_sndcp_xid.c create mode 100644 src/sgsn/gprs_subscriber.c create mode 100644 src/sgsn/sgsn_auth.c create mode 100644 src/sgsn/sgsn_cdr.c create mode 100644 src/sgsn/sgsn_ctrl.c create mode 100644 src/sgsn/sgsn_libgtp.c create mode 100644 src/sgsn/sgsn_main.c create mode 100644 src/sgsn/sgsn_vty.c create mode 100644 src/sgsn/slhc.c create mode 100644 src/sgsn/v42bis.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 2abd4375e..c45d3ab46 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,6 @@ SUBDIRS = \ gprs \ + sgsn \ gbproxy \ gtphub \ $(NULL) diff --git a/src/gprs/Makefile.am b/src/gprs/Makefile.am index e5d50d375..170ded8fc 100644 --- a/src/gprs/Makefile.am +++ b/src/gprs/Makefile.am @@ -26,15 +26,6 @@ AM_CFLAGS += \ $(NULL) endif -OSMO_LIBS = \ - $(LIBOSMOCORE_LIBS) \ - $(LIBOSMOGSM_LIBS) \ - $(LIBOSMOVTY_LIBS) \ - $(LIBOSMOCTRL_LIBS) \ - $(LIBOSMOGB_LIBS) \ - $(LIBGTP_LIBS) \ - $(NULL) - noinst_LTLIBRARIES = libcommon.la libcommon_la_SOURCES = \ @@ -53,52 +44,3 @@ libcommon_la_LIBADD = \ $(LIBOSMOSIGTRAN_LIBS) \ $(LIBCARES_LIBS) \ $(NULL) - -bin_PROGRAMS = \ - osmo-sgsn \ - $(NULL) - -osmo_sgsn_SOURCES = \ - gprs_gb.c \ - gprs_gmm_attach.c \ - gprs_gmm.c \ - gprs_mm_state_gb_fsm.c \ - gprs_mm_state_iu_fsm.c \ - gprs_ranap.c \ - gprs_sgsn.c \ - gprs_sndcp.c \ - gprs_sndcp_comp.c \ - gprs_sndcp_dcomp.c \ - gprs_sndcp_pcomp.c \ - gprs_sndcp_vty.c \ - gprs_sndcp_xid.c \ - sgsn_main.c \ - sgsn_vty.c \ - sgsn_libgtp.c \ - gprs_llc.c \ - gprs_llc_vty.c \ - sgsn_ctrl.c \ - sgsn_auth.c \ - gprs_subscriber.c \ - sgsn_cdr.c \ - slhc.c \ - gprs_llc_xid.c \ - v42bis.c \ - $(NULL) -osmo_sgsn_LDADD = \ - libcommon.la \ - $(OSMO_LIBS) \ - $(LIBOSMOABIS_LIBS) \ - $(LIBOSMOGSUPCLIENT_LIBS) \ - $(LIBCARES_LIBS) \ - $(LIBGTP_LIBS) \ - -lrt \ - -lm \ - $(NULL) -if BUILD_IU -osmo_sgsn_LDADD += \ - $(LIBOSMOSIGTRAN_LIBS) \ - $(LIBOSMORANAP_LIBS) \ - $(LIBASN1C_LIBS) \ - $(NULL) -endif diff --git a/src/gprs/gprs_gb.c b/src/gprs/gprs_gb.c deleted file mode 100644 index 65342cf22..000000000 --- a/src/gprs/gprs_gb.c +++ /dev/null @@ -1,75 +0,0 @@ -/* Messages on the Gb interface (A/Gb mode) */ - -/* (C) 2009-2015 by Harald Welte - * (C) 2010 by On-Waves - * (C) 2019 by sysmocom s.f.m.c. GmbH - * - * 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 . - * - */ - -#include - -#include -#include - -#include "bscconfig.h" - -#include -#include -#include -#include - -/* Main entry point for incoming 04.08 GPRS messages from Gb */ -int gsm0408_gprs_rcvmsg_gb(struct msgb *msg, struct gprs_llc_llme *llme, - bool drop_cipherable) -{ - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); - uint8_t pdisc = gsm48_hdr_pdisc(gh); - struct sgsn_mm_ctx *mmctx; - struct gprs_ra_id ra_id; - int rc = -EINVAL; - - bssgp_parse_cell_id(&ra_id, msgb_bcid(msg)); - mmctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &ra_id); - if (mmctx) { - msgid2mmctx(mmctx, msg); - rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_SIG_IN]); - mmctx->gb.llme = llme; - osmo_fsm_inst_dispatch(mmctx->gb.mm_state_fsm, E_MM_PDU_RECEPTION, NULL); - } - - /* MMCTX can be NULL */ - - switch (pdisc) { - case GSM48_PDISC_MM_GPRS: - rc = gsm0408_rcv_gmm(mmctx, msg, llme, drop_cipherable); - break; - case GSM48_PDISC_SM_GPRS: - rc = gsm0408_rcv_gsm(mmctx, msg, llme); - break; - default: - LOGMMCTXP(LOGL_NOTICE, mmctx, - "Unknown GSM 04.08 discriminator 0x%02x: %s\n", - pdisc, osmo_hexdump((uint8_t *)gh, msgb_l3len(msg))); - /* FIXME: return status message */ - break; - } - - /* MMCTX can be invalid */ - - return rc; -} diff --git a/src/gprs/gprs_gmm.c b/src/gprs/gprs_gmm.c deleted file mode 100644 index edb7eea1e..000000000 --- a/src/gprs/gprs_gmm.c +++ /dev/null @@ -1,2852 +0,0 @@ -/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface - * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */ - -/* (C) 2009-2015 by Harald Welte - * (C) 2010 by On-Waves - * - * 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 . - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "bscconfig.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define PTMSI_ALLOC - -/* 3GPP TS 04.08 sec 6.1.3.4.3(.a) "Abnormal cases" */ -#define T339X_MAX_RETRANS 4 - -extern struct sgsn_instance *sgsn; -extern void *tall_sgsn_ctx; - -static const struct tlv_definition gsm48_gmm_att_tlvdef = { - .def = { - [GSM48_IE_GMM_CIPH_CKSN] = { TLV_TYPE_FIXED, 1 }, - [GSM48_IE_GMM_TIMER_READY] = { TLV_TYPE_TV, 1 }, - [GSM48_IE_GMM_ALLOC_PTMSI] = { TLV_TYPE_TLV, 0 }, - [GSM48_IE_GMM_PTMSI_SIG] = { TLV_TYPE_FIXED, 3 }, - [GSM48_IE_GMM_AUTH_RAND] = { TLV_TYPE_FIXED, 16 }, - [GSM48_IE_GMM_AUTH_SRES] = { TLV_TYPE_FIXED, 4 }, - [GSM48_IE_GMM_AUTH_RES_EXT] = { TLV_TYPE_TLV, 0 }, - [GSM48_IE_GMM_AUTH_FAIL_PAR] = { TLV_TYPE_TLV, 0 }, - [GSM48_IE_GMM_IMEISV] = { TLV_TYPE_TLV, 0 }, - [GSM48_IE_GMM_DRX_PARAM] = { TLV_TYPE_FIXED, 2 }, - [GSM48_IE_GMM_MS_NET_CAPA] = { TLV_TYPE_TLV, 0 }, - [GSM48_IE_GMM_PDP_CTX_STATUS] = { TLV_TYPE_TLV, 0 }, - [GSM48_IE_GMM_PS_LCS_CAPA] = { TLV_TYPE_TLV, 0 }, - [GSM48_IE_GMM_GMM_MBMS_CTX_ST] = { TLV_TYPE_TLV, 0 }, - }, -}; - -static const struct tlv_definition gsm48_sm_att_tlvdef = { - .def = { - [GSM48_IE_GSM_APN] = { TLV_TYPE_TLV, 0 }, - [GSM48_IE_GSM_PROTO_CONF_OPT] = { TLV_TYPE_TLV, 0 }, - [GSM48_IE_GSM_PDP_ADDR] = { TLV_TYPE_TLV, 0 }, - [GSM48_IE_GSM_AA_TMR] = { TLV_TYPE_TV, 1 }, - [GSM48_IE_GSM_NAME_FULL] = { TLV_TYPE_TLV, 0 }, - [GSM48_IE_GSM_NAME_SHORT] = { TLV_TYPE_TLV, 0 }, - [GSM48_IE_GSM_TIMEZONE] = { TLV_TYPE_FIXED, 1 }, - [GSM48_IE_GSM_UTC_AND_TZ] = { TLV_TYPE_FIXED, 7 }, - [GSM48_IE_GSM_LSA_ID] = { TLV_TYPE_TLV, 0 }, - }, -}; - -/* Our implementation, should be kept in SGSN */ - -static void mmctx_timer_cb(void *_mm); - -static void mmctx_timer_start(struct sgsn_mm_ctx *mm, unsigned int T) -{ - unsigned long seconds; - if (osmo_timer_pending(&mm->timer)) - LOGMMCTXP(LOGL_ERROR, mm, "Starting MM timer %u while old " - "timer %u pending\n", T, mm->T); - - seconds = osmo_tdef_get(sgsn->cfg.T_defs, T, OSMO_TDEF_S, -1); - - mm->T = T; - mm->num_T_exp = 0; - - /* FIXME: we should do this only once ? */ - osmo_timer_setup(&mm->timer, mmctx_timer_cb, mm); - osmo_timer_schedule(&mm->timer, seconds, 0); -} - -static void mmctx_timer_stop(struct sgsn_mm_ctx *mm, unsigned int T) -{ - if (mm->T != T) - LOGMMCTXP(LOGL_ERROR, mm, "Stopping MM timer %u but " - "%u is running\n", T, mm->T); - osmo_timer_del(&mm->timer); -} - -time_t gprs_max_time_to_idle(void) -{ - unsigned long T3314, T3312; - - T3314 = osmo_tdef_get(sgsn->cfg.T_defs, 3314, OSMO_TDEF_S, -1); - T3312 = osmo_tdef_get(sgsn->cfg.T_defs, 3312, OSMO_TDEF_S, -1); - return T3314 + (T3312 + 4 * 60); -} - -/* Send a message through the underlying layer. - * For param encryptable, see 3GPP TS 24.008 § 4.7.1.2 and - * gsm48_hdr_gmm_cipherable(). Pass false for not cipherable messages. */ -static int gsm48_gmm_sendmsg(struct msgb *msg, int command, - struct sgsn_mm_ctx *mm, bool encryptable) -{ - if (mm) { - rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PKTS_SIG_OUT]); -#ifdef BUILD_IU - if (mm->ran_type == MM_CTX_T_UTRAN_Iu) - return ranap_iu_tx(msg, GPRS_SAPI_GMM); -#endif - } - -#ifdef BUILD_IU - if (MSG_IU_UE_CTX(msg)) - return ranap_iu_tx(msg, GPRS_SAPI_GMM); -#endif - - /* caller needs to provide TLLI, BVCI and NSEI */ - return gprs_llc_tx_ui(msg, GPRS_SAPI_GMM, command, mm, encryptable); -} - -/* copy identifiers from old message to new message, this - * is required so lower layers can route it correctly */ -static void gmm_copy_id(struct msgb *msg, const struct msgb *old) -{ - msgb_tlli(msg) = msgb_tlli(old); - msgb_bvci(msg) = msgb_bvci(old); - msgb_nsei(msg) = msgb_nsei(old); - MSG_IU_UE_CTX_SET(msg, MSG_IU_UE_CTX(old)); -} - -/* Store BVCI/NSEI in MM context */ -void msgid2mmctx(struct sgsn_mm_ctx *mm, const struct msgb *msg) -{ - /* check for Iu or Gb */ - if (!MSG_IU_UE_CTX(msg)) { - mm->gb.bvci = msgb_bvci(msg); - mm->gb.nsei = msgb_nsei(msg); - } -#ifdef BUILD_IU - else { - /* In case a Iu connection is reconnected we need to update the ue ctx */ - /* FIXME: the old ue_ctx have to be freed/disconnected */ - mm->iu.ue_ctx = MSG_IU_UE_CTX(msg); - if (mm->ran_type == MM_CTX_T_UTRAN_Iu - && mm->iu.ue_ctx) { - mm->iu.ue_ctx->rab_assign_addr_enc = - sgsn->cfg.iu.rab_assign_addr_enc; - } - } -#endif -} - -/* Store BVCI/NSEI in MM context */ -static void mmctx2msgid(struct msgb *msg, const struct sgsn_mm_ctx *mm) -{ - msgb_tlli(msg) = mm->gb.tlli; - msgb_bvci(msg) = mm->gb.bvci; - msgb_nsei(msg) = mm->gb.nsei; - MSG_IU_UE_CTX_SET(msg, mm->iu.ue_ctx); -} - -static void mm_ctx_cleanup_free(struct sgsn_mm_ctx *ctx, const char *log_text) -{ - LOGMMCTXP(LOGL_INFO, ctx, "Cleaning MM context due to %s\n", log_text); - - /* Mark MM state as deregistered */ - ctx->gmm_state = GMM_DEREGISTERED; - - switch(ctx->ran_type) { - case MM_CTX_T_UTRAN_Iu: - osmo_fsm_inst_dispatch(ctx->iu.mm_state_fsm, E_PMM_IMPLICIT_DETACH, NULL); - break; - case MM_CTX_T_GERAN_Gb: - osmo_fsm_inst_dispatch(ctx->gb.mm_state_fsm, E_MM_IMPLICIT_DETACH, NULL); - break; - } - - sgsn_mm_ctx_cleanup_free(ctx); -} - -/* Chapter 9.4.18 */ -static int _tx_status(struct msgb *msg, uint8_t cause, - struct sgsn_mm_ctx *mmctx, int sm) -{ - struct gsm48_hdr *gh; - - /* MMCTX might be NULL! */ - - DEBUGP(DMM, "<- GPRS MM STATUS (cause: %s)\n", - get_value_string(gsm48_gmm_cause_names, cause)); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); - if (sm) { - gh->proto_discr = GSM48_PDISC_SM_GPRS; - gh->msg_type = GSM48_MT_GSM_STATUS; - } else { - gh->proto_discr = GSM48_PDISC_MM_GPRS; - gh->msg_type = GSM48_MT_GMM_STATUS; - } - gh->data[0] = cause; - - return gsm48_gmm_sendmsg(msg, 0, mmctx, true); -} - -static int gsm48_tx_gmm_status(struct sgsn_mm_ctx *mmctx, uint8_t cause) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 GMM STATUS"); - - mmctx2msgid(msg, mmctx); - return _tx_status(msg, cause, mmctx, 0); -} - -static int gsm48_tx_sm_status(struct sgsn_mm_ctx *mmctx, uint8_t cause) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 SM STATUS"); - - mmctx2msgid(msg, mmctx); - return _tx_status(msg, cause, mmctx, 1); -} - -static int _tx_detach_req(struct msgb *msg, uint8_t detach_type, uint8_t cause, - struct sgsn_mm_ctx *mmctx) -{ - struct gsm48_hdr *gh; - - /* MMCTX might be NULL! */ - - DEBUGP(DMM, "<- GPRS MM DETACH REQ (type: %s, cause: %s)\n", - get_value_string(gprs_det_t_mt_strs, detach_type), - get_value_string(gsm48_gmm_cause_names, cause)); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); - - gh->proto_discr = GSM48_PDISC_MM_GPRS; - gh->msg_type = GSM48_MT_GMM_DETACH_REQ; - gh->data[0] = detach_type & 0x07; - - msgb_tv_put(msg, GSM48_IE_GMM_CAUSE, cause); - - return gsm48_gmm_sendmsg(msg, 0, mmctx, true); -} - -static int gsm48_tx_gmm_detach_req(struct sgsn_mm_ctx *mmctx, - uint8_t detach_type, uint8_t cause) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DET REQ"); - - mmctx2msgid(msg, mmctx); - return _tx_detach_req(msg, detach_type, cause, mmctx); -} - -static int gsm48_tx_gmm_detach_req_oldmsg(struct msgb *oldmsg, - uint8_t detach_type, uint8_t cause) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DET OLD"); - - gmm_copy_id(msg, oldmsg); - return _tx_detach_req(msg, detach_type, cause, NULL); -} - -static struct gsm48_qos default_qos = { - .delay_class = 4, /* best effort */ - .reliab_class = GSM48_QOS_RC_LLC_UN_RLC_ACK_DATA_PROT, - .peak_tput = GSM48_QOS_PEAK_TPUT_32000bps, - .preced_class = GSM48_QOS_PC_NORMAL, - .mean_tput = GSM48_QOS_MEAN_TPUT_BEST_EFFORT, - .traf_class = GSM48_QOS_TC_INTERACTIVE, - .deliv_order = GSM48_QOS_DO_UNORDERED, - .deliv_err_sdu = GSM48_QOS_ERRSDU_YES, - .max_sdu_size = GSM48_QOS_MAXSDU_1520, - .max_bitrate_up = GSM48_QOS_MBRATE_63k, - .max_bitrate_down = GSM48_QOS_MBRATE_63k, - .resid_ber = GSM48_QOS_RBER_5e_2, - .sdu_err_ratio = GSM48_QOS_SERR_1e_2, - .handling_prio = 3, - .xfer_delay = 0x10, /* 200ms */ - .guar_bitrate_up = GSM48_QOS_MBRATE_0k, - .guar_bitrate_down = GSM48_QOS_MBRATE_0k, - .sig_ind = 0, /* not optimised for signalling */ - .max_bitrate_down_ext = 0, /* use octet 9 */ - .guar_bitrate_down_ext = 0, /* use octet 13 */ -}; - -/* Chapter 9.4.2: Attach accept */ -int gsm48_tx_gmm_att_ack(struct sgsn_mm_ctx *mm) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ATT ACK"); - struct gsm48_hdr *gh; - struct gsm48_attach_ack *aa; - uint8_t *mid; - unsigned long t; -#if 0 - uint8_t *ptsig; -#endif - - LOGMMCTXP(LOGL_INFO, mm, "<- GPRS ATTACH ACCEPT (new P-TMSI=0x%08x)\n", mm->p_tmsi); - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_ATTACH_ACKED]); - - mmctx2msgid(msg, mm); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); - gh->proto_discr = GSM48_PDISC_MM_GPRS; - gh->msg_type = GSM48_MT_GMM_ATTACH_ACK; - - aa = (struct gsm48_attach_ack *) msgb_put(msg, sizeof(*aa)); - aa->force_stby = 0; /* not indicated */ - aa->att_result = 1; /* GPRS only */ - t = osmo_tdef_get(sgsn->cfg.T_defs, 3312, OSMO_TDEF_S, -1); - aa->ra_upd_timer = gprs_secs_to_tmr_floor(t); - aa->radio_prio = 4; /* lowest */ - gsm48_encode_ra(&aa->ra_id, &mm->ra); - -#if 0 - /* Optional: P-TMSI signature */ - msgb_v_put(msg, GSM48_IE_GMM_PTMSI_SIG); - ptsig = msgb_put(msg, 3); - ptsig[0] = mm->p_tmsi_sig >> 16; - ptsig[1] = mm->p_tmsi_sig >> 8; - ptsig[2] = mm->p_tmsi_sig & 0xff; - -#endif - /* Optional: Negotiated Ready timer value - * (fixed 44s, default value, GSM 04.08, table 11.4a) to safely limit - * the inactivity time READY->STANDBY. - */ - t = osmo_tdef_get(sgsn->cfg.T_defs, 3314, OSMO_TDEF_S, -1); - msgb_tv_put(msg, GSM48_IE_GMM_TIMER_READY, gprs_secs_to_tmr_floor(t)); - -#ifdef PTMSI_ALLOC - /* Optional: Allocated P-TMSI */ - mid = msgb_put(msg, GSM48_MID_TMSI_LEN); - gsm48_generate_mid_from_tmsi(mid, mm->p_tmsi); - mid[0] = GSM48_IE_GMM_ALLOC_PTMSI; -#endif - - /* Optional: MS-identity (combined attach) */ - /* Optional: GMM cause (partial attach result for combined attach) */ - - return gsm48_gmm_sendmsg(msg, 0, mm, true); -} - -/* Chapter 9.4.5: Attach reject */ -static int _tx_gmm_att_rej(struct msgb *msg, uint8_t gmm_cause, - const struct sgsn_mm_ctx *mm) -{ - struct gsm48_hdr *gh; - - LOGMMCTXP(LOGL_NOTICE, mm, "<- GPRS ATTACH REJECT: %s\n", - get_value_string(gsm48_gmm_cause_names, gmm_cause)); - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_ATTACH_REJECTED]); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); - gh->proto_discr = GSM48_PDISC_MM_GPRS; - gh->msg_type = GSM48_MT_GMM_ATTACH_REJ; - gh->data[0] = gmm_cause; - - return gsm48_gmm_sendmsg(msg, 0, NULL, false); -} -static int gsm48_tx_gmm_att_rej_oldmsg(const struct msgb *old_msg, - uint8_t gmm_cause) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ATT REJ OLD"); - gmm_copy_id(msg, old_msg); - return _tx_gmm_att_rej(msg, gmm_cause, NULL); -} -int gsm48_tx_gmm_att_rej(struct sgsn_mm_ctx *mm, - uint8_t gmm_cause) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ATT REJ"); - mmctx2msgid(msg, mm); - return _tx_gmm_att_rej(msg, gmm_cause, mm); -} - -/* Chapter 9.4.6.2 Detach accept */ -static int _tx_detach_ack(struct msgb *msg, uint8_t force_stby, - struct sgsn_mm_ctx *mm) -{ - struct gsm48_hdr *gh; - - /* MMCTX might be NULL! */ - - DEBUGP(DMM, "<- GPRS MM DETACH ACC (force-standby: %d)\n", force_stby); - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_DETACH_ACKED]); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); - gh->proto_discr = GSM48_PDISC_MM_GPRS; - gh->msg_type = GSM48_MT_GMM_DETACH_ACK; - gh->data[0] = force_stby; - - return gsm48_gmm_sendmsg(msg, 0, mm, true); -} - -static int gsm48_tx_gmm_det_ack(struct sgsn_mm_ctx *mm, uint8_t force_stby) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DET ACK"); - - mmctx2msgid(msg, mm); - return _tx_detach_ack(msg, force_stby, mm); -} - -static int gsm48_tx_gmm_det_ack_oldmsg(struct msgb *oldmsg, uint8_t force_stby) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DET ACK OLD"); - - gmm_copy_id(msg, oldmsg); - return _tx_detach_ack(msg, force_stby, NULL); -} - -/* Transmit Chapter 9.4.12 Identity Request */ -int gsm48_tx_gmm_id_req(struct sgsn_mm_ctx *mm, uint8_t id_type) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ID REQ"); - struct gsm48_hdr *gh; - - LOGMMCTXP(LOGL_DEBUG, mm, "<- GPRS IDENTITY REQUEST: mi_type=%s\n", - gsm48_mi_type_name(id_type)); - - mmctx2msgid(msg, mm); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); - gh->proto_discr = GSM48_PDISC_MM_GPRS; - gh->msg_type = GSM48_MT_GMM_ID_REQ; - /* 10.5.5.9 ID type 2 + identity type and 10.5.5.7 'force to standby' IE */ - gh->data[0] = id_type & 0xf; - - return gsm48_gmm_sendmsg(msg, 1, mm, false); -} - -/* determine if the MS/UE supports R99 or later */ -static bool mmctx_is_r99(const struct sgsn_mm_ctx *mm) -{ - if (mm->ms_network_capa.len < 1) - return false; - if (mm->ms_network_capa.buf[0] & 0x01) - return true; - return false; -} - -/* 3GPP TS 24.008 § 9.4.9: Authentication and Ciphering Request */ -int gsm48_tx_gmm_auth_ciph_req(struct sgsn_mm_ctx *mm, - const struct osmo_auth_vector *vec, - uint8_t key_seq, bool force_standby) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 AUTH CIPH REQ"); - struct gsm48_hdr *gh; - struct gsm48_auth_ciph_req *acreq; - uint8_t *m_rand, *m_cksn, rbyte; - int rc; - - LOGMMCTXP(LOGL_INFO, mm, "<- GPRS AUTH AND CIPHERING REQ (rand = %s," - " mmctx_is_r99=%d, vec->auth_types=0x%x", - osmo_hexdump(vec->rand, sizeof(vec->rand)), - mmctx_is_r99(mm), vec->auth_types); - if (mmctx_is_r99(mm) && vec - && (vec->auth_types & OSMO_AUTH_TYPE_UMTS)) { - LOGPC(DMM, LOGL_INFO, ", autn = %s)\n", - osmo_hexdump(vec->autn, sizeof(vec->autn))); - } else - LOGPC(DMM, LOGL_INFO, ")\n"); - - mmctx2msgid(msg, mm); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); - gh->proto_discr = GSM48_PDISC_MM_GPRS; - gh->msg_type = GSM48_MT_GMM_AUTH_CIPH_REQ; - - acreq = (struct gsm48_auth_ciph_req *) msgb_put(msg, sizeof(*acreq)); - acreq->ciph_alg = mm->ciph_algo & 0xf; - /* § 10.5.5.10: */ - acreq->imeisv_req = 0x1; - /* § 10.5.5.7: */ - acreq->force_stby = force_standby; - /* 3GPP TS 24.008 § 10.5.5.19: */ - rc = osmo_get_rand_id(&rbyte, 1); - if (rc < 0) { - LOGMMCTXP(LOGL_ERROR, mm, "osmo_get_rand_id() failed for A&C ref: %s\n", strerror(-rc)); - return rc; - } - - acreq->ac_ref_nr = rbyte; - mm->ac_ref_nr_used = acreq->ac_ref_nr; - - /* Only if authentication is requested we need to set RAND + CKSN */ - if (vec) { - m_rand = msgb_put(msg, sizeof(vec->rand) + 1); - m_rand[0] = GSM48_IE_GMM_AUTH_RAND; - memcpy(m_rand + 1, vec->rand, sizeof(vec->rand)); - - /* § 10.5.1.2: */ - m_cksn = msgb_put(msg, 1); - m_cksn[0] = (GSM48_IE_GMM_CIPH_CKSN << 4) | (key_seq & 0x07); - - /* A Release99 or higher MS/UE must be able to handle - * the optional AUTN IE. If a classic GSM SIM is - * inserted, it will simply ignore AUTN and just use - * RAND */ - if (mmctx_is_r99(mm) && - (vec->auth_types & OSMO_AUTH_TYPE_UMTS)) { - msgb_tlv_put(msg, GSM48_IE_GMM_AUTN, - sizeof(vec->autn), vec->autn); - } - } - - return gsm48_gmm_sendmsg(msg, 1, mm, false); -} - -/* 3GPP TS 24.008 § 9.4.11: Authentication and Ciphering Reject */ -static int gsm48_tx_gmm_auth_ciph_rej(struct sgsn_mm_ctx *mm) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 AUTH CIPH REJ"); - struct gsm48_hdr *gh; - - LOGMMCTXP(LOGL_NOTICE, mm, "<- GPRS AUTH AND CIPH REJECT\n"); - - mmctx2msgid(msg, mm); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); - gh->proto_discr = GSM48_PDISC_MM_GPRS; - gh->msg_type = GSM48_MT_GMM_AUTH_CIPH_REJ; - - return gsm48_gmm_sendmsg(msg, 0, mm, false); -} - -/* check if the received authentication response matches */ -static enum osmo_sub_auth_type check_auth_resp(struct sgsn_mm_ctx *ctx, - bool is_utran, - const struct osmo_auth_vector *vec, - const uint8_t *res, uint8_t res_len) -{ - const uint8_t *expect_res; - uint8_t expect_res_len; - enum osmo_sub_auth_type expect_type; - const char *expect_str; - - /* On UTRAN (3G) we always expect UMTS AKA. On GERAN (2G) we sent AUTN - * and expect UMTS AKA if there is R99 capability and our vector - * supports UMTS AKA, otherwise we expect GSM AKA. - * However, on GERAN, even if we sent a UMTS AKA Authentication Request, the MS may decide to - * instead reply with a GSM AKA SRES response. */ - if (is_utran - || (mmctx_is_r99(ctx) && (vec->auth_types & OSMO_AUTH_TYPE_UMTS) - && (res_len > sizeof(vec->sres)))) { - expect_type = OSMO_AUTH_TYPE_UMTS; - expect_str = "UMTS RES"; - expect_res = vec->res; - expect_res_len = vec->res_len; - } else { - expect_type = OSMO_AUTH_TYPE_GSM; - expect_str = "GSM SRES"; - expect_res = vec->sres; - expect_res_len = sizeof(vec->sres); - } - - if (!(vec->auth_types & expect_type)) { - LOGMMCTXP(LOGL_ERROR, ctx, "Auth error: auth vector does" - " not provide the expected auth type:" - " expected %s = 0x%x, auth_types are 0x%x\n", - expect_str, expect_type, vec->auth_types); - return OSMO_AUTH_TYPE_NONE; - } - - if (!res) - goto auth_mismatch; - - if (res_len != expect_res_len) - goto auth_mismatch; - - if (memcmp(res, expect_res, res_len) != 0) - goto auth_mismatch; - - /* Authorized! */ - return expect_type; - -auth_mismatch: - LOGMMCTXP(LOGL_ERROR, ctx, "Auth mismatch: expected %s = %s\n", - expect_str, osmo_hexdump_nospc(expect_res, expect_res_len)); - return OSMO_AUTH_TYPE_NONE; -} - -/* 3GPP TS 24.008 § 9.4.10: Authentication and Ciphering Response */ -static int gsm48_rx_gmm_auth_ciph_resp(struct sgsn_mm_ctx *ctx, - struct msgb *msg) -{ - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); - struct gsm48_auth_ciph_resp *acr = (struct gsm48_auth_ciph_resp *)gh->data; - struct tlv_parsed tp; - struct gsm_auth_tuple *at; - const char *res_name = "(no response)"; - uint8_t res[16]; - uint8_t res_len; - int rc; - - LOGMMCTXP(LOGL_INFO, ctx, "-> GPRS AUTH AND CIPH RESPONSE\n"); - - if (ctx->auth_triplet.key_seq == GSM_KEY_SEQ_INVAL) { - LOGMMCTXP(LOGL_NOTICE, ctx, - "Unexpected Auth & Ciph Response (ignored)\n"); - return 0; - } - - if (acr->ac_ref_nr != ctx->ac_ref_nr_used) { - LOGMMCTXP(LOGL_NOTICE, ctx, "Reference mismatch for Auth & Ciph" - " Response: %u received, %u expected\n", - acr->ac_ref_nr, ctx->ac_ref_nr_used); - return 0; - } - - /* Stop T3360 */ - mmctx_timer_stop(ctx, 3360); - - tlv_parse(&tp, &gsm48_gmm_att_tlvdef, acr->data, - (msg->data + msg->len) - acr->data, 0, 0); - - if (!TLVP_PRESENT(&tp, GSM48_IE_GMM_AUTH_SRES) || - !TLVP_PRESENT(&tp, GSM48_IE_GMM_IMEISV) || - TLVP_LEN(&tp,GSM48_IE_GMM_AUTH_SRES) != 4) { - /* TODO: missing mandatory IE, return STATUS or REJ? */ - LOGMMCTXP(LOGL_ERROR, ctx, "Missing mandantory IE\n"); - return -EINVAL; - } - - /* Start with the good old 4-byte SRES */ - memcpy(res, TLVP_VAL(&tp, GSM48_IE_GMM_AUTH_SRES), 4); - res_len = 4; - res_name = "GSM SRES"; - - /* Append extended RES as part of UMTS AKA, if any */ - if (TLVP_PRESENT(&tp, GSM48_IE_GMM_AUTH_RES_EXT)) { - unsigned int l = TLVP_LEN(&tp, GSM48_IE_GMM_AUTH_RES_EXT); - if (l > sizeof(res)-4) - l = sizeof(res)-4; - memcpy(res+4, TLVP_VAL(&tp, GSM48_IE_GMM_AUTH_RES_EXT), l); - res_len += l; - res_name = "UMTS RES"; - } - - at = &ctx->auth_triplet; - - LOGMMCTXP(LOGL_DEBUG, ctx, "checking auth: received %s = %s\n", - res_name, osmo_hexdump(res, res_len)); - ctx->sec_ctx = check_auth_resp(ctx, false, &at->vec, res, res_len); - if (!sgsn_mm_ctx_is_authenticated(ctx)) { - rc = gsm48_tx_gmm_auth_ciph_rej(ctx); - mm_ctx_cleanup_free(ctx, "GPRS AUTH AND CIPH REJECT"); - return rc; - } - - if (ctx->ran_type == MM_CTX_T_UTRAN_Iu) - ctx->iu.new_key = 1; - - /* FIXME: enable LLC cipheirng */ - - /* Check if we can let the mobile station enter */ - return osmo_fsm_inst_dispatch(ctx->gmm_att_req.fsm, E_AUTH_RESP_RECV_SUCCESS, NULL); -} - -/* 3GPP TS 24.008 § 9.4.10: Authentication and Ciphering Failure */ -static int gsm48_rx_gmm_auth_ciph_fail(struct sgsn_mm_ctx *ctx, - struct msgb *msg) -{ - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); - struct tlv_parsed tp; - const uint8_t gmm_cause = gh->data[0]; - const uint8_t *auts; - int rc; - - LOGMMCTXP(LOGL_INFO, ctx, "-> GPRS AUTH AND CIPH FAILURE (cause = %s)\n", - get_value_string(gsm48_gmm_cause_names, gmm_cause)); - - tlv_parse(&tp, &gsm48_gmm_att_tlvdef, gh->data+1, msg->len - 1, 0, 0); - - /* Only if GMM cause is present and the AUTS is provided, we can - * start re-sync procedure */ - if (gmm_cause == GMM_CAUSE_SYNC_FAIL && - TLVP_PRESENT(&tp, GSM48_IE_GMM_AUTH_FAIL_PAR)) { - if (TLVP_LEN(&tp, GSM48_IE_GMM_AUTH_FAIL_PAR) != 14) { - LOGMMCTXP(LOGL_ERROR, ctx, "AUTS IE has wrong size:" - " expected %d, got %u\n", 14, - TLVP_LEN(&tp, GSM48_IE_GMM_AUTH_FAIL_PAR)); - return -EINVAL; - } - auts = TLVP_VAL(&tp, GSM48_IE_GMM_AUTH_FAIL_PAR); - - LOGMMCTXP(LOGL_INFO, ctx, - "R99 AUTHENTICATION SYNCH (AUTS = %s)\n", - osmo_hexdump_nospc(auts, 14)); - - /* make sure we'll refresh the auth_triplet in - * sgsn_auth_update() */ - ctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL; - - /* make sure we'll retry authentication after the resync */ - ctx->auth_state = SGSN_AUTH_UMTS_RESYNC; - - /* Send AUTS to HLR and wait for new Auth Info Result */ - rc = gprs_subscr_request_auth_info(ctx, auts, - ctx->auth_triplet.vec.rand); - if (!rc) - return osmo_fsm_inst_dispatch(ctx->gmm_att_req.fsm, E_AUTH_RESP_RECV_RESYNC, NULL); - /* on error, fall through to send a reject */ - LOGMMCTXP(LOGL_ERROR, ctx, - "Sending AUTS to HLR failed (rc = %d)\n", rc); - } - - LOGMMCTXP(LOGL_NOTICE, ctx, "Authentication failed\n"); - rc = gsm48_tx_gmm_auth_ciph_rej(ctx); - mm_ctx_cleanup_free(ctx, "GPRS AUTH FAILURE"); - return rc; -} - -void extract_subscr_msisdn(struct sgsn_mm_ctx *ctx) -{ - struct gsm_mncc_number called; - uint8_t msisdn[sizeof(ctx->subscr->sgsn_data->msisdn) + 1]; - - /* Convert MSISDN from encoded to string.. */ - if (!ctx->subscr) - return; - - if (ctx->subscr->sgsn_data->msisdn_len < 1) - return; - - /* prepare the data for the decoder */ - memset(&called, 0, sizeof(called)); - msisdn[0] = ctx->subscr->sgsn_data->msisdn_len; - memcpy(&msisdn[1], ctx->subscr->sgsn_data->msisdn, - ctx->subscr->sgsn_data->msisdn_len); - - /* decode the string now */ - gsm48_decode_called(&called, msisdn); - - /* Prepend a '+' for international numbers */ - if (called.plan == 1 && called.type == 1) { - ctx->msisdn[0] = '+'; - osmo_strlcpy(&ctx->msisdn[1], called.number, - sizeof(ctx->msisdn)); - } else { - osmo_strlcpy(ctx->msisdn, called.number, sizeof(ctx->msisdn)); - } -} - -void extract_subscr_hlr(struct sgsn_mm_ctx *ctx) -{ - struct gsm_mncc_number called; - uint8_t hlr_number[sizeof(ctx->subscr->sgsn_data->hlr) + 1]; - - if (!ctx->subscr) - return; - - if (ctx->subscr->sgsn_data->hlr_len < 1) - return; - - /* prepare the data for the decoder */ - memset(&called, 0, sizeof(called)); - hlr_number[0] = ctx->subscr->sgsn_data->hlr_len; - memcpy(&hlr_number[1], ctx->subscr->sgsn_data->hlr, - ctx->subscr->sgsn_data->hlr_len); - - /* decode the string now */ - gsm48_decode_called(&called, hlr_number); - - if (called.plan != 1) { - LOGMMCTXP(LOGL_ERROR, ctx, - "Numbering plan(%d) not allowed\n", - called.plan); - return; - } - - if (called.type != 1) { - LOGMMCTXP(LOGL_ERROR, ctx, - "Numbering type(%d) not allowed\n", - called.type); - return; - } - - osmo_strlcpy(ctx->hlr, called.number, sizeof(ctx->hlr)); -} - -#ifdef BUILD_IU -/* Chapter 9.4.21: Service accept */ -static int gsm48_tx_gmm_service_ack(struct sgsn_mm_ctx *mm) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 SERVICE ACK"); - struct gsm48_hdr *gh; - - LOGMMCTXP(LOGL_INFO, mm, "<- GPRS SERVICE ACCEPT (P-TMSI=0x%08x)\n", mm->p_tmsi); - - mmctx2msgid(msg, mm); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); - gh->proto_discr = GSM48_PDISC_MM_GPRS; - gh->msg_type = GSM48_MT_GMM_SERVICE_ACK; - - /* Optional: PDP context status */ - /* Optional: MBMS context status */ - - return gsm48_gmm_sendmsg(msg, 0, mm, false); -} -#endif - -/* Chapter 9.4.22: Service reject */ -static int _tx_gmm_service_rej(struct msgb *msg, uint8_t gmm_cause, - const struct sgsn_mm_ctx *mm) -{ - struct gsm48_hdr *gh; - - LOGMMCTXP(LOGL_NOTICE, mm, "<- GPRS SERVICE REJECT: %s\n", - get_value_string(gsm48_gmm_cause_names, gmm_cause)); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); - gh->proto_discr = GSM48_PDISC_MM_GPRS; - gh->msg_type = GSM48_MT_GMM_SERVICE_REJ; - gh->data[0] = gmm_cause; - - return gsm48_gmm_sendmsg(msg, 0, NULL, true); -} -static int gsm48_tx_gmm_service_rej_oldmsg(const struct msgb *old_msg, - uint8_t gmm_cause) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 SERVICE REJ OLD"); - gmm_copy_id(msg, old_msg); - return _tx_gmm_service_rej(msg, gmm_cause, NULL); -} -#if 0 --- currently unused -- -static int gsm48_tx_gmm_service_rej(struct sgsn_mm_ctx *mm, - uint8_t gmm_cause) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 SERVICE REJ"); - mmctx2msgid(msg, mm); - return _tx_gmm_service_rej(msg, gmm_cause, mm); -} -#endif - -static int gsm48_tx_gmm_ra_upd_ack(struct sgsn_mm_ctx *mm); - -/* Check if we can already authorize a subscriber */ -int gsm48_gmm_authorize(struct sgsn_mm_ctx *ctx) -{ -#ifdef BUILD_IU - int rc; -#endif -#ifndef PTMSI_ALLOC - struct sgsn_signal_data sig_data; -#endif - - /* Request IMSI and IMEI from the MS if they are unknown */ - if (!strlen(ctx->imei)) { - ctx->t3370_id_type = GSM_MI_TYPE_IMEI; - mmctx_timer_start(ctx, 3370); - return gsm48_tx_gmm_id_req(ctx, GSM_MI_TYPE_IMEI); - } - if (!strlen(ctx->imsi)) { - ctx->t3370_id_type = GSM_MI_TYPE_IMSI; - mmctx_timer_start(ctx, 3370); - return gsm48_tx_gmm_id_req(ctx, GSM_MI_TYPE_IMSI); - } - - /* All information required for authentication is available */ - ctx->t3370_id_type = GSM_MI_TYPE_NONE; - - if (ctx->auth_state == SGSN_AUTH_UNKNOWN) { - /* Request authorization, this leads to a call to - * sgsn_auth_update which in turn calls - * gsm0408_gprs_access_granted or gsm0408_gprs_access_denied */ - - sgsn_auth_request(ctx); - /* Note that gsm48_gmm_authorize can be called recursively via - * sgsn_auth_request iff ctx->auth_info changes to AUTH_ACCEPTED - */ - return 0; - } - - if (ctx->auth_state == SGSN_AUTH_AUTHENTICATE - && !sgsn_mm_ctx_is_authenticated(ctx)) { - struct gsm_auth_tuple *at = &ctx->auth_triplet; - - mmctx_timer_start(ctx, 3360); - return gsm48_tx_gmm_auth_ciph_req(ctx, &at->vec, at->key_seq, - false); - } - - if (ctx->auth_state == SGSN_AUTH_AUTHENTICATE && sgsn_mm_ctx_is_authenticated(ctx) && - ctx->auth_triplet.key_seq != GSM_KEY_SEQ_INVAL) { - /* Check again for authorization */ - sgsn_auth_request(ctx); - return 0; - } - - if (ctx->auth_state != SGSN_AUTH_ACCEPTED) { - LOGMMCTXP(LOGL_NOTICE, ctx, - "authorization is denied, aborting procedure\n"); - return -EACCES; - } - - /* The MS is authorized */ -#ifdef BUILD_IU - if (ctx->ran_type == MM_CTX_T_UTRAN_Iu && !ctx->iu.ue_ctx->integrity_active) { - rc = ranap_iu_tx_sec_mode_cmd(ctx->iu.ue_ctx, &ctx->auth_triplet.vec, 0, ctx->iu.new_key); - ctx->iu.new_key = 0; - return rc; - } -#endif - - switch (ctx->pending_req) { - case 0: - LOGMMCTXP(LOGL_INFO, ctx, - "no pending request, authorization completed\n"); - break; - case GSM48_MT_GMM_ATTACH_REQ: - ctx->pending_req = 0; - - extract_subscr_msisdn(ctx); - extract_subscr_hlr(ctx); -#ifdef PTMSI_ALLOC - /* Start T3350 and re-transmit up to 5 times until ATTACH COMPLETE */ - mmctx_timer_start(ctx, 3350); - ctx->t3350_mode = GMM_T3350_MODE_ATT; -#else - memset(&sig_data, 0, sizeof(sig_data)); - sig_data.mm = mmctx; - osmo_signal_dispatch(SS_SGSN, S_SGSN_ATTACH, &sig_data); - ctx->gmm_state = GMM_REGISTERED_NORMAL; -#endif - - return gsm48_tx_gmm_att_ack(ctx); -#ifdef BUILD_IU - case GSM48_MT_GMM_SERVICE_REQ: - ctx->pending_req = 0; - osmo_fsm_inst_dispatch(ctx->iu.mm_state_fsm, E_PMM_PS_ATTACH, NULL); - rc = gsm48_tx_gmm_service_ack(ctx); - - if (ctx->iu.service.type != GPRS_SERVICE_T_SIGNALLING) - activate_pdp_rabs(ctx); - - return rc; -#endif - case GSM48_MT_GMM_RA_UPD_REQ: - ctx->pending_req = 0; - /* Send RA UPDATE ACCEPT */ - return gsm48_tx_gmm_ra_upd_ack(ctx); - - default: - LOGMMCTXP(LOGL_ERROR, ctx, - "only Attach Request is supported yet, " - "got request type %u\n", ctx->pending_req); - break; - } - - return 0; -} - -void gsm0408_gprs_authenticate(struct sgsn_mm_ctx *ctx) -{ - ctx->sec_ctx = OSMO_AUTH_TYPE_NONE; - - if (ctx->gmm_att_req.fsm->state != ST_INIT) - osmo_fsm_inst_dispatch(ctx->gmm_att_req.fsm, E_VLR_ANSWERED, (void *) 0); - else - gsm48_gmm_authorize(ctx); -} - -void gsm0408_gprs_access_granted(struct sgsn_mm_ctx *ctx) -{ - switch (ctx->gmm_state) { - case GMM_COMMON_PROC_INIT: - LOGMMCTXP(LOGL_NOTICE, ctx, - "Authorized, continuing procedure, IMSI=%s\n", - ctx->imsi); - /* Continue with the authorization */ - if (ctx->gmm_att_req.fsm->state != ST_INIT) - osmo_fsm_inst_dispatch(ctx->gmm_att_req.fsm, E_VLR_ANSWERED, (void *) 0); - break; - default: - LOGMMCTXP(LOGL_INFO, ctx, - "Authorized, ignored, IMSI=%s\n", - ctx->imsi); - } -} - -void gsm0408_gprs_access_denied(struct sgsn_mm_ctx *ctx, int gmm_cause) -{ - if (gmm_cause == SGSN_ERROR_CAUSE_NONE) - gmm_cause = GMM_CAUSE_GPRS_NOTALLOWED; - - switch (ctx->gmm_state) { - case GMM_COMMON_PROC_INIT: - LOGMMCTXP(LOGL_NOTICE, ctx, - "Not authorized, rejecting ATTACH REQUEST " - "with cause '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, gmm_cause), - gmm_cause); - if (ctx->gmm_att_req.fsm->state != ST_INIT) - osmo_fsm_inst_dispatch(ctx->gmm_att_req.fsm, E_REJECT, (void *) (long) gmm_cause); - break; - case GMM_REGISTERED_NORMAL: - case GMM_REGISTERED_SUSPENDED: - LOGMMCTXP(LOGL_NOTICE, ctx, - "Authorization lost, detaching " - "with cause '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, gmm_cause), - gmm_cause); - gsm48_tx_gmm_detach_req( - ctx, GPRS_DET_T_MT_IMSI, gmm_cause); - - mm_ctx_cleanup_free(ctx, "auth lost"); - break; - default: - LOGMMCTXP(LOGL_INFO, ctx, - "Authorization lost, cause is '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, gmm_cause), - gmm_cause); - mm_ctx_cleanup_free(ctx, "auth lost"); - } -} - -void gsm0408_gprs_access_cancelled(struct sgsn_mm_ctx *ctx, int gmm_cause) -{ - if (gmm_cause != SGSN_ERROR_CAUSE_NONE) { - LOGMMCTXP(LOGL_INFO, ctx, - "Cancelled with cause '%s' (%d), deleting context\n", - get_value_string(gsm48_gmm_cause_names, gmm_cause), - gmm_cause); - gsm0408_gprs_access_denied(ctx, gmm_cause); - return; - } - - LOGMMCTXP(LOGL_INFO, ctx, "Cancelled, deleting context silently\n"); - mm_ctx_cleanup_free(ctx, "access cancelled"); -} - -/* Parse Chapter 9.4.13 Identity Response */ -static int gsm48_rx_gmm_id_resp(struct sgsn_mm_ctx *ctx, struct msgb *msg) -{ - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); - uint8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK; - long mi_typel = mi_type; - char mi_string[GSM48_MI_SIZE]; - - gsm48_mi_to_string(mi_string, sizeof(mi_string), &gh->data[1], gh->data[0]); - if (!ctx) { - DEBUGP(DMM, "from unknown TLLI 0x%08x?!? This should not happen\n", msgb_tlli(msg)); - return -EINVAL; - } - - LOGMMCTXP(LOGL_DEBUG, ctx, "-> GMM IDENTITY RESPONSE: MI(%s)=%s\n", - gsm48_mi_type_name(mi_type), mi_string); - - if (ctx->t3370_id_type == GSM_MI_TYPE_NONE) { - LOGMMCTXP(LOGL_NOTICE, ctx, - "Got unexpected IDENTITY RESPONSE: MI(%s)=%s, " - "ignoring message\n", - gsm48_mi_type_name(mi_type), mi_string); - return -EINVAL; - } - - if (mi_type == ctx->t3370_id_type) - mmctx_timer_stop(ctx, 3370); - - switch (mi_type) { - case GSM_MI_TYPE_IMSI: - /* we already have a mm context with current TLLI, but no - * P-TMSI / IMSI yet. What we now need to do is to fill - * this initial context with data from the HLR */ - if (strlen(ctx->imsi) == 0) { - /* Check if we already have a MM context for this IMSI */ - struct sgsn_mm_ctx *ictx; - ictx = sgsn_mm_ctx_by_imsi(mi_string); - if (ictx) { - /* Handle it like in gsm48_rx_gmm_det_req, - * except that no messages are sent to the BSS */ - - LOGMMCTXP(LOGL_NOTICE, ctx, "Deleting old MM Context for same IMSI " - "p_tmsi_old=0x%08x\n", - ictx->p_tmsi); - - mm_ctx_cleanup_free(ictx, "GPRS IMSI re-use"); - } - } - osmo_strlcpy(ctx->imsi, mi_string, sizeof(ctx->imsi)); - break; - case GSM_MI_TYPE_IMEI: - osmo_strlcpy(ctx->imei, mi_string, sizeof(ctx->imei)); - break; - case GSM_MI_TYPE_IMEISV: - break; - } - - /* Check if we can let the mobile station enter */ - return osmo_fsm_inst_dispatch(ctx->gmm_att_req.fsm, E_IDEN_RESP_RECV, (void *)mi_typel); -} - -/* Allocate a new P-TMSI and change context state */ -static inline void ptmsi_update(struct sgsn_mm_ctx *ctx) -{ - uint32_t ptmsi; - /* Don't change the P-TMSI if a P-TMSI re-assignment is under way */ - if (ctx->gmm_state != GMM_COMMON_PROC_INIT) { - ptmsi = sgsn_alloc_ptmsi(); - if (ptmsi != GSM_RESERVED_TMSI) { - ctx->p_tmsi_old = ctx->p_tmsi; - ctx->p_tmsi = ptmsi; - } else - LOGMMCTXP(LOGL_ERROR, ctx, "P-TMSI allocation failure: using old one.\n"); - } - ctx->gmm_state = GMM_COMMON_PROC_INIT; -} - -/* 3GPP TS 24.008 § 9.4.1 Attach request */ -static int gsm48_rx_gmm_att_req(struct sgsn_mm_ctx *ctx, struct msgb *msg, - struct gprs_llc_llme *llme) -{ - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); - uint8_t *cur = gh->data, *msnc, *mi, *ms_ra_acc_cap; - uint8_t msnc_len, att_type, mi_len, mi_type, ms_ra_acc_cap_len; - uint16_t drx_par; - uint32_t tmsi; - char mi_string[GSM48_MI_SIZE]; - struct gprs_ra_id ra_id; - uint16_t cid = 0; - enum gsm48_gmm_cause reject_cause; - int rc; - - LOGMMCTXP(LOGL_INFO, ctx, "-> GMM ATTACH REQUEST "); - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_ATTACH_REQUEST]); - - /* As per TS 04.08 Chapter 4.7.1.4, the attach request arrives either - * with a foreign TLLI (P-TMSI that was allocated to the MS before), - * or with random TLLI. */ - - if (!MSG_IU_UE_CTX(msg)) { - /* Gb mode */ - cid = bssgp_parse_cell_id(&ra_id, msgb_bcid(msg)); - } else { -#ifdef BUILD_IU - ra_id = MSG_IU_UE_CTX(msg)->ra_id; -#else - LOGMMCTXP(LOGL_ERROR, ctx, "Cannot handle Iu Attach Request, built without Iu support\n"); - return -ENOTSUP; -#endif - } - - /* MS network capability 10.5.5.12 */ - msnc_len = *cur++; - msnc = cur; - if (msnc_len > sizeof(ctx->ms_network_capa.buf)) - goto err_inval; - cur += msnc_len; - - /* TODO: In iu mode - handle follow-on request. - * The follow-on request can be signaled in an Attach Request on IuPS. - * This means the MS/UE asks to keep the PS connection open for further requests - * after the Attach Request succeed. - * The SGSN can decide if it close the connection or not. Both are spec conform. */ - - /* aTTACH Type 10.5.5.2 */ - att_type = *cur++ & 0x07; - - /* DRX parameter 10.5.5.6 */ - drx_par = *cur++ << 8; - drx_par |= *cur++; - - /* Mobile Identity (P-TMSI or IMSI) 10.5.1.4 */ - mi_len = *cur++; - mi = cur; - if (mi_len > 8) - goto err_inval; - mi_type = *mi & GSM_MI_TYPE_MASK; - cur += mi_len; - - gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len); - - DEBUGPC(DMM, "MI(%s) type=\"%s\" ", mi_string, - get_value_string(gprs_att_t_strs, att_type)); - - /* Old routing area identification 10.5.5.15. Skip it */ - cur += 6; - - /* MS Radio Access Capability 10.5.5.12a */ - ms_ra_acc_cap_len = *cur++; - ms_ra_acc_cap = cur; - if (ms_ra_acc_cap_len > sizeof(ctx->ms_radio_access_capa.buf)) - goto err_inval; - cur += ms_ra_acc_cap_len; - - LOGPC(DMM, LOGL_INFO, "\n"); - - /* Optional: Old P-TMSI Signature, Requested READY timer, TMSI Status */ - - switch (mi_type) { - case GSM_MI_TYPE_IMSI: - /* Try to find MM context based on IMSI */ - if (!ctx) - ctx = sgsn_mm_ctx_by_imsi(mi_string); - if (!ctx) { - if (MSG_IU_UE_CTX(msg)) - ctx = sgsn_mm_ctx_alloc_iu(MSG_IU_UE_CTX(msg)); - else - ctx = sgsn_mm_ctx_alloc_gb(0, &ra_id); - if (!ctx) { - reject_cause = GMM_CAUSE_NET_FAIL; - goto rejected; - } - osmo_strlcpy(ctx->imsi, mi_string, sizeof(ctx->imsi)); - } - break; - case GSM_MI_TYPE_TMSI: - memcpy(&tmsi, mi+1, 4); - tmsi = ntohl(tmsi); - /* Try to find MM context based on P-TMSI */ - if (!ctx) - ctx = sgsn_mm_ctx_by_ptmsi(tmsi); - if (!ctx) { - /* Allocate a context as most of our code expects one. - * Context will not have an IMSI ultil ID RESP is received */ - if (MSG_IU_UE_CTX(msg)) - ctx = sgsn_mm_ctx_alloc_iu(MSG_IU_UE_CTX(msg)); - else - ctx = sgsn_mm_ctx_alloc_gb(msgb_tlli(msg), &ra_id); - if (!ctx) { - reject_cause = GMM_CAUSE_NET_FAIL; - goto rejected; - } - ctx->p_tmsi = tmsi; - } - break; - default: - LOGMMCTXP(LOGL_NOTICE, ctx, "Rejecting ATTACH REQUEST with " - "MI type %s\n", gsm48_mi_type_name(mi_type)); - reject_cause = GMM_CAUSE_MS_ID_NOT_DERIVED; - goto rejected; - } - - if (ctx->ran_type == MM_CTX_T_GERAN_Gb) { - ctx->gb.tlli = msgb_tlli(msg); - ctx->gb.llme = llme; - } - msgid2mmctx(ctx, msg); - /* Update MM Context with currient RA and Cell ID */ - ctx->ra = ra_id; - if (ctx->ran_type == MM_CTX_T_GERAN_Gb) - ctx->gb.cell_id = cid; - - /* Update MM Context with other data */ - ctx->drx_parms = drx_par; - ctx->ms_radio_access_capa.len = ms_ra_acc_cap_len; - memcpy(ctx->ms_radio_access_capa.buf, ms_ra_acc_cap, - ctx->ms_radio_access_capa.len); - ctx->ms_network_capa.len = msnc_len; - memcpy(ctx->ms_network_capa.buf, msnc, msnc_len); - if (!gprs_ms_net_cap_gea_supported(ctx->ms_network_capa.buf, msnc_len, - ctx->ciph_algo)) { - reject_cause = GMM_CAUSE_PROTO_ERR_UNSPEC; - LOGMMCTXP(LOGL_NOTICE, ctx, "Rejecting ATTACH REQUEST with MI " - "type %s because MS do not support required %s " - "encryption\n", gsm48_mi_type_name(mi_type), - get_value_string(gprs_cipher_names,ctx->ciph_algo)); - goto rejected; - } -#ifdef PTMSI_ALLOC - /* Allocate a new P-TMSI (+ P-TMSI signature) and update TLLI */ - ptmsi_update(ctx); -#endif - - if (ctx->ran_type == MM_CTX_T_GERAN_Gb) { - /* Even if there is no P-TMSI allocated, the MS will - * switch from foreign TLLI to local TLLI */ - ctx->gb.tlli_new = gprs_tmsi2tlli(ctx->p_tmsi, TLLI_LOCAL); - - /* Inform LLC layer about new TLLI but keep old active */ - if (sgsn_mm_ctx_is_authenticated(ctx)) - gprs_llme_copy_key(ctx, ctx->gb.llme); - - gprs_llgmm_assign(ctx->gb.llme, ctx->gb.tlli, ctx->gb.tlli_new); - } - - osmo_fsm_inst_dispatch(ctx->gmm_att_req.fsm, E_ATTACH_REQ_RECV, msg); - return 0; - -err_inval: - LOGPC(DMM, LOGL_INFO, "\n"); - reject_cause = GMM_CAUSE_SEM_INCORR_MSG; - -rejected: - /* Send ATTACH REJECT */ - LOGMMCTXP(LOGL_NOTICE, ctx, - "Rejecting Attach Request with cause '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, reject_cause), reject_cause); - rc = gsm48_tx_gmm_att_rej_oldmsg(msg, reject_cause); - if (ctx) - mm_ctx_cleanup_free(ctx, "GPRS ATTACH REJ"); - else if (llme) - gprs_llgmm_unassign(llme); - - return rc; - -} - - -/* Checks if two attach request contain the IEs and IE values - * return 0 if equal - * return -1 if error - * return 1 if unequal - * - * Only do a simple memcmp for now. - */ -int gprs_gmm_attach_req_ies(struct msgb *a, struct msgb *b) -{ - struct gsm48_hdr *gh_a = (struct gsm48_hdr *) msgb_gmmh(a); - struct gsm48_hdr *gh_b = (struct gsm48_hdr *) msgb_gmmh(b); - -#define GMM_ATTACH_REQ_LEN 26 - - /* there is the LLC FCS behind */ - if (msgb_l3len(a) < GMM_ATTACH_REQ_LEN || msgb_l3len(b) < GMM_ATTACH_REQ_LEN) - return -1; - - return !!memcmp(gh_a, gh_b, GMM_ATTACH_REQ_LEN); -} - -/* 3GPP TS 24.008 § 4.7.4.1 / 9.4.5.2 MO Detach request */ -static int gsm48_rx_gmm_det_req(struct sgsn_mm_ctx *ctx, struct msgb *msg) -{ - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); - uint8_t detach_type, power_off; - int rc = 0; - - detach_type = gh->data[0] & 0x7; - power_off = gh->data[0] & 0x8; - - /* FIXME: In 24.008 there is an optional P-TMSI and P-TMSI signature IE */ - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_DETACH_REQUEST]); - LOGMMCTXP(LOGL_INFO, ctx, "-> GMM DETACH REQUEST TLLI=0x%08x type=%s %s\n", - msgb_tlli(msg), get_value_string(gprs_det_t_mo_strs, detach_type), - power_off ? "Power-off" : ""); - - /* Only send the Detach Accept (MO) if power off isn't indicated, - * see 04.08, 4.7.4.1.2/3 for details */ - if (!power_off) { - /* force_stby = 0 */ - if (ctx) - rc = gsm48_tx_gmm_det_ack(ctx, 0); - else - rc = gsm48_tx_gmm_det_ack_oldmsg(msg, 0); - } - - if (ctx) { - struct sgsn_signal_data sig_data; - memset(&sig_data, 0, sizeof(sig_data)); - sig_data.mm = ctx; - osmo_signal_dispatch(SS_SGSN, S_SGSN_DETACH, &sig_data); - mm_ctx_cleanup_free(ctx, "GPRS DETACH REQUEST"); - } - - return rc; -} - -/* Chapter 9.4.15: Routing area update accept */ -static int gsm48_tx_gmm_ra_upd_ack(struct sgsn_mm_ctx *mm) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 UPD ACK"); - struct gsm48_hdr *gh; - struct gsm48_ra_upd_ack *rua; - uint8_t *mid; - unsigned long t; - - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_ROUTING_AREA_ACKED]); - LOGMMCTXP(LOGL_INFO, mm, "<- ROUTING AREA UPDATE ACCEPT\n"); - - mmctx2msgid(msg, mm); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); - gh->proto_discr = GSM48_PDISC_MM_GPRS; - gh->msg_type = GSM48_MT_GMM_RA_UPD_ACK; - - rua = (struct gsm48_ra_upd_ack *) msgb_put(msg, sizeof(*rua)); - rua->force_stby = 0; /* not indicated */ - rua->upd_result = 0; /* RA updated */ - t = osmo_tdef_get(sgsn->cfg.T_defs, 3312, OSMO_TDEF_S, -1); - rua->ra_upd_timer = gprs_secs_to_tmr_floor(t); - - gsm48_encode_ra(&rua->ra_id, &mm->ra); - -#if 0 - /* Optional: P-TMSI signature */ - msgb_v_put(msg, GSM48_IE_GMM_PTMSI_SIG); - ptsig = msgb_put(msg, 3); - ptsig[0] = mm->p_tmsi_sig >> 16; - ptsig[1] = mm->p_tmsi_sig >> 8; - ptsig[2] = mm->p_tmsi_sig & 0xff; -#endif - -#ifdef PTMSI_ALLOC - /* Optional: Allocated P-TMSI */ - mid = msgb_put(msg, GSM48_MID_TMSI_LEN); - gsm48_generate_mid_from_tmsi(mid, mm->p_tmsi); - mid[0] = GSM48_IE_GMM_ALLOC_PTMSI; -#endif - - /* Optional: Negotiated READY timer value */ - t = osmo_tdef_get(sgsn->cfg.T_defs, 3314, OSMO_TDEF_S, -1); - msgb_tv_put(msg, GSM48_IE_GMM_TIMER_READY, gprs_secs_to_tmr_floor(t)); - - /* Option: MS ID, ... */ - return gsm48_gmm_sendmsg(msg, 0, mm, true); -} - -/* Chapter 9.4.17: Routing area update reject */ -int gsm48_tx_gmm_ra_upd_rej(struct msgb *old_msg, uint8_t cause) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 RA UPD REJ"); - struct gsm48_hdr *gh; - - LOGP(DMM, LOGL_NOTICE, "<- ROUTING AREA UPDATE REJECT\n"); - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_ROUTING_AREA_REJECT]); - - gmm_copy_id(msg, old_msg); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 2); - gh->proto_discr = GSM48_PDISC_MM_GPRS; - gh->msg_type = GSM48_MT_GMM_RA_UPD_REJ; - gh->data[0] = cause; - gh->data[1] = 0; /* ? */ - - /* Option: P-TMSI signature, allocated P-TMSI, MS ID, ... */ - return gsm48_gmm_sendmsg(msg, 0, NULL, false); -} - -static void process_ms_ctx_status(struct sgsn_mm_ctx *mmctx, - const uint8_t *pdp_status) -{ - struct sgsn_pdp_ctx *pdp, *pdp2; - /* 24.008 4.7.5.1.3: If the PDP context status information element is - * included in ROUTING AREA UPDATE REQUEST message, then the network - * shall deactivate all those PDP contexts locally (without peer to - * peer signalling between the MS and the network), which are not in SM - * state PDP-INACTIVE on network side but are indicated by the MS as - * being in state PDP-INACTIVE. */ - - llist_for_each_entry_safe(pdp, pdp2, &mmctx->pdp_list, list) { - if (pdp->nsapi < 8) { - if (!(pdp_status[0] & (1 << pdp->nsapi))) { - LOGMMCTXP(LOGL_NOTICE, mmctx, "Dropping PDP context for NSAPI=%u " - "due to PDP CTX STATUS IE= 0x%02x%02x\n", - pdp->nsapi, pdp_status[1], pdp_status[0]); - sgsn_delete_pdp_ctx(pdp); - } - } else { - if (!(pdp_status[1] & (1 << (pdp->nsapi - 8)))) { - LOGMMCTXP(LOGL_NOTICE, mmctx, "Dropping PDP context for NSAPI=%u " - "due to PDP CTX STATUS IE= 0x%02x%02x\n", - pdp->nsapi, pdp_status[1], pdp_status[0]); - sgsn_delete_pdp_ctx(pdp); - } - } - } -} - -/* 3GPP TS 24.008 § 4.7.13.4 Service request procedure not accepted by the - * network. Returns true if MS has active PDP contexts in pdp_status */ -bool pdp_status_has_active_nsapis(const uint8_t *pdp_status, const size_t pdp_status_len) -{ - size_t i; - - for (i = 0; i < pdp_status_len; i++) - if (pdp_status[i] != 0) - return true; - - return false; -} - -/* Chapter 9.4.14: Routing area update request */ -static int gsm48_rx_gmm_ra_upd_req(struct sgsn_mm_ctx *mmctx, struct msgb *msg, - struct gprs_llc_llme *llme) -{ -#ifndef PTMSI_ALLOC - struct sgsn_signal_data sig_data; -#endif - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); - uint8_t *cur = gh->data; - uint8_t ms_ra_acc_cap_len; - struct gprs_ra_id old_ra_id; - struct tlv_parsed tp; - uint8_t upd_type; - enum gsm48_gmm_cause reject_cause = GMM_CAUSE_PROTO_ERR_UNSPEC; - int rc; - - /* TODO: In iu mode - handle follow-on request. - * The follow-on request can be signaled in an Attach Request on IuPS. - * This means the MS/UE asks to keep the PS connection open for further requests - * after the Attach Request succeed. - * The SGSN can decide if it close the connection or not. Both are spec conform. */ - - /* Update Type 10.5.5.18 */ - upd_type = *cur++ & 0x07; - - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_ROUTING_AREA_REQUEST]); - LOGMMCTXP(LOGL_INFO, mmctx, "-> GMM RA UPDATE REQUEST type=\"%s\"\n", - get_value_string(gprs_upd_t_strs, upd_type)); - - /* Old routing area identification 10.5.5.15 */ - gsm48_parse_ra(&old_ra_id, cur); - cur += 6; - - /* MS Radio Access Capability 10.5.5.12a */ - ms_ra_acc_cap_len = *cur++; - if (ms_ra_acc_cap_len > 52) { - LOGMMCTXP(LOGL_ERROR, mmctx, - "Rejecting GMM RA Update Request: MS Radio Access Capability too long" - " (ms_ra_acc_cap_len = %u > 52)\n", ms_ra_acc_cap_len); - reject_cause = GMM_CAUSE_PROTO_ERR_UNSPEC; - goto rejected; - } - cur += ms_ra_acc_cap_len; - - /* Optional: Old P-TMSI Signature, Requested READY timer, TMSI Status, - * DRX parameter, MS network capability */ - tlv_parse(&tp, &gsm48_gmm_att_tlvdef, cur, - (msg->data + msg->len) - cur, 0, 0); - - switch (upd_type) { - case GPRS_UPD_T_RA_LA: - case GPRS_UPD_T_RA_LA_IMSI_ATT: - LOGMMCTXP(LOGL_NOTICE, mmctx, "Update type %i unsupported in Mode III, is your SI13 corrupt?\n", upd_type); - reject_cause = GMM_CAUSE_PROTO_ERR_UNSPEC; - goto rejected; - case GPRS_UPD_T_RA: - case GPRS_UPD_T_PERIODIC: - break; - } - - if (!mmctx) { - /* BSSGP doesn't give us an mmctx */ - - /* TODO: Check if there is an MM CTX with old_ra_id and - * the P-TMSI (if given, reguired for UMTS) or as last resort - * if the TLLI matches foreign_tlli (P-TMSI). Note that this - * is an optimization to avoid the RA reject (impl detached) - * below, which will cause a new attach cycle. */ - /* Look-up the MM context based on old RA-ID and TLLI */ - if (!MSG_IU_UE_CTX(msg)) { - /* Gb */ - mmctx = sgsn_mm_ctx_by_tlli_and_ptmsi(msgb_tlli(msg), &old_ra_id); - } else if (TLVP_PRESENT(&tp, GSM48_IE_GMM_ALLOC_PTMSI)) { -#ifdef BUILD_IU - /* In Iu mode search only for ptmsi */ - char mi_string[GSM48_MI_SIZE]; - uint8_t mi_len = TLVP_LEN(&tp, GSM48_IE_GMM_ALLOC_PTMSI); - const uint8_t *mi = TLVP_VAL(&tp, GSM48_IE_GMM_ALLOC_PTMSI); - uint8_t mi_type = *mi & GSM_MI_TYPE_MASK; - uint32_t tmsi; - - gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len); - - if (mi_type == GSM_MI_TYPE_TMSI) { - memcpy(&tmsi, mi+1, 4); - tmsi = ntohl(tmsi); - mmctx = sgsn_mm_ctx_by_ptmsi(tmsi); - } -#else - LOGIUP(MSG_IU_UE_CTX(msg), LOGL_ERROR, - "Rejecting GMM RA Update Request: No Iu support\n"); - goto rejected; -#endif - } - if (mmctx) { - LOGMMCTXP(LOGL_INFO, mmctx, - "Looked up by matching TLLI and P_TMSI. " - "BSSGP TLLI: %08x, P-TMSI: %08x (%08x), " - "TLLI: %08x (%08x), RA: %s\n", - msgb_tlli(msg), - mmctx->p_tmsi, mmctx->p_tmsi_old, - mmctx->gb.tlli, mmctx->gb.tlli_new, - osmo_rai_name(&mmctx->ra)); - - mmctx->gmm_state = GMM_COMMON_PROC_INIT; - } - } else if (!gprs_ra_id_equals(&mmctx->ra, &old_ra_id) || - mmctx->gmm_state == GMM_DEREGISTERED) - { - /* We cannot use the mmctx */ - LOGMMCTXP(LOGL_INFO, mmctx, - "The MM context cannot be used, RA: %s\n", - osmo_rai_name(&mmctx->ra)); - /* mmctx is set to NULL and gprs_llgmm_unassign(llme) will be - called below, let's make sure we don't keep dangling llme - pointers in mmctx (OS#3957). */ - if (mmctx->ran_type == MM_CTX_T_GERAN_Gb) - OSMO_ASSERT(mmctx->gb.llme == NULL); - mmctx = NULL; - } - - if (!mmctx) { - if (llme) { - /* send a XID reset to re-set all LLC sequence numbers - * in the MS */ - LOGGBP(llme, LOGL_NOTICE, "LLC XID RESET\n"); - gprs_llgmm_reset(llme); - } - /* The MS has to perform GPRS attach */ - /* Device is still IMSI attached for CS but initiate GPRS ATTACH, - * see GSM 04.08, 4.7.5.1.4 and G.6 */ - LOGGBIUP(llme, msg, LOGL_ERROR, "Rejecting GMM RA Update Request: MS should GMM Attach first\n"); - reject_cause = GMM_CAUSE_IMPL_DETACHED; - goto rejected; - } - - /* Store new BVCI/NSEI in MM context (FIXME: delay until we ack?) */ - msgid2mmctx(mmctx, msg); - /* Bump the statistics of received signalling msgs for this MM context */ - rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_SIG_IN]); - - /* Update the MM context with the new RA-ID */ - if (mmctx->ran_type == MM_CTX_T_GERAN_Gb) { - bssgp_parse_cell_id(&mmctx->ra, msgb_bcid(msg)); - /* Update the MM context with the new (i.e. foreign) TLLI */ - mmctx->gb.tlli = msgb_tlli(msg); - } - /* FIXME: Update the MM context with the MS radio acc capabilities */ - /* FIXME: Update the MM context with the MS network capabilities */ - - rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_RA_UPDATE]); - -#ifdef PTMSI_ALLOC - ptmsi_update(mmctx); - - /* Start T3350 and re-transmit up to 5 times until ATTACH COMPLETE */ - mmctx->t3350_mode = GMM_T3350_MODE_RAU; - mmctx_timer_start(mmctx, 3350); -#else - /* Make sure we are NORMAL (i.e. not SUSPENDED anymore) */ - mmctx->gmm_state = GMM_REGISTERED_NORMAL; - - memset(&sig_data, 0, sizeof(sig_data)); - sig_data.mm = mmctx; - osmo_signal_dispatch(SS_SGSN, S_SGSN_UPDATE, &sig_data); -#endif - if (mmctx->ran_type == MM_CTX_T_GERAN_Gb) { - /* Even if there is no P-TMSI allocated, the MS will switch from - * foreign TLLI to local TLLI */ - mmctx->gb.tlli_new = gprs_tmsi2tlli(mmctx->p_tmsi, TLLI_LOCAL); - - /* Inform LLC layer about new TLLI but keep accepting the old one during Rx */ - gprs_llgmm_assign(mmctx->gb.llme, mmctx->gb.tlli, - mmctx->gb.tlli_new); - } - - /* Look at PDP Context Status IE and see if MS's view of - * activated/deactivated NSAPIs agrees with our view */ - if (TLVP_PRESENT(&tp, GSM48_IE_GMM_PDP_CTX_STATUS)) { - const uint8_t *pdp_status = TLVP_VAL(&tp, GSM48_IE_GMM_PDP_CTX_STATUS); - process_ms_ctx_status(mmctx, pdp_status); - } - - /* Send RA UPDATE ACCEPT. In Iu, the RA upd request can be called from - * a new Iu connection, so we might need to re-authenticate the - * connection as well as turn on integrity protection. */ - mmctx->pending_req = GSM48_MT_GMM_RA_UPD_REQ; - return gsm48_gmm_authorize(mmctx); - -rejected: - /* Send RA UPDATE REJECT */ - LOGMMCTXP(LOGL_NOTICE, mmctx, - "Rejecting RA Update Request with cause '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, reject_cause), reject_cause); - rc = gsm48_tx_gmm_ra_upd_rej(msg, reject_cause); - if (mmctx) - mm_ctx_cleanup_free(mmctx, "GPRS RA UPDATE REJ"); - else if (llme) - gprs_llgmm_unassign(llme); - - return rc; -} - -/* 3GPP TS 24.008 § 9.4.20 Service request. - * In Iu, a UE in PMM-IDLE mode can use GSM48_MT_GMM_SERVICE_REQ to switch back - * to PMM-CONNECTED mode. */ -static int gsm48_rx_gmm_service_req(struct sgsn_mm_ctx *ctx, struct msgb *msg) -{ - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); - uint8_t *cur = gh->data, *mi; - uint8_t service_type, mi_len, mi_type; - uint32_t tmsi; - struct tlv_parsed tp; - char mi_string[GSM48_MI_SIZE]; - enum gsm48_gmm_cause reject_cause; - int rc; - - LOGMMCTXP(LOGL_INFO, ctx, "-> GMM SERVICE REQUEST "); - - /* This message is only valid in Iu mode */ - if (!MSG_IU_UE_CTX(msg)) { - LOGPC(DMM, LOGL_INFO, "Invalid if not in Iu mode\n"); - return -1; - } - - /* Skip Ciphering key sequence number 10.5.1.2 */ - /* uint8_t ciph_seq_nr = *cur & 0x07; */ - - /* Service type 10.5.5.20 */ - service_type = (*cur++ >> 4) & 0x07; - - /* Mobile Identity (P-TMSI or IMSI) 10.5.1.4 */ - mi_len = *cur++; - mi = cur; - if (mi_len > 8) - goto err_inval; - mi_type = *mi & GSM_MI_TYPE_MASK; - cur += mi_len; - - gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len); - - DEBUGPC(DMM, "MI(%s) type=\"%s\" ", mi_string, - get_value_string(gprs_service_t_strs, service_type)); - - LOGPC(DMM, LOGL_INFO, "\n"); - - /* Optional: PDP context status, MBMS context status, Uplink data status, Device properties */ - tlv_parse(&tp, &gsm48_gmm_att_tlvdef, cur, (msg->data + msg->len) - cur, 0, 0); - - switch (mi_type) { - case GSM_MI_TYPE_IMSI: - /* Try to find MM context based on IMSI */ - if (!ctx) - ctx = sgsn_mm_ctx_by_imsi(mi_string); - if (!ctx) { - /* FIXME: We need to have a context for service request? */ - reject_cause = GMM_CAUSE_IMPL_DETACHED; - goto rejected; - } - msgid2mmctx(ctx, msg); - break; - case GSM_MI_TYPE_TMSI: - memcpy(&tmsi, mi+1, 4); - tmsi = ntohl(tmsi); - /* Try to find MM context based on P-TMSI */ - if (!ctx) - ctx = sgsn_mm_ctx_by_ptmsi(tmsi); - if (!ctx) { - /* FIXME: We need to have a context for service request? */ - reject_cause = GMM_CAUSE_IMPL_DETACHED; - goto rejected; - } - msgid2mmctx(ctx, msg); - break; - default: - LOGMMCTXP(LOGL_NOTICE, ctx, "Rejecting SERVICE REQUEST with " - "MI type %s\n", gsm48_mi_type_name(mi_type)); - reject_cause = GMM_CAUSE_MS_ID_NOT_DERIVED; - goto rejected; - } - - ctx->gmm_state = GMM_COMMON_PROC_INIT; - - ctx->iu.service.type = service_type; - - /* Look at PDP Context Status IE and see if MS's view of - * activated/deactivated NSAPIs agrees with our view */ - if (TLVP_PRESENT(&tp, GSM48_IE_GMM_PDP_CTX_STATUS)) { - const uint8_t *pdp_status = TLVP_VAL(&tp, GSM48_IE_GMM_PDP_CTX_STATUS); - const size_t pdp_status_len = TLVP_LEN(&tp, GSM48_IE_GMM_PDP_CTX_STATUS); - - process_ms_ctx_status(ctx, pdp_status); - - /* 3GPP TS 24.008 § 4.7.13.4 Service request procedure not - * accepted by the network. Cause #40. If MS has PDP Contexts in - * Active state in pdp_status but there is no PDP contexts on - * SGSN side then Reject with the cause will force the mobile to - * reset PDP contexts */ - if (llist_empty(&ctx->pdp_list) && pdp_status_has_active_nsapis(pdp_status, pdp_status_len)) { - reject_cause = GMM_CAUSE_NO_PDP_ACTIVATED; - goto rejected; - } - } - - - ctx->pending_req = GSM48_MT_GMM_SERVICE_REQ; - return gsm48_gmm_authorize(ctx); - -err_inval: - LOGPC(DMM, LOGL_INFO, "\n"); - reject_cause = GMM_CAUSE_SEM_INCORR_MSG; - -rejected: - /* Send SERVICE REJECT */ - LOGMMCTXP(LOGL_NOTICE, ctx, - "Rejecting Service Request with cause '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, reject_cause), reject_cause); - rc = gsm48_tx_gmm_service_rej_oldmsg(msg, reject_cause); - - return rc; - -} - - -static int gsm48_rx_gmm_status(struct sgsn_mm_ctx *mmctx, struct msgb *msg) -{ - struct gsm48_hdr *gh = msgb_l3(msg); - - LOGMMCTXP(LOGL_INFO, mmctx, "-> GPRS MM STATUS (cause: %s)\n", - get_value_string(gsm48_gmm_cause_names, gh->data[0])); - - return 0; -} - -/* Rx GPRS Mobility Management. MMCTX can be NULL when called. On !Gb (Iu), llme is NULL */ -int gsm0408_rcv_gmm(struct sgsn_mm_ctx *mmctx, struct msgb *msg, - struct gprs_llc_llme *llme, bool drop_cipherable) -{ - struct sgsn_signal_data sig_data; - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); - int rc; - - if (drop_cipherable && gsm48_hdr_gmm_cipherable(gh)) { - LOGMMCTXP(LOGL_NOTICE, mmctx, "Dropping cleartext GMM %s which " - "is expected to be encrypted for TLLI 0x%08x\n", - get_value_string(gprs_msgt_gmm_names, gh->msg_type), - llme->tlli); - return -EBADMSG; - } - - if (llme && !mmctx && - gh->msg_type != GSM48_MT_GMM_ATTACH_REQ && - gh->msg_type != GSM48_MT_GMM_RA_UPD_REQ) { - LOGGBP(llme, LOGL_NOTICE, "Cannot handle GMM for unknown MM CTX\n"); - /* 4.7.10 */ - if (gh->msg_type == GSM48_MT_GMM_STATUS) { - /* TLLI unassignment */ - gprs_llgmm_unassign(llme); - return 0; - } - - /* Don't reply or establish a LLME on DETACH_ACK */ - if (gh->msg_type == GSM48_MT_GMM_DETACH_ACK) - return gprs_llgmm_unassign(llme); - - /* Don't reply to deatch requests, reason power off */ - if (gh->msg_type == GSM48_MT_GMM_DETACH_REQ && - gh->data[0] & 0x8) { - return 0; - } - - - gprs_llgmm_reset(llme); - - /* Don't force it into re-attachment */ - if (gh->msg_type == GSM48_MT_GMM_DETACH_REQ) { - /* Handle Detach Request */ - rc = gsm48_rx_gmm_det_req(NULL, msg); - - /* TLLI unassignment */ - gprs_llgmm_unassign(llme); - return rc; - } - - /* Force the MS to re-attach */ - rc = gsm0408_gprs_force_reattach_oldmsg(msg, llme); - - /* TLLI unassignment */ - gprs_llgmm_unassign(llme); - return rc; - } - - /* - * For a few messages, mmctx may be NULL. For most, we want to ensure a - * non-NULL mmctx. At the same time, we want to keep the message - * validity check intact, so that all message types appear in the - * switch statement and the default case thus means "unknown message". - * If we split the switch in two parts to check non-NULL halfway, the - * unknown-message check breaks, or we'd need to duplicate the switch - * cases in both parts. Just keep one large switch and add some gotos. - */ - switch (gh->msg_type) { - case GSM48_MT_GMM_RA_UPD_REQ: - rc = gsm48_rx_gmm_ra_upd_req(mmctx, msg, llme); - break; - case GSM48_MT_GMM_ATTACH_REQ: - rc = gsm48_rx_gmm_att_req(mmctx, msg, llme); - break; - case GSM48_MT_GMM_SERVICE_REQ: - rc = gsm48_rx_gmm_service_req(mmctx, msg); - break; - /* For all the following types mmctx can not be NULL */ - case GSM48_MT_GMM_ID_RESP: - if (!mmctx) - goto null_mmctx; - rc = gsm48_rx_gmm_id_resp(mmctx, msg); - break; - case GSM48_MT_GMM_STATUS: - if (!mmctx) - goto null_mmctx; - rc = gsm48_rx_gmm_status(mmctx, msg); - break; - case GSM48_MT_GMM_DETACH_REQ: - if (!mmctx) - goto null_mmctx; - rc = gsm48_rx_gmm_det_req(mmctx, msg); - break; - case GSM48_MT_GMM_DETACH_ACK: - if (!mmctx) - goto null_mmctx; - LOGMMCTXP(LOGL_INFO, mmctx, "-> DETACH ACK\n"); - mm_ctx_cleanup_free(mmctx, "GPRS DETACH ACK"); - rc = 0; - break; - case GSM48_MT_GMM_ATTACH_COMPL: - if (!mmctx) - goto null_mmctx; - /* only in case SGSN offered new P-TMSI */ - LOGMMCTXP(LOGL_INFO, mmctx, "-> ATTACH COMPLETE\n"); - -#ifdef BUILD_IU - if (mmctx->iu.ue_ctx) { - ranap_iu_tx_release(mmctx->iu.ue_ctx, NULL); - } -#endif - - mmctx_timer_stop(mmctx, 3350); - mmctx->t3350_mode = GMM_T3350_MODE_NONE; - mmctx->p_tmsi_old = 0; - mmctx->pending_req = 0; - mmctx->gmm_state = GMM_REGISTERED_NORMAL; - switch(mmctx->ran_type) { - case MM_CTX_T_UTRAN_Iu: - osmo_fsm_inst_dispatch(mmctx->iu.mm_state_fsm, E_PMM_PS_ATTACH, NULL); - break; - case MM_CTX_T_GERAN_Gb: - /* Unassign the old TLLI */ - mmctx->gb.tlli = mmctx->gb.tlli_new; - gprs_llme_copy_key(mmctx, mmctx->gb.llme); - gprs_llgmm_assign(mmctx->gb.llme, TLLI_UNASSIGNED, - mmctx->gb.tlli_new); - osmo_fsm_inst_dispatch(mmctx->gb.mm_state_fsm, E_MM_GPRS_ATTACH, NULL); - break; - } - rc = 0; - - osmo_fsm_inst_dispatch(mmctx->gmm_att_req.fsm, E_ATTACH_COMPLETE_RECV, 0); - memset(&sig_data, 0, sizeof(sig_data)); - sig_data.mm = mmctx; - osmo_signal_dispatch(SS_SGSN, S_SGSN_ATTACH, &sig_data); - break; - case GSM48_MT_GMM_RA_UPD_COMPL: - if (!mmctx) - goto null_mmctx; - /* only in case SGSN offered new P-TMSI */ - LOGMMCTXP(LOGL_INFO, mmctx, "-> ROUTING AREA UPDATE COMPLETE\n"); - mmctx_timer_stop(mmctx, 3350); - mmctx->t3350_mode = GMM_T3350_MODE_NONE; - mmctx->p_tmsi_old = 0; - mmctx->pending_req = 0; - mmctx->gmm_state = GMM_REGISTERED_NORMAL; - switch(mmctx->ran_type) { - case MM_CTX_T_UTRAN_Iu: - osmo_fsm_inst_dispatch(mmctx->iu.mm_state_fsm, E_PMM_RA_UPDATE, NULL); - break; - case MM_CTX_T_GERAN_Gb: - /* Unassign the old TLLI */ - mmctx->gb.tlli = mmctx->gb.tlli_new; - gprs_llgmm_assign(mmctx->gb.llme, TLLI_UNASSIGNED, - mmctx->gb.tlli_new); - osmo_fsm_inst_dispatch(mmctx->gb.mm_state_fsm, E_MM_RA_UPDATE, NULL); - break; - } - rc = 0; - - memset(&sig_data, 0, sizeof(sig_data)); - sig_data.mm = mmctx; - osmo_signal_dispatch(SS_SGSN, S_SGSN_UPDATE, &sig_data); - break; - case GSM48_MT_GMM_PTMSI_REALL_COMPL: - if (!mmctx) - goto null_mmctx; - LOGMMCTXP(LOGL_INFO, mmctx, "-> PTMSI REALLOCATION COMPLETE\n"); - mmctx_timer_stop(mmctx, 3350); - mmctx->t3350_mode = GMM_T3350_MODE_NONE; - mmctx->p_tmsi_old = 0; - mmctx->pending_req = 0; - if (mmctx->ran_type == MM_CTX_T_GERAN_Gb) { - /* Unassign the old TLLI */ - mmctx->gb.tlli = mmctx->gb.tlli_new; - //gprs_llgmm_assign(mmctx->gb.llme, TLLI_UNASSIGNED, mmctx->gb.tlli_new, GPRS_ALGO_GEA0, NULL); - } - rc = 0; - break; - case GSM48_MT_GMM_AUTH_CIPH_RESP: - if (!mmctx) - goto null_mmctx; - rc = gsm48_rx_gmm_auth_ciph_resp(mmctx, msg); - break; - case GSM48_MT_GMM_AUTH_CIPH_FAIL: - rc = gsm48_rx_gmm_auth_ciph_fail(mmctx, msg); - break; - default: - LOGMMCTXP(LOGL_NOTICE, mmctx, "Unknown GSM 04.08 GMM msg type 0x%02x\n", - gh->msg_type); - if (mmctx) - rc = gsm48_tx_gmm_status(mmctx, GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL); - else - rc = -EINVAL; - break; - } - - return rc; - -null_mmctx: - LOGGBIUP(llme, msg, LOGL_ERROR, - "Received GSM 04.08 message type 0x%02x," - " but no MM context available\n", - gh->msg_type); - return -EINVAL; -} - -static void mmctx_timer_cb(void *_mm) -{ - struct sgsn_mm_ctx *mm = _mm; - struct gsm_auth_tuple *at; - int rc; - unsigned long seconds; - - mm->num_T_exp++; - - switch (mm->T) { - case 3350: /* waiting for ATTACH COMPLETE */ - if (mm->num_T_exp >= 5) { - LOGMMCTXP(LOGL_NOTICE, mm, "T3350 expired >= 5 times\n"); - mm_ctx_cleanup_free(mm, "T3350"); - /* FIXME: should we return some error? */ - break; - } - /* re-transmit the respective msg and re-start timer */ - switch (mm->t3350_mode) { - case GMM_T3350_MODE_ATT: - gsm48_tx_gmm_att_ack(mm); - break; - case GMM_T3350_MODE_RAU: - gsm48_tx_gmm_ra_upd_ack(mm); - break; - case GMM_T3350_MODE_PTMSI_REALL: - /* FIXME */ - break; - case GMM_T3350_MODE_NONE: - LOGMMCTXP(LOGL_NOTICE, mm, - "T3350 mode wasn't set, ignoring timeout\n"); - break; - } - seconds = osmo_tdef_get(sgsn->cfg.T_defs, 3350, OSMO_TDEF_S, -1); - osmo_timer_schedule(&mm->timer, seconds, 0); - break; - case 3360: /* waiting for AUTH AND CIPH RESP */ - if (mm->num_T_exp >= 5) { - LOGMMCTXP(LOGL_NOTICE, mm, "T3360 expired >= 5 times\n"); - mm_ctx_cleanup_free(mm, "T3360"); - break; - } - /* Re-transmit the respective msg and re-start timer */ - if (mm->auth_triplet.key_seq == GSM_KEY_SEQ_INVAL) { - LOGMMCTXP(LOGL_ERROR, mm, - "timeout: invalid auth triplet reference\n"); - mm_ctx_cleanup_free(mm, "T3360"); - break; - } - at = &mm->auth_triplet; - - rc = gsm48_tx_gmm_auth_ciph_req(mm, &at->vec, at->key_seq, false); - if (rc < 0) { - LOGMMCTXP(LOGL_ERROR, mm, "failed sending Auth. & Ciph. Request: %s \n", strerror(-rc)); - } else { - seconds = osmo_tdef_get(sgsn->cfg.T_defs, 3360, OSMO_TDEF_S, -1); - osmo_timer_schedule(&mm->timer, seconds, 0); - } - break; - case 3370: /* waiting for IDENTITY RESPONSE */ - if (mm->num_T_exp >= 5) { - LOGMMCTXP(LOGL_NOTICE, mm, "T3370 expired >= 5 times\n"); - gsm48_tx_gmm_att_rej(mm, GMM_CAUSE_MS_ID_NOT_DERIVED); - mm_ctx_cleanup_free(mm, "GPRS ATTACH REJECT (T3370)"); - break; - } - /* re-tranmit IDENTITY REQUEST and re-start timer */ - gsm48_tx_gmm_id_req(mm, mm->t3370_id_type); - seconds = osmo_tdef_get(sgsn->cfg.T_defs, 3370, OSMO_TDEF_S, -1); - osmo_timer_schedule(&mm->timer, seconds, 0); - break; - default: - LOGMMCTXP(LOGL_ERROR, mm, "timer expired in unknown mode %u\n", - mm->T); - } -} - -/* GPRS SESSION MANAGEMENT */ - -static void pdpctx_timer_cb(void *_mm); - - -static void pdpctx_timer_rearm(struct sgsn_pdp_ctx *pdp, unsigned int T) -{ - unsigned long seconds; - if (osmo_timer_pending(&pdp->timer)) - LOGPDPCTXP(LOGL_ERROR, pdp, "Scheduling PDP timer %u while old " - "timer %u pending\n", T, pdp->T); - seconds = osmo_tdef_get(sgsn->cfg.T_defs, T, OSMO_TDEF_S, -1); - osmo_timer_schedule(&pdp->timer, seconds, 0); -} - -static void pdpctx_timer_start(struct sgsn_pdp_ctx *pdp, unsigned int T) -{ - if (osmo_timer_pending(&pdp->timer)) - LOGPDPCTXP(LOGL_ERROR, pdp, "Starting PDP timer %u while old " - "timer %u pending\n", T, pdp->T); - pdp->T = T; - pdp->num_T_exp = 0; - - osmo_timer_setup(&pdp->timer, pdpctx_timer_cb, pdp); - pdpctx_timer_rearm(pdp, pdp->T); -} - -static void pdpctx_timer_stop(struct sgsn_pdp_ctx *pdp, unsigned int T) -{ - if (pdp->T != T) - LOGPDPCTXP(LOGL_ERROR, pdp, "Stopping PDP timer %u but " - "%u is running\n", T, pdp->T); - osmo_timer_del(&pdp->timer); -} - -void pdp_ctx_detach_mm_ctx(struct sgsn_pdp_ctx *pdp) -{ - /* Detach from MM context */ - llist_del(&pdp->list); - pdp->mm = NULL; - - /* stop timer 3395 */ - pdpctx_timer_stop(pdp, 3395); -} - -#if 0 -static void msgb_put_pdp_addr_ipv4(struct msgb *msg, uint32_t ipaddr) -{ - uint8_t v[6]; - - v[0] = PDP_TYPE_ORG_IETF; - v[1] = PDP_TYPE_N_IETF_IPv4; - *(uint32_t *)(v+2) = htonl(ipaddr); - - msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, sizeof(v), v); -} - -static void msgb_put_pdp_addr_ppp(struct msgb *msg) -{ - uint8_t v[2]; - - v[0] = PDP_TYPE_ORG_ETSI; - v[1] = PDP_TYPE_N_ETSI_PPP; - - msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, sizeof(v), v); -} -#endif - -/* 3GPP TS 24.008 § 9.5.2: Activate PDP Context Accept */ -int gsm48_tx_gsm_act_pdp_acc(struct sgsn_pdp_ctx *pdp) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 PDP ACC"); - struct gsm48_hdr *gh; - uint8_t transaction_id = pdp->ti ^ 0x8; /* flip */ - - LOGPDPCTXP(LOGL_INFO, pdp, "<- ACTIVATE PDP CONTEXT ACK\n"); - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_PDP_ACTIVATE_ACCEPT]); - - mmctx2msgid(msg, pdp->mm); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); - gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4); - gh->msg_type = GSM48_MT_GSM_ACT_PDP_ACK; - - /* Negotiated LLC SAPI */ - msgb_v_put(msg, pdp->sapi); - - /* FIXME: copy QoS parameters from original request */ - //msgb_lv_put(msg, pdp->lib->qos_neg.l, pdp->lib->qos_neg.v); - msgb_lv_put(msg, sizeof(default_qos), (uint8_t *)&default_qos); - - /* Radio priority 10.5.7.2 */ - msgb_v_put(msg, pdp->lib->radio_pri); - - /* PDP address */ - /* Highest 4 bits of first byte need to be set to 1, otherwise - * the IE is identical with the 04.08 PDP Address IE */ - pdp->lib->eua.v[0] &= ~0xf0; - msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, - pdp->lib->eua.l, pdp->lib->eua.v); - pdp->lib->eua.v[0] |= 0xf0; - - /* Optional: Protocol configuration options (FIXME: why 'req') */ - if (pdp->lib->pco_req.l) - msgb_tlv_put(msg, GSM48_IE_GSM_PROTO_CONF_OPT, - pdp->lib->pco_req.l, pdp->lib->pco_req.v); - - /* Optional: Packet Flow Identifier */ - - return gsm48_gmm_sendmsg(msg, 0, pdp->mm, true); -} - -/* 3GPP TS 24.008 § 9.5.3: Activate PDP Context reject */ -int gsm48_tx_gsm_act_pdp_rej(struct sgsn_mm_ctx *mm, uint8_t tid, - uint8_t cause, uint8_t pco_len, uint8_t *pco_v) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 PDP REJ"); - struct gsm48_hdr *gh; - uint8_t transaction_id = tid ^ 0x8; /* flip */ - - LOGMMCTXP(LOGL_NOTICE, mm, "<- ACTIVATE PDP CONTEXT REJ: %s\n", - get_value_string(gsm48_gsm_cause_names, cause)); - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_PDP_ACTIVATE_REJECT]); - - mmctx2msgid(msg, mm); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); - gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4); - gh->msg_type = GSM48_MT_GSM_ACT_PDP_REJ; - - msgb_v_put(msg, cause); - if (pco_len && pco_v) - msgb_tlv_put(msg, GSM48_IE_GSM_PROTO_CONF_OPT, pco_len, pco_v); - - return gsm48_gmm_sendmsg(msg, 0, mm, true); -} - -/* 3GPP TS 24.008 § 9.5.8: Deactivate PDP Context Request */ -static int _gsm48_tx_gsm_deact_pdp_req(struct sgsn_mm_ctx *mm, uint8_t tid, - uint8_t sm_cause, bool teardown) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 PDP DET REQ"); - struct gsm48_hdr *gh; - uint8_t transaction_id = tid ^ 0x8; /* flip */ - uint8_t tear_down_ind = (0x9 << 4) | (!!teardown); - - LOGMMCTXP(LOGL_INFO, mm, "<- DEACTIVATE PDP CONTEXT REQ\n"); - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_PDP_DL_DEACTIVATE_REQUEST]); - - mmctx2msgid(msg, mm); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); - gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4); - gh->msg_type = GSM48_MT_GSM_DEACT_PDP_REQ; - - msgb_v_put(msg, sm_cause); - msgb_v_put(msg, tear_down_ind); - - return gsm48_gmm_sendmsg(msg, 0, mm, true); -} -int gsm48_tx_gsm_deact_pdp_req(struct sgsn_pdp_ctx *pdp, uint8_t sm_cause, bool teardown) -{ - pdpctx_timer_start(pdp, 3395); - - return _gsm48_tx_gsm_deact_pdp_req(pdp->mm, pdp->ti, sm_cause, teardown); -} - -/* 3GPP TS 24.008 § 9.5.9: Deactivate PDP Context Accept */ -static int _gsm48_tx_gsm_deact_pdp_acc(struct sgsn_mm_ctx *mm, uint8_t tid) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 PDP DET ACC"); - struct gsm48_hdr *gh; - uint8_t transaction_id = tid ^ 0x8; /* flip */ - - LOGMMCTXP(LOGL_INFO, mm, "<- DEACTIVATE PDP CONTEXT ACK\n"); - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_PDP_DL_DEACTIVATE_ACCEPT]); - - mmctx2msgid(msg, mm); - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); - gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4); - gh->msg_type = GSM48_MT_GSM_DEACT_PDP_ACK; - - return gsm48_gmm_sendmsg(msg, 0, mm, true); -} -int gsm48_tx_gsm_deact_pdp_acc(struct sgsn_pdp_ctx *pdp) -{ - return _gsm48_tx_gsm_deact_pdp_acc(pdp->mm, pdp->ti); -} - -static int activate_ggsn(struct sgsn_mm_ctx *mmctx, - struct sgsn_ggsn_ctx *ggsn, const uint8_t transaction_id, - const uint8_t req_nsapi, const uint8_t req_llc_sapi, - struct tlv_parsed *tp, int destroy_ggsn) -{ - struct sgsn_pdp_ctx *pdp; - - LOGMMCTXP(LOGL_DEBUG, mmctx, "Using GGSN %u\n", ggsn->id); - ggsn->gsn = sgsn->gsn; - pdp = sgsn_create_pdp_ctx(ggsn, mmctx, req_nsapi, tp); - if (!pdp) - return -1; - - /* Store SAPI and Transaction Identifier */ - pdp->sapi = req_llc_sapi; - pdp->ti = transaction_id; - pdp->destroy_ggsn = destroy_ggsn; - - return 0; -} - -static void ggsn_lookup_cb(void *arg, int status, int timeouts, struct hostent *hostent) -{ - struct sgsn_ggsn_ctx *ggsn; - struct sgsn_ggsn_lookup *lookup = arg; - struct in_addr *addr = NULL; - char buf[INET_ADDRSTRLEN]; - - /* The context is gone while we made a request */ - if (!lookup->mmctx) { - talloc_free(lookup->orig_msg); - talloc_free(lookup); - return; - } - - if (status != ARES_SUCCESS) { - struct sgsn_mm_ctx *mmctx = lookup->mmctx; - - LOGMMCTXP(LOGL_ERROR, mmctx, "DNS query failed.\n"); - - /* Need to try with three digits now */ - if (lookup->state == SGSN_GGSN_2DIGIT) { - char *hostname; - int rc; - - lookup->state = SGSN_GGSN_3DIGIT; - hostname = osmo_apn_qualify_from_imsi(mmctx->imsi, - lookup->apn_str, 1); - LOGMMCTXP(LOGL_DEBUG, mmctx, - "Going to query %s\n", hostname); - rc = sgsn_ares_query(sgsn, hostname, - ggsn_lookup_cb, lookup); - if (rc != 0) { - LOGMMCTXP(LOGL_ERROR, mmctx, "Couldn't start GGSN\n"); - goto reject_due_failure; - } - return; - } - - LOGMMCTXP(LOGL_ERROR, mmctx, "Couldn't resolve GGSN\n"); - goto reject_due_failure; - } - - if (hostent->h_length != sizeof(struct in_addr)) { - LOGMMCTXP(LOGL_ERROR, lookup->mmctx, - "Wrong addr size(%zu)\n", sizeof(struct in_addr)); - goto reject_due_failure; - } - - /* Get the first addr from the list */ - addr = (struct in_addr *) hostent->h_addr_list[0]; - if (!addr) { - LOGMMCTXP(LOGL_ERROR, lookup->mmctx, "No host address.\n"); - goto reject_due_failure; - } - - ggsn = sgsn_ggsn_ctx_alloc(UINT32_MAX); - if (!ggsn) { - LOGMMCTXP(LOGL_ERROR, lookup->mmctx, "Failed to create ggsn.\n"); - goto reject_due_failure; - } - ggsn->remote_addr = *addr; - LOGMMCTXP(LOGL_NOTICE, lookup->mmctx, - "Selected %s as GGSN.\n", - inet_ntop(AF_INET, addr, buf, sizeof(buf))); - - /* forget about the ggsn look-up */ - lookup->mmctx->ggsn_lookup = NULL; - - activate_ggsn(lookup->mmctx, ggsn, lookup->ti, lookup->nsapi, - lookup->sapi, &lookup->tp, 1); - - /* Now free it */ - talloc_free(lookup->orig_msg); - talloc_free(lookup); - return; - -reject_due_failure: - gsm48_tx_gsm_act_pdp_rej(lookup->mmctx, lookup->ti, - GMM_CAUSE_NET_FAIL, 0, NULL); - lookup->mmctx->ggsn_lookup = NULL; - talloc_free(lookup->orig_msg); - talloc_free(lookup); -} - -static int do_act_pdp_req(struct sgsn_mm_ctx *mmctx, struct msgb *msg, bool *delete) -{ - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); - struct gsm48_act_pdp_ctx_req *act_req = (struct gsm48_act_pdp_ctx_req *) gh->data; - uint8_t req_qos_len, req_pdpa_len; - uint8_t *req_qos, *req_pdpa; - struct tlv_parsed tp; - uint8_t transaction_id = gsm48_hdr_trans_id(gh); - struct sgsn_ggsn_ctx *ggsn; - struct sgsn_pdp_ctx *pdp; - enum gsm48_gsm_cause gsm_cause; - char apn_str[GSM_APN_LENGTH] = { 0, }; - char *hostname; - int rc; - struct gprs_llc_lle *lle; - char buf[INET_ADDRSTRLEN]; - - LOGMMCTXP(LOGL_INFO, mmctx, "-> ACTIVATE PDP CONTEXT REQ: SAPI=%u NSAPI=%u ", - act_req->req_llc_sapi, act_req->req_nsapi); - - /* FIXME: length checks! */ - req_qos_len = act_req->data[0]; - req_qos = act_req->data + 1; /* 10.5.6.5 */ - req_pdpa_len = act_req->data[1 + req_qos_len]; - req_pdpa = act_req->data + 1 + req_qos_len + 1; /* 10.5.6.4 */ - - switch (req_pdpa[0] & 0xf) { - case 0x0: - DEBUGPC(DMM, "ETSI "); - break; - case 0x1: - DEBUGPC(DMM, "IETF "); - break; - case 0xf: - DEBUGPC(DMM, "Empty "); - break; - } - - switch (req_pdpa[1]) { - case 0x21: - DEBUGPC(DMM, "IPv4 "); - if (req_pdpa_len >= 6) { - struct in_addr ia; - ia.s_addr = ntohl(*((uint32_t *) (req_pdpa+2))); - DEBUGPC(DMM, "%s ", inet_ntop(AF_INET, &ia, buf, sizeof(buf))); - } - break; - case 0x57: - DEBUGPC(DMM, "IPv6 "); - if (req_pdpa_len >= 18) { - /* FIXME: print IPv6 address */ - } - break; - default: - DEBUGPC(DMM, "0x%02x ", req_pdpa[1]); - break; - } - - LOGPC(DMM, LOGL_INFO, "\n"); - - /* Check if NSAPI is out of range (TS 04.65 / 7.2) */ - if (act_req->req_nsapi < 5 || act_req->req_nsapi > 15) { - /* Send reject with GSM_CAUSE_INV_MAND_INFO */ - return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id, - GSM_CAUSE_INV_MAND_INFO, - 0, NULL); - } - - /* Optional: Access Point Name, Protocol Config Options */ - if (req_pdpa + req_pdpa_len < msg->data + msg->len) - tlv_parse(&tp, &gsm48_sm_att_tlvdef, req_pdpa + req_pdpa_len, - (msg->data + msg->len) - (req_pdpa + req_pdpa_len), 0, 0); - else - memset(&tp, 0, sizeof(tp)); - - - /* put the non-TLV elements in the TLV parser structure to - * pass them on to the SGSN / GTP code */ - tp.lv[OSMO_IE_GSM_REQ_QOS].len = req_qos_len; - tp.lv[OSMO_IE_GSM_REQ_QOS].val = req_qos; - tp.lv[OSMO_IE_GSM_REQ_PDP_ADDR].len = req_pdpa_len; - tp.lv[OSMO_IE_GSM_REQ_PDP_ADDR].val = req_pdpa; - - /* Check if NSAPI is already in use */ - pdp = sgsn_pdp_ctx_by_nsapi(mmctx, act_req->req_nsapi); - if (pdp) { - /* We already have a PDP context for this TLLI + NSAPI tuple */ - if (pdp->sapi == act_req->req_llc_sapi && - pdp->ti == transaction_id) { - /* This apparently is a re-transmission of a PDP CTX - * ACT REQ (our ACT ACK must have got dropped) */ - rc = gsm48_tx_gsm_act_pdp_acc(pdp); - if (rc < 0) - return rc; - - if (pdp->mm->ran_type == MM_CTX_T_GERAN_Gb) { - /* Also re-transmit the SNDCP XID message */ - lle = &pdp->mm->gb.llme->lle[pdp->sapi]; - rc = sndcp_sn_xid_req(lle,pdp->nsapi); - if (rc < 0) - return rc; - } - - return 0; - } - - /* Send reject with GSM_CAUSE_NSAPI_IN_USE */ - return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id, - GSM_CAUSE_NSAPI_IN_USE, - 0, NULL); - } - - if (mmctx->ggsn_lookup) { - if (mmctx->ggsn_lookup->sapi == act_req->req_llc_sapi && - mmctx->ggsn_lookup->ti == transaction_id) { - LOGMMCTXP(LOGL_NOTICE, mmctx, - "Re-transmission while doing look-up. Ignoring.\n"); - return 0; - } - } - - /* Only increment counter for a real activation, after we checked - * for re-transmissions */ - rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PDP_CTX_ACT]); - - /* Determine GGSN based on APN and subscription options */ - ggsn = sgsn_mm_ctx_find_ggsn_ctx(mmctx, &tp, &gsm_cause, apn_str); - if (ggsn) - return activate_ggsn(mmctx, ggsn, transaction_id, - act_req->req_nsapi, act_req->req_llc_sapi, - &tp, 0); - - if (strlen(apn_str) == 0) - goto no_context; - if (!sgsn->cfg.dynamic_lookup) - goto no_context; - - /* schedule a dynamic look-up */ - mmctx->ggsn_lookup = talloc_zero(tall_sgsn_ctx, struct sgsn_ggsn_lookup); - if (!mmctx->ggsn_lookup) - goto no_context; - - mmctx->ggsn_lookup->state = SGSN_GGSN_2DIGIT; - mmctx->ggsn_lookup->mmctx = mmctx; - strcpy(mmctx->ggsn_lookup->apn_str, apn_str); - - mmctx->ggsn_lookup->orig_msg = msg; - mmctx->ggsn_lookup->tp = tp; - - mmctx->ggsn_lookup->ti = transaction_id; - mmctx->ggsn_lookup->nsapi = act_req->req_nsapi; - mmctx->ggsn_lookup->sapi = act_req->req_llc_sapi; - - hostname = osmo_apn_qualify_from_imsi(mmctx->imsi, - mmctx->ggsn_lookup->apn_str, 0); - - LOGMMCTXP(LOGL_DEBUG, mmctx, "Going to query %s\n", hostname); - rc = sgsn_ares_query(sgsn, hostname, - ggsn_lookup_cb, mmctx->ggsn_lookup); - if (rc != 0) { - LOGMMCTXP(LOGL_ERROR, mmctx, "Failed to start ares query.\n"); - goto no_context; - } - *delete = 0; - - return 0; - -no_context: - LOGMMCTXP(LOGL_ERROR, mmctx, "No GGSN context found!\n"); - return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id, - gsm_cause, 0, NULL); -} - -/* 3GPP TS 24.008 § 9.5.1: Activate PDP Context Request */ -static int gsm48_rx_gsm_act_pdp_req(struct sgsn_mm_ctx *mmctx, - struct msgb *_msg) -{ - bool delete = 1; - struct msgb *msg; - int rc; - - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_PDP_ACTIVATE_REQUEST]); - - /* - * This is painful. We might not have a static GGSN - * configuration and then would need to copy the msg - * and re-do most of this routine (or call it again - * and make sure it only goes through the dynamic - * resolving. The question is what to optimize for - * and the dynamic resolution will be the right thing - * in the long run. - */ - msg = bssgp_msgb_copy(_msg, __func__); - if (!msg) { - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(_msg); - uint8_t transaction_id = gsm48_hdr_trans_id(gh); - - LOGMMCTXP(LOGL_ERROR, mmctx, "-> ACTIVATE PDP CONTEXT REQ failed copy.\n"); - /* Send reject with GSM_CAUSE_INV_MAND_INFO */ - return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id, - GSM_CAUSE_NET_FAIL, - 0, NULL); - } - - rc = do_act_pdp_req(mmctx, msg, &delete); - if (delete) - msgb_free(msg); - return rc; -} - -/* 3GPP TS 24.008 § 9.5.8: Deactivate PDP Context Request */ -static int gsm48_rx_gsm_deact_pdp_req(struct sgsn_mm_ctx *mm, struct msgb *msg) -{ - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); - uint8_t transaction_id = gsm48_hdr_trans_id(gh); - struct sgsn_pdp_ctx *pdp; - - LOGMMCTXP(LOGL_INFO, mm, "-> DEACTIVATE PDP CONTEXT REQ (cause: %s)\n", - get_value_string(gsm48_gsm_cause_names, gh->data[0])); - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_PDP_UL_DEACTIVATE_REQUEST]); - - pdp = sgsn_pdp_ctx_by_tid(mm, transaction_id); - if (!pdp) { - LOGMMCTXP(LOGL_NOTICE, mm, "Deactivate PDP Context Request for " - "non-existing PDP Context (IMSI=%s, TI=%u)\n", - mm->imsi, transaction_id); - return _gsm48_tx_gsm_deact_pdp_acc(mm, transaction_id); - } - - return sgsn_delete_pdp_ctx(pdp); -} - -/* 3GPP TS 24.008 § 9.5.9: Deactivate PDP Context Accept */ -static int gsm48_rx_gsm_deact_pdp_ack(struct sgsn_mm_ctx *mm, struct msgb *msg) -{ - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); - uint8_t transaction_id = gsm48_hdr_trans_id(gh); - struct sgsn_pdp_ctx *pdp; - - LOGMMCTXP(LOGL_INFO, mm, "-> DEACTIVATE PDP CONTEXT ACK\n"); - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_PDP_UL_DEACTIVATE_ACCEPT]); - - pdp = sgsn_pdp_ctx_by_tid(mm, transaction_id); - if (!pdp) { - LOGMMCTXP(LOGL_NOTICE, mm, "Deactivate PDP Context Accept for " - "non-existing PDP Context (IMSI=%s, TI=%u)\n", - mm->imsi, transaction_id); - return 0; - } - /* stop timer 3395 */ - pdpctx_timer_stop(pdp, 3395); - if (pdp->ggsn) - return sgsn_delete_pdp_ctx(pdp); - /* GTP side already detached, freeing */ - sgsn_pdp_ctx_free(pdp); - return 0; -} - -static int gsm48_rx_gsm_status(struct sgsn_mm_ctx *ctx, struct msgb *msg) -{ - struct gsm48_hdr *gh = msgb_l3(msg); - - LOGMMCTXP(LOGL_INFO, ctx, "-> GPRS SM STATUS (cause: %s)\n", - get_value_string(gsm48_gsm_cause_names, gh->data[0])); - - return 0; -} - -static void pdpctx_timer_cb(void *_pdp) -{ - struct sgsn_pdp_ctx *pdp = _pdp; - - pdp->num_T_exp++; - - switch (pdp->T) { - case 3395: /* waiting for PDP CTX DEACT ACK */ - if (pdp->num_T_exp > T339X_MAX_RETRANS) { - LOGPDPCTXP(LOGL_NOTICE, pdp, "T3395 expired > %d times\n", T339X_MAX_RETRANS); - pdp->state = PDP_STATE_INACTIVE; - if (pdp->ggsn) - sgsn_delete_pdp_ctx(pdp); - else - sgsn_pdp_ctx_free(pdp); - break; - } - _gsm48_tx_gsm_deact_pdp_req(pdp->mm, pdp->ti, GSM_CAUSE_NET_FAIL, true); - pdpctx_timer_rearm(pdp, 3395); - break; - default: - LOGPDPCTXP(LOGL_ERROR, pdp, "timer expired in unknown mode %u\n", - pdp->T); - } -} - - -/* GPRS Session Management */ -int gsm0408_rcv_gsm(struct sgsn_mm_ctx *mmctx, struct msgb *msg, - struct gprs_llc_llme *llme) -{ - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); - int rc; - - /* MMCTX can be NULL when called */ - - if (!mmctx) { - LOGGBIUP(llme, msg, LOGL_NOTICE, "Cannot handle SM for unknown MM CTX\n"); - /* 6.1.3.6 */ - if (gh->msg_type == GSM48_MT_GSM_STATUS) - return 0; - - return gsm0408_gprs_force_reattach_oldmsg(msg, llme); - } - - switch (gh->msg_type) { - case GSM48_MT_GSM_ACT_PDP_REQ: - rc = gsm48_rx_gsm_act_pdp_req(mmctx, msg); - break; - case GSM48_MT_GSM_DEACT_PDP_REQ: - rc = gsm48_rx_gsm_deact_pdp_req(mmctx, msg); - break; - case GSM48_MT_GSM_DEACT_PDP_ACK: - rc = gsm48_rx_gsm_deact_pdp_ack(mmctx, msg); - break; - case GSM48_MT_GSM_STATUS: - rc = gsm48_rx_gsm_status(mmctx, msg); - break; - case GSM48_MT_GSM_REQ_PDP_ACT_REJ: - case GSM48_MT_GSM_ACT_AA_PDP_REQ: - case GSM48_MT_GSM_DEACT_AA_PDP_REQ: - LOGMMCTXP(LOGL_NOTICE, mmctx, "Unimplemented GSM 04.08 GSM msg type 0x%02x: %s\n", - gh->msg_type, osmo_hexdump((uint8_t *)gh, msgb_l3len(msg))); - rc = gsm48_tx_sm_status(mmctx, GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL); - break; - default: - LOGMMCTXP(LOGL_NOTICE, mmctx, "Unknown GSM 04.08 GSM msg type 0x%02x: %s\n", - gh->msg_type, osmo_hexdump((uint8_t *)gh, msgb_l3len(msg))); - rc = gsm48_tx_sm_status(mmctx, GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL); - break; - - } - - return rc; -} - -int gsm0408_gprs_force_reattach_oldmsg(struct msgb *msg, - struct gprs_llc_llme *llme) -{ - int rc; - if (llme) - gprs_llgmm_reset_oldmsg(msg, GPRS_SAPI_GMM, llme); - - rc = gsm48_tx_gmm_detach_req_oldmsg( - msg, GPRS_DET_T_MT_REATT_REQ, GMM_CAUSE_IMPL_DETACHED); - - return rc; -} - -int gsm0408_gprs_force_reattach(struct sgsn_mm_ctx *mmctx) -{ - int rc; - if (mmctx->ran_type == MM_CTX_T_GERAN_Gb) - gprs_llgmm_reset(mmctx->gb.llme); - - rc = gsm48_tx_gmm_detach_req( - mmctx, GPRS_DET_T_MT_REATT_REQ, GMM_CAUSE_IMPL_DETACHED); - - mm_ctx_cleanup_free(mmctx, "forced reattach"); - - return rc; -} - -int gprs_gmm_rx_suspend(struct gprs_ra_id *raid, uint32_t tlli) -{ - struct sgsn_mm_ctx *mmctx; - - mmctx = sgsn_mm_ctx_by_tlli(tlli, raid); - if (!mmctx) { - LOGP(DMM, LOGL_NOTICE, "SUSPEND request for unknown " - "TLLI=%08x\n", tlli); - return -EINVAL; - } - - if (mmctx->gmm_state != GMM_REGISTERED_NORMAL && - mmctx->gmm_state != GMM_REGISTERED_SUSPENDED) { - LOGMMCTXP(LOGL_NOTICE, mmctx, "SUSPEND request while state " - "!= REGISTERED (TLLI=%08x)\n", tlli); - return -EINVAL; - } - - /* Transition from REGISTERED_NORMAL to REGISTERED_SUSPENDED */ - mmctx->gmm_state = GMM_REGISTERED_SUSPENDED; - return 0; -} - -int gprs_gmm_rx_resume(struct gprs_ra_id *raid, uint32_t tlli, - uint8_t suspend_ref) -{ - struct sgsn_mm_ctx *mmctx; - - /* FIXME: make use of suspend reference? */ - - mmctx = sgsn_mm_ctx_by_tlli(tlli, raid); - if (!mmctx) { - LOGP(DMM, LOGL_NOTICE, "RESUME request for unknown " - "TLLI=%08x\n", tlli); - return -EINVAL; - } - - if (mmctx->gmm_state != GMM_REGISTERED_NORMAL && - mmctx->gmm_state != GMM_REGISTERED_SUSPENDED) { - LOGMMCTXP(LOGL_NOTICE, mmctx, "RESUME request while state " - "!= SUSPENDED (TLLI=%08x)\n", tlli); - /* FIXME: should we not simply ignore it? */ - return -EINVAL; - } - - /* Transition from SUSPENDED to NORMAL */ - mmctx->gmm_state = GMM_REGISTERED_NORMAL; - return 0; -} diff --git a/src/gprs/gprs_gmm_attach.c b/src/gprs/gprs_gmm_attach.c deleted file mode 100644 index 130f8d1c0..000000000 --- a/src/gprs/gprs_gmm_attach.c +++ /dev/null @@ -1,461 +0,0 @@ -#include - -#include - -#include -#include -#include -#include - -#define X(s) (1 << (s)) - -static int require_identity_imei = 1; -static int require_auth = 1; - -static const struct osmo_tdef_state_timeout gmm_attach_fsm_timeouts[32] = { - [ST_IDENTIY] = { .T=3370 }, - [ST_AUTH] = { .T=3360 }, - [ST_ACCEPT] = { .T=3350 }, - [ST_ASK_VLR] = { .T=3350 }, - [ST_IU_SECURITY_CMD] = { .T=3350 }, -}; - -#define gmm_attach_fsm_state_chg(fi, NEXT_STATE) \ - osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, gmm_attach_fsm_timeouts, sgsn->cfg.T_defs, -1) - - -static void st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct sgsn_mm_ctx *ctx = fi->priv; - struct msgb *attach_req = data; - - /* we can run st_init multiple times */ - if (ctx->gmm_att_req.attach_req) - msgb_free(ctx->gmm_att_req.attach_req); - - ctx->gmm_att_req.attach_req = msgb_copy(attach_req, "Attach Request"); - ctx->auth_state = SGSN_AUTH_UNKNOWN; - ctx->gmm_att_req.auth_reattempt = 0; - - /* - * TODO: remove pending_req as soon the sgsn_auth code doesn't depend - * on it. - * pending_req must be set, even this fsm doesn't use it, because - * the sgsn_auth code is using this too - */ - ctx->pending_req = GSM48_MT_GMM_ATTACH_REQ; - - if (require_identity_imei) { - ctx->gmm_att_req.id_type = GSM_MI_TYPE_IMEI; - gmm_attach_fsm_state_chg(fi, ST_IDENTIY); - } else if (!strlen(ctx->imsi)) { - ctx->gmm_att_req.id_type = GSM_MI_TYPE_IMSI; - gmm_attach_fsm_state_chg(fi, ST_IDENTIY); - } else if (require_auth) - gmm_attach_fsm_state_chg(fi, ST_AUTH); - else - gmm_attach_fsm_state_chg(fi, ST_ACCEPT); -} - -static void st_identity_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) -{ - struct sgsn_mm_ctx *ctx = fi->priv; - int ret = 0; - - ctx->num_T_exp = 0; - - switch (ctx->gmm_att_req.id_type) { - case GSM_MI_TYPE_IMEI: - case GSM_MI_TYPE_IMSI: - break; - default: - /* TODO logging */ - osmo_fsm_inst_dispatch(fi, E_REJECT, NULL); - return; - } - - ctx->t3370_id_type = ctx->gmm_att_req.id_type; - ret = gsm48_tx_gmm_id_req(ctx, ctx->gmm_att_req.id_type); - if (ret < 0) { - LOGPFSM(fi, "Can not send tx_gmm_id %d.\n", ret); - osmo_fsm_inst_dispatch(fi, E_REJECT, NULL); - } -} - -static void st_identity(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct sgsn_mm_ctx *ctx = fi->priv; - - OSMO_ASSERT(event == E_IDEN_RESP_RECV); - - /* check if we received a identity response */ - long type = (long) data; - switch (type) { - case GSM_MI_TYPE_IMEI: - case GSM_MI_TYPE_IMSI: - break; - default: - LOGMMCTXP(LOGL_ERROR, ctx, "Unknown mi type: 0x%lx, rejecting MS.\n", type); - osmo_fsm_inst_dispatch(fi, E_REJECT, (void *) GMM_CAUSE_NET_FAIL); - return; - } - - if (type != ctx->gmm_att_req.id_type) { - /* ignore wrong package */ - /* TODO logging */ - return; - } - - if (type == GSM_MI_TYPE_IMEI && !strlen(ctx->imsi)) { - ctx->gmm_att_req.id_type = GSM_MI_TYPE_IMSI; - gmm_attach_fsm_state_chg(fi, ST_IDENTIY); - } else if (require_auth) - gmm_attach_fsm_state_chg(fi, ST_AUTH); - else - gmm_attach_fsm_state_chg(fi, ST_ACCEPT); -} - -static void st_auth_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) -{ - struct sgsn_mm_ctx *ctx = fi->priv; - enum sgsn_auth_state auth_state; - - ctx->num_T_exp = 0; - - /* TODO: remove this layer violation. Don't parse any auth_policy here - * The correct way would be to ask the SGSN is this mmctx has to be auth - * regardless of the state. - * Otherwise someone else could steal the TLLI and just use it without further - * auth. - */ - if (sgsn->cfg.auth_policy != SGSN_AUTH_POLICY_REMOTE) { - /* we can "trust" sgsn_auth_state as long it's not remote */ - auth_state = sgsn_auth_state(ctx); - } else { - auth_state = ctx->auth_state; - } - - switch(auth_state) { - case SGSN_AUTH_UMTS_RESYNC: /* ask the vlr for a new vector to match the simcards seq */ - case SGSN_AUTH_UNKNOWN: /* the SGSN doesn know this MS */ - gmm_attach_fsm_state_chg(fi, ST_ASK_VLR); - break; - case SGSN_AUTH_REJECTED: - /* TODO: correct GMM cause */ - osmo_fsm_inst_dispatch(fi, E_REJECT, (void *) GMM_CAUSE_GPRS_NOTALLOWED); - break; - case SGSN_AUTH_ACCEPTED: - gmm_attach_fsm_state_chg(fi, ST_ACCEPT); - break; - case SGSN_AUTH_AUTHENTICATE: - if (ctx->auth_triplet.key_seq == GSM_KEY_SEQ_INVAL) { - /* invalid key material */ - gmm_attach_fsm_state_chg(fi, ST_ASK_VLR); - } - - struct gsm_auth_tuple *at = &ctx->auth_triplet; - if (gsm48_tx_gmm_auth_ciph_req(ctx, &at->vec, at->key_seq, - false) < 0) { - /* network failure */ - osmo_fsm_inst_dispatch(fi, E_REJECT, (void *) GMM_CAUSE_NET_FAIL); - } - ctx->gmm_att_req.auth_reattempt++; - break; - } -} - -static void st_auth(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct sgsn_mm_ctx *ctx = fi->priv; - - switch (event) { - case E_AUTH_RESP_RECV_SUCCESS: - sgsn_auth_request(ctx); -#ifdef BUILD_IU - if (ctx->ran_type == MM_CTX_T_UTRAN_Iu && !ctx->iu.ue_ctx->integrity_active) - gmm_attach_fsm_state_chg(fi, ST_IU_SECURITY_CMD); - else -#endif /* BUILD_IU */ - gmm_attach_fsm_state_chg(fi, ST_ACCEPT); - break; - case E_AUTH_RESP_RECV_RESYNC: - if (ctx->gmm_att_req.auth_reattempt <= 1) - gmm_attach_fsm_state_chg(fi, ST_ASK_VLR); - else - osmo_fsm_inst_dispatch(fi, E_REJECT, (void *) GMM_CAUSE_SYNC_FAIL); - break; - } -} - -static void st_accept_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) -{ - struct sgsn_mm_ctx *ctx = fi->priv; - - ctx->num_T_exp = 0; - - /* TODO: remove pending_req as soon the sgsn_auth code doesn't depend on it */ - ctx->pending_req = 0; - gsm48_tx_gmm_att_ack(ctx); -} - -static void st_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct sgsn_mm_ctx *ctx = fi->priv; - - switch(event) { - case E_ATTACH_COMPLETE_RECV: - /* TODO: #ifdef ! PTMSI_ALLOC is not supported */ - extract_subscr_msisdn(ctx); - extract_subscr_hlr(ctx); - osmo_fsm_inst_state_chg(fi, ST_INIT, 0, 0); - break; - case E_VLR_ANSWERED: - extract_subscr_msisdn(ctx); - extract_subscr_hlr(ctx); - LOGMMCTXP(LOGL_NOTICE, ctx, - "Unusual event: if MS got no data connection, check that it has APN configured.\n"); - break; - } -} - -static void st_reject(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct sgsn_mm_ctx *ctx = fi->priv; - long reject_cause = (long) data; - - if (reject_cause != GMM_DISCARD_MS_WITHOUT_REJECT) - gsm48_tx_gmm_att_rej(ctx, (uint8_t) reject_cause); - - sgsn_mm_ctx_cleanup_free(ctx); -} - -static void st_ask_vlr_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) -{ - struct sgsn_mm_ctx *ctx = fi->priv; - - /* FIXME: remove this layer violation. - * The VLR should send the message to the HLR and not the rx function - * gsm48_rx_gmm_auth_ciph_fail. Because gmm_auth_ciph_fail already send a - * message to the HLR, we don't send here a request. */ - if (ctx->auth_state == SGSN_AUTH_UMTS_RESYNC) - return; - - /* ask the auth layer for more data */ - sgsn_auth_request(ctx); -} - -static void st_ask_vlr(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - switch(event) { - case E_VLR_ANSWERED: - gmm_attach_fsm_state_chg(fi, ST_AUTH); - break; - } -} - -static void st_iu_security_cmd_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) -{ -#ifdef BUILD_IU - struct sgsn_mm_ctx *ctx = fi->priv; - - /* TODO: shouldn't this set always? not only when the integrity_active? */ - if (ctx->iu.ue_ctx->integrity_active) { - gmm_attach_fsm_state_chg(fi, ST_ACCEPT); - return; - } - - ranap_iu_tx_sec_mode_cmd(ctx->iu.ue_ctx, &ctx->auth_triplet.vec, 0, ctx->iu.new_key); - ctx->iu.new_key = 0; -#endif -} - -static void st_iu_security_cmd(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - switch(event) { - case E_IU_SECURITY_CMD_COMPLETE: - gmm_attach_fsm_state_chg(fi, ST_ACCEPT); - break; - } -} - -static struct osmo_fsm_state gmm_attach_req_fsm_states[] = { - /* default state for non-DTX and DTX when SPEECH is in progress */ - [ST_INIT] = { - .in_event_mask = X(E_ATTACH_REQ_RECV), - .out_state_mask = X(ST_INIT) | X(ST_IDENTIY) | X(ST_AUTH) | X(ST_ACCEPT), - .name = "Init", - .action = st_init, - }, - [ST_ASK_VLR] = { - .in_event_mask = X(E_VLR_ANSWERED), - .out_state_mask = X(ST_INIT) | X(ST_AUTH) | X(ST_ACCEPT) | X(ST_REJECT), - .name = "AskVLR", - .onenter = st_ask_vlr_on_enter, - .action = st_ask_vlr, - }, - [ST_IDENTIY] = { - .in_event_mask = X(E_IDEN_RESP_RECV), - .out_state_mask = X(ST_INIT) | X(ST_AUTH) | X(ST_ACCEPT) | X(ST_IDENTIY) | X(ST_REJECT), - .onenter = st_identity_on_enter, - .name = "CheckIdentity", - .action = st_identity, - }, - [ST_AUTH] = { - .in_event_mask = X(E_AUTH_RESP_RECV_SUCCESS) | X(E_AUTH_RESP_RECV_RESYNC), - .out_state_mask = X(ST_INIT) | X(ST_AUTH) | X(ST_IU_SECURITY_CMD) | X(ST_ACCEPT) | X(ST_ASK_VLR) | X(ST_REJECT), - .name = "Authenticate", - .onenter = st_auth_on_enter, - .action = st_auth, - }, - [ST_IU_SECURITY_CMD] = { - .in_event_mask = X(E_IU_SECURITY_CMD_COMPLETE), - .out_state_mask = X(ST_INIT) | X(ST_AUTH) | X(ST_ACCEPT) | X(ST_REJECT), - .name = "IuSecurityCommand", - .onenter = st_iu_security_cmd_on_enter, - .action = st_iu_security_cmd, - }, - [ST_ACCEPT] = { - .in_event_mask = X(E_ATTACH_COMPLETE_RECV) | X(E_VLR_ANSWERED), - .out_state_mask = X(ST_INIT) | X(ST_REJECT), - .name = "WaitAttachComplete", - .onenter = st_accept_on_enter, - .action = st_accept, - }, - [ST_REJECT] = { - .in_event_mask = X(E_REJECT), - .out_state_mask = X(ST_INIT), - .name = "Reject", - .action = st_reject, - }, -}; - -const struct value_string gmm_attach_req_fsm_event_names[] = { - OSMO_VALUE_STRING(E_ATTACH_REQ_RECV), - OSMO_VALUE_STRING(E_IDEN_RESP_RECV), - OSMO_VALUE_STRING(E_AUTH_RESP_RECV_SUCCESS), - OSMO_VALUE_STRING(E_AUTH_RESP_RECV_RESYNC), - OSMO_VALUE_STRING(E_ATTACH_ACCEPTED), - OSMO_VALUE_STRING(E_ATTACH_ACCEPT_SENT), - OSMO_VALUE_STRING(E_ATTACH_COMPLETE_RECV), - OSMO_VALUE_STRING(E_IU_SECURITY_CMD_COMPLETE), - OSMO_VALUE_STRING(E_REJECT), - OSMO_VALUE_STRING(E_VLR_ANSWERED), - { 0, NULL } -}; - -void gmm_attach_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) { - struct sgsn_mm_ctx *ctx = fi->priv; - struct msgb *new_attach_req = data; - - switch (event) { - case E_ATTACH_REQ_RECV: - switch (fi->state) { - case ST_INIT: - case ST_REJECT: - st_init(fi, event, data); - break; - - case ST_ACCEPT: - /* TODO: drop all state (e.g. PDP Ctx) and do this procedure */ - osmo_fsm_inst_state_chg(fi, ST_INIT, 0, 0); - st_init(fi, event, data); - break; - - case ST_ASK_VLR: - case ST_AUTH: - case ST_IDENTIY: - case ST_RETRIEVE_AUTH: - /* 04.08 4.7.3.1.6 d) Abnormal Case - * Only do action if Req IEs differs. */ - if (ctx->gmm_att_req.attach_req && - gprs_gmm_attach_req_ies(new_attach_req, ctx->gmm_att_req.attach_req)) { - osmo_fsm_inst_state_chg(fi, ST_INIT, 0, 0); - st_init(fi, event, data); - } - break; - } - break; - case E_REJECT: - if (fi->state != ST_REJECT) - osmo_fsm_inst_state_chg(fi, ST_REJECT, 0, 0); - st_reject(fi, event, data); - break; - } -} - -int gmm_attach_timer_cb(struct osmo_fsm_inst *fi) -{ - struct sgsn_mm_ctx *ctx = fi->priv; - struct gsm_auth_tuple *at = &ctx->auth_triplet; - unsigned long t_secs; - - ctx->num_T_exp++; - - switch(fi->state) { - case ST_ASK_VLR: - /* TODO: replace T3350 by a better timer or it's own - * re-use T3350 - not defined by standard */ - LOGMMCTXP(LOGL_ERROR, ctx, "HLR did not answer in time. Rejecting.\n"); - osmo_fsm_inst_dispatch(fi, E_REJECT, - (void *) GMM_CAUSE_NET_FAIL); - break; - case ST_IDENTIY: - /* T3370 */ - if (ctx->num_T_exp >= 5) { - osmo_fsm_inst_dispatch(fi, E_REJECT, - (void *) GMM_CAUSE_MS_ID_NOT_DERIVED); - break; - } - gsm48_tx_gmm_id_req(ctx, ctx->gmm_att_req.id_type); - t_secs = osmo_tdef_get(sgsn->cfg.T_defs, 3370, OSMO_TDEF_S, -1); - osmo_timer_schedule(&fi->timer, t_secs, 0); - - break; - case ST_AUTH: - /* T3360 */ - if (ctx->num_T_exp >= 5) { - osmo_fsm_inst_dispatch(fi, E_REJECT, (void *) GMM_DISCARD_MS_WITHOUT_REJECT); - break; - } - gsm48_tx_gmm_auth_ciph_req(ctx, &at->vec, at->key_seq, false); - t_secs = osmo_tdef_get(sgsn->cfg.T_defs, 3360, OSMO_TDEF_S, -1); - osmo_timer_schedule(&fi->timer, t_secs, 0); - break; - case ST_ACCEPT: - /* T3350 */ - if (ctx->num_T_exp >= 5) { - osmo_fsm_inst_dispatch(fi, E_REJECT, (void *) GMM_DISCARD_MS_WITHOUT_REJECT); - break; - } - gsm48_tx_gmm_att_ack(ctx); - t_secs = osmo_tdef_get(sgsn->cfg.T_defs, 3350, OSMO_TDEF_S, -1); - osmo_timer_schedule(&fi->timer, t_secs, 0); - break; - } - - return 0; -} - -struct osmo_fsm gmm_attach_req_fsm = { - .name = "GMM_ATTACH_REQ_FSM", - .states = gmm_attach_req_fsm_states, - .num_states = ARRAY_SIZE(gmm_attach_req_fsm_states), - .event_names = gmm_attach_req_fsm_event_names, - .allstate_event_mask = X(E_REJECT) | X(E_ATTACH_REQ_RECV), - .allstate_action = gmm_attach_allstate_action, - .log_subsys = DMM, - .timer_cb = gmm_attach_timer_cb, -}; - -static __attribute__((constructor)) void gprs_gmm_fsm_init(void) -{ - osmo_fsm_register(&gmm_attach_req_fsm); -} - -void gmm_att_req_free(struct sgsn_mm_ctx *mm) { - if (mm->gmm_att_req.fsm) - osmo_fsm_inst_free(mm->gmm_att_req.fsm); - - if (mm->gmm_att_req.attach_req) - msgb_free(mm->gmm_att_req.attach_req); -} diff --git a/src/gprs/gprs_llc.c b/src/gprs/gprs_llc.c deleted file mode 100644 index 2a27da844..000000000 --- a/src/gprs/gprs_llc.c +++ /dev/null @@ -1,1175 +0,0 @@ -/* GPRS LLC protocol implementation as per 3GPP TS 04.64 */ - -/* (C) 2009-2010 by Harald Welte - * - * 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 . - * - */ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -const struct value_string gprs_llc_llme_state_names[] = { - { GPRS_LLMS_UNASSIGNED, "UNASSIGNED" }, - { GPRS_LLMS_ASSIGNED, "ASSIGNED" }, - { 0, NULL } -}; - -static struct gprs_llc_llme *llme_alloc(uint32_t tlli); -static int gprs_llc_tx_xid(struct gprs_llc_lle *lle, struct msgb *msg, - int command); -static int gprs_llc_tx_dm(struct gprs_llc_lle *lle); -static int gprs_llc_tx_u(struct msgb *msg, uint8_t sapi, - int command, enum gprs_llc_u_cmd u_cmd, int pf_bit); - -/* BEGIN XID RELATED */ - -/* Generate XID message */ -static int gprs_llc_generate_xid(uint8_t *bytes, int bytes_len, - struct gprs_llc_xid_field *l3_xid_field, - struct gprs_llc_lle *lle) -{ - /* Note: Called by gprs_ll_xid_req() */ - - LLIST_HEAD(xid_fields); - - struct gprs_llc_xid_field xid_version; - struct gprs_llc_xid_field xid_n201u; - struct gprs_llc_xid_field xid_n201i; - - xid_version.type = GPRS_LLC_XID_T_VERSION; - xid_version.data = (uint8_t *) "\x00"; - xid_version.data_len = 1; - - xid_n201u.type = GPRS_LLC_XID_T_N201_U; - xid_n201u.data = (uint8_t *) "\x05\xf0"; - xid_n201u.data_len = 2; - - xid_n201i.type = GPRS_LLC_XID_T_N201_I; - xid_n201i.data = (uint8_t *) "\x05\xf0"; - xid_n201i.data_len = 2; - - /* Add locally managed XID Fields */ - llist_add(&xid_version.list, &xid_fields); - llist_add(&xid_n201u.list, &xid_fields); - llist_add(&xid_n201i.list, &xid_fields); - - /* Append layer 3 XID field (if present) */ - if (l3_xid_field) { - /* Enforce layer 3 XID type (just to be sure) */ - l3_xid_field->type = GPRS_LLC_XID_T_L3_PAR; - - /* Add Layer 3 XID field to the list */ - llist_add(&l3_xid_field->list, &xid_fields); - } - - /* Store generated XID for later reference */ - talloc_free(lle->xid); - lle->xid = gprs_llc_copy_xid(lle->llme, &xid_fields); - - return gprs_llc_compile_xid(bytes, bytes_len, &xid_fields); -} - -/* Generate XID message that will cause the GMM to reset */ -static int gprs_llc_generate_xid_for_gmm_reset(uint8_t *bytes, - int bytes_len, uint32_t iov_ui, - struct gprs_llc_lle *lle) -{ - /* Called by gprs_llgmm_reset() and - * gprs_llgmm_reset_oldmsg() */ - - LLIST_HEAD(xid_fields); - - struct gprs_llc_xid_field xid_reset; - struct gprs_llc_xid_field xid_iovui; - - /* First XID component must be RESET */ - xid_reset.type = GPRS_LLC_XID_T_RESET; - xid_reset.data = NULL; - xid_reset.data_len = 0; - - /* Add new IOV-UI */ - xid_iovui.type = GPRS_LLC_XID_T_IOV_UI; - xid_iovui.data = (uint8_t *) & iov_ui; - xid_iovui.data_len = 4; - - /* Add locally managed XID Fields */ - llist_add(&xid_iovui.list, &xid_fields); - llist_add(&xid_reset.list, &xid_fields); - - /* Store generated XID for later reference */ - talloc_free(lle->xid); - lle->xid = gprs_llc_copy_xid(lle->llme, &xid_fields); - - return gprs_llc_compile_xid(bytes, bytes_len, &xid_fields); -} - -/* Process an incoming XID confirmation */ -static int gprs_llc_process_xid_conf(uint8_t *bytes, int bytes_len, - struct gprs_llc_lle *lle) -{ - /* 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 llist_head *xid_fields; - struct gprs_llc_xid_field *xid_field; - struct gprs_llc_xid_field *xid_field_request; - struct gprs_llc_xid_field *xid_field_request_l3 = NULL; - - /* Pick layer3 XID from the XID request we have sent last */ - if (lle->xid) { - llist_for_each_entry(xid_field_request, lle->xid, list) { - if (xid_field_request->type == GPRS_LLC_XID_T_L3_PAR) - xid_field_request_l3 = xid_field_request; - } - } - - /* Parse and analyze XID-Response */ - xid_fields = gprs_llc_parse_xid(NULL, bytes, bytes_len); - - if (xid_fields) { - - gprs_llc_dump_xid_fields(xid_fields, LOGL_DEBUG); - llist_for_each_entry(xid_field, xid_fields, list) { - - /* Forward SNDCP-XID fields to Layer 3 (SNDCP) */ - if (xid_field->type == GPRS_LLC_XID_T_L3_PAR && - xid_field_request_l3) { - sndcp_sn_xid_conf(xid_field, - xid_field_request_l3, lle); - } - - /* Process LLC-XID fields: */ - else { - - /* 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! */ - LOGP(DLLC, LOGL_NOTICE, - "Ignoring XID-Field: XID: type %s, data_len=%d, data=%s\n", - get_value_string(gprs_llc_xid_type_names, - xid_field->type), - xid_field->data_len, - osmo_hexdump_nospc(xid_field->data, - xid_field->data_len)); - } - } - talloc_free(xid_fields); - } - - /* Flush pending XID fields */ - talloc_free(lle->xid); - lle->xid = NULL; - - return 0; -} - -/* Process an incoming XID indication and generate an appropiate response */ -static int gprs_llc_process_xid_ind(uint8_t *bytes_request, - int bytes_request_len, - uint8_t *bytes_response, - int bytes_response_maxlen, - struct gprs_llc_lle *lle) -{ - /* Note: This function computes the response that is sent back to the - * MS when a mobile originated XID is received. The function is - * called by rx_llc_xid() */ - - int rc = -EINVAL; - - struct llist_head *xid_fields; - struct llist_head *xid_fields_response; - - struct gprs_llc_xid_field *xid_field; - struct gprs_llc_xid_field *xid_field_response; - - /* Parse and analyze XID-Request */ - xid_fields = - gprs_llc_parse_xid(lle->llme, bytes_request, bytes_request_len); - if (xid_fields) { - xid_fields_response = talloc_zero(lle->llme, struct llist_head); - INIT_LLIST_HEAD(xid_fields_response); - gprs_llc_dump_xid_fields(xid_fields, LOGL_DEBUG); - - /* Process LLC-XID fields: */ - llist_for_each_entry(xid_field, xid_fields, list) { - - if (xid_field->type != GPRS_LLC_XID_T_L3_PAR) { - /* 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! */ - LOGP(DLLC, LOGL_NOTICE, - "Echoing XID-Field: XID: type %s, data_len=%d, data=%s\n", - get_value_string(gprs_llc_xid_type_names, - xid_field->type), - xid_field->data_len, - osmo_hexdump_nospc(xid_field->data, - xid_field->data_len)); - xid_field_response = - gprs_llc_dup_xid_field - (lle->llme, xid_field); - llist_add(&xid_field_response->list, - xid_fields_response); - } - } - - /* Forward SNDCP-XID fields to Layer 3 (SNDCP) */ - llist_for_each_entry(xid_field, xid_fields, list) { - if (xid_field->type == GPRS_LLC_XID_T_L3_PAR) { - - xid_field_response = - talloc_zero(lle->llme, - struct gprs_llc_xid_field); - rc = sndcp_sn_xid_ind(xid_field, - xid_field_response, lle); - if (rc == 0) - llist_add(&xid_field_response->list, - xid_fields_response); - else - talloc_free(xid_field_response); - } - } - - rc = gprs_llc_compile_xid(bytes_response, - bytes_response_maxlen, - xid_fields_response); - talloc_free(xid_fields_response); - talloc_free(xid_fields); - } - - return rc; -} - -/* Dispatch XID indications and responses comming from the MS */ -static void rx_llc_xid(struct gprs_llc_lle *lle, - struct gprs_llc_hdr_parsed *gph) -{ - uint8_t response[1024]; - int response_len; - - /* FIXME: 8.5.3.3: check if XID is invalid */ - if (gph->is_cmd) { - LOGP(DLLC, LOGL_NOTICE, - "Received XID indication from MS.\n"); - - struct msgb *resp; - uint8_t *xid; - resp = msgb_alloc_headroom(4096, 1024, "LLC_XID"); - - response_len = - gprs_llc_process_xid_ind(gph->data, gph->data_len, - response, sizeof(response), - lle); - if (response_len < 0) { - LOGP(DLLC, LOGL_ERROR, - "invalid XID indication received!\n"); - } else { - xid = msgb_put(resp, response_len); - memcpy(xid, response, response_len); - } - gprs_llc_tx_xid(lle, resp, 0); - } else { - LOGP(DLLC, LOGL_NOTICE, - "Received XID confirmation from MS.\n"); - gprs_llc_process_xid_conf(gph->data, gph->data_len, lle); - /* FIXME: if we had sent a XID reset, send - * LLGMM-RESET.conf to GMM */ - } -} - -/* Set of LL-XID negotiation (See also: TS 101 351, Section 7.2.2.4) */ -int gprs_ll_xid_req(struct gprs_llc_lle *lle, - struct gprs_llc_xid_field *l3_xid_field) -{ - /* Note: This functions is calle from gprs_sndcp.c */ - - uint8_t xid_bytes[1024];; - int xid_bytes_len; - uint8_t *xid; - struct msgb *msg; - const char *ftype; - - /* Generate XID */ - xid_bytes_len = - gprs_llc_generate_xid(xid_bytes, sizeof(xid_bytes), l3_xid_field, lle); - - /* Only perform XID sending if the XID message contains something */ - if (xid_bytes_len > 0) { - /* Transmit XID bytes */ - msg = msgb_alloc_headroom(4096, 1024, "LLC_XID"); - xid = msgb_put(msg, xid_bytes_len); - memcpy(xid, xid_bytes, xid_bytes_len); - if (l3_xid_field) - ftype = get_value_string(gprs_llc_xid_type_names, - l3_xid_field->type); - else - ftype = "NULL"; - LOGP(DLLC, LOGL_NOTICE, "Sending XID type %s (%d bytes) request" - " to MS...\n", ftype, xid_bytes_len); - gprs_llc_tx_xid(lle, msg, 1); - } else { - LOGP(DLLC, LOGL_ERROR, - "XID-Message generation failed, XID not sent!\n"); - return -EINVAL; - } - - return 0; -} -/* END XID RELATED */ - - - - -/* Entry function from upper level (LLC), asking us to transmit a BSSGP PDU - * to a remote MS (identified by TLLI) at a BTS identified by its BVCI and NSEI */ -static int _bssgp_tx_dl_ud(struct msgb *msg, struct sgsn_mm_ctx *mmctx) -{ - struct bssgp_dl_ud_par dup; - const uint8_t qos_profile_default[3] = { 0x00, 0x00, 0x20 }; - - memset(&dup, 0, sizeof(dup)); - /* before we have received some identity from the MS, we might - * not yet have a MMC context (e.g. XID negotiation of primarly - * LLC connection from GMM sapi). */ - if (mmctx) { - dup.imsi = mmctx->imsi; - dup.drx_parms = mmctx->drx_parms; - dup.ms_ra_cap.len = mmctx->ms_radio_access_capa.len; - dup.ms_ra_cap.v = mmctx->ms_radio_access_capa.buf; - - /* make sure we only send it to the right llme */ - if (!(msgb_tlli(msg) == mmctx->gb.llme->tlli - || msgb_tlli(msg) == mmctx->gb.llme->old_tlli)) { - LOGP(DLLC, LOGL_ERROR, - "_bssgp_tx_dl_ud(): Attempt to send Downlink Unitdata to wrong LLME:" - " msgb_tlli=0x%x mmctx->gb.llme->tlli=0x%x ->old_tlli=0x%x\n", - msgb_tlli(msg), mmctx->gb.llme->tlli, mmctx->gb.llme->old_tlli); - msgb_free(msg); - return -EINVAL; - } - } - memcpy(&dup.qos_profile, qos_profile_default, - sizeof(qos_profile_default)); - - return bssgp_tx_dl_ud(msg, 1000, &dup); -} - - -/* 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, - }, -}; - -LLIST_HEAD(gprs_llc_llmes); -void *llc_tall_ctx; - -/* lookup LLC Entity based on DLCI (TLLI+SAPI tuple) */ -static struct gprs_llc_lle *lle_by_tlli_sapi(const uint32_t tlli, uint8_t sapi) -{ - struct gprs_llc_llme *llme; - - llist_for_each_entry(llme, &gprs_llc_llmes, list) { - if (llme->tlli == tlli || llme->old_tlli == tlli) - return &llme->lle[sapi]; - } - return NULL; -} - -struct gprs_llc_lle *gprs_lle_get_or_create(const uint32_t tlli, uint8_t sapi) -{ - struct gprs_llc_llme *llme; - struct gprs_llc_lle *lle; - - lle = lle_by_tlli_sapi(tlli, sapi); - if (lle) - return lle; - - LOGP(DLLC, LOGL_NOTICE, "LLC: unknown TLLI 0x%08x, " - "creating LLME on the fly\n", tlli); - llme = llme_alloc(tlli); - lle = &llme->lle[sapi]; - return lle; -} - -struct llist_head *gprs_llme_list(void) -{ - return &gprs_llc_llmes; -} - -/* lookup LLC Entity for RX based on DLCI (TLLI+SAPI tuple) */ -static struct gprs_llc_lle *lle_for_rx_by_tlli_sapi(const uint32_t tlli, - uint8_t sapi, enum gprs_llc_cmd cmd) -{ - struct gprs_llc_lle *lle; - - /* We already know about this TLLI */ - lle = 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 = lle_by_tlli_sapi(tlli, sapi); - if (lle) { - LOGP(DLLC, 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 == GPRS_SAPI_GMM && - (cmd == GPRS_LLC_XID || cmd == GPRS_LLC_UI)) { - struct gprs_llc_llme *llme; - /* FIXME: don't use the TLLI but the 0xFFFF unassigned? */ - llme = llme_alloc(tlli); - LOGP(DLLC, LOGL_NOTICE, "LLC RX: unknown TLLI 0x%08x, " - "creating LLME on the fly\n", tlli); - lle = &llme->lle[sapi]; - return lle; - } - - LOGP(DLLC, LOGL_NOTICE, - "unknown TLLI(0x%08x)/SAPI(%d): Silently dropping\n", - tlli, sapi); - return NULL; -} - -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 = GPRS_LLES_UNASSIGNED; - - /* Initialize according to parameters */ - memcpy(&lle->params, &llc_default_params[sapi], sizeof(lle->params)); -} - -static struct gprs_llc_llme *llme_alloc(uint32_t tlli) -{ - struct gprs_llc_llme *llme; - uint32_t i; - - llme = talloc_zero(llc_tall_ctx, struct gprs_llc_llme); - if (!llme) - return NULL; - - llme->tlli = tlli; - llme->old_tlli = TLLI_UNASSIGNED; - llme->state = GPRS_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, &gprs_llc_llmes); - - llme->comp.proto = gprs_sndcp_comp_alloc(llme); - llme->comp.data = gprs_sndcp_comp_alloc(llme); - - return llme; -} - -static void llme_free(struct gprs_llc_llme *llme) -{ - gprs_sndcp_comp_free(llme->comp.proto); - gprs_sndcp_comp_free(llme->comp.data); - llist_del(&llme->list); - talloc_free(llme); -} - -#if 0 -/* FIXME: Unused code... */ -static void t200_expired(void *data) -{ - struct gprs_llc_lle *lle = data; - - /* 8.5.1.3: Expiry of T200 */ - - if (lle->retrans_ctr >= lle->params.n200) { - /* FIXME: LLGM-STATUS-IND, LL-RELEASE-IND/CNF */ - lle->state = GPRS_LLES_ASSIGNED_ADM; - } - - switch (lle->state) { - case GPRS_LLES_LOCAL_EST: - /* FIXME: retransmit SABM */ - /* FIXME: re-start T200 */ - lle->retrans_ctr++; - break; - case GPRS_LLES_LOCAL_REL: - /* FIXME: retransmit DISC */ - /* FIXME: re-start T200 */ - lle->retrans_ctr++; - break; - default: - LOGP(DLLC, LOGL_ERROR, "LLC unhandled state: %d\n", lle->state); - break; - } - -} - -static void t201_expired(void *data) -{ - struct gprs_llc_lle *lle = data; - - if (lle->retrans_ctr < lle->params.n200) { - /* FIXME: transmit apropriate supervisory frame (8.6.4.1) */ - /* FIXME: set timer T201 */ - lle->retrans_ctr++; - } -} -#endif - -int gprs_llc_tx_u(struct msgb *msg, uint8_t sapi, int command, - enum gprs_llc_u_cmd u_cmd, int pf_bit) -{ - uint8_t *fcs, *llch; - uint8_t addr, ctrl; - uint32_t fcs_calc; - - /* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */ - - /* Address Field */ - addr = sapi & 0xf; - if (command) - addr |= 0x40; - - /* 6.3 Figure 8 */ - ctrl = 0xe0 | u_cmd; - if (pf_bit) - ctrl |= 0x10; - - /* prepend LLC UI header */ - llch = msgb_push(msg, 2); - llch[0] = addr; - llch[1] = ctrl; - - /* append FCS to end of frame */ - fcs = msgb_put(msg, 3); - fcs_calc = gprs_llc_fcs(llch, fcs - llch); - fcs[0] = fcs_calc & 0xff; - fcs[1] = (fcs_calc >> 8) & 0xff; - fcs[2] = (fcs_calc >> 16) & 0xff; - - /* Identifiers passed down: (BVCI, NSEI) */ - - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_LLC_DL_PACKETS]); - rate_ctr_add(&sgsn->rate_ctrs->ctr[CTR_LLC_DL_BYTES], msg->len); - - /* Send BSSGP-DL-UNITDATA.req */ - return _bssgp_tx_dl_ud(msg, NULL); -} - -/* Send XID response to LLE */ -static int gprs_llc_tx_xid(struct gprs_llc_lle *lle, struct msgb *msg, - int command) -{ - /* copy identifiers from LLE to ensure lower layers can route */ - msgb_tlli(msg) = lle->llme->tlli; - msgb_bvci(msg) = lle->llme->bvci; - msgb_nsei(msg) = lle->llme->nsei; - - return gprs_llc_tx_u(msg, lle->sapi, command, GPRS_LLC_U_XID, 1); -} - -static int gprs_llc_tx_dm(struct gprs_llc_lle *lle) -{ - struct msgb *msg = msgb_alloc_headroom(4096, 1024, "LLC_DM"); - - /* copy identifiers from LLE to ensure lower layers can route */ - msgb_tlli(msg) = lle->llme->tlli; - msgb_bvci(msg) = lle->llme->bvci; - msgb_nsei(msg) = lle->llme->nsei; - - return gprs_llc_tx_u(msg, lle->sapi, 0, GPRS_LLC_U_DM_RESP, 1); -} - -/* encrypt information field + FCS, if needed! */ -static int apply_gea(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) { - LOGP(DLLC, 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; -} - -/* 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_tx_ui(struct msgb *msg, uint8_t sapi, int command, - struct sgsn_mm_ctx *mmctx, bool encryptable) -{ - struct gprs_llc_lle *lle; - uint8_t *fcs, *llch; - uint8_t addr, ctrl[2]; - uint32_t fcs_calc; - uint16_t nu = 0; - uint32_t oc; - - /* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */ - - /* look-up or create the LL Entity for this (TLLI, SAPI) tuple */ - lle = gprs_lle_get_or_create(msgb_tlli(msg), sapi); - - if (msg->len > lle->params.n201_u) { - LOGP(DLLC, LOGL_ERROR, "Cannot Tx %u bytes (N201-U=%u)\n", - msg->len, lle->params.n201_u); - msgb_free(msg); - return -EFBIG; - } - - gprs_llme_copy_key(mmctx, lle->llme); - - /* Update LLE's (BVCI, NSEI) tuple */ - lle->llme->bvci = msgb_bvci(msg); - lle->llme->nsei = msgb_nsei(msg); - - /* Obtain current values for N(u) and OC */ - nu = lle->vu_send; - oc = lle->oc_ui_send; - /* 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; - - /* Address Field */ - addr = sapi & 0xf; - if (command) - addr |= 0x40; - - /* Control Field */ - ctrl[0] = 0xc0; - ctrl[0] |= nu >> 6; - ctrl[1] = (nu << 2) & 0xfc; - ctrl[1] |= 0x01; /* Protected Mode */ - - /* prepend LLC UI header */ - llch = msgb_push(msg, 3); - llch[0] = addr; - llch[1] = ctrl[0]; - llch[2] = ctrl[1]; - - /* append FCS to end of frame */ - fcs = msgb_put(msg, 3); - fcs_calc = gprs_llc_fcs(llch, fcs - llch); - fcs[0] = fcs_calc & 0xff; - fcs[1] = (fcs_calc >> 8) & 0xff; - fcs[2] = (fcs_calc >> 16) & 0xff; - - if (lle->llme->algo != GPRS_ALGO_GEA0 && encryptable) { - int rc = apply_gea(lle, fcs - llch, nu, oc, sapi, fcs, llch); - if (rc < 0) { - msgb_free(msg); - return rc; - } - } - - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_LLC_DL_PACKETS]); - rate_ctr_add(&sgsn->rate_ctrs->ctr[CTR_LLC_DL_BYTES], msg->len); - - /* Identifiers passed down: (BVCI, NSEI) */ - - /* Send BSSGP-DL-UNITDATA.req */ - return _bssgp_tx_dl_ud(msg, mmctx); -} - -static int gprs_llc_hdr_rx(struct gprs_llc_hdr_parsed *gph, - struct gprs_llc_lle *lle) -{ - switch (gph->cmd) { -#if 0 - /* we don't fully imoplement ABM, so refuse it properly (OS#3953) */ - case GPRS_LLC_SABM: /* Section 6.4.1.1 */ - lle->v_sent = lle->v_ack = lle->v_recv = 0; - if (lle->state == GPRS_LLES_ASSIGNED_ADM) { - /* start re-establishment (8.7.1) */ - } - lle->state = GPRS_LLES_REMOTE_EST; - /* FIXME: Send UA */ - lle->state = GPRS_LLES_ABM; - /* FIXME: process data */ - break; - case GPRS_LLC_DISC: /* Section 6.4.1.2 */ - /* FIXME: Send UA */ - /* terminate ABM */ - lle->state = GPRS_LLES_ASSIGNED_ADM; - break; - case GPRS_LLC_UA: /* Section 6.4.1.3 */ - if (lle->state == GPRS_LLES_LOCAL_EST) - lle->state = GPRS_LLES_ABM; - break; - case GPRS_LLC_DM: /* Section 6.4.1.4: ABM cannot be performed */ - if (lle->state == GPRS_LLES_LOCAL_EST) - lle->state = GPRS_LLES_ASSIGNED_ADM; - break; - case GPRS_LLC_FRMR: /* Section 6.4.1.5 */ - break; -#else - case GPRS_LLC_SABM: - case GPRS_LLC_DISC: - /* send DM to properly signal we don't do ABM */ - gprs_llc_tx_dm(lle); - break; -#endif - case GPRS_LLC_XID: /* Section 6.4.1.6 */ - rx_llc_xid(lle, gph); - break; - case GPRS_LLC_UI: - if (gprs_llc_is_retransmit(gph->seq_tx, lle->vu_recv)) { - LOGP(DLLC, LOGL_NOTICE, - "TLLI=%08x dropping UI, N(U=%d) not in window V(URV(UR:%d).\n", - lle->llme ? lle->llme->tlli : -1, - gph->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 (gph->seq_tx != lle->vu_recv_last) { - lle->vu_recv_last = gph->seq_tx; - lle->vu_recv_duplicates = 0; - } else { - lle->vu_recv_duplicates++; - if (lle->vu_recv_duplicates < 3) - return -EIO; - LOGP(DLLC, LOGL_NOTICE, "TLLI=%08x recovering " - "N(U=%d) after receiving %u duplicates\n", - lle->llme ? lle->llme->tlli : -1, - gph->seq_tx, lle->vu_recv_duplicates); - } - } - /* Increment the sequence number that we expect in the next frame */ - lle->vu_recv = (gph->seq_tx + 1) % 512; - /* Increment Overflow Counter */ - if ((gph->seq_tx + 1) / 512) - lle->oc_ui_recv += 512; - break; - case GPRS_LLC_NULL: - LOGP(DLLC, LOGL_DEBUG, "TLLI=%08x sends us LLC NULL\n", lle->llme ? lle->llme->tlli : -1); - break; - default: - LOGP(DLLC, LOGL_NOTICE, "Unhandled command: %d\n", gph->cmd); - break; - } - - return 0; -} - -/* receive an incoming LLC PDU (BSSGP-UL-UNITDATA-IND, 7.2.4.2) */ -int gprs_llc_rcvmsg(struct msgb *msg, struct tlv_parsed *tv) -{ - struct gprs_llc_hdr *lh = (struct gprs_llc_hdr *) msgb_llch(msg); - struct gprs_llc_hdr_parsed llhp; - struct gprs_llc_lle *lle = NULL; - bool drop_cipherable = false; - int rc = 0; - - /* Identifiers from DOWN: NSEI, BVCI, TLLI */ - - memset(&llhp, 0, sizeof(llhp)); - rc = gprs_llc_hdr_parse(&llhp, (uint8_t *) lh, TLVP_LEN(tv, BSSGP_IE_LLC_PDU)); - if (rc < 0) { - LOGP(DLLC, LOGL_NOTICE, "Error during LLC header parsing\n"); - return rc; - } - - switch (gprs_tlli_type(msgb_tlli(msg))) { - case TLLI_LOCAL: - case TLLI_FOREIGN: - case TLLI_RANDOM: - case TLLI_AUXILIARY: - break; - default: - LOGP(DLLC, LOGL_ERROR, - "Discarding frame with strange TLLI type\n"); - break; - } - - /* find the LLC Entity for this TLLI+SAPI tuple */ - lle = lle_for_rx_by_tlli_sapi(msgb_tlli(msg), llhp.sapi, llhp.cmd); - if (!lle) { - switch (llhp.sapi) { - case GPRS_SAPI_SNDCP3: - case GPRS_SAPI_SNDCP5: - case GPRS_SAPI_SNDCP9: - case GPRS_SAPI_SNDCP11: - /* Ask an upper layer for help. */ - return gsm0408_gprs_force_reattach_oldmsg(msg, NULL); - default: - break; - } - return 0; - } - gprs_llc_hdr_dump(&llhp, lle); - /* reset age computation */ - lle->llme->age_timestamp = GPRS_LLME_RESET_AGE; - - /* decrypt information field + FCS, if needed! */ - if (llhp.is_encrypted) { - if (lle->llme->algo != GPRS_ALGO_GEA0) { - rc = apply_gea(lle, llhp.data_len + 3, llhp.seq_tx, - lle->oc_ui_recv, lle->sapi, NULL, - llhp.data); - if (rc < 0) - return rc; - llhp.fcs = *(llhp.data + llhp.data_len); - llhp.fcs |= *(llhp.data + llhp.data_len + 1) << 8; - llhp.fcs |= *(llhp.data + llhp.data_len + 2) << 16; - } else { - LOGP(DLLC, 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 */ - llhp.fcs_calc = gprs_llc_fcs((uint8_t *)lh, llhp.crc_length); - if (llhp.fcs != llhp.fcs_calc) { - LOGP(DLLC, LOGL_INFO, "Dropping frame with invalid FCS\n"); - return -EIO; - } - - /* Update LLE's (BVCI, NSEI) tuple */ - lle->llme->bvci = msgb_bvci(msg); - lle->llme->nsei = msgb_nsei(msg); - - /* Receive and Process the actual LLC frame */ - rc = gprs_llc_hdr_rx(&llhp, lle); - if (rc < 0) - return rc; - - /* there are many frame types that don't carry user information - * and which hence have llhp.data = NULL */ - if (llhp.data) { - /* set l3 layer & remove the fcs */ - msg->l3h = llhp.data; - msgb_l3trim(msg, llhp.data_len); - } - - rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_LLC_UL_PACKETS]); - rate_ctr_add(&sgsn->rate_ctrs->ctr[CTR_LLC_UL_BYTES], msg->len); - - /* llhp.data is only set when we need to send LL_[UNIT]DATA_IND up */ - if (llhp.cmd == GPRS_LLC_UI && llhp.data && llhp.data_len) { - switch (llhp.sapi) { - case GPRS_SAPI_GMM: - /* send LL_UNITDATA_IND to GMM */ - rc = gsm0408_gprs_rcvmsg_gb(msg, lle->llme, - drop_cipherable); - break; - case GPRS_SAPI_SNDCP3: - case GPRS_SAPI_SNDCP5: - case GPRS_SAPI_SNDCP9: - case GPRS_SAPI_SNDCP11: - /* send LL_DATA_IND/LL_UNITDATA_IND to SNDCP */ - rc = sndcp_llunitdata_ind(msg, lle, llhp.data, llhp.data_len); - break; - case GPRS_SAPI_SMS: - /* FIXME */ - case GPRS_SAPI_TOM2: - case GPRS_SAPI_TOM8: - /* FIXME: send LL_DATA_IND/LL_UNITDATA_IND to TOM */ - default: - LOGP(DLLC, LOGL_NOTICE, "Unsupported SAPI %u\n", llhp.sapi); - rc = -EINVAL; - break; - } - } - - return rc; -} - -/* Propagate crypto parameters MM -> LLME */ -void gprs_llme_copy_key(struct sgsn_mm_ctx *mm, struct gprs_llc_llme *llme) -{ - if (!mm) - return; - if (mm->ciph_algo != GPRS_ALGO_GEA0) { - llme->algo = mm->ciph_algo; - if (llme->cksn != mm->auth_triplet.key_seq && - mm->auth_triplet.key_seq != GSM_KEY_SEQ_INVAL) { - memcpy(llme->kc, mm->auth_triplet.vec.kc, - gprs_cipher_key_length(mm->ciph_algo)); - llme->cksn = mm->auth_triplet.key_seq; - } - } else - llme->cksn = GSM_KEY_SEQ_INVAL; -} - -/* 04.64 Chapter 7.2.1.1 LLGMM-ASSIGN */ -int gprs_llgmm_assign(struct gprs_llc_llme *llme, - uint32_t old_tlli, uint32_t new_tlli) -{ - unsigned int i; - - 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->tlli = new_tlli; - llme->state = GPRS_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->vu_send = l->vu_recv = 0; - l->retrans_ctr = 0; - l->state = GPRS_LLES_ASSIGNED_ADM; - /* FIXME Set parameters according to table 9 */ - } - } - } 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 = GPRS_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 = GPRS_LLMS_UNASSIGNED; - for (i = 0; i < ARRAY_SIZE(llme->lle); i++) { - struct gprs_llc_lle *l = &llme->lle[i]; - l->state = GPRS_LLES_UNASSIGNED; - } - llme_free(llme); - } else - return -EINVAL; - - return 0; -} - -/* TLLI unassignment */ -int gprs_llgmm_unassign(struct gprs_llc_llme *llme) -{ - return gprs_llgmm_assign(llme, llme->tlli, TLLI_UNASSIGNED); -} - -/* Chapter 7.2.1.2 LLGMM-RESET.req */ -int gprs_llgmm_reset(struct gprs_llc_llme *llme) -{ - struct msgb *msg = msgb_alloc_headroom(4096, 1024, "LLC_XID"); - struct gprs_llc_lle *lle = &llme->lle[1]; - uint8_t xid_bytes[1024]; - int xid_bytes_len, rc; - uint8_t *xid; - - LOGP(DLLC, LOGL_NOTICE, "LLGM Reset\n"); - - rc = osmo_get_rand_id((uint8_t *) &llme->iov_ui, 4); - if (rc < 0) { - LOGP(DLLC, LOGL_ERROR, "osmo_get_rand_id() failed for LLC XID reset: %s\n", strerror(-rc)); - return rc; - } - - /* Generate XID message */ - xid_bytes_len = gprs_llc_generate_xid_for_gmm_reset(xid_bytes, sizeof(xid_bytes), - llme->iov_ui, lle); - if (xid_bytes_len < 0) - return -EINVAL; - xid = msgb_put(msg, xid_bytes_len); - memcpy(xid, xid_bytes, xid_bytes_len); - - /* 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; - - /* FIXME: Start T200, wait for XID response */ - return gprs_llc_tx_xid(lle, msg, 1); -} - -int gprs_llgmm_reset_oldmsg(struct msgb* oldmsg, uint8_t sapi, - struct gprs_llc_llme *llme) -{ - struct msgb *msg = msgb_alloc_headroom(4096, 1024, "LLC_XID"); - struct gprs_llc_lle *lle = &llme->lle[sapi]; - uint8_t xid_bytes[1024]; - int xid_bytes_len, rc; - uint8_t *xid; - - LOGP(DLLC, LOGL_NOTICE, "LLGM Reset\n"); - - rc = osmo_get_rand_id((uint8_t *) &llme->iov_ui, 4); - if (rc < 0) { - LOGP(DLLC, LOGL_ERROR, "osmo_get_rand_id() failed for LLC XID reset: %s\n", strerror(-rc)); - return rc; - } - - /* Generate XID message */ - xid_bytes_len = gprs_llc_generate_xid_for_gmm_reset(xid_bytes, sizeof(xid_bytes), - llme->iov_ui, lle); - if (xid_bytes_len < 0) - return -EINVAL; - xid = msgb_put(msg, xid_bytes_len); - memcpy(xid, xid_bytes, xid_bytes_len); - - /* FIXME: Start T200, wait for XID response */ - - msgb_tlli(msg) = msgb_tlli(oldmsg); - msgb_bvci(msg) = msgb_bvci(oldmsg); - msgb_nsei(msg) = msgb_nsei(oldmsg); - - return gprs_llc_tx_u(msg, sapi, 1, GPRS_LLC_U_XID, 1); -} - -int gprs_llc_init(const char *cipher_plugin_path) -{ - return gprs_cipher_load(cipher_plugin_path); -} diff --git a/src/gprs/gprs_llc_vty.c b/src/gprs/gprs_llc_vty.c deleted file mode 100644 index 418be8348..000000000 --- a/src/gprs/gprs_llc_vty.c +++ /dev/null @@ -1,115 +0,0 @@ -/* VTY interface for our GPRS LLC implementation */ - -/* (C) 2010 by Harald Welte - * - * 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 . - * - */ - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -struct value_string gprs_llc_state_strs[] = { - { GPRS_LLES_UNASSIGNED, "TLLI Unassigned" }, - { GPRS_LLES_ASSIGNED_ADM, "TLLI Assigned" }, - { GPRS_LLES_LOCAL_EST, "Local Establishment" }, - { GPRS_LLES_REMOTE_EST, "Remote Establishment" }, - { GPRS_LLES_ABM, "Asynchronous Balanced Mode" }, - { GPRS_LLES_LOCAL_REL, "Local Release" }, - { GPRS_LLES_TIMER_REC, "Timer Recovery" }, - { 0, NULL } -}; - -static void vty_dump_lle(struct vty *vty, struct gprs_llc_lle *lle) -{ - struct gprs_llc_params *par = &lle->params; - vty_out(vty, " SAPI %2u State %s VUsend=%u, VUrecv=%u", lle->sapi, - get_value_string(gprs_llc_state_strs, lle->state), - lle->vu_send, lle->vu_recv); - vty_out(vty, " Vsent=%u Vack=%u Vrecv=%u, RetransCtr=%u%s", - lle->v_sent, lle->v_ack, lle->v_recv, - lle->retrans_ctr, VTY_NEWLINE); - vty_out(vty, " T200=%u, N200=%u, N201-U=%u, N201-I=%u, mD=%u, " - "mU=%u, kD=%u, kU=%u%s", par->t200_201, par->n200, - par->n201_u, par->n201_i, par->mD, par->mU, par->kD, - par->kU, VTY_NEWLINE); -} - -static uint8_t valid_sapis[] = { 1, 2, 3, 5, 7, 8, 9, 11 }; - -static void vty_dump_llme(struct vty *vty, struct gprs_llc_llme *llme) -{ - unsigned int i; - struct timespec now_tp = {0}; - osmo_clock_gettime(CLOCK_MONOTONIC, &now_tp); - - vty_out(vty, "TLLI %08x (Old TLLI %08x) BVCI=%u NSEI=%u %s: " - "IOV-UI=0x%06x CKSN=%d Age=%d: State %s%s", llme->tlli, - llme->old_tlli, llme->bvci, llme->nsei, - get_value_string(gprs_cipher_names, llme->algo), llme->iov_ui, - llme->cksn, llme->age_timestamp == GPRS_LLME_RESET_AGE ? 0 : - (int)(now_tp.tv_sec - (time_t)llme->age_timestamp), - get_value_string(gprs_llc_state_strs, llme->state), VTY_NEWLINE); - - for (i = 0; i < ARRAY_SIZE(valid_sapis); i++) { - struct gprs_llc_lle *lle; - uint8_t sapi = valid_sapis[i]; - - if (sapi >= ARRAY_SIZE(llme->lle)) - continue; - - lle = &llme->lle[sapi]; - vty_dump_lle(vty, lle); - } -} - - -DEFUN(show_llc, show_llc_cmd, - "show llc", - SHOW_STR "Display information about the LLC protocol") -{ - struct gprs_llc_llme *llme; - - vty_out(vty, "State of LLC Entities%s", VTY_NEWLINE); - llist_for_each_entry(llme, &gprs_llc_llmes, list) { - vty_dump_llme(vty, llme); - } - return CMD_SUCCESS; -} - -int gprs_llc_vty_init(void) -{ - install_element_ve(&show_llc_cmd); - - return 0; -} diff --git a/src/gprs/gprs_llc_xid.c b/src/gprs/gprs_llc_xid.c deleted file mode 100644 index b91fa6bfd..000000000 --- a/src/gprs/gprs_llc_xid.c +++ /dev/null @@ -1,281 +0,0 @@ -/* GPRS LLC XID field encoding/decoding as per 3GPP TS 44.064 */ - -/* (C) 2016 by sysmocom s.f.m.c. GmbH - * All Rights Reserved - * - * Author: Philipp Maier - * - * 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 . - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -const struct value_string gprs_llc_xid_type_names[] = { - { GPRS_LLC_XID_T_VERSION, "VERSION"}, - { GPRS_LLC_XID_T_IOV_UI, "IOV_UI"}, - { GPRS_LLC_XID_T_IOV_I, "IOV_I"}, - { GPRS_LLC_XID_T_T200, "T200"}, - { GPRS_LLC_XID_T_N200, "N200"}, - { GPRS_LLC_XID_T_N201_U, "N201_U"}, - { GPRS_LLC_XID_T_N201_I, "N201_I"}, - { GPRS_LLC_XID_T_mD, "mD"}, - { GPRS_LLC_XID_T_mU, "mU"}, - { GPRS_LLC_XID_T_kD, "kD"}, - { GPRS_LLC_XID_T_kU, "kU"}, - { GPRS_LLC_XID_T_L3_PAR, "L3_PAR"}, - { GPRS_LLC_XID_T_RESET, "RESET"}, - { 0, NULL }, -}; - -/* Parse XID parameter field */ -static int decode_xid_field(struct gprs_llc_xid_field *xid_field, - const uint8_t *src, uint8_t src_len) -{ - uint8_t xl; - uint8_t type; - uint8_t len; - int src_counter = 0; - - /* Exit immediately if it is clear that no - * parseable data is present */ - if (src_len < 1 || !src) - return -EINVAL; - - /* Extract header info */ - xl = (*src >> 7) & 1; - type = (*src >> 2) & 0x1F; - - /* Extract length field */ - len = (*src) & 0x3; - src++; - src_counter++; - if (xl) { - if (src_len < 2) - return -EINVAL; - len = (len << 6) & 0xC0; - len |= ((*src) >> 2) & 0x3F; - src++; - src_counter++; - } - - /* Fill out struct */ - xid_field->type = type; - xid_field->data_len = len; - if (len > 0) { - if (src_len < src_counter + len) - return -EINVAL; - xid_field->data = - talloc_memdup(xid_field,src,xid_field->data_len); - } else - xid_field->data = NULL; - - /* Return consumed length */ - return src_counter + len; -} - -/* Encode XID parameter field */ -static int encode_xid_field(uint8_t *dst, int dst_maxlen, - const struct gprs_llc_xid_field *xid_field) -{ - int xl = 0; - - /* When the length does not fit into 2 bits, - * we need extended length fields */ - if (xid_field->data_len > 3) - xl = 1; - - /* Exit immediately if it is clear that no - * encoding result can be stored */ - if (dst_maxlen < xid_field->data_len + 1 + xl) - return -EINVAL; - - /* There are only 5 bits reserved for the type, exit on exceed */ - if (xid_field->type > 31) - return -EINVAL; - - /* Encode header */ - memset(dst, 0, dst_maxlen); - if (xl) - dst[0] |= 0x80; - dst[0] |= (((xid_field->type) & 0x1F) << 2); - - if (xl) { - dst[0] |= (((xid_field->data_len) >> 6) & 0x03); - dst[1] = ((xid_field->data_len) << 2) & 0xFC; - } else - dst[0] |= ((xid_field->data_len) & 0x03); - - /* Append payload data */ - if (xid_field->data && xid_field->data_len) - memcpy(dst + 1 + xl, xid_field->data, xid_field->data_len); - - /* Return generated length */ - return xid_field->data_len + 1 + xl; -} - -/* Transform a list with XID fields into a XID message (dst) */ -int gprs_llc_compile_xid(uint8_t *dst, int dst_maxlen, - const struct llist_head *xid_fields) -{ - struct gprs_llc_xid_field *xid_field; - int rc; - int byte_counter = 0; - - OSMO_ASSERT(xid_fields); - OSMO_ASSERT(dst); - - llist_for_each_entry_reverse(xid_field, xid_fields, list) { - /* Encode XID-Field */ - rc = encode_xid_field(dst, dst_maxlen, xid_field); - if (rc < 0) - return -EINVAL; - - /* Advance pointer and lower maxlen for the - * next encoding round */ - dst += rc; - byte_counter += rc; - dst_maxlen -= rc; - } - - /* Return generated length */ - return byte_counter; -} - -/* Transform a XID message (dst) into a list of XID fields */ -struct llist_head *gprs_llc_parse_xid(const void *ctx, const uint8_t *src, - int src_len) -{ - struct gprs_llc_xid_field *xid_field; - struct llist_head *xid_fields; - - int rc; - int max_loops = src_len; - - OSMO_ASSERT(src); - - xid_fields = talloc_zero(ctx, struct llist_head); - INIT_LLIST_HEAD(xid_fields); - - while (1) { - /* Bail in case decode_xid_field() constantly returns zero */ - if (max_loops <= 0) { - talloc_free(xid_fields); - return NULL; - } - - /* Decode XID field */ - xid_field = talloc_zero(xid_fields, struct gprs_llc_xid_field); - rc = decode_xid_field(xid_field, src, src_len); - - /* Immediately stop on error */ - if (rc < 0) { - talloc_free(xid_fields); - return NULL; - } - - /* Add parsed XID field to list */ - llist_add(&xid_field->list, xid_fields); - - /* Advance pointer and lower dst_len for the next - * decoding round */ - src += rc; - src_len -= rc; - - /* We are (scuccessfully) done when no further byes are left */ - if (src_len == 0) - return xid_fields; - - max_loops--; - } -} - -/* Create a duplicate of an XID-Field */ -struct gprs_llc_xid_field *gprs_llc_dup_xid_field(const void *ctx, const struct - gprs_llc_xid_field - *xid_field) -{ - struct gprs_llc_xid_field *dup; - - OSMO_ASSERT(xid_field); - - /* Create a copy of the XID field in memory */ - dup = talloc_memdup(ctx, xid_field, sizeof(*xid_field)); - dup->data = talloc_memdup(ctx, xid_field->data, xid_field->data_len); - - /* Unlink duplicate from source list */ - INIT_LLIST_HEAD(&dup->list); - - return dup; -} - -/* Copy an llist with xid fields */ -struct llist_head *gprs_llc_copy_xid(const void *ctx, - const struct llist_head *xid_fields) -{ - struct gprs_llc_xid_field *xid_field; - struct llist_head *xid_fields_copy; - - OSMO_ASSERT(xid_fields); - - xid_fields_copy = talloc_zero(ctx, struct llist_head); - INIT_LLIST_HEAD(xid_fields_copy); - - /* Create duplicates and add them to the target list */ - llist_for_each_entry(xid_field, xid_fields, list) { - llist_add(&gprs_llc_dup_xid_field(ctx, xid_field)->list, - xid_fields_copy); - } - - return xid_fields_copy; -} - -/* Dump a list with XID fields (Debug) */ -void gprs_llc_dump_xid_fields(const struct llist_head *xid_fields, - unsigned int logl) -{ - struct gprs_llc_xid_field *xid_field; - - OSMO_ASSERT(xid_fields); - - llist_for_each_entry(xid_field, xid_fields, list) { - if (xid_field->data_len) { - OSMO_ASSERT(xid_field->data); - LOGP(DLLC, logl, - "XID: type %s, data_len=%d, data=%s\n", - get_value_string(gprs_llc_xid_type_names, - xid_field->type), - xid_field->data_len, - osmo_hexdump_nospc(xid_field->data, - xid_field->data_len)); - } else { - LOGP(DLLC, logl, - "XID: type=%d, data_len=%d, data=NULL\n", - xid_field->type, xid_field->data_len); - } - } -} diff --git a/src/gprs/gprs_mm_state_gb_fsm.c b/src/gprs/gprs_mm_state_gb_fsm.c deleted file mode 100644 index 2056540db..000000000 --- a/src/gprs/gprs_mm_state_gb_fsm.c +++ /dev/null @@ -1,112 +0,0 @@ -#include - -#include - -#include -#include - -#define X(s) (1 << (s)) - -static const struct osmo_tdef_state_timeout mm_state_gb_fsm_timeouts[32] = { - [ST_MM_IDLE] = { }, - [ST_MM_READY] = { .T=3314 }, - [ST_MM_STANDBY] = { }, -}; - -#define mm_state_gb_fsm_state_chg(fi, NEXT_STATE) \ - osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, mm_state_gb_fsm_timeouts, sgsn->cfg.T_defs, -1) - -static void st_mm_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - switch(event) { - case E_MM_GPRS_ATTACH: - mm_state_gb_fsm_state_chg(fi, ST_MM_READY); - break; - case E_MM_PDU_RECEPTION: - break; - } -} - -static void st_mm_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - unsigned long t_secs; - - switch(event) { - case E_MM_READY_TIMER_EXPIRY: - case E_MM_IMPLICIT_DETACH: - mm_state_gb_fsm_state_chg(fi, ST_MM_STANDBY); - break; - case E_MM_PDU_RECEPTION: - /* RE-arm the READY timer upon receival of Gb PDUs */ - t_secs = osmo_tdef_get(sgsn->cfg.T_defs, 3314, OSMO_TDEF_S, -1); - osmo_timer_schedule(&fi->timer, t_secs, 0); - break; - case E_MM_RA_UPDATE: - break; - } -} - -static void st_mm_standby(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - switch(event) { - case E_MM_PDU_RECEPTION: - mm_state_gb_fsm_state_chg(fi, ST_MM_READY); - break; - } -} - -static struct osmo_fsm_state mm_state_gb_fsm_states[] = { - [ST_MM_IDLE] = { - .in_event_mask = X(E_MM_GPRS_ATTACH) | X(E_MM_PDU_RECEPTION), - .out_state_mask = X(ST_MM_READY), - .name = "Idle", - .action = st_mm_idle, - }, - [ST_MM_READY] = { - .in_event_mask = X(E_MM_READY_TIMER_EXPIRY) | X(E_MM_RA_UPDATE) | X(E_MM_IMPLICIT_DETACH) | X(E_MM_PDU_RECEPTION), - .out_state_mask = X(ST_MM_IDLE) | X(ST_MM_STANDBY), - .name = "Ready", - .action = st_mm_ready, - }, - [ST_MM_STANDBY] = { - .in_event_mask = X(E_MM_PDU_RECEPTION), - .out_state_mask = X(ST_MM_IDLE) | X(ST_MM_READY), - .name = "Standby", - .action = st_mm_standby, - }, -}; - -const struct value_string mm_state_gb_fsm_event_names[] = { - OSMO_VALUE_STRING(E_MM_GPRS_ATTACH), - OSMO_VALUE_STRING(E_MM_PDU_RECEPTION), - OSMO_VALUE_STRING(E_MM_IMPLICIT_DETACH), - OSMO_VALUE_STRING(E_MM_READY_TIMER_EXPIRY), - OSMO_VALUE_STRING(E_MM_RA_UPDATE), - { 0, NULL } -}; - -int mm_state_gb_fsm_timer_cb(struct osmo_fsm_inst *fi) -{ - switch(fi->state) { - case ST_MM_READY: - /* timer for mm state. state=READY: T3314 (aka TS 23.060 "READY timer") */ - osmo_fsm_inst_dispatch(fi, E_MM_READY_TIMER_EXPIRY, NULL); - break; - } - - return 0; -} - -struct osmo_fsm mm_state_gb_fsm = { - .name = "MM_STATE_Gb", - .states = mm_state_gb_fsm_states, - .num_states = ARRAY_SIZE(mm_state_gb_fsm_states), - .event_names = mm_state_gb_fsm_event_names, - .log_subsys = DMM, - .timer_cb = mm_state_gb_fsm_timer_cb, -}; - -static __attribute__((constructor)) void mm_state_gb_fsm_init(void) -{ - osmo_fsm_register(&mm_state_gb_fsm); -} diff --git a/src/gprs/gprs_mm_state_iu_fsm.c b/src/gprs/gprs_mm_state_iu_fsm.c deleted file mode 100644 index 1ed5f56f1..000000000 --- a/src/gprs/gprs_mm_state_iu_fsm.c +++ /dev/null @@ -1,121 +0,0 @@ -#include - -#include - -#include - -#include -#include - -#define X(s) (1 << (s)) - -static const struct osmo_tdef_state_timeout mm_state_iu_fsm_timeouts[32] = { - [ST_PMM_DETACHED] = { }, - [ST_PMM_CONNECTED] = { }, - [ST_PMM_IDLE] = { }, -}; - -#define mm_state_iu_fsm_state_chg(fi, NEXT_STATE) \ - osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, mm_state_iu_fsm_timeouts, sgsn->cfg.T_defs, -1) - -static void mmctx_change_gtpu_endpoints_to_sgsn(struct sgsn_mm_ctx *mm_ctx) -{ - char buf[INET_ADDRSTRLEN]; - struct sgsn_pdp_ctx *pdp; - llist_for_each_entry(pdp, &mm_ctx->pdp_list, list) { - LOGMMCTXP(LOGL_INFO, mm_ctx, "Changing GTP-U endpoints %s -> %s\n", - sgsn_gtp_ntoa(&pdp->lib->gsnlu), - inet_ntop(AF_INET, &sgsn->cfg.gtp_listenaddr.sin_addr, buf, sizeof(buf))); - sgsn_pdp_upd_gtp_u(pdp, - &sgsn->cfg.gtp_listenaddr.sin_addr, - sizeof(sgsn->cfg.gtp_listenaddr.sin_addr)); - } -} - -static void st_pmm_detached(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - switch(event) { - case E_PMM_PS_ATTACH: - mm_state_iu_fsm_state_chg(fi, ST_PMM_CONNECTED); - break; - case E_PMM_IMPLICIT_DETACH: - break; - } -} - -static void st_pmm_connected(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - switch(event) { - case E_PMM_PS_CONN_RELEASE: - mm_state_iu_fsm_state_chg(fi, ST_PMM_IDLE); - break; - case E_PMM_IMPLICIT_DETACH: - mm_state_iu_fsm_state_chg(fi, ST_PMM_DETACHED); - break; - case E_PMM_RA_UPDATE: - break; - } -} - -static void st_pmm_idle_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) -{ - struct sgsn_mm_ctx *ctx = fi->priv; - - mmctx_change_gtpu_endpoints_to_sgsn(ctx); -} - -static void st_pmm_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - switch(event) { - case E_PMM_PS_CONN_ESTABLISH: - mm_state_iu_fsm_state_chg(fi, ST_PMM_CONNECTED); - break; - case E_PMM_IMPLICIT_DETACH: - mm_state_iu_fsm_state_chg(fi, ST_PMM_DETACHED); - break; - } -} - -static struct osmo_fsm_state mm_state_iu_fsm_states[] = { - [ST_PMM_DETACHED] = { - .in_event_mask = X(E_PMM_PS_ATTACH) | X(E_PMM_IMPLICIT_DETACH), - .out_state_mask = X(ST_PMM_CONNECTED), - .name = "Detached", - .action = st_pmm_detached, - }, - [ST_PMM_CONNECTED] = { - .in_event_mask = X(E_PMM_PS_CONN_RELEASE) | X(E_PMM_RA_UPDATE) | X(E_PMM_IMPLICIT_DETACH), - .out_state_mask = X(ST_PMM_DETACHED) | X(ST_PMM_IDLE), - .name = "Connected", - .action = st_pmm_connected, - }, - [ST_PMM_IDLE] = { - .in_event_mask = X(E_PMM_IMPLICIT_DETACH) | X(E_PMM_PS_CONN_ESTABLISH), - .out_state_mask = X(ST_PMM_DETACHED) | X(ST_PMM_CONNECTED), - .name = "Idle", - .onenter = st_pmm_idle_on_enter, - .action = st_pmm_idle, - }, -}; - -const struct value_string mm_state_iu_fsm_event_names[] = { - OSMO_VALUE_STRING(E_PMM_PS_ATTACH), - OSMO_VALUE_STRING(E_PMM_PS_CONN_RELEASE), - OSMO_VALUE_STRING(E_PMM_PS_CONN_ESTABLISH), - OSMO_VALUE_STRING(E_PMM_IMPLICIT_DETACH), - OSMO_VALUE_STRING(E_PMM_RA_UPDATE), - { 0, NULL } -}; - -struct osmo_fsm mm_state_iu_fsm = { - .name = "MM_STATE_Iu", - .states = mm_state_iu_fsm_states, - .num_states = ARRAY_SIZE(mm_state_iu_fsm_states), - .event_names = mm_state_iu_fsm_event_names, - .log_subsys = DMM, -}; - -static __attribute__((constructor)) void mm_state_iu_fsm_init(void) -{ - osmo_fsm_register(&mm_state_iu_fsm); -} diff --git a/src/gprs/gprs_ranap.c b/src/gprs/gprs_ranap.c deleted file mode 100644 index 027b6665a..000000000 --- a/src/gprs/gprs_ranap.c +++ /dev/null @@ -1,231 +0,0 @@ -/* Messages on the RANAP interface (Iu mode) */ - -/* (C) 2009-2015 by Harald Welte - * (C) 2015 by Holger Hans Peter Freyther - * (C) 2019 by sysmocom s.f.m.c. GmbH - * - * 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 . - * - */ - -#include "bscconfig.h" - -#ifdef BUILD_IU - -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include - -/* Send RAB activation requests for all PDP contexts */ -void activate_pdp_rabs(struct sgsn_mm_ctx *ctx) -{ - struct sgsn_pdp_ctx *pdp; - if (ctx->ran_type != MM_CTX_T_UTRAN_Iu) - return; - llist_for_each_entry(pdp, &ctx->pdp_list, list) { - iu_rab_act_ps(pdp->nsapi, pdp); - } -} - -/* Callback for RAB assignment response */ -static int sgsn_ranap_rab_ass_resp(struct sgsn_mm_ctx *ctx, RANAP_RAB_SetupOrModifiedItemIEs_t *setup_ies) -{ - uint8_t rab_id; - bool require_pdp_update = false; - struct sgsn_pdp_ctx *pdp = NULL; - RANAP_RAB_SetupOrModifiedItem_t *item = &setup_ies->raB_SetupOrModifiedItem; - - rab_id = item->rAB_ID.buf[0]; - - pdp = sgsn_pdp_ctx_by_nsapi(ctx, rab_id); - if (!pdp) { - LOGP(DRANAP, LOGL_ERROR, "RAB Assignment Response for unknown RAB/NSAPI=%u\n", rab_id); - return -1; - } - - if (item->transportLayerAddress) { - LOGPC(DRANAP, LOGL_INFO, " Setup: (%u/%s)", rab_id, osmo_hexdump(item->transportLayerAddress->buf, - item->transportLayerAddress->size)); - switch (item->transportLayerAddress->size) { - case 7: - /* It must be IPv4 inside a X213 NSAP */ - memcpy(pdp->lib->gsnlu.v, &item->transportLayerAddress->buf[3], 4); - break; - case 4: - /* It must be a raw IPv4 address */ - memcpy(pdp->lib->gsnlu.v, item->transportLayerAddress->buf, 4); - break; - case 16: - /* TODO: It must be a raw IPv6 address */ - case 19: - /* TODO: It must be IPv6 inside a X213 NSAP */ - default: - LOGP(DRANAP, LOGL_ERROR, "RAB Assignment Resp: Unknown " - "transport layer address size %u\n", - item->transportLayerAddress->size); - return -1; - } - require_pdp_update = true; - } - - /* The TEI on the RNC side might have changed, too */ - if (item->iuTransportAssociation && - item->iuTransportAssociation->present == RANAP_IuTransportAssociation_PR_gTP_TEI && - item->iuTransportAssociation->choice.gTP_TEI.buf && - item->iuTransportAssociation->choice.gTP_TEI.size >= 4) { - uint32_t tei = osmo_load32be(item->iuTransportAssociation->choice.gTP_TEI.buf); - LOGP(DRANAP, LOGL_DEBUG, "Updating TEID on RNC side from 0x%08x to 0x%08x\n", - pdp->lib->teid_own, tei); - pdp->lib->teid_own = tei; - require_pdp_update = true; - } - - if (require_pdp_update) - gtp_update_context(pdp->ggsn->gsn, pdp->lib, pdp, &pdp->lib->hisaddr0); - - if (pdp->state != PDP_STATE_CR_CONF) { - send_act_pdp_cont_acc(pdp); - pdp->state = PDP_STATE_CR_CONF; - } - return 0; - -} - -int sgsn_ranap_iu_event(struct ranap_ue_conn_ctx *ctx, enum ranap_iu_event_type type, void *data) -{ - struct sgsn_mm_ctx *mm; - int rc = -1; - - mm = sgsn_mm_ctx_by_ue_ctx(ctx); - -#define REQUIRE_MM \ - if (!mm) { \ - LOGIUP(ctx, LOGL_NOTICE, "Cannot find mm ctx for IU event %d\n", type); \ - return rc; \ - } - - switch (type) { - case RANAP_IU_EVENT_RAB_ASSIGN: - REQUIRE_MM - rc = sgsn_ranap_rab_ass_resp(mm, (RANAP_RAB_SetupOrModifiedItemIEs_t *)data); - break; - case RANAP_IU_EVENT_IU_RELEASE: - /* fall thru */ - case RANAP_IU_EVENT_LINK_INVALIDATED: - /* Clean up ranap_ue_conn_ctx here */ - if (mm) { - LOGMMCTXP(LOGL_INFO, mm, "IU release for imsi %s\n", mm->imsi); - osmo_fsm_inst_dispatch(mm->iu.mm_state_fsm, E_PMM_PS_CONN_RELEASE, NULL); - } else - LOGIUP(ctx, LOGL_INFO, "IU release\n"); - rc = 0; - break; - case RANAP_IU_EVENT_SECURITY_MODE_COMPLETE: - REQUIRE_MM - /* Continue authentication here */ - mm->iu.ue_ctx->integrity_active = 1; - - /* FIXME: remove gmm_authorize */ - if (mm->pending_req != GSM48_MT_GMM_ATTACH_REQ) - gsm48_gmm_authorize(mm); - else - osmo_fsm_inst_dispatch(mm->gmm_att_req.fsm, E_IU_SECURITY_CMD_COMPLETE, NULL); - break; - default: - if (mm) - LOGMMCTXP(LOGL_NOTICE, mm, "Unknown event received: %i\n", type); - else - LOGIUP(ctx, LOGL_NOTICE, "Unknown event received: %i\n", type); - rc = -1; - break; - } - return rc; -} - -int iu_rab_act_ps(uint8_t rab_id, struct sgsn_pdp_ctx *pdp) -{ - struct msgb *msg; - struct sgsn_mm_ctx *mm = pdp->mm; - struct ranap_ue_conn_ctx *uectx; - uint32_t ggsn_ip; - bool use_x213_nsap; - - uectx = mm->iu.ue_ctx; - use_x213_nsap = (uectx->rab_assign_addr_enc == RANAP_NSAP_ADDR_ENC_X213); - - /* Get the IP address for ggsn user plane */ - memcpy(&ggsn_ip, pdp->lib->gsnru.v, pdp->lib->gsnru.l); - ggsn_ip = htonl(ggsn_ip); - - LOGP(DRANAP, LOGL_DEBUG, "Assigning RAB: rab_id=%d, ggsn_ip=%x," - " teid_gn=%x, use_x213_nsap=%d\n", - rab_id, ggsn_ip, pdp->lib->teid_gn, use_x213_nsap); - - msg = ranap_new_msg_rab_assign_data(rab_id, ggsn_ip, - pdp->lib->teid_gn, use_x213_nsap); - msg->l2h = msg->data; - return ranap_iu_rab_act(uectx, msg); -} - - -/* Main entry point for incoming 04.08 GPRS messages from Iu */ -int gsm0408_gprs_rcvmsg_iu(struct msgb *msg, struct gprs_ra_id *ra_id, - uint16_t *sai) -{ - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); - uint8_t pdisc = gsm48_hdr_pdisc(gh); - struct sgsn_mm_ctx *mmctx; - int rc = -EINVAL; - - mmctx = sgsn_mm_ctx_by_ue_ctx(MSG_IU_UE_CTX(msg)); - if (mmctx) { - rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_SIG_IN]); - if (ra_id) - memcpy(&mmctx->ra, ra_id, sizeof(mmctx->ra)); - } - - /* MMCTX can be NULL */ - - switch (pdisc) { - case GSM48_PDISC_MM_GPRS: - rc = gsm0408_rcv_gmm(mmctx, msg, NULL, false); -#pragma message "set drop_cipherable arg for gsm0408_rcv_gmm() from IuPS?" - break; - case GSM48_PDISC_SM_GPRS: - rc = gsm0408_rcv_gsm(mmctx, msg, NULL); - break; - default: - LOGMMCTXP(LOGL_NOTICE, mmctx, - "Unknown GSM 04.08 discriminator 0x%02x: %s\n", - pdisc, osmo_hexdump((uint8_t *)gh, msgb_l3len(msg))); - /* FIXME: return status message */ - break; - } - - /* MMCTX can be invalid */ - - return rc; -} -#endif diff --git a/src/gprs/gprs_sgsn.c b/src/gprs/gprs_sgsn.c deleted file mode 100644 index 387c0d5a1..000000000 --- a/src/gprs/gprs_sgsn.c +++ /dev/null @@ -1,1017 +0,0 @@ -/* GPRS SGSN functionality */ - -/* (C) 2009 by Harald Welte - * - * 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 . - * - */ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include "../../bscconfig.h" - -#define GPRS_LLME_CHECK_TICK 30 - -extern struct sgsn_instance *sgsn; -extern void *tall_sgsn_ctx; - -LLIST_HEAD(sgsn_mm_ctxts); -LLIST_HEAD(sgsn_ggsn_ctxts); -LLIST_HEAD(sgsn_apn_ctxts); -LLIST_HEAD(sgsn_pdp_ctxts); - -const struct value_string sgsn_ran_type_names[] = { - { MM_CTX_T_GERAN_Gb, "GPRS/EDGE via Gb" }, - { MM_CTX_T_UTRAN_Iu, "UMTS via Iu" }, -#if 0 - { MM_CTX_T_GERAN_Iu, "GPRS/EDGE via Iu" }, -#endif - { 0, NULL } -}; - -static const struct rate_ctr_desc mmctx_ctr_description[] = { - { "sign:packets:in", "Signalling Messages ( In)" }, - { "sign:packets:out", "Signalling Messages (Out)" }, - { "udata:packets:in", "User Data Messages ( In)" }, - { "udata:packets:out", "User Data Messages (Out)" }, - { "udata:bytes:in", "User Data Bytes ( In)" }, - { "udata:bytes:out", "User Data Bytes (Out)" }, - { "pdp_ctx_act", "PDP Context Activations " }, - { "suspend", "SUSPEND Count " }, - { "paging:ps", "Paging Packet Switched " }, - { "paging:cs", "Paging Circuit Switched " }, - { "ra_update", "Routing Area Update " }, -}; - -static const struct rate_ctr_group_desc mmctx_ctrg_desc = { - .group_name_prefix = "sgsn:mmctx", - .group_description = "SGSN MM Context Statistics", - .num_ctr = ARRAY_SIZE(mmctx_ctr_description), - .ctr_desc = mmctx_ctr_description, - .class_id = OSMO_STATS_CLASS_SUBSCRIBER, -}; - -static const struct rate_ctr_desc pdpctx_ctr_description[] = { - { "udata:packets:in", "User Data Messages ( In)" }, - { "udata:packets:out", "User Data Messages (Out)" }, - { "udata:bytes:in", "User Data Bytes ( In)" }, - { "udata:bytes:out", "User Data Bytes (Out)" }, -}; - -static const struct rate_ctr_group_desc pdpctx_ctrg_desc = { - .group_name_prefix = "sgsn:pdpctx", - .group_description = "SGSN PDP Context Statistics", - .num_ctr = ARRAY_SIZE(pdpctx_ctr_description), - .ctr_desc = pdpctx_ctr_description, - .class_id = OSMO_STATS_CLASS_SUBSCRIBER, -}; - -static const struct rate_ctr_desc sgsn_ctr_description[] = { - { "llc:dl_bytes", "Count sent LLC bytes before giving it to the bssgp layer" }, - { "llc:ul_bytes", "Count successful received LLC bytes (encrypt & fcs correct)" }, - { "llc:dl_packets", "Count successful sent LLC packets before giving it to the bssgp layer" }, - { "llc:ul_packets", "Count successful received LLC packets (encrypt & fcs correct)" }, - { "gprs:attach_requested", "Received attach requests" }, - { "gprs:attach_accepted", "Sent attach accepts" }, - { "gprs:attach_rejected", "Sent attach rejects" }, - { "gprs:detach_requested", "Received detach requests" }, - { "gprs:detach_acked", "Sent detach acks" }, - { "gprs:routing_area_requested", "Received routing area requests" }, - { "gprs:routing_area_requested", "Sent routing area acks" }, - { "gprs:routing_area_requested", "Sent routing area rejects" }, - { "pdp:activate_requested", "Received activate requests" }, - { "pdp:activate_rejected", "Sent activate rejects" }, - { "pdp:activate_accepted", "Sent activate accepts" }, - { "pdp:request_activated", "unused" }, - { "pdp:request_activate_rejected", "unused" }, - { "pdp:modify_requested", "unused" }, - { "pdp:modify_accepted", "unused" }, - { "pdp:dl_deactivate_requested", "Sent deactivate requests" }, - { "pdp:dl_deactivate_accepted", "Sent deactivate accepted" }, - { "pdp:ul_deactivate_requested", "Received deactivate requests" }, - { "pdp:ul_deactivate_accepted", "Received deactivate accepts" }, -}; - -static const struct rate_ctr_group_desc sgsn_ctrg_desc = { - "sgsn", - "SGSN Overall Statistics", - OSMO_STATS_CLASS_GLOBAL, - ARRAY_SIZE(sgsn_ctr_description), - sgsn_ctr_description, -}; - -void sgsn_rate_ctr_init() { - sgsn->rate_ctrs = rate_ctr_group_alloc(tall_sgsn_ctx, &sgsn_ctrg_desc, 0); - OSMO_ASSERT(sgsn->rate_ctrs); -} - -/* look-up an SGSN MM context based on Iu UE context (struct ue_conn_ctx)*/ -struct sgsn_mm_ctx *sgsn_mm_ctx_by_ue_ctx(const void *uectx) -{ - struct sgsn_mm_ctx *ctx; - - llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { - if (ctx->ran_type == MM_CTX_T_UTRAN_Iu - && uectx == ctx->iu.ue_ctx) - return ctx; - } - - return NULL; -} - -/* look-up a SGSN MM context based on TLLI + RAI */ -struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli(uint32_t tlli, - const struct gprs_ra_id *raid) -{ - struct sgsn_mm_ctx *ctx; - - llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { - if ((tlli == ctx->gb.tlli || tlli == ctx->gb.tlli_new) && - gprs_ra_id_equals(raid, &ctx->ra)) - return ctx; - } - - return NULL; -} - -struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli_and_ptmsi(uint32_t tlli, - const struct gprs_ra_id *raid) -{ - struct sgsn_mm_ctx *ctx; - int tlli_type; - - /* TODO: Also check the P_TMSI signature to be safe. That signature - * should be different (at least with a sufficiently high probability) - * after SGSN restarts and for multiple SGSN instances. - */ - - tlli_type = gprs_tlli_type(tlli); - if (tlli_type != TLLI_FOREIGN && tlli_type != TLLI_LOCAL) - return NULL; - - llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { - if ((gprs_tmsi2tlli(ctx->p_tmsi, tlli_type) == tlli || - gprs_tmsi2tlli(ctx->p_tmsi_old, tlli_type) == tlli) && - gprs_ra_id_equals(raid, &ctx->ra)) - return ctx; - } - - return NULL; -} - -struct sgsn_mm_ctx *sgsn_mm_ctx_by_ptmsi(uint32_t p_tmsi) -{ - struct sgsn_mm_ctx *ctx; - - llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { - if (p_tmsi == ctx->p_tmsi || - (ctx->p_tmsi_old && ctx->p_tmsi_old == p_tmsi)) - return ctx; - } - return NULL; -} - -struct sgsn_mm_ctx *sgsn_mm_ctx_by_imsi(const char *imsi) -{ - struct sgsn_mm_ctx *ctx; - - llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { - if (!strcmp(imsi, ctx->imsi)) - return ctx; - } - return NULL; - -} - -/* Allocate a new SGSN MM context, generic part */ -struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t rate_ctr_id) -{ - struct sgsn_mm_ctx *ctx; - - ctx = talloc_zero(tall_sgsn_ctx, struct sgsn_mm_ctx); - if (!ctx) - return NULL; - - ctx->gmm_state = GMM_DEREGISTERED; - ctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL; - ctx->ctrg = rate_ctr_group_alloc(ctx, &mmctx_ctrg_desc, rate_ctr_id); - if (!ctx->ctrg) { - LOGMMCTXP(LOGL_ERROR, ctx, "Cannot allocate counter group\n"); - talloc_free(ctx); - return NULL; - } - ctx->gmm_att_req.fsm = osmo_fsm_inst_alloc(&gmm_attach_req_fsm, ctx, ctx, LOGL_DEBUG, "gb_gmm_req"); - INIT_LLIST_HEAD(&ctx->pdp_list); - - llist_add(&ctx->list, &sgsn_mm_ctxts); - - return ctx; -} -/* Allocate a new SGSN MM context for GERAN_Gb */ -struct sgsn_mm_ctx *sgsn_mm_ctx_alloc_gb(uint32_t tlli, - const struct gprs_ra_id *raid) -{ - struct sgsn_mm_ctx *ctx; - char buf[32]; - - ctx = sgsn_mm_ctx_alloc(tlli); - if (!ctx) - return NULL; - - memcpy(&ctx->ra, raid, sizeof(ctx->ra)); - ctx->ran_type = MM_CTX_T_GERAN_Gb; - ctx->gb.tlli = tlli; - ctx->ciph_algo = sgsn->cfg.cipher; - snprintf(buf, sizeof(buf), "%" PRIu32, tlli); - ctx->gb.mm_state_fsm = osmo_fsm_inst_alloc(&mm_state_gb_fsm, ctx, ctx, LOGL_DEBUG, buf); - - LOGMMCTXP(LOGL_DEBUG, ctx, "Allocated with %s cipher.\n", - get_value_string(gprs_cipher_names, ctx->ciph_algo)); - return ctx; -} - -/* Allocate a new SGSN MM context for UTRAN_Iu */ -struct sgsn_mm_ctx *sgsn_mm_ctx_alloc_iu(void *uectx) -{ -#if BUILD_IU - char buf[32]; - struct sgsn_mm_ctx *ctx; - struct ranap_ue_conn_ctx *ue_ctx = uectx; - - ctx = sgsn_mm_ctx_alloc(ue_ctx->conn_id); - if (!ctx) - return NULL; - - /* Need to get RAID from IU conn */ - ctx->ra = ue_ctx->ra_id; - ctx->ran_type = MM_CTX_T_UTRAN_Iu; - ctx->iu.ue_ctx = ue_ctx; - ctx->iu.ue_ctx->rab_assign_addr_enc = sgsn->cfg.iu.rab_assign_addr_enc; - ctx->iu.new_key = 1; - snprintf(buf, sizeof(buf), "%" PRIu32, ue_ctx->conn_id); - ctx->iu.mm_state_fsm = osmo_fsm_inst_alloc(&mm_state_iu_fsm, ctx, ctx, LOGL_DEBUG, buf); - - - return ctx; -#else - return NULL; -#endif -} - - -/* this is a hard _free_ function, it doesn't clean up the PDP contexts - * in libgtp! */ -static void sgsn_mm_ctx_free(struct sgsn_mm_ctx *mm) -{ - struct sgsn_pdp_ctx *pdp, *pdp2; - - /* Unlink from global list of MM contexts */ - llist_del(&mm->list); - - /* Free all PDP contexts */ - llist_for_each_entry_safe(pdp, pdp2, &mm->pdp_list, list) - sgsn_pdp_ctx_free(pdp); - - rate_ctr_group_free(mm->ctrg); - - talloc_free(mm); -} - -void sgsn_mm_ctx_cleanup_free(struct sgsn_mm_ctx *mm) -{ - struct gprs_llc_llme *llme = NULL; - struct sgsn_pdp_ctx *pdp, *pdp2; - struct sgsn_signal_data sig_data; - - if (mm->ran_type == MM_CTX_T_GERAN_Gb) - llme = mm->gb.llme; - else - OSMO_ASSERT(mm->gb.llme == NULL); - - /* Forget about ongoing look-ups */ - if (mm->ggsn_lookup) { - LOGMMCTXP(LOGL_NOTICE, mm, - "Cleaning mmctx with on-going query.\n"); - mm->ggsn_lookup->mmctx = NULL; - mm->ggsn_lookup = NULL; - } - - /* delete all existing PDP contexts for this MS */ - llist_for_each_entry_safe(pdp, pdp2, &mm->pdp_list, list) { - LOGMMCTXP(LOGL_NOTICE, mm, - "Dropping PDP context for NSAPI=%u\n", pdp->nsapi); - sgsn_pdp_ctx_terminate(pdp); - } - - if (osmo_timer_pending(&mm->timer)) { - LOGMMCTXP(LOGL_INFO, mm, "Cancelling MM timer %u\n", mm->T); - osmo_timer_del(&mm->timer); - } - - memset(&sig_data, 0, sizeof(sig_data)); - sig_data.mm = mm; - osmo_signal_dispatch(SS_SGSN, S_SGSN_MM_FREE, &sig_data); - - - /* Detach from subscriber which is possibly freed then */ - if (mm->subscr) { - struct gprs_subscr *subscr = gprs_subscr_get(mm->subscr); - gprs_subscr_cleanup(subscr); - gprs_subscr_put(subscr); - } - - if (mm->gmm_att_req.fsm) - gmm_att_req_free(mm); - if (mm->gb.mm_state_fsm) - osmo_fsm_inst_free(mm->gb.mm_state_fsm); - if (mm->iu.mm_state_fsm) - osmo_fsm_inst_free(mm->iu.mm_state_fsm); - - sgsn_mm_ctx_free(mm); - mm = NULL; - - if (llme) { - /* TLLI unassignment, must be called after sgsn_mm_ctx_free */ - if (gprs_llgmm_unassign(llme) < 0) - LOGMMCTXP(LOGL_ERROR, mm, "gprs_llgmm_unassign failed, llme not freed!\n"); - } -} - - -/* look up PDP context by MM context and NSAPI */ -struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_nsapi(const struct sgsn_mm_ctx *mm, - uint8_t nsapi) -{ - struct sgsn_pdp_ctx *pdp; - - llist_for_each_entry(pdp, &mm->pdp_list, list) { - if (pdp->nsapi == nsapi) - return pdp; - } - return NULL; -} - -/* look up PDP context by MM context and transaction ID */ -struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_tid(const struct sgsn_mm_ctx *mm, - uint8_t tid) -{ - struct sgsn_pdp_ctx *pdp; - - llist_for_each_entry(pdp, &mm->pdp_list, list) { - if (pdp->ti == tid) - return pdp; - } - return NULL; -} - -/* you don't want to use this directly, call sgsn_create_pdp_ctx() */ -struct sgsn_pdp_ctx *sgsn_pdp_ctx_alloc(struct sgsn_mm_ctx *mm, - struct sgsn_ggsn_ctx *ggsn, - uint8_t nsapi) -{ - struct sgsn_pdp_ctx *pdp; - - pdp = sgsn_pdp_ctx_by_nsapi(mm, nsapi); - if (pdp) - return NULL; - - pdp = talloc_zero(tall_sgsn_ctx, struct sgsn_pdp_ctx); - if (!pdp) - return NULL; - - pdp->mm = mm; - pdp->ggsn = ggsn; - pdp->nsapi = nsapi; - pdp->ctrg = rate_ctr_group_alloc(pdp, &pdpctx_ctrg_desc, nsapi); - if (!pdp->ctrg) { - LOGPDPCTXP(LOGL_ERROR, pdp, "Error allocation counter group\n"); - talloc_free(pdp); - return NULL; - } - llist_add(&pdp->list, &mm->pdp_list); - sgsn_ggsn_ctx_add_pdp(pdp->ggsn, pdp); - llist_add(&pdp->g_list, &sgsn_pdp_ctxts); - - return pdp; -} - -/* - * This function will not trigger any GSM DEACT PDP ACK messages, so you - * probably want to call sgsn_delete_pdp_ctx() instead if the connection - * isn't detached already. - */ -void sgsn_pdp_ctx_terminate(struct sgsn_pdp_ctx *pdp) -{ - struct sgsn_signal_data sig_data; - - OSMO_ASSERT(pdp->mm != NULL); - - /* There might still be pending callbacks in libgtp. So the parts of - * this object relevant to GTP need to remain intact in this case. */ - - LOGPDPCTXP(LOGL_INFO, pdp, "Forcing release of PDP context\n"); - - if (pdp->mm->ran_type == MM_CTX_T_GERAN_Gb) { - /* Force the deactivation of the SNDCP layer */ - sndcp_sm_deactivate_ind(&pdp->mm->gb.llme->lle[pdp->sapi], pdp->nsapi); - } - - memset(&sig_data, 0, sizeof(sig_data)); - sig_data.pdp = pdp; - osmo_signal_dispatch(SS_SGSN, S_SGSN_PDP_TERMINATE, &sig_data); - - /* Detach from MM context */ - pdp_ctx_detach_mm_ctx(pdp); - if (pdp->ggsn) - sgsn_delete_pdp_ctx(pdp); -} - -/* - * Don't call this function directly unless you know what you are doing. - * In normal conditions use sgsn_delete_pdp_ctx and in unspecified or - * implementation dependent abnormal ones sgsn_pdp_ctx_terminate. - */ -void sgsn_pdp_ctx_free(struct sgsn_pdp_ctx *pdp) -{ - struct sgsn_signal_data sig_data; - - memset(&sig_data, 0, sizeof(sig_data)); - sig_data.pdp = pdp; - osmo_signal_dispatch(SS_SGSN, S_SGSN_PDP_FREE, &sig_data); - - rate_ctr_group_free(pdp->ctrg); - if (pdp->mm) - llist_del(&pdp->list); - if (pdp->ggsn) - sgsn_ggsn_ctx_remove_pdp(pdp->ggsn, pdp); - llist_del(&pdp->g_list); - - /* _if_ we still have a library handle, at least set it to NULL - * to avoid any dereferences of the now-deleted PDP context from - * sgsn_libgtp:cb_data_ind() */ - if (pdp->lib) { - struct pdp_t *lib = pdp->lib; - LOGPDPCTXP(LOGL_NOTICE, pdp, "freeing PDP context that still " - "has a libgtp handle attached to it, this shouldn't " - "happen!\n"); - osmo_generate_backtrace(); - lib->priv = NULL; - } - - talloc_free(pdp); -} - -void sgsn_ggsn_ctx_check_echo_timer(struct sgsn_ggsn_ctx *ggc) -{ - bool pending = osmo_timer_pending(&ggc->echo_timer); - - /* Only enable if allowed by policy and at least 1 pdp ctx exists against ggsn */ - if (!llist_empty(&ggc->pdp_list) && ggc->echo_interval) { - if (!pending) - osmo_timer_schedule(&ggc->echo_timer, ggc->echo_interval, 0); - } else { - if (pending) - osmo_timer_del(&ggc->echo_timer); - } -} - -/* GGSN contexts */ -static void echo_timer_cb(void *data) -{ - struct sgsn_ggsn_ctx *ggc = (struct sgsn_ggsn_ctx *) data; - sgsn_ggsn_echo_req(ggc); - osmo_timer_schedule(&ggc->echo_timer, ggc->echo_interval, 0); -} - -struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_alloc(uint32_t id) -{ - struct sgsn_ggsn_ctx *ggc; - - ggc = talloc_zero(tall_sgsn_ctx, struct sgsn_ggsn_ctx); - if (!ggc) - return NULL; - - ggc->id = id; - ggc->gtp_version = 1; - ggc->remote_restart_ctr = -1; - /* if we are called from config file parse, this gsn doesn't exist yet */ - ggc->gsn = sgsn->gsn; - INIT_LLIST_HEAD(&ggc->pdp_list); - osmo_timer_setup(&ggc->echo_timer, echo_timer_cb, ggc); - llist_add(&ggc->list, &sgsn_ggsn_ctxts); - - return ggc; -} - -void sgsn_ggsn_ctx_free(struct sgsn_ggsn_ctx *ggc) -{ - OSMO_ASSERT(llist_empty(&ggc->pdp_list)); - llist_del(&ggc->list); - talloc_free(ggc); -} - -struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_id(uint32_t id) -{ - struct sgsn_ggsn_ctx *ggc; - - llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) { - if (id == ggc->id) - return ggc; - } - return NULL; -} - -struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_addr(struct in_addr *addr) -{ - struct sgsn_ggsn_ctx *ggc; - - llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) { - if (!memcmp(addr, &ggc->remote_addr, sizeof(*addr))) - return ggc; - } - return NULL; -} - - -struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_find_alloc(uint32_t id) -{ - struct sgsn_ggsn_ctx *ggc; - - ggc = sgsn_ggsn_ctx_by_id(id); - if (!ggc) - ggc = sgsn_ggsn_ctx_alloc(id); - return ggc; -} - -/* APN contexts */ - -static struct apn_ctx *sgsn_apn_ctx_alloc(const char *ap_name, const char *imsi_prefix) -{ - struct apn_ctx *actx; - - actx = talloc_zero(tall_sgsn_ctx, struct apn_ctx); - if (!actx) - return NULL; - actx->name = talloc_strdup(actx, ap_name); - actx->imsi_prefix = talloc_strdup(actx, imsi_prefix); - - llist_add_tail(&actx->list, &sgsn_apn_ctxts); - - return actx; -} - -void sgsn_apn_ctx_free(struct apn_ctx *actx) -{ - llist_del(&actx->list); - talloc_free(actx); -} - -struct apn_ctx *sgsn_apn_ctx_match(const char *name, const char *imsi) -{ - struct apn_ctx *actx; - struct apn_ctx *found_actx = NULL; - size_t imsi_prio = 0; - size_t name_prio = 0; - size_t name_req_len = strlen(name); - - llist_for_each_entry(actx, &sgsn_apn_ctxts, list) { - size_t name_ref_len, imsi_ref_len; - const char *name_ref_start, *name_match_start; - - imsi_ref_len = strlen(actx->imsi_prefix); - if (strncmp(actx->imsi_prefix, imsi, imsi_ref_len) != 0) - continue; - - if (imsi_ref_len < imsi_prio) - continue; - - /* IMSI matches */ - - name_ref_start = &actx->name[0]; - if (name_ref_start[0] == '*') { - /* Suffix match */ - name_ref_start += 1; - name_ref_len = strlen(name_ref_start); - if (name_ref_len > name_req_len) - continue; - } else { - name_ref_len = strlen(name_ref_start); - if (name_ref_len != name_req_len) - continue; - } - - name_match_start = name + (name_req_len - name_ref_len); - if (strcasecmp(name_match_start, name_ref_start) != 0) - continue; - - /* IMSI and name match */ - - if (imsi_ref_len == imsi_prio && name_ref_len < name_prio) - /* Lower priority, skip */ - continue; - - imsi_prio = imsi_ref_len; - name_prio = name_ref_len; - found_actx = actx; - } - return found_actx; -} - -struct apn_ctx *sgsn_apn_ctx_by_name(const char *name, const char *imsi_prefix) -{ - struct apn_ctx *actx; - - llist_for_each_entry(actx, &sgsn_apn_ctxts, list) { - if (strcasecmp(name, actx->name) == 0 && - strcasecmp(imsi_prefix, actx->imsi_prefix) == 0) - return actx; - } - return NULL; -} - -struct apn_ctx *sgsn_apn_ctx_find_alloc(const char *name, const char *imsi_prefix) -{ - struct apn_ctx *actx; - - actx = sgsn_apn_ctx_by_name(name, imsi_prefix); - if (!actx) - actx = sgsn_apn_ctx_alloc(name, imsi_prefix); - - return actx; -} - -uint32_t sgsn_alloc_ptmsi(void) -{ - struct sgsn_mm_ctx *mm; - uint32_t ptmsi = 0xdeadbeef; - int max_retries = 100, rc = 0; - -restart: - rc = osmo_get_rand_id((uint8_t *) &ptmsi, sizeof(ptmsi)); - if (rc < 0) - goto failed; - - /* Enforce that the 2 MSB are set without loosing the distance between - * identical values. Since rand() has no duplicate values within a - * period (because the size of the state is the same like the size of - * the random value), this leads to a distance of period/4 when the - * distribution of the 2 MSB is uniform. This approach fails with a - * probability of (3/4)^max_retries, only 1% of the approaches will - * need more than 16 numbers (even distribution assumed). - * - * Alternatively, a freeze list could be used if another PRNG is used - * or when this approach proves to be not sufficient. - */ - if (ptmsi >= GSM23003_TMSI_SGSN_MASK) { - if (!max_retries--) - goto failed; - goto restart; - } - ptmsi |= GSM23003_TMSI_SGSN_MASK; - - if (ptmsi == GSM_RESERVED_TMSI) { - if (!max_retries--) - goto failed; - goto restart; - } - - llist_for_each_entry(mm, &sgsn_mm_ctxts, list) { - if (mm->p_tmsi == ptmsi) { - if (!max_retries--) - goto failed; - goto restart; - } - } - - return ptmsi; - -failed: - LOGP(DGPRS, LOGL_ERROR, "Failed to allocate a P-TMSI: %d (%s)\n", rc, strerror(-rc)); - return GSM_RESERVED_TMSI; -} - -void sgsn_ggsn_ctx_drop_pdp(struct sgsn_pdp_ctx *pctx) -{ - /* the MM context can be deleted while the GGSN is not reachable or - * if has been crashed. */ - if (pctx->mm && pctx->mm->gmm_state == GMM_REGISTERED_NORMAL) { - gsm48_tx_gsm_deact_pdp_req(pctx, GSM_CAUSE_NET_FAIL, true); - sgsn_ggsn_ctx_remove_pdp(pctx->ggsn, pctx); - } else { - /* FIXME: GPRS paging in case MS is SUSPENDED */ - LOGPDPCTXP(LOGL_NOTICE, pctx, "Hard-dropping PDP ctx due to GGSN " - "recovery\n"); - /* FIXME: how to tell this to libgtp? */ - sgsn_pdp_ctx_free(pctx); - } -} - -/* 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. */ -int sgsn_ggsn_ctx_drop_all_pdp_except(struct sgsn_ggsn_ctx *ggsn, struct sgsn_pdp_ctx *except) -{ - int num = 0; - - struct sgsn_pdp_ctx *pdp, *pdp2; - llist_for_each_entry_safe(pdp, pdp2, &ggsn->pdp_list, ggsn_list) { - if (pdp == except) - continue; - sgsn_ggsn_ctx_drop_pdp(pdp); - num++; - } - - return num; -} - -int sgsn_ggsn_ctx_drop_all_pdp(struct sgsn_ggsn_ctx *ggsn) -{ - return sgsn_ggsn_ctx_drop_all_pdp_except(ggsn, NULL); -} - -void sgsn_ggsn_ctx_add_pdp(struct sgsn_ggsn_ctx *ggc, struct sgsn_pdp_ctx *pdp) -{ - llist_add(&pdp->ggsn_list, &ggc->pdp_list); - sgsn_ggsn_ctx_check_echo_timer(ggc); -} -void sgsn_ggsn_ctx_remove_pdp(struct sgsn_ggsn_ctx *ggc, struct sgsn_pdp_ctx *pdp) -{ - llist_del(&pdp->ggsn_list); - sgsn_ggsn_ctx_check_echo_timer(ggc); - if (pdp->destroy_ggsn) - sgsn_ggsn_ctx_free(pdp->ggsn); - pdp->ggsn = NULL; - /* Drop references to libgtp since the conn is down */ - if (pdp->lib) - pdp_freepdp(pdp->lib); - pdp->lib = NULL; -} - -void sgsn_update_subscriber_data(struct sgsn_mm_ctx *mmctx) -{ - OSMO_ASSERT(mmctx != NULL); - LOGMMCTXP(LOGL_INFO, mmctx, "Subscriber data update\n"); - - sgsn_auth_update(mmctx); -} - -static void insert_extra(struct tlv_parsed *tp, - struct sgsn_subscriber_data *data, - struct sgsn_subscriber_pdp_data *pdp) -{ - tp->lv[OSMO_IE_GSM_SUB_QOS].len = pdp->qos_subscribed_len; - tp->lv[OSMO_IE_GSM_SUB_QOS].val = pdp->qos_subscribed; - - /* Prefer PDP charging characteristics of per subscriber one */ - if (pdp->has_pdp_charg) { - tp->lv[OSMO_IE_GSM_CHARG_CHAR].len = sizeof(pdp->pdp_charg); - tp->lv[OSMO_IE_GSM_CHARG_CHAR].val = &pdp->pdp_charg[0]; - } else if (data->has_pdp_charg) { - tp->lv[OSMO_IE_GSM_CHARG_CHAR].len = sizeof(data->pdp_charg); - tp->lv[OSMO_IE_GSM_CHARG_CHAR].val = &data->pdp_charg[0]; - } -} - -/** - * The tlv_parsed tp parameter will be modified to insert a - * OSMO_IE_GSM_SUB_QOS in case the data is available in the - * PDP context handling. - */ -struct sgsn_ggsn_ctx *sgsn_mm_ctx_find_ggsn_ctx(struct sgsn_mm_ctx *mmctx, - struct tlv_parsed *tp, - enum gsm48_gsm_cause *gsm_cause, - char *out_apn_str) -{ - char req_apn_str[GSM_APN_LENGTH] = {0}; - const struct apn_ctx *apn_ctx = NULL; - const char *selected_apn_str = NULL; - struct sgsn_subscriber_pdp_data *pdp; - struct sgsn_ggsn_ctx *ggsn = NULL; - int allow_any_apn = 0; - - out_apn_str[0] = '\0'; - - if (TLVP_PRESENT(tp, GSM48_IE_GSM_APN)) { - if (TLVP_LEN(tp, GSM48_IE_GSM_APN) >= GSM_APN_LENGTH - 1) { - LOGMMCTXP(LOGL_ERROR, mmctx, "APN IE too long\n"); - *gsm_cause = GSM_CAUSE_INV_MAND_INFO; - return NULL; - } - - osmo_apn_to_str(req_apn_str, - TLVP_VAL(tp, GSM48_IE_GSM_APN), - TLVP_LEN(tp, GSM48_IE_GSM_APN)); - - if (strcmp(req_apn_str, "*") == 0) - req_apn_str[0] = 0; - } - - if (mmctx->subscr == NULL) - allow_any_apn = 1; - - if (strlen(req_apn_str) == 0 && !allow_any_apn) { - /* No specific APN requested, check for an APN that is both - * granted and configured */ - - llist_for_each_entry(pdp, &mmctx->subscr->sgsn_data->pdp_list, list) { - if (strcmp(pdp->apn_str, "*") == 0) - { - allow_any_apn = 1; - selected_apn_str = ""; - insert_extra(tp, mmctx->subscr->sgsn_data, pdp); - continue; - } - if (!llist_empty(&sgsn_apn_ctxts)) { - apn_ctx = sgsn_apn_ctx_match(req_apn_str, mmctx->imsi); - /* Not configured */ - if (apn_ctx == NULL) - continue; - } - insert_extra(tp, mmctx->subscr->sgsn_data, pdp); - selected_apn_str = pdp->apn_str; - break; - } - } else if (!allow_any_apn) { - /* Check whether the given APN is granted */ - llist_for_each_entry(pdp, &mmctx->subscr->sgsn_data->pdp_list, list) { - if (strcmp(pdp->apn_str, "*") == 0) { - insert_extra(tp, mmctx->subscr->sgsn_data, pdp); - selected_apn_str = req_apn_str; - allow_any_apn = 1; - continue; - } - if (strcasecmp(pdp->apn_str, req_apn_str) == 0) { - insert_extra(tp, mmctx->subscr->sgsn_data, pdp); - selected_apn_str = req_apn_str; - break; - } - } - } else if (strlen(req_apn_str) != 0) { - /* Any APN is allowed */ - selected_apn_str = req_apn_str; - } else { - /* Prefer the GGSN associated with the wildcard APN */ - selected_apn_str = ""; - } - - if (!allow_any_apn && selected_apn_str == NULL) { - /* Access not granted */ - LOGMMCTXP(LOGL_NOTICE, mmctx, - "The requested APN '%s' is not allowed\n", - req_apn_str); - *gsm_cause = GSM_CAUSE_REQ_SERV_OPT_NOTSUB; - return NULL; - } - - /* copy the selected apn_str */ - if (selected_apn_str) - strcpy(out_apn_str, selected_apn_str); - else - out_apn_str[0] = '\0'; - - if (apn_ctx == NULL && selected_apn_str) - apn_ctx = sgsn_apn_ctx_match(selected_apn_str, mmctx->imsi); - - if (apn_ctx != NULL) { - ggsn = apn_ctx->ggsn; - } else if (llist_empty(&sgsn_apn_ctxts)) { - /* No configuration -> use GGSN 0 */ - ggsn = sgsn_ggsn_ctx_by_id(0); - } else if (allow_any_apn && - (selected_apn_str == NULL || strlen(selected_apn_str) == 0)) { - /* No APN given and no default configuration -> Use GGSN 0 */ - ggsn = sgsn_ggsn_ctx_by_id(0); - } else { - /* No matching configuration found */ - LOGMMCTXP(LOGL_NOTICE, mmctx, - "The selected APN '%s' has not been configured\n", - selected_apn_str); - *gsm_cause = GSM_CAUSE_MISSING_APN; - return NULL; - } - - if (!ggsn) { - LOGMMCTXP(LOGL_NOTICE, mmctx, - "No static GGSN configured. Selected APN '%s'\n", - selected_apn_str); - *gsm_cause = GSM_CAUSE_MISSING_APN; - return NULL; - } - - LOGMMCTXP(LOGL_INFO, mmctx, - "Found GGSN %d for APN '%s' (requested '%s')\n", - ggsn->id, selected_apn_str ? selected_apn_str : "---", - req_apn_str); - - return ggsn; -} - -static void sgsn_llme_cleanup_free(struct gprs_llc_llme *llme) -{ - struct sgsn_mm_ctx *mmctx = NULL; - - llist_for_each_entry(mmctx, &sgsn_mm_ctxts, list) { - if (llme == mmctx->gb.llme) { - gsm0408_gprs_access_cancelled(mmctx, SGSN_ERROR_CAUSE_NONE); - return; - } - } - - /* No MM context found */ - LOGP(DGPRS, LOGL_INFO, "Deleting orphaned LLME, TLLI 0x%08x\n", - llme->tlli); - gprs_llgmm_unassign(llme); -} - -static void sgsn_llme_check_cb(void *data_) -{ - struct gprs_llc_llme *llme, *llme_tmp; - struct timespec now_tp; - time_t now, age; - time_t max_age = gprs_max_time_to_idle(); - - int rc; - - rc = osmo_clock_gettime(CLOCK_MONOTONIC, &now_tp); - OSMO_ASSERT(rc >= 0); - now = now_tp.tv_sec; - - LOGP(DGPRS, LOGL_DEBUG, - "Checking for inactive LLMEs, time = %u\n", (unsigned)now); - - llist_for_each_entry_safe(llme, llme_tmp, &gprs_llc_llmes, list) { - if (llme->age_timestamp == GPRS_LLME_RESET_AGE) - llme->age_timestamp = now; - - age = now - llme->age_timestamp; - - if (age > max_age || age < 0) { - LOGP(DGPRS, LOGL_INFO, - "Inactivity timeout for TLLI 0x%08x, age %d\n", - llme->tlli, (int)age); - sgsn_llme_cleanup_free(llme); - } - } - - osmo_timer_schedule(&sgsn->llme_timer, GPRS_LLME_CHECK_TICK, 0); -} - -struct sgsn_instance *sgsn_instance_alloc(void *talloc_ctx) -{ - struct sgsn_instance *inst; - inst = talloc_zero(talloc_ctx, struct sgsn_instance); - inst->cfg.gtp_statedir = talloc_strdup(inst, "./"); - inst->cfg.auth_policy = SGSN_AUTH_POLICY_CLOSED; - inst->cfg.require_authentication = true; /* only applies if auth_policy is REMOTE */ - inst->cfg.gsup_server_port = OSMO_GSUP_PORT; - return inst; -} - -void sgsn_inst_init(struct sgsn_instance *sgsn) -{ - osmo_timer_setup(&sgsn->llme_timer, sgsn_llme_check_cb, NULL); - osmo_timer_schedule(&sgsn->llme_timer, GPRS_LLME_CHECK_TICK, 0); -} diff --git a/src/gprs/gprs_sndcp.c b/src/gprs/gprs_sndcp.c deleted file mode 100644 index d9aa1e576..000000000 --- a/src/gprs/gprs_sndcp.c +++ /dev/null @@ -1,1265 +0,0 @@ -/* GPRS SNDCP protocol implementation as per 3GPP TS 04.65 */ - -/* (C) 2010 by Harald Welte - * (C) 2010 by On-Waves - * - * 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 . - * - */ - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DEBUG_IP_PACKETS 0 /* 0=Disabled, 1=Enabled */ - -#if DEBUG_IP_PACKETS == 1 -/* Calculate TCP/IP checksum */ -static uint16_t calc_ip_csum(uint8_t *data, int len) -{ - int i; - uint32_t accumulator = 0; - uint16_t *pointer = (uint16_t *) data; - - for (i = len; i > 1; i -= 2) { - accumulator += *pointer; - pointer++; - } - - if (len % 2) - accumulator += *pointer; - - accumulator = (accumulator & 0xffff) + ((accumulator >> 16) & 0xffff); - accumulator += (accumulator >> 16) & 0xffff; - return (~accumulator); -} - -/* Calculate TCP/IP checksum */ -static uint16_t calc_tcpip_csum(const void *ctx, uint8_t *packet, int len) -{ - uint8_t *buf; - uint16_t csum; - - buf = talloc_zero_size(ctx, len); - memset(buf, 0, len); - memcpy(buf, packet + 12, 8); - buf[9] = packet[9]; - buf[11] = (len - 20) & 0xFF; - buf[10] = (len - 20) >> 8 & 0xFF; - memcpy(buf + 12, packet + 20, len - 20); - csum = calc_ip_csum(buf, len - 20 + 12); - talloc_free(buf); - return csum; -} - -/* Show some ip packet details */ -static void debug_ip_packet(uint8_t *data, int len, int dir, char *info) -{ - uint8_t tcp_flags; - char flags_debugmsg[256]; - int len_short; - static unsigned int packet_count = 0; - static unsigned int tcp_csum_err_count = 0; - static unsigned int ip_csum_err_count = 0; - - packet_count++; - - if (len > 80) - len_short = 80; - else - len_short = len; - - if (dir) - DEBUGP(DSNDCP, "%s: MS => SGSN: %s\n", info, - osmo_hexdump_nospc(data, len_short)); - else - DEBUGP(DSNDCP, "%s: MS <= SGSN: %s\n", info, - osmo_hexdump_nospc(data, len_short)); - - DEBUGP(DSNDCP, "%s: Length.: %d\n", info, len); - DEBUGP(DSNDCP, "%s: NO.: %d\n", info, packet_count); - - if (len < 20) { - DEBUGP(DSNDCP, "%s: Error: Short IP packet!\n", info); - return; - } - - if (calc_ip_csum(data, 20) != 0) { - DEBUGP(DSNDCP, "%s: Bad IP-Header checksum!\n", info); - ip_csum_err_count++; - } else - DEBUGP(DSNDCP, "%s: IP-Header checksum ok.\n", info); - - if (data[9] == 0x06) { - if (len < 40) { - DEBUGP(DSNDCP, "%s: Error: Short TCP packet!\n", info); - return; - } - - DEBUGP(DSNDCP, "%s: Protocol type: TCP\n", info); - tcp_flags = data[33]; - - if (calc_tcpip_csum(NULL, data, len) != 0) { - DEBUGP(DSNDCP, "%s: Bad TCP checksum!\n", info); - tcp_csum_err_count++; - } else - DEBUGP(DSNDCP, "%s: TCP checksum ok.\n", info); - - memset(flags_debugmsg, 0, sizeof(flags_debugmsg)); - if (tcp_flags & 1) - strcat(flags_debugmsg, "FIN "); - if (tcp_flags & 2) - strcat(flags_debugmsg, "SYN "); - if (tcp_flags & 4) - strcat(flags_debugmsg, "RST "); - if (tcp_flags & 8) - strcat(flags_debugmsg, "PSH "); - if (tcp_flags & 16) - strcat(flags_debugmsg, "ACK "); - if (tcp_flags & 32) - strcat(flags_debugmsg, "URG "); - DEBUGP(DSNDCP, "%s: FLAGS: %s\n", info, flags_debugmsg); - } else if (data[9] == 0x11) { - DEBUGP(DSNDCP, "%s: Protocol type: UDP\n", info); - } else { - DEBUGP(DSNDCP, "%s: Protocol type: (%02x)\n", info, data[9]); - } - - DEBUGP(DSNDCP, "%s: IP-Header checksum errors: %d\n", info, - ip_csum_err_count); - DEBUGP(DSNDCP, "%s: TCP-Checksum errors: %d\n", info, - tcp_csum_err_count); -} -#endif - -/* Chapter 7.2: SN-PDU Formats */ -struct sndcp_common_hdr { - /* octet 1 */ - uint8_t nsapi:4; - uint8_t more:1; - uint8_t type:1; - uint8_t first:1; - uint8_t spare:1; -} __attribute__((packed)); - -/* PCOMP / DCOMP only exist in first fragment */ -struct sndcp_comp_hdr { - /* octet 2 */ - uint8_t pcomp:4; - uint8_t dcomp:4; -} __attribute__((packed)); - -struct sndcp_udata_hdr { - /* octet 3 */ - uint8_t npdu_high:4; - uint8_t seg_nr:4; - /* octet 4 */ - uint8_t npdu_low; -} __attribute__((packed)); - - -static void *tall_sndcp_ctx; - -/* A fragment queue entry, containing one framgent of a N-PDU */ -struct defrag_queue_entry { - struct llist_head list; - /* segment number of this fragment */ - uint32_t seg_nr; - /* length of the data area of this fragment */ - uint32_t data_len; - /* pointer to the data of this fragment */ - uint8_t *data; -}; - -LLIST_HEAD(gprs_sndcp_entities); - -/* Check if any compression parameters are set in the sgsn configuration */ -static inline int any_pcomp_or_dcomp_active(struct sgsn_instance *sgsn) { - if (sgsn->cfg.pcomp_rfc1144.active || sgsn->cfg.pcomp_rfc1144.passive || - sgsn->cfg.dcomp_v42bis.active || sgsn->cfg.dcomp_v42bis.passive) - return true; - else - return false; -} - -/* Enqueue a fragment into the defragment queue */ -static int defrag_enqueue(struct gprs_sndcp_entity *sne, uint8_t seg_nr, - uint8_t *data, uint32_t data_len) -{ - struct defrag_queue_entry *dqe; - - dqe = talloc_zero(tall_sndcp_ctx, struct defrag_queue_entry); - if (!dqe) - return -ENOMEM; - dqe->data = talloc_zero_size(dqe, data_len); - if (!dqe->data) { - talloc_free(dqe); - return -ENOMEM; - } - dqe->seg_nr = seg_nr; - dqe->data_len = data_len; - - llist_add(&dqe->list, &sne->defrag.frag_list); - - if (seg_nr > sne->defrag.highest_seg) - sne->defrag.highest_seg = seg_nr; - - sne->defrag.seg_have |= (1 << seg_nr); - sne->defrag.tot_len += data_len; - - memcpy(dqe->data, data, data_len); - - return 0; -} - -/* return if we have all segments of this N-PDU */ -static int defrag_have_all_segments(struct gprs_sndcp_entity *sne) -{ - uint32_t seg_needed = 0; - unsigned int i; - - /* create a bitmask of needed segments */ - for (i = 0; i <= sne->defrag.highest_seg; i++) - seg_needed |= (1 << i); - - if (seg_needed == sne->defrag.seg_have) - return 1; - - return 0; -} - -static struct defrag_queue_entry *defrag_get_seg(struct gprs_sndcp_entity *sne, - uint32_t seg_nr) -{ - struct defrag_queue_entry *dqe; - - llist_for_each_entry(dqe, &sne->defrag.frag_list, list) { - if (dqe->seg_nr == seg_nr) { - llist_del(&dqe->list); - return dqe; - } - } - return NULL; -} - -/* Perform actual defragmentation and create an output packet */ -static int defrag_segments(struct gprs_sndcp_entity *sne) -{ - struct msgb *msg; - unsigned int seg_nr; - uint8_t *npdu; - int npdu_len; - int rc; - uint8_t *expnd = NULL; - - LOGP(DSNDCP, LOGL_DEBUG, "TLLI=0x%08x NSAPI=%u: Defragment output PDU %u " - "num_seg=%u tot_len=%u\n", sne->lle->llme->tlli, sne->nsapi, - sne->defrag.npdu, sne->defrag.highest_seg, sne->defrag.tot_len); - msg = msgb_alloc_headroom(sne->defrag.tot_len+256, 128, "SNDCP Defrag"); - if (!msg) - return -ENOMEM; - - /* FIXME: message headers + identifiers */ - - npdu = msg->data; - - for (seg_nr = 0; seg_nr <= sne->defrag.highest_seg; seg_nr++) { - struct defrag_queue_entry *dqe; - uint8_t *data; - - dqe = defrag_get_seg(sne, seg_nr); - if (!dqe) { - LOGP(DSNDCP, LOGL_ERROR, "Segment %u missing\n", seg_nr); - msgb_free(msg); - return -EIO; - } - /* actually append the segment to the N-PDU */ - data = msgb_put(msg, dqe->data_len); - memcpy(data, dqe->data, dqe->data_len); - - /* release memory for the fragment queue entry */ - talloc_free(dqe); - } - - npdu_len = sne->defrag.tot_len; - - /* FIXME: cancel timer */ - - /* actually send the N-PDU to the SGSN core code, which then - * hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */ - - /* Decompress packet */ -#if DEBUG_IP_PACKETS == 1 - DEBUGP(DSNDCP, " \n"); - DEBUGP(DSNDCP, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n"); - DEBUGP(DSNDCP, "===================================================\n"); -#endif - if (any_pcomp_or_dcomp_active(sgsn)) { - - expnd = talloc_zero_size(msg, npdu_len * MAX_DATADECOMPR_FAC + - MAX_HDRDECOMPR_INCR); - memcpy(expnd, npdu, npdu_len); - - /* Apply data decompression */ - rc = gprs_sndcp_dcomp_expand(expnd, npdu_len, sne->defrag.dcomp, - sne->defrag.data); - if (rc < 0) { - LOGP(DSNDCP, LOGL_ERROR, - "Data decompression failed!\n"); - talloc_free(expnd); - return -EIO; - } - - /* Apply header decompression */ - rc = gprs_sndcp_pcomp_expand(expnd, rc, sne->defrag.pcomp, - sne->defrag.proto); - if (rc < 0) { - LOGP(DSNDCP, LOGL_ERROR, - "TCP/IP Header decompression failed!\n"); - talloc_free(expnd); - return -EIO; - } - - /* Modify npu length, expnd is handed directly handed - * over to gsn_rx_sndcp_ud_ind(), see below */ - npdu_len = rc; - } else - expnd = npdu; -#if DEBUG_IP_PACKETS == 1 - debug_ip_packet(expnd, npdu_len, 1, "defrag_segments()"); - DEBUGP(DSNDCP, "===================================================\n"); - DEBUGP(DSNDCP, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n"); - DEBUGP(DSNDCP, " \n"); -#endif - - /* Hand off packet to gtp */ - rc = sgsn_rx_sndcp_ud_ind(&sne->ra_id, sne->lle->llme->tlli, - sne->nsapi, msg, npdu_len, expnd); - - if (any_pcomp_or_dcomp_active(sgsn)) - talloc_free(expnd); - - return rc; -} - -static int defrag_input(struct gprs_sndcp_entity *sne, struct msgb *msg, - uint8_t *hdr, unsigned int len) -{ - struct sndcp_common_hdr *sch; - struct sndcp_udata_hdr *suh; - uint16_t npdu_num; - uint8_t *data; - int rc; - - sch = (struct sndcp_common_hdr *) hdr; - if (sch->first) { - suh = (struct sndcp_udata_hdr *) (hdr + 1 + sizeof(struct sndcp_common_hdr)); - } else - suh = (struct sndcp_udata_hdr *) (hdr + sizeof(struct sndcp_common_hdr)); - - data = (uint8_t *)suh + sizeof(struct sndcp_udata_hdr); - - npdu_num = (suh->npdu_high << 8) | suh->npdu_low; - - LOGP(DSNDCP, LOGL_DEBUG, "TLLI=0x%08x NSAPI=%u: Input PDU %u Segment %u " - "Length %u %s %s\n", sne->lle->llme->tlli, sne->nsapi, npdu_num, - suh->seg_nr, len, sch->first ? "F " : "", sch->more ? "M" : ""); - - if (sch->first) { - /* first segment of a new packet. Discard all leftover fragments of - * previous packet */ - if (!llist_empty(&sne->defrag.frag_list)) { - struct defrag_queue_entry *dqe, *dqe2; - LOGP(DSNDCP, LOGL_INFO, "TLLI=0x%08x NSAPI=%u: Dropping " - "SN-PDU %u due to insufficient segments (%04x)\n", - sne->lle->llme->tlli, sne->nsapi, sne->defrag.npdu, - sne->defrag.seg_have); - llist_for_each_entry_safe(dqe, dqe2, &sne->defrag.frag_list, list) { - llist_del(&dqe->list); - talloc_free(dqe); - } - } - /* store the currently de-fragmented PDU number */ - sne->defrag.npdu = npdu_num; - - /* Re-set fragmentation state */ - sne->defrag.no_more = sne->defrag.highest_seg = sne->defrag.seg_have = 0; - sne->defrag.tot_len = 0; - /* FIXME: (re)start timer */ - } - - if (sne->defrag.npdu != npdu_num) { - LOGP(DSNDCP, LOGL_INFO, "Segment for different SN-PDU " - "(%u != %u)\n", npdu_num, sne->defrag.npdu); - /* FIXME */ - } - - /* FIXME: check if seg_nr already exists */ - /* make sure to subtract length of SNDCP header from 'len' */ - rc = defrag_enqueue(sne, suh->seg_nr, data, len - (data - hdr)); - if (rc < 0) - return rc; - - if (!sch->more) { - /* this is suppsed to be the last segment of the N-PDU, but it - * might well be not the last to arrive */ - sne->defrag.no_more = 1; - } - - if (sne->defrag.no_more) { - /* we have already received the last segment before, let's check - * if all the previous segments exist */ - if (defrag_have_all_segments(sne)) - return defrag_segments(sne); - } - - return 0; -} - -static struct gprs_sndcp_entity *gprs_sndcp_entity_by_lle(const struct gprs_llc_lle *lle, - uint8_t nsapi) -{ - struct gprs_sndcp_entity *sne; - - llist_for_each_entry(sne, &gprs_sndcp_entities, list) { - if (sne->lle == lle && sne->nsapi == nsapi) - return sne; - } - return NULL; -} - -static struct gprs_sndcp_entity *gprs_sndcp_entity_alloc(struct gprs_llc_lle *lle, - uint8_t nsapi) -{ - struct gprs_sndcp_entity *sne; - - sne = talloc_zero(tall_sndcp_ctx, struct gprs_sndcp_entity); - if (!sne) - return NULL; - - sne->lle = lle; - sne->nsapi = nsapi; - sne->defrag.timer.data = sne; - //sne->fqueue.timer.cb = FIXME; - sne->rx_state = SNDCP_RX_S_FIRST; - INIT_LLIST_HEAD(&sne->defrag.frag_list); - - llist_add(&sne->list, &gprs_sndcp_entities); - - return sne; -} - -/* Entry point for the SNSM-ACTIVATE.indication */ -int sndcp_sm_activate_ind(struct gprs_llc_lle *lle, uint8_t nsapi) -{ - LOGP(DSNDCP, LOGL_INFO, "SNSM-ACTIVATE.ind (lle=%p TLLI=%08x, " - "SAPI=%u, NSAPI=%u)\n", lle, lle->llme->tlli, lle->sapi, nsapi); - - if (gprs_sndcp_entity_by_lle(lle, nsapi)) { - LOGP(DSNDCP, LOGL_ERROR, "Trying to ACTIVATE " - "already-existing entity (TLLI=%08x, NSAPI=%u)\n", - lle->llme->tlli, nsapi); - return -EEXIST; - } - - if (!gprs_sndcp_entity_alloc(lle, nsapi)) { - LOGP(DSNDCP, LOGL_ERROR, "Out of memory during ACTIVATE\n"); - return -ENOMEM; - } - - return 0; -} - -/* Entry point for the SNSM-DEACTIVATE.indication */ -int sndcp_sm_deactivate_ind(struct gprs_llc_lle *lle, uint8_t nsapi) -{ - struct gprs_sndcp_entity *sne; - - LOGP(DSNDCP, LOGL_INFO, "SNSM-DEACTIVATE.ind (lle=%p, TLLI=%08x, " - "SAPI=%u, NSAPI=%u)\n", lle, lle->llme->tlli, lle->sapi, nsapi); - - sne = gprs_sndcp_entity_by_lle(lle, nsapi); - if (!sne) { - LOGP(DSNDCP, LOGL_ERROR, "SNSM-DEACTIVATE.ind for non-" - "existing TLLI=%08x SAPI=%u NSAPI=%u\n", lle->llme->tlli, - lle->sapi, nsapi); - return -ENOENT; - } - llist_del(&sne->list); - /* frag queue entries are hierarchically allocated, so no need to - * free them explicitly here */ - talloc_free(sne); - - return 0; -} - -/* Fragmenter state */ -struct sndcp_frag_state { - uint8_t frag_nr; - struct msgb *msg; /* original message */ - uint8_t *next_byte; /* first byte of next fragment */ - - struct gprs_sndcp_entity *sne; - void *mmcontext; -}; - -/* returns '1' if there are more fragments to send, '0' if none */ -static int sndcp_send_ud_frag(struct sndcp_frag_state *fs, - uint8_t pcomp, uint8_t dcomp) -{ - struct gprs_sndcp_entity *sne = fs->sne; - struct gprs_llc_lle *lle = sne->lle; - struct sndcp_common_hdr *sch; - struct sndcp_comp_hdr *scomph; - struct sndcp_udata_hdr *suh; - struct msgb *fmsg; - unsigned int max_payload_len; - unsigned int len; - uint8_t *data; - int rc, more; - - fmsg = msgb_alloc_headroom(fs->sne->lle->params.n201_u+256, 128, - "SNDCP Frag"); - if (!fmsg) { - msgb_free(fs->msg); - return -ENOMEM; - } - - /* make sure lower layers route the fragment like the original */ - msgb_tlli(fmsg) = msgb_tlli(fs->msg); - msgb_bvci(fmsg) = msgb_bvci(fs->msg); - msgb_nsei(fmsg) = msgb_nsei(fs->msg); - - /* prepend common SNDCP header */ - sch = (struct sndcp_common_hdr *) msgb_put(fmsg, sizeof(*sch)); - sch->nsapi = sne->nsapi; - /* Set FIRST bit if we are the first fragment in a series */ - if (fs->frag_nr == 0) - sch->first = 1; - sch->type = 1; - - /* append the compression header for first fragment */ - if (sch->first) { - scomph = (struct sndcp_comp_hdr *) - msgb_put(fmsg, sizeof(*scomph)); - scomph->pcomp = pcomp; - scomph->dcomp = dcomp; - } - - /* append the user-data header */ - suh = (struct sndcp_udata_hdr *) msgb_put(fmsg, sizeof(*suh)); - suh->npdu_low = sne->tx_npdu_nr & 0xff; - suh->npdu_high = (sne->tx_npdu_nr >> 8) & 0xf; - suh->seg_nr = fs->frag_nr % 0xf; - - /* calculate remaining length to be sent */ - len = (fs->msg->data + fs->msg->len) - fs->next_byte; - /* how much payload can we actually send via LLC? */ - max_payload_len = lle->params.n201_u - (sizeof(*sch) + sizeof(*suh)); - if (sch->first) - max_payload_len -= sizeof(*scomph); - /* check if we're exceeding the max */ - if (len > max_payload_len) - len = max_payload_len; - - /* copy the actual fragment data into our fmsg */ - data = msgb_put(fmsg, len); - memcpy(data, fs->next_byte, len); - - /* Increment fragment number and data pointer to next fragment */ - fs->frag_nr++; - fs->next_byte += len; - - /* determine if we have more fragemnts to send */ - if ((fs->msg->data + fs->msg->len) <= fs->next_byte) - more = 0; - else - more = 1; - - /* set the MORE bit of the SNDCP header accordingly */ - sch->more = more; - - rc = gprs_llc_tx_ui(fmsg, lle->sapi, 0, fs->mmcontext, true); - /* abort in case of error, do not advance frag_nr / next_byte */ - if (rc < 0) { - msgb_free(fs->msg); - return rc; - } - - if (!more) { - /* we've sent all fragments */ - msgb_free(fs->msg); - memset(fs, 0, sizeof(*fs)); - /* increment NPDU number for next frame */ - sne->tx_npdu_nr = (sne->tx_npdu_nr + 1) % 0xfff; - return 0; - } - - /* default: more fragments to send */ - return 1; -} - -/* Request transmission of a SN-PDU over specified LLC Entity + SAPI */ -int sndcp_unitdata_req(struct msgb *msg, struct gprs_llc_lle *lle, uint8_t nsapi, - void *mmcontext) -{ - struct gprs_sndcp_entity *sne; - struct sndcp_common_hdr *sch; - struct sndcp_comp_hdr *scomph; - struct sndcp_udata_hdr *suh; - struct sndcp_frag_state fs; - uint8_t pcomp = 0; - uint8_t dcomp = 0; - int rc; - - /* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */ - - /* Compress packet */ -#if DEBUG_IP_PACKETS == 1 - DEBUGP(DSNDCP, " \n"); - DEBUGP(DSNDCP, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n"); - DEBUGP(DSNDCP, "===================================================\n"); - debug_ip_packet(msg->data, msg->len, 0, "sndcp_initdata_req()"); -#endif - if (any_pcomp_or_dcomp_active(sgsn)) { - - /* Apply header compression */ - rc = gprs_sndcp_pcomp_compress(msg->data, msg->len, &pcomp, - lle->llme->comp.proto, nsapi); - if (rc < 0) { - LOGP(DSNDCP, LOGL_ERROR, - "TCP/IP Header compression failed!\n"); - return -EIO; - } - - /* Fixup pointer locations and sizes in message buffer to match - * the new, compressed buffer size */ - msgb_get(msg, msg->len); - msgb_put(msg, rc); - - /* Apply data compression */ - rc = gprs_sndcp_dcomp_compress(msg->data, msg->len, &dcomp, - lle->llme->comp.data, nsapi); - if (rc < 0) { - LOGP(DSNDCP, LOGL_ERROR, "Data compression failed!\n"); - return -EIO; - } - - /* Fixup pointer locations and sizes in message buffer to match - * the new, compressed buffer size */ - msgb_get(msg, msg->len); - msgb_put(msg, rc); - } -#if DEBUG_IP_PACKETS == 1 - DEBUGP(DSNDCP, "===================================================\n"); - DEBUGP(DSNDCP, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n"); - DEBUGP(DSNDCP, " \n"); -#endif - - sne = gprs_sndcp_entity_by_lle(lle, nsapi); - if (!sne) { - LOGP(DSNDCP, LOGL_ERROR, "Cannot find SNDCP Entity\n"); - msgb_free(msg); - return -EIO; - } - - /* Check if we need to fragment this N-PDU into multiple SN-PDUs */ - if (msg->len > lle->params.n201_u - - (sizeof(*sch) + sizeof(*suh) + sizeof(*scomph))) { - /* initialize the fragmenter state */ - fs.msg = msg; - fs.frag_nr = 0; - fs.next_byte = msg->data; - fs.sne = sne; - fs.mmcontext = mmcontext; - - /* call function to generate and send fragments until all - * of the N-PDU has been sent */ - while (1) { - int rc = sndcp_send_ud_frag(&fs,pcomp,dcomp); - if (rc == 0) - return 0; - if (rc < 0) - return rc; - } - /* not reached */ - return 0; - } - - /* this is the non-fragmenting case where we only build 1 SN-PDU */ - - /* prepend the user-data header */ - suh = (struct sndcp_udata_hdr *) msgb_push(msg, sizeof(*suh)); - suh->npdu_low = sne->tx_npdu_nr & 0xff; - suh->npdu_high = (sne->tx_npdu_nr >> 8) & 0xf; - suh->seg_nr = 0; - sne->tx_npdu_nr = (sne->tx_npdu_nr + 1) % 0xfff; - - scomph = (struct sndcp_comp_hdr *) msgb_push(msg, sizeof(*scomph)); - scomph->pcomp = pcomp; - scomph->dcomp = dcomp; - - /* prepend common SNDCP header */ - sch = (struct sndcp_common_hdr *) msgb_push(msg, sizeof(*sch)); - sch->first = 1; - sch->type = 1; - sch->nsapi = nsapi; - - return gprs_llc_tx_ui(msg, lle->sapi, 0, mmcontext, true); -} - -/* Section 5.1.2.17 LL-UNITDATA.ind */ -int sndcp_llunitdata_ind(struct msgb *msg, struct gprs_llc_lle *lle, - uint8_t *hdr, uint16_t len) -{ - struct gprs_sndcp_entity *sne; - struct sndcp_common_hdr *sch = (struct sndcp_common_hdr *)hdr; - struct sndcp_comp_hdr *scomph = NULL; - struct sndcp_udata_hdr *suh; - uint8_t *npdu; - uint16_t npdu_num __attribute__((unused)); - int npdu_len; - int rc; - uint8_t *expnd = NULL; - - sch = (struct sndcp_common_hdr *) hdr; - if (sch->first) { - scomph = (struct sndcp_comp_hdr *) (hdr + 1); - suh = (struct sndcp_udata_hdr *) (hdr + 1 + sizeof(struct sndcp_common_hdr)); - } else - suh = (struct sndcp_udata_hdr *) (hdr + sizeof(struct sndcp_common_hdr)); - - if (sch->type == 0) { - LOGP(DSNDCP, LOGL_ERROR, "SN-DATA PDU at unitdata_ind() function\n"); - return -EINVAL; - } - - if (len < sizeof(*sch) + sizeof(*suh)) { - LOGP(DSNDCP, LOGL_ERROR, "SN-UNITDATA PDU too short (%u)\n", len); - return -EIO; - } - - sne = gprs_sndcp_entity_by_lle(lle, sch->nsapi); - if (!sne) { - LOGP(DSNDCP, LOGL_ERROR, "Message for non-existing SNDCP Entity " - "(lle=%p, TLLI=%08x, SAPI=%u, NSAPI=%u)\n", lle, - lle->llme->tlli, lle->sapi, sch->nsapi); - return -EIO; - } - /* FIXME: move this RA_ID up to the LLME or even higher */ - bssgp_parse_cell_id(&sne->ra_id, msgb_bcid(msg)); - - if (scomph) { - sne->defrag.pcomp = scomph->pcomp; - sne->defrag.dcomp = scomph->dcomp; - sne->defrag.proto = lle->llme->comp.proto; - sne->defrag.data = lle->llme->comp.data; - } - - /* any non-first segment is by definition something to defragment - * as is any segment that tells us there are more segments */ - if (!sch->first || sch->more) - return defrag_input(sne, msg, hdr, len); - - npdu_num = (suh->npdu_high << 8) | suh->npdu_low; - npdu = (uint8_t *)suh + sizeof(*suh); - npdu_len = (msg->data + msg->len) - npdu; - - if (npdu_len <= 0) { - LOGP(DSNDCP, LOGL_ERROR, "Short SNDCP N-PDU: %d\n", npdu_len); - return -EIO; - } - /* actually send the N-PDU to the SGSN core code, which then - * hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */ - - /* Decompress packet */ -#if DEBUG_IP_PACKETS == 1 - DEBUGP(DSNDCP, " \n"); - DEBUGP(DSNDCP, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n"); - DEBUGP(DSNDCP, "===================================================\n"); -#endif - if (any_pcomp_or_dcomp_active(sgsn)) { - - expnd = talloc_zero_size(msg, npdu_len * MAX_DATADECOMPR_FAC + - MAX_HDRDECOMPR_INCR); - memcpy(expnd, npdu, npdu_len); - - /* Apply data decompression */ - rc = gprs_sndcp_dcomp_expand(expnd, npdu_len, sne->defrag.dcomp, - sne->defrag.data); - if (rc < 0) { - LOGP(DSNDCP, LOGL_ERROR, - "Data decompression failed!\n"); - talloc_free(expnd); - return -EIO; - } - - /* Apply header decompression */ - rc = gprs_sndcp_pcomp_expand(expnd, rc, sne->defrag.pcomp, - sne->defrag.proto); - if (rc < 0) { - LOGP(DSNDCP, LOGL_ERROR, - "TCP/IP Header decompression failed!\n"); - talloc_free(expnd); - return -EIO; - } - - /* Modify npu length, expnd is handed directly handed - * over to gsn_rx_sndcp_ud_ind(), see below */ - npdu_len = rc; - } else - expnd = npdu; -#if DEBUG_IP_PACKETS == 1 - debug_ip_packet(expnd, npdu_len, 1, "sndcp_llunitdata_ind()"); - DEBUGP(DSNDCP, "===================================================\n"); - DEBUGP(DSNDCP, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n"); - DEBUGP(DSNDCP, " \n"); -#endif - - /* Hand off packet to gtp */ - rc = sgsn_rx_sndcp_ud_ind(&sne->ra_id, lle->llme->tlli, - sne->nsapi, msg, npdu_len, expnd); - - if (any_pcomp_or_dcomp_active(sgsn)) - talloc_free(expnd); - - return rc; -} - -#if 0 -/* Section 5.1.2.1 LL-RESET.ind */ -static int sndcp_ll_reset_ind(struct gprs_sndcp_entity *se) -{ - /* treat all outstanding SNDCP-LLC request type primitives as not sent */ - /* reset all SNDCP XID parameters to default values */ - LOGP(DSNDCP, LOGL_NOTICE, "not implemented.\n"); - return 0; -} - -static int sndcp_ll_status_ind() -{ - /* inform the SM sub-layer by means of SNSM-STATUS.req */ - LOGP(DSNDCP, LOGL_NOTICE, "not implemented.\n"); - return 0; -} - -static struct sndcp_state_list {{ - uint32_t states; - unsigned int type; - int (*rout)(struct gprs_sndcp_entity *se, struct msgb *msg); -} sndcp_state_list[] = { - { ALL_STATES, - LL_RESET_IND, sndcp_ll_reset_ind }, - { ALL_STATES, - LL_ESTABLISH_IND, sndcp_ll_est_ind }, - { SBIT(SNDCP_S_EST_RQD), - LL_ESTABLISH_RESP, sndcp_ll_est_ind }, - { SBIT(SNDCP_S_EST_RQD), - LL_ESTABLISH_CONF, sndcp_ll_est_conf }, - { SBIT(SNDCP_S_ -}; - -static int sndcp_rx_llc_prim() -{ - case LL_ESTABLISH_REQ: - case LL_RELEASE_REQ: - case LL_XID_REQ: - case LL_DATA_REQ: - LL_UNITDATA_REQ, /* TLLI, SN-PDU, Ref, QoS, Radio Prio, Ciph */ - - switch (prim) { - case LL_RESET_IND: - case LL_ESTABLISH_IND: - case LL_ESTABLISH_RESP: - case LL_ESTABLISH_CONF: - case LL_RELEASE_IND: - case LL_RELEASE_CONF: - case LL_XID_IND: - case LL_XID_RESP: - case LL_XID_CONF: - case LL_DATA_IND: - case LL_DATA_CONF: - case LL_UNITDATA_IND: - case LL_STATUS_IND: - } -} -#endif - -/* Generate SNDCP-XID message */ -static int gprs_llc_gen_sndcp_xid(uint8_t *bytes, int bytes_len, uint8_t nsapi) -{ - int entity = 0; - LLIST_HEAD(comp_fields); - struct gprs_sndcp_pcomp_rfc1144_params rfc1144_params; - struct gprs_sndcp_comp_field rfc1144_comp_field; - struct gprs_sndcp_dcomp_v42bis_params v42bis_params; - struct gprs_sndcp_comp_field v42bis_comp_field; - - memset(&rfc1144_comp_field, 0, sizeof(struct gprs_sndcp_comp_field)); - memset(&v42bis_comp_field, 0, sizeof(struct gprs_sndcp_comp_field)); - - /* Setup rfc1144 */ - if (sgsn->cfg.pcomp_rfc1144.active) { - rfc1144_params.nsapi[0] = nsapi; - rfc1144_params.nsapi_len = 1; - rfc1144_params.s01 = sgsn->cfg.pcomp_rfc1144.s01; - rfc1144_comp_field.p = 1; - rfc1144_comp_field.entity = entity; - rfc1144_comp_field.algo.pcomp = RFC_1144; - rfc1144_comp_field.comp[RFC1144_PCOMP1] = 1; - rfc1144_comp_field.comp[RFC1144_PCOMP2] = 2; - rfc1144_comp_field.comp_len = RFC1144_PCOMP_NUM; - rfc1144_comp_field.rfc1144_params = &rfc1144_params; - entity++; - llist_add(&rfc1144_comp_field.list, &comp_fields); - } - - /* Setup V.42bis */ - if (sgsn->cfg.dcomp_v42bis.active) { - v42bis_params.nsapi[0] = nsapi; - v42bis_params.nsapi_len = 1; - v42bis_params.p0 = sgsn->cfg.dcomp_v42bis.p0; - v42bis_params.p1 = sgsn->cfg.dcomp_v42bis.p1; - v42bis_params.p2 = sgsn->cfg.dcomp_v42bis.p2; - v42bis_comp_field.p = 1; - v42bis_comp_field.entity = entity; - v42bis_comp_field.algo.dcomp = V42BIS; - v42bis_comp_field.comp[V42BIS_DCOMP1] = 1; - v42bis_comp_field.comp_len = V42BIS_DCOMP_NUM; - v42bis_comp_field.v42bis_params = &v42bis_params; - entity++; - llist_add(&v42bis_comp_field.list, &comp_fields); - } - - /* Do not attempt to compile anything if there is no data in the list */ - if (llist_empty(&comp_fields)) - return 0; - - /* Compile bytestream */ - return gprs_sndcp_compile_xid(bytes, bytes_len, &comp_fields, - DEFAULT_SNDCP_VERSION); -} - -/* Set of SNDCP-XID bnegotiation (See also: TS 144 065, - * Section 6.8 XID parameter negotiation) */ -int sndcp_sn_xid_req(struct gprs_llc_lle *lle, uint8_t nsapi) -{ - /* Note: The specification requires the SNDCP-User to set of an - * SNDCP xid request. See also 3GPP TS 44.065, 6.8 XID parameter - * negotiation, Figure 11: SNDCP XID negotiation procedure. In - * our case the SNDCP-User is sgsn_libgtp.c, which calls - * sndcp_sn_xid_req directly. */ - - uint8_t l3params[1024]; - int xid_len; - struct gprs_llc_xid_field xid_field_request; - - /* Wipe off all compression entities and their states to - * get rid of possible leftovers from a previous session */ - gprs_sndcp_comp_free(lle->llme->comp.proto); - gprs_sndcp_comp_free(lle->llme->comp.data); - lle->llme->comp.proto = gprs_sndcp_comp_alloc(lle->llme); - lle->llme->comp.data = gprs_sndcp_comp_alloc(lle->llme); - talloc_free(lle->xid); - lle->xid = NULL; - - /* Generate compression parameter bytestream */ - xid_len = gprs_llc_gen_sndcp_xid(l3params, sizeof(l3params), nsapi); - - /* Send XID with the SNDCP-XID bytetsream included */ - if (xid_len > 0) { - xid_field_request.type = GPRS_LLC_XID_T_L3_PAR; - xid_field_request.data = l3params; - xid_field_request.data_len = xid_len; - return gprs_ll_xid_req(lle, &xid_field_request); - } - - /* When bytestream can not be generated, proceed without SNDCP-XID */ - return gprs_ll_xid_req(lle, NULL); - -} - -/* Handle header compression entites */ -static int handle_pcomp_entities(struct gprs_sndcp_comp_field *comp_field, - struct gprs_llc_lle *lle) -{ - /* Note: This functions also transforms the comp_field into its - * echo form (strips comp values, resets propose bit etc...) - * the processed comp_fields can then be sent back as XID- - * Response without further modification. */ - - /* Delete propose bit */ - comp_field->p = 0; - - /* Process proposed parameters */ - switch (comp_field->algo.pcomp) { - case RFC_1144: - if (sgsn->cfg.pcomp_rfc1144.passive - && comp_field->rfc1144_params->nsapi_len > 0) { - DEBUGP(DSNDCP, - "Accepting RFC1144 header compression...\n"); - gprs_sndcp_comp_add(lle->llme, lle->llme->comp.proto, - comp_field); - } else { - DEBUGP(DSNDCP, - "Rejecting RFC1144 header compression...\n"); - gprs_sndcp_comp_delete(lle->llme->comp.proto, - comp_field->entity); - comp_field->rfc1144_params->nsapi_len = 0; - } - break; - case RFC_2507: - /* RFC 2507 is not yet supported, - * so we set applicable nsapis to zero */ - DEBUGP(DSNDCP, "Rejecting RFC2507 header compression...\n"); - comp_field->rfc2507_params->nsapi_len = 0; - gprs_sndcp_comp_delete(lle->llme->comp.proto, - comp_field->entity); - break; - case ROHC: - /* ROHC is not yet supported, - * so we set applicable nsapis to zero */ - DEBUGP(DSNDCP, "Rejecting ROHC header compression...\n"); - comp_field->rohc_params->nsapi_len = 0; - gprs_sndcp_comp_delete(lle->llme->comp.proto, - comp_field->entity); - break; - } - - return 0; -} - -/* Hanle data compression entites */ -static int handle_dcomp_entities(struct gprs_sndcp_comp_field *comp_field, - struct gprs_llc_lle *lle) -{ - /* See note in handle_pcomp_entities() */ - - /* Delete propose bit */ - comp_field->p = 0; - - /* Process proposed parameters */ - switch (comp_field->algo.dcomp) { - case V42BIS: - if (sgsn->cfg.dcomp_v42bis.passive && - comp_field->v42bis_params->nsapi_len > 0) { - DEBUGP(DSNDCP, - "Accepting V.42bis data compression...\n"); - gprs_sndcp_comp_add(lle->llme, lle->llme->comp.data, - comp_field); - } else { - LOGP(DSNDCP, LOGL_DEBUG, - "Rejecting V.42bis data compression...\n"); - gprs_sndcp_comp_delete(lle->llme->comp.data, - comp_field->entity); - comp_field->v42bis_params->nsapi_len = 0; - } - break; - case V44: - /* V44 is not yet supported, - * so we set applicable nsapis to zero */ - DEBUGP(DSNDCP, "Rejecting V.44 data compression...\n"); - comp_field->v44_params->nsapi_len = 0; - gprs_sndcp_comp_delete(lle->llme->comp.data, - comp_field->entity); - break; - } - - return 0; - -} - -/* Process SNDCP-XID indication - * (See also: TS 144 065, Section 6.8 XID parameter negotiation) */ -int sndcp_sn_xid_ind(struct gprs_llc_xid_field *xid_field_indication, - struct gprs_llc_xid_field *xid_field_response, - struct gprs_llc_lle *lle) -{ - /* Note: This function computes the SNDCP-XID response that is sent - * back to the ms when a ms originated XID is received. The - * Input XID fields are directly processed and the result is directly - * handed back. */ - - int rc; - int compclass; - int version; - - struct llist_head *comp_fields; - struct gprs_sndcp_comp_field *comp_field; - - OSMO_ASSERT(xid_field_indication); - OSMO_ASSERT(xid_field_response); - OSMO_ASSERT(lle); - - /* Some phones send zero byte length SNDCP frames - * and do require a confirmation response. */ - if (xid_field_indication->data_len == 0) { - xid_field_response->type = GPRS_LLC_XID_T_L3_PAR; - xid_field_response->data_len = 0; - return 0; - } - - /* Parse SNDCP-CID XID-Field */ - comp_fields = gprs_sndcp_parse_xid(&version, lle->llme, - xid_field_indication->data, - xid_field_indication->data_len, - NULL); - if (!comp_fields) - return -EINVAL; - - /* Handle compression entites */ - DEBUGP(DSNDCP, "SNDCP-XID-IND (ms):\n"); - gprs_sndcp_dump_comp_fields(comp_fields, LOGL_DEBUG); - - llist_for_each_entry(comp_field, comp_fields, list) { - compclass = gprs_sndcp_get_compression_class(comp_field); - if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION) - rc = handle_pcomp_entities(comp_field, lle); - else if (compclass == SNDCP_XID_DATA_COMPRESSION) - rc = handle_dcomp_entities(comp_field, lle); - else { - gprs_sndcp_comp_delete(lle->llme->comp.proto, - comp_field->entity); - gprs_sndcp_comp_delete(lle->llme->comp.data, - comp_field->entity); - rc = 0; - } - - if (rc < 0) { - talloc_free(comp_fields); - return -EINVAL; - } - } - - DEBUGP(DSNDCP, "SNDCP-XID-RES (sgsn):\n"); - gprs_sndcp_dump_comp_fields(comp_fields, LOGL_DEBUG); - - /* Reserve some memory to store the modified SNDCP-XID bytes */ - xid_field_response->data = - talloc_zero_size(lle->llme, xid_field_indication->data_len); - - /* Set Type flag for response */ - xid_field_response->type = GPRS_LLC_XID_T_L3_PAR; - - /* Compile modified SNDCP-XID bytes */ - rc = gprs_sndcp_compile_xid(xid_field_response->data, - xid_field_indication->data_len, - comp_fields, 0); - - if (rc > 0) - xid_field_response->data_len = rc; - else { - talloc_free(xid_field_response->data); - xid_field_response->data = NULL; - xid_field_response->data_len = 0; - return -EINVAL; - } - - talloc_free(comp_fields); - - return 0; -} - -/* Process SNDCP-XID indication - * (See also: TS 144 065, Section 6.8 XID parameter negotiation) */ -int sndcp_sn_xid_conf(struct gprs_llc_xid_field *xid_field_conf, - struct gprs_llc_xid_field *xid_field_request, - struct gprs_llc_lle *lle) -{ - /* Note: This function handles an incomming SNDCP-XID confirmiation. - * Since the confirmation fields may lack important parameters we - * will reconstruct these missing fields using the original request - * we have sent. After that we will create (or delete) the - * compression entites */ - - struct llist_head *comp_fields_req; - struct llist_head *comp_fields_conf; - struct gprs_sndcp_comp_field *comp_field; - int rc; - int compclass; - - /* We need both, the confirmation that is sent back by the ms, - * and the original request we have sent. If one of this is missing - * we can not process the confirmation, the caller must check if - * request and confirmation fields are available. */ - OSMO_ASSERT(xid_field_conf); - OSMO_ASSERT(xid_field_request); - - /* Parse SNDCP-CID XID-Field */ - comp_fields_req = gprs_sndcp_parse_xid(NULL, lle->llme, - xid_field_request->data, - xid_field_request->data_len, - NULL); - if (!comp_fields_req) - return -EINVAL; - - DEBUGP(DSNDCP, "SNDCP-XID-REQ (sgsn):\n"); - gprs_sndcp_dump_comp_fields(comp_fields_req, LOGL_DEBUG); - - /* Parse SNDCP-CID XID-Field */ - comp_fields_conf = gprs_sndcp_parse_xid(NULL, lle->llme, - xid_field_conf->data, - xid_field_conf->data_len, - comp_fields_req); - if (!comp_fields_conf) - return -EINVAL; - - DEBUGP(DSNDCP, "SNDCP-XID-CONF (ms):\n"); - gprs_sndcp_dump_comp_fields(comp_fields_conf, LOGL_DEBUG); - - /* Handle compression entites */ - llist_for_each_entry(comp_field, comp_fields_conf, list) { - compclass = gprs_sndcp_get_compression_class(comp_field); - if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION) - rc = handle_pcomp_entities(comp_field, lle); - else if (compclass == SNDCP_XID_DATA_COMPRESSION) - rc = handle_dcomp_entities(comp_field, lle); - else { - gprs_sndcp_comp_delete(lle->llme->comp.proto, - comp_field->entity); - gprs_sndcp_comp_delete(lle->llme->comp.data, - comp_field->entity); - rc = 0; - } - - if (rc < 0) { - talloc_free(comp_fields_req); - talloc_free(comp_fields_conf); - return -EINVAL; - } - } - - talloc_free(comp_fields_req); - talloc_free(comp_fields_conf); - - return 0; -} diff --git a/src/gprs/gprs_sndcp_comp.c b/src/gprs/gprs_sndcp_comp.c deleted file mode 100644 index c71cc8982..000000000 --- a/src/gprs/gprs_sndcp_comp.c +++ /dev/null @@ -1,338 +0,0 @@ -/* GPRS SNDCP header compression entity management tools */ - -/* (C) 2016 by sysmocom s.f.m.c. GmbH - * All Rights Reserved - * - * Author: Philipp Maier - * - * 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 . - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -/* Create a new compression entity from a XID-Field */ -static struct gprs_sndcp_comp *gprs_sndcp_comp_create(const void *ctx, - const struct - gprs_sndcp_comp_field - *comp_field) -{ - struct gprs_sndcp_comp *comp_entity; - comp_entity = talloc_zero(ctx, struct gprs_sndcp_comp); - - /* Copy relevant information from the SNDCP-XID field */ - comp_entity->entity = comp_field->entity; - comp_entity->comp_len = comp_field->comp_len; - memcpy(comp_entity->comp, comp_field->comp, sizeof(comp_entity->comp)); - - if (comp_field->rfc1144_params) { - comp_entity->nsapi_len = comp_field->rfc1144_params->nsapi_len; - memcpy(comp_entity->nsapi, - comp_field->rfc1144_params->nsapi, - sizeof(comp_entity->nsapi)); - comp_entity->algo.pcomp = comp_field->algo.pcomp; - } else if (comp_field->rfc2507_params) { - comp_entity->nsapi_len = comp_field->rfc2507_params->nsapi_len; - memcpy(comp_entity->nsapi, - comp_field->rfc2507_params->nsapi, - sizeof(comp_entity->nsapi)); - comp_entity->algo.pcomp = comp_field->algo.pcomp; - } else if (comp_field->rohc_params) { - comp_entity->nsapi_len = comp_field->rohc_params->nsapi_len; - memcpy(comp_entity->nsapi, comp_field->rohc_params->nsapi, - sizeof(comp_entity->nsapi)); - comp_entity->algo.pcomp = comp_field->algo.pcomp; - } else if (comp_field->v42bis_params) { - comp_entity->nsapi_len = comp_field->v42bis_params->nsapi_len; - memcpy(comp_entity->nsapi, - comp_field->v42bis_params->nsapi, - sizeof(comp_entity->nsapi)); - comp_entity->algo.dcomp = comp_field->algo.dcomp; - } else if (comp_field->v44_params) { - comp_entity->nsapi_len = comp_field->v44_params->nsapi_len; - memcpy(comp_entity->nsapi, - comp_field->v44_params->nsapi, - sizeof(comp_entity->nsapi)); - comp_entity->algo.dcomp = comp_field->algo.dcomp; - } else { - /* The caller is expected to check carefully if the all - * data fields required for compression entity creation - * are present. Otherwise we blow an assertion here */ - OSMO_ASSERT(false); - } - - /* Check if an NSAPI is selected, if not, it does not make sense - * to create the compression entity, since the caller should - * have checked the presence of the NSAPI, we blow an assertion - * in case of missing NSAPIs */ - OSMO_ASSERT(comp_entity->nsapi_len > 0); - - /* Determine of which class our compression entity will be - * (Protocol or Data compresson ?) */ - comp_entity->compclass = gprs_sndcp_get_compression_class(comp_field); - - /* Create an algorithm specific compression context */ - switch (comp_entity->compclass) { - case SNDCP_XID_PROTOCOL_COMPRESSION: - if (gprs_sndcp_pcomp_init(ctx, comp_entity, comp_field) != 0) { - talloc_free(comp_entity); - comp_entity = NULL; - } - break; - case SNDCP_XID_DATA_COMPRESSION: - if (gprs_sndcp_dcomp_init(ctx, comp_entity, comp_field) != 0) { - talloc_free(comp_entity); - comp_entity = NULL; - } - break; - default: - /* comp_field is somehow invalid */ - OSMO_ASSERT(false); - } - - /* Bail on failure */ - if (comp_entity == NULL) { - LOGP(DSNDCP, LOGL_ERROR, - "Compression entity creation failed!\n"); - return NULL; - } - - /* Display info message */ - if (comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION) { - LOGP(DSNDCP, LOGL_INFO, - "New header compression entity (%d) created.\n", - comp_entity->entity); - } else { - LOGP(DSNDCP, LOGL_INFO, - "New data compression entity (%d) created.\n", - comp_entity->entity); - } - - return comp_entity; -} - -/* Allocate a compression enitiy list */ -struct llist_head *gprs_sndcp_comp_alloc(const void *ctx) -{ - struct llist_head *lh; - - lh = talloc_zero(ctx, struct llist_head); - INIT_LLIST_HEAD(lh); - - return lh; -} - -/* Free a compression entitiy list */ -void gprs_sndcp_comp_free(struct llist_head *comp_entities) -{ - struct gprs_sndcp_comp *comp_entity; - - /* We expect the caller to take care of allocating a - * compression entity list properly. Attempting to - * free a non existing list clearly points out - * a malfunction. */ - OSMO_ASSERT(comp_entities); - - llist_for_each_entry(comp_entity, comp_entities, list) { - /* Free compression entity */ - switch (comp_entity->compclass) { - case SNDCP_XID_PROTOCOL_COMPRESSION: - LOGP(DSNDCP, LOGL_INFO, - "Deleting header compression entity %d ...\n", - comp_entity->entity); - gprs_sndcp_pcomp_term(comp_entity); - break; - case SNDCP_XID_DATA_COMPRESSION: - LOGP(DSNDCP, LOGL_INFO, - "Deleting data compression entity %d ...\n", - comp_entity->entity); - gprs_sndcp_dcomp_term(comp_entity); - break; - default: - LOGP(DSNDCP, LOGL_INFO, - "Invalid compression class %d!\n", comp_entity->compclass); - OSMO_ASSERT(false); - } - } - - talloc_free(comp_entities); -} - -/* Delete a compression entity */ -void gprs_sndcp_comp_delete(struct llist_head *comp_entities, - unsigned int entity) -{ - struct gprs_sndcp_comp *comp_entity; - struct gprs_sndcp_comp *comp_entity_to_delete = NULL; - - OSMO_ASSERT(comp_entities); - - llist_for_each_entry(comp_entity, comp_entities, list) { - if (comp_entity->entity == entity) { - comp_entity_to_delete = comp_entity; - break; - } - } - - if (!comp_entity_to_delete) - return; - - if (comp_entity_to_delete->compclass == SNDCP_XID_PROTOCOL_COMPRESSION) { - LOGP(DSNDCP, LOGL_INFO, - "Deleting header compression entity %d ...\n", - comp_entity_to_delete->entity); - gprs_sndcp_pcomp_term(comp_entity_to_delete); - } else { - LOGP(DSNDCP, LOGL_INFO, - "Deleting data compression entity %d ...\n", - comp_entity_to_delete->entity); - } - - /* Delete compression entity */ - llist_del(&comp_entity_to_delete->list); - talloc_free(comp_entity_to_delete); -} - -/* Create and Add a new compression entity - * (returns a pointer to the compression entity that has just been created) */ -struct gprs_sndcp_comp *gprs_sndcp_comp_add(const void *ctx, - struct llist_head *comp_entities, - const struct gprs_sndcp_comp_field - *comp_field) -{ - struct gprs_sndcp_comp *comp_entity; - - OSMO_ASSERT(comp_entities); - OSMO_ASSERT(comp_field); - - /* Just to be sure, if the entity is already in - * the list it will be deleted now */ - gprs_sndcp_comp_delete(comp_entities, comp_field->entity); - - /* Create and add a new entity to the list */ - comp_entity = gprs_sndcp_comp_create(ctx, comp_field); - - if (!comp_entity) - return NULL; - - llist_add(&comp_entity->list, comp_entities); - return comp_entity; -} - -/* Find which compression entity handles the specified pcomp/dcomp */ -struct gprs_sndcp_comp *gprs_sndcp_comp_by_comp(const struct llist_head - *comp_entities, uint8_t comp) -{ - struct gprs_sndcp_comp *comp_entity; - int i; - - OSMO_ASSERT(comp_entities); - - llist_for_each_entry(comp_entity, comp_entities, list) { - for (i = 0; i < comp_entity->comp_len; i++) { - if (comp_entity->comp[i] == comp) - return comp_entity; - } - } - - LOGP(DSNDCP, LOGL_ERROR, - "Could not find a matching compression entity for given pcomp/dcomp value %d.\n", - comp); - return NULL; -} - -/* Find which compression entity handles the specified nsapi */ -struct gprs_sndcp_comp *gprs_sndcp_comp_by_nsapi(const struct llist_head - *comp_entities, uint8_t nsapi) -{ - struct gprs_sndcp_comp *comp_entity; - int i; - - OSMO_ASSERT(comp_entities); - - llist_for_each_entry(comp_entity, comp_entities, list) { - for (i = 0; i < comp_entity->nsapi_len; i++) { - if (comp_entity->nsapi[i] == nsapi) - return comp_entity; - } - } - - return NULL; -} - -/* Find a comp_index for a given pcomp/dcomp value */ -uint8_t gprs_sndcp_comp_get_idx(const struct gprs_sndcp_comp *comp_entity, - uint8_t comp) -{ - /* Note: This function returns a normalized version of the comp value, - * which matches up with the position of the comp field. Since comp=0 - * is reserved for "no compression", the index value starts counting - * at one. The return value is the PCOMPn/DCOMPn value one can find - * in the Specification (see e.g. 3GPP TS 44.065, 6.5.3.2, Table 7) */ - - int i; - OSMO_ASSERT(comp_entity); - - /* A pcomp/dcomp value of zero is reserved for "no comproession", - * So we just bail and return zero in this case */ - if (comp == 0) - return 0; - - /* Look in the pcomp/dcomp list for the index */ - for (i = 0; i < comp_entity->comp_len; i++) { - if (comp_entity->comp[i] == comp) - return i + 1; - } - - LOGP(DSNDCP, LOGL_ERROR, - "Could not find a matching comp_index for given pcomp/dcomp value %d\n", - comp); - return 0; -} - -/* Find a pcomp/dcomp value for a given comp_index */ -uint8_t gprs_sndcp_comp_get_comp(const struct gprs_sndcp_comp *comp_entity, - uint8_t comp_index) -{ - OSMO_ASSERT(comp_entity); - - /* A comp_index of zero translates to zero right away. */ - if (comp_index == 0) - return 0; - - if (comp_index > comp_entity->comp_len) { - LOGP(DSNDCP, LOGL_ERROR, - "Could not find a matching pcomp/dcomp value for given comp_index value %d.\n", - comp_index); - return 0; - } - - /* Look in the pcomp/dcomp list for the comp_index, see - * note in gprs_sndcp_comp_get_idx() */ - return comp_entity->comp[comp_index - 1]; -} diff --git a/src/gprs/gprs_sndcp_dcomp.c b/src/gprs/gprs_sndcp_dcomp.c deleted file mode 100644 index c0da84d47..000000000 --- a/src/gprs/gprs_sndcp_dcomp.c +++ /dev/null @@ -1,358 +0,0 @@ -/* GPRS SNDCP data compression handler */ - -/* (C) 2016 by Sysmocom s.f.m.c. GmbH - * All Rights Reserved - * - * Author: Philipp Maier - * - * 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 . - * - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -/* A struct to capture the output data of compressor and decompressor */ -struct v42bis_output_buffer { - uint8_t *buf; - uint8_t *buf_pointer; - int len; -}; - -/* Handler to capture the output data from the compressor */ -void tx_v42bis_frame_handler(void *user_data, const uint8_t *pkt, int len) -{ - struct v42bis_output_buffer *output_buffer = - (struct v42bis_output_buffer *)user_data; - memcpy(output_buffer->buf_pointer, pkt, len); - output_buffer->buf_pointer += len; - output_buffer->len += len; - return; -} - -/* Handler to capture the output data from the decompressor */ -void rx_v42bis_data_handler(void *user_data, const uint8_t *buf, int len) -{ - struct v42bis_output_buffer *output_buffer = - (struct v42bis_output_buffer *)user_data; - memcpy(output_buffer->buf_pointer, buf, len); - output_buffer->buf_pointer += len; - output_buffer->len += len; - return; -} - -/* Initalize data compression */ -int gprs_sndcp_dcomp_init(const void *ctx, struct gprs_sndcp_comp *comp_entity, - const struct gprs_sndcp_comp_field *comp_field) -{ - /* Note: This function is automatically called from - * gprs_sndcp_comp.c when a new data compression - * entity is created by gprs_sndcp.c */ - - OSMO_ASSERT(comp_entity); - OSMO_ASSERT(comp_field); - - if (comp_entity->compclass == SNDCP_XID_DATA_COMPRESSION - && comp_entity->algo.dcomp == V42BIS) { - OSMO_ASSERT(comp_field->v42bis_params); - comp_entity->state = - v42bis_init(ctx, NULL, comp_field->v42bis_params->p0, - comp_field->v42bis_params->p1, - comp_field->v42bis_params->p2, - &tx_v42bis_frame_handler, NULL, - V42BIS_MAX_OUTPUT_LENGTH, - &rx_v42bis_data_handler, NULL, - V42BIS_MAX_OUTPUT_LENGTH); - LOGP(DSNDCP, LOGL_INFO, - "V.42bis data compression initialized.\n"); - return 0; - } - - /* Just in case someone tries to initalize an unknown or unsupported - * data compresson. Since everything is checked during the SNDCP - * negotiation process, this should never happen! */ - OSMO_ASSERT(false); -} - -/* Terminate data compression */ -void gprs_sndcp_dcomp_term(struct gprs_sndcp_comp *comp_entity) -{ - /* Note: This function is automatically called from - * gprs_sndcp_comp.c when a data compression - * entity is deleted by gprs_sndcp.c */ - - OSMO_ASSERT(comp_entity); - - if (comp_entity->compclass == SNDCP_XID_DATA_COMPRESSION - && comp_entity->algo.dcomp == V42BIS) { - if (comp_entity->state) { - v42bis_free((v42bis_state_t *) comp_entity->state); - comp_entity->state = NULL; - } - LOGP(DSNDCP, LOGL_INFO, - "V.42bis data compression terminated.\n"); - return; - } - - /* Just in case someone tries to terminate an unknown or unsupported - * data compresson. Since everything is checked during the SNDCP - * negotiation process, this should never happen! */ - OSMO_ASSERT(false); -} - -/* Perform a full reset of the V.42bis compression state */ -static void v42bis_reset(v42bis_state_t *comp) -{ - /* This function performs a complete reset of the V.42bis compression - * state by reinitalizing the state withe the previously negotiated - * parameters. */ - - int p0, p1, p2; - p0 = comp->decompress.v42bis_parm_p0 | comp->compress.v42bis_parm_p0; - p1 = comp->decompress.v42bis_parm_n2; - p2 = comp->decompress.v42bis_parm_n7; - - DEBUGP(DSNDCP, "Resetting compression state: %p, p0=%d, p1=%d, p2=%d\n", - comp, p0, p1, p2); - - v42bis_init(NULL, comp, p0, p1, p2, &tx_v42bis_frame_handler, NULL, - V42BIS_MAX_OUTPUT_LENGTH, &rx_v42bis_data_handler, NULL, - V42BIS_MAX_OUTPUT_LENGTH); -} - -/* Compress a packet using V.42bis data compression */ -static int v42bis_compress_unitdata(uint8_t *pcomp_index, uint8_t *data, - unsigned int len, v42bis_state_t *comp) -{ - /* Note: This implementation may only be used to compress SN_UNITDATA - * packets, since it resets the compression state for each NPDU. */ - - uint8_t *data_o; - int rc; - int skip = 0; - struct v42bis_output_buffer compressed_data; - - /* Don't bother with short packets */ - if (len < MIN_COMPR_PAYLOAD) - skip = 1; - - /* Skip if compression is not enabled for TX direction */ - if (!comp->compress.v42bis_parm_p0) - skip = 1; - - /* Skip compression */ - if (skip) { - *pcomp_index = 0; - return len; - } - - /* Reset V.42bis compression state */ - v42bis_reset(comp); - - /* Run compressor */ - data_o = talloc_zero_size(comp, len * MAX_DATADECOMPR_FAC); - compressed_data.buf = data_o; - compressed_data.buf_pointer = data_o; - compressed_data.len = 0; - comp->compress.user_data = (&compressed_data); - rc = v42bis_compress(comp, data, len); - if (rc < 0) { - LOGP(DSNDCP, LOGL_ERROR, - "Data compression failed, skipping...\n"); - skip = 1; - } - rc = v42bis_compress_flush(comp); - if (rc < 0) { - LOGP(DSNDCP, LOGL_ERROR, - "Data compression failed, skipping...\n"); - skip = 1; - } - - /* The compressor might yield negative compression gain, in - * this case, we just decide to send the packat as normal, - * uncompressed payload => skip compresssion */ - if (compressed_data.len >= len) { - LOGP(DSNDCP, LOGL_ERROR, - "Data compression ineffective, skipping...\n"); - skip = 1; - } - - /* Skip compression */ - if (skip) { - *pcomp_index = 0; - talloc_free(data_o); - return len; - } - - *pcomp_index = 1; - memcpy(data, data_o, compressed_data.len); - talloc_free(data_o); - - return compressed_data.len; -} - -/* Expand a packet using V.42bis data compression */ -static int v42bis_expand_unitdata(uint8_t *data, unsigned int len, - uint8_t pcomp_index, v42bis_state_t *comp) -{ - /* Note: This implementation may only be used to compress SN_UNITDATA - * packets, since it resets the compression state for each NPDU. */ - - int rc; - struct v42bis_output_buffer uncompressed_data; - uint8_t *data_i; - - /* Skip when the packet is marked as uncompressed */ - if (pcomp_index == 0) { - return len; - } - - /* Reset V.42bis compression state */ - v42bis_reset(comp); - - /* Decompress packet */ - data_i = talloc_zero_size(comp, len); - memcpy(data_i, data, len); - uncompressed_data.buf = data; - uncompressed_data.buf_pointer = data; - uncompressed_data.len = 0; - comp->decompress.user_data = (&uncompressed_data); - rc = v42bis_decompress(comp, data_i, len); - talloc_free(data_i); - if (rc < 0) - return -EINVAL; - rc = v42bis_decompress_flush(comp); - if (rc < 0) - return -EINVAL; - - return uncompressed_data.len; -} - -/* Expand packet */ -int gprs_sndcp_dcomp_expand(uint8_t *data, unsigned int len, uint8_t pcomp, - const struct llist_head *comp_entities) -{ - int rc; - uint8_t pcomp_index = 0; - struct gprs_sndcp_comp *comp_entity; - - OSMO_ASSERT(data); - OSMO_ASSERT(comp_entities); - - LOGP(DSNDCP, LOGL_DEBUG, - "Data compression entity list: comp_entities=%p\n", comp_entities); - - LOGP(DSNDCP, LOGL_DEBUG, "Data compression mode: dcomp=%d\n", pcomp); - - /* Skip on pcomp=0 */ - if (pcomp == 0) { - return len; - } - - /* Find out which compression entity handles the data */ - comp_entity = gprs_sndcp_comp_by_comp(comp_entities, pcomp); - - /* Skip compression if no suitable compression entity can be found */ - if (!comp_entity) { - return len; - } - - /* Note: Only data compression entities may appear in - * data compression context */ - OSMO_ASSERT(comp_entity->compclass == SNDCP_XID_DATA_COMPRESSION); - - /* Note: Currently V42BIS is the only compression method we - * support, so the only allowed algorithm is V42BIS */ - OSMO_ASSERT(comp_entity->algo.dcomp == V42BIS); - - /* Find pcomp_index */ - pcomp_index = gprs_sndcp_comp_get_idx(comp_entity, pcomp); - - /* Run decompression algo */ - rc = v42bis_expand_unitdata(data, len, pcomp_index, comp_entity->state); - - LOGP(DSNDCP, LOGL_DEBUG, - "Data expansion done, old length=%d, new length=%d, entity=%p\n", - len, rc, comp_entity); - - return rc; -} - -/* Compress packet */ -int gprs_sndcp_dcomp_compress(uint8_t *data, unsigned int len, uint8_t *pcomp, - const struct llist_head *comp_entities, - uint8_t nsapi) -{ - int rc; - uint8_t pcomp_index = 0; - struct gprs_sndcp_comp *comp_entity; - - OSMO_ASSERT(data); - OSMO_ASSERT(pcomp); - OSMO_ASSERT(comp_entities); - - LOGP(DSNDCP, LOGL_DEBUG, - "Data compression entity list: comp_entities=%p\n", comp_entities); - - /* Find out which compression entity handles the data */ - comp_entity = gprs_sndcp_comp_by_nsapi(comp_entities, nsapi); - - /* Skip compression if no suitable compression entity can be found */ - if (!comp_entity) { - *pcomp = 0; - return len; - } - - /* Note: Only data compression entities may appear in - * data compression context */ - OSMO_ASSERT(comp_entity->compclass == SNDCP_XID_DATA_COMPRESSION); - - /* Note: Currently V42BIS is the only compression method we - * support, so the only allowed algorithm is V42BIS */ - OSMO_ASSERT(comp_entity->algo.dcomp == V42BIS); - - /* Run compression algo */ - rc = v42bis_compress_unitdata(&pcomp_index, data, len, - comp_entity->state); - - /* Find pcomp value */ - *pcomp = gprs_sndcp_comp_get_comp(comp_entity, pcomp_index); - - LOGP(DSNDCP, LOGL_DEBUG, "Data compression mode: dcomp=%d\n", *pcomp); - - LOGP(DSNDCP, LOGL_DEBUG, - "Data compression done, old length=%d, new length=%d, entity=%p\n", - len, rc, comp_entity); - - return rc; -} diff --git a/src/gprs/gprs_sndcp_pcomp.c b/src/gprs/gprs_sndcp_pcomp.c deleted file mode 100644 index 8c2fc974d..000000000 --- a/src/gprs/gprs_sndcp_pcomp.c +++ /dev/null @@ -1,282 +0,0 @@ -/* GPRS SNDCP header compression handler */ - -/* (C) 2016 by Sysmocom s.f.m.c. GmbH - * All Rights Reserved - * - * Author: Philipp Maier - * - * 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 . - * - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -/* Initalize header compression */ -int gprs_sndcp_pcomp_init(const void *ctx, struct gprs_sndcp_comp *comp_entity, - const struct gprs_sndcp_comp_field *comp_field) -{ - /* Note: This function is automatically called from - * gprs_sndcp_comp.c when a new header compression - * entity is created by gprs_sndcp.c */ - - OSMO_ASSERT(comp_entity); - OSMO_ASSERT(comp_field); - - if (comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION - && comp_entity->algo.pcomp == RFC_1144) { - OSMO_ASSERT(comp_field->rfc1144_params); - comp_entity->state = - slhc_init(ctx, comp_field->rfc1144_params->s01 + 1, - comp_field->rfc1144_params->s01 + 1); - LOGP(DSNDCP, LOGL_INFO, - "RFC1144 header compression initialized.\n"); - return 0; - } - - /* Just in case someone tries to initalize an unknown or unsupported - * header compresson. Since everything is checked during the SNDCP - * negotiation process, this should never happen! */ - OSMO_ASSERT(false); -} - -/* Terminate header compression */ -void gprs_sndcp_pcomp_term(struct gprs_sndcp_comp *comp_entity) -{ - /* Note: This function is automatically called from - * gprs_sndcp_comp.c when a header compression - * entity is deleted by gprs_sndcp.c */ - - OSMO_ASSERT(comp_entity); - - if (comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION - && comp_entity->algo.pcomp == RFC_1144) { - if (comp_entity->state) { - slhc_free((struct slcompress *)comp_entity->state); - comp_entity->state = NULL; - } - LOGP(DSNDCP, LOGL_INFO, - "RFC1144 header compression terminated.\n"); - return; - } - - /* Just in case someone tries to terminate an unknown or unsupported - * data compresson. Since everything is checked during the SNDCP - * negotiation process, this should never happen! */ - OSMO_ASSERT(false); -} - -/* Compress a packet using Van Jacobson RFC1144 header compression */ -static int rfc1144_compress(uint8_t *pcomp_index, uint8_t *data, - unsigned int len, struct slcompress *comp) -{ - uint8_t *comp_ptr; - int compr_len; - uint8_t *data_o; - - /* Create a working copy of the incoming data */ - data_o = talloc_zero_size(comp, len); - memcpy(data_o, data, len); - - /* Run compressor */ - compr_len = slhc_compress(comp, data, len, data_o, &comp_ptr, 0); - - /* Generate pcomp_index */ - if (data_o[0] & SL_TYPE_COMPRESSED_TCP) { - *pcomp_index = 2; - data_o[0] &= ~SL_TYPE_COMPRESSED_TCP; - memcpy(data, data_o, compr_len); - } else if ((data_o[0] & SL_TYPE_UNCOMPRESSED_TCP) == - SL_TYPE_UNCOMPRESSED_TCP) { - *pcomp_index = 1; - data_o[0] &= 0x4F; - memcpy(data, data_o, compr_len); - } else - *pcomp_index = 0; - - talloc_free(data_o); - return compr_len; -} - -/* Expand a packet using Van Jacobson RFC1144 header compression */ -static int rfc1144_expand(uint8_t *data, unsigned int len, uint8_t pcomp_index, - struct slcompress *comp) -{ - int data_decompressed_len; - int type; - - /* Note: this function should never be called with pcomp_index=0, - * since this condition is already filtered - * out by gprs_sndcp_pcomp_expand() */ - - /* Determine the data type by the PCOMP index */ - switch (pcomp_index) { - case 0: - type = SL_TYPE_IP; - break; - case 1: - type = SL_TYPE_UNCOMPRESSED_TCP; - break; - case 2: - type = SL_TYPE_COMPRESSED_TCP; - break; - default: - LOGP(DSNDCP, LOGL_ERROR, - "rfc1144_expand() Invalid pcomp_index value (%d) detected, assuming no compression!\n", - pcomp_index); - type = SL_TYPE_IP; - break; - } - - /* Restore the original version nibble on - * marked uncompressed packets */ - if (type == SL_TYPE_UNCOMPRESSED_TCP) { - /* Just in case the phone tags uncompressed tcp-data - * (normally this is handled by pcomp so there is - * no need for tagging the data) */ - data[0] &= 0x4F; - data_decompressed_len = slhc_remember(comp, data, len); - return data_decompressed_len; - } - - /* Uncompress compressed packets */ - else if (type == SL_TYPE_COMPRESSED_TCP) { - data_decompressed_len = slhc_uncompress(comp, data, len); - return data_decompressed_len; - } - - /* Regular or unknown packets will not be touched */ - return len; -} - -/* Expand packet header */ -int gprs_sndcp_pcomp_expand(uint8_t *data, unsigned int len, uint8_t pcomp, - const struct llist_head *comp_entities) -{ - int rc; - uint8_t pcomp_index = 0; - struct gprs_sndcp_comp *comp_entity; - - OSMO_ASSERT(data); - OSMO_ASSERT(comp_entities); - - LOGP(DSNDCP, LOGL_DEBUG, - "Header compression entity list: comp_entities=%p\n", - comp_entities); - - LOGP(DSNDCP, LOGL_DEBUG, "Header compression mode: pcomp=%d\n", pcomp); - - /* Skip on pcomp=0 */ - if (pcomp == 0) { - return len; - } - - /* Find out which compression entity handles the data */ - comp_entity = gprs_sndcp_comp_by_comp(comp_entities, pcomp); - - /* Skip compression if no suitable compression entity can be found */ - if (!comp_entity) { - return len; - } - - /* Note: Only protocol compression entities may appear in - * protocol compression context */ - OSMO_ASSERT(comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION); - - /* Note: Currently RFC1144 is the only compression method we - * support, so the only allowed algorithm is RFC1144 */ - OSMO_ASSERT(comp_entity->algo.pcomp == RFC_1144); - - /* Find pcomp_index */ - pcomp_index = gprs_sndcp_comp_get_idx(comp_entity, pcomp); - - /* Run decompression algo */ - rc = rfc1144_expand(data, len, pcomp_index, comp_entity->state); - slhc_i_status(comp_entity->state); - slhc_o_status(comp_entity->state); - - LOGP(DSNDCP, LOGL_DEBUG, - "Header expansion done, old length=%d, new length=%d, entity=%p\n", - len, rc, comp_entity); - - return rc; -} - -/* Compress packet header */ -int gprs_sndcp_pcomp_compress(uint8_t *data, unsigned int len, uint8_t *pcomp, - const struct llist_head *comp_entities, - uint8_t nsapi) -{ - int rc; - uint8_t pcomp_index = 0; - struct gprs_sndcp_comp *comp_entity; - - OSMO_ASSERT(data); - OSMO_ASSERT(pcomp); - OSMO_ASSERT(comp_entities); - - LOGP(DSNDCP, LOGL_DEBUG, - "Header compression entity list: comp_entities=%p\n", - comp_entities); - - /* Find out which compression entity handles the data */ - comp_entity = gprs_sndcp_comp_by_nsapi(comp_entities, nsapi); - - /* Skip compression if no suitable compression entity can be found */ - if (!comp_entity) { - *pcomp = 0; - return len; - } - - /* Note: Only protocol compression entities may appear in - * protocol compression context */ - OSMO_ASSERT(comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION); - - /* Note: Currently RFC1144 is the only compression method we - * support, so the only allowed algorithm is RFC1144 */ - OSMO_ASSERT(comp_entity->algo.pcomp == RFC_1144); - - /* Run compression algo */ - rc = rfc1144_compress(&pcomp_index, data, len, comp_entity->state); - slhc_i_status(comp_entity->state); - slhc_o_status(comp_entity->state); - - /* Find pcomp value */ - *pcomp = gprs_sndcp_comp_get_comp(comp_entity, pcomp_index); - - LOGP(DSNDCP, LOGL_DEBUG, "Header compression mode: pcomp=%d\n", *pcomp); - - LOGP(DSNDCP, LOGL_DEBUG, - "Header compression done, old length=%d, new length=%d, entity=%p\n", - len, rc, comp_entity); - return rc; -} diff --git a/src/gprs/gprs_sndcp_vty.c b/src/gprs/gprs_sndcp_vty.c deleted file mode 100644 index 0994a4cd2..000000000 --- a/src/gprs/gprs_sndcp_vty.c +++ /dev/null @@ -1,70 +0,0 @@ -/* VTY interface for our GPRS SNDCP implementation */ - -/* (C) 2010 by Harald Welte - * - * 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 . - * - */ - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -static void vty_dump_sne(struct vty *vty, struct gprs_sndcp_entity *sne) -{ - vty_out(vty, " TLLI %08x SAPI=%u NSAPI=%u:%s", - sne->lle->llme->tlli, sne->lle->sapi, sne->nsapi, VTY_NEWLINE); - vty_out(vty, " Defrag: npdu=%u highest_seg=%u seg_have=0x%08x tot_len=%u%s", - sne->defrag.npdu, sne->defrag.highest_seg, sne->defrag.seg_have, - sne->defrag.tot_len, VTY_NEWLINE); -} - - -DEFUN(show_sndcp, show_sndcp_cmd, - "show sndcp", - SHOW_STR "Display information about the SNDCP protocol") -{ - struct gprs_sndcp_entity *sne; - - vty_out(vty, "State of SNDCP Entities%s", VTY_NEWLINE); - llist_for_each_entry(sne, &gprs_sndcp_entities, list) - vty_dump_sne(vty, sne); - - return CMD_SUCCESS; -} - -int gprs_sndcp_vty_init(void) -{ - install_element_ve(&show_sndcp_cmd); - - return 0; -} diff --git a/src/gprs/gprs_sndcp_xid.c b/src/gprs/gprs_sndcp_xid.c deleted file mode 100644 index a19f64514..000000000 --- a/src/gprs/gprs_sndcp_xid.c +++ /dev/null @@ -1,1901 +0,0 @@ -/* GPRS SNDCP XID field encoding/decoding as per 3GPP TS 44.065 */ - -/* (C) 2016 by sysmocom s.f.m.c. GmbH - * All Rights Reserved - * - * Author: Philipp Maier - * - * 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 . - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -/* When the propose bit in an SNDCP-XID compression field is set to zero, - * the algorithm identifier is stripped. The algoritm parameters are specific - * for each algorithms. The following struct is used to pass the information - * about the referenced algorithm to the parser. */ -struct entity_algo_table { - unsigned int entity; /* see also: 6.5.1.1.3 and 6.6.1.1.3 */ - union gprs_sndcp_comp_algo algo; - enum gprs_sndcp_xid_param_types compclass; -}; - -/* FUNCTIONS RELATED TO SNDCP-XID ENCODING */ - -/* Encode applicable sapis (works the same in all three compression schemes) */ -static int encode_pcomp_applicable_sapis(uint8_t *dst, - const uint8_t *nsapis, - uint8_t nsapis_len) -{ - /* NOTE: Buffer *dst needs offer at 2 bytes - * of space to store the generation results */ - - uint16_t blob; - uint8_t nsapi; - int i; - - /* Bail if number of possible nsapis exceeds valid range - * (Only 11 nsapis possible for PDP-Contexts) */ - OSMO_ASSERT(nsapis_len <= 11); - - /* Encode applicable SAPIs */ - blob = 0; - for (i = 0; i < nsapis_len; i++) { - nsapi = nsapis[i]; - /* Only NSAPI 5 to 15 are applicable for user traffic (PDP- - * contexts). Only for these NSAPIs SNDCP-XID parameters - * can apply. See also 3GPP TS 44.065, 5.1 Service primitives */ - OSMO_ASSERT(nsapi >= 5 && nsapi <= 15); - blob |= (1 << nsapi); - } - - /* Store result */ - *dst = (blob >> 8) & 0xFF; - dst++; - *dst = blob & 0xFF; - - return 2; -} - -/* Encode rfc1144 parameter field - * (see also: 3GPP TS 44.065, 6.5.2.1, Table 5) */ -static int encode_pcomp_rfc1144_params(uint8_t *dst, unsigned int dst_maxlen, - const struct - gprs_sndcp_pcomp_rfc1144_params *params) -{ - /* NOTE: Buffer *dst should offer at least 3 bytes - * of space to store the generation results */ - - int dst_counter = 0; - int rc; - - OSMO_ASSERT(dst_maxlen >= 3); - - /* Zero out buffer */ - memset(dst, 0, dst_maxlen); - - /* Encode applicable SAPIs */ - rc = encode_pcomp_applicable_sapis(dst, params->nsapi, - params->nsapi_len); - dst += rc; - dst_counter += rc; - - /* Encode s01 (see also: 3GPP TS 44.065, 6.5.2.1, Table 5) */ - OSMO_ASSERT(params->s01 >= 0); - OSMO_ASSERT(params->s01 <= 255); - *dst = params->s01; - dst++; - dst_counter++; - - /* Return generated length */ - return dst_counter; -} - -/* - * Encode rfc2507 parameter field - * (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) - */ -static int encode_pcomp_rfc2507_params(uint8_t *dst, unsigned int dst_maxlen, - const struct - gprs_sndcp_pcomp_rfc2507_params *params) -{ - /* NOTE: Buffer *dst should offer at least 3 bytes - * of space to store the generation results */ - - int dst_counter = 0; - int rc; - - OSMO_ASSERT(dst_maxlen >= 9); - - /* Zero out buffer */ - memset(dst, 0, dst_maxlen); - - /* Encode applicable SAPIs */ - rc = encode_pcomp_applicable_sapis(dst, params->nsapi, - params->nsapi_len); - dst += rc; - dst_counter += rc; - - /* Encode F_MAX_PERIOD (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ - OSMO_ASSERT(params->f_max_period >= 1); - OSMO_ASSERT(params->f_max_period <= 65535); - *dst = (params->f_max_period >> 8) & 0xFF; - dst++; - dst_counter++; - *dst = (params->f_max_period) & 0xFF; - dst++; - dst_counter++; - - /* Encode F_MAX_TIME (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ - OSMO_ASSERT(params->f_max_time >= 1); - OSMO_ASSERT(params->f_max_time <= 255); - *dst = params->f_max_time; - dst++; - dst_counter++; - - /* Encode MAX_HEADER (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ - OSMO_ASSERT(params->max_header >= 60); - OSMO_ASSERT(params->max_header <= 255); - *dst = params->max_header; - dst++; - dst_counter++; - - /* Encode TCP_SPACE (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ - OSMO_ASSERT(params->tcp_space >= 3); - OSMO_ASSERT(params->tcp_space <= 255); - *dst = params->tcp_space; - dst++; - dst_counter++; - - /* Encode NON_TCP_SPACE (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ - OSMO_ASSERT(params->non_tcp_space >= 3); - OSMO_ASSERT(params->non_tcp_space <= 65535); - *dst = (params->non_tcp_space >> 8) & 0xFF; - dst++; - dst_counter++; - *dst = (params->non_tcp_space) & 0xFF; - dst++; - dst_counter++; - - /* Return generated length */ - return dst_counter; -} - -/* Encode ROHC parameter field - * (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ -static int encode_pcomp_rohc_params(uint8_t *dst, unsigned int dst_maxlen, - const struct gprs_sndcp_pcomp_rohc_params - *params) -{ - /* NOTE: Buffer *dst should offer at least 36 - * (2 * 16 Profiles + 2 * 3 Parameter) bytes - * of memory space to store generation results */ - - int i; - int dst_counter = 0; - int rc; - - OSMO_ASSERT(dst_maxlen >= 38); - - /* Bail if number of ROHC profiles exceeds limit - * (ROHC supports only a maximum of 16 different profiles) */ - OSMO_ASSERT(params->profile_len <= 16); - - /* Zero out buffer */ - memset(dst, 0, dst_maxlen); - - /* Encode applicable SAPIs */ - rc = encode_pcomp_applicable_sapis(dst, params->nsapi, - params->nsapi_len); - dst += rc; - dst_counter += rc; - - /* Encode MAX_CID (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ - OSMO_ASSERT(params->max_cid >= 0); - OSMO_ASSERT(params->max_cid <= 16383); - *dst = (params->max_cid >> 8) & 0xFF; - dst++; - *dst = params->max_cid & 0xFF; - dst++; - dst_counter += 2; - - /* Encode MAX_HEADER (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ - OSMO_ASSERT(params->max_header >= 60); - OSMO_ASSERT(params->max_header <= 255); - *dst = (params->max_header >> 8) & 0xFF; - dst++; - *dst = params->max_header & 0xFF; - dst++; - dst_counter += 2; - - /* Encode ROHC Profiles (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ - for (i = 0; i < params->profile_len; i++) { - *dst = (params->profile[i] >> 8) & 0xFF; - dst++; - *dst = params->profile[i] & 0xFF; - dst++; - dst_counter += 2; - } - - /* Return generated length */ - return dst_counter; -} - -/* Encode V.42bis parameter field - * (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ -static int encode_dcomp_v42bis_params(uint8_t *dst, unsigned int dst_maxlen, - const struct - gprs_sndcp_dcomp_v42bis_params *params) -{ - /* NOTE: Buffer *dst should offer at least 6 bytes - * of space to store the generation results */ - - int dst_counter = 0; - int rc; - - OSMO_ASSERT(dst_maxlen >= 6); - - /* Zero out buffer */ - memset(dst, 0, dst_maxlen); - - /* Encode applicable SAPIs */ - rc = encode_pcomp_applicable_sapis(dst, params->nsapi, - params->nsapi_len); - dst += rc; - dst_counter += rc; - - /* Encode P0 (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ - OSMO_ASSERT(params->p0 >= 0); - OSMO_ASSERT(params->p0 <= 3); - *dst = params->p0 & 0x03; - dst++; - dst_counter++; - - /* Encode P1 (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ - OSMO_ASSERT(params->p1 >= 512); - OSMO_ASSERT(params->p1 <= 65535); - *dst = (params->p1 >> 8) & 0xFF; - dst++; - *dst = params->p1 & 0xFF; - dst++; - dst_counter += 2; - - /* Encode P2 (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ - OSMO_ASSERT(params->p2 >= 6); - OSMO_ASSERT(params->p2 <= 250); - *dst = params->p2; - dst++; - dst_counter++; - - /* Return generated length */ - return dst_counter; -} - -/* Encode V44 parameter field - * (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ -static int encode_dcomp_v44_params(uint8_t *dst, unsigned int dst_maxlen, - const struct gprs_sndcp_dcomp_v44_params - *params) -{ - /* NOTE: Buffer *dst should offer at least 12 bytes - * of space to store the generation results */ - - int dst_counter = 0; - int rc; - - OSMO_ASSERT(dst_maxlen >= 12); - - /* Zero out buffer */ - memset(dst, 0, dst_maxlen); - - /* Encode applicable SAPIs */ - rc = encode_pcomp_applicable_sapis(dst, params->nsapi, - params->nsapi_len); - dst += rc; - dst_counter += rc; - - /* Encode C0 (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ - OSMO_ASSERT(params->c0 == 0x80 || params->c0 == 0xC0); - *dst = params->c0 & 0xC0; - dst++; - dst_counter++; - - /* Encode P0 (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ - OSMO_ASSERT(params->p0 >= 0); - OSMO_ASSERT(params->p0 <= 3); - *dst = params->p0 & 0x03; - dst++; - dst_counter++; - - /* Encode P1T (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ - OSMO_ASSERT(params->p1t >= 256); - OSMO_ASSERT(params->p1t <= 65535); - *dst = (params->p1t >> 8) & 0xFF; - dst++; - *dst = params->p1t & 0xFF; - dst++; - dst_counter += 2; - - /* Encode P1R (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ - OSMO_ASSERT(params->p1r >= 256); - OSMO_ASSERT(params->p1r <= 65535); - *dst = (params->p1r >> 8) & 0xFF; - dst++; - *dst = params->p1r & 0xFF; - dst++; - dst_counter += 2; - - /* Encode P3T (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ - OSMO_ASSERT(params->p3t >= 0); - OSMO_ASSERT(params->p3t <= 65535); - OSMO_ASSERT(params->p3t >= 2 * params->p1t); - *dst = (params->p3t >> 8) & 0xFF; - dst++; - *dst = params->p3t & 0xFF; - dst++; - dst_counter += 2; - - /* Encode P3R (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ - OSMO_ASSERT(params->p3r >= 0); - OSMO_ASSERT(params->p3r <= 65535); - OSMO_ASSERT(params->p3r >= 2 * params->p1r); - *dst = (params->p3r >> 8) & 0xFF; - dst++; - *dst = params->p3r & 0xFF; - dst++; - dst_counter += 2; - - /* Return generated length */ - return dst_counter; -} - -/* Encode data or protocol control information compression field - * (see also: 3GPP TS 44.065, 6.6.1.1, Figure 9 and - * 3GPP TS 44.065, 6.5.1.1, Figure 7) */ -static int encode_comp_field(uint8_t *dst, unsigned int dst_maxlen, - const struct gprs_sndcp_comp_field *comp_field) -{ - int dst_counter = 0; - int len; - int expected_length; - int i; - enum gprs_sndcp_xid_param_types compclass; - - uint8_t payload_bytes[256]; - int payload_bytes_len = -1; - - /* If possible, try do encode payload bytes first */ - if (comp_field->rfc1144_params) { - payload_bytes_len = - encode_pcomp_rfc1144_params(payload_bytes, - sizeof(payload_bytes), - comp_field->rfc1144_params); - } else if (comp_field->rfc2507_params) { - payload_bytes_len = - encode_pcomp_rfc2507_params(payload_bytes, - sizeof(payload_bytes), - comp_field->rfc2507_params); - } else if (comp_field->rohc_params) { - payload_bytes_len = - encode_pcomp_rohc_params(payload_bytes, - sizeof(payload_bytes), - comp_field->rohc_params); - } else if (comp_field->v42bis_params) { - payload_bytes_len = - encode_dcomp_v42bis_params(payload_bytes, - sizeof(payload_bytes), - comp_field->v42bis_params); - } else if (comp_field->v44_params) { - payload_bytes_len = - encode_dcomp_v44_params(payload_bytes, - sizeof(payload_bytes), - comp_field->v44_params); - } else - OSMO_ASSERT(false); - - /* Bail immediately if payload byte generation failed */ - OSMO_ASSERT(payload_bytes_len >= 0); - - /* Bail if comp_len is out of bounds */ - OSMO_ASSERT(comp_field->comp_len <= sizeof(comp_field->comp)); - - /* Calculate length field of the data block */ - if (comp_field->p) { - len = - payload_bytes_len + - ceil((double)(comp_field->comp_len) / 2.0); - expected_length = len + 3; - } else { - len = payload_bytes_len; - expected_length = len + 2; - } - - /* Bail immediately if no sufficient memory space is supplied */ - OSMO_ASSERT(dst_maxlen >= expected_length); - - /* Check if the entity number is within bounds */ - OSMO_ASSERT(comp_field->entity <= 0x1f); - - /* Check if the algorithm number is within bounds */ - compclass = gprs_sndcp_get_compression_class(comp_field); - switch (compclass) { - case SNDCP_XID_PROTOCOL_COMPRESSION: - OSMO_ASSERT(comp_field->algo.pcomp >= 0 && comp_field->algo.pcomp <= 0x1f); - break; - case SNDCP_XID_DATA_COMPRESSION: - OSMO_ASSERT(comp_field->algo.dcomp >= 0 && comp_field->algo.dcomp <= 0x1f); - break; - default: - OSMO_ASSERT(false); - } - - /* Zero out buffer */ - memset(dst, 0, dst_maxlen); - - /* Encode Propose bit */ - if (comp_field->p) - *dst |= (1 << 7); - - /* Encode entity number */ - *dst |= comp_field->entity & 0x1F; - dst++; - dst_counter++; - - /* Encode algorithm number */ - if (comp_field->p) { - if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION) - *dst |= comp_field->algo.pcomp & 0x1F; - else - *dst |= comp_field->algo.dcomp & 0x1F; - dst++; - dst_counter++; - } - - /* Encode length field */ - *dst |= len & 0xFF; - dst++; - dst_counter++; - - /* Encode PCOMP/DCOMP values */ - if (comp_field->p) { - for (i = 0; i < comp_field->comp_len; i++) { - /* Check if submitted PCOMP/DCOMP - values are within bounds */ - if (comp_field->comp[i] > 0x0F) - return -EINVAL; - - if (i & 1) { - *dst |= comp_field->comp[i] & 0x0F; - dst++; - dst_counter++; - } else - *dst |= (comp_field->comp[i] << 4) & 0xF0; - } - - if (i & 1) { - dst++; - dst_counter++; - } - } - - /* Append payload bytes */ - memcpy(dst, payload_bytes, payload_bytes_len); - dst_counter += payload_bytes_len; - - /* Return generated length */ - return dst_counter; -} - -/* Find out to which compression class the specified comp-field belongs - * (header compression or data compression?) */ -enum gprs_sndcp_xid_param_types gprs_sndcp_get_compression_class(const struct gprs_sndcp_comp_field - *comp_field) -{ - OSMO_ASSERT(comp_field); - - if (comp_field->rfc1144_params) - return SNDCP_XID_PROTOCOL_COMPRESSION; - else if (comp_field->rfc2507_params) - return SNDCP_XID_PROTOCOL_COMPRESSION; - else if (comp_field->rohc_params) - return SNDCP_XID_PROTOCOL_COMPRESSION; - else if (comp_field->v42bis_params) - return SNDCP_XID_DATA_COMPRESSION; - else if (comp_field->v44_params) - return SNDCP_XID_DATA_COMPRESSION; - else - return SNDCP_XID_INVALID_COMPRESSION; -} - -/* Convert all compression fields to bytstreams */ -static int gprs_sndcp_pack_fields(const struct llist_head *comp_fields, - uint8_t *dst, - unsigned int dst_maxlen, int class) -{ - struct gprs_sndcp_comp_field *comp_field; - int byte_counter = 0; - int rc; - - llist_for_each_entry_reverse(comp_field, comp_fields, list) { - if (class == gprs_sndcp_get_compression_class(comp_field)) { - rc = encode_comp_field(dst + byte_counter, - dst_maxlen - byte_counter, - comp_field); - - /* When input data is correct, there is - * no reason for the encoder to fail! */ - OSMO_ASSERT(rc >= 0); - - byte_counter += rc; - } - } - - /* Return generated length */ - return byte_counter; -} - -/* Transform a list with compression fields into an SNDCP-XID message (dst) */ -int gprs_sndcp_compile_xid(uint8_t *dst, unsigned int dst_maxlen, - const struct llist_head *comp_fields, int version) -{ - int rc; - int byte_counter = 0; - uint8_t comp_bytes[512]; - uint8_t xid_version_number[1]; - - OSMO_ASSERT(comp_fields); - OSMO_ASSERT(dst); - OSMO_ASSERT(dst_maxlen >= 2 + sizeof(xid_version_number)); - - /* Prepend header with version number */ - if (version >= 0) { - xid_version_number[0] = (uint8_t) (version & 0xff); - dst = - tlv_put(dst, SNDCP_XID_VERSION_NUMBER, - sizeof(xid_version_number), xid_version_number); - byte_counter += (sizeof(xid_version_number) + 2); - } - - /* Stop if there is no compression fields supplied */ - if (llist_empty(comp_fields)) - return byte_counter; - - /* Add data compression fields */ - rc = gprs_sndcp_pack_fields(comp_fields, comp_bytes, - sizeof(comp_bytes), - SNDCP_XID_DATA_COMPRESSION); - OSMO_ASSERT(rc >= 0); - - if (rc > 0) { - dst = tlv_put(dst, SNDCP_XID_DATA_COMPRESSION, rc, comp_bytes); - byte_counter += rc + 2; - } - - /* Add header compression fields */ - rc = gprs_sndcp_pack_fields(comp_fields, comp_bytes, - sizeof(comp_bytes), - SNDCP_XID_PROTOCOL_COMPRESSION); - OSMO_ASSERT(rc >= 0); - - if (rc > 0) { - dst = tlv_put(dst, SNDCP_XID_PROTOCOL_COMPRESSION, rc, - comp_bytes); - byte_counter += rc + 2; - } - - /* Return generated length */ - return byte_counter; -} - -/* FUNCTIONS RELATED TO SNDCP-XID DECODING */ - -/* Decode applicable sapis (works the same in all three compression schemes) */ -static int decode_pcomp_applicable_sapis(uint8_t *nsapis, - uint8_t *nsapis_len, - const uint8_t *src, - unsigned int src_len) -{ - uint16_t blob; - int i; - int nsapi_len = 0; - - /* Exit immediately if no result can be stored */ - if (!nsapis) - return -EINVAL; - - /* Exit immediately if not enough input data is available */ - if (src_len < 2) - return -EINVAL; - - /* Read bitmask */ - blob = *src; - blob = (blob << 8) & 0xFF00; - src++; - blob |= (*src) & 0xFF; - blob = (blob >> 5); - - /* Decode applicable SAPIs */ - for (i = 0; i < 15; i++) { - if ((blob >> i) & 1) { - nsapis[nsapi_len] = i + 5; - nsapi_len++; - } - } - - /* Return consumed length */ - *nsapis_len = nsapi_len; - return 2; -} - -/* Decode 16 bit field */ -static int decode_pcomp_16_bit_field(int *value_int, uint16_t * value_uint16, - const uint8_t *src, - unsigned int src_len, - int value_min, int value_max) -{ - uint16_t blob; - - /* Reset values to zero (just to be sure) */ - if (value_int) - *value_int = -1; - if (value_uint16) - *value_uint16 = 0; - - /* Exit if not enough src are available */ - if (src_len < 2) - return -EINVAL; - - /* Decode bit value */ - blob = *src; - blob = (blob << 8) & 0xFF00; - src++; - blob |= *src; - - /* Check if parsed value is within bounds */ - if (blob < value_min) - return -EINVAL; - if (blob > value_max) - return -EINVAL; - - /* Hand back results to the caller */ - if (value_int) - *value_int = blob; - if (value_uint16) - *value_uint16 = blob; - - /* Return consumed length */ - return 2; -} - -/* Decode 8 bit field */ -static int decode_pcomp_8_bit_field(int *value_int, uint8_t *value_uint8, - const uint8_t *src, - unsigned int src_len, - int value_min, int value_max) -{ - uint8_t blob; - - /* Reset values to invalid (just to be sure) */ - if (value_int) - *value_int = -1; - if (value_uint8) - *value_uint8 = 0; - - /* Exit if not enough src are available */ - if (src_len < 1) - return -EINVAL; - - /* Decode bit value */ - blob = *src; - - /* Check if parsed value is within bounds */ - if (blob < value_min) - return -EINVAL; - if (blob > value_max) - return -EINVAL; - - /* Hand back results to the caller */ - if (value_int) - *value_int = blob; - if (value_uint8) - *value_uint8 = blob; - - /* Return consumed length */ - return 1; -} - -/* Decode rfc1144 parameter field see also: 3GPP TS 44.065, 6.5.2.1, Table 5) */ -static int decode_pcomp_rfc1144_params(struct gprs_sndcp_pcomp_rfc1144_params - *params, const uint8_t *src, - unsigned int src_len) -{ - int rc; - int byte_counter = 0; - - /* Mark all optional parameters invalid by default */ - params->s01 = -1; - - /* Decode applicable SAPIs */ - rc = decode_pcomp_applicable_sapis(params->nsapi, ¶ms->nsapi_len, - src, src_len); - if (rc > 0) { - byte_counter += rc; - src += rc; - } else - return byte_counter; - - /* Decode parameter S0 -1 - * (see also: 3GPP TS 44.065, 6.5.2.1, Table 5) */ - rc = decode_pcomp_8_bit_field(¶ms->s01, NULL, src, - src_len - byte_counter, 0, 255); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Return consumed length */ - return byte_counter; -} - -/* Decode rfc2507 parameter field - * (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ -static int decode_pcomp_rfc2507_params(struct gprs_sndcp_pcomp_rfc2507_params - *params, const uint8_t *src, - unsigned int src_len) -{ - int rc; - int byte_counter = 0; - - /* Mark all optional parameters invalid by default */ - params->f_max_period = -1; - params->f_max_time = -1; - params->max_header = -1; - params->tcp_space = -1; - params->non_tcp_space = -1; - - /* Decode applicable SAPIs */ - rc = decode_pcomp_applicable_sapis(params->nsapi, ¶ms->nsapi_len, - src, src_len); - if (rc > 0) { - byte_counter += rc; - src += rc; - } else - return byte_counter; - - /* Decode F_MAX_PERIOD (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ - rc = decode_pcomp_16_bit_field(¶ms->f_max_period, NULL, src, - src_len - byte_counter, 1, 65535); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Decode F_MAX_TIME (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ - rc = decode_pcomp_8_bit_field(¶ms->f_max_time, NULL, src, - src_len - byte_counter, 1, 255); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Decode MAX_HEADER (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ - rc = decode_pcomp_8_bit_field(¶ms->max_header, NULL, src, - src_len - byte_counter, 60, 255); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Decode TCP_SPACE (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ - rc = decode_pcomp_8_bit_field(¶ms->tcp_space, NULL, src, - src_len - byte_counter, 3, 255); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Decode NON_TCP_SPACE (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ - rc = decode_pcomp_16_bit_field(¶ms->non_tcp_space, NULL, src, - src_len - byte_counter, 3, 65535); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Return consumed length */ - return byte_counter; -} - -/* Decode ROHC parameter field (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ -static int decode_pcomp_rohc_params(struct gprs_sndcp_pcomp_rohc_params *params, - const uint8_t *src, unsigned int src_len) -{ - int rc; - int byte_counter = 0; - int i; - - /* Mark all optional parameters invalid by default */ - params->max_cid = -1; - params->max_header = -1; - - /* Decode applicable SAPIs */ - rc = decode_pcomp_applicable_sapis(params->nsapi, ¶ms->nsapi_len, - src, src_len); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Decode MAX_CID (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ - rc = decode_pcomp_16_bit_field(¶ms->max_cid, NULL, src, - src_len - byte_counter, 0, 16383); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Decode MAX_HEADER (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ - rc = decode_pcomp_16_bit_field(¶ms->max_header, NULL, src, - src_len - byte_counter, 60, 255); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Decode Profiles (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ - for (i = 0; i < 16; i++) { - params->profile_len = 0; - rc = decode_pcomp_16_bit_field(NULL, ¶ms->profile[i], src, - src_len - byte_counter, 0, - 65535); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - params->profile_len = i + 1; - } - - /* Return consumed length */ - return byte_counter; -} - -/* Decode V.42bis parameter field - * (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ -static int decode_dcomp_v42bis_params(struct gprs_sndcp_dcomp_v42bis_params - *params, const uint8_t *src, - unsigned int src_len) -{ - int rc; - int byte_counter = 0; - - /* Mark all optional parameters invalid by default */ - params->p0 = -1; - params->p1 = -1; - params->p2 = -1; - - /* Decode applicable SAPIs */ - rc = decode_pcomp_applicable_sapis(params->nsapi, ¶ms->nsapi_len, - src, src_len); - if (rc > 0) { - byte_counter += rc; - src += rc; - } else - return byte_counter; - - /* Decode P0 (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ - rc = decode_pcomp_8_bit_field(¶ms->p0, NULL, src, - src_len - byte_counter, 0, 3); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Decode P1 (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ - rc = decode_pcomp_16_bit_field(¶ms->p1, NULL, src, - src_len - byte_counter, 512, 65535); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Decode P2 (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ - rc = decode_pcomp_8_bit_field(¶ms->p2, NULL, src, - src_len - byte_counter, 6, 250); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Return consumed length */ - return byte_counter; -} - -/* Decode V44 parameter field (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ -static int decode_dcomp_v44_params(struct gprs_sndcp_dcomp_v44_params *params, - const uint8_t *src, unsigned int src_len) -{ - int rc; - int byte_counter = 0; - - /* Mark all optional parameters invalid by default */ - params->c0 = -1; - params->p0 = -1; - params->p1t = -1; - params->p1r = -1; - params->p3t = -1; - params->p3r = -1; - - /* Decode applicable SAPIs */ - rc = decode_pcomp_applicable_sapis(params->nsapi, ¶ms->nsapi_len, - src, src_len); - if (rc > 0) { - byte_counter += rc; - src += rc; - } else - return byte_counter; - - /* Decode C0 (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ - rc = decode_pcomp_8_bit_field(¶ms->c0, NULL, src, - src_len - byte_counter, 0, 255); - if (rc <= 0) - return byte_counter; - if ((params->c0 != 0x80) && (params->c0 != 0xC0)) - return -EINVAL; - byte_counter += rc; - src += rc; - - /* Decode P0 (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ - rc = decode_pcomp_8_bit_field(¶ms->p0, NULL, src, - src_len - byte_counter, 0, 3); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Decode P1T (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ - rc = decode_pcomp_16_bit_field(¶ms->p1t, NULL, src, - src_len - byte_counter, 265, 65535); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Decode P1R (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ - rc = decode_pcomp_16_bit_field(¶ms->p1r, NULL, src, - src_len - byte_counter, 265, 65535); - if (rc <= 0) - return byte_counter; - byte_counter += rc; - src += rc; - - /* Decode P3T (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ - rc = decode_pcomp_16_bit_field(¶ms->p3t, NULL, src, - src_len - byte_counter, 265, 65535); - if (rc <= 0) - return byte_counter; - if (params->p3t < 2 * params->p1t) - return -EINVAL; - byte_counter += rc; - src += rc; - - /* Decode P3R (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ - rc = decode_pcomp_16_bit_field(¶ms->p3r, NULL, src, - src_len - byte_counter, 265, 65535); - if (rc <= 0) - return byte_counter; - if (params->p3r < 2 * params->p1r) - return -EINVAL; - byte_counter += rc; - src += rc; - - /* Return consumed length */ - return byte_counter; -} - -/* Lookup protocol compression algorithm identfier by entity ID */ -static enum gprs_sndcp_hdr_comp_algo lookup_algorithm_identifier_pcomp(int entity, - const struct entity_algo_table *lt, - unsigned int lt_len) -{ - int i; - - if (!lt) - return -1; - - for (i = 0; i < lt_len; i++) { - if ((lt[i].entity == entity) - && (lt[i].compclass == SNDCP_XID_PROTOCOL_COMPRESSION)) - return lt[i].algo.pcomp; - } - - return SNDCP_XID_INVALID_COMPRESSION; -} - -/* Lookup a data compression algorithm identfier by entity ID */ -static enum gprs_sndcp_data_comp_algo lookup_algorithm_identifier_dcomp(int entity, - const struct entity_algo_table *lt, - unsigned int lt_len) -{ - int i; - - if (!lt) - return -1; - - for (i = 0; i < lt_len; i++) { - if ((lt[i].entity == entity) - && (lt[i].compclass == SNDCP_XID_DATA_COMPRESSION)) - return lt[i].algo.dcomp; - } - - return SNDCP_XID_INVALID_COMPRESSION; -} - -/* Helper function for decode_comp_field(), decodes - * numeric pcomp/dcomp values */ -static int decode_comp_values(struct gprs_sndcp_comp_field *comp_field, - const uint8_t *src, enum gprs_sndcp_xid_param_types compclass) -{ - int src_counter = 0; - int i; - - if (comp_field->p) { - /* Determine the number of expected PCOMP/DCOMP values */ - switch (compclass) { - case SNDCP_XID_PROTOCOL_COMPRESSION: - /* For protocol compression */ - switch (comp_field->algo.pcomp) { - case RFC_1144: - comp_field->comp_len = RFC1144_PCOMP_NUM; - break; - case RFC_2507: - comp_field->comp_len = RFC2507_PCOMP_NUM; - break; - case ROHC: - comp_field->comp_len = ROHC_PCOMP_NUM; - break; - - /* Exit if the algorithem type encodes - something unknown / unspecified */ - default: - return -EINVAL; - } - break; - case SNDCP_XID_DATA_COMPRESSION: - /* For data compression */ - switch (comp_field->algo.dcomp) { - case V42BIS: - comp_field->comp_len = V42BIS_DCOMP_NUM; - break; - case V44: - comp_field->comp_len = V44_DCOMP_NUM; - break; - - /* Exit if the algorithem type encodes - something unknown / unspecified */ - default: - return -EINVAL; - } - break; - default: - return -EINVAL; - } - - for (i = 0; i < comp_field->comp_len; i++) { - if (i & 1) { - comp_field->comp[i] = (*src) & 0x0F; - src++; - src_counter++; - } else - comp_field->comp[i] = ((*src) >> 4) & 0x0F; - } - - if (i & 1) { - src++; - src_counter++; - } - } - - return src_counter; -} - -/* Helper function for decode_comp_field(), decodes the parameters - * which are protocol compression specific */ -static int decode_pcomp_params(struct gprs_sndcp_comp_field *comp_field, - const uint8_t *src, int src_len) -{ - int rc; - - switch (comp_field->algo.pcomp) { - case RFC_1144: - comp_field->rfc1144_params = talloc_zero(comp_field, struct - gprs_sndcp_pcomp_rfc1144_params); - rc = decode_pcomp_rfc1144_params(comp_field->rfc1144_params, - src, src_len); - if (rc < 0) - talloc_free(comp_field->rfc1144_params); - break; - case RFC_2507: - comp_field->rfc2507_params = talloc_zero(comp_field, struct - gprs_sndcp_pcomp_rfc2507_params); - rc = decode_pcomp_rfc2507_params(comp_field->rfc2507_params, - src, src_len); - if (rc < 0) - talloc_free(comp_field->rfc1144_params); - break; - case ROHC: - comp_field->rohc_params = talloc_zero(comp_field, struct - gprs_sndcp_pcomp_rohc_params); - rc = decode_pcomp_rohc_params(comp_field->rohc_params, src, - src_len); - if (rc < 0) - talloc_free(comp_field->rohc_params); - break; - - /* If no suitable decoder is detected, - leave the remaining bytes undecoded */ - default: - rc = src_len; - } - - if (rc < 0) { - comp_field->rfc1144_params = NULL; - comp_field->rfc2507_params = NULL; - comp_field->rohc_params = NULL; - } - - return rc; -} - -/* Helper function for decode_comp_field(), decodes the parameters - * which are data compression specific */ -static int decode_dcomp_params(struct gprs_sndcp_comp_field *comp_field, - const uint8_t *src, int src_len) -{ - int rc; - - switch (comp_field->algo.dcomp) { - case V42BIS: - comp_field->v42bis_params = talloc_zero(comp_field, struct - gprs_sndcp_dcomp_v42bis_params); - rc = decode_dcomp_v42bis_params(comp_field->v42bis_params, src, - src_len); - if (rc < 0) - talloc_free(comp_field->v42bis_params); - break; - case V44: - comp_field->v44_params = talloc_zero(comp_field, struct - gprs_sndcp_dcomp_v44_params); - rc = decode_dcomp_v44_params(comp_field->v44_params, src, - src_len); - if (rc < 0) - talloc_free(comp_field->v44_params); - break; - - /* If no suitable decoder is detected, - * leave the remaining bytes undecoded */ - default: - rc = src_len; - } - - if (rc < 0) { - comp_field->v42bis_params = NULL; - comp_field->v44_params = NULL; - } - - return rc; -} - -/* Decode data or protocol control information compression field - * (see also: 3GPP TS 44.065, 6.6.1.1, Figure 9 and - * 3GPP TS 44.065, 6.5.1.1, Figure 7) */ -static int decode_comp_field(struct gprs_sndcp_comp_field *comp_field, - const uint8_t *src, unsigned int src_len, - const struct entity_algo_table *lt, - unsigned int lt_len, - enum gprs_sndcp_xid_param_types compclass) -{ - int src_counter = 0; - unsigned int len; - int rc; - - OSMO_ASSERT(comp_field); - - /* Exit immediately if it is clear that no - parseable data is present */ - if (src_len < 1 || !src) - return -EINVAL; - - /* Zero out target struct */ - memset(comp_field, 0, sizeof(struct gprs_sndcp_comp_field)); - - /* Decode Propose bit and Entity number */ - if ((*src) & 0x80) - comp_field->p = 1; - comp_field->entity = (*src) & 0x1F; - src_counter++; - src++; - - /* Decode algorithm number (if present) */ - if (comp_field->p) { - switch (compclass) { - case SNDCP_XID_PROTOCOL_COMPRESSION: - comp_field->algo.pcomp = (*src) & 0x1F; - break; - case SNDCP_XID_DATA_COMPRESSION: - comp_field->algo.dcomp = (*src) & 0x1F; - break; - default: - return -EINVAL; - } - src_counter++; - src++; - } - /* Alternatively take the information from the lookup table */ - else { - switch (compclass) { - case SNDCP_XID_PROTOCOL_COMPRESSION: - comp_field->algo.pcomp = - lookup_algorithm_identifier_pcomp(comp_field->entity, lt, lt_len); - break; - case SNDCP_XID_DATA_COMPRESSION: - comp_field->algo.dcomp = - lookup_algorithm_identifier_dcomp(comp_field->entity, lt, lt_len); - break; - default: - return -EINVAL; - } - } - - /* Decode length field */ - len = *src; - src_counter++; - src++; - - /* Decode PCOMP/DCOMP values */ - rc = decode_comp_values(comp_field, src, compclass); - if (rc < 0) - return -EINVAL; - src_counter += rc; - src += rc; - len -= rc; - - /* Decode algorithm specific payload data */ - if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION) - rc = decode_pcomp_params(comp_field, src, len); - else if (compclass == SNDCP_XID_DATA_COMPRESSION) - rc = decode_dcomp_params(comp_field, src, len); - else - return -EINVAL; - - if (rc >= 0) - src_counter += rc; - else - return -EINVAL; - - /* Return consumed length */ - return src_counter; -} - -/* Helper function for gprs_sndcp_decode_xid() to decode XID blocks */ -static int decode_xid_block(struct llist_head *comp_fields, uint8_t tag, - uint16_t tag_len, const uint8_t *val, - const struct entity_algo_table *lt, - unsigned int lt_len) -{ - struct gprs_sndcp_comp_field *comp_field; - int byte_counter = 0; - int comp_field_count = 0; - int rc; - - byte_counter = 0; - do { - /* Bail if more than the maximum number of - comp_fields is generated */ - if (comp_field_count > MAX_ENTITIES * 2) { - return -EINVAL; - } - - /* Parse and add comp_field */ - comp_field = - talloc_zero(comp_fields, struct gprs_sndcp_comp_field); - - rc = decode_comp_field(comp_field, val + byte_counter, - tag_len - byte_counter, lt, lt_len, tag); - - if (rc < 0) { - talloc_free(comp_field); - return -EINVAL; - } - - byte_counter += rc; - llist_add(&comp_field->list, comp_fields); - comp_field_count++; - } - while (tag_len - byte_counter > 0); - - return byte_counter; -} - -/* Transform an SNDCP-XID message (src) into a list of SNDCP-XID fields */ -static int gprs_sndcp_decode_xid(int *version, struct llist_head *comp_fields, - const uint8_t *src, unsigned int src_len, - const struct entity_algo_table *lt, - unsigned int lt_len) -{ - int src_pos = 0; - uint8_t tag; - uint16_t tag_len; - const uint8_t *val; - int byte_counter = 0; - int rc; - int tlv_count = 0; - - /* Preset version value as invalid */ - if (version) - *version = -1; - - /* Valid TLV-Tag and types */ - static const struct tlv_definition sndcp_xid_def = { - .def = { - [SNDCP_XID_VERSION_NUMBER] = {TLV_TYPE_TLV,}, - [SNDCP_XID_DATA_COMPRESSION] = {TLV_TYPE_TLV,}, - [SNDCP_XID_PROTOCOL_COMPRESSION] = {TLV_TYPE_TLV,}, - }, - }; - - /* Parse TLV-Encoded SNDCP-XID message and defer payload - to the apporpiate sub-parser functions */ - while (1) { - - /* Bail if an the maximum number of TLV fields - * have been parsed */ - if (tlv_count >= 3) { - talloc_free(comp_fields); - return -EINVAL; - } - - /* Parse TLV field */ - rc = tlv_parse_one(&tag, &tag_len, &val, &sndcp_xid_def, - src + src_pos, src_len - src_pos); - if (rc > 0) - src_pos += rc; - else { - talloc_free(comp_fields); - return -EINVAL; - } - - /* Decode sndcp xid version number */ - if (version && tag == SNDCP_XID_VERSION_NUMBER) - *version = val[0]; - - /* Decode compression parameters */ - if ((tag == SNDCP_XID_PROTOCOL_COMPRESSION) - || (tag == SNDCP_XID_DATA_COMPRESSION)) { - rc = decode_xid_block(comp_fields, tag, tag_len, val, - lt, lt_len); - - if (rc < 0) { - talloc_free(comp_fields); - return -EINVAL; - } else - byte_counter += rc; - } - - /* Stop when no further TLV elements can be expected */ - if (src_len - src_pos <= 2) - break; - - tlv_count++; - } - - return 0; -} - -/* Fill up lookutable from a list with comression entitiy fields */ -static int gprs_sndcp_fill_table(struct - entity_algo_table *lt, - unsigned int lt_len, - const struct llist_head *comp_fields) -{ - struct gprs_sndcp_comp_field *comp_field; - int i = 0; - enum gprs_sndcp_xid_param_types compclass; - - if (!comp_fields) - return -EINVAL; - if (!lt) - return -EINVAL; - - memset(lt, 0, sizeof(*lt)); - - llist_for_each_entry(comp_field, comp_fields, list) { - compclass = gprs_sndcp_get_compression_class(comp_field); - switch (compclass) { - case SNDCP_XID_PROTOCOL_COMPRESSION: - lt[i].algo.pcomp = comp_field->algo.pcomp; - break; - case SNDCP_XID_DATA_COMPRESSION: - lt[i].algo.dcomp = comp_field->algo.dcomp; - break; - case SNDCP_XID_INVALID_COMPRESSION: - continue; - default: - memset(lt, 0, sizeof(*lt)); - return -EINVAL; - } - lt[i].compclass = compclass; - lt[i].entity = comp_field->entity; - i++; - } - - return i; -} - -/* Complete comp field params - * (if a param (dst) is not valid, it will be copied from source (src) */ -static int complete_comp_field_params(struct gprs_sndcp_comp_field - *comp_field_dst, const struct - gprs_sndcp_comp_field *comp_field_src) -{ - enum gprs_sndcp_xid_param_types compclass; - - compclass = gprs_sndcp_get_compression_class(comp_field_dst); - if (compclass == SNDCP_XID_INVALID_COMPRESSION) - return -EINVAL; - - if (comp_field_dst->rfc1144_params && comp_field_src->rfc1144_params) { - if (comp_field_dst->rfc1144_params->s01 < 0) { - comp_field_dst->rfc1144_params->s01 = - comp_field_src->rfc1144_params->s01; - } - return 0; - } - - if (comp_field_dst->rfc2507_params && comp_field_src->rfc2507_params) { - - if (comp_field_dst->rfc2507_params->f_max_period < 0) { - comp_field_dst->rfc2507_params->f_max_period = - comp_field_src->rfc2507_params->f_max_period; - } - if (comp_field_dst->rfc2507_params->f_max_time < 0) { - comp_field_dst->rfc2507_params->f_max_time = - comp_field_src->rfc2507_params->f_max_time; - } - if (comp_field_dst->rfc2507_params->max_header < 0) { - comp_field_dst->rfc2507_params->max_header = - comp_field_src->rfc2507_params->max_header; - } - if (comp_field_dst->rfc2507_params->tcp_space < 0) { - comp_field_dst->rfc2507_params->tcp_space = - comp_field_src->rfc2507_params->tcp_space; - } - if (comp_field_dst->rfc2507_params->non_tcp_space < 0) { - comp_field_dst->rfc2507_params->non_tcp_space = - comp_field_src->rfc2507_params->non_tcp_space; - } - return 0; - } - - if (comp_field_dst->rohc_params && comp_field_src->rohc_params) { - if (comp_field_dst->rohc_params->max_cid < 0) { - comp_field_dst->rohc_params->max_cid = - comp_field_src->rohc_params->max_cid; - } - if (comp_field_dst->rohc_params->max_header < 0) { - comp_field_dst->rohc_params->max_header = - comp_field_src->rohc_params->max_header; - } - if (comp_field_dst->rohc_params->profile_len > 0) { - memcpy(comp_field_dst->rohc_params->profile, - comp_field_src->rohc_params->profile, - sizeof(comp_field_dst->rohc_params->profile)); - comp_field_dst->rohc_params->profile_len = - comp_field_src->rohc_params->profile_len; - } - - return 0; - } - - if (comp_field_dst->v42bis_params && comp_field_src->v42bis_params) { - if (comp_field_dst->v42bis_params->p0 < 0) { - comp_field_dst->v42bis_params->p0 = - comp_field_src->v42bis_params->p0; - } - if (comp_field_dst->v42bis_params->p1 < 0) { - comp_field_dst->v42bis_params->p1 = - comp_field_src->v42bis_params->p1; - } - if (comp_field_dst->v42bis_params->p2 < 0) { - comp_field_dst->v42bis_params->p2 = - comp_field_src->v42bis_params->p2; - } - return 0; - } - - if (comp_field_dst->v44_params && comp_field_src->v44_params) { - if (comp_field_dst->v44_params->c0 < 0) { - comp_field_dst->v44_params->c0 = - comp_field_src->v44_params->c0; - } - if (comp_field_dst->v44_params->p0 < 0) { - comp_field_dst->v44_params->p0 = - comp_field_src->v44_params->p0; - } - if (comp_field_dst->v44_params->p1t < 0) { - comp_field_dst->v44_params->p1t = - comp_field_src->v44_params->p1t; - } - if (comp_field_dst->v44_params->p1r < 0) { - comp_field_dst->v44_params->p1r = - comp_field_src->v44_params->p1r; - } - if (comp_field_dst->v44_params->p3t < 0) { - comp_field_dst->v44_params->p3t = - comp_field_src->v44_params->p3t; - } - if (comp_field_dst->v44_params->p3r < 0) { - comp_field_dst->v44_params->p3r = - comp_field_src->v44_params->p3r; - } - return 0; - } - - /* There should be at least exist one param set - * in the destination struct, otherwise something - * must be wrong! */ - return -EINVAL; -} - -/* Complete missing parameters in a comp_field */ -static int gprs_sndcp_complete_comp_field(struct gprs_sndcp_comp_field - *comp_field, const struct llist_head - *comp_fields) -{ - struct gprs_sndcp_comp_field *comp_field_src; - int rc = 0; - - llist_for_each_entry(comp_field_src, comp_fields, list) { - if (comp_field_src->entity == comp_field->entity) { - - /* Complete header fields */ - if (comp_field_src->comp_len > 0) { - memcpy(comp_field->comp, - comp_field_src->comp, - sizeof(comp_field_src->comp)); - comp_field->comp_len = comp_field_src->comp_len; - } - - /* Complete parameter fields */ - rc = complete_comp_field_params(comp_field, - comp_field_src); - } - } - - return rc; -} - -/* Complete missing parameters of all comp_field in a list */ -static int gprs_sndcp_complete_comp_fields(struct llist_head - *comp_fields_incomplete, - const struct llist_head *comp_fields) -{ - struct gprs_sndcp_comp_field *comp_field_incomplete; - int rc; - - llist_for_each_entry(comp_field_incomplete, comp_fields_incomplete, - list) { - - rc = gprs_sndcp_complete_comp_field(comp_field_incomplete, - comp_fields); - if (rc < 0) - return -EINVAL; - - } - - return 0; -} - -/* Transform an SNDCP-XID message (src) into a list of SNDCP-XID fields */ -struct llist_head *gprs_sndcp_parse_xid(int *version, - const void *ctx, - const uint8_t *src, - unsigned int src_len, - const struct llist_head - *comp_fields_req) -{ - int rc; - int lt_len; - struct llist_head *comp_fields; - struct entity_algo_table lt[MAX_ENTITIES * 2]; - - /* In case of a zero length field, just exit */ - if (src_len == 0) - return NULL; - - /* We should go any further if we have a field length greater - * zero and a null pointer as buffer! */ - OSMO_ASSERT(src); - - comp_fields = talloc_zero(ctx, struct llist_head); - INIT_LLIST_HEAD(comp_fields); - - if (comp_fields_req) { - /* Generate lookup table */ - lt_len = - gprs_sndcp_fill_table(lt, MAX_ENTITIES * 2, - comp_fields_req); - if (lt_len < 0) { - talloc_free(comp_fields); - return NULL; - } - - /* Parse SNDCP-CID XID-Field */ - rc = gprs_sndcp_decode_xid(version, comp_fields, src, src_len, - lt, lt_len); - if (rc < 0) { - talloc_free(comp_fields); - return NULL; - } - - rc = gprs_sndcp_complete_comp_fields(comp_fields, - comp_fields_req); - if (rc < 0) { - talloc_free(comp_fields); - return NULL; - } - - } else { - /* Parse SNDCP-CID XID-Field */ - rc = gprs_sndcp_decode_xid(version, comp_fields, src, src_len, - NULL, 0); - if (rc < 0) { - talloc_free(comp_fields); - return NULL; - } - } - - return comp_fields; -} - -/* Helper for gprs_sndcp_dump_comp_fields(), - * dumps protocol compression parameters */ -static void dump_pcomp_params(const struct gprs_sndcp_comp_field - *comp_field, unsigned int logl) -{ - int i; - - switch (comp_field->algo.pcomp) { - case RFC_1144: - if (comp_field->rfc1144_params == NULL) { - LOGP(DSNDCP, logl, - " gprs_sndcp_pcomp_rfc1144_params=NULL\n"); - break; - } - LOGP(DSNDCP, logl, " gprs_sndcp_pcomp_rfc1144_params {\n"); - LOGP(DSNDCP, logl, - " nsapi_len=%d;\n", - comp_field->rfc1144_params->nsapi_len); - if (comp_field->rfc1144_params->nsapi_len == 0) - LOGP(DSNDCP, logl, " nsapi[] = NULL;\n"); - for (i = 0; i < comp_field->rfc1144_params->nsapi_len; i++) { - LOGP(DSNDCP, logl, - " nsapi[%d]=%d;\n", i, - comp_field->rfc1144_params->nsapi[i]); - } - LOGP(DSNDCP, logl, " s01=%d;\n", - comp_field->rfc1144_params->s01); - LOGP(DSNDCP, logl, " }\n"); - break; - case RFC_2507: - if (comp_field->rfc2507_params == NULL) { - LOGP(DSNDCP, logl, - " gprs_sndcp_pcomp_rfc2507_params=NULL\n"); - break; - } - LOGP(DSNDCP, logl, " gprs_sndcp_pcomp_rfc2507_params {\n"); - LOGP(DSNDCP, logl, - " nsapi_len=%d;\n", - comp_field->rfc2507_params->nsapi_len); - if (comp_field->rfc2507_params->nsapi_len == 0) - LOGP(DSNDCP, logl, " nsapi[] = NULL;\n"); - for (i = 0; i < comp_field->rfc2507_params->nsapi_len; i++) { - LOGP(DSNDCP, logl, - " nsapi[%d]=%d;\n", i, - comp_field->rfc2507_params->nsapi[i]); - } - LOGP(DSNDCP, logl, - " f_max_period=%d;\n", - comp_field->rfc2507_params->f_max_period); - LOGP(DSNDCP, logl, - " f_max_time=%d;\n", - comp_field->rfc2507_params->f_max_time); - LOGP(DSNDCP, logl, - " max_header=%d;\n", - comp_field->rfc2507_params->max_header); - LOGP(DSNDCP, logl, - " tcp_space=%d;\n", - comp_field->rfc2507_params->tcp_space); - LOGP(DSNDCP, logl, - " non_tcp_space=%d;\n", - comp_field->rfc2507_params->non_tcp_space); - LOGP(DSNDCP, logl, " }\n"); - break; - case ROHC: - if (comp_field->rohc_params == NULL) { - LOGP(DSNDCP, logl, - " gprs_sndcp_pcomp_rohc_params=NULL\n"); - break; - } - LOGP(DSNDCP, logl, " gprs_sndcp_pcomp_rohc_params {\n"); - LOGP(DSNDCP, logl, - " nsapi_len=%d;\n", - comp_field->rohc_params->nsapi_len); - if (comp_field->rohc_params->nsapi_len == 0) - LOGP(DSNDCP, logl, " nsapi[] = NULL;\n"); - for (i = 0; i < comp_field->rohc_params->nsapi_len; i++) { - LOGP(DSNDCP, logl, - " nsapi[%d]=%d;\n", i, - comp_field->rohc_params->nsapi[i]); - } - LOGP(DSNDCP, logl, - " max_cid=%d;\n", comp_field->rohc_params->max_cid); - LOGP(DSNDCP, logl, - " max_header=%d;\n", - comp_field->rohc_params->max_header); - LOGP(DSNDCP, logl, - " profile_len=%d;\n", - comp_field->rohc_params->profile_len); - if (comp_field->rohc_params->profile_len == 0) - LOGP(DSNDCP, logl, " profile[] = NULL;\n"); - for (i = 0; i < comp_field->rohc_params->profile_len; i++) - LOGP(DSNDCP, logl, - " profile[%d]=%04x;\n", - i, comp_field->rohc_params->profile[i]); - LOGP(DSNDCP, logl, " }\n"); - break; - } - -} - -/* Helper for gprs_sndcp_dump_comp_fields(), - * data protocol compression parameters */ -static void dump_dcomp_params(const struct gprs_sndcp_comp_field - *comp_field, unsigned int logl) -{ - int i; - - switch (comp_field->algo.dcomp) { - case V42BIS: - if (comp_field->v42bis_params == NULL) { - LOGP(DSNDCP, logl, - " gprs_sndcp_dcomp_v42bis_params=NULL\n"); - break; - } - LOGP(DSNDCP, logl, " gprs_sndcp_dcomp_v42bis_params {\n"); - LOGP(DSNDCP, logl, - " nsapi_len=%d;\n", - comp_field->v42bis_params->nsapi_len); - if (comp_field->v42bis_params->nsapi_len == 0) - LOGP(DSNDCP, logl, " nsapi[] = NULL;\n"); - for (i = 0; i < comp_field->v42bis_params->nsapi_len; i++) - LOGP(DSNDCP, logl, - " nsapi[%d]=%d;\n", i, - comp_field->v42bis_params->nsapi[i]); - LOGP(DSNDCP, logl, " p0=%d;\n", - comp_field->v42bis_params->p0); - LOGP(DSNDCP, logl, " p1=%d;\n", - comp_field->v42bis_params->p1); - LOGP(DSNDCP, logl, " p2=%d;\n", - comp_field->v42bis_params->p2); - LOGP(DSNDCP, logl, " }\n"); - break; - case V44: - if (comp_field->v44_params == NULL) { - LOGP(DSNDCP, logl, - " gprs_sndcp_dcomp_v44_params=NULL\n"); - break; - } - LOGP(DSNDCP, logl, " gprs_sndcp_dcomp_v44_params {\n"); - LOGP(DSNDCP, logl, - " nsapi_len=%d;\n", - comp_field->v44_params->nsapi_len); - if (comp_field->v44_params->nsapi_len == 0) - LOGP(DSNDCP, logl, " nsapi[] = NULL;\n"); - for (i = 0; i < comp_field->v44_params->nsapi_len; i++) { - LOGP(DSNDCP, logl, - " nsapi[%d]=%d;\n", i, - comp_field->v44_params->nsapi[i]); - } - LOGP(DSNDCP, logl, " c0=%d;\n", - comp_field->v44_params->c0); - LOGP(DSNDCP, logl, " p0=%d;\n", - comp_field->v44_params->p0); - LOGP(DSNDCP, logl, " p1t=%d;\n", - comp_field->v44_params->p1t); - LOGP(DSNDCP, logl, " p1r=%d;\n", - comp_field->v44_params->p1r); - LOGP(DSNDCP, logl, " p3t=%d;\n", - comp_field->v44_params->p3t); - LOGP(DSNDCP, logl, " p3r=%d;\n", - comp_field->v44_params->p3r); - LOGP(DSNDCP, logl, " }\n"); - break; - } -} - -/* Dump a list with SNDCP-XID fields (Debug) */ -void gprs_sndcp_dump_comp_fields(const struct llist_head *comp_fields, - unsigned int logl) -{ - struct gprs_sndcp_comp_field *comp_field; - int i; - enum gprs_sndcp_xid_param_types compclass; - - OSMO_ASSERT(comp_fields); - - llist_for_each_entry(comp_field, comp_fields, list) { - compclass = gprs_sndcp_get_compression_class(comp_field); - LOGP(DSNDCP, logl, "SNDCP-XID:\n"); - LOGP(DSNDCP, logl, "struct gprs_sndcp_comp_field {\n"); - LOGP(DSNDCP, logl, " entity=%d;\n", comp_field->entity); - switch (compclass) { - case SNDCP_XID_PROTOCOL_COMPRESSION: - LOGP(DSNDCP, logl, " algo=%d;\n", comp_field->algo.pcomp); - break; - case SNDCP_XID_DATA_COMPRESSION: - LOGP(DSNDCP, logl, " algo=%d;\n", comp_field->algo.dcomp); - break; - default: - LOGP(DSNDCP, logl, " algo invalid!\n"); - break; - } - LOGP(DSNDCP, logl, " comp_len=%d;\n", comp_field->comp_len); - if (comp_field->comp_len == 0) - LOGP(DSNDCP, logl, " comp[] = NULL;\n"); - for (i = 0; i < comp_field->comp_len; i++) { - LOGP(DSNDCP, logl, " comp[%d]=%d;\n", i, - comp_field->comp[i]); - } - - switch (compclass) { - case SNDCP_XID_PROTOCOL_COMPRESSION: - dump_pcomp_params(comp_field, logl); - break; - case SNDCP_XID_DATA_COMPRESSION: - dump_dcomp_params(comp_field, logl); - break; - default: - LOGP(DSNDCP, logl, " compression algorithm invalid!\n"); - break; - } - - LOGP(DSNDCP, logl, "}\n"); - } - -} diff --git a/src/gprs/gprs_subscriber.c b/src/gprs/gprs_subscriber.c deleted file mode 100644 index 484c7ef4e..000000000 --- a/src/gprs/gprs_subscriber.c +++ /dev/null @@ -1,949 +0,0 @@ -/* MS subscriber data handling */ - -/* (C) 2014 by sysmocom s.f.m.c. GmbH - * (C) 2015 by Holger Hans Peter Freyther - * - * 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 . - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include -#include -#include - -#define SGSN_SUBSCR_MAX_RETRIES 3 -#define SGSN_SUBSCR_RETRY_INTERVAL 10 - -#define LOGGSUPP(level, gsup, fmt, args...) \ - LOGP(DGPRS, level, "GSUP(%s) " fmt, \ - (gsup)->imsi, \ - ## args) - -extern void *tall_sgsn_ctx; - -LLIST_HEAD(_gprs_subscribers); -struct llist_head * const gprs_subscribers = &_gprs_subscribers; - -static int gsup_read_cb(struct osmo_gsup_client *gsupc, struct msgb *msg); - -/* TODO: Some functions are specific to the SGSN, but this file is more general - * (it has gprs_* name). Either move these functions elsewhere, split them and - * move a part, or replace the gprs_ prefix by sgsn_. The applies to - * gprs_subscr_init, gsup_read_cb, and gprs_subscr_tx_gsup_message. - */ - -int gprs_subscr_init(struct sgsn_instance *sgi) -{ - const char *addr_str; - struct ipaccess_unit *ipa_dev; - - if (!sgi->cfg.gsup_server_addr.sin_addr.s_addr) - return 0; - - addr_str = inet_ntoa(sgi->cfg.gsup_server_addr.sin_addr); - - ipa_dev = talloc_zero(sgi, struct ipaccess_unit); - ipa_dev->unit_name = "SGSN"; - ipa_dev->serno = sgi->cfg.sgsn_ipa_name; /* NULL unless configured via VTY */ - - sgi->gsup_client = osmo_gsup_client_create2( - sgi, - ipa_dev, - addr_str, sgi->cfg.gsup_server_port, - &gsup_read_cb, - &sgi->cfg.oap); - - if (!sgi->gsup_client) - return -1; - - return 1; -} - -static int gsup_read_cb(struct osmo_gsup_client *gsupc, struct msgb *msg) -{ - int rc; - - rc = gprs_subscr_rx_gsup_message(msg); - msgb_free(msg); - if (rc < 0) - return -1; - - return rc; -} - -int gprs_subscr_purge(struct gprs_subscr *subscr); - -static struct sgsn_subscriber_data *sgsn_subscriber_data_alloc(void *ctx) -{ - struct sgsn_subscriber_data *sdata; - int idx; - - sdata = talloc_zero(ctx, struct sgsn_subscriber_data); - - sdata->error_cause = SGSN_ERROR_CAUSE_NONE; - - for (idx = 0; idx < ARRAY_SIZE(sdata->auth_triplets); idx++) - sdata->auth_triplets[idx].key_seq = GSM_KEY_SEQ_INVAL; - - INIT_LLIST_HEAD(&sdata->pdp_list); - - return sdata; -} - -struct sgsn_subscriber_pdp_data* sgsn_subscriber_pdp_data_alloc( - struct sgsn_subscriber_data *sdata) -{ - struct sgsn_subscriber_pdp_data* pdata; - - pdata = talloc_zero(sdata, struct sgsn_subscriber_pdp_data); - - llist_add_tail(&pdata->list, &sdata->pdp_list); - - return pdata; -} - -struct gprs_subscr *gprs_subscr_get_by_imsi(const char *imsi) -{ - struct gprs_subscr *gsub; - - if (!imsi || !*imsi) - return NULL; - - llist_for_each_entry(gsub, gprs_subscribers, entry) { - if (!strcmp(gsub->imsi, imsi)) - return gprs_subscr_get(gsub); - } - return NULL; -} - -static struct gprs_subscr *gprs_subscr_alloc(void) -{ - struct gprs_subscr *gsub; - gsub = talloc_zero(tall_sgsn_ctx, struct gprs_subscr); - if (!gsub) - return NULL; - llist_add_tail(&gsub->entry, gprs_subscribers); - gsub->use_count = 1; - gsub->tmsi = GSM_RESERVED_TMSI; - return gsub; -} - -struct gprs_subscr *gprs_subscr_get_or_create(const char *imsi) -{ - struct gprs_subscr *gsub; - - gsub = gprs_subscr_get_by_imsi(imsi); - if (!gsub) { - gsub = gprs_subscr_alloc(); - if (!gsub) - return NULL; - osmo_strlcpy(gsub->imsi, imsi, sizeof(gsub->imsi)); - } - - if (!gsub->sgsn_data) - gsub->sgsn_data = sgsn_subscriber_data_alloc(gsub); - return gsub; -} - -void gprs_subscr_cleanup(struct gprs_subscr *subscr) -{ - if (subscr->sgsn_data->mm) { - gprs_subscr_put(subscr->sgsn_data->mm->subscr); - subscr->sgsn_data->mm->subscr = NULL; - subscr->sgsn_data->mm = NULL; - } - - if (subscr->flags & GPRS_SUBSCRIBER_ENABLE_PURGE) { - gprs_subscr_purge(subscr); - subscr->flags &= ~GPRS_SUBSCRIBER_ENABLE_PURGE; - } -} - -void gprs_subscr_cancel(struct gprs_subscr *subscr) -{ - subscr->authorized = 0; - subscr->flags |= GPRS_SUBSCRIBER_CANCELLED; - subscr->flags &= ~GPRS_SUBSCRIBER_ENABLE_PURGE; - - gprs_subscr_update(subscr); - gprs_subscr_cleanup(subscr); -} - -static int gprs_subscr_tx_gsup_message(struct gprs_subscr *subscr, - struct osmo_gsup_message *gsup_msg) -{ - struct msgb *msg = osmo_gsup_client_msgb_alloc(); - - if (strlen(gsup_msg->imsi) == 0 && subscr) - osmo_strlcpy(gsup_msg->imsi, subscr->imsi, - sizeof(gsup_msg->imsi)); - gsup_msg->cn_domain = OSMO_GSUP_CN_DOMAIN_PS; - osmo_gsup_encode(msg, gsup_msg); - - LOGGSUBSCRP(LOGL_INFO, subscr, - "Sending GSUP, will send: %s\n", msgb_hexdump(msg)); - - if (!sgsn->gsup_client) { - msgb_free(msg); - return -ENOTSUP; - } - - return osmo_gsup_client_send(sgsn->gsup_client, msg); -} - -static int gprs_subscr_tx_gsup_error_reply(struct gprs_subscr *subscr, - struct osmo_gsup_message *gsup_orig, - enum gsm48_gmm_cause cause) -{ - struct osmo_gsup_message gsup_reply = {0}; - - osmo_strlcpy(gsup_reply.imsi, gsup_orig->imsi, - sizeof(gsup_reply.imsi)); - gsup_reply.cause = cause; - gsup_reply.message_type = - OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type); - - return gprs_subscr_tx_gsup_message(subscr, &gsup_reply); -} - -static int gprs_subscr_handle_gsup_auth_res(struct gprs_subscr *subscr, - struct osmo_gsup_message *gsup_msg) -{ - unsigned idx; - struct sgsn_subscriber_data *sdata = subscr->sgsn_data; - - LOGGSUBSCRP(LOGL_INFO, subscr, - "Got SendAuthenticationInfoResult, num_auth_vectors = %zu\n", - gsup_msg->num_auth_vectors); - - if (gsup_msg->num_auth_vectors > 0) { - memset(sdata->auth_triplets, 0, sizeof(sdata->auth_triplets)); - - for (idx = 0; idx < ARRAY_SIZE(sdata->auth_triplets); idx++) - sdata->auth_triplets[idx].key_seq = GSM_KEY_SEQ_INVAL; - } - - for (idx = 0; idx < gsup_msg->num_auth_vectors; idx++) { - size_t key_seq = idx; - LOGGSUBSCRP(LOGL_DEBUG, subscr, - "Adding auth tuple, cksn = %zu\n", key_seq); - if (key_seq >= ARRAY_SIZE(sdata->auth_triplets)) { - LOGGSUBSCRP(LOGL_NOTICE, subscr, - "Skipping auth triplet with invalid cksn %zu\n", - key_seq); - continue; - } - sdata->auth_triplets[key_seq].vec = gsup_msg->auth_vectors[idx]; - sdata->auth_triplets[key_seq].key_seq = key_seq; - } - - sdata->auth_triplets_updated = 1; - sdata->error_cause = SGSN_ERROR_CAUSE_NONE; - - gprs_subscr_update_auth_info(subscr); - - return 0; -} - -static int gprs_subscr_pdp_data_clear(struct gprs_subscr *subscr) -{ - struct sgsn_subscriber_pdp_data *pdp, *pdp2; - int count = 0; - - llist_for_each_entry_safe(pdp, pdp2, &subscr->sgsn_data->pdp_list, list) { - llist_del(&pdp->list); - talloc_free(pdp); - count += 1; - } - - return count; -} - -static struct sgsn_subscriber_pdp_data *gprs_subscr_pdp_data_get_by_id( - struct gprs_subscr *subscr, unsigned context_id) -{ - struct sgsn_subscriber_pdp_data *pdp; - - llist_for_each_entry(pdp, &subscr->sgsn_data->pdp_list, list) { - if (pdp->context_id == context_id) - return pdp; - } - - return NULL; -} - - -static void gprs_subscr_gsup_insert_data(struct gprs_subscr *subscr, - struct osmo_gsup_message *gsup_msg) -{ - struct sgsn_subscriber_data *sdata = subscr->sgsn_data; - unsigned idx; - int rc; - - if (gsup_msg->msisdn_enc) { - if (gsup_msg->msisdn_enc_len > sizeof(sdata->msisdn)) { - LOGP(DGPRS, LOGL_ERROR, "MSISDN too long (%zu)\n", - gsup_msg->msisdn_enc_len); - sdata->msisdn_len = 0; - } else { - memcpy(sdata->msisdn, gsup_msg->msisdn_enc, - gsup_msg->msisdn_enc_len); - sdata->msisdn_len = gsup_msg->msisdn_enc_len; - } - } - - if (gsup_msg->hlr_enc) { - if (gsup_msg->hlr_enc_len > sizeof(sdata->hlr)) { - LOGP(DGPRS, LOGL_ERROR, "HLR-Number too long (%zu)\n", - gsup_msg->hlr_enc_len); - sdata->hlr_len = 0; - } else { - memcpy(sdata->hlr, gsup_msg->hlr_enc, - gsup_msg->hlr_enc_len); - sdata->hlr_len = gsup_msg->hlr_enc_len; - } - } - - if (gsup_msg->pdp_charg_enc && gsup_msg->pdp_charg_enc_len >= sizeof(sdata->pdp_charg)) { - memcpy(&sdata->pdp_charg, gsup_msg->pdp_charg_enc, sizeof(sdata->pdp_charg)); - sdata->has_pdp_charg = 1; - } else { - sdata->has_pdp_charg = 0; - } - - if (gsup_msg->pdp_info_compl) { - rc = gprs_subscr_pdp_data_clear(subscr); - if (rc > 0) - LOGP(DGPRS, LOGL_INFO, "Cleared existing PDP info\n"); - } - - for (idx = 0; idx < gsup_msg->num_pdp_infos; idx++) { - struct osmo_gsup_pdp_info *pdp_info = &gsup_msg->pdp_infos[idx]; - size_t ctx_id = pdp_info->context_id; - struct sgsn_subscriber_pdp_data *pdp_data; - - if (pdp_info->apn_enc_len >= sizeof(pdp_data->apn_str)-1) { - LOGGSUBSCRP(LOGL_ERROR, subscr, - "APN too long, context id = %zu, APN = %s\n", - ctx_id, osmo_hexdump(pdp_info->apn_enc, - pdp_info->apn_enc_len)); - continue; - } - - if (pdp_info->qos_enc_len > sizeof(pdp_data->qos_subscribed)) { - LOGGSUBSCRP(LOGL_ERROR, subscr, - "QoS info too long (%zu)\n", - pdp_info->qos_enc_len); - continue; - } - - LOGGSUBSCRP(LOGL_INFO, subscr, - "Will set PDP info, context id = %zu, APN = %s\n", - ctx_id, osmo_hexdump(pdp_info->apn_enc, pdp_info->apn_enc_len)); - - /* Set PDP info [ctx_id] */ - pdp_data = gprs_subscr_pdp_data_get_by_id(subscr, ctx_id); - if (!pdp_data) { - pdp_data = sgsn_subscriber_pdp_data_alloc(subscr->sgsn_data); - pdp_data->context_id = ctx_id; - } - - OSMO_ASSERT(pdp_data != NULL); - pdp_data->pdp_type = pdp_info->pdp_type; - osmo_apn_to_str(pdp_data->apn_str, - pdp_info->apn_enc, pdp_info->apn_enc_len); - - if (pdp_info->qos_enc) { - memcpy(&pdp_data->qos_subscribed[0], pdp_info->qos_enc, - pdp_info->qos_enc_len); - } - pdp_data->qos_subscribed_len = pdp_info->qos_enc_len; - - if (pdp_info->pdp_charg_enc && pdp_info->pdp_charg_enc_len >= sizeof(pdp_data->pdp_charg)) { - memcpy(&pdp_data->pdp_charg, pdp_info->pdp_charg_enc, sizeof(pdp_data->pdp_charg)); - pdp_data->has_pdp_charg = 1; - } else { - pdp_data->has_pdp_charg = 0; - } - } -} - -static int gprs_subscr_handle_gsup_upd_loc_res(struct gprs_subscr *subscr, - struct osmo_gsup_message *gsup_msg) -{ - /* contrary to MAP, we allow piggy-backing subscriber data onto - * the UPDATE LOCATION RESULT, and don't mandate the use of a - * separate nested INSERT SUBSCRIBER DATA transaction */ - gprs_subscr_gsup_insert_data(subscr, gsup_msg); - - subscr->authorized = 1; - subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE; - - subscr->flags |= GPRS_SUBSCRIBER_ENABLE_PURGE; - - gprs_subscr_update(subscr); - return 0; -} - -static int gprs_subscr_handle_gsup_dsd_req(struct gprs_subscr *subscr, - struct osmo_gsup_message *gsup_msg) -{ - struct osmo_gsup_message gsup_reply = {0}; - - if (gsup_msg->cn_domain != OSMO_GSUP_CN_DOMAIN_PS) { - LOGGSUBSCRP(LOGL_ERROR, subscr, - "Rx GSUP message %s not supported for CS\n", - osmo_gsup_message_type_name(gsup_msg->message_type)); - gsup_reply.cause = GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL; - gsup_reply.message_type = OSMO_GSUP_MSGT_DELETE_DATA_ERROR; - } else { - gsm0408_gprs_access_cancelled(subscr->sgsn_data->mm, - GMM_CAUSE_GPRS_NOTALLOWED); - gsup_reply.message_type = OSMO_GSUP_MSGT_DELETE_DATA_RESULT; - } - - return gprs_subscr_tx_gsup_message(subscr, &gsup_reply); -} - -static int gprs_subscr_handle_gsup_isd_req(struct gprs_subscr *subscr, - struct osmo_gsup_message *gsup_msg) -{ - struct osmo_gsup_message gsup_reply = {0}; - - gprs_subscr_gsup_insert_data(subscr, gsup_msg); - - subscr->authorized = 1; - subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE; - subscr->flags |= GPRS_SUBSCRIBER_ENABLE_PURGE; - gprs_subscr_update(subscr); - - gsup_reply.message_type = OSMO_GSUP_MSGT_INSERT_DATA_RESULT; - return gprs_subscr_tx_gsup_message(subscr, &gsup_reply); -} - -static int check_cause(int cause) -{ - switch (cause) { - case GMM_CAUSE_IMSI_UNKNOWN ... GMM_CAUSE_ILLEGAL_ME: - case GMM_CAUSE_GPRS_NOTALLOWED ... GMM_CAUSE_NO_GPRS_PLMN: - return EACCES; - - case GMM_CAUSE_MSC_TEMP_NOTREACH ... GMM_CAUSE_CONGESTION: - return EHOSTUNREACH; - - case GMM_CAUSE_SEM_INCORR_MSG ... GMM_CAUSE_PROTO_ERR_UNSPEC: - default: - return EINVAL; - } -} - -static int gprs_subscr_handle_gsup_auth_err(struct gprs_subscr *subscr, - struct osmo_gsup_message *gsup_msg) -{ - unsigned idx; - struct sgsn_subscriber_data *sdata = subscr->sgsn_data; - int cause_err; - - cause_err = check_cause(gsup_msg->cause); - - LOGGSUBSCRP(LOGL_DEBUG, subscr, - "Send authentication info has failed with cause %d, " - "handled as: %s\n", - gsup_msg->cause, strerror(cause_err)); - - switch (cause_err) { - case EACCES: - LOGGSUBSCRP(LOGL_NOTICE, subscr, - "GPRS send auth info req failed, access denied, " - "GMM cause = '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), - gsup_msg->cause); - /* Clear auth tuples */ - memset(sdata->auth_triplets, 0, sizeof(sdata->auth_triplets)); - for (idx = 0; idx < ARRAY_SIZE(sdata->auth_triplets); idx++) - sdata->auth_triplets[idx].key_seq = GSM_KEY_SEQ_INVAL; - - subscr->authorized = 0; - sdata->error_cause = gsup_msg->cause; - gprs_subscr_update_auth_info(subscr); - break; - - case EHOSTUNREACH: - LOGGSUBSCRP(LOGL_NOTICE, subscr, - "GPRS send auth info req failed, GMM cause = '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), - gsup_msg->cause); - - sdata->error_cause = gsup_msg->cause; - gprs_subscr_update_auth_info(subscr); - break; - - default: - case EINVAL: - LOGGSUBSCRP(LOGL_ERROR, subscr, - "GSUP protocol remote error, GMM cause = '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), - gsup_msg->cause); - sdata->error_cause = gsup_msg->cause; - break; - } - - return -gsup_msg->cause; -} - -static int gprs_subscr_handle_gsup_upd_loc_err(struct gprs_subscr *subscr, - struct osmo_gsup_message *gsup_msg) -{ - int cause_err; - - cause_err = check_cause(gsup_msg->cause); - - LOGGSUBSCRP(LOGL_DEBUG, subscr, - "Update location has failed with cause %d, handled as: %s\n", - gsup_msg->cause, strerror(cause_err)); - - switch (cause_err) { - case EACCES: - LOGGSUBSCRP(LOGL_NOTICE, subscr, - "GPRS update location failed, access denied, " - "GMM cause = '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), - gsup_msg->cause); - - subscr->authorized = 0; - subscr->sgsn_data->error_cause = gsup_msg->cause; - gprs_subscr_update_auth_info(subscr); - break; - - case EHOSTUNREACH: - LOGGSUBSCRP(LOGL_NOTICE, subscr, - "GPRS update location failed, GMM cause = '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), - gsup_msg->cause); - - subscr->sgsn_data->error_cause = gsup_msg->cause; - gprs_subscr_update_auth_info(subscr); - break; - - default: - case EINVAL: - LOGGSUBSCRP(LOGL_ERROR, subscr, - "GSUP protocol remote error, GMM cause = '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), - gsup_msg->cause); - break; - } - - return -gsup_msg->cause; -} - -static int gprs_subscr_handle_gsup_purge_no_subscr( - struct osmo_gsup_message *gsup_msg) -{ - if (OSMO_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) { - LOGGSUPP(LOGL_NOTICE, gsup_msg, - "Purge MS has failed with cause '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), - gsup_msg->cause); - return -gsup_msg->cause; - } - - LOGGSUPP(LOGL_INFO, gsup_msg, "Completing purge MS\n"); - return 0; -} - -static int gprs_subscr_handle_gsup_purge_res(struct gprs_subscr *subscr, - struct osmo_gsup_message *gsup_msg) -{ - LOGGSUBSCRP(LOGL_INFO, subscr, "Completing purge MS\n"); - - /* Force silent cancellation */ - subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE; - gprs_subscr_cancel(subscr); - - return 0; -} - -static int gprs_subscr_handle_gsup_purge_err(struct gprs_subscr *subscr, - struct osmo_gsup_message *gsup_msg) -{ - LOGGSUBSCRP(LOGL_NOTICE, subscr, - "Purge MS has failed with cause '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), - gsup_msg->cause); - - /* In GSM 09.02, 19.1.4.4, the text and the SDL diagram imply that - * the subscriber data is not removed if the request has failed. On the - * other hand, keeping the subscriber data in either error case - * (subscriber unknown, syntactical message error, connection error) - * doesn't seem to give any advantage, since the data will be restored - * on the next Attach Request anyway. - * This approach ensures, that the subscriber record will not stick if - * an error happens. - */ - - /* TODO: Check whether this behaviour is acceptable and either just - * remove this TODO-notice or change the implementation to not delete - * the subscriber data (eventually resetting the ENABLE_PURGE flag and - * restarting the expiry timer based on the cause). - * - * Subscriber Unknown: cancel subscr - * Temporary network problems: do nothing (handled by timer based retry) - * Message problems (syntax, nyi, ...): cancel subscr (retry won't help) - */ - - gprs_subscr_handle_gsup_purge_res(subscr, gsup_msg); - - return -gsup_msg->cause; -} - -static int gprs_subscr_handle_loc_cancel_req(struct gprs_subscr *subscr, - struct osmo_gsup_message *gsup_msg) -{ - struct osmo_gsup_message gsup_reply = {0}; - int is_update_procedure = !gsup_msg->cancel_type || - gsup_msg->cancel_type == OSMO_GSUP_CANCEL_TYPE_UPDATE; - - LOGGSUBSCRP(LOGL_INFO, subscr, "Cancelling MS subscriber (%s)\n", - is_update_procedure ? - "update procedure" : "subscription withdraw"); - - gsup_reply.message_type = OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT; - gprs_subscr_tx_gsup_message(subscr, &gsup_reply); - - if (is_update_procedure) - subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE; - else - /* Since a withdraw cause is not specified, just abort the - * current attachment. The following re-attachment should then - * be rejected with a proper cause value. - */ - subscr->sgsn_data->error_cause = GMM_CAUSE_IMPL_DETACHED; - - gprs_subscr_cancel(subscr); - - return 0; -} - -static int gprs_subscr_handle_unknown_imsi(struct osmo_gsup_message *gsup_msg) -{ - if (OSMO_GSUP_IS_MSGT_REQUEST(gsup_msg->message_type)) { - gprs_subscr_tx_gsup_error_reply(NULL, gsup_msg, - GMM_CAUSE_IMSI_UNKNOWN); - LOGP(DGPRS, LOGL_NOTICE, - "Unknown IMSI %s, discarding GSUP request " - "of type 0x%02x\n", - gsup_msg->imsi, gsup_msg->message_type); - } else if (OSMO_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) { - LOGP(DGPRS, LOGL_NOTICE, - "Unknown IMSI %s, discarding GSUP error " - "of type 0x%02x, cause '%s' (%d)\n", - gsup_msg->imsi, gsup_msg->message_type, - get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), - gsup_msg->cause); - } else { - LOGP(DGPRS, LOGL_NOTICE, - "Unknown IMSI %s, discarding GSUP response " - "of type 0x%02x\n", - gsup_msg->imsi, gsup_msg->message_type); - } - - return -GMM_CAUSE_IMSI_UNKNOWN; -} - -int gprs_subscr_rx_gsup_message(struct msgb *msg) -{ - uint8_t *data = msgb_l2(msg); - size_t data_len = msgb_l2len(msg); - int rc = 0; - - struct osmo_gsup_message gsup_msg = {0}; - struct gprs_subscr *subscr; - - rc = osmo_gsup_decode(data, data_len, &gsup_msg); - if (rc < 0) { - LOGP(DGPRS, LOGL_ERROR, - "decoding GSUP message fails with error '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, -rc), -rc); - return rc; - } - - if (!gsup_msg.imsi[0]) { - LOGP(DGPRS, LOGL_ERROR, "Missing IMSI in GSUP message\n"); - - if (OSMO_GSUP_IS_MSGT_REQUEST(gsup_msg.message_type)) - gprs_subscr_tx_gsup_error_reply(NULL, &gsup_msg, - GMM_CAUSE_INV_MAND_INFO); - return -GMM_CAUSE_INV_MAND_INFO; - } - - if (!gsup_msg.cause && OSMO_GSUP_IS_MSGT_ERROR(gsup_msg.message_type)) - gsup_msg.cause = GMM_CAUSE_NET_FAIL; - - subscr = gprs_subscr_get_by_imsi(gsup_msg.imsi); - - if (!subscr) { - switch (gsup_msg.message_type) { - case OSMO_GSUP_MSGT_PURGE_MS_RESULT: - case OSMO_GSUP_MSGT_PURGE_MS_ERROR: - return gprs_subscr_handle_gsup_purge_no_subscr(&gsup_msg); - default: - return gprs_subscr_handle_unknown_imsi(&gsup_msg); - } - } - - LOGGSUBSCRP(LOGL_INFO, subscr, - "Received GSUP message %s\n", - osmo_gsup_message_type_name(gsup_msg.message_type)); - - switch (gsup_msg.message_type) { - case OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST: - rc = gprs_subscr_handle_loc_cancel_req(subscr, &gsup_msg); - break; - - case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT: - rc = gprs_subscr_handle_gsup_auth_res(subscr, &gsup_msg); - break; - - case OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR: - rc = gprs_subscr_handle_gsup_auth_err(subscr, &gsup_msg); - break; - - case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT: - rc = gprs_subscr_handle_gsup_upd_loc_res(subscr, &gsup_msg); - break; - - case OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR: - rc = gprs_subscr_handle_gsup_upd_loc_err(subscr, &gsup_msg); - break; - - case OSMO_GSUP_MSGT_PURGE_MS_ERROR: - rc = gprs_subscr_handle_gsup_purge_err(subscr, &gsup_msg); - break; - - case OSMO_GSUP_MSGT_PURGE_MS_RESULT: - rc = gprs_subscr_handle_gsup_purge_res(subscr, &gsup_msg); - break; - - case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST: - rc = gprs_subscr_handle_gsup_isd_req(subscr, &gsup_msg); - break; - - case OSMO_GSUP_MSGT_DELETE_DATA_REQUEST: - rc = gprs_subscr_handle_gsup_dsd_req(subscr, &gsup_msg); - break; - - default: - LOGGSUBSCRP(LOGL_ERROR, subscr, - "Rx GSUP message %s not valid at SGSN\n", - osmo_gsup_message_type_name(gsup_msg.message_type)); - if (OSMO_GSUP_IS_MSGT_REQUEST(gsup_msg.message_type)) - gprs_subscr_tx_gsup_error_reply( - subscr, &gsup_msg, GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL); - rc = -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL; - break; - }; - - gprs_subscr_put(subscr); - - return rc; -} - -int gprs_subscr_purge(struct gprs_subscr *subscr) -{ - struct sgsn_subscriber_data *sdata = subscr->sgsn_data; - struct osmo_gsup_message gsup_msg = {0}; - - LOGGSUBSCRP(LOGL_INFO, subscr, "purging MS subscriber\n"); - - gsup_msg.message_type = OSMO_GSUP_MSGT_PURGE_MS_REQUEST; - - /* Provide the HLR number in case it is known */ - gsup_msg.hlr_enc_len = sdata->hlr_len; - gsup_msg.hlr_enc = sdata->hlr; - - return gprs_subscr_tx_gsup_message(subscr, &gsup_msg); -} - -static int gprs_subscr_query_auth_info(struct gprs_subscr *subscr, - const uint8_t *auts, - const uint8_t *auts_rand) -{ - struct osmo_gsup_message gsup_msg = {0}; - - /* Make sure we have a complete resync or clearly no resync. */ - OSMO_ASSERT((auts != NULL) == (auts_rand != NULL)); - - LOGGSUBSCRP(LOGL_INFO, subscr, "requesting auth info%s\n", - auts ? " with AUTS (UMTS Resynch)" : ""); - - gsup_msg.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST; - gsup_msg.auts = auts; - gsup_msg.rand = auts_rand; - return gprs_subscr_tx_gsup_message(subscr, &gsup_msg); -} - -int gprs_subscr_location_update(struct gprs_subscr *subscr) -{ - struct osmo_gsup_message gsup_msg = {0}; - - LOGGSUBSCRP(LOGL_INFO, subscr, - "subscriber data is not available\n"); - - gsup_msg.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST; - return gprs_subscr_tx_gsup_message(subscr, &gsup_msg); -} - -void gprs_subscr_update(struct gprs_subscr *subscr) -{ - LOGGSUBSCRP(LOGL_DEBUG, subscr, "Updating subscriber data\n"); - - subscr->flags &= ~GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING; - subscr->flags &= ~GPRS_SUBSCRIBER_FIRST_CONTACT; - - if (subscr->sgsn_data->mm) - sgsn_update_subscriber_data(subscr->sgsn_data->mm); -} - -void gprs_subscr_update_auth_info(struct gprs_subscr *subscr) -{ - LOGGSUBSCRP(LOGL_DEBUG, subscr, - "Updating subscriber authentication info\n"); - - subscr->flags &= ~GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING; - subscr->flags &= ~GPRS_SUBSCRIBER_FIRST_CONTACT; - - if (subscr->sgsn_data->mm) - sgsn_update_subscriber_data(subscr->sgsn_data->mm); -} - -struct gprs_subscr *gprs_subscr_get_or_create_by_mmctx(struct sgsn_mm_ctx *mmctx) -{ - struct gprs_subscr *subscr = NULL; - - if (mmctx->subscr) - return gprs_subscr_get(mmctx->subscr); - - if (mmctx->imsi[0]) - subscr = gprs_subscr_get_by_imsi(mmctx->imsi); - - if (!subscr) { - subscr = gprs_subscr_get_or_create(mmctx->imsi); - subscr->flags |= GPRS_SUBSCRIBER_FIRST_CONTACT; - subscr->flags &= ~GPRS_SUBSCRIBER_ENABLE_PURGE; - } - - osmo_strlcpy(subscr->imei, mmctx->imei, sizeof(subscr->imei)); - - if (subscr->lac != mmctx->ra.lac) - subscr->lac = mmctx->ra.lac; - - subscr->sgsn_data->mm = mmctx; - mmctx->subscr = gprs_subscr_get(subscr); - - return subscr; -} - -int gprs_subscr_request_update_location(struct sgsn_mm_ctx *mmctx) -{ - struct gprs_subscr *subscr = NULL; - int rc; - - LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting subscriber data update\n"); - - subscr = gprs_subscr_get_or_create_by_mmctx(mmctx); - - subscr->flags |= GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING; - - rc = gprs_subscr_location_update(subscr); - gprs_subscr_put(subscr); - return rc; -} - -/*! \brief Send Update Auth Info request via GSUP, with or without resync. - * \param[in] mmctx MM context to request authentication tuples for. - * \param[in] auts 14 octet AUTS token for UMTS resync, or NULL. - * \param[in] auts_rand 16 octet Random token for UMTS resync, or NULL. - * In case of normal Authentication Info request, both \a auts and \a auts_rand - * must be NULL. For resync, both must be non-NULL. - */ -int gprs_subscr_request_auth_info(struct sgsn_mm_ctx *mmctx, - const uint8_t *auts, - const uint8_t *auts_rand) -{ - struct gprs_subscr *subscr = NULL; - int rc; - - LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting subscriber authentication info\n"); - - subscr = gprs_subscr_get_or_create_by_mmctx(mmctx); - - subscr->flags |= GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING; - - rc = gprs_subscr_query_auth_info(subscr, auts, auts_rand); - gprs_subscr_put(subscr); - return rc; -} - -static void gprs_subscr_free(struct gprs_subscr *gsub) -{ - llist_del(&gsub->entry); - talloc_free(gsub); -} - -struct gprs_subscr *_gprs_subscr_get(struct gprs_subscr *gsub, - const char *file, int line) -{ - OSMO_ASSERT(gsub->use_count < INT_MAX); - gsub->use_count++; - LOGPSRC(DREF, LOGL_DEBUG, file, line, - "subscr %s usage increases to: %d\n", - gsub->imsi, gsub->use_count); - return gsub; -} - -struct gprs_subscr *_gprs_subscr_put(struct gprs_subscr *gsub, - const char *file, int line) -{ - gsub->use_count--; - LOGPSRC(DREF, gsub->use_count >= 0? LOGL_DEBUG : LOGL_ERROR, - file, line, - "subscr %s usage decreases to: %d%s\n", - gsub->imsi, gsub->use_count, - gsub->keep_in_ram? ", keep-in-ram flag is set" : ""); - if (gsub->use_count > 0) - return gsub; - if (gsub->keep_in_ram) - return gsub; - gprs_subscr_free(gsub); - return NULL; -} diff --git a/src/gprs/sgsn_auth.c b/src/gprs/sgsn_auth.c deleted file mode 100644 index b8d803590..000000000 --- a/src/gprs/sgsn_auth.c +++ /dev/null @@ -1,312 +0,0 @@ -/* MS authorization and subscriber data handling */ - -/* (C) 2009-2010 by Harald Welte - * - * 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 . - * - */ - -#include -#include -#include -#include -#include -#include -#include - -const struct value_string auth_state_names[] = { - { SGSN_AUTH_ACCEPTED, "accepted"}, - { SGSN_AUTH_REJECTED, "rejected"}, - { SGSN_AUTH_UNKNOWN, "unknown"}, - { SGSN_AUTH_AUTHENTICATE, "authenticate" }, - { SGSN_AUTH_UMTS_RESYNC, "UMTS-resync" }, - { 0, NULL } -}; - -const struct value_string *sgsn_auth_state_names = auth_state_names; - -void sgsn_auth_init(struct sgsn_instance *sgsn) -{ - INIT_LLIST_HEAD(&sgsn->cfg.imsi_acl); -} - -struct imsi_acl_entry *sgsn_acl_lookup(const char *imsi, const struct sgsn_config *cfg) -{ - struct imsi_acl_entry *acl; - llist_for_each_entry(acl, &cfg->imsi_acl, list) { - if (!strcmp(imsi, acl->imsi)) - return acl; - } - return NULL; -} - -int sgsn_acl_add(const char *imsi, struct sgsn_config *cfg) -{ - struct imsi_acl_entry *acl; - - if (sgsn_acl_lookup(imsi, cfg)) - return -EEXIST; - - acl = talloc_zero(NULL, struct imsi_acl_entry); - if (!acl) - return -ENOMEM; - osmo_strlcpy(acl->imsi, imsi, sizeof(acl->imsi)); - - llist_add(&acl->list, &cfg->imsi_acl); - - return 0; -} - -int sgsn_acl_del(const char *imsi, struct sgsn_config *cfg) -{ - struct imsi_acl_entry *acl; - - acl = sgsn_acl_lookup(imsi, cfg); - if (!acl) - return -ENODEV; - - llist_del(&acl->list); - talloc_free(acl); - - return 0; -} - -enum sgsn_auth_state sgsn_auth_state(struct sgsn_mm_ctx *mmctx) -{ - char mccmnc[16]; - int check_net = 0; - int check_acl = 0; - - OSMO_ASSERT(mmctx); - - switch (sgsn->cfg.auth_policy) { - case SGSN_AUTH_POLICY_OPEN: - return SGSN_AUTH_ACCEPTED; - - case SGSN_AUTH_POLICY_CLOSED: - check_net = 1; - check_acl = 1; - break; - - case SGSN_AUTH_POLICY_ACL_ONLY: - check_acl = 1; - break; - - case SGSN_AUTH_POLICY_REMOTE: - if (!mmctx->subscr) - return mmctx->auth_state; - - if (mmctx->subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING_MASK) - return mmctx->auth_state; - - if (sgsn->cfg.require_authentication && - (!sgsn_mm_ctx_is_authenticated(mmctx) || - mmctx->subscr->sgsn_data->auth_triplets_updated)) - return SGSN_AUTH_AUTHENTICATE; - - if (mmctx->subscr->authorized) - return SGSN_AUTH_ACCEPTED; - - return SGSN_AUTH_REJECTED; - } - - if (!strlen(mmctx->imsi)) { - LOGMMCTXP(LOGL_NOTICE, mmctx, - "Missing IMSI, authorization state not known\n"); - return SGSN_AUTH_UNKNOWN; - } - - if (check_net) { - /* We simply assume that the IMSI exists, as long as it is part - * of 'our' network */ - snprintf(mccmnc, sizeof(mccmnc), "%s%s", - osmo_mcc_name(mmctx->ra.mcc), - osmo_mnc_name(mmctx->ra.mnc, mmctx->ra.mnc_3_digits)); - if (strncmp(mccmnc, mmctx->imsi, mmctx->ra.mnc_3_digits ? 6 : 5) == 0) - return SGSN_AUTH_ACCEPTED; - } - - if (check_acl && sgsn_acl_lookup(mmctx->imsi, &sgsn->cfg)) - return SGSN_AUTH_ACCEPTED; - - return SGSN_AUTH_REJECTED; -} - -/* - * This function is directly called by e.g. the GMM layer. It returns either - * after calling sgsn_auth_update directly or after triggering an asynchronous - * procedure which will call sgsn_auth_update later on. - */ -int sgsn_auth_request(struct sgsn_mm_ctx *mmctx) -{ - struct gprs_subscr *subscr; - struct gsm_auth_tuple *at; - int need_update_location; - int rc; - - LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting authorization\n"); - - if (sgsn->cfg.auth_policy != SGSN_AUTH_POLICY_REMOTE) { - sgsn_auth_update(mmctx); - return 0; - } - - need_update_location = sgsn->cfg.require_update_location && - (mmctx->subscr == NULL || - mmctx->pending_req == GSM48_MT_GMM_ATTACH_REQ); - - /* This has the side effect of registering the subscr with the mmctx */ - subscr = gprs_subscr_get_or_create_by_mmctx(mmctx); - gprs_subscr_put(subscr); - - OSMO_ASSERT(mmctx->subscr != NULL); - - if (sgsn->cfg.require_authentication && !sgsn_mm_ctx_is_authenticated(mmctx)) { - /* Find next tuple */ - at = sgsn_auth_get_tuple(mmctx, mmctx->auth_triplet.key_seq); - - if (!at) { - /* No valid tuple found, request fresh ones */ - mmctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL; - LOGMMCTXP(LOGL_INFO, mmctx, - "Requesting authentication tuples\n"); - rc = gprs_subscr_request_auth_info(mmctx, NULL, NULL); - if (rc >= 0) - return 0; - - return rc; - } - - mmctx->auth_triplet = *at; - } else if (need_update_location) { - LOGMMCTXP(LOGL_INFO, mmctx, - "Missing information, requesting subscriber data\n"); - rc = gprs_subscr_request_update_location(mmctx); - if (rc >= 0) - return 0; - - return rc; - } - - sgsn_auth_update(mmctx); - return 0; -} - -void sgsn_auth_update(struct sgsn_mm_ctx *mmctx) -{ - enum sgsn_auth_state auth_state; - struct gprs_subscr *subscr = mmctx->subscr; - struct gsm_auth_tuple *at; - int gmm_cause; - - auth_state = sgsn_auth_state(mmctx); - - LOGMMCTXP(LOGL_DEBUG, mmctx, "Updating authorization (%s -> %s)\n", - get_value_string(sgsn_auth_state_names, mmctx->auth_state), - get_value_string(sgsn_auth_state_names, auth_state)); - - if (auth_state == SGSN_AUTH_UNKNOWN && subscr && - !(subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING_MASK)) { - /* Reject requests if gprs_subscr_request_update_location fails */ - LOGMMCTXP(LOGL_ERROR, mmctx, - "Missing information, authorization not possible\n"); - auth_state = SGSN_AUTH_REJECTED; - } - - if (auth_state == SGSN_AUTH_AUTHENTICATE && - mmctx->auth_triplet.key_seq == GSM_KEY_SEQ_INVAL) { - /* The current tuple is not valid, but we are possibly called - * because new auth tuples have been received */ - at = sgsn_auth_get_tuple(mmctx, mmctx->auth_triplet.key_seq); - if (!at) { - LOGMMCTXP(LOGL_ERROR, mmctx, - "Missing auth tuples, authorization not possible\n"); - auth_state = SGSN_AUTH_REJECTED; - } else { - mmctx->auth_triplet = *at; - } - } - - if (mmctx->auth_state == auth_state) - return; - - LOGMMCTXP(LOGL_INFO, mmctx, "Got authorization update: state %s -> %s\n", - get_value_string(sgsn_auth_state_names, mmctx->auth_state), - get_value_string(sgsn_auth_state_names, auth_state)); - - mmctx->auth_state = auth_state; - - switch (auth_state) { - case SGSN_AUTH_AUTHENTICATE: - if (subscr) - subscr->sgsn_data->auth_triplets_updated = 0; - - gsm0408_gprs_authenticate(mmctx); - break; - case SGSN_AUTH_ACCEPTED: - gsm0408_gprs_access_granted(mmctx); - break; - case SGSN_AUTH_REJECTED: - gmm_cause = - subscr ? subscr->sgsn_data->error_cause : - SGSN_ERROR_CAUSE_NONE; - - if (subscr && (subscr->flags & GPRS_SUBSCRIBER_CANCELLED) != 0) - gsm0408_gprs_access_cancelled(mmctx, gmm_cause); - else - gsm0408_gprs_access_denied(mmctx, gmm_cause); - break; - default: - break; - } -} - -struct gsm_auth_tuple *sgsn_auth_get_tuple(struct sgsn_mm_ctx *mmctx, - unsigned key_seq) -{ - unsigned count; - unsigned idx; - struct gsm_auth_tuple *at = NULL; - - struct sgsn_subscriber_data *sdata; - - if (!mmctx->subscr) - return NULL; - - if (key_seq == GSM_KEY_SEQ_INVAL) - /* Start with 0 after increment module array size */ - idx = ARRAY_SIZE(sdata->auth_triplets) - 1; - else - idx = key_seq; - - sdata = mmctx->subscr->sgsn_data; - - /* Find next tuple */ - for (count = ARRAY_SIZE(sdata->auth_triplets); count > 0; count--) { - idx = (idx + 1) % ARRAY_SIZE(sdata->auth_triplets); - - if (sdata->auth_triplets[idx].key_seq == GSM_KEY_SEQ_INVAL) - continue; - - if (sdata->auth_triplets[idx].use_count == 0) { - at = &sdata->auth_triplets[idx]; - at->use_count = 1; - return at; - } - } - - return NULL; -} diff --git a/src/gprs/sgsn_cdr.c b/src/gprs/sgsn_cdr.c deleted file mode 100644 index a50b4dfdd..000000000 --- a/src/gprs/sgsn_cdr.c +++ /dev/null @@ -1,301 +0,0 @@ -/* GPRS SGSN CDR dumper */ - -/* (C) 2015 by Holger Hans Peter Freyther - * 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 . - * - */ - -#include - -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include - -#include - -#include -#include - -/* TODO...avoid going through a global */ -extern struct sgsn_instance *sgsn; -extern struct ctrl_handle *g_ctrlh; - -/** - * The CDR module will generate an entry like: - * - * IMSI, # Subscriber IMSI - * IMEI, # Subscriber IMEI - * MSISDN, # Subscriber MISDN - * Charging_Timestamp, # Event start Time - * Charging_UTC, # Time zone of event start time - * Duration, # Session DURATION - * Cell_Id, # CELL_ID - * Location_Area, # LAC - * GGSN_ADDR, # GGSN_ADDR - * SGSN_ADDR, # SGSN_ADDR - * APNI, # APNI - * PDP_ADDR, # PDP_ADDR - * VOL_IN, # VOL_IN in Bytes - * VOL_OUT, # VOL_OUT in Bytes - * CAUSE_FOR_TERM, # CAUSE_FOR_TERM - */ - -static void send_cdr_trap(char *value) -{ - if (ctrl_cmd_send_trap(g_ctrlh, "cdr-v1", value) < 0) - LOGP(DGPRS, LOGL_ERROR, "Failed to create and send TRAP cdr-v1\n"); -} - -static void maybe_print_header(FILE *cdr_file) -{ - if (ftell(cdr_file) != 0) - return; - - fprintf(cdr_file, "timestamp,imsi,imei,msisdn,cell_id,lac,hlr,event,pdp_duration,ggsn_addr,sgsn_addr,apni,eua_addr,vol_in,vol_out,charging_id\n"); -} - -static int cdr_snprintf_mm(char *buf, size_t size, const char *ev, - struct sgsn_mm_ctx *mmctx) -{ - struct tm tm; - struct timeval tv; - int ret; - - gettimeofday(&tv, NULL); - gmtime_r(&tv.tv_sec, &tm); - ret = snprintf(buf, size, "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s", - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec, - (int)(tv.tv_usec / 1000), - mmctx->imsi, - mmctx->imei, - mmctx->msisdn, - mmctx->gb.cell_id, - mmctx->ra.lac, - mmctx->hlr, - ev); - return ret; -} - -static void cdr_log_mm(struct sgsn_instance *inst, const char *ev, - struct sgsn_mm_ctx *mmctx) -{ - FILE *cdr_file; - char buf[1024]; - - if (!inst->cfg.cdr.filename && !inst->cfg.cdr.trap) - return; - - cdr_snprintf_mm(buf, sizeof(buf), ev, mmctx); - - if (inst->cfg.cdr.trap) - send_cdr_trap(buf); - - if (inst->cfg.cdr.filename) { - cdr_file = fopen(inst->cfg.cdr.filename, "a"); - if (!cdr_file) { - LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n", - inst->cfg.cdr.filename); - return; - } - - maybe_print_header(cdr_file); - fprintf(cdr_file, "%s\n", buf); - - fclose(cdr_file); - } -} - -static void extract_eua(struct ul66_t *eua, char *eua_addr) -{ - if (eua->l < 2) - return; - - /* there is no addr for ETSI/PPP */ - if ((eua->v[0] & 0x0F) != 1) { - strcpy(eua_addr, "ETSI"); - return; - } - - if (eua->v[1] == 0x21 && eua->l == 6) - inet_ntop(AF_INET, &eua->v[2], eua_addr, INET_ADDRSTRLEN); - else if (eua->v[1] == 0x57 && eua->l == 18) - inet_ntop(AF_INET6, &eua->v[2], eua_addr, INET6_ADDRSTRLEN); - else { - /* e.g. both IPv4 and IPv6 */ - strcpy(eua_addr, "Unknown address"); - } -} - -static int cdr_snprintf_pdp(char *buf, size_t size, const char *ev, - struct sgsn_pdp_ctx *pdp) -{ - char apni[(pdp->lib ? pdp->lib->apn_use.l : 0) + 1]; - char ggsn_addr[INET_ADDRSTRLEN]; - char sgsn_addr[INET_ADDRSTRLEN]; - char eua_addr[INET6_ADDRSTRLEN]; - struct tm tm; - struct timeval tv; - time_t duration; - struct timespec tp; - int ret; - - memset(apni, 0, sizeof(apni)); - memset(ggsn_addr, 0, sizeof(ggsn_addr)); - memset(sgsn_addr, 0, sizeof(sgsn_addr)); - memset(eua_addr, 0, sizeof(eua_addr)); - - - if (pdp->lib) { - osmo_apn_to_str(apni, pdp->lib->apn_use.v, pdp->lib->apn_use.l); - inet_ntop(AF_INET, &pdp->lib->hisaddr0.s_addr, ggsn_addr, sizeof(ggsn_addr)); - extract_eua(&pdp->lib->eua, eua_addr); - } - - if (pdp->ggsn) - inet_ntop(AF_INET, &pdp->ggsn->gsn->gsnc.s_addr, sgsn_addr, sizeof(sgsn_addr)); - - osmo_clock_gettime(CLOCK_MONOTONIC, &tp); - gettimeofday(&tv, NULL); - - /* convert the timestamp to UTC */ - gmtime_r(&tv.tv_sec, &tm); - - /* Check the duration of the PDP context */ - duration = tp.tv_sec - pdp->cdr_start.tv_sec; - - ret = snprintf(buf, size, - "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s,%ld,%s,%s,%s,%s,%" PRIu64 ",%" PRIu64 ",%u", - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec, - (int)(tv.tv_usec / 1000), - pdp->mm ? pdp->mm->imsi : "N/A", - pdp->mm ? pdp->mm->imei : "N/A", - pdp->mm ? pdp->mm->msisdn : "N/A", - pdp->mm ? pdp->mm->gb.cell_id : -1, - pdp->mm ? pdp->mm->ra.lac : -1, - pdp->mm ? pdp->mm->hlr : "N/A", - ev, - (unsigned long ) duration, - ggsn_addr, - sgsn_addr, - apni, - eua_addr, - pdp->cdr_bytes_in, - pdp->cdr_bytes_out, - pdp->cdr_charging_id); - return ret; -} - -static void cdr_log_pdp(struct sgsn_instance *inst, const char *ev, - struct sgsn_pdp_ctx *pdp) -{ - FILE *cdr_file; - char buf[1024]; - - if (!inst->cfg.cdr.filename && !inst->cfg.cdr.trap) - return; - - cdr_snprintf_pdp(buf, sizeof(buf), ev, pdp); - - if (inst->cfg.cdr.trap) - send_cdr_trap(buf); - - if (inst->cfg.cdr.filename) { - cdr_file = fopen(inst->cfg.cdr.filename, "a"); - if (!cdr_file) { - LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n", - inst->cfg.cdr.filename); - return; - } - - maybe_print_header(cdr_file); - fprintf(cdr_file, "%s\n", buf); - fclose(cdr_file); - } -} - -static void cdr_pdp_timeout(void *_data) -{ - struct sgsn_pdp_ctx *pdp = _data; - cdr_log_pdp(sgsn, "pdp-periodic", pdp); - osmo_timer_schedule(&pdp->cdr_timer, sgsn->cfg.cdr.interval, 0); -} - -static int handle_sgsn_sig(unsigned int subsys, unsigned int signal, - void *handler_data, void *_signal_data) -{ - struct sgsn_signal_data *signal_data = _signal_data; - struct sgsn_instance *inst = handler_data; - - if (subsys != SS_SGSN) - return 0; - - switch (signal) { - case S_SGSN_ATTACH: - cdr_log_mm(inst, "attach", signal_data->mm); - break; - case S_SGSN_UPDATE: - cdr_log_mm(inst, "update", signal_data->mm); - break; - case S_SGSN_DETACH: - cdr_log_mm(inst, "detach", signal_data->mm); - break; - case S_SGSN_MM_FREE: - cdr_log_mm(inst, "free", signal_data->mm); - break; - case S_SGSN_PDP_ACT: - osmo_clock_gettime(CLOCK_MONOTONIC, &signal_data->pdp->cdr_start); - signal_data->pdp->cdr_charging_id = signal_data->pdp->lib->cid; - cdr_log_pdp(inst, "pdp-act", signal_data->pdp); - osmo_timer_setup(&signal_data->pdp->cdr_timer, cdr_pdp_timeout, - signal_data->pdp); - osmo_timer_schedule(&signal_data->pdp->cdr_timer, inst->cfg.cdr.interval, 0); - break; - case S_SGSN_PDP_DEACT: - cdr_log_pdp(inst, "pdp-deact", signal_data->pdp); - osmo_timer_del(&signal_data->pdp->cdr_timer); - break; - case S_SGSN_PDP_TERMINATE: - cdr_log_pdp(inst, "pdp-terminate", signal_data->pdp); - osmo_timer_del(&signal_data->pdp->cdr_timer); - break; - case S_SGSN_PDP_FREE: - cdr_log_pdp(inst, "pdp-free", signal_data->pdp); - osmo_timer_del(&signal_data->pdp->cdr_timer); - break; - } - - return 0; -} - -int sgsn_cdr_init(struct sgsn_instance *sgsn) -{ - /* register for CDR related events */ - sgsn->cfg.cdr.interval = 10 * 60; - osmo_signal_register_handler(SS_SGSN, handle_sgsn_sig, sgsn); - - return 0; -} diff --git a/src/gprs/sgsn_ctrl.c b/src/gprs/sgsn_ctrl.c deleted file mode 100644 index ad91d25a5..000000000 --- a/src/gprs/sgsn_ctrl.c +++ /dev/null @@ -1,62 +0,0 @@ -/* Control Interface Implementation for the SGSN */ -/* - * (C) 2014 by Holger Hans Peter Freyther - * (C) 2014 by sysmocom s.f.m.c. GmbH - * 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 . - * - */ - -#include -#include -#include -#include -#include - -#include - -extern vector ctrl_node_vec; - -static int get_subscriber_list(struct ctrl_cmd *cmd, void *d) -{ - struct sgsn_mm_ctx *mm; - - cmd->reply = talloc_strdup(cmd, ""); - llist_for_each_entry(mm, &sgsn_mm_ctxts, list) { - char *addr = NULL; - struct sgsn_pdp_ctx *pdp; - - if (strlen(mm->imsi) == 0) - continue; - - llist_for_each_entry(pdp, &mm->pdp_list, list) - addr = gprs_pdpaddr2str(pdp->lib->eua.v, - pdp->lib->eua.l); - - cmd->reply = talloc_asprintf_append( - cmd->reply, - "%s,%s\n", mm->imsi, addr ? addr : ""); - } - - return CTRL_CMD_REPLY; -} -CTRL_CMD_DEFINE_RO(subscriber_list, "subscriber-list-active-v1"); - -int sgsn_ctrl_cmds_install(void) -{ - int rc = 0; - rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_subscriber_list); - return rc; -} diff --git a/src/gprs/sgsn_libgtp.c b/src/gprs/sgsn_libgtp.c deleted file mode 100644 index 5e3f48f3f..000000000 --- a/src/gprs/sgsn_libgtp.c +++ /dev/null @@ -1,842 +0,0 @@ -/* GPRS SGSN integration with libgtp of OpenGGSN */ -/* libgtp implements the GPRS Tunelling Protocol GTP per TS 09.60 / 29.060 */ - -/* (C) 2010 by Harald Welte - * (C) 2010 by On-Waves - * (C) 2015 by Holger Hans Peter Freyther - * 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 . - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "bscconfig.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -/* TS 23.003: The MSISDN shall take the dummy MSISDN value composed of - * 15 digits set to 0 (encoded as an E.164 international number) when - * the MSISDN is not available in messages in which the presence of the - * MSISDN parameter */ -static const uint8_t dummy_msisdn[] = - { 0x91, /* No extension, international, E.164 */ - 0, 0, 0, 0, 0, 0, 0, /* 14 digits of zeroes */ - 0xF0 /* 15th digit of zero + padding */ }; - -const struct value_string gtp_cause_strs[] = { - { GTPCAUSE_REQ_IMSI, "Request IMSI" }, - { GTPCAUSE_REQ_IMEI, "Request IMEI" }, - { GTPCAUSE_REQ_IMSI_IMEI, "Request IMSI and IMEI" }, - { GTPCAUSE_NO_ID_NEEDED, "No identity needed" }, - { GTPCAUSE_MS_REFUSES_X, "MS refuses" }, - { GTPCAUSE_MS_NOT_RESP_X, "MS is not GPRS responding" }, - { GTPCAUSE_ACC_REQ, "Request accepted" }, - { GTPCAUSE_NON_EXIST, "Non-existent" }, - { GTPCAUSE_INVALID_MESSAGE, "Invalid message format" }, - { GTPCAUSE_IMSI_NOT_KNOWN, "IMSI not known" }, - { GTPCAUSE_MS_DETACHED, "MS is GPRS detached" }, - { GTPCAUSE_MS_NOT_RESP, "MS is not GPRS responding" }, - { GTPCAUSE_MS_REFUSES, "MS refuses" }, - { GTPCAUSE_NO_RESOURCES, "No resources available" }, - { GTPCAUSE_NOT_SUPPORTED, "Service not supported" }, - { GTPCAUSE_MAN_IE_INCORRECT, "Mandatory IE incorrect" }, - { GTPCAUSE_MAN_IE_MISSING, "Mandatory IE missing" }, - { GTPCAUSE_OPT_IE_INCORRECT, "Optional IE incorrect" }, - { GTPCAUSE_SYS_FAIL, "System failure" }, - { GTPCAUSE_ROAMING_REST, "Roaming restrictions" }, - { GTPCAUSE_PTIMSI_MISMATCH, "P-TMSI Signature mismatch" }, - { GTPCAUSE_CONN_SUSP, "GPRS connection suspended" }, - { GTPCAUSE_AUTH_FAIL, "Authentication failure" }, - { GTPCAUSE_USER_AUTH_FAIL, "User authentication failed" }, - { GTPCAUSE_CONTEXT_NOT_FOUND, "Context not found" }, - { GTPCAUSE_ADDR_OCCUPIED, "All dynamic PDP addresses occupied" }, - { GTPCAUSE_NO_MEMORY, "No memory is available" }, - { GTPCAUSE_RELOC_FAIL, "Relocation failure" }, - { GTPCAUSE_UNKNOWN_MAN_EXTHEADER, "Unknown mandatory ext. header" }, - { GTPCAUSE_SEM_ERR_TFT, "Semantic error in TFT operation" }, - { GTPCAUSE_SYN_ERR_TFT, "Syntactic error in TFT operation" }, - { GTPCAUSE_SEM_ERR_FILTER, "Semantic errors in packet filter" }, - { GTPCAUSE_SYN_ERR_FILTER, "Syntactic errors in packet filter" }, - { GTPCAUSE_MISSING_APN, "Missing or unknown APN" }, - { GTPCAUSE_UNKNOWN_PDP, "Unknown PDP address or PDP type" }, - { 0, NULL } -}; - -/* Generate the GTP IMSI IE according to 09.60 Section 7.9.2 */ -static uint64_t imsi_str2gtp(char *str) -{ - uint64_t imsi64 = 0; - unsigned int n; - unsigned int imsi_len = strlen(str); - - if (imsi_len > 16) { - LOGP(DGPRS, LOGL_NOTICE, "IMSI length > 16 not supported!\n"); - return 0; - } - - for (n = 0; n < 16; n++) { - uint64_t val; - if (n < imsi_len) - val = (str[n]-'0') & 0xf; - else - val = 0xf; - imsi64 |= (val << (n*4)); - } - return imsi64; -} - -/* generate a PDP context based on the IE's from the 04.08 message, - * and send the GTP create pdp context request to the GGSN */ -struct sgsn_pdp_ctx *sgsn_create_pdp_ctx(struct sgsn_ggsn_ctx *ggsn, - struct sgsn_mm_ctx *mmctx, - uint16_t nsapi, - struct tlv_parsed *tp) -{ - struct gprs_ra_id raid; - struct sgsn_pdp_ctx *pctx; - struct pdp_t *pdp; - uint64_t imsi_ui64; - size_t qos_len; - const uint8_t *qos; - int rc; - - pctx = sgsn_pdp_ctx_alloc(mmctx, ggsn, nsapi); - if (!pctx) { - LOGP(DGPRS, LOGL_ERROR, "Couldn't allocate PDP Ctx\n"); - return NULL; - } - - imsi_ui64 = imsi_str2gtp(mmctx->imsi); - - rc = gtp_pdp_newpdp(ggsn->gsn, &pdp, imsi_ui64, nsapi, NULL); - if (rc) { - LOGP(DGPRS, LOGL_ERROR, "Out of libgtp PDP Contexts\n"); - return NULL; - } - pdp->priv = pctx; - pctx->lib = pdp; - - //pdp->peer = /* sockaddr_in of GGSN (receive) */ - //pdp->ipif = /* not used by library */ - pdp->version = ggsn->gtp_version; - pdp->hisaddr0 = ggsn->remote_addr; - pdp->hisaddr1 = ggsn->remote_addr; - //pdp->cch_pdp = 512; /* Charging Flat Rate */ - - /* MS provided APN, subscription was verified by the caller */ - pdp->selmode = 0xFC | 0x00; - - /* IMSI, TEID/TEIC, FLLU/FLLC, TID, NSAPI set in pdp_newpdp */ - LOGPDPCTXP(LOGL_NOTICE, pctx, "Create PDP Context\n"); - - /* Put the MSISDN in case we have it */ - if (mmctx->subscr && mmctx->subscr->sgsn_data->msisdn_len) { - pdp->msisdn.l = OSMO_MIN(mmctx->subscr->sgsn_data->msisdn_len, sizeof(pdp->msisdn.v)); - memcpy(pdp->msisdn.v, mmctx->subscr->sgsn_data->msisdn, - pdp->msisdn.l); - } else { - /* use the dummy 15-digits-zero MSISDN value */ - pdp->msisdn.l = sizeof(dummy_msisdn); - memcpy(pdp->msisdn.v, dummy_msisdn, pdp->msisdn.l); - } - - /* End User Address from GMM requested PDP address */ - pdp->eua.l = TLVP_LEN(tp, OSMO_IE_GSM_REQ_PDP_ADDR); - if (pdp->eua.l > sizeof(pdp->eua.v)) - pdp->eua.l = sizeof(pdp->eua.v); - memcpy(pdp->eua.v, TLVP_VAL(tp, OSMO_IE_GSM_REQ_PDP_ADDR), - pdp->eua.l); - /* Highest 4 bits of first byte need to be set to 1, otherwise - * the IE is identical with the 04.08 PDP Address IE */ - pdp->eua.v[0] |= 0xf0; - - /* APN name from GMM */ - if (TLVP_PRESENT(tp, GSM48_IE_GSM_APN)) { - pdp->apn_use.l = TLVP_LEN(tp, GSM48_IE_GSM_APN); - if (pdp->apn_use.l > sizeof(pdp->apn_use.v)) - pdp->apn_use.l = sizeof(pdp->apn_use.v); - memcpy(pdp->apn_use.v, TLVP_VAL(tp, GSM48_IE_GSM_APN), pdp->apn_use.l); - } else { - pdp->apn_use.l = 0; - } - - /* Protocol Configuration Options from GMM */ - if (TLVP_PRESENT(tp, GSM48_IE_GSM_PROTO_CONF_OPT)) { - pdp->pco_req.l = TLVP_LEN(tp, GSM48_IE_GSM_PROTO_CONF_OPT); - if (pdp->pco_req.l > sizeof(pdp->pco_req.v)) - pdp->pco_req.l = sizeof(pdp->pco_req.v); - memcpy(pdp->pco_req.v, TLVP_VAL(tp, GSM48_IE_GSM_PROTO_CONF_OPT), - pdp->pco_req.l); - } else { - pdp->pco_req.l = 0; - } - - /* QoS options from GMM or remote */ - if (TLVP_LEN(tp, OSMO_IE_GSM_SUB_QOS) > 0) { - qos_len = TLVP_LEN(tp, OSMO_IE_GSM_SUB_QOS); - qos = TLVP_VAL(tp, OSMO_IE_GSM_SUB_QOS); - } else { - qos_len = TLVP_LEN(tp, OSMO_IE_GSM_REQ_QOS); - qos = TLVP_VAL(tp, OSMO_IE_GSM_REQ_QOS); - } - - if (qos_len <= 3) { - pdp->qos_req.l = qos_len + 1; - if (pdp->qos_req.l > sizeof(pdp->qos_req.v)) - pdp->qos_req.l = sizeof(pdp->qos_req.v); - pdp->qos_req.v[0] = 0; /* Allocation/Retention policy */ - memcpy(&pdp->qos_req.v[1], qos, pdp->qos_req.l - 1); - } else { - pdp->qos_req.l = qos_len; - if (pdp->qos_req.l > sizeof(pdp->qos_req.v)) - pdp->qos_req.l = sizeof(pdp->qos_req.v); - memcpy(pdp->qos_req.v, qos, pdp->qos_req.l); - } - - /* charging characteristics if present */ - if (TLVP_LEN(tp, OSMO_IE_GSM_CHARG_CHAR) >= sizeof(pdp->cch_pdp)) - pdp->cch_pdp = tlvp_val16be(tp, OSMO_IE_GSM_CHARG_CHAR); - - /* SGSN address for control plane */ - pdp->gsnlc.l = sizeof(sgsn->cfg.gtp_listenaddr.sin_addr); - memcpy(pdp->gsnlc.v, &sgsn->cfg.gtp_listenaddr.sin_addr, - sizeof(sgsn->cfg.gtp_listenaddr.sin_addr)); - - /* SGSN address for user plane - * Default to the control plane addr for now. If we are connected to a - * hnbgw via IuPS we'll need to send a PDP context update with the - * correct IP address after the RAB Assignment is complete */ - pdp->gsnlu.l = sizeof(sgsn->cfg.gtp_listenaddr.sin_addr); - memcpy(pdp->gsnlu.v, &sgsn->cfg.gtp_listenaddr.sin_addr, - sizeof(sgsn->cfg.gtp_listenaddr.sin_addr)); - - /* Encode RAT Type according to TS 29.060 7.7.50 */ - pdp->rattype.l = 1; - if (mmctx->ran_type == MM_CTX_T_UTRAN_Iu) - pdp->rattype.v[0] = 1; - else - pdp->rattype.v[0] = 2; - pdp->rattype_given = 1; - - /* Include RAI and ULI all the time */ - pdp->rai_given = 1; - pdp->rai.l = 6; - - /* Routing Area Identifier with LAC and RAC fixed values, as - * requested in 29.006 7.3.1 */ - raid = mmctx->ra; - raid.lac = 0xFFFE; - raid.rac = 0xFF; - gsm48_encode_ra((struct gsm48_ra_id *)pdp->rai.v, &raid); - - /* Encode User Location Information accordint to TS 29.060 7.7.51 */ - pdp->userloc_given = 1; - pdp->userloc.l = 8; - switch (mmctx->ran_type) { - case MM_CTX_T_GERAN_Gb: -#if 0 - case MM_CTX_T_GERAN_Iu: -#endif - pdp->rattype.v[0] = 2; - /* User Location Information */ - pdp->userloc_given = 1; - pdp->userloc.l = 8; - pdp->userloc.v[0] = 0; /* CGI for GERAN */ - bssgp_create_cell_id(&pdp->userloc.v[1], &mmctx->ra, mmctx->gb.cell_id); - break; - case MM_CTX_T_UTRAN_Iu: - pdp->userloc.v[0] = 1; /* SAI for UTRAN */ - /* SAI is like CGI but with SAC instead of CID, so we can abuse this function */ - bssgp_create_cell_id(&pdp->userloc.v[1], &mmctx->ra, mmctx->iu.sac); - break; - } - - /* include the IMEI(SV) */ - pdp->imeisv_given = 1; - gsm48_encode_bcd_number(&pdp->imeisv.v[0], 8, 0, mmctx->imei); - pdp->imeisv.l = pdp->imeisv.v[0]; - memmove(&pdp->imeisv.v[0], &pdp->imeisv.v[1], 8); - - /* change pdp state to 'requested' */ - pctx->state = PDP_STATE_CR_REQ; - - rc = gtp_create_context_req(ggsn->gsn, pdp, pctx); - /* FIXME */ - - return pctx; -} - -/* SGSN wants to delete a PDP context */ -int sgsn_delete_pdp_ctx(struct sgsn_pdp_ctx *pctx) -{ - LOGPDPCTXP(LOGL_INFO, pctx, "Delete PDP Context\n"); - - /* FIXME: decide if we need teardown or not ! */ - return gtp_delete_context_req2(pctx->ggsn->gsn, pctx->lib, pctx, 1); -} - -struct cause_map { - uint8_t cause_in; - uint8_t cause_out; -}; - -static uint8_t cause_map(const struct cause_map *map, uint8_t in, uint8_t deflt) -{ - const struct cause_map *m; - - for (m = map; m->cause_in && m->cause_out; m++) { - if (m->cause_in == in) - return m->cause_out; - } - return deflt; -} - -/* how do we map from gtp cause to SM cause */ -static const struct cause_map gtp2sm_cause_map[] = { - { GTPCAUSE_NO_RESOURCES, GSM_CAUSE_INSUFF_RSRC }, - { GTPCAUSE_NOT_SUPPORTED, GSM_CAUSE_SERV_OPT_NOTSUPP }, - { GTPCAUSE_MAN_IE_INCORRECT, GSM_CAUSE_INV_MAND_INFO }, - { GTPCAUSE_MAN_IE_MISSING, GSM_CAUSE_INV_MAND_INFO }, - { GTPCAUSE_OPT_IE_INCORRECT, GSM_CAUSE_PROTO_ERR_UNSPEC }, - { GTPCAUSE_SYS_FAIL, GSM_CAUSE_NET_FAIL }, - { GTPCAUSE_ROAMING_REST, GSM_CAUSE_REQ_SERV_OPT_NOTSUB }, - { GTPCAUSE_PTIMSI_MISMATCH, GSM_CAUSE_PROTO_ERR_UNSPEC }, - { GTPCAUSE_CONN_SUSP, GSM_CAUSE_PROTO_ERR_UNSPEC }, - { GTPCAUSE_AUTH_FAIL, GSM_CAUSE_AUTH_FAILED }, - { GTPCAUSE_USER_AUTH_FAIL, GSM_CAUSE_ACT_REJ_GGSN }, - { GTPCAUSE_CONTEXT_NOT_FOUND, GSM_CAUSE_PROTO_ERR_UNSPEC }, - { GTPCAUSE_ADDR_OCCUPIED, GSM_CAUSE_INSUFF_RSRC }, - { GTPCAUSE_NO_MEMORY, GSM_CAUSE_INSUFF_RSRC }, - { GTPCAUSE_RELOC_FAIL, GSM_CAUSE_PROTO_ERR_UNSPEC }, - { GTPCAUSE_UNKNOWN_MAN_EXTHEADER, GSM_CAUSE_PROTO_ERR_UNSPEC }, - { GTPCAUSE_MISSING_APN, GSM_CAUSE_MISSING_APN }, - { GTPCAUSE_UNKNOWN_PDP, GSM_CAUSE_UNKNOWN_PDP }, - { 0, 0 } -}; - -int send_act_pdp_cont_acc(struct sgsn_pdp_ctx *pctx) -{ - struct sgsn_signal_data sig_data; - int rc; - struct gprs_llc_lle *lle; - - /* Inform others about it */ - memset(&sig_data, 0, sizeof(sig_data)); - sig_data.pdp = pctx; - osmo_signal_dispatch(SS_SGSN, S_SGSN_PDP_ACT, &sig_data); - - /* Send PDP CTX ACT to MS */ - rc = gsm48_tx_gsm_act_pdp_acc(pctx); - if (rc < 0) - return rc; - - if (pctx->mm->ran_type == MM_CTX_T_GERAN_Gb) { - /* Send SNDCP XID to MS */ - lle = &pctx->mm->gb.llme->lle[pctx->sapi]; - rc = sndcp_sn_xid_req(lle,pctx->nsapi); - if (rc < 0) - return rc; - } - - return 0; -} - -/* The GGSN has confirmed the creation of a PDP Context */ -static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause) -{ - struct sgsn_pdp_ctx *pctx = cbp; - uint8_t reject_cause = 0; - - LOGPDPCTXP(LOGL_INFO, pctx, "Received CREATE PDP CTX CONF, cause=%d(%s)\n", - cause, get_value_string(gtp_cause_strs, cause)); - - if (!pctx->mm) { - goto reject; - } - - /* Check for cause value if it was really successful */ - if (cause < 0) { - LOGP(DGPRS, LOGL_NOTICE, "Create PDP ctx req timed out\n"); - if (pdp && pdp->version == 1) { - pdp->version = 0; - gtp_create_context_req(sgsn->gsn, pdp, cbp); - return 0; - } else { - reject_cause = GSM_CAUSE_NET_FAIL; - goto reject; - } - } - - /* Check for cause value if it was really successful */ - if (cause != GTPCAUSE_ACC_REQ) { - reject_cause = cause_map(gtp2sm_cause_map, cause, - GSM_CAUSE_ACT_REJ_GGSN); - goto reject; - } - - if (pctx->mm->ran_type == MM_CTX_T_GERAN_Gb) { - /* Activate the SNDCP layer */ - sndcp_sm_activate_ind(&pctx->mm->gb.llme->lle[pctx->sapi], pctx->nsapi); - return send_act_pdp_cont_acc(pctx); - } else if (pctx->mm->ran_type == MM_CTX_T_UTRAN_Iu) { -#ifdef BUILD_IU - /* Activate a radio bearer */ - iu_rab_act_ps(pdp->nsapi, pctx); - return 0; -#else - return -ENOTSUP; -#endif - } - - LOGP(DGPRS, LOGL_ERROR, "Unknown ran_type %d\n", - pctx->mm->ran_type); - reject_cause = GSM_CAUSE_PROTO_ERR_UNSPEC; - -reject: - /* - * In case of a timeout pdp will be NULL but we have a valid pointer - * in pctx->lib. For other rejects pctx->lib and pdp might be the - * same. - */ - pctx->state = PDP_STATE_NONE; - if (pctx->lib && pctx->lib != pdp) - pdp_freepdp(pctx->lib); - pctx->lib = NULL; - - if (pdp) - pdp_freepdp(pdp); - - /* Send PDP CTX ACT REJ to MS */ - if (pctx->mm) - gsm48_tx_gsm_act_pdp_rej(pctx->mm, pctx->ti, reject_cause, - 0, NULL); - sgsn_pdp_ctx_free(pctx); - - return EOF; -} - -void sgsn_pdp_upd_gtp_u(struct sgsn_pdp_ctx *pdp, void *addr, size_t alen) -{ - pdp->lib->gsnlu.l = alen; - memcpy(pdp->lib->gsnlu.v, addr, alen); - gtp_update_context(pdp->ggsn->gsn, pdp->lib, pdp, &pdp->lib->hisaddr0); -} - -void sgsn_ggsn_echo_req(struct sgsn_ggsn_ctx *ggc) -{ - LOGGGSN(ggc, LOGL_INFO, "GTP Tx Echo Request\n"); - gtp_echo_req(ggc->gsn, ggc->gtp_version, ggc, &ggc->remote_addr); -} - -/* Confirmation of a PDP Context Delete */ -static int delete_pdp_conf(struct pdp_t *pdp, void *cbp, int cause) -{ - struct sgsn_signal_data sig_data; - struct sgsn_pdp_ctx *pctx = cbp; - int rc = 0; - - LOGPDPCTXP(LOGL_INFO, pctx, "Received DELETE PDP CTX CONF, cause=%d(%s)\n", - cause, get_value_string(gtp_cause_strs, cause)); - - memset(&sig_data, 0, sizeof(sig_data)); - sig_data.pdp = pctx; - osmo_signal_dispatch(SS_SGSN, S_SGSN_PDP_DEACT, &sig_data); - - if (pctx->mm) { - if (pctx->mm->ran_type == MM_CTX_T_GERAN_Gb) { - /* Deactivate the SNDCP layer */ - sndcp_sm_deactivate_ind(&pctx->mm->gb.llme->lle[pctx->sapi], pctx->nsapi); - } else { -#ifdef BUILD_IU - /* Deactivate radio bearer */ - ranap_iu_rab_deact(pctx->mm->iu.ue_ctx, 1); -#else - return -ENOTSUP; -#endif - } - - /* Confirm deactivation of PDP context to MS */ - rc = gsm48_tx_gsm_deact_pdp_acc(pctx); - } else { - LOGPDPCTXP(LOGL_NOTICE, pctx, - "Not deactivating SNDCP layer since the MM context " - "is not available\n"); - } - - sgsn_pdp_ctx_free(pctx); - - return rc; -} - -/* Confirmation of an GTP ECHO request */ -static int echo_conf(void *cbp, bool timeout) -{ - struct sgsn_ggsn_ctx *ggc = (struct sgsn_ggsn_ctx *)cbp; - if (timeout) { - LOGGGSN(ggc, LOGL_NOTICE, "GTP Echo Request timed out\n"); - /* FIXME: if version == 1, retry with version 0 */ - sgsn_ggsn_ctx_drop_all_pdp(ggc); - } else { - LOGGGSN(ggc, LOGL_INFO, "GTP Rx Echo Response\n"); - } - return 0; -} - -/* Any message received by GGSN contains a recovery IE */ -static int cb_recovery2(struct sockaddr_in *peer, struct pdp_t *pdp, uint8_t recovery) -{ - struct sgsn_ggsn_ctx *ggsn; - struct sgsn_pdp_ctx *pctx = NULL; - - ggsn = sgsn_ggsn_ctx_by_addr(&peer->sin_addr); - if (!ggsn) { - LOGP(DGPRS, LOGL_NOTICE, "Received Recovery IE for unknown GGSN\n"); - return -EINVAL; - } - - if (ggsn->remote_restart_ctr == -1) { - /* First received ECHO RESPONSE, note the restart ctr */ - ggsn->remote_restart_ctr = recovery; - } else if (ggsn->remote_restart_ctr != recovery) { - /* counter has changed (GGSN restart): release all PDP */ - LOGP(DGPRS, LOGL_NOTICE, "GGSN recovery (%u->%u) pdp=%p, " - "releasing all%s PDP contexts\n", - ggsn->remote_restart_ctr, recovery, pdp, pdp ? " other" : ""); - ggsn->remote_restart_ctr = recovery; - if (pdp) - pctx = pdp->priv; - sgsn_ggsn_ctx_drop_all_pdp_except(ggsn, pctx); - } - return 0; -} - -/* libgtp callback for confirmations */ -static int cb_conf(int type, int cause, struct pdp_t *pdp, void *cbp) -{ - DEBUGP(DGPRS, "libgtp cb_conf(type=%d, cause=%d, pdp=%p, cbp=%p)\n", - type, cause, pdp, cbp); - - if (cause == EOF) - LOGP(DGPRS, LOGL_ERROR, "libgtp EOF (type=%u, pdp=%p, cbp=%p)\n", - type, pdp, cbp); - - switch (type) { - case GTP_ECHO_REQ: - /* libgtp hands us the RECOVERY number instead of a cause (EOF on timeout) */ - return echo_conf(cbp, cause == EOF); - case GTP_CREATE_PDP_REQ: - return create_pdp_conf(pdp, cbp, cause); - case GTP_DELETE_PDP_REQ: - return delete_pdp_conf(pdp, cbp, cause); - default: - break; - } - return 0; -} - -/* Called whenever a PDP context is deleted for any reason */ -static int cb_delete_context(struct pdp_t *pdp) -{ - struct sgsn_pdp_ctx *pctx = pdp->priv; - - LOGPDPX(DGPRS, LOGL_INFO, pdp, "Context %p was deleted\n", pdp); - - /* unlink the now non-existing library handle from the pdp context. - This way we avoid calling pdp_freepdp() on it, since after returning - from cb_delete_context callback, libgtp is already doing so. */ - pctx->lib = NULL; - - sgsn_ggsn_ctx_drop_pdp(pctx); - return 0; -} - -/* Called when we receive a Version Not Supported message */ -static int cb_unsup_ind(struct sockaddr_in *peer) -{ - LOGP(DGPRS, LOGL_INFO, "GTP Version not supported Indication " - "from %s:%u\n", inet_ntoa(peer->sin_addr), - ntohs(peer->sin_port)); - return 0; -} - -/* Called when we receive a Supported Ext Headers Notification */ -static int cb_extheader_ind(struct sockaddr_in *peer) -{ - LOGP(DGPRS, LOGL_INFO, "GTP Supported Ext Headers Notification " - "from %s:%u\n", inet_ntoa(peer->sin_addr), - ntohs(peer->sin_port)); - return 0; -} - -/* Called whenever we receive a DATA packet */ -static int cb_data_ind(struct pdp_t *lib, void *packet, unsigned int len) -{ - struct bssgp_paging_info pinfo; - struct sgsn_pdp_ctx *pdp; - struct sgsn_mm_ctx *mm; - struct msgb *msg; - uint8_t *ud; - - pdp = lib->priv; - if (!pdp) { - LOGP(DGPRS, LOGL_NOTICE, - "GTP DATA IND from GGSN for unknown PDP\n"); - return -EIO; - } - mm = pdp->mm; - if (!mm) { - LOGP(DGPRS, LOGL_ERROR, - "PDP context (address=%u) without MM context!\n", - pdp->address); - return -EIO; - } - - DEBUGP(DGPRS, "GTP DATA IND from GGSN for %s, length=%u\n", mm->imsi, - len); - - if (mm->ran_type == MM_CTX_T_UTRAN_Iu) { -#ifdef BUILD_IU - /* Ignore the packet for now and page the UE to get the RAB - * reestablished */ - ranap_iu_page_ps(mm->imsi, &mm->p_tmsi, mm->ra.lac, mm->ra.rac); - - return 0; -#else - return -ENOTSUP; -#endif - } - - msg = msgb_alloc_headroom(len+256, 128, "GTP->SNDCP"); - ud = msgb_put(msg, len); - memcpy(ud, packet, len); - - msgb_tlli(msg) = mm->gb.tlli; - msgb_bvci(msg) = mm->gb.bvci; - msgb_nsei(msg) = mm->gb.nsei; - - switch (mm->gmm_state) { - case GMM_REGISTERED_SUSPENDED: - /* initiate PS PAGING procedure */ - memset(&pinfo, 0, sizeof(pinfo)); - pinfo.mode = BSSGP_PAGING_PS; - pinfo.scope = BSSGP_PAGING_BVCI; - pinfo.bvci = mm->gb.bvci; - pinfo.imsi = mm->imsi; - pinfo.ptmsi = &mm->p_tmsi; - pinfo.drx_params = mm->drx_parms; - pinfo.qos[0] = 0; // FIXME - bssgp_tx_paging(mm->gb.nsei, 0, &pinfo); - rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PAGING_PS]); - /* FIXME: queue the packet we received from GTP */ - break; - case GMM_REGISTERED_NORMAL: - break; - default: - LOGP(DGPRS, LOGL_ERROR, "GTP DATA IND for TLLI %08X in state " - "%u\n", mm->gb.tlli, mm->gmm_state); - msgb_free(msg); - return -1; - } - - rate_ctr_inc(&pdp->ctrg->ctr[PDP_CTR_PKTS_UDATA_OUT]); - rate_ctr_add(&pdp->ctrg->ctr[PDP_CTR_BYTES_UDATA_OUT], len); - rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PKTS_UDATA_OUT]); - rate_ctr_add(&mm->ctrg->ctr[GMM_CTR_BYTES_UDATA_OUT], len); - - /* It is easier to have a global count */ - pdp->cdr_bytes_out += len; - - return sndcp_unitdata_req(msg, &mm->gb.llme->lle[pdp->sapi], - pdp->nsapi, mm); -} - -/* Called by SNDCP when it has received/re-assembled a N-PDU */ -int sgsn_rx_sndcp_ud_ind(struct gprs_ra_id *ra_id, int32_t tlli, uint8_t nsapi, - struct msgb *msg, uint32_t npdu_len, uint8_t *npdu) -{ - struct sgsn_mm_ctx *mmctx; - struct sgsn_pdp_ctx *pdp; - - /* look-up the MM context for this message */ - mmctx = sgsn_mm_ctx_by_tlli(tlli, ra_id); - if (!mmctx) { - LOGP(DGPRS, LOGL_ERROR, - "Cannot find MM CTX for TLLI %08x\n", tlli); - return -EIO; - } - /* look-up the PDP context for this message */ - pdp = sgsn_pdp_ctx_by_nsapi(mmctx, nsapi); - if (!pdp) { - LOGP(DGPRS, LOGL_ERROR, "Cannot find PDP CTX for " - "TLLI=%08x, NSAPI=%u\n", tlli, nsapi); - return -EIO; - } - if (!pdp->lib) { - LOGP(DGPRS, LOGL_ERROR, "PDP CTX without libgtp\n"); - return -EIO; - } - - rate_ctr_inc(&pdp->ctrg->ctr[PDP_CTR_PKTS_UDATA_IN]); - rate_ctr_add(&pdp->ctrg->ctr[PDP_CTR_BYTES_UDATA_IN], npdu_len); - rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_UDATA_IN]); - rate_ctr_add(&mmctx->ctrg->ctr[GMM_CTR_BYTES_UDATA_IN], npdu_len); - - /* It is easier to have a global count */ - pdp->cdr_bytes_in += npdu_len; - - return gtp_data_req(pdp->ggsn->gsn, pdp->lib, npdu, npdu_len); -} - -/* libgtp select loop integration */ -static int sgsn_gtp_fd_cb(struct osmo_fd *fd, unsigned int what) -{ - struct sgsn_instance *sgi = fd->data; - int rc; - - if (!(what & BSC_FD_READ)) - return 0; - - switch (fd->priv_nr) { - case 0: - rc = gtp_decaps0(sgi->gsn); - break; - case 1: - rc = gtp_decaps1c(sgi->gsn); - break; - case 2: - rc = gtp_decaps1u(sgi->gsn); - break; - default: - rc = -EINVAL; - break; - } - return rc; -} - -static void sgsn_gtp_tmr_start(struct sgsn_instance *sgi) -{ - struct timeval next; - - /* Retrieve next retransmission as struct timeval */ - gtp_retranstimeout(sgi->gsn, &next); - - /* re-schedule the timer */ - osmo_timer_schedule(&sgi->gtp_timer, next.tv_sec, next.tv_usec/1000); -} - -/* timer callback for libgtp retransmissions and ping */ -static void sgsn_gtp_tmr_cb(void *data) -{ - struct sgsn_instance *sgi = data; - - /* Do all the retransmissions as needed */ - gtp_retrans(sgi->gsn); - - sgsn_gtp_tmr_start(sgi); -} - -int sgsn_gtp_init(struct sgsn_instance *sgi) -{ - int rc; - struct gsn_t *gsn; - - rc = gtp_new(&sgi->gsn, sgi->cfg.gtp_statedir, - &sgi->cfg.gtp_listenaddr.sin_addr, GTP_MODE_SGSN); - if (rc) { - LOGP(DGPRS, LOGL_ERROR, "Failed to create GTP: %d\n", rc); - return rc; - } - LOGP(DGPRS, LOGL_NOTICE, "Created GTP on %s\n", inet_ntoa(sgi->cfg.gtp_listenaddr.sin_addr)); - - gsn = sgi->gsn; - - if (gsn->mode != GTP_MODE_SGSN) - return -EINVAL; - - sgi->gtp_fd0.fd = gsn->fd0; - sgi->gtp_fd0.priv_nr = 0; - sgi->gtp_fd0.data = sgi; - sgi->gtp_fd0.when = BSC_FD_READ; - sgi->gtp_fd0.cb = sgsn_gtp_fd_cb; - rc = osmo_fd_register(&sgi->gtp_fd0); - if (rc < 0) - return rc; - - sgi->gtp_fd1c.fd = gsn->fd1c; - sgi->gtp_fd1c.priv_nr = 1; - sgi->gtp_fd1c.data = sgi; - sgi->gtp_fd1c.when = BSC_FD_READ; - sgi->gtp_fd1c.cb = sgsn_gtp_fd_cb; - rc = osmo_fd_register(&sgi->gtp_fd1c); - if (rc < 0) { - osmo_fd_unregister(&sgi->gtp_fd0); - return rc; - } - - sgi->gtp_fd1u.fd = gsn->fd1u; - sgi->gtp_fd1u.priv_nr = 2; - sgi->gtp_fd1u.data = sgi; - sgi->gtp_fd1u.when = BSC_FD_READ; - sgi->gtp_fd1u.cb = sgsn_gtp_fd_cb; - rc = osmo_fd_register(&sgi->gtp_fd1u); - if (rc < 0) { - osmo_fd_unregister(&sgi->gtp_fd0); - osmo_fd_unregister(&sgi->gtp_fd1c); - return rc; - } - - /* Start GTP re-transmission timer */ - osmo_timer_setup(&sgi->gtp_timer, sgsn_gtp_tmr_cb, sgi); - sgsn_gtp_tmr_start(sgi); - - /* Register callbackcs with libgtp */ - gtp_set_cb_delete_context(gsn, cb_delete_context); - gtp_set_cb_conf(gsn, cb_conf); - gtp_set_cb_recovery2(gsn, cb_recovery2); - gtp_set_cb_data_ind(gsn, cb_data_ind); - gtp_set_cb_unsup_ind(gsn, cb_unsup_ind); - gtp_set_cb_extheader_ind(gsn, cb_extheader_ind); - - return 0; -} diff --git a/src/gprs/sgsn_main.c b/src/gprs/sgsn_main.c deleted file mode 100644 index eef5f8f22..000000000 --- a/src/gprs/sgsn_main.c +++ /dev/null @@ -1,536 +0,0 @@ -/* GPRS SGSN Implementation */ - -/* (C) 2010 by Harald Welte - * (C) 2010 by On-Waves - * 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 . - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include "../../bscconfig.h" - -#if BUILD_IU -#include -#include -#include -#endif - -#define _GNU_SOURCE -#include - -void *tall_sgsn_ctx; -struct ctrl_handle *g_ctrlh; - -struct gprs_ns_inst *sgsn_nsi; -static int daemonize = 0; -const char *openbsc_copyright = - "Copyright (C) 2010 Harald Welte and On-Waves\r\n" - "License AGPLv3+: GNU AGPL version 3 or later \r\n" - "This is free software: you are free to change and redistribute it.\r\n" - "There is NO WARRANTY, to the extent permitted by law.\r\n"; - -#define CONFIG_FILE_DEFAULT "osmo-sgsn.cfg" -#define CONFIG_FILE_LEGACY "osmo_sgsn.cfg" - - -struct sgsn_instance *sgsn; - -/* call-back function for the NS protocol */ -static int sgsn_ns_cb(enum gprs_ns_evt event, struct gprs_nsvc *nsvc, - struct msgb *msg, uint16_t bvci) -{ - int rc = 0; - - switch (event) { - case GPRS_NS_EVT_UNIT_DATA: - /* hand the message into the BSSGP implementation */ - rc = bssgp_rcvmsg(msg); - break; - default: - LOGP(DGPRS, LOGL_ERROR, "SGSN: Unknown event %u from NS\n", event); - if (msg) - msgb_free(msg); - rc = -EIO; - break; - } - return rc; -} - -/* call-back function for the BSSGP protocol */ -int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx) -{ - struct osmo_bssgp_prim *bp; - bp = container_of(oph, struct osmo_bssgp_prim, oph); - - switch (oph->sap) { - case SAP_BSSGP_LL: - switch (oph->primitive) { - case PRIM_BSSGP_UL_UD: - return gprs_llc_rcvmsg(oph->msg, bp->tp); - } - break; - case SAP_BSSGP_GMM: - switch (oph->primitive) { - case PRIM_BSSGP_GMM_SUSPEND: - return gprs_gmm_rx_suspend(bp->ra_id, bp->tlli); - case PRIM_BSSGP_GMM_RESUME: - return gprs_gmm_rx_resume(bp->ra_id, bp->tlli, - bp->u.resume.suspend_ref); - } - break; - case SAP_BSSGP_NM: - break; - } - return 0; -} - -static void signal_handler(int signal) -{ - fprintf(stdout, "signal %u received\n", signal); - - switch (signal) { - case SIGINT: - case SIGTERM: - osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL); - sleep(1); - exit(0); - break; - case SIGABRT: - /* in case of abort, we want to obtain a talloc report - * and then return to the caller, who will abort the process */ - case SIGUSR1: - talloc_report(tall_vty_ctx, stderr); - talloc_report_full(tall_sgsn_ctx, stderr); - break; - case SIGUSR2: - talloc_report_full(tall_vty_ctx, stderr); - break; - default: - break; - } -} - -/* NSI that BSSGP uses when transmitting on NS */ -extern struct gprs_ns_inst *bssgp_nsi; - -int sgsn_vty_is_config_node(struct vty *vty, int node) -{ - /* So far the SGSN has no nested nodes that need parent node - * declaration, except for the ss7 vty nodes. */ - switch (node) { - case SGSN_NODE: - return 1; - default: -#if BUILD_IU - return osmo_ss7_is_config_node(vty, node); -#else - return 0; -#endif - } -} - -int sgsn_vty_go_parent(struct vty *vty) -{ - /* So far the SGSN has no nested nodes that need parent node - * declaration, except for the ss7 vty nodes. */ -#if BUILD_IU - return osmo_ss7_vty_go_parent(vty); -#else - vty->node = CONFIG_NODE; - vty->index = NULL; - return 0; -#endif -} - -static struct vty_app_info vty_info = { - .name = "OsmoSGSN", - .version = PACKAGE_VERSION, - .go_parent_cb = sgsn_vty_go_parent, - .is_config_node = sgsn_vty_is_config_node, -}; - -static void print_help(void) -{ - printf("Some useful help...\n"); - printf(" -h --help\tthis text\n"); - printf(" -V --version\tPrint the version\n"); - printf(" -D --daemonize\tFork the process into a background daemon\n"); - printf(" -d option --debug\tenable Debugging\n"); - printf(" -s --disable-color\n"); - printf(" -c --config-file\tThe config file to use [%s]\n", CONFIG_FILE_DEFAULT); - printf(" -e --log-level number\tSet a global log level\n"); -} - -static void handle_options(int argc, char **argv) -{ - while (1) { - int option_index = 0, c; - static struct option long_options[] = { - {"help", 0, 0, 'h'}, - {"debug", 1, 0, 'd'}, - {"daemonize", 0, 0, 'D'}, - {"config-file", 1, 0, 'c'}, - {"disable-color", 0, 0, 's'}, - {"timestamp", 0, 0, 'T'}, - { "version", 0, 0, 'V' }, - {"log-level", 1, 0, 'e'}, - {NULL, 0, 0, 0} - }; - - c = getopt_long(argc, argv, "hd:Dc:sTVe:", - long_options, &option_index); - if (c == -1) - break; - - switch (c) { - case 'h': - //print_usage(); - print_help(); - exit(0); - case 's': - log_set_use_color(osmo_stderr_target, 0); - break; - case 'd': - log_parse_category_mask(osmo_stderr_target, optarg); - break; - case 'D': - daemonize = 1; - break; - case 'c': - osmo_talloc_replace_string(sgsn, &sgsn->config_file, optarg); - break; - case 'T': - log_set_print_timestamp(osmo_stderr_target, 1); - break; - case 'V': - print_version(1); - exit(0); - break; - case 'e': - log_set_log_level(osmo_stderr_target, atoi(optarg)); - break; - default: - /* ignore */ - break; - } - } -} - -/* default categories */ -static struct log_info_cat gprs_categories[] = { - [DMM] = { - .name = "DMM", - .description = "Layer3 Mobility Management (MM)", - .color = "\033[1;33m", - .enabled = 1, .loglevel = LOGL_NOTICE, - }, - [DPAG] = { - .name = "DPAG", - .description = "Paging Subsystem", - .color = "\033[1;38m", - .enabled = 1, .loglevel = LOGL_NOTICE, - }, - [DMEAS] = { - .name = "DMEAS", - .description = "Radio Measurement Processing", - .enabled = 0, .loglevel = LOGL_NOTICE, - }, - [DREF] = { - .name = "DREF", - .description = "Reference Counting", - .enabled = 0, .loglevel = LOGL_NOTICE, - }, - [DGPRS] = { - .name = "DGPRS", - .description = "GPRS Packet Service", - .enabled = 1, .loglevel = LOGL_NOTICE, - }, - [DNS] = { - .name = "DNS", - .description = "GPRS Network Service (NS)", - .enabled = 1, .loglevel = LOGL_NOTICE, - }, - [DBSSGP] = { - .name = "DBSSGP", - .description = "GPRS BSS Gateway Protocol (BSSGP)", - .enabled = 1, .loglevel = LOGL_NOTICE, - }, - [DLLC] = { - .name = "DLLC", - .description = "GPRS Logical Link Control Protocol (LLC)", - .enabled = 1, .loglevel = LOGL_NOTICE, - }, - [DSNDCP] = { - .name = "DSNDCP", - .description = "GPRS Sub-Network Dependent Control Protocol (SNDCP)", - .enabled = 1, .loglevel = LOGL_NOTICE, - }, - [DRANAP] = { - .name = "DRANAP", - .description = "RAN Application Part (RANAP)", - .enabled = 1, .loglevel = LOGL_NOTICE, - }, - [DSUA] = { - .name = "DSUA", - .description = "SCCP User Adaptation (SUA)", - .enabled = 1, .loglevel = LOGL_NOTICE, - }, - [DSLHC] = { - .name = "DSLHC", - .description = "RFC1144 TCP/IP Header compression (SLHC)", - .enabled = 1, .loglevel = LOGL_NOTICE, - }, - [DV42BIS] = { - .name = "DV42BIS", - .description = "V.42bis data compression (SNDCP)", - .enabled = 1, .loglevel = LOGL_NOTICE, - }, - [DGTP] = { - .name = "DGTP", - .description = "GPRS Tunnelling Protocol (GTP)", - .enabled = 1, .loglevel = LOGL_NOTICE, - }, -}; - -static const struct log_info gprs_log_info = { - .filter_fn = gprs_log_filter_fn, - .cat = gprs_categories, - .num_cat = ARRAY_SIZE(gprs_categories), -}; - -static bool file_exists(const char *path) -{ - struct stat sb; - return stat(path, &sb) ? false : true; -} - -int main(int argc, char **argv) -{ - int rc; -#if BUILD_IU - struct osmo_sccp_instance *sccp; -#endif - - srand(time(NULL)); - tall_sgsn_ctx = talloc_named_const(NULL, 0, "osmo_sgsn"); - sgsn = sgsn_instance_alloc(tall_sgsn_ctx); - msgb_talloc_ctx_init(tall_sgsn_ctx, 0); - vty_info.tall_ctx = tall_sgsn_ctx; - - signal(SIGINT, &signal_handler); - signal(SIGTERM, &signal_handler); - signal(SIGABRT, &signal_handler); - signal(SIGUSR1, &signal_handler); - signal(SIGUSR2, &signal_handler); - - osmo_init_ignore_signals(); - osmo_init_logging2(tall_sgsn_ctx, &gprs_log_info); - osmo_stats_init(tall_sgsn_ctx); - - vty_info.copyright = openbsc_copyright; - vty_init(&vty_info); - logging_vty_add_cmds(); - osmo_talloc_vty_add_cmds(); - osmo_stats_vty_add_cmds(); - sgsn_vty_init(&sgsn->cfg); - ctrl_vty_init(tall_sgsn_ctx); - -#if BUILD_IU - osmo_ss7_init(); - osmo_ss7_vty_init_asp(tall_sgsn_ctx); - osmo_sccp_vty_init(); -#endif - - handle_options(argc, argv); - - /* Backwards compatibility: for years, the default config file name was - * osmo_sgsn.cfg. All other Osmocom programs use osmo-*.cfg with a - * dash. To be able to use the new config file name without breaking - * previous setups that might rely on the legacy default config file - * name, we need to look for the old config file if no -c option was - * passed AND no file exists with the new default file name. */ - if (!sgsn->config_file) { - /* No -c option was passed */ - if (file_exists(CONFIG_FILE_LEGACY) - && !file_exists(CONFIG_FILE_DEFAULT)) - osmo_talloc_replace_string(sgsn, &sgsn->config_file, CONFIG_FILE_LEGACY); - else - osmo_talloc_replace_string(sgsn, &sgsn->config_file, CONFIG_FILE_DEFAULT); - } - - rate_ctr_init(tall_sgsn_ctx); - - gprs_ns_set_log_ss(DNS); - bssgp_set_log_ss(DBSSGP); - - sgsn_nsi = gprs_ns_instantiate(&sgsn_ns_cb, tall_sgsn_ctx); - if (!sgsn_nsi) { - LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n"); - exit(1); - } - bssgp_nsi = sgsn->cfg.nsi = sgsn_nsi; - - gprs_llc_init("/usr/local/lib/osmocom/crypt/"); - sgsn_rate_ctr_init(); - sgsn_inst_init(sgsn); - - gprs_ns_vty_init(bssgp_nsi); - bssgp_vty_init(); - gprs_llc_vty_init(); - gprs_sndcp_vty_init(); - sgsn_auth_init(sgsn); - sgsn_cdr_init(sgsn); - /* FIXME: register signal handler for SS_L_NS */ - - rc = sgsn_parse_config(sgsn->config_file); - if (rc < 0) { - LOGP(DGPRS, LOGL_FATAL, "Error in config file\n"); - exit(2); - } - - /* start telnet after reading config for vty_get_bind_addr() */ - rc = telnet_init_dynif(tall_sgsn_ctx, NULL, - vty_get_bind_addr(), OSMO_VTY_PORT_SGSN); - if (rc < 0) - exit(1); - - /* start control interface after reading config for - * ctrl_vty_get_bind_addr() */ - g_ctrlh = ctrl_interface_setup_dynip(NULL, ctrl_vty_get_bind_addr(), - OSMO_CTRL_PORT_SGSN, NULL); - if (!g_ctrlh) { - LOGP(DGPRS, LOGL_ERROR, "Failed to create CTRL interface.\n"); - exit(1); - } - - if (sgsn_ctrl_cmds_install() != 0) { - LOGP(DGPRS, LOGL_ERROR, "Failed to install CTRL commands.\n"); - exit(1); - } - - - rc = sgsn_gtp_init(sgsn); - if (rc) { - LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on GTP socket\n"); - exit(2); - } else - LOGP(DGPRS, LOGL_NOTICE, "libGTP v%s initialized\n", gtp_version()); - - rc = gprs_subscr_init(sgsn); - if (rc < 0) { - LOGP(DGPRS, LOGL_FATAL, "Cannot set up subscriber management\n"); - exit(2); - } - - rc = gprs_ns_nsip_listen(sgsn_nsi); - if (rc < 0) { - LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on NSIP socket\n"); - exit(2); - } - - rc = gprs_ns_frgre_listen(sgsn_nsi); - if (rc < 0) { - LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen GRE " - "socket. Do you have CAP_NET_RAW?\n"); - exit(2); - } - - if (sgsn->cfg.dynamic_lookup) { - if (sgsn_ares_init(sgsn) != 0) { - LOGP(DGPRS, LOGL_FATAL, - "Failed to initialize c-ares(%d)\n", rc); - exit(4); - } - } - -#if BUILD_IU - /* Note that these are mostly defaults and can be overriden from the VTY */ - sccp = osmo_sccp_simple_client_on_ss7_id(tall_sgsn_ctx, - sgsn->cfg.iu.cs7_instance, - "OsmoSGSN", - (23 << 3) + 4, - OSMO_SS7_ASP_PROT_M3UA, - 0, NULL, - 0, "127.0.0.1"); - if (!sccp) { - printf("Setting up SCCP client failed.\n"); - return 8; - } - - ranap_iu_init(tall_sgsn_ctx, DRANAP, "OsmoSGSN-IuPS", sccp, gsm0408_gprs_rcvmsg_iu, sgsn_ranap_iu_event); -#endif - - if (daemonize) { - rc = osmo_daemonize(); - if (rc < 0) { - perror("Error during daemonize"); - exit(1); - } - } - - while (1) { - rc = osmo_select_main(0); - if (rc < 0) - exit(3); - } - - /* not reached */ - exit(0); -} diff --git a/src/gprs/sgsn_vty.c b/src/gprs/sgsn_vty.c deleted file mode 100644 index 184ece761..000000000 --- a/src/gprs/sgsn_vty.c +++ /dev/null @@ -1,1504 +0,0 @@ -/* - * (C) 2010-2016 by Harald Welte - * (C) 2010 by On-Waves - * (C) 2015 by Holger Hans Peter Freyther - * 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 . - * - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include "../../bscconfig.h" - -#ifdef BUILD_IU -#include -#endif - -extern void *tall_sgsn_ctx; - -static struct sgsn_config *g_cfg = NULL; - -const struct value_string sgsn_auth_pol_strs[] = { - { SGSN_AUTH_POLICY_OPEN, "accept-all" }, - { SGSN_AUTH_POLICY_CLOSED, "closed" }, - { SGSN_AUTH_POLICY_ACL_ONLY, "acl-only" }, - { SGSN_AUTH_POLICY_REMOTE, "remote" }, - { 0, NULL } -}; - -/* Section 11.2.2 / Table 11.3a GPRS Mobility management timers – MS side */ -#define GSM0408_T3312_SECS (10*60) /* periodic RAU interval, default 54min */ - -/* Section 11.2.2 / Table 11.4 MM timers netwokr side */ -#define GSM0408_T3322_SECS 6 /* DETACH_REQ -> DETACH_ACC */ -#define GSM0408_T3350_SECS 6 /* waiting for ATT/RAU/TMSI COMPL */ -#define GSM0408_T3360_SECS 6 /* waiting for AUTH/CIPH RESP */ -#define GSM0408_T3370_SECS 6 /* waiting for ID RESP */ - -/* Section 11.2.2 / Table 11.4a MM timers network side */ -#define GSM0408_T3313_SECS 30 /* waiting for paging response */ -#define GSM0408_T3314_SECS 44 /* force to STBY on expiry, Ready timer */ -#define GSM0408_T3316_SECS 44 - -/* Section 11.3 / Table 11.2d Timers of Session Management - network side */ -#define GSM0408_T3385_SECS 8 /* wait for ACT PDP CTX REQ */ -#define GSM0408_T3386_SECS 8 /* wait for MODIFY PDP CTX ACK */ -#define GSM0408_T3395_SECS 8 /* wait for DEACT PDP CTX ACK */ -#define GSM0408_T3397_SECS 8 /* wait for DEACT AA PDP CTX ACK */ - - -static struct osmo_tdef sgsn_T_defs[] = { - { .T=3312, .default_val=GSM0408_T3312_SECS, .desc="Periodic RA Update timer (s)" }, - { .T=3313, .default_val=GSM0408_T3313_SECS, .desc="Waiting for paging response timer (s)" }, - { .T=3314, .default_val=GSM0408_T3314_SECS, .desc="READY timer. Force to STANDBY on expiry timer (s)" }, - { .T=3316, .default_val=GSM0408_T3316_SECS, .desc="AA-Ready timer (s)" }, - { .T=3322, .default_val=GSM0408_T3322_SECS, .desc="Detach request -> accept timer (s)" }, - { .T=3350, .default_val=GSM0408_T3350_SECS, .desc="Waiting for ATT/RAU/TMSI_COMPL timer (s)" }, - { .T=3360, .default_val=GSM0408_T3360_SECS, .desc="Waiting for AUTH/CIPH response timer (s)" }, - { .T=3370, .default_val=GSM0408_T3370_SECS, .desc="Waiting for IDENTITY response timer (s)" }, - { .T=3385, .default_val=GSM0408_T3385_SECS, .desc="Wait for ACT PDP CTX REQ timer (s)" }, - { .T=3386, .default_val=GSM0408_T3386_SECS, .desc="Wait for MODIFY PDP CTX ACK timer (s)" }, - { .T=3395, .default_val=GSM0408_T3395_SECS, .desc="Wait for DEACT PDP CTX ACK timer (s)" }, - { .T=3397, .default_val=GSM0408_T3397_SECS, .desc="Wait for DEACT AA PDP CTX ACK timer (s)" }, - {} -}; - -DEFUN(show_timer, show_timer_cmd, - "show timer " OSMO_TDEF_VTY_ARG_T_OPTIONAL, - SHOW_STR "Show timers\n" - OSMO_TDEF_VTY_DOC_T) -{ - const char *T_arg = argc > 0 ? argv[0] : NULL; - return osmo_tdef_vty_show_cmd(vty, g_cfg->T_defs, T_arg, NULL); -} - -DEFUN(cfg_sgsn_timer, cfg_sgsn_timer_cmd, - "timer " OSMO_TDEF_VTY_ARG_SET_OPTIONAL, - "Configure or show timers\n" - OSMO_TDEF_VTY_DOC_SET) -{ - /* If any arguments are missing, redirect to 'show' */ - if (argc < 2) - return show_timer(self, vty, argc, argv); - return osmo_tdef_vty_set_cmd(vty, g_cfg->T_defs, argv); -} - -char *gprs_pdpaddr2str(uint8_t *pdpa, uint8_t len) -{ - static char str[INET6_ADDRSTRLEN + 10]; - - if (!pdpa || len < 2) - return "none"; - - switch (pdpa[0] & 0x0f) { - case PDP_TYPE_ORG_IETF: - switch (pdpa[1]) { - case PDP_TYPE_N_IETF_IPv4: - if (len < 2 + 4) - break; - strcpy(str, "IPv4 "); - inet_ntop(AF_INET, pdpa+2, str+5, sizeof(str)-5); - return str; - case PDP_TYPE_N_IETF_IPv6: - if (len < 2 + 8) - break; - strcpy(str, "IPv6 "); - inet_ntop(AF_INET6, pdpa+2, str+5, sizeof(str)-5); - return str; - default: - break; - } - break; - case PDP_TYPE_ORG_ETSI: - if (pdpa[1] == PDP_TYPE_N_ETSI_PPP) - return "PPP"; - break; - default: - break; - } - - return "invalid"; -} - -static struct cmd_node sgsn_node = { - SGSN_NODE, - "%s(config-sgsn)# ", - 1, -}; - -static int config_write_sgsn(struct vty *vty) -{ - struct sgsn_ggsn_ctx *gctx; - struct imsi_acl_entry *acl; - struct apn_ctx *actx; - struct ares_addr_node *server; - - vty_out(vty, "sgsn%s", VTY_NEWLINE); - - vty_out(vty, " gtp local-ip %s%s", - inet_ntoa(g_cfg->gtp_listenaddr.sin_addr), VTY_NEWLINE); - - llist_for_each_entry(gctx, &sgsn_ggsn_ctxts, list) { - if (gctx->id == UINT32_MAX) - continue; - - vty_out(vty, " ggsn %u remote-ip %s%s", gctx->id, - inet_ntoa(gctx->remote_addr), VTY_NEWLINE); - vty_out(vty, " ggsn %u gtp-version %u%s", gctx->id, - gctx->gtp_version, VTY_NEWLINE); - if (gctx->echo_interval) - vty_out(vty, " ggsn %u echo-interval %u%s", - gctx->id, gctx->echo_interval, VTY_NEWLINE); - else - vty_out(vty, " ggsn %u no echo-interval%s", - gctx->id, VTY_NEWLINE); - } - - if (sgsn->cfg.dynamic_lookup) - vty_out(vty, " ggsn dynamic%s", VTY_NEWLINE); - - for (server = sgsn->ares_servers; server; server = server->next) - vty_out(vty, " grx-dns-add %s%s", inet_ntoa(server->addr.addr4), VTY_NEWLINE); - - if (g_cfg->cipher != GPRS_ALGO_GEA0) - vty_out(vty, " encryption %s%s", - get_value_string(gprs_cipher_names, g_cfg->cipher), - VTY_NEWLINE); - if (g_cfg->sgsn_ipa_name) - vty_out(vty, " gsup ipa-name %s%s", g_cfg->sgsn_ipa_name, VTY_NEWLINE); - if (g_cfg->gsup_server_addr.sin_addr.s_addr) - vty_out(vty, " gsup remote-ip %s%s", - inet_ntoa(g_cfg->gsup_server_addr.sin_addr), VTY_NEWLINE); - if (g_cfg->gsup_server_port) - vty_out(vty, " gsup remote-port %d%s", - g_cfg->gsup_server_port, VTY_NEWLINE); - if (g_cfg->auth_policy == SGSN_AUTH_POLICY_REMOTE && !g_cfg->require_authentication) - vty_out(vty, " authentication optional%s", VTY_NEWLINE); - vty_out(vty, " auth-policy %s%s", - get_value_string(sgsn_auth_pol_strs, g_cfg->auth_policy), - VTY_NEWLINE); - - vty_out(vty, " gsup oap-id %d%s", - (int)g_cfg->oap.client_id, VTY_NEWLINE); - if (g_cfg->oap.secret_k_present != 0) - vty_out(vty, " gsup oap-k %s%s", - osmo_hexdump_nospc(g_cfg->oap.secret_k, sizeof(g_cfg->oap.secret_k)), - VTY_NEWLINE); - if (g_cfg->oap.secret_opc_present != 0) - vty_out(vty, " gsup oap-opc %s%s", - osmo_hexdump_nospc(g_cfg->oap.secret_opc, sizeof(g_cfg->oap.secret_opc)), - VTY_NEWLINE); - - llist_for_each_entry(acl, &g_cfg->imsi_acl, list) - vty_out(vty, " imsi-acl add %s%s", acl->imsi, VTY_NEWLINE); - - if (llist_empty(&sgsn_apn_ctxts)) - vty_out(vty, " ! apn * ggsn 0%s", VTY_NEWLINE); - llist_for_each_entry(actx, &sgsn_apn_ctxts, list) { - if (strlen(actx->imsi_prefix) > 0) - vty_out(vty, " apn %s imsi-prefix %s ggsn %u%s", - actx->name, actx->imsi_prefix, actx->ggsn->id, - VTY_NEWLINE); - else - vty_out(vty, " apn %s ggsn %u%s", actx->name, - actx->ggsn->id, VTY_NEWLINE); - } - - if (g_cfg->cdr.filename) - vty_out(vty, " cdr filename %s%s", g_cfg->cdr.filename, VTY_NEWLINE); - else - vty_out(vty, " no cdr filename%s", VTY_NEWLINE); - if (g_cfg->cdr.trap) - vty_out(vty, " cdr trap%s", VTY_NEWLINE); - else - vty_out(vty, " no cdr trap%s", VTY_NEWLINE); - vty_out(vty, " cdr interval %d%s", g_cfg->cdr.interval, VTY_NEWLINE); - - osmo_tdef_vty_write(vty, g_cfg->T_defs, " timer "); - - if (g_cfg->pcomp_rfc1144.active) { - vty_out(vty, " compression rfc1144 active slots %d%s", - g_cfg->pcomp_rfc1144.s01 + 1, VTY_NEWLINE); - } else if (g_cfg->pcomp_rfc1144.passive) { - vty_out(vty, " compression rfc1144 passive%s", VTY_NEWLINE); - } else - vty_out(vty, " no compression rfc1144%s", VTY_NEWLINE); - - if (g_cfg->dcomp_v42bis.active && g_cfg->dcomp_v42bis.p0 == 1) { - vty_out(vty, - " compression v42bis active direction sgsn codewords %d strlen %d%s", - g_cfg->dcomp_v42bis.p1, g_cfg->dcomp_v42bis.p2, - VTY_NEWLINE); - } else if (g_cfg->dcomp_v42bis.active && g_cfg->dcomp_v42bis.p0 == 2) { - vty_out(vty, - " compression v42bis active direction ms codewords %d strlen %d%s", - g_cfg->dcomp_v42bis.p1, g_cfg->dcomp_v42bis.p2, - VTY_NEWLINE); - } else if (g_cfg->dcomp_v42bis.active && g_cfg->dcomp_v42bis.p0 == 3) { - vty_out(vty, - " compression v42bis active direction both codewords %d strlen %d%s", - g_cfg->dcomp_v42bis.p1, g_cfg->dcomp_v42bis.p2, - VTY_NEWLINE); - } else if (g_cfg->dcomp_v42bis.passive) { - vty_out(vty, " compression v42bis passive%s", VTY_NEWLINE); - } else - vty_out(vty, " no compression v42bis%s", VTY_NEWLINE); - -#ifdef BUILD_IU - vty_out(vty, " cs7-instance-iu %u%s", g_cfg->iu.cs7_instance, - VTY_NEWLINE); - ranap_iu_vty_config_write(vty, " "); -#endif - - return CMD_SUCCESS; -} - -#define SGSN_STR "Configure the SGSN\n" -#define GGSN_STR "Configure the GGSN information\n" - -DEFUN(cfg_sgsn, cfg_sgsn_cmd, - "sgsn", - SGSN_STR) -{ - vty->node = SGSN_NODE; - return CMD_SUCCESS; -} - -DEFUN(cfg_sgsn_bind_addr, cfg_sgsn_bind_addr_cmd, - "gtp local-ip A.B.C.D", - "GTP Parameters\n" - "Set the IP address for the local GTP bind for the Gp interface (towards the GGSNs)." - " Note: in case you would like to run the GGSN on the same machine as the SGSN, you can not run" - " both on the same IP address, since both sides are specified to use the same GTP port numbers" - " (" OSMO_STRINGIFY_VAL(GTP1C_PORT) " and " OSMO_STRINGIFY_VAL(GTP1U_PORT) ")." - " For example, you could use 127.0.0.1 for the SGSN and 127.0.0.2 for the GGSN in such" - " situations.\n" - "IPv4 Address\n") -{ - inet_aton(argv[0], &g_cfg->gtp_listenaddr.sin_addr); - - return CMD_SUCCESS; -} - -DEFUN(cfg_ggsn_remote_ip, cfg_ggsn_remote_ip_cmd, - "ggsn <0-255> remote-ip A.B.C.D", - GGSN_STR "GGSN Number\n" - "Configure this static GGSN to use the specified remote IP address.\n" - "IPv4 Address\n") -{ - uint32_t id = atoi(argv[0]); - struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id); - - inet_aton(argv[1], &ggc->remote_addr); - - return CMD_SUCCESS; -} - -#if 0 -DEFUN(cfg_ggsn_remote_port, cfg_ggsn_remote_port_cmd, - "ggsn <0-255> remote-port <0-65535>", - "") -{ - uint32_t id = atoi(argv[0]); - struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id); - uint16_t port = atoi(argv[1]); - -} -#endif - -DEFUN(cfg_ggsn_gtp_version, cfg_ggsn_gtp_version_cmd, - "ggsn <0-255> gtp-version (0|1)", - GGSN_STR "GGSN Number\n" "GTP Version\n" - "Version 0\n" "Version 1\n") -{ - uint32_t id = atoi(argv[0]); - struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id); - - if (atoi(argv[1])) - ggc->gtp_version = 1; - else - ggc->gtp_version = 0; - - return CMD_SUCCESS; -} - -/* Seee 3GPP TS 29.060 section 7.2.1 */ -DEFUN(cfg_ggsn_echo_interval, cfg_ggsn_echo_interval_cmd, - "ggsn <0-255> 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") -{ - uint32_t id = atoi(argv[0]); - struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id); - - ggc->echo_interval = atoi(argv[1]); - - if (ggc->echo_interval < 60) - vty_out(vty, "%% 3GPP TS 29.060 section 7.2.1 states interval should " \ - "not be lower than 60 seconds, use this value for " \ - "testing purposes only!%s", VTY_NEWLINE); - - sgsn_ggsn_ctx_check_echo_timer(ggc); - return CMD_SUCCESS; -} - -DEFUN(cfg_ggsn_no_echo_interval, cfg_ggsn_no_echo_interval_cmd, - "ggsn <0-255> no echo-interval", - GGSN_STR "GGSN Number\n" - NO_STR "Send an echo request to this static GGSN every interval.\n") -{ - uint32_t id = atoi(argv[0]); - struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id); - - ggc->echo_interval = 0; - sgsn_ggsn_ctx_check_echo_timer(ggc); - - return CMD_SUCCESS; -} - -DEFUN(cfg_ggsn_dynamic_lookup, cfg_ggsn_dynamic_lookup_cmd, - "ggsn dynamic", - GGSN_STR - "Enable dynamic resolving of GGSNs based on DNS resolving the APN name like in a GRX-style setup." - " Changing this setting requires a restart.\n") -{ - sgsn->cfg.dynamic_lookup = 1; - return CMD_SUCCESS; -} - -DEFUN(cfg_grx_ggsn, cfg_grx_ggsn_cmd, - "grx-dns-add A.B.C.D", - "Use the specified IP address for DNS-resolving the AP names to GGSN IP addresses\n" - "IPv4 address\n") -{ - struct ares_addr_node *node = talloc_zero(tall_sgsn_ctx, struct ares_addr_node); - node->family = AF_INET; - inet_aton(argv[0], &node->addr.addr4); - - node->next = sgsn->ares_servers; - sgsn->ares_servers = node; - return CMD_SUCCESS; -} - -#define APN_STR "Configure the information per APN\n" -#define APN_GW_STR "The APN gateway name optionally prefixed by '*' (wildcard)\n" - -static int add_apn_ggsn_mapping(struct vty *vty, const char *apn_str, - const char *imsi_prefix, int ggsn_id) -{ - struct apn_ctx *actx; - struct sgsn_ggsn_ctx *ggsn; - - ggsn = sgsn_ggsn_ctx_by_id(ggsn_id); - if (ggsn == NULL) { - vty_out(vty, "%% a GGSN with id %d has not been defined%s", - ggsn_id, VTY_NEWLINE); - return CMD_WARNING; - } - - actx = sgsn_apn_ctx_find_alloc(apn_str, imsi_prefix); - if (!actx) { - vty_out(vty, "%% unable to create APN context for %s/%s%s", - apn_str, imsi_prefix, VTY_NEWLINE); - return CMD_WARNING; - } - - actx->ggsn = ggsn; - - return CMD_SUCCESS; -} - -DEFUN(cfg_apn_ggsn, cfg_apn_ggsn_cmd, - "apn APNAME ggsn <0-255>", - APN_STR APN_GW_STR - "Select the GGSN to use for the given APN gateway prefix\n" - "The GGSN id") -{ - - return add_apn_ggsn_mapping(vty, argv[0], "", atoi(argv[1])); -} - -DEFUN(cfg_apn_imsi_ggsn, cfg_apn_imsi_ggsn_cmd, - "apn APNAME imsi-prefix IMSIPRE ggsn <0-255>", - APN_STR APN_GW_STR - "Select the GGSN to use for the given APN gateway prefix if and only if the IMSI matches the" - " given prefix.\n" - "An IMSI prefix\n" - "Select the GGSN to use when APN gateway and IMSI prefix match\n" - "The GGSN id") -{ - - return add_apn_ggsn_mapping(vty, argv[0], argv[1], atoi(argv[2])); -} - -const struct value_string gprs_mm_st_strs[] = { - { GMM_DEREGISTERED, "DEREGISTERED" }, - { GMM_COMMON_PROC_INIT, "COMMON PROCEDURE (INIT)" }, - { GMM_REGISTERED_NORMAL, "REGISTERED (NORMAL)" }, - { GMM_REGISTERED_SUSPENDED, "REGISTERED (SUSPENDED)" }, - { GMM_DEREGISTERED_INIT, "DEREGISTERED (INIT)" }, - { 0, NULL } -}; - -char *sgsn_gtp_ntoa(struct ul16_t *ul) -{ - struct in_addr ia; - - if (gsna2in_addr(&ia, ul) != 0) - return "UNKNOWN"; - - return inet_ntoa(ia); -} - -static void vty_dump_pdp(struct vty *vty, const char *pfx, - struct sgsn_pdp_ctx *pdp) -{ - const char *imsi = pdp->mm ? pdp->mm->imsi : "(detaching)"; - vty_out(vty, "%sPDP Context IMSI: %s, SAPI: %u, NSAPI: %u, TI: %u%s", - pfx, imsi, pdp->sapi, pdp->nsapi, pdp->ti, VTY_NEWLINE); - if (pdp->lib) { - char apnbuf[APN_MAXLEN + 1]; - vty_out(vty, "%s APN: %s%s", pfx, - osmo_apn_to_str(apnbuf, pdp->lib->apn_use.v, pdp->lib->apn_use.l), - VTY_NEWLINE); - vty_out(vty, "%s PDP Address: %s%s", pfx, - gprs_pdpaddr2str(pdp->lib->eua.v, pdp->lib->eua.l), - VTY_NEWLINE); - vty_out(vty, "%s GTPv%d Local Control(%s / TEIC: 0x%08x) ", pfx, pdp->lib->version, - sgsn_gtp_ntoa(&pdp->lib->gsnlc), pdp->lib->teic_own); - vty_out(vty, "Data(%s / TEID: 0x%08x)%s", - sgsn_gtp_ntoa(&pdp->lib->gsnlu), pdp->lib->teid_own, VTY_NEWLINE); - vty_out(vty, "%s GTPv%d Remote Control(%s / TEIC: 0x%08x) ", pfx, pdp->lib->version, - sgsn_gtp_ntoa(&pdp->lib->gsnrc), pdp->lib->teic_gn); - vty_out(vty, "Data(%s / TEID: 0x%08x)%s", - sgsn_gtp_ntoa(&pdp->lib->gsnru), pdp->lib->teid_gn, VTY_NEWLINE); - } - - vty_out_rate_ctr_group(vty, " ", pdp->ctrg); -} - -static void vty_dump_mmctx(struct vty *vty, const char *pfx, - struct sgsn_mm_ctx *mm, int pdp) -{ - uint32_t id = 0; - const char *mm_state_name = NULL; - - switch(mm->ran_type) { - case MM_CTX_T_UTRAN_Iu: -#if BUILD_IU - id = mm->iu.ue_ctx->conn_id; - mm_state_name = osmo_fsm_inst_state_name(mm->iu.mm_state_fsm); -#endif - break; - case MM_CTX_T_GERAN_Gb: - id = mm->gb.tlli; - mm_state_name = osmo_fsm_inst_state_name(mm->gb.mm_state_fsm); - break; - } - - vty_out(vty, "%sMM Context for IMSI %s, IMEI %s, P-TMSI %08x%s", - pfx, mm->imsi, mm->imei, mm->p_tmsi, VTY_NEWLINE); - vty_out(vty, "%s MSISDN: %s, TLLI: %08x%s HLR: %s", - pfx, mm->msisdn, id, mm->hlr, VTY_NEWLINE); - vty_out(vty, "%s GMM State: %s, Routeing Area: %s, Cell ID: %u%s", - pfx, get_value_string(gprs_mm_st_strs, mm->gmm_state), - osmo_rai_name(&mm->ra), mm->gb.cell_id, VTY_NEWLINE); - vty_out(vty, "%s MM State: %s, RAN Type: %s%s", pfx, mm_state_name, - get_value_string(sgsn_ran_type_names, mm->ran_type), VTY_NEWLINE); - - vty_out_rate_ctr_group(vty, " ", mm->ctrg); - - if (pdp) { - struct sgsn_pdp_ctx *pdp; - - llist_for_each_entry(pdp, &mm->pdp_list, list) - vty_dump_pdp(vty, " ", pdp); - } -} - -DEFUN(show_sgsn, show_sgsn_cmd, "show sgsn", - SHOW_STR "Display information about the SGSN") -{ - if (sgsn->gsup_client) { - struct ipa_client_conn *link = sgsn->gsup_client->link; - vty_out(vty, - " Remote authorization: %sconnected to %s:%d via GSUP%s", - sgsn->gsup_client->is_connected ? "" : "not ", - link->addr, link->port, - VTY_NEWLINE); - } - if (sgsn->gsn) - vty_out(vty, " GSN: signalling %s, user traffic %s%s", - inet_ntoa(sgsn->gsn->gsnc), inet_ntoa(sgsn->gsn->gsnu), VTY_NEWLINE); - - /* FIXME: statistics */ - return CMD_SUCCESS; -} - -#define MMCTX_STR "MM Context\n" -#define INCLUDE_PDP_STR "Include PDP Context Information\n" - -#if 0 -DEFUN(show_mmctx_tlli, show_mmctx_tlli_cmd, - "show mm-context tlli HEX [pdp]", - SHOW_STR MMCTX_STR "Identify by TLLI\n" "TLLI\n" INCLUDE_PDP_STR) -{ - uint32_t tlli; - struct sgsn_mm_ctx *mm; - - tlli = strtoul(argv[0], NULL, 16); - mm = sgsn_mm_ctx_by_tlli(tlli); - if (!mm) { - vty_out(vty, "No MM context for TLLI %08x%s", - tlli, VTY_NEWLINE); - return CMD_WARNING; - } - vty_dump_mmctx(vty, "", mm, argv[1] ? 1 : 0); - return CMD_SUCCESS; -} -#endif - -DEFUN(swow_mmctx_imsi, show_mmctx_imsi_cmd, - "show mm-context imsi IMSI [pdp]", - SHOW_STR MMCTX_STR "Identify by IMSI\n" "IMSI of the MM Context\n" - INCLUDE_PDP_STR) -{ - struct sgsn_mm_ctx *mm; - - mm = sgsn_mm_ctx_by_imsi(argv[0]); - if (!mm) { - vty_out(vty, "No MM context for IMSI %s%s", - argv[0], VTY_NEWLINE); - return CMD_WARNING; - } - vty_dump_mmctx(vty, "", mm, argv[1] ? 1 : 0); - return CMD_SUCCESS; -} - -DEFUN(swow_mmctx_all, show_mmctx_all_cmd, - "show mm-context all [pdp]", - SHOW_STR MMCTX_STR "All MM Contexts\n" INCLUDE_PDP_STR) -{ - struct sgsn_mm_ctx *mm; - - llist_for_each_entry(mm, &sgsn_mm_ctxts, list) - vty_dump_mmctx(vty, "", mm, argv[0] ? 1 : 0); - - return CMD_SUCCESS; -} - -DEFUN(show_pdpctx_all, show_pdpctx_all_cmd, - "show pdp-context all", - SHOW_STR "Display information on PDP Context\n" "Show everything\n") -{ - struct sgsn_pdp_ctx *pdp; - - llist_for_each_entry(pdp, &sgsn_pdp_ctxts, g_list) - vty_dump_pdp(vty, "", pdp); - - return CMD_SUCCESS; -} - - -DEFUN(imsi_acl, cfg_imsi_acl_cmd, - "imsi-acl (add|del) IMSI", - "Access Control List of foreign IMSIs\n" - "Add IMSI to ACL\n" - "Remove IMSI from ACL\n" - "IMSI of subscriber\n") -{ - char imsi_sanitized[GSM23003_IMSI_MAX_DIGITS + 1]; - const char *op = argv[0]; - const char *imsi = imsi_sanitized; - size_t len = strnlen(argv[1], GSM23003_IMSI_MAX_DIGITS + 1); - int rc; - - memset(imsi_sanitized, '0', GSM23003_IMSI_MAX_DIGITS); - imsi_sanitized[GSM23003_IMSI_MAX_DIGITS] = '\0'; - - /* Sanitize IMSI */ - if (len > GSM23003_IMSI_MAX_DIGITS) { - vty_out(vty, "%% IMSI (%s) too long (max %u digits) -- ignored!%s", - argv[1], GSM23003_IMSI_MAX_DIGITS, VTY_NEWLINE); - return CMD_WARNING; - } - - osmo_strlcpy(imsi_sanitized + GSM23003_IMSI_MAX_DIGITS - len, argv[1], - sizeof(imsi_sanitized) - (GSM23003_IMSI_MAX_DIGITS - len)); - - if (!strcmp(op, "add")) - rc = sgsn_acl_add(imsi, g_cfg); - else - rc = sgsn_acl_del(imsi, g_cfg); - - if (rc < 0) { - vty_out(vty, "%% unable to %s ACL%s", op, VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - -DEFUN(cfg_encrypt, cfg_encrypt_cmd, - "encryption (GEA0|GEA1|GEA2|GEA3|GEA4)", - "Set encryption algorithm for SGSN\n" - "Use GEA0 (no encryption)\n" - "Use GEA1\nUse GEA2\nUse GEA3\nUse GEA4\n") -{ - enum gprs_ciph_algo c = get_string_value(gprs_cipher_names, argv[0]); - if (c != GPRS_ALGO_GEA0) { - if (!gprs_cipher_supported(c)) { - vty_out(vty, "%% cipher %s is unsupported in current version%s", argv[0], VTY_NEWLINE); - return CMD_WARNING; - } - - if (!g_cfg->require_authentication) { - vty_out(vty, "%% unable to use encryption %s without authentication: please adjust auth-policy%s", - argv[0], VTY_NEWLINE); - return CMD_WARNING; - } - } - - g_cfg->cipher = c; - - return CMD_SUCCESS; -} - -DEFUN(cfg_authentication, cfg_authentication_cmd, - "authentication (optional|required)", - "Whether to enforce MS authentication in GERAN (only with auth-policy remote)\n" - "Allow MS to attach via GERAN without authentication (default and only possible value for non-remote auth-policy)\n" - "Always require authentication (only available for auth-policy remote, default with that auth-policy)\n") -{ - int required = (argv[0][0] == 'r'); - - if (vty->type != VTY_FILE) { - if (g_cfg->auth_policy != SGSN_AUTH_POLICY_REMOTE && required) { - vty_out(vty, "%% Authentication is not possible without HLR, " - "consider setting 'auth-policy' to 'remote'%s", - VTY_NEWLINE); - return CMD_WARNING; - } - } - - g_cfg->require_authentication = required; - return CMD_SUCCESS; -} - -DEFUN(cfg_auth_policy, cfg_auth_policy_cmd, - "auth-policy (accept-all|closed|acl-only|remote)", - "Configure the Authorization policy of the SGSN. This setting determines which subscribers are" - " permitted to register to the network.\n" - "Accept all IMSIs (DANGEROUS)\n" - "Accept only home network subscribers or those in the ACL\n" - "Accept only subscribers in the ACL\n" - "Use remote subscription data only (HLR)\n") -{ - int val = get_string_value(sgsn_auth_pol_strs, argv[0]); - OSMO_ASSERT(val >= SGSN_AUTH_POLICY_OPEN && val <= SGSN_AUTH_POLICY_REMOTE); - g_cfg->auth_policy = val; - g_cfg->require_update_location = (val == SGSN_AUTH_POLICY_REMOTE); - - return CMD_SUCCESS; -} - -/* Subscriber */ -#include - -static void subscr_dump_full_vty(struct vty *vty, struct gprs_subscr *gsub, int pending) -{ -#if 0 - char expire_time[200]; -#endif - struct gsm_auth_tuple *at; - int at_idx; - struct sgsn_subscriber_pdp_data *pdp; - - vty_out(vty, " Authorized: %d%s", - gsub->authorized, VTY_NEWLINE); - vty_out(vty, " LAC: %d/0x%x%s", - gsub->lac, gsub->lac, VTY_NEWLINE); - vty_out(vty, " IMSI: %s%s", gsub->imsi, VTY_NEWLINE); - if (gsub->tmsi != GSM_RESERVED_TMSI) - vty_out(vty, " TMSI: %08X%s", gsub->tmsi, - VTY_NEWLINE); - if (gsub->sgsn_data->msisdn_len > 0) - vty_out(vty, " MSISDN (BCD): %s%s", - osmo_hexdump(gsub->sgsn_data->msisdn, - gsub->sgsn_data->msisdn_len), - VTY_NEWLINE); - - if (strlen(gsub->imei) > 0) - vty_out(vty, " IMEI: %s%s", gsub->imei, VTY_NEWLINE); - - for (at_idx = 0; at_idx < ARRAY_SIZE(gsub->sgsn_data->auth_triplets); - at_idx++) { - at = &gsub->sgsn_data->auth_triplets[at_idx]; - if (at->key_seq == GSM_KEY_SEQ_INVAL) - continue; - - vty_out(vty, " A3A8 tuple (used %d times): ", - at->use_count); - vty_out(vty, " CKSN: %d, ", - at->key_seq); - if (at->vec.auth_types & OSMO_AUTH_TYPE_GSM) { - vty_out(vty, "RAND: %s, ", - osmo_hexdump_nospc(at->vec.rand, - sizeof(at->vec.rand))); - vty_out(vty, "SRES: %s, ", - osmo_hexdump_nospc(at->vec.sres, - sizeof(at->vec.sres))); - vty_out(vty, "Kc: %s%s", - osmo_hexdump_nospc(at->vec.kc, - sizeof(at->vec.kc)), VTY_NEWLINE); - } - if (at->vec.auth_types & OSMO_AUTH_TYPE_UMTS) { - vty_out(vty, " AUTN: %s, ", - osmo_hexdump(at->vec.autn, - sizeof(at->vec.autn))); - vty_out(vty, "RES: %s, ", - osmo_hexdump_nospc(at->vec.res, at->vec.res_len)); - vty_out(vty, "IK: %s, ", - osmo_hexdump_nospc(at->vec.ik, sizeof(at->vec.ik))); - vty_out(vty, "CK: %s, ", - osmo_hexdump_nospc(at->vec.ck, sizeof(at->vec.ck))); - } - } - - llist_for_each_entry(pdp, &gsub->sgsn_data->pdp_list, list) { - vty_out(vty, " PDP info: Id: %d, Type: 0x%04x, APN: '%s'", - pdp->context_id, pdp->pdp_type, pdp->apn_str); - - if (pdp->qos_subscribed_len) - vty_out(vty, " QoS: %s", osmo_hexdump(pdp->qos_subscribed, pdp->qos_subscribed_len)); - - vty_out(vty, "%s", VTY_NEWLINE); - } - -#if 0 - /* print the expiration time of a subscriber */ - if (gsub->expire_lu) { - strftime(expire_time, sizeof(expire_time), - "%a, %d %b %Y %T %z", localtime(&gsub->expire_lu)); - expire_time[sizeof(expire_time) - 1] = '\0'; - vty_out(vty, " Expiration Time: %s%s", expire_time, VTY_NEWLINE); - } -#endif - - if (gsub->flags) - vty_out(vty, " Flags: %s%s%s%s%s%s", - gsub->flags & GPRS_SUBSCRIBER_FIRST_CONTACT ? - "FIRST_CONTACT " : "", - gsub->flags & GPRS_SUBSCRIBER_CANCELLED ? - "CANCELLED " : "", - gsub->flags & GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING ? - "UPDATE_LOCATION_PENDING " : "", - gsub->flags & GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING ? - "AUTH_INFO_PENDING " : "", - gsub->flags & GPRS_SUBSCRIBER_ENABLE_PURGE ? - "ENABLE_PURGE " : "", - VTY_NEWLINE); - - vty_out(vty, " Use count: %u%s", gsub->use_count, VTY_NEWLINE); -} - -#define RESET_SGSN_STATE_STR \ - "Remove all known subscribers, MM contexts and flush BSSGP queues." \ - " Useful only when running tests against the SGSN\n" - -DEFUN_HIDDEN(reset_sgsn_state, - reset_sgsn_state_cmd, - "reset sgsn state", - RESET_SGSN_STATE_STR RESET_SGSN_STATE_STR RESET_SGSN_STATE_STR) -{ - struct gprs_subscr *subscr, *tmp_subscr; - struct sgsn_mm_ctx *mm, *tmp_mm; - - llist_for_each_entry_safe(mm, tmp_mm, &sgsn_mm_ctxts, list) - { - gsm0408_gprs_access_cancelled(mm, SGSN_ERROR_CAUSE_NONE); - } - vty_out(vty, "Cancelled MM Ctx. %s", VTY_NEWLINE); - - llist_for_each_entry_safe(subscr, tmp_subscr, gprs_subscribers, entry) { - gprs_subscr_get(subscr); - gprs_subscr_cancel(subscr); - gprs_subscr_put(subscr); - } - vty_out(vty, "Removed all gprs subscribers.%s", VTY_NEWLINE); - - bssgp_flush_all_queues(); - vty_out(vty, "Flushed all BSSGPs queues.%s", VTY_NEWLINE); - - gtp_clear_queues(sgsn->gsn); - vty_out(vty, "Flushed rx & tx queus towards the GGSN.%s", VTY_NEWLINE); - - /* remove all queues to bssgp */ - return CMD_SUCCESS; -} - -DEFUN(show_subscr_cache, - show_subscr_cache_cmd, - "show subscriber cache", - SHOW_STR "Show information about subscribers\n" - "Display contents of subscriber cache\n") -{ - struct gprs_subscr *subscr; - - llist_for_each_entry(subscr, gprs_subscribers, entry) { - vty_out(vty, " Subscriber:%s", VTY_NEWLINE); - subscr_dump_full_vty(vty, subscr, 0); - } - - return CMD_SUCCESS; -} - -#define UPDATE_SUBSCR_STR "update-subscriber imsi IMSI " -#define UPDATE_SUBSCR_HELP "Update subscriber list\n" \ - "Use the IMSI to select the subscriber\n" \ - "The IMSI\n" - -#define UPDATE_SUBSCR_INSERT_HELP "Insert data into the subscriber record\n" - -DEFUN(update_subscr_insert_auth_triplet, update_subscr_insert_auth_triplet_cmd, - UPDATE_SUBSCR_STR "insert auth-triplet <1-5> sres SRES rand RAND kc KC", - UPDATE_SUBSCR_HELP - UPDATE_SUBSCR_INSERT_HELP - "Update authentication triplet\n" - "Triplet index\n" - "Set SRES value\nSRES value (4 byte) in hex\n" - "Set RAND value\nRAND value (16 byte) in hex\n" - "Set Kc value\nKc value (8 byte) in hex\n") -{ - const char *imsi = argv[0]; - const int cksn = atoi(argv[1]) - 1; - const char *sres_str = argv[2]; - const char *rand_str = argv[3]; - const char *kc_str = argv[4]; - struct gsm_auth_tuple at = {0,}; - - struct gprs_subscr *subscr; - - subscr = gprs_subscr_get_by_imsi(imsi); - if (!subscr) { - vty_out(vty, "%% unable get subscriber record for %s%s", - imsi, VTY_NEWLINE); - return CMD_WARNING; - } - - OSMO_ASSERT(subscr->sgsn_data); - - if (osmo_hexparse(sres_str, &at.vec.sres[0], sizeof(at.vec.sres)) < 0) { - vty_out(vty, "%% invalid SRES value '%s'%s", - sres_str, VTY_NEWLINE); - goto failed; - } - if (osmo_hexparse(rand_str, &at.vec.rand[0], sizeof(at.vec.rand)) < 0) { - vty_out(vty, "%% invalid RAND value '%s'%s", - rand_str, VTY_NEWLINE); - goto failed; - } - if (osmo_hexparse(kc_str, &at.vec.kc[0], sizeof(at.vec.kc)) < 0) { - vty_out(vty, "%% invalid Kc value '%s'%s", - kc_str, VTY_NEWLINE); - goto failed; - } - at.key_seq = cksn; - - subscr->sgsn_data->auth_triplets[cksn] = at; - subscr->sgsn_data->auth_triplets_updated = 1; - - gprs_subscr_put(subscr); - - return CMD_SUCCESS; - -failed: - gprs_subscr_put(subscr); - return CMD_SUCCESS; -} - -DEFUN(update_subscr_cancel, update_subscr_cancel_cmd, - UPDATE_SUBSCR_STR "cancel (update-procedure|subscription-withdraw)", - UPDATE_SUBSCR_HELP - "Cancel (remove) subscriber record\n" - "The MS moved to another SGSN\n" - "The subscription is no longer valid\n") -{ - const char *imsi = argv[0]; - const char *cancel_type = argv[1]; - - struct gprs_subscr *subscr; - - subscr = gprs_subscr_get_by_imsi(imsi); - if (!subscr) { - vty_out(vty, "%% no subscriber record for %s%s", - imsi, VTY_NEWLINE); - return CMD_WARNING; - } - - if (strcmp(cancel_type, "update-procedure") == 0) - subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE; - else - subscr->sgsn_data->error_cause = GMM_CAUSE_IMPL_DETACHED; - - gprs_subscr_cancel(subscr); - gprs_subscr_put(subscr); - - return CMD_SUCCESS; -} - -DEFUN(update_subscr_create, update_subscr_create_cmd, - UPDATE_SUBSCR_STR "create", - UPDATE_SUBSCR_HELP - "Create a subscriber entry\n") -{ - const char *imsi = argv[0]; - - struct gprs_subscr *subscr; - - subscr = gprs_subscr_get_by_imsi(imsi); - if (subscr) { - vty_out(vty, "%% subscriber record already exists for %s%s", - imsi, VTY_NEWLINE); - return CMD_WARNING; - } - - subscr = gprs_subscr_get_or_create(imsi); - subscr->keep_in_ram = 1; - gprs_subscr_put(subscr); - - return CMD_SUCCESS; -} - -DEFUN(update_subscr_destroy, update_subscr_destroy_cmd, - UPDATE_SUBSCR_STR "destroy", - UPDATE_SUBSCR_HELP - "Destroy a subscriber entry\n") -{ - const char *imsi = argv[0]; - - struct gprs_subscr *subscr; - - subscr = gprs_subscr_get_by_imsi(imsi); - if (!subscr) { - vty_out(vty, "%% subscriber record does not exist for %s%s", - imsi, VTY_NEWLINE); - return CMD_WARNING; - } - - subscr->keep_in_ram = 0; - subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE; - gprs_subscr_cancel(subscr); - if (subscr->use_count > 1) - vty_out(vty, "%% subscriber is still in use%s", - VTY_NEWLINE); - gprs_subscr_put(subscr); - - return CMD_SUCCESS; -} - -#define UL_ERR_STR "system-failure|data-missing|unexpected-data-value|" \ - "unknown-subscriber|roaming-not-allowed" - -#define UL_ERR_HELP \ - "Force error code SystemFailure\n" \ - "Force error code DataMissing\n" \ - "Force error code UnexpectedDataValue\n" \ - "Force error code UnknownSubscriber\n" \ - "Force error code RoamingNotAllowed\n" - -DEFUN(update_subscr_update_location_result, update_subscr_update_location_result_cmd, - UPDATE_SUBSCR_STR "update-location-result (ok|" UL_ERR_STR ")", - UPDATE_SUBSCR_HELP - "Complete the update location procedure\n" - "The update location request succeeded\n" - UL_ERR_HELP) -{ - const char *imsi = argv[0]; - const char *ret_code_str = argv[1]; - - struct gprs_subscr *subscr; - - const struct value_string cause_mapping[] = { - { GMM_CAUSE_NET_FAIL, "system-failure" }, - { GMM_CAUSE_INV_MAND_INFO, "data-missing" }, - { GMM_CAUSE_PROTO_ERR_UNSPEC, "unexpected-data-value" }, - { GMM_CAUSE_IMSI_UNKNOWN, "unknown-subscriber" }, - { GMM_CAUSE_GPRS_NOTALLOWED, "roaming-not-allowed" }, - { 0, NULL } - }; - - subscr = gprs_subscr_get_by_imsi(imsi); - if (!subscr) { - vty_out(vty, "%% unable to get subscriber record for %s%s", - imsi, VTY_NEWLINE); - return CMD_WARNING; - } - - if (strcmp(ret_code_str, "ok") == 0) { - subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE; - subscr->authorized = 1; - } else { - subscr->sgsn_data->error_cause = - get_string_value(cause_mapping, ret_code_str); - subscr->authorized = 0; - } - - gprs_subscr_update(subscr); - - gprs_subscr_put(subscr); - - return CMD_SUCCESS; -} - -DEFUN(update_subscr_update_auth_info, update_subscr_update_auth_info_cmd, - UPDATE_SUBSCR_STR "update-auth-info", - UPDATE_SUBSCR_HELP - "Complete the send authentication info procedure\n") -{ - const char *imsi = argv[0]; - - struct gprs_subscr *subscr; - - subscr = gprs_subscr_get_by_imsi(imsi); - if (!subscr) { - vty_out(vty, "%% unable to get subscriber record for %s%s", - imsi, VTY_NEWLINE); - return CMD_WARNING; - } - - gprs_subscr_update_auth_info(subscr); - - gprs_subscr_put(subscr); - - return CMD_SUCCESS; -} - -DEFUN(cfg_gsup_ipa_name, - cfg_gsup_ipa_name_cmd, - "gsup ipa-name NAME", - "GSUP Parameters\n" - "Set the IPA name of this SGSN\n" - "A unique name for this SGSN. For example: PLMN + redundancy server number: SGSN-901-70-0. " - "This name is used for GSUP routing and must be set if more than one SGSN is connected to the network. " - "The default is 'SGSN-00-00-00-00-00-00'.\n") -{ - if (vty->type != VTY_FILE) { - vty_out(vty, "The IPA name cannot be changed at run-time; " - "It can only be set in the configuraton file.%s", VTY_NEWLINE); - return CMD_WARNING; - } - - g_cfg->sgsn_ipa_name = talloc_strdup(tall_vty_ctx, argv[0]); - return CMD_SUCCESS; -} - -DEFUN(cfg_gsup_remote_ip, cfg_gsup_remote_ip_cmd, - "gsup remote-ip A.B.C.D", - "GSUP Parameters\n" - "Set the IP address of the remote GSUP server (e.g. OsmoHLR)." - " This setting only applies if 'auth-policy remote' is used.\n" - "IPv4 Address\n") -{ - inet_aton(argv[0], &g_cfg->gsup_server_addr.sin_addr); - - return CMD_SUCCESS; -} - -DEFUN(cfg_gsup_remote_port, cfg_gsup_remote_port_cmd, - "gsup remote-port <0-65535>", - "GSUP Parameters\n" - "Set the TCP port of the remote GSUP server, see also 'gsup remote-ip'\n" - "Remote TCP port\n") -{ - g_cfg->gsup_server_port = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN(cfg_gsup_oap_id, cfg_gsup_oap_id_cmd, - "gsup oap-id <0-65535>", - "GSUP Parameters\n" - "Set the OAP client ID for authentication on the GSUP protocol." - " This setting only applies if 'auth-policy remote' is used.\n" - "OAP client ID (0 == disabled)\n") -{ - /* VTY ensures range */ - g_cfg->oap.client_id = (uint16_t)atoi(argv[0]); - return CMD_SUCCESS; -} - -DEFUN(cfg_gsup_oap_k, cfg_gsup_oap_k_cmd, - "gsup oap-k K", - "GSUP Parameters\n" - "Set the OAP shared secret key K for authentication on the GSUP protocol." - " This setting only applies if auth-policy remote is used.\n" - "K value (16 byte) hex\n") -{ - const char *k = argv[0]; - - g_cfg->oap.secret_k_present = 0; - - if ((!k) || (strlen(k) == 0)) - goto disable; - - int k_len = osmo_hexparse(k, - g_cfg->oap.secret_k, - sizeof(g_cfg->oap.secret_k)); - if (k_len != 16) { - vty_out(vty, "%% need exactly 16 octets for oap-k, got %d.%s", - k_len, VTY_NEWLINE); - goto disable; - } - - g_cfg->oap.secret_k_present = 1; - return CMD_SUCCESS; - -disable: - if (g_cfg->oap.client_id > 0) { - vty_out(vty, "%% OAP client ID set, but invalid oap-k value disables OAP.%s", - VTY_NEWLINE); - return CMD_WARNING; - } - return CMD_SUCCESS; -} - -DEFUN(cfg_gsup_oap_opc, cfg_gsup_oap_opc_cmd, - "gsup oap-opc OPC", - "GSUP Parameters\n" - "Set the OAP shared secret OPC for authentication on the GSUP protocol." - " This setting only applies if auth-policy remote is used.\n" - "OPC value (16 byte) hex\n") -{ - const char *opc = argv[0]; - - g_cfg->oap.secret_opc_present = 0; - - if ((!opc) || (strlen(opc) == 0)) - goto disable; - - int opc_len = osmo_hexparse(opc, - g_cfg->oap.secret_opc, - sizeof(g_cfg->oap.secret_opc)); - if (opc_len != 16) { - vty_out(vty, "%% need exactly 16 octets for oap-opc, got %d.%s", - opc_len, VTY_NEWLINE); - goto disable; - } - - g_cfg->oap.secret_opc_present = 1; - return CMD_SUCCESS; - -disable: - if (g_cfg->oap.client_id > 0) { - vty_out(vty, "%% OAP client ID set, but invalid oap-opc value disables OAP.%s", - VTY_NEWLINE); - return CMD_WARNING; - } - return CMD_SUCCESS; -} - -DEFUN(cfg_apn_name, cfg_apn_name_cmd, - "access-point-name NAME", - "Globally allow the given APN name for all subscribers.\n" - "Add this NAME to the list\n") -{ - return add_apn_ggsn_mapping(vty, argv[0], "", 0); -} - -DEFUN(cfg_no_apn_name, cfg_no_apn_name_cmd, - "no access-point-name NAME", - NO_STR "Configure a global list of allowed APNs\n" - "Remove entry with NAME\n") -{ - struct apn_ctx *apn_ctx = sgsn_apn_ctx_by_name(argv[0], ""); - if (!apn_ctx) - return CMD_SUCCESS; - - sgsn_apn_ctx_free(apn_ctx); - return CMD_SUCCESS; -} - -DEFUN(cfg_cdr_filename, cfg_cdr_filename_cmd, - "cdr filename NAME", - "CDR\n" - "Set the file name for the call-data-record file, logging the data usage of each subscriber.\n" - "filename\n") -{ - talloc_free(g_cfg->cdr.filename); - g_cfg->cdr.filename = talloc_strdup(tall_vty_ctx, argv[0]); - return CMD_SUCCESS; -} - -DEFUN(cfg_no_cdr_filename, cfg_no_cdr_filename_cmd, - "no cdr filename", - NO_STR "CDR\nDisable saving CDR to file\n") -{ - talloc_free(g_cfg->cdr.filename); - g_cfg->cdr.filename = NULL; - return CMD_SUCCESS; -} - -DEFUN(cfg_cdr_trap, cfg_cdr_trap_cmd, - "cdr trap", - "CDR\nEnable sending CDR via TRAP CTRL messages\n") -{ - g_cfg->cdr.trap = true; - return CMD_SUCCESS; -} - -DEFUN(cfg_no_cdr_trap, cfg_no_cdr_trap_cmd, - "no cdr trap", - NO_STR "CDR\nDisable sending CDR via TRAP CTRL messages\n") -{ - g_cfg->cdr.trap = false; - return CMD_SUCCESS; -} - -DEFUN(cfg_cdr_interval, cfg_cdr_interval_cmd, - "cdr interval <1-2147483647>", - "CDR\n" - "Set the interval for the call-data-record file\n" - "interval in seconds\n") -{ - g_cfg->cdr.interval = atoi(argv[0]); - return CMD_SUCCESS; -} - -#define COMPRESSION_STR "Configure compression\n" -DEFUN(cfg_no_comp_rfc1144, cfg_no_comp_rfc1144_cmd, - "no compression rfc1144", - NO_STR COMPRESSION_STR "disable rfc1144 TCP/IP header compression\n") -{ - g_cfg->pcomp_rfc1144.active = 0; - g_cfg->pcomp_rfc1144.passive = 0; - return CMD_SUCCESS; -} - -DEFUN(cfg_comp_rfc1144, cfg_comp_rfc1144_cmd, - "compression rfc1144 active slots <1-256>", - COMPRESSION_STR - "RFC1144 Header compression scheme\n" - "Compression is actively proposed\n" - "Number of compression state slots\n" - "Number of compression state slots\n") -{ - g_cfg->pcomp_rfc1144.active = 1; - g_cfg->pcomp_rfc1144.passive = 1; - g_cfg->pcomp_rfc1144.s01 = atoi(argv[0]) - 1; - return CMD_SUCCESS; -} - -DEFUN(cfg_comp_rfc1144p, cfg_comp_rfc1144p_cmd, - "compression rfc1144 passive", - COMPRESSION_STR - "RFC1144 Header compression scheme\n" - "Compression is available on request\n") -{ - g_cfg->pcomp_rfc1144.active = 0; - g_cfg->pcomp_rfc1144.passive = 1; - return CMD_SUCCESS; -} - -DEFUN(cfg_no_comp_v42bis, cfg_no_comp_v42bis_cmd, - "no compression v42bis", - NO_STR COMPRESSION_STR "disable V.42bis data compression\n") -{ - g_cfg->dcomp_v42bis.active = 0; - g_cfg->dcomp_v42bis.passive = 0; - return CMD_SUCCESS; -} - -DEFUN(cfg_comp_v42bis, cfg_comp_v42bis_cmd, - "compression v42bis active direction (ms|sgsn|both) codewords <512-65535> strlen <6-250>", - COMPRESSION_STR - "V.42bis data compression scheme\n" - "Compression is actively proposed\n" - "Direction in which the compression shall be active (p0)\n" - "Compress ms->sgsn direction only\n" - "Compress sgsn->ms direction only\n" - "Both directions\n" - "Number of codewords (p1)\n" - "Number of codewords\n" - "Maximum string length (p2)\n" "Maximum string length\n") -{ - g_cfg->dcomp_v42bis.active = 1; - g_cfg->dcomp_v42bis.passive = 1; - - switch (argv[0][0]) { - case 'm': - g_cfg->dcomp_v42bis.p0 = 1; - break; - case 's': - g_cfg->dcomp_v42bis.p0 = 2; - break; - case 'b': - g_cfg->dcomp_v42bis.p0 = 3; - break; - } - - g_cfg->dcomp_v42bis.p1 = atoi(argv[1]); - g_cfg->dcomp_v42bis.p2 = atoi(argv[2]); - return CMD_SUCCESS; -} - -DEFUN(cfg_comp_v42bisp, cfg_comp_v42bisp_cmd, - "compression v42bis passive", - COMPRESSION_STR - "V.42bis data compression scheme\n" - "Compression is available on request\n") -{ - g_cfg->dcomp_v42bis.active = 0; - g_cfg->dcomp_v42bis.passive = 1; - return CMD_SUCCESS; -} - -#if BUILD_IU -DEFUN(cfg_sgsn_cs7_instance_iu, - cfg_sgsn_cs7_instance_iu_cmd, - "cs7-instance-iu <0-15>", - "Set SS7 to be used by the Iu-Interface.\n" "SS7 instance reference number (default: 0)\n") -{ - g_cfg->iu.cs7_instance = atoi(argv[0]); - return CMD_SUCCESS; -} -#endif - -int sgsn_vty_init(struct sgsn_config *cfg) -{ - g_cfg = cfg; - - g_cfg->T_defs = sgsn_T_defs; - osmo_tdefs_reset(g_cfg->T_defs); - - install_element_ve(&show_sgsn_cmd); - //install_element_ve(&show_mmctx_tlli_cmd); - install_element_ve(&show_mmctx_imsi_cmd); - install_element_ve(&show_mmctx_all_cmd); - install_element_ve(&show_pdpctx_all_cmd); - install_element_ve(&show_subscr_cache_cmd); - install_element_ve(&show_timer_cmd); - - install_element(ENABLE_NODE, &update_subscr_insert_auth_triplet_cmd); - install_element(ENABLE_NODE, &update_subscr_create_cmd); - install_element(ENABLE_NODE, &update_subscr_destroy_cmd); - install_element(ENABLE_NODE, &update_subscr_cancel_cmd); - install_element(ENABLE_NODE, &update_subscr_update_location_result_cmd); - install_element(ENABLE_NODE, &update_subscr_update_auth_info_cmd); - install_element(ENABLE_NODE, &reset_sgsn_state_cmd); - - install_element(CONFIG_NODE, &cfg_sgsn_cmd); - install_node(&sgsn_node, config_write_sgsn); - install_element(SGSN_NODE, &cfg_sgsn_bind_addr_cmd); - install_element(SGSN_NODE, &cfg_ggsn_remote_ip_cmd); - //install_element(SGSN_NODE, &cfg_ggsn_remote_port_cmd); - install_element(SGSN_NODE, &cfg_ggsn_gtp_version_cmd); - install_element(SGSN_NODE, &cfg_ggsn_echo_interval_cmd); - install_element(SGSN_NODE, &cfg_ggsn_no_echo_interval_cmd); - install_element(SGSN_NODE, &cfg_imsi_acl_cmd); - install_element(SGSN_NODE, &cfg_auth_policy_cmd); - install_element(SGSN_NODE, &cfg_authentication_cmd); - install_element(SGSN_NODE, &cfg_encrypt_cmd); - install_element(SGSN_NODE, &cfg_gsup_ipa_name_cmd); - install_element(SGSN_NODE, &cfg_gsup_remote_ip_cmd); - install_element(SGSN_NODE, &cfg_gsup_remote_port_cmd); - install_element(SGSN_NODE, &cfg_gsup_oap_id_cmd); - install_element(SGSN_NODE, &cfg_gsup_oap_k_cmd); - install_element(SGSN_NODE, &cfg_gsup_oap_opc_cmd); - install_element(SGSN_NODE, &cfg_apn_ggsn_cmd); - install_element(SGSN_NODE, &cfg_apn_imsi_ggsn_cmd); - install_element(SGSN_NODE, &cfg_apn_name_cmd); - install_element(SGSN_NODE, &cfg_no_apn_name_cmd); - install_element(SGSN_NODE, &cfg_cdr_filename_cmd); - install_element(SGSN_NODE, &cfg_no_cdr_filename_cmd); - install_element(SGSN_NODE, &cfg_cdr_trap_cmd); - install_element(SGSN_NODE, &cfg_no_cdr_trap_cmd); - install_element(SGSN_NODE, &cfg_cdr_interval_cmd); - install_element(SGSN_NODE, &cfg_ggsn_dynamic_lookup_cmd); - install_element(SGSN_NODE, &cfg_grx_ggsn_cmd); - - install_element(SGSN_NODE, &cfg_sgsn_timer_cmd); - - install_element(SGSN_NODE, &cfg_no_comp_rfc1144_cmd); - install_element(SGSN_NODE, &cfg_comp_rfc1144_cmd); - install_element(SGSN_NODE, &cfg_comp_rfc1144p_cmd); - install_element(SGSN_NODE, &cfg_no_comp_v42bis_cmd); - install_element(SGSN_NODE, &cfg_comp_v42bis_cmd); - install_element(SGSN_NODE, &cfg_comp_v42bisp_cmd); - -#ifdef BUILD_IU - install_element(SGSN_NODE, &cfg_sgsn_cs7_instance_iu_cmd); - ranap_iu_vty_init(SGSN_NODE, &g_cfg->iu.rab_assign_addr_enc); -#endif - return 0; -} - -int sgsn_parse_config(const char *config_file) -{ - int rc; - - /* make sure sgsn_vty_init() was called before this */ - OSMO_ASSERT(g_cfg); - - rc = vty_read_config_file(config_file, NULL); - if (rc < 0) { - fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file); - return rc; - } - - if (g_cfg->auth_policy == SGSN_AUTH_POLICY_REMOTE - && !(g_cfg->gsup_server_addr.sin_addr.s_addr - && g_cfg->gsup_server_port)) { - fprintf(stderr, "Configuration error:" - " 'auth-policy remote' requires both" - " 'gsup remote-ip' and 'gsup remote-port'\n"); - return -EINVAL; - } - - return 0; -} diff --git a/src/gprs/slhc.c b/src/gprs/slhc.c deleted file mode 100644 index 20e571d48..000000000 --- a/src/gprs/slhc.c +++ /dev/null @@ -1,813 +0,0 @@ -/* - * Routines to compress and uncompress tcp packets (for transmission - * over low speed serial lines). - * - * Copyright (c) 1989 Regents of the University of California. - * All rights reserved. - * - * Redistribution and use in source and binary forms are permitted - * provided that the above copyright notice and this paragraph are - * duplicated in all such forms and that any documentation, - * advertising materials, and other materials related to such - * distribution and use acknowledge that the software was developed - * by the University of California, Berkeley. The name of the - * University may not be used to endorse or promote products derived - * from this software without specific prior written permission. - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - * - * Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989: - * - Initial distribution. - * - * - * modified for KA9Q Internet Software Package by - * Katie Stevens (dkstevens@ucdavis.edu) - * University of California, Davis - * Computing Services - * - 01-31-90 initial adaptation (from 1.19) - * PPP.05 02-15-90 [ks] - * PPP.08 05-02-90 [ks] use PPP protocol field to signal compression - * PPP.15 09-90 [ks] improve mbuf handling - * PPP.16 11-02 [karn] substantially rewritten to use NOS facilities - * - * - Feb 1991 Bill_Simpson@um.cc.umich.edu - * variable number of conversation slots - * allow zero or one slots - * separate routines - * status display - * - Jul 1994 Dmitry Gorodchanin - * Fixes for memory leaks. - * - Oct 1994 Dmitry Gorodchanin - * Modularization. - * - Jan 1995 Bjorn Ekwall - * Use ip_fast_csum from ip.h - * - July 1995 Christos A. Polyzols - * Spotted bug in tcp option checking - * - * - * This module is a difficult issue. It's clearly inet code but it's also clearly - * driver code belonging close to PPP and SLIP - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define ERR_PTR(x) x - - -static unsigned char *encode(unsigned char *cp, unsigned short n); -static long decode(unsigned char **cpp); -static unsigned char * put16(unsigned char *cp, unsigned short x); -static unsigned short pull16(unsigned char **cpp); - -/* Replacement for kernel space function ip_fast_csum() */ -static uint16_t ip_fast_csum(uint8_t *iph, int ihl) -{ - int i; - uint16_t temp; - uint32_t accumulator = 0xFFFF; - - for(i=0;i0xFFFF) - { - accumulator++; - accumulator&=0xFFFF; - } - } - - return (uint16_t)(htons(~accumulator)&0xFFFF); -} - -/* Replacement for kernel space function put_unaligned() */ -static void put_unaligned(uint16_t val, void *ptr) -{ - memcpy(ptr,&val,sizeof(val)); -} - - -/* Allocate compression data structure - * slots must be in range 0 to 255 (zero meaning no compression) - * Returns pointer to structure or ERR_PTR() on error. - */ -struct slcompress * -slhc_init(const void *ctx, int rslots, int tslots) -{ - register short i; - register struct cstate *ts; - struct slcompress *comp; - - if (rslots < 0 || rslots > 255 || tslots < 0 || tslots > 255) - return NULL; - - comp = (struct slcompress *)talloc_zero_size(ctx,sizeof(struct slcompress)); - if (! comp) - goto out_fail; - - if (rslots > 0) { - size_t rsize = rslots * sizeof(struct cstate); - comp->rstate = (struct cstate *) talloc_zero_size(ctx, rsize); - if (! comp->rstate) - goto out_free; - comp->rslot_limit = rslots - 1; - } - - if (tslots > 0) { - size_t tsize = tslots * sizeof(struct cstate); - comp->tstate = (struct cstate *) talloc_zero_size(ctx, tsize); - if (! comp->tstate) - goto out_free2; - comp->tslot_limit = tslots - 1; - } - - comp->xmit_oldest = 0; - comp->xmit_current = 255; - comp->recv_current = 255; - /* - * don't accept any packets with implicit index until we get - * one with an explicit index. Otherwise the uncompress code - * will try to use connection 255, which is almost certainly - * out of range - */ - comp->flags |= SLF_TOSS; - - if ( tslots > 0 ) { - ts = comp->tstate; - for(i = comp->tslot_limit; i > 0; --i){ - ts[i].cs_this = i; - ts[i].next = &(ts[i - 1]); - } - ts[0].next = &(ts[comp->tslot_limit]); - ts[0].cs_this = 0; - } - return comp; - -out_free2: - talloc_free(comp->rstate); -out_free: - talloc_free(comp); -out_fail: - return NULL; -} - - -/* Free a compression data structure */ -void -slhc_free(struct slcompress *comp) -{ - DEBUGP(DSLHC, "slhc_free(): Freeing compression states...\n"); - - if ( comp == NULLSLCOMPR ) - return; - - if ( comp->tstate != NULLSLSTATE ) - talloc_free(comp->tstate ); - - if ( comp->rstate != NULLSLSTATE ) - talloc_free( comp->rstate ); - - talloc_free( comp ); -} - - -/* Put a short in host order into a char array in network order */ -static inline unsigned char * -put16(unsigned char *cp, unsigned short x) -{ - *cp++ = x >> 8; - *cp++ = x; - - return cp; -} - - -/* Encode a number */ -static unsigned char * -encode(unsigned char *cp, unsigned short n) -{ - if(n >= 256 || n == 0){ - *cp++ = 0; - cp = put16(cp,n); - } else { - *cp++ = n; - } - - DEBUGP(DSLHC, "encode(): n=%04x\n",n); - return cp; -} - -/* Pull a 16-bit integer in host order from buffer in network byte order */ -static unsigned short -pull16(unsigned char **cpp) -{ - short rval; - - rval = *(*cpp)++; - rval <<= 8; - rval |= *(*cpp)++; - return rval; -} - -/* Decode a number */ -static long -decode(unsigned char **cpp) -{ - register int x; - - x = *(*cpp)++; - if(x == 0){ - return pull16(cpp) & 0xffff; /* pull16 returns -1 on error */ - } else { - return x & 0xff; /* -1 if PULLCHAR returned error */ - } -} - -/* - * icp and isize are the original packet. - * ocp is a place to put a copy if necessary. - * cpp is initially a pointer to icp. If the copy is used, - * change it to ocp. - */ - -int -slhc_compress(struct slcompress *comp, unsigned char *icp, int isize, - unsigned char *ocp, unsigned char **cpp, int compress_cid) -{ - register struct cstate *ocs = &(comp->tstate[comp->xmit_oldest]); - register struct cstate *lcs = ocs; - register struct cstate *cs = lcs->next; - register unsigned long deltaS, deltaA; - register short changes = 0; - int hlen; - unsigned char new_seq[16]; - register unsigned char *cp = new_seq; - struct iphdr *ip; - struct tcphdr *th, *oth; - __sum16 csum; - - - /* - * Don't play with runt packets. - */ - - if(isizeprotocol != IPPROTO_TCP || (ntohs(ip->frag_off) & 0x3fff)) { - /* Send as regular IP */ - if(ip->protocol != IPPROTO_TCP) - comp->sls_o_nontcp++; - else - comp->sls_o_tcp++; - DEBUGP(DSLHC, "slhc_compress(): Not a TCP packat, will not touch...\n"); - return isize; - } - /* Extract TCP header */ - - th = (struct tcphdr *)(((unsigned char *)ip) + ip->ihl*4); - hlen = ip->ihl*4 + th->doff*4; - - /* Bail if the TCP packet isn't `compressible' (i.e., ACK isn't set or - * some other control bit is set). Also uncompressible if - * it's a runt. - */ - if(hlen > isize || th->syn || th->fin || th->rst || - ! (th->ack)){ - /* TCP connection stuff; send as regular IP */ - comp->sls_o_tcp++; - DEBUGP(DSLHC, "slhc_compress(): Packet is part of a TCP connection, will not touch...\n"); - return isize; - } - /* - * Packet is compressible -- we're going to send either a - * COMPRESSED_TCP or UNCOMPRESSED_TCP packet. Either way, - * we need to locate (or create) the connection state. - * - * States are kept in a circularly linked list with - * xmit_oldest pointing to the end of the list. The - * list is kept in lru order by moving a state to the - * head of the list whenever it is referenced. Since - * the list is short and, empirically, the connection - * we want is almost always near the front, we locate - * states via linear search. If we don't find a state - * for the datagram, the oldest state is (re-)used. - */ - - DEBUGP(DSLHC, "slhc_compress(): Compressible packet detected!\n"); - - for ( ; ; ) { - if( ip->saddr == cs->cs_ip.saddr - && ip->daddr == cs->cs_ip.daddr - && th->source == cs->cs_tcp.source - && th->dest == cs->cs_tcp.dest) - goto found; - - /* if current equal oldest, at end of list */ - if ( cs == ocs ) - break; - lcs = cs; - cs = cs->next; - comp->sls_o_searches++; - } - /* - * Didn't find it -- re-use oldest cstate. Send an - * uncompressed packet that tells the other side what - * connection number we're using for this conversation. - * - * Note that since the state list is circular, the oldest - * state points to the newest and we only need to set - * xmit_oldest to update the lru linkage. - */ - - DEBUGP(DSLHC, "slhc_compress(): Header not yet seen, will memorize header for the next turn...\n"); - comp->sls_o_misses++; - comp->xmit_oldest = lcs->cs_this; - goto uncompressed; - -found: - DEBUGP(DSLHC, "slhc_compress(): Header already seen, trying to compress...\n"); - /* - * Found it -- move to the front on the connection list. - */ - if(lcs == ocs) { - /* found at most recently used */ - } else if (cs == ocs) { - /* found at least recently used */ - comp->xmit_oldest = lcs->cs_this; - } else { - /* more than 2 elements */ - lcs->next = cs->next; - cs->next = ocs->next; - ocs->next = cs; - } - - /* - * Make sure that only what we expect to change changed. - * Check the following: - * IP protocol version, header length & type of service. - * The "Don't fragment" bit. - * The time-to-live field. - * The TCP header length. - * IP options, if any. - * TCP options, if any. - * If any of these things are different between the previous & - * current datagram, we send the current datagram `uncompressed'. - */ - oth = &cs->cs_tcp; - - /* Display a little more debug information about which of the - * header fields changed unexpectedly */ - if(ip->version != cs->cs_ip.version) - DEBUGP(DSLHC, "slhc_compress(): Unexpected change: ip->version != cs->cs_ip.version\n"); - if(ip->ihl != cs->cs_ip.ihl) - DEBUGP(DSLHC, "slhc_compress(): Unexpected change: ip->ihl != cs->cs_ip.ihl\n"); - if(ip->tos != cs->cs_ip.tos) - DEBUGP(DSLHC, "slhc_compress(): Unexpected change: ip->tos != cs->cs_ip.tos\n"); - if((ip->frag_off & htons(0x4000)) != (cs->cs_ip.frag_off & htons(0x4000))) - DEBUGP(DSLHC, "slhc_compress(): Unexpected change: (ip->frag_off & htons(0x4000)) != (cs->cs_ip.frag_off & htons(0x4000))\n"); - if(ip->ttl != cs->cs_ip.ttl) - DEBUGP(DSLHC, "slhc_compress(): Unexpected change: ip->ttl != cs->cs_ip.ttl\n"); - if(th->doff != cs->cs_tcp.doff) - DEBUGP(DSLHC, "slhc_compress(): Unexpected change: th->doff != cs->cs_tcp.doff\n"); - if(ip->ihl > 5 && memcmp(ip+1,cs->cs_ipopt,((ip->ihl)-5)*4) != 0) { - DEBUGP(DSLHC, "slhc_compress(): Unexpected change: (ip->ihl > 5 && memcmp(ip+1,cs->cs_ipopt,((ip->ihl)-5)*4) != 0)\n"); - DEBUGP(DSLHC, "slhc_compress(): ip->ihl = %i\n", ip->ihl); - DEBUGP(DSLHC, "slhc_compress(): ip+1 = %s\n", - osmo_hexdump_nospc((uint8_t*)(ip+1),((ip->ihl)-5)*4)); - DEBUGP(DSLHC, "slhc_compress(): Unexpected change: cs->cs_ipopt = %s\n", - osmo_hexdump_nospc((uint8_t*)(cs->cs_ipopt),((ip->ihl)-5)*4)); - } - if(th->doff > 5 && memcmp(th+1,cs->cs_tcpopt,((th->doff)-5)*4) != 0) { - DEBUGP(DSLHC, "slhc_compress(): Unexpected change: (th->doff > 5 && memcmp(th+1,cs->cs_tcpopt,((th->doff)-5)*4) != 0)\n"); - DEBUGP(DSLHC, "slhc_compress(): th->doff = %i\n", th->doff); - DEBUGP(DSLHC, "slhc_compress(): th+1 = %s\n", - osmo_hexdump_nospc((uint8_t*)(th+1),((th->doff)-5)*4)); - DEBUGP(DSLHC, "slhc_compress(): cs->cs_tcpopt = %s\n", - osmo_hexdump_nospc((uint8_t*)cs->cs_tcpopt, - ((th->doff)-5)*4)); - } - - - if(ip->version != cs->cs_ip.version || ip->ihl != cs->cs_ip.ihl - || ip->tos != cs->cs_ip.tos - || (ip->frag_off & htons(0x4000)) != (cs->cs_ip.frag_off & htons(0x4000)) - || ip->ttl != cs->cs_ip.ttl - || th->doff != cs->cs_tcp.doff - || (ip->ihl > 5 && memcmp(ip+1,cs->cs_ipopt,((ip->ihl)-5)*4) != 0) - || (th->doff > 5 && memcmp(th+1,cs->cs_tcpopt,((th->doff)-5)*4) != 0)){ - DEBUGP(DSLHC, "slhc_compress(): The header contains unexpected changes, can't compress...\n"); - goto uncompressed; - } - - /* - * Figure out which of the changing fields changed. The - * receiver expects changes in the order: urgent, window, - * ack, seq (the order minimizes the number of temporaries - * needed in this section of code). - */ - if(th->urg){ - deltaS = ntohs(th->urg_ptr); - DEBUGP(DSLHC, "slhc_compress(): flag: Urgent Pointer (U) = 1\n"); - cp = encode(cp,deltaS); - changes |= NEW_U; - } else if(th->urg_ptr != oth->urg_ptr){ - /* argh! URG not set but urp changed -- a sensible - * implementation should never do this but RFC793 - * doesn't prohibit the change so we have to deal - * with it. */ - DEBUGP(DSLHC, "slhc_compress(): URG not set but urp changed, can't compress...\n"); - goto uncompressed; - } - if((deltaS = ntohs(th->window) - ntohs(oth->window)) != 0){ - DEBUGP(DSLHC, "slhc_compress(): flag: Delta Window (W) = 1\n"); - cp = encode(cp,deltaS); - changes |= NEW_W; - } - if((deltaA = ntohl(th->ack_seq) - ntohl(oth->ack_seq)) != 0L){ - if(deltaA > 0x0000ffff) { - DEBUGP(DSLHC, "slhc_compress(): (deltaA = ntohl(th->ack_seq) - ntohl(oth->ack_seq)) != 0L, can't compress...\n"); - goto uncompressed; - } - DEBUGP(DSLHC, "slhc_compress(): flag: Delta Ack (A) = 1\n"); - cp = encode(cp,deltaA); - changes |= NEW_A; - } - if((deltaS = ntohl(th->seq) - ntohl(oth->seq)) != 0L){ - if(deltaS > 0x0000ffff) { - DEBUGP(DSLHC, "slhc_compress(): (deltaS = ntohl(th->seq) - ntohl(oth->seq)) != 0L, can't compress...\n"); - goto uncompressed; - } - DEBUGP(DSLHC, "slhc_compress(): flag: Delta Sequence (S) = 1\n"); - cp = encode(cp,deltaS); - changes |= NEW_S; - } - - switch(changes){ - case 0: /* Nothing changed. If this packet contains data and the - * last one didn't, this is probably a data packet following - * an ack (normal on an interactive connection) and we send - * it compressed. Otherwise it's probably a retransmit, - * retransmitted ack or window probe. Send it uncompressed - * in case the other side missed the compressed version. - */ - if(ip->tot_len != cs->cs_ip.tot_len && - ntohs(cs->cs_ip.tot_len) == hlen) - break; - DEBUGP(DSLHC, "slhc_compress(): Retransmitted packet detected, can't compress...\n"); - goto uncompressed; - case SPECIAL_I: - case SPECIAL_D: - /* actual changes match one of our special case encodings -- - * send packet uncompressed. - */ - DEBUGP(DSLHC, "slhc_compress(): Special case detected, can't compress...\n"); - goto uncompressed; - case NEW_S|NEW_A: - if(deltaS == deltaA && - deltaS == ntohs(cs->cs_ip.tot_len) - hlen){ - /* special case for echoed terminal traffic */ - DEBUGP(DSLHC, "slhc_compress(): Special case for echoed terminal traffic detected...\n"); - DEBUGP(DSLHC, "slhc_compress(): flag: Delta Sequence (S) = 1, Delta Window (W) = 1, Urgent Pointer (U) = 1\n"); - changes = SPECIAL_I; - cp = new_seq; - } - break; - case NEW_S: - if(deltaS == ntohs(cs->cs_ip.tot_len) - hlen){ - /* special case for data xfer */ - DEBUGP(DSLHC, "slhc_compress(): Special case for data xfer detected...\n"); - DEBUGP(DSLHC, "slhc_compress(): flag: Delta Sequence (S) = 1, Delta Ack (A) = 1, Delta Window (W) = 1, Urgent Pointer (U) = 1\n"); - changes = SPECIAL_D; - cp = new_seq; - } - break; - } - deltaS = ntohs(ip->id) - ntohs(cs->cs_ip.id); - if(deltaS != 1){ - DEBUGP(DSLHC, "slhc_compress(): flag: Delta IP ID (I) = 1\n"); - cp = encode(cp,deltaS); - changes |= NEW_I; - } - if(th->psh) { - DEBUGP(DSLHC, "slhc_compress(): flag: Push (P) = 1\n"); - changes |= TCP_PUSH_BIT; - } - /* Grab the cksum before we overwrite it below. Then update our - * state with this packet's header. - */ - csum = th->check; - memcpy(&cs->cs_ip,ip,20); - memcpy(&cs->cs_tcp,th,20); - /* We want to use the original packet as our compressed packet. - * (cp - new_seq) is the number of bytes we need for compressed - * sequence numbers. In addition we need one byte for the change - * mask, one for the connection id and two for the tcp checksum. - * So, (cp - new_seq) + 4 bytes of header are needed. - */ - deltaS = cp - new_seq; - if(compress_cid == 0 || comp->xmit_current != cs->cs_this){ - cp = ocp; - *cpp = ocp; - DEBUGP(DSLHC, "slhc_compress(): flag: Connection number (C) = 1\n"); - *cp++ = changes | NEW_C; - *cp++ = cs->cs_this; - comp->xmit_current = cs->cs_this; - } else { - cp = ocp; - *cpp = ocp; - *cp++ = changes; - } - *(__sum16 *)cp = csum; - cp += 2; -/* deltaS is now the size of the change section of the compressed header */ - - DEBUGP(DSLHC, "slhc_compress(): Delta-list length (deltaS) = %li\n",deltaS); - DEBUGP(DSLHC, "slhc_compress(): Original header len (hlen) = %i\n",hlen); - - memcpy(cp,new_seq,deltaS); /* Write list of deltas */ - memcpy(cp+deltaS,icp+hlen,isize-hlen); - comp->sls_o_compressed++; - ocp[0] |= SL_TYPE_COMPRESSED_TCP; - return isize - hlen + deltaS + (cp - ocp); - - /* Update connection state cs & send uncompressed packet (i.e., - * a regular ip/tcp packet but with the 'conversation id' we hope - * to use on future compressed packets in the protocol field). - */ -uncompressed: - DEBUGP(DSLHC, "slhc_compress(): Packet will be sent uncompressed...\n"); - memcpy(&cs->cs_ip,ip,20); - memcpy(&cs->cs_tcp,th,20); - if (ip->ihl > 5) - memcpy(cs->cs_ipopt, ip+1, ((ip->ihl) - 5) * 4); - if (th->doff > 5) - memcpy(cs->cs_tcpopt, th+1, ((th->doff) - 5) * 4); - comp->xmit_current = cs->cs_this; - comp->sls_o_uncompressed++; - memcpy(ocp, icp, isize); - *cpp = ocp; - ocp[9] = cs->cs_this; - ocp[0] |= SL_TYPE_UNCOMPRESSED_TCP; - return isize; -} - - -int -slhc_uncompress(struct slcompress *comp, unsigned char *icp, int isize) -{ - register int changes; - long x; - register struct tcphdr *thp; - register struct iphdr *ip; - register struct cstate *cs; - int len, hdrlen; - unsigned char *cp = icp; - - /* We've got a compressed packet; read the change byte */ - comp->sls_i_compressed++; - if(isize < 3){ - comp->sls_i_error++; - return 0; - } - changes = *cp++; - if(changes & NEW_C){ - /* Make sure the state index is in range, then grab the state. - * If we have a good state index, clear the 'discard' flag. - */ - x = *cp++; /* Read conn index */ - if(x < 0 || x > comp->rslot_limit) - goto bad; - - comp->flags &=~ SLF_TOSS; - comp->recv_current = x; - } else { - /* this packet has an implicit state index. If we've - * had a line error since the last time we got an - * explicit state index, we have to toss the packet. */ - if(comp->flags & SLF_TOSS){ - comp->sls_i_tossed++; - return 0; - } - } - cs = &comp->rstate[comp->recv_current]; - thp = &cs->cs_tcp; - ip = &cs->cs_ip; - - thp->check = *(__sum16 *)cp; - cp += 2; - - thp->psh = (changes & TCP_PUSH_BIT) ? 1 : 0; -/* - * we can use the same number for the length of the saved header and - * the current one, because the packet wouldn't have been sent - * as compressed unless the options were the same as the previous one - */ - - hdrlen = ip->ihl * 4 + thp->doff * 4; - - switch(changes & SPECIALS_MASK){ - case SPECIAL_I: /* Echoed terminal traffic */ - DEBUGP(DSLHC, "slhc_uncompress(): Echoed terminal traffic detected\n"); - - { - register short i; - i = ntohs(ip->tot_len) - hdrlen; - thp->ack_seq = htonl( ntohl(thp->ack_seq) + i); - thp->seq = htonl( ntohl(thp->seq) + i); - } - break; - - case SPECIAL_D: /* Unidirectional data */ - DEBUGP(DSLHC, "slhc_uncompress(): Unidirectional data detected\n"); - thp->seq = htonl( ntohl(thp->seq) + - ntohs(ip->tot_len) - hdrlen); - break; - - default: - DEBUGP(DSLHC, "slhc_uncompress(): default packet type detected\n"); - if(changes & NEW_U){ - thp->urg = 1; - if((x = decode(&cp)) == -1) { - goto bad; - } - thp->urg_ptr = htons(x); - } else - thp->urg = 0; - if(changes & NEW_W){ - if((x = decode(&cp)) == -1) { - goto bad; - } - thp->window = htons( ntohs(thp->window) + x); - } - if(changes & NEW_A){ - if((x = decode(&cp)) == -1) { - goto bad; - } - thp->ack_seq = htonl( ntohl(thp->ack_seq) + x); - } - if(changes & NEW_S){ - if((x = decode(&cp)) == -1) { - goto bad; - } - thp->seq = htonl( ntohl(thp->seq) + x); - } - break; - } - if(changes & NEW_I){ - if((x = decode(&cp)) == -1) { - goto bad; - } - ip->id = htons (ntohs (ip->id) + x); - } else - ip->id = htons (ntohs (ip->id) + 1); - - /* - * At this point, cp points to the first byte of data in the - * packet. Put the reconstructed TCP and IP headers back on the - * packet. Recalculate IP checksum (but not TCP checksum). - */ - - len = isize - (cp - icp); - if (len < 0) - goto bad; - len += hdrlen; - ip->tot_len = htons(len); - ip->check = 0; - - DEBUGP(DSLHC, "slhc_uncompress(): making space for the reconstructed header...\n"); - memmove(icp + hdrlen, cp, len - hdrlen); - - cp = icp; - memcpy(cp, ip, 20); - cp += 20; - - if (ip->ihl > 5) { - memcpy(cp, cs->cs_ipopt, (ip->ihl - 5) * 4); - cp += (ip->ihl - 5) * 4; - } - - put_unaligned(ip_fast_csum(icp, ip->ihl), - &((struct iphdr *)icp)->check); - - memcpy(cp, thp, 20); - cp += 20; - - if (thp->doff > 5) { - memcpy(cp, cs->cs_tcpopt, ((thp->doff) - 5) * 4); - cp += ((thp->doff) - 5) * 4; - } - - return len; -bad: - DEBUGP(DSLHC, "slhc_uncompress(): bad packet detected!\n"); - comp->sls_i_error++; - return slhc_toss( comp ); -} - - -int -slhc_remember(struct slcompress *comp, unsigned char *icp, int isize) -{ - register struct cstate *cs; - unsigned ihl; - - unsigned char index; - - if(isize < 20) { - /* The packet is shorter than a legal IP header */ - comp->sls_i_runt++; - DEBUGP(DSLHC, "slhc_remember(): The packet is shorter than a legal IP header ==> slhc_toss()\n"); - return slhc_toss( comp ); - } - /* Peek at the IP header's IHL field to find its length */ - ihl = icp[0] & 0xf; - if(ihl < 20 / 4){ - /* The IP header length field is too small */ - comp->sls_i_runt++; - DEBUGP(DSLHC, "slhc_remember(): The IP header length field is too small ==> slhc_toss()\n"); - return slhc_toss( comp ); - } - index = icp[9]; - icp[9] = IPPROTO_TCP; - - if (ip_fast_csum(icp, ihl)) { - /* Bad IP header checksum; discard */ - comp->sls_i_badcheck++; - DEBUGP(DSLHC, "slhc_remember(): Bad IP header checksum; discard ==> slhc_toss()\n"); - return slhc_toss( comp ); - } - if(index > comp->rslot_limit) { - comp->sls_i_error++; - DEBUGP(DSLHC, "slhc_remember(): index > comp->rslot_limit ==> slhc_toss()\n"); - return slhc_toss(comp); - } - - /* Update local state */ - cs = &comp->rstate[comp->recv_current = index]; - comp->flags &=~ SLF_TOSS; - memcpy(&cs->cs_ip,icp,20); - memcpy(&cs->cs_tcp,icp + ihl*4,20); - if (ihl > 5) - memcpy(cs->cs_ipopt, icp + sizeof(struct iphdr), (ihl - 5) * 4); - if (cs->cs_tcp.doff > 5) - memcpy(cs->cs_tcpopt, icp + ihl*4 + sizeof(struct tcphdr), (cs->cs_tcp.doff - 5) * 4); - cs->cs_hsize = ihl*2 + cs->cs_tcp.doff*2; - /* Put headers back on packet - * Neither header checksum is recalculated - */ - comp->sls_i_uncompressed++; - return isize; -} - -int -slhc_toss(struct slcompress *comp) -{ - DEBUGP(DSLHC, "slhc_toss(): Reset compression state...\n"); - if ( comp == NULLSLCOMPR ) - return 0; - - comp->flags |= SLF_TOSS; - return 0; -} - -void slhc_i_status(struct slcompress *comp) -{ - if (comp != NULLSLCOMPR) { - DEBUGP(DSLHC, "slhc_i_status(): %d Cmp, %d Uncmp, %d Bad, %d Tossed\n", - comp->sls_i_compressed, - comp->sls_i_uncompressed, - comp->sls_i_error, - comp->sls_i_tossed); - } -} - -void slhc_o_status(struct slcompress *comp) -{ - if (comp != NULLSLCOMPR) { - DEBUGP(DSLHC, "slhc_o_status(): %d Cmp, %d Uncmp, %d AsIs, %d NotTCP %d Searches, %d Misses\n", - comp->sls_o_compressed, - comp->sls_o_uncompressed, - comp->sls_o_tcp, - comp->sls_o_nontcp, - comp->sls_o_searches, - comp->sls_o_misses); - } -} - diff --git a/src/gprs/v42bis.c b/src/gprs/v42bis.c deleted file mode 100644 index 0759cdf1c..000000000 --- a/src/gprs/v42bis.c +++ /dev/null @@ -1,767 +0,0 @@ -/* - * SpanDSP - a series of DSP components for telephony - * - * v42bis.c - * - * Written by Steve Underwood - * - * Copyright (C) 2005, 2011 Steve Underwood - * - * All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 2.1, - * as published by the Free Software Foundation. - * - * 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* THIS IS A WORK IN PROGRESS. IT IS NOT FINISHED. - Currently it performs the core compression and decompression functions OK. - However, a number of the bells and whistles in V.42bis are incomplete. */ - -/*! \file */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - - -#define span_log(x,y,msg, ...) DEBUGP(DV42BIS,msg, ##__VA_ARGS__) -#define span_log_init(x,y,z) -#define span_log_set_protocol(x,y) - - -#define FALSE 0 -#define TRUE 1 - -/* Fixed parameters from the spec. */ -/* Character size (bits) */ -#define V42BIS_N3 8 -/* Number of characters in the alphabet */ -#define V42BIS_N4 256 -/* Index number of first dictionary entry used to store a string */ -#define V42BIS_N5 (V42BIS_N4 + V42BIS_N6) -/* Number of control codewords */ -#define V42BIS_N6 3 -/* V.42bis/9.2 */ -#define V42BIS_ESC_STEP 51 - -/* Compreeibility monitoring parameters for assessing automated switches between - transparent and compressed mode */ -#define COMPRESSIBILITY_MONITOR (256*V42BIS_N3) -#define COMPRESSIBILITY_MONITOR_HYSTERESIS 11 - -/* Control code words in compressed mode */ -enum -{ - V42BIS_ETM = 0, /* Enter transparent mode */ - V42BIS_FLUSH = 1, /* Flush data */ - V42BIS_STEPUP = 2 /* Step up codeword size */ -}; - -/* Command codes in transparent mode */ -enum -{ - V42BIS_ECM = 0, /* Enter compression mode */ - V42BIS_EID = 1, /* Escape character in data */ - V42BIS_RESET = 2 /* Force reinitialisation */ -}; - -static __inline__ void push_octet(v42bis_comp_state_t *s, int octet) -{ - s->output_buf[s->output_octet_count++] = (uint8_t) octet; - if (s->output_octet_count >= s->max_output_len) - { - s->handler(s->user_data, s->output_buf, s->output_octet_count); - s->output_octet_count = 0; - } -} -/*- End of function --------------------------------------------------------*/ - -static __inline__ void push_octets(v42bis_comp_state_t *s, const uint8_t buf[], int len) -{ - int i; - int chunk; - - i = 0; - while ((s->output_octet_count + len - i) >= s->max_output_len) - { - chunk = s->max_output_len - s->output_octet_count; - memcpy(&s->output_buf[s->output_octet_count], &buf[i], chunk); - s->handler(s->user_data, s->output_buf, s->max_output_len); - s->output_octet_count = 0; - i += chunk; - } - chunk = len - i; - if (chunk > 0) - { - memcpy(&s->output_buf[s->output_octet_count], &buf[i], chunk); - s->output_octet_count += chunk; - } -} -/*- End of function --------------------------------------------------------*/ - -static __inline__ void push_compressed_code(v42bis_comp_state_t *s, int code) -{ - s->bit_buffer |= code << s->bit_count; - s->bit_count += s->v42bis_parm_c2; - while (s->bit_count >= 8) - { - push_octet(s, s->bit_buffer & 0xFF); - s->bit_buffer >>= 8; - s->bit_count -= 8; - } -} -/*- End of function --------------------------------------------------------*/ - -static __inline__ void push_octet_alignment(v42bis_comp_state_t *s) -{ - if ((s->bit_count & 7)) - { - s->bit_count += (8 - (s->bit_count & 7)); - while (s->bit_count >= 8) - { - push_octet(s, s->bit_buffer & 0xFF); - s->bit_buffer >>= 8; - s->bit_count -= 8; - } - } -} -/*- End of function --------------------------------------------------------*/ - -static __inline__ void flush_octets(v42bis_comp_state_t *s) -{ - if (s->output_octet_count > 0) - { - s->handler(s->user_data, s->output_buf, s->output_octet_count); - s->output_octet_count = 0; - } -} -/*- End of function --------------------------------------------------------*/ - -static void dictionary_init(v42bis_comp_state_t *s) -{ - int i; - - memset(s->dict, 0, sizeof(s->dict)); - for (i = 0; i < V42BIS_N4; i++) - s->dict[i + V42BIS_N6].node_octet = i; - s->v42bis_parm_c1 = V42BIS_N5; - s->v42bis_parm_c2 = V42BIS_N3 + 1; - s->v42bis_parm_c3 = V42BIS_N4 << 1; - s->last_matched = 0; - s->update_at = 0; - s->last_added = 0; - s->bit_buffer = 0; - s->bit_count = 0; - s->flushed_length = 0; - s->string_length = 0; - s->escape_code = 0; - s->transparent = TRUE; - s->escaped = FALSE; - s->compression_performance = COMPRESSIBILITY_MONITOR; -} -/*- End of function --------------------------------------------------------*/ - -static uint16_t match_octet(v42bis_comp_state_t *s, uint16_t at, uint8_t octet) -{ - uint16_t e; - - if (at == 0) - return octet + V42BIS_N6; - e = s->dict[at].child; - while (e) - { - if (s->dict[e].node_octet == octet) - return e; - e = s->dict[e].next; - } - return 0; -} -/*- End of function --------------------------------------------------------*/ - -static uint16_t add_octet_to_dictionary(v42bis_comp_state_t *s, uint16_t at, uint8_t octet) -{ - uint16_t newx; - uint16_t next; - uint16_t e; - - newx = s->v42bis_parm_c1; - s->dict[newx].node_octet = octet; - s->dict[newx].parent = at; - s->dict[newx].child = 0; - s->dict[newx].next = s->dict[at].child; - s->dict[at].child = newx; - next = newx; - /* 6.5 Recovering a dictionary entry to use next */ - do - { - /* 6.5(a) and (b) */ - if (++next == s->v42bis_parm_n2) - next = V42BIS_N5; - } - while (s->dict[next].child); - /* 6.5(c) We need to reuse a leaf node */ - if (s->dict[next].parent) - { - /* 6.5(d) Detach the leaf node from its parent, and re-use it */ - e = s->dict[next].parent; - if (s->dict[e].child == next) - { - s->dict[e].child = s->dict[next].next; - } - else - { - e = s->dict[e].child; - while (s->dict[e].next != next) - e = s->dict[e].next; - s->dict[e].next = s->dict[next].next; - } - } - s->v42bis_parm_c1 = next; - return newx; -} -/*- End of function --------------------------------------------------------*/ - -static void send_string(v42bis_comp_state_t *s) -{ - push_octets(s, s->string, s->string_length); - s->string_length = 0; - s->flushed_length = 0; -} -/*- End of function --------------------------------------------------------*/ - -static void expand_codeword_to_string(v42bis_comp_state_t *s, uint16_t code) -{ - int i; - uint16_t p; - - /* Work out the length */ - for (i = 0, p = code; p; i++) - p = s->dict[p].parent; - s->string_length += i; - /* Now expand the known length of string */ - i = s->string_length - 1; - for (p = code; p; ) - { - s->string[i--] = s->dict[p].node_octet; - p = s->dict[p].parent; - } -} -/*- End of function --------------------------------------------------------*/ - -static void send_encoded_data(v42bis_comp_state_t *s, uint16_t code) -{ - int i; - - /* Update compressibility metric */ - /* Integrate at the compressed bit rate, and leak at the pre-compression bit rate */ - s->compression_performance += (s->v42bis_parm_c2 - s->compression_performance*s->string_length*V42BIS_N3/COMPRESSIBILITY_MONITOR); - if (s->transparent) - { - for (i = 0; i < s->string_length; i++) - { - push_octet(s, s->string[i]); - if (s->string[i] == s->escape_code) - { - push_octet(s, V42BIS_EID); - s->escape_code += V42BIS_ESC_STEP; - } - } - } - else - { - /* Allow for any escape octets in the string */ - for (i = 0; i < s->string_length; i++) - { - if (s->string[i] == s->escape_code) - s->escape_code += V42BIS_ESC_STEP; - } - /* 7.4 Encoding - we now have the longest matchable string, and will need to output the code for it. */ - while (code >= s->v42bis_parm_c3) - { - /* We need to increase the codeword size */ - /* 7.4(a) */ - push_compressed_code(s, V42BIS_STEPUP); - /* 7.4(b) */ - s->v42bis_parm_c2++; - /* 7.4(c) */ - s->v42bis_parm_c3 <<= 1; - /* 7.4(d) this might need to be repeated, so we loop */ - } - /* 7.5 Transfer - output the last state of the string */ - push_compressed_code(s, code); - } - s->string_length = 0; - s->flushed_length = 0; -} -/*- End of function --------------------------------------------------------*/ - -static void go_compressed(v42bis_state_t *ss) -{ - v42bis_comp_state_t *s; - - s = &ss->compress; - if (!s->transparent) - return; - span_log(&ss->logging, SPAN_LOG_FLOW, "Changing to compressed mode\n"); - /* Switch out of transparent now, between codes. We need to send the octet which did not - match, just before switching. */ - if (s->last_matched) - { - s->update_at = s->last_matched; - send_encoded_data(s, s->last_matched); - s->last_matched = 0; - } - push_octet(s, s->escape_code); - push_octet(s, V42BIS_ECM); - s->bit_buffer = 0; - s->transparent = FALSE; -} -/*- End of function --------------------------------------------------------*/ - -static void go_transparent(v42bis_state_t *ss) -{ - v42bis_comp_state_t *s; - - s = &ss->compress; - if (s->transparent) - return; - span_log(&ss->logging, SPAN_LOG_FLOW, "Changing to transparent mode\n"); - /* Switch into transparent now, between codes, and the unmatched octet should - go out in transparent mode, just below */ - if (s->last_matched) - { - s->update_at = s->last_matched; - send_encoded_data(s, s->last_matched); - s->last_matched = 0; - } - s->last_added = 0; - push_compressed_code(s, V42BIS_ETM); - push_octet_alignment(s); - s->transparent = TRUE; -} -/*- End of function --------------------------------------------------------*/ - -static void monitor_for_mode_change(v42bis_state_t *ss) -{ - v42bis_comp_state_t *s; - - s = &ss->compress; - switch (s->compression_mode) - { - case V42BIS_COMPRESSION_MODE_DYNAMIC: - /* 7.8 Data compressibility test */ - if (s->transparent) - { - if (s->compression_performance < COMPRESSIBILITY_MONITOR - COMPRESSIBILITY_MONITOR_HYSTERESIS) - { - /* 7.8.1 Transition to compressed mode */ - go_compressed(ss); - } - } - else - { - if (s->compression_performance > COMPRESSIBILITY_MONITOR) - { - /* 7.8.2 Transition to transparent mode */ - go_transparent(ss); - } - } - /* 7.8.3 Reset function - TODO */ - break; - case V42BIS_COMPRESSION_MODE_ALWAYS: - if (s->transparent) - go_compressed(ss); - break; - case V42BIS_COMPRESSION_MODE_NEVER: - if (!s->transparent) - go_transparent(ss); - break; - } -} -/*- End of function --------------------------------------------------------*/ - -static int v42bis_comp_init(v42bis_comp_state_t *s, - int p1, - int p2, - put_msg_func_t handler, - void *user_data, - int max_output_len) -{ - memset(s, 0, sizeof(*s)); - s->v42bis_parm_n2 = p1; - s->v42bis_parm_n7 = p2; - s->handler = handler; - s->user_data = user_data; - s->max_output_len = (max_output_len < V42BIS_MAX_OUTPUT_LENGTH) ? max_output_len : V42BIS_MAX_OUTPUT_LENGTH; - s->output_octet_count = 0; - dictionary_init(s); - return 0; -} -/*- End of function --------------------------------------------------------*/ - -static int comp_exit(v42bis_comp_state_t *s) -{ - s->v42bis_parm_n2 = 0; - return 0; -} -/*- End of function --------------------------------------------------------*/ - -SPAN_DECLARE(int) v42bis_compress(v42bis_state_t *ss, const uint8_t buf[], int len) -{ - v42bis_comp_state_t *s; - int i; - uint16_t code; - - s = &ss->compress; - if (!s->v42bis_parm_p0) - { - /* Compression is off - just push the incoming data out */ - push_octets(s, buf, len); - return 0; - } - for (i = 0; i < len; ) - { - /* 6.4 Add the string to the dictionary */ - if (s->update_at) - { - if (match_octet(s, s->update_at, buf[i]) == 0) - s->last_added = add_octet_to_dictionary(s, s->update_at, buf[i]); - s->update_at = 0; - } - /* Match string */ - while (i < len) - { - code = match_octet(s, s->last_matched, buf[i]); - if (code == 0) - { - s->update_at = s->last_matched; - send_encoded_data(s, s->last_matched); - s->last_matched = 0; - break; - } - if (code == s->last_added) - { - s->last_added = 0; - send_encoded_data(s, s->last_matched); - s->last_matched = 0; - break; - } - s->last_matched = code; - /* 6.3(b) If the string matches a dictionary entry, and the entry is not that entry - created by the last invocation of the string matching procedure, then the - next character shall be read and appended to the string and this step - repeated. */ - s->string[s->string_length++] = buf[i++]; - /* 6.4(a) The string must not exceed N7 in length */ - if (s->string_length + s->flushed_length == s->v42bis_parm_n7) - { - send_encoded_data(s, s->last_matched); - s->last_matched = 0; - break; - } - } - monitor_for_mode_change(ss); - } - return 0; -} -/*- End of function --------------------------------------------------------*/ - -SPAN_DECLARE(int) v42bis_compress_flush(v42bis_state_t *ss) -{ - v42bis_comp_state_t *s; - int len; - - s = &ss->compress; - if (s->update_at) - return 0; - if (s->last_matched) - { - len = s->string_length; - send_encoded_data(s, s->last_matched); - s->flushed_length += len; - } - if (!s->transparent) - { - s->update_at = s->last_matched; - s->last_matched = 0; - s->flushed_length = 0; - push_compressed_code(s, V42BIS_FLUSH); - push_octet_alignment(s); - } - flush_octets(s); - return 0; -} -/*- End of function --------------------------------------------------------*/ - -SPAN_DECLARE(int) v42bis_decompress(v42bis_state_t *ss, const uint8_t buf[], int len) -{ - v42bis_comp_state_t *s; - int i; - int j; - int yyy; - uint16_t code; - uint16_t p; - uint8_t ch; - uint8_t in; - - s = &ss->decompress; - if (!s->v42bis_parm_p0) - { - /* Compression is off - just push the incoming data out */ - push_octets(s, buf, len); - return 0; - } - for (i = 0; i < len; ) - { - if (s->transparent) - { - in = buf[i]; - if (s->escaped) - { - /* Command */ - s->escaped = FALSE; - switch (in) - { - case V42BIS_ECM: - /* Enter compressed mode */ - span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_ECM\n"); - send_string(s); - s->transparent = FALSE; - s->update_at = s->last_matched; - s->last_matched = 0; - i++; - continue; - case V42BIS_EID: - /* Escape symbol */ - span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_EID\n"); - in = s->escape_code; - s->escape_code += V42BIS_ESC_STEP; - break; - case V42BIS_RESET: - /* Reset dictionary */ - span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_RESET\n"); - /* TODO: */ - send_string(s); - dictionary_init(s); - i++; - continue; - default: - span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_???? - %" PRIu32 "\n", in); - return -1; - } - } - else if (in == s->escape_code) - { - s->escaped = TRUE; - i++; - continue; - } - - yyy = TRUE; - for (j = 0; j < 2 && yyy; j++) - { - if (s->update_at) - { - if (match_octet(s, s->update_at, in) == 0) - s->last_added = add_octet_to_dictionary(s, s->update_at, in); - s->update_at = 0; - } - - code = match_octet(s, s->last_matched, in); - if (code == 0) - { - s->update_at = s->last_matched; - send_string(s); - s->last_matched = 0; - } - else if (code == s->last_added) - { - s->last_added = 0; - send_string(s); - s->last_matched = 0; - } - else - { - s->last_matched = code; - s->string[s->string_length++] = in; - if (s->string_length + s->flushed_length == s->v42bis_parm_n7) - { - send_string(s); - s->last_matched = 0; - } - i++; - yyy = FALSE; - } - } - } - else - { - /* Get code from input */ - while (s->bit_count < s->v42bis_parm_c2 && i < len) - { - s->bit_buffer |= buf[i++] << s->bit_count; - s->bit_count += 8; - } - if (s->bit_count < s->v42bis_parm_c2) - continue; - code = s->bit_buffer & ((1 << s->v42bis_parm_c2) - 1); - s->bit_buffer >>= s->v42bis_parm_c2; - s->bit_count -= s->v42bis_parm_c2; - - if (code < V42BIS_N6) - { - /* We have a control code. */ - switch (code) - { - case V42BIS_ETM: - /* Enter transparent mode */ - span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_ETM\n"); - s->bit_count = 0; - s->transparent = TRUE; - s->last_matched = 0; - s->last_added = 0; - break; - case V42BIS_FLUSH: - /* Flush signal */ - span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_FLUSH\n"); - s->bit_count = 0; - break; - case V42BIS_STEPUP: - /* Increase code word size */ - span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_STEPUP\n"); - s->v42bis_parm_c2++; - s->v42bis_parm_c3 <<= 1; - if (s->v42bis_parm_c2 > (s->v42bis_parm_n2 >> 3)) - return -1; - break; - } - continue; - } - /* Regular codeword */ - if (code == s->v42bis_parm_c1) - return -1; - expand_codeword_to_string(s, code); - if (s->update_at) - { - ch = s->string[0]; - if ((p = match_octet(s, s->update_at, ch)) == 0) - { - s->last_added = add_octet_to_dictionary(s, s->update_at, ch); - if (code == s->v42bis_parm_c1) - return -1; - } - else if (p == s->last_added) - { - s->last_added = 0; - } - } - s->update_at = ((s->string_length + s->flushed_length) == s->v42bis_parm_n7) ? 0 : code; - /* Allow for any escapes which may be in this string */ - for (j = 0; j < s->string_length; j++) - { - if (s->string[j] == s->escape_code) - s->escape_code += V42BIS_ESC_STEP; - } - send_string(s); - } - } - return 0; -} -/*- End of function --------------------------------------------------------*/ - -SPAN_DECLARE(int) v42bis_decompress_flush(v42bis_state_t *ss) -{ - v42bis_comp_state_t *s; - int len; - - s = &ss->decompress; - len = s->string_length; - send_string(s); - s->flushed_length += len; - flush_octets(s); - return 0; -} -/*- End of function --------------------------------------------------------*/ - -SPAN_DECLARE(void) v42bis_compression_control(v42bis_state_t *s, int mode) -{ - s->compress.compression_mode = mode; -} -/*- End of function --------------------------------------------------------*/ - -SPAN_DECLARE(v42bis_state_t *) v42bis_init(const void *ctx, - v42bis_state_t *s, - int negotiated_p0, - int negotiated_p1, - int negotiated_p2, - put_msg_func_t encode_handler, - void *encode_user_data, - int max_encode_len, - put_msg_func_t decode_handler, - void *decode_user_data, - int max_decode_len) -{ - int ret; - - if (negotiated_p1 < V42BIS_MIN_DICTIONARY_SIZE || negotiated_p1 > 65535) - return NULL; - if (negotiated_p2 < V42BIS_MIN_STRING_SIZE || negotiated_p2 > V42BIS_MAX_STRING_SIZE) - return NULL; - if (s == NULL) - { - if ((s = (v42bis_state_t *) talloc_zero_size(ctx,sizeof(*s))) == NULL) - return NULL; - } - memset(s, 0, sizeof(*s)); - span_log_init(&s->logging, SPAN_LOG_NONE, NULL); - span_log_set_protocol(&s->logging, "V.42bis"); - - if ((ret = v42bis_comp_init(&s->compress, negotiated_p1, negotiated_p2, encode_handler, encode_user_data, max_encode_len))) - return NULL; - if ((ret = v42bis_comp_init(&s->decompress, negotiated_p1, negotiated_p2, decode_handler, decode_user_data, max_decode_len))) - { - comp_exit(&s->compress); - return NULL; - } - s->compress.v42bis_parm_p0 = negotiated_p0 & 2; - s->decompress.v42bis_parm_p0 = negotiated_p0 & 1; - - return s; -} -/*- End of function --------------------------------------------------------*/ - -SPAN_DECLARE(int) v42bis_release(v42bis_state_t *s) -{ - return 0; -} -/*- End of function --------------------------------------------------------*/ - -SPAN_DECLARE(int) v42bis_free(v42bis_state_t *s) -{ - comp_exit(&s->compress); - comp_exit(&s->decompress); - talloc_free(s); - return 0; -} -/*- End of function --------------------------------------------------------*/ -/*- End of file ------------------------------------------------------------*/ diff --git a/src/sgsn/Makefile.am b/src/sgsn/Makefile.am new file mode 100644 index 000000000..a8da94308 --- /dev/null +++ b/src/sgsn/Makefile.am @@ -0,0 +1,88 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -fno-strict-aliasing \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(LIBOSMOGB_CFLAGS) \ + $(LIBOSMOGSUPCLIENT_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(LIBCARES_CFLAGS) \ + $(LIBGTP_CFLAGS) \ + $(NULL) +if BUILD_IU +AM_CFLAGS += \ + $(LIBASN1C_CFLAGS) \ + $(LIBOSMOSIGTRAN_CFLAGS) \ + $(LIBOSMORANAP_CFLAGS) \ + $(NULL) +endif + +OSMO_LIBS = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOGB_LIBS) \ + $(LIBGTP_LIBS) \ + $(NULL) + +bin_PROGRAMS = \ + osmo-sgsn \ + $(NULL) + +osmo_sgsn_SOURCES = \ + gprs_gb.c \ + gprs_gmm_attach.c \ + gprs_gmm.c \ + gprs_mm_state_gb_fsm.c \ + gprs_mm_state_iu_fsm.c \ + gprs_ranap.c \ + gprs_sgsn.c \ + gprs_sndcp.c \ + gprs_sndcp_comp.c \ + gprs_sndcp_dcomp.c \ + gprs_sndcp_pcomp.c \ + gprs_sndcp_vty.c \ + gprs_sndcp_xid.c \ + sgsn_main.c \ + sgsn_vty.c \ + sgsn_libgtp.c \ + gprs_llc.c \ + gprs_llc_vty.c \ + sgsn_ctrl.c \ + sgsn_auth.c \ + gprs_subscriber.c \ + sgsn_cdr.c \ + slhc.c \ + gprs_llc_xid.c \ + v42bis.c \ + $(NULL) +osmo_sgsn_LDADD = \ + $(top_builddir)/src/gprs/gprs_llc_parse.o \ + $(top_builddir)/src/gprs/crc24.o \ + $(top_builddir)/src/gprs/gprs_utils.o \ + $(top_builddir)/src/gprs/sgsn_ares.o \ + $(OSMO_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(LIBOSMOGSUPCLIENT_LIBS) \ + $(LIBCARES_LIBS) \ + $(LIBGTP_LIBS) \ + -lrt \ + -lm \ + $(NULL) +if BUILD_IU +osmo_sgsn_LDADD += \ + $(LIBOSMOSIGTRAN_LIBS) \ + $(LIBOSMORANAP_LIBS) \ + $(LIBASN1C_LIBS) \ + $(NULL) +endif diff --git a/src/sgsn/gprs_gb.c b/src/sgsn/gprs_gb.c new file mode 100644 index 000000000..65342cf22 --- /dev/null +++ b/src/sgsn/gprs_gb.c @@ -0,0 +1,75 @@ +/* Messages on the Gb interface (A/Gb mode) */ + +/* (C) 2009-2015 by Harald Welte + * (C) 2010 by On-Waves + * (C) 2019 by sysmocom s.f.m.c. GmbH + * + * 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 . + * + */ + +#include + +#include +#include + +#include "bscconfig.h" + +#include +#include +#include +#include + +/* Main entry point for incoming 04.08 GPRS messages from Gb */ +int gsm0408_gprs_rcvmsg_gb(struct msgb *msg, struct gprs_llc_llme *llme, + bool drop_cipherable) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t pdisc = gsm48_hdr_pdisc(gh); + struct sgsn_mm_ctx *mmctx; + struct gprs_ra_id ra_id; + int rc = -EINVAL; + + bssgp_parse_cell_id(&ra_id, msgb_bcid(msg)); + mmctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &ra_id); + if (mmctx) { + msgid2mmctx(mmctx, msg); + rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_SIG_IN]); + mmctx->gb.llme = llme; + osmo_fsm_inst_dispatch(mmctx->gb.mm_state_fsm, E_MM_PDU_RECEPTION, NULL); + } + + /* MMCTX can be NULL */ + + switch (pdisc) { + case GSM48_PDISC_MM_GPRS: + rc = gsm0408_rcv_gmm(mmctx, msg, llme, drop_cipherable); + break; + case GSM48_PDISC_SM_GPRS: + rc = gsm0408_rcv_gsm(mmctx, msg, llme); + break; + default: + LOGMMCTXP(LOGL_NOTICE, mmctx, + "Unknown GSM 04.08 discriminator 0x%02x: %s\n", + pdisc, osmo_hexdump((uint8_t *)gh, msgb_l3len(msg))); + /* FIXME: return status message */ + break; + } + + /* MMCTX can be invalid */ + + return rc; +} diff --git a/src/sgsn/gprs_gmm.c b/src/sgsn/gprs_gmm.c new file mode 100644 index 000000000..edb7eea1e --- /dev/null +++ b/src/sgsn/gprs_gmm.c @@ -0,0 +1,2852 @@ +/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */ + +/* (C) 2009-2015 by Harald Welte + * (C) 2010 by On-Waves + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bscconfig.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define PTMSI_ALLOC + +/* 3GPP TS 04.08 sec 6.1.3.4.3(.a) "Abnormal cases" */ +#define T339X_MAX_RETRANS 4 + +extern struct sgsn_instance *sgsn; +extern void *tall_sgsn_ctx; + +static const struct tlv_definition gsm48_gmm_att_tlvdef = { + .def = { + [GSM48_IE_GMM_CIPH_CKSN] = { TLV_TYPE_FIXED, 1 }, + [GSM48_IE_GMM_TIMER_READY] = { TLV_TYPE_TV, 1 }, + [GSM48_IE_GMM_ALLOC_PTMSI] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GMM_PTMSI_SIG] = { TLV_TYPE_FIXED, 3 }, + [GSM48_IE_GMM_AUTH_RAND] = { TLV_TYPE_FIXED, 16 }, + [GSM48_IE_GMM_AUTH_SRES] = { TLV_TYPE_FIXED, 4 }, + [GSM48_IE_GMM_AUTH_RES_EXT] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GMM_AUTH_FAIL_PAR] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GMM_IMEISV] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GMM_DRX_PARAM] = { TLV_TYPE_FIXED, 2 }, + [GSM48_IE_GMM_MS_NET_CAPA] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GMM_PDP_CTX_STATUS] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GMM_PS_LCS_CAPA] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GMM_GMM_MBMS_CTX_ST] = { TLV_TYPE_TLV, 0 }, + }, +}; + +static const struct tlv_definition gsm48_sm_att_tlvdef = { + .def = { + [GSM48_IE_GSM_APN] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GSM_PROTO_CONF_OPT] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GSM_PDP_ADDR] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GSM_AA_TMR] = { TLV_TYPE_TV, 1 }, + [GSM48_IE_GSM_NAME_FULL] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GSM_NAME_SHORT] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GSM_TIMEZONE] = { TLV_TYPE_FIXED, 1 }, + [GSM48_IE_GSM_UTC_AND_TZ] = { TLV_TYPE_FIXED, 7 }, + [GSM48_IE_GSM_LSA_ID] = { TLV_TYPE_TLV, 0 }, + }, +}; + +/* Our implementation, should be kept in SGSN */ + +static void mmctx_timer_cb(void *_mm); + +static void mmctx_timer_start(struct sgsn_mm_ctx *mm, unsigned int T) +{ + unsigned long seconds; + if (osmo_timer_pending(&mm->timer)) + LOGMMCTXP(LOGL_ERROR, mm, "Starting MM timer %u while old " + "timer %u pending\n", T, mm->T); + + seconds = osmo_tdef_get(sgsn->cfg.T_defs, T, OSMO_TDEF_S, -1); + + mm->T = T; + mm->num_T_exp = 0; + + /* FIXME: we should do this only once ? */ + osmo_timer_setup(&mm->timer, mmctx_timer_cb, mm); + osmo_timer_schedule(&mm->timer, seconds, 0); +} + +static void mmctx_timer_stop(struct sgsn_mm_ctx *mm, unsigned int T) +{ + if (mm->T != T) + LOGMMCTXP(LOGL_ERROR, mm, "Stopping MM timer %u but " + "%u is running\n", T, mm->T); + osmo_timer_del(&mm->timer); +} + +time_t gprs_max_time_to_idle(void) +{ + unsigned long T3314, T3312; + + T3314 = osmo_tdef_get(sgsn->cfg.T_defs, 3314, OSMO_TDEF_S, -1); + T3312 = osmo_tdef_get(sgsn->cfg.T_defs, 3312, OSMO_TDEF_S, -1); + return T3314 + (T3312 + 4 * 60); +} + +/* Send a message through the underlying layer. + * For param encryptable, see 3GPP TS 24.008 § 4.7.1.2 and + * gsm48_hdr_gmm_cipherable(). Pass false for not cipherable messages. */ +static int gsm48_gmm_sendmsg(struct msgb *msg, int command, + struct sgsn_mm_ctx *mm, bool encryptable) +{ + if (mm) { + rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PKTS_SIG_OUT]); +#ifdef BUILD_IU + if (mm->ran_type == MM_CTX_T_UTRAN_Iu) + return ranap_iu_tx(msg, GPRS_SAPI_GMM); +#endif + } + +#ifdef BUILD_IU + if (MSG_IU_UE_CTX(msg)) + return ranap_iu_tx(msg, GPRS_SAPI_GMM); +#endif + + /* caller needs to provide TLLI, BVCI and NSEI */ + return gprs_llc_tx_ui(msg, GPRS_SAPI_GMM, command, mm, encryptable); +} + +/* copy identifiers from old message to new message, this + * is required so lower layers can route it correctly */ +static void gmm_copy_id(struct msgb *msg, const struct msgb *old) +{ + msgb_tlli(msg) = msgb_tlli(old); + msgb_bvci(msg) = msgb_bvci(old); + msgb_nsei(msg) = msgb_nsei(old); + MSG_IU_UE_CTX_SET(msg, MSG_IU_UE_CTX(old)); +} + +/* Store BVCI/NSEI in MM context */ +void msgid2mmctx(struct sgsn_mm_ctx *mm, const struct msgb *msg) +{ + /* check for Iu or Gb */ + if (!MSG_IU_UE_CTX(msg)) { + mm->gb.bvci = msgb_bvci(msg); + mm->gb.nsei = msgb_nsei(msg); + } +#ifdef BUILD_IU + else { + /* In case a Iu connection is reconnected we need to update the ue ctx */ + /* FIXME: the old ue_ctx have to be freed/disconnected */ + mm->iu.ue_ctx = MSG_IU_UE_CTX(msg); + if (mm->ran_type == MM_CTX_T_UTRAN_Iu + && mm->iu.ue_ctx) { + mm->iu.ue_ctx->rab_assign_addr_enc = + sgsn->cfg.iu.rab_assign_addr_enc; + } + } +#endif +} + +/* Store BVCI/NSEI in MM context */ +static void mmctx2msgid(struct msgb *msg, const struct sgsn_mm_ctx *mm) +{ + msgb_tlli(msg) = mm->gb.tlli; + msgb_bvci(msg) = mm->gb.bvci; + msgb_nsei(msg) = mm->gb.nsei; + MSG_IU_UE_CTX_SET(msg, mm->iu.ue_ctx); +} + +static void mm_ctx_cleanup_free(struct sgsn_mm_ctx *ctx, const char *log_text) +{ + LOGMMCTXP(LOGL_INFO, ctx, "Cleaning MM context due to %s\n", log_text); + + /* Mark MM state as deregistered */ + ctx->gmm_state = GMM_DEREGISTERED; + + switch(ctx->ran_type) { + case MM_CTX_T_UTRAN_Iu: + osmo_fsm_inst_dispatch(ctx->iu.mm_state_fsm, E_PMM_IMPLICIT_DETACH, NULL); + break; + case MM_CTX_T_GERAN_Gb: + osmo_fsm_inst_dispatch(ctx->gb.mm_state_fsm, E_MM_IMPLICIT_DETACH, NULL); + break; + } + + sgsn_mm_ctx_cleanup_free(ctx); +} + +/* Chapter 9.4.18 */ +static int _tx_status(struct msgb *msg, uint8_t cause, + struct sgsn_mm_ctx *mmctx, int sm) +{ + struct gsm48_hdr *gh; + + /* MMCTX might be NULL! */ + + DEBUGP(DMM, "<- GPRS MM STATUS (cause: %s)\n", + get_value_string(gsm48_gmm_cause_names, cause)); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + if (sm) { + gh->proto_discr = GSM48_PDISC_SM_GPRS; + gh->msg_type = GSM48_MT_GSM_STATUS; + } else { + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_STATUS; + } + gh->data[0] = cause; + + return gsm48_gmm_sendmsg(msg, 0, mmctx, true); +} + +static int gsm48_tx_gmm_status(struct sgsn_mm_ctx *mmctx, uint8_t cause) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 GMM STATUS"); + + mmctx2msgid(msg, mmctx); + return _tx_status(msg, cause, mmctx, 0); +} + +static int gsm48_tx_sm_status(struct sgsn_mm_ctx *mmctx, uint8_t cause) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 SM STATUS"); + + mmctx2msgid(msg, mmctx); + return _tx_status(msg, cause, mmctx, 1); +} + +static int _tx_detach_req(struct msgb *msg, uint8_t detach_type, uint8_t cause, + struct sgsn_mm_ctx *mmctx) +{ + struct gsm48_hdr *gh; + + /* MMCTX might be NULL! */ + + DEBUGP(DMM, "<- GPRS MM DETACH REQ (type: %s, cause: %s)\n", + get_value_string(gprs_det_t_mt_strs, detach_type), + get_value_string(gsm48_gmm_cause_names, cause)); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_DETACH_REQ; + gh->data[0] = detach_type & 0x07; + + msgb_tv_put(msg, GSM48_IE_GMM_CAUSE, cause); + + return gsm48_gmm_sendmsg(msg, 0, mmctx, true); +} + +static int gsm48_tx_gmm_detach_req(struct sgsn_mm_ctx *mmctx, + uint8_t detach_type, uint8_t cause) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DET REQ"); + + mmctx2msgid(msg, mmctx); + return _tx_detach_req(msg, detach_type, cause, mmctx); +} + +static int gsm48_tx_gmm_detach_req_oldmsg(struct msgb *oldmsg, + uint8_t detach_type, uint8_t cause) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DET OLD"); + + gmm_copy_id(msg, oldmsg); + return _tx_detach_req(msg, detach_type, cause, NULL); +} + +static struct gsm48_qos default_qos = { + .delay_class = 4, /* best effort */ + .reliab_class = GSM48_QOS_RC_LLC_UN_RLC_ACK_DATA_PROT, + .peak_tput = GSM48_QOS_PEAK_TPUT_32000bps, + .preced_class = GSM48_QOS_PC_NORMAL, + .mean_tput = GSM48_QOS_MEAN_TPUT_BEST_EFFORT, + .traf_class = GSM48_QOS_TC_INTERACTIVE, + .deliv_order = GSM48_QOS_DO_UNORDERED, + .deliv_err_sdu = GSM48_QOS_ERRSDU_YES, + .max_sdu_size = GSM48_QOS_MAXSDU_1520, + .max_bitrate_up = GSM48_QOS_MBRATE_63k, + .max_bitrate_down = GSM48_QOS_MBRATE_63k, + .resid_ber = GSM48_QOS_RBER_5e_2, + .sdu_err_ratio = GSM48_QOS_SERR_1e_2, + .handling_prio = 3, + .xfer_delay = 0x10, /* 200ms */ + .guar_bitrate_up = GSM48_QOS_MBRATE_0k, + .guar_bitrate_down = GSM48_QOS_MBRATE_0k, + .sig_ind = 0, /* not optimised for signalling */ + .max_bitrate_down_ext = 0, /* use octet 9 */ + .guar_bitrate_down_ext = 0, /* use octet 13 */ +}; + +/* Chapter 9.4.2: Attach accept */ +int gsm48_tx_gmm_att_ack(struct sgsn_mm_ctx *mm) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ATT ACK"); + struct gsm48_hdr *gh; + struct gsm48_attach_ack *aa; + uint8_t *mid; + unsigned long t; +#if 0 + uint8_t *ptsig; +#endif + + LOGMMCTXP(LOGL_INFO, mm, "<- GPRS ATTACH ACCEPT (new P-TMSI=0x%08x)\n", mm->p_tmsi); + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_ATTACH_ACKED]); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_ATTACH_ACK; + + aa = (struct gsm48_attach_ack *) msgb_put(msg, sizeof(*aa)); + aa->force_stby = 0; /* not indicated */ + aa->att_result = 1; /* GPRS only */ + t = osmo_tdef_get(sgsn->cfg.T_defs, 3312, OSMO_TDEF_S, -1); + aa->ra_upd_timer = gprs_secs_to_tmr_floor(t); + aa->radio_prio = 4; /* lowest */ + gsm48_encode_ra(&aa->ra_id, &mm->ra); + +#if 0 + /* Optional: P-TMSI signature */ + msgb_v_put(msg, GSM48_IE_GMM_PTMSI_SIG); + ptsig = msgb_put(msg, 3); + ptsig[0] = mm->p_tmsi_sig >> 16; + ptsig[1] = mm->p_tmsi_sig >> 8; + ptsig[2] = mm->p_tmsi_sig & 0xff; + +#endif + /* Optional: Negotiated Ready timer value + * (fixed 44s, default value, GSM 04.08, table 11.4a) to safely limit + * the inactivity time READY->STANDBY. + */ + t = osmo_tdef_get(sgsn->cfg.T_defs, 3314, OSMO_TDEF_S, -1); + msgb_tv_put(msg, GSM48_IE_GMM_TIMER_READY, gprs_secs_to_tmr_floor(t)); + +#ifdef PTMSI_ALLOC + /* Optional: Allocated P-TMSI */ + mid = msgb_put(msg, GSM48_MID_TMSI_LEN); + gsm48_generate_mid_from_tmsi(mid, mm->p_tmsi); + mid[0] = GSM48_IE_GMM_ALLOC_PTMSI; +#endif + + /* Optional: MS-identity (combined attach) */ + /* Optional: GMM cause (partial attach result for combined attach) */ + + return gsm48_gmm_sendmsg(msg, 0, mm, true); +} + +/* Chapter 9.4.5: Attach reject */ +static int _tx_gmm_att_rej(struct msgb *msg, uint8_t gmm_cause, + const struct sgsn_mm_ctx *mm) +{ + struct gsm48_hdr *gh; + + LOGMMCTXP(LOGL_NOTICE, mm, "<- GPRS ATTACH REJECT: %s\n", + get_value_string(gsm48_gmm_cause_names, gmm_cause)); + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_ATTACH_REJECTED]); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_ATTACH_REJ; + gh->data[0] = gmm_cause; + + return gsm48_gmm_sendmsg(msg, 0, NULL, false); +} +static int gsm48_tx_gmm_att_rej_oldmsg(const struct msgb *old_msg, + uint8_t gmm_cause) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ATT REJ OLD"); + gmm_copy_id(msg, old_msg); + return _tx_gmm_att_rej(msg, gmm_cause, NULL); +} +int gsm48_tx_gmm_att_rej(struct sgsn_mm_ctx *mm, + uint8_t gmm_cause) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ATT REJ"); + mmctx2msgid(msg, mm); + return _tx_gmm_att_rej(msg, gmm_cause, mm); +} + +/* Chapter 9.4.6.2 Detach accept */ +static int _tx_detach_ack(struct msgb *msg, uint8_t force_stby, + struct sgsn_mm_ctx *mm) +{ + struct gsm48_hdr *gh; + + /* MMCTX might be NULL! */ + + DEBUGP(DMM, "<- GPRS MM DETACH ACC (force-standby: %d)\n", force_stby); + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_DETACH_ACKED]); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_DETACH_ACK; + gh->data[0] = force_stby; + + return gsm48_gmm_sendmsg(msg, 0, mm, true); +} + +static int gsm48_tx_gmm_det_ack(struct sgsn_mm_ctx *mm, uint8_t force_stby) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DET ACK"); + + mmctx2msgid(msg, mm); + return _tx_detach_ack(msg, force_stby, mm); +} + +static int gsm48_tx_gmm_det_ack_oldmsg(struct msgb *oldmsg, uint8_t force_stby) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DET ACK OLD"); + + gmm_copy_id(msg, oldmsg); + return _tx_detach_ack(msg, force_stby, NULL); +} + +/* Transmit Chapter 9.4.12 Identity Request */ +int gsm48_tx_gmm_id_req(struct sgsn_mm_ctx *mm, uint8_t id_type) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ID REQ"); + struct gsm48_hdr *gh; + + LOGMMCTXP(LOGL_DEBUG, mm, "<- GPRS IDENTITY REQUEST: mi_type=%s\n", + gsm48_mi_type_name(id_type)); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_ID_REQ; + /* 10.5.5.9 ID type 2 + identity type and 10.5.5.7 'force to standby' IE */ + gh->data[0] = id_type & 0xf; + + return gsm48_gmm_sendmsg(msg, 1, mm, false); +} + +/* determine if the MS/UE supports R99 or later */ +static bool mmctx_is_r99(const struct sgsn_mm_ctx *mm) +{ + if (mm->ms_network_capa.len < 1) + return false; + if (mm->ms_network_capa.buf[0] & 0x01) + return true; + return false; +} + +/* 3GPP TS 24.008 § 9.4.9: Authentication and Ciphering Request */ +int gsm48_tx_gmm_auth_ciph_req(struct sgsn_mm_ctx *mm, + const struct osmo_auth_vector *vec, + uint8_t key_seq, bool force_standby) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 AUTH CIPH REQ"); + struct gsm48_hdr *gh; + struct gsm48_auth_ciph_req *acreq; + uint8_t *m_rand, *m_cksn, rbyte; + int rc; + + LOGMMCTXP(LOGL_INFO, mm, "<- GPRS AUTH AND CIPHERING REQ (rand = %s," + " mmctx_is_r99=%d, vec->auth_types=0x%x", + osmo_hexdump(vec->rand, sizeof(vec->rand)), + mmctx_is_r99(mm), vec->auth_types); + if (mmctx_is_r99(mm) && vec + && (vec->auth_types & OSMO_AUTH_TYPE_UMTS)) { + LOGPC(DMM, LOGL_INFO, ", autn = %s)\n", + osmo_hexdump(vec->autn, sizeof(vec->autn))); + } else + LOGPC(DMM, LOGL_INFO, ")\n"); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_AUTH_CIPH_REQ; + + acreq = (struct gsm48_auth_ciph_req *) msgb_put(msg, sizeof(*acreq)); + acreq->ciph_alg = mm->ciph_algo & 0xf; + /* § 10.5.5.10: */ + acreq->imeisv_req = 0x1; + /* § 10.5.5.7: */ + acreq->force_stby = force_standby; + /* 3GPP TS 24.008 § 10.5.5.19: */ + rc = osmo_get_rand_id(&rbyte, 1); + if (rc < 0) { + LOGMMCTXP(LOGL_ERROR, mm, "osmo_get_rand_id() failed for A&C ref: %s\n", strerror(-rc)); + return rc; + } + + acreq->ac_ref_nr = rbyte; + mm->ac_ref_nr_used = acreq->ac_ref_nr; + + /* Only if authentication is requested we need to set RAND + CKSN */ + if (vec) { + m_rand = msgb_put(msg, sizeof(vec->rand) + 1); + m_rand[0] = GSM48_IE_GMM_AUTH_RAND; + memcpy(m_rand + 1, vec->rand, sizeof(vec->rand)); + + /* § 10.5.1.2: */ + m_cksn = msgb_put(msg, 1); + m_cksn[0] = (GSM48_IE_GMM_CIPH_CKSN << 4) | (key_seq & 0x07); + + /* A Release99 or higher MS/UE must be able to handle + * the optional AUTN IE. If a classic GSM SIM is + * inserted, it will simply ignore AUTN and just use + * RAND */ + if (mmctx_is_r99(mm) && + (vec->auth_types & OSMO_AUTH_TYPE_UMTS)) { + msgb_tlv_put(msg, GSM48_IE_GMM_AUTN, + sizeof(vec->autn), vec->autn); + } + } + + return gsm48_gmm_sendmsg(msg, 1, mm, false); +} + +/* 3GPP TS 24.008 § 9.4.11: Authentication and Ciphering Reject */ +static int gsm48_tx_gmm_auth_ciph_rej(struct sgsn_mm_ctx *mm) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 AUTH CIPH REJ"); + struct gsm48_hdr *gh; + + LOGMMCTXP(LOGL_NOTICE, mm, "<- GPRS AUTH AND CIPH REJECT\n"); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_AUTH_CIPH_REJ; + + return gsm48_gmm_sendmsg(msg, 0, mm, false); +} + +/* check if the received authentication response matches */ +static enum osmo_sub_auth_type check_auth_resp(struct sgsn_mm_ctx *ctx, + bool is_utran, + const struct osmo_auth_vector *vec, + const uint8_t *res, uint8_t res_len) +{ + const uint8_t *expect_res; + uint8_t expect_res_len; + enum osmo_sub_auth_type expect_type; + const char *expect_str; + + /* On UTRAN (3G) we always expect UMTS AKA. On GERAN (2G) we sent AUTN + * and expect UMTS AKA if there is R99 capability and our vector + * supports UMTS AKA, otherwise we expect GSM AKA. + * However, on GERAN, even if we sent a UMTS AKA Authentication Request, the MS may decide to + * instead reply with a GSM AKA SRES response. */ + if (is_utran + || (mmctx_is_r99(ctx) && (vec->auth_types & OSMO_AUTH_TYPE_UMTS) + && (res_len > sizeof(vec->sres)))) { + expect_type = OSMO_AUTH_TYPE_UMTS; + expect_str = "UMTS RES"; + expect_res = vec->res; + expect_res_len = vec->res_len; + } else { + expect_type = OSMO_AUTH_TYPE_GSM; + expect_str = "GSM SRES"; + expect_res = vec->sres; + expect_res_len = sizeof(vec->sres); + } + + if (!(vec->auth_types & expect_type)) { + LOGMMCTXP(LOGL_ERROR, ctx, "Auth error: auth vector does" + " not provide the expected auth type:" + " expected %s = 0x%x, auth_types are 0x%x\n", + expect_str, expect_type, vec->auth_types); + return OSMO_AUTH_TYPE_NONE; + } + + if (!res) + goto auth_mismatch; + + if (res_len != expect_res_len) + goto auth_mismatch; + + if (memcmp(res, expect_res, res_len) != 0) + goto auth_mismatch; + + /* Authorized! */ + return expect_type; + +auth_mismatch: + LOGMMCTXP(LOGL_ERROR, ctx, "Auth mismatch: expected %s = %s\n", + expect_str, osmo_hexdump_nospc(expect_res, expect_res_len)); + return OSMO_AUTH_TYPE_NONE; +} + +/* 3GPP TS 24.008 § 9.4.10: Authentication and Ciphering Response */ +static int gsm48_rx_gmm_auth_ciph_resp(struct sgsn_mm_ctx *ctx, + struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + struct gsm48_auth_ciph_resp *acr = (struct gsm48_auth_ciph_resp *)gh->data; + struct tlv_parsed tp; + struct gsm_auth_tuple *at; + const char *res_name = "(no response)"; + uint8_t res[16]; + uint8_t res_len; + int rc; + + LOGMMCTXP(LOGL_INFO, ctx, "-> GPRS AUTH AND CIPH RESPONSE\n"); + + if (ctx->auth_triplet.key_seq == GSM_KEY_SEQ_INVAL) { + LOGMMCTXP(LOGL_NOTICE, ctx, + "Unexpected Auth & Ciph Response (ignored)\n"); + return 0; + } + + if (acr->ac_ref_nr != ctx->ac_ref_nr_used) { + LOGMMCTXP(LOGL_NOTICE, ctx, "Reference mismatch for Auth & Ciph" + " Response: %u received, %u expected\n", + acr->ac_ref_nr, ctx->ac_ref_nr_used); + return 0; + } + + /* Stop T3360 */ + mmctx_timer_stop(ctx, 3360); + + tlv_parse(&tp, &gsm48_gmm_att_tlvdef, acr->data, + (msg->data + msg->len) - acr->data, 0, 0); + + if (!TLVP_PRESENT(&tp, GSM48_IE_GMM_AUTH_SRES) || + !TLVP_PRESENT(&tp, GSM48_IE_GMM_IMEISV) || + TLVP_LEN(&tp,GSM48_IE_GMM_AUTH_SRES) != 4) { + /* TODO: missing mandatory IE, return STATUS or REJ? */ + LOGMMCTXP(LOGL_ERROR, ctx, "Missing mandantory IE\n"); + return -EINVAL; + } + + /* Start with the good old 4-byte SRES */ + memcpy(res, TLVP_VAL(&tp, GSM48_IE_GMM_AUTH_SRES), 4); + res_len = 4; + res_name = "GSM SRES"; + + /* Append extended RES as part of UMTS AKA, if any */ + if (TLVP_PRESENT(&tp, GSM48_IE_GMM_AUTH_RES_EXT)) { + unsigned int l = TLVP_LEN(&tp, GSM48_IE_GMM_AUTH_RES_EXT); + if (l > sizeof(res)-4) + l = sizeof(res)-4; + memcpy(res+4, TLVP_VAL(&tp, GSM48_IE_GMM_AUTH_RES_EXT), l); + res_len += l; + res_name = "UMTS RES"; + } + + at = &ctx->auth_triplet; + + LOGMMCTXP(LOGL_DEBUG, ctx, "checking auth: received %s = %s\n", + res_name, osmo_hexdump(res, res_len)); + ctx->sec_ctx = check_auth_resp(ctx, false, &at->vec, res, res_len); + if (!sgsn_mm_ctx_is_authenticated(ctx)) { + rc = gsm48_tx_gmm_auth_ciph_rej(ctx); + mm_ctx_cleanup_free(ctx, "GPRS AUTH AND CIPH REJECT"); + return rc; + } + + if (ctx->ran_type == MM_CTX_T_UTRAN_Iu) + ctx->iu.new_key = 1; + + /* FIXME: enable LLC cipheirng */ + + /* Check if we can let the mobile station enter */ + return osmo_fsm_inst_dispatch(ctx->gmm_att_req.fsm, E_AUTH_RESP_RECV_SUCCESS, NULL); +} + +/* 3GPP TS 24.008 § 9.4.10: Authentication and Ciphering Failure */ +static int gsm48_rx_gmm_auth_ciph_fail(struct sgsn_mm_ctx *ctx, + struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + struct tlv_parsed tp; + const uint8_t gmm_cause = gh->data[0]; + const uint8_t *auts; + int rc; + + LOGMMCTXP(LOGL_INFO, ctx, "-> GPRS AUTH AND CIPH FAILURE (cause = %s)\n", + get_value_string(gsm48_gmm_cause_names, gmm_cause)); + + tlv_parse(&tp, &gsm48_gmm_att_tlvdef, gh->data+1, msg->len - 1, 0, 0); + + /* Only if GMM cause is present and the AUTS is provided, we can + * start re-sync procedure */ + if (gmm_cause == GMM_CAUSE_SYNC_FAIL && + TLVP_PRESENT(&tp, GSM48_IE_GMM_AUTH_FAIL_PAR)) { + if (TLVP_LEN(&tp, GSM48_IE_GMM_AUTH_FAIL_PAR) != 14) { + LOGMMCTXP(LOGL_ERROR, ctx, "AUTS IE has wrong size:" + " expected %d, got %u\n", 14, + TLVP_LEN(&tp, GSM48_IE_GMM_AUTH_FAIL_PAR)); + return -EINVAL; + } + auts = TLVP_VAL(&tp, GSM48_IE_GMM_AUTH_FAIL_PAR); + + LOGMMCTXP(LOGL_INFO, ctx, + "R99 AUTHENTICATION SYNCH (AUTS = %s)\n", + osmo_hexdump_nospc(auts, 14)); + + /* make sure we'll refresh the auth_triplet in + * sgsn_auth_update() */ + ctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL; + + /* make sure we'll retry authentication after the resync */ + ctx->auth_state = SGSN_AUTH_UMTS_RESYNC; + + /* Send AUTS to HLR and wait for new Auth Info Result */ + rc = gprs_subscr_request_auth_info(ctx, auts, + ctx->auth_triplet.vec.rand); + if (!rc) + return osmo_fsm_inst_dispatch(ctx->gmm_att_req.fsm, E_AUTH_RESP_RECV_RESYNC, NULL); + /* on error, fall through to send a reject */ + LOGMMCTXP(LOGL_ERROR, ctx, + "Sending AUTS to HLR failed (rc = %d)\n", rc); + } + + LOGMMCTXP(LOGL_NOTICE, ctx, "Authentication failed\n"); + rc = gsm48_tx_gmm_auth_ciph_rej(ctx); + mm_ctx_cleanup_free(ctx, "GPRS AUTH FAILURE"); + return rc; +} + +void extract_subscr_msisdn(struct sgsn_mm_ctx *ctx) +{ + struct gsm_mncc_number called; + uint8_t msisdn[sizeof(ctx->subscr->sgsn_data->msisdn) + 1]; + + /* Convert MSISDN from encoded to string.. */ + if (!ctx->subscr) + return; + + if (ctx->subscr->sgsn_data->msisdn_len < 1) + return; + + /* prepare the data for the decoder */ + memset(&called, 0, sizeof(called)); + msisdn[0] = ctx->subscr->sgsn_data->msisdn_len; + memcpy(&msisdn[1], ctx->subscr->sgsn_data->msisdn, + ctx->subscr->sgsn_data->msisdn_len); + + /* decode the string now */ + gsm48_decode_called(&called, msisdn); + + /* Prepend a '+' for international numbers */ + if (called.plan == 1 && called.type == 1) { + ctx->msisdn[0] = '+'; + osmo_strlcpy(&ctx->msisdn[1], called.number, + sizeof(ctx->msisdn)); + } else { + osmo_strlcpy(ctx->msisdn, called.number, sizeof(ctx->msisdn)); + } +} + +void extract_subscr_hlr(struct sgsn_mm_ctx *ctx) +{ + struct gsm_mncc_number called; + uint8_t hlr_number[sizeof(ctx->subscr->sgsn_data->hlr) + 1]; + + if (!ctx->subscr) + return; + + if (ctx->subscr->sgsn_data->hlr_len < 1) + return; + + /* prepare the data for the decoder */ + memset(&called, 0, sizeof(called)); + hlr_number[0] = ctx->subscr->sgsn_data->hlr_len; + memcpy(&hlr_number[1], ctx->subscr->sgsn_data->hlr, + ctx->subscr->sgsn_data->hlr_len); + + /* decode the string now */ + gsm48_decode_called(&called, hlr_number); + + if (called.plan != 1) { + LOGMMCTXP(LOGL_ERROR, ctx, + "Numbering plan(%d) not allowed\n", + called.plan); + return; + } + + if (called.type != 1) { + LOGMMCTXP(LOGL_ERROR, ctx, + "Numbering type(%d) not allowed\n", + called.type); + return; + } + + osmo_strlcpy(ctx->hlr, called.number, sizeof(ctx->hlr)); +} + +#ifdef BUILD_IU +/* Chapter 9.4.21: Service accept */ +static int gsm48_tx_gmm_service_ack(struct sgsn_mm_ctx *mm) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 SERVICE ACK"); + struct gsm48_hdr *gh; + + LOGMMCTXP(LOGL_INFO, mm, "<- GPRS SERVICE ACCEPT (P-TMSI=0x%08x)\n", mm->p_tmsi); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_SERVICE_ACK; + + /* Optional: PDP context status */ + /* Optional: MBMS context status */ + + return gsm48_gmm_sendmsg(msg, 0, mm, false); +} +#endif + +/* Chapter 9.4.22: Service reject */ +static int _tx_gmm_service_rej(struct msgb *msg, uint8_t gmm_cause, + const struct sgsn_mm_ctx *mm) +{ + struct gsm48_hdr *gh; + + LOGMMCTXP(LOGL_NOTICE, mm, "<- GPRS SERVICE REJECT: %s\n", + get_value_string(gsm48_gmm_cause_names, gmm_cause)); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_SERVICE_REJ; + gh->data[0] = gmm_cause; + + return gsm48_gmm_sendmsg(msg, 0, NULL, true); +} +static int gsm48_tx_gmm_service_rej_oldmsg(const struct msgb *old_msg, + uint8_t gmm_cause) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 SERVICE REJ OLD"); + gmm_copy_id(msg, old_msg); + return _tx_gmm_service_rej(msg, gmm_cause, NULL); +} +#if 0 +-- currently unused -- +static int gsm48_tx_gmm_service_rej(struct sgsn_mm_ctx *mm, + uint8_t gmm_cause) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 SERVICE REJ"); + mmctx2msgid(msg, mm); + return _tx_gmm_service_rej(msg, gmm_cause, mm); +} +#endif + +static int gsm48_tx_gmm_ra_upd_ack(struct sgsn_mm_ctx *mm); + +/* Check if we can already authorize a subscriber */ +int gsm48_gmm_authorize(struct sgsn_mm_ctx *ctx) +{ +#ifdef BUILD_IU + int rc; +#endif +#ifndef PTMSI_ALLOC + struct sgsn_signal_data sig_data; +#endif + + /* Request IMSI and IMEI from the MS if they are unknown */ + if (!strlen(ctx->imei)) { + ctx->t3370_id_type = GSM_MI_TYPE_IMEI; + mmctx_timer_start(ctx, 3370); + return gsm48_tx_gmm_id_req(ctx, GSM_MI_TYPE_IMEI); + } + if (!strlen(ctx->imsi)) { + ctx->t3370_id_type = GSM_MI_TYPE_IMSI; + mmctx_timer_start(ctx, 3370); + return gsm48_tx_gmm_id_req(ctx, GSM_MI_TYPE_IMSI); + } + + /* All information required for authentication is available */ + ctx->t3370_id_type = GSM_MI_TYPE_NONE; + + if (ctx->auth_state == SGSN_AUTH_UNKNOWN) { + /* Request authorization, this leads to a call to + * sgsn_auth_update which in turn calls + * gsm0408_gprs_access_granted or gsm0408_gprs_access_denied */ + + sgsn_auth_request(ctx); + /* Note that gsm48_gmm_authorize can be called recursively via + * sgsn_auth_request iff ctx->auth_info changes to AUTH_ACCEPTED + */ + return 0; + } + + if (ctx->auth_state == SGSN_AUTH_AUTHENTICATE + && !sgsn_mm_ctx_is_authenticated(ctx)) { + struct gsm_auth_tuple *at = &ctx->auth_triplet; + + mmctx_timer_start(ctx, 3360); + return gsm48_tx_gmm_auth_ciph_req(ctx, &at->vec, at->key_seq, + false); + } + + if (ctx->auth_state == SGSN_AUTH_AUTHENTICATE && sgsn_mm_ctx_is_authenticated(ctx) && + ctx->auth_triplet.key_seq != GSM_KEY_SEQ_INVAL) { + /* Check again for authorization */ + sgsn_auth_request(ctx); + return 0; + } + + if (ctx->auth_state != SGSN_AUTH_ACCEPTED) { + LOGMMCTXP(LOGL_NOTICE, ctx, + "authorization is denied, aborting procedure\n"); + return -EACCES; + } + + /* The MS is authorized */ +#ifdef BUILD_IU + if (ctx->ran_type == MM_CTX_T_UTRAN_Iu && !ctx->iu.ue_ctx->integrity_active) { + rc = ranap_iu_tx_sec_mode_cmd(ctx->iu.ue_ctx, &ctx->auth_triplet.vec, 0, ctx->iu.new_key); + ctx->iu.new_key = 0; + return rc; + } +#endif + + switch (ctx->pending_req) { + case 0: + LOGMMCTXP(LOGL_INFO, ctx, + "no pending request, authorization completed\n"); + break; + case GSM48_MT_GMM_ATTACH_REQ: + ctx->pending_req = 0; + + extract_subscr_msisdn(ctx); + extract_subscr_hlr(ctx); +#ifdef PTMSI_ALLOC + /* Start T3350 and re-transmit up to 5 times until ATTACH COMPLETE */ + mmctx_timer_start(ctx, 3350); + ctx->t3350_mode = GMM_T3350_MODE_ATT; +#else + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.mm = mmctx; + osmo_signal_dispatch(SS_SGSN, S_SGSN_ATTACH, &sig_data); + ctx->gmm_state = GMM_REGISTERED_NORMAL; +#endif + + return gsm48_tx_gmm_att_ack(ctx); +#ifdef BUILD_IU + case GSM48_MT_GMM_SERVICE_REQ: + ctx->pending_req = 0; + osmo_fsm_inst_dispatch(ctx->iu.mm_state_fsm, E_PMM_PS_ATTACH, NULL); + rc = gsm48_tx_gmm_service_ack(ctx); + + if (ctx->iu.service.type != GPRS_SERVICE_T_SIGNALLING) + activate_pdp_rabs(ctx); + + return rc; +#endif + case GSM48_MT_GMM_RA_UPD_REQ: + ctx->pending_req = 0; + /* Send RA UPDATE ACCEPT */ + return gsm48_tx_gmm_ra_upd_ack(ctx); + + default: + LOGMMCTXP(LOGL_ERROR, ctx, + "only Attach Request is supported yet, " + "got request type %u\n", ctx->pending_req); + break; + } + + return 0; +} + +void gsm0408_gprs_authenticate(struct sgsn_mm_ctx *ctx) +{ + ctx->sec_ctx = OSMO_AUTH_TYPE_NONE; + + if (ctx->gmm_att_req.fsm->state != ST_INIT) + osmo_fsm_inst_dispatch(ctx->gmm_att_req.fsm, E_VLR_ANSWERED, (void *) 0); + else + gsm48_gmm_authorize(ctx); +} + +void gsm0408_gprs_access_granted(struct sgsn_mm_ctx *ctx) +{ + switch (ctx->gmm_state) { + case GMM_COMMON_PROC_INIT: + LOGMMCTXP(LOGL_NOTICE, ctx, + "Authorized, continuing procedure, IMSI=%s\n", + ctx->imsi); + /* Continue with the authorization */ + if (ctx->gmm_att_req.fsm->state != ST_INIT) + osmo_fsm_inst_dispatch(ctx->gmm_att_req.fsm, E_VLR_ANSWERED, (void *) 0); + break; + default: + LOGMMCTXP(LOGL_INFO, ctx, + "Authorized, ignored, IMSI=%s\n", + ctx->imsi); + } +} + +void gsm0408_gprs_access_denied(struct sgsn_mm_ctx *ctx, int gmm_cause) +{ + if (gmm_cause == SGSN_ERROR_CAUSE_NONE) + gmm_cause = GMM_CAUSE_GPRS_NOTALLOWED; + + switch (ctx->gmm_state) { + case GMM_COMMON_PROC_INIT: + LOGMMCTXP(LOGL_NOTICE, ctx, + "Not authorized, rejecting ATTACH REQUEST " + "with cause '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gmm_cause), + gmm_cause); + if (ctx->gmm_att_req.fsm->state != ST_INIT) + osmo_fsm_inst_dispatch(ctx->gmm_att_req.fsm, E_REJECT, (void *) (long) gmm_cause); + break; + case GMM_REGISTERED_NORMAL: + case GMM_REGISTERED_SUSPENDED: + LOGMMCTXP(LOGL_NOTICE, ctx, + "Authorization lost, detaching " + "with cause '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gmm_cause), + gmm_cause); + gsm48_tx_gmm_detach_req( + ctx, GPRS_DET_T_MT_IMSI, gmm_cause); + + mm_ctx_cleanup_free(ctx, "auth lost"); + break; + default: + LOGMMCTXP(LOGL_INFO, ctx, + "Authorization lost, cause is '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gmm_cause), + gmm_cause); + mm_ctx_cleanup_free(ctx, "auth lost"); + } +} + +void gsm0408_gprs_access_cancelled(struct sgsn_mm_ctx *ctx, int gmm_cause) +{ + if (gmm_cause != SGSN_ERROR_CAUSE_NONE) { + LOGMMCTXP(LOGL_INFO, ctx, + "Cancelled with cause '%s' (%d), deleting context\n", + get_value_string(gsm48_gmm_cause_names, gmm_cause), + gmm_cause); + gsm0408_gprs_access_denied(ctx, gmm_cause); + return; + } + + LOGMMCTXP(LOGL_INFO, ctx, "Cancelled, deleting context silently\n"); + mm_ctx_cleanup_free(ctx, "access cancelled"); +} + +/* Parse Chapter 9.4.13 Identity Response */ +static int gsm48_rx_gmm_id_resp(struct sgsn_mm_ctx *ctx, struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK; + long mi_typel = mi_type; + char mi_string[GSM48_MI_SIZE]; + + gsm48_mi_to_string(mi_string, sizeof(mi_string), &gh->data[1], gh->data[0]); + if (!ctx) { + DEBUGP(DMM, "from unknown TLLI 0x%08x?!? This should not happen\n", msgb_tlli(msg)); + return -EINVAL; + } + + LOGMMCTXP(LOGL_DEBUG, ctx, "-> GMM IDENTITY RESPONSE: MI(%s)=%s\n", + gsm48_mi_type_name(mi_type), mi_string); + + if (ctx->t3370_id_type == GSM_MI_TYPE_NONE) { + LOGMMCTXP(LOGL_NOTICE, ctx, + "Got unexpected IDENTITY RESPONSE: MI(%s)=%s, " + "ignoring message\n", + gsm48_mi_type_name(mi_type), mi_string); + return -EINVAL; + } + + if (mi_type == ctx->t3370_id_type) + mmctx_timer_stop(ctx, 3370); + + switch (mi_type) { + case GSM_MI_TYPE_IMSI: + /* we already have a mm context with current TLLI, but no + * P-TMSI / IMSI yet. What we now need to do is to fill + * this initial context with data from the HLR */ + if (strlen(ctx->imsi) == 0) { + /* Check if we already have a MM context for this IMSI */ + struct sgsn_mm_ctx *ictx; + ictx = sgsn_mm_ctx_by_imsi(mi_string); + if (ictx) { + /* Handle it like in gsm48_rx_gmm_det_req, + * except that no messages are sent to the BSS */ + + LOGMMCTXP(LOGL_NOTICE, ctx, "Deleting old MM Context for same IMSI " + "p_tmsi_old=0x%08x\n", + ictx->p_tmsi); + + mm_ctx_cleanup_free(ictx, "GPRS IMSI re-use"); + } + } + osmo_strlcpy(ctx->imsi, mi_string, sizeof(ctx->imsi)); + break; + case GSM_MI_TYPE_IMEI: + osmo_strlcpy(ctx->imei, mi_string, sizeof(ctx->imei)); + break; + case GSM_MI_TYPE_IMEISV: + break; + } + + /* Check if we can let the mobile station enter */ + return osmo_fsm_inst_dispatch(ctx->gmm_att_req.fsm, E_IDEN_RESP_RECV, (void *)mi_typel); +} + +/* Allocate a new P-TMSI and change context state */ +static inline void ptmsi_update(struct sgsn_mm_ctx *ctx) +{ + uint32_t ptmsi; + /* Don't change the P-TMSI if a P-TMSI re-assignment is under way */ + if (ctx->gmm_state != GMM_COMMON_PROC_INIT) { + ptmsi = sgsn_alloc_ptmsi(); + if (ptmsi != GSM_RESERVED_TMSI) { + ctx->p_tmsi_old = ctx->p_tmsi; + ctx->p_tmsi = ptmsi; + } else + LOGMMCTXP(LOGL_ERROR, ctx, "P-TMSI allocation failure: using old one.\n"); + } + ctx->gmm_state = GMM_COMMON_PROC_INIT; +} + +/* 3GPP TS 24.008 § 9.4.1 Attach request */ +static int gsm48_rx_gmm_att_req(struct sgsn_mm_ctx *ctx, struct msgb *msg, + struct gprs_llc_llme *llme) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t *cur = gh->data, *msnc, *mi, *ms_ra_acc_cap; + uint8_t msnc_len, att_type, mi_len, mi_type, ms_ra_acc_cap_len; + uint16_t drx_par; + uint32_t tmsi; + char mi_string[GSM48_MI_SIZE]; + struct gprs_ra_id ra_id; + uint16_t cid = 0; + enum gsm48_gmm_cause reject_cause; + int rc; + + LOGMMCTXP(LOGL_INFO, ctx, "-> GMM ATTACH REQUEST "); + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_ATTACH_REQUEST]); + + /* As per TS 04.08 Chapter 4.7.1.4, the attach request arrives either + * with a foreign TLLI (P-TMSI that was allocated to the MS before), + * or with random TLLI. */ + + if (!MSG_IU_UE_CTX(msg)) { + /* Gb mode */ + cid = bssgp_parse_cell_id(&ra_id, msgb_bcid(msg)); + } else { +#ifdef BUILD_IU + ra_id = MSG_IU_UE_CTX(msg)->ra_id; +#else + LOGMMCTXP(LOGL_ERROR, ctx, "Cannot handle Iu Attach Request, built without Iu support\n"); + return -ENOTSUP; +#endif + } + + /* MS network capability 10.5.5.12 */ + msnc_len = *cur++; + msnc = cur; + if (msnc_len > sizeof(ctx->ms_network_capa.buf)) + goto err_inval; + cur += msnc_len; + + /* TODO: In iu mode - handle follow-on request. + * The follow-on request can be signaled in an Attach Request on IuPS. + * This means the MS/UE asks to keep the PS connection open for further requests + * after the Attach Request succeed. + * The SGSN can decide if it close the connection or not. Both are spec conform. */ + + /* aTTACH Type 10.5.5.2 */ + att_type = *cur++ & 0x07; + + /* DRX parameter 10.5.5.6 */ + drx_par = *cur++ << 8; + drx_par |= *cur++; + + /* Mobile Identity (P-TMSI or IMSI) 10.5.1.4 */ + mi_len = *cur++; + mi = cur; + if (mi_len > 8) + goto err_inval; + mi_type = *mi & GSM_MI_TYPE_MASK; + cur += mi_len; + + gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len); + + DEBUGPC(DMM, "MI(%s) type=\"%s\" ", mi_string, + get_value_string(gprs_att_t_strs, att_type)); + + /* Old routing area identification 10.5.5.15. Skip it */ + cur += 6; + + /* MS Radio Access Capability 10.5.5.12a */ + ms_ra_acc_cap_len = *cur++; + ms_ra_acc_cap = cur; + if (ms_ra_acc_cap_len > sizeof(ctx->ms_radio_access_capa.buf)) + goto err_inval; + cur += ms_ra_acc_cap_len; + + LOGPC(DMM, LOGL_INFO, "\n"); + + /* Optional: Old P-TMSI Signature, Requested READY timer, TMSI Status */ + + switch (mi_type) { + case GSM_MI_TYPE_IMSI: + /* Try to find MM context based on IMSI */ + if (!ctx) + ctx = sgsn_mm_ctx_by_imsi(mi_string); + if (!ctx) { + if (MSG_IU_UE_CTX(msg)) + ctx = sgsn_mm_ctx_alloc_iu(MSG_IU_UE_CTX(msg)); + else + ctx = sgsn_mm_ctx_alloc_gb(0, &ra_id); + if (!ctx) { + reject_cause = GMM_CAUSE_NET_FAIL; + goto rejected; + } + osmo_strlcpy(ctx->imsi, mi_string, sizeof(ctx->imsi)); + } + break; + case GSM_MI_TYPE_TMSI: + memcpy(&tmsi, mi+1, 4); + tmsi = ntohl(tmsi); + /* Try to find MM context based on P-TMSI */ + if (!ctx) + ctx = sgsn_mm_ctx_by_ptmsi(tmsi); + if (!ctx) { + /* Allocate a context as most of our code expects one. + * Context will not have an IMSI ultil ID RESP is received */ + if (MSG_IU_UE_CTX(msg)) + ctx = sgsn_mm_ctx_alloc_iu(MSG_IU_UE_CTX(msg)); + else + ctx = sgsn_mm_ctx_alloc_gb(msgb_tlli(msg), &ra_id); + if (!ctx) { + reject_cause = GMM_CAUSE_NET_FAIL; + goto rejected; + } + ctx->p_tmsi = tmsi; + } + break; + default: + LOGMMCTXP(LOGL_NOTICE, ctx, "Rejecting ATTACH REQUEST with " + "MI type %s\n", gsm48_mi_type_name(mi_type)); + reject_cause = GMM_CAUSE_MS_ID_NOT_DERIVED; + goto rejected; + } + + if (ctx->ran_type == MM_CTX_T_GERAN_Gb) { + ctx->gb.tlli = msgb_tlli(msg); + ctx->gb.llme = llme; + } + msgid2mmctx(ctx, msg); + /* Update MM Context with currient RA and Cell ID */ + ctx->ra = ra_id; + if (ctx->ran_type == MM_CTX_T_GERAN_Gb) + ctx->gb.cell_id = cid; + + /* Update MM Context with other data */ + ctx->drx_parms = drx_par; + ctx->ms_radio_access_capa.len = ms_ra_acc_cap_len; + memcpy(ctx->ms_radio_access_capa.buf, ms_ra_acc_cap, + ctx->ms_radio_access_capa.len); + ctx->ms_network_capa.len = msnc_len; + memcpy(ctx->ms_network_capa.buf, msnc, msnc_len); + if (!gprs_ms_net_cap_gea_supported(ctx->ms_network_capa.buf, msnc_len, + ctx->ciph_algo)) { + reject_cause = GMM_CAUSE_PROTO_ERR_UNSPEC; + LOGMMCTXP(LOGL_NOTICE, ctx, "Rejecting ATTACH REQUEST with MI " + "type %s because MS do not support required %s " + "encryption\n", gsm48_mi_type_name(mi_type), + get_value_string(gprs_cipher_names,ctx->ciph_algo)); + goto rejected; + } +#ifdef PTMSI_ALLOC + /* Allocate a new P-TMSI (+ P-TMSI signature) and update TLLI */ + ptmsi_update(ctx); +#endif + + if (ctx->ran_type == MM_CTX_T_GERAN_Gb) { + /* Even if there is no P-TMSI allocated, the MS will + * switch from foreign TLLI to local TLLI */ + ctx->gb.tlli_new = gprs_tmsi2tlli(ctx->p_tmsi, TLLI_LOCAL); + + /* Inform LLC layer about new TLLI but keep old active */ + if (sgsn_mm_ctx_is_authenticated(ctx)) + gprs_llme_copy_key(ctx, ctx->gb.llme); + + gprs_llgmm_assign(ctx->gb.llme, ctx->gb.tlli, ctx->gb.tlli_new); + } + + osmo_fsm_inst_dispatch(ctx->gmm_att_req.fsm, E_ATTACH_REQ_RECV, msg); + return 0; + +err_inval: + LOGPC(DMM, LOGL_INFO, "\n"); + reject_cause = GMM_CAUSE_SEM_INCORR_MSG; + +rejected: + /* Send ATTACH REJECT */ + LOGMMCTXP(LOGL_NOTICE, ctx, + "Rejecting Attach Request with cause '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, reject_cause), reject_cause); + rc = gsm48_tx_gmm_att_rej_oldmsg(msg, reject_cause); + if (ctx) + mm_ctx_cleanup_free(ctx, "GPRS ATTACH REJ"); + else if (llme) + gprs_llgmm_unassign(llme); + + return rc; + +} + + +/* Checks if two attach request contain the IEs and IE values + * return 0 if equal + * return -1 if error + * return 1 if unequal + * + * Only do a simple memcmp for now. + */ +int gprs_gmm_attach_req_ies(struct msgb *a, struct msgb *b) +{ + struct gsm48_hdr *gh_a = (struct gsm48_hdr *) msgb_gmmh(a); + struct gsm48_hdr *gh_b = (struct gsm48_hdr *) msgb_gmmh(b); + +#define GMM_ATTACH_REQ_LEN 26 + + /* there is the LLC FCS behind */ + if (msgb_l3len(a) < GMM_ATTACH_REQ_LEN || msgb_l3len(b) < GMM_ATTACH_REQ_LEN) + return -1; + + return !!memcmp(gh_a, gh_b, GMM_ATTACH_REQ_LEN); +} + +/* 3GPP TS 24.008 § 4.7.4.1 / 9.4.5.2 MO Detach request */ +static int gsm48_rx_gmm_det_req(struct sgsn_mm_ctx *ctx, struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t detach_type, power_off; + int rc = 0; + + detach_type = gh->data[0] & 0x7; + power_off = gh->data[0] & 0x8; + + /* FIXME: In 24.008 there is an optional P-TMSI and P-TMSI signature IE */ + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_DETACH_REQUEST]); + LOGMMCTXP(LOGL_INFO, ctx, "-> GMM DETACH REQUEST TLLI=0x%08x type=%s %s\n", + msgb_tlli(msg), get_value_string(gprs_det_t_mo_strs, detach_type), + power_off ? "Power-off" : ""); + + /* Only send the Detach Accept (MO) if power off isn't indicated, + * see 04.08, 4.7.4.1.2/3 for details */ + if (!power_off) { + /* force_stby = 0 */ + if (ctx) + rc = gsm48_tx_gmm_det_ack(ctx, 0); + else + rc = gsm48_tx_gmm_det_ack_oldmsg(msg, 0); + } + + if (ctx) { + struct sgsn_signal_data sig_data; + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.mm = ctx; + osmo_signal_dispatch(SS_SGSN, S_SGSN_DETACH, &sig_data); + mm_ctx_cleanup_free(ctx, "GPRS DETACH REQUEST"); + } + + return rc; +} + +/* Chapter 9.4.15: Routing area update accept */ +static int gsm48_tx_gmm_ra_upd_ack(struct sgsn_mm_ctx *mm) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 UPD ACK"); + struct gsm48_hdr *gh; + struct gsm48_ra_upd_ack *rua; + uint8_t *mid; + unsigned long t; + + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_ROUTING_AREA_ACKED]); + LOGMMCTXP(LOGL_INFO, mm, "<- ROUTING AREA UPDATE ACCEPT\n"); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_RA_UPD_ACK; + + rua = (struct gsm48_ra_upd_ack *) msgb_put(msg, sizeof(*rua)); + rua->force_stby = 0; /* not indicated */ + rua->upd_result = 0; /* RA updated */ + t = osmo_tdef_get(sgsn->cfg.T_defs, 3312, OSMO_TDEF_S, -1); + rua->ra_upd_timer = gprs_secs_to_tmr_floor(t); + + gsm48_encode_ra(&rua->ra_id, &mm->ra); + +#if 0 + /* Optional: P-TMSI signature */ + msgb_v_put(msg, GSM48_IE_GMM_PTMSI_SIG); + ptsig = msgb_put(msg, 3); + ptsig[0] = mm->p_tmsi_sig >> 16; + ptsig[1] = mm->p_tmsi_sig >> 8; + ptsig[2] = mm->p_tmsi_sig & 0xff; +#endif + +#ifdef PTMSI_ALLOC + /* Optional: Allocated P-TMSI */ + mid = msgb_put(msg, GSM48_MID_TMSI_LEN); + gsm48_generate_mid_from_tmsi(mid, mm->p_tmsi); + mid[0] = GSM48_IE_GMM_ALLOC_PTMSI; +#endif + + /* Optional: Negotiated READY timer value */ + t = osmo_tdef_get(sgsn->cfg.T_defs, 3314, OSMO_TDEF_S, -1); + msgb_tv_put(msg, GSM48_IE_GMM_TIMER_READY, gprs_secs_to_tmr_floor(t)); + + /* Option: MS ID, ... */ + return gsm48_gmm_sendmsg(msg, 0, mm, true); +} + +/* Chapter 9.4.17: Routing area update reject */ +int gsm48_tx_gmm_ra_upd_rej(struct msgb *old_msg, uint8_t cause) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 RA UPD REJ"); + struct gsm48_hdr *gh; + + LOGP(DMM, LOGL_NOTICE, "<- ROUTING AREA UPDATE REJECT\n"); + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_ROUTING_AREA_REJECT]); + + gmm_copy_id(msg, old_msg); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 2); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_RA_UPD_REJ; + gh->data[0] = cause; + gh->data[1] = 0; /* ? */ + + /* Option: P-TMSI signature, allocated P-TMSI, MS ID, ... */ + return gsm48_gmm_sendmsg(msg, 0, NULL, false); +} + +static void process_ms_ctx_status(struct sgsn_mm_ctx *mmctx, + const uint8_t *pdp_status) +{ + struct sgsn_pdp_ctx *pdp, *pdp2; + /* 24.008 4.7.5.1.3: If the PDP context status information element is + * included in ROUTING AREA UPDATE REQUEST message, then the network + * shall deactivate all those PDP contexts locally (without peer to + * peer signalling between the MS and the network), which are not in SM + * state PDP-INACTIVE on network side but are indicated by the MS as + * being in state PDP-INACTIVE. */ + + llist_for_each_entry_safe(pdp, pdp2, &mmctx->pdp_list, list) { + if (pdp->nsapi < 8) { + if (!(pdp_status[0] & (1 << pdp->nsapi))) { + LOGMMCTXP(LOGL_NOTICE, mmctx, "Dropping PDP context for NSAPI=%u " + "due to PDP CTX STATUS IE= 0x%02x%02x\n", + pdp->nsapi, pdp_status[1], pdp_status[0]); + sgsn_delete_pdp_ctx(pdp); + } + } else { + if (!(pdp_status[1] & (1 << (pdp->nsapi - 8)))) { + LOGMMCTXP(LOGL_NOTICE, mmctx, "Dropping PDP context for NSAPI=%u " + "due to PDP CTX STATUS IE= 0x%02x%02x\n", + pdp->nsapi, pdp_status[1], pdp_status[0]); + sgsn_delete_pdp_ctx(pdp); + } + } + } +} + +/* 3GPP TS 24.008 § 4.7.13.4 Service request procedure not accepted by the + * network. Returns true if MS has active PDP contexts in pdp_status */ +bool pdp_status_has_active_nsapis(const uint8_t *pdp_status, const size_t pdp_status_len) +{ + size_t i; + + for (i = 0; i < pdp_status_len; i++) + if (pdp_status[i] != 0) + return true; + + return false; +} + +/* Chapter 9.4.14: Routing area update request */ +static int gsm48_rx_gmm_ra_upd_req(struct sgsn_mm_ctx *mmctx, struct msgb *msg, + struct gprs_llc_llme *llme) +{ +#ifndef PTMSI_ALLOC + struct sgsn_signal_data sig_data; +#endif + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t *cur = gh->data; + uint8_t ms_ra_acc_cap_len; + struct gprs_ra_id old_ra_id; + struct tlv_parsed tp; + uint8_t upd_type; + enum gsm48_gmm_cause reject_cause = GMM_CAUSE_PROTO_ERR_UNSPEC; + int rc; + + /* TODO: In iu mode - handle follow-on request. + * The follow-on request can be signaled in an Attach Request on IuPS. + * This means the MS/UE asks to keep the PS connection open for further requests + * after the Attach Request succeed. + * The SGSN can decide if it close the connection or not. Both are spec conform. */ + + /* Update Type 10.5.5.18 */ + upd_type = *cur++ & 0x07; + + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_ROUTING_AREA_REQUEST]); + LOGMMCTXP(LOGL_INFO, mmctx, "-> GMM RA UPDATE REQUEST type=\"%s\"\n", + get_value_string(gprs_upd_t_strs, upd_type)); + + /* Old routing area identification 10.5.5.15 */ + gsm48_parse_ra(&old_ra_id, cur); + cur += 6; + + /* MS Radio Access Capability 10.5.5.12a */ + ms_ra_acc_cap_len = *cur++; + if (ms_ra_acc_cap_len > 52) { + LOGMMCTXP(LOGL_ERROR, mmctx, + "Rejecting GMM RA Update Request: MS Radio Access Capability too long" + " (ms_ra_acc_cap_len = %u > 52)\n", ms_ra_acc_cap_len); + reject_cause = GMM_CAUSE_PROTO_ERR_UNSPEC; + goto rejected; + } + cur += ms_ra_acc_cap_len; + + /* Optional: Old P-TMSI Signature, Requested READY timer, TMSI Status, + * DRX parameter, MS network capability */ + tlv_parse(&tp, &gsm48_gmm_att_tlvdef, cur, + (msg->data + msg->len) - cur, 0, 0); + + switch (upd_type) { + case GPRS_UPD_T_RA_LA: + case GPRS_UPD_T_RA_LA_IMSI_ATT: + LOGMMCTXP(LOGL_NOTICE, mmctx, "Update type %i unsupported in Mode III, is your SI13 corrupt?\n", upd_type); + reject_cause = GMM_CAUSE_PROTO_ERR_UNSPEC; + goto rejected; + case GPRS_UPD_T_RA: + case GPRS_UPD_T_PERIODIC: + break; + } + + if (!mmctx) { + /* BSSGP doesn't give us an mmctx */ + + /* TODO: Check if there is an MM CTX with old_ra_id and + * the P-TMSI (if given, reguired for UMTS) or as last resort + * if the TLLI matches foreign_tlli (P-TMSI). Note that this + * is an optimization to avoid the RA reject (impl detached) + * below, which will cause a new attach cycle. */ + /* Look-up the MM context based on old RA-ID and TLLI */ + if (!MSG_IU_UE_CTX(msg)) { + /* Gb */ + mmctx = sgsn_mm_ctx_by_tlli_and_ptmsi(msgb_tlli(msg), &old_ra_id); + } else if (TLVP_PRESENT(&tp, GSM48_IE_GMM_ALLOC_PTMSI)) { +#ifdef BUILD_IU + /* In Iu mode search only for ptmsi */ + char mi_string[GSM48_MI_SIZE]; + uint8_t mi_len = TLVP_LEN(&tp, GSM48_IE_GMM_ALLOC_PTMSI); + const uint8_t *mi = TLVP_VAL(&tp, GSM48_IE_GMM_ALLOC_PTMSI); + uint8_t mi_type = *mi & GSM_MI_TYPE_MASK; + uint32_t tmsi; + + gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len); + + if (mi_type == GSM_MI_TYPE_TMSI) { + memcpy(&tmsi, mi+1, 4); + tmsi = ntohl(tmsi); + mmctx = sgsn_mm_ctx_by_ptmsi(tmsi); + } +#else + LOGIUP(MSG_IU_UE_CTX(msg), LOGL_ERROR, + "Rejecting GMM RA Update Request: No Iu support\n"); + goto rejected; +#endif + } + if (mmctx) { + LOGMMCTXP(LOGL_INFO, mmctx, + "Looked up by matching TLLI and P_TMSI. " + "BSSGP TLLI: %08x, P-TMSI: %08x (%08x), " + "TLLI: %08x (%08x), RA: %s\n", + msgb_tlli(msg), + mmctx->p_tmsi, mmctx->p_tmsi_old, + mmctx->gb.tlli, mmctx->gb.tlli_new, + osmo_rai_name(&mmctx->ra)); + + mmctx->gmm_state = GMM_COMMON_PROC_INIT; + } + } else if (!gprs_ra_id_equals(&mmctx->ra, &old_ra_id) || + mmctx->gmm_state == GMM_DEREGISTERED) + { + /* We cannot use the mmctx */ + LOGMMCTXP(LOGL_INFO, mmctx, + "The MM context cannot be used, RA: %s\n", + osmo_rai_name(&mmctx->ra)); + /* mmctx is set to NULL and gprs_llgmm_unassign(llme) will be + called below, let's make sure we don't keep dangling llme + pointers in mmctx (OS#3957). */ + if (mmctx->ran_type == MM_CTX_T_GERAN_Gb) + OSMO_ASSERT(mmctx->gb.llme == NULL); + mmctx = NULL; + } + + if (!mmctx) { + if (llme) { + /* send a XID reset to re-set all LLC sequence numbers + * in the MS */ + LOGGBP(llme, LOGL_NOTICE, "LLC XID RESET\n"); + gprs_llgmm_reset(llme); + } + /* The MS has to perform GPRS attach */ + /* Device is still IMSI attached for CS but initiate GPRS ATTACH, + * see GSM 04.08, 4.7.5.1.4 and G.6 */ + LOGGBIUP(llme, msg, LOGL_ERROR, "Rejecting GMM RA Update Request: MS should GMM Attach first\n"); + reject_cause = GMM_CAUSE_IMPL_DETACHED; + goto rejected; + } + + /* Store new BVCI/NSEI in MM context (FIXME: delay until we ack?) */ + msgid2mmctx(mmctx, msg); + /* Bump the statistics of received signalling msgs for this MM context */ + rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_SIG_IN]); + + /* Update the MM context with the new RA-ID */ + if (mmctx->ran_type == MM_CTX_T_GERAN_Gb) { + bssgp_parse_cell_id(&mmctx->ra, msgb_bcid(msg)); + /* Update the MM context with the new (i.e. foreign) TLLI */ + mmctx->gb.tlli = msgb_tlli(msg); + } + /* FIXME: Update the MM context with the MS radio acc capabilities */ + /* FIXME: Update the MM context with the MS network capabilities */ + + rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_RA_UPDATE]); + +#ifdef PTMSI_ALLOC + ptmsi_update(mmctx); + + /* Start T3350 and re-transmit up to 5 times until ATTACH COMPLETE */ + mmctx->t3350_mode = GMM_T3350_MODE_RAU; + mmctx_timer_start(mmctx, 3350); +#else + /* Make sure we are NORMAL (i.e. not SUSPENDED anymore) */ + mmctx->gmm_state = GMM_REGISTERED_NORMAL; + + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.mm = mmctx; + osmo_signal_dispatch(SS_SGSN, S_SGSN_UPDATE, &sig_data); +#endif + if (mmctx->ran_type == MM_CTX_T_GERAN_Gb) { + /* Even if there is no P-TMSI allocated, the MS will switch from + * foreign TLLI to local TLLI */ + mmctx->gb.tlli_new = gprs_tmsi2tlli(mmctx->p_tmsi, TLLI_LOCAL); + + /* Inform LLC layer about new TLLI but keep accepting the old one during Rx */ + gprs_llgmm_assign(mmctx->gb.llme, mmctx->gb.tlli, + mmctx->gb.tlli_new); + } + + /* Look at PDP Context Status IE and see if MS's view of + * activated/deactivated NSAPIs agrees with our view */ + if (TLVP_PRESENT(&tp, GSM48_IE_GMM_PDP_CTX_STATUS)) { + const uint8_t *pdp_status = TLVP_VAL(&tp, GSM48_IE_GMM_PDP_CTX_STATUS); + process_ms_ctx_status(mmctx, pdp_status); + } + + /* Send RA UPDATE ACCEPT. In Iu, the RA upd request can be called from + * a new Iu connection, so we might need to re-authenticate the + * connection as well as turn on integrity protection. */ + mmctx->pending_req = GSM48_MT_GMM_RA_UPD_REQ; + return gsm48_gmm_authorize(mmctx); + +rejected: + /* Send RA UPDATE REJECT */ + LOGMMCTXP(LOGL_NOTICE, mmctx, + "Rejecting RA Update Request with cause '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, reject_cause), reject_cause); + rc = gsm48_tx_gmm_ra_upd_rej(msg, reject_cause); + if (mmctx) + mm_ctx_cleanup_free(mmctx, "GPRS RA UPDATE REJ"); + else if (llme) + gprs_llgmm_unassign(llme); + + return rc; +} + +/* 3GPP TS 24.008 § 9.4.20 Service request. + * In Iu, a UE in PMM-IDLE mode can use GSM48_MT_GMM_SERVICE_REQ to switch back + * to PMM-CONNECTED mode. */ +static int gsm48_rx_gmm_service_req(struct sgsn_mm_ctx *ctx, struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t *cur = gh->data, *mi; + uint8_t service_type, mi_len, mi_type; + uint32_t tmsi; + struct tlv_parsed tp; + char mi_string[GSM48_MI_SIZE]; + enum gsm48_gmm_cause reject_cause; + int rc; + + LOGMMCTXP(LOGL_INFO, ctx, "-> GMM SERVICE REQUEST "); + + /* This message is only valid in Iu mode */ + if (!MSG_IU_UE_CTX(msg)) { + LOGPC(DMM, LOGL_INFO, "Invalid if not in Iu mode\n"); + return -1; + } + + /* Skip Ciphering key sequence number 10.5.1.2 */ + /* uint8_t ciph_seq_nr = *cur & 0x07; */ + + /* Service type 10.5.5.20 */ + service_type = (*cur++ >> 4) & 0x07; + + /* Mobile Identity (P-TMSI or IMSI) 10.5.1.4 */ + mi_len = *cur++; + mi = cur; + if (mi_len > 8) + goto err_inval; + mi_type = *mi & GSM_MI_TYPE_MASK; + cur += mi_len; + + gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len); + + DEBUGPC(DMM, "MI(%s) type=\"%s\" ", mi_string, + get_value_string(gprs_service_t_strs, service_type)); + + LOGPC(DMM, LOGL_INFO, "\n"); + + /* Optional: PDP context status, MBMS context status, Uplink data status, Device properties */ + tlv_parse(&tp, &gsm48_gmm_att_tlvdef, cur, (msg->data + msg->len) - cur, 0, 0); + + switch (mi_type) { + case GSM_MI_TYPE_IMSI: + /* Try to find MM context based on IMSI */ + if (!ctx) + ctx = sgsn_mm_ctx_by_imsi(mi_string); + if (!ctx) { + /* FIXME: We need to have a context for service request? */ + reject_cause = GMM_CAUSE_IMPL_DETACHED; + goto rejected; + } + msgid2mmctx(ctx, msg); + break; + case GSM_MI_TYPE_TMSI: + memcpy(&tmsi, mi+1, 4); + tmsi = ntohl(tmsi); + /* Try to find MM context based on P-TMSI */ + if (!ctx) + ctx = sgsn_mm_ctx_by_ptmsi(tmsi); + if (!ctx) { + /* FIXME: We need to have a context for service request? */ + reject_cause = GMM_CAUSE_IMPL_DETACHED; + goto rejected; + } + msgid2mmctx(ctx, msg); + break; + default: + LOGMMCTXP(LOGL_NOTICE, ctx, "Rejecting SERVICE REQUEST with " + "MI type %s\n", gsm48_mi_type_name(mi_type)); + reject_cause = GMM_CAUSE_MS_ID_NOT_DERIVED; + goto rejected; + } + + ctx->gmm_state = GMM_COMMON_PROC_INIT; + + ctx->iu.service.type = service_type; + + /* Look at PDP Context Status IE and see if MS's view of + * activated/deactivated NSAPIs agrees with our view */ + if (TLVP_PRESENT(&tp, GSM48_IE_GMM_PDP_CTX_STATUS)) { + const uint8_t *pdp_status = TLVP_VAL(&tp, GSM48_IE_GMM_PDP_CTX_STATUS); + const size_t pdp_status_len = TLVP_LEN(&tp, GSM48_IE_GMM_PDP_CTX_STATUS); + + process_ms_ctx_status(ctx, pdp_status); + + /* 3GPP TS 24.008 § 4.7.13.4 Service request procedure not + * accepted by the network. Cause #40. If MS has PDP Contexts in + * Active state in pdp_status but there is no PDP contexts on + * SGSN side then Reject with the cause will force the mobile to + * reset PDP contexts */ + if (llist_empty(&ctx->pdp_list) && pdp_status_has_active_nsapis(pdp_status, pdp_status_len)) { + reject_cause = GMM_CAUSE_NO_PDP_ACTIVATED; + goto rejected; + } + } + + + ctx->pending_req = GSM48_MT_GMM_SERVICE_REQ; + return gsm48_gmm_authorize(ctx); + +err_inval: + LOGPC(DMM, LOGL_INFO, "\n"); + reject_cause = GMM_CAUSE_SEM_INCORR_MSG; + +rejected: + /* Send SERVICE REJECT */ + LOGMMCTXP(LOGL_NOTICE, ctx, + "Rejecting Service Request with cause '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, reject_cause), reject_cause); + rc = gsm48_tx_gmm_service_rej_oldmsg(msg, reject_cause); + + return rc; + +} + + +static int gsm48_rx_gmm_status(struct sgsn_mm_ctx *mmctx, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + + LOGMMCTXP(LOGL_INFO, mmctx, "-> GPRS MM STATUS (cause: %s)\n", + get_value_string(gsm48_gmm_cause_names, gh->data[0])); + + return 0; +} + +/* Rx GPRS Mobility Management. MMCTX can be NULL when called. On !Gb (Iu), llme is NULL */ +int gsm0408_rcv_gmm(struct sgsn_mm_ctx *mmctx, struct msgb *msg, + struct gprs_llc_llme *llme, bool drop_cipherable) +{ + struct sgsn_signal_data sig_data; + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + int rc; + + if (drop_cipherable && gsm48_hdr_gmm_cipherable(gh)) { + LOGMMCTXP(LOGL_NOTICE, mmctx, "Dropping cleartext GMM %s which " + "is expected to be encrypted for TLLI 0x%08x\n", + get_value_string(gprs_msgt_gmm_names, gh->msg_type), + llme->tlli); + return -EBADMSG; + } + + if (llme && !mmctx && + gh->msg_type != GSM48_MT_GMM_ATTACH_REQ && + gh->msg_type != GSM48_MT_GMM_RA_UPD_REQ) { + LOGGBP(llme, LOGL_NOTICE, "Cannot handle GMM for unknown MM CTX\n"); + /* 4.7.10 */ + if (gh->msg_type == GSM48_MT_GMM_STATUS) { + /* TLLI unassignment */ + gprs_llgmm_unassign(llme); + return 0; + } + + /* Don't reply or establish a LLME on DETACH_ACK */ + if (gh->msg_type == GSM48_MT_GMM_DETACH_ACK) + return gprs_llgmm_unassign(llme); + + /* Don't reply to deatch requests, reason power off */ + if (gh->msg_type == GSM48_MT_GMM_DETACH_REQ && + gh->data[0] & 0x8) { + return 0; + } + + + gprs_llgmm_reset(llme); + + /* Don't force it into re-attachment */ + if (gh->msg_type == GSM48_MT_GMM_DETACH_REQ) { + /* Handle Detach Request */ + rc = gsm48_rx_gmm_det_req(NULL, msg); + + /* TLLI unassignment */ + gprs_llgmm_unassign(llme); + return rc; + } + + /* Force the MS to re-attach */ + rc = gsm0408_gprs_force_reattach_oldmsg(msg, llme); + + /* TLLI unassignment */ + gprs_llgmm_unassign(llme); + return rc; + } + + /* + * For a few messages, mmctx may be NULL. For most, we want to ensure a + * non-NULL mmctx. At the same time, we want to keep the message + * validity check intact, so that all message types appear in the + * switch statement and the default case thus means "unknown message". + * If we split the switch in two parts to check non-NULL halfway, the + * unknown-message check breaks, or we'd need to duplicate the switch + * cases in both parts. Just keep one large switch and add some gotos. + */ + switch (gh->msg_type) { + case GSM48_MT_GMM_RA_UPD_REQ: + rc = gsm48_rx_gmm_ra_upd_req(mmctx, msg, llme); + break; + case GSM48_MT_GMM_ATTACH_REQ: + rc = gsm48_rx_gmm_att_req(mmctx, msg, llme); + break; + case GSM48_MT_GMM_SERVICE_REQ: + rc = gsm48_rx_gmm_service_req(mmctx, msg); + break; + /* For all the following types mmctx can not be NULL */ + case GSM48_MT_GMM_ID_RESP: + if (!mmctx) + goto null_mmctx; + rc = gsm48_rx_gmm_id_resp(mmctx, msg); + break; + case GSM48_MT_GMM_STATUS: + if (!mmctx) + goto null_mmctx; + rc = gsm48_rx_gmm_status(mmctx, msg); + break; + case GSM48_MT_GMM_DETACH_REQ: + if (!mmctx) + goto null_mmctx; + rc = gsm48_rx_gmm_det_req(mmctx, msg); + break; + case GSM48_MT_GMM_DETACH_ACK: + if (!mmctx) + goto null_mmctx; + LOGMMCTXP(LOGL_INFO, mmctx, "-> DETACH ACK\n"); + mm_ctx_cleanup_free(mmctx, "GPRS DETACH ACK"); + rc = 0; + break; + case GSM48_MT_GMM_ATTACH_COMPL: + if (!mmctx) + goto null_mmctx; + /* only in case SGSN offered new P-TMSI */ + LOGMMCTXP(LOGL_INFO, mmctx, "-> ATTACH COMPLETE\n"); + +#ifdef BUILD_IU + if (mmctx->iu.ue_ctx) { + ranap_iu_tx_release(mmctx->iu.ue_ctx, NULL); + } +#endif + + mmctx_timer_stop(mmctx, 3350); + mmctx->t3350_mode = GMM_T3350_MODE_NONE; + mmctx->p_tmsi_old = 0; + mmctx->pending_req = 0; + mmctx->gmm_state = GMM_REGISTERED_NORMAL; + switch(mmctx->ran_type) { + case MM_CTX_T_UTRAN_Iu: + osmo_fsm_inst_dispatch(mmctx->iu.mm_state_fsm, E_PMM_PS_ATTACH, NULL); + break; + case MM_CTX_T_GERAN_Gb: + /* Unassign the old TLLI */ + mmctx->gb.tlli = mmctx->gb.tlli_new; + gprs_llme_copy_key(mmctx, mmctx->gb.llme); + gprs_llgmm_assign(mmctx->gb.llme, TLLI_UNASSIGNED, + mmctx->gb.tlli_new); + osmo_fsm_inst_dispatch(mmctx->gb.mm_state_fsm, E_MM_GPRS_ATTACH, NULL); + break; + } + rc = 0; + + osmo_fsm_inst_dispatch(mmctx->gmm_att_req.fsm, E_ATTACH_COMPLETE_RECV, 0); + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.mm = mmctx; + osmo_signal_dispatch(SS_SGSN, S_SGSN_ATTACH, &sig_data); + break; + case GSM48_MT_GMM_RA_UPD_COMPL: + if (!mmctx) + goto null_mmctx; + /* only in case SGSN offered new P-TMSI */ + LOGMMCTXP(LOGL_INFO, mmctx, "-> ROUTING AREA UPDATE COMPLETE\n"); + mmctx_timer_stop(mmctx, 3350); + mmctx->t3350_mode = GMM_T3350_MODE_NONE; + mmctx->p_tmsi_old = 0; + mmctx->pending_req = 0; + mmctx->gmm_state = GMM_REGISTERED_NORMAL; + switch(mmctx->ran_type) { + case MM_CTX_T_UTRAN_Iu: + osmo_fsm_inst_dispatch(mmctx->iu.mm_state_fsm, E_PMM_RA_UPDATE, NULL); + break; + case MM_CTX_T_GERAN_Gb: + /* Unassign the old TLLI */ + mmctx->gb.tlli = mmctx->gb.tlli_new; + gprs_llgmm_assign(mmctx->gb.llme, TLLI_UNASSIGNED, + mmctx->gb.tlli_new); + osmo_fsm_inst_dispatch(mmctx->gb.mm_state_fsm, E_MM_RA_UPDATE, NULL); + break; + } + rc = 0; + + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.mm = mmctx; + osmo_signal_dispatch(SS_SGSN, S_SGSN_UPDATE, &sig_data); + break; + case GSM48_MT_GMM_PTMSI_REALL_COMPL: + if (!mmctx) + goto null_mmctx; + LOGMMCTXP(LOGL_INFO, mmctx, "-> PTMSI REALLOCATION COMPLETE\n"); + mmctx_timer_stop(mmctx, 3350); + mmctx->t3350_mode = GMM_T3350_MODE_NONE; + mmctx->p_tmsi_old = 0; + mmctx->pending_req = 0; + if (mmctx->ran_type == MM_CTX_T_GERAN_Gb) { + /* Unassign the old TLLI */ + mmctx->gb.tlli = mmctx->gb.tlli_new; + //gprs_llgmm_assign(mmctx->gb.llme, TLLI_UNASSIGNED, mmctx->gb.tlli_new, GPRS_ALGO_GEA0, NULL); + } + rc = 0; + break; + case GSM48_MT_GMM_AUTH_CIPH_RESP: + if (!mmctx) + goto null_mmctx; + rc = gsm48_rx_gmm_auth_ciph_resp(mmctx, msg); + break; + case GSM48_MT_GMM_AUTH_CIPH_FAIL: + rc = gsm48_rx_gmm_auth_ciph_fail(mmctx, msg); + break; + default: + LOGMMCTXP(LOGL_NOTICE, mmctx, "Unknown GSM 04.08 GMM msg type 0x%02x\n", + gh->msg_type); + if (mmctx) + rc = gsm48_tx_gmm_status(mmctx, GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL); + else + rc = -EINVAL; + break; + } + + return rc; + +null_mmctx: + LOGGBIUP(llme, msg, LOGL_ERROR, + "Received GSM 04.08 message type 0x%02x," + " but no MM context available\n", + gh->msg_type); + return -EINVAL; +} + +static void mmctx_timer_cb(void *_mm) +{ + struct sgsn_mm_ctx *mm = _mm; + struct gsm_auth_tuple *at; + int rc; + unsigned long seconds; + + mm->num_T_exp++; + + switch (mm->T) { + case 3350: /* waiting for ATTACH COMPLETE */ + if (mm->num_T_exp >= 5) { + LOGMMCTXP(LOGL_NOTICE, mm, "T3350 expired >= 5 times\n"); + mm_ctx_cleanup_free(mm, "T3350"); + /* FIXME: should we return some error? */ + break; + } + /* re-transmit the respective msg and re-start timer */ + switch (mm->t3350_mode) { + case GMM_T3350_MODE_ATT: + gsm48_tx_gmm_att_ack(mm); + break; + case GMM_T3350_MODE_RAU: + gsm48_tx_gmm_ra_upd_ack(mm); + break; + case GMM_T3350_MODE_PTMSI_REALL: + /* FIXME */ + break; + case GMM_T3350_MODE_NONE: + LOGMMCTXP(LOGL_NOTICE, mm, + "T3350 mode wasn't set, ignoring timeout\n"); + break; + } + seconds = osmo_tdef_get(sgsn->cfg.T_defs, 3350, OSMO_TDEF_S, -1); + osmo_timer_schedule(&mm->timer, seconds, 0); + break; + case 3360: /* waiting for AUTH AND CIPH RESP */ + if (mm->num_T_exp >= 5) { + LOGMMCTXP(LOGL_NOTICE, mm, "T3360 expired >= 5 times\n"); + mm_ctx_cleanup_free(mm, "T3360"); + break; + } + /* Re-transmit the respective msg and re-start timer */ + if (mm->auth_triplet.key_seq == GSM_KEY_SEQ_INVAL) { + LOGMMCTXP(LOGL_ERROR, mm, + "timeout: invalid auth triplet reference\n"); + mm_ctx_cleanup_free(mm, "T3360"); + break; + } + at = &mm->auth_triplet; + + rc = gsm48_tx_gmm_auth_ciph_req(mm, &at->vec, at->key_seq, false); + if (rc < 0) { + LOGMMCTXP(LOGL_ERROR, mm, "failed sending Auth. & Ciph. Request: %s \n", strerror(-rc)); + } else { + seconds = osmo_tdef_get(sgsn->cfg.T_defs, 3360, OSMO_TDEF_S, -1); + osmo_timer_schedule(&mm->timer, seconds, 0); + } + break; + case 3370: /* waiting for IDENTITY RESPONSE */ + if (mm->num_T_exp >= 5) { + LOGMMCTXP(LOGL_NOTICE, mm, "T3370 expired >= 5 times\n"); + gsm48_tx_gmm_att_rej(mm, GMM_CAUSE_MS_ID_NOT_DERIVED); + mm_ctx_cleanup_free(mm, "GPRS ATTACH REJECT (T3370)"); + break; + } + /* re-tranmit IDENTITY REQUEST and re-start timer */ + gsm48_tx_gmm_id_req(mm, mm->t3370_id_type); + seconds = osmo_tdef_get(sgsn->cfg.T_defs, 3370, OSMO_TDEF_S, -1); + osmo_timer_schedule(&mm->timer, seconds, 0); + break; + default: + LOGMMCTXP(LOGL_ERROR, mm, "timer expired in unknown mode %u\n", + mm->T); + } +} + +/* GPRS SESSION MANAGEMENT */ + +static void pdpctx_timer_cb(void *_mm); + + +static void pdpctx_timer_rearm(struct sgsn_pdp_ctx *pdp, unsigned int T) +{ + unsigned long seconds; + if (osmo_timer_pending(&pdp->timer)) + LOGPDPCTXP(LOGL_ERROR, pdp, "Scheduling PDP timer %u while old " + "timer %u pending\n", T, pdp->T); + seconds = osmo_tdef_get(sgsn->cfg.T_defs, T, OSMO_TDEF_S, -1); + osmo_timer_schedule(&pdp->timer, seconds, 0); +} + +static void pdpctx_timer_start(struct sgsn_pdp_ctx *pdp, unsigned int T) +{ + if (osmo_timer_pending(&pdp->timer)) + LOGPDPCTXP(LOGL_ERROR, pdp, "Starting PDP timer %u while old " + "timer %u pending\n", T, pdp->T); + pdp->T = T; + pdp->num_T_exp = 0; + + osmo_timer_setup(&pdp->timer, pdpctx_timer_cb, pdp); + pdpctx_timer_rearm(pdp, pdp->T); +} + +static void pdpctx_timer_stop(struct sgsn_pdp_ctx *pdp, unsigned int T) +{ + if (pdp->T != T) + LOGPDPCTXP(LOGL_ERROR, pdp, "Stopping PDP timer %u but " + "%u is running\n", T, pdp->T); + osmo_timer_del(&pdp->timer); +} + +void pdp_ctx_detach_mm_ctx(struct sgsn_pdp_ctx *pdp) +{ + /* Detach from MM context */ + llist_del(&pdp->list); + pdp->mm = NULL; + + /* stop timer 3395 */ + pdpctx_timer_stop(pdp, 3395); +} + +#if 0 +static void msgb_put_pdp_addr_ipv4(struct msgb *msg, uint32_t ipaddr) +{ + uint8_t v[6]; + + v[0] = PDP_TYPE_ORG_IETF; + v[1] = PDP_TYPE_N_IETF_IPv4; + *(uint32_t *)(v+2) = htonl(ipaddr); + + msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, sizeof(v), v); +} + +static void msgb_put_pdp_addr_ppp(struct msgb *msg) +{ + uint8_t v[2]; + + v[0] = PDP_TYPE_ORG_ETSI; + v[1] = PDP_TYPE_N_ETSI_PPP; + + msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, sizeof(v), v); +} +#endif + +/* 3GPP TS 24.008 § 9.5.2: Activate PDP Context Accept */ +int gsm48_tx_gsm_act_pdp_acc(struct sgsn_pdp_ctx *pdp) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 PDP ACC"); + struct gsm48_hdr *gh; + uint8_t transaction_id = pdp->ti ^ 0x8; /* flip */ + + LOGPDPCTXP(LOGL_INFO, pdp, "<- ACTIVATE PDP CONTEXT ACK\n"); + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_PDP_ACTIVATE_ACCEPT]); + + mmctx2msgid(msg, pdp->mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4); + gh->msg_type = GSM48_MT_GSM_ACT_PDP_ACK; + + /* Negotiated LLC SAPI */ + msgb_v_put(msg, pdp->sapi); + + /* FIXME: copy QoS parameters from original request */ + //msgb_lv_put(msg, pdp->lib->qos_neg.l, pdp->lib->qos_neg.v); + msgb_lv_put(msg, sizeof(default_qos), (uint8_t *)&default_qos); + + /* Radio priority 10.5.7.2 */ + msgb_v_put(msg, pdp->lib->radio_pri); + + /* PDP address */ + /* Highest 4 bits of first byte need to be set to 1, otherwise + * the IE is identical with the 04.08 PDP Address IE */ + pdp->lib->eua.v[0] &= ~0xf0; + msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, + pdp->lib->eua.l, pdp->lib->eua.v); + pdp->lib->eua.v[0] |= 0xf0; + + /* Optional: Protocol configuration options (FIXME: why 'req') */ + if (pdp->lib->pco_req.l) + msgb_tlv_put(msg, GSM48_IE_GSM_PROTO_CONF_OPT, + pdp->lib->pco_req.l, pdp->lib->pco_req.v); + + /* Optional: Packet Flow Identifier */ + + return gsm48_gmm_sendmsg(msg, 0, pdp->mm, true); +} + +/* 3GPP TS 24.008 § 9.5.3: Activate PDP Context reject */ +int gsm48_tx_gsm_act_pdp_rej(struct sgsn_mm_ctx *mm, uint8_t tid, + uint8_t cause, uint8_t pco_len, uint8_t *pco_v) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 PDP REJ"); + struct gsm48_hdr *gh; + uint8_t transaction_id = tid ^ 0x8; /* flip */ + + LOGMMCTXP(LOGL_NOTICE, mm, "<- ACTIVATE PDP CONTEXT REJ: %s\n", + get_value_string(gsm48_gsm_cause_names, cause)); + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_PDP_ACTIVATE_REJECT]); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4); + gh->msg_type = GSM48_MT_GSM_ACT_PDP_REJ; + + msgb_v_put(msg, cause); + if (pco_len && pco_v) + msgb_tlv_put(msg, GSM48_IE_GSM_PROTO_CONF_OPT, pco_len, pco_v); + + return gsm48_gmm_sendmsg(msg, 0, mm, true); +} + +/* 3GPP TS 24.008 § 9.5.8: Deactivate PDP Context Request */ +static int _gsm48_tx_gsm_deact_pdp_req(struct sgsn_mm_ctx *mm, uint8_t tid, + uint8_t sm_cause, bool teardown) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 PDP DET REQ"); + struct gsm48_hdr *gh; + uint8_t transaction_id = tid ^ 0x8; /* flip */ + uint8_t tear_down_ind = (0x9 << 4) | (!!teardown); + + LOGMMCTXP(LOGL_INFO, mm, "<- DEACTIVATE PDP CONTEXT REQ\n"); + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_PDP_DL_DEACTIVATE_REQUEST]); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4); + gh->msg_type = GSM48_MT_GSM_DEACT_PDP_REQ; + + msgb_v_put(msg, sm_cause); + msgb_v_put(msg, tear_down_ind); + + return gsm48_gmm_sendmsg(msg, 0, mm, true); +} +int gsm48_tx_gsm_deact_pdp_req(struct sgsn_pdp_ctx *pdp, uint8_t sm_cause, bool teardown) +{ + pdpctx_timer_start(pdp, 3395); + + return _gsm48_tx_gsm_deact_pdp_req(pdp->mm, pdp->ti, sm_cause, teardown); +} + +/* 3GPP TS 24.008 § 9.5.9: Deactivate PDP Context Accept */ +static int _gsm48_tx_gsm_deact_pdp_acc(struct sgsn_mm_ctx *mm, uint8_t tid) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 PDP DET ACC"); + struct gsm48_hdr *gh; + uint8_t transaction_id = tid ^ 0x8; /* flip */ + + LOGMMCTXP(LOGL_INFO, mm, "<- DEACTIVATE PDP CONTEXT ACK\n"); + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_PDP_DL_DEACTIVATE_ACCEPT]); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4); + gh->msg_type = GSM48_MT_GSM_DEACT_PDP_ACK; + + return gsm48_gmm_sendmsg(msg, 0, mm, true); +} +int gsm48_tx_gsm_deact_pdp_acc(struct sgsn_pdp_ctx *pdp) +{ + return _gsm48_tx_gsm_deact_pdp_acc(pdp->mm, pdp->ti); +} + +static int activate_ggsn(struct sgsn_mm_ctx *mmctx, + struct sgsn_ggsn_ctx *ggsn, const uint8_t transaction_id, + const uint8_t req_nsapi, const uint8_t req_llc_sapi, + struct tlv_parsed *tp, int destroy_ggsn) +{ + struct sgsn_pdp_ctx *pdp; + + LOGMMCTXP(LOGL_DEBUG, mmctx, "Using GGSN %u\n", ggsn->id); + ggsn->gsn = sgsn->gsn; + pdp = sgsn_create_pdp_ctx(ggsn, mmctx, req_nsapi, tp); + if (!pdp) + return -1; + + /* Store SAPI and Transaction Identifier */ + pdp->sapi = req_llc_sapi; + pdp->ti = transaction_id; + pdp->destroy_ggsn = destroy_ggsn; + + return 0; +} + +static void ggsn_lookup_cb(void *arg, int status, int timeouts, struct hostent *hostent) +{ + struct sgsn_ggsn_ctx *ggsn; + struct sgsn_ggsn_lookup *lookup = arg; + struct in_addr *addr = NULL; + char buf[INET_ADDRSTRLEN]; + + /* The context is gone while we made a request */ + if (!lookup->mmctx) { + talloc_free(lookup->orig_msg); + talloc_free(lookup); + return; + } + + if (status != ARES_SUCCESS) { + struct sgsn_mm_ctx *mmctx = lookup->mmctx; + + LOGMMCTXP(LOGL_ERROR, mmctx, "DNS query failed.\n"); + + /* Need to try with three digits now */ + if (lookup->state == SGSN_GGSN_2DIGIT) { + char *hostname; + int rc; + + lookup->state = SGSN_GGSN_3DIGIT; + hostname = osmo_apn_qualify_from_imsi(mmctx->imsi, + lookup->apn_str, 1); + LOGMMCTXP(LOGL_DEBUG, mmctx, + "Going to query %s\n", hostname); + rc = sgsn_ares_query(sgsn, hostname, + ggsn_lookup_cb, lookup); + if (rc != 0) { + LOGMMCTXP(LOGL_ERROR, mmctx, "Couldn't start GGSN\n"); + goto reject_due_failure; + } + return; + } + + LOGMMCTXP(LOGL_ERROR, mmctx, "Couldn't resolve GGSN\n"); + goto reject_due_failure; + } + + if (hostent->h_length != sizeof(struct in_addr)) { + LOGMMCTXP(LOGL_ERROR, lookup->mmctx, + "Wrong addr size(%zu)\n", sizeof(struct in_addr)); + goto reject_due_failure; + } + + /* Get the first addr from the list */ + addr = (struct in_addr *) hostent->h_addr_list[0]; + if (!addr) { + LOGMMCTXP(LOGL_ERROR, lookup->mmctx, "No host address.\n"); + goto reject_due_failure; + } + + ggsn = sgsn_ggsn_ctx_alloc(UINT32_MAX); + if (!ggsn) { + LOGMMCTXP(LOGL_ERROR, lookup->mmctx, "Failed to create ggsn.\n"); + goto reject_due_failure; + } + ggsn->remote_addr = *addr; + LOGMMCTXP(LOGL_NOTICE, lookup->mmctx, + "Selected %s as GGSN.\n", + inet_ntop(AF_INET, addr, buf, sizeof(buf))); + + /* forget about the ggsn look-up */ + lookup->mmctx->ggsn_lookup = NULL; + + activate_ggsn(lookup->mmctx, ggsn, lookup->ti, lookup->nsapi, + lookup->sapi, &lookup->tp, 1); + + /* Now free it */ + talloc_free(lookup->orig_msg); + talloc_free(lookup); + return; + +reject_due_failure: + gsm48_tx_gsm_act_pdp_rej(lookup->mmctx, lookup->ti, + GMM_CAUSE_NET_FAIL, 0, NULL); + lookup->mmctx->ggsn_lookup = NULL; + talloc_free(lookup->orig_msg); + talloc_free(lookup); +} + +static int do_act_pdp_req(struct sgsn_mm_ctx *mmctx, struct msgb *msg, bool *delete) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + struct gsm48_act_pdp_ctx_req *act_req = (struct gsm48_act_pdp_ctx_req *) gh->data; + uint8_t req_qos_len, req_pdpa_len; + uint8_t *req_qos, *req_pdpa; + struct tlv_parsed tp; + uint8_t transaction_id = gsm48_hdr_trans_id(gh); + struct sgsn_ggsn_ctx *ggsn; + struct sgsn_pdp_ctx *pdp; + enum gsm48_gsm_cause gsm_cause; + char apn_str[GSM_APN_LENGTH] = { 0, }; + char *hostname; + int rc; + struct gprs_llc_lle *lle; + char buf[INET_ADDRSTRLEN]; + + LOGMMCTXP(LOGL_INFO, mmctx, "-> ACTIVATE PDP CONTEXT REQ: SAPI=%u NSAPI=%u ", + act_req->req_llc_sapi, act_req->req_nsapi); + + /* FIXME: length checks! */ + req_qos_len = act_req->data[0]; + req_qos = act_req->data + 1; /* 10.5.6.5 */ + req_pdpa_len = act_req->data[1 + req_qos_len]; + req_pdpa = act_req->data + 1 + req_qos_len + 1; /* 10.5.6.4 */ + + switch (req_pdpa[0] & 0xf) { + case 0x0: + DEBUGPC(DMM, "ETSI "); + break; + case 0x1: + DEBUGPC(DMM, "IETF "); + break; + case 0xf: + DEBUGPC(DMM, "Empty "); + break; + } + + switch (req_pdpa[1]) { + case 0x21: + DEBUGPC(DMM, "IPv4 "); + if (req_pdpa_len >= 6) { + struct in_addr ia; + ia.s_addr = ntohl(*((uint32_t *) (req_pdpa+2))); + DEBUGPC(DMM, "%s ", inet_ntop(AF_INET, &ia, buf, sizeof(buf))); + } + break; + case 0x57: + DEBUGPC(DMM, "IPv6 "); + if (req_pdpa_len >= 18) { + /* FIXME: print IPv6 address */ + } + break; + default: + DEBUGPC(DMM, "0x%02x ", req_pdpa[1]); + break; + } + + LOGPC(DMM, LOGL_INFO, "\n"); + + /* Check if NSAPI is out of range (TS 04.65 / 7.2) */ + if (act_req->req_nsapi < 5 || act_req->req_nsapi > 15) { + /* Send reject with GSM_CAUSE_INV_MAND_INFO */ + return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id, + GSM_CAUSE_INV_MAND_INFO, + 0, NULL); + } + + /* Optional: Access Point Name, Protocol Config Options */ + if (req_pdpa + req_pdpa_len < msg->data + msg->len) + tlv_parse(&tp, &gsm48_sm_att_tlvdef, req_pdpa + req_pdpa_len, + (msg->data + msg->len) - (req_pdpa + req_pdpa_len), 0, 0); + else + memset(&tp, 0, sizeof(tp)); + + + /* put the non-TLV elements in the TLV parser structure to + * pass them on to the SGSN / GTP code */ + tp.lv[OSMO_IE_GSM_REQ_QOS].len = req_qos_len; + tp.lv[OSMO_IE_GSM_REQ_QOS].val = req_qos; + tp.lv[OSMO_IE_GSM_REQ_PDP_ADDR].len = req_pdpa_len; + tp.lv[OSMO_IE_GSM_REQ_PDP_ADDR].val = req_pdpa; + + /* Check if NSAPI is already in use */ + pdp = sgsn_pdp_ctx_by_nsapi(mmctx, act_req->req_nsapi); + if (pdp) { + /* We already have a PDP context for this TLLI + NSAPI tuple */ + if (pdp->sapi == act_req->req_llc_sapi && + pdp->ti == transaction_id) { + /* This apparently is a re-transmission of a PDP CTX + * ACT REQ (our ACT ACK must have got dropped) */ + rc = gsm48_tx_gsm_act_pdp_acc(pdp); + if (rc < 0) + return rc; + + if (pdp->mm->ran_type == MM_CTX_T_GERAN_Gb) { + /* Also re-transmit the SNDCP XID message */ + lle = &pdp->mm->gb.llme->lle[pdp->sapi]; + rc = sndcp_sn_xid_req(lle,pdp->nsapi); + if (rc < 0) + return rc; + } + + return 0; + } + + /* Send reject with GSM_CAUSE_NSAPI_IN_USE */ + return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id, + GSM_CAUSE_NSAPI_IN_USE, + 0, NULL); + } + + if (mmctx->ggsn_lookup) { + if (mmctx->ggsn_lookup->sapi == act_req->req_llc_sapi && + mmctx->ggsn_lookup->ti == transaction_id) { + LOGMMCTXP(LOGL_NOTICE, mmctx, + "Re-transmission while doing look-up. Ignoring.\n"); + return 0; + } + } + + /* Only increment counter for a real activation, after we checked + * for re-transmissions */ + rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PDP_CTX_ACT]); + + /* Determine GGSN based on APN and subscription options */ + ggsn = sgsn_mm_ctx_find_ggsn_ctx(mmctx, &tp, &gsm_cause, apn_str); + if (ggsn) + return activate_ggsn(mmctx, ggsn, transaction_id, + act_req->req_nsapi, act_req->req_llc_sapi, + &tp, 0); + + if (strlen(apn_str) == 0) + goto no_context; + if (!sgsn->cfg.dynamic_lookup) + goto no_context; + + /* schedule a dynamic look-up */ + mmctx->ggsn_lookup = talloc_zero(tall_sgsn_ctx, struct sgsn_ggsn_lookup); + if (!mmctx->ggsn_lookup) + goto no_context; + + mmctx->ggsn_lookup->state = SGSN_GGSN_2DIGIT; + mmctx->ggsn_lookup->mmctx = mmctx; + strcpy(mmctx->ggsn_lookup->apn_str, apn_str); + + mmctx->ggsn_lookup->orig_msg = msg; + mmctx->ggsn_lookup->tp = tp; + + mmctx->ggsn_lookup->ti = transaction_id; + mmctx->ggsn_lookup->nsapi = act_req->req_nsapi; + mmctx->ggsn_lookup->sapi = act_req->req_llc_sapi; + + hostname = osmo_apn_qualify_from_imsi(mmctx->imsi, + mmctx->ggsn_lookup->apn_str, 0); + + LOGMMCTXP(LOGL_DEBUG, mmctx, "Going to query %s\n", hostname); + rc = sgsn_ares_query(sgsn, hostname, + ggsn_lookup_cb, mmctx->ggsn_lookup); + if (rc != 0) { + LOGMMCTXP(LOGL_ERROR, mmctx, "Failed to start ares query.\n"); + goto no_context; + } + *delete = 0; + + return 0; + +no_context: + LOGMMCTXP(LOGL_ERROR, mmctx, "No GGSN context found!\n"); + return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id, + gsm_cause, 0, NULL); +} + +/* 3GPP TS 24.008 § 9.5.1: Activate PDP Context Request */ +static int gsm48_rx_gsm_act_pdp_req(struct sgsn_mm_ctx *mmctx, + struct msgb *_msg) +{ + bool delete = 1; + struct msgb *msg; + int rc; + + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_PDP_ACTIVATE_REQUEST]); + + /* + * This is painful. We might not have a static GGSN + * configuration and then would need to copy the msg + * and re-do most of this routine (or call it again + * and make sure it only goes through the dynamic + * resolving. The question is what to optimize for + * and the dynamic resolution will be the right thing + * in the long run. + */ + msg = bssgp_msgb_copy(_msg, __func__); + if (!msg) { + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(_msg); + uint8_t transaction_id = gsm48_hdr_trans_id(gh); + + LOGMMCTXP(LOGL_ERROR, mmctx, "-> ACTIVATE PDP CONTEXT REQ failed copy.\n"); + /* Send reject with GSM_CAUSE_INV_MAND_INFO */ + return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id, + GSM_CAUSE_NET_FAIL, + 0, NULL); + } + + rc = do_act_pdp_req(mmctx, msg, &delete); + if (delete) + msgb_free(msg); + return rc; +} + +/* 3GPP TS 24.008 § 9.5.8: Deactivate PDP Context Request */ +static int gsm48_rx_gsm_deact_pdp_req(struct sgsn_mm_ctx *mm, struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t transaction_id = gsm48_hdr_trans_id(gh); + struct sgsn_pdp_ctx *pdp; + + LOGMMCTXP(LOGL_INFO, mm, "-> DEACTIVATE PDP CONTEXT REQ (cause: %s)\n", + get_value_string(gsm48_gsm_cause_names, gh->data[0])); + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_PDP_UL_DEACTIVATE_REQUEST]); + + pdp = sgsn_pdp_ctx_by_tid(mm, transaction_id); + if (!pdp) { + LOGMMCTXP(LOGL_NOTICE, mm, "Deactivate PDP Context Request for " + "non-existing PDP Context (IMSI=%s, TI=%u)\n", + mm->imsi, transaction_id); + return _gsm48_tx_gsm_deact_pdp_acc(mm, transaction_id); + } + + return sgsn_delete_pdp_ctx(pdp); +} + +/* 3GPP TS 24.008 § 9.5.9: Deactivate PDP Context Accept */ +static int gsm48_rx_gsm_deact_pdp_ack(struct sgsn_mm_ctx *mm, struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t transaction_id = gsm48_hdr_trans_id(gh); + struct sgsn_pdp_ctx *pdp; + + LOGMMCTXP(LOGL_INFO, mm, "-> DEACTIVATE PDP CONTEXT ACK\n"); + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_PDP_UL_DEACTIVATE_ACCEPT]); + + pdp = sgsn_pdp_ctx_by_tid(mm, transaction_id); + if (!pdp) { + LOGMMCTXP(LOGL_NOTICE, mm, "Deactivate PDP Context Accept for " + "non-existing PDP Context (IMSI=%s, TI=%u)\n", + mm->imsi, transaction_id); + return 0; + } + /* stop timer 3395 */ + pdpctx_timer_stop(pdp, 3395); + if (pdp->ggsn) + return sgsn_delete_pdp_ctx(pdp); + /* GTP side already detached, freeing */ + sgsn_pdp_ctx_free(pdp); + return 0; +} + +static int gsm48_rx_gsm_status(struct sgsn_mm_ctx *ctx, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + + LOGMMCTXP(LOGL_INFO, ctx, "-> GPRS SM STATUS (cause: %s)\n", + get_value_string(gsm48_gsm_cause_names, gh->data[0])); + + return 0; +} + +static void pdpctx_timer_cb(void *_pdp) +{ + struct sgsn_pdp_ctx *pdp = _pdp; + + pdp->num_T_exp++; + + switch (pdp->T) { + case 3395: /* waiting for PDP CTX DEACT ACK */ + if (pdp->num_T_exp > T339X_MAX_RETRANS) { + LOGPDPCTXP(LOGL_NOTICE, pdp, "T3395 expired > %d times\n", T339X_MAX_RETRANS); + pdp->state = PDP_STATE_INACTIVE; + if (pdp->ggsn) + sgsn_delete_pdp_ctx(pdp); + else + sgsn_pdp_ctx_free(pdp); + break; + } + _gsm48_tx_gsm_deact_pdp_req(pdp->mm, pdp->ti, GSM_CAUSE_NET_FAIL, true); + pdpctx_timer_rearm(pdp, 3395); + break; + default: + LOGPDPCTXP(LOGL_ERROR, pdp, "timer expired in unknown mode %u\n", + pdp->T); + } +} + + +/* GPRS Session Management */ +int gsm0408_rcv_gsm(struct sgsn_mm_ctx *mmctx, struct msgb *msg, + struct gprs_llc_llme *llme) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + int rc; + + /* MMCTX can be NULL when called */ + + if (!mmctx) { + LOGGBIUP(llme, msg, LOGL_NOTICE, "Cannot handle SM for unknown MM CTX\n"); + /* 6.1.3.6 */ + if (gh->msg_type == GSM48_MT_GSM_STATUS) + return 0; + + return gsm0408_gprs_force_reattach_oldmsg(msg, llme); + } + + switch (gh->msg_type) { + case GSM48_MT_GSM_ACT_PDP_REQ: + rc = gsm48_rx_gsm_act_pdp_req(mmctx, msg); + break; + case GSM48_MT_GSM_DEACT_PDP_REQ: + rc = gsm48_rx_gsm_deact_pdp_req(mmctx, msg); + break; + case GSM48_MT_GSM_DEACT_PDP_ACK: + rc = gsm48_rx_gsm_deact_pdp_ack(mmctx, msg); + break; + case GSM48_MT_GSM_STATUS: + rc = gsm48_rx_gsm_status(mmctx, msg); + break; + case GSM48_MT_GSM_REQ_PDP_ACT_REJ: + case GSM48_MT_GSM_ACT_AA_PDP_REQ: + case GSM48_MT_GSM_DEACT_AA_PDP_REQ: + LOGMMCTXP(LOGL_NOTICE, mmctx, "Unimplemented GSM 04.08 GSM msg type 0x%02x: %s\n", + gh->msg_type, osmo_hexdump((uint8_t *)gh, msgb_l3len(msg))); + rc = gsm48_tx_sm_status(mmctx, GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL); + break; + default: + LOGMMCTXP(LOGL_NOTICE, mmctx, "Unknown GSM 04.08 GSM msg type 0x%02x: %s\n", + gh->msg_type, osmo_hexdump((uint8_t *)gh, msgb_l3len(msg))); + rc = gsm48_tx_sm_status(mmctx, GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL); + break; + + } + + return rc; +} + +int gsm0408_gprs_force_reattach_oldmsg(struct msgb *msg, + struct gprs_llc_llme *llme) +{ + int rc; + if (llme) + gprs_llgmm_reset_oldmsg(msg, GPRS_SAPI_GMM, llme); + + rc = gsm48_tx_gmm_detach_req_oldmsg( + msg, GPRS_DET_T_MT_REATT_REQ, GMM_CAUSE_IMPL_DETACHED); + + return rc; +} + +int gsm0408_gprs_force_reattach(struct sgsn_mm_ctx *mmctx) +{ + int rc; + if (mmctx->ran_type == MM_CTX_T_GERAN_Gb) + gprs_llgmm_reset(mmctx->gb.llme); + + rc = gsm48_tx_gmm_detach_req( + mmctx, GPRS_DET_T_MT_REATT_REQ, GMM_CAUSE_IMPL_DETACHED); + + mm_ctx_cleanup_free(mmctx, "forced reattach"); + + return rc; +} + +int gprs_gmm_rx_suspend(struct gprs_ra_id *raid, uint32_t tlli) +{ + struct sgsn_mm_ctx *mmctx; + + mmctx = sgsn_mm_ctx_by_tlli(tlli, raid); + if (!mmctx) { + LOGP(DMM, LOGL_NOTICE, "SUSPEND request for unknown " + "TLLI=%08x\n", tlli); + return -EINVAL; + } + + if (mmctx->gmm_state != GMM_REGISTERED_NORMAL && + mmctx->gmm_state != GMM_REGISTERED_SUSPENDED) { + LOGMMCTXP(LOGL_NOTICE, mmctx, "SUSPEND request while state " + "!= REGISTERED (TLLI=%08x)\n", tlli); + return -EINVAL; + } + + /* Transition from REGISTERED_NORMAL to REGISTERED_SUSPENDED */ + mmctx->gmm_state = GMM_REGISTERED_SUSPENDED; + return 0; +} + +int gprs_gmm_rx_resume(struct gprs_ra_id *raid, uint32_t tlli, + uint8_t suspend_ref) +{ + struct sgsn_mm_ctx *mmctx; + + /* FIXME: make use of suspend reference? */ + + mmctx = sgsn_mm_ctx_by_tlli(tlli, raid); + if (!mmctx) { + LOGP(DMM, LOGL_NOTICE, "RESUME request for unknown " + "TLLI=%08x\n", tlli); + return -EINVAL; + } + + if (mmctx->gmm_state != GMM_REGISTERED_NORMAL && + mmctx->gmm_state != GMM_REGISTERED_SUSPENDED) { + LOGMMCTXP(LOGL_NOTICE, mmctx, "RESUME request while state " + "!= SUSPENDED (TLLI=%08x)\n", tlli); + /* FIXME: should we not simply ignore it? */ + return -EINVAL; + } + + /* Transition from SUSPENDED to NORMAL */ + mmctx->gmm_state = GMM_REGISTERED_NORMAL; + return 0; +} diff --git a/src/sgsn/gprs_gmm_attach.c b/src/sgsn/gprs_gmm_attach.c new file mode 100644 index 000000000..130f8d1c0 --- /dev/null +++ b/src/sgsn/gprs_gmm_attach.c @@ -0,0 +1,461 @@ +#include + +#include + +#include +#include +#include +#include + +#define X(s) (1 << (s)) + +static int require_identity_imei = 1; +static int require_auth = 1; + +static const struct osmo_tdef_state_timeout gmm_attach_fsm_timeouts[32] = { + [ST_IDENTIY] = { .T=3370 }, + [ST_AUTH] = { .T=3360 }, + [ST_ACCEPT] = { .T=3350 }, + [ST_ASK_VLR] = { .T=3350 }, + [ST_IU_SECURITY_CMD] = { .T=3350 }, +}; + +#define gmm_attach_fsm_state_chg(fi, NEXT_STATE) \ + osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, gmm_attach_fsm_timeouts, sgsn->cfg.T_defs, -1) + + +static void st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sgsn_mm_ctx *ctx = fi->priv; + struct msgb *attach_req = data; + + /* we can run st_init multiple times */ + if (ctx->gmm_att_req.attach_req) + msgb_free(ctx->gmm_att_req.attach_req); + + ctx->gmm_att_req.attach_req = msgb_copy(attach_req, "Attach Request"); + ctx->auth_state = SGSN_AUTH_UNKNOWN; + ctx->gmm_att_req.auth_reattempt = 0; + + /* + * TODO: remove pending_req as soon the sgsn_auth code doesn't depend + * on it. + * pending_req must be set, even this fsm doesn't use it, because + * the sgsn_auth code is using this too + */ + ctx->pending_req = GSM48_MT_GMM_ATTACH_REQ; + + if (require_identity_imei) { + ctx->gmm_att_req.id_type = GSM_MI_TYPE_IMEI; + gmm_attach_fsm_state_chg(fi, ST_IDENTIY); + } else if (!strlen(ctx->imsi)) { + ctx->gmm_att_req.id_type = GSM_MI_TYPE_IMSI; + gmm_attach_fsm_state_chg(fi, ST_IDENTIY); + } else if (require_auth) + gmm_attach_fsm_state_chg(fi, ST_AUTH); + else + gmm_attach_fsm_state_chg(fi, ST_ACCEPT); +} + +static void st_identity_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct sgsn_mm_ctx *ctx = fi->priv; + int ret = 0; + + ctx->num_T_exp = 0; + + switch (ctx->gmm_att_req.id_type) { + case GSM_MI_TYPE_IMEI: + case GSM_MI_TYPE_IMSI: + break; + default: + /* TODO logging */ + osmo_fsm_inst_dispatch(fi, E_REJECT, NULL); + return; + } + + ctx->t3370_id_type = ctx->gmm_att_req.id_type; + ret = gsm48_tx_gmm_id_req(ctx, ctx->gmm_att_req.id_type); + if (ret < 0) { + LOGPFSM(fi, "Can not send tx_gmm_id %d.\n", ret); + osmo_fsm_inst_dispatch(fi, E_REJECT, NULL); + } +} + +static void st_identity(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sgsn_mm_ctx *ctx = fi->priv; + + OSMO_ASSERT(event == E_IDEN_RESP_RECV); + + /* check if we received a identity response */ + long type = (long) data; + switch (type) { + case GSM_MI_TYPE_IMEI: + case GSM_MI_TYPE_IMSI: + break; + default: + LOGMMCTXP(LOGL_ERROR, ctx, "Unknown mi type: 0x%lx, rejecting MS.\n", type); + osmo_fsm_inst_dispatch(fi, E_REJECT, (void *) GMM_CAUSE_NET_FAIL); + return; + } + + if (type != ctx->gmm_att_req.id_type) { + /* ignore wrong package */ + /* TODO logging */ + return; + } + + if (type == GSM_MI_TYPE_IMEI && !strlen(ctx->imsi)) { + ctx->gmm_att_req.id_type = GSM_MI_TYPE_IMSI; + gmm_attach_fsm_state_chg(fi, ST_IDENTIY); + } else if (require_auth) + gmm_attach_fsm_state_chg(fi, ST_AUTH); + else + gmm_attach_fsm_state_chg(fi, ST_ACCEPT); +} + +static void st_auth_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct sgsn_mm_ctx *ctx = fi->priv; + enum sgsn_auth_state auth_state; + + ctx->num_T_exp = 0; + + /* TODO: remove this layer violation. Don't parse any auth_policy here + * The correct way would be to ask the SGSN is this mmctx has to be auth + * regardless of the state. + * Otherwise someone else could steal the TLLI and just use it without further + * auth. + */ + if (sgsn->cfg.auth_policy != SGSN_AUTH_POLICY_REMOTE) { + /* we can "trust" sgsn_auth_state as long it's not remote */ + auth_state = sgsn_auth_state(ctx); + } else { + auth_state = ctx->auth_state; + } + + switch(auth_state) { + case SGSN_AUTH_UMTS_RESYNC: /* ask the vlr for a new vector to match the simcards seq */ + case SGSN_AUTH_UNKNOWN: /* the SGSN doesn know this MS */ + gmm_attach_fsm_state_chg(fi, ST_ASK_VLR); + break; + case SGSN_AUTH_REJECTED: + /* TODO: correct GMM cause */ + osmo_fsm_inst_dispatch(fi, E_REJECT, (void *) GMM_CAUSE_GPRS_NOTALLOWED); + break; + case SGSN_AUTH_ACCEPTED: + gmm_attach_fsm_state_chg(fi, ST_ACCEPT); + break; + case SGSN_AUTH_AUTHENTICATE: + if (ctx->auth_triplet.key_seq == GSM_KEY_SEQ_INVAL) { + /* invalid key material */ + gmm_attach_fsm_state_chg(fi, ST_ASK_VLR); + } + + struct gsm_auth_tuple *at = &ctx->auth_triplet; + if (gsm48_tx_gmm_auth_ciph_req(ctx, &at->vec, at->key_seq, + false) < 0) { + /* network failure */ + osmo_fsm_inst_dispatch(fi, E_REJECT, (void *) GMM_CAUSE_NET_FAIL); + } + ctx->gmm_att_req.auth_reattempt++; + break; + } +} + +static void st_auth(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sgsn_mm_ctx *ctx = fi->priv; + + switch (event) { + case E_AUTH_RESP_RECV_SUCCESS: + sgsn_auth_request(ctx); +#ifdef BUILD_IU + if (ctx->ran_type == MM_CTX_T_UTRAN_Iu && !ctx->iu.ue_ctx->integrity_active) + gmm_attach_fsm_state_chg(fi, ST_IU_SECURITY_CMD); + else +#endif /* BUILD_IU */ + gmm_attach_fsm_state_chg(fi, ST_ACCEPT); + break; + case E_AUTH_RESP_RECV_RESYNC: + if (ctx->gmm_att_req.auth_reattempt <= 1) + gmm_attach_fsm_state_chg(fi, ST_ASK_VLR); + else + osmo_fsm_inst_dispatch(fi, E_REJECT, (void *) GMM_CAUSE_SYNC_FAIL); + break; + } +} + +static void st_accept_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct sgsn_mm_ctx *ctx = fi->priv; + + ctx->num_T_exp = 0; + + /* TODO: remove pending_req as soon the sgsn_auth code doesn't depend on it */ + ctx->pending_req = 0; + gsm48_tx_gmm_att_ack(ctx); +} + +static void st_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sgsn_mm_ctx *ctx = fi->priv; + + switch(event) { + case E_ATTACH_COMPLETE_RECV: + /* TODO: #ifdef ! PTMSI_ALLOC is not supported */ + extract_subscr_msisdn(ctx); + extract_subscr_hlr(ctx); + osmo_fsm_inst_state_chg(fi, ST_INIT, 0, 0); + break; + case E_VLR_ANSWERED: + extract_subscr_msisdn(ctx); + extract_subscr_hlr(ctx); + LOGMMCTXP(LOGL_NOTICE, ctx, + "Unusual event: if MS got no data connection, check that it has APN configured.\n"); + break; + } +} + +static void st_reject(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sgsn_mm_ctx *ctx = fi->priv; + long reject_cause = (long) data; + + if (reject_cause != GMM_DISCARD_MS_WITHOUT_REJECT) + gsm48_tx_gmm_att_rej(ctx, (uint8_t) reject_cause); + + sgsn_mm_ctx_cleanup_free(ctx); +} + +static void st_ask_vlr_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct sgsn_mm_ctx *ctx = fi->priv; + + /* FIXME: remove this layer violation. + * The VLR should send the message to the HLR and not the rx function + * gsm48_rx_gmm_auth_ciph_fail. Because gmm_auth_ciph_fail already send a + * message to the HLR, we don't send here a request. */ + if (ctx->auth_state == SGSN_AUTH_UMTS_RESYNC) + return; + + /* ask the auth layer for more data */ + sgsn_auth_request(ctx); +} + +static void st_ask_vlr(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch(event) { + case E_VLR_ANSWERED: + gmm_attach_fsm_state_chg(fi, ST_AUTH); + break; + } +} + +static void st_iu_security_cmd_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ +#ifdef BUILD_IU + struct sgsn_mm_ctx *ctx = fi->priv; + + /* TODO: shouldn't this set always? not only when the integrity_active? */ + if (ctx->iu.ue_ctx->integrity_active) { + gmm_attach_fsm_state_chg(fi, ST_ACCEPT); + return; + } + + ranap_iu_tx_sec_mode_cmd(ctx->iu.ue_ctx, &ctx->auth_triplet.vec, 0, ctx->iu.new_key); + ctx->iu.new_key = 0; +#endif +} + +static void st_iu_security_cmd(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch(event) { + case E_IU_SECURITY_CMD_COMPLETE: + gmm_attach_fsm_state_chg(fi, ST_ACCEPT); + break; + } +} + +static struct osmo_fsm_state gmm_attach_req_fsm_states[] = { + /* default state for non-DTX and DTX when SPEECH is in progress */ + [ST_INIT] = { + .in_event_mask = X(E_ATTACH_REQ_RECV), + .out_state_mask = X(ST_INIT) | X(ST_IDENTIY) | X(ST_AUTH) | X(ST_ACCEPT), + .name = "Init", + .action = st_init, + }, + [ST_ASK_VLR] = { + .in_event_mask = X(E_VLR_ANSWERED), + .out_state_mask = X(ST_INIT) | X(ST_AUTH) | X(ST_ACCEPT) | X(ST_REJECT), + .name = "AskVLR", + .onenter = st_ask_vlr_on_enter, + .action = st_ask_vlr, + }, + [ST_IDENTIY] = { + .in_event_mask = X(E_IDEN_RESP_RECV), + .out_state_mask = X(ST_INIT) | X(ST_AUTH) | X(ST_ACCEPT) | X(ST_IDENTIY) | X(ST_REJECT), + .onenter = st_identity_on_enter, + .name = "CheckIdentity", + .action = st_identity, + }, + [ST_AUTH] = { + .in_event_mask = X(E_AUTH_RESP_RECV_SUCCESS) | X(E_AUTH_RESP_RECV_RESYNC), + .out_state_mask = X(ST_INIT) | X(ST_AUTH) | X(ST_IU_SECURITY_CMD) | X(ST_ACCEPT) | X(ST_ASK_VLR) | X(ST_REJECT), + .name = "Authenticate", + .onenter = st_auth_on_enter, + .action = st_auth, + }, + [ST_IU_SECURITY_CMD] = { + .in_event_mask = X(E_IU_SECURITY_CMD_COMPLETE), + .out_state_mask = X(ST_INIT) | X(ST_AUTH) | X(ST_ACCEPT) | X(ST_REJECT), + .name = "IuSecurityCommand", + .onenter = st_iu_security_cmd_on_enter, + .action = st_iu_security_cmd, + }, + [ST_ACCEPT] = { + .in_event_mask = X(E_ATTACH_COMPLETE_RECV) | X(E_VLR_ANSWERED), + .out_state_mask = X(ST_INIT) | X(ST_REJECT), + .name = "WaitAttachComplete", + .onenter = st_accept_on_enter, + .action = st_accept, + }, + [ST_REJECT] = { + .in_event_mask = X(E_REJECT), + .out_state_mask = X(ST_INIT), + .name = "Reject", + .action = st_reject, + }, +}; + +const struct value_string gmm_attach_req_fsm_event_names[] = { + OSMO_VALUE_STRING(E_ATTACH_REQ_RECV), + OSMO_VALUE_STRING(E_IDEN_RESP_RECV), + OSMO_VALUE_STRING(E_AUTH_RESP_RECV_SUCCESS), + OSMO_VALUE_STRING(E_AUTH_RESP_RECV_RESYNC), + OSMO_VALUE_STRING(E_ATTACH_ACCEPTED), + OSMO_VALUE_STRING(E_ATTACH_ACCEPT_SENT), + OSMO_VALUE_STRING(E_ATTACH_COMPLETE_RECV), + OSMO_VALUE_STRING(E_IU_SECURITY_CMD_COMPLETE), + OSMO_VALUE_STRING(E_REJECT), + OSMO_VALUE_STRING(E_VLR_ANSWERED), + { 0, NULL } +}; + +void gmm_attach_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) { + struct sgsn_mm_ctx *ctx = fi->priv; + struct msgb *new_attach_req = data; + + switch (event) { + case E_ATTACH_REQ_RECV: + switch (fi->state) { + case ST_INIT: + case ST_REJECT: + st_init(fi, event, data); + break; + + case ST_ACCEPT: + /* TODO: drop all state (e.g. PDP Ctx) and do this procedure */ + osmo_fsm_inst_state_chg(fi, ST_INIT, 0, 0); + st_init(fi, event, data); + break; + + case ST_ASK_VLR: + case ST_AUTH: + case ST_IDENTIY: + case ST_RETRIEVE_AUTH: + /* 04.08 4.7.3.1.6 d) Abnormal Case + * Only do action if Req IEs differs. */ + if (ctx->gmm_att_req.attach_req && + gprs_gmm_attach_req_ies(new_attach_req, ctx->gmm_att_req.attach_req)) { + osmo_fsm_inst_state_chg(fi, ST_INIT, 0, 0); + st_init(fi, event, data); + } + break; + } + break; + case E_REJECT: + if (fi->state != ST_REJECT) + osmo_fsm_inst_state_chg(fi, ST_REJECT, 0, 0); + st_reject(fi, event, data); + break; + } +} + +int gmm_attach_timer_cb(struct osmo_fsm_inst *fi) +{ + struct sgsn_mm_ctx *ctx = fi->priv; + struct gsm_auth_tuple *at = &ctx->auth_triplet; + unsigned long t_secs; + + ctx->num_T_exp++; + + switch(fi->state) { + case ST_ASK_VLR: + /* TODO: replace T3350 by a better timer or it's own + * re-use T3350 - not defined by standard */ + LOGMMCTXP(LOGL_ERROR, ctx, "HLR did not answer in time. Rejecting.\n"); + osmo_fsm_inst_dispatch(fi, E_REJECT, + (void *) GMM_CAUSE_NET_FAIL); + break; + case ST_IDENTIY: + /* T3370 */ + if (ctx->num_T_exp >= 5) { + osmo_fsm_inst_dispatch(fi, E_REJECT, + (void *) GMM_CAUSE_MS_ID_NOT_DERIVED); + break; + } + gsm48_tx_gmm_id_req(ctx, ctx->gmm_att_req.id_type); + t_secs = osmo_tdef_get(sgsn->cfg.T_defs, 3370, OSMO_TDEF_S, -1); + osmo_timer_schedule(&fi->timer, t_secs, 0); + + break; + case ST_AUTH: + /* T3360 */ + if (ctx->num_T_exp >= 5) { + osmo_fsm_inst_dispatch(fi, E_REJECT, (void *) GMM_DISCARD_MS_WITHOUT_REJECT); + break; + } + gsm48_tx_gmm_auth_ciph_req(ctx, &at->vec, at->key_seq, false); + t_secs = osmo_tdef_get(sgsn->cfg.T_defs, 3360, OSMO_TDEF_S, -1); + osmo_timer_schedule(&fi->timer, t_secs, 0); + break; + case ST_ACCEPT: + /* T3350 */ + if (ctx->num_T_exp >= 5) { + osmo_fsm_inst_dispatch(fi, E_REJECT, (void *) GMM_DISCARD_MS_WITHOUT_REJECT); + break; + } + gsm48_tx_gmm_att_ack(ctx); + t_secs = osmo_tdef_get(sgsn->cfg.T_defs, 3350, OSMO_TDEF_S, -1); + osmo_timer_schedule(&fi->timer, t_secs, 0); + break; + } + + return 0; +} + +struct osmo_fsm gmm_attach_req_fsm = { + .name = "GMM_ATTACH_REQ_FSM", + .states = gmm_attach_req_fsm_states, + .num_states = ARRAY_SIZE(gmm_attach_req_fsm_states), + .event_names = gmm_attach_req_fsm_event_names, + .allstate_event_mask = X(E_REJECT) | X(E_ATTACH_REQ_RECV), + .allstate_action = gmm_attach_allstate_action, + .log_subsys = DMM, + .timer_cb = gmm_attach_timer_cb, +}; + +static __attribute__((constructor)) void gprs_gmm_fsm_init(void) +{ + osmo_fsm_register(&gmm_attach_req_fsm); +} + +void gmm_att_req_free(struct sgsn_mm_ctx *mm) { + if (mm->gmm_att_req.fsm) + osmo_fsm_inst_free(mm->gmm_att_req.fsm); + + if (mm->gmm_att_req.attach_req) + msgb_free(mm->gmm_att_req.attach_req); +} diff --git a/src/sgsn/gprs_llc.c b/src/sgsn/gprs_llc.c new file mode 100644 index 000000000..2a27da844 --- /dev/null +++ b/src/sgsn/gprs_llc.c @@ -0,0 +1,1175 @@ +/* GPRS LLC protocol implementation as per 3GPP TS 04.64 */ + +/* (C) 2009-2010 by Harald Welte + * + * 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 . + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const struct value_string gprs_llc_llme_state_names[] = { + { GPRS_LLMS_UNASSIGNED, "UNASSIGNED" }, + { GPRS_LLMS_ASSIGNED, "ASSIGNED" }, + { 0, NULL } +}; + +static struct gprs_llc_llme *llme_alloc(uint32_t tlli); +static int gprs_llc_tx_xid(struct gprs_llc_lle *lle, struct msgb *msg, + int command); +static int gprs_llc_tx_dm(struct gprs_llc_lle *lle); +static int gprs_llc_tx_u(struct msgb *msg, uint8_t sapi, + int command, enum gprs_llc_u_cmd u_cmd, int pf_bit); + +/* BEGIN XID RELATED */ + +/* Generate XID message */ +static int gprs_llc_generate_xid(uint8_t *bytes, int bytes_len, + struct gprs_llc_xid_field *l3_xid_field, + struct gprs_llc_lle *lle) +{ + /* Note: Called by gprs_ll_xid_req() */ + + LLIST_HEAD(xid_fields); + + struct gprs_llc_xid_field xid_version; + struct gprs_llc_xid_field xid_n201u; + struct gprs_llc_xid_field xid_n201i; + + xid_version.type = GPRS_LLC_XID_T_VERSION; + xid_version.data = (uint8_t *) "\x00"; + xid_version.data_len = 1; + + xid_n201u.type = GPRS_LLC_XID_T_N201_U; + xid_n201u.data = (uint8_t *) "\x05\xf0"; + xid_n201u.data_len = 2; + + xid_n201i.type = GPRS_LLC_XID_T_N201_I; + xid_n201i.data = (uint8_t *) "\x05\xf0"; + xid_n201i.data_len = 2; + + /* Add locally managed XID Fields */ + llist_add(&xid_version.list, &xid_fields); + llist_add(&xid_n201u.list, &xid_fields); + llist_add(&xid_n201i.list, &xid_fields); + + /* Append layer 3 XID field (if present) */ + if (l3_xid_field) { + /* Enforce layer 3 XID type (just to be sure) */ + l3_xid_field->type = GPRS_LLC_XID_T_L3_PAR; + + /* Add Layer 3 XID field to the list */ + llist_add(&l3_xid_field->list, &xid_fields); + } + + /* Store generated XID for later reference */ + talloc_free(lle->xid); + lle->xid = gprs_llc_copy_xid(lle->llme, &xid_fields); + + return gprs_llc_compile_xid(bytes, bytes_len, &xid_fields); +} + +/* Generate XID message that will cause the GMM to reset */ +static int gprs_llc_generate_xid_for_gmm_reset(uint8_t *bytes, + int bytes_len, uint32_t iov_ui, + struct gprs_llc_lle *lle) +{ + /* Called by gprs_llgmm_reset() and + * gprs_llgmm_reset_oldmsg() */ + + LLIST_HEAD(xid_fields); + + struct gprs_llc_xid_field xid_reset; + struct gprs_llc_xid_field xid_iovui; + + /* First XID component must be RESET */ + xid_reset.type = GPRS_LLC_XID_T_RESET; + xid_reset.data = NULL; + xid_reset.data_len = 0; + + /* Add new IOV-UI */ + xid_iovui.type = GPRS_LLC_XID_T_IOV_UI; + xid_iovui.data = (uint8_t *) & iov_ui; + xid_iovui.data_len = 4; + + /* Add locally managed XID Fields */ + llist_add(&xid_iovui.list, &xid_fields); + llist_add(&xid_reset.list, &xid_fields); + + /* Store generated XID for later reference */ + talloc_free(lle->xid); + lle->xid = gprs_llc_copy_xid(lle->llme, &xid_fields); + + return gprs_llc_compile_xid(bytes, bytes_len, &xid_fields); +} + +/* Process an incoming XID confirmation */ +static int gprs_llc_process_xid_conf(uint8_t *bytes, int bytes_len, + struct gprs_llc_lle *lle) +{ + /* 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 llist_head *xid_fields; + struct gprs_llc_xid_field *xid_field; + struct gprs_llc_xid_field *xid_field_request; + struct gprs_llc_xid_field *xid_field_request_l3 = NULL; + + /* Pick layer3 XID from the XID request we have sent last */ + if (lle->xid) { + llist_for_each_entry(xid_field_request, lle->xid, list) { + if (xid_field_request->type == GPRS_LLC_XID_T_L3_PAR) + xid_field_request_l3 = xid_field_request; + } + } + + /* Parse and analyze XID-Response */ + xid_fields = gprs_llc_parse_xid(NULL, bytes, bytes_len); + + if (xid_fields) { + + gprs_llc_dump_xid_fields(xid_fields, LOGL_DEBUG); + llist_for_each_entry(xid_field, xid_fields, list) { + + /* Forward SNDCP-XID fields to Layer 3 (SNDCP) */ + if (xid_field->type == GPRS_LLC_XID_T_L3_PAR && + xid_field_request_l3) { + sndcp_sn_xid_conf(xid_field, + xid_field_request_l3, lle); + } + + /* Process LLC-XID fields: */ + else { + + /* 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! */ + LOGP(DLLC, LOGL_NOTICE, + "Ignoring XID-Field: XID: type %s, data_len=%d, data=%s\n", + get_value_string(gprs_llc_xid_type_names, + xid_field->type), + xid_field->data_len, + osmo_hexdump_nospc(xid_field->data, + xid_field->data_len)); + } + } + talloc_free(xid_fields); + } + + /* Flush pending XID fields */ + talloc_free(lle->xid); + lle->xid = NULL; + + return 0; +} + +/* Process an incoming XID indication and generate an appropiate response */ +static int gprs_llc_process_xid_ind(uint8_t *bytes_request, + int bytes_request_len, + uint8_t *bytes_response, + int bytes_response_maxlen, + struct gprs_llc_lle *lle) +{ + /* Note: This function computes the response that is sent back to the + * MS when a mobile originated XID is received. The function is + * called by rx_llc_xid() */ + + int rc = -EINVAL; + + struct llist_head *xid_fields; + struct llist_head *xid_fields_response; + + struct gprs_llc_xid_field *xid_field; + struct gprs_llc_xid_field *xid_field_response; + + /* Parse and analyze XID-Request */ + xid_fields = + gprs_llc_parse_xid(lle->llme, bytes_request, bytes_request_len); + if (xid_fields) { + xid_fields_response = talloc_zero(lle->llme, struct llist_head); + INIT_LLIST_HEAD(xid_fields_response); + gprs_llc_dump_xid_fields(xid_fields, LOGL_DEBUG); + + /* Process LLC-XID fields: */ + llist_for_each_entry(xid_field, xid_fields, list) { + + if (xid_field->type != GPRS_LLC_XID_T_L3_PAR) { + /* 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! */ + LOGP(DLLC, LOGL_NOTICE, + "Echoing XID-Field: XID: type %s, data_len=%d, data=%s\n", + get_value_string(gprs_llc_xid_type_names, + xid_field->type), + xid_field->data_len, + osmo_hexdump_nospc(xid_field->data, + xid_field->data_len)); + xid_field_response = + gprs_llc_dup_xid_field + (lle->llme, xid_field); + llist_add(&xid_field_response->list, + xid_fields_response); + } + } + + /* Forward SNDCP-XID fields to Layer 3 (SNDCP) */ + llist_for_each_entry(xid_field, xid_fields, list) { + if (xid_field->type == GPRS_LLC_XID_T_L3_PAR) { + + xid_field_response = + talloc_zero(lle->llme, + struct gprs_llc_xid_field); + rc = sndcp_sn_xid_ind(xid_field, + xid_field_response, lle); + if (rc == 0) + llist_add(&xid_field_response->list, + xid_fields_response); + else + talloc_free(xid_field_response); + } + } + + rc = gprs_llc_compile_xid(bytes_response, + bytes_response_maxlen, + xid_fields_response); + talloc_free(xid_fields_response); + talloc_free(xid_fields); + } + + return rc; +} + +/* Dispatch XID indications and responses comming from the MS */ +static void rx_llc_xid(struct gprs_llc_lle *lle, + struct gprs_llc_hdr_parsed *gph) +{ + uint8_t response[1024]; + int response_len; + + /* FIXME: 8.5.3.3: check if XID is invalid */ + if (gph->is_cmd) { + LOGP(DLLC, LOGL_NOTICE, + "Received XID indication from MS.\n"); + + struct msgb *resp; + uint8_t *xid; + resp = msgb_alloc_headroom(4096, 1024, "LLC_XID"); + + response_len = + gprs_llc_process_xid_ind(gph->data, gph->data_len, + response, sizeof(response), + lle); + if (response_len < 0) { + LOGP(DLLC, LOGL_ERROR, + "invalid XID indication received!\n"); + } else { + xid = msgb_put(resp, response_len); + memcpy(xid, response, response_len); + } + gprs_llc_tx_xid(lle, resp, 0); + } else { + LOGP(DLLC, LOGL_NOTICE, + "Received XID confirmation from MS.\n"); + gprs_llc_process_xid_conf(gph->data, gph->data_len, lle); + /* FIXME: if we had sent a XID reset, send + * LLGMM-RESET.conf to GMM */ + } +} + +/* Set of LL-XID negotiation (See also: TS 101 351, Section 7.2.2.4) */ +int gprs_ll_xid_req(struct gprs_llc_lle *lle, + struct gprs_llc_xid_field *l3_xid_field) +{ + /* Note: This functions is calle from gprs_sndcp.c */ + + uint8_t xid_bytes[1024];; + int xid_bytes_len; + uint8_t *xid; + struct msgb *msg; + const char *ftype; + + /* Generate XID */ + xid_bytes_len = + gprs_llc_generate_xid(xid_bytes, sizeof(xid_bytes), l3_xid_field, lle); + + /* Only perform XID sending if the XID message contains something */ + if (xid_bytes_len > 0) { + /* Transmit XID bytes */ + msg = msgb_alloc_headroom(4096, 1024, "LLC_XID"); + xid = msgb_put(msg, xid_bytes_len); + memcpy(xid, xid_bytes, xid_bytes_len); + if (l3_xid_field) + ftype = get_value_string(gprs_llc_xid_type_names, + l3_xid_field->type); + else + ftype = "NULL"; + LOGP(DLLC, LOGL_NOTICE, "Sending XID type %s (%d bytes) request" + " to MS...\n", ftype, xid_bytes_len); + gprs_llc_tx_xid(lle, msg, 1); + } else { + LOGP(DLLC, LOGL_ERROR, + "XID-Message generation failed, XID not sent!\n"); + return -EINVAL; + } + + return 0; +} +/* END XID RELATED */ + + + + +/* Entry function from upper level (LLC), asking us to transmit a BSSGP PDU + * to a remote MS (identified by TLLI) at a BTS identified by its BVCI and NSEI */ +static int _bssgp_tx_dl_ud(struct msgb *msg, struct sgsn_mm_ctx *mmctx) +{ + struct bssgp_dl_ud_par dup; + const uint8_t qos_profile_default[3] = { 0x00, 0x00, 0x20 }; + + memset(&dup, 0, sizeof(dup)); + /* before we have received some identity from the MS, we might + * not yet have a MMC context (e.g. XID negotiation of primarly + * LLC connection from GMM sapi). */ + if (mmctx) { + dup.imsi = mmctx->imsi; + dup.drx_parms = mmctx->drx_parms; + dup.ms_ra_cap.len = mmctx->ms_radio_access_capa.len; + dup.ms_ra_cap.v = mmctx->ms_radio_access_capa.buf; + + /* make sure we only send it to the right llme */ + if (!(msgb_tlli(msg) == mmctx->gb.llme->tlli + || msgb_tlli(msg) == mmctx->gb.llme->old_tlli)) { + LOGP(DLLC, LOGL_ERROR, + "_bssgp_tx_dl_ud(): Attempt to send Downlink Unitdata to wrong LLME:" + " msgb_tlli=0x%x mmctx->gb.llme->tlli=0x%x ->old_tlli=0x%x\n", + msgb_tlli(msg), mmctx->gb.llme->tlli, mmctx->gb.llme->old_tlli); + msgb_free(msg); + return -EINVAL; + } + } + memcpy(&dup.qos_profile, qos_profile_default, + sizeof(qos_profile_default)); + + return bssgp_tx_dl_ud(msg, 1000, &dup); +} + + +/* 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, + }, +}; + +LLIST_HEAD(gprs_llc_llmes); +void *llc_tall_ctx; + +/* lookup LLC Entity based on DLCI (TLLI+SAPI tuple) */ +static struct gprs_llc_lle *lle_by_tlli_sapi(const uint32_t tlli, uint8_t sapi) +{ + struct gprs_llc_llme *llme; + + llist_for_each_entry(llme, &gprs_llc_llmes, list) { + if (llme->tlli == tlli || llme->old_tlli == tlli) + return &llme->lle[sapi]; + } + return NULL; +} + +struct gprs_llc_lle *gprs_lle_get_or_create(const uint32_t tlli, uint8_t sapi) +{ + struct gprs_llc_llme *llme; + struct gprs_llc_lle *lle; + + lle = lle_by_tlli_sapi(tlli, sapi); + if (lle) + return lle; + + LOGP(DLLC, LOGL_NOTICE, "LLC: unknown TLLI 0x%08x, " + "creating LLME on the fly\n", tlli); + llme = llme_alloc(tlli); + lle = &llme->lle[sapi]; + return lle; +} + +struct llist_head *gprs_llme_list(void) +{ + return &gprs_llc_llmes; +} + +/* lookup LLC Entity for RX based on DLCI (TLLI+SAPI tuple) */ +static struct gprs_llc_lle *lle_for_rx_by_tlli_sapi(const uint32_t tlli, + uint8_t sapi, enum gprs_llc_cmd cmd) +{ + struct gprs_llc_lle *lle; + + /* We already know about this TLLI */ + lle = 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 = lle_by_tlli_sapi(tlli, sapi); + if (lle) { + LOGP(DLLC, 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 == GPRS_SAPI_GMM && + (cmd == GPRS_LLC_XID || cmd == GPRS_LLC_UI)) { + struct gprs_llc_llme *llme; + /* FIXME: don't use the TLLI but the 0xFFFF unassigned? */ + llme = llme_alloc(tlli); + LOGP(DLLC, LOGL_NOTICE, "LLC RX: unknown TLLI 0x%08x, " + "creating LLME on the fly\n", tlli); + lle = &llme->lle[sapi]; + return lle; + } + + LOGP(DLLC, LOGL_NOTICE, + "unknown TLLI(0x%08x)/SAPI(%d): Silently dropping\n", + tlli, sapi); + return NULL; +} + +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 = GPRS_LLES_UNASSIGNED; + + /* Initialize according to parameters */ + memcpy(&lle->params, &llc_default_params[sapi], sizeof(lle->params)); +} + +static struct gprs_llc_llme *llme_alloc(uint32_t tlli) +{ + struct gprs_llc_llme *llme; + uint32_t i; + + llme = talloc_zero(llc_tall_ctx, struct gprs_llc_llme); + if (!llme) + return NULL; + + llme->tlli = tlli; + llme->old_tlli = TLLI_UNASSIGNED; + llme->state = GPRS_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, &gprs_llc_llmes); + + llme->comp.proto = gprs_sndcp_comp_alloc(llme); + llme->comp.data = gprs_sndcp_comp_alloc(llme); + + return llme; +} + +static void llme_free(struct gprs_llc_llme *llme) +{ + gprs_sndcp_comp_free(llme->comp.proto); + gprs_sndcp_comp_free(llme->comp.data); + llist_del(&llme->list); + talloc_free(llme); +} + +#if 0 +/* FIXME: Unused code... */ +static void t200_expired(void *data) +{ + struct gprs_llc_lle *lle = data; + + /* 8.5.1.3: Expiry of T200 */ + + if (lle->retrans_ctr >= lle->params.n200) { + /* FIXME: LLGM-STATUS-IND, LL-RELEASE-IND/CNF */ + lle->state = GPRS_LLES_ASSIGNED_ADM; + } + + switch (lle->state) { + case GPRS_LLES_LOCAL_EST: + /* FIXME: retransmit SABM */ + /* FIXME: re-start T200 */ + lle->retrans_ctr++; + break; + case GPRS_LLES_LOCAL_REL: + /* FIXME: retransmit DISC */ + /* FIXME: re-start T200 */ + lle->retrans_ctr++; + break; + default: + LOGP(DLLC, LOGL_ERROR, "LLC unhandled state: %d\n", lle->state); + break; + } + +} + +static void t201_expired(void *data) +{ + struct gprs_llc_lle *lle = data; + + if (lle->retrans_ctr < lle->params.n200) { + /* FIXME: transmit apropriate supervisory frame (8.6.4.1) */ + /* FIXME: set timer T201 */ + lle->retrans_ctr++; + } +} +#endif + +int gprs_llc_tx_u(struct msgb *msg, uint8_t sapi, int command, + enum gprs_llc_u_cmd u_cmd, int pf_bit) +{ + uint8_t *fcs, *llch; + uint8_t addr, ctrl; + uint32_t fcs_calc; + + /* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */ + + /* Address Field */ + addr = sapi & 0xf; + if (command) + addr |= 0x40; + + /* 6.3 Figure 8 */ + ctrl = 0xe0 | u_cmd; + if (pf_bit) + ctrl |= 0x10; + + /* prepend LLC UI header */ + llch = msgb_push(msg, 2); + llch[0] = addr; + llch[1] = ctrl; + + /* append FCS to end of frame */ + fcs = msgb_put(msg, 3); + fcs_calc = gprs_llc_fcs(llch, fcs - llch); + fcs[0] = fcs_calc & 0xff; + fcs[1] = (fcs_calc >> 8) & 0xff; + fcs[2] = (fcs_calc >> 16) & 0xff; + + /* Identifiers passed down: (BVCI, NSEI) */ + + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_LLC_DL_PACKETS]); + rate_ctr_add(&sgsn->rate_ctrs->ctr[CTR_LLC_DL_BYTES], msg->len); + + /* Send BSSGP-DL-UNITDATA.req */ + return _bssgp_tx_dl_ud(msg, NULL); +} + +/* Send XID response to LLE */ +static int gprs_llc_tx_xid(struct gprs_llc_lle *lle, struct msgb *msg, + int command) +{ + /* copy identifiers from LLE to ensure lower layers can route */ + msgb_tlli(msg) = lle->llme->tlli; + msgb_bvci(msg) = lle->llme->bvci; + msgb_nsei(msg) = lle->llme->nsei; + + return gprs_llc_tx_u(msg, lle->sapi, command, GPRS_LLC_U_XID, 1); +} + +static int gprs_llc_tx_dm(struct gprs_llc_lle *lle) +{ + struct msgb *msg = msgb_alloc_headroom(4096, 1024, "LLC_DM"); + + /* copy identifiers from LLE to ensure lower layers can route */ + msgb_tlli(msg) = lle->llme->tlli; + msgb_bvci(msg) = lle->llme->bvci; + msgb_nsei(msg) = lle->llme->nsei; + + return gprs_llc_tx_u(msg, lle->sapi, 0, GPRS_LLC_U_DM_RESP, 1); +} + +/* encrypt information field + FCS, if needed! */ +static int apply_gea(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) { + LOGP(DLLC, 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; +} + +/* 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_tx_ui(struct msgb *msg, uint8_t sapi, int command, + struct sgsn_mm_ctx *mmctx, bool encryptable) +{ + struct gprs_llc_lle *lle; + uint8_t *fcs, *llch; + uint8_t addr, ctrl[2]; + uint32_t fcs_calc; + uint16_t nu = 0; + uint32_t oc; + + /* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */ + + /* look-up or create the LL Entity for this (TLLI, SAPI) tuple */ + lle = gprs_lle_get_or_create(msgb_tlli(msg), sapi); + + if (msg->len > lle->params.n201_u) { + LOGP(DLLC, LOGL_ERROR, "Cannot Tx %u bytes (N201-U=%u)\n", + msg->len, lle->params.n201_u); + msgb_free(msg); + return -EFBIG; + } + + gprs_llme_copy_key(mmctx, lle->llme); + + /* Update LLE's (BVCI, NSEI) tuple */ + lle->llme->bvci = msgb_bvci(msg); + lle->llme->nsei = msgb_nsei(msg); + + /* Obtain current values for N(u) and OC */ + nu = lle->vu_send; + oc = lle->oc_ui_send; + /* 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; + + /* Address Field */ + addr = sapi & 0xf; + if (command) + addr |= 0x40; + + /* Control Field */ + ctrl[0] = 0xc0; + ctrl[0] |= nu >> 6; + ctrl[1] = (nu << 2) & 0xfc; + ctrl[1] |= 0x01; /* Protected Mode */ + + /* prepend LLC UI header */ + llch = msgb_push(msg, 3); + llch[0] = addr; + llch[1] = ctrl[0]; + llch[2] = ctrl[1]; + + /* append FCS to end of frame */ + fcs = msgb_put(msg, 3); + fcs_calc = gprs_llc_fcs(llch, fcs - llch); + fcs[0] = fcs_calc & 0xff; + fcs[1] = (fcs_calc >> 8) & 0xff; + fcs[2] = (fcs_calc >> 16) & 0xff; + + if (lle->llme->algo != GPRS_ALGO_GEA0 && encryptable) { + int rc = apply_gea(lle, fcs - llch, nu, oc, sapi, fcs, llch); + if (rc < 0) { + msgb_free(msg); + return rc; + } + } + + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_LLC_DL_PACKETS]); + rate_ctr_add(&sgsn->rate_ctrs->ctr[CTR_LLC_DL_BYTES], msg->len); + + /* Identifiers passed down: (BVCI, NSEI) */ + + /* Send BSSGP-DL-UNITDATA.req */ + return _bssgp_tx_dl_ud(msg, mmctx); +} + +static int gprs_llc_hdr_rx(struct gprs_llc_hdr_parsed *gph, + struct gprs_llc_lle *lle) +{ + switch (gph->cmd) { +#if 0 + /* we don't fully imoplement ABM, so refuse it properly (OS#3953) */ + case GPRS_LLC_SABM: /* Section 6.4.1.1 */ + lle->v_sent = lle->v_ack = lle->v_recv = 0; + if (lle->state == GPRS_LLES_ASSIGNED_ADM) { + /* start re-establishment (8.7.1) */ + } + lle->state = GPRS_LLES_REMOTE_EST; + /* FIXME: Send UA */ + lle->state = GPRS_LLES_ABM; + /* FIXME: process data */ + break; + case GPRS_LLC_DISC: /* Section 6.4.1.2 */ + /* FIXME: Send UA */ + /* terminate ABM */ + lle->state = GPRS_LLES_ASSIGNED_ADM; + break; + case GPRS_LLC_UA: /* Section 6.4.1.3 */ + if (lle->state == GPRS_LLES_LOCAL_EST) + lle->state = GPRS_LLES_ABM; + break; + case GPRS_LLC_DM: /* Section 6.4.1.4: ABM cannot be performed */ + if (lle->state == GPRS_LLES_LOCAL_EST) + lle->state = GPRS_LLES_ASSIGNED_ADM; + break; + case GPRS_LLC_FRMR: /* Section 6.4.1.5 */ + break; +#else + case GPRS_LLC_SABM: + case GPRS_LLC_DISC: + /* send DM to properly signal we don't do ABM */ + gprs_llc_tx_dm(lle); + break; +#endif + case GPRS_LLC_XID: /* Section 6.4.1.6 */ + rx_llc_xid(lle, gph); + break; + case GPRS_LLC_UI: + if (gprs_llc_is_retransmit(gph->seq_tx, lle->vu_recv)) { + LOGP(DLLC, LOGL_NOTICE, + "TLLI=%08x dropping UI, N(U=%d) not in window V(URV(UR:%d).\n", + lle->llme ? lle->llme->tlli : -1, + gph->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 (gph->seq_tx != lle->vu_recv_last) { + lle->vu_recv_last = gph->seq_tx; + lle->vu_recv_duplicates = 0; + } else { + lle->vu_recv_duplicates++; + if (lle->vu_recv_duplicates < 3) + return -EIO; + LOGP(DLLC, LOGL_NOTICE, "TLLI=%08x recovering " + "N(U=%d) after receiving %u duplicates\n", + lle->llme ? lle->llme->tlli : -1, + gph->seq_tx, lle->vu_recv_duplicates); + } + } + /* Increment the sequence number that we expect in the next frame */ + lle->vu_recv = (gph->seq_tx + 1) % 512; + /* Increment Overflow Counter */ + if ((gph->seq_tx + 1) / 512) + lle->oc_ui_recv += 512; + break; + case GPRS_LLC_NULL: + LOGP(DLLC, LOGL_DEBUG, "TLLI=%08x sends us LLC NULL\n", lle->llme ? lle->llme->tlli : -1); + break; + default: + LOGP(DLLC, LOGL_NOTICE, "Unhandled command: %d\n", gph->cmd); + break; + } + + return 0; +} + +/* receive an incoming LLC PDU (BSSGP-UL-UNITDATA-IND, 7.2.4.2) */ +int gprs_llc_rcvmsg(struct msgb *msg, struct tlv_parsed *tv) +{ + struct gprs_llc_hdr *lh = (struct gprs_llc_hdr *) msgb_llch(msg); + struct gprs_llc_hdr_parsed llhp; + struct gprs_llc_lle *lle = NULL; + bool drop_cipherable = false; + int rc = 0; + + /* Identifiers from DOWN: NSEI, BVCI, TLLI */ + + memset(&llhp, 0, sizeof(llhp)); + rc = gprs_llc_hdr_parse(&llhp, (uint8_t *) lh, TLVP_LEN(tv, BSSGP_IE_LLC_PDU)); + if (rc < 0) { + LOGP(DLLC, LOGL_NOTICE, "Error during LLC header parsing\n"); + return rc; + } + + switch (gprs_tlli_type(msgb_tlli(msg))) { + case TLLI_LOCAL: + case TLLI_FOREIGN: + case TLLI_RANDOM: + case TLLI_AUXILIARY: + break; + default: + LOGP(DLLC, LOGL_ERROR, + "Discarding frame with strange TLLI type\n"); + break; + } + + /* find the LLC Entity for this TLLI+SAPI tuple */ + lle = lle_for_rx_by_tlli_sapi(msgb_tlli(msg), llhp.sapi, llhp.cmd); + if (!lle) { + switch (llhp.sapi) { + case GPRS_SAPI_SNDCP3: + case GPRS_SAPI_SNDCP5: + case GPRS_SAPI_SNDCP9: + case GPRS_SAPI_SNDCP11: + /* Ask an upper layer for help. */ + return gsm0408_gprs_force_reattach_oldmsg(msg, NULL); + default: + break; + } + return 0; + } + gprs_llc_hdr_dump(&llhp, lle); + /* reset age computation */ + lle->llme->age_timestamp = GPRS_LLME_RESET_AGE; + + /* decrypt information field + FCS, if needed! */ + if (llhp.is_encrypted) { + if (lle->llme->algo != GPRS_ALGO_GEA0) { + rc = apply_gea(lle, llhp.data_len + 3, llhp.seq_tx, + lle->oc_ui_recv, lle->sapi, NULL, + llhp.data); + if (rc < 0) + return rc; + llhp.fcs = *(llhp.data + llhp.data_len); + llhp.fcs |= *(llhp.data + llhp.data_len + 1) << 8; + llhp.fcs |= *(llhp.data + llhp.data_len + 2) << 16; + } else { + LOGP(DLLC, 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 */ + llhp.fcs_calc = gprs_llc_fcs((uint8_t *)lh, llhp.crc_length); + if (llhp.fcs != llhp.fcs_calc) { + LOGP(DLLC, LOGL_INFO, "Dropping frame with invalid FCS\n"); + return -EIO; + } + + /* Update LLE's (BVCI, NSEI) tuple */ + lle->llme->bvci = msgb_bvci(msg); + lle->llme->nsei = msgb_nsei(msg); + + /* Receive and Process the actual LLC frame */ + rc = gprs_llc_hdr_rx(&llhp, lle); + if (rc < 0) + return rc; + + /* there are many frame types that don't carry user information + * and which hence have llhp.data = NULL */ + if (llhp.data) { + /* set l3 layer & remove the fcs */ + msg->l3h = llhp.data; + msgb_l3trim(msg, llhp.data_len); + } + + rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_LLC_UL_PACKETS]); + rate_ctr_add(&sgsn->rate_ctrs->ctr[CTR_LLC_UL_BYTES], msg->len); + + /* llhp.data is only set when we need to send LL_[UNIT]DATA_IND up */ + if (llhp.cmd == GPRS_LLC_UI && llhp.data && llhp.data_len) { + switch (llhp.sapi) { + case GPRS_SAPI_GMM: + /* send LL_UNITDATA_IND to GMM */ + rc = gsm0408_gprs_rcvmsg_gb(msg, lle->llme, + drop_cipherable); + break; + case GPRS_SAPI_SNDCP3: + case GPRS_SAPI_SNDCP5: + case GPRS_SAPI_SNDCP9: + case GPRS_SAPI_SNDCP11: + /* send LL_DATA_IND/LL_UNITDATA_IND to SNDCP */ + rc = sndcp_llunitdata_ind(msg, lle, llhp.data, llhp.data_len); + break; + case GPRS_SAPI_SMS: + /* FIXME */ + case GPRS_SAPI_TOM2: + case GPRS_SAPI_TOM8: + /* FIXME: send LL_DATA_IND/LL_UNITDATA_IND to TOM */ + default: + LOGP(DLLC, LOGL_NOTICE, "Unsupported SAPI %u\n", llhp.sapi); + rc = -EINVAL; + break; + } + } + + return rc; +} + +/* Propagate crypto parameters MM -> LLME */ +void gprs_llme_copy_key(struct sgsn_mm_ctx *mm, struct gprs_llc_llme *llme) +{ + if (!mm) + return; + if (mm->ciph_algo != GPRS_ALGO_GEA0) { + llme->algo = mm->ciph_algo; + if (llme->cksn != mm->auth_triplet.key_seq && + mm->auth_triplet.key_seq != GSM_KEY_SEQ_INVAL) { + memcpy(llme->kc, mm->auth_triplet.vec.kc, + gprs_cipher_key_length(mm->ciph_algo)); + llme->cksn = mm->auth_triplet.key_seq; + } + } else + llme->cksn = GSM_KEY_SEQ_INVAL; +} + +/* 04.64 Chapter 7.2.1.1 LLGMM-ASSIGN */ +int gprs_llgmm_assign(struct gprs_llc_llme *llme, + uint32_t old_tlli, uint32_t new_tlli) +{ + unsigned int i; + + 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->tlli = new_tlli; + llme->state = GPRS_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->vu_send = l->vu_recv = 0; + l->retrans_ctr = 0; + l->state = GPRS_LLES_ASSIGNED_ADM; + /* FIXME Set parameters according to table 9 */ + } + } + } 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 = GPRS_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 = GPRS_LLMS_UNASSIGNED; + for (i = 0; i < ARRAY_SIZE(llme->lle); i++) { + struct gprs_llc_lle *l = &llme->lle[i]; + l->state = GPRS_LLES_UNASSIGNED; + } + llme_free(llme); + } else + return -EINVAL; + + return 0; +} + +/* TLLI unassignment */ +int gprs_llgmm_unassign(struct gprs_llc_llme *llme) +{ + return gprs_llgmm_assign(llme, llme->tlli, TLLI_UNASSIGNED); +} + +/* Chapter 7.2.1.2 LLGMM-RESET.req */ +int gprs_llgmm_reset(struct gprs_llc_llme *llme) +{ + struct msgb *msg = msgb_alloc_headroom(4096, 1024, "LLC_XID"); + struct gprs_llc_lle *lle = &llme->lle[1]; + uint8_t xid_bytes[1024]; + int xid_bytes_len, rc; + uint8_t *xid; + + LOGP(DLLC, LOGL_NOTICE, "LLGM Reset\n"); + + rc = osmo_get_rand_id((uint8_t *) &llme->iov_ui, 4); + if (rc < 0) { + LOGP(DLLC, LOGL_ERROR, "osmo_get_rand_id() failed for LLC XID reset: %s\n", strerror(-rc)); + return rc; + } + + /* Generate XID message */ + xid_bytes_len = gprs_llc_generate_xid_for_gmm_reset(xid_bytes, sizeof(xid_bytes), + llme->iov_ui, lle); + if (xid_bytes_len < 0) + return -EINVAL; + xid = msgb_put(msg, xid_bytes_len); + memcpy(xid, xid_bytes, xid_bytes_len); + + /* 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; + + /* FIXME: Start T200, wait for XID response */ + return gprs_llc_tx_xid(lle, msg, 1); +} + +int gprs_llgmm_reset_oldmsg(struct msgb* oldmsg, uint8_t sapi, + struct gprs_llc_llme *llme) +{ + struct msgb *msg = msgb_alloc_headroom(4096, 1024, "LLC_XID"); + struct gprs_llc_lle *lle = &llme->lle[sapi]; + uint8_t xid_bytes[1024]; + int xid_bytes_len, rc; + uint8_t *xid; + + LOGP(DLLC, LOGL_NOTICE, "LLGM Reset\n"); + + rc = osmo_get_rand_id((uint8_t *) &llme->iov_ui, 4); + if (rc < 0) { + LOGP(DLLC, LOGL_ERROR, "osmo_get_rand_id() failed for LLC XID reset: %s\n", strerror(-rc)); + return rc; + } + + /* Generate XID message */ + xid_bytes_len = gprs_llc_generate_xid_for_gmm_reset(xid_bytes, sizeof(xid_bytes), + llme->iov_ui, lle); + if (xid_bytes_len < 0) + return -EINVAL; + xid = msgb_put(msg, xid_bytes_len); + memcpy(xid, xid_bytes, xid_bytes_len); + + /* FIXME: Start T200, wait for XID response */ + + msgb_tlli(msg) = msgb_tlli(oldmsg); + msgb_bvci(msg) = msgb_bvci(oldmsg); + msgb_nsei(msg) = msgb_nsei(oldmsg); + + return gprs_llc_tx_u(msg, sapi, 1, GPRS_LLC_U_XID, 1); +} + +int gprs_llc_init(const char *cipher_plugin_path) +{ + return gprs_cipher_load(cipher_plugin_path); +} diff --git a/src/sgsn/gprs_llc_vty.c b/src/sgsn/gprs_llc_vty.c new file mode 100644 index 000000000..418be8348 --- /dev/null +++ b/src/sgsn/gprs_llc_vty.c @@ -0,0 +1,115 @@ +/* VTY interface for our GPRS LLC implementation */ + +/* (C) 2010 by Harald Welte + * + * 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 . + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct value_string gprs_llc_state_strs[] = { + { GPRS_LLES_UNASSIGNED, "TLLI Unassigned" }, + { GPRS_LLES_ASSIGNED_ADM, "TLLI Assigned" }, + { GPRS_LLES_LOCAL_EST, "Local Establishment" }, + { GPRS_LLES_REMOTE_EST, "Remote Establishment" }, + { GPRS_LLES_ABM, "Asynchronous Balanced Mode" }, + { GPRS_LLES_LOCAL_REL, "Local Release" }, + { GPRS_LLES_TIMER_REC, "Timer Recovery" }, + { 0, NULL } +}; + +static void vty_dump_lle(struct vty *vty, struct gprs_llc_lle *lle) +{ + struct gprs_llc_params *par = &lle->params; + vty_out(vty, " SAPI %2u State %s VUsend=%u, VUrecv=%u", lle->sapi, + get_value_string(gprs_llc_state_strs, lle->state), + lle->vu_send, lle->vu_recv); + vty_out(vty, " Vsent=%u Vack=%u Vrecv=%u, RetransCtr=%u%s", + lle->v_sent, lle->v_ack, lle->v_recv, + lle->retrans_ctr, VTY_NEWLINE); + vty_out(vty, " T200=%u, N200=%u, N201-U=%u, N201-I=%u, mD=%u, " + "mU=%u, kD=%u, kU=%u%s", par->t200_201, par->n200, + par->n201_u, par->n201_i, par->mD, par->mU, par->kD, + par->kU, VTY_NEWLINE); +} + +static uint8_t valid_sapis[] = { 1, 2, 3, 5, 7, 8, 9, 11 }; + +static void vty_dump_llme(struct vty *vty, struct gprs_llc_llme *llme) +{ + unsigned int i; + struct timespec now_tp = {0}; + osmo_clock_gettime(CLOCK_MONOTONIC, &now_tp); + + vty_out(vty, "TLLI %08x (Old TLLI %08x) BVCI=%u NSEI=%u %s: " + "IOV-UI=0x%06x CKSN=%d Age=%d: State %s%s", llme->tlli, + llme->old_tlli, llme->bvci, llme->nsei, + get_value_string(gprs_cipher_names, llme->algo), llme->iov_ui, + llme->cksn, llme->age_timestamp == GPRS_LLME_RESET_AGE ? 0 : + (int)(now_tp.tv_sec - (time_t)llme->age_timestamp), + get_value_string(gprs_llc_state_strs, llme->state), VTY_NEWLINE); + + for (i = 0; i < ARRAY_SIZE(valid_sapis); i++) { + struct gprs_llc_lle *lle; + uint8_t sapi = valid_sapis[i]; + + if (sapi >= ARRAY_SIZE(llme->lle)) + continue; + + lle = &llme->lle[sapi]; + vty_dump_lle(vty, lle); + } +} + + +DEFUN(show_llc, show_llc_cmd, + "show llc", + SHOW_STR "Display information about the LLC protocol") +{ + struct gprs_llc_llme *llme; + + vty_out(vty, "State of LLC Entities%s", VTY_NEWLINE); + llist_for_each_entry(llme, &gprs_llc_llmes, list) { + vty_dump_llme(vty, llme); + } + return CMD_SUCCESS; +} + +int gprs_llc_vty_init(void) +{ + install_element_ve(&show_llc_cmd); + + return 0; +} diff --git a/src/sgsn/gprs_llc_xid.c b/src/sgsn/gprs_llc_xid.c new file mode 100644 index 000000000..b91fa6bfd --- /dev/null +++ b/src/sgsn/gprs_llc_xid.c @@ -0,0 +1,281 @@ +/* GPRS LLC XID field encoding/decoding as per 3GPP TS 44.064 */ + +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Philipp Maier + * + * 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 . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +const struct value_string gprs_llc_xid_type_names[] = { + { GPRS_LLC_XID_T_VERSION, "VERSION"}, + { GPRS_LLC_XID_T_IOV_UI, "IOV_UI"}, + { GPRS_LLC_XID_T_IOV_I, "IOV_I"}, + { GPRS_LLC_XID_T_T200, "T200"}, + { GPRS_LLC_XID_T_N200, "N200"}, + { GPRS_LLC_XID_T_N201_U, "N201_U"}, + { GPRS_LLC_XID_T_N201_I, "N201_I"}, + { GPRS_LLC_XID_T_mD, "mD"}, + { GPRS_LLC_XID_T_mU, "mU"}, + { GPRS_LLC_XID_T_kD, "kD"}, + { GPRS_LLC_XID_T_kU, "kU"}, + { GPRS_LLC_XID_T_L3_PAR, "L3_PAR"}, + { GPRS_LLC_XID_T_RESET, "RESET"}, + { 0, NULL }, +}; + +/* Parse XID parameter field */ +static int decode_xid_field(struct gprs_llc_xid_field *xid_field, + const uint8_t *src, uint8_t src_len) +{ + uint8_t xl; + uint8_t type; + uint8_t len; + int src_counter = 0; + + /* Exit immediately if it is clear that no + * parseable data is present */ + if (src_len < 1 || !src) + return -EINVAL; + + /* Extract header info */ + xl = (*src >> 7) & 1; + type = (*src >> 2) & 0x1F; + + /* Extract length field */ + len = (*src) & 0x3; + src++; + src_counter++; + if (xl) { + if (src_len < 2) + return -EINVAL; + len = (len << 6) & 0xC0; + len |= ((*src) >> 2) & 0x3F; + src++; + src_counter++; + } + + /* Fill out struct */ + xid_field->type = type; + xid_field->data_len = len; + if (len > 0) { + if (src_len < src_counter + len) + return -EINVAL; + xid_field->data = + talloc_memdup(xid_field,src,xid_field->data_len); + } else + xid_field->data = NULL; + + /* Return consumed length */ + return src_counter + len; +} + +/* Encode XID parameter field */ +static int encode_xid_field(uint8_t *dst, int dst_maxlen, + const struct gprs_llc_xid_field *xid_field) +{ + int xl = 0; + + /* When the length does not fit into 2 bits, + * we need extended length fields */ + if (xid_field->data_len > 3) + xl = 1; + + /* Exit immediately if it is clear that no + * encoding result can be stored */ + if (dst_maxlen < xid_field->data_len + 1 + xl) + return -EINVAL; + + /* There are only 5 bits reserved for the type, exit on exceed */ + if (xid_field->type > 31) + return -EINVAL; + + /* Encode header */ + memset(dst, 0, dst_maxlen); + if (xl) + dst[0] |= 0x80; + dst[0] |= (((xid_field->type) & 0x1F) << 2); + + if (xl) { + dst[0] |= (((xid_field->data_len) >> 6) & 0x03); + dst[1] = ((xid_field->data_len) << 2) & 0xFC; + } else + dst[0] |= ((xid_field->data_len) & 0x03); + + /* Append payload data */ + if (xid_field->data && xid_field->data_len) + memcpy(dst + 1 + xl, xid_field->data, xid_field->data_len); + + /* Return generated length */ + return xid_field->data_len + 1 + xl; +} + +/* Transform a list with XID fields into a XID message (dst) */ +int gprs_llc_compile_xid(uint8_t *dst, int dst_maxlen, + const struct llist_head *xid_fields) +{ + struct gprs_llc_xid_field *xid_field; + int rc; + int byte_counter = 0; + + OSMO_ASSERT(xid_fields); + OSMO_ASSERT(dst); + + llist_for_each_entry_reverse(xid_field, xid_fields, list) { + /* Encode XID-Field */ + rc = encode_xid_field(dst, dst_maxlen, xid_field); + if (rc < 0) + return -EINVAL; + + /* Advance pointer and lower maxlen for the + * next encoding round */ + dst += rc; + byte_counter += rc; + dst_maxlen -= rc; + } + + /* Return generated length */ + return byte_counter; +} + +/* Transform a XID message (dst) into a list of XID fields */ +struct llist_head *gprs_llc_parse_xid(const void *ctx, const uint8_t *src, + int src_len) +{ + struct gprs_llc_xid_field *xid_field; + struct llist_head *xid_fields; + + int rc; + int max_loops = src_len; + + OSMO_ASSERT(src); + + xid_fields = talloc_zero(ctx, struct llist_head); + INIT_LLIST_HEAD(xid_fields); + + while (1) { + /* Bail in case decode_xid_field() constantly returns zero */ + if (max_loops <= 0) { + talloc_free(xid_fields); + return NULL; + } + + /* Decode XID field */ + xid_field = talloc_zero(xid_fields, struct gprs_llc_xid_field); + rc = decode_xid_field(xid_field, src, src_len); + + /* Immediately stop on error */ + if (rc < 0) { + talloc_free(xid_fields); + return NULL; + } + + /* Add parsed XID field to list */ + llist_add(&xid_field->list, xid_fields); + + /* Advance pointer and lower dst_len for the next + * decoding round */ + src += rc; + src_len -= rc; + + /* We are (scuccessfully) done when no further byes are left */ + if (src_len == 0) + return xid_fields; + + max_loops--; + } +} + +/* Create a duplicate of an XID-Field */ +struct gprs_llc_xid_field *gprs_llc_dup_xid_field(const void *ctx, const struct + gprs_llc_xid_field + *xid_field) +{ + struct gprs_llc_xid_field *dup; + + OSMO_ASSERT(xid_field); + + /* Create a copy of the XID field in memory */ + dup = talloc_memdup(ctx, xid_field, sizeof(*xid_field)); + dup->data = talloc_memdup(ctx, xid_field->data, xid_field->data_len); + + /* Unlink duplicate from source list */ + INIT_LLIST_HEAD(&dup->list); + + return dup; +} + +/* Copy an llist with xid fields */ +struct llist_head *gprs_llc_copy_xid(const void *ctx, + const struct llist_head *xid_fields) +{ + struct gprs_llc_xid_field *xid_field; + struct llist_head *xid_fields_copy; + + OSMO_ASSERT(xid_fields); + + xid_fields_copy = talloc_zero(ctx, struct llist_head); + INIT_LLIST_HEAD(xid_fields_copy); + + /* Create duplicates and add them to the target list */ + llist_for_each_entry(xid_field, xid_fields, list) { + llist_add(&gprs_llc_dup_xid_field(ctx, xid_field)->list, + xid_fields_copy); + } + + return xid_fields_copy; +} + +/* Dump a list with XID fields (Debug) */ +void gprs_llc_dump_xid_fields(const struct llist_head *xid_fields, + unsigned int logl) +{ + struct gprs_llc_xid_field *xid_field; + + OSMO_ASSERT(xid_fields); + + llist_for_each_entry(xid_field, xid_fields, list) { + if (xid_field->data_len) { + OSMO_ASSERT(xid_field->data); + LOGP(DLLC, logl, + "XID: type %s, data_len=%d, data=%s\n", + get_value_string(gprs_llc_xid_type_names, + xid_field->type), + xid_field->data_len, + osmo_hexdump_nospc(xid_field->data, + xid_field->data_len)); + } else { + LOGP(DLLC, logl, + "XID: type=%d, data_len=%d, data=NULL\n", + xid_field->type, xid_field->data_len); + } + } +} diff --git a/src/sgsn/gprs_mm_state_gb_fsm.c b/src/sgsn/gprs_mm_state_gb_fsm.c new file mode 100644 index 000000000..2056540db --- /dev/null +++ b/src/sgsn/gprs_mm_state_gb_fsm.c @@ -0,0 +1,112 @@ +#include + +#include + +#include +#include + +#define X(s) (1 << (s)) + +static const struct osmo_tdef_state_timeout mm_state_gb_fsm_timeouts[32] = { + [ST_MM_IDLE] = { }, + [ST_MM_READY] = { .T=3314 }, + [ST_MM_STANDBY] = { }, +}; + +#define mm_state_gb_fsm_state_chg(fi, NEXT_STATE) \ + osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, mm_state_gb_fsm_timeouts, sgsn->cfg.T_defs, -1) + +static void st_mm_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch(event) { + case E_MM_GPRS_ATTACH: + mm_state_gb_fsm_state_chg(fi, ST_MM_READY); + break; + case E_MM_PDU_RECEPTION: + break; + } +} + +static void st_mm_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + unsigned long t_secs; + + switch(event) { + case E_MM_READY_TIMER_EXPIRY: + case E_MM_IMPLICIT_DETACH: + mm_state_gb_fsm_state_chg(fi, ST_MM_STANDBY); + break; + case E_MM_PDU_RECEPTION: + /* RE-arm the READY timer upon receival of Gb PDUs */ + t_secs = osmo_tdef_get(sgsn->cfg.T_defs, 3314, OSMO_TDEF_S, -1); + osmo_timer_schedule(&fi->timer, t_secs, 0); + break; + case E_MM_RA_UPDATE: + break; + } +} + +static void st_mm_standby(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch(event) { + case E_MM_PDU_RECEPTION: + mm_state_gb_fsm_state_chg(fi, ST_MM_READY); + break; + } +} + +static struct osmo_fsm_state mm_state_gb_fsm_states[] = { + [ST_MM_IDLE] = { + .in_event_mask = X(E_MM_GPRS_ATTACH) | X(E_MM_PDU_RECEPTION), + .out_state_mask = X(ST_MM_READY), + .name = "Idle", + .action = st_mm_idle, + }, + [ST_MM_READY] = { + .in_event_mask = X(E_MM_READY_TIMER_EXPIRY) | X(E_MM_RA_UPDATE) | X(E_MM_IMPLICIT_DETACH) | X(E_MM_PDU_RECEPTION), + .out_state_mask = X(ST_MM_IDLE) | X(ST_MM_STANDBY), + .name = "Ready", + .action = st_mm_ready, + }, + [ST_MM_STANDBY] = { + .in_event_mask = X(E_MM_PDU_RECEPTION), + .out_state_mask = X(ST_MM_IDLE) | X(ST_MM_READY), + .name = "Standby", + .action = st_mm_standby, + }, +}; + +const struct value_string mm_state_gb_fsm_event_names[] = { + OSMO_VALUE_STRING(E_MM_GPRS_ATTACH), + OSMO_VALUE_STRING(E_MM_PDU_RECEPTION), + OSMO_VALUE_STRING(E_MM_IMPLICIT_DETACH), + OSMO_VALUE_STRING(E_MM_READY_TIMER_EXPIRY), + OSMO_VALUE_STRING(E_MM_RA_UPDATE), + { 0, NULL } +}; + +int mm_state_gb_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + switch(fi->state) { + case ST_MM_READY: + /* timer for mm state. state=READY: T3314 (aka TS 23.060 "READY timer") */ + osmo_fsm_inst_dispatch(fi, E_MM_READY_TIMER_EXPIRY, NULL); + break; + } + + return 0; +} + +struct osmo_fsm mm_state_gb_fsm = { + .name = "MM_STATE_Gb", + .states = mm_state_gb_fsm_states, + .num_states = ARRAY_SIZE(mm_state_gb_fsm_states), + .event_names = mm_state_gb_fsm_event_names, + .log_subsys = DMM, + .timer_cb = mm_state_gb_fsm_timer_cb, +}; + +static __attribute__((constructor)) void mm_state_gb_fsm_init(void) +{ + osmo_fsm_register(&mm_state_gb_fsm); +} diff --git a/src/sgsn/gprs_mm_state_iu_fsm.c b/src/sgsn/gprs_mm_state_iu_fsm.c new file mode 100644 index 000000000..1ed5f56f1 --- /dev/null +++ b/src/sgsn/gprs_mm_state_iu_fsm.c @@ -0,0 +1,121 @@ +#include + +#include + +#include + +#include +#include + +#define X(s) (1 << (s)) + +static const struct osmo_tdef_state_timeout mm_state_iu_fsm_timeouts[32] = { + [ST_PMM_DETACHED] = { }, + [ST_PMM_CONNECTED] = { }, + [ST_PMM_IDLE] = { }, +}; + +#define mm_state_iu_fsm_state_chg(fi, NEXT_STATE) \ + osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, mm_state_iu_fsm_timeouts, sgsn->cfg.T_defs, -1) + +static void mmctx_change_gtpu_endpoints_to_sgsn(struct sgsn_mm_ctx *mm_ctx) +{ + char buf[INET_ADDRSTRLEN]; + struct sgsn_pdp_ctx *pdp; + llist_for_each_entry(pdp, &mm_ctx->pdp_list, list) { + LOGMMCTXP(LOGL_INFO, mm_ctx, "Changing GTP-U endpoints %s -> %s\n", + sgsn_gtp_ntoa(&pdp->lib->gsnlu), + inet_ntop(AF_INET, &sgsn->cfg.gtp_listenaddr.sin_addr, buf, sizeof(buf))); + sgsn_pdp_upd_gtp_u(pdp, + &sgsn->cfg.gtp_listenaddr.sin_addr, + sizeof(sgsn->cfg.gtp_listenaddr.sin_addr)); + } +} + +static void st_pmm_detached(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch(event) { + case E_PMM_PS_ATTACH: + mm_state_iu_fsm_state_chg(fi, ST_PMM_CONNECTED); + break; + case E_PMM_IMPLICIT_DETACH: + break; + } +} + +static void st_pmm_connected(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch(event) { + case E_PMM_PS_CONN_RELEASE: + mm_state_iu_fsm_state_chg(fi, ST_PMM_IDLE); + break; + case E_PMM_IMPLICIT_DETACH: + mm_state_iu_fsm_state_chg(fi, ST_PMM_DETACHED); + break; + case E_PMM_RA_UPDATE: + break; + } +} + +static void st_pmm_idle_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct sgsn_mm_ctx *ctx = fi->priv; + + mmctx_change_gtpu_endpoints_to_sgsn(ctx); +} + +static void st_pmm_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch(event) { + case E_PMM_PS_CONN_ESTABLISH: + mm_state_iu_fsm_state_chg(fi, ST_PMM_CONNECTED); + break; + case E_PMM_IMPLICIT_DETACH: + mm_state_iu_fsm_state_chg(fi, ST_PMM_DETACHED); + break; + } +} + +static struct osmo_fsm_state mm_state_iu_fsm_states[] = { + [ST_PMM_DETACHED] = { + .in_event_mask = X(E_PMM_PS_ATTACH) | X(E_PMM_IMPLICIT_DETACH), + .out_state_mask = X(ST_PMM_CONNECTED), + .name = "Detached", + .action = st_pmm_detached, + }, + [ST_PMM_CONNECTED] = { + .in_event_mask = X(E_PMM_PS_CONN_RELEASE) | X(E_PMM_RA_UPDATE) | X(E_PMM_IMPLICIT_DETACH), + .out_state_mask = X(ST_PMM_DETACHED) | X(ST_PMM_IDLE), + .name = "Connected", + .action = st_pmm_connected, + }, + [ST_PMM_IDLE] = { + .in_event_mask = X(E_PMM_IMPLICIT_DETACH) | X(E_PMM_PS_CONN_ESTABLISH), + .out_state_mask = X(ST_PMM_DETACHED) | X(ST_PMM_CONNECTED), + .name = "Idle", + .onenter = st_pmm_idle_on_enter, + .action = st_pmm_idle, + }, +}; + +const struct value_string mm_state_iu_fsm_event_names[] = { + OSMO_VALUE_STRING(E_PMM_PS_ATTACH), + OSMO_VALUE_STRING(E_PMM_PS_CONN_RELEASE), + OSMO_VALUE_STRING(E_PMM_PS_CONN_ESTABLISH), + OSMO_VALUE_STRING(E_PMM_IMPLICIT_DETACH), + OSMO_VALUE_STRING(E_PMM_RA_UPDATE), + { 0, NULL } +}; + +struct osmo_fsm mm_state_iu_fsm = { + .name = "MM_STATE_Iu", + .states = mm_state_iu_fsm_states, + .num_states = ARRAY_SIZE(mm_state_iu_fsm_states), + .event_names = mm_state_iu_fsm_event_names, + .log_subsys = DMM, +}; + +static __attribute__((constructor)) void mm_state_iu_fsm_init(void) +{ + osmo_fsm_register(&mm_state_iu_fsm); +} diff --git a/src/sgsn/gprs_ranap.c b/src/sgsn/gprs_ranap.c new file mode 100644 index 000000000..027b6665a --- /dev/null +++ b/src/sgsn/gprs_ranap.c @@ -0,0 +1,231 @@ +/* Messages on the RANAP interface (Iu mode) */ + +/* (C) 2009-2015 by Harald Welte + * (C) 2015 by Holger Hans Peter Freyther + * (C) 2019 by sysmocom s.f.m.c. GmbH + * + * 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 . + * + */ + +#include "bscconfig.h" + +#ifdef BUILD_IU + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +/* Send RAB activation requests for all PDP contexts */ +void activate_pdp_rabs(struct sgsn_mm_ctx *ctx) +{ + struct sgsn_pdp_ctx *pdp; + if (ctx->ran_type != MM_CTX_T_UTRAN_Iu) + return; + llist_for_each_entry(pdp, &ctx->pdp_list, list) { + iu_rab_act_ps(pdp->nsapi, pdp); + } +} + +/* Callback for RAB assignment response */ +static int sgsn_ranap_rab_ass_resp(struct sgsn_mm_ctx *ctx, RANAP_RAB_SetupOrModifiedItemIEs_t *setup_ies) +{ + uint8_t rab_id; + bool require_pdp_update = false; + struct sgsn_pdp_ctx *pdp = NULL; + RANAP_RAB_SetupOrModifiedItem_t *item = &setup_ies->raB_SetupOrModifiedItem; + + rab_id = item->rAB_ID.buf[0]; + + pdp = sgsn_pdp_ctx_by_nsapi(ctx, rab_id); + if (!pdp) { + LOGP(DRANAP, LOGL_ERROR, "RAB Assignment Response for unknown RAB/NSAPI=%u\n", rab_id); + return -1; + } + + if (item->transportLayerAddress) { + LOGPC(DRANAP, LOGL_INFO, " Setup: (%u/%s)", rab_id, osmo_hexdump(item->transportLayerAddress->buf, + item->transportLayerAddress->size)); + switch (item->transportLayerAddress->size) { + case 7: + /* It must be IPv4 inside a X213 NSAP */ + memcpy(pdp->lib->gsnlu.v, &item->transportLayerAddress->buf[3], 4); + break; + case 4: + /* It must be a raw IPv4 address */ + memcpy(pdp->lib->gsnlu.v, item->transportLayerAddress->buf, 4); + break; + case 16: + /* TODO: It must be a raw IPv6 address */ + case 19: + /* TODO: It must be IPv6 inside a X213 NSAP */ + default: + LOGP(DRANAP, LOGL_ERROR, "RAB Assignment Resp: Unknown " + "transport layer address size %u\n", + item->transportLayerAddress->size); + return -1; + } + require_pdp_update = true; + } + + /* The TEI on the RNC side might have changed, too */ + if (item->iuTransportAssociation && + item->iuTransportAssociation->present == RANAP_IuTransportAssociation_PR_gTP_TEI && + item->iuTransportAssociation->choice.gTP_TEI.buf && + item->iuTransportAssociation->choice.gTP_TEI.size >= 4) { + uint32_t tei = osmo_load32be(item->iuTransportAssociation->choice.gTP_TEI.buf); + LOGP(DRANAP, LOGL_DEBUG, "Updating TEID on RNC side from 0x%08x to 0x%08x\n", + pdp->lib->teid_own, tei); + pdp->lib->teid_own = tei; + require_pdp_update = true; + } + + if (require_pdp_update) + gtp_update_context(pdp->ggsn->gsn, pdp->lib, pdp, &pdp->lib->hisaddr0); + + if (pdp->state != PDP_STATE_CR_CONF) { + send_act_pdp_cont_acc(pdp); + pdp->state = PDP_STATE_CR_CONF; + } + return 0; + +} + +int sgsn_ranap_iu_event(struct ranap_ue_conn_ctx *ctx, enum ranap_iu_event_type type, void *data) +{ + struct sgsn_mm_ctx *mm; + int rc = -1; + + mm = sgsn_mm_ctx_by_ue_ctx(ctx); + +#define REQUIRE_MM \ + if (!mm) { \ + LOGIUP(ctx, LOGL_NOTICE, "Cannot find mm ctx for IU event %d\n", type); \ + return rc; \ + } + + switch (type) { + case RANAP_IU_EVENT_RAB_ASSIGN: + REQUIRE_MM + rc = sgsn_ranap_rab_ass_resp(mm, (RANAP_RAB_SetupOrModifiedItemIEs_t *)data); + break; + case RANAP_IU_EVENT_IU_RELEASE: + /* fall thru */ + case RANAP_IU_EVENT_LINK_INVALIDATED: + /* Clean up ranap_ue_conn_ctx here */ + if (mm) { + LOGMMCTXP(LOGL_INFO, mm, "IU release for imsi %s\n", mm->imsi); + osmo_fsm_inst_dispatch(mm->iu.mm_state_fsm, E_PMM_PS_CONN_RELEASE, NULL); + } else + LOGIUP(ctx, LOGL_INFO, "IU release\n"); + rc = 0; + break; + case RANAP_IU_EVENT_SECURITY_MODE_COMPLETE: + REQUIRE_MM + /* Continue authentication here */ + mm->iu.ue_ctx->integrity_active = 1; + + /* FIXME: remove gmm_authorize */ + if (mm->pending_req != GSM48_MT_GMM_ATTACH_REQ) + gsm48_gmm_authorize(mm); + else + osmo_fsm_inst_dispatch(mm->gmm_att_req.fsm, E_IU_SECURITY_CMD_COMPLETE, NULL); + break; + default: + if (mm) + LOGMMCTXP(LOGL_NOTICE, mm, "Unknown event received: %i\n", type); + else + LOGIUP(ctx, LOGL_NOTICE, "Unknown event received: %i\n", type); + rc = -1; + break; + } + return rc; +} + +int iu_rab_act_ps(uint8_t rab_id, struct sgsn_pdp_ctx *pdp) +{ + struct msgb *msg; + struct sgsn_mm_ctx *mm = pdp->mm; + struct ranap_ue_conn_ctx *uectx; + uint32_t ggsn_ip; + bool use_x213_nsap; + + uectx = mm->iu.ue_ctx; + use_x213_nsap = (uectx->rab_assign_addr_enc == RANAP_NSAP_ADDR_ENC_X213); + + /* Get the IP address for ggsn user plane */ + memcpy(&ggsn_ip, pdp->lib->gsnru.v, pdp->lib->gsnru.l); + ggsn_ip = htonl(ggsn_ip); + + LOGP(DRANAP, LOGL_DEBUG, "Assigning RAB: rab_id=%d, ggsn_ip=%x," + " teid_gn=%x, use_x213_nsap=%d\n", + rab_id, ggsn_ip, pdp->lib->teid_gn, use_x213_nsap); + + msg = ranap_new_msg_rab_assign_data(rab_id, ggsn_ip, + pdp->lib->teid_gn, use_x213_nsap); + msg->l2h = msg->data; + return ranap_iu_rab_act(uectx, msg); +} + + +/* Main entry point for incoming 04.08 GPRS messages from Iu */ +int gsm0408_gprs_rcvmsg_iu(struct msgb *msg, struct gprs_ra_id *ra_id, + uint16_t *sai) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t pdisc = gsm48_hdr_pdisc(gh); + struct sgsn_mm_ctx *mmctx; + int rc = -EINVAL; + + mmctx = sgsn_mm_ctx_by_ue_ctx(MSG_IU_UE_CTX(msg)); + if (mmctx) { + rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_SIG_IN]); + if (ra_id) + memcpy(&mmctx->ra, ra_id, sizeof(mmctx->ra)); + } + + /* MMCTX can be NULL */ + + switch (pdisc) { + case GSM48_PDISC_MM_GPRS: + rc = gsm0408_rcv_gmm(mmctx, msg, NULL, false); +#pragma message "set drop_cipherable arg for gsm0408_rcv_gmm() from IuPS?" + break; + case GSM48_PDISC_SM_GPRS: + rc = gsm0408_rcv_gsm(mmctx, msg, NULL); + break; + default: + LOGMMCTXP(LOGL_NOTICE, mmctx, + "Unknown GSM 04.08 discriminator 0x%02x: %s\n", + pdisc, osmo_hexdump((uint8_t *)gh, msgb_l3len(msg))); + /* FIXME: return status message */ + break; + } + + /* MMCTX can be invalid */ + + return rc; +} +#endif diff --git a/src/sgsn/gprs_sgsn.c b/src/sgsn/gprs_sgsn.c new file mode 100644 index 000000000..387c0d5a1 --- /dev/null +++ b/src/sgsn/gprs_sgsn.c @@ -0,0 +1,1017 @@ +/* GPRS SGSN functionality */ + +/* (C) 2009 by Harald Welte + * + * 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 . + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "../../bscconfig.h" + +#define GPRS_LLME_CHECK_TICK 30 + +extern struct sgsn_instance *sgsn; +extern void *tall_sgsn_ctx; + +LLIST_HEAD(sgsn_mm_ctxts); +LLIST_HEAD(sgsn_ggsn_ctxts); +LLIST_HEAD(sgsn_apn_ctxts); +LLIST_HEAD(sgsn_pdp_ctxts); + +const struct value_string sgsn_ran_type_names[] = { + { MM_CTX_T_GERAN_Gb, "GPRS/EDGE via Gb" }, + { MM_CTX_T_UTRAN_Iu, "UMTS via Iu" }, +#if 0 + { MM_CTX_T_GERAN_Iu, "GPRS/EDGE via Iu" }, +#endif + { 0, NULL } +}; + +static const struct rate_ctr_desc mmctx_ctr_description[] = { + { "sign:packets:in", "Signalling Messages ( In)" }, + { "sign:packets:out", "Signalling Messages (Out)" }, + { "udata:packets:in", "User Data Messages ( In)" }, + { "udata:packets:out", "User Data Messages (Out)" }, + { "udata:bytes:in", "User Data Bytes ( In)" }, + { "udata:bytes:out", "User Data Bytes (Out)" }, + { "pdp_ctx_act", "PDP Context Activations " }, + { "suspend", "SUSPEND Count " }, + { "paging:ps", "Paging Packet Switched " }, + { "paging:cs", "Paging Circuit Switched " }, + { "ra_update", "Routing Area Update " }, +}; + +static const struct rate_ctr_group_desc mmctx_ctrg_desc = { + .group_name_prefix = "sgsn:mmctx", + .group_description = "SGSN MM Context Statistics", + .num_ctr = ARRAY_SIZE(mmctx_ctr_description), + .ctr_desc = mmctx_ctr_description, + .class_id = OSMO_STATS_CLASS_SUBSCRIBER, +}; + +static const struct rate_ctr_desc pdpctx_ctr_description[] = { + { "udata:packets:in", "User Data Messages ( In)" }, + { "udata:packets:out", "User Data Messages (Out)" }, + { "udata:bytes:in", "User Data Bytes ( In)" }, + { "udata:bytes:out", "User Data Bytes (Out)" }, +}; + +static const struct rate_ctr_group_desc pdpctx_ctrg_desc = { + .group_name_prefix = "sgsn:pdpctx", + .group_description = "SGSN PDP Context Statistics", + .num_ctr = ARRAY_SIZE(pdpctx_ctr_description), + .ctr_desc = pdpctx_ctr_description, + .class_id = OSMO_STATS_CLASS_SUBSCRIBER, +}; + +static const struct rate_ctr_desc sgsn_ctr_description[] = { + { "llc:dl_bytes", "Count sent LLC bytes before giving it to the bssgp layer" }, + { "llc:ul_bytes", "Count successful received LLC bytes (encrypt & fcs correct)" }, + { "llc:dl_packets", "Count successful sent LLC packets before giving it to the bssgp layer" }, + { "llc:ul_packets", "Count successful received LLC packets (encrypt & fcs correct)" }, + { "gprs:attach_requested", "Received attach requests" }, + { "gprs:attach_accepted", "Sent attach accepts" }, + { "gprs:attach_rejected", "Sent attach rejects" }, + { "gprs:detach_requested", "Received detach requests" }, + { "gprs:detach_acked", "Sent detach acks" }, + { "gprs:routing_area_requested", "Received routing area requests" }, + { "gprs:routing_area_requested", "Sent routing area acks" }, + { "gprs:routing_area_requested", "Sent routing area rejects" }, + { "pdp:activate_requested", "Received activate requests" }, + { "pdp:activate_rejected", "Sent activate rejects" }, + { "pdp:activate_accepted", "Sent activate accepts" }, + { "pdp:request_activated", "unused" }, + { "pdp:request_activate_rejected", "unused" }, + { "pdp:modify_requested", "unused" }, + { "pdp:modify_accepted", "unused" }, + { "pdp:dl_deactivate_requested", "Sent deactivate requests" }, + { "pdp:dl_deactivate_accepted", "Sent deactivate accepted" }, + { "pdp:ul_deactivate_requested", "Received deactivate requests" }, + { "pdp:ul_deactivate_accepted", "Received deactivate accepts" }, +}; + +static const struct rate_ctr_group_desc sgsn_ctrg_desc = { + "sgsn", + "SGSN Overall Statistics", + OSMO_STATS_CLASS_GLOBAL, + ARRAY_SIZE(sgsn_ctr_description), + sgsn_ctr_description, +}; + +void sgsn_rate_ctr_init() { + sgsn->rate_ctrs = rate_ctr_group_alloc(tall_sgsn_ctx, &sgsn_ctrg_desc, 0); + OSMO_ASSERT(sgsn->rate_ctrs); +} + +/* look-up an SGSN MM context based on Iu UE context (struct ue_conn_ctx)*/ +struct sgsn_mm_ctx *sgsn_mm_ctx_by_ue_ctx(const void *uectx) +{ + struct sgsn_mm_ctx *ctx; + + llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { + if (ctx->ran_type == MM_CTX_T_UTRAN_Iu + && uectx == ctx->iu.ue_ctx) + return ctx; + } + + return NULL; +} + +/* look-up a SGSN MM context based on TLLI + RAI */ +struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli(uint32_t tlli, + const struct gprs_ra_id *raid) +{ + struct sgsn_mm_ctx *ctx; + + llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { + if ((tlli == ctx->gb.tlli || tlli == ctx->gb.tlli_new) && + gprs_ra_id_equals(raid, &ctx->ra)) + return ctx; + } + + return NULL; +} + +struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli_and_ptmsi(uint32_t tlli, + const struct gprs_ra_id *raid) +{ + struct sgsn_mm_ctx *ctx; + int tlli_type; + + /* TODO: Also check the P_TMSI signature to be safe. That signature + * should be different (at least with a sufficiently high probability) + * after SGSN restarts and for multiple SGSN instances. + */ + + tlli_type = gprs_tlli_type(tlli); + if (tlli_type != TLLI_FOREIGN && tlli_type != TLLI_LOCAL) + return NULL; + + llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { + if ((gprs_tmsi2tlli(ctx->p_tmsi, tlli_type) == tlli || + gprs_tmsi2tlli(ctx->p_tmsi_old, tlli_type) == tlli) && + gprs_ra_id_equals(raid, &ctx->ra)) + return ctx; + } + + return NULL; +} + +struct sgsn_mm_ctx *sgsn_mm_ctx_by_ptmsi(uint32_t p_tmsi) +{ + struct sgsn_mm_ctx *ctx; + + llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { + if (p_tmsi == ctx->p_tmsi || + (ctx->p_tmsi_old && ctx->p_tmsi_old == p_tmsi)) + return ctx; + } + return NULL; +} + +struct sgsn_mm_ctx *sgsn_mm_ctx_by_imsi(const char *imsi) +{ + struct sgsn_mm_ctx *ctx; + + llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { + if (!strcmp(imsi, ctx->imsi)) + return ctx; + } + return NULL; + +} + +/* Allocate a new SGSN MM context, generic part */ +struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t rate_ctr_id) +{ + struct sgsn_mm_ctx *ctx; + + ctx = talloc_zero(tall_sgsn_ctx, struct sgsn_mm_ctx); + if (!ctx) + return NULL; + + ctx->gmm_state = GMM_DEREGISTERED; + ctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL; + ctx->ctrg = rate_ctr_group_alloc(ctx, &mmctx_ctrg_desc, rate_ctr_id); + if (!ctx->ctrg) { + LOGMMCTXP(LOGL_ERROR, ctx, "Cannot allocate counter group\n"); + talloc_free(ctx); + return NULL; + } + ctx->gmm_att_req.fsm = osmo_fsm_inst_alloc(&gmm_attach_req_fsm, ctx, ctx, LOGL_DEBUG, "gb_gmm_req"); + INIT_LLIST_HEAD(&ctx->pdp_list); + + llist_add(&ctx->list, &sgsn_mm_ctxts); + + return ctx; +} +/* Allocate a new SGSN MM context for GERAN_Gb */ +struct sgsn_mm_ctx *sgsn_mm_ctx_alloc_gb(uint32_t tlli, + const struct gprs_ra_id *raid) +{ + struct sgsn_mm_ctx *ctx; + char buf[32]; + + ctx = sgsn_mm_ctx_alloc(tlli); + if (!ctx) + return NULL; + + memcpy(&ctx->ra, raid, sizeof(ctx->ra)); + ctx->ran_type = MM_CTX_T_GERAN_Gb; + ctx->gb.tlli = tlli; + ctx->ciph_algo = sgsn->cfg.cipher; + snprintf(buf, sizeof(buf), "%" PRIu32, tlli); + ctx->gb.mm_state_fsm = osmo_fsm_inst_alloc(&mm_state_gb_fsm, ctx, ctx, LOGL_DEBUG, buf); + + LOGMMCTXP(LOGL_DEBUG, ctx, "Allocated with %s cipher.\n", + get_value_string(gprs_cipher_names, ctx->ciph_algo)); + return ctx; +} + +/* Allocate a new SGSN MM context for UTRAN_Iu */ +struct sgsn_mm_ctx *sgsn_mm_ctx_alloc_iu(void *uectx) +{ +#if BUILD_IU + char buf[32]; + struct sgsn_mm_ctx *ctx; + struct ranap_ue_conn_ctx *ue_ctx = uectx; + + ctx = sgsn_mm_ctx_alloc(ue_ctx->conn_id); + if (!ctx) + return NULL; + + /* Need to get RAID from IU conn */ + ctx->ra = ue_ctx->ra_id; + ctx->ran_type = MM_CTX_T_UTRAN_Iu; + ctx->iu.ue_ctx = ue_ctx; + ctx->iu.ue_ctx->rab_assign_addr_enc = sgsn->cfg.iu.rab_assign_addr_enc; + ctx->iu.new_key = 1; + snprintf(buf, sizeof(buf), "%" PRIu32, ue_ctx->conn_id); + ctx->iu.mm_state_fsm = osmo_fsm_inst_alloc(&mm_state_iu_fsm, ctx, ctx, LOGL_DEBUG, buf); + + + return ctx; +#else + return NULL; +#endif +} + + +/* this is a hard _free_ function, it doesn't clean up the PDP contexts + * in libgtp! */ +static void sgsn_mm_ctx_free(struct sgsn_mm_ctx *mm) +{ + struct sgsn_pdp_ctx *pdp, *pdp2; + + /* Unlink from global list of MM contexts */ + llist_del(&mm->list); + + /* Free all PDP contexts */ + llist_for_each_entry_safe(pdp, pdp2, &mm->pdp_list, list) + sgsn_pdp_ctx_free(pdp); + + rate_ctr_group_free(mm->ctrg); + + talloc_free(mm); +} + +void sgsn_mm_ctx_cleanup_free(struct sgsn_mm_ctx *mm) +{ + struct gprs_llc_llme *llme = NULL; + struct sgsn_pdp_ctx *pdp, *pdp2; + struct sgsn_signal_data sig_data; + + if (mm->ran_type == MM_CTX_T_GERAN_Gb) + llme = mm->gb.llme; + else + OSMO_ASSERT(mm->gb.llme == NULL); + + /* Forget about ongoing look-ups */ + if (mm->ggsn_lookup) { + LOGMMCTXP(LOGL_NOTICE, mm, + "Cleaning mmctx with on-going query.\n"); + mm->ggsn_lookup->mmctx = NULL; + mm->ggsn_lookup = NULL; + } + + /* delete all existing PDP contexts for this MS */ + llist_for_each_entry_safe(pdp, pdp2, &mm->pdp_list, list) { + LOGMMCTXP(LOGL_NOTICE, mm, + "Dropping PDP context for NSAPI=%u\n", pdp->nsapi); + sgsn_pdp_ctx_terminate(pdp); + } + + if (osmo_timer_pending(&mm->timer)) { + LOGMMCTXP(LOGL_INFO, mm, "Cancelling MM timer %u\n", mm->T); + osmo_timer_del(&mm->timer); + } + + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.mm = mm; + osmo_signal_dispatch(SS_SGSN, S_SGSN_MM_FREE, &sig_data); + + + /* Detach from subscriber which is possibly freed then */ + if (mm->subscr) { + struct gprs_subscr *subscr = gprs_subscr_get(mm->subscr); + gprs_subscr_cleanup(subscr); + gprs_subscr_put(subscr); + } + + if (mm->gmm_att_req.fsm) + gmm_att_req_free(mm); + if (mm->gb.mm_state_fsm) + osmo_fsm_inst_free(mm->gb.mm_state_fsm); + if (mm->iu.mm_state_fsm) + osmo_fsm_inst_free(mm->iu.mm_state_fsm); + + sgsn_mm_ctx_free(mm); + mm = NULL; + + if (llme) { + /* TLLI unassignment, must be called after sgsn_mm_ctx_free */ + if (gprs_llgmm_unassign(llme) < 0) + LOGMMCTXP(LOGL_ERROR, mm, "gprs_llgmm_unassign failed, llme not freed!\n"); + } +} + + +/* look up PDP context by MM context and NSAPI */ +struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_nsapi(const struct sgsn_mm_ctx *mm, + uint8_t nsapi) +{ + struct sgsn_pdp_ctx *pdp; + + llist_for_each_entry(pdp, &mm->pdp_list, list) { + if (pdp->nsapi == nsapi) + return pdp; + } + return NULL; +} + +/* look up PDP context by MM context and transaction ID */ +struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_tid(const struct sgsn_mm_ctx *mm, + uint8_t tid) +{ + struct sgsn_pdp_ctx *pdp; + + llist_for_each_entry(pdp, &mm->pdp_list, list) { + if (pdp->ti == tid) + return pdp; + } + return NULL; +} + +/* you don't want to use this directly, call sgsn_create_pdp_ctx() */ +struct sgsn_pdp_ctx *sgsn_pdp_ctx_alloc(struct sgsn_mm_ctx *mm, + struct sgsn_ggsn_ctx *ggsn, + uint8_t nsapi) +{ + struct sgsn_pdp_ctx *pdp; + + pdp = sgsn_pdp_ctx_by_nsapi(mm, nsapi); + if (pdp) + return NULL; + + pdp = talloc_zero(tall_sgsn_ctx, struct sgsn_pdp_ctx); + if (!pdp) + return NULL; + + pdp->mm = mm; + pdp->ggsn = ggsn; + pdp->nsapi = nsapi; + pdp->ctrg = rate_ctr_group_alloc(pdp, &pdpctx_ctrg_desc, nsapi); + if (!pdp->ctrg) { + LOGPDPCTXP(LOGL_ERROR, pdp, "Error allocation counter group\n"); + talloc_free(pdp); + return NULL; + } + llist_add(&pdp->list, &mm->pdp_list); + sgsn_ggsn_ctx_add_pdp(pdp->ggsn, pdp); + llist_add(&pdp->g_list, &sgsn_pdp_ctxts); + + return pdp; +} + +/* + * This function will not trigger any GSM DEACT PDP ACK messages, so you + * probably want to call sgsn_delete_pdp_ctx() instead if the connection + * isn't detached already. + */ +void sgsn_pdp_ctx_terminate(struct sgsn_pdp_ctx *pdp) +{ + struct sgsn_signal_data sig_data; + + OSMO_ASSERT(pdp->mm != NULL); + + /* There might still be pending callbacks in libgtp. So the parts of + * this object relevant to GTP need to remain intact in this case. */ + + LOGPDPCTXP(LOGL_INFO, pdp, "Forcing release of PDP context\n"); + + if (pdp->mm->ran_type == MM_CTX_T_GERAN_Gb) { + /* Force the deactivation of the SNDCP layer */ + sndcp_sm_deactivate_ind(&pdp->mm->gb.llme->lle[pdp->sapi], pdp->nsapi); + } + + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.pdp = pdp; + osmo_signal_dispatch(SS_SGSN, S_SGSN_PDP_TERMINATE, &sig_data); + + /* Detach from MM context */ + pdp_ctx_detach_mm_ctx(pdp); + if (pdp->ggsn) + sgsn_delete_pdp_ctx(pdp); +} + +/* + * Don't call this function directly unless you know what you are doing. + * In normal conditions use sgsn_delete_pdp_ctx and in unspecified or + * implementation dependent abnormal ones sgsn_pdp_ctx_terminate. + */ +void sgsn_pdp_ctx_free(struct sgsn_pdp_ctx *pdp) +{ + struct sgsn_signal_data sig_data; + + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.pdp = pdp; + osmo_signal_dispatch(SS_SGSN, S_SGSN_PDP_FREE, &sig_data); + + rate_ctr_group_free(pdp->ctrg); + if (pdp->mm) + llist_del(&pdp->list); + if (pdp->ggsn) + sgsn_ggsn_ctx_remove_pdp(pdp->ggsn, pdp); + llist_del(&pdp->g_list); + + /* _if_ we still have a library handle, at least set it to NULL + * to avoid any dereferences of the now-deleted PDP context from + * sgsn_libgtp:cb_data_ind() */ + if (pdp->lib) { + struct pdp_t *lib = pdp->lib; + LOGPDPCTXP(LOGL_NOTICE, pdp, "freeing PDP context that still " + "has a libgtp handle attached to it, this shouldn't " + "happen!\n"); + osmo_generate_backtrace(); + lib->priv = NULL; + } + + talloc_free(pdp); +} + +void sgsn_ggsn_ctx_check_echo_timer(struct sgsn_ggsn_ctx *ggc) +{ + bool pending = osmo_timer_pending(&ggc->echo_timer); + + /* Only enable if allowed by policy and at least 1 pdp ctx exists against ggsn */ + if (!llist_empty(&ggc->pdp_list) && ggc->echo_interval) { + if (!pending) + osmo_timer_schedule(&ggc->echo_timer, ggc->echo_interval, 0); + } else { + if (pending) + osmo_timer_del(&ggc->echo_timer); + } +} + +/* GGSN contexts */ +static void echo_timer_cb(void *data) +{ + struct sgsn_ggsn_ctx *ggc = (struct sgsn_ggsn_ctx *) data; + sgsn_ggsn_echo_req(ggc); + osmo_timer_schedule(&ggc->echo_timer, ggc->echo_interval, 0); +} + +struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_alloc(uint32_t id) +{ + struct sgsn_ggsn_ctx *ggc; + + ggc = talloc_zero(tall_sgsn_ctx, struct sgsn_ggsn_ctx); + if (!ggc) + return NULL; + + ggc->id = id; + ggc->gtp_version = 1; + ggc->remote_restart_ctr = -1; + /* if we are called from config file parse, this gsn doesn't exist yet */ + ggc->gsn = sgsn->gsn; + INIT_LLIST_HEAD(&ggc->pdp_list); + osmo_timer_setup(&ggc->echo_timer, echo_timer_cb, ggc); + llist_add(&ggc->list, &sgsn_ggsn_ctxts); + + return ggc; +} + +void sgsn_ggsn_ctx_free(struct sgsn_ggsn_ctx *ggc) +{ + OSMO_ASSERT(llist_empty(&ggc->pdp_list)); + llist_del(&ggc->list); + talloc_free(ggc); +} + +struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_id(uint32_t id) +{ + struct sgsn_ggsn_ctx *ggc; + + llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) { + if (id == ggc->id) + return ggc; + } + return NULL; +} + +struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_addr(struct in_addr *addr) +{ + struct sgsn_ggsn_ctx *ggc; + + llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) { + if (!memcmp(addr, &ggc->remote_addr, sizeof(*addr))) + return ggc; + } + return NULL; +} + + +struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_find_alloc(uint32_t id) +{ + struct sgsn_ggsn_ctx *ggc; + + ggc = sgsn_ggsn_ctx_by_id(id); + if (!ggc) + ggc = sgsn_ggsn_ctx_alloc(id); + return ggc; +} + +/* APN contexts */ + +static struct apn_ctx *sgsn_apn_ctx_alloc(const char *ap_name, const char *imsi_prefix) +{ + struct apn_ctx *actx; + + actx = talloc_zero(tall_sgsn_ctx, struct apn_ctx); + if (!actx) + return NULL; + actx->name = talloc_strdup(actx, ap_name); + actx->imsi_prefix = talloc_strdup(actx, imsi_prefix); + + llist_add_tail(&actx->list, &sgsn_apn_ctxts); + + return actx; +} + +void sgsn_apn_ctx_free(struct apn_ctx *actx) +{ + llist_del(&actx->list); + talloc_free(actx); +} + +struct apn_ctx *sgsn_apn_ctx_match(const char *name, const char *imsi) +{ + struct apn_ctx *actx; + struct apn_ctx *found_actx = NULL; + size_t imsi_prio = 0; + size_t name_prio = 0; + size_t name_req_len = strlen(name); + + llist_for_each_entry(actx, &sgsn_apn_ctxts, list) { + size_t name_ref_len, imsi_ref_len; + const char *name_ref_start, *name_match_start; + + imsi_ref_len = strlen(actx->imsi_prefix); + if (strncmp(actx->imsi_prefix, imsi, imsi_ref_len) != 0) + continue; + + if (imsi_ref_len < imsi_prio) + continue; + + /* IMSI matches */ + + name_ref_start = &actx->name[0]; + if (name_ref_start[0] == '*') { + /* Suffix match */ + name_ref_start += 1; + name_ref_len = strlen(name_ref_start); + if (name_ref_len > name_req_len) + continue; + } else { + name_ref_len = strlen(name_ref_start); + if (name_ref_len != name_req_len) + continue; + } + + name_match_start = name + (name_req_len - name_ref_len); + if (strcasecmp(name_match_start, name_ref_start) != 0) + continue; + + /* IMSI and name match */ + + if (imsi_ref_len == imsi_prio && name_ref_len < name_prio) + /* Lower priority, skip */ + continue; + + imsi_prio = imsi_ref_len; + name_prio = name_ref_len; + found_actx = actx; + } + return found_actx; +} + +struct apn_ctx *sgsn_apn_ctx_by_name(const char *name, const char *imsi_prefix) +{ + struct apn_ctx *actx; + + llist_for_each_entry(actx, &sgsn_apn_ctxts, list) { + if (strcasecmp(name, actx->name) == 0 && + strcasecmp(imsi_prefix, actx->imsi_prefix) == 0) + return actx; + } + return NULL; +} + +struct apn_ctx *sgsn_apn_ctx_find_alloc(const char *name, const char *imsi_prefix) +{ + struct apn_ctx *actx; + + actx = sgsn_apn_ctx_by_name(name, imsi_prefix); + if (!actx) + actx = sgsn_apn_ctx_alloc(name, imsi_prefix); + + return actx; +} + +uint32_t sgsn_alloc_ptmsi(void) +{ + struct sgsn_mm_ctx *mm; + uint32_t ptmsi = 0xdeadbeef; + int max_retries = 100, rc = 0; + +restart: + rc = osmo_get_rand_id((uint8_t *) &ptmsi, sizeof(ptmsi)); + if (rc < 0) + goto failed; + + /* Enforce that the 2 MSB are set without loosing the distance between + * identical values. Since rand() has no duplicate values within a + * period (because the size of the state is the same like the size of + * the random value), this leads to a distance of period/4 when the + * distribution of the 2 MSB is uniform. This approach fails with a + * probability of (3/4)^max_retries, only 1% of the approaches will + * need more than 16 numbers (even distribution assumed). + * + * Alternatively, a freeze list could be used if another PRNG is used + * or when this approach proves to be not sufficient. + */ + if (ptmsi >= GSM23003_TMSI_SGSN_MASK) { + if (!max_retries--) + goto failed; + goto restart; + } + ptmsi |= GSM23003_TMSI_SGSN_MASK; + + if (ptmsi == GSM_RESERVED_TMSI) { + if (!max_retries--) + goto failed; + goto restart; + } + + llist_for_each_entry(mm, &sgsn_mm_ctxts, list) { + if (mm->p_tmsi == ptmsi) { + if (!max_retries--) + goto failed; + goto restart; + } + } + + return ptmsi; + +failed: + LOGP(DGPRS, LOGL_ERROR, "Failed to allocate a P-TMSI: %d (%s)\n", rc, strerror(-rc)); + return GSM_RESERVED_TMSI; +} + +void sgsn_ggsn_ctx_drop_pdp(struct sgsn_pdp_ctx *pctx) +{ + /* the MM context can be deleted while the GGSN is not reachable or + * if has been crashed. */ + if (pctx->mm && pctx->mm->gmm_state == GMM_REGISTERED_NORMAL) { + gsm48_tx_gsm_deact_pdp_req(pctx, GSM_CAUSE_NET_FAIL, true); + sgsn_ggsn_ctx_remove_pdp(pctx->ggsn, pctx); + } else { + /* FIXME: GPRS paging in case MS is SUSPENDED */ + LOGPDPCTXP(LOGL_NOTICE, pctx, "Hard-dropping PDP ctx due to GGSN " + "recovery\n"); + /* FIXME: how to tell this to libgtp? */ + sgsn_pdp_ctx_free(pctx); + } +} + +/* 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. */ +int sgsn_ggsn_ctx_drop_all_pdp_except(struct sgsn_ggsn_ctx *ggsn, struct sgsn_pdp_ctx *except) +{ + int num = 0; + + struct sgsn_pdp_ctx *pdp, *pdp2; + llist_for_each_entry_safe(pdp, pdp2, &ggsn->pdp_list, ggsn_list) { + if (pdp == except) + continue; + sgsn_ggsn_ctx_drop_pdp(pdp); + num++; + } + + return num; +} + +int sgsn_ggsn_ctx_drop_all_pdp(struct sgsn_ggsn_ctx *ggsn) +{ + return sgsn_ggsn_ctx_drop_all_pdp_except(ggsn, NULL); +} + +void sgsn_ggsn_ctx_add_pdp(struct sgsn_ggsn_ctx *ggc, struct sgsn_pdp_ctx *pdp) +{ + llist_add(&pdp->ggsn_list, &ggc->pdp_list); + sgsn_ggsn_ctx_check_echo_timer(ggc); +} +void sgsn_ggsn_ctx_remove_pdp(struct sgsn_ggsn_ctx *ggc, struct sgsn_pdp_ctx *pdp) +{ + llist_del(&pdp->ggsn_list); + sgsn_ggsn_ctx_check_echo_timer(ggc); + if (pdp->destroy_ggsn) + sgsn_ggsn_ctx_free(pdp->ggsn); + pdp->ggsn = NULL; + /* Drop references to libgtp since the conn is down */ + if (pdp->lib) + pdp_freepdp(pdp->lib); + pdp->lib = NULL; +} + +void sgsn_update_subscriber_data(struct sgsn_mm_ctx *mmctx) +{ + OSMO_ASSERT(mmctx != NULL); + LOGMMCTXP(LOGL_INFO, mmctx, "Subscriber data update\n"); + + sgsn_auth_update(mmctx); +} + +static void insert_extra(struct tlv_parsed *tp, + struct sgsn_subscriber_data *data, + struct sgsn_subscriber_pdp_data *pdp) +{ + tp->lv[OSMO_IE_GSM_SUB_QOS].len = pdp->qos_subscribed_len; + tp->lv[OSMO_IE_GSM_SUB_QOS].val = pdp->qos_subscribed; + + /* Prefer PDP charging characteristics of per subscriber one */ + if (pdp->has_pdp_charg) { + tp->lv[OSMO_IE_GSM_CHARG_CHAR].len = sizeof(pdp->pdp_charg); + tp->lv[OSMO_IE_GSM_CHARG_CHAR].val = &pdp->pdp_charg[0]; + } else if (data->has_pdp_charg) { + tp->lv[OSMO_IE_GSM_CHARG_CHAR].len = sizeof(data->pdp_charg); + tp->lv[OSMO_IE_GSM_CHARG_CHAR].val = &data->pdp_charg[0]; + } +} + +/** + * The tlv_parsed tp parameter will be modified to insert a + * OSMO_IE_GSM_SUB_QOS in case the data is available in the + * PDP context handling. + */ +struct sgsn_ggsn_ctx *sgsn_mm_ctx_find_ggsn_ctx(struct sgsn_mm_ctx *mmctx, + struct tlv_parsed *tp, + enum gsm48_gsm_cause *gsm_cause, + char *out_apn_str) +{ + char req_apn_str[GSM_APN_LENGTH] = {0}; + const struct apn_ctx *apn_ctx = NULL; + const char *selected_apn_str = NULL; + struct sgsn_subscriber_pdp_data *pdp; + struct sgsn_ggsn_ctx *ggsn = NULL; + int allow_any_apn = 0; + + out_apn_str[0] = '\0'; + + if (TLVP_PRESENT(tp, GSM48_IE_GSM_APN)) { + if (TLVP_LEN(tp, GSM48_IE_GSM_APN) >= GSM_APN_LENGTH - 1) { + LOGMMCTXP(LOGL_ERROR, mmctx, "APN IE too long\n"); + *gsm_cause = GSM_CAUSE_INV_MAND_INFO; + return NULL; + } + + osmo_apn_to_str(req_apn_str, + TLVP_VAL(tp, GSM48_IE_GSM_APN), + TLVP_LEN(tp, GSM48_IE_GSM_APN)); + + if (strcmp(req_apn_str, "*") == 0) + req_apn_str[0] = 0; + } + + if (mmctx->subscr == NULL) + allow_any_apn = 1; + + if (strlen(req_apn_str) == 0 && !allow_any_apn) { + /* No specific APN requested, check for an APN that is both + * granted and configured */ + + llist_for_each_entry(pdp, &mmctx->subscr->sgsn_data->pdp_list, list) { + if (strcmp(pdp->apn_str, "*") == 0) + { + allow_any_apn = 1; + selected_apn_str = ""; + insert_extra(tp, mmctx->subscr->sgsn_data, pdp); + continue; + } + if (!llist_empty(&sgsn_apn_ctxts)) { + apn_ctx = sgsn_apn_ctx_match(req_apn_str, mmctx->imsi); + /* Not configured */ + if (apn_ctx == NULL) + continue; + } + insert_extra(tp, mmctx->subscr->sgsn_data, pdp); + selected_apn_str = pdp->apn_str; + break; + } + } else if (!allow_any_apn) { + /* Check whether the given APN is granted */ + llist_for_each_entry(pdp, &mmctx->subscr->sgsn_data->pdp_list, list) { + if (strcmp(pdp->apn_str, "*") == 0) { + insert_extra(tp, mmctx->subscr->sgsn_data, pdp); + selected_apn_str = req_apn_str; + allow_any_apn = 1; + continue; + } + if (strcasecmp(pdp->apn_str, req_apn_str) == 0) { + insert_extra(tp, mmctx->subscr->sgsn_data, pdp); + selected_apn_str = req_apn_str; + break; + } + } + } else if (strlen(req_apn_str) != 0) { + /* Any APN is allowed */ + selected_apn_str = req_apn_str; + } else { + /* Prefer the GGSN associated with the wildcard APN */ + selected_apn_str = ""; + } + + if (!allow_any_apn && selected_apn_str == NULL) { + /* Access not granted */ + LOGMMCTXP(LOGL_NOTICE, mmctx, + "The requested APN '%s' is not allowed\n", + req_apn_str); + *gsm_cause = GSM_CAUSE_REQ_SERV_OPT_NOTSUB; + return NULL; + } + + /* copy the selected apn_str */ + if (selected_apn_str) + strcpy(out_apn_str, selected_apn_str); + else + out_apn_str[0] = '\0'; + + if (apn_ctx == NULL && selected_apn_str) + apn_ctx = sgsn_apn_ctx_match(selected_apn_str, mmctx->imsi); + + if (apn_ctx != NULL) { + ggsn = apn_ctx->ggsn; + } else if (llist_empty(&sgsn_apn_ctxts)) { + /* No configuration -> use GGSN 0 */ + ggsn = sgsn_ggsn_ctx_by_id(0); + } else if (allow_any_apn && + (selected_apn_str == NULL || strlen(selected_apn_str) == 0)) { + /* No APN given and no default configuration -> Use GGSN 0 */ + ggsn = sgsn_ggsn_ctx_by_id(0); + } else { + /* No matching configuration found */ + LOGMMCTXP(LOGL_NOTICE, mmctx, + "The selected APN '%s' has not been configured\n", + selected_apn_str); + *gsm_cause = GSM_CAUSE_MISSING_APN; + return NULL; + } + + if (!ggsn) { + LOGMMCTXP(LOGL_NOTICE, mmctx, + "No static GGSN configured. Selected APN '%s'\n", + selected_apn_str); + *gsm_cause = GSM_CAUSE_MISSING_APN; + return NULL; + } + + LOGMMCTXP(LOGL_INFO, mmctx, + "Found GGSN %d for APN '%s' (requested '%s')\n", + ggsn->id, selected_apn_str ? selected_apn_str : "---", + req_apn_str); + + return ggsn; +} + +static void sgsn_llme_cleanup_free(struct gprs_llc_llme *llme) +{ + struct sgsn_mm_ctx *mmctx = NULL; + + llist_for_each_entry(mmctx, &sgsn_mm_ctxts, list) { + if (llme == mmctx->gb.llme) { + gsm0408_gprs_access_cancelled(mmctx, SGSN_ERROR_CAUSE_NONE); + return; + } + } + + /* No MM context found */ + LOGP(DGPRS, LOGL_INFO, "Deleting orphaned LLME, TLLI 0x%08x\n", + llme->tlli); + gprs_llgmm_unassign(llme); +} + +static void sgsn_llme_check_cb(void *data_) +{ + struct gprs_llc_llme *llme, *llme_tmp; + struct timespec now_tp; + time_t now, age; + time_t max_age = gprs_max_time_to_idle(); + + int rc; + + rc = osmo_clock_gettime(CLOCK_MONOTONIC, &now_tp); + OSMO_ASSERT(rc >= 0); + now = now_tp.tv_sec; + + LOGP(DGPRS, LOGL_DEBUG, + "Checking for inactive LLMEs, time = %u\n", (unsigned)now); + + llist_for_each_entry_safe(llme, llme_tmp, &gprs_llc_llmes, list) { + if (llme->age_timestamp == GPRS_LLME_RESET_AGE) + llme->age_timestamp = now; + + age = now - llme->age_timestamp; + + if (age > max_age || age < 0) { + LOGP(DGPRS, LOGL_INFO, + "Inactivity timeout for TLLI 0x%08x, age %d\n", + llme->tlli, (int)age); + sgsn_llme_cleanup_free(llme); + } + } + + osmo_timer_schedule(&sgsn->llme_timer, GPRS_LLME_CHECK_TICK, 0); +} + +struct sgsn_instance *sgsn_instance_alloc(void *talloc_ctx) +{ + struct sgsn_instance *inst; + inst = talloc_zero(talloc_ctx, struct sgsn_instance); + inst->cfg.gtp_statedir = talloc_strdup(inst, "./"); + inst->cfg.auth_policy = SGSN_AUTH_POLICY_CLOSED; + inst->cfg.require_authentication = true; /* only applies if auth_policy is REMOTE */ + inst->cfg.gsup_server_port = OSMO_GSUP_PORT; + return inst; +} + +void sgsn_inst_init(struct sgsn_instance *sgsn) +{ + osmo_timer_setup(&sgsn->llme_timer, sgsn_llme_check_cb, NULL); + osmo_timer_schedule(&sgsn->llme_timer, GPRS_LLME_CHECK_TICK, 0); +} diff --git a/src/sgsn/gprs_sndcp.c b/src/sgsn/gprs_sndcp.c new file mode 100644 index 000000000..d9aa1e576 --- /dev/null +++ b/src/sgsn/gprs_sndcp.c @@ -0,0 +1,1265 @@ +/* GPRS SNDCP protocol implementation as per 3GPP TS 04.65 */ + +/* (C) 2010 by Harald Welte + * (C) 2010 by On-Waves + * + * 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 . + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_IP_PACKETS 0 /* 0=Disabled, 1=Enabled */ + +#if DEBUG_IP_PACKETS == 1 +/* Calculate TCP/IP checksum */ +static uint16_t calc_ip_csum(uint8_t *data, int len) +{ + int i; + uint32_t accumulator = 0; + uint16_t *pointer = (uint16_t *) data; + + for (i = len; i > 1; i -= 2) { + accumulator += *pointer; + pointer++; + } + + if (len % 2) + accumulator += *pointer; + + accumulator = (accumulator & 0xffff) + ((accumulator >> 16) & 0xffff); + accumulator += (accumulator >> 16) & 0xffff; + return (~accumulator); +} + +/* Calculate TCP/IP checksum */ +static uint16_t calc_tcpip_csum(const void *ctx, uint8_t *packet, int len) +{ + uint8_t *buf; + uint16_t csum; + + buf = talloc_zero_size(ctx, len); + memset(buf, 0, len); + memcpy(buf, packet + 12, 8); + buf[9] = packet[9]; + buf[11] = (len - 20) & 0xFF; + buf[10] = (len - 20) >> 8 & 0xFF; + memcpy(buf + 12, packet + 20, len - 20); + csum = calc_ip_csum(buf, len - 20 + 12); + talloc_free(buf); + return csum; +} + +/* Show some ip packet details */ +static void debug_ip_packet(uint8_t *data, int len, int dir, char *info) +{ + uint8_t tcp_flags; + char flags_debugmsg[256]; + int len_short; + static unsigned int packet_count = 0; + static unsigned int tcp_csum_err_count = 0; + static unsigned int ip_csum_err_count = 0; + + packet_count++; + + if (len > 80) + len_short = 80; + else + len_short = len; + + if (dir) + DEBUGP(DSNDCP, "%s: MS => SGSN: %s\n", info, + osmo_hexdump_nospc(data, len_short)); + else + DEBUGP(DSNDCP, "%s: MS <= SGSN: %s\n", info, + osmo_hexdump_nospc(data, len_short)); + + DEBUGP(DSNDCP, "%s: Length.: %d\n", info, len); + DEBUGP(DSNDCP, "%s: NO.: %d\n", info, packet_count); + + if (len < 20) { + DEBUGP(DSNDCP, "%s: Error: Short IP packet!\n", info); + return; + } + + if (calc_ip_csum(data, 20) != 0) { + DEBUGP(DSNDCP, "%s: Bad IP-Header checksum!\n", info); + ip_csum_err_count++; + } else + DEBUGP(DSNDCP, "%s: IP-Header checksum ok.\n", info); + + if (data[9] == 0x06) { + if (len < 40) { + DEBUGP(DSNDCP, "%s: Error: Short TCP packet!\n", info); + return; + } + + DEBUGP(DSNDCP, "%s: Protocol type: TCP\n", info); + tcp_flags = data[33]; + + if (calc_tcpip_csum(NULL, data, len) != 0) { + DEBUGP(DSNDCP, "%s: Bad TCP checksum!\n", info); + tcp_csum_err_count++; + } else + DEBUGP(DSNDCP, "%s: TCP checksum ok.\n", info); + + memset(flags_debugmsg, 0, sizeof(flags_debugmsg)); + if (tcp_flags & 1) + strcat(flags_debugmsg, "FIN "); + if (tcp_flags & 2) + strcat(flags_debugmsg, "SYN "); + if (tcp_flags & 4) + strcat(flags_debugmsg, "RST "); + if (tcp_flags & 8) + strcat(flags_debugmsg, "PSH "); + if (tcp_flags & 16) + strcat(flags_debugmsg, "ACK "); + if (tcp_flags & 32) + strcat(flags_debugmsg, "URG "); + DEBUGP(DSNDCP, "%s: FLAGS: %s\n", info, flags_debugmsg); + } else if (data[9] == 0x11) { + DEBUGP(DSNDCP, "%s: Protocol type: UDP\n", info); + } else { + DEBUGP(DSNDCP, "%s: Protocol type: (%02x)\n", info, data[9]); + } + + DEBUGP(DSNDCP, "%s: IP-Header checksum errors: %d\n", info, + ip_csum_err_count); + DEBUGP(DSNDCP, "%s: TCP-Checksum errors: %d\n", info, + tcp_csum_err_count); +} +#endif + +/* Chapter 7.2: SN-PDU Formats */ +struct sndcp_common_hdr { + /* octet 1 */ + uint8_t nsapi:4; + uint8_t more:1; + uint8_t type:1; + uint8_t first:1; + uint8_t spare:1; +} __attribute__((packed)); + +/* PCOMP / DCOMP only exist in first fragment */ +struct sndcp_comp_hdr { + /* octet 2 */ + uint8_t pcomp:4; + uint8_t dcomp:4; +} __attribute__((packed)); + +struct sndcp_udata_hdr { + /* octet 3 */ + uint8_t npdu_high:4; + uint8_t seg_nr:4; + /* octet 4 */ + uint8_t npdu_low; +} __attribute__((packed)); + + +static void *tall_sndcp_ctx; + +/* A fragment queue entry, containing one framgent of a N-PDU */ +struct defrag_queue_entry { + struct llist_head list; + /* segment number of this fragment */ + uint32_t seg_nr; + /* length of the data area of this fragment */ + uint32_t data_len; + /* pointer to the data of this fragment */ + uint8_t *data; +}; + +LLIST_HEAD(gprs_sndcp_entities); + +/* Check if any compression parameters are set in the sgsn configuration */ +static inline int any_pcomp_or_dcomp_active(struct sgsn_instance *sgsn) { + if (sgsn->cfg.pcomp_rfc1144.active || sgsn->cfg.pcomp_rfc1144.passive || + sgsn->cfg.dcomp_v42bis.active || sgsn->cfg.dcomp_v42bis.passive) + return true; + else + return false; +} + +/* Enqueue a fragment into the defragment queue */ +static int defrag_enqueue(struct gprs_sndcp_entity *sne, uint8_t seg_nr, + uint8_t *data, uint32_t data_len) +{ + struct defrag_queue_entry *dqe; + + dqe = talloc_zero(tall_sndcp_ctx, struct defrag_queue_entry); + if (!dqe) + return -ENOMEM; + dqe->data = talloc_zero_size(dqe, data_len); + if (!dqe->data) { + talloc_free(dqe); + return -ENOMEM; + } + dqe->seg_nr = seg_nr; + dqe->data_len = data_len; + + llist_add(&dqe->list, &sne->defrag.frag_list); + + if (seg_nr > sne->defrag.highest_seg) + sne->defrag.highest_seg = seg_nr; + + sne->defrag.seg_have |= (1 << seg_nr); + sne->defrag.tot_len += data_len; + + memcpy(dqe->data, data, data_len); + + return 0; +} + +/* return if we have all segments of this N-PDU */ +static int defrag_have_all_segments(struct gprs_sndcp_entity *sne) +{ + uint32_t seg_needed = 0; + unsigned int i; + + /* create a bitmask of needed segments */ + for (i = 0; i <= sne->defrag.highest_seg; i++) + seg_needed |= (1 << i); + + if (seg_needed == sne->defrag.seg_have) + return 1; + + return 0; +} + +static struct defrag_queue_entry *defrag_get_seg(struct gprs_sndcp_entity *sne, + uint32_t seg_nr) +{ + struct defrag_queue_entry *dqe; + + llist_for_each_entry(dqe, &sne->defrag.frag_list, list) { + if (dqe->seg_nr == seg_nr) { + llist_del(&dqe->list); + return dqe; + } + } + return NULL; +} + +/* Perform actual defragmentation and create an output packet */ +static int defrag_segments(struct gprs_sndcp_entity *sne) +{ + struct msgb *msg; + unsigned int seg_nr; + uint8_t *npdu; + int npdu_len; + int rc; + uint8_t *expnd = NULL; + + LOGP(DSNDCP, LOGL_DEBUG, "TLLI=0x%08x NSAPI=%u: Defragment output PDU %u " + "num_seg=%u tot_len=%u\n", sne->lle->llme->tlli, sne->nsapi, + sne->defrag.npdu, sne->defrag.highest_seg, sne->defrag.tot_len); + msg = msgb_alloc_headroom(sne->defrag.tot_len+256, 128, "SNDCP Defrag"); + if (!msg) + return -ENOMEM; + + /* FIXME: message headers + identifiers */ + + npdu = msg->data; + + for (seg_nr = 0; seg_nr <= sne->defrag.highest_seg; seg_nr++) { + struct defrag_queue_entry *dqe; + uint8_t *data; + + dqe = defrag_get_seg(sne, seg_nr); + if (!dqe) { + LOGP(DSNDCP, LOGL_ERROR, "Segment %u missing\n", seg_nr); + msgb_free(msg); + return -EIO; + } + /* actually append the segment to the N-PDU */ + data = msgb_put(msg, dqe->data_len); + memcpy(data, dqe->data, dqe->data_len); + + /* release memory for the fragment queue entry */ + talloc_free(dqe); + } + + npdu_len = sne->defrag.tot_len; + + /* FIXME: cancel timer */ + + /* actually send the N-PDU to the SGSN core code, which then + * hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */ + + /* Decompress packet */ +#if DEBUG_IP_PACKETS == 1 + DEBUGP(DSNDCP, " \n"); + DEBUGP(DSNDCP, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n"); + DEBUGP(DSNDCP, "===================================================\n"); +#endif + if (any_pcomp_or_dcomp_active(sgsn)) { + + expnd = talloc_zero_size(msg, npdu_len * MAX_DATADECOMPR_FAC + + MAX_HDRDECOMPR_INCR); + memcpy(expnd, npdu, npdu_len); + + /* Apply data decompression */ + rc = gprs_sndcp_dcomp_expand(expnd, npdu_len, sne->defrag.dcomp, + sne->defrag.data); + if (rc < 0) { + LOGP(DSNDCP, LOGL_ERROR, + "Data decompression failed!\n"); + talloc_free(expnd); + return -EIO; + } + + /* Apply header decompression */ + rc = gprs_sndcp_pcomp_expand(expnd, rc, sne->defrag.pcomp, + sne->defrag.proto); + if (rc < 0) { + LOGP(DSNDCP, LOGL_ERROR, + "TCP/IP Header decompression failed!\n"); + talloc_free(expnd); + return -EIO; + } + + /* Modify npu length, expnd is handed directly handed + * over to gsn_rx_sndcp_ud_ind(), see below */ + npdu_len = rc; + } else + expnd = npdu; +#if DEBUG_IP_PACKETS == 1 + debug_ip_packet(expnd, npdu_len, 1, "defrag_segments()"); + DEBUGP(DSNDCP, "===================================================\n"); + DEBUGP(DSNDCP, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n"); + DEBUGP(DSNDCP, " \n"); +#endif + + /* Hand off packet to gtp */ + rc = sgsn_rx_sndcp_ud_ind(&sne->ra_id, sne->lle->llme->tlli, + sne->nsapi, msg, npdu_len, expnd); + + if (any_pcomp_or_dcomp_active(sgsn)) + talloc_free(expnd); + + return rc; +} + +static int defrag_input(struct gprs_sndcp_entity *sne, struct msgb *msg, + uint8_t *hdr, unsigned int len) +{ + struct sndcp_common_hdr *sch; + struct sndcp_udata_hdr *suh; + uint16_t npdu_num; + uint8_t *data; + int rc; + + sch = (struct sndcp_common_hdr *) hdr; + if (sch->first) { + suh = (struct sndcp_udata_hdr *) (hdr + 1 + sizeof(struct sndcp_common_hdr)); + } else + suh = (struct sndcp_udata_hdr *) (hdr + sizeof(struct sndcp_common_hdr)); + + data = (uint8_t *)suh + sizeof(struct sndcp_udata_hdr); + + npdu_num = (suh->npdu_high << 8) | suh->npdu_low; + + LOGP(DSNDCP, LOGL_DEBUG, "TLLI=0x%08x NSAPI=%u: Input PDU %u Segment %u " + "Length %u %s %s\n", sne->lle->llme->tlli, sne->nsapi, npdu_num, + suh->seg_nr, len, sch->first ? "F " : "", sch->more ? "M" : ""); + + if (sch->first) { + /* first segment of a new packet. Discard all leftover fragments of + * previous packet */ + if (!llist_empty(&sne->defrag.frag_list)) { + struct defrag_queue_entry *dqe, *dqe2; + LOGP(DSNDCP, LOGL_INFO, "TLLI=0x%08x NSAPI=%u: Dropping " + "SN-PDU %u due to insufficient segments (%04x)\n", + sne->lle->llme->tlli, sne->nsapi, sne->defrag.npdu, + sne->defrag.seg_have); + llist_for_each_entry_safe(dqe, dqe2, &sne->defrag.frag_list, list) { + llist_del(&dqe->list); + talloc_free(dqe); + } + } + /* store the currently de-fragmented PDU number */ + sne->defrag.npdu = npdu_num; + + /* Re-set fragmentation state */ + sne->defrag.no_more = sne->defrag.highest_seg = sne->defrag.seg_have = 0; + sne->defrag.tot_len = 0; + /* FIXME: (re)start timer */ + } + + if (sne->defrag.npdu != npdu_num) { + LOGP(DSNDCP, LOGL_INFO, "Segment for different SN-PDU " + "(%u != %u)\n", npdu_num, sne->defrag.npdu); + /* FIXME */ + } + + /* FIXME: check if seg_nr already exists */ + /* make sure to subtract length of SNDCP header from 'len' */ + rc = defrag_enqueue(sne, suh->seg_nr, data, len - (data - hdr)); + if (rc < 0) + return rc; + + if (!sch->more) { + /* this is suppsed to be the last segment of the N-PDU, but it + * might well be not the last to arrive */ + sne->defrag.no_more = 1; + } + + if (sne->defrag.no_more) { + /* we have already received the last segment before, let's check + * if all the previous segments exist */ + if (defrag_have_all_segments(sne)) + return defrag_segments(sne); + } + + return 0; +} + +static struct gprs_sndcp_entity *gprs_sndcp_entity_by_lle(const struct gprs_llc_lle *lle, + uint8_t nsapi) +{ + struct gprs_sndcp_entity *sne; + + llist_for_each_entry(sne, &gprs_sndcp_entities, list) { + if (sne->lle == lle && sne->nsapi == nsapi) + return sne; + } + return NULL; +} + +static struct gprs_sndcp_entity *gprs_sndcp_entity_alloc(struct gprs_llc_lle *lle, + uint8_t nsapi) +{ + struct gprs_sndcp_entity *sne; + + sne = talloc_zero(tall_sndcp_ctx, struct gprs_sndcp_entity); + if (!sne) + return NULL; + + sne->lle = lle; + sne->nsapi = nsapi; + sne->defrag.timer.data = sne; + //sne->fqueue.timer.cb = FIXME; + sne->rx_state = SNDCP_RX_S_FIRST; + INIT_LLIST_HEAD(&sne->defrag.frag_list); + + llist_add(&sne->list, &gprs_sndcp_entities); + + return sne; +} + +/* Entry point for the SNSM-ACTIVATE.indication */ +int sndcp_sm_activate_ind(struct gprs_llc_lle *lle, uint8_t nsapi) +{ + LOGP(DSNDCP, LOGL_INFO, "SNSM-ACTIVATE.ind (lle=%p TLLI=%08x, " + "SAPI=%u, NSAPI=%u)\n", lle, lle->llme->tlli, lle->sapi, nsapi); + + if (gprs_sndcp_entity_by_lle(lle, nsapi)) { + LOGP(DSNDCP, LOGL_ERROR, "Trying to ACTIVATE " + "already-existing entity (TLLI=%08x, NSAPI=%u)\n", + lle->llme->tlli, nsapi); + return -EEXIST; + } + + if (!gprs_sndcp_entity_alloc(lle, nsapi)) { + LOGP(DSNDCP, LOGL_ERROR, "Out of memory during ACTIVATE\n"); + return -ENOMEM; + } + + return 0; +} + +/* Entry point for the SNSM-DEACTIVATE.indication */ +int sndcp_sm_deactivate_ind(struct gprs_llc_lle *lle, uint8_t nsapi) +{ + struct gprs_sndcp_entity *sne; + + LOGP(DSNDCP, LOGL_INFO, "SNSM-DEACTIVATE.ind (lle=%p, TLLI=%08x, " + "SAPI=%u, NSAPI=%u)\n", lle, lle->llme->tlli, lle->sapi, nsapi); + + sne = gprs_sndcp_entity_by_lle(lle, nsapi); + if (!sne) { + LOGP(DSNDCP, LOGL_ERROR, "SNSM-DEACTIVATE.ind for non-" + "existing TLLI=%08x SAPI=%u NSAPI=%u\n", lle->llme->tlli, + lle->sapi, nsapi); + return -ENOENT; + } + llist_del(&sne->list); + /* frag queue entries are hierarchically allocated, so no need to + * free them explicitly here */ + talloc_free(sne); + + return 0; +} + +/* Fragmenter state */ +struct sndcp_frag_state { + uint8_t frag_nr; + struct msgb *msg; /* original message */ + uint8_t *next_byte; /* first byte of next fragment */ + + struct gprs_sndcp_entity *sne; + void *mmcontext; +}; + +/* returns '1' if there are more fragments to send, '0' if none */ +static int sndcp_send_ud_frag(struct sndcp_frag_state *fs, + uint8_t pcomp, uint8_t dcomp) +{ + struct gprs_sndcp_entity *sne = fs->sne; + struct gprs_llc_lle *lle = sne->lle; + struct sndcp_common_hdr *sch; + struct sndcp_comp_hdr *scomph; + struct sndcp_udata_hdr *suh; + struct msgb *fmsg; + unsigned int max_payload_len; + unsigned int len; + uint8_t *data; + int rc, more; + + fmsg = msgb_alloc_headroom(fs->sne->lle->params.n201_u+256, 128, + "SNDCP Frag"); + if (!fmsg) { + msgb_free(fs->msg); + return -ENOMEM; + } + + /* make sure lower layers route the fragment like the original */ + msgb_tlli(fmsg) = msgb_tlli(fs->msg); + msgb_bvci(fmsg) = msgb_bvci(fs->msg); + msgb_nsei(fmsg) = msgb_nsei(fs->msg); + + /* prepend common SNDCP header */ + sch = (struct sndcp_common_hdr *) msgb_put(fmsg, sizeof(*sch)); + sch->nsapi = sne->nsapi; + /* Set FIRST bit if we are the first fragment in a series */ + if (fs->frag_nr == 0) + sch->first = 1; + sch->type = 1; + + /* append the compression header for first fragment */ + if (sch->first) { + scomph = (struct sndcp_comp_hdr *) + msgb_put(fmsg, sizeof(*scomph)); + scomph->pcomp = pcomp; + scomph->dcomp = dcomp; + } + + /* append the user-data header */ + suh = (struct sndcp_udata_hdr *) msgb_put(fmsg, sizeof(*suh)); + suh->npdu_low = sne->tx_npdu_nr & 0xff; + suh->npdu_high = (sne->tx_npdu_nr >> 8) & 0xf; + suh->seg_nr = fs->frag_nr % 0xf; + + /* calculate remaining length to be sent */ + len = (fs->msg->data + fs->msg->len) - fs->next_byte; + /* how much payload can we actually send via LLC? */ + max_payload_len = lle->params.n201_u - (sizeof(*sch) + sizeof(*suh)); + if (sch->first) + max_payload_len -= sizeof(*scomph); + /* check if we're exceeding the max */ + if (len > max_payload_len) + len = max_payload_len; + + /* copy the actual fragment data into our fmsg */ + data = msgb_put(fmsg, len); + memcpy(data, fs->next_byte, len); + + /* Increment fragment number and data pointer to next fragment */ + fs->frag_nr++; + fs->next_byte += len; + + /* determine if we have more fragemnts to send */ + if ((fs->msg->data + fs->msg->len) <= fs->next_byte) + more = 0; + else + more = 1; + + /* set the MORE bit of the SNDCP header accordingly */ + sch->more = more; + + rc = gprs_llc_tx_ui(fmsg, lle->sapi, 0, fs->mmcontext, true); + /* abort in case of error, do not advance frag_nr / next_byte */ + if (rc < 0) { + msgb_free(fs->msg); + return rc; + } + + if (!more) { + /* we've sent all fragments */ + msgb_free(fs->msg); + memset(fs, 0, sizeof(*fs)); + /* increment NPDU number for next frame */ + sne->tx_npdu_nr = (sne->tx_npdu_nr + 1) % 0xfff; + return 0; + } + + /* default: more fragments to send */ + return 1; +} + +/* Request transmission of a SN-PDU over specified LLC Entity + SAPI */ +int sndcp_unitdata_req(struct msgb *msg, struct gprs_llc_lle *lle, uint8_t nsapi, + void *mmcontext) +{ + struct gprs_sndcp_entity *sne; + struct sndcp_common_hdr *sch; + struct sndcp_comp_hdr *scomph; + struct sndcp_udata_hdr *suh; + struct sndcp_frag_state fs; + uint8_t pcomp = 0; + uint8_t dcomp = 0; + int rc; + + /* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */ + + /* Compress packet */ +#if DEBUG_IP_PACKETS == 1 + DEBUGP(DSNDCP, " \n"); + DEBUGP(DSNDCP, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n"); + DEBUGP(DSNDCP, "===================================================\n"); + debug_ip_packet(msg->data, msg->len, 0, "sndcp_initdata_req()"); +#endif + if (any_pcomp_or_dcomp_active(sgsn)) { + + /* Apply header compression */ + rc = gprs_sndcp_pcomp_compress(msg->data, msg->len, &pcomp, + lle->llme->comp.proto, nsapi); + if (rc < 0) { + LOGP(DSNDCP, LOGL_ERROR, + "TCP/IP Header compression failed!\n"); + return -EIO; + } + + /* Fixup pointer locations and sizes in message buffer to match + * the new, compressed buffer size */ + msgb_get(msg, msg->len); + msgb_put(msg, rc); + + /* Apply data compression */ + rc = gprs_sndcp_dcomp_compress(msg->data, msg->len, &dcomp, + lle->llme->comp.data, nsapi); + if (rc < 0) { + LOGP(DSNDCP, LOGL_ERROR, "Data compression failed!\n"); + return -EIO; + } + + /* Fixup pointer locations and sizes in message buffer to match + * the new, compressed buffer size */ + msgb_get(msg, msg->len); + msgb_put(msg, rc); + } +#if DEBUG_IP_PACKETS == 1 + DEBUGP(DSNDCP, "===================================================\n"); + DEBUGP(DSNDCP, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n"); + DEBUGP(DSNDCP, " \n"); +#endif + + sne = gprs_sndcp_entity_by_lle(lle, nsapi); + if (!sne) { + LOGP(DSNDCP, LOGL_ERROR, "Cannot find SNDCP Entity\n"); + msgb_free(msg); + return -EIO; + } + + /* Check if we need to fragment this N-PDU into multiple SN-PDUs */ + if (msg->len > lle->params.n201_u - + (sizeof(*sch) + sizeof(*suh) + sizeof(*scomph))) { + /* initialize the fragmenter state */ + fs.msg = msg; + fs.frag_nr = 0; + fs.next_byte = msg->data; + fs.sne = sne; + fs.mmcontext = mmcontext; + + /* call function to generate and send fragments until all + * of the N-PDU has been sent */ + while (1) { + int rc = sndcp_send_ud_frag(&fs,pcomp,dcomp); + if (rc == 0) + return 0; + if (rc < 0) + return rc; + } + /* not reached */ + return 0; + } + + /* this is the non-fragmenting case where we only build 1 SN-PDU */ + + /* prepend the user-data header */ + suh = (struct sndcp_udata_hdr *) msgb_push(msg, sizeof(*suh)); + suh->npdu_low = sne->tx_npdu_nr & 0xff; + suh->npdu_high = (sne->tx_npdu_nr >> 8) & 0xf; + suh->seg_nr = 0; + sne->tx_npdu_nr = (sne->tx_npdu_nr + 1) % 0xfff; + + scomph = (struct sndcp_comp_hdr *) msgb_push(msg, sizeof(*scomph)); + scomph->pcomp = pcomp; + scomph->dcomp = dcomp; + + /* prepend common SNDCP header */ + sch = (struct sndcp_common_hdr *) msgb_push(msg, sizeof(*sch)); + sch->first = 1; + sch->type = 1; + sch->nsapi = nsapi; + + return gprs_llc_tx_ui(msg, lle->sapi, 0, mmcontext, true); +} + +/* Section 5.1.2.17 LL-UNITDATA.ind */ +int sndcp_llunitdata_ind(struct msgb *msg, struct gprs_llc_lle *lle, + uint8_t *hdr, uint16_t len) +{ + struct gprs_sndcp_entity *sne; + struct sndcp_common_hdr *sch = (struct sndcp_common_hdr *)hdr; + struct sndcp_comp_hdr *scomph = NULL; + struct sndcp_udata_hdr *suh; + uint8_t *npdu; + uint16_t npdu_num __attribute__((unused)); + int npdu_len; + int rc; + uint8_t *expnd = NULL; + + sch = (struct sndcp_common_hdr *) hdr; + if (sch->first) { + scomph = (struct sndcp_comp_hdr *) (hdr + 1); + suh = (struct sndcp_udata_hdr *) (hdr + 1 + sizeof(struct sndcp_common_hdr)); + } else + suh = (struct sndcp_udata_hdr *) (hdr + sizeof(struct sndcp_common_hdr)); + + if (sch->type == 0) { + LOGP(DSNDCP, LOGL_ERROR, "SN-DATA PDU at unitdata_ind() function\n"); + return -EINVAL; + } + + if (len < sizeof(*sch) + sizeof(*suh)) { + LOGP(DSNDCP, LOGL_ERROR, "SN-UNITDATA PDU too short (%u)\n", len); + return -EIO; + } + + sne = gprs_sndcp_entity_by_lle(lle, sch->nsapi); + if (!sne) { + LOGP(DSNDCP, LOGL_ERROR, "Message for non-existing SNDCP Entity " + "(lle=%p, TLLI=%08x, SAPI=%u, NSAPI=%u)\n", lle, + lle->llme->tlli, lle->sapi, sch->nsapi); + return -EIO; + } + /* FIXME: move this RA_ID up to the LLME or even higher */ + bssgp_parse_cell_id(&sne->ra_id, msgb_bcid(msg)); + + if (scomph) { + sne->defrag.pcomp = scomph->pcomp; + sne->defrag.dcomp = scomph->dcomp; + sne->defrag.proto = lle->llme->comp.proto; + sne->defrag.data = lle->llme->comp.data; + } + + /* any non-first segment is by definition something to defragment + * as is any segment that tells us there are more segments */ + if (!sch->first || sch->more) + return defrag_input(sne, msg, hdr, len); + + npdu_num = (suh->npdu_high << 8) | suh->npdu_low; + npdu = (uint8_t *)suh + sizeof(*suh); + npdu_len = (msg->data + msg->len) - npdu; + + if (npdu_len <= 0) { + LOGP(DSNDCP, LOGL_ERROR, "Short SNDCP N-PDU: %d\n", npdu_len); + return -EIO; + } + /* actually send the N-PDU to the SGSN core code, which then + * hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */ + + /* Decompress packet */ +#if DEBUG_IP_PACKETS == 1 + DEBUGP(DSNDCP, " \n"); + DEBUGP(DSNDCP, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n"); + DEBUGP(DSNDCP, "===================================================\n"); +#endif + if (any_pcomp_or_dcomp_active(sgsn)) { + + expnd = talloc_zero_size(msg, npdu_len * MAX_DATADECOMPR_FAC + + MAX_HDRDECOMPR_INCR); + memcpy(expnd, npdu, npdu_len); + + /* Apply data decompression */ + rc = gprs_sndcp_dcomp_expand(expnd, npdu_len, sne->defrag.dcomp, + sne->defrag.data); + if (rc < 0) { + LOGP(DSNDCP, LOGL_ERROR, + "Data decompression failed!\n"); + talloc_free(expnd); + return -EIO; + } + + /* Apply header decompression */ + rc = gprs_sndcp_pcomp_expand(expnd, rc, sne->defrag.pcomp, + sne->defrag.proto); + if (rc < 0) { + LOGP(DSNDCP, LOGL_ERROR, + "TCP/IP Header decompression failed!\n"); + talloc_free(expnd); + return -EIO; + } + + /* Modify npu length, expnd is handed directly handed + * over to gsn_rx_sndcp_ud_ind(), see below */ + npdu_len = rc; + } else + expnd = npdu; +#if DEBUG_IP_PACKETS == 1 + debug_ip_packet(expnd, npdu_len, 1, "sndcp_llunitdata_ind()"); + DEBUGP(DSNDCP, "===================================================\n"); + DEBUGP(DSNDCP, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n"); + DEBUGP(DSNDCP, " \n"); +#endif + + /* Hand off packet to gtp */ + rc = sgsn_rx_sndcp_ud_ind(&sne->ra_id, lle->llme->tlli, + sne->nsapi, msg, npdu_len, expnd); + + if (any_pcomp_or_dcomp_active(sgsn)) + talloc_free(expnd); + + return rc; +} + +#if 0 +/* Section 5.1.2.1 LL-RESET.ind */ +static int sndcp_ll_reset_ind(struct gprs_sndcp_entity *se) +{ + /* treat all outstanding SNDCP-LLC request type primitives as not sent */ + /* reset all SNDCP XID parameters to default values */ + LOGP(DSNDCP, LOGL_NOTICE, "not implemented.\n"); + return 0; +} + +static int sndcp_ll_status_ind() +{ + /* inform the SM sub-layer by means of SNSM-STATUS.req */ + LOGP(DSNDCP, LOGL_NOTICE, "not implemented.\n"); + return 0; +} + +static struct sndcp_state_list {{ + uint32_t states; + unsigned int type; + int (*rout)(struct gprs_sndcp_entity *se, struct msgb *msg); +} sndcp_state_list[] = { + { ALL_STATES, + LL_RESET_IND, sndcp_ll_reset_ind }, + { ALL_STATES, + LL_ESTABLISH_IND, sndcp_ll_est_ind }, + { SBIT(SNDCP_S_EST_RQD), + LL_ESTABLISH_RESP, sndcp_ll_est_ind }, + { SBIT(SNDCP_S_EST_RQD), + LL_ESTABLISH_CONF, sndcp_ll_est_conf }, + { SBIT(SNDCP_S_ +}; + +static int sndcp_rx_llc_prim() +{ + case LL_ESTABLISH_REQ: + case LL_RELEASE_REQ: + case LL_XID_REQ: + case LL_DATA_REQ: + LL_UNITDATA_REQ, /* TLLI, SN-PDU, Ref, QoS, Radio Prio, Ciph */ + + switch (prim) { + case LL_RESET_IND: + case LL_ESTABLISH_IND: + case LL_ESTABLISH_RESP: + case LL_ESTABLISH_CONF: + case LL_RELEASE_IND: + case LL_RELEASE_CONF: + case LL_XID_IND: + case LL_XID_RESP: + case LL_XID_CONF: + case LL_DATA_IND: + case LL_DATA_CONF: + case LL_UNITDATA_IND: + case LL_STATUS_IND: + } +} +#endif + +/* Generate SNDCP-XID message */ +static int gprs_llc_gen_sndcp_xid(uint8_t *bytes, int bytes_len, uint8_t nsapi) +{ + int entity = 0; + LLIST_HEAD(comp_fields); + struct gprs_sndcp_pcomp_rfc1144_params rfc1144_params; + struct gprs_sndcp_comp_field rfc1144_comp_field; + struct gprs_sndcp_dcomp_v42bis_params v42bis_params; + struct gprs_sndcp_comp_field v42bis_comp_field; + + memset(&rfc1144_comp_field, 0, sizeof(struct gprs_sndcp_comp_field)); + memset(&v42bis_comp_field, 0, sizeof(struct gprs_sndcp_comp_field)); + + /* Setup rfc1144 */ + if (sgsn->cfg.pcomp_rfc1144.active) { + rfc1144_params.nsapi[0] = nsapi; + rfc1144_params.nsapi_len = 1; + rfc1144_params.s01 = sgsn->cfg.pcomp_rfc1144.s01; + rfc1144_comp_field.p = 1; + rfc1144_comp_field.entity = entity; + rfc1144_comp_field.algo.pcomp = RFC_1144; + rfc1144_comp_field.comp[RFC1144_PCOMP1] = 1; + rfc1144_comp_field.comp[RFC1144_PCOMP2] = 2; + rfc1144_comp_field.comp_len = RFC1144_PCOMP_NUM; + rfc1144_comp_field.rfc1144_params = &rfc1144_params; + entity++; + llist_add(&rfc1144_comp_field.list, &comp_fields); + } + + /* Setup V.42bis */ + if (sgsn->cfg.dcomp_v42bis.active) { + v42bis_params.nsapi[0] = nsapi; + v42bis_params.nsapi_len = 1; + v42bis_params.p0 = sgsn->cfg.dcomp_v42bis.p0; + v42bis_params.p1 = sgsn->cfg.dcomp_v42bis.p1; + v42bis_params.p2 = sgsn->cfg.dcomp_v42bis.p2; + v42bis_comp_field.p = 1; + v42bis_comp_field.entity = entity; + v42bis_comp_field.algo.dcomp = V42BIS; + v42bis_comp_field.comp[V42BIS_DCOMP1] = 1; + v42bis_comp_field.comp_len = V42BIS_DCOMP_NUM; + v42bis_comp_field.v42bis_params = &v42bis_params; + entity++; + llist_add(&v42bis_comp_field.list, &comp_fields); + } + + /* Do not attempt to compile anything if there is no data in the list */ + if (llist_empty(&comp_fields)) + return 0; + + /* Compile bytestream */ + return gprs_sndcp_compile_xid(bytes, bytes_len, &comp_fields, + DEFAULT_SNDCP_VERSION); +} + +/* Set of SNDCP-XID bnegotiation (See also: TS 144 065, + * Section 6.8 XID parameter negotiation) */ +int sndcp_sn_xid_req(struct gprs_llc_lle *lle, uint8_t nsapi) +{ + /* Note: The specification requires the SNDCP-User to set of an + * SNDCP xid request. See also 3GPP TS 44.065, 6.8 XID parameter + * negotiation, Figure 11: SNDCP XID negotiation procedure. In + * our case the SNDCP-User is sgsn_libgtp.c, which calls + * sndcp_sn_xid_req directly. */ + + uint8_t l3params[1024]; + int xid_len; + struct gprs_llc_xid_field xid_field_request; + + /* Wipe off all compression entities and their states to + * get rid of possible leftovers from a previous session */ + gprs_sndcp_comp_free(lle->llme->comp.proto); + gprs_sndcp_comp_free(lle->llme->comp.data); + lle->llme->comp.proto = gprs_sndcp_comp_alloc(lle->llme); + lle->llme->comp.data = gprs_sndcp_comp_alloc(lle->llme); + talloc_free(lle->xid); + lle->xid = NULL; + + /* Generate compression parameter bytestream */ + xid_len = gprs_llc_gen_sndcp_xid(l3params, sizeof(l3params), nsapi); + + /* Send XID with the SNDCP-XID bytetsream included */ + if (xid_len > 0) { + xid_field_request.type = GPRS_LLC_XID_T_L3_PAR; + xid_field_request.data = l3params; + xid_field_request.data_len = xid_len; + return gprs_ll_xid_req(lle, &xid_field_request); + } + + /* When bytestream can not be generated, proceed without SNDCP-XID */ + return gprs_ll_xid_req(lle, NULL); + +} + +/* Handle header compression entites */ +static int handle_pcomp_entities(struct gprs_sndcp_comp_field *comp_field, + struct gprs_llc_lle *lle) +{ + /* Note: This functions also transforms the comp_field into its + * echo form (strips comp values, resets propose bit etc...) + * the processed comp_fields can then be sent back as XID- + * Response without further modification. */ + + /* Delete propose bit */ + comp_field->p = 0; + + /* Process proposed parameters */ + switch (comp_field->algo.pcomp) { + case RFC_1144: + if (sgsn->cfg.pcomp_rfc1144.passive + && comp_field->rfc1144_params->nsapi_len > 0) { + DEBUGP(DSNDCP, + "Accepting RFC1144 header compression...\n"); + gprs_sndcp_comp_add(lle->llme, lle->llme->comp.proto, + comp_field); + } else { + DEBUGP(DSNDCP, + "Rejecting RFC1144 header compression...\n"); + gprs_sndcp_comp_delete(lle->llme->comp.proto, + comp_field->entity); + comp_field->rfc1144_params->nsapi_len = 0; + } + break; + case RFC_2507: + /* RFC 2507 is not yet supported, + * so we set applicable nsapis to zero */ + DEBUGP(DSNDCP, "Rejecting RFC2507 header compression...\n"); + comp_field->rfc2507_params->nsapi_len = 0; + gprs_sndcp_comp_delete(lle->llme->comp.proto, + comp_field->entity); + break; + case ROHC: + /* ROHC is not yet supported, + * so we set applicable nsapis to zero */ + DEBUGP(DSNDCP, "Rejecting ROHC header compression...\n"); + comp_field->rohc_params->nsapi_len = 0; + gprs_sndcp_comp_delete(lle->llme->comp.proto, + comp_field->entity); + break; + } + + return 0; +} + +/* Hanle data compression entites */ +static int handle_dcomp_entities(struct gprs_sndcp_comp_field *comp_field, + struct gprs_llc_lle *lle) +{ + /* See note in handle_pcomp_entities() */ + + /* Delete propose bit */ + comp_field->p = 0; + + /* Process proposed parameters */ + switch (comp_field->algo.dcomp) { + case V42BIS: + if (sgsn->cfg.dcomp_v42bis.passive && + comp_field->v42bis_params->nsapi_len > 0) { + DEBUGP(DSNDCP, + "Accepting V.42bis data compression...\n"); + gprs_sndcp_comp_add(lle->llme, lle->llme->comp.data, + comp_field); + } else { + LOGP(DSNDCP, LOGL_DEBUG, + "Rejecting V.42bis data compression...\n"); + gprs_sndcp_comp_delete(lle->llme->comp.data, + comp_field->entity); + comp_field->v42bis_params->nsapi_len = 0; + } + break; + case V44: + /* V44 is not yet supported, + * so we set applicable nsapis to zero */ + DEBUGP(DSNDCP, "Rejecting V.44 data compression...\n"); + comp_field->v44_params->nsapi_len = 0; + gprs_sndcp_comp_delete(lle->llme->comp.data, + comp_field->entity); + break; + } + + return 0; + +} + +/* Process SNDCP-XID indication + * (See also: TS 144 065, Section 6.8 XID parameter negotiation) */ +int sndcp_sn_xid_ind(struct gprs_llc_xid_field *xid_field_indication, + struct gprs_llc_xid_field *xid_field_response, + struct gprs_llc_lle *lle) +{ + /* Note: This function computes the SNDCP-XID response that is sent + * back to the ms when a ms originated XID is received. The + * Input XID fields are directly processed and the result is directly + * handed back. */ + + int rc; + int compclass; + int version; + + struct llist_head *comp_fields; + struct gprs_sndcp_comp_field *comp_field; + + OSMO_ASSERT(xid_field_indication); + OSMO_ASSERT(xid_field_response); + OSMO_ASSERT(lle); + + /* Some phones send zero byte length SNDCP frames + * and do require a confirmation response. */ + if (xid_field_indication->data_len == 0) { + xid_field_response->type = GPRS_LLC_XID_T_L3_PAR; + xid_field_response->data_len = 0; + return 0; + } + + /* Parse SNDCP-CID XID-Field */ + comp_fields = gprs_sndcp_parse_xid(&version, lle->llme, + xid_field_indication->data, + xid_field_indication->data_len, + NULL); + if (!comp_fields) + return -EINVAL; + + /* Handle compression entites */ + DEBUGP(DSNDCP, "SNDCP-XID-IND (ms):\n"); + gprs_sndcp_dump_comp_fields(comp_fields, LOGL_DEBUG); + + llist_for_each_entry(comp_field, comp_fields, list) { + compclass = gprs_sndcp_get_compression_class(comp_field); + if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION) + rc = handle_pcomp_entities(comp_field, lle); + else if (compclass == SNDCP_XID_DATA_COMPRESSION) + rc = handle_dcomp_entities(comp_field, lle); + else { + gprs_sndcp_comp_delete(lle->llme->comp.proto, + comp_field->entity); + gprs_sndcp_comp_delete(lle->llme->comp.data, + comp_field->entity); + rc = 0; + } + + if (rc < 0) { + talloc_free(comp_fields); + return -EINVAL; + } + } + + DEBUGP(DSNDCP, "SNDCP-XID-RES (sgsn):\n"); + gprs_sndcp_dump_comp_fields(comp_fields, LOGL_DEBUG); + + /* Reserve some memory to store the modified SNDCP-XID bytes */ + xid_field_response->data = + talloc_zero_size(lle->llme, xid_field_indication->data_len); + + /* Set Type flag for response */ + xid_field_response->type = GPRS_LLC_XID_T_L3_PAR; + + /* Compile modified SNDCP-XID bytes */ + rc = gprs_sndcp_compile_xid(xid_field_response->data, + xid_field_indication->data_len, + comp_fields, 0); + + if (rc > 0) + xid_field_response->data_len = rc; + else { + talloc_free(xid_field_response->data); + xid_field_response->data = NULL; + xid_field_response->data_len = 0; + return -EINVAL; + } + + talloc_free(comp_fields); + + return 0; +} + +/* Process SNDCP-XID indication + * (See also: TS 144 065, Section 6.8 XID parameter negotiation) */ +int sndcp_sn_xid_conf(struct gprs_llc_xid_field *xid_field_conf, + struct gprs_llc_xid_field *xid_field_request, + struct gprs_llc_lle *lle) +{ + /* Note: This function handles an incomming SNDCP-XID confirmiation. + * Since the confirmation fields may lack important parameters we + * will reconstruct these missing fields using the original request + * we have sent. After that we will create (or delete) the + * compression entites */ + + struct llist_head *comp_fields_req; + struct llist_head *comp_fields_conf; + struct gprs_sndcp_comp_field *comp_field; + int rc; + int compclass; + + /* We need both, the confirmation that is sent back by the ms, + * and the original request we have sent. If one of this is missing + * we can not process the confirmation, the caller must check if + * request and confirmation fields are available. */ + OSMO_ASSERT(xid_field_conf); + OSMO_ASSERT(xid_field_request); + + /* Parse SNDCP-CID XID-Field */ + comp_fields_req = gprs_sndcp_parse_xid(NULL, lle->llme, + xid_field_request->data, + xid_field_request->data_len, + NULL); + if (!comp_fields_req) + return -EINVAL; + + DEBUGP(DSNDCP, "SNDCP-XID-REQ (sgsn):\n"); + gprs_sndcp_dump_comp_fields(comp_fields_req, LOGL_DEBUG); + + /* Parse SNDCP-CID XID-Field */ + comp_fields_conf = gprs_sndcp_parse_xid(NULL, lle->llme, + xid_field_conf->data, + xid_field_conf->data_len, + comp_fields_req); + if (!comp_fields_conf) + return -EINVAL; + + DEBUGP(DSNDCP, "SNDCP-XID-CONF (ms):\n"); + gprs_sndcp_dump_comp_fields(comp_fields_conf, LOGL_DEBUG); + + /* Handle compression entites */ + llist_for_each_entry(comp_field, comp_fields_conf, list) { + compclass = gprs_sndcp_get_compression_class(comp_field); + if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION) + rc = handle_pcomp_entities(comp_field, lle); + else if (compclass == SNDCP_XID_DATA_COMPRESSION) + rc = handle_dcomp_entities(comp_field, lle); + else { + gprs_sndcp_comp_delete(lle->llme->comp.proto, + comp_field->entity); + gprs_sndcp_comp_delete(lle->llme->comp.data, + comp_field->entity); + rc = 0; + } + + if (rc < 0) { + talloc_free(comp_fields_req); + talloc_free(comp_fields_conf); + return -EINVAL; + } + } + + talloc_free(comp_fields_req); + talloc_free(comp_fields_conf); + + return 0; +} diff --git a/src/sgsn/gprs_sndcp_comp.c b/src/sgsn/gprs_sndcp_comp.c new file mode 100644 index 000000000..c71cc8982 --- /dev/null +++ b/src/sgsn/gprs_sndcp_comp.c @@ -0,0 +1,338 @@ +/* GPRS SNDCP header compression entity management tools */ + +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Philipp Maier + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +/* Create a new compression entity from a XID-Field */ +static struct gprs_sndcp_comp *gprs_sndcp_comp_create(const void *ctx, + const struct + gprs_sndcp_comp_field + *comp_field) +{ + struct gprs_sndcp_comp *comp_entity; + comp_entity = talloc_zero(ctx, struct gprs_sndcp_comp); + + /* Copy relevant information from the SNDCP-XID field */ + comp_entity->entity = comp_field->entity; + comp_entity->comp_len = comp_field->comp_len; + memcpy(comp_entity->comp, comp_field->comp, sizeof(comp_entity->comp)); + + if (comp_field->rfc1144_params) { + comp_entity->nsapi_len = comp_field->rfc1144_params->nsapi_len; + memcpy(comp_entity->nsapi, + comp_field->rfc1144_params->nsapi, + sizeof(comp_entity->nsapi)); + comp_entity->algo.pcomp = comp_field->algo.pcomp; + } else if (comp_field->rfc2507_params) { + comp_entity->nsapi_len = comp_field->rfc2507_params->nsapi_len; + memcpy(comp_entity->nsapi, + comp_field->rfc2507_params->nsapi, + sizeof(comp_entity->nsapi)); + comp_entity->algo.pcomp = comp_field->algo.pcomp; + } else if (comp_field->rohc_params) { + comp_entity->nsapi_len = comp_field->rohc_params->nsapi_len; + memcpy(comp_entity->nsapi, comp_field->rohc_params->nsapi, + sizeof(comp_entity->nsapi)); + comp_entity->algo.pcomp = comp_field->algo.pcomp; + } else if (comp_field->v42bis_params) { + comp_entity->nsapi_len = comp_field->v42bis_params->nsapi_len; + memcpy(comp_entity->nsapi, + comp_field->v42bis_params->nsapi, + sizeof(comp_entity->nsapi)); + comp_entity->algo.dcomp = comp_field->algo.dcomp; + } else if (comp_field->v44_params) { + comp_entity->nsapi_len = comp_field->v44_params->nsapi_len; + memcpy(comp_entity->nsapi, + comp_field->v44_params->nsapi, + sizeof(comp_entity->nsapi)); + comp_entity->algo.dcomp = comp_field->algo.dcomp; + } else { + /* The caller is expected to check carefully if the all + * data fields required for compression entity creation + * are present. Otherwise we blow an assertion here */ + OSMO_ASSERT(false); + } + + /* Check if an NSAPI is selected, if not, it does not make sense + * to create the compression entity, since the caller should + * have checked the presence of the NSAPI, we blow an assertion + * in case of missing NSAPIs */ + OSMO_ASSERT(comp_entity->nsapi_len > 0); + + /* Determine of which class our compression entity will be + * (Protocol or Data compresson ?) */ + comp_entity->compclass = gprs_sndcp_get_compression_class(comp_field); + + /* Create an algorithm specific compression context */ + switch (comp_entity->compclass) { + case SNDCP_XID_PROTOCOL_COMPRESSION: + if (gprs_sndcp_pcomp_init(ctx, comp_entity, comp_field) != 0) { + talloc_free(comp_entity); + comp_entity = NULL; + } + break; + case SNDCP_XID_DATA_COMPRESSION: + if (gprs_sndcp_dcomp_init(ctx, comp_entity, comp_field) != 0) { + talloc_free(comp_entity); + comp_entity = NULL; + } + break; + default: + /* comp_field is somehow invalid */ + OSMO_ASSERT(false); + } + + /* Bail on failure */ + if (comp_entity == NULL) { + LOGP(DSNDCP, LOGL_ERROR, + "Compression entity creation failed!\n"); + return NULL; + } + + /* Display info message */ + if (comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION) { + LOGP(DSNDCP, LOGL_INFO, + "New header compression entity (%d) created.\n", + comp_entity->entity); + } else { + LOGP(DSNDCP, LOGL_INFO, + "New data compression entity (%d) created.\n", + comp_entity->entity); + } + + return comp_entity; +} + +/* Allocate a compression enitiy list */ +struct llist_head *gprs_sndcp_comp_alloc(const void *ctx) +{ + struct llist_head *lh; + + lh = talloc_zero(ctx, struct llist_head); + INIT_LLIST_HEAD(lh); + + return lh; +} + +/* Free a compression entitiy list */ +void gprs_sndcp_comp_free(struct llist_head *comp_entities) +{ + struct gprs_sndcp_comp *comp_entity; + + /* We expect the caller to take care of allocating a + * compression entity list properly. Attempting to + * free a non existing list clearly points out + * a malfunction. */ + OSMO_ASSERT(comp_entities); + + llist_for_each_entry(comp_entity, comp_entities, list) { + /* Free compression entity */ + switch (comp_entity->compclass) { + case SNDCP_XID_PROTOCOL_COMPRESSION: + LOGP(DSNDCP, LOGL_INFO, + "Deleting header compression entity %d ...\n", + comp_entity->entity); + gprs_sndcp_pcomp_term(comp_entity); + break; + case SNDCP_XID_DATA_COMPRESSION: + LOGP(DSNDCP, LOGL_INFO, + "Deleting data compression entity %d ...\n", + comp_entity->entity); + gprs_sndcp_dcomp_term(comp_entity); + break; + default: + LOGP(DSNDCP, LOGL_INFO, + "Invalid compression class %d!\n", comp_entity->compclass); + OSMO_ASSERT(false); + } + } + + talloc_free(comp_entities); +} + +/* Delete a compression entity */ +void gprs_sndcp_comp_delete(struct llist_head *comp_entities, + unsigned int entity) +{ + struct gprs_sndcp_comp *comp_entity; + struct gprs_sndcp_comp *comp_entity_to_delete = NULL; + + OSMO_ASSERT(comp_entities); + + llist_for_each_entry(comp_entity, comp_entities, list) { + if (comp_entity->entity == entity) { + comp_entity_to_delete = comp_entity; + break; + } + } + + if (!comp_entity_to_delete) + return; + + if (comp_entity_to_delete->compclass == SNDCP_XID_PROTOCOL_COMPRESSION) { + LOGP(DSNDCP, LOGL_INFO, + "Deleting header compression entity %d ...\n", + comp_entity_to_delete->entity); + gprs_sndcp_pcomp_term(comp_entity_to_delete); + } else { + LOGP(DSNDCP, LOGL_INFO, + "Deleting data compression entity %d ...\n", + comp_entity_to_delete->entity); + } + + /* Delete compression entity */ + llist_del(&comp_entity_to_delete->list); + talloc_free(comp_entity_to_delete); +} + +/* Create and Add a new compression entity + * (returns a pointer to the compression entity that has just been created) */ +struct gprs_sndcp_comp *gprs_sndcp_comp_add(const void *ctx, + struct llist_head *comp_entities, + const struct gprs_sndcp_comp_field + *comp_field) +{ + struct gprs_sndcp_comp *comp_entity; + + OSMO_ASSERT(comp_entities); + OSMO_ASSERT(comp_field); + + /* Just to be sure, if the entity is already in + * the list it will be deleted now */ + gprs_sndcp_comp_delete(comp_entities, comp_field->entity); + + /* Create and add a new entity to the list */ + comp_entity = gprs_sndcp_comp_create(ctx, comp_field); + + if (!comp_entity) + return NULL; + + llist_add(&comp_entity->list, comp_entities); + return comp_entity; +} + +/* Find which compression entity handles the specified pcomp/dcomp */ +struct gprs_sndcp_comp *gprs_sndcp_comp_by_comp(const struct llist_head + *comp_entities, uint8_t comp) +{ + struct gprs_sndcp_comp *comp_entity; + int i; + + OSMO_ASSERT(comp_entities); + + llist_for_each_entry(comp_entity, comp_entities, list) { + for (i = 0; i < comp_entity->comp_len; i++) { + if (comp_entity->comp[i] == comp) + return comp_entity; + } + } + + LOGP(DSNDCP, LOGL_ERROR, + "Could not find a matching compression entity for given pcomp/dcomp value %d.\n", + comp); + return NULL; +} + +/* Find which compression entity handles the specified nsapi */ +struct gprs_sndcp_comp *gprs_sndcp_comp_by_nsapi(const struct llist_head + *comp_entities, uint8_t nsapi) +{ + struct gprs_sndcp_comp *comp_entity; + int i; + + OSMO_ASSERT(comp_entities); + + llist_for_each_entry(comp_entity, comp_entities, list) { + for (i = 0; i < comp_entity->nsapi_len; i++) { + if (comp_entity->nsapi[i] == nsapi) + return comp_entity; + } + } + + return NULL; +} + +/* Find a comp_index for a given pcomp/dcomp value */ +uint8_t gprs_sndcp_comp_get_idx(const struct gprs_sndcp_comp *comp_entity, + uint8_t comp) +{ + /* Note: This function returns a normalized version of the comp value, + * which matches up with the position of the comp field. Since comp=0 + * is reserved for "no compression", the index value starts counting + * at one. The return value is the PCOMPn/DCOMPn value one can find + * in the Specification (see e.g. 3GPP TS 44.065, 6.5.3.2, Table 7) */ + + int i; + OSMO_ASSERT(comp_entity); + + /* A pcomp/dcomp value of zero is reserved for "no comproession", + * So we just bail and return zero in this case */ + if (comp == 0) + return 0; + + /* Look in the pcomp/dcomp list for the index */ + for (i = 0; i < comp_entity->comp_len; i++) { + if (comp_entity->comp[i] == comp) + return i + 1; + } + + LOGP(DSNDCP, LOGL_ERROR, + "Could not find a matching comp_index for given pcomp/dcomp value %d\n", + comp); + return 0; +} + +/* Find a pcomp/dcomp value for a given comp_index */ +uint8_t gprs_sndcp_comp_get_comp(const struct gprs_sndcp_comp *comp_entity, + uint8_t comp_index) +{ + OSMO_ASSERT(comp_entity); + + /* A comp_index of zero translates to zero right away. */ + if (comp_index == 0) + return 0; + + if (comp_index > comp_entity->comp_len) { + LOGP(DSNDCP, LOGL_ERROR, + "Could not find a matching pcomp/dcomp value for given comp_index value %d.\n", + comp_index); + return 0; + } + + /* Look in the pcomp/dcomp list for the comp_index, see + * note in gprs_sndcp_comp_get_idx() */ + return comp_entity->comp[comp_index - 1]; +} diff --git a/src/sgsn/gprs_sndcp_dcomp.c b/src/sgsn/gprs_sndcp_dcomp.c new file mode 100644 index 000000000..c0da84d47 --- /dev/null +++ b/src/sgsn/gprs_sndcp_dcomp.c @@ -0,0 +1,358 @@ +/* GPRS SNDCP data compression handler */ + +/* (C) 2016 by Sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Philipp Maier + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/* A struct to capture the output data of compressor and decompressor */ +struct v42bis_output_buffer { + uint8_t *buf; + uint8_t *buf_pointer; + int len; +}; + +/* Handler to capture the output data from the compressor */ +void tx_v42bis_frame_handler(void *user_data, const uint8_t *pkt, int len) +{ + struct v42bis_output_buffer *output_buffer = + (struct v42bis_output_buffer *)user_data; + memcpy(output_buffer->buf_pointer, pkt, len); + output_buffer->buf_pointer += len; + output_buffer->len += len; + return; +} + +/* Handler to capture the output data from the decompressor */ +void rx_v42bis_data_handler(void *user_data, const uint8_t *buf, int len) +{ + struct v42bis_output_buffer *output_buffer = + (struct v42bis_output_buffer *)user_data; + memcpy(output_buffer->buf_pointer, buf, len); + output_buffer->buf_pointer += len; + output_buffer->len += len; + return; +} + +/* Initalize data compression */ +int gprs_sndcp_dcomp_init(const void *ctx, struct gprs_sndcp_comp *comp_entity, + const struct gprs_sndcp_comp_field *comp_field) +{ + /* Note: This function is automatically called from + * gprs_sndcp_comp.c when a new data compression + * entity is created by gprs_sndcp.c */ + + OSMO_ASSERT(comp_entity); + OSMO_ASSERT(comp_field); + + if (comp_entity->compclass == SNDCP_XID_DATA_COMPRESSION + && comp_entity->algo.dcomp == V42BIS) { + OSMO_ASSERT(comp_field->v42bis_params); + comp_entity->state = + v42bis_init(ctx, NULL, comp_field->v42bis_params->p0, + comp_field->v42bis_params->p1, + comp_field->v42bis_params->p2, + &tx_v42bis_frame_handler, NULL, + V42BIS_MAX_OUTPUT_LENGTH, + &rx_v42bis_data_handler, NULL, + V42BIS_MAX_OUTPUT_LENGTH); + LOGP(DSNDCP, LOGL_INFO, + "V.42bis data compression initialized.\n"); + return 0; + } + + /* Just in case someone tries to initalize an unknown or unsupported + * data compresson. Since everything is checked during the SNDCP + * negotiation process, this should never happen! */ + OSMO_ASSERT(false); +} + +/* Terminate data compression */ +void gprs_sndcp_dcomp_term(struct gprs_sndcp_comp *comp_entity) +{ + /* Note: This function is automatically called from + * gprs_sndcp_comp.c when a data compression + * entity is deleted by gprs_sndcp.c */ + + OSMO_ASSERT(comp_entity); + + if (comp_entity->compclass == SNDCP_XID_DATA_COMPRESSION + && comp_entity->algo.dcomp == V42BIS) { + if (comp_entity->state) { + v42bis_free((v42bis_state_t *) comp_entity->state); + comp_entity->state = NULL; + } + LOGP(DSNDCP, LOGL_INFO, + "V.42bis data compression terminated.\n"); + return; + } + + /* Just in case someone tries to terminate an unknown or unsupported + * data compresson. Since everything is checked during the SNDCP + * negotiation process, this should never happen! */ + OSMO_ASSERT(false); +} + +/* Perform a full reset of the V.42bis compression state */ +static void v42bis_reset(v42bis_state_t *comp) +{ + /* This function performs a complete reset of the V.42bis compression + * state by reinitalizing the state withe the previously negotiated + * parameters. */ + + int p0, p1, p2; + p0 = comp->decompress.v42bis_parm_p0 | comp->compress.v42bis_parm_p0; + p1 = comp->decompress.v42bis_parm_n2; + p2 = comp->decompress.v42bis_parm_n7; + + DEBUGP(DSNDCP, "Resetting compression state: %p, p0=%d, p1=%d, p2=%d\n", + comp, p0, p1, p2); + + v42bis_init(NULL, comp, p0, p1, p2, &tx_v42bis_frame_handler, NULL, + V42BIS_MAX_OUTPUT_LENGTH, &rx_v42bis_data_handler, NULL, + V42BIS_MAX_OUTPUT_LENGTH); +} + +/* Compress a packet using V.42bis data compression */ +static int v42bis_compress_unitdata(uint8_t *pcomp_index, uint8_t *data, + unsigned int len, v42bis_state_t *comp) +{ + /* Note: This implementation may only be used to compress SN_UNITDATA + * packets, since it resets the compression state for each NPDU. */ + + uint8_t *data_o; + int rc; + int skip = 0; + struct v42bis_output_buffer compressed_data; + + /* Don't bother with short packets */ + if (len < MIN_COMPR_PAYLOAD) + skip = 1; + + /* Skip if compression is not enabled for TX direction */ + if (!comp->compress.v42bis_parm_p0) + skip = 1; + + /* Skip compression */ + if (skip) { + *pcomp_index = 0; + return len; + } + + /* Reset V.42bis compression state */ + v42bis_reset(comp); + + /* Run compressor */ + data_o = talloc_zero_size(comp, len * MAX_DATADECOMPR_FAC); + compressed_data.buf = data_o; + compressed_data.buf_pointer = data_o; + compressed_data.len = 0; + comp->compress.user_data = (&compressed_data); + rc = v42bis_compress(comp, data, len); + if (rc < 0) { + LOGP(DSNDCP, LOGL_ERROR, + "Data compression failed, skipping...\n"); + skip = 1; + } + rc = v42bis_compress_flush(comp); + if (rc < 0) { + LOGP(DSNDCP, LOGL_ERROR, + "Data compression failed, skipping...\n"); + skip = 1; + } + + /* The compressor might yield negative compression gain, in + * this case, we just decide to send the packat as normal, + * uncompressed payload => skip compresssion */ + if (compressed_data.len >= len) { + LOGP(DSNDCP, LOGL_ERROR, + "Data compression ineffective, skipping...\n"); + skip = 1; + } + + /* Skip compression */ + if (skip) { + *pcomp_index = 0; + talloc_free(data_o); + return len; + } + + *pcomp_index = 1; + memcpy(data, data_o, compressed_data.len); + talloc_free(data_o); + + return compressed_data.len; +} + +/* Expand a packet using V.42bis data compression */ +static int v42bis_expand_unitdata(uint8_t *data, unsigned int len, + uint8_t pcomp_index, v42bis_state_t *comp) +{ + /* Note: This implementation may only be used to compress SN_UNITDATA + * packets, since it resets the compression state for each NPDU. */ + + int rc; + struct v42bis_output_buffer uncompressed_data; + uint8_t *data_i; + + /* Skip when the packet is marked as uncompressed */ + if (pcomp_index == 0) { + return len; + } + + /* Reset V.42bis compression state */ + v42bis_reset(comp); + + /* Decompress packet */ + data_i = talloc_zero_size(comp, len); + memcpy(data_i, data, len); + uncompressed_data.buf = data; + uncompressed_data.buf_pointer = data; + uncompressed_data.len = 0; + comp->decompress.user_data = (&uncompressed_data); + rc = v42bis_decompress(comp, data_i, len); + talloc_free(data_i); + if (rc < 0) + return -EINVAL; + rc = v42bis_decompress_flush(comp); + if (rc < 0) + return -EINVAL; + + return uncompressed_data.len; +} + +/* Expand packet */ +int gprs_sndcp_dcomp_expand(uint8_t *data, unsigned int len, uint8_t pcomp, + const struct llist_head *comp_entities) +{ + int rc; + uint8_t pcomp_index = 0; + struct gprs_sndcp_comp *comp_entity; + + OSMO_ASSERT(data); + OSMO_ASSERT(comp_entities); + + LOGP(DSNDCP, LOGL_DEBUG, + "Data compression entity list: comp_entities=%p\n", comp_entities); + + LOGP(DSNDCP, LOGL_DEBUG, "Data compression mode: dcomp=%d\n", pcomp); + + /* Skip on pcomp=0 */ + if (pcomp == 0) { + return len; + } + + /* Find out which compression entity handles the data */ + comp_entity = gprs_sndcp_comp_by_comp(comp_entities, pcomp); + + /* Skip compression if no suitable compression entity can be found */ + if (!comp_entity) { + return len; + } + + /* Note: Only data compression entities may appear in + * data compression context */ + OSMO_ASSERT(comp_entity->compclass == SNDCP_XID_DATA_COMPRESSION); + + /* Note: Currently V42BIS is the only compression method we + * support, so the only allowed algorithm is V42BIS */ + OSMO_ASSERT(comp_entity->algo.dcomp == V42BIS); + + /* Find pcomp_index */ + pcomp_index = gprs_sndcp_comp_get_idx(comp_entity, pcomp); + + /* Run decompression algo */ + rc = v42bis_expand_unitdata(data, len, pcomp_index, comp_entity->state); + + LOGP(DSNDCP, LOGL_DEBUG, + "Data expansion done, old length=%d, new length=%d, entity=%p\n", + len, rc, comp_entity); + + return rc; +} + +/* Compress packet */ +int gprs_sndcp_dcomp_compress(uint8_t *data, unsigned int len, uint8_t *pcomp, + const struct llist_head *comp_entities, + uint8_t nsapi) +{ + int rc; + uint8_t pcomp_index = 0; + struct gprs_sndcp_comp *comp_entity; + + OSMO_ASSERT(data); + OSMO_ASSERT(pcomp); + OSMO_ASSERT(comp_entities); + + LOGP(DSNDCP, LOGL_DEBUG, + "Data compression entity list: comp_entities=%p\n", comp_entities); + + /* Find out which compression entity handles the data */ + comp_entity = gprs_sndcp_comp_by_nsapi(comp_entities, nsapi); + + /* Skip compression if no suitable compression entity can be found */ + if (!comp_entity) { + *pcomp = 0; + return len; + } + + /* Note: Only data compression entities may appear in + * data compression context */ + OSMO_ASSERT(comp_entity->compclass == SNDCP_XID_DATA_COMPRESSION); + + /* Note: Currently V42BIS is the only compression method we + * support, so the only allowed algorithm is V42BIS */ + OSMO_ASSERT(comp_entity->algo.dcomp == V42BIS); + + /* Run compression algo */ + rc = v42bis_compress_unitdata(&pcomp_index, data, len, + comp_entity->state); + + /* Find pcomp value */ + *pcomp = gprs_sndcp_comp_get_comp(comp_entity, pcomp_index); + + LOGP(DSNDCP, LOGL_DEBUG, "Data compression mode: dcomp=%d\n", *pcomp); + + LOGP(DSNDCP, LOGL_DEBUG, + "Data compression done, old length=%d, new length=%d, entity=%p\n", + len, rc, comp_entity); + + return rc; +} diff --git a/src/sgsn/gprs_sndcp_pcomp.c b/src/sgsn/gprs_sndcp_pcomp.c new file mode 100644 index 000000000..8c2fc974d --- /dev/null +++ b/src/sgsn/gprs_sndcp_pcomp.c @@ -0,0 +1,282 @@ +/* GPRS SNDCP header compression handler */ + +/* (C) 2016 by Sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Philipp Maier + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* Initalize header compression */ +int gprs_sndcp_pcomp_init(const void *ctx, struct gprs_sndcp_comp *comp_entity, + const struct gprs_sndcp_comp_field *comp_field) +{ + /* Note: This function is automatically called from + * gprs_sndcp_comp.c when a new header compression + * entity is created by gprs_sndcp.c */ + + OSMO_ASSERT(comp_entity); + OSMO_ASSERT(comp_field); + + if (comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION + && comp_entity->algo.pcomp == RFC_1144) { + OSMO_ASSERT(comp_field->rfc1144_params); + comp_entity->state = + slhc_init(ctx, comp_field->rfc1144_params->s01 + 1, + comp_field->rfc1144_params->s01 + 1); + LOGP(DSNDCP, LOGL_INFO, + "RFC1144 header compression initialized.\n"); + return 0; + } + + /* Just in case someone tries to initalize an unknown or unsupported + * header compresson. Since everything is checked during the SNDCP + * negotiation process, this should never happen! */ + OSMO_ASSERT(false); +} + +/* Terminate header compression */ +void gprs_sndcp_pcomp_term(struct gprs_sndcp_comp *comp_entity) +{ + /* Note: This function is automatically called from + * gprs_sndcp_comp.c when a header compression + * entity is deleted by gprs_sndcp.c */ + + OSMO_ASSERT(comp_entity); + + if (comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION + && comp_entity->algo.pcomp == RFC_1144) { + if (comp_entity->state) { + slhc_free((struct slcompress *)comp_entity->state); + comp_entity->state = NULL; + } + LOGP(DSNDCP, LOGL_INFO, + "RFC1144 header compression terminated.\n"); + return; + } + + /* Just in case someone tries to terminate an unknown or unsupported + * data compresson. Since everything is checked during the SNDCP + * negotiation process, this should never happen! */ + OSMO_ASSERT(false); +} + +/* Compress a packet using Van Jacobson RFC1144 header compression */ +static int rfc1144_compress(uint8_t *pcomp_index, uint8_t *data, + unsigned int len, struct slcompress *comp) +{ + uint8_t *comp_ptr; + int compr_len; + uint8_t *data_o; + + /* Create a working copy of the incoming data */ + data_o = talloc_zero_size(comp, len); + memcpy(data_o, data, len); + + /* Run compressor */ + compr_len = slhc_compress(comp, data, len, data_o, &comp_ptr, 0); + + /* Generate pcomp_index */ + if (data_o[0] & SL_TYPE_COMPRESSED_TCP) { + *pcomp_index = 2; + data_o[0] &= ~SL_TYPE_COMPRESSED_TCP; + memcpy(data, data_o, compr_len); + } else if ((data_o[0] & SL_TYPE_UNCOMPRESSED_TCP) == + SL_TYPE_UNCOMPRESSED_TCP) { + *pcomp_index = 1; + data_o[0] &= 0x4F; + memcpy(data, data_o, compr_len); + } else + *pcomp_index = 0; + + talloc_free(data_o); + return compr_len; +} + +/* Expand a packet using Van Jacobson RFC1144 header compression */ +static int rfc1144_expand(uint8_t *data, unsigned int len, uint8_t pcomp_index, + struct slcompress *comp) +{ + int data_decompressed_len; + int type; + + /* Note: this function should never be called with pcomp_index=0, + * since this condition is already filtered + * out by gprs_sndcp_pcomp_expand() */ + + /* Determine the data type by the PCOMP index */ + switch (pcomp_index) { + case 0: + type = SL_TYPE_IP; + break; + case 1: + type = SL_TYPE_UNCOMPRESSED_TCP; + break; + case 2: + type = SL_TYPE_COMPRESSED_TCP; + break; + default: + LOGP(DSNDCP, LOGL_ERROR, + "rfc1144_expand() Invalid pcomp_index value (%d) detected, assuming no compression!\n", + pcomp_index); + type = SL_TYPE_IP; + break; + } + + /* Restore the original version nibble on + * marked uncompressed packets */ + if (type == SL_TYPE_UNCOMPRESSED_TCP) { + /* Just in case the phone tags uncompressed tcp-data + * (normally this is handled by pcomp so there is + * no need for tagging the data) */ + data[0] &= 0x4F; + data_decompressed_len = slhc_remember(comp, data, len); + return data_decompressed_len; + } + + /* Uncompress compressed packets */ + else if (type == SL_TYPE_COMPRESSED_TCP) { + data_decompressed_len = slhc_uncompress(comp, data, len); + return data_decompressed_len; + } + + /* Regular or unknown packets will not be touched */ + return len; +} + +/* Expand packet header */ +int gprs_sndcp_pcomp_expand(uint8_t *data, unsigned int len, uint8_t pcomp, + const struct llist_head *comp_entities) +{ + int rc; + uint8_t pcomp_index = 0; + struct gprs_sndcp_comp *comp_entity; + + OSMO_ASSERT(data); + OSMO_ASSERT(comp_entities); + + LOGP(DSNDCP, LOGL_DEBUG, + "Header compression entity list: comp_entities=%p\n", + comp_entities); + + LOGP(DSNDCP, LOGL_DEBUG, "Header compression mode: pcomp=%d\n", pcomp); + + /* Skip on pcomp=0 */ + if (pcomp == 0) { + return len; + } + + /* Find out which compression entity handles the data */ + comp_entity = gprs_sndcp_comp_by_comp(comp_entities, pcomp); + + /* Skip compression if no suitable compression entity can be found */ + if (!comp_entity) { + return len; + } + + /* Note: Only protocol compression entities may appear in + * protocol compression context */ + OSMO_ASSERT(comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION); + + /* Note: Currently RFC1144 is the only compression method we + * support, so the only allowed algorithm is RFC1144 */ + OSMO_ASSERT(comp_entity->algo.pcomp == RFC_1144); + + /* Find pcomp_index */ + pcomp_index = gprs_sndcp_comp_get_idx(comp_entity, pcomp); + + /* Run decompression algo */ + rc = rfc1144_expand(data, len, pcomp_index, comp_entity->state); + slhc_i_status(comp_entity->state); + slhc_o_status(comp_entity->state); + + LOGP(DSNDCP, LOGL_DEBUG, + "Header expansion done, old length=%d, new length=%d, entity=%p\n", + len, rc, comp_entity); + + return rc; +} + +/* Compress packet header */ +int gprs_sndcp_pcomp_compress(uint8_t *data, unsigned int len, uint8_t *pcomp, + const struct llist_head *comp_entities, + uint8_t nsapi) +{ + int rc; + uint8_t pcomp_index = 0; + struct gprs_sndcp_comp *comp_entity; + + OSMO_ASSERT(data); + OSMO_ASSERT(pcomp); + OSMO_ASSERT(comp_entities); + + LOGP(DSNDCP, LOGL_DEBUG, + "Header compression entity list: comp_entities=%p\n", + comp_entities); + + /* Find out which compression entity handles the data */ + comp_entity = gprs_sndcp_comp_by_nsapi(comp_entities, nsapi); + + /* Skip compression if no suitable compression entity can be found */ + if (!comp_entity) { + *pcomp = 0; + return len; + } + + /* Note: Only protocol compression entities may appear in + * protocol compression context */ + OSMO_ASSERT(comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION); + + /* Note: Currently RFC1144 is the only compression method we + * support, so the only allowed algorithm is RFC1144 */ + OSMO_ASSERT(comp_entity->algo.pcomp == RFC_1144); + + /* Run compression algo */ + rc = rfc1144_compress(&pcomp_index, data, len, comp_entity->state); + slhc_i_status(comp_entity->state); + slhc_o_status(comp_entity->state); + + /* Find pcomp value */ + *pcomp = gprs_sndcp_comp_get_comp(comp_entity, pcomp_index); + + LOGP(DSNDCP, LOGL_DEBUG, "Header compression mode: pcomp=%d\n", *pcomp); + + LOGP(DSNDCP, LOGL_DEBUG, + "Header compression done, old length=%d, new length=%d, entity=%p\n", + len, rc, comp_entity); + return rc; +} diff --git a/src/sgsn/gprs_sndcp_vty.c b/src/sgsn/gprs_sndcp_vty.c new file mode 100644 index 000000000..0994a4cd2 --- /dev/null +++ b/src/sgsn/gprs_sndcp_vty.c @@ -0,0 +1,70 @@ +/* VTY interface for our GPRS SNDCP implementation */ + +/* (C) 2010 by Harald Welte + * + * 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 . + * + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static void vty_dump_sne(struct vty *vty, struct gprs_sndcp_entity *sne) +{ + vty_out(vty, " TLLI %08x SAPI=%u NSAPI=%u:%s", + sne->lle->llme->tlli, sne->lle->sapi, sne->nsapi, VTY_NEWLINE); + vty_out(vty, " Defrag: npdu=%u highest_seg=%u seg_have=0x%08x tot_len=%u%s", + sne->defrag.npdu, sne->defrag.highest_seg, sne->defrag.seg_have, + sne->defrag.tot_len, VTY_NEWLINE); +} + + +DEFUN(show_sndcp, show_sndcp_cmd, + "show sndcp", + SHOW_STR "Display information about the SNDCP protocol") +{ + struct gprs_sndcp_entity *sne; + + vty_out(vty, "State of SNDCP Entities%s", VTY_NEWLINE); + llist_for_each_entry(sne, &gprs_sndcp_entities, list) + vty_dump_sne(vty, sne); + + return CMD_SUCCESS; +} + +int gprs_sndcp_vty_init(void) +{ + install_element_ve(&show_sndcp_cmd); + + return 0; +} diff --git a/src/sgsn/gprs_sndcp_xid.c b/src/sgsn/gprs_sndcp_xid.c new file mode 100644 index 000000000..a19f64514 --- /dev/null +++ b/src/sgsn/gprs_sndcp_xid.c @@ -0,0 +1,1901 @@ +/* GPRS SNDCP XID field encoding/decoding as per 3GPP TS 44.065 */ + +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Philipp Maier + * + * 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 . + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* When the propose bit in an SNDCP-XID compression field is set to zero, + * the algorithm identifier is stripped. The algoritm parameters are specific + * for each algorithms. The following struct is used to pass the information + * about the referenced algorithm to the parser. */ +struct entity_algo_table { + unsigned int entity; /* see also: 6.5.1.1.3 and 6.6.1.1.3 */ + union gprs_sndcp_comp_algo algo; + enum gprs_sndcp_xid_param_types compclass; +}; + +/* FUNCTIONS RELATED TO SNDCP-XID ENCODING */ + +/* Encode applicable sapis (works the same in all three compression schemes) */ +static int encode_pcomp_applicable_sapis(uint8_t *dst, + const uint8_t *nsapis, + uint8_t nsapis_len) +{ + /* NOTE: Buffer *dst needs offer at 2 bytes + * of space to store the generation results */ + + uint16_t blob; + uint8_t nsapi; + int i; + + /* Bail if number of possible nsapis exceeds valid range + * (Only 11 nsapis possible for PDP-Contexts) */ + OSMO_ASSERT(nsapis_len <= 11); + + /* Encode applicable SAPIs */ + blob = 0; + for (i = 0; i < nsapis_len; i++) { + nsapi = nsapis[i]; + /* Only NSAPI 5 to 15 are applicable for user traffic (PDP- + * contexts). Only for these NSAPIs SNDCP-XID parameters + * can apply. See also 3GPP TS 44.065, 5.1 Service primitives */ + OSMO_ASSERT(nsapi >= 5 && nsapi <= 15); + blob |= (1 << nsapi); + } + + /* Store result */ + *dst = (blob >> 8) & 0xFF; + dst++; + *dst = blob & 0xFF; + + return 2; +} + +/* Encode rfc1144 parameter field + * (see also: 3GPP TS 44.065, 6.5.2.1, Table 5) */ +static int encode_pcomp_rfc1144_params(uint8_t *dst, unsigned int dst_maxlen, + const struct + gprs_sndcp_pcomp_rfc1144_params *params) +{ + /* NOTE: Buffer *dst should offer at least 3 bytes + * of space to store the generation results */ + + int dst_counter = 0; + int rc; + + OSMO_ASSERT(dst_maxlen >= 3); + + /* Zero out buffer */ + memset(dst, 0, dst_maxlen); + + /* Encode applicable SAPIs */ + rc = encode_pcomp_applicable_sapis(dst, params->nsapi, + params->nsapi_len); + dst += rc; + dst_counter += rc; + + /* Encode s01 (see also: 3GPP TS 44.065, 6.5.2.1, Table 5) */ + OSMO_ASSERT(params->s01 >= 0); + OSMO_ASSERT(params->s01 <= 255); + *dst = params->s01; + dst++; + dst_counter++; + + /* Return generated length */ + return dst_counter; +} + +/* + * Encode rfc2507 parameter field + * (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) + */ +static int encode_pcomp_rfc2507_params(uint8_t *dst, unsigned int dst_maxlen, + const struct + gprs_sndcp_pcomp_rfc2507_params *params) +{ + /* NOTE: Buffer *dst should offer at least 3 bytes + * of space to store the generation results */ + + int dst_counter = 0; + int rc; + + OSMO_ASSERT(dst_maxlen >= 9); + + /* Zero out buffer */ + memset(dst, 0, dst_maxlen); + + /* Encode applicable SAPIs */ + rc = encode_pcomp_applicable_sapis(dst, params->nsapi, + params->nsapi_len); + dst += rc; + dst_counter += rc; + + /* Encode F_MAX_PERIOD (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ + OSMO_ASSERT(params->f_max_period >= 1); + OSMO_ASSERT(params->f_max_period <= 65535); + *dst = (params->f_max_period >> 8) & 0xFF; + dst++; + dst_counter++; + *dst = (params->f_max_period) & 0xFF; + dst++; + dst_counter++; + + /* Encode F_MAX_TIME (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ + OSMO_ASSERT(params->f_max_time >= 1); + OSMO_ASSERT(params->f_max_time <= 255); + *dst = params->f_max_time; + dst++; + dst_counter++; + + /* Encode MAX_HEADER (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ + OSMO_ASSERT(params->max_header >= 60); + OSMO_ASSERT(params->max_header <= 255); + *dst = params->max_header; + dst++; + dst_counter++; + + /* Encode TCP_SPACE (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ + OSMO_ASSERT(params->tcp_space >= 3); + OSMO_ASSERT(params->tcp_space <= 255); + *dst = params->tcp_space; + dst++; + dst_counter++; + + /* Encode NON_TCP_SPACE (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ + OSMO_ASSERT(params->non_tcp_space >= 3); + OSMO_ASSERT(params->non_tcp_space <= 65535); + *dst = (params->non_tcp_space >> 8) & 0xFF; + dst++; + dst_counter++; + *dst = (params->non_tcp_space) & 0xFF; + dst++; + dst_counter++; + + /* Return generated length */ + return dst_counter; +} + +/* Encode ROHC parameter field + * (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ +static int encode_pcomp_rohc_params(uint8_t *dst, unsigned int dst_maxlen, + const struct gprs_sndcp_pcomp_rohc_params + *params) +{ + /* NOTE: Buffer *dst should offer at least 36 + * (2 * 16 Profiles + 2 * 3 Parameter) bytes + * of memory space to store generation results */ + + int i; + int dst_counter = 0; + int rc; + + OSMO_ASSERT(dst_maxlen >= 38); + + /* Bail if number of ROHC profiles exceeds limit + * (ROHC supports only a maximum of 16 different profiles) */ + OSMO_ASSERT(params->profile_len <= 16); + + /* Zero out buffer */ + memset(dst, 0, dst_maxlen); + + /* Encode applicable SAPIs */ + rc = encode_pcomp_applicable_sapis(dst, params->nsapi, + params->nsapi_len); + dst += rc; + dst_counter += rc; + + /* Encode MAX_CID (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ + OSMO_ASSERT(params->max_cid >= 0); + OSMO_ASSERT(params->max_cid <= 16383); + *dst = (params->max_cid >> 8) & 0xFF; + dst++; + *dst = params->max_cid & 0xFF; + dst++; + dst_counter += 2; + + /* Encode MAX_HEADER (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ + OSMO_ASSERT(params->max_header >= 60); + OSMO_ASSERT(params->max_header <= 255); + *dst = (params->max_header >> 8) & 0xFF; + dst++; + *dst = params->max_header & 0xFF; + dst++; + dst_counter += 2; + + /* Encode ROHC Profiles (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ + for (i = 0; i < params->profile_len; i++) { + *dst = (params->profile[i] >> 8) & 0xFF; + dst++; + *dst = params->profile[i] & 0xFF; + dst++; + dst_counter += 2; + } + + /* Return generated length */ + return dst_counter; +} + +/* Encode V.42bis parameter field + * (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ +static int encode_dcomp_v42bis_params(uint8_t *dst, unsigned int dst_maxlen, + const struct + gprs_sndcp_dcomp_v42bis_params *params) +{ + /* NOTE: Buffer *dst should offer at least 6 bytes + * of space to store the generation results */ + + int dst_counter = 0; + int rc; + + OSMO_ASSERT(dst_maxlen >= 6); + + /* Zero out buffer */ + memset(dst, 0, dst_maxlen); + + /* Encode applicable SAPIs */ + rc = encode_pcomp_applicable_sapis(dst, params->nsapi, + params->nsapi_len); + dst += rc; + dst_counter += rc; + + /* Encode P0 (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ + OSMO_ASSERT(params->p0 >= 0); + OSMO_ASSERT(params->p0 <= 3); + *dst = params->p0 & 0x03; + dst++; + dst_counter++; + + /* Encode P1 (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ + OSMO_ASSERT(params->p1 >= 512); + OSMO_ASSERT(params->p1 <= 65535); + *dst = (params->p1 >> 8) & 0xFF; + dst++; + *dst = params->p1 & 0xFF; + dst++; + dst_counter += 2; + + /* Encode P2 (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ + OSMO_ASSERT(params->p2 >= 6); + OSMO_ASSERT(params->p2 <= 250); + *dst = params->p2; + dst++; + dst_counter++; + + /* Return generated length */ + return dst_counter; +} + +/* Encode V44 parameter field + * (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ +static int encode_dcomp_v44_params(uint8_t *dst, unsigned int dst_maxlen, + const struct gprs_sndcp_dcomp_v44_params + *params) +{ + /* NOTE: Buffer *dst should offer at least 12 bytes + * of space to store the generation results */ + + int dst_counter = 0; + int rc; + + OSMO_ASSERT(dst_maxlen >= 12); + + /* Zero out buffer */ + memset(dst, 0, dst_maxlen); + + /* Encode applicable SAPIs */ + rc = encode_pcomp_applicable_sapis(dst, params->nsapi, + params->nsapi_len); + dst += rc; + dst_counter += rc; + + /* Encode C0 (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ + OSMO_ASSERT(params->c0 == 0x80 || params->c0 == 0xC0); + *dst = params->c0 & 0xC0; + dst++; + dst_counter++; + + /* Encode P0 (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ + OSMO_ASSERT(params->p0 >= 0); + OSMO_ASSERT(params->p0 <= 3); + *dst = params->p0 & 0x03; + dst++; + dst_counter++; + + /* Encode P1T (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ + OSMO_ASSERT(params->p1t >= 256); + OSMO_ASSERT(params->p1t <= 65535); + *dst = (params->p1t >> 8) & 0xFF; + dst++; + *dst = params->p1t & 0xFF; + dst++; + dst_counter += 2; + + /* Encode P1R (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ + OSMO_ASSERT(params->p1r >= 256); + OSMO_ASSERT(params->p1r <= 65535); + *dst = (params->p1r >> 8) & 0xFF; + dst++; + *dst = params->p1r & 0xFF; + dst++; + dst_counter += 2; + + /* Encode P3T (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ + OSMO_ASSERT(params->p3t >= 0); + OSMO_ASSERT(params->p3t <= 65535); + OSMO_ASSERT(params->p3t >= 2 * params->p1t); + *dst = (params->p3t >> 8) & 0xFF; + dst++; + *dst = params->p3t & 0xFF; + dst++; + dst_counter += 2; + + /* Encode P3R (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ + OSMO_ASSERT(params->p3r >= 0); + OSMO_ASSERT(params->p3r <= 65535); + OSMO_ASSERT(params->p3r >= 2 * params->p1r); + *dst = (params->p3r >> 8) & 0xFF; + dst++; + *dst = params->p3r & 0xFF; + dst++; + dst_counter += 2; + + /* Return generated length */ + return dst_counter; +} + +/* Encode data or protocol control information compression field + * (see also: 3GPP TS 44.065, 6.6.1.1, Figure 9 and + * 3GPP TS 44.065, 6.5.1.1, Figure 7) */ +static int encode_comp_field(uint8_t *dst, unsigned int dst_maxlen, + const struct gprs_sndcp_comp_field *comp_field) +{ + int dst_counter = 0; + int len; + int expected_length; + int i; + enum gprs_sndcp_xid_param_types compclass; + + uint8_t payload_bytes[256]; + int payload_bytes_len = -1; + + /* If possible, try do encode payload bytes first */ + if (comp_field->rfc1144_params) { + payload_bytes_len = + encode_pcomp_rfc1144_params(payload_bytes, + sizeof(payload_bytes), + comp_field->rfc1144_params); + } else if (comp_field->rfc2507_params) { + payload_bytes_len = + encode_pcomp_rfc2507_params(payload_bytes, + sizeof(payload_bytes), + comp_field->rfc2507_params); + } else if (comp_field->rohc_params) { + payload_bytes_len = + encode_pcomp_rohc_params(payload_bytes, + sizeof(payload_bytes), + comp_field->rohc_params); + } else if (comp_field->v42bis_params) { + payload_bytes_len = + encode_dcomp_v42bis_params(payload_bytes, + sizeof(payload_bytes), + comp_field->v42bis_params); + } else if (comp_field->v44_params) { + payload_bytes_len = + encode_dcomp_v44_params(payload_bytes, + sizeof(payload_bytes), + comp_field->v44_params); + } else + OSMO_ASSERT(false); + + /* Bail immediately if payload byte generation failed */ + OSMO_ASSERT(payload_bytes_len >= 0); + + /* Bail if comp_len is out of bounds */ + OSMO_ASSERT(comp_field->comp_len <= sizeof(comp_field->comp)); + + /* Calculate length field of the data block */ + if (comp_field->p) { + len = + payload_bytes_len + + ceil((double)(comp_field->comp_len) / 2.0); + expected_length = len + 3; + } else { + len = payload_bytes_len; + expected_length = len + 2; + } + + /* Bail immediately if no sufficient memory space is supplied */ + OSMO_ASSERT(dst_maxlen >= expected_length); + + /* Check if the entity number is within bounds */ + OSMO_ASSERT(comp_field->entity <= 0x1f); + + /* Check if the algorithm number is within bounds */ + compclass = gprs_sndcp_get_compression_class(comp_field); + switch (compclass) { + case SNDCP_XID_PROTOCOL_COMPRESSION: + OSMO_ASSERT(comp_field->algo.pcomp >= 0 && comp_field->algo.pcomp <= 0x1f); + break; + case SNDCP_XID_DATA_COMPRESSION: + OSMO_ASSERT(comp_field->algo.dcomp >= 0 && comp_field->algo.dcomp <= 0x1f); + break; + default: + OSMO_ASSERT(false); + } + + /* Zero out buffer */ + memset(dst, 0, dst_maxlen); + + /* Encode Propose bit */ + if (comp_field->p) + *dst |= (1 << 7); + + /* Encode entity number */ + *dst |= comp_field->entity & 0x1F; + dst++; + dst_counter++; + + /* Encode algorithm number */ + if (comp_field->p) { + if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION) + *dst |= comp_field->algo.pcomp & 0x1F; + else + *dst |= comp_field->algo.dcomp & 0x1F; + dst++; + dst_counter++; + } + + /* Encode length field */ + *dst |= len & 0xFF; + dst++; + dst_counter++; + + /* Encode PCOMP/DCOMP values */ + if (comp_field->p) { + for (i = 0; i < comp_field->comp_len; i++) { + /* Check if submitted PCOMP/DCOMP + values are within bounds */ + if (comp_field->comp[i] > 0x0F) + return -EINVAL; + + if (i & 1) { + *dst |= comp_field->comp[i] & 0x0F; + dst++; + dst_counter++; + } else + *dst |= (comp_field->comp[i] << 4) & 0xF0; + } + + if (i & 1) { + dst++; + dst_counter++; + } + } + + /* Append payload bytes */ + memcpy(dst, payload_bytes, payload_bytes_len); + dst_counter += payload_bytes_len; + + /* Return generated length */ + return dst_counter; +} + +/* Find out to which compression class the specified comp-field belongs + * (header compression or data compression?) */ +enum gprs_sndcp_xid_param_types gprs_sndcp_get_compression_class(const struct gprs_sndcp_comp_field + *comp_field) +{ + OSMO_ASSERT(comp_field); + + if (comp_field->rfc1144_params) + return SNDCP_XID_PROTOCOL_COMPRESSION; + else if (comp_field->rfc2507_params) + return SNDCP_XID_PROTOCOL_COMPRESSION; + else if (comp_field->rohc_params) + return SNDCP_XID_PROTOCOL_COMPRESSION; + else if (comp_field->v42bis_params) + return SNDCP_XID_DATA_COMPRESSION; + else if (comp_field->v44_params) + return SNDCP_XID_DATA_COMPRESSION; + else + return SNDCP_XID_INVALID_COMPRESSION; +} + +/* Convert all compression fields to bytstreams */ +static int gprs_sndcp_pack_fields(const struct llist_head *comp_fields, + uint8_t *dst, + unsigned int dst_maxlen, int class) +{ + struct gprs_sndcp_comp_field *comp_field; + int byte_counter = 0; + int rc; + + llist_for_each_entry_reverse(comp_field, comp_fields, list) { + if (class == gprs_sndcp_get_compression_class(comp_field)) { + rc = encode_comp_field(dst + byte_counter, + dst_maxlen - byte_counter, + comp_field); + + /* When input data is correct, there is + * no reason for the encoder to fail! */ + OSMO_ASSERT(rc >= 0); + + byte_counter += rc; + } + } + + /* Return generated length */ + return byte_counter; +} + +/* Transform a list with compression fields into an SNDCP-XID message (dst) */ +int gprs_sndcp_compile_xid(uint8_t *dst, unsigned int dst_maxlen, + const struct llist_head *comp_fields, int version) +{ + int rc; + int byte_counter = 0; + uint8_t comp_bytes[512]; + uint8_t xid_version_number[1]; + + OSMO_ASSERT(comp_fields); + OSMO_ASSERT(dst); + OSMO_ASSERT(dst_maxlen >= 2 + sizeof(xid_version_number)); + + /* Prepend header with version number */ + if (version >= 0) { + xid_version_number[0] = (uint8_t) (version & 0xff); + dst = + tlv_put(dst, SNDCP_XID_VERSION_NUMBER, + sizeof(xid_version_number), xid_version_number); + byte_counter += (sizeof(xid_version_number) + 2); + } + + /* Stop if there is no compression fields supplied */ + if (llist_empty(comp_fields)) + return byte_counter; + + /* Add data compression fields */ + rc = gprs_sndcp_pack_fields(comp_fields, comp_bytes, + sizeof(comp_bytes), + SNDCP_XID_DATA_COMPRESSION); + OSMO_ASSERT(rc >= 0); + + if (rc > 0) { + dst = tlv_put(dst, SNDCP_XID_DATA_COMPRESSION, rc, comp_bytes); + byte_counter += rc + 2; + } + + /* Add header compression fields */ + rc = gprs_sndcp_pack_fields(comp_fields, comp_bytes, + sizeof(comp_bytes), + SNDCP_XID_PROTOCOL_COMPRESSION); + OSMO_ASSERT(rc >= 0); + + if (rc > 0) { + dst = tlv_put(dst, SNDCP_XID_PROTOCOL_COMPRESSION, rc, + comp_bytes); + byte_counter += rc + 2; + } + + /* Return generated length */ + return byte_counter; +} + +/* FUNCTIONS RELATED TO SNDCP-XID DECODING */ + +/* Decode applicable sapis (works the same in all three compression schemes) */ +static int decode_pcomp_applicable_sapis(uint8_t *nsapis, + uint8_t *nsapis_len, + const uint8_t *src, + unsigned int src_len) +{ + uint16_t blob; + int i; + int nsapi_len = 0; + + /* Exit immediately if no result can be stored */ + if (!nsapis) + return -EINVAL; + + /* Exit immediately if not enough input data is available */ + if (src_len < 2) + return -EINVAL; + + /* Read bitmask */ + blob = *src; + blob = (blob << 8) & 0xFF00; + src++; + blob |= (*src) & 0xFF; + blob = (blob >> 5); + + /* Decode applicable SAPIs */ + for (i = 0; i < 15; i++) { + if ((blob >> i) & 1) { + nsapis[nsapi_len] = i + 5; + nsapi_len++; + } + } + + /* Return consumed length */ + *nsapis_len = nsapi_len; + return 2; +} + +/* Decode 16 bit field */ +static int decode_pcomp_16_bit_field(int *value_int, uint16_t * value_uint16, + const uint8_t *src, + unsigned int src_len, + int value_min, int value_max) +{ + uint16_t blob; + + /* Reset values to zero (just to be sure) */ + if (value_int) + *value_int = -1; + if (value_uint16) + *value_uint16 = 0; + + /* Exit if not enough src are available */ + if (src_len < 2) + return -EINVAL; + + /* Decode bit value */ + blob = *src; + blob = (blob << 8) & 0xFF00; + src++; + blob |= *src; + + /* Check if parsed value is within bounds */ + if (blob < value_min) + return -EINVAL; + if (blob > value_max) + return -EINVAL; + + /* Hand back results to the caller */ + if (value_int) + *value_int = blob; + if (value_uint16) + *value_uint16 = blob; + + /* Return consumed length */ + return 2; +} + +/* Decode 8 bit field */ +static int decode_pcomp_8_bit_field(int *value_int, uint8_t *value_uint8, + const uint8_t *src, + unsigned int src_len, + int value_min, int value_max) +{ + uint8_t blob; + + /* Reset values to invalid (just to be sure) */ + if (value_int) + *value_int = -1; + if (value_uint8) + *value_uint8 = 0; + + /* Exit if not enough src are available */ + if (src_len < 1) + return -EINVAL; + + /* Decode bit value */ + blob = *src; + + /* Check if parsed value is within bounds */ + if (blob < value_min) + return -EINVAL; + if (blob > value_max) + return -EINVAL; + + /* Hand back results to the caller */ + if (value_int) + *value_int = blob; + if (value_uint8) + *value_uint8 = blob; + + /* Return consumed length */ + return 1; +} + +/* Decode rfc1144 parameter field see also: 3GPP TS 44.065, 6.5.2.1, Table 5) */ +static int decode_pcomp_rfc1144_params(struct gprs_sndcp_pcomp_rfc1144_params + *params, const uint8_t *src, + unsigned int src_len) +{ + int rc; + int byte_counter = 0; + + /* Mark all optional parameters invalid by default */ + params->s01 = -1; + + /* Decode applicable SAPIs */ + rc = decode_pcomp_applicable_sapis(params->nsapi, ¶ms->nsapi_len, + src, src_len); + if (rc > 0) { + byte_counter += rc; + src += rc; + } else + return byte_counter; + + /* Decode parameter S0 -1 + * (see also: 3GPP TS 44.065, 6.5.2.1, Table 5) */ + rc = decode_pcomp_8_bit_field(¶ms->s01, NULL, src, + src_len - byte_counter, 0, 255); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Return consumed length */ + return byte_counter; +} + +/* Decode rfc2507 parameter field + * (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ +static int decode_pcomp_rfc2507_params(struct gprs_sndcp_pcomp_rfc2507_params + *params, const uint8_t *src, + unsigned int src_len) +{ + int rc; + int byte_counter = 0; + + /* Mark all optional parameters invalid by default */ + params->f_max_period = -1; + params->f_max_time = -1; + params->max_header = -1; + params->tcp_space = -1; + params->non_tcp_space = -1; + + /* Decode applicable SAPIs */ + rc = decode_pcomp_applicable_sapis(params->nsapi, ¶ms->nsapi_len, + src, src_len); + if (rc > 0) { + byte_counter += rc; + src += rc; + } else + return byte_counter; + + /* Decode F_MAX_PERIOD (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ + rc = decode_pcomp_16_bit_field(¶ms->f_max_period, NULL, src, + src_len - byte_counter, 1, 65535); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Decode F_MAX_TIME (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ + rc = decode_pcomp_8_bit_field(¶ms->f_max_time, NULL, src, + src_len - byte_counter, 1, 255); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Decode MAX_HEADER (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ + rc = decode_pcomp_8_bit_field(¶ms->max_header, NULL, src, + src_len - byte_counter, 60, 255); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Decode TCP_SPACE (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ + rc = decode_pcomp_8_bit_field(¶ms->tcp_space, NULL, src, + src_len - byte_counter, 3, 255); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Decode NON_TCP_SPACE (see also: 3GPP TS 44.065, 6.5.3.1, Table 6) */ + rc = decode_pcomp_16_bit_field(¶ms->non_tcp_space, NULL, src, + src_len - byte_counter, 3, 65535); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Return consumed length */ + return byte_counter; +} + +/* Decode ROHC parameter field (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ +static int decode_pcomp_rohc_params(struct gprs_sndcp_pcomp_rohc_params *params, + const uint8_t *src, unsigned int src_len) +{ + int rc; + int byte_counter = 0; + int i; + + /* Mark all optional parameters invalid by default */ + params->max_cid = -1; + params->max_header = -1; + + /* Decode applicable SAPIs */ + rc = decode_pcomp_applicable_sapis(params->nsapi, ¶ms->nsapi_len, + src, src_len); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Decode MAX_CID (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ + rc = decode_pcomp_16_bit_field(¶ms->max_cid, NULL, src, + src_len - byte_counter, 0, 16383); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Decode MAX_HEADER (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ + rc = decode_pcomp_16_bit_field(¶ms->max_header, NULL, src, + src_len - byte_counter, 60, 255); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Decode Profiles (see also: 3GPP TS 44.065, 6.5.4.1, Table 10) */ + for (i = 0; i < 16; i++) { + params->profile_len = 0; + rc = decode_pcomp_16_bit_field(NULL, ¶ms->profile[i], src, + src_len - byte_counter, 0, + 65535); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + params->profile_len = i + 1; + } + + /* Return consumed length */ + return byte_counter; +} + +/* Decode V.42bis parameter field + * (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ +static int decode_dcomp_v42bis_params(struct gprs_sndcp_dcomp_v42bis_params + *params, const uint8_t *src, + unsigned int src_len) +{ + int rc; + int byte_counter = 0; + + /* Mark all optional parameters invalid by default */ + params->p0 = -1; + params->p1 = -1; + params->p2 = -1; + + /* Decode applicable SAPIs */ + rc = decode_pcomp_applicable_sapis(params->nsapi, ¶ms->nsapi_len, + src, src_len); + if (rc > 0) { + byte_counter += rc; + src += rc; + } else + return byte_counter; + + /* Decode P0 (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ + rc = decode_pcomp_8_bit_field(¶ms->p0, NULL, src, + src_len - byte_counter, 0, 3); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Decode P1 (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ + rc = decode_pcomp_16_bit_field(¶ms->p1, NULL, src, + src_len - byte_counter, 512, 65535); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Decode P2 (see also: 3GPP TS 44.065, 6.6.2.1, Table 7a) */ + rc = decode_pcomp_8_bit_field(¶ms->p2, NULL, src, + src_len - byte_counter, 6, 250); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Return consumed length */ + return byte_counter; +} + +/* Decode V44 parameter field (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ +static int decode_dcomp_v44_params(struct gprs_sndcp_dcomp_v44_params *params, + const uint8_t *src, unsigned int src_len) +{ + int rc; + int byte_counter = 0; + + /* Mark all optional parameters invalid by default */ + params->c0 = -1; + params->p0 = -1; + params->p1t = -1; + params->p1r = -1; + params->p3t = -1; + params->p3r = -1; + + /* Decode applicable SAPIs */ + rc = decode_pcomp_applicable_sapis(params->nsapi, ¶ms->nsapi_len, + src, src_len); + if (rc > 0) { + byte_counter += rc; + src += rc; + } else + return byte_counter; + + /* Decode C0 (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ + rc = decode_pcomp_8_bit_field(¶ms->c0, NULL, src, + src_len - byte_counter, 0, 255); + if (rc <= 0) + return byte_counter; + if ((params->c0 != 0x80) && (params->c0 != 0xC0)) + return -EINVAL; + byte_counter += rc; + src += rc; + + /* Decode P0 (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ + rc = decode_pcomp_8_bit_field(¶ms->p0, NULL, src, + src_len - byte_counter, 0, 3); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Decode P1T (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ + rc = decode_pcomp_16_bit_field(¶ms->p1t, NULL, src, + src_len - byte_counter, 265, 65535); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Decode P1R (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ + rc = decode_pcomp_16_bit_field(¶ms->p1r, NULL, src, + src_len - byte_counter, 265, 65535); + if (rc <= 0) + return byte_counter; + byte_counter += rc; + src += rc; + + /* Decode P3T (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ + rc = decode_pcomp_16_bit_field(¶ms->p3t, NULL, src, + src_len - byte_counter, 265, 65535); + if (rc <= 0) + return byte_counter; + if (params->p3t < 2 * params->p1t) + return -EINVAL; + byte_counter += rc; + src += rc; + + /* Decode P3R (see also: 3GPP TS 44.065, 6.6.3.1, Table 7c) */ + rc = decode_pcomp_16_bit_field(¶ms->p3r, NULL, src, + src_len - byte_counter, 265, 65535); + if (rc <= 0) + return byte_counter; + if (params->p3r < 2 * params->p1r) + return -EINVAL; + byte_counter += rc; + src += rc; + + /* Return consumed length */ + return byte_counter; +} + +/* Lookup protocol compression algorithm identfier by entity ID */ +static enum gprs_sndcp_hdr_comp_algo lookup_algorithm_identifier_pcomp(int entity, + const struct entity_algo_table *lt, + unsigned int lt_len) +{ + int i; + + if (!lt) + return -1; + + for (i = 0; i < lt_len; i++) { + if ((lt[i].entity == entity) + && (lt[i].compclass == SNDCP_XID_PROTOCOL_COMPRESSION)) + return lt[i].algo.pcomp; + } + + return SNDCP_XID_INVALID_COMPRESSION; +} + +/* Lookup a data compression algorithm identfier by entity ID */ +static enum gprs_sndcp_data_comp_algo lookup_algorithm_identifier_dcomp(int entity, + const struct entity_algo_table *lt, + unsigned int lt_len) +{ + int i; + + if (!lt) + return -1; + + for (i = 0; i < lt_len; i++) { + if ((lt[i].entity == entity) + && (lt[i].compclass == SNDCP_XID_DATA_COMPRESSION)) + return lt[i].algo.dcomp; + } + + return SNDCP_XID_INVALID_COMPRESSION; +} + +/* Helper function for decode_comp_field(), decodes + * numeric pcomp/dcomp values */ +static int decode_comp_values(struct gprs_sndcp_comp_field *comp_field, + const uint8_t *src, enum gprs_sndcp_xid_param_types compclass) +{ + int src_counter = 0; + int i; + + if (comp_field->p) { + /* Determine the number of expected PCOMP/DCOMP values */ + switch (compclass) { + case SNDCP_XID_PROTOCOL_COMPRESSION: + /* For protocol compression */ + switch (comp_field->algo.pcomp) { + case RFC_1144: + comp_field->comp_len = RFC1144_PCOMP_NUM; + break; + case RFC_2507: + comp_field->comp_len = RFC2507_PCOMP_NUM; + break; + case ROHC: + comp_field->comp_len = ROHC_PCOMP_NUM; + break; + + /* Exit if the algorithem type encodes + something unknown / unspecified */ + default: + return -EINVAL; + } + break; + case SNDCP_XID_DATA_COMPRESSION: + /* For data compression */ + switch (comp_field->algo.dcomp) { + case V42BIS: + comp_field->comp_len = V42BIS_DCOMP_NUM; + break; + case V44: + comp_field->comp_len = V44_DCOMP_NUM; + break; + + /* Exit if the algorithem type encodes + something unknown / unspecified */ + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + for (i = 0; i < comp_field->comp_len; i++) { + if (i & 1) { + comp_field->comp[i] = (*src) & 0x0F; + src++; + src_counter++; + } else + comp_field->comp[i] = ((*src) >> 4) & 0x0F; + } + + if (i & 1) { + src++; + src_counter++; + } + } + + return src_counter; +} + +/* Helper function for decode_comp_field(), decodes the parameters + * which are protocol compression specific */ +static int decode_pcomp_params(struct gprs_sndcp_comp_field *comp_field, + const uint8_t *src, int src_len) +{ + int rc; + + switch (comp_field->algo.pcomp) { + case RFC_1144: + comp_field->rfc1144_params = talloc_zero(comp_field, struct + gprs_sndcp_pcomp_rfc1144_params); + rc = decode_pcomp_rfc1144_params(comp_field->rfc1144_params, + src, src_len); + if (rc < 0) + talloc_free(comp_field->rfc1144_params); + break; + case RFC_2507: + comp_field->rfc2507_params = talloc_zero(comp_field, struct + gprs_sndcp_pcomp_rfc2507_params); + rc = decode_pcomp_rfc2507_params(comp_field->rfc2507_params, + src, src_len); + if (rc < 0) + talloc_free(comp_field->rfc1144_params); + break; + case ROHC: + comp_field->rohc_params = talloc_zero(comp_field, struct + gprs_sndcp_pcomp_rohc_params); + rc = decode_pcomp_rohc_params(comp_field->rohc_params, src, + src_len); + if (rc < 0) + talloc_free(comp_field->rohc_params); + break; + + /* If no suitable decoder is detected, + leave the remaining bytes undecoded */ + default: + rc = src_len; + } + + if (rc < 0) { + comp_field->rfc1144_params = NULL; + comp_field->rfc2507_params = NULL; + comp_field->rohc_params = NULL; + } + + return rc; +} + +/* Helper function for decode_comp_field(), decodes the parameters + * which are data compression specific */ +static int decode_dcomp_params(struct gprs_sndcp_comp_field *comp_field, + const uint8_t *src, int src_len) +{ + int rc; + + switch (comp_field->algo.dcomp) { + case V42BIS: + comp_field->v42bis_params = talloc_zero(comp_field, struct + gprs_sndcp_dcomp_v42bis_params); + rc = decode_dcomp_v42bis_params(comp_field->v42bis_params, src, + src_len); + if (rc < 0) + talloc_free(comp_field->v42bis_params); + break; + case V44: + comp_field->v44_params = talloc_zero(comp_field, struct + gprs_sndcp_dcomp_v44_params); + rc = decode_dcomp_v44_params(comp_field->v44_params, src, + src_len); + if (rc < 0) + talloc_free(comp_field->v44_params); + break; + + /* If no suitable decoder is detected, + * leave the remaining bytes undecoded */ + default: + rc = src_len; + } + + if (rc < 0) { + comp_field->v42bis_params = NULL; + comp_field->v44_params = NULL; + } + + return rc; +} + +/* Decode data or protocol control information compression field + * (see also: 3GPP TS 44.065, 6.6.1.1, Figure 9 and + * 3GPP TS 44.065, 6.5.1.1, Figure 7) */ +static int decode_comp_field(struct gprs_sndcp_comp_field *comp_field, + const uint8_t *src, unsigned int src_len, + const struct entity_algo_table *lt, + unsigned int lt_len, + enum gprs_sndcp_xid_param_types compclass) +{ + int src_counter = 0; + unsigned int len; + int rc; + + OSMO_ASSERT(comp_field); + + /* Exit immediately if it is clear that no + parseable data is present */ + if (src_len < 1 || !src) + return -EINVAL; + + /* Zero out target struct */ + memset(comp_field, 0, sizeof(struct gprs_sndcp_comp_field)); + + /* Decode Propose bit and Entity number */ + if ((*src) & 0x80) + comp_field->p = 1; + comp_field->entity = (*src) & 0x1F; + src_counter++; + src++; + + /* Decode algorithm number (if present) */ + if (comp_field->p) { + switch (compclass) { + case SNDCP_XID_PROTOCOL_COMPRESSION: + comp_field->algo.pcomp = (*src) & 0x1F; + break; + case SNDCP_XID_DATA_COMPRESSION: + comp_field->algo.dcomp = (*src) & 0x1F; + break; + default: + return -EINVAL; + } + src_counter++; + src++; + } + /* Alternatively take the information from the lookup table */ + else { + switch (compclass) { + case SNDCP_XID_PROTOCOL_COMPRESSION: + comp_field->algo.pcomp = + lookup_algorithm_identifier_pcomp(comp_field->entity, lt, lt_len); + break; + case SNDCP_XID_DATA_COMPRESSION: + comp_field->algo.dcomp = + lookup_algorithm_identifier_dcomp(comp_field->entity, lt, lt_len); + break; + default: + return -EINVAL; + } + } + + /* Decode length field */ + len = *src; + src_counter++; + src++; + + /* Decode PCOMP/DCOMP values */ + rc = decode_comp_values(comp_field, src, compclass); + if (rc < 0) + return -EINVAL; + src_counter += rc; + src += rc; + len -= rc; + + /* Decode algorithm specific payload data */ + if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION) + rc = decode_pcomp_params(comp_field, src, len); + else if (compclass == SNDCP_XID_DATA_COMPRESSION) + rc = decode_dcomp_params(comp_field, src, len); + else + return -EINVAL; + + if (rc >= 0) + src_counter += rc; + else + return -EINVAL; + + /* Return consumed length */ + return src_counter; +} + +/* Helper function for gprs_sndcp_decode_xid() to decode XID blocks */ +static int decode_xid_block(struct llist_head *comp_fields, uint8_t tag, + uint16_t tag_len, const uint8_t *val, + const struct entity_algo_table *lt, + unsigned int lt_len) +{ + struct gprs_sndcp_comp_field *comp_field; + int byte_counter = 0; + int comp_field_count = 0; + int rc; + + byte_counter = 0; + do { + /* Bail if more than the maximum number of + comp_fields is generated */ + if (comp_field_count > MAX_ENTITIES * 2) { + return -EINVAL; + } + + /* Parse and add comp_field */ + comp_field = + talloc_zero(comp_fields, struct gprs_sndcp_comp_field); + + rc = decode_comp_field(comp_field, val + byte_counter, + tag_len - byte_counter, lt, lt_len, tag); + + if (rc < 0) { + talloc_free(comp_field); + return -EINVAL; + } + + byte_counter += rc; + llist_add(&comp_field->list, comp_fields); + comp_field_count++; + } + while (tag_len - byte_counter > 0); + + return byte_counter; +} + +/* Transform an SNDCP-XID message (src) into a list of SNDCP-XID fields */ +static int gprs_sndcp_decode_xid(int *version, struct llist_head *comp_fields, + const uint8_t *src, unsigned int src_len, + const struct entity_algo_table *lt, + unsigned int lt_len) +{ + int src_pos = 0; + uint8_t tag; + uint16_t tag_len; + const uint8_t *val; + int byte_counter = 0; + int rc; + int tlv_count = 0; + + /* Preset version value as invalid */ + if (version) + *version = -1; + + /* Valid TLV-Tag and types */ + static const struct tlv_definition sndcp_xid_def = { + .def = { + [SNDCP_XID_VERSION_NUMBER] = {TLV_TYPE_TLV,}, + [SNDCP_XID_DATA_COMPRESSION] = {TLV_TYPE_TLV,}, + [SNDCP_XID_PROTOCOL_COMPRESSION] = {TLV_TYPE_TLV,}, + }, + }; + + /* Parse TLV-Encoded SNDCP-XID message and defer payload + to the apporpiate sub-parser functions */ + while (1) { + + /* Bail if an the maximum number of TLV fields + * have been parsed */ + if (tlv_count >= 3) { + talloc_free(comp_fields); + return -EINVAL; + } + + /* Parse TLV field */ + rc = tlv_parse_one(&tag, &tag_len, &val, &sndcp_xid_def, + src + src_pos, src_len - src_pos); + if (rc > 0) + src_pos += rc; + else { + talloc_free(comp_fields); + return -EINVAL; + } + + /* Decode sndcp xid version number */ + if (version && tag == SNDCP_XID_VERSION_NUMBER) + *version = val[0]; + + /* Decode compression parameters */ + if ((tag == SNDCP_XID_PROTOCOL_COMPRESSION) + || (tag == SNDCP_XID_DATA_COMPRESSION)) { + rc = decode_xid_block(comp_fields, tag, tag_len, val, + lt, lt_len); + + if (rc < 0) { + talloc_free(comp_fields); + return -EINVAL; + } else + byte_counter += rc; + } + + /* Stop when no further TLV elements can be expected */ + if (src_len - src_pos <= 2) + break; + + tlv_count++; + } + + return 0; +} + +/* Fill up lookutable from a list with comression entitiy fields */ +static int gprs_sndcp_fill_table(struct + entity_algo_table *lt, + unsigned int lt_len, + const struct llist_head *comp_fields) +{ + struct gprs_sndcp_comp_field *comp_field; + int i = 0; + enum gprs_sndcp_xid_param_types compclass; + + if (!comp_fields) + return -EINVAL; + if (!lt) + return -EINVAL; + + memset(lt, 0, sizeof(*lt)); + + llist_for_each_entry(comp_field, comp_fields, list) { + compclass = gprs_sndcp_get_compression_class(comp_field); + switch (compclass) { + case SNDCP_XID_PROTOCOL_COMPRESSION: + lt[i].algo.pcomp = comp_field->algo.pcomp; + break; + case SNDCP_XID_DATA_COMPRESSION: + lt[i].algo.dcomp = comp_field->algo.dcomp; + break; + case SNDCP_XID_INVALID_COMPRESSION: + continue; + default: + memset(lt, 0, sizeof(*lt)); + return -EINVAL; + } + lt[i].compclass = compclass; + lt[i].entity = comp_field->entity; + i++; + } + + return i; +} + +/* Complete comp field params + * (if a param (dst) is not valid, it will be copied from source (src) */ +static int complete_comp_field_params(struct gprs_sndcp_comp_field + *comp_field_dst, const struct + gprs_sndcp_comp_field *comp_field_src) +{ + enum gprs_sndcp_xid_param_types compclass; + + compclass = gprs_sndcp_get_compression_class(comp_field_dst); + if (compclass == SNDCP_XID_INVALID_COMPRESSION) + return -EINVAL; + + if (comp_field_dst->rfc1144_params && comp_field_src->rfc1144_params) { + if (comp_field_dst->rfc1144_params->s01 < 0) { + comp_field_dst->rfc1144_params->s01 = + comp_field_src->rfc1144_params->s01; + } + return 0; + } + + if (comp_field_dst->rfc2507_params && comp_field_src->rfc2507_params) { + + if (comp_field_dst->rfc2507_params->f_max_period < 0) { + comp_field_dst->rfc2507_params->f_max_period = + comp_field_src->rfc2507_params->f_max_period; + } + if (comp_field_dst->rfc2507_params->f_max_time < 0) { + comp_field_dst->rfc2507_params->f_max_time = + comp_field_src->rfc2507_params->f_max_time; + } + if (comp_field_dst->rfc2507_params->max_header < 0) { + comp_field_dst->rfc2507_params->max_header = + comp_field_src->rfc2507_params->max_header; + } + if (comp_field_dst->rfc2507_params->tcp_space < 0) { + comp_field_dst->rfc2507_params->tcp_space = + comp_field_src->rfc2507_params->tcp_space; + } + if (comp_field_dst->rfc2507_params->non_tcp_space < 0) { + comp_field_dst->rfc2507_params->non_tcp_space = + comp_field_src->rfc2507_params->non_tcp_space; + } + return 0; + } + + if (comp_field_dst->rohc_params && comp_field_src->rohc_params) { + if (comp_field_dst->rohc_params->max_cid < 0) { + comp_field_dst->rohc_params->max_cid = + comp_field_src->rohc_params->max_cid; + } + if (comp_field_dst->rohc_params->max_header < 0) { + comp_field_dst->rohc_params->max_header = + comp_field_src->rohc_params->max_header; + } + if (comp_field_dst->rohc_params->profile_len > 0) { + memcpy(comp_field_dst->rohc_params->profile, + comp_field_src->rohc_params->profile, + sizeof(comp_field_dst->rohc_params->profile)); + comp_field_dst->rohc_params->profile_len = + comp_field_src->rohc_params->profile_len; + } + + return 0; + } + + if (comp_field_dst->v42bis_params && comp_field_src->v42bis_params) { + if (comp_field_dst->v42bis_params->p0 < 0) { + comp_field_dst->v42bis_params->p0 = + comp_field_src->v42bis_params->p0; + } + if (comp_field_dst->v42bis_params->p1 < 0) { + comp_field_dst->v42bis_params->p1 = + comp_field_src->v42bis_params->p1; + } + if (comp_field_dst->v42bis_params->p2 < 0) { + comp_field_dst->v42bis_params->p2 = + comp_field_src->v42bis_params->p2; + } + return 0; + } + + if (comp_field_dst->v44_params && comp_field_src->v44_params) { + if (comp_field_dst->v44_params->c0 < 0) { + comp_field_dst->v44_params->c0 = + comp_field_src->v44_params->c0; + } + if (comp_field_dst->v44_params->p0 < 0) { + comp_field_dst->v44_params->p0 = + comp_field_src->v44_params->p0; + } + if (comp_field_dst->v44_params->p1t < 0) { + comp_field_dst->v44_params->p1t = + comp_field_src->v44_params->p1t; + } + if (comp_field_dst->v44_params->p1r < 0) { + comp_field_dst->v44_params->p1r = + comp_field_src->v44_params->p1r; + } + if (comp_field_dst->v44_params->p3t < 0) { + comp_field_dst->v44_params->p3t = + comp_field_src->v44_params->p3t; + } + if (comp_field_dst->v44_params->p3r < 0) { + comp_field_dst->v44_params->p3r = + comp_field_src->v44_params->p3r; + } + return 0; + } + + /* There should be at least exist one param set + * in the destination struct, otherwise something + * must be wrong! */ + return -EINVAL; +} + +/* Complete missing parameters in a comp_field */ +static int gprs_sndcp_complete_comp_field(struct gprs_sndcp_comp_field + *comp_field, const struct llist_head + *comp_fields) +{ + struct gprs_sndcp_comp_field *comp_field_src; + int rc = 0; + + llist_for_each_entry(comp_field_src, comp_fields, list) { + if (comp_field_src->entity == comp_field->entity) { + + /* Complete header fields */ + if (comp_field_src->comp_len > 0) { + memcpy(comp_field->comp, + comp_field_src->comp, + sizeof(comp_field_src->comp)); + comp_field->comp_len = comp_field_src->comp_len; + } + + /* Complete parameter fields */ + rc = complete_comp_field_params(comp_field, + comp_field_src); + } + } + + return rc; +} + +/* Complete missing parameters of all comp_field in a list */ +static int gprs_sndcp_complete_comp_fields(struct llist_head + *comp_fields_incomplete, + const struct llist_head *comp_fields) +{ + struct gprs_sndcp_comp_field *comp_field_incomplete; + int rc; + + llist_for_each_entry(comp_field_incomplete, comp_fields_incomplete, + list) { + + rc = gprs_sndcp_complete_comp_field(comp_field_incomplete, + comp_fields); + if (rc < 0) + return -EINVAL; + + } + + return 0; +} + +/* Transform an SNDCP-XID message (src) into a list of SNDCP-XID fields */ +struct llist_head *gprs_sndcp_parse_xid(int *version, + const void *ctx, + const uint8_t *src, + unsigned int src_len, + const struct llist_head + *comp_fields_req) +{ + int rc; + int lt_len; + struct llist_head *comp_fields; + struct entity_algo_table lt[MAX_ENTITIES * 2]; + + /* In case of a zero length field, just exit */ + if (src_len == 0) + return NULL; + + /* We should go any further if we have a field length greater + * zero and a null pointer as buffer! */ + OSMO_ASSERT(src); + + comp_fields = talloc_zero(ctx, struct llist_head); + INIT_LLIST_HEAD(comp_fields); + + if (comp_fields_req) { + /* Generate lookup table */ + lt_len = + gprs_sndcp_fill_table(lt, MAX_ENTITIES * 2, + comp_fields_req); + if (lt_len < 0) { + talloc_free(comp_fields); + return NULL; + } + + /* Parse SNDCP-CID XID-Field */ + rc = gprs_sndcp_decode_xid(version, comp_fields, src, src_len, + lt, lt_len); + if (rc < 0) { + talloc_free(comp_fields); + return NULL; + } + + rc = gprs_sndcp_complete_comp_fields(comp_fields, + comp_fields_req); + if (rc < 0) { + talloc_free(comp_fields); + return NULL; + } + + } else { + /* Parse SNDCP-CID XID-Field */ + rc = gprs_sndcp_decode_xid(version, comp_fields, src, src_len, + NULL, 0); + if (rc < 0) { + talloc_free(comp_fields); + return NULL; + } + } + + return comp_fields; +} + +/* Helper for gprs_sndcp_dump_comp_fields(), + * dumps protocol compression parameters */ +static void dump_pcomp_params(const struct gprs_sndcp_comp_field + *comp_field, unsigned int logl) +{ + int i; + + switch (comp_field->algo.pcomp) { + case RFC_1144: + if (comp_field->rfc1144_params == NULL) { + LOGP(DSNDCP, logl, + " gprs_sndcp_pcomp_rfc1144_params=NULL\n"); + break; + } + LOGP(DSNDCP, logl, " gprs_sndcp_pcomp_rfc1144_params {\n"); + LOGP(DSNDCP, logl, + " nsapi_len=%d;\n", + comp_field->rfc1144_params->nsapi_len); + if (comp_field->rfc1144_params->nsapi_len == 0) + LOGP(DSNDCP, logl, " nsapi[] = NULL;\n"); + for (i = 0; i < comp_field->rfc1144_params->nsapi_len; i++) { + LOGP(DSNDCP, logl, + " nsapi[%d]=%d;\n", i, + comp_field->rfc1144_params->nsapi[i]); + } + LOGP(DSNDCP, logl, " s01=%d;\n", + comp_field->rfc1144_params->s01); + LOGP(DSNDCP, logl, " }\n"); + break; + case RFC_2507: + if (comp_field->rfc2507_params == NULL) { + LOGP(DSNDCP, logl, + " gprs_sndcp_pcomp_rfc2507_params=NULL\n"); + break; + } + LOGP(DSNDCP, logl, " gprs_sndcp_pcomp_rfc2507_params {\n"); + LOGP(DSNDCP, logl, + " nsapi_len=%d;\n", + comp_field->rfc2507_params->nsapi_len); + if (comp_field->rfc2507_params->nsapi_len == 0) + LOGP(DSNDCP, logl, " nsapi[] = NULL;\n"); + for (i = 0; i < comp_field->rfc2507_params->nsapi_len; i++) { + LOGP(DSNDCP, logl, + " nsapi[%d]=%d;\n", i, + comp_field->rfc2507_params->nsapi[i]); + } + LOGP(DSNDCP, logl, + " f_max_period=%d;\n", + comp_field->rfc2507_params->f_max_period); + LOGP(DSNDCP, logl, + " f_max_time=%d;\n", + comp_field->rfc2507_params->f_max_time); + LOGP(DSNDCP, logl, + " max_header=%d;\n", + comp_field->rfc2507_params->max_header); + LOGP(DSNDCP, logl, + " tcp_space=%d;\n", + comp_field->rfc2507_params->tcp_space); + LOGP(DSNDCP, logl, + " non_tcp_space=%d;\n", + comp_field->rfc2507_params->non_tcp_space); + LOGP(DSNDCP, logl, " }\n"); + break; + case ROHC: + if (comp_field->rohc_params == NULL) { + LOGP(DSNDCP, logl, + " gprs_sndcp_pcomp_rohc_params=NULL\n"); + break; + } + LOGP(DSNDCP, logl, " gprs_sndcp_pcomp_rohc_params {\n"); + LOGP(DSNDCP, logl, + " nsapi_len=%d;\n", + comp_field->rohc_params->nsapi_len); + if (comp_field->rohc_params->nsapi_len == 0) + LOGP(DSNDCP, logl, " nsapi[] = NULL;\n"); + for (i = 0; i < comp_field->rohc_params->nsapi_len; i++) { + LOGP(DSNDCP, logl, + " nsapi[%d]=%d;\n", i, + comp_field->rohc_params->nsapi[i]); + } + LOGP(DSNDCP, logl, + " max_cid=%d;\n", comp_field->rohc_params->max_cid); + LOGP(DSNDCP, logl, + " max_header=%d;\n", + comp_field->rohc_params->max_header); + LOGP(DSNDCP, logl, + " profile_len=%d;\n", + comp_field->rohc_params->profile_len); + if (comp_field->rohc_params->profile_len == 0) + LOGP(DSNDCP, logl, " profile[] = NULL;\n"); + for (i = 0; i < comp_field->rohc_params->profile_len; i++) + LOGP(DSNDCP, logl, + " profile[%d]=%04x;\n", + i, comp_field->rohc_params->profile[i]); + LOGP(DSNDCP, logl, " }\n"); + break; + } + +} + +/* Helper for gprs_sndcp_dump_comp_fields(), + * data protocol compression parameters */ +static void dump_dcomp_params(const struct gprs_sndcp_comp_field + *comp_field, unsigned int logl) +{ + int i; + + switch (comp_field->algo.dcomp) { + case V42BIS: + if (comp_field->v42bis_params == NULL) { + LOGP(DSNDCP, logl, + " gprs_sndcp_dcomp_v42bis_params=NULL\n"); + break; + } + LOGP(DSNDCP, logl, " gprs_sndcp_dcomp_v42bis_params {\n"); + LOGP(DSNDCP, logl, + " nsapi_len=%d;\n", + comp_field->v42bis_params->nsapi_len); + if (comp_field->v42bis_params->nsapi_len == 0) + LOGP(DSNDCP, logl, " nsapi[] = NULL;\n"); + for (i = 0; i < comp_field->v42bis_params->nsapi_len; i++) + LOGP(DSNDCP, logl, + " nsapi[%d]=%d;\n", i, + comp_field->v42bis_params->nsapi[i]); + LOGP(DSNDCP, logl, " p0=%d;\n", + comp_field->v42bis_params->p0); + LOGP(DSNDCP, logl, " p1=%d;\n", + comp_field->v42bis_params->p1); + LOGP(DSNDCP, logl, " p2=%d;\n", + comp_field->v42bis_params->p2); + LOGP(DSNDCP, logl, " }\n"); + break; + case V44: + if (comp_field->v44_params == NULL) { + LOGP(DSNDCP, logl, + " gprs_sndcp_dcomp_v44_params=NULL\n"); + break; + } + LOGP(DSNDCP, logl, " gprs_sndcp_dcomp_v44_params {\n"); + LOGP(DSNDCP, logl, + " nsapi_len=%d;\n", + comp_field->v44_params->nsapi_len); + if (comp_field->v44_params->nsapi_len == 0) + LOGP(DSNDCP, logl, " nsapi[] = NULL;\n"); + for (i = 0; i < comp_field->v44_params->nsapi_len; i++) { + LOGP(DSNDCP, logl, + " nsapi[%d]=%d;\n", i, + comp_field->v44_params->nsapi[i]); + } + LOGP(DSNDCP, logl, " c0=%d;\n", + comp_field->v44_params->c0); + LOGP(DSNDCP, logl, " p0=%d;\n", + comp_field->v44_params->p0); + LOGP(DSNDCP, logl, " p1t=%d;\n", + comp_field->v44_params->p1t); + LOGP(DSNDCP, logl, " p1r=%d;\n", + comp_field->v44_params->p1r); + LOGP(DSNDCP, logl, " p3t=%d;\n", + comp_field->v44_params->p3t); + LOGP(DSNDCP, logl, " p3r=%d;\n", + comp_field->v44_params->p3r); + LOGP(DSNDCP, logl, " }\n"); + break; + } +} + +/* Dump a list with SNDCP-XID fields (Debug) */ +void gprs_sndcp_dump_comp_fields(const struct llist_head *comp_fields, + unsigned int logl) +{ + struct gprs_sndcp_comp_field *comp_field; + int i; + enum gprs_sndcp_xid_param_types compclass; + + OSMO_ASSERT(comp_fields); + + llist_for_each_entry(comp_field, comp_fields, list) { + compclass = gprs_sndcp_get_compression_class(comp_field); + LOGP(DSNDCP, logl, "SNDCP-XID:\n"); + LOGP(DSNDCP, logl, "struct gprs_sndcp_comp_field {\n"); + LOGP(DSNDCP, logl, " entity=%d;\n", comp_field->entity); + switch (compclass) { + case SNDCP_XID_PROTOCOL_COMPRESSION: + LOGP(DSNDCP, logl, " algo=%d;\n", comp_field->algo.pcomp); + break; + case SNDCP_XID_DATA_COMPRESSION: + LOGP(DSNDCP, logl, " algo=%d;\n", comp_field->algo.dcomp); + break; + default: + LOGP(DSNDCP, logl, " algo invalid!\n"); + break; + } + LOGP(DSNDCP, logl, " comp_len=%d;\n", comp_field->comp_len); + if (comp_field->comp_len == 0) + LOGP(DSNDCP, logl, " comp[] = NULL;\n"); + for (i = 0; i < comp_field->comp_len; i++) { + LOGP(DSNDCP, logl, " comp[%d]=%d;\n", i, + comp_field->comp[i]); + } + + switch (compclass) { + case SNDCP_XID_PROTOCOL_COMPRESSION: + dump_pcomp_params(comp_field, logl); + break; + case SNDCP_XID_DATA_COMPRESSION: + dump_dcomp_params(comp_field, logl); + break; + default: + LOGP(DSNDCP, logl, " compression algorithm invalid!\n"); + break; + } + + LOGP(DSNDCP, logl, "}\n"); + } + +} diff --git a/src/sgsn/gprs_subscriber.c b/src/sgsn/gprs_subscriber.c new file mode 100644 index 000000000..484c7ef4e --- /dev/null +++ b/src/sgsn/gprs_subscriber.c @@ -0,0 +1,949 @@ +/* MS subscriber data handling */ + +/* (C) 2014 by sysmocom s.f.m.c. GmbH + * (C) 2015 by Holger Hans Peter Freyther + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#define SGSN_SUBSCR_MAX_RETRIES 3 +#define SGSN_SUBSCR_RETRY_INTERVAL 10 + +#define LOGGSUPP(level, gsup, fmt, args...) \ + LOGP(DGPRS, level, "GSUP(%s) " fmt, \ + (gsup)->imsi, \ + ## args) + +extern void *tall_sgsn_ctx; + +LLIST_HEAD(_gprs_subscribers); +struct llist_head * const gprs_subscribers = &_gprs_subscribers; + +static int gsup_read_cb(struct osmo_gsup_client *gsupc, struct msgb *msg); + +/* TODO: Some functions are specific to the SGSN, but this file is more general + * (it has gprs_* name). Either move these functions elsewhere, split them and + * move a part, or replace the gprs_ prefix by sgsn_. The applies to + * gprs_subscr_init, gsup_read_cb, and gprs_subscr_tx_gsup_message. + */ + +int gprs_subscr_init(struct sgsn_instance *sgi) +{ + const char *addr_str; + struct ipaccess_unit *ipa_dev; + + if (!sgi->cfg.gsup_server_addr.sin_addr.s_addr) + return 0; + + addr_str = inet_ntoa(sgi->cfg.gsup_server_addr.sin_addr); + + ipa_dev = talloc_zero(sgi, struct ipaccess_unit); + ipa_dev->unit_name = "SGSN"; + ipa_dev->serno = sgi->cfg.sgsn_ipa_name; /* NULL unless configured via VTY */ + + sgi->gsup_client = osmo_gsup_client_create2( + sgi, + ipa_dev, + addr_str, sgi->cfg.gsup_server_port, + &gsup_read_cb, + &sgi->cfg.oap); + + if (!sgi->gsup_client) + return -1; + + return 1; +} + +static int gsup_read_cb(struct osmo_gsup_client *gsupc, struct msgb *msg) +{ + int rc; + + rc = gprs_subscr_rx_gsup_message(msg); + msgb_free(msg); + if (rc < 0) + return -1; + + return rc; +} + +int gprs_subscr_purge(struct gprs_subscr *subscr); + +static struct sgsn_subscriber_data *sgsn_subscriber_data_alloc(void *ctx) +{ + struct sgsn_subscriber_data *sdata; + int idx; + + sdata = talloc_zero(ctx, struct sgsn_subscriber_data); + + sdata->error_cause = SGSN_ERROR_CAUSE_NONE; + + for (idx = 0; idx < ARRAY_SIZE(sdata->auth_triplets); idx++) + sdata->auth_triplets[idx].key_seq = GSM_KEY_SEQ_INVAL; + + INIT_LLIST_HEAD(&sdata->pdp_list); + + return sdata; +} + +struct sgsn_subscriber_pdp_data* sgsn_subscriber_pdp_data_alloc( + struct sgsn_subscriber_data *sdata) +{ + struct sgsn_subscriber_pdp_data* pdata; + + pdata = talloc_zero(sdata, struct sgsn_subscriber_pdp_data); + + llist_add_tail(&pdata->list, &sdata->pdp_list); + + return pdata; +} + +struct gprs_subscr *gprs_subscr_get_by_imsi(const char *imsi) +{ + struct gprs_subscr *gsub; + + if (!imsi || !*imsi) + return NULL; + + llist_for_each_entry(gsub, gprs_subscribers, entry) { + if (!strcmp(gsub->imsi, imsi)) + return gprs_subscr_get(gsub); + } + return NULL; +} + +static struct gprs_subscr *gprs_subscr_alloc(void) +{ + struct gprs_subscr *gsub; + gsub = talloc_zero(tall_sgsn_ctx, struct gprs_subscr); + if (!gsub) + return NULL; + llist_add_tail(&gsub->entry, gprs_subscribers); + gsub->use_count = 1; + gsub->tmsi = GSM_RESERVED_TMSI; + return gsub; +} + +struct gprs_subscr *gprs_subscr_get_or_create(const char *imsi) +{ + struct gprs_subscr *gsub; + + gsub = gprs_subscr_get_by_imsi(imsi); + if (!gsub) { + gsub = gprs_subscr_alloc(); + if (!gsub) + return NULL; + osmo_strlcpy(gsub->imsi, imsi, sizeof(gsub->imsi)); + } + + if (!gsub->sgsn_data) + gsub->sgsn_data = sgsn_subscriber_data_alloc(gsub); + return gsub; +} + +void gprs_subscr_cleanup(struct gprs_subscr *subscr) +{ + if (subscr->sgsn_data->mm) { + gprs_subscr_put(subscr->sgsn_data->mm->subscr); + subscr->sgsn_data->mm->subscr = NULL; + subscr->sgsn_data->mm = NULL; + } + + if (subscr->flags & GPRS_SUBSCRIBER_ENABLE_PURGE) { + gprs_subscr_purge(subscr); + subscr->flags &= ~GPRS_SUBSCRIBER_ENABLE_PURGE; + } +} + +void gprs_subscr_cancel(struct gprs_subscr *subscr) +{ + subscr->authorized = 0; + subscr->flags |= GPRS_SUBSCRIBER_CANCELLED; + subscr->flags &= ~GPRS_SUBSCRIBER_ENABLE_PURGE; + + gprs_subscr_update(subscr); + gprs_subscr_cleanup(subscr); +} + +static int gprs_subscr_tx_gsup_message(struct gprs_subscr *subscr, + struct osmo_gsup_message *gsup_msg) +{ + struct msgb *msg = osmo_gsup_client_msgb_alloc(); + + if (strlen(gsup_msg->imsi) == 0 && subscr) + osmo_strlcpy(gsup_msg->imsi, subscr->imsi, + sizeof(gsup_msg->imsi)); + gsup_msg->cn_domain = OSMO_GSUP_CN_DOMAIN_PS; + osmo_gsup_encode(msg, gsup_msg); + + LOGGSUBSCRP(LOGL_INFO, subscr, + "Sending GSUP, will send: %s\n", msgb_hexdump(msg)); + + if (!sgsn->gsup_client) { + msgb_free(msg); + return -ENOTSUP; + } + + return osmo_gsup_client_send(sgsn->gsup_client, msg); +} + +static int gprs_subscr_tx_gsup_error_reply(struct gprs_subscr *subscr, + struct osmo_gsup_message *gsup_orig, + enum gsm48_gmm_cause cause) +{ + struct osmo_gsup_message gsup_reply = {0}; + + osmo_strlcpy(gsup_reply.imsi, gsup_orig->imsi, + sizeof(gsup_reply.imsi)); + gsup_reply.cause = cause; + gsup_reply.message_type = + OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type); + + return gprs_subscr_tx_gsup_message(subscr, &gsup_reply); +} + +static int gprs_subscr_handle_gsup_auth_res(struct gprs_subscr *subscr, + struct osmo_gsup_message *gsup_msg) +{ + unsigned idx; + struct sgsn_subscriber_data *sdata = subscr->sgsn_data; + + LOGGSUBSCRP(LOGL_INFO, subscr, + "Got SendAuthenticationInfoResult, num_auth_vectors = %zu\n", + gsup_msg->num_auth_vectors); + + if (gsup_msg->num_auth_vectors > 0) { + memset(sdata->auth_triplets, 0, sizeof(sdata->auth_triplets)); + + for (idx = 0; idx < ARRAY_SIZE(sdata->auth_triplets); idx++) + sdata->auth_triplets[idx].key_seq = GSM_KEY_SEQ_INVAL; + } + + for (idx = 0; idx < gsup_msg->num_auth_vectors; idx++) { + size_t key_seq = idx; + LOGGSUBSCRP(LOGL_DEBUG, subscr, + "Adding auth tuple, cksn = %zu\n", key_seq); + if (key_seq >= ARRAY_SIZE(sdata->auth_triplets)) { + LOGGSUBSCRP(LOGL_NOTICE, subscr, + "Skipping auth triplet with invalid cksn %zu\n", + key_seq); + continue; + } + sdata->auth_triplets[key_seq].vec = gsup_msg->auth_vectors[idx]; + sdata->auth_triplets[key_seq].key_seq = key_seq; + } + + sdata->auth_triplets_updated = 1; + sdata->error_cause = SGSN_ERROR_CAUSE_NONE; + + gprs_subscr_update_auth_info(subscr); + + return 0; +} + +static int gprs_subscr_pdp_data_clear(struct gprs_subscr *subscr) +{ + struct sgsn_subscriber_pdp_data *pdp, *pdp2; + int count = 0; + + llist_for_each_entry_safe(pdp, pdp2, &subscr->sgsn_data->pdp_list, list) { + llist_del(&pdp->list); + talloc_free(pdp); + count += 1; + } + + return count; +} + +static struct sgsn_subscriber_pdp_data *gprs_subscr_pdp_data_get_by_id( + struct gprs_subscr *subscr, unsigned context_id) +{ + struct sgsn_subscriber_pdp_data *pdp; + + llist_for_each_entry(pdp, &subscr->sgsn_data->pdp_list, list) { + if (pdp->context_id == context_id) + return pdp; + } + + return NULL; +} + + +static void gprs_subscr_gsup_insert_data(struct gprs_subscr *subscr, + struct osmo_gsup_message *gsup_msg) +{ + struct sgsn_subscriber_data *sdata = subscr->sgsn_data; + unsigned idx; + int rc; + + if (gsup_msg->msisdn_enc) { + if (gsup_msg->msisdn_enc_len > sizeof(sdata->msisdn)) { + LOGP(DGPRS, LOGL_ERROR, "MSISDN too long (%zu)\n", + gsup_msg->msisdn_enc_len); + sdata->msisdn_len = 0; + } else { + memcpy(sdata->msisdn, gsup_msg->msisdn_enc, + gsup_msg->msisdn_enc_len); + sdata->msisdn_len = gsup_msg->msisdn_enc_len; + } + } + + if (gsup_msg->hlr_enc) { + if (gsup_msg->hlr_enc_len > sizeof(sdata->hlr)) { + LOGP(DGPRS, LOGL_ERROR, "HLR-Number too long (%zu)\n", + gsup_msg->hlr_enc_len); + sdata->hlr_len = 0; + } else { + memcpy(sdata->hlr, gsup_msg->hlr_enc, + gsup_msg->hlr_enc_len); + sdata->hlr_len = gsup_msg->hlr_enc_len; + } + } + + if (gsup_msg->pdp_charg_enc && gsup_msg->pdp_charg_enc_len >= sizeof(sdata->pdp_charg)) { + memcpy(&sdata->pdp_charg, gsup_msg->pdp_charg_enc, sizeof(sdata->pdp_charg)); + sdata->has_pdp_charg = 1; + } else { + sdata->has_pdp_charg = 0; + } + + if (gsup_msg->pdp_info_compl) { + rc = gprs_subscr_pdp_data_clear(subscr); + if (rc > 0) + LOGP(DGPRS, LOGL_INFO, "Cleared existing PDP info\n"); + } + + for (idx = 0; idx < gsup_msg->num_pdp_infos; idx++) { + struct osmo_gsup_pdp_info *pdp_info = &gsup_msg->pdp_infos[idx]; + size_t ctx_id = pdp_info->context_id; + struct sgsn_subscriber_pdp_data *pdp_data; + + if (pdp_info->apn_enc_len >= sizeof(pdp_data->apn_str)-1) { + LOGGSUBSCRP(LOGL_ERROR, subscr, + "APN too long, context id = %zu, APN = %s\n", + ctx_id, osmo_hexdump(pdp_info->apn_enc, + pdp_info->apn_enc_len)); + continue; + } + + if (pdp_info->qos_enc_len > sizeof(pdp_data->qos_subscribed)) { + LOGGSUBSCRP(LOGL_ERROR, subscr, + "QoS info too long (%zu)\n", + pdp_info->qos_enc_len); + continue; + } + + LOGGSUBSCRP(LOGL_INFO, subscr, + "Will set PDP info, context id = %zu, APN = %s\n", + ctx_id, osmo_hexdump(pdp_info->apn_enc, pdp_info->apn_enc_len)); + + /* Set PDP info [ctx_id] */ + pdp_data = gprs_subscr_pdp_data_get_by_id(subscr, ctx_id); + if (!pdp_data) { + pdp_data = sgsn_subscriber_pdp_data_alloc(subscr->sgsn_data); + pdp_data->context_id = ctx_id; + } + + OSMO_ASSERT(pdp_data != NULL); + pdp_data->pdp_type = pdp_info->pdp_type; + osmo_apn_to_str(pdp_data->apn_str, + pdp_info->apn_enc, pdp_info->apn_enc_len); + + if (pdp_info->qos_enc) { + memcpy(&pdp_data->qos_subscribed[0], pdp_info->qos_enc, + pdp_info->qos_enc_len); + } + pdp_data->qos_subscribed_len = pdp_info->qos_enc_len; + + if (pdp_info->pdp_charg_enc && pdp_info->pdp_charg_enc_len >= sizeof(pdp_data->pdp_charg)) { + memcpy(&pdp_data->pdp_charg, pdp_info->pdp_charg_enc, sizeof(pdp_data->pdp_charg)); + pdp_data->has_pdp_charg = 1; + } else { + pdp_data->has_pdp_charg = 0; + } + } +} + +static int gprs_subscr_handle_gsup_upd_loc_res(struct gprs_subscr *subscr, + struct osmo_gsup_message *gsup_msg) +{ + /* contrary to MAP, we allow piggy-backing subscriber data onto + * the UPDATE LOCATION RESULT, and don't mandate the use of a + * separate nested INSERT SUBSCRIBER DATA transaction */ + gprs_subscr_gsup_insert_data(subscr, gsup_msg); + + subscr->authorized = 1; + subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE; + + subscr->flags |= GPRS_SUBSCRIBER_ENABLE_PURGE; + + gprs_subscr_update(subscr); + return 0; +} + +static int gprs_subscr_handle_gsup_dsd_req(struct gprs_subscr *subscr, + struct osmo_gsup_message *gsup_msg) +{ + struct osmo_gsup_message gsup_reply = {0}; + + if (gsup_msg->cn_domain != OSMO_GSUP_CN_DOMAIN_PS) { + LOGGSUBSCRP(LOGL_ERROR, subscr, + "Rx GSUP message %s not supported for CS\n", + osmo_gsup_message_type_name(gsup_msg->message_type)); + gsup_reply.cause = GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL; + gsup_reply.message_type = OSMO_GSUP_MSGT_DELETE_DATA_ERROR; + } else { + gsm0408_gprs_access_cancelled(subscr->sgsn_data->mm, + GMM_CAUSE_GPRS_NOTALLOWED); + gsup_reply.message_type = OSMO_GSUP_MSGT_DELETE_DATA_RESULT; + } + + return gprs_subscr_tx_gsup_message(subscr, &gsup_reply); +} + +static int gprs_subscr_handle_gsup_isd_req(struct gprs_subscr *subscr, + struct osmo_gsup_message *gsup_msg) +{ + struct osmo_gsup_message gsup_reply = {0}; + + gprs_subscr_gsup_insert_data(subscr, gsup_msg); + + subscr->authorized = 1; + subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE; + subscr->flags |= GPRS_SUBSCRIBER_ENABLE_PURGE; + gprs_subscr_update(subscr); + + gsup_reply.message_type = OSMO_GSUP_MSGT_INSERT_DATA_RESULT; + return gprs_subscr_tx_gsup_message(subscr, &gsup_reply); +} + +static int check_cause(int cause) +{ + switch (cause) { + case GMM_CAUSE_IMSI_UNKNOWN ... GMM_CAUSE_ILLEGAL_ME: + case GMM_CAUSE_GPRS_NOTALLOWED ... GMM_CAUSE_NO_GPRS_PLMN: + return EACCES; + + case GMM_CAUSE_MSC_TEMP_NOTREACH ... GMM_CAUSE_CONGESTION: + return EHOSTUNREACH; + + case GMM_CAUSE_SEM_INCORR_MSG ... GMM_CAUSE_PROTO_ERR_UNSPEC: + default: + return EINVAL; + } +} + +static int gprs_subscr_handle_gsup_auth_err(struct gprs_subscr *subscr, + struct osmo_gsup_message *gsup_msg) +{ + unsigned idx; + struct sgsn_subscriber_data *sdata = subscr->sgsn_data; + int cause_err; + + cause_err = check_cause(gsup_msg->cause); + + LOGGSUBSCRP(LOGL_DEBUG, subscr, + "Send authentication info has failed with cause %d, " + "handled as: %s\n", + gsup_msg->cause, strerror(cause_err)); + + switch (cause_err) { + case EACCES: + LOGGSUBSCRP(LOGL_NOTICE, subscr, + "GPRS send auth info req failed, access denied, " + "GMM cause = '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + /* Clear auth tuples */ + memset(sdata->auth_triplets, 0, sizeof(sdata->auth_triplets)); + for (idx = 0; idx < ARRAY_SIZE(sdata->auth_triplets); idx++) + sdata->auth_triplets[idx].key_seq = GSM_KEY_SEQ_INVAL; + + subscr->authorized = 0; + sdata->error_cause = gsup_msg->cause; + gprs_subscr_update_auth_info(subscr); + break; + + case EHOSTUNREACH: + LOGGSUBSCRP(LOGL_NOTICE, subscr, + "GPRS send auth info req failed, GMM cause = '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + + sdata->error_cause = gsup_msg->cause; + gprs_subscr_update_auth_info(subscr); + break; + + default: + case EINVAL: + LOGGSUBSCRP(LOGL_ERROR, subscr, + "GSUP protocol remote error, GMM cause = '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + sdata->error_cause = gsup_msg->cause; + break; + } + + return -gsup_msg->cause; +} + +static int gprs_subscr_handle_gsup_upd_loc_err(struct gprs_subscr *subscr, + struct osmo_gsup_message *gsup_msg) +{ + int cause_err; + + cause_err = check_cause(gsup_msg->cause); + + LOGGSUBSCRP(LOGL_DEBUG, subscr, + "Update location has failed with cause %d, handled as: %s\n", + gsup_msg->cause, strerror(cause_err)); + + switch (cause_err) { + case EACCES: + LOGGSUBSCRP(LOGL_NOTICE, subscr, + "GPRS update location failed, access denied, " + "GMM cause = '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + + subscr->authorized = 0; + subscr->sgsn_data->error_cause = gsup_msg->cause; + gprs_subscr_update_auth_info(subscr); + break; + + case EHOSTUNREACH: + LOGGSUBSCRP(LOGL_NOTICE, subscr, + "GPRS update location failed, GMM cause = '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + + subscr->sgsn_data->error_cause = gsup_msg->cause; + gprs_subscr_update_auth_info(subscr); + break; + + default: + case EINVAL: + LOGGSUBSCRP(LOGL_ERROR, subscr, + "GSUP protocol remote error, GMM cause = '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + break; + } + + return -gsup_msg->cause; +} + +static int gprs_subscr_handle_gsup_purge_no_subscr( + struct osmo_gsup_message *gsup_msg) +{ + if (OSMO_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) { + LOGGSUPP(LOGL_NOTICE, gsup_msg, + "Purge MS has failed with cause '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + return -gsup_msg->cause; + } + + LOGGSUPP(LOGL_INFO, gsup_msg, "Completing purge MS\n"); + return 0; +} + +static int gprs_subscr_handle_gsup_purge_res(struct gprs_subscr *subscr, + struct osmo_gsup_message *gsup_msg) +{ + LOGGSUBSCRP(LOGL_INFO, subscr, "Completing purge MS\n"); + + /* Force silent cancellation */ + subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE; + gprs_subscr_cancel(subscr); + + return 0; +} + +static int gprs_subscr_handle_gsup_purge_err(struct gprs_subscr *subscr, + struct osmo_gsup_message *gsup_msg) +{ + LOGGSUBSCRP(LOGL_NOTICE, subscr, + "Purge MS has failed with cause '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + + /* In GSM 09.02, 19.1.4.4, the text and the SDL diagram imply that + * the subscriber data is not removed if the request has failed. On the + * other hand, keeping the subscriber data in either error case + * (subscriber unknown, syntactical message error, connection error) + * doesn't seem to give any advantage, since the data will be restored + * on the next Attach Request anyway. + * This approach ensures, that the subscriber record will not stick if + * an error happens. + */ + + /* TODO: Check whether this behaviour is acceptable and either just + * remove this TODO-notice or change the implementation to not delete + * the subscriber data (eventually resetting the ENABLE_PURGE flag and + * restarting the expiry timer based on the cause). + * + * Subscriber Unknown: cancel subscr + * Temporary network problems: do nothing (handled by timer based retry) + * Message problems (syntax, nyi, ...): cancel subscr (retry won't help) + */ + + gprs_subscr_handle_gsup_purge_res(subscr, gsup_msg); + + return -gsup_msg->cause; +} + +static int gprs_subscr_handle_loc_cancel_req(struct gprs_subscr *subscr, + struct osmo_gsup_message *gsup_msg) +{ + struct osmo_gsup_message gsup_reply = {0}; + int is_update_procedure = !gsup_msg->cancel_type || + gsup_msg->cancel_type == OSMO_GSUP_CANCEL_TYPE_UPDATE; + + LOGGSUBSCRP(LOGL_INFO, subscr, "Cancelling MS subscriber (%s)\n", + is_update_procedure ? + "update procedure" : "subscription withdraw"); + + gsup_reply.message_type = OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT; + gprs_subscr_tx_gsup_message(subscr, &gsup_reply); + + if (is_update_procedure) + subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE; + else + /* Since a withdraw cause is not specified, just abort the + * current attachment. The following re-attachment should then + * be rejected with a proper cause value. + */ + subscr->sgsn_data->error_cause = GMM_CAUSE_IMPL_DETACHED; + + gprs_subscr_cancel(subscr); + + return 0; +} + +static int gprs_subscr_handle_unknown_imsi(struct osmo_gsup_message *gsup_msg) +{ + if (OSMO_GSUP_IS_MSGT_REQUEST(gsup_msg->message_type)) { + gprs_subscr_tx_gsup_error_reply(NULL, gsup_msg, + GMM_CAUSE_IMSI_UNKNOWN); + LOGP(DGPRS, LOGL_NOTICE, + "Unknown IMSI %s, discarding GSUP request " + "of type 0x%02x\n", + gsup_msg->imsi, gsup_msg->message_type); + } else if (OSMO_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) { + LOGP(DGPRS, LOGL_NOTICE, + "Unknown IMSI %s, discarding GSUP error " + "of type 0x%02x, cause '%s' (%d)\n", + gsup_msg->imsi, gsup_msg->message_type, + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + } else { + LOGP(DGPRS, LOGL_NOTICE, + "Unknown IMSI %s, discarding GSUP response " + "of type 0x%02x\n", + gsup_msg->imsi, gsup_msg->message_type); + } + + return -GMM_CAUSE_IMSI_UNKNOWN; +} + +int gprs_subscr_rx_gsup_message(struct msgb *msg) +{ + uint8_t *data = msgb_l2(msg); + size_t data_len = msgb_l2len(msg); + int rc = 0; + + struct osmo_gsup_message gsup_msg = {0}; + struct gprs_subscr *subscr; + + rc = osmo_gsup_decode(data, data_len, &gsup_msg); + if (rc < 0) { + LOGP(DGPRS, LOGL_ERROR, + "decoding GSUP message fails with error '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, -rc), -rc); + return rc; + } + + if (!gsup_msg.imsi[0]) { + LOGP(DGPRS, LOGL_ERROR, "Missing IMSI in GSUP message\n"); + + if (OSMO_GSUP_IS_MSGT_REQUEST(gsup_msg.message_type)) + gprs_subscr_tx_gsup_error_reply(NULL, &gsup_msg, + GMM_CAUSE_INV_MAND_INFO); + return -GMM_CAUSE_INV_MAND_INFO; + } + + if (!gsup_msg.cause && OSMO_GSUP_IS_MSGT_ERROR(gsup_msg.message_type)) + gsup_msg.cause = GMM_CAUSE_NET_FAIL; + + subscr = gprs_subscr_get_by_imsi(gsup_msg.imsi); + + if (!subscr) { + switch (gsup_msg.message_type) { + case OSMO_GSUP_MSGT_PURGE_MS_RESULT: + case OSMO_GSUP_MSGT_PURGE_MS_ERROR: + return gprs_subscr_handle_gsup_purge_no_subscr(&gsup_msg); + default: + return gprs_subscr_handle_unknown_imsi(&gsup_msg); + } + } + + LOGGSUBSCRP(LOGL_INFO, subscr, + "Received GSUP message %s\n", + osmo_gsup_message_type_name(gsup_msg.message_type)); + + switch (gsup_msg.message_type) { + case OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST: + rc = gprs_subscr_handle_loc_cancel_req(subscr, &gsup_msg); + break; + + case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT: + rc = gprs_subscr_handle_gsup_auth_res(subscr, &gsup_msg); + break; + + case OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR: + rc = gprs_subscr_handle_gsup_auth_err(subscr, &gsup_msg); + break; + + case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT: + rc = gprs_subscr_handle_gsup_upd_loc_res(subscr, &gsup_msg); + break; + + case OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR: + rc = gprs_subscr_handle_gsup_upd_loc_err(subscr, &gsup_msg); + break; + + case OSMO_GSUP_MSGT_PURGE_MS_ERROR: + rc = gprs_subscr_handle_gsup_purge_err(subscr, &gsup_msg); + break; + + case OSMO_GSUP_MSGT_PURGE_MS_RESULT: + rc = gprs_subscr_handle_gsup_purge_res(subscr, &gsup_msg); + break; + + case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST: + rc = gprs_subscr_handle_gsup_isd_req(subscr, &gsup_msg); + break; + + case OSMO_GSUP_MSGT_DELETE_DATA_REQUEST: + rc = gprs_subscr_handle_gsup_dsd_req(subscr, &gsup_msg); + break; + + default: + LOGGSUBSCRP(LOGL_ERROR, subscr, + "Rx GSUP message %s not valid at SGSN\n", + osmo_gsup_message_type_name(gsup_msg.message_type)); + if (OSMO_GSUP_IS_MSGT_REQUEST(gsup_msg.message_type)) + gprs_subscr_tx_gsup_error_reply( + subscr, &gsup_msg, GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL); + rc = -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL; + break; + }; + + gprs_subscr_put(subscr); + + return rc; +} + +int gprs_subscr_purge(struct gprs_subscr *subscr) +{ + struct sgsn_subscriber_data *sdata = subscr->sgsn_data; + struct osmo_gsup_message gsup_msg = {0}; + + LOGGSUBSCRP(LOGL_INFO, subscr, "purging MS subscriber\n"); + + gsup_msg.message_type = OSMO_GSUP_MSGT_PURGE_MS_REQUEST; + + /* Provide the HLR number in case it is known */ + gsup_msg.hlr_enc_len = sdata->hlr_len; + gsup_msg.hlr_enc = sdata->hlr; + + return gprs_subscr_tx_gsup_message(subscr, &gsup_msg); +} + +static int gprs_subscr_query_auth_info(struct gprs_subscr *subscr, + const uint8_t *auts, + const uint8_t *auts_rand) +{ + struct osmo_gsup_message gsup_msg = {0}; + + /* Make sure we have a complete resync or clearly no resync. */ + OSMO_ASSERT((auts != NULL) == (auts_rand != NULL)); + + LOGGSUBSCRP(LOGL_INFO, subscr, "requesting auth info%s\n", + auts ? " with AUTS (UMTS Resynch)" : ""); + + gsup_msg.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST; + gsup_msg.auts = auts; + gsup_msg.rand = auts_rand; + return gprs_subscr_tx_gsup_message(subscr, &gsup_msg); +} + +int gprs_subscr_location_update(struct gprs_subscr *subscr) +{ + struct osmo_gsup_message gsup_msg = {0}; + + LOGGSUBSCRP(LOGL_INFO, subscr, + "subscriber data is not available\n"); + + gsup_msg.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST; + return gprs_subscr_tx_gsup_message(subscr, &gsup_msg); +} + +void gprs_subscr_update(struct gprs_subscr *subscr) +{ + LOGGSUBSCRP(LOGL_DEBUG, subscr, "Updating subscriber data\n"); + + subscr->flags &= ~GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING; + subscr->flags &= ~GPRS_SUBSCRIBER_FIRST_CONTACT; + + if (subscr->sgsn_data->mm) + sgsn_update_subscriber_data(subscr->sgsn_data->mm); +} + +void gprs_subscr_update_auth_info(struct gprs_subscr *subscr) +{ + LOGGSUBSCRP(LOGL_DEBUG, subscr, + "Updating subscriber authentication info\n"); + + subscr->flags &= ~GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING; + subscr->flags &= ~GPRS_SUBSCRIBER_FIRST_CONTACT; + + if (subscr->sgsn_data->mm) + sgsn_update_subscriber_data(subscr->sgsn_data->mm); +} + +struct gprs_subscr *gprs_subscr_get_or_create_by_mmctx(struct sgsn_mm_ctx *mmctx) +{ + struct gprs_subscr *subscr = NULL; + + if (mmctx->subscr) + return gprs_subscr_get(mmctx->subscr); + + if (mmctx->imsi[0]) + subscr = gprs_subscr_get_by_imsi(mmctx->imsi); + + if (!subscr) { + subscr = gprs_subscr_get_or_create(mmctx->imsi); + subscr->flags |= GPRS_SUBSCRIBER_FIRST_CONTACT; + subscr->flags &= ~GPRS_SUBSCRIBER_ENABLE_PURGE; + } + + osmo_strlcpy(subscr->imei, mmctx->imei, sizeof(subscr->imei)); + + if (subscr->lac != mmctx->ra.lac) + subscr->lac = mmctx->ra.lac; + + subscr->sgsn_data->mm = mmctx; + mmctx->subscr = gprs_subscr_get(subscr); + + return subscr; +} + +int gprs_subscr_request_update_location(struct sgsn_mm_ctx *mmctx) +{ + struct gprs_subscr *subscr = NULL; + int rc; + + LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting subscriber data update\n"); + + subscr = gprs_subscr_get_or_create_by_mmctx(mmctx); + + subscr->flags |= GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING; + + rc = gprs_subscr_location_update(subscr); + gprs_subscr_put(subscr); + return rc; +} + +/*! \brief Send Update Auth Info request via GSUP, with or without resync. + * \param[in] mmctx MM context to request authentication tuples for. + * \param[in] auts 14 octet AUTS token for UMTS resync, or NULL. + * \param[in] auts_rand 16 octet Random token for UMTS resync, or NULL. + * In case of normal Authentication Info request, both \a auts and \a auts_rand + * must be NULL. For resync, both must be non-NULL. + */ +int gprs_subscr_request_auth_info(struct sgsn_mm_ctx *mmctx, + const uint8_t *auts, + const uint8_t *auts_rand) +{ + struct gprs_subscr *subscr = NULL; + int rc; + + LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting subscriber authentication info\n"); + + subscr = gprs_subscr_get_or_create_by_mmctx(mmctx); + + subscr->flags |= GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING; + + rc = gprs_subscr_query_auth_info(subscr, auts, auts_rand); + gprs_subscr_put(subscr); + return rc; +} + +static void gprs_subscr_free(struct gprs_subscr *gsub) +{ + llist_del(&gsub->entry); + talloc_free(gsub); +} + +struct gprs_subscr *_gprs_subscr_get(struct gprs_subscr *gsub, + const char *file, int line) +{ + OSMO_ASSERT(gsub->use_count < INT_MAX); + gsub->use_count++; + LOGPSRC(DREF, LOGL_DEBUG, file, line, + "subscr %s usage increases to: %d\n", + gsub->imsi, gsub->use_count); + return gsub; +} + +struct gprs_subscr *_gprs_subscr_put(struct gprs_subscr *gsub, + const char *file, int line) +{ + gsub->use_count--; + LOGPSRC(DREF, gsub->use_count >= 0? LOGL_DEBUG : LOGL_ERROR, + file, line, + "subscr %s usage decreases to: %d%s\n", + gsub->imsi, gsub->use_count, + gsub->keep_in_ram? ", keep-in-ram flag is set" : ""); + if (gsub->use_count > 0) + return gsub; + if (gsub->keep_in_ram) + return gsub; + gprs_subscr_free(gsub); + return NULL; +} diff --git a/src/sgsn/sgsn_auth.c b/src/sgsn/sgsn_auth.c new file mode 100644 index 000000000..b8d803590 --- /dev/null +++ b/src/sgsn/sgsn_auth.c @@ -0,0 +1,312 @@ +/* MS authorization and subscriber data handling */ + +/* (C) 2009-2010 by Harald Welte + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +const struct value_string auth_state_names[] = { + { SGSN_AUTH_ACCEPTED, "accepted"}, + { SGSN_AUTH_REJECTED, "rejected"}, + { SGSN_AUTH_UNKNOWN, "unknown"}, + { SGSN_AUTH_AUTHENTICATE, "authenticate" }, + { SGSN_AUTH_UMTS_RESYNC, "UMTS-resync" }, + { 0, NULL } +}; + +const struct value_string *sgsn_auth_state_names = auth_state_names; + +void sgsn_auth_init(struct sgsn_instance *sgsn) +{ + INIT_LLIST_HEAD(&sgsn->cfg.imsi_acl); +} + +struct imsi_acl_entry *sgsn_acl_lookup(const char *imsi, const struct sgsn_config *cfg) +{ + struct imsi_acl_entry *acl; + llist_for_each_entry(acl, &cfg->imsi_acl, list) { + if (!strcmp(imsi, acl->imsi)) + return acl; + } + return NULL; +} + +int sgsn_acl_add(const char *imsi, struct sgsn_config *cfg) +{ + struct imsi_acl_entry *acl; + + if (sgsn_acl_lookup(imsi, cfg)) + return -EEXIST; + + acl = talloc_zero(NULL, struct imsi_acl_entry); + if (!acl) + return -ENOMEM; + osmo_strlcpy(acl->imsi, imsi, sizeof(acl->imsi)); + + llist_add(&acl->list, &cfg->imsi_acl); + + return 0; +} + +int sgsn_acl_del(const char *imsi, struct sgsn_config *cfg) +{ + struct imsi_acl_entry *acl; + + acl = sgsn_acl_lookup(imsi, cfg); + if (!acl) + return -ENODEV; + + llist_del(&acl->list); + talloc_free(acl); + + return 0; +} + +enum sgsn_auth_state sgsn_auth_state(struct sgsn_mm_ctx *mmctx) +{ + char mccmnc[16]; + int check_net = 0; + int check_acl = 0; + + OSMO_ASSERT(mmctx); + + switch (sgsn->cfg.auth_policy) { + case SGSN_AUTH_POLICY_OPEN: + return SGSN_AUTH_ACCEPTED; + + case SGSN_AUTH_POLICY_CLOSED: + check_net = 1; + check_acl = 1; + break; + + case SGSN_AUTH_POLICY_ACL_ONLY: + check_acl = 1; + break; + + case SGSN_AUTH_POLICY_REMOTE: + if (!mmctx->subscr) + return mmctx->auth_state; + + if (mmctx->subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING_MASK) + return mmctx->auth_state; + + if (sgsn->cfg.require_authentication && + (!sgsn_mm_ctx_is_authenticated(mmctx) || + mmctx->subscr->sgsn_data->auth_triplets_updated)) + return SGSN_AUTH_AUTHENTICATE; + + if (mmctx->subscr->authorized) + return SGSN_AUTH_ACCEPTED; + + return SGSN_AUTH_REJECTED; + } + + if (!strlen(mmctx->imsi)) { + LOGMMCTXP(LOGL_NOTICE, mmctx, + "Missing IMSI, authorization state not known\n"); + return SGSN_AUTH_UNKNOWN; + } + + if (check_net) { + /* We simply assume that the IMSI exists, as long as it is part + * of 'our' network */ + snprintf(mccmnc, sizeof(mccmnc), "%s%s", + osmo_mcc_name(mmctx->ra.mcc), + osmo_mnc_name(mmctx->ra.mnc, mmctx->ra.mnc_3_digits)); + if (strncmp(mccmnc, mmctx->imsi, mmctx->ra.mnc_3_digits ? 6 : 5) == 0) + return SGSN_AUTH_ACCEPTED; + } + + if (check_acl && sgsn_acl_lookup(mmctx->imsi, &sgsn->cfg)) + return SGSN_AUTH_ACCEPTED; + + return SGSN_AUTH_REJECTED; +} + +/* + * This function is directly called by e.g. the GMM layer. It returns either + * after calling sgsn_auth_update directly or after triggering an asynchronous + * procedure which will call sgsn_auth_update later on. + */ +int sgsn_auth_request(struct sgsn_mm_ctx *mmctx) +{ + struct gprs_subscr *subscr; + struct gsm_auth_tuple *at; + int need_update_location; + int rc; + + LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting authorization\n"); + + if (sgsn->cfg.auth_policy != SGSN_AUTH_POLICY_REMOTE) { + sgsn_auth_update(mmctx); + return 0; + } + + need_update_location = sgsn->cfg.require_update_location && + (mmctx->subscr == NULL || + mmctx->pending_req == GSM48_MT_GMM_ATTACH_REQ); + + /* This has the side effect of registering the subscr with the mmctx */ + subscr = gprs_subscr_get_or_create_by_mmctx(mmctx); + gprs_subscr_put(subscr); + + OSMO_ASSERT(mmctx->subscr != NULL); + + if (sgsn->cfg.require_authentication && !sgsn_mm_ctx_is_authenticated(mmctx)) { + /* Find next tuple */ + at = sgsn_auth_get_tuple(mmctx, mmctx->auth_triplet.key_seq); + + if (!at) { + /* No valid tuple found, request fresh ones */ + mmctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL; + LOGMMCTXP(LOGL_INFO, mmctx, + "Requesting authentication tuples\n"); + rc = gprs_subscr_request_auth_info(mmctx, NULL, NULL); + if (rc >= 0) + return 0; + + return rc; + } + + mmctx->auth_triplet = *at; + } else if (need_update_location) { + LOGMMCTXP(LOGL_INFO, mmctx, + "Missing information, requesting subscriber data\n"); + rc = gprs_subscr_request_update_location(mmctx); + if (rc >= 0) + return 0; + + return rc; + } + + sgsn_auth_update(mmctx); + return 0; +} + +void sgsn_auth_update(struct sgsn_mm_ctx *mmctx) +{ + enum sgsn_auth_state auth_state; + struct gprs_subscr *subscr = mmctx->subscr; + struct gsm_auth_tuple *at; + int gmm_cause; + + auth_state = sgsn_auth_state(mmctx); + + LOGMMCTXP(LOGL_DEBUG, mmctx, "Updating authorization (%s -> %s)\n", + get_value_string(sgsn_auth_state_names, mmctx->auth_state), + get_value_string(sgsn_auth_state_names, auth_state)); + + if (auth_state == SGSN_AUTH_UNKNOWN && subscr && + !(subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING_MASK)) { + /* Reject requests if gprs_subscr_request_update_location fails */ + LOGMMCTXP(LOGL_ERROR, mmctx, + "Missing information, authorization not possible\n"); + auth_state = SGSN_AUTH_REJECTED; + } + + if (auth_state == SGSN_AUTH_AUTHENTICATE && + mmctx->auth_triplet.key_seq == GSM_KEY_SEQ_INVAL) { + /* The current tuple is not valid, but we are possibly called + * because new auth tuples have been received */ + at = sgsn_auth_get_tuple(mmctx, mmctx->auth_triplet.key_seq); + if (!at) { + LOGMMCTXP(LOGL_ERROR, mmctx, + "Missing auth tuples, authorization not possible\n"); + auth_state = SGSN_AUTH_REJECTED; + } else { + mmctx->auth_triplet = *at; + } + } + + if (mmctx->auth_state == auth_state) + return; + + LOGMMCTXP(LOGL_INFO, mmctx, "Got authorization update: state %s -> %s\n", + get_value_string(sgsn_auth_state_names, mmctx->auth_state), + get_value_string(sgsn_auth_state_names, auth_state)); + + mmctx->auth_state = auth_state; + + switch (auth_state) { + case SGSN_AUTH_AUTHENTICATE: + if (subscr) + subscr->sgsn_data->auth_triplets_updated = 0; + + gsm0408_gprs_authenticate(mmctx); + break; + case SGSN_AUTH_ACCEPTED: + gsm0408_gprs_access_granted(mmctx); + break; + case SGSN_AUTH_REJECTED: + gmm_cause = + subscr ? subscr->sgsn_data->error_cause : + SGSN_ERROR_CAUSE_NONE; + + if (subscr && (subscr->flags & GPRS_SUBSCRIBER_CANCELLED) != 0) + gsm0408_gprs_access_cancelled(mmctx, gmm_cause); + else + gsm0408_gprs_access_denied(mmctx, gmm_cause); + break; + default: + break; + } +} + +struct gsm_auth_tuple *sgsn_auth_get_tuple(struct sgsn_mm_ctx *mmctx, + unsigned key_seq) +{ + unsigned count; + unsigned idx; + struct gsm_auth_tuple *at = NULL; + + struct sgsn_subscriber_data *sdata; + + if (!mmctx->subscr) + return NULL; + + if (key_seq == GSM_KEY_SEQ_INVAL) + /* Start with 0 after increment module array size */ + idx = ARRAY_SIZE(sdata->auth_triplets) - 1; + else + idx = key_seq; + + sdata = mmctx->subscr->sgsn_data; + + /* Find next tuple */ + for (count = ARRAY_SIZE(sdata->auth_triplets); count > 0; count--) { + idx = (idx + 1) % ARRAY_SIZE(sdata->auth_triplets); + + if (sdata->auth_triplets[idx].key_seq == GSM_KEY_SEQ_INVAL) + continue; + + if (sdata->auth_triplets[idx].use_count == 0) { + at = &sdata->auth_triplets[idx]; + at->use_count = 1; + return at; + } + } + + return NULL; +} diff --git a/src/sgsn/sgsn_cdr.c b/src/sgsn/sgsn_cdr.c new file mode 100644 index 000000000..a50b4dfdd --- /dev/null +++ b/src/sgsn/sgsn_cdr.c @@ -0,0 +1,301 @@ +/* GPRS SGSN CDR dumper */ + +/* (C) 2015 by Holger Hans Peter Freyther + * 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 . + * + */ + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include + +#include +#include + +/* TODO...avoid going through a global */ +extern struct sgsn_instance *sgsn; +extern struct ctrl_handle *g_ctrlh; + +/** + * The CDR module will generate an entry like: + * + * IMSI, # Subscriber IMSI + * IMEI, # Subscriber IMEI + * MSISDN, # Subscriber MISDN + * Charging_Timestamp, # Event start Time + * Charging_UTC, # Time zone of event start time + * Duration, # Session DURATION + * Cell_Id, # CELL_ID + * Location_Area, # LAC + * GGSN_ADDR, # GGSN_ADDR + * SGSN_ADDR, # SGSN_ADDR + * APNI, # APNI + * PDP_ADDR, # PDP_ADDR + * VOL_IN, # VOL_IN in Bytes + * VOL_OUT, # VOL_OUT in Bytes + * CAUSE_FOR_TERM, # CAUSE_FOR_TERM + */ + +static void send_cdr_trap(char *value) +{ + if (ctrl_cmd_send_trap(g_ctrlh, "cdr-v1", value) < 0) + LOGP(DGPRS, LOGL_ERROR, "Failed to create and send TRAP cdr-v1\n"); +} + +static void maybe_print_header(FILE *cdr_file) +{ + if (ftell(cdr_file) != 0) + return; + + fprintf(cdr_file, "timestamp,imsi,imei,msisdn,cell_id,lac,hlr,event,pdp_duration,ggsn_addr,sgsn_addr,apni,eua_addr,vol_in,vol_out,charging_id\n"); +} + +static int cdr_snprintf_mm(char *buf, size_t size, const char *ev, + struct sgsn_mm_ctx *mmctx) +{ + struct tm tm; + struct timeval tv; + int ret; + + gettimeofday(&tv, NULL); + gmtime_r(&tv.tv_sec, &tm); + ret = snprintf(buf, size, "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + (int)(tv.tv_usec / 1000), + mmctx->imsi, + mmctx->imei, + mmctx->msisdn, + mmctx->gb.cell_id, + mmctx->ra.lac, + mmctx->hlr, + ev); + return ret; +} + +static void cdr_log_mm(struct sgsn_instance *inst, const char *ev, + struct sgsn_mm_ctx *mmctx) +{ + FILE *cdr_file; + char buf[1024]; + + if (!inst->cfg.cdr.filename && !inst->cfg.cdr.trap) + return; + + cdr_snprintf_mm(buf, sizeof(buf), ev, mmctx); + + if (inst->cfg.cdr.trap) + send_cdr_trap(buf); + + if (inst->cfg.cdr.filename) { + cdr_file = fopen(inst->cfg.cdr.filename, "a"); + if (!cdr_file) { + LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n", + inst->cfg.cdr.filename); + return; + } + + maybe_print_header(cdr_file); + fprintf(cdr_file, "%s\n", buf); + + fclose(cdr_file); + } +} + +static void extract_eua(struct ul66_t *eua, char *eua_addr) +{ + if (eua->l < 2) + return; + + /* there is no addr for ETSI/PPP */ + if ((eua->v[0] & 0x0F) != 1) { + strcpy(eua_addr, "ETSI"); + return; + } + + if (eua->v[1] == 0x21 && eua->l == 6) + inet_ntop(AF_INET, &eua->v[2], eua_addr, INET_ADDRSTRLEN); + else if (eua->v[1] == 0x57 && eua->l == 18) + inet_ntop(AF_INET6, &eua->v[2], eua_addr, INET6_ADDRSTRLEN); + else { + /* e.g. both IPv4 and IPv6 */ + strcpy(eua_addr, "Unknown address"); + } +} + +static int cdr_snprintf_pdp(char *buf, size_t size, const char *ev, + struct sgsn_pdp_ctx *pdp) +{ + char apni[(pdp->lib ? pdp->lib->apn_use.l : 0) + 1]; + char ggsn_addr[INET_ADDRSTRLEN]; + char sgsn_addr[INET_ADDRSTRLEN]; + char eua_addr[INET6_ADDRSTRLEN]; + struct tm tm; + struct timeval tv; + time_t duration; + struct timespec tp; + int ret; + + memset(apni, 0, sizeof(apni)); + memset(ggsn_addr, 0, sizeof(ggsn_addr)); + memset(sgsn_addr, 0, sizeof(sgsn_addr)); + memset(eua_addr, 0, sizeof(eua_addr)); + + + if (pdp->lib) { + osmo_apn_to_str(apni, pdp->lib->apn_use.v, pdp->lib->apn_use.l); + inet_ntop(AF_INET, &pdp->lib->hisaddr0.s_addr, ggsn_addr, sizeof(ggsn_addr)); + extract_eua(&pdp->lib->eua, eua_addr); + } + + if (pdp->ggsn) + inet_ntop(AF_INET, &pdp->ggsn->gsn->gsnc.s_addr, sgsn_addr, sizeof(sgsn_addr)); + + osmo_clock_gettime(CLOCK_MONOTONIC, &tp); + gettimeofday(&tv, NULL); + + /* convert the timestamp to UTC */ + gmtime_r(&tv.tv_sec, &tm); + + /* Check the duration of the PDP context */ + duration = tp.tv_sec - pdp->cdr_start.tv_sec; + + ret = snprintf(buf, size, + "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s,%ld,%s,%s,%s,%s,%" PRIu64 ",%" PRIu64 ",%u", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + (int)(tv.tv_usec / 1000), + pdp->mm ? pdp->mm->imsi : "N/A", + pdp->mm ? pdp->mm->imei : "N/A", + pdp->mm ? pdp->mm->msisdn : "N/A", + pdp->mm ? pdp->mm->gb.cell_id : -1, + pdp->mm ? pdp->mm->ra.lac : -1, + pdp->mm ? pdp->mm->hlr : "N/A", + ev, + (unsigned long ) duration, + ggsn_addr, + sgsn_addr, + apni, + eua_addr, + pdp->cdr_bytes_in, + pdp->cdr_bytes_out, + pdp->cdr_charging_id); + return ret; +} + +static void cdr_log_pdp(struct sgsn_instance *inst, const char *ev, + struct sgsn_pdp_ctx *pdp) +{ + FILE *cdr_file; + char buf[1024]; + + if (!inst->cfg.cdr.filename && !inst->cfg.cdr.trap) + return; + + cdr_snprintf_pdp(buf, sizeof(buf), ev, pdp); + + if (inst->cfg.cdr.trap) + send_cdr_trap(buf); + + if (inst->cfg.cdr.filename) { + cdr_file = fopen(inst->cfg.cdr.filename, "a"); + if (!cdr_file) { + LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n", + inst->cfg.cdr.filename); + return; + } + + maybe_print_header(cdr_file); + fprintf(cdr_file, "%s\n", buf); + fclose(cdr_file); + } +} + +static void cdr_pdp_timeout(void *_data) +{ + struct sgsn_pdp_ctx *pdp = _data; + cdr_log_pdp(sgsn, "pdp-periodic", pdp); + osmo_timer_schedule(&pdp->cdr_timer, sgsn->cfg.cdr.interval, 0); +} + +static int handle_sgsn_sig(unsigned int subsys, unsigned int signal, + void *handler_data, void *_signal_data) +{ + struct sgsn_signal_data *signal_data = _signal_data; + struct sgsn_instance *inst = handler_data; + + if (subsys != SS_SGSN) + return 0; + + switch (signal) { + case S_SGSN_ATTACH: + cdr_log_mm(inst, "attach", signal_data->mm); + break; + case S_SGSN_UPDATE: + cdr_log_mm(inst, "update", signal_data->mm); + break; + case S_SGSN_DETACH: + cdr_log_mm(inst, "detach", signal_data->mm); + break; + case S_SGSN_MM_FREE: + cdr_log_mm(inst, "free", signal_data->mm); + break; + case S_SGSN_PDP_ACT: + osmo_clock_gettime(CLOCK_MONOTONIC, &signal_data->pdp->cdr_start); + signal_data->pdp->cdr_charging_id = signal_data->pdp->lib->cid; + cdr_log_pdp(inst, "pdp-act", signal_data->pdp); + osmo_timer_setup(&signal_data->pdp->cdr_timer, cdr_pdp_timeout, + signal_data->pdp); + osmo_timer_schedule(&signal_data->pdp->cdr_timer, inst->cfg.cdr.interval, 0); + break; + case S_SGSN_PDP_DEACT: + cdr_log_pdp(inst, "pdp-deact", signal_data->pdp); + osmo_timer_del(&signal_data->pdp->cdr_timer); + break; + case S_SGSN_PDP_TERMINATE: + cdr_log_pdp(inst, "pdp-terminate", signal_data->pdp); + osmo_timer_del(&signal_data->pdp->cdr_timer); + break; + case S_SGSN_PDP_FREE: + cdr_log_pdp(inst, "pdp-free", signal_data->pdp); + osmo_timer_del(&signal_data->pdp->cdr_timer); + break; + } + + return 0; +} + +int sgsn_cdr_init(struct sgsn_instance *sgsn) +{ + /* register for CDR related events */ + sgsn->cfg.cdr.interval = 10 * 60; + osmo_signal_register_handler(SS_SGSN, handle_sgsn_sig, sgsn); + + return 0; +} diff --git a/src/sgsn/sgsn_ctrl.c b/src/sgsn/sgsn_ctrl.c new file mode 100644 index 000000000..ad91d25a5 --- /dev/null +++ b/src/sgsn/sgsn_ctrl.c @@ -0,0 +1,62 @@ +/* Control Interface Implementation for the SGSN */ +/* + * (C) 2014 by Holger Hans Peter Freyther + * (C) 2014 by sysmocom s.f.m.c. GmbH + * 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 . + * + */ + +#include +#include +#include +#include +#include + +#include + +extern vector ctrl_node_vec; + +static int get_subscriber_list(struct ctrl_cmd *cmd, void *d) +{ + struct sgsn_mm_ctx *mm; + + cmd->reply = talloc_strdup(cmd, ""); + llist_for_each_entry(mm, &sgsn_mm_ctxts, list) { + char *addr = NULL; + struct sgsn_pdp_ctx *pdp; + + if (strlen(mm->imsi) == 0) + continue; + + llist_for_each_entry(pdp, &mm->pdp_list, list) + addr = gprs_pdpaddr2str(pdp->lib->eua.v, + pdp->lib->eua.l); + + cmd->reply = talloc_asprintf_append( + cmd->reply, + "%s,%s\n", mm->imsi, addr ? addr : ""); + } + + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_RO(subscriber_list, "subscriber-list-active-v1"); + +int sgsn_ctrl_cmds_install(void) +{ + int rc = 0; + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_subscriber_list); + return rc; +} diff --git a/src/sgsn/sgsn_libgtp.c b/src/sgsn/sgsn_libgtp.c new file mode 100644 index 000000000..5e3f48f3f --- /dev/null +++ b/src/sgsn/sgsn_libgtp.c @@ -0,0 +1,842 @@ +/* GPRS SGSN integration with libgtp of OpenGGSN */ +/* libgtp implements the GPRS Tunelling Protocol GTP per TS 09.60 / 29.060 */ + +/* (C) 2010 by Harald Welte + * (C) 2010 by On-Waves + * (C) 2015 by Holger Hans Peter Freyther + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bscconfig.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* TS 23.003: The MSISDN shall take the dummy MSISDN value composed of + * 15 digits set to 0 (encoded as an E.164 international number) when + * the MSISDN is not available in messages in which the presence of the + * MSISDN parameter */ +static const uint8_t dummy_msisdn[] = + { 0x91, /* No extension, international, E.164 */ + 0, 0, 0, 0, 0, 0, 0, /* 14 digits of zeroes */ + 0xF0 /* 15th digit of zero + padding */ }; + +const struct value_string gtp_cause_strs[] = { + { GTPCAUSE_REQ_IMSI, "Request IMSI" }, + { GTPCAUSE_REQ_IMEI, "Request IMEI" }, + { GTPCAUSE_REQ_IMSI_IMEI, "Request IMSI and IMEI" }, + { GTPCAUSE_NO_ID_NEEDED, "No identity needed" }, + { GTPCAUSE_MS_REFUSES_X, "MS refuses" }, + { GTPCAUSE_MS_NOT_RESP_X, "MS is not GPRS responding" }, + { GTPCAUSE_ACC_REQ, "Request accepted" }, + { GTPCAUSE_NON_EXIST, "Non-existent" }, + { GTPCAUSE_INVALID_MESSAGE, "Invalid message format" }, + { GTPCAUSE_IMSI_NOT_KNOWN, "IMSI not known" }, + { GTPCAUSE_MS_DETACHED, "MS is GPRS detached" }, + { GTPCAUSE_MS_NOT_RESP, "MS is not GPRS responding" }, + { GTPCAUSE_MS_REFUSES, "MS refuses" }, + { GTPCAUSE_NO_RESOURCES, "No resources available" }, + { GTPCAUSE_NOT_SUPPORTED, "Service not supported" }, + { GTPCAUSE_MAN_IE_INCORRECT, "Mandatory IE incorrect" }, + { GTPCAUSE_MAN_IE_MISSING, "Mandatory IE missing" }, + { GTPCAUSE_OPT_IE_INCORRECT, "Optional IE incorrect" }, + { GTPCAUSE_SYS_FAIL, "System failure" }, + { GTPCAUSE_ROAMING_REST, "Roaming restrictions" }, + { GTPCAUSE_PTIMSI_MISMATCH, "P-TMSI Signature mismatch" }, + { GTPCAUSE_CONN_SUSP, "GPRS connection suspended" }, + { GTPCAUSE_AUTH_FAIL, "Authentication failure" }, + { GTPCAUSE_USER_AUTH_FAIL, "User authentication failed" }, + { GTPCAUSE_CONTEXT_NOT_FOUND, "Context not found" }, + { GTPCAUSE_ADDR_OCCUPIED, "All dynamic PDP addresses occupied" }, + { GTPCAUSE_NO_MEMORY, "No memory is available" }, + { GTPCAUSE_RELOC_FAIL, "Relocation failure" }, + { GTPCAUSE_UNKNOWN_MAN_EXTHEADER, "Unknown mandatory ext. header" }, + { GTPCAUSE_SEM_ERR_TFT, "Semantic error in TFT operation" }, + { GTPCAUSE_SYN_ERR_TFT, "Syntactic error in TFT operation" }, + { GTPCAUSE_SEM_ERR_FILTER, "Semantic errors in packet filter" }, + { GTPCAUSE_SYN_ERR_FILTER, "Syntactic errors in packet filter" }, + { GTPCAUSE_MISSING_APN, "Missing or unknown APN" }, + { GTPCAUSE_UNKNOWN_PDP, "Unknown PDP address or PDP type" }, + { 0, NULL } +}; + +/* Generate the GTP IMSI IE according to 09.60 Section 7.9.2 */ +static uint64_t imsi_str2gtp(char *str) +{ + uint64_t imsi64 = 0; + unsigned int n; + unsigned int imsi_len = strlen(str); + + if (imsi_len > 16) { + LOGP(DGPRS, LOGL_NOTICE, "IMSI length > 16 not supported!\n"); + return 0; + } + + for (n = 0; n < 16; n++) { + uint64_t val; + if (n < imsi_len) + val = (str[n]-'0') & 0xf; + else + val = 0xf; + imsi64 |= (val << (n*4)); + } + return imsi64; +} + +/* generate a PDP context based on the IE's from the 04.08 message, + * and send the GTP create pdp context request to the GGSN */ +struct sgsn_pdp_ctx *sgsn_create_pdp_ctx(struct sgsn_ggsn_ctx *ggsn, + struct sgsn_mm_ctx *mmctx, + uint16_t nsapi, + struct tlv_parsed *tp) +{ + struct gprs_ra_id raid; + struct sgsn_pdp_ctx *pctx; + struct pdp_t *pdp; + uint64_t imsi_ui64; + size_t qos_len; + const uint8_t *qos; + int rc; + + pctx = sgsn_pdp_ctx_alloc(mmctx, ggsn, nsapi); + if (!pctx) { + LOGP(DGPRS, LOGL_ERROR, "Couldn't allocate PDP Ctx\n"); + return NULL; + } + + imsi_ui64 = imsi_str2gtp(mmctx->imsi); + + rc = gtp_pdp_newpdp(ggsn->gsn, &pdp, imsi_ui64, nsapi, NULL); + if (rc) { + LOGP(DGPRS, LOGL_ERROR, "Out of libgtp PDP Contexts\n"); + return NULL; + } + pdp->priv = pctx; + pctx->lib = pdp; + + //pdp->peer = /* sockaddr_in of GGSN (receive) */ + //pdp->ipif = /* not used by library */ + pdp->version = ggsn->gtp_version; + pdp->hisaddr0 = ggsn->remote_addr; + pdp->hisaddr1 = ggsn->remote_addr; + //pdp->cch_pdp = 512; /* Charging Flat Rate */ + + /* MS provided APN, subscription was verified by the caller */ + pdp->selmode = 0xFC | 0x00; + + /* IMSI, TEID/TEIC, FLLU/FLLC, TID, NSAPI set in pdp_newpdp */ + LOGPDPCTXP(LOGL_NOTICE, pctx, "Create PDP Context\n"); + + /* Put the MSISDN in case we have it */ + if (mmctx->subscr && mmctx->subscr->sgsn_data->msisdn_len) { + pdp->msisdn.l = OSMO_MIN(mmctx->subscr->sgsn_data->msisdn_len, sizeof(pdp->msisdn.v)); + memcpy(pdp->msisdn.v, mmctx->subscr->sgsn_data->msisdn, + pdp->msisdn.l); + } else { + /* use the dummy 15-digits-zero MSISDN value */ + pdp->msisdn.l = sizeof(dummy_msisdn); + memcpy(pdp->msisdn.v, dummy_msisdn, pdp->msisdn.l); + } + + /* End User Address from GMM requested PDP address */ + pdp->eua.l = TLVP_LEN(tp, OSMO_IE_GSM_REQ_PDP_ADDR); + if (pdp->eua.l > sizeof(pdp->eua.v)) + pdp->eua.l = sizeof(pdp->eua.v); + memcpy(pdp->eua.v, TLVP_VAL(tp, OSMO_IE_GSM_REQ_PDP_ADDR), + pdp->eua.l); + /* Highest 4 bits of first byte need to be set to 1, otherwise + * the IE is identical with the 04.08 PDP Address IE */ + pdp->eua.v[0] |= 0xf0; + + /* APN name from GMM */ + if (TLVP_PRESENT(tp, GSM48_IE_GSM_APN)) { + pdp->apn_use.l = TLVP_LEN(tp, GSM48_IE_GSM_APN); + if (pdp->apn_use.l > sizeof(pdp->apn_use.v)) + pdp->apn_use.l = sizeof(pdp->apn_use.v); + memcpy(pdp->apn_use.v, TLVP_VAL(tp, GSM48_IE_GSM_APN), pdp->apn_use.l); + } else { + pdp->apn_use.l = 0; + } + + /* Protocol Configuration Options from GMM */ + if (TLVP_PRESENT(tp, GSM48_IE_GSM_PROTO_CONF_OPT)) { + pdp->pco_req.l = TLVP_LEN(tp, GSM48_IE_GSM_PROTO_CONF_OPT); + if (pdp->pco_req.l > sizeof(pdp->pco_req.v)) + pdp->pco_req.l = sizeof(pdp->pco_req.v); + memcpy(pdp->pco_req.v, TLVP_VAL(tp, GSM48_IE_GSM_PROTO_CONF_OPT), + pdp->pco_req.l); + } else { + pdp->pco_req.l = 0; + } + + /* QoS options from GMM or remote */ + if (TLVP_LEN(tp, OSMO_IE_GSM_SUB_QOS) > 0) { + qos_len = TLVP_LEN(tp, OSMO_IE_GSM_SUB_QOS); + qos = TLVP_VAL(tp, OSMO_IE_GSM_SUB_QOS); + } else { + qos_len = TLVP_LEN(tp, OSMO_IE_GSM_REQ_QOS); + qos = TLVP_VAL(tp, OSMO_IE_GSM_REQ_QOS); + } + + if (qos_len <= 3) { + pdp->qos_req.l = qos_len + 1; + if (pdp->qos_req.l > sizeof(pdp->qos_req.v)) + pdp->qos_req.l = sizeof(pdp->qos_req.v); + pdp->qos_req.v[0] = 0; /* Allocation/Retention policy */ + memcpy(&pdp->qos_req.v[1], qos, pdp->qos_req.l - 1); + } else { + pdp->qos_req.l = qos_len; + if (pdp->qos_req.l > sizeof(pdp->qos_req.v)) + pdp->qos_req.l = sizeof(pdp->qos_req.v); + memcpy(pdp->qos_req.v, qos, pdp->qos_req.l); + } + + /* charging characteristics if present */ + if (TLVP_LEN(tp, OSMO_IE_GSM_CHARG_CHAR) >= sizeof(pdp->cch_pdp)) + pdp->cch_pdp = tlvp_val16be(tp, OSMO_IE_GSM_CHARG_CHAR); + + /* SGSN address for control plane */ + pdp->gsnlc.l = sizeof(sgsn->cfg.gtp_listenaddr.sin_addr); + memcpy(pdp->gsnlc.v, &sgsn->cfg.gtp_listenaddr.sin_addr, + sizeof(sgsn->cfg.gtp_listenaddr.sin_addr)); + + /* SGSN address for user plane + * Default to the control plane addr for now. If we are connected to a + * hnbgw via IuPS we'll need to send a PDP context update with the + * correct IP address after the RAB Assignment is complete */ + pdp->gsnlu.l = sizeof(sgsn->cfg.gtp_listenaddr.sin_addr); + memcpy(pdp->gsnlu.v, &sgsn->cfg.gtp_listenaddr.sin_addr, + sizeof(sgsn->cfg.gtp_listenaddr.sin_addr)); + + /* Encode RAT Type according to TS 29.060 7.7.50 */ + pdp->rattype.l = 1; + if (mmctx->ran_type == MM_CTX_T_UTRAN_Iu) + pdp->rattype.v[0] = 1; + else + pdp->rattype.v[0] = 2; + pdp->rattype_given = 1; + + /* Include RAI and ULI all the time */ + pdp->rai_given = 1; + pdp->rai.l = 6; + + /* Routing Area Identifier with LAC and RAC fixed values, as + * requested in 29.006 7.3.1 */ + raid = mmctx->ra; + raid.lac = 0xFFFE; + raid.rac = 0xFF; + gsm48_encode_ra((struct gsm48_ra_id *)pdp->rai.v, &raid); + + /* Encode User Location Information accordint to TS 29.060 7.7.51 */ + pdp->userloc_given = 1; + pdp->userloc.l = 8; + switch (mmctx->ran_type) { + case MM_CTX_T_GERAN_Gb: +#if 0 + case MM_CTX_T_GERAN_Iu: +#endif + pdp->rattype.v[0] = 2; + /* User Location Information */ + pdp->userloc_given = 1; + pdp->userloc.l = 8; + pdp->userloc.v[0] = 0; /* CGI for GERAN */ + bssgp_create_cell_id(&pdp->userloc.v[1], &mmctx->ra, mmctx->gb.cell_id); + break; + case MM_CTX_T_UTRAN_Iu: + pdp->userloc.v[0] = 1; /* SAI for UTRAN */ + /* SAI is like CGI but with SAC instead of CID, so we can abuse this function */ + bssgp_create_cell_id(&pdp->userloc.v[1], &mmctx->ra, mmctx->iu.sac); + break; + } + + /* include the IMEI(SV) */ + pdp->imeisv_given = 1; + gsm48_encode_bcd_number(&pdp->imeisv.v[0], 8, 0, mmctx->imei); + pdp->imeisv.l = pdp->imeisv.v[0]; + memmove(&pdp->imeisv.v[0], &pdp->imeisv.v[1], 8); + + /* change pdp state to 'requested' */ + pctx->state = PDP_STATE_CR_REQ; + + rc = gtp_create_context_req(ggsn->gsn, pdp, pctx); + /* FIXME */ + + return pctx; +} + +/* SGSN wants to delete a PDP context */ +int sgsn_delete_pdp_ctx(struct sgsn_pdp_ctx *pctx) +{ + LOGPDPCTXP(LOGL_INFO, pctx, "Delete PDP Context\n"); + + /* FIXME: decide if we need teardown or not ! */ + return gtp_delete_context_req2(pctx->ggsn->gsn, pctx->lib, pctx, 1); +} + +struct cause_map { + uint8_t cause_in; + uint8_t cause_out; +}; + +static uint8_t cause_map(const struct cause_map *map, uint8_t in, uint8_t deflt) +{ + const struct cause_map *m; + + for (m = map; m->cause_in && m->cause_out; m++) { + if (m->cause_in == in) + return m->cause_out; + } + return deflt; +} + +/* how do we map from gtp cause to SM cause */ +static const struct cause_map gtp2sm_cause_map[] = { + { GTPCAUSE_NO_RESOURCES, GSM_CAUSE_INSUFF_RSRC }, + { GTPCAUSE_NOT_SUPPORTED, GSM_CAUSE_SERV_OPT_NOTSUPP }, + { GTPCAUSE_MAN_IE_INCORRECT, GSM_CAUSE_INV_MAND_INFO }, + { GTPCAUSE_MAN_IE_MISSING, GSM_CAUSE_INV_MAND_INFO }, + { GTPCAUSE_OPT_IE_INCORRECT, GSM_CAUSE_PROTO_ERR_UNSPEC }, + { GTPCAUSE_SYS_FAIL, GSM_CAUSE_NET_FAIL }, + { GTPCAUSE_ROAMING_REST, GSM_CAUSE_REQ_SERV_OPT_NOTSUB }, + { GTPCAUSE_PTIMSI_MISMATCH, GSM_CAUSE_PROTO_ERR_UNSPEC }, + { GTPCAUSE_CONN_SUSP, GSM_CAUSE_PROTO_ERR_UNSPEC }, + { GTPCAUSE_AUTH_FAIL, GSM_CAUSE_AUTH_FAILED }, + { GTPCAUSE_USER_AUTH_FAIL, GSM_CAUSE_ACT_REJ_GGSN }, + { GTPCAUSE_CONTEXT_NOT_FOUND, GSM_CAUSE_PROTO_ERR_UNSPEC }, + { GTPCAUSE_ADDR_OCCUPIED, GSM_CAUSE_INSUFF_RSRC }, + { GTPCAUSE_NO_MEMORY, GSM_CAUSE_INSUFF_RSRC }, + { GTPCAUSE_RELOC_FAIL, GSM_CAUSE_PROTO_ERR_UNSPEC }, + { GTPCAUSE_UNKNOWN_MAN_EXTHEADER, GSM_CAUSE_PROTO_ERR_UNSPEC }, + { GTPCAUSE_MISSING_APN, GSM_CAUSE_MISSING_APN }, + { GTPCAUSE_UNKNOWN_PDP, GSM_CAUSE_UNKNOWN_PDP }, + { 0, 0 } +}; + +int send_act_pdp_cont_acc(struct sgsn_pdp_ctx *pctx) +{ + struct sgsn_signal_data sig_data; + int rc; + struct gprs_llc_lle *lle; + + /* Inform others about it */ + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.pdp = pctx; + osmo_signal_dispatch(SS_SGSN, S_SGSN_PDP_ACT, &sig_data); + + /* Send PDP CTX ACT to MS */ + rc = gsm48_tx_gsm_act_pdp_acc(pctx); + if (rc < 0) + return rc; + + if (pctx->mm->ran_type == MM_CTX_T_GERAN_Gb) { + /* Send SNDCP XID to MS */ + lle = &pctx->mm->gb.llme->lle[pctx->sapi]; + rc = sndcp_sn_xid_req(lle,pctx->nsapi); + if (rc < 0) + return rc; + } + + return 0; +} + +/* The GGSN has confirmed the creation of a PDP Context */ +static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause) +{ + struct sgsn_pdp_ctx *pctx = cbp; + uint8_t reject_cause = 0; + + LOGPDPCTXP(LOGL_INFO, pctx, "Received CREATE PDP CTX CONF, cause=%d(%s)\n", + cause, get_value_string(gtp_cause_strs, cause)); + + if (!pctx->mm) { + goto reject; + } + + /* Check for cause value if it was really successful */ + if (cause < 0) { + LOGP(DGPRS, LOGL_NOTICE, "Create PDP ctx req timed out\n"); + if (pdp && pdp->version == 1) { + pdp->version = 0; + gtp_create_context_req(sgsn->gsn, pdp, cbp); + return 0; + } else { + reject_cause = GSM_CAUSE_NET_FAIL; + goto reject; + } + } + + /* Check for cause value if it was really successful */ + if (cause != GTPCAUSE_ACC_REQ) { + reject_cause = cause_map(gtp2sm_cause_map, cause, + GSM_CAUSE_ACT_REJ_GGSN); + goto reject; + } + + if (pctx->mm->ran_type == MM_CTX_T_GERAN_Gb) { + /* Activate the SNDCP layer */ + sndcp_sm_activate_ind(&pctx->mm->gb.llme->lle[pctx->sapi], pctx->nsapi); + return send_act_pdp_cont_acc(pctx); + } else if (pctx->mm->ran_type == MM_CTX_T_UTRAN_Iu) { +#ifdef BUILD_IU + /* Activate a radio bearer */ + iu_rab_act_ps(pdp->nsapi, pctx); + return 0; +#else + return -ENOTSUP; +#endif + } + + LOGP(DGPRS, LOGL_ERROR, "Unknown ran_type %d\n", + pctx->mm->ran_type); + reject_cause = GSM_CAUSE_PROTO_ERR_UNSPEC; + +reject: + /* + * In case of a timeout pdp will be NULL but we have a valid pointer + * in pctx->lib. For other rejects pctx->lib and pdp might be the + * same. + */ + pctx->state = PDP_STATE_NONE; + if (pctx->lib && pctx->lib != pdp) + pdp_freepdp(pctx->lib); + pctx->lib = NULL; + + if (pdp) + pdp_freepdp(pdp); + + /* Send PDP CTX ACT REJ to MS */ + if (pctx->mm) + gsm48_tx_gsm_act_pdp_rej(pctx->mm, pctx->ti, reject_cause, + 0, NULL); + sgsn_pdp_ctx_free(pctx); + + return EOF; +} + +void sgsn_pdp_upd_gtp_u(struct sgsn_pdp_ctx *pdp, void *addr, size_t alen) +{ + pdp->lib->gsnlu.l = alen; + memcpy(pdp->lib->gsnlu.v, addr, alen); + gtp_update_context(pdp->ggsn->gsn, pdp->lib, pdp, &pdp->lib->hisaddr0); +} + +void sgsn_ggsn_echo_req(struct sgsn_ggsn_ctx *ggc) +{ + LOGGGSN(ggc, LOGL_INFO, "GTP Tx Echo Request\n"); + gtp_echo_req(ggc->gsn, ggc->gtp_version, ggc, &ggc->remote_addr); +} + +/* Confirmation of a PDP Context Delete */ +static int delete_pdp_conf(struct pdp_t *pdp, void *cbp, int cause) +{ + struct sgsn_signal_data sig_data; + struct sgsn_pdp_ctx *pctx = cbp; + int rc = 0; + + LOGPDPCTXP(LOGL_INFO, pctx, "Received DELETE PDP CTX CONF, cause=%d(%s)\n", + cause, get_value_string(gtp_cause_strs, cause)); + + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.pdp = pctx; + osmo_signal_dispatch(SS_SGSN, S_SGSN_PDP_DEACT, &sig_data); + + if (pctx->mm) { + if (pctx->mm->ran_type == MM_CTX_T_GERAN_Gb) { + /* Deactivate the SNDCP layer */ + sndcp_sm_deactivate_ind(&pctx->mm->gb.llme->lle[pctx->sapi], pctx->nsapi); + } else { +#ifdef BUILD_IU + /* Deactivate radio bearer */ + ranap_iu_rab_deact(pctx->mm->iu.ue_ctx, 1); +#else + return -ENOTSUP; +#endif + } + + /* Confirm deactivation of PDP context to MS */ + rc = gsm48_tx_gsm_deact_pdp_acc(pctx); + } else { + LOGPDPCTXP(LOGL_NOTICE, pctx, + "Not deactivating SNDCP layer since the MM context " + "is not available\n"); + } + + sgsn_pdp_ctx_free(pctx); + + return rc; +} + +/* Confirmation of an GTP ECHO request */ +static int echo_conf(void *cbp, bool timeout) +{ + struct sgsn_ggsn_ctx *ggc = (struct sgsn_ggsn_ctx *)cbp; + if (timeout) { + LOGGGSN(ggc, LOGL_NOTICE, "GTP Echo Request timed out\n"); + /* FIXME: if version == 1, retry with version 0 */ + sgsn_ggsn_ctx_drop_all_pdp(ggc); + } else { + LOGGGSN(ggc, LOGL_INFO, "GTP Rx Echo Response\n"); + } + return 0; +} + +/* Any message received by GGSN contains a recovery IE */ +static int cb_recovery2(struct sockaddr_in *peer, struct pdp_t *pdp, uint8_t recovery) +{ + struct sgsn_ggsn_ctx *ggsn; + struct sgsn_pdp_ctx *pctx = NULL; + + ggsn = sgsn_ggsn_ctx_by_addr(&peer->sin_addr); + if (!ggsn) { + LOGP(DGPRS, LOGL_NOTICE, "Received Recovery IE for unknown GGSN\n"); + return -EINVAL; + } + + if (ggsn->remote_restart_ctr == -1) { + /* First received ECHO RESPONSE, note the restart ctr */ + ggsn->remote_restart_ctr = recovery; + } else if (ggsn->remote_restart_ctr != recovery) { + /* counter has changed (GGSN restart): release all PDP */ + LOGP(DGPRS, LOGL_NOTICE, "GGSN recovery (%u->%u) pdp=%p, " + "releasing all%s PDP contexts\n", + ggsn->remote_restart_ctr, recovery, pdp, pdp ? " other" : ""); + ggsn->remote_restart_ctr = recovery; + if (pdp) + pctx = pdp->priv; + sgsn_ggsn_ctx_drop_all_pdp_except(ggsn, pctx); + } + return 0; +} + +/* libgtp callback for confirmations */ +static int cb_conf(int type, int cause, struct pdp_t *pdp, void *cbp) +{ + DEBUGP(DGPRS, "libgtp cb_conf(type=%d, cause=%d, pdp=%p, cbp=%p)\n", + type, cause, pdp, cbp); + + if (cause == EOF) + LOGP(DGPRS, LOGL_ERROR, "libgtp EOF (type=%u, pdp=%p, cbp=%p)\n", + type, pdp, cbp); + + switch (type) { + case GTP_ECHO_REQ: + /* libgtp hands us the RECOVERY number instead of a cause (EOF on timeout) */ + return echo_conf(cbp, cause == EOF); + case GTP_CREATE_PDP_REQ: + return create_pdp_conf(pdp, cbp, cause); + case GTP_DELETE_PDP_REQ: + return delete_pdp_conf(pdp, cbp, cause); + default: + break; + } + return 0; +} + +/* Called whenever a PDP context is deleted for any reason */ +static int cb_delete_context(struct pdp_t *pdp) +{ + struct sgsn_pdp_ctx *pctx = pdp->priv; + + LOGPDPX(DGPRS, LOGL_INFO, pdp, "Context %p was deleted\n", pdp); + + /* unlink the now non-existing library handle from the pdp context. + This way we avoid calling pdp_freepdp() on it, since after returning + from cb_delete_context callback, libgtp is already doing so. */ + pctx->lib = NULL; + + sgsn_ggsn_ctx_drop_pdp(pctx); + return 0; +} + +/* Called when we receive a Version Not Supported message */ +static int cb_unsup_ind(struct sockaddr_in *peer) +{ + LOGP(DGPRS, LOGL_INFO, "GTP Version not supported Indication " + "from %s:%u\n", inet_ntoa(peer->sin_addr), + ntohs(peer->sin_port)); + return 0; +} + +/* Called when we receive a Supported Ext Headers Notification */ +static int cb_extheader_ind(struct sockaddr_in *peer) +{ + LOGP(DGPRS, LOGL_INFO, "GTP Supported Ext Headers Notification " + "from %s:%u\n", inet_ntoa(peer->sin_addr), + ntohs(peer->sin_port)); + return 0; +} + +/* Called whenever we receive a DATA packet */ +static int cb_data_ind(struct pdp_t *lib, void *packet, unsigned int len) +{ + struct bssgp_paging_info pinfo; + struct sgsn_pdp_ctx *pdp; + struct sgsn_mm_ctx *mm; + struct msgb *msg; + uint8_t *ud; + + pdp = lib->priv; + if (!pdp) { + LOGP(DGPRS, LOGL_NOTICE, + "GTP DATA IND from GGSN for unknown PDP\n"); + return -EIO; + } + mm = pdp->mm; + if (!mm) { + LOGP(DGPRS, LOGL_ERROR, + "PDP context (address=%u) without MM context!\n", + pdp->address); + return -EIO; + } + + DEBUGP(DGPRS, "GTP DATA IND from GGSN for %s, length=%u\n", mm->imsi, + len); + + if (mm->ran_type == MM_CTX_T_UTRAN_Iu) { +#ifdef BUILD_IU + /* Ignore the packet for now and page the UE to get the RAB + * reestablished */ + ranap_iu_page_ps(mm->imsi, &mm->p_tmsi, mm->ra.lac, mm->ra.rac); + + return 0; +#else + return -ENOTSUP; +#endif + } + + msg = msgb_alloc_headroom(len+256, 128, "GTP->SNDCP"); + ud = msgb_put(msg, len); + memcpy(ud, packet, len); + + msgb_tlli(msg) = mm->gb.tlli; + msgb_bvci(msg) = mm->gb.bvci; + msgb_nsei(msg) = mm->gb.nsei; + + switch (mm->gmm_state) { + case GMM_REGISTERED_SUSPENDED: + /* initiate PS PAGING procedure */ + memset(&pinfo, 0, sizeof(pinfo)); + pinfo.mode = BSSGP_PAGING_PS; + pinfo.scope = BSSGP_PAGING_BVCI; + pinfo.bvci = mm->gb.bvci; + pinfo.imsi = mm->imsi; + pinfo.ptmsi = &mm->p_tmsi; + pinfo.drx_params = mm->drx_parms; + pinfo.qos[0] = 0; // FIXME + bssgp_tx_paging(mm->gb.nsei, 0, &pinfo); + rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PAGING_PS]); + /* FIXME: queue the packet we received from GTP */ + break; + case GMM_REGISTERED_NORMAL: + break; + default: + LOGP(DGPRS, LOGL_ERROR, "GTP DATA IND for TLLI %08X in state " + "%u\n", mm->gb.tlli, mm->gmm_state); + msgb_free(msg); + return -1; + } + + rate_ctr_inc(&pdp->ctrg->ctr[PDP_CTR_PKTS_UDATA_OUT]); + rate_ctr_add(&pdp->ctrg->ctr[PDP_CTR_BYTES_UDATA_OUT], len); + rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PKTS_UDATA_OUT]); + rate_ctr_add(&mm->ctrg->ctr[GMM_CTR_BYTES_UDATA_OUT], len); + + /* It is easier to have a global count */ + pdp->cdr_bytes_out += len; + + return sndcp_unitdata_req(msg, &mm->gb.llme->lle[pdp->sapi], + pdp->nsapi, mm); +} + +/* Called by SNDCP when it has received/re-assembled a N-PDU */ +int sgsn_rx_sndcp_ud_ind(struct gprs_ra_id *ra_id, int32_t tlli, uint8_t nsapi, + struct msgb *msg, uint32_t npdu_len, uint8_t *npdu) +{ + struct sgsn_mm_ctx *mmctx; + struct sgsn_pdp_ctx *pdp; + + /* look-up the MM context for this message */ + mmctx = sgsn_mm_ctx_by_tlli(tlli, ra_id); + if (!mmctx) { + LOGP(DGPRS, LOGL_ERROR, + "Cannot find MM CTX for TLLI %08x\n", tlli); + return -EIO; + } + /* look-up the PDP context for this message */ + pdp = sgsn_pdp_ctx_by_nsapi(mmctx, nsapi); + if (!pdp) { + LOGP(DGPRS, LOGL_ERROR, "Cannot find PDP CTX for " + "TLLI=%08x, NSAPI=%u\n", tlli, nsapi); + return -EIO; + } + if (!pdp->lib) { + LOGP(DGPRS, LOGL_ERROR, "PDP CTX without libgtp\n"); + return -EIO; + } + + rate_ctr_inc(&pdp->ctrg->ctr[PDP_CTR_PKTS_UDATA_IN]); + rate_ctr_add(&pdp->ctrg->ctr[PDP_CTR_BYTES_UDATA_IN], npdu_len); + rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_UDATA_IN]); + rate_ctr_add(&mmctx->ctrg->ctr[GMM_CTR_BYTES_UDATA_IN], npdu_len); + + /* It is easier to have a global count */ + pdp->cdr_bytes_in += npdu_len; + + return gtp_data_req(pdp->ggsn->gsn, pdp->lib, npdu, npdu_len); +} + +/* libgtp select loop integration */ +static int sgsn_gtp_fd_cb(struct osmo_fd *fd, unsigned int what) +{ + struct sgsn_instance *sgi = fd->data; + int rc; + + if (!(what & BSC_FD_READ)) + return 0; + + switch (fd->priv_nr) { + case 0: + rc = gtp_decaps0(sgi->gsn); + break; + case 1: + rc = gtp_decaps1c(sgi->gsn); + break; + case 2: + rc = gtp_decaps1u(sgi->gsn); + break; + default: + rc = -EINVAL; + break; + } + return rc; +} + +static void sgsn_gtp_tmr_start(struct sgsn_instance *sgi) +{ + struct timeval next; + + /* Retrieve next retransmission as struct timeval */ + gtp_retranstimeout(sgi->gsn, &next); + + /* re-schedule the timer */ + osmo_timer_schedule(&sgi->gtp_timer, next.tv_sec, next.tv_usec/1000); +} + +/* timer callback for libgtp retransmissions and ping */ +static void sgsn_gtp_tmr_cb(void *data) +{ + struct sgsn_instance *sgi = data; + + /* Do all the retransmissions as needed */ + gtp_retrans(sgi->gsn); + + sgsn_gtp_tmr_start(sgi); +} + +int sgsn_gtp_init(struct sgsn_instance *sgi) +{ + int rc; + struct gsn_t *gsn; + + rc = gtp_new(&sgi->gsn, sgi->cfg.gtp_statedir, + &sgi->cfg.gtp_listenaddr.sin_addr, GTP_MODE_SGSN); + if (rc) { + LOGP(DGPRS, LOGL_ERROR, "Failed to create GTP: %d\n", rc); + return rc; + } + LOGP(DGPRS, LOGL_NOTICE, "Created GTP on %s\n", inet_ntoa(sgi->cfg.gtp_listenaddr.sin_addr)); + + gsn = sgi->gsn; + + if (gsn->mode != GTP_MODE_SGSN) + return -EINVAL; + + sgi->gtp_fd0.fd = gsn->fd0; + sgi->gtp_fd0.priv_nr = 0; + sgi->gtp_fd0.data = sgi; + sgi->gtp_fd0.when = BSC_FD_READ; + sgi->gtp_fd0.cb = sgsn_gtp_fd_cb; + rc = osmo_fd_register(&sgi->gtp_fd0); + if (rc < 0) + return rc; + + sgi->gtp_fd1c.fd = gsn->fd1c; + sgi->gtp_fd1c.priv_nr = 1; + sgi->gtp_fd1c.data = sgi; + sgi->gtp_fd1c.when = BSC_FD_READ; + sgi->gtp_fd1c.cb = sgsn_gtp_fd_cb; + rc = osmo_fd_register(&sgi->gtp_fd1c); + if (rc < 0) { + osmo_fd_unregister(&sgi->gtp_fd0); + return rc; + } + + sgi->gtp_fd1u.fd = gsn->fd1u; + sgi->gtp_fd1u.priv_nr = 2; + sgi->gtp_fd1u.data = sgi; + sgi->gtp_fd1u.when = BSC_FD_READ; + sgi->gtp_fd1u.cb = sgsn_gtp_fd_cb; + rc = osmo_fd_register(&sgi->gtp_fd1u); + if (rc < 0) { + osmo_fd_unregister(&sgi->gtp_fd0); + osmo_fd_unregister(&sgi->gtp_fd1c); + return rc; + } + + /* Start GTP re-transmission timer */ + osmo_timer_setup(&sgi->gtp_timer, sgsn_gtp_tmr_cb, sgi); + sgsn_gtp_tmr_start(sgi); + + /* Register callbackcs with libgtp */ + gtp_set_cb_delete_context(gsn, cb_delete_context); + gtp_set_cb_conf(gsn, cb_conf); + gtp_set_cb_recovery2(gsn, cb_recovery2); + gtp_set_cb_data_ind(gsn, cb_data_ind); + gtp_set_cb_unsup_ind(gsn, cb_unsup_ind); + gtp_set_cb_extheader_ind(gsn, cb_extheader_ind); + + return 0; +} diff --git a/src/sgsn/sgsn_main.c b/src/sgsn/sgsn_main.c new file mode 100644 index 000000000..eef5f8f22 --- /dev/null +++ b/src/sgsn/sgsn_main.c @@ -0,0 +1,536 @@ +/* GPRS SGSN Implementation */ + +/* (C) 2010 by Harald Welte + * (C) 2010 by On-Waves + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "../../bscconfig.h" + +#if BUILD_IU +#include +#include +#include +#endif + +#define _GNU_SOURCE +#include + +void *tall_sgsn_ctx; +struct ctrl_handle *g_ctrlh; + +struct gprs_ns_inst *sgsn_nsi; +static int daemonize = 0; +const char *openbsc_copyright = + "Copyright (C) 2010 Harald Welte and On-Waves\r\n" + "License AGPLv3+: GNU AGPL version 3 or later \r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +#define CONFIG_FILE_DEFAULT "osmo-sgsn.cfg" +#define CONFIG_FILE_LEGACY "osmo_sgsn.cfg" + + +struct sgsn_instance *sgsn; + +/* call-back function for the NS protocol */ +static int sgsn_ns_cb(enum gprs_ns_evt event, struct gprs_nsvc *nsvc, + struct msgb *msg, uint16_t bvci) +{ + int rc = 0; + + switch (event) { + case GPRS_NS_EVT_UNIT_DATA: + /* hand the message into the BSSGP implementation */ + rc = bssgp_rcvmsg(msg); + break; + default: + LOGP(DGPRS, LOGL_ERROR, "SGSN: Unknown event %u from NS\n", event); + if (msg) + msgb_free(msg); + rc = -EIO; + break; + } + return rc; +} + +/* call-back function for the BSSGP protocol */ +int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx) +{ + struct osmo_bssgp_prim *bp; + bp = container_of(oph, struct osmo_bssgp_prim, oph); + + switch (oph->sap) { + case SAP_BSSGP_LL: + switch (oph->primitive) { + case PRIM_BSSGP_UL_UD: + return gprs_llc_rcvmsg(oph->msg, bp->tp); + } + break; + case SAP_BSSGP_GMM: + switch (oph->primitive) { + case PRIM_BSSGP_GMM_SUSPEND: + return gprs_gmm_rx_suspend(bp->ra_id, bp->tlli); + case PRIM_BSSGP_GMM_RESUME: + return gprs_gmm_rx_resume(bp->ra_id, bp->tlli, + bp->u.resume.suspend_ref); + } + break; + case SAP_BSSGP_NM: + break; + } + return 0; +} + +static void signal_handler(int signal) +{ + fprintf(stdout, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + case SIGTERM: + osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL); + sleep(1); + exit(0); + break; + case SIGABRT: + /* in case of abort, we want to obtain a talloc report + * and then return to the caller, who will abort the process */ + case SIGUSR1: + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(tall_sgsn_ctx, stderr); + break; + case SIGUSR2: + talloc_report_full(tall_vty_ctx, stderr); + break; + default: + break; + } +} + +/* NSI that BSSGP uses when transmitting on NS */ +extern struct gprs_ns_inst *bssgp_nsi; + +int sgsn_vty_is_config_node(struct vty *vty, int node) +{ + /* So far the SGSN has no nested nodes that need parent node + * declaration, except for the ss7 vty nodes. */ + switch (node) { + case SGSN_NODE: + return 1; + default: +#if BUILD_IU + return osmo_ss7_is_config_node(vty, node); +#else + return 0; +#endif + } +} + +int sgsn_vty_go_parent(struct vty *vty) +{ + /* So far the SGSN has no nested nodes that need parent node + * declaration, except for the ss7 vty nodes. */ +#if BUILD_IU + return osmo_ss7_vty_go_parent(vty); +#else + vty->node = CONFIG_NODE; + vty->index = NULL; + return 0; +#endif +} + +static struct vty_app_info vty_info = { + .name = "OsmoSGSN", + .version = PACKAGE_VERSION, + .go_parent_cb = sgsn_vty_go_parent, + .is_config_node = sgsn_vty_is_config_node, +}; + +static void print_help(void) +{ + printf("Some useful help...\n"); + printf(" -h --help\tthis text\n"); + printf(" -V --version\tPrint the version\n"); + printf(" -D --daemonize\tFork the process into a background daemon\n"); + printf(" -d option --debug\tenable Debugging\n"); + printf(" -s --disable-color\n"); + printf(" -c --config-file\tThe config file to use [%s]\n", CONFIG_FILE_DEFAULT); + printf(" -e --log-level number\tSet a global log level\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"debug", 1, 0, 'd'}, + {"daemonize", 0, 0, 'D'}, + {"config-file", 1, 0, 'c'}, + {"disable-color", 0, 0, 's'}, + {"timestamp", 0, 0, 'T'}, + { "version", 0, 0, 'V' }, + {"log-level", 1, 0, 'e'}, + {NULL, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hd:Dc:sTVe:", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + //print_usage(); + print_help(); + exit(0); + case 's': + log_set_use_color(osmo_stderr_target, 0); + break; + case 'd': + log_parse_category_mask(osmo_stderr_target, optarg); + break; + case 'D': + daemonize = 1; + break; + case 'c': + osmo_talloc_replace_string(sgsn, &sgsn->config_file, optarg); + break; + case 'T': + log_set_print_timestamp(osmo_stderr_target, 1); + break; + case 'V': + print_version(1); + exit(0); + break; + case 'e': + log_set_log_level(osmo_stderr_target, atoi(optarg)); + break; + default: + /* ignore */ + break; + } + } +} + +/* default categories */ +static struct log_info_cat gprs_categories[] = { + [DMM] = { + .name = "DMM", + .description = "Layer3 Mobility Management (MM)", + .color = "\033[1;33m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DPAG] = { + .name = "DPAG", + .description = "Paging Subsystem", + .color = "\033[1;38m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DMEAS] = { + .name = "DMEAS", + .description = "Radio Measurement Processing", + .enabled = 0, .loglevel = LOGL_NOTICE, + }, + [DREF] = { + .name = "DREF", + .description = "Reference Counting", + .enabled = 0, .loglevel = LOGL_NOTICE, + }, + [DGPRS] = { + .name = "DGPRS", + .description = "GPRS Packet Service", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DNS] = { + .name = "DNS", + .description = "GPRS Network Service (NS)", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DBSSGP] = { + .name = "DBSSGP", + .description = "GPRS BSS Gateway Protocol (BSSGP)", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DLLC] = { + .name = "DLLC", + .description = "GPRS Logical Link Control Protocol (LLC)", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DSNDCP] = { + .name = "DSNDCP", + .description = "GPRS Sub-Network Dependent Control Protocol (SNDCP)", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DRANAP] = { + .name = "DRANAP", + .description = "RAN Application Part (RANAP)", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DSUA] = { + .name = "DSUA", + .description = "SCCP User Adaptation (SUA)", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DSLHC] = { + .name = "DSLHC", + .description = "RFC1144 TCP/IP Header compression (SLHC)", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DV42BIS] = { + .name = "DV42BIS", + .description = "V.42bis data compression (SNDCP)", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DGTP] = { + .name = "DGTP", + .description = "GPRS Tunnelling Protocol (GTP)", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, +}; + +static const struct log_info gprs_log_info = { + .filter_fn = gprs_log_filter_fn, + .cat = gprs_categories, + .num_cat = ARRAY_SIZE(gprs_categories), +}; + +static bool file_exists(const char *path) +{ + struct stat sb; + return stat(path, &sb) ? false : true; +} + +int main(int argc, char **argv) +{ + int rc; +#if BUILD_IU + struct osmo_sccp_instance *sccp; +#endif + + srand(time(NULL)); + tall_sgsn_ctx = talloc_named_const(NULL, 0, "osmo_sgsn"); + sgsn = sgsn_instance_alloc(tall_sgsn_ctx); + msgb_talloc_ctx_init(tall_sgsn_ctx, 0); + vty_info.tall_ctx = tall_sgsn_ctx; + + signal(SIGINT, &signal_handler); + signal(SIGTERM, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + + osmo_init_ignore_signals(); + osmo_init_logging2(tall_sgsn_ctx, &gprs_log_info); + osmo_stats_init(tall_sgsn_ctx); + + vty_info.copyright = openbsc_copyright; + vty_init(&vty_info); + logging_vty_add_cmds(); + osmo_talloc_vty_add_cmds(); + osmo_stats_vty_add_cmds(); + sgsn_vty_init(&sgsn->cfg); + ctrl_vty_init(tall_sgsn_ctx); + +#if BUILD_IU + osmo_ss7_init(); + osmo_ss7_vty_init_asp(tall_sgsn_ctx); + osmo_sccp_vty_init(); +#endif + + handle_options(argc, argv); + + /* Backwards compatibility: for years, the default config file name was + * osmo_sgsn.cfg. All other Osmocom programs use osmo-*.cfg with a + * dash. To be able to use the new config file name without breaking + * previous setups that might rely on the legacy default config file + * name, we need to look for the old config file if no -c option was + * passed AND no file exists with the new default file name. */ + if (!sgsn->config_file) { + /* No -c option was passed */ + if (file_exists(CONFIG_FILE_LEGACY) + && !file_exists(CONFIG_FILE_DEFAULT)) + osmo_talloc_replace_string(sgsn, &sgsn->config_file, CONFIG_FILE_LEGACY); + else + osmo_talloc_replace_string(sgsn, &sgsn->config_file, CONFIG_FILE_DEFAULT); + } + + rate_ctr_init(tall_sgsn_ctx); + + gprs_ns_set_log_ss(DNS); + bssgp_set_log_ss(DBSSGP); + + sgsn_nsi = gprs_ns_instantiate(&sgsn_ns_cb, tall_sgsn_ctx); + if (!sgsn_nsi) { + LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n"); + exit(1); + } + bssgp_nsi = sgsn->cfg.nsi = sgsn_nsi; + + gprs_llc_init("/usr/local/lib/osmocom/crypt/"); + sgsn_rate_ctr_init(); + sgsn_inst_init(sgsn); + + gprs_ns_vty_init(bssgp_nsi); + bssgp_vty_init(); + gprs_llc_vty_init(); + gprs_sndcp_vty_init(); + sgsn_auth_init(sgsn); + sgsn_cdr_init(sgsn); + /* FIXME: register signal handler for SS_L_NS */ + + rc = sgsn_parse_config(sgsn->config_file); + if (rc < 0) { + LOGP(DGPRS, LOGL_FATAL, "Error in config file\n"); + exit(2); + } + + /* start telnet after reading config for vty_get_bind_addr() */ + rc = telnet_init_dynif(tall_sgsn_ctx, NULL, + vty_get_bind_addr(), OSMO_VTY_PORT_SGSN); + if (rc < 0) + exit(1); + + /* start control interface after reading config for + * ctrl_vty_get_bind_addr() */ + g_ctrlh = ctrl_interface_setup_dynip(NULL, ctrl_vty_get_bind_addr(), + OSMO_CTRL_PORT_SGSN, NULL); + if (!g_ctrlh) { + LOGP(DGPRS, LOGL_ERROR, "Failed to create CTRL interface.\n"); + exit(1); + } + + if (sgsn_ctrl_cmds_install() != 0) { + LOGP(DGPRS, LOGL_ERROR, "Failed to install CTRL commands.\n"); + exit(1); + } + + + rc = sgsn_gtp_init(sgsn); + if (rc) { + LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on GTP socket\n"); + exit(2); + } else + LOGP(DGPRS, LOGL_NOTICE, "libGTP v%s initialized\n", gtp_version()); + + rc = gprs_subscr_init(sgsn); + if (rc < 0) { + LOGP(DGPRS, LOGL_FATAL, "Cannot set up subscriber management\n"); + exit(2); + } + + rc = gprs_ns_nsip_listen(sgsn_nsi); + if (rc < 0) { + LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on NSIP socket\n"); + exit(2); + } + + rc = gprs_ns_frgre_listen(sgsn_nsi); + if (rc < 0) { + LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen GRE " + "socket. Do you have CAP_NET_RAW?\n"); + exit(2); + } + + if (sgsn->cfg.dynamic_lookup) { + if (sgsn_ares_init(sgsn) != 0) { + LOGP(DGPRS, LOGL_FATAL, + "Failed to initialize c-ares(%d)\n", rc); + exit(4); + } + } + +#if BUILD_IU + /* Note that these are mostly defaults and can be overriden from the VTY */ + sccp = osmo_sccp_simple_client_on_ss7_id(tall_sgsn_ctx, + sgsn->cfg.iu.cs7_instance, + "OsmoSGSN", + (23 << 3) + 4, + OSMO_SS7_ASP_PROT_M3UA, + 0, NULL, + 0, "127.0.0.1"); + if (!sccp) { + printf("Setting up SCCP client failed.\n"); + return 8; + } + + ranap_iu_init(tall_sgsn_ctx, DRANAP, "OsmoSGSN-IuPS", sccp, gsm0408_gprs_rcvmsg_iu, sgsn_ranap_iu_event); +#endif + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + while (1) { + rc = osmo_select_main(0); + if (rc < 0) + exit(3); + } + + /* not reached */ + exit(0); +} diff --git a/src/sgsn/sgsn_vty.c b/src/sgsn/sgsn_vty.c new file mode 100644 index 000000000..184ece761 --- /dev/null +++ b/src/sgsn/sgsn_vty.c @@ -0,0 +1,1504 @@ +/* + * (C) 2010-2016 by Harald Welte + * (C) 2010 by On-Waves + * (C) 2015 by Holger Hans Peter Freyther + * 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 . + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "../../bscconfig.h" + +#ifdef BUILD_IU +#include +#endif + +extern void *tall_sgsn_ctx; + +static struct sgsn_config *g_cfg = NULL; + +const struct value_string sgsn_auth_pol_strs[] = { + { SGSN_AUTH_POLICY_OPEN, "accept-all" }, + { SGSN_AUTH_POLICY_CLOSED, "closed" }, + { SGSN_AUTH_POLICY_ACL_ONLY, "acl-only" }, + { SGSN_AUTH_POLICY_REMOTE, "remote" }, + { 0, NULL } +}; + +/* Section 11.2.2 / Table 11.3a GPRS Mobility management timers – MS side */ +#define GSM0408_T3312_SECS (10*60) /* periodic RAU interval, default 54min */ + +/* Section 11.2.2 / Table 11.4 MM timers netwokr side */ +#define GSM0408_T3322_SECS 6 /* DETACH_REQ -> DETACH_ACC */ +#define GSM0408_T3350_SECS 6 /* waiting for ATT/RAU/TMSI COMPL */ +#define GSM0408_T3360_SECS 6 /* waiting for AUTH/CIPH RESP */ +#define GSM0408_T3370_SECS 6 /* waiting for ID RESP */ + +/* Section 11.2.2 / Table 11.4a MM timers network side */ +#define GSM0408_T3313_SECS 30 /* waiting for paging response */ +#define GSM0408_T3314_SECS 44 /* force to STBY on expiry, Ready timer */ +#define GSM0408_T3316_SECS 44 + +/* Section 11.3 / Table 11.2d Timers of Session Management - network side */ +#define GSM0408_T3385_SECS 8 /* wait for ACT PDP CTX REQ */ +#define GSM0408_T3386_SECS 8 /* wait for MODIFY PDP CTX ACK */ +#define GSM0408_T3395_SECS 8 /* wait for DEACT PDP CTX ACK */ +#define GSM0408_T3397_SECS 8 /* wait for DEACT AA PDP CTX ACK */ + + +static struct osmo_tdef sgsn_T_defs[] = { + { .T=3312, .default_val=GSM0408_T3312_SECS, .desc="Periodic RA Update timer (s)" }, + { .T=3313, .default_val=GSM0408_T3313_SECS, .desc="Waiting for paging response timer (s)" }, + { .T=3314, .default_val=GSM0408_T3314_SECS, .desc="READY timer. Force to STANDBY on expiry timer (s)" }, + { .T=3316, .default_val=GSM0408_T3316_SECS, .desc="AA-Ready timer (s)" }, + { .T=3322, .default_val=GSM0408_T3322_SECS, .desc="Detach request -> accept timer (s)" }, + { .T=3350, .default_val=GSM0408_T3350_SECS, .desc="Waiting for ATT/RAU/TMSI_COMPL timer (s)" }, + { .T=3360, .default_val=GSM0408_T3360_SECS, .desc="Waiting for AUTH/CIPH response timer (s)" }, + { .T=3370, .default_val=GSM0408_T3370_SECS, .desc="Waiting for IDENTITY response timer (s)" }, + { .T=3385, .default_val=GSM0408_T3385_SECS, .desc="Wait for ACT PDP CTX REQ timer (s)" }, + { .T=3386, .default_val=GSM0408_T3386_SECS, .desc="Wait for MODIFY PDP CTX ACK timer (s)" }, + { .T=3395, .default_val=GSM0408_T3395_SECS, .desc="Wait for DEACT PDP CTX ACK timer (s)" }, + { .T=3397, .default_val=GSM0408_T3397_SECS, .desc="Wait for DEACT AA PDP CTX ACK timer (s)" }, + {} +}; + +DEFUN(show_timer, show_timer_cmd, + "show timer " OSMO_TDEF_VTY_ARG_T_OPTIONAL, + SHOW_STR "Show timers\n" + OSMO_TDEF_VTY_DOC_T) +{ + const char *T_arg = argc > 0 ? argv[0] : NULL; + return osmo_tdef_vty_show_cmd(vty, g_cfg->T_defs, T_arg, NULL); +} + +DEFUN(cfg_sgsn_timer, cfg_sgsn_timer_cmd, + "timer " OSMO_TDEF_VTY_ARG_SET_OPTIONAL, + "Configure or show timers\n" + OSMO_TDEF_VTY_DOC_SET) +{ + /* If any arguments are missing, redirect to 'show' */ + if (argc < 2) + return show_timer(self, vty, argc, argv); + return osmo_tdef_vty_set_cmd(vty, g_cfg->T_defs, argv); +} + +char *gprs_pdpaddr2str(uint8_t *pdpa, uint8_t len) +{ + static char str[INET6_ADDRSTRLEN + 10]; + + if (!pdpa || len < 2) + return "none"; + + switch (pdpa[0] & 0x0f) { + case PDP_TYPE_ORG_IETF: + switch (pdpa[1]) { + case PDP_TYPE_N_IETF_IPv4: + if (len < 2 + 4) + break; + strcpy(str, "IPv4 "); + inet_ntop(AF_INET, pdpa+2, str+5, sizeof(str)-5); + return str; + case PDP_TYPE_N_IETF_IPv6: + if (len < 2 + 8) + break; + strcpy(str, "IPv6 "); + inet_ntop(AF_INET6, pdpa+2, str+5, sizeof(str)-5); + return str; + default: + break; + } + break; + case PDP_TYPE_ORG_ETSI: + if (pdpa[1] == PDP_TYPE_N_ETSI_PPP) + return "PPP"; + break; + default: + break; + } + + return "invalid"; +} + +static struct cmd_node sgsn_node = { + SGSN_NODE, + "%s(config-sgsn)# ", + 1, +}; + +static int config_write_sgsn(struct vty *vty) +{ + struct sgsn_ggsn_ctx *gctx; + struct imsi_acl_entry *acl; + struct apn_ctx *actx; + struct ares_addr_node *server; + + vty_out(vty, "sgsn%s", VTY_NEWLINE); + + vty_out(vty, " gtp local-ip %s%s", + inet_ntoa(g_cfg->gtp_listenaddr.sin_addr), VTY_NEWLINE); + + llist_for_each_entry(gctx, &sgsn_ggsn_ctxts, list) { + if (gctx->id == UINT32_MAX) + continue; + + vty_out(vty, " ggsn %u remote-ip %s%s", gctx->id, + inet_ntoa(gctx->remote_addr), VTY_NEWLINE); + vty_out(vty, " ggsn %u gtp-version %u%s", gctx->id, + gctx->gtp_version, VTY_NEWLINE); + if (gctx->echo_interval) + vty_out(vty, " ggsn %u echo-interval %u%s", + gctx->id, gctx->echo_interval, VTY_NEWLINE); + else + vty_out(vty, " ggsn %u no echo-interval%s", + gctx->id, VTY_NEWLINE); + } + + if (sgsn->cfg.dynamic_lookup) + vty_out(vty, " ggsn dynamic%s", VTY_NEWLINE); + + for (server = sgsn->ares_servers; server; server = server->next) + vty_out(vty, " grx-dns-add %s%s", inet_ntoa(server->addr.addr4), VTY_NEWLINE); + + if (g_cfg->cipher != GPRS_ALGO_GEA0) + vty_out(vty, " encryption %s%s", + get_value_string(gprs_cipher_names, g_cfg->cipher), + VTY_NEWLINE); + if (g_cfg->sgsn_ipa_name) + vty_out(vty, " gsup ipa-name %s%s", g_cfg->sgsn_ipa_name, VTY_NEWLINE); + if (g_cfg->gsup_server_addr.sin_addr.s_addr) + vty_out(vty, " gsup remote-ip %s%s", + inet_ntoa(g_cfg->gsup_server_addr.sin_addr), VTY_NEWLINE); + if (g_cfg->gsup_server_port) + vty_out(vty, " gsup remote-port %d%s", + g_cfg->gsup_server_port, VTY_NEWLINE); + if (g_cfg->auth_policy == SGSN_AUTH_POLICY_REMOTE && !g_cfg->require_authentication) + vty_out(vty, " authentication optional%s", VTY_NEWLINE); + vty_out(vty, " auth-policy %s%s", + get_value_string(sgsn_auth_pol_strs, g_cfg->auth_policy), + VTY_NEWLINE); + + vty_out(vty, " gsup oap-id %d%s", + (int)g_cfg->oap.client_id, VTY_NEWLINE); + if (g_cfg->oap.secret_k_present != 0) + vty_out(vty, " gsup oap-k %s%s", + osmo_hexdump_nospc(g_cfg->oap.secret_k, sizeof(g_cfg->oap.secret_k)), + VTY_NEWLINE); + if (g_cfg->oap.secret_opc_present != 0) + vty_out(vty, " gsup oap-opc %s%s", + osmo_hexdump_nospc(g_cfg->oap.secret_opc, sizeof(g_cfg->oap.secret_opc)), + VTY_NEWLINE); + + llist_for_each_entry(acl, &g_cfg->imsi_acl, list) + vty_out(vty, " imsi-acl add %s%s", acl->imsi, VTY_NEWLINE); + + if (llist_empty(&sgsn_apn_ctxts)) + vty_out(vty, " ! apn * ggsn 0%s", VTY_NEWLINE); + llist_for_each_entry(actx, &sgsn_apn_ctxts, list) { + if (strlen(actx->imsi_prefix) > 0) + vty_out(vty, " apn %s imsi-prefix %s ggsn %u%s", + actx->name, actx->imsi_prefix, actx->ggsn->id, + VTY_NEWLINE); + else + vty_out(vty, " apn %s ggsn %u%s", actx->name, + actx->ggsn->id, VTY_NEWLINE); + } + + if (g_cfg->cdr.filename) + vty_out(vty, " cdr filename %s%s", g_cfg->cdr.filename, VTY_NEWLINE); + else + vty_out(vty, " no cdr filename%s", VTY_NEWLINE); + if (g_cfg->cdr.trap) + vty_out(vty, " cdr trap%s", VTY_NEWLINE); + else + vty_out(vty, " no cdr trap%s", VTY_NEWLINE); + vty_out(vty, " cdr interval %d%s", g_cfg->cdr.interval, VTY_NEWLINE); + + osmo_tdef_vty_write(vty, g_cfg->T_defs, " timer "); + + if (g_cfg->pcomp_rfc1144.active) { + vty_out(vty, " compression rfc1144 active slots %d%s", + g_cfg->pcomp_rfc1144.s01 + 1, VTY_NEWLINE); + } else if (g_cfg->pcomp_rfc1144.passive) { + vty_out(vty, " compression rfc1144 passive%s", VTY_NEWLINE); + } else + vty_out(vty, " no compression rfc1144%s", VTY_NEWLINE); + + if (g_cfg->dcomp_v42bis.active && g_cfg->dcomp_v42bis.p0 == 1) { + vty_out(vty, + " compression v42bis active direction sgsn codewords %d strlen %d%s", + g_cfg->dcomp_v42bis.p1, g_cfg->dcomp_v42bis.p2, + VTY_NEWLINE); + } else if (g_cfg->dcomp_v42bis.active && g_cfg->dcomp_v42bis.p0 == 2) { + vty_out(vty, + " compression v42bis active direction ms codewords %d strlen %d%s", + g_cfg->dcomp_v42bis.p1, g_cfg->dcomp_v42bis.p2, + VTY_NEWLINE); + } else if (g_cfg->dcomp_v42bis.active && g_cfg->dcomp_v42bis.p0 == 3) { + vty_out(vty, + " compression v42bis active direction both codewords %d strlen %d%s", + g_cfg->dcomp_v42bis.p1, g_cfg->dcomp_v42bis.p2, + VTY_NEWLINE); + } else if (g_cfg->dcomp_v42bis.passive) { + vty_out(vty, " compression v42bis passive%s", VTY_NEWLINE); + } else + vty_out(vty, " no compression v42bis%s", VTY_NEWLINE); + +#ifdef BUILD_IU + vty_out(vty, " cs7-instance-iu %u%s", g_cfg->iu.cs7_instance, + VTY_NEWLINE); + ranap_iu_vty_config_write(vty, " "); +#endif + + return CMD_SUCCESS; +} + +#define SGSN_STR "Configure the SGSN\n" +#define GGSN_STR "Configure the GGSN information\n" + +DEFUN(cfg_sgsn, cfg_sgsn_cmd, + "sgsn", + SGSN_STR) +{ + vty->node = SGSN_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_sgsn_bind_addr, cfg_sgsn_bind_addr_cmd, + "gtp local-ip A.B.C.D", + "GTP Parameters\n" + "Set the IP address for the local GTP bind for the Gp interface (towards the GGSNs)." + " Note: in case you would like to run the GGSN on the same machine as the SGSN, you can not run" + " both on the same IP address, since both sides are specified to use the same GTP port numbers" + " (" OSMO_STRINGIFY_VAL(GTP1C_PORT) " and " OSMO_STRINGIFY_VAL(GTP1U_PORT) ")." + " For example, you could use 127.0.0.1 for the SGSN and 127.0.0.2 for the GGSN in such" + " situations.\n" + "IPv4 Address\n") +{ + inet_aton(argv[0], &g_cfg->gtp_listenaddr.sin_addr); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ggsn_remote_ip, cfg_ggsn_remote_ip_cmd, + "ggsn <0-255> remote-ip A.B.C.D", + GGSN_STR "GGSN Number\n" + "Configure this static GGSN to use the specified remote IP address.\n" + "IPv4 Address\n") +{ + uint32_t id = atoi(argv[0]); + struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id); + + inet_aton(argv[1], &ggc->remote_addr); + + return CMD_SUCCESS; +} + +#if 0 +DEFUN(cfg_ggsn_remote_port, cfg_ggsn_remote_port_cmd, + "ggsn <0-255> remote-port <0-65535>", + "") +{ + uint32_t id = atoi(argv[0]); + struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id); + uint16_t port = atoi(argv[1]); + +} +#endif + +DEFUN(cfg_ggsn_gtp_version, cfg_ggsn_gtp_version_cmd, + "ggsn <0-255> gtp-version (0|1)", + GGSN_STR "GGSN Number\n" "GTP Version\n" + "Version 0\n" "Version 1\n") +{ + uint32_t id = atoi(argv[0]); + struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id); + + if (atoi(argv[1])) + ggc->gtp_version = 1; + else + ggc->gtp_version = 0; + + return CMD_SUCCESS; +} + +/* Seee 3GPP TS 29.060 section 7.2.1 */ +DEFUN(cfg_ggsn_echo_interval, cfg_ggsn_echo_interval_cmd, + "ggsn <0-255> 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") +{ + uint32_t id = atoi(argv[0]); + struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id); + + ggc->echo_interval = atoi(argv[1]); + + if (ggc->echo_interval < 60) + vty_out(vty, "%% 3GPP TS 29.060 section 7.2.1 states interval should " \ + "not be lower than 60 seconds, use this value for " \ + "testing purposes only!%s", VTY_NEWLINE); + + sgsn_ggsn_ctx_check_echo_timer(ggc); + return CMD_SUCCESS; +} + +DEFUN(cfg_ggsn_no_echo_interval, cfg_ggsn_no_echo_interval_cmd, + "ggsn <0-255> no echo-interval", + GGSN_STR "GGSN Number\n" + NO_STR "Send an echo request to this static GGSN every interval.\n") +{ + uint32_t id = atoi(argv[0]); + struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id); + + ggc->echo_interval = 0; + sgsn_ggsn_ctx_check_echo_timer(ggc); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ggsn_dynamic_lookup, cfg_ggsn_dynamic_lookup_cmd, + "ggsn dynamic", + GGSN_STR + "Enable dynamic resolving of GGSNs based on DNS resolving the APN name like in a GRX-style setup." + " Changing this setting requires a restart.\n") +{ + sgsn->cfg.dynamic_lookup = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_grx_ggsn, cfg_grx_ggsn_cmd, + "grx-dns-add A.B.C.D", + "Use the specified IP address for DNS-resolving the AP names to GGSN IP addresses\n" + "IPv4 address\n") +{ + struct ares_addr_node *node = talloc_zero(tall_sgsn_ctx, struct ares_addr_node); + node->family = AF_INET; + inet_aton(argv[0], &node->addr.addr4); + + node->next = sgsn->ares_servers; + sgsn->ares_servers = node; + return CMD_SUCCESS; +} + +#define APN_STR "Configure the information per APN\n" +#define APN_GW_STR "The APN gateway name optionally prefixed by '*' (wildcard)\n" + +static int add_apn_ggsn_mapping(struct vty *vty, const char *apn_str, + const char *imsi_prefix, int ggsn_id) +{ + struct apn_ctx *actx; + struct sgsn_ggsn_ctx *ggsn; + + ggsn = sgsn_ggsn_ctx_by_id(ggsn_id); + if (ggsn == NULL) { + vty_out(vty, "%% a GGSN with id %d has not been defined%s", + ggsn_id, VTY_NEWLINE); + return CMD_WARNING; + } + + actx = sgsn_apn_ctx_find_alloc(apn_str, imsi_prefix); + if (!actx) { + vty_out(vty, "%% unable to create APN context for %s/%s%s", + apn_str, imsi_prefix, VTY_NEWLINE); + return CMD_WARNING; + } + + actx->ggsn = ggsn; + + return CMD_SUCCESS; +} + +DEFUN(cfg_apn_ggsn, cfg_apn_ggsn_cmd, + "apn APNAME ggsn <0-255>", + APN_STR APN_GW_STR + "Select the GGSN to use for the given APN gateway prefix\n" + "The GGSN id") +{ + + return add_apn_ggsn_mapping(vty, argv[0], "", atoi(argv[1])); +} + +DEFUN(cfg_apn_imsi_ggsn, cfg_apn_imsi_ggsn_cmd, + "apn APNAME imsi-prefix IMSIPRE ggsn <0-255>", + APN_STR APN_GW_STR + "Select the GGSN to use for the given APN gateway prefix if and only if the IMSI matches the" + " given prefix.\n" + "An IMSI prefix\n" + "Select the GGSN to use when APN gateway and IMSI prefix match\n" + "The GGSN id") +{ + + return add_apn_ggsn_mapping(vty, argv[0], argv[1], atoi(argv[2])); +} + +const struct value_string gprs_mm_st_strs[] = { + { GMM_DEREGISTERED, "DEREGISTERED" }, + { GMM_COMMON_PROC_INIT, "COMMON PROCEDURE (INIT)" }, + { GMM_REGISTERED_NORMAL, "REGISTERED (NORMAL)" }, + { GMM_REGISTERED_SUSPENDED, "REGISTERED (SUSPENDED)" }, + { GMM_DEREGISTERED_INIT, "DEREGISTERED (INIT)" }, + { 0, NULL } +}; + +char *sgsn_gtp_ntoa(struct ul16_t *ul) +{ + struct in_addr ia; + + if (gsna2in_addr(&ia, ul) != 0) + return "UNKNOWN"; + + return inet_ntoa(ia); +} + +static void vty_dump_pdp(struct vty *vty, const char *pfx, + struct sgsn_pdp_ctx *pdp) +{ + const char *imsi = pdp->mm ? pdp->mm->imsi : "(detaching)"; + vty_out(vty, "%sPDP Context IMSI: %s, SAPI: %u, NSAPI: %u, TI: %u%s", + pfx, imsi, pdp->sapi, pdp->nsapi, pdp->ti, VTY_NEWLINE); + if (pdp->lib) { + char apnbuf[APN_MAXLEN + 1]; + vty_out(vty, "%s APN: %s%s", pfx, + osmo_apn_to_str(apnbuf, pdp->lib->apn_use.v, pdp->lib->apn_use.l), + VTY_NEWLINE); + vty_out(vty, "%s PDP Address: %s%s", pfx, + gprs_pdpaddr2str(pdp->lib->eua.v, pdp->lib->eua.l), + VTY_NEWLINE); + vty_out(vty, "%s GTPv%d Local Control(%s / TEIC: 0x%08x) ", pfx, pdp->lib->version, + sgsn_gtp_ntoa(&pdp->lib->gsnlc), pdp->lib->teic_own); + vty_out(vty, "Data(%s / TEID: 0x%08x)%s", + sgsn_gtp_ntoa(&pdp->lib->gsnlu), pdp->lib->teid_own, VTY_NEWLINE); + vty_out(vty, "%s GTPv%d Remote Control(%s / TEIC: 0x%08x) ", pfx, pdp->lib->version, + sgsn_gtp_ntoa(&pdp->lib->gsnrc), pdp->lib->teic_gn); + vty_out(vty, "Data(%s / TEID: 0x%08x)%s", + sgsn_gtp_ntoa(&pdp->lib->gsnru), pdp->lib->teid_gn, VTY_NEWLINE); + } + + vty_out_rate_ctr_group(vty, " ", pdp->ctrg); +} + +static void vty_dump_mmctx(struct vty *vty, const char *pfx, + struct sgsn_mm_ctx *mm, int pdp) +{ + uint32_t id = 0; + const char *mm_state_name = NULL; + + switch(mm->ran_type) { + case MM_CTX_T_UTRAN_Iu: +#if BUILD_IU + id = mm->iu.ue_ctx->conn_id; + mm_state_name = osmo_fsm_inst_state_name(mm->iu.mm_state_fsm); +#endif + break; + case MM_CTX_T_GERAN_Gb: + id = mm->gb.tlli; + mm_state_name = osmo_fsm_inst_state_name(mm->gb.mm_state_fsm); + break; + } + + vty_out(vty, "%sMM Context for IMSI %s, IMEI %s, P-TMSI %08x%s", + pfx, mm->imsi, mm->imei, mm->p_tmsi, VTY_NEWLINE); + vty_out(vty, "%s MSISDN: %s, TLLI: %08x%s HLR: %s", + pfx, mm->msisdn, id, mm->hlr, VTY_NEWLINE); + vty_out(vty, "%s GMM State: %s, Routeing Area: %s, Cell ID: %u%s", + pfx, get_value_string(gprs_mm_st_strs, mm->gmm_state), + osmo_rai_name(&mm->ra), mm->gb.cell_id, VTY_NEWLINE); + vty_out(vty, "%s MM State: %s, RAN Type: %s%s", pfx, mm_state_name, + get_value_string(sgsn_ran_type_names, mm->ran_type), VTY_NEWLINE); + + vty_out_rate_ctr_group(vty, " ", mm->ctrg); + + if (pdp) { + struct sgsn_pdp_ctx *pdp; + + llist_for_each_entry(pdp, &mm->pdp_list, list) + vty_dump_pdp(vty, " ", pdp); + } +} + +DEFUN(show_sgsn, show_sgsn_cmd, "show sgsn", + SHOW_STR "Display information about the SGSN") +{ + if (sgsn->gsup_client) { + struct ipa_client_conn *link = sgsn->gsup_client->link; + vty_out(vty, + " Remote authorization: %sconnected to %s:%d via GSUP%s", + sgsn->gsup_client->is_connected ? "" : "not ", + link->addr, link->port, + VTY_NEWLINE); + } + if (sgsn->gsn) + vty_out(vty, " GSN: signalling %s, user traffic %s%s", + inet_ntoa(sgsn->gsn->gsnc), inet_ntoa(sgsn->gsn->gsnu), VTY_NEWLINE); + + /* FIXME: statistics */ + return CMD_SUCCESS; +} + +#define MMCTX_STR "MM Context\n" +#define INCLUDE_PDP_STR "Include PDP Context Information\n" + +#if 0 +DEFUN(show_mmctx_tlli, show_mmctx_tlli_cmd, + "show mm-context tlli HEX [pdp]", + SHOW_STR MMCTX_STR "Identify by TLLI\n" "TLLI\n" INCLUDE_PDP_STR) +{ + uint32_t tlli; + struct sgsn_mm_ctx *mm; + + tlli = strtoul(argv[0], NULL, 16); + mm = sgsn_mm_ctx_by_tlli(tlli); + if (!mm) { + vty_out(vty, "No MM context for TLLI %08x%s", + tlli, VTY_NEWLINE); + return CMD_WARNING; + } + vty_dump_mmctx(vty, "", mm, argv[1] ? 1 : 0); + return CMD_SUCCESS; +} +#endif + +DEFUN(swow_mmctx_imsi, show_mmctx_imsi_cmd, + "show mm-context imsi IMSI [pdp]", + SHOW_STR MMCTX_STR "Identify by IMSI\n" "IMSI of the MM Context\n" + INCLUDE_PDP_STR) +{ + struct sgsn_mm_ctx *mm; + + mm = sgsn_mm_ctx_by_imsi(argv[0]); + if (!mm) { + vty_out(vty, "No MM context for IMSI %s%s", + argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + vty_dump_mmctx(vty, "", mm, argv[1] ? 1 : 0); + return CMD_SUCCESS; +} + +DEFUN(swow_mmctx_all, show_mmctx_all_cmd, + "show mm-context all [pdp]", + SHOW_STR MMCTX_STR "All MM Contexts\n" INCLUDE_PDP_STR) +{ + struct sgsn_mm_ctx *mm; + + llist_for_each_entry(mm, &sgsn_mm_ctxts, list) + vty_dump_mmctx(vty, "", mm, argv[0] ? 1 : 0); + + return CMD_SUCCESS; +} + +DEFUN(show_pdpctx_all, show_pdpctx_all_cmd, + "show pdp-context all", + SHOW_STR "Display information on PDP Context\n" "Show everything\n") +{ + struct sgsn_pdp_ctx *pdp; + + llist_for_each_entry(pdp, &sgsn_pdp_ctxts, g_list) + vty_dump_pdp(vty, "", pdp); + + return CMD_SUCCESS; +} + + +DEFUN(imsi_acl, cfg_imsi_acl_cmd, + "imsi-acl (add|del) IMSI", + "Access Control List of foreign IMSIs\n" + "Add IMSI to ACL\n" + "Remove IMSI from ACL\n" + "IMSI of subscriber\n") +{ + char imsi_sanitized[GSM23003_IMSI_MAX_DIGITS + 1]; + const char *op = argv[0]; + const char *imsi = imsi_sanitized; + size_t len = strnlen(argv[1], GSM23003_IMSI_MAX_DIGITS + 1); + int rc; + + memset(imsi_sanitized, '0', GSM23003_IMSI_MAX_DIGITS); + imsi_sanitized[GSM23003_IMSI_MAX_DIGITS] = '\0'; + + /* Sanitize IMSI */ + if (len > GSM23003_IMSI_MAX_DIGITS) { + vty_out(vty, "%% IMSI (%s) too long (max %u digits) -- ignored!%s", + argv[1], GSM23003_IMSI_MAX_DIGITS, VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_strlcpy(imsi_sanitized + GSM23003_IMSI_MAX_DIGITS - len, argv[1], + sizeof(imsi_sanitized) - (GSM23003_IMSI_MAX_DIGITS - len)); + + if (!strcmp(op, "add")) + rc = sgsn_acl_add(imsi, g_cfg); + else + rc = sgsn_acl_del(imsi, g_cfg); + + if (rc < 0) { + vty_out(vty, "%% unable to %s ACL%s", op, VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_encrypt, cfg_encrypt_cmd, + "encryption (GEA0|GEA1|GEA2|GEA3|GEA4)", + "Set encryption algorithm for SGSN\n" + "Use GEA0 (no encryption)\n" + "Use GEA1\nUse GEA2\nUse GEA3\nUse GEA4\n") +{ + enum gprs_ciph_algo c = get_string_value(gprs_cipher_names, argv[0]); + if (c != GPRS_ALGO_GEA0) { + if (!gprs_cipher_supported(c)) { + vty_out(vty, "%% cipher %s is unsupported in current version%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + if (!g_cfg->require_authentication) { + vty_out(vty, "%% unable to use encryption %s without authentication: please adjust auth-policy%s", + argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + } + + g_cfg->cipher = c; + + return CMD_SUCCESS; +} + +DEFUN(cfg_authentication, cfg_authentication_cmd, + "authentication (optional|required)", + "Whether to enforce MS authentication in GERAN (only with auth-policy remote)\n" + "Allow MS to attach via GERAN without authentication (default and only possible value for non-remote auth-policy)\n" + "Always require authentication (only available for auth-policy remote, default with that auth-policy)\n") +{ + int required = (argv[0][0] == 'r'); + + if (vty->type != VTY_FILE) { + if (g_cfg->auth_policy != SGSN_AUTH_POLICY_REMOTE && required) { + vty_out(vty, "%% Authentication is not possible without HLR, " + "consider setting 'auth-policy' to 'remote'%s", + VTY_NEWLINE); + return CMD_WARNING; + } + } + + g_cfg->require_authentication = required; + return CMD_SUCCESS; +} + +DEFUN(cfg_auth_policy, cfg_auth_policy_cmd, + "auth-policy (accept-all|closed|acl-only|remote)", + "Configure the Authorization policy of the SGSN. This setting determines which subscribers are" + " permitted to register to the network.\n" + "Accept all IMSIs (DANGEROUS)\n" + "Accept only home network subscribers or those in the ACL\n" + "Accept only subscribers in the ACL\n" + "Use remote subscription data only (HLR)\n") +{ + int val = get_string_value(sgsn_auth_pol_strs, argv[0]); + OSMO_ASSERT(val >= SGSN_AUTH_POLICY_OPEN && val <= SGSN_AUTH_POLICY_REMOTE); + g_cfg->auth_policy = val; + g_cfg->require_update_location = (val == SGSN_AUTH_POLICY_REMOTE); + + return CMD_SUCCESS; +} + +/* Subscriber */ +#include + +static void subscr_dump_full_vty(struct vty *vty, struct gprs_subscr *gsub, int pending) +{ +#if 0 + char expire_time[200]; +#endif + struct gsm_auth_tuple *at; + int at_idx; + struct sgsn_subscriber_pdp_data *pdp; + + vty_out(vty, " Authorized: %d%s", + gsub->authorized, VTY_NEWLINE); + vty_out(vty, " LAC: %d/0x%x%s", + gsub->lac, gsub->lac, VTY_NEWLINE); + vty_out(vty, " IMSI: %s%s", gsub->imsi, VTY_NEWLINE); + if (gsub->tmsi != GSM_RESERVED_TMSI) + vty_out(vty, " TMSI: %08X%s", gsub->tmsi, + VTY_NEWLINE); + if (gsub->sgsn_data->msisdn_len > 0) + vty_out(vty, " MSISDN (BCD): %s%s", + osmo_hexdump(gsub->sgsn_data->msisdn, + gsub->sgsn_data->msisdn_len), + VTY_NEWLINE); + + if (strlen(gsub->imei) > 0) + vty_out(vty, " IMEI: %s%s", gsub->imei, VTY_NEWLINE); + + for (at_idx = 0; at_idx < ARRAY_SIZE(gsub->sgsn_data->auth_triplets); + at_idx++) { + at = &gsub->sgsn_data->auth_triplets[at_idx]; + if (at->key_seq == GSM_KEY_SEQ_INVAL) + continue; + + vty_out(vty, " A3A8 tuple (used %d times): ", + at->use_count); + vty_out(vty, " CKSN: %d, ", + at->key_seq); + if (at->vec.auth_types & OSMO_AUTH_TYPE_GSM) { + vty_out(vty, "RAND: %s, ", + osmo_hexdump_nospc(at->vec.rand, + sizeof(at->vec.rand))); + vty_out(vty, "SRES: %s, ", + osmo_hexdump_nospc(at->vec.sres, + sizeof(at->vec.sres))); + vty_out(vty, "Kc: %s%s", + osmo_hexdump_nospc(at->vec.kc, + sizeof(at->vec.kc)), VTY_NEWLINE); + } + if (at->vec.auth_types & OSMO_AUTH_TYPE_UMTS) { + vty_out(vty, " AUTN: %s, ", + osmo_hexdump(at->vec.autn, + sizeof(at->vec.autn))); + vty_out(vty, "RES: %s, ", + osmo_hexdump_nospc(at->vec.res, at->vec.res_len)); + vty_out(vty, "IK: %s, ", + osmo_hexdump_nospc(at->vec.ik, sizeof(at->vec.ik))); + vty_out(vty, "CK: %s, ", + osmo_hexdump_nospc(at->vec.ck, sizeof(at->vec.ck))); + } + } + + llist_for_each_entry(pdp, &gsub->sgsn_data->pdp_list, list) { + vty_out(vty, " PDP info: Id: %d, Type: 0x%04x, APN: '%s'", + pdp->context_id, pdp->pdp_type, pdp->apn_str); + + if (pdp->qos_subscribed_len) + vty_out(vty, " QoS: %s", osmo_hexdump(pdp->qos_subscribed, pdp->qos_subscribed_len)); + + vty_out(vty, "%s", VTY_NEWLINE); + } + +#if 0 + /* print the expiration time of a subscriber */ + if (gsub->expire_lu) { + strftime(expire_time, sizeof(expire_time), + "%a, %d %b %Y %T %z", localtime(&gsub->expire_lu)); + expire_time[sizeof(expire_time) - 1] = '\0'; + vty_out(vty, " Expiration Time: %s%s", expire_time, VTY_NEWLINE); + } +#endif + + if (gsub->flags) + vty_out(vty, " Flags: %s%s%s%s%s%s", + gsub->flags & GPRS_SUBSCRIBER_FIRST_CONTACT ? + "FIRST_CONTACT " : "", + gsub->flags & GPRS_SUBSCRIBER_CANCELLED ? + "CANCELLED " : "", + gsub->flags & GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING ? + "UPDATE_LOCATION_PENDING " : "", + gsub->flags & GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING ? + "AUTH_INFO_PENDING " : "", + gsub->flags & GPRS_SUBSCRIBER_ENABLE_PURGE ? + "ENABLE_PURGE " : "", + VTY_NEWLINE); + + vty_out(vty, " Use count: %u%s", gsub->use_count, VTY_NEWLINE); +} + +#define RESET_SGSN_STATE_STR \ + "Remove all known subscribers, MM contexts and flush BSSGP queues." \ + " Useful only when running tests against the SGSN\n" + +DEFUN_HIDDEN(reset_sgsn_state, + reset_sgsn_state_cmd, + "reset sgsn state", + RESET_SGSN_STATE_STR RESET_SGSN_STATE_STR RESET_SGSN_STATE_STR) +{ + struct gprs_subscr *subscr, *tmp_subscr; + struct sgsn_mm_ctx *mm, *tmp_mm; + + llist_for_each_entry_safe(mm, tmp_mm, &sgsn_mm_ctxts, list) + { + gsm0408_gprs_access_cancelled(mm, SGSN_ERROR_CAUSE_NONE); + } + vty_out(vty, "Cancelled MM Ctx. %s", VTY_NEWLINE); + + llist_for_each_entry_safe(subscr, tmp_subscr, gprs_subscribers, entry) { + gprs_subscr_get(subscr); + gprs_subscr_cancel(subscr); + gprs_subscr_put(subscr); + } + vty_out(vty, "Removed all gprs subscribers.%s", VTY_NEWLINE); + + bssgp_flush_all_queues(); + vty_out(vty, "Flushed all BSSGPs queues.%s", VTY_NEWLINE); + + gtp_clear_queues(sgsn->gsn); + vty_out(vty, "Flushed rx & tx queus towards the GGSN.%s", VTY_NEWLINE); + + /* remove all queues to bssgp */ + return CMD_SUCCESS; +} + +DEFUN(show_subscr_cache, + show_subscr_cache_cmd, + "show subscriber cache", + SHOW_STR "Show information about subscribers\n" + "Display contents of subscriber cache\n") +{ + struct gprs_subscr *subscr; + + llist_for_each_entry(subscr, gprs_subscribers, entry) { + vty_out(vty, " Subscriber:%s", VTY_NEWLINE); + subscr_dump_full_vty(vty, subscr, 0); + } + + return CMD_SUCCESS; +} + +#define UPDATE_SUBSCR_STR "update-subscriber imsi IMSI " +#define UPDATE_SUBSCR_HELP "Update subscriber list\n" \ + "Use the IMSI to select the subscriber\n" \ + "The IMSI\n" + +#define UPDATE_SUBSCR_INSERT_HELP "Insert data into the subscriber record\n" + +DEFUN(update_subscr_insert_auth_triplet, update_subscr_insert_auth_triplet_cmd, + UPDATE_SUBSCR_STR "insert auth-triplet <1-5> sres SRES rand RAND kc KC", + UPDATE_SUBSCR_HELP + UPDATE_SUBSCR_INSERT_HELP + "Update authentication triplet\n" + "Triplet index\n" + "Set SRES value\nSRES value (4 byte) in hex\n" + "Set RAND value\nRAND value (16 byte) in hex\n" + "Set Kc value\nKc value (8 byte) in hex\n") +{ + const char *imsi = argv[0]; + const int cksn = atoi(argv[1]) - 1; + const char *sres_str = argv[2]; + const char *rand_str = argv[3]; + const char *kc_str = argv[4]; + struct gsm_auth_tuple at = {0,}; + + struct gprs_subscr *subscr; + + subscr = gprs_subscr_get_by_imsi(imsi); + if (!subscr) { + vty_out(vty, "%% unable get subscriber record for %s%s", + imsi, VTY_NEWLINE); + return CMD_WARNING; + } + + OSMO_ASSERT(subscr->sgsn_data); + + if (osmo_hexparse(sres_str, &at.vec.sres[0], sizeof(at.vec.sres)) < 0) { + vty_out(vty, "%% invalid SRES value '%s'%s", + sres_str, VTY_NEWLINE); + goto failed; + } + if (osmo_hexparse(rand_str, &at.vec.rand[0], sizeof(at.vec.rand)) < 0) { + vty_out(vty, "%% invalid RAND value '%s'%s", + rand_str, VTY_NEWLINE); + goto failed; + } + if (osmo_hexparse(kc_str, &at.vec.kc[0], sizeof(at.vec.kc)) < 0) { + vty_out(vty, "%% invalid Kc value '%s'%s", + kc_str, VTY_NEWLINE); + goto failed; + } + at.key_seq = cksn; + + subscr->sgsn_data->auth_triplets[cksn] = at; + subscr->sgsn_data->auth_triplets_updated = 1; + + gprs_subscr_put(subscr); + + return CMD_SUCCESS; + +failed: + gprs_subscr_put(subscr); + return CMD_SUCCESS; +} + +DEFUN(update_subscr_cancel, update_subscr_cancel_cmd, + UPDATE_SUBSCR_STR "cancel (update-procedure|subscription-withdraw)", + UPDATE_SUBSCR_HELP + "Cancel (remove) subscriber record\n" + "The MS moved to another SGSN\n" + "The subscription is no longer valid\n") +{ + const char *imsi = argv[0]; + const char *cancel_type = argv[1]; + + struct gprs_subscr *subscr; + + subscr = gprs_subscr_get_by_imsi(imsi); + if (!subscr) { + vty_out(vty, "%% no subscriber record for %s%s", + imsi, VTY_NEWLINE); + return CMD_WARNING; + } + + if (strcmp(cancel_type, "update-procedure") == 0) + subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE; + else + subscr->sgsn_data->error_cause = GMM_CAUSE_IMPL_DETACHED; + + gprs_subscr_cancel(subscr); + gprs_subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(update_subscr_create, update_subscr_create_cmd, + UPDATE_SUBSCR_STR "create", + UPDATE_SUBSCR_HELP + "Create a subscriber entry\n") +{ + const char *imsi = argv[0]; + + struct gprs_subscr *subscr; + + subscr = gprs_subscr_get_by_imsi(imsi); + if (subscr) { + vty_out(vty, "%% subscriber record already exists for %s%s", + imsi, VTY_NEWLINE); + return CMD_WARNING; + } + + subscr = gprs_subscr_get_or_create(imsi); + subscr->keep_in_ram = 1; + gprs_subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(update_subscr_destroy, update_subscr_destroy_cmd, + UPDATE_SUBSCR_STR "destroy", + UPDATE_SUBSCR_HELP + "Destroy a subscriber entry\n") +{ + const char *imsi = argv[0]; + + struct gprs_subscr *subscr; + + subscr = gprs_subscr_get_by_imsi(imsi); + if (!subscr) { + vty_out(vty, "%% subscriber record does not exist for %s%s", + imsi, VTY_NEWLINE); + return CMD_WARNING; + } + + subscr->keep_in_ram = 0; + subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE; + gprs_subscr_cancel(subscr); + if (subscr->use_count > 1) + vty_out(vty, "%% subscriber is still in use%s", + VTY_NEWLINE); + gprs_subscr_put(subscr); + + return CMD_SUCCESS; +} + +#define UL_ERR_STR "system-failure|data-missing|unexpected-data-value|" \ + "unknown-subscriber|roaming-not-allowed" + +#define UL_ERR_HELP \ + "Force error code SystemFailure\n" \ + "Force error code DataMissing\n" \ + "Force error code UnexpectedDataValue\n" \ + "Force error code UnknownSubscriber\n" \ + "Force error code RoamingNotAllowed\n" + +DEFUN(update_subscr_update_location_result, update_subscr_update_location_result_cmd, + UPDATE_SUBSCR_STR "update-location-result (ok|" UL_ERR_STR ")", + UPDATE_SUBSCR_HELP + "Complete the update location procedure\n" + "The update location request succeeded\n" + UL_ERR_HELP) +{ + const char *imsi = argv[0]; + const char *ret_code_str = argv[1]; + + struct gprs_subscr *subscr; + + const struct value_string cause_mapping[] = { + { GMM_CAUSE_NET_FAIL, "system-failure" }, + { GMM_CAUSE_INV_MAND_INFO, "data-missing" }, + { GMM_CAUSE_PROTO_ERR_UNSPEC, "unexpected-data-value" }, + { GMM_CAUSE_IMSI_UNKNOWN, "unknown-subscriber" }, + { GMM_CAUSE_GPRS_NOTALLOWED, "roaming-not-allowed" }, + { 0, NULL } + }; + + subscr = gprs_subscr_get_by_imsi(imsi); + if (!subscr) { + vty_out(vty, "%% unable to get subscriber record for %s%s", + imsi, VTY_NEWLINE); + return CMD_WARNING; + } + + if (strcmp(ret_code_str, "ok") == 0) { + subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE; + subscr->authorized = 1; + } else { + subscr->sgsn_data->error_cause = + get_string_value(cause_mapping, ret_code_str); + subscr->authorized = 0; + } + + gprs_subscr_update(subscr); + + gprs_subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(update_subscr_update_auth_info, update_subscr_update_auth_info_cmd, + UPDATE_SUBSCR_STR "update-auth-info", + UPDATE_SUBSCR_HELP + "Complete the send authentication info procedure\n") +{ + const char *imsi = argv[0]; + + struct gprs_subscr *subscr; + + subscr = gprs_subscr_get_by_imsi(imsi); + if (!subscr) { + vty_out(vty, "%% unable to get subscriber record for %s%s", + imsi, VTY_NEWLINE); + return CMD_WARNING; + } + + gprs_subscr_update_auth_info(subscr); + + gprs_subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(cfg_gsup_ipa_name, + cfg_gsup_ipa_name_cmd, + "gsup ipa-name NAME", + "GSUP Parameters\n" + "Set the IPA name of this SGSN\n" + "A unique name for this SGSN. For example: PLMN + redundancy server number: SGSN-901-70-0. " + "This name is used for GSUP routing and must be set if more than one SGSN is connected to the network. " + "The default is 'SGSN-00-00-00-00-00-00'.\n") +{ + if (vty->type != VTY_FILE) { + vty_out(vty, "The IPA name cannot be changed at run-time; " + "It can only be set in the configuraton file.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + g_cfg->sgsn_ipa_name = talloc_strdup(tall_vty_ctx, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_gsup_remote_ip, cfg_gsup_remote_ip_cmd, + "gsup remote-ip A.B.C.D", + "GSUP Parameters\n" + "Set the IP address of the remote GSUP server (e.g. OsmoHLR)." + " This setting only applies if 'auth-policy remote' is used.\n" + "IPv4 Address\n") +{ + inet_aton(argv[0], &g_cfg->gsup_server_addr.sin_addr); + + return CMD_SUCCESS; +} + +DEFUN(cfg_gsup_remote_port, cfg_gsup_remote_port_cmd, + "gsup remote-port <0-65535>", + "GSUP Parameters\n" + "Set the TCP port of the remote GSUP server, see also 'gsup remote-ip'\n" + "Remote TCP port\n") +{ + g_cfg->gsup_server_port = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_gsup_oap_id, cfg_gsup_oap_id_cmd, + "gsup oap-id <0-65535>", + "GSUP Parameters\n" + "Set the OAP client ID for authentication on the GSUP protocol." + " This setting only applies if 'auth-policy remote' is used.\n" + "OAP client ID (0 == disabled)\n") +{ + /* VTY ensures range */ + g_cfg->oap.client_id = (uint16_t)atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_gsup_oap_k, cfg_gsup_oap_k_cmd, + "gsup oap-k K", + "GSUP Parameters\n" + "Set the OAP shared secret key K for authentication on the GSUP protocol." + " This setting only applies if auth-policy remote is used.\n" + "K value (16 byte) hex\n") +{ + const char *k = argv[0]; + + g_cfg->oap.secret_k_present = 0; + + if ((!k) || (strlen(k) == 0)) + goto disable; + + int k_len = osmo_hexparse(k, + g_cfg->oap.secret_k, + sizeof(g_cfg->oap.secret_k)); + if (k_len != 16) { + vty_out(vty, "%% need exactly 16 octets for oap-k, got %d.%s", + k_len, VTY_NEWLINE); + goto disable; + } + + g_cfg->oap.secret_k_present = 1; + return CMD_SUCCESS; + +disable: + if (g_cfg->oap.client_id > 0) { + vty_out(vty, "%% OAP client ID set, but invalid oap-k value disables OAP.%s", + VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(cfg_gsup_oap_opc, cfg_gsup_oap_opc_cmd, + "gsup oap-opc OPC", + "GSUP Parameters\n" + "Set the OAP shared secret OPC for authentication on the GSUP protocol." + " This setting only applies if auth-policy remote is used.\n" + "OPC value (16 byte) hex\n") +{ + const char *opc = argv[0]; + + g_cfg->oap.secret_opc_present = 0; + + if ((!opc) || (strlen(opc) == 0)) + goto disable; + + int opc_len = osmo_hexparse(opc, + g_cfg->oap.secret_opc, + sizeof(g_cfg->oap.secret_opc)); + if (opc_len != 16) { + vty_out(vty, "%% need exactly 16 octets for oap-opc, got %d.%s", + opc_len, VTY_NEWLINE); + goto disable; + } + + g_cfg->oap.secret_opc_present = 1; + return CMD_SUCCESS; + +disable: + if (g_cfg->oap.client_id > 0) { + vty_out(vty, "%% OAP client ID set, but invalid oap-opc value disables OAP.%s", + VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(cfg_apn_name, cfg_apn_name_cmd, + "access-point-name NAME", + "Globally allow the given APN name for all subscribers.\n" + "Add this NAME to the list\n") +{ + return add_apn_ggsn_mapping(vty, argv[0], "", 0); +} + +DEFUN(cfg_no_apn_name, cfg_no_apn_name_cmd, + "no access-point-name NAME", + NO_STR "Configure a global list of allowed APNs\n" + "Remove entry with NAME\n") +{ + struct apn_ctx *apn_ctx = sgsn_apn_ctx_by_name(argv[0], ""); + if (!apn_ctx) + return CMD_SUCCESS; + + sgsn_apn_ctx_free(apn_ctx); + return CMD_SUCCESS; +} + +DEFUN(cfg_cdr_filename, cfg_cdr_filename_cmd, + "cdr filename NAME", + "CDR\n" + "Set the file name for the call-data-record file, logging the data usage of each subscriber.\n" + "filename\n") +{ + talloc_free(g_cfg->cdr.filename); + g_cfg->cdr.filename = talloc_strdup(tall_vty_ctx, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_no_cdr_filename, cfg_no_cdr_filename_cmd, + "no cdr filename", + NO_STR "CDR\nDisable saving CDR to file\n") +{ + talloc_free(g_cfg->cdr.filename); + g_cfg->cdr.filename = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_cdr_trap, cfg_cdr_trap_cmd, + "cdr trap", + "CDR\nEnable sending CDR via TRAP CTRL messages\n") +{ + g_cfg->cdr.trap = true; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_cdr_trap, cfg_no_cdr_trap_cmd, + "no cdr trap", + NO_STR "CDR\nDisable sending CDR via TRAP CTRL messages\n") +{ + g_cfg->cdr.trap = false; + return CMD_SUCCESS; +} + +DEFUN(cfg_cdr_interval, cfg_cdr_interval_cmd, + "cdr interval <1-2147483647>", + "CDR\n" + "Set the interval for the call-data-record file\n" + "interval in seconds\n") +{ + g_cfg->cdr.interval = atoi(argv[0]); + return CMD_SUCCESS; +} + +#define COMPRESSION_STR "Configure compression\n" +DEFUN(cfg_no_comp_rfc1144, cfg_no_comp_rfc1144_cmd, + "no compression rfc1144", + NO_STR COMPRESSION_STR "disable rfc1144 TCP/IP header compression\n") +{ + g_cfg->pcomp_rfc1144.active = 0; + g_cfg->pcomp_rfc1144.passive = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_comp_rfc1144, cfg_comp_rfc1144_cmd, + "compression rfc1144 active slots <1-256>", + COMPRESSION_STR + "RFC1144 Header compression scheme\n" + "Compression is actively proposed\n" + "Number of compression state slots\n" + "Number of compression state slots\n") +{ + g_cfg->pcomp_rfc1144.active = 1; + g_cfg->pcomp_rfc1144.passive = 1; + g_cfg->pcomp_rfc1144.s01 = atoi(argv[0]) - 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_comp_rfc1144p, cfg_comp_rfc1144p_cmd, + "compression rfc1144 passive", + COMPRESSION_STR + "RFC1144 Header compression scheme\n" + "Compression is available on request\n") +{ + g_cfg->pcomp_rfc1144.active = 0; + g_cfg->pcomp_rfc1144.passive = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_comp_v42bis, cfg_no_comp_v42bis_cmd, + "no compression v42bis", + NO_STR COMPRESSION_STR "disable V.42bis data compression\n") +{ + g_cfg->dcomp_v42bis.active = 0; + g_cfg->dcomp_v42bis.passive = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_comp_v42bis, cfg_comp_v42bis_cmd, + "compression v42bis active direction (ms|sgsn|both) codewords <512-65535> strlen <6-250>", + COMPRESSION_STR + "V.42bis data compression scheme\n" + "Compression is actively proposed\n" + "Direction in which the compression shall be active (p0)\n" + "Compress ms->sgsn direction only\n" + "Compress sgsn->ms direction only\n" + "Both directions\n" + "Number of codewords (p1)\n" + "Number of codewords\n" + "Maximum string length (p2)\n" "Maximum string length\n") +{ + g_cfg->dcomp_v42bis.active = 1; + g_cfg->dcomp_v42bis.passive = 1; + + switch (argv[0][0]) { + case 'm': + g_cfg->dcomp_v42bis.p0 = 1; + break; + case 's': + g_cfg->dcomp_v42bis.p0 = 2; + break; + case 'b': + g_cfg->dcomp_v42bis.p0 = 3; + break; + } + + g_cfg->dcomp_v42bis.p1 = atoi(argv[1]); + g_cfg->dcomp_v42bis.p2 = atoi(argv[2]); + return CMD_SUCCESS; +} + +DEFUN(cfg_comp_v42bisp, cfg_comp_v42bisp_cmd, + "compression v42bis passive", + COMPRESSION_STR + "V.42bis data compression scheme\n" + "Compression is available on request\n") +{ + g_cfg->dcomp_v42bis.active = 0; + g_cfg->dcomp_v42bis.passive = 1; + return CMD_SUCCESS; +} + +#if BUILD_IU +DEFUN(cfg_sgsn_cs7_instance_iu, + cfg_sgsn_cs7_instance_iu_cmd, + "cs7-instance-iu <0-15>", + "Set SS7 to be used by the Iu-Interface.\n" "SS7 instance reference number (default: 0)\n") +{ + g_cfg->iu.cs7_instance = atoi(argv[0]); + return CMD_SUCCESS; +} +#endif + +int sgsn_vty_init(struct sgsn_config *cfg) +{ + g_cfg = cfg; + + g_cfg->T_defs = sgsn_T_defs; + osmo_tdefs_reset(g_cfg->T_defs); + + install_element_ve(&show_sgsn_cmd); + //install_element_ve(&show_mmctx_tlli_cmd); + install_element_ve(&show_mmctx_imsi_cmd); + install_element_ve(&show_mmctx_all_cmd); + install_element_ve(&show_pdpctx_all_cmd); + install_element_ve(&show_subscr_cache_cmd); + install_element_ve(&show_timer_cmd); + + install_element(ENABLE_NODE, &update_subscr_insert_auth_triplet_cmd); + install_element(ENABLE_NODE, &update_subscr_create_cmd); + install_element(ENABLE_NODE, &update_subscr_destroy_cmd); + install_element(ENABLE_NODE, &update_subscr_cancel_cmd); + install_element(ENABLE_NODE, &update_subscr_update_location_result_cmd); + install_element(ENABLE_NODE, &update_subscr_update_auth_info_cmd); + install_element(ENABLE_NODE, &reset_sgsn_state_cmd); + + install_element(CONFIG_NODE, &cfg_sgsn_cmd); + install_node(&sgsn_node, config_write_sgsn); + install_element(SGSN_NODE, &cfg_sgsn_bind_addr_cmd); + install_element(SGSN_NODE, &cfg_ggsn_remote_ip_cmd); + //install_element(SGSN_NODE, &cfg_ggsn_remote_port_cmd); + install_element(SGSN_NODE, &cfg_ggsn_gtp_version_cmd); + install_element(SGSN_NODE, &cfg_ggsn_echo_interval_cmd); + install_element(SGSN_NODE, &cfg_ggsn_no_echo_interval_cmd); + install_element(SGSN_NODE, &cfg_imsi_acl_cmd); + install_element(SGSN_NODE, &cfg_auth_policy_cmd); + install_element(SGSN_NODE, &cfg_authentication_cmd); + install_element(SGSN_NODE, &cfg_encrypt_cmd); + install_element(SGSN_NODE, &cfg_gsup_ipa_name_cmd); + install_element(SGSN_NODE, &cfg_gsup_remote_ip_cmd); + install_element(SGSN_NODE, &cfg_gsup_remote_port_cmd); + install_element(SGSN_NODE, &cfg_gsup_oap_id_cmd); + install_element(SGSN_NODE, &cfg_gsup_oap_k_cmd); + install_element(SGSN_NODE, &cfg_gsup_oap_opc_cmd); + install_element(SGSN_NODE, &cfg_apn_ggsn_cmd); + install_element(SGSN_NODE, &cfg_apn_imsi_ggsn_cmd); + install_element(SGSN_NODE, &cfg_apn_name_cmd); + install_element(SGSN_NODE, &cfg_no_apn_name_cmd); + install_element(SGSN_NODE, &cfg_cdr_filename_cmd); + install_element(SGSN_NODE, &cfg_no_cdr_filename_cmd); + install_element(SGSN_NODE, &cfg_cdr_trap_cmd); + install_element(SGSN_NODE, &cfg_no_cdr_trap_cmd); + install_element(SGSN_NODE, &cfg_cdr_interval_cmd); + install_element(SGSN_NODE, &cfg_ggsn_dynamic_lookup_cmd); + install_element(SGSN_NODE, &cfg_grx_ggsn_cmd); + + install_element(SGSN_NODE, &cfg_sgsn_timer_cmd); + + install_element(SGSN_NODE, &cfg_no_comp_rfc1144_cmd); + install_element(SGSN_NODE, &cfg_comp_rfc1144_cmd); + install_element(SGSN_NODE, &cfg_comp_rfc1144p_cmd); + install_element(SGSN_NODE, &cfg_no_comp_v42bis_cmd); + install_element(SGSN_NODE, &cfg_comp_v42bis_cmd); + install_element(SGSN_NODE, &cfg_comp_v42bisp_cmd); + +#ifdef BUILD_IU + install_element(SGSN_NODE, &cfg_sgsn_cs7_instance_iu_cmd); + ranap_iu_vty_init(SGSN_NODE, &g_cfg->iu.rab_assign_addr_enc); +#endif + return 0; +} + +int sgsn_parse_config(const char *config_file) +{ + int rc; + + /* make sure sgsn_vty_init() was called before this */ + OSMO_ASSERT(g_cfg); + + rc = vty_read_config_file(config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file); + return rc; + } + + if (g_cfg->auth_policy == SGSN_AUTH_POLICY_REMOTE + && !(g_cfg->gsup_server_addr.sin_addr.s_addr + && g_cfg->gsup_server_port)) { + fprintf(stderr, "Configuration error:" + " 'auth-policy remote' requires both" + " 'gsup remote-ip' and 'gsup remote-port'\n"); + return -EINVAL; + } + + return 0; +} diff --git a/src/sgsn/slhc.c b/src/sgsn/slhc.c new file mode 100644 index 000000000..20e571d48 --- /dev/null +++ b/src/sgsn/slhc.c @@ -0,0 +1,813 @@ +/* + * Routines to compress and uncompress tcp packets (for transmission + * over low speed serial lines). + * + * Copyright (c) 1989 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that the above copyright notice and this paragraph are + * duplicated in all such forms and that any documentation, + * advertising materials, and other materials related to such + * distribution and use acknowledge that the software was developed + * by the University of California, Berkeley. The name of the + * University may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989: + * - Initial distribution. + * + * + * modified for KA9Q Internet Software Package by + * Katie Stevens (dkstevens@ucdavis.edu) + * University of California, Davis + * Computing Services + * - 01-31-90 initial adaptation (from 1.19) + * PPP.05 02-15-90 [ks] + * PPP.08 05-02-90 [ks] use PPP protocol field to signal compression + * PPP.15 09-90 [ks] improve mbuf handling + * PPP.16 11-02 [karn] substantially rewritten to use NOS facilities + * + * - Feb 1991 Bill_Simpson@um.cc.umich.edu + * variable number of conversation slots + * allow zero or one slots + * separate routines + * status display + * - Jul 1994 Dmitry Gorodchanin + * Fixes for memory leaks. + * - Oct 1994 Dmitry Gorodchanin + * Modularization. + * - Jan 1995 Bjorn Ekwall + * Use ip_fast_csum from ip.h + * - July 1995 Christos A. Polyzols + * Spotted bug in tcp option checking + * + * + * This module is a difficult issue. It's clearly inet code but it's also clearly + * driver code belonging close to PPP and SLIP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ERR_PTR(x) x + + +static unsigned char *encode(unsigned char *cp, unsigned short n); +static long decode(unsigned char **cpp); +static unsigned char * put16(unsigned char *cp, unsigned short x); +static unsigned short pull16(unsigned char **cpp); + +/* Replacement for kernel space function ip_fast_csum() */ +static uint16_t ip_fast_csum(uint8_t *iph, int ihl) +{ + int i; + uint16_t temp; + uint32_t accumulator = 0xFFFF; + + for(i=0;i0xFFFF) + { + accumulator++; + accumulator&=0xFFFF; + } + } + + return (uint16_t)(htons(~accumulator)&0xFFFF); +} + +/* Replacement for kernel space function put_unaligned() */ +static void put_unaligned(uint16_t val, void *ptr) +{ + memcpy(ptr,&val,sizeof(val)); +} + + +/* Allocate compression data structure + * slots must be in range 0 to 255 (zero meaning no compression) + * Returns pointer to structure or ERR_PTR() on error. + */ +struct slcompress * +slhc_init(const void *ctx, int rslots, int tslots) +{ + register short i; + register struct cstate *ts; + struct slcompress *comp; + + if (rslots < 0 || rslots > 255 || tslots < 0 || tslots > 255) + return NULL; + + comp = (struct slcompress *)talloc_zero_size(ctx,sizeof(struct slcompress)); + if (! comp) + goto out_fail; + + if (rslots > 0) { + size_t rsize = rslots * sizeof(struct cstate); + comp->rstate = (struct cstate *) talloc_zero_size(ctx, rsize); + if (! comp->rstate) + goto out_free; + comp->rslot_limit = rslots - 1; + } + + if (tslots > 0) { + size_t tsize = tslots * sizeof(struct cstate); + comp->tstate = (struct cstate *) talloc_zero_size(ctx, tsize); + if (! comp->tstate) + goto out_free2; + comp->tslot_limit = tslots - 1; + } + + comp->xmit_oldest = 0; + comp->xmit_current = 255; + comp->recv_current = 255; + /* + * don't accept any packets with implicit index until we get + * one with an explicit index. Otherwise the uncompress code + * will try to use connection 255, which is almost certainly + * out of range + */ + comp->flags |= SLF_TOSS; + + if ( tslots > 0 ) { + ts = comp->tstate; + for(i = comp->tslot_limit; i > 0; --i){ + ts[i].cs_this = i; + ts[i].next = &(ts[i - 1]); + } + ts[0].next = &(ts[comp->tslot_limit]); + ts[0].cs_this = 0; + } + return comp; + +out_free2: + talloc_free(comp->rstate); +out_free: + talloc_free(comp); +out_fail: + return NULL; +} + + +/* Free a compression data structure */ +void +slhc_free(struct slcompress *comp) +{ + DEBUGP(DSLHC, "slhc_free(): Freeing compression states...\n"); + + if ( comp == NULLSLCOMPR ) + return; + + if ( comp->tstate != NULLSLSTATE ) + talloc_free(comp->tstate ); + + if ( comp->rstate != NULLSLSTATE ) + talloc_free( comp->rstate ); + + talloc_free( comp ); +} + + +/* Put a short in host order into a char array in network order */ +static inline unsigned char * +put16(unsigned char *cp, unsigned short x) +{ + *cp++ = x >> 8; + *cp++ = x; + + return cp; +} + + +/* Encode a number */ +static unsigned char * +encode(unsigned char *cp, unsigned short n) +{ + if(n >= 256 || n == 0){ + *cp++ = 0; + cp = put16(cp,n); + } else { + *cp++ = n; + } + + DEBUGP(DSLHC, "encode(): n=%04x\n",n); + return cp; +} + +/* Pull a 16-bit integer in host order from buffer in network byte order */ +static unsigned short +pull16(unsigned char **cpp) +{ + short rval; + + rval = *(*cpp)++; + rval <<= 8; + rval |= *(*cpp)++; + return rval; +} + +/* Decode a number */ +static long +decode(unsigned char **cpp) +{ + register int x; + + x = *(*cpp)++; + if(x == 0){ + return pull16(cpp) & 0xffff; /* pull16 returns -1 on error */ + } else { + return x & 0xff; /* -1 if PULLCHAR returned error */ + } +} + +/* + * icp and isize are the original packet. + * ocp is a place to put a copy if necessary. + * cpp is initially a pointer to icp. If the copy is used, + * change it to ocp. + */ + +int +slhc_compress(struct slcompress *comp, unsigned char *icp, int isize, + unsigned char *ocp, unsigned char **cpp, int compress_cid) +{ + register struct cstate *ocs = &(comp->tstate[comp->xmit_oldest]); + register struct cstate *lcs = ocs; + register struct cstate *cs = lcs->next; + register unsigned long deltaS, deltaA; + register short changes = 0; + int hlen; + unsigned char new_seq[16]; + register unsigned char *cp = new_seq; + struct iphdr *ip; + struct tcphdr *th, *oth; + __sum16 csum; + + + /* + * Don't play with runt packets. + */ + + if(isizeprotocol != IPPROTO_TCP || (ntohs(ip->frag_off) & 0x3fff)) { + /* Send as regular IP */ + if(ip->protocol != IPPROTO_TCP) + comp->sls_o_nontcp++; + else + comp->sls_o_tcp++; + DEBUGP(DSLHC, "slhc_compress(): Not a TCP packat, will not touch...\n"); + return isize; + } + /* Extract TCP header */ + + th = (struct tcphdr *)(((unsigned char *)ip) + ip->ihl*4); + hlen = ip->ihl*4 + th->doff*4; + + /* Bail if the TCP packet isn't `compressible' (i.e., ACK isn't set or + * some other control bit is set). Also uncompressible if + * it's a runt. + */ + if(hlen > isize || th->syn || th->fin || th->rst || + ! (th->ack)){ + /* TCP connection stuff; send as regular IP */ + comp->sls_o_tcp++; + DEBUGP(DSLHC, "slhc_compress(): Packet is part of a TCP connection, will not touch...\n"); + return isize; + } + /* + * Packet is compressible -- we're going to send either a + * COMPRESSED_TCP or UNCOMPRESSED_TCP packet. Either way, + * we need to locate (or create) the connection state. + * + * States are kept in a circularly linked list with + * xmit_oldest pointing to the end of the list. The + * list is kept in lru order by moving a state to the + * head of the list whenever it is referenced. Since + * the list is short and, empirically, the connection + * we want is almost always near the front, we locate + * states via linear search. If we don't find a state + * for the datagram, the oldest state is (re-)used. + */ + + DEBUGP(DSLHC, "slhc_compress(): Compressible packet detected!\n"); + + for ( ; ; ) { + if( ip->saddr == cs->cs_ip.saddr + && ip->daddr == cs->cs_ip.daddr + && th->source == cs->cs_tcp.source + && th->dest == cs->cs_tcp.dest) + goto found; + + /* if current equal oldest, at end of list */ + if ( cs == ocs ) + break; + lcs = cs; + cs = cs->next; + comp->sls_o_searches++; + } + /* + * Didn't find it -- re-use oldest cstate. Send an + * uncompressed packet that tells the other side what + * connection number we're using for this conversation. + * + * Note that since the state list is circular, the oldest + * state points to the newest and we only need to set + * xmit_oldest to update the lru linkage. + */ + + DEBUGP(DSLHC, "slhc_compress(): Header not yet seen, will memorize header for the next turn...\n"); + comp->sls_o_misses++; + comp->xmit_oldest = lcs->cs_this; + goto uncompressed; + +found: + DEBUGP(DSLHC, "slhc_compress(): Header already seen, trying to compress...\n"); + /* + * Found it -- move to the front on the connection list. + */ + if(lcs == ocs) { + /* found at most recently used */ + } else if (cs == ocs) { + /* found at least recently used */ + comp->xmit_oldest = lcs->cs_this; + } else { + /* more than 2 elements */ + lcs->next = cs->next; + cs->next = ocs->next; + ocs->next = cs; + } + + /* + * Make sure that only what we expect to change changed. + * Check the following: + * IP protocol version, header length & type of service. + * The "Don't fragment" bit. + * The time-to-live field. + * The TCP header length. + * IP options, if any. + * TCP options, if any. + * If any of these things are different between the previous & + * current datagram, we send the current datagram `uncompressed'. + */ + oth = &cs->cs_tcp; + + /* Display a little more debug information about which of the + * header fields changed unexpectedly */ + if(ip->version != cs->cs_ip.version) + DEBUGP(DSLHC, "slhc_compress(): Unexpected change: ip->version != cs->cs_ip.version\n"); + if(ip->ihl != cs->cs_ip.ihl) + DEBUGP(DSLHC, "slhc_compress(): Unexpected change: ip->ihl != cs->cs_ip.ihl\n"); + if(ip->tos != cs->cs_ip.tos) + DEBUGP(DSLHC, "slhc_compress(): Unexpected change: ip->tos != cs->cs_ip.tos\n"); + if((ip->frag_off & htons(0x4000)) != (cs->cs_ip.frag_off & htons(0x4000))) + DEBUGP(DSLHC, "slhc_compress(): Unexpected change: (ip->frag_off & htons(0x4000)) != (cs->cs_ip.frag_off & htons(0x4000))\n"); + if(ip->ttl != cs->cs_ip.ttl) + DEBUGP(DSLHC, "slhc_compress(): Unexpected change: ip->ttl != cs->cs_ip.ttl\n"); + if(th->doff != cs->cs_tcp.doff) + DEBUGP(DSLHC, "slhc_compress(): Unexpected change: th->doff != cs->cs_tcp.doff\n"); + if(ip->ihl > 5 && memcmp(ip+1,cs->cs_ipopt,((ip->ihl)-5)*4) != 0) { + DEBUGP(DSLHC, "slhc_compress(): Unexpected change: (ip->ihl > 5 && memcmp(ip+1,cs->cs_ipopt,((ip->ihl)-5)*4) != 0)\n"); + DEBUGP(DSLHC, "slhc_compress(): ip->ihl = %i\n", ip->ihl); + DEBUGP(DSLHC, "slhc_compress(): ip+1 = %s\n", + osmo_hexdump_nospc((uint8_t*)(ip+1),((ip->ihl)-5)*4)); + DEBUGP(DSLHC, "slhc_compress(): Unexpected change: cs->cs_ipopt = %s\n", + osmo_hexdump_nospc((uint8_t*)(cs->cs_ipopt),((ip->ihl)-5)*4)); + } + if(th->doff > 5 && memcmp(th+1,cs->cs_tcpopt,((th->doff)-5)*4) != 0) { + DEBUGP(DSLHC, "slhc_compress(): Unexpected change: (th->doff > 5 && memcmp(th+1,cs->cs_tcpopt,((th->doff)-5)*4) != 0)\n"); + DEBUGP(DSLHC, "slhc_compress(): th->doff = %i\n", th->doff); + DEBUGP(DSLHC, "slhc_compress(): th+1 = %s\n", + osmo_hexdump_nospc((uint8_t*)(th+1),((th->doff)-5)*4)); + DEBUGP(DSLHC, "slhc_compress(): cs->cs_tcpopt = %s\n", + osmo_hexdump_nospc((uint8_t*)cs->cs_tcpopt, + ((th->doff)-5)*4)); + } + + + if(ip->version != cs->cs_ip.version || ip->ihl != cs->cs_ip.ihl + || ip->tos != cs->cs_ip.tos + || (ip->frag_off & htons(0x4000)) != (cs->cs_ip.frag_off & htons(0x4000)) + || ip->ttl != cs->cs_ip.ttl + || th->doff != cs->cs_tcp.doff + || (ip->ihl > 5 && memcmp(ip+1,cs->cs_ipopt,((ip->ihl)-5)*4) != 0) + || (th->doff > 5 && memcmp(th+1,cs->cs_tcpopt,((th->doff)-5)*4) != 0)){ + DEBUGP(DSLHC, "slhc_compress(): The header contains unexpected changes, can't compress...\n"); + goto uncompressed; + } + + /* + * Figure out which of the changing fields changed. The + * receiver expects changes in the order: urgent, window, + * ack, seq (the order minimizes the number of temporaries + * needed in this section of code). + */ + if(th->urg){ + deltaS = ntohs(th->urg_ptr); + DEBUGP(DSLHC, "slhc_compress(): flag: Urgent Pointer (U) = 1\n"); + cp = encode(cp,deltaS); + changes |= NEW_U; + } else if(th->urg_ptr != oth->urg_ptr){ + /* argh! URG not set but urp changed -- a sensible + * implementation should never do this but RFC793 + * doesn't prohibit the change so we have to deal + * with it. */ + DEBUGP(DSLHC, "slhc_compress(): URG not set but urp changed, can't compress...\n"); + goto uncompressed; + } + if((deltaS = ntohs(th->window) - ntohs(oth->window)) != 0){ + DEBUGP(DSLHC, "slhc_compress(): flag: Delta Window (W) = 1\n"); + cp = encode(cp,deltaS); + changes |= NEW_W; + } + if((deltaA = ntohl(th->ack_seq) - ntohl(oth->ack_seq)) != 0L){ + if(deltaA > 0x0000ffff) { + DEBUGP(DSLHC, "slhc_compress(): (deltaA = ntohl(th->ack_seq) - ntohl(oth->ack_seq)) != 0L, can't compress...\n"); + goto uncompressed; + } + DEBUGP(DSLHC, "slhc_compress(): flag: Delta Ack (A) = 1\n"); + cp = encode(cp,deltaA); + changes |= NEW_A; + } + if((deltaS = ntohl(th->seq) - ntohl(oth->seq)) != 0L){ + if(deltaS > 0x0000ffff) { + DEBUGP(DSLHC, "slhc_compress(): (deltaS = ntohl(th->seq) - ntohl(oth->seq)) != 0L, can't compress...\n"); + goto uncompressed; + } + DEBUGP(DSLHC, "slhc_compress(): flag: Delta Sequence (S) = 1\n"); + cp = encode(cp,deltaS); + changes |= NEW_S; + } + + switch(changes){ + case 0: /* Nothing changed. If this packet contains data and the + * last one didn't, this is probably a data packet following + * an ack (normal on an interactive connection) and we send + * it compressed. Otherwise it's probably a retransmit, + * retransmitted ack or window probe. Send it uncompressed + * in case the other side missed the compressed version. + */ + if(ip->tot_len != cs->cs_ip.tot_len && + ntohs(cs->cs_ip.tot_len) == hlen) + break; + DEBUGP(DSLHC, "slhc_compress(): Retransmitted packet detected, can't compress...\n"); + goto uncompressed; + case SPECIAL_I: + case SPECIAL_D: + /* actual changes match one of our special case encodings -- + * send packet uncompressed. + */ + DEBUGP(DSLHC, "slhc_compress(): Special case detected, can't compress...\n"); + goto uncompressed; + case NEW_S|NEW_A: + if(deltaS == deltaA && + deltaS == ntohs(cs->cs_ip.tot_len) - hlen){ + /* special case for echoed terminal traffic */ + DEBUGP(DSLHC, "slhc_compress(): Special case for echoed terminal traffic detected...\n"); + DEBUGP(DSLHC, "slhc_compress(): flag: Delta Sequence (S) = 1, Delta Window (W) = 1, Urgent Pointer (U) = 1\n"); + changes = SPECIAL_I; + cp = new_seq; + } + break; + case NEW_S: + if(deltaS == ntohs(cs->cs_ip.tot_len) - hlen){ + /* special case for data xfer */ + DEBUGP(DSLHC, "slhc_compress(): Special case for data xfer detected...\n"); + DEBUGP(DSLHC, "slhc_compress(): flag: Delta Sequence (S) = 1, Delta Ack (A) = 1, Delta Window (W) = 1, Urgent Pointer (U) = 1\n"); + changes = SPECIAL_D; + cp = new_seq; + } + break; + } + deltaS = ntohs(ip->id) - ntohs(cs->cs_ip.id); + if(deltaS != 1){ + DEBUGP(DSLHC, "slhc_compress(): flag: Delta IP ID (I) = 1\n"); + cp = encode(cp,deltaS); + changes |= NEW_I; + } + if(th->psh) { + DEBUGP(DSLHC, "slhc_compress(): flag: Push (P) = 1\n"); + changes |= TCP_PUSH_BIT; + } + /* Grab the cksum before we overwrite it below. Then update our + * state with this packet's header. + */ + csum = th->check; + memcpy(&cs->cs_ip,ip,20); + memcpy(&cs->cs_tcp,th,20); + /* We want to use the original packet as our compressed packet. + * (cp - new_seq) is the number of bytes we need for compressed + * sequence numbers. In addition we need one byte for the change + * mask, one for the connection id and two for the tcp checksum. + * So, (cp - new_seq) + 4 bytes of header are needed. + */ + deltaS = cp - new_seq; + if(compress_cid == 0 || comp->xmit_current != cs->cs_this){ + cp = ocp; + *cpp = ocp; + DEBUGP(DSLHC, "slhc_compress(): flag: Connection number (C) = 1\n"); + *cp++ = changes | NEW_C; + *cp++ = cs->cs_this; + comp->xmit_current = cs->cs_this; + } else { + cp = ocp; + *cpp = ocp; + *cp++ = changes; + } + *(__sum16 *)cp = csum; + cp += 2; +/* deltaS is now the size of the change section of the compressed header */ + + DEBUGP(DSLHC, "slhc_compress(): Delta-list length (deltaS) = %li\n",deltaS); + DEBUGP(DSLHC, "slhc_compress(): Original header len (hlen) = %i\n",hlen); + + memcpy(cp,new_seq,deltaS); /* Write list of deltas */ + memcpy(cp+deltaS,icp+hlen,isize-hlen); + comp->sls_o_compressed++; + ocp[0] |= SL_TYPE_COMPRESSED_TCP; + return isize - hlen + deltaS + (cp - ocp); + + /* Update connection state cs & send uncompressed packet (i.e., + * a regular ip/tcp packet but with the 'conversation id' we hope + * to use on future compressed packets in the protocol field). + */ +uncompressed: + DEBUGP(DSLHC, "slhc_compress(): Packet will be sent uncompressed...\n"); + memcpy(&cs->cs_ip,ip,20); + memcpy(&cs->cs_tcp,th,20); + if (ip->ihl > 5) + memcpy(cs->cs_ipopt, ip+1, ((ip->ihl) - 5) * 4); + if (th->doff > 5) + memcpy(cs->cs_tcpopt, th+1, ((th->doff) - 5) * 4); + comp->xmit_current = cs->cs_this; + comp->sls_o_uncompressed++; + memcpy(ocp, icp, isize); + *cpp = ocp; + ocp[9] = cs->cs_this; + ocp[0] |= SL_TYPE_UNCOMPRESSED_TCP; + return isize; +} + + +int +slhc_uncompress(struct slcompress *comp, unsigned char *icp, int isize) +{ + register int changes; + long x; + register struct tcphdr *thp; + register struct iphdr *ip; + register struct cstate *cs; + int len, hdrlen; + unsigned char *cp = icp; + + /* We've got a compressed packet; read the change byte */ + comp->sls_i_compressed++; + if(isize < 3){ + comp->sls_i_error++; + return 0; + } + changes = *cp++; + if(changes & NEW_C){ + /* Make sure the state index is in range, then grab the state. + * If we have a good state index, clear the 'discard' flag. + */ + x = *cp++; /* Read conn index */ + if(x < 0 || x > comp->rslot_limit) + goto bad; + + comp->flags &=~ SLF_TOSS; + comp->recv_current = x; + } else { + /* this packet has an implicit state index. If we've + * had a line error since the last time we got an + * explicit state index, we have to toss the packet. */ + if(comp->flags & SLF_TOSS){ + comp->sls_i_tossed++; + return 0; + } + } + cs = &comp->rstate[comp->recv_current]; + thp = &cs->cs_tcp; + ip = &cs->cs_ip; + + thp->check = *(__sum16 *)cp; + cp += 2; + + thp->psh = (changes & TCP_PUSH_BIT) ? 1 : 0; +/* + * we can use the same number for the length of the saved header and + * the current one, because the packet wouldn't have been sent + * as compressed unless the options were the same as the previous one + */ + + hdrlen = ip->ihl * 4 + thp->doff * 4; + + switch(changes & SPECIALS_MASK){ + case SPECIAL_I: /* Echoed terminal traffic */ + DEBUGP(DSLHC, "slhc_uncompress(): Echoed terminal traffic detected\n"); + + { + register short i; + i = ntohs(ip->tot_len) - hdrlen; + thp->ack_seq = htonl( ntohl(thp->ack_seq) + i); + thp->seq = htonl( ntohl(thp->seq) + i); + } + break; + + case SPECIAL_D: /* Unidirectional data */ + DEBUGP(DSLHC, "slhc_uncompress(): Unidirectional data detected\n"); + thp->seq = htonl( ntohl(thp->seq) + + ntohs(ip->tot_len) - hdrlen); + break; + + default: + DEBUGP(DSLHC, "slhc_uncompress(): default packet type detected\n"); + if(changes & NEW_U){ + thp->urg = 1; + if((x = decode(&cp)) == -1) { + goto bad; + } + thp->urg_ptr = htons(x); + } else + thp->urg = 0; + if(changes & NEW_W){ + if((x = decode(&cp)) == -1) { + goto bad; + } + thp->window = htons( ntohs(thp->window) + x); + } + if(changes & NEW_A){ + if((x = decode(&cp)) == -1) { + goto bad; + } + thp->ack_seq = htonl( ntohl(thp->ack_seq) + x); + } + if(changes & NEW_S){ + if((x = decode(&cp)) == -1) { + goto bad; + } + thp->seq = htonl( ntohl(thp->seq) + x); + } + break; + } + if(changes & NEW_I){ + if((x = decode(&cp)) == -1) { + goto bad; + } + ip->id = htons (ntohs (ip->id) + x); + } else + ip->id = htons (ntohs (ip->id) + 1); + + /* + * At this point, cp points to the first byte of data in the + * packet. Put the reconstructed TCP and IP headers back on the + * packet. Recalculate IP checksum (but not TCP checksum). + */ + + len = isize - (cp - icp); + if (len < 0) + goto bad; + len += hdrlen; + ip->tot_len = htons(len); + ip->check = 0; + + DEBUGP(DSLHC, "slhc_uncompress(): making space for the reconstructed header...\n"); + memmove(icp + hdrlen, cp, len - hdrlen); + + cp = icp; + memcpy(cp, ip, 20); + cp += 20; + + if (ip->ihl > 5) { + memcpy(cp, cs->cs_ipopt, (ip->ihl - 5) * 4); + cp += (ip->ihl - 5) * 4; + } + + put_unaligned(ip_fast_csum(icp, ip->ihl), + &((struct iphdr *)icp)->check); + + memcpy(cp, thp, 20); + cp += 20; + + if (thp->doff > 5) { + memcpy(cp, cs->cs_tcpopt, ((thp->doff) - 5) * 4); + cp += ((thp->doff) - 5) * 4; + } + + return len; +bad: + DEBUGP(DSLHC, "slhc_uncompress(): bad packet detected!\n"); + comp->sls_i_error++; + return slhc_toss( comp ); +} + + +int +slhc_remember(struct slcompress *comp, unsigned char *icp, int isize) +{ + register struct cstate *cs; + unsigned ihl; + + unsigned char index; + + if(isize < 20) { + /* The packet is shorter than a legal IP header */ + comp->sls_i_runt++; + DEBUGP(DSLHC, "slhc_remember(): The packet is shorter than a legal IP header ==> slhc_toss()\n"); + return slhc_toss( comp ); + } + /* Peek at the IP header's IHL field to find its length */ + ihl = icp[0] & 0xf; + if(ihl < 20 / 4){ + /* The IP header length field is too small */ + comp->sls_i_runt++; + DEBUGP(DSLHC, "slhc_remember(): The IP header length field is too small ==> slhc_toss()\n"); + return slhc_toss( comp ); + } + index = icp[9]; + icp[9] = IPPROTO_TCP; + + if (ip_fast_csum(icp, ihl)) { + /* Bad IP header checksum; discard */ + comp->sls_i_badcheck++; + DEBUGP(DSLHC, "slhc_remember(): Bad IP header checksum; discard ==> slhc_toss()\n"); + return slhc_toss( comp ); + } + if(index > comp->rslot_limit) { + comp->sls_i_error++; + DEBUGP(DSLHC, "slhc_remember(): index > comp->rslot_limit ==> slhc_toss()\n"); + return slhc_toss(comp); + } + + /* Update local state */ + cs = &comp->rstate[comp->recv_current = index]; + comp->flags &=~ SLF_TOSS; + memcpy(&cs->cs_ip,icp,20); + memcpy(&cs->cs_tcp,icp + ihl*4,20); + if (ihl > 5) + memcpy(cs->cs_ipopt, icp + sizeof(struct iphdr), (ihl - 5) * 4); + if (cs->cs_tcp.doff > 5) + memcpy(cs->cs_tcpopt, icp + ihl*4 + sizeof(struct tcphdr), (cs->cs_tcp.doff - 5) * 4); + cs->cs_hsize = ihl*2 + cs->cs_tcp.doff*2; + /* Put headers back on packet + * Neither header checksum is recalculated + */ + comp->sls_i_uncompressed++; + return isize; +} + +int +slhc_toss(struct slcompress *comp) +{ + DEBUGP(DSLHC, "slhc_toss(): Reset compression state...\n"); + if ( comp == NULLSLCOMPR ) + return 0; + + comp->flags |= SLF_TOSS; + return 0; +} + +void slhc_i_status(struct slcompress *comp) +{ + if (comp != NULLSLCOMPR) { + DEBUGP(DSLHC, "slhc_i_status(): %d Cmp, %d Uncmp, %d Bad, %d Tossed\n", + comp->sls_i_compressed, + comp->sls_i_uncompressed, + comp->sls_i_error, + comp->sls_i_tossed); + } +} + +void slhc_o_status(struct slcompress *comp) +{ + if (comp != NULLSLCOMPR) { + DEBUGP(DSLHC, "slhc_o_status(): %d Cmp, %d Uncmp, %d AsIs, %d NotTCP %d Searches, %d Misses\n", + comp->sls_o_compressed, + comp->sls_o_uncompressed, + comp->sls_o_tcp, + comp->sls_o_nontcp, + comp->sls_o_searches, + comp->sls_o_misses); + } +} + diff --git a/src/sgsn/v42bis.c b/src/sgsn/v42bis.c new file mode 100644 index 000000000..0759cdf1c --- /dev/null +++ b/src/sgsn/v42bis.c @@ -0,0 +1,767 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * v42bis.c + * + * Written by Steve Underwood + * + * Copyright (C) 2005, 2011 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* THIS IS A WORK IN PROGRESS. IT IS NOT FINISHED. + Currently it performs the core compression and decompression functions OK. + However, a number of the bells and whistles in V.42bis are incomplete. */ + +/*! \file */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +#define span_log(x,y,msg, ...) DEBUGP(DV42BIS,msg, ##__VA_ARGS__) +#define span_log_init(x,y,z) +#define span_log_set_protocol(x,y) + + +#define FALSE 0 +#define TRUE 1 + +/* Fixed parameters from the spec. */ +/* Character size (bits) */ +#define V42BIS_N3 8 +/* Number of characters in the alphabet */ +#define V42BIS_N4 256 +/* Index number of first dictionary entry used to store a string */ +#define V42BIS_N5 (V42BIS_N4 + V42BIS_N6) +/* Number of control codewords */ +#define V42BIS_N6 3 +/* V.42bis/9.2 */ +#define V42BIS_ESC_STEP 51 + +/* Compreeibility monitoring parameters for assessing automated switches between + transparent and compressed mode */ +#define COMPRESSIBILITY_MONITOR (256*V42BIS_N3) +#define COMPRESSIBILITY_MONITOR_HYSTERESIS 11 + +/* Control code words in compressed mode */ +enum +{ + V42BIS_ETM = 0, /* Enter transparent mode */ + V42BIS_FLUSH = 1, /* Flush data */ + V42BIS_STEPUP = 2 /* Step up codeword size */ +}; + +/* Command codes in transparent mode */ +enum +{ + V42BIS_ECM = 0, /* Enter compression mode */ + V42BIS_EID = 1, /* Escape character in data */ + V42BIS_RESET = 2 /* Force reinitialisation */ +}; + +static __inline__ void push_octet(v42bis_comp_state_t *s, int octet) +{ + s->output_buf[s->output_octet_count++] = (uint8_t) octet; + if (s->output_octet_count >= s->max_output_len) + { + s->handler(s->user_data, s->output_buf, s->output_octet_count); + s->output_octet_count = 0; + } +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void push_octets(v42bis_comp_state_t *s, const uint8_t buf[], int len) +{ + int i; + int chunk; + + i = 0; + while ((s->output_octet_count + len - i) >= s->max_output_len) + { + chunk = s->max_output_len - s->output_octet_count; + memcpy(&s->output_buf[s->output_octet_count], &buf[i], chunk); + s->handler(s->user_data, s->output_buf, s->max_output_len); + s->output_octet_count = 0; + i += chunk; + } + chunk = len - i; + if (chunk > 0) + { + memcpy(&s->output_buf[s->output_octet_count], &buf[i], chunk); + s->output_octet_count += chunk; + } +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void push_compressed_code(v42bis_comp_state_t *s, int code) +{ + s->bit_buffer |= code << s->bit_count; + s->bit_count += s->v42bis_parm_c2; + while (s->bit_count >= 8) + { + push_octet(s, s->bit_buffer & 0xFF); + s->bit_buffer >>= 8; + s->bit_count -= 8; + } +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void push_octet_alignment(v42bis_comp_state_t *s) +{ + if ((s->bit_count & 7)) + { + s->bit_count += (8 - (s->bit_count & 7)); + while (s->bit_count >= 8) + { + push_octet(s, s->bit_buffer & 0xFF); + s->bit_buffer >>= 8; + s->bit_count -= 8; + } + } +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void flush_octets(v42bis_comp_state_t *s) +{ + if (s->output_octet_count > 0) + { + s->handler(s->user_data, s->output_buf, s->output_octet_count); + s->output_octet_count = 0; + } +} +/*- End of function --------------------------------------------------------*/ + +static void dictionary_init(v42bis_comp_state_t *s) +{ + int i; + + memset(s->dict, 0, sizeof(s->dict)); + for (i = 0; i < V42BIS_N4; i++) + s->dict[i + V42BIS_N6].node_octet = i; + s->v42bis_parm_c1 = V42BIS_N5; + s->v42bis_parm_c2 = V42BIS_N3 + 1; + s->v42bis_parm_c3 = V42BIS_N4 << 1; + s->last_matched = 0; + s->update_at = 0; + s->last_added = 0; + s->bit_buffer = 0; + s->bit_count = 0; + s->flushed_length = 0; + s->string_length = 0; + s->escape_code = 0; + s->transparent = TRUE; + s->escaped = FALSE; + s->compression_performance = COMPRESSIBILITY_MONITOR; +} +/*- End of function --------------------------------------------------------*/ + +static uint16_t match_octet(v42bis_comp_state_t *s, uint16_t at, uint8_t octet) +{ + uint16_t e; + + if (at == 0) + return octet + V42BIS_N6; + e = s->dict[at].child; + while (e) + { + if (s->dict[e].node_octet == octet) + return e; + e = s->dict[e].next; + } + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static uint16_t add_octet_to_dictionary(v42bis_comp_state_t *s, uint16_t at, uint8_t octet) +{ + uint16_t newx; + uint16_t next; + uint16_t e; + + newx = s->v42bis_parm_c1; + s->dict[newx].node_octet = octet; + s->dict[newx].parent = at; + s->dict[newx].child = 0; + s->dict[newx].next = s->dict[at].child; + s->dict[at].child = newx; + next = newx; + /* 6.5 Recovering a dictionary entry to use next */ + do + { + /* 6.5(a) and (b) */ + if (++next == s->v42bis_parm_n2) + next = V42BIS_N5; + } + while (s->dict[next].child); + /* 6.5(c) We need to reuse a leaf node */ + if (s->dict[next].parent) + { + /* 6.5(d) Detach the leaf node from its parent, and re-use it */ + e = s->dict[next].parent; + if (s->dict[e].child == next) + { + s->dict[e].child = s->dict[next].next; + } + else + { + e = s->dict[e].child; + while (s->dict[e].next != next) + e = s->dict[e].next; + s->dict[e].next = s->dict[next].next; + } + } + s->v42bis_parm_c1 = next; + return newx; +} +/*- End of function --------------------------------------------------------*/ + +static void send_string(v42bis_comp_state_t *s) +{ + push_octets(s, s->string, s->string_length); + s->string_length = 0; + s->flushed_length = 0; +} +/*- End of function --------------------------------------------------------*/ + +static void expand_codeword_to_string(v42bis_comp_state_t *s, uint16_t code) +{ + int i; + uint16_t p; + + /* Work out the length */ + for (i = 0, p = code; p; i++) + p = s->dict[p].parent; + s->string_length += i; + /* Now expand the known length of string */ + i = s->string_length - 1; + for (p = code; p; ) + { + s->string[i--] = s->dict[p].node_octet; + p = s->dict[p].parent; + } +} +/*- End of function --------------------------------------------------------*/ + +static void send_encoded_data(v42bis_comp_state_t *s, uint16_t code) +{ + int i; + + /* Update compressibility metric */ + /* Integrate at the compressed bit rate, and leak at the pre-compression bit rate */ + s->compression_performance += (s->v42bis_parm_c2 - s->compression_performance*s->string_length*V42BIS_N3/COMPRESSIBILITY_MONITOR); + if (s->transparent) + { + for (i = 0; i < s->string_length; i++) + { + push_octet(s, s->string[i]); + if (s->string[i] == s->escape_code) + { + push_octet(s, V42BIS_EID); + s->escape_code += V42BIS_ESC_STEP; + } + } + } + else + { + /* Allow for any escape octets in the string */ + for (i = 0; i < s->string_length; i++) + { + if (s->string[i] == s->escape_code) + s->escape_code += V42BIS_ESC_STEP; + } + /* 7.4 Encoding - we now have the longest matchable string, and will need to output the code for it. */ + while (code >= s->v42bis_parm_c3) + { + /* We need to increase the codeword size */ + /* 7.4(a) */ + push_compressed_code(s, V42BIS_STEPUP); + /* 7.4(b) */ + s->v42bis_parm_c2++; + /* 7.4(c) */ + s->v42bis_parm_c3 <<= 1; + /* 7.4(d) this might need to be repeated, so we loop */ + } + /* 7.5 Transfer - output the last state of the string */ + push_compressed_code(s, code); + } + s->string_length = 0; + s->flushed_length = 0; +} +/*- End of function --------------------------------------------------------*/ + +static void go_compressed(v42bis_state_t *ss) +{ + v42bis_comp_state_t *s; + + s = &ss->compress; + if (!s->transparent) + return; + span_log(&ss->logging, SPAN_LOG_FLOW, "Changing to compressed mode\n"); + /* Switch out of transparent now, between codes. We need to send the octet which did not + match, just before switching. */ + if (s->last_matched) + { + s->update_at = s->last_matched; + send_encoded_data(s, s->last_matched); + s->last_matched = 0; + } + push_octet(s, s->escape_code); + push_octet(s, V42BIS_ECM); + s->bit_buffer = 0; + s->transparent = FALSE; +} +/*- End of function --------------------------------------------------------*/ + +static void go_transparent(v42bis_state_t *ss) +{ + v42bis_comp_state_t *s; + + s = &ss->compress; + if (s->transparent) + return; + span_log(&ss->logging, SPAN_LOG_FLOW, "Changing to transparent mode\n"); + /* Switch into transparent now, between codes, and the unmatched octet should + go out in transparent mode, just below */ + if (s->last_matched) + { + s->update_at = s->last_matched; + send_encoded_data(s, s->last_matched); + s->last_matched = 0; + } + s->last_added = 0; + push_compressed_code(s, V42BIS_ETM); + push_octet_alignment(s); + s->transparent = TRUE; +} +/*- End of function --------------------------------------------------------*/ + +static void monitor_for_mode_change(v42bis_state_t *ss) +{ + v42bis_comp_state_t *s; + + s = &ss->compress; + switch (s->compression_mode) + { + case V42BIS_COMPRESSION_MODE_DYNAMIC: + /* 7.8 Data compressibility test */ + if (s->transparent) + { + if (s->compression_performance < COMPRESSIBILITY_MONITOR - COMPRESSIBILITY_MONITOR_HYSTERESIS) + { + /* 7.8.1 Transition to compressed mode */ + go_compressed(ss); + } + } + else + { + if (s->compression_performance > COMPRESSIBILITY_MONITOR) + { + /* 7.8.2 Transition to transparent mode */ + go_transparent(ss); + } + } + /* 7.8.3 Reset function - TODO */ + break; + case V42BIS_COMPRESSION_MODE_ALWAYS: + if (s->transparent) + go_compressed(ss); + break; + case V42BIS_COMPRESSION_MODE_NEVER: + if (!s->transparent) + go_transparent(ss); + break; + } +} +/*- End of function --------------------------------------------------------*/ + +static int v42bis_comp_init(v42bis_comp_state_t *s, + int p1, + int p2, + put_msg_func_t handler, + void *user_data, + int max_output_len) +{ + memset(s, 0, sizeof(*s)); + s->v42bis_parm_n2 = p1; + s->v42bis_parm_n7 = p2; + s->handler = handler; + s->user_data = user_data; + s->max_output_len = (max_output_len < V42BIS_MAX_OUTPUT_LENGTH) ? max_output_len : V42BIS_MAX_OUTPUT_LENGTH; + s->output_octet_count = 0; + dictionary_init(s); + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static int comp_exit(v42bis_comp_state_t *s) +{ + s->v42bis_parm_n2 = 0; + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) v42bis_compress(v42bis_state_t *ss, const uint8_t buf[], int len) +{ + v42bis_comp_state_t *s; + int i; + uint16_t code; + + s = &ss->compress; + if (!s->v42bis_parm_p0) + { + /* Compression is off - just push the incoming data out */ + push_octets(s, buf, len); + return 0; + } + for (i = 0; i < len; ) + { + /* 6.4 Add the string to the dictionary */ + if (s->update_at) + { + if (match_octet(s, s->update_at, buf[i]) == 0) + s->last_added = add_octet_to_dictionary(s, s->update_at, buf[i]); + s->update_at = 0; + } + /* Match string */ + while (i < len) + { + code = match_octet(s, s->last_matched, buf[i]); + if (code == 0) + { + s->update_at = s->last_matched; + send_encoded_data(s, s->last_matched); + s->last_matched = 0; + break; + } + if (code == s->last_added) + { + s->last_added = 0; + send_encoded_data(s, s->last_matched); + s->last_matched = 0; + break; + } + s->last_matched = code; + /* 6.3(b) If the string matches a dictionary entry, and the entry is not that entry + created by the last invocation of the string matching procedure, then the + next character shall be read and appended to the string and this step + repeated. */ + s->string[s->string_length++] = buf[i++]; + /* 6.4(a) The string must not exceed N7 in length */ + if (s->string_length + s->flushed_length == s->v42bis_parm_n7) + { + send_encoded_data(s, s->last_matched); + s->last_matched = 0; + break; + } + } + monitor_for_mode_change(ss); + } + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) v42bis_compress_flush(v42bis_state_t *ss) +{ + v42bis_comp_state_t *s; + int len; + + s = &ss->compress; + if (s->update_at) + return 0; + if (s->last_matched) + { + len = s->string_length; + send_encoded_data(s, s->last_matched); + s->flushed_length += len; + } + if (!s->transparent) + { + s->update_at = s->last_matched; + s->last_matched = 0; + s->flushed_length = 0; + push_compressed_code(s, V42BIS_FLUSH); + push_octet_alignment(s); + } + flush_octets(s); + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) v42bis_decompress(v42bis_state_t *ss, const uint8_t buf[], int len) +{ + v42bis_comp_state_t *s; + int i; + int j; + int yyy; + uint16_t code; + uint16_t p; + uint8_t ch; + uint8_t in; + + s = &ss->decompress; + if (!s->v42bis_parm_p0) + { + /* Compression is off - just push the incoming data out */ + push_octets(s, buf, len); + return 0; + } + for (i = 0; i < len; ) + { + if (s->transparent) + { + in = buf[i]; + if (s->escaped) + { + /* Command */ + s->escaped = FALSE; + switch (in) + { + case V42BIS_ECM: + /* Enter compressed mode */ + span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_ECM\n"); + send_string(s); + s->transparent = FALSE; + s->update_at = s->last_matched; + s->last_matched = 0; + i++; + continue; + case V42BIS_EID: + /* Escape symbol */ + span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_EID\n"); + in = s->escape_code; + s->escape_code += V42BIS_ESC_STEP; + break; + case V42BIS_RESET: + /* Reset dictionary */ + span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_RESET\n"); + /* TODO: */ + send_string(s); + dictionary_init(s); + i++; + continue; + default: + span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_???? - %" PRIu32 "\n", in); + return -1; + } + } + else if (in == s->escape_code) + { + s->escaped = TRUE; + i++; + continue; + } + + yyy = TRUE; + for (j = 0; j < 2 && yyy; j++) + { + if (s->update_at) + { + if (match_octet(s, s->update_at, in) == 0) + s->last_added = add_octet_to_dictionary(s, s->update_at, in); + s->update_at = 0; + } + + code = match_octet(s, s->last_matched, in); + if (code == 0) + { + s->update_at = s->last_matched; + send_string(s); + s->last_matched = 0; + } + else if (code == s->last_added) + { + s->last_added = 0; + send_string(s); + s->last_matched = 0; + } + else + { + s->last_matched = code; + s->string[s->string_length++] = in; + if (s->string_length + s->flushed_length == s->v42bis_parm_n7) + { + send_string(s); + s->last_matched = 0; + } + i++; + yyy = FALSE; + } + } + } + else + { + /* Get code from input */ + while (s->bit_count < s->v42bis_parm_c2 && i < len) + { + s->bit_buffer |= buf[i++] << s->bit_count; + s->bit_count += 8; + } + if (s->bit_count < s->v42bis_parm_c2) + continue; + code = s->bit_buffer & ((1 << s->v42bis_parm_c2) - 1); + s->bit_buffer >>= s->v42bis_parm_c2; + s->bit_count -= s->v42bis_parm_c2; + + if (code < V42BIS_N6) + { + /* We have a control code. */ + switch (code) + { + case V42BIS_ETM: + /* Enter transparent mode */ + span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_ETM\n"); + s->bit_count = 0; + s->transparent = TRUE; + s->last_matched = 0; + s->last_added = 0; + break; + case V42BIS_FLUSH: + /* Flush signal */ + span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_FLUSH\n"); + s->bit_count = 0; + break; + case V42BIS_STEPUP: + /* Increase code word size */ + span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_STEPUP\n"); + s->v42bis_parm_c2++; + s->v42bis_parm_c3 <<= 1; + if (s->v42bis_parm_c2 > (s->v42bis_parm_n2 >> 3)) + return -1; + break; + } + continue; + } + /* Regular codeword */ + if (code == s->v42bis_parm_c1) + return -1; + expand_codeword_to_string(s, code); + if (s->update_at) + { + ch = s->string[0]; + if ((p = match_octet(s, s->update_at, ch)) == 0) + { + s->last_added = add_octet_to_dictionary(s, s->update_at, ch); + if (code == s->v42bis_parm_c1) + return -1; + } + else if (p == s->last_added) + { + s->last_added = 0; + } + } + s->update_at = ((s->string_length + s->flushed_length) == s->v42bis_parm_n7) ? 0 : code; + /* Allow for any escapes which may be in this string */ + for (j = 0; j < s->string_length; j++) + { + if (s->string[j] == s->escape_code) + s->escape_code += V42BIS_ESC_STEP; + } + send_string(s); + } + } + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) v42bis_decompress_flush(v42bis_state_t *ss) +{ + v42bis_comp_state_t *s; + int len; + + s = &ss->decompress; + len = s->string_length; + send_string(s); + s->flushed_length += len; + flush_octets(s); + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(void) v42bis_compression_control(v42bis_state_t *s, int mode) +{ + s->compress.compression_mode = mode; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(v42bis_state_t *) v42bis_init(const void *ctx, + v42bis_state_t *s, + int negotiated_p0, + int negotiated_p1, + int negotiated_p2, + put_msg_func_t encode_handler, + void *encode_user_data, + int max_encode_len, + put_msg_func_t decode_handler, + void *decode_user_data, + int max_decode_len) +{ + int ret; + + if (negotiated_p1 < V42BIS_MIN_DICTIONARY_SIZE || negotiated_p1 > 65535) + return NULL; + if (negotiated_p2 < V42BIS_MIN_STRING_SIZE || negotiated_p2 > V42BIS_MAX_STRING_SIZE) + return NULL; + if (s == NULL) + { + if ((s = (v42bis_state_t *) talloc_zero_size(ctx,sizeof(*s))) == NULL) + return NULL; + } + memset(s, 0, sizeof(*s)); + span_log_init(&s->logging, SPAN_LOG_NONE, NULL); + span_log_set_protocol(&s->logging, "V.42bis"); + + if ((ret = v42bis_comp_init(&s->compress, negotiated_p1, negotiated_p2, encode_handler, encode_user_data, max_encode_len))) + return NULL; + if ((ret = v42bis_comp_init(&s->decompress, negotiated_p1, negotiated_p2, decode_handler, decode_user_data, max_decode_len))) + { + comp_exit(&s->compress); + return NULL; + } + s->compress.v42bis_parm_p0 = negotiated_p0 & 2; + s->decompress.v42bis_parm_p0 = negotiated_p0 & 1; + + return s; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) v42bis_release(v42bis_state_t *s) +{ + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) v42bis_free(v42bis_state_t *s) +{ + comp_exit(&s->compress); + comp_exit(&s->decompress); + talloc_free(s); + return 0; +} +/*- End of function --------------------------------------------------------*/ +/*- End of file ------------------------------------------------------------*/ -- cgit v1.2.3