/* Copyright 2019 by sysmocom s.f.m.c. GmbH * * 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 . * */ #include #include #include #include #include #include #include #include #include #include #include static const struct osmo_mslookup_result not_found = { .rc = OSMO_MSLOOKUP_RC_NOT_FOUND, }; const struct osmo_ipa_name mslookup_server_msc_wildcard = {}; static void set_result(struct osmo_mslookup_result *result, const struct mslookup_service_host *service_host, uint32_t age) { if (!osmo_sockaddr_str_is_nonzero(&service_host->host_v4) && !osmo_sockaddr_str_is_nonzero(&service_host->host_v6)) { *result = not_found; return; } result->rc = OSMO_MSLOOKUP_RC_RESULT; result->host_v4 = service_host->host_v4; result->host_v6 = service_host->host_v6; result->age = age; } const struct mslookup_service_host *mslookup_server_get_local_gsup_addr(void) { static struct mslookup_service_host gsup_bind = {}; struct mslookup_service_host *host; /* Find a HLR/GSUP service set for the server (no VLR unit name) */ host = mslookup_server_service_get(&mslookup_server_msc_wildcard, OSMO_MSLOOKUP_SERVICE_HLR_GSUP); if (host) return host; /* Try to use the locally configured GSUP bind address */ osmo_sockaddr_str_from_str(&gsup_bind.host_v4, g_hlr->gsup_bind_addr, OSMO_GSUP_PORT); if (gsup_bind.host_v4.af == AF_INET6) { gsup_bind.host_v6 = gsup_bind.host_v4; gsup_bind.host_v4 = (struct osmo_sockaddr_str){}; } return &gsup_bind; } struct mslookup_server_msc_cfg *mslookup_server_msc_get(const struct osmo_ipa_name *msc_name, bool create) { struct llist_head *c = &g_hlr->mslookup.server.local_site_services; struct mslookup_server_msc_cfg *msc; if (!msc_name) return NULL; llist_for_each_entry(msc, c, entry) { if (osmo_ipa_name_cmp(&msc->name, msc_name)) continue; return msc; } if (!create) return NULL; msc = talloc_zero(g_hlr, struct mslookup_server_msc_cfg); OSMO_ASSERT(msc); INIT_LLIST_HEAD(&msc->service_hosts); msc->name = *msc_name; llist_add_tail(&msc->entry, c); return msc; } struct mslookup_service_host *mslookup_server_msc_service_get(struct mslookup_server_msc_cfg *msc, const char *service, bool create) { struct mslookup_service_host *e; if (!msc) return NULL; llist_for_each_entry(e, &msc->service_hosts, entry) { if (!strcmp(e->service, service)) return e; } if (!create) return NULL; e = talloc_zero(msc, struct mslookup_service_host); OSMO_ASSERT(e); OSMO_STRLCPY_ARRAY(e->service, service); llist_add_tail(&e->entry, &msc->service_hosts); return e; } struct mslookup_service_host *mslookup_server_service_get(const struct osmo_ipa_name *msc_name, const char *service) { struct mslookup_server_msc_cfg *msc = mslookup_server_msc_get(msc_name, false); if (!msc) return NULL; return mslookup_server_msc_service_get(msc, service, false); } int mslookup_server_msc_service_set(struct mslookup_server_msc_cfg *msc, const char *service, const struct osmo_sockaddr_str *addr) { struct mslookup_service_host *e; if (!service || !service[0] || strlen(service) > OSMO_MSLOOKUP_SERVICE_MAXLEN) return -EINVAL; if (!addr || !osmo_sockaddr_str_is_nonzero(addr)) return -EINVAL; e = mslookup_server_msc_service_get(msc, service, true); if (!e) return -EINVAL; switch (addr->af) { case AF_INET: e->host_v4 = *addr; break; case AF_INET6: e->host_v6 = *addr; break; default: return -EINVAL; } return 0; } int mslookup_server_msc_service_del(struct mslookup_server_msc_cfg *msc, const char *service, const struct osmo_sockaddr_str *addr) { struct mslookup_service_host *e, *n; int deleted = 0; if (!msc) return -ENOENT; llist_for_each_entry_safe(e, n, &msc->service_hosts, entry) { if (service && strcmp(service, e->service)) continue; if (addr) { if (!osmo_sockaddr_str_cmp(addr, &e->host_v4)) { e->host_v4 = (struct osmo_sockaddr_str){}; /* Removed one addr. If the other is still there, keep the entry. */ if (osmo_sockaddr_str_is_nonzero(&e->host_v6)) continue; } else if (!osmo_sockaddr_str_cmp(addr, &e->host_v6)) { e->host_v6 = (struct osmo_sockaddr_str){}; /* Removed one addr. If the other is still there, keep the entry. */ if (osmo_sockaddr_str_is_nonzero(&e->host_v4)) continue; } else /* No addr match, keep the entry. */ continue; /* Addr matched and none is left. Delete. */ } llist_del(&e->entry); talloc_free(e); deleted++; } return deleted; } /* A remote entity is asking us whether we are the home HLR of the given subscriber. */ static void mslookup_server_rx_hlr_gsup(const struct osmo_mslookup_query *query, struct osmo_mslookup_result *result) { const struct mslookup_service_host *host; int rc; switch (query->id.type) { case OSMO_MSLOOKUP_ID_IMSI: rc = db_subscr_exists_by_imsi(g_hlr->dbc, query->id.imsi); break; case OSMO_MSLOOKUP_ID_MSISDN: rc = db_subscr_exists_by_msisdn(g_hlr->dbc, query->id.msisdn); break; default: LOGP(DMSLOOKUP, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type); *result = not_found; return; } if (rc) { LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: does not exist in local HLR\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL)); *result = not_found; return; } LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: found in local HLR\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL)); host = mslookup_server_get_local_gsup_addr(); set_result(result, host, 0); if (result->rc != OSMO_MSLOOKUP_RC_RESULT) { LOGP(DMSLOOKUP, LOGL_ERROR, "Subscriber found, but error in service '" OSMO_MSLOOKUP_SERVICE_HLR_GSUP "' config:" " v4: " OSMO_SOCKADDR_STR_FMT " v6: " OSMO_SOCKADDR_STR_FMT "\n", OSMO_SOCKADDR_STR_FMT_ARGS(&host->host_v4), OSMO_SOCKADDR_STR_FMT_ARGS(&host->host_v6)); } } /* Look in the local HLR record: If the subscriber is "at home" in this HLR and is also currently located at a local * VLR, we will find a valid location updating with vlr_number, and no vlr_via_proxy entry. */ static bool subscriber_has_done_lu_here_hlr(const struct osmo_mslookup_query *query, uint32_t *lu_age, struct osmo_ipa_name *local_msc_name, struct hlr_subscriber *ret_subscr) { struct hlr_subscriber _subscr; int rc; uint32_t age; struct hlr_subscriber *subscr = ret_subscr ? : &_subscr; switch (query->id.type) { case OSMO_MSLOOKUP_ID_IMSI: rc = db_subscr_get_by_imsi(g_hlr->dbc, query->id.imsi, subscr); break; case OSMO_MSLOOKUP_ID_MSISDN: rc = db_subscr_get_by_msisdn(g_hlr->dbc, query->id.msisdn, subscr); break; default: LOGP(DMSLOOKUP, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type); return false; } if (rc) { LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: does not exist in local HLR\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL)); return false; } if (!subscr->vlr_number[0]) { LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: not attached (vlr_number unset)\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL)); return false; } if (subscr->vlr_via_proxy.len) { /* The VLR is behind a proxy, the subscriber is not attached to a local VLR but a remote one. That * remote proxy should instead respond to the service lookup request. */ LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: last attach is not at local VLR, but at VLR '%s' via proxy %s\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL), subscr->vlr_number, osmo_ipa_name_to_str(&subscr->vlr_via_proxy)); return false; } if (!timestamp_age(&subscr->last_lu_seen, &age)) { LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: Invalid last_lu_seen timestamp for subscriber\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL)); return false; } if (age > g_hlr->mslookup.server.local_attach_max_age) { LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: last attach was here, but too long ago: %us > %us\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL), age, g_hlr->mslookup.server.local_attach_max_age); return false; } *lu_age = age; osmo_ipa_name_set_str(local_msc_name, subscr->vlr_number); LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: attached %u seconds ago at local VLR %s\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL), age, osmo_ipa_name_to_str(local_msc_name)); return true; } /* Determine whether the subscriber with the given ID has routed a Location Updating via this HLR as first hop. Return * true if it is attached at a local VLR, and we are serving as proxy for a remote home HLR. */ static bool subscriber_has_done_lu_here_proxy(const struct osmo_mslookup_query *query, uint32_t *lu_age, struct osmo_ipa_name *local_msc_name, struct proxy_subscr *ret_proxy_subscr) { struct proxy_subscr proxy_subscr; uint32_t age; int rc; /* See the local HLR record. If the subscriber is "at home" in this HLR and is also currently located here, we * will find a valid location updating and no vlr_via_proxy entry. */ switch (query->id.type) { case OSMO_MSLOOKUP_ID_IMSI: rc = proxy_subscr_get_by_imsi(&proxy_subscr, g_hlr->gs->proxy, query->id.imsi); break; case OSMO_MSLOOKUP_ID_MSISDN: rc = proxy_subscr_get_by_msisdn(&proxy_subscr, g_hlr->gs->proxy, query->id.msisdn); break; default: LOGP(DDGSM, LOGL_ERROR, "%s: unknown ID type\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL)); return false; } if (rc) { LOGP(DDGSM, LOGL_DEBUG, "%s: does not exist in GSUP proxy\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL)); return false; } /* We only need to care about CS LU, since only CS services need D-GSM routing. */ if (!timestamp_age(&proxy_subscr.cs.last_lu, &age) || age > g_hlr->mslookup.server.local_attach_max_age) { LOGP(DDGSM, LOGL_ERROR, "%s: last attach was at local VLR (proxying for remote HLR), but too long ago: %us > %us\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL), age, g_hlr->mslookup.server.local_attach_max_age); return false; } if (proxy_subscr.cs.vlr_via_proxy.len) { LOGP(DDGSM, LOGL_DEBUG, "%s: last attach is not at local VLR, but at VLR '%s' via proxy '%s'\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL), osmo_ipa_name_to_str(&proxy_subscr.cs.vlr_name), osmo_ipa_name_to_str(&proxy_subscr.cs.vlr_via_proxy)); return false; } *lu_age = age; *local_msc_name = proxy_subscr.cs.vlr_name; LOGP(DDGSM, LOGL_DEBUG, "%s: attached %u seconds ago at local VLR %s; proxying for remote HLR " OSMO_SOCKADDR_STR_FMT "\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL), age, osmo_ipa_name_to_str(local_msc_name), OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr.remote_hlr_addr)); if (ret_proxy_subscr) *ret_proxy_subscr = proxy_subscr; return true; } bool subscriber_has_done_lu_here(const struct osmo_mslookup_query *query, uint32_t *lu_age_p, struct osmo_ipa_name *local_msc_name, char *ret_imsi, size_t ret_imsi_len) { bool attached_here; uint32_t lu_age = 0; struct osmo_ipa_name msc_name = {}; bool attached_here_proxy; uint32_t proxy_lu_age = 0; struct osmo_ipa_name proxy_msc_name = {}; struct proxy_subscr proxy_subscr; struct hlr_subscriber db_subscr; /* First ask the local HLR db, but if the local proxy record indicates a more recent LU, use that instead. * For all usual cases, only one of these will reflect a LU, even if a subscriber had more than one home HLR: * - if the subscriber is known here, we will never proxy. * - if the subscriber is not known here, this local HLR db will never record a LU. * However, if a subscriber was being proxied to a remote home HLR, and if then the subscriber was also added to * the local HLR database, there might occur a situation where both reflect a LU. So, to be safe against all * situations, compare the two entries. */ attached_here = subscriber_has_done_lu_here_hlr(query, &lu_age, &msc_name, &db_subscr); attached_here_proxy = subscriber_has_done_lu_here_proxy(query, &proxy_lu_age, &proxy_msc_name, &proxy_subscr); /* If proxy has a younger lu, replace. */ if (attached_here_proxy && (!attached_here || (proxy_lu_age < lu_age))) { attached_here = true; lu_age = proxy_lu_age; msc_name = proxy_msc_name; if (ret_imsi) osmo_strlcpy(ret_imsi, proxy_subscr.imsi, ret_imsi_len); } else if (attached_here) { if (ret_imsi) osmo_strlcpy(ret_imsi, db_subscr.imsi, ret_imsi_len); } if (attached_here && !msc_name.len) { LOGP(DMSLOOKUP, LOGL_ERROR, "%s: attached here, but no VLR name known\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL)); return false; } if (!attached_here) { /* Already logged "not attached" for both local-db and proxy attach */ return false; } LOGP(DMSLOOKUP, LOGL_INFO, "%s: attached here, at VLR %s\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL), osmo_ipa_name_to_str(&msc_name)); *lu_age_p = lu_age; *local_msc_name = msc_name; return true; } /* A remote entity is asking us whether we are providing the given service for the given subscriber. */ void mslookup_server_rx(const struct osmo_mslookup_query *query, struct osmo_mslookup_result *result) { const struct mslookup_service_host *service_host; uint32_t age; struct osmo_ipa_name msc_name; /* A request for a home HLR: answer exactly if this is the subscriber's home HLR, i.e. the IMSI is listed in the * HLR database. */ if (strcmp(query->service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP) == 0) return mslookup_server_rx_hlr_gsup(query, result); /* All other service types: answer when the subscriber has done a LU that is either listed in the local HLR or * in the GSUP proxy database: i.e. if the subscriber has done a Location Updating at an VLR belonging to this * HLR. Respond with whichever services are configured in the osmo-hlr.cfg. */ if (!subscriber_has_done_lu_here(query, &age, &msc_name, NULL, 0)) { *result = not_found; return; } /* We've detected a LU here. The VLR where the LU happened is stored in msc_unit_name, and the LU age is stored * in 'age'. Figure out the address configured for that VLR and service name. */ service_host = mslookup_server_service_get(&msc_name, query->service); if (!service_host) { /* Find such service set globally (no VLR unit name) */ service_host = mslookup_server_service_get(&mslookup_server_msc_wildcard, query->service); } if (!service_host) { LOGP(DMSLOOKUP, LOGL_ERROR, "%s: subscriber found, but no service %s configured, cannot service lookup request\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL), osmo_quote_str_c(OTC_SELECT, query->service, -1)); *result = not_found; return; } set_result(result, service_host, age); }