diff options
author | Oliver Smith <osmith@sysmocom.de> | 2019-11-20 10:56:35 +0100 |
---|---|---|
committer | laforge <laforge@osmocom.org> | 2020-01-10 16:07:40 +0000 |
commit | bf7deda0fc30dba8cdd8f3cc9d5047f9800ca50f (patch) | |
tree | e93fac5ab953405b086ed182037298c53ccb9788 /src | |
parent | 81b92bbe69fdc548680af651a44071d948a50292 (diff) |
add libosmo-mslookup abstract client
mslookup is a key concept in Distributed GSM, which allows querying the current
location of a subscriber in a number of cooperating but independent core
network sites, by arbitrary service names and by MSISDN/IMSI.
Add the abstract mslookup client library. An actual lookup method (besides
mslookup_client_fake.c) is added in a subsequent patch.
For a detailed overview of this and upcoming patches, please see the elaborate
comment at the top of mslookup.c.
Add as separate library, libosmo-mslookup, to allow adding D-GSM capability to
arbitrary client programs.
osmo-hlr will be the only mslookup server implementation, added in a subsequent
patch.
osmo-hlr itself will also use this library and act as an mslookup client, when
requesting the home HLR for locally unknown IMSIs.
Related: OS#4237
Patch-by: osmith, nhofmeyr
Change-Id: I83487ab8aad1611eb02e997dafbcb8344da13df1
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 5 | ||||
-rw-r--r-- | src/logging.c | 7 | ||||
-rw-r--r-- | src/mslookup/Makefile.am | 23 | ||||
-rw-r--r-- | src/mslookup/mslookup.c | 321 | ||||
-rw-r--r-- | src/mslookup/mslookup_client.c | 310 | ||||
-rw-r--r-- | src/mslookup/mslookup_client_fake.c | 156 |
6 files changed, 820 insertions, 2 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index a5b71cf..f858ff0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,7 @@ -SUBDIRS = gsupclient +SUBDIRS = \ + gsupclient \ + mslookup \ + $(NULL) AM_CFLAGS = \ -Wall \ diff --git a/src/logging.c b/src/logging.c index 3713ab3..d0b79cf 100644 --- a/src/logging.c +++ b/src/logging.c @@ -25,7 +25,12 @@ const struct log_info_cat hlr_log_info_cat[] = { .color = "\033[1;34m", .enabled = 1, .loglevel = LOGL_NOTICE, }, - + [DMSLOOKUP] = { + .name = "DMSLOOKUP", + .description = "Mobile Subscriber Lookup", + .color = "\033[1;35m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, }; const struct log_info hlr_log_info = { diff --git a/src/mslookup/Makefile.am b/src/mslookup/Makefile.am new file mode 100644 index 0000000..01be401 --- /dev/null +++ b/src/mslookup/Makefile.am @@ -0,0 +1,23 @@ +# This is _NOT_ the library release version, it's an API version. +# Please read chapter "Library interface versions" of the libtool documentation +# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html +LIBVERSION=0:0:0 + +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include +AM_CFLAGS = -fPIC -Wall $(PCSC_CFLAGS) $(TALLOC_CFLAGS) $(LIBOSMOCORE_CFLAGS) +AM_LDFLAGS = $(COVERAGE_LDFLAGS) + +lib_LTLIBRARIES = libosmo-mslookup.la + +libosmo_mslookup_la_SOURCES = \ + mslookup.c \ + mslookup_client.c \ + mslookup_client_fake.c \ + $(NULL) + +libosmo_mslookup_la_LDFLAGS = -version-info $(LIBVERSION) +libosmo_mslookup_la_LIBADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(TALLOC_LIBS) \ + $(NULL) diff --git a/src/mslookup/mslookup.c b/src/mslookup/mslookup.c new file mode 100644 index 0000000..d399e3a --- /dev/null +++ b/src/mslookup/mslookup.c @@ -0,0 +1,321 @@ +/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <errno.h> +#include <osmocom/gsm/gsm23003.h> +#include <osmocom/mslookup/mslookup.h> + +/*! \addtogroup mslookup + * + * Distributed GSM: finding subscribers + * + * There are various aspects of the D-GSM code base in osmo-hlr.git, here is an overview: + * + * mslookup is the main enabler of D-GSM, a concept for connecting services between independent core network stacks. + * + * D-GSM consists of: + * (1) mslookup client to find subscribers: + * (a) external clients like ESME, SIP PBX, ... ask osmo-hlr to tell where to send SMS, voice calls, ... + * (b) osmo-hlr's own mslookup client asks remote osmo-hlrs whether they know a given IMSI. + * (2) when a subscriber was found at a remote HLR, GSUP gets forwarded there: + * (a) to deliver messages for the GSUP proxy, osmo-hlr manages many GSUP clients to establish links to remote HLRs. + * (b) osmo-hlr has a GSUP proxy layer that caches data of IMSIs that get proxied to a remote HLR. + * (c) decision making to distinguish local IMSIs from ones proxied to a remote HLR. + * + * (1) mslookup is a method of finding subscribers using (multicast) queries, by MSISDN or by IMSI. + * It is open to various lookup methods, the first one being multicast DNS. + * An mslookup client sends a request, and an mslookup server responds. + * The mslookup server is implemented by osmo-hlr. mslookup clients are arbitrary programs, like an ESME or a SIP PBX. + * Hence the mslookup client is public API, while the mslookup server is implemented "privately" in osmo-hlr. + * + * (1a) Public mslookup client: libosmo-mslookup + * src/mslookup/mslookup.c Things useful for both client and server. + * src/mslookup/mslookup_client.c The client API, which can use various lookup methods, + * and consolidates results from various responders. + * src/mslookup/mslookup_client_mdns.c lookup method implementing multicast DNS, client side. + * + * src/mslookup/osmo-mslookup-client.c Utility program to ease invocation for (blocking) mslookup clients. + * + * src/mslookup/mslookup_client_fake.c lookup method generating fake results, for testing client implementations. + * + * src/mslookup/mdns*.c implementation of DNS to be used by mslookup_client_mdns.c, + * and the mslookup_server.c. + * + * contrib/dgsm/esme_dgsm.py Example implementation for an mslookup enabled SMS handler. + * contrib/dgsm/freeswitch_dialplan_dgsm.py Example implementation for an mslookup enabled FreeSWITCH dialplan. + * contrib/dgsm/osmo-mslookup-pipe.py Example for writing a python client using the osmo-mslookup-client + * cmdline. + * contrib/dgsm/osmo-mslookup-socket.py Example for writing a python client using the osmo-mslookup-client + * unix domain socket. + * + * (1b) "Private" mslookup server in osmo-hlr: + * src/mslookup_server.c Respond to mslookup queries, independent from the particular lookup method. + * src/mslookup_server_mdns.c mDNS specific implementation for mslookup_server.c. + * src/dgsm_vty.c Configure services that mslookup server sends to remote requests. + * + * (2) Proxy and GSUP clients to remote HLR instances: + * + * (a) Be a GSUP client to forward to a remote HLR: + * src/gsupclient/ The same API that is used by osmo-{msc,sgsn} is also used to forward GSUP to remote osmo-hlrs. + * src/remote_hlr.c Establish links to remote osmo-hlrs, where this osmo-hlr is a client (proxying e.g. for an MSC). + * + * (b) Keep track of remotely handled IMSIs: + * src/proxy.c Keep track of proxied IMSIs and cache important subscriber data. + * + * (c) Direct GSUP request to the right destination: either the local or a remote HLR: + * src/dgsm.c The glue that makes osmo-hlr distinguish between local IMSIs and those that are proxied to another + * osmo-hlr. + * src/dgsm_vty.c Config. + * + * @{ + * \file mslookup.c + */ + +const struct value_string osmo_mslookup_id_type_names[] = { + { OSMO_MSLOOKUP_ID_NONE, "none" }, + { OSMO_MSLOOKUP_ID_IMSI, "imsi" }, + { OSMO_MSLOOKUP_ID_MSISDN, "msisdn" }, + {} +}; + +const struct value_string osmo_mslookup_result_code_names[] = { + { OSMO_MSLOOKUP_RC_NONE, "none" }, + { OSMO_MSLOOKUP_RC_RESULT, "result" }, + { OSMO_MSLOOKUP_RC_NOT_FOUND, "not-found" }, + {} +}; + +/*! Compare two struct osmo_mslookup_id. + * \returns 0 if a and b are equal, + * < 0 if a (or the ID type / start of ID) is < b, + * > 0 if a (or the ID type / start of ID) is > b. + */ +int osmo_mslookup_id_cmp(const struct osmo_mslookup_id *a, const struct osmo_mslookup_id *b) +{ + int cmp; + if (a == b) + return 0; + if (!a) + return -1; + if (!b) + return 1; + + cmp = OSMO_CMP(a->type, b->type); + if (cmp) + return cmp; + + switch (a->type) { + case OSMO_MSLOOKUP_ID_IMSI: + return strncmp(a->imsi, b->imsi, sizeof(a->imsi)); + case OSMO_MSLOOKUP_ID_MSISDN: + return strncmp(a->msisdn, b->msisdn, sizeof(a->msisdn)); + default: + return 0; + } +} + +bool osmo_mslookup_id_valid(const struct osmo_mslookup_id *id) +{ + switch (id->type) { + case OSMO_MSLOOKUP_ID_IMSI: + return osmo_imsi_str_valid(id->imsi); + case OSMO_MSLOOKUP_ID_MSISDN: + return osmo_msisdn_str_valid(id->msisdn); + default: + return false; + } +} + +bool osmo_mslookup_service_valid(const char *service) +{ + return strlen(service) > 0; +} + +/*! Write ID and ID type to a buffer. + * \param[out] buf nul-terminated {id}.{id_type} string (e.g. "1234.msisdn") or +* "?.none" if the ID type is invalid. + * \returns amount of bytes written to buf. + */ +size_t osmo_mslookup_id_name_buf(char *buf, size_t buflen, const struct osmo_mslookup_id *id) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + switch (id->type) { + case OSMO_MSLOOKUP_ID_IMSI: + OSMO_STRBUF_PRINTF(sb, "%s", id->imsi); + break; + case OSMO_MSLOOKUP_ID_MSISDN: + OSMO_STRBUF_PRINTF(sb, "%s", id->msisdn); + break; + default: + OSMO_STRBUF_PRINTF(sb, "?"); + break; + } + OSMO_STRBUF_PRINTF(sb, ".%s", osmo_mslookup_id_type_name(id->type)); + return sb.chars_needed; +} + +/*! Same as osmo_mslookup_id_name_buf(), but return a talloc allocated string of sufficient size. */ +char *osmo_mslookup_id_name_c(void *ctx, const struct osmo_mslookup_id *id) +{ + OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_mslookup_id_name_buf, id) +} + +/*! Same as osmo_mslookup_id_name_buf(), but directly return the char* (for printf-like string formats). */ +char *osmo_mslookup_id_name_b(char *buf, size_t buflen, const struct osmo_mslookup_id *id) +{ + int rc = osmo_mslookup_id_name_buf(buf, buflen, id); + if (rc < 0 && buflen) + buf[0] = '\0'; + return buf; +} + +/*! Write mslookup result string to buffer. + * \param[in] query with the service, ID and ID type to be written to buf like a domain string, or NULL to omit. + * \param[in] result with the result code, IPv4/v6 and age to be written to buf or NULL to omit. + * \param[out] buf result as flat string, which looks like the following for a valid query and result with IPv4 and v6 + * answer: "sip.voice.1234.msisdn -> ipv4: 42.42.42.42:1337 -> ipv6: [1234:5678:9ABC::]:1338 (age=1)", + * the result part can also be " -> timeout" or " -> rc=5" depending on the result code. + * \returns amount of bytes written to buf. + */ +size_t osmo_mslookup_result_to_str_buf(char *buf, size_t buflen, + const struct osmo_mslookup_query *query, + const struct osmo_mslookup_result *result) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + if (query) { + OSMO_STRBUF_PRINTF(sb, "%s.", query->service); + OSMO_STRBUF_APPEND(sb, osmo_mslookup_id_name_buf, &query->id); + } + if (result && result->rc == OSMO_MSLOOKUP_RC_NONE) + result = NULL; + if (result) { + if (result->rc != OSMO_MSLOOKUP_RC_RESULT) { + OSMO_STRBUF_PRINTF(sb, " %s", osmo_mslookup_result_code_name(result->rc)); + } else { + if (result->host_v4.ip[0]) { + OSMO_STRBUF_PRINTF(sb, " -> ipv4: " OSMO_SOCKADDR_STR_FMT, + OSMO_SOCKADDR_STR_FMT_ARGS(&result->host_v4)); + } + if (result->host_v6.ip[0]) { + OSMO_STRBUF_PRINTF(sb, " -> ipv6: " OSMO_SOCKADDR_STR_FMT, + OSMO_SOCKADDR_STR_FMT_ARGS(&result->host_v6)); + } + OSMO_STRBUF_PRINTF(sb, " (age=%u)", result->age); + } + OSMO_STRBUF_PRINTF(sb, " %s", result->last ? "(last)" : "(not-last)"); + } + return sb.chars_needed; +} + +/*! Same as osmo_mslookup_result_to_str_buf(), but return a talloc allocated string of sufficient size. */ +char *osmo_mslookup_result_name_c(void *ctx, + const struct osmo_mslookup_query *query, + const struct osmo_mslookup_result *result) +{ + OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_mslookup_result_to_str_buf, query, result) +} + +/*! Same as osmo_mslookup_result_to_str_buf(), but directly return the char* (for printf-like string formats). */ +char *osmo_mslookup_result_name_b(char *buf, size_t buflen, + const struct osmo_mslookup_query *query, + const struct osmo_mslookup_result *result) +{ + int rc = osmo_mslookup_result_to_str_buf(buf, buflen, query, result); + if (rc < 0 && buflen) + buf[0] = '\0'; + return buf; +} + +/*! Copy part of a string to a buffer and nul-terminate it. + * \returns 0 on success, negative on error. + */ +static int token(char *dest, size_t dest_size, const char *start, const char *end) +{ + int len; + if (start >= end) + return -10; + len = end - start; + if (len >= dest_size) + return -11; + strncpy(dest, start, len); + dest[len] = '\0'; + return 0; +} + +/*! Parse a string like "foo.moo.goo.123456789012345.msisdn" into service="foo.moo.goo", id="123456789012345" and + * id_type="msisdn", placed in a struct osmo_mslookup_query. + * \param q Write parsed query to this osmo_mslookup_query. + * \param domain Human readable domain string like "sip.voice.12345678.msisdn". + * \returns 0 on success, negative on error. + */ +int osmo_mslookup_query_init_from_domain_str(struct osmo_mslookup_query *q, const char *domain) +{ + const char *last_dot; + const char *second_last_dot; + const char *id_type; + const char *id; + int rc; + + *q = (struct osmo_mslookup_query){}; + + if (!domain) + return -1; + + last_dot = strrchr(domain, '.'); + + if (!last_dot) + return -2; + + if (last_dot <= domain) + return -3; + + for (second_last_dot = last_dot - 1; second_last_dot > domain && *second_last_dot != '.'; second_last_dot--); + if (second_last_dot == domain || *second_last_dot != '.') + return -3; + + id_type = last_dot + 1; + if (!*id_type) + return -4; + + q->id.type = get_string_value(osmo_mslookup_id_type_names, id_type); + + id = second_last_dot + 1; + switch (q->id.type) { + case OSMO_MSLOOKUP_ID_IMSI: + rc = token(q->id.imsi, sizeof(q->id.imsi), id, last_dot); + if (rc) + return rc; + if (!osmo_imsi_str_valid(q->id.imsi)) + return -5; + break; + case OSMO_MSLOOKUP_ID_MSISDN: + rc = token(q->id.msisdn, sizeof(q->id.msisdn), id, last_dot); + if (rc) + return rc; + if (!osmo_msisdn_str_valid(q->id.msisdn)) + return -6; + break; + default: + return -7; + } + + return token(q->service, sizeof(q->service), domain, second_last_dot); +} + +/*! @} */ diff --git a/src/mslookup/mslookup_client.c b/src/mslookup/mslookup_client.c new file mode 100644 index 0000000..67977e4 --- /dev/null +++ b/src/mslookup/mslookup_client.c @@ -0,0 +1,310 @@ +/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <osmocom/hlr/logging.h> +#include <osmocom/mslookup/mslookup_client.h> + +/*! Lookup client's internal data for a query. */ +struct osmo_mslookup_client { + struct llist_head lookup_methods; + struct llist_head requests; + uint32_t next_request_handle; +}; + +/*! Lookup client's internal data for a query. + * The request methods only get to see the query part, and result handling is done commonly for all request methods. */ +struct osmo_mslookup_client_request { + struct llist_head entry; + struct osmo_mslookup_client *client; + uint32_t request_handle; + + struct osmo_mslookup_query query; + struct osmo_mslookup_query_handling handling; + struct osmo_timer_list timeout; + bool waiting_min_delay; + + struct osmo_mslookup_result result; +}; + +static struct osmo_mslookup_client_request *get_request(struct osmo_mslookup_client *client, uint32_t request_handle) +{ + struct osmo_mslookup_client_request *r; + if (!request_handle) + return NULL; + llist_for_each_entry(r, &client->requests, entry) { + if (r->request_handle == request_handle) + return r; + } + return NULL; +} + +struct osmo_mslookup_client *osmo_mslookup_client_new(void *ctx) +{ + struct osmo_mslookup_client *client = talloc_zero(ctx, struct osmo_mslookup_client); + OSMO_ASSERT(client); + INIT_LLIST_HEAD(&client->lookup_methods); + INIT_LLIST_HEAD(&client->requests); + return client; +} + +/*! Return whether any lookup methods are available. + * \param[in] client Client to query. + * \return true when a client is present that has at least one osmo_mslookup_client_method registered. + */ +bool osmo_mslookup_client_active(struct osmo_mslookup_client *client) +{ + if (!client) + return false; + if (llist_empty(&client->lookup_methods)) + return false; + return true; +} + +static void _osmo_mslookup_client_method_del(struct osmo_mslookup_client_method *method) +{ + if (method->destruct) + method->destruct(method); + llist_del(&method->entry); + talloc_free(method); +} + +/*! Stop and free mslookup client and all registered lookup methods. + */ +void osmo_mslookup_client_free(struct osmo_mslookup_client *client) +{ + struct osmo_mslookup_client_method *m, *n; + if (!client) + return; + llist_for_each_entry_safe(m, n, &client->lookup_methods, entry) { + _osmo_mslookup_client_method_del(m); + } + talloc_free(client); +} + +/*! Add an osmo_mslookup_client_method to service MS Lookup requests. + * Note, osmo_mslookup_client_method_del() will talloc_free() the method pointer, so it needs to be dynamically + * allocated. + * \param client The osmo_mslookup_client instance to add to. + * \param method A fully initialized method struct, allocated by talloc. + */ +void osmo_mslookup_client_method_add(struct osmo_mslookup_client *client, + struct osmo_mslookup_client_method *method) +{ + method->client = client; + llist_add_tail(&method->entry, &client->lookup_methods); +} + +/*! \return false if the method was not listed, true if the method was listed, removed and talloc_free()d. + */ +bool osmo_mslookup_client_method_del(struct osmo_mslookup_client *client, + struct osmo_mslookup_client_method *method) +{ + struct osmo_mslookup_client_method *m; + llist_for_each_entry(m, &client->lookup_methods, entry) { + if (m == method) { + _osmo_mslookup_client_method_del(method); + return true; + } + } + return false; +} + +static void osmo_mslookup_request_send_result(struct osmo_mslookup_client_request *r, bool finish) +{ + struct osmo_mslookup_client *client = r->client; + uint32_t request_handle = r->request_handle; + + r->result.last = finish; + r->handling.result_cb(r->client, r->request_handle, &r->query, &r->result); + + /* Make sure the request struct is discarded. + * The result_cb() may already have triggered a cleanup, so query by request_handle. */ + if (finish) + osmo_mslookup_client_request_cancel(client, request_handle); +} + +void osmo_mslookup_client_rx_result(struct osmo_mslookup_client *client, uint32_t request_handle, + const struct osmo_mslookup_result *result) +{ + struct osmo_mslookup_client_request *req = get_request(client, request_handle); + + if (!req) { + LOGP(DMSLOOKUP, LOGL_ERROR, + "Internal error: Got mslookup result for a request that does not exist (handle %u)\n", + req->request_handle); + return; + } + + /* Ignore incoming results that are not successful */ + if (result->rc != OSMO_MSLOOKUP_RC_RESULT) + return; + + /* If we already stored an earlier successful result, keep that if its age is younger. */ + if (req->result.rc == OSMO_MSLOOKUP_RC_RESULT + && result->age >= req->result.age) + return; + + req->result = *result; + + /* If age == 0, it doesn't get any better, so return the result immediately. */ + if (req->result.age == 0) { + osmo_mslookup_request_send_result(req, true); + return; + } + + if (req->waiting_min_delay) + return; + + osmo_mslookup_request_send_result(req, false); +} + +static void _osmo_mslookup_client_request_cleanup(struct osmo_mslookup_client_request *r) +{ + struct osmo_mslookup_client_method *m; + osmo_timer_del(&r->timeout); + llist_for_each_entry(m, &r->client->lookup_methods, entry) { + if (!m->request_cleanup) + continue; + m->request_cleanup(m, r->request_handle); + } + llist_del(&r->entry); + talloc_free(r); +} + +static void timeout_cb(void *data); + +static void set_timer(struct osmo_mslookup_client_request *r, unsigned long milliseconds) +{ + osmo_timer_setup(&r->timeout, timeout_cb, r); + osmo_timer_schedule(&r->timeout, milliseconds / 1000, (milliseconds % 1000) * 1000); +} + +static void timeout_cb(void *data) +{ + struct osmo_mslookup_client_request *r = data; + if (r->waiting_min_delay) { + /* The initial delay has passed. See if it stops here, or whether the overall timeout continues. */ + r->waiting_min_delay = false; + + if (r->handling.result_timeout_milliseconds <= r->handling.min_wait_milliseconds) { + /* It ends here. Return a final result. */ + if (r->result.rc != OSMO_MSLOOKUP_RC_RESULT) + r->result.rc = OSMO_MSLOOKUP_RC_NOT_FOUND; + osmo_mslookup_request_send_result(r, true); + return; + } + + /* We continue to listen for results. If one is already on record, send it now. */ + if (r->result.rc == OSMO_MSLOOKUP_RC_RESULT) + osmo_mslookup_request_send_result(r, false); + + set_timer(r, r->handling.result_timeout_milliseconds - r->handling.min_wait_milliseconds); + return; + } + /* The final timeout has passed, finish and clean up the request. */ + switch (r->result.rc) { + case OSMO_MSLOOKUP_RC_RESULT: + /* If the rc == OSMO_MSLOOKUP_RC_RESULT, this result has already been sent. + * Don't send it again, instead send an RC_NONE, last=true result. */ + r->result.rc = OSMO_MSLOOKUP_RC_NONE; + break; + default: + r->result.rc = OSMO_MSLOOKUP_RC_NOT_FOUND; + break; + } + osmo_mslookup_request_send_result(r, true); +} + +/*! Launch a subscriber lookup with the provided query. + * A request is cleared implicitly when the handling->result_cb is invoked; if the quer->priv pointer becomes invalid + * before that, a request should be canceled by calling osmo_mslookup_client_request_cancel() with the returned + * request_handle. A request handle of zero indicates error. + * \return a nonzero request_handle that allows ending the request, or 0 on invalid query data. */ +uint32_t osmo_mslookup_client_request(struct osmo_mslookup_client *client, + const struct osmo_mslookup_query *query, + const struct osmo_mslookup_query_handling *handling) +{ + struct osmo_mslookup_client_request *r; + struct osmo_mslookup_client_request *other; + struct osmo_mslookup_client_method *m; + + if (!osmo_mslookup_service_valid(query->service) + || !osmo_mslookup_id_valid(&query->id)) { + char buf[256]; + LOGP(DMSLOOKUP, LOGL_ERROR, "Invalid query: %s\n", + osmo_mslookup_result_name_b(buf, sizeof(buf), query, NULL)); + return 0; + } + + r = talloc_zero(client, struct osmo_mslookup_client_request); + OSMO_ASSERT(r); + + /* A request_handle of zero means error, so make sure we don't use a zero handle. */ + if (!client->next_request_handle) + client->next_request_handle++; + *r = (struct osmo_mslookup_client_request){ + .client = client, + .query = *query, + .handling = *handling, + .request_handle = client->next_request_handle++, + }; + + if (!r->handling.result_timeout_milliseconds) + r->handling.result_timeout_milliseconds = r->handling.min_wait_milliseconds; + if (!r->handling.result_timeout_milliseconds) + r->handling.result_timeout_milliseconds = 1000; + + /* Paranoia: make sure a request_handle exists only once, by expiring an already existing one. This is unlikely + * to happen in practice: before we get near wrapping a uint32_t range, previous requests should long have + * timed out or ended. */ + llist_for_each_entry(other, &client->requests, entry) { + if (other->request_handle != r->request_handle) + continue; + osmo_mslookup_request_send_result(other, true); + /* we're sure it exists only once. */ + break; + } + + /* Now sure that the new request_handle does not exist a second time. */ + llist_add_tail(&r->entry, &client->requests); + + if (r->handling.min_wait_milliseconds) { + r->waiting_min_delay = true; + set_timer(r, r->handling.min_wait_milliseconds); + } else { + set_timer(r, r->handling.result_timeout_milliseconds); + } + + /* Let the lookup implementations know */ + llist_for_each_entry(m, &client->lookup_methods, entry) { + m->request(m, query, r->request_handle); + } + return r->request_handle; +} + +/*! End or cancel a subscriber lookup. This *must* be invoked exactly once per osmo_mslookup_client_request() invocation, + * either after a lookup has concluded or to abort an ongoing lookup. + * \param[in] request_handle The request_handle returned by an osmo_mslookup_client_request() invocation. + */ +void osmo_mslookup_client_request_cancel(struct osmo_mslookup_client *client, uint32_t request_handle) +{ + struct osmo_mslookup_client_request *r = get_request(client, request_handle); + if (!r) + return; + _osmo_mslookup_client_request_cleanup(r); +} diff --git a/src/mslookup/mslookup_client_fake.c b/src/mslookup/mslookup_client_fake.c new file mode 100644 index 0000000..cae73f2 --- /dev/null +++ b/src/mslookup/mslookup_client_fake.c @@ -0,0 +1,156 @@ +/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <osmocom/hlr/logging.h> +#include <osmocom/mslookup/mslookup_client.h> +#include <osmocom/mslookup/mslookup_client_fake.h> + +#include <string.h> + +/* Fake mslookup method */ + +struct fake_lookup_state { + struct osmo_mslookup_client *client; + struct llist_head requests; + struct osmo_timer_list async_response_timer; + struct osmo_mslookup_fake_response *responses; + size_t responses_len; +}; + +struct fake_lookup_request { + struct llist_head entry; + uint32_t request_handle; + struct osmo_mslookup_query query; + struct timeval received_at; +}; + +/*! Args for osmo_timer_schedule: seconds and microseconds. */ +#define ASYNC_RESPONSE_PERIOD 0, (1e6 / 10) +static void fake_lookup_async_response(void *state); + +static void fake_lookup_request(struct osmo_mslookup_client_method *method, + const struct osmo_mslookup_query *query, + uint32_t request_handle) +{ + struct fake_lookup_state *state = method->priv; + char buf[256]; + LOGP(DMSLOOKUP, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_mslookup_result_name_b(buf, sizeof(buf), query, NULL)); + + /* A real implementation would send packets to some remote server. + * Here this is simulated: add to the list of requests, which fake_lookup_async_response() will reply upon + * according to the test data listing the replies that the test wants to generate. */ + + struct fake_lookup_request *r = talloc_zero(method->client, struct fake_lookup_request); + *r = (struct fake_lookup_request){ + .request_handle = request_handle, + .query = *query, + }; + osmo_gettimeofday(&r->received_at, NULL); + llist_add_tail(&r->entry, &state->requests); +} + +static void fake_lookup_request_cleanup(struct osmo_mslookup_client_method *method, + uint32_t request_handle) +{ + struct fake_lookup_state *state = method->priv; + + /* Tear down any state associated with this handle. */ + struct fake_lookup_request *r; + llist_for_each_entry(r, &state->requests, entry) { + if (r->request_handle != request_handle) + continue; + llist_del(&r->entry); + talloc_free(r); + LOGP(DMSLOOKUP, LOGL_DEBUG, "%s() ok\n", __func__); + return; + } + LOGP(DMSLOOKUP, LOGL_DEBUG, "%s() FAILED\n", __func__); +} + +static void fake_lookup_async_response(void *data) +{ + struct fake_lookup_state *state = data; + struct fake_lookup_request *req, *n; + struct timeval now; + char str[256]; + + osmo_gettimeofday(&now, NULL); + + llist_for_each_entry_safe(req, n, &state->requests, entry) { + struct osmo_mslookup_fake_response *resp; + + for (resp = state->responses; + (resp - state->responses) < state->responses_len; + resp++) { + struct timeval diff; + + if (resp->sent) + continue; + if (osmo_mslookup_id_cmp(&req->query.id, &resp->for_id) != 0) + continue; + if (strcmp(req->query.service, resp->for_service) != 0) + continue; + + timersub(&now, &req->received_at, &diff); + if (timercmp(&diff, &resp->time_to_reply, <)) + continue; + + /* It's time to reply to this request. */ + LOGP(DMSLOOKUP, LOGL_DEBUG, "osmo_mslookup_client_rx_result(): %s\n", + osmo_mslookup_result_name_b(str, sizeof(str), &req->query, &resp->result)); + osmo_mslookup_client_rx_result(state->client, req->request_handle, &resp->result); + resp->sent = true; + + /* The req will have been cleaned up now, so we must not iterate over state->responses anymore + * with this req. */ + break; + } + } + + osmo_timer_schedule(&state->async_response_timer, ASYNC_RESPONSE_PERIOD); +} + +struct osmo_mslookup_client_method *osmo_mslookup_client_add_fake(struct osmo_mslookup_client *client, + struct osmo_mslookup_fake_response *responses, + size_t responses_len) +{ + struct osmo_mslookup_client_method *method = talloc_zero(client, struct osmo_mslookup_client_method); + OSMO_ASSERT(method); + + struct fake_lookup_state *state = talloc_zero(method, struct fake_lookup_state); + OSMO_ASSERT(state); + *state = (struct fake_lookup_state){ + .client = client, + .responses = responses, + .responses_len = responses_len, + }; + INIT_LLIST_HEAD(&state->requests); + + *method = (struct osmo_mslookup_client_method){ + .name = "fake", + .priv = state, + .request = fake_lookup_request, + .request_cleanup = fake_lookup_request_cleanup, + }; + + osmo_timer_setup(&state->async_response_timer, fake_lookup_async_response, state); + osmo_mslookup_client_method_add(client, method); + + osmo_timer_schedule(&state->async_response_timer, ASYNC_RESPONSE_PERIOD); + return method; +} |