diff options
Diffstat (limited to 'openbsc/src/gprs/gb_proxy_patch.c')
-rw-r--r-- | openbsc/src/gprs/gb_proxy_patch.c | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/openbsc/src/gprs/gb_proxy_patch.c b/openbsc/src/gprs/gb_proxy_patch.c new file mode 100644 index 000000000..59d0b2f2b --- /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; +} + |