osmo-hlr/src/remote_hlr.c

253 lines
7.9 KiB
C

/* 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 <osmocom/gsm/protocol/gsm_04_08_gprs.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/abis/ipa.h>
#include <osmocom/gsupclient/gsup_client.h>
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/gsup_router.h>
#include <osmocom/hlr/remote_hlr.h>
#include <osmocom/hlr/proxy.h>
static LLIST_HEAD(remote_hlrs);
static void remote_hlr_err_reply(struct remote_hlr *rh, const struct osmo_gsup_message *gsup_orig,
enum gsm48_gmm_cause cause)
{
struct osmo_gsup_message gsup_reply;
/* No need to answer if we couldn't parse an ERROR message type, only REQUESTs need an error reply. */
if (!OSMO_GSUP_IS_MSGT_REQUEST(gsup_orig->message_type))
return;
gsup_reply = (struct osmo_gsup_message){
.cause = cause,
.message_type = OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type),
.message_class = gsup_orig->message_class,
/* RP-Message-Reference is mandatory for SM Service */
.sm_rp_mr = gsup_orig->sm_rp_mr,
};
OSMO_STRLCPY_ARRAY(gsup_reply.imsi, gsup_orig->imsi);
/* For SS/USSD, it's important to keep both session state and ID IEs */
if (gsup_orig->session_state != OSMO_GSUP_SESSION_STATE_NONE) {
gsup_reply.session_state = OSMO_GSUP_SESSION_STATE_END;
gsup_reply.session_id = gsup_orig->session_id;
}
if (osmo_gsup_client_enc_send(rh->gsupc, &gsup_reply))
LOGP(DLGSUP, LOGL_ERROR, "Failed to send Error reply (imsi=%s)\n",
osmo_quote_str(gsup_orig->imsi, -1));
}
/* We are receiving a GSUP message from a remote HLR to go back to a local MSC.
* The local MSC shall be indicated by gsup.destination_name. */
static int remote_hlr_rx(struct osmo_gsup_client *gsupc, struct msgb *msg)
{
struct remote_hlr *rh = gsupc->data;
struct proxy_subscr proxy_subscr;
struct osmo_gsup_message gsup;
int rc;
rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
if (rc < 0) {
LOG_REMOTE_HLR(rh, LOGL_ERROR, "Failed to decode GSUP message: '%s' (%d) [ %s]\n",
get_value_string(gsm48_gmm_cause_names, -rc),
-rc, osmo_hexdump(msg->data, msg->len));
return rc;
}
if (!osmo_imsi_str_valid(gsup.imsi)) {
LOG_REMOTE_HLR_MSG(rh, &gsup, LOGL_ERROR, "Invalid IMSI\n");
remote_hlr_err_reply(rh, &gsup, GMM_CAUSE_INV_MAND_INFO);
return -GMM_CAUSE_INV_MAND_INFO;
}
if (proxy_subscr_get_by_imsi(&proxy_subscr, g_hlr->gs->proxy, gsup.imsi)) {
LOG_REMOTE_HLR_MSG(rh, &gsup, LOGL_ERROR, "No proxy entry for this IMSI\n");
remote_hlr_err_reply(rh, &gsup, GMM_CAUSE_NET_FAIL);
return -GMM_CAUSE_NET_FAIL;
}
rc = proxy_subscr_forward_to_vlr(g_hlr->gs->proxy, &proxy_subscr, &gsup, rh);
if (rc) {
LOG_REMOTE_HLR_MSG(rh, &gsup, LOGL_ERROR, "Failed to forward GSUP message towards VLR\n");
remote_hlr_err_reply(rh, &gsup, GMM_CAUSE_NET_FAIL);
return -GMM_CAUSE_NET_FAIL;
}
return 0;
}
struct remote_hlr_pending_up {
struct llist_head entry;
remote_hlr_connect_result_cb_t connect_result_cb;
void *data;
};
static bool remote_hlr_up_down(struct osmo_gsup_client *gsupc, bool up)
{
struct remote_hlr *remote_hlr = gsupc->data;
struct remote_hlr_pending_up *p, *n;
if (!up) {
LOG_REMOTE_HLR(remote_hlr, LOGL_NOTICE, "link to remote HLR is down, removing GSUP client\n");
remote_hlr_destroy(remote_hlr);
return false;
}
LOG_REMOTE_HLR(remote_hlr, LOGL_NOTICE, "link up\n");
llist_for_each_entry_safe(p, n, &remote_hlr->pending_up_callbacks, entry) {
if (p->connect_result_cb)
p->connect_result_cb(&remote_hlr->addr, remote_hlr, p->data);
llist_del(&p->entry);
}
return true;
}
bool remote_hlr_is_up(struct remote_hlr *remote_hlr)
{
return remote_hlr && remote_hlr->gsupc && remote_hlr->gsupc->is_connected;
}
struct remote_hlr *remote_hlr_get_or_connect(const struct osmo_sockaddr_str *addr, bool connect,
remote_hlr_connect_result_cb_t connect_result_cb, void *data)
{
struct remote_hlr *rh = NULL;
struct remote_hlr *rh_i;
struct osmo_gsup_client_config cfg;
llist_for_each_entry(rh_i, &remote_hlrs, entry) {
if (!osmo_sockaddr_str_cmp(&rh_i->addr, addr)) {
rh = rh_i;
break;
}
}
if (rh)
goto add_result_cb;
if (!connect) {
if (connect_result_cb)
connect_result_cb(addr, NULL, data);
return NULL;
}
/* Doesn't exist yet, create a GSUP client to remote HLR. */
cfg = (struct osmo_gsup_client_config){
.ipa_dev = &g_hlr->gsup_unit_name,
.ip_addr = addr->ip,
.tcp_port = addr->port,
.oapc_config = NULL,
.read_cb = remote_hlr_rx,
.up_down_cb = remote_hlr_up_down,
.data = rh,
};
rh = talloc_zero(dgsm_ctx, struct remote_hlr);
OSMO_ASSERT(rh);
*rh = (struct remote_hlr){
.addr = *addr,
.gsupc = osmo_gsup_client_create3(rh, &cfg),
};
INIT_LLIST_HEAD(&rh->pending_up_callbacks);
if (!rh->gsupc) {
LOGP(DDGSM, LOGL_ERROR,
"Failed to establish connection to remote HLR " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(addr));
talloc_free(rh);
if (connect_result_cb)
connect_result_cb(addr, NULL, data);
return NULL;
}
rh->gsupc->data = rh;
llist_add(&rh->entry, &remote_hlrs);
add_result_cb:
if (connect_result_cb) {
if (remote_hlr_is_up(rh)) {
connect_result_cb(addr, rh, data);
} else {
struct remote_hlr_pending_up *p;
p = talloc_zero(rh, struct remote_hlr_pending_up);
OSMO_ASSERT(p);
p->connect_result_cb = connect_result_cb;
p->data = data;
llist_add_tail(&p->entry, &rh->pending_up_callbacks);
}
}
return rh;
}
void remote_hlr_destroy(struct remote_hlr *remote_hlr)
{
osmo_gsup_client_destroy(remote_hlr->gsupc);
remote_hlr->gsupc = NULL;
llist_del(&remote_hlr->entry);
talloc_free(remote_hlr);
}
/* This function takes ownership of the msg, do not free it after passing to this function. */
int remote_hlr_msgb_send(struct remote_hlr *remote_hlr, struct msgb *msg)
{
int rc = osmo_gsup_client_send(remote_hlr->gsupc, msg);
if (rc) {
LOGP(DDGSM, LOGL_ERROR, "Failed to send GSUP message to " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
}
return rc;
}
/* A GSUP message was received from the MS/MSC side, forward it to the remote HLR. */
void remote_hlr_gsup_forward_to_remote_hlr(struct remote_hlr *remote_hlr, struct osmo_gsup_req *req,
struct osmo_gsup_message *modified_gsup)
{
int rc;
struct msgb *msg;
/* To forward to a remote HLR, we need to indicate the source MSC's name in the Source Name IE to make sure the
* reply can be routed back. Store the sender MSC in gsup->source_name -- the remote HLR is required to return
* this as gsup->destination_name so that the reply gets routed to the original MSC. */
struct osmo_gsup_message forward;
if (modified_gsup)
forward = *modified_gsup;
else
forward = req->gsup;
if (req->source_name.type != OSMO_CNI_PEER_ID_IPA_NAME) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Unsupported GSUP peer id type: %s",
osmo_cni_peer_id_type_name(req->source_name.type));
return;
}
forward.source_name = req->source_name.ipa_name.val;
forward.source_name_len = req->source_name.ipa_name.len;
msg = osmo_gsup_msgb_alloc("GSUP proxy to remote HLR");
rc = osmo_gsup_encode(msg, &forward);
if (rc) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Failed to encode GSUP message for forwarding");
return;
}
remote_hlr_msgb_send(remote_hlr, msg);
osmo_gsup_req_free(req);
}