blob: 72729b3ec84ccffd39705d2db07ab5865178837b [file] [log] [blame]
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +01001/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
2 *
3 * All Rights Reserved
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20#include <string.h>
21#include <errno.h>
22#include <osmocom/core/sockaddr_str.h>
23#include <osmocom/gsm/gsup.h>
24#include <osmocom/mslookup/mslookup.h>
25#include <osmocom/hlr/logging.h>
26#include <osmocom/hlr/hlr.h>
27#include <osmocom/hlr/db.h>
28#include <osmocom/hlr/timestamp.h>
29#include <osmocom/hlr/mslookup_server.h>
Neels Hofmeyr62d916f2019-11-20 03:35:37 +010030#include <osmocom/hlr/proxy.h>
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +010031
32static const struct osmo_mslookup_result not_found = {
33 .rc = OSMO_MSLOOKUP_RC_NOT_FOUND,
34 };
35const struct osmo_ipa_name mslookup_server_msc_wildcard = {};
36
37static void set_result(struct osmo_mslookup_result *result,
38 const struct mslookup_service_host *service_host,
39 uint32_t age)
40{
41 if (!osmo_sockaddr_str_is_nonzero(&service_host->host_v4)
42 && !osmo_sockaddr_str_is_nonzero(&service_host->host_v6)) {
43 *result = not_found;
44 return;
45 }
46 result->rc = OSMO_MSLOOKUP_RC_RESULT;
47 result->host_v4 = service_host->host_v4;
48 result->host_v6 = service_host->host_v6;
49 result->age = age;
50}
51
Neels Hofmeyre78241a2019-12-06 17:09:56 +010052const struct mslookup_service_host *mslookup_server_get_local_gsup_addr()
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +010053{
54 static struct mslookup_service_host gsup_bind = {};
55 struct mslookup_service_host *host;
56
57 /* Find a HLR/GSUP service set for the server (no VLR unit name) */
58 host = mslookup_server_service_get(&mslookup_server_msc_wildcard, OSMO_MSLOOKUP_SERVICE_HLR_GSUP);
59 if (host)
60 return host;
61
62 /* Try to use the locally configured GSUP bind address */
63 osmo_sockaddr_str_from_str(&gsup_bind.host_v4, g_hlr->gsup_bind_addr, OSMO_GSUP_PORT);
64 if (gsup_bind.host_v4.af == AF_INET6) {
65 gsup_bind.host_v6 = gsup_bind.host_v4;
66 gsup_bind.host_v4 = (struct osmo_sockaddr_str){};
67 }
68 return &gsup_bind;
69}
70
71struct mslookup_server_msc_cfg *mslookup_server_msc_get(const struct osmo_ipa_name *msc_name, bool create)
72{
73 struct llist_head *c = &g_hlr->mslookup.server.local_site_services;
74 struct mslookup_server_msc_cfg *msc;
75
76 if (!msc_name)
77 return NULL;
78
79 llist_for_each_entry(msc, c, entry) {
80 if (osmo_ipa_name_cmp(&msc->name, msc_name))
81 continue;
82 return msc;
83 }
84 if (!create)
85 return NULL;
86
87 msc = talloc_zero(g_hlr, struct mslookup_server_msc_cfg);
88 OSMO_ASSERT(msc);
89 INIT_LLIST_HEAD(&msc->service_hosts);
90 msc->name = *msc_name;
91 llist_add_tail(&msc->entry, c);
92 return msc;
93}
94
95struct mslookup_service_host *mslookup_server_msc_service_get(struct mslookup_server_msc_cfg *msc, const char *service,
96 bool create)
97{
98 struct mslookup_service_host *e;
99 if (!msc)
100 return NULL;
101
102 llist_for_each_entry(e, &msc->service_hosts, entry) {
103 if (!strcmp(e->service, service))
104 return e;
105 }
106
107 if (!create)
108 return NULL;
109
110 e = talloc_zero(msc, struct mslookup_service_host);
111 OSMO_ASSERT(e);
112 OSMO_STRLCPY_ARRAY(e->service, service);
113 llist_add_tail(&e->entry, &msc->service_hosts);
114 return e;
115}
116
117struct mslookup_service_host *mslookup_server_service_get(const struct osmo_ipa_name *msc_name, const char *service)
118{
119 struct mslookup_server_msc_cfg *msc = mslookup_server_msc_get(msc_name, false);
120 if (!msc)
121 return NULL;
122 return mslookup_server_msc_service_get(msc, service, false);
123}
124
125int mslookup_server_msc_service_set(struct mslookup_server_msc_cfg *msc, const char *service,
126 const struct osmo_sockaddr_str *addr)
127{
128 struct mslookup_service_host *e;
129
130 if (!service || !service[0]
131 || strlen(service) > OSMO_MSLOOKUP_SERVICE_MAXLEN)
132 return -EINVAL;
133 if (!addr || !osmo_sockaddr_str_is_nonzero(addr))
134 return -EINVAL;
135
136 e = mslookup_server_msc_service_get(msc, service, true);
137 if (!e)
138 return -EINVAL;
139
140 switch (addr->af) {
141 case AF_INET:
142 e->host_v4 = *addr;
143 break;
144 case AF_INET6:
145 e->host_v6 = *addr;
146 break;
147 default:
148 return -EINVAL;
149 }
150 return 0;
151}
152
153int mslookup_server_msc_service_del(struct mslookup_server_msc_cfg *msc, const char *service,
154 const struct osmo_sockaddr_str *addr)
155{
156 struct mslookup_service_host *e, *n;
157 int deleted = 0;
158
159 if (!msc)
160 return -ENOENT;
161
162 llist_for_each_entry_safe(e, n, &msc->service_hosts, entry) {
163 if (service && strcmp(service, e->service))
164 continue;
165
166 if (addr) {
167 if (!osmo_sockaddr_str_cmp(addr, &e->host_v4)) {
168 e->host_v4 = (struct osmo_sockaddr_str){};
169 /* Removed one addr. If the other is still there, keep the entry. */
170 if (osmo_sockaddr_str_is_nonzero(&e->host_v6))
171 continue;
172 } else if (!osmo_sockaddr_str_cmp(addr, &e->host_v6)) {
173 e->host_v6 = (struct osmo_sockaddr_str){};
174 /* Removed one addr. If the other is still there, keep the entry. */
175 if (osmo_sockaddr_str_is_nonzero(&e->host_v4))
176 continue;
177 } else
178 /* No addr match, keep the entry. */
179 continue;
180 /* Addr matched and none is left. Delete. */
181 }
182 llist_del(&e->entry);
183 talloc_free(e);
184 deleted++;
185 }
186 return deleted;
187}
188
189/* A remote entity is asking us whether we are the home HLR of the given subscriber. */
190static void mslookup_server_rx_hlr_gsup(const struct osmo_mslookup_query *query,
191 struct osmo_mslookup_result *result)
192{
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100193 const struct mslookup_service_host *host;
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100194 int rc;
195 switch (query->id.type) {
196 case OSMO_MSLOOKUP_ID_IMSI:
197 rc = db_subscr_exists_by_imsi(g_hlr->dbc, query->id.imsi);
198 break;
199 case OSMO_MSLOOKUP_ID_MSISDN:
200 rc = db_subscr_exists_by_msisdn(g_hlr->dbc, query->id.msisdn);
201 break;
202 default:
203 LOGP(DMSLOOKUP, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type);
204 *result = not_found;
205 return;
206 }
207
208 if (rc) {
209 LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: does not exist in local HLR\n",
210 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
211 *result = not_found;
212 return;
213 }
214
215 LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: found in local HLR\n",
216 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
217
218 host = mslookup_server_get_local_gsup_addr();
219
220 set_result(result, host, 0);
221 if (result->rc != OSMO_MSLOOKUP_RC_RESULT) {
222 LOGP(DMSLOOKUP, LOGL_ERROR,
223 "Subscriber found, but error in service '" OSMO_MSLOOKUP_SERVICE_HLR_GSUP "' config:"
224 " v4: " OSMO_SOCKADDR_STR_FMT " v6: " OSMO_SOCKADDR_STR_FMT "\n",
225 OSMO_SOCKADDR_STR_FMT_ARGS(&host->host_v4),
226 OSMO_SOCKADDR_STR_FMT_ARGS(&host->host_v6));
227 }
228}
229
230/* Look in the local HLR record: If the subscriber is "at home" in this HLR and is also currently located at a local
231 * VLR, we will find a valid location updating with vlr_number, and no vlr_via_proxy entry. */
232static bool subscriber_has_done_lu_here_hlr(const struct osmo_mslookup_query *query,
233 uint32_t *lu_age,
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100234 struct osmo_ipa_name *local_msc_name,
235 struct hlr_subscriber *ret_subscr)
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100236{
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100237 struct hlr_subscriber _subscr;
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100238 int rc;
239 uint32_t age;
240
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100241 struct hlr_subscriber *subscr = ret_subscr ? : &_subscr;
242
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100243 switch (query->id.type) {
244 case OSMO_MSLOOKUP_ID_IMSI:
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100245 rc = db_subscr_get_by_imsi(g_hlr->dbc, query->id.imsi, subscr);
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100246 break;
247 case OSMO_MSLOOKUP_ID_MSISDN:
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100248 rc = db_subscr_get_by_msisdn(g_hlr->dbc, query->id.msisdn, subscr);
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100249 break;
250 default:
251 LOGP(DMSLOOKUP, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type);
252 return false;
253 }
254
255 if (rc) {
256 LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: does not exist in local HLR\n",
257 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
258 return false;
259 }
260
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100261 if (!subscr->vlr_number[0]) {
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100262 LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: not attached (vlr_number unset)\n",
263 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
264 }
265
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100266 if (subscr->vlr_via_proxy.len) {
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100267 /* The VLR is behind a proxy, the subscriber is not attached to a local VLR but a remote one. That
268 * remote proxy should instead respond to the service lookup request. */
269 LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: last attach is not at local VLR, but at VLR '%s' via proxy %s\n",
270 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100271 subscr->vlr_number,
272 osmo_ipa_name_to_str(&subscr->vlr_via_proxy));
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100273 return false;
274 }
275
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100276 if (!timestamp_age(&subscr->last_lu_seen, &age)) {
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100277 LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: Invalid last_lu_seen timestamp for subscriber\n",
278 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
279 return false;
280 }
281 if (age > g_hlr->mslookup.server.local_attach_max_age) {
282 LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: last attach was here, but too long ago: %us > %us\n",
283 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
284 age, g_hlr->mslookup.server.local_attach_max_age);
285 return false;
286 }
287
288 *lu_age = age;
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100289 osmo_ipa_name_set_str(local_msc_name, subscr->vlr_number);
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100290 LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: attached %u seconds ago at local VLR %s\n",
291 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
292 age, osmo_ipa_name_to_str(local_msc_name));
293
294 return true;
295}
296
Neels Hofmeyr62d916f2019-11-20 03:35:37 +0100297
298/* Determine whether the subscriber with the given ID has routed a Location Updating via this HLR as first hop. Return
299 * true if it is attached at a local VLR, and we are serving as proxy for a remote home HLR.
300 */
301static bool subscriber_has_done_lu_here_proxy(const struct osmo_mslookup_query *query,
302 uint32_t *lu_age,
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100303 struct osmo_ipa_name *local_msc_name,
304 struct proxy_subscr *ret_proxy_subscr)
Neels Hofmeyr62d916f2019-11-20 03:35:37 +0100305{
306 struct proxy_subscr proxy_subscr;
307 uint32_t age;
308 int rc;
309
310 /* See the local HLR record. If the subscriber is "at home" in this HLR and is also currently located here, we
311 * will find a valid location updating and no vlr_via_proxy entry. */
312 switch (query->id.type) {
313 case OSMO_MSLOOKUP_ID_IMSI:
314 rc = proxy_subscr_get_by_imsi(&proxy_subscr, g_hlr->gs->proxy, query->id.imsi);
315 break;
316 case OSMO_MSLOOKUP_ID_MSISDN:
317 rc = proxy_subscr_get_by_msisdn(&proxy_subscr, g_hlr->gs->proxy, query->id.msisdn);
318 break;
319 default:
320 LOGP(DDGSM, LOGL_ERROR, "%s: unknown ID type\n",
321 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
322 return false;
323 }
324
325 if (rc) {
326 LOGP(DDGSM, LOGL_DEBUG, "%s: does not exist in GSUP proxy\n",
327 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
328 return false;
329 }
330
331 /* We only need to care about CS LU, since only CS services need D-GSM routing. */
332 if (!timestamp_age(&proxy_subscr.cs.last_lu, &age)
333 || age > g_hlr->mslookup.server.local_attach_max_age) {
334 LOGP(DDGSM, LOGL_ERROR,
335 "%s: last attach was at local VLR (proxying for remote HLR), but too long ago: %us > %us\n",
336 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
337 age, g_hlr->mslookup.server.local_attach_max_age);
338 return false;
339 }
340
341 if (proxy_subscr.cs.vlr_via_proxy.len) {
342 LOGP(DDGSM, LOGL_DEBUG, "%s: last attach is not at local VLR, but at VLR '%s' via proxy '%s'\n",
343 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
344 osmo_ipa_name_to_str(&proxy_subscr.cs.vlr_name),
345 osmo_ipa_name_to_str(&proxy_subscr.cs.vlr_via_proxy));
346 return false;
347 }
348
349 *lu_age = age;
350 *local_msc_name = proxy_subscr.cs.vlr_name;
351 LOGP(DDGSM, LOGL_DEBUG, "%s: attached %u seconds ago at local VLR %s; proxying for remote HLR "
352 OSMO_SOCKADDR_STR_FMT "\n",
353 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
354 age, osmo_ipa_name_to_str(local_msc_name),
355 OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr.remote_hlr_addr));
356
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100357 if (ret_proxy_subscr)
358 *ret_proxy_subscr = proxy_subscr;
Neels Hofmeyr62d916f2019-11-20 03:35:37 +0100359 return true;
360}
361
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100362bool subscriber_has_done_lu_here(const struct osmo_mslookup_query *query,
363 uint32_t *lu_age_p, struct osmo_ipa_name *local_msc_name,
364 char *ret_imsi, size_t ret_imsi_len)
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100365{
366 bool attached_here;
367 uint32_t lu_age = 0;
368 struct osmo_ipa_name msc_name = {};
Neels Hofmeyr62d916f2019-11-20 03:35:37 +0100369 bool attached_here_proxy;
370 uint32_t proxy_lu_age = 0;
371 struct osmo_ipa_name proxy_msc_name = {};
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100372 struct proxy_subscr proxy_subscr;
373 struct hlr_subscriber db_subscr;
374
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100375
376 /* First ask the local HLR db, but if the local proxy record indicates a more recent LU, use that instead.
377 * For all usual cases, only one of these will reflect a LU, even if a subscriber had more than one home HLR:
378 * - if the subscriber is known here, we will never proxy.
379 * - if the subscriber is not known here, this local HLR db will never record a LU.
380 * However, if a subscriber was being proxied to a remote home HLR, and if then the subscriber was also added to
381 * the local HLR database, there might occur a situation where both reflect a LU. So, to be safe against all
382 * situations, compare the two entries.
383 */
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100384 attached_here = subscriber_has_done_lu_here_hlr(query, &lu_age, &msc_name, &db_subscr);
385 attached_here_proxy = subscriber_has_done_lu_here_proxy(query, &proxy_lu_age, &proxy_msc_name, &proxy_subscr);
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100386
Neels Hofmeyr62d916f2019-11-20 03:35:37 +0100387 /* If proxy has a younger lu, replace. */
388 if (attached_here_proxy && (!attached_here || (proxy_lu_age < lu_age))) {
389 attached_here = true;
390 lu_age = proxy_lu_age;
391 msc_name = proxy_msc_name;
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100392 if (ret_imsi)
393 osmo_strlcpy(ret_imsi, proxy_subscr.imsi, ret_imsi_len);
394 } else if (attached_here) {
395 if (ret_imsi)
396 osmo_strlcpy(ret_imsi, db_subscr.imsi, ret_imsi_len);
Neels Hofmeyr62d916f2019-11-20 03:35:37 +0100397 }
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100398
399 if (attached_here && !msc_name.len) {
400 LOGP(DMSLOOKUP, LOGL_ERROR, "%s: attached here, but no VLR name known\n",
401 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
402 return false;
403 }
404
405 if (!attached_here) {
406 /* Already logged "not attached" for both local-db and proxy attach */
407 return false;
408 }
409
410 LOGP(DMSLOOKUP, LOGL_INFO, "%s: attached here, at VLR %s\n",
411 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
412 osmo_ipa_name_to_str(&msc_name));
413 *lu_age_p = lu_age;
414 *local_msc_name = msc_name;
415 return true;
416}
417
418/* A remote entity is asking us whether we are providing the given service for the given subscriber. */
419void mslookup_server_rx(const struct osmo_mslookup_query *query,
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100420 struct osmo_mslookup_result *result)
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100421{
422 const struct mslookup_service_host *service_host;
423 uint32_t age;
424 struct osmo_ipa_name msc_name;
425
426 /* A request for a home HLR: answer exactly if this is the subscriber's home HLR, i.e. the IMSI is listed in the
427 * HLR database. */
428 if (strcmp(query->service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP) == 0)
429 return mslookup_server_rx_hlr_gsup(query, result);
430
431 /* All other service types: answer when the subscriber has done a LU that is either listed in the local HLR or
432 * in the GSUP proxy database: i.e. if the subscriber has done a Location Updating at an VLR belonging to this
433 * HLR. Respond with whichever services are configured in the osmo-hlr.cfg. */
Neels Hofmeyre78241a2019-12-06 17:09:56 +0100434 if (!subscriber_has_done_lu_here(query, &age, &msc_name, NULL, 0)) {
Neels Hofmeyr6aa871d2019-11-20 03:35:37 +0100435 *result = not_found;
436 return;
437 }
438
439 /* We've detected a LU here. The VLR where the LU happened is stored in msc_unit_name, and the LU age is stored
440 * in 'age'. Figure out the address configured for that VLR and service name. */
441 service_host = mslookup_server_service_get(&msc_name, query->service);
442
443 if (!service_host) {
444 /* Find such service set globally (no VLR unit name) */
445 service_host = mslookup_server_service_get(&mslookup_server_msc_wildcard, query->service);
446 }
447
448 if (!service_host) {
449 LOGP(DMSLOOKUP, LOGL_ERROR,
450 "%s: subscriber found, but no service %s configured, cannot service lookup request\n",
451 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
452 osmo_quote_str_c(OTC_SELECT, query->service, -1));
453 *result = not_found;
454 return;
455 }
456
457 set_result(result, service_host, age);
458}