From 9d016fd499cb23ad6d9e4c2757dbc4e1c154107d Mon Sep 17 00:00:00 2001 From: Pau Espin Pedrol Date: Fri, 30 Aug 2019 19:48:34 +0200 Subject: Move out gbproxy to its own subdir Change-Id: I2cc98d3a276d953609bbbbaa9782a0112687791e --- src/gbproxy/Makefile.am | 46 ++ src/gbproxy/gb_proxy.c | 1454 ++++++++++++++++++++++++++++++++++++++++++ src/gbproxy/gb_proxy_ctrl.c | 98 +++ src/gbproxy/gb_proxy_main.c | 393 ++++++++++++ src/gbproxy/gb_proxy_patch.c | 465 ++++++++++++++ src/gbproxy/gb_proxy_peer.c | 240 +++++++ src/gbproxy/gb_proxy_tlli.c | 723 +++++++++++++++++++++ src/gbproxy/gb_proxy_vty.c | 926 +++++++++++++++++++++++++++ 8 files changed, 4345 insertions(+) create mode 100644 src/gbproxy/Makefile.am create mode 100644 src/gbproxy/gb_proxy.c create mode 100644 src/gbproxy/gb_proxy_ctrl.c create mode 100644 src/gbproxy/gb_proxy_main.c create mode 100644 src/gbproxy/gb_proxy_patch.c create mode 100644 src/gbproxy/gb_proxy_peer.c create mode 100644 src/gbproxy/gb_proxy_tlli.c create mode 100644 src/gbproxy/gb_proxy_vty.c (limited to 'src/gbproxy') diff --git a/src/gbproxy/Makefile.am b/src/gbproxy/Makefile.am new file mode 100644 index 000000000..6876f68a5 --- /dev/null +++ b/src/gbproxy/Makefile.am @@ -0,0 +1,46 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -fno-strict-aliasing \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(LIBOSMOGB_CFLAGS) \ + $(LIBOSMOGSUPCLIENT_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(LIBGTP_CFLAGS) \ + $(NULL) + +bin_PROGRAMS = \ + osmo-gbproxy \ + $(NULL) + +osmo_gbproxy_SOURCES = \ + gb_proxy.c \ + gb_proxy_main.c \ + gb_proxy_vty.c \ + gb_proxy_ctrl.c \ + gb_proxy_patch.c \ + gb_proxy_tlli.c \ + gb_proxy_peer.c \ + $(NULL) +osmo_gbproxy_LDADD = \ + $(top_builddir)/src/gprs/gprs_gb_parse.o \ + $(top_builddir)/src/gprs/gprs_llc_parse.o \ + $(top_builddir)/src/gprs/crc24.o \ + $(top_builddir)/src/gprs/gprs_utils.o \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOGB_LIBS) \ + $(LIBGTP_LIBS) \ + -lrt \ + $(NULL) diff --git a/src/gbproxy/gb_proxy.c b/src/gbproxy/gb_proxy.c new file mode 100644 index 000000000..3f471ff82 --- /dev/null +++ b/src/gbproxy/gb_proxy.c @@ -0,0 +1,1454 @@ +/* NS-over-IP proxy */ + +/* (C) 2010 by Harald Welte + * (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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +extern void *tall_sgsn_ctx; + +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 struct osmo_plmn_id old_plmn = state->local_plmn; + struct gprs_ra_id raid; + + if (!raid_enc) + return; + + gsm48_parse_ra(&raid, raid_enc); + + /* save source side MCC/MNC */ + if (!peer->cfg->core_plmn.mcc || raid.mcc == peer->cfg->core_plmn.mcc) { + state->local_plmn.mcc = 0; + } else { + state->local_plmn.mcc = raid.mcc; + } + + if (!peer->cfg->core_plmn.mnc + || !osmo_mnc_cmp(raid.mnc, raid.mnc_3_digits, + peer->cfg->core_plmn.mnc, peer->cfg->core_plmn.mnc_3_digits)) { + state->local_plmn.mnc = 0; + state->local_plmn.mnc_3_digits = false; + } else { + state->local_plmn.mnc = raid.mnc; + state->local_plmn.mnc_3_digits = raid.mnc_3_digits; + } + + if (osmo_plmn_cmp(&old_plmn, &state->local_plmn)) + LOGP(DGPRS, LOGL_NOTICE, + "Patching RAID %sactivated, msg: %s, " + "local: %s, core: %s\n", + state->local_plmn.mcc || state->local_plmn.mnc ? + "" : "de", + log_text, + osmo_plmn_name(&state->local_plmn), + osmo_plmn_name2(&peer->cfg->core_plmn)); +} + +uint32_t gbproxy_make_bss_ptmsi(struct gbproxy_peer *peer, + uint32_t sgsn_ptmsi) +{ + uint32_t bss_ptmsi; + int max_retries = 23, rc = 0; + if (!peer->cfg->patch_ptmsi) { + bss_ptmsi = sgsn_ptmsi; + } else { + do { + rc = osmo_get_rand_id((uint8_t *) &bss_ptmsi, sizeof(bss_ptmsi)); + if (rc < 0) { + bss_ptmsi = GSM_RESERVED_TMSI; + break; + } + + bss_ptmsi = bss_ptmsi | GSM23003_TMSI_SGSN_MASK; + + 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: %d (%s)\n", rc, strerror(-rc)); + + 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, rc = 0; + 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... */ + rc = osmo_get_rand_id((uint8_t *) &sgsn_tlli, sizeof(sgsn_tlli)); + if (rc < 0) { + 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: %d (%s)\n", rc, strerror(-rc)); + + 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 = false; + + 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; +} + +/* Got identity response with IMSI, assuming the request had + * been generated by the gbproxy */ +static int gbproxy_flush_stored_messages(struct gbproxy_peer *peer, + time_t now, + struct gbproxy_link_info* link_info) +{ + int rc; + struct msgb *stored_msg; + + /* Patch and flush stored messages towards the SGSN */ + while ((stored_msg = msgb_dequeue_count(&link_info->stored_msgs, + &link_info->stored_msgs_len))) { + 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(stored_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(stored_msg), link_info->sgsn_nsei); + + if (rc < 0) + LOGP(DLLC, LOGL_ERROR, + "NSEI=%d(BSS) failed to send stored message " + "(%s)\n", + tmp_parse_ctx.peer_nsei, + tmp_parse_ctx.llc_msg_name ? + tmp_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; + + LOGP(DLLC, LOGL_DEBUG, + "NSEI=%d(BSS) IMSI acquisition succeeded, " + "flushing stored messages\n", + msgb_nsei(msg)); + /* 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, now, link_info) < 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 */ + + /* If queue is getting too large, drop oldest msgb before adding new one */ + if (peer->cfg->stored_msgs_max_len > 0) { + int exceeded_max_len = link_info->stored_msgs_len + + 1 - peer->cfg->stored_msgs_max_len; + + for (; exceeded_max_len > 0; exceeded_max_len--) { + struct msgb *msgb_drop; + msgb_drop = msgb_dequeue_count(&link_info->stored_msgs, + &link_info->stored_msgs_len); + LOGP(DLLC, LOGL_INFO, + "NSEI=%d(BSS) Dropping stored msgb from list " + "(!acq imsi, length %d, max_len exceeded)\n", + msgb_nsei(msgb_drop), link_info->stored_msgs_len); + + msgb_free(msgb_drop); + } + } + + /* 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 = bssgp_msgb_copy(msg, "process_bssgp_ul"); + msgb_enqueue_count(&link_info->stored_msgs, stored_msg, + &link_info->stored_msgs_len); + + 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 = true; + } + + 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_plmn.mcc && !cfg->core_plmn.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; + + + osmo_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_plmn.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_plmn.mcc && !cfg->core_plmn.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; + + osmo_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 = bssgp_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 = bssgp_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 = true; + rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_BLOCKED]); + break; + case BSSGP_PDUT_BVC_UNBLOCK_ACK: + peer->blocked = false; + 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 %s behind BVCI=%u\n", + nsei, osmo_rai_name(&raid), 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); + OSMO_ASSERT(from_peer); + 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 %s\n", + nsei, bvci, osmo_rai_name(&raid)); + } + 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 = bssgp_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 %s not supported\n", bssgp_pdu_str(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 '%s' for unknown peer NSEI=%u/NSVCI=%u\n", + get_value_string(gprs_ns_signal_ns_names, 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 '%s' from NSEI=%u/NSVCI=%u into BSSGP_BVC_BLOCK to SGSN\n", + get_value_string(gprs_ns_signal_ns_names, signal), 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_sgsn_ctx, &global_ctrg_desc, 0); + if (!cfg->ctrg) { + LOGP(DGPRS, LOGL_ERROR, "Cannot allocate global counter group!\n"); + return -1; + } + osmo_clock_gettime(CLOCK_REALTIME, &tp); + + return 0; +} diff --git a/src/gbproxy/gb_proxy_ctrl.c b/src/gbproxy/gb_proxy_ctrl.c new file mode 100644 index 000000000..4b7c2f430 --- /dev/null +++ b/src/gbproxy/gb_proxy_ctrl.c @@ -0,0 +1,98 @@ +/* Control Interface Implementation for the Gb-proxy */ +/* + * (C) 2018 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Daniel Willmann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + + +#include +#include + +#include +#include +#include +#include + +extern vector ctrl_node_vec; + +static int get_nsvc_state(struct ctrl_cmd *cmd, void *data) +{ + struct gbproxy_config *cfg = data; + struct gprs_ns_inst *nsi = cfg->nsi; + struct gprs_nsvc *nsvc; + + cmd->reply = talloc_strdup(cmd, ""); + + llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) { + if (nsvc == nsi->unknown_nsvc) + continue; + + cmd->reply = gprs_nsvc_state_append(cmd->reply, nsvc); + } + + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_RO(nsvc_state, "nsvc-state"); + +static int get_gbproxy_state(struct ctrl_cmd *cmd, void *data) +{ + struct gbproxy_config *cfg = data; + struct gbproxy_peer *peer; + + cmd->reply = talloc_strdup(cmd, ""); + + llist_for_each_entry(peer, &cfg->bts_peers, list) { + struct gprs_ra_id raid; + gsm48_parse_ra(&raid, peer->ra); + + cmd->reply = talloc_asprintf_append(cmd->reply, "%u,%u,%u,%u,%u,%u,%s\n", + peer->nsei, peer->bvci, + raid.mcc, raid.mnc, + raid.lac, raid.rac, + peer->blocked ? "BLOCKED" : "UNBLOCKED"); + } + + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_RO(gbproxy_state, "gbproxy-state"); + +static int get_num_peers(struct ctrl_cmd *cmd, void *data) +{ + struct gbproxy_config *cfg = data; + + cmd->reply = talloc_strdup(cmd, ""); + cmd->reply = talloc_asprintf_append(cmd->reply, "%u", llist_count(&cfg->bts_peers)); + + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_RO(num_peers, "number-of-peers"); + +int gb_ctrl_cmds_install(void) +{ + int rc = 0; + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_nsvc_state); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_gbproxy_state); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_num_peers); + + return rc; +} diff --git a/src/gbproxy/gb_proxy_main.c b/src/gbproxy/gb_proxy_main.c new file mode 100644 index 000000000..4319fda63 --- /dev/null +++ b/src/gbproxy/gb_proxy_main.c @@ -0,0 +1,393 @@ +/* NS-over-IP proxy */ + +/* (C) 2010 by Harald Welte + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../../bscconfig.h" + +#define _GNU_SOURCE +#include + +void *tall_sgsn_ctx; + +const char *openbsc_copyright = + "Copyright (C) 2010 Harald Welte and On-Waves\r\n" + "License AGPLv3+: GNU AGPL version 3 or later \r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +#define CONFIG_FILE_DEFAULT "osmo-gbproxy.cfg" +#define CONFIG_FILE_LEGACY "osmo_gbproxy.cfg" + +static char *config_file = NULL; +struct gbproxy_config *gbcfg; +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: + case SIGTERM: + osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL); + sleep(1); + exit(0); + break; + case SIGABRT: + /* in case of abort, we want to obtain a talloc report + * and then return to the caller, who will abort the process */ + case SIGUSR1: + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(tall_sgsn_ctx, stderr); + break; + case SIGUSR2: + talloc_report_full(tall_vty_ctx, stderr); + break; + default: + break; + } +} + +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 [%s]\n", CONFIG_FILE_DEFAULT); + printf(" -s --disable-color\n"); + printf(" -T --timestamp Prefix every log line with a timestamp\n"); + printf(" -V --version. Print the version.\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; + } + } +} + +int gbproxy_vty_is_config_node(struct vty *vty, int node) +{ + switch (node) { + /* add items that are not config */ + case CONFIG_NODE: + return 0; + + default: + return 1; + } +} + +int gbproxy_vty_go_parent(struct vty *vty) +{ + switch (vty->node) { + case GBPROXY_NODE: + default: + if (gbproxy_vty_is_config_node(vty, vty->node)) + vty->node = CONFIG_NODE; + else + vty->node = ENABLE_NODE; + + vty->index = NULL; + } + + return vty->node; +} + +static struct vty_app_info vty_info = { + .name = "OsmoGbProxy", + .version = PACKAGE_VERSION, + .go_parent_cb = gbproxy_vty_go_parent, + .is_config_node = gbproxy_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), +}; + +static bool file_exists(const char *path) +{ + struct stat sb; + return stat(path, &sb) ? false : true; +} + +int main(int argc, char **argv) +{ + int rc; + struct ctrl_handle *ctrl; + + tall_sgsn_ctx = talloc_named_const(NULL, 0, "nsip_proxy"); + msgb_talloc_ctx_init(tall_sgsn_ctx, 0); + vty_info.tall_ctx = tall_sgsn_ctx; + + signal(SIGINT, &signal_handler); + signal(SIGTERM, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + osmo_init_ignore_signals(); + + osmo_init_logging2(tall_sgsn_ctx, &gprs_log_info); + + vty_info.copyright = openbsc_copyright; + vty_init(&vty_info); + logging_vty_add_cmds(); + osmo_talloc_vty_add_cmds(); + osmo_stats_vty_add_cmds(); + gbproxy_vty_init(); + + handle_options(argc, argv); + + /* Backwards compatibility: for years, the default config file name was + * osmo_gbproxy.cfg. All other Osmocom programs use osmo-*.cfg with a + * dash. To be able to use the new config file name without breaking + * previous setups that might rely on the legacy default config file + * name, we need to look for the old config file if no -c option was + * passed AND no file exists with the new default file name. */ + if (!config_file) { + /* No -c option was passed */ + if (file_exists(CONFIG_FILE_LEGACY) + && !file_exists(CONFIG_FILE_DEFAULT)) + config_file = CONFIG_FILE_LEGACY; + else + config_file = CONFIG_FILE_DEFAULT; + } + + rate_ctr_init(tall_sgsn_ctx); + osmo_stats_init(tall_sgsn_ctx); + + bssgp_nsi = gprs_ns_instantiate(&proxy_ns_cb, tall_sgsn_ctx); + if (!bssgp_nsi) { + LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n"); + exit(1); + } + + gbcfg = talloc_zero(tall_sgsn_ctx, struct gbproxy_config); + if (!gbcfg) { + LOGP(DGPRS, LOGL_FATAL, "Unable to allocate config\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 '%s'\n", config_file); + exit(2); + } + + /* start telnet after reading config for vty_get_bind_addr() */ + rc = telnet_init_dynif(tall_sgsn_ctx, NULL, + vty_get_bind_addr(), OSMO_VTY_PORT_GBPROXY); + if (rc < 0) + exit(1); + + /* Start control interface after getting config for + * ctrl_vty_get_bind_addr() */ + ctrl = ctrl_interface_setup_dynip(gbcfg, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_GBPROXY, NULL); + if (!ctrl) { + LOGP(DGPRS, LOGL_FATAL, "Failed to create CTRL interface.\n"); + exit(1); + } + + if (gb_ctrl_cmds_install() != 0) { + LOGP(DGPRS, LOGL_FATAL, "Failed to install CTRL commands.\n"); + 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/gbproxy/gb_proxy_patch.c b/src/gbproxy/gb_proxy_patch.c new file mode 100644 index 000000000..6235b04f4 --- /dev/null +++ b/src/gbproxy/gb_proxy_patch.c @@ -0,0 +1,465 @@ +/* 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 . + * + */ + +#include + +#include +#include + +#include + +#include +#include +#include + +extern void *tall_sgsn_ctx; + +/* patch RA identifier in place */ +static void gbproxy_patch_raid(struct gsm48_ra_id *raid_enc, struct gbproxy_peer *peer, + int to_bss, const char *log_text) +{ + struct gbproxy_patch_state *state = &peer->patch_state; + struct osmo_plmn_id old_plmn; + 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_plmn.mcc || !state->local_plmn.mnc) + return; + + gsm48_parse_ra(&raid, (uint8_t *)raid_enc); + + old_plmn = (struct osmo_plmn_id){ + .mcc = raid.mcc, + .mnc = raid.mnc, + .mnc_3_digits = raid.mnc_3_digits, + }; + + if (!to_bss) { + /* BSS -> SGSN */ + if (state->local_plmn.mcc) + raid.mcc = peer->cfg->core_plmn.mcc; + + if (state->local_plmn.mnc) { + raid.mnc = peer->cfg->core_plmn.mnc; + raid.mnc_3_digits = peer->cfg->core_plmn.mnc_3_digits; + } + } else { + /* SGSN -> BSS */ + if (state->local_plmn.mcc) + raid.mcc = state->local_plmn.mcc; + + if (state->local_plmn.mnc) { + raid.mnc = state->local_plmn.mnc; + raid.mnc_3_digits = state->local_plmn.mnc_3_digits; + } + } + + LOGP(DGPRS, LOGL_DEBUG, + "Patching %s to %s: " + "%s-%d-%d -> %s\n", + log_text, + to_bss ? "BSS" : "SGSN", + osmo_plmn_name(&old_plmn), raid.lac, raid.rac, + osmo_rai_name(&raid)); + + gsm48_encode_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, + osmo_apn_to_str(str1, apn, apn_len)); + + *new_apn_ie_len = 0; + 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, + osmo_apn_to_str(str1, apn, apn_len), + osmo_apn_to_str(str2, peer->cfg->core_apn, + peer->cfg->core_apn_size)); + + *new_apn_ie_len = peer->cfg->core_apn_size + 2; + 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((struct gsm48_ra_id *)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((struct gsm48_ra_id *)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_plmn.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((struct gsm48_ra_id *)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 = false; + } + 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 = true; + match->re_str = talloc_strdup(tall_sgsn_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/gbproxy/gb_proxy_peer.c b/src/gbproxy/gb_proxy_peer.c new file mode 100644 index 000000000..48482b6a1 --- /dev/null +++ b/src/gbproxy/gb_proxy_peer.c @@ -0,0 +1,240 @@ +/* Gb proxy peer handling */ + +/* (C) 2010 by Harald Welte + * (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 . + * + */ + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +extern void *tall_sgsn_ctx; + +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; +} + +static void clean_stale_timer_cb(void *data) +{ + time_t now; + struct timespec ts = {0,}; + struct gbproxy_peer *peer = (struct gbproxy_peer *) data; + + osmo_clock_gettime(CLOCK_MONOTONIC, &ts); + now = ts.tv_sec; + gbproxy_remove_stale_link_infos(peer, now); + if (peer->cfg->clean_stale_timer_freq != 0) + osmo_timer_schedule(&peer->clean_stale_timer, + peer->cfg->clean_stale_timer_freq, 0); +} + +struct gbproxy_peer *gbproxy_peer_alloc(struct gbproxy_config *cfg, uint16_t bvci) +{ + struct gbproxy_peer *peer; + + peer = talloc_zero(tall_sgsn_ctx, struct gbproxy_peer); + if (!peer) + return NULL; + + peer->bvci = bvci; + peer->ctrg = rate_ctr_group_alloc(peer, &peer_ctrg_desc, bvci); + if (!peer->ctrg) { + talloc_free(peer); + return NULL; + } + peer->cfg = cfg; + + llist_add(&peer->list, &cfg->bts_peers); + + INIT_LLIST_HEAD(&peer->patch_state.logical_links); + + osmo_timer_setup(&peer->clean_stale_timer, clean_stale_timer_cb, peer); + if (peer->cfg->clean_stale_timer_freq != 0) + osmo_timer_schedule(&peer->clean_stale_timer, + peer->cfg->clean_stale_timer_freq, 0); + + return peer; +} + +void gbproxy_peer_free(struct gbproxy_peer *peer) +{ + llist_del(&peer->list); + osmo_timer_del(&peer->clean_stale_timer); + 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/gbproxy/gb_proxy_tlli.c b/src/gbproxy/gb_proxy_tlli.c new file mode 100644 index 000000000..4e21ede86 --- /dev/null +++ b/src/gbproxy/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 . + * + */ + +#include + +#include + +#include +#include + +#include + +#include + +#include +#include + +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 = false; + tlli_state->net_validated = false; +} + +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 = true; + else + tlli_state->bss_validated = true; + + 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 = true; + + 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]; +} + +static 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 ? true : false; + } +} + +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 = false; + + 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 = false; + + 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/gbproxy/gb_proxy_vty.c b/src/gbproxy/gb_proxy_vty.c new file mode 100644 index 000000000..5c4f45420 --- /dev/null +++ b/src/gbproxy/gb_proxy_vty.c @@ -0,0 +1,926 @@ +/* + * (C) 2010 by Harald Welte + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +static struct gbproxy_config *g_cfg = NULL; + +/* + * vty code for gbproxy 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 %s", peer->nsei, peer->bvci, osmo_rai_name(&raid)); + 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_plmn.mcc > 0) + vty_out(vty, " core-mobile-country-code %s%s", + osmo_mcc_name(g_cfg->core_plmn.mcc), VTY_NEWLINE); + if (g_cfg->core_plmn.mnc > 0) + vty_out(vty, " core-mobile-network-code %s%s", + osmo_mnc_name(g_cfg->core_plmn.mnc, g_cfg->core_plmn.mnc_3_digits), 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", + osmo_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->clean_stale_timer_freq > 0) + vty_out(vty, " link-list clean-stale-timer %u%s", + g_cfg->clean_stale_timer_freq, 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); + if (g_cfg->stored_msgs_max_len > 0) + vty_out(vty, " link stored-msgs-max-length %"PRIu32"%s", + g_cfg->stored_msgs_max_len, 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") +{ + uint16_t mnc; + bool mnc_3_digits; + if (osmo_mnc_from_str(argv[0], &mnc, &mnc_3_digits)) { + vty_out(vty, "%% Invalid MNC: %s%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + g_cfg->core_plmn.mnc = mnc; + g_cfg->core_plmn.mnc_3_digits = mnc_3_digits; + 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_plmn.mnc = 0; + g_cfg->core_plmn.mnc_3_digits = false; + 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_plmn.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_plmn.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 = true; + + 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 = false; + + 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 = true; + + 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 = false; + + 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 = true; + + 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 = false; + + 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 = true; + g_cfg->nsip_sgsn2_nsei = nsei; + + g_cfg->patch_ptmsi = true; + + 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 = false; + g_cfg->nsip_sgsn2_nsei = 0xFFFF; + + g_cfg->patch_ptmsi = false; + + return CMD_SUCCESS; +} + +#define GBPROXY_LINK_LIST_STR "Set TLLI list parameters\n" +#define GBPROXY_LINK_STR "Set TLLI parameters\n" + +#define GBPROXY_CLEAN_STALE_TIMER_STR "Periodic timer to clean stale links\n" + +DEFUN(cfg_gbproxy_link_list_clean_stale_timer, + cfg_gbproxy_link_list_clean_stale_timer_cmd, + "link-list clean-stale-timer <1-999999>", + GBPROXY_LINK_LIST_STR GBPROXY_CLEAN_STALE_TIMER_STR + "Frequency at which the periodic timer is fired (in seconds)\n") +{ + struct gbproxy_peer *peer; + g_cfg->clean_stale_timer_freq = (unsigned int) atoi(argv[0]); + + /* Re-schedule running timers soon in case prev frequency was really big + and new frequency is desired to be lower. After initial run, periodic + time is used. Use random() to avoid firing timers for all peers at + the same time */ + llist_for_each_entry(peer, &g_cfg->bts_peers, list) + osmo_timer_schedule(&peer->clean_stale_timer, + random() % 5, random() % 1000000); + + return CMD_SUCCESS; +} + +DEFUN(cfg_gbproxy_link_list_no_clean_stale_timer, + cfg_gbproxy_link_list_no_clean_stale_timer_cmd, + "no link-list clean-stale-timer", + NO_STR GBPROXY_LINK_LIST_STR GBPROXY_CLEAN_STALE_TIMER_STR) + +{ + struct gbproxy_peer *peer; + g_cfg->clean_stale_timer_freq = 0; + + llist_for_each_entry(peer, &g_cfg->bts_peers, list) + osmo_timer_del(&peer->clean_stale_timer); + + return CMD_SUCCESS; +} + +#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(cfg_gbproxy_link_stored_msgs_max_len, + cfg_gbproxy_link_stored_msgs_max_len_cmd, + "link stored-msgs-max-length <1-99999>", + GBPROXY_LINK_STR GBPROXY_MAX_LEN_STR + "Maximum number of msgb stored in the logical link waiting to acquire its IMSI\n") +{ + g_cfg->stored_msgs_max_len = (uint32_t) atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_gbproxy_link_no_stored_msgs_max_len, + cfg_gbproxy_link_no_stored_msgs_max_len_cmd, + "no link stored-msgs-max-length", + NO_STR GBPROXY_LINK_STR GBPROXY_MAX_LEN_STR) +{ + g_cfg->stored_msgs_max_len = 0; + + 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,}; + + osmo_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; + + 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 (link_info->stored_msgs_len) + vty_out(vty, ", STORED %"PRIu32"/%"PRIu32, + link_info->stored_msgs_len, + g_cfg->stored_msgs_max_len); + + 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; + + osmo_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 = true; + + 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); + 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_clean_stale_timer_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_link_stored_msgs_max_len_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_clean_stale_timer_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); + install_element(GBPROXY_NODE, &cfg_gbproxy_link_no_stored_msgs_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; +} -- cgit v1.2.3