aboutsummaryrefslogtreecommitdiffstats
path: root/src/proxy.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/proxy.c')
-rw-r--r--src/proxy.c554
1 files changed, 554 insertions, 0 deletions
diff --git a/src/proxy.c b/src/proxy.c
new file mode 100644
index 0000000..1ae152c
--- /dev/null
+++ b/src/proxy.c
@@ -0,0 +1,554 @@
+/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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 <string.h>
+#include <talloc.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/gsm/gsm23003.h>
+#include <osmocom/gsm/gsm48_ie.h>
+#include <osmocom/gsupclient/gsup_client.h>
+#include <osmocom/gsupclient/gsup_req.h>
+
+#include <osmocom/hlr/logging.h>
+#include <osmocom/hlr/proxy.h>
+#include <osmocom/hlr/remote_hlr.h>
+#include <osmocom/hlr/gsup_server.h>
+#include <osmocom/hlr/gsup_router.h>
+
+#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)) {
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "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);
+ 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);
+}