blob: d923e9089c7a37b6359124c6a0adf7071490fb8c [file] [log] [blame]
Mychaela N. Falconiaff7c7ea2023-09-21 01:55:51 +00001/* OsmoHLR SMS-over-GSUP routing implementation */
2
3/* Author: Mychaela N. Falconia <falcon@freecalypso.org>, 2023 - however,
4 * Mother Mychaela's contributions are NOT subject to copyright.
5 * No rights reserved, all rights relinquished.
6 *
7 * Based on earlier unmerged work by Vadim Yanitskiy, 2019.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU Affero General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Affero General Public License for more details.
18 *
19 * You should have received a copy of the GNU Affero General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23#include <stdint.h>
24#include <string.h>
25#include <errno.h>
26
27#include <osmocom/core/talloc.h>
28#include <osmocom/gsm/gsup.h>
Mychaela N. Falconia786ec5b2023-09-19 17:15:43 +000029#include <osmocom/gsm/gsm48_ie.h>
30#include <osmocom/gsm/protocol/gsm_04_11.h>
Mychaela N. Falconiaff7c7ea2023-09-21 01:55:51 +000031
32#include <osmocom/hlr/hlr.h>
33#include <osmocom/hlr/hlr_sms.h>
34#include <osmocom/hlr/gsup_server.h>
35#include <osmocom/hlr/gsup_router.h>
36#include <osmocom/hlr/logging.h>
37#include <osmocom/hlr/db.h>
38
39/***********************************************************************
40 * core data structures expressing config from VTY
41 ***********************************************************************/
42
43struct hlr_smsc *smsc_find(struct hlr *hlr, const char *name)
44{
45 struct hlr_smsc *smsc;
46
47 llist_for_each_entry(smsc, &hlr->smsc_list, list) {
48 if (!strcmp(smsc->name, name))
49 return smsc;
50 }
51 return NULL;
52}
53
54struct hlr_smsc *smsc_alloc(struct hlr *hlr, const char *name)
55{
56 struct hlr_smsc *smsc = smsc_find(hlr, name);
57 if (smsc)
58 return NULL;
59
60 smsc = talloc_zero(hlr, struct hlr_smsc);
61 smsc->name = talloc_strdup(smsc, name);
62 smsc->hlr = hlr;
63 llist_add_tail(&smsc->list, &hlr->smsc_list);
64
65 return smsc;
66}
67
68void smsc_free(struct hlr_smsc *smsc)
69{
70 llist_del(&smsc->list);
71 talloc_free(smsc);
72}
73
74struct hlr_smsc_route *smsc_route_find(struct hlr *hlr, const char *num_addr)
75{
76 struct hlr_smsc_route *rt;
77
78 llist_for_each_entry(rt, &hlr->smsc_routes, list) {
79 if (!strcmp(rt->num_addr, num_addr))
80 return rt;
81 }
82 return NULL;
83}
84
85struct hlr_smsc_route *smsc_route_alloc(struct hlr *hlr, const char *num_addr,
86 struct hlr_smsc *smsc)
87{
88 struct hlr_smsc_route *rt;
89
90 if (smsc_route_find(hlr, num_addr))
91 return NULL;
92
93 rt = talloc_zero(hlr, struct hlr_smsc_route);
94 rt->num_addr = talloc_strdup(rt, num_addr);
95 rt->smsc = smsc;
96 llist_add_tail(&rt->list, &hlr->smsc_routes);
97
98 return rt;
99}
100
101void smsc_route_free(struct hlr_smsc_route *rt)
102{
103 llist_del(&rt->list);
104 talloc_free(rt);
105}
Mychaela N. Falconia786ec5b2023-09-19 17:15:43 +0000106
107/***********************************************************************
108 * forwarding of MO SMS to SMSCs based on SM-RP-DA
109 ***********************************************************************/
110
111static const struct hlr_smsc *find_smsc_route(const char *smsc_addr)
112{
113 const struct hlr_smsc_route *rt;
114
115 rt = smsc_route_find(g_hlr, smsc_addr);
116 if (rt)
117 return rt->smsc;
118 return g_hlr->smsc_default;
119}
120
121static void respond_with_sm_rp_cause(struct osmo_gsup_req *req,
122 uint8_t sm_rp_cause)
123{
124 struct osmo_gsup_message rsp_msg = { };
125
126 rsp_msg.sm_rp_cause = &sm_rp_cause;
127 osmo_gsup_req_respond(req, &rsp_msg, true, true);
128}
129
130/* Short Message from MSC/VLR towards SMSC */
131void forward_mo_sms(struct osmo_gsup_req *req)
132{
133 uint8_t gsm48_decode_buffer[GSM411_SMSC_ADDR_MAX_OCTETS];
134 char smsc_addr[GSM411_SMSC_ADDR_MAX_DIGITS+1];
135 const struct hlr_smsc *smsc;
136 struct osmo_cni_peer_id dest_peer;
137
138 /* Make sure SM-RP-DA (SMSC address) is present */
139 if (req->gsup.sm_rp_da == NULL || !req->gsup.sm_rp_da_len) {
140 osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
141 "missing SM-RP-DA");
142 return;
143 }
144
145 if (req->gsup.sm_rp_da_type != OSMO_GSUP_SMS_SM_RP_ODA_SMSC_ADDR) {
146 osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
147 "SM-RP-DA type is not SMSC");
148 return;
149 }
150
151 /* Enforce the length constrainst on SM-RP-DA, as specified in
152 * GSM 04.11 section 8.2.5.2. Also enforce absence of ToN/NPI
153 * extension octets at the same time. */
154 if (req->gsup.sm_rp_da_len < GSM411_SMSC_ADDR_MIN_OCTETS ||
155 req->gsup.sm_rp_da_len > GSM411_SMSC_ADDR_MAX_OCTETS ||
156 !(req->gsup.sm_rp_da[0] & 0x80)) {
157 /* This form of bogosity originates from the MS,
158 * not from OsmoMSC or any other Osmocom network elements! */
159 LOGP(DLSMS, LOGL_NOTICE,
160 "Rx '%s' (IMSI-%s) contains invalid SM-RP-DA from MS\n",
161 osmo_gsup_message_type_name(req->gsup.message_type),
162 req->gsup.imsi);
163 respond_with_sm_rp_cause(req, GSM411_RP_CAUSE_SEMANT_INC_MSG);
164 return;
165 }
166
167 /* Decode SMSC address from SM-RP-DA */
168 gsm48_decode_buffer[0] = req->gsup.sm_rp_da_len - 1;
169 memcpy(gsm48_decode_buffer + 1, req->gsup.sm_rp_da + 1,
170 req->gsup.sm_rp_da_len - 1);
171 gsm48_decode_bcd_number2(smsc_addr, sizeof(smsc_addr),
172 gsm48_decode_buffer,
173 req->gsup.sm_rp_da_len, 0);
174
175 /* Look for a route to this SMSC */
176 smsc = find_smsc_route(smsc_addr);
177 if (smsc == NULL) {
178 LOGP(DLSMS, LOGL_NOTICE,
179 "Failed to find a route for '%s' (IMSI-%s, SMSC-Addr-%s)\n",
180 osmo_gsup_message_type_name(req->gsup.message_type),
181 req->gsup.imsi, smsc_addr);
182 respond_with_sm_rp_cause(req,
183 GSM411_RP_CAUSE_MO_NUM_UNASSIGNED);
184 return;
185 }
186
187 /* We got the IPA name of our SMSC - forward the message */
188 osmo_cni_peer_id_set(&dest_peer, OSMO_CNI_PEER_ID_IPA_NAME,
189 (const uint8_t *) smsc->name,
190 strlen(smsc->name) + 1);
191 osmo_gsup_forward_to_local_peer(req->cb_data, &dest_peer, req, NULL);
192}
Mychaela N. Falconiaf6a303c2023-08-26 01:06:57 +0000193
194/***********************************************************************
195 * forwarding of MT SMS from SMSCs to MSC/VLR based on IMSI
196 ***********************************************************************/
197
198void forward_mt_sms(struct osmo_gsup_req *req)
199{
200 struct hlr_subscriber subscr;
201 struct osmo_cni_peer_id dest_peer;
202 int rc;
203
204 rc = db_subscr_get_by_imsi(g_hlr->dbc, req->gsup.imsi, &subscr);
205 if (rc < 0) {
206 osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN,
207 "IMSI unknown");
208 return;
209 }
210 /* is this subscriber currently attached to a VLR? */
211 if (!subscr.vlr_number[0]) {
212 osmo_gsup_req_respond_err(req, GMM_CAUSE_IMPL_DETACHED,
213 "subscriber not attached to a VLR");
214 return;
215 }
216 osmo_cni_peer_id_set(&dest_peer, OSMO_CNI_PEER_ID_IPA_NAME,
217 (const uint8_t *) subscr.vlr_number,
218 strlen(subscr.vlr_number) + 1);
219 osmo_gsup_forward_to_local_peer(req->cb_data, &dest_peer, req, NULL);
220}
Mychaela N. Falconiae513c432023-08-28 18:46:41 +0000221
222/***********************************************************************
223 * READY-FOR-SM handling
224 *
225 * An MSC indicates that an MS is ready to receive messages. If one
226 * or more SMSCs have previously tried to send MT SMS to this MS and
227 * failed, we should pass this READY-FOR-SM message to them so they
228 * can resend their queued SMS right away. But which SMSC do we
229 * forward the message to? 3GPP specs call for a complicated system
230 * where the HLR remembers which SMSCs have tried and failed to deliver
231 * MT SMS, and those SMSCs then get notified - but that design is too
232 * much complexity for the current state of Osmocom. So we keep it
233 * simple: we iterate over all configured SMSCs and forward a copy
234 * of the READY-FOR-SM.req message to each.
235 *
236 * Routing of responses is another problem: the MSC that sent
237 * READY-FOR-SM.req expects only one response, and one can even argue
238 * that the operation is a "success" from the perspective of the MS
239 * irrespective of whether each given SMSC handled the notification
240 * successfully or not. Hence our approach: we always return success
241 * to the MS, and when we forward copies of READY-FOR-SM.req to SMSCs,
242 * we list the HLR as the message source - this way SMSC responses
243 * will terminate at this HLR and won't be forwarded to the MSC.
244 ***********************************************************************/
245
246static void forward_req_copy_to_smsc(const struct osmo_gsup_req *req,
247 const struct hlr_smsc *smsc)
248{
249 const char *my_ipa_name = g_hlr->gsup_unit_name.serno;
250 struct osmo_gsup_message forward = req->gsup;
251 struct osmo_ipa_name smsc_ipa_name;
252
253 /* set the source to this HLR */
254 forward.source_name = (const uint8_t *) my_ipa_name;
255 forward.source_name_len = strlen(my_ipa_name) + 1;
256
257 /* send it off */
258 LOG_GSUP_REQ(req, LOGL_INFO, "Forwarding source-reset copy to %s\n",
259 smsc->name);
260 osmo_ipa_name_set(&smsc_ipa_name, (const uint8_t *) smsc->name,
261 strlen(smsc->name) + 1);
262 osmo_gsup_enc_send_to_ipa_name(g_hlr->gs, &smsc_ipa_name, &forward);
263}
264
265void rx_ready_for_sm_req(struct osmo_gsup_req *req)
266{
267 struct hlr_smsc *smsc;
268
269 /* fan request msg out to all SMSCs */
270 llist_for_each_entry(smsc, &g_hlr->smsc_list, list)
271 forward_req_copy_to_smsc(req, smsc);
272
273 /* send OK response to the MSC and the MS */
274 osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_READY_FOR_SM_RESULT,
275 true);
276}