gbproxy: Refactor gb_proxy.c into several files

This patch moves several functions and declarations out of gb_proxy.c
to make them reusable by other components and to separate them by
context and task.

Counter enums (prefix is changed to gbproxy_):
  enum gbprox_global_ctr -> gprs/gb_proxy.h
  enum gbprox_peer_ctr -> gprs/gb_proxy.h

Generic Gb parsing (prefix is changed to gprs_gb_):
  struct gbproxy_parse_context -> openbsc/gprs_gb_parse.h
  gbprox_parse_dtap() -> gprs/gprs_gb_parse.c
  gbprox_parse_llc() -> gprs/gprs_gb_parse.c
  gbprox_parse_bssgp() -> gprs/gprs_gb_parse.c
  gbprox_log_parse_context() -> gprs/gprs_gb_parse.c
  *_shift(), *_match() -> gprs/gprs_gb_parse.c (no prefix)
  gbprox_parse_gmm_* -> gprs/gprs_gb_parse.c (static)
  gbprox_parse_gsm_* -> gprs/gprs_gb_parse.c (static)

MI testing/parsing (prefix gprs_ added):
  is_mi_tmsi() -> gprs/gprs_utils.c
  is_mi_imsi() -> gprs/gprs_utils.c
  parse_mi_tmsi() -> gprs/gprs_utils.c

TLLI state handling (prefix is changed to gbproxy_):
  gbprox_*tlli* -> gprs/gb_proxy_tlli.c
  (except gbprox_patch_tlli, gbproxy_make_sgsn_tlli)

Message patching (prefix is changed to gbproxy_):
  gbprox_*patch* -> gprs/gb_proxy_patch.c
  gbprox_check_imsi -> gprs/gb_proxy_patch.c

Sponsored-by: On-Waves ehf
diff --git a/openbsc/src/gprs/gb_proxy_patch.c b/openbsc/src/gprs/gb_proxy_patch.c
new file mode 100644
index 0000000..59d0b2f
--- /dev/null
+++ b/openbsc/src/gprs/gb_proxy_patch.c
@@ -0,0 +1,475 @@
+/* Gb-proxy message patching */
+
+/* (C) 2014 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/gb_proxy.h>
+
+#include <openbsc/gprs_utils.h>
+#include <openbsc/gprs_gb_parse.h>
+
+#include <openbsc/gsm_data_shared.h>
+#include <openbsc/gsm_04_08_gprs.h>
+#include <openbsc/debug.h>
+
+#include <osmocom/gprs/protocol/gsm_08_18.h>
+#include <osmocom/core/rate_ctr.h>
+
+/* check whether patching is enabled at this level */
+static int patching_is_enabled(struct gbproxy_peer *peer,
+			       enum gbproxy_patch_mode need_at_least)
+{
+	enum gbproxy_patch_mode patch_mode = peer->cfg->patch_mode;
+	if (patch_mode == GBPROX_PATCH_DEFAULT)
+		patch_mode = GBPROX_PATCH_LLC;
+
+	return need_at_least <= patch_mode;
+}
+
+/* check whether patching is enabled at this level */
+static int patching_is_required(struct gbproxy_peer *peer,
+				enum gbproxy_patch_mode need_at_least)
+{
+	return need_at_least <= peer->cfg->patch_mode;
+}
+
+static int allow_message_patching(struct gbproxy_peer *peer, int msg_type)
+{
+	if (msg_type >= GSM48_MT_GSM_ACT_PDP_REQ) {
+		return patching_is_enabled(peer, GBPROX_PATCH_LLC_GSM);
+	} else if (msg_type > GSM48_MT_GMM_ATTACH_REJ) {
+		return patching_is_enabled(peer, GBPROX_PATCH_LLC);
+	} else if (msg_type > GSM48_MT_GMM_ATTACH_REQ) {
+		return patching_is_enabled(peer, GBPROX_PATCH_LLC_ATTACH);
+	} else {
+		return patching_is_enabled(peer, GBPROX_PATCH_LLC_ATTACH_REQ);
+	}
+}
+
+/* patch RA identifier in place */
+static void gbproxy_patch_raid(uint8_t *raid_enc, struct gbproxy_peer *peer,
+			       int to_bss, const char *log_text)
+{
+	struct gbproxy_patch_state *state = &peer->patch_state;
+	int old_mcc;
+	int old_mnc;
+	struct gprs_ra_id raid;
+
+	gsm48_parse_ra(&raid, raid_enc);
+
+	old_mcc = raid.mcc;
+	old_mnc = raid.mnc;
+
+	if (!to_bss) {
+		/* BSS -> SGSN */
+		if (state->local_mcc)
+			raid.mcc = peer->cfg->core_mcc;
+
+		if (state->local_mnc)
+			raid.mnc = peer->cfg->core_mnc;
+	} else {
+		/* SGSN -> BSS */
+		if (state->local_mcc)
+			raid.mcc = state->local_mcc;
+
+		if (state->local_mnc)
+			raid.mnc = state->local_mnc;
+	}
+
+	if (state->local_mcc || state->local_mnc) {
+		enum gbproxy_peer_ctr counter =
+			to_bss ?
+			GBPROX_PEER_CTR_RAID_PATCHED_SGSN :
+			GBPROX_PEER_CTR_RAID_PATCHED_BSS;
+
+		LOGP(DGPRS, LOGL_DEBUG,
+		       "Patching %s to %s: "
+		       "%d-%d-%d-%d -> %d-%d-%d-%d\n",
+		       log_text,
+		       to_bss ? "BSS" : "SGSN",
+		       old_mcc, old_mnc, raid.lac, raid.rac,
+		       raid.mcc, raid.mnc, raid.lac, raid.rac);
+
+		gsm48_construct_ra(raid_enc, &raid);
+		rate_ctr_inc(&peer->ctrg->ctr[counter]);
+	}
+}
+
+static void gbproxy_patch_apn_ie(struct msgb *msg,
+				 uint8_t *apn_ie, size_t apn_ie_len,
+				 struct gbproxy_peer *peer,
+				 size_t *new_apn_ie_len, const char *log_text)
+{
+	struct apn_ie_hdr {
+		uint8_t iei;
+		uint8_t apn_len;
+		uint8_t apn[0];
+	} *hdr = (void *)apn_ie;
+
+	size_t apn_len = hdr->apn_len;
+	uint8_t *apn = hdr->apn;
+
+	OSMO_ASSERT(apn_ie_len == apn_len + sizeof(struct apn_ie_hdr));
+	OSMO_ASSERT(apn_ie_len > 2 && apn_ie_len <= 102);
+
+	if (peer->cfg->core_apn_size == 0) {
+		char str1[110];
+		/* Remove the IE */
+		LOGP(DGPRS, LOGL_DEBUG,
+		     "Patching %s to SGSN: Removing APN '%s'\n",
+		     log_text,
+		     gprs_apn_to_str(str1, apn, apn_len));
+
+		*new_apn_ie_len = 0;
+		gprs_msgb_resize_area(msg, apn_ie, apn_ie_len, 0);
+	} else {
+		/* Resize the IE */
+		char str1[110];
+		char str2[110];
+
+		OSMO_ASSERT(peer->cfg->core_apn_size <= 100);
+
+		LOGP(DGPRS, LOGL_DEBUG,
+		     "Patching %s to SGSN: "
+		     "Replacing APN '%s' -> '%s'\n",
+		     log_text,
+		     gprs_apn_to_str(str1, apn, apn_len),
+		     gprs_apn_to_str(str2, peer->cfg->core_apn,
+				       peer->cfg->core_apn_size));
+
+		*new_apn_ie_len = peer->cfg->core_apn_size + 2;
+		gprs_msgb_resize_area(msg, apn, apn_len, peer->cfg->core_apn_size);
+		memcpy(apn, peer->cfg->core_apn, peer->cfg->core_apn_size);
+		hdr->apn_len = peer->cfg->core_apn_size;
+	}
+
+	rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_APN_PATCHED]);
+}
+
+static int gbproxy_patch_tlli(uint8_t *tlli_enc,
+			      struct gbproxy_peer *peer,
+			      uint32_t new_tlli,
+			      int to_bss, const char *log_text)
+{
+	uint32_t tlli_be;
+	uint32_t tlli;
+	enum gbproxy_peer_ctr counter =
+		to_bss ?
+		GBPROX_PEER_CTR_TLLI_PATCHED_SGSN :
+		GBPROX_PEER_CTR_TLLI_PATCHED_BSS;
+
+	memcpy(&tlli_be, tlli_enc, sizeof(tlli_be));
+	tlli = ntohl(tlli_be);
+
+	if (tlli == new_tlli)
+		return 0;
+
+	LOGP(DGPRS, LOGL_DEBUG,
+	     "Patching %ss: "
+	     "Replacing %08x -> %08x\n",
+	     log_text, tlli, new_tlli);
+
+	tlli_be = htonl(new_tlli);
+	memcpy(tlli_enc, &tlli_be, sizeof(tlli_be));
+
+	rate_ctr_inc(&peer->ctrg->ctr[counter]);
+
+	return 1;
+}
+
+static int gbproxy_patch_ptmsi(uint8_t *ptmsi_enc,
+			       struct gbproxy_peer *peer,
+			       uint32_t new_ptmsi,
+			       int to_bss, const char *log_text)
+{
+	uint32_t ptmsi_be;
+	uint32_t ptmsi;
+	enum gbproxy_peer_ctr counter =
+		to_bss ?
+		GBPROX_PEER_CTR_PTMSI_PATCHED_SGSN :
+		GBPROX_PEER_CTR_PTMSI_PATCHED_BSS;
+	memcpy(&ptmsi_be, ptmsi_enc + 1, 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 + 1, &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_tlli_info *tlli_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;
+
+	if (parse_ctx->g48_hdr && !allow_message_patching(peer, parse_ctx->g48_hdr->msg_type))
+		return have_patched;
+
+	if (parse_ctx->ptmsi_enc && tlli_info) {
+		uint32_t ptmsi;
+		if (parse_ctx->to_bss)
+			ptmsi = tlli_info->tlli.ptmsi;
+		else
+			ptmsi = tlli_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 && tlli_info) {
+		uint32_t ptmsi;
+		if (parse_ctx->to_bss)
+			ptmsi = tlli_info->tlli.ptmsi;
+		else
+			ptmsi = tlli_info->sgsn_tlli.ptmsi;
+
+		OSMO_ASSERT(ptmsi);
+		if (gbproxy_patch_ptmsi(parse_ctx->new_ptmsi_enc, peer,
+					ptmsi, parse_ctx->to_bss, "new P-TMSI"))
+			have_patched = 1;
+	}
+
+	if (parse_ctx->raid_enc) {
+		gbproxy_patch_raid(parse_ctx->raid_enc, peer, parse_ctx->to_bss,
+				   parse_ctx->llc_msg_name);
+		have_patched = 1;
+	}
+
+	if (parse_ctx->old_raid_enc && parse_ctx->old_raid_matches) {
+		/* TODO: Patch to invalid if P-TMSI unknown. */
+		gbproxy_patch_raid(parse_ctx->old_raid_enc, peer, parse_ctx->to_bss,
+				   parse_ctx->llc_msg_name);
+		have_patched = 1;
+	}
+
+	if (parse_ctx->apn_ie &&
+	    peer->cfg->core_apn &&
+	    !parse_ctx->to_bss &&
+	    gbproxy_check_tlli(peer, parse_ctx->tlli)) {
+		size_t new_len;
+		gbproxy_patch_apn_ie(msg,
+				     parse_ctx->apn_ie, parse_ctx->apn_ie_len,
+				     peer, &new_len, parse_ctx->llc_msg_name);
+		*len_change += (int)new_len - (int)parse_ctx->apn_ie_len;
+
+		have_patched = 1;
+	}
+
+	if (have_patched) {
+		llc_len += *len_change;
+		ghp->crc_length += *len_change;
+
+		/* Fix FCS */
+		fcs = gprs_llc_fcs(llc, ghp->crc_length);
+		LOGP(DLLC, LOGL_DEBUG, "Updated LLC message, CRC: %06x -> %06x\n",
+		     ghp->fcs, fcs);
+
+		llc[llc_len - 3] = fcs & 0xff;
+		llc[llc_len - 2] = (fcs >> 8) & 0xff;
+		llc[llc_len - 1] = (fcs >> 16) & 0xff;
+	}
+
+	return have_patched;
+}
+
+/* patch BSSGP message to use core_mcc/mnc on the SGSN side */
+void gbproxy_patch_bssgp(struct msgb *msg, uint8_t *bssgp, size_t bssgp_len,
+			 struct gbproxy_peer *peer,
+			 struct gbproxy_tlli_info *tlli_info, int *len_change,
+			 struct gprs_gb_parse_context *parse_ctx)
+{
+	const char *err_info = NULL;
+	int err_ctr = -1;
+
+	if (!patching_is_enabled(peer, GBPROX_PATCH_BSSGP))
+		return;
+
+	if (parse_ctx->bssgp_raid_enc)
+		gbproxy_patch_raid(parse_ctx->bssgp_raid_enc, peer,
+				   parse_ctx->to_bss, "BSSGP");
+
+	if (!patching_is_enabled(peer, GBPROX_PATCH_LLC_ATTACH_REQ))
+		return;
+
+	if (parse_ctx->need_decryption &&
+	    patching_is_required(peer, GBPROX_PATCH_LLC_ATTACH)) {
+		/* 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 (parse_ctx->tlli_enc && tlli_info) {
+		uint32_t tlli = gbproxy_map_tlli(parse_ctx->tlli,
+						 tlli_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 if (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;
+		} else {
+			/* Internal error */
+			err_ctr = GBPROX_PEER_CTR_PATCH_ERR;
+			err_info = "Replacement TLLI is 0";
+			goto patch_error;
+		}
+	}
+
+	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, tlli_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_config *cfg)
+{
+	if (cfg->check_imsi) {
+		regfree(&cfg->imsi_re_comp);
+		cfg->check_imsi = 0;
+	}
+}
+
+int gbproxy_set_patch_filter(struct gbproxy_config *cfg, const char *filter,
+		const char **err_msg)
+{
+	static char err_buf[300];
+	int rc;
+
+	gbproxy_clear_patch_filter(cfg);
+
+	if (!filter)
+		return 0;
+
+	rc = regcomp(&cfg->imsi_re_comp, filter,
+		     REG_EXTENDED | REG_NOSUB | REG_ICASE);
+
+	if (rc == 0) {
+		cfg->check_imsi = 1;
+		return 0;
+	}
+
+	if (err_msg) {
+		regerror(rc, &cfg->imsi_re_comp,
+			 err_buf, sizeof(err_buf));
+		*err_msg = err_buf;
+	}
+
+	return -1;
+}
+
+int gbproxy_check_imsi(struct gbproxy_peer *peer,
+		       const uint8_t *imsi, size_t imsi_len)
+{
+	char mi_buf[200];
+	int rc;
+
+	if (!peer->cfg->check_imsi)
+		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(&peer->cfg->imsi_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, peer->cfg->match_re);
+		return 0;
+	}
+
+	return 1;
+}
+