277 lines
8.9 KiB
C
277 lines
8.9 KiB
C
/* OsmoHLR SMS-over-GSUP routing implementation */
|
|
|
|
/* Author: Mychaela N. Falconia <falcon@freecalypso.org>, 2023 - however,
|
|
* Mother Mychaela's contributions are NOT subject to copyright.
|
|
* No rights reserved, all rights relinquished.
|
|
*
|
|
* Based on earlier unmerged work by Vadim Yanitskiy, 2019.
|
|
*
|
|
* 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 <stdint.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include <osmocom/core/talloc.h>
|
|
#include <osmocom/gsm/gsup.h>
|
|
#include <osmocom/gsm/gsm48_ie.h>
|
|
#include <osmocom/gsm/protocol/gsm_04_11.h>
|
|
|
|
#include <osmocom/hlr/hlr.h>
|
|
#include <osmocom/hlr/hlr_sms.h>
|
|
#include <osmocom/hlr/gsup_server.h>
|
|
#include <osmocom/hlr/gsup_router.h>
|
|
#include <osmocom/hlr/logging.h>
|
|
#include <osmocom/hlr/db.h>
|
|
|
|
/***********************************************************************
|
|
* core data structures expressing config from VTY
|
|
***********************************************************************/
|
|
|
|
struct hlr_smsc *smsc_find(struct hlr *hlr, const char *name)
|
|
{
|
|
struct hlr_smsc *smsc;
|
|
|
|
llist_for_each_entry(smsc, &hlr->smsc_list, list) {
|
|
if (!strcmp(smsc->name, name))
|
|
return smsc;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct hlr_smsc *smsc_alloc(struct hlr *hlr, const char *name)
|
|
{
|
|
struct hlr_smsc *smsc = smsc_find(hlr, name);
|
|
if (smsc)
|
|
return NULL;
|
|
|
|
smsc = talloc_zero(hlr, struct hlr_smsc);
|
|
smsc->name = talloc_strdup(smsc, name);
|
|
smsc->hlr = hlr;
|
|
llist_add_tail(&smsc->list, &hlr->smsc_list);
|
|
|
|
return smsc;
|
|
}
|
|
|
|
void smsc_free(struct hlr_smsc *smsc)
|
|
{
|
|
llist_del(&smsc->list);
|
|
talloc_free(smsc);
|
|
}
|
|
|
|
struct hlr_smsc_route *smsc_route_find(struct hlr *hlr, const char *num_addr)
|
|
{
|
|
struct hlr_smsc_route *rt;
|
|
|
|
llist_for_each_entry(rt, &hlr->smsc_routes, list) {
|
|
if (!strcmp(rt->num_addr, num_addr))
|
|
return rt;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct hlr_smsc_route *smsc_route_alloc(struct hlr *hlr, const char *num_addr,
|
|
struct hlr_smsc *smsc)
|
|
{
|
|
struct hlr_smsc_route *rt;
|
|
|
|
if (smsc_route_find(hlr, num_addr))
|
|
return NULL;
|
|
|
|
rt = talloc_zero(hlr, struct hlr_smsc_route);
|
|
rt->num_addr = talloc_strdup(rt, num_addr);
|
|
rt->smsc = smsc;
|
|
llist_add_tail(&rt->list, &hlr->smsc_routes);
|
|
|
|
return rt;
|
|
}
|
|
|
|
void smsc_route_free(struct hlr_smsc_route *rt)
|
|
{
|
|
llist_del(&rt->list);
|
|
talloc_free(rt);
|
|
}
|
|
|
|
/***********************************************************************
|
|
* forwarding of MO SMS to SMSCs based on SM-RP-DA
|
|
***********************************************************************/
|
|
|
|
static const struct hlr_smsc *find_smsc_route(const char *smsc_addr)
|
|
{
|
|
const struct hlr_smsc_route *rt;
|
|
|
|
rt = smsc_route_find(g_hlr, smsc_addr);
|
|
if (rt)
|
|
return rt->smsc;
|
|
return g_hlr->smsc_default;
|
|
}
|
|
|
|
static void respond_with_sm_rp_cause(struct osmo_gsup_req *req,
|
|
uint8_t sm_rp_cause)
|
|
{
|
|
struct osmo_gsup_message rsp_msg = { };
|
|
|
|
rsp_msg.sm_rp_cause = &sm_rp_cause;
|
|
osmo_gsup_req_respond(req, &rsp_msg, true, true);
|
|
}
|
|
|
|
/* Short Message from MSC/VLR towards SMSC */
|
|
void forward_mo_sms(struct osmo_gsup_req *req)
|
|
{
|
|
uint8_t gsm48_decode_buffer[GSM411_SMSC_ADDR_MAX_OCTETS];
|
|
char smsc_addr[GSM411_SMSC_ADDR_MAX_DIGITS+1];
|
|
const struct hlr_smsc *smsc;
|
|
struct osmo_cni_peer_id dest_peer;
|
|
|
|
/* Make sure SM-RP-DA (SMSC address) is present */
|
|
if (req->gsup.sm_rp_da == NULL || !req->gsup.sm_rp_da_len) {
|
|
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
|
|
"missing SM-RP-DA");
|
|
return;
|
|
}
|
|
|
|
if (req->gsup.sm_rp_da_type != OSMO_GSUP_SMS_SM_RP_ODA_SMSC_ADDR) {
|
|
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
|
|
"SM-RP-DA type is not SMSC");
|
|
return;
|
|
}
|
|
|
|
/* Enforce the length constrainst on SM-RP-DA, as specified in
|
|
* GSM 04.11 section 8.2.5.2. Also enforce absence of ToN/NPI
|
|
* extension octets at the same time. */
|
|
if (req->gsup.sm_rp_da_len < GSM411_SMSC_ADDR_MIN_OCTETS ||
|
|
req->gsup.sm_rp_da_len > GSM411_SMSC_ADDR_MAX_OCTETS ||
|
|
!(req->gsup.sm_rp_da[0] & 0x80)) {
|
|
/* This form of bogosity originates from the MS,
|
|
* not from OsmoMSC or any other Osmocom network elements! */
|
|
LOGP(DLSMS, LOGL_NOTICE,
|
|
"Rx '%s' (IMSI-%s) contains invalid SM-RP-DA from MS\n",
|
|
osmo_gsup_message_type_name(req->gsup.message_type),
|
|
req->gsup.imsi);
|
|
respond_with_sm_rp_cause(req, GSM411_RP_CAUSE_SEMANT_INC_MSG);
|
|
return;
|
|
}
|
|
|
|
/* Decode SMSC address from SM-RP-DA */
|
|
gsm48_decode_buffer[0] = req->gsup.sm_rp_da_len - 1;
|
|
memcpy(gsm48_decode_buffer + 1, req->gsup.sm_rp_da + 1,
|
|
req->gsup.sm_rp_da_len - 1);
|
|
gsm48_decode_bcd_number2(smsc_addr, sizeof(smsc_addr),
|
|
gsm48_decode_buffer,
|
|
req->gsup.sm_rp_da_len, 0);
|
|
|
|
/* Look for a route to this SMSC */
|
|
smsc = find_smsc_route(smsc_addr);
|
|
if (smsc == NULL) {
|
|
LOGP(DLSMS, LOGL_NOTICE,
|
|
"Failed to find a route for '%s' (IMSI-%s, SMSC-Addr-%s)\n",
|
|
osmo_gsup_message_type_name(req->gsup.message_type),
|
|
req->gsup.imsi, smsc_addr);
|
|
respond_with_sm_rp_cause(req,
|
|
GSM411_RP_CAUSE_MO_NUM_UNASSIGNED);
|
|
return;
|
|
}
|
|
|
|
/* We got the IPA name of our SMSC - forward the message */
|
|
osmo_cni_peer_id_set(&dest_peer, OSMO_CNI_PEER_ID_IPA_NAME,
|
|
(const uint8_t *) smsc->name,
|
|
strlen(smsc->name) + 1);
|
|
osmo_gsup_forward_to_local_peer(req->cb_data, &dest_peer, req, NULL);
|
|
}
|
|
|
|
/***********************************************************************
|
|
* forwarding of MT SMS from SMSCs to MSC/VLR based on IMSI
|
|
***********************************************************************/
|
|
|
|
void forward_mt_sms(struct osmo_gsup_req *req)
|
|
{
|
|
struct hlr_subscriber subscr;
|
|
struct osmo_cni_peer_id dest_peer;
|
|
int rc;
|
|
|
|
rc = db_subscr_get_by_imsi(g_hlr->dbc, req->gsup.imsi, &subscr);
|
|
if (rc < 0) {
|
|
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN,
|
|
"IMSI unknown");
|
|
return;
|
|
}
|
|
/* is this subscriber currently attached to a VLR? */
|
|
if (!subscr.vlr_number[0]) {
|
|
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMPL_DETACHED,
|
|
"subscriber not attached to a VLR");
|
|
return;
|
|
}
|
|
osmo_cni_peer_id_set(&dest_peer, OSMO_CNI_PEER_ID_IPA_NAME,
|
|
(const uint8_t *) subscr.vlr_number,
|
|
strlen(subscr.vlr_number) + 1);
|
|
osmo_gsup_forward_to_local_peer(req->cb_data, &dest_peer, req, NULL);
|
|
}
|
|
|
|
/***********************************************************************
|
|
* READY-FOR-SM handling
|
|
*
|
|
* An MSC indicates that an MS is ready to receive messages. If one
|
|
* or more SMSCs have previously tried to send MT SMS to this MS and
|
|
* failed, we should pass this READY-FOR-SM message to them so they
|
|
* can resend their queued SMS right away. But which SMSC do we
|
|
* forward the message to? 3GPP specs call for a complicated system
|
|
* where the HLR remembers which SMSCs have tried and failed to deliver
|
|
* MT SMS, and those SMSCs then get notified - but that design is too
|
|
* much complexity for the current state of Osmocom. So we keep it
|
|
* simple: we iterate over all configured SMSCs and forward a copy
|
|
* of the READY-FOR-SM.req message to each.
|
|
*
|
|
* Routing of responses is another problem: the MSC that sent
|
|
* READY-FOR-SM.req expects only one response, and one can even argue
|
|
* that the operation is a "success" from the perspective of the MS
|
|
* irrespective of whether each given SMSC handled the notification
|
|
* successfully or not. Hence our approach: we always return success
|
|
* to the MS, and when we forward copies of READY-FOR-SM.req to SMSCs,
|
|
* we list the HLR as the message source - this way SMSC responses
|
|
* will terminate at this HLR and won't be forwarded to the MSC.
|
|
***********************************************************************/
|
|
|
|
static void forward_req_copy_to_smsc(const struct osmo_gsup_req *req,
|
|
const struct hlr_smsc *smsc)
|
|
{
|
|
const char *my_ipa_name = g_hlr->gsup_unit_name.serno;
|
|
struct osmo_gsup_message forward = req->gsup;
|
|
struct osmo_ipa_name smsc_ipa_name;
|
|
|
|
/* set the source to this HLR */
|
|
forward.source_name = (const uint8_t *) my_ipa_name;
|
|
forward.source_name_len = strlen(my_ipa_name) + 1;
|
|
|
|
/* send it off */
|
|
LOG_GSUP_REQ(req, LOGL_INFO, "Forwarding source-reset copy to %s\n",
|
|
smsc->name);
|
|
osmo_ipa_name_set(&smsc_ipa_name, (const uint8_t *) smsc->name,
|
|
strlen(smsc->name) + 1);
|
|
osmo_gsup_enc_send_to_ipa_name(g_hlr->gs, &smsc_ipa_name, &forward);
|
|
}
|
|
|
|
void rx_ready_for_sm_req(struct osmo_gsup_req *req)
|
|
{
|
|
struct hlr_smsc *smsc;
|
|
|
|
/* fan request msg out to all SMSCs */
|
|
llist_for_each_entry(smsc, &g_hlr->smsc_list, list)
|
|
forward_req_copy_to_smsc(req, smsc);
|
|
|
|
/* send OK response to the MSC and the MS */
|
|
osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_READY_FOR_SM_RESULT,
|
|
true);
|
|
}
|