aboutsummaryrefslogtreecommitdiffstats
path: root/src/gprs
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2017-07-04 23:08:44 +0200
committerNeels Hofmeyr <nhofmeyr@sysmocom.de>2017-07-12 23:42:44 +0200
commit0b5cf1d1d40815b5b910330074ab4e234014448a (patch)
tree77eed5bde035b276b63f92c0f23e944049e59897 /src/gprs
parent9e3c66b1814246f6c06a6f78975f54dfe9e2cf8c (diff)
move openbsc/* to repos root
This is the first step in creating this repository from the legacy openbsc.git. Like all other Osmocom repositories, keep the autoconf and automake files in the repository root. openbsc.git has been the sole exception, which ends now. Change-Id: I9c6f2a448d9cb1cc088cf1cf6918b69d7e69b4e7
Diffstat (limited to 'src/gprs')
-rw-r--r--src/gprs/.gitignore2
-rw-r--r--src/gprs/Makefile.am132
-rw-r--r--src/gprs/crc24.c67
-rw-r--r--src/gprs/gb_proxy.c1437
-rw-r--r--src/gprs/gb_proxy_main.c315
-rw-r--r--src/gprs/gb_proxy_patch.c458
-rw-r--r--src/gprs/gb_proxy_peer.c218
-rw-r--r--src/gprs/gb_proxy_tlli.c723
-rw-r--r--src/gprs/gb_proxy_vty.c852
-rw-r--r--src/gprs/gprs_gb_parse.c636
-rw-r--r--src/gprs/gprs_gmm.c2939
-rw-r--r--src/gprs/gprs_llc.c1132
-rw-r--r--src/gprs/gprs_llc_parse.c251
-rw-r--r--src/gprs/gprs_llc_vty.c116
-rw-r--r--src/gprs/gprs_llc_xid.c281
-rw-r--r--src/gprs/gprs_sgsn.c895
-rw-r--r--src/gprs/gprs_sndcp.c1258
-rw-r--r--src/gprs/gprs_sndcp_comp.c323
-rw-r--r--src/gprs/gprs_sndcp_dcomp.c358
-rw-r--r--src/gprs/gprs_sndcp_pcomp.c282
-rw-r--r--src/gprs/gprs_sndcp_vty.c71
-rw-r--r--src/gprs/gprs_sndcp_xid.c1822
-rw-r--r--src/gprs/gprs_subscriber.c921
-rw-r--r--src/gprs/gprs_utils.c274
-rw-r--r--src/gprs/gtphub.c2931
-rw-r--r--src/gprs/gtphub_ares.c220
-rw-r--r--src/gprs/gtphub_main.c357
-rw-r--r--src/gprs/gtphub_sock.c60
-rw-r--r--src/gprs/gtphub_vty.c613
-rw-r--r--src/gprs/osmo_sgsn.cfg23
-rw-r--r--src/gprs/sgsn_ares.c173
-rw-r--r--src/gprs/sgsn_auth.c312
-rw-r--r--src/gprs/sgsn_cdr.c258
-rw-r--r--src/gprs/sgsn_ctrl.c69
-rw-r--r--src/gprs/sgsn_libgtp.c860
-rw-r--r--src/gprs/sgsn_main.c462
-rw-r--r--src/gprs/sgsn_vty.c1323
-rw-r--r--src/gprs/slhc.c813
-rw-r--r--src/gprs/v42bis.c767
39 files changed, 25004 insertions, 0 deletions
diff --git a/src/gprs/.gitignore b/src/gprs/.gitignore
new file mode 100644
index 000000000..7cfefbac2
--- /dev/null
+++ b/src/gprs/.gitignore
@@ -0,0 +1,2 @@
+gsn_restart
+osmo_*.cfg*
diff --git a/src/gprs/Makefile.am b/src/gprs/Makefile.am
new file mode 100644
index 000000000..cb0997902
--- /dev/null
+++ b/src/gprs/Makefile.am
@@ -0,0 +1,132 @@
+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) \
+ $(COVERAGE_CFLAGS) \
+ $(LIBCARES_CFLAGS) \
+ $(LIBCRYPTO_CFLAGS) \
+ $(LIBGTP_CFLAGS) \
+ $(NULL)
+if BUILD_IU
+AM_CFLAGS += \
+ $(LIBASN1C_CFLAGS) \
+ $(LIBOSMOSIGTRAN_CFLAGS) \
+ $(LIBOSMORANAP_CFLAGS) \
+ $(NULL)
+endif
+
+OSMO_LIBS = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMOGB_LIBS) \
+ $(LIBGTP_LIBS) \
+ $(NULL)
+
+bin_PROGRAMS = \
+ osmo-gbproxy \
+ $(NULL)
+if HAVE_LIBGTP
+if HAVE_LIBCARES
+bin_PROGRAMS += \
+ osmo-sgsn \
+ osmo-gtphub \
+ $(NULL)
+endif
+endif
+
+osmo_gbproxy_SOURCES = \
+ gb_proxy.c \
+ gb_proxy_main.c \
+ gb_proxy_vty.c \
+ gb_proxy_patch.c \
+ gb_proxy_tlli.c \
+ gb_proxy_peer.c \
+ gprs_gb_parse.c \
+ gprs_llc_parse.c \
+ crc24.c \
+ gprs_utils.c \
+ $(NULL)
+osmo_gbproxy_LDADD = \
+ $(top_builddir)/src/libcommon/libcommon.a \
+ $(OSMO_LIBS) \
+ $(LIBCRYPTO_LIBS) \
+ -lrt \
+ $(NULL)
+
+osmo_sgsn_SOURCES = \
+ gprs_gmm.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_parse.c \
+ gprs_llc_vty.c \
+ crc24.c \
+ sgsn_ctrl.c \
+ sgsn_auth.c \
+ gprs_subscriber.c \
+ gprs_utils.c \
+ sgsn_cdr.c \
+ sgsn_ares.c \
+ slhc.c \
+ gprs_llc_xid.c \
+ v42bis.c \
+ $(NULL)
+osmo_sgsn_LDADD = \
+ $(top_builddir)/src/libcommon/libcommon.a \
+ $(OSMO_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBCARES_LIBS) \
+ $(LIBCRYPTO_LIBS) \
+ $(LIBGTP_LIBS) \
+ -lrt \
+ -lm \
+ $(NULL)
+if BUILD_IU
+osmo_sgsn_LDADD += \
+ $(top_builddir)/src/libiu/libiu.a \
+ $(LIBOSMOSIGTRAN_LIBS) \
+ $(LIBOSMORANAP_LIBS) \
+ $(LIBASN1C_LIBS) \
+ $(NULL)
+endif
+
+osmo_gtphub_SOURCES = \
+ gtphub_main.c \
+ gtphub.c \
+ gtphub_sock.c \
+ gtphub_ares.c \
+ gtphub_vty.c \
+ sgsn_ares.c \
+ gprs_utils.c \
+ $(NULL)
+osmo_gtphub_LDADD = \
+ $(top_builddir)/src/libcommon/libcommon.a \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBCARES_LIBS) \
+ $(LIBGTP_LIBS) \
+ -lrt \
+ $(NULL)
diff --git a/src/gprs/crc24.c b/src/gprs/crc24.c
new file mode 100644
index 000000000..1a420ed66
--- /dev/null
+++ b/src/gprs/crc24.c
@@ -0,0 +1,67 @@
+/* GPRS LLC CRC-24 Implementation */
+
+/* (C) 2008-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 <openbsc/crc24.h>
+
+/* CRC24 table - FCS */
+static const uint32_t tbl_crc24[256] = {
+ 0x00000000, 0x00d6a776, 0x00f64557, 0x0020e221, 0x00b78115, 0x00612663, 0x0041c442, 0x00976334,
+ 0x00340991, 0x00e2aee7, 0x00c24cc6, 0x0014ebb0, 0x00838884, 0x00552ff2, 0x0075cdd3, 0x00a36aa5,
+ 0x00681322, 0x00beb454, 0x009e5675, 0x0048f103, 0x00df9237, 0x00093541, 0x0029d760, 0x00ff7016,
+ 0x005c1ab3, 0x008abdc5, 0x00aa5fe4, 0x007cf892, 0x00eb9ba6, 0x003d3cd0, 0x001ddef1, 0x00cb7987,
+ 0x00d02644, 0x00068132, 0x00266313, 0x00f0c465, 0x0067a751, 0x00b10027, 0x0091e206, 0x00474570,
+ 0x00e42fd5, 0x003288a3, 0x00126a82, 0x00c4cdf4, 0x0053aec0, 0x008509b6, 0x00a5eb97, 0x00734ce1,
+ 0x00b83566, 0x006e9210, 0x004e7031, 0x0098d747, 0x000fb473, 0x00d91305, 0x00f9f124, 0x002f5652,
+ 0x008c3cf7, 0x005a9b81, 0x007a79a0, 0x00acded6, 0x003bbde2, 0x00ed1a94, 0x00cdf8b5, 0x001b5fc3,
+ 0x00fb4733, 0x002de045, 0x000d0264, 0x00dba512, 0x004cc626, 0x009a6150, 0x00ba8371, 0x006c2407,
+ 0x00cf4ea2, 0x0019e9d4, 0x00390bf5, 0x00efac83, 0x0078cfb7, 0x00ae68c1, 0x008e8ae0, 0x00582d96,
+ 0x00935411, 0x0045f367, 0x00651146, 0x00b3b630, 0x0024d504, 0x00f27272, 0x00d29053, 0x00043725,
+ 0x00a75d80, 0x0071faf6, 0x005118d7, 0x0087bfa1, 0x0010dc95, 0x00c67be3, 0x00e699c2, 0x00303eb4,
+ 0x002b6177, 0x00fdc601, 0x00dd2420, 0x000b8356, 0x009ce062, 0x004a4714, 0x006aa535, 0x00bc0243,
+ 0x001f68e6, 0x00c9cf90, 0x00e92db1, 0x003f8ac7, 0x00a8e9f3, 0x007e4e85, 0x005eaca4, 0x00880bd2,
+ 0x00437255, 0x0095d523, 0x00b53702, 0x00639074, 0x00f4f340, 0x00225436, 0x0002b617, 0x00d41161,
+ 0x00777bc4, 0x00a1dcb2, 0x00813e93, 0x005799e5, 0x00c0fad1, 0x00165da7, 0x0036bf86, 0x00e018f0,
+ 0x00ad85dd, 0x007b22ab, 0x005bc08a, 0x008d67fc, 0x001a04c8, 0x00cca3be, 0x00ec419f, 0x003ae6e9,
+ 0x00998c4c, 0x004f2b3a, 0x006fc91b, 0x00b96e6d, 0x002e0d59, 0x00f8aa2f, 0x00d8480e, 0x000eef78,
+ 0x00c596ff, 0x00133189, 0x0033d3a8, 0x00e574de, 0x007217ea, 0x00a4b09c, 0x008452bd, 0x0052f5cb,
+ 0x00f19f6e, 0x00273818, 0x0007da39, 0x00d17d4f, 0x00461e7b, 0x0090b90d, 0x00b05b2c, 0x0066fc5a,
+ 0x007da399, 0x00ab04ef, 0x008be6ce, 0x005d41b8, 0x00ca228c, 0x001c85fa, 0x003c67db, 0x00eac0ad,
+ 0x0049aa08, 0x009f0d7e, 0x00bfef5f, 0x00694829, 0x00fe2b1d, 0x00288c6b, 0x00086e4a, 0x00dec93c,
+ 0x0015b0bb, 0x00c317cd, 0x00e3f5ec, 0x0035529a, 0x00a231ae, 0x007496d8, 0x005474f9, 0x0082d38f,
+ 0x0021b92a, 0x00f71e5c, 0x00d7fc7d, 0x00015b0b, 0x0096383f, 0x00409f49, 0x00607d68, 0x00b6da1e,
+ 0x0056c2ee, 0x00806598, 0x00a087b9, 0x007620cf, 0x00e143fb, 0x0037e48d, 0x001706ac, 0x00c1a1da,
+ 0x0062cb7f, 0x00b46c09, 0x00948e28, 0x0042295e, 0x00d54a6a, 0x0003ed1c, 0x00230f3d, 0x00f5a84b,
+ 0x003ed1cc, 0x00e876ba, 0x00c8949b, 0x001e33ed, 0x008950d9, 0x005ff7af, 0x007f158e, 0x00a9b2f8,
+ 0x000ad85d, 0x00dc7f2b, 0x00fc9d0a, 0x002a3a7c, 0x00bd5948, 0x006bfe3e, 0x004b1c1f, 0x009dbb69,
+ 0x0086e4aa, 0x005043dc, 0x0070a1fd, 0x00a6068b, 0x003165bf, 0x00e7c2c9, 0x00c720e8, 0x0011879e,
+ 0x00b2ed3b, 0x00644a4d, 0x0044a86c, 0x00920f1a, 0x00056c2e, 0x00d3cb58, 0x00f32979, 0x00258e0f,
+ 0x00eef788, 0x003850fe, 0x0018b2df, 0x00ce15a9, 0x0059769d, 0x008fd1eb, 0x00af33ca, 0x007994bc,
+ 0x00dafe19, 0x000c596f, 0x002cbb4e, 0x00fa1c38, 0x006d7f0c, 0x00bbd87a, 0x009b3a5b, 0x004d9d2d
+};
+
+#define INIT_CRC24 0xffffff
+
+uint32_t crc24_calc(uint32_t fcs, uint8_t *cp, unsigned int len)
+{
+ while (len--)
+ fcs = (fcs >> 8) ^ tbl_crc24[(fcs ^ *cp++) & 0xff];
+ return fcs;
+}
diff --git a/src/gprs/gb_proxy.c b/src/gprs/gb_proxy.c
new file mode 100644
index 000000000..d95139f8d
--- /dev/null
+++ b/src/gprs/gb_proxy.c
@@ -0,0 +1,1437 @@
+/* NS-over-IP proxy */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2013 by On-Waves
+ * (C) 2013 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 <sys/fcntl.h>
+#include <sys/stat.h>
+#include <arpa/inet.h>
+#include <time.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stats.h>
+
+#include <osmocom/gprs/gprs_ns.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_gb_parse.h>
+#include <openbsc/gb_proxy.h>
+
+#include <openbsc/gprs_llc.h>
+#include <openbsc/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <openbsc/gprs_utils.h>
+
+#include <openssl/rand.h>
+
+static const struct rate_ctr_desc global_ctr_description[] = {
+ { "inv-bvci", "Invalid BVC Identifier " },
+ { "inv-lai", "Invalid Location Area Identifier" },
+ { "inv-rai", "Invalid Routing Area Identifier " },
+ { "inv-nsei", "No BVC established for NSEI " },
+ { "proto-err.bss", "BSSGP protocol error (BSS )" },
+ { "proto-err.sgsn", "BSSGP protocol error (SGSN)" },
+ { "not-supp.bss", "Feature not supported (BSS )" },
+ { "not-supp.sgsn", "Feature not supported (SGSN)" },
+ { "restart.sgsn", "Restarted RESET procedure (SGSN)" },
+ { "tx-err.sgsn", "NS Transmission error (SGSN)" },
+ { "error", "Other error " },
+ { "mod-peer-err", "Patch error: no peer " },
+};
+
+static const struct rate_ctr_group_desc global_ctrg_desc = {
+ .group_name_prefix = "gbproxy.global",
+ .group_description = "GBProxy Global Statistics",
+ .num_ctr = ARRAY_SIZE(global_ctr_description),
+ .ctr_desc = global_ctr_description,
+ .class_id = OSMO_STATS_CLASS_GLOBAL,
+};
+
+static int gbprox_relay2peer(struct msgb *old_msg, struct gbproxy_peer *peer,
+ uint16_t ns_bvci);
+static int gbprox_relay2sgsn(struct gbproxy_config *cfg, struct msgb *old_msg,
+ uint16_t ns_bvci, uint16_t sgsn_nsei);
+static void gbproxy_reset_imsi_acquisition(struct gbproxy_link_info* link_info);
+
+static int check_peer_nsei(struct gbproxy_peer *peer, uint16_t nsei)
+{
+ if (peer->nsei != nsei) {
+ LOGP(DGPRS, LOGL_NOTICE, "Peer entry doesn't match current NSEI "
+ "BVCI=%u via NSEI=%u (expected NSEI=%u)\n",
+ peer->bvci, nsei, peer->nsei);
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_INV_NSEI]);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* strip off the NS header */
+static void strip_ns_hdr(struct msgb *msg)
+{
+ int strip_len = msgb_bssgph(msg) - msg->data;
+ msgb_pull(msg, strip_len);
+}
+
+/* Transmit Chapter 9.2.10 Identity Request */
+static void gprs_put_identity_req(struct msgb *msg, uint8_t id_type)
+{
+ struct gsm48_hdr *gh;
+
+ id_type &= GSM_MI_TYPE_MASK;
+
+ 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;
+ gh->data[0] = id_type;
+}
+
+/* Transmit Chapter 9.4.6.2 Detach Accept (mobile originated detach) */
+static void gprs_put_mo_detach_acc(struct msgb *msg)
+{
+ struct gsm48_hdr *gh;
+
+ 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] = 0; /* no force to standby */
+}
+
+static void gprs_push_llc_ui(struct msgb *msg,
+ int is_uplink, unsigned sapi, unsigned nu)
+{
+ const uint8_t e_bit = 0;
+ const uint8_t pm_bit = 1;
+ const uint8_t cr_bit = is_uplink ? 0 : 1;
+ uint8_t *llc;
+ uint8_t *fcs_field;
+ uint32_t fcs;
+
+ nu &= 0x01ff; /* 9 Bit */
+
+ llc = msgb_push(msg, 3);
+ llc[0] = (cr_bit << 6) | (sapi & 0x0f);
+ llc[1] = 0xc0 | (nu >> 6); /* UI frame */
+ llc[2] = (nu << 2) | ((e_bit & 1) << 1) | (pm_bit & 1);
+
+ fcs = gprs_llc_fcs(llc, msgb_length(msg));
+ fcs_field = msgb_put(msg, 3);
+ fcs_field[0] = (uint8_t)(fcs >> 0);
+ fcs_field[1] = (uint8_t)(fcs >> 8);
+ fcs_field[2] = (uint8_t)(fcs >> 16);
+}
+
+static void gprs_push_bssgp_dl_unitdata(struct msgb *msg,
+ uint32_t tlli)
+{
+ struct bssgp_ud_hdr *budh;
+ uint8_t *llc = msgb_data(msg);
+ size_t llc_size = msgb_length(msg);
+ const size_t llc_ie_hdr_size = 3;
+ const uint8_t qos_profile[] = {0x00, 0x50, 0x20}; /* hard-coded */
+ const uint8_t lifetime[] = {0x02, 0x58}; /* 6s hard-coded */
+
+ const size_t bssgp_overhead = sizeof(*budh) +
+ TVLV_GROSS_LEN(sizeof(lifetime)) + llc_ie_hdr_size;
+ uint8_t *ie;
+ uint32_t tlli_be = htonl(tlli);
+
+ budh = (struct bssgp_ud_hdr *)msgb_push(msg, bssgp_overhead);
+
+ budh->pdu_type = BSSGP_PDUT_DL_UNITDATA;
+ memcpy(&budh->tlli, &tlli_be, sizeof(budh->tlli));
+ memcpy(&budh->qos_profile, qos_profile, sizeof(budh->qos_profile));
+
+ ie = budh->data;
+ tvlv_put(ie, BSSGP_IE_PDU_LIFETIME, sizeof(lifetime), lifetime);
+ ie += TVLV_GROSS_LEN(sizeof(lifetime));
+
+ /* Note: Add alignment before the LLC IE if inserting other IE */
+
+ *(ie++) = BSSGP_IE_LLC_PDU;
+ *(ie++) = llc_size / 256;
+ *(ie++) = llc_size % 256;
+
+ OSMO_ASSERT(ie == llc);
+
+ msgb_bssgph(msg) = (uint8_t *)budh;
+ msgb_tlli(msg) = tlli;
+}
+
+/* update peer according to the BSS message */
+static void gbprox_update_current_raid(uint8_t *raid_enc,
+ struct gbproxy_peer *peer,
+ const char *log_text)
+{
+ struct gbproxy_patch_state *state = &peer->patch_state;
+ const int old_local_mcc = state->local_mcc;
+ const int old_local_mnc = state->local_mnc;
+ struct gprs_ra_id raid;
+
+ if (!raid_enc)
+ return;
+
+ gsm48_parse_ra(&raid, raid_enc);
+
+ /* save source side MCC/MNC */
+ if (!peer->cfg->core_mcc || raid.mcc == peer->cfg->core_mcc) {
+ state->local_mcc = 0;
+ } else {
+ state->local_mcc = raid.mcc;
+ }
+
+ if (!peer->cfg->core_mnc || raid.mnc == peer->cfg->core_mnc) {
+ state->local_mnc = 0;
+ } else {
+ state->local_mnc = raid.mnc;
+ }
+
+ if (old_local_mcc != state->local_mcc ||
+ old_local_mnc != state->local_mnc)
+ LOGP(DGPRS, LOGL_NOTICE,
+ "Patching RAID %sactivated, msg: %s, "
+ "local: %d-%d, core: %d-%d\n",
+ state->local_mcc || state->local_mnc ?
+ "" : "de",
+ log_text,
+ state->local_mcc, state->local_mnc,
+ peer->cfg->core_mcc, peer->cfg->core_mnc);
+}
+
+uint32_t gbproxy_make_bss_ptmsi(struct gbproxy_peer *peer,
+ uint32_t sgsn_ptmsi)
+{
+ uint32_t bss_ptmsi;
+ int max_retries = 23;
+ if (!peer->cfg->patch_ptmsi) {
+ bss_ptmsi = sgsn_ptmsi;
+ } else {
+ do {
+ if (RAND_bytes((uint8_t *) &bss_ptmsi, sizeof(bss_ptmsi)) != 1) {
+ bss_ptmsi = GSM_RESERVED_TMSI;
+ break;
+ }
+
+ bss_ptmsi = bss_ptmsi | 0xC0000000;
+
+ if (gbproxy_link_info_by_ptmsi(peer, bss_ptmsi))
+ bss_ptmsi = GSM_RESERVED_TMSI;
+ } while (bss_ptmsi == GSM_RESERVED_TMSI && max_retries--);
+ }
+
+ if (bss_ptmsi == GSM_RESERVED_TMSI)
+ LOGP(DGPRS, LOGL_ERROR, "Failed to allocate a BSS P-TMSI\n");
+
+ return bss_ptmsi;
+}
+
+uint32_t gbproxy_make_sgsn_tlli(struct gbproxy_peer *peer,
+ struct gbproxy_link_info *link_info,
+ uint32_t bss_tlli)
+{
+ uint32_t sgsn_tlli;
+ int max_retries = 23;
+ if (!peer->cfg->patch_ptmsi) {
+ sgsn_tlli = bss_tlli;
+ } else if (link_info->sgsn_tlli.ptmsi != GSM_RESERVED_TMSI &&
+ gprs_tlli_type(bss_tlli) == TLLI_FOREIGN) {
+ sgsn_tlli = gprs_tmsi2tlli(link_info->sgsn_tlli.ptmsi,
+ TLLI_FOREIGN);
+ } else if (link_info->sgsn_tlli.ptmsi != GSM_RESERVED_TMSI &&
+ gprs_tlli_type(bss_tlli) == TLLI_LOCAL) {
+ sgsn_tlli = gprs_tmsi2tlli(link_info->sgsn_tlli.ptmsi,
+ TLLI_LOCAL);
+ } else {
+ do {
+ /* create random TLLI, 0b01111xxx... */
+ if (RAND_bytes((uint8_t *) &sgsn_tlli, sizeof(sgsn_tlli)) != 1) {
+ sgsn_tlli = 0;
+ break;
+ }
+
+ sgsn_tlli = (sgsn_tlli & 0x7fffffff) | 0x78000000;
+
+ if (gbproxy_link_info_by_any_sgsn_tlli(peer, sgsn_tlli))
+ sgsn_tlli = 0;
+ } while (!sgsn_tlli && max_retries--);
+ }
+
+ if (!sgsn_tlli)
+ LOGP(DGPRS, LOGL_ERROR, "Failed to allocate an SGSN TLLI\n");
+
+ return sgsn_tlli;
+}
+
+void gbproxy_reset_link(struct gbproxy_link_info *link_info)
+{
+ gbproxy_reset_imsi_acquisition(link_info);
+}
+
+/* Returns != 0 iff IMSI acquisition was in progress */
+static int gbproxy_restart_imsi_acquisition(struct gbproxy_link_info* link_info)
+{
+ int in_progress = 0;
+ if (!link_info)
+ return 0;
+
+ if (link_info->imsi_acq_pending)
+ in_progress = 1;
+
+ gbproxy_link_info_discard_messages(link_info);
+ link_info->imsi_acq_pending = 0;
+
+ return in_progress;
+}
+
+static void gbproxy_reset_imsi_acquisition(struct gbproxy_link_info* link_info)
+{
+ gbproxy_restart_imsi_acquisition(link_info);
+ link_info->vu_gen_tx_bss = GBPROXY_INIT_VU_GEN_TX;
+}
+
+static int gbproxy_flush_stored_messages(struct gbproxy_peer *peer,
+ struct msgb *msg,
+ time_t now,
+ struct gbproxy_link_info* link_info,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ int rc;
+ struct msgb *stored_msg;
+ /* Got identity response with IMSI, assuming the request had
+ * been generated by the gbproxy */
+
+ LOGP(DLLC, LOGL_DEBUG,
+ "NSEI=%d(BSS) IMSI acquisition succeeded, "
+ "flushing stored messages\n",
+ msgb_nsei(msg));
+
+ /* Patch and flush stored messages towards the SGSN */
+ while ((stored_msg = msgb_dequeue(&link_info->stored_msgs))) {
+ struct gprs_gb_parse_context tmp_parse_ctx = {0};
+ tmp_parse_ctx.to_bss = 0;
+ tmp_parse_ctx.peer_nsei = msgb_nsei(stored_msg);
+ int len_change = 0;
+
+ gprs_gb_parse_bssgp(msgb_bssgph(stored_msg),
+ msgb_bssgp_len(stored_msg),
+ &tmp_parse_ctx);
+ gbproxy_patch_bssgp(msg, msgb_bssgph(stored_msg),
+ msgb_bssgp_len(stored_msg),
+ peer, link_info, &len_change,
+ &tmp_parse_ctx);
+
+ rc = gbproxy_update_link_state_after(peer, link_info, now,
+ &tmp_parse_ctx);
+ if (rc == 1) {
+ LOGP(DLLC, LOGL_NOTICE, "link_info deleted while flushing stored messages\n");
+ msgb_free(stored_msg);
+ return -1;
+ }
+
+ rc = gbprox_relay2sgsn(peer->cfg, stored_msg,
+ msgb_bvci(msg), link_info->sgsn_nsei);
+
+ if (rc < 0)
+ LOGP(DLLC, LOGL_ERROR,
+ "NSEI=%d(BSS) failed to send stored message "
+ "(%s)\n",
+ msgb_nsei(msg),
+ parse_ctx->llc_msg_name ?
+ parse_ctx->llc_msg_name : "BSSGP");
+ msgb_free(stored_msg);
+ }
+
+ return 0;
+}
+
+static int gbproxy_gsm48_to_peer(struct gbproxy_peer *peer,
+ struct gbproxy_link_info* link_info,
+ uint16_t bvci,
+ struct msgb *msg /* Takes msg ownership */)
+{
+ int rc;
+
+ /* Workaround to avoid N(U) collisions and to enable a restart
+ * of the IMSI acquisition procedure. This will work unless the
+ * SGSN has an initial V(UT) within [256-32, 256+n_retries]
+ * (see GSM 04.64, 8.4.2). */
+ gprs_push_llc_ui(msg, 0, GPRS_SAPI_GMM, link_info->vu_gen_tx_bss);
+ link_info->vu_gen_tx_bss = (link_info->vu_gen_tx_bss + 1) % 512;
+
+ gprs_push_bssgp_dl_unitdata(msg, link_info->tlli.current);
+ rc = gbprox_relay2peer(msg, peer, bvci);
+ msgb_free(msg);
+ return rc;
+}
+
+static void gbproxy_acquire_imsi(struct gbproxy_peer *peer,
+ struct gbproxy_link_info* link_info,
+ uint16_t bvci)
+{
+ struct msgb *idreq_msg;
+
+ /* Send IDENT REQ */
+ idreq_msg = gsm48_msgb_alloc_name("GSM 04.08 ACQ IMSI");
+ gprs_put_identity_req(idreq_msg, GSM_MI_TYPE_IMSI);
+ gbproxy_gsm48_to_peer(peer, link_info, bvci, idreq_msg);
+}
+
+static void gbproxy_tx_detach_acc(struct gbproxy_peer *peer,
+ struct gbproxy_link_info* link_info,
+ uint16_t bvci)
+{
+ struct msgb *detacc_msg;
+
+ /* Send DETACH ACC */
+ detacc_msg = gsm48_msgb_alloc_name("GSM 04.08 DET ACC");
+ gprs_put_mo_detach_acc(detacc_msg);
+ gbproxy_gsm48_to_peer(peer, link_info, bvci, detacc_msg);
+}
+
+/* Return != 0 iff msg still needs to be processed */
+static int gbproxy_imsi_acquisition(struct gbproxy_peer *peer,
+ struct msgb *msg,
+ time_t now,
+ struct gbproxy_link_info* link_info,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ struct msgb *stored_msg;
+
+ if (!link_info)
+ return 1;
+
+ if (!link_info->imsi_acq_pending && link_info->imsi_len > 0)
+ return 1;
+
+ if (parse_ctx->g48_hdr)
+ switch (parse_ctx->g48_hdr->msg_type)
+ {
+ case GSM48_MT_GMM_RA_UPD_REQ:
+ case GSM48_MT_GMM_ATTACH_REQ:
+ if (gbproxy_restart_imsi_acquisition(link_info)) {
+ LOGP(DLLC, LOGL_INFO,
+ "NSEI=%d(BSS) IMSI acquisition was in progress "
+ "when receiving an %s.\n",
+ msgb_nsei(msg), parse_ctx->llc_msg_name);
+ }
+ break;
+ case GSM48_MT_GMM_DETACH_REQ:
+ /* Nothing has been sent to the SGSN yet */
+ if (link_info->imsi_acq_pending) {
+ LOGP(DLLC, LOGL_INFO,
+ "NSEI=%d(BSS) IMSI acquisition was in progress "
+ "when receiving a DETACH_REQ.\n",
+ msgb_nsei(msg));
+ }
+ if (!parse_ctx->invalidate_tlli) {
+ LOGP(DLLC, LOGL_INFO,
+ "NSEI=%d(BSS) IMSI not yet acquired, "
+ "faking a DETACH_ACC.\n",
+ msgb_nsei(msg));
+ gbproxy_tx_detach_acc(peer, link_info, msgb_bvci(msg));
+ parse_ctx->invalidate_tlli = 1;
+ }
+ gbproxy_reset_imsi_acquisition(link_info);
+ gbproxy_update_link_state_after(peer, link_info, now,
+ parse_ctx);
+ return 0;
+ }
+
+ if (link_info->imsi_acq_pending && link_info->imsi_len > 0) {
+ int is_ident_resp =
+ parse_ctx->g48_hdr &&
+ gsm48_hdr_pdisc(parse_ctx->g48_hdr) == GSM48_PDISC_MM_GPRS &&
+ gsm48_hdr_msg_type(parse_ctx->g48_hdr) == GSM48_MT_GMM_ID_RESP;
+
+ /* The IMSI is now available. If flushing the messages fails,
+ * then link_info has been deleted and we should return
+ * immediately. */
+ if (gbproxy_flush_stored_messages(peer, msg, now, link_info,
+ parse_ctx) < 0)
+ return 0;
+
+ gbproxy_reset_imsi_acquisition(link_info);
+
+ /* This message is most probably the response to the ident
+ * request sent by gbproxy_acquire_imsi(). Don't forward it to
+ * the SGSN. */
+ return !is_ident_resp;
+ }
+
+ /* The message cannot be processed since the IMSI is still missing */
+
+ /* Enqueue unpatched messages */
+ LOGP(DLLC, LOGL_INFO,
+ "NSEI=%d(BSS) IMSI acquisition in progress, "
+ "storing message (%s)\n",
+ msgb_nsei(msg),
+ parse_ctx->llc_msg_name ? parse_ctx->llc_msg_name : "BSSGP");
+
+ stored_msg = gprs_msgb_copy(msg, "process_bssgp_ul");
+ msgb_enqueue(&link_info->stored_msgs, stored_msg);
+
+ if (!link_info->imsi_acq_pending) {
+ LOGP(DLLC, LOGL_INFO,
+ "NSEI=%d(BSS) IMSI is required but not available, "
+ "initiating identification procedure (%s)\n",
+ msgb_nsei(msg),
+ parse_ctx->llc_msg_name ? parse_ctx->llc_msg_name : "BSSGP");
+
+ gbproxy_acquire_imsi(peer, link_info, msgb_bvci(msg));
+
+ /* There is no explicit retransmission handling, the
+ * implementation relies on the MS doing proper retransmissions
+ * of the triggering message instead */
+
+ link_info->imsi_acq_pending = 1;
+ }
+
+ return 0;
+}
+
+struct gbproxy_peer *gbproxy_find_peer(struct gbproxy_config *cfg,
+ struct msgb *msg,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ struct gbproxy_peer *peer = NULL;
+
+ if (msgb_bvci(msg) >= 2)
+ peer = gbproxy_peer_by_bvci(cfg, msgb_bvci(msg));
+
+ if (!peer && !parse_ctx->to_bss)
+ peer = gbproxy_peer_by_nsei(cfg, msgb_nsei(msg));
+
+ if (!peer)
+ peer = gbproxy_peer_by_bssgp_tlv(cfg, &parse_ctx->bssgp_tp);
+
+ if (!peer) {
+ LOGP(DLLC, LOGL_INFO,
+ "NSEI=%d(%s) patching: didn't find peer for message, "
+ "PDU %d\n",
+ msgb_nsei(msg), parse_ctx->to_bss ? "BSS" : "SGSN",
+ parse_ctx->pdu_type);
+ /* Increment counter */
+ rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_PATCH_PEER_ERR]);
+ }
+ return peer;
+}
+
+/* patch BSSGP message */
+static int gbprox_process_bssgp_ul(struct gbproxy_config *cfg,
+ struct msgb *msg,
+ struct gbproxy_peer *peer)
+{
+ struct gprs_gb_parse_context parse_ctx = {0};
+ int rc;
+ int len_change = 0;
+ time_t now;
+ struct timespec ts = {0,};
+ struct gbproxy_link_info *link_info = NULL;
+ uint32_t sgsn_nsei = cfg->nsip_sgsn_nsei;
+
+ if (!cfg->core_mcc && !cfg->core_mnc && !cfg->core_apn &&
+ !cfg->acquire_imsi && !cfg->patch_ptmsi && !cfg->route_to_sgsn2)
+ return 1;
+
+ parse_ctx.to_bss = 0;
+ parse_ctx.peer_nsei = msgb_nsei(msg);
+
+ /* Parse BSSGP/LLC */
+ rc = gprs_gb_parse_bssgp(msgb_bssgph(msg), msgb_bssgp_len(msg),
+ &parse_ctx);
+
+ if (!rc && !parse_ctx.need_decryption) {
+ LOGP(DGPRS, LOGL_ERROR,
+ "NSEI=%u(BSS) patching: failed to parse invalid %s message\n",
+ msgb_nsei(msg), gprs_gb_message_name(&parse_ctx, "NS_UNITDATA"));
+ gprs_gb_log_parse_context(LOGL_NOTICE, &parse_ctx, "NS_UNITDATA");
+ LOGP(DGPRS, LOGL_NOTICE,
+ "NSEI=%u(BSS) invalid message was: %s\n",
+ msgb_nsei(msg), msgb_hexdump(msg));
+ return 0;
+ }
+
+ /* Get peer */
+ if (!peer)
+ peer = gbproxy_find_peer(cfg, msg, &parse_ctx);
+
+ if (!peer)
+ return 0;
+
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ now = ts.tv_sec;
+
+ gbprox_update_current_raid(parse_ctx.bssgp_raid_enc, peer,
+ parse_ctx.llc_msg_name);
+
+ gprs_gb_log_parse_context(LOGL_DEBUG, &parse_ctx, "NS_UNITDATA");
+
+ link_info = gbproxy_update_link_state_ul(peer, now, &parse_ctx);
+
+ if (parse_ctx.g48_hdr) {
+ switch (parse_ctx.g48_hdr->msg_type) {
+ case GSM48_MT_GMM_ATTACH_REQ:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_ATTACH_REQS]);
+ break;
+ case GSM48_MT_GMM_DETACH_REQ:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_DETACH_REQS]);
+ break;
+ case GSM48_MT_GMM_ATTACH_COMPL:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_ATTACH_COMPLS]);
+ break;
+ case GSM48_MT_GMM_RA_UPD_REQ:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_RA_UPD_REQS]);
+ break;
+ case GSM48_MT_GMM_RA_UPD_COMPL:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_RA_UPD_COMPLS]);
+ break;
+ case GSM48_MT_GMM_STATUS:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_GMM_STATUS_BSS]);
+ break;
+ case GSM48_MT_GSM_ACT_PDP_REQ:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_PDP_ACT_REQS]);
+ break;
+ case GSM48_MT_GSM_DEACT_PDP_REQ:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_PDP_DEACT_REQS]);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (link_info && cfg->route_to_sgsn2) {
+ if (cfg->acquire_imsi && link_info->imsi_len == 0)
+ sgsn_nsei = 0xffff;
+ else if (gbproxy_imsi_matches(cfg, GBPROX_MATCH_ROUTING,
+ link_info))
+ sgsn_nsei = cfg->nsip_sgsn2_nsei;
+ }
+
+ if (link_info)
+ link_info->sgsn_nsei = sgsn_nsei;
+
+ /* Handle IMSI acquisition */
+ if (cfg->acquire_imsi) {
+ rc = gbproxy_imsi_acquisition(peer, msg, now, link_info,
+ &parse_ctx);
+ if (rc <= 0)
+ return rc;
+ }
+
+ gbproxy_patch_bssgp(msg, msgb_bssgph(msg), msgb_bssgp_len(msg),
+ peer, link_info, &len_change, &parse_ctx);
+
+ gbproxy_update_link_state_after(peer, link_info, now, &parse_ctx);
+
+ if (sgsn_nsei != cfg->nsip_sgsn_nsei) {
+ /* Send message directly to the selected SGSN */
+ rc = gbprox_relay2sgsn(cfg, msg, msgb_bvci(msg), sgsn_nsei);
+ /* Don't let the calling code handle the transmission */
+ return 0;
+ }
+
+ return 1;
+}
+
+/* patch BSSGP message to use core_mcc/mnc on the SGSN side */
+static void gbprox_process_bssgp_dl(struct gbproxy_config *cfg,
+ struct msgb *msg,
+ struct gbproxy_peer *peer)
+{
+ struct gprs_gb_parse_context parse_ctx = {0};
+ int rc;
+ int len_change = 0;
+ time_t now;
+ struct timespec ts = {0,};
+ struct gbproxy_link_info *link_info = NULL;
+
+ if (!cfg->core_mcc && !cfg->core_mnc && !cfg->core_apn &&
+ !cfg->acquire_imsi && !cfg->patch_ptmsi && !cfg->route_to_sgsn2)
+ return;
+
+ parse_ctx.to_bss = 1;
+ parse_ctx.peer_nsei = msgb_nsei(msg);
+
+ rc = gprs_gb_parse_bssgp(msgb_bssgph(msg), msgb_bssgp_len(msg),
+ &parse_ctx);
+
+ if (!rc && !parse_ctx.need_decryption) {
+ LOGP(DGPRS, LOGL_ERROR,
+ "NSEI=%u(SGSN) patching: failed to parse invalid %s message\n",
+ msgb_nsei(msg), gprs_gb_message_name(&parse_ctx, "NS_UNITDATA"));
+ gprs_gb_log_parse_context(LOGL_NOTICE, &parse_ctx, "NS_UNITDATA");
+ LOGP(DGPRS, LOGL_NOTICE,
+ "NSEI=%u(SGSN) invalid message was: %s\n",
+ msgb_nsei(msg), msgb_hexdump(msg));
+ return;
+ }
+
+ /* Get peer */
+ if (!peer)
+ peer = gbproxy_find_peer(cfg, msg, &parse_ctx);
+
+ if (!peer)
+ return;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ now = ts.tv_sec;
+
+ if (parse_ctx.g48_hdr) {
+ switch (parse_ctx.g48_hdr->msg_type) {
+ case GSM48_MT_GMM_ATTACH_ACK:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_ATTACH_ACKS]);
+ break;
+ case GSM48_MT_GMM_ATTACH_REJ:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_ATTACH_REJS]);
+ break;
+ case GSM48_MT_GMM_DETACH_ACK:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_DETACH_ACKS]);
+ break;
+ case GSM48_MT_GMM_RA_UPD_ACK:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_RA_UPD_ACKS]);
+ break;
+ case GSM48_MT_GMM_RA_UPD_REJ:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_RA_UPD_REJS]);
+ break;
+ case GSM48_MT_GMM_STATUS:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_GMM_STATUS_SGSN]);
+ break;
+ case GSM48_MT_GSM_ACT_PDP_ACK:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_PDP_ACT_ACKS]);
+ break;
+ case GSM48_MT_GSM_ACT_PDP_REJ:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_PDP_ACT_REJS]);
+ break;
+ case GSM48_MT_GSM_DEACT_PDP_ACK:
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_PDP_DEACT_ACKS]);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ gprs_gb_log_parse_context(LOGL_DEBUG, &parse_ctx, "NS_UNITDATA");
+
+ link_info = gbproxy_update_link_state_dl(peer, now, &parse_ctx);
+
+ gbproxy_patch_bssgp(msg, msgb_bssgph(msg), msgb_bssgp_len(msg),
+ peer, link_info, &len_change, &parse_ctx);
+
+ gbproxy_update_link_state_after(peer, link_info, now, &parse_ctx);
+
+ return;
+}
+
+/* feed a message down the NS-VC associated with the specified peer */
+static int gbprox_relay2sgsn(struct gbproxy_config *cfg, struct msgb *old_msg,
+ uint16_t ns_bvci, uint16_t sgsn_nsei)
+{
+ /* create a copy of the message so the old one can
+ * be free()d safely when we return from gbprox_rcvmsg() */
+ struct msgb *msg = gprs_msgb_copy(old_msg, "msgb_relay2sgsn");
+ int rc;
+
+ DEBUGP(DGPRS, "NSEI=%u proxying BTS->SGSN (NS_BVCI=%u, NSEI=%u)\n",
+ msgb_nsei(msg), ns_bvci, sgsn_nsei);
+
+ msgb_bvci(msg) = ns_bvci;
+ msgb_nsei(msg) = sgsn_nsei;
+
+ strip_ns_hdr(msg);
+
+ rc = gprs_ns_sendmsg(bssgp_nsi, msg);
+ if (rc < 0)
+ rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_TX_ERR_SGSN]);
+
+ return rc;
+}
+
+/* feed a message down the NS-VC associated with the specified peer */
+static int gbprox_relay2peer(struct msgb *old_msg, struct gbproxy_peer *peer,
+ uint16_t ns_bvci)
+{
+ /* create a copy of the message so the old one can
+ * be free()d safely when we return from gbprox_rcvmsg() */
+ struct msgb *msg = gprs_msgb_copy(old_msg, "msgb_relay2peer");
+ int rc;
+
+ DEBUGP(DGPRS, "NSEI=%u proxying SGSN->BSS (NS_BVCI=%u, NSEI=%u)\n",
+ msgb_nsei(msg), ns_bvci, peer->nsei);
+
+ msgb_bvci(msg) = ns_bvci;
+ msgb_nsei(msg) = peer->nsei;
+
+ /* Strip the old NS header, it will be replaced with a new one */
+ strip_ns_hdr(msg);
+
+ rc = gprs_ns_sendmsg(bssgp_nsi, msg);
+ if (rc < 0)
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_TX_ERR]);
+
+ return rc;
+}
+
+static int block_unblock_peer(struct gbproxy_config *cfg, uint16_t ptp_bvci, uint8_t pdu_type)
+{
+ struct gbproxy_peer *peer;
+
+ peer = gbproxy_peer_by_bvci(cfg, ptp_bvci);
+ if (!peer) {
+ LOGP(DGPRS, LOGL_ERROR, "BVCI=%u: Cannot find BSS\n",
+ ptp_bvci);
+ rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_INV_BVCI]);
+ return -ENOENT;
+ }
+
+ switch (pdu_type) {
+ case BSSGP_PDUT_BVC_BLOCK_ACK:
+ peer->blocked = 1;
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_BLOCKED]);
+ break;
+ case BSSGP_PDUT_BVC_UNBLOCK_ACK:
+ peer->blocked = 0;
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_UNBLOCKED]);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/* Send a message to a peer identified by ptp_bvci but using ns_bvci
+ * in the NS hdr */
+static int gbprox_relay2bvci(struct gbproxy_config *cfg, struct msgb *msg, uint16_t ptp_bvci,
+ uint16_t ns_bvci)
+{
+ struct gbproxy_peer *peer;
+
+ peer = gbproxy_peer_by_bvci(cfg, ptp_bvci);
+ if (!peer) {
+ LOGP(DGPRS, LOGL_ERROR, "BVCI=%u: Cannot find BSS\n",
+ ptp_bvci);
+ rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_INV_BVCI]);
+ return -ENOENT;
+ }
+
+ return gbprox_relay2peer(msg, peer, ns_bvci);
+}
+
+int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ return 0;
+}
+
+/* Receive an incoming PTP message from a BSS-side NS-VC */
+static int gbprox_rx_ptp_from_bss(struct gbproxy_config *cfg,
+ struct msgb *msg, uint16_t nsei,
+ uint16_t nsvci, uint16_t ns_bvci)
+{
+ struct gbproxy_peer *peer;
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+ uint8_t pdu_type = bgph->pdu_type;
+ int rc;
+
+ peer = gbproxy_peer_by_bvci(cfg, ns_bvci);
+ if (!peer) {
+ LOGP(DGPRS, LOGL_NOTICE, "Didn't find peer for "
+ "BVCI=%u for PTP message from NSVC=%u/NSEI=%u (BSS), "
+ "discarding message\n",
+ ns_bvci, nsvci, nsei);
+ return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI,
+ &ns_bvci, msg);
+ }
+
+ check_peer_nsei(peer, nsei);
+
+ rc = gbprox_process_bssgp_ul(cfg, msg, peer);
+ if (!rc)
+ return 0;
+
+ switch (pdu_type) {
+ case BSSGP_PDUT_FLOW_CONTROL_BVC:
+ if (!cfg->route_to_sgsn2)
+ break;
+
+ /* Send a copy to the secondary SGSN */
+ gbprox_relay2sgsn(cfg, msg, ns_bvci, cfg->nsip_sgsn2_nsei);
+ break;
+ default:
+ break;
+ }
+
+
+ return gbprox_relay2sgsn(cfg, msg, ns_bvci, cfg->nsip_sgsn_nsei);
+}
+
+/* Receive an incoming PTP message from a SGSN-side NS-VC */
+static int gbprox_rx_ptp_from_sgsn(struct gbproxy_config *cfg,
+ struct msgb *msg, uint16_t nsei,
+ uint16_t nsvci, uint16_t ns_bvci)
+{
+ struct gbproxy_peer *peer;
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+ uint8_t pdu_type = bgph->pdu_type;
+
+ peer = gbproxy_peer_by_bvci(cfg, ns_bvci);
+
+ /* Send status messages before patching */
+
+ if (!peer) {
+ LOGP(DGPRS, LOGL_INFO, "Didn't find peer for "
+ "BVCI=%u for message from NSVC=%u/NSEI=%u (SGSN)\n",
+ ns_bvci, nsvci, nsei);
+ rate_ctr_inc(&cfg->ctrg->
+ ctr[GBPROX_GLOB_CTR_INV_BVCI]);
+ return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI,
+ &ns_bvci, msg);
+ }
+
+ if (peer->blocked) {
+ LOGP(DGPRS, LOGL_NOTICE, "Dropping PDU for "
+ "blocked BVCI=%u via NSVC=%u/NSEI=%u\n",
+ ns_bvci, nsvci, nsei);
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_DROPPED]);
+ return bssgp_tx_status(BSSGP_CAUSE_BVCI_BLOCKED, &ns_bvci, msg);
+ }
+
+ switch (pdu_type) {
+ case BSSGP_PDUT_FLOW_CONTROL_BVC_ACK:
+ case BSSGP_PDUT_BVC_BLOCK_ACK:
+ case BSSGP_PDUT_BVC_UNBLOCK_ACK:
+ if (cfg->route_to_sgsn2 && nsei == cfg->nsip_sgsn2_nsei)
+ /* Hide ACKs from the secondary SGSN, the primary SGSN
+ * is responsible to send them. */
+ return 0;
+ break;
+ default:
+ break;
+ }
+
+ /* Optionally patch the message */
+ gbprox_process_bssgp_dl(cfg, msg, peer);
+
+ return gbprox_relay2peer(msg, peer, ns_bvci);
+}
+
+/* Receive an incoming signalling message from a BSS-side NS-VC */
+static int gbprox_rx_sig_from_bss(struct gbproxy_config *cfg,
+ struct msgb *msg, uint16_t nsei,
+ uint16_t ns_bvci)
+{
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+ struct tlv_parsed tp;
+ uint8_t pdu_type = bgph->pdu_type;
+ int data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+ struct gbproxy_peer *from_peer = NULL;
+ struct gprs_ra_id raid;
+ int copy_to_sgsn2 = 0;
+ int rc;
+
+ if (ns_bvci != 0 && ns_bvci != 1) {
+ LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u BVCI=%u is not signalling\n",
+ nsei, ns_bvci);
+ return -EINVAL;
+ }
+
+ /* we actually should never see those two for BVCI == 0, but double-check
+ * just to make sure */
+ if (pdu_type == BSSGP_PDUT_UL_UNITDATA ||
+ pdu_type == BSSGP_PDUT_DL_UNITDATA) {
+ LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u UNITDATA not allowed in "
+ "signalling\n", nsei);
+ return -EINVAL;
+ }
+
+ bssgp_tlv_parse(&tp, bgph->data, data_len);
+
+ switch (pdu_type) {
+ case BSSGP_PDUT_SUSPEND:
+ case BSSGP_PDUT_RESUME:
+ /* We implement RAI snooping during SUSPEND/RESUME, since it
+ * establishes a relationsip between BVCI/peer and the routeing
+ * area identification. The snooped information is then used
+ * for routing the {SUSPEND,RESUME}_[N]ACK back to the correct
+ * BSSGP */
+ if (!TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA))
+ goto err_mand_ie;
+ from_peer = gbproxy_peer_by_nsei(cfg, nsei);
+ if (!from_peer)
+ goto err_no_peer;
+ memcpy(from_peer->ra, TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA),
+ sizeof(from_peer->ra));
+ gsm48_parse_ra(&raid, from_peer->ra);
+ LOGP(DGPRS, LOGL_INFO, "NSEI=%u BSSGP SUSPEND/RESUME "
+ "RAI snooping: RAI %u-%u-%u-%u behind BVCI=%u\n",
+ nsei, raid.mcc, raid.mnc, raid.lac,
+ raid.rac , from_peer->bvci);
+ /* FIXME: This only supports one BSS per RA */
+ break;
+ case BSSGP_PDUT_BVC_RESET:
+ /* If we receive a BVC reset on the signalling endpoint, we
+ * don't want the SGSN to reset, as the signalling endpoint
+ * is common for all point-to-point BVCs (and thus all BTS) */
+ if (TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) {
+ uint16_t bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI));
+ LOGP(DGPRS, LOGL_INFO, "NSEI=%u Rx BVC RESET (BVCI=%u)\n",
+ nsei, bvci);
+ if (bvci == 0) {
+ /* FIXME: only do this if SGSN is alive! */
+ LOGP(DGPRS, LOGL_INFO, "NSEI=%u Tx fake "
+ "BVC RESET ACK of BVCI=0\n", nsei);
+ return bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK,
+ nsei, 0, ns_bvci);
+ }
+ from_peer = gbproxy_peer_by_bvci(cfg, bvci);
+ if (!from_peer) {
+ /* if a PTP-BVC is reset, and we don't know that
+ * PTP-BVCI yet, we should allocate a new peer */
+ LOGP(DGPRS, LOGL_INFO, "Allocationg new peer for "
+ "BVCI=%u via NSEI=%u\n", bvci, nsei);
+ from_peer = gbproxy_peer_alloc(cfg, bvci);
+ from_peer->nsei = nsei;
+ }
+
+ if (!check_peer_nsei(from_peer, nsei))
+ from_peer->nsei = nsei;
+
+ if (TLVP_PRESENT(&tp, BSSGP_IE_CELL_ID)) {
+ struct gprs_ra_id raid;
+ /* We have a Cell Identifier present in this
+ * PDU, this means we can extend our local
+ * state information about this particular cell
+ * */
+ memcpy(from_peer->ra,
+ TLVP_VAL(&tp, BSSGP_IE_CELL_ID),
+ sizeof(from_peer->ra));
+ gsm48_parse_ra(&raid, from_peer->ra);
+ LOGP(DGPRS, LOGL_INFO, "NSEI=%u/BVCI=%u "
+ "Cell ID %u-%u-%u-%u\n", nsei,
+ bvci, raid.mcc, raid.mnc, raid.lac,
+ raid.rac);
+ }
+ if (cfg->route_to_sgsn2)
+ copy_to_sgsn2 = 1;
+ }
+ break;
+ }
+
+ /* Normally, we can simply pass on all signalling messages from BSS to
+ * SGSN */
+ rc = gbprox_process_bssgp_ul(cfg, msg, from_peer);
+ if (!rc)
+ return 0;
+
+ if (copy_to_sgsn2)
+ gbprox_relay2sgsn(cfg, msg, ns_bvci, cfg->nsip_sgsn2_nsei);
+
+ return gbprox_relay2sgsn(cfg, msg, ns_bvci, cfg->nsip_sgsn_nsei);
+err_no_peer:
+ LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(BSS) cannot find peer based on NSEI\n",
+ nsei);
+ rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_INV_NSEI]);
+ return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, msg);
+err_mand_ie:
+ LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(BSS) missing mandatory RA IE\n",
+ nsei);
+ rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_PROTO_ERR_BSS]);
+ return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+}
+
+/* Receive paging request from SGSN, we need to relay to proper BSS */
+static int gbprox_rx_paging(struct gbproxy_config *cfg, struct msgb *msg, struct tlv_parsed *tp,
+ uint32_t nsei, uint16_t ns_bvci)
+{
+ struct gbproxy_peer *peer = NULL;
+ int errctr = GBPROX_GLOB_CTR_PROTO_ERR_SGSN;
+
+ LOGP(DGPRS, LOGL_INFO, "NSEI=%u(SGSN) BSSGP PAGING ",
+ nsei);
+ if (TLVP_PRESENT(tp, BSSGP_IE_BVCI)) {
+ uint16_t bvci = ntohs(tlvp_val16_unal(tp, BSSGP_IE_BVCI));
+ LOGPC(DGPRS, LOGL_INFO, "routing by BVCI to peer BVCI=%u\n",
+ bvci);
+ errctr = GBPROX_GLOB_CTR_OTHER_ERR;
+ } else if (TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA)) {
+ peer = gbproxy_peer_by_rai(cfg, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA));
+ LOGPC(DGPRS, LOGL_INFO, "routing by RAI to peer BVCI=%u\n",
+ peer ? peer->bvci : -1);
+ errctr = GBPROX_GLOB_CTR_INV_RAI;
+ } else if (TLVP_PRESENT(tp, BSSGP_IE_LOCATION_AREA)) {
+ peer = gbproxy_peer_by_lai(cfg, TLVP_VAL(tp, BSSGP_IE_LOCATION_AREA));
+ LOGPC(DGPRS, LOGL_INFO, "routing by LAI to peer BVCI=%u\n",
+ peer ? peer->bvci : -1);
+ errctr = GBPROX_GLOB_CTR_INV_LAI;
+ } else
+ LOGPC(DGPRS, LOGL_INFO, "\n");
+
+ if (!peer) {
+ LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(SGSN) BSSGP PAGING: "
+ "unable to route, missing IE\n", nsei);
+ rate_ctr_inc(&cfg->ctrg->ctr[errctr]);
+ return -EINVAL;
+ }
+ return gbprox_relay2peer(msg, peer, ns_bvci);
+}
+
+/* Receive an incoming BVC-RESET message from the SGSN */
+static int rx_reset_from_sgsn(struct gbproxy_config *cfg,
+ struct msgb *orig_msg,
+ struct msgb *msg, struct tlv_parsed *tp,
+ uint32_t nsei, uint16_t ns_bvci)
+{
+ struct gbproxy_peer *peer;
+ uint16_t ptp_bvci;
+
+ if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI)) {
+ rate_ctr_inc(&cfg->ctrg->
+ ctr[GBPROX_GLOB_CTR_PROTO_ERR_SGSN]);
+ return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE,
+ NULL, orig_msg);
+ }
+ ptp_bvci = ntohs(tlvp_val16_unal(tp, BSSGP_IE_BVCI));
+
+ if (ptp_bvci >= 2) {
+ /* A reset for a PTP BVC was received, forward it to its
+ * respective peer */
+ peer = gbproxy_peer_by_bvci(cfg, ptp_bvci);
+ if (!peer) {
+ LOGP(DGPRS, LOGL_ERROR, "NSEI=%u BVCI=%u: Cannot find BSS\n",
+ nsei, ptp_bvci);
+ rate_ctr_inc(&cfg->ctrg->
+ ctr[GBPROX_GLOB_CTR_INV_BVCI]);
+ return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI,
+ &ptp_bvci, orig_msg);
+ }
+ return gbprox_relay2peer(msg, peer, ns_bvci);
+ }
+
+ /* A reset for the Signalling entity has been received
+ * from the SGSN. As the signalling BVCI is shared
+ * among all the BSS's that we multiplex, it needs to
+ * be relayed */
+ llist_for_each_entry(peer, &cfg->bts_peers, list)
+ gbprox_relay2peer(msg, peer, ns_bvci);
+
+ return 0;
+}
+
+/* Receive an incoming signalling message from the SGSN-side NS-VC */
+static int gbprox_rx_sig_from_sgsn(struct gbproxy_config *cfg,
+ struct msgb *orig_msg, uint32_t nsei,
+ uint16_t ns_bvci)
+{
+ struct bssgp_normal_hdr *bgph =
+ (struct bssgp_normal_hdr *) msgb_bssgph(orig_msg);
+ struct tlv_parsed tp;
+ uint8_t pdu_type = bgph->pdu_type;
+ int data_len;
+ struct gbproxy_peer *peer;
+ uint16_t bvci;
+ struct msgb *msg;
+ int rc = 0;
+ int cause;
+
+ if (ns_bvci != 0 && ns_bvci != 1) {
+ LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u(SGSN) BVCI=%u is not "
+ "signalling\n", nsei, ns_bvci);
+ /* FIXME: Send proper error message */
+ return -EINVAL;
+ }
+
+ /* we actually should never see those two for BVCI == 0, but double-check
+ * just to make sure */
+ if (pdu_type == BSSGP_PDUT_UL_UNITDATA ||
+ pdu_type == BSSGP_PDUT_DL_UNITDATA) {
+ LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u(SGSN) UNITDATA not allowed in "
+ "signalling\n", nsei);
+ return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, orig_msg);
+ }
+
+ msg = gprs_msgb_copy(orig_msg, "rx_sig_from_sgsn");
+ gbprox_process_bssgp_dl(cfg, msg, NULL);
+ /* Update message info */
+ bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+ data_len = msgb_bssgp_len(orig_msg) - sizeof(*bgph);
+ rc = bssgp_tlv_parse(&tp, bgph->data, data_len);
+
+ switch (pdu_type) {
+ case BSSGP_PDUT_BVC_RESET:
+ rc = rx_reset_from_sgsn(cfg, msg, orig_msg, &tp, nsei, ns_bvci);
+ break;
+ case BSSGP_PDUT_BVC_RESET_ACK:
+ if (cfg->route_to_sgsn2 && nsei == cfg->nsip_sgsn2_nsei)
+ break;
+ /* fall through */
+ case BSSGP_PDUT_FLUSH_LL:
+ /* simple case: BVCI IE is mandatory */
+ if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI))
+ goto err_mand_ie;
+ bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI));
+ rc = gbprox_relay2bvci(cfg, msg, bvci, ns_bvci);
+ break;
+ case BSSGP_PDUT_PAGING_PS:
+ case BSSGP_PDUT_PAGING_CS:
+ /* process the paging request (LAI/RAI lookup) */
+ rc = gbprox_rx_paging(cfg, msg, &tp, nsei, ns_bvci);
+ break;
+ case BSSGP_PDUT_STATUS:
+ /* Some exception has occurred */
+ LOGP(DGPRS, LOGL_NOTICE,
+ "NSEI=%u(SGSN) BSSGP STATUS ", nsei);
+ if (!TLVP_PRESENT(&tp, BSSGP_IE_CAUSE)) {
+ LOGPC(DGPRS, LOGL_NOTICE, "\n");
+ goto err_mand_ie;
+ }
+ cause = *TLVP_VAL(&tp, BSSGP_IE_CAUSE);
+ LOGPC(DGPRS, LOGL_NOTICE,
+ "cause=0x%02x(%s) ", *TLVP_VAL(&tp, BSSGP_IE_CAUSE),
+ bssgp_cause_str(cause));
+ if (TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) {
+ bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI));
+ LOGPC(DGPRS, LOGL_NOTICE, "BVCI=%u\n", bvci);
+
+ if (cause == BSSGP_CAUSE_UNKNOWN_BVCI)
+ rc = gbprox_relay2bvci(cfg, msg, bvci, ns_bvci);
+ } else
+ LOGPC(DGPRS, LOGL_NOTICE, "\n");
+ break;
+ /* those only exist in the SGSN -> BSS direction */
+ case BSSGP_PDUT_SUSPEND_ACK:
+ case BSSGP_PDUT_SUSPEND_NACK:
+ case BSSGP_PDUT_RESUME_ACK:
+ case BSSGP_PDUT_RESUME_NACK:
+ /* RAI IE is mandatory */
+ if (!TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA))
+ goto err_mand_ie;
+ peer = gbproxy_peer_by_rai(cfg, TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA));
+ if (!peer)
+ goto err_no_peer;
+ rc = gbprox_relay2peer(msg, peer, ns_bvci);
+ break;
+ case BSSGP_PDUT_BVC_BLOCK_ACK:
+ case BSSGP_PDUT_BVC_UNBLOCK_ACK:
+ if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI))
+ goto err_mand_ie;
+ bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI));
+ if (bvci == 0) {
+ LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u(SGSN) BSSGP "
+ "%sBLOCK_ACK for signalling BVCI ?!?\n", nsei,
+ pdu_type == BSSGP_PDUT_BVC_UNBLOCK_ACK ? "UN":"");
+ /* should we send STATUS ? */
+ rate_ctr_inc(&cfg->ctrg->
+ ctr[GBPROX_GLOB_CTR_INV_BVCI]);
+ } else {
+ /* Mark BVC as (un)blocked */
+ block_unblock_peer(cfg, bvci, pdu_type);
+ }
+ rc = gbprox_relay2bvci(cfg, msg, bvci, ns_bvci);
+ break;
+ case BSSGP_PDUT_SGSN_INVOKE_TRACE:
+ LOGP(DGPRS, LOGL_ERROR,
+ "NSEI=%u(SGSN) BSSGP INVOKE TRACE not supported\n",nsei);
+ rate_ctr_inc(&cfg->ctrg->
+ ctr[GBPROX_GLOB_CTR_NOT_SUPPORTED_SGSN]);
+ rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, orig_msg);
+ break;
+ default:
+ LOGP(DGPRS, LOGL_NOTICE, "BSSGP PDU type 0x%02x unknown\n",
+ pdu_type);
+ rate_ctr_inc(&cfg->ctrg->
+ ctr[GBPROX_GLOB_CTR_PROTO_ERR_SGSN]);
+ rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, orig_msg);
+ break;
+ }
+
+ msgb_free(msg);
+
+ return rc;
+err_mand_ie:
+ LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(SGSN) missing mandatory IE\n",
+ nsei);
+ rate_ctr_inc(&cfg->ctrg->
+ ctr[GBPROX_GLOB_CTR_PROTO_ERR_SGSN]);
+ msgb_free(msg);
+ return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, orig_msg);
+err_no_peer:
+ LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(SGSN) cannot find peer based on RAI\n",
+ nsei);
+ rate_ctr_inc(&cfg->ctrg-> ctr[GBPROX_GLOB_CTR_INV_RAI]);
+ msgb_free(msg);
+ return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, orig_msg);
+}
+
+static int gbproxy_is_sgsn_nsei(struct gbproxy_config *cfg, uint16_t nsei)
+{
+ return nsei == cfg->nsip_sgsn_nsei ||
+ (cfg->route_to_sgsn2 && nsei == cfg->nsip_sgsn2_nsei);
+}
+
+/* Main input function for Gb proxy */
+int gbprox_rcvmsg(struct gbproxy_config *cfg, struct msgb *msg, uint16_t nsei,
+ uint16_t ns_bvci, uint16_t nsvci)
+{
+ int rc;
+ int remote_end_is_sgsn = gbproxy_is_sgsn_nsei(cfg, nsei);
+
+ /* Only BVCI=0 messages need special treatment */
+ if (ns_bvci == 0 || ns_bvci == 1) {
+ if (remote_end_is_sgsn)
+ rc = gbprox_rx_sig_from_sgsn(cfg, msg, nsei, ns_bvci);
+ else
+ rc = gbprox_rx_sig_from_bss(cfg, msg, nsei, ns_bvci);
+ } else {
+ /* All other BVCI are PTP */
+ if (remote_end_is_sgsn)
+ rc = gbprox_rx_ptp_from_sgsn(cfg, msg, nsei, nsvci,
+ ns_bvci);
+ else
+ rc = gbprox_rx_ptp_from_bss(cfg, msg, nsei, nsvci,
+ ns_bvci);
+ }
+
+ return rc;
+}
+
+int gbprox_reset_persistent_nsvcs(struct gprs_ns_inst *nsi)
+{
+ struct gprs_nsvc *nsvc;
+
+ llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+ if (!nsvc->persistent)
+ continue;
+ gprs_nsvc_reset(nsvc, NS_CAUSE_OM_INTERVENTION);
+ }
+ return 0;
+}
+
+/* Signal handler for signals from NS layer */
+int gbprox_signal(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gbproxy_config *cfg = handler_data;
+ struct ns_signal_data *nssd = signal_data;
+ struct gprs_nsvc *nsvc = nssd->nsvc;
+ struct gbproxy_peer *peer;
+ int remote_end_is_sgsn = gbproxy_is_sgsn_nsei(cfg, nsvc->nsei);
+
+ if (subsys != SS_L_NS)
+ return 0;
+
+ if (signal == S_NS_RESET && remote_end_is_sgsn) {
+ /* We have received a NS-RESET from the NSEI and NSVC
+ * of the SGSN. This might happen with SGSN that start
+ * their own NS-RESET procedure without waiting for our
+ * NS-RESET */
+ nsvc->remote_end_is_sgsn = 1;
+ }
+
+ if (signal == S_NS_ALIVE_EXP && nsvc->remote_end_is_sgsn) {
+ LOGP(DGPRS, LOGL_NOTICE, "Tns alive expired too often, "
+ "re-starting RESET procedure\n");
+ rate_ctr_inc(&cfg->ctrg->
+ ctr[GBPROX_GLOB_CTR_RESTART_RESET_SGSN]);
+ gprs_ns_nsip_connect(nsvc->nsi, &nsvc->ip.bts_addr,
+ nsvc->nsei, nsvc->nsvci);
+ }
+
+ if (!nsvc->remote_end_is_sgsn) {
+ /* from BSS to SGSN */
+ peer = gbproxy_peer_by_nsei(cfg, nsvc->nsei);
+ if (!peer) {
+ LOGP(DGPRS, LOGL_NOTICE, "signal %u for unknown peer "
+ "NSEI=%u/NSVCI=%u\n", signal, nsvc->nsei,
+ nsvc->nsvci);
+ return 0;
+ }
+ switch (signal) {
+ case S_NS_RESET:
+ case S_NS_BLOCK:
+ if (!peer->blocked)
+ break;
+ LOGP(DGPRS, LOGL_NOTICE, "Converting NS_RESET from "
+ "NSEI=%u/NSVCI=%u into BSSGP_BVC_BLOCK to SGSN\n",
+ nsvc->nsei, nsvc->nsvci);
+ bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_BLOCK, nsvc->nsei,
+ peer->bvci, 0);
+ break;
+ }
+ } else {
+ /* Forward this message to all NS-VC to BSS */
+ struct gprs_ns_inst *nsi = cfg->nsi;
+ struct gprs_nsvc *next_nsvc;
+
+ llist_for_each_entry(next_nsvc, &nsi->gprs_nsvcs, list) {
+ if (next_nsvc->remote_end_is_sgsn)
+ continue;
+
+ /* Note that the following does not start the full
+ * procedures including timer based retransmissions. */
+ switch (signal) {
+ case S_NS_RESET:
+ gprs_ns_tx_reset(next_nsvc, nssd->cause);
+ break;
+ case S_NS_BLOCK:
+ gprs_ns_tx_block(next_nsvc, nssd->cause);
+ break;
+ case S_NS_UNBLOCK:
+ gprs_ns_tx_unblock(next_nsvc);
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+void gbprox_reset(struct gbproxy_config *cfg)
+{
+ struct gbproxy_peer *peer, *tmp;
+
+ llist_for_each_entry_safe(peer, tmp, &cfg->bts_peers, list)
+ gbproxy_peer_free(peer);
+
+ rate_ctr_group_free(cfg->ctrg);
+ gbproxy_init_config(cfg);
+}
+
+int gbproxy_init_config(struct gbproxy_config *cfg)
+{
+ struct timespec tp;
+
+ INIT_LLIST_HEAD(&cfg->bts_peers);
+ cfg->ctrg = rate_ctr_group_alloc(tall_bsc_ctx, &global_ctrg_desc, 0);
+ clock_gettime(CLOCK_REALTIME, &tp);
+
+ return 0;
+}
diff --git a/src/gprs/gb_proxy_main.c b/src/gprs/gb_proxy_main.c
new file mode 100644
index 000000000..69a93b6f7
--- /dev/null
+++ b/src/gprs/gb_proxy_main.c
@@ -0,0 +1,315 @@
+/* NS-over-IP proxy */
+
+/* (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 <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/stats.h>
+
+#include <osmocom/gprs/gprs_ns.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/vty.h>
+#include <openbsc/gb_proxy.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/ports.h>
+
+#include "../../bscconfig.h"
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+void *tall_bsc_ctx;
+
+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";
+
+static char *config_file = "osmo_gbproxy.cfg";
+struct gbproxy_config gbcfg = {0};
+static int daemonize = 0;
+
+/* Pointer to the SGSN peer */
+extern struct gbprox_peer *gbprox_peer_sgsn;
+
+/* call-back function for the NS protocol */
+static int proxy_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:
+ rc = gbprox_rcvmsg(&gbcfg, msg, nsvc->nsei, bvci, nsvc->nsvci);
+ break;
+ default:
+ LOGP(DGPRS, LOGL_ERROR, "SGSN: Unknown event %u from NS\n", event);
+ if (msg)
+ msgb_free(msg);
+ rc = -EIO;
+ break;
+ }
+ return rc;
+}
+
+static void signal_handler(int signal)
+{
+ fprintf(stdout, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ 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_bsc_ctx, stderr);
+ break;
+ case SIGUSR2:
+ talloc_report_full(tall_vty_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+static void print_usage()
+{
+ printf("Usage: bsc_hack\n");
+}
+
+static void print_help()
+{
+ printf(" Some useful help...\n");
+ printf(" -h --help this text\n");
+ printf(" -d option --debug=DNS:DGPRS,0:0 enable debugging\n");
+ printf(" -D --daemonize Fork the process into a background daemon\n");
+ printf(" -c --config-file filename The config file to use.\n");
+ printf(" -s --disable-color\n");
+ printf(" -T --timestamp Prefix every log line with a timestamp\n");
+ printf(" -V --version. Print the version of OpenBSC.\n");
+ printf(" -e --log-level number. Set a global loglevel.\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' },
+ { 0, 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':
+ config_file = optarg;
+ break;
+ case 'T':
+ log_set_print_timestamp(osmo_stderr_target, 1);
+ break;
+ case 'e':
+ log_set_log_level(osmo_stderr_target, atoi(optarg));
+ break;
+ case 'V':
+ print_version(1);
+ exit(0);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+extern int bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+ .name = "OsmoGbProxy",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = bsc_vty_go_parent,
+ .is_config_node = bsc_vty_is_config_node,
+};
+
+/* default categories */
+static struct log_info_cat gprs_categories[] = {
+ [DGPRS] = {
+ .name = "DGPRS",
+ .description = "GPRS Packet Service",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DNS] = {
+ .name = "DNS",
+ .description = "GPRS Network Service (NS)",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DBSSGP] = {
+ .name = "DBSSGP",
+ .description = "GPRS BSS Gateway Protocol (BSSGP)",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+};
+
+static const struct log_info gprs_log_info = {
+ .filter_fn = gprs_log_filter_fn,
+ .cat = gprs_categories,
+ .num_cat = ARRAY_SIZE(gprs_categories),
+};
+
+int main(int argc, char **argv)
+{
+ struct gsm_network dummy_network;
+ int rc;
+
+ tall_bsc_ctx = talloc_named_const(NULL, 0, "nsip_proxy");
+ msgb_talloc_ctx_init(tall_bsc_ctx, 0);
+
+ signal(SIGINT, &signal_handler);
+ signal(SIGABRT, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+ osmo_init_ignore_signals();
+
+ osmo_init_logging(&gprs_log_info);
+
+ vty_info.copyright = openbsc_copyright;
+ vty_init(&vty_info);
+ logging_vty_add_cmds(NULL);
+ osmo_stats_vty_add_cmds(&gprs_log_info);
+ gbproxy_vty_init();
+
+ handle_options(argc, argv);
+
+ rate_ctr_init(tall_bsc_ctx);
+ osmo_stats_init(tall_bsc_ctx);
+
+ bssgp_nsi = gprs_ns_instantiate(&proxy_ns_cb, tall_bsc_ctx);
+ if (!bssgp_nsi) {
+ LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n");
+ exit(1);
+ }
+ gbproxy_init_config(&gbcfg);
+ gbcfg.nsi = bssgp_nsi;
+ gprs_ns_vty_init(bssgp_nsi);
+ gprs_ns_set_log_ss(DNS);
+ bssgp_set_log_ss(DBSSGP);
+ osmo_signal_register_handler(SS_L_NS, &gbprox_signal, &gbcfg);
+
+ rc = gbproxy_parse_config(config_file, &gbcfg);
+ if (rc < 0) {
+ LOGP(DGPRS, LOGL_FATAL, "Cannot parse config file\n");
+ exit(2);
+ }
+
+ /* start telnet after reading config for vty_get_bind_addr() */
+ rc = telnet_init_dynif(tall_bsc_ctx, &dummy_network,
+ vty_get_bind_addr(), OSMO_VTY_PORT_GBPROXY);
+ if (rc < 0)
+ exit(1);
+
+ if (!gprs_nsvc_by_nsei(gbcfg.nsi, gbcfg.nsip_sgsn_nsei)) {
+ LOGP(DGPRS, LOGL_FATAL, "You cannot proxy to NSEI %u "
+ "without creating that NSEI before\n",
+ gbcfg.nsip_sgsn_nsei);
+ exit(2);
+ }
+
+ rc = gprs_ns_nsip_listen(bssgp_nsi);
+ if (rc < 0) {
+ LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on NSIP socket\n");
+ exit(2);
+ }
+
+ rc = gprs_ns_frgre_listen(bssgp_nsi);
+ if (rc < 0) {
+ LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen GRE "
+ "socket. Do you have CAP_NET_RAW?\n");
+ exit(2);
+ }
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ /* Reset all the persistent NS-VCs that we've read from the config */
+ gbprox_reset_persistent_nsvcs(bssgp_nsi);
+
+ while (1) {
+ rc = osmo_select_main(0);
+ if (rc < 0)
+ exit(3);
+ }
+
+ exit(0);
+}
diff --git a/src/gprs/gb_proxy_patch.c b/src/gprs/gb_proxy_patch.c
new file mode 100644
index 000000000..7bddc4494
--- /dev/null
+++ b/src/gprs/gb_proxy_patch.c
@@ -0,0 +1,458 @@
+/* Gb-proxy message patching */
+
+/* (C) 2014 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 <openbsc/gb_proxy.h>
+
+#include <openbsc/gprs_utils.h>
+#include <openbsc/gprs_gb_parse.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+
+#include <osmocom/gprs/protocol/gsm_08_18.h>
+#include <osmocom/core/rate_ctr.h>
+
+/* patch RA identifier in place */
+static void gbproxy_patch_raid(uint8_t *raid_enc, struct gbproxy_peer *peer,
+ int to_bss, const char *log_text)
+{
+ struct gbproxy_patch_state *state = &peer->patch_state;
+ int old_mcc;
+ int old_mnc;
+ struct gprs_ra_id raid;
+ enum gbproxy_peer_ctr counter =
+ to_bss ?
+ GBPROX_PEER_CTR_RAID_PATCHED_SGSN :
+ GBPROX_PEER_CTR_RAID_PATCHED_BSS;
+
+ if (!state->local_mcc || !state->local_mnc)
+ return;
+
+ gsm48_parse_ra(&raid, raid_enc);
+
+ old_mcc = raid.mcc;
+ old_mnc = raid.mnc;
+
+ if (!to_bss) {
+ /* BSS -> SGSN */
+ if (state->local_mcc)
+ raid.mcc = peer->cfg->core_mcc;
+
+ if (state->local_mnc)
+ raid.mnc = peer->cfg->core_mnc;
+ } else {
+ /* SGSN -> BSS */
+ if (state->local_mcc)
+ raid.mcc = state->local_mcc;
+
+ if (state->local_mnc)
+ raid.mnc = state->local_mnc;
+ }
+
+ LOGP(DGPRS, LOGL_DEBUG,
+ "Patching %s to %s: "
+ "%d-%d-%d-%d -> %d-%d-%d-%d\n",
+ log_text,
+ to_bss ? "BSS" : "SGSN",
+ old_mcc, old_mnc, raid.lac, raid.rac,
+ raid.mcc, raid.mnc, raid.lac, raid.rac);
+
+ gsm48_construct_ra(raid_enc, &raid);
+ rate_ctr_inc(&peer->ctrg->ctr[counter]);
+}
+
+static void gbproxy_patch_apn_ie(struct msgb *msg,
+ uint8_t *apn_ie, size_t apn_ie_len,
+ struct gbproxy_peer *peer,
+ size_t *new_apn_ie_len, const char *log_text)
+{
+ struct apn_ie_hdr {
+ uint8_t iei;
+ uint8_t apn_len;
+ uint8_t apn[0];
+ } *hdr = (void *)apn_ie;
+
+ size_t apn_len = hdr->apn_len;
+ uint8_t *apn = hdr->apn;
+
+ OSMO_ASSERT(apn_ie_len == apn_len + sizeof(struct apn_ie_hdr));
+ OSMO_ASSERT(apn_ie_len > 2 && apn_ie_len <= 102);
+
+ if (peer->cfg->core_apn_size == 0) {
+ char str1[110];
+ /* Remove the IE */
+ LOGP(DGPRS, LOGL_DEBUG,
+ "Patching %s to SGSN: Removing APN '%s'\n",
+ log_text,
+ gprs_apn_to_str(str1, apn, apn_len));
+
+ *new_apn_ie_len = 0;
+ gprs_msgb_resize_area(msg, apn_ie, apn_ie_len, 0);
+ } else {
+ /* Resize the IE */
+ char str1[110];
+ char str2[110];
+
+ OSMO_ASSERT(peer->cfg->core_apn_size <= 100);
+
+ LOGP(DGPRS, LOGL_DEBUG,
+ "Patching %s to SGSN: "
+ "Replacing APN '%s' -> '%s'\n",
+ log_text,
+ gprs_apn_to_str(str1, apn, apn_len),
+ gprs_apn_to_str(str2, peer->cfg->core_apn,
+ peer->cfg->core_apn_size));
+
+ *new_apn_ie_len = peer->cfg->core_apn_size + 2;
+ gprs_msgb_resize_area(msg, apn, apn_len, peer->cfg->core_apn_size);
+ memcpy(apn, peer->cfg->core_apn, peer->cfg->core_apn_size);
+ hdr->apn_len = peer->cfg->core_apn_size;
+ }
+
+ rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_APN_PATCHED]);
+}
+
+static int gbproxy_patch_tlli(uint8_t *tlli_enc,
+ struct gbproxy_peer *peer,
+ uint32_t new_tlli,
+ int to_bss, const char *log_text)
+{
+ uint32_t tlli_be;
+ uint32_t tlli;
+ enum gbproxy_peer_ctr counter =
+ to_bss ?
+ GBPROX_PEER_CTR_TLLI_PATCHED_SGSN :
+ GBPROX_PEER_CTR_TLLI_PATCHED_BSS;
+
+ memcpy(&tlli_be, tlli_enc, sizeof(tlli_be));
+ tlli = ntohl(tlli_be);
+
+ if (tlli == new_tlli)
+ return 0;
+
+ LOGP(DGPRS, LOGL_DEBUG,
+ "Patching %ss: "
+ "Replacing %08x -> %08x\n",
+ log_text, tlli, new_tlli);
+
+ tlli_be = htonl(new_tlli);
+ memcpy(tlli_enc, &tlli_be, sizeof(tlli_be));
+
+ rate_ctr_inc(&peer->ctrg->ctr[counter]);
+
+ return 1;
+}
+
+static int gbproxy_patch_ptmsi(uint8_t *ptmsi_enc,
+ struct gbproxy_peer *peer,
+ uint32_t new_ptmsi,
+ int to_bss, const char *log_text)
+{
+ uint32_t ptmsi_be;
+ uint32_t ptmsi;
+ enum gbproxy_peer_ctr counter =
+ to_bss ?
+ GBPROX_PEER_CTR_PTMSI_PATCHED_SGSN :
+ GBPROX_PEER_CTR_PTMSI_PATCHED_BSS;
+ memcpy(&ptmsi_be, ptmsi_enc, sizeof(ptmsi_be));
+ ptmsi = ntohl(ptmsi_be);
+
+ if (ptmsi == new_ptmsi)
+ return 0;
+
+ LOGP(DGPRS, LOGL_DEBUG,
+ "Patching %ss: "
+ "Replacing %08x -> %08x\n",
+ log_text, ptmsi, new_ptmsi);
+
+ ptmsi_be = htonl(new_ptmsi);
+ memcpy(ptmsi_enc, &ptmsi_be, sizeof(ptmsi_be));
+
+ rate_ctr_inc(&peer->ctrg->ctr[counter]);
+
+ return 1;
+}
+
+int gbproxy_patch_llc(struct msgb *msg, uint8_t *llc, size_t llc_len,
+ struct gbproxy_peer *peer,
+ struct gbproxy_link_info *link_info, int *len_change,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ struct gprs_llc_hdr_parsed *ghp = &parse_ctx->llc_hdr_parsed;
+ int have_patched = 0;
+ int fcs;
+ struct gbproxy_config *cfg = peer->cfg;
+
+ if (parse_ctx->ptmsi_enc && link_info &&
+ !parse_ctx->old_raid_is_foreign && peer->cfg->patch_ptmsi) {
+ uint32_t ptmsi;
+ if (parse_ctx->to_bss)
+ ptmsi = link_info->tlli.ptmsi;
+ else
+ ptmsi = link_info->sgsn_tlli.ptmsi;
+
+ if (ptmsi != GSM_RESERVED_TMSI) {
+ if (gbproxy_patch_ptmsi(parse_ctx->ptmsi_enc, peer,
+ ptmsi, parse_ctx->to_bss, "P-TMSI"))
+ have_patched = 1;
+ } else {
+ /* TODO: invalidate old RAI if present (see below) */
+ }
+ }
+
+ if (parse_ctx->new_ptmsi_enc && link_info && cfg->patch_ptmsi) {
+ uint32_t ptmsi;
+ if (parse_ctx->to_bss)
+ ptmsi = link_info->tlli.ptmsi;
+ else
+ ptmsi = link_info->sgsn_tlli.ptmsi;
+
+ OSMO_ASSERT(ptmsi);
+ if (gbproxy_patch_ptmsi(parse_ctx->new_ptmsi_enc, peer,
+ ptmsi, parse_ctx->to_bss, "new P-TMSI"))
+ have_patched = 1;
+ }
+
+ if (parse_ctx->raid_enc) {
+ gbproxy_patch_raid(parse_ctx->raid_enc, peer, parse_ctx->to_bss,
+ parse_ctx->llc_msg_name);
+ have_patched = 1;
+ }
+
+ if (parse_ctx->old_raid_enc && !parse_ctx->old_raid_is_foreign) {
+ /* TODO: Patch to invalid if P-TMSI unknown. */
+ gbproxy_patch_raid(parse_ctx->old_raid_enc, peer, parse_ctx->to_bss,
+ parse_ctx->llc_msg_name);
+ have_patched = 1;
+ }
+
+ if (parse_ctx->apn_ie &&
+ cfg->core_apn &&
+ !parse_ctx->to_bss &&
+ gbproxy_imsi_matches(cfg, GBPROX_MATCH_PATCHING, link_info) &&
+ cfg->core_apn) {
+ size_t new_len;
+ gbproxy_patch_apn_ie(msg,
+ parse_ctx->apn_ie, parse_ctx->apn_ie_len,
+ peer, &new_len, parse_ctx->llc_msg_name);
+ *len_change += (int)new_len - (int)parse_ctx->apn_ie_len;
+
+ have_patched = 1;
+ }
+
+ if (have_patched) {
+ llc_len += *len_change;
+ ghp->crc_length += *len_change;
+
+ /* Fix FCS */
+ fcs = gprs_llc_fcs(llc, ghp->crc_length);
+ LOGP(DLLC, LOGL_DEBUG, "Updated LLC message, CRC: %06x -> %06x\n",
+ ghp->fcs, fcs);
+
+ llc[llc_len - 3] = fcs & 0xff;
+ llc[llc_len - 2] = (fcs >> 8) & 0xff;
+ llc[llc_len - 1] = (fcs >> 16) & 0xff;
+ }
+
+ return have_patched;
+}
+
+/* patch BSSGP message to use core_mcc/mnc on the SGSN side */
+void gbproxy_patch_bssgp(struct msgb *msg, uint8_t *bssgp, size_t bssgp_len,
+ struct gbproxy_peer *peer,
+ struct gbproxy_link_info *link_info, int *len_change,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ const char *err_info = NULL;
+ int err_ctr = -1;
+
+ if (parse_ctx->bssgp_raid_enc)
+ gbproxy_patch_raid(parse_ctx->bssgp_raid_enc, peer,
+ parse_ctx->to_bss, "BSSGP");
+
+ if (parse_ctx->need_decryption &&
+ (peer->cfg->patch_ptmsi || peer->cfg->core_apn)) {
+ /* Patching LLC messages has been requested
+ * explicitly, but the message (including the
+ * type) is encrypted, so we possibly fail to
+ * patch the LLC part of the message. */
+ err_ctr = GBPROX_PEER_CTR_PATCH_CRYPT_ERR;
+ err_info = "GMM message is encrypted";
+ goto patch_error;
+ }
+
+ if (!link_info && parse_ctx->tlli_enc && parse_ctx->to_bss) {
+ /* Happens with unknown (not cached) TLLI coming from
+ * the SGSN */
+ /* TODO: What shall be done with the message in this case? */
+ err_ctr = GBPROX_PEER_CTR_TLLI_UNKNOWN;
+ err_info = "TLLI sent by the SGSN is unknown";
+ goto patch_error;
+ }
+
+ if (!link_info)
+ return;
+
+ if (parse_ctx->tlli_enc && peer->cfg->patch_ptmsi) {
+ uint32_t tlli = gbproxy_map_tlli(parse_ctx->tlli,
+ link_info, parse_ctx->to_bss);
+
+ if (tlli) {
+ gbproxy_patch_tlli(parse_ctx->tlli_enc, peer, tlli,
+ parse_ctx->to_bss, "TLLI");
+ parse_ctx->tlli = tlli;
+ } else {
+ /* Internal error */
+ err_ctr = GBPROX_PEER_CTR_PATCH_ERR;
+ err_info = "Replacement TLLI is 0";
+ goto patch_error;
+ }
+ }
+
+ if (parse_ctx->bssgp_ptmsi_enc && peer->cfg->patch_ptmsi) {
+ uint32_t ptmsi;
+ if (parse_ctx->to_bss)
+ ptmsi = link_info->tlli.ptmsi;
+ else
+ ptmsi = link_info->sgsn_tlli.ptmsi;
+
+ if (ptmsi != GSM_RESERVED_TMSI)
+ gbproxy_patch_ptmsi(
+ parse_ctx->bssgp_ptmsi_enc, peer,
+ ptmsi, parse_ctx->to_bss, "BSSGP P-TMSI");
+ }
+
+ if (parse_ctx->llc) {
+ uint8_t *llc = parse_ctx->llc;
+ size_t llc_len = parse_ctx->llc_len;
+ int llc_len_change = 0;
+
+ gbproxy_patch_llc(msg, llc, llc_len, peer, link_info,
+ &llc_len_change, parse_ctx);
+ /* Note that the APN might have been resized here, but no
+ * pointer int the parse_ctx will refer to an adress after the
+ * APN. So it's possible to patch first and do the TLLI
+ * handling afterwards. */
+
+ if (llc_len_change) {
+ llc_len += llc_len_change;
+
+ /* Fix LLC IE len */
+ /* TODO: This is a kludge, but the a pointer to the
+ * start of the IE is not available here */
+ if (llc[-2] == BSSGP_IE_LLC_PDU && llc[-1] & 0x80) {
+ /* most probably a one byte length */
+ if (llc_len > 127) {
+ err_info = "Cannot increase size";
+ err_ctr = GBPROX_PEER_CTR_PATCH_ERR;
+ goto patch_error;
+ }
+ llc[-1] = llc_len | 0x80;
+ } else {
+ llc[-2] = (llc_len >> 8) & 0x7f;
+ llc[-1] = llc_len & 0xff;
+ }
+ *len_change += llc_len_change;
+ }
+ /* Note that the tp struct might contain invalid pointers here
+ * if the LLC field has changed its size */
+ parse_ctx->llc_len = llc_len;
+ }
+ return;
+
+patch_error:
+ OSMO_ASSERT(err_ctr >= 0);
+ rate_ctr_inc(&peer->ctrg->ctr[err_ctr]);
+ LOGP(DGPRS, LOGL_ERROR,
+ "NSEI=%u(%s) failed to patch BSSGP message as requested: %s.\n",
+ msgb_nsei(msg), parse_ctx->to_bss ? "SGSN" : "BSS",
+ err_info);
+}
+
+void gbproxy_clear_patch_filter(struct gbproxy_match *match)
+{
+ if (match->enable) {
+ regfree(&match->re_comp);
+ match->enable = 0;
+ }
+ talloc_free(match->re_str);
+ match->re_str = NULL;
+}
+
+int gbproxy_set_patch_filter(struct gbproxy_match *match, const char *filter,
+ const char **err_msg)
+{
+ static char err_buf[300];
+ int rc;
+
+ gbproxy_clear_patch_filter(match);
+
+ if (!filter)
+ return 0;
+
+ rc = regcomp(&match->re_comp, filter,
+ REG_EXTENDED | REG_NOSUB | REG_ICASE);
+
+ if (rc == 0) {
+ match->enable = 1;
+ match->re_str = talloc_strdup(tall_bsc_ctx, filter);
+ return 0;
+ }
+
+ if (err_msg) {
+ regerror(rc, &match->re_comp,
+ err_buf, sizeof(err_buf));
+ *err_msg = err_buf;
+ }
+
+ return -1;
+}
+
+int gbproxy_check_imsi(struct gbproxy_match *match,
+ const uint8_t *imsi, size_t imsi_len)
+{
+ char mi_buf[200];
+ int rc;
+
+ if (!match->enable)
+ return 1;
+
+ rc = gprs_is_mi_imsi(imsi, imsi_len);
+ if (rc > 0)
+ rc = gsm48_mi_to_string(mi_buf, sizeof(mi_buf), imsi, imsi_len);
+ if (rc <= 0) {
+ LOGP(DGPRS, LOGL_NOTICE, "Invalid IMSI %s\n",
+ osmo_hexdump(imsi, imsi_len));
+ return -1;
+ }
+
+ LOGP(DGPRS, LOGL_DEBUG, "Checking IMSI '%s' (%d)\n", mi_buf, rc);
+
+ rc = regexec(&match->re_comp, mi_buf, 0, NULL, 0);
+ if (rc == REG_NOMATCH) {
+ LOGP(DGPRS, LOGL_INFO,
+ "IMSI '%s' doesn't match pattern '%s'\n",
+ mi_buf, match->re_str);
+ return 0;
+ }
+
+ return 1;
+}
+
diff --git a/src/gprs/gb_proxy_peer.c b/src/gprs/gb_proxy_peer.c
new file mode 100644
index 000000000..5365ff0fa
--- /dev/null
+++ b/src/gprs/gb_proxy_peer.c
@@ -0,0 +1,218 @@
+/* Gb proxy peer handling */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2013 by On-Waves
+ * (C) 2013 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 <openbsc/gb_proxy.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_data_shared.h>
+#include <openbsc/debug.h>
+
+#include <osmocom/gprs/protocol/gsm_08_18.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/talloc.h>
+
+#include <string.h>
+
+static const struct rate_ctr_desc peer_ctr_description[] = {
+ { "blocked", "BVC Block " },
+ { "unblocked", "BVC Unblock " },
+ { "dropped", "BVC blocked, dropped packet " },
+ { "inv-nsei", "NSEI mismatch " },
+ { "tx-err", "NS Transmission error " },
+ { "raid-mod.bss", "RAID patched (BSS )" },
+ { "raid-mod.sgsn", "RAID patched (SGSN)" },
+ { "apn-mod.sgsn", "APN patched " },
+ { "tlli-mod.bss", "TLLI patched (BSS )" },
+ { "tlli-mod.sgsn", "TLLI patched (SGSN)" },
+ { "ptmsi-mod.bss", "P-TMSI patched (BSS )" },
+ { "ptmsi-mod.sgsn","P-TMSI patched (SGSN)" },
+ { "mod-crypt-err", "Patch error: encrypted " },
+ { "mod-err", "Patch error: other " },
+ { "attach-reqs", "Attach Request count " },
+ { "attach-rejs", "Attach Reject count " },
+ { "attach-acks", "Attach Accept count " },
+ { "attach-cpls", "Attach Completed count " },
+ { "ra-upd-reqs", "RoutingArea Update Request count" },
+ { "ra-upd-rejs", "RoutingArea Update Reject count " },
+ { "ra-upd-acks", "RoutingArea Update Accept count " },
+ { "ra-upd-cpls", "RoutingArea Update Compltd count" },
+ { "gmm-status", "GMM Status count (BSS)" },
+ { "gmm-status", "GMM Status count (SGSN)" },
+ { "detach-reqs", "Detach Request count " },
+ { "detach-acks", "Detach Accept count " },
+ { "pdp-act-reqs", "PDP Activation Request count " },
+ { "pdp-act-rejs", "PDP Activation Reject count " },
+ { "pdp-act-acks", "PDP Activation Accept count " },
+ { "pdp-deact-reqs","PDP Deactivation Request count " },
+ { "pdp-deact-acks","PDP Deactivation Accept count " },
+ { "tlli-unknown", "TLLI from SGSN unknown " },
+ { "tlli-cache", "TLLI cache size " },
+};
+
+osmo_static_assert(ARRAY_SIZE(peer_ctr_description) == GBPROX_PEER_CTR_LAST, everything_described);
+
+static const struct rate_ctr_group_desc peer_ctrg_desc = {
+ .group_name_prefix = "gbproxy.peer",
+ .group_description = "GBProxy Peer Statistics",
+ .num_ctr = ARRAY_SIZE(peer_ctr_description),
+ .ctr_desc = peer_ctr_description,
+ .class_id = OSMO_STATS_CLASS_PEER,
+};
+
+
+/* Find the gbprox_peer by its BVCI */
+struct gbproxy_peer *gbproxy_peer_by_bvci(struct gbproxy_config *cfg, uint16_t bvci)
+{
+ struct gbproxy_peer *peer;
+ llist_for_each_entry(peer, &cfg->bts_peers, list) {
+ if (peer->bvci == bvci)
+ return peer;
+ }
+ return NULL;
+}
+
+/* Find the gbprox_peer by its NSEI */
+struct gbproxy_peer *gbproxy_peer_by_nsei(struct gbproxy_config *cfg,
+ uint16_t nsei)
+{
+ struct gbproxy_peer *peer;
+ llist_for_each_entry(peer, &cfg->bts_peers, list) {
+ if (peer->nsei == nsei)
+ return peer;
+ }
+ return NULL;
+}
+
+/* look-up a peer by its Routeing Area Identification (RAI) */
+struct gbproxy_peer *gbproxy_peer_by_rai(struct gbproxy_config *cfg,
+ const uint8_t *ra)
+{
+ struct gbproxy_peer *peer;
+ llist_for_each_entry(peer, &cfg->bts_peers, list) {
+ if (!memcmp(peer->ra, ra, 6))
+ return peer;
+ }
+ return NULL;
+}
+
+/* look-up a peer by its Location Area Identification (LAI) */
+struct gbproxy_peer *gbproxy_peer_by_lai(struct gbproxy_config *cfg,
+ const uint8_t *la)
+{
+ struct gbproxy_peer *peer;
+ llist_for_each_entry(peer, &cfg->bts_peers, list) {
+ if (!memcmp(peer->ra, la, 5))
+ return peer;
+ }
+ return NULL;
+}
+
+/* look-up a peer by its Location Area Code (LAC) */
+struct gbproxy_peer *gbproxy_peer_by_lac(struct gbproxy_config *cfg,
+ const uint8_t *la)
+{
+ struct gbproxy_peer *peer;
+ llist_for_each_entry(peer, &cfg->bts_peers, list) {
+ if (!memcmp(peer->ra + 3, la + 3, 2))
+ return peer;
+ }
+ return NULL;
+}
+
+struct gbproxy_peer *gbproxy_peer_by_bssgp_tlv(struct gbproxy_config *cfg,
+ struct tlv_parsed *tp)
+{
+ if (TLVP_PRESENT(tp, BSSGP_IE_BVCI)) {
+ uint16_t bvci;
+
+ bvci = ntohs(tlvp_val16_unal(tp, BSSGP_IE_BVCI));
+ if (bvci >= 2)
+ return gbproxy_peer_by_bvci(cfg, bvci);
+ }
+
+ if (TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA)) {
+ uint8_t *rai = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA);
+ /* Only compare LAC part, since MCC/MNC are possibly patched.
+ * Since the LAC of different BSS must be different when
+ * MCC/MNC are patched, collisions shouldn't happen. */
+ return gbproxy_peer_by_lac(cfg, rai);
+ }
+
+ if (TLVP_PRESENT(tp, BSSGP_IE_LOCATION_AREA)) {
+ uint8_t *lai = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_LOCATION_AREA);
+ return gbproxy_peer_by_lac(cfg, lai);
+ }
+
+ return NULL;
+}
+
+
+struct gbproxy_peer *gbproxy_peer_alloc(struct gbproxy_config *cfg, uint16_t bvci)
+{
+ struct gbproxy_peer *peer;
+
+ peer = talloc_zero(tall_bsc_ctx, struct gbproxy_peer);
+ if (!peer)
+ return NULL;
+
+ peer->bvci = bvci;
+ peer->ctrg = rate_ctr_group_alloc(peer, &peer_ctrg_desc, bvci);
+ peer->cfg = cfg;
+
+ llist_add(&peer->list, &cfg->bts_peers);
+
+ INIT_LLIST_HEAD(&peer->patch_state.logical_links);
+
+ return peer;
+}
+
+void gbproxy_peer_free(struct gbproxy_peer *peer)
+{
+ llist_del(&peer->list);
+
+ gbproxy_delete_link_infos(peer);
+
+ rate_ctr_group_free(peer->ctrg);
+ peer->ctrg = NULL;
+
+ talloc_free(peer);
+}
+
+int gbproxy_cleanup_peers(struct gbproxy_config *cfg, uint16_t nsei, uint16_t bvci)
+{
+ int counter = 0;
+ struct gbproxy_peer *peer, *tmp;
+
+ llist_for_each_entry_safe(peer, tmp, &cfg->bts_peers, list) {
+ if (peer->nsei != nsei)
+ continue;
+ if (bvci && peer->bvci != bvci)
+ continue;
+
+ gbproxy_peer_free(peer);
+ counter += 1;
+ }
+
+ return counter;
+}
+
diff --git a/src/gprs/gb_proxy_tlli.c b/src/gprs/gb_proxy_tlli.c
new file mode 100644
index 000000000..3b3b976a5
--- /dev/null
+++ b/src/gprs/gb_proxy_tlli.c
@@ -0,0 +1,723 @@
+/* Gb-proxy TLLI state handling */
+
+/* (C) 2014 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 <osmocom/gsm/gsm48.h>
+
+#include <openbsc/gb_proxy.h>
+
+#include <openbsc/gprs_utils.h>
+#include <openbsc/gprs_gb_parse.h>
+
+#include <openbsc/debug.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/talloc.h>
+
+struct gbproxy_link_info *gbproxy_link_info_by_tlli(struct gbproxy_peer *peer,
+ uint32_t tlli)
+{
+ struct gbproxy_link_info *link_info;
+ struct gbproxy_patch_state *state = &peer->patch_state;
+
+ if (!tlli)
+ return NULL;
+
+ llist_for_each_entry(link_info, &state->logical_links, list)
+ if (link_info->tlli.current == tlli ||
+ link_info->tlli.assigned == tlli)
+ return link_info;
+
+ return NULL;
+}
+
+struct gbproxy_link_info *gbproxy_link_info_by_ptmsi(
+ struct gbproxy_peer *peer,
+ uint32_t ptmsi)
+{
+ struct gbproxy_link_info *link_info;
+ struct gbproxy_patch_state *state = &peer->patch_state;
+
+ if (ptmsi == GSM_RESERVED_TMSI)
+ return NULL;
+
+ llist_for_each_entry(link_info, &state->logical_links, list)
+ if (link_info->tlli.ptmsi == ptmsi)
+ return link_info;
+
+ return NULL;
+}
+
+struct gbproxy_link_info *gbproxy_link_info_by_any_sgsn_tlli(
+ struct gbproxy_peer *peer,
+ uint32_t tlli)
+{
+ struct gbproxy_link_info *link_info;
+ struct gbproxy_patch_state *state = &peer->patch_state;
+
+ if (!tlli)
+ return NULL;
+
+ /* Don't care about the NSEI */
+ llist_for_each_entry(link_info, &state->logical_links, list)
+ if (link_info->sgsn_tlli.current == tlli ||
+ link_info->sgsn_tlli.assigned == tlli)
+ return link_info;
+
+ return NULL;
+}
+
+struct gbproxy_link_info *gbproxy_link_info_by_sgsn_tlli(
+ struct gbproxy_peer *peer,
+ uint32_t tlli, uint32_t sgsn_nsei)
+{
+ struct gbproxy_link_info *link_info;
+ struct gbproxy_patch_state *state = &peer->patch_state;
+
+ if (!tlli)
+ return NULL;
+
+ llist_for_each_entry(link_info, &state->logical_links, list)
+ if ((link_info->sgsn_tlli.current == tlli ||
+ link_info->sgsn_tlli.assigned == tlli) &&
+ link_info->sgsn_nsei == sgsn_nsei)
+ return link_info;
+
+ return NULL;
+}
+
+struct gbproxy_link_info *gbproxy_link_info_by_imsi(
+ struct gbproxy_peer *peer,
+ const uint8_t *imsi,
+ size_t imsi_len)
+{
+ struct gbproxy_link_info *link_info;
+ struct gbproxy_patch_state *state = &peer->patch_state;
+
+ if (!gprs_is_mi_imsi(imsi, imsi_len))
+ return NULL;
+
+ llist_for_each_entry(link_info, &state->logical_links, list) {
+ if (link_info->imsi_len != imsi_len)
+ continue;
+ if (memcmp(link_info->imsi, imsi, imsi_len) != 0)
+ continue;
+
+ return link_info;
+ }
+
+ return NULL;
+}
+
+void gbproxy_link_info_discard_messages(struct gbproxy_link_info *link_info)
+{
+ struct msgb *msg, *nxt;
+
+ llist_for_each_entry_safe(msg, nxt, &link_info->stored_msgs, list) {
+ llist_del(&msg->list);
+ msgb_free(msg);
+ }
+}
+
+void gbproxy_delete_link_info(struct gbproxy_peer *peer,
+ struct gbproxy_link_info *link_info)
+{
+ struct gbproxy_patch_state *state = &peer->patch_state;
+
+ gbproxy_link_info_discard_messages(link_info);
+
+ llist_del(&link_info->list);
+ talloc_free(link_info);
+ state->logical_link_count -= 1;
+
+ peer->ctrg->ctr[GBPROX_PEER_CTR_TLLI_CACHE_SIZE].current =
+ state->logical_link_count;
+}
+
+void gbproxy_delete_link_infos(struct gbproxy_peer *peer)
+{
+ struct gbproxy_link_info *link_info, *nxt;
+ struct gbproxy_patch_state *state = &peer->patch_state;
+
+ llist_for_each_entry_safe(link_info, nxt, &state->logical_links, list)
+ gbproxy_delete_link_info(peer, link_info);
+
+ OSMO_ASSERT(state->logical_link_count == 0);
+ OSMO_ASSERT(llist_empty(&state->logical_links));
+}
+
+void gbproxy_attach_link_info(struct gbproxy_peer *peer, time_t now,
+ struct gbproxy_link_info *link_info)
+{
+ struct gbproxy_patch_state *state = &peer->patch_state;
+
+ link_info->timestamp = now;
+ llist_add(&link_info->list, &state->logical_links);
+ state->logical_link_count += 1;
+
+ peer->ctrg->ctr[GBPROX_PEER_CTR_TLLI_CACHE_SIZE].current =
+ state->logical_link_count;
+}
+
+int gbproxy_remove_stale_link_infos(struct gbproxy_peer *peer, time_t now)
+{
+ struct gbproxy_patch_state *state = &peer->patch_state;
+ int exceeded_max_len = 0;
+ int deleted_count = 0;
+ int check_for_age;
+
+ if (peer->cfg->tlli_max_len > 0)
+ exceeded_max_len =
+ state->logical_link_count - peer->cfg->tlli_max_len;
+
+ check_for_age = peer->cfg->tlli_max_age > 0;
+
+ for (; exceeded_max_len > 0; exceeded_max_len--) {
+ struct gbproxy_link_info *link_info;
+ OSMO_ASSERT(!llist_empty(&state->logical_links));
+ link_info = llist_entry(state->logical_links.prev,
+ struct gbproxy_link_info,
+ list);
+ LOGP(DGPRS, LOGL_INFO,
+ "Removing TLLI %08x from list "
+ "(stale, length %d, max_len exceeded)\n",
+ link_info->tlli.current, state->logical_link_count);
+
+ gbproxy_delete_link_info(peer, link_info);
+ deleted_count += 1;
+ }
+
+ while (check_for_age && !llist_empty(&state->logical_links)) {
+ time_t age;
+ struct gbproxy_link_info *link_info;
+ link_info = llist_entry(state->logical_links.prev,
+ struct gbproxy_link_info,
+ list);
+ age = now - link_info->timestamp;
+ /* age < 0 only happens after system time jumps, discard entry */
+ if (age <= peer->cfg->tlli_max_age && age >= 0) {
+ check_for_age = 0;
+ continue;
+ }
+
+ LOGP(DGPRS, LOGL_INFO,
+ "Removing TLLI %08x from list "
+ "(stale, age %d, max_age exceeded)\n",
+ link_info->tlli.current, (int)age);
+
+ gbproxy_delete_link_info(peer, link_info);
+ deleted_count += 1;
+ }
+
+ return deleted_count;
+}
+
+struct gbproxy_link_info *gbproxy_link_info_alloc( struct gbproxy_peer *peer)
+{
+ struct gbproxy_link_info *link_info;
+
+ link_info = talloc_zero(peer, struct gbproxy_link_info);
+ link_info->tlli.ptmsi = GSM_RESERVED_TMSI;
+ link_info->sgsn_tlli.ptmsi = GSM_RESERVED_TMSI;
+
+ link_info->vu_gen_tx_bss = GBPROXY_INIT_VU_GEN_TX;
+
+ INIT_LLIST_HEAD(&link_info->stored_msgs);
+
+ return link_info;
+}
+
+void gbproxy_detach_link_info(
+ struct gbproxy_peer *peer,
+ struct gbproxy_link_info *link_info)
+{
+ struct gbproxy_patch_state *state = &peer->patch_state;
+
+ llist_del(&link_info->list);
+ OSMO_ASSERT(state->logical_link_count > 0);
+ state->logical_link_count -= 1;
+
+ peer->ctrg->ctr[GBPROX_PEER_CTR_TLLI_CACHE_SIZE].current =
+ state->logical_link_count;
+}
+
+void gbproxy_update_link_info(struct gbproxy_link_info *link_info,
+ const uint8_t *imsi, size_t imsi_len)
+{
+ if (!gprs_is_mi_imsi(imsi, imsi_len))
+ return;
+
+ link_info->imsi_len = imsi_len;
+ link_info->imsi =
+ talloc_realloc_size(link_info, link_info->imsi, imsi_len);
+ OSMO_ASSERT(link_info->imsi != NULL);
+ memcpy(link_info->imsi, imsi, imsi_len);
+}
+
+void gbproxy_reassign_tlli(struct gbproxy_tlli_state *tlli_state,
+ struct gbproxy_peer *peer, uint32_t new_tlli)
+{
+ if (new_tlli == tlli_state->current)
+ return;
+
+ LOGP(DGPRS, LOGL_INFO,
+ "The TLLI has been reassigned from %08x to %08x\n",
+ tlli_state->current, new_tlli);
+
+ /* Remember assigned TLLI */
+ tlli_state->assigned = new_tlli;
+ tlli_state->bss_validated = 0;
+ tlli_state->net_validated = 0;
+}
+
+uint32_t gbproxy_map_tlli(uint32_t other_tlli,
+ struct gbproxy_link_info *link_info, int to_bss)
+{
+ uint32_t tlli = 0;
+ struct gbproxy_tlli_state *src, *dst;
+ if (to_bss) {
+ src = &link_info->sgsn_tlli;
+ dst = &link_info->tlli;
+ } else {
+ src = &link_info->tlli;
+ dst = &link_info->sgsn_tlli;
+ }
+ if (src->current == other_tlli)
+ tlli = dst->current;
+ else if (src->assigned == other_tlli)
+ tlli = dst->assigned;
+
+ return tlli;
+}
+
+static void gbproxy_validate_tlli(struct gbproxy_tlli_state *tlli_state,
+ uint32_t tlli, int to_bss)
+{
+ LOGP(DGPRS, LOGL_DEBUG,
+ "%s({current = %08x, assigned = %08x, net_vld = %d, bss_vld = %d}, %08x)\n",
+ __func__, tlli_state->current, tlli_state->assigned,
+ tlli_state->net_validated, tlli_state->bss_validated, tlli);
+
+ if (!tlli_state->assigned || tlli_state->assigned != tlli)
+ return;
+
+ /* TODO: Is this ok? Check spec */
+ if (gprs_tlli_type(tlli) != TLLI_LOCAL)
+ return;
+
+ /* See GSM 04.08, 4.7.1.5 */
+ if (to_bss)
+ tlli_state->net_validated = 1;
+ else
+ tlli_state->bss_validated = 1;
+
+ if (!tlli_state->bss_validated || !tlli_state->net_validated)
+ return;
+
+ LOGP(DGPRS, LOGL_INFO,
+ "The TLLI %08x has been validated (was %08x)\n",
+ tlli_state->assigned, tlli_state->current);
+
+ tlli_state->current = tlli;
+ tlli_state->assigned = 0;
+}
+
+static void gbproxy_touch_link_info(struct gbproxy_peer *peer,
+ struct gbproxy_link_info *link_info,
+ time_t now)
+{
+ gbproxy_detach_link_info(peer, link_info);
+ gbproxy_attach_link_info(peer, now, link_info);
+}
+
+static int gbproxy_unregister_link_info(struct gbproxy_peer *peer,
+ struct gbproxy_link_info *link_info)
+{
+ if (!link_info)
+ return 1;
+
+ if (link_info->tlli.ptmsi == GSM_RESERVED_TMSI && !link_info->imsi_len) {
+ LOGP(DGPRS, LOGL_INFO,
+ "Removing TLLI %08x from list (P-TMSI or IMSI are not set)\n",
+ link_info->tlli.current);
+ gbproxy_delete_link_info(peer, link_info);
+ return 1;
+ }
+
+ link_info->tlli.current = 0;
+ link_info->tlli.assigned = 0;
+ link_info->sgsn_tlli.current = 0;
+ link_info->sgsn_tlli.assigned = 0;
+
+ link_info->is_deregistered = 1;
+
+ gbproxy_reset_link(link_info);
+
+ return 0;
+}
+
+int gbproxy_imsi_matches(struct gbproxy_config *cfg,
+ enum gbproxy_match_id match_id,
+ struct gbproxy_link_info *link_info)
+{
+ struct gbproxy_match *match;
+ OSMO_ASSERT(match_id >= 0 && match_id < ARRAY_SIZE(cfg->matches));
+
+ match = &cfg->matches[match_id];
+ if (!match->enable)
+ return 1;
+
+ return link_info != NULL && link_info->is_matching[match_id];
+}
+
+void gbproxy_assign_imsi(struct gbproxy_peer *peer,
+ struct gbproxy_link_info *link_info,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ int imsi_matches;
+ struct gbproxy_link_info *other_link_info;
+ enum gbproxy_match_id match_id;
+
+ /* Make sure that there is a second entry with the same IMSI */
+ other_link_info = gbproxy_link_info_by_imsi(
+ peer, parse_ctx->imsi, parse_ctx->imsi_len);
+
+ if (other_link_info && other_link_info != link_info) {
+ char mi_buf[200];
+ mi_buf[0] = '\0';
+ gsm48_mi_to_string(mi_buf, sizeof(mi_buf),
+ parse_ctx->imsi, parse_ctx->imsi_len);
+ LOGP(DGPRS, LOGL_INFO,
+ "Removing TLLI %08x from list (IMSI %s re-used)\n",
+ other_link_info->tlli.current, mi_buf);
+ gbproxy_delete_link_info(peer, other_link_info);
+ }
+
+ /* Update the IMSI field */
+ gbproxy_update_link_info(link_info,
+ parse_ctx->imsi, parse_ctx->imsi_len);
+
+ /* Check, whether the IMSI matches */
+ OSMO_ASSERT(ARRAY_SIZE(link_info->is_matching) ==
+ ARRAY_SIZE(peer->cfg->matches));
+ for (match_id = 0; match_id < ARRAY_SIZE(link_info->is_matching);
+ ++match_id) {
+ imsi_matches = gbproxy_check_imsi(
+ &peer->cfg->matches[match_id],
+ parse_ctx->imsi, parse_ctx->imsi_len);
+ if (imsi_matches >= 0)
+ link_info->is_matching[match_id] = imsi_matches;
+ }
+}
+
+static int gbproxy_tlli_match(const struct gbproxy_tlli_state *a,
+ const struct gbproxy_tlli_state *b)
+{
+ if (a->current && a->current == b->current)
+ return 1;
+
+ if (a->assigned && a->assigned == b->assigned)
+ return 1;
+
+ if (a->ptmsi != GSM_RESERVED_TMSI && a->ptmsi == b->ptmsi)
+ return 1;
+
+ return 0;
+}
+
+static void gbproxy_remove_matching_link_infos(
+ struct gbproxy_peer *peer, struct gbproxy_link_info *link_info)
+{
+ struct gbproxy_link_info *info, *nxt;
+ struct gbproxy_patch_state *state = &peer->patch_state;
+
+ /* Make sure that there is no second entry with the same P-TMSI or TLLI */
+ llist_for_each_entry_safe(info, nxt, &state->logical_links, list) {
+ if (info == link_info)
+ continue;
+
+ if (!gbproxy_tlli_match(&link_info->tlli, &info->tlli) &&
+ (link_info->sgsn_nsei != info->sgsn_nsei ||
+ !gbproxy_tlli_match(&link_info->sgsn_tlli, &info->sgsn_tlli)))
+ continue;
+
+ LOGP(DGPRS, LOGL_INFO,
+ "Removing TLLI %08x from list (P-TMSI/TLLI re-used)\n",
+ info->tlli.current);
+ gbproxy_delete_link_info(peer, info);
+ }
+}
+
+static struct gbproxy_link_info *gbproxy_get_link_info_ul(
+ struct gbproxy_peer *peer,
+ int *tlli_is_valid,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ struct gbproxy_link_info *link_info = NULL;
+
+ if (parse_ctx->tlli_enc) {
+ link_info = gbproxy_link_info_by_tlli(peer, parse_ctx->tlli);
+
+ if (link_info) {
+ *tlli_is_valid = 1;
+ return link_info;
+ }
+ }
+
+ *tlli_is_valid = 0;
+
+ if (!link_info && parse_ctx->imsi) {
+ link_info = gbproxy_link_info_by_imsi(
+ peer, parse_ctx->imsi, parse_ctx->imsi_len);
+ }
+
+ if (!link_info && parse_ctx->ptmsi_enc && !parse_ctx->old_raid_is_foreign) {
+ uint32_t bss_ptmsi;
+ gprs_parse_tmsi(parse_ctx->ptmsi_enc, &bss_ptmsi);
+ link_info = gbproxy_link_info_by_ptmsi(peer, bss_ptmsi);
+ }
+
+ if (!link_info)
+ return NULL;
+
+ link_info->is_deregistered = 0;
+
+ return link_info;
+}
+
+struct gbproxy_link_info *gbproxy_update_link_state_ul(
+ struct gbproxy_peer *peer,
+ time_t now,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ struct gbproxy_link_info *link_info;
+ int tlli_is_valid;
+
+ link_info = gbproxy_get_link_info_ul(peer, &tlli_is_valid, parse_ctx);
+
+ if (parse_ctx->tlli_enc && parse_ctx->llc) {
+ uint32_t sgsn_tlli;
+
+ if (!link_info) {
+ LOGP(DGPRS, LOGL_INFO, "Adding TLLI %08x to list\n",
+ parse_ctx->tlli);
+ link_info = gbproxy_link_info_alloc(peer);
+ gbproxy_attach_link_info(peer, now, link_info);
+
+ /* Setup TLLIs */
+ sgsn_tlli = gbproxy_make_sgsn_tlli(peer, link_info,
+ parse_ctx->tlli);
+ link_info->sgsn_tlli.current = sgsn_tlli;
+ link_info->tlli.current = parse_ctx->tlli;
+ } else if (!tlli_is_valid) {
+ /* New TLLI (info found by IMSI or P-TMSI) */
+ link_info->tlli.current = parse_ctx->tlli;
+ link_info->tlli.assigned = 0;
+ link_info->sgsn_tlli.current =
+ gbproxy_make_sgsn_tlli(peer, link_info,
+ parse_ctx->tlli);
+ link_info->sgsn_tlli.assigned = 0;
+ gbproxy_touch_link_info(peer, link_info, now);
+ } else {
+ sgsn_tlli = gbproxy_map_tlli(parse_ctx->tlli, link_info, 0);
+ if (!sgsn_tlli)
+ sgsn_tlli = gbproxy_make_sgsn_tlli(peer, link_info,
+ parse_ctx->tlli);
+
+ gbproxy_validate_tlli(&link_info->tlli,
+ parse_ctx->tlli, 0);
+ gbproxy_validate_tlli(&link_info->sgsn_tlli,
+ sgsn_tlli, 0);
+ gbproxy_touch_link_info(peer, link_info, now);
+ }
+ } else if (link_info) {
+ gbproxy_touch_link_info(peer, link_info, now);
+ }
+
+ if (parse_ctx->imsi && link_info && link_info->imsi_len == 0)
+ gbproxy_assign_imsi(peer, link_info, parse_ctx);
+
+ return link_info;
+}
+
+static struct gbproxy_link_info *gbproxy_get_link_info_dl(
+ struct gbproxy_peer *peer,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ struct gbproxy_link_info *link_info = NULL;
+
+ /* Which key to use depends on its availability only, if that fails, do
+ * not retry it with another key (e.g. IMSI). */
+ if (parse_ctx->tlli_enc)
+ link_info = gbproxy_link_info_by_sgsn_tlli(peer, parse_ctx->tlli,
+ parse_ctx->peer_nsei);
+
+ /* TODO: Get link_info by (SGSN) P-TMSI if that is available (see
+ * GSM 08.18, 7.2) instead of using the IMSI as key. */
+ else if (parse_ctx->imsi)
+ link_info = gbproxy_link_info_by_imsi(
+ peer, parse_ctx->imsi, parse_ctx->imsi_len);
+
+ if (link_info)
+ link_info->is_deregistered = 0;
+
+ return link_info;
+}
+
+struct gbproxy_link_info *gbproxy_update_link_state_dl(
+ struct gbproxy_peer *peer,
+ time_t now,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ struct gbproxy_link_info *link_info = NULL;
+
+ link_info = gbproxy_get_link_info_dl(peer, parse_ctx);
+
+ if (parse_ctx->tlli_enc && parse_ctx->new_ptmsi_enc && link_info) {
+ /* A new P-TMSI has been signalled in the message,
+ * register new TLLI */
+ uint32_t new_sgsn_ptmsi;
+ uint32_t new_bss_ptmsi = GSM_RESERVED_TMSI;
+ gprs_parse_tmsi(parse_ctx->new_ptmsi_enc, &new_sgsn_ptmsi);
+
+ if (link_info->sgsn_tlli.ptmsi == new_sgsn_ptmsi)
+ new_bss_ptmsi = link_info->tlli.ptmsi;
+
+ if (new_bss_ptmsi == GSM_RESERVED_TMSI)
+ new_bss_ptmsi = gbproxy_make_bss_ptmsi(peer, new_sgsn_ptmsi);
+
+ LOGP(DGPRS, LOGL_INFO,
+ "Got new PTMSI %08x from SGSN, using %08x for BSS\n",
+ new_sgsn_ptmsi, new_bss_ptmsi);
+ /* Setup PTMSIs */
+ link_info->sgsn_tlli.ptmsi = new_sgsn_ptmsi;
+ link_info->tlli.ptmsi = new_bss_ptmsi;
+ } else if (parse_ctx->tlli_enc && parse_ctx->new_ptmsi_enc && !link_info &&
+ !peer->cfg->patch_ptmsi) {
+ /* A new P-TMSI has been signalled in the message with an unknown
+ * TLLI, create a new link_info */
+ /* TODO: Add a test case for this branch */
+ uint32_t new_ptmsi;
+ gprs_parse_tmsi(parse_ctx->new_ptmsi_enc, &new_ptmsi);
+
+ LOGP(DGPRS, LOGL_INFO,
+ "Adding TLLI %08x to list (SGSN, new P-TMSI is %08x)\n",
+ parse_ctx->tlli, new_ptmsi);
+
+ link_info = gbproxy_link_info_alloc(peer);
+ link_info->sgsn_tlli.current = parse_ctx->tlli;
+ link_info->tlli.current = parse_ctx->tlli;
+ link_info->sgsn_tlli.ptmsi = new_ptmsi;
+ link_info->tlli.ptmsi = new_ptmsi;
+ gbproxy_attach_link_info(peer, now, link_info);
+ } else if (parse_ctx->tlli_enc && parse_ctx->llc && !link_info &&
+ !peer->cfg->patch_ptmsi) {
+ /* Unknown SGSN TLLI, create a new link_info */
+ uint32_t new_ptmsi;
+ link_info = gbproxy_link_info_alloc(peer);
+ LOGP(DGPRS, LOGL_INFO, "Adding TLLI %08x to list (SGSN)\n",
+ parse_ctx->tlli);
+
+ gbproxy_attach_link_info(peer, now, link_info);
+
+ /* Setup TLLIs */
+ link_info->sgsn_tlli.current = parse_ctx->tlli;
+ link_info->tlli.current = parse_ctx->tlli;
+
+ if (!parse_ctx->new_ptmsi_enc)
+ return link_info;
+ /* A new P-TMSI has been signalled in the message */
+
+ gprs_parse_tmsi(parse_ctx->new_ptmsi_enc, &new_ptmsi);
+ LOGP(DGPRS, LOGL_INFO,
+ "Assigning new P-TMSI %08x\n", new_ptmsi);
+ /* Setup P-TMSIs */
+ link_info->sgsn_tlli.ptmsi = new_ptmsi;
+ link_info->tlli.ptmsi = new_ptmsi;
+ } else if (parse_ctx->tlli_enc && parse_ctx->llc && link_info) {
+ uint32_t bss_tlli = gbproxy_map_tlli(parse_ctx->tlli,
+ link_info, 1);
+ gbproxy_validate_tlli(&link_info->sgsn_tlli, parse_ctx->tlli, 1);
+ gbproxy_validate_tlli(&link_info->tlli, bss_tlli, 1);
+ gbproxy_touch_link_info(peer, link_info, now);
+ } else if (link_info) {
+ gbproxy_touch_link_info(peer, link_info, now);
+ }
+
+ if (parse_ctx->imsi && link_info && link_info->imsi_len == 0)
+ gbproxy_assign_imsi(peer, link_info, parse_ctx);
+
+ return link_info;
+}
+
+int gbproxy_update_link_state_after(
+ struct gbproxy_peer *peer,
+ struct gbproxy_link_info *link_info,
+ time_t now,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ int rc = 0;
+ if (parse_ctx->invalidate_tlli && link_info) {
+ int keep_info =
+ peer->cfg->keep_link_infos == GBPROX_KEEP_ALWAYS ||
+ (peer->cfg->keep_link_infos == GBPROX_KEEP_REATTACH &&
+ parse_ctx->await_reattach) ||
+ (peer->cfg->keep_link_infos == GBPROX_KEEP_IDENTIFIED &&
+ link_info->imsi_len > 0);
+ if (keep_info) {
+ LOGP(DGPRS, LOGL_INFO, "Unregistering TLLI %08x\n",
+ link_info->tlli.current);
+ rc = gbproxy_unregister_link_info(peer, link_info);
+ } else {
+ LOGP(DGPRS, LOGL_INFO, "Removing TLLI %08x from list\n",
+ link_info->tlli.current);
+ gbproxy_delete_link_info(peer, link_info);
+ rc = 1;
+ }
+ } else if (parse_ctx->to_bss && parse_ctx->tlli_enc &&
+ parse_ctx->new_ptmsi_enc && link_info) {
+ /* A new PTMSI has been signaled in the message,
+ * register new TLLI */
+ uint32_t new_sgsn_ptmsi = link_info->sgsn_tlli.ptmsi;
+ uint32_t new_bss_ptmsi = link_info->tlli.ptmsi;
+ uint32_t new_sgsn_tlli;
+ uint32_t new_bss_tlli = 0;
+
+ new_sgsn_tlli = gprs_tmsi2tlli(new_sgsn_ptmsi, TLLI_LOCAL);
+ if (new_bss_ptmsi != GSM_RESERVED_TMSI)
+ new_bss_tlli = gprs_tmsi2tlli(new_bss_ptmsi, TLLI_LOCAL);
+ LOGP(DGPRS, LOGL_INFO,
+ "Assigning new TLLI %08x to SGSN, %08x to BSS\n",
+ new_sgsn_tlli, new_bss_tlli);
+
+ gbproxy_reassign_tlli(&link_info->sgsn_tlli,
+ peer, new_sgsn_tlli);
+ gbproxy_reassign_tlli(&link_info->tlli,
+ peer, new_bss_tlli);
+ gbproxy_remove_matching_link_infos(peer, link_info);
+ }
+
+ gbproxy_remove_stale_link_infos(peer, now);
+
+ return rc;
+}
+
+
diff --git a/src/gprs/gb_proxy_vty.c b/src/gprs/gb_proxy_vty.c
new file mode 100644
index 000000000..933b6b010
--- /dev/null
+++ b/src/gprs/gb_proxy_vty.c
@@ -0,0 +1,852 @@
+/*
+ * (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 <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <time.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/rate_ctr.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <osmocom/gprs/gprs_ns.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gb_proxy.h>
+#include <openbsc/gprs_utils.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/misc.h>
+
+static struct gbproxy_config *g_cfg = NULL;
+
+/*
+ * vty code for mgcp below
+ */
+static struct cmd_node gbproxy_node = {
+ GBPROXY_NODE,
+ "%s(config-gbproxy)# ",
+ 1,
+};
+
+static const struct value_string keep_modes[] = {
+ {GBPROX_KEEP_NEVER, "never"},
+ {GBPROX_KEEP_REATTACH, "re-attach"},
+ {GBPROX_KEEP_IDENTIFIED, "identified"},
+ {GBPROX_KEEP_ALWAYS, "always"},
+ {0, NULL}
+};
+
+static const struct value_string match_ids[] = {
+ {GBPROX_MATCH_PATCHING, "patching"},
+ {GBPROX_MATCH_ROUTING, "routing"},
+ {0, NULL}
+};
+
+static void gbprox_vty_print_peer(struct vty *vty, struct gbproxy_peer *peer)
+{
+ struct gprs_ra_id raid;
+ gsm48_parse_ra(&raid, peer->ra);
+
+ vty_out(vty, "NSEI %5u, PTP-BVCI %5u, "
+ "RAI %u-%u-%u-%u",
+ peer->nsei, peer->bvci,
+ raid.mcc, raid.mnc, raid.lac, raid.rac);
+ if (peer->blocked)
+ vty_out(vty, " [BVC-BLOCKED]");
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+}
+
+static int config_write_gbproxy(struct vty *vty)
+{
+ enum gbproxy_match_id match_id;
+
+ vty_out(vty, "gbproxy%s", VTY_NEWLINE);
+
+ vty_out(vty, " sgsn nsei %u%s", g_cfg->nsip_sgsn_nsei,
+ VTY_NEWLINE);
+
+ if (g_cfg->core_mcc > 0)
+ vty_out(vty, " core-mobile-country-code %d%s",
+ g_cfg->core_mcc, VTY_NEWLINE);
+ if (g_cfg->core_mnc > 0)
+ vty_out(vty, " core-mobile-network-code %d%s",
+ g_cfg->core_mnc, VTY_NEWLINE);
+
+ for (match_id = 0; match_id < ARRAY_SIZE(g_cfg->matches); ++match_id) {
+ struct gbproxy_match *match = &g_cfg->matches[match_id];
+ if (match->re_str)
+ vty_out(vty, " match-imsi %s %s%s",
+ get_value_string(match_ids, match_id),
+ match->re_str, VTY_NEWLINE);
+ }
+
+ if (g_cfg->core_apn != NULL) {
+ if (g_cfg->core_apn_size > 0) {
+ char str[500] = {0};
+ vty_out(vty, " core-access-point-name %s%s",
+ gprs_apn_to_str(str, g_cfg->core_apn,
+ g_cfg->core_apn_size),
+ VTY_NEWLINE);
+ } else {
+ vty_out(vty, " core-access-point-name none%s",
+ VTY_NEWLINE);
+ }
+ }
+
+ if (g_cfg->route_to_sgsn2)
+ vty_out(vty, " secondary-sgsn nsei %u%s", g_cfg->nsip_sgsn2_nsei,
+ VTY_NEWLINE);
+
+ if (g_cfg->tlli_max_age > 0)
+ vty_out(vty, " link-list max-age %d%s",
+ g_cfg->tlli_max_age, VTY_NEWLINE);
+ if (g_cfg->tlli_max_len > 0)
+ vty_out(vty, " link-list max-length %d%s",
+ g_cfg->tlli_max_len, VTY_NEWLINE);
+ vty_out(vty, " link-list keep-mode %s%s",
+ get_value_string(keep_modes, g_cfg->keep_link_infos),
+ VTY_NEWLINE);
+
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy,
+ cfg_gbproxy_cmd,
+ "gbproxy",
+ "Configure the Gb proxy")
+{
+ vty->node = GBPROXY_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_sgsn_nsei,
+ cfg_nsip_sgsn_nsei_cmd,
+ "sgsn nsei <0-65534>",
+ "SGSN information\n"
+ "NSEI to be used in the connection with the SGSN\n"
+ "The NSEI\n")
+{
+ unsigned int nsei = atoi(argv[0]);
+
+ if (g_cfg->route_to_sgsn2 && g_cfg->nsip_sgsn2_nsei == nsei) {
+ vty_out(vty, "SGSN NSEI %d conflicts with secondary SGSN NSEI%s",
+ nsei, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ g_cfg->nsip_sgsn_nsei = nsei;
+ return CMD_SUCCESS;
+}
+
+#define GBPROXY_CORE_MNC_STR "Use this network code for the core network\n"
+
+DEFUN(cfg_gbproxy_core_mnc,
+ cfg_gbproxy_core_mnc_cmd,
+ "core-mobile-network-code <1-999>",
+ GBPROXY_CORE_MNC_STR "NCC value\n")
+{
+ g_cfg->core_mnc = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy_no_core_mnc,
+ cfg_gbproxy_no_core_mnc_cmd,
+ "no core-mobile-network-code",
+ NO_STR GBPROXY_CORE_MNC_STR)
+{
+ g_cfg->core_mnc = 0;
+ return CMD_SUCCESS;
+}
+
+#define GBPROXY_CORE_MCC_STR "Use this country code for the core network\n"
+
+DEFUN(cfg_gbproxy_core_mcc,
+ cfg_gbproxy_core_mcc_cmd,
+ "core-mobile-country-code <1-999>",
+ GBPROXY_CORE_MCC_STR "MCC value\n")
+{
+ g_cfg->core_mcc = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy_no_core_mcc,
+ cfg_gbproxy_no_core_mcc_cmd,
+ "no core-mobile-country-code",
+ NO_STR GBPROXY_CORE_MCC_STR)
+{
+ g_cfg->core_mcc = 0;
+ return CMD_SUCCESS;
+}
+
+#define GBPROXY_MATCH_IMSI_STR "Restrict actions to certain IMSIs\n"
+
+DEFUN(cfg_gbproxy_match_imsi,
+ cfg_gbproxy_match_imsi_cmd,
+ "match-imsi (patching|routing) .REGEXP",
+ GBPROXY_MATCH_IMSI_STR
+ "Patch MS related information elements on match only\n"
+ "Route to the secondary SGSN on match only\n"
+ "Regular expression for the IMSI match\n")
+{
+ const char *filter = argv[1];
+ const char *err_msg = NULL;
+ struct gbproxy_match *match;
+ enum gbproxy_match_id match_id = get_string_value(match_ids, argv[0]);
+
+ OSMO_ASSERT(match_id >= GBPROX_MATCH_PATCHING &&
+ match_id < GBPROX_MATCH_LAST);
+ match = &g_cfg->matches[match_id];
+
+ if (gbproxy_set_patch_filter(match, filter, &err_msg) != 0) {
+ vty_out(vty, "Match expression invalid: %s%s",
+ err_msg, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ g_cfg->acquire_imsi = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy_no_match_imsi,
+ cfg_gbproxy_no_match_imsi_cmd,
+ "no match-imsi",
+ NO_STR GBPROXY_MATCH_IMSI_STR)
+{
+ enum gbproxy_match_id match_id;
+
+ for (match_id = 0; match_id < ARRAY_SIZE(g_cfg->matches); ++match_id)
+ gbproxy_clear_patch_filter(&g_cfg->matches[match_id]);
+
+ g_cfg->acquire_imsi = 0;
+
+ return CMD_SUCCESS;
+}
+
+#define GBPROXY_CORE_APN_STR "Use this access point name (APN) for the backbone\n"
+#define GBPROXY_CORE_APN_ARG_STR "Replace APN by this string\n" "Remove APN\n"
+
+static int set_core_apn(struct vty *vty, const char *apn)
+{
+ int apn_len;
+
+ if (!apn) {
+ talloc_free(g_cfg->core_apn);
+ g_cfg->core_apn = NULL;
+ g_cfg->core_apn_size = 0;
+ return CMD_SUCCESS;
+ }
+
+ apn_len = strlen(apn);
+
+ if (apn_len >= 100) {
+ vty_out(vty, "APN string too long (max 99 chars)%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (apn_len == 0) {
+ talloc_free(g_cfg->core_apn);
+ /* TODO: replace NULL */
+ g_cfg->core_apn = talloc_zero_size(NULL, 2);
+ g_cfg->core_apn_size = 0;
+ } else {
+ /* TODO: replace NULL */
+ g_cfg->core_apn =
+ talloc_realloc_size(NULL, g_cfg->core_apn, apn_len + 1);
+ g_cfg->core_apn_size =
+ gprs_str_to_apn(g_cfg->core_apn, apn_len + 1, apn);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy_core_apn,
+ cfg_gbproxy_core_apn_cmd,
+ "core-access-point-name (APN|none)",
+ GBPROXY_CORE_APN_STR GBPROXY_CORE_APN_ARG_STR)
+{
+ if (strcmp(argv[0], "none") == 0)
+ return set_core_apn(vty, "");
+ else
+ return set_core_apn(vty, argv[0]);
+}
+
+DEFUN(cfg_gbproxy_no_core_apn,
+ cfg_gbproxy_no_core_apn_cmd,
+ "no core-access-point-name",
+ NO_STR GBPROXY_CORE_APN_STR)
+{
+ return set_core_apn(vty, NULL);
+}
+
+/* TODO: Remove the patch-ptmsi command, since P-TMSI patching is enabled
+ * automatically when needed. This command is only left for manual testing
+ * (e.g. doing P-TMSI patching without using a secondary SGSN)
+ */
+#define GBPROXY_PATCH_PTMSI_STR "Patch P-TMSI/TLLI\n"
+
+DEFUN(cfg_gbproxy_patch_ptmsi,
+ cfg_gbproxy_patch_ptmsi_cmd,
+ "patch-ptmsi",
+ GBPROXY_PATCH_PTMSI_STR)
+{
+ g_cfg->patch_ptmsi = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy_no_patch_ptmsi,
+ cfg_gbproxy_no_patch_ptmsi_cmd,
+ "no patch-ptmsi",
+ NO_STR GBPROXY_PATCH_PTMSI_STR)
+{
+ g_cfg->patch_ptmsi = 0;
+
+ return CMD_SUCCESS;
+}
+
+/* TODO: Remove the acquire-imsi command, since that feature is enabled
+ * automatically when IMSI matching is enabled. This command is only left for
+ * manual testing (e.g. doing IMSI acquisition without IMSI based patching)
+ */
+#define GBPROXY_ACQUIRE_IMSI_STR "Acquire the IMSI before establishing a LLC connection (Experimental)\n"
+
+DEFUN(cfg_gbproxy_acquire_imsi,
+ cfg_gbproxy_acquire_imsi_cmd,
+ "acquire-imsi",
+ GBPROXY_ACQUIRE_IMSI_STR)
+{
+ g_cfg->acquire_imsi = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy_no_acquire_imsi,
+ cfg_gbproxy_no_acquire_imsi_cmd,
+ "no acquire-imsi",
+ NO_STR GBPROXY_ACQUIRE_IMSI_STR)
+{
+ g_cfg->acquire_imsi = 0;
+
+ return CMD_SUCCESS;
+}
+
+#define GBPROXY_SECOND_SGSN_STR "Route matching LLC connections to a second SGSN (Experimental)\n"
+
+DEFUN(cfg_gbproxy_secondary_sgsn,
+ cfg_gbproxy_secondary_sgsn_cmd,
+ "secondary-sgsn nsei <0-65534>",
+ GBPROXY_SECOND_SGSN_STR
+ "NSEI to be used in the connection with the SGSN\n"
+ "The NSEI\n")
+{
+ unsigned int nsei = atoi(argv[0]);
+
+ if (g_cfg->nsip_sgsn_nsei == nsei) {
+ vty_out(vty, "Secondary SGSN NSEI %d conflicts with primary SGSN NSEI%s",
+ nsei, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ g_cfg->route_to_sgsn2 = 1;
+ g_cfg->nsip_sgsn2_nsei = nsei;
+
+ g_cfg->patch_ptmsi = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy_no_secondary_sgsn,
+ cfg_gbproxy_no_secondary_sgsn_cmd,
+ "no secondary-sgsn",
+ NO_STR GBPROXY_SECOND_SGSN_STR)
+{
+ g_cfg->route_to_sgsn2 = 0;
+ g_cfg->nsip_sgsn2_nsei = 0xFFFF;
+
+ g_cfg->patch_ptmsi = 0;
+
+ return CMD_SUCCESS;
+}
+
+#define GBPROXY_LINK_LIST_STR "Set TLLI list parameters\n"
+#define GBPROXY_MAX_AGE_STR "Limit maximum age\n"
+
+DEFUN(cfg_gbproxy_link_list_max_age,
+ cfg_gbproxy_link_list_max_age_cmd,
+ "link-list max-age <1-999999>",
+ GBPROXY_LINK_LIST_STR GBPROXY_MAX_AGE_STR
+ "Maximum age in seconds\n")
+{
+ g_cfg->tlli_max_age = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy_link_list_no_max_age,
+ cfg_gbproxy_link_list_no_max_age_cmd,
+ "no link-list max-age",
+ NO_STR GBPROXY_LINK_LIST_STR GBPROXY_MAX_AGE_STR)
+{
+ g_cfg->tlli_max_age = 0;
+
+ return CMD_SUCCESS;
+}
+
+#define GBPROXY_MAX_LEN_STR "Limit list length\n"
+
+DEFUN(cfg_gbproxy_link_list_max_len,
+ cfg_gbproxy_link_list_max_len_cmd,
+ "link-list max-length <1-99999>",
+ GBPROXY_LINK_LIST_STR GBPROXY_MAX_LEN_STR
+ "Maximum number of logical links in the list\n")
+{
+ g_cfg->tlli_max_len = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy_link_list_no_max_len,
+ cfg_gbproxy_link_list_no_max_len_cmd,
+ "no link-list max-length",
+ NO_STR GBPROXY_LINK_LIST_STR GBPROXY_MAX_LEN_STR)
+{
+ g_cfg->tlli_max_len = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy_link_list_keep_mode,
+ cfg_gbproxy_link_list_keep_mode_cmd,
+ "link-list keep-mode (never|re-attach|identified|always)",
+ GBPROXY_LINK_LIST_STR "How to keep entries for detached logical links\n"
+ "Discard entry immediately after detachment\n"
+ "Keep entry if a re-attachment has be requested\n"
+ "Keep entry if it associated with an IMSI\n"
+ "Don't discard entries after detachment\n")
+{
+ int val = get_string_value(keep_modes, argv[0]);
+ OSMO_ASSERT(val >= GBPROX_KEEP_NEVER && val <= GBPROX_KEEP_ALWAYS);
+ g_cfg->keep_link_infos = val;
+
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(show_gbproxy, show_gbproxy_cmd, "show gbproxy [stats]",
+ SHOW_STR "Display information about the Gb proxy\n" "Show statistics\n")
+{
+ struct gbproxy_peer *peer;
+ int show_stats = argc >= 1;
+
+ if (show_stats)
+ vty_out_rate_ctr_group(vty, "", g_cfg->ctrg);
+
+ llist_for_each_entry(peer, &g_cfg->bts_peers, list) {
+ gbprox_vty_print_peer(vty, peer);
+
+ if (show_stats)
+ vty_out_rate_ctr_group(vty, " ", peer->ctrg);
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_gbproxy_links, show_gbproxy_links_cmd, "show gbproxy links",
+ SHOW_STR "Display information about the Gb proxy\n" "Show logical links\n")
+{
+ struct gbproxy_peer *peer;
+ char mi_buf[200];
+ time_t now;
+ struct timespec ts = {0,};
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ now = ts.tv_sec;
+
+ llist_for_each_entry(peer, &g_cfg->bts_peers, list) {
+ struct gbproxy_link_info *link_info;
+ struct gbproxy_patch_state *state = &peer->patch_state;
+
+ gbprox_vty_print_peer(vty, peer);
+
+ llist_for_each_entry(link_info, &state->logical_links, list) {
+ time_t age = now - link_info->timestamp;
+ int stored_msgs = 0;
+ struct llist_head *iter;
+ llist_for_each(iter, &link_info->stored_msgs)
+ stored_msgs++;
+
+ if (link_info->imsi > 0) {
+ snprintf(mi_buf, sizeof(mi_buf), "(invalid)");
+ gsm48_mi_to_string(mi_buf, sizeof(mi_buf),
+ link_info->imsi,
+ link_info->imsi_len);
+ } else {
+ snprintf(mi_buf, sizeof(mi_buf), "(none)");
+ }
+ vty_out(vty, " TLLI %08x, IMSI %s, AGE %d",
+ link_info->tlli.current, mi_buf, (int)age);
+
+ if (stored_msgs)
+ vty_out(vty, ", STORED %d", stored_msgs);
+
+ if (g_cfg->route_to_sgsn2)
+ vty_out(vty, ", SGSN NSEI %d",
+ link_info->sgsn_nsei);
+
+ if (link_info->is_deregistered)
+ vty_out(vty, ", DE-REGISTERED");
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+ }
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(delete_gb_bvci, delete_gb_bvci_cmd,
+ "delete-gbproxy-peer <0-65534> bvci <2-65534>",
+ "Delete a GBProxy peer by NSEI and optionally BVCI\n"
+ "NSEI number\n"
+ "Only delete peer with a matching BVCI\n"
+ "BVCI number\n")
+{
+ const uint16_t nsei = atoi(argv[0]);
+ const uint16_t bvci = atoi(argv[1]);
+ int counter;
+
+ counter = gbproxy_cleanup_peers(g_cfg, nsei, bvci);
+
+ if (counter == 0) {
+ vty_out(vty, "BVC not found%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(delete_gb_nsei, delete_gb_nsei_cmd,
+ "delete-gbproxy-peer <0-65534> (only-bvc|only-nsvc|all) [dry-run]",
+ "Delete a GBProxy peer by NSEI and optionally BVCI\n"
+ "NSEI number\n"
+ "Only delete BSSGP connections (BVC)\n"
+ "Only delete dynamic NS connections (NS-VC)\n"
+ "Delete BVC and dynamic NS connections\n"
+ "Show what would be deleted instead of actually deleting\n"
+ )
+{
+ const uint16_t nsei = atoi(argv[0]);
+ const char *mode = argv[1];
+ int dry_run = argc > 2;
+ int delete_bvc = 0;
+ int delete_nsvc = 0;
+ int counter;
+
+ if (strcmp(mode, "only-bvc") == 0)
+ delete_bvc = 1;
+ else if (strcmp(mode, "only-nsvc") == 0)
+ delete_nsvc = 1;
+ else
+ delete_bvc = delete_nsvc = 1;
+
+ if (delete_bvc) {
+ if (!dry_run)
+ counter = gbproxy_cleanup_peers(g_cfg, nsei, 0);
+ else {
+ struct gbproxy_peer *peer;
+ counter = 0;
+ llist_for_each_entry(peer, &g_cfg->bts_peers, list) {
+ if (peer->nsei != nsei)
+ continue;
+
+ vty_out(vty, "BVC: ");
+ gbprox_vty_print_peer(vty, peer);
+ counter += 1;
+ }
+ }
+ vty_out(vty, "%sDeleted %d BVC%s",
+ dry_run ? "Not " : "", counter, VTY_NEWLINE);
+ }
+
+ if (delete_nsvc) {
+ struct gprs_ns_inst *nsi = g_cfg->nsi;
+ struct gprs_nsvc *nsvc, *nsvc2;
+
+ counter = 0;
+ llist_for_each_entry_safe(nsvc, nsvc2, &nsi->gprs_nsvcs, list) {
+ if (nsvc->nsei != nsei)
+ continue;
+ if (nsvc->persistent)
+ continue;
+
+ if (!dry_run)
+ gprs_nsvc_delete(nsvc);
+ else
+ vty_out(vty, "NS-VC: NSEI %5u, NS-VCI %5u, "
+ "remote %s%s",
+ nsvc->nsei, nsvc->nsvci,
+ gprs_ns_ll_str(nsvc), VTY_NEWLINE);
+ counter += 1;
+ }
+ vty_out(vty, "%sDeleted %d NS-VC%s",
+ dry_run ? "Not " : "", counter, VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+#define GBPROXY_DELETE_LINK_STR \
+ "Delete a GBProxy logical link entry by NSEI and identification\nNSEI number\n"
+
+DEFUN(delete_gb_link_by_id, delete_gb_link_by_id_cmd,
+ "delete-gbproxy-link <0-65534> (tlli|imsi|sgsn-nsei) IDENT",
+ GBPROXY_DELETE_LINK_STR
+ "Delete entries with a matching TLLI (hex)\n"
+ "Delete entries with a matching IMSI\n"
+ "Delete entries with a matching SGSN NSEI\n"
+ "Identification to match\n")
+{
+ const uint16_t nsei = atoi(argv[0]);
+ enum {MATCH_TLLI = 't', MATCH_IMSI = 'i', MATCH_SGSN = 's'} match;
+ uint32_t ident = 0;
+ const char *imsi = NULL;
+ struct gbproxy_peer *peer = 0;
+ struct gbproxy_link_info *link_info, *nxt;
+ struct gbproxy_patch_state *state;
+ char mi_buf[200];
+ int found = 0;
+
+ match = argv[1][0];
+
+ switch (match) {
+ case MATCH_TLLI: ident = strtoll(argv[2], NULL, 16); break;
+ case MATCH_IMSI: imsi = argv[2]; break;
+ case MATCH_SGSN: ident = strtoll(argv[2], NULL, 0); break;
+ };
+
+ peer = gbproxy_peer_by_nsei(g_cfg, nsei);
+ if (!peer) {
+ vty_out(vty, "Didn't find peer with NSEI %d%s",
+ nsei, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ state = &peer->patch_state;
+
+ llist_for_each_entry_safe(link_info, nxt, &state->logical_links, list) {
+ switch (match) {
+ case MATCH_TLLI:
+ if (link_info->tlli.current != ident)
+ continue;
+ break;
+ case MATCH_SGSN:
+ if (link_info->sgsn_nsei != ident)
+ continue;
+ break;
+ case MATCH_IMSI:
+ if (!link_info->imsi)
+ continue;
+ mi_buf[0] = '\0';
+ gsm48_mi_to_string(mi_buf, sizeof(mi_buf),
+ link_info->imsi,
+ link_info->imsi_len);
+
+ if (strcmp(mi_buf, imsi) != 0)
+ continue;
+ break;
+ }
+
+ vty_out(vty, "Deleting link with TLLI %08x%s", link_info->tlli.current,
+ VTY_NEWLINE);
+ gbproxy_delete_link_info(peer, link_info);
+ found += 1;
+ }
+
+ if (!found && argc >= 2) {
+ vty_out(vty, "Didn't find link entry with %s %s%s",
+ argv[1], argv[2], VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(delete_gb_link, delete_gb_link_cmd,
+ "delete-gbproxy-link <0-65534> (stale|de-registered)",
+ GBPROXY_DELETE_LINK_STR
+ "Delete stale entries\n"
+ "Delete de-registered entries\n")
+{
+ const uint16_t nsei = atoi(argv[0]);
+ enum {MATCH_STALE = 's', MATCH_DEREGISTERED = 'd'} match;
+ struct gbproxy_peer *peer = 0;
+ struct gbproxy_link_info *link_info, *nxt;
+ struct gbproxy_patch_state *state;
+ time_t now;
+ struct timespec ts = {0,};
+
+ int found = 0;
+
+ match = argv[1][0];
+
+ peer = gbproxy_peer_by_nsei(g_cfg, nsei);
+ if (!peer) {
+ vty_out(vty, "Didn't find peer with NSEI %d%s",
+ nsei, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ state = &peer->patch_state;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ now = ts.tv_sec;
+
+ if (match == MATCH_STALE) {
+ found = gbproxy_remove_stale_link_infos(peer, now);
+ if (found)
+ vty_out(vty, "Deleted %d stale logical link%s%s",
+ found, found == 1 ? "" : "s", VTY_NEWLINE);
+ } else {
+ llist_for_each_entry_safe(link_info, nxt,
+ &state->logical_links, list) {
+ if (!link_info->is_deregistered)
+ continue;
+
+ gbproxy_delete_link_info(peer, link_info);
+ found += 1;
+ }
+ }
+
+ if (found)
+ vty_out(vty, "Deleted %d %s logical link%s%s",
+ found, argv[1], found == 1 ? "" : "s", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+/*
+ * legacy commands to provide an upgrade path from "broken" releases
+ * or pre-releases
+ */
+DEFUN_DEPRECATED(cfg_gbproxy_broken_apn_match,
+ cfg_gbproxy_broken_apn_match_cmd,
+ "core-access-point-name none match-imsi .REGEXP",
+ GBPROXY_CORE_APN_STR GBPROXY_MATCH_IMSI_STR "Remove APN\n"
+ "Patch MS related information elements on match only\n"
+ "Route to the secondary SGSN on match only\n"
+ "Regular expression for the IMSI match\n")
+{
+ const char *filter = argv[0];
+ const char *err_msg = NULL;
+ struct gbproxy_match *match;
+ enum gbproxy_match_id match_id = get_string_value(match_ids, "patching");
+
+ /* apply APN none */
+ set_core_apn(vty, "");
+
+ /* do the matching... with copy and paste */
+ OSMO_ASSERT(match_id >= GBPROX_MATCH_PATCHING &&
+ match_id < GBPROX_MATCH_LAST);
+ match = &g_cfg->matches[match_id];
+
+ if (gbproxy_set_patch_filter(match, filter, &err_msg) != 0) {
+ vty_out(vty, "Match expression invalid: %s%s",
+ err_msg, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ g_cfg->acquire_imsi = 1;
+
+ return CMD_SUCCESS;
+}
+
+#define GBPROXY_TLLI_LIST_STR "Set TLLI list parameters\n"
+#define GBPROXY_MAX_LEN_STR "Limit list length\n"
+DEFUN_DEPRECATED(cfg_gbproxy_depr_tlli_list_max_len,
+ cfg_gbproxy_depr_tlli_list_max_len_cmd,
+ "tlli-list max-length <1-99999>",
+ GBPROXY_TLLI_LIST_STR GBPROXY_MAX_LEN_STR
+ "Maximum number of TLLIs in the list\n")
+{
+ g_cfg->tlli_max_len = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+int gbproxy_vty_init(void)
+{
+ install_element_ve(&show_gbproxy_cmd);
+ install_element_ve(&show_gbproxy_links_cmd);
+
+ install_element(ENABLE_NODE, &delete_gb_bvci_cmd);
+ install_element(ENABLE_NODE, &delete_gb_nsei_cmd);
+ install_element(ENABLE_NODE, &delete_gb_link_by_id_cmd);
+ install_element(ENABLE_NODE, &delete_gb_link_cmd);
+
+ install_element(CONFIG_NODE, &cfg_gbproxy_cmd);
+ install_node(&gbproxy_node, config_write_gbproxy);
+ vty_install_default(GBPROXY_NODE);
+ install_element(GBPROXY_NODE, &cfg_nsip_sgsn_nsei_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_core_mcc_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_core_mnc_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_match_imsi_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_core_apn_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_secondary_sgsn_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_patch_ptmsi_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_acquire_imsi_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_link_list_max_age_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_link_list_max_len_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_link_list_keep_mode_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_no_core_mcc_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_no_core_mnc_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_no_match_imsi_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_no_core_apn_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_no_secondary_sgsn_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_no_patch_ptmsi_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_no_acquire_imsi_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_link_list_no_max_age_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_link_list_no_max_len_cmd);
+
+ /* broken or deprecated to allow an upgrade path */
+ install_element(GBPROXY_NODE, &cfg_gbproxy_broken_apn_match_cmd);
+ install_element(GBPROXY_NODE, &cfg_gbproxy_depr_tlli_list_max_len_cmd);
+
+ return 0;
+}
+
+int gbproxy_parse_config(const char *config_file, struct gbproxy_config *cfg)
+{
+ int rc;
+
+ g_cfg = 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;
+ }
+
+ return 0;
+}
+
diff --git a/src/gprs/gprs_gb_parse.c b/src/gprs/gprs_gb_parse.c
new file mode 100644
index 000000000..d5a122bda
--- /dev/null
+++ b/src/gprs/gprs_gb_parse.c
@@ -0,0 +1,636 @@
+/* GPRS Gb message parser */
+
+/* (C) 2014 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 <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+
+#include <openbsc/gprs_gb_parse.h>
+
+#include <openbsc/gprs_utils.h>
+
+#include <openbsc/debug.h>
+
+#include <osmocom/gprs/gprs_bssgp.h>
+
+static int gprs_gb_parse_gmm_attach_req(uint8_t *data, size_t data_len,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ uint8_t *value;
+ size_t value_len;
+
+ parse_ctx->llc_msg_name = "ATTACH_REQ";
+
+ /* Skip MS network capability */
+ if (osmo_shift_lv(&data, &data_len, NULL, &value_len) <= 0 ||
+ value_len < 1 || value_len > 8)
+ /* invalid */
+ return 0;
+
+ /* Skip Attach type */
+ /* Skip Ciphering key sequence number */
+ /* Skip DRX parameter */
+ osmo_shift_v_fixed(&data, &data_len, 3, NULL);
+
+ /* Get Mobile identity */
+ if (osmo_shift_lv(&data, &data_len, &value, &value_len) <= 0 ||
+ value_len < 5 || value_len > 8)
+ /* invalid */
+ return 0;
+
+ if (gprs_is_mi_tmsi(value, value_len)) {
+ parse_ctx->ptmsi_enc = value + 1;
+ } else if (gprs_is_mi_imsi(value, value_len)) {
+ parse_ctx->imsi = value;
+ parse_ctx->imsi_len = value_len;
+ }
+
+ if (osmo_shift_v_fixed(&data, &data_len, 6, &value) <= 0)
+ return 0;
+
+ parse_ctx->old_raid_enc = value;
+
+ return 1;
+}
+
+static int gprs_gb_parse_gmm_attach_ack(uint8_t *data, size_t data_len,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ uint8_t *value;
+ size_t value_len;
+
+ parse_ctx->llc_msg_name = "ATTACH_ACK";
+
+ /* Skip Attach result */
+ /* Skip Force to standby */
+ /* Skip Periodic RA update timer */
+ /* Skip Radio priority for SMS */
+ /* Skip Spare half octet */
+ osmo_shift_v_fixed(&data, &data_len, 3, NULL);
+
+ if (osmo_shift_v_fixed(&data, &data_len, 6, &value) <= 0)
+ return 0;
+
+ parse_ctx->raid_enc = value;
+
+ /* Skip P-TMSI signature (P-TMSI signature, opt, TV, length 4) */
+ osmo_match_shift_tv_fixed(&data, &data_len, GSM48_IE_GMM_PTMSI_SIG, 3, NULL);
+
+ /* Skip Negotiated READY timer value (GPRS timer, opt, TV, length 2) */
+ osmo_match_shift_tv_fixed(&data, &data_len, GSM48_IE_GMM_TIMER_READY, 1, NULL);
+
+ /* Allocated P-TMSI (Mobile identity, opt, TLV, length 7) */
+ if (osmo_match_shift_tlv(&data, &data_len, GSM48_IE_GMM_ALLOC_PTMSI,
+ &value, &value_len) > 0 &&
+ gprs_is_mi_tmsi(value, value_len))
+ parse_ctx->new_ptmsi_enc = value + 1;
+ return 1;
+}
+
+static int gprs_gb_parse_gmm_attach_rej(uint8_t *data, size_t data_len,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ uint8_t *value;
+
+ parse_ctx->llc_msg_name = "ATTACH_REJ";
+
+ /* GMM cause */
+ if (osmo_shift_v_fixed(&data, &data_len, 1, &value) <= 0)
+ return 0;
+
+ parse_ctx->invalidate_tlli = 1;
+
+ return 1;
+}
+
+
+static int gprs_gb_parse_gmm_detach_req(uint8_t *data, size_t data_len,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ uint8_t *value;
+ size_t value_len;
+ int detach_type;
+ int power_off;
+
+ parse_ctx->llc_msg_name = "DETACH_REQ";
+
+ /* Skip spare half octet */
+ /* Get Detach type */
+ if (osmo_shift_v_fixed(&data, &data_len, 1, &value) <= 0)
+ /* invalid */
+ return 0;
+
+ detach_type = *value & 0x07;
+ power_off = *value & 0x08 ? 1 : 0;
+
+ if (parse_ctx->to_bss) {
+ /* Network originated */
+ if (detach_type == GPRS_DET_T_MT_REATT_REQ)
+ parse_ctx->await_reattach = 1;
+ } else {
+ /* Mobile originated */
+
+ if (power_off)
+ parse_ctx->invalidate_tlli = 1;
+
+ /* Get P-TMSI (Mobile identity), see GSM 24.008, 9.4.5.2 */
+ if (osmo_match_shift_tlv(&data, &data_len,
+ GSM48_IE_GMM_ALLOC_PTMSI, &value, &value_len) > 0)
+ {
+ if (gprs_is_mi_tmsi(value, value_len))
+ parse_ctx->ptmsi_enc = value + 1;
+ }
+ }
+
+ return 1;
+}
+
+static int gprs_gb_parse_gmm_ra_upd_req(uint8_t *data, size_t data_len,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ uint8_t *value;
+
+ parse_ctx->llc_msg_name = "RA_UPD_REQ";
+
+ /* Skip Update type */
+ /* Skip GPRS ciphering key sequence number */
+ osmo_shift_v_fixed(&data, &data_len, 1, NULL);
+
+ if (osmo_shift_v_fixed(&data, &data_len, 6, &value) <= 0)
+ return 0;
+
+ parse_ctx->old_raid_enc = value;
+
+ return 1;
+}
+
+static int gprs_gb_parse_gmm_ra_upd_rej(uint8_t *data, size_t data_len,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ uint8_t *value;
+ uint8_t cause;
+ int force_standby;
+
+ parse_ctx->llc_msg_name = "RA_UPD_REJ";
+
+ /* GMM cause */
+ if (osmo_shift_v_fixed(&data, &data_len, 1, &value) <= 0)
+ return 0;
+
+ cause = value[0];
+
+ /* Force to standby, 1/2 */
+ /* spare bits, 1/2 */
+ if (osmo_shift_v_fixed(&data, &data_len, 1, &value) <= 0)
+ return 0;
+
+ force_standby = (value[0] & 0x07) == 0x01;
+
+ if (cause == GMM_CAUSE_IMPL_DETACHED && !force_standby)
+ parse_ctx->await_reattach = 1;
+
+ parse_ctx->invalidate_tlli = 1;
+
+ return 1;
+}
+
+static int gprs_gb_parse_gmm_ra_upd_ack(uint8_t *data, size_t data_len,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ uint8_t *value;
+ size_t value_len;
+
+ parse_ctx->llc_msg_name = "RA_UPD_ACK";
+
+ /* Skip Force to standby */
+ /* Skip Update result */
+ /* Skip Periodic RA update timer */
+ osmo_shift_v_fixed(&data, &data_len, 2, NULL);
+
+ if (osmo_shift_v_fixed(&data, &data_len, 6, &value) <= 0)
+ return 0;
+
+ parse_ctx->raid_enc = value;
+
+ /* Skip P-TMSI signature (P-TMSI signature, opt, TV, length 4) */
+ osmo_match_shift_tv_fixed(&data, &data_len, GSM48_IE_GMM_PTMSI_SIG, 3, NULL);
+
+ /* Allocated P-TMSI (Mobile identity, opt, TLV, length 7) */
+ if (osmo_match_shift_tlv(&data, &data_len, GSM48_IE_GMM_ALLOC_PTMSI,
+ &value, &value_len) > 0 &&
+ gprs_is_mi_tmsi(value, value_len))
+ parse_ctx->new_ptmsi_enc = value + 1;
+
+ return 1;
+}
+
+static int gprs_gb_parse_gmm_ptmsi_reall_cmd(uint8_t *data, size_t data_len,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ uint8_t *value;
+ size_t value_len;
+
+ parse_ctx->llc_msg_name = "PTMSI_REALL_CMD";
+
+ LOGP(DLLC, LOGL_NOTICE,
+ "Got P-TMSI Reallocation Command which is not covered by unit tests yet.\n");
+
+ /* Allocated P-TMSI */
+ if (osmo_shift_lv(&data, &data_len, &value, &value_len) > 0 &&
+ gprs_is_mi_tmsi(value, value_len))
+ parse_ctx->new_ptmsi_enc = value + 1;
+
+ if (osmo_shift_v_fixed(&data, &data_len, 6, &value) <= 0)
+ return 0;
+
+ parse_ctx->raid_enc = value;
+
+ return 1;
+}
+
+static int gprs_gb_parse_gmm_id_resp(uint8_t *data, size_t data_len,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ uint8_t *value;
+ size_t value_len;
+
+ parse_ctx->llc_msg_name = "ID_RESP";
+
+ /* Mobile identity, Mobile identity 10.5.1.4, M LV 2-10 */
+ if (osmo_shift_lv(&data, &data_len, &value, &value_len) <= 0 ||
+ value_len < 1 || value_len > 9)
+ /* invalid */
+ return 0;
+
+ if (gprs_is_mi_tmsi(value, value_len)) {
+ parse_ctx->ptmsi_enc = value + 1;
+ } else if (gprs_is_mi_imsi(value, value_len)) {
+ parse_ctx->imsi = value;
+ parse_ctx->imsi_len = value_len;
+ }
+
+ return 1;
+}
+
+static int gprs_gb_parse_gsm_act_pdp_req(uint8_t *data, size_t data_len,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ ssize_t old_len;
+ uint8_t *value;
+ size_t value_len;
+
+ parse_ctx->llc_msg_name = "ACT_PDP_REQ";
+
+ /* Skip Requested NSAPI */
+ /* Skip Requested LLC SAPI */
+ osmo_shift_v_fixed(&data, &data_len, 2, NULL);
+
+ /* Skip Requested QoS (support 04.08 and 24.008) */
+ if (osmo_shift_lv(&data, &data_len, NULL, &value_len) <= 0 ||
+ value_len < 4 || value_len > 14)
+ /* invalid */
+ return 0;
+
+ /* Skip Requested PDP address */
+ if (osmo_shift_lv(&data, &data_len, NULL, &value_len) <= 0 ||
+ value_len < 2 || value_len > 18)
+ /* invalid */
+ return 0;
+
+ /* Access point name */
+ old_len = osmo_match_shift_tlv(&data, &data_len,
+ GSM48_IE_GSM_APN, &value, &value_len);
+
+ if (old_len > 0 && value_len >=1 && value_len <= 100) {
+ parse_ctx->apn_ie = data - old_len;
+ parse_ctx->apn_ie_len = old_len;
+ }
+
+ return 1;
+}
+
+int gprs_gb_parse_dtap(uint8_t *data, size_t data_len,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ struct gsm48_hdr *g48h;
+ uint8_t pdisc;
+ uint8_t msg_type;
+
+ if (osmo_shift_v_fixed(&data, &data_len, sizeof(*g48h), (uint8_t **)&g48h) <= 0)
+ return 0;
+
+ parse_ctx->g48_hdr = g48h;
+
+ pdisc = gsm48_hdr_pdisc(g48h);
+ if (pdisc != GSM48_PDISC_MM_GPRS && pdisc != GSM48_PDISC_SM_GPRS)
+ return 1;
+
+ msg_type = gsm48_hdr_msg_type(g48h);
+ switch (msg_type) {
+ case GSM48_MT_GMM_ATTACH_REQ:
+ return gprs_gb_parse_gmm_attach_req(data, data_len, parse_ctx);
+
+ case GSM48_MT_GMM_ATTACH_REJ:
+ return gprs_gb_parse_gmm_attach_rej(data, data_len, parse_ctx);
+
+ case GSM48_MT_GMM_ATTACH_ACK:
+ return gprs_gb_parse_gmm_attach_ack(data, data_len, parse_ctx);
+
+ case GSM48_MT_GMM_RA_UPD_REQ:
+ return gprs_gb_parse_gmm_ra_upd_req(data, data_len, parse_ctx);
+
+ case GSM48_MT_GMM_RA_UPD_REJ:
+ return gprs_gb_parse_gmm_ra_upd_rej(data, data_len, parse_ctx);
+
+ case GSM48_MT_GMM_RA_UPD_ACK:
+ return gprs_gb_parse_gmm_ra_upd_ack(data, data_len, parse_ctx);
+
+ case GSM48_MT_GMM_PTMSI_REALL_CMD:
+ return gprs_gb_parse_gmm_ptmsi_reall_cmd(data, data_len, parse_ctx);
+
+ case GSM48_MT_GSM_ACT_PDP_REQ:
+ return gprs_gb_parse_gsm_act_pdp_req(data, data_len, parse_ctx);
+
+ case GSM48_MT_GMM_ID_RESP:
+ return gprs_gb_parse_gmm_id_resp(data, data_len, parse_ctx);
+
+ case GSM48_MT_GMM_DETACH_REQ:
+ return gprs_gb_parse_gmm_detach_req(data, data_len, parse_ctx);
+
+ case GSM48_MT_GMM_DETACH_ACK:
+ parse_ctx->llc_msg_name = "DETACH_ACK";
+ parse_ctx->invalidate_tlli = 1;
+ break;
+
+ default:
+ LOGP(DLLC, LOGL_NOTICE,
+ "Unhandled GSM 04.08 message type %s for protocol discriminator %s.\n",
+ get_value_string(gprs_msgt_gmm_names, msg_type), get_value_string(gsm48_pdisc_names, pdisc));
+ break;
+ };
+
+ return 1;
+}
+
+int gprs_gb_parse_llc(uint8_t *llc, size_t llc_len,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ struct gprs_llc_hdr_parsed *ghp = &parse_ctx->llc_hdr_parsed;
+ int rc;
+ int fcs;
+
+ /* parse LLC */
+ rc = gprs_llc_hdr_parse(ghp, llc, llc_len);
+ gprs_llc_hdr_dump(ghp, NULL);
+ if (rc != 0) {
+ LOGP(DLLC, LOGL_NOTICE, "Error during LLC header parsing\n");
+ return 0;
+ }
+
+ fcs = gprs_llc_fcs(llc, ghp->crc_length);
+ LOGP(DLLC, LOGL_DEBUG, "Got LLC message, CRC: %06x (computed %06x)\n",
+ ghp->fcs, fcs);
+
+ if (!ghp->data)
+ return 0;
+
+ if (ghp->sapi != GPRS_SAPI_GMM)
+ return 1;
+
+ if (ghp->cmd != GPRS_LLC_UI)
+ return 1;
+
+ if (ghp->is_encrypted) {
+ parse_ctx->need_decryption = 1;
+ return 0;
+ }
+
+ return gprs_gb_parse_dtap(ghp->data, ghp->data_len, parse_ctx);
+}
+
+int gprs_gb_parse_bssgp(uint8_t *bssgp, size_t bssgp_len,
+ struct gprs_gb_parse_context *parse_ctx)
+{
+ struct bssgp_normal_hdr *bgph;
+ struct bssgp_ud_hdr *budh = NULL;
+ struct tlv_parsed *tp = &parse_ctx->bssgp_tp;
+ uint8_t pdu_type;
+ uint8_t *data;
+ size_t data_len;
+ int rc;
+
+ if (bssgp_len < sizeof(struct bssgp_normal_hdr))
+ return 0;
+
+ bgph = (struct bssgp_normal_hdr *)bssgp;
+ pdu_type = bgph->pdu_type;
+
+ if (pdu_type == BSSGP_PDUT_UL_UNITDATA ||
+ pdu_type == BSSGP_PDUT_DL_UNITDATA) {
+ if (bssgp_len < sizeof(struct bssgp_ud_hdr))
+ return 0;
+ budh = (struct bssgp_ud_hdr *)bssgp;
+ bgph = NULL;
+ data = budh->data;
+ data_len = bssgp_len - sizeof(*budh);
+ } else {
+ data = bgph->data;
+ data_len = bssgp_len - sizeof(*bgph);
+ }
+
+ parse_ctx->pdu_type = pdu_type;
+ parse_ctx->bud_hdr = budh;
+ parse_ctx->bgp_hdr = bgph;
+ parse_ctx->bssgp_data = data;
+ parse_ctx->bssgp_data_len = data_len;
+
+ if (bssgp_tlv_parse(tp, data, data_len) < 0)
+ return 0;
+
+ if (budh)
+ parse_ctx->tlli_enc = (uint8_t *)&budh->tlli;
+
+ if (TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA))
+ parse_ctx->bssgp_raid_enc = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA);
+
+ if (TLVP_PRESENT(tp, BSSGP_IE_CELL_ID))
+ parse_ctx->bssgp_raid_enc = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_CELL_ID);
+
+ if (TLVP_PRESENT(tp, BSSGP_IE_IMSI)) {
+ parse_ctx->imsi = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_IMSI);
+ parse_ctx->imsi_len = TLVP_LEN(tp, BSSGP_IE_IMSI);
+ }
+
+ if (TLVP_PRESENT(tp, BSSGP_IE_TLLI)) {
+ if (parse_ctx->tlli_enc)
+ /* This is TLLI old, don't confuse it with TLLI current */
+ parse_ctx->old_tlli_enc = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_TLLI);
+ else
+ parse_ctx->tlli_enc = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_TLLI);
+ }
+
+ if (TLVP_PRESENT(tp, BSSGP_IE_TMSI) && pdu_type == BSSGP_PDUT_PAGING_PS)
+ parse_ctx->bssgp_ptmsi_enc = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_TMSI);
+
+ if (TLVP_PRESENT(tp, BSSGP_IE_LLC_PDU)) {
+ uint8_t *llc = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_LLC_PDU);
+ size_t llc_len = TLVP_LEN(tp, BSSGP_IE_LLC_PDU);
+
+ rc = gprs_gb_parse_llc(llc, llc_len, parse_ctx);
+ if (!rc)
+ return 0;
+
+ parse_ctx->llc = llc;
+ parse_ctx->llc_len = llc_len;
+ }
+
+ if (parse_ctx->tlli_enc) {
+ uint32_t tmp_tlli;
+ memcpy(&tmp_tlli, parse_ctx->tlli_enc, sizeof(tmp_tlli));
+ parse_ctx->tlli = ntohl(tmp_tlli);
+ }
+
+ if (parse_ctx->bssgp_raid_enc && parse_ctx->old_raid_enc &&
+ memcmp(parse_ctx->bssgp_raid_enc, parse_ctx->old_raid_enc, 6) != 0)
+ parse_ctx->old_raid_is_foreign = 1;
+
+ return 1;
+}
+
+void gprs_gb_log_parse_context(int log_level,
+ struct gprs_gb_parse_context *parse_ctx,
+ const char *default_msg_name)
+{
+ const char *msg_name;
+ const char *sep = "";
+
+ if (!parse_ctx->tlli_enc &&
+ !parse_ctx->ptmsi_enc &&
+ !parse_ctx->new_ptmsi_enc &&
+ !parse_ctx->bssgp_ptmsi_enc &&
+ !parse_ctx->imsi)
+ return;
+
+ msg_name = gprs_gb_message_name(parse_ctx, default_msg_name);
+
+ if (parse_ctx->llc_msg_name)
+ msg_name = parse_ctx->llc_msg_name;
+
+ LOGP(DGPRS, log_level, "%s: Got", msg_name);
+
+ if (parse_ctx->tlli_enc) {
+ LOGPC(DGPRS, log_level, "%s TLLI %08x", sep, parse_ctx->tlli);
+ sep = ",";
+ }
+
+ if (parse_ctx->old_tlli_enc) {
+ LOGPC(DGPRS, log_level, "%s old TLLI %02x%02x%02x%02x", sep,
+ parse_ctx->old_tlli_enc[0],
+ parse_ctx->old_tlli_enc[1],
+ parse_ctx->old_tlli_enc[2],
+ parse_ctx->old_tlli_enc[3]);
+ sep = ",";
+ }
+
+ if (parse_ctx->bssgp_raid_enc) {
+ struct gprs_ra_id raid;
+ gsm48_parse_ra(&raid, parse_ctx->bssgp_raid_enc);
+ LOGPC(DGPRS, log_level, "%s BSSGP RAID %u-%u-%u-%u", sep,
+ raid.mcc, raid.mnc, raid.lac, raid.rac);
+ sep = ",";
+ }
+
+ if (parse_ctx->raid_enc) {
+ struct gprs_ra_id raid;
+ gsm48_parse_ra(&raid, parse_ctx->raid_enc);
+ LOGPC(DGPRS, log_level, "%s RAID %u-%u-%u-%u", sep,
+ raid.mcc, raid.mnc, raid.lac, raid.rac);
+ sep = ",";
+ }
+
+ if (parse_ctx->old_raid_enc) {
+ struct gprs_ra_id raid;
+ gsm48_parse_ra(&raid, parse_ctx->old_raid_enc);
+ LOGPC(DGPRS, log_level, "%s old RAID %u-%u-%u-%u", sep,
+ raid.mcc, raid.mnc, raid.lac, raid.rac);
+ sep = ",";
+ }
+
+ if (parse_ctx->bssgp_ptmsi_enc) {
+ uint32_t ptmsi = GSM_RESERVED_TMSI;
+ gprs_parse_tmsi(parse_ctx->bssgp_ptmsi_enc, &ptmsi);
+ LOGPC(DGPRS, log_level, "%s BSSGP PTMSI %08x", sep, ptmsi);
+ sep = ",";
+ }
+
+ if (parse_ctx->ptmsi_enc) {
+ uint32_t ptmsi = GSM_RESERVED_TMSI;
+ gprs_parse_tmsi(parse_ctx->ptmsi_enc, &ptmsi);
+ LOGPC(DGPRS, log_level, "%s PTMSI %08x", sep, ptmsi);
+ sep = ",";
+ }
+
+ if (parse_ctx->new_ptmsi_enc) {
+ uint32_t new_ptmsi = GSM_RESERVED_TMSI;
+ gprs_parse_tmsi(parse_ctx->new_ptmsi_enc, &new_ptmsi);
+ LOGPC(DGPRS, log_level, "%s new PTMSI %08x", sep, new_ptmsi);
+ sep = ",";
+ }
+
+ if (parse_ctx->imsi) {
+ char mi_buf[200];
+ mi_buf[0] = '\0';
+ gsm48_mi_to_string(mi_buf, sizeof(mi_buf),
+ parse_ctx->imsi, parse_ctx->imsi_len);
+ LOGPC(DGPRS, log_level, "%s IMSI %s",
+ sep, mi_buf);
+ sep = ",";
+ }
+ if (parse_ctx->invalidate_tlli) {
+ LOGPC(DGPRS, log_level, "%s invalidate", sep);
+ sep = ",";
+ }
+ if (parse_ctx->await_reattach) {
+ LOGPC(DGPRS, log_level, "%s re-attach", sep);
+ sep = ",";
+ }
+
+ LOGPC(DGPRS, log_level, "\n");
+}
+
+const char *gprs_gb_message_name(const struct gprs_gb_parse_context *parse_ctx,
+ const char *default_msg_name)
+{
+ if (parse_ctx->llc_msg_name)
+ return parse_ctx->llc_msg_name;
+
+ if (parse_ctx->g48_hdr)
+ return "GMM";
+
+ if (parse_ctx->llc)
+ return "LLC";
+
+ if (parse_ctx->bud_hdr)
+ return "BSSGP-UNITDATA";
+
+ if (parse_ctx->bgp_hdr)
+ return "BSSGP";
+
+ return "unknown";
+}
diff --git a/src/gprs/gprs_gmm.c b/src/gprs/gprs_gmm.c
new file mode 100644
index 000000000..e6751db7c
--- /dev/null
+++ b/src/gprs/gprs_gmm.c
@@ -0,0 +1,2939 @@
+/* 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 <openssl/rand.h>
+
+#include "bscconfig.h"
+
+#include <openbsc/db.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/crypt/auth.h>
+#include <osmocom/gsm/apn.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+
+#include <osmocom/gprs/gprs_bssgp.h>
+
+#ifdef BUILD_IU
+#include <osmocom/ranap/ranap_ies_defs.h>
+#include <osmocom/ranap/ranap_msg_factory.h>
+#endif
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/paging.h>
+#include <openbsc/transaction.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_gmm.h>
+#include <openbsc/gprs_utils.h>
+#include <openbsc/gprs_subscriber.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/signal.h>
+#include <openbsc/iu.h>
+#include <openbsc/gprs_sndcp.h>
+
+#include <pdp.h>
+
+#define PTMSI_ALLOC
+
+extern struct sgsn_instance *sgsn;
+
+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 },
+ },
+};
+
+static const struct value_string gprs_pmm_state_names[] = {
+ { PMM_DETACHED, "PMM DETACH" },
+ { PMM_CONNECTED, "PMM CONNECTED" },
+ { PMM_IDLE, "PMM IDLE" },
+ { MM_IDLE, "MM IDLE" },
+ { MM_READY, "MM READY" },
+ { MM_STANDBY, "MM STANDBY" },
+ { 0, NULL }
+};
+
+static int gsm48_gmm_authorize(struct sgsn_mm_ctx *ctx);
+
+static void mmctx_change_gtpu_endpoints_to_sgsn(struct sgsn_mm_ctx *mm_ctx)
+{
+ struct sgsn_pdp_ctx *pdp;
+ llist_for_each_entry(pdp, &mm_ctx->pdp_list, list) {
+ sgsn_pdp_upd_gtp_u(pdp,
+ &sgsn->cfg.gtp_listenaddr.sin_addr,
+ sizeof(sgsn->cfg.gtp_listenaddr.sin_addr));
+ }
+}
+
+void mmctx_set_pmm_state(struct sgsn_mm_ctx *ctx, enum gprs_pmm_state state)
+{
+ if (ctx->ran_type != MM_CTX_T_UTRAN_Iu)
+ return;
+
+ if (ctx->pmm_state == state)
+ return;
+
+ LOGMMCTXP(LOGL_INFO, ctx, "Changing PMM state from %s to %s\n",
+ get_value_string(gprs_pmm_state_names, ctx->pmm_state),
+ get_value_string(gprs_pmm_state_names, state));
+
+ switch (state) {
+ case PMM_IDLE:
+ /* TODO: start RA Upd timer */
+ mmctx_change_gtpu_endpoints_to_sgsn(ctx);
+ break;
+ case PMM_CONNECTED:
+ break;
+ default:
+ break;
+ }
+
+ ctx->pmm_state = state;
+}
+
+void mmctx_set_mm_state(struct sgsn_mm_ctx *ctx, enum gprs_pmm_state state)
+{
+ if (ctx->ran_type != MM_CTX_T_GERAN_Gb)
+ return;
+
+ if (ctx->pmm_state == state)
+ return;
+
+ LOGMMCTXP(LOGL_INFO, ctx, "Changing MM state from %s to %s\n",
+ get_value_string(gprs_pmm_state_names, ctx->pmm_state),
+ get_value_string(gprs_pmm_state_names, state));
+
+ ctx->pmm_state = state;
+}
+
+#ifdef BUILD_IU
+int sgsn_ranap_rab_ass_resp(struct sgsn_mm_ctx *ctx, RANAP_RAB_SetupOrModifiedItemIEs_t *setup_ies);
+int sgsn_ranap_iu_event(struct ue_conn_ctx *ctx, enum iu_event_type type, void *data)
+{
+ struct sgsn_mm_ctx *mm;
+ int rc = -1;
+
+ mm = sgsn_mm_ctx_by_ue_ctx(ctx);
+ if (!mm) {
+ LOGP(DRANAP, LOGL_NOTICE, "Cannot find mm ctx for IU event %i!\n", type);
+ return rc;
+ }
+
+ switch (type) {
+ case IU_EVENT_RAB_ASSIGN:
+ rc = sgsn_ranap_rab_ass_resp(mm, (RANAP_RAB_SetupOrModifiedItemIEs_t *)data);
+ break;
+ case IU_EVENT_IU_RELEASE:
+ /* fall thru */
+ case IU_EVENT_LINK_INVALIDATED:
+ /* Clean up ue_conn_ctx here */
+ LOGMMCTXP(LOGL_INFO, mm, "IU release for imsi %s\n", mm->imsi);
+ if (mm->pmm_state == PMM_CONNECTED)
+ mmctx_set_pmm_state(mm, PMM_IDLE);
+ rc = 0;
+ break;
+ case IU_EVENT_SECURITY_MODE_COMPLETE:
+ /* Continue authentication here */
+ mm->iu.ue_ctx->integrity_active = 1;
+ rc = gsm48_gmm_authorize(mm);
+ break;
+ default:
+ LOGP(DRANAP, LOGL_NOTICE, "Unknown event received: %i\n", type);
+ rc = -1;
+ break;
+ }
+ return rc;
+}
+#endif
+
+
+/* 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 int seconds)
+{
+ if (osmo_timer_pending(&mm->timer))
+ LOGMMCTXP(LOGL_ERROR, mm, "Starting MM timer %u while old "
+ "timer %u pending\n", T, mm->T);
+ 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)
+{
+ return sgsn->cfg.timers.T3314 + (sgsn->cfg.timers.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 iu_tx(msg, GPRS_SAPI_GMM);
+#endif
+ }
+
+#ifdef BUILD_IU
+ /* In Iu mode, msg->dst contains the ue_conn_ctx pointer, in Gb mode
+ * dst is empty. */
+ /* FIXME: have a more explicit indicator for Iu messages */
+ if (msg->dst)
+ return 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->dst = old->dst;
+}
+
+/* Store BVCI/NSEI in MM context */
+static void msgid2mmctx(struct sgsn_mm_ctx *mm, const struct msgb *msg)
+{
+ mm->gb.bvci = msgb_bvci(msg);
+ mm->gb.nsei = msgb_nsei(msg);
+ /* In case a Iu connection is reconnected we need to update the ue ctx */
+ mm->iu.ue_ctx = msg->dst;
+}
+
+/* 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->dst = 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;
+ mmctx_set_pmm_state(ctx, PMM_DETACHED);
+ mmctx_set_pmm_state(ctx, MM_IDLE);
+
+ 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 */
+static 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;
+#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 */
+ aa->ra_upd_timer = gprs_secs_to_tmr_floor(sgsn->cfg.timers.T3312);
+ aa->radio_prio = 4; /* lowest */
+ gsm48_construct_ra(aa->ra_id.digits, &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.
+ */
+ msgb_tv_put(msg, GSM48_IE_GMM_TIMER_READY,
+ gprs_secs_to_tmr_floor(sgsn->cfg.timers.T3314));
+
+#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);
+}
+static 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 */
+static 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 Section 9.4.9: Authentication and Ciphering Request */
+static 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;
+
+ LOGMMCTXP(LOGL_INFO, mm, "<- GPRS AUTH AND CIPHERING REQ (rand = %s",
+ osmo_hexdump(vec->rand, sizeof(vec->rand)));
+ 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: */
+ if (RAND_bytes(&rbyte, 1) != 1) {
+ LOGP(DMM, LOGL_NOTICE, "RAND_bytes failed for A&C ref, falling "
+ "back to rand()\n");
+ acreq->ac_ref_nr = rand();
+ } else
+ 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);
+}
+
+/* Section 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 bool 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;
+
+ if (!vec)
+ return true; /* really!? */
+
+ /* 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. */
+ if (is_utran
+ || (mmctx_is_r99(ctx) && (vec->auth_types & OSMO_AUTH_TYPE_UMTS))) {
+ 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 false;
+ }
+
+ 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 true;
+
+auth_mismatch:
+ LOGMMCTXP(LOGL_ERROR, ctx, "Auth mismatch: expected %s = %s\n",
+ expect_str, osmo_hexdump_nospc(expect_res, expect_res_len));
+ return false;
+}
+
+/* Section 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));
+ rc = check_auth_resp(ctx, false, &at->vec, res, res_len);
+ if (!rc) {
+ rc = gsm48_tx_gmm_auth_ciph_rej(ctx);
+ mm_ctx_cleanup_free(ctx, "GPRS AUTH AND CIPH REJECT");
+ return rc;
+ }
+
+ ctx->is_authenticated = 1;
+
+ 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 gsm48_gmm_authorize(ctx);
+}
+
+/* Section 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 0;
+ /* 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;
+}
+
+static 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));
+ }
+}
+
+static 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);
+
+#ifdef BUILD_IU
+void activate_pdp_rabs(struct sgsn_mm_ctx *ctx)
+{
+ /* Send RAB activation requests for all PDP contexts */
+ struct sgsn_pdp_ctx *pdp;
+ llist_for_each_entry(pdp, &ctx->pdp_list, list) {
+ iu_rab_act_ps(pdp->nsapi, pdp, 1);
+ }
+}
+#endif
+
+/* Check if we can already authorize a subscriber */
+static 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, sgsn->cfg.timers.T3370);
+ 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, sgsn->cfg.timers.T3370);
+ 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 && !ctx->is_authenticated) {
+ struct gsm_auth_tuple *at = &ctx->auth_triplet;
+
+ mmctx_timer_start(ctx, 3360, sgsn->cfg.timers.T3360);
+ return gsm48_tx_gmm_auth_ciph_req(ctx, &at->vec, at->key_seq,
+ false);
+ }
+
+ if (ctx->auth_state == SGSN_AUTH_AUTHENTICATE && ctx->is_authenticated &&
+ 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 = iu_tx_sec_mode_cmd(ctx->iu.ue_ctx, &ctx->auth_triplet, 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, sgsn->cfg.timers.T3350);
+ 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;
+ mmctx_set_pmm_state(ctx, PMM_CONNECTED);
+ 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->is_authenticated = 0;
+
+ 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 */
+ gsm48_gmm_authorize(ctx);
+ 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);
+ gsm48_tx_gmm_att_rej(ctx, gmm_cause);
+ mm_ctx_cleanup_free(ctx, "GPRS ATTACH REJECT");
+ 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_REATT_NOTREQ, 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;
+ 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 gsm48_gmm_authorize(ctx);
+}
+
+/* Section 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. */
+
+ /* In Iu mode, msg->dst contains the ue_conn_ctx pointer, in Gb mode
+ * dst is empty. */
+ /* FIXME: have a more explicit indicator for Iu messages */
+ if (!msg->dst) {
+ /* Gb mode */
+ cid = bssgp_parse_cell_id(&ra_id, msgb_bcid(msg));
+ } else
+ ra_id = ((struct ue_conn_ctx*)msg->dst)->ra_id;
+
+ /* 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 */
+
+ /* 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 0
+ return gsm48_tx_gmm_att_rej(msg, GMM_CAUSE_IMSI_UNKNOWN);
+#else
+ if (msg->dst)
+ ctx = sgsn_mm_ctx_alloc_iu(msg->dst);
+ 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));
+#endif
+ }
+ if (ctx->ran_type == MM_CTX_T_GERAN_Gb) {
+ ctx->gb.tlli = msgb_tlli(msg);
+ ctx->gb.llme = llme;
+ }
+ 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) {
+ /* Allocate a context as most of our code expects one.
+ * Context will not have an IMSI ultil ID RESP is received */
+ if (msg->dst)
+ ctx = sgsn_mm_ctx_alloc_iu(msg->dst);
+ else
+ ctx = sgsn_mm_ctx_alloc_gb(msgb_tlli(msg), &ra_id);
+ ctx->p_tmsi = tmsi;
+ }
+ if (ctx->ran_type == MM_CTX_T_GERAN_Gb) {
+ ctx->gb.tlli = msgb_tlli(msg);
+ ctx->gb.llme = llme;
+ }
+ msgid2mmctx(ctx, msg);
+ 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;
+ }
+ /* 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;
+ else if (ctx->ran_type == MM_CTX_T_UTRAN_Iu) {
+ /* DEVELOPMENT HACK: Our current HLR does not support 3G
+ * authentication tokens. A new HLR/VLR implementation is being
+ * developed. Until it is ready and actual milenage
+ * authentication is properly supported, we are hardcoding a
+ * fixed Ki and use 2G auth. */
+ unsigned char tmp_rand[16];
+ /* Ki 000102030405060708090a0b0c0d0e0f */
+ struct osmo_sub_auth_data auth = {
+ .type = OSMO_AUTH_TYPE_GSM,
+ .algo = OSMO_AUTH_ALG_COMP128v1,
+ .u.gsm.ki = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+ 0x0e, 0x0f
+ },
+ };
+ /* XXX: Hack to make 3G auth work with special SIM card */
+ ctx->auth_state = SGSN_AUTH_AUTHENTICATE;
+
+ RAND_bytes(tmp_rand, 16);
+
+ memset(&ctx->auth_triplet.vec, 0, sizeof(ctx->auth_triplet.vec));
+ osmo_auth_gen_vec(&ctx->auth_triplet.vec, &auth, tmp_rand);
+
+ ctx->auth_triplet.key_seq = 0;
+ }
+
+ /* 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 */
+ /* Don't change the P-TMSI if a P-TMSI re-assignment is under way */
+ if (ctx->gmm_state != GMM_COMMON_PROC_INIT) {
+ ctx->p_tmsi_old = ctx->p_tmsi;
+ ctx->p_tmsi = sgsn_alloc_ptmsi();
+ }
+ ctx->gmm_state = GMM_COMMON_PROC_INIT;
+#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 (ctx->is_authenticated)
+ gprs_llme_copy_key(ctx, ctx->gb.llme);
+
+ gprs_llgmm_assign(ctx->gb.llme, ctx->gb.tlli, ctx->gb.tlli_new);
+ }
+
+ ctx->pending_req = GSM48_MT_GMM_ATTACH_REQ;
+ return gsm48_gmm_authorize(ctx);
+
+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
+ gprs_llgmm_unassign(llme);
+
+ return rc;
+
+}
+
+/* Section 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;
+
+ 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 */
+ rua->ra_upd_timer = gprs_secs_to_tmr_floor(sgsn->cfg.timers.T3312);
+
+ gsm48_construct_ra(rua->ra_id.digits, &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 */
+ msgb_tv_put(msg, GSM48_IE_GMM_TIMER_READY,
+ gprs_secs_to_tmr_floor(sgsn->cfg.timers.T3314));
+
+ /* Option: MS ID, ... */
+ return gsm48_gmm_sendmsg(msg, 0, mm, true);
+}
+
+/* Chapter 9.4.17: Routing area update reject */
+static 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);
+ }
+ }
+ }
+}
+
+/* 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 */
+
+ /* Update Type 10.5.5.18 */
+ upd_type = *cur++ & 0x07;
+
+ rate_ctr_inc(&sgsn->rate_ctrs->ctr[CTR_GPRS_ROUTING_AREA_REQUEST]);
+ LOGP(DMM, LOGL_INFO, "-> 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) {
+ 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:
+ LOGP(DMM, LOGL_NOTICE, "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 */
+ /* In Iu mode, msg->dst contains the ue_conn_ctx pointer, in Gb
+ * mode dst is empty. */
+ /* FIXME: have a more explicit indicator for Iu messages */
+ if (!msg->dst) {
+ 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);
+ 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
+ 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: %d-%d-%d-%d\n",
+ msgb_tlli(msg),
+ mmctx->p_tmsi, mmctx->p_tmsi_old,
+ mmctx->gb.tlli, mmctx->gb.tlli_new,
+ mmctx->ra.mcc, mmctx->ra.mnc,
+ mmctx->ra.lac, mmctx->ra.rac);
+
+ 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: %d-%d-%d-%d\n",
+ mmctx->ra.mcc, mmctx->ra.mnc,
+ mmctx->ra.lac, mmctx->ra.rac);
+ mmctx = NULL;
+ }
+
+ if (!mmctx) {
+ if (llme) {
+ /* send a XID reset to re-set all LLC sequence numbers
+ * in the MS */
+ LOGMMCTXP(LOGL_NOTICE, mmctx, "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 */
+ 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
+ /* Don't change the P-TMSI if a P-TMSI re-assignment is under way */
+ if (mmctx->gmm_state != GMM_COMMON_PROC_INIT) {
+ mmctx->p_tmsi_old = mmctx->p_tmsi;
+ mmctx->p_tmsi = sgsn_alloc_ptmsi();
+ }
+ /* Start T3350 and re-transmit up to 5 times until ATTACH COMPLETE */
+ mmctx->t3350_mode = GMM_T3350_MODE_RAU;
+ mmctx_timer_start(mmctx, 3350, sgsn->cfg.timers.T3350);
+
+ mmctx->gmm_state = GMM_COMMON_PROC_INIT;
+#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 old active */
+ 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 Section 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 ciph_seq_nr, 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->dst) {
+ LOGPC(DMM, LOGL_INFO, "Invalid if not in Iu mode\n");
+ return -1;
+ }
+
+ /* Skip Ciphering key sequence number 10.5.1.2 */
+ 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_NET_FAIL;
+ 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_NET_FAIL;
+ 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;
+
+ /* TODO: Handle those only in case of accept? */
+ /* 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(ctx, pdp_status);
+ }
+
+
+ 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;
+}
+
+/* GPRS Mobility Management */
+static 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;
+
+ /* MMCTX can be NULL when called */
+ 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) {
+ LOGP(DMM, 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);
+
+ 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");
+ 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_llme_copy_key(mmctx, mmctx->gb.llme);
+ gprs_llgmm_assign(mmctx->gb.llme, 0xffffffff,
+ mmctx->gb.tlli_new);
+ }
+ mmctx->gmm_state = GMM_REGISTERED_NORMAL;
+ mmctx_set_pmm_state(mmctx, PMM_CONNECTED);
+ mmctx_set_mm_state(mmctx, MM_READY);
+ rc = 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;
+ 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, 0xffffffff,
+ mmctx->gb.tlli_new);
+ }
+ mmctx->gmm_state = GMM_REGISTERED_NORMAL;
+ mmctx_set_pmm_state(mmctx, PMM_CONNECTED);
+ mmctx_set_mm_state(mmctx, MM_READY);
+ 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 REALLLICATION 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, 0xffffffff, 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:
+ LOGP(DMM, 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;
+
+ 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;
+ }
+ osmo_timer_schedule(&mm->timer, sgsn->cfg.timers.T3350, 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;
+
+ gsm48_tx_gmm_auth_ciph_req(mm, &at->vec, at->key_seq, false);
+ osmo_timer_schedule(&mm->timer, sgsn->cfg.timers.T3360, 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);
+ osmo_timer_schedule(&mm->timer, sgsn->cfg.timers.T3370, 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_start(struct sgsn_pdp_ctx *pdp, unsigned int T,
+ unsigned int seconds)
+{
+ 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;
+
+ /* FIXME: we should do this only once ? */
+ osmo_timer_setup(&pdp->timer, pdpctx_timer_cb, pdp);
+ osmo_timer_schedule(&pdp->timer, seconds, 0);
+}
+
+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);
+}
+
+#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
+
+/* Section 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);
+}
+
+/* Section 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(cause=%u)\n", 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);
+}
+
+/* Section 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)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 PDP DET REQ");
+ struct gsm48_hdr *gh;
+ uint8_t transaction_id = tid ^ 0x8; /* flip */
+
+ 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);
+
+ return gsm48_gmm_sendmsg(msg, 0, mm, true);
+}
+int gsm48_tx_gsm_deact_pdp_req(struct sgsn_pdp_ctx *pdp, uint8_t sm_cause)
+{
+ pdpctx_timer_start(pdp, 3395, sgsn->cfg.timers.T3395);
+
+ return _gsm48_tx_gsm_deact_pdp_req(pdp->mm, pdp->ti, sm_cause);
+}
+
+/* Section 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;
+
+ /* 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_ntoa(*addr));
+
+ /* 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;
+
+ 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_ntoa(ia));
+ }
+ 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_bsc_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);
+}
+
+/* Section 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 = gprs_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;
+}
+
+/* Section 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);
+}
+
+/* Section 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);
+ return sgsn_delete_pdp_ctx(pdp);
+}
+
+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 >= 4) {
+ LOGPDPCTXP(LOGL_NOTICE, pdp, "T3395 expired >= 5 times\n");
+ pdp->state = PDP_STATE_INACTIVE;
+ sgsn_delete_pdp_ctx(pdp);
+ break;
+ }
+ gsm48_tx_gsm_deact_pdp_req(pdp, GSM_CAUSE_NET_FAIL);
+ break;
+ default:
+ LOGPDPCTXP(LOGL_ERROR, pdp, "timer expired in unknown mode %u\n",
+ pdp->T);
+ }
+}
+
+
+/* GPRS Session Management */
+static 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) {
+ LOGP(DMM, 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;
+}
+
+/* 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->dst);
+ 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);
+#warning "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;
+}
+
+/* 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;
+ }
+
+ /* 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;
+}
+
+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;
+}
+
+#ifdef BUILD_IU
+int iu_rab_act_ps(uint8_t rab_id, struct sgsn_pdp_ctx *pdp, bool use_x213_nsap)
+{
+ struct msgb *msg;
+ struct sgsn_mm_ctx *mm = pdp->mm;
+ struct ue_conn_ctx *uectx;
+ uint32_t ggsn_ip;
+
+ uectx = mm->iu.ue_ctx;
+
+ /* 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 iu_rab_act(uectx, msg);
+}
+#endif
diff --git a/src/gprs/gprs_llc.c b/src/gprs/gprs_llc.c
new file mode 100644
index 000000000..2be663f98
--- /dev/null
+++ b/src/gprs/gprs_llc.c
@@ -0,0 +1,1132 @@
+/* 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 <openssl/rand.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 <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_gmm.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/crc24.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gprs_llc_xid.h>
+#include <openbsc/gprs_sndcp_comp.h>
+#include <openbsc/gprs_sndcp.h>
+
+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_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_llme *llme)
+{
+ /* 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(llme->xid);
+ llme->xid = gprs_llc_copy_xid(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_llme *llme)
+{
+ /* 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(llme->xid);
+ llme->xid = gprs_llc_copy_xid(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->llme->xid) {
+ llist_for_each_entry(xid_field_request, lle->llme->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->llme->xid);
+ lle->llme->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->llme);
+
+ /* 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 */
+ OSMO_ASSERT(msgb_tlli(msg) == mmctx->gb.llme->tlli
+ || msgb_tlli(msg) == mmctx->gb.llme->old_tlli);
+ }
+ 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 = 0xffffffff;
+ 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);
+ talloc_free(llme->xid);
+ 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);
+}
+
+/* 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) {
+ 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;
+ 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;
+ 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;
+
+ 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) {
+ msgb_gmmh(msg) = llhp.data;
+ 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 == 0xffffffff && new_tlli != 0xffffffff) {
+ /* TLLI Assignment 8.3.1 */
+ /* New TLLI shall be assigned and used when (re)transmitting LLC frames */
+ /* If old TLLI != 0xffffffff was assigned to LLME, then TLLI
+ * old is unassigned. Only TLLI new shall be accepted when
+ * received from peer. */
+ if (llme->old_tlli != 0xffffffff) {
+ llme->old_tlli = 0xffffffff;
+ llme->tlli = new_tlli;
+ } else {
+ /* If TLLI old == 0xffffffff was assigned to LLME, then this is
+ * TLLI assignmemt according to 8.3.1 */
+ llme->old_tlli = 0xffffffff;
+ 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 != 0xffffffff && new_tlli != 0xffffffff) {
+ /* 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 != 0xffffffff && new_tlli == 0xffffffff) {
+ /* 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, 0xffffffff);
+}
+
+/* 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;
+ uint8_t *xid;
+
+ LOGP(DLLC, LOGL_NOTICE, "LLGM Reset\n");
+ if (RAND_bytes((uint8_t *) &llme->iov_ui, 4) != 1) {
+ LOGP(DLLC, LOGL_NOTICE, "RAND_bytes failed for LLC XID reset, "
+ "falling back to rand()\n");
+ llme->iov_ui = rand();
+ }
+
+ /* Generate XID message */
+ xid_bytes_len = gprs_llc_generate_xid_for_gmm_reset(xid_bytes,
+ sizeof(xid_bytes),llme->iov_ui,llme);
+ 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");
+ uint8_t xid_bytes[1024];
+ int xid_bytes_len;
+ uint8_t *xid;
+
+ LOGP(DLLC, LOGL_NOTICE, "LLGM Reset\n");
+ if (RAND_bytes((uint8_t *) &llme->iov_ui, 4) != 1) {
+ LOGP(DLLC, LOGL_NOTICE, "RAND_bytes failed for LLC XID reset, "
+ "falling back to rand()\n");
+ llme->iov_ui = rand();
+ }
+
+ /* Generate XID message */
+ xid_bytes_len = gprs_llc_generate_xid_for_gmm_reset(xid_bytes,
+ sizeof(xid_bytes),llme->iov_ui,llme);
+ if (xid_bytes_len < 0)
+ return -EINVAL;
+ xid = msgb_put(msg, xid_bytes_len);
+ memcpy(xid, xid_bytes, xid_bytes_len);
+
+ /* FIXME: Start T200, wait for XID response */
+
+ msgb_tlli(msg) = msgb_tlli(oldmsg);
+ msgb_bvci(msg) = msgb_bvci(oldmsg);
+ msgb_nsei(msg) = msgb_nsei(oldmsg);
+
+ return gprs_llc_tx_u(msg, sapi, 1, GPRS_LLC_U_XID, 1);
+}
+
+int gprs_llc_init(const char *cipher_plugin_path)
+{
+ return gprs_cipher_load(cipher_plugin_path);
+}
diff --git a/src/gprs/gprs_llc_parse.c b/src/gprs/gprs_llc_parse.c
new file mode 100644
index 000000000..a5a7a7122
--- /dev/null
+++ b/src/gprs/gprs_llc_parse.c
@@ -0,0 +1,251 @@
+/* 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 <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 <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_gmm.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/crc24.h>
+
+static const struct value_string llc_cmd_strs[] = {
+ { GPRS_LLC_NULL, "NULL" },
+ { GPRS_LLC_RR, "RR" },
+ { GPRS_LLC_ACK, "ACK" },
+ { GPRS_LLC_RNR, "RNR" },
+ { GPRS_LLC_SACK, "SACK" },
+ { GPRS_LLC_DM, "DM" },
+ { GPRS_LLC_DISC, "DISC" },
+ { GPRS_LLC_UA, "UA" },
+ { GPRS_LLC_SABM, "SABM" },
+ { GPRS_LLC_FRMR, "FRMR" },
+ { GPRS_LLC_XID, "XID" },
+ { GPRS_LLC_UI, "UI" },
+ { 0, NULL }
+};
+
+#define LLC_ALLOC_SIZE 16384
+#define UI_HDR_LEN 3
+#define N202 4
+#define CRC24_LENGTH 3
+
+int gprs_llc_fcs(uint8_t *data, unsigned int len)
+{
+ uint32_t fcs_calc;
+
+ fcs_calc = crc24_calc(INIT_CRC24, data, len);
+ fcs_calc = ~fcs_calc;
+ fcs_calc &= 0xffffff;
+
+ return fcs_calc;
+}
+
+void gprs_llc_hdr_dump(struct gprs_llc_hdr_parsed *gph, struct gprs_llc_lle *lle)
+{
+ const char *gea;
+ uint32_t iov_ui = 0;
+ if (lle) {
+ gea = get_value_string(gprs_cipher_names, lle->llme->algo);
+ iov_ui = lle->llme->iov_ui;
+ } else
+ gea = "GEA?";
+ DEBUGP(DLLC, "LLC SAPI=%u %c %c %c %s IOV-UI=0x%06x FCS=0x%06x ",
+ gph->sapi, gph->is_cmd ? 'C' : 'R', gph->ack_req ? 'A' : ' ',
+ gph->is_encrypted ? 'E' : 'U',
+ gea, iov_ui, gph->fcs);
+
+ if (gph->cmd)
+ DEBUGPC(DLLC, "CMD=%s ", get_value_string(llc_cmd_strs, gph->cmd));
+
+ if (gph->data)
+ DEBUGPC(DLLC, "DATA ");
+
+ DEBUGPC(DLLC, "\n");
+}
+
+/* parse a GPRS LLC header, also check for invalid frames */
+int gprs_llc_hdr_parse(struct gprs_llc_hdr_parsed *ghp,
+ uint8_t *llc_hdr, int len)
+{
+ uint8_t *ctrl = llc_hdr+1;
+
+ if (len <= CRC24_LENGTH)
+ return -EIO;
+
+ ghp->crc_length = len - CRC24_LENGTH;
+
+ ghp->ack_req = 0;
+
+ /* Section 5.5: FCS */
+ ghp->fcs = *(llc_hdr + len - 3);
+ ghp->fcs |= *(llc_hdr + len - 2) << 8;
+ ghp->fcs |= *(llc_hdr + len - 1) << 16;
+
+ /* Section 6.2.1: invalid PD field */
+ if (llc_hdr[0] & 0x80)
+ return -EIO;
+
+ /* This only works for the MS->SGSN direction */
+ if (llc_hdr[0] & 0x40)
+ ghp->is_cmd = 0;
+ else
+ ghp->is_cmd = 1;
+
+ ghp->sapi = llc_hdr[0] & 0xf;
+
+ /* Section 6.2.3: check for reserved SAPI */
+ switch (ghp->sapi) {
+ case 0:
+ case 4:
+ case 6:
+ case 0xa:
+ case 0xc:
+ case 0xd:
+ case 0xf:
+ return -EINVAL;
+ }
+
+ if ((ctrl[0] & 0x80) == 0) {
+ /* I (Information transfer + Supervisory) format */
+ uint8_t k;
+
+ ghp->data = ctrl + 3;
+
+ if (ctrl[0] & 0x40)
+ ghp->ack_req = 1;
+
+ ghp->seq_tx = (ctrl[0] & 0x1f) << 4;
+ ghp->seq_tx |= (ctrl[1] >> 4);
+
+ ghp->seq_rx = (ctrl[1] & 0x7) << 6;
+ ghp->seq_rx |= (ctrl[2] >> 2);
+
+ switch (ctrl[2] & 0x03) {
+ case 0:
+ ghp->cmd = GPRS_LLC_RR;
+ break;
+ case 1:
+ ghp->cmd = GPRS_LLC_ACK;
+ break;
+ case 2:
+ ghp->cmd = GPRS_LLC_RNR;
+ break;
+ case 3:
+ ghp->cmd = GPRS_LLC_SACK;
+ k = ctrl[3] & 0x1f;
+ ghp->data += 1 + k;
+ break;
+ }
+ ghp->data_len = (llc_hdr + len - 3) - ghp->data;
+ } else if ((ctrl[0] & 0xc0) == 0x80) {
+ /* S (Supervisory) format */
+ ghp->data = NULL;
+ ghp->data_len = 0;
+
+ if (ctrl[0] & 0x20)
+ ghp->ack_req = 1;
+ ghp->seq_rx = (ctrl[0] & 0x7) << 6;
+ ghp->seq_rx |= (ctrl[1] >> 2);
+
+ switch (ctrl[1] & 0x03) {
+ case 0:
+ ghp->cmd = GPRS_LLC_RR;
+ break;
+ case 1:
+ ghp->cmd = GPRS_LLC_ACK;
+ break;
+ case 2:
+ ghp->cmd = GPRS_LLC_RNR;
+ break;
+ case 3:
+ ghp->cmd = GPRS_LLC_SACK;
+ break;
+ }
+ } else if ((ctrl[0] & 0xe0) == 0xc0) {
+ /* UI (Unconfirmed Inforamtion) format */
+ ghp->cmd = GPRS_LLC_UI;
+ ghp->data = ctrl + 2;
+ ghp->data_len = (llc_hdr + len - 3) - ghp->data;
+
+ ghp->seq_tx = (ctrl[0] & 0x7) << 6;
+ ghp->seq_tx |= (ctrl[1] >> 2);
+ if (ctrl[1] & 0x02) {
+ ghp->is_encrypted = 1;
+ /* FIXME: encryption */
+ }
+ if (ctrl[1] & 0x01) {
+ /* FCS over hdr + all inf fields */
+ } else {
+ /* FCS over hdr + N202 octets (4) */
+ if (ghp->crc_length > UI_HDR_LEN + N202)
+ ghp->crc_length = UI_HDR_LEN + N202;
+ }
+ } else {
+ /* U (Unnumbered) format: 1 1 1 P/F M4 M3 M2 M1 */
+ ghp->data = NULL;
+ ghp->data_len = 0;
+
+ switch (ctrl[0] & 0xf) {
+ case GPRS_LLC_U_NULL_CMD:
+ ghp->cmd = GPRS_LLC_NULL;
+ break;
+ case GPRS_LLC_U_DM_RESP:
+ ghp->cmd = GPRS_LLC_DM;
+ break;
+ case GPRS_LLC_U_DISC_CMD:
+ ghp->cmd = GPRS_LLC_DISC;
+ break;
+ case GPRS_LLC_U_UA_RESP:
+ ghp->cmd = GPRS_LLC_UA;
+ break;
+ case GPRS_LLC_U_SABM_CMD:
+ ghp->cmd = GPRS_LLC_SABM;
+ break;
+ case GPRS_LLC_U_FRMR_RESP:
+ ghp->cmd = GPRS_LLC_FRMR;
+ break;
+ case GPRS_LLC_U_XID:
+ ghp->cmd = GPRS_LLC_XID;
+ ghp->data = ctrl + 1;
+ ghp->data_len = (llc_hdr + len - 3) - ghp->data;
+ break;
+ default:
+ return -EIO;
+ }
+ }
+
+ /* FIXME: parse sack frame */
+ if (ghp->cmd == GPRS_LLC_SACK) {
+ LOGP(DLLC, LOGL_NOTICE, "Unsupported SACK frame\n");
+ return -EIO;
+ }
+
+ return 0;
+}
diff --git a/src/gprs/gprs_llc_vty.c b/src/gprs/gprs_llc_vty.c
new file mode 100644
index 000000000..bf34e9782
--- /dev/null
+++ b/src/gprs/gprs_llc_vty.c
@@ -0,0 +1,116 @@
+/* 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 <openbsc/gsm_data.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 <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/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};
+ clock_gettime(CLOCK_MONOTONIC, &now_tp);
+
+ vty_out(vty, "TLLI %08x (Old TLLI %08x) BVCI=%u NSEI=%u %s: "
+ "IOV-UI=0x%06x CKSN=%d Age=%d: State %s%s", llme->tlli,
+ llme->old_tlli, llme->bvci, llme->nsei,
+ get_value_string(gprs_cipher_names, llme->algo), llme->iov_ui,
+ llme->cksn, llme->age_timestamp == GPRS_LLME_RESET_AGE ? 0 :
+ (int)(now_tp.tv_sec - (time_t)llme->age_timestamp),
+ get_value_string(gprs_llc_state_strs, llme->state), VTY_NEWLINE);
+
+ for (i = 0; i < ARRAY_SIZE(valid_sapis); i++) {
+ struct gprs_llc_lle *lle;
+ uint8_t sapi = valid_sapis[i];
+
+ if (sapi >= ARRAY_SIZE(llme->lle))
+ continue;
+
+ lle = &llme->lle[sapi];
+ vty_dump_lle(vty, lle);
+ }
+}
+
+
+DEFUN(show_llc, show_llc_cmd,
+ "show llc",
+ SHOW_STR "Display information about the LLC protocol")
+{
+ struct gprs_llc_llme *llme;
+
+ vty_out(vty, "State of LLC Entities%s", VTY_NEWLINE);
+ llist_for_each_entry(llme, &gprs_llc_llmes, list) {
+ vty_dump_llme(vty, llme);
+ }
+ return CMD_SUCCESS;
+}
+
+int gprs_llc_vty_init(void)
+{
+ install_element_ve(&show_llc_cmd);
+
+ return 0;
+}
diff --git a/src/gprs/gprs_llc_xid.c b/src/gprs/gprs_llc_xid.c
new file mode 100644
index 000000000..fe631715a
--- /dev/null
+++ b/src/gprs/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 <openbsc/debug.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/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_"},
+ { GPRS_LLC_XID_T_N201_I, "N201_I"},
+ { GPRS_LLC_XID_T_mD, "mD"},
+ { GPRS_LLC_XID_T_mU, "mU"},
+ { GPRS_LLC_XID_T_kD, "kD"},
+ { GPRS_LLC_XID_T_kU, "kU"},
+ { GPRS_LLC_XID_T_L3_PAR, "L3_PAR"},
+ { GPRS_LLC_XID_T_RESET, "RESET"},
+ { 0, NULL },
+};
+
+/* Parse XID parameter field */
+static int decode_xid_field(struct gprs_llc_xid_field *xid_field,
+ const uint8_t *src, uint8_t src_len)
+{
+ uint8_t xl;
+ uint8_t type;
+ uint8_t len;
+ int src_counter = 0;
+
+ /* Exit immediately if it is clear that no
+ * parseable data is present */
+ if (src_len < 1 || !src)
+ return -EINVAL;
+
+ /* Extract header info */
+ xl = (*src >> 7) & 1;
+ type = (*src >> 2) & 0x1F;
+
+ /* Extract length field */
+ len = (*src) & 0x3;
+ src++;
+ src_counter++;
+ if (xl) {
+ if (src_len < 2)
+ return -EINVAL;
+ len = (len << 6) & 0xC0;
+ len |= ((*src) >> 2) & 0x3F;
+ src++;
+ src_counter++;
+ }
+
+ /* Fill out struct */
+ xid_field->type = type;
+ xid_field->data_len = len;
+ if (len > 0) {
+ if (src_len < src_counter + len)
+ return -EINVAL;
+ xid_field->data =
+ talloc_memdup(xid_field,src,xid_field->data_len);
+ } else
+ xid_field->data = NULL;
+
+ /* Return consumed length */
+ return src_counter + len;
+}
+
+/* Encode XID parameter field */
+static int encode_xid_field(uint8_t *dst, int dst_maxlen,
+ const struct gprs_llc_xid_field *xid_field)
+{
+ int xl = 0;
+
+ /* When the length does not fit into 2 bits,
+ * we need extended length fields */
+ if (xid_field->data_len > 3)
+ xl = 1;
+
+ /* Exit immediately if it is clear that no
+ * encoding result can be stored */
+ if (dst_maxlen < xid_field->data_len + 1 + xl)
+ return -EINVAL;
+
+ /* There are only 5 bits reserved for the type, exit on exceed */
+ if (xid_field->type > 31)
+ return -EINVAL;
+
+ /* Encode header */
+ memset(dst, 0, dst_maxlen);
+ if (xl)
+ dst[0] |= 0x80;
+ dst[0] |= (((xid_field->type) & 0x1F) << 2);
+
+ if (xl) {
+ dst[0] |= (((xid_field->data_len) >> 6) & 0x03);
+ dst[1] = ((xid_field->data_len) << 2) & 0xFC;
+ } else
+ dst[0] |= ((xid_field->data_len) & 0x03);
+
+ /* Append payload data */
+ if (xid_field->data && xid_field->data_len)
+ memcpy(dst + 1 + xl, xid_field->data, xid_field->data_len);
+
+ /* Return generated length */
+ return xid_field->data_len + 1 + xl;
+}
+
+/* Transform a list with XID fields into a XID message (dst) */
+int gprs_llc_compile_xid(uint8_t *dst, int dst_maxlen,
+ const struct llist_head *xid_fields)
+{
+ struct gprs_llc_xid_field *xid_field;
+ int rc;
+ int byte_counter = 0;
+
+ OSMO_ASSERT(xid_fields);
+ OSMO_ASSERT(dst);
+
+ llist_for_each_entry_reverse(xid_field, xid_fields, list) {
+ /* Encode XID-Field */
+ rc = encode_xid_field(dst, dst_maxlen, xid_field);
+ if (rc < 0)
+ return -EINVAL;
+
+ /* Advance pointer and lower maxlen for the
+ * next encoding round */
+ dst += rc;
+ byte_counter += rc;
+ dst_maxlen -= rc;
+ }
+
+ /* Return generated length */
+ return byte_counter;
+}
+
+/* Transform a XID message (dst) into a list of XID fields */
+struct llist_head *gprs_llc_parse_xid(const void *ctx, const uint8_t *src,
+ int src_len)
+{
+ struct gprs_llc_xid_field *xid_field;
+ struct llist_head *xid_fields;
+
+ int rc;
+ int max_loops = src_len;
+
+ OSMO_ASSERT(src);
+
+ xid_fields = talloc_zero(ctx, struct llist_head);
+ INIT_LLIST_HEAD(xid_fields);
+
+ while (1) {
+ /* Bail in case decode_xid_field() constantly returns zero */
+ if (max_loops <= 0) {
+ talloc_free(xid_fields);
+ return NULL;
+ }
+
+ /* Decode XID field */
+ xid_field = talloc_zero(xid_fields, struct gprs_llc_xid_field);
+ rc = decode_xid_field(xid_field, src, src_len);
+
+ /* Immediately stop on error */
+ if (rc < 0) {
+ talloc_free(xid_fields);
+ return NULL;
+ }
+
+ /* Add parsed XID field to list */
+ llist_add(&xid_field->list, xid_fields);
+
+ /* Advance pointer and lower dst_len for the next
+ * decoding round */
+ src += rc;
+ src_len -= rc;
+
+ /* We are (scuccessfully) done when no further byes are left */
+ if (src_len == 0)
+ return xid_fields;
+
+ max_loops--;
+ }
+}
+
+/* Create a duplicate of an XID-Field */
+struct gprs_llc_xid_field *gprs_llc_dup_xid_field(const void *ctx, const struct
+ gprs_llc_xid_field
+ *xid_field)
+{
+ struct gprs_llc_xid_field *dup;
+
+ OSMO_ASSERT(xid_field);
+
+ /* Create a copy of the XID field in memory */
+ dup = talloc_memdup(ctx, xid_field, sizeof(*xid_field));
+ dup->data = talloc_memdup(ctx, xid_field->data, xid_field->data_len);
+
+ /* Unlink duplicate from source list */
+ INIT_LLIST_HEAD(&dup->list);
+
+ return dup;
+}
+
+/* Copy an llist with xid fields */
+struct llist_head *gprs_llc_copy_xid(const void *ctx,
+ const struct llist_head *xid_fields)
+{
+ struct gprs_llc_xid_field *xid_field;
+ struct llist_head *xid_fields_copy;
+
+ OSMO_ASSERT(xid_fields);
+
+ xid_fields_copy = talloc_zero(ctx, struct llist_head);
+ INIT_LLIST_HEAD(xid_fields_copy);
+
+ /* Create duplicates and add them to the target list */
+ llist_for_each_entry(xid_field, xid_fields, list) {
+ llist_add(&gprs_llc_dup_xid_field(ctx, xid_field)->list,
+ xid_fields_copy);
+ }
+
+ return xid_fields_copy;
+}
+
+/* Dump a list with XID fields (Debug) */
+void gprs_llc_dump_xid_fields(const struct llist_head *xid_fields,
+ unsigned int logl)
+{
+ struct gprs_llc_xid_field *xid_field;
+
+ OSMO_ASSERT(xid_fields);
+
+ llist_for_each_entry(xid_field, xid_fields, list) {
+ if (xid_field->data_len) {
+ OSMO_ASSERT(xid_field->data);
+ LOGP(DLLC, logl,
+ "XID: type %s, data_len=%d, data=%s\n",
+ get_value_string(gprs_llc_xid_type_names,
+ xid_field->type),
+ xid_field->data_len,
+ osmo_hexdump_nospc(xid_field->data,
+ xid_field->data_len));
+ } else {
+ LOGP(DLLC, logl,
+ "XID: type=%d, data_len=%d, data=NULL\n",
+ xid_field->type, xid_field->data_len);
+ }
+ }
+}
diff --git a/src/gprs/gprs_sgsn.c b/src/gprs/gprs_sgsn.c
new file mode 100644
index 000000000..071dd97c8
--- /dev/null
+++ b/src/gprs/gprs_sgsn.c
@@ -0,0 +1,895 @@
+/* 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 <openbsc/gprs_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_gmm.h>
+#include <openbsc/gprs_utils.h>
+#include <openbsc/signal.h>
+#include "openbsc/gprs_llc.h"
+#include <openbsc/iu.h>
+
+#include <pdp.h>
+
+#include <time.h>
+
+#include <openssl/rand.h>
+
+#define GPRS_LLME_CHECK_TICK 30
+
+extern struct sgsn_instance *sgsn;
+
+LLIST_HEAD(sgsn_mm_ctxts);
+LLIST_HEAD(sgsn_ggsn_ctxts);
+LLIST_HEAD(sgsn_apn_ctxts);
+LLIST_HEAD(sgsn_pdp_ctxts);
+
+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 sucessful received LLC bytes (encrypt & fcs correct)" },
+ { "llc.dl_packets", "Count sucessful sent LLC packets before giving it to the bssgp layer" },
+ { "llc.ul_packets", "Count sucessful 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_bsc_ctx, &sgsn_ctrg_desc, 0);
+}
+
+/* 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 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;
+
+ ctx = talloc_zero(tall_bsc_ctx, struct sgsn_mm_ctx);
+ if (!ctx)
+ return NULL;
+
+ memcpy(&ctx->ra, raid, sizeof(ctx->ra));
+ ctx->ran_type = MM_CTX_T_GERAN_Gb;
+ ctx->gb.tlli = tlli;
+ ctx->gmm_state = GMM_DEREGISTERED;
+ ctx->pmm_state = MM_IDLE;
+ ctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL;
+ ctx->ciph_algo = sgsn->cfg.cipher;
+ LOGMMCTXP(LOGL_DEBUG, ctx, "Allocated with %s cipher.\n",
+ get_value_string(gprs_cipher_names, ctx->ciph_algo));
+ ctx->ctrg = rate_ctr_group_alloc(ctx, &mmctx_ctrg_desc, tlli);
+ INIT_LLIST_HEAD(&ctx->pdp_list);
+
+ llist_add(&ctx->list, &sgsn_mm_ctxts);
+
+ return ctx;
+}
+
+/* Allocate a new SGSN MM context */
+struct sgsn_mm_ctx *sgsn_mm_ctx_alloc_iu(void *uectx)
+{
+ struct sgsn_mm_ctx *ctx;
+
+ ctx = talloc_zero(tall_bsc_ctx, struct sgsn_mm_ctx);
+ if (!ctx)
+ return NULL;
+
+ ctx->ran_type = MM_CTX_T_UTRAN_Iu;
+ ctx->iu.ue_ctx = uectx;
+ ctx->iu.new_key = 1;
+ ctx->gmm_state = GMM_DEREGISTERED;
+ ctx->pmm_state = PMM_DETACHED;
+ ctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL;
+ ctx->ctrg = rate_ctr_group_alloc(ctx, &mmctx_ctrg_desc, 0);
+
+ /* Need to get RAID from IU conn */
+ ctx->ra = ctx->iu.ue_ctx->ra_id;
+
+ INIT_LLIST_HEAD(&ctx->pdp_list);
+
+ llist_add(&ctx->list, &sgsn_mm_ctxts);
+
+ return ctx;
+}
+
+
+/* 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;
+ uint32_t tlli = mm->gb.tlli;
+ 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);
+ }
+
+ sgsn_mm_ctx_free(mm);
+ mm = NULL;
+
+ if (llme) {
+ /* TLLI unassignment, must be called after sgsn_mm_ctx_free */
+ gprs_llgmm_assign(llme, tlli, 0xffffffff);
+ }
+}
+
+
+/* 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,
+ uint8_t nsapi)
+{
+ struct sgsn_pdp_ctx *pdp;
+
+ pdp = sgsn_pdp_ctx_by_nsapi(mm, nsapi);
+ if (pdp)
+ return NULL;
+
+ pdp = talloc_zero(tall_bsc_ctx, struct sgsn_pdp_ctx);
+ if (!pdp)
+ return NULL;
+
+ pdp->mm = mm;
+ pdp->nsapi = nsapi;
+ pdp->ctrg = rate_ctr_group_alloc(pdp, &pdpctx_ctrg_desc, nsapi);
+ llist_add(&pdp->list, &mm->pdp_list);
+ 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 */
+ llist_del(&pdp->list);
+ pdp->mm = NULL;
+
+ 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);
+ 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;
+ }
+
+ if (pdp->destroy_ggsn)
+ sgsn_ggsn_ctx_free(pdp->ggsn);
+ talloc_free(pdp);
+}
+
+/* GGSN contexts */
+
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_alloc(uint32_t id)
+{
+ struct sgsn_ggsn_ctx *ggc;
+
+ ggc = talloc_zero(tall_bsc_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;
+ llist_add(&ggc->list, &sgsn_ggsn_ctxts);
+
+ return ggc;
+}
+
+void sgsn_ggsn_ctx_free(struct sgsn_ggsn_ctx *ggc)
+{
+ 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_bsc_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;
+
+restart:
+ if (RAND_bytes((uint8_t *) &ptmsi, sizeof(ptmsi)) != 1)
+ 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 >= 0xC0000000) {
+ if (!max_retries--)
+ goto failed;
+ goto restart;
+ }
+ ptmsi |= 0xC0000000;
+
+ 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\n");
+ return GSM_RESERVED_TMSI;
+}
+
+static void drop_one_pdp(struct sgsn_pdp_ctx *pdp)
+{
+ if (pdp->mm->gmm_state == GMM_REGISTERED_NORMAL)
+ gsm48_tx_gsm_deact_pdp_req(pdp, GSM_CAUSE_NET_FAIL);
+ else {
+ /* FIXME: GPRS paging in case MS is SUSPENDED */
+ LOGPDPCTXP(LOGL_NOTICE, pdp, "Hard-dropping PDP ctx due to GGSN "
+ "recovery\n");
+ /* FIXME: how to tell this to libgtp? */
+ sgsn_pdp_ctx_free(pdp);
+ }
+}
+
+/* High-level function to be called in case a GGSN has disappeared or
+ * otherwise lost state (recovery procedure) */
+int drop_all_pdp_for_ggsn(struct sgsn_ggsn_ctx *ggsn)
+{
+ struct sgsn_mm_ctx *mm;
+ int num = 0;
+
+ llist_for_each_entry(mm, &sgsn_mm_ctxts, list) {
+ struct sgsn_pdp_ctx *pdp;
+ llist_for_each_entry(pdp, &mm->pdp_list, list) {
+ if (pdp->ggsn == ggsn) {
+ drop_one_pdp(pdp);
+ num++;
+ }
+ }
+ }
+
+ return num;
+}
+
+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_qos(struct tlv_parsed *tp, 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;
+}
+
+/**
+ * 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;
+ }
+
+ gprs_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_qos(tp, 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_qos(tp, 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_qos(tp, pdp);
+ selected_apn_str = req_apn_str;
+ allow_any_apn = 1;
+ continue;
+ }
+ if (strcasecmp(pdp->apn_str, req_apn_str) == 0) {
+ insert_qos(tp, 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);
+ 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 = 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);
+}
+
+void sgsn_inst_init()
+{
+ osmo_timer_setup(&sgsn->llme_timer, sgsn_llme_check_cb, NULL);
+ osmo_timer_schedule(&sgsn->llme_timer, GPRS_LLME_CHECK_TICK, 0);
+}
+
diff --git a/src/gprs/gprs_sndcp.c b/src/gprs/gprs_sndcp.c
new file mode 100644
index 000000000..a18998f9e
--- /dev/null
+++ b/src/gprs/gprs_sndcp.c
@@ -0,0 +1,1258 @@
+/* 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 <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_sndcp.h>
+#include <openbsc/gprs_llc_xid.h>
+#include <openbsc/gprs_sndcp_xid.h>
+#include <openbsc/gprs_sndcp_pcomp.h>
+#include <openbsc/gprs_sndcp_dcomp.h>
+#include <openbsc/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 - 3; /* -3 'removes' the FCS */
+
+ 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 = 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 = 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->llme->xid);
+ lle->llme->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) {
+ 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) {
+ 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);
+
+ /* Parse SNDCP-CID XID-Field */
+ comp_fields = gprs_sndcp_parse_xid(&version, lle->llme,
+ xid_field_indication->data,
+ xid_field_indication->data_len,
+ NULL);
+ if (!comp_fields)
+ return -EINVAL;
+
+ /* Handle compression entites */
+ DEBUGP(DSNDCP, "SNDCP-XID-IND (ms):\n");
+ gprs_sndcp_dump_comp_fields(comp_fields, LOGL_DEBUG);
+
+ llist_for_each_entry(comp_field, comp_fields, list) {
+ compclass = gprs_sndcp_get_compression_class(comp_field);
+ if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION)
+ rc = handle_pcomp_entities(comp_field, lle);
+ else if (compclass == SNDCP_XID_DATA_COMPRESSION)
+ rc = handle_dcomp_entities(comp_field, lle);
+ else {
+ gprs_sndcp_comp_delete(lle->llme->comp.proto,
+ comp_field->entity);
+ gprs_sndcp_comp_delete(lle->llme->comp.data,
+ comp_field->entity);
+ rc = 0;
+ }
+
+ if (rc < 0) {
+ talloc_free(comp_fields);
+ return -EINVAL;
+ }
+ }
+
+ DEBUGP(DSNDCP, "SNDCP-XID-RES (sgsn):\n");
+ gprs_sndcp_dump_comp_fields(comp_fields, LOGL_DEBUG);
+
+ /* Reserve some memory to store the modified SNDCP-XID bytes */
+ xid_field_response->data =
+ talloc_zero_size(lle->llme, xid_field_indication->data_len);
+
+ /* Set Type flag for response */
+ xid_field_response->type = GPRS_LLC_XID_T_L3_PAR;
+
+ /* Compile modified SNDCP-XID bytes */
+ rc = gprs_sndcp_compile_xid(xid_field_response->data,
+ xid_field_indication->data_len,
+ comp_fields, 0);
+
+ if (rc > 0)
+ xid_field_response->data_len = rc;
+ else {
+ talloc_free(xid_field_response->data);
+ xid_field_response->data = NULL;
+ xid_field_response->data_len = 0;
+ return -EINVAL;
+ }
+
+ talloc_free(comp_fields);
+
+ return 0;
+}
+
+/* Process SNDCP-XID indication
+ * (See also: TS 144 065, Section 6.8 XID parameter negotiation) */
+int sndcp_sn_xid_conf(struct gprs_llc_xid_field *xid_field_conf,
+ struct gprs_llc_xid_field *xid_field_request,
+ struct gprs_llc_lle *lle)
+{
+ /* Note: This function handles an incomming SNDCP-XID confirmiation.
+ * Since the confirmation fields may lack important parameters we
+ * will reconstruct these missing fields using the original request
+ * we have sent. After that we will create (or delete) the
+ * compression entites */
+
+ struct llist_head *comp_fields_req;
+ struct llist_head *comp_fields_conf;
+ struct gprs_sndcp_comp_field *comp_field;
+ int rc;
+ int compclass;
+
+ /* We need both, the confirmation that is sent back by the ms,
+ * and the original request we have sent. If one of this is missing
+ * we can not process the confirmation, the caller must check if
+ * request and confirmation fields are available. */
+ OSMO_ASSERT(xid_field_conf);
+ OSMO_ASSERT(xid_field_request);
+
+ /* Parse SNDCP-CID XID-Field */
+ comp_fields_req = gprs_sndcp_parse_xid(NULL, lle->llme,
+ xid_field_request->data,
+ xid_field_request->data_len,
+ NULL);
+ if (!comp_fields_req)
+ return -EINVAL;
+
+ DEBUGP(DSNDCP, "SNDCP-XID-REQ (sgsn):\n");
+ gprs_sndcp_dump_comp_fields(comp_fields_req, LOGL_DEBUG);
+
+ /* Parse SNDCP-CID XID-Field */
+ comp_fields_conf = gprs_sndcp_parse_xid(NULL, lle->llme,
+ xid_field_conf->data,
+ xid_field_conf->data_len,
+ comp_fields_req);
+ if (!comp_fields_conf)
+ return -EINVAL;
+
+ DEBUGP(DSNDCP, "SNDCP-XID-CONF (ms):\n");
+ gprs_sndcp_dump_comp_fields(comp_fields_conf, LOGL_DEBUG);
+
+ /* Handle compression entites */
+ llist_for_each_entry(comp_field, comp_fields_conf, list) {
+ compclass = gprs_sndcp_get_compression_class(comp_field);
+ if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION)
+ rc = handle_pcomp_entities(comp_field, lle);
+ else if (compclass == SNDCP_XID_DATA_COMPRESSION)
+ rc = handle_dcomp_entities(comp_field, lle);
+ else {
+ gprs_sndcp_comp_delete(lle->llme->comp.proto,
+ comp_field->entity);
+ gprs_sndcp_comp_delete(lle->llme->comp.data,
+ comp_field->entity);
+ rc = 0;
+ }
+
+ if (rc < 0) {
+ talloc_free(comp_fields_req);
+ talloc_free(comp_fields_conf);
+ return -EINVAL;
+ }
+ }
+
+ talloc_free(comp_fields_req);
+ talloc_free(comp_fields_conf);
+
+ return 0;
+}
diff --git a/src/gprs/gprs_sndcp_comp.c b/src/gprs/gprs_sndcp_comp.c
new file mode 100644
index 000000000..a12c39aa6
--- /dev/null
+++ b/src/gprs/gprs_sndcp_comp.c
@@ -0,0 +1,323 @@
+/* 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 <openbsc/debug.h>
+#include <openbsc/gprs_sndcp_xid.h>
+#include <openbsc/gprs_sndcp_comp.h>
+#include <openbsc/gprs_sndcp_pcomp.h>
+#include <openbsc/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));
+ } 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));
+ } 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));
+ } 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));
+ } 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));
+ } 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);
+ }
+ comp_entity->algo = comp_field->algo;
+
+ /* 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);
+
+ OSMO_ASSERT(comp_entity->compclass != -1);
+
+ /* Create an algorithm specific compression context */
+ if (comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION) {
+ if (gprs_sndcp_pcomp_init(ctx, comp_entity, comp_field) != 0) {
+ talloc_free(comp_entity);
+ comp_entity = NULL;
+ }
+ } else {
+ if (gprs_sndcp_dcomp_init(ctx, comp_entity, comp_field) != 0) {
+ talloc_free(comp_entity);
+ comp_entity = NULL;
+ }
+ }
+
+ /* 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 */
+ if (comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION) {
+ LOGP(DSNDCP, LOGL_INFO,
+ "Deleting header compression entity %d ...\n",
+ comp_entity->entity);
+ gprs_sndcp_pcomp_term(comp_entity);
+ } else {
+ LOGP(DSNDCP, LOGL_INFO,
+ "Deleting data compression entity %d ...\n",
+ comp_entity->entity);
+ gprs_sndcp_dcomp_term(comp_entity);
+ }
+ }
+
+ talloc_free(comp_entities);
+}
+
+/* Delete a compression entity */
+void gprs_sndcp_comp_delete(struct llist_head *comp_entities,
+ unsigned int entity)
+{
+ struct gprs_sndcp_comp *comp_entity;
+ struct gprs_sndcp_comp *comp_entity_to_delete = NULL;
+
+ OSMO_ASSERT(comp_entities);
+
+ llist_for_each_entry(comp_entity, comp_entities, list) {
+ if (comp_entity->entity == entity) {
+ comp_entity_to_delete = comp_entity;
+ break;
+ }
+ }
+
+ if (!comp_entity_to_delete)
+ return;
+
+ if (comp_entity_to_delete->compclass == SNDCP_XID_PROTOCOL_COMPRESSION) {
+ LOGP(DSNDCP, LOGL_INFO,
+ "Deleting header compression entity %d ...\n",
+ comp_entity_to_delete->entity);
+ gprs_sndcp_pcomp_term(comp_entity_to_delete);
+ } else {
+ LOGP(DSNDCP, LOGL_INFO,
+ "Deleting data compression entity %d ...\n",
+ comp_entity_to_delete->entity);
+ }
+
+ /* Delete compression entity */
+ llist_del(&comp_entity_to_delete->list);
+ talloc_free(comp_entity_to_delete);
+}
+
+/* Create and Add a new compression entity
+ * (returns a pointer to the compression entity that has just been created) */
+struct gprs_sndcp_comp *gprs_sndcp_comp_add(const void *ctx,
+ struct llist_head *comp_entities,
+ const struct gprs_sndcp_comp_field
+ *comp_field)
+{
+ struct gprs_sndcp_comp *comp_entity;
+
+ OSMO_ASSERT(comp_entities);
+ OSMO_ASSERT(comp_field);
+
+ /* Just to be sure, if the entity is already in
+ * the list it will be deleted now */
+ gprs_sndcp_comp_delete(comp_entities, comp_field->entity);
+
+ /* Create and add a new entity to the list */
+ comp_entity = gprs_sndcp_comp_create(ctx, comp_field);
+
+ if (!comp_entity)
+ return NULL;
+
+ llist_add(&comp_entity->list, comp_entities);
+ return comp_entity;
+}
+
+/* Find which compression entity handles the specified pcomp/dcomp */
+struct gprs_sndcp_comp *gprs_sndcp_comp_by_comp(const struct llist_head
+ *comp_entities, uint8_t comp)
+{
+ struct gprs_sndcp_comp *comp_entity;
+ int i;
+
+ OSMO_ASSERT(comp_entities);
+
+ llist_for_each_entry(comp_entity, comp_entities, list) {
+ for (i = 0; i < comp_entity->comp_len; i++) {
+ if (comp_entity->comp[i] == comp)
+ return comp_entity;
+ }
+ }
+
+ LOGP(DSNDCP, LOGL_ERROR,
+ "Could not find a matching compression entity for given pcomp/dcomp value %d.\n",
+ comp);
+ return NULL;
+}
+
+/* Find which compression entity handles the specified nsapi */
+struct gprs_sndcp_comp *gprs_sndcp_comp_by_nsapi(const struct llist_head
+ *comp_entities, uint8_t nsapi)
+{
+ struct gprs_sndcp_comp *comp_entity;
+ int i;
+
+ OSMO_ASSERT(comp_entities);
+
+ llist_for_each_entry(comp_entity, comp_entities, list) {
+ for (i = 0; i < comp_entity->nsapi_len; i++) {
+ if (comp_entity->nsapi[i] == nsapi)
+ return comp_entity;
+ }
+ }
+
+ return NULL;
+}
+
+/* Find a comp_index for a given pcomp/dcomp value */
+uint8_t gprs_sndcp_comp_get_idx(const struct gprs_sndcp_comp *comp_entity,
+ uint8_t comp)
+{
+ /* Note: This function returns a normalized version of the comp value,
+ * which matches up with the position of the comp field. Since comp=0
+ * is reserved for "no compression", the index value starts counting
+ * at one. The return value is the PCOMPn/DCOMPn value one can find
+ * in the Specification (see e.g. 3GPP TS 44.065, 6.5.3.2, Table 7) */
+
+ int i;
+ OSMO_ASSERT(comp_entity);
+
+ /* A pcomp/dcomp value of zero is reserved for "no comproession",
+ * So we just bail and return zero in this case */
+ if (comp == 0)
+ return 0;
+
+ /* Look in the pcomp/dcomp list for the index */
+ for (i = 0; i < comp_entity->comp_len; i++) {
+ if (comp_entity->comp[i] == comp)
+ return i + 1;
+ }
+
+ LOGP(DSNDCP, LOGL_ERROR,
+ "Could not find a matching comp_index for given pcomp/dcomp value %d\n",
+ comp);
+ return 0;
+}
+
+/* Find a pcomp/dcomp value for a given comp_index */
+uint8_t gprs_sndcp_comp_get_comp(const struct gprs_sndcp_comp *comp_entity,
+ uint8_t comp_index)
+{
+ OSMO_ASSERT(comp_entity);
+
+ /* A comp_index of zero translates to zero right away. */
+ if (comp_index == 0)
+ return 0;
+
+ if (comp_index > comp_entity->comp_len) {
+ LOGP(DSNDCP, LOGL_ERROR,
+ "Could not find a matching pcomp/dcomp value for given comp_index value %d.\n",
+ comp_index);
+ return 0;
+ }
+
+ /* Look in the pcomp/dcomp list for the comp_index, see
+ * note in gprs_sndcp_comp_get_idx() */
+ return comp_entity->comp[comp_index - 1];
+}
diff --git a/src/gprs/gprs_sndcp_dcomp.c b/src/gprs/gprs_sndcp_dcomp.c
new file mode 100644
index 000000000..b0f95b486
--- /dev/null
+++ b/src/gprs/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 <openbsc/gprs_llc.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_sndcp_xid.h>
+#include <openbsc/v42bis.h>
+#include <openbsc/v42bis_private.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_sndcp_comp.h>
+#include <openbsc/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 == 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 initalized.\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 == 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 == 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 == V42BIS);
+
+ /* Run compression algo */
+ rc = v42bis_compress_unitdata(&pcomp_index, data, len,
+ comp_entity->state);
+
+ /* Find pcomp value */
+ *pcomp = gprs_sndcp_comp_get_comp(comp_entity, pcomp_index);
+
+ LOGP(DSNDCP, LOGL_DEBUG, "Data compression mode: dcomp=%d\n", *pcomp);
+
+ LOGP(DSNDCP, LOGL_DEBUG,
+ "Data compression done, old length=%d, new length=%d, entity=%p\n",
+ len, rc, comp_entity);
+
+ return rc;
+}
diff --git a/src/gprs/gprs_sndcp_pcomp.c b/src/gprs/gprs_sndcp_pcomp.c
new file mode 100644
index 000000000..a2236c3b1
--- /dev/null
+++ b/src/gprs/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 <openbsc/gprs_llc.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_sndcp_xid.h>
+#include <openbsc/slhc.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_sndcp_comp.h>
+#include <openbsc/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 == 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 initalized.\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 == 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 == 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 == RFC_1144);
+
+ /* Run compression algo */
+ rc = rfc1144_compress(&pcomp_index, data, len, comp_entity->state);
+ slhc_i_status(comp_entity->state);
+ slhc_o_status(comp_entity->state);
+
+ /* Find pcomp value */
+ *pcomp = gprs_sndcp_comp_get_comp(comp_entity, pcomp_index);
+
+ LOGP(DSNDCP, LOGL_DEBUG, "Header compression mode: pcomp=%d\n", *pcomp);
+
+ LOGP(DSNDCP, LOGL_DEBUG,
+ "Header compression done, old length=%d, new length=%d, entity=%p\n",
+ len, rc, comp_entity);
+ return rc;
+}
diff --git a/src/gprs/gprs_sndcp_vty.c b/src/gprs/gprs_sndcp_vty.c
new file mode 100644
index 000000000..430881fc8
--- /dev/null
+++ b/src/gprs/gprs_sndcp_vty.c
@@ -0,0 +1,71 @@
+/* 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 <openbsc/gsm_data.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 <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/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/gprs/gprs_sndcp_xid.c b/src/gprs/gprs_sndcp_xid.c
new file mode 100644
index 000000000..dfea5febc
--- /dev/null
+++ b/src/gprs/gprs_sndcp_xid.c
@@ -0,0 +1,1822 @@
+/* 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 <openbsc/debug.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/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 */
+ unsigned int algo; /* see also: 6.5.1.1.4 and 6.6.1.1.4 */
+ unsigned int compclass; /* Can be either SNDCP_XID_DATA_COMPRESSION or
+ SNDCP_XID_PROTOCOL_COMPRESSION */
+};
+
+/* 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;
+
+ 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 */
+ OSMO_ASSERT(comp_field->algo >= 0 || comp_field->algo <= 0x1f);
+
+ /* 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) {
+ *dst |= comp_field->algo & 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?) */
+int 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 -EINVAL;
+}
+
+/* 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 algorithm identfier by entity ID */
+static int lookup_algorithm_identifier(int entity, const struct
+ entity_algo_table
+ *lt, unsigned int lt_len, int compclass)
+{
+ int i;
+
+ if (!lt)
+ return -1;
+
+ for (i = 0; i < lt_len; i++) {
+ if ((lt[i].entity == entity)
+ && (lt[i].compclass == compclass))
+ return lt[i].algo;
+ }
+
+ return -1;
+}
+
+/* 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, int compclass)
+{
+ int src_counter = 0;
+ int i;
+
+ if (comp_field->p) {
+ /* Determine the number of expected PCOMP/DCOMP values */
+ if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION) {
+ /* For protocol compression */
+ switch (comp_field->algo) {
+ 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;
+ }
+ } else {
+ /* For data compression */
+ switch (comp_field->algo) {
+ 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;
+ }
+ }
+
+ 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) {
+ 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) {
+ 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, int 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) {
+ comp_field->algo = (*src) & 0x1F;
+ src_counter++;
+ src++;
+ }
+ /* Alternatively take the information from the lookup table */
+ else
+ comp_field->algo =
+ lookup_algorithm_identifier(comp_field->entity, lt,
+ lt_len, compclass);
+
+ /* 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;
+ int rc;
+
+ if (!comp_fields)
+ return -EINVAL;
+ if (!lt)
+ return -EINVAL;
+
+ memset(lt, 0, sizeof(*lt));
+
+ llist_for_each_entry(comp_field, comp_fields, list) {
+ if (comp_field->algo >= 0) {
+ lt[i].entity = comp_field->entity;
+ lt[i].algo = comp_field->algo;
+ rc = gprs_sndcp_get_compression_class(comp_field);
+
+ if (rc < 0) {
+ memset(lt, 0, sizeof(*lt));
+ return -EINVAL;
+ }
+
+ lt[i].compclass = rc;
+ 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)
+{
+ if (comp_field_dst->algo < 0)
+ 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) {
+ 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) {
+ 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;
+ int compclass;
+
+ OSMO_ASSERT(comp_fields);
+
+ llist_for_each_entry(comp_field, comp_fields, list) {
+ LOGP(DSNDCP, logl, "SNDCP-XID:\n");
+ LOGP(DSNDCP, logl, "struct gprs_sndcp_comp_field {\n");
+ LOGP(DSNDCP, logl, " entity=%d;\n", comp_field->entity);
+ LOGP(DSNDCP, logl, " algo=%d;\n", comp_field->algo);
+ 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]);
+ }
+
+ compclass = gprs_sndcp_get_compression_class(comp_field);
+
+ if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION) {
+ dump_pcomp_params(comp_field, logl);
+ } else if (compclass == SNDCP_XID_DATA_COMPRESSION) {
+ dump_dcomp_params(comp_field, logl);
+ }
+
+ LOGP(DSNDCP, logl, "}\n");
+ }
+
+}
diff --git a/src/gprs/gprs_subscriber.c b/src/gprs/gprs_subscriber.c
new file mode 100644
index 000000000..1bb51418a
--- /dev/null
+++ b/src/gprs/gprs_subscriber.c
@@ -0,0 +1,921 @@
+/* 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/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <openbsc/gprs_subscriber.h>
+#include <openbsc/gsup_client.h>
+
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_gmm.h>
+#include <openbsc/gprs_utils.h>
+
+#include <openbsc/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_bsc_ctx;
+
+LLIST_HEAD(_gprs_subscribers);
+struct llist_head * const gprs_subscribers = &_gprs_subscribers;
+
+static int gsup_read_cb(struct 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;
+
+ if (!sgi->cfg.gsup_server_addr.sin_addr.s_addr)
+ return 0;
+
+ addr_str = inet_ntoa(sgi->cfg.gsup_server_addr.sin_addr);
+
+ sgi->gsup_client = gsup_client_create(
+ 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 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_bsc_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 = 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 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_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;
+ gprs_apn_to_str(pdp_data->apn_str,
+ pdp_info->apn_enc, pdp_info->apn_enc_len);
+ memcpy(pdp_data->qos_subscribed, pdp_info->qos_enc, pdp_info->qos_enc_len);
+ pdp_data->qos_subscribed_len = pdp_info->qos_enc_len;
+ }
+}
+
+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);
+ break;
+ }
+
+ return -gsup_msg->cause;
+}
+
+static int gprs_subscr_handle_gsup_upd_loc_err(struct gprs_subscr *subscr,
+ struct osmo_gsup_message *gsup_msg)
+{
+ int cause_err;
+
+ cause_err = check_cause(gsup_msg->cause);
+
+ LOGGSUBSCRP(LOGL_DEBUG, subscr,
+ "Update location has failed with cause %d, handled as: %s\n",
+ gsup_msg->cause, strerror(cause_err));
+
+ switch (cause_err) {
+ case EACCES:
+ LOGGSUBSCRP(LOGL_NOTICE, subscr,
+ "GPRS update location failed, access denied, "
+ "GMM cause = '%s' (%d)\n",
+ get_value_string(gsm48_gmm_cause_names, gsup_msg->cause),
+ gsup_msg->cause);
+
+ subscr->authorized = 0;
+ subscr->sgsn_data->error_cause = gsup_msg->cause;
+ gprs_subscr_update_auth_info(subscr);
+ break;
+
+ case EHOSTUNREACH:
+ LOGGSUBSCRP(LOGL_NOTICE, subscr,
+ "GPRS update location failed, GMM cause = '%s' (%d)\n",
+ get_value_string(gsm48_gmm_cause_names, gsup_msg->cause),
+ gsup_msg->cause);
+
+ subscr->sgsn_data->error_cause = gsup_msg->cause;
+ gprs_subscr_update_auth_info(subscr);
+ break;
+
+ default:
+ case EINVAL:
+ LOGGSUBSCRP(LOGL_ERROR, subscr,
+ "GSUP protocol remote error, GMM cause = '%s' (%d)\n",
+ get_value_string(gsm48_gmm_cause_names, gsup_msg->cause),
+ gsup_msg->cause);
+ break;
+ }
+
+ return -gsup_msg->cause;
+}
+
+static int gprs_subscr_handle_gsup_purge_no_subscr(
+ struct osmo_gsup_message *gsup_msg)
+{
+ if (OSMO_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) {
+ LOGGSUPP(LOGL_NOTICE, gsup_msg,
+ "Purge MS has failed with cause '%s' (%d)\n",
+ get_value_string(gsm48_gmm_cause_names, gsup_msg->cause),
+ gsup_msg->cause);
+ return -gsup_msg->cause;
+ }
+
+ LOGGSUPP(LOGL_INFO, gsup_msg, "Completing purge MS\n");
+ return 0;
+}
+
+static int gprs_subscr_handle_gsup_purge_res(struct gprs_subscr *subscr,
+ struct osmo_gsup_message *gsup_msg)
+{
+ LOGGSUBSCRP(LOGL_INFO, subscr, "Completing purge MS\n");
+
+ /* Force silent cancellation */
+ subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE;
+ gprs_subscr_cancel(subscr);
+
+ return 0;
+}
+
+static int gprs_subscr_handle_gsup_purge_err(struct gprs_subscr *subscr,
+ struct osmo_gsup_message *gsup_msg)
+{
+ LOGGSUBSCRP(LOGL_NOTICE, subscr,
+ "Purge MS has failed with cause '%s' (%d)\n",
+ get_value_string(gsm48_gmm_cause_names, gsup_msg->cause),
+ gsup_msg->cause);
+
+ /* In GSM 09.02, 19.1.4.4, the text and the SDL diagram imply that
+ * the subscriber data is not removed if the request has failed. On the
+ * other hand, keeping the subscriber data in either error case
+ * (subscriber unknown, syntactical message error, connection error)
+ * doesn't seem to give any advantage, since the data will be restored
+ * on the next Attach Request anyway.
+ * This approach ensures, that the subscriber record will not stick if
+ * an error happens.
+ */
+
+ /* TODO: Check whether this behaviour is acceptable and either just
+ * remove this TODO-notice or change the implementation to not delete
+ * the subscriber data (eventually resetting the ENABLE_PURGE flag and
+ * restarting the expiry timer based on the cause).
+ *
+ * Subscriber Unknown: cancel subscr
+ * Temporary network problems: do nothing (handled by timer based retry)
+ * Message problems (syntax, nyi, ...): cancel subscr (retry won't help)
+ */
+
+ gprs_subscr_handle_gsup_purge_res(subscr, gsup_msg);
+
+ return -gsup_msg->cause;
+}
+
+static int gprs_subscr_handle_loc_cancel_req(struct gprs_subscr *subscr,
+ struct osmo_gsup_message *gsup_msg)
+{
+ struct osmo_gsup_message gsup_reply = {0};
+ int is_update_procedure = !gsup_msg->cancel_type ||
+ gsup_msg->cancel_type == OSMO_GSUP_CANCEL_TYPE_UPDATE;
+
+ LOGGSUBSCRP(LOGL_INFO, subscr, "Cancelling MS subscriber (%s)\n",
+ is_update_procedure ?
+ "update procedure" : "subscription withdraw");
+
+ gsup_reply.message_type = OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT;
+ gprs_subscr_tx_gsup_message(subscr, &gsup_reply);
+
+ if (is_update_procedure)
+ subscr->sgsn_data->error_cause = SGSN_ERROR_CAUSE_NONE;
+ else
+ /* Since a withdraw cause is not specified, just abort the
+ * current attachment. The following re-attachment should then
+ * be rejected with a proper cause value.
+ */
+ subscr->sgsn_data->error_cause = GMM_CAUSE_IMPL_DETACHED;
+
+ gprs_subscr_cancel(subscr);
+
+ return 0;
+}
+
+static int gprs_subscr_handle_unknown_imsi(struct osmo_gsup_message *gsup_msg)
+{
+ if (OSMO_GSUP_IS_MSGT_REQUEST(gsup_msg->message_type)) {
+ gprs_subscr_tx_gsup_error_reply(NULL, gsup_msg,
+ GMM_CAUSE_IMSI_UNKNOWN);
+ LOGP(DGPRS, LOGL_NOTICE,
+ "Unknown IMSI %s, discarding GSUP request "
+ "of type 0x%02x\n",
+ gsup_msg->imsi, gsup_msg->message_type);
+ } else if (OSMO_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) {
+ LOGP(DGPRS, LOGL_NOTICE,
+ "Unknown IMSI %s, discarding GSUP error "
+ "of type 0x%02x, cause '%s' (%d)\n",
+ gsup_msg->imsi, gsup_msg->message_type,
+ get_value_string(gsm48_gmm_cause_names, gsup_msg->cause),
+ gsup_msg->cause);
+ } else {
+ LOGP(DGPRS, LOGL_NOTICE,
+ "Unknown IMSI %s, discarding GSUP response "
+ "of type 0x%02x\n",
+ gsup_msg->imsi, gsup_msg->message_type);
+ }
+
+ return -GMM_CAUSE_IMSI_UNKNOWN;
+}
+
+int gprs_subscr_rx_gsup_message(struct msgb *msg)
+{
+ uint8_t *data = msgb_l2(msg);
+ size_t data_len = msgb_l2len(msg);
+ int rc = 0;
+
+ struct osmo_gsup_message gsup_msg = {0};
+ struct gprs_subscr *subscr;
+
+ rc = osmo_gsup_decode(data, data_len, &gsup_msg);
+ if (rc < 0) {
+ LOGP(DGPRS, LOGL_ERROR,
+ "decoding GSUP message fails with error '%s' (%d)\n",
+ get_value_string(gsm48_gmm_cause_names, -rc), -rc);
+ return rc;
+ }
+
+ if (!gsup_msg.imsi[0]) {
+ LOGP(DGPRS, LOGL_ERROR, "Missing IMSI in GSUP message\n");
+
+ if (OSMO_GSUP_IS_MSGT_REQUEST(gsup_msg.message_type))
+ gprs_subscr_tx_gsup_error_reply(NULL, &gsup_msg,
+ GMM_CAUSE_INV_MAND_INFO);
+ return -GMM_CAUSE_INV_MAND_INFO;
+ }
+
+ if (!gsup_msg.cause && OSMO_GSUP_IS_MSGT_ERROR(gsup_msg.message_type))
+ gsup_msg.cause = GMM_CAUSE_NET_FAIL;
+
+ subscr = gprs_subscr_get_by_imsi(gsup_msg.imsi);
+
+ if (!subscr) {
+ switch (gsup_msg.message_type) {
+ case OSMO_GSUP_MSGT_PURGE_MS_RESULT:
+ case OSMO_GSUP_MSGT_PURGE_MS_ERROR:
+ return gprs_subscr_handle_gsup_purge_no_subscr(&gsup_msg);
+ default:
+ return gprs_subscr_handle_unknown_imsi(&gsup_msg);
+ }
+ }
+
+ LOGGSUBSCRP(LOGL_INFO, subscr,
+ "Received GSUP message %s\n",
+ osmo_gsup_message_type_name(gsup_msg.message_type));
+
+ switch (gsup_msg.message_type) {
+ case OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST:
+ rc = gprs_subscr_handle_loc_cancel_req(subscr, &gsup_msg);
+ break;
+
+ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT:
+ rc = gprs_subscr_handle_gsup_auth_res(subscr, &gsup_msg);
+ break;
+
+ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR:
+ rc = gprs_subscr_handle_gsup_auth_err(subscr, &gsup_msg);
+ break;
+
+ case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT:
+ rc = gprs_subscr_handle_gsup_upd_loc_res(subscr, &gsup_msg);
+ break;
+
+ case OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR:
+ rc = gprs_subscr_handle_gsup_upd_loc_err(subscr, &gsup_msg);
+ break;
+
+ case OSMO_GSUP_MSGT_PURGE_MS_ERROR:
+ rc = gprs_subscr_handle_gsup_purge_err(subscr, &gsup_msg);
+ break;
+
+ case OSMO_GSUP_MSGT_PURGE_MS_RESULT:
+ rc = gprs_subscr_handle_gsup_purge_res(subscr, &gsup_msg);
+ break;
+
+ case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST:
+ rc = gprs_subscr_handle_gsup_isd_req(subscr, &gsup_msg);
+ break;
+
+ case OSMO_GSUP_MSGT_DELETE_DATA_REQUEST:
+ rc = gprs_subscr_handle_gsup_dsd_req(subscr, &gsup_msg);
+ break;
+
+ default:
+ LOGGSUBSCRP(LOGL_ERROR, subscr,
+ "Rx GSUP message %s not valid at SGSN\n",
+ osmo_gsup_message_type_name(gsup_msg.message_type));
+ if (OSMO_GSUP_IS_MSGT_REQUEST(gsup_msg.message_type))
+ gprs_subscr_tx_gsup_error_reply(
+ subscr, &gsup_msg, GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL);
+ rc = -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL;
+ break;
+ };
+
+ gprs_subscr_put(subscr);
+
+ return rc;
+}
+
+int gprs_subscr_purge(struct gprs_subscr *subscr)
+{
+ struct sgsn_subscriber_data *sdata = subscr->sgsn_data;
+ struct osmo_gsup_message gsup_msg = {0};
+
+ LOGGSUBSCRP(LOGL_INFO, subscr, "purging MS subscriber\n");
+
+ gsup_msg.message_type = OSMO_GSUP_MSGT_PURGE_MS_REQUEST;
+
+ /* Provide the HLR number in case it is known */
+ gsup_msg.hlr_enc_len = sdata->hlr_len;
+ gsup_msg.hlr_enc = sdata->hlr;
+
+ return gprs_subscr_tx_gsup_message(subscr, &gsup_msg);
+}
+
+static int gprs_subscr_query_auth_info(struct gprs_subscr *subscr,
+ const uint8_t *auts,
+ const uint8_t *auts_rand)
+{
+ struct osmo_gsup_message gsup_msg = {0};
+
+ /* Make sure we have a complete resync or clearly no resync. */
+ OSMO_ASSERT((auts != NULL) == (auts_rand != NULL));
+
+ LOGGSUBSCRP(LOGL_INFO, subscr, "requesting auth info%s\n",
+ auts ? " with AUTS (UMTS Resynch)" : "");
+
+ gsup_msg.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST;
+ gsup_msg.auts = auts;
+ gsup_msg.rand = auts_rand;
+ return gprs_subscr_tx_gsup_message(subscr, &gsup_msg);
+}
+
+int gprs_subscr_location_update(struct gprs_subscr *subscr)
+{
+ struct osmo_gsup_message gsup_msg = {0};
+
+ LOGGSUBSCRP(LOGL_INFO, subscr,
+ "subscriber data is not available\n");
+
+ gsup_msg.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST;
+ return gprs_subscr_tx_gsup_message(subscr, &gsup_msg);
+}
+
+void gprs_subscr_update(struct gprs_subscr *subscr)
+{
+ LOGGSUBSCRP(LOGL_DEBUG, subscr, "Updating subscriber data\n");
+
+ subscr->flags &= ~GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING;
+ subscr->flags &= ~GPRS_SUBSCRIBER_FIRST_CONTACT;
+
+ if (subscr->sgsn_data->mm)
+ sgsn_update_subscriber_data(subscr->sgsn_data->mm);
+}
+
+void gprs_subscr_update_auth_info(struct gprs_subscr *subscr)
+{
+ LOGGSUBSCRP(LOGL_DEBUG, subscr,
+ "Updating subscriber authentication info\n");
+
+ subscr->flags &= ~GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING;
+ subscr->flags &= ~GPRS_SUBSCRIBER_FIRST_CONTACT;
+
+ if (subscr->sgsn_data->mm)
+ sgsn_update_subscriber_data(subscr->sgsn_data->mm);
+}
+
+struct gprs_subscr *gprs_subscr_get_or_create_by_mmctx(struct sgsn_mm_ctx *mmctx)
+{
+ struct gprs_subscr *subscr = NULL;
+
+ if (mmctx->subscr)
+ return gprs_subscr_get(mmctx->subscr);
+
+ if (mmctx->imsi[0])
+ subscr = gprs_subscr_get_by_imsi(mmctx->imsi);
+
+ if (!subscr) {
+ subscr = gprs_subscr_get_or_create(mmctx->imsi);
+ subscr->flags |= GPRS_SUBSCRIBER_FIRST_CONTACT;
+ subscr->flags &= ~GPRS_SUBSCRIBER_ENABLE_PURGE;
+ }
+
+ osmo_strlcpy(subscr->imei, mmctx->imei, sizeof(subscr->imei));
+
+ if (subscr->lac != mmctx->ra.lac)
+ subscr->lac = mmctx->ra.lac;
+
+ subscr->sgsn_data->mm = mmctx;
+ mmctx->subscr = gprs_subscr_get(subscr);
+
+ return subscr;
+}
+
+int gprs_subscr_request_update_location(struct sgsn_mm_ctx *mmctx)
+{
+ struct gprs_subscr *subscr = NULL;
+ int rc;
+
+ LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting subscriber data update\n");
+
+ subscr = gprs_subscr_get_or_create_by_mmctx(mmctx);
+
+ subscr->flags |= GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING;
+
+ rc = gprs_subscr_location_update(subscr);
+ gprs_subscr_put(subscr);
+ return rc;
+}
+
+/*! \brief Send Update Auth Info request via GSUP, with or without resync.
+ * \param[in] mmctx MM context to request authentication tuples for.
+ * \param[in] auts 14 octet AUTS token for UMTS resync, or NULL.
+ * \param[in] auts_rand 16 octet Random token for UMTS resync, or NULL.
+ * In case of normal Authentication Info request, both \a auts and \a auts_rand
+ * must be NULL. For resync, both must be non-NULL.
+ */
+int gprs_subscr_request_auth_info(struct sgsn_mm_ctx *mmctx,
+ const uint8_t *auts,
+ const uint8_t *auts_rand)
+{
+ struct gprs_subscr *subscr = NULL;
+ int rc;
+
+ LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting subscriber authentication info\n");
+
+ subscr = gprs_subscr_get_or_create_by_mmctx(mmctx);
+
+ subscr->flags |= GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING;
+
+ rc = gprs_subscr_query_auth_info(subscr, auts, auts_rand);
+ gprs_subscr_put(subscr);
+ return rc;
+}
+
+static void gprs_subscr_free(struct gprs_subscr *gsub)
+{
+ llist_del(&gsub->entry);
+ talloc_free(gsub);
+}
+
+struct gprs_subscr *_gprs_subscr_get(struct gprs_subscr *gsub,
+ const char *file, int line)
+{
+ OSMO_ASSERT(gsub->use_count < INT_MAX);
+ gsub->use_count++;
+ LOGPSRC(DREF, LOGL_DEBUG, file, line,
+ "subscr %s usage increases to: %d\n",
+ gsub->imsi, gsub->use_count);
+ return gsub;
+}
+
+struct gprs_subscr *_gprs_subscr_put(struct gprs_subscr *gsub,
+ const char *file, int line)
+{
+ gsub->use_count--;
+ LOGPSRC(DREF, gsub->use_count >= 0? LOGL_DEBUG : LOGL_ERROR,
+ file, line,
+ "subscr %s usage decreases to: %d%s\n",
+ gsub->imsi, gsub->use_count,
+ gsub->keep_in_ram? ", keep-in-ram flag is set" : "");
+ if (gsub->use_count > 0)
+ return gsub;
+ if (gsub->keep_in_ram)
+ return gsub;
+ gprs_subscr_free(gsub);
+ return NULL;
+}
diff --git a/src/gprs/gprs_utils.c b/src/gprs/gprs_utils.c
new file mode 100644
index 000000000..64ed9788d
--- /dev/null
+++ b/src/gprs/gprs_utils.c
@@ -0,0 +1,274 @@
+/* GPRS utility functions */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2014 by On-Waves
+ * (C) 2013 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 <openbsc/gprs_utils.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gprs/gprs_ns.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include <string.h>
+
+/* FIXME: this needs to go to libosmocore/msgb.c */
+struct msgb *gprs_msgb_copy(const struct msgb *msg, const char *name)
+{
+ struct libgb_msgb_cb *old_cb, *new_cb;
+ struct msgb *new_msg;
+
+ new_msg = msgb_alloc(msg->data_len, name);
+ if (!new_msg)
+ return NULL;
+
+ /* copy data */
+ memcpy(new_msg->_data, msg->_data, new_msg->data_len);
+
+ /* copy header */
+ new_msg->len = msg->len;
+ new_msg->data += msg->data - msg->_data;
+ new_msg->head += msg->head - msg->_data;
+ new_msg->tail += msg->tail - msg->_data;
+
+ if (msg->l1h)
+ new_msg->l1h = new_msg->_data + (msg->l1h - msg->_data);
+ if (msg->l2h)
+ new_msg->l2h = new_msg->_data + (msg->l2h - msg->_data);
+ if (msg->l3h)
+ new_msg->l3h = new_msg->_data + (msg->l3h - msg->_data);
+ if (msg->l4h)
+ new_msg->l4h = new_msg->_data + (msg->l4h - msg->_data);
+
+ /* copy GB specific data */
+ old_cb = LIBGB_MSGB_CB(msg);
+ new_cb = LIBGB_MSGB_CB(new_msg);
+
+ if (old_cb->bssgph)
+ new_cb->bssgph = new_msg->_data + (old_cb->bssgph - msg->_data);
+ if (old_cb->llch)
+ new_cb->llch = new_msg->_data + (old_cb->llch - msg->_data);
+
+ /* bssgp_cell_id is a pointer into the old msgb, so we need to make
+ * it a pointer into the new msgb */
+ if (old_cb->bssgp_cell_id)
+ new_cb->bssgp_cell_id = new_msg->_data +
+ (old_cb->bssgp_cell_id - msg->_data);
+ new_cb->nsei = old_cb->nsei;
+ new_cb->bvci = old_cb->bvci;
+ new_cb->tlli = old_cb->tlli;
+
+ return new_msg;
+}
+
+/* TODO: Move this to libosmocore/msgb.c */
+int gprs_msgb_resize_area(struct msgb *msg, uint8_t *area,
+ size_t old_size, size_t new_size)
+{
+ int rc;
+ uint8_t *rest = area + old_size;
+ int rest_len = msg->len - old_size - (area - msg->data);
+ int delta_size = (int)new_size - (int)old_size;
+
+ if (delta_size == 0)
+ return 0;
+
+ if (delta_size > 0) {
+ rc = msgb_trim(msg, msg->len + delta_size);
+ if (rc < 0)
+ return rc;
+ }
+
+ memmove(area + new_size, area + old_size, rest_len);
+
+ if (msg->l1h >= rest)
+ msg->l1h += delta_size;
+ if (msg->l2h >= rest)
+ msg->l2h += delta_size;
+ if (msg->l3h >= rest)
+ msg->l3h += delta_size;
+ if (msg->l4h >= rest)
+ msg->l4h += delta_size;
+
+ if (delta_size < 0)
+ msgb_trim(msg, msg->len + delta_size);
+
+ return 0;
+}
+
+/* TODO: Move these conversion functions to a utils file. */
+/* TODO: consolidate with gprs_apn2str(). */
+/** memmove apn_enc to out_str, replacing the length octets in apn_enc with '.'
+ * (omitting the first one) and terminating with a '\0'.
+ * out_str needs to have rest_chars amount of bytes or 1 whatever is bigger.
+ */
+char * gprs_apn_to_str(char *out_str, const uint8_t *apn_enc, size_t rest_chars)
+{
+ char *str = out_str;
+
+ while (rest_chars > 0 && apn_enc[0]) {
+ size_t label_size = apn_enc[0];
+ if (label_size + 1 > rest_chars)
+ return NULL;
+
+ memmove(str, apn_enc + 1, label_size);
+ str += label_size;
+ rest_chars -= label_size + 1;
+ apn_enc += label_size + 1;
+
+ if (rest_chars)
+ *(str++) = '.';
+ }
+ str[0] = '\0';
+
+ return out_str;
+}
+
+int gprs_str_to_apn(uint8_t *apn_enc, size_t max_len, const char *str)
+{
+ uint8_t *last_len_field;
+ int len;
+
+ /* Can we even write the length field to the output? */
+ if (max_len == 0)
+ return -1;
+
+ /* Remember where we need to put the length once we know it */
+ last_len_field = apn_enc;
+ len = 1;
+ apn_enc += 1;
+
+ while (str[0]) {
+ if (len >= max_len)
+ return -1;
+
+ if (str[0] == '.') {
+ *last_len_field = (apn_enc - last_len_field) - 1;
+ last_len_field = apn_enc;
+ } else {
+ *apn_enc = str[0];
+ }
+ apn_enc += 1;
+ str += 1;
+ len += 1;
+ }
+
+ *last_len_field = (apn_enc - last_len_field) - 1;
+
+ return len;
+}
+
+/* GSM 04.08, 10.5.7.3 GPRS Timer */
+int gprs_tmr_to_secs(uint8_t tmr)
+{
+ switch (tmr & GPRS_TMR_UNIT_MASK) {
+ case GPRS_TMR_2SECONDS:
+ return 2 * (tmr & GPRS_TMR_FACT_MASK);
+ default:
+ case GPRS_TMR_MINUTE:
+ return 60 * (tmr & GPRS_TMR_FACT_MASK);
+ case GPRS_TMR_6MINUTE:
+ return 360 * (tmr & GPRS_TMR_FACT_MASK);
+ case GPRS_TMR_DEACTIVATED:
+ return -1;
+ }
+}
+
+/* This functions returns a tmr value such that
+ * - f is monotonic
+ * - f(s) <= s
+ * - f(s) == s if a tmr exists with s = gprs_tmr_to_secs(tmr)
+ * - the best possible resolution is used
+ * where
+ * f(s) = gprs_tmr_to_secs(gprs_secs_to_tmr_floor(s))
+ */
+uint8_t gprs_secs_to_tmr_floor(int secs)
+{
+ if (secs < 0)
+ return GPRS_TMR_DEACTIVATED;
+ if (secs < 2 * 32)
+ return GPRS_TMR_2SECONDS | (secs / 2);
+ if (secs < 60 * 2)
+ /* Ensure monotonicity */
+ return GPRS_TMR_2SECONDS | GPRS_TMR_FACT_MASK;
+ if (secs < 60 * 32)
+ return GPRS_TMR_MINUTE | (secs / 60);
+ if (secs < 360 * 6)
+ /* Ensure monotonicity */
+ return GPRS_TMR_MINUTE | GPRS_TMR_FACT_MASK;
+ if (secs < 360 * 32)
+ return GPRS_TMR_6MINUTE | (secs / 360);
+
+ return GPRS_TMR_6MINUTE | GPRS_TMR_FACT_MASK;
+}
+
+/* GSM 04.08, 10.5.1.4 */
+int gprs_is_mi_tmsi(const uint8_t *value, size_t value_len)
+{
+ if (value_len != GSM48_TMSI_LEN)
+ return 0;
+
+ if (!value || (value[0] & GSM_MI_TYPE_MASK) != GSM_MI_TYPE_TMSI)
+ return 0;
+
+ return 1;
+}
+
+/* GSM 04.08, 10.5.1.4 */
+int gprs_is_mi_imsi(const uint8_t *value, size_t value_len)
+{
+ if (value_len == 0)
+ return 0;
+
+ if (!value || (value[0] & GSM_MI_TYPE_MASK) != GSM_MI_TYPE_IMSI)
+ return 0;
+
+ return 1;
+}
+
+int gprs_parse_mi_tmsi(const uint8_t *value, size_t value_len, uint32_t *tmsi)
+{
+ uint32_t tmsi_be;
+
+ if (!gprs_is_mi_tmsi(value, value_len))
+ return 0;
+
+ memcpy(&tmsi_be, value + 1, sizeof(tmsi_be));
+
+ *tmsi = ntohl(tmsi_be);
+ return 1;
+}
+
+void gprs_parse_tmsi(const uint8_t *value, uint32_t *tmsi)
+{
+ uint32_t tmsi_be;
+
+ memcpy(&tmsi_be, value, sizeof(tmsi_be));
+
+ *tmsi = ntohl(tmsi_be);
+}
+
+int gprs_ra_id_equals(const struct gprs_ra_id *id1,
+ const struct gprs_ra_id *id2)
+{
+ return (id1->mcc == id2->mcc && id1->mnc == id2->mnc &&
+ id1->lac == id2->lac && id1->rac == id2->rac);
+}
diff --git a/src/gprs/gtphub.c b/src/gprs/gtphub.c
new file mode 100644
index 000000000..211018b53
--- /dev/null
+++ b/src/gprs/gtphub.c
@@ -0,0 +1,2931 @@
+/* GTP Hub Implementation */
+
+/* (C) 2015 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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 <string.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <time.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <gtp.h>
+#include <gtpie.h>
+
+#include <openbsc/gtphub.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_utils.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stats.h>
+
+
+static const int GTPH_GC_TICK_SECONDS = 1;
+
+void *osmo_gtphub_ctx;
+
+/* Convenience makro, note: only within this C file. */
+#define LOG(level, fmt, args...) \
+ LOGP(DGTPHUB, level, fmt, ##args)
+
+#define ZERO_STRUCT(struct_pointer) memset(struct_pointer, '\0', \
+ sizeof(*(struct_pointer)))
+
+/* TODO move this to osmocom/core/select.h ? */
+typedef int (*osmo_fd_cb_t)(struct osmo_fd *fd, unsigned int what);
+
+/* TODO move this to osmocom/core/linuxlist.h ? */
+#define __llist_first(head) (((head)->next == (head)) ? NULL : (head)->next)
+#define llist_first(head, type, entry) \
+ llist_entry(__llist_first(head), type, entry)
+
+#define __llist_last(head) (((head)->next == (head)) ? NULL : (head)->prev)
+#define llist_last(head, type, entry) \
+ llist_entry(__llist_last(head), type, entry)
+
+/* TODO move GTP header stuff to openggsn/gtp/ ? See gtp_decaps*() */
+
+enum gtp_rc {
+ GTP_RC_UNKNOWN = 0,
+ GTP_RC_TINY = 1, /* no IEs (like ping/pong) */
+ GTP_RC_PDU_C = 2, /* a real packet with IEs */
+ GTP_RC_PDU_U = 3, /* a real packet with User data */
+
+ GTP_RC_TOOSHORT = -1,
+ GTP_RC_UNSUPPORTED_VERSION = -2,
+ GTP_RC_INVALID_IE = -3,
+};
+
+struct gtp_packet_desc {
+ union gtp_packet *data;
+ int data_len;
+ int header_len;
+ int version;
+ uint8_t type;
+ uint16_t seq;
+ uint32_t header_tei_rx;
+ uint32_t header_tei;
+ int rc; /* enum gtp_rc */
+ unsigned int plane_idx;
+ unsigned int side_idx;
+ struct gtphub_tunnel *tun;
+ time_t timestamp;
+ union gtpie_member *ie[GTPIE_SIZE];
+};
+
+struct pending_delete {
+ struct llist_head entry;
+ struct expiring_item expiry_entry;
+
+ struct gtphub_tunnel *tun;
+ uint8_t teardown_ind;
+ uint8_t nsapi;
+};
+
+
+/* counters */
+
+enum gtphub_counters_io {
+ GTPH_CTR_PKTS_IN = 0,
+ GTPH_CTR_PKTS_OUT,
+ GTPH_CTR_BYTES_IN,
+ GTPH_CTR_BYTES_OUT
+};
+
+static const struct rate_ctr_desc gtphub_counters_io_desc[] = {
+ { "packets.in", "Packets ( In)" },
+ { "packets.out", "Packets (Out)" },
+ { "bytes.in", "Bytes ( In)" },
+ { "bytes.out", "Bytes (Out)" },
+};
+
+static const struct rate_ctr_group_desc gtphub_ctrg_io_desc = {
+ .group_name_prefix = "gtphub.bind",
+ .group_description = "I/O Statistics",
+ .num_ctr = ARRAY_SIZE(gtphub_counters_io_desc),
+ .ctr_desc = gtphub_counters_io_desc,
+ .class_id = OSMO_STATS_CLASS_GLOBAL,
+};
+
+
+/* support */
+
+static const char *gtp_type_str(uint8_t type)
+{
+ switch (type) {
+ case 1:
+ return " (Echo Request)";
+ case 2:
+ return " (Echo Response)";
+ case 16:
+ return " (Create PDP Ctx Request)";
+ case 17:
+ return " (Create PDP Ctx Response)";
+ case 18:
+ return " (Update PDP Ctx Request)";
+ case 19:
+ return " (Update PDP Ctx Response)";
+ case 20:
+ return " (Delete PDP Ctx Request)";
+ case 21:
+ return " (Delete PDP Ctx Response)";
+ case 255:
+ return " (User Data)";
+ default:
+ return "";
+ }
+}
+
+void gsn_addr_copy(struct gsn_addr *gsna, const struct gsn_addr *src)
+{
+ *gsna = *src;
+}
+
+int gsn_addr_from_sockaddr(struct gsn_addr *gsna, uint16_t *port,
+ const struct osmo_sockaddr *sa)
+{
+ char addr_str[256];
+ char port_str[6];
+
+ if (osmo_sockaddr_to_strs(addr_str, sizeof(addr_str),
+ port_str, sizeof(port_str),
+ sa, (NI_NUMERICHOST | NI_NUMERICSERV))
+ != 0) {
+ return -1;
+ }
+
+ if (port)
+ *port = atoi(port_str);
+
+ return gsn_addr_from_str(gsna, addr_str);
+}
+
+int gsn_addr_from_str(struct gsn_addr *gsna, const char *numeric_addr_str)
+{
+ if ((!gsna) || (!numeric_addr_str))
+ return -1;
+
+ int af = AF_INET;
+ gsna->len = 4;
+ const char *pos = numeric_addr_str;
+ for (; *pos; pos++) {
+ if (*pos == ':') {
+ af = AF_INET6;
+ gsna->len = 16;
+ break;
+ }
+ }
+
+ int rc = inet_pton(af, numeric_addr_str, gsna->buf);
+ if (rc != 1) {
+ LOG(LOGL_ERROR, "Cannot resolve numeric address: '%s'\n",
+ numeric_addr_str);
+ return -1;
+ }
+ return 0;
+}
+
+const char *gsn_addr_to_str(const struct gsn_addr *gsna)
+{
+ static char buf[INET6_ADDRSTRLEN + 1];
+ return gsn_addr_to_strb(gsna, buf, sizeof(buf));
+}
+
+const char *gsn_addr_to_strb(const struct gsn_addr *gsna,
+ char *strbuf,
+ int strbuf_len)
+{
+ int af;
+ switch (gsna->len) {
+ case 4:
+ af = AF_INET;
+ break;
+ case 16:
+ af = AF_INET6;
+ break;
+ default:
+ return NULL;
+ }
+
+ const char *r = inet_ntop(af, gsna->buf, strbuf, strbuf_len);
+ if (!r) {
+ LOG(LOGL_ERROR, "Cannot convert gsn_addr to string:"
+ " %s: len=%d, buf=%s\n",
+ strerror(errno),
+ (int)gsna->len,
+ osmo_hexdump(gsna->buf, sizeof(gsna->buf)));
+ }
+ return r;
+}
+
+int gsn_addr_same(const struct gsn_addr *a, const struct gsn_addr *b)
+{
+ if (a == b)
+ return 1;
+ if ((!a) || (!b))
+ return 0;
+ if (a->len != b->len)
+ return 0;
+ return (memcmp(a->buf, b->buf, a->len) == 0)? 1 : 0;
+}
+
+static int gsn_addr_get(struct gsn_addr *gsna, const struct gtp_packet_desc *p,
+ int idx)
+{
+ if (p->rc != GTP_RC_PDU_C)
+ return -1;
+
+ unsigned int len;
+ /* gtpie.h fails to declare gtpie_gettlv()'s first arg as const. */
+ if (gtpie_gettlv((union gtpie_member**)p->ie, GTPIE_GSN_ADDR, idx,
+ &len, gsna->buf, sizeof(gsna->buf))
+ != 0)
+ return -1;
+ gsna->len = len;
+ return 0;
+}
+
+static int gsn_addr_put(const struct gsn_addr *gsna, struct gtp_packet_desc *p,
+ int idx)
+{
+ if (p->rc != GTP_RC_PDU_C)
+ return -1;
+
+ int ie_idx;
+ ie_idx = gtpie_getie(p->ie, GTPIE_GSN_ADDR, idx);
+
+ if (ie_idx < 0)
+ return -1;
+
+ struct gtpie_tlv *ie = &p->ie[ie_idx]->tlv;
+ int ie_l = ntoh16(ie->l);
+ if (ie_l != gsna->len) {
+ LOG(LOGL_ERROR, "Not implemented:"
+ " replace an IE address of different size:"
+ " replace %d with %d\n", (int)ie_l, (int)gsna->len);
+ return -1;
+ }
+
+ memcpy(ie->v, gsna->buf, (int)ie_l);
+ return 0;
+}
+
+/* Validate GTP version 0 data; analogous to validate_gtp1_header(), see there.
+ */
+void validate_gtp0_header(struct gtp_packet_desc *p)
+{
+ const struct gtp0_header *pheader = &(p->data->gtp0.h);
+ p->rc = GTP_RC_UNKNOWN;
+ p->header_len = 0;
+
+ OSMO_ASSERT(p->data_len >= 1);
+ OSMO_ASSERT(p->version == 0);
+
+ if (p->data_len < GTP0_HEADER_SIZE) {
+ LOG(LOGL_ERROR, "GTP0 packet too short: %d\n", p->data_len);
+ p->rc = GTP_RC_TOOSHORT;
+ return;
+ }
+
+ p->type = ntoh8(pheader->type);
+ p->seq = ntoh16(pheader->seq);
+ p->header_tei_rx = 0; /* TODO */
+ p->header_tei = p->header_tei_rx;
+
+ if (p->data_len == GTP0_HEADER_SIZE) {
+ p->rc = GTP_RC_TINY;
+ p->header_len = GTP0_HEADER_SIZE;
+ return;
+ }
+
+ /* Check packet length field versus length of packet */
+ if (p->data_len != (ntoh16(pheader->length) + GTP0_HEADER_SIZE)) {
+ LOG(LOGL_ERROR, "GTP packet length field (%d + %d) does not"
+ " match actual length (%d)\n",
+ GTP0_HEADER_SIZE, (int)ntoh16(pheader->length),
+ p->data_len);
+ p->rc = GTP_RC_TOOSHORT;
+ return;
+ }
+
+ LOG(LOGL_DEBUG, "GTP v0 TID = %" PRIu64 "\n", pheader->tid);
+ p->header_len = GTP0_HEADER_SIZE;
+ p->rc = GTP_RC_PDU_C;
+}
+
+/* Validate GTP version 1 data, and update p->rc with the result, as well as
+ * p->header_len in case of a valid header. */
+void validate_gtp1_header(struct gtp_packet_desc *p)
+{
+ const struct gtp1_header_long *pheader = &(p->data->gtp1l.h);
+ p->rc = GTP_RC_UNKNOWN;
+ p->header_len = 0;
+
+ OSMO_ASSERT(p->data_len >= 1);
+ OSMO_ASSERT(p->version == 1);
+
+ if ((p->data_len < GTP1_HEADER_SIZE_LONG)
+ && (p->data_len != GTP1_HEADER_SIZE_SHORT)){
+ LOG(LOGL_ERROR, "GTP packet too short: %d\n", p->data_len);
+ p->rc = GTP_RC_TOOSHORT;
+ return;
+ }
+
+ p->type = ntoh8(pheader->type);
+ p->header_tei_rx = ntoh32(pheader->tei);
+ p->header_tei = p->header_tei_rx;
+ p->seq = ntoh16(pheader->seq);
+
+ LOG(LOGL_DEBUG, "| GTPv1\n");
+ LOG(LOGL_DEBUG, "| type = %" PRIu8 " 0x%02" PRIx8 "\n", p->type, p->type);
+ LOG(LOGL_DEBUG, "| length = %" PRIu16 " 0x%04" PRIx16 "\n", ntoh16(pheader->length), ntoh16(pheader->length));
+ LOG(LOGL_DEBUG, "| TEI = %" PRIu32 " 0x%08" PRIx32 "\n", p->header_tei_rx, p->header_tei_rx);
+ LOG(LOGL_DEBUG, "| seq = %" PRIu16 " 0x%04" PRIx16 "\n", p->seq, p->seq);
+ LOG(LOGL_DEBUG, "| npdu = %" PRIu8 " 0x%02" PRIx8 "\n", pheader->npdu, pheader->npdu);
+ LOG(LOGL_DEBUG, "| next = %" PRIu8 " 0x%02" PRIx8 "\n", pheader->next, pheader->next);
+
+ if (p->data_len <= GTP1_HEADER_SIZE_LONG) {
+ p->rc = GTP_RC_TINY;
+ p->header_len = GTP1_HEADER_SIZE_SHORT;
+ return;
+ }
+
+ /* Check packet length field versus length of packet */
+ int announced_len = ntoh16(pheader->length) + GTP1_HEADER_SIZE_SHORT;
+ if (p->data_len != announced_len) {
+ LOG(LOGL_ERROR, "GTP packet length field (%d + %d) does not"
+ " match actual length (%d)\n",
+ GTP1_HEADER_SIZE_SHORT, (int)ntoh16(pheader->length),
+ p->data_len);
+ p->rc = GTP_RC_TOOSHORT;
+ return;
+ }
+
+ p->rc = GTP_RC_PDU_C;
+ p->header_len = GTP1_HEADER_SIZE_LONG;
+}
+
+/* Examine whether p->data of size p->data_len has a valid GTP header. Set
+ * p->version, p->rc and p->header_len. On error, p->rc <= 0 (see enum
+ * gtp_rc). p->data must point at a buffer with p->data_len set. */
+void validate_gtp_header(struct gtp_packet_desc *p)
+{
+ p->rc = GTP_RC_UNKNOWN;
+
+ /* Need at least 1 byte in order to check version */
+ if (p->data_len < 1) {
+ LOG(LOGL_ERROR, "Discarding packet - too small: %d\n",
+ p->data_len);
+ p->rc = GTP_RC_TOOSHORT;
+ return;
+ }
+
+ p->version = p->data->flags >> 5;
+
+ switch (p->version) {
+ case 0:
+ validate_gtp0_header(p);
+ break;
+ case 1:
+ validate_gtp1_header(p);
+ break;
+ default:
+ LOG(LOGL_ERROR, "Unsupported GTP version: %d\n", p->version);
+ p->rc = GTP_RC_UNSUPPORTED_VERSION;
+ break;
+ }
+}
+
+
+/* Return the value of the i'th IMSI IEI by copying to *imsi.
+ * The first IEI is reached by passing i = 0.
+ * imsi must point at allocated space of (at least) 8 bytes.
+ * Return 1 on success, or 0 if not found. */
+static int get_ie_imsi(union gtpie_member *ie[], int i, uint8_t *imsi)
+{
+ return gtpie_gettv0(ie, GTPIE_IMSI, i, imsi, 8) == 0;
+}
+
+/* Analogous to get_ie_imsi(). nsapi must point at a single uint8_t. */
+static int get_ie_nsapi(union gtpie_member *ie[], int i, uint8_t *nsapi)
+{
+ return gtpie_gettv1(ie, GTPIE_NSAPI, i, nsapi) == 0;
+}
+
+static char imsi_digit_to_char(uint8_t nibble)
+{
+ nibble &= 0x0f;
+ if (nibble > 9)
+ return (nibble == 0x0f) ? '\0' : '?';
+ return '0' + nibble;
+}
+
+/* Return a human readable IMSI string, in a static buffer.
+ * imsi must point at 8 octets of IMSI IE encoded IMSI data. */
+static int imsi_to_str(uint8_t *imsi, const char **imsi_str)
+{
+ static char str[17];
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ char c;
+ c = imsi_digit_to_char(imsi[i]);
+ if (c == '?')
+ return -1;
+ str[2*i] = c;
+
+ c = imsi_digit_to_char(imsi[i] >> 4);
+ if (c == '?')
+ return -1;
+ str[2*i + 1] = c;
+ }
+ str[16] = '\0';
+ *imsi_str = str;
+ return 1;
+}
+
+/* Return 0 if not present, 1 if present and decoded successfully, -1 if
+ * present but cannot be decoded. */
+static int get_ie_imsi_str(union gtpie_member *ie[], int i,
+ const char **imsi_str)
+{
+ uint8_t imsi_buf[8];
+ if (!get_ie_imsi(ie, i, imsi_buf))
+ return 0;
+ return imsi_to_str(imsi_buf, imsi_str);
+}
+
+/* Return 0 if not present, 1 if present and decoded successfully, -1 if
+ * present but cannot be decoded. */
+static int get_ie_apn_str(union gtpie_member *ie[], const char **apn_str)
+{
+ static char apn_buf[GSM_APN_LENGTH];
+ unsigned int len;
+ if (gtpie_gettlv(ie, GTPIE_APN, 0,
+ &len, apn_buf, sizeof(apn_buf)) != 0)
+ return 0;
+
+ if (len < 2) {
+ LOG(LOGL_ERROR, "APN IE: invalid length: %d\n",
+ (int)len);
+ return -1;
+ }
+
+ if (len > (sizeof(apn_buf) - 1))
+ len = sizeof(apn_buf) - 1;
+ apn_buf[len] = '\0';
+
+ *apn_str = gprs_apn_to_str(apn_buf, (uint8_t*)apn_buf, len);
+ if (!(*apn_str)) {
+ LOG(LOGL_ERROR, "APN IE: present but cannot be decoded: %s\n",
+ osmo_hexdump((uint8_t*)apn_buf, len));
+ return -1;
+ }
+ return 1;
+}
+
+
+/* Validate header, and index information elements. Write decoded packet
+ * information to *res. res->data will point at the given data buffer. On
+ * error, p->rc is set <= 0 (see enum gtp_rc). */
+static void gtp_decode(const uint8_t *data, int data_len,
+ unsigned int from_side_idx,
+ unsigned int from_plane_idx,
+ struct gtp_packet_desc *res,
+ time_t now)
+{
+ ZERO_STRUCT(res);
+ res->data = (union gtp_packet*)data;
+ res->data_len = data_len;
+ res->side_idx = from_side_idx;
+ res->plane_idx = from_plane_idx;
+ res->timestamp = now;
+
+ validate_gtp_header(res);
+
+ if (res->rc <= 0)
+ return;
+
+ LOG(LOGL_DEBUG, "Valid GTP header (v%d)\n", res->version);
+
+ if (from_plane_idx == GTPH_PLANE_USER) {
+ res->rc = GTP_RC_PDU_U;
+ return;
+ }
+
+ if (res->rc != GTP_RC_PDU_C) {
+ LOG(LOGL_DEBUG, "no IEs in this GTP packet\n");
+ return;
+ }
+
+ if (gtpie_decaps(res->ie, res->version,
+ (void*)(data + res->header_len),
+ res->data_len - res->header_len) != 0) {
+ res->rc = GTP_RC_INVALID_IE;
+ LOG(LOGL_ERROR, "INVALID: cannot decode IEs."
+ " Dropping GTP packet%s.\n",
+ gtp_type_str(res->type)
+ );
+ return;
+ }
+
+#if 1
+ /* TODO if (<loglevel is debug>)
+ (waiting for a commit from jerlbeck) */
+ int i;
+
+ for (i = 0; i < 10; i++) {
+ const char *imsi;
+ if (get_ie_imsi_str(res->ie, i, &imsi) < 1)
+ break;
+ LOG(LOGL_DEBUG, "| IMSI %s\n", imsi);
+ }
+
+ for (i = 0; i < 10; i++) {
+ uint8_t nsapi;
+ if (!get_ie_nsapi(res->ie, i, &nsapi))
+ break;
+ LOG(LOGL_DEBUG, "| NSAPI %d\n", (int)nsapi);
+ }
+
+ for (i = 0; i < 2; i++) {
+ struct gsn_addr addr;
+ if (gsn_addr_get(&addr, res, i) == 0)
+ LOG(LOGL_DEBUG, "| addr %s\n", gsn_addr_to_str(&addr));
+ }
+
+ for (i = 0; i < 10; i++) {
+ uint32_t tei;
+ if (gtpie_gettv4(res->ie, GTPIE_TEI_DI, i, &tei) != 0)
+ break;
+ LOG(LOGL_DEBUG, "| TEI DI (USER) %" PRIu32 " 0x%08" PRIx32 "\n",
+ tei, tei);
+ }
+
+ for (i = 0; i < 10; i++) {
+ uint32_t tei;
+ if (gtpie_gettv4(res->ie, GTPIE_TEI_C, i, &tei) != 0)
+ break;
+ LOG(LOGL_DEBUG, "| TEI (CTRL) %" PRIu32 " 0x%08" PRIx32 "\n",
+ tei, tei);
+ }
+#endif
+}
+
+
+/* expiry */
+
+void expiry_init(struct expiry *exq, int expiry_in_seconds)
+{
+ ZERO_STRUCT(exq);
+ exq->expiry_in_seconds = expiry_in_seconds;
+ INIT_LLIST_HEAD(&exq->items);
+}
+
+void expiry_add(struct expiry *exq, struct expiring_item *item, time_t now)
+{
+ item->expiry = now + exq->expiry_in_seconds;
+
+ OSMO_ASSERT(llist_empty(&exq->items)
+ || (item->expiry
+ >= llist_last(&exq->items, struct expiring_item, entry)->expiry));
+
+ /* Add/move to the tail to always sort by expiry, ascending. */
+ llist_del(&item->entry);
+ llist_add_tail(&item->entry, &exq->items);
+}
+
+int expiry_tick(struct expiry *exq, time_t now)
+{
+ int expired = 0;
+ struct expiring_item *m, *n;
+ llist_for_each_entry_safe(m, n, &exq->items, entry) {
+ if (m->expiry <= now) {
+ expiring_item_del(m);
+ expired ++;
+ } else {
+ /* The items are added sorted by expiry. So when we hit
+ * an unexpired entry, only more unexpired ones will
+ * follow. */
+ break;
+ }
+ }
+ return expired;
+}
+
+void expiry_clear(struct expiry *exq)
+{
+ struct expiring_item *m, *n;
+ llist_for_each_entry_safe(m, n, &exq->items, entry) {
+ expiring_item_del(m);
+ }
+}
+
+void expiring_item_init(struct expiring_item *item)
+{
+ ZERO_STRUCT(item);
+ INIT_LLIST_HEAD(&item->entry);
+}
+
+void expiring_item_del(struct expiring_item *item)
+{
+ OSMO_ASSERT(item);
+ llist_del(&item->entry);
+ INIT_LLIST_HEAD(&item->entry);
+ if (item->del_cb) {
+ /* avoid loops */
+ del_cb_t del_cb = item->del_cb;
+ item->del_cb = 0;
+ (del_cb)(item);
+ }
+}
+
+
+/* nr_map, nr_pool */
+
+void nr_pool_init(struct nr_pool *pool, nr_t nr_min, nr_t nr_max)
+{
+ *pool = (struct nr_pool){
+ .nr_min = nr_min,
+ .nr_max = nr_max,
+ .last_nr = nr_max
+ };
+}
+
+nr_t nr_pool_next(struct nr_pool *pool)
+{
+ if (pool->last_nr >= pool->nr_max)
+ pool->last_nr = pool->nr_min;
+ else
+ pool->last_nr ++;
+
+ return pool->last_nr;
+}
+
+void nr_map_init(struct nr_map *map, struct nr_pool *pool,
+ struct expiry *exq)
+{
+ ZERO_STRUCT(map);
+ map->pool = pool;
+ map->add_items_to_expiry = exq;
+ INIT_LLIST_HEAD(&map->mappings);
+}
+
+void nr_mapping_init(struct nr_mapping *m)
+{
+ ZERO_STRUCT(m);
+ INIT_LLIST_HEAD(&m->entry);
+ expiring_item_init(&m->expiry_entry);
+}
+
+void nr_map_add(struct nr_map *map, struct nr_mapping *mapping, time_t now)
+{
+ /* Generate a mapped number */
+ mapping->repl = nr_pool_next(map->pool);
+
+ /* Add to the tail to always yield a list sorted by expiry, in
+ * ascending order. */
+ llist_add_tail(&mapping->entry, &map->mappings);
+ nr_map_refresh(map, mapping, now);
+}
+
+void nr_map_refresh(struct nr_map *map, struct nr_mapping *mapping, time_t now)
+{
+ if (!map->add_items_to_expiry)
+ return;
+ expiry_add(map->add_items_to_expiry,
+ &mapping->expiry_entry,
+ now);
+}
+
+void nr_map_clear(struct nr_map *map)
+{
+ struct nr_mapping *m;
+ struct nr_mapping *n;
+ llist_for_each_entry_safe(m, n, &map->mappings, entry) {
+ nr_mapping_del(m);
+ }
+}
+
+int nr_map_empty(const struct nr_map *map)
+{
+ return llist_empty(&map->mappings);
+}
+
+struct nr_mapping *nr_map_get(const struct nr_map *map,
+ void *origin, nr_t nr_orig)
+{
+ struct nr_mapping *mapping;
+ llist_for_each_entry(mapping, &map->mappings, entry) {
+ if ((mapping->origin == origin)
+ && (mapping->orig == nr_orig))
+ return mapping;
+ }
+ /* Not found. */
+ return NULL;
+}
+
+struct nr_mapping *nr_map_get_inv(const struct nr_map *map, nr_t nr_repl)
+{
+ struct nr_mapping *mapping;
+ llist_for_each_entry(mapping, &map->mappings, entry) {
+ if (mapping->repl == nr_repl) {
+ return mapping;
+ }
+ }
+ /* Not found. */
+ return NULL;
+}
+
+void nr_mapping_del(struct nr_mapping *mapping)
+{
+ OSMO_ASSERT(mapping);
+ llist_del(&mapping->entry);
+ INIT_LLIST_HEAD(&mapping->entry);
+ expiring_item_del(&mapping->expiry_entry);
+}
+
+
+/* gtphub */
+
+const char* const gtphub_plane_idx_names[GTPH_PLANE_N] = {
+ "CTRL",
+ "USER",
+};
+
+const uint16_t gtphub_plane_idx_default_port[GTPH_PLANE_N] = {
+ 2123,
+ 2152,
+};
+
+const char* const gtphub_side_idx_names[GTPH_SIDE_N] = {
+ "SGSN",
+ "GGSN",
+};
+
+time_t gtphub_now(void)
+{
+ struct timespec now_tp;
+ OSMO_ASSERT(clock_gettime(CLOCK_MONOTONIC, &now_tp) >= 0);
+ return now_tp.tv_sec;
+}
+
+/* Remove a gtphub_peer from its list and free it. */
+static void gtphub_peer_del(struct gtphub_peer *peer)
+{
+ OSMO_ASSERT(llist_empty(&peer->addresses));
+ nr_map_clear(&peer->seq_map);
+ llist_del(&peer->entry);
+ talloc_free(peer);
+}
+
+static void gtphub_peer_addr_del(struct gtphub_peer_addr *pa)
+{
+ OSMO_ASSERT(llist_empty(&pa->ports));
+ llist_del(&pa->entry);
+ talloc_free(pa);
+}
+
+static void gtphub_peer_port_del(struct gtphub_peer_port *pp)
+{
+ OSMO_ASSERT(pp->ref_count == 0);
+ llist_del(&pp->entry);
+ rate_ctr_group_free(pp->counters_io);
+ talloc_free(pp);
+}
+
+/* From the information in the gtp_packet_desc, return the address of a GGSN.
+ * Return -1 on error. */
+static int gtphub_resolve_ggsn(struct gtphub *hub,
+ struct gtp_packet_desc *p,
+ struct gtphub_peer_port **pp);
+
+/* See gtphub_ext.c (wrapped by unit test) */
+struct gtphub_peer_port *gtphub_resolve_ggsn_addr(struct gtphub *hub,
+ const char *imsi_str,
+ const char *apn_ni_str);
+int gtphub_ares_init(struct gtphub *hub);
+
+static void gtphub_zero(struct gtphub *hub)
+{
+ ZERO_STRUCT(hub);
+ INIT_LLIST_HEAD(&hub->ggsn_lookups);
+ INIT_LLIST_HEAD(&hub->resolved_ggsns);
+}
+
+static int gtphub_sock_init(struct osmo_fd *ofd,
+ const struct gtphub_cfg_addr *addr,
+ osmo_fd_cb_t cb,
+ void *data,
+ int ofd_id)
+{
+ if (!addr->addr_str) {
+ LOG(LOGL_FATAL, "Cannot bind: empty address.\n");
+ return -1;
+ }
+ if (!addr->port) {
+ LOG(LOGL_FATAL, "Cannot bind: zero port not permitted.\n");
+ return -1;
+ }
+
+ ofd->when = BSC_FD_READ;
+ ofd->cb = cb;
+ ofd->data = data;
+ ofd->priv_nr = ofd_id;
+
+ int rc;
+ rc = osmo_sock_init_ofd(ofd,
+ AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
+ addr->addr_str, addr->port,
+ OSMO_SOCK_F_BIND);
+ if (rc < 1) {
+ LOG(LOGL_FATAL, "Cannot bind to %s port %d (rc %d)\n",
+ addr->addr_str, (int)addr->port, rc);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void gtphub_sock_close(struct osmo_fd *ofd)
+{
+ close(ofd->fd);
+ osmo_fd_unregister(ofd);
+ ofd->cb = NULL;
+}
+
+static void gtphub_bind_init(struct gtphub_bind *b)
+{
+ ZERO_STRUCT(b);
+
+ INIT_LLIST_HEAD(&b->peers);
+
+ b->counters_io = rate_ctr_group_alloc(osmo_gtphub_ctx,
+ &gtphub_ctrg_io_desc, 0);
+ OSMO_ASSERT(b->counters_io);
+}
+
+static int gtphub_bind_start(struct gtphub_bind *b,
+ const struct gtphub_cfg_bind *cfg,
+ osmo_fd_cb_t cb, void *cb_data,
+ unsigned int ofd_id)
+{
+ LOG(LOGL_DEBUG, "Starting bind %s\n", b->label);
+ if (gsn_addr_from_str(&b->local_addr, cfg->bind.addr_str) != 0) {
+ LOG(LOGL_FATAL, "Invalid bind address for %s: %s\n",
+ b->label, cfg->bind.addr_str);
+ return -1;
+ }
+ if (gtphub_sock_init(&b->ofd, &cfg->bind, cb, cb_data, ofd_id) != 0) {
+ LOG(LOGL_FATAL, "Cannot bind for %s: %s\n",
+ b->label, cfg->bind.addr_str);
+ return -1;
+ }
+ b->local_port = cfg->bind.port;
+ return 0;
+}
+
+static void gtphub_bind_free(struct gtphub_bind *b)
+{
+ OSMO_ASSERT(llist_empty(&b->peers));
+ rate_ctr_group_free(b->counters_io);
+}
+
+static void gtphub_bind_stop(struct gtphub_bind *b) {
+ gtphub_sock_close(&b->ofd);
+ gtphub_bind_free(b);
+}
+
+/* Recv datagram from from->fd, write sender's address to *from_addr.
+ * Return the number of bytes read, zero on error. */
+static int gtphub_read(const struct osmo_fd *from,
+ struct osmo_sockaddr *from_addr,
+ uint8_t *buf, size_t buf_len)
+{
+ OSMO_ASSERT(from_addr);
+
+ /* recvfrom requires the available length set in *from_addr_len. */
+ from_addr->l = sizeof(from_addr->a);
+ errno = 0;
+ ssize_t received = recvfrom(from->fd, buf, buf_len, 0,
+ (struct sockaddr*)&from_addr->a,
+ &from_addr->l);
+ /* TODO use recvmsg and get a MSG_TRUNC flag to make sure the message
+ * is not truncated. Then maybe reduce buf's size. */
+
+ if (received <= 0) {
+ LOG((errno == EAGAIN? LOGL_DEBUG : LOGL_ERROR),
+ "error: %s\n", strerror(errno));
+ return 0;
+ }
+
+ LOG(LOGL_DEBUG, "Received %d bytes from %s: %s%s\n",
+ (int)received, osmo_sockaddr_to_str(from_addr),
+ osmo_hexdump(buf, received > 1000? 1000 : received),
+ received > 1000 ? "..." : "");
+
+ return received;
+}
+
+static inline void gtphub_port_ref_count_inc(struct gtphub_peer_port *pp)
+{
+ OSMO_ASSERT(pp);
+ OSMO_ASSERT(pp->ref_count < UINT_MAX);
+ pp->ref_count++;
+}
+
+static inline void gtphub_port_ref_count_dec(struct gtphub_peer_port *pp)
+{
+ OSMO_ASSERT(pp);
+ OSMO_ASSERT(pp->ref_count > 0);
+ pp->ref_count--;
+}
+
+static inline void set_seq(struct gtp_packet_desc *p, uint16_t seq)
+{
+ OSMO_ASSERT(p->version == 1);
+ p->data->gtp1l.h.seq = hton16(seq);
+ p->seq = seq;
+}
+
+static inline void set_tei(struct gtp_packet_desc *p, uint32_t tei)
+{
+ OSMO_ASSERT(p->version == 1);
+ p->data->gtp1l.h.tei = hton32(tei);
+ p->header_tei = tei;
+}
+
+static void gtphub_mapping_del_cb(struct expiring_item *expi);
+
+static struct nr_mapping *gtphub_mapping_new()
+{
+ struct nr_mapping *nrm;
+ nrm = talloc_zero(osmo_gtphub_ctx, struct nr_mapping);
+ OSMO_ASSERT(nrm);
+
+ nr_mapping_init(nrm);
+ nrm->expiry_entry.del_cb = gtphub_mapping_del_cb;
+ return nrm;
+}
+
+
+#define APPEND(args...) \
+ l = snprintf(pos, left, args); \
+ pos += l; \
+ left -= l
+
+static const char *gtphub_tunnel_side_str(struct gtphub_tunnel *tun,
+ int side_idx)
+{
+ static char buf[256];
+ char *pos = buf;
+ int left = sizeof(buf);
+ int l;
+
+ struct gtphub_tunnel_endpoint *c, *u;
+ c = &tun->endpoint[side_idx][GTPH_PLANE_CTRL];
+ u = &tun->endpoint[side_idx][GTPH_PLANE_USER];
+
+ /* print both only if they differ. */
+ if (!c->peer) {
+ APPEND("(uninitialized)");
+ } else {
+ APPEND("%s", gsn_addr_to_str(&c->peer->peer_addr->addr));
+ }
+
+ if (!u->peer) {
+ if (c->peer) {
+ APPEND("/(uninitialized)");
+ }
+ } else if ((!c->peer)
+ || (!gsn_addr_same(&u->peer->peer_addr->addr,
+ &c->peer->peer_addr->addr))) {
+ APPEND("/%s", gsn_addr_to_str(&u->peer->peer_addr->addr));
+ }
+
+ APPEND(" (TEI C=%x U=%x)",
+ c->tei_orig,
+ u->tei_orig);
+ return buf;
+}
+
+const char *gtphub_tunnel_str(struct gtphub_tunnel *tun)
+{
+ static char buf[512];
+ char *pos = buf;
+ int left = sizeof(buf);
+ int l;
+
+ if (!tun)
+ return "null-tunnel";
+
+ APPEND("TEI=%x: ", tun->tei_repl);
+ APPEND("%s", gtphub_tunnel_side_str(tun, GTPH_SIDE_SGSN));
+ APPEND(" <-> %s", gtphub_tunnel_side_str(tun, GTPH_SIDE_GGSN));
+
+ return buf;
+}
+
+#undef APPEND
+
+void gtphub_tunnel_endpoint_set_peer(struct gtphub_tunnel_endpoint *te,
+ struct gtphub_peer_port *pp)
+{
+ if (te->peer)
+ gtphub_port_ref_count_dec(te->peer);
+ te->peer = pp;
+ if (te->peer)
+ gtphub_port_ref_count_inc(te->peer);
+}
+
+int gtphub_tunnel_complete(struct gtphub_tunnel *tun)
+{
+ if (!tun)
+ return 0;
+ if (!tun->tei_repl)
+ return 0;
+ int side_idx;
+ int plane_idx;
+ for_each_side_and_plane(side_idx, plane_idx) {
+ struct gtphub_tunnel_endpoint *te =
+ &tun->endpoint[side_idx][plane_idx];
+ if (!(te->peer && te->tei_orig))
+ return 0;
+ }
+ return 1;
+}
+
+static void gtphub_tunnel_del_cb(struct expiring_item *expi)
+{
+ struct gtphub_tunnel *tun = container_of(expi,
+ struct gtphub_tunnel,
+ expiry_entry);
+ LOG(LOGL_DEBUG, "expired: %s\n", gtphub_tunnel_str(tun));
+
+ llist_del(&tun->entry);
+ INIT_LLIST_HEAD(&tun->entry); /* mark unused */
+
+ expi->del_cb = 0; /* avoid recursion loops */
+ expiring_item_del(&tun->expiry_entry); /* usually already done, but make sure. */
+
+ int side_idx;
+ int plane_idx;
+ for_each_side_and_plane(side_idx, plane_idx) {
+ struct gtphub_tunnel_endpoint *te = &tun->endpoint[side_idx][plane_idx];
+
+ /* clear ref count */
+ gtphub_tunnel_endpoint_set_peer(te, NULL);
+
+ rate_ctr_group_free(te->counters_io);
+ }
+
+ talloc_free(tun);
+}
+
+static struct gtphub_tunnel *gtphub_tunnel_new()
+{
+ struct gtphub_tunnel *tun;
+ tun = talloc_zero(osmo_gtphub_ctx, struct gtphub_tunnel);
+ OSMO_ASSERT(tun);
+
+ INIT_LLIST_HEAD(&tun->entry);
+ expiring_item_init(&tun->expiry_entry);
+
+ int side_idx, plane_idx;
+ for_each_side_and_plane(side_idx, plane_idx) {
+ struct gtphub_tunnel_endpoint *te = &tun->endpoint[side_idx][plane_idx];
+ te->counters_io = rate_ctr_group_alloc(osmo_gtphub_ctx,
+ &gtphub_ctrg_io_desc,
+ 0);
+ OSMO_ASSERT(te->counters_io);
+ }
+
+ tun->expiry_entry.del_cb = gtphub_tunnel_del_cb;
+ return tun;
+}
+
+static const char *gtphub_peer_strb(struct gtphub_peer *peer, char *buf,
+ int buflen)
+{
+ if (llist_empty(&peer->addresses))
+ return "(addressless)";
+
+ struct gtphub_peer_addr *a = llist_first(&peer->addresses,
+ struct gtphub_peer_addr,
+ entry);
+ return gsn_addr_to_strb(&a->addr, buf, buflen);
+}
+
+static const char *gtphub_port_strb(struct gtphub_peer_port *port, char *buf,
+ int buflen)
+{
+ if (!port)
+ return "(null port)";
+
+ snprintf(buf, buflen, "%s port %d",
+ gsn_addr_to_str(&port->peer_addr->addr),
+ (int)port->port);
+ return buf;
+}
+
+const char *gtphub_peer_str(struct gtphub_peer *peer)
+{
+ static char buf[256];
+ return gtphub_peer_strb(peer, buf, sizeof(buf));
+}
+
+const char *gtphub_port_str(struct gtphub_peer_port *port)
+{
+ static char buf[256];
+ return gtphub_port_strb(port, buf, sizeof(buf));
+}
+
+static const char *gtphub_port_str2(struct gtphub_peer_port *port)
+{
+ static char buf[256];
+ return gtphub_port_strb(port, buf, sizeof(buf));
+}
+
+static void gtphub_mapping_del_cb(struct expiring_item *expi)
+{
+ expi->del_cb = 0; /* avoid recursion loops */
+ expiring_item_del(expi); /* usually already done, but make sure. */
+
+ struct nr_mapping *nrm = container_of(expi,
+ struct nr_mapping,
+ expiry_entry);
+ llist_del(&nrm->entry);
+ INIT_LLIST_HEAD(&nrm->entry); /* mark unused */
+
+ /* Just for log */
+ struct gtphub_peer_port *from = nrm->origin;
+ OSMO_ASSERT(from);
+ LOG(LOGL_DEBUG, "expired: %d: nr mapping from %s: %u->%u\n",
+ (int)nrm->expiry_entry.expiry,
+ gtphub_port_str(from),
+ (unsigned int)nrm->orig, (unsigned int)nrm->repl);
+
+ gtphub_port_ref_count_dec(from);
+
+ talloc_free(nrm);
+}
+
+static struct nr_mapping *gtphub_mapping_have(struct nr_map *map,
+ struct gtphub_peer_port *from,
+ nr_t orig_nr,
+ time_t now)
+{
+ struct nr_mapping *nrm;
+
+ nrm = nr_map_get(map, from, orig_nr);
+
+ if (!nrm) {
+ nrm = gtphub_mapping_new();
+ nrm->orig = orig_nr;
+ nrm->origin = from;
+ nr_map_add(map, nrm, now);
+ gtphub_port_ref_count_inc(from);
+ LOG(LOGL_DEBUG, "peer %s: sequence map %d --> %d\n",
+ gtphub_port_str(from),
+ (int)(nrm->orig), (int)(nrm->repl));
+ } else {
+ nr_map_refresh(map, nrm, now);
+ }
+
+ OSMO_ASSERT(nrm);
+ return nrm;
+}
+
+static void gtphub_map_seq(struct gtp_packet_desc *p,
+ struct gtphub_peer_port *from_port,
+ struct gtphub_peer_port *to_port)
+{
+ /* Store a mapping in to_peer's map, so when we later receive a GTP
+ * packet back from to_peer, the seq nr can be unmapped back to its
+ * origin (from_peer here). */
+ struct nr_mapping *nrm;
+ nrm = gtphub_mapping_have(&to_port->peer_addr->peer->seq_map,
+ from_port, p->seq, p->timestamp);
+
+ /* Change the GTP packet to yield the new, mapped seq nr */
+ set_seq(p, nrm->repl);
+}
+
+static struct gtphub_peer_port *gtphub_unmap_seq(struct gtp_packet_desc *p,
+ struct gtphub_peer_port *responding_port)
+{
+ OSMO_ASSERT(p->version == 1);
+ struct nr_mapping *nrm =
+ nr_map_get_inv(&responding_port->peer_addr->peer->seq_map,
+ p->seq);
+ if (!nrm)
+ return NULL;
+ LOG(LOGL_DEBUG, "peer %p: sequence unmap %d <-- %d\n",
+ nrm->origin, (int)(nrm->orig), (int)(nrm->repl));
+ set_seq(p, nrm->orig);
+ return nrm->origin;
+}
+
+static int gtphub_check_mapped_tei(struct gtphub_tunnel *new_tun,
+ struct gtphub_tunnel *iterated_tun,
+ uint32_t *tei_min,
+ uint32_t *tei_max)
+{
+ if (!new_tun->tei_repl || !iterated_tun->tei_repl)
+ return 1;
+
+ *tei_min = (*tei_min < iterated_tun->tei_repl)? *tei_min : iterated_tun->tei_repl;
+ *tei_max = (*tei_max > iterated_tun->tei_repl)? *tei_max : iterated_tun->tei_repl;
+
+ if (new_tun->tei_repl != iterated_tun->tei_repl)
+ return 1;
+
+ /* new_tun->tei_repl is already taken. Try to find one out of the known
+ * range. */
+ LOG(LOGL_DEBUG, "TEI replacement %d already taken.\n", new_tun->tei_repl);
+
+ if ((*tei_max) < 0xffffffff) {
+ (*tei_max)++;
+ new_tun->tei_repl = *tei_max;
+ LOG(LOGL_DEBUG, "Using TEI %d instead.\n", new_tun->tei_repl);
+ return 1;
+ } else if ((*tei_min) > 1) {
+ (*tei_min)--;
+ new_tun->tei_repl = *tei_min;
+ LOG(LOGL_DEBUG, "Using TEI %d instead.\n", new_tun->tei_repl);
+ return 1;
+ }
+
+ /* None seems to be available. */
+ return 0;
+}
+
+static int gtphub_check_reused_teis(struct gtphub *hub,
+ struct gtphub_tunnel *new_tun)
+{
+ uint32_t tei_min = 0xffffffff;
+ uint32_t tei_max = 0;
+ int side_idx;
+ int plane_idx;
+ struct gtphub_tunnel_endpoint *te;
+ struct gtphub_tunnel_endpoint *te2;
+
+ struct gtphub_tunnel *tun, *ntun;
+
+ llist_for_each_entry_safe(tun, ntun, &hub->tunnels, entry) {
+ if (tun == new_tun)
+ continue;
+
+ /* Check whether the GSN sent a TEI that it is reusing from a
+ * previous tunnel. */
+ int tun_continue = 0;
+ for_each_side(side_idx) {
+ for_each_plane(plane_idx) {
+ te = &tun->endpoint[side_idx][plane_idx];
+ te2 = &new_tun->endpoint[side_idx][plane_idx];
+ if ((te->tei_orig == 0)
+ || (te->tei_orig != te2->tei_orig)
+ || (!te->peer)
+ || (!te2->peer)
+ || !gsn_addr_same(&te->peer->peer_addr->addr,
+ &te2->peer->peer_addr->addr))
+ continue;
+
+ /* The peer is reusing a TEI that I believe to
+ * be part of another tunnel. The other tunnel
+ * must be stale, then. */
+ LOG(LOGL_NOTICE,
+ "Expiring tunnel due to reused TEI:"
+ " %s peer %s sent %s TEI %x,"
+ " previously used by tunnel %s...\n",
+ gtphub_side_idx_names[side_idx],
+ gtphub_port_str(te->peer),
+ gtphub_plane_idx_names[plane_idx],
+ te->tei_orig,
+ gtphub_tunnel_str(tun));
+ LOG(LOGL_NOTICE, "...while establishing tunnel %s\n",
+ gtphub_tunnel_str(new_tun));
+
+ expiring_item_del(&tun->expiry_entry);
+ /* continue to find more matches. There shouldn't be
+ * any, but let's make sure. However, tun is deleted,
+ * so we need to skip to the next tunnel. */
+ tun_continue = 1;
+ break;
+ }
+ if (tun_continue)
+ break;
+ }
+ if (tun_continue)
+ continue;
+
+ /* Check whether the mapped TEI is already used by another
+ * tunnel. */
+ if (!gtphub_check_mapped_tei(new_tun, tun, &tei_min, &tei_max)) {
+ LOG(LOGL_ERROR,
+ "No mapped TEI is readily available."
+ " Searching for holes between occupied"
+ " TEIs not implemented.");
+ return 0;
+ }
+
+ }
+
+ return 1;
+}
+
+static void gtphub_tunnel_refresh(struct gtphub *hub,
+ struct gtphub_tunnel *tun,
+ time_t now)
+{
+ expiry_add(&hub->expire_slowly,
+ &tun->expiry_entry,
+ now);
+}
+
+static struct gtphub_tunnel_endpoint *gtphub_unmap_tei(struct gtphub *hub,
+ struct gtp_packet_desc *p,
+ struct gtphub_peer_port *from,
+ struct gtphub_tunnel **unmapped_from_tun)
+{
+ OSMO_ASSERT(from);
+ int other_side = other_side_idx(p->side_idx);
+
+ struct gtphub_tunnel *tun;
+ llist_for_each_entry(tun, &hub->tunnels, entry) {
+ struct gtphub_tunnel_endpoint *te_from =
+ &tun->endpoint[p->side_idx][p->plane_idx];
+ struct gtphub_tunnel_endpoint *te_to =
+ &tun->endpoint[other_side][p->plane_idx];
+ if ((tun->tei_repl == p->header_tei_rx)
+ && te_from->peer
+ && gsn_addr_same(&te_from->peer->peer_addr->addr,
+ &from->peer_addr->addr)) {
+ gtphub_tunnel_refresh(hub, tun, p->timestamp);
+ if (unmapped_from_tun)
+ *unmapped_from_tun = tun;
+ return te_to;
+ }
+ }
+
+ if (unmapped_from_tun)
+ *unmapped_from_tun = NULL;
+ return NULL;
+}
+
+static void gtphub_map_restart_counter(struct gtphub *hub,
+ struct gtp_packet_desc *p)
+{
+ if (p->rc != GTP_RC_PDU_C)
+ return;
+
+ int ie_idx;
+ ie_idx = gtpie_getie(p->ie, GTPIE_RECOVERY, 0);
+ if (ie_idx < 0)
+ return;
+
+ /* Always send gtphub's own restart counter */
+ p->ie[ie_idx]->tv1.v = hton8(hub->restart_counter);
+}
+
+static int gtphub_unmap_header_tei(struct gtphub_peer_port **to_port_p,
+ struct gtphub_tunnel **unmapped_from_tun,
+ struct gtphub *hub,
+ struct gtp_packet_desc *p,
+ struct gtphub_peer_port *from_port)
+{
+ OSMO_ASSERT(p->version == 1);
+ *to_port_p = NULL;
+ if (unmapped_from_tun)
+ *unmapped_from_tun = NULL;
+
+ /* If the header's TEI is zero, no PDP context has been established
+ * yet. If nonzero, a mapping should actually already exist for this
+ * TEI, since it must have been announced in a PDP context creation. */
+ if (!p->header_tei_rx)
+ return 0;
+
+ /* to_peer has previously announced a TEI, which was stored and
+ * mapped in a tunnel struct. */
+ struct gtphub_tunnel_endpoint *to;
+ to = gtphub_unmap_tei(hub, p, from_port, unmapped_from_tun);
+ if (!to) {
+ LOG(LOGL_ERROR, "Received unknown TEI %" PRIx32 " from %s\n",
+ p->header_tei_rx, gtphub_port_str(from_port));
+ return -1;
+ }
+
+ if (unmapped_from_tun) {
+ OSMO_ASSERT(*unmapped_from_tun);
+ LOG(LOGL_DEBUG, "Unmapped TEI coming from: %s\n",
+ gtphub_tunnel_str(*unmapped_from_tun));
+ }
+
+ uint32_t unmapped_tei = to->tei_orig;
+ set_tei(p, unmapped_tei);
+
+ /* May be NULL for an invalidated tunnel. */
+ *to_port_p = to->peer;
+
+ return 0;
+}
+
+static int gtphub_handle_create_pdp_ctx(struct gtphub *hub,
+ struct gtp_packet_desc *p,
+ struct gtphub_peer_port *from_ctrl,
+ struct gtphub_peer_port *to_ctrl)
+{
+ int plane_idx;
+
+ osmo_static_assert((GTPH_PLANE_CTRL == 0) && (GTPH_PLANE_USER == 1),
+ plane_nrs_match_GSN_addr_IE_indices);
+
+ struct gtphub_tunnel *tun = p->tun;
+
+ if (p->type == GTP_CREATE_PDP_REQ) {
+ if (p->side_idx != GTPH_SIDE_SGSN) {
+ LOG(LOGL_ERROR, "Wrong side: Create PDP Context"
+ " Request from the GGSN side: %s",
+ gtphub_port_str(from_ctrl));
+ return -1;
+ }
+
+ if (tun) {
+ LOG(LOGL_ERROR, "Not implemented: Received"
+ " Create PDP Context Request for an already"
+ " established tunnel:"
+ " from %s, tunnel %s\n",
+ gtphub_port_str(from_ctrl),
+ gtphub_tunnel_str(p->tun));
+ return -1;
+ }
+
+ /* A new tunnel. */
+ p->tun = tun = gtphub_tunnel_new();
+
+ /* Create TEI mapping */
+ tun->tei_repl = nr_pool_next(&hub->tei_pool);
+
+ llist_add(&tun->entry, &hub->tunnels);
+ gtphub_tunnel_refresh(hub, tun, p->timestamp);
+ /* The endpoint peers on this side (SGSN) will be set from IEs
+ * below. Also set the GGSN Ctrl endpoint, for logging. */
+ gtphub_tunnel_endpoint_set_peer(&tun->endpoint[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL],
+ to_ctrl);
+ } else if (p->type == GTP_CREATE_PDP_RSP) {
+ if (p->side_idx != GTPH_SIDE_GGSN) {
+ LOG(LOGL_ERROR, "Wrong side: Create PDP Context"
+ " Response from the SGSN side: %s",
+ gtphub_port_str(from_ctrl));
+ return -1;
+ }
+
+ /* The tunnel should already have been resolved from the header
+ * TEI and be available in tun (== p->tun). Just fill in the
+ * GSN Addresses below.*/
+ OSMO_ASSERT(tun);
+ OSMO_ASSERT(tun->tei_repl == p->header_tei_rx);
+ OSMO_ASSERT(to_ctrl);
+ }
+
+ uint8_t ie_type[] = { GTPIE_TEI_C, GTPIE_TEI_DI };
+ int ie_mandatory = (p->type == GTP_CREATE_PDP_REQ);
+ unsigned int side_idx = p->side_idx;
+
+ for (plane_idx = 0; plane_idx < 2; plane_idx++) {
+ int rc;
+ struct gsn_addr use_addr;
+ uint16_t use_port;
+ uint32_t tei_from_ie;
+ int ie_idx;
+
+ /* Fetch GSN Address and TEI from IEs. As ensured by above
+ * static asserts, plane_idx corresponds to the GSN Address IE
+ * index (the first one = 0 = ctrl, second one = 1 = user). */
+ rc = gsn_addr_get(&use_addr, p, plane_idx);
+ if (rc) {
+ LOG(LOGL_ERROR, "Cannot read %s GSN Address IE\n",
+ gtphub_plane_idx_names[plane_idx]);
+ return -1;
+ }
+ LOG(LOGL_DEBUG, "Read %s GSN addr %s (%d)\n",
+ gtphub_plane_idx_names[plane_idx],
+ gsn_addr_to_str(&use_addr),
+ use_addr.len);
+
+ ie_idx = gtpie_getie(p->ie, ie_type[plane_idx], 0);
+ if (ie_idx < 0) {
+ if (ie_mandatory) {
+ LOG(LOGL_ERROR,
+ "Create PDP Context message invalid:"
+ " missing IE %d\n",
+ (int)ie_type[plane_idx]);
+ return -1;
+ }
+ tei_from_ie = 0;
+ }
+ else
+ tei_from_ie = ntoh32(p->ie[ie_idx]->tv4.v);
+
+ /* Make sure an entry for this peer address with default port
+ * exists.
+ *
+ * Exception: if sgsn_use_sender is set, instead use the
+ * sender's address and port for Ctrl -- the User port is not
+ * known until the first User packet arrives.
+ *
+ * Note: doing this here is just an optimization, because
+ * gtphub_handle_buf() has code to replace the tunnel
+ * endpoints' addresses with the sender (needed for User
+ * plane). We could just ignore sgsn_use_sender here. But if we
+ * set up a default port here and replace it in
+ * gtphub_handle_buf(), we'd be creating a peer port just to
+ * expire it right away. */
+ if (hub->sgsn_use_sender && (side_idx == GTPH_SIDE_SGSN)) {
+ gsn_addr_from_sockaddr(&use_addr, &use_port, &from_ctrl->sa);
+ } else {
+ use_port = gtphub_plane_idx_default_port[plane_idx];
+
+ }
+
+ struct gtphub_peer_port *peer_from_ie;
+ peer_from_ie = gtphub_port_have(hub,
+ &hub->to_gsns[side_idx][plane_idx],
+ &use_addr, use_port);
+
+ gtphub_tunnel_endpoint_set_peer(&tun->endpoint[side_idx][plane_idx],
+ peer_from_ie);
+
+ if (!tei_from_ie &&
+ !tun->endpoint[side_idx][plane_idx].tei_orig) {
+ LOG(LOGL_ERROR,
+ "Create PDP Context message omits %s TEI, but"
+ " no TEI has been announced for this tunnel: %s\n",
+ gtphub_plane_idx_names[plane_idx],
+ gtphub_tunnel_str(tun));
+ return -1;
+ }
+
+ if (tei_from_ie) {
+ /* Replace TEI in GTP packet IE */
+ tun->endpoint[side_idx][plane_idx].tei_orig = tei_from_ie;
+ p->ie[ie_idx]->tv4.v = hton32(tun->tei_repl);
+
+ if (!gtphub_check_reused_teis(hub, tun)) {
+ /* It's highly unlikely that all TEIs are
+ * taken. But the code looking for an unused
+ * TEI is, at the time of writing this comment,
+ * not able to find gaps in the TEI space. To
+ * explicitly alert the user of this problem,
+ * rather abort than carry on. */
+ LOG(LOGL_FATAL, "TEI range exhausted. Cannot create TEI mapping, aborting.\n");
+ abort();
+ }
+ }
+
+ /* Replace the GSN address to reflect gtphub. */
+ rc = gsn_addr_put(&hub->to_gsns[other_side_idx(side_idx)][plane_idx].local_addr,
+ p, plane_idx);
+ if (rc) {
+ LOG(LOGL_ERROR, "Cannot write %s GSN Address IE\n",
+ gtphub_plane_idx_names[plane_idx]);
+ return -1;
+ }
+ }
+
+ if (p->type == GTP_CREATE_PDP_REQ) {
+ LOG(LOGL_DEBUG, "New tunnel, first half: %s\n",
+ gtphub_tunnel_str(tun));
+ } else if (p->type == GTP_CREATE_PDP_RSP) {
+ LOG(LOGL_DEBUG, "New tunnel: %s\n",
+ gtphub_tunnel_str(tun));
+ }
+
+ return 0;
+}
+
+static void pending_delete_del_cb(struct expiring_item *expi)
+{
+ struct pending_delete *pd;
+ pd = container_of(expi, struct pending_delete, expiry_entry);
+
+ llist_del(&pd->entry);
+ INIT_LLIST_HEAD(&pd->entry);
+
+ pd->expiry_entry.del_cb = 0;
+ expiring_item_del(&pd->expiry_entry);
+
+ talloc_free(pd);
+}
+
+static struct pending_delete *pending_delete_new(void)
+{
+ struct pending_delete *pd = talloc_zero(osmo_gtphub_ctx, struct pending_delete);
+ INIT_LLIST_HEAD(&pd->entry);
+ expiring_item_init(&pd->expiry_entry);
+ pd->expiry_entry.del_cb = pending_delete_del_cb;
+ return pd;
+}
+
+static int gtphub_handle_delete_pdp_ctx(struct gtphub *hub,
+ struct gtp_packet_desc *p,
+ struct gtphub_peer_port *from_ctrl,
+ struct gtphub_peer_port *to_ctrl)
+{
+ struct gtphub_tunnel *known_tun = p->tun;
+
+ if (p->type == GTP_DELETE_PDP_REQ) {
+ if (!known_tun) {
+ LOG(LOGL_ERROR, "Cannot find tunnel for Delete PDP Context Request.\n");
+ return -1;
+ }
+
+ /* Store the Delete Request until a successful Response is seen. */
+ uint8_t teardown_ind;
+ uint8_t nsapi;
+
+ if (gtpie_gettv1(p->ie, GTPIE_TEARDOWN, 0, &teardown_ind) != 0) {
+ LOG(LOGL_ERROR, "Missing Teardown Ind IE in Delete PDP Context Request.\n");
+ return -1;
+ }
+
+ if (gtpie_gettv1(p->ie, GTPIE_NSAPI, 0, &nsapi) != 0) {
+ LOG(LOGL_ERROR, "Missing NSAPI IE in Delete PDP Context Request.\n");
+ return -1;
+ }
+
+ struct pending_delete *pd = NULL;
+
+ struct pending_delete *pdi = NULL;
+ llist_for_each_entry(pdi, &hub->pending_deletes, entry) {
+ if ((pdi->tun == known_tun)
+ && (pdi->teardown_ind == teardown_ind)
+ && (pdi->nsapi == nsapi)) {
+ pd = pdi;
+ break;
+ }
+ }
+
+ if (!pd) {
+ pd = pending_delete_new();
+ pd->tun = known_tun;
+ pd->teardown_ind = teardown_ind;
+ pd->nsapi = nsapi;
+
+ LOG(LOGL_DEBUG, "Tunnel delete pending: %s\n",
+ gtphub_tunnel_str(known_tun));
+ llist_add(&pd->entry, &hub->pending_deletes);
+ }
+
+ /* Add or refresh timeout. */
+ expiry_add(&hub->expire_quickly, &pd->expiry_entry, p->timestamp);
+
+ /* If a pending_delete should expire before the response to
+ * indicate success comes in, the responding peer will have the
+ * tunnel deactivated, while the requesting peer gets no reply
+ * and keeps the tunnel. The hope is that the requesting peer
+ * will try again and get a useful response. */
+ } else if (p->type == GTP_DELETE_PDP_RSP) {
+ /* Find the Delete Request for this Response. */
+ struct pending_delete *pd = NULL;
+
+ struct pending_delete *pdi;
+ llist_for_each_entry(pdi, &hub->pending_deletes, entry) {
+ if (known_tun == pdi->tun) {
+ pd = pdi;
+ break;
+ }
+ }
+
+ if (!pd) {
+ LOG(LOGL_ERROR, "Delete PDP Context Response:"
+ " Cannot find matching request.");
+ /* If we delete the tunnel now, anyone can send a
+ * Delete response to kill tunnels at will. */
+ return -1;
+ }
+
+ /* TODO handle teardown_ind and nsapi */
+
+ expiring_item_del(&pd->expiry_entry);
+
+ uint8_t cause;
+ if (gtpie_gettv1(p->ie, GTPIE_CAUSE, 0, &cause) != 0) {
+ LOG(LOGL_ERROR, "Delete PDP Context Response:"
+ " Missing Cause IE.");
+ /* If we delete the tunnel now, at least one of the
+ * peers may still think it is active. */
+ return -1;
+ }
+
+ if (cause != GTPCAUSE_ACC_REQ) {
+ LOG(LOGL_NOTICE,
+ "Delete PDP Context Response indicates failure;"
+ "for %s\n",
+ gtphub_tunnel_str(known_tun));
+ return -1;
+ }
+
+ LOG(LOGL_DEBUG, "Delete PDP Context: removing tunnel %s\n",
+ gtphub_tunnel_str(known_tun));
+ p->tun = NULL;
+ expiring_item_del(&known_tun->expiry_entry);
+ }
+
+ return 0;
+}
+
+static int gtphub_handle_update_pdp_ctx(struct gtphub *hub,
+ struct gtp_packet_desc *p,
+ struct gtphub_peer_port *from_ctrl,
+ struct gtphub_peer_port *to_ctrl)
+{
+ /* TODO */
+ return 0;
+}
+
+/* Read GSN address IEs from p, and make sure these peer addresses exist in
+ * bind[plane_idx] with default ports, in their respective planes (both Ctrl
+ * and User). Map TEIs announced in IEs, and write mapped TEIs in-place into
+ * the packet p. */
+static int gtphub_handle_pdp_ctx(struct gtphub *hub,
+ struct gtp_packet_desc *p,
+ struct gtphub_peer_port *from_ctrl,
+ struct gtphub_peer_port *to_ctrl)
+{
+ OSMO_ASSERT(p->plane_idx == GTPH_PLANE_CTRL);
+
+ switch (p->type) {
+ case GTP_CREATE_PDP_REQ:
+ case GTP_CREATE_PDP_RSP:
+ return gtphub_handle_create_pdp_ctx(hub, p,
+ from_ctrl, to_ctrl);
+
+ case GTP_DELETE_PDP_REQ:
+ case GTP_DELETE_PDP_RSP:
+ return gtphub_handle_delete_pdp_ctx(hub, p,
+ from_ctrl, to_ctrl);
+
+ case GTP_UPDATE_PDP_REQ:
+ case GTP_UPDATE_PDP_RSP:
+ return gtphub_handle_update_pdp_ctx(hub, p,
+ from_ctrl, to_ctrl);
+
+ default:
+ /* Nothing to do for this message type. */
+ return 0;
+ }
+
+}
+
+static int gtphub_send_del_pdp_ctx(struct gtphub *hub,
+ struct gtphub_tunnel *tun,
+ int to_side)
+{
+ static uint8_t del_ctx_msg[16] = {
+ 0x32, /* GTP v1 flags */
+ GTP_DELETE_PDP_REQ,
+ 0x00, 0x08, /* Length in network byte order */
+ 0x00, 0x00, 0x00, 0x00, /* TEI to be replaced */
+ 0, 0, /* Seq, to be replaced */
+ 0, 0, /* no extensions */
+ 0x13, 0xff, /* 19: Teardown ind = 1 */
+ 0x14, 0 /* 20: NSAPI = 0 */
+ };
+
+ uint32_t *tei = (uint32_t*)&del_ctx_msg[4];
+ uint16_t *seq = (uint16_t*)&del_ctx_msg[8];
+
+ struct gtphub_tunnel_endpoint *te =
+ &tun->endpoint[to_side][GTPH_PLANE_CTRL];
+
+ if (! te->peer)
+ return 0;
+
+ *tei = hton32(te->tei_orig);
+ *seq = hton16(nr_pool_next(&te->peer->peer_addr->peer->seq_pool));
+
+ struct gtphub_bind *to_bind = &hub->to_gsns[to_side][GTPH_PLANE_CTRL];
+ int rc = gtphub_write(&to_bind->ofd, &te->peer->sa,
+ del_ctx_msg, sizeof(del_ctx_msg));
+ if (rc != 0) {
+ LOG(LOGL_ERROR,
+ "Failed to send out-of-band Delete PDP Context Request to %s\n",
+ gtphub_port_str(te->peer));
+ }
+ return rc;
+}
+
+/* Tell all peers on the other end of tunnels that PDP contexts are void. */
+static void gtphub_restarted(struct gtphub *hub,
+ struct gtp_packet_desc *p,
+ struct gtphub_peer_port *pp)
+{
+ LOG(LOGL_NOTICE, "Peer has restarted: %s\n",
+ gtphub_port_str(pp));
+
+ int deleted_count = 0;
+ struct gtphub_tunnel *tun;
+ llist_for_each_entry(tun, &hub->tunnels, entry) {
+ int side_idx;
+ for_each_side(side_idx) {
+ struct gtphub_tunnel_endpoint *te = &tun->endpoint[side_idx][GTPH_PLANE_CTRL];
+ struct gtphub_tunnel_endpoint *te2 = &tun->endpoint[other_side_idx(side_idx)][GTPH_PLANE_CTRL];
+ if ((!te->peer)
+ || (!te2->tei_orig)
+ || (pp->peer_addr->peer != te->peer->peer_addr->peer))
+ continue;
+
+ LOG(LOGL_DEBUG, "Deleting tunnel due to peer restart: %s\n",
+ gtphub_tunnel_str(tun));
+ deleted_count ++;
+
+ /* Send a Delete PDP Context Request to the
+ * peer on the other side, remember the pending
+ * delete and wait for the response to delete
+ * the tunnel. Clear this side of the tunnel to
+ * make sure it isn't used.
+ *
+ * Should the delete message send fail, or if no
+ * response is received, this tunnel will expire. If
+ * its TEIs come up in a new PDP Context Request, it
+ * will be removed. If messages for this tunnel should
+ * come in (from the not restarted side), they will be
+ * dropped because the tunnel is rendered unusable. */
+ gtphub_send_del_pdp_ctx(hub, tun, other_side_idx(side_idx));
+
+ gtphub_tunnel_endpoint_set_peer(&tun->endpoint[side_idx][GTPH_PLANE_CTRL],
+ NULL);
+ gtphub_tunnel_endpoint_set_peer(&tun->endpoint[side_idx][GTPH_PLANE_USER],
+ NULL);
+ }
+ }
+
+ if (deleted_count)
+ LOG(LOGL_NOTICE, "Deleting %d tunnels due to restart of: %s\n",
+ deleted_count,
+ gtphub_port_str(pp));
+}
+
+static int get_restart_count(struct gtp_packet_desc *p)
+{
+ int ie_idx;
+ ie_idx = gtpie_getie(p->ie, GTPIE_RECOVERY, 0);
+ if (ie_idx < 0)
+ return -1;
+ return ntoh8(p->ie[ie_idx]->tv1.v);
+}
+
+static void gtphub_check_restart_counter(struct gtphub *hub,
+ struct gtp_packet_desc *p,
+ struct gtphub_peer_port *from)
+{
+ /* If the peer is sending a Recovery IE (7.7.11) with a restart counter
+ * that doesn't match the peer's previously sent restart counter, clear
+ * that peer and cancel PDP contexts. */
+
+ int restart = get_restart_count(p);
+
+ if ((restart < 0) || (restart > 255))
+ return;
+
+ if ((from->last_restart_count >= 0) && (from->last_restart_count <= 255)) {
+ if (from->last_restart_count != restart) {
+ gtphub_restarted(hub, p, from);
+ }
+ }
+
+ from->last_restart_count = restart;
+}
+
+static int from_sgsns_read_cb(struct osmo_fd *from_sgsns_ofd, unsigned int what)
+{
+ unsigned int plane_idx = from_sgsns_ofd->priv_nr;
+ OSMO_ASSERT(plane_idx < GTPH_PLANE_N);
+ LOG(LOGL_DEBUG, "=== reading from SGSN (%s)\n",
+ gtphub_plane_idx_names[plane_idx]);
+
+ if (!(what & BSC_FD_READ))
+ return 0;
+
+ struct gtphub *hub = from_sgsns_ofd->data;
+
+ static uint8_t buf[4096];
+ struct osmo_sockaddr from_addr;
+ struct osmo_sockaddr to_addr;
+ struct osmo_fd *to_ofd;
+ int len;
+ uint8_t *reply_buf;
+
+ len = gtphub_read(from_sgsns_ofd, &from_addr, buf, sizeof(buf));
+ if (len < 1)
+ return 0;
+
+ len = gtphub_handle_buf(hub, GTPH_SIDE_SGSN, plane_idx, &from_addr,
+ buf, len, gtphub_now(),
+ &reply_buf, &to_ofd, &to_addr);
+ if (len < 1)
+ return 0;
+
+ return gtphub_write(to_ofd, &to_addr, reply_buf, len);
+}
+
+static int from_ggsns_read_cb(struct osmo_fd *from_ggsns_ofd, unsigned int what)
+{
+ unsigned int plane_idx = from_ggsns_ofd->priv_nr;
+ OSMO_ASSERT(plane_idx < GTPH_PLANE_N);
+ LOG(LOGL_DEBUG, "=== reading from GGSN (%s)\n",
+ gtphub_plane_idx_names[plane_idx]);
+ if (!(what & BSC_FD_READ))
+ return 0;
+
+ struct gtphub *hub = from_ggsns_ofd->data;
+
+ static uint8_t buf[4096];
+ struct osmo_sockaddr from_addr;
+ struct osmo_sockaddr to_addr;
+ struct osmo_fd *to_ofd;
+ int len;
+ uint8_t *reply_buf;
+
+ len = gtphub_read(from_ggsns_ofd, &from_addr, buf, sizeof(buf));
+ if (len < 1)
+ return 0;
+
+ len = gtphub_handle_buf(hub, GTPH_SIDE_GGSN, plane_idx, &from_addr,
+ buf, len, gtphub_now(),
+ &reply_buf, &to_ofd, &to_addr);
+ if (len < 1)
+ return 0;
+
+ return gtphub_write(to_ofd, &to_addr, reply_buf, len);
+}
+
+static int gtphub_unmap(struct gtphub *hub,
+ struct gtp_packet_desc *p,
+ struct gtphub_peer_port *from,
+ struct gtphub_peer_port *to_proxy,
+ struct gtphub_peer_port **final_unmapped,
+ struct gtphub_peer_port **unmapped_from_seq)
+{
+ /* Always (try to) unmap sequence and TEI numbers, which need to be
+ * replaced in the packet. Either way, give precedence to the proxy, if
+ * configured. */
+
+ if (unmapped_from_seq)
+ *unmapped_from_seq = NULL;
+ if (final_unmapped)
+ *final_unmapped = NULL;
+ p->tun = NULL;
+
+ struct gtphub_peer_port *from_seq = NULL;
+ struct gtphub_peer_port *from_tei = NULL;
+ struct gtphub_peer_port *unmapped = NULL;
+
+ from_seq = gtphub_unmap_seq(p, from);
+
+ if (gtphub_unmap_header_tei(&from_tei, &p->tun, hub, p, from) < 0)
+ return -1;
+
+ struct gtphub_peer *from_peer = from->peer_addr->peer;
+ if (from_seq && from_tei && (from_seq != from_tei)) {
+ LOG(LOGL_DEBUG,
+ "Seq unmap and TEI unmap yield two different peers."
+ " Using seq unmap."
+ " (from %s %s: seq %d yields %s, tei %u yields %s)\n",
+ gtphub_plane_idx_names[p->plane_idx],
+ gtphub_peer_str(from_peer),
+ (int)p->seq,
+ gtphub_port_str(from_seq),
+ (unsigned int)p->header_tei_rx,
+ gtphub_port_str2(from_tei)
+ );
+ }
+ unmapped = (from_seq? from_seq : from_tei);
+
+ if (unmapped && to_proxy && (unmapped != to_proxy)) {
+ LOG(LOGL_NOTICE,
+ "Unmap yields a different peer than the configured proxy."
+ " Using proxy."
+ " unmapped: %s proxy: %s\n",
+ gtphub_port_str(unmapped),
+ gtphub_port_str2(to_proxy)
+ );
+ }
+ unmapped = (to_proxy? to_proxy : unmapped);
+
+ if (!unmapped) {
+ /* Return no error, but returned pointers are all NULL. */
+ return 0;
+ }
+
+ if (unmapped_from_seq)
+ *unmapped_from_seq = from_seq;
+ if (final_unmapped)
+ *final_unmapped = unmapped;
+ return 0;
+}
+
+static int gsn_addr_to_sockaddr(struct gsn_addr *src,
+ uint16_t port,
+ struct osmo_sockaddr *dst)
+{
+ return osmo_sockaddr_init_udp(dst, gsn_addr_to_str(src), port);
+}
+
+/* If p is an Echo request, replace p's data with the matching response and
+ * return 1. If p is no Echo request, return 0, or -1 if an invalid packet is
+ * detected. */
+static int gtphub_handle_echo_req(struct gtphub *hub, struct gtp_packet_desc *p,
+ uint8_t **reply_buf)
+{
+ if (p->type != GTP_ECHO_REQ)
+ return 0;
+
+ static uint8_t echo_response_data[14] = {
+ 0x32, /* GTP v1 flags */
+ GTP_ECHO_RSP,
+ 0x00, 14 - 8, /* Length in network byte order */
+ 0x00, 0x00, 0x00, 0x00, /* Zero TEI */
+ 0, 0, /* Seq, to be replaced */
+ 0, 0, /* no extensions */
+ 0x0e, /* Recovery IE */
+ 0 /* Restart counter, to be replaced */
+ };
+ uint16_t *seq = (uint16_t*)&echo_response_data[8];
+ uint8_t *recovery = &echo_response_data[13];
+
+ *seq = hton16(p->seq);
+ *recovery = hub->restart_counter;
+
+ *reply_buf = echo_response_data;
+
+ return sizeof(echo_response_data);
+}
+
+struct gtphub_peer_port *gtphub_known_addr_have_port(const struct gtphub_bind *bind,
+ const struct osmo_sockaddr *addr);
+
+/* Parse buffer as GTP packet, replace elements in-place and return the ofd and
+ * address to forward to. Return a pointer to the osmo_fd, but copy the
+ * sockaddr to *to_addr. The reason for this is that the sockaddr may expire at
+ * any moment, while the osmo_fd is guaranteed to persist. Return the number of
+ * bytes to forward, 0 or less on failure. */
+int gtphub_handle_buf(struct gtphub *hub,
+ unsigned int side_idx,
+ unsigned int plane_idx,
+ const struct osmo_sockaddr *from_addr,
+ uint8_t *buf,
+ size_t received,
+ time_t now,
+ uint8_t **reply_buf,
+ struct osmo_fd **to_ofd,
+ struct osmo_sockaddr *to_addr)
+{
+ struct gtphub_bind *from_bind = &hub->to_gsns[side_idx][plane_idx];
+ struct gtphub_bind *to_bind = &hub->to_gsns[other_side_idx(side_idx)][plane_idx];
+
+ rate_ctr_add(&from_bind->counters_io->ctr[GTPH_CTR_BYTES_IN],
+ received);
+
+ struct gtp_packet_desc p;
+ gtp_decode(buf, received, side_idx, plane_idx, &p, now);
+
+ LOG(LOGL_DEBUG, "%s rx %s from %s %s%s\n",
+ (side_idx == GTPH_SIDE_GGSN)? "<-" : "->",
+ gtphub_plane_idx_names[plane_idx],
+ gtphub_side_idx_names[side_idx],
+ osmo_sockaddr_to_str(from_addr),
+ gtp_type_str(p.type));
+
+ if (p.rc <= 0) {
+ LOG(LOGL_ERROR, "INVALID: dropping GTP packet%s from %s %s %s\n",
+ gtp_type_str(p.type),
+ gtphub_side_idx_names[side_idx],
+ gtphub_plane_idx_names[plane_idx],
+ osmo_sockaddr_to_str(from_addr));
+ return -1;
+ }
+
+ rate_ctr_inc(&from_bind->counters_io->ctr[GTPH_CTR_PKTS_IN]);
+
+ int reply_len;
+ reply_len = gtphub_handle_echo_req(hub, &p, reply_buf);
+ if (reply_len > 0) {
+ /* It was an echo. Nothing left to do. */
+ osmo_sockaddr_copy(to_addr, from_addr);
+ *to_ofd = &from_bind->ofd;
+
+ rate_ctr_inc(&from_bind->counters_io->ctr[GTPH_CTR_PKTS_OUT]);
+ rate_ctr_add(&from_bind->counters_io->ctr[GTPH_CTR_BYTES_OUT],
+ reply_len);
+ LOG(LOGL_DEBUG, "%s Echo response to %s: %d bytes to %s\n",
+ (side_idx == GTPH_SIDE_GGSN)? "-->" : "<--",
+ gtphub_side_idx_names[side_idx],
+ (int)reply_len, osmo_sockaddr_to_str(to_addr));
+ return reply_len;
+ }
+ if (reply_len < 0)
+ return -1;
+
+ *to_ofd = &to_bind->ofd;
+
+ /* If a proxy is configured, check that it's indeed that proxy talking
+ * to us. A proxy is a forced 1:1 connection, e.g. to another gtphub,
+ * so no-one else is allowed to talk to us from that side. */
+ struct gtphub_peer_port *from_peer = hub->proxy[side_idx][plane_idx];
+ if (from_peer) {
+ if (osmo_sockaddr_cmp(&from_peer->sa, from_addr) != 0) {
+ LOG(LOGL_ERROR,
+ "Rejecting: %s proxy configured, but GTP packet"
+ " received on %s bind is from another sender:"
+ " proxy: %s sender: %s\n",
+ gtphub_side_idx_names[side_idx],
+ gtphub_side_idx_names[side_idx],
+ gtphub_port_str(from_peer),
+ osmo_sockaddr_to_str(from_addr));
+ return -1;
+ }
+ }
+
+ if (!from_peer) {
+ /* Find or create a peer with a matching address. The sender's
+ * port may in fact differ. */
+ from_peer = gtphub_known_addr_have_port(from_bind, from_addr);
+ }
+
+ /* If any PDP context has been created, we already have an entry for
+ * this GSN. If we don't have an entry, a GGSN has nothing to tell us
+ * about, while an SGSN may initiate a PDP context. */
+ if (!from_peer) {
+ if (side_idx == GTPH_SIDE_GGSN) {
+ LOG(LOGL_ERROR, "Dropping packet%s: unknown GGSN peer: %s\n",
+ gtp_type_str(p.type),
+ osmo_sockaddr_to_str(from_addr));
+ return -1;
+ } else {
+ /* SGSN */
+ /* A new peer. If this is on the Ctrl plane, an SGSN
+ * may make first contact without being known yet, so
+ * create the peer struct for the current sender. */
+ if (plane_idx != GTPH_PLANE_CTRL) {
+ LOG(LOGL_ERROR,
+ "Dropping packet%s: User plane peer was not"
+ "announced by PDP Context: %s\n",
+ gtp_type_str(p.type),
+ osmo_sockaddr_to_str(from_addr));
+ return -1;
+ }
+
+ struct gsn_addr from_gsna;
+ uint16_t from_port;
+ if (gsn_addr_from_sockaddr(&from_gsna, &from_port, from_addr) != 0)
+ return -1;
+
+ from_peer = gtphub_port_have(hub, from_bind, &from_gsna, from_port);
+ }
+ }
+
+ if (!from_peer) {
+ /* This could theoretically happen for invalid address data or
+ * somesuch. */
+ LOG(LOGL_ERROR, "Dropping packet%s: invalid %s peer: %s\n",
+ gtp_type_str(p.type),
+ gtphub_side_idx_names[side_idx],
+ osmo_sockaddr_to_str(from_addr));
+ return -1;
+ }
+
+ rate_ctr_add(&from_peer->counters_io->ctr[GTPH_CTR_BYTES_IN],
+ received);
+ rate_ctr_inc(&from_peer->counters_io->ctr[GTPH_CTR_PKTS_IN]);
+
+ LOG(LOGL_DEBUG, "from %s peer: %s\n", gtphub_side_idx_names[side_idx],
+ gtphub_port_str(from_peer));
+
+ gtphub_check_restart_counter(hub, &p, from_peer);
+ gtphub_map_restart_counter(hub, &p);
+
+ struct gtphub_peer_port *to_peer_from_seq;
+ struct gtphub_peer_port *to_peer;
+ if (gtphub_unmap(hub, &p, from_peer,
+ hub->proxy[other_side_idx(side_idx)][plane_idx],
+ &to_peer, &to_peer_from_seq)
+ != 0) {
+ return -1;
+ }
+
+ if (p.tun) {
+ struct gtphub_tunnel_endpoint *te = &p.tun->endpoint[p.side_idx][p.plane_idx];
+ rate_ctr_add(&te->counters_io->ctr[GTPH_CTR_BYTES_IN],
+ received);
+ rate_ctr_inc(&te->counters_io->ctr[GTPH_CTR_PKTS_IN]);
+ }
+
+ if ((!to_peer) && (side_idx == GTPH_SIDE_SGSN)) {
+ if (gtphub_resolve_ggsn(hub, &p, &to_peer) < 0)
+ return -1;
+ }
+
+ if (!to_peer && p.tun && p.type == GTP_DELETE_PDP_RSP) {
+ /* It's a delete confirmation for a tunnel that is partly
+ * invalid, probably marked unsuable due to a restarted peer.
+ * Remove the tunnel and be happy without forwarding. */
+ expiring_item_del(&p.tun->expiry_entry);
+ p.tun = NULL;
+ return 0;
+ }
+
+ if (!to_peer) {
+ LOG(LOGL_ERROR, "No %s to send to. Dropping packet%s"
+ " (type=%" PRIu8 ", header-TEI=%" PRIx32 ", seq=%" PRIx16 ").\n",
+ gtphub_side_idx_names[other_side_idx(side_idx)],
+ gtp_type_str(p.type),
+ p.type, p.header_tei_rx, p.seq
+ );
+ return -1;
+ }
+
+ if (plane_idx == GTPH_PLANE_CTRL) {
+ /* This may be a Create PDP Context response. If it is, there
+ * are other addresses in the GTP message to set up apart from
+ * the sender. */
+ if (gtphub_handle_pdp_ctx(hub, &p, from_peer, to_peer)
+ != 0)
+ return -1;
+ }
+
+ /* Either to_peer was resolved from an existing tunnel,
+ * or a PDP Ctx and thus a tunnel has just been created,
+ * or the tunnel has been deleted due to this message. */
+ OSMO_ASSERT(p.tun || (p.type == GTP_DELETE_PDP_RSP));
+
+ /* If the GGSN is replying to an SGSN request, the sequence nr has
+ * already been unmapped above (to_peer_from_seq != NULL), and we need not
+ * create a new mapping. */
+ if (!to_peer_from_seq)
+ gtphub_map_seq(&p, from_peer, to_peer);
+
+ osmo_sockaddr_copy(to_addr, &to_peer->sa);
+
+ *reply_buf = (uint8_t*)p.data;
+
+ if (received) {
+ rate_ctr_inc(&to_bind->counters_io->ctr[GTPH_CTR_PKTS_OUT]);
+ rate_ctr_add(&to_bind->counters_io->ctr[GTPH_CTR_BYTES_OUT],
+ received);
+
+ rate_ctr_inc(&to_peer->counters_io->ctr[GTPH_CTR_PKTS_OUT]);
+ rate_ctr_add(&to_peer->counters_io->ctr[GTPH_CTR_BYTES_OUT],
+ received);
+ }
+
+ if (p.tun) {
+ struct gtphub_tunnel_endpoint *te = &p.tun->endpoint[other_side_idx(p.side_idx)][p.plane_idx];
+ rate_ctr_inc(&te->counters_io->ctr[GTPH_CTR_PKTS_OUT]);
+ rate_ctr_add(&te->counters_io->ctr[GTPH_CTR_BYTES_OUT],
+ received);
+ }
+
+ LOG(LOGL_DEBUG, "%s Forward to %s:"
+ " header-TEI %" PRIx32", seq %" PRIx16", %d bytes to %s\n",
+ (side_idx == GTPH_SIDE_SGSN)? "-->" : "<--",
+ gtphub_side_idx_names[other_side_idx(side_idx)],
+ p.header_tei, p.seq,
+ (int)received, osmo_sockaddr_to_str(to_addr));
+ return received;
+}
+
+static void resolved_gssn_del_cb(struct expiring_item *expi)
+{
+ struct gtphub_resolved_ggsn *ggsn;
+ ggsn = container_of(expi, struct gtphub_resolved_ggsn, expiry_entry);
+
+ gtphub_port_ref_count_dec(ggsn->peer);
+ llist_del(&ggsn->entry);
+
+ ggsn->expiry_entry.del_cb = 0;
+ expiring_item_del(&ggsn->expiry_entry);
+
+ talloc_free(ggsn);
+}
+
+void gtphub_resolved_ggsn(struct gtphub *hub, const char *apn_oi_str,
+ struct gsn_addr *resolved_addr,
+ time_t now)
+{
+ struct gtphub_peer_port *pp;
+ struct gtphub_resolved_ggsn *ggsn;
+
+ LOG(LOGL_DEBUG, "Resolved GGSN callback: %s %s\n",
+ apn_oi_str, osmo_hexdump((unsigned char*)resolved_addr,
+ sizeof(*resolved_addr)));
+
+ pp = gtphub_port_have(hub, &hub->to_gsns[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL],
+ resolved_addr, 2123);
+ if (!pp) {
+ LOG(LOGL_ERROR, "Internal: Cannot create/find peer '%s'\n",
+ gsn_addr_to_str(resolved_addr));
+ return;
+ }
+
+ ggsn = talloc_zero(osmo_gtphub_ctx, struct gtphub_resolved_ggsn);
+ OSMO_ASSERT(ggsn);
+ INIT_LLIST_HEAD(&ggsn->entry);
+ expiring_item_init(&ggsn->expiry_entry);
+
+ ggsn->peer = pp;
+ gtphub_port_ref_count_inc(pp);
+
+ osmo_strlcpy(ggsn->apn_oi_str, apn_oi_str, sizeof(ggsn->apn_oi_str));
+
+ ggsn->expiry_entry.del_cb = resolved_gssn_del_cb;
+ expiry_add(&hub->expire_slowly, &ggsn->expiry_entry, now);
+
+ llist_add(&ggsn->entry, &hub->resolved_ggsns);
+}
+
+static int gtphub_gc_peer_port(struct gtphub_peer_port *pp)
+{
+ return pp->ref_count == 0;
+}
+
+static int gtphub_gc_peer_addr(struct gtphub_peer_addr *pa)
+{
+ struct gtphub_peer_port *pp, *npp;
+ llist_for_each_entry_safe(pp, npp, &pa->ports, entry) {
+ if (gtphub_gc_peer_port(pp)) {
+ LOG(LOGL_DEBUG, "expired: peer %s\n",
+ gtphub_port_str(pp));
+ gtphub_peer_port_del(pp);
+ }
+ }
+ return llist_empty(&pa->ports);
+}
+
+static int gtphub_gc_peer(struct gtphub_peer *p)
+{
+ struct gtphub_peer_addr *pa, *npa;
+ llist_for_each_entry_safe(pa, npa, &p->addresses, entry) {
+ if (gtphub_gc_peer_addr(pa)) {
+ gtphub_peer_addr_del(pa);
+ }
+ }
+
+ /* Note that there's a ref_count in each gtphub_peer_port instance
+ * listed within p->addresses, referenced by TEI mappings from
+ * hub->tei_map. As long as those don't expire, this peer will stay. */
+
+ return llist_empty(&p->addresses)
+ && nr_map_empty(&p->seq_map);
+}
+
+static void gtphub_gc_bind(struct gtphub_bind *b)
+{
+ struct gtphub_peer *p, *n;
+ llist_for_each_entry_safe(p, n, &b->peers, entry) {
+ if (gtphub_gc_peer(p)) {
+ gtphub_peer_del(p);
+ }
+ }
+}
+
+void gtphub_gc(struct gtphub *hub, time_t now)
+{
+ int expired;
+ expired = expiry_tick(&hub->expire_quickly, now);
+ expired += expiry_tick(&hub->expire_slowly, now);
+
+ /* ... */
+
+ if (expired) {
+ int s, p;
+ for_each_side_and_plane(s, p) {
+ gtphub_gc_bind(&hub->to_gsns[s][p]);
+ }
+ }
+}
+
+static void gtphub_gc_cb(void *data)
+{
+ struct gtphub *hub = data;
+ gtphub_gc(hub, gtphub_now());
+ osmo_timer_schedule(&hub->gc_timer, GTPH_GC_TICK_SECONDS, 0);
+}
+
+static void gtphub_gc_start(struct gtphub *hub)
+{
+ osmo_timer_setup(&hub->gc_timer, gtphub_gc_cb, hub);
+ osmo_timer_schedule(&hub->gc_timer, GTPH_GC_TICK_SECONDS, 0);
+}
+
+/* called by unit tests */
+void gtphub_init(struct gtphub *hub)
+{
+ gtphub_zero(hub);
+
+ INIT_LLIST_HEAD(&hub->tunnels);
+ INIT_LLIST_HEAD(&hub->pending_deletes);
+
+ expiry_init(&hub->expire_quickly, GTPH_EXPIRE_QUICKLY_SECS);
+ expiry_init(&hub->expire_slowly, GTPH_EXPIRE_SLOWLY_MINUTES * 60);
+
+ nr_pool_init(&hub->tei_pool, 1, 0xffffffff);
+
+ int side_idx;
+ int plane_idx;
+ for_each_side_and_plane(side_idx, plane_idx) {
+ gtphub_bind_init(&hub->to_gsns[side_idx][plane_idx]);
+ }
+
+ hub->to_gsns[GTPH_SIDE_SGSN][GTPH_PLANE_CTRL].label = "SGSN Ctrl";
+ hub->to_gsns[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL].label = "GGSN Ctrl";
+ hub->to_gsns[GTPH_SIDE_SGSN][GTPH_PLANE_USER].label = "SGSN User";
+ hub->to_gsns[GTPH_SIDE_GGSN][GTPH_PLANE_USER].label = "GGSN User";
+}
+
+/* For the test suite, this is kept separate from gtphub_stop(), which also
+ * closes sockets. The test suite avoids using sockets and would cause
+ * segfaults when trying to close uninitialized ofds. */
+void gtphub_free(struct gtphub *hub)
+{
+ /* By expiring all mappings, a garbage collection should free
+ * everything else. A gtphub_bind_free() will assert that everything is
+ * indeed empty. */
+ expiry_clear(&hub->expire_quickly);
+ expiry_clear(&hub->expire_slowly);
+
+ int side_idx;
+ int plane_idx;
+ for_each_side_and_plane(side_idx, plane_idx) {
+ gtphub_gc_bind(&hub->to_gsns[side_idx][plane_idx]);
+ gtphub_bind_free(&hub->to_gsns[side_idx][plane_idx]);
+ }
+}
+
+void gtphub_stop(struct gtphub *hub)
+{
+ int side_idx;
+ int plane_idx;
+ for_each_side_and_plane(side_idx, plane_idx) {
+ gtphub_bind_stop(&hub->to_gsns[side_idx][plane_idx]);
+ }
+ gtphub_free(hub);
+}
+
+static int gtphub_make_proxy(struct gtphub *hub,
+ struct gtphub_peer_port **pp,
+ struct gtphub_bind *bind,
+ const struct gtphub_cfg_addr *addr)
+{
+ if (!addr->addr_str)
+ return 0;
+
+ struct gsn_addr gsna;
+ if (gsn_addr_from_str(&gsna, addr->addr_str) != 0)
+ return -1;
+
+ *pp = gtphub_port_have(hub, bind, &gsna, addr->port);
+
+ /* This is *the* proxy. Make sure it is never expired. */
+ gtphub_port_ref_count_inc(*pp);
+ return 0;
+}
+
+int gtphub_start(struct gtphub *hub, struct gtphub_cfg *cfg,
+ uint8_t restart_counter)
+{
+ gtphub_init(hub);
+
+ hub->restart_counter = restart_counter;
+ hub->sgsn_use_sender = cfg->sgsn_use_sender? 1 : 0;
+
+ /* If a Ctrl plane proxy is configured, ares will never be used. */
+ if (!cfg->proxy[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL].addr_str) {
+ if (gtphub_ares_init(hub) != 0) {
+ LOG(LOGL_FATAL, "Failed to initialize ares\n");
+ return -1;
+ }
+ }
+
+ int side_idx;
+ int plane_idx;
+ for_each_side_and_plane(side_idx, plane_idx) {
+ int rc;
+ rc = gtphub_bind_start(&hub->to_gsns[side_idx][plane_idx],
+ &cfg->to_gsns[side_idx][plane_idx],
+ (side_idx == GTPH_SIDE_SGSN)
+ ? from_sgsns_read_cb
+ : from_ggsns_read_cb,
+ hub, plane_idx);
+ if (rc) {
+ LOG(LOGL_FATAL, "Failed to bind for %ss (%s)\n",
+ gtphub_side_idx_names[side_idx],
+ gtphub_plane_idx_names[plane_idx]);
+ return rc;
+ }
+ }
+
+ for_each_side_and_plane(side_idx, plane_idx) {
+ if (gtphub_make_proxy(hub,
+ &hub->proxy[side_idx][plane_idx],
+ &hub->to_gsns[side_idx][plane_idx],
+ &cfg->proxy[side_idx][plane_idx])
+ != 0) {
+ LOG(LOGL_FATAL, "Cannot configure %s proxy"
+ " %s port %d.\n",
+ gtphub_side_idx_names[side_idx],
+ cfg->proxy[side_idx][plane_idx].addr_str,
+ (int)cfg->proxy[side_idx][plane_idx].port);
+ return -1;
+ }
+ }
+
+ for_each_side_and_plane(side_idx, plane_idx) {
+ if (hub->proxy[side_idx][plane_idx])
+ LOG(LOGL_NOTICE, "Using %s %s proxy %s\n",
+ gtphub_side_idx_names[side_idx],
+ gtphub_plane_idx_names[plane_idx],
+ gtphub_port_str(hub->proxy[side_idx][plane_idx]));
+ }
+
+ if (hub->sgsn_use_sender)
+ LOG(LOGL_NOTICE, "Using sender address and port for SGSN instead of GSN Addr IE and default ports.\n");
+
+ gtphub_gc_start(hub);
+ return 0;
+}
+
+static struct gtphub_peer_addr *gtphub_peer_find_addr(const struct gtphub_peer *peer,
+ const struct gsn_addr *addr)
+{
+ struct gtphub_peer_addr *a;
+ llist_for_each_entry(a, &peer->addresses, entry) {
+ if (gsn_addr_same(&a->addr, addr))
+ return a;
+ }
+ return NULL;
+}
+
+static struct gtphub_peer_port *gtphub_addr_find_port(const struct gtphub_peer_addr *a,
+ uint16_t port)
+{
+ OSMO_ASSERT(port);
+ struct gtphub_peer_port *pp;
+ llist_for_each_entry(pp, &a->ports, entry) {
+ if (pp->port == port)
+ return pp;
+ }
+ return NULL;
+}
+
+static struct gtphub_peer_addr *gtphub_addr_find(const struct gtphub_bind *bind,
+ const struct gsn_addr *addr)
+{
+ struct gtphub_peer *peer;
+ llist_for_each_entry(peer, &bind->peers, entry) {
+ struct gtphub_peer_addr *a = gtphub_peer_find_addr(peer, addr);
+ if (a)
+ return a;
+ }
+ return NULL;
+}
+
+static struct gtphub_peer_port *gtphub_port_find(const struct gtphub_bind *bind,
+ const struct gsn_addr *addr,
+ uint16_t port)
+{
+ struct gtphub_peer_addr *a = gtphub_addr_find(bind, addr);
+ if (!a)
+ return NULL;
+ return gtphub_addr_find_port(a, port);
+}
+
+struct gtphub_peer_port *gtphub_port_find_sa(const struct gtphub_bind *bind,
+ const struct osmo_sockaddr *addr)
+{
+ struct gsn_addr gsna;
+ uint16_t port;
+ gsn_addr_from_sockaddr(&gsna, &port, addr);
+ return gtphub_port_find(bind, &gsna, port);
+}
+
+static struct gtphub_peer *gtphub_peer_new(struct gtphub *hub,
+ struct gtphub_bind *bind)
+{
+ struct gtphub_peer *peer = talloc_zero(osmo_gtphub_ctx,
+ struct gtphub_peer);
+ OSMO_ASSERT(peer);
+
+ INIT_LLIST_HEAD(&peer->addresses);
+
+ nr_pool_init(&peer->seq_pool, 0, 0xffff);
+ nr_map_init(&peer->seq_map, &peer->seq_pool, &hub->expire_quickly);
+
+ /* TODO use something random to pick the initial sequence nr.
+ 0x6d31 produces the ASCII character sequence 'm1', currently used in
+ gtphub_nc_test.sh. */
+ peer->seq_pool.last_nr = 0x6d31 - 1;
+
+ llist_add(&peer->entry, &bind->peers);
+ return peer;
+}
+
+static struct gtphub_peer_addr *gtphub_peer_add_addr(struct gtphub_peer *peer,
+ const struct gsn_addr *addr)
+{
+ struct gtphub_peer_addr *a;
+ a = talloc_zero(osmo_gtphub_ctx, struct gtphub_peer_addr);
+ OSMO_ASSERT(a);
+ a->peer = peer;
+ gsn_addr_copy(&a->addr, addr);
+ INIT_LLIST_HEAD(&a->ports);
+ llist_add(&a->entry, &peer->addresses);
+
+ return a;
+}
+
+static struct gtphub_peer_addr *gtphub_addr_have(struct gtphub *hub,
+ struct gtphub_bind *bind,
+ const struct gsn_addr *addr)
+{
+ struct gtphub_peer_addr *a = gtphub_addr_find(bind, addr);
+ if (a)
+ return a;
+
+ /* If we haven't found an address, that means we need to create an
+ * entirely new peer for the new address. More addresses may be added
+ * to this peer later, but not via this function. */
+ struct gtphub_peer *peer = gtphub_peer_new(hub, bind);
+
+ a = gtphub_peer_add_addr(peer, addr);
+
+ LOG(LOGL_DEBUG, "New peer address: %s %s\n",
+ bind->label,
+ gsn_addr_to_str(&a->addr));
+
+ return a;
+}
+
+static struct gtphub_peer_port *gtphub_addr_add_port(struct gtphub_peer_addr *a,
+ uint16_t port)
+{
+ struct gtphub_peer_port *pp;
+
+ pp = talloc_zero(osmo_gtphub_ctx, struct gtphub_peer_port);
+ OSMO_ASSERT(pp);
+ pp->peer_addr = a;
+ pp->port = port;
+ pp->last_restart_count = -1;
+
+ if (gsn_addr_to_sockaddr(&a->addr, port, &pp->sa) != 0) {
+ talloc_free(pp);
+ return NULL;
+ }
+
+ pp->counters_io = rate_ctr_group_alloc(osmo_gtphub_ctx,
+ &gtphub_ctrg_io_desc, 0);
+
+ llist_add(&pp->entry, &a->ports);
+
+ LOG(LOGL_DEBUG, "New peer port: %s port %d\n",
+ gsn_addr_to_str(&a->addr),
+ (int)port);
+
+ return pp;
+}
+
+struct gtphub_peer_port *gtphub_port_have(struct gtphub *hub,
+ struct gtphub_bind *bind,
+ const struct gsn_addr *addr,
+ uint16_t port)
+{
+ struct gtphub_peer_addr *a = gtphub_addr_have(hub, bind, addr);
+
+ struct gtphub_peer_port *pp = gtphub_addr_find_port(a, port);
+ if (pp)
+ return pp;
+
+ return gtphub_addr_add_port(a, port);
+}
+
+/* Find a GGSN peer with a matching address. If the address is known but the
+ * port not, create a new port for that peer address. */
+struct gtphub_peer_port *gtphub_known_addr_have_port(const struct gtphub_bind *bind,
+ const struct osmo_sockaddr *addr)
+{
+ struct gtphub_peer_addr *pa;
+ struct gtphub_peer_port *pp;
+
+ struct gsn_addr gsna;
+ uint16_t port;
+ gsn_addr_from_sockaddr(&gsna, &port, addr);
+
+ pa = gtphub_addr_find(bind, &gsna);
+ if (!pa)
+ return NULL;
+
+ pp = gtphub_addr_find_port(pa, port);
+
+ if (!pp)
+ pp = gtphub_addr_add_port(pa, port);
+
+ return pp;
+}
+
+
+/* Return 0 if the message in p is not applicable for GGSN resolution, -1 if
+ * resolution should be possible but failed, and 1 if resolution was
+ * successful. *pp will be set to NULL if <1 is returned. */
+static int gtphub_resolve_ggsn(struct gtphub *hub,
+ struct gtp_packet_desc *p,
+ struct gtphub_peer_port **pp)
+{
+ *pp = NULL;
+
+ /* TODO determine from message type whether IEs should be present? */
+
+ int rc;
+ const char *imsi_str;
+ rc = get_ie_imsi_str(p->ie, 0, &imsi_str);
+ if (rc < 1)
+ return rc;
+ OSMO_ASSERT(imsi_str);
+
+ const char *apn_str;
+ rc = get_ie_apn_str(p->ie, &apn_str);
+ if (rc < 1)
+ return rc;
+ OSMO_ASSERT(apn_str);
+
+ *pp = gtphub_resolve_ggsn_addr(hub, imsi_str, apn_str);
+ return (*pp)? 1 : -1;
+}
+
+
+/* TODO move to osmocom/core/socket.c ? */
+/* use this in osmo_sock_init() to remove dup. */
+/* Internal: call getaddrinfo for osmo_sockaddr_init(). The caller is required
+ to call freeaddrinfo(*result), iff zero is returned. */
+static int _osmo_getaddrinfo(struct addrinfo **result,
+ uint16_t family, uint16_t type, uint8_t proto,
+ const char *host, uint16_t port)
+{
+ struct addrinfo hints;
+ char portbuf[16];
+
+ sprintf(portbuf, "%u", port);
+ memset(&hints, '\0', sizeof(struct addrinfo));
+ hints.ai_family = family;
+ if (type == SOCK_RAW) {
+ /* Workaround for glibc, that returns EAI_SERVICE (-8) if
+ * SOCK_RAW and IPPROTO_GRE is used.
+ */
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ } else {
+ hints.ai_socktype = type;
+ hints.ai_protocol = proto;
+ }
+
+ return getaddrinfo(host, portbuf, &hints, result);
+}
+
+/* TODO move to osmocom/core/socket.c ? */
+int osmo_sockaddr_init(struct osmo_sockaddr *addr,
+ uint16_t family, uint16_t type, uint8_t proto,
+ const char *host, uint16_t port)
+{
+ struct addrinfo *res;
+ int rc;
+ rc = _osmo_getaddrinfo(&res, family, type, proto, host, port);
+
+ if (rc != 0) {
+ LOG(LOGL_ERROR, "getaddrinfo returned error %d\n", (int)rc);
+ return -EINVAL;
+ }
+
+ OSMO_ASSERT(res->ai_addrlen <= sizeof(addr->a));
+ memcpy(&addr->a, res->ai_addr, res->ai_addrlen);
+ addr->l = res->ai_addrlen;
+ freeaddrinfo(res);
+
+ return 0;
+}
+
+int osmo_sockaddr_to_strs(char *addr_str, size_t addr_str_len,
+ char *port_str, size_t port_str_len,
+ const struct osmo_sockaddr *addr,
+ int flags)
+{
+ int rc;
+
+ if ((addr->l < 1) || (addr->l > sizeof(addr->a))) {
+ LOGP(DGTPHUB, LOGL_ERROR, "Invalid address size: %d\n", addr->l);
+ return -1;
+ }
+
+ if (addr->l > sizeof(addr->a)) {
+ LOGP(DGTPHUB, LOGL_ERROR, "Invalid address: too long: %d\n",
+ addr->l);
+ return -1;
+ }
+
+ rc = getnameinfo((struct sockaddr*)&addr->a, addr->l,
+ addr_str, addr_str_len,
+ port_str, port_str_len,
+ flags);
+
+ if (rc)
+ LOGP(DGTPHUB, LOGL_ERROR, "Invalid address: %s: %s\n",
+ gai_strerror(rc), osmo_hexdump((uint8_t*)&addr->a,
+ addr->l));
+
+ return rc;
+}
+
+const char *osmo_sockaddr_to_strb(const struct osmo_sockaddr *addr,
+ char *buf, size_t buf_len)
+{
+ const int portbuf_len = 6;
+ OSMO_ASSERT(buf_len > portbuf_len);
+ char *portbuf = buf + buf_len - portbuf_len;
+ buf_len -= portbuf_len;
+ if (osmo_sockaddr_to_strs(buf, buf_len,
+ portbuf, portbuf_len,
+ addr,
+ NI_NUMERICHOST | NI_NUMERICSERV))
+ return NULL;
+
+ char *pos = buf + strnlen(buf, buf_len-1);
+ size_t len = buf_len - (pos - buf);
+
+ snprintf(pos, len, " port %s", portbuf);
+ buf[buf_len-1] = '\0';
+
+ return buf;
+}
+
+const char *osmo_sockaddr_to_str(const struct osmo_sockaddr *addr)
+{
+ static char buf[256];
+ const char *result = osmo_sockaddr_to_strb(addr, buf, sizeof(buf));
+ if (! result)
+ return "(invalid)";
+ return result;
+}
+
+int osmo_sockaddr_cmp(const struct osmo_sockaddr *a,
+ const struct osmo_sockaddr *b)
+{
+ if (a == b)
+ return 0;
+ if (!a)
+ return -1;
+ if (!b)
+ return 1;
+ if (a->l != b->l) {
+ /* Lengths are not the same, but determine the order. Will
+ * anyone ever sort a list by osmo_sockaddr though...? */
+ int cmp = memcmp(&a->a, &b->a, (a->l < b->l)? a->l : b->l);
+ if (cmp == 0) {
+ if (a->l < b->l)
+ return -1;
+ else
+ return 1;
+ }
+ return cmp;
+ }
+ return memcmp(&a->a, &b->a, a->l);
+}
+
+void osmo_sockaddr_copy(struct osmo_sockaddr *dst,
+ const struct osmo_sockaddr *src)
+{
+ OSMO_ASSERT(src->l <= sizeof(dst->a));
+ memcpy(&dst->a, &src->a, src->l);
+ dst->l = src->l;
+}
diff --git a/src/gprs/gtphub_ares.c b/src/gprs/gtphub_ares.c
new file mode 100644
index 000000000..afeeda657
--- /dev/null
+++ b/src/gprs/gtphub_ares.c
@@ -0,0 +1,220 @@
+/* GTP Hub Implementation */
+
+/* (C) 2015 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * gtphub_ares.c.
+ *
+ * This file is kept separate so that these functions can be wrapped for
+ * gtphub_test.c. When a function and its callers are in the same compilational
+ * unit, the wrappability may be optimized away.
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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 <string.h>
+#include <unistd.h>
+
+#include <openbsc/gtphub.h>
+#include <openbsc/debug.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/apn.h>
+
+/* TODO split GRX ares from sgsn into a separate struct and allow use without
+ * globals. */
+#include <openbsc/sgsn.h>
+extern struct sgsn_instance *sgsn;
+
+struct sgsn_instance sgsn_inst = { 0 };
+struct sgsn_instance *sgsn = &sgsn_inst;
+
+extern void *osmo_gtphub_ctx;
+
+int gtphub_ares_init(struct gtphub *hub)
+{
+ return sgsn_ares_init(sgsn);
+}
+
+struct ggsn_lookup {
+ struct llist_head entry;
+ struct expiring_item expiry_entry;
+
+ struct gtphub *hub;
+
+ char imsi_str[GSM23003_IMSI_MAX_DIGITS+1];
+ char apn_ni_str[GSM_APN_LENGTH];
+ char apn_oi_str[GSM_APN_LENGTH];
+ int have_3dig_mnc;
+};
+
+static int start_ares_query(struct ggsn_lookup *lookup);
+
+static void ggsn_lookup_cb(void *arg, int status, int timeouts,
+ struct hostent *hostent)
+{
+ struct ggsn_lookup *lookup = arg;
+ LOGP(DGTPHUB, LOGL_NOTICE, "ggsn_lookup_cb(%p / %p)", lookup,
+ &lookup->expiry_entry);
+
+ if (status != ARES_SUCCESS) {
+ LOGP(DGTPHUB, LOGL_ERROR, "DNS query failed.\n");
+
+ /* Need to try with three digits now */
+ if (!lookup->have_3dig_mnc) {
+ lookup->have_3dig_mnc = 1;
+ if (start_ares_query(lookup) == 0)
+ return;
+ }
+
+ LOGP(DGTPHUB, LOGL_ERROR, "Failed to resolve GGSN. (%p)\n",
+ lookup);
+ goto remove_from_queue;
+ }
+
+ struct gsn_addr resolved_addr;
+ if (hostent->h_length > sizeof(resolved_addr.buf)) {
+ LOGP(DGTPHUB, LOGL_ERROR, "Addr size too large: %d > %d\n",
+ (int)hostent->h_length, (int)sizeof(resolved_addr.buf));
+ goto remove_from_queue;
+ }
+
+ /* Get the first addr from the list */
+ char *addr0 = hostent->h_addr_list[0];
+ if (!addr0) {
+ LOGP(DGTPHUB, LOGL_ERROR, "No host address.\n");
+ goto remove_from_queue;
+ }
+
+ memcpy(resolved_addr.buf, addr0, hostent->h_length);
+ resolved_addr.len = hostent->h_length;
+
+ LOGP(DGTPHUB, LOGL_NOTICE, "resolved addr %s\n",
+ osmo_hexdump((unsigned char*)&resolved_addr,
+ sizeof(resolved_addr)));
+
+ gtphub_resolved_ggsn(lookup->hub, lookup->apn_oi_str, &resolved_addr,
+ gtphub_now());
+
+remove_from_queue:
+ LOGP(DGTPHUB, LOGL_ERROR, "Removing GGSN lookup. (%p / %p)\n", lookup,
+ &lookup->expiry_entry);
+ expiring_item_del(&lookup->expiry_entry);
+}
+
+static void make_addr_str(struct ggsn_lookup *lookup)
+{
+ char *apn_oi_str;
+ apn_oi_str = osmo_apn_qualify_from_imsi(lookup->imsi_str,
+ lookup->apn_ni_str,
+ lookup->have_3dig_mnc);
+ osmo_strlcpy(lookup->apn_oi_str, apn_oi_str,
+ sizeof(lookup->apn_oi_str));
+}
+
+static int start_ares_query(struct ggsn_lookup *lookup)
+{
+ LOGP(DGTPHUB, LOGL_DEBUG, "Going to query %s (%p / %p)\n",
+ lookup->apn_oi_str, lookup, &lookup->expiry_entry);
+
+ int rc = sgsn_ares_query(sgsn, lookup->apn_oi_str, ggsn_lookup_cb,
+ lookup);
+ if (rc != 0)
+ LOGP(DGTPHUB, LOGL_ERROR, "Failed to start ares query.\n");
+ return rc;
+}
+
+static void ggsn_lookup_del_cb(struct expiring_item *expi)
+{
+ struct ggsn_lookup *lookup;
+ lookup = container_of(expi, struct ggsn_lookup, expiry_entry);
+
+ LOGP(DGTPHUB, LOGL_NOTICE, "ggsn_lookup_del_cb(%p / %p)\n", lookup,
+ expi);
+
+ lookup->expiry_entry.del_cb = 0;
+ expiring_item_del(expi);
+
+ llist_del(&lookup->entry);
+ talloc_free(lookup);
+}
+
+struct gtphub_peer_port *gtphub_resolve_ggsn_addr(struct gtphub *hub,
+ const char *imsi_str,
+ const char *apn_ni_str)
+{
+ OSMO_ASSERT(imsi_str);
+ OSMO_ASSERT(apn_ni_str);
+
+ struct ggsn_lookup *lookup = talloc_zero(osmo_gtphub_ctx,
+ struct ggsn_lookup);
+ OSMO_ASSERT(lookup);
+
+ LOGP(DGTPHUB, LOGL_DEBUG, "Request to resolve IMSI"
+ " '%s' with APN-NI '%s' (%p / %p)\n",
+ imsi_str, apn_ni_str, lookup, &lookup->expiry_entry);
+
+ expiring_item_init(&lookup->expiry_entry);
+ lookup->hub = hub;
+
+ osmo_strlcpy(lookup->imsi_str, imsi_str, sizeof(lookup->imsi_str));
+ osmo_strlcpy(lookup->apn_ni_str, apn_ni_str,
+ sizeof(lookup->apn_ni_str));
+
+ make_addr_str(lookup);
+
+ struct ggsn_lookup *active;
+ llist_for_each_entry(active, &hub->ggsn_lookups, entry) {
+ if (strncmp(active->apn_oi_str, lookup->apn_oi_str,
+ sizeof(lookup->apn_oi_str)) == 0) {
+ LOGP(DGTPHUB, LOGL_DEBUG,
+ "Query already pending for %s\n",
+ lookup->apn_oi_str);
+ /* A query already pending. Just tip our hat. */
+ return NULL;
+ }
+ }
+
+ struct gtphub_resolved_ggsn *resolved;
+ llist_for_each_entry(resolved, &hub->resolved_ggsns, entry) {
+ if (strncmp(resolved->apn_oi_str, lookup->apn_oi_str,
+ sizeof(lookup->apn_oi_str)) == 0) {
+ LOGP(DGTPHUB, LOGL_DEBUG,
+ "GGSN resolved from cache: %s -> %s\n",
+ lookup->apn_oi_str,
+ gtphub_port_str(resolved->peer));
+ return resolved->peer;
+ }
+ }
+
+ /* Kick off a resolution, but so far return nothing. The hope is that
+ * the peer will resend the request (a couple of times), and by then
+ * the GGSN will be resolved. */
+ LOGP(DGTPHUB, LOGL_DEBUG,
+ "Sending out DNS query for %s..."
+ " (Returning failure, hoping for a retry once resolution"
+ " has concluded)\n",
+ lookup->apn_oi_str);
+
+ llist_add(&lookup->entry, &hub->ggsn_lookups);
+
+ lookup->expiry_entry.del_cb = ggsn_lookup_del_cb;
+ expiry_add(&hub->expire_quickly, &lookup->expiry_entry, gtphub_now());
+
+ start_ares_query(lookup);
+
+ return NULL;
+}
diff --git a/src/gprs/gtphub_main.c b/src/gprs/gtphub_main.c
new file mode 100644
index 000000000..73a122c31
--- /dev/null
+++ b/src/gprs/gtphub_main.c
@@ -0,0 +1,357 @@
+/* GTP Hub main program */
+
+/* (C) 2015 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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 <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <osmocom/core/signal.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/ports.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gtphub.h>
+#include <openbsc/vty.h>
+
+#include "../../bscconfig.h"
+
+extern void *osmo_gtphub_ctx;
+
+
+const char *gtphub_copyright =
+ "Copyright (C) 2015 sysmocom s.f.m.c GmbH <info@sysmocom.de>\r\n"
+ "License AGPLv3+: GNU AGPL version 2 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";
+
+static struct log_info_cat gtphub_categories[] = {
+ [DGTPHUB] = {
+ .name = "DGTPHUB",
+ .description = "GTP Hub",
+ .color = "\033[1;33m",
+ .enabled = 1,
+ .loglevel = LOGL_INFO,
+ },
+};
+
+int gtphub_log_filter_fn(const struct log_context *ctx,
+ struct log_target *tar)
+{
+ return 0;
+}
+
+static const struct log_info gtphub_log_info = {
+ .filter_fn = gtphub_log_filter_fn,
+ .cat = gtphub_categories,
+ .num_cat = ARRAY_SIZE(gtphub_categories),
+};
+
+void log_cfg(struct gtphub_cfg *cfg)
+{
+ int side_idx, plane_idx;
+ for_each_side_and_plane(side_idx, plane_idx) {
+ struct gtphub_cfg_addr *a;
+ a = &cfg->to_gsns[side_idx][plane_idx].bind;
+ LOGP(DGTPHUB, LOGL_NOTICE,
+ "to-%ss bind, %s: %s port %d\n",
+ gtphub_side_idx_names[side_idx],
+ gtphub_plane_idx_names[plane_idx],
+ a->addr_str, a->port);
+ }
+}
+
+static void signal_handler(int signal)
+{
+ fprintf(stdout, "signal %d received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ 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:
+ case SIGUSR2:
+ talloc_report_full(osmo_gtphub_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+extern int bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+ .name = "OsmoGTPhub",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = bsc_vty_go_parent,
+ .is_config_node = bsc_vty_is_config_node,
+};
+
+struct cmdline_cfg {
+ const char *config_file;
+ const char *restart_counter_file;
+ int daemonize;
+};
+
+static uint8_t next_restart_count(const char *path)
+{
+ int umask_was = umask(022);
+
+ uint8_t counter = 0;
+
+ FILE *f = fopen(path, "r");
+ if (f) {
+ int rc = fscanf(f, "%hhu", &counter);
+
+ if (rc != 1)
+ goto failed_to_read;
+
+ char c;
+ while (fread(&c, 1, 1, f) > 0) {
+ switch (c) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ break;
+ default:
+ goto failed_to_read;
+ }
+ }
+ fclose(f);
+ }
+
+ counter ++;
+
+ f = fopen(path, "w");
+ if (!f)
+ goto failed_to_write;
+ if (fprintf(f, "%" PRIu8 "\n", counter) < 2)
+ goto failed_to_write;
+ if (fclose(f)) {
+ f = NULL;
+ goto failed_to_write;
+ }
+
+ umask(umask_was);
+
+ LOGP(DGTPHUB, LOGL_NOTICE, "Restarted with counter %hhu\n", counter);
+ return counter;
+
+failed_to_read:
+ fclose(f);
+ umask(umask_was);
+ LOGP(DGTPHUB, LOGL_FATAL, "Restart counter file cannot be parsed:"
+ " %s\n", path);
+ exit(1);
+
+failed_to_write:
+ if (f)
+ fclose(f);
+ umask(umask_was);
+ LOGP(DGTPHUB, LOGL_FATAL, "Restart counter file cannot be written:"
+ " %s\n", path);
+ exit(1);
+}
+
+static void print_help(struct cmdline_cfg *ccfg)
+{
+ printf("gtphub commandline options\n");
+ printf(" -h,--help This text.\n");
+ printf(" -D,--daemonize Fork the process into a background daemon.\n");
+ printf(" -d,--debug <cat> Enable Debugging for this category.\n");
+ printf(" Pass '-d list' to get a category listing.\n");
+ printf(" -s,--disable-color\n");
+ printf(" -c,--config-file <path> The config file to use [%s].\n",
+ ccfg->config_file);
+ printf(" -e,--log-level <nr> Set a global log level.\n");
+ printf(" -r,--restart-file <path> File for counting restarts [%s].\n",
+ ccfg->restart_counter_file);
+}
+
+static void list_categories(void)
+{
+ printf("Avaliable debug categories:\n");
+ int i;
+ for (i = 0; i < gtphub_log_info.num_cat; ++i) {
+ if (!gtphub_log_info.cat[i].name)
+ continue;
+
+ printf("%s\n", gtphub_log_info.cat[i].name);
+ }
+}
+
+static void handle_options(struct cmdline_cfg *ccfg, 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'},
+ {"log-level", 1, 0, 'e'},
+ {"restart-file", 1, 0, 'r'},
+ {NULL, 0, 0, 0}
+ };
+
+ c = getopt_long(argc, argv, "hd:Dc:sTe:r:",
+ long_options, &option_index);
+ if (c == -1) {
+ if (optind < argc) {
+ LOGP(DGTPHUB, LOGL_FATAL,
+ "Excess commandline arguments ('%s').\n",
+ argv[optind]);
+ exit(2);
+ }
+ break;
+ }
+
+ switch (c) {
+ case 'h':
+ //print_usage();
+ print_help(ccfg);
+ exit(0);
+ case 's':
+ log_set_use_color(osmo_stderr_target, 0);
+ break;
+ case 'd':
+ if (strcmp("list", optarg) == 0) {
+ list_categories();
+ exit(0);
+ } else
+ log_parse_category_mask(osmo_stderr_target, optarg);
+ break;
+ case 'D':
+ ccfg->daemonize = 1;
+ break;
+ case 'c':
+ ccfg->config_file = optarg;
+ break;
+ case 'T':
+ log_set_print_timestamp(osmo_stderr_target, 1);
+ break;
+ case 'e':
+ log_set_log_level(osmo_stderr_target, atoi(optarg));
+ break;
+ case 'r':
+ ccfg->restart_counter_file = optarg;
+ break;
+ default:
+ LOGP(DGTPHUB, LOGL_FATAL, "Invalid command line argument, abort.\n");
+ exit(1);
+ break;
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int rc;
+
+ struct cmdline_cfg _ccfg;
+ struct cmdline_cfg *ccfg = &_ccfg;
+ memset(ccfg, '\0', sizeof(*ccfg));
+ ccfg->config_file = "./gtphub.conf";
+ ccfg->restart_counter_file = "./gtphub_restart_count";
+
+ struct gtphub_cfg _cfg;
+ struct gtphub_cfg *cfg = &_cfg;
+ memset(cfg, '\0', sizeof(*cfg));
+
+ struct gtphub _hub;
+ struct gtphub *hub = &_hub;
+
+ osmo_gtphub_ctx = talloc_named_const(NULL, 0, "osmo_gtphub");
+ msgb_talloc_ctx_init(osmo_gtphub_ctx, 0);
+
+ signal(SIGINT, &signal_handler);
+ signal(SIGABRT, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+ osmo_init_ignore_signals();
+
+ osmo_init_logging(&gtphub_log_info);
+
+ vty_info.copyright = gtphub_copyright;
+ vty_init(&vty_info);
+ logging_vty_add_cmds(NULL);
+ gtphub_vty_init(hub, cfg);
+
+ rate_ctr_init(osmo_gtphub_ctx);
+
+ handle_options(ccfg, argc, argv);
+
+ rc = gtphub_cfg_read(cfg, ccfg->config_file);
+ if (rc < 0) {
+ LOGP(DGTPHUB, LOGL_FATAL, "Cannot parse config file '%s'\n",
+ ccfg->config_file);
+ exit(2);
+ }
+
+ /* start telnet after reading config for vty_get_bind_addr() */
+ rc = telnet_init_dynif(osmo_gtphub_ctx, 0, vty_get_bind_addr(),
+ OSMO_VTY_PORT_GTPHUB);
+ if (rc < 0)
+ exit(1);
+
+ if (gtphub_start(hub, cfg,
+ next_restart_count(ccfg->restart_counter_file))
+ != 0)
+ return -1;
+
+ log_cfg(cfg);
+
+ if (ccfg->daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ LOGP(DGTPHUB, LOGL_FATAL, "Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (1) {
+ rc = osmo_select_main(0);
+ if (rc < 0)
+ exit(3);
+ }
+
+ /* not reached */
+ exit(0);
+}
diff --git a/src/gprs/gtphub_sock.c b/src/gprs/gtphub_sock.c
new file mode 100644
index 000000000..60bebaaeb
--- /dev/null
+++ b/src/gprs/gtphub_sock.c
@@ -0,0 +1,60 @@
+/* GTP Hub Implementation */
+
+/* (C) 2015 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * gtphub_sock.c.
+ *
+ * This file is kept separate so that these functions can be wrapped for
+ * gtphub_test.c. When a function and its callers are in the same compilational
+ * unit, the wrappability may be optimized away.
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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 <openbsc/gtphub.h>
+#include <openbsc/debug.h>
+
+/* Convenience makro, note: only within this C file. */
+#define LOG(level, fmt, args...) \
+ LOGP(DGTPHUB, level, fmt, ##args)
+
+int gtphub_write(const struct osmo_fd *to,
+ const struct osmo_sockaddr *to_addr,
+ const uint8_t *buf, size_t buf_len)
+{
+ errno = 0;
+ ssize_t sent = sendto(to->fd, buf, buf_len, 0,
+ (struct sockaddr*)&to_addr->a, to_addr->l);
+ LOG(LOGL_DEBUG, "to %s\n", osmo_sockaddr_to_str(to_addr));
+
+ if (sent == -1) {
+ LOG(LOGL_ERROR, "error: %s\n", strerror(errno));
+ return -EINVAL;
+ }
+
+ if (sent != buf_len)
+ LOG(LOGL_ERROR, "sent(%d) != data_len(%d)\n",
+ (int)sent, (int)buf_len);
+ else
+ LOG(LOGL_DEBUG, "Sent %d: %s%s\n",
+ (int)sent,
+ osmo_hexdump(buf, sent > 1000? 1000 : sent),
+ sent > 1000 ? "..." : "");
+
+ return 0;
+}
+
diff --git a/src/gprs/gtphub_vty.c b/src/gprs/gtphub_vty.c
new file mode 100644
index 000000000..a30ad2a54
--- /dev/null
+++ b/src/gprs/gtphub_vty.c
@@ -0,0 +1,613 @@
+/* (C) 2015 by sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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 <string.h>
+#include <inttypes.h>
+
+#include <ares.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <openbsc/vty.h>
+#include <openbsc/gtphub.h>
+
+/* TODO split GRX ares from sgsn into a separate struct and allow use without
+ * globals. */
+#include <openbsc/sgsn.h>
+extern struct sgsn_instance *sgsn;
+
+static struct gtphub *g_hub = 0;
+static struct gtphub_cfg *g_cfg = 0;
+
+static struct cmd_node gtphub_node = {
+ GTPHUB_NODE,
+ "%s(config-gtphub)# ",
+ 1,
+};
+
+#define GTPH_DEFAULT_CONTROL_PORT 2123
+#define GTPH_DEFAULT_USER_PORT 2152
+
+static void write_addrs(struct vty *vty, const char *name,
+ struct gtphub_cfg_addr *c, struct gtphub_cfg_addr *u)
+{
+ if ((c->port == GTPH_DEFAULT_CONTROL_PORT)
+ && (u->port == GTPH_DEFAULT_USER_PORT)
+ && (strcmp(c->addr_str, u->addr_str) == 0)) {
+ /* Default port numbers and same IP address: write "short"
+ * variant. */
+ vty_out(vty, " %s %s%s",
+ name,
+ c->addr_str,
+ VTY_NEWLINE);
+ return;
+ }
+
+ vty_out(vty, " %s ctrl %s %d user %s %d%s",
+ name,
+ c->addr_str, (int)c->port,
+ u->addr_str, (int)u->port,
+ VTY_NEWLINE);
+
+ struct ares_addr_node *server;
+ for (server = sgsn->ares_servers; server; server = server->next)
+ vty_out(vty, " grx-dns-add %s%s", inet_ntoa(server->addr.addr4), VTY_NEWLINE);
+}
+
+static int config_write_gtphub(struct vty *vty)
+{
+ vty_out(vty, "gtphub%s", VTY_NEWLINE);
+
+ write_addrs(vty, "bind-to-sgsns",
+ &g_cfg->to_gsns[GTPH_SIDE_SGSN][GTPH_PLANE_CTRL].bind,
+ &g_cfg->to_gsns[GTPH_SIDE_SGSN][GTPH_PLANE_USER].bind);
+
+ write_addrs(vty, "bind-to-ggsns",
+ &g_cfg->to_gsns[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL].bind,
+ &g_cfg->to_gsns[GTPH_SIDE_GGSN][GTPH_PLANE_USER].bind);
+
+ if (g_cfg->sgsn_use_sender) {
+ vty_out(vty, "sgsn-use-sender%s", VTY_NEWLINE);
+ }
+
+ if (g_cfg->proxy[GTPH_SIDE_SGSN][GTPH_PLANE_CTRL].addr_str) {
+ write_addrs(vty, "sgsn-proxy",
+ &g_cfg->proxy[GTPH_SIDE_SGSN][GTPH_PLANE_CTRL],
+ &g_cfg->proxy[GTPH_SIDE_SGSN][GTPH_PLANE_USER]);
+ }
+
+ if (g_cfg->proxy[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL].addr_str) {
+ write_addrs(vty, "ggsn-proxy",
+ &g_cfg->proxy[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL],
+ &g_cfg->proxy[GTPH_SIDE_GGSN][GTPH_PLANE_USER]);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gtphub, cfg_gtphub_cmd,
+ "gtphub",
+ "Configure the GTP hub\n")
+{
+ vty->node = GTPHUB_NODE;
+ return CMD_SUCCESS;
+}
+
+#define BIND_ARGS "ctrl ADDR <0-65535> user ADDR <0-65535>"
+#define BIND_DOCS \
+ "Set GTP-C bind\n" \
+ "GTP-C local IP address (v4 or v6)\n" \
+ "GTP-C local port\n" \
+ "Set GTP-U bind\n" \
+ "GTP-U local IP address (v4 or v6)\n" \
+ "GTP-U local port\n"
+
+
+DEFUN(cfg_gtphub_bind_to_sgsns_short, cfg_gtphub_bind_to_sgsns_short_cmd,
+ "bind-to-sgsns ADDR",
+ "GTP Hub Parameters\n"
+ "Set the local bind address to listen for SGSNs, for both GTP-C and GTP-U\n"
+ "Local IP address (v4 or v6)\n"
+ )
+{
+ int i;
+ for_each_plane(i)
+ g_cfg->to_gsns[GTPH_SIDE_SGSN][i].bind.addr_str = talloc_strdup(tall_vty_ctx, argv[0]);
+ g_cfg->to_gsns[GTPH_SIDE_SGSN][GTPH_PLANE_CTRL].bind.port = GTPH_DEFAULT_CONTROL_PORT;
+ g_cfg->to_gsns[GTPH_SIDE_SGSN][GTPH_PLANE_USER].bind.port = GTPH_DEFAULT_USER_PORT;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gtphub_bind_to_ggsns_short, cfg_gtphub_bind_to_ggsns_short_cmd,
+ "bind-to-ggsns ADDR",
+ "GTP Hub Parameters\n"
+ "Set the local bind address to listen for GGSNs, for both GTP-C and GTP-U\n"
+ "Local IP address (v4 or v6)\n"
+ )
+{
+ int i;
+ for_each_plane(i)
+ g_cfg->to_gsns[GTPH_SIDE_GGSN][i].bind.addr_str = talloc_strdup(tall_vty_ctx, argv[0]);
+ g_cfg->to_gsns[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL].bind.port = GTPH_DEFAULT_CONTROL_PORT;
+ g_cfg->to_gsns[GTPH_SIDE_GGSN][GTPH_PLANE_USER].bind.port = GTPH_DEFAULT_USER_PORT;
+ return CMD_SUCCESS;
+}
+
+
+static int handle_binds(struct gtphub_cfg_bind *b, const char **argv)
+{
+ b[GTPH_PLANE_CTRL].bind.addr_str = talloc_strdup(tall_vty_ctx, argv[0]);
+ b[GTPH_PLANE_CTRL].bind.port = atoi(argv[1]);
+ b[GTPH_PLANE_USER].bind.addr_str = talloc_strdup(tall_vty_ctx, argv[2]);
+ b[GTPH_PLANE_USER].bind.port = atoi(argv[3]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gtphub_bind_to_sgsns, cfg_gtphub_bind_to_sgsns_cmd,
+ "bind-to-sgsns " BIND_ARGS,
+ "GTP Hub Parameters\n"
+ "Set the local bind addresses and ports to listen for SGSNs\n"
+ BIND_DOCS
+ )
+{
+ return handle_binds(g_cfg->to_gsns[GTPH_SIDE_SGSN], argv);
+}
+
+DEFUN(cfg_gtphub_bind_to_ggsns, cfg_gtphub_bind_to_ggsns_cmd,
+ "bind-to-ggsns " BIND_ARGS,
+ "GTP Hub Parameters\n"
+ "Set the local bind addresses and ports to listen for GGSNs\n"
+ BIND_DOCS
+ )
+{
+ return handle_binds(g_cfg->to_gsns[GTPH_SIDE_GGSN], argv);
+}
+
+DEFUN(cfg_gtphub_ggsn_proxy_short, cfg_gtphub_ggsn_proxy_short_cmd,
+ "ggsn-proxy ADDR",
+ "GTP Hub Parameters\n"
+ "Redirect all GGSN bound traffic to default ports on this address (another gtphub)\n"
+ "Remote IP address (v4 or v6)\n"
+ )
+{
+ g_cfg->proxy[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL].addr_str = talloc_strdup(tall_vty_ctx, argv[0]);
+ g_cfg->proxy[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL].port = GTPH_DEFAULT_CONTROL_PORT;
+ g_cfg->proxy[GTPH_SIDE_GGSN][GTPH_PLANE_USER].addr_str = talloc_strdup(tall_vty_ctx, argv[0]);
+ g_cfg->proxy[GTPH_SIDE_GGSN][GTPH_PLANE_USER].port = GTPH_DEFAULT_USER_PORT;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gtphub_ggsn_proxy, cfg_gtphub_ggsn_proxy_cmd,
+ "ggsn-proxy " BIND_ARGS,
+ "GTP Hub Parameters\n"
+ "Redirect all GGSN bound traffic to these addresses and ports (another gtphub)\n"
+ BIND_DOCS
+ )
+{
+ g_cfg->proxy[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL].addr_str = talloc_strdup(tall_vty_ctx, argv[0]);
+ g_cfg->proxy[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL].port = atoi(argv[1]);
+ g_cfg->proxy[GTPH_SIDE_GGSN][GTPH_PLANE_USER].addr_str = talloc_strdup(tall_vty_ctx, argv[2]);
+ g_cfg->proxy[GTPH_SIDE_GGSN][GTPH_PLANE_USER].port = atoi(argv[3]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gtphub_sgsn_proxy_short, cfg_gtphub_sgsn_proxy_short_cmd,
+ "sgsn-proxy ADDR",
+ "GTP Hub Parameters\n"
+ "Redirect all SGSN bound traffic to default ports on this address (another gtphub)\n"
+ "Remote IP address (v4 or v6)\n"
+ )
+{
+ g_cfg->proxy[GTPH_SIDE_SGSN][GTPH_PLANE_CTRL].addr_str = talloc_strdup(tall_vty_ctx, argv[0]);
+ g_cfg->proxy[GTPH_SIDE_SGSN][GTPH_PLANE_CTRL].port = GTPH_DEFAULT_CONTROL_PORT;
+ g_cfg->proxy[GTPH_SIDE_SGSN][GTPH_PLANE_USER].addr_str = talloc_strdup(tall_vty_ctx, argv[0]);
+ g_cfg->proxy[GTPH_SIDE_SGSN][GTPH_PLANE_USER].port = GTPH_DEFAULT_USER_PORT;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gtphub_sgsn_proxy, cfg_gtphub_sgsn_proxy_cmd,
+ "sgsn-proxy " BIND_ARGS,
+ "GTP Hub Parameters\n"
+ "Redirect all SGSN bound traffic to these addresses and ports (another gtphub)\n"
+ BIND_DOCS
+ )
+{
+ g_cfg->proxy[GTPH_SIDE_SGSN][GTPH_PLANE_CTRL].addr_str = talloc_strdup(tall_vty_ctx, argv[0]);
+ g_cfg->proxy[GTPH_SIDE_SGSN][GTPH_PLANE_CTRL].port = atoi(argv[1]);
+ g_cfg->proxy[GTPH_SIDE_SGSN][GTPH_PLANE_USER].addr_str = talloc_strdup(tall_vty_ctx, argv[2]);
+ g_cfg->proxy[GTPH_SIDE_SGSN][GTPH_PLANE_USER].port = atoi(argv[3]);
+ return CMD_SUCCESS;
+}
+
+
+#define SGSN_USE_SENDER_STR \
+ "Ignore SGSN's Address IEs, use sender address and port (useful over NAT)\n"
+
+DEFUN(cfg_gtphub_sgsn_use_sender,
+ cfg_gtphub_sgsn_use_sender_cmd,
+ "sgsn-use-sender",
+ SGSN_USE_SENDER_STR)
+{
+ g_cfg->sgsn_use_sender = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gtphub_no_sgsn_use_sender,
+ cfg_gtphub_no_sgsn_use_sender_cmd,
+ "no sgsn-use-sender",
+ NO_STR SGSN_USE_SENDER_STR)
+{
+ g_cfg->sgsn_use_sender = 0;
+ return CMD_SUCCESS;
+}
+
+
+/* Copied from sgsn_vty.h */
+DEFUN(cfg_grx_ggsn, cfg_grx_ggsn_cmd,
+ "grx-dns-add A.B.C.D",
+ "Add DNS server\nIPv4 address\n")
+{
+ struct ares_addr_node *node = talloc_zero(tall_bsc_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;
+}
+
+
+static void show_bind_stats_all(struct vty *vty)
+{
+ int plane_idx;
+ for_each_plane(plane_idx) {
+ vty_out(vty, "- %s Plane:%s",
+ gtphub_plane_idx_names[plane_idx], VTY_NEWLINE);
+
+ int side_idx;
+ for_each_side(side_idx) {
+ struct gtphub_bind *b = &g_hub->to_gsns[side_idx][plane_idx];
+ vty_out(vty, " - local addr to/from %ss: %s port %d%s",
+ gtphub_side_idx_names[side_idx],
+ gsn_addr_to_str(&b->local_addr), (int)b->local_port,
+ VTY_NEWLINE);
+ vty_out_rate_ctr_group(vty, " ", b->counters_io);
+ }
+ }
+}
+
+static void show_tunnel_stats(struct vty *vty, struct gtphub_tunnel *tun)
+{
+ int plane_idx;
+ for_each_plane(plane_idx) {
+ vty_out(vty, "- %s Plane:%s",
+ gtphub_plane_idx_names[plane_idx], VTY_NEWLINE);
+
+ int side_idx;
+ for_each_side(side_idx) {
+ struct gtphub_tunnel_endpoint *te = &tun->endpoint[side_idx][plane_idx];
+ vty_out(vty, " - to/from %s:%s",
+ gtphub_side_idx_names[side_idx],
+ VTY_NEWLINE);
+ vty_out_rate_ctr_group(vty, " ", te->counters_io);
+ }
+ }
+}
+
+static void show_peer_summary(struct vty *vty, const char *prefix,
+ int side_idx, int plane_idx,
+ struct gtphub_peer *p, int with_io_stats)
+{
+ struct gtphub_peer_addr *pa;
+ int p2l = strlen(prefix) + 4 + 1;
+ char prefix2[p2l];
+ memset(prefix2, ' ', p2l - 1);
+ prefix2[p2l - 1] = '\0';
+
+ if (with_io_stats) {
+ llist_for_each_entry(pa, &p->addresses, entry) {
+ vty_out(vty, "%s- %s %s %s%s", prefix,
+ gtphub_side_idx_names[side_idx],
+ gtphub_plane_idx_names[plane_idx],
+ gsn_addr_to_str(&pa->addr),
+ VTY_NEWLINE);
+
+
+ struct gtphub_peer_port *pp;
+ llist_for_each_entry(pp, &pa->ports, entry) {
+ vty_out(vty, "%s Port %" PRIu16 "%s", prefix, pp->port, VTY_NEWLINE);
+ vty_out_rate_ctr_group(vty, prefix2, pp->counters_io);
+ }
+ }
+ } else {
+ llist_for_each_entry(pa, &p->addresses, entry) {
+ vty_out(vty, "%s- %s %s %s", prefix,
+ gtphub_side_idx_names[side_idx],
+ gtphub_plane_idx_names[plane_idx],
+ gsn_addr_to_str(&pa->addr));
+ struct gtphub_peer_port *pp;
+ llist_for_each_entry(pp, &pa->ports, entry) {
+ vty_out(vty, ":%" PRIu16, pp->port);
+ }
+ vty_out(vty, VTY_NEWLINE);
+ }
+ }
+}
+
+static void show_peers_summary(struct vty *vty)
+{
+ int side_idx;
+ int plane_idx;
+
+ int count[GTPH_SIDE_N][GTPH_PLANE_N] = {{0}};
+
+ for_each_side(side_idx) {
+ for_each_plane(plane_idx) {
+ struct gtphub_peer *p;
+ llist_for_each_entry(p, &g_hub->to_gsns[side_idx][plane_idx].peers, entry) {
+ count[side_idx][plane_idx] ++;
+ }
+ }
+ }
+
+ vty_out(vty, "Peers Count:%s", VTY_NEWLINE);
+ for_each_side_and_plane(side_idx, plane_idx) {
+ vty_out(vty, " %s %s peers: %d%s",
+ gtphub_side_idx_names[side_idx],
+ gtphub_plane_idx_names[plane_idx],
+ count[side_idx][plane_idx],
+ VTY_NEWLINE);
+ }
+}
+
+static void show_peers_all(struct vty *vty, int with_io_stats)
+{
+ int side_idx;
+ int plane_idx;
+
+ int count[GTPH_SIDE_N][GTPH_PLANE_N] = {{0}};
+
+ vty_out(vty, "All Peers%s%s",
+ with_io_stats? " with I/O stats" : "",
+ VTY_NEWLINE);
+ for_each_side(side_idx) {
+ vty_out(vty, "- %s%s", gtphub_side_idx_names[side_idx], VTY_NEWLINE);
+ for_each_plane(plane_idx) {
+ struct gtphub_peer *p;
+ llist_for_each_entry(p, &g_hub->to_gsns[side_idx][plane_idx].peers, entry) {
+ count[side_idx][plane_idx] ++;
+ show_peer_summary(vty, " ", side_idx, plane_idx, p, with_io_stats);
+ }
+ }
+ }
+ for_each_side_and_plane(side_idx, plane_idx) {
+ vty_out(vty, "%s %s peers: %d%s",
+ gtphub_side_idx_names[side_idx],
+ gtphub_plane_idx_names[plane_idx],
+ count[side_idx][plane_idx],
+ VTY_NEWLINE);
+ }
+}
+
+
+static void show_tunnels_summary(struct vty *vty)
+{
+ time_t now = gtphub_now();
+
+ const int w = 36;
+ int max_expiry = g_hub->expire_slowly.expiry_in_seconds;
+ float seconds_per_step = ((float)max_expiry) / w;
+
+ /* Print TEI mapping expiry in an ASCII histogram, like:
+ TEI map summary
+ Legend: '_'=0 '.'<=1% ':'<=2% '|'<=10% '#'>10% (10.0 m/step)
+ CTRL: 30 mappings, valid for 360m[# :. | . : . ]1m
+ USER: 30 mappings, valid for 360m[# :. | . : . ]1m
+ 4 TEI mappings in total, last expiry in 359.4 min
+ */
+ vty_out(vty,
+ "Tunnels summary%s"
+ " Legend: ' '=0 '.'<=1%% ':'<=2%% '|'<=10%% '#'>10%% (%.1f m/step)%s",
+ VTY_NEWLINE,
+ seconds_per_step / 60.,
+ VTY_NEWLINE);
+
+ int last_expiry = 0;
+
+ unsigned int count = 0;
+
+ int histogram[w];
+ memset(histogram, 0, sizeof(histogram));
+
+ struct gtphub_tunnel *t;
+ llist_for_each_entry(t, &g_hub->tunnels, entry) {
+ count ++;
+ int expiry = t->expiry_entry.expiry - now;
+ last_expiry = (last_expiry > expiry) ? last_expiry : expiry;
+
+ int hi = ((float)expiry) / seconds_per_step;
+ if (hi < 0)
+ hi = 0;
+ if (hi > (w - 1))
+ hi = w - 1;
+ histogram[hi] ++;
+ }
+
+ vty_out(vty,
+ " %u tunnels, valid for %dm[",
+ count, max_expiry / 60);
+
+ int i;
+ for (i = w - 1; i >= 0; i--) {
+ char c;
+ int val = histogram[i];
+ int percent = 100. * val / count;
+ if (!val)
+ c = ' ';
+ else if (percent <= 1)
+ c = '.';
+ else if (percent <= 2)
+ c = ':';
+ else if (percent <= 10)
+ c = '|';
+ else c = '#';
+ vty_out(vty, "%c", c);
+ }
+ vty_out(vty, "]1m%s", VTY_NEWLINE);
+
+ vty_out(vty, " last expiry in %.1f min%s",
+ ((float)last_expiry) / 60.,
+ VTY_NEWLINE);
+}
+
+static void show_tunnels_all(struct vty *vty, int with_io_stats)
+{
+ time_t now = gtphub_now();
+
+ vty_out(vty, "All tunnels%s:%s"
+ "Legend: TEI=<hex>: SGSN <-> GGSN (expiry in minutes), with each:%s"
+ " <IP-Ctrl>[/<IP-User>] (TEI C=<TEI-Ctrl-hex> U=<TEI-User-hex>)%s",
+ with_io_stats? "with I/O stats" : "",
+ VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+
+ unsigned int count = 0;
+ unsigned int incomplete = 0;
+ struct gtphub_tunnel *tun;
+ llist_for_each_entry(tun, &g_hub->tunnels, entry) {
+ vty_out(vty,
+ "%s (expiry in %dm)%s",
+ gtphub_tunnel_str(tun),
+ (int)((tun->expiry_entry.expiry - now) / 60),
+ VTY_NEWLINE);
+ count ++;
+ if (!gtphub_tunnel_complete(tun))
+ incomplete ++;
+ if (with_io_stats)
+ show_tunnel_stats(vty, tun);
+ }
+ vty_out(vty, "Total: %u tunnels (of which %u incomplete)%s",
+ count, incomplete, VTY_NEWLINE);
+}
+
+#define SHOW_GTPHUB_STRS SHOW_STR "Show info on running GTP hub\n"
+#define SHOW_GTPHUB_PEERS_STRS SHOW_GTPHUB_STRS "Active peers\n"
+#define SHOW_GTPHUB_TUNS_STRS SHOW_GTPHUB_STRS "Active tunnels\n"
+
+DEFUN(show_gtphub_peers_summary, show_gtphub_peers_summary_cmd, "show gtphub peers summary",
+ SHOW_GTPHUB_PEERS_STRS "Summary of all peers\n")
+{
+ show_peers_summary(vty);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_gtphub_peers_list, show_gtphub_peers_list_cmd, "show gtphub peers list",
+ SHOW_GTPHUB_PEERS_STRS "List all peers\n")
+{
+ show_peers_all(vty, 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_gtphub_peers_stats, show_gtphub_peers_stats_cmd, "show gtphub peers stats",
+ SHOW_GTPHUB_PEERS_STRS "List all peers with I/O stats\n")
+{
+ show_peers_all(vty, 1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_gtphub_tunnels_summary, show_gtphub_tunnels_summary_cmd, "show gtphub tunnels summary",
+ SHOW_GTPHUB_TUNS_STRS "Summary of all tunnels\n")
+{
+ show_tunnels_summary(vty);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_gtphub_tunnels_list, show_gtphub_tunnels_list_cmd, "show gtphub tunnels list",
+ SHOW_GTPHUB_TUNS_STRS "List all tunnels\n")
+{
+ show_tunnels_all(vty, 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_gtphub_tunnels_stats, show_gtphub_tunnels_stats_cmd, "show gtphub tunnels stats",
+ SHOW_GTPHUB_TUNS_STRS "List all tunnels with I/O stats\n")
+{
+ show_tunnels_all(vty, 1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_gtphub, show_gtphub_cmd, "show gtphub all",
+ SHOW_GTPHUB_STRS "Summarize everything about the GTP hub\n")
+{
+ show_bind_stats_all(vty);
+ show_peers_summary(vty);
+ show_tunnels_summary(vty);
+ return CMD_SUCCESS;
+}
+
+
+int gtphub_vty_init(struct gtphub *global_hub, struct gtphub_cfg *global_cfg)
+{
+ g_hub = global_hub;
+ g_cfg = global_cfg;
+
+ install_element_ve(&show_gtphub_cmd);
+ install_element_ve(&show_gtphub_peers_summary_cmd);
+ install_element_ve(&show_gtphub_peers_list_cmd);
+ install_element_ve(&show_gtphub_peers_stats_cmd);
+ install_element_ve(&show_gtphub_tunnels_summary_cmd);
+ install_element_ve(&show_gtphub_tunnels_list_cmd);
+ install_element_ve(&show_gtphub_tunnels_stats_cmd);
+
+ install_element(CONFIG_NODE, &cfg_gtphub_cmd);
+ install_node(&gtphub_node, config_write_gtphub);
+ vty_install_default(GTPHUB_NODE);
+
+ install_element(GTPHUB_NODE, &cfg_gtphub_bind_to_sgsns_short_cmd);
+ install_element(GTPHUB_NODE, &cfg_gtphub_bind_to_sgsns_cmd);
+ install_element(GTPHUB_NODE, &cfg_gtphub_bind_to_ggsns_short_cmd);
+ install_element(GTPHUB_NODE, &cfg_gtphub_bind_to_ggsns_cmd);
+ install_element(GTPHUB_NODE, &cfg_gtphub_ggsn_proxy_short_cmd);
+ install_element(GTPHUB_NODE, &cfg_gtphub_ggsn_proxy_cmd);
+ install_element(GTPHUB_NODE, &cfg_gtphub_sgsn_proxy_short_cmd);
+ install_element(GTPHUB_NODE, &cfg_gtphub_sgsn_proxy_cmd);
+ install_element(GTPHUB_NODE, &cfg_gtphub_sgsn_use_sender_cmd);
+ install_element(GTPHUB_NODE, &cfg_gtphub_no_sgsn_use_sender_cmd);
+ install_element(GTPHUB_NODE, &cfg_grx_ggsn_cmd);
+
+ return 0;
+}
+
+int gtphub_cfg_read(struct gtphub_cfg *cfg, const char *config_file)
+{
+ int rc;
+
+ 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;
+ }
+
+ return 0;
+}
diff --git a/src/gprs/osmo_sgsn.cfg b/src/gprs/osmo_sgsn.cfg
new file mode 100644
index 000000000..c4c9ec1cf
--- /dev/null
+++ b/src/gprs/osmo_sgsn.cfg
@@ -0,0 +1,23 @@
+!
+! Osmocom SGSN (0.9.0.474-0ede2) configuration saved from vty
+!!
+!
+line vty
+ no login
+!
+sgsn
+ gtp local-ip 192.168.100.11
+ ggsn 0 remote-ip 192.168.100.239
+ ggsn 0 gtp-version 1
+ns
+ timer tns-block 3
+ timer tns-block-retries 3
+ timer tns-reset 3
+ timer tns-reset-retries 3
+ timer tns-test 30
+ timer tns-alive 3
+ timer tns-alive-retries 10
+ encapsulation udp local-ip 192.168.100.11
+ encapsulation udp local-port 23000
+ encapsulation framerelay-gre enabled 0
+bssgp
diff --git a/src/gprs/sgsn_ares.c b/src/gprs/sgsn_ares.c
new file mode 100644
index 000000000..d94d184a3
--- /dev/null
+++ b/src/gprs/sgsn_ares.c
@@ -0,0 +1,173 @@
+/* C-ARES DNS resolver integration */
+
+/*
+ * (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 <openbsc/sgsn.h>
+#include <openbsc/debug.h>
+
+#include <netdb.h>
+
+struct cares_event_fd {
+ struct llist_head head;
+ struct osmo_fd fd;
+};
+
+struct cares_cb_data {
+ ares_host_callback cb;
+ void *data;
+};
+
+static void osmo_ares_reschedule(struct sgsn_instance *sgsn);
+static void ares_cb(void *_arg, int status, int timeouts, struct hostent *hostent)
+{
+ struct cares_cb_data *arg = _arg;
+
+ arg->cb(arg->data, status, timeouts, hostent);
+ osmo_ares_reschedule(sgsn);
+ talloc_free(arg);
+}
+
+static int ares_osmo_fd_cb(struct osmo_fd *fd, unsigned int what)
+{
+ LOGP(DGPRS, LOGL_DEBUG, "C-ares fd(%d) ready(%d)\n", fd->fd, what);
+
+ ares_process_fd(sgsn->ares_channel,
+ (what & BSC_FD_READ) ? fd->fd : ARES_SOCKET_BAD,
+ (what & BSC_FD_WRITE) ? fd->fd : ARES_SOCKET_BAD);
+ osmo_ares_reschedule(sgsn);
+ return 0;
+}
+
+static void ares_timeout_cb(void *data)
+{
+ struct sgsn_instance *sgsn = data;
+
+ LOGP(DGPRS, LOGL_DEBUG, "C-ares triggering timeout\n");
+ ares_process_fd(sgsn->ares_channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
+ osmo_ares_reschedule(sgsn);
+}
+
+static void osmo_ares_reschedule(struct sgsn_instance *sgsn)
+{
+ struct timeval *timeout, tv;
+
+ osmo_timer_del(&sgsn->ares_timer);
+ timeout = ares_timeout(sgsn->ares_channel, NULL, &tv);
+ if (timeout) {
+ LOGP(DGPRS, LOGL_DEBUG, "C-ares scheduling timeout %llu.%llu\n",
+ (unsigned long long) tv.tv_sec,
+ (unsigned long long) tv.tv_usec);
+ osmo_timer_setup(&sgsn->ares_timer, ares_timeout_cb, sgsn);
+ osmo_timer_schedule(&sgsn->ares_timer, tv.tv_sec, tv.tv_usec);
+ }
+}
+
+static void setup_ares_osmo_fd(void *data, int fd, int read, int write)
+{
+ struct cares_event_fd *ufd, *tmp;
+
+ /* delete the entry */
+ if (read == 0 && write == 0) {
+ llist_for_each_entry_safe(ufd, tmp, &sgsn->ares_fds, head) {
+ if (ufd->fd.fd != fd)
+ continue;
+
+ LOGP(DGPRS, LOGL_DEBUG,
+ "Removing C-ares watched fd (%d)\n", fd);
+ osmo_fd_unregister(&ufd->fd);
+ llist_del(&ufd->head);
+ talloc_free(ufd);
+ return;
+ }
+ }
+
+ /* Search for the fd or create a new one */
+ llist_for_each_entry(ufd, &sgsn->ares_fds, head) {
+ if (ufd->fd.fd != fd)
+ continue;
+
+ LOGP(DGPRS, LOGL_DEBUG, "Updating C-ares fd (%d)\n", fd);
+ goto update_fd;
+ }
+
+ LOGP(DGPRS, LOGL_DEBUG, "Registering C-ares fd (%d)\n", fd);
+ ufd = talloc_zero(tall_bsc_ctx, struct cares_event_fd);
+ ufd->fd.fd = fd;
+ ufd->fd.cb = ares_osmo_fd_cb;
+ ufd->fd.data = data;
+ if (osmo_fd_register(&ufd->fd) != 0)
+ LOGP(DGPRS, LOGL_ERROR, "Failed to register C-ares fd (%d)\n", fd);
+ llist_add(&ufd->head, &sgsn->ares_fds);
+
+update_fd:
+ if (read)
+ ufd->fd.when |= BSC_FD_READ;
+ else
+ ufd->fd.when &= ~BSC_FD_READ;
+
+ if (write)
+ ufd->fd.when |= BSC_FD_WRITE;
+ else
+ ufd->fd.when &= ~BSC_FD_WRITE;
+
+ osmo_ares_reschedule(sgsn);
+}
+
+int sgsn_ares_query(struct sgsn_instance *sgsn, const char *name,
+ ares_host_callback cb, void *data)
+{
+ struct cares_cb_data *cb_data;
+
+ cb_data = talloc_zero(tall_bsc_ctx, struct cares_cb_data);
+ cb_data->cb = cb;
+ cb_data->data = data;
+ ares_gethostbyname(sgsn->ares_channel, name, AF_INET, ares_cb, cb_data);
+ osmo_ares_reschedule(sgsn);
+ return 0;
+}
+
+int sgsn_ares_init(struct sgsn_instance *sgsn)
+{
+ struct ares_options options;
+ int optmask;
+ int rc;
+
+ INIT_LLIST_HEAD(&sgsn->ares_fds);
+ memset(&options, 0, sizeof(options));
+ options.sock_state_cb = setup_ares_osmo_fd;
+ options.sock_state_cb_data = sgsn;
+
+ optmask = ARES_OPT_FLAGS | ARES_OPT_SOCK_STATE_CB | ARES_OPT_DOMAINS;
+
+ if (sgsn->ares_servers)
+ optmask |= ARES_OPT_SERVERS;
+
+ ares_library_init(ARES_LIB_INIT_ALL);
+ rc = ares_init_options(&sgsn->ares_channel, &options, optmask);
+ if (rc != ARES_SUCCESS)
+ return rc;
+
+ if (sgsn->ares_servers)
+ rc = ares_set_servers(sgsn->ares_channel, sgsn->ares_servers);
+
+ return rc;
+}
+
+osmo_static_assert(ARES_SUCCESS == 0, ares_success_zero);
diff --git a/src/gprs/sgsn_auth.c b/src/gprs/sgsn_auth.c
new file mode 100644
index 000000000..a64339c3e
--- /dev/null
+++ b/src/gprs/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 <openbsc/sgsn.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_gmm.h>
+#include <openbsc/gprs_subscriber.h>
+#include <openbsc/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(void)
+{
+ INIT_LLIST_HEAD(&sgsn->cfg.imsi_acl);
+}
+
+/* temporary IMSI ACL hack */
+struct imsi_acl_entry *sgsn_acl_lookup(const char *imsi, 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 &&
+ (!mmctx->is_authenticated ||
+ 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), "%03d%02d",
+ mmctx->ra.mcc, mmctx->ra.mnc);
+ if (strncmp(mccmnc, mmctx->imsi, 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 && !mmctx->is_authenticated) {
+ /* Find next tuple */
+ at = sgsn_auth_get_tuple(mmctx, mmctx->auth_triplet.key_seq);
+
+ if (!at) {
+ /* No valid tuple found, request fresh ones */
+ mmctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL;
+ LOGMMCTXP(LOGL_INFO, mmctx,
+ "Requesting authentication tuples\n");
+ rc = gprs_subscr_request_auth_info(mmctx, NULL, NULL);
+ if (rc >= 0)
+ return 0;
+
+ return rc;
+ }
+
+ mmctx->auth_triplet = *at;
+ } else if (need_update_location) {
+ LOGMMCTXP(LOGL_INFO, mmctx,
+ "Missing information, requesting subscriber data\n");
+ rc = gprs_subscr_request_update_location(mmctx);
+ if (rc >= 0)
+ return 0;
+
+ return rc;
+ }
+
+ sgsn_auth_update(mmctx);
+ return 0;
+}
+
+void sgsn_auth_update(struct sgsn_mm_ctx *mmctx)
+{
+ enum sgsn_auth_state auth_state;
+ struct gprs_subscr *subscr = mmctx->subscr;
+ struct gsm_auth_tuple *at;
+ int gmm_cause;
+
+ auth_state = sgsn_auth_state(mmctx);
+
+ LOGMMCTXP(LOGL_DEBUG, mmctx, "Updating authorization (%s -> %s)\n",
+ get_value_string(sgsn_auth_state_names, mmctx->auth_state),
+ get_value_string(sgsn_auth_state_names, auth_state));
+
+ if (auth_state == SGSN_AUTH_UNKNOWN && subscr &&
+ !(subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING_MASK)) {
+ /* Reject requests if gprs_subscr_request_update_location fails */
+ LOGMMCTXP(LOGL_ERROR, mmctx,
+ "Missing information, authorization not possible\n");
+ auth_state = SGSN_AUTH_REJECTED;
+ }
+
+ if (auth_state == SGSN_AUTH_AUTHENTICATE &&
+ mmctx->auth_triplet.key_seq == GSM_KEY_SEQ_INVAL) {
+ /* The current tuple is not valid, but we are possibly called
+ * because new auth tuples have been received */
+ at = sgsn_auth_get_tuple(mmctx, mmctx->auth_triplet.key_seq);
+ if (!at) {
+ LOGMMCTXP(LOGL_ERROR, mmctx,
+ "Missing auth tuples, authorization not possible\n");
+ auth_state = SGSN_AUTH_REJECTED;
+ } else {
+ mmctx->auth_triplet = *at;
+ }
+ }
+
+ if (mmctx->auth_state == auth_state)
+ return;
+
+ LOGMMCTXP(LOGL_INFO, mmctx, "Got authorization update: state %s -> %s\n",
+ get_value_string(sgsn_auth_state_names, mmctx->auth_state),
+ get_value_string(sgsn_auth_state_names, auth_state));
+
+ mmctx->auth_state = auth_state;
+
+ switch (auth_state) {
+ case SGSN_AUTH_AUTHENTICATE:
+ if (subscr)
+ subscr->sgsn_data->auth_triplets_updated = 0;
+
+ gsm0408_gprs_authenticate(mmctx);
+ break;
+ case SGSN_AUTH_ACCEPTED:
+ gsm0408_gprs_access_granted(mmctx);
+ break;
+ case SGSN_AUTH_REJECTED:
+ gmm_cause =
+ subscr ? subscr->sgsn_data->error_cause :
+ SGSN_ERROR_CAUSE_NONE;
+
+ if (subscr && (subscr->flags & GPRS_SUBSCRIBER_CANCELLED) != 0)
+ gsm0408_gprs_access_cancelled(mmctx, gmm_cause);
+ else
+ gsm0408_gprs_access_denied(mmctx, gmm_cause);
+ break;
+ default:
+ break;
+ }
+}
+
+struct gsm_auth_tuple *sgsn_auth_get_tuple(struct sgsn_mm_ctx *mmctx,
+ unsigned key_seq)
+{
+ unsigned count;
+ unsigned idx;
+ struct gsm_auth_tuple *at = NULL;
+
+ struct sgsn_subscriber_data *sdata;
+
+ if (!mmctx->subscr)
+ return NULL;
+
+ if (key_seq == GSM_KEY_SEQ_INVAL)
+ /* Start with 0 after increment module array size */
+ idx = ARRAY_SIZE(sdata->auth_triplets) - 1;
+ else
+ idx = key_seq;
+
+ sdata = mmctx->subscr->sgsn_data;
+
+ /* Find next tuple */
+ for (count = ARRAY_SIZE(sdata->auth_triplets); count > 0; count--) {
+ idx = (idx + 1) % ARRAY_SIZE(sdata->auth_triplets);
+
+ if (sdata->auth_triplets[idx].key_seq == GSM_KEY_SEQ_INVAL)
+ continue;
+
+ if (sdata->auth_triplets[idx].use_count == 0) {
+ at = &sdata->auth_triplets[idx];
+ at->use_count = 1;
+ return at;
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/gprs/sgsn_cdr.c b/src/gprs/sgsn_cdr.c
new file mode 100644
index 000000000..091089610
--- /dev/null
+++ b/src/gprs/sgsn_cdr.c
@@ -0,0 +1,258 @@
+/* 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 <openbsc/sgsn.h>
+#include <openbsc/signal.h>
+#include <openbsc/gprs_utils.h>
+#include <openbsc/debug.h>
+
+#include <openbsc/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;
+
+/**
+ * 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 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 void cdr_log_mm(struct sgsn_instance *inst, const char *ev,
+ struct sgsn_mm_ctx *mmctx)
+{
+ FILE *cdr_file;
+ struct tm tm;
+ struct timeval tv;
+
+ if (!inst->cfg.cdr.filename)
+ return;
+
+ 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);
+ gettimeofday(&tv, NULL);
+ gmtime_r(&tv.tv_sec, &tm);
+ fprintf(cdr_file, "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s\n",
+ 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);
+
+ 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 void cdr_log_pdp(struct sgsn_instance *inst, const char *ev,
+ struct sgsn_pdp_ctx *pdp)
+{
+ FILE *cdr_file;
+ char apni[(pdp->lib ? pdp->lib->apn_use.l : 0) + 1];
+ char ggsn_addr[INET_ADDRSTRLEN + 1];
+ char sgsn_addr[INET_ADDRSTRLEN + 1];
+ char eua_addr[INET6_ADDRSTRLEN + 1];
+ struct tm tm;
+ struct timeval tv;
+ time_t duration;
+ struct timespec tp;
+
+ if (!inst->cfg.cdr.filename)
+ return;
+
+ memset(apni, 0, sizeof(apni));
+ memset(ggsn_addr, 0, sizeof(ggsn_addr));
+ memset(eua_addr, 0, sizeof(eua_addr));
+
+
+ if (pdp->lib) {
+ gprs_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));
+
+ 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);
+
+ 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;
+
+ fprintf(cdr_file,
+ "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s,%ld,%s,%s,%s,%s,%" PRIu64 ",%" PRIu64 ",%u\n",
+ 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);
+ 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:
+ clock_gettime(CLOCK_MONOTONIC, &signal_data->pdp->cdr_start);
+ signal_data->pdp->cdr_charging_id = signal_data->pdp->lib->cid;
+ cdr_log_pdp(inst, "pdp-act", signal_data->pdp);
+ osmo_timer_setup(&signal_data->pdp->cdr_timer, cdr_pdp_timeout,
+ signal_data->pdp);
+ osmo_timer_schedule(&signal_data->pdp->cdr_timer, inst->cfg.cdr.interval, 0);
+ break;
+ case S_SGSN_PDP_DEACT:
+ cdr_log_pdp(inst, "pdp-deact", signal_data->pdp);
+ osmo_timer_del(&signal_data->pdp->cdr_timer);
+ break;
+ case S_SGSN_PDP_TERMINATE:
+ cdr_log_pdp(inst, "pdp-terminate", signal_data->pdp);
+ osmo_timer_del(&signal_data->pdp->cdr_timer);
+ break;
+ case S_SGSN_PDP_FREE:
+ cdr_log_pdp(inst, "pdp-free", signal_data->pdp);
+ osmo_timer_del(&signal_data->pdp->cdr_timer);
+ break;
+ }
+
+ return 0;
+}
+
+int sgsn_cdr_init(struct sgsn_instance *sgsn)
+{
+ /* register for CDR related events */
+ sgsn->cfg.cdr.interval = 10 * 60;
+ osmo_signal_register_handler(SS_SGSN, handle_sgsn_sig, sgsn);
+
+ return 0;
+}
diff --git a/src/gprs/sgsn_ctrl.c b/src/gprs/sgsn_ctrl.c
new file mode 100644
index 000000000..31ac74f1f
--- /dev/null
+++ b/src/gprs/sgsn_ctrl.c
@@ -0,0 +1,69 @@
+/* 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 <openbsc/gsm_data.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/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;
+}
+
+struct ctrl_handle *sgsn_controlif_setup(struct gsm_network *net,
+ const char *bind_addr, uint16_t port)
+{
+ return ctrl_interface_setup_dynip(net, bind_addr, port, NULL);
+}
diff --git a/src/gprs/sgsn_libgtp.c b/src/gprs/sgsn_libgtp.c
new file mode 100644
index 000000000..001e61146
--- /dev/null
+++ b/src/gprs/sgsn_libgtp.c
@@ -0,0 +1,860 @@
+/* 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 <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_gmm.h>
+#include <openbsc/gprs_subscriber.h>
+#include <openbsc/gprs_sndcp.h>
+
+#ifdef BUILD_IU
+#include <openbsc/iu.h>
+#include <osmocom/ranap/ranap_ies_defs.h>
+#endif
+
+#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;
+
+ LOGP(DGPRS, LOGL_ERROR, "Create PDP Context\n");
+ pctx = sgsn_pdp_ctx_alloc(mmctx, nsapi);
+ if (!pctx) {
+ LOGP(DGPRS, LOGL_ERROR, "Couldn't allocate PDP Ctx\n");
+ return NULL;
+ }
+
+ imsi_ui64 = imsi_str2gtp(mmctx->imsi);
+
+ rc = pdp_newpdp(&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;
+ pctx->ggsn = ggsn;
+
+ //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 */
+
+ /* Put the MSISDN in case we have it */
+ if (mmctx->subscr && mmctx->subscr->sgsn_data->msisdn_len) {
+ pdp->msisdn.l = mmctx->subscr->sgsn_data->msisdn_len;
+ if (pdp->msisdn.l > sizeof(pdp->msisdn.v))
+ pdp->msisdn.l = 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 */
+ 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);
+
+ /* Protocol Configuration Options from GMM */
+ 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);
+
+ /* 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);
+ }
+
+ /* 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));
+
+ /* Assume we are a GERAN system */
+ pdp->rattype.l = 1;
+ pdp->rattype.v[0] = 2;
+ pdp->rattype_given = 1;
+
+ /* Include RAI and ULI all the time */
+ pdp->rai_given = 1;
+ pdp->rai.l = 6;
+ raid = mmctx->ra;
+ raid.lac = 0xFFFE;
+ raid.rac = 0xFF;
+ gsm48_construct_ra(pdp->rai.v, &raid);
+
+ 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);
+
+ /* 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_ERROR, pctx, "Delete PDP Context\n");
+
+ /* FIXME: decide if we need teardown or not ! */
+ return gtp_delete_context_req(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 }
+};
+
+static 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;
+
+ LOGPDPCTXP(LOGL_INFO, pctx, "Received CREATE PDP CTX CONF, cause=%d(%s)\n",
+ cause, get_value_string(gtp_cause_strs, cause));
+
+ if (!pctx->mm) {
+ LOGP(DGPRS, LOGL_INFO,
+ "No MM context, aborting CREATE PDP CTX CONF\n");
+ return -EIO;
+ }
+
+ /* 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, 1);
+ 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 */
+ 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);
+}
+
+#ifdef BUILD_IU
+/* Callback for RAB assignment response */
+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;
+
+}
+#endif
+
+/* 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 */
+ 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");
+ }
+
+ /* unlink the now non-existing library handle from the pdp
+ * context */
+ pctx->lib = NULL;
+
+ sgsn_pdp_ctx_free(pctx);
+
+ return rc;
+}
+
+/* Confirmation of an GTP ECHO request */
+static int echo_conf(struct pdp_t *pdp, void *cbp, int recovery)
+{
+ if (recovery < 0) {
+ LOGP(DGPRS, LOGL_NOTICE, "GTP Echo Request timed out\n");
+ /* FIXME: if version == 1, retry with version 0 */
+ } else {
+ DEBUGP(DGPRS, "GTP Rx Echo Response\n");
+ }
+ return 0;
+}
+
+/* Any message received by GGSN contains a recovery IE */
+static int cb_recovery(struct sockaddr_in *peer, uint8_t recovery)
+{
+ struct sgsn_ggsn_ctx *ggsn;
+
+ 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), "
+ "releasing all PDP contexts\n",
+ ggsn->remote_restart_ctr, recovery);
+ ggsn->remote_restart_ctr = recovery;
+ drop_all_pdp_for_ggsn(ggsn);
+ }
+ 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 */
+ return echo_conf(pdp, cbp, cause);
+ 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)
+{
+ LOGP(DGPRS, LOGL_INFO, "PDP Context was deleted\n");
+ 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 Noficiation "
+ "from %s:%u\n", inet_ntoa(peer->sin_addr),
+ ntohs(peer->sin_port));
+ return 0;
+}
+
+/* Called whenever we recive 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 */
+ 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;
+ }
+ gsn = sgi->gsn;
+
+ 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_recovery(gsn, cb_recovery);
+ gtp_set_cb_data_ind(gsn, cb_data_ind);
+ gtp_set_cb_unsup_ind(gsn, cb_unsup_ind);
+ gtp_set_cb_extheader_ind(gsn, cb_extheader_ind);
+
+ return 0;
+}
diff --git a/src/gprs/sgsn_main.c b/src/gprs/sgsn_main.c
new file mode 100644
index 000000000..04f2825f7
--- /dev/null
+++ b/src/gprs/sgsn_main.c
@@ -0,0 +1,462 @@
+/* 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/ctrl/control_vty.h>
+
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/vty.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/gprs_gmm.h>
+#include <openbsc/iu.h>
+
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+
+#include <gtp.h>
+
+#include "../../bscconfig.h"
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+void *tall_bsc_ctx;
+
+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";
+
+static struct sgsn_instance sgsn_inst = {
+ .config_file = "osmo_sgsn.cfg",
+ .cfg = {
+ .gtp_statedir = "./",
+ .auth_policy = SGSN_AUTH_POLICY_CLOSED,
+ },
+};
+struct sgsn_instance *sgsn = &sgsn_inst;
+
+/* 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:
+ 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_bsc_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;
+
+extern int bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+ .name = "OsmoSGSN",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = bsc_vty_go_parent,
+ .is_config_node = bsc_vty_is_config_node,
+};
+
+static void print_help(void)
+{
+ printf("Some useful help...\n");
+ printf(" -h --help\tthis text\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", sgsn->config_file);
+ 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':
+ sgsn_inst.config_file = strdup(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_DEBUG,
+ },
+ [DNS] = {
+ .name = "DNS",
+ .description = "GPRS Network Service (NS)",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DBSSGP] = {
+ .name = "DBSSGP",
+ .description = "GPRS BSS Gateway Protocol (BSSGP)",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DLLC] = {
+ .name = "DLLC",
+ .description = "GPRS Logical Link Control Protocol (LLC)",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DSNDCP] = {
+ .name = "DSNDCP",
+ .description = "GPRS Sub-Network Dependent Control Protocol (SNDCP)",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DRANAP] = {
+ .name = "DRANAP",
+ .description = "RAN Application Part (RANAP)",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DSUA] = {
+ .name = "DSUA",
+ .description = "SCCP User Adaptation (SUA)",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DSLHC] = {
+ .name = "DSLHC",
+ .description = "RFC1144 TCP/IP Header compression (SLHC)",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DV42BIS] = {
+ .name = "DV42BIS",
+ .description = "V.42bis data compression (SNDCP)",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ }
+};
+
+static const struct log_info gprs_log_info = {
+ .filter_fn = gprs_log_filter_fn,
+ .cat = gprs_categories,
+ .num_cat = ARRAY_SIZE(gprs_categories),
+};
+
+/* Implement the extern asn_debug from libasn1c to indicate whether the ASN.1
+ * binary code decoded and encoded during Iu communication should be logged to
+ * stderr. See osmocom's libasn1c, asn_internal.h, at "if (asn_debug)":
+ * http://git.osmocom.org/libasn1c/tree/include/asn1c/asn_internal.h */
+int asn_debug = 0;
+
+int sgsn_ranap_iu_event(struct ue_conn_ctx *ctx, enum iu_event_type type, void *data);
+
+int main(int argc, char **argv)
+{
+ struct ctrl_handle *ctrl;
+ struct gsm_network dummy_network;
+ int rc;
+
+ srand(time(NULL));
+ tall_bsc_ctx = talloc_named_const(NULL, 0, "osmo_sgsn");
+ msgb_talloc_ctx_init(tall_bsc_ctx, 0);
+
+ signal(SIGINT, &signal_handler);
+ signal(SIGABRT, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+
+ osmo_init_ignore_signals();
+ osmo_init_logging(&gprs_log_info);
+ osmo_stats_init(tall_bsc_ctx);
+
+ vty_info.copyright = openbsc_copyright;
+ vty_init(&vty_info);
+ logging_vty_add_cmds(NULL);
+ osmo_stats_vty_add_cmds(&gprs_log_info);
+ sgsn_vty_init();
+ ctrl_vty_init(tall_bsc_ctx);
+#ifdef BUILD_IU
+ iu_vty_init(&asn_debug);
+#endif
+
+ handle_options(argc, argv);
+
+ rate_ctr_init(tall_bsc_ctx);
+
+ gprs_ns_set_log_ss(DNS);
+ bssgp_set_log_ss(DBSSGP);
+
+ sgsn_nsi = gprs_ns_instantiate(&sgsn_ns_cb, tall_bsc_ctx);
+ if (!sgsn_nsi) {
+ LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n");
+ exit(1);
+ }
+ bssgp_nsi = sgsn_inst.cfg.nsi = sgsn_nsi;
+
+ gprs_llc_init("/usr/local/lib/osmocom/crypt/");
+ sgsn_rate_ctr_init();
+ sgsn_inst_init();
+
+ gprs_ns_vty_init(bssgp_nsi);
+ bssgp_vty_init();
+ gprs_llc_vty_init();
+ gprs_sndcp_vty_init();
+ sgsn_auth_init();
+ sgsn_cdr_init(&sgsn_inst);
+ /* FIXME: register signal handler for SS_L_NS */
+
+ rc = sgsn_parse_config(sgsn_inst.config_file, &sgsn_inst.cfg);
+ 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_bsc_ctx, &dummy_network,
+ 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() */
+ ctrl = sgsn_controlif_setup(NULL, ctrl_vty_get_bind_addr(),
+ OSMO_CTRL_PORT_SGSN);
+ if (!ctrl) {
+ 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_inst);
+ if (rc) {
+ LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on GTP socket\n");
+ exit(2);
+ }
+
+ rc = gprs_subscr_init(&sgsn_inst);
+ 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);
+ }
+ }
+
+#ifdef BUILD_IU
+ iu_init(tall_bsc_ctx, "127.0.0.2", 14001, gsm0408_gprs_rcvmsg_iu, sgsn_ranap_iu_event);
+#endif
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (1) {
+ rc = osmo_select_main(0);
+ if (rc < 0)
+ exit(3);
+ }
+
+ /* not reached */
+ exit(0);
+}
diff --git a/src/gprs/sgsn_vty.c b/src/gprs/sgsn_vty.c
new file mode 100644
index 000000000..e09a0296b
--- /dev/null
+++ b/src/gprs/sgsn_vty.c
@@ -0,0 +1,1323 @@
+/*
+ * (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 <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/sgsn.h>
+#include <osmocom/gprs/gprs_ns.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/vty.h>
+#include <openbsc/gsup_client.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 <pdp.h>
+
+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 */
+
+#define DECLARE_TIMER(number, doc) \
+ DEFUN(cfg_sgsn_T##number, \
+ cfg_sgsn_T##number##_cmd, \
+ "timer t" #number " <0-65535>", \
+ "Configure GPRS Timers\n" \
+ doc "\nTimer Value in seconds\n") \
+{ \
+ int value = atoi(argv[0]); \
+ \
+ if (value < 0 || value > 65535) { \
+ vty_out(vty, "Timer value %s out of range.%s", \
+ argv[0], VTY_NEWLINE); \
+ return CMD_WARNING; \
+ } \
+ \
+ g_cfg->timers.T##number = value; \
+ return CMD_SUCCESS; \
+}
+
+DECLARE_TIMER(3312, "Periodic RA Update timer (s)")
+DECLARE_TIMER(3322, "Detach request -> accept timer (s)")
+DECLARE_TIMER(3350, "Waiting for ATT/RAU/TMSI_COMPL timer (s)")
+DECLARE_TIMER(3360, "Waiting for AUTH/CIPH response timer (s)")
+DECLARE_TIMER(3370, "Waiting for IDENTITY response timer (s)")
+
+DECLARE_TIMER(3313, "Waiting for paging response timer (s)")
+DECLARE_TIMER(3314, "Force to STANDBY on expiry timer (s)")
+DECLARE_TIMER(3316, "AA-Ready timer (s)")
+
+DECLARE_TIMER(3385, "Wait for ACT PDP CTX REQ timer (s)")
+DECLARE_TIMER(3386, "Wait for MODIFY PDP CTX ACK timer (s)")
+DECLARE_TIMER(3395, "Wait for DEACT PDP CTX ACK timer (s)")
+DECLARE_TIMER(3397, "Wait for DEACT AA PDP CTX ACK timer (s)")
+
+
+#define GSM48_MAX_APN_LEN 102 /* 10.5.6.1 */
+/* TODO: consolidate with gprs_apn_to_str(). */
+/** Copy apn to a static buffer, replacing the length octets in apn_enc with '.'
+ * and terminating with a '\0'. Return the static buffer.
+ * len: the length of the encoded APN (which has no terminating zero).
+ */
+static char *gprs_apn2str(uint8_t *apn, unsigned int len)
+{
+ static char apnbuf[GSM48_MAX_APN_LEN+1];
+ unsigned int i = 0;
+
+ if (!apn)
+ return "";
+
+ if (len > sizeof(apnbuf)-1)
+ len = sizeof(apnbuf)-1;
+
+ memcpy(apnbuf, apn, len);
+ apnbuf[len] = '\0';
+
+ /* replace the domain name step sizes with dots */
+ while (i < len) {
+ unsigned int step = apnbuf[i];
+ apnbuf[i] = '.';
+ i += step+1;
+ }
+
+ return apnbuf+1;
+}
+
+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 (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->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);
+ 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);
+ vty_out(vty, " cdr interval %d%s", g_cfg->cdr.interval, VTY_NEWLINE);
+
+ vty_out(vty, " timer t3312 %d%s", g_cfg->timers.T3312, VTY_NEWLINE);
+ vty_out(vty, " timer t3322 %d%s", g_cfg->timers.T3322, VTY_NEWLINE);
+ vty_out(vty, " timer t3350 %d%s", g_cfg->timers.T3350, VTY_NEWLINE);
+ vty_out(vty, " timer t3360 %d%s", g_cfg->timers.T3360, VTY_NEWLINE);
+ vty_out(vty, " timer t3370 %d%s", g_cfg->timers.T3370, VTY_NEWLINE);
+ vty_out(vty, " timer t3313 %d%s", g_cfg->timers.T3313, VTY_NEWLINE);
+ vty_out(vty, " timer t3314 %d%s", g_cfg->timers.T3314, VTY_NEWLINE);
+ vty_out(vty, " timer t3316 %d%s", g_cfg->timers.T3316, VTY_NEWLINE);
+ vty_out(vty, " timer t3385 %d%s", g_cfg->timers.T3385, VTY_NEWLINE);
+ vty_out(vty, " timer t3386 %d%s", g_cfg->timers.T3386, VTY_NEWLINE);
+ vty_out(vty, " timer t3395 %d%s", g_cfg->timers.T3395, VTY_NEWLINE);
+ vty_out(vty, " timer t3397 %d%s", g_cfg->timers.T3397, VTY_NEWLINE);
+
+ 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);
+
+ 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\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" IP_STR "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;
+}
+
+DEFUN(cfg_ggsn_dynamic_lookup, cfg_ggsn_dynamic_lookup_cmd,
+ "ggsn dynamic",
+ GGSN_STR "Enable dynamic GRX based look-up (requires 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",
+ "Add DNS server\nIPv4 address\n")
+{
+ struct ares_addr_node *node = talloc_zero(tall_bsc_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 when the APN gateway prefix matches\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
+ "Restrict rule to a certain IMSI 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 }
+};
+
+static char *gtp_ntoa(struct ul16_t *ul)
+{
+ if (ul->l == 4) {
+ struct in_addr *ia = (struct in_addr *) ul;
+ return inet_ntoa(*ia);
+ } else {
+ return "UNKNOWN";
+ }
+}
+
+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);
+ vty_out(vty, "%s APN: %s%s", pfx,
+ gprs_apn2str(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 GTP Local Control(%s / TEIC: 0x%08x) ", pfx,
+ gtp_ntoa(&pdp->lib->gsnlc), pdp->lib->teic_own);
+ vty_out(vty, "Data(%s / TEID: 0x%08x)%s",
+ gtp_ntoa(&pdp->lib->gsnlu), pdp->lib->teid_own, VTY_NEWLINE);
+ vty_out(vty, "%s GTP Remote Control(%s / TEIC: 0x%08x) ", pfx,
+ gtp_ntoa(&pdp->lib->gsnrc), pdp->lib->teic_gn);
+ vty_out(vty, "Data(%s / TEID: 0x%08x)%s",
+ 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)
+{
+ 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, mm->gb.tlli, mm->hlr, VTY_NEWLINE);
+ vty_out(vty, "%s MM State: %s, Routeing Area: %u-%u-%u-%u, "
+ "Cell ID: %u%s", pfx,
+ get_value_string(gprs_mm_st_strs, mm->gmm_state),
+ mm->ra.mcc, mm->ra.mnc, mm->ra.lac, mm->ra.rac,
+ mm->gb.cell_id, 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);
+ }
+ /* 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;
+ int rc;
+
+ /* Sanitize IMSI */
+ if (strlen(argv[1]) > GSM23003_IMSI_MAX_DIGITS) {
+ vty_out(vty, "%% IMSI (%s) too long -- ignored!%s",
+ argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ memset(imsi_sanitized, '0', sizeof(imsi_sanitized));
+ strcpy(imsi_sanitized+GSM23003_IMSI_MAX_DIGITS-strlen(argv[1]),argv[1]);
+
+ 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_auth_policy, cfg_auth_policy_cmd,
+ "auth-policy (accept-all|closed|acl-only|remote)",
+ "Autorization Policy of SGSN\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_authentication = (val == SGSN_AUTH_POLICY_REMOTE);
+ g_cfg->require_update_location = (val == SGSN_AUTH_POLICY_REMOTE);
+
+ return CMD_SUCCESS;
+}
+
+/* Subscriber */
+#include <openbsc/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(at->vec.rand,
+ sizeof(at->vec.rand)));
+ vty_out(vty, "SRES: %s, ",
+ osmo_hexdump(at->vec.sres,
+ sizeof(at->vec.sres)));
+ vty_out(vty, "Kc: %s%s",
+ osmo_hexdump(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(at->vec.res, at->vec.res_len));
+ vty_out(vty, "IK: %s, ",
+ osmo_hexdump(at->vec.ik, sizeof(at->vec.ik)));
+ vty_out(vty, "CK: %s, ",
+ osmo_hexdump(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' QoS: %s%s",
+ pdp->context_id, pdp->pdp_type, pdp->apn_str,
+ osmo_hexdump(pdp->qos_subscribed, pdp->qos_subscribed_len),
+ 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);
+}
+
+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_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\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\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 SGSN's OAP client ID\nOAP 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 K\nK 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\nOPC 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",
+ "Configure a global list of allowed APNs\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\nSet filename\nname\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 CDR generation\n")
+{
+ talloc_free(g_cfg->cdr.filename);
+ g_cfg->cdr.filename = NULL;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cdr_interval, cfg_cdr_interval_cmd,
+ "cdr interval <1-2147483647>",
+ "CDR\nPDP periodic log interval\nSeconds\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 compresion 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 compresion 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 compresion 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 compresion scheme\n"
+ "Compression is available on request\n")
+{
+ g_cfg->dcomp_v42bis.active = 0;
+ g_cfg->dcomp_v42bis.passive = 1;
+ return CMD_SUCCESS;
+}
+
+int sgsn_vty_init(void)
+{
+ 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(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(CONFIG_NODE, &cfg_sgsn_cmd);
+ install_node(&sgsn_node, config_write_sgsn);
+ vty_install_default(SGSN_NODE);
+ 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_imsi_acl_cmd);
+ install_element(SGSN_NODE, &cfg_auth_policy_cmd);
+ install_element(SGSN_NODE, &cfg_encrypt_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_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_T3312_cmd);
+ install_element(SGSN_NODE, &cfg_sgsn_T3322_cmd);
+ install_element(SGSN_NODE, &cfg_sgsn_T3350_cmd);
+ install_element(SGSN_NODE, &cfg_sgsn_T3360_cmd);
+ install_element(SGSN_NODE, &cfg_sgsn_T3370_cmd);
+ install_element(SGSN_NODE, &cfg_sgsn_T3313_cmd);
+ install_element(SGSN_NODE, &cfg_sgsn_T3314_cmd);
+ install_element(SGSN_NODE, &cfg_sgsn_T3316_cmd);
+ install_element(SGSN_NODE, &cfg_sgsn_T3385_cmd);
+ install_element(SGSN_NODE, &cfg_sgsn_T3386_cmd);
+ install_element(SGSN_NODE, &cfg_sgsn_T3395_cmd);
+ install_element(SGSN_NODE, &cfg_sgsn_T3397_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);
+ return 0;
+}
+
+int sgsn_parse_config(const char *config_file, struct sgsn_config *cfg)
+{
+ int rc;
+
+ g_cfg = cfg;
+
+ g_cfg->timers.T3312 = GSM0408_T3312_SECS;
+ g_cfg->timers.T3322 = GSM0408_T3322_SECS;
+ g_cfg->timers.T3350 = GSM0408_T3350_SECS;
+ g_cfg->timers.T3360 = GSM0408_T3360_SECS;
+ g_cfg->timers.T3370 = GSM0408_T3370_SECS;
+ g_cfg->timers.T3313 = GSM0408_T3313_SECS;
+ g_cfg->timers.T3314 = GSM0408_T3314_SECS;
+ g_cfg->timers.T3316 = GSM0408_T3316_SECS;
+ g_cfg->timers.T3385 = GSM0408_T3385_SECS;
+ g_cfg->timers.T3386 = GSM0408_T3386_SECS;
+ g_cfg->timers.T3395 = GSM0408_T3395_SECS;
+ g_cfg->timers.T3397 = GSM0408_T3397_SECS;
+
+ rc = vty_read_config_file(config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+ return rc;
+ }
+
+ if (g_cfg->auth_policy == SGSN_AUTH_POLICY_REMOTE
+ && !(g_cfg->gsup_server_addr.sin_addr.s_addr
+ && g_cfg->gsup_server_port)) {
+ fprintf(stderr, "Configuration error:"
+ " 'auth-policy remote' requires both"
+ " 'gsup remote-ip' and 'gsup remote-port'\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
diff --git a/src/gprs/slhc.c b/src/gprs/slhc.c
new file mode 100644
index 000000000..cbdf8dbd8
--- /dev/null
+++ b/src/gprs/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 <openbsc/slhc.h>
+#include <openbsc/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/gprs/v42bis.c b/src/gprs/v42bis.c
new file mode 100644
index 000000000..a04b0af5c
--- /dev/null
+++ b/src/gprs/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., 675 Mass Ave, Cambridge, MA 02139, 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 <openbsc/v42bis.h>
+#include <openbsc/v42bis_private.h>
+#include <openbsc/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 ------------------------------------------------------------*/