From bf7deda0fc30dba8cdd8f3cc9d5047f9800ca50f Mon Sep 17 00:00:00 2001 From: Oliver Smith Date: Wed, 20 Nov 2019 10:56:35 +0100 Subject: [PATCH] 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 --- .gitignore | 2 + configure.ac | 3 + debian/control | 22 ++ debian/libosmo-mslookup-dev.install | 5 + debian/libosmo-mslookup0.install | 1 + include/Makefile.am | 7 +- include/osmocom/hlr/logging.h | 1 + include/osmocom/mslookup/mslookup.h | 121 +++++++ include/osmocom/mslookup/mslookup_client.h | 132 +++++++ .../osmocom/mslookup/mslookup_client_fake.h | 34 ++ libosmo-mslookup.pc.in | 11 + src/Makefile.am | 5 +- src/logging.c | 7 +- src/mslookup/Makefile.am | 23 ++ src/mslookup/mslookup.c | 321 ++++++++++++++++++ src/mslookup/mslookup_client.c | 310 +++++++++++++++++ src/mslookup/mslookup_client_fake.c | 156 +++++++++ tests/Makefile.am | 1 + tests/mslookup/Makefile.am | 49 +++ tests/mslookup/mslookup_client_test.c | 245 +++++++++++++ tests/mslookup/mslookup_client_test.err | 47 +++ tests/mslookup/mslookup_test.c | 88 +++++ tests/mslookup/mslookup_test.err | 22 ++ tests/testsuite.at | 12 + 24 files changed, 1622 insertions(+), 3 deletions(-) create mode 100644 debian/libosmo-mslookup-dev.install create mode 100644 debian/libosmo-mslookup0.install create mode 100644 include/osmocom/mslookup/mslookup.h create mode 100644 include/osmocom/mslookup/mslookup_client.h create mode 100644 include/osmocom/mslookup/mslookup_client_fake.h create mode 100644 libosmo-mslookup.pc.in create mode 100644 src/mslookup/Makefile.am create mode 100644 src/mslookup/mslookup.c create mode 100644 src/mslookup/mslookup_client.c create mode 100644 src/mslookup/mslookup_client_fake.c create mode 100644 tests/mslookup/Makefile.am create mode 100644 tests/mslookup/mslookup_client_test.c create mode 100644 tests/mslookup/mslookup_client_test.err create mode 100644 tests/mslookup/mslookup_test.c create mode 100644 tests/mslookup/mslookup_test.err diff --git a/.gitignore b/.gitignore index 8d4b4502..acfea843 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,8 @@ tests/gsup/gsup_test tests/db/db_test tests/hlr_vty_test.db* tests/db_upgrade/*.dump +tests/mslookup/mslookup_client_test +tests/mslookup/mslookup_test # manuals doc/manuals/*.html diff --git a/configure.ac b/configure.ac index 334a7e88..217df9fb 100644 --- a/configure.ac +++ b/configure.ac @@ -174,10 +174,12 @@ AC_OUTPUT( doc/examples/Makefile src/Makefile src/gsupclient/Makefile + src/mslookup/Makefile include/Makefile include/osmocom/Makefile include/osmocom/hlr/Makefile libosmo-gsup-client.pc + libosmo-mslookup.pc sql/Makefile doc/manuals/Makefile contrib/Makefile @@ -188,4 +190,5 @@ AC_OUTPUT( tests/gsup_server/Makefile tests/db/Makefile tests/db_upgrade/Makefile + tests/mslookup/Makefile ) diff --git a/debian/control b/debian/control index a32c68d7..c1eb464e 100644 --- a/debian/control +++ b/debian/control @@ -59,6 +59,28 @@ Description: Development headers of Osmocom GSUP client library . This package contains the development headers. +Package: libosmo-mslookup0 +Section: libs +Architecture: any +Multi-Arch: same +Depends: ${shlibs:Depends}, + ${misc:Depends} +Pre-Depends: ${misc:Pre-Depends} +Description: Osmocom MS lookup library + This shared library contains routines for looking up mobile subscribers. + +Package: libosmo-mslookup-dev +Architecture: any +Multi-Arch: same +Depends: ${misc:Depends}, + libosmo-mslookup0 (= ${binary:Version}), + libosmocore-dev +Pre-Depends: ${misc:Pre-Depends} +Description: Development headers of Osmocom MS lookup library + This shared library contains routines for looking up mobile subscribers. + . + This package contains the development headers. + Package: osmo-hlr-doc Architecture: all Section: doc diff --git a/debian/libosmo-mslookup-dev.install b/debian/libosmo-mslookup-dev.install new file mode 100644 index 00000000..539bba8c --- /dev/null +++ b/debian/libosmo-mslookup-dev.install @@ -0,0 +1,5 @@ +usr/include/osmocom/mslookup +usr/lib/*/libosmo-mslookup*.a +usr/lib/*/libosmo-mslookup*.so +usr/lib/*/libosmo-mslookup*.la +usr/lib/*/pkgconfig/libosmo-mslookup.pc diff --git a/debian/libosmo-mslookup0.install b/debian/libosmo-mslookup0.install new file mode 100644 index 00000000..9cad0e86 --- /dev/null +++ b/debian/libosmo-mslookup0.install @@ -0,0 +1 @@ +usr/lib/*/libosmo-mslookup*.so.* diff --git a/include/Makefile.am b/include/Makefile.am index d8eb1ec7..e9a71260 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -1,3 +1,8 @@ SUBDIRS = osmocom -nobase_include_HEADERS = osmocom/gsupclient/gsup_client.h +nobase_include_HEADERS = \ + osmocom/gsupclient/gsup_client.h \ + osmocom/mslookup/mslookup_client_fake.h \ + osmocom/mslookup/mslookup_client.h \ + osmocom/mslookup/mslookup.h \ + $(NULL) diff --git a/include/osmocom/hlr/logging.h b/include/osmocom/hlr/logging.h index ed240752..83f1acd5 100644 --- a/include/osmocom/hlr/logging.h +++ b/include/osmocom/hlr/logging.h @@ -8,6 +8,7 @@ enum { DGSUP, DAUC, DSS, + DMSLOOKUP, }; extern const struct log_info hlr_log_info; diff --git a/include/osmocom/mslookup/mslookup.h b/include/osmocom/mslookup/mslookup.h new file mode 100644 index 00000000..e90af338 --- /dev/null +++ b/include/osmocom/mslookup/mslookup.h @@ -0,0 +1,121 @@ +/* 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 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 . + */ + +/*! \defgroup mslookup Distributed GSM: finding subscribers + * @{ + * \file mslookup.h + */ + +#pragma once + +#include +#include +#include + +#define OSMO_MSLOOKUP_SERVICE_MAXLEN 64 + +bool osmo_mslookup_service_valid(const char *service); + +enum osmo_mslookup_id_type { + OSMO_MSLOOKUP_ID_NONE = 0, + OSMO_MSLOOKUP_ID_IMSI, + OSMO_MSLOOKUP_ID_MSISDN, +}; + +extern const struct value_string osmo_mslookup_id_type_names[]; +static inline const char *osmo_mslookup_id_type_name(enum osmo_mslookup_id_type val) +{ return get_value_string(osmo_mslookup_id_type_names, val); } + +struct osmo_mslookup_id { + enum osmo_mslookup_id_type type; + union { + char imsi[GSM23003_IMSI_MAX_DIGITS+1]; + char msisdn[GSM23003_MSISDN_MAX_DIGITS+1]; + }; +}; + +int osmo_mslookup_id_cmp(const struct osmo_mslookup_id *a, const struct osmo_mslookup_id *b); +bool osmo_mslookup_id_valid(const struct osmo_mslookup_id *id); + +enum osmo_mslookup_result_code { + OSMO_MSLOOKUP_RC_NONE = 0, + /*! An intermediate valid result. The request is still open for more results. */ + OSMO_MSLOOKUP_RC_RESULT, + /*! Returned when the final request timeout has elapsed without results. */ + OSMO_MSLOOKUP_RC_NOT_FOUND, +}; + +extern const struct value_string osmo_mslookup_result_code_names[]; +static inline const char *osmo_mslookup_result_code_name(enum osmo_mslookup_result_code val) +{ return get_value_string(osmo_mslookup_result_code_names, val); } + +/*! Information to request from a lookup. */ +struct osmo_mslookup_query { + /*! Which service to request, by freely invented names. For service name conventions (for voice, SMS, HLR,...), + * refer to the OsmoHLR user's manual http://ftp.osmocom.org/docs/latest/osmohlr-usermanual.pdf */ + char service[OSMO_MSLOOKUP_SERVICE_MAXLEN + 1]; + /*! IMSI or MSISDN to look up. */ + struct osmo_mslookup_id id; + + /*! Caller provided private data, if desired. */ + void *priv; +}; + +/*! Result data as passed back to a lookup client that invoked an osmo_mslookup_client_request. */ +struct osmo_mslookup_result { + /*! Outcome of the request. */ + enum osmo_mslookup_result_code rc; + + /*! IP address and port to reach the given service via IPv4, if any. */ + struct osmo_sockaddr_str host_v4; + + /*! IP address and port to reach the given service via IPv6, if any. */ + struct osmo_sockaddr_str host_v6; + + /*! How long ago the service last verified presence of the subscriber, in seconds, or zero if the presence is + * invariable (like the home HLR record for an IMSI). + * If a subscriber has recently moved to a different location, we get multiple replies and want to choose the + * most recent one. If this were a timestamp, firstly the time zones would need to be taken care of. + * Even if we choose UTC, a service provider with an inaccurate date/time would end up affecting the result. + * The least susceptible to configuration errors or difference in local and remote clock is a value that + * indicates the actual age of the record in seconds. The time that the lookup query took to be answered should + * be neglectable here, since we would typically wait one second (or very few seconds) for lookup replies, + * while typical Location Updating periods are in the range of 15 minutes. */ + uint32_t age; + + /*! Whether this is the last result returned for this request. */ + bool last; +}; + +int osmo_mslookup_query_init_from_domain_str(struct osmo_mslookup_query *q, const char *domain); + +size_t osmo_mslookup_id_name_buf(char *buf, size_t buflen, const struct osmo_mslookup_id *id); +char *osmo_mslookup_id_name_c(void *ctx, const struct osmo_mslookup_id *id); +char *osmo_mslookup_id_name_b(char *buf, size_t buflen, const struct osmo_mslookup_id *id); + +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); +char *osmo_mslookup_result_name_c(void *ctx, + const struct osmo_mslookup_query *query, + const struct osmo_mslookup_result *result); +char *osmo_mslookup_result_name_b(char *buf, size_t buflen, + const struct osmo_mslookup_query *query, + const struct osmo_mslookup_result *result); + +/*! @} */ diff --git a/include/osmocom/mslookup/mslookup_client.h b/include/osmocom/mslookup/mslookup_client.h new file mode 100644 index 00000000..cd0c21f1 --- /dev/null +++ b/include/osmocom/mslookup/mslookup_client.h @@ -0,0 +1,132 @@ +/* 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 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 . + */ + +#pragma once + +#include +#include +#include +#include + +struct osmo_mslookup_client; +struct osmo_mslookup_result; + +typedef void (*osmo_mslookup_cb_t)(struct osmo_mslookup_client *client, + uint32_t request_handle, + const struct osmo_mslookup_query *query, + const struct osmo_mslookup_result *result); + +/*! This handling information is passed along with a lookup request. + * It tells the osmo_mslookup_client layer how to handle responses received from various mslookup methods (at the time + * of writing only mDNS exists as a method, but the intention is to easily allow adding other methods in the future). + * This query handling info is not seen by the individual method implementations, to clarify that it is the + * osmo_mslookup_client layer that takes care of these details. */ +struct osmo_mslookup_query_handling { + /*! Wait at least this long before returning any results. + * + * If nonzero, result_cb will be called as soon as this delay has elapsed, either with the so far youngest age + * result, or with a "not found yet" result. After this delay has elapsed, receiving results will continue + * until result_timeout_milliseconds has elapsed. + * + * If zero, responses are fed to the result_cb right from the start, every time a younger aged result than + * before comes in. + * + * If a result with age == 0 is received, min_wait_milliseconds is ignored, the result is returned immediately + * and listening for responses ends. + * + * Rationale: If a subscriber has recently moved between sites, multiple results will arrive, and the youngest + * age wins. It can make sense to wait a minimum time for responses before determining the winning result. + * + * However, if no result or no valid result has arrived within a short period, the subscriber may be at a site + * that is far away or that is currently experiencing high latency. It is thus a good safety net to still + * receive results for an extended period of time. + * + * For some services, it is possible to establish links to every received result, and whichever link succeeds + * will be used (for example for SIP calls: first to pick up the call gets connected, the others are dropped + * silently). + */ + uint32_t min_wait_milliseconds; + + /*! Total time in milliseconds to listen for lookup responses. + * + * When this timeout elapses, osmo_mslookup_client_request_cancel() is called implicitly; Manually invoking + * osmo_mslookup_client_request_cancel() after result_timeout_milliseconds has elapsed is not necessary, but is + * still safe to do anyway. + * + * If zero, min_wait_milliseconds is also used as result_timeout_milliseconds; if that is also zero, a default + * timeout value is used. + * + * If result_timeout_milliseconds <= min_wait_milliseconds, then min_wait_milliseconds is used as + * result_timeout_milliseconds, i.e. the timeout triggers as soon as min_wait_milliseconds hits. + * + * osmo_mslookup_client_request_cancel() can be called any time to end the request. + */ + uint32_t result_timeout_milliseconds; + + /*! Invoked every time a result with a younger age than the previous result has arrived. + * To stop receiving results before result_timeout_milliseconds has elapsed, call + * osmo_mslookup_client_request_cancel(). + */ + osmo_mslookup_cb_t result_cb; +}; + +uint32_t osmo_mslookup_client_request(struct osmo_mslookup_client *client, + const struct osmo_mslookup_query *query, + const struct osmo_mslookup_query_handling *handling); + +void osmo_mslookup_client_request_cancel(struct osmo_mslookup_client *client, uint32_t request_handle); + +struct osmo_mslookup_client *osmo_mslookup_client_new(void *ctx); +bool osmo_mslookup_client_active(struct osmo_mslookup_client *client); +void osmo_mslookup_client_free(struct osmo_mslookup_client *client); + +/*! Describe a specific mslookup client method implementation. This struct is only useful for a lookup method + * implementation to add itself to an osmo_mslookup_client, see for example osmo_mslookup_client_add_mdns(). */ +struct osmo_mslookup_client_method { + struct llist_head entry; + + /*! Human readable name of this lookup method. */ + const char *name; + + /*! Private data for the lookup method implementation. */ + void *priv; + + /*! Backpointer to the client this method is added to. */ + struct osmo_mslookup_client *client; + + /*! Launch a lookup query. Called from osmo_mslookup_client_request(). + * The implementation returns results by calling osmo_mslookup_client_rx_result(). */ + void (*request)(struct osmo_mslookup_client_method *method, + const struct osmo_mslookup_query *query, + uint32_t request_handle); + /*! End a lookup query. Called from osmo_mslookup_client_request_cancel(). It is guaranteed to be called + * exactly once per above request() invocation. (The API user is required to invoke + * osmo_mslookup_client_request_cancel() exactly once per osmo_mslookup_client_request().) */ + void (*request_cleanup)(struct osmo_mslookup_client_method *method, + uint32_t request_handle); + + /*! The mslookup_client is removing this method, clean up all open requests, lists and allocations. */ + void (*destruct)(struct osmo_mslookup_client_method *method); +}; + +void osmo_mslookup_client_method_add(struct osmo_mslookup_client *client, + struct osmo_mslookup_client_method *method); +bool osmo_mslookup_client_method_del(struct osmo_mslookup_client *client, + struct osmo_mslookup_client_method *method); +void osmo_mslookup_client_rx_result(struct osmo_mslookup_client *client, uint32_t request_handle, + const struct osmo_mslookup_result *result); diff --git a/include/osmocom/mslookup/mslookup_client_fake.h b/include/osmocom/mslookup/mslookup_client_fake.h new file mode 100644 index 00000000..9fffc945 --- /dev/null +++ b/include/osmocom/mslookup/mslookup_client_fake.h @@ -0,0 +1,34 @@ +/* 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 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 . + */ + +#pragma once + +/*! MS lookup fake API for testing purposes. */ +#include + +struct osmo_mslookup_fake_response { + struct timeval time_to_reply; + struct osmo_mslookup_id for_id; + const char *for_service; + struct osmo_mslookup_result result; + bool sent; +}; + +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); diff --git a/libosmo-mslookup.pc.in b/libosmo-mslookup.pc.in new file mode 100644 index 00000000..25a873cb --- /dev/null +++ b/libosmo-mslookup.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Osmocom MS Lookup Library +Description: C Utility Library +Version: @VERSION@ +Libs: -L${libdir} @TALLOC_LIBS@ -losmogsm -losmo-mslookup -losmocore +Cflags: -I${includedir}/ + diff --git a/src/Makefile.am b/src/Makefile.am index a5b71cf2..f858ff03 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 3713ab35..d0b79cf4 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 00000000..01be401c --- /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 00000000..d399e3a8 --- /dev/null +++ b/src/mslookup/mslookup.c @@ -0,0 +1,321 @@ +/* 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 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 . + */ + +#include +#include +#include +#include + +/*! \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 00000000..67977e49 --- /dev/null +++ b/src/mslookup/mslookup_client.c @@ -0,0 +1,310 @@ +/* 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 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 . + */ + +#include +#include + +/*! 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 00000000..cae73f29 --- /dev/null +++ b/src/mslookup/mslookup_client_fake.c @@ -0,0 +1,156 @@ +/* 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 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 . + */ + +#include +#include +#include + +#include + +/* 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; +} diff --git a/tests/Makefile.am b/tests/Makefile.am index f8591a57..776f8a93 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -3,6 +3,7 @@ SUBDIRS = \ gsup_server \ db \ db_upgrade \ + mslookup \ $(NULL) # The `:;' works around a Bash 3.2 bug when the output is not writeable. diff --git a/tests/mslookup/Makefile.am b/tests/mslookup/Makefile.am new file mode 100644 index 00000000..71602a32 --- /dev/null +++ b/tests/mslookup/Makefile.am @@ -0,0 +1,49 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + -I$(top_srcdir)/include \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + -no-install \ + $(NULL) + +EXTRA_DIST = \ + mslookup_client_test.err \ + mslookup_test.err \ + $(NULL) + +check_PROGRAMS = \ + mslookup_client_test \ + mslookup_test \ + $(NULL) + +mslookup_test_SOURCES = \ + mslookup_test.c \ + $(NULL) +mslookup_test_LDADD = \ + $(top_builddir)/src/mslookup/libosmo-mslookup.la \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) + +mslookup_client_test_SOURCES = \ + mslookup_client_test.c \ + $(NULL) +mslookup_client_test_LDADD = \ + $(top_builddir)/src/mslookup/libosmo-mslookup.la \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) + +.PHONY: update_exp +update_exp: + for i in $(check_PROGRAMS); do \ + echo "Updating $$i.err"; \ + $(builddir)/$$i 2>"$(srcdir)/$$i.err"; \ + done diff --git a/tests/mslookup/mslookup_client_test.c b/tests/mslookup/mslookup_client_test.c new file mode 100644 index 00000000..40be011b --- /dev/null +++ b/tests/mslookup/mslookup_client_test.c @@ -0,0 +1,245 @@ +/* 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 + +#define SERVICE_HLR_GSUP "gsup.hlr" +#define SERVICE_SIP "sip.voice" + +void *ctx = NULL; + +static struct osmo_mslookup_fake_response fake_lookup_responses[] = { + { + .time_to_reply = { .tv_sec = 1, }, + .for_id = { + .type = OSMO_MSLOOKUP_ID_IMSI, + .imsi = "1234567", + }, + .for_service = SERVICE_HLR_GSUP, + .result = { + .rc = OSMO_MSLOOKUP_RC_RESULT, + .host_v4 = { + .af = AF_INET, + .ip = "12.34.56.7", + .port = 42, + }, + .host_v6 = { + .af = AF_INET6, + .ip = "be:ef:ed:ca:fe:fa:ce::1", + .port = 42, + }, + .age = 0, + }, + }, + { + .time_to_reply = { .tv_usec = 600 * 1000, }, + .for_id = { + .type = OSMO_MSLOOKUP_ID_MSISDN, + .msisdn = "112", + }, + .for_service = SERVICE_SIP, + .result = { + .rc = OSMO_MSLOOKUP_RC_RESULT, + .host_v4 = { + .af = AF_INET, + .ip = "66.66.66.66", + .port = 666, + }, + .host_v6 = { + .af = AF_INET, + .ip = "6666:6666:6666::6", + .port = 666, + }, + .age = 423, + }, + }, + { + .time_to_reply = { .tv_usec = 800 * 1000, }, + .for_id = { + .type = OSMO_MSLOOKUP_ID_MSISDN, + .msisdn = "112", + }, + .for_service = SERVICE_SIP, + .result = { + .rc = OSMO_MSLOOKUP_RC_RESULT, + .host_v4 = { + .af = AF_INET, + .ip = "112.112.112.112", + .port = 23, + }, + .age = 235, + }, + }, + { + .time_to_reply = { .tv_sec = 1, .tv_usec = 200 * 1000, }, + .for_id = { + .type = OSMO_MSLOOKUP_ID_MSISDN, + .msisdn = "112", + }, + .for_service = SERVICE_SIP, + .result = { + .rc = OSMO_MSLOOKUP_RC_RESULT, + .host_v4 = { + .af = AF_INET, + .ip = "99.99.99.99", + .port = 999, + }, + .host_v6 = { + .af = AF_INET, + .ip = "9999:9999:9999::9", + .port = 999, + }, + .age = 335, + }, + }, + { + .time_to_reply = { .tv_sec = 1, .tv_usec = 500 * 1000, }, + .for_id = { + .type = OSMO_MSLOOKUP_ID_MSISDN, + .msisdn = "112", + }, + .for_service = SERVICE_SIP, + .result = { + .rc = OSMO_MSLOOKUP_RC_RESULT, + .host_v4 = { + .af = AF_INET, + .ip = "99.99.99.99", + .port = 999, + }, + .age = 999, + }, + }, +}; + +const struct timeval fake_time_start_time = { 0, 0 }; + +#define fake_time_passes(secs, usecs) do \ +{ \ + struct timeval diff; \ + osmo_gettimeofday_override_add(secs, usecs); \ + osmo_clock_override_add(CLOCK_MONOTONIC, secs, usecs * 1000); \ + timersub(&osmo_gettimeofday_override_time, &fake_time_start_time, &diff); \ + LOGP(DMSLOOKUP, LOGL_DEBUG, "Total time passed: %d.%06d s\n", \ + (int)diff.tv_sec, (int)diff.tv_usec); \ + osmo_timers_prepare(); \ + osmo_timers_update(); \ +} while (0) + +static void fake_time_start() +{ + struct timespec *clock_override; + + osmo_gettimeofday_override_time = fake_time_start_time; + osmo_gettimeofday_override = true; + clock_override = osmo_clock_override_gettimespec(CLOCK_MONOTONIC); + OSMO_ASSERT(clock_override); + clock_override->tv_sec = fake_time_start_time.tv_sec; + clock_override->tv_nsec = fake_time_start_time.tv_usec * 1000; + osmo_clock_override_enable(CLOCK_MONOTONIC, true); + fake_time_passes(0, 0); +} + +static void result_cb_once(struct osmo_mslookup_client *client, + uint32_t request_handle, + const struct osmo_mslookup_query *query, + const struct osmo_mslookup_result *result) +{ + LOGP(DMSLOOKUP, LOGL_DEBUG, "result_cb(): %s\n", osmo_mslookup_result_name_c(ctx, query, result)); +} + +int main() +{ + ctx = talloc_named_const(NULL, 0, "main"); + osmo_init_logging2(ctx, NULL); + + log_set_print_filename(osmo_stderr_target, 0); + log_set_print_level(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 0); + log_set_print_category_hex(osmo_stderr_target, 0); + log_set_use_color(osmo_stderr_target, 0); + log_set_category_filter(osmo_stderr_target, DMSLOOKUP, true, LOGL_DEBUG); + + fake_time_start(); + + struct osmo_mslookup_client *client = osmo_mslookup_client_new(ctx); + osmo_mslookup_client_add_fake(client, fake_lookup_responses, ARRAY_SIZE(fake_lookup_responses)); + + /* Place some requests to be replied upon asynchronously */ + + struct osmo_mslookup_query_handling handling = { + .result_timeout_milliseconds = 1, /* set some timeout < min_wait_milliseconds */ + .min_wait_milliseconds = 2000, + .result_cb = result_cb_once, + }; + + struct osmo_mslookup_query q1 = { + .service = SERVICE_HLR_GSUP, + .id = { + .type = OSMO_MSLOOKUP_ID_IMSI, + .imsi = "1234567", + }, + }; + OSMO_ASSERT(osmo_mslookup_client_request(client, &q1, &handling)); + + struct osmo_mslookup_query q2 = { + .service = SERVICE_SIP, + .id = { + .type = OSMO_MSLOOKUP_ID_MSISDN, + .msisdn = "112", + }, + }; + handling.min_wait_milliseconds = 3000; + OSMO_ASSERT(osmo_mslookup_client_request(client, &q2, &handling)); + + struct osmo_mslookup_query q3 = { + .service = "smpp.sms", + .id = { + .type = OSMO_MSLOOKUP_ID_MSISDN, + .msisdn = "00000", + }, + }; + handling.min_wait_milliseconds = 5000; + OSMO_ASSERT(osmo_mslookup_client_request(client, &q3, &handling)); + + struct osmo_mslookup_query q4 = { + .service = SERVICE_HLR_GSUP, + .id = { + .type = OSMO_MSLOOKUP_ID_MSISDN, + .msisdn = "666", + }, + }; + handling.min_wait_milliseconds = 10000; + uint32_t q4_handle; + OSMO_ASSERT((q4_handle = osmo_mslookup_client_request(client, &q4, &handling))); + + while (osmo_gettimeofday_override_time.tv_sec < 6) { + log_reset_context(); + fake_time_passes(0, 1e6 / 5); + } + + osmo_mslookup_client_request_cancel(client, q4_handle); + + return 0; +} diff --git a/tests/mslookup/mslookup_client_test.err b/tests/mslookup/mslookup_client_test.err new file mode 100644 index 00000000..c5528374 --- /dev/null +++ b/tests/mslookup/mslookup_client_test.err @@ -0,0 +1,47 @@ +Total time passed: 0.000000 s +fake_lookup_request(gsup.hlr.1234567.imsi) +fake_lookup_request(sip.voice.112.msisdn) +fake_lookup_request(smpp.sms.00000.msisdn) +fake_lookup_request(gsup.hlr.666.msisdn) +Total time passed: 0.200000 s +Total time passed: 0.400000 s +Total time passed: 0.600000 s +osmo_mslookup_client_rx_result(): sip.voice.112.msisdn -> ipv4: 66.66.66.66:666 -> ipv6: 6666:6666:6666::6:666 (age=423) (not-last) +Total time passed: 0.800000 s +osmo_mslookup_client_rx_result(): sip.voice.112.msisdn -> ipv4: 112.112.112.112:23 (age=235) (not-last) +Total time passed: 1.000000 s +osmo_mslookup_client_rx_result(): gsup.hlr.1234567.imsi -> ipv4: 12.34.56.7:42 -> ipv6: [be:ef:ed:ca:fe:fa:ce::1]:42 (age=0) (not-last) +result_cb(): gsup.hlr.1234567.imsi -> ipv4: 12.34.56.7:42 -> ipv6: [be:ef:ed:ca:fe:fa:ce::1]:42 (age=0) (last) +fake_lookup_request_cleanup() ok +Total time passed: 1.200000 s +osmo_mslookup_client_rx_result(): sip.voice.112.msisdn -> ipv4: 99.99.99.99:999 -> ipv6: 9999:9999:9999::9:999 (age=335) (not-last) +Total time passed: 1.400000 s +Total time passed: 1.600000 s +osmo_mslookup_client_rx_result(): sip.voice.112.msisdn -> ipv4: 99.99.99.99:999 (age=999) (not-last) +Total time passed: 1.800000 s +Total time passed: 2.000000 s +Total time passed: 2.200000 s +Total time passed: 2.400000 s +Total time passed: 2.600000 s +Total time passed: 2.800000 s +Total time passed: 3.000000 s +result_cb(): sip.voice.112.msisdn -> ipv4: 112.112.112.112:23 (age=235) (last) +fake_lookup_request_cleanup() ok +Total time passed: 3.200000 s +Total time passed: 3.400000 s +Total time passed: 3.600000 s +Total time passed: 3.800000 s +Total time passed: 4.000000 s +Total time passed: 4.200000 s +Total time passed: 4.400000 s +Total time passed: 4.600000 s +Total time passed: 4.800000 s +Total time passed: 5.000000 s +result_cb(): smpp.sms.00000.msisdn not-found (last) +fake_lookup_request_cleanup() ok +Total time passed: 5.200000 s +Total time passed: 5.400000 s +Total time passed: 5.600000 s +Total time passed: 5.800000 s +Total time passed: 6.000000 s +fake_lookup_request_cleanup() ok diff --git a/tests/mslookup/mslookup_test.c b/tests/mslookup/mslookup_test.c new file mode 100644 index 00000000..1672bd0a --- /dev/null +++ b/tests/mslookup/mslookup_test.c @@ -0,0 +1,88 @@ +/* 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 + +void *ctx; + +const char *domains[] = { + "gsup.hlr.123456789012345.imsi", + "gsup.hlr.1.imsi", + "sip.voice.1.msisdn", + "a.b.c.imsi", + "", + ".", + "...", + ".....", + ".....1.msisdn", + "fofdndsf. d.ads ofdsf. ads.kj.1243455132.msisdn", + "foo.12345678901234567890.imsi", + "gsup.hlr.123456789012345.what", + NULL, + "blarg", + "blarg.", + "blarg.1.", + "blarg.1.msisdn", + "blarg.1.msisdn.", + ".1.msisdn", + "1.msisdn", + "qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmm.1.msisdn", + "qwerty.1.qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmm", +}; + +void test_osmo_mslookup_query_init_from_domain_str() +{ + int i; + for (i = 0; i < ARRAY_SIZE(domains); i++) { + const char *d = domains[i]; + struct osmo_mslookup_query q; + + int rc = osmo_mslookup_query_init_from_domain_str(&q, d); + if (rc) + fprintf(stderr, "%s -> rc = %d\n", osmo_quote_str(d, -1), rc); + else + fprintf(stderr, "%s -> %s %s %s\n", osmo_quote_str(d, -1), + osmo_quote_str_c(ctx, q.service, -1), + osmo_quote_str_c(ctx, q.id.imsi, -1), + osmo_mslookup_id_type_name(q.id.type)); + } +} + +int main() +{ + ctx = talloc_named_const(NULL, 0, "main"); + osmo_init_logging2(ctx, NULL); + + log_set_print_filename(osmo_stderr_target, 0); + log_set_print_level(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 0); + log_set_print_category_hex(osmo_stderr_target, 0); + log_set_use_color(osmo_stderr_target, 0); + log_set_category_filter(osmo_stderr_target, DMSLOOKUP, true, LOGL_DEBUG); + + test_osmo_mslookup_query_init_from_domain_str(); + + talloc_free(ctx); + + return 0; +} diff --git a/tests/mslookup/mslookup_test.err b/tests/mslookup/mslookup_test.err new file mode 100644 index 00000000..ee5ff215 --- /dev/null +++ b/tests/mslookup/mslookup_test.err @@ -0,0 +1,22 @@ +"gsup.hlr.123456789012345.imsi" -> "gsup.hlr" "123456789012345" imsi +"gsup.hlr.1.imsi" -> rc = -5 +"sip.voice.1.msisdn" -> "sip.voice" "1" msisdn +"a.b.c.imsi" -> rc = -5 +"" -> rc = -2 +"." -> rc = -3 +"..." -> rc = -4 +"....." -> rc = -4 +".....1.msisdn" -> "...." "1" msisdn +"fofdndsf. d.ads ofdsf. ads.kj.1243455132.msisdn" -> "fofdndsf. d.ads ofdsf. ads.kj" "1243455132" msisdn +"foo.12345678901234567890.imsi" -> rc = -11 +"gsup.hlr.123456789012345.what" -> rc = -7 +NULL -> rc = -1 +"blarg" -> rc = -2 +"blarg." -> rc = -3 +"blarg.1." -> rc = -4 +"blarg.1.msisdn" -> "blarg" "1" msisdn +"blarg.1.msisdn." -> rc = -4 +".1.msisdn" -> rc = -3 +"1.msisdn" -> rc = -3 +"qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmm.1.msisdn" -> rc = -11 +"qwerty.1.qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmm" -> rc = -7 diff --git a/tests/testsuite.at b/tests/testsuite.at index 58c197d0..39df7aa7 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -39,3 +39,15 @@ cat $abs_srcdir/db_upgrade/db_upgrade_test.ok > expout cat $abs_srcdir/db_upgrade/db_upgrade_test.err > experr AT_CHECK([$abs_srcdir/db_upgrade/db_upgrade_test.sh $abs_srcdir/db_upgrade $abs_builddir/db_upgrade], [], [expout], [experr]) AT_CLEANUP + +AT_SETUP([mslookup]) +AT_KEYWORDS([mslookup]) +cat $abs_srcdir/mslookup/mslookup_test.err > experr +AT_CHECK([$abs_top_builddir/tests/mslookup/mslookup_test], [0], [ignore], [experr]) +AT_CLEANUP + +AT_SETUP([mslookup_client]) +AT_KEYWORDS([mslookup_client]) +cat $abs_srcdir/mslookup/mslookup_client_test.err > experr +AT_CHECK([$abs_top_builddir/tests/mslookup/mslookup_client_test], [0], [ignore], [experr]) +AT_CLEANUP