aboutsummaryrefslogtreecommitdiffstats
path: root/src/sgsn
diff options
context:
space:
mode:
Diffstat (limited to 'src/sgsn')
-rw-r--r--src/sgsn/Makefile.am88
-rw-r--r--src/sgsn/gprs_gb.c75
-rw-r--r--src/sgsn/gprs_gmm.c2852
-rw-r--r--src/sgsn/gprs_gmm_attach.c461
-rw-r--r--src/sgsn/gprs_llc.c1175
-rw-r--r--src/sgsn/gprs_llc_vty.c115
-rw-r--r--src/sgsn/gprs_llc_xid.c281
-rw-r--r--src/sgsn/gprs_mm_state_gb_fsm.c112
-rw-r--r--src/sgsn/gprs_mm_state_iu_fsm.c121
-rw-r--r--src/sgsn/gprs_ranap.c231
-rw-r--r--src/sgsn/gprs_sgsn.c1017
-rw-r--r--src/sgsn/gprs_sndcp.c1265
-rw-r--r--src/sgsn/gprs_sndcp_comp.c338
-rw-r--r--src/sgsn/gprs_sndcp_dcomp.c358
-rw-r--r--src/sgsn/gprs_sndcp_pcomp.c282
-rw-r--r--src/sgsn/gprs_sndcp_vty.c70
-rw-r--r--src/sgsn/gprs_sndcp_xid.c1901
-rw-r--r--src/sgsn/gprs_subscriber.c949
-rw-r--r--src/sgsn/sgsn_auth.c312
-rw-r--r--src/sgsn/sgsn_cdr.c301
-rw-r--r--src/sgsn/sgsn_ctrl.c62
-rw-r--r--src/sgsn/sgsn_libgtp.c842
-rw-r--r--src/sgsn/sgsn_main.c536
-rw-r--r--src/sgsn/sgsn_vty.c1504
-rw-r--r--src/sgsn/slhc.c813
-rw-r--r--src/sgsn/v42bis.c767
26 files changed, 16828 insertions, 0 deletions
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 <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+
+#include "bscconfig.h"
+
+#include <osmocom/sgsn/gprs_mm_state_gb_fsm.h>
+#include <osmocom/sgsn/gprs_sgsn.h>
+#include <osmocom/sgsn/gprs_gmm.h>
+#include <osmocom/sgsn/debug.h>
+
+/* 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 <laforge@gnumonks.org>
+ * (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "bscconfig.h"
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/crypt/auth.h>
+#include <osmocom/gsm/apn.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+
+#include <osmocom/gprs/gprs_bssgp.h>
+
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/gprs_llc.h>
+#include <osmocom/sgsn/gprs_sgsn.h>
+#include <osmocom/sgsn/gprs_gmm.h>
+#include <osmocom/sgsn/gprs_utils.h>
+#include <osmocom/sgsn/gprs_subscriber.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/gprs_gmm_attach.h>
+#include <osmocom/sgsn/gprs_mm_state_gb_fsm.h>
+#include <osmocom/sgsn/gprs_mm_state_iu_fsm.h>
+#include <osmocom/sgsn/signal.h>
+#include <osmocom/sgsn/gprs_sndcp.h>
+#include <osmocom/sgsn/gprs_ranap.h>
+
+#include <pdp.h>
+
+#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 <osmocom/core/tdef.h>
+
+#include <osmocom/sgsn/gprs_gmm_attach.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/gprs_gmm.h>
+#include <osmocom/sgsn/sgsn.h>
+
+#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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/gprs_sgsn.h>
+#include <osmocom/sgsn/gprs_gmm.h>
+#include <osmocom/sgsn/gprs_llc.h>
+#include <osmocom/sgsn/crc24.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/gprs_llc_xid.h>
+#include <osmocom/sgsn/gprs_sndcp_comp.h>
+#include <osmocom/sgsn/gprs_sndcp.h>
+
+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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <time.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/signal.h>
+#include <osmocom/sgsn/gprs_llc.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+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 <info@sysmocom.de>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/gprs_llc.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/gprs_llc_xid.h>
+
+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 <osmocom/core/tdef.h>
+
+#include <osmocom/sgsn/gprs_mm_state_gb_fsm.h>
+
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/sgsn.h>
+
+#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 <arpa/inet.h>
+
+#include <osmocom/core/tdef.h>
+
+#include <osmocom/sgsn/gprs_mm_state_iu_fsm.h>
+
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/sgsn.h>
+
+#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 <laforge@gnumonks.org>
+ * (C) 2015 by Holger Hans Peter Freyther
+ * (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "bscconfig.h"
+
+#ifdef BUILD_IU
+
+#include <gtp.h>
+
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/ranap/ranap_common.h>
+
+#include <osmocom/sgsn/gprs_gmm.h>
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/gprs_ranap.h>
+#include <osmocom/sgsn/gprs_gmm_attach.h>
+#include <osmocom/sgsn/gprs_mm_state_iu_fsm.h>
+
+/* 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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/backtrace.h>
+#include <osmocom/gprs/gprs_ns.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/gsm/apn.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/gsup.h>
+
+#include <osmocom/sgsn/gprs_subscriber.h>
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/gprs_sgsn.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/gprs_gmm.h>
+#include <osmocom/sgsn/gprs_utils.h>
+#include <osmocom/sgsn/signal.h>
+#include <osmocom/sgsn/gprs_gmm_attach.h>
+#include <osmocom/sgsn/gprs_mm_state_gb_fsm.h>
+#include <osmocom/sgsn/gprs_mm_state_iu_fsm.h>
+#include <osmocom/sgsn/gprs_llc.h>
+
+#include <pdp.h>
+
+#include <time.h>
+
+#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 <laforge@gnumonks.org>
+ * (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/gprs_llc.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/gprs_sndcp.h>
+#include <osmocom/sgsn/gprs_llc_xid.h>
+#include <osmocom/sgsn/gprs_sndcp_xid.h>
+#include <osmocom/sgsn/gprs_sndcp_pcomp.h>
+#include <osmocom/sgsn/gprs_sndcp_dcomp.h>
+#include <osmocom/sgsn/gprs_sndcp_comp.h>
+
+#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 <info@sysmocom.de>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <math.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/gprs_sndcp_xid.h>
+#include <osmocom/sgsn/gprs_sndcp_comp.h>
+#include <osmocom/sgsn/gprs_sndcp_pcomp.h>
+#include <osmocom/sgsn/gprs_sndcp_dcomp.h>
+
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <math.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/sgsn/gprs_llc.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/gprs_sndcp_xid.h>
+#include <osmocom/sgsn/v42bis.h>
+#include <osmocom/sgsn/v42bis_private.h>
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/gprs_sndcp_comp.h>
+#include <osmocom/sgsn/gprs_sndcp_dcomp.h>
+
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <math.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/sgsn/gprs_llc.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/gprs_sndcp_xid.h>
+#include <osmocom/sgsn/slhc.h>
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/gprs_sndcp_comp.h>
+#include <osmocom/sgsn/gprs_sndcp_pcomp.h>
+
+/* 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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/signal.h>
+#include <osmocom/sgsn/gprs_llc.h>
+#include <osmocom/sgsn/gprs_sndcp.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+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 <info@sysmocom.de>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <math.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/gprs_llc.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/gprs_sndcp_xid.h>
+
+/* 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, &params->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(&params->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, &params->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(&params->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(&params->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(&params->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(&params->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(&params->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, &params->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(&params->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(&params->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, &params->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, &params->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(&params->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(&params->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(&params->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, &params->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(&params->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(&params->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(&params->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(&params->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(&params->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(&params->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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/gsm/apn.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/sgsn/gprs_subscriber.h>
+#include <osmocom/gsupclient/gsup_client.h>
+
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/gprs_sgsn.h>
+#include <osmocom/sgsn/gprs_gmm.h>
+#include <osmocom/sgsn/gprs_utils.h>
+
+#include <osmocom/sgsn/debug.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <limits.h>
+
+#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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/gprs_sgsn.h>
+#include <osmocom/sgsn/gprs_gmm.h>
+#include <osmocom/sgsn/gprs_subscriber.h>
+#include <osmocom/sgsn/debug.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/ctrl/control_if.h>
+
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/signal.h>
+#include <osmocom/sgsn/gprs_utils.h>
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/gsm/apn.h>
+
+#include <osmocom/sgsn/vty.h>
+
+#include <gtp.h>
+#include <pdp.h>
+
+#include <arpa/inet.h>
+
+#include <time.h>
+
+#include <stdio.h>
+#include <inttypes.h>
+
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/sgsn/gprs_sgsn.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/debug.h>
+
+#include <pdp.h>
+
+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 <laforge@gnumonks.org>
+ * (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "bscconfig.h"
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+
+#include <osmocom/sgsn/signal.h>
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/gprs_llc.h>
+#include <osmocom/sgsn/gprs_sgsn.h>
+#include <osmocom/sgsn/gprs_gmm.h>
+#include <osmocom/sgsn/gprs_subscriber.h>
+#include <osmocom/sgsn/gprs_sndcp.h>
+#include <osmocom/sgsn/gprs_ranap.h>
+
+#include <gtp.h>
+#include <pdp.h>
+
+/* 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 <laforge@gnumonks.org>
+ * (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/stats.h>
+
+#include <osmocom/gprs/gprs_ns.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmocom/ctrl/control_vty.h>
+
+#include <osmocom/sgsn/signal.h>
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/vty.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/gprs_llc.h>
+#include <osmocom/sgsn/gprs_gmm.h>
+#include <osmocom/sgsn/gprs_ranap.h>
+
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+
+#include <gtp.h>
+
+#include "../../bscconfig.h"
+
+#if BUILD_IU
+#include <osmocom/sigtran/osmo_ss7.h>
+#include <osmocom/sigtran/protocol/m3ua.h>
+#include <osmocom/ranap/iu_client.h>
+#endif
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+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 <http://gnu.org/licenses/agpl-3.0.html>\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 <laforge@gnumonks.org>
+ * (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <time.h>
+#include <inttypes.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/gsm/apn.h>
+
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/gprs/gprs_ns.h>
+#include <osmocom/sgsn/gprs_gmm.h>
+#include <osmocom/sgsn/gprs_sgsn.h>
+#include <osmocom/sgsn/vty.h>
+#include <osmocom/gsupclient/gsup_client.h>
+
+#include <osmocom/vty/tdef_vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/misc.h>
+#include <osmocom/crypt/gprs_cipher.h>
+#include <osmocom/abis/ipa.h>
+
+#include <osmocom/gprs/gprs_bssgp.h>
+
+#include <pdp.h>
+#include <gtp.h>
+
+#include "../../bscconfig.h"
+
+#ifdef BUILD_IU
+#include <osmocom/ranap/iu_client.h>
+#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 <osmocom/sgsn/gprs_subscriber.h>
+
+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 <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/sgsn/slhc.h>
+#include <osmocom/sgsn/debug.h>
+
+#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;i<ihl*2;i++)
+ {
+ temp = ((*iph) << 8)&0xFF00;
+ iph++;
+ temp |= (*iph)&0xFF;
+ iph++;
+
+ accumulator+=temp;
+ if(accumulator>0xFFFF)
+ {
+ 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(isize<sizeof(struct iphdr))
+ return isize;
+
+ ip = (struct iphdr *) icp;
+
+ /* Bail if this packet isn't TCP, or is an IP fragment */
+ if (ip->protocol != 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 <steveu@coppice.org>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <osmocom/sgsn/v42bis.h>
+#include <osmocom/sgsn/v42bis_private.h>
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/core/talloc.h>
+
+
+#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 ------------------------------------------------------------*/