Compare commits

...

169 Commits

Author SHA1 Message Date
Oliver Smith
41ee4e8534 Add post-upgrade script for automatic db upgrade
Closes: OS#5138
Change-Id: I34e943e5020285f63a2162010eb42675af1520bf
2021-06-01 09:13:57 +02:00
Keith
ca8e6efca6 vty: enable show subscribers filtered by IMEI
In 89fda3024a I added a
vty command to show a summary of filtered subscribers by imsi
or msisdn. In practice there is a also need to be able to
filter on IMEI.

The idea here is not to replace every operation that could be
done directly on the sql database in the vty, but this one
is useful.

Change-Id: Ic4a11d3ebcf8909c68e9f4617e94dc822491e008
2021-05-13 18:00:43 +00:00
Harald Welte
ed2e36316b add README.md file as customary for cgit, github, gitlab, etc.
Change-Id: Ide03584aae21d5972e96e7862feb86e1265c29b3
2021-03-21 22:46:07 +01:00
Keith
649c335602 Correct configuration written from vty
osmo-hlr would not read back written configuration due to
bad indentation in the mslookup->server->service section.

Also ipa-name was missing.

Change-Id: Ied4b9504f06a51c3895054a8b9d07a67eda3aeeb
2021-02-26 01:21:41 +01:00
Pau Espin Pedrol
6240465503 Bump version: 1.2.0.78-8c90-dirty → 1.3.0
Change-Id: Iffdee17c1f70f472fb9cfe722b77967c2634ea37
2021-02-23 18:13:54 +01:00
Harald Welte
8c9087dd16 manuals: generate vty reference xml at build time
Move 'doc' subdir further down to "make sure" the osmo-hlr binary is built
before the docs

Remove hlr_vty_reference from the source tree.

In manuals//Makefile.am use the new BUILT_REFERENCE_XML feature recently added
to osmo-gsm-manuals, and add a build target to generate the XML using the new
osmo-hlr --vty-ref-xml cmdline switch.

Change-Id: I02c9862ced48ce4271b328a0963d5f09086b295c
Depends: I613d692328050a036d05b49a436ab495fc2087ba
Related: OS#5041
2021-02-23 16:48:36 +00:00
Harald Welte
2bd1a45553 main: add --vty-ref-mode, use vty_dump_xml_ref_mode()
Change-Id: I939f75d6c03145dbe185d849b95b2dd99782f35c
Depends: Ie2022a7f9e167e5ceacf15350c037dd43768ff40
Related: OS#5041
2021-02-23 16:48:36 +00:00
Pau Espin Pedrol
d6993ea4b5 tests: Replace deprecated API log_set_print_filename
log_set_print_category_hex(0) is added in some places since the old API
used to set that internally to keep backward compatiblity.

Change-Id: I3cf46b7c24452254319d5c3eacceff418211bcf7
2021-02-19 13:31:13 +01:00
Pau Espin Pedrol
f551ccf9fb .gitignore: Ignore new autofoo tmp files
Change-Id: I155e9fd644263a8b47301f6b0986b9beb5a45d0f
2021-02-04 12:46:34 +01:00
Keith
b5a5676cff Fix Coverity Warnings
Explicitly check filter_type is not NULL even though the current code
never passed a NULL filter_type unless filter was also NULL.

Remove unreachable code.

Change-Id: Ib2bd9b2d6e9e559e61a895a25235669dae05fdf6
Related: coverity CID#216865
Related: coverity CID#216867
2021-01-30 07:35:26 +01:00
Keith
89fda3024a Add vty command to show summary of all or filtered subscribers
Adds the following commands:

show subscribers all - Display summary of all entries in HLR
show subscribers (imsi|msisdn|cs|ps) ... As above but filter on search field/string
show subscribers last seen - Display only subscribers with data in
Last LU update field, and sorts by Last LU.

Change-Id: I7f0573381a6d0d13841ac6d42d50f0e8389decf4
2021-01-29 21:26:08 +00:00
Oliver Smith
dd746949d0 configure.ac: set -std=gnu11
Change-Id: I5ea5365338248e29591a40ec1e19db95f8ae6877
2021-01-28 09:28:12 +00:00
Keith
cc90bfd0f4 Correct vty inline help for show subscriber
Change-Id: I035435859b60ce6d54da307c0d6397d4bd515439
2021-01-19 07:18:50 +00:00
Oliver Smith
f4d64cb98b contrib/jenkins: don't build osmo-gsm-manuals
Related: OS#4912
Change-Id: I22246f3ff105183a7a7a0279fd6c5cde9cd19952
2021-01-13 13:15:54 +01:00
Harald Welte
bd94b41fa8 auc_test.c: Add some comments on what the test cases actually do
Change-Id: Id2b9cf12823e05aeadc6f15df396f8a46ae1639d
2021-01-04 14:03:25 +01:00
Harald Welte
6e237d3a90 support the XOR algorithm for UMTS AKA
Test USIMs as specified by 3GPP use the XOR algorithm not only for 2G
but also for 3G.  libosmocore includes the XOR-3G support since v1.3.0,
but osmo-hlr somehow never made use of it.

Change-Id: I3a452af9c18cd90d00ab4766d6bd1679456bc1a2
Closes: OS#4924
2020-12-28 22:40:09 +01:00
Vadim Yanitskiy
dac855e5c8 USSD: add special 'idle' handler to IUSE for testing
Change-Id: I0d566a60e95ce2da951f22ad47c6155c5b6a338c
2020-11-17 18:50:27 +07:00
Vadim Yanitskiy
6a6c7f87ca USSD: fix handle_ussd(): do not free() unconditionally
An internal handler may want to continue session, e.g. to request
more information from the MS.  Let's make the handlers responsible
for session state management, and check that state before calling
ss_session_free(), so a session can remain alive.

Before this patch ss->state was not set/used at all...

Change-Id: I49262e7fe26f29dedbf126087cfb8f3bb3c548dc
2020-11-17 18:47:58 +07:00
Vadim Yanitskiy
6cfef3ac26 doc/manuals: re-organize description of internal USSD handlers
Change-Id: Ieddde02f3f41e0eb05b7223026da4252c17c3712
2020-11-17 18:47:58 +07:00
Vadim Yanitskiy
66f0b5fbea doc/manuals: fix s/There/The/ in 'USSD Configuration'
Change-Id: Idbff93b6be5f546f18642ee330746e8734378b39
2020-11-17 18:47:58 +07:00
Vadim Yanitskiy
c47d5c0d77 cosmetic: fix spelling in logging message: existAnt -> existEnt
Change-Id: Id803d0c71f3762b8353289853918ea78859780b4
2020-10-29 18:05:22 +07:00
Harald Welte
dfbc2cbbc2 Use OSMO_FD_* instead of deprecated BSC_FD_*
Change-Id: I24a9a81382ea723ad20b8caa61fb5c2abcdd86d0
2020-10-18 22:39:01 +02:00
Pau Espin Pedrol
89649ea997 contrib/jenkins: Enable parallel make in make distcheck
Change-Id: I1973e7a2a3be07b2db1db1cf5f05fd29101ee06a
Related: OS#4421
2020-10-12 19:29:36 +02:00
Pau Espin Pedrol
23ac586522 Set TCP NODELAY sockopt to GSUP cli and srv connections
GSUP is message based on top of IPA, and hence TCP. We don't want to
have Nagle algorithm enabled, since we are interested in having messages
sent as quickly as possible and there's no need for lower layers to wait
for more data (because we send all the message data at once).

Related: OS#4499
Change-Id: I4653b95ef0d4e1184f81f28408e9eb5d665206ec
2020-10-12 16:39:58 +02:00
Keith
de50b20116 osmo-hlr-db-tool: Make import from osmo-nitb less "lossy"
Include the IMEI and the last seen time in an import from
an osmo-nitb database.

Change-Id: Ic47e549be3551ae43ab6a84228d47ae03e9652a6
2020-09-23 19:41:27 +02:00
Pau Espin Pedrol
ed18fa908c Support setting rt-prio and cpu-affinity mask through VTY
Change-Id: I33101aae3e2851febc335f6fbf96228eab7cf6df
Depends: libosmocore.git Change-Id If76a4bd2cc7b3c7adf5d84790a944d78be70e10a
Depends: osmo-gsm-masnuals.git Change-Id Icd75769ef630c3fa985fc5e2154d5521689cdd3c
Related: SYS#4986
2020-08-18 13:25:34 +02:00
Pau Espin Pedrol
f464fff173 doc: Update VTY reference xml file
Change-Id: Ifdefba331ae2542b1cdc5860d0f9e53ef9f703c4
2020-08-18 12:50:21 +02:00
Pau Espin Pedrol
e893eeb1b3 configure.ac: Fix trailing whitespace
Change-Id: If0569167922695bb88c8f168f89fa300dd181c72
2020-08-18 12:49:05 +02:00
Vadim Yanitskiy
b77d568196 debian/control: change maintainer to the Osmocom team / mailing list
Change-Id: I6b38640b57480c6950be491eeb3c5167be1c6aab
2020-08-13 16:09:02 +07:00
Neels Hofmeyr
80cb6c93b9 manual: describe subscriber import by SQL
It seems a bad idea to cement the internal SQL structure in the user manual,
but since we currently lack a safe and portable import mechanism (like CSV
import in osmo-hlr-db-tool), this is currently valuable info to users.

Change-Id: I3246e6d5364215a71c33b5aca876deab7b6cfd70
2020-06-29 17:12:06 +02:00
Neels Hofmeyr
565cf83a42 osmo-mslookup-client: fix segfault for respond_error() caller
respond_error() passes r == 0. On error, consider it the last response for that
request.

Change-Id: I1acb0b8aa00c098d1f1f1cc17035daa38ce29fd3
2020-06-03 18:38:56 +02:00
Vadim Yanitskiy
fa20702e67 gsup_server: fix typo: s/omso_gsup_message/osmo_gsup_message/
Change-Id: I77eac6df9836f2361d87df7ba5ab6fc14ba06b1d
2020-05-31 18:52:38 +00:00
Oliver Smith
949a53cdf0 Makefile.am: EXTRA_DIST: debian, contrib/*.spec.in
Change-Id: I401a4849ae186bddd667446ff7247976090e1db7
2020-05-22 13:40:09 +02:00
Oliver Smith
102e362943 contrib: integrate RPM spec
Remove OpenSUSE bug report link, set version to @VERSION@, make it build
with CentOS 8 etc.

Related: OS#4550
Change-Id: I38f80d0f867a2bbaa09b5a42cad5028f23a8effa
2020-05-19 17:08:28 +02:00
Oliver Smith
2f7fb2e36b contrib: import RPM spec
Copy the RPM spec file from:
https://build.opensuse.org/project/show/home:mnhauke:osmocom:nightly

Related: OS#4550
Change-Id: Icb6f4335d5157f058b39701e9fcb332264911ba3
2020-05-19 17:08:28 +02:00
Philipp Maier
377fe5a645 doc: do not use loglevel info for log category ss
The log category ss uses info as loglevel vor ss, this is to verbose,
lets use notice instead.

Change-Id: I192a5f07cb7f45adb6f3af1c511b706738bdadf4
2020-05-12 13:34:13 +02:00
Neels Hofmeyr
c7ea21357a esme_dgsm.py: add --always-fail option for debugging SMPP
Change-Id: Ibacf2676cae40712c89b57ced34085311d9a416d
2020-05-09 21:07:53 +00:00
Neels Hofmeyr
9b8e7b4e39 hlr_subscr_nam(): fix condition to fix nam=false notifications
Firstly, vlr_number[] is an array and we need to check the first char for nul
instead of the pointer.

Also, osmo_ipa_name_set_str() returns zero on success, so the condition is
reversed.

hlr_subscr_nam() was rewritten in I3a8dff3d4a1cbe10d6ab08257a0138d6b2a082d9
ad868e29ba and this likely fixes a bug introduced
there.

Related: coverity CID#210169
Change-Id: I3a0e9ed4b865c88aa4a6341a3bf1a96c10d20ed9
2020-05-04 19:24:26 +02:00
Neels Hofmeyr
010ceb8206 osmo_mslookup_server_mdns_rx(): handle read() rc == 0
Coverity says "tainted buffer", I hope it means the case of read() returning
zero.

Related: coverity CID#210170
Change-Id: Ia2d57cb8bbacc6f54dc410047da69a983aedd24d
2020-05-04 19:15:29 +02:00
Neels Hofmeyr
1bd3ec49b1 gsup_server.c: properly handle negative rc from osmo_gsup_conn_ccm_get()
Related: coverity CID#210171
Change-Id: I5b56fe33cbc75679a3fffc034a53f06cd0e4043b
2020-05-04 17:53:52 +02:00
Neels Hofmeyr
dfe6f41c81 osmo_gsup_req_new(): require from_peer != NULL
In practice, from_peer is always non-NULL, yet some conditions checked against
NULL, looking like a possible NULL deref. Require non-NULL.

Related: coverity CID#210172
Change-Id: I3cb73ec0d31f84d4b613ecb026169c944d240e4c
2020-05-04 17:37:15 +02:00
Neels Hofmeyr
3e79a38440 make osmo_cni_peer_id_cmp() NULL safe
Related: coverity CID#210172
Change-Id: I400b23ac3f0eb68d5e4c757ea02d130fbe12f80b
2020-05-04 17:33:36 +02:00
Neels Hofmeyr
a450a85956 auc3g: officially wrap IND around IND_bitlen space
To determine distinct IND pools for each connected VLR, we need to pick ever
increasing values for any new peer showing up. Each subscriber's individual
IND_bitlen is then required to modulo the least significant N of bits that fit
in its IND_bitlen to effectively round-robin in the available IND pool space.
So far we did that but issued a warning message. This is actually exactly what
we want and it doesn't need to be treated like it weren't so.

Change-Id: I716d8a8a249235c8093d7a6a78b3535d893d867e
2020-04-30 19:22:24 +02:00
Neels Hofmeyr
26b4905e7f vty: show subscriber: show lu d,h,m,s ago, not just seconds
Change-Id: I0fe34e0f065160ef959b2b7b4dd040f3f2985f43
2020-04-30 19:22:24 +02:00
Neels Hofmeyr
d9b3606234 vty: show subscriber: change format of 'last LU seen'
So far, the time string format comes from ctime_r, and we manually add "UTC" to it.

The ctime_r format is wildly chaotic IMHO, mixing weekday, day-of-month and
hour and year in very unsorted ways.

Adding "UTC" to it is non-standard.

Instead use an ISO-8601 standardized time string via strftime().

Change-Id: I6731968f05050399f4dd43b241290186e0c59e1a
2020-04-30 19:22:24 +02:00
Neels Hofmeyr
0d82a87c0d drop error log for when a subscriber does not exist
Checking for existence of a subscriber and seeing that there is none is not
inherently an error. However, osmo-hlr currently logs on all occasions:

  DAUC ERROR Cannot read subscriber from db: MSISDN='1001': No such subscriber

This spams the ERROR log level. Particularly when a D-GSM setup does subscriber
existence checks for every incoming mslookup request, that potentially creates
constant ERROR logging.

The "No such subscriber" part comes from db_sel(), which might also return an
sqlite3_errmsg(). We still want those sqlite3_errmsg()es in the ERROR log.

Hence print an ERROR log only if db_sel() returns an rc != -ENOENT.

Change-Id: I5044e9b4519b948edc4e451cef0f7830d315619b
2020-04-30 19:22:24 +02:00
Neels Hofmeyr
9489a9ce4b adoc: add D-GSM chapter to osmohlr-usermanual
Change-Id: I392b5523870c2ef3267179160028d26f3f761b77
2020-04-30 19:22:24 +02:00
Oliver Smith
edc27ef390 hlr_vty_subscr: prettier output for last LU seen
Extend the "last LU seen on ..." line with the amount of seconds that
passed since now, or "(invalid timestamp)".

Patch split from Id7fc50567211a0870ac0524f6dee94d4513781ba, because it
depends on timestamp_age which was just added in
Ife4a61d71926d08f310a1aeed9d9f1974f64178b.

Change-Id: I24f9e86c1aa0b1576290094e024562f41b988f37
2020-04-30 19:22:24 +02:00
Neels Hofmeyr
af748923bd gsup_server: send routing error back to the correct peer
If a peer attempts to add a route to an ipa-name that we already have in the
routing system, don't send the routing error to the peer that already has the
name, but to the peer that attempts to re-use it and would cause the collision.

This is fixing a situation where for example a locally attached MSC has name
'MSC-1', and a remote site is proxying GSUP here for a remote MSC that also has
the name 'MSC-1'. Send the routing error back to the proxy, not local 'MSC-1'.

Change-Id: Icafaedc11b5925149d338bdcb987ae985a7323d6
2020-04-30 19:22:24 +02:00
Neels Hofmeyr
76328bdc91 D-GSM 3/n: implement roaming by mslookup in osmo-hlr
Add mslookup client to find remote home HLRs of unknown IMSIs, and
proxy/forward GSUP for those to the right remote HLR instances.

Add remote_hlr.c to manage one GSUP client per remote HLR GSUP address.

Add proxy.c to keep state about remotely handled IMSIs (remote GSUP address,
MSISDN, and probably more in future patches).  The mslookup_server that
determines whether a given MSISDN is attached locally now also needs to look in
the proxy record: it is always the osmo-hlr immediately peering for the MSC
that should respond to mslookup service address queries like SIP and SMPP.
(Only gsup.hlr service is always answered by the home HLR.)

Add dgsm.c to set up an mdns mslookup client, ask for IMSI homes, and to decide
which GSUP is handled locally and which needs to go to a remote HLR.

Add full VTY config and VTY tests.

For a detailed overview of the D-GSM and mslookup related files, please see the
elaborate comment at the top of mslookup.c (already added in an earlier patch).

Change-Id: I2fe453553c90e6ee527ed13a13089900efd488aa
2020-04-30 19:22:24 +02:00
Neels Hofmeyr
407925dcab D-GSM 2/n: implement mDNS method of mslookup server
Implement the mslookup server's mDNS responder, to actually service remote
mslookup requests:
- VTY mslookup/server config with service names,
- the mslookup_mdns_server listening for mslookup requests,

For a detailed overview of the D-GSM and mslookup related files, please see the
elaborate comment at the top of mslookup.c (already added in an earlier patch).

Change-Id: I5cae6459090588b4dd292be90a5e8903432669d2
2020-04-30 19:22:24 +02:00
Neels Hofmeyr
ab7dc40f16 D-GSM 1/n: add mslookup server in osmo-hlr
Implement the mslookup server to service remote mslookup requests.

This patch merely adds the logic to answer incoming mslookup requests, an
actual method to receive requests (mDNS) follows in a subsequent patch.

- API to configure service names and addresses for the local site (per MSC).
- determine whether a subscriber is on a local MSC
  (checking the local proxy will be added in subsequent patch that adds proxy
  capability).
- VTY config follows in a subsequent patch.

For a detailed overview of the D-GSM and mslookup related files, please see the
elaborate comment at the top of mslookup.c (already added in an earlier patch).

Change-Id: Ife4a61d71926d08f310a1aeed9d9f1974f64178b
2020-04-30 19:22:24 +02:00
Neels Hofmeyr
86b507b6ea test_nodes.vty: remove cruft
This stuff is not testing osmo-hlr specific nodes, remove.

Change-Id: Ia11a209778b78ab02424e2abf3f9004fe97cf570
2020-04-30 19:22:24 +02:00
Neels Hofmeyr
d017d7b215 enlarge the GSUP message headroom
Make room for (more) arbitrary IPA headers, like longer IPA names as configured
by the user.

Change-Id: I7d86f2dadcae29fe1550ea2c9773394ab31a837b
2020-04-30 19:22:24 +02:00
Neels Hofmeyr
04c2375b38 db v5: prep for D-GSM: add vlr_via_proxy and sgsn_via_proxy
D-GSM will store in the HLR DB whether a locally connected MSC has attached the
subscriber (last_lu_seen[_ps]), or whether the attach happened via a GSUP proxy
from a different site.

Add columns for this separately in this patch.

Change-Id: I98c7b3870559ede84adf56e4bf111f53c7487745
2020-04-30 19:22:24 +02:00
Neels Hofmeyr
939f508f00 gsup client: add up_down_cb(), add osmo_gsup_client_create3()
For the GSUP clients in upcoming D-GSM enabled osmo-hlr, it will be necessary
to trigger an event as soon as a GSUP client connection becomes ready for
communication. Add the osmo_gsup_client->up_down_cb.

Add osmo_gsup_client_create3() to pass the up_down_cb in the arguments. Also
add a cb data argument to populate the already existing osmo_gsup_client->data
item directly from osmo_gsup_client_create3().

We need the callbacks and data pointer in the osmo_gsup_client_create()
function right before startup, because this function immediately starts up the
connection. Who knows whether callbacks might trigger right away.

Because there are so many arguments, and to prevent the need for ever new
versions of this function, pass the arguments as an extendable struct.

Change-Id: I6f181e42b678465bc9945f192559dc57d2083c6d
2020-04-30 19:21:18 +02:00
Neels Hofmeyr
c79bcdedc9 2/2: wrap ipa_name in osmo_cni_peer_id with type enum and union
To be prepared for the future in public API, wrap the new osmo_ipa_name struct
in an enum-type and union called osmo_cni_peer.

During code review it was requested to insert an ability to handle different
kinds of peer id, in order to be able to add a Global Title in the future.

Use the generic osmo_cni_peer only in the publicly visible API. For osmo-hlr
internal code, I intend to postpone implementing this into the future, when a
different peer identification actually gets introduced.

This way we don't need to implement it now in all osmo-hlr code paths (save
time now), but still make all external API users aware that this type may be
extended in the future.

Change-Id: Ide9dcdca283ab989240cfc6e53e9211862a199c5
2020-04-30 19:19:17 +02:00
Neels Hofmeyr
ad868e29ba 1/2: refactor: add and use lu_fsm, osmo_gsup_req, osmo_ipa_name
These are seemingly orthogonal changes in one patch, because they are in fact
sufficiently intertwined that we are not willing to spend the time to separate
them. They are also refactoring changes, unlikely to make sense on their own.

** lu_fsm:

Attempting to make luop.c keep state about incoming GSUP requests made me find
shortcomings in several places:
- since it predates osmo_fsm, it is a state machine that does not strictly
  enforce the order of state transitions or the right sequence of incoming
  events.
- several places OSMO_ASSERT() on data received from the network.
- modifies the subscriber state before a LU is accepted.
- dead code about canceling a subscriber in a previous VLR. That would be a
  good thing to actually do, which should also be trivial now that we record
  vlr_name and sgsn_name, but I decided to remove the dead code for now.

To both step up the LU game *and* make it easier for me to integrate
osmo_gsup_req handling, I decided to create a lu_fsm, drawing from my, by now,
ample experience of writing osmo_fsms.

** osmo_gsup_req:

Prepare for D-GSM, where osmo-hlr will do proxy routing for remote HLRs /
communicate with remote MSCs via a proxy:

a) It is important that a response that osmo-hlr generates and that is sent
back to a requesting MSC contains all IEs that are needed to route it back to
the requester. Particularly source_name must become destination_name in the
response to be able to even reach the requesting MSC. Other fields are also
necessary to match, which were so far taken care of in individual numerous code
paths.

b) For some operations, the response to a GSUP request is generated
asynchronously (like Update Location Request -> Response, or taking the
response from an EUSE, or the upcoming proxying to a remote HLR). To be able to
feed a request message's information back into the response, we must thus keep
the request data around. Since struct osmo_gsup_message references a lot of
external data, usually with pointers directly into the received msgb, it is not
so trivial to pass GSUP message data around asynchronously, on its own.

osmo_gsup_req is the combined solution for both a and b: it keeps all data for
a GSUP message by taking ownership of the incoming msgb, and it provides an
explicit API "forcing" callers to respond with osmo_gsup_req_respond(), so that
all code paths trivially are definitely responding with the correct IEs set to
match the request's routing (by using osmo_gsup_make_response() recently added
to libosmocore).

Adjust all osmo-hlr code paths to use *only* osmo_gsup_req to respond to
incoming requests received on the GSUP server (above LU code being one of
them).

In fact, the same should be done on the client side. Hence osmo_gsup_req is
implemented in a server/client agnostic way, and is placed in
libosmo-gsupclient. As soon as we see routing errors in complex GSUP setups,
using osmo_gsup_req in the related GSUP client is likely to resolve those
problems without much thinking required beyond making all code paths use it.

libosmo-gsupclient is hence added to osmo-hlr binary's own library
dependencies. It would have been added by the D-GSM proxy routing anyway, we
are just doing it a little sooner.

** cni_peer_id.c / osmo_ipa_name:

We so far handle an IPA unit name as pointer + size, or as just pointer with
implicit talloc size. To ease working with GSUP peer identification data, I
require:

- a non-allocated storage of an IPA Name. It brings the drawback of being
  size limited, but our current implementation is anyway only able to handle
  MSC and SGSN names of 31 characters (see struct hlr_subscriber).
- a single-argument handle for IPA Name,
- easy to use utility functions like osmo_ipa_name_to_str(), osmo_ipa_name_cmp(), and copying
  by simple assignment, a = b.

Hence this patch adds a osmo_ipa_name in cni_peer_id.h and cni_peer_id.c. Heavily
used in LU and osmo_gsup_req.

Depends: libosmocore Id9692880079ea0f219f52d81b1923a76fc640566
Change-Id: I3a8dff3d4a1cbe10d6ab08257a0138d6b2a082d9
2020-04-30 19:16:09 +02:00
Oliver Smith
0c27a4c2d1 Cosmetic: mention OS#4491 in location cancel code
Change-Id: Idce0816172b92699d86ba3574a4fded0b73333cf
2020-04-16 09:16:27 +02:00
Eric
08358b2752 configure.ac: fix libtool issue with clang and sanitizer
As pointed out at https://github.com/libexpat/libexpat/issues/312
libtool does not play nice with clang sanitizer builds at all.
For those builds LD shoud be set to clang too (and LDFLAGS needs the
sanitizer flags as well), because the clang compiler driver knows how
linking to the sanitizer libs works, but then at a later stage libtool
fails to actually produce the shared libraries and the build fails. This
is fixed by this patch.

Addtionally LD_LIBRARY_PATH has no effect on conftest runs during
configure time, so the rpath needs to be set to the asan library path to
ensure the configure run does not fail due to a missing asan library,
i.e.:

SANS='-fsanitize=memory -fsanitize-recover=all -shared-libsan'
export CC=clang-10
ASANPATH=$(dirname `$CC -print-file-name=libclang_rt.asan-x86_64.so`)
export LDFLAGS="-Wl,-rpath,$ASANPATH $SANS $LDFLAGS"

Change-Id: Ib98b84ac156f52ecdbb7ae49eaaea35442527b22
2020-04-11 18:11:26 +00:00
Oliver Smith
5424dcb879 mslookup_client_mdns_test: no automatic skip
Exit with error code if multicast is disabled. The test is disabled by
default already, so when explicitly enabling it, we should not
automatically skip it.

Related: OS#4385
Change-Id: I82022c23fa9c40535f922b12d917efd7e229912b
2020-02-14 11:16:47 +00:00
Oliver Smith
f0e90e6bd5 mslookup_client_mdns_test: disable by default
Only build and run the test, if --enable-mslookup-client-mdns-test is
passed to ./configure. Enable that option in jenkins.sh.

Related: OS#4385
Change-Id: Ie0cd4b0c55a1fbb00c215aeec7dcd0c15805add3
2020-02-14 11:16:47 +00:00
Vadim Yanitskiy
15ad7bef5f db: fix possible SQLite3 allocated memory leak in db_open()
From https://sqlite.org/c3ref/exec.html:

  To avoid memory leaks, the application should invoke sqlite3_free()
  on error message strings returned through the 5th parameter of
  sqlite3_exec() after the error message string is no longer needed.
  If the 5th parameter to sqlite3_exec() is not NULL and no errors
  occur, then sqlite3_exec() sets the pointer in its 5th parameter
  to NULL before returning.

Change-Id: Ic9ed9bad3165bc4a637fe963f51e923f012e19ac
Fixes: CID#208182
2020-02-09 05:32:48 +07:00
Oliver Smith
74e7072f63 mdns_rfc.c: fix possible access of uninit. mem
Fixes: CID#207548
Change-Id: Ifa269d8a88d84c01349668cdd8d1f1d24c12c6d8
2020-01-31 15:27:50 +00:00
Neels Hofmeyr
4fa9653733 gsup_client.c: fix deprecation for client create func
Change-Id: Iab9f416519b5df3bd4683592b2976f16675d9be7
2020-01-29 12:48:26 +01:00
Oliver Smith
5e5ce4aef2 mdns_sock.c: fix resource leak of sock
Fixes: CID#207542
Change-Id: I0216b17c3ff67910a39520e2f2a5a16e23575a86
2020-01-14 08:43:52 +01:00
Oliver Smith
9e533f666d osmo-mslookup-client: fix dereferencing null
Fixes: CID#207543
Change-Id: Ia9ff5b2c767853dd00f577a7bc3583f408e061b5
2020-01-14 08:40:34 +01:00
Oliver Smith
544b15d3fa osmo-mslookup-client.c: fix dereferencing null
Fixes: CID#207544
Change-Id: I51b974420975e670708f0d2318f63615d281922c
2020-01-14 08:40:19 +01:00
Oliver Smith
89afb7f78b mslookup_client_mdns.c: fix dereferencing null
Fixes: CID#207545
Change-Id: I0f7fbbdb663304bd5214cba4a6e4abe1dd165cfa
2020-01-13 15:00:37 +01:00
Oliver Smith
b1775162ea mdns_msg.c: always call va_end
Fixes: CID#207546
Change-Id: I39829e78619a6412618e1140ff9b1185bad975bd
2020-01-13 14:57:18 +01:00
Oliver Smith
f55f605931 mslookup_client.c: fix dereferencing null pointer
Fixes: CID#207547
Change-Id: If19e1e68fad76a3d65788ac208da7dde1f8745c1
2020-01-13 14:52:51 +01:00
Neels Hofmeyr
fbe4929543 fix missing braces in LOGP_GSUP_FWD
Change-Id: I8634ea8822c8ccba4081014c5540f2b6a229fc7e
2020-01-13 14:12:00 +01:00
Oliver Smith
e53a34a7e1 contrib/dgsm/ add example esme and dialplan
Add example scripts for the distributed GSM network:

esme_dgsm.py: connect to the SMPP port of OsmoMSC A and forward SMS to the SMPP
port of OsmoMSC B. The IP and port of OsmoMSC B is retrieved by the receiver's
MSISDN using osmo-mslookup-client.

contrib/dgsm/freeswitch_dialplan_dgsm.py: resolve the destination SIP servers
of calls with osmo-mslookup-client and bridge the calls accordingly.

For a detailed overview of the D-GSM and mslookup related files, please see the
elaborate comment at the top of mslookup.c (already added in an earlier patch).

Related: OS#4254
Related: OS#4255
Change-Id: I26e8dd8d9a08187fccb3e74ee91366bc24f6c608
2020-01-13 14:12:00 +01:00
Neels Hofmeyr
52ef60fe96 add osmo-mslookup-client program (#2)
Standalone program using libosmo-mslookup to easily integrate with programs
that want to connect services (SIP, SMS,...) to the current location of a
subscriber. Also useful for manual testing.

For a detailed overview of the D-GSM and mslookup related files, please see the
elaborate comment at the top of mslookup.c (already added in an earlier patch).

Resubmit of 637bbfcd92 after revert in
41fe362591.

Change-Id: Ie39d30e20461ab10ae3584863d8bfc6b76a12f37
2020-01-13 14:11:48 +01:00
Oliver Smith
3a9f267983 add mDNS lookup method to libosmo-mslookup (#2)
Add the first actually useful lookup method to the mslookup library: multicast
DNS.

The server side is added in a subsequent commit, when the mslookup server is
implemented for the osmo-hlr program.

Use custom DNS encoding instead of libc-ares (which we use in OsmoSGSN
already), because libc-ares is only a DNS client implementation and we will
need both client and server.

Resubmit of f10463c5fc after being
reverted in 110a49f69f. This new version
skips the mslookup_client_mdns test if multicast is not supported in the
build environment. I have verified that it doesn't break the build
anymore in my own OBS namespace.

Related: OS#4237, OS#4361
Patch-by: osmith, nhofmeyr
Change-Id: I3c340627181b632dd6a0d577aa2ea2a7cd035c0c
2020-01-13 14:10:50 +01:00
Oliver Smith
5436c77a96 Makefile.am: fix pkgconfig_DATA
Fix building debian packages:
	dh_install: Cannot find (any matches for) "usr/lib/*/pkgconfig/libosmo-mslookup.pc" (tried in "." and "debian/tmp")
	dh_install: libosmo-mslookup-dev missing files: usr/lib/*/pkgconfig/libosmo-mslookup.pc
	dh_install: missing files, aborting

Fixes: bf7deda0fc ("add libosmo-mslookup abstract client")
Change-Id: Ib0bce2d09b41834f7331969eaf7c57a9787f7efb
2020-01-13 14:08:27 +01:00
Harald Welte
110a49f69f Revert "add mDNS lookup method to libosmo-mslookup"
This reverts commit f10463c5fc, as it
causes all OBS osmo-hlr builds to fail in the mslookup_client_mdns test.

Change-Id: I5aec5b59f304c7f732c4a31131beedf29c966d9d
2020-01-12 16:27:42 +01:00
Harald Welte
41fe362591 Revert "add osmo-mslookup-client program"
This reverts commit 637bbfcd92, as it
is a follow-up commit to Change-Id I03a0ffa1d4dc1b24ac78a5ad0975bca90a49c728
which causes all OBS osmo-hlr builds to fail in the mslookup_client_mdns test.

Change-Id: I43084ac3b24684f17df43fefc82019e44baaa236
2020-01-12 16:26:52 +01:00
Alexander Couzens
0bb8fce2f1 hlr: remove unused internal USSD list
struct hlr.iuse_list is not used at all.

Change-Id: I7b51c195bbc107beb0a0bde72b266757fc4fd5e2
2020-01-11 23:48:38 +01:00
Neels Hofmeyr
637bbfcd92 add osmo-mslookup-client program
Standalone program using libosmo-mslookup to easily integrate with programs
that want to connect services (SIP, SMS,...) to the current location of a
subscriber. Also useful for manual testing.

For a detailed overview of the D-GSM and mslookup related files, please see the
elaborate comment at the top of mslookup.c (already added in an earlier patch).

Change-Id: Ie68a5c1db04fb4dff00dc3c774a1162f5b9fabf7
2020-01-10 16:07:40 +00:00
Oliver Smith
f10463c5fc add mDNS lookup method to libosmo-mslookup
Add the first actually useful lookup method to the mslookup library: multicast
DNS.

The server side is added in a subsequent commit, when the mslookup server is
implemented for the osmo-hlr program.

Use custom DNS encoding instead of libc-ares (which we use in OsmoSGSN
already), because libc-ares is only a DNS client implementation and we will
need both client and server.

Related: OS#4237
Patch-by: osmith, nhofmeyr
Change-Id: I03a0ffa1d4dc1b24ac78a5ad0975bca90a49c728
2020-01-10 16:07:40 +00:00
Oliver Smith
bf7deda0fc 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
2020-01-10 16:07:40 +00:00
Alexander Couzens
81b92bbe69 hlr: respect the num_auth_vectors requested
Previous the hlr always returned the maximum possible auth vectors (5)
to the client. Even when only asked for a single auth vector.

Change-Id: I20c2b648456bc7ba1fc1321a7d42852158a3523c
2020-01-10 07:51:07 +01:00
Pau Espin Pedrol
3a66698d87 Bump version: 1.1.0.26-276c-dirty → 1.2.0
Change-Id: I5778895237511c62903784e8bed6920ecf058a50
2020-01-03 12:37:35 +01:00
Oliver Smith
276c5a7719 tests/auc: change back to python3
Change the interpreter of the python script back to python3, as it was
when the script was initially added in
Idff9d757ab956179aa41ada2a223fd9f439aafbd. In the meantime, it had been
changed to python2 to make it work with build slaves that were missing
python3, but this is not necessary anymore.

This should be merged shortly after osmo-python-tests was migrated to
python3, and the jenkins build slaves were (automatically) updated to
have the new osmo-python-tests installed.

Related: OS#2819
Depends: osmo-python-tests I3ffc3519bf6c22536a49dad7a966188ddad351a7
Change-Id: Ifbb8f8f5428657a1c2d4d6d1217f8e374be43aba
2019-12-10 14:37:25 +01:00
Harald Welte
80dc9ae4be hlr: exit(2) on unsupported positional arguments on command line
Change-Id: I0c2738d380a7e79622fb3db2062c17782555e82d
2019-12-03 22:17:00 +01:00
Oliver Smith
a377c41bd4 gsup_client.h: fix license header: GPLv2+
gsup_client.c is GPLv2 too, having AGPLv3 in the header file does not
make sense.

Change-Id: I3827a7c70d60137ffc654c1ca53c2652bb3df147
2019-12-03 14:51:38 +01:00
Harald Welte
06f5af22c8 AUC: Add support for setting the AMF separation bit to '1' for EUTRAN
Despite LTE/EUTRAN using the same authentication procedure (UMTS AKA)
as 3G, there's one difference: The "operator defined" 16bit AMF field
is reduced to 15 bits, with the first bit now being used as 'separation
bit'.  That bit should be '0' for 2G/3G (as it is right now) and '1'
for authentication vectores generated for authentication over
EUTRAN/EPS.

Depends: libosmocore I93850710ab55a605bf61b95063a69682a2899bb1 (OSMO_GSUP_RAT_TYPES_IE)
Change-Id: Ic766bc40f6126bb479bd0a05b0e96bec3e240008
2019-12-01 16:09:16 +01:00
Neels Hofmeyr
07e1602d2d db v4: add column last_lu_seen_ps
Location Updating procedures from both CS and PS overwrite the same
last_lu_seen field of a subscriber. For upcoming D-GSM it will be important to
distinguish those, because only CS attaches qualify for MSISDN lookup.

Add column last_lu_seen_ps, and upon PS LU, do not overwrite last_lu_seen, so
that last_lu_seen now only reflects CS LU.

In the VTY, dump both LU dates distinctively.

Change-Id: Id7fc50567211a0870ac0524f6dee94d4513781ba
2019-11-27 03:19:06 +01:00
Neels Hofmeyr
abdfdb8a4a test_nodes.vty: tweak: add some '?' checks
I added these "by accident" when implementing D-GSM related VTY tests, now
submitting them separately.

Change-Id: I92a4245cae806270b00330403cc114017ab7af53
2019-11-25 13:58:51 +01:00
Neels Hofmeyr
7355d0ddfe remove gsup_test
The test doesn't do much: it encodes an Insert Subscriber Data request for the
sole purpose to ensure the msgb is allocated large enough. A bug like that is
easily avoided statically.

Also, the lu functions will get refactored soon, it doesn't make sense to me to
drag this test along.

Change-Id: I42e1c72bf4cce8034f968cd4392773bf2b643c1b
2019-11-25 13:58:51 +01:00
Neels Hofmeyr
6b305b4c30 Makefile convenience: add VTY_TEST var to run only one test
VTY transcript tests run all *.vty test scripts, and it is not so
trivial to figure out the test-db creation and cmdline to run only one
of them when debugging. Add VTY_TEST var, useful to pick one test on the
cmdline:

  cd tests
  make vty-test VTY_TEST=test_nodes.vty

Not all VTY tests leave files behind that match hlr_vty_test.db-*, so
make sure that make does not fail it they can't be deleted (rm -f).

Change-Id: I4ad7ddb31b2bfb668b3540cfef658417dd442375
2019-11-25 13:58:16 +01:00
Neels Hofmeyr
a7d0f87eb7 add osmo_gsup_msgb_alloc()
Throughout osmo-hlr's code, the GSUP msgb allocation is duplicated as:

  msgb_alloc_headroom(1024+16, 16, "foo");

Instead, use one common function to keep the magic numbers in one place.

Change-Id: I40e99b5bc4fd8f750da7643c03b2119ac3bfd95e
2019-11-25 13:13:19 +01:00
Neels Hofmeyr
981e126686 db upgrade: remove some code dup
Instead of a switch() for each version number with identical switch cases
except for the function name, use an array of function pointers and loop.

Also print a success message after each individual version upgrade, instead of
only one in the end (see change in db_upgrade_test.ok).

Change-Id: I1736af3d9a3f02e29db836966ac15ce49f94737b
2019-11-25 13:13:18 +01:00
Neels Hofmeyr
7143f3a0cb fix upgrade test in presence of ~/.sqliterc
db_upgrade_test.sh:

- If an ~/.sqliterc file exists, it causes output of '-- Loading resources from
  ~/.sqliterc'. Use -batch option to omit that.

- To make sure that column headers are off when required, add -noheaders in
  some places.

Change-Id: I279a39984563594a4a3914b2ce3d803ad9468fe8
2019-11-25 13:13:18 +01:00
Oliver Smith
f0968798a2 gitignore: add tests/db_upgrade/*.dump
Change-Id: I0dca7a94883bbe69151d919ae204edfff12288ab
2019-11-25 13:13:05 +01:00
Neels Hofmeyr
2f75803e5d move headers to include/osmocom/hlr
Apply the same headers structure that we keep in most Osmocom source trees:
Keep noinst_HEADERS in include/osmocom/hlr and include them using
  #include <osmocom/hlr/*.h>

The only header kept in src/ is db_bootstrap.h, because it is generated during
build time. If it was built in include/osmocom/hlr, we would need db.o to
depend on db_bootstrap.h in a different subdir, which automake can't do well.

Change-Id: Ic912fe27f545b85443c5fb713d8c3c8aac23c9ad
2019-11-20 01:25:39 +01:00
Neels Hofmeyr
7f4dd11682 db.c: code dup: add db_run_statements() for arrays of statements
The db bootstrap as well as the upgrade code all execute a number of
statements, and have massive code dup around each statement execution. Instead
have one db_run_statements() that takes an array of strings and runs all.

Change-Id: I2721dfc0a9aadcc7f5ac81a1c0fa87452098996f
2019-11-12 16:23:48 +00:00
Neels Hofmeyr
a8045daeef hlr db schema 3: hlr_number -> msc_number
The osmo-hlr DB schema indicates a hlr_number column and references it as 3GPP
TS 23.008 chapter 2.4.6. However, chapter 2.4.6 refers to the "MSC number",
while the "HLR number" is chapter 2.4.7.

Taking a closer look, 2.4.6 says "The MSC number is [...] stored in the HLR",
while 2.4.7 says "The HLR number may be stored in the VLR". As quite obvious,
the HLR does not store the HLR number. This was a typo from the start.

The osmo-hlr code base so far does not use the hlr_number column at all, so we
get away with renaming the column without any effects on the code base.
However, let's rather make this a new schema version to be safe.

Change-Id: I527e8627b24b79f3e9eec32675c7f5a3a6d25440
2019-11-12 16:23:48 +00:00
Oliver Smith
4359b885d4 tests/db_upgrade: disable for old sqlite versions
Skip the test if the installed sqlite version is older than 3.16.2
(current version of debian 9). This prevents test failures caused by
the way we dump tables in the test, which does not work with older
versions.

This patch is a middle ground between reverting the db upgrade patch,
and spending lots of time to replace the table dumping code with
something that works with old sqlite versions to fix current build
failures in OBS.

Usually version checking is done in configure.ac, however I could not
find a good way to pass the result to testsuite.at. So I decided to
use pkg-config to do the test there.

Fixes: 5b65461d68 ("add db_upgrade test")
Related: https://lists.osmocom.org/pipermail/openbsc/2019-November/013063.html
Change-Id: I348c133003a95badbd6807d1519aa669115872fb
2019-11-12 13:39:00 +01:00
Neels Hofmeyr
5b65461d68 add db_upgrade test
We have a database schema upgrade path, but so far nothing that verifies that
we don't break it. It almost seems like the user data weren't important to us!?

Add a db upgrade test:
- Create a db with an .sql dump taken from a db created with an old osmo-hlr,
  producing DB schema version 0.
- Run osmo-hlr --db-upgrade --db-check
- Verify that the upgrade exited successfully.
- Verify that restarting with the upgraded DB works.

If python tests are enabled, also:
- create a new database using the new osmo-hlr (just built).
- replay a VTY transcript to create subscribers as in the old snapshot.
- replay some sql modifications as done in the old snapshot.
- Get a list of sorted table names,
- a list of their sorted columns with all their properties,
- and dump the table contents in a column- and value-sorted way.
- Compare the resulting dumps and error if there are any diffs.
(This is how I found the difference in the imei column that was fixed in
I68a00014a3d603fcba8781470bc5285f78b538d0)

Change-Id: I0961bab0e17cfde5b030576c5bc243c2b51d9dc4
2019-10-31 21:32:58 +01:00
Neels Hofmeyr
f8ad67e7fc hlr.sql: move comment
Move a comment for ind_bitlen column to a separate line, so that it doesn't
show in PRAGMA_TABLE_INFO('subscriber').

An upcoming patch introduces db_upgrade_test, which dumps a sorted db schema.
In newer sqlite3 versions, a comment following a 'DEFAULT' keyword actually
shows up in the PRAGMA_TABLE_INFO() results (on my machine), but older versions
(on the build slaves) drop that comment. The ind_bitlen column is the only one
producing this odd side effect, because it is the last column and has no comma
between 'DEFAULT' and the comment.

The easiest way to get matching results across sqlite3 client versions is to
move the comment to above ind_bitlen instead of having it on the same line.
(Adding a comma doesn't work.)

Change-Id: Id66ad68dd3f22d533fc3a428223ea6ad0282bde0
2019-10-31 19:50:28 +01:00
Neels Hofmeyr
c3d40326ec add --db-check option
This allows starting osmo-hlr to merely open the database, do upgrades if
necessary, and quit, without opening any ports.

So that no ports are opened, move the telnet VTY startup to below the database
check.

Needed for upcoming patch that introduces a db_upgrade test, in
I0961bab0e17cfde5b030576c5bc243c2b51d9dc4.

Change-Id: I1a4b3360690acd2cd3cffdadffbb00a28d421316
2019-10-31 17:48:01 +01:00
Neels Hofmeyr
a9f8a4bdce fix upgrade to version 2: imei column default value
A subsequent commit will add a db_upgrade test, which verifies that the db
resulting from an upgrade is identical to one created from scratch in the new
version. That test currently would show a diff: an upgraded 'imei' column has
'default NULL', where a new db created in version 2 has no default value on the
imei column.

Fix the upgrade path to add an imei column without 'default NULL', so that
adding the upgrade test will result in success. The test is added in
I0961bab0e17cfde5b030576c5bc243c2b51d9dc4

Change-Id: I68a00014a3d603fcba8781470bc5285f78b538d0
2019-10-31 04:48:39 +01:00
Neels Hofmeyr
f5459de2e2 db upgrade to v2: log version 2, not 1
Change-Id: I9237b64e5748e693a5f039c5a5554d417eed3633
2019-10-31 04:48:39 +01:00
Neels Hofmeyr
7d2843df4c fix double free in osmo_gsup_client_enc_send()
Change-Id: Iee675fd498ab0867ac2411d9142358f513276182
2019-10-30 03:57:20 +01:00
Oliver Smith
2b0bf31183 tests/test_nodes.vty: check less libosmocore cmds
Use three dots to avoid checking for exact commands between help and
exit, which originate from libosmocore. This avoids test failues when
we slightly change the commands, like the change from "write file" to
"write file [PATH]" in [1] that is currently causing the vty tests to
fail.

[1] libosmocore I38edcf902a08b6bd0ebb9aa6fc1a7041421af525
Change-Id: I4a964b86195141e5a50705425206f3602f908999
2019-10-08 11:11:15 +02:00
Pau Espin Pedrol
28f0774e34 tests: Fix db_test err file to expect error code name instead of value
Previous commit changed db_test to output code names to fix issues on
some platforms (I guess due to different error code values), but somehow
this log line was not updated.

Fixes: 8b860e54be
Change-Id: I598de6f83a86d528174d3d188596314572f5d70d
2019-08-13 11:59:09 +02:00
Thorsten Alteholz
b07f33df41 fix spelling errors detected by lintian
Change-Id: I2a1a21aceabc20fadc5dd28985a94689d2b873a2
2019-08-12 08:36:22 +00:00
Ruben Undheim
8b860e54be Fix test for return codes on mipsel and alpha archs
Change-Id: Ia64f1d9f39fe2b1fb704f7b6c4d9cce93ab708cd
2019-08-12 08:33:33 +00:00
Pau Espin Pedrol
9cf0030b6a Bump version: 1.0.0.49-e493-dirty → 1.1.0
Change-Id: If53bdb2e216cb98df4d5a482cbbf24096db536ed
2019-08-07 16:14:24 +02:00
Pau Espin Pedrol
b9b224c7bd configure.ac: Require libosmocore 1.2.0
Current code uses GSM23003_MSISDN_MAX_DIGITS, which is only available in
libosmocore 1.2.0 onwards. Let's update configure.ac accordingly.

Fixes: 2e403d6c3f
Change-Id: Iad03a8cf7a36bdc824ec2acc8fb8f9c27a1c0421
2019-08-07 16:08:59 +02:00
Pau Espin Pedrol
e49391bfc4 Remove undefined param passed to logging_vty_add_cmds
Since March 15th 2017, libosmocore API logging_vty_add_cmds() had its
parameter removed (c65c5b4ea075ef6cef11fff9442ae0b15c1d6af7). However,
definition in C file doesn't contain "(void)", which means number of
parameters is undefined and thus compiler doesn't complain. Let's remove
parameters from all callers before enforcing "(void)" on it.

Related: OS#4138
Change-Id: I6d0dbbd83ce17ee798bfb6e30378ed1dbae19134
2019-08-05 15:57:13 +02:00
Vadim Yanitskiy
fbd736ef37 src/db.c: integrate SQLite3 with talloc allocator
This change introduces an optional feature that allows to make
SQLite3 use talloc for all internal allocations. This would
facilitate finding memleaks. OsmoHLR needs to be configured
with '--enable-sqlite-talloc'.

  full talloc report on 'OsmoHLR' (total 292168 bytes in 449 blocks)
    struct osmo_gsup_server        contains    162 bytes in   3 blocks (ref 0)
      ...
    struct db_context              contains 288407 bytes in 420 blocks (ref 0)
      hlr.db                       contains      7 bytes in   1 blocks (ref 0)
    SQLite3                        contains 288192 bytes in 418 blocks (ref 0)
      db.c:95                      contains     48 bytes in   1 blocks (ref 0)
      db.c:95                      contains      2 bytes in   1 blocks (ref 0)
      ...

Unfortunately, old SQLite3 versions (such as 3.8.2) run out
of memory when trying to initialize a new database:

  DDB ERROR  db.c:88 (7) statement aborts at 3: []
  DDB ERROR  db.c:420 Unable to set Write-Ahead Logging: out of memory
  DDB ERROR  db.c:88 (7) statement aborts at 3: []
  DDB ERROR  db.c:238 Unable to prepare SQL statement
             'SELECT name FROM sqlite_master WHERE type='table' AND name=?'
  ...

I've noticed a huge difference in heap usage footprint compared to
generic malloc. At the same time, the recent versions (at least
3.24.0), work just fine.

Change-Id: Icfe67ed0f063b63e6794f9516da3003d01cf20a7
2019-07-30 17:15:17 +00:00
Vadim Yanitskiy
dc30154fdf tests/db_test: close the database when test is finished
Change-Id: I96fedf9181e89e4d68815b04f494a9c2ecc0e057
2019-07-30 17:15:17 +00:00
Vadim Yanitskiy
37642177f9 build: fix mess with 'db_test_SOURCES' and 'db_test_LDADD'
Somehow both 'db_test_SOURCES' and 'db_test_LDADD' ended up in
'src/Makefile.am'. This causes automake / autoconf to complain.
Let's get rid of both useless declarations.

Furthermore, the actual 'db_test_LDADD' in 'tests/Makefile.am'
contained references to the source files from '$(top_srcdir)'.
Most likely, the original intention was to depend on the object
files in '$(top_builddir)'. Let's also fix this.

Change-Id: Ib2e436ed91d9b7551dc5b205329d468c2b0ced04
2019-07-30 17:15:17 +00:00
Oliver Smith
6401b90574 db_auc.c: verify hex key sizes read from DB
Replace commented out size check for Ki with a real check, and use it
consistently for Ki, K, OP and OPC. Add a test that sets all keys to the
wrong size and tries to read them.

Related: OS#2565
Change-Id: Ib8e8e9394fb65c6e7932ce9f8bebc321b99f7696
2019-07-25 14:52:20 +02:00
Oliver Smith
5b5cac7e94 gitignore: ignore everything generated in db_test
Change-Id: I3545be056cc7e4f72f6f86e772f9cc70a8e5c03c
2019-07-25 12:45:55 +00:00
Vadim Yanitskiy
937f583a7e hlr_ussd.c: rx_proc_ss_req(): fix NULL pointer dereference
The SS payload is mandatory for GSUP PROC_SS_{REQ,RSP} messages
with session state BEGIN or CONTINUE, and optional for the END.

Make sure that it's present for both BEGIN and CONTINUE, consider
received message as incorrect otherwise. In case of the END, call
handle_ussd() / handle_ss() only if SS payload is present.

Change-Id: Ia71cabbf396bd1388e764a1749e953ac1782e307
Fixes: CID#188841
2019-07-24 19:14:44 +07:00
Vadim Yanitskiy
4ca7f6a17e hlr_ussd.c: fix: properly pass invokeID in handle_ussd_own_msisdn()
Change-Id: I06845c2c9ebee61671477ee1c9d82010f1f37b7b
2019-07-23 20:01:35 +07:00
Oliver Smith
b64cb27003 manuals: improve subscribers create on demand
Write all VTY commands in monospace and add configuration example
blocks. Add an example VTY session for enabling CS and PS NAM.

Realted: OS#2542
Change-Id: I54f80810db3dac7a4a56ad42c5d2154b6006108c
2019-07-15 14:13:50 +02:00
Oliver Smith
3b33b01fb0 VTY: add subscriber update network-access-mode
Allow updating the NAM (Network Access Mode) of subscribers with the
VTY. This is important for the subscriber create on demand use case
where subscribers get created without access to PS and CS NAM by
default. Regenerate hlr_vty_reference.xml.

Related: OS#2542
Change-Id: I231e03219355ebe6467d62ae2e40bef9d8303e3b
2019-07-15 14:13:46 +02:00
Oliver Smith
78abea6a0e contrib/jenkins.sh: run "make maintainer-clean"
Related: OS#3047
Change-Id: I63808c5c2724b8f4c3cf40db682f74eec54f1e76
2019-07-10 13:29:54 +02:00
Daniel Willmann
9ac494f486 manuals: Update vty documentation
Related: OS#1700
Change-Id: Ia650ec9ab97dcb64e4b701328bc7e88d691d427a
2019-06-17 17:11:41 +02:00
Daniel Willmann
d62d401d07 manuals: Add script to update vty/counter documentation from docker
Related: OS#1700
Change-Id: Id57c34214396b02fafa55da223764748086290e8
2019-06-17 17:11:35 +02:00
Oliver Smith
103c11bd24 rx_check_imei_req(): fix IMEI bounds checking
IMEIs (without the checksum) always have 14 digits. Replace the previous
check (length <= 14) with a proper one (length == 14) and set the buffer
to the right size. While at it, add the return code of
gsm48_decode_bc_number2() to the error log message.

I have tested with new TTCN3 tests, that the length check is working
properly now.

Related: OS#2541
Change-Id: I060a8db98fb882e4815d1709a5d85bc0143a73a6
2019-06-11 08:43:49 +02:00
Oliver Smith
63de00cfc1 db_hlr: zero-initialize "struct tm"
The last LU time gets read from the database as string, parsed with
strptime to "struct tm", and then gets converted to time_t with mktime.

A recent behavior change in glibc's mktime implementation unconvered,
that we don't have tm.tm_isdst (daylight saving time) set properly. As
"struct tm" was not initialized, and strptime did not write to tm_isdst,
it was set to a random value. When it was not 0, db_test failed on UTC
systems with a more recent glibc (e.g. Ubuntu 19.04).

Fix this by zero-initializing "struct tm" and remove the previous
workaround that made db_test pass on UTC systems.

Related: OS#4026
Change-Id: Iebbbe42fc5cd43324206d9433ede67b39251389c
2019-06-04 12:41:54 +02:00
Oliver Smith
1a1398ed54 db_test: set timezone to work around mktime bug
Apply workaround for db_test failure on ubuntu 19.04 in OBS. When the
timezone is set to UTC (default in OBS), and the glibc version is 2.29,
the test case is failing sometimes (not always) with the following
errors in the test log:

DAUC Cannot convert LU timestamp '2019-05-26 03:05:03' to time_t: Value too large for defined

Force the timezone to be CET when running the test, so it passes again.
I found this workaround in a Fedora bugreport [1], and tested with an
ubuntu 19.04 system running in docker, that setting the environment
variable like this makes the test pass 25 times in a row. Without it, it
fails every two or three times. Then I verified in my OBS namespace,
that the test also passes in OBS again.

[1] https://bugzilla.redhat.com/show_bug.cgi?id=1653340

Related: OS#4026
Change-Id: Ic8080ba1914bb364169ab0c563b132a4ab9a9aab
2019-06-03 11:35:45 +02:00
Oliver Smith
a8253a54ba debian: create -doc subpackage with pdf manuals
I have verified, that the resulting debian packages build in my own OBS
namespace (see the -doc packages):
https://download.opensuse.org/repositories/home:/osmith42/Debian_9.0/all/
https://build.opensuse.org/project/show/home:osmith42

Depends: Ib7251cca9116151e473798879375cd5eb48ff3ad (osmo-ci)
Related: OS#3899
Change-Id: I4a327bac68769892634236c573c313c7859c6199
2019-05-29 12:14:13 +02:00
Vadim Yanitskiy
29f371fddf src/hlr.c: fix deprecation warning: use gsm48_decode_bcd_number2()
Change-Id: I84fc1a0a6a334805b5dc1cef994f70b01a5ffcd4
2019-05-25 19:22:41 +07:00
Vadim Yanitskiy
2e403d6c3f src/db.h: use GSM23003_MSISDN_MAX_DIGITS for MSISDN buffer size
Change-Id: I253e6a04a77c29f62d3c696515d115f8a829283b
Depends on: Idc74f4d94ad44b9fc1b6d43178f5f33d551ebfb1
2019-05-25 19:13:22 +07:00
Oliver Smith
c41572330d Document subscribers create on demand feature
Add a new section in the subscribers chapter, with detailed explanation
of the use cases and related OsmoHLR and OsmoMSC configuration.

Related: OS#2542
Change-Id: I2dd4a56f7b8be8b5d0e6fc32e04459e5e278d0a9
2019-05-19 14:43:17 +07:00
Oliver Smith
c7f1787c18 Create subscribers on demand
Add a new vty option and allow to optionally generate a random msisdn,
as well as setting the default NAM:

subscriber-create-on-demand (no-msisdn|<3-15>) (none|cs|ps|both)

Thanks to Vadim for the random MSISDN patch [1], which was squashed into
this one.

[1] Change-Id: I475c71f9902950fa7498855a616e1ec231fad6ac

Depends on: Idc74f4d94ad44b9fc1b6d43178f5f33d551ebfb1 (libosmocore)
Change-Id: I0c9fe93f5c24b5e9fefb513c4d049fb7ebd47ecd
Related: OS#2542
2019-05-19 14:42:46 +07:00
Vadim Yanitskiy
c13599dc69 db_hlr.c: add db_subscr_exists_by_msisdn()
Check if a subscriber exists without generating an error log entry if
it does not. This is cheaper than db_subscr_get_by_msisdn(), as it
does not fetch the subscriber entry.

subscriber-create-on-demand will use this function to generate
a random unique MSISDN for new subscribers.

Related: OS#2542
Change-Id: Ibfbc408c966197682ba2b12d166ade4bfeb7abc2
2019-05-13 08:55:24 +02:00
Oliver Smith
6b73fd9678 db_hlr.c: add db_subscr_exists_by_imsi()
Check if a subscriber exists without generating an error log entry if
it does not. This is cheaper than db_subscr_get_by_imsi(), as it does
not fetch the subscriber entry. subscriber-create-on-demand will use
this function.

Related: OS#2542
Change-Id: I63818c0dd4fd22b41dadeeba2a07a651b5454c54
2019-05-13 08:55:24 +02:00
Oliver Smith
cd2af5ead7 db_hlr.c: db_subscr_create(): add flags argument
Allow creating new subscribers without giving them access to CS or PS.
This will be used by the create-subscriber-on-demand feature.

Related: OS#2542
Change-Id: I1a6dd85387723dab5487c53b33d2d9ec6d05d006
2019-05-13 08:55:18 +02:00
Neels Hofmeyr
e21b45aecd use new OSMO_IMSI_BUF_SIZE
Depends: Id11ada4c96b79f7f0ad58185ab7dbf24622fb770 (libosmocore)
Change-Id: I8e8fa221e97303df3c6cce96b25d31a53f67b939
2019-05-08 04:12:38 +00:00
Neels Hofmeyr
5857c595b3 osmo-hlr: allow configuring db path from cfg file
So far, the cmdline argument was the only way to set a database config file.
Add a similar config to VTY as 'hlr' / 'database'. The cmdline arg is stronger
than the 'database' cfg item. DB is not reloaded from VTY command.

Change-Id: I87b8673324e1e6225afb758fb4963ff3279ea3d8
2019-05-08 04:12:38 +00:00
Vadim Yanitskiy
d9724f4298 hlr.c: fix possible msgb memleaks in read_cb()
Change-Id: I1226eeb24d7657e2782760fab1b49d5581ab53e2
2019-05-07 21:12:04 +07:00
Vadim Yanitskiy
c69a18bb3d hlr.c: check the presence of msgb->l2h in read_cb()
Checking the presence of msgb->l2h in read_cb_forward() doesn't
make sense, since in read_cb() we pass it to osmo_gsup_decode().
Let's rather do this before calling osmo_gsup_decode().

Fix for Change-Id: Ia4f345abc877baaf0a8f73b8988e6514d9589bf5
Change-Id: I69a3d31aacbbb1abef3d83e42e46c899fe2f914b
2019-05-07 21:12:04 +07:00
Vadim Yanitskiy
8625cdaf2a hlr.c: fix: properly print the original message type in read_cb_forward()
Printing 'OSMO_GSUP_MSGT_E_ROUTING_ERROR' in routing error messages
instead of the original message type may be confusing. Let's store
the original message type, and change just before sending.

Fix for Change-Id: Ia4f345abc877baaf0a8f73b8988e6514d9589bf5
Change-Id: Ic1db1e089fc0f8e03653a9f05058e95d2adaee39
2019-05-07 21:12:04 +07:00
Vadim Yanitskiy
609978d0ab hlr.c: fix: also store the session state in read_cb_forward()
If the session state is not set (OSMO_GSUP_SESSION_STATE_NONE),
osmo_gsup_encode() would omit the OSMO_GSUP_SESSION_ID_IE.

Fix for Change-Id: Ia4f345abc877baaf0a8f73b8988e6514d9589bf5
Change-Id: Idcd209a59d1ee5230104f3101740140d366b0646
2019-05-07 20:35:13 +07:00
Oliver Smith
28f0af872e hlr.c: forward GSUP messages between clients
Allow clients to forward any GSUP message between clients. Determine the
sender and receiver from the new {source,dest}_name{,_len} IEs. Reject
messages with a forged source name.

This will be used for the inter-MSC handover.

Depends: Ic00b0601eacff6d72927cea51767801142ee75db (libosmocore.git)
Related: OS#3793
Change-Id: Ia4f345abc877baaf0a8f73b8988e6514d9589bf5
2019-04-18 15:41:01 +02:00
Neels Hofmeyr
9f6e558215 add missing error log: invalid IMSI
Change-Id: I65e9ecac06dc6d1abb9802d621c385d3b4fab83a
2019-04-18 15:30:47 +02:00
Neels Hofmeyr
633fe291f5 fix error logging for GSUP route
The addr may not be nul terminated.

Change-Id: Ie4def16008af573ed2e1367d9da50c3d2b5a71ef
2019-04-18 15:30:25 +02:00
Oliver Smith
7d53ae1db8 USSD: don't use gsm0480_msgb_alloc_name()
We have nothing to do with GSM 04.80 at the HLR - it's only used to
encapsulate the SS payload between MS and MSC. This is not that
critical, but may be misleading.

Also, gsm0480_msgb_alloc_name() allocates a smaller buffer:

  return msgb_alloc_headroom(1024, 128, name);

than osmo_gsup_client_msgb_alloc() does:

  return msgb_alloc_headroom(4000, 64, __func__);

Change-Id: Icdab40c6a933888eb9f51bee9c5264c8919dbf7b
2019-04-11 08:29:20 +00:00
Oliver Smith
95abc2be17 USSD: save MO USSD's originating MSC's vlr_number
Save the source IPA name in ss_session, so we can send "invalid IMSI"
messages to the originating MSC.

Remove the fixed size from ss->vlr_number (we don't know the size of the
IPA name when it does not come from the database). Add
ss->vlr_number_len to give osmo_gsup_addr_send() the format it expects,
and to have one less place in the code where the IPA names are not
stored as blob.

Looking up the IPA name from struct osmo_gsup_conn could either be done
like in osmo_gsup_server_ccm_cb() by reading the IPA IEs (which has a
FIXME comment), or by finding the route associated with conn. I went
with the latter approach, because it seems cleaner to me.

Related: OS#3710
Change-Id: If5a65f471672949192061c5fe396603611123bc1
2019-04-09 13:42:26 +02:00
Neels Hofmeyr
f1fe94c8ca USSD: fix routing to multiple MSC
hlr_ussd.c so far hardcoded osmo-msc's default IPA-name and hence was
only able to negotiate USSD sessions to
- a single osmo-msc
- that has no custom IPA-name set.
Fix: correctly route USSD to any number of MSC with any custom IPA
names.

Resolve the right MSC's IPA name from the USSD session's IMSI, using
hlr_subscriber->vlr_number to determine the right GSUP peer. Cache it in
ss_session->vlr_number to have at most one db hit for routing per
session.

Related: OS#3710
Change-Id: I18067bfadd33a6bc59a9ee336b6937313826fce3
2019-04-09 08:33:57 +02:00
Oliver Smith
f7d3251d9a Cosmetic: gsup_route_find: comment addr, addrlen
Describe the addr, addrlen parameters in gsup_route_find() and (more
commonly used) osmo_gsup_addr_send(). Without this description, it is
easy to get the parameters wrong and have routes not being found, as
shown with debug prints like these:

gsup_route_find: addr, addrlen: "MSC-13-37-00-00-00-00", 21
gsup_route_find: comparing with: "MSC-13-37-00-00-00-00\0", 22

Change-Id: Ib79878970bd07caac6eb921af8ae95403b90a4cb
2019-04-08 15:12:49 +02:00
Oliver Smith
3cf87fe22c tests: use -no-install libtool flag to avoid ./lt-* scripts
This ensures that the rpath of the generated binaries is set to use only
the just-compiled so-files and not any system-wide installed libraries
while avoiding the ugly shell script wrapper.

Change-Id: I927561289b17b313d52fb5c1da55e237fc1d33be
2019-03-19 13:04:49 +00:00
Vadim Yanitskiy
ee7c0cb8d9 hlr.c: properly terminate the process on SIGTERM
As per the systemd.kill manual, when a service is going to be
stopped by systemd, the process will first be terminated via
SIGTERM. If then, after a delay, processes still remain, the
the termination request is repeated with the SIGKILL.

It was observed that osmo-hlr immediately terminates on SIGTERM,
leaving the SQLite database open. As a result, several temporary
files (such as hlr.db-shm, hlr.db-wal) remain, allowing the
further recovery:

  DDB ERROR <0001> db.c:86 (283) recovered 10 frames from WAL file

Let's properly handle SIGTERM in the same way as we handle SIGINT.

Change-Id: I1a4a48b95bbaed74ff5a03fb5797a44bdb1fcd3a
2019-03-19 18:13:32 +07:00
Oliver Smith
c5044cfd80 hlr.c: move hlr_ctx to the top
Allow all functions to use hlr_ctx, so it can be used by the upcoming
read_cb_forward() function in [1]. That new function is placed next to
the existing read_cb() function, which is above the current hlr_ctx
declaration.

[1]: Change-Id: Ia4f345abc877baaf0a8f73b8988e6514d9589bf5

Related: OS#3793
Change-Id: I5edf0a233ff323a63e321a1ca47736b5b212d1bb
2019-02-26 16:40:36 +01:00
Max
20ddfdbc53 Enable statsd support
Change-Id: I00b8aa4e59028a4c1098a3bae034e8d8ddfbe681
2019-02-18 19:57:58 +00:00
Max
227834b6bc Add link to project wiki to .service file
Change-Id: Icd4d1270c1765b8424f6df083318a1b6bc31bdb7
2019-02-18 13:52:44 +01:00
Max
44a2180009 Log ip:port when adding GSUP routes
Change-Id: I7202fdf59e763dbca261508685555b324ac7e4c0
2019-02-14 11:28:21 +01:00
Oliver Smith
f9cf180ebe hlr.c: replace deprecated osmo_gsup_get_err_msg_type()
Use OSMO_GSUP_TO_MSGT_ERROR() instead. The other function has been
deprecated in [1].

Remove the < 0 return check, because the macro doesn't do any error
handling. It relies on all "request" messages having an appropriate
"error" message, which is part of the GSUP spec now (see [2]).

[1] change-id I46d9f2327791978710e2f90b4d28a3761d723d8f (libosmocore)
[2] change-id Iec1b4ce4b7d8eb157406f006e1c4241e8fba2cd6 (osmo-gsm-manuals)

Change-Id: I5435ec4c29d6acee814c33499c68d18aaa91d4fb
2019-02-04 11:22:46 +01:00
Oliver Smith
02078b7d91 VTY: integrate IMEI
Display the IMEI in "subscriber ... show", allow showing and modifying
subscribers by their IMEI with: "subscriber imei ...". For debug
purposes (and to have proper VTY tests), make it possible to change the
IMEI with "subscriber ... update imei".

IMEIs are saved in the database without the 15th checksum number. When
the checksum gets passed, verify it and cut it off.

Related: OS#2541
Depends: I02b54cf01a674a1911c5c897fbec02240f88b521 (libosmocore)
Change-Id: I1af7b573ca2a1cb22497052665012d9c1acf3b30
2019-01-24 15:29:08 +00:00
Oliver Smith
ef64b231dc VTY tests: fill DB before running test
Create a test_subscriber.vty.sql file with a dummy entry that has the
ID 100. All entries created in test_subscriber.vty have an ID > 100
now. This will be used in follow-up commit [1] to create a database
entry with an invalid IMEI value to test the related error handling
code path (that entry could not be created through the VTY).

[1]: change-id I1af7b573ca2a1cb22497052665012d9c1acf3b30
     "VTY: integrate IMEI"

Related: OS#3733
Change-Id: I48a3a503d7ca96798e2d5f70429b5fc36393420e
2019-01-24 15:29:08 +00:00
Oliver Smith
851814aa7c Optionally store IMEI in subscriber table
Add VTY config option "store-imei". When it is set, store the IMEI
sent from the VLR with CHECK-IMEI in the database.

Related: OS#2541
Change-Id: I09274ecbed64224f7ae305e09ede773931da2a57
2019-01-24 15:29:08 +00:00
Oliver Smith
81db389fd4 Add IMEI column to subscriber table
Extend the database scheme, add imei to the hlr_subscriber struct and
create db_subscr_update_imei_by_imsi() and db_subscr_get_by_imei(). The
new functions are used in db_test, and in follow-up commits [1], [2].

Upgrade DB schema to version 2. SQLite can only insert new columns at
the end of the table, so this happens when upgrading the database. In
new databases, the column is placed after the IMEISV column (where it
makes more sense in my opinion). This should not have any effect, as
we never rely on the order of the columns in the tables.

Follow-up commit [1] will make use of this column to save the IMEI as
received from the MSC/VLR with the Check-IMEI Procedure. It was
decided to use Check-IMEI instead of the recent Automatic Device
Detection Procedure (which would send the IMEISV) in OS#3733, because
with Check-IMEI we don't need to rely on very recent releases of the
specification.

[1] change-id I09274ecbed64224f7ae305e09ede773931da2a57
    "Optionally store IMEI in subscriber table"
[2] change-id I1af7b573ca2a1cb22497052665012d9c1acf3b30
    "VTY: integrate IMEI"

Depends: Id2d2a3a93b033bafc74c62e15297034bf4aafe61 (libosmocore)
Related: OS#2541
Change-Id: If232c80bea35d5c6864b889ae92d477eeaa3f45d
2019-01-24 15:29:08 +00:00
Oliver Smith
7943e26938 docs: running: document --db-upgrade
Related: OS#3759
Change-Id: I641fd258091974662d9f63697aea103eaf151d09
2019-01-22 15:40:18 +01:00
Harald Welte
e0c6fe5921 Bump version: 0.2.1.55-607c-dirty → 1.0.0
Change-Id: I696beb6f0b82dfaf664f62066cffbcc94e31b700
2019-01-20 20:08:14 +00:00
Harald Welte
f58f44543f test_nodes.vty: Since libosmocore 1.0.0, we only have one space
The output of "logging level" was wrongly indented with two speaces, and
in commit 0d67f483e2d240089105a4d241cb8c9085e245af of libosmocore, first
released as part of libosmcoore-1.0.0 it was fixed.

This makes "make check" pass against libosmocore-1.0.0, but not against
earlier releases.

Change-Id: I2cc12b3f0df19d9055022db41f7f3f2789bd19c6
2019-01-20 20:08:14 +00:00
Oliver Smith
15f624ec53 docs: running: s/OsmoBTS/OsmoHLR
Change-Id: Ib4e1c8460dbe0a9b7dca8d2291a5e6c5406180e7
2019-01-17 12:31:56 +00:00
Oliver Smith
d4e0e4d503 docs: running: same argument order as osmo-hlr -h
Change the Synopsis and Options sections of the Running OsmoHLR chapter
to list the arguments in the same order as osmo-hlr. This makes it
easier to compare, which options are already documented, and which ones
are missing.

A follow-up commit will document the missing -U/--db-upgrade option.

Change-Id: If866124e9cfb43c6986d458712961713541e03b6
2019-01-17 12:31:56 +00:00
Oliver Smith
2dc7d960a1 Cosmetic: fix arg desc of db_subscr_update_msisdn_by_imsi()
Properly note that NULL can only be passed as msisdn, not imsi in that
function.

Change-Id: I19b6ad0cf6e9a4bfa9bffa447ebfc7bd1bcac361
2019-01-15 14:16:16 +01:00
Oliver Smith
52c4aa09b2 gitignore: add tests/hlr_vty_test.db*
Ignore files generated from the VTY test.

Change-Id: I8f55f655fd6694ac9db7e6280670d16fad61ee72
2019-01-15 13:33:10 +01:00
Oliver Smith
66106c0992 Cosmetic: hlr.c: remove confusing indent below if
Remove a misleading block in rx_upd_loc_req() around the call to
lu_op_tx_insert_subscr_data(). At least for me, this made the block look
like it belonged to the if statement above (which has no brackets),
before I looked more closely at it.

Change-Id: I96d3ba4108f4811279caf088a9179afce24e8112
2019-01-09 12:05:15 +01:00
Oliver Smith
783ac81b9c Reply to CHECK-IMEI GSUP messages
Decode the IMEI from incoming CHECK-IMEI messages, print the IMEI to
the log and always send ACK back to the VLR/MSC.

In the future, we will not only log the IMEI, but store it in the HLR
(OS#2541). This is not the original intention of CHECK-IMEI from the
3GPP spec, but an useful side effect.

Depends: I085819df0ea7f3bfeb0cabebb5fd1942a23c6155 (libosmocore)
Related: OS#3733
Change-Id: Ib240474b0c3c603ba840cf26babb38a44dfc9364
2019-01-07 09:43:37 +00:00
148 changed files with 15171 additions and 2653 deletions

13
.gitignore vendored
View File

@@ -2,6 +2,8 @@
*.lo
*.la
*.db
*.db-shm
*.db-wal
*.pyc
.*.sw?
.version
@@ -24,6 +26,7 @@ m4
*.m4
missing
.deps
*~
*.pc
.libs
@@ -34,10 +37,12 @@ src/osmo-hlr
src/osmo-hlr-db-tool
src/osmo-euse-demo
src/gsupclient/gsup-test-client
src/mslookup/osmo-mslookup-client
tests/atconfig
tests/testsuite
tests/testsuite.log
tests/testsuite.dir
tests/auc/auc_3g_test
tests/auc/auc_ts_55_205_test_sets.c
@@ -46,6 +51,12 @@ tests/auc/auc_test
tests/gsup_server/gsup_server_test
tests/gsup/gsup_test
tests/db/db_test
tests/hlr_vty_test.db*
tests/db_upgrade/*.dump
tests/mslookup/mdns_test
tests/mslookup/mslookup_client_mdns_test
tests/mslookup/mslookup_client_test
tests/mslookup/mslookup_test
# manuals
doc/manuals/*.html
@@ -57,3 +68,5 @@ doc/manuals/generated/
doc/manuals/osmomsc-usermanual.xml
doc/manuals/common
doc/manuals/build
contrib/osmo-hlr.spec

View File

@@ -1,9 +1,9 @@
AUTOMAKE_OPTIONS = foreign dist-bzip2
SUBDIRS = \
doc \
src \
include \
doc \
sql \
contrib \
tests \
@@ -11,13 +11,16 @@ SUBDIRS = \
EXTRA_DIST = \
.version \
contrib/osmo-hlr.spec.in \
debian \
$(NULL)
AM_DISTCHECK_CONFIGURE_FLAGS = \
--with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libosmo-gsup-client.pc
pkgconfig_DATA = libosmo-gsup-client.pc \
libosmo-mslookup.pc
@RELMAKE@

66
README.md Normal file
View File

@@ -0,0 +1,66 @@
osmo-hlr - Osmocom HLR Implementation
=====================================
This repository contains a C-language implementation of a GSM Home
Location Register (HLR). It is part of the
[Osmocom](https://osmocom.org/) Open Source Mobile Communications
project.
Warning: While the HLR logical functionality is implemented, OsmoHLR
does not use the ETSI/3GPP TCAP/MAP protocol stack. Instead, a much
simpler custom protocol (GSUP) is used. This means, OsmoHLR is of
no use outside the context of an Osmocom core network. You can use
it with OsmoMSC, OsmoSGSN etc. - but not with third party components.
Homepage
--------
The official homepage of the project is
https://osmocom.org/projects/osmo-hlr/wiki
GIT Repository
--------------
You can clone from the official osmo-hlr.git repository using
git clone git://git.osmocom.org/osmo-hlr.git
git clone https://git.osmocom.org/osmo-hlr.git
There is a cgit interface at https://git.osmocom.org/osmo-hlr/
Documentation
-------------
User Manuals and VTY reference manuals are [optionally] built in PDF form
as part of the build process.
Pre-rendered PDF version of the current "master" can be found at
[User Manual](https://ftp.osmocom.org/docs/latest/osmohlr-usermanual.pdf)
as well as the VTY reference manuals
* [VTY Reference Manual for osmo-hlr](https://ftp.osmocom.org/docs/latest/osmohlr-vty-reference.pdf)
Mailing List
------------
Discussions related to osmo-hlr are happening on the
openbsc@lists.osmocom.org mailing list, please see
https://lists.osmocom.org/mailman/listinfo/openbsc for subscription
options and the list archive.
Please observe the [Osmocom Mailing List
Rules](https://osmocom.org/projects/cellular-infrastructure/wiki/Mailing_List_Rules)
when posting.
Contributing
------------
Our coding standards are described at
https://osmocom.org/projects/cellular-infrastructure/wiki/Coding_standards
We us a gerrit based patch submission/review process for managing
contributions. Please see
https://osmocom.org/projects/cellular-infrastructure/wiki/Gerrit for
more details
The current patch queue for osmo-hlr can be seen at
https://gerrit.osmocom.org/#/q/project:osmo-hlr+status:open

9
TODO-RELEASE Normal file
View File

@@ -0,0 +1,9 @@
# When cleaning up this file: bump API version in corresponding Makefile.am and rename corresponding debian/lib*.install
# according to https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
# In short:
# LIBVERSION=c:r:a
# If the library source code has changed at all since the last update, then increment revision: c:r + 1:a.
# If any interfaces have been added, removed, or changed since the last update: c + 1:0:0.
# If any interfaces have been added since the last public release: c:r:a + 1.
# If any interfaces have been removed or changed since the last public release: c:r:0.
#library what description / commit summary line

View File

@@ -12,6 +12,8 @@ AM_INIT_AUTOMAKE([foreign dist-bzip2 no-dist-gzip 1.9])
AC_CONFIG_TESTDIR(tests)
CFLAGS="$CFLAGS -std=gnu11"
dnl kernel style compile messages
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
@@ -25,6 +27,11 @@ AC_PROG_MKDIR_P
AC_PROG_CC
AC_PROG_INSTALL
dnl patching ${archive_cmds} to affect generation of file "libtool" to fix linking with clang
AS_CASE(["$LD"],[*clang*],
[AS_CASE(["${host_os}"],
[*linux*],[archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'])])
dnl check for pkg-config (explained in detail in libosmocore/configure.ac)
AC_PATH_PROG(PKG_CONFIG_INSTALLED, pkg-config, no)
if test "x$PKG_CONFIG_INSTALLED" = "xno"; then
@@ -34,11 +41,11 @@ PKG_PROG_PKG_CONFIG([0.20])
PKG_CHECK_MODULES(TALLOC, [talloc >= 2.0.1])
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.11.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.11.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.11.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 0.11.0)
PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.5.0)
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.5.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.5.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.5.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.5.0)
PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 1.1.0)
PKG_CHECK_MODULES(SQLITE3, sqlite3)
@@ -59,6 +66,21 @@ then
CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined"
fi
AC_ARG_ENABLE([sqlite_talloc],
AC_HELP_STRING([--enable-sqlite-talloc],
[Configure SQLite3 to use talloc memory allocator [default=no]]),
[sqlite_talloc="$enableval"],[sqlite_talloc="no"])
if test "x$sqlite_talloc" = "xyes" ; then
# Older versions of SQLite3 (at least 3.8.2) become unstable with talloc.
# Feel free to relax to 3.24.0 > VER > 3.8.2 if it works for you.
# FIXME: PKG_CHECK_MODULES() may return cached result here!
PKG_CHECK_MODULES(SQLITE3, sqlite3 >= 3.24.0)
AC_DEFINE([SQLITE_USE_TALLOC], 1, [Use talloc for SQLite3])
fi
AC_MSG_CHECKING([whether to use talloc for SQLite3])
AC_MSG_RESULT([$sqlite_talloc])
AM_CONDITIONAL([DB_SQLITE_DEBUG], [test "x$sqlite_talloc" = "xyes"])
AC_ARG_ENABLE(werror,
[AS_HELP_STRING(
[--enable-werror],
@@ -92,6 +114,15 @@ AC_MSG_CHECKING([whether to enable VTY/CTRL tests])
AC_MSG_RESULT([$enable_ext_tests])
AM_CONDITIONAL(ENABLE_EXT_TESTS, test "x$enable_ext_tests" = "xyes")
# mslookup_client_mdns_test (OS#4385: does not work everywhere)
AC_ARG_ENABLE([mslookup_client_mdns_test],
AC_HELP_STRING([--enable-mslookup-client-mdns-test],
[Include the mslookup_client_mdns_test in make check [default=no]]),
[enable_mslookup_client_mdns_test="$enableval"],[enable_mslookup_client_mdns_test="no"])
AC_MSG_CHECKING([whether to enable mslookup_client_mdns_test])
AC_MSG_RESULT([$enable_mslookup_client_mdns_test])
AM_CONDITIONAL(ENABLE_MSLOOKUP_CLIENT_MDNS_TEST, test "x$enable_mslookup_client_mdns_test" = "xyes")
# Generate manuals
AC_ARG_ENABLE(manuals,
[AS_HELP_STRING(
@@ -159,16 +190,25 @@ AC_OUTPUT(
doc/examples/Makefile
src/Makefile
src/gsupclient/Makefile
src/mslookup/Makefile
include/Makefile
include/osmocom/Makefile
include/osmocom/hlr/Makefile
include/osmocom/mslookup/Makefile
libosmo-gsup-client.pc
libosmo-mslookup.pc
sql/Makefile
doc/manuals/Makefile
contrib/Makefile
contrib/systemd/Makefile
contrib/dgsm/Makefile
contrib/osmo-hlr.spec
tests/Makefile
tests/auc/Makefile
tests/auc/gen_ts_55_205_test_sets/Makefile
tests/gsup_server/Makefile
tests/gsup/Makefile
tests/db/Makefile
tests/db_upgrade/Makefile
tests/mslookup/Makefile
)

View File

@@ -1 +1,14 @@
SUBDIRS = systemd
SUBDIRS = \
systemd \
dgsm \
$(NULL)
EXTRA_DIST = osmo-hlr-post-upgrade.sh
install-data-hook:
install -Dm755 $(srcdir)/osmo-hlr-post-upgrade.sh \
-t $(DESTDIR)$(datadir)/osmocom/
uninstall-hook:
@$(PRE_UNINSTALL)
$(RM) $(DESTDIR)$(datadir)/osmocom/osmo-hlr-post-upgrade.sh

6
contrib/dgsm/Makefile.am Normal file
View File

@@ -0,0 +1,6 @@
EXTRA_DIST = \
esme_dgsm.py \
freeswitch_dialplan_dgsm.py \
osmo-mslookup-pipe.py \
osmo-mslookup-socket.py \
$(NULL)

184
contrib/dgsm/esme_dgsm.py Executable file
View File

@@ -0,0 +1,184 @@
#!/usr/bin/env python3
"""
SPDX-License-Identifier: MIT
Copyright 2019 sysmocom s.f.m.c GmbH <info@sysmocom.de>
WARNING: this is just a proof-of-concept implementation, it blocks for every
received SMPP request and is not suitable for servicing more than one request
at a time.
Based on esme.py from RCCN (license changed with permission from author):
https://github.com/Rhizomatica/rccn/blob/master/rccn/esme.py
Copyright 2017 keith <keith@rhizomatica.org>
Forward SMS to the receiver's SMSC, as determined with mslookup.
Requires smpplip (pip3 install --user smpplib) and osmo-mslookup-client.
Example SMPP configuration for osmo-msc.cfg:
smpp
local-tcp-ip 127.0.0.1 2775
policy closed
smpp-first
# outgoing to esme_dgsm.py
esme OSMPP
no alert-notifications
password foo
default-route
# incoming from esme_dgsm.py
esme ISMPP
no alert-notifications
password foo
"""
import argparse
import json
import logging
import smpplib
import subprocess
import time
def can_handle_pdu(pdu):
if not isinstance(pdu, smpplib.command.DeliverSM):
logging.info('PDU is not a DeliverSM, ignoring')
return False
if int(pdu.dest_addr_ton) == smpplib.consts.SMPP_TON_INTL:
logging.info("Unable to handle SMS for %s: SMPP_TON_INTL" %
(pdu.destination_addr))
return False
return True
def query_mslookup(service_type, id, id_type='msisdn'):
query_str = '%s.%s.%s' % (service_type, id, id_type)
logging.info('mslookup: ' + query_str)
result_line = subprocess.check_output(['osmo-mslookup-client', query_str,
'-f', 'json'])
if isinstance(result_line, bytes):
result_line = result_line.decode('ascii')
logging.info('mslookup result: ' + result_line.rstrip())
return json.loads(result_line)
def tx_sms(dst_host, dst_port, source, destination, registered_delivery,
unicode_text):
smpp_client = smpplib.client.Client(dst_host, dst_port, 90)
smpp_client.connect()
smpp_client.bind_transceiver(system_id=args.dst_id, password=args.dst_pass)
logging.info('Connected to destination SMSC (%s@%s:%s)' % (args.dst_id,
dst_host, dst_port))
pdu = smpp_client.send_message(
source_addr_ton=smpplib.consts.SMPP_TON_ALNUM,
source_addr_npi=smpplib.consts.SMPP_NPI_UNK,
source_addr=source.decode(),
dest_addr_ton=smpplib.consts.SMPP_TON_SBSCR,
dest_addr_npi=smpplib.consts.SMPP_NPI_ISDN,
destination_addr=destination.decode(),
short_message=unicode_text,
registered_delivery=registered_delivery,
)
smpp_client.unbind()
smpp_client.disconnect()
del pdu
del smpp_client
def rx_deliver_sm(pdu):
if not can_handle_pdu(pdu):
return smpplib.consts.SMPP_ESME_RSYSERR
msisdn = pdu.destination_addr.decode()
logging.info("Incoming SMS for: " + msisdn)
if args.sleep:
logging.info("Sleeping for %i seconds" % (args.sleep))
time.sleep(args.sleep)
logging.info("Sleep done")
if args.always_fail is not None:
return args.always_fail
result = query_mslookup("smpp.sms", msisdn)
if 'v4' not in result or not result['v4']:
logging.info('No IPv4 result from mslookup! This example only'
' makes use of IPv4, dropping.')
return smpplib.consts.SMPP_ESME_RSYSERR
dst_host, dst_port = result['v4']
tx_sms(dst_host, dst_port, pdu.source_addr,
pdu.destination_addr, int(pdu.registered_delivery),
pdu.short_message)
return smpplib.consts.SMPP_ESME_ROK
def smpp_bind():
client = smpplib.client.Client(args.src_host, args.src_port, 90)
client.set_message_received_handler(rx_deliver_sm)
client.connect()
client.bind_transceiver(system_id=args.src_id, password=args.src_pass)
logging.info('Connected to source SMSC (%s@%s:%s)' % (args.src_id,
args.src_host, args.src_port))
logging.info('Waiting for SMS...')
client.listen()
def main():
global args
parser = argparse.ArgumentParser()
parser.add_argument('--src-host', default='127.0.0.1',
help='source SMSC (OsmoMSC) host (default: 127.0.0.1)')
parser.add_argument('--src-port', default=2775, type=int,
help='source SMSC (OsmoMSC) port (default: 2775)')
parser.add_argument('--src-id', default='OSMPP',
help='source system id, as configured in osmo-msc.cfg'
' (default: OSMPP)')
parser.add_argument('--src-pass', default='foo',
help='source system password, as configured in'
' osmo-msc.cfg (default: foo)')
parser.add_argument('--dst-id', default='ISMPP',
help='destination system id, as configured in'
' osmo-msc.cfg (default: ISMPP)')
parser.add_argument('--dst-pass', default='foo',
help='destination system password, as configured in'
' osmo-msc.cfg (default: foo)')
parser.add_argument('--sleep', default=0, type=float,
help='sleep time in seconds before forwarding an SMS,'
' to test multithreading (default: 0)')
parser.add_argument('--always-fail', default=None, metavar='SMPP_ESME_ERRCODE',
help='test delivery failure: always return an error code on Deliver-SM,'
' pass an smpplib error code name like RDELIVERYFAILURE (see smpplib/consts.py),'
' or an SMPP error code in hex digits')
args = parser.parse_args()
logging.basicConfig(level=logging.INFO, format='[%(asctime)s]'
' (%(threadName)s) %(message)s', datefmt="%H:%M:%S")
if args.always_fail:
resolved = None
name = 'SMPP_ESME_' + args.always_fail
if hasattr(smpplib.consts, name):
resolved = getattr(smpplib.consts, name)
if resolved is None:
try:
resolved = int(args.always_fail, 16)
except ValueError:
resolved = None
if resolved is None:
print('Invalid argument for --always-fail: %r' % args.always_fail)
exit(1)
args.always_fail = resolved
logging.info('--always-fail: returning error code %s to all Deliver-SM' % hex(args.always_fail))
smpp_bind()
if __name__ == "__main__":
main()
# vim: expandtab tabstop=4 shiftwidth=4

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""
SPDX-License-Identifier: MIT
Copyright 2019 sysmocom s.f.m.c GmbH <info@sysmocom.de>
This is a freeswitch dialplan implementation, see:
https://freeswitch.org/confluence/display/FREESWITCH/mod_python
Find the right SIP server with mslookup (depending on the destination number)
and bridge calls accordingly.
"""
import json
import subprocess
def query_mslookup(service_type, id, id_type='msisdn'):
query_str = '%s.%s.%s' % (service_type, id, id_type)
print('[dialplan-dgsm] mslookup: ' + query_str)
result_line = subprocess.check_output([
'osmo-mslookup-client', query_str, '-f', 'json'])
if isinstance(result_line, bytes):
result_line = result_line.decode('ascii')
print('[dialplan-dgsm] mslookup result: ' + result_line)
return json.loads(result_line)
def handler(session, args):
""" Handle calls: bridge to the SIP server found with mslookup. """
print('[dialplan-dgsm] call handler')
msisdn = session.getVariable('destination_number')
# Run osmo-mslookup-client binary. We have also tried to directly call the
# C functions with ctypes but this has lead to hard-to-debug segfaults.
try:
result = query_mslookup("sip.voice", msisdn)
# This example only makes use of IPv4
if not result['v4']:
print('[dialplan-dgsm] no IPv4 result from mslookup')
session.hangup('UNALLOCATED_NUMBER')
return
sip_ip, sip_port = result['v4']
dial_str = 'sofia/internal/sip:{}@{}:{}'.format(
msisdn, sip_ip, sip_port)
print('[dialplan-dgsm] dial_str: ' + str(dial_str))
session.execute('bridge', dial_str)
except:
print('[dialplan-dgsm]: exception during call handler')
session.hangup('UNALLOCATED_NUMBER')
def fsapi(session, stream, env, args):
""" Freeswitch refuses to load the module without this. """
stream.write(env.serialize())
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('id', type=int)
parser.add_argument('-i', '--id-type', default='msisdn',
help='default: "msisdn"')
parser.add_argument('-s', '--service', default='sip.voice',
help='default: "sip.voice"')
args = parser.parse_args()
result = query_mslookup(args.service, args.id, args.id_type)
print(json.dumps(result))
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env python3
# vim: shiftwidth=4 tabstop=4 expandtab
import subprocess
import json
def query_mslookup(query_str):
result = {'result': 'not-found'}
proc = subprocess.Popen(('osmo-mslookup-client', '-f', 'json', query_str),
stdout=subprocess.PIPE)
for line in iter(proc.stdout.readline,''):
if not line:
break
response = json.loads(line)
if response.get('result') == 'result':
result = response
print('Response: %r' % response)
return result
if __name__ == '__main__':
import sys
query_str = '1000-5000@sip.voice.12345.msisdn'
if len(sys.argv) > 1:
query_str = sys.argv[1]
print('Final result: %r' % query_mslookup(query_str))

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python3
# vim: shiftwidth=4 tabstop=4 expandtab
import socket
import time
MSLOOKUP_SOCKET_PATH = '/tmp/mslookup'
def query_mslookup_socket(query_str, socket_path=MSLOOKUP_SOCKET_PATH):
mslookup_socket = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)
mslookup_socket.setblocking(True)
mslookup_socket.connect(socket_path)
result = {'result': 'not-found'}
column_names = mslookup_socket.recv(1024).decode('ascii')
if not column_names:
return result
column_names = column_names.split('\t')
mslookup_socket.sendall(query_str.encode('ascii'))
while True:
csv = mslookup_socket.recv(1024).decode('ascii')
if not csv:
break
response = dict(zip(column_names, csv.split('\t')))
if response.get('result') == 'result':
result = response
print('Response: %r' % response)
return result
if __name__ == '__main__':
import sys
print(
'\nPlease run separately: osmo-mslookup-client --socket /tmp/mslookup -d\n')
query_str = '1000-5000@sip.voice.12345.msisdn'
if len(sys.argv) > 1:
query_str = sys.argv[1]
print('Final result: %r' % query_mslookup_socket(query_str))

View File

@@ -35,7 +35,6 @@ osmo-build-dep.sh libosmo-abis
# Additional configure options and depends
CONFIG=""
if [ "$WITH_MANUALS" = "1" ]; then
osmo-build-dep.sh osmo-gsm-manuals
CONFIG="--enable-manuals"
fi
@@ -49,13 +48,19 @@ set -x
cd "$base"
autoreconf --install --force
./configure --enable-sanitize --enable-external-tests --enable-werror $CONFIG
./configure \
--enable-sanitize \
--enable-external-tests \
--enable-mslookup-client-mdns-test \
--enable-werror \
$CONFIG
$MAKE $PARALLEL_MAKE
$MAKE check || cat-testlogs.sh
DISTCHECK_CONFIGURE_FLAGS="$CONFIG" $MAKE distcheck || cat-testlogs.sh
DISTCHECK_CONFIGURE_FLAGS="$CONFIG" $MAKE $PARALLEL_MAKE distcheck || cat-testlogs.sh
if [ "$WITH_MANUALS" = "1" ] && [ "$PUBLISH" = "1" ]; then
make -C "$base/doc/manuals" publish
fi
$MAKE $PARALLEL_MAKE maintainer-clean
osmo-clean-workspace.sh

View File

@@ -0,0 +1,95 @@
#!/bin/sh -e
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright 2021 sysmocom s.f.m.c GmbH <info@sysmocom.de>
#
# Packagers are supposed to call this script in post-upgrade, so it can safely
# upgrade the database scheme if required.
DB="/var/lib/osmocom/hlr.db"
IS_ACTIVE=0
msg() {
echo "osmo-hlr-post-upgrade: $@"
}
err() {
msg "ERROR: $@"
}
open_db() {
# Attempt to open the database with osmo-hlr-db-tool, it will fail if
# upgrading the schema is required
osmo-hlr-db-tool -s -l "$DB" create
}
check_upgrade_required() {
if ! [ -e "$DB" ]; then
msg "nothing to do (no existing database)"
exit 0
fi
if open_db 2>/dev/null; then
msg "nothing to do (database version is up to date)"
exit 0
fi
msg "database upgrade is required"
}
stop_service() {
if systemctl is-active -q osmo-hlr; then
IS_ACTIVE=1
msg "stopping osmo-hlr service"
systemctl stop osmo-hlr
# Verify that it stopped
for i in $(seq 1 100); do
if ! systemctl is-active -q osmo-hlr; then
return
fi
sleep 0.1
done
err "failed to stop osmo-hlr service"
exit 1
else
msg "osmo-hlr service is not running"
fi
}
create_backup() {
backup="$DB.$(date +%Y%m%d%H%M%S).bak"
msg "creating backup: $backup"
if [ -e "$backup" ]; then
err "backup already exists: $backup"
exit 1
fi
cp "$DB" "$backup"
}
upgrade() {
msg "performing database upgrade"
osmo-hlr-db-tool -s -U -l "$DB" create
if ! open_db 2>/dev/null; then
err "failed to open the database after upgrade"
err "osmo-hlr-db-tool output:"
open_db
# exit because of "set -e"
fi
msg "database upgrade successful"
}
start_service() {
if [ "$IS_ACTIVE" = "1" ]; then
msg "starting osmo-hlr service"
systemctl start osmo-hlr
fi
}
check_upgrade_required
stop_service
create_backup
upgrade
start_service

195
contrib/osmo-hlr.spec.in Normal file
View File

@@ -0,0 +1,195 @@
#
# spec file for package osmo-hlr
#
# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
# Copyright (c) 2016, Martin Hauke <mardnh@gmx.de>
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
Name: osmo-hlr
Version: @VERSION@
Release: 0
Summary: Osmocom Home Location Register for GSUP protocol towards OsmoSGSN and OsmoCSCN
License: AGPL-3.0-or-later AND GPL-2.0-or-later
Group: Productivity/Telephony/Servers
URL: https://osmocom.org/projects/osmo-hlr
Source: %{name}-%{version}.tar.xz
BuildRequires: autoconf
BuildRequires: automake
BuildRequires: libtool
BuildRequires: pkgconfig >= 0.20
BuildRequires: python3
%if 0%{?suse_version}
BuildRequires: systemd-rpm-macros
%endif
BuildRequires: pkgconfig(libosmoabis) >= 1.1.0
BuildRequires: pkgconfig(libosmocore) >= 1.5.0
BuildRequires: pkgconfig(libosmoctrl) >= 1.5.0
BuildRequires: pkgconfig(libosmogsm) >= 1.5.0
BuildRequires: pkgconfig(libosmovty) >= 1.5.0
BuildRequires: pkgconfig(sqlite3)
BuildRequires: pkgconfig(talloc) >= 2.0.1
# only needed for populate_hlr_db.pl
Requires: libdbi-drivers-dbd-sqlite3
%{?systemd_requires}
%description
The GSUP HLR is a stand-alone HLR (Home Location Register) for SIM
and USIM based subscribers which exposes the GSUP protocol towards
its users. OsmoSGSN supports this protocol.
osmo-gsup-hlr is still very simplistic. It is a single-threaded
architecture and uses only sqlite3 tables as back-end. It is suitable
for installations of the scale that OsmoNITB was able to handle. It
also lacks various features like fine-grained control of subscribed
services (like supplementary services).
%package -n libosmo-gsup-client0
Summary: Osmocom GSUP (General Subscriber Update Protocol) client library
License: GPL-2.0-or-later
Group: System/Libraries
%description -n libosmo-gsup-client0
This is a shared library that can be used to implement client programs for
the GSUP protocol. The typical GSUP server is OsmoHLR, with OsmoMSC, OsmoSGSN
and External USSD Entities (EUSEs) using this library to implement clients.
%package -n libosmo-gsup-client-devel
Summary: Development files for the Osmocom GSUP client library
License: GPL-2.0-or-later
Group: Development/Libraries/C and C++
Requires: libosmo-gsup-client0 = %{version}
%description -n libosmo-gsup-client-devel
This is a shared library that can be used to implement client programs for
the GSUP protocol. The typical GSUP server is OsmoHLR, with OsmoMSC, OsmoSGSN
and External USSD Entities (EUSEs) using this library to implement clients.
This subpackage contains libraries and header files for developing
applications that want to make use of libosmo-gsup-client.
%package -n libosmo-mslookup0
Summary: Osmocom MS lookup library
License: GPL-2.0-or-later
Group: System/Libraries
%description -n libosmo-mslookup0
This shared library contains routines for looking up mobile subscribers.
%package -n libosmo-mslookup-devel
Summary: Development files for the Osmocom MS lookup library
License: GPL-2.0-or-later
Group: Development/Libraries/C and C++
Requires: libosmo-mslookup0 = %{version}
%description -n libosmo-mslookup-devel
This shared library contains routines for looking up mobile subscribers.
This subpackage contains libraries and header files for developing
applications that want to make use of libosmo-mslookup.
%package -n osmo-mslookup-client
Summary: Standalone program using libosmo-mslookup
License: GPL-2.0-or-later
Group: Development/Libraries/C and C++
%description -n osmo-mslookup-client
Standalone program using libosmo-mslookup to easily integrate with programs
that want to connect services (SIP, SMS,...) to the current location of a
subscriber.
%prep
%setup -q
%build
echo "%{version}" >.tarball-version
autoreconf -fi
%configure \
--docdir="%{_docdir}/%{name}" \
--with-systemdsystemunitdir=%{_unitdir} \
--enable-shared \
--disable-static
make V=1 %{?_smp_mflags}
%install
%make_install
install -d "%{buildroot}/%{_localstatedir}/lib/osmocom"
find %{buildroot} -type f -name "*.la" -delete -print
%check
make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
%if 0%{?suse_version}
%preun
%service_del_preun %{name}.service
%postun
%service_del_postun %{name}.service
%pre
%service_add_pre %{name}.service
%endif
%post
%if 0%{?suse_version}
%service_add_post %{name}.service
%endif
/usr/share/osmocom/osmo-hlr-post-upgrade.sh
%post -n libosmo-gsup-client0 -p /sbin/ldconfig
%postun -n libosmo-gsup-client0 -p /sbin/ldconfig
%post -n libosmo-mslookup0 -p /sbin/ldconfig
%postun -n libosmo-mslookup0 -p /sbin/ldconfig
%files
%license COPYING
%dir %{_docdir}/%{name}
%dir %{_docdir}/%{name}/examples
%{_docdir}/%{name}/examples/osmo-hlr.cfg
%{_docdir}/%{name}/examples/osmo-hlr-dgsm.cfg
%dir %{_docdir}/%{name}/sql
%{_docdir}/%{name}/sql/hlr.sql
%{_docdir}/%{name}/sql//hlr_data.sql
%dir %{_sysconfdir}/osmocom
%dir %{_localstatedir}/lib/osmocom
%{_bindir}/osmo-hlr
%{_bindir}/osmo-hlr-db-tool
%dir %{_sysconfdir}/osmocom
%config %{_sysconfdir}/osmocom/osmo-hlr.cfg
%{_unitdir}/osmo-hlr.service
%dir %{_datadir}/osmocom
%{_datadir}/osmocom/osmo-hlr-post-upgrade.sh
%files -n libosmo-gsup-client0
%{_libdir}/libosmo-gsup-client.so.0*
%files -n libosmo-gsup-client-devel
%{_bindir}/osmo-euse-demo
%dir %{_includedir}/osmocom
%dir %{_includedir}/osmocom/gsupclient
%{_includedir}/osmocom/gsupclient/*.h
%{_libdir}/libosmo-gsup-client.so
%{_libdir}/pkgconfig/libosmo-gsup-client.pc
%files -n libosmo-mslookup0
%{_libdir}/libosmo-mslookup.so.0*
%files -n libosmo-mslookup-devel
%dir %{_includedir}/osmocom
%dir %{_includedir}/osmocom/mslookup
%{_includedir}/osmocom/mslookup/*.h
%{_libdir}/libosmo-mslookup.so
%{_libdir}/pkgconfig/libosmo-mslookup.pc
%files -n osmo-mslookup-client
%{_bindir}/osmo-mslookup-client
%changelog

View File

@@ -1,5 +1,6 @@
[Unit]
Description=Osmocom Home Location Register (OsmoHLR)
Documentation=https://osmocom.org/projects/osmo-hlr/wiki/OsmoHLR
[Service]
Type=simple

280
debian/changelog vendored
View File

@@ -1,3 +1,283 @@
osmo-hlr (1.3.0) unstable; urgency=medium
[ Alexander Couzens ]
* hlr: respect the num_auth_vectors requested
* hlr: remove unused internal USSD list
[ Oliver Smith ]
* add libosmo-mslookup abstract client
* add mDNS lookup method to libosmo-mslookup
* Makefile.am: fix pkgconfig_DATA
* add mDNS lookup method to libosmo-mslookup (#2)
* contrib/dgsm/ add example esme and dialplan
* mslookup_client.c: fix dereferencing null pointer
* mdns_msg.c: always call va_end
* mslookup_client_mdns.c: fix dereferencing null
* osmo-mslookup-client.c: fix dereferencing null
* osmo-mslookup-client: fix dereferencing null
* mdns_sock.c: fix resource leak of sock
* mdns_rfc.c: fix possible access of uninit. mem
* mslookup_client_mdns_test: disable by default
* mslookup_client_mdns_test: no automatic skip
* Cosmetic: mention OS#4491 in location cancel code
* hlr_vty_subscr: prettier output for last LU seen
* contrib: import RPM spec
* contrib: integrate RPM spec
* Makefile.am: EXTRA_DIST: debian, contrib/*.spec.in
* contrib/jenkins: don't build osmo-gsm-manuals
* configure.ac: set -std=gnu11
[ Neels Hofmeyr ]
* add osmo-mslookup-client program
* add osmo-mslookup-client program (#2)
* fix missing braces in LOGP_GSUP_FWD
* gsup_client.c: fix deprecation for client create func
* 1/2: refactor: add and use lu_fsm, osmo_gsup_req, osmo_ipa_name
* 2/2: wrap ipa_name in osmo_cni_peer_id with type enum and union
* gsup client: add up_down_cb(), add osmo_gsup_client_create3()
* db v5: prep for D-GSM: add vlr_via_proxy and sgsn_via_proxy
* enlarge the GSUP message headroom
* test_nodes.vty: remove cruft
* D-GSM 1/n: add mslookup server in osmo-hlr
* D-GSM 2/n: implement mDNS method of mslookup server
* D-GSM 3/n: implement roaming by mslookup in osmo-hlr
* gsup_server: send routing error back to the correct peer
* adoc: add D-GSM chapter to osmohlr-usermanual
* drop error log for when a subscriber does not exist
* vty: show subscriber: change format of 'last LU seen'
* vty: show subscriber: show lu d,h,m,s ago, not just seconds
* auc3g: officially wrap IND around IND_bitlen space
* make osmo_cni_peer_id_cmp() NULL safe
* osmo_gsup_req_new(): require from_peer != NULL
* gsup_server.c: properly handle negative rc from osmo_gsup_conn_ccm_get()
* osmo_mslookup_server_mdns_rx(): handle read() rc == 0
* hlr_subscr_nam(): fix condition to fix nam=false notifications
* esme_dgsm.py: add --always-fail option for debugging SMPP
* osmo-mslookup-client: fix segfault for respond_error() caller
* manual: describe subscriber import by SQL
[ Harald Welte ]
* Revert "add osmo-mslookup-client program"
* Revert "add mDNS lookup method to libosmo-mslookup"
* Use OSMO_FD_* instead of deprecated BSC_FD_*
* support the XOR algorithm for UMTS AKA
* auc_test.c: Add some comments on what the test cases actually do
* main: add --vty-ref-mode, use vty_dump_xml_ref_mode()
* manuals: generate vty reference xml at build time
[ Vadim Yanitskiy ]
* db: fix possible SQLite3 allocated memory leak in db_open()
* gsup_server: fix typo: s/omso_gsup_message/osmo_gsup_message/
* debian/control: change maintainer to the Osmocom team / mailing list
* cosmetic: fix spelling in logging message: existAnt -> existEnt
* doc/manuals: fix s/There/The/ in 'USSD Configuration'
* doc/manuals: re-organize description of internal USSD handlers
* USSD: fix handle_ussd(): do not free() unconditionally
* USSD: add special 'idle' handler to IUSE for testing
[ Eric ]
* configure.ac: fix libtool issue with clang and sanitizer
[ Philipp Maier ]
* doc: do not use loglevel info for log category ss
[ Pau Espin Pedrol ]
* configure.ac: Fix trailing whitespace
* doc: Update VTY reference xml file
* Support setting rt-prio and cpu-affinity mask through VTY
* Set TCP NODELAY sockopt to GSUP cli and srv connections
* contrib/jenkins: Enable parallel make in make distcheck
* .gitignore: Ignore new autofoo tmp files
* tests: Replace deprecated API log_set_print_filename
[ Keith ]
* osmo-hlr-db-tool: Make import from osmo-nitb less "lossy"
* Correct vty inline help for show subscriber
* Add vty command to show summary of all or filtered subscribers
* Fix Coverity Warnings
-- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 23 Feb 2021 18:13:53 +0100
osmo-hlr (1.2.0) unstable; urgency=medium
[ Ruben Undheim ]
* Fix test for return codes on mipsel and alpha archs
[ Thorsten Alteholz ]
* fix spelling errors detected by lintian
[ Pau Espin Pedrol ]
* tests: Fix db_test err file to expect error code name instead of value
[ Oliver Smith ]
* tests/test_nodes.vty: check less libosmocore cmds
* tests/db_upgrade: disable for old sqlite versions
* gitignore: add tests/db_upgrade/*.dump
* gsup_client.h: fix license header: GPLv2+
* tests/auc: change back to python3
[ Neels Hofmeyr ]
* fix double free in osmo_gsup_client_enc_send()
* db upgrade to v2: log version 2, not 1
* fix upgrade to version 2: imei column default value
* add --db-check option
* hlr.sql: move comment
* add db_upgrade test
* hlr db schema 3: hlr_number -> msc_number
* db.c: code dup: add db_run_statements() for arrays of statements
* move headers to include/osmocom/hlr
* fix upgrade test in presence of ~/.sqliterc
* db upgrade: remove some code dup
* add osmo_gsup_msgb_alloc()
* Makefile convenience: add VTY_TEST var to run only one test
* remove gsup_test
* test_nodes.vty: tweak: add some '?' checks
* db v4: add column last_lu_seen_ps
[ Harald Welte ]
* AUC: Add support for setting the AMF separation bit to '1' for EUTRAN
* hlr: exit(2) on unsupported positional arguments on command line
-- Pau Espin Pedrol <pespin@sysmocom.de> Fri, 03 Jan 2020 12:37:35 +0100
osmo-hlr (1.1.0) unstable; urgency=medium
[ Oliver Smith ]
* docs: running: document --db-upgrade
* Add IMEI column to subscriber table
* Optionally store IMEI in subscriber table
* VTY tests: fill DB before running test
* VTY: integrate IMEI
* hlr.c: replace deprecated osmo_gsup_get_err_msg_type()
* hlr.c: move hlr_ctx to the top
* tests: use -no-install libtool flag to avoid ./lt-* scripts
* Cosmetic: gsup_route_find: comment addr, addrlen
* USSD: save MO USSD's originating MSC's vlr_number
* USSD: don't use gsm0480_msgb_alloc_name()
* hlr.c: forward GSUP messages between clients
* db_hlr.c: db_subscr_create(): add flags argument
* db_hlr.c: add db_subscr_exists_by_imsi()
* Create subscribers on demand
* Document subscribers create on demand feature
* debian: create -doc subpackage with pdf manuals
* db_test: set timezone to work around mktime bug
* db_hlr: zero-initialize "struct tm"
* rx_check_imei_req(): fix IMEI bounds checking
* contrib/jenkins.sh: run "make maintainer-clean"
* VTY: add subscriber update network-access-mode
* manuals: improve subscribers create on demand
* gitignore: ignore everything generated in db_test
* db_auc.c: verify hex key sizes read from DB
[ Max ]
* Log ip:port when adding GSUP routes
* Add link to project wiki to .service file
* Enable statsd support
[ Vadim Yanitskiy ]
* hlr.c: properly terminate the process on SIGTERM
* hlr.c: fix: also store the session state in read_cb_forward()
* hlr.c: fix: properly print the original message type in read_cb_forward()
* hlr.c: check the presence of msgb->l2h in read_cb()
* hlr.c: fix possible msgb memleaks in read_cb()
* db_hlr.c: add db_subscr_exists_by_msisdn()
* src/db.h: use GSM23003_MSISDN_MAX_DIGITS for MSISDN buffer size
* src/hlr.c: fix deprecation warning: use gsm48_decode_bcd_number2()
* hlr_ussd.c: fix: properly pass invokeID in handle_ussd_own_msisdn()
* hlr_ussd.c: rx_proc_ss_req(): fix NULL pointer dereference
* build: fix mess with 'db_test_SOURCES' and 'db_test_LDADD'
* tests/db_test: close the database when test is finished
* src/db.c: integrate SQLite3 with talloc allocator
[ Neels Hofmeyr ]
* USSD: fix routing to multiple MSC
* fix error logging for GSUP route
* add missing error log: invalid IMSI
* osmo-hlr: allow configuring db path from cfg file
* use new OSMO_IMSI_BUF_SIZE
[ Daniel Willmann ]
* manuals: Add script to update vty/counter documentation from docker
* manuals: Update vty documentation
[ Pau Espin Pedrol ]
* Remove undefined param passed to logging_vty_add_cmds
* configure.ac: Require libosmocore 1.2.0
-- Pau Espin Pedrol <pespin@sysmocom.de> Wed, 07 Aug 2019 16:14:23 +0200
osmo-hlr (1.0.0) unstable; urgency=medium
[ Stefan Sperling ]
* move creation of insert subscriber data messages to a common function
[ Harald Welte ]
* Return proper GSUP error in case of too short IMSI
* disable blind subscriber insertion into every VLR/SGSN
* gsup_server: Add "priv" pointer and make it point to 'struct hlr'
* move osmo_gsup_addr_send() declaration from luop.h to gsup_router.h
* gsup_router: Use "#pragma once" and add missing #includes
* Add "show gsup-connections" VTY command
* import gsup_client.c as new libosmo-gsup-client
* gsup_client: rename gsup_client_* to osmo_gsup_client_*
* GSUP: Log GSUP route add/remove
* hlr: Export + Declare global g_hlr symbol
* USSD: Add Core USSD handling + VTY routing config to HLR
* USSD: Add basic dispatch + decode of GSUP-encapsulated SS/USSD
* hlr_ussd: Introduce LOGPSS() macro
* USSD: Send ReturnError component if USSD Code unknown / EUSE disconnected
* USSD: Further unification of log output; Use LOGPSS when possible
* osmo-hlr.cfg: Don't enable DEBUG logging by default
* USSD: Add new "DSS" logging category and use it appropriately
* USSD: fix null-pointer deref in "default-route" vty/config cmd
* Add osmo-euse-demo as minimalistic test of a External USSD (EUSE) handler
* USSD: Add support for internal USSD handlers
* debian: Add sub-package for libosmo-gsup-client
* pkg-config: Fix libosmo-gsup-client pkg-config file
* gitignore: Add .tarball-version
* debian: Make libosmo-gsup-client-dev depend on libosmo-gsup-client0
* USSD: Fix "ussd default-route"
* libosmo-gsup-client: License is GPLv2-or-later
* osmo-hlr.cfg: Ensure well-formed config file example
* test_nodes.vty: Since libosmocore 1.0.0, we only have one space
[ Martin Hauke ]
* sql/Makefile.am: Make docsdir completely configurable
* debian: Fix typo in package description
[ Pau Espin Pedrol ]
* debian: Avoid installing duplicate cfg file in /etc
* sql/Makefile: Install hlr_data.sql as example together with hlr.sql
* sql/Makefile: Install sql files under doc/.../sql subdir
* sql/Makefile: Create empty /var/lib/osmocom directory at install time
* Install systemd services with autotools
* Move doc/Makefile.am to doc/examples/Makefile.am
* Install sample cfg file to /etc/osmocom
[ Vadim Yanitskiy ]
* hlr.c: move deinitialization code from SIGINT handler
* hlr.c: free root talloc context on exit
* hlr.c: track the use of talloc NULL memory contexts
* src/db.c: fix: make sure the database is properly closed
* src/db.c: don't ignore the result of db_bootstrap()
* hlr_vty_subscr.c: fix subscriber creation command help
* Update .gitignore: add missing build products
* tests/Makefile.am: also remove temporary sqlite files
* hlr_ussd.h: add #pragma once include guard
* hlr_ussd.h: use proper libc headers
* Update .gitignore: ignore osmo-euse-demo
* hlr_ussd.h: drop meaningless forward declaration
* USSD/hlr_vty.c: print error if EUSE is not found
* hlr_ussd.c: fix: properly print a EUSE / IUSE name
* hlr_ussd.c: avoid using CR and NL in IUSE responses
[ Neels Hofmeyr ]
* fix build: adjust test_nodes.vty to logging change
* tweak example config
* make: always allow running python tests manually
-- Harald Welte <laforge@gnumonks.org> Sun, 20 Jan 2019 19:29:58 +0100
osmo-hlr (0.2.1) unstable; urgency=medium
[ Neels Hofmeyr ]

44
debian/control vendored
View File

@@ -1,18 +1,19 @@
Source: osmo-hlr
Section: net
Priority: optional
Maintainer: Max Suraev <msuraev@sysmocom.de>
Maintainer: Osmocom team <openbsc@lists.osmocom.org>
Build-Depends: debhelper (>= 9),
pkg-config,
dh-autoreconf,
dh-systemd (>= 1.5),
autotools-dev,
python-minimal,
libosmocore-dev,
libosmo-abis-dev,
libosmo-netif-dev,
python3-minimal,
libosmocore-dev (>= 1.5.0),
libosmo-abis-dev (>= 1.1.0),
libosmo-netif-dev (>= 1.1.0),
libsqlite3-dev,
sqlite3
sqlite3,
osmo-gsm-manuals-dev (>= 1.1.0)
Standards-Version: 3.9.6
Vcs-Browser: http://cgit.osmocom.org/osmo-hlr
Vcs-Git: git://git.osmocom.org/osmo-hlr
@@ -57,3 +58,34 @@ Description: Development headers of Osmocom GSUP client library
and External USSD Entities (EUSEs) using this library to implement clients.
.
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
Priority: optional
Depends: ${misc:Depends}
Description: ${misc:Package} PDF documentation
Various manuals: user manual, VTY reference manual and/or
protocol/interface manuals.

5
debian/libosmo-mslookup-dev.install vendored Normal file
View File

@@ -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

1
debian/libosmo-mslookup0.install vendored Normal file
View File

@@ -0,0 +1 @@
usr/lib/*/libosmo-mslookup*.so.*

1
debian/osmo-hlr-doc.install vendored Normal file
View File

@@ -0,0 +1 @@
usr/share/doc/osmo-hlr-doc/*.pdf

View File

@@ -5,4 +5,5 @@
/usr/share/doc/osmo-hlr/sql/hlr.sql
/usr/share/doc/osmo-hlr/sql/hlr_data.sql
/usr/share/doc/osmo-hlr/examples/osmo-hlr.cfg
/usr/share/osmocom/osmo-hlr-post-upgrade.sh
/var/lib/osmocom

5
debian/postinst vendored Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/sh -e
# Debian's postinst script is called on both installation and upgrade. Call the
# post-upgrade script in both cases, it won't do anything if there is nothing
# to do.
/usr/share/osmocom/osmo-hlr-post-upgrade.sh

6
debian/rules vendored
View File

@@ -17,4 +17,8 @@ override_dh_auto_test:
dh_auto_test || (find . -name testsuite.log -exec cat {} \; ; false)
override_dh_auto_configure:
dh_auto_configure -- --with-systemdsystemunitdir=/lib/systemd/system
dh_auto_configure -- --with-systemdsystemunitdir=/lib/systemd/system --enable-manuals
# Don't create .pdf.gz files (barely saves space and they can't be opened directly by most pdf readers)
override_dh_compress:
dh_compress -X.pdf

View File

@@ -0,0 +1,22 @@
# OsmoHLR example configuration for Distributed GSM (mslookup)
hlr
gsup
# For D-GSM roaming, osmo-hlr's GSUP must listen on a public IP:
bind ip 10.9.8.7
# Each HLR should identify with a distinct name
ipa-name hlr-23
mslookup
# Bind mslookup mDNS server and client on default multicast address and port:
# 239.192.23.42 port 4266
mdns bind
# Tell the mslookup server in osmo-hlr which IP+ports to return when a
# remote voice call or SMS wants to deliver to a local subscriber:
server
# local osmo-sip-connector SIP port
service sip.voice at 10.9.8.7 5060
# local osmo-msc SMPP port
service smpp.sms at 10.9.8.7 2775
# experimental: SMS-over-GSUP: this HLR's GSUP port
service gsup.sms at 10.9.8.7 4222
# only required if different from above 'gsup'/'bind ip':
#service gsup.hlr at 10.9.8.7 4222

View File

@@ -12,7 +12,7 @@ log stderr
logging level main notice
logging level db notice
logging level auc notice
logging level ss info
logging level ss notice
logging level linp error
!
line vty

View File

@@ -4,6 +4,7 @@ EXTRA_DIST = example_subscriber_add_update_delete.vty \
osmohlr-usermanual.adoc \
osmohlr-usermanual-docinfo.xml \
osmohlr-vty-reference.xml \
regen_doc.sh \
chapters \
vty
@@ -13,8 +14,15 @@ if BUILD_MANUALS
include $(OSMO_GSM_MANUALS_DIR)/build/Makefile.asciidoc.inc
VTY_REFERENCE = osmohlr-vty-reference.xml
BUILT_REFERENCE_XML = $(builddir)/vty/hlr_vty_reference.xml
$(builddir)/vty/hlr_vty_reference.xml: $(top_builddir)/src/osmo-hlr
mkdir -p $(builddir)/vty
$(top_builddir)/src/osmo-hlr --vty-ref-xml > $@
include $(OSMO_GSM_MANUALS_DIR)/build/Makefile.vty-reference.inc
OSMO_REPOSITORY = osmo-hlr
include $(OSMO_GSM_MANUALS_DIR)/build/Makefile.common.inc
endif

View File

@@ -0,0 +1,491 @@
== Distributed GSM / Multicast MS Lookup
Distributed GSM (D-GSM) allows independent mobile core network stacks to provide voice, SMS and Roaming services to each
other, without the need for centralised entities or administration authority, and in a way that is resilient against
unstable network links between sites.
D-GSM aims at communal networks, where several independent sites, let's call them villages, each have a full mobile core
network infrastructure. It elegantly provides ad-hoc service for subscribers moving across villages, and allows villages
to dynamically join or leave the cooperative network without the need for configuration changes at other sites.
A challenge for linking separate sites is to find the current location of a subscriber. Typically, in mobile networks, a
centralized entity keeps track of where to Page for subscribers. Running several fully independent sites with unreliable
links between them makes it hard to provide such centralisation.
D-GSM finds subscribers by mslookup, a service provided by OsmoHLR, typically using multicast DNS queries. This allows
routing Location Updating requests, calls, and SMS to the right site without administrative delay nor the need for a
reliable link to a central database.
D-GSM is highly resilient against single sites or links becoming temporarily unavailable. Service between still
reachable sites simply continues; Service to a disconnected site resumes as soon as it becomes reachable again.
This brings an entirely new paradigm to mobile core network infrastructure: as sites become reachable on the IP network
and join the common IP multicast group, services between them become available immediately. Basically, the only premise
is that IP routing and multicast works across sites, and that each site uses unique IPA names in the GSUP config.
This chapter describes how D-GSM and mslookup work, and how to configure sites to use D-GSM, using Osmocom core network
infrastructure.
=== Finding Subscribers: mslookup Clients
There are two fundamentally distinct subscriber lookups provided by the mslookup service.
==== Find the Current Location of an MSISDN
[[fig_dgsm_connect]]
.mslookup for connecting subscribers: Alice is visiting village C; a phone call gets routed directly to her current location independently from her resident village infrastructure
[graphviz]
----
digraph G {
rankdir=LR
subgraph cluster_village_b {
label="Village B"
ms_bob [label="Bob\n(from village B)",shape=box]
pbx_b [label="SIP B"]
}
subgraph cluster_village_c {
label="Village C"
ms_alice [label="Alice\n(from village A)",shape=box]
msc_c [label="MSC C"]
hlr_c [label="HLR C"]
sip_c [label="SIP C"]
}
ms_alice -> msc_c [style=dashed,arrowhead=none]
msc_c -> hlr_c [label="attached",style=dashed,arrowhead=none]
ms_bob -> pbx_b [label="call Alice"]
pbx_b -> hlr_c [label="mslookup by MSISDN",style=dotted,dir=both]
pbx_b -> sip_c -> msc_c -> ms_alice [label="call"]
}
----
For example, if a subscriber is currently visiting another village, establish a phone call / send SMS towards that
village.
- To deliver a phone call, a SIP agent integrates an mslookup client to request the SIP service of an MSISDN's current
location (example: <<dgsm_conf_dialplan>>). It receives an IP address and port to send the SIP Invite to.
- To deliver an SMS, an ESME integrates an mslookup client to request the SMPP service of an MSISDN's current location
(example: <<dgsm_conf_esme_smpp>>).
The current location of a subscriber may change at any time, and, when moving across locations, a subscriber may
suddenly lose reception to the previous location without explicitly detaching. Hence an mslookup request for the current
location of an MSISDN may get numerous responses. To find the currently valid location, mslookup includes the age of the
subscriber record, i.e. how long ago the subscriber was last reached. The one response with the youngest age reflects
the current location.
In order to evaluate several responses, mslookup always waits for a fixed amount of time (1 second), and then evaluates
the available responses.
Services are not limited to SIP and SMPP, arbitrarily named services can be added to the mslookup configuration.
.Message sequence for locating an MSISDN to deliver a voice call
["mscgen"]
----
msc {
hscale="2";
moms[label="MS,BSS\nvillage A"],momsc[label="MSC,MGW\nvillage A"],mosipcon[label="osmo-sip-connector\nvillage A"],mopbx[label="PBX\nvillage A"],mthlr[label="OsmoHLR\nvillage B"],mtsipcon[label="osmo-sip-connector\nvillage B"],mtmsc[label="MGW,MSC\nvillage B"],mtms[label="RAN,MS\nvillage B"];
moms =>> momsc [label="CC Setup"];
momsc =>> mosipcon [label="MNCC_SETUP_IND"];
mosipcon =>> mopbx [label="SIP INVITE"];
mopbx rbox mopbx [label="dialplan: launch mslookup by MSISDN"];
--- [label="multicast-DNS query to all connected sites"];
...;
mopbx <<= mthlr [label="mDNS response\n(age)"];
mopbx rbox mopbx [label="wait ~ 1s for more mDNS responses"];
...;
mopbx =>> mtsipcon [label="SIP INVITE (MT)"];
mtmsc <<= mtsipcon [label="MNCC_SETUP_REQ"];
mtms <<= mtmsc [label="Paging (CC)"];
moms rbox mtms [label="voice call commences"];
}
----
==== Find the Home HLR for an IMSI
[[fig_dgsm_roaming]]
.mslookup for Roaming: Alice visits village B; she can attach to the local mobile network, which proxies HLR administration to her home village.
[graphviz]
----
digraph G {
rankdir=LR
subgraph cluster_village_b {
label="Village B"
ms_alice [label="Alice\n(from village A)",shape=box]
msc_b [label="MSC B"]
hlr_b [label="HLR B"]
}
subgraph cluster_village_a {
label="Village A"
hlr_alice [label="Alice's home HLR"]
}
ms_alice -> msc_b -> hlr_b [label="Location\nUpdating"]
hlr_b -> hlr_alice [label="mslookup by IMSI",style=dotted,dir=both]
hlr_b -> hlr_alice [label="GSUP proxy forwarding"]
}
----
For example, when attaching to a local network, a local resident gets serviced directly by the local village's HLR,
while a visitor from another village gets serviced by the remote village's HLR (Roaming).
A home HLR typically stays the same for a given IMSI. If the home site is reachable, there should be exactly one
response to an mslookup request asking for it. The age of such a home-HLR response is always sent as zero.
If a response's age is zero, mslookup does not wait for further responses and immediately uses the result.
If there were more than one HLR accepting service for an IMSI, the one with the shortest response latency is used.
=== mslookup Configuration
OsmoHLR the main mslookup agent. It provides the responses for both current location services as well as for locating
the fixed home-HLR. But naturally, depending on the mslookup request's purpose, different OsmoHLR instances will respond
for a given subscriber.
- When querying the home HLR, it is always the (typically single) home HLR instance that sends the mslookup response. As
soon as it finds the queried IMSI in the local HLR database, an OsmoHLR will respond to home-HLR requests.
In <<fig_dgsm_roaming>>, Alice's home HLR responds to the Roaming request ("where is the home HLR?").
- When querying the location of an MSISDN, it is always the HLR proxy nearest to the servicing MSC that sends the
mslookup response. Even though the home HLR keeps the Location Updating record also for Roaming cases, it will only
respond to an mslookup service request if the subscriber has attached at a directly connected MSC. If attached at a
remote MSC, that MSC's remote HLR will be the GSUP proxy for the home HLR, and the remote HLR is responsible for
responding to service requests.
In <<fig_dgsm_roaming>>, HLR B is the nearest proxy and will answer all service requests ("where is this MSISDN?").
Alice's home HLR will not answer service requests, because it detects that the servicing MSC is connected via another
HLR proxy.
[[dgsm_example_config]]
==== Example
Here is an osmo-hlr.cfg mslookup configuration example for one site, which is explained in subsequent chapters.
hlr
gsup
bind ip 10.9.8.7
ipa-name hlr-23
mslookup
mdns bind
server
service sip.voice at 10.9.8.7 5060
service smpp.sms at 10.9.8.7 2775
OsmoHLR has both an mslookup server and a client.
- The server responds to incoming service and home-HLR requests, when the local HLR is responsible.
- The client is used as GSUP proxy to a remote home HLR (found by mslookup upon a locally unknown IMSI).
- The client may also be used for forwarding SMS-over-GSUP.
The mslookup service can be implemented by various methods.
At the time of writing, the only method implemented is mDNS.
==== mDNS
The stock mslookup method is mDNS, multicast DNS. It consists of standard DNS encoding according to <<ietf-rfc1035>> and
<<ietf-rfc3596>>, but sent and received on IP multicast. In the response, standard A and AAAA records return the
service's IP address, while additional TXT records provide the service's port number and the MS attach age.
TIP: To watch D-GSM mDNS conversations in wireshark, select "udp.port == 4266" (the default mslookup mDNS port
number), right click on the packet to "Decode as...", and select "DNS".
In OsmoHLR, the mDNS server and client are typically both enabled at the same time:
mslookup
mdns bind
Server and client can also be enabled/disabled individually:
mslookup
server
mdns bind
client
mdns bind
These examples use the default mslookup multicast IP address and port. It is possible to configure custom IP address and
port, but beware that the IP address must be from a multicast range, see <<ietf-rfc5771>>:
mslookup
mdns bind 239.192.23.42 4266
Domain names generated from mslookup queries (e.g. "sip.voice.123.msisdn") should not collide with IANA permitted
domains. Therefore we add the "mdns.osmocom.org" suffix. It can be overridden as follows:
mslookup
mdns domain-suffix mdns.osmocom.org
==== Server: Site Services
The mslookup server requires a list of service addresses provided at the local site, in order to respond to service
requests matching locally attached subscribers.
mslookup
server
service sip.voice at 10.9.8.7 5060
service smpp.sms at 10.9.8.7 2775
In this example:
- "10.9.8.7 5060" are the IP address and port on which the local site's osmo-sip-connector is bound to receive SIP
Invite requests.
- "10.9.8.7 2775" are the local site's OsmoMSC SMPP bind address and port.
Obviously, these IP addresses must be routable back to this site from all other sites. Using link-local or "ANY"
addresses, like 127.0.0.1 or 0.0.0.0, will not work here. Instead, each service config requires a public IP address that
all remote requestors are able to reach (not necessarily on the host that osmo-hlr is running on).
If a site has more than one MSC, services can also be configured for each MSC individually, keyed by the IPA unit name
that each MSC sends on the GSUP link:
mslookup
server
msc ipa-name msc-262-42-0
service sip.voice at 10.11.12.13 5060
service smpp.sms at 10.11.12.13 2775
msc ipa-name msc-901-70-0
service sip.voice at 10.9.8.7 5060
service smpp.sms at 10.9.8.7 2775
Here, "msc-262-42-0" is the IPA name of a local OsmoMSC instance. To configure an OsmoMSC's IPA name on the GSUP link,
see osmo-msc.cfg, setting `hlr` / `ipa-name`.
For mslookup service responses, only Location Updatings in the Circuit Switched domain are relevant. OsmoHLR does manage
IMSIs attaching in the Packet Switched domain (via an SGSN) similarly to Circuit Switched (via an MSC), but mslookup
completely ignores the Packet Switched attach status.
==== Server: Own GSUP Address
When responding to home-HLR requests, OsmoHLR implicitly by default responds with its locally configured GSUP bind
address (setting `hlr` / `gsup` / `bind ip`). If required, an explicit local GSUP address and port can be configured,
for example:
hlr
gsup
bind ip 0.0.0.0
ipa-name hlr-23
mslookup
server
# osmo-hlr's own GSUP address to send in mslookup responses:
service gsup.hlr at 10.9.8.7 4222
The gsup.hlr service can only be configured globally (because requests come from arbitrary mDNS clients, before a
Location Updating has associated the IMSI with the requesting MSC).
==== Client IPA Naming
For reliable GSUP proxy routing to a remote HLR (Roaming), it is important that each GSUP client, i.e. each HLR, MSC and
SGSN instance, has a unique IPA name.
Example for configuring an OsmoHLR instance's IPA name:
hlr
gsup
ipa-name hlr-23
Here, "hlr-23" is the unique identification of this OsmoHLR instance across all potentially connected D-GSM sites.
Furthermore, each MSC and SGSN must have a uniquely distinct IPA name across all sites (here "msc-262-42-0" and
"msc-901-70-0" are used as example IPA names for local MSCs).
When this OsmoHLR connects to a remote HLR, be it for GSUP proxying or SMS-over-GSUP, it communicates its own IPA name
(on GSUP link-up) as well as the IPA name of the requesting client MSC/SGSN (as Source Name in each message) to the
remote OsmoHLR GSUP server. These names are used to route GSUP responses back to the respective requesting peer.
If two MSCs were accidentally configured with identical names, a problem will occur as soon as both MSCs attempt to
attach to the same OsmoHLR (either directly or via GSUP proxying). The MSC that shows up first will work normally, but
any duplicate that shows up later will be rejected, since a route for its name already exists.
=== Queries
In URL notation, typical mslookup queries look like:
gsup.hlr.123456789.imsi
sip.voice.123.msisdn
smpp.sms.123.msisdn
A query consists of
- a service name ("gsup.hlr"),
- an id ("123456789"),
- the id type ("imsi").
The calling client also defines a timeout to wait for responses.
The mslookup ID types are fixed, while service names can be chosen arbitrarily.
.mslookup ID types, no other ID types are understood by mslookup
[options="header",width="100%",cols="20%,80%"]
|===
|ID Type|Description
|imsi|An IMSI as existing in an OsmoHLR subscriber database
|msisdn|A phone number as configured in an OsmoHLR subscriber database
|===
.mslookup service name conventions, arbitrary service names can be added as required
[options="header",width="100%",cols="20%,20%,60%"]
|===
|Service Name|Protocol|Description
|gsup.hlr | GSUP | Home HLR's GSUP server, to handle Location Updating related procedures
|sip.voice | SIP | OsmoSIPConnector, to receive a SIP Invite (MT side of a call)
|smpp.sms | SMPP | Destination OsmoMSC (or other SMPP server) to deliver an SMS to the recipient
|gsup.sms | GSUP | GSUP peer to deliver an SMS to the recipient using SMS-over-GSUP
|===
Arbitrarily named services can be added to the mslookup configuration and queried by mslookup clients; as soon as a
service name is present in osmo-hlr.cfg, it can be queried from any mslookup client.
Service names should consist of a protocol name (like "sip", "gsup", "english") and an intended action/entity (like
"voice", "hlr", "greeting").
=== Service Client Implementation
In principle, arbitrary services could query target addresses via mslookup, leaving it up to any and all kinds of
clients to find their respective destination addresses. But of course, mslookup was designed with specific services in
mind, namely:
- SIP call agents and
- SMS delivery (an ESME or SMSC)
The following chapters describe examples of setting up a working distributed core network providing SIP voice calls and
SMS forwarding across sites.
==== mslookup Library
The OsmoHLR provides an mslookup client C library, libosmo-mslookup. Service lookups can be integrated directly
in client programs using this library. However, its mDNS implementation requires the libosmocore select() loop, which
can be challenging to integrate in practice. An alternative solution is the osmo-mslookup-client tool.
[[dgsm_osmo_mslookup_client]]
==== osmo-mslookup-client
The mslookup C library is available, but often, a simpler approach for client implementations is desirable:
- When querying for a service address, the client is typically interested in the single final best result (youngest age
/ first responding home HLR).
- Voice call and SMS clients typically would block until an mslookup result is known. For example, the FreeSwitch
dialplan integration expects a result synchronously, i.e. without waiting for mslookup responses via a select() loop.
- Integrating the libosmocore select() loop required for mDNS can break the already existing socket handling in the
client program.
The osmo-mslookup-client cmdline tool provides a trivial way to synchronously acquire the single result for an mslookup
request. The service client can invoke an osmo-mslookup-client process per request and read the result from stdout.
Each invocation obviously spawns a separate process and opens a multicast socket for mDNS. For better scalability,
osmo-mslookup-client can also be run as a daemon, providing results via a unix domain socket. Using synchronous write()
and recv() allows blocking until a result is received without interfering with the client program's select() setup.
By itself, osmo-mslookup-client is also helpful as a diagnostic tool:
----
$ osmo-mslookup-client sip.voice.1001.msisdn
sip.voice.1001.msisdn ok 10.9.8.7 5060
$ osmo-mslookup-client gsup.hlr.901700000014701.imsi
gsup.hlr.901700000014701.imsi ok 10.9.8.7 4222
$ osmo-mslookup-client gsup.hlr.111111.imsi
gsup.hlr.111111.imsi not-found
$ osmo-mslookup-client gsup.hlr.1001.msisdn sip.voice.1001.msisdn smpp.sms.1001.msisdn foo.1001.msisdn
gsup.hlr.1001.msisdn ok 10.9.8.7 4222
foo.1001.msisdn not-found
smpp.sms.1001.msisdn ok 10.9.8.7 2775
sip.voice.1001.msisdn ok 10.9.8.7 5060
$ osmo-mslookup-client --csv-headers gsup.hlr.901700000014701.imsi
QUERY RESULT V4_IP V4_PORT V6_IP V6_PORT
gsup.hlr.901700000014701.imsi ok 10.9.8.7 4222
$ osmo-mslookup-client -f json gsup.hlr.901700000014701.imsi
{"query": "gsup.hlr.901700000014701.imsi", "result": "ok", "v4": ["10.9.8.7", "4222"]}
----
For full help including example client invocations in Python, see the output of:
osmo-mslookup-client -h
==== SIP Service Client
[[dgsm_conf_dialplan]]
===== FreeSwitch dialplan.py
The FreeSWITCH PBX software <<freeswitch_pbx>> offers a Python integration to determine a SIP call recipient by a custom
dialplan implementation. An example dialplan implementation for FreeSWITCH that uses D-GSM mslookup is provided in the
osmo-hlr source tree under `contrib`, called `freeswitch_dialplan_dgsm.py`.
To integrate it with your FREESWITCH setup, add a new `extension` block to your `dialplan/public.xml`:
----
<extension name="outbound">
<condition field="destination_number" expression=".*">
<action application="set" data="hangup_after_bridge=true"/>
<action application="set" data="session_in_hangup_hook=true"/>
<action application="set" data="ringback=%(2000, 4000, 440.0, 480.0)"/>
<action application="python" data="freeswitch_dialplan_dgsm"/>
</condition>
</extension>
----
Make sure that the dir containing `freeswitch_dialplan_dgsm.py` is in your `PYTHONPATH` environment variable, and start
the server:
----
$ export PYTHONPATH="$PYTHONPATH:/home/user/code/osmo-hlr/contrib/dgsm"
$ freeswitch -nf -nonat -nonatmap -nocal -nort -c
----
==== SMS Service Client
[[dgsm_conf_esme_smpp]]
===== SMS via SMPP Port
An example ESME using D-GSM mslookup, `esme_dgsm.py`, is provided in the osmo-hlr source tree under `contrib`. It
attaches to OsmoMSC's SMPP port to send SMS to recipients determined by mslookup.
OsmoMSC should be configured as "smpp-first", so that all SMS routing is determined by mslookup. If configured without
smpp-first, OsmoMSC may try to deliver an SMS locally, even though the recipient has recently moved to a different site.
An example OsmoMSC configuration to work with esme_dgsm.py:
----
smpp
local-tcp-ip 127.0.0.1 2775
system-id test-msc
policy closed
smpp-first
# outgoing to esme_dgsm.py
esme OSMPP
no alert-notifications
password foo
default-route
# incoming from esme_dgsm.py
esme ISMPP
no alert-notifications
password foo
----
Launch esme_dgsm.py alongside OsmoMSC:
----
./esme_dgsm.py --src-host 127.0.0.1
----
esme_dgsm.py will be notified via SMPP for each SMS to be delivered, and will forward them either to a remote
recipient, or back to the same OsmoMSC, depending on the mslookup result. If the MSISDN is not reachable (or
esme_dgsm.py can't handle the message for other reasons), it returns the RSYSERR code back to OsmoMSC.
Note that the esme_dgsm.py is a proof of concept and should not be used in production. It has several limitations, such
as not supporting multipart SMS messages.
===== SMS-Over-GSUP
The GSUP protocol defines SMS delivery messages. When OsmoMSC is configured to deliver SMS via GSUP, MO SMS are directly
forwarded to the HLR, which will determine where to forward the SMS-over-GSUP messages using its mslookup client.
FIXME implement this

View File

@@ -5,24 +5,27 @@ arguments:
=== SYNOPSIS
*osmo-hlr* [-h|-V] [-d 'DBGMASK'] [-D] [-c 'CONFIGFILE'] [-s] [-T] [-e 'LOGLEVEL'] [-l 'DATABASE']
*osmo-hlr* [-h] [-c 'CONFIGFILE'] [-l 'DATABASE'] [-d 'DBGMASK'] [-D] [-s] [-T] [-e 'LOGLEVEL'] [-U] [-V]
=== OPTIONS
// Keep the order the same as in osmo-hlr --help!
*-h, --help*::
Print a short help message about the supported options
*-V, --version*::
Print the compile-time version number of the OsmoBTS program
*-c, --config-file 'CONFIGFILE'*::
Specify the file and path name of the configuration file to be
used. If none is specified, use `osmo-hlr.cfg` in the current
working directory.
*-l, --database 'DATABASE'*::
Specify the file name of the SQLite3 database to use as HLR/AUC
storage
*-d, --debug 'DBGMASK','DBGLEVELS'*::
Set the log subsystems and levels for logging to stderr. This
has mostly been superseded by VTY-based logging configuration,
see <<logging>> for further information.
*-D, --daemonize*::
Fork the process as a daemon into background.
*-c, --config-file 'CONFIGFILE'*::
Specify the file and path name of the configuration file to be
used. If none is specified, use `osmo-hlr.cfg` in the current
working directory.
*-s, --disable-color*::
Disable colors for logging to stderr. This has mostly been
deprecated by VTY based logging configuration, see <<logging>>
@@ -35,9 +38,13 @@ arguments:
Set the global log level for logging to stderr. This has mostly
been deprecated by VTY based logging configuration, see
<<logging>> for more information.
*-l, --database 'DATABASE'*::
Specify the file name of the SQLite3 database to use as HLR/AUC
storage
*-U, --db-upgrade*::
Allow HLR database schema upgrades. If OsmoHLR was updated and
requires a newer database schema, it will refuse to start unless
this option is specified. The updated database can not be
downgraded, make backups as necessary.
*-V, --version*::
Print the compile-time version number of the OsmoHLR program
=== Bootstrap the Database
@@ -47,7 +54,7 @@ this database file will be created in the current working directory.
Alternatively, you may use the `osmo-hlr-db-tool`, which is installed along
with `osmo-hlr`, to bootstrap an empty database, or to migrate subscriber data
from an old 'OsmoNITB' database. See `osmo-hlr-db-tool --help`.
from an old 'OsmoNITB' database. See <<db_import_nitb>>.
=== Multiple instances

View File

@@ -52,7 +52,7 @@ transceiving only RAND and SRES, may be applicable. (See 3GPP TS 33.102, chapter
|aud3g.ind_bitlen|5|Nr of index bits at lower SQN end
|apn||
|vlr_number||3GPP TS 23.008 chapter 2.4.5
|hlr_number||3GPP TS 23.008 chapter 2.4.6
|msc_number||3GPP TS 23.008 chapter 2.4.6
|sgsn_number||3GPP TS 23.008 chapter 2.4.8.1
|sgsn_address||3GPP TS 23.008 chapter 2.13.10
|ggsn_number||3GPP TS 23.008 chapter 2.4.8.2
@@ -67,3 +67,143 @@ transceiving only RAND and SRES, may be applicable. (See 3GPP TS 33.102, chapter
|ms_purged_ps|1|3GPP TS 23.008 chapter 2.7.6
|===
=== Configuring the Subscribers Create on Demand Feature
Usually a HLR will only allow mobile equipment (ME) on the network, if the HLR
has a subscriber entry with the ME's IMSI. But OsmoHLR can also be configured to
automatically create new entries for new IMSIs, with the
`subscriber-create-on-demand` VTY option. The obvious use case is creating the
new subscriber entry and then allowing the ME to use both the CS
(Circuit Switched) and PS (Packet Switched) NAM (Network Access Mode).
.osmo-hlr.cfg
----
hlr
subscriber-create-on-demand 5 cs+ps
----
On the other hand, operators might only want to give network access to IMSIs, of
which they know the owner. In order to do that, one can set the default NAM to
`none` and manually approve new subscribers by changing the NAM (e.g. over the
VTY, see the example below).
Oftentimes it is hard to know, which IMSI belongs to which ME, but the IMEI is
readily available. If you configure your MSC to send IMEI checking requests to
the HLR, before sending location update requests, the subscribers created on
demand can also have the IMEI stored in the HLR database. With OsmoMSC, this
is done by writing `check-imei-rqd early` in the `msc` section of osmo-msc.cfg.
Then enable storing the IMEI when receiving check IMEI requests with
`store-imei` in the OsmoHLR configuration.
.osmo-msc.cfg
----
msc
check-imei-rqd early
----
.osmo-hlr.cfg
----
hlr
subscriber-create-on-demand 5 none
store-imei
----
.Example: Enabling CS and PS NAM via VTY for a known IMEI
----
OsmoHLR> enable
OsmoHLR# subscriber imei 35761300444848 show
ID: 1
IMSI: 123456789023000
MSISDN: 58192 <1>
IMEI: 35761300444848
CS disabled <2>
PS disabled <2>
OsmoHLR# subscriber imei 35761300444848 update network-access-mode cs+ps
OsmoHLR# subscriber imei 35761300444848 show
ID: 1
IMSI: 123456789023000
MSISDN: 58192
IMEI: 35761300444848
----
<1> Randomly generated 5 digit MSISDN
<2> Disabled CS and PS NAM prevent the subscriber from accessing the network
=== Import Subscriber Data
==== Scripted Import
WARNING: It is not generally a good idea to depend on the HLR database's internal table structure, but in the lack of an
automated import procedure, this example is provided as an ad-hoc method to aid automated subscriber import. This is not
guaranteed to remain valid.
NOTE: We may add CSV and other import methods to the `osmo-hlr-db-tool`, but so far that is not implemented. Contact the
community if you are interested in such a feature being implemented.
NOTE: `sqlite3` is available from your distribution packages or `sqlite.org`.
Currently, probably the easiest way to automatically import subscribers to OsmoHLR is to write out a text file with SQL
commands per subscriber, and feed that to `sqlite3`, as described below.
A difficulty is to always choose subscriber IDs that are not yet in use. For an initial import, the subscriber ID may be
incremented per subscriber record. If adding more subscribers to an existing database, it is necessary to choose
subscriber IDs that are not yet in use. Get the highest ID in use with:
----
sqlite3 hlr.db 'select max(id) from subscriber'
----
A full SQL example of adding a single subscriber with id 23, IMSI 001010123456789, MSISDN 1234, Ki for COMP128v1, and K
and OPC for Milenage:
----
INSERT subscriber (id, imsi, msisdn) VALUES (23, '001010123456789', '1234');
INSERT INTO auc_2g (subscriber_id, algo_id_2g, ki)
VALUES(23, 1, '0123456789abcdef0123456789abcdef');
INSERT INTO auc_3g (subscriber_id, algo_id_3g, k, op, opc)
VALUES(23, 5, '0123456789abcdef0123456789abcdef',NULL,'0123456789abcdef0123456789abcdef');
----
Table entries to `auc_2g` and/or `auc_3g` may be omitted if no such key material is required.
UMTS Milenage auth (on both 2G and 3G RAN) is configured by the `auc_3g` table. `algo_id_3g` must currently always be 5
(MILENAGE).
The algorithm IDs for `algo_id_2g` and `algo_id_3g` are:
.Algorithm IDs in OsmoHLR's database
[options="header",width="50%",cols="40%,60%"]
|===
|`algo_id_2g` / `algo_id_3g` | Authentication Algorithm
| 1 | COMP128v1
| 2 | COMP128v2
| 3 | COMP128v3
| 4 | XOR
| 5 | MILENAGE
|===
Create an empty HLR database with
----
osmo-hlr-db-tool -l hlr.db create
----
Repeat above SQL commands per subscriber, incrementing the subscriber ID for each block, then feed the SQL commands for
the subscribers to be imported to the `sqlite3` command line tool:
----
sqlite3 hlr.db < subscribers.sql
----
[[db_import_nitb]]
==== Import OsmoNITB database
To upgrade from old OsmoNITB to OsmoHLR, use `osmo-hlr-db-tool`:
----
osmo-hlr-db-tool -l hlr.db import-nitb-db nitb.db
----
Be aware that the import is lossy, only the IMSI, MSISDN, nam_cs/ps and 2G auth data are set.

View File

@@ -50,15 +50,29 @@ prefix route to the named EUSE. All USSD short codes starting with *123 will be
routed to the named EUSE.
`ussd route prefix *#100# internal own-msisdn` installs a prefix route
to the named internal USSD handler. There above command will restore
to the named internal USSD handler. The above command will restore
the old behavior, in which *#100# will return a text message containing
the subscribers own phone number. There is one other handler called
`own-imsi` which will return the IMSI instead of the MSISDN.
the subscribers own phone number. More information on internal USSD
handlers can be found in <<iuse_handlers>>.
`ussd default-route external foobar-00-00-00-00-00-00` installs a
default route to the named EUSE. This means that all USSD codes for
which no more specific route exists will be routed to the named EUSE.
[[iuse_handlers]]
=== Built-in USSD handlers
OsmoHLR has an Internal USSD Entity (IUSE) that allows to handle some
USSD requests internally. It features a set of simple handlers, which
can be assigned to one or more USSD request prefixes:
* `own-msisdn` returns subscriber's MSISDN (if assigned);
* `own-imsi` returns subscriber's IMSI;
* `test-idle` keeps the session idle until the MS terminates it, or
the guard timer expires (may be useful for testing).
Additional handlers can be added on request.
=== Example EUSE program
We have provided an example EUSE developed in C language using existing

View File

@@ -24,8 +24,12 @@ include::{srcdir}/chapters/control.adoc[]
include::./common/chapters/control_if.adoc[]
include::{srcdir}/chapters/dgsm.adoc[]
include::./common/chapters/gsup.adoc[]
include::./common/chapters/vty_cpu_sched.adoc[]
include::./common/chapters/port_numbers.adoc[]
include::./common/chapters/bibliography.adoc[]
@@ -33,4 +37,3 @@ include::./common/chapters/bibliography.adoc[]
include::./common/chapters/glossary.adoc[]
include::./common/chapters/gfdl.adoc[]

17
doc/manuals/regen_doc.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/sh -x
if [ -z "$DOCKER_PLAYGROUND" ]; then
echo "You need to set DOCKER_PLAYGROUND"
exit 1
fi
SCRIPT=$(realpath "$0")
MANUAL_DIR=$(dirname "$SCRIPT")
COMMIT=${COMMIT:-$(git log -1 --format=format:%H)}
cd "$DOCKER_PLAYGROUND/scripts" || exit 1
OSMO_HLR_BRANCH=$COMMIT ./regen_doc.sh osmo-hlr 4258 \
"$MANUAL_DIR/chapters/counters_generated.adoc" \
"$MANUAL_DIR/vty/hlr_vty_reference.xml"

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +1,13 @@
nobase_include_HEADERS = osmocom/gsupclient/gsup_client.h
SUBDIRS = osmocom
nobase_include_HEADERS = \
osmocom/gsupclient/cni_peer_id.h \
osmocom/gsupclient/gsup_client.h \
osmocom/gsupclient/gsup_req.h \
osmocom/mslookup/mdns.h \
osmocom/mslookup/mdns_sock.h \
osmocom/mslookup/mslookup_client_fake.h \
osmocom/mslookup/mslookup_client.h \
osmocom/mslookup/mslookup_client_mdns.h \
osmocom/mslookup/mslookup.h \
$(NULL)

View File

@@ -0,0 +1,4 @@
SUBDIRS = \
hlr \
mslookup \
$(NULL)

View File

@@ -0,0 +1,66 @@
/* 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/>.
*/
#pragma once
#include <unistd.h>
#include <stdint.h>
#include <osmocom/core/utils.h>
/*! IPA Name: Arbitrary length blob, not necessarily zero-terminated.
* In osmo-hlr, struct hlr_subscriber is mostly used as static reference and cannot serve as talloc context, which is
* why this is also implemented as a fixed-maximum-size buffer instead of a talloc'd arbitrary sized buffer.
* NOTE: The length of val may be extended in the future if it becomes necessary.
* At the time of writing, this holds IPA unit name strings of very limited length.
*/
struct osmo_ipa_name {
size_t len;
uint8_t val[128];
};
bool osmo_ipa_name_is_empty(const struct osmo_ipa_name *ipa_name);
int osmo_ipa_name_set(struct osmo_ipa_name *ipa_name, const uint8_t *val, size_t len);
int osmo_ipa_name_set_str(struct osmo_ipa_name *ipa_name, const char *str_fmt, ...);
int osmo_ipa_name_cmp(const struct osmo_ipa_name *a, const struct osmo_ipa_name *b);
const char *osmo_ipa_name_to_str_c(void *ctx, const struct osmo_ipa_name *ipa_name);
const char *osmo_ipa_name_to_str(const struct osmo_ipa_name *ipa_name);
enum osmo_cni_peer_id_type {
OSMO_CNI_PEER_ID_EMPTY=0,
OSMO_CNI_PEER_ID_IPA_NAME,
/* OSMO_CNI_PEER_ID_GLOBAL_TITLE, <-- currently not implemented, but likely future possibility */
};
extern const struct value_string osmo_cni_peer_id_type_names[];
static inline const char *osmo_cni_peer_id_type_name(enum osmo_cni_peer_id_type val)
{ return get_value_string(osmo_cni_peer_id_type_names, val); }
struct osmo_cni_peer_id {
enum osmo_cni_peer_id_type type;
union {
struct osmo_ipa_name ipa_name;
};
};
bool osmo_cni_peer_id_is_empty(const struct osmo_cni_peer_id *cni_peer_id);
int osmo_cni_peer_id_set(struct osmo_cni_peer_id *cni_peer_id, enum osmo_cni_peer_id_type type,
const uint8_t *val, size_t len);
int osmo_cni_peer_id_set_str(struct osmo_cni_peer_id *cni_peer_id, enum osmo_cni_peer_id_type type,
const char *str_fmt, ...);
int osmo_cni_peer_id_cmp(const struct osmo_cni_peer_id *a, const struct osmo_cni_peer_id *b);
const char *osmo_cni_peer_id_to_str(const struct osmo_cni_peer_id *cni_peer_id);
const char *osmo_cni_peer_id_to_str_c(void *ctx, const struct osmo_cni_peer_id *cni_peer_id);

View File

@@ -6,18 +6,17 @@
* Author: Jacob Erlbeck
*
* 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
* 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 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 <http://www.gnu.org/licenses/>.
* 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/>.
*/
#pragma once
@@ -39,6 +38,8 @@ struct osmo_gsup_client;
/* Expects message in msg->l2h */
typedef int (*osmo_gsup_client_read_cb_t)(struct osmo_gsup_client *gsupc, struct msgb *msg);
typedef bool (*osmo_gsup_client_up_down_cb_t)(struct osmo_gsup_client *gsupc, bool up);
struct osmo_gsup_client {
const char *unit_name; /* same as ipa_dev->unit_name, for backwards compat */
@@ -54,8 +55,31 @@ struct osmo_gsup_client {
int got_ipa_pong;
struct ipaccess_unit *ipa_dev; /* identification information sent to IPA server */
osmo_gsup_client_up_down_cb_t up_down_cb;
};
struct osmo_gsup_client_config {
/*! IP access unit which contains client identification information; must be allocated in talloc_ctx as well to
* ensure it lives throughout the lifetime of the connection. */
struct ipaccess_unit *ipa_dev;
/*! GSUP server IP address to connect to. */
const char *ip_addr;
/*! GSUP server TCP port to connect to. */
unsigned int tcp_port;
/*! OPA client configuration, or NULL. */
struct osmo_oap_client_config *oapc_config;
/*! callback for reading from the GSUP connection. */
osmo_gsup_client_read_cb_t read_cb;
/*! Invoked when the GSUP link is ready for communication, and when the link drops. */
osmo_gsup_client_up_down_cb_t up_down_cb;
/*! User data stored in the returned gsupc->data, as context for the callbacks. */
void *data;
/*! Marker for future extension, always pass this as false. */
bool more;
};
struct osmo_gsup_client *osmo_gsup_client_create3(void *talloc_ctx, struct osmo_gsup_client_config *config);
struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx,
struct ipaccess_unit *ipa_dev,
const char *ip_addr,

View File

@@ -0,0 +1,119 @@
/* 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/>.
*/
#pragma once
#include <osmocom/gsm/gsup.h>
#include <osmocom/gsupclient/cni_peer_id.h>
struct osmo_gsup_req;
#define LOG_GSUP_REQ_CAT_SRC(req, subsys, level, file, line, fmt, args...) \
LOGPSRC(subsys, level, file, line, "GSUP %u: %s: IMSI-%s %s: " fmt, \
(req) ? (req)->nr : 0, \
(req) ? osmo_cni_peer_id_to_str(&(req)->source_name) : "NULL", \
(req) ? (req)->gsup.imsi : "NULL", \
(req) ? osmo_gsup_message_type_name((req)->gsup.message_type) : "NULL", \
##args)
#define LOG_GSUP_REQ_CAT(req, subsys, level, fmt, args...) \
LOG_GSUP_REQ_CAT_SRC(req, subsys, level, __FILE__, __LINE__, fmt, ##args)
#define LOG_GSUP_REQ_SRC(req, level, file, line, fmt, args...) \
LOG_GSUP_REQ_CAT_SRC(req, DLGSUP, level, file, line, fmt, ##args)
#define LOG_GSUP_REQ(req, level, fmt, args...) \
LOG_GSUP_REQ_SRC(req, level, __FILE__, __LINE__, fmt, ##args)
typedef void (*osmo_gsup_req_send_response_t)(struct osmo_gsup_req *req, struct osmo_gsup_message *response);
/* Keep track of an incoming request, to route back a response when it is ready.
* Particularly, a GSUP response to a request must contain various bits of information that need to be copied from the
* request for proxy/routing to work and for session states to remain valid. That is the main reason why (almost) all
* GSUP request/response should go through an osmo_gsup_req, even if it is handled synchronously.
*/
struct osmo_gsup_req {
/* The incoming GSUP message in decoded form. */
const struct osmo_gsup_message gsup;
/* Decoding result code. If decoding failed, this will be != 0. */
int decode_rc;
/* The ultimate source of this message: the source_name form the GSUP message, or, if not present, then the
* immediate GSUP peer. GSUP messages going via a proxy reflect the initial source in the source_name.
* This source_name is implicitly added to the routes for the conn the message was received on. */
struct osmo_cni_peer_id source_name;
/* If the source_name is not an immediate GSUP peer, this is set to the closest intermediate peer between here
* and source_name. */
struct osmo_cni_peer_id via_proxy;
/* Identify this request by number, for logging. */
unsigned int nr;
/* osmo_gsup_req can be used by both gsup_server and gsup_client. The individual method of actually sending a
* GSUP message is provided by this callback. */
osmo_gsup_req_send_response_t send_response_cb;
/* User supplied data pointer, may be used to provide context to send_response_cb(). */
void *cb_data;
/* List entry that can be used to keep a list of osmo_gsup_req instances; not used directly by osmo_gsup_req.c,
* it is up to using implementations to keep a list. If this is non-NULL, osmo_gsup_req_free() calls
* llist_del() on this. */
struct llist_head entry;
/* A decoded GSUP message still points into the received msgb. For a decoded osmo_gsup_message to remain valid,
* we also need to keep the msgb. */
struct msgb *msg;
};
struct osmo_gsup_req *osmo_gsup_req_new(void *ctx, const struct osmo_cni_peer_id *from_peer, struct msgb *msg,
osmo_gsup_req_send_response_t send_response_cb, void *cb_data,
struct llist_head *add_to_list);
void osmo_gsup_req_free(struct osmo_gsup_req *req);
/*! See _osmo_gsup_req_respond() for details.
* Call _osmo_gsup_req_respond(), passing the caller's source file and line for logging. */
#define osmo_gsup_req_respond(REQ, RESPONSE, ERROR, FINAL_RESPONSE) \
_osmo_gsup_req_respond(REQ, RESPONSE, ERROR, FINAL_RESPONSE, __FILE__, __LINE__)
int _osmo_gsup_req_respond(struct osmo_gsup_req *req, struct osmo_gsup_message *response,
bool error, bool final_response, const char *file, int line);
/*! See _osmo_gsup_req_respond_msgt() for details.
* Call _osmo_gsup_req_respond_msgt(), passing the caller's source file and line for logging. */
#define osmo_gsup_req_respond_msgt(REQ, MESSAGE_TYPE, FINAL_RESPONSE) \
_osmo_gsup_req_respond_msgt(REQ, MESSAGE_TYPE, FINAL_RESPONSE, __FILE__, __LINE__)
int _osmo_gsup_req_respond_msgt(struct osmo_gsup_req *req, enum osmo_gsup_message_type message_type,
bool final_response, const char *file, int line);
/*! See _osmo_gsup_req_respond_err() for details.
* Log an error message, and call _osmo_gsup_req_respond_err(), passing the caller's source file and line for logging.
*/
#define osmo_gsup_req_respond_err(REQ, CAUSE, FMT, args...) do { \
LOG_GSUP_REQ(REQ, LOGL_ERROR, "%s: " FMT "\n", \
get_value_string(gsm48_gmm_cause_names, CAUSE), ##args); \
_osmo_gsup_req_respond_err(REQ, CAUSE, __FILE__, __LINE__); \
} while(0)
void _osmo_gsup_req_respond_err(struct osmo_gsup_req *req, enum gsm48_gmm_cause cause,
const char *file, int line);
int osmo_gsup_make_response(struct osmo_gsup_message *reply,
const struct osmo_gsup_message *rx, bool error, bool final_response);
size_t osmo_gsup_message_to_str_buf(char *buf, size_t bufsize, const struct osmo_gsup_message *msg);
char *osmo_gsup_message_to_str_c(void *ctx, const struct osmo_gsup_message *msg);

View File

@@ -0,0 +1,20 @@
noinst_HEADERS = \
auc.h \
ctrl.h \
db.h \
dgsm.h \
gsup_router.h \
gsup_server.h \
hlr.h \
hlr_ussd.h \
hlr_vty.h \
hlr_vty_subscr.h \
logging.h \
lu_fsm.h \
mslookup_server.h \
mslookup_server_mdns.h \
proxy.h \
rand.h \
remote_hlr.h \
timestamp.h \
$(NULL)

View File

@@ -3,14 +3,25 @@
#include <stdbool.h>
#include <sqlite3.h>
#include <osmocom/gsupclient/cni_peer_id.h>
struct hlr;
enum stmt_idx {
DB_STMT_SEL_ALL,
DB_STMT_SEL_ALL_ORDER_LAST_SEEN,
DB_STMT_SEL_FILTER_MSISDN,
DB_STMT_SEL_FILTER_IMSI,
DB_STMT_SEL_FILTER_IMEI,
DB_STMT_SEL_FILTER_CS,
DB_STMT_SEL_FILTER_PS,
DB_STMT_SEL_BY_IMSI,
DB_STMT_SEL_BY_MSISDN,
DB_STMT_SEL_BY_ID,
DB_STMT_SEL_BY_IMEI,
DB_STMT_UPD_VLR_BY_ID,
DB_STMT_UPD_SGSN_BY_ID,
DB_STMT_UPD_IMEI_BY_IMSI,
DB_STMT_AUC_BY_IMSI,
DB_STMT_AUC_UPD_SQN,
DB_STMT_UPD_PURGE_CS_BY_IMSI,
@@ -26,6 +37,9 @@ enum stmt_idx {
DB_STMT_AUC_3G_INSERT,
DB_STMT_AUC_3G_DELETE,
DB_STMT_SET_LAST_LU_SEEN,
DB_STMT_SET_LAST_LU_SEEN_PS,
DB_STMT_EXISTS_BY_IMSI,
DB_STMT_EXISTS_BY_MSISDN,
_NUM_DB_STMT
};
@@ -35,10 +49,16 @@ struct db_context {
sqlite3_stmt *stmt[_NUM_DB_STMT];
};
/* Optional feature to make SQLite3 using talloc */
#ifdef SQLITE_USE_TALLOC
int db_sqlite3_use_talloc(void *ctx);
#endif
void db_remove_reset(sqlite3_stmt *stmt);
bool db_bind_text(sqlite3_stmt *stmt, const char *param_name, const char *text);
bool db_bind_int(sqlite3_stmt *stmt, const char *param_name, int nr);
bool db_bind_int64(sqlite3_stmt *stmt, const char *param_name, int64_t nr);
bool db_bind_null(sqlite3_stmt *stmt, const char *param_name);
void db_close(struct db_context *dbc);
struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite3_logging, bool allow_upgrades);
@@ -56,7 +76,7 @@ int db_update_sqn(struct db_context *dbc, int64_t id,
int db_get_auc(struct db_context *dbc, const char *imsi,
unsigned int auc_3g_ind, struct osmo_auth_vector *vec,
unsigned int num_vec, const uint8_t *rand_auts,
const uint8_t *auts);
const uint8_t *auts, bool separation_bit);
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/protocol/gsm_23_003.h>
@@ -69,8 +89,9 @@ struct hlr_subscriber {
int64_t id;
char imsi[GSM23003_IMSI_MAX_DIGITS+1];
char msisdn[GT_MAX_DIGITS+1];
char msisdn[GSM23003_MSISDN_MAX_DIGITS+1];
/* imeisv? */
char imei[GSM23003_IMEI_NUM_DIGITS+1];
char vlr_number[32];
char sgsn_number[32];
char sgsn_address[GT_MAX_DIGITS+1];
@@ -85,6 +106,10 @@ struct hlr_subscriber {
bool ms_purged_cs;
bool ms_purged_ps;
time_t last_lu_seen;
time_t last_lu_seen_ps;
/* talloc'd IPA unit name */
struct osmo_ipa_name vlr_via_proxy;
struct osmo_ipa_name sgsn_via_proxy;
};
/* A format string for use with strptime(3). This format string is
@@ -115,29 +140,39 @@ struct sub_auth_data_str {
} u;
};
int db_subscr_create(struct db_context *dbc, const char *imsi);
#define DB_SUBSCR_FLAG_NAM_CS (1 << 1)
#define DB_SUBSCR_FLAG_NAM_PS (1 << 2)
int db_subscr_create(struct db_context *dbc, const char *imsi, uint8_t flags);
int db_subscr_delete_by_id(struct db_context *dbc, int64_t subscr_id);
int db_subscr_update_msisdn_by_imsi(struct db_context *dbc, const char *imsi,
const char *msisdn);
int db_subscr_update_aud_by_id(struct db_context *dbc, int64_t subscr_id,
const struct sub_auth_data_str *aud);
int db_subscr_update_imei_by_imsi(struct db_context *dbc, const char* imsi, const char *imei);
int db_subscr_exists_by_imsi(struct db_context *dbc, const char *imsi);
int db_subscr_exists_by_msisdn(struct db_context *dbc, const char *msisdn);
int db_subscrs_get(struct db_context *dbc, const char *filter_type, const char *filter,
void (*get_cb)(struct hlr_subscriber *subscr, void *data), void *data,
int *count, const char **err);
int db_subscr_get_by_imsi(struct db_context *dbc, const char *imsi,
struct hlr_subscriber *subscr);
int db_subscr_get_by_msisdn(struct db_context *dbc, const char *msisdn,
struct hlr_subscriber *subscr);
int db_subscr_get_by_id(struct db_context *dbc, int64_t id,
struct hlr_subscriber *subscr);
int db_subscr_get_by_imei(struct db_context *dbc, const char *imei, struct hlr_subscriber *subscr);
int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps);
int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
const char *vlr_or_sgsn_number, bool is_ps);
const struct osmo_ipa_name *vlr_name, bool is_ps,
const struct osmo_ipa_name *via_proxy);
int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
bool purge_val, bool is_ps);
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps);
/*! Call sqlite3_column_text() and copy result to a char[].
* \param[out] buf A char[] used as sizeof() arg(!) and osmo_strlcpy() target.
* \param[in] stmt An sqlite3_stmt*.
@@ -148,3 +183,14 @@ int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val,
const char *_txt = (const char *) sqlite3_column_text(stmt, idx); \
osmo_strlcpy(buf, _txt, sizeof(buf)); \
} while (0)
/*! Call sqlite3_column_text() and copy result to a struct osmo_ipa_name.
* \param[out] ipa_name A struct osmo_ipa_name* to write to.
* \param[in] stmt An sqlite3_stmt*.
* \param[in] idx Index in stmt's returned columns.
*/
#define copy_sqlite3_text_to_ipa_name(ipa_name, stmt, idx) \
do { \
const char *_txt = (const char *) sqlite3_column_text(stmt, idx); \
osmo_ipa_name_set_str(ipa_name, _txt); \
} while (0)

View File

@@ -0,0 +1,46 @@
/* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <osmocom/mslookup/mslookup.h>
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/logging.h>
#include <osmocom/gsupclient/cni_peer_id.h>
#include <osmocom/gsupclient/gsup_req.h>
#define LOG_DGSM(imsi, level, fmt, args...) \
LOGP(DDGSM, level, "(IMSI-%s) " fmt, imsi, ##args)
struct vty;
struct remote_hlr;
struct hlr_subscriber;
extern void *dgsm_ctx;
void dgsm_init(void *ctx);
void dgsm_start(void *ctx);
void dgsm_stop();
bool dgsm_check_forward_gsup_msg(struct osmo_gsup_req *req);
void dgsm_vty_init();
void dgsm_mdns_client_config_apply(void);
bool hlr_subscr_lu_age(const struct hlr_subscriber *subscr, uint32_t *age_p);

View File

@@ -0,0 +1,33 @@
#pragma once
#include <stdint.h>
#include <osmocom/hlr/gsup_server.h>
struct osmo_ipa_name;
struct gsup_route {
struct llist_head list;
uint8_t *addr;
struct osmo_gsup_conn *conn;
};
struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs,
const uint8_t *addr, size_t addrlen);
struct osmo_gsup_conn *gsup_route_find_by_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name);
struct gsup_route *gsup_route_find_by_conn(const struct osmo_gsup_conn *conn);
/* add a new route for the given address to the given conn */
int gsup_route_add_ipa_name(struct osmo_gsup_conn *conn, const struct osmo_ipa_name *ipa_name);
int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addrlen);
/* delete all routes for the given connection */
int gsup_route_del_conn(struct osmo_gsup_conn *conn);
int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
const uint8_t *addr, size_t addrlen,
struct msgb *msg);
int osmo_gsup_send_to_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name, struct msgb *msg);
int osmo_gsup_enc_send_to_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name,
const struct osmo_gsup_message *gsup);

View File

@@ -5,6 +5,8 @@
#include <osmocom/abis/ipa.h>
#include <osmocom/abis/ipaccess.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/gsupclient/cni_peer_id.h>
#include <osmocom/gsupclient/gsup_req.h>
#ifndef OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN
#define OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN 43 /* TS 24.008 10.5.4.7 */
@@ -22,12 +24,12 @@ struct osmo_gsup_server {
/* list of osmo_gsup_conn */
struct llist_head clients;
/* lu_operations list */
struct llist_head *luop;
struct ipa_server_link *link;
osmo_gsup_read_cb_t read_cb;
struct llist_head routes;
/* Proxy requests from this server's clients to remote GSUP servers. */
struct proxy *proxy;
};
@@ -45,9 +47,15 @@ struct osmo_gsup_conn {
/* Set when Location Update is received: */
bool supports_cs; /* client supports OSMO_GSUP_CN_DOMAIN_CS */
bool supports_ps; /* client supports OSMO_GSUP_CN_DOMAIN_PS */
/* The IPA unit name received on this link. Routes with more unit names serviced by this link may exist in
* osmo_gsup_server->routes, but this is the name the immediate peer identified as in the IPA handshake. */
struct osmo_ipa_name peer_name;
};
struct msgb *osmo_gsup_msgb_alloc(const char *label);
struct osmo_gsup_req *osmo_gsup_conn_rx(struct osmo_gsup_conn *conn, struct msgb *msg);
int osmo_gsup_conn_send(struct osmo_gsup_conn *conn, struct msgb *msg);
int osmo_gsup_conn_ccm_get(const struct osmo_gsup_conn *clnt, uint8_t **addr,
uint8_t tag);
@@ -56,7 +64,6 @@ struct osmo_gsup_server *osmo_gsup_server_create(void *ctx,
const char *ip_addr,
uint16_t tcp_port,
osmo_gsup_read_cb_t read_cb,
struct llist_head *lu_op_lst,
void *priv);
void osmo_gsup_server_destroy(struct osmo_gsup_server *gsups);
@@ -67,3 +74,5 @@ int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup,
uint8_t *msisdn_enc, size_t msisdn_enc_size,
uint8_t *apn_buf, size_t apn_buf_size,
enum osmo_gsup_cn_domain cn_domain);
int osmo_gsup_forward_to_local_peer(struct osmo_gsup_server *server, const struct osmo_cni_peer_id *to_peer,
struct osmo_gsup_req *req, struct osmo_gsup_message *modified_gsup);

121
include/osmocom/hlr/hlr.h Normal file
View File

@@ -0,0 +1,121 @@
/* OsmoHLR generic header */
/* (C) 2017 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Max Suraev <msuraev@sysmocom.de>
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <stdbool.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/ipa.h>
#include <osmocom/core/tdef.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/hlr/dgsm.h>
#define HLR_DEFAULT_DB_FILE_PATH "hlr.db"
struct hlr_euse;
struct osmo_gsup_conn;
enum osmo_gsup_message_type;
extern struct osmo_tdef g_hlr_tdefs[];
struct hlr {
/* GSUP server pointer */
struct osmo_gsup_server *gs;
/* DB context */
char *db_file_path;
struct db_context *dbc;
/* Control Interface */
struct ctrl_handle *ctrl;
const char *ctrl_bind_addr;
/* Local bind addr */
char *gsup_bind_addr;
struct ipaccess_unit gsup_unit_name;
struct llist_head euse_list;
struct hlr_euse *euse_default;
/* NCSS (call independent) session guard timeout value */
int ncss_guard_timeout;
struct llist_head ussd_routes;
struct llist_head ss_sessions;
bool store_imei;
bool subscr_create_on_demand;
/* Bitmask of DB_SUBSCR_FLAG_* */
uint8_t subscr_create_on_demand_flags;
unsigned int subscr_create_on_demand_rand_msisdn_len;
struct {
bool allow_startup;
struct {
/* Whether the mslookup server should be active in general (all lookup methods) */
bool enable;
uint32_t local_attach_max_age;
struct llist_head local_site_services;
struct {
/* Whether the mDNS method of the mslookup server should be active. */
bool enable;
/* The mDNS bind address and domain suffix as set by the VTY, not necessarily in use. */
struct osmo_sockaddr_str bind_addr;
char *domain_suffix;
struct osmo_mslookup_server_mdns *running;
} mdns;
} server;
/* The mslookup client in osmo-hlr is used to find out which remote HLRs service a locally unknown IMSI.
* (It may also be used to resolve recipients for SMS-over-GSUP in the future.) */
struct {
/* Whether to proxy/forward to remote HLRs */
bool enable;
/* If this is set, all GSUP for unknown IMSIs is forwarded directly to this GSUP address,
* unconditionally. */
struct osmo_sockaddr_str gsup_gateway_proxy;
/* mslookup client request handling */
unsigned int result_timeout_milliseconds;
struct osmo_mslookup_client *client;
struct {
/* Whether to use mDNS for IMSI MS Lookup */
bool enable;
struct osmo_sockaddr_str query_addr;
char *domain_suffix;
struct osmo_mslookup_client_method *running;
} mdns;
} client;
} mslookup;
};
extern struct hlr *g_hlr;
struct hlr_subscriber;
void osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr);
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps);

View File

@@ -5,7 +5,7 @@
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/gsup.h>
#include "gsup_server.h"
#include <osmocom/hlr/gsup_server.h>
#define NCSS_GUARD_TIMEOUT_DEFAULT 30
@@ -46,8 +46,8 @@ struct hlr_ussd_route *ussd_route_prefix_alloc_ext(struct hlr *hlr, const char *
struct hlr_euse *euse);
void ussd_route_del(struct hlr_ussd_route *rt);
int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);
int rx_proc_ss_error(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);
void rx_proc_ss_req(struct osmo_gsup_req *req);
void rx_proc_ss_error(struct osmo_gsup_req *req);
struct ss_session;
struct ss_request;
@@ -56,6 +56,5 @@ struct ss_request;
struct hlr_iuse {
const char *name;
/* call-back to be called for any incoming USSD messages for this IUSE */
int (*handle_ussd)(struct osmo_gsup_conn *conn, struct ss_session *ss,
const struct osmo_gsup_message *gsup, const struct ss_request *req);
int (*handle_ussd)(struct ss_session *ss, const struct osmo_gsup_message *gsup, const struct ss_request *req);
};

View File

@@ -25,14 +25,19 @@
#include <osmocom/core/logging.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include "hlr.h"
#include <osmocom/hlr/hlr.h>
enum hlr_vty_node {
HLR_NODE = _LAST_OSMOVTY_NODE + 1,
GSUP_NODE,
EUSE_NODE,
MSLOOKUP_NODE,
MSLOOKUP_SERVER_NODE,
MSLOOKUP_SERVER_MSC_NODE,
MSLOOKUP_CLIENT_NODE,
};
int hlr_vty_is_config_node(struct vty *vty, int node);
int hlr_vty_go_parent(struct vty *vty);
void hlr_vty_init(const struct log_info *cat);
void hlr_vty_init(void);
void dgsm_vty_init(void);

View File

@@ -8,6 +8,9 @@ enum {
DGSUP,
DAUC,
DSS,
DMSLOOKUP,
DLU,
DDGSM,
};
extern const struct log_info hlr_log_info;

View File

@@ -0,0 +1,22 @@
/* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#pragma once
void lu_rx_gsup(struct osmo_gsup_req *req);

View File

@@ -0,0 +1,72 @@
/* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <osmocom/gsupclient/cni_peer_id.h>
#include <osmocom/mslookup/mslookup.h>
struct osmo_mslookup_query;
struct osmo_mslookup_result;
/*! mslookup service name used for roaming/proxying between osmo-hlr instances. */
#define OSMO_MSLOOKUP_SERVICE_HLR_GSUP "gsup.hlr"
/*! What addresses to return to mslookup queries when a subscriber is attached at the local site.
* Mapping of service name to IP address and port. This corresponds to the VTY config for
* 'mslookup' / 'server' [/ 'msc MSC-1-2-3'] / 'service sip.voice at 1.2.3.4 1234'.
*/
struct mslookup_service_host {
struct llist_head entry;
char service[OSMO_MSLOOKUP_SERVICE_MAXLEN+1];
struct osmo_sockaddr_str host_v4;
struct osmo_sockaddr_str host_v6;
};
/*! Sets of mslookup_service_host per connected MSC.
* When there are more than one MSC connected to this osmo-hlr, this allows keeping separate sets of service addresses
* for each MSC. The entry with mslookup_server_msc_wildcard as MSC name is used for all MSCs (if no match for that
* particular MSC is found). This corresponds to the VTY config for
* 'mslookup' / 'server' / 'msc MSC-1-2-3'.
*/
struct mslookup_server_msc_cfg {
struct llist_head entry;
struct osmo_ipa_name name;
struct llist_head service_hosts;
};
struct mslookup_service_host *mslookup_server_service_get(const struct osmo_ipa_name *msc_name, const char *service);
struct mslookup_service_host *mslookup_server_msc_service_get(struct mslookup_server_msc_cfg *msc, const char *service,
bool create);
int mslookup_server_msc_service_set(struct mslookup_server_msc_cfg *msc, const char *service,
const struct osmo_sockaddr_str *addr);
int mslookup_server_msc_service_del(struct mslookup_server_msc_cfg *msc, const char *service,
const struct osmo_sockaddr_str *addr);
extern const struct osmo_ipa_name mslookup_server_msc_wildcard;
struct mslookup_server_msc_cfg *mslookup_server_msc_get(const struct osmo_ipa_name *msc_name, bool create);
const struct mslookup_service_host *mslookup_server_get_local_gsup_addr();
void mslookup_server_rx(const struct osmo_mslookup_query *query,
struct osmo_mslookup_result *result);
bool subscriber_has_done_lu_here(const struct osmo_mslookup_query *query,
uint32_t *lu_age_p, struct osmo_ipa_name *local_msc_name,
char *ret_imsi, size_t ret_imsi_len);

View File

@@ -0,0 +1,36 @@
/* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <stdbool.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/mslookup/mdns_sock.h>
struct osmo_mslookup_server_mdns {
struct osmo_mslookup_server *mslookup;
struct osmo_sockaddr_str bind_addr;
char *domain_suffix;
struct osmo_mdns_sock *sock;
};
struct osmo_mslookup_server_mdns *osmo_mslookup_server_mdns_start(void *ctx, const struct osmo_sockaddr_str *bind_addr,
const char *domain_suffix);
void osmo_mslookup_server_mdns_stop(struct osmo_mslookup_server_mdns *server);
void mslookup_server_mdns_config_apply();

View File

@@ -0,0 +1,95 @@
/* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <time.h>
#include <osmocom/gsm/protocol/gsm_23_003.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/gsupclient/cni_peer_id.h>
#include <osmocom/hlr/timestamp.h>
struct osmo_gsup_req;
struct remote_hlr;
struct proxy {
struct llist_head subscr_list;
struct llist_head pending_gsup_reqs;
/* When messages arrive back from a remote HLR that this is the proxy for, reach the VLR to forward the response
* to via this osmo_gsup_server. */
struct osmo_gsup_server *gsup_server_to_vlr;
/* How long to keep proxy entries without a refresh, in seconds. */
uint32_t fresh_time;
/* How often to garbage collect the proxy cache, period in seconds.
* To change this and take effect immediately, rather use proxy_set_gc_period(). */
uint32_t gc_period;
struct osmo_timer_list gc_timer;
};
struct proxy_subscr_domain_state {
struct osmo_ipa_name vlr_name;
timestamp_t last_lu;
/* The name from which an Update Location Request was received. Copied to vlr_name as soon as the LU is
* completed successfully. */
struct osmo_ipa_name vlr_name_preliminary;
/* Set if this is a middle proxy, i.e. a proxy behind another proxy.
* That is mostly to know whether the MS is attached at a local MSC/SGSN or further away.
* It could be a boolean, but store the full name for logging. Set only at successful LU acceptance. */
struct osmo_ipa_name vlr_via_proxy;
};
struct proxy_subscr {
char imsi[GSM23003_IMSI_MAX_DIGITS+1];
char msisdn[GSM23003_MSISDN_MAX_DIGITS+1];
struct osmo_sockaddr_str remote_hlr_addr;
struct proxy_subscr_domain_state cs, ps;
};
void proxy_init(struct osmo_gsup_server *gsup_server_to_vlr);
void proxy_del(struct proxy *proxy);
void proxy_set_gc_period(struct proxy *proxy, uint32_t gc_period);
/* The API to access / modify proxy entries keeps the implementation opaque, to make sure that we can easily move proxy
* storage to SQLite db. */
int proxy_subscr_get_by_imsi(struct proxy_subscr *dst, struct proxy *proxy, const char *imsi);
int proxy_subscr_get_by_msisdn(struct proxy_subscr *dst, struct proxy *proxy, const char *msisdn);
void proxy_subscrs_get_by_remote_hlr(struct proxy *proxy, const struct osmo_sockaddr_str *remote_hlr_addr,
bool (*yield)(struct proxy *proxy, const struct proxy_subscr *subscr, void *data),
void *data);
int proxy_subscr_create_or_update(struct proxy *proxy, const struct proxy_subscr *proxy_subscr);
int proxy_subscr_del(struct proxy *proxy, const char *imsi);
int proxy_subscr_forward_to_remote_hlr(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
struct osmo_gsup_req *req);
void proxy_subscr_forward_to_remote_hlr_resolved(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
struct remote_hlr *remote_hlr, struct osmo_gsup_req *req);
int proxy_subscr_forward_to_vlr(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
const struct osmo_gsup_message *gsup, struct remote_hlr *from_remote_hlr);
void proxy_subscr_remote_hlr_resolved(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
const struct osmo_sockaddr_str *remote_hlr_addr);
void proxy_subscr_remote_hlr_up(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
struct remote_hlr *remote_hlr);

View File

@@ -0,0 +1,59 @@
/* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <stdbool.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/sockaddr_str.h>
struct osmo_gsup_client;
struct osmo_gsup_message;
struct osmo_gsup_req;
struct msgb;
#define LOG_REMOTE_HLR(remote_hlr, level, fmt, args...) \
LOGP(DDGSM, level, "(Proxy HLR-" OSMO_SOCKADDR_STR_FMT ") " fmt, \
OSMO_SOCKADDR_STR_FMT_ARGS((remote_hlr) ? &(remote_hlr)->addr : NULL), ##args)
#define LOG_REMOTE_HLR_MSG(remote_hlr, gsup_msg, level, fmt, args...) \
LOG_REMOTE_HLR(remote_hlr, level, "%s: " fmt, osmo_gsup_message_type_name((gsup_msg)->message_type), ##args)
/* GSUP client link for proxying to a remote HLR. */
struct remote_hlr {
struct llist_head entry;
struct osmo_sockaddr_str addr;
struct osmo_gsup_client *gsupc;
struct llist_head pending_up_callbacks;
};
/*! Receive a remote_hlr address when connecting succeeded, or remote_hlr == NULL on error.
* \param addr GSUP IP address and port for which the connection was requested.
* \param remote_hlr The connected remote_hlr ready for sending, or NULL if connecting failed.
* \param data Same a passed to remote_hlr_get_or_connect(). */
typedef void (*remote_hlr_connect_result_cb_t)(const struct osmo_sockaddr_str *addr, struct remote_hlr *remote_hlr, void *data);
struct remote_hlr *remote_hlr_get_or_connect(const struct osmo_sockaddr_str *addr, bool connect,
remote_hlr_connect_result_cb_t connect_result_cb, void *data);
void remote_hlr_destroy(struct remote_hlr *remote_hlr);
int remote_hlr_msgb_send(struct remote_hlr *remote_hlr, struct msgb *msg);
void remote_hlr_gsup_forward_to_remote_hlr(struct remote_hlr *remote_hlr, struct osmo_gsup_req *req,
struct osmo_gsup_message *modified_gsup);
bool remote_hlr_is_up(struct remote_hlr *remote_hlr);

View File

@@ -0,0 +1,28 @@
/* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <sys/time.h>
#include <stdbool.h>
#include <stdint.h>
typedef time_t timestamp_t;
void timestamp_update(timestamp_t *timestamp);
bool timestamp_age(const timestamp_t *timestamp, uint32_t *age);

View File

@@ -0,0 +1,6 @@
# most headers here are installed, see /include/Makefile.am
noinst_HEADERS = \
mdns_msg.h \
mdns_rfc.h \
$(NULL)

View File

@@ -0,0 +1,39 @@
/* 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/>.
*/
/*! \file mdns.h */
#pragma once
#include <osmocom/core/msgb.h>
#include <osmocom/mslookup/mslookup.h>
#define OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT "mdns.osmocom.org"
struct msgb *osmo_mdns_query_encode(void *ctx, uint16_t packet_id, const struct osmo_mslookup_query *query,
const char *domain_suffix);
struct osmo_mslookup_query *osmo_mdns_query_decode(void *ctx, const uint8_t *data, size_t data_len,
uint16_t *packet_id, const char *domain_suffix);
struct msgb *osmo_mdns_result_encode(void *ctx, uint16_t packet_id, const struct osmo_mslookup_query *query,
const struct osmo_mslookup_result *result, const char *domain_suffix);
int osmo_mdns_result_decode(void *ctx, const uint8_t *data, size_t data_len, uint16_t *packet_id,
struct osmo_mslookup_query *query, struct osmo_mslookup_result *result,
const char *domain_suffix);

View File

@@ -0,0 +1,54 @@
/* 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/>.
*/
#pragma once
#include <stdint.h>
#include "mdns_rfc.h"
struct osmo_mdns_record {
struct llist_head list;
enum osmo_mdns_rfc_record_type type;
uint16_t length;
uint8_t *data;
};
struct osmo_mdns_msg_request {
uint16_t id;
char *domain;
enum osmo_mdns_rfc_record_type type;
};
struct osmo_mdns_msg_answer {
uint16_t id;
char *domain;
/*! list of osmo_mdns_record. */
struct llist_head records;
};
int osmo_mdns_msg_request_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_msg_request *req);
struct osmo_mdns_msg_request *osmo_mdns_msg_request_decode(void *ctx, const uint8_t *data, size_t data_len);
void osmo_mdns_msg_answer_init(struct osmo_mdns_msg_answer *answer);
int osmo_mdns_msg_answer_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_msg_answer *ans);
struct osmo_mdns_msg_answer *osmo_mdns_msg_answer_decode(void *ctx, const uint8_t *data, size_t data_len);
int osmo_mdns_result_from_answer(struct osmo_mslookup_result *result, const struct osmo_mdns_msg_answer *ans);
struct osmo_mdns_record *osmo_mdns_record_txt_keyval_encode(void *ctx, const char *key, const char *value_fmt, ...);
int osmo_mdns_record_txt_keyval_decode(const struct osmo_mdns_record *rec,
char *key_buf, size_t key_size, char *value_buf, size_t value_size);

View File

@@ -0,0 +1,113 @@
/* 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/>.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/endian.h>
#include <osmocom/mslookup/mdns.h>
/* RFC 1035 2.3.4 */
#define OSMO_MDNS_RFC_MAX_NAME_LEN 255
/* RFC 1035 3.3 <character-string> */
#define OSMO_MDNS_RFC_MAX_CHARACTER_STRING_LEN 256
enum osmo_mdns_rfc_record_type {
OSMO_MDNS_RFC_RECORD_TYPE_UNKNOWN = 0,
/* RFC 1035 3.2.2 */
OSMO_MDNS_RFC_RECORD_TYPE_A = 1, /* IPv4 address */
OSMO_MDNS_RFC_RECORD_TYPE_TXT = 16, /* Text strings */
/* RFC 3596 2.1 */
OSMO_MDNS_RFC_RECORD_TYPE_AAAA = 28, /* IPv6 address */
/* RFC 1035 3.2.3 */
OSMO_MDNS_RFC_RECORD_TYPE_ALL = 255, /* Request only: ask for all */
};
enum osmo_mdns_rfc_class {
OSMO_MDNS_RFC_CLASS_UNKNOWN = 0,
/* RFC 1035 3.2.4 */
OSMO_MDNS_RFC_CLASS_IN = 1, /* Internet and IP networks */
/* RFC 1035 3.2.5 */
OSMO_MDNS_RFC_CLASS_ALL = 255, /* Request only: ask for all */
};
/* RFC 1035 4.1.1 */
struct osmo_mdns_rfc_header {
#if OSMO_IS_LITTLE_ENDIAN
uint16_t id;
uint8_t rd:1,
tc:1,
aa:1,
opcode:4,
qr:1; /* QR (0: query, 1: response) */
uint8_t rcode:4,
z:3,
ra:1;
uint16_t qdcount; /* Number of questions */
uint16_t ancount; /* Number of answers */
uint16_t nscount; /* Number of authority records */
uint16_t arcount; /* Number of additional records */
#elif OSMO_IS_BIG_ENDIAN
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
uint16_t id;
uint8_t qr:1, opcode:4, aa:1, tc:1, rd:1;
uint8_t ra:1, z:3, rcode:4;
uint16_t qdcount;
uint16_t ancount;
uint16_t nscount;
uint16_t arcount;
#endif
} __attribute__ ((packed));
/* RFC 1035 4.1.2 */
struct osmo_mdns_rfc_question {
char *domain; /* Domain to be encoded as qname (e.g. "gsup.hlr.1234567.imsi") */
enum osmo_mdns_rfc_record_type qtype;
enum osmo_mdns_rfc_class qclass;
};
/* RFC 1035 4.1.3 */
struct osmo_mdns_rfc_record {
char *domain; /* Domain to be encoded as name (e.g. "gsup.hlr.1234567.imsi") */
enum osmo_mdns_rfc_record_type type;
enum osmo_mdns_rfc_class class;
uint32_t ttl;
uint16_t rdlength;
uint8_t *rdata;
};
char *osmo_mdns_rfc_qname_encode(void *ctx, const char *domain);
char *osmo_mdns_rfc_qname_decode(void *ctx, const char *qname, size_t qname_len);
void osmo_mdns_rfc_header_encode(struct msgb *msg, const struct osmo_mdns_rfc_header *hdr);
int osmo_mdns_rfc_header_decode(const uint8_t *data, size_t data_len, struct osmo_mdns_rfc_header *hdr);
int osmo_mdns_rfc_question_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_rfc_question *qst);
struct osmo_mdns_rfc_question *osmo_mdns_rfc_question_decode(void *ctx, const uint8_t *data, size_t data_len);
int osmo_mdns_rfc_record_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_rfc_record *rec);
struct osmo_mdns_rfc_record *osmo_mdns_rfc_record_decode(void *ctx, const uint8_t *data, size_t data_len,
size_t *record_len);

View File

@@ -0,0 +1,33 @@
/* 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/>.
*/
#pragma once
#include <netdb.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
struct osmo_mdns_sock {
struct osmo_fd osmo_fd;
struct addrinfo *ai;
};
struct osmo_mdns_sock *osmo_mdns_sock_init(void *ctx, const char *ip, unsigned int port,
int (*cb)(struct osmo_fd *fd, unsigned int what),
void *data, unsigned int priv_nr);
int osmo_mdns_sock_send(const struct osmo_mdns_sock *mdns_sock, struct msgb *msg);
void osmo_mdns_sock_cleanup(struct osmo_mdns_sock *mdns_sock);

View File

@@ -0,0 +1,121 @@
/* 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/>.
*/
/*! \defgroup mslookup Distributed GSM: finding subscribers
* @{
* \file mslookup.h
*/
#pragma once
#include <osmocom/core/utils.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/gsm/protocol/gsm_23_003.h>
#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);
/*! @} */

View File

@@ -0,0 +1,132 @@
/* 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/>.
*/
#pragma once
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/mslookup/mslookup.h>
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);

View File

@@ -0,0 +1,34 @@
/* 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/>.
*/
#pragma once
/*! MS lookup fake API for testing purposes. */
#include <osmocom/mslookup/mslookup_client.h>
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);

View File

@@ -0,0 +1,38 @@
/* 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/>.
*/
#pragma once
#include <stdint.h>
struct osmo_mslookup_client;
struct osmo_mslookup_client_method;
/*! MS Lookup mDNS server bind default IP. Taken from the Administratevly Scoped block, particularly the Organizational
* Scoped range, https://tools.ietf.org/html/rfc2365 . */
#define OSMO_MSLOOKUP_MDNS_IP4 "239.192.23.42"
#define OSMO_MSLOOKUP_MDNS_IP6 "ff08::23:42" // <-- TODO: sane?
#define OSMO_MSLOOKUP_MDNS_PORT 4266
struct osmo_mslookup_client_method *osmo_mslookup_client_add_mdns(struct osmo_mslookup_client *client, const char *ip,
uint16_t port, int initial_packet_id,
const char *domain_suffix);
const struct osmo_sockaddr_str *osmo_mslookup_client_method_mdns_get_bind_addr(struct osmo_mslookup_client_method *dns_method);
const char *osmo_mslookup_client_method_mdns_get_domain_suffix(struct osmo_mslookup_client_method *dns_method);

11
libosmo-mslookup.pc.in Normal file
View File

@@ -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}/

View File

@@ -5,12 +5,14 @@ CREATE TABLE subscriber (
imsi VARCHAR(15) UNIQUE NOT NULL,
-- Chapter 2.1.2
msisdn VARCHAR(15) UNIQUE,
-- Chapter 2.2.3: Most recent / current IMEI
-- Chapter 2.2.3: Most recent / current IMEISV
imeisv VARCHAR,
-- Chapter 2.1.9: Most recent / current IMEI
imei VARCHAR(14),
-- Chapter 2.4.5
vlr_number VARCHAR(15),
-- Chapter 2.4.6
hlr_number VARCHAR(15),
msc_number VARCHAR(15),
-- Chapter 2.4.8.1
sgsn_number VARCHAR(15),
-- Chapter 2.13.10
@@ -40,7 +42,13 @@ CREATE TABLE subscriber (
-- Timestamp of last location update seen from subscriber
-- The value is a string which encodes a UTC timestamp in granularity of seconds.
last_lu_seen TIMESTAMP default NULL
last_lu_seen TIMESTAMP default NULL,
last_lu_seen_ps TIMESTAMP default NULL,
-- When a LU was received via a proxy, that proxy's hlr_number is stored here,
-- while vlr_number reflects the MSC on the far side of that proxy.
vlr_via_proxy VARCHAR,
sgsn_via_proxy VARCHAR
);
CREATE TABLE subscriber_apn (
@@ -67,11 +75,12 @@ CREATE TABLE auc_3g (
op VARCHAR(32), -- hex string: operator's secret key (128bit)
opc VARCHAR(32), -- hex string: derived from OP and K (128bit)
sqn INTEGER NOT NULL DEFAULT 0, -- sequence number of key usage
ind_bitlen INTEGER NOT NULL DEFAULT 5 -- nr of index bits at lower SQN end
-- nr of index bits at lower SQN end
ind_bitlen INTEGER NOT NULL DEFAULT 5
);
CREATE UNIQUE INDEX idx_subscr_imsi ON subscriber (imsi);
-- Set HLR database schema version number
-- Note: This constant is currently duplicated in src/db.c and must be kept in sync!
PRAGMA user_version = 1;
PRAGMA user_version = 5;

View File

@@ -1,4 +1,7 @@
SUBDIRS = gsupclient
SUBDIRS = \
gsupclient \
mslookup \
$(NULL)
AM_CFLAGS = \
-Wall \
@@ -6,11 +9,13 @@ AM_CFLAGS = \
$(LIBOSMOGSM_CFLAGS) \
$(LIBOSMOVTY_CFLAGS) \
$(LIBOSMOCTRL_CFLAGS) \
$(LIBOSMOMSLOOKUP_CFLAGS) \
$(LIBOSMOABIS_CFLAGS) \
$(SQLITE3_CFLAGS) \
$(NULL)
AM_CPPFLAGS = -I$(top_srcdir)/include \
-I$(top_builddir)/include \
$(NULL)
EXTRA_DIST = \
@@ -24,18 +29,6 @@ BUILT_SOURCES = \
CLEANFILES = $(BUILT_SOURCES)
noinst_HEADERS = \
auc.h \
db.h \
hlr.h \
luop.h \
gsup_router.h \
gsup_server.h \
logging.h \
rand.h \
ctrl.h \
hlr_vty.h \
hlr_vty_subscr.h \
hlr_ussd.h \
db_bootstrap.h \
$(NULL)
@@ -49,7 +42,6 @@ osmo_hlr_SOURCES = \
auc.c \
ctrl.c \
db.c \
luop.c \
db_auc.c \
db_hlr.c \
gsup_router.c \
@@ -61,13 +53,24 @@ osmo_hlr_SOURCES = \
hlr_vty_subscr.c \
gsup_send.c \
hlr_ussd.c \
proxy.c \
dgsm.c \
remote_hlr.c \
lu_fsm.c \
timestamp.c \
mslookup_server.c \
mslookup_server_mdns.c \
dgsm_vty.c \
$(NULL)
osmo_hlr_LDADD = \
$(top_builddir)/src/gsupclient/libosmo-gsup-client.la \
$(top_builddir)/src/mslookup/libosmo-mslookup.la \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOVTY_LIBS) \
$(LIBOSMOCTRL_LIBS) \
$(LIBOSMOMSLOOKUP_LIBS) \
$(LIBOSMOABIS_LIBS) \
$(SQLITE3_LIBS) \
$(NULL)
@@ -79,6 +82,7 @@ osmo_hlr_db_tool_SOURCES = \
logging.c \
rand_urandom.c \
dbd_decode_binary.c \
$(srcdir)/gsupclient/cni_peer_id.c \
$(NULL)
osmo_hlr_db_tool_LDADD = \
@@ -87,21 +91,6 @@ osmo_hlr_db_tool_LDADD = \
$(SQLITE3_LIBS) \
$(NULL)
db_test_SOURCES = \
auc.c \
db.c \
db_auc.c \
db_test.c \
logging.c \
rand_fake.c \
$(NULL)
db_test_LDADD = \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(SQLITE3_LIBS) \
$(NULL)
osmo_euse_demo_SOURCES = \
osmo-euse-demo.c \
$(NULL)
@@ -112,6 +101,11 @@ osmo_euse_demo_LDADD = \
$(LIBOSMOGSM_LIBS) \
$(NULL)
if DB_SQLITE_DEBUG
osmo_hlr_SOURCES += db_debug.c
osmo_hlr_db_tool_SOURCES += db_debug.c
endif
BOOTSTRAP_SQL = $(top_srcdir)/sql/hlr.sql
db_bootstrap.h: $(BOOTSTRAP_SQL) $(srcdir)/db_sql2c.sed

View File

@@ -23,8 +23,8 @@
#include <osmocom/core/utils.h>
#include <osmocom/crypt/auth.h>
#include "logging.h"
#include "rand.h"
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/rand.h>
#define hexb(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf))
#define hex(buf,sz) osmo_hexdump_nospc((void*)buf, sz)

View File

@@ -28,9 +28,9 @@
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/ctrl/ports.h>
#include "hlr.h"
#include "ctrl.h"
#include "db.h"
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/ctrl.h>
#include <osmocom/hlr/db.h>
#define SEL_BY "by-"
#define SEL_BY_IMSI SEL_BY "imsi-"
@@ -95,7 +95,7 @@ static bool get_subscriber(struct db_context *dbc,
cmd->reply = "No such subscriber.";
return false;
default:
cmd->reply = "An unknown error has occured during get_subscriber().";
cmd->reply = "An unknown error has occurred during get_subscriber().";
return false;
}
}

324
src/db.c
View File

@@ -23,17 +23,18 @@
#include <sqlite3.h>
#include <string.h>
#include "logging.h"
#include "db.h"
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/db.h>
#include "db_bootstrap.h"
/* This constant is currently duplicated in sql/hlr.sql and must be kept in sync! */
#define CURRENT_SCHEMA_VERSION 1
#define CURRENT_SCHEMA_VERSION 5
#define SEL_COLUMNS \
"id," \
"imsi," \
"msisdn," \
"imei," \
"vlr_number," \
"sgsn_number," \
"sgsn_address," \
@@ -44,14 +45,27 @@
"lmsi," \
"ms_purged_cs," \
"ms_purged_ps," \
"last_lu_seen"
"last_lu_seen," \
"last_lu_seen_ps," \
"vlr_via_proxy," \
"sgsn_via_proxy"
static const char *stmt_sql[] = {
[DB_STMT_SEL_ALL] = "SELECT " SEL_COLUMNS " FROM subscriber;",
[DB_STMT_SEL_ALL_ORDER_LAST_SEEN] = "SELECT " SEL_COLUMNS " FROM subscriber "
"WHERE last_lu_seen IS NOT NULL ORDER BY last_lu_seen;",
[DB_STMT_SEL_FILTER_MSISDN] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE msisdn LIKE $search ORDER BY msisdn",
[DB_STMT_SEL_FILTER_IMSI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imsi LIKE $search ORDER BY imsi",
[DB_STMT_SEL_FILTER_IMEI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imei LIKE $search ORDER BY imei",
[DB_STMT_SEL_FILTER_CS] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE nam_cs = $search ORDER BY last_lu_seen",
[DB_STMT_SEL_FILTER_PS] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE nam_ps = $search ORDER BY last_lu_seen",
[DB_STMT_SEL_BY_IMSI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imsi = ?",
[DB_STMT_SEL_BY_MSISDN] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE msisdn = ?",
[DB_STMT_SEL_BY_ID] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE id = ?",
[DB_STMT_UPD_VLR_BY_ID] = "UPDATE subscriber SET vlr_number = $number WHERE id = $subscriber_id",
[DB_STMT_UPD_SGSN_BY_ID] = "UPDATE subscriber SET sgsn_number = $number WHERE id = $subscriber_id",
[DB_STMT_SEL_BY_IMEI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imei = ?",
[DB_STMT_UPD_VLR_BY_ID] = "UPDATE subscriber SET vlr_number = $number, vlr_via_proxy = $proxy WHERE id = $subscriber_id",
[DB_STMT_UPD_SGSN_BY_ID] = "UPDATE subscriber SET sgsn_number = $number, sgsn_via_proxy = $proxy WHERE id = $subscriber_id",
[DB_STMT_UPD_IMEI_BY_IMSI] = "UPDATE subscriber SET imei = $imei WHERE imsi = $imsi",
[DB_STMT_AUC_BY_IMSI] =
"SELECT id, algo_id_2g, ki, algo_id_3g, k, op, opc, sqn, ind_bitlen"
" FROM subscriber"
@@ -63,7 +77,7 @@ static const char *stmt_sql[] = {
[DB_STMT_UPD_PURGE_PS_BY_IMSI] = "UPDATE subscriber SET ms_purged_ps = $val WHERE imsi = $imsi",
[DB_STMT_UPD_NAM_CS_BY_IMSI] = "UPDATE subscriber SET nam_cs = $val WHERE imsi = $imsi",
[DB_STMT_UPD_NAM_PS_BY_IMSI] = "UPDATE subscriber SET nam_ps = $val WHERE imsi = $imsi",
[DB_STMT_SUBSCR_CREATE] = "INSERT INTO subscriber (imsi) VALUES ($imsi)",
[DB_STMT_SUBSCR_CREATE] = "INSERT INTO subscriber (imsi, nam_cs, nam_ps) VALUES ($imsi, $nam_cs, $nam_ps)",
[DB_STMT_DEL_BY_ID] = "DELETE FROM subscriber WHERE id = $subscriber_id",
[DB_STMT_SET_MSISDN_BY_IMSI] = "UPDATE subscriber SET msisdn = $msisdn WHERE imsi = $imsi",
[DB_STMT_DELETE_MSISDN_BY_IMSI] = "UPDATE subscriber SET msisdn = NULL WHERE imsi = $imsi",
@@ -76,6 +90,9 @@ static const char *stmt_sql[] = {
" VALUES($subscriber_id, $algo_id_3g, $k, $op, $opc, $ind_bitlen)",
[DB_STMT_AUC_3G_DELETE] = "DELETE FROM auc_3g WHERE subscriber_id = $subscriber_id",
[DB_STMT_SET_LAST_LU_SEEN] = "UPDATE subscriber SET last_lu_seen = datetime($val, 'unixepoch') WHERE id = $subscriber_id",
[DB_STMT_SET_LAST_LU_SEEN_PS] = "UPDATE subscriber SET last_lu_seen_ps = datetime($val, 'unixepoch') WHERE id = $subscriber_id",
[DB_STMT_EXISTS_BY_IMSI] = "SELECT 1 FROM subscriber WHERE imsi = $imsi",
[DB_STMT_EXISTS_BY_MSISDN] = "SELECT 1 FROM subscriber WHERE msisdn = $msisdn",
};
static void sql3_error_log_cb(void *arg, int err_code, const char *msg)
@@ -176,6 +193,25 @@ bool db_bind_int64(sqlite3_stmt *stmt, const char *param_name, int64_t nr)
return true;
}
bool db_bind_null(sqlite3_stmt *stmt, const char *param_name)
{
int rc;
int idx = param_name ? sqlite3_bind_parameter_index(stmt, param_name) : 1;
if (idx < 1) {
LOGP(DDB, LOGL_ERROR, "Error composing SQL, cannot bind parameter '%s'\n",
param_name);
return false;
}
rc = sqlite3_bind_null(stmt, idx);
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "Error binding NULL to SQL parameter %s: %d\n",
param_name ? param_name : "#1", rc);
db_remove_reset(stmt);
return false;
}
return true;
}
void db_close(struct db_context *dbc)
{
unsigned int i;
@@ -196,28 +232,38 @@ void db_close(struct db_context *dbc)
talloc_free(dbc);
}
static int db_bootstrap(struct db_context *dbc)
static int db_run_statements(struct db_context *dbc, const char **statements, size_t statements_count)
{
int rc;
int i;
for (i = 0; i < ARRAY_SIZE(stmt_bootstrap_sql); i++) {
int rc;
for (i = 0; i < statements_count; i++) {
const char *stmt_str = statements[i];
sqlite3_stmt *stmt;
rc = sqlite3_prepare_v2(dbc->db, stmt_bootstrap_sql[i], -1, &stmt, NULL);
rc = sqlite3_prepare_v2(dbc->db, stmt_str, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", stmt_bootstrap_sql[i]);
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", stmt_str);
return rc;
}
rc = sqlite3_step(stmt);
db_remove_reset(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Cannot bootstrap database: SQL error: (%d) %s,"
" during stmt '%s'",
rc, sqlite3_errmsg(dbc->db), stmt_bootstrap_sql[i]);
LOGP(DDB, LOGL_ERROR, "SQL error: (%d) %s, during stmt '%s'",
rc, sqlite3_errmsg(dbc->db), stmt_str);
return rc;
}
}
return rc;
}
static int db_bootstrap(struct db_context *dbc)
{
int rc = db_run_statements(dbc, stmt_bootstrap_sql, ARRAY_SIZE(stmt_bootstrap_sql));
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Cannot bootstrap database\n");
return rc;
}
return SQLITE_OK;
}
@@ -258,38 +304,198 @@ static bool db_is_bootstrapped_v0(struct db_context *dbc)
static int
db_upgrade_v1(struct db_context *dbc)
{
sqlite3_stmt *stmt;
int rc;
const char *update_stmt_sql = "ALTER TABLE subscriber ADD COLUMN last_lu_seen TIMESTAMP default NULL";
const char *set_schema_version_sql = "PRAGMA user_version = 1";
const char *statements[] = {
"ALTER TABLE subscriber ADD COLUMN last_lu_seen TIMESTAMP default NULL",
"PRAGMA user_version = 1",
};
rc = sqlite3_prepare_v2(dbc->db, update_stmt_sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", update_stmt_sql);
return rc;
}
rc = sqlite3_step(stmt);
db_remove_reset(stmt);
sqlite3_finalize(stmt);
rc = db_run_statements(dbc, statements, ARRAY_SIZE(statements));
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version %d\n", 1);
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 1\n");
return rc;
}
rc = sqlite3_prepare_v2(dbc->db, set_schema_version_sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", set_schema_version_sql);
return rc;
}
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version %d\n", 1);
db_remove_reset(stmt);
sqlite3_finalize(stmt);
return rc;
}
static int db_upgrade_v2(struct db_context *dbc)
{
int rc;
const char *statements[] = {
"ALTER TABLE subscriber ADD COLUMN imei VARCHAR(14)",
"PRAGMA user_version = 2",
};
rc = db_run_statements(dbc, statements, ARRAY_SIZE(statements));
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 2\n");
return rc;
}
return rc;
}
static int db_upgrade_v3(struct db_context *dbc)
{
int rc;
/* A newer SQLite version would allow simply 'ATLER TABLE subscriber RENAME COLUMN hlr_number TO msc_number'.
* This is a really expensive workaround for that in order to cover earlier SQLite versions as well:
* Create a new table with the new column name and copy the data over (https://www.sqlite.org/faq.html#q11).
*/
#define SUBSCR_V3_CREATE \
"(\n" \
"-- OsmoHLR's DB scheme is modelled roughly after TS 23.008 version 13.3.0\n" \
" id INTEGER PRIMARY KEY,\n" \
" -- Chapter 2.1.1.1\n" \
" imsi VARCHAR(15) UNIQUE NOT NULL,\n" \
" -- Chapter 2.1.2\n" \
" msisdn VARCHAR(15) UNIQUE,\n" \
" -- Chapter 2.2.3: Most recent / current IMEISV\n" \
" imeisv VARCHAR,\n" \
" -- Chapter 2.1.9: Most recent / current IMEI\n" \
" imei VARCHAR(14),\n" \
" -- Chapter 2.4.5\n" \
" vlr_number VARCHAR(15),\n" \
" -- Chapter 2.4.6\n" \
" msc_number VARCHAR(15),\n" \
" -- Chapter 2.4.8.1\n" \
" sgsn_number VARCHAR(15),\n" \
" -- Chapter 2.13.10\n" \
" sgsn_address VARCHAR,\n" \
" -- Chapter 2.4.8.2\n" \
" ggsn_number VARCHAR(15),\n" \
" -- Chapter 2.4.9.2\n" \
" gmlc_number VARCHAR(15),\n" \
" -- Chapter 2.4.23\n" \
" smsc_number VARCHAR(15),\n" \
" -- Chapter 2.4.24\n" \
" periodic_lu_tmr INTEGER,\n" \
" -- Chapter 2.13.115\n" \
" periodic_rau_tau_tmr INTEGER,\n" \
" -- Chapter 2.1.1.2: network access mode\n" \
" nam_cs BOOLEAN NOT NULL DEFAULT 1,\n" \
" nam_ps BOOLEAN NOT NULL DEFAULT 1,\n" \
" -- Chapter 2.1.8\n" \
" lmsi INTEGER,\n" \
\
" -- The below purged flags might not even be stored non-volatile,\n" \
" -- refer to TS 23.012 Chapter 3.6.1.4\n" \
" -- Chapter 2.7.5\n" \
" ms_purged_cs BOOLEAN NOT NULL DEFAULT 0,\n" \
" -- Chapter 2.7.6\n" \
" ms_purged_ps BOOLEAN NOT NULL DEFAULT 0,\n" \
\
" -- Timestamp of last location update seen from subscriber\n" \
" -- The value is a string which encodes a UTC timestamp in granularity of seconds.\n" \
" last_lu_seen TIMESTAMP default NULL\n" \
")\n"
#define SUBSCR_V2_COLUMN_NAMES \
"id," \
"imsi," \
"msisdn," \
"imeisv," \
"imei," \
"vlr_number," \
"hlr_number," \
"sgsn_number," \
"sgsn_address," \
"ggsn_number," \
"gmlc_number," \
"smsc_number," \
"periodic_lu_tmr," \
"periodic_rau_tau_tmr," \
"nam_cs," \
"nam_ps," \
"lmsi," \
"ms_purged_cs," \
"ms_purged_ps," \
"last_lu_seen"
#define SUBSCR_V3_COLUMN_NAMES \
"id," \
"imsi," \
"msisdn," \
"imeisv," \
"imei," \
"vlr_number," \
"msc_number," \
"sgsn_number," \
"sgsn_address," \
"ggsn_number," \
"gmlc_number," \
"smsc_number," \
"periodic_lu_tmr," \
"periodic_rau_tau_tmr," \
"nam_cs," \
"nam_ps," \
"lmsi," \
"ms_purged_cs," \
"ms_purged_ps," \
"last_lu_seen"
const char *statements[] = {
"BEGIN TRANSACTION",
"CREATE TEMPORARY TABLE subscriber_backup" SUBSCR_V3_CREATE,
"INSERT INTO subscriber_backup SELECT " SUBSCR_V2_COLUMN_NAMES " FROM subscriber",
"DROP TABLE subscriber",
"CREATE TABLE subscriber" SUBSCR_V3_CREATE,
"INSERT INTO subscriber SELECT " SUBSCR_V3_COLUMN_NAMES " FROM subscriber_backup",
"DROP TABLE subscriber_backup",
"COMMIT",
"PRAGMA user_version = 3",
};
rc = db_run_statements(dbc, statements, ARRAY_SIZE(statements));
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 3\n");
return rc;
}
return rc;
}
static int db_upgrade_v4(struct db_context *dbc)
{
int rc;
const char *statements[] = {
"ALTER TABLE subscriber ADD COLUMN last_lu_seen_ps TIMESTAMP default NULL",
"PRAGMA user_version = 4",
};
rc = db_run_statements(dbc, statements, ARRAY_SIZE(statements));
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 4\n");
return rc;
}
return rc;
}
static int db_upgrade_v5(struct db_context *dbc)
{
int rc;
const char *statements[] = {
"ALTER TABLE subscriber ADD COLUMN vlr_via_proxy VARCHAR",
"ALTER TABLE subscriber ADD COLUMN sgsn_via_proxy VARCHAR",
"PRAGMA user_version = 5",
};
rc = db_run_statements(dbc, statements, ARRAY_SIZE(statements));
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 5\n");
return rc;
}
return rc;
}
typedef int (*db_upgrade_func_t)(struct db_context *dbc);
static db_upgrade_func_t db_upgrade_path[] = {
db_upgrade_v1,
db_upgrade_v2,
db_upgrade_v3,
db_upgrade_v4,
db_upgrade_v5,
};
static int db_get_user_version(struct db_context *dbc)
{
const char *user_version_sql = "PRAGMA user_version";
@@ -326,6 +532,17 @@ struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite_logg
LOGP(DDB, LOGL_INFO, "Compiled against SQLite3 lib version %s\n", SQLITE_VERSION);
LOGP(DDB, LOGL_INFO, "Running with SQLite3 lib version %s\n", sqlite3_libversion());
#ifdef SQLITE_USE_TALLOC
/* Configure SQLite3 to use talloc memory allocator */
rc = db_sqlite3_use_talloc(ctx);
if (rc == SQLITE_OK) {
LOGP(DDB, LOGL_NOTICE, "SQLite3 is configured to use talloc\n");
} else {
LOGP(DDB, LOGL_ERROR, "Failed to configure SQLite3 "
"to use talloc, using default memory allocator\n");
}
#endif
dbc->fname = talloc_strdup(dbc, fname);
for (i = 0; i < 0xfffff; i++) {
@@ -365,9 +582,11 @@ struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite_logg
char *err_msg;
rc = sqlite3_exec(dbc->db, "PRAGMA journal_mode=WAL; PRAGMA synchonous = NORMAL;", 0, 0, &err_msg);
if (rc != SQLITE_OK)
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "Unable to set Write-Ahead Logging: %s\n",
err_msg);
sqlite3_free(err_msg);
}
version = db_get_user_version(dbc);
if (version < 0) {
@@ -389,23 +608,16 @@ struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite_logg
LOGP(DDB, LOGL_NOTICE, "Database '%s' has HLR DB schema version %d\n", dbc->fname, version);
if (version < CURRENT_SCHEMA_VERSION && allow_upgrade) {
switch (version) {
case 0:
rc = db_upgrade_v1(dbc);
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Failed to upgrade HLR DB schema to version 1: (rc=%d) %s\n",
rc, sqlite3_errmsg(dbc->db));
goto out_free;
}
version = 1;
/* fall through */
/* case N: ... */
default:
break;
for (; allow_upgrade && (version < ARRAY_SIZE(db_upgrade_path)); version++) {
db_upgrade_func_t upgrade_func = db_upgrade_path[version];
rc = upgrade_func(dbc);
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Failed to upgrade HLR DB schema to version %d: (rc=%d) %s\n",
version+1, rc, sqlite3_errmsg(dbc->db));
goto out_free;
}
LOGP(DDB, LOGL_NOTICE, "Database '%s' has been upgraded to HLR DB schema version %d\n",
dbc->fname, version);
dbc->fname, version+1);
}
if (version != CURRENT_SCHEMA_VERSION) {

View File

@@ -26,10 +26,10 @@
#include <sqlite3.h>
#include "logging.h"
#include "db.h"
#include "auc.h"
#include "rand.h"
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/db.h>
#include <osmocom/hlr/auc.h>
#include <osmocom/hlr/rand.h>
#define LOGAUC(imsi, level, fmt, args ...) LOGP(DAUC, level, "IMSI='%s': " fmt, imsi, ## args)
@@ -73,6 +73,32 @@ out:
return ret;
}
/* hexparse a specific column of a sqlite prepared statement into dst (with length check)
* returns 0 for success, -EIO on error */
static int hexparse_stmt(uint8_t *dst, size_t dst_len, sqlite3_stmt *stmt, int col, const char *col_name,
const char *imsi)
{
const uint8_t *text;
size_t col_len;
/* Bytes are stored as hex strings in database, hence divide length by two */
col_len = sqlite3_column_bytes(stmt, col) / 2;
if (col_len != dst_len) {
LOGAUC(imsi, LOGL_ERROR, "Error reading %s, expected length %lu but has length %lu\n", col_name,
dst_len, col_len);
return -EIO;
}
text = sqlite3_column_text(stmt, col);
if (!text) {
LOGAUC(imsi, LOGL_ERROR, "Error reading %s\n", col_name);
return -EIO;
}
osmo_hexparse((void *)text, dst, dst_len);
return 0;
}
/* obtain the authentication data for a given imsi
* returns 0 for success, negative value on error:
* -ENOENT if the IMSI is not known, -ENOKEY if the IMSI is known but has no auth data,
@@ -113,49 +139,34 @@ int db_get_auth_data(struct db_context *dbc, const char *imsi,
/* obtain result values using sqlite3_column_*() */
if (sqlite3_column_type(stmt, 1) == SQLITE_INTEGER) {
/* we do have some 2G authentication data */
const uint8_t *ki;
aud2g->algo = sqlite3_column_int(stmt, 1);
ki = sqlite3_column_text(stmt, 2);
#if 0
if (sqlite3_column_bytes(stmt, 2) != sizeof(aud2g->u.gsm.ki)) {
LOGAUC(imsi, LOGL_ERROR, "Error reading Ki: %d\n", rc);
if (hexparse_stmt(aud2g->u.gsm.ki, sizeof(aud2g->u.gsm.ki), stmt, 2, "Ki", imsi))
goto end_2g;
}
#endif
osmo_hexparse((void*)ki, (void*)&aud2g->u.gsm.ki, sizeof(aud2g->u.gsm.ki));
aud2g->algo = sqlite3_column_int(stmt, 1);
aud2g->type = OSMO_AUTH_TYPE_GSM;
} else
LOGAUC(imsi, LOGL_DEBUG, "No 2G Auth Data\n");
//end_2g:
end_2g:
if (sqlite3_column_type(stmt, 3) == SQLITE_INTEGER) {
/* we do have some 3G authentication data */
const uint8_t *k, *op, *opc;
aud3g->algo = sqlite3_column_int(stmt, 3);
k = sqlite3_column_text(stmt, 4);
if (!k) {
LOGAUC(imsi, LOGL_ERROR, "Error reading K: %d\n", rc);
if (hexparse_stmt(aud3g->u.umts.k, sizeof(aud3g->u.umts.k), stmt, 4, "K", imsi)) {
ret = -EIO;
goto out;
}
osmo_hexparse((void*)k, (void*)&aud3g->u.umts.k, sizeof(aud3g->u.umts.k));
aud3g->algo = sqlite3_column_int(stmt, 3);
/* UMTS Subscribers can have either OP or OPC */
op = sqlite3_column_text(stmt, 5);
if (!op) {
opc = sqlite3_column_text(stmt, 6);
if (!opc) {
LOGAUC(imsi, LOGL_ERROR, "Error reading OPC: %d\n", rc);
if (sqlite3_column_text(stmt, 5)) {
if (hexparse_stmt(aud3g->u.umts.opc, sizeof(aud3g->u.umts.opc), stmt, 5, "OP", imsi)) {
ret = -EIO;
goto out;
}
osmo_hexparse((void*)opc, (void*)&aud3g->u.umts.opc,
sizeof(aud3g->u.umts.opc));
aud3g->u.umts.opc_is_op = 0;
} else {
osmo_hexparse((void*)op, (void*)&aud3g->u.umts.opc,
sizeof(aud3g->u.umts.opc));
aud3g->u.umts.opc_is_op = 1;
} else {
if (hexparse_stmt(aud3g->u.umts.opc, sizeof(aud3g->u.umts.opc), stmt, 6, "OPC", imsi)) {
ret = -EIO;
goto out;
}
aud3g->u.umts.opc_is_op = 0;
}
aud3g->u.umts.sqn = sqlite3_column_int64(stmt, 7);
aud3g->u.umts.ind_bitlen = sqlite3_column_int(stmt, 8);
@@ -178,7 +189,7 @@ out:
int db_get_auc(struct db_context *dbc, const char *imsi,
unsigned int auc_3g_ind, struct osmo_auth_vector *vec,
unsigned int num_vec, const uint8_t *rand_auts,
const uint8_t *auts)
const uint8_t *auts, bool separation_bit)
{
struct osmo_sub_auth_data aud2g, aud3g;
int64_t subscr_id;
@@ -189,15 +200,22 @@ int db_get_auc(struct db_context *dbc, const char *imsi,
if (rc)
return rc;
/* modulo by the IND bitlen value range. For example, ind_bitlen == 5 would modulo 32:
* 1 << 5 == 0b0100000 == 32
* - 1 == 0b0011111 == bitmask of 5 lowest bits
* x &= 0b0011111 == modulo 32
* Why do this? osmo-hlr cannot possibly choose individual VLR INDs always matching all subscribers' IND_bitlen,
* which might vary wildly. Instead, let hlr.c pass in an arbitrarily high number here, and the modulo does a
* round-robin if the IND pools that this subscriber has available. */
auc_3g_ind &= (1U << aud3g.u.umts.ind_bitlen) - 1;
aud3g.u.umts.ind = auc_3g_ind;
if (aud3g.type == OSMO_AUTH_TYPE_UMTS
&& aud3g.u.umts.ind >= (1U << aud3g.u.umts.ind_bitlen)) {
LOGAUC(imsi, LOGL_NOTICE, "3G auth: SQN's IND bitlen %u is"
" too small to hold an index of %u. Truncating. This"
" may cause numerous additional AUTS resyncing.\n",
aud3g.u.umts.ind_bitlen, aud3g.u.umts.ind);
aud3g.u.umts.ind &= (1U << aud3g.u.umts.ind_bitlen) - 1;
}
/* the first bit (bit0) cannot be used as AMF anymore, but has been
* re-appropriated as the separation bit. See 3GPP TS 33.102 Annex H
* together with 3GPP TS 33.401 / 33.402 / 33.501 */
aud3g.u.umts.amf[0] = aud3g.u.umts.amf[0] & 0x7f;
if (separation_bit)
aud3g.u.umts.amf[0] |= 0x80;
LOGAUC(imsi, LOGL_DEBUG, "Calling to generate %u vectors\n", num_vec);
rc = auc_compute_vectors(vec, num_vec, &aud2g, &aud3g, rand_auts, auts);

86
src/db_debug.c Normal file
View File

@@ -0,0 +1,86 @@
/*
* libtalloc based memory allocator for SQLite3.
*
* (C) 2019 by Vadim Yanitskiy <axilirator@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#include <sqlite3.h>
#include <talloc.h>
#include <errno.h>
/* Dedicated talloc context for SQLite */
static void *db_sqlite_ctx = NULL;
static void *tall_xMalloc(int size)
{
return talloc_size(db_sqlite_ctx, size);
}
static void tall_xFree(void *ptr)
{
talloc_free(ptr);
}
static void *tall_xRealloc(void *ptr, int size)
{
return talloc_realloc_fn(db_sqlite_ctx, ptr, size);
}
static int tall_xSize(void *ptr)
{
return talloc_total_size(ptr);
}
/* DUMMY: talloc doesn't round up the allocation size */
static int tall_xRoundup(int size) { return size; }
/* DUMMY: nothing to initialize */
static int tall_xInit(void *data) { return 0; }
/* DUMMY: nothing to deinitialize */
static void tall_xShutdown(void *data) { }
/* Interface between SQLite and talloc memory allocator */
static const struct sqlite3_mem_methods tall_sqlite_if = {
/* Memory allocation function */
.xMalloc = &tall_xMalloc,
/* Free a prior allocation */
.xFree = &tall_xFree,
/* Resize an allocation */
.xRealloc = &tall_xRealloc,
/* Return the size of an allocation */
.xSize = &tall_xSize,
/* Round up request size to allocation size */
.xRoundup = &tall_xRoundup,
/* Initialize the memory allocator */
.xInit = &tall_xInit,
/* Deinitialize the memory allocator */
.xShutdown = &tall_xShutdown,
/* Argument to xInit() and xShutdown() */
.pAppData = NULL,
};
int db_sqlite3_use_talloc(void *ctx)
{
if (db_sqlite_ctx != NULL)
return -EEXIST;
db_sqlite_ctx = talloc_named_const(ctx, 0, "SQLite3");
return sqlite3_config(SQLITE_CONFIG_MALLOC, &tall_sqlite_if);
}

View File

@@ -28,25 +28,26 @@
#include <time.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/timer.h>
#include <osmocom/crypt/auth.h>
#include <osmocom/gsm/gsm23003.h>
#include <sqlite3.h>
#include "logging.h"
#include "hlr.h"
#include "db.h"
#include "gsup_server.h"
#include "luop.h"
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/db.h>
#include <osmocom/gsupclient/cni_peer_id.h>
#define LOGHLR(imsi, level, fmt, args ...) LOGP(DAUC, level, "IMSI='%s': " fmt, imsi, ## args)
/*! Add new subscriber record to the HLR database.
* \param[in,out] dbc database context.
* \param[in] imsi ASCII string of IMSI digits, is validated.
* \param[in] flags Bitmask of DB_SUBSCR_FLAG_*.
* \returns 0 on success, -EINVAL on invalid IMSI, -EIO on database error.
*/
int db_subscr_create(struct db_context *dbc, const char *imsi)
int db_subscr_create(struct db_context *dbc, const char *imsi, uint8_t flags)
{
sqlite3_stmt *stmt;
int rc;
@@ -61,6 +62,10 @@ int db_subscr_create(struct db_context *dbc, const char *imsi)
if (!db_bind_text(stmt, "$imsi", imsi))
return -EIO;
if (!db_bind_int(stmt, "$nam_cs", (flags & DB_SUBSCR_FLAG_NAM_CS) != 0))
return -EIO;
if (!db_bind_int(stmt, "$nam_ps", (flags & DB_SUBSCR_FLAG_NAM_PS) != 0))
return -EIO;
/* execute the statement */
rc = sqlite3_step(stmt);
@@ -141,8 +146,8 @@ int db_subscr_delete_by_id(struct db_context *dbc, int64_t subscr_id)
/*! Set a subscriber's MSISDN in the HLR database.
* \param[in,out] dbc database context.
* \param[in] imsi ASCII string of IMSI digits, or NULL to remove the MSISDN.
* \param[in] msisdn ASCII string of MSISDN digits.
* \param[in] imsi ASCII string of IMSI digits
* \param[in] msisdn ASCII string of MSISDN digits, or NULL to remove the MSISDN.
* \returns 0 on success, -EINVAL in case of invalid MSISDN string, -EIO on
* database failure, -ENOENT if no such subscriber exists.
*/
@@ -259,11 +264,11 @@ int db_subscr_update_aud_by_id(struct db_context *dbc, int64_t subscr_id,
switch (aud->algo) {
case OSMO_AUTH_ALG_NONE:
case OSMO_AUTH_ALG_MILENAGE:
case OSMO_AUTH_ALG_XOR:
break;
case OSMO_AUTH_ALG_COMP128v1:
case OSMO_AUTH_ALG_COMP128v2:
case OSMO_AUTH_ALG_COMP128v3:
case OSMO_AUTH_ALG_XOR:
LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
" auth algo not suited for 3G: %s\n",
osmo_auth_alg_name(aud->algo));
@@ -386,14 +391,83 @@ out:
return ret;
}
/*! Set a subscriber's IMEI in the HLR database.
* \param[in,out] dbc database context.
* \param[in] imsi ASCII string of IMSI digits
* \param[in] imei ASCII string of identifier digits, or NULL to remove the IMEI.
* \returns 0 on success, -ENOENT when the given subscriber does not exist,
* -EIO on database errors.
*/
int db_subscr_update_imei_by_imsi(struct db_context *dbc, const char* imsi, const char *imei)
{
int rc, ret = 0;
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_UPD_IMEI_BY_IMSI];
if (imei && !osmo_imei_str_valid(imei, false)) {
LOGP(DAUC, LOGL_ERROR, "Cannot update subscriber IMSI='%s': invalid IMEI: '%s'\n", imsi, imei);
return -EINVAL;
}
if (!db_bind_text(stmt, "$imsi", imsi))
return -EIO;
if (imei && !db_bind_text(stmt, "$imei", imei))
return -EIO;
/* execute the statement */
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGP(DAUC, LOGL_ERROR, "Update IMEI for subscriber IMSI='%s': SQL Error: %s\n", imsi,
sqlite3_errmsg(dbc->db));
ret = -EIO;
goto out;
}
/* verify execution result */
rc = sqlite3_changes(dbc->db);
if (!rc) {
LOGP(DAUC, LOGL_ERROR, "Cannot update IMEI for subscriber IMSI='%s': no such subscriber\n", imsi);
ret = -ENOENT;
} else if (rc != 1) {
LOGP(DAUC, LOGL_ERROR, "Update IMEI for subscriber IMSI='%s': SQL modified %d rows (expected 1)\n",
imsi, rc);
ret = -EIO;
}
out:
db_remove_reset(stmt);
return ret;
}
static void parse_last_lu_seen(time_t *dst, const char *last_lu_seen_str, const char *imsi, const char *label)
{
struct tm tm = {0};
time_t val;
if (!last_lu_seen_str || last_lu_seen_str[0] == '\0')
return;
if (strptime(last_lu_seen_str, DB_LAST_LU_SEEN_FMT, &tm) == NULL) {
LOGP(DAUC, LOGL_ERROR, "IMSI-%s: Last LU Seen %s: Cannot parse timestamp '%s'\n",
imsi, label, last_lu_seen_str);
return;
}
errno = 0;
val = mktime(&tm);
if (val == -1) {
LOGP(DAUC, LOGL_ERROR, "IMSI-%s: Last LU Seen %s: Cannot convert timestamp '%s' to time_t: %s\n",
imsi, label, last_lu_seen_str, strerror(errno));
val = 0;
}
*dst = val;
}
/* Common code for db_subscr_get_by_*() functions. */
static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscriber *subscr,
const char **err)
{
int rc;
int ret = 0;
const char *last_lu_seen_str;
struct tm tm;
/* execute the statement */
rc = sqlite3_step(stmt);
@@ -415,31 +489,24 @@ static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscri
subscr->id = sqlite3_column_int64(stmt, 0);
copy_sqlite3_text_to_buf(subscr->imsi, stmt, 1);
copy_sqlite3_text_to_buf(subscr->msisdn, stmt, 2);
copy_sqlite3_text_to_buf(subscr->imei, stmt, 3);
/* FIXME: These should all be BLOBs as they might contain NUL */
copy_sqlite3_text_to_buf(subscr->vlr_number, stmt, 3);
copy_sqlite3_text_to_buf(subscr->sgsn_number, stmt, 4);
copy_sqlite3_text_to_buf(subscr->sgsn_address, stmt, 5);
subscr->periodic_lu_timer = sqlite3_column_int(stmt, 6);
subscr->periodic_rau_tau_timer = sqlite3_column_int(stmt, 7);
subscr->nam_cs = sqlite3_column_int(stmt, 8);
subscr->nam_ps = sqlite3_column_int(stmt, 9);
subscr->lmsi = sqlite3_column_int(stmt, 10);
subscr->ms_purged_cs = sqlite3_column_int(stmt, 11);
subscr->ms_purged_ps = sqlite3_column_int(stmt, 12);
last_lu_seen_str = (const char *)sqlite3_column_text(stmt, 13);
if (last_lu_seen_str && last_lu_seen_str[0] != '\0') {
if (strptime(last_lu_seen_str, DB_LAST_LU_SEEN_FMT, &tm) == NULL) {
LOGP(DAUC, LOGL_ERROR, "Cannot parse last LU timestamp '%s' of subscriber with IMSI='%s': %s\n",
last_lu_seen_str, subscr->imsi, strerror(errno));
} else {
subscr->last_lu_seen = mktime(&tm);
if (subscr->last_lu_seen == -1) {
LOGP(DAUC, LOGL_ERROR, "Cannot convert LU timestamp '%s' to time_t: %s\n",
last_lu_seen_str, strerror(errno));
subscr->last_lu_seen = 0;
}
}
}
copy_sqlite3_text_to_buf(subscr->vlr_number, stmt, 4);
copy_sqlite3_text_to_buf(subscr->sgsn_number, stmt, 5);
copy_sqlite3_text_to_buf(subscr->sgsn_address, stmt, 6);
subscr->periodic_lu_timer = sqlite3_column_int(stmt, 7);
subscr->periodic_rau_tau_timer = sqlite3_column_int(stmt, 8);
subscr->nam_cs = sqlite3_column_int(stmt, 9);
subscr->nam_ps = sqlite3_column_int(stmt, 10);
subscr->lmsi = sqlite3_column_int(stmt, 11);
subscr->ms_purged_cs = sqlite3_column_int(stmt, 12);
subscr->ms_purged_ps = sqlite3_column_int(stmt, 13);
parse_last_lu_seen(&subscr->last_lu_seen, (const char *)sqlite3_column_text(stmt, 14),
subscr->imsi, "CS");
parse_last_lu_seen(&subscr->last_lu_seen_ps, (const char *)sqlite3_column_text(stmt, 15),
subscr->imsi, "PS");
copy_sqlite3_text_to_ipa_name(&subscr->vlr_via_proxy, stmt, 16);
copy_sqlite3_text_to_ipa_name(&subscr->sgsn_via_proxy, stmt, 17);
out:
db_remove_reset(stmt);
@@ -458,6 +525,31 @@ out:
return ret;
}
/*! Check if a subscriber exists in the HLR database.
* \param[in, out] dbc database context.
* \param[in] imsi ASCII string of IMSI digits.
* \returns 0 if it exists, -ENOENT if it does not exist, -EIO on database error.
*/
int db_subscr_exists_by_imsi(struct db_context *dbc, const char *imsi) {
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_EXISTS_BY_IMSI];
const char *err;
int rc;
if (!db_bind_text(stmt, NULL, imsi))
return -EIO;
rc = sqlite3_step(stmt);
db_remove_reset(stmt);
if (rc == SQLITE_ROW)
return 0; /* exists */
if (rc == SQLITE_DONE)
return -ENOENT; /* does not exist */
err = sqlite3_errmsg(dbc->db);
LOGP(DAUC, LOGL_ERROR, "Failed to check if subscriber exists by IMSI='%s': %s\n", imsi, err);
return rc;
}
/*! Retrieve subscriber data from the HLR database.
* \param[in,out] dbc database context.
* \param[in] imsi ASCII string of IMSI digits.
@@ -476,12 +568,39 @@ int db_subscr_get_by_imsi(struct db_context *dbc, const char *imsi,
return -EIO;
rc = db_sel(dbc, stmt, subscr, &err);
if (rc)
if (rc && rc != -ENOENT)
LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: IMSI='%s': %s\n",
imsi, err);
return rc;
}
/*! Check if a subscriber exists in the HLR database.
* \param[in, out] dbc database context.
* \param[in] msisdn ASCII string of MSISDN digits.
* \returns 0 if it exists, -ENOENT if it does not exist, -EIO on database error.
*/
int db_subscr_exists_by_msisdn(struct db_context *dbc, const char *msisdn)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_EXISTS_BY_MSISDN];
const char *err;
int rc;
if (!db_bind_text(stmt, NULL, msisdn))
return -EIO;
rc = sqlite3_step(stmt);
db_remove_reset(stmt);
if (rc == SQLITE_ROW)
return 0; /* exists */
if (rc == SQLITE_DONE)
return -ENOENT; /* does not exist */
err = sqlite3_errmsg(dbc->db);
LOGP(DAUC, LOGL_ERROR, "Failed to check if subscriber exists "
"by MSISDN='%s': %s\n", msisdn, err);
return rc;
}
/*! Retrieve subscriber data from the HLR database.
* \param[in,out] dbc database context.
* \param[in] msisdn ASCII string of MSISDN digits.
@@ -500,12 +619,100 @@ int db_subscr_get_by_msisdn(struct db_context *dbc, const char *msisdn,
return -EIO;
rc = db_sel(dbc, stmt, subscr, &err);
if (rc)
if (rc && rc != -ENOENT)
LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: MSISDN='%s': %s\n",
msisdn, err);
return rc;
}
/*! Retrieve subscriber data from the HLR database.
* \param[in,out] dbc database context.
* \param[in] filter_type ASCII string of identifier type to search.
* \param[in] filter ASCII string to search.
* \param[in] get_cb pointer to call back function for data.
* \param[in,out] data pointer to pass to callback function.
* \param[in,out] count counter for number of matched subscribers.
* \param[in,our] err
* \returns 0 on success, -ENOENT if no subscriber was found, -EIO on
* database error.
*/
int db_subscrs_get(struct db_context *dbc, const char *filter_type, const char *filter,
void (*get_cb)(struct hlr_subscriber *subscr, void *data), void *data,
int *count, const char **err)
{
sqlite3_stmt *stmt;
char search[256];
int rc;
struct hlr_subscriber subscr;
bool show_ls = false;
if (!filter_type) {
stmt = dbc->stmt[DB_STMT_SEL_ALL];
} else if (strcmp(filter_type, "imei") == 0) {
stmt = dbc->stmt[DB_STMT_SEL_FILTER_IMEI];
} else if (strcmp(filter_type, "imsi") == 0) {
stmt = dbc->stmt[DB_STMT_SEL_FILTER_IMSI];
} else if (strcmp(filter_type, "msisdn") == 0) {
stmt = dbc->stmt[DB_STMT_SEL_FILTER_MSISDN];
} else if (strcmp(filter_type, "cs") == 0) {
stmt = dbc->stmt[DB_STMT_SEL_FILTER_CS];
} else if (strcmp(filter_type, "ps") == 0) {
stmt = dbc->stmt[DB_STMT_SEL_FILTER_PS];
} else if (strcmp(filter_type, "last_lu_seen") == 0) {
show_ls = true;
stmt = dbc->stmt[DB_STMT_SEL_ALL_ORDER_LAST_SEEN];
} else {
return -EIO;
}
if (filter_type && filter && strcmp(filter_type, "last_lu_seen") != 0) {
if (strcmp(filter, "on") == 0) {
sprintf(search, "%s", "1");
} else if (strcmp(filter, "off") == 0) {
sprintf(search, "%s", "0");
} else {
sprintf(search, "%%%s%%", filter);
}
if (!db_bind_text(stmt, "$search", search)) {
*err = sqlite3_errmsg(dbc->db);
return -EIO;
}
}
rc = sqlite3_step(stmt);
if (rc == SQLITE_DONE) {
db_remove_reset(stmt);
*err = "No matching subscriber(s)";
return -ENOENT;
}
while (rc == SQLITE_ROW) {
subscr = (struct hlr_subscriber){
.id = sqlite3_column_int64(stmt, 0),};
copy_sqlite3_text_to_buf(subscr.imsi, stmt, 1);
copy_sqlite3_text_to_buf(subscr.msisdn, stmt, 2);
copy_sqlite3_text_to_buf(subscr.imei, stmt, 3);
subscr.nam_cs = sqlite3_column_int(stmt, 9);
subscr.nam_ps = sqlite3_column_int(stmt, 10);
if (show_ls)
parse_last_lu_seen(&subscr.last_lu_seen, (const char *)sqlite3_column_text(stmt, 14),
subscr.imsi, "CS");
get_cb(&subscr, data);
rc = sqlite3_step(stmt);
(*count)++;
}
db_remove_reset(stmt);
if (rc != SQLITE_DONE) {
*err = sqlite3_errmsg(dbc->db);
LOGP(DAUC, LOGL_ERROR, "Cannot read subscribers from db:: %s\n", *err);
return rc;
}
*err = NULL;
return 0;
}
/*! Retrieve subscriber data from the HLR database.
* \param[in,out] dbc database context.
* \param[in] id ID of the subscriber in the HLR db.
@@ -524,12 +731,34 @@ int db_subscr_get_by_id(struct db_context *dbc, int64_t id,
return -EIO;
rc = db_sel(dbc, stmt, subscr, &err);
if (rc)
if (rc && rc != -ENOENT)
LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: ID=%" PRId64 ": %s\n",
id, err);
return rc;
}
/*! Retrieve subscriber data from the HLR database.
* \param[in,out] dbc database context.
* \param[in] imei ASCII string of identifier digits
* \param[out] subscr place retrieved data in this struct.
* \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
* database error.
*/
int db_subscr_get_by_imei(struct db_context *dbc, const char *imei, struct hlr_subscriber *subscr)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_IMEI];
const char *err;
int rc;
if (!db_bind_text(stmt, NULL, imei))
return -EIO;
rc = db_sel(dbc, stmt, subscr, &err);
if (rc && rc != -ENOENT)
LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: IMEI=%s: %s\n", imei, err);
return rc;
}
/*! You should use hlr_subscr_nam() instead; enable or disable PS or CS for a
* subscriber without notifying GSUP clients.
* \param[in,out] dbc database context.
@@ -595,7 +824,8 @@ out:
* -EIO on database errors.
*/
int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
const char *vlr_or_sgsn_number, bool is_ps)
const struct osmo_ipa_name *vlr_name, bool is_ps,
const struct osmo_ipa_name *via_proxy)
{
sqlite3_stmt *stmt;
int rc, ret = 0;
@@ -607,9 +837,17 @@ int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
return -EIO;
if (!db_bind_text(stmt, "$number", vlr_or_sgsn_number))
if (!db_bind_text(stmt, "$number", (char*)vlr_name->val))
return -EIO;
if (via_proxy && via_proxy->len) {
if (!db_bind_text(stmt, "$proxy", (char*)via_proxy->val))
return -EIO;
} else {
if (!db_bind_null(stmt, "$proxy"))
return -EIO;
}
/* execute the statement */
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
@@ -643,7 +881,7 @@ int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
goto out;
}
stmt = dbc->stmt[DB_STMT_SET_LAST_LU_SEEN];
stmt = dbc->stmt[is_ps? DB_STMT_SET_LAST_LU_SEEN_PS : DB_STMT_SET_LAST_LU_SEEN];
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
return -EIO;
@@ -734,51 +972,3 @@ out:
return ret;
}
/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients.
* \param[in,out] hlr Global hlr context.
* \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call.
* \param[in] nam_val True to enable CS/PS, false to disable.
* \param[in] is_ps True to enable/disable PS, false for CS.
* \returns 0 on success, ENOEXEC if there is no need to change, a negative
* value on error.
*/
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps)
{
int rc;
struct lu_operation *luop;
struct osmo_gsup_conn *co;
bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs;
if (is_val == nam_val) {
LOGHLR(subscr->imsi, LOGL_DEBUG, "Already has the requested value when asked to %s %s\n",
nam_val ? "enable" : "disable", is_ps ? "PS" : "CS");
return ENOEXEC;
}
rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps);
if (rc)
return rc > 0? -rc : rc;
/* If we're disabling, send a notice out to the GSUP client that is
* responsible. Otherwise no need. */
if (nam_val)
return 0;
/* FIXME: only send to single SGSN where latest update for IMSI came from */
llist_for_each_entry(co, &hlr->gs->clients, list) {
luop = lu_op_alloc_conn(co);
if (!luop) {
LOGHLR(subscr->imsi, LOGL_ERROR,
"Cannot notify GSUP client, cannot allocate lu_operation,"
" for %s:%u\n",
co && co->conn && co->conn->server? co->conn->server->addr : "unset",
co && co->conn && co->conn->server? co->conn->server->port : 0);
continue;
}
luop->subscr = *subscr;
lu_op_tx_del_subscr_data(luop);
lu_op_free(luop);
}
return 0;
}

247
src/dgsm.c Normal file
View File

@@ -0,0 +1,247 @@
/* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include <errno.h>
#include <osmocom/core/logging.h>
#include <osmocom/mslookup/mslookup_client.h>
#include <osmocom/mslookup/mslookup_client_mdns.h>
#include <osmocom/gsupclient/gsup_client.h>
#include <osmocom/gsupclient/cni_peer_id.h>
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/db.h>
#include <osmocom/hlr/gsup_router.h>
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/dgsm.h>
#include <osmocom/hlr/proxy.h>
#include <osmocom/hlr/remote_hlr.h>
#include <osmocom/hlr/mslookup_server.h>
#include <osmocom/hlr/mslookup_server_mdns.h>
#include <osmocom/hlr/dgsm.h>
void *dgsm_ctx = NULL;
static void resolve_hlr_result_cb(struct osmo_mslookup_client *client,
uint32_t request_handle,
const struct osmo_mslookup_query *query,
const struct osmo_mslookup_result *result)
{
struct proxy *proxy = g_hlr->gs->proxy;
struct proxy_subscr proxy_subscr;
const struct osmo_sockaddr_str *remote_hlr_addr;
/* A remote HLR is answering back, indicating that it is the home HLR for a given IMSI.
* There should be a mostly empty proxy entry for that IMSI.
* Add the remote address data in the proxy. */
if (query->id.type != OSMO_MSLOOKUP_ID_IMSI) {
LOGP(DDGSM, LOGL_ERROR, "Expected IMSI ID type in mslookup query+result: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
return;
}
if (result->rc != OSMO_MSLOOKUP_RC_RESULT) {
LOG_DGSM(query->id.imsi, LOGL_ERROR, "Failed to resolve remote HLR: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
proxy_subscr_del(proxy, query->id.imsi);
return;
}
if (osmo_sockaddr_str_is_nonzero(&result->host_v4))
remote_hlr_addr = &result->host_v4;
else if (osmo_sockaddr_str_is_nonzero(&result->host_v6))
remote_hlr_addr = &result->host_v6;
else {
LOG_DGSM(query->id.imsi, LOGL_ERROR, "Invalid address for remote HLR: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
proxy_subscr_del(proxy, query->id.imsi);
return;
}
if (proxy_subscr_get_by_imsi(&proxy_subscr, proxy, query->id.imsi)) {
LOG_DGSM(query->id.imsi, LOGL_ERROR, "No proxy entry for mslookup result: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
return;
}
proxy_subscr_remote_hlr_resolved(proxy, &proxy_subscr, remote_hlr_addr);
}
/* Return true when the message has been handled by D-GSM. */
bool dgsm_check_forward_gsup_msg(struct osmo_gsup_req *req)
{
struct proxy_subscr proxy_subscr;
struct proxy *proxy = g_hlr->gs->proxy;
struct osmo_mslookup_query query;
struct osmo_mslookup_query_handling handling;
uint32_t request_handle;
/* If the IMSI is known in the local HLR, then we won't proxy. */
if (db_subscr_exists_by_imsi(g_hlr->dbc, req->gsup.imsi) == 0)
return false;
/* Are we already forwarding this IMSI to a remote HLR? */
if (proxy_subscr_get_by_imsi(&proxy_subscr, proxy, req->gsup.imsi) == 0) {
proxy_subscr_forward_to_remote_hlr(proxy, &proxy_subscr, req);
return true;
}
/* The IMSI is not known locally, so we want to proxy to a remote HLR, but no proxy entry exists yet. We need to
* look up the subscriber in remote HLRs via D-GSM mslookup, forward GSUP and reply once a result is back from
* there. Defer message and kick off MS lookup. */
/* Add a proxy entry without a remote address to indicate that we are busy querying for a remote HLR. */
proxy_subscr = (struct proxy_subscr){};
OSMO_STRLCPY_ARRAY(proxy_subscr.imsi, req->gsup.imsi);
if (proxy_subscr_create_or_update(proxy, &proxy_subscr)) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Failed to create proxy entry\n");
return true;
}
/* Is a fixed gateway proxy configured? */
if (osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy)) {
proxy_subscr_remote_hlr_resolved(proxy, &proxy_subscr, &g_hlr->mslookup.client.gsup_gateway_proxy);
/* Proxy database modified, update info */
if (proxy_subscr_get_by_imsi(&proxy_subscr, proxy, req->gsup.imsi)) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Internal proxy error\n");
return true;
}
proxy_subscr_forward_to_remote_hlr(proxy, &proxy_subscr, req);
return true;
}
/* Kick off an mslookup for the remote HLR? This check could be up first on the top, but do it only now so that
* if the mslookup client disconnected, we still continue to service open proxy entries. */
if (!osmo_mslookup_client_active(g_hlr->mslookup.client.client)) {
LOG_GSUP_REQ(req, LOGL_DEBUG, "mslookup client not running, cannot query remote home HLR\n");
return false;
}
/* First spool message, then kick off mslookup. If the proxy denies this message type, then don't do anything. */
if (proxy_subscr_forward_to_remote_hlr(proxy, &proxy_subscr, req)) {
/* If the proxy denied forwarding, an error response was already generated. */
return true;
}
query = (struct osmo_mslookup_query){
.id = {
.type = OSMO_MSLOOKUP_ID_IMSI,
},
};
OSMO_STRLCPY_ARRAY(query.id.imsi, req->gsup.imsi);
OSMO_STRLCPY_ARRAY(query.service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP);
handling = (struct osmo_mslookup_query_handling){
.min_wait_milliseconds = g_hlr->mslookup.client.result_timeout_milliseconds,
.result_cb = resolve_hlr_result_cb,
};
request_handle = osmo_mslookup_client_request(g_hlr->mslookup.client.client, &query, &handling);
if (!request_handle) {
LOG_DGSM(req->gsup.imsi, LOGL_ERROR, "Error dispatching mslookup query for home HLR: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, &query, NULL));
proxy_subscr_del(proxy, req->gsup.imsi);
/* mslookup seems to not be working. Try handling it locally. */
return false;
}
return true;
}
void dgsm_init(void *ctx)
{
dgsm_ctx = talloc_named_const(ctx, 0, "dgsm");
INIT_LLIST_HEAD(&g_hlr->mslookup.server.local_site_services);
g_hlr->mslookup.server.local_attach_max_age = 60 * 60;
g_hlr->mslookup.client.result_timeout_milliseconds = 2000;
g_hlr->gsup_unit_name.unit_name = "HLR";
g_hlr->gsup_unit_name.serno = "unnamed-HLR";
g_hlr->gsup_unit_name.swversion = PACKAGE_NAME "-" PACKAGE_VERSION;
osmo_sockaddr_str_from_str(&g_hlr->mslookup.server.mdns.bind_addr,
OSMO_MSLOOKUP_MDNS_IP4, OSMO_MSLOOKUP_MDNS_PORT);
osmo_sockaddr_str_from_str(&g_hlr->mslookup.client.mdns.query_addr,
OSMO_MSLOOKUP_MDNS_IP4, OSMO_MSLOOKUP_MDNS_PORT);
}
void dgsm_start(void *ctx)
{
g_hlr->mslookup.client.client = osmo_mslookup_client_new(dgsm_ctx);
OSMO_ASSERT(g_hlr->mslookup.client.client);
g_hlr->mslookup.allow_startup = true;
mslookup_server_mdns_config_apply();
dgsm_mdns_client_config_apply();
}
void dgsm_stop()
{
g_hlr->mslookup.allow_startup = false;
mslookup_server_mdns_config_apply();
dgsm_mdns_client_config_apply();
}
void dgsm_mdns_client_config_apply(void)
{
/* Check whether to start/stop/restart mDNS client */
const struct osmo_sockaddr_str *current_bind_addr;
const char *current_domain_suffix;
current_bind_addr = osmo_mslookup_client_method_mdns_get_bind_addr(g_hlr->mslookup.client.mdns.running);
current_domain_suffix = osmo_mslookup_client_method_mdns_get_domain_suffix(g_hlr->mslookup.client.mdns.running);
bool should_run = g_hlr->mslookup.allow_startup
&& g_hlr->mslookup.client.enable && g_hlr->mslookup.client.mdns.enable;
bool should_stop = g_hlr->mslookup.client.mdns.running &&
(!should_run
|| osmo_sockaddr_str_cmp(&g_hlr->mslookup.client.mdns.query_addr,
current_bind_addr)
|| strcmp(g_hlr->mslookup.client.mdns.domain_suffix,
current_domain_suffix));
if (should_stop) {
osmo_mslookup_client_method_del(g_hlr->mslookup.client.client, g_hlr->mslookup.client.mdns.running);
g_hlr->mslookup.client.mdns.running = NULL;
LOGP(DDGSM, LOGL_NOTICE, "Stopped mslookup mDNS client\n");
}
if (should_run && !g_hlr->mslookup.client.mdns.running) {
g_hlr->mslookup.client.mdns.running =
osmo_mslookup_client_add_mdns(g_hlr->mslookup.client.client,
g_hlr->mslookup.client.mdns.query_addr.ip,
g_hlr->mslookup.client.mdns.query_addr.port,
-1,
g_hlr->mslookup.client.mdns.domain_suffix);
if (!g_hlr->mslookup.client.mdns.running)
LOGP(DDGSM, LOGL_ERROR, "Failed to start mslookup mDNS client with target "
OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.client.mdns.query_addr));
else
LOGP(DDGSM, LOGL_NOTICE, "Started mslookup mDNS client, sending mDNS requests to multicast "
OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.client.mdns.query_addr));
}
if (g_hlr->mslookup.client.enable && osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy))
LOGP(DDGSM, LOGL_NOTICE,
"mslookup client: all GSUP requests for unknown IMSIs will be forwarded to"
" gateway-proxy " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.client.gsup_gateway_proxy));
}

580
src/dgsm_vty.c Normal file
View File

@@ -0,0 +1,580 @@
/* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/mslookup/mslookup_client_mdns.h>
#include <osmocom/mslookup/mdns.h>
#include <osmocom/hlr/hlr_vty.h>
#include <osmocom/hlr/mslookup_server.h>
#include <osmocom/hlr/mslookup_server_mdns.h>
#include <osmocom/gsupclient/cni_peer_id.h>
struct cmd_node mslookup_node = {
MSLOOKUP_NODE,
"%s(config-mslookup)# ",
1,
};
DEFUN(cfg_mslookup,
cfg_mslookup_cmd,
"mslookup",
"Configure Distributed GSM mslookup")
{
vty->node = MSLOOKUP_NODE;
return CMD_SUCCESS;
}
static int mslookup_server_mdns_bind(struct vty *vty, int argc, const char **argv)
{
const char *ip_str = argc > 0? argv[0] : g_hlr->mslookup.server.mdns.bind_addr.ip;
const char *port_str = argc > 1? argv[1] : NULL;
uint16_t port_nr = port_str ? atoi(port_str) : g_hlr->mslookup.server.mdns.bind_addr.port;
struct osmo_sockaddr_str addr;
if (osmo_sockaddr_str_from_str(&addr, ip_str, port_nr)
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
vty_out(vty, "%% mslookup server: Invalid mDNS bind address: %s %u%s",
ip_str, port_nr, VTY_NEWLINE);
return CMD_WARNING;
}
g_hlr->mslookup.server.mdns.bind_addr = addr;
g_hlr->mslookup.server.mdns.enable = true;
g_hlr->mslookup.server.enable = true;
mslookup_server_mdns_config_apply();
return CMD_SUCCESS;
}
static int mslookup_client_mdns_to(struct vty *vty, int argc, const char **argv)
{
const char *ip_str = argc > 0? argv[0] : g_hlr->mslookup.client.mdns.query_addr.ip;
const char *port_str = argc > 1? argv[1] : NULL;
uint16_t port_nr = port_str ? atoi(port_str) : g_hlr->mslookup.client.mdns.query_addr.port;
struct osmo_sockaddr_str addr;
if (osmo_sockaddr_str_from_str(&addr, ip_str, port_nr)
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
vty_out(vty, "%% mslookup client: Invalid mDNS target address: %s %u%s",
ip_str, port_nr, VTY_NEWLINE);
return CMD_WARNING;
}
g_hlr->mslookup.client.mdns.query_addr = addr;
g_hlr->mslookup.client.mdns.enable = true;
g_hlr->mslookup.client.enable = true;
dgsm_mdns_client_config_apply();
return CMD_SUCCESS;
}
#define MDNS_STR "Multicast DNS related configuration\n"
#define MDNS_IP46_STR "multicast IPv4 address like " OSMO_MSLOOKUP_MDNS_IP4 \
" or IPv6 address like " OSMO_MSLOOKUP_MDNS_IP6 "\n"
#define MDNS_PORT_STR "mDNS UDP Port number\n"
#define MDNS_DOMAIN_SUFFIX_STR "mDNS domain suffix (default: " OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT "). This is appended" \
" and stripped from mDNS packets during encoding/decoding, so we don't collide with" \
" top-level domains administrated by IANA\n"
#define IP46_STR "IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1\n"
#define PORT_STR "Service-specific port number\n"
DEFUN(cfg_mslookup_mdns,
cfg_mslookup_mdns_cmd,
"mdns bind [IP] [<1-65535>]",
MDNS_STR
"Convenience shortcut: enable and configure both server and client for mDNS mslookup\n"
MDNS_IP46_STR MDNS_PORT_STR)
{
int rc1 = mslookup_server_mdns_bind(vty, argc, argv);
int rc2 = mslookup_client_mdns_to(vty, argc, argv);
if (rc1 != CMD_SUCCESS)
return rc1;
return rc2;
}
DEFUN(cfg_mslookup_mdns_domain_suffix,
cfg_mslookup_mdns_domain_suffix_cmd,
"mdns domain-suffix DOMAIN_SUFFIX",
MDNS_STR MDNS_DOMAIN_SUFFIX_STR MDNS_DOMAIN_SUFFIX_STR)
{
osmo_talloc_replace_string(g_hlr, &g_hlr->mslookup.server.mdns.domain_suffix, argv[0]);
osmo_talloc_replace_string(g_hlr, &g_hlr->mslookup.client.mdns.domain_suffix, argv[0]);
mslookup_server_mdns_config_apply();
dgsm_mdns_client_config_apply();
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_no_mdns,
cfg_mslookup_no_mdns_cmd,
"no mdns bind",
NO_STR "Disable both server and client for mDNS mslookup\n")
{
g_hlr->mslookup.server.mdns.enable = false;
g_hlr->mslookup.client.mdns.enable = false;
mslookup_server_mdns_config_apply();
dgsm_mdns_client_config_apply();
return CMD_SUCCESS;
}
struct cmd_node mslookup_server_node = {
MSLOOKUP_SERVER_NODE,
"%s(config-mslookup-server)# ",
1,
};
DEFUN(cfg_mslookup_server,
cfg_mslookup_server_cmd,
"server",
"Enable and configure Distributed GSM mslookup server")
{
vty->node = MSLOOKUP_SERVER_NODE;
g_hlr->mslookup.server.enable = true;
mslookup_server_mdns_config_apply();
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_no_server,
cfg_mslookup_no_server_cmd,
"no server",
NO_STR "Disable Distributed GSM mslookup server")
{
g_hlr->mslookup.server.enable = false;
mslookup_server_mdns_config_apply();
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_server_mdns_bind,
cfg_mslookup_server_mdns_bind_cmd,
"mdns bind [IP] [<1-65535>]",
MDNS_STR
"Configure where the mDNS server listens for mslookup requests\n"
MDNS_IP46_STR MDNS_PORT_STR)
{
return mslookup_server_mdns_bind(vty, argc, argv);
}
DEFUN(cfg_mslookup_server_mdns_domain_suffix,
cfg_mslookup_server_mdns_domain_suffix_cmd,
"mdns domain-suffix DOMAIN_SUFFIX",
MDNS_STR
MDNS_DOMAIN_SUFFIX_STR
MDNS_DOMAIN_SUFFIX_STR)
{
osmo_talloc_replace_string(g_hlr, &g_hlr->mslookup.server.mdns.domain_suffix, argv[0]);
mslookup_server_mdns_config_apply();
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_server_no_mdns_bind,
cfg_mslookup_server_no_mdns_bind_cmd,
"no mdns bind",
NO_STR "Disable server for mDNS mslookup (do not answer remote requests)\n")
{
g_hlr->mslookup.server.mdns.enable = false;
mslookup_server_mdns_config_apply();
return CMD_SUCCESS;
}
struct cmd_node mslookup_server_msc_node = {
MSLOOKUP_SERVER_MSC_NODE,
"%s(config-mslookup-server-msc)# ",
1,
};
DEFUN(cfg_mslookup_server_msc,
cfg_mslookup_server_msc_cmd,
"msc ipa-name .IPA_NAME",
"Configure services for individual local MSCs\n"
"Identify locally connected MSC by IPA Unit Name\n"
"IPA Unit Name of the local MSC to configure\n")
{
struct osmo_ipa_name msc_name;
struct mslookup_server_msc_cfg *msc;
osmo_ipa_name_set_str(&msc_name, argv_concat(argv, argc, 0));
msc = mslookup_server_msc_get(&msc_name, true);
if (!msc) {
vty_out(vty, "%% Error creating MSC %s%s", osmo_ipa_name_to_str(&msc_name), VTY_NEWLINE);
return CMD_WARNING;
}
vty->node = MSLOOKUP_SERVER_MSC_NODE;
vty->index = msc;
return CMD_SUCCESS;
}
#define SERVICE_NAME_STR \
"mslookup service name, e.g. sip.voice or smpp.sms\n"
static struct mslookup_server_msc_cfg *msc_from_node(struct vty *vty)
{
switch (vty->node) {
case MSLOOKUP_SERVER_NODE:
/* On the mslookup.server node, set services on the wildcard msc, without a particular name. */
return mslookup_server_msc_get(&mslookup_server_msc_wildcard, true);
case MSLOOKUP_SERVER_MSC_NODE:
return vty->index;
default:
return NULL;
}
}
DEFUN(cfg_mslookup_server_msc_service,
cfg_mslookup_server_msc_service_cmd,
"service NAME at IP <1-65535>",
"Configure addresses of local services, as sent in replies to remote mslookup requests.\n"
SERVICE_NAME_STR "at\n" IP46_STR PORT_STR)
{
/* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
* MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
struct mslookup_server_msc_cfg *msc = msc_from_node(vty);
const char *service = argv[0];
const char *ip_str = argv[1];
const char *port_str = argv[2];
struct osmo_sockaddr_str addr;
if (!msc) {
vty_out(vty, "%% Error: no MSC object on this node%s", VTY_NEWLINE);
return CMD_WARNING;
}
if (osmo_sockaddr_str_from_str(&addr, ip_str, atoi(port_str))
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
vty_out(vty, "%% mslookup server: Invalid address for service %s: %s %s%s",
service, ip_str, port_str, VTY_NEWLINE);
return CMD_WARNING;
}
if (mslookup_server_msc_service_set(msc, service, &addr)) {
vty_out(vty, "%% mslookup server: Error setting service %s to %s %s%s",
service, ip_str, port_str, VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
#define NO_SERVICE_AND_NAME_STR NO_STR "Remove one or more service address entries\n" SERVICE_NAME_STR
DEFUN(cfg_mslookup_server_msc_no_service,
cfg_mslookup_server_msc_no_service_cmd,
"no service NAME",
NO_SERVICE_AND_NAME_STR)
{
/* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
* MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
struct mslookup_server_msc_cfg *msc = msc_from_node(vty);
const char *service = argv[0];
if (!msc) {
vty_out(vty, "%% Error: no MSC object on this node%s", VTY_NEWLINE);
return CMD_WARNING;
}
if (mslookup_server_msc_service_del(msc, service, NULL) < 1) {
vty_out(vty, "%% mslookup server: cannot remove service '%s'%s",
service, VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_server_msc_no_service_addr,
cfg_mslookup_server_msc_no_service_addr_cmd,
"no service NAME at IP <1-65535>",
NO_SERVICE_AND_NAME_STR "at\n" IP46_STR PORT_STR)
{
/* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
* MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
struct mslookup_server_msc_cfg *msc = msc_from_node(vty);
const char *service = argv[0];
const char *ip_str = argv[1];
const char *port_str = argv[2];
struct osmo_sockaddr_str addr;
if (!msc) {
vty_out(vty, "%% Error: no MSC object on this node%s", VTY_NEWLINE);
return CMD_WARNING;
}
if (osmo_sockaddr_str_from_str(&addr, ip_str, atoi(port_str))
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
vty_out(vty, "%% mslookup server: Invalid address for 'no service' %s: %s %s%s",
service, ip_str, port_str, VTY_NEWLINE);
return CMD_WARNING;
}
if (mslookup_server_msc_service_del(msc, service, &addr) < 1) {
vty_out(vty, "%% mslookup server: cannot remove service '%s' to %s %s%s",
service, ip_str, port_str, VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
struct cmd_node mslookup_client_node = {
MSLOOKUP_CLIENT_NODE,
"%s(config-mslookup-client)# ",
1,
};
DEFUN(cfg_mslookup_client,
cfg_mslookup_client_cmd,
"client",
"Enable and configure Distributed GSM mslookup client")
{
vty->node = MSLOOKUP_CLIENT_NODE;
g_hlr->mslookup.client.enable = true;
dgsm_mdns_client_config_apply();
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_no_client,
cfg_mslookup_no_client_cmd,
"no client",
NO_STR "Disable Distributed GSM mslookup client")
{
g_hlr->mslookup.client.enable = false;
dgsm_mdns_client_config_apply();
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_client_timeout,
cfg_mslookup_client_timeout_cmd,
"timeout <1-100000>",
"How long should the mslookup client wait for remote responses before evaluating received results\n"
"timeout in milliseconds\n")
{
uint32_t val = atol(argv[0]);
g_hlr->mslookup.client.result_timeout_milliseconds = val;
return CMD_SUCCESS;
}
#define EXIT_HINT() \
if (vty->type != VTY_FILE) \
vty_out(vty, "%% 'exit' this node to apply changes%s", VTY_NEWLINE)
DEFUN(cfg_mslookup_client_mdns_bind,
cfg_mslookup_client_mdns_bind_cmd,
"mdns bind [IP] [<1-65535>]",
MDNS_STR
"Enable mDNS client, and configure multicast address to send mDNS mslookup requests to\n"
MDNS_IP46_STR MDNS_PORT_STR)
{
return mslookup_client_mdns_to(vty, argc, argv);
}
DEFUN(cfg_mslookup_client_mdns_domain_suffix,
cfg_mslookup_client_mdns_domain_suffix_cmd,
"mdns domain-suffix DOMAIN_SUFFIX",
MDNS_STR
MDNS_DOMAIN_SUFFIX_STR
MDNS_DOMAIN_SUFFIX_STR)
{
osmo_talloc_replace_string(g_hlr, &g_hlr->mslookup.client.mdns.domain_suffix, argv[0]);
dgsm_mdns_client_config_apply();
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_client_no_mdns_bind,
cfg_mslookup_client_no_mdns_bind_cmd,
"no mdns bind",
NO_STR "Disable mDNS client, do not query remote services by mDNS\n")
{
g_hlr->mslookup.client.mdns.enable = false;
dgsm_mdns_client_config_apply();
return CMD_SUCCESS;
}
void config_write_msc_services(struct vty *vty, const char *indent, struct mslookup_server_msc_cfg *msc)
{
struct mslookup_service_host *e;
llist_for_each_entry(e, &msc->service_hosts, entry) {
if (osmo_sockaddr_str_is_nonzero(&e->host_v4))
vty_out(vty, "%sservice %s at %s %u%s", indent, e->service, e->host_v4.ip, e->host_v4.port,
VTY_NEWLINE);
if (osmo_sockaddr_str_is_nonzero(&e->host_v6))
vty_out(vty, "%sservice %s at %s %u%s", indent, e->service, e->host_v6.ip, e->host_v6.port,
VTY_NEWLINE);
}
}
int config_write_mslookup(struct vty *vty)
{
if (!g_hlr->mslookup.server.enable
&& llist_empty(&g_hlr->mslookup.server.local_site_services)
&& !g_hlr->mslookup.client.enable)
return CMD_SUCCESS;
vty_out(vty, "mslookup%s", VTY_NEWLINE);
if (g_hlr->mslookup.server.enable || !llist_empty(&g_hlr->mslookup.server.local_site_services)) {
struct mslookup_server_msc_cfg *msc;
vty_out(vty, " server%s", VTY_NEWLINE);
if (g_hlr->mslookup.server.mdns.enable) {
vty_out(vty, " mdns bind");
if (osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.server.mdns.bind_addr)) {
vty_out(vty, " %s %u",
g_hlr->mslookup.server.mdns.bind_addr.ip,
g_hlr->mslookup.server.mdns.bind_addr.port);
}
vty_out(vty, "%s", VTY_NEWLINE);
}
if (strcmp(g_hlr->mslookup.server.mdns.domain_suffix, OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT))
vty_out(vty, " mdns domain-suffix %s%s",
g_hlr->mslookup.server.mdns.domain_suffix,
VTY_NEWLINE);
msc = mslookup_server_msc_get(&mslookup_server_msc_wildcard, false);
if (msc)
config_write_msc_services(vty, " ", msc);
llist_for_each_entry(msc, &g_hlr->mslookup.server.local_site_services, entry) {
if (!osmo_ipa_name_cmp(&mslookup_server_msc_wildcard, &msc->name))
continue;
vty_out(vty, " msc %s%s", osmo_ipa_name_to_str(&msc->name), VTY_NEWLINE);
config_write_msc_services(vty, " ", msc);
}
/* If the server is disabled, still output the above to not lose the service config. */
if (!g_hlr->mslookup.server.enable)
vty_out(vty, " no server%s", VTY_NEWLINE);
}
if (g_hlr->mslookup.client.enable) {
vty_out(vty, " client%s", VTY_NEWLINE);
if (osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy))
vty_out(vty, " gateway-proxy %s %u%s",
g_hlr->mslookup.client.gsup_gateway_proxy.ip,
g_hlr->mslookup.client.gsup_gateway_proxy.port,
VTY_NEWLINE);
if (g_hlr->mslookup.client.mdns.enable
&& osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.mdns.query_addr))
vty_out(vty, " mdns bind %s %u%s",
g_hlr->mslookup.client.mdns.query_addr.ip,
g_hlr->mslookup.client.mdns.query_addr.port,
VTY_NEWLINE);
if (strcmp(g_hlr->mslookup.client.mdns.domain_suffix, OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT))
vty_out(vty, " mdns domain-suffix %s%s",
g_hlr->mslookup.client.mdns.domain_suffix,
VTY_NEWLINE);
}
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_client_gateway_proxy,
cfg_mslookup_client_gateway_proxy_cmd,
"gateway-proxy IP [<1-65535>]",
"Configure a fixed IP address to send all GSUP requests for unknown IMSIs to, without invoking a lookup for IMSI\n"
"IP address of the remote HLR\n" "GSUP port number (omit for default " OSMO_STRINGIFY_VAL(OSMO_GSUP_PORT) ")\n")
{
const char *ip_str = argv[0];
const char *port_str = argc > 1 ? argv[1] : NULL;
struct osmo_sockaddr_str addr;
if (osmo_sockaddr_str_from_str(&addr, ip_str, port_str ? atoi(port_str) : OSMO_GSUP_PORT)
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
vty_out(vty, "%% mslookup client: Invalid address for gateway-proxy: %s %s%s",
ip_str, port_str ? : "", VTY_NEWLINE);
return CMD_WARNING;
}
g_hlr->mslookup.client.gsup_gateway_proxy = addr;
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_client_no_gateway_proxy,
cfg_mslookup_client_no_gateway_proxy_cmd,
"no gateway-proxy",
NO_STR "Disable gateway proxy for GSUP with unknown IMSIs\n")
{
g_hlr->mslookup.client.gsup_gateway_proxy = (struct osmo_sockaddr_str){};
return CMD_SUCCESS;
}
DEFUN(do_mslookup_show_services,
do_mslookup_show_services_cmd,
"show mslookup services",
SHOW_STR "Distributed GSM / mslookup related information\n"
"List configured service addresses as sent to remote mslookup requests\n")
{
struct mslookup_server_msc_cfg *msc;
const struct mslookup_service_host *local_hlr = mslookup_server_get_local_gsup_addr();
vty_out(vty, "Local GSUP HLR address returned in mslookup responses for local IMSIs:");
if (osmo_sockaddr_str_is_nonzero(&local_hlr->host_v4))
vty_out(vty, " " OSMO_SOCKADDR_STR_FMT,
OSMO_SOCKADDR_STR_FMT_ARGS(&local_hlr->host_v4));
if (osmo_sockaddr_str_is_nonzero(&local_hlr->host_v6))
vty_out(vty, " " OSMO_SOCKADDR_STR_FMT,
OSMO_SOCKADDR_STR_FMT_ARGS(&local_hlr->host_v6));
vty_out(vty, "%s", VTY_NEWLINE);
msc = mslookup_server_msc_get(&mslookup_server_msc_wildcard, false);
if (msc)
config_write_msc_services(vty, "", msc);
llist_for_each_entry(msc, &g_hlr->mslookup.server.local_site_services, entry) {
if (!osmo_ipa_name_cmp(&mslookup_server_msc_wildcard, &msc->name))
continue;
vty_out(vty, "msc ipa-name %s%s", osmo_ipa_name_to_str(&msc->name), VTY_NEWLINE);
config_write_msc_services(vty, " ", msc);
}
return CMD_SUCCESS;
}
void dgsm_vty_init(void)
{
install_element(CONFIG_NODE, &cfg_mslookup_cmd);
install_node(&mslookup_node, config_write_mslookup);
install_element(MSLOOKUP_NODE, &cfg_mslookup_mdns_cmd);
install_element(MSLOOKUP_NODE, &cfg_mslookup_mdns_domain_suffix_cmd);
install_element(MSLOOKUP_NODE, &cfg_mslookup_no_mdns_cmd);
install_element(MSLOOKUP_NODE, &cfg_mslookup_server_cmd);
install_element(MSLOOKUP_NODE, &cfg_mslookup_no_server_cmd);
install_node(&mslookup_server_node, NULL);
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_mdns_bind_cmd);
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_mdns_domain_suffix_cmd);
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_no_mdns_bind_cmd);
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_service_cmd);
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_no_service_cmd);
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_no_service_addr_cmd);
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_cmd);
install_node(&mslookup_server_msc_node, NULL);
install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_service_cmd);
install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_no_service_cmd);
install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_no_service_addr_cmd);
install_element(MSLOOKUP_NODE, &cfg_mslookup_client_cmd);
install_element(MSLOOKUP_NODE, &cfg_mslookup_no_client_cmd);
install_node(&mslookup_client_node, NULL);
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_timeout_cmd);
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_mdns_bind_cmd);
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_mdns_domain_suffix_cmd);
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_no_mdns_bind_cmd);
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_gateway_proxy_cmd);
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_no_gateway_proxy_cmd);
install_element_ve(&do_mslookup_show_services_cmd);
}

View File

@@ -23,17 +23,17 @@
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/talloc.h>
#include "logging.h"
#include "gsup_server.h"
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/gsup_router.h>
struct gsup_route {
struct llist_head list;
uint8_t *addr;
struct osmo_gsup_conn *conn;
};
/* find a route for the given address */
/*! Find a route for the given address.
* \param[in] gs gsup server
* \param[in] addr IPA name of the client (SGSN, MSC/VLR). Although this is passed like a blob, together with the
* length, it must be nul-terminated! This is for legacy reasons, see the discussion here:
* https://gerrit.osmocom.org/#/c/osmo-hlr/+/13048/
* \param[in] addrlen length of addr, *including the nul-byte* (strlen(addr) + 1).
*/
struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs,
const uint8_t *addr, size_t addrlen)
{
@@ -47,21 +47,47 @@ struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs,
return NULL;
}
struct osmo_gsup_conn *gsup_route_find_by_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name)
{
return gsup_route_find(gs, ipa_name->val, ipa_name->len);
}
/*! Find a GSUP connection's route (to read the IPA address from the route).
* \param[in] conn GSUP connection
* \return GSUP route
*/
struct gsup_route *gsup_route_find_by_conn(const struct osmo_gsup_conn *conn)
{
struct gsup_route *gr;
llist_for_each_entry(gr, &conn->server->routes, list) {
if (gr->conn == conn)
return gr;
}
return NULL;
}
/* add a new route for the given address to the given conn */
int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addrlen)
{
struct gsup_route *gr;
struct osmo_gsup_conn *exists_on_conn;
/* Check if we already have a route for this address */
if (gsup_route_find(conn->server, addr, addrlen))
return -EEXIST;
exists_on_conn = gsup_route_find(conn->server, addr, addrlen);
if (exists_on_conn) {
if (exists_on_conn != conn)
return -EEXIST;
return 0;
}
/* allocate new route and populate it */
gr = talloc_zero(conn->server, struct gsup_route);
if (!gr)
return -ENOMEM;
LOGP(DMAIN, LOGL_INFO, "Adding GSUP route for %s\n", addr);
LOGP(DMAIN, LOGL_INFO, "Adding GSUP route for %s via %s:%u\n", addr, conn->conn->addr, conn->conn->port);
gr->addr = talloc_memdup(gr, addr, addrlen);
gr->conn = conn;
@@ -70,6 +96,11 @@ int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addr
return 0;
}
int gsup_route_add_ipa_name(struct osmo_gsup_conn *conn, const struct osmo_ipa_name *ipa_name)
{
return gsup_route_add(conn, ipa_name->val, ipa_name->len);
}
/* delete all routes for the given connection */
int gsup_route_del_conn(struct osmo_gsup_conn *conn)
{
@@ -79,7 +110,7 @@ int gsup_route_del_conn(struct osmo_gsup_conn *conn)
llist_for_each_entry_safe(gr, gr2, &conn->server->routes, list) {
if (gr->conn == conn) {
LOGP(DMAIN, LOGL_INFO, "Removing GSUP route for %s (GSUP disconnect)\n",
gr->addr);
osmo_quote_str_c(OTC_SELECT, (char*)gr->addr, talloc_total_size(gr->addr)));
llist_del(&gr->list);
talloc_free(gr);
num_deleted++;

View File

@@ -1,17 +0,0 @@
#pragma once
#include <stdint.h>
#include "gsup_server.h"
struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs,
const uint8_t *addr, size_t addrlen);
/* add a new route for the given address to the given conn */
int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addrlen);
/* delete all routes for the given connection */
int gsup_route_del_conn(struct osmo_gsup_conn *conn);
int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
const uint8_t *addr, size_t addrlen,
struct msgb *msg);

View File

@@ -21,12 +21,19 @@
#include <errno.h>
#include "gsup_server.h"
#include "gsup_router.h"
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/gsup_router.h>
#include <osmocom/core/logging.h>
/* Send a msgb to a given address using routing */
/*! Send a msgb to a given address using routing.
* \param[in] gs gsup server
* \param[in] addr IPA name of the client (SGSN, MSC/VLR). Although this is passed like a blob, together with the
* length, it must be nul-terminated! This is for legacy reasons, see the discussion here:
* https://gerrit.osmocom.org/#/c/osmo-hlr/+/13048/
* \param[in] addrlen length of addr, *including the nul-byte* (strlen(addr) + 1).
* \param[in] msg message buffer
*/
int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
const uint8_t *addr, size_t addrlen,
struct msgb *msg)
@@ -35,7 +42,8 @@ int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
conn = gsup_route_find(gs, addr, addrlen);
if (!conn) {
DEBUGP(DLGSUP, "Cannot find route for addr %s\n", addr);
LOGP(DLGSUP, LOGL_ERROR,
"Cannot find route for addr %s\n", osmo_quote_str((const char*)addr, addrlen));
msgb_free(msg);
return -ENODEV;
}
@@ -43,3 +51,41 @@ int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
return osmo_gsup_conn_send(conn, msg);
}
/*! Send a msgb to a given address using routing.
* \param[in] gs gsup server
* \param[in] ipa_name IPA unit name of the client (SGSN, MSC/VLR, proxy).
* \param[in] msg message buffer
*/
int osmo_gsup_send_to_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name, struct msgb *msg)
{
if (ipa_name->val[ipa_name->len - 1]) {
/* Is not nul terminated. But for legacy reasons we (still) require that. */
if (ipa_name->len >= sizeof(ipa_name->val)) {
LOGP(DLGSUP, LOGL_ERROR, "IPA unit name is too long: %s\n",
osmo_ipa_name_to_str(ipa_name));
return -EINVAL;
}
struct osmo_ipa_name ipa_name2 = *ipa_name;
ipa_name2.val[ipa_name->len] = '\0';
ipa_name2.len++;
return osmo_gsup_addr_send(gs, ipa_name2.val, ipa_name2.len, msg);
}
return osmo_gsup_addr_send(gs, ipa_name->val, ipa_name->len, msg);
}
int osmo_gsup_enc_send_to_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name,
const struct osmo_gsup_message *gsup)
{
struct msgb *msg = osmo_gsup_msgb_alloc("GSUP Tx");
int rc;
rc = osmo_gsup_encode(msg, gsup);
if (rc) {
LOGP(DLGSUP, LOGL_ERROR, "IMSI-%s: Cannot encode GSUP: %s\n",
gsup->imsi, osmo_gsup_message_type_name(gsup->message_type));
msgb_free(msg);
return -EINVAL;
}
LOGP(DLGSUP, LOGL_DEBUG, "IMSI-%s: Tx: %s\n", gsup->imsi, osmo_gsup_message_type_name(gsup->message_type));
return osmo_gsup_send_to_ipa_name(gs, ipa_name, msg);
}

View File

@@ -18,6 +18,8 @@
*/
#include <errno.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/logging.h>
@@ -26,9 +28,21 @@
#include <osmocom/abis/ipaccess.h>
#include <osmocom/gsm/gsm48_ie.h>
#include <osmocom/gsm/apn.h>
#include <osmocom/gsm/gsm23003.h>
#include "gsup_server.h"
#include "gsup_router.h"
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/gsup_router.h>
#define LOG_GSUP_CONN(conn, level, fmt, args...) \
LOGP(DLGSUP, level, "GSUP peer %s: " fmt, \
(conn) ? osmo_ipa_name_to_str(&(conn)->peer_name) : "NULL", ##args)
struct msgb *osmo_gsup_msgb_alloc(const char *label)
{
struct msgb *msg = msgb_alloc_headroom(1024+512, 512, label);
OSMO_ASSERT(msg);
return msg;
}
static void osmo_gsup_server_send(struct osmo_gsup_conn *conn,
int proto_ext, struct msgb *msg_tx)
@@ -50,6 +64,91 @@ int osmo_gsup_conn_send(struct osmo_gsup_conn *conn, struct msgb *msg)
return 0;
}
static void gsup_server_send_req_response(struct osmo_gsup_req *req, struct osmo_gsup_message *response)
{
struct osmo_gsup_server *server = req->cb_data;
struct osmo_cni_peer_id *routing;
struct osmo_gsup_conn *conn = NULL;
struct msgb *msg = osmo_gsup_msgb_alloc("GSUP Tx");
int rc;
if (response->message_type == OSMO_GSUP_MSGT_ROUTING_ERROR
&& !osmo_cni_peer_id_is_empty(&req->via_proxy)) {
/* If a routing error occured, we need to route back via the immediate sending peer, not via the
* intended final recipient -- because one source of routing errors is a duplicate name for a recipient.
* If we resolve to req->source_name, we may send to a completely unrelated recipient. */
routing = &req->via_proxy;
} else {
routing = &req->source_name;
}
switch (routing->type) {
case OSMO_CNI_PEER_ID_IPA_NAME:
conn = gsup_route_find_by_ipa_name(server, &routing->ipa_name);
break;
default:
LOG_GSUP_REQ(req, LOGL_ERROR, "GSUP peer id kind not supported: %s\n",
osmo_cni_peer_id_type_name(routing->type));
break;
}
if (!conn) {
LOG_GSUP_REQ(req, LOGL_ERROR, "GSUP client that sent this request not found, cannot respond\n");
msgb_free(msg);
return;
}
rc = osmo_gsup_encode(msg, response);
if (rc) {
LOG_GSUP_REQ(req, LOGL_ERROR, "Unable to encode: {%s}\n",
osmo_gsup_message_to_str_c(OTC_SELECT, response));
msgb_free(msg);
return;
}
rc = osmo_gsup_conn_send(conn, msg);
if (rc)
LOG_GSUP_CONN(conn, LOGL_ERROR, "Unable to send: %s\n", osmo_gsup_message_to_str_c(OTC_SELECT, response));
}
struct osmo_gsup_req *osmo_gsup_conn_rx(struct osmo_gsup_conn *conn, struct msgb *msg)
{
struct osmo_gsup_req *req;
struct osmo_cni_peer_id cpi = {
.type = OSMO_CNI_PEER_ID_IPA_NAME,
.ipa_name = conn->peer_name,
};
req = osmo_gsup_req_new(conn->server, &cpi, msg, gsup_server_send_req_response, conn->server, NULL);
if (!req)
return NULL;
if (!osmo_cni_peer_id_is_empty(&req->via_proxy)) {
switch (req->via_proxy.type) {
case OSMO_CNI_PEER_ID_IPA_NAME:
break;
default:
LOG_GSUP_REQ(req, LOGL_ERROR, "GSUP peer id kind not supported: %s\n",
osmo_cni_peer_id_type_name(req->source_name.type));
osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_ROUTING_ERROR, true);
return NULL;
}
/* The source of the GSUP message is not the immediate GSUP peer, but that peer is our proxy for that
* source. Add it to the routes for this conn (so we can route responses back). */
if (gsup_route_add_ipa_name(conn, &req->source_name.ipa_name)) {
LOG_GSUP_REQ(req, LOGL_ERROR,
"GSUP message received from %s via peer %s, but there already exists a"
" different route to this source, message is not routable\n",
osmo_cni_peer_id_to_str(&req->source_name),
osmo_ipa_name_to_str(&conn->peer_name));
osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_ROUTING_ERROR, true);
return NULL;
}
}
return req;
}
static int osmo_gsup_conn_oap_handle(struct osmo_gsup_conn *conn,
struct msgb *msg_rx)
{
@@ -177,7 +276,7 @@ static int osmo_gsup_server_ccm_cb(struct ipa_server_conn *conn,
{
struct osmo_gsup_conn *clnt = (struct osmo_gsup_conn *)conn->data;
uint8_t *addr = NULL;
size_t addr_len;
int addr_len;
LOGP(DLGSUP, LOGL_INFO, "CCM Callback\n");
@@ -195,10 +294,18 @@ static int osmo_gsup_server_ccm_cb(struct ipa_server_conn *conn,
return -EINVAL;
}
gsup_route_add(clnt, addr, addr_len);
osmo_ipa_name_set(&clnt->peer_name, addr, addr_len);
gsup_route_add_ipa_name(clnt, &clnt->peer_name);
return 0;
}
static void osmo_gsup_conn_free(struct osmo_gsup_conn *conn)
{
gsup_route_del_conn(conn);
llist_del(&conn->list);
talloc_free(conn);
}
static int osmo_gsup_server_closed_cb(struct ipa_server_conn *conn)
{
struct osmo_gsup_conn *clnt = (struct osmo_gsup_conn *)conn->data;
@@ -206,10 +313,7 @@ static int osmo_gsup_server_closed_cb(struct ipa_server_conn *conn)
LOGP(DLGSUP, LOGL_INFO, "Lost GSUP client %s:%d\n",
conn->addr, conn->port);
gsup_route_del_conn(clnt);
llist_del(&clnt->list);
talloc_free(clnt);
osmo_gsup_conn_free(clnt);
return 0;
}
@@ -250,6 +354,19 @@ void osmo_gsup_server_add_conn(struct llist_head *clients,
llist_add(&conn->list, &prev_conn->list);
}
static void update_fd_settings(int fd)
{
int ret;
int val;
/*TODO: Set keepalive settings here. See OS#4312 */
val = 1;
ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
if (ret < 0)
LOGP(DLGSUP, LOGL_ERROR, "Failed to set TCP_NODELAY: %s\n", strerror(errno));
}
/* a client has connected to the server socket and we have accept()ed it */
static int osmo_gsup_server_accept_cb(struct ipa_server_link *link, int fd)
{
@@ -274,6 +391,8 @@ static int osmo_gsup_server_accept_cb(struct ipa_server_link *link, int fd)
LOGP(DLGSUP, LOGL_INFO, "New GSUP client %s:%d (IND=%u)\n",
conn->conn->addr, conn->conn->port, conn->auc_3g_ind);
update_fd_settings(fd);
/* request the identity of the client */
rc = ipa_ccm_send_id_req(fd);
if (rc < 0)
@@ -291,8 +410,7 @@ failed:
struct osmo_gsup_server *
osmo_gsup_server_create(void *ctx, const char *ip_addr, uint16_t tcp_port,
osmo_gsup_read_cb_t read_cb,
struct llist_head *lu_op_lst, void *priv)
osmo_gsup_read_cb_t read_cb, void *priv)
{
struct osmo_gsup_server *gsups;
int rc;
@@ -318,8 +436,6 @@ osmo_gsup_server_create(void *ctx, const char *ip_addr, uint16_t tcp_port,
if (rc < 0)
goto failed;
gsups->luop = lu_op_lst;
return gsups;
failed:
@@ -362,7 +478,7 @@ int osmo_gsup_configure_wildcard_apn(struct osmo_gsup_message *gsup,
/**
* Populate a gsup message structure with an Insert Subscriber Data Message.
* All required memory buffers for data pointed to by pointers in struct omso_gsup_message
* All required memory buffers for data pointed to by pointers in struct osmo_gsup_message
* must be allocated by the caller and should have the same lifetime as the gsup parameter.
*
* \param[out] gsup The gsup message to populate.
@@ -383,8 +499,10 @@ int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup,
int len;
OSMO_ASSERT(gsup);
*gsup = (struct osmo_gsup_message){
.message_type = OSMO_GSUP_MSGT_INSERT_DATA_REQUEST,
};
gsup->message_type = OSMO_GSUP_MSGT_INSERT_DATA_REQUEST;
osmo_strlcpy(gsup->imsi, imsi, sizeof(gsup->imsi));
if (msisdn_enc_size < OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN)
@@ -412,3 +530,39 @@ int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup,
return 0;
}
int osmo_gsup_forward_to_local_peer(struct osmo_gsup_server *server, const struct osmo_cni_peer_id *to_peer,
struct osmo_gsup_req *req, struct osmo_gsup_message *modified_gsup)
{
int rc;
/* To forward to a remote entity (HLR, SMSC,...), we need to indicate the original source name in the Source
* Name IE to make sure the reply can be routed back. Store the sender in gsup->source_name -- the remote entity
* is required to return this as gsup->destination_name so that the reply gets routed to the original sender. */
struct osmo_gsup_message forward = *(modified_gsup? : &req->gsup);
if (req->source_name.type != OSMO_CNI_PEER_ID_IPA_NAME) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Unsupported GSUP peer id type: %s",
osmo_cni_peer_id_type_name(req->source_name.type));
rc = -ENOTSUP;
goto routing_error;
}
forward.source_name = req->source_name.ipa_name.val;
forward.source_name_len = req->source_name.ipa_name.len;
if (to_peer->type != OSMO_CNI_PEER_ID_IPA_NAME) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Unsupported GSUP peer id type: %s",
osmo_cni_peer_id_type_name(to_peer->type));
rc = -ENOTSUP;
goto routing_error;
}
LOG_GSUP_REQ(req, LOGL_INFO, "Forwarding to %s\n", osmo_cni_peer_id_to_str(to_peer));
rc = osmo_gsup_enc_send_to_ipa_name(server, &to_peer->ipa_name, &forward);
if (rc)
goto routing_error;
osmo_gsup_req_free(req);
return 0;
routing_error:
osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_ROUTING_ERROR, true);
return rc;
}

View File

@@ -1,14 +1,18 @@
# 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
LIBVERSION=1:0:1
AM_CFLAGS = -Wall $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include \
$(TALLOC_CFLAGS) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOABIS_CFLAGS)
lib_LTLIBRARIES = libosmo-gsup-client.la
libosmo_gsup_client_la_SOURCES = gsup_client.c
libosmo_gsup_client_la_SOURCES = \
cni_peer_id.c \
gsup_client.c \
gsup_req.c \
$(NULL)
libosmo_gsup_client_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
libosmo_gsup_client_la_LIBADD = $(TALLOC_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOABIS_LIBS)

View File

@@ -0,0 +1,193 @@
/* 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 <errno.h>
#include <string.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsupclient/cni_peer_id.h>
bool osmo_ipa_name_is_empty(const struct osmo_ipa_name *ipa_name)
{
return (!ipa_name) || (!ipa_name->len);
}
int osmo_ipa_name_set(struct osmo_ipa_name *ipa_name, const uint8_t *val, size_t len)
{
if (!val || !len) {
*ipa_name = (struct osmo_ipa_name){};
return 0;
}
if (len > sizeof(ipa_name->val))
return -ENOSPC;
ipa_name->len = len;
memcpy(ipa_name->val, val, len);
return 0;
}
static int osmo_ipa_name_set_str_va(struct osmo_ipa_name *ipa_name, const char *str_fmt, va_list ap)
{
if (!str_fmt)
return osmo_ipa_name_set(ipa_name, NULL, 0);
vsnprintf((char*)(ipa_name->val), sizeof(ipa_name->val), str_fmt, ap);
ipa_name->len = strlen((char*)(ipa_name->val))+1;
return 0;
}
int osmo_ipa_name_set_str(struct osmo_ipa_name *ipa_name, const char *str_fmt, ...)
{
va_list ap;
int rc;
va_start(ap, str_fmt);
rc = osmo_ipa_name_set_str_va(ipa_name, str_fmt, ap);
va_end(ap);
return rc;
}
int osmo_ipa_name_cmp(const struct osmo_ipa_name *a, const struct osmo_ipa_name *b)
{
int cmp;
if (a == b)
return 0;
if (!a)
return -1;
if (!b)
return 1;
if (!a->len && !b->len)
return 0;
if (!a->len && b->len)
return -1;
if (!b->len && a->len)
return 1;
if (a->len == b->len)
return memcmp(a->val, b->val, a->len);
else if (a->len < b->len) {
cmp = memcmp(a->val, b->val, a->len);
if (!cmp)
cmp = -1;
return cmp;
} else {
/* a->len > b->len */
cmp = memcmp(a->val, b->val, b->len);
if (!cmp)
cmp = 1;
return cmp;
}
}
/* Call osmo_ipa_name_to_str_c with OTC_SELECT. */
const char *osmo_ipa_name_to_str(const struct osmo_ipa_name *ipa_name)
{
return osmo_ipa_name_to_str_c(OTC_SELECT, ipa_name);
}
/* Return an unquoted string, not including the terminating zero. Used for writing VTY config. */
const char *osmo_ipa_name_to_str_c(void *ctx, const struct osmo_ipa_name *ipa_name)
{
size_t len = ipa_name->len;
if (!len)
return talloc_strdup(ctx, "");
if (ipa_name->val[len-1] == '\0')
len--;
return osmo_escape_str_c(ctx, (char*)ipa_name->val, len);
}
bool osmo_cni_peer_id_is_empty(const struct osmo_cni_peer_id *cni_peer_id)
{
if (!cni_peer_id)
return true;
switch (cni_peer_id->type) {
case OSMO_CNI_PEER_ID_EMPTY:
return true;
case OSMO_CNI_PEER_ID_IPA_NAME:
return osmo_ipa_name_is_empty(&cni_peer_id->ipa_name);
default:
return false;
}
}
int osmo_cni_peer_id_set(struct osmo_cni_peer_id *cni_peer_id, enum osmo_cni_peer_id_type type,
const uint8_t *val, size_t len)
{
cni_peer_id->type = type;
switch (type) {
case OSMO_CNI_PEER_ID_IPA_NAME:
return osmo_ipa_name_set(&cni_peer_id->ipa_name, val, len);
default:
return -EINVAL;
}
}
int osmo_cni_peer_id_set_str(struct osmo_cni_peer_id *cni_peer_id, enum osmo_cni_peer_id_type type,
const char *str_fmt, ...)
{
va_list ap;
int rc;
*cni_peer_id = (struct osmo_cni_peer_id){};
switch (type) {
case OSMO_CNI_PEER_ID_IPA_NAME:
cni_peer_id->type = OSMO_CNI_PEER_ID_IPA_NAME;
va_start(ap, str_fmt);
rc = osmo_ipa_name_set_str_va(&cni_peer_id->ipa_name, str_fmt, ap);
va_end(ap);
return rc;
default:
return -EINVAL;
}
}
int osmo_cni_peer_id_cmp(const struct osmo_cni_peer_id *a, const struct osmo_cni_peer_id *b)
{
if (a == b)
return 0;
if (!a)
return -1;
if (!b)
return 1;
if (a->type != b->type)
return OSMO_CMP(a->type, b->type);
switch (a->type) {
case OSMO_CNI_PEER_ID_IPA_NAME:
return osmo_ipa_name_cmp(&a->ipa_name, &b->ipa_name);
default:
return -EINVAL;
}
}
const struct value_string osmo_cni_peer_id_type_names[] = {
{ OSMO_CNI_PEER_ID_IPA_NAME, "IPA-name" },
{}
};
/* Call osmo_cni_peer_id_to_str_c with OTC_SELECT */
const char *osmo_cni_peer_id_to_str(const struct osmo_cni_peer_id *cpi)
{
return osmo_cni_peer_id_to_str_c(OTC_SELECT, cpi);
}
/* Return an unquoted string, not including the terminating zero. Used for writing VTY config. */
const char *osmo_cni_peer_id_to_str_c(void *ctx, const struct osmo_cni_peer_id *cpi)
{
switch (cpi->type) {
case OSMO_CNI_PEER_ID_IPA_NAME:
return osmo_ipa_name_to_str_c(ctx, &cpi->ipa_name);
default:
return talloc_strdup(ctx, osmo_cni_peer_id_type_name(cpi->type));
}
}

View File

@@ -31,6 +31,8 @@
#include <errno.h>
#include <string.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
static void start_test_procedure(struct osmo_gsup_client *gsupc);
@@ -97,6 +99,12 @@ static void connect_timer_cb(void *gsupc_)
if (gsupc->is_connected)
return;
if (gsupc->up_down_cb) {
/* When the up_down_cb() returns false, the user asks us not to retry connecting. */
if (!gsupc->up_down_cb(gsupc, false))
return;
}
gsup_client_connect(gsupc);
}
@@ -123,6 +131,19 @@ static void gsup_client_oap_register(struct osmo_gsup_client *gsupc)
client_send(gsupc, IPAC_PROTO_EXT_OAP, msg_tx);
}
static void update_fd_settings(int fd)
{
int ret;
int val;
/*TODO: Set keepalive settings here. See OS#4312 */
val = 1;
ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
if (ret < 0)
LOGP(DLGSUP, LOGL_ERROR, "Failed to set TCP_NODELAY: %s\n", strerror(errno));
}
static void gsup_client_updown_cb(struct ipa_client_conn *link, int up)
{
struct osmo_gsup_client *gsupc = link->data;
@@ -133,15 +154,25 @@ static void gsup_client_updown_cb(struct ipa_client_conn *link, int up)
gsupc->is_connected = up;
if (up) {
update_fd_settings(link->ofd->fd);
start_test_procedure(gsupc);
if (gsupc->oap_state.state == OSMO_OAP_INITIALIZED)
gsup_client_oap_register(gsupc);
osmo_timer_del(&gsupc->connect_timer);
if (gsupc->up_down_cb)
gsupc->up_down_cb(gsupc, true);
} else {
osmo_timer_del(&gsupc->ping_timer);
if (gsupc->up_down_cb) {
/* When the up_down_cb() returns false, the user asks us not to retry connecting. */
if (!gsupc->up_down_cb(gsupc, false))
return;
}
osmo_timer_schedule(&gsupc->connect_timer,
OSMO_GSUP_CLIENT_RECONNECT_INTERVAL, 0);
}
@@ -263,42 +294,40 @@ static void start_test_procedure(struct osmo_gsup_client *gsupc)
* Use the provided ipaccess unit as the client-side identifier; ipa_dev should
* be allocated in talloc_ctx talloc_ctx as well.
* \param[in] talloc_ctx talloc context.
* \param[in] ipa_dev IP access unit which contains client identification information; must be allocated
* in talloc_ctx as well to ensure it lives throughout the lifetime of the connection.
* \param[in] ip_addr GSUP server IP address.
* \param[in] tcp_port GSUP server TCP port.
* \param[in] read_cb callback for reading from the GSUP connection.
* \param[in] oapc_config OPA client configuration.
* \returns a GSUP client connection or NULL on failure.
* \param[in] config Parameters for setting up the GSUP client.
* \return a GSUP client connection, or NULL on failure.
*/
struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx,
struct ipaccess_unit *ipa_dev,
const char *ip_addr,
unsigned int tcp_port,
osmo_gsup_client_read_cb_t read_cb,
struct osmo_oap_client_config *oapc_config)
struct osmo_gsup_client *osmo_gsup_client_create3(void *talloc_ctx, struct osmo_gsup_client_config *config)
{
struct osmo_gsup_client *gsupc;
int rc;
OSMO_ASSERT(config->ipa_dev->unit_name);
gsupc = talloc_zero(talloc_ctx, struct osmo_gsup_client);
OSMO_ASSERT(gsupc);
gsupc->unit_name = (const char *)ipa_dev->unit_name; /* API backwards compat */
gsupc->ipa_dev = ipa_dev;
*gsupc = (struct osmo_gsup_client){
.unit_name = (const char *)config->ipa_dev->unit_name, /* API backwards compat */
.ipa_dev = config->ipa_dev,
.read_cb = config->read_cb,
.up_down_cb = config->up_down_cb,
.data = config->data,
};
/* a NULL oapc_config will mark oap_state disabled. */
rc = osmo_oap_client_init(oapc_config, &gsupc->oap_state);
rc = osmo_oap_client_init(config->oapc_config, &gsupc->oap_state);
if (rc != 0)
goto failed;
gsupc->link = ipa_client_conn_create(gsupc,
/* no e1inp */ NULL,
0,
ip_addr, tcp_port,
gsup_client_updown_cb,
gsup_client_read_cb,
/* default write_cb */ NULL,
gsupc);
gsupc->link = ipa_client_conn_create2(gsupc,
/* no e1inp */ NULL,
0,
/* no specific local IP:port */ NULL, 0,
config->ip_addr, config->tcp_port,
gsup_client_updown_cb,
gsup_client_read_cb,
/* default write_cb */ NULL,
gsupc);
if (!gsupc->link)
goto failed;
@@ -308,9 +337,6 @@ struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx,
if (rc < 0)
goto failed;
gsupc->read_cb = read_cb;
return gsupc;
failed:
@@ -318,6 +344,26 @@ failed:
return NULL;
}
/*! Like osmo_gsup_client_create3() but without the up_down_cb and data arguments, and with the oapc_config argument in
* a different position.
*/
struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx,
struct ipaccess_unit *ipa_dev,
const char *ip_addr,
unsigned int tcp_port,
osmo_gsup_client_read_cb_t read_cb,
struct osmo_oap_client_config *oapc_config)
{
struct osmo_gsup_client_config cfg = {
.ipa_dev = ipa_dev,
.ip_addr = ip_addr,
.tcp_port = tcp_port,
.oapc_config = oapc_config,
.read_cb = read_cb,
};
return osmo_gsup_client_create3(talloc_ctx, &cfg);
}
/**
* Like osmo_gsup_client_create2() except it expects a unit name instead
* of a full-blown ipacess_unit as the client-side identifier.
@@ -386,7 +432,8 @@ int osmo_gsup_client_enc_send(struct osmo_gsup_client *gsupc,
rc = osmo_gsup_client_send(gsupc, gsup_msgb);
if (rc) {
LOGP(DLGSUP, LOGL_ERROR, "Couldn't send GSUP message\n");
goto error;
/* Do not free, osmo_gsup_client_send() already has. */
return rc;
}
return 0;

416
src/gsupclient/gsup_req.c Normal file
View File

@@ -0,0 +1,416 @@
/* 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 <errno.h>
#include <inttypes.h>
#include <osmocom/core/logging.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/gsupclient/gsup_req.h>
/*! Create a new osmo_gsup_req record, decode GSUP and add to a provided list of requests.
*
* Rationales:
*
* - osmo_gsup_req makes it easy to handle GSUP requests asynchronously. Before this, a GSUP message struct would be
* valid only within a read callback function, and would not survive asynchronous handling, because the struct often
* points directly into the received msgb. An osmo_gsup_req takes ownership of the msgb and ensures that the data
* remains valid, so that it can easily be queued for later handling.
* - osmo_gsup_req unifies the composition of response messages to ensure that all IEs that identify it to belong to
* the initial request are preserved / derived, like the source_name, destination_name, session_id, etc (see
* osmo_gsup_make_response() for details).
* - Deallocation of an osmo_gsup_req is implicit upon sending a response. The idea is that msgb memory leaks are a
* recurring source of bugs. By enforcing a request-response relation with implicit deallocation, osmo_gsup_req aims
* to help avoid most such memory leaks implicitly.
*
* The typical GSUP message sequence is:
* -> rx request,
* <- tx response.
*
* With osmo_gsup_req we can easily expand to:
* -> rx request,
* ... wait asynchronously,
* <- tx response.
*
* Only few GSUP conversations go beyond a 1:1 request-response match. But some have a session (e.g. USSD) or more
* negotiation may happen before the initial request is completed (e.g. Update Location with interleaved Insert
* Subscriber Data), so osmo_gsup_req also allows passing non-final responses.
* The final_response flag allows for:
* -> rx request,
* ... wait async,
* <- tx intermediate message to same peer (final_response = false, req remains open),
* ... wait async,
* -> rx intermediate response,
* ... wait async,
* <- tx final response (final_response = true, req is deallocated).
*
* This function takes ownership of the msgb, which will, on success, be owned by the returned osmo_gsup_req instance
* until osmo_gsup_req_free(). If a decoding error occurs, send an error response immediately, and return NULL.
*
* The original CNI entity that sent the message is found in req->source_name. If the message was passed on by an
* intermediate CNI peer, then req->via_proxy is set to the immediate peer, and it is the responsibility of the caller
* to add req->source_name to the GSUP routes that are serviced by req->via_proxy (usually not relevant for clients with
* a single GSUP conn).
* Examples:
*
* "msc" ---> here
* source_name = "msc"
* via_proxy = <empty>
*
* "msc" ---> "proxy-HLR" ---> here (e.g. home HLR)
* source_name = "msc"
* via_proxy = "proxy-HLR"
*
* "msc" ---> "proxy-HLR" ---> "home-HLR" ---> here (e.g. EUSE)
* source_name = "msc"
* via_proxy = "home-HLR"
*
* An osmo_gsup_req must be concluded (and deallocated) by calling one of the osmo_gsup_req_respond* functions.
*
* Note: osmo_gsup_req API makes use of OTC_SELECT to allocate volatile buffers for logging. Use of
* osmo_select_main_ctx() is mandatory when using osmo_gsup_req.
*
* \param[in] ctx Talloc context for allocation of the new request.
* \param[in] from_peer The IPA unit name of the immediate GSUP peer from which this msgb was received.
* \param[in] msg The message buffer containing the received GSUP message, where msgb_l2() shall point to the GSUP
* message start. The caller no longer owns the msgb when it is passed to this function: on error, the
* msgb is freed immediately, and on success, the msgb is owned by the returned osmo_gsup_req.
* \param[in] send_response_cb User specific method to send a GSUP response message, invoked upon
* osmo_gsup_req_respond*() functions. Typically this invokes encoding and transmitting the
* GSUP message over a network socket. See for example gsup_server_send_req_response().
* \param[inout] cb_data Context data to be used freely by the caller.
* \param[inout] add_to_list List to which to append this request, or NULL for no list.
* \return a newly allocated osmo_gsup_req, or NULL on error. If NULL is returned, an error response has already been
* dispatched to the send_response_cb.
*/
struct osmo_gsup_req *osmo_gsup_req_new(void *ctx, const struct osmo_cni_peer_id *from_peer, struct msgb *msg,
osmo_gsup_req_send_response_t send_response_cb, void *cb_data,
struct llist_head *add_to_list)
{
static unsigned int next_req_nr = 1;
struct osmo_gsup_req *req;
int rc;
if (!from_peer) {
LOGP(DLGSUP, LOGL_ERROR, "Rx GSUP from NULL peer is not allowed\n");
msgb_free(msg);
return NULL;
}
if (!msgb_l2(msg) || !msgb_l2len(msg)) {
LOGP(DLGSUP, LOGL_ERROR, "Rx GSUP from %s: missing or empty L2 data\n",
osmo_cni_peer_id_to_str(from_peer));
msgb_free(msg);
return NULL;
}
req = talloc_zero(ctx, struct osmo_gsup_req);
OSMO_ASSERT(req);
/* Note: req->gsup is declared const, so that the incoming message cannot be modified by handlers. */
req->nr = next_req_nr++;
req->msg = msg;
req->send_response_cb = send_response_cb;
req->cb_data = cb_data;
req->source_name = *from_peer;
rc = osmo_gsup_decode(msgb_l2(req->msg), msgb_l2len(req->msg), (struct osmo_gsup_message*)&req->gsup);
if (rc < 0) {
LOGP(DLGSUP, LOGL_ERROR, "Rx GSUP from %s: cannot decode (rc=%d)\n", osmo_cni_peer_id_to_str(from_peer), rc);
osmo_gsup_req_free(req);
return NULL;
}
LOG_GSUP_REQ(req, LOGL_DEBUG, "new request: {%s}\n", osmo_gsup_message_to_str_c(OTC_SELECT, &req->gsup));
if (req->gsup.source_name_len) {
if (osmo_cni_peer_id_set(&req->source_name, OSMO_CNI_PEER_ID_IPA_NAME,
req->gsup.source_name, req->gsup.source_name_len)) {
LOGP(DLGSUP, LOGL_ERROR,
"Rx GSUP from %s: failed to decode source_name, message is not routable\n",
osmo_cni_peer_id_to_str(from_peer));
osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_ROUTING_ERROR, true);
return NULL;
}
/* The source of the GSUP message is not the immediate GSUP peer; the peer is our proxy for that source.
*/
if (osmo_cni_peer_id_cmp(&req->source_name, from_peer))
req->via_proxy = *from_peer;
}
if (!osmo_imsi_str_valid(req->gsup.imsi)) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "invalid IMSI: %s",
osmo_quote_str(req->gsup.imsi, -1));
return NULL;
}
if (add_to_list)
llist_add_tail(&req->entry, add_to_list);
return req;
}
/*! Free an osmo_gsup_req and its msgb -- this is usually implicit in osmo_gsup_req_resond_*(), it should not be
* necessary to call this directly. */
void osmo_gsup_req_free(struct osmo_gsup_req *req)
{
LOG_GSUP_REQ(req, LOGL_DEBUG, "free\n");
if (req->msg)
msgb_free(req->msg);
if (req->entry.prev)
llist_del(&req->entry);
talloc_free(req);
}
/*! Send a response to a GSUP request.
*
* Ensure that the response message contains all GSUP IEs that identify it as a response for the request req, by calling
* osmo_gsup_make_response().
*
* The final complete response message is passed to req->send_response_cb() to take care of the transmission.
*
* \param req Request as previously initialized by osmo_gsup_req_new().
* \param response Buffer to compose the response, possibly with some pre-configured IEs.
* Any missing IEs are added via osmo_gsup_make_response().
* Must not be NULL. Does not need to remain valid memory beyond the function call,
* i.e. this can just be a local variable in the calling function.
* \param error True when the response message indicates an error response (error message type).
* \param final_response True when the request is concluded by this response, which deallocates the req.
* False when the request should remain open after this response.
* For most plain request->response GSUP messages, this should be True.
* \param file Source file for logging as in __FILE__, added by osmo_gsup_req_respond() macro.
* \param line Source line for logging as in __LINE__, added by osmo_gsup_req_respond() macro.
*/
int _osmo_gsup_req_respond(struct osmo_gsup_req *req, struct osmo_gsup_message *response,
bool error, bool final_response, const char *file, int line)
{
int rc;
rc = osmo_gsup_make_response(response, &req->gsup, error, final_response);
if (rc) {
LOG_GSUP_REQ_SRC(req, LOGL_ERROR, file, line, "Invalid response (rc=%d): {%s}\n",
rc, osmo_gsup_message_to_str_c(OTC_SELECT, response));
rc = -EINVAL;
goto exit_cleanup;
}
if (!req->send_response_cb) {
LOG_GSUP_REQ_SRC(req, LOGL_ERROR, file, line, "No send_response_cb set, cannot send: {%s}\n",
osmo_gsup_message_to_str_c(OTC_SELECT, response));
rc = -EINVAL;
goto exit_cleanup;
}
LOG_GSUP_REQ_SRC(req, LOGL_DEBUG, file, line, "Tx response: {%s}\n",
osmo_gsup_message_to_str_c(OTC_SELECT, response));
req->send_response_cb(req, response);
exit_cleanup:
if (final_response)
osmo_gsup_req_free(req);
return rc;
}
/*! Shorthand for _osmo_gsup_req_respond() with no additional IEs and a fixed message type.
* Set the message type in a local osmo_gsup_message and feed it to _osmo_gsup_req_respond().
* That will ensure to add all IEs that identify it as a response to req.
*
* \param req Request as previously initialized by osmo_gsup_req_new().
* \param message_type The GSUP message type discriminator to respond with.
* \param final_response True when the request is concluded by this response, which deallocates the req.
* False when the request should remain open after this response.
* For most plain request->response GSUP messages, this should be True.
* \param file Source file for logging as in __FILE__, added by osmo_gsup_req_respond_msgt() macro.
* \param line Source line for logging as in __LINE__, added by osmo_gsup_req_respond_msgt() macro.
*/
int _osmo_gsup_req_respond_msgt(struct osmo_gsup_req *req, enum osmo_gsup_message_type message_type,
bool final_response, const char *file, int line)
{
struct osmo_gsup_message response = {
.message_type = message_type,
};
return _osmo_gsup_req_respond(req, &response, OSMO_GSUP_IS_MSGT_ERROR(message_type), final_response,
file, line);
}
/*! Shorthand for _osmo_gsup_req_respond() with an error cause IEs and using the req's matched error message type.
* Set the error cause in a local osmo_gsup_message and feed it to _osmo_gsup_req_respond().
* That will ensure to add all IEs that identify it as a response to req.
*
* Responding with an error always implies a final response: req is implicitly deallocated.
*
* \param req Request as previously initialized by osmo_gsup_req_new().
* \param cause The error cause to include in a OSMO_GSUP_CAUSE_IE.
* \param file Source file for logging as in __FILE__, added by osmo_gsup_req_respond_err() macro.
* \param line Source line for logging as in __LINE__, added by osmo_gsup_req_respond_err() macro.
*/
void _osmo_gsup_req_respond_err(struct osmo_gsup_req *req, enum gsm48_gmm_cause cause,
const char *file, int line)
{
struct osmo_gsup_message response = {
.cause = cause,
};
/* No need to answer if we couldn't parse an ERROR message type, only REQUESTs need an error reply. */
if (!OSMO_GSUP_IS_MSGT_REQUEST(req->gsup.message_type)) {
osmo_gsup_req_free(req);
return;
}
osmo_gsup_req_respond(req, &response, true, true);
}
/*! This function is implicitly called by the osmo_gsup_req API, if at all possible rather use osmo_gsup_req_respond().
* This function is non-static mostly to allow unit testing.
*
* Set fields, if still unset, that need to be copied from a received message over to its response message, to ensure
* the response can be routed back to the requesting peer even via GSUP proxies.
*
* Note: after calling this function, fields in the reply may reference the same memory as rx and are not deep-copied,
* as is the usual way we are handling decoded GSUP messages.
*
* These fields are set in the reply message, iff they are still unset:
* - Set reply->message_type to the rx's matching RESULT code (or ERROR code if error == true).
* - IMSI,
* - Set reply->destination_name to rx->source_name (for proxy routing),
* - sm_rp_mr (for SMS),
* - session_id (for SS/USSD),
* - if rx->session_state is not NONE, set tx->session_state depending on the final_response argument:
* If false, set to OSMO_GSUP_SESSION_STATE_CONTINUE, else OSMO_GSUP_SESSION_STATE_END.
*
* If values in reply are already set, they will not be overwritten. The return code is an optional way of finding out
* whether all values that were already set in 'reply' are indeed matching the 'rx' values that would have been set.
*
* \param[in] rx Received GSUP message that is being replied to.
* \param[inout] reply The message that should be the response to rx, either empty or with some values already set up.
* \return 0 if the resulting message is a valid response for rx, nonzero otherwise. A nonzero rc has no effect on the
* values set in the reply message: all unset fields are first updated, and then the rc is determined.
* The rc is intended to merely warn if the reply message already contained data that is incompatible with rx,
* e.g. a mismatching IMSI.
*/
int osmo_gsup_make_response(struct osmo_gsup_message *reply,
const struct osmo_gsup_message *rx, bool error, bool final_response)
{
int rc = 0;
if (!reply->message_type) {
if (error)
reply->message_type = OSMO_GSUP_TO_MSGT_ERROR(rx->message_type);
else
reply->message_type = OSMO_GSUP_TO_MSGT_RESULT(rx->message_type);
}
if (*reply->imsi == '\0')
OSMO_STRLCPY_ARRAY(reply->imsi, rx->imsi);
if (reply->message_class == OSMO_GSUP_MESSAGE_CLASS_UNSET)
reply->message_class = rx->message_class;
if (!reply->destination_name || !reply->destination_name_len) {
reply->destination_name = rx->source_name;
reply->destination_name_len = rx->source_name_len;
}
/* RP-Message-Reference is mandatory for SM Service */
if (!reply->sm_rp_mr)
reply->sm_rp_mr = rx->sm_rp_mr;
/* For SS/USSD, it's important to keep both session state and ID IEs */
if (!reply->session_id)
reply->session_id = rx->session_id;
if (rx->session_state != OSMO_GSUP_SESSION_STATE_NONE
&& reply->session_state == OSMO_GSUP_SESSION_STATE_NONE) {
if (final_response || rx->session_state == OSMO_GSUP_SESSION_STATE_END)
reply->session_state = OSMO_GSUP_SESSION_STATE_END;
else
reply->session_state = OSMO_GSUP_SESSION_STATE_CONTINUE;
}
if (strcmp(reply->imsi, rx->imsi))
rc |= 1 << 0;
if (reply->message_class != rx->message_class)
rc |= 1 << 1;
if (rx->sm_rp_mr && (!reply->sm_rp_mr || *rx->sm_rp_mr != *reply->sm_rp_mr))
rc |= 1 << 2;
if (reply->session_id != rx->session_id)
rc |= 1 << 3;
return rc;
}
/*! Print the most important value of a GSUP message to a string buffer in human readable form.
* \param[out] buf The buffer to write to.
* \param[out] buflen sizeof(buf).
* \param[in] msg GSUP message to print.
*/
size_t osmo_gsup_message_to_str_buf(char *buf, size_t buflen, const struct osmo_gsup_message *msg)
{
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
if (!msg) {
OSMO_STRBUF_PRINTF(sb, "NULL");
return sb.chars_needed;
}
if (msg->message_class)
OSMO_STRBUF_PRINTF(sb, "%s ", osmo_gsup_message_class_name(msg->message_class));
OSMO_STRBUF_PRINTF(sb, "%s:", osmo_gsup_message_type_name(msg->message_type));
OSMO_STRBUF_PRINTF(sb, " imsi=");
OSMO_STRBUF_APPEND(sb, osmo_quote_cstr_buf, msg->imsi, strnlen(msg->imsi, sizeof(msg->imsi)));
if (msg->cause)
OSMO_STRBUF_PRINTF(sb, " cause=%s", get_value_string(gsm48_gmm_cause_names, msg->cause));
switch (msg->cn_domain) {
case OSMO_GSUP_CN_DOMAIN_CS:
OSMO_STRBUF_PRINTF(sb, " cn_domain=CS");
break;
case OSMO_GSUP_CN_DOMAIN_PS:
OSMO_STRBUF_PRINTF(sb, " cn_domain=PS");
break;
default:
if (msg->cn_domain)
OSMO_STRBUF_PRINTF(sb, " cn_domain=?(%d)", msg->cn_domain);
break;
}
if (msg->source_name_len) {
OSMO_STRBUF_PRINTF(sb, " source_name=");
OSMO_STRBUF_APPEND(sb, osmo_quote_cstr_buf, (char*)msg->source_name, msg->source_name_len);
}
if (msg->destination_name_len) {
OSMO_STRBUF_PRINTF(sb, " destination_name=");
OSMO_STRBUF_APPEND(sb, osmo_quote_cstr_buf, (char*)msg->destination_name, msg->destination_name_len);
}
if (msg->session_id)
OSMO_STRBUF_PRINTF(sb, " session_id=%" PRIu32, msg->session_id);
if (msg->session_state)
OSMO_STRBUF_PRINTF(sb, " session_state=%s", osmo_gsup_session_state_name(msg->session_state));
if (msg->sm_rp_mr)
OSMO_STRBUF_PRINTF(sb, " sm_rp_mr=%" PRIu8, *msg->sm_rp_mr);
return sb.chars_needed;
}
/*! Same as osmo_gsup_message_to_str_buf() but returns a talloc allocated string. */
char *osmo_gsup_message_to_str_c(void *ctx, const struct osmo_gsup_message *msg)
{
OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_gsup_message_to_str_buf, msg)
}

678
src/hlr.c
View File

@@ -23,6 +23,7 @@
#include <getopt.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/application.h>
#include <osmocom/gsm/gsup.h>
@@ -30,23 +31,39 @@
#include <osmocom/vty/command.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/ports.h>
#include <osmocom/vty/cpu_sched_vty.h>
#include <osmocom/ctrl/control_vty.h>
#include <osmocom/gsm/apn.h>
#include <osmocom/gsm/gsm48_ie.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/mslookup/mslookup_client.h>
#include "db.h"
#include "hlr.h"
#include "ctrl.h"
#include "logging.h"
#include "gsup_server.h"
#include "gsup_router.h"
#include "rand.h"
#include "luop.h"
#include "hlr_vty.h"
#include "hlr_ussd.h"
#include <osmocom/gsupclient/cni_peer_id.h>
#include <osmocom/hlr/db.h>
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/ctrl.h>
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/gsup_router.h>
#include <osmocom/hlr/rand.h>
#include <osmocom/hlr/hlr_vty.h>
#include <osmocom/hlr/hlr_ussd.h>
#include <osmocom/hlr/dgsm.h>
#include <osmocom/hlr/proxy.h>
#include <osmocom/hlr/lu_fsm.h>
#include <osmocom/mslookup/mdns.h>
struct hlr *g_hlr;
static void *hlr_ctx = NULL;
static int quit = 0;
struct osmo_tdef g_hlr_tdefs[] = {
/* 4222 is also the OSMO_GSUP_PORT */
{ .T = -4222, .default_val = 30, .desc = "GSUP Update Location timeout" },
{}
};
/* Trigger 'Insert Subscriber Data' messages to all connected GSUP clients.
*
* \param[in] subscr A subscriber we have new data to send for.
@@ -64,6 +81,8 @@ osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr)
return;
}
/* FIXME: send only to current vlr_number and sgsn_number */
llist_for_each_entry(co, &g_hlr->gs->clients, list) {
struct osmo_gsup_message gsup = { };
uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
@@ -123,7 +142,7 @@ osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr)
}
/* Send ISD to MSC/SGSN */
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP ISD UPDATE");
msg_out = osmo_gsup_msgb_alloc("GSUP ISD UPDATE");
if (msg_out == NULL) {
LOGP(DLGSUP, LOGL_ERROR,
"IMSI='%s': Cannot notify GSUP client; could not allocate msg buffer "
@@ -145,134 +164,174 @@ osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr)
}
}
static int generate_new_msisdn(char *msisdn, const char *imsi, unsigned int len)
{
int i, j, rc;
uint8_t rand_buf[GSM23003_MSISDN_MAX_DIGITS];
OSMO_ASSERT(len <= sizeof(rand_buf));
/* Generate a random unique MSISDN (with retry) */
for (i = 0; i < 10; i++) {
/* Get a random number (with retry) */
for (j = 0; j < 10; j++) {
rc = osmo_get_rand_id(rand_buf, len);
if (!rc)
break;
}
if (rc) {
LOGP(DMAIN, LOGL_ERROR, "IMSI='%s': Failed to generate new MSISDN, random number generator"
" failed (rc=%d)\n", imsi, rc);
return rc;
}
/* Shift 0x00 ... 0xff range to 30 ... 39 (ASCII numbers) */
for (j = 0; j < len; j++)
msisdn[j] = 48 + (rand_buf[j] % 10);
msisdn[j] = '\0';
/* Ensure there is no subscriber with such MSISDN */
if (db_subscr_exists_by_msisdn(g_hlr->dbc, msisdn) == -ENOENT)
return 0;
}
/* Failure */
LOGP(DMAIN, LOGL_ERROR, "IMSI='%s': Failed to generate a new MSISDN, consider increasing "
"the length for the automatically assigned MSISDNs "
"(see 'subscriber-create-on-demand' command)\n", imsi);
return -1;
}
static int subscr_create_on_demand(const char *imsi)
{
char msisdn[GSM23003_MSISDN_MAX_DIGITS + 1];
int rc;
unsigned int rand_msisdn_len = g_hlr->subscr_create_on_demand_rand_msisdn_len;
if (!g_hlr->subscr_create_on_demand)
return -1;
if (db_subscr_exists_by_imsi(g_hlr->dbc, imsi) == 0)
return -1;
if (rand_msisdn_len && generate_new_msisdn(msisdn, imsi, rand_msisdn_len) != 0)
return -1;
LOGP(DMAIN, LOGL_INFO, "IMSI='%s': Creating subscriber on demand\n", imsi);
rc = db_subscr_create(g_hlr->dbc, imsi, g_hlr->subscr_create_on_demand_flags);
if (rc) {
LOGP(DMAIN, LOGL_ERROR, "Failed to create subscriber on demand (rc=%d): IMSI='%s'\n", rc, imsi);
return rc;
}
if (!rand_msisdn_len)
return 0;
/* Update MSISDN of the new (just allocated) subscriber */
rc = db_subscr_update_msisdn_by_imsi(g_hlr->dbc, imsi, msisdn);
if (rc) {
LOGP(DMAIN, LOGL_ERROR, "IMSI='%s': Failed to assign MSISDN='%s' (rc=%d)\n", imsi, msisdn, rc);
return rc;
}
LOGP(DMAIN, LOGL_INFO, "IMSI='%s': Successfully assigned MSISDN='%s'\n", imsi, msisdn);
return 0;
}
/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients.
* \param[in,out] hlr Global hlr context.
* \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call.
* \param[in] nam_val True to enable CS/PS, false to disable.
* \param[in] is_ps True to enable/disable PS, false for CS.
* \returns 0 on success, ENOEXEC if there is no need to change, a negative
* value on error.
*/
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps)
{
int rc;
bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs;
struct osmo_ipa_name vlr_name;
struct osmo_gsup_message gsup_del_data = {
.message_type = OSMO_GSUP_MSGT_DELETE_DATA_REQUEST,
};
OSMO_STRLCPY_ARRAY(gsup_del_data.imsi, subscr->imsi);
if (is_val == nam_val) {
LOGP(DAUC, LOGL_DEBUG, "IMSI-%s: Already has the requested value when asked to %s %s\n",
subscr->imsi, nam_val ? "enable" : "disable", is_ps ? "PS" : "CS");
return ENOEXEC;
}
rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps);
if (rc)
return rc > 0? -rc : rc;
/* If we're disabling, send a notice out to the GSUP client that is
* responsible. Otherwise no need. */
if (nam_val)
return 0;
if (subscr->vlr_number[0] && !osmo_ipa_name_set_str(&vlr_name, subscr->vlr_number))
osmo_gsup_enc_send_to_ipa_name(g_hlr->gs, &vlr_name, &gsup_del_data);
if (subscr->sgsn_number[0] && !osmo_ipa_name_set_str(&vlr_name, subscr->sgsn_number))
osmo_gsup_enc_send_to_ipa_name(g_hlr->gs, &vlr_name, &gsup_del_data);
return 0;
}
/***********************************************************************
* Send Auth Info handling
***********************************************************************/
/* process an incoming SAI request */
static int rx_send_auth_info(struct osmo_gsup_conn *conn,
const struct osmo_gsup_message *gsup,
struct db_context *dbc)
static int rx_send_auth_info(unsigned int auc_3g_ind, struct osmo_gsup_req *req)
{
struct osmo_gsup_message gsup_out;
struct msgb *msg_out;
struct osmo_gsup_message gsup_out = {
.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT,
};
bool separation_bit = false;
int num_auth_vectors = OSMO_GSUP_MAX_NUM_AUTH_INFO;
int rc;
/* initialize return message structure */
memset(&gsup_out, 0, sizeof(gsup_out));
memcpy(&gsup_out.imsi, &gsup->imsi, sizeof(gsup_out.imsi));
subscr_create_on_demand(req->gsup.imsi);
rc = db_get_auc(dbc, gsup->imsi, conn->auc_3g_ind,
if (req->gsup.current_rat_type == OSMO_RAT_EUTRAN_SGS)
separation_bit = true;
if (req->gsup.num_auth_vectors > 0 &&
req->gsup.num_auth_vectors <= OSMO_GSUP_MAX_NUM_AUTH_INFO)
num_auth_vectors = req->gsup.num_auth_vectors;
rc = db_get_auc(g_hlr->dbc, req->gsup.imsi, auc_3g_ind,
gsup_out.auth_vectors,
ARRAY_SIZE(gsup_out.auth_vectors),
gsup->rand, gsup->auts);
num_auth_vectors,
req->gsup.rand, req->gsup.auts, separation_bit);
if (rc <= 0) {
gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR;
switch (rc) {
case 0:
/* 0 means "0 tuples generated", which shouldn't happen.
* Treat the same as "no auth data". */
case -ENOKEY:
LOGP(DAUC, LOGL_NOTICE, "%s: IMSI known, but has no auth data;"
" Returning slightly inaccurate cause 'IMSI Unknown' via GSUP\n",
gsup->imsi);
gsup_out.cause = GMM_CAUSE_IMSI_UNKNOWN;
break;
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN,
"IMSI known, but has no auth data;"
" Returning slightly inaccurate cause 'IMSI Unknown' via GSUP");
return rc;
case -ENOENT:
LOGP(DAUC, LOGL_NOTICE, "%s: IMSI not known\n", gsup->imsi);
gsup_out.cause = GMM_CAUSE_IMSI_UNKNOWN;
break;
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "IMSI unknown");
return rc;
default:
LOGP(DAUC, LOGL_ERROR, "%s: failure to look up IMSI in db\n", gsup->imsi);
gsup_out.cause = GMM_CAUSE_NET_FAIL;
break;
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "failure to look up IMSI in db");
return rc;
}
} else {
gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT;
gsup_out.num_auth_vectors = rc;
}
gsup_out.num_auth_vectors = rc;
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP AUC response");
osmo_gsup_encode(msg_out, &gsup_out);
return osmo_gsup_conn_send(conn, msg_out);
osmo_gsup_req_respond(req, &gsup_out, false, true);
return 0;
}
/***********************************************************************
* LU Operation State / Structure
***********************************************************************/
static LLIST_HEAD(g_lu_ops);
/*! Receive Cancel Location Result from old VLR/SGSN */
void lu_op_rx_cancel_old_ack(struct lu_operation *luop,
const struct osmo_gsup_message *gsup)
/*! Receive Update Location Request, creates new lu_operation */
static int rx_upd_loc_req(struct osmo_gsup_conn *conn, struct osmo_gsup_req *req)
{
OSMO_ASSERT(luop->state == LU_S_CANCEL_SENT);
/* FIXME: Check for spoofing */
osmo_timer_del(&luop->timer);
/* FIXME */
lu_op_tx_insert_subscr_data(luop);
}
/*! Receive Insert Subscriber Data Result from new VLR/SGSN */
static void lu_op_rx_insert_subscr_data_ack(struct lu_operation *luop,
const struct osmo_gsup_message *gsup)
{
OSMO_ASSERT(luop->state == LU_S_ISD_SENT);
/* FIXME: Check for spoofing */
osmo_timer_del(&luop->timer);
/* Subscriber_Present_HLR */
/* CS only: Check_SS_required? -> MAP-FW-CHECK_SS_IND.req */
/* Send final ACK towards inquiring VLR/SGSN */
lu_op_tx_ack(luop);
}
/*! Receive GSUP message for given \ref lu_operation */
void lu_op_rx_gsup(struct lu_operation *luop,
const struct osmo_gsup_message *gsup)
{
switch (gsup->message_type) {
case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
/* FIXME */
break;
case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
lu_op_rx_insert_subscr_data_ack(luop, gsup);
break;
case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR:
/* FIXME */
break;
case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT:
lu_op_rx_cancel_old_ack(luop, gsup);
break;
default:
LOGP(DMAIN, LOGL_ERROR, "Unhandled GSUP msg_type 0x%02x\n",
gsup->message_type);
break;
}
}
/*! Receive Update Location Request, creates new \ref lu_operation */
static int rx_upd_loc_req(struct osmo_gsup_conn *conn,
const struct osmo_gsup_message *gsup)
{
struct hlr_subscriber *subscr;
struct lu_operation *luop = lu_op_alloc_conn(conn);
if (!luop) {
LOGP(DMAIN, LOGL_ERROR, "LU REQ from conn without addr?\n");
return -EINVAL;
}
subscr = &luop->subscr;
lu_op_statechg(luop, LU_S_LU_RECEIVED);
switch (gsup->cn_domain) {
switch (req->gsup.cn_domain) {
case OSMO_GSUP_CN_DOMAIN_CS:
conn->supports_cs = true;
break;
@@ -283,188 +342,220 @@ static int rx_upd_loc_req(struct osmo_gsup_conn *conn,
* a request, the PS Domain is assumed." */
case OSMO_GSUP_CN_DOMAIN_PS:
conn->supports_ps = true;
luop->is_ps = true;
break;
}
llist_add(&luop->list, &g_lu_ops);
/* Roughly follwing "Process Update_Location_HLR" of TS 09.02 */
subscr_create_on_demand(req->gsup.imsi);
/* check if subscriber is known at all */
if (!lu_op_fill_subscr(luop, g_hlr->dbc, gsup->imsi)) {
/* Send Error back: Subscriber Unknown in HLR */
osmo_strlcpy(luop->subscr.imsi, gsup->imsi, sizeof(luop->subscr.imsi));
lu_op_tx_error(luop, GMM_CAUSE_IMSI_UNKNOWN);
return 0;
}
/* Check if subscriber is generally permitted on CS or PS
* service (as requested) */
if (!luop->is_ps && !luop->subscr.nam_cs) {
lu_op_tx_error(luop, GMM_CAUSE_PLMN_NOTALLOWED);
return 0;
} else if (luop->is_ps && !luop->subscr.nam_ps) {
lu_op_tx_error(luop, GMM_CAUSE_GPRS_NOTALLOWED);
return 0;
}
/* TODO: Set subscriber tracing = deactive in VLR/SGSN */
#if 0
/* Cancel in old VLR/SGSN, if new VLR/SGSN differs from old */
if (luop->is_ps == false &&
strcmp(subscr->vlr_number, vlr_number)) {
lu_op_tx_cancel_old(luop);
} else if (luop->is_ps == true &&
strcmp(subscr->sgsn_number, sgsn_number)) {
lu_op_tx_cancel_old(luop);
} else
#endif
/* Store the VLR / SGSN number with the subscriber, so we know where it was last seen. */
LOGP(DAUC, LOGL_DEBUG, "IMSI='%s': storing %s = %s\n",
subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number",
osmo_quote_str((const char*)luop->peer, -1));
if (db_subscr_lu(g_hlr->dbc, subscr->id, (const char *)luop->peer, luop->is_ps))
LOGP(DAUC, LOGL_ERROR, "IMSI='%s': Cannot update %s in the database\n",
subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number");
{
/* TODO: Subscriber allowed to roam in PLMN? */
/* TODO: Update RoutingInfo */
/* TODO: Reset Flag MS Purged (cs/ps) */
/* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */
lu_op_tx_insert_subscr_data(luop);
}
lu_rx_gsup(req);
return 0;
}
static int rx_purge_ms_req(struct osmo_gsup_conn *conn,
const struct osmo_gsup_message *gsup)
static int rx_purge_ms_req(struct osmo_gsup_req *req)
{
struct osmo_gsup_message gsup_reply = {0};
struct msgb *msg_out;
bool is_ps = false;
bool is_ps = (req->gsup.cn_domain != OSMO_GSUP_CN_DOMAIN_CS);
int rc;
LOGP(DAUC, LOGL_INFO, "%s: Purge MS (%s)\n", gsup->imsi,
is_ps ? "PS" : "CS");
memcpy(gsup_reply.imsi, gsup->imsi, sizeof(gsup_reply.imsi));
if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS)
is_ps = true;
LOG_GSUP_REQ_CAT(req, DAUC, LOGL_INFO, "Purge MS (%s)\n", is_ps ? "PS" : "CS");
/* FIXME: check if the VLR that sends the purge is the same that
* we have on record. Only update if yes */
/* Perform the actual update of the DB */
rc = db_subscr_purge(g_hlr->dbc, gsup->imsi, true, is_ps);
rc = db_subscr_purge(g_hlr->dbc, req->gsup.imsi, true, is_ps);
if (rc == 0)
gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_RESULT;
else if (rc == -ENOENT) {
gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR;
gsup_reply.cause = GMM_CAUSE_IMSI_UNKNOWN;
} else {
gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR;
gsup_reply.cause = GMM_CAUSE_NET_FAIL;
}
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP AUC response");
osmo_gsup_encode(msg_out, &gsup_reply);
return osmo_gsup_conn_send(conn, msg_out);
osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_PURGE_MS_RESULT, true);
else if (rc == -ENOENT)
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "IMSI unknown");
else
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "db error");
return rc;
}
static int gsup_send_err_reply(struct osmo_gsup_conn *conn, const char *imsi,
enum osmo_gsup_message_type type_in, uint8_t err_cause)
static int rx_check_imei_req(struct osmo_gsup_req *req)
{
int type_err = osmo_gsup_get_err_msg_type(type_in);
struct osmo_gsup_message gsup_reply = {0};
struct msgb *msg_out;
struct osmo_gsup_message gsup_reply;
char imei[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1] = {0};
const struct osmo_gsup_message *gsup = &req->gsup;
int rc;
if (type_err < 0) {
LOGP(DMAIN, LOGL_ERROR, "unable to determine error response for %s\n",
osmo_gsup_message_type_name(type_in));
return type_err;
/* Require IMEI */
if (!gsup->imei_enc) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "missing IMEI");
return -1;
}
OSMO_STRLCPY_ARRAY(gsup_reply.imsi, imsi);
gsup_reply.message_type = type_err;
gsup_reply.cause = err_cause;
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP ERR response");
OSMO_ASSERT(msg_out);
osmo_gsup_encode(msg_out, &gsup_reply);
LOGP(DMAIN, LOGL_NOTICE, "Tx %s\n", osmo_gsup_message_type_name(type_err));
return osmo_gsup_conn_send(conn, msg_out);
/* Decode IMEI (fails if IMEI is too long) */
rc = gsm48_decode_bcd_number2(imei, sizeof(imei), gsup->imei_enc, gsup->imei_enc_len, 0);
if (rc < 0) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
"failed to decode IMEI %s (rc: %d)",
osmo_hexdump_c(OTC_SELECT, gsup->imei_enc, gsup->imei_enc_len),
rc);
return -1;
}
/* Check if IMEI is too short */
if (!osmo_imei_str_valid(imei, false)) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
"invalid IMEI: %s", osmo_quote_str_c(OTC_SELECT, imei, -1));
return -1;
}
subscr_create_on_demand(gsup->imsi);
/* Save in DB if desired */
if (g_hlr->store_imei) {
LOGP(DAUC, LOGL_DEBUG, "IMSI='%s': storing IMEI = %s\n", gsup->imsi, imei);
if (db_subscr_update_imei_by_imsi(g_hlr->dbc, gsup->imsi, imei) < 0) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "Failed to store IMEI in HLR db");
return -1;
}
} else {
/* Check if subscriber exists and print IMEI */
LOGP(DMAIN, LOGL_INFO, "IMSI='%s': has IMEI = %s (consider setting 'store-imei')\n", gsup->imsi, imei);
struct hlr_subscriber subscr;
if (db_subscr_get_by_imsi(g_hlr->dbc, gsup->imsi, &subscr) < 0) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "IMSI unknown");
return -1;
}
}
/* Accept all IMEIs */
gsup_reply = (struct osmo_gsup_message){
.message_type = OSMO_GSUP_MSGT_CHECK_IMEI_RESULT,
.imei_result = OSMO_GSUP_IMEI_RESULT_ACK,
};
return osmo_gsup_req_respond(req, &gsup_reply, false, true);
}
static char namebuf[255];
#define LOGP_GSUP_FWD(gsup, level, fmt, args ...) \
LOGP(DMAIN, level, "Forward %s (class=%s, IMSI=%s, %s->%s): " fmt, \
osmo_gsup_message_type_name((gsup)->message_type), \
osmo_gsup_message_class_name((gsup)->message_class), \
(gsup)->imsi, \
osmo_quote_str((const char *)(gsup)->source_name, (gsup)->source_name_len), \
osmo_quote_str_buf2(namebuf, sizeof(namebuf), (const char *)(gsup)->destination_name, (gsup)->destination_name_len), \
## args)
static int read_cb_forward(struct osmo_gsup_req *req)
{
int ret = -EINVAL;
const struct osmo_gsup_message *gsup = &req->gsup;
struct osmo_gsup_message gsup_err;
struct msgb *forward_msg;
struct osmo_ipa_name destination_name;
/* Check for routing IEs */
if (!req->gsup.source_name || !req->gsup.source_name_len
|| !req->gsup.destination_name || !req->gsup.destination_name_len) {
LOGP_GSUP_FWD(&req->gsup, LOGL_ERROR, "missing routing IEs\n");
goto routing_error;
}
if (osmo_ipa_name_set(&destination_name, req->gsup.destination_name, req->gsup.destination_name_len)) {
LOGP_GSUP_FWD(&req->gsup, LOGL_ERROR, "invalid destination name\n");
goto routing_error;
}
LOG_GSUP_REQ(req, LOGL_INFO, "Forwarding to %s\n", osmo_ipa_name_to_str(&destination_name));
/* Forward message without re-encoding (so we don't remove unknown IEs).
* Copy GSUP part to forward, removing incoming IPA header to be able to prepend an outgoing IPA header */
forward_msg = osmo_gsup_msgb_alloc("GSUP forward");
forward_msg->l2h = msgb_put(forward_msg, msgb_l2len(req->msg));
memcpy(forward_msg->l2h, msgb_l2(req->msg), msgb_l2len(req->msg));
ret = osmo_gsup_send_to_ipa_name(g_hlr->gs, &destination_name, forward_msg);
if (ret) {
LOGP_GSUP_FWD(gsup, LOGL_ERROR, "%s (rc=%d)\n",
ret == -ENODEV ? "destination not connected" : "unknown error",
ret);
goto routing_error;
}
osmo_gsup_req_free(req);
return 0;
routing_error:
gsup_err = (struct osmo_gsup_message){
.message_type = OSMO_GSUP_MSGT_ROUTING_ERROR,
.source_name = gsup->destination_name,
.source_name_len = gsup->destination_name_len,
};
osmo_gsup_req_respond(req, &gsup_err, true, true);
return -1;
}
static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
{
static struct osmo_gsup_message gsup;
int rc;
struct osmo_gsup_req *req = osmo_gsup_conn_rx(conn, msg);
if (!req)
return -EINVAL;
rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
if (rc < 0) {
LOGP(DMAIN, LOGL_ERROR, "error in GSUP decode: %d\n", rc);
return rc;
/* If the GSUP recipient is other than this HLR, forward. */
if (req->gsup.destination_name_len) {
struct osmo_ipa_name destination_name;
struct osmo_ipa_name my_name;
osmo_ipa_name_set_str(&my_name, g_hlr->gsup_unit_name.serno);
if (!osmo_ipa_name_set(&destination_name, req->gsup.destination_name, req->gsup.destination_name_len)
&& osmo_ipa_name_cmp(&destination_name, &my_name)) {
return read_cb_forward(req);
}
}
/* 3GPP TS 23.003 Section 2.2 clearly states that an IMSI with less than 5
* digits is impossible. Even 5 digits is a highly theoretical case */
if (strlen(gsup.imsi) < 5)
return gsup_send_err_reply(conn, gsup.imsi, gsup.message_type, GMM_CAUSE_INV_MAND_INFO);
/* Distributed GSM: check whether to proxy for / lookup a remote HLR.
* It would require less database hits to do this only if a local-only operation fails with "unknown IMSI", but
* it becomes semantically easier if we do this once-off ahead of time. */
if (osmo_mslookup_client_active(g_hlr->mslookup.client.client)
|| osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy)) {
if (dgsm_check_forward_gsup_msg(req))
return 0;
}
switch (gsup.message_type) {
/* HLR related messages that are handled at this HLR instance */
switch (req->gsup.message_type) {
/* requests sent to us */
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST:
rx_send_auth_info(conn, &gsup, g_hlr->dbc);
rx_send_auth_info(conn->auc_3g_ind, req);
break;
case OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST:
rx_upd_loc_req(conn, &gsup);
rx_upd_loc_req(conn, req);
break;
case OSMO_GSUP_MSGT_PURGE_MS_REQUEST:
rx_purge_ms_req(conn, &gsup);
rx_purge_ms_req(req);
break;
/* responses to requests sent by us */
case OSMO_GSUP_MSGT_DELETE_DATA_ERROR:
LOGP(DMAIN, LOGL_ERROR, "Error while deleting subscriber data "
"for IMSI %s\n", gsup.imsi);
LOG_GSUP_REQ(req, LOGL_ERROR, "Peer responds with: Error while deleting subscriber data\n");
osmo_gsup_req_free(req);
break;
case OSMO_GSUP_MSGT_DELETE_DATA_RESULT:
LOGP(DMAIN, LOGL_ERROR, "Deleting subscriber data for IMSI %s\n",
gsup.imsi);
LOG_GSUP_REQ(req, LOGL_DEBUG, "Peer responds with: Subscriber data deleted\n");
osmo_gsup_req_free(req);
break;
case OSMO_GSUP_MSGT_PROC_SS_REQUEST:
case OSMO_GSUP_MSGT_PROC_SS_RESULT:
rx_proc_ss_req(conn, &gsup);
rx_proc_ss_req(req);
break;
case OSMO_GSUP_MSGT_PROC_SS_ERROR:
rx_proc_ss_error(conn, &gsup);
rx_proc_ss_error(req);
break;
case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR:
case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT:
{
struct lu_operation *luop = lu_op_by_imsi(gsup.imsi,
&g_lu_ops);
if (!luop) {
LOGP(DMAIN, LOGL_ERROR, "GSUP message %s for "
"unknown IMSI %s\n",
osmo_gsup_message_type_name(gsup.message_type),
gsup.imsi);
break;
}
lu_op_rx_gsup(luop, &gsup);
}
lu_rx_gsup(req);
break;
case OSMO_GSUP_MSGT_CHECK_IMEI_REQUEST:
rx_check_imei_req(req);
break;
default:
LOGP(DMAIN, LOGL_DEBUG, "Unhandled GSUP message type %s\n",
osmo_gsup_message_type_name(gsup.message_type));
osmo_gsup_message_type_name(req->gsup.message_type));
osmo_gsup_req_free(req);
break;
}
msgb_free(msg);
return 0;
}
@@ -484,7 +575,12 @@ static void print_help()
printf(" -T --timestamp Prefix every log line with a timestamp.\n");
printf(" -e --log-level number Set a global loglevel.\n");
printf(" -U --db-upgrade Allow HLR database schema upgrades.\n");
printf(" -C --db-check Quit after opening (and upgrading) the database.\n");
printf(" -V --version Print the version of OsmoHLR.\n");
printf("\nVTY reference generation:\n");
printf(" --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n");
printf(" --vty-ref-xml Generate the VTY reference XML output and exit.\n");
}
static struct {
@@ -492,17 +588,45 @@ static struct {
const char *db_file;
bool daemonize;
bool db_upgrade;
bool db_check;
} cmdline_opts = {
.config_file = "osmo-hlr.cfg",
.db_file = "hlr.db",
.db_file = NULL,
.daemonize = false,
.db_upgrade = false,
};
static void handle_long_options(const char *prog_name, const int long_option)
{
static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT;
switch (long_option) {
case 1:
vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg);
if (vty_ref_mode < 0) {
fprintf(stderr, "%s: Unknown VTY reference generation "
"mode '%s'\n", prog_name, optarg);
exit(2);
}
break;
case 2:
fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
get_value_string(vty_ref_gen_mode_names, vty_ref_mode),
get_value_string(vty_ref_gen_mode_desc, vty_ref_mode));
vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode);
exit(0);
default:
fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
exit(2);
}
}
static void handle_options(int argc, char **argv)
{
while (1) {
int option_index = 0, c;
static int long_option = 0;
static struct option long_options[] = {
{"help", 0, 0, 'h'},
{"config-file", 1, 0, 'c'},
@@ -513,7 +637,10 @@ static void handle_options(int argc, char **argv)
{"log-level", 1, 0, 'e'},
{"timestamp", 0, 0, 'T'},
{"db-upgrade", 0, 0, 'U' },
{"db-check", 0, 0, 'C' },
{"version", 0, 0, 'V' },
{"vty-ref-mode", 1, &long_option, 1},
{"vty-ref-xml", 0, &long_option, 2},
{0, 0, 0, 0}
};
@@ -523,6 +650,9 @@ static void handle_options(int argc, char **argv)
break;
switch (c) {
case 0:
handle_long_options(argv[0], long_option);
break;
case 'h':
print_usage();
print_help();
@@ -551,6 +681,9 @@ static void handle_options(int argc, char **argv)
case 'U':
cmdline_opts.db_upgrade = true;
break;
case 'C':
cmdline_opts.db_check = true;
break;
case 'V':
print_version(1);
exit(0);
@@ -562,15 +695,19 @@ static void handle_options(int argc, char **argv)
break;
}
}
}
static void *hlr_ctx = NULL;
if (argc > optind) {
fprintf(stderr, "Unsupported positional arguments on command line\n");
exit(2);
}
}
static void signal_hdlr(int signal)
{
switch (signal) {
case SIGTERM:
case SIGINT:
LOGP(DMAIN, LOGL_NOTICE, "Terminating due to SIGINT\n");
LOGP(DMAIN, LOGL_NOTICE, "Terminating due to signal=%d\n", signal);
quit++;
break;
case SIGUSR1:
@@ -607,9 +744,12 @@ int main(int argc, char **argv)
g_hlr = talloc_zero(hlr_ctx, struct hlr);
INIT_LLIST_HEAD(&g_hlr->euse_list);
INIT_LLIST_HEAD(&g_hlr->iuse_list);
INIT_LLIST_HEAD(&g_hlr->ss_sessions);
INIT_LLIST_HEAD(&g_hlr->ussd_routes);
INIT_LLIST_HEAD(&g_hlr->mslookup.server.local_site_services);
g_hlr->db_file_path = talloc_strdup(g_hlr, HLR_DEFAULT_DB_FILE_PATH);
g_hlr->mslookup.server.mdns.domain_suffix = talloc_strdup(g_hlr, OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT);
g_hlr->mslookup.client.mdns.domain_suffix = talloc_strdup(g_hlr, OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT);
/* Init default (call independent) SS session guard timeout value */
g_hlr->ncss_guard_timeout = NCSS_GUARD_TIMEOUT_DEFAULT;
@@ -620,10 +760,16 @@ int main(int argc, char **argv)
exit(1);
}
/* Set up llists and objects, startup is happening from VTY commands. */
dgsm_init(hlr_ctx);
osmo_stats_init(hlr_ctx);
vty_init(&vty_info);
ctrl_vty_init(hlr_ctx);
hlr_vty_init();
dgsm_vty_init();
osmo_cpu_sched_vty_init(hlr_ctx);
handle_options(argc, argv);
hlr_vty_init(&hlr_log_info);
rc = vty_read_config_file(cmdline_opts.config_file, NULL);
if (rc < 0) {
@@ -633,12 +779,6 @@ int main(int argc, char **argv)
return rc;
}
/* start telnet after reading config for vty_get_bind_addr() */
rc = telnet_init_dynif(hlr_ctx, NULL, vty_get_bind_addr(),
OSMO_VTY_PORT_HLR);
if (rc < 0)
return rc;
LOGP(DMAIN, LOGL_NOTICE, "hlr starting\n");
rc = rand_init();
@@ -647,24 +787,48 @@ int main(int argc, char **argv)
exit(1);
}
g_hlr->dbc = db_open(hlr_ctx, cmdline_opts.db_file, true, cmdline_opts.db_upgrade);
if (cmdline_opts.db_file)
osmo_talloc_replace_string(g_hlr, &g_hlr->db_file_path, cmdline_opts.db_file);
g_hlr->dbc = db_open(hlr_ctx, g_hlr->db_file_path, true, cmdline_opts.db_upgrade);
if (!g_hlr->dbc) {
LOGP(DMAIN, LOGL_FATAL, "Error opening database\n");
LOGP(DMAIN, LOGL_FATAL, "Error opening database %s\n", osmo_quote_str(g_hlr->db_file_path, -1));
exit(1);
}
if (cmdline_opts.db_check) {
LOGP(DMAIN, LOGL_NOTICE, "Cmdline option --db-check: Database was opened successfully, quitting.\n");
db_close(g_hlr->dbc);
log_fini();
talloc_free(hlr_ctx);
talloc_free(tall_vty_ctx);
talloc_disable_null_tracking();
exit(0);
}
/* start telnet after reading config for vty_get_bind_addr() */
rc = telnet_init_dynif(hlr_ctx, NULL, vty_get_bind_addr(),
OSMO_VTY_PORT_HLR);
if (rc < 0)
return rc;
g_hlr->gs = osmo_gsup_server_create(hlr_ctx, g_hlr->gsup_bind_addr, OSMO_GSUP_PORT,
read_cb, &g_lu_ops, g_hlr);
read_cb, g_hlr);
if (!g_hlr->gs) {
LOGP(DMAIN, LOGL_FATAL, "Error starting GSUP server\n");
exit(1);
}
proxy_init(g_hlr->gs);
g_hlr->ctrl_bind_addr = ctrl_vty_get_bind_addr();
g_hlr->ctrl = hlr_controlif_setup(g_hlr);
dgsm_start(hlr_ctx);
osmo_init_ignore_signals();
signal(SIGINT, &signal_hdlr);
signal(SIGTERM, &signal_hdlr);
signal(SIGUSR1, &signal_hdlr);
if (cmdline_opts.daemonize) {
@@ -676,7 +840,9 @@ int main(int argc, char **argv)
}
while (!quit)
osmo_select_main(0);
osmo_select_main_ctx(0);
dgsm_stop();
osmo_gsup_server_destroy(g_hlr->gs);
db_close(g_hlr->dbc);

View File

@@ -1,60 +0,0 @@
/* OsmoHLR generic header */
/* (C) 2017 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Max Suraev <msuraev@sysmocom.de>
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <stdbool.h>
#include <osmocom/core/linuxlist.h>
struct hlr_euse;
struct hlr {
/* GSUP server pointer */
struct osmo_gsup_server *gs;
/* DB context */
struct db_context *dbc;
/* Control Interface */
struct ctrl_handle *ctrl;
const char *ctrl_bind_addr;
/* Local bind addr */
char *gsup_bind_addr;
struct llist_head euse_list;
struct hlr_euse *euse_default;
struct llist_head iuse_list;
/* NCSS (call independent) session guard timeout value */
int ncss_guard_timeout;
struct llist_head ussd_routes;
struct llist_head ss_sessions;
};
extern struct hlr *g_hlr;
struct hlr_subscriber;
void osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr);

View File

@@ -25,13 +25,14 @@
#include <getopt.h>
#include <inttypes.h>
#include <string.h>
#include <errno.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/application.h>
#include "logging.h"
#include "db.h"
#include "rand.h"
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/db.h>
#include <osmocom/hlr/rand.h>
struct hlr_db_tool_ctx {
/* DB context */
@@ -70,8 +71,9 @@ static void print_help()
printf(" (All commands imply this if none exists yet.)\n");
printf("\n");
printf(" import-nitb-db <nitb.db> Add OsmoNITB db's subscribers to OsmoHLR db.\n");
printf(" Be aware that the import is lossy, only the\n");
printf(" IMSI, MSISDN, nam_cs/ps and 2G auth data are set.\n");
printf(" Be aware that the import is somewhat lossy, only the IMSI,\n");
printf(" MSISDN, IMEI, nam_cs/ps, 2G auth data and last seen LU are set.\n");
printf(" The most recently associated IMEI from the Equipment table is used.\n");
}
static void print_version(int print_copyright)
@@ -212,9 +214,15 @@ enum nitb_stmt {
static const char *nitb_stmt_sql[] = {
[NITB_SELECT_SUBSCR] =
"SELECT imsi, id, extension, authorized"
" FROM Subscriber"
" ORDER BY id",
"SELECT s.imsi, s.id, s.extension, s.authorized,"
" SUBSTR(e.imei,0,15), STRFTIME('%s', s.expire_lu)"
" FROM Subscriber s LEFT JOIN"
" (SELECT imei, subscriber_id, MAX(Equipment.updated) AS updated"
" FROM Equipment,EquipmentWatch"
" WHERE Equipment.id = EquipmentWatch.equipment_id"
" GROUP BY EquipmentWatch.subscriber_id) e"
" ON e.subscriber_id = s.id"
" ORDER by s.id",
[NITB_SELECT_AUTH_KEYS] =
"SELECT algorithm_id, a3a8_ki from authkeys"
" WHERE subscriber_id = $subscr_id",
@@ -222,8 +230,65 @@ static const char *nitb_stmt_sql[] = {
sqlite3_stmt *nitb_stmt[ARRAY_SIZE(nitb_stmt_sql)] = {};
enum hlr_db_stmt {
HLR_DB_STMT_SET_IMPLICIT_LU_BY_IMSI,
};
static const char *hlr_db_stmt_sql[] = {
[HLR_DB_STMT_SET_IMPLICIT_LU_BY_IMSI] =
"UPDATE subscriber SET last_lu_seen = datetime($last_lu, 'unixepoch') WHERE imsi = $imsi",
};
sqlite3_stmt *hlr_db_stmt[ARRAY_SIZE(hlr_db_stmt_sql)] = {};
size_t _dbd_decode_binary(const unsigned char *in, unsigned char *out);
/*! Set a subscriber's LU timestamp in the HLR database.
* In normal operations there is never any need to explicitly
* update the value of last_lu_seen, so this function can live here.
*
* \param[in,out] dbc database context.
* \param[in] imsi ASCII string of IMSI digits
* \param[in] imei ASCII string of identifier digits, or NULL to remove the IMEI.
* \returns 0 on success, -ENOENT when the given subscriber does not exist,
* -EIO on database errors.
*/
int db_subscr_update_lu_by_imsi(struct db_context *dbc, const char* imsi, const int last_lu)
{
int rc, ret = 0;
sqlite3_stmt *stmt = hlr_db_stmt[HLR_DB_STMT_SET_IMPLICIT_LU_BY_IMSI];
if (!db_bind_text(stmt, "$imsi", imsi))
return -EIO;
if (last_lu && !db_bind_int(stmt, "$last_lu", last_lu))
return -EIO;
/* execute the statement */
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGP(DAUC, LOGL_ERROR, "Update last_lu_seen for subscriber IMSI='%s': SQL Error: %s\n", imsi,
sqlite3_errmsg(dbc->db));
ret = -EIO;
goto out;
}
/* verify execution result */
rc = sqlite3_changes(dbc->db);
if (!rc) {
LOGP(DAUC, LOGL_ERROR, "Cannot update last_lu_seen for subscriber IMSI='%s': no such subscriber\n", imsi);
ret = -ENOENT;
} else if (rc != 1) {
LOGP(DAUC, LOGL_ERROR, "Update last_lu_seen for subscriber IMSI='%s': SQL modified %d rows (expected 1)\n",
imsi, rc);
ret = -EIO;
}
out:
db_remove_reset(stmt);
return ret;
}
void import_nitb_subscr_aud(sqlite3 *nitb_db, const char *imsi, int64_t nitb_id, int64_t hlr_id)
{
int rc;
@@ -297,12 +362,13 @@ void import_nitb_subscr(sqlite3 *nitb_db, sqlite3_stmt *stmt)
int64_t imsi;
char imsi_str[32];
bool authorized;
int last_lu_int;
imsi = sqlite3_column_int64(stmt, 0);
snprintf(imsi_str, sizeof(imsi_str), "%" PRId64, imsi);
rc = db_subscr_create(dbc, imsi_str);
rc = db_subscr_create(dbc, imsi_str, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS);
if (rc < 0) {
LOGP(DDB, LOGL_ERROR, "OsmoNITB DB import to %s: failed to create IMSI %s: %d: %s\n",
dbc->fname,
@@ -315,8 +381,18 @@ void import_nitb_subscr(sqlite3 *nitb_db, sqlite3_stmt *stmt)
nitb_id = sqlite3_column_int64(stmt, 1);
copy_sqlite3_text_to_buf(subscr.msisdn, stmt, 2);
authorized = sqlite3_column_int(stmt, 3) ? true : false;
copy_sqlite3_text_to_buf(subscr.imei, stmt, 4);
/* Default periodic LU was 30 mins and the expire_lu
* was twice that + 1 min
*/
last_lu_int = sqlite3_column_int(stmt, 5) - 3660;
db_subscr_update_msisdn_by_imsi(dbc, imsi_str, subscr.msisdn);
/* In case the subscriber was somehow never seen, invent an IMEI */
if (strlen(subscr.imei) == 14)
db_subscr_update_imei_by_imsi(dbc, imsi_str, subscr.imei);
db_subscr_update_lu_by_imsi(dbc, imsi_str, last_lu_int);
db_subscr_nam(dbc, imsi_str, authorized, true);
db_subscr_nam(dbc, imsi_str, authorized, false);
@@ -361,6 +437,17 @@ int import_nitb_db(void)
}
}
for (i = 0; i < ARRAY_SIZE(hlr_db_stmt_sql); i++) {
sql = hlr_db_stmt_sql[i];
rc = sqlite3_prepare_v2(g_hlr_db_tool_ctx->dbc->db, hlr_db_stmt_sql[i], -1,
&hlr_db_stmt[i], NULL);
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "OsmoHLR DB: Unable to prepare SQL statement '%s'\n", sql);
ret = -1;
goto out_free;
}
}
stmt = nitb_stmt[NITB_SELECT_SUBSCR];
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
@@ -387,6 +474,7 @@ int main(int argc, char **argv)
{
int rc;
int (*main_action)(void);
int i;
main_action = NULL;
g_hlr_db_tool_ctx = talloc_zero(NULL, struct hlr_db_tool_ctx);
@@ -430,6 +518,11 @@ int main(int argc, char **argv)
if (main_action)
rc = (*main_action)();
/* db_close will only finalize statments in g_hlr_db_tool_ctx->dbc->stmt
* it is ok to call finalize on NULL */
for (i = 0; i < ARRAY_SIZE(hlr_db_stmt); i++) {
sqlite3_finalize(hlr_db_stmt[i]);
}
db_close(g_hlr_db_tool_ctx->dbc);
log_fini();
exit(rc ? EXIT_FAILURE : EXIT_SUCCESS);

View File

@@ -29,11 +29,12 @@
#include <string.h>
#include <errno.h>
#include "hlr.h"
#include "hlr_ussd.h"
#include "gsup_server.h"
#include "gsup_router.h"
#include "logging.h"
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/hlr_ussd.h>
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/gsup_router.h>
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/db.h>
/***********************************************************************
* core data structures expressing config from VTY
@@ -149,7 +150,7 @@ struct ss_session {
/* link us to hlr->ss_sessions */
struct llist_head list;
/* imsi of this session */
char imsi[GSM23003_IMSI_MAX_DIGITS+2];
char imsi[OSMO_IMSI_BUF_SIZE];
/* ID of this session (unique per IMSI) */
uint32_t session_id;
/* state of the session */
@@ -166,9 +167,17 @@ struct ss_session {
const struct hlr_iuse *iuse;
} u;
/* subscriber's vlr_number
* MO USSD: originating MSC's vlr_number
* MT USSD: looked up once per session and cached here */
struct osmo_ipa_name vlr_name;
/* we don't keep a pointer to the osmo_gsup_{route,conn} towards the MSC/VLR here,
* as this might change during inter-VLR hand-over, and we simply look-up the serving MSC/VLR
* every time we receive an USSD component from the EUSE */
struct osmo_gsup_req *initial_req_from_ms;
struct osmo_gsup_req *initial_req_from_euse;
};
struct ss_session *ss_session_find(struct hlr *hlr, const char *imsi, uint32_t session_id)
@@ -184,6 +193,10 @@ struct ss_session *ss_session_find(struct hlr *hlr, const char *imsi, uint32_t s
void ss_session_free(struct ss_session *ss)
{
osmo_timer_del(&ss->timeout);
if (ss->initial_req_from_ms)
osmo_gsup_req_free(ss->initial_req_from_ms);
if (ss->initial_req_from_euse)
osmo_gsup_req_free(ss->initial_req_from_euse);
llist_del(&ss->list);
talloc_free(ss);
}
@@ -222,32 +235,73 @@ struct ss_session *ss_session_alloc(struct hlr *hlr, const char *imsi, uint32_t
* handling functions for encoding SS messages + wrapping them in GSUP
***********************************************************************/
/* Resolve the target MSC by ss->imsi and send GSUP message. */
static int ss_gsup_send_to_ms(struct ss_session *ss, struct osmo_gsup_server *gs, struct osmo_gsup_message *gsup)
{
struct hlr_subscriber subscr = {};
struct msgb *msg;
int rc;
if (ss->initial_req_from_ms) {
/* Use non-final osmo_gsup_req_respond() to not deallocate the ss->initial_req_from_ms */
osmo_gsup_req_respond(ss->initial_req_from_ms, gsup, false, false);
return 0;
}
msg = osmo_gsup_msgb_alloc("GSUP USSD FW");
rc = osmo_gsup_encode(msg, gsup);
if (rc) {
LOGPSS(ss, LOGL_ERROR, "Failed to encode GSUP message\n");
msgb_free(msg);
return rc;
}
/* Use vlr_number as looked up by the caller, or look up now. */
if (!ss->vlr_name.len) {
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
if (rc < 0) {
LOGPSS(ss, LOGL_ERROR, "Cannot find subscriber, cannot route GSUP message\n");
msgb_free(msg);
return -EINVAL;
}
osmo_ipa_name_set_str(&ss->vlr_name, subscr.vlr_number);
}
/* Check for empty string (all vlr_number strings end in "\0", because otherwise gsup_route_find() fails) */
if (ss->vlr_name.len <= 1) {
LOGPSS(ss, LOGL_ERROR, "Cannot send GSUP message, no VLR number stored for subscriber\n");
msgb_free(msg);
return -EINVAL;
}
LOGPSS(ss, LOGL_DEBUG, "Tx SS/USSD to VLR %s\n", osmo_ipa_name_to_str(&ss->vlr_name));
return osmo_gsup_send_to_ipa_name(gs, &ss->vlr_name, msg);
}
static int ss_tx_to_ms(struct ss_session *ss, enum osmo_gsup_message_type gsup_msg_type,
bool final, struct msgb *ss_msg)
struct msgb *ss_msg)
{
struct osmo_gsup_message resp = {0};
struct msgb *resp_msg;
struct osmo_gsup_message resp;
int rc;
resp = (struct osmo_gsup_message) {
.message_type = gsup_msg_type,
.session_id = ss->session_id,
.session_state = ss->state,
};
resp.message_type = gsup_msg_type;
OSMO_STRLCPY_ARRAY(resp.imsi, ss->imsi);
if (final)
resp.session_state = OSMO_GSUP_SESSION_STATE_END;
else
resp.session_state = OSMO_GSUP_SESSION_STATE_CONTINUE;
resp.session_id = ss->session_id;
if (ss_msg) {
resp.ss_info = msgb_data(ss_msg);
resp.ss_info_len = msgb_length(ss_msg);
}
resp_msg = gsm0480_msgb_alloc_name(__func__);
OSMO_ASSERT(resp_msg);
osmo_gsup_encode(resp_msg, &resp);
msgb_free(ss_msg);
rc = ss_gsup_send_to_ms(ss, g_hlr->gs, &resp);
/* FIXME: resolve this based on the database vlr_addr */
return osmo_gsup_addr_send(g_hlr->gs, (uint8_t *)"MSC-00-00-00-00-00-00", 22, resp_msg);
msgb_free(ss_msg);
return rc;
}
#if 0
@@ -258,39 +312,43 @@ static int ss_tx_reject(struct ss_session *ss, int invoke_id, uint8_t problem_ta
LOGPSS(ss, LOGL_NOTICE, "Tx Reject(%u, 0x%02x, 0x%02x)\n", invoke_id,
problem_tag, problem_code);
OSMO_ASSERT(msg);
return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, true, msg);
ss->state = OSMO_GSUP_SESSION_STATE_END;
return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, msg);
}
#endif
static int ss_tx_error(struct ss_session *ss, uint8_t invoke_id, uint8_t error_code)
static int ss_tx_to_ms_error(struct ss_session *ss, uint8_t invoke_id, uint8_t error_code)
{
struct msgb *msg = gsm0480_gen_return_error(invoke_id, error_code);
LOGPSS(ss, LOGL_NOTICE, "Tx ReturnError(%u, 0x%02x)\n", invoke_id, error_code);
OSMO_ASSERT(msg);
return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, true, msg);
ss->state = OSMO_GSUP_SESSION_STATE_END;
return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, msg);
}
static int ss_tx_ussd_7bit(struct ss_session *ss, bool final, uint8_t invoke_id, const char *text)
static int ss_tx_to_ms_ussd_7bit(struct ss_session *ss, uint8_t invoke_id, const char *text)
{
struct msgb *msg = gsm0480_gen_ussd_resp_7bit(invoke_id, text);
LOGPSS(ss, LOGL_INFO, "Tx USSD '%s'\n", text);
OSMO_ASSERT(msg);
return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, final, msg);
return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, msg);
}
/***********************************************************************
* Internal USSD Handlers
***********************************************************************/
#include "db.h"
#include <osmocom/hlr/db.h>
static int handle_ussd_own_msisdn(struct osmo_gsup_conn *conn, struct ss_session *ss,
static int handle_ussd_own_msisdn(struct ss_session *ss,
const struct osmo_gsup_message *gsup, const struct ss_request *req)
{
struct hlr_subscriber subscr;
char buf[GSM0480_USSD_7BIT_STRING_LEN+1];
int rc;
ss->state = OSMO_GSUP_SESSION_STATE_END;
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
switch (rc) {
case 0:
@@ -298,25 +356,39 @@ static int handle_ussd_own_msisdn(struct osmo_gsup_conn *conn, struct ss_session
snprintf(buf, sizeof(buf), "You have no MSISDN!");
else
snprintf(buf, sizeof(buf), "Your extension is %s", subscr.msisdn);
ss_tx_ussd_7bit(ss, true, req->invoke_id, buf);
ss_tx_to_ms_ussd_7bit(ss, req->invoke_id, buf);
break;
case -ENOENT:
ss_tx_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
ss_tx_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return 0;
}
static int handle_ussd_own_imsi(struct osmo_gsup_conn *conn, struct ss_session *ss,
static int handle_ussd_own_imsi(struct ss_session *ss,
const struct osmo_gsup_message *gsup, const struct ss_request *req)
{
char buf[GSM0480_USSD_7BIT_STRING_LEN+1];
snprintf(buf, sizeof(buf), "Your IMSI is %s", ss->imsi);
ss_tx_ussd_7bit(ss, true, req->invoke_id, buf);
ss->state = OSMO_GSUP_SESSION_STATE_END;
ss_tx_to_ms_ussd_7bit(ss, req->invoke_id, buf);
return 0;
}
/* This handler just keeps the session idle unless the guard timer expires. */
static int handle_ussd_test_idle(struct ss_session *ss,
const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
char buf[GSM0480_USSD_7BIT_STRING_LEN + 1];
snprintf(buf, sizeof(buf), "Keeping your session idle, it will expire "
"at most in %u seconds.", g_hlr->ncss_guard_timeout);
ss->state = OSMO_GSUP_SESSION_STATE_CONTINUE;
ss_tx_to_ms_ussd_7bit(ss, req->invoke_id, buf);
return 0;
}
@@ -330,6 +402,10 @@ static const struct hlr_iuse hlr_iuses[] = {
.name = "own-imsi",
.handle_ussd = handle_ussd_own_imsi,
},
{
.name = "test-idle",
.handle_ussd = handle_ussd_test_idle,
},
};
const struct hlr_iuse *iuse_find(const char *name)
@@ -363,37 +439,28 @@ static bool ss_op_is_ussd(uint8_t opcode)
}
/* is this GSUP connection an EUSE (true) or not (false)? */
static bool conn_is_euse(struct osmo_gsup_conn *conn)
static bool peer_name_is_euse(const struct osmo_cni_peer_id *peer_name)
{
int rc;
uint8_t *addr;
rc = osmo_gsup_conn_ccm_get(conn, &addr, IPAC_IDTAG_SERNR);
if (rc <= 5)
if (peer_name->type != OSMO_CNI_PEER_ID_IPA_NAME)
return false;
if (!strncmp((char *)addr, "EUSE-", 5))
return true;
else
if (peer_name->ipa_name.len <= 5)
return false;
return strncmp((char *)(peer_name->ipa_name.val), "EUSE-", 5) == 0;
}
static struct hlr_euse *euse_by_conn(struct osmo_gsup_conn *conn)
static struct hlr_euse *euse_by_name(const struct osmo_cni_peer_id *peer_name)
{
int rc;
char *addr;
struct hlr *hlr = conn->server->priv;
rc = osmo_gsup_conn_ccm_get(conn, (uint8_t **) &addr, IPAC_IDTAG_SERNR);
if (rc <= 5)
return NULL;
if (strncmp(addr, "EUSE-", 5))
if (!peer_name_is_euse(peer_name))
return NULL;
return euse_find(hlr, addr+5);
/* above peer_name_is_euse() ensures this: */
OSMO_ASSERT(peer_name->type == OSMO_CNI_PEER_ID_IPA_NAME);
return euse_find(g_hlr, (const char*)(peer_name->ipa_name.val)+5);
}
static int handle_ss(struct ss_session *ss, const struct osmo_gsup_message *gsup,
const struct ss_request *req)
static int handle_ss(struct ss_session *ss, bool is_euse_originated, const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
uint8_t comp_type = gsup->ss_info[0];
@@ -406,17 +473,16 @@ static int handle_ss(struct ss_session *ss, const struct osmo_gsup_message *gsup
* we don't handle "structured" SS requests at all.
*/
LOGPSS(ss, LOGL_NOTICE, "Structured SS requests are not supported, rejecting...\n");
ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_FACILITY_NOT_SUPPORTED);
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_FACILITY_NOT_SUPPORTED);
return -ENOTSUP;
}
/* Handle a USSD GSUP message for a given SS Session received from VLR or EUSE */
static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss,
const struct osmo_gsup_message *gsup, const struct ss_request *req)
static int handle_ussd(struct ss_session *ss, bool is_euse_originated, const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
uint8_t comp_type = gsup->ss_info[0];
struct msgb *msg_out;
bool is_euse_originated = conn_is_euse(conn);
LOGPSS(ss, LOGL_INFO, "USSD CompType=%s, OpCode=%s '%s'\n",
gsm0480_comp_type_name(comp_type), gsm0480_op_code_name(req->opcode),
@@ -424,39 +490,38 @@ static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss,
if ((ss->is_external && !ss->u.euse) || !ss->u.iuse) {
LOGPSS(ss, LOGL_NOTICE, "USSD for unknown code '%s'\n", req->ussd_text);
ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SS_NOT_AVAILABLE);
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SS_NOT_AVAILABLE);
return 0;
}
if (is_euse_originated) {
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP USSD FW");
OSMO_ASSERT(msg_out);
/* Received from EUSE, Forward to VLR */
osmo_gsup_encode(msg_out, gsup);
/* FIXME: resolve this based on the database vlr_addr */
osmo_gsup_addr_send(conn->server, (uint8_t *)"MSC-00-00-00-00-00-00", 22, msg_out);
/* Need a non-const osmo_gsup_message, because sending might modify some (routing related?) parts. */
struct osmo_gsup_message forward = *gsup;
ss_gsup_send_to_ms(ss, g_hlr->gs, &forward);
} else {
/* Received from VLR (MS) */
if (ss->is_external) {
/* Forward to EUSE */
char addr[128];
strcpy(addr, "EUSE-");
osmo_strlcpy(addr+5, ss->u.euse->name, sizeof(addr)-5);
conn = gsup_route_find(conn->server, (uint8_t *)addr, strlen(addr)+1);
struct osmo_ipa_name euse_name;
struct osmo_gsup_conn *conn;
osmo_ipa_name_set_str(&euse_name, "EUSE-%s", ss->u.euse->name);
conn = gsup_route_find_by_ipa_name(g_hlr->gs, &euse_name);
if (!conn) {
LOGPSS(ss, LOGL_ERROR, "Cannot find conn for EUSE %s\n", addr);
ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
LOGPSS(ss, LOGL_ERROR, "Cannot find conn for EUSE %s\n",
osmo_ipa_name_to_str(&euse_name));
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
} else {
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP USSD FW");
OSMO_ASSERT(msg_out);
msg_out = osmo_gsup_msgb_alloc("GSUP USSD FW");
osmo_gsup_encode(msg_out, gsup);
osmo_gsup_conn_send(conn, msg_out);
}
} else {
/* Handle internally */
ss->u.iuse->handle_ussd(conn, ss, gsup, req);
/* Release session immediately */
ss_session_free(ss);
ss->u.iuse->handle_ussd(ss, gsup, req);
/* Release session if the handler has changed its state to END */
if (ss->state == OSMO_GSUP_SESSION_STATE_END)
ss_session_free(ss);
}
}
@@ -466,24 +531,43 @@ static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss,
/* this function is called for any SS_REQ/SS_RESP messages from both the MSC/VLR side as well
* as from the EUSE side */
int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
void rx_proc_ss_req(struct osmo_gsup_req *gsup_req)
{
struct hlr *hlr = conn->server->priv;
struct hlr *hlr = g_hlr;
struct ss_session *ss;
struct ss_request req = {0};
const struct osmo_gsup_message *gsup = &gsup_req->gsup;
/* Remember whether this function should free the incoming gsup_req: if it is placed as ss->initial_req_from_*,
* do not free it here. If not, free it here. */
struct osmo_gsup_req *free_gsup_req = gsup_req;
bool is_euse_originated = peer_name_is_euse(&gsup_req->source_name);
LOGP(DSS, LOGL_DEBUG, "%s/0x%08x: Process SS (%s)\n", gsup->imsi, gsup->session_id,
osmo_gsup_session_state_name(gsup->session_state));
if (gsup_req->source_name.type != OSMO_CNI_PEER_ID_IPA_NAME) {
LOGP(DSS, LOGL_ERROR, "%s/0x%082x: Unable to process SS request: Unsupported GSUP peer id type%s\n",
gsup->imsi, gsup->session_id,
osmo_cni_peer_id_type_name(gsup_req->source_name.type));
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_PROTO_ERR_UNSPEC, "error processing SS request");
return;
}
/* decode and find out what kind of SS message it is */
if (gsup->ss_info && gsup->ss_info_len) {
if (gsm0480_parse_facility_ie(gsup->ss_info, gsup->ss_info_len, &req)) {
LOGP(DSS, LOGL_ERROR, "%s/0x%082x: Unable to parse SS request: %s\n",
gsup->imsi, gsup->session_id,
osmo_hexdump(gsup->ss_info, gsup->ss_info_len));
/* FIXME: Send a Reject component? */
goto out_err;
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "error parsing SS request");
return;
}
} else if (gsup->session_state != OSMO_GSUP_SESSION_STATE_END) {
LOGP(DSS, LOGL_ERROR, "%s/0x%082x: Missing SS payload for '%s'\n",
gsup->imsi, gsup->session_id,
osmo_gsup_session_state_name(gsup->session_state));
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "missing SS payload");
return;
}
switch (gsup->session_state) {
@@ -492,18 +576,30 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
if (ss_session_find(hlr, gsup->imsi, gsup->session_id)) {
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: BEGIN with non-unique session ID!\n",
gsup->imsi, gsup->session_id);
goto out_err;
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "BEGIN with non-unique session ID");
return;
}
ss = ss_session_alloc(hlr, gsup->imsi, gsup->session_id);
if (!ss) {
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: Unable to allocate SS session\n",
gsup->imsi, gsup->session_id);
goto out_err;
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_NET_FAIL, "Unable to allocate SS session");
return;
}
/* Get IPA name from VLR conn and save as ss->vlr_number */
if (!is_euse_originated) {
ss->initial_req_from_ms = gsup_req;
free_gsup_req = NULL;
OSMO_ASSERT(gsup_req->source_name.type == OSMO_CNI_PEER_ID_IPA_NAME); /* checked above */
ss->vlr_name = gsup_req->source_name.ipa_name;
} else {
ss->initial_req_from_euse = gsup_req;
free_gsup_req = NULL;
}
if (ss_op_is_ussd(req.opcode)) {
if (conn_is_euse(conn)) {
if (is_euse_originated) {
/* EUSE->VLR: MT USSD. EUSE is known ('conn'), VLR is to be resolved */
ss->u.euse = euse_by_conn(conn);
ss->u.euse = euse_by_name(&gsup_req->source_name);
} else {
/* VLR->EUSE: MO USSD. VLR is known ('conn'), EUSE is to be resolved */
struct hlr_ussd_route *rt;
@@ -524,10 +620,10 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
}
}
/* dispatch unstructured SS to routing */
handle_ussd(conn, ss, gsup, &req);
handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req);
} else {
/* dispatch non-call SS to internal code */
handle_ss(ss, gsup, &req);
handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req);
}
break;
case OSMO_GSUP_SESSION_STATE_CONTINUE:
@@ -535,7 +631,8 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
if (!ss) {
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: CONTINUE for unknown SS session\n",
gsup->imsi, gsup->session_id);
goto out_err;
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "CONTINUE for unknown SS session");
return;
}
/* Reschedule self-destruction timer */
@@ -544,10 +641,10 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
if (ss_op_is_ussd(req.opcode)) {
/* dispatch unstructured SS to routing */
handle_ussd(conn, ss, gsup, &req);
handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req);
} else {
/* dispatch non-call SS to internal code */
handle_ss(ss, gsup, &req);
handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req);
}
break;
case OSMO_GSUP_SESSION_STATE_END:
@@ -555,32 +652,34 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
if (!ss) {
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: END for unknown SS session\n",
gsup->imsi, gsup->session_id);
goto out_err;
return;
}
if (ss_op_is_ussd(req.opcode)) {
/* dispatch unstructured SS to routing */
handle_ussd(conn, ss, gsup, &req);
} else {
/* dispatch non-call SS to internal code */
handle_ss(ss, gsup, &req);
/* SS payload is optional for END */
if (gsup->ss_info && gsup->ss_info_len) {
if (ss_op_is_ussd(req.opcode)) {
/* dispatch unstructured SS to routing */
handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req);
} else {
/* dispatch non-call SS to internal code */
handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req);
}
}
ss_session_free(ss);
break;
default:
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: Unknown SS State %d\n", gsup->imsi,
gsup->session_id, gsup->session_state);
goto out_err;
break;
}
return 0;
out_err:
return 0;
if (free_gsup_req)
osmo_gsup_req_free(free_gsup_req);
}
int rx_proc_ss_error(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
void rx_proc_ss_error(struct osmo_gsup_req *req)
{
LOGP(DSS, LOGL_NOTICE, "%s/0x%08x: Process SS ERROR (%s)\n", gsup->imsi, gsup->session_id,
osmo_gsup_session_state_name(gsup->session_state));
return 0;
LOGP(DSS, LOGL_NOTICE, "%s/0x%08x: Process SS ERROR (%s)\n", req->gsup.imsi, req->gsup.session_id,
osmo_gsup_session_state_name(req->gsup.session_state));
}

View File

@@ -27,16 +27,18 @@
#include <osmocom/core/talloc.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/stats.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/misc.h>
#include <osmocom/abis/ipa.h>
#include "hlr.h"
#include "hlr_vty.h"
#include "hlr_vty_subscr.h"
#include "hlr_ussd.h"
#include "gsup_server.h"
#include <osmocom/hlr/db.h>
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/hlr_vty.h>
#include <osmocom/hlr/hlr_vty_subscr.h>
#include <osmocom/hlr/hlr_ussd.h>
#include <osmocom/hlr/gsup_server.h>
struct cmd_node hlr_node = {
HLR_NODE,
@@ -71,6 +73,27 @@ DEFUN(cfg_gsup,
static int config_write_hlr(struct vty *vty)
{
vty_out(vty, "hlr%s", VTY_NEWLINE);
if (g_hlr->store_imei)
vty_out(vty, " store-imei%s", VTY_NEWLINE);
if (g_hlr->db_file_path && strcmp(g_hlr->db_file_path, HLR_DEFAULT_DB_FILE_PATH))
vty_out(vty, " database %s%s", g_hlr->db_file_path, VTY_NEWLINE);
if (g_hlr->subscr_create_on_demand) {
const char *flags_str = "none";
uint8_t flags = g_hlr->subscr_create_on_demand_flags;
unsigned int rand_msisdn_len = g_hlr->subscr_create_on_demand_rand_msisdn_len;
if ((flags & DB_SUBSCR_FLAG_NAM_CS) && (flags & DB_SUBSCR_FLAG_NAM_PS))
flags_str = "cs+ps";
else if (flags & DB_SUBSCR_FLAG_NAM_CS)
flags_str = "cs";
else if (flags & DB_SUBSCR_FLAG_NAM_PS)
flags_str = "ps";
if (rand_msisdn_len)
vty_out(vty, " subscriber-create-on-demand %i %s%s", rand_msisdn_len, flags_str, VTY_NEWLINE);
else
vty_out(vty, " subscriber-create-on-demand no-msisdn %s%s", flags_str, VTY_NEWLINE);
}
return CMD_SUCCESS;
}
@@ -79,6 +102,8 @@ static int config_write_hlr_gsup(struct vty *vty)
vty_out(vty, " gsup%s", VTY_NEWLINE);
if (g_hlr->gsup_bind_addr)
vty_out(vty, " bind ip %s%s", g_hlr->gsup_bind_addr, VTY_NEWLINE);
if (g_hlr->gsup_unit_name.serno)
vty_out(vty, " ipa-name %s%s", g_hlr->gsup_unit_name.serno, VTY_NEWLINE);
return CMD_SUCCESS;
}
@@ -123,20 +148,39 @@ DEFUN(cfg_hlr_gsup_bind_ip,
return CMD_SUCCESS;
}
DEFUN(cfg_hlr_gsup_ipa_name,
cfg_hlr_gsup_ipa_name_cmd,
"ipa-name NAME",
"Set the IPA name of this HLR, for proxying to remote HLRs\n"
"A globally unique name for this HLR. For example: PLMN + redundancy server number: HLR-901-70-0. "
"This name is used for GSUP routing and must be set if multiple HLRs interconnect (e.g. mslookup "
"for Distributed GSM).\n")
{
if (vty->type != VTY_FILE) {
vty_out(vty, "gsup/ipa-name: The GSUP IPA name cannot be changed at run-time; "
"It can only be set in the configuraton file.%s", VTY_NEWLINE);
return CMD_WARNING;
}
g_hlr->gsup_unit_name.serno = talloc_strdup(g_hlr, argv[0]);
return CMD_SUCCESS;
}
/***********************************************************************
* USSD Entity
***********************************************************************/
#include "hlr_ussd.h"
#include <osmocom/hlr/hlr_ussd.h>
#define USSD_STR "USSD Configuration\n"
#define UROUTE_STR "Routing Configuration\n"
#define PREFIX_STR "Prefix-Matching Route\n" "USSD Prefix\n"
#define INT_CHOICE "(own-msisdn|own-imsi)"
#define INT_CHOICE "(own-msisdn|own-imsi|test-idle)"
#define INT_STR "Internal USSD Handler\n" \
"Respond with subscribers' own MSISDN\n" \
"Respond with subscribers' own IMSI\n"
"Respond with subscribers' own IMSI\n" \
"Keep the session idle (useful for testing)\n"
#define EXT_STR "External USSD Handler\n" \
"Name of External USSD Handler (IPA CCM ID)\n"
@@ -221,6 +265,15 @@ DEFUN(cfg_ussd_no_defaultroute, cfg_ussd_no_defaultroute_cmd,
return CMD_SUCCESS;
}
DEFUN(cfg_database, cfg_database_cmd,
"database PATH",
"Set the path to the HLR database file\n"
"Relative or absolute file system path to the database file (default is '" HLR_DEFAULT_DB_FILE_PATH "')\n")
{
osmo_talloc_replace_string(g_hlr, &g_hlr->db_file_path, argv[0]);
return CMD_SUCCESS;
}
struct cmd_node euse_node = {
EUSE_NODE,
"%s(config-hlr-euse)# ",
@@ -255,7 +308,7 @@ DEFUN(cfg_no_euse, cfg_no_euse_cmd,
{
struct hlr_euse *euse = euse_find(g_hlr, argv[0]);
if (!euse) {
vty_out(vty, "%% Cannot remove non-existant EUSE %s%s", argv[0], VTY_NEWLINE);
vty_out(vty, "%% Cannot remove non-existent EUSE %s%s", argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
if (g_hlr->euse_default == euse) {
@@ -305,6 +358,59 @@ DEFUN(cfg_ncss_guard_timeout, cfg_ncss_guard_timeout_cmd,
return CMD_SUCCESS;
}
DEFUN(cfg_store_imei, cfg_store_imei_cmd,
"store-imei",
"Save the IMEI in the database when receiving Check IMEI requests. Note that an MSC does not necessarily send"
" Check IMEI requests (for OsmoMSC, you may want to set 'check-imei-rqd 1').")
{
g_hlr->store_imei = true;
return CMD_SUCCESS;
}
DEFUN(cfg_no_store_imei, cfg_no_store_imei_cmd,
"no store-imei",
"Do not save the IMEI in the database, when receiving Check IMEI requests.")
{
g_hlr->store_imei = false;
return CMD_SUCCESS;
}
DEFUN(cfg_subscr_create_on_demand, cfg_subscr_create_on_demand_cmd,
"subscriber-create-on-demand (no-msisdn|<3-15>) (none|cs|ps|cs+ps)",
"Make a new record when a subscriber is first seen.\n"
"Do not automatically assign MSISDN.\n"
"Length of an automatically assigned MSISDN.\n"
"Do not allow any NAM (Network Access Mode) by default.\n"
"Allow access to circuit switched NAM by default.\n"
"Allow access to packet switched NAM by default.\n"
"Allow access to circuit and packet switched NAM by default.\n")
{
unsigned int rand_msisdn_len = 0;
uint8_t flags = 0x00;
if (strcmp(argv[0], "no-msisdn") != 0)
rand_msisdn_len = atoi(argv[0]);
if (strstr(argv[1], "cs"))
flags |= DB_SUBSCR_FLAG_NAM_CS;
if (strstr(argv[1], "ps"))
flags |= DB_SUBSCR_FLAG_NAM_PS;
g_hlr->subscr_create_on_demand = true;
g_hlr->subscr_create_on_demand_rand_msisdn_len = rand_msisdn_len;
g_hlr->subscr_create_on_demand_flags = flags;
return CMD_SUCCESS;
}
DEFUN(cfg_no_subscr_create_on_demand, cfg_no_subscr_create_on_demand_cmd,
"no subscriber-create-on-demand",
"Do not make a new record when a subscriber is first seen.\n")
{
g_hlr->subscr_create_on_demand = false;
return CMD_SUCCESS;
}
/***********************************************************************
* Common Code
***********************************************************************/
@@ -344,10 +450,11 @@ int hlr_vty_is_config_node(struct vty *vty, int node)
}
}
void hlr_vty_init(const struct log_info *cat)
void hlr_vty_init(void)
{
logging_vty_add_cmds(cat);
logging_vty_add_cmds();
osmo_talloc_vty_add_cmds();
osmo_stats_vty_add_cmds();
install_element_ve(&show_gsup_conn_cmd);
@@ -358,6 +465,9 @@ void hlr_vty_init(const struct log_info *cat)
install_node(&gsup_node, config_write_hlr_gsup);
install_element(GSUP_NODE, &cfg_hlr_gsup_bind_ip_cmd);
install_element(GSUP_NODE, &cfg_hlr_gsup_ipa_name_cmd);
install_element(HLR_NODE, &cfg_database_cmd);
install_element(HLR_NODE, &cfg_euse_cmd);
install_element(HLR_NODE, &cfg_no_euse_cmd);
@@ -368,6 +478,10 @@ void hlr_vty_init(const struct log_info *cat)
install_element(HLR_NODE, &cfg_ussd_defaultroute_cmd);
install_element(HLR_NODE, &cfg_ussd_no_defaultroute_cmd);
install_element(HLR_NODE, &cfg_ncss_guard_timeout_cmd);
install_element(HLR_NODE, &cfg_store_imei_cmd);
install_element(HLR_NODE, &cfg_no_store_imei_cmd);
install_element(HLR_NODE, &cfg_subscr_create_on_demand_cmd);
install_element(HLR_NODE, &cfg_no_subscr_create_on_demand_cmd);
hlr_vty_subscriber_init();
}

View File

@@ -28,23 +28,49 @@
#include <osmocom/vty/command.h>
#include <osmocom/core/utils.h>
#include "hlr.h"
#include "db.h"
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/db.h>
#include <osmocom/hlr/timestamp.h>
struct vty;
#define hexdump_buf(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf))
static char *
get_datestr(const time_t *t, char *datebuf)
static char *get_datestr(const time_t *t, char *buf, size_t bufsize)
{
char *p, *s = ctime_r(t, datebuf);
struct tm tm;
gmtime_r(t, &tm);
strftime(buf, bufsize, "%FT%T+00:00", &tm);
return buf;
}
/* Strip trailing newline. */
p = strchr(s, '\n');
if (p)
*p = '\0';
return s;
static void dump_last_lu_seen(struct vty *vty, const char *domain_label, time_t last_lu_seen, bool only_age)
{
uint32_t age;
char datebuf[32];
if (!last_lu_seen)
return;
if (!only_age)
vty_out(vty, " last LU seen on %s: %s", domain_label, get_datestr(&last_lu_seen, datebuf, sizeof(datebuf)));
if (!timestamp_age(&last_lu_seen, &age))
vty_out(vty, " (invalid timestamp)%s", VTY_NEWLINE);
else {
vty_out(vty, " (");
#define UNIT_AGO(UNITNAME, UNITVAL) \
if (age >= (UNITVAL)) { \
vty_out(vty, "%u%s", age / (UNITVAL), UNITNAME); \
age = age % (UNITVAL); \
}
UNIT_AGO("d", 60*60*24);
UNIT_AGO("h", 60*60);
UNIT_AGO("m", 60);
UNIT_AGO("s", 1);
if (!only_age)
vty_out(vty, " ago)%s", VTY_NEWLINE);
else
vty_out(vty, " ago)");
#undef UNIT_AGO
}
}
static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
@@ -52,12 +78,20 @@ static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
int rc;
struct osmo_sub_auth_data aud2g;
struct osmo_sub_auth_data aud3g;
char datebuf[26]; /* for ctime_r(3) */
vty_out(vty, " ID: %"PRIu64"%s", subscr->id, VTY_NEWLINE);
vty_out(vty, " IMSI: %s%s", *subscr->imsi ? subscr->imsi : "none", VTY_NEWLINE);
vty_out(vty, " MSISDN: %s%s", *subscr->msisdn ? subscr->msisdn : "none", VTY_NEWLINE);
if (*subscr->imei) {
char checksum = osmo_luhn(subscr->imei, 14);
if (checksum == -EINVAL)
vty_out(vty, " IMEI: %s (INVALID LENGTH!)%s", subscr->imei, VTY_NEWLINE);
else
vty_out(vty, " IMEI: %s%c%s", subscr->imei, checksum, VTY_NEWLINE);
}
if (*subscr->vlr_number)
vty_out(vty, " VLR number: %s%s", subscr->vlr_number, VTY_NEWLINE);
if (*subscr->sgsn_number)
@@ -78,8 +112,8 @@ static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
vty_out(vty, " PS disabled%s", VTY_NEWLINE);
if (subscr->ms_purged_ps)
vty_out(vty, " PS purged%s", VTY_NEWLINE);
if (subscr->last_lu_seen)
vty_out(vty, " last LU seen: %s UTC%s", get_datestr(&subscr->last_lu_seen, datebuf), VTY_NEWLINE);
dump_last_lu_seen(vty, "CS", subscr->last_lu_seen, false);
dump_last_lu_seen(vty, "PS", subscr->last_lu_seen_ps, false);
if (!*subscr->imsi)
return;
@@ -129,8 +163,31 @@ static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
}
}
static void subscr_dump_summary_vty(struct hlr_subscriber *subscr, void *data)
{
struct vty *vty = data;
vty_out(vty, "%-5"PRIu64" %-12s %-16s", subscr->id,
*subscr->msisdn ? subscr->msisdn : "none",
*subscr->imsi ? subscr->imsi : "none");
if (*subscr->imei) {
char checksum = osmo_luhn(subscr->imei, 14);
if (checksum == -EINVAL)
vty_out(vty, " %-14s (INVALID LENGTH!)", subscr->imei);
else
vty_out(vty, " %-14s%c", subscr->imei, checksum);
} else {
vty_out(vty," ------------- ");
}
vty_out(vty, " %-2s%-2s ", subscr->nam_cs ? "CS" : "", subscr->nam_ps ? "PS" : "");
if (subscr->last_lu_seen)
dump_last_lu_seen(vty, "CS", subscr->last_lu_seen, true);
vty_out_newline(vty);
}
static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id, struct hlr_subscriber *subscr)
{
char imei_buf[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1];
int rc = -1;
if (strcmp(type, "imsi") == 0)
rc = db_subscr_get_by_imsi(g_hlr->dbc, id, subscr);
@@ -138,21 +195,75 @@ static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id,
rc = db_subscr_get_by_msisdn(g_hlr->dbc, id, subscr);
else if (strcmp(type, "id") == 0)
rc = db_subscr_get_by_id(g_hlr->dbc, atoll(id), subscr);
else if (strcmp(type, "imei") == 0) {
/* Verify IMEI with checksum digit */
if (osmo_imei_str_valid(id, true)) {
/* Cut the checksum off */
osmo_strlcpy(imei_buf, id, sizeof(imei_buf));
id = imei_buf;
vty_out(vty, "%% Checksum validated and stripped for search: imei = '%s'%s", id,
VTY_NEWLINE);
}
rc = db_subscr_get_by_imei(g_hlr->dbc, id, subscr);
}
if (rc)
vty_out(vty, "%% No subscriber for %s = '%s'%s",
type, id, VTY_NEWLINE);
return rc;
}
static void dump_summary_table_vty(struct vty *vty, bool header, bool show_ls)
{
const char *texts = "ID MSISDN IMSI IMEI NAM";
const char *lines = "----- ------------ ---------------- ---------------- -----";
const char *ls_text = " LAST SEEN";
const char *ls_line = " ------------";
if (header) {
if (!show_ls)
vty_out(vty, "%s%s%s%s", texts, VTY_NEWLINE, lines, VTY_NEWLINE);
else
vty_out(vty, "%s%s%s%s%s%s", texts, ls_text, VTY_NEWLINE, lines, ls_line, VTY_NEWLINE);
} else {
if (!show_ls)
vty_out(vty, "%s%s%s%s", lines, VTY_NEWLINE, texts, VTY_NEWLINE);
else
vty_out(vty, "%s%s%s%s%s%s", lines, ls_line, VTY_NEWLINE, texts, ls_text, VTY_NEWLINE);
}
}
static int get_subscrs(struct vty *vty, const char *filter_type, const char *filter)
{
int rc = -1;
int count = 0;
const char *err;
bool show_ls = (filter_type && strcmp(filter_type, "last_lu_seen") == 0);
dump_summary_table_vty(vty, true, show_ls);
rc = db_subscrs_get(g_hlr->dbc, filter_type, filter, subscr_dump_summary_vty, vty, &count, &err);
if (count > 40) {
dump_summary_table_vty(vty, false, show_ls);
}
if (count > 0)
vty_out(vty, " Subscribers Shown: %d%s", count, VTY_NEWLINE);
if (rc)
vty_out(vty, "%% %s%s", err, VTY_NEWLINE);
return rc;
}
#define SUBSCR_CMD "subscriber "
#define SUBSCR_CMD_HELP "Subscriber management commands\n"
#define SUBSCR_SHOW_HELP "Show subscriber information\n"
#define SUBSCRS_SHOW_HELP "Show all subscribers (with filter possibility)\n"
#define SUBSCR_ID "(imsi|msisdn|id|imei) IDENT"
#define SUBSCR_FILTER "(imei|imsi|msisdn) FILTER"
#define SUBSCR_ID "(imsi|msisdn|id) IDENT"
#define SUBSCR_ID_HELP \
"Identify subscriber by IMSI\n" \
"Identify subscriber by MSISDN (phone number)\n" \
"Identify subscriber by database ID\n" \
"IMSI/MSISDN/ID of the subscriber\n"
"Identify subscriber by IMEI\n" \
"IMSI/MSISDN/ID/IMEI of the subscriber\n"
#define SUBSCR SUBSCR_CMD SUBSCR_ID " "
#define SUBSCR_HELP SUBSCR_CMD_HELP SUBSCR_ID_HELP
@@ -164,7 +275,7 @@ static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id,
DEFUN(subscriber_show,
subscriber_show_cmd,
SUBSCR "show",
SUBSCR_HELP "Show subscriber information\n")
SUBSCR_HELP SUBSCR_SHOW_HELP)
{
struct hlr_subscriber subscr;
const char *id_type = argv[0];
@@ -179,7 +290,50 @@ DEFUN(subscriber_show,
ALIAS(subscriber_show, show_subscriber_cmd,
"show " SUBSCR_CMD SUBSCR_ID,
SHOW_STR SUBSCR_CMD_HELP SUBSCR_ID_HELP);
SHOW_STR SUBSCR_SHOW_HELP SUBSCR_ID_HELP);
DEFUN(show_subscriber_all,
show_subscriber_all_cmd,
"show subscribers all",
SHOW_STR SUBSCRS_SHOW_HELP "Show summary of all subscribers\n")
{
if (get_subscrs(vty, NULL, NULL))
return CMD_WARNING;
return CMD_SUCCESS;
}
DEFUN(show_subscriber_filtered,
show_subscriber_filtered_cmd,
"show subscribers " SUBSCR_FILTER,
SHOW_STR SUBSCRS_SHOW_HELP
"Filter Subscribers by IMEI\n" "Filter Subscribers by IMSI\n" "Filter Subscribers by MSISDN\n"
"String to match in imei, imsi or msisdn\n")
{
const char *filter_type = argv[0];
const char *filter = argv[1];
if (get_subscrs(vty, filter_type, filter))
return CMD_WARNING;
return CMD_SUCCESS;
}
ALIAS(show_subscriber_filtered, show_subscriber_filtered_cmd2,
"show subscribers (cs|ps) (on|off)",
SHOW_STR SUBSCR_SHOW_HELP
"Filter Subscribers by CS Network Access Mode\n" "Filter Subscribers by PS Network Access Mode\n"
"Authorised\n" "Not Authorised\n");
DEFUN(show_subscriber_order_last_seen, show_subscriber_order_last_seen_cmd,
"show subscribers last-seen",
SHOW_STR SUBSCR_SHOW_HELP "Show Subscribers Ordered by Last Seen Time\n")
{
if (get_subscrs(vty, "last_lu_seen", NULL))
return CMD_WARNING;
return CMD_SUCCESS;
}
DEFUN(subscriber_create,
subscriber_create_cmd,
@@ -198,7 +352,7 @@ DEFUN(subscriber_create,
return CMD_WARNING;
}
rc = db_subscr_create(g_hlr->dbc, imsi);
rc = db_subscr_create(g_hlr->dbc, imsi, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS);
if (rc) {
if (rc == -EEXIST)
@@ -508,8 +662,136 @@ DEFUN(subscriber_aud3g,
return CMD_SUCCESS;
}
DEFUN(subscriber_aud3g_xor,
subscriber_aud3g_xor_cmd,
SUBSCR_UPDATE "aud3g xor k K"
" [ind-bitlen] [<0-28>]",
SUBSCR_UPDATE_HELP
"Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
"Use XOR algorithm\n"
"Set Encryption Key K\n" "K as 32 hexadecimal characters\n"
"Set IND bit length\n" "IND bit length value (default: 5)\n")
{
struct hlr_subscriber subscr;
int minlen = 0;
int maxlen = 0;
int rc;
const char *id_type = argv[0];
const char *id = argv[1];
const char *k = argv[2];
int ind_bitlen = argc > 4? atoi(argv[4]) : 5;
struct sub_auth_data_str aud3g = {
.type = OSMO_AUTH_TYPE_UMTS,
.u.umts = {
.k = k,
.opc_is_op = 0,
.opc = "00000000000000000000000000000000",
.ind_bitlen = ind_bitlen,
},
};
if (!auth_algo_parse("xor", &aud3g.algo, &minlen, &maxlen)) {
vty_out(vty, "%% Unknown auth algorithm: '%s'%s", "xor", VTY_NEWLINE);
return CMD_WARNING;
}
if (!is_hexkey_valid(vty, "K", aud3g.u.umts.k, minlen, maxlen))
return CMD_WARNING;
if (get_subscr_by_argv(vty, id_type, id, &subscr))
return CMD_WARNING;
rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud3g);
if (rc) {
vty_out(vty, "%% Error: cannot set 3G auth data for IMSI='%s'%s",
subscr.imsi, VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
DEFUN(subscriber_imei,
subscriber_imei_cmd,
SUBSCR_UPDATE "imei (none|IMEI)",
SUBSCR_UPDATE_HELP
"Set IMEI of the subscriber (normally populated from MSC, no need to set this manually)\n"
"Forget IMEI\n"
"Set IMEI (use for debug only!)\n")
{
struct hlr_subscriber subscr;
const char *id_type = argv[0];
const char *id = argv[1];
const char *imei = argv[2];
char imei_buf[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1];
if (strcmp(imei, "none") == 0)
imei = NULL;
else {
/* Verify IMEI with checksum digit */
if (osmo_imei_str_valid(imei, true)) {
/* Cut the checksum off */
osmo_strlcpy(imei_buf, imei, sizeof(imei_buf));
imei = imei_buf;
} else if (!osmo_imei_str_valid(imei, false)) {
vty_out(vty, "%% IMEI invalid: '%s'%s", imei, VTY_NEWLINE);
return CMD_WARNING;
}
}
if (get_subscr_by_argv(vty, id_type, id, &subscr))
return CMD_WARNING;
if (db_subscr_update_imei_by_imsi(g_hlr->dbc, subscr.imsi, imei)) {
vty_out(vty, "%% Error: cannot update IMEI for subscriber IMSI='%s'%s",
subscr.imsi, VTY_NEWLINE);
return CMD_WARNING;
}
if (imei)
vty_out(vty, "%% Updated subscriber IMSI='%s' to IMEI='%s'%s",
subscr.imsi, imei, VTY_NEWLINE);
else
vty_out(vty, "%% Updated subscriber IMSI='%s': removed IMEI%s",
subscr.imsi, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(subscriber_nam,
subscriber_nam_cmd,
SUBSCR_UPDATE "network-access-mode (none|cs|ps|cs+ps)",
SUBSCR_UPDATE_HELP
"Set Network Access Mode (NAM) of the subscriber\n"
"Do not allow access to circuit switched or packet switched services\n"
"Allow access to circuit switched services only\n"
"Allow access to packet switched services only\n"
"Allow access to both circuit and packet switched services\n")
{
struct hlr_subscriber subscr;
const char *id_type = argv[0];
const char *id = argv[1];
bool nam_cs = strstr(argv[2], "cs");
bool nam_ps = strstr(argv[2], "ps");
if (get_subscr_by_argv(vty, id_type, id, &subscr))
return CMD_WARNING;
if (nam_cs != subscr.nam_cs)
hlr_subscr_nam(g_hlr, &subscr, nam_cs, 0);
if (nam_ps != subscr.nam_ps)
hlr_subscr_nam(g_hlr, &subscr, nam_ps, 1);
return CMD_SUCCESS;
}
void hlr_vty_subscriber_init(void)
{
install_element_ve(&show_subscriber_all_cmd);
install_element_ve(&show_subscriber_filtered_cmd);
install_element_ve(&show_subscriber_filtered_cmd2);
install_element_ve(&show_subscriber_order_last_seen_cmd);
install_element_ve(&subscriber_show_cmd);
install_element_ve(&show_subscriber_cmd);
install_element(ENABLE_NODE, &subscriber_create_cmd);
@@ -519,4 +801,7 @@ void hlr_vty_subscriber_init(void)
install_element(ENABLE_NODE, &subscriber_aud2g_cmd);
install_element(ENABLE_NODE, &subscriber_no_aud3g_cmd);
install_element(ENABLE_NODE, &subscriber_aud3g_cmd);
install_element(ENABLE_NODE, &subscriber_aud3g_xor_cmd);
install_element(ENABLE_NODE, &subscriber_imei_cmd);
install_element(ENABLE_NODE, &subscriber_nam_cmd);
}

View File

@@ -1,5 +1,5 @@
#include <osmocom/core/utils.h>
#include "logging.h"
#include <osmocom/hlr/logging.h>
const struct log_info_cat hlr_log_info_cat[] = {
[DMAIN] = {
@@ -25,7 +25,24 @@ 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,
},
[DLU] = {
.name = "DLU",
.description = "Location Updating",
.color = "\033[1;33m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DDGSM] = {
.name = "DDGSM",
.description = "Distributed GSM: MS lookup and proxy",
.color = "\033[1;35m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
};
const struct log_info hlr_log_info = {

320
src/lu_fsm.c Normal file
View File

@@ -0,0 +1,320 @@
/* Roughly following "Process Update_Location_HLR" of TS 09.02 */
/* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/core/utils.h>
#include <osmocom/core/tdef.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/fsm.h>
#include <osmocom/gsm/apn.h>
#include <osmocom/gsm/gsm48_ie.h>
#include <osmocom/gsupclient/cni_peer_id.h>
#include <osmocom/gsupclient/gsup_req.h>
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/db.h>
#define LOG_LU(lu, level, fmt, args...) \
LOGPFSML((lu)? (lu)->fi : NULL, level, fmt, ##args)
#define LOG_LU_REQ(lu, req, level, fmt, args...) \
LOGPFSML((lu)? (lu)->fi : NULL, level, "%s:" fmt, \
osmo_gsup_message_type_name((req)->gsup.message_type), ##args)
struct lu {
struct llist_head entry;
struct osmo_fsm_inst *fi;
struct osmo_gsup_req *update_location_req;
/* Subscriber state at time of initial Update Location Request */
struct hlr_subscriber subscr;
bool is_ps;
/* VLR requesting the LU. */
struct osmo_cni_peer_id vlr_name;
/* If the LU request was received via a proxy and not immediately from a local VLR, this indicates the closest
* peer that forwarded the GSUP message. */
struct osmo_cni_peer_id via_proxy;
};
LLIST_HEAD(g_all_lu);
enum lu_fsm_event {
LU_EV_RX_GSUP,
};
enum lu_fsm_state {
LU_ST_UNVALIDATED,
LU_ST_WAIT_INSERT_DATA_RESULT,
LU_ST_WAIT_LOCATION_CANCEL_RESULT,
};
static const struct value_string lu_fsm_event_names[] = {
OSMO_VALUE_STRING(LU_EV_RX_GSUP),
{}
};
static struct osmo_tdef_state_timeout lu_fsm_timeouts[32] = {
[LU_ST_WAIT_INSERT_DATA_RESULT] = { .T = -4222 },
[LU_ST_WAIT_LOCATION_CANCEL_RESULT] = { .T = -4222 },
};
#define lu_state_chg(lu, state) \
osmo_tdef_fsm_inst_state_chg((lu)->fi, state, lu_fsm_timeouts, g_hlr_tdefs, 5)
static void lu_success(struct lu *lu)
{
if (!lu->update_location_req)
LOG_LU(lu, LOGL_ERROR, "No request for this LU\n");
else
osmo_gsup_req_respond_msgt(lu->update_location_req, OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT, true);
lu->update_location_req = NULL;
osmo_fsm_inst_term(lu->fi, OSMO_FSM_TERM_REGULAR, NULL);
}
#define lu_failure(LU, CAUSE, log_msg, args...) do { \
if (!(LU)->update_location_req) \
LOG_LU(LU, LOGL_ERROR, "No request for this LU\n"); \
else \
osmo_gsup_req_respond_err((LU)->update_location_req, CAUSE, log_msg, ##args); \
(LU)->update_location_req = NULL; \
osmo_fsm_inst_term((LU)->fi, OSMO_FSM_TERM_REGULAR, NULL); \
} while(0)
static struct osmo_fsm lu_fsm;
static void lu_start(struct osmo_gsup_req *update_location_req)
{
struct osmo_fsm_inst *fi;
struct lu *lu;
OSMO_ASSERT(update_location_req);
OSMO_ASSERT(update_location_req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST);
fi = osmo_fsm_inst_alloc(&lu_fsm, g_hlr, NULL, LOGL_DEBUG, update_location_req->gsup.imsi);
OSMO_ASSERT(fi);
lu = talloc(fi, struct lu);
OSMO_ASSERT(lu);
fi->priv = lu;
*lu = (struct lu){
.fi = fi,
.update_location_req = update_location_req,
.vlr_name = update_location_req->source_name,
.via_proxy = update_location_req->via_proxy,
/* According to GSUP specs, OSMO_GSUP_CN_DOMAIN_PS is the default. */
.is_ps = (update_location_req->gsup.cn_domain != OSMO_GSUP_CN_DOMAIN_CS),
};
llist_add(&lu->entry, &g_all_lu);
osmo_fsm_inst_update_id_f_sanitize(fi, '_', "%s:IMSI-%s", lu->is_ps ? "PS" : "CS", update_location_req->gsup.imsi);
if (osmo_cni_peer_id_is_empty(&lu->vlr_name)) {
lu_failure(lu, GMM_CAUSE_NET_FAIL, "LU without a VLR");
return;
}
if (db_subscr_get_by_imsi(g_hlr->dbc, update_location_req->gsup.imsi, &lu->subscr) < 0) {
lu_failure(lu, GMM_CAUSE_IMSI_UNKNOWN, "Subscriber does not exist");
return;
}
/* Check if subscriber is generally permitted on CS or PS
* service (as requested) */
if (!lu->is_ps && !lu->subscr.nam_cs) {
lu_failure(lu, GMM_CAUSE_PLMN_NOTALLOWED, "nam_cs == false");
return;
}
if (lu->is_ps && !lu->subscr.nam_ps) {
lu_failure(lu, GMM_CAUSE_GPRS_NOTALLOWED, "nam_ps == false");
return;
}
/* TODO: Set subscriber tracing = deactive in VLR/SGSN */
#if 0
/* Cancel in old VLR/SGSN, if new VLR/SGSN differs from old (FIXME: OS#4491) */
if (!lu->is_ps && strcmp(subscr->vlr_number, vlr_number)) {
lu_op_tx_cancel_old(lu);
} else if (lu->is_ps && strcmp(subscr->sgsn_number, sgsn_number)) {
lu_op_tx_cancel_old(lu);
}
#endif
/* Store the VLR / SGSN number with the subscriber, so we know where it was last seen. */
if (!osmo_cni_peer_id_is_empty(&lu->via_proxy)) {
LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s, via proxy %s\n",
lu->is_ps ? "SGSN number" : "VLR number",
osmo_cni_peer_id_to_str(&lu->vlr_name),
osmo_cni_peer_id_to_str(&lu->via_proxy));
} else {
LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s\n",
lu->is_ps ? "SGSN number" : "VLR number",
osmo_cni_peer_id_to_str(&lu->vlr_name));
}
if (osmo_cni_peer_id_is_empty(&lu->vlr_name)
|| (lu->vlr_name.type != OSMO_CNI_PEER_ID_IPA_NAME)) {
lu_failure(lu, GMM_CAUSE_PROTO_ERR_UNSPEC, "Unsupported GSUP peer id type for vlr_name: %s",
osmo_cni_peer_id_type_name(lu->vlr_name.type));
return;
}
if (!osmo_cni_peer_id_is_empty(&lu->via_proxy) && (lu->via_proxy.type != OSMO_CNI_PEER_ID_IPA_NAME)) {
lu_failure(lu, GMM_CAUSE_PROTO_ERR_UNSPEC, "Unsupported GSUP peer id type for via_proxy: %s",
osmo_cni_peer_id_type_name(lu->via_proxy.type));
return;
}
if (db_subscr_lu(g_hlr->dbc, lu->subscr.id, &lu->vlr_name.ipa_name, lu->is_ps,
osmo_cni_peer_id_is_empty(&lu->via_proxy)? NULL : &lu->via_proxy.ipa_name)) {
lu_failure(lu, GMM_CAUSE_NET_FAIL, "Cannot update %s in the database",
lu->is_ps ? "SGSN number" : "VLR number");
return;
}
/* TODO: Subscriber allowed to roam in PLMN? */
/* TODO: Update RoutingInfo */
/* TODO: Reset Flag MS Purged (cs/ps) */
/* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */
lu_state_chg(lu, LU_ST_WAIT_INSERT_DATA_RESULT);
}
void lu_rx_gsup(struct osmo_gsup_req *req)
{
struct lu *lu;
if (req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST)
return lu_start(req);
llist_for_each_entry(lu, &g_all_lu, entry) {
if (strcmp(lu->subscr.imsi, req->gsup.imsi))
continue;
if (osmo_fsm_inst_dispatch(lu->fi, LU_EV_RX_GSUP, req)) {
LOG_LU_REQ(lu, req, LOGL_ERROR, "Cannot receive GSUP messages in this state\n");
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE,
"LU does not accept GSUP rx");
}
return;
}
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "No Location Updating in progress for this IMSI");
}
static int lu_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct lu *lu = fi->priv;
lu_failure(lu, GSM_CAUSE_NET_FAIL, "Timeout");
return 0;
}
static void lu_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct lu *lu = fi->priv;
if (lu->update_location_req)
osmo_gsup_req_respond_err(lu->update_location_req, GSM_CAUSE_NET_FAIL, "LU aborted");
lu->update_location_req = NULL;
llist_del(&lu->entry);
}
static void lu_fsm_wait_insert_data_result_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
/* Transmit Insert Data Request to the VLR */
struct lu *lu = fi->priv;
struct hlr_subscriber *subscr = &lu->subscr;
struct osmo_gsup_message gsup;
uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
uint8_t apn[APN_MAXLEN];
if (osmo_gsup_create_insert_subscriber_data_msg(&gsup, subscr->imsi,
subscr->msisdn, msisdn_enc, sizeof(msisdn_enc),
apn, sizeof(apn),
lu->is_ps? OSMO_GSUP_CN_DOMAIN_PS : OSMO_GSUP_CN_DOMAIN_CS)) {
lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot encode Insert Subscriber Data message");
return;
}
if (osmo_gsup_req_respond(lu->update_location_req, &gsup, false, false))
lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot send %s", osmo_gsup_message_type_name(gsup.message_type));
}
void lu_fsm_wait_insert_data_result(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct lu *lu = fi->priv;
struct osmo_gsup_req *req;
switch (event) {
case LU_EV_RX_GSUP:
req = data;
break;
default:
OSMO_ASSERT(false);
}
switch (req->gsup.message_type) {
case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
osmo_gsup_req_free(req);
lu_success(lu);
break;
case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
lu_failure(lu, GMM_CAUSE_NET_FAIL, "Rx %s", osmo_gsup_message_type_name(req->gsup.message_type));
break;
default:
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "unexpected message type in this state");
break;
}
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state lu_fsm_states[] = {
[LU_ST_UNVALIDATED] = {
.name = "UNVALIDATED",
.out_state_mask = 0
| S(LU_ST_WAIT_INSERT_DATA_RESULT)
,
},
[LU_ST_WAIT_INSERT_DATA_RESULT] = {
.name = "WAIT_INSERT_DATA_RESULT",
.in_event_mask = 0
| S(LU_EV_RX_GSUP)
,
.onenter = lu_fsm_wait_insert_data_result_onenter,
.action = lu_fsm_wait_insert_data_result,
},
};
static struct osmo_fsm lu_fsm = {
.name = "lu",
.states = lu_fsm_states,
.num_states = ARRAY_SIZE(lu_fsm_states),
.log_subsys = DLU,
.event_names = lu_fsm_event_names,
.timer_cb = lu_fsm_timer_cb,
.cleanup = lu_fsm_cleanup,
};
static __attribute__((constructor)) void lu_fsm_init()
{
OSMO_ASSERT(osmo_fsm_register(&lu_fsm) == 0);
}

View File

@@ -1,259 +0,0 @@
/* OsmoHLR TX/RX lu operations */
/* (C) 2017 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Harald Welte <laforge@gnumonks.org>
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <osmocom/core/logging.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/gsm/apn.h>
#include "gsup_server.h"
#include "gsup_router.h"
#include "logging.h"
#include "luop.h"
const struct value_string lu_state_names[] = {
{ LU_S_NULL, "NULL" },
{ LU_S_LU_RECEIVED, "LU RECEIVED" },
{ LU_S_CANCEL_SENT, "CANCEL SENT" },
{ LU_S_CANCEL_ACK_RECEIVED, "CANCEL-ACK RECEIVED" },
{ LU_S_ISD_SENT, "ISD SENT" },
{ LU_S_ISD_ACK_RECEIVED, "ISD-ACK RECEIVED" },
{ LU_S_COMPLETE, "COMPLETE" },
{ 0, NULL }
};
/* Transmit a given GSUP message for the given LU operation */
static void _luop_tx_gsup(struct lu_operation *luop,
const struct osmo_gsup_message *gsup)
{
struct msgb *msg_out;
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP LUOP");
OSMO_ASSERT(msg_out);
osmo_gsup_encode(msg_out, gsup);
osmo_gsup_addr_send(luop->gsup_server, luop->peer,
talloc_total_size(luop->peer),
msg_out);
}
static inline void fill_gsup_msg(struct osmo_gsup_message *out,
const struct lu_operation *lu,
enum osmo_gsup_message_type mt)
{
memset(out, 0, sizeof(struct osmo_gsup_message));
if (lu)
osmo_strlcpy(out->imsi, lu->subscr.imsi,
GSM23003_IMSI_MAX_DIGITS + 1);
out->message_type = mt;
}
/* timer call-back in case LU operation doesn't receive an response */
static void lu_op_timer_cb(void *data)
{
struct lu_operation *luop = data;
DEBUGP(DMAIN, "LU OP timer expired in state %s\n",
get_value_string(lu_state_names, luop->state));
switch (luop->state) {
case LU_S_CANCEL_SENT:
break;
case LU_S_ISD_SENT:
break;
default:
break;
}
lu_op_tx_error(luop, GMM_CAUSE_NET_FAIL);
}
bool lu_op_fill_subscr(struct lu_operation *luop, struct db_context *dbc,
const char *imsi)
{
struct hlr_subscriber *subscr = &luop->subscr;
if (db_subscr_get_by_imsi(dbc, imsi, subscr) < 0)
return false;
return true;
}
struct lu_operation *lu_op_alloc(struct osmo_gsup_server *srv)
{
struct lu_operation *luop;
luop = talloc_zero(srv, struct lu_operation);
OSMO_ASSERT(luop);
luop->gsup_server = srv;
osmo_timer_setup(&luop->timer, lu_op_timer_cb, luop);
return luop;
}
void lu_op_free(struct lu_operation *luop)
{
/* Only attempt to remove when it was ever added to a list. */
if (luop->list.next)
llist_del(&luop->list);
/* Delete timer just in case it is still pending. */
osmo_timer_del(&luop->timer);
talloc_free(luop);
}
struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn)
{
uint8_t *peer_addr;
struct lu_operation *luop = lu_op_alloc(conn->server);
int rc = osmo_gsup_conn_ccm_get(conn, &peer_addr, IPAC_IDTAG_SERNR);
if (rc < 0) {
lu_op_free(luop);
return NULL;
}
luop->peer = talloc_memdup(luop, peer_addr, rc);
return luop;
}
/* FIXME: this doesn't seem to work at all */
struct lu_operation *lu_op_by_imsi(const char *imsi,
const struct llist_head *lst)
{
struct lu_operation *luop;
llist_for_each_entry(luop, lst, list) {
if (!strcmp(imsi, luop->subscr.imsi))
return luop;
}
return NULL;
}
void lu_op_statechg(struct lu_operation *luop, enum lu_state new_state)
{
enum lu_state old_state = luop->state;
DEBUGP(DMAIN, "LU OP state change: %s -> ",
get_value_string(lu_state_names, old_state));
DEBUGPC(DMAIN, "%s\n",
get_value_string(lu_state_names, new_state));
luop->state = new_state;
}
/*! Transmit UPD_LOC_ERROR and destroy lu_operation */
void lu_op_tx_error(struct lu_operation *luop, enum gsm48_gmm_cause cause)
{
struct osmo_gsup_message gsup;
DEBUGP(DMAIN, "%s: LU OP Tx Error (cause %s)\n",
luop->subscr.imsi, get_value_string(gsm48_gmm_cause_names,
cause));
fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR);
gsup.cause = cause;
_luop_tx_gsup(luop, &gsup);
lu_op_free(luop);
}
/*! Transmit UPD_LOC_RESULT and destroy lu_operation */
void lu_op_tx_ack(struct lu_operation *luop)
{
struct osmo_gsup_message gsup;
fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT);
//FIXME gsup.hlr_enc;
_luop_tx_gsup(luop, &gsup);
lu_op_free(luop);
}
/*! Send Cancel Location to old VLR/SGSN */
void lu_op_tx_cancel_old(struct lu_operation *luop)
{
struct osmo_gsup_message gsup;
OSMO_ASSERT(luop->state == LU_S_LU_RECEIVED);
fill_gsup_msg(&gsup, NULL, OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST);
//gsup.cause = FIXME;
//gsup.cancel_type = FIXME;
_luop_tx_gsup(luop, &gsup);
lu_op_statechg(luop, LU_S_CANCEL_SENT);
osmo_timer_schedule(&luop->timer, CANCEL_TIMEOUT_SECS, 0);
}
/*! Transmit Insert Subscriber Data to new VLR/SGSN */
void lu_op_tx_insert_subscr_data(struct lu_operation *luop)
{
struct hlr_subscriber *subscr = &luop->subscr;
struct osmo_gsup_message gsup = { };
uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
uint8_t apn[APN_MAXLEN];
enum osmo_gsup_cn_domain cn_domain;
OSMO_ASSERT(luop->state == LU_S_LU_RECEIVED ||
luop->state == LU_S_CANCEL_ACK_RECEIVED);
if (luop->is_ps)
cn_domain = OSMO_GSUP_CN_DOMAIN_PS;
else
cn_domain = OSMO_GSUP_CN_DOMAIN_CS;
if (osmo_gsup_create_insert_subscriber_data_msg(&gsup, subscr->imsi, subscr->msisdn, msisdn_enc,
sizeof(msisdn_enc), apn, sizeof(apn), cn_domain) != 0) {
LOGP(DMAIN, LOGL_ERROR,
"IMSI='%s': Cannot notify GSUP client; could not create gsup message "
"for %s\n", subscr->imsi, luop->peer);
return;
}
/* Send ISD to new VLR/SGSN */
_luop_tx_gsup(luop, &gsup);
lu_op_statechg(luop, LU_S_ISD_SENT);
osmo_timer_schedule(&luop->timer, ISD_TIMEOUT_SECS, 0);
}
/*! Transmit Delete Subscriber Data to new VLR/SGSN.
* The luop is not freed. */
void lu_op_tx_del_subscr_data(struct lu_operation *luop)
{
struct osmo_gsup_message gsup;
fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_DELETE_DATA_REQUEST);
gsup.cn_domain = OSMO_GSUP_CN_DOMAIN_PS;
/* Send ISD to new VLR/SGSN */
_luop_tx_gsup(luop, &gsup);
}

View File

@@ -1,81 +0,0 @@
/* OsmoHLR TX/RX lu operations */
/* (C) 2017 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Harald Welte <laforge@gnumonks.org>
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <stdbool.h>
#include <osmocom/core/timer.h>
#include <osmocom/gsm/gsup.h>
#include "db.h"
#include "gsup_server.h"
#define CANCEL_TIMEOUT_SECS 30
#define ISD_TIMEOUT_SECS 30
enum lu_state {
LU_S_NULL,
LU_S_LU_RECEIVED,
LU_S_CANCEL_SENT,
LU_S_CANCEL_ACK_RECEIVED,
LU_S_ISD_SENT,
LU_S_ISD_ACK_RECEIVED,
LU_S_COMPLETE,
};
extern const struct value_string lu_state_names[];
struct lu_operation {
/*! entry in global list of location update operations */
struct llist_head list;
/*! to which gsup_server do we belong */
struct osmo_gsup_server *gsup_server;
/*! state of the location update */
enum lu_state state;
/*! CS (false) or PS (true) Location Update? */
bool is_ps;
/*! currently running timer */
struct osmo_timer_list timer;
/*! subscriber related to this operation */
struct hlr_subscriber subscr;
/*! peer VLR/SGSN starting the request */
uint8_t *peer;
};
struct lu_operation *lu_op_alloc(struct osmo_gsup_server *srv);
struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn);
void lu_op_statechg(struct lu_operation *luop, enum lu_state new_state);
bool lu_op_fill_subscr(struct lu_operation *luop, struct db_context *dbc,
const char *imsi);
struct lu_operation *lu_op_by_imsi(const char *imsi,
const struct llist_head *lst);
void lu_op_tx_error(struct lu_operation *luop, enum gsm48_gmm_cause cause);
void lu_op_tx_ack(struct lu_operation *luop);
void lu_op_tx_cancel_old(struct lu_operation *luop);
void lu_op_tx_insert_subscr_data(struct lu_operation *luop);
void lu_op_tx_del_subscr_data(struct lu_operation *luop);
void lu_op_free(struct lu_operation *luop);

37
src/mslookup/Makefile.am Normal file
View File

@@ -0,0 +1,37 @@
# 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 = \
mdns.c \
mdns_msg.c \
mdns_rfc.c \
mdns_sock.c \
mslookup.c \
mslookup_client.c \
mslookup_client_fake.c \
mslookup_client_mdns.c \
$(NULL)
libosmo_mslookup_la_LDFLAGS = -version-info $(LIBVERSION)
libosmo_mslookup_la_LIBADD = \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(TALLOC_LIBS) \
$(NULL)
bin_PROGRAMS = osmo-mslookup-client
osmo_mslookup_client_SOURCES = osmo-mslookup-client.c
osmo_mslookup_client_LDADD = \
libosmo-mslookup.la \
$(LIBOSMOCORE_LIBS) \
$(TALLOC_LIBS) \
$(NULL)
osmo_mslookup_client_CFLAGS = $(TALLOC_CFLAGS) $(LIBOSMOCORE_CFLAGS)

425
src/mslookup/mdns.c Normal file
View File

@@ -0,0 +1,425 @@
/* mslookup specific functions for encoding and decoding mslookup queries/results into mDNS packets, using the high
* level functions from mdns_msg.c and mdns_record.c to build the request/answer messages. */
/* 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/core/msgb.h>
#include <osmocom/mslookup/mslookup.h>
#include <osmocom/mslookup/mdns_msg.h>
#include <osmocom/mslookup/mdns_rfc.h>
#include <errno.h>
#include <inttypes.h>
static struct msgb *osmo_mdns_msgb_alloc(const char *label)
{
return msgb_alloc(1024, label);
}
/*! Combine the mslookup query service, ID and ID type into a domain string.
* \param[in] domain_suffix is appended to each domain in the queries to avoid colliding with the top-level domains
* administrated by IANA. Example: "mdns.osmocom.org"
* \returns allocated buffer with the resulting domain (i.e. "sip.voice.123.msisdn.mdns.osmocom.org") on success,
* NULL on failure.
*/
static char *domain_from_query(void *ctx, const struct osmo_mslookup_query *query, const char *domain_suffix)
{
const char *id;
/* Get id from query */
switch (query->id.type) {
case OSMO_MSLOOKUP_ID_IMSI:
id = query->id.imsi;
break;
case OSMO_MSLOOKUP_ID_MSISDN:
id = query->id.msisdn;
break;
default:
LOGP(DMSLOOKUP, LOGL_ERROR, "can't encode mslookup query id type %i", query->id.type);
return NULL;
}
return talloc_asprintf(ctx, "%s.%s.%s.%s", query->service, id, osmo_mslookup_id_type_name(query->id.type),
domain_suffix);
}
/*! Split up query service, ID and ID type from a domain string into a mslookup query.
* \param[in] domain with domain_suffix, e.g. "sip.voice.123.msisdn.mdns.osmocom.org"
* \param[in] domain_suffix is appended to each domain in the queries to avoid colliding with the top-level domains
* administrated by IANA. It is not part of the resulting struct osmo_mslookup_query, so we
* remove it in this function. Example: "mdns.osmocom.org"
*/
int query_from_domain(struct osmo_mslookup_query *query, const char *domain, const char *domain_suffix)
{
int domain_len = strlen(domain) - strlen(domain_suffix) - 1;
char domain_buf[OSMO_MDNS_RFC_MAX_NAME_LEN];
if (domain_len <= 0 || domain_len >= sizeof(domain_buf))
return -EINVAL;
if (domain[domain_len] != '.' || strcmp(domain + domain_len + 1, domain_suffix) != 0)
return -EINVAL;
memcpy(domain_buf, domain, domain_len);
domain_buf[domain_len] = '\0';
return osmo_mslookup_query_init_from_domain_str(query, domain_buf);
}
/*! Encode a mslookup query into a mDNS packet.
* \param[in] domain_suffix is appended to each domain in the queries to avoid colliding with the top-level domains
* administrated by IANA. Example: "mdns.osmocom.org"
* \returns msgb, or NULL on error.
*/
struct msgb *osmo_mdns_query_encode(void *ctx, uint16_t packet_id, const struct osmo_mslookup_query *query,
const char *domain_suffix)
{
struct osmo_mdns_msg_request req = {0};
struct msgb *msg = osmo_mdns_msgb_alloc(__func__);
req.id = packet_id;
req.type = OSMO_MDNS_RFC_RECORD_TYPE_ALL;
req.domain = domain_from_query(ctx, query, domain_suffix);
if (!req.domain)
goto error;
if (osmo_mdns_msg_request_encode(ctx, msg, &req))
goto error;
talloc_free(req.domain);
return msg;
error:
msgb_free(msg);
talloc_free(req.domain);
return NULL;
}
/*! Decode a mDNS request packet into a mslookup query.
* \param[out] packet_id the result must be sent with the same packet_id.
* \param[in] domain_suffix is appended to each domain in the queries to avoid colliding with the top-level domains
* administrated by IANA. Example: "mdns.osmocom.org"
* \returns allocated mslookup query on success, NULL on error.
*/
struct osmo_mslookup_query *osmo_mdns_query_decode(void *ctx, const uint8_t *data, size_t data_len,
uint16_t *packet_id, const char *domain_suffix)
{
struct osmo_mdns_msg_request *req = NULL;
struct osmo_mslookup_query *query = NULL;
req = osmo_mdns_msg_request_decode(ctx, data, data_len);
if (!req)
return NULL;
query = talloc_zero(ctx, struct osmo_mslookup_query);
OSMO_ASSERT(query);
if (query_from_domain(query, req->domain, domain_suffix) < 0)
goto error_free;
*packet_id = req->id;
talloc_free(req);
return query;
error_free:
talloc_free(req);
talloc_free(query);
return NULL;
}
/*! Parse sockaddr_str from mDNS record, so the mslookup result can be filled with it.
* \param[out] sockaddr_str resulting IPv4 or IPv6 sockaddr_str.
* \param[in] rec single record of the abstracted list of mDNS records
* \returns 0 on success, -EINVAL on error.
*/
static int sockaddr_str_from_mdns_record(struct osmo_sockaddr_str *sockaddr_str, struct osmo_mdns_record *rec)
{
switch (rec->type) {
case OSMO_MDNS_RFC_RECORD_TYPE_A:
if (rec->length != 4) {
LOGP(DMSLOOKUP, LOGL_ERROR, "unexpected length of A record\n");
return -EINVAL;
}
osmo_sockaddr_str_from_32(sockaddr_str, *(uint32_t *)rec->data, 0);
break;
case OSMO_MDNS_RFC_RECORD_TYPE_AAAA:
if (rec->length != 16) {
LOGP(DMSLOOKUP, LOGL_ERROR, "unexpected length of AAAA record\n");
return -EINVAL;
}
osmo_sockaddr_str_from_in6_addr(sockaddr_str, (struct in6_addr*)rec->data, 0);
break;
default:
LOGP(DMSLOOKUP, LOGL_ERROR, "unexpected record type\n");
return -EINVAL;
}
return 0;
}
/*! Encode a successful mslookup result, along with the original query and packet_id into one mDNS answer packet.
*
* The records in the packet are ordered as follows:
* 1) "age", ip_v4/v6, "port" (only IPv4 or IPv6 present) or
* 2) "age", ip_v4, "port", ip_v6, "port" (both IPv4 and v6 present).
* "age" and "port" are TXT records, ip_v4 is an A record, ip_v6 is an AAAA record.
*
* \param[in] packet_id as received in osmo_mdns_query_decode().
* \param[in] query the original query, so we can send the domain back in the answer (i.e. "sip.voice.1234.msisdn").
* \param[in] result holds the age, IPs and ports of the queried service.
* \param[in] domain_suffix is appended to each domain in the queries to avoid colliding with the top-level domains
* administrated by IANA. Example: "mdns.osmocom.org"
* \returns msg on success, NULL on error.
*/
struct msgb *osmo_mdns_result_encode(void *ctx, uint16_t packet_id, const struct osmo_mslookup_query *query,
const struct osmo_mslookup_result *result, const char *domain_suffix)
{
struct osmo_mdns_msg_answer ans = {};
struct osmo_mdns_record *rec_age = NULL;
struct osmo_mdns_record rec_ip_v4 = {0};
struct osmo_mdns_record rec_ip_v6 = {0};
struct osmo_mdns_record *rec_ip_v4_port = NULL;
struct osmo_mdns_record *rec_ip_v6_port = NULL;
struct in_addr rec_ip_v4_in;
struct in6_addr rec_ip_v6_in;
struct msgb *msg = osmo_mdns_msgb_alloc(__func__);
char buf[256];
ctx = talloc_named(ctx, 0, "osmo_mdns_result_encode");
/* Prepare answer (ans) */
ans.domain = domain_from_query(ctx, query, domain_suffix);
if (!ans.domain)
goto error;
ans.id = packet_id;
INIT_LLIST_HEAD(&ans.records);
/* Record for age */
rec_age = osmo_mdns_record_txt_keyval_encode(ctx, "age", "%"PRIu32, result->age);
OSMO_ASSERT(rec_age);
llist_add_tail(&rec_age->list, &ans.records);
/* Records for IPv4 */
if (osmo_sockaddr_str_is_set(&result->host_v4)) {
if (osmo_sockaddr_str_to_in_addr(&result->host_v4, &rec_ip_v4_in) < 0) {
LOGP(DMSLOOKUP, LOGL_ERROR, "failed to encode ipv4: %s\n",
osmo_mslookup_result_name_b(buf, sizeof(buf), query, result));
goto error;
}
rec_ip_v4.type = OSMO_MDNS_RFC_RECORD_TYPE_A;
rec_ip_v4.data = (uint8_t *)&rec_ip_v4_in;
rec_ip_v4.length = sizeof(rec_ip_v4_in);
llist_add_tail(&rec_ip_v4.list, &ans.records);
rec_ip_v4_port = osmo_mdns_record_txt_keyval_encode(ctx, "port", "%"PRIu16, result->host_v4.port);
OSMO_ASSERT(rec_ip_v4_port);
llist_add_tail(&rec_ip_v4_port->list, &ans.records);
}
/* Records for IPv6 */
if (osmo_sockaddr_str_is_set(&result->host_v6)) {
if (osmo_sockaddr_str_to_in6_addr(&result->host_v6, &rec_ip_v6_in) < 0) {
LOGP(DMSLOOKUP, LOGL_ERROR, "failed to encode ipv6: %s\n",
osmo_mslookup_result_name_b(buf, sizeof(buf), query, result));
goto error;
}
rec_ip_v6.type = OSMO_MDNS_RFC_RECORD_TYPE_AAAA;
rec_ip_v6.data = (uint8_t *)&rec_ip_v6_in;
rec_ip_v6.length = sizeof(rec_ip_v6_in);
llist_add_tail(&rec_ip_v6.list, &ans.records);
rec_ip_v6_port = osmo_mdns_record_txt_keyval_encode(ctx, "port", "%"PRIu16, result->host_v6.port);
OSMO_ASSERT(rec_ip_v6_port);
llist_add_tail(&rec_ip_v6_port->list, &ans.records);
}
if (osmo_mdns_msg_answer_encode(ctx, msg, &ans)) {
LOGP(DMSLOOKUP, LOGL_ERROR, "failed to encode mDNS answer: %s\n",
osmo_mslookup_result_name_b(buf, sizeof(buf), query, result));
goto error;
}
talloc_free(ctx);
return msg;
error:
msgb_free(msg);
talloc_free(ctx);
return NULL;
}
static int decode_uint32_t(const char *str, uint32_t *val)
{
long long int lld;
char *endptr = NULL;
*val = 0;
errno = 0;
lld = strtoll(str, &endptr, 10);
if (errno || !endptr || *endptr)
return -EINVAL;
if (lld < 0 || lld > UINT32_MAX)
return -EINVAL;
*val = lld;
return 0;
}
static int decode_port(const char *str, uint16_t *port)
{
uint32_t val;
if (decode_uint32_t(str, &val))
return -EINVAL;
if (val > 65535)
return -EINVAL;
*port = val;
return 0;
}
/*! Read expected mDNS records into mslookup result.
*
* The records in the packet must be ordered as follows:
* 1) "age", ip_v4/v6, "port" (only IPv4 or IPv6 present) or
* 2) "age", ip_v4, "port", ip_v6, "port" (both IPv4 and v6 present).
* "age" and "port" are TXT records, ip_v4 is an A record, ip_v6 is an AAAA record.
*
* \param[out] result holds the age, IPs and ports of the queried service.
* \param[in] ans abstracted mDNS answer with a list of resource records.
* \returns 0 on success, -EINVAL on error.
*/
int osmo_mdns_result_from_answer(struct osmo_mslookup_result *result, const struct osmo_mdns_msg_answer *ans)
{
struct osmo_mdns_record *rec;
char txt_key[64];
char txt_value[64];
bool found_age = false;
bool found_ip_v4 = false;
bool found_ip_v6 = false;
struct osmo_sockaddr_str *expect_port_for = NULL;
*result = (struct osmo_mslookup_result){};
result->rc = OSMO_MSLOOKUP_RC_NONE;
llist_for_each_entry(rec, &ans->records, list) {
switch (rec->type) {
case OSMO_MDNS_RFC_RECORD_TYPE_A:
if (expect_port_for) {
LOGP(DMSLOOKUP, LOGL_ERROR,
"'A' record found, but still expecting a 'port' value first\n");
return -EINVAL;
}
if (found_ip_v4) {
LOGP(DMSLOOKUP, LOGL_ERROR, "'A' record found twice in mDNS answer\n");
return -EINVAL;
}
found_ip_v4 = true;
expect_port_for = &result->host_v4;
if (sockaddr_str_from_mdns_record(expect_port_for, rec)) {
LOGP(DMSLOOKUP, LOGL_ERROR, "'A' record with invalid address data\n");
return -EINVAL;
}
break;
case OSMO_MDNS_RFC_RECORD_TYPE_AAAA:
if (expect_port_for) {
LOGP(DMSLOOKUP, LOGL_ERROR,
"'AAAA' record found, but still expecting a 'port' value first\n");
return -EINVAL;
}
if (found_ip_v6) {
LOGP(DMSLOOKUP, LOGL_ERROR, "'AAAA' record found twice in mDNS answer\n");
return -EINVAL;
}
found_ip_v6 = true;
expect_port_for = &result->host_v6;
if (sockaddr_str_from_mdns_record(expect_port_for, rec) != 0) {
LOGP(DMSLOOKUP, LOGL_ERROR, "'AAAA' record with invalid address data\n");
return -EINVAL;
}
break;
case OSMO_MDNS_RFC_RECORD_TYPE_TXT:
if (osmo_mdns_record_txt_keyval_decode(rec, txt_key, sizeof(txt_key),
txt_value, sizeof(txt_value)) != 0) {
LOGP(DMSLOOKUP, LOGL_ERROR, "failed to decode txt record\n");
return -EINVAL;
}
if (strcmp(txt_key, "age") == 0) {
if (found_age) {
LOGP(DMSLOOKUP, LOGL_ERROR, "duplicate 'TXT' record for 'age'\n");
return -EINVAL;
}
found_age = true;
if (decode_uint32_t(txt_value, &result->age)) {
LOGP(DMSLOOKUP, LOGL_ERROR,
"'TXT' record: invalid 'age' value ('age=%s')\n", txt_value);
return -EINVAL;
}
} else if (strcmp(txt_key, "port") == 0) {
if (!expect_port_for) {
LOGP(DMSLOOKUP, LOGL_ERROR,
"'TXT' record for 'port' without previous 'A' or 'AAAA' record\n");
return -EINVAL;
}
if (decode_port(txt_value, &expect_port_for->port)) {
LOGP(DMSLOOKUP, LOGL_ERROR,
"'TXT' record: invalid 'port' value ('port=%s')\n", txt_value);
return -EINVAL;
}
expect_port_for = NULL;
} else {
LOGP(DMSLOOKUP, LOGL_ERROR, "unexpected key '%s' in TXT record\n", txt_key);
return -EINVAL;
}
break;
default:
LOGP(DMSLOOKUP, LOGL_ERROR, "unexpected record type\n");
return -EINVAL;
}
}
/* Check if everything was found */
if (!found_age || !(found_ip_v4 || found_ip_v6) || expect_port_for) {
LOGP(DMSLOOKUP, LOGL_ERROR, "missing resource records in mDNS answer\n");
return -EINVAL;
}
result->rc = OSMO_MSLOOKUP_RC_RESULT;
return 0;
}
/*! Decode a mDNS answer packet into a mslookup result, query and packet_id.
* \param[out] packet_id same ID as sent in the request packet.
* \param[out] query the original query (service, ID, ID type).
* \param[out] result holds the age, IPs and ports of the queried service.
* \param[in] domain_suffix is appended to each domain in the queries to avoid colliding with the top-level domains
* administrated by IANA. Example: "mdns.osmocom.org"
* \returns 0 on success, -EINVAL on error.
*/
int osmo_mdns_result_decode(void *ctx, const uint8_t *data, size_t data_len, uint16_t *packet_id,
struct osmo_mslookup_query *query, struct osmo_mslookup_result *result,
const char *domain_suffix)
{
int rc = -EINVAL;
struct osmo_mdns_msg_answer *ans;
ans = osmo_mdns_msg_answer_decode(ctx, data, data_len);
if (!ans)
goto exit_free;
if (query_from_domain(query, ans->domain, domain_suffix) < 0)
goto exit_free;
if (osmo_mdns_result_from_answer(result, ans) < 0)
goto exit_free;
*packet_id = ans->id;
rc = 0;
exit_free:
talloc_free(ans);
return rc;
}

261
src/mslookup/mdns_msg.c Normal file
View File

@@ -0,0 +1,261 @@
/* High level mDNS encoding and decoding functions for whole messages:
* Request message (header, question)
* Answer message (header, resource record 1, ... resource record N)*/
/* 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 <errno.h>
#include <string.h>
#include <osmocom/hlr/logging.h>
#include <osmocom/mslookup/mdns_msg.h>
/*! Encode request message into one mDNS packet, consisting of the header section and one question section.
* \returns 0 on success, -EINVAL on error.
*/
int osmo_mdns_msg_request_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_msg_request *req)
{
struct osmo_mdns_rfc_header hdr = {0};
struct osmo_mdns_rfc_question qst = {0};
hdr.id = req->id;
hdr.qdcount = 1;
osmo_mdns_rfc_header_encode(msg, &hdr);
qst.domain = req->domain;
qst.qtype = req->type;
qst.qclass = OSMO_MDNS_RFC_CLASS_IN;
if (osmo_mdns_rfc_question_encode(ctx, msg, &qst) != 0)
return -EINVAL;
return 0;
}
/*! Decode request message from a mDNS packet, consisting of the header section and one question section.
* \returns allocated request message on success, NULL on error.
*/
struct osmo_mdns_msg_request *osmo_mdns_msg_request_decode(void *ctx, const uint8_t *data, size_t data_len)
{
struct osmo_mdns_rfc_header hdr = {0};
size_t hdr_len = sizeof(struct osmo_mdns_rfc_header);
struct osmo_mdns_rfc_question* qst = NULL;
struct osmo_mdns_msg_request *ret = NULL;
if (data_len < hdr_len || osmo_mdns_rfc_header_decode(data, hdr_len, &hdr) != 0 || hdr.qr != 0)
return NULL;
qst = osmo_mdns_rfc_question_decode(ctx, data + hdr_len, data_len - hdr_len);
if (!qst)
return NULL;
ret = talloc_zero(ctx, struct osmo_mdns_msg_request);
ret->id = hdr.id;
ret->domain = talloc_strdup(ret, qst->domain);
ret->type = qst->qtype;
talloc_free(qst);
return ret;
}
/*! Initialize the linked list for resource records in a answer message. */
void osmo_mdns_msg_answer_init(struct osmo_mdns_msg_answer *ans)
{
*ans = (struct osmo_mdns_msg_answer){};
INIT_LLIST_HEAD(&ans->records);
}
/*! Encode answer message into one mDNS packet, consisting of the header section and N resource records.
*
* To keep things simple, this sends the domain with each resource record. Other DNS implementations make use of
* "message compression", which would send a question section with the domain before the resource records, and then
* point inside each resource record with an offset back to the domain in the question section (RFC 1035 4.1.4).
* \returns 0 on success, -EINVAL on error.
*/
int osmo_mdns_msg_answer_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_msg_answer *ans)
{
struct osmo_mdns_rfc_header hdr = {0};
struct osmo_mdns_record *ans_record;
hdr.id = ans->id;
hdr.qr = 1;
hdr.ancount = llist_count(&ans->records);
osmo_mdns_rfc_header_encode(msg, &hdr);
llist_for_each_entry(ans_record, &ans->records, list) {
struct osmo_mdns_rfc_record rec = {0};
rec.domain = ans->domain;
rec.type = ans_record->type;
rec.class = OSMO_MDNS_RFC_CLASS_IN;
rec.ttl = 0;
rec.rdlength = ans_record->length;
rec.rdata = ans_record->data;
if (osmo_mdns_rfc_record_encode(ctx, msg, &rec) != 0)
return -EINVAL;
}
return 0;
}
/*! Decode answer message from a mDNS packet.
*
* Answer messages must consist of one header and one or more resource records. An additional question section or
* message compression (RFC 1035 4.1.4) are not supported.
* \returns allocated answer message on success, NULL on error.
*/
struct osmo_mdns_msg_answer *osmo_mdns_msg_answer_decode(void *ctx, const uint8_t *data, size_t data_len)
{
struct osmo_mdns_rfc_header hdr = {};
size_t hdr_len = sizeof(struct osmo_mdns_rfc_header);
struct osmo_mdns_msg_answer *ret = talloc_zero(ctx, struct osmo_mdns_msg_answer);
/* Parse header section */
if (data_len < hdr_len || osmo_mdns_rfc_header_decode(data, hdr_len, &hdr) != 0 || hdr.qr != 1)
goto error;
ret->id = hdr.id;
data_len -= hdr_len;
data += hdr_len;
/* Parse resource records */
INIT_LLIST_HEAD(&ret->records);
while (data_len) {
size_t record_len;
struct osmo_mdns_rfc_record *rec;
struct osmo_mdns_record* ret_record;
rec = osmo_mdns_rfc_record_decode(ret, data, data_len, &record_len);
if (!rec)
goto error;
/* Copy domain to ret */
if (ret->domain) {
if (strcmp(ret->domain, rec->domain) != 0) {
LOGP(DMSLOOKUP, LOGL_ERROR, "domain mismatch in resource records ('%s' vs '%s')\n",
ret->domain, rec->domain);
goto error;
}
}
else
ret->domain = talloc_strdup(ret, rec->domain);
/* Add simplified record to ret */
ret_record = talloc_zero(ret, struct osmo_mdns_record);
ret_record->type = rec->type;
ret_record->length = rec->rdlength;
ret_record->data = talloc_memdup(ret_record, rec->rdata, rec->rdlength);
llist_add_tail(&ret_record->list, &ret->records);
data += record_len;
data_len -= record_len;
talloc_free(rec);
}
/* Verify record count */
if (llist_count(&ret->records) != hdr.ancount) {
LOGP(DMSLOOKUP, LOGL_ERROR, "amount of parsed records (%i) doesn't match count in header (%i)\n",
llist_count(&ret->records), hdr.ancount);
goto error;
}
return ret;
error:
talloc_free(ret);
return NULL;
}
/*! Get a TXT resource record, which stores a key=value string.
* \returns allocated resource record on success, NULL on error.
*/
static struct osmo_mdns_record *_osmo_mdns_record_txt_encode(void *ctx, const char *key, const char *value)
{
struct osmo_mdns_record *ret = talloc_zero(ctx, struct osmo_mdns_record);
size_t len = strlen(key) + 1 + strlen(value);
if (len > OSMO_MDNS_RFC_MAX_CHARACTER_STRING_LEN - 1)
return NULL;
/* redundant len is required, see RFC 1035 3.3.14 and 3.3. */
ret->data = (uint8_t *)talloc_asprintf(ctx, "%c%s=%s", (char)len, key, value);
if (!ret->data)
return NULL;
ret->type = OSMO_MDNS_RFC_RECORD_TYPE_TXT;
ret->length = len + 1;
return ret;
}
/*! Get a TXT resource record, which stores a key=value string, but build value from a format string.
* \returns allocated resource record on success, NULL on error.
*/
struct osmo_mdns_record *osmo_mdns_record_txt_keyval_encode(void *ctx, const char *key, const char *value_fmt, ...)
{
va_list ap;
char *value = NULL;
struct osmo_mdns_record *r;
if (!value_fmt)
return _osmo_mdns_record_txt_encode(ctx, key, "");
va_start(ap, value_fmt);
value = talloc_vasprintf(ctx, value_fmt, ap);
va_end(ap);
if (!value)
return NULL;
r = _osmo_mdns_record_txt_encode(ctx, key, value);
talloc_free(value);
return r;
}
/*! Decode a TXT resource record, which stores a key=value string.
* \returns 0 on success, -EINVAL on error.
*/
int osmo_mdns_record_txt_keyval_decode(const struct osmo_mdns_record *rec,
char *key_buf, size_t key_size, char *value_buf, size_t value_size)
{
const char *key_value;
const char *key_value_end;
const char *sep;
const char *value;
if (rec->type != OSMO_MDNS_RFC_RECORD_TYPE_TXT)
return -EINVAL;
key_value = (const char *)rec->data;
key_value_end = key_value + rec->length;
/* Verify and then skip the redundant string length byte */
if (*key_value != rec->length - 1)
return -EINVAL;
key_value++;
if (key_value >= key_value_end)
return -EINVAL;
/* Find equals sign */
sep = osmo_strnchr(key_value, key_value_end - key_value, '=');
if (!sep)
return -EINVAL;
/* Parse key */
osmo_print_n(key_buf, key_size, key_value, sep - key_value);
/* Parse value */
value = sep + 1;
osmo_print_n(value_buf, value_size, value, key_value_end - value);
return 0;
}

270
src/mslookup/mdns_rfc.c Normal file
View File

@@ -0,0 +1,270 @@
/* Low level mDNS encoding and decoding functions of the qname IE, header/question sections and resource records,
* as described in these RFCs:
* - RFC 1035 (Domain names - implementation and specification)
* - RFC 3596 (DNS Extensions to Support IP Version 6) */
/* 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 <ctype.h>
#include <errno.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/bitvec.h>
#include <osmocom/core/logging.h>
#include <osmocom/mslookup/mdns_rfc.h>
/*
* Encode/decode IEs
*/
/*! Encode a domain string as qname (RFC 1035 4.1.2).
* \param[in] domain multiple labels separated by dots, e.g. "sip.voice.1234.msisdn".
* \returns allocated buffer with length-value pairs for each label (e.g. 0x03 "sip" 0x05 "voice" ...), NULL on error.
*/
char *osmo_mdns_rfc_qname_encode(void *ctx, const char *domain)
{
char *domain_dup;
char *domain_iter;
char buf[OSMO_MDNS_RFC_MAX_NAME_LEN + 2] = ""; /* len(qname) is len(domain) +1 */
struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };
char *label;
if (strlen(domain) > OSMO_MDNS_RFC_MAX_NAME_LEN)
return NULL;
domain_iter = domain_dup = talloc_strdup(ctx, domain);
while ((label = strsep(&domain_iter, "."))) {
size_t len = strlen(label);
/* Empty domain, dot at start, two dots in a row, or ending with a dot */
if (!len)
goto error;
OSMO_STRBUF_PRINTF(sb, "%c%s", (char)len, label);
}
talloc_free(domain_dup);
return talloc_strdup(ctx, buf);
error:
talloc_free(domain_dup);
return NULL;
}
/*! Decode a domain string from a qname (RFC 1035 4.1.2).
* \param[in] qname buffer with length-value pairs for each label (e.g. 0x03 "sip" 0x05 "voice" ...)
* \param[in] qname_max_len amount of bytes that can be read at most from the memory location that qname points to.
* \returns allocated buffer with domain string, multiple labels separated by dots (e.g. "sip.voice.1234.msisdn"),
* NULL on error.
*/
char *osmo_mdns_rfc_qname_decode(void *ctx, const char *qname, size_t qname_max_len)
{
const char *next_label, *qname_end = qname + qname_max_len;
char buf[OSMO_MDNS_RFC_MAX_NAME_LEN + 1];
int i = 0;
if (qname_max_len < 1)
return NULL;
while (*qname) {
size_t len;
if (i >= qname_max_len)
return NULL;
len = *qname;
next_label = qname + len + 1;
if (next_label >= qname_end || i + len > OSMO_MDNS_RFC_MAX_NAME_LEN)
return NULL;
if (i) {
/* Two dots in a row is not allowed */
if (buf[i - 1] == '.')
return NULL;
buf[i] = '.';
i++;
}
memcpy(buf + i, qname + 1, len);
i += len;
qname = next_label;
}
buf[i] = '\0';
return talloc_strdup(ctx, buf);
}
/*
* Encode/decode message sections
*/
/*! Encode header section (RFC 1035 4.1.1).
* \param[in] msgb mesage buffer to which the encoded data will be appended.
*/
void osmo_mdns_rfc_header_encode(struct msgb *msg, const struct osmo_mdns_rfc_header *hdr)
{
struct osmo_mdns_rfc_header *buf = (struct osmo_mdns_rfc_header *) msgb_put(msg, sizeof(*hdr));
memcpy(buf, hdr, sizeof(*hdr));
osmo_store16be(buf->id, &buf->id);
osmo_store16be(buf->qdcount, &buf->qdcount);
osmo_store16be(buf->ancount, &buf->ancount);
osmo_store16be(buf->nscount, &buf->nscount);
osmo_store16be(buf->arcount, &buf->arcount);
}
/*! Decode header section (RFC 1035 4.1.1). */
int osmo_mdns_rfc_header_decode(const uint8_t *data, size_t data_len, struct osmo_mdns_rfc_header *hdr)
{
if (data_len != sizeof(*hdr))
return -EINVAL;
memcpy(hdr, data, data_len);
hdr->id = osmo_load16be(&hdr->id);
hdr->qdcount = osmo_load16be(&hdr->qdcount);
hdr->ancount = osmo_load16be(&hdr->ancount);
hdr->nscount = osmo_load16be(&hdr->nscount);
hdr->arcount = osmo_load16be(&hdr->arcount);
return 0;
}
/*! Encode question section (RFC 1035 4.1.2).
* \param[in] msgb mesage buffer to which the encoded data will be appended.
*/
int osmo_mdns_rfc_question_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_rfc_question *qst)
{
char *qname;
size_t qname_len;
uint8_t *qname_buf;
/* qname */
qname = osmo_mdns_rfc_qname_encode(ctx, qst->domain);
if (!qname)
return -EINVAL;
qname_len = strlen(qname) + 1;
qname_buf = msgb_put(msg, qname_len);
memcpy(qname_buf, qname, qname_len);
talloc_free(qname);
/* qtype and qclass */
msgb_put_u16(msg, qst->qtype);
msgb_put_u16(msg, qst->qclass);
return 0;
}
/*! Decode question section (RFC 1035 4.1.2). */
struct osmo_mdns_rfc_question *osmo_mdns_rfc_question_decode(void *ctx, const uint8_t *data, size_t data_len)
{
struct osmo_mdns_rfc_question *ret;
size_t qname_len = data_len - 4;
if (data_len < 6)
return NULL;
/* qname */
ret = talloc_zero(ctx, struct osmo_mdns_rfc_question);
if (!ret)
return NULL;
ret->domain = osmo_mdns_rfc_qname_decode(ret, (const char *)data, qname_len);
if (!ret->domain) {
talloc_free(ret);
return NULL;
}
/* qtype and qclass */
ret->qtype = osmo_load16be(data + qname_len);
ret->qclass = osmo_load16be(data + qname_len + 2);
return ret;
}
/*
* Encode/decode resource records
*/
/*! Encode one resource record (RFC 1035 4.1.3).
* \param[in] msgb mesage buffer to which the encoded data will be appended.
*/
int osmo_mdns_rfc_record_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_rfc_record *rec)
{
char *name;
size_t name_len;
uint8_t *buf;
/* name */
name = osmo_mdns_rfc_qname_encode(ctx, rec->domain);
if (!name)
return -EINVAL;
name_len = strlen(name) + 1;
buf = msgb_put(msg, name_len);
memcpy(buf, name, name_len);
talloc_free(name);
/* type, class, ttl, rdlength */
msgb_put_u16(msg, rec->type);
msgb_put_u16(msg, rec->class);
msgb_put_u32(msg, rec->ttl);
msgb_put_u16(msg, rec->rdlength);
/* rdata */
buf = msgb_put(msg, rec->rdlength);
memcpy(buf, rec->rdata, rec->rdlength);
return 0;
}
/*! Decode one resource record (RFC 1035 4.1.3). */
struct osmo_mdns_rfc_record *osmo_mdns_rfc_record_decode(void *ctx, const uint8_t *data, size_t data_len,
size_t *record_len)
{
struct osmo_mdns_rfc_record *ret = talloc_zero(ctx, struct osmo_mdns_rfc_record);
size_t name_len;
/* name */
ret->domain = osmo_mdns_rfc_qname_decode(ret, (const char *)data, data_len - 10);
if (!ret->domain)
goto error;
name_len = strlen(ret->domain) + 2;
if (name_len + 10 > data_len)
goto error;
/* type, class, ttl, rdlength */
ret->type = osmo_load16be(data + name_len);
ret->class = osmo_load16be(data + name_len + 2);
ret->ttl = osmo_load32be(data + name_len + 4);
ret->rdlength = osmo_load16be(data + name_len + 8);
if (name_len + 10 + ret->rdlength > data_len)
goto error;
/* rdata */
ret->rdata = talloc_memdup(ret, data + name_len + 10, ret->rdlength);
if (!ret->rdata)
return NULL;
*record_len = name_len + 10 + ret->rdlength;
return ret;
error:
talloc_free(ret);
return NULL;
}

146
src/mslookup/mdns_sock.c Normal file
View File

@@ -0,0 +1,146 @@
/* 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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdbool.h>
#include <talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/select.h>
#include <osmocom/hlr/logging.h>
#include <osmocom/mslookup/mdns_sock.h>
/*! Open socket to send and receive multicast data.
*
* The socket is opened with SO_REUSEADDR, so we can bind to the same IP and port multiple times. This socket receives
* everything sent to that multicast IP/port, including its own data data sent from osmo_mdns_sock_send(). So whenever
* sending something, the receive callback will be called with the same data and should discard it.
*
* \param[in] ip multicast IPv4 or IPv6 address.
* \param[in] port port number.
* \param[in] cb callback for incoming data that will be passed to osmo_fd_setup (should read from osmo_fd->fd).
* \param[in] data userdata passed to osmo_fd (available in cb as osmo_fd->data).
* \param[in] priv_nr additional userdata integer passed to osmo_fd (available in cb as osmo_fd->priv_nr).
* \returns allocated osmo_mdns_sock, NULL on error.
*/
struct osmo_mdns_sock *osmo_mdns_sock_init(void *ctx, const char *ip, unsigned int port,
int (*cb)(struct osmo_fd *fd, unsigned int what),
void *data, unsigned int priv_nr)
{
struct osmo_mdns_sock *ret;
int sock, rc;
struct addrinfo hints = {0};
struct ip_mreq multicast_req = {0};
in_addr_t iface = INADDR_ANY;
char portbuf[10];
int y = 1;
snprintf(portbuf, sizeof(portbuf) -1, "%u", port);
ret = talloc_zero(ctx, struct osmo_mdns_sock);
OSMO_ASSERT(ret);
/* Fill addrinfo */
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = (AI_PASSIVE | AI_NUMERICHOST);
rc = getaddrinfo(ip, portbuf, &hints, &ret->ai);
if (rc != 0) {
LOGP(DMSLOOKUP, LOGL_ERROR, "osmo_mdns_sock_init: getaddrinfo: %s\n", gai_strerror(rc));
ret->ai = NULL;
goto error;
}
/* Open socket */
sock = socket(ret->ai->ai_family, ret->ai->ai_socktype, 0);
if (sock == -1) {
LOGP(DMSLOOKUP, LOGL_ERROR, "osmo_mdns_sock_init: socket: %s\n", strerror(errno));
goto error;
}
/* Set multicast options */
rc = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (char*)&iface, sizeof(iface));
if (rc == -1) {
LOGP(DMSLOOKUP, LOGL_ERROR, "osmo_mdns_sock_init: setsockopt: %s\n", strerror(errno));
goto error_sock;
}
memcpy(&multicast_req.imr_multiaddr, &((struct sockaddr_in*)(ret->ai->ai_addr))->sin_addr,
sizeof(multicast_req.imr_multiaddr));
multicast_req.imr_interface.s_addr = htonl(INADDR_ANY);
rc = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&multicast_req, sizeof(multicast_req));
if (rc == -1) {
LOGP(DMSLOOKUP, LOGL_ERROR, "osmo_mdns_sock_init: setsockopt: %s\n", strerror(errno));
goto error_sock;
}
/* Always allow binding the same IP and port twice. This is needed in OsmoHLR (where the code becomes cleaner by
* just using a different socket for server and client code) and in the mslookup_client_mdns_test. Also for
* osmo-mslookup-client if it is running multiple times in parallel (i.e. two incoming calls almost at the same
* time need to be resolved with the simple dialplan example that just starts new processes). */
rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&y, sizeof(y));
if (rc == -1) {
LOGP(DMSLOOKUP, LOGL_ERROR, "osmo_mdns_sock_init: setsockopt: %s\n", strerror(errno));
goto error_sock;
}
/* Bind and register osmo_fd callback */
rc = bind(sock, ret->ai->ai_addr, ret->ai->ai_addrlen);
if (rc == -1) {
LOGP(DMSLOOKUP, LOGL_ERROR, "osmo_mdns_sock_init: bind: %s\n", strerror(errno));
goto error_sock;
}
osmo_fd_setup(&ret->osmo_fd, sock, OSMO_FD_READ, cb, data, priv_nr);
if (osmo_fd_register(&ret->osmo_fd) != 0)
goto error_sock;
return ret;
error_sock:
close(sock);
error:
if (ret->ai)
freeaddrinfo(ret->ai);
talloc_free(ret);
return NULL;
}
/*! Send msgb over mdns_sock and consume msgb.
* \returns 0 on success, -1 on error.
*/
int osmo_mdns_sock_send(const struct osmo_mdns_sock *mdns_sock, struct msgb *msg)
{
size_t len = msgb_length(msg);
int rc = sendto(mdns_sock->osmo_fd.fd, msgb_data(msg), len, 0, mdns_sock->ai->ai_addr,
mdns_sock->ai->ai_addrlen);
msgb_free(msg);
return (rc == len) ? 0 : -1;
}
/*! Tear down osmo_mdns_sock. */
void osmo_mdns_sock_cleanup(struct osmo_mdns_sock *mdns_sock)
{
osmo_fd_close(&mdns_sock->osmo_fd);
freeaddrinfo(mdns_sock->ai);
talloc_free(mdns_sock);
}

Some files were not shown because too many files have changed in this diff Show More