/* Copyright 2019 by sysmocom s.f.m.c. GmbH * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_PROXY_SUBSCR(proxy_subscr, level, fmt, args...) \ LOGP(DDGSM, level, "(Proxy IMSI-%s MSISDN-%s HLR-" OSMO_SOCKADDR_STR_FMT ") " fmt, \ ((proxy_subscr) && *(proxy_subscr)->imsi)? (proxy_subscr)->imsi : "?", \ ((proxy_subscr) && *(proxy_subscr)->msisdn)? (proxy_subscr)->msisdn : "?", \ OSMO_SOCKADDR_STR_FMT_ARGS((proxy_subscr)? &(proxy_subscr)->remote_hlr_addr : NULL), \ ##args) #define LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup_msg, level, fmt, args...) \ LOG_PROXY_SUBSCR(proxy_subscr, level, "%s: " fmt, \ (gsup_msg) ? osmo_gsup_message_type_name((gsup_msg)->message_type) : "NULL", \ ##args) /* The proxy subscriber database. * Why have a separate struct to add an llist_head entry? * This is to keep the option open to store the proxy data in the database instead, without any visible effect outside * of proxy.c. */ struct proxy_subscr_listentry { struct llist_head entry; timestamp_t last_update; struct proxy_subscr data; }; struct proxy_pending_gsup_req { struct llist_head entry; struct osmo_gsup_req *req; timestamp_t received_at; }; /* Defer a GSUP message until we know a remote HLR to proxy to. * Where to send this GSUP message is indicated by its IMSI: as soon as an MS lookup has yielded the IMSI's home HLR, * that's where the message should go. */ static void proxy_deferred_gsup_req_add(struct proxy *proxy, struct osmo_gsup_req *req) { struct proxy_pending_gsup_req *m; m = talloc_zero(proxy, struct proxy_pending_gsup_req); OSMO_ASSERT(m); m->req = req; timestamp_update(&m->received_at); llist_add_tail(&m->entry, &proxy->pending_gsup_reqs); } static void proxy_pending_req_remote_hlr_connect_result(struct osmo_gsup_req *req, struct remote_hlr *remote_hlr) { if (!remote_hlr || !remote_hlr_is_up(remote_hlr)) { /* Do not respond with an error to a CHECK_IMEI_REQUEST as osmo-msc will send a LU Reject Cause #6 * Just respond ACK and deal with the IMSI check that comes next. */ if (req->gsup.message_type == OSMO_GSUP_MSGT_CHECK_IMEI_REQUEST) { /* Accept all IMEIs */ struct osmo_gsup_message gsup_reply = (struct osmo_gsup_message){ .message_type = OSMO_GSUP_MSGT_CHECK_IMEI_RESULT, .imei_result = OSMO_GSUP_IMEI_RESULT_ACK, }; osmo_gsup_req_respond(req, &gsup_reply, false, true); return; } osmo_gsup_req_respond_err(req, g_hlr->no_proxy_reject_cause, "Proxy: Failed to connect to home HLR"); return; } remote_hlr_gsup_forward_to_remote_hlr(remote_hlr, req, NULL); } static bool proxy_deferred_gsup_req_waiting(struct proxy *proxy, const char *imsi) { struct proxy_pending_gsup_req *p; OSMO_ASSERT(imsi); llist_for_each_entry(p, &proxy->pending_gsup_reqs, entry) { if (strcmp(p->req->gsup.imsi, imsi)) continue; return true; } return false; } /* Result of looking for remote HLR. If it failed, pass remote_hlr as NULL. On failure, the remote_hlr may be passed * NULL. */ static void proxy_deferred_gsup_req_pop(struct proxy *proxy, const char *imsi, struct remote_hlr *remote_hlr) { struct proxy_pending_gsup_req *p, *n; OSMO_ASSERT(imsi); llist_for_each_entry_safe(p, n, &proxy->pending_gsup_reqs, entry) { if (strcmp(p->req->gsup.imsi, imsi)) continue; proxy_pending_req_remote_hlr_connect_result(p->req, remote_hlr); p->req = NULL; llist_del(&p->entry); talloc_free(p); } } static bool proxy_subscr_matches_imsi(const struct proxy_subscr *proxy_subscr, const char *imsi) { if (!proxy_subscr || !imsi) return false; return strcmp(proxy_subscr->imsi, imsi) == 0; } static bool proxy_subscr_matches_msisdn(const struct proxy_subscr *proxy_subscr, const char *msisdn) { if (!proxy_subscr || !msisdn) return false; return strcmp(proxy_subscr->msisdn, msisdn) == 0; } static struct proxy_subscr_listentry *_proxy_get_by_imsi(struct proxy *proxy, const char *imsi) { struct proxy_subscr_listentry *e; if (!proxy) return NULL; llist_for_each_entry(e, &proxy->subscr_list, entry) { if (proxy_subscr_matches_imsi(&e->data, imsi)) return e; } return NULL; } static struct proxy_subscr_listentry *_proxy_get_by_msisdn(struct proxy *proxy, const char *msisdn) { struct proxy_subscr_listentry *e; if (!proxy) return NULL; llist_for_each_entry(e, &proxy->subscr_list, entry) { if (proxy_subscr_matches_msisdn(&e->data, msisdn)) return e; } return NULL; } int proxy_subscr_get_by_imsi(struct proxy_subscr *dst, struct proxy *proxy, const char *imsi) { struct proxy_subscr_listentry *e = _proxy_get_by_imsi(proxy, imsi); if (!e) return -ENOENT; *dst = e->data; return 0; } int proxy_subscr_get_by_msisdn(struct proxy_subscr *dst, struct proxy *proxy, const char *msisdn) { struct proxy_subscr_listentry *e = _proxy_get_by_msisdn(proxy, msisdn); if (!e) return -ENOENT; *dst = e->data; return 0; } int proxy_subscr_create_or_update(struct proxy *proxy, const struct proxy_subscr *proxy_subscr) { struct proxy_subscr_listentry *e = _proxy_get_by_imsi(proxy, proxy_subscr->imsi); if (!e) { /* Does not exist yet */ e = talloc_zero(proxy, struct proxy_subscr_listentry); llist_add(&e->entry, &proxy->subscr_list); } e->data = *proxy_subscr; timestamp_update(&e->last_update); return 0; } int _proxy_subscr_del(struct proxy_subscr_listentry *e) { llist_del(&e->entry); talloc_free(e); return 0; } int proxy_subscr_del(struct proxy *proxy, const char *imsi) { struct proxy_subscr_listentry *e; proxy_deferred_gsup_req_pop(proxy, imsi, NULL); e = _proxy_get_by_imsi(proxy, imsi); if (!e) return -ENOENT; return _proxy_subscr_del(e); } /* Discard stale proxy entries. */ static void proxy_cleanup(void *proxy_v) { struct proxy *proxy = proxy_v; struct proxy_subscr_listentry *e, *n; uint32_t age; llist_for_each_entry_safe(e, n, &proxy->subscr_list, entry) { if (!timestamp_age(&e->last_update, &age)) LOGP(DDGSM, LOGL_ERROR, "Invalid timestamp, deleting proxy entry\n"); else if (age <= proxy->fresh_time) continue; LOG_PROXY_SUBSCR(&e->data, LOGL_INFO, "proxy entry timed out, deleting\n"); _proxy_subscr_del(e); } if (proxy->gc_period) osmo_timer_schedule(&proxy->gc_timer, proxy->gc_period, 0); else LOGP(DDGSM, LOGL_NOTICE, "Proxy cleanup is switched off (gc_period == 0)\n"); } void proxy_set_gc_period(struct proxy *proxy, uint32_t gc_period) { proxy->gc_period = gc_period; proxy_cleanup(proxy); } void proxy_init(struct osmo_gsup_server *gsup_server_to_vlr) { OSMO_ASSERT(!gsup_server_to_vlr->proxy); struct proxy *proxy = talloc_zero(gsup_server_to_vlr, struct proxy); *proxy = (struct proxy){ .gsup_server_to_vlr = gsup_server_to_vlr, .fresh_time = 60*60, .gc_period = 60, }; INIT_LLIST_HEAD(&proxy->subscr_list); INIT_LLIST_HEAD(&proxy->pending_gsup_reqs); osmo_timer_setup(&proxy->gc_timer, proxy_cleanup, proxy); /* Invoke to trigger the first timer schedule */ proxy_set_gc_period(proxy, proxy->gc_period); gsup_server_to_vlr->proxy = proxy; } void proxy_del(struct proxy *proxy) { osmo_timer_del(&proxy->gc_timer); talloc_free(proxy); } /* All GSUP messages sent to the remote HLR pass through this function, to modify the subscriber state or disallow * sending the message. Return 0 to allow sending the message. */ static int proxy_acknowledge_gsup_to_remote_hlr(struct proxy *proxy, const struct proxy_subscr *proxy_subscr, const struct osmo_gsup_req *req) { struct proxy_subscr proxy_subscr_new = *proxy_subscr; bool ps; bool cs; int rc; if (req->source_name.type != OSMO_CNI_PEER_ID_IPA_NAME) { LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_ERROR, "Unsupported GSUP peer id type: %s\n", osmo_cni_peer_id_type_name(req->source_name.type)); return -ENOTSUP; } switch (req->gsup.message_type) { case OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST: /* Store the CS and PS VLR name in vlr_name_preliminary to later update the right {cs,ps} LU timestamp * when receiving an OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT. Store in vlr_name_preliminary so that in * case the LU fails, we keep the vlr_name intact. */ switch (req->gsup.cn_domain) { case OSMO_GSUP_CN_DOMAIN_CS: proxy_subscr_new.cs.vlr_name_preliminary = req->source_name.ipa_name; break; case OSMO_GSUP_CN_DOMAIN_PS: proxy_subscr_new.ps.vlr_name_preliminary = req->source_name.ipa_name; break; default: break; } ps = cs = false; if (osmo_ipa_name_cmp(&proxy_subscr_new.cs.vlr_name_preliminary, &proxy_subscr->cs.vlr_name_preliminary)) cs = true; if (osmo_ipa_name_cmp(&proxy_subscr_new.ps.vlr_name_preliminary, &proxy_subscr->ps.vlr_name_preliminary)) ps = true; if (!(cs || ps)) { LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_DEBUG, "VLR names remain unchanged\n"); break; } rc = proxy_subscr_create_or_update(proxy, &proxy_subscr_new); LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, rc ? LOGL_ERROR : LOGL_INFO, "%s: preliminary VLR name for%s%s to %s\n", rc ? "failed to update" : "updated", cs ? " CS" : "", ps ? " PS" : "", osmo_cni_peer_id_to_str(&req->source_name)); break; /* TODO: delete proxy entry in case of a Purge Request? */ default: break; } return 0; } /* All GSUP messages received from the remote HLR to be sent to a local MSC pass through this function, to modify the * subscriber state or disallow sending the message. Return 0 to allow sending the message. * The local MSC shall be indicated by gsup.destination_name. */ static int proxy_acknowledge_gsup_from_remote_hlr(struct proxy *proxy, const struct proxy_subscr *proxy_subscr, const struct osmo_gsup_message *gsup, struct remote_hlr *from_remote_hlr, const struct osmo_ipa_name *destination, const struct osmo_ipa_name *via_peer) { struct proxy_subscr proxy_subscr_new = *proxy_subscr; bool ps; bool cs; bool vlr_name_changed_cs = false; bool vlr_name_changed_ps = false; int rc; struct osmo_ipa_name via_proxy = {}; if (osmo_ipa_name_cmp(via_peer, destination)) via_proxy = *via_peer; switch (gsup->message_type) { case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST: /* Remember the MSISDN of the subscriber. This does not need to be a preliminary record, because when * the HLR tells us about subscriber data, it is definitive info and there is no ambiguity (like there * would be with failed LU attempts from various sources). */ if (!gsup->msisdn_enc_len) LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_DEBUG, "No MSISDN in this Insert Data Request\n"); else if (gsm48_decode_bcd_number2(proxy_subscr_new.msisdn, sizeof(proxy_subscr_new.msisdn), gsup->msisdn_enc, gsup->msisdn_enc_len, 0)) LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR, "Failed to decode MSISDN\n"); else if (!osmo_msisdn_str_valid(proxy_subscr_new.msisdn)) LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR, "invalid MSISDN: %s\n", osmo_quote_str_c(OTC_SELECT, proxy_subscr_new.msisdn, -1)); else if (!strcmp(proxy_subscr->msisdn, proxy_subscr_new.msisdn)) LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_DEBUG, "already have MSISDN = %s\n", proxy_subscr_new.msisdn); else if (proxy_subscr_create_or_update(proxy, &proxy_subscr_new)) LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR, "failed to update MSISDN to %s\n", proxy_subscr_new.msisdn); else LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_INFO, "stored MSISDN=%s\n", proxy_subscr_new.msisdn); break; case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT: /* Update the Location Updating timestamp */ cs = ps = false; if (!osmo_ipa_name_cmp(destination, &proxy_subscr->cs.vlr_name_preliminary)) { timestamp_update(&proxy_subscr_new.cs.last_lu); proxy_subscr_new.cs.vlr_name_preliminary = (struct osmo_ipa_name){}; vlr_name_changed_cs = osmo_ipa_name_cmp(&proxy_subscr->cs.vlr_name, destination) || osmo_ipa_name_cmp(&proxy_subscr->cs.vlr_via_proxy, &via_proxy); proxy_subscr_new.cs.vlr_name = *destination; proxy_subscr_new.cs.vlr_via_proxy = via_proxy; cs = true; } if (!osmo_ipa_name_cmp(destination, &proxy_subscr->ps.vlr_name_preliminary)) { timestamp_update(&proxy_subscr_new.ps.last_lu); proxy_subscr_new.ps.vlr_name_preliminary = (struct osmo_ipa_name){}; proxy_subscr_new.ps.vlr_name = *destination; vlr_name_changed_ps = osmo_ipa_name_cmp(&proxy_subscr->ps.vlr_name, destination) || osmo_ipa_name_cmp(&proxy_subscr->ps.vlr_via_proxy, &via_proxy); proxy_subscr_new.ps.vlr_via_proxy = via_proxy; ps = true; } if (!(cs || ps)) { LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR, "destination is neither CS nor PS VLR: %s\n", osmo_ipa_name_to_str(destination)); return GMM_CAUSE_PROTO_ERR_UNSPEC; } rc = proxy_subscr_create_or_update(proxy, &proxy_subscr_new); LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, rc ? LOGL_ERROR : LOGL_INFO, "%s LU: timestamp for%s%s%s%s%s%s%s%s%s%s\n", rc ? "failed to update" : "updated", cs ? " CS" : "", ps ? " PS" : "", vlr_name_changed_cs? ", CS VLR=" : "", vlr_name_changed_cs? osmo_ipa_name_to_str(&proxy_subscr_new.cs.vlr_name) : "", proxy_subscr_new.cs.vlr_via_proxy.len ? " via proxy " : "", proxy_subscr_new.cs.vlr_via_proxy.len ? osmo_ipa_name_to_str(&proxy_subscr_new.cs.vlr_via_proxy) : "", vlr_name_changed_ps? ", PS VLR=" : "", vlr_name_changed_ps? osmo_ipa_name_to_str(&proxy_subscr_new.ps.vlr_name) : "", proxy_subscr_new.ps.vlr_via_proxy.len ? " via proxy " : "", proxy_subscr_new.ps.vlr_via_proxy.len ? osmo_ipa_name_to_str(&proxy_subscr_new.ps.vlr_via_proxy) : "" ); break; default: break; } return 0; } static void proxy_remote_hlr_connect_result_cb(const struct osmo_sockaddr_str *addr, struct remote_hlr *remote_hlr, void *data) { struct proxy *proxy = data; struct proxy_subscr_listentry *e; if (!proxy) return; llist_for_each_entry(e, &proxy->subscr_list, entry) { if (!osmo_sockaddr_str_cmp(addr, &e->data.remote_hlr_addr)) { proxy_deferred_gsup_req_pop(proxy, e->data.imsi, remote_hlr); } } } /* Store the remote HLR's GSUP address for this proxy subscriber. * This can be set before the remote_hlr is connected, or after. * And, this can be set before the gsup_req has been queued for this HLR, or after. */ void proxy_subscr_remote_hlr_resolved(struct proxy *proxy, const struct proxy_subscr *proxy_subscr, const struct osmo_sockaddr_str *remote_hlr_addr) { struct proxy_subscr proxy_subscr_new; if (osmo_sockaddr_str_is_nonzero(&proxy_subscr->remote_hlr_addr)) { if (!osmo_sockaddr_str_cmp(remote_hlr_addr, &proxy_subscr->remote_hlr_addr)) { /* Already have this remote address */ return; } else { LOG_PROXY_SUBSCR(proxy_subscr, LOGL_NOTICE, "Remote HLR address changes to " OSMO_SOCKADDR_STR_FMT "\n", OSMO_SOCKADDR_STR_FMT_ARGS(remote_hlr_addr)); } } /* Store the address. Make a copy to modify. */ proxy_subscr_new = *proxy_subscr; proxy_subscr = &proxy_subscr_new; proxy_subscr_new.remote_hlr_addr = *remote_hlr_addr; if (proxy_subscr_create_or_update(proxy, proxy_subscr)) { LOG_PROXY_SUBSCR(proxy_subscr, LOGL_ERROR, "Failed to store proxy entry for remote HLR\n"); /* If no remote HLR is known for the IMSI, the proxy entry is pointless. */ proxy_subscr_del(proxy, proxy_subscr->imsi); return; } LOG_PROXY_SUBSCR(proxy_subscr, LOGL_DEBUG, "Remote HLR resolved, stored address\n"); /* If any messages for this HLR are already spooled, connect now. Otherwise wait for * proxy_subscr_forward_to_remote_hlr() to connect then. */ if (proxy_deferred_gsup_req_waiting(proxy, proxy_subscr->imsi)) remote_hlr_get_or_connect(&proxy_subscr->remote_hlr_addr, true, proxy_remote_hlr_connect_result_cb, proxy); } int proxy_subscr_forward_to_remote_hlr(struct proxy *proxy, const struct proxy_subscr *proxy_subscr, struct osmo_gsup_req *req) { struct remote_hlr *remote_hlr; int rc; rc = proxy_acknowledge_gsup_to_remote_hlr(proxy, proxy_subscr, req); if (rc) { osmo_gsup_req_respond_err(req, GMM_CAUSE_PROTO_ERR_UNSPEC, "Proxy does not allow this message"); return rc; } if (!osmo_sockaddr_str_is_nonzero(&proxy_subscr->remote_hlr_addr)) { /* We don't know the remote target yet. Still waiting for an MS lookup response, which will end up * calling proxy_subscr_remote_hlr_resolved(). See dgsm.c. */ LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_DEBUG, "deferring until remote HLR is known\n"); proxy_deferred_gsup_req_add(proxy, req); return 0; } if (!osmo_cni_peer_id_is_empty(&req->via_proxy)) { LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_INFO, "VLR->HLR: forwarding from %s via proxy %s\n", osmo_cni_peer_id_to_str(&req->source_name), osmo_cni_peer_id_to_str(&req->via_proxy)); } else { LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_INFO, "VLR->HLR: forwarding from %s\n", osmo_cni_peer_id_to_str(&req->source_name)); } /* We could always store in the defer queue and empty the queue if the connection is already up. * Slight optimisation: if the remote_hlr is already up and running, skip the defer queue. * First ask for an existing remote_hlr. */ remote_hlr = remote_hlr_get_or_connect(&proxy_subscr->remote_hlr_addr, false, NULL, NULL); if (remote_hlr && remote_hlr_is_up(remote_hlr)) { proxy_pending_req_remote_hlr_connect_result(req, remote_hlr); return 0; } /* Not existing or not up. Defer req and ask to be notified when it is up. * If the remote_hlr exists but is not connected yet, there should actually already be a pending * proxy_remote_hlr_connect_result_cb queued, but it doesn't hurt to do that more often. */ proxy_deferred_gsup_req_add(proxy, req); remote_hlr_get_or_connect(&proxy_subscr->remote_hlr_addr, true, proxy_remote_hlr_connect_result_cb, proxy); return 0; } int proxy_subscr_forward_to_vlr(struct proxy *proxy, const struct proxy_subscr *proxy_subscr, const struct osmo_gsup_message *gsup, struct remote_hlr *from_remote_hlr) { struct osmo_ipa_name destination; struct osmo_gsup_conn *vlr_conn; struct msgb *msg; if (osmo_ipa_name_set(&destination, gsup->destination_name, gsup->destination_name_len)) { LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR, "no valid Destination Name IE, cannot route to VLR.\n"); return GMM_CAUSE_INV_MAND_INFO; } /* Route to MSC/SGSN that we're proxying for */ vlr_conn = gsup_route_find_by_ipa_name(proxy->gsup_server_to_vlr, &destination); if (!vlr_conn) { LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR, "Destination VLR unreachable: %s\n", osmo_ipa_name_to_str(&destination)); return GMM_CAUSE_MSC_TEMP_NOTREACH; } if (proxy_acknowledge_gsup_from_remote_hlr(proxy, proxy_subscr, gsup, from_remote_hlr, &destination, &vlr_conn->peer_name)) { LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR, "Proxy does not allow forwarding this message\n"); return GMM_CAUSE_PROTO_ERR_UNSPEC; } msg = osmo_gsup_msgb_alloc("GSUP proxy to VLR"); if (osmo_gsup_encode(msg, gsup)) { LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR, "Failed to re-encode GSUP message, cannot forward\n"); return GMM_CAUSE_INV_MAND_INFO; } LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_INFO, "VLR<-HLR: forwarding to %s%s%s\n", osmo_ipa_name_to_str(&destination), osmo_ipa_name_cmp(&destination, &vlr_conn->peer_name) ? " via " : "", osmo_ipa_name_cmp(&destination, &vlr_conn->peer_name) ? osmo_ipa_name_to_str(&vlr_conn->peer_name) : ""); return osmo_gsup_conn_send(vlr_conn, msg); }