blob: 9c4dc58d44d62273ccb34f37d6449d02907e9fc8 [file] [log] [blame]
Neels Hofmeyrab7dc402019-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>
30
31static const struct osmo_mslookup_result not_found = {
32 .rc = OSMO_MSLOOKUP_RC_NOT_FOUND,
33 };
34const struct osmo_ipa_name mslookup_server_msc_wildcard = {};
35
36static void set_result(struct osmo_mslookup_result *result,
37 const struct mslookup_service_host *service_host,
38 uint32_t age)
39{
40 if (!osmo_sockaddr_str_is_nonzero(&service_host->host_v4)
41 && !osmo_sockaddr_str_is_nonzero(&service_host->host_v6)) {
42 *result = not_found;
43 return;
44 }
45 result->rc = OSMO_MSLOOKUP_RC_RESULT;
46 result->host_v4 = service_host->host_v4;
47 result->host_v6 = service_host->host_v6;
48 result->age = age;
49}
50
51const struct mslookup_service_host *mslookup_server_get_local_gsup_addr()
52{
53 static struct mslookup_service_host gsup_bind = {};
54 struct mslookup_service_host *host;
55
56 /* Find a HLR/GSUP service set for the server (no VLR unit name) */
57 host = mslookup_server_service_get(&mslookup_server_msc_wildcard, OSMO_MSLOOKUP_SERVICE_HLR_GSUP);
58 if (host)
59 return host;
60
61 /* Try to use the locally configured GSUP bind address */
62 osmo_sockaddr_str_from_str(&gsup_bind.host_v4, g_hlr->gsup_bind_addr, OSMO_GSUP_PORT);
63 if (gsup_bind.host_v4.af == AF_INET6) {
64 gsup_bind.host_v6 = gsup_bind.host_v4;
65 gsup_bind.host_v4 = (struct osmo_sockaddr_str){};
66 }
67 return &gsup_bind;
68}
69
70struct mslookup_server_msc_cfg *mslookup_server_msc_get(const struct osmo_ipa_name *msc_name, bool create)
71{
72 struct llist_head *c = &g_hlr->mslookup.server.local_site_services;
73 struct mslookup_server_msc_cfg *msc;
74
75 if (!msc_name)
76 return NULL;
77
78 llist_for_each_entry(msc, c, entry) {
79 if (osmo_ipa_name_cmp(&msc->name, msc_name))
80 continue;
81 return msc;
82 }
83 if (!create)
84 return NULL;
85
86 msc = talloc_zero(g_hlr, struct mslookup_server_msc_cfg);
87 OSMO_ASSERT(msc);
88 INIT_LLIST_HEAD(&msc->service_hosts);
89 msc->name = *msc_name;
90 llist_add_tail(&msc->entry, c);
91 return msc;
92}
93
94struct mslookup_service_host *mslookup_server_msc_service_get(struct mslookup_server_msc_cfg *msc, const char *service,
95 bool create)
96{
97 struct mslookup_service_host *e;
98 if (!msc)
99 return NULL;
100
101 llist_for_each_entry(e, &msc->service_hosts, entry) {
102 if (!strcmp(e->service, service))
103 return e;
104 }
105
106 if (!create)
107 return NULL;
108
109 e = talloc_zero(msc, struct mslookup_service_host);
110 OSMO_ASSERT(e);
111 OSMO_STRLCPY_ARRAY(e->service, service);
112 llist_add_tail(&e->entry, &msc->service_hosts);
113 return e;
114}
115
116struct mslookup_service_host *mslookup_server_service_get(const struct osmo_ipa_name *msc_name, const char *service)
117{
118 struct mslookup_server_msc_cfg *msc = mslookup_server_msc_get(msc_name, false);
119 if (!msc)
120 return NULL;
121 return mslookup_server_msc_service_get(msc, service, false);
122}
123
124int mslookup_server_msc_service_set(struct mslookup_server_msc_cfg *msc, const char *service,
125 const struct osmo_sockaddr_str *addr)
126{
127 struct mslookup_service_host *e;
128
129 if (!service || !service[0]
130 || strlen(service) > OSMO_MSLOOKUP_SERVICE_MAXLEN)
131 return -EINVAL;
132 if (!addr || !osmo_sockaddr_str_is_nonzero(addr))
133 return -EINVAL;
134
135 e = mslookup_server_msc_service_get(msc, service, true);
136 if (!e)
137 return -EINVAL;
138
139 switch (addr->af) {
140 case AF_INET:
141 e->host_v4 = *addr;
142 break;
143 case AF_INET6:
144 e->host_v6 = *addr;
145 break;
146 default:
147 return -EINVAL;
148 }
149 return 0;
150}
151
152int mslookup_server_msc_service_del(struct mslookup_server_msc_cfg *msc, const char *service,
153 const struct osmo_sockaddr_str *addr)
154{
155 struct mslookup_service_host *e, *n;
156 int deleted = 0;
157
158 if (!msc)
159 return -ENOENT;
160
161 llist_for_each_entry_safe(e, n, &msc->service_hosts, entry) {
162 if (service && strcmp(service, e->service))
163 continue;
164
165 if (addr) {
166 if (!osmo_sockaddr_str_cmp(addr, &e->host_v4)) {
167 e->host_v4 = (struct osmo_sockaddr_str){};
168 /* Removed one addr. If the other is still there, keep the entry. */
169 if (osmo_sockaddr_str_is_nonzero(&e->host_v6))
170 continue;
171 } else if (!osmo_sockaddr_str_cmp(addr, &e->host_v6)) {
172 e->host_v6 = (struct osmo_sockaddr_str){};
173 /* Removed one addr. If the other is still there, keep the entry. */
174 if (osmo_sockaddr_str_is_nonzero(&e->host_v4))
175 continue;
176 } else
177 /* No addr match, keep the entry. */
178 continue;
179 /* Addr matched and none is left. Delete. */
180 }
181 llist_del(&e->entry);
182 talloc_free(e);
183 deleted++;
184 }
185 return deleted;
186}
187
188/* A remote entity is asking us whether we are the home HLR of the given subscriber. */
189static void mslookup_server_rx_hlr_gsup(const struct osmo_mslookup_query *query,
190 struct osmo_mslookup_result *result)
191{
192 const struct mslookup_service_host *host;
193 int rc;
194 switch (query->id.type) {
195 case OSMO_MSLOOKUP_ID_IMSI:
196 rc = db_subscr_exists_by_imsi(g_hlr->dbc, query->id.imsi);
197 break;
198 case OSMO_MSLOOKUP_ID_MSISDN:
199 rc = db_subscr_exists_by_msisdn(g_hlr->dbc, query->id.msisdn);
200 break;
201 default:
202 LOGP(DMSLOOKUP, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type);
203 *result = not_found;
204 return;
205 }
206
207 if (rc) {
208 LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: does not exist in local HLR\n",
209 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
210 *result = not_found;
211 return;
212 }
213
214 LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: found in local HLR\n",
215 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
216
217 host = mslookup_server_get_local_gsup_addr();
218
219 set_result(result, host, 0);
220 if (result->rc != OSMO_MSLOOKUP_RC_RESULT) {
221 LOGP(DMSLOOKUP, LOGL_ERROR,
222 "Subscriber found, but error in service '" OSMO_MSLOOKUP_SERVICE_HLR_GSUP "' config:"
223 " v4: " OSMO_SOCKADDR_STR_FMT " v6: " OSMO_SOCKADDR_STR_FMT "\n",
224 OSMO_SOCKADDR_STR_FMT_ARGS(&host->host_v4),
225 OSMO_SOCKADDR_STR_FMT_ARGS(&host->host_v6));
226 }
227}
228
229/* Look in the local HLR record: If the subscriber is "at home" in this HLR and is also currently located at a local
230 * VLR, we will find a valid location updating with vlr_number, and no vlr_via_proxy entry. */
231static bool subscriber_has_done_lu_here_hlr(const struct osmo_mslookup_query *query,
232 uint32_t *lu_age,
233 struct osmo_ipa_name *local_msc_name,
234 struct hlr_subscriber *ret_subscr)
235{
236 struct hlr_subscriber _subscr;
237 int rc;
238 uint32_t age;
239
240 struct hlr_subscriber *subscr = ret_subscr ? : &_subscr;
241
242 switch (query->id.type) {
243 case OSMO_MSLOOKUP_ID_IMSI:
244 rc = db_subscr_get_by_imsi(g_hlr->dbc, query->id.imsi, subscr);
245 break;
246 case OSMO_MSLOOKUP_ID_MSISDN:
247 rc = db_subscr_get_by_msisdn(g_hlr->dbc, query->id.msisdn, subscr);
248 break;
249 default:
250 LOGP(DMSLOOKUP, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type);
251 return false;
252 }
253
254 if (rc) {
255 LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: does not exist in local HLR\n",
256 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
257 return false;
258 }
259
260 if (!subscr->vlr_number[0]) {
261 LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: not attached (vlr_number unset)\n",
262 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
263 return false;
264 }
265
266 if (subscr->vlr_via_proxy.len) {
267 /* 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),
271 subscr->vlr_number,
272 osmo_ipa_name_to_str(&subscr->vlr_via_proxy));
273 return false;
274 }
275
276 if (!timestamp_age(&subscr->last_lu_seen, &age)) {
277 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;
289 osmo_ipa_name_set_str(local_msc_name, subscr->vlr_number);
290 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
297static bool subscriber_has_done_lu_here(const struct osmo_mslookup_query *query,
298 uint32_t *lu_age_p,
299 struct osmo_ipa_name *local_msc_name)
300{
301 bool attached_here;
302 uint32_t lu_age = 0;
303 struct osmo_ipa_name msc_name = {};
304
305 /* First ask the local HLR db, but if the local proxy record indicates a more recent LU, use that instead.
306 * For all usual cases, only one of these will reflect a LU, even if a subscriber had more than one home HLR:
307 * - if the subscriber is known here, we will never proxy.
308 * - if the subscriber is not known here, this local HLR db will never record a LU.
309 * However, if a subscriber was being proxied to a remote home HLR, and if then the subscriber was also added to
310 * the local HLR database, there might occur a situation where both reflect a LU. So, to be safe against all
311 * situations, compare the two entries.
312 */
313 attached_here = subscriber_has_done_lu_here_hlr(query, &lu_age, &msc_name, NULL);
314
315 /* Future: If proxy has a younger lu, replace. */
316
317 if (attached_here && !msc_name.len) {
318 LOGP(DMSLOOKUP, LOGL_ERROR, "%s: attached here, but no VLR name known\n",
319 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
320 return false;
321 }
322
323 if (!attached_here) {
324 /* Already logged "not attached" for both local-db and proxy attach */
325 return false;
326 }
327
328 LOGP(DMSLOOKUP, LOGL_INFO, "%s: attached here, at VLR %s\n",
329 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
330 osmo_ipa_name_to_str(&msc_name));
331 *lu_age_p = lu_age;
332 *local_msc_name = msc_name;
333 return true;
334}
335
336/* A remote entity is asking us whether we are providing the given service for the given subscriber. */
337void mslookup_server_rx(const struct osmo_mslookup_query *query,
338 struct osmo_mslookup_result *result)
339{
340 const struct mslookup_service_host *service_host;
341 uint32_t age;
342 struct osmo_ipa_name msc_name;
343
344 /* A request for a home HLR: answer exactly if this is the subscriber's home HLR, i.e. the IMSI is listed in the
345 * HLR database. */
346 if (strcmp(query->service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP) == 0)
347 return mslookup_server_rx_hlr_gsup(query, result);
348
349 /* All other service types: answer when the subscriber has done a LU that is either listed in the local HLR or
350 * in the GSUP proxy database: i.e. if the subscriber has done a Location Updating at an VLR belonging to this
351 * HLR. Respond with whichever services are configured in the osmo-hlr.cfg. */
352 if (!subscriber_has_done_lu_here(query, &age, &msc_name)) {
353 *result = not_found;
354 return;
355 }
356
357 /* We've detected a LU here. The VLR where the LU happened is stored in msc_unit_name, and the LU age is stored
358 * in 'age'. Figure out the address configured for that VLR and service name. */
359 service_host = mslookup_server_service_get(&msc_name, query->service);
360
361 if (!service_host) {
362 /* Find such service set globally (no VLR unit name) */
363 service_host = mslookup_server_service_get(&mslookup_server_msc_wildcard, query->service);
364 }
365
366 if (!service_host) {
367 LOGP(DMSLOOKUP, LOGL_ERROR,
368 "%s: subscriber found, but no service %s configured, cannot service lookup request\n",
369 osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
370 osmo_quote_str_c(OTC_SELECT, query->service, -1));
371 *result = not_found;
372 return;
373 }
374
375 set_result(result, service_host, age);
376}