Compare commits

..

53 Commits

Author SHA1 Message Date
Neels Hofmeyr
1f8f1b9777 cosmetic comment tweak
Change-Id: I0482d663e6c9042545677f9280c751323d332439
2019-12-25 19:09:45 +01:00
Piotr Krysik
a5ed663dea Enabling/disabling 4G RAN for given subscriber
The change adds support for enabling and disabling connection
through 4G RAN by a subscriber with use of USSD codes.

Change-Id: Idf964d9c770a0a1cb5c486eb2a7592d6bf44171c
2019-12-25 19:09:45 +01:00
Harald Welte
15031855bf AUC: Add support for setting the AMF separation bit to '1' for EUTRAN
Change-Id: Ic766bc40f6126bb479bd0a05b0e96bec3e240008
2019-12-25 19:09:45 +01:00
Neels Hofmeyr
83b909e4f3 Revert "mention *#201#, *#301# in USSD get_ran (HACK)"
This reverts commit 05e888ed7c.
2019-12-25 19:09:45 +01:00
Neels Hofmeyr
fde8f64d1d mention *#201#, *#301# in USSD get_ran (HACK)
Change-Id: Idbedbc679868a88dae1c85f82527f3f20067b879
2019-12-25 19:09:45 +01:00
gsmevent admin
d8e8a8425a send GMM_CAUSE_ROAMING_NOTALLOWED instead of GMM_CAUSE_IMSI_UNKNOWN HARDCODED 2019-12-25 19:09:45 +01:00
Neels Hofmeyr
c1d56fd2e9 add USSD handlers to enable/disable 2G access
Change-Id: Ib266bf6ebc7692362a443efbf9b4ae69aee671d6
2019-12-25 19:09:45 +01:00
Neels Hofmeyr
6ac66ac3ad for ussd_get_ran, show current RAT
Change-Id: I81adb1785c1a46e9153a6914ef2c699e9c90b731
2019-12-25 19:09:45 +01:00
Neels Hofmeyr
769a7f46f4 auto upgrade hlr.db in the service file
Change-Id: Icd3af5160e6d8f31fed5e84055fbb09322c54c2b
2019-12-25 19:09:45 +01:00
Neels Hofmeyr
5bc457e19a add column 'last_lu_rat', show last RAN type
Change-Id: I5d73b1d928b61175d3198326706b7f49ba50e16f
2019-12-25 19:09:45 +01:00
Vadim Yanitskiy
e3c788d9a8 SS/USSD: update configuretion example
Change-Id: I3801968d29bae917bf8669bb4ac03a9c4f3cb96c
2019-12-25 19:09:45 +01:00
Vadim Yanitskiy
e6751dd207 SS/USSD: add IUSEs to enable / disable UMTS
Change-Id: Icf85ec8dd71813a9bbf359b9011456844f398337
2019-12-25 19:09:45 +01:00
Vadim Yanitskiy
e8b7b8634b SS/USSD: implement an IUSE for getting RAN type(s)
Change-Id: I8d95413784b039df50a4cdcac49289060f0414c6
2019-12-25 19:09:45 +01:00
Neels Hofmeyr
ce172efc0b Add DB storage for enabling / disabling arbitrary RAT types.
So far we have only GERAN-A and UTRAN-Iu, but to be future compatible,
implement an arbitrary length list of RAT types: add DB table subscriber_rat.

Backwards compatibility: if there is no entry in subscriber_rat, all RAT types
shall be allowed. As soon as there is an entry, it can either be false to
forbid a RAT or true to still allow a RAT type.

Depends: I93850710ab55a605bf61b95063a69682a2899bb1 (libosmocore)
Change-Id: I3e399ca8a85421f77a9a15e608413d1507722955
2019-12-25 19:09:45 +01:00
Neels Hofmeyr
2f1049827b trial: SMS-over-GSUP 2019-12-23 16:52:24 +01:00
Neels Hofmeyr
92d1c4a0de db v6: determine 3G AUC IND from VLR name
Each VLR requesting auth tuples should use a distinct IND pool for 3G auth.  So
far we tied the IND to the GSUP peer connection; MSC and SGSN were always
distinct GSUP peers, they ended up using distinct INDs.

However, we have implemented a GSUP proxy, so that, in a distributed setup, a
remotely roaming subscriber has only one direct GSUP peer proxying for both
remote MSC and SGSN. That means as soon as a subscriber roams to a different
site, we would use the GSUP proxy name to determine the IND instead of the
separate MSC and SGSN. The site's MSC and SGSN appear as the same client, get
the same IND bucket, waste SQNs rapidly and cause auth tuple generation load.

So instead of using the local client as IND, persistently keep a list of VLR
names and assign a different IND to each. Use the gsup_req->source_name as
indicator, which reflects the actual remote VLR's name (remote MSC or SGSN).

Persist the site <-> IND assignments in the database.

Add an IND test to db_test.c

There was an earlier patch version that separated the IND pools by cn_domain,
but it turned out to add complex semantics, while only solving one aspect of
the "adjacent VLR" problem. We need a solution not only for CS vs PS, but also
for 2,3G vs 4G, and for sites that are physically adjacent to each other. This
patch version does not offer any automatic solution for that -- as soon as more
than 2^IND_bitlen (usually 32) VLRs show up, it is the responsibility of the
admin to ensure the 'ind' table in the hlr.db does not have unfortunate IND
assignments. So far no VTY commands exist for that, they may be added in the
future.

Related: OS#4319
Change-Id: I6f0a6bbef3a27507605c3b4a0e1a89bdfd468374
2019-12-23 16:52:08 +01:00
Neels Hofmeyr
fc4af602ba 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
2019-12-16 17:19:16 +01:00
Neels Hofmeyr
011e781da7 vty: show subscriber: show lu d,h,m,s ago, not just seconds
Change-Id: I0fe34e0f065160ef959b2b7b4dd040f3f2985f43
2019-12-16 17:17:58 +01:00
Neels Hofmeyr
cb88c34aca 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
2019-12-16 17:17:58 +01:00
Neels Hofmeyr
e18e3a42f1 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
2019-12-16 17:17:58 +01:00
Neels Hofmeyr
710e0c9486 adoc: add D-GSM chapter to osmohlr-usermanual
Change-Id: I392b5523870c2ef3267179160028d26f3f761b77
2019-12-16 17:17:58 +01:00
Oliver Smith
f6d457e7ef 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
2019-12-16 17:17:58 +01:00
Neels Hofmeyr
fa287c579c 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
2019-12-16 17:17:58 +01:00
Neels Hofmeyr
e78241ae07 proxy routing refactor
Change-Id: I43ad27f6d768df02abb3459ac4c43bb80cc1cbeb
2019-12-16 17:17:58 +01:00
Neels Hofmeyr
62d916f3cd 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
2019-12-16 16:56:33 +01:00
Neels Hofmeyr
3ad481afbf 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
2019-12-16 16:21:53 +01:00
Neels Hofmeyr
6aa871db71 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
2019-12-16 16:21:53 +01:00
Neels Hofmeyr
16a16e6aa4 test_nodes.vty: remove cruft
This stuff is not testing osmo-hlr specific nodes, remove.

Change-Id: Ia11a209778b78ab02424e2abf3f9004fe97cf570
2019-12-16 16:21:53 +01:00
Neels Hofmeyr
fac52fbd6b enlarge the GSUP message headroom
Make room for (more) arbitrary IPA headers, like longer IPA names as configured
by the user.

Change-Id: I7d86f2dadcae29fe1550ea2c9773394ab31a837b
2019-12-16 16:21:53 +01:00
Neels Hofmeyr
f0c02ad9c7 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
2019-12-16 16:21:53 +01:00
Neels Hofmeyr
43c36f99dd 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() pass the up_down_cb in the arguments. Also add
a cb data argument.

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
2019-12-16 16:21:53 +01:00
Neels Hofmeyr
008ce4bd43 2/2: fixup: add osmo_gsup_peer_id with type enum and union
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.

Add this, but only in the publicly visible API. For osmo-hlr internal code, I
intend to push 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 API users aware that this type may be extended in
the future.

Change-Id: Ide9dcdca283ab989240cfc6e53e9211862a199c5
2019-12-16 16:21:53 +01:00
Neels Hofmeyr
f13a8bc4f9 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.

** gsup_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 gsup_peer_id.h and gsup_peer_id.c. Heavily
used in LU and osmo_gsup_req.

Depends: libosmocore Id9692880079ea0f219f52d81b1923a76fc640566
Change-Id: I3a8dff3d4a1cbe10d6ab08257a0138d6b2a082d9
2019-12-16 16:21:53 +01:00
Oliver Smith
50bf7b775b 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
2019-12-16 16:21:53 +01:00
Neels Hofmeyr
cb508554df 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
2019-12-16 16:21:53 +01:00
Oliver Smith
06455eac9c 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
2019-12-16 16:21:53 +01:00
Oliver Smith
2bdcc8eec9 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
2019-12-16 16:21:53 +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
132 changed files with 11131 additions and 2094 deletions

6
.gitignore vendored
View File

@@ -36,6 +36,7 @@ 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
@@ -50,6 +51,11 @@ 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

View File

@@ -38,7 +38,6 @@ PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.2.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.2.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.2.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.2.0)
PKG_CHECK_MODULES(LIBOSMOMSLOOKUP, libosmomslookup >= 1.2.0)
PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.6.0)
PKG_CHECK_MODULES(SQLITE3, sqlite3)
@@ -175,17 +174,24 @@ 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
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_manual_test/Makefile
tests/mslookup/Makefile
)

View File

@@ -1 +1,4 @@
SUBDIRS = systemd
SUBDIRS = \
systemd \
dgsm \
$(NULL)

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)

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

@@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""
SPDX-License-Identifier: AGPL-3.0-or-later
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:
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. Is OsmoMSC configured properly?')
return False
# Multipart SMS etc. not handled here (see RCCN's esme.py)
if pdu.esm_class & smpplib.consts.SMPP_GSMFEAT_UDHI:
logging.info("UDH (User Data Header) handling not implemented in this"
" example, dropping message.")
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, 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=False,
)
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")
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, 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)')
args = parser.parse_args()
logging.basicConfig(level=logging.INFO, format='[%(asctime)s]'
' (%(threadName)s) %(message)s', datefmt="%H:%M:%S")
smpp_bind()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""
SPDX-License-Identifier: AGPL-3.0-or-later
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

@@ -5,7 +5,7 @@ Documentation=https://osmocom.org/projects/osmo-hlr/wiki/OsmoHLR
[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/osmo-hlr -c /etc/osmocom/osmo-hlr.cfg -l /var/lib/osmocom/hlr.db
ExecStart=/usr/bin/osmo-hlr -U -c /etc/osmocom/osmo-hlr.cfg -l /var/lib/osmocom/hlr.db
RestartSec=2
[Install]

24
debian/control vendored
View File

@@ -7,7 +7,7 @@ Build-Depends: debhelper (>= 9),
dh-autoreconf,
dh-systemd (>= 1.5),
autotools-dev,
python-minimal,
python3-minimal,
libosmocore-dev,
libosmo-abis-dev,
libosmo-netif-dev,
@@ -59,6 +59,28 @@ Description: Development headers of Osmocom GSUP client library
.
This package contains the development headers.
Package: libosmo-mslookup0
Section: libs
Architecture: any
Multi-Arch: same
Depends: ${shlibs:Depends},
${misc:Depends}
Pre-Depends: ${misc:Pre-Depends}
Description: Osmocom MS lookup library
This shared library contains routines for looking up mobile subscribers.
Package: libosmo-mslookup-dev
Architecture: any
Multi-Arch: same
Depends: ${misc:Depends},
libosmo-mslookup0 (= ${binary:Version}),
libosmocore-dev
Pre-Depends: ${misc:Pre-Depends}
Description: Development headers of Osmocom MS lookup library
This shared library contains routines for looking up mobile subscribers.
.
This package contains the development headers.
Package: osmo-hlr-doc
Architecture: all
Section: doc

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.*

View File

@@ -24,3 +24,10 @@ hlr
bind ip 127.0.0.1
ussd route prefix *#100# internal own-msisdn
ussd route prefix *#101# internal own-imsi
ussd route prefix *#102# internal get-ran
ussd route prefix *#200# internal gsm-off
ussd route prefix *#201# internal gsm-on
ussd route prefix *#300# internal umts-off
ussd route prefix *#301# internal umts-on
ussd route prefix *#400# internal lte-off
ussd route prefix *#401# internal lte-on

View File

@@ -0,0 +1,481 @@
== 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
==== 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" would be the IP address and port on which the local site's PBX is bound to receive SIP
Invite requests; "10.9.8.7 2775" would be 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. If, for example, the PBX is
configured to bind on "0.0.0.0", it won't work to enter the same as service address -- remote sites cannot route to
0.0.0.0. Instead, the mslookup service requires a public IP address of a local interface. For the same reasons, never
add link-local addresses like 127.0.0.1 as mslookup services.
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 msc-262-42-0
service sip.voice at 10.11.12.13 5060
service smpp.sms at 10.11.12.13 2775
msc 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 | SIP PBX or 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

@@ -24,6 +24,8 @@ include::{srcdir}/chapters/control.adoc[]
include::./common/chapters/control_if.adoc[]
include::{srcdir}/chapters/dgsm.adoc[]
include::./common/chapters/gsup.adoc[]
include::./common/chapters/port_numbers.adoc[]

View File

@@ -1,2 +1,13 @@
nobase_include_HEADERS = osmocom/gsupclient/gsup_client.h
SUBDIRS = osmocom
nobase_include_HEADERS = \
osmocom/gsupclient/gsup_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

@@ -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
@@ -60,14 +59,27 @@ struct osmo_gsup_client {
osmo_gsup_client_up_down_cb_t up_down_cb;
};
struct osmo_gsup_client *osmo_gsup_client_create3(void *talloc_ctx,
struct ipaccess_unit *ipa_dev,
const char *ip_addr,
unsigned int tcp_port,
struct osmo_oap_client_config *oapc_config,
osmo_gsup_client_read_cb_t read_cb,
osmo_gsup_client_up_down_cb_t up_down_cb,
void *data);
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,64 @@
/* 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(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(const struct osmo_ipa_name *ipa_name);
enum osmo_gsup_peer_id_type {
OSMO_GSUP_PEER_ID_EMPTY=0,
OSMO_GSUP_PEER_ID_IPA_NAME,
/* OSMO_GSUP_PEER_ID_GLOBAL_TITLE, <-- currently not implemented, but likely future possibility */
};
extern const struct value_string osmo_gsup_peer_id_type_names[];
static inline const char *osmo_gsup_peer_id_type_name(enum osmo_gsup_peer_id_type val)
{ return get_value_string(osmo_gsup_peer_id_type_names, val); }
struct osmo_gsup_peer_id {
enum osmo_gsup_peer_id_type type;
union {
struct osmo_ipa_name ipa_name;
};
};
bool osmo_gsup_peer_id_is_empty(struct osmo_gsup_peer_id *gsup_peer_id);
int osmo_gsup_peer_id_set(struct osmo_gsup_peer_id *gsup_peer_id, enum osmo_gsup_peer_id_type type,
const uint8_t *val, size_t len);
int osmo_gsup_peer_id_set_str(struct osmo_gsup_peer_id *gsup_peer_id, enum osmo_gsup_peer_id_type type,
const char *str_fmt, ...);
int osmo_gsup_peer_id_cmp(const struct osmo_gsup_peer_id *a, const struct osmo_gsup_peer_id *b);
const char *osmo_gsup_peer_id_to_str(const struct osmo_gsup_peer_id *gsup_peer_id);

View File

@@ -0,0 +1,115 @@
/* 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/gsup_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_gsup_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_gsup_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_gsup_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_gsup_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);
/*! Call _osmo_gsup_req_respond() to convey the sender's source file and line in the logs. */
#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);
/*! Call _osmo_gsup_req_respond_msgt() to convey the sender's source file and line in the logs. */
#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);
/*! Log an error message, and call _osmo_gsup_req_respond() to convey the sender's source file and line in the logs. */
#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,21 @@
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 \
sms_over_gsup.h \
timestamp.h \
$(NULL)

View File

@@ -3,7 +3,9 @@
#include <stdbool.h>
#include <sqlite3.h>
#include "global_title.h"
#include <osmocom/gsupclient/gsup_peer_id.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/gsm/gsm_utils.h>
struct hlr;
@@ -30,12 +32,14 @@ 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,
#if 0
DB_STMT_PROXY_UPDATE,
DB_STMT_PROXY_GET_BY_IMSI,
#endif
DB_STMT_IND_ADD,
DB_STMT_IND_SELECT,
DB_STMT_IND_DEL,
DB_STMT_UPD_RAT_FLAG,
DB_STMT_RAT_BY_ID,
_NUM_DB_STMT
};
@@ -72,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>
@@ -102,10 +106,19 @@ struct hlr_subscriber {
bool ms_purged_cs;
bool ms_purged_ps;
time_t last_lu_seen;
time_t last_lu_seen_ps;
char last_lu_rat_cs[128];
char last_lu_rat_ps[128];
/* talloc'd IPA unit name */
struct global_title vlr_via_proxy;
struct osmo_ipa_name vlr_via_proxy;
struct osmo_ipa_name sgsn_via_proxy;
bool rat_types[OSMO_RAT_COUNT];
};
static const struct hlr_subscriber hlr_subscriber_empty = {
.rat_types = { true, true, true },
};
/* A format string for use with strptime(3). This format string is
* used to parse the last_lu_seen column stored in the HLR database.
* See https://sqlite.org/lang_datefunc.html, function datetime(). */
@@ -158,12 +171,20 @@ int db_subscr_get_by_id(struct db_context *dbc, int64_t id,
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 struct global_title *vlr_name, bool is_ps,
const struct global_title *via_proxy);
const struct osmo_ipa_name *vlr_name, bool is_ps,
const struct osmo_ipa_name *via_proxy,
const enum osmo_rat_type rat_types[], size_t rat_types_len);
int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
bool purge_val, bool is_ps);
int db_ind(struct db_context *dbc, const struct osmo_gsup_peer_id *vlr, unsigned int *ind);
int db_ind_del(struct db_context *dbc, const struct osmo_gsup_peer_id *vlr);
int db_subscr_set_rat_type_flag(struct db_context *dbc, int64_t subscr_id, enum osmo_rat_type rat, bool allowed);
int db_subscr_get_rat_types(struct db_context *dbc, struct hlr_subscriber *subscr);
int hlr_subscr_rat_flag(struct hlr *hlr, struct hlr_subscriber *subscr, enum osmo_rat_type rat, bool allowed);
/*! 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*.
@@ -175,13 +196,13 @@ int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
osmo_strlcpy(buf, _txt, sizeof(buf)); \
} while (0)
/*! Call sqlite3_column_text() and copy result to a struct global_title.
* \param[out] gt A struct global_title* to write to.
/*! 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_gt(gt, stmt, idx) \
#define copy_sqlite3_text_to_ipa_name(ipa_name, stmt, idx) \
do { \
const char *_txt = (const char *) sqlite3_column_text(stmt, idx); \
global_title_set_str(gt, _txt); \
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/gsup_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

@@ -1,8 +1,9 @@
#pragma once
#include <stdint.h>
#include "global_title.h"
#include "gsup_server.h"
#include <osmocom/hlr/gsup_server.h>
struct osmo_ipa_name;
struct gsup_route {
struct llist_head list;
@@ -13,12 +14,12 @@ struct gsup_route {
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_gt(struct osmo_gsup_server *gs, const struct global_title *gt);
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_gt(struct osmo_gsup_conn *conn, const struct global_title *gt);
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 */
@@ -27,6 +28,6 @@ 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_gt_send(struct osmo_gsup_server *gs, const struct global_title *gt, struct msgb *msg);
int osmo_gsup_gt_enc_send(struct osmo_gsup_server *gs, const struct global_title *gt,
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

@@ -0,0 +1,78 @@
#pragma once
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/msgb.h>
#include <osmocom/abis/ipa.h>
#include <osmocom/abis/ipaccess.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/gsupclient/gsup_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 */
#endif
struct osmo_gsup_conn;
/* Expects message in msg->l2h */
typedef int (*osmo_gsup_read_cb_t)(struct osmo_gsup_conn *conn, struct msgb *msg);
struct osmo_gsup_server {
/* private data of the application/user */
void *priv;
/* list of osmo_gsup_conn */
struct llist_head clients;
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;
};
/* a single connection to a given client (SGSN, MSC) */
struct osmo_gsup_conn {
struct llist_head list;
struct osmo_gsup_server *server;
struct ipa_server_conn *conn;
//struct oap_state oap_state;
struct tlv_parsed ccm;
unsigned int auc_3g_ind; /*!< IND index used for UMTS AKA SQN */
/* 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);
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,
void *priv);
void osmo_gsup_server_destroy(struct osmo_gsup_server *gsups);
int osmo_gsup_configure_wildcard_apn(struct osmo_gsup_message *gsup,
uint8_t *apn_buf, size_t apn_buf_size);
int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup, const char *imsi, const char *msisdn,
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_gsup_peer_id *to_peer,
struct osmo_gsup_req *req, struct osmo_gsup_message *modified_gsup);

View File

@@ -26,7 +26,9 @@
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/ipa.h>
#include <osmocom/core/tdef.h>
#include "dgsm.h"
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/hlr/dgsm.h>
#define HLR_DEFAULT_DB_FILE_PATH "hlr.db"
@@ -72,21 +74,52 @@ struct hlr {
struct {
bool allow_startup;
struct dgsm_config vty;
struct {
struct osmo_mslookup_server_mdns *mdns;
uint32_t max_age;
/* 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 osmo_mslookup_client_method *mdns;
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;
struct proxy *proxy;
struct {
/* FIXME actually use branch fixeria/sms for SMSC routing. completely unimplemented */
struct osmo_gsup_peer_id smsc;
/* If no SMSC is present / responsible, try punching the SMS through directly when this is true. */
bool try_direct_delivery;
} sms_over_gsup;
};
extern struct hlr *g_hlr;

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

View File

@@ -25,7 +25,7 @@
#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,
@@ -40,3 +40,4 @@ enum hlr_vty_node {
int hlr_vty_is_config_node(struct vty *vty, int node);
int hlr_vty_go_parent(struct vty *vty);
void hlr_vty_init(void);
void dgsm_vty_init(void);

View File

@@ -8,8 +8,9 @@ enum {
DGSUP,
DAUC,
DSS,
DDGSM,
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/gsup_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/gsup_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,8 @@
#pragma once
#include <stdbool.h>
#include <osmocom/gsupclient/gsup_req.h>
#define OSMO_MSLOOKUP_SERVICE_SMS_GSUP "gsup.sms"
bool sms_over_gsup_check_handle_msg(struct osmo_gsup_req *req);

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

@@ -43,6 +43,14 @@ 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_ps TIMESTAMP default NULL,
-- Last Radio Access Type list as sent during Location Updating Request.
-- This is usually just one RAT name, but can be a comma separated list of strings
-- of all the RAT types sent during Location Updating Request.
last_lu_rat_cs TEXT default NULL,
last_lu_rat_ps TEXT 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,
@@ -77,8 +85,26 @@ CREATE TABLE auc_3g (
ind_bitlen INTEGER NOT NULL DEFAULT 5
);
CREATE TABLE ind (
-- 3G auth IND pool to be used for this VLR
ind INTEGER PRIMARY KEY,
-- VLR identification, usually the GSUP source_name
vlr TEXT NOT NULL,
UNIQUE (vlr)
);
-- Optional: add subscriber entries to allow or disallow specific RATs (2G or 3G or ...).
-- If a subscriber has no entry, that means that all RATs are allowed (backwards compat).
CREATE TABLE subscriber_rat (
subscriber_id INTEGER, -- subscriber.id
rat TEXT CHECK(rat in ('GERAN-A', 'UTRAN-Iu','EUTRAN-SGs')) NOT NULL, -- Radio Access Technology, see enum ran_type
allowed BOOLEAN CHECK(allowed in (0, 1)) NOT NULL DEFAULT 0,
UNIQUE (subscriber_id, rat)
);
CREATE UNIQUE INDEX idx_subscr_imsi ON subscriber (imsi);
CREATE UNIQUE INDEX idx_subscr_rat_flag ON subscriber_rat (subscriber_id, rat);
-- 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 = 4;
PRAGMA user_version = 8;

8
sql/upgrade_v2_to_v3.sql Normal file
View File

@@ -0,0 +1,8 @@
CREATE TABLE subscriber_rat (
subscriber_id INTEGER, -- subscriber.id
rat TEXT CHECK(rat in ('GERAN-A', 'UTRAN-Iu')) NOT NULL, -- Radio Access Technology, see enum ran_type
allowed BOOLEAN NOT NULL DEFAULT 0,
);
PRAGMA user_version = 3;

View File

@@ -1,4 +1,7 @@
SUBDIRS = gsupclient
SUBDIRS = \
gsupclient \
mslookup \
$(NULL)
AM_CFLAGS = \
-Wall \
@@ -12,6 +15,7 @@ AM_CFLAGS = \
$(NULL)
AM_CPPFLAGS = -I$(top_srcdir)/include \
-I$(top_builddir)/include \
$(NULL)
EXTRA_DIST = \
@@ -25,25 +29,7 @@ BUILT_SOURCES = \
CLEANFILES = $(BUILT_SOURCES)
noinst_HEADERS = \
auc.h \
db.h \
hlr.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 \
proxy.h \
dgsm.h \
remote_hlr.h \
global_title.h \
mslookup_server.h \
mslookup_server_mdns.h \
lu_fsm.h \
$(NULL)
bin_PROGRAMS = \
@@ -69,16 +55,18 @@ osmo_hlr_SOURCES = \
hlr_ussd.c \
proxy.c \
dgsm.c \
dgsm_vty.c \
remote_hlr.c \
lu_fsm.c \
timestamp.c \
mslookup_server.c \
mslookup_server_mdns.c \
global_title.c \
lu_fsm.c \
dgsm_vty.c \
sms_over_gsup.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) \
@@ -95,7 +83,7 @@ osmo_hlr_db_tool_SOURCES = \
logging.c \
rand_urandom.c \
dbd_decode_binary.c \
global_title.c \
$(srcdir)/gsupclient/gsup_peer_id.c \
$(NULL)
osmo_hlr_db_tool_LDADD = \

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

127
src/db.c
View File

@@ -24,12 +24,13 @@
#include <string.h>
#include <errno.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 4
#define CURRENT_SCHEMA_VERSION 8
#define SEL_COLUMNS \
"id," \
@@ -47,6 +48,9 @@
"ms_purged_cs," \
"ms_purged_ps," \
"last_lu_seen," \
"last_lu_seen_ps," \
"last_lu_rat_cs," \
"last_lu_rat_ps," \
"vlr_via_proxy," \
"sgsn_via_proxy"
@@ -81,16 +85,23 @@ static const char *stmt_sql[] = {
"INSERT INTO auc_3g (subscriber_id, algo_id_3g, k, op, opc, ind_bitlen)"
" 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] = "UPDATE subscriber SET last_lu_seen = datetime($val, 'unixepoch'),"
" last_lu_rat_cs = $rat"
" WHERE id = $subscriber_id",
[DB_STMT_SET_LAST_LU_SEEN_PS] = "UPDATE subscriber SET last_lu_seen_ps = datetime($val, 'unixepoch'),"
" last_lu_rat_ps = $rat"
" 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",
#if 0
[DB_STMT_PROXY_UPDATE] = "INSERT OR REPLACE INTO"
" proxy (imsi, remote_ip, remote_port)"
" VALUES ($imsi, $remote_ip, $remote_port)",
[DB_STMT_PROXY_GET_BY_IMSI] = "SELECT imsi, remote_ip, remote_port FROM proxy WHERE imsi = $imsi",
#endif
[DB_STMT_IND_ADD] = "INSERT INTO ind (vlr) VALUES ($vlr)",
[DB_STMT_IND_SELECT] = "SELECT ind FROM ind WHERE vlr = $vlr",
[DB_STMT_IND_DEL] = "DELETE FROM ind WHERE vlr = $vlr",
[DB_STMT_UPD_RAT_FLAG] = "INSERT OR REPLACE INTO subscriber_rat (subscriber_id, rat, allowed)"
" VALUES ($subscriber_id, $rat, $allowed)",
[DB_STMT_RAT_BY_ID] =
"SELECT rat, allowed"
" FROM subscriber_rat"
" WHERE subscriber_id = $subscriber_id",
};
static void sql3_error_log_cb(void *arg, int err_code, const char *msg)
@@ -336,7 +347,7 @@ 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'.
/* A newer SQLite version would allow simply 'ALTER 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).
*/
@@ -456,8 +467,7 @@ static int db_upgrade_v4(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",
"ALTER TABLE subscriber ADD COLUMN last_lu_seen_ps TIMESTAMP default NULL",
"PRAGMA user_version = 4",
};
@@ -469,12 +479,97 @@ static int db_upgrade_v4(struct db_context *dbc)
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;
}
static int db_upgrade_v6(struct db_context *dbc)
{
int rc;
const char *statements[] = {
"CREATE TABLE ind (\n"
" -- 3G auth IND pool to be used for this VLR\n"
" ind INTEGER PRIMARY KEY,\n"
" -- VLR identification, usually the GSUP source_name\n"
" vlr TEXT NOT NULL,\n"
" UNIQUE (vlr)\n"
")"
,
"PRAGMA user_version = 6",
};
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 6\n");
return rc;
}
return rc;
}
static int db_upgrade_v7(struct db_context *dbc)
{
int rc;
const char *statements[] = {
"-- Optional: add subscriber entries to allow or disallow specific RATs (2G or 3G or ...).\n"
"-- If a subscriber has no entry, that means that all RATs are allowed (backwards compat).\n"
"CREATE TABLE subscriber_rat (\n"
" subscriber_id INTEGER, -- subscriber.id\n"
" rat TEXT CHECK(rat in ('GERAN-A', 'UTRAN-Iu')) NOT NULL, -- Radio Access Technology, see enum ran_type\n"
" allowed BOOLEAN CHECK(allowed in (0, 1)) NOT NULL DEFAULT 0,\n"
" UNIQUE (subscriber_id, rat)\n"
")",
"CREATE UNIQUE INDEX idx_subscr_rat_flag ON subscriber_rat (subscriber_id, rat)",
"PRAGMA user_version = 7",
};
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 7\n");
return rc;
}
return rc;
}
static int db_upgrade_v8(struct db_context *dbc)
{
int rc;
const char *statements[] = {
"ALTER TABLE subscriber ADD COLUMN last_lu_rat_cs TEXT default NULL",
"ALTER TABLE subscriber ADD COLUMN last_lu_rat_ps TEXT default NULL",
"PRAGMA user_version = 8",
};
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 7\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,
db_upgrade_v6,
db_upgrade_v7,
db_upgrade_v8,
};
static int db_get_user_version(struct db_context *dbc)
@@ -603,9 +698,9 @@ struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite_logg
if (version < CURRENT_SCHEMA_VERSION) {
LOGP(DDB, LOGL_NOTICE, "HLR DB schema version %d is outdated\n", version);
if (!allow_upgrade) {
LOGP(DDB, LOGL_ERROR, "Not upgrading HLR database to schema version %d; "
LOGP(DDB, LOGL_ERROR, "Not upgrading HLR database from schema version %d to %d; "
"use the --db-upgrade option to allow HLR database upgrades\n",
CURRENT_SCHEMA_VERSION);
version, CURRENT_SCHEMA_VERSION);
}
} else
LOGP(DDB, LOGL_ERROR, "HLR DB schema version %d is unknown\n", 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)
@@ -189,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;
@@ -200,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);

View File

@@ -28,15 +28,16 @@
#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 <osmocom/hlr/logging.h>
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/db.h>
#include <osmocom/gsupclient/gsup_peer_id.h>
#define LOGHLR(imsi, level, fmt, args ...) LOGP(DAUC, level, "IMSI='%s': " fmt, imsi, ## args)
@@ -437,14 +438,36 @@ out:
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 = {0};
/* execute the statement */
rc = sqlite3_step(stmt);
@@ -460,7 +483,7 @@ static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscri
if (!subscr)
goto out;
*subscr = (struct hlr_subscriber){};
*subscr = hlr_subscriber_empty;
/* obtain the various columns */
subscr->id = sqlite3_column_int64(stmt, 0);
@@ -478,25 +501,21 @@ static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscri
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);
last_lu_seen_str = (const char *)sqlite3_column_text(stmt, 14);
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_gt(&subscr->vlr_via_proxy, stmt, 15);
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_buf(subscr->last_lu_rat_cs, stmt, 16);
copy_sqlite3_text_to_buf(subscr->last_lu_rat_ps, stmt, 17);
copy_sqlite3_text_to_ipa_name(&subscr->vlr_via_proxy, stmt, 18);
copy_sqlite3_text_to_ipa_name(&subscr->sgsn_via_proxy, stmt, 19);
out:
db_remove_reset(stmt);
if (ret == 0)
db_subscr_get_rat_types(dbc, subscr);
switch (ret) {
case 0:
*err = NULL;
@@ -554,7 +573,7 @@ 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;
@@ -605,7 +624,7 @@ 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;
@@ -629,7 +648,7 @@ 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;
@@ -652,7 +671,7 @@ int db_subscr_get_by_imei(struct db_context *dbc, const char *imei, struct hlr_s
return -EIO;
rc = db_sel(dbc, stmt, subscr, &err);
if (rc)
if (rc && rc != -ENOENT)
LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: IMEI=%s: %s\n", imei, err);
return rc;
}
@@ -722,12 +741,15 @@ out:
* -EIO on database errors.
*/
int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
const struct global_title *vlr_name, bool is_ps,
const struct global_title *via_proxy)
const struct osmo_ipa_name *vlr_name, bool is_ps,
const struct osmo_ipa_name *via_proxy,
const enum osmo_rat_type rat_types[], size_t rat_types_len)
{
sqlite3_stmt *stmt;
int rc, ret = 0;
struct timespec localtime;
char rat_types_str[128] = "";
int i;
stmt = dbc->stmt[is_ps ? DB_STMT_UPD_SGSN_BY_ID
: DB_STMT_UPD_VLR_BY_ID];
@@ -779,7 +801,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;
@@ -789,6 +811,21 @@ int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
goto out;
}
for (i = 0; i < rat_types_len; i++) {
char *pos = rat_types_str + strnlen(rat_types_str, sizeof(rat_types_str));
int len = sizeof(rat_types_str) - (pos - rat_types_str);
rc = snprintf(pos, len, "%s%s", pos == rat_types_str ? "" : ",", osmo_rat_type_name(rat_types[i]));
if (rc > len) {
osmo_strlcpy(rat_types_str + sizeof(rat_types_str) - 4, "...", 4);
break;
}
}
if (!db_bind_text(stmt, "$rat", rat_types_str)) {
ret = -EIO;
goto out;
}
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGP(DAUC, LOGL_ERROR,
@@ -870,3 +907,222 @@ out:
return ret;
}
static int _db_ind_run(struct db_context *dbc, sqlite3_stmt *stmt, const char *vlr, bool reset)
{
int rc;
if (!db_bind_text(stmt, "$vlr", vlr))
return -EIO;
/* execute the statement */
rc = sqlite3_step(stmt);
if (reset)
db_remove_reset(stmt);
return rc;
}
static int _db_ind_add(struct db_context *dbc, const char *vlr)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_IND_ADD];
if (_db_ind_run(dbc, stmt, vlr, true) != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Cannot create IND entry for %s\n", osmo_quote_str_c(OTC_SELECT, vlr, -1));
return -EIO;
}
return 0;
}
static int _db_ind_del(struct db_context *dbc, const char *vlr)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_IND_DEL];
_db_ind_run(dbc, stmt, vlr, true);
/* We don't really care about the result. If it didn't exist, then that was the goal anyway. */
return 0;
}
static int _db_ind_get(struct db_context *dbc, const char *vlr, unsigned int *ind)
{
int ret = 0;
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_IND_SELECT];
int rc = _db_ind_run(dbc, stmt, vlr, false);
if (rc == SQLITE_DONE) {
/* Does not exist yet */
ret = -ENOENT;
goto out;
} else if (rc != SQLITE_ROW) {
LOGP(DDB, LOGL_ERROR, "Error executing SQL: %d\n", rc);
ret = -EIO;
goto out;
}
OSMO_ASSERT(ind);
*ind = sqlite3_column_int64(stmt, 0);
out:
db_remove_reset(stmt);
return ret;
}
int _db_ind(struct db_context *dbc, const struct osmo_gsup_peer_id *vlr,
unsigned int *ind, bool del)
{
const char *vlr_name = NULL;
int rc;
switch (vlr->type) {
case OSMO_GSUP_PEER_ID_IPA_NAME:
if (vlr->ipa_name.len < 2 || vlr->ipa_name.val[vlr->ipa_name.len - 1] != '\0') {
LOGP(DDB, LOGL_ERROR, "Expecting VLR ipa_name to be zero terminated; found %s\n",
osmo_ipa_name_to_str(&vlr->ipa_name));
return -ENOTSUP;
}
vlr_name = (const char*)vlr->ipa_name.val;
break;
default:
LOGP(DDB, LOGL_ERROR, "Unsupported osmo_gsup_peer_id type: %s\n",
osmo_gsup_peer_id_type_name(vlr->type));
return -ENOTSUP;
}
if (del)
return _db_ind_del(dbc, vlr_name);
rc = _db_ind_get(dbc, vlr_name, ind);
if (!rc)
return 0;
/* Does not exist yet, create. */
rc = _db_ind_add(dbc, vlr_name);
if (rc) {
LOGP(DDB, LOGL_ERROR, "Error creating IND entry for %s\n", osmo_quote_str_c(OTC_SELECT, vlr_name, -1));
return rc;
}
/* To be sure, query again from scratch. */
return _db_ind_get(dbc, vlr_name, ind);
}
int db_ind(struct db_context *dbc, const struct osmo_gsup_peer_id *vlr, unsigned int *ind)
{
return _db_ind(dbc, vlr, ind, false);
}
int db_ind_del(struct db_context *dbc, const struct osmo_gsup_peer_id *vlr)
{
return _db_ind(dbc, vlr, NULL, true);
}
int db_subscr_set_rat_type_flag(struct db_context *dbc, int64_t subscr_id, enum osmo_rat_type rat, bool allowed)
{
int rc;
int ret = 0;
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_UPD_RAT_FLAG];
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
return -EIO;
OSMO_ASSERT(rat >= 0 && rat < OSMO_RAT_COUNT);
if (!db_bind_text(stmt, "$rat", osmo_rat_type_name(rat)))
return -EIO;
if (!db_bind_int(stmt, "$allowed", allowed ? 1 : 0))
return -EIO;
/* execute the statement */
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "%s %s: SQL error: %s\n",
allowed ? "enable" : "disable", osmo_rat_type_name(rat),
sqlite3_errmsg(dbc->db));
ret = -EIO;
goto out;
}
/* verify execution result */
rc = sqlite3_changes(dbc->db);
if (!rc) {
LOGP(DDB, LOGL_ERROR, "Cannot %s %s: no such subscriber: ID=%" PRIu64 "\n",
allowed ? "enable" : "disable", osmo_rat_type_name(rat),
subscr_id);
ret = -ENOENT;
goto out;
} else if (rc != 1) {
LOGP(DDB, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
allowed ? "enable" : "disable", osmo_rat_type_name(rat),
rc);
ret = -EIO;
}
out:
db_remove_reset(stmt);
return ret;
}
int db_subscr_get_rat_types(struct db_context *dbc, struct hlr_subscriber *subscr)
{
int rc;
int ret = 0;
int i;
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_RAT_BY_ID];
if (!db_bind_int64(stmt, "$subscriber_id", subscr->id))
return -EIO;
for (i = 0; i < OSMO_RAT_COUNT; i++)
subscr->rat_types[i] = true;
/* execute the statement */
while (1) {
enum osmo_rat_type rat;
bool allowed;
rc = sqlite3_step(stmt);
if (rc == SQLITE_DONE)
break;
if (rc != SQLITE_ROW)
return -rc;
rc = get_string_value(osmo_rat_type_names, (const char*)sqlite3_column_text(stmt, 0));
if (rc == -EINVAL) {
ret = -EINVAL;
goto out;
}
if (rc <= 0 || rc >= OSMO_RAT_COUNT) {
ret = -EINVAL;
goto out;
}
rat = rc;
allowed = sqlite3_column_int(stmt, 1);
subscr->rat_types[rat] = allowed;
LOGP(DAUC, LOGL_DEBUG, "db: imsi='%s' %s %s\n",
subscr->imsi, osmo_rat_type_name(rat), allowed ? "allowed" : "forbidden");
}
out:
db_remove_reset(stmt);
return ret;
}
int hlr_subscr_rat_flag(struct hlr *hlr, struct hlr_subscriber *subscr, enum osmo_rat_type rat, bool allowed)
{
int rc;
OSMO_ASSERT(rat >= 0 && rat < OSMO_RAT_COUNT);
db_subscr_get_rat_types(hlr->dbc, subscr);
if (subscr->rat_types[rat] == allowed) {
LOGHLR(subscr->imsi, LOGL_DEBUG, "Already has the requested value when asked to %s %s\n",
allowed ? "enable" : "disable", osmo_rat_type_name(rat));
return -ENOEXEC;
}
rc = db_subscr_set_rat_type_flag(hlr->dbc, subscr->id, rat, allowed);
if (rc)
return rc > 0? -rc : rc;
/* FIXME: If we're disabling, send message to VLR to detach subscriber */
return 0;
}

View File

@@ -1,266 +1,50 @@
/* 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 "logging.h"
#include "hlr.h"
#include "db.h"
#include "gsup_router.h"
#include "gsup_server.h"
#include "dgsm.h"
#include "proxy.h"
#include "remote_hlr.h"
#include "mslookup_server_mdns.h"
#include "global_title.h"
#include <osmocom/gsupclient/gsup_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;
const struct global_title dgsm_config_msc_wildcard = {};
struct dgsm_msc_config *dgsm_config_msc_get(const struct global_title *msc_name, bool create)
{
struct dgsm_msc_config *msc;
if (!msc_name)
return NULL;
llist_for_each_entry(msc, &g_hlr->mslookup.vty.server.msc_configs, entry) {
if (global_title_cmp(&msc->name, msc_name))
continue;
return msc;
}
if (!create)
return NULL;
msc = talloc_zero(dgsm_ctx, struct dgsm_msc_config);
OSMO_ASSERT(msc);
INIT_LLIST_HEAD(&msc->service_hosts);
msc->name = *msc_name;
return msc;
}
struct dgsm_service_host *dgsm_config_msc_service_get(struct dgsm_msc_config *msc, const char *service, bool create)
{
struct dgsm_service_host *e;
if (!msc)
return NULL;
llist_for_each_entry(e, &msc->service_hosts, entry) {
if (!strcmp(e->service, service))
return e;
}
if (!create)
return NULL;
e = talloc_zero(msc, struct dgsm_service_host);
OSMO_ASSERT(e);
OSMO_STRLCPY_ARRAY(e->service, service);
llist_add_tail(&e->entry, &msc->service_hosts);
return e;
}
struct dgsm_service_host *dgsm_config_service_get(const struct global_title *msc_name, const char *service)
{
struct dgsm_msc_config *msc = dgsm_config_msc_get(msc_name, false);
if (!msc)
return NULL;
return dgsm_config_msc_service_get(msc, service, false);
}
int dgsm_config_msc_service_set(struct dgsm_msc_config *msc, const char *service, const struct osmo_sockaddr_str *addr)
{
struct dgsm_service_host *e;
if (!service || !service[0]
|| strlen(service) > OSMO_MSLOOKUP_SERVICE_MAXLEN)
return -EINVAL;
if (!addr || !osmo_sockaddr_str_is_nonzero(addr))
return -EINVAL;
e = dgsm_config_msc_service_get(msc, service, true);
if (!e)
return -EINVAL;
switch (addr->af) {
case AF_INET:
e->host_v4 = *addr;
break;
case AF_INET6:
e->host_v6 = *addr;
break;
default:
return -EINVAL;
}
return 0;
}
int dgsm_config_service_set(const struct global_title *msc_name, const char *service, const struct osmo_sockaddr_str *addr)
{
struct dgsm_msc_config *msc;
msc = dgsm_config_msc_get(msc_name, true);
if (!msc)
return -EINVAL;
return dgsm_config_msc_service_set(msc, service, addr);
}
int dgsm_config_msc_service_del(struct dgsm_msc_config *msc, const char *service, const struct osmo_sockaddr_str *addr)
{
struct dgsm_service_host *e, *n;
if (!msc)
return -ENOENT;
llist_for_each_entry_safe(e, n, &msc->service_hosts, entry) {
if (service && strcmp(service, e->service))
continue;
if (addr) {
if (!osmo_sockaddr_str_cmp(addr, &e->host_v4)) {
e->host_v4 = (struct osmo_sockaddr_str){};
/* Removed one addr. If the other is still there, keep the entry. */
if (osmo_sockaddr_str_is_nonzero(&e->host_v6))
continue;
} else if (!osmo_sockaddr_str_cmp(addr, &e->host_v6)) {
e->host_v6 = (struct osmo_sockaddr_str){};
/* Removed one addr. If the other is still there, keep the entry. */
if (osmo_sockaddr_str_is_nonzero(&e->host_v4))
continue;
} else
/* No addr match, keep the entry. */
continue;
/* Addr matched and none is left. Delete. */
}
llist_del(&e->entry);
talloc_free(e);
}
return 0;
}
int dgsm_config_service_del(const struct global_title *msc_name,
const char *service, const struct osmo_sockaddr_str *addr)
{
return dgsm_config_msc_service_del(dgsm_config_msc_get(msc_name, false),
service, addr);
}
static void *dgsm_pending_messages_ctx = NULL;
struct pending_gsup_message {
struct llist_head entry;
struct osmo_gsup_req *req;
struct timeval received_at;
};
static LLIST_HEAD(pending_gsup_messages);
/* Defer a GSUP message until we know a remote HLR to proxy to.
* Where to send this GSUP message is indicated by its IMSI: as soon as an MS lookup has yielded the IMSI's home HLR,
* that's where the message should go. */
static void defer_gsup_req(struct osmo_gsup_req *req)
{
struct pending_gsup_message *m;
m = talloc_zero(dgsm_pending_messages_ctx, struct pending_gsup_message);
OSMO_ASSERT(m);
m->req = req;
timestamp_update(&m->received_at);
llist_add_tail(&m->entry, &pending_gsup_messages);
}
/* Unable to resolve remote HLR for this IMSI, Answer with error back to the sender. */
static void defer_gsup_message_err(struct pending_gsup_message *m)
{
osmo_gsup_req_respond_err(m->req, GMM_CAUSE_IMSI_UNKNOWN, "could not reach home HLR");
m->req = NULL;
}
/* Forward spooled message for this IMSI to remote HLR. */
static void defer_gsup_message_send(struct pending_gsup_message *m, struct remote_hlr *remote_hlr)
{
LOG_GSUP_REQ(m->req, LOGL_INFO, "Forwarding deferred message to " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
/* If sending fails, still discard. */
if (!remote_hlr->gsupc || !remote_hlr->gsupc->is_connected) {
LOGP(DDGSM, LOGL_ERROR, "GSUP link to remote HLR is not connected: " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
defer_gsup_message_err(m);
return;
}
remote_hlr_gsup_forward(remote_hlr, m->req);
m->req = NULL;
}
/* Result of looking for remote HLR. If it failed, pass remote_hlr as NULL. */
static void defer_gsup_message_pop(const char *imsi, struct remote_hlr *remote_hlr)
{
struct pending_gsup_message *m, *n;
if (remote_hlr)
LOG_DGSM(imsi, LOGL_DEBUG, "Sending spooled GSUP messages to remote HLR at " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
else
LOG_DGSM(imsi, LOGL_ERROR, "No remote HLR found, dropping spooled GSUP messages\n");
llist_for_each_entry_safe(m, n, &pending_gsup_messages, entry) {
if (strcmp(m->req->gsup.imsi, imsi))
continue;
if (!remote_hlr)
defer_gsup_message_err(m);
else
defer_gsup_message_send(m, remote_hlr);
llist_del(&m->entry);
talloc_free(m);
}
}
void dgsm_send_to_remote_hlr(const struct proxy_subscr *proxy_subscr, struct osmo_gsup_req *req)
{
struct remote_hlr *remote_hlr;
if (!osmo_sockaddr_str_is_nonzero(&proxy_subscr->remote_hlr_addr)) {
/* We don't know the remote target yet. Still waiting for an MS lookup response. */
LOG_GSUP_REQ(req, LOGL_DEBUG, "Proxy: deferring until remote HLR is known\n");
defer_gsup_req(req);
return;
}
LOG_GSUP_REQ(req, LOGL_INFO, "Proxy: forwarding to " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr->remote_hlr_addr));
remote_hlr = remote_hlr_get(&proxy_subscr->remote_hlr_addr, true);
if (!remote_hlr) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL,
"Proxy: Failed to establish connection to remote HLR " OSMO_SOCKADDR_STR_FMT,
OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr->remote_hlr_addr));
return;
}
if (!remote_hlr->gsupc || !remote_hlr->gsupc->is_connected) {
/* GSUP link is still busy establishing... */
LOG_GSUP_REQ(req, LOGL_DEBUG, "Proxy: deferring until link to remote HLR is up\n");
defer_gsup_req(req);
return;
}
remote_hlr_gsup_forward(remote_hlr, req);
}
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->proxy;
const struct proxy_subscr *proxy_subscr;
struct proxy_subscr proxy_subscr_new;
struct remote_hlr *remote_hlr;
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.
@@ -271,86 +55,38 @@ static void resolve_hlr_result_cb(struct osmo_mslookup_client *client,
return;
}
proxy_subscr = proxy_subscr_get_by_imsi(proxy, query->id.imsi);
if (!proxy_subscr) {
LOG_DGSM(query->id.imsi, LOGL_ERROR, "No proxy entry for mslookup result: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
defer_gsup_message_pop(query->id.imsi, NULL);
return;
}
if (result->rc != OSMO_MSLOOKUP_RC_OK) {
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));
defer_gsup_message_pop(query->id.imsi, NULL);
proxy_subscr_del(proxy, proxy_subscr->imsi);
proxy_subscr_del(proxy, query->id.imsi);
return;
}
/* Store the address. Make a copy to modify. */
proxy_subscr_new = *proxy_subscr;
proxy_subscr = &proxy_subscr_new;
if (osmo_sockaddr_str_is_nonzero(&result->host_v4))
proxy_subscr_new.remote_hlr_addr = result->host_v4;
remote_hlr_addr = &result->host_v4;
else if (osmo_sockaddr_str_is_nonzero(&result->host_v6))
proxy_subscr_new.remote_hlr_addr = 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));
defer_gsup_message_pop(query->id.imsi, NULL);
proxy_subscr_del(proxy, proxy_subscr->imsi);
proxy_subscr_del(proxy, query->id.imsi);
return;
}
if (proxy_subscr_update(proxy, proxy_subscr)) {
LOG_DGSM(query->id.imsi, LOGL_ERROR, "Failed to store proxy entry for remote HLR: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
defer_gsup_message_pop(query->id.imsi, NULL);
proxy_subscr_del(proxy, proxy_subscr->imsi);
return;
}
LOG_DGSM(proxy_subscr->imsi, LOGL_DEBUG, "Stored remote hlr address for this IMSI: " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr->remote_hlr_addr));
remote_hlr = remote_hlr_get(&proxy_subscr->remote_hlr_addr, true);
if (!remote_hlr) {
defer_gsup_message_pop(query->id.imsi, NULL);
proxy_subscr_del(proxy, proxy_subscr->imsi);
return;
}
if (!remote_hlr->gsupc || !remote_hlr->gsupc->is_connected) {
LOG_DGSM(query->id.imsi, LOGL_DEBUG, "Resolved remote HLR, waiting for link-up: %s\n",
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;
}
LOG_DGSM(query->id.imsi, LOGL_DEBUG, "Resolved remote HLR, sending spooled GSUP messages: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
defer_gsup_message_pop(query->id.imsi, remote_hlr);
}
static bool remote_hlr_up_yield(struct proxy *proxy, const struct proxy_subscr *proxy_subscr, void *data)
{
struct remote_hlr *remote_hlr = data;
defer_gsup_message_pop(proxy_subscr->imsi, remote_hlr);
return true;
}
void dgsm_remote_hlr_up(struct remote_hlr *remote_hlr)
{
LOGP(DDGSM, LOGL_NOTICE, "link to remote HLR is up, sending spooled GSUP messages: " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
/* Send all spooled GSUP messaged for IMSIs that are waiting for this link to establish. */
proxy_subscrs_get_by_remote_hlr(g_hlr->proxy, &remote_hlr->addr, remote_hlr_up_yield, remote_hlr);
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)
{
const struct proxy_subscr *proxy_subscr;
struct proxy_subscr proxy_subscr_new;
struct proxy *proxy = g_hlr->proxy;
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;
@@ -360,29 +96,59 @@ bool dgsm_check_forward_gsup_msg(struct osmo_gsup_req *req)
return false;
/* Are we already forwarding this IMSI to a remote HLR? */
proxy_subscr = proxy_subscr_get_by_imsi(proxy, req->gsup.imsi);
if (proxy_subscr)
goto yes_we_are_proxying;
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. */
/* Kick off an mslookup for the remote HLR. */
if (!g_hlr->mslookup.client.client) {
/* 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){
.result_timeout_milliseconds = g_hlr->mslookup.client.result_timeout_milliseconds,
.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);
@@ -390,32 +156,19 @@ bool dgsm_check_forward_gsup_msg(struct osmo_gsup_req *req)
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;
}
/* Add a proxy entry without a remote address to indicate that we are busy querying for a remote HLR. */
proxy_subscr_new = (struct proxy_subscr){};
OSMO_STRLCPY_ARRAY(proxy_subscr_new.imsi, req->gsup.imsi);
proxy_subscr = &proxy_subscr_new;
proxy_subscr_update(proxy, proxy_subscr);
yes_we_are_proxying:
OSMO_ASSERT(proxy_subscr);
/* If the remote HLR is already known, directly forward the GSUP message; otherwise, spool the GSUP message
* until the remote HLR will respond / until timeout aborts. */
dgsm_send_to_remote_hlr(proxy_subscr, req);
return true;
}
void dgsm_init(void *ctx)
{
dgsm_ctx = talloc_named_const(ctx, 0, "dgsm");
dgsm_pending_messages_ctx = talloc_named_const(dgsm_ctx, 0, "dgsm_pending_messages");
INIT_LLIST_HEAD(&g_hlr->mslookup.vty.server.msc_configs);
INIT_LLIST_HEAD(&g_hlr->mslookup.server.local_site_services);
g_hlr->mslookup.server.max_age = 60 * 60;
g_hlr->mslookup.server.local_attach_max_age = 60 * 60;
g_hlr->mslookup.client.result_timeout_milliseconds = 2000;
@@ -423,9 +176,9 @@ void dgsm_init(void *ctx)
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.vty.server.mdns.bind_addr,
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.vty.client.mdns.query_addr,
osmo_sockaddr_str_from_str(&g_hlr->mslookup.client.mdns.query_addr,
OSMO_MSLOOKUP_MDNS_IP4, OSMO_MSLOOKUP_MDNS_PORT);
}
@@ -434,82 +187,61 @@ 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;
dgsm_config_apply();
}
static void dgsm_mdns_server_config_apply()
{
/* Check whether to start/stop/restart mDNS server */
bool should_run;
bool should_stop;
if (!g_hlr->mslookup.allow_startup)
return;
should_run = g_hlr->mslookup.vty.server.enable && g_hlr->mslookup.vty.server.mdns.enable;
should_stop = g_hlr->mslookup.server.mdns
&& (!should_run
|| osmo_sockaddr_str_cmp(&g_hlr->mslookup.vty.server.mdns.bind_addr,
&g_hlr->mslookup.server.mdns->bind_addr));
if (should_stop) {
osmo_mslookup_server_mdns_stop(g_hlr->mslookup.server.mdns);
g_hlr->mslookup.server.mdns = NULL;
LOGP(DDGSM, LOGL_NOTICE, "Stopped mslookup mDNS server\n");
}
if (should_run && !g_hlr->mslookup.server.mdns) {
g_hlr->mslookup.server.mdns =
osmo_mslookup_server_mdns_start(g_hlr, &g_hlr->mslookup.vty.server.mdns.bind_addr);
if (!g_hlr->mslookup.server.mdns)
LOGP(DDGSM, LOGL_ERROR, "Failed to start mslookup mDNS server on " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.server.mdns->bind_addr));
else
LOGP(DDGSM, LOGL_NOTICE, "Started mslookup mDNS server, receiving mDNS requests at multicast "
OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.server.mdns->bind_addr));
}
}
static void dgsm_mdns_client_config_apply()
{
if (!g_hlr->mslookup.allow_startup)
return;
/* Check whether to start/stop/restart mDNS client */
const struct osmo_sockaddr_str *current_bind_addr;
current_bind_addr = osmo_mslookup_client_method_mdns_get_bind_addr(g_hlr->mslookup.client.mdns);
bool should_run = g_hlr->mslookup.vty.client.enable && g_hlr->mslookup.vty.client.mdns.enable;
bool should_stop = g_hlr->mslookup.client.mdns &&
(!should_run
|| osmo_sockaddr_str_cmp(&g_hlr->mslookup.vty.client.mdns.query_addr,
current_bind_addr));
if (should_stop) {
osmo_mslookup_client_method_del(g_hlr->mslookup.client.client, g_hlr->mslookup.client.mdns);
g_hlr->mslookup.client.mdns = NULL;
LOGP(DDGSM, LOGL_NOTICE, "Stopped mslookup mDNS client\n");
}
if (should_run && !g_hlr->mslookup.client.mdns) {
g_hlr->mslookup.client.mdns =
osmo_mslookup_client_add_mdns(g_hlr->mslookup.client.client,
g_hlr->mslookup.vty.client.mdns.query_addr.ip,
g_hlr->mslookup.vty.client.mdns.query_addr.port,
true);
if (!g_hlr->mslookup.client.mdns)
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.vty.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.vty.client.mdns.query_addr));
}
}
void dgsm_config_apply()
{
dgsm_mdns_server_config_apply();
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));
}

View File

@@ -1,75 +0,0 @@
#pragma once
#include <osmocom/mslookup/mslookup.h>
#include "gsup_server.h"
#include "global_title.h"
#define LOG_DGSM(imsi, level, fmt, args...) \
LOGP(DDGSM, level, "(IMSI-%s) " fmt, imsi, ##args)
struct vty;
struct remote_hlr;
extern void *dgsm_ctx;
struct dgsm_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;
};
struct dgsm_msc_config {
struct llist_head entry;
struct global_title name;
struct llist_head service_hosts;
};
/* "Sketch pad" where the VTY can store config items without yet applying. The changes will be applied by e.g.
* dgsm_mdns_server_config_apply() and dgsm_mdns_client_config_apply(). */
struct dgsm_config {
struct {
/* Whether to listen for incoming MS Lookup requests */
bool enable;
struct {
bool enable;
struct osmo_sockaddr_str bind_addr;
} mdns;
struct llist_head msc_configs;
} server;
struct {
/* Whether to ask remote HLRs via MS Lookup if an IMSI is not known locally. */
bool enable;
struct timeval timeout;
struct {
/* Whether to use mDNS for IMSI MS Lookup */
bool enable;
struct osmo_sockaddr_str query_addr;
} mdns;
} client;
};
void dgsm_config_apply();
struct dgsm_service_host *dgsm_config_service_get(const struct global_title *msc_name, const char *service);
int dgsm_config_service_set(const struct global_title *msc_name, const char *service, const struct osmo_sockaddr_str *addr);
int dgsm_config_service_del(const struct global_title *msc_name, const char *service, const struct osmo_sockaddr_str *addr);
struct dgsm_service_host *dgsm_config_msc_service_get(struct dgsm_msc_config *msc, const char *service, bool create);
int dgsm_config_msc_service_set(struct dgsm_msc_config *msc, const char *service, const struct osmo_sockaddr_str *addr);
int dgsm_config_msc_service_del(struct dgsm_msc_config *msc, const char *service, const struct osmo_sockaddr_str *addr);
extern const struct global_title dgsm_config_msc_wildcard;
struct dgsm_msc_config *dgsm_config_msc_get(const struct global_title *msc_name, bool create);
void dgsm_init(void *ctx);
void dgsm_start(void *ctx);
bool dgsm_check_forward_gsup_msg(struct osmo_gsup_req *req);
void dgsm_vty_init();
void dgsm_remote_hlr_up(struct remote_hlr *remote_hlr);

View File

@@ -1,7 +1,30 @@
/* 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 "hlr_vty.h"
#include "dgsm.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/gsup_peer_id.h>
struct cmd_node mslookup_node = {
MSLOOKUP_NODE,
@@ -12,33 +35,97 @@ struct cmd_node mslookup_node = {
DEFUN(cfg_mslookup,
cfg_mslookup_cmd,
"mslookup",
"Configure Distributed GSM / multicast MS Lookup")
"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",
"Convenience shortcut: enable both server and client for DNS/mDNS MS Lookup with default config\n")
"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)
{
g_hlr->mslookup.vty.server.enable = true;
g_hlr->mslookup.vty.server.mdns.enable = true;
g_hlr->mslookup.vty.client.enable = true;
g_hlr->mslookup.vty.client.mdns.enable = true;
dgsm_config_apply();
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",
NO_STR "Disable both server and client for DNS/mDNS MS Lookup\n")
NO_STR "Disable both server and client for mDNS mslookup\n")
{
g_hlr->mslookup.vty.server.mdns.enable = false;
g_hlr->mslookup.vty.client.mdns.enable = false;
dgsm_config_apply();
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;
}
@@ -51,58 +138,53 @@ struct cmd_node mslookup_server_node = {
DEFUN(cfg_mslookup_server,
cfg_mslookup_server_cmd,
"server",
"Enable and configure Distributed GSM / multicast MS Lookup server")
"Enable and configure Distributed GSM mslookup server")
{
vty->node = MSLOOKUP_SERVER_NODE;
g_hlr->mslookup.vty.server.enable = true;
dgsm_config_apply();
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 / multicast MS Lookup server")
NO_STR "Disable Distributed GSM mslookup server")
{
g_hlr->mslookup.vty.server.enable = false;
dgsm_config_apply();
g_hlr->mslookup.server.enable = false;
mslookup_server_mdns_config_apply();
return CMD_SUCCESS;
}
#define MDNS_STR "Configure mslookup by multicast DNS\n"
#define MDNS_BIND_STR MDNS_STR "Configure where the mDNS server listens for MS Lookup requests\n"
#define IP46_STR "IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1\n"
#define PORT_STR "Port number\n"
DEFUN(cfg_mslookup_server_mdns_bind,
cfg_mslookup_server_mdns_bind_cmd,
"mdns [bind] [IP] [<1-65535>]",
MDNS_BIND_STR IP46_STR PORT_STR)
"mdns bind [IP] [<1-65535>]",
MDNS_STR
"Configure where the mDNS server listens for mslookup requests\n"
MDNS_IP46_STR MDNS_PORT_STR)
{
const char *ip_str = argc > 1? argv[1] : g_hlr->mslookup.vty.server.mdns.bind_addr.ip;
const char *port_str = argc > 2? argv[2] : NULL;
uint16_t port_nr = port_str ? atoi(port_str) : g_hlr->mslookup.vty.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, "%% MS Lookup server: Invalid mDNS bind address: %s %u%s",
ip_str, port_nr, VTY_NEWLINE);
return CMD_WARNING;
}
return mslookup_server_mdns_bind(vty, argc, argv);
}
g_hlr->mslookup.vty.server.mdns.bind_addr = addr;
g_hlr->mslookup.vty.server.mdns.enable = true;
dgsm_config_apply();
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,
cfg_mslookup_server_no_mdns_cmd,
"no mdns",
NO_STR "Disable server for DNS/mDNS MS Lookup (do not answer remote requests)\n")
NO_STR "Disable server for mDNS mslookup (do not answer remote requests)\n")
{
g_hlr->mslookup.vty.server.mdns.enable = false;
dgsm_config_apply();
g_hlr->mslookup.server.mdns.enable = false;
mslookup_server_mdns_config_apply();
return CMD_SUCCESS;
}
@@ -118,13 +200,13 @@ DEFUN(cfg_mslookup_server_msc,
"Configure services for individual local MSCs\n"
"IPA Unit Name of the local MSC to configure\n")
{
struct global_title msc_name;
struct dgsm_msc_config *msc;
global_title_set_str(&msc_name, argv_concat(argv, argc, 0));
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 = dgsm_config_msc_get(&msc_name, true);
msc = mslookup_server_msc_get(&msc_name, true);
if (!msc) {
vty_out(vty, "%% Error creating MSC %s%s", global_title_name(&msc_name), VTY_NEWLINE);
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;
@@ -133,30 +215,35 @@ DEFUN(cfg_mslookup_server_msc,
}
#define SERVICE_NAME_STR \
"MS Lookup service name, e.g. " OSMO_MSLOOKUP_SERVICE_SIP " or " OSMO_MSLOOKUP_SERVICE_SMPP "\n"
#define SERVICE_AND_NAME_STR \
"Configure addresses of local services, as sent in replies to remote MS Lookup requests.\n" \
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>",
SERVICE_AND_NAME_STR "at\n" IP46_STR PORT_STR)
"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 dgsm_msc_config *msc = (vty->node == MSLOOKUP_SERVER_MSC_NODE) ? vty->index : NULL;
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;
/* On the mslookup.server node, set services on the wildcard msc, without a particular name. */
if (vty->node == MSLOOKUP_SERVER_NODE)
msc = dgsm_config_msc_get(&dgsm_config_msc_wildcard, true);
if (!msc) {
vty_out(vty, "%% Error: no MSC object on this node%s", VTY_NEWLINE);
return CMD_WARNING;
@@ -164,13 +251,13 @@ DEFUN(cfg_mslookup_server_msc_service,
if (osmo_sockaddr_str_from_str(&addr, ip_str, atoi(port_str))
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
vty_out(vty, "%% MS Lookup server: Invalid address for service %s: %s %s%s",
vty_out(vty, "%% mslookup server: Invalid address for service %s: %s %s%s",
service, ip_str, port_str, VTY_NEWLINE);
return CMD_WARNING;
}
if (dgsm_config_msc_service_set(msc, service, &addr)) {
vty_out(vty, "%% MS Lookup server: Error setting service %s to %s %s%s",
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;
}
@@ -186,11 +273,16 @@ DEFUN(cfg_mslookup_server_msc_no_service,
{
/* 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 dgsm_msc_config *msc = (vty->node == MSLOOKUP_SERVER_MSC_NODE) ? vty->index : NULL;
struct mslookup_server_msc_cfg *msc = msc_from_node(vty);
const char *service = argv[0];
if (dgsm_config_msc_service_del(msc, service, NULL)) {
vty_out(vty, "%% MS Lookup server: Error removing service %s%s",
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;
}
@@ -204,21 +296,26 @@ DEFUN(cfg_mslookup_server_msc_no_service_addr,
{
/* 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 dgsm_msc_config *msc = (vty->node == MSLOOKUP_SERVER_MSC_NODE) ? vty->index : NULL;
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, "%% MS Lookup server: Invalid address for 'no service' %s: %s %s%s",
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 (dgsm_config_service_del(&msc->name, service, &addr)) {
vty_out(vty, "%% MS Lookup server: Error removing service %s to %s %s%s",
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;
}
@@ -234,26 +331,24 @@ struct cmd_node mslookup_client_node = {
DEFUN(cfg_mslookup_client,
cfg_mslookup_client_cmd,
"client",
"Enable and configure Distributed GSM / multicast MS Lookup client")
"Enable and configure Distributed GSM mslookup client")
{
vty->node = MSLOOKUP_CLIENT_NODE;
g_hlr->mslookup.vty.client.enable = true;
dgsm_config_apply();
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 / multicast MS Lookup client")
NO_STR "Disable Distributed GSM mslookup client")
{
g_hlr->mslookup.vty.client.enable = false;
dgsm_config_apply();
g_hlr->mslookup.client.enable = false;
dgsm_mdns_client_config_apply();
return CMD_SUCCESS;
}
#define MDNS_TO_STR MDNS_STR "Configure to which multicast address mDNS MS Lookup requests are sent\n"
DEFUN(cfg_mslookup_client_timeout,
cfg_mslookup_client_timeout_cmd,
"timeout <1-100000>",
@@ -261,8 +356,7 @@ DEFUN(cfg_mslookup_client_timeout,
"timeout in milliseconds\n")
{
uint32_t val = atol(argv[0]);
g_hlr->mslookup.vty.client.timeout.tv_sec = val / 1000;
g_hlr->mslookup.vty.client.timeout.tv_usec = (val % 1000) * 1000;
g_hlr->mslookup.client.result_timeout_milliseconds = val;
return CMD_SUCCESS;
}
@@ -270,25 +364,26 @@ DEFUN(cfg_mslookup_client_timeout,
if (vty->type != VTY_FILE) \
vty_out(vty, "%% 'exit' this node to apply changes%s", VTY_NEWLINE)
DEFUN(cfg_mslookup_client_mdns,
cfg_mslookup_client_mdns_cmd,
"mdns [to] [IP] [<1-65535>]",
MDNS_STR "Configure multicast address to send mDNS mslookup requests to\n" IP46_STR PORT_STR)
"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)
{
const char *ip_str = argc > 1? argv[1] : g_hlr->mslookup.vty.client.mdns.query_addr.ip;
const char *port_str = argc > 2? argv[2] : NULL;
uint16_t port_nr = port_str ? atoi(port_str) : g_hlr->mslookup.vty.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, "%% MS Lookup client: Invalid mDNS target address: %s %u%s",
ip_str, port_nr, VTY_NEWLINE);
return CMD_WARNING;
}
return mslookup_client_mdns_to(vty, argc, argv);
}
g_hlr->mslookup.vty.client.mdns.query_addr = addr;
g_hlr->mslookup.vty.client.mdns.enable = true;
dgsm_config_apply();
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;
}
@@ -297,59 +392,188 @@ DEFUN(cfg_mslookup_client_no_mdns,
"no mdns",
NO_STR "Disable mDNS client, do not query remote services by mDNS\n")
{
g_hlr->mslookup.vty.client.mdns.enable = false;
dgsm_config_apply();
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 to %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;
}
int config_write_mslookup_server(struct vty *vty)
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;
}
int config_write_mslookup_server_msc(struct vty *vty)
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;
}
int config_write_mslookup_client(struct vty *vty)
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 %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 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, config_write_mslookup_server);
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_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, config_write_mslookup_server_msc);
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, config_write_mslookup_client);
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_cmd);
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_mdns_domain_suffix_cmd);
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_no_mdns_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

@@ -1,68 +0,0 @@
#include <errno.h>
#include <string.h>
#include <osmocom/core/utils.h>
#include "global_title.h"
int global_title_set(struct global_title *gt, const uint8_t *val, size_t len)
{
if (!val || !len) {
*gt = (struct global_title){};
return 0;
}
if (len > sizeof(gt->val))
return -ENOSPC;
gt->len = len;
memcpy(gt->val, val, len);
return 0;
}
int global_title_set_str(struct global_title *gt, const char *str_fmt, ...)
{
va_list ap;
if (!str_fmt)
return global_title_set(gt, NULL, 0);
va_start(ap, str_fmt);
vsnprintf((char*)(gt->val), sizeof(gt->val), str_fmt, ap);
va_end(ap);
gt->len = strlen((char*)(gt->val))+1;
return 0;
}
int global_title_cmp(const struct global_title *a, const struct global_title *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;
}
}
const char *global_title_name(const struct global_title *gt)
{
return osmo_quote_str_c(OTC_SELECT, (char*)gt->val, gt->len);
}

View File

@@ -1,17 +0,0 @@
#pragma once
#include <unistd.h>
#include <stdint.h>
/* 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.
*/
struct global_title {
size_t len;
uint8_t val[128];
};
int global_title_set(struct global_title *gt, const uint8_t *val, size_t len);
int global_title_set_str(struct global_title *gt, const char *str_fmt, ...);
int global_title_cmp(const struct global_title *a, const struct global_title *b);
const char *global_title_name(const struct global_title *gt);

View File

@@ -23,9 +23,9 @@
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/talloc.h>
#include "logging.h"
#include "gsup_server.h"
#include "gsup_router.h"
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/gsup_router.h>
/*! Find a route for the given address.
* \param[in] gs gsup server
@@ -47,9 +47,9 @@ struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs,
return NULL;
}
struct osmo_gsup_conn *gsup_route_find_gt(struct osmo_gsup_server *gs, const struct global_title *gt)
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, gt->val, gt->len);
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).
@@ -96,9 +96,9 @@ int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addr
return 0;
}
int gsup_route_add_gt(struct osmo_gsup_conn *conn, const struct global_title *gt)
int gsup_route_add_ipa_name(struct osmo_gsup_conn *conn, const struct osmo_ipa_name *ipa_name)
{
return gsup_route_add(conn, gt->val, gt->len);
return gsup_route_add(conn, ipa_name->val, ipa_name->len);
}
/* delete all routes for the given connection */

View File

@@ -21,8 +21,8 @@
#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>
@@ -53,27 +53,27 @@ int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
/*! Send a msgb to a given address using routing.
* \param[in] gs gsup server
* \param[in] gt IPA unit name of the client (SGSN, MSC/VLR, proxy).
* \param[in] ipa_name IPA unit name of the client (SGSN, MSC/VLR, proxy).
* \param[in] msg message buffer
*/
int osmo_gsup_gt_send(struct osmo_gsup_server *gs, const struct global_title *gt, 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)
{
if (gt->val[gt->len - 1]) {
if (ipa_name->val[ipa_name->len - 1]) {
/* Is not nul terminated. But for legacy reasons we (still) require that. */
if (gt->len >= sizeof(gt->val)) {
LOGP(DLGSUP, LOGL_ERROR, "Global title (IPA unit name) is too long: %s\n",
global_title_name(gt));
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 global_title gt2 = *gt;
gt2.val[gt->len] = '\0';
gt2.len++;
return osmo_gsup_addr_send(gs, gt2.val, gt2.len, msg);
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, gt->val, gt->len, msg);
return osmo_gsup_addr_send(gs, ipa_name->val, ipa_name->len, msg);
}
int osmo_gsup_gt_enc_send(struct osmo_gsup_server *gs, const struct global_title *gt,
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");
@@ -87,5 +87,5 @@ int osmo_gsup_gt_enc_send(struct osmo_gsup_server *gs, const struct global_title
}
LOGP(DLGSUP, LOGL_DEBUG, "IMSI-%s: Tx: %s\n", gsup->imsi, osmo_gsup_message_type_name(gsup->message_type));
return osmo_gsup_gt_send(gs, gt, msg);
return osmo_gsup_send_to_ipa_name(gs, ipa_name, msg);
}

View File

@@ -28,9 +28,12 @@
#include <osmocom/gsm/apn.h>
#include <osmocom/gsm/gsm23003.h>
#include "hlr.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)
{
@@ -59,227 +62,89 @@ int osmo_gsup_conn_send(struct osmo_gsup_conn *conn, struct msgb *msg)
return 0;
}
/* Only for requests originating here. When answering to a remote request, rather use osmo_gsup_req_respond() or
* osmo_gsup_req_respond_err(). */
int osmo_gsup_conn_enc_send(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
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_gsup_peer_id *routing;
struct osmo_gsup_conn *conn = NULL;
struct msgb *msg = osmo_gsup_msgb_alloc("GSUP Tx");
int rc;
rc = osmo_gsup_encode(msg, gsup);
if (rc) {
LOG_GSUP_CONN(conn, LOGL_ERROR, "Cannot encode GSUP: %s\n",
osmo_gsup_message_type_name(gsup->message_type));
msgb_free(msg);
return -EINVAL;
if (response->message_type == OSMO_GSUP_MSGT_ROUTING_ERROR
&& !osmo_gsup_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_GSUP_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_gsup_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;
}
LOG_GSUP_CONN(conn, LOGL_DEBUG, "Tx: %s\n", osmo_gsup_message_type_name(gsup->message_type));
rc = osmo_gsup_conn_send(conn, msg);
if (rc)
LOG_GSUP_CONN(conn, LOGL_ERROR, "Unable to send: %s\n",
osmo_gsup_message_type_name(gsup->message_type));
return 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_req_new(struct osmo_gsup_conn *conn, struct msgb *msg)
struct osmo_gsup_req *osmo_gsup_conn_rx(struct osmo_gsup_conn *conn, struct msgb *msg)
{
static unsigned int next_req_nr = 1;
struct osmo_gsup_req *req;
struct osmo_gsup_message gsup_err;
int rc;
struct osmo_gsup_peer_id gpi = {
.type = OSMO_GSUP_PEER_ID_IPA_NAME,
.ipa_name = conn->peer_name,
};
if (!msgb_l2(msg) || !msgb_l2len(msg)) {
LOGP(DLGSUP, LOGL_ERROR, "Rx GSUP message: missing or empty L2 data\n");
msgb_free(msg);
req = osmo_gsup_req_new(conn->server, &gpi, msg, gsup_server_send_req_response, conn->server, NULL);
if (!req)
return NULL;
}
req = talloc_zero(conn->server, struct osmo_gsup_req);
OSMO_ASSERT(req);
req->nr = next_req_nr++;
req->server = conn->server;
req->msg = msg;
req->source_name = conn->peer_name;
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 message: cannot decode (rc=%d)\n", rc);
osmo_gsup_req_free(req);
return NULL;
}
LOG_GSUP_REQ(req, LOGL_DEBUG, "new\n");
if (req->gsup.source_name_len) {
if (global_title_set(&req->source_name, req->gsup.source_name, req->gsup.source_name_len)) {
LOGP(DLGSUP, LOGL_ERROR, "cannot decode GSUP message's source_name, message is not routable\n");
goto unroutable_error;
if (!osmo_gsup_peer_id_is_empty(&req->via_proxy)) {
switch (req->via_proxy.type) {
case OSMO_GSUP_PEER_ID_IPA_NAME:
break;
default:
LOG_GSUP_REQ(req, LOGL_ERROR, "GSUP peer id kind not supported: %s\n",
osmo_gsup_peer_id_type_name(req->source_name.type));
osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_ROUTING_ERROR, true);
return NULL;
}
if (global_title_cmp(&req->source_name, &conn->peer_name)) {
/* 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_gt(conn, &req->source_name)) {
LOGP(DLGSUP, 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",
global_title_name(&req->source_name),
global_title_name(&conn->peer_name));
goto unroutable_error;
}
req->via_proxy = conn->peer_name;
/* 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_gsup_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;
}
}
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;
}
return req;
unroutable_error:
gsup_err = (struct osmo_gsup_message){
.message_type = OSMO_GSUP_MSGT_E_ROUTING_ERROR,
.destination_name = req->gsup.destination_name,
.destination_name_len = req->gsup.destination_name_len,
.source_name = req->gsup.source_name,
.source_name_len = req->gsup.source_name_len,
};
osmo_gsup_set_reply(&req->gsup, &gsup_err);
osmo_gsup_conn_enc_send(conn, &gsup_err);
osmo_gsup_req_free(req);
return NULL;
}
void _osmo_gsup_req_free(struct osmo_gsup_req *req)
{
if (req->msg)
msgb_free(req->msg);
talloc_free(req);
}
int osmo_gsup_req_respond_nonfinal(struct osmo_gsup_req *req, struct osmo_gsup_message *response)
{
struct osmo_gsup_conn *conn;
struct msgb *msg;
int rc;
conn = gsup_route_find_gt(req->server, &req->source_name);
if (!conn) {
LOG_GSUP_REQ(req, LOGL_ERROR, "GSUP client that sent this request was disconnected, cannot respond\n");
return -EINVAL;
}
osmo_gsup_set_reply(&req->gsup, response);
msg = osmo_gsup_msgb_alloc("GSUP response");
rc = osmo_gsup_encode(msg, response);
if (rc) {
LOG_GSUP_REQ(req, LOGL_ERROR, "Unable to encode response: %s\n",
osmo_gsup_message_type_name(response->message_type));
return -EINVAL;
}
LOG_GSUP_REQ(req, LOGL_DEBUG, "Tx response: %s\n", osmo_gsup_message_type_name(response->message_type));
rc = osmo_gsup_conn_send(conn, msg);
if (rc)
LOG_GSUP_REQ(req, LOGL_ERROR, "Unable to send response: %s\n",
osmo_gsup_message_type_name(response->message_type));
return rc;
}
/* Make sure the response message contains all IEs that are required to be a valid response for the received GSUP
* request, and send back to the requesting peer. */
int osmo_gsup_req_respond(struct osmo_gsup_req *req, struct osmo_gsup_message *response)
{
int rc = osmo_gsup_req_respond_nonfinal(req, response);
osmo_gsup_req_free(req);
return rc;
}
int osmo_gsup_req_respond_msgt(struct osmo_gsup_req *req, enum osmo_gsup_message_type message_type)
{
struct osmo_gsup_message response = {
.message_type = message_type,
};
return osmo_gsup_req_respond(req, &response);
}
#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); \
} while(0)
void _osmo_gsup_req_respond_err(struct osmo_gsup_req *req, enum gsm48_gmm_cause cause)
{
struct osmo_gsup_message response = {
.cause = cause,
.message_type = OSMO_GSUP_TO_MSGT_ERROR(req->gsup.message_type),
};
/* 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;
}
if (req->gsup.session_state != OSMO_GSUP_SESSION_STATE_NONE)
response.session_state = OSMO_GSUP_SESSION_STATE_END;
osmo_gsup_req_respond(req, &response);
}
/* Encode an error reponse to the given GSUP message with the given cause.
* Determine the error message type via OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type).
* Only send an error response if the original message is a Request message.
* On failure, log an error, but don't return anything: if an error occurs while trying to report an earlier error,
* there is nothing we can do really except log the error (there are no callers that would use the return code).
*/
void osmo_gsup_conn_send_err_reply(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup_orig,
enum gsm48_gmm_cause cause)
{
struct osmo_gsup_message gsup_reply;
struct msgb *msg_out;
int rc;
/* 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(gsup_orig->message_type))
return;
gsup_reply = (struct osmo_gsup_message){
.cause = cause,
.message_type = OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type),
.message_class = gsup_orig->message_class,
/* RP-Message-Reference is mandatory for SM Service */
.sm_rp_mr = gsup_orig->sm_rp_mr,
};
osmo_gsup_set_reply(gsup_orig, &gsup_reply);
if (gsup_orig->session_state != OSMO_GSUP_SESSION_STATE_NONE)
gsup_reply.session_state = OSMO_GSUP_SESSION_STATE_END;
msg_out = osmo_gsup_msgb_alloc("GSUP ERR response");
rc = osmo_gsup_encode(msg_out, &gsup_reply);
if (rc) {
LOGP(DLGSUP, LOGL_ERROR, "%s: Unable to encode error response %s (rc=%d)\n",
osmo_quote_str(gsup_orig->imsi, -1), osmo_gsup_message_type_name(gsup_reply.message_type),
rc);
return;
}
LOGP(DLGSUP, LOGL_DEBUG, "%s: GSUP tx %s\n",
osmo_quote_str(gsup_orig->imsi, -1), osmo_gsup_message_type_name(gsup_reply.message_type));
rc = osmo_gsup_conn_send(conn, msg_out);
if (rc)
LOGP(DLGSUP, LOGL_ERROR, "%s: Unable to send error response %s (rc=%d)\n",
osmo_quote_str(gsup_orig->imsi, -1), osmo_gsup_message_type_name(gsup_reply.message_type),
rc);
}
static int osmo_gsup_conn_oap_handle(struct osmo_gsup_conn *conn,
@@ -427,8 +292,8 @@ static int osmo_gsup_server_ccm_cb(struct ipa_server_conn *conn,
return -EINVAL;
}
global_title_set(&clnt->peer_name, addr, addr_len);
gsup_route_add_gt(clnt, &clnt->peer_name);
osmo_ipa_name_set(&clnt->peer_name, addr, addr_len);
gsup_route_add_ipa_name(clnt, &clnt->peer_name);
return 0;
}
@@ -649,3 +514,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_gsup_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_GSUP_PEER_ID_IPA_NAME) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Unsupported GSUP peer id type: %s",
osmo_gsup_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_GSUP_PEER_ID_IPA_NAME) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Unsupported GSUP peer id type: %s",
osmo_gsup_peer_id_type_name(to_peer->type));
rc = -ENOTSUP;
goto routing_error;
}
LOG_GSUP_REQ(req, LOGL_INFO, "Forwarding to %s\n", osmo_gsup_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,140 +0,0 @@
#pragma once
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/socket.h>
#include <osmocom/abis/ipa.h>
#include <osmocom/abis/ipaccess.h>
#include <osmocom/gsm/gsup.h>
#include "global_title.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 */
#endif
#define LOG_GSUP_CONN(conn, level, fmt, args...) \
LOGP(DLGSUP, level, "GSUP from %s: " fmt, \
conn && conn->server && conn->server->link ? \
osmo_sock_get_name2_c(OTC_SELECT, conn->server->link->ofd.fd) \
: "?", \
##args)
#if 0
#define LOG_GSUP_CONN_MSG(conn, gsup_msg, level, fmt, args...) \
LOG_GSUP_CONN(conn, level, "IMSI-%s %s: " fmt, (gsup_msg)->imsi, \
osmo_gsup_message_type_name((gsup_msg)->message_type), \
##args)
#endif
#define LOG_GSUP_REQ_CAT(req, subsys, level, fmt, args...) \
LOGP(subsys, level, "GSUP %u: %s: IMSI-%s %s: " fmt, \
(req) ? (req)->nr : 0, \
(req) ? global_title_name(&(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(req, level, fmt, args...) \
LOG_GSUP_REQ_CAT(req, DLGSUP, level, fmt, ##args)
struct osmo_gsup_conn;
/* Expects message in msg->l2h */
typedef int (*osmo_gsup_read_cb_t)(struct osmo_gsup_conn *conn, struct msgb *msg);
struct osmo_gsup_server {
/* private data of the application/user */
void *priv;
/* list of osmo_gsup_conn */
struct llist_head clients;
struct ipa_server_link *link;
osmo_gsup_read_cb_t read_cb;
struct llist_head routes;
};
/* a single connection to a given client (SGSN, MSC) */
struct osmo_gsup_conn {
struct llist_head list;
struct osmo_gsup_server *server;
struct ipa_server_conn *conn;
//struct oap_state oap_state;
struct tlv_parsed ccm;
unsigned int auc_3g_ind; /*!< IND index used for UMTS AKA SQN */
/* 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 global_title peer_name;
};
/* Keep track of an incoming request to which osmo-hlr composes (or routes back) a response.
* Particularly, if a request contained a source_name, we need to add this as destination_name in the response for any
* intermediate GSUP proxies to be able to route back to the initial requestor. */
struct osmo_gsup_req {
struct osmo_gsup_server *server;
/* Identify this request by number, for logging. */
unsigned int nr;
/* 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;
/* Decoded msg. */
int decode_rc;
const struct osmo_gsup_message gsup;
/* 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 global_title 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 global_title via_proxy;
};
struct osmo_gsup_req *osmo_gsup_req_new(struct osmo_gsup_conn *conn, struct msgb *msg);
#define osmo_gsup_req_free(REQ) do { \
LOG_GSUP_REQ(REQ, LOGL_DEBUG, "free\n"); \
_osmo_gsup_req_free(REQ); \
} while(0)
void _osmo_gsup_req_free(struct osmo_gsup_req *req);
int osmo_gsup_req_respond(struct osmo_gsup_req *req, struct osmo_gsup_message *response);
int osmo_gsup_req_respond_nonfinal(struct osmo_gsup_req *req, struct osmo_gsup_message *response);
int osmo_gsup_req_respond_msgt(struct osmo_gsup_req *req, enum osmo_gsup_message_type message_type);
#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); \
} while(0)
void _osmo_gsup_req_respond_err(struct osmo_gsup_req *req, enum gsm48_gmm_cause cause);
struct msgb *osmo_gsup_msgb_alloc(const char *label);
int osmo_gsup_conn_send(struct osmo_gsup_conn *conn, struct msgb *msg);
int osmo_gsup_conn_enc_send(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);
int osmo_gsup_conn_ccm_get(const struct osmo_gsup_conn *clnt, uint8_t **addr,
uint8_t tag);
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,
void *priv);
void osmo_gsup_server_destroy(struct osmo_gsup_server *gsups);
int osmo_gsup_configure_wildcard_apn(struct osmo_gsup_message *gsup,
uint8_t *apn_buf, size_t apn_buf_size);
int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup, const char *imsi, const char *msisdn,
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);

View File

@@ -8,7 +8,11 @@ AM_CFLAGS = -Wall $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/incl
lib_LTLIBRARIES = libosmo-gsup-client.la
libosmo_gsup_client_la_SOURCES = gsup_client.c
libosmo_gsup_client_la_SOURCES = \
gsup_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

@@ -104,7 +104,6 @@ static void connect_timer_cb(void *gsupc_)
}
gsup_client_connect(gsupc);
}
static void client_send(struct osmo_gsup_client *gsupc, int proto_ext,
@@ -274,48 +273,52 @@ static void start_test_procedure(struct osmo_gsup_client *gsupc)
gsup_client_send_ping(gsupc);
}
struct osmo_gsup_client *osmo_gsup_client_create3(void *talloc_ctx,
struct ipaccess_unit *ipa_dev,
const char *ip_addr,
unsigned int tcp_port,
struct osmo_oap_client_config *oapc_config,
osmo_gsup_client_read_cb_t read_cb,
osmo_gsup_client_up_down_cb_t up_down_cb,
void *data)
/*!
* Create a gsup client connecting to the specified IP address and TCP port.
* 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] config Parameters for setting up the GSUP client.
* \return a GSUP client connection, or NULL on failure.
*/
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(ipa_dev->unit_name);
OSMO_ASSERT(config->ipa_dev->unit_name);
gsupc = talloc_zero(talloc_ctx, struct osmo_gsup_client);
OSMO_ASSERT(gsupc);
*gsupc = (struct osmo_gsup_client){
.unit_name = (const char *)ipa_dev->unit_name, /* API backwards compat */
.ipa_dev = ipa_dev,
.read_cb = read_cb,
.up_down_cb = up_down_cb,
.data = data,
.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;
osmo_timer_setup(&gsupc->connect_timer, connect_timer_cb, gsupc);
rc = gsup_client_connect(gsupc);
if (rc < 0)
goto failed;
return gsupc;
@@ -325,18 +328,8 @@ failed:
return NULL;
}
/*!
* Create a gsup client connecting to the specified IP address and TCP port.
* 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.
/*! 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,
@@ -345,8 +338,14 @@ struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx,
osmo_gsup_client_read_cb_t read_cb,
struct osmo_oap_client_config *oapc_config)
{
return osmo_gsup_client_create3(talloc_ctx, ipa_dev, ip_addr, tcp_port, oapc_config,
read_cb, NULL, NULL);
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);
}
/**

View File

@@ -0,0 +1,175 @@
/* 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/gsup_peer_id.h>
bool osmo_ipa_name_is_empty(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;
}
}
/* Return an unquoted string, not including the terminating zero. Used for writing VTY config. */
const char *osmo_ipa_name_to_str(const struct osmo_ipa_name *ipa_name)
{
size_t len = ipa_name->len;
if (!len)
return "";
if (ipa_name->val[len-1] == '\0')
len--;
return osmo_escape_str_c(OTC_SELECT, (char*)ipa_name->val, len);
}
bool osmo_gsup_peer_id_is_empty(struct osmo_gsup_peer_id *gsup_peer_id)
{
if (!gsup_peer_id)
return true;
switch (gsup_peer_id->type) {
case OSMO_GSUP_PEER_ID_EMPTY:
return true;
case OSMO_GSUP_PEER_ID_IPA_NAME:
return osmo_ipa_name_is_empty(&gsup_peer_id->ipa_name);
default:
return false;
}
}
int osmo_gsup_peer_id_set(struct osmo_gsup_peer_id *gsup_peer_id, enum osmo_gsup_peer_id_type type,
const uint8_t *val, size_t len)
{
gsup_peer_id->type = type;
switch (type) {
case OSMO_GSUP_PEER_ID_IPA_NAME:
return osmo_ipa_name_set(&gsup_peer_id->ipa_name, val, len);
default:
return -EINVAL;
}
}
int osmo_gsup_peer_id_set_str(struct osmo_gsup_peer_id *gsup_peer_id, enum osmo_gsup_peer_id_type type,
const char *str_fmt, ...)
{
va_list ap;
int rc;
*gsup_peer_id = (struct osmo_gsup_peer_id){};
switch (type) {
case OSMO_GSUP_PEER_ID_IPA_NAME:
gsup_peer_id->type = OSMO_GSUP_PEER_ID_IPA_NAME;
va_start(ap, str_fmt);
rc = osmo_ipa_name_set_str_va(&gsup_peer_id->ipa_name, str_fmt, ap);
va_end(ap);
return rc;
default:
return -EINVAL;
}
}
int osmo_gsup_peer_id_cmp(const struct osmo_gsup_peer_id *a, const struct osmo_gsup_peer_id *b)
{
if (a->type != b->type)
return OSMO_CMP(a->type, b->type);
switch (a->type) {
case OSMO_GSUP_PEER_ID_IPA_NAME:
return osmo_ipa_name_cmp(&a->ipa_name, &b->ipa_name);
default:
return -EINVAL;
}
}
const struct value_string osmo_gsup_peer_id_type_names[] = {
{ OSMO_GSUP_PEER_ID_IPA_NAME, "IPA-name" },
{}
};
/* Return an unquoted string, not including the terminating zero. Used for writing VTY config. */
const char *osmo_gsup_peer_id_to_str(const struct osmo_gsup_peer_id *gpi)
{
switch (gpi->type) {
case OSMO_GSUP_PEER_ID_IPA_NAME:
return osmo_ipa_name_to_str(&gpi->ipa_name);
default:
return osmo_gsup_peer_id_type_name(gpi->type);
}
}

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

@@ -0,0 +1,312 @@
/* 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.
* 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.
*
* When this function returns, the original sender is found in req->source_name. If this is not the immediate peer name,
* 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).
*
* 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 GSUP message buffer.
* \param[in] send_response_cb User specific method to send a GSUP response message, invoked upon
* osmo_gsup_req_respond*() functions.
* \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.
*/
struct osmo_gsup_req *osmo_gsup_req_new(void *ctx, const struct osmo_gsup_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 (!msgb_l2(msg) || !msgb_l2len(msg)) {
LOGP(DLGSUP, LOGL_ERROR, "Rx GSUP from %s: missing or empty L2 data\n",
osmo_gsup_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;
if (from_peer)
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_gsup_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_gsup_peer_id_set(&req->source_name, OSMO_GSUP_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_gsup_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_gsup_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;
}
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);
}
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;
}
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);
}
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)
}

127
src/hlr.c
View File

@@ -35,30 +35,29 @@
#include <osmocom/gsm/apn.h>
#include <osmocom/gsm/gsm48_ie.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/protocol/gsm_23_003.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 "hlr_vty.h"
#include "hlr_ussd.h"
#include "dgsm.h"
#include "proxy.h"
#include "global_title.h"
#include "lu_fsm.h"
#include <osmocom/gsupclient/gsup_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/hlr/sms_over_gsup.h>
#include <osmocom/mslookup/mdns.h>
struct hlr *g_hlr;
static void *hlr_ctx = NULL;
static int quit = 0;
#define RAN_TDEFS \
struct osmo_tdef g_hlr_tdefs[] = {
/* 4222 is also the OSMO_GSUP_PORT */
{ .T = -4222, .default_val = 30, .desc = "GSUP Update Location timeout" },
@@ -249,7 +248,7 @@ int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val,
{
int rc;
bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs;
struct global_title vlr_name;
struct osmo_ipa_name vlr_name;
struct osmo_gsup_message gsup_del_data = {
.message_type = OSMO_GSUP_MSGT_DELETE_DATA_REQUEST,
};
@@ -270,10 +269,10 @@ int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val,
if (nam_val)
return 0;
if (subscr->vlr_number && global_title_set_str(&vlr_name, subscr->vlr_number))
osmo_gsup_gt_enc_send(g_hlr->gs, &vlr_name, &gsup_del_data);
if (subscr->sgsn_number && global_title_set_str(&vlr_name, subscr->sgsn_number))
osmo_gsup_gt_enc_send(g_hlr->gs, &vlr_name, &gsup_del_data);
if (subscr->vlr_number && 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 && 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;
}
@@ -282,19 +281,34 @@ int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val,
***********************************************************************/
/* process an incoming SAI request */
static int rx_send_auth_info(unsigned int auc_3g_ind, struct osmo_gsup_req *req)
static int rx_send_auth_info(struct osmo_gsup_req *req)
{
struct osmo_gsup_message gsup_out = {
.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT,
};
bool separation_bit = false;
unsigned int auc_3g_ind;
int rc;
subscr_create_on_demand(req->gsup.imsi);
if (req->gsup.current_rat_type == OSMO_RAT_EUTRAN_SGS)
separation_bit = true;
rc = db_ind(g_hlr->dbc, &req->source_name, &auc_3g_ind);
if (rc) {
LOG_GSUP_REQ(req, LOGL_ERROR,
"Unable to determine 3G auth IND for source %s (rc=%d),"
" generating tuples with IND = 0\n",
osmo_gsup_peer_id_to_str(&req->source_name), rc);
auc_3g_ind = 0;
}
rc = db_get_auc(g_hlr->dbc, req->gsup.imsi, auc_3g_ind,
gsup_out.auth_vectors,
ARRAY_SIZE(gsup_out.auth_vectors),
req->gsup.rand, req->gsup.auts);
req->gsup.rand, req->gsup.auts, separation_bit);
if (rc <= 0) {
switch (rc) {
case 0:
@@ -306,16 +320,16 @@ static int rx_send_auth_info(unsigned int auc_3g_ind, struct osmo_gsup_req *req)
" Returning slightly inaccurate cause 'IMSI Unknown' via GSUP");
return rc;
case -ENOENT:
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "IMSI unknown");
osmo_gsup_req_respond_err(req, GMM_CAUSE_ROAMING_NOTALLOWED, "IMSI unknown");
return rc;
default:
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "failure to look up IMSI in db");
return rc;
}
}
gsup_out.num_auth_vectors = rc;
osmo_gsup_req_respond(req, &gsup_out);
osmo_gsup_req_respond(req, &gsup_out, false, true);
return 0;
}
@@ -356,9 +370,9 @@ static int rx_purge_ms_req(struct osmo_gsup_req *req)
rc = db_subscr_purge(g_hlr->dbc, req->gsup.imsi, true, is_ps);
if (rc == 0)
osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_PURGE_MS_RESULT);
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");
osmo_gsup_req_respond_err(req, GMM_CAUSE_ROAMING_NOTALLOWED, "IMSI unknown");
else
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "db error");
return rc;
@@ -418,7 +432,7 @@ static int rx_check_imei_req(struct osmo_gsup_req *req)
.message_type = OSMO_GSUP_MSGT_CHECK_IMEI_RESULT,
.imei_result = OSMO_GSUP_IMEI_RESULT_ACK,
};
return osmo_gsup_req_respond(req, &gsup_reply);
return osmo_gsup_req_respond(req, &gsup_reply, false, true);
}
static char namebuf[255];
@@ -437,7 +451,7 @@ static int read_cb_forward(struct osmo_gsup_req *req)
const struct osmo_gsup_message *gsup = &req->gsup;
struct osmo_gsup_message gsup_err;
struct msgb *forward_msg;
struct global_title destination_name;
struct osmo_ipa_name destination_name;
/* Check for routing IEs */
if (!req->gsup.source_name[0] || !req->gsup.source_name_len
@@ -446,19 +460,19 @@ static int read_cb_forward(struct osmo_gsup_req *req)
goto routing_error;
}
if (global_title_set(&destination_name, req->gsup.destination_name, req->gsup.destination_name_len)) {
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", global_title_name(&destination_name));
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_gt_send(g_hlr->gs, &destination_name, forward_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",
@@ -470,46 +484,49 @@ static int read_cb_forward(struct osmo_gsup_req *req)
routing_error:
gsup_err = (struct osmo_gsup_message){
.message_type = OSMO_GSUP_MSGT_E_ROUTING_ERROR,
.destination_name = gsup->destination_name,
.destination_name_len = gsup->destination_name_len,
.source_name = gsup->source_name,
.source_name_len = gsup->source_name_len,
.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);
osmo_gsup_req_respond(req, &gsup_err, true, true);
return -1;
}
static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
{
struct osmo_gsup_req *req = osmo_gsup_req_new(conn, msg);
struct osmo_gsup_req *req = osmo_gsup_conn_rx(conn, msg);
if (!req)
return -EINVAL;
/* If the GSUP recipient is other than this HLR, forward. */
if (req->gsup.destination_name_len) {
struct global_title destination_name;
struct global_title my_name;
global_title_set_str(&my_name, g_hlr->gsup_unit_name.serno);
if (!global_title_set(&destination_name, req->gsup.destination_name, req->gsup.destination_name_len)
&& global_title_cmp(&destination_name, &my_name)) {
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);
}
}
/* SMS over GSUP */
if (sms_over_gsup_check_handle_msg(req))
return 0;
/* 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)) {
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;
}
/* 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->auc_3g_ind, req);
rx_send_auth_info(req);
break;
case OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST:
rx_upd_loc_req(conn, req);
@@ -651,6 +668,11 @@ static void handle_options(int argc, char **argv)
break;
}
}
if (argc > optind) {
fprintf(stderr, "Unsupported positional arguments on command line\n");
exit(2);
}
}
static void signal_hdlr(int signal)
@@ -698,12 +720,15 @@ int main(int argc, char **argv)
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;
g_hlr->proxy = proxy_init(g_hlr);
g_hlr->sms_over_gsup.try_direct_delivery = true;
rc = osmo_init_logging2(hlr_ctx, &hlr_log_info);
if (rc < 0) {
@@ -719,6 +744,7 @@ int main(int argc, char **argv)
ctrl_vty_init(hlr_ctx);
handle_options(argc, argv);
hlr_vty_init();
dgsm_vty_init();
rc = vty_read_config_file(cmdline_opts.config_file, NULL);
if (rc < 0) {
@@ -768,6 +794,7 @@ int main(int argc, char **argv)
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);
@@ -790,6 +817,8 @@ int main(int argc, char **argv)
while (!quit)
osmo_select_main_ctx(0);
dgsm_stop();
osmo_gsup_server_destroy(g_hlr->gs);
db_close(g_hlr->dbc);
log_fini();

View File

@@ -29,9 +29,9 @@
#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 */

View File

@@ -29,12 +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 "db.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
@@ -170,7 +170,7 @@ struct ss_session {
/* subscriber's vlr_number
* MO USSD: originating MSC's vlr_number
* MT USSD: looked up once per session and cached here */
struct global_title vlr_name;
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
@@ -243,10 +243,8 @@ static int ss_gsup_send_to_ms(struct ss_session *ss, struct osmo_gsup_server *gs
int rc;
if (ss->initial_req_from_ms) {
/* If this is a response to an incoming GSUP request from the MS, respond via osmo_gsup_req_respond() to
* make sure that all required routing information is kept intact.
* Use osmo_gsup_req_respond_nonfinal() to not deallocate the ss->initial_req_from_ms */
osmo_gsup_req_respond_nonfinal(ss->initial_req_from_ms, gsup);
/* 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;
}
@@ -265,7 +263,7 @@ static int ss_gsup_send_to_ms(struct ss_session *ss, struct osmo_gsup_server *gs
msgb_free(msg);
return -EINVAL;
}
global_title_set_str(&ss->vlr_name, subscr.vlr_number);
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) */
@@ -275,8 +273,8 @@ static int ss_gsup_send_to_ms(struct ss_session *ss, struct osmo_gsup_server *gs
return -EINVAL;
}
LOGPSS(ss, LOGL_DEBUG, "Tx SS/USSD to VLR %s\n", global_title_name(&ss->vlr_name));
return osmo_gsup_gt_send(gs, &ss->vlr_name, msg);
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,
@@ -337,7 +335,7 @@ static int ss_tx_to_ms_ussd_7bit(struct ss_session *ss, bool final, uint8_t invo
* Internal USSD Handlers
***********************************************************************/
#include "db.h"
#include <osmocom/hlr/db.h>
static int handle_ussd_own_msisdn(struct ss_session *ss,
const struct osmo_gsup_message *gsup, const struct ss_request *req)
@@ -375,6 +373,206 @@ static int handle_ussd_own_imsi(struct ss_session *ss,
return 0;
}
static int handle_ussd_get_ran(struct ss_session *ss,
const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
struct hlr_subscriber subscr;
char response[512];
int rc;
const char *rat;
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
switch (rc) {
case 0:
if (!*subscr.last_lu_rat_cs)
rat = "nothing, you don't exist";
else if (!strcmp(subscr.last_lu_rat_cs, "GERAN-A"))
rat = "2G";
else if (!strcmp(subscr.last_lu_rat_cs, "UTRAN-Iu"))
rat = "3G";
else if (!strcmp(subscr.last_lu_rat_cs, "EUTRAN-SGs"))
rat = "4G";
else
rat = subscr.last_lu_rat_cs;
snprintf(response, sizeof(response),
"Now on %s. Available:%s%s%s.",
rat,
subscr.rat_types[OSMO_RAT_GERAN_A]? " 2G" : "",
subscr.rat_types[OSMO_RAT_UTRAN_IU]? " 3G" : "",
subscr.rat_types[OSMO_RAT_EUTRAN_SGS]? " 4G" : "");
rc = ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id, response);
break;
case -ENOENT:
rc = ss_tx_to_ms_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
rc = ss_tx_to_ms_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return rc;
}
static int handle_ussd_gsm_on(struct ss_session *ss,
const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
struct hlr_subscriber subscr;
int rc;
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
switch (rc) {
case 0:
hlr_subscr_rat_flag(g_hlr, &subscr, OSMO_RAT_GERAN_A, true);
rc = ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id,
"Enabled GERAN-A (2G)");
break;
case -ENOENT:
rc = ss_tx_to_ms_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
rc = ss_tx_to_ms_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return rc;
}
static int handle_ussd_gsm_off(struct ss_session *ss,
const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
struct hlr_subscriber subscr;
int rc;
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
switch (rc) {
case 0:
hlr_subscr_rat_flag(g_hlr, &subscr, OSMO_RAT_GERAN_A, false);
rc = ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id,
"Disabled GERAN-A (2G)");
break;
case -ENOENT:
rc = ss_tx_to_ms_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
rc = ss_tx_to_ms_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return rc;
}
static int handle_ussd_umts_on(struct ss_session *ss,
const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
struct hlr_subscriber subscr;
int rc;
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
switch (rc) {
case 0:
hlr_subscr_rat_flag(g_hlr, &subscr, OSMO_RAT_UTRAN_IU, true);
rc = ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id,
"Enabled UTRAN-Iu (3G)");
break;
case -ENOENT:
rc = ss_tx_to_ms_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
rc = ss_tx_to_ms_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return rc;
}
static int handle_ussd_umts_off(struct ss_session *ss,
const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
struct hlr_subscriber subscr;
int rc;
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
switch (rc) {
case 0:
hlr_subscr_rat_flag(g_hlr, &subscr, OSMO_RAT_UTRAN_IU, false);
rc = ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id,
"Disabled UTRAN-Iu (3G)");
break;
case -ENOENT:
rc = ss_tx_to_ms_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
rc = ss_tx_to_ms_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return rc;
}
static int handle_ussd_lte_on(struct ss_session *ss,
const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
struct hlr_subscriber subscr;
int rc;
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
switch (rc) {
case 0:
hlr_subscr_rat_flag(g_hlr, &subscr, OSMO_RAT_EUTRAN_SGS, true);
rc = ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id,
"Enabled EUTRAN-SGs (4G)");
break;
case -ENOENT:
rc = ss_tx_to_ms_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
rc = ss_tx_to_ms_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return rc;
}
static int handle_ussd_lte_off(struct ss_session *ss,
const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
struct hlr_subscriber subscr;
int rc;
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
switch (rc) {
case 0:
hlr_subscr_rat_flag(g_hlr, &subscr, OSMO_RAT_EUTRAN_SGS, false);
rc = ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id,
"Disabled EUTRAN-SGs (4G)");
break;
case -ENOENT:
rc = ss_tx_to_ms_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
rc = ss_tx_to_ms_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return rc;
}
static const struct hlr_iuse hlr_iuses[] = {
{
@@ -385,6 +583,34 @@ static const struct hlr_iuse hlr_iuses[] = {
.name = "own-imsi",
.handle_ussd = handle_ussd_own_imsi,
},
{
.name = "get-ran",
.handle_ussd = handle_ussd_get_ran,
},
{
.name = "gsm-on",
.handle_ussd = handle_ussd_gsm_on,
},
{
.name = "gsm-off",
.handle_ussd = handle_ussd_gsm_off,
},
{
.name = "umts-on",
.handle_ussd = handle_ussd_umts_on,
},
{
.name = "umts-off",
.handle_ussd = handle_ussd_umts_off,
},
{
.name = "lte-on",
.handle_ussd = handle_ussd_lte_on,
},
{
.name = "lte-off",
.handle_ussd = handle_ussd_lte_off,
},
};
const struct hlr_iuse *iuse_find(const char *name)
@@ -418,22 +644,24 @@ static bool ss_op_is_ussd(uint8_t opcode)
}
/* is this GSUP connection an EUSE (true) or not (false)? */
static bool peer_name_is_euse(const struct global_title *peer_name)
static bool peer_name_is_euse(const struct osmo_gsup_peer_id *peer_name)
{
if (peer_name->len <= 5)
if (peer_name->type != OSMO_GSUP_PEER_ID_IPA_NAME)
return false;
if (!strncmp((char *)(peer_name->val), "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_name(const struct global_title *peer_name)
static struct hlr_euse *euse_by_name(const struct osmo_gsup_peer_id *peer_name)
{
if (!peer_name_is_euse(peer_name))
return NULL;
return euse_find(g_hlr, (const char*)(peer_name->val)+5);
/* above peer_name_is_euse() ensures this: */
OSMO_ASSERT(peer_name->type == OSMO_GSUP_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, bool is_euse_originated, const struct osmo_gsup_message *gsup,
@@ -480,17 +708,16 @@ static int handle_ussd(struct ss_session *ss, bool is_euse_originated, const str
/* Received from VLR (MS) */
if (ss->is_external) {
/* Forward to EUSE */
struct global_title euse_name;
struct osmo_ipa_name euse_name;
struct osmo_gsup_conn *conn;
global_title_set_str(&euse_name, "EUSE-%s", ss->u.euse->name);
conn = gsup_route_find_gt(g_hlr->gs, &euse_name);
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",
global_title_name(&euse_name));
osmo_ipa_name_to_str(&euse_name));
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
} else {
msg_out = osmo_gsup_msgb_alloc("GSUP USSD FW");
OSMO_ASSERT(msg_out);
osmo_gsup_encode(msg_out, gsup);
osmo_gsup_conn_send(conn, msg_out);
}
@@ -523,6 +750,14 @@ void rx_proc_ss_req(struct osmo_gsup_req *gsup_req)
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_GSUP_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_gsup_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)) {
@@ -560,7 +795,8 @@ void rx_proc_ss_req(struct osmo_gsup_req *gsup_req)
if (!is_euse_originated) {
ss->initial_req_from_ms = gsup_req;
free_gsup_req = NULL;
ss->vlr_name = gsup_req->source_name;
OSMO_ASSERT(gsup_req->source_name.type == OSMO_GSUP_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;

View File

@@ -33,13 +33,12 @@
#include <osmocom/vty/misc.h>
#include <osmocom/abis/ipa.h>
#include "db.h"
#include "hlr.h"
#include "hlr_vty.h"
#include "hlr_vty_subscr.h"
#include "hlr_ussd.h"
#include "gsup_server.h"
#include "dgsm.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,
@@ -169,16 +168,23 @@ DEFUN(cfg_hlr_gsup_ipa_name,
* 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|get-ran|gsm-on|gsm-off|umts-on|umts-off|lte-on|lte-off)"
#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" \
"Respond with available RAN types\n" \
"Enable GSM service\n" \
"Disable GSM service\n" \
"Enable UMTS service\n" \
"Disable UMTS service\n" \
"Enable LTE service\n" \
"Disable LTE service\n"
#define EXT_STR "External USSD Handler\n" \
"Name of External USSD Handler (IPA CCM ID)\n"
@@ -422,17 +428,6 @@ int hlr_vty_go_parent(struct vty *vty)
vty->index = NULL;
vty->index_sub = NULL;
break;
case MSLOOKUP_CLIENT_NODE:
case MSLOOKUP_SERVER_NODE:
vty->node = CONFIG_NODE;
vty->index = NULL;
vty->index_sub = NULL;
break;
case MSLOOKUP_SERVER_MSC_NODE:
vty->node = CONFIG_NODE;
vty->index = NULL;
vty->index_sub = NULL;
break;
default:
case HLR_NODE:
vty->node = CONFIG_NODE;
@@ -493,5 +488,4 @@ void hlr_vty_init(void)
install_element(HLR_NODE, &cfg_no_subscr_create_on_demand_cmd);
hlr_vty_subscriber_init();
dgsm_vty_init();
}

View File

@@ -27,32 +27,62 @@
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/gsm_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;
/* Strip trailing newline. */
p = strchr(s, '\n');
if (p)
*p = '\0';
return s;
tm = gmtime(t);
if (!tm)
return "UNKNOWN";
strftime(buf, bufsize, "%FT%T+00:00", tm);
return buf;
}
static void dump_last_lu_seen(struct vty *vty, const char *domain_label, time_t last_lu_seen, const char *last_lu_rat)
{
uint32_t age;
char datebuf[32];
if (!last_lu_seen)
return;
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)");
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);
vty_out(vty, " ago)");
#undef UNIT_AGO
}
if (last_lu_rat && *last_lu_rat != '\0')
vty_out(vty, " on %s", last_lu_rat);
vty_out(vty, "%s", VTY_NEWLINE);
}
static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
{
int rc;
int i;
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);
@@ -87,8 +117,12 @@ 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, subscr->last_lu_rat_cs);
dump_last_lu_seen(vty, "PS", subscr->last_lu_seen_ps, subscr->last_lu_rat_ps);
for (i = OSMO_RAT_UNKNOWN + 1; i < ARRAY_SIZE(subscr->rat_types); i++) {
vty_out(vty, " %s: %s%s", osmo_rat_type_name(i), subscr->rat_types[i] ? "allowed" : "forbidden",
VTY_NEWLINE);
}
if (!*subscr->imsi)
return;
@@ -605,6 +639,48 @@ DEFUN(subscriber_nam,
}
DEFUN(subscriber_rat,
subscriber_rat_cmd,
SUBSCR_UPDATE "rat (geran-a|utran-iu|eutran-sgs) (allowed|forbidden)",
SUBSCR_UPDATE_HELP
"Allow or forbid specific Radio Access Types\n"
"Set access to GERAN-A\n"
"Set access to UTRAN-Iu\n"
"Set access to EUTRAN-SGs\n"
"Allow access\n"
"Forbid access\n")
{
struct hlr_subscriber subscr;
const char *id_type = argv[0];
const char *id = argv[1];
const char *rat_str = argv[2];
const char *allowed_forbidden = argv[3];
enum osmo_rat_type rat = OSMO_RAT_UNKNOWN;
bool allowed;
int rc;
if (strcmp(rat_str, "geran-a") == 0)
rat = OSMO_RAT_GERAN_A;
else if (strcmp(rat_str, "utran-iu") == 0)
rat = OSMO_RAT_UTRAN_IU;
else if (strcmp(rat_str, "eutran-sgs") == 0)
rat = OSMO_RAT_EUTRAN_SGS;
allowed = (strcmp(allowed_forbidden, "allowed") == 0);
if (get_subscr_by_argv(vty, id_type, id, &subscr))
return CMD_WARNING;
rc = hlr_subscr_rat_flag(g_hlr, &subscr, rat, allowed);
if (rc && rc != -ENOEXEC) {
vty_out(vty, "%% Error: cannot set %s to %s%s",
osmo_rat_type_name(rat), allowed ? "allowed" : "forbidden", VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
void hlr_vty_subscriber_init(void)
{
install_element_ve(&subscriber_show_cmd);
@@ -618,4 +694,5 @@ void hlr_vty_subscriber_init(void)
install_element(ENABLE_NODE, &subscriber_aud3g_cmd);
install_element(ENABLE_NODE, &subscriber_imei_cmd);
install_element(ENABLE_NODE, &subscriber_nam_cmd);
install_element(ENABLE_NODE, &subscriber_rat_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,9 +25,9 @@ const struct log_info_cat hlr_log_info_cat[] = {
.color = "\033[1;34m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DDGSM] = {
.name = "DDGSM",
.description = "Distributed GSM: MS lookup and proxy",
[DMSLOOKUP] = {
.name = "DMSLOOKUP",
.description = "Mobile Subscriber Lookup",
.color = "\033[1;35m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
@@ -37,7 +37,12 @@ const struct log_info_cat hlr_log_info_cat[] = {
.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 = {

View File

@@ -1,4 +1,23 @@
/* Roughly follwing "Process Update_Location_HLR" of TS 09.02 */
/* 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>
@@ -7,11 +26,13 @@
#include <osmocom/gsm/apn.h>
#include <osmocom/gsm/gsm48_ie.h>
#include "logging.h"
#include "hlr.h"
#include "gsup_server.h"
#include <osmocom/gsupclient/gsup_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 "db.h"
#include <osmocom/hlr/db.h>
#define LOG_LU(lu, level, fmt, args...) \
LOGPFSML((lu)? (lu)->fi : NULL, level, fmt, ##args)
@@ -31,11 +52,11 @@ struct lu {
bool is_ps;
/* VLR requesting the LU. */
struct global_title vlr_name;
struct osmo_gsup_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 global_title via_proxy;
struct osmo_gsup_peer_id via_proxy;
};
LLIST_HEAD(g_all_lu);
@@ -67,7 +88,7 @@ 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);
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);
}
@@ -87,6 +108,8 @@ static void lu_start(struct osmo_gsup_req *update_location_req)
{
struct osmo_fsm_inst *fi;
struct lu *lu;
bool any_rat_allowed;
int i;
OSMO_ASSERT(update_location_req);
OSMO_ASSERT(update_location_req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST);
@@ -109,13 +132,13 @@ static void lu_start(struct osmo_gsup_req *update_location_req)
osmo_fsm_inst_update_id_f_sanitize(fi, '_', "%s:IMSI-%s", lu->is_ps ? "PS" : "CS", update_location_req->gsup.imsi);
if (!lu->vlr_name.len) {
if (osmo_gsup_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");
lu_failure(lu, GMM_CAUSE_ROAMING_NOTALLOWED, "Subscriber does not exist");
return;
}
@@ -130,6 +153,28 @@ static void lu_start(struct osmo_gsup_req *update_location_req)
return;
}
/* Check if any available RAT type is allowed. See 3GPP TS 29.010 3.2 'Routeing area updating' and 3.8 'Location
* update' for the "No Suitable cells in location area" error code. */
any_rat_allowed = false;
for (i = 0; i < update_location_req->gsup.supported_rat_types_len; i++) {
enum osmo_rat_type rat = update_location_req->gsup.supported_rat_types[i];
if (rat <= 0 || rat >= OSMO_RAT_COUNT) {
lu_failure(lu, GMM_CAUSE_COND_IE_ERR, "Invalid RAT type in GSUP request: %s",
osmo_rat_type_name(rat));
return;
}
if (lu->subscr.rat_types[rat]) {
any_rat_allowed = true;
LOG_LU(lu, LOGL_DEBUG, "subscriber allowed on %s\n", osmo_rat_type_name(rat));
} else {
LOG_LU(lu, LOGL_DEBUG, "subscriber not allowed on %s\n", osmo_rat_type_name(rat));
}
}
if (!any_rat_allowed && update_location_req->gsup.supported_rat_types_len > 0) {
lu_failure(lu, GMM_CAUSE_NO_SUIT_CELL_IN_LA, "subscriber not allowed on any available RAT type");
return;
}
/* TODO: Set subscriber tracing = deactive in VLR/SGSN */
#if 0
@@ -142,18 +187,31 @@ static void lu_start(struct osmo_gsup_req *update_location_req)
#endif
/* Store the VLR / SGSN number with the subscriber, so we know where it was last seen. */
if (lu->via_proxy.len) {
if (!osmo_gsup_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",
global_title_name(&lu->vlr_name),
global_title_name(&lu->via_proxy));
osmo_gsup_peer_id_to_str(&lu->vlr_name),
osmo_gsup_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",
global_title_name(&lu->vlr_name));
osmo_gsup_peer_id_to_str(&lu->vlr_name));
}
if (db_subscr_lu(g_hlr->dbc, lu->subscr.id, &lu->vlr_name, lu->is_ps, &lu->via_proxy)) {
if (osmo_gsup_peer_id_is_empty(&lu->vlr_name)
|| (lu->vlr_name.type != OSMO_GSUP_PEER_ID_IPA_NAME)) {
lu_failure(lu, GMM_CAUSE_PROTO_ERR_UNSPEC, "Unsupported GSUP peer id type for vlr_name: %s",
osmo_gsup_peer_id_type_name(lu->vlr_name.type));
return;
}
if (!osmo_gsup_peer_id_is_empty(&lu->via_proxy) && (lu->via_proxy.type != OSMO_GSUP_PEER_ID_IPA_NAME)) {
lu_failure(lu, GMM_CAUSE_PROTO_ERR_UNSPEC, "Unsupported GSUP peer id type for via_proxy: %s",
osmo_gsup_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_gsup_peer_id_is_empty(&lu->via_proxy)? NULL : &lu->via_proxy.ipa_name,
update_location_req->gsup.supported_rat_types, update_location_req->gsup.supported_rat_types_len)) {
lu_failure(lu, GMM_CAUSE_NET_FAIL, "Cannot update %s in the database",
lu->is_ps ? "SGSN number" : "VLR number");
return;
@@ -219,7 +277,7 @@ static void lu_fsm_wait_insert_data_result_onenter(struct osmo_fsm_inst *fi, uin
return;
}
if (osmo_gsup_req_respond_nonfinal(lu->update_location_req, &gsup))
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));
}

View File

@@ -1,2 +0,0 @@
void lu_rx_gsup(struct osmo_gsup_req *req);

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);
if (!value)
return NULL;
va_end(ap);
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;
}

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

@@ -0,0 +1,265 @@
/* 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 = *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;
}

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

@@ -0,0 +1,144 @@
/* 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;
}
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;
}
/* 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;
}
/* 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;
}
osmo_fd_setup(&ret->osmo_fd, sock, OSMO_FD_READ, cb, data, priv_nr);
if (osmo_fd_register(&ret->osmo_fd) != 0)
goto error;
return ret;
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);
}

319
src/mslookup/mslookup.c Normal file
View File

@@ -0,0 +1,319 @@
/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <errno.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/mslookup/mslookup.h>
/*! \addtogroup mslookup
*
* Distributed GSM: finding subscribers
*
* There are various aspects of the D-GSM code base in osmo-hlr.git, here is an overview:
*
* mslookup is the main enabler of D-GSM, a concept for connecting services between independent core network stacks.
*
* D-GSM consists of:
* (1) mslookup client to find subscribers:
* (a) external clients like ESME, SIP PBX, ... ask osmo-hlr to tell where to send SMS, voice calls, ...
* (b) osmo-hlr's own mslookup client asks remote osmo-hlrs whether they know a given IMSI.
* (2) when a subscriber was found at a remote HLR, GSUP gets forwarded there:
* (a) to deliver messages for the GSUP proxy, osmo-hlr manages many GSUP clients to establish links to remote HLRs.
* (b) osmo-hlr has a GSUP proxy layer that caches data of IMSIs that get proxied to a remote HLR.
* (c) decision making to distinguish local IMSIs from ones proxied to a remote HLR.
*
* (1) mslookup is a method of finding subscribers using (multicast) queries, by MSISDN or by IMSI.
* It is open to various lookup methods, the first one being multicast DNS.
* An mslookup client sends a request, and an mslookup server responds.
* The mslookup server is implemented by osmo-hlr. mslookup clients are arbitrary programs, like an ESME or a SIP PBX.
* Hence the mslookup client is public API, while the mslookup server is implemented "privately" in osmo-hlr.
*
* (1a) Public mslookup client: libosmo-mslookup
* src/mslookup/mslookup.c Things useful for both client and server.
* src/mslookup/mslookup_client.c The client API, which can use various lookup methods,
* and consolidates results from various responders.
* src/mslookup/mslookup_client_mdns.c lookup method implementing multicast DNS, client side.
*
* src/mslookup/osmo-mslookup-client.c Utility program to ease invocation for (blocking) mslookup clients.
*
* src/mslookup/mslookup_client_fake.c lookup method generating fake results, for testing client implementations.
*
* src/mslookup/mdns*.c implementation of DNS to be used by mslookup_client_mdns.c,
* and the mslookup_server.c.
*
* contrib/dgsm/esme_dgsm.py Example implementation for an mslookup enabled SMS handler.
* contrib/dgsm/freeswitch_dialplan_dgsm.py Example implementation for an mslookup enabled FreeSWITCH dialplan.
* contrib/dgsm/osmo-mslookup-pipe.py Example for writing a python client using the osmo-mslookup-client
* cmdline.
* contrib/dgsm/osmo-mslookup-socket.py Example for writing a python client using the osmo-mslookup-client
* unix domain socket.
*
* (1b) "Private" mslookup server in osmo-hlr:
* src/mslookup_server.c Respond to mslookup queries, independent from the particular lookup method.
* src/mslookup_server_mdns.c mDNS specific implementation for mslookup_server.c.
* src/dgsm_vty.c Configure services that mslookup server sends to remote requests.
*
* (2) Proxy and GSUP clients to remote HLR instances:
*
* (a) Be a GSUP client to forward to a remote HLR:
* src/gsupclient/ The same API that is used by osmo-{msc,sgsn} is also used to forward GSUP to remote osmo-hlrs.
* src/remote_hlr.c Establish links to remote osmo-hlrs, where this osmo-hlr is a client (proxying e.g. for an MSC).
*
* (b) Keep track of remotely handled IMSIs:
* src/proxy.c Keep track of proxied IMSIs and cache important subscriber data.
*
* (c) Direct GSUP request to the right destination: either the local or a remote HLR:
* src/dgsm.c The glue that makes osmo-hlr distinguish between local IMSIs and those that are proxied to another
* osmo-hlr.
* src/dgsm_vty.c Config.
*
* @{
* \file mslookup.c
*/
const struct value_string osmo_mslookup_id_type_names[] = {
{ OSMO_MSLOOKUP_ID_NONE, "none" },
{ OSMO_MSLOOKUP_ID_IMSI, "imsi" },
{ OSMO_MSLOOKUP_ID_MSISDN, "msisdn" },
{}
};
const struct value_string osmo_mslookup_result_code_names[] = {
{ OSMO_MSLOOKUP_RC_NONE, "none" },
{ OSMO_MSLOOKUP_RC_RESULT, "result" },
{ OSMO_MSLOOKUP_RC_NOT_FOUND, "not-found" },
{}
};
/*! Compare two struct osmo_mslookup_id.
* \returns 0 if a and b are equal,
* < 0 if a (or the ID type / start of ID) is < b,
* > 0 if a (or the ID type / start of ID) is > b.
*/
int osmo_mslookup_id_cmp(const struct osmo_mslookup_id *a, const struct osmo_mslookup_id *b)
{
int cmp;
if (a == b)
return 0;
if (!a)
return -1;
if (!b)
return 1;
cmp = OSMO_CMP(a->type, b->type);
if (cmp)
return cmp;
switch (a->type) {
case OSMO_MSLOOKUP_ID_IMSI:
return strncmp(a->imsi, b->imsi, sizeof(a->imsi));
case OSMO_MSLOOKUP_ID_MSISDN:
return strncmp(a->msisdn, b->msisdn, sizeof(a->msisdn));
default:
return 0;
}
}
bool osmo_mslookup_id_valid(const struct osmo_mslookup_id *id)
{
switch (id->type) {
case OSMO_MSLOOKUP_ID_IMSI:
return osmo_imsi_str_valid(id->imsi);
case OSMO_MSLOOKUP_ID_MSISDN:
return osmo_msisdn_str_valid(id->msisdn);
default:
return false;
}
}
bool osmo_mslookup_service_valid(const char *service)
{
return strlen(service) > 0;
}
/*! Write ID and ID type to a buffer.
* \param[out] buf nul-terminated {id}.{id_type} string (e.g. "1234.msisdn") or
* "?.none" if the ID type is invalid.
* \returns amount of bytes written to buf.
*/
size_t osmo_mslookup_id_name_buf(char *buf, size_t buflen, const struct osmo_mslookup_id *id)
{
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
switch (id->type) {
case OSMO_MSLOOKUP_ID_IMSI:
OSMO_STRBUF_PRINTF(sb, "%s", id->imsi);
break;
case OSMO_MSLOOKUP_ID_MSISDN:
OSMO_STRBUF_PRINTF(sb, "%s", id->msisdn);
break;
default:
OSMO_STRBUF_PRINTF(sb, "?");
break;
}
OSMO_STRBUF_PRINTF(sb, ".%s", osmo_mslookup_id_type_name(id->type));
return sb.chars_needed;
}
/*! Same as osmo_mslookup_id_name_buf(), but return a talloc allocated string of sufficient size. */
char *osmo_mslookup_id_name_c(void *ctx, const struct osmo_mslookup_id *id)
{
OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_mslookup_id_name_buf, id)
}
/*! Same as osmo_mslookup_id_name_buf(), but directly return the char* (for printf-like string formats). */
char *osmo_mslookup_id_name_b(char *buf, size_t buflen, const struct osmo_mslookup_id *id)
{
int rc = osmo_mslookup_id_name_buf(buf, buflen, id);
if (rc < 0 && buflen)
buf[0] = '\0';
return buf;
}
/*! Write mslookup result string to buffer.
* \param[in] query with the service, ID and ID type to be written to buf like a domain string, or NULL to omit.
* \param[in] result with the result code, IPv4/v6 and age to be written to buf or NULL to omit.
* \param[out] buf result as flat string, which looks like the following for a valid query and result with IPv4 and v6
* answer: "sip.voice.1234.msisdn -> ipv4: 42.42.42.42:1337 -> ipv6: [1234:5678:9ABC::]:1338 (age=1)",
* the result part can also be " -> timeout" or " -> rc=5" depending on the result code.
* \returns amount of bytes written to buf.
*/
size_t osmo_mslookup_result_to_str_buf(char *buf, size_t buflen,
const struct osmo_mslookup_query *query,
const struct osmo_mslookup_result *result)
{
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
if (query) {
OSMO_STRBUF_PRINTF(sb, "%s.", query->service);
OSMO_STRBUF_APPEND(sb, osmo_mslookup_id_name_buf, &query->id);
}
if (result && result->rc == OSMO_MSLOOKUP_RC_NONE)
result = NULL;
if (result) {
if (result->rc != OSMO_MSLOOKUP_RC_RESULT)
OSMO_STRBUF_PRINTF(sb, " %s", osmo_mslookup_result_code_name(result->rc));
if (result->rc == OSMO_MSLOOKUP_RC_RESULT) {
if (result->host_v4.ip[0]) {
OSMO_STRBUF_PRINTF(sb, " -> ipv4: " OSMO_SOCKADDR_STR_FMT,
OSMO_SOCKADDR_STR_FMT_ARGS(&result->host_v4));
}
if (result->host_v6.ip[0]) {
OSMO_STRBUF_PRINTF(sb, " -> ipv6: " OSMO_SOCKADDR_STR_FMT,
OSMO_SOCKADDR_STR_FMT_ARGS(&result->host_v6));
}
OSMO_STRBUF_PRINTF(sb, " (age=%u)", result->age);
}
OSMO_STRBUF_PRINTF(sb, " %s", result->last ? "(last)" : "(not-last)");
}
return sb.chars_needed;
}
/*! Same as osmo_mslookup_result_to_str_buf(), but return a talloc allocated string of sufficient size. */
char *osmo_mslookup_result_name_c(void *ctx,
const struct osmo_mslookup_query *query,
const struct osmo_mslookup_result *result)
{
OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_mslookup_result_to_str_buf, query, result)
}
/*! Same as osmo_mslookup_result_to_str_buf(), but directly return the char* (for printf-like string formats). */
char *osmo_mslookup_result_name_b(char *buf, size_t buflen,
const struct osmo_mslookup_query *query,
const struct osmo_mslookup_result *result)
{
int rc = osmo_mslookup_result_to_str_buf(buf, buflen, query, result);
if (rc < 0 && buflen)
buf[0] = '\0';
return buf;
}
/*! Copy part of a string to a buffer and nul-terminate it.
* \returns 0 on success, negative on error.
*/
static int token(char *dest, size_t dest_size, const char *start, const char *end)
{
int len;
if (start >= end)
return -10;
len = end - start;
if (len >= dest_size)
return -11;
strncpy(dest, start, len);
dest[len] = '\0';
return 0;
}
/*! Parse a string like "foo.moo.goo.123456789012345.msisdn" into service="foo.moo.goo", id="123456789012345" and
* id_type="msisdn", placed in a struct osmo_mslookup_query.
* \returns 0 on success, negative on error.
*/
int osmo_mslookup_query_init_from_domain_str(struct osmo_mslookup_query *q, const char *domain)
{
const char *last_dot;
const char *second_last_dot;
const char *id_type;
const char *id;
int rc;
*q = (struct osmo_mslookup_query){};
if (!domain)
return -1;
last_dot = strrchr(domain, '.');
if (!last_dot)
return -2;
if (last_dot <= domain)
return -3;
for (second_last_dot = last_dot - 1; second_last_dot > domain && *second_last_dot != '.'; second_last_dot--);
if (second_last_dot == domain || *second_last_dot != '.')
return -3;
id_type = last_dot + 1;
if (!*id_type)
return -4;
q->id.type = get_string_value(osmo_mslookup_id_type_names, id_type);
id = second_last_dot + 1;
switch (q->id.type) {
case OSMO_MSLOOKUP_ID_IMSI:
rc = token(q->id.imsi, sizeof(q->id.imsi), id, last_dot);
if (rc)
return rc;
if (!osmo_imsi_str_valid(q->id.imsi))
return -5;
break;
case OSMO_MSLOOKUP_ID_MSISDN:
rc = token(q->id.msisdn, sizeof(q->id.msisdn), id, last_dot);
if (rc)
return rc;
if (!osmo_msisdn_str_valid(q->id.msisdn))
return -6;
break;
default:
return -7;
}
return token(q->service, sizeof(q->service), domain, second_last_dot);
}
/*! @} */

View File

@@ -0,0 +1,310 @@
/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <osmocom/hlr/logging.h>
#include <osmocom/mslookup/mslookup_client.h>
/*! Lookup client's internal data for a query. */
struct osmo_mslookup_client {
struct llist_head lookup_methods;
struct llist_head requests;
uint32_t next_request_handle;
};
/*! Lookup client's internal data for a query.
* The request methods only get to see the query part, and result handling is done commonly for all request methods. */
struct osmo_mslookup_client_request {
struct llist_head entry;
struct osmo_mslookup_client *client;
uint32_t request_handle;
struct osmo_mslookup_query query;
struct osmo_mslookup_query_handling handling;
struct osmo_timer_list timeout;
bool waiting_min_delay;
struct osmo_mslookup_result result;
};
static struct osmo_mslookup_client_request *get_request(struct osmo_mslookup_client *client, uint32_t request_handle)
{
struct osmo_mslookup_client_request *r;
if (!request_handle)
return NULL;
llist_for_each_entry(r, &client->requests, entry) {
if (r->request_handle == request_handle)
return r;
}
return NULL;
}
struct osmo_mslookup_client *osmo_mslookup_client_new(void *ctx)
{
struct osmo_mslookup_client *client = talloc_zero(ctx, struct osmo_mslookup_client);
OSMO_ASSERT(client);
INIT_LLIST_HEAD(&client->lookup_methods);
INIT_LLIST_HEAD(&client->requests);
return client;
}
/*! Return whether any lookup methods are available.
* \param[in] client Client to query.
* \return true when a client is present that has at least one osmo_mslookup_client_method registered.
*/
bool osmo_mslookup_client_active(struct osmo_mslookup_client *client)
{
if (!client)
return false;
if (llist_empty(&client->lookup_methods))
return false;
return true;
}
static void _osmo_mslookup_client_method_del(struct osmo_mslookup_client_method *method)
{
if (method->destruct)
method->destruct(method);
llist_del(&method->entry);
talloc_free(method);
}
/*! Stop and free mslookup client and all registered lookup methods.
*/
void osmo_mslookup_client_free(struct osmo_mslookup_client *client)
{
struct osmo_mslookup_client_method *m, *n;
if (!client)
return;
llist_for_each_entry_safe(m, n, &client->lookup_methods, entry) {
_osmo_mslookup_client_method_del(m);
}
talloc_free(client);
}
/*! Add an osmo_mslookup_client_method to service MS Lookup requests.
* Note, osmo_mslookup_client_method_del() will talloc_free() the method pointer, so it needs to be dynamically
* allocated.
* \param client The osmo_mslookup_client instance to add to.
* \param method A fully initialized method struct, allocated by talloc.
*/
void osmo_mslookup_client_method_add(struct osmo_mslookup_client *client,
struct osmo_mslookup_client_method *method)
{
method->client = client;
llist_add_tail(&method->entry, &client->lookup_methods);
}
/*! \return false if the method was not listed, true if the method was listed, removed and talloc_free()d.
*/
bool osmo_mslookup_client_method_del(struct osmo_mslookup_client *client,
struct osmo_mslookup_client_method *method)
{
struct osmo_mslookup_client_method *m;
llist_for_each_entry(m, &client->lookup_methods, entry) {
if (m == method) {
_osmo_mslookup_client_method_del(method);
return true;
}
}
return false;
}
static void osmo_mslookup_request_send_result(struct osmo_mslookup_client_request *r, bool finish)
{
struct osmo_mslookup_client *client = r->client;
uint32_t request_handle = r->request_handle;
r->result.last = finish;
r->handling.result_cb(r->client, r->request_handle, &r->query, &r->result);
/* Make sure the request struct is discarded.
* The result_cb() may already have triggered a cleanup, so query by request_handle. */
if (finish)
osmo_mslookup_client_request_cancel(client, request_handle);
}
void osmo_mslookup_client_rx_result(struct osmo_mslookup_client *client, uint32_t request_handle,
const struct osmo_mslookup_result *result)
{
struct osmo_mslookup_client_request *req = get_request(client, request_handle);
if (!req) {
LOGP(DMSLOOKUP, LOGL_ERROR,
"Internal error: Got mslookup result for a request that does not exist (handle %u)\n",
req->request_handle);
return;
}
/* Ignore incoming results that are not successful */
if (result->rc != OSMO_MSLOOKUP_RC_RESULT)
return;
/* If we already stored an earlier successful result, keep that if its age is younger. */
if (req->result.rc == OSMO_MSLOOKUP_RC_RESULT
&& result->age >= req->result.age)
return;
req->result = *result;
/* If age == 0, it doesn't get any better, so return the result immediately. */
if (req->result.age == 0) {
osmo_mslookup_request_send_result(req, true);
return;
}
if (req->waiting_min_delay)
return;
osmo_mslookup_request_send_result(req, false);
}
static void _osmo_mslookup_client_request_cleanup(struct osmo_mslookup_client_request *r)
{
struct osmo_mslookup_client_method *m;
osmo_timer_del(&r->timeout);
llist_for_each_entry(m, &r->client->lookup_methods, entry) {
if (!m->request_cleanup)
continue;
m->request_cleanup(m, r->request_handle);
}
llist_del(&r->entry);
talloc_free(r);
}
static void timeout_cb(void *data);
static void set_timer(struct osmo_mslookup_client_request *r, unsigned long milliseconds)
{
osmo_timer_setup(&r->timeout, timeout_cb, r);
osmo_timer_schedule(&r->timeout, milliseconds / 1000, (milliseconds % 1000) * 1000);
}
static void timeout_cb(void *data)
{
struct osmo_mslookup_client_request *r = data;
if (r->waiting_min_delay) {
/* The initial delay has passed. See if it stops here, or whether the overall timeout continues. */
r->waiting_min_delay = false;
if (r->handling.result_timeout_milliseconds <= r->handling.min_wait_milliseconds) {
/* It ends here. Return a final result. */
if (r->result.rc != OSMO_MSLOOKUP_RC_RESULT)
r->result.rc = OSMO_MSLOOKUP_RC_NOT_FOUND;
osmo_mslookup_request_send_result(r, true);
return;
}
/* We continue to listen for results. If one is already on record, send it now. */
if (r->result.rc == OSMO_MSLOOKUP_RC_RESULT)
osmo_mslookup_request_send_result(r, false);
set_timer(r, r->handling.result_timeout_milliseconds - r->handling.min_wait_milliseconds);
return;
}
/* The final timeout has passed, finish and clean up the request. */
switch (r->result.rc) {
case OSMO_MSLOOKUP_RC_RESULT:
/* If the rc == OSMO_MSLOOKUP_RC_RESULT, this result has already been sent.
* Don't send it again, instead send an RC_NONE, last=true result. */
r->result.rc = OSMO_MSLOOKUP_RC_NONE;
break;
default:
r->result.rc = OSMO_MSLOOKUP_RC_NOT_FOUND;
break;
}
osmo_mslookup_request_send_result(r, true);
}
/*! Launch a subscriber lookup with the provided query.
* A request is cleared implicitly when the handling->result_cb is invoked; if the quer->priv pointer becomes invalid
* before that, a request should be canceled by calling osmo_mslookup_client_request_cancel() with the returned
* request_handle. A request handle of zero indicates error.
* \return a nonzero request_handle that allows ending the request, or 0 on invalid query data. */
uint32_t osmo_mslookup_client_request(struct osmo_mslookup_client *client,
const struct osmo_mslookup_query *query,
const struct osmo_mslookup_query_handling *handling)
{
struct osmo_mslookup_client_request *r;
struct osmo_mslookup_client_request *other;
struct osmo_mslookup_client_method *m;
if (!osmo_mslookup_service_valid(query->service)
|| !osmo_mslookup_id_valid(&query->id)) {
char buf[256];
LOGP(DMSLOOKUP, LOGL_ERROR, "Invalid query: %s\n",
osmo_mslookup_result_name_b(buf, sizeof(buf), query, NULL));
return 0;
}
r = talloc_zero(client, struct osmo_mslookup_client_request);
OSMO_ASSERT(r);
/* A request_handle of zero means error, so make sure we don't use a zero handle. */
if (!client->next_request_handle)
client->next_request_handle++;
*r = (struct osmo_mslookup_client_request){
.client = client,
.query = *query,
.handling = *handling,
.request_handle = client->next_request_handle++,
};
if (!r->handling.result_timeout_milliseconds)
r->handling.result_timeout_milliseconds = r->handling.min_wait_milliseconds;
if (!r->handling.result_timeout_milliseconds)
r->handling.result_timeout_milliseconds = 1000;
/* Paranoia: make sure a request_handle exists only once, by expiring an already existing one. This is unlikely
* to happen in practice: before we get near wrapping a uint32_t range, previous requests should long have
* timed out or ended. */
llist_for_each_entry(other, &client->requests, entry) {
if (other->request_handle != r->request_handle)
continue;
osmo_mslookup_request_send_result(other, true);
/* we're sure it exists only once. */
break;
}
/* Now sure that the new request_handle does not exist a second time. */
llist_add_tail(&r->entry, &client->requests);
if (r->handling.min_wait_milliseconds) {
r->waiting_min_delay = true;
set_timer(r, r->handling.min_wait_milliseconds);
} else {
set_timer(r, r->handling.result_timeout_milliseconds);
}
/* Let the lookup implementations know */
llist_for_each_entry(m, &client->lookup_methods, entry) {
m->request(m, query, r->request_handle);
}
return r->request_handle;
}
/*! End or cancel a subscriber lookup. This *must* be invoked exactly once per osmo_mslookup_client_request() invocation,
* either after a lookup has concluded or to abort an ongoing lookup.
* \param[in] request_handle The request_handle returned by an osmo_mslookup_client_request() invocation.
*/
void osmo_mslookup_client_request_cancel(struct osmo_mslookup_client *client, uint32_t request_handle)
{
struct osmo_mslookup_client_request *r = get_request(client, request_handle);
if (!r)
return;
_osmo_mslookup_client_request_cleanup(r);
}

View File

@@ -0,0 +1,156 @@
/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <osmocom/hlr/logging.h>
#include <osmocom/mslookup/mslookup_client.h>
#include <osmocom/mslookup/mslookup_client_fake.h>
#include <string.h>
/* Fake mslookup method */
struct fake_lookup_state {
struct osmo_mslookup_client *client;
struct llist_head requests;
struct osmo_timer_list async_response_timer;
struct osmo_mslookup_fake_response *responses;
size_t responses_len;
};
struct fake_lookup_request {
struct llist_head entry;
uint32_t request_handle;
struct osmo_mslookup_query query;
struct timeval received_at;
};
/*! Args for osmo_timer_schedule: seconds and microseconds. */
#define ASYNC_RESPONSE_PERIOD 0, (1e6 / 10)
static void fake_lookup_async_response(void *state);
static void fake_lookup_request(struct osmo_mslookup_client_method *method,
const struct osmo_mslookup_query *query,
uint32_t request_handle)
{
struct fake_lookup_state *state = method->priv;
char buf[256];
LOGP(DMSLOOKUP, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_mslookup_result_name_b(buf, sizeof(buf), query, NULL));
/* A real implementation would send packets to some remote server.
* Here this is simulated: add to the list of requests, which fake_lookup_async_response() will reply upon
* according to the test data listing the replies that the test wants to generate. */
struct fake_lookup_request *r = talloc_zero(method->client, struct fake_lookup_request);
*r = (struct fake_lookup_request){
.request_handle = request_handle,
.query = *query,
};
osmo_gettimeofday(&r->received_at, NULL);
llist_add_tail(&r->entry, &state->requests);
}
static void fake_lookup_request_cleanup(struct osmo_mslookup_client_method *method,
uint32_t request_handle)
{
struct fake_lookup_state *state = method->priv;
/* Tear down any state associated with this handle. */
struct fake_lookup_request *r;
llist_for_each_entry(r, &state->requests, entry) {
if (r->request_handle != request_handle)
continue;
llist_del(&r->entry);
talloc_free(r);
LOGP(DMSLOOKUP, LOGL_DEBUG, "%s() ok\n", __func__);
return;
}
LOGP(DMSLOOKUP, LOGL_DEBUG, "%s() FAILED\n", __func__);
}
static void fake_lookup_async_response(void *data)
{
struct fake_lookup_state *state = data;
struct fake_lookup_request *req, *n;
struct timeval now;
char str[256];
osmo_gettimeofday(&now, NULL);
llist_for_each_entry_safe(req, n, &state->requests, entry) {
struct osmo_mslookup_fake_response *resp;
for (resp = state->responses;
(resp - state->responses) < state->responses_len;
resp++) {
struct timeval diff;
if (resp->sent)
continue;
if (osmo_mslookup_id_cmp(&req->query.id, &resp->for_id) != 0)
continue;
if (strcmp(req->query.service, resp->for_service) != 0)
continue;
timersub(&now, &req->received_at, &diff);
if (timercmp(&diff, &resp->time_to_reply, <))
continue;
/* It's time to reply to this request. */
LOGP(DMSLOOKUP, LOGL_DEBUG, "osmo_mslookup_client_rx_result(): %s\n",
osmo_mslookup_result_name_b(str, sizeof(str), &req->query, &resp->result));
osmo_mslookup_client_rx_result(state->client, req->request_handle, &resp->result);
resp->sent = true;
/* The req will have been cleaned up now, so we must not iterate over state->responses anymore
* with this req. */
break;
}
}
osmo_timer_schedule(&state->async_response_timer, ASYNC_RESPONSE_PERIOD);
}
struct osmo_mslookup_client_method *osmo_mslookup_client_add_fake(struct osmo_mslookup_client *client,
struct osmo_mslookup_fake_response *responses,
size_t responses_len)
{
struct osmo_mslookup_client_method *method = talloc_zero(client, struct osmo_mslookup_client_method);
OSMO_ASSERT(method);
struct fake_lookup_state *state = talloc_zero(method, struct fake_lookup_state);
OSMO_ASSERT(state);
*state = (struct fake_lookup_state){
.client = client,
.responses = responses,
.responses_len = responses_len,
};
INIT_LLIST_HEAD(&state->requests);
*method = (struct osmo_mslookup_client_method){
.name = "fake",
.priv = state,
.request = fake_lookup_request,
.request_cleanup = fake_lookup_request_cleanup,
};
osmo_timer_setup(&state->async_response_timer, fake_lookup_async_response, state);
osmo_mslookup_client_method_add(client, method);
osmo_timer_schedule(&state->async_response_timer, ASYNC_RESPONSE_PERIOD);
return method;
}

View File

@@ -0,0 +1,235 @@
/* 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 <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <osmocom/core/select.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/hlr/logging.h>
#include <osmocom/mslookup/mdns.h>
#include <osmocom/mslookup/mdns_sock.h>
#include <osmocom/mslookup/mslookup_client.h>
#include <osmocom/mslookup/mslookup_client_mdns.h>
struct osmo_mdns_method_state {
/* Parameters passed by _add_method_dns() */
struct osmo_sockaddr_str bind_addr;
const char *domain_suffix;
struct osmo_mdns_sock *mc;
struct osmo_mslookup_client *client;
struct llist_head requests;
uint16_t next_packet_id;
};
struct osmo_mdns_method_request {
struct llist_head entry;
uint32_t request_handle;
struct osmo_mslookup_query query;
uint16_t packet_id;
};
static int request_handle_by_query(uint32_t *request_handle, struct osmo_mdns_method_state *state,
struct osmo_mslookup_query *query, uint16_t packet_id)
{
struct osmo_mdns_method_request *request;
llist_for_each_entry(request, &state->requests, entry) {
if (strcmp(request->query.service, query->service) != 0)
continue;
if (osmo_mslookup_id_cmp(&request->query.id, &query->id) != 0)
continue;
/* Match! */
*request_handle = request->request_handle;
return 0;
}
return -1;
}
static int mdns_method_recv(struct osmo_fd *osmo_fd, unsigned int what)
{
struct osmo_mdns_method_state *state = osmo_fd->data;
struct osmo_mslookup_result result;
struct osmo_mslookup_query query;
uint16_t packet_id;
int n;
uint8_t buffer[1024];
uint32_t request_handle = 0;
void *ctx = state;
n = read(osmo_fd->fd, buffer, sizeof(buffer));
if (n < 0) {
LOGP(DMSLOOKUP, LOGL_ERROR, "failed to read from socket\n");
return n;
}
if (osmo_mdns_result_decode(ctx, buffer, n, &packet_id, &query, &result, state->domain_suffix) < 0)
return -EINVAL;
if (request_handle_by_query(&request_handle, state, &query, packet_id) != 0)
return -EINVAL;
osmo_mslookup_client_rx_result(state->client, request_handle, &result);
return n;
}
static void mdns_method_request(struct osmo_mslookup_client_method *method, const struct osmo_mslookup_query *query,
uint32_t request_handle)
{
char buf[256];
struct osmo_mdns_method_state *state = method->priv;
struct msgb *msg;
struct osmo_mdns_method_request *r = talloc_zero(method->client, struct osmo_mdns_method_request);
*r = (struct osmo_mdns_method_request){
.request_handle = request_handle,
.query = *query,
.packet_id = state->next_packet_id,
};
llist_add(&r->entry, &state->requests);
state->next_packet_id++;
msg = osmo_mdns_query_encode(method->client, r->packet_id, query, state->domain_suffix);
if (!msg) {
LOGP(DMSLOOKUP, LOGL_ERROR, "Cannot encode request: %s\n",
osmo_mslookup_result_name_b(buf, sizeof(buf), query, NULL));
}
/* Send over the wire */
LOGP(DMSLOOKUP, LOGL_DEBUG, "sending mDNS query: %s.%s\n", query->service,
osmo_mslookup_id_name_b(buf, sizeof(buf), &query->id));
if (osmo_mdns_sock_send(state->mc, msg) == -1)
LOGP(DMSLOOKUP, LOGL_ERROR, "sending mDNS query failed\n");
}
static void mdns_method_request_cleanup(struct osmo_mslookup_client_method *method, uint32_t request_handle)
{
struct osmo_mdns_method_state *state = method->priv;
/* Tear down any state associated with this handle. */
struct osmo_mdns_method_request *r;
llist_for_each_entry(r, &state->requests, entry) {
if (r->request_handle != request_handle)
continue;
llist_del(&r->entry);
talloc_free(r);
return;
}
}
static void mdns_method_destruct(struct osmo_mslookup_client_method *method)
{
struct osmo_mdns_method_state *state = method->priv;
struct osmo_mdns_method_request *e, *n;
if (!state)
return;
/* Drop all DNS lookup request state. Triggering a timeout event and cleanup for mslookup client users will
* happen in the mslookup_client.c, we will simply stop responding from this lookup method. */
llist_for_each_entry_safe(e, n, &state->requests, entry) {
llist_del(&e->entry);
}
osmo_mdns_sock_cleanup(state->mc);
}
/*! Initialize the mDNS lookup method.
* \param[in] client the client to attach the method to.
* \param[in] ip IPv4 or IPv6 address string.
* \param[in] port The port to bind to.
* \param[in] initial_packet_id Used in the first mslookup query, then increased by one in each following query. All
* servers answer to each query with the same packet ID. Set to -1 to use a random
* initial ID (recommended unless you need deterministic output). This ID is for visually
* distinguishing the packets in packet sniffers, the mslookup client uses not just the
* ID, but all query parameters (service type, ID, ID type), to determine if a reply is
* relevant.
* \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" */
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)
{
struct osmo_mdns_method_state *state;
struct osmo_mslookup_client_method *m;
m = talloc_zero(client, struct osmo_mslookup_client_method);
OSMO_ASSERT(m);
state = talloc_zero(m, struct osmo_mdns_method_state);
OSMO_ASSERT(state);
INIT_LLIST_HEAD(&state->requests);
if (osmo_sockaddr_str_from_str(&state->bind_addr, ip, port)) {
LOGP(DMSLOOKUP, LOGL_ERROR, "mslookup mDNS: invalid address/port: %s %u\n",
ip, port);
goto error_cleanup;
}
if (initial_packet_id == -1) {
if (osmo_get_rand_id((uint8_t *)&state->next_packet_id, 2) < 0) {
LOGP(DMSLOOKUP, LOGL_ERROR, "mslookup mDNS: failed to generate random initial packet ID\n");
goto error_cleanup;
}
} else
state->next_packet_id = initial_packet_id;
state->client = client;
state->domain_suffix = domain_suffix;
state->mc = osmo_mdns_sock_init(state, ip, port, mdns_method_recv, state, 0);
if (!state->mc)
goto error_cleanup;
*m = (struct osmo_mslookup_client_method){
.name = "mDNS",
.priv = state,
.request = mdns_method_request,
.request_cleanup = mdns_method_request_cleanup,
.destruct = mdns_method_destruct,
};
osmo_mslookup_client_method_add(client, m);
return m;
error_cleanup:
talloc_free(m);
return NULL;
}
const struct osmo_sockaddr_str *osmo_mslookup_client_method_mdns_get_bind_addr(struct osmo_mslookup_client_method *dns_method)
{
struct osmo_mdns_method_state *state;
if (!dns_method || !dns_method->priv)
return NULL;
state = dns_method->priv;
return &state->bind_addr;
}
const char *osmo_mslookup_client_method_mdns_get_domain_suffix(struct osmo_mslookup_client_method *dns_method)
{
struct osmo_mdns_method_state *state;
if (!dns_method || !dns_method->priv)
return NULL;
state = dns_method->priv;
return state->domain_suffix;
}

View File

@@ -0,0 +1,879 @@
/*! \file osmo-mslookup-client.c
* Distributed GSM: find the location of subscribers, for example by multicast DNS,
* to obtain HLR, SIP or SMPP server addresses (or arbitrary service names).
*/
/*
* (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* (C) 2019 by Neels Hofmeyr <neels@hofmeyr.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 <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <talloc.h>
#include <sys/un.h>
#include <osmocom/core/application.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/mslookup/mslookup_client.h>
#include <osmocom/mslookup/mslookup_client_mdns.h>
#include <osmocom/mslookup/mdns_sock.h>
#include <osmocom/mslookup/mdns.h>
#define CSV_HEADERS "query\tresult\tlast\tage\tv4_ip\tv4_port\tv6_ip\tv6_port"
static void print_version(void)
{
printf("osmo-mslookup-client version %s\n", PACKAGE_VERSION);
printf("\n"
"Copyright (C) 2019 by sysmocom - s.f.m.c. GmbH\n"
"Copyright (C) 2019 by Neels Hofmeyr <neels@hofmeyr.de>\n"
"This program is free software; you can redistribute it and/or modify\n"
"it under the terms of the GNU General Public License as published by\n"
"the Free Software Foundation; either version 2 of the License, or\n"
"(at your option) any later version.\n"
"\n");
}
static void print_help()
{
print_version();
printf(
"Standalone mslookup client for Distributed GSM\n"
"\n"
"Receiving mslookup results means listening for responses on a socket. Often,\n"
"integration (e.g. FreeSwitch dialplan.py) makes it hard to select() on a socket\n"
"to read responses, because that interferes with the main program (e.g.\n"
"FreeSwitch's dialplan.py seems to be integrated with an own select() main loop\n"
"that interferes with osmo_select_main(), or an smpp.py uses\n"
"smpplib.client.listen() as main loop, etc.).\n"
"\n"
"This program provides a trivial solution, by outsourcing the mslookup main loop\n"
"to a separate process. Communication is done via cmdline arg and stdout pipe or\n"
"a (blocking) unix domain socket, results are returned in CSV or JSON format.\n"
"\n"
"This can be done one-shot, i.e. exit as soon as the response has been\n"
"determined, or in daemon form, i.e. continuously listen for requests and return\n"
"responses.\n"
"\n"
"About running a local daemon: it is unintuitive to connect to a socket to solve\n"
"a problem of reading from a socket -- it seems like just more of the same\n"
"problem. The reasons why the daemon is in fact useful are:\n"
"- The osmo-mslookup-client daemon will return only those results matching\n"
" requests issued on that socket connection.\n"
"- A program can simply blockingly recv() from the osmo-mslookup-client socket\n"
" instead of needing to run osmo_select_main() so that libosmo-mslookup is able\n"
" to asynchronously receive responses from remote servers.\n"
"- Only one long-lived multicast socket needs to be opened instead of a new\n"
" socket for each request.\n"
"\n"
"Output is in CSV or json, see --format. The default is tab-separated CSV\n"
"with these columns:\n"
CSV_HEADERS "\n"
"\n"
"One-shot operation example:\n"
"$ osmo-mslookup-client 1000-@sip.voice.12345.msisdn -f json\n"
"{\"query\": \"sip.voice.12345.msisdn\", \"result\": \"result\", \"last\": true, \"age\": 5, \"v4\": [\"1.2.3.7\", \"23\"]}\n"
"$\n"
"\n"
"Daemon operation example:\n"
"$ osmo-mslookup-client -s /tmp/mslookup -d\n"
"(and a client program then connects to /tmp/mslookup, find an implementation\n"
"example below)\n"
"\n"
"Integrating with calling programs can be done by:\n"
"- call osmo-mslookup-client with the query string as argument.\n"
" It will open a multicast DNS socket, send out a query and wait for the\n"
" matching response. It will print the result on stdout and exit.\n"
" This method launches a new process for every mslookup query,\n"
" and creates a short-lived multicast listener for each invocation.\n"
" This is fine for low activity, but does not scale well.\n"
"\n"
"- invoke osmo-mslookup-client --socket /tmp/mslookup -d.\n"
" Individual queries can be sent by connecting to that unix domain socket,\n"
" blockingly reading the response when it arrives and disconnecting.\n"
" This way only one process keeps one multicast listener open.\n"
" Callers can connect to this socket without spawning processes.\n"
" This is recommended for scale.\n"
"\n"
"Python example clients for {CSV,JSON}x{cmdline,socket} can be found here:\n"
"http://git.osmocom.org/osmo-hlr/tree/contrib/dgsm/osmo-mslookup-pipe.py\n"
"http://git.osmocom.org/osmo-hlr/tree/contrib/dgsm/osmo-mslookup-socket.py\n"
"\n"
"\n"
"Options:\n"
"\n"
"[[delay-][timeout]@]service.number.id\n"
" A service query string with optional individual timeout.\n"
" The same format is also used on a daemon socket, if any.\n"
" The timeout consists of the min-delay and the timeout numbers,\n"
" corresponding to the --min-delay and --timeout options, in milliseconds.\n"
" These options apply if a query string lacks own numbers.\n"
" Examples:\n"
" gsup.hlr.1234567.imsi Use cmdline timeout settings\n"
" 5000@gsup.hlr.1234567.imsi Return N results for 5 seconds\n"
" 1000-5000@sip.voice.123.msisdn Same, but silent for first second\n"
" 10000-@smpp.sms.567.msisdn Return 1 result after 10 seconds\n"
"\n"
"--format -f csv (default)\n"
" Format result lines in CSV format.\n"
"--no-csv-headers -H\n"
" If the format is 'csv', by default, the first output line prints the\n"
" CSV headers used for CSV output format. This option disables these CSV\n"
" headers.\n"
"\n"
"--format -f json\n"
" Format result lines in json instead of semicolon separated, like:\n"
" {\"query\": \"sip.voice.12345.msisdn\", \"result\": \"ok\", \"v4\": [\"10.9.8.7\", \"5060\"]}\n"
"\n"
"--daemon -d\n"
" Keep running after a request has been serviced\n"
"\n"
"--mdns-ip -m " OSMO_MSLOOKUP_MDNS_IP4 " -m " OSMO_MSLOOKUP_MDNS_IP6 "\n"
"--mdns-port -M " OSMO_STRINGIFY_VAL(OSMO_MSLOOKUP_MDNS_PORT) "\n"
" Set multicast IP address / port to send mDNS requests and listen for\n"
" mDNS reponses\n"
"--mdns-domain-suffix -D " OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT "\n"
" Append this suffix to each mDNS query's domain to avoid colliding with the\n"
" top-level domains administrated by IANA.\n"
"\n"
"--min-delay -t 1000 (in milliseconds)\n"
" Set minimum delay to wait before returning any results.\n"
" When this timeout has elapsed, the best current result is returned,\n"
" if any is available.\n"
" Responses arriving after the min-delay has elapsed which have a younger\n"
" age than previous results are returned immediately.\n"
" Note: When a response with age of zero comes in, the result is returned\n"
" immediately and the request is discarded: non-daemon mode exits, daemon\n"
" mode ignores later results.\n"
"\n"
"--timeout -T 1000 (in milliseconds)\n"
" Set timeout after which to stop listening for responses.\n"
" If this is smaller than -t, the value from -t will be used for -T as well.\n"
" Note: When a response with age of zero comes in, the result is returned\n"
" immediately and the request is discarded: non-daemon mode exits, daemon\n"
" mode ignores later results.\n"
"\n"
"--socket -s /path/to/unix-domain-socket\n"
" Listen to requests from and write responses to a UNIX domain socket.\n"
"\n"
"--send -S <query> <age> <ip1> <port1> <ip2> <port2>\n"
" Do not query, but send an mslookup result. This is useful only for\n"
" testing. Examples:\n"
" --send foo.123.msisdn 300 23.42.17.11 1234\n"
" --send foo.123.msisdn 300 2323:4242:1717:1111::42 1234\n"
" --send foo.123.msisdn 300 23.42.17.11 1234 2323:4242:1717:1111::42 1234\n"
"\n"
"--quiet -q\n"
" Do not print errors to stderr, do not log to stderr.\n"
"\n"
"--help -h\n"
" This help\n"
);
}
enum result_format {
FORMAT_CSV = 0,
FORMAT_JSON,
};
static struct {
bool daemon;
struct osmo_sockaddr_str mdns_addr;
uint32_t min_delay;
uint32_t timeout;
const char *socket_path;
const char *format_str;
const char *mdns_domain_suffix;
bool csv_headers;
bool send;
bool quiet;
} cmdline_opts = {
.mdns_addr = { .af=AF_INET, .ip=OSMO_MSLOOKUP_MDNS_IP4, .port=OSMO_MSLOOKUP_MDNS_PORT },
.min_delay = 1000,
.timeout = 1000,
.csv_headers = true,
.mdns_domain_suffix = OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT,
};
#define print_error(fmt, args...) do { \
if (!cmdline_opts.quiet) \
fprintf(stderr, fmt, ##args); \
} while (0)
char g_buf[1024];
long long int parse_int(long long int minval, long long int maxval, const char *arg, int *rc)
{
long long int val;
char *endptr;
if (rc)
*rc = -1;
if (!arg)
return -1;
errno = 0;
val = strtoll(arg, &endptr, 10);
if (errno || val < minval || val > maxval || *endptr)
return -1;
if (rc)
*rc = 0;
return val;
}
int cb_doing_nothing(struct osmo_fd *fd, unsigned int what)
{
return 0;
}
/* --send: Just send a response, for manual testing. */
int do_send(int argc, char ** argv)
{
/* parse args <query> <age> <v4-ip> <v4-port> <v6-ip> <v6-port> */
#define ARG(NR) ((argc > NR)? argv[NR] : NULL)
const char *query_str = ARG(0);
const char *age_str = ARG(1);
const char *ip_strs[2][2] = {
{ ARG(2), ARG(3) },
{ ARG(4), ARG(5) },
};
struct osmo_mslookup_query q = {};
struct osmo_mslookup_result r = { .rc = OSMO_MSLOOKUP_RC_RESULT };
int i;
int rc;
void *ctx = talloc_named_const(NULL, 0, __func__);
struct osmo_mdns_sock *sock;
if (!query_str) {
print_error("--send needs a query string like foo.123456.imsi\n");
return 1;
}
if (osmo_mslookup_query_init_from_domain_str(&q, query_str)) {
print_error("Invalid query string '%s', need a query string like foo.123456.imsi\n",
query_str);
return 1;
}
if (!age_str) {
print_error("--send needs an age\n");
return 1;
}
r.age = parse_int(0, UINT32_MAX, age_str, &rc);
if (rc) {
print_error("invalid age\n");
return 1;
}
for (i = 0; i < 2; i++) {
struct osmo_sockaddr_str addr;
uint16_t port;
if (!ip_strs[i][0])
continue;
port = parse_int(1, 65535, ip_strs[i][1] ? : "2342", &rc);
if (rc) {
print_error("invalid port: %s\n", ip_strs[i][1] ? : "NULL");
return 1;
}
if (osmo_sockaddr_str_from_str(&addr, ip_strs[i][0], port)) {
print_error("invalid IP addr: %s\n", ip_strs[i][0]);
return 1;
}
if (addr.af == AF_INET)
r.host_v4 = addr;
else
r.host_v6 = addr;
}
printf("Sending mDNS to " OSMO_SOCKADDR_STR_FMT ": %s\n", OSMO_SOCKADDR_STR_FMT_ARGS(&cmdline_opts.mdns_addr),
osmo_mslookup_result_name_c(ctx, &q, &r));
rc = 1;
sock = osmo_mdns_sock_init(ctx, cmdline_opts.mdns_addr.ip, cmdline_opts.mdns_addr.port,
cb_doing_nothing, NULL, 0);
if (!sock) {
print_error("unable to open mDNS socket\n");
goto exit_cleanup;
}
struct msgb *msg = osmo_mdns_result_encode(ctx, 0, &q, &r, cmdline_opts.mdns_domain_suffix);
if (!msg) {
print_error("unable to encode mDNS response\n");
goto exit_cleanup;
}
if (osmo_mdns_sock_send(sock, msg)) {
print_error("unable to send mDNS message\n");
goto exit_cleanup;
}
rc = 0;
exit_cleanup:
osmo_mdns_sock_cleanup(sock);
talloc_free(ctx);
return rc;
}
static struct {
void *ctx;
unsigned int requests_handled;
struct osmo_fd socket_ofd;
struct osmo_mslookup_client *mslookup_client;
struct llist_head queries;
struct llist_head socket_clients;
enum result_format format;
} globals = {
.queries = LLIST_HEAD_INIT(globals.queries),
.socket_clients = LLIST_HEAD_INIT(globals.socket_clients),
};
typedef void (*formatter_t)(char *buf, size_t buflen, const char *query_str, const struct osmo_mslookup_result *r);
void formatter_csv(char *buf, size_t buflen, const char *query_str, const struct osmo_mslookup_result *r)
{
struct osmo_strbuf sb = { .buf=buf, .len=buflen };
OSMO_STRBUF_PRINTF(sb, "%s", query_str);
if (!r)
OSMO_STRBUF_PRINTF(sb, "\tERROR\t\t\t\t\t\t");
else {
OSMO_STRBUF_PRINTF(sb, "\t%s", osmo_mslookup_result_code_name(r->rc));
OSMO_STRBUF_PRINTF(sb, "\t%s", r->last ? "last" : "not-last");
OSMO_STRBUF_PRINTF(sb, "\t%u", r->age);
switch (r->rc) {
case OSMO_MSLOOKUP_RC_RESULT:
if (osmo_sockaddr_str_is_nonzero(&r->host_v4))
OSMO_STRBUF_PRINTF(sb, "\t%s\t%u", r->host_v4.ip, r->host_v4.port);
else
OSMO_STRBUF_PRINTF(sb, "\t\t");
if (osmo_sockaddr_str_is_nonzero(&r->host_v6))
OSMO_STRBUF_PRINTF(sb, "\t%s\t%u", r->host_v6.ip, r->host_v6.port);
else
OSMO_STRBUF_PRINTF(sb, "\t\t");
break;
default:
OSMO_STRBUF_PRINTF(sb, "\t\t\t\t\t");
break;
}
}
}
void formatter_json(char *buf, size_t buflen, const char *query_str, const struct osmo_mslookup_result *r)
{
struct osmo_strbuf sb = { .buf=buf, .len=buflen };
OSMO_STRBUF_PRINTF(sb, "{\"query\": \"%s\"", query_str);
if (!r)
OSMO_STRBUF_PRINTF(sb, ", \"result\": \"ERROR\"");
else {
OSMO_STRBUF_PRINTF(sb, ", \"result\": \"%s\"", osmo_mslookup_result_code_name(r->rc));
OSMO_STRBUF_PRINTF(sb, ", \"last\": %s", r->last ? "true" : "false");
OSMO_STRBUF_PRINTF(sb, ", \"age\": %u", r->age);
if (r->rc == OSMO_MSLOOKUP_RC_RESULT) {
if (osmo_sockaddr_str_is_nonzero(&r->host_v4))
OSMO_STRBUF_PRINTF(sb, ", \"v4\": [\"%s\", \"%u\"]", r->host_v4.ip, r->host_v4.port);
if (osmo_sockaddr_str_is_nonzero(&r->host_v6))
OSMO_STRBUF_PRINTF(sb, ", \"v6\": [\"%s\", \"%u\"]", r->host_v6.ip, r->host_v6.port);
}
}
OSMO_STRBUF_PRINTF(sb, "}");
}
formatter_t formatters[] = {
[FORMAT_CSV] = formatter_csv,
[FORMAT_JSON] = formatter_json,
};
void respond_str_stdout(const char *str) {
fprintf(stdout, "%s\n", str);
fflush(stdout);
}
void start_query_str(const char *query_str);
void start_query_strs(char **query_strs, size_t query_strs_len);
struct socket_client {
struct llist_head entry;
struct osmo_fd ofd;
char query_str[1024];
};
static void socket_client_close(struct socket_client *c)
{
struct osmo_fd *ofd = &c->ofd;
close(ofd->fd);
ofd->fd = -1;
osmo_fd_unregister(ofd);
llist_del(&c->entry);
talloc_free(c);
}
void socket_client_respond_result(struct socket_client *c, const char *response)
{
write(c->ofd.fd, response, strlen(response));
}
static int socket_read_cb(struct osmo_fd *ofd)
{
struct socket_client *c = ofd->data;
int rc;
char rxbuf[1024];
char *query_with_timeout;
char *query_str;
char *at;
rc = recv(ofd->fd, rxbuf, sizeof(rxbuf), 0);
if (rc == 0)
goto close;
if (rc < 0) {
if (errno == EAGAIN)
return 0;
goto close;
}
if (rc >= sizeof(c->query_str))
goto close;
rxbuf[rc] = '\0';
query_with_timeout = strtok(rxbuf, "\r\n");
at = strchr(query_with_timeout, '@');
query_str = at ? at + 1 : query_with_timeout;
if (c->query_str[0]) {
print_error("ERROR: Only one query per client connect is allowed;"
" received '%s' and '%s' on the same connection\n",
c->query_str, query_str);
formatters[globals.format](g_buf, sizeof(g_buf), query_str, NULL);
socket_client_respond_result(c, g_buf);
return 0;
}
OSMO_STRLCPY_ARRAY(c->query_str, query_str);
start_query_str(query_with_timeout);
printf("query: %s\n", query_with_timeout);
return rc;
close:
socket_client_close(c);
return -1;
}
static int socket_cb(struct osmo_fd *ofd, unsigned int flags)
{
int rc = 0;
if (flags & BSC_FD_READ)
rc = socket_read_cb(ofd);
if (rc < 0)
return rc;
return rc;
}
int socket_accept(struct osmo_fd *ofd, unsigned int flags)
{
struct socket_client *c;
struct sockaddr_un un_addr;
socklen_t len;
int rc;
len = sizeof(un_addr);
rc = accept(ofd->fd, (struct sockaddr*)&un_addr, &len);
if (rc < 0) {
print_error("Failed to accept a new connection\n");
return -1;
}
c = talloc_zero(globals.ctx, struct socket_client);
OSMO_ASSERT(c);
c->ofd.fd = rc;
c->ofd.when = BSC_FD_READ;
c->ofd.cb = socket_cb;
c->ofd.data = c;
if (osmo_fd_register(&c->ofd) != 0) {
print_error("Failed to register new connection fd\n");
close(c->ofd.fd);
c->ofd.fd = -1;
talloc_free(c);
return -1;
}
llist_add(&c->entry, &globals.socket_clients);
if (globals.format == FORMAT_CSV && cmdline_opts.csv_headers)
write(c->ofd.fd, CSV_HEADERS, strlen(CSV_HEADERS));
return 0;
}
int socket_init(const char *sock_path)
{
struct osmo_fd *ofd = &globals.socket_ofd;
int rc;
ofd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, sock_path, OSMO_SOCK_F_BIND);
if (ofd->fd < 0) {
print_error("Could not create unix socket: %s: %s\n", sock_path, strerror(errno));
return -1;
}
ofd->when = BSC_FD_READ;
ofd->cb = socket_accept;
rc = osmo_fd_register(ofd);
if (rc < 0) {
print_error("Could not register listen fd: %d\n", rc);
close(ofd->fd);
return rc;
}
return 0;
}
void socket_close()
{
struct socket_client *c, *n;
llist_for_each_entry_safe(c, n, &globals.socket_clients, entry)
socket_client_close(c);
if (osmo_fd_is_registered(&globals.socket_ofd)) {
close(globals.socket_ofd.fd);
globals.socket_ofd.fd = -1;
osmo_fd_unregister(&globals.socket_ofd);
}
}
struct query {
struct llist_head entry;
char query_str[128];
struct osmo_mslookup_query query;
uint32_t handle;
};
void respond_result(const char *query_str, const struct osmo_mslookup_result *r)
{
struct socket_client *c, *n;
formatters[globals.format](g_buf, sizeof(g_buf), query_str, r);
respond_str_stdout(g_buf);
llist_for_each_entry_safe(c, n, &globals.socket_clients, entry) {
if (!strcmp(query_str, c->query_str)) {
socket_client_respond_result(c, g_buf);
if (r->last)
socket_client_close(c);
}
}
if (r->last)
globals.requests_handled++;
}
void respond_err(const char *query_str)
{
respond_result(query_str, NULL);
}
struct query *query_by_handle(uint32_t request_handle)
{
struct query *q;
llist_for_each_entry(q, &globals.queries, entry) {
if (request_handle == q->handle)
return q;
}
return NULL;
}
void mslookup_result_cb(struct osmo_mslookup_client *client,
uint32_t request_handle,
const struct osmo_mslookup_query *query,
const struct osmo_mslookup_result *result)
{
struct query *q = query_by_handle(request_handle);
if (!q)
return;
respond_result(q->query_str, result);
if (result->last) {
llist_del(&q->entry);
talloc_free(q);
}
}
void start_query_str(const char *query_str)
{
struct query *q;
const char *domain_str = query_str;
char *at;
struct osmo_mslookup_query_handling h = {
.min_wait_milliseconds = cmdline_opts.min_delay,
.result_timeout_milliseconds = cmdline_opts.timeout,
.result_cb = mslookup_result_cb,
};
at = strchr(query_str, '@');
if (at) {
int rc;
char timeouts[16];
char *dash;
char *timeout;
domain_str = at + 1;
h.min_wait_milliseconds = h.result_timeout_milliseconds = 0;
if (osmo_print_n(timeouts, sizeof(timeouts), query_str, at - query_str) >= sizeof(timeouts)) {
print_error("ERROR: timeouts part too long in query string\n");
respond_err(domain_str);
return;
}
dash = strchr(timeouts, '-');
if (dash) {
char min_delay[16];
osmo_print_n(min_delay, sizeof(min_delay), timeouts, dash - timeouts);
h.min_wait_milliseconds = parse_int(0, UINT32_MAX, min_delay, &rc);
if (rc) {
print_error("ERROR: invalid min-delay number: %s\n", min_delay);
respond_err(domain_str);
return;
}
timeout = dash + 1;
} else {
timeout = timeouts;
}
if (*timeout) {
h.result_timeout_milliseconds = parse_int(0, UINT32_MAX, timeout, &rc);
if (rc) {
print_error("ERROR: invalid timeout number: %s\n", timeout);
respond_err(domain_str);
return;
}
}
}
if (strlen(domain_str) >= sizeof(q->query_str)) {
print_error("ERROR: query string is too long: '%s'\n", domain_str);
respond_err(domain_str);
return;
}
q = talloc_zero(globals.ctx, struct query);
OSMO_ASSERT(q);
OSMO_STRLCPY_ARRAY(q->query_str, domain_str);
if (osmo_mslookup_query_init_from_domain_str(&q->query, q->query_str)) {
print_error("ERROR: cannot parse query string: '%s'\n", domain_str);
respond_err(domain_str);
talloc_free(q);
return;
}
q->handle = osmo_mslookup_client_request(globals.mslookup_client, &q->query, &h);
if (!q->handle) {
print_error("ERROR: cannot send query: '%s'\n", domain_str);
respond_err(domain_str);
talloc_free(q);
return;
}
llist_add(&q->entry, &globals.queries);
}
void start_query_strs(char **query_strs, size_t query_strs_len)
{
int i;
for (i = 0; i < query_strs_len; i++)
start_query_str(query_strs[i]);
}
int main(int argc, char **argv)
{
int rc = EXIT_FAILURE;
globals.ctx = talloc_named_const(NULL, 0, "osmo-mslookup-client");
osmo_init_logging2(globals.ctx, NULL);
log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME);
log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END);
log_set_print_level(osmo_stderr_target, 1);
log_set_print_category(osmo_stderr_target, 1);
log_set_print_category_hex(osmo_stderr_target, 0);
log_set_print_extended_timestamp(osmo_stderr_target, 1);
log_set_use_color(osmo_stderr_target, 0);
while (1) {
int c;
long long int val;
char *endptr;
int option_index = 0;
static struct option long_options[] = {
{ "format", 1, 0, 'f' },
{ "no-csv-headers", 0, 0, 'H' },
{ "daemon", 0, 0, 'd' },
{ "mdns-ip", 1, 0, 'm' },
{ "mdns-port", 1, 0, 'M' },
{ "mdns-domain-suffix", 1, 0, 'D' },
{ "timeout", 1, 0, 'T' },
{ "min-delay", 1, 0, 't' },
{ "socket", 1, 0, 's' },
{ "send", 0, 0, 'S' },
{ "quiet", 0, 0, 'q' },
{ "help", 0, 0, 'h' },
{ "version", 0, 0, 'V' },
{}
};
#define PARSE_INT(TARGET, MINVAL, MAXVAL) do { \
int _rc; \
TARGET = parse_int(MINVAL, MAXVAL, optarg, &_rc); \
if (_rc) { \
print_error("Invalid " #TARGET ": %s\n", optarg); \
goto program_exit; \
} \
} while (0)
c = getopt_long(argc, argv, "f:Hdm:M:D:t:T:s:SqhV", long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'f':
cmdline_opts.format_str = optarg;
break;
case 'H':
cmdline_opts.csv_headers = false;
break;
case 'd':
cmdline_opts.daemon = true;
break;
case 'm':
if (osmo_sockaddr_str_from_str(&cmdline_opts.mdns_addr, optarg, cmdline_opts.mdns_addr.port)
|| !osmo_sockaddr_str_is_nonzero(&cmdline_opts.mdns_addr)) {
print_error("Invalid mDNS IP address: %s\n", optarg);
goto program_exit;
}
break;
case 'M':
PARSE_INT(cmdline_opts.mdns_addr.port, 1, 65535);
break;
case 'D':
cmdline_opts.mdns_domain_suffix = optarg;
break;
case 't':
PARSE_INT(cmdline_opts.min_delay, 0, UINT32_MAX);
break;
case 'T':
PARSE_INT(cmdline_opts.timeout, 0, UINT32_MAX);
break;
case 's':
cmdline_opts.socket_path = optarg;
break;
case 'S':
cmdline_opts.send = true;
break;
case 'q':
cmdline_opts.quiet = true;
break;
case 'h':
print_help();
rc = 0;
goto program_exit;
case 'V':
print_version();
rc = 0;
goto program_exit;
default:
/* catch unknown options *as well as* missing arguments. */
print_error("Error in command line options. Exiting.\n");
goto program_exit;
}
}
if (cmdline_opts.send) {
if (cmdline_opts.daemon || cmdline_opts.format_str || cmdline_opts.socket_path) {
print_error("--send option cannot have any listening related args.");
}
rc = do_send(argc - optind, argv + optind);
goto program_exit;
}
if (!cmdline_opts.daemon && !(argc - optind)) {
print_help();
goto program_exit;
}
if (cmdline_opts.daemon && !cmdline_opts.timeout) {
print_error("In daemon mode, --timeout must not be zero.\n");
goto program_exit;
}
if (cmdline_opts.quiet)
log_target_destroy(osmo_stderr_target);
if (cmdline_opts.format_str) {
if (osmo_str_startswith("json", cmdline_opts.format_str))
globals.format = FORMAT_JSON;
else if (osmo_str_startswith("csv", cmdline_opts.format_str))
globals.format = FORMAT_CSV;
else {
print_error("Invalid format: %s\n", cmdline_opts.format_str);
goto program_exit;
}
}
if (globals.format == FORMAT_CSV && cmdline_opts.csv_headers)
respond_str_stdout(CSV_HEADERS);
globals.mslookup_client = osmo_mslookup_client_new(globals.ctx);
if (!globals.mslookup_client
|| !osmo_mslookup_client_add_mdns(globals.mslookup_client,
cmdline_opts.mdns_addr.ip, cmdline_opts.mdns_addr.port,
-1, cmdline_opts.mdns_domain_suffix)) {
print_error("Failed to start mDNS client\n");
goto program_exit;
}
if (cmdline_opts.socket_path) {
if (socket_init(cmdline_opts.socket_path))
goto program_exit;
}
start_query_strs(&argv[optind], argc - optind);
while (1) {
osmo_select_main_ctx(0);
if (!cmdline_opts.daemon
&& globals.requests_handled
&& llist_empty(&globals.queries))
break;
}
rc = 0;
program_exit:
osmo_mslookup_client_free(globals.mslookup_client);
socket_close();
log_fini();
talloc_free(globals.ctx);
return rc;
}

View File

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

View File

@@ -1,7 +0,0 @@
#pragma once
struct osmo_mslookup_query;
struct osmo_mslookup_result;
void osmo_mslookup_server_rx(const struct osmo_mslookup_query *query,
struct osmo_mslookup_result *result);

View File

@@ -1,116 +1,70 @@
/* 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 <stdlib.h>
#include <unistd.h>
#include <osmocom/mslookup/mslookup.h>
#include <osmocom/mslookup/mdns.h>
#include "logging.h"
#include "mslookup_server.h"
#include "mslookup_server_mdns.h"
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/mslookup_server.h>
#include <osmocom/hlr/mslookup_server_mdns.h>
static void osmo_mslookup_server_mdns_tx(struct osmo_mslookup_server_mdns *server,
const struct osmo_mdns_request *req,
uint16_t packet_id,
const struct osmo_mslookup_query *query,
const struct osmo_mslookup_result *result)
{
const char *errmsg = NULL;
struct msgb *msg;
struct osmo_mdns_answer ans;
struct osmo_mdns_record *rec_age;
struct osmo_mdns_record rec_ip_v4 = {};
struct osmo_mdns_record *rec_ip_v4_port;
struct osmo_mdns_record rec_ip_v6 = {};
struct osmo_mdns_record *rec_ip_v6_port;
uint32_t ip_v4;
struct in6_addr ip_v6;
const char *errmsg = NULL;
void *ctx = talloc_named_const(server, 0, __func__);
LOGP(DDGSM, LOGL_DEBUG, "%s: sending mDNS response\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
osmo_mdns_answer_init(&ans);
ans.id = req->id;
ans.domain = req->domain;
rec_age = osmo_mdns_encode_txt_record(ctx, "age", "%u", result->age);
llist_add_tail(&rec_age->list, &ans.records);
if (osmo_sockaddr_str_is_nonzero(&result->host_v4)) {
if (osmo_sockaddr_str_to_32n(&result->host_v4, &ip_v4)) {
errmsg = "Error encoding IPv4 address";
goto clean_and_exit;
}
rec_ip_v4.type = OSMO_MSLOOKUP_MDNS_RECORD_TYPE_A;
rec_ip_v4.data = (void*)&ip_v4;
rec_ip_v4.length = sizeof(ip_v4);
llist_add_tail(&rec_ip_v4.list, &ans.records);
rec_ip_v4_port = osmo_mdns_encode_txt_record(ctx, "port", "%u", result->host_v4.port);
if (!rec_ip_v4_port) {
errmsg = "Error encoding IPv4 port";
goto clean_and_exit;
}
llist_add_tail(&rec_ip_v4_port->list, &ans.records);
}
if (osmo_sockaddr_str_is_nonzero(&result->host_v6)) {
if (osmo_sockaddr_str_to_in6_addr(&result->host_v6, &ip_v6)) {
errmsg = "Error encoding IPv6 address";
goto clean_and_exit;
}
rec_ip_v6.type = OSMO_MSLOOKUP_MDNS_RECORD_TYPE_AAAA;
rec_ip_v6.data = (void*)&ip_v6;
rec_ip_v6.length = sizeof(ip_v6);
llist_add_tail(&rec_ip_v6.list, &ans.records);
rec_ip_v6_port = osmo_mdns_encode_txt_record(ctx, "port", "%u", result->host_v6.port);
if (!rec_ip_v6_port) {
errmsg = "Error encoding IPv6 port";
goto clean_and_exit;
}
llist_add_tail(&rec_ip_v6_port->list, &ans.records);
}
msg = msgb_alloc(1024, __func__);
if (osmo_mdns_encode_answer(ctx, msg, &ans)) {
errmsg = "Error encoding DNS answer packet";
goto clean_and_exit;
}
if (osmo_mdns_sock_send(server->sock, msg))
errmsg = "Error sending DNS answer";
clean_and_exit:
msg = osmo_mdns_result_encode(ctx, packet_id, query, result, server->domain_suffix);
if (!msg)
errmsg = "Error encoding mDNS answer packet";
else if (osmo_mdns_sock_send(server->sock, msg))
errmsg = "Error sending mDNS answer";
if (errmsg)
LOGP(DDGSM, LOGL_ERROR, "%s: DNS: %s\n", osmo_mslookup_result_name_c(ctx, query, result), errmsg);
LOGP(DMSLOOKUP, LOGL_ERROR, "%s: mDNS: %s\n", osmo_mslookup_result_name_c(ctx, query, result), errmsg);
talloc_free(ctx);
}
static void osmo_mslookup_server_mdns_handle_request(struct osmo_mslookup_server_mdns *server,
const struct osmo_mdns_request *req)
static void osmo_mslookup_server_mdns_handle_request(uint16_t packet_id,
struct osmo_mslookup_server_mdns *server,
const struct osmo_mslookup_query *query)
{
struct osmo_mslookup_query query;
struct osmo_mslookup_result result;
if (osmo_mslookup_query_from_domain_str(&query, req->domain)) {
LOGP(DDGSM, LOGL_ERROR, "mDNS mslookup server: unable to parse request domain string: %s\n",
osmo_quote_str_c(OTC_SELECT, req->domain, -1));
return;
}
osmo_mslookup_server_rx(&query, &result);
/* Error logging already happens in osmo_mslookup_server_rx() */
if (result.rc != OSMO_MSLOOKUP_RC_OK)
mslookup_server_rx(query, &result);
/* Error logging already happens in mslookup_server_rx() */
if (result.rc != OSMO_MSLOOKUP_RC_RESULT)
return;
osmo_mslookup_server_mdns_tx(server, req, &query, &result);
osmo_mslookup_server_mdns_tx(server, packet_id, query, &result);
}
static int osmo_mslookup_server_mdns_rx(struct osmo_fd *osmo_fd, unsigned int what)
{
struct osmo_mslookup_server_mdns *server = osmo_fd->data;
struct osmo_mdns_request *req;
struct osmo_mslookup_query *query;
uint16_t packet_id;
int n;
uint8_t buffer[1024];
void *ctx;
@@ -121,33 +75,35 @@ static int osmo_mslookup_server_mdns_rx(struct osmo_fd *osmo_fd, unsigned int wh
return n;
ctx = talloc_named_const(server, 0, __func__);
req = osmo_mdns_decode_request(ctx, buffer, n);
if (!req) {
LOGP(DDGSM, LOGL_DEBUG, "mDNS rx: ignoring: not a request\n");
query = osmo_mdns_query_decode(ctx, buffer, n, &packet_id, server->domain_suffix);
if (!query) {
talloc_free(ctx);
return -1;
}
LOGP(DDGSM, LOGL_DEBUG, "mDNS rx request: %s\n", osmo_quote_str_c(OTC_SELECT, req->domain, -1));
osmo_mslookup_server_mdns_handle_request(server, req);
osmo_mslookup_id_name_buf((char *)buffer, sizeof(buffer), &query->id);
LOGP(DMSLOOKUP, LOGL_DEBUG, "mDNS rx request: %s.%s\n", query->service, buffer);
osmo_mslookup_server_mdns_handle_request(packet_id, server, query);
talloc_free(ctx);
return n;
}
struct osmo_mslookup_server_mdns *osmo_mslookup_server_mdns_start(void *ctx, const struct osmo_sockaddr_str *bind_addr)
struct osmo_mslookup_server_mdns *osmo_mslookup_server_mdns_start(void *ctx, const struct osmo_sockaddr_str *bind_addr,
const char *domain_suffix)
{
struct osmo_mslookup_server_mdns *server = talloc_zero(ctx, struct osmo_mslookup_server_mdns);
OSMO_ASSERT(server);
*server = (struct osmo_mslookup_server_mdns){
.bind_addr = *bind_addr,
.domain_suffix = talloc_strdup(server, domain_suffix)
};
server->sock = osmo_mdns_sock_init(server,
bind_addr->ip, bind_addr->port, true,
bind_addr->ip, bind_addr->port,
osmo_mslookup_server_mdns_rx,
server, 0);
if (!server->sock) {
LOGP(DDGSM, LOGL_ERROR,
LOGP(DMSLOOKUP, LOGL_ERROR,
"mslookup mDNS server: error initializing multicast bind on " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(bind_addr));
talloc_free(server);
@@ -164,3 +120,38 @@ void osmo_mslookup_server_mdns_stop(struct osmo_mslookup_server_mdns *server)
osmo_mdns_sock_cleanup(server->sock);
talloc_free(server);
}
void mslookup_server_mdns_config_apply()
{
/* Check whether to start/stop/restart mDNS server */
bool should_run;
bool should_stop;
should_run = g_hlr->mslookup.allow_startup
&& g_hlr->mslookup.server.enable && g_hlr->mslookup.server.mdns.enable;
should_stop = g_hlr->mslookup.server.mdns.running
&& (!should_run
|| osmo_sockaddr_str_cmp(&g_hlr->mslookup.server.mdns.bind_addr,
&g_hlr->mslookup.server.mdns.running->bind_addr)
|| strcmp(g_hlr->mslookup.server.mdns.domain_suffix,
g_hlr->mslookup.server.mdns.running->domain_suffix));
if (should_stop) {
osmo_mslookup_server_mdns_stop(g_hlr->mslookup.server.mdns.running);
g_hlr->mslookup.server.mdns.running = NULL;
LOGP(DMSLOOKUP, LOGL_NOTICE, "Stopped mslookup mDNS server\n");
}
if (should_run && !g_hlr->mslookup.server.mdns.running) {
g_hlr->mslookup.server.mdns.running =
osmo_mslookup_server_mdns_start(g_hlr, &g_hlr->mslookup.server.mdns.bind_addr,
g_hlr->mslookup.server.mdns.domain_suffix);
if (!g_hlr->mslookup.server.mdns.running)
LOGP(DMSLOOKUP, LOGL_ERROR, "Failed to start mslookup mDNS server on " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.server.mdns.running->bind_addr));
else
LOGP(DMSLOOKUP, LOGL_NOTICE, "Started mslookup mDNS server, receiving mDNS requests at multicast "
OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.server.mdns.running->bind_addr));
}
}

View File

@@ -1,14 +0,0 @@
#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;
struct osmo_mdns_sock *sock;
};
struct osmo_mslookup_server_mdns *osmo_mslookup_server_mdns_start(void *ctx, const struct osmo_sockaddr_str *bind_addr);
void osmo_mslookup_server_mdns_stop(struct osmo_mslookup_server_mdns *server);

View File

@@ -44,7 +44,7 @@
#include <osmocom/gsupclient/gsup_client.h>
#include "logging.h"
#include <osmocom/hlr/logging.h>
static struct osmo_gsup_client *g_gc;

View File

@@ -1,36 +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 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 <sys/time.h>
#include <string.h>
#include <talloc.h>
#include <errno.h>
#include <inttypes.h>
#include <osmocom/core/timer.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/gsm/gsm48_ie.h>
#include <osmocom/gsupclient/gsup_client.h>
#include <osmocom/gsupclient/gsup_req.h>
#include "logging.h"
#include "proxy.h"
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/proxy.h>
#include <osmocom/hlr/remote_hlr.h>
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/gsup_router.h>
/* Why have a separate struct to add an llist_head entry?
#define LOG_PROXY_SUBSCR(proxy_subscr, level, fmt, args...) \
LOGP(DDGSM, level, "(Proxy IMSI-%s MSISDN-%s HLR-" OSMO_SOCKADDR_STR_FMT ") " fmt, \
((proxy_subscr) && *(proxy_subscr)->imsi)? (proxy_subscr)->imsi : "?", \
((proxy_subscr) && *(proxy_subscr)->msisdn)? (proxy_subscr)->msisdn : "?", \
OSMO_SOCKADDR_STR_FMT_ARGS((proxy_subscr)? &(proxy_subscr)->remote_hlr_addr : NULL), \
##args)
#define LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup_msg, level, fmt, args...) \
LOG_PROXY_SUBSCR(proxy_subscr, level, "%s: " fmt, \
(gsup_msg) ? osmo_gsup_message_type_name((gsup_msg)->message_type) : "NULL", \
##args)
/* The proxy subscriber database.
* Why have a separate struct to add an llist_head entry?
* This is to keep the option open to store the proxy data in the database instead, without any visible effect outside
* of proxy.c. */
struct proxy_subscr_listentry {
struct llist_head entry;
struct timeval last_update;
timestamp_t last_update;
struct proxy_subscr data;
};
/* Central implementation to set a timestamp to the current time, in case we want to modify this in the future. */
void timestamp_update(struct timeval *tv)
struct proxy_pending_gsup_req {
struct llist_head entry;
struct osmo_gsup_req *req;
timestamp_t received_at;
};
/* Defer a GSUP message until we know a remote HLR to proxy to.
* Where to send this GSUP message is indicated by its IMSI: as soon as an MS lookup has yielded the IMSI's home HLR,
* that's where the message should go. */
static void proxy_deferred_gsup_req_add(struct proxy *proxy, struct osmo_gsup_req *req)
{
osmo_gettimeofday(tv, NULL);
struct proxy_pending_gsup_req *m;
m = talloc_zero(proxy, struct proxy_pending_gsup_req);
OSMO_ASSERT(m);
m->req = req;
timestamp_update(&m->received_at);
llist_add_tail(&m->entry, &proxy->pending_gsup_reqs);
}
time_t timestamp_age(const struct timeval *last_update)
static void proxy_pending_req_remote_hlr_connect_result(struct osmo_gsup_req *req, struct remote_hlr *remote_hlr)
{
struct timeval age;
struct timeval now;
timestamp_update(&now);
timersub(&now, last_update, &age);
return age.tv_sec;
if (!remote_hlr || !remote_hlr_is_up(remote_hlr)) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_ROAMING_NOTALLOWED, "Proxy: Failed to connect to home HLR");
return;
}
remote_hlr_gsup_forward_to_remote_hlr(remote_hlr, req, NULL);
}
static bool proxy_deferred_gsup_req_waiting(struct proxy *proxy, const char *imsi)
{
struct proxy_pending_gsup_req *p;
OSMO_ASSERT(imsi);
llist_for_each_entry(p, &proxy->pending_gsup_reqs, entry) {
if (strcmp(p->req->gsup.imsi, imsi))
continue;
return true;
}
return false;
}
/* Result of looking for remote HLR. If it failed, pass remote_hlr as NULL. On failure, the remote_hlr may be passed
* NULL. */
static void proxy_deferred_gsup_req_pop(struct proxy *proxy, const char *imsi, struct remote_hlr *remote_hlr)
{
struct proxy_pending_gsup_req *p, *n;
OSMO_ASSERT(imsi);
llist_for_each_entry_safe(p, n, &proxy->pending_gsup_reqs, entry) {
if (strcmp(p->req->gsup.imsi, imsi))
continue;
proxy_pending_req_remote_hlr_connect_result(p->req, remote_hlr);
p->req = NULL;
llist_del(&p->entry);
talloc_free(p);
}
}
static bool proxy_subscr_matches_imsi(const struct proxy_subscr *proxy_subscr, const char *imsi)
@@ -71,38 +156,25 @@ static struct proxy_subscr_listentry *_proxy_get_by_msisdn(struct proxy *proxy,
return NULL;
}
const struct proxy_subscr *proxy_subscr_get_by_imsi(struct proxy *proxy, const char *imsi)
int proxy_subscr_get_by_imsi(struct proxy_subscr *dst, struct proxy *proxy, const char *imsi)
{
struct proxy_subscr_listentry *e = _proxy_get_by_imsi(proxy, imsi);
if (!e)
return NULL;
return &e->data;
return -ENOENT;
*dst = e->data;
return 0;
}
const struct proxy_subscr *proxy_subscr_get_by_msisdn(struct proxy *proxy, const char *msisdn)
int proxy_subscr_get_by_msisdn(struct proxy_subscr *dst, struct proxy *proxy, const char *msisdn)
{
struct proxy_subscr_listentry *e = _proxy_get_by_msisdn(proxy, msisdn);
if (!e)
return NULL;
return &e->data;
return -ENOENT;
*dst = e->data;
return 0;
}
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)
{
struct proxy_subscr_listentry *e;
if (!proxy)
return;
llist_for_each_entry(e, &proxy->subscr_list, entry) {
if (!osmo_sockaddr_str_ip_cmp(remote_hlr_addr, &e->data.remote_hlr_addr)) {
if (!yield(proxy, &e->data, data))
return;
}
}
}
int proxy_subscr_update(struct proxy *proxy, const struct proxy_subscr *proxy_subscr)
int proxy_subscr_create_or_update(struct proxy *proxy, const struct proxy_subscr *proxy_subscr)
{
struct proxy_subscr_listentry *e = _proxy_get_by_imsi(proxy, proxy_subscr->imsi);
if (!e) {
@@ -123,7 +195,9 @@ int _proxy_subscr_del(struct proxy_subscr_listentry *e)
int proxy_subscr_del(struct proxy *proxy, const char *imsi)
{
struct proxy_subscr_listentry *e = _proxy_get_by_imsi(proxy, imsi);
struct proxy_subscr_listentry *e;
proxy_deferred_gsup_req_pop(proxy, imsi, NULL);
e = _proxy_get_by_imsi(proxy, imsi);
if (!e)
return -ENOENT;
return _proxy_subscr_del(e);
@@ -134,9 +208,13 @@ static void proxy_cleanup(void *proxy_v)
{
struct proxy *proxy = proxy_v;
struct proxy_subscr_listentry *e, *n;
uint32_t age;
llist_for_each_entry_safe(e, n, &proxy->subscr_list, entry) {
if (timestamp_age(&e->last_update) <= proxy->fresh_time)
if (!timestamp_age(&e->last_update, &age))
LOGP(DDGSM, LOGL_ERROR, "Invalid timestamp, deleting proxy entry\n");
else if (age <= proxy->fresh_time)
continue;
LOG_PROXY_SUBSCR(&e->data, LOGL_INFO, "proxy entry timed out, deleting\n");
_proxy_subscr_del(e);
}
if (proxy->gc_period)
@@ -151,19 +229,22 @@ void proxy_set_gc_period(struct proxy *proxy, uint32_t gc_period)
proxy_cleanup(proxy);
}
struct proxy *proxy_init(void *ctx)
void proxy_init(struct osmo_gsup_server *gsup_server_to_vlr)
{
struct proxy *proxy = talloc_zero(ctx, struct proxy);
OSMO_ASSERT(!gsup_server_to_vlr->proxy);
struct proxy *proxy = talloc_zero(gsup_server_to_vlr, struct proxy);
*proxy = (struct proxy){
.gsup_server_to_vlr = gsup_server_to_vlr,
.fresh_time = 60*60,
.gc_period = 60,
};
INIT_LLIST_HEAD(&proxy->subscr_list);
INIT_LLIST_HEAD(&proxy->pending_gsup_reqs);
osmo_timer_setup(&proxy->gc_timer, proxy_cleanup, proxy);
/* Invoke to trigger the first timer schedule */
proxy_set_gc_period(proxy, proxy->gc_period);
return proxy;
gsup_server_to_vlr->proxy = proxy;
}
void proxy_del(struct proxy *proxy)
@@ -171,3 +252,303 @@ void proxy_del(struct proxy *proxy)
osmo_timer_del(&proxy->gc_timer);
talloc_free(proxy);
}
/* All GSUP messages sent to the remote HLR pass through this function, to modify the subscriber state or disallow
* sending the message. Return 0 to allow sending the message. */
static int proxy_acknowledge_gsup_to_remote_hlr(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
const struct osmo_gsup_req *req)
{
struct proxy_subscr proxy_subscr_new = *proxy_subscr;
bool ps;
bool cs;
int rc;
if (req->source_name.type != OSMO_GSUP_PEER_ID_IPA_NAME) {
LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_ERROR,
"Unsupported GSUP peer id type: %s\n",
osmo_gsup_peer_id_type_name(req->source_name.type));
return -ENOTSUP;
}
switch (req->gsup.message_type) {
case OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST:
/* Store the CS and PS VLR name in vlr_name_preliminary to later update the right {cs,ps} LU timestamp
* when receiving an OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT. Store in vlr_name_preliminary so that in
* case the LU fails, we keep the vlr_name intact. */
switch (req->gsup.cn_domain) {
case OSMO_GSUP_CN_DOMAIN_CS:
proxy_subscr_new.cs.vlr_name_preliminary = req->source_name.ipa_name;
break;
case OSMO_GSUP_CN_DOMAIN_PS:
proxy_subscr_new.ps.vlr_name_preliminary = req->source_name.ipa_name;
break;
default:
break;
}
ps = cs = false;
if (osmo_ipa_name_cmp(&proxy_subscr_new.cs.vlr_name_preliminary, &proxy_subscr->cs.vlr_name_preliminary))
cs = true;
if (osmo_ipa_name_cmp(&proxy_subscr_new.ps.vlr_name_preliminary, &proxy_subscr->ps.vlr_name_preliminary))
ps = true;
if (!(cs || ps)) {
LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_DEBUG, "VLR names remain unchanged\n");
break;
}
rc = proxy_subscr_create_or_update(proxy, &proxy_subscr_new);
LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, rc ? LOGL_ERROR : LOGL_INFO,
"%s: preliminary VLR name for%s%s to %s\n",
rc ? "failed to update" : "updated",
cs ? " CS" : "", ps ? " PS" : "",
osmo_gsup_peer_id_to_str(&req->source_name));
break;
/* TODO: delete proxy entry in case of a Purge Request? */
default:
break;
}
return 0;
}
/* All GSUP messages received from the remote HLR to be sent to a local MSC pass through this function, to modify the
* subscriber state or disallow sending the message. Return 0 to allow sending the message.
* The local MSC shall be indicated by gsup.destination_name. */
static int proxy_acknowledge_gsup_from_remote_hlr(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
const struct osmo_gsup_message *gsup,
struct remote_hlr *from_remote_hlr,
const struct osmo_ipa_name *destination,
const struct osmo_ipa_name *via_peer)
{
struct proxy_subscr proxy_subscr_new = *proxy_subscr;
bool ps;
bool cs;
bool vlr_name_changed_cs = false;
bool vlr_name_changed_ps = false;
int rc;
struct osmo_ipa_name via_proxy = {};
if (osmo_ipa_name_cmp(via_peer, destination))
via_proxy = *via_peer;
switch (gsup->message_type) {
case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST:
/* Remember the MSISDN of the subscriber. This does not need to be a preliminary record, because when
* the HLR tells us about subscriber data, it is definitive info and there is no ambiguity (like there
* would be with failed LU attempts from various sources). */
if (!gsup->msisdn_enc_len)
LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_DEBUG, "No MSISDN in this Insert Data Request\n");
else if (gsm48_decode_bcd_number2(proxy_subscr_new.msisdn, sizeof(proxy_subscr_new.msisdn),
gsup->msisdn_enc, gsup->msisdn_enc_len, 0))
LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR, "Failed to decode MSISDN\n");
else if (!osmo_msisdn_str_valid(proxy_subscr_new.msisdn))
LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR, "invalid MSISDN: %s\n",
osmo_quote_str_c(OTC_SELECT, proxy_subscr_new.msisdn, -1));
else if (!strcmp(proxy_subscr->msisdn, proxy_subscr_new.msisdn))
LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_DEBUG, "already have MSISDN = %s\n",
proxy_subscr_new.msisdn);
else if (proxy_subscr_create_or_update(proxy, &proxy_subscr_new))
LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR, "failed to update MSISDN to %s\n",
proxy_subscr_new.msisdn);
else
LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_INFO, "stored MSISDN=%s\n",
proxy_subscr_new.msisdn);
break;
case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT:
/* Update the Location Updating timestamp */
cs = ps = false;
if (!osmo_ipa_name_cmp(destination, &proxy_subscr->cs.vlr_name_preliminary)) {
timestamp_update(&proxy_subscr_new.cs.last_lu);
proxy_subscr_new.cs.vlr_name_preliminary = (struct osmo_ipa_name){};
vlr_name_changed_cs =
osmo_ipa_name_cmp(&proxy_subscr->cs.vlr_name, destination)
|| osmo_ipa_name_cmp(&proxy_subscr->cs.vlr_via_proxy, &via_proxy);
proxy_subscr_new.cs.vlr_name = *destination;
proxy_subscr_new.cs.vlr_via_proxy = via_proxy;
cs = true;
}
if (!osmo_ipa_name_cmp(destination, &proxy_subscr->ps.vlr_name_preliminary)) {
timestamp_update(&proxy_subscr_new.ps.last_lu);
proxy_subscr_new.ps.vlr_name_preliminary = (struct osmo_ipa_name){};
proxy_subscr_new.ps.vlr_name = *destination;
vlr_name_changed_ps =
osmo_ipa_name_cmp(&proxy_subscr->ps.vlr_name, destination)
|| osmo_ipa_name_cmp(&proxy_subscr->ps.vlr_via_proxy, &via_proxy);
proxy_subscr_new.ps.vlr_via_proxy = via_proxy;
ps = true;
}
if (!(cs || ps)) {
LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR,
"destination is neither CS nor PS VLR: %s\n",
osmo_ipa_name_to_str(destination));
return GMM_CAUSE_PROTO_ERR_UNSPEC;
}
rc = proxy_subscr_create_or_update(proxy, &proxy_subscr_new);
LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, rc ? LOGL_ERROR : LOGL_INFO,
"%s LU: timestamp for%s%s%s%s%s%s%s%s%s%s\n",
rc ? "failed to update" : "updated",
cs ? " CS" : "", ps ? " PS" : "",
vlr_name_changed_cs? ", CS VLR=" : "",
vlr_name_changed_cs? osmo_ipa_name_to_str(&proxy_subscr_new.cs.vlr_name) : "",
proxy_subscr_new.cs.vlr_via_proxy.len ? " via proxy " : "",
proxy_subscr_new.cs.vlr_via_proxy.len ?
osmo_ipa_name_to_str(&proxy_subscr_new.cs.vlr_via_proxy) : "",
vlr_name_changed_ps? ", PS VLR=" : "",
vlr_name_changed_ps? osmo_ipa_name_to_str(&proxy_subscr_new.ps.vlr_name) : "",
proxy_subscr_new.ps.vlr_via_proxy.len ? " via proxy " : "",
proxy_subscr_new.ps.vlr_via_proxy.len ?
osmo_ipa_name_to_str(&proxy_subscr_new.ps.vlr_via_proxy) : ""
);
break;
default:
break;
}
return 0;
}
static void proxy_remote_hlr_connect_result_cb(const struct osmo_sockaddr_str *addr, struct remote_hlr *remote_hlr,
void *data)
{
struct proxy *proxy = data;
struct proxy_subscr_listentry *e;
if (!proxy)
return;
llist_for_each_entry(e, &proxy->subscr_list, entry) {
if (!osmo_sockaddr_str_cmp(addr, &e->data.remote_hlr_addr)) {
proxy_deferred_gsup_req_pop(proxy, e->data.imsi, remote_hlr);
}
}
}
/* Store the remote HLR's GSUP address for this proxy subscriber.
* This can be set before the remote_hlr is connected, or after.
* And, this can be set before the gsup_req has been queued for this HLR, or after.
*/
void proxy_subscr_remote_hlr_resolved(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
const struct osmo_sockaddr_str *remote_hlr_addr)
{
struct proxy_subscr proxy_subscr_new;
if (osmo_sockaddr_str_is_nonzero(&proxy_subscr->remote_hlr_addr)) {
if (!osmo_sockaddr_str_cmp(remote_hlr_addr, &proxy_subscr->remote_hlr_addr)) {
/* Already have this remote address */
return;
} else {
LOG_PROXY_SUBSCR(proxy_subscr, LOGL_NOTICE,
"Remote HLR address changes to " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(remote_hlr_addr));
}
}
/* Store the address. Make a copy to modify. */
proxy_subscr_new = *proxy_subscr;
proxy_subscr = &proxy_subscr_new;
proxy_subscr_new.remote_hlr_addr = *remote_hlr_addr;
if (proxy_subscr_create_or_update(proxy, proxy_subscr)) {
LOG_PROXY_SUBSCR(proxy_subscr, LOGL_ERROR, "Failed to store proxy entry for remote HLR\n");
/* If no remote HLR is known for the IMSI, the proxy entry is pointless. */
proxy_subscr_del(proxy, proxy_subscr->imsi);
return;
}
LOG_PROXY_SUBSCR(proxy_subscr, LOGL_DEBUG, "Remote HLR resolved, stored address\n");
/* If any messages for this HLR are already spooled, connect now. Otherwise wait for
* proxy_subscr_forward_to_remote_hlr() to connect then. */
if (proxy_deferred_gsup_req_waiting(proxy, proxy_subscr->imsi))
remote_hlr_get_or_connect(&proxy_subscr->remote_hlr_addr, true,
proxy_remote_hlr_connect_result_cb, proxy);
}
int proxy_subscr_forward_to_remote_hlr(struct proxy *proxy, const struct proxy_subscr *proxy_subscr, struct osmo_gsup_req *req)
{
struct remote_hlr *remote_hlr;
int rc;
rc = proxy_acknowledge_gsup_to_remote_hlr(proxy, proxy_subscr, req);
if (rc) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_PROTO_ERR_UNSPEC, "Proxy does not allow this message");
return rc;
}
if (!osmo_sockaddr_str_is_nonzero(&proxy_subscr->remote_hlr_addr)) {
/* We don't know the remote target yet. Still waiting for an MS lookup response, which will end up
* calling proxy_subscr_remote_hlr_resolved(). See dgsm.c. */
LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_DEBUG, "deferring until remote HLR is known\n");
proxy_deferred_gsup_req_add(proxy, req);
return 0;
}
if (!osmo_gsup_peer_id_is_empty(&req->via_proxy)) {
LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_INFO, "VLR->HLR: forwarding from %s via proxy %s\n",
osmo_gsup_peer_id_to_str(&req->source_name),
osmo_gsup_peer_id_to_str(&req->via_proxy));
} else {
LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_INFO, "VLR->HLR: forwarding from %s\n",
osmo_gsup_peer_id_to_str(&req->source_name));
}
/* We could always store in the defer queue and empty the queue if the connection is already up.
* Slight optimisation: if the remote_hlr is already up and running, skip the defer queue.
* First ask for an existing remote_hlr. */
remote_hlr = remote_hlr_get_or_connect(&proxy_subscr->remote_hlr_addr, false, NULL, NULL);
if (remote_hlr && remote_hlr_is_up(remote_hlr)) {
proxy_pending_req_remote_hlr_connect_result(req, remote_hlr);
return 0;
}
/* Not existing or not up. Defer req and ask to be notified when it is up.
* If the remote_hlr exists but is not connected yet, there should actually already be a pending
* proxy_remote_hlr_connect_result_cb queued, but it doesn't hurt to do that more often. */
proxy_deferred_gsup_req_add(proxy, req);
remote_hlr_get_or_connect(&proxy_subscr->remote_hlr_addr, true,
proxy_remote_hlr_connect_result_cb, proxy);
return 0;
}
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)
{
struct osmo_ipa_name destination;
struct osmo_gsup_conn *vlr_conn;
struct msgb *msg;
if (osmo_ipa_name_set(&destination, gsup->destination_name, gsup->destination_name_len)) {
LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR,
"no valid Destination Name IE, cannot route to VLR.\n");
return GMM_CAUSE_INV_MAND_INFO;
}
/* Route to MSC/SGSN that we're proxying for */
vlr_conn = gsup_route_find_by_ipa_name(proxy->gsup_server_to_vlr, &destination);
if (!vlr_conn) {
LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR,
"Destination VLR unreachable: %s\n", osmo_ipa_name_to_str(&destination));
return GMM_CAUSE_MSC_TEMP_NOTREACH;
}
if (proxy_acknowledge_gsup_from_remote_hlr(proxy, proxy_subscr, gsup, from_remote_hlr, &destination,
&vlr_conn->peer_name)) {
LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR,
"Proxy does not allow forwarding this message\n");
return GMM_CAUSE_PROTO_ERR_UNSPEC;
}
msg = osmo_gsup_msgb_alloc("GSUP proxy to VLR");
if (osmo_gsup_encode(msg, gsup)) {
LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR,
"Failed to re-encode GSUP message, cannot forward\n");
return GMM_CAUSE_INV_MAND_INFO;
}
LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_INFO, "VLR<-HLR: forwarding to %s%s%s\n",
osmo_ipa_name_to_str(&destination),
osmo_ipa_name_cmp(&destination, &vlr_conn->peer_name) ? " via " : "",
osmo_ipa_name_cmp(&destination, &vlr_conn->peer_name) ?
osmo_ipa_name_to_str(&vlr_conn->peer_name) : "");
return osmo_gsup_conn_send(vlr_conn, msg);
}

View File

@@ -1,56 +0,0 @@
#pragma once
#include <time.h>
#include <osmocom/gsm/protocol/gsm_23_003.h>
#include <osmocom/core/sockaddr_str.h>
#include "global_title.h"
void timestamp_update(struct timeval *timestamp);
time_t timestamp_age(const struct timeval *timestamp);
struct proxy {
struct llist_head subscr_list;
/* 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 global_title vlr_name;
struct timeval last_lu;
#if 0
/* not needed unless we ever want a system with multiple proxy hops: */
/* 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. */
struct global_title vlr_via_proxy;
#endif
};
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;
};
struct proxy *proxy_init(void *ctx);
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. */
const struct proxy_subscr *proxy_subscr_get_by_imsi(struct proxy *proxy, const char *imsi);
const struct proxy_subscr *proxy_subscr_get_by_msisdn(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);
const struct proxy_subscr *proxy_subscr_get_by_imsi(struct proxy *proxy, const char *imsi);
int proxy_subscr_update(struct proxy *proxy, const struct proxy_subscr *proxy_subscr);
int proxy_subscr_del(struct proxy *proxy, const char *imsi);

View File

@@ -1,19 +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 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/gsm/protocol/gsm_04_08_gprs.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/abis/ipa.h>
#include <osmocom/gsupclient/gsup_client.h>
#include "logging.h"
#include "hlr.h"
#include "gsup_server.h"
#include "gsup_router.h"
#include "dgsm.h"
#include "remote_hlr.h"
#include "proxy.h"
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/gsup_router.h>
#include <osmocom/hlr/remote_hlr.h>
#include <osmocom/hlr/proxy.h>
static LLIST_HEAD(remote_hlrs);
void remote_hlr_err_reply(struct osmo_gsup_client *gsupc, const struct osmo_gsup_message *gsup_orig,
enum gsm48_gmm_cause cause)
static void remote_hlr_err_reply(struct remote_hlr *rh, const struct osmo_gsup_message *gsup_orig,
enum gsm48_gmm_cause cause)
{
struct osmo_gsup_message gsup_reply;
@@ -38,118 +57,145 @@ void remote_hlr_err_reply(struct osmo_gsup_client *gsupc, const struct osmo_gsup
gsup_reply.session_id = gsup_orig->session_id;
}
if (osmo_gsup_client_enc_send(gsupc, &gsup_reply))
if (osmo_gsup_client_enc_send(rh->gsupc, &gsup_reply))
LOGP(DLGSUP, LOGL_ERROR, "Failed to send Error reply (imsi=%s)\n",
osmo_quote_str(gsup_orig->imsi, -1));
}
/* We are receiving back a GSUP message from a remote HLR to go back to a local MSC.
/* We are receiving a GSUP message from a remote HLR to go back to a local MSC.
* The local MSC shall be indicated by gsup.destination_name. */
static int remote_hlr_rx(struct osmo_gsup_client *gsupc, struct msgb *msg)
{
struct remote_hlr *rh = gsupc->data;
struct proxy_subscr proxy_subscr;
struct osmo_gsup_message gsup;
struct osmo_gsup_conn *vlr_conn;
const struct proxy_subscr *proxy_subscr;
struct global_title destination;
struct msgb *gsup_copy;
int rc;
rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
if (rc < 0) {
LOG_GSUPC(gsupc, LOGL_ERROR, "Failed to decode GSUP message: '%s' (%d) [ %s]\n",
get_value_string(gsm48_gmm_cause_names, -rc), -rc, osmo_hexdump(msg->data, msg->len));
LOG_REMOTE_HLR(rh, LOGL_ERROR, "Failed to decode GSUP message: '%s' (%d) [ %s]\n",
get_value_string(gsm48_gmm_cause_names, -rc),
-rc, osmo_hexdump(msg->data, msg->len));
return rc;
}
if (!gsup.imsi[0]) {
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "Failed to decode GSUP message: missing IMSI\n");
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_INV_MAND_INFO);
if (!osmo_imsi_str_valid(gsup.imsi)) {
LOG_REMOTE_HLR_MSG(rh, &gsup, LOGL_ERROR, "Invalid IMSI\n");
remote_hlr_err_reply(rh, &gsup, GMM_CAUSE_INV_MAND_INFO);
return -GMM_CAUSE_INV_MAND_INFO;
}
if (!gsup.destination_name || !gsup.destination_name_len
|| global_title_set(&destination, gsup.destination_name, gsup.destination_name_len)) {
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "no valid Destination Name IE, cannot route to VLR.\n");
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_INV_MAND_INFO);
return -GMM_CAUSE_INV_MAND_INFO;
if (proxy_subscr_get_by_imsi(&proxy_subscr, g_hlr->gs->proxy, gsup.imsi)) {
LOG_REMOTE_HLR_MSG(rh, &gsup, LOGL_ERROR, "No proxy entry for this IMSI\n");
remote_hlr_err_reply(rh, &gsup, GMM_CAUSE_NET_FAIL);
return -GMM_CAUSE_NET_FAIL;
}
proxy_subscr = proxy_subscr_get_by_imsi(g_hlr->proxy, gsup.imsi);
if (!proxy_subscr) {
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "Cannot route, no GSUP proxy record for this IMSI\n");
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_IMSI_UNKNOWN);
return -GMM_CAUSE_IMSI_UNKNOWN;
rc = proxy_subscr_forward_to_vlr(g_hlr->gs->proxy, &proxy_subscr, &gsup, rh);
if (rc) {
LOG_REMOTE_HLR_MSG(rh, &gsup, LOGL_ERROR, "Failed to forward GSUP message towards VLR\n");
remote_hlr_err_reply(rh, &gsup, GMM_CAUSE_NET_FAIL);
return -GMM_CAUSE_NET_FAIL;
}
/* Route to MSC that we're proxying for */
vlr_conn = gsup_route_find_gt(g_hlr->gs, &destination);
if (!vlr_conn) {
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "Destination VLR unreachable: %s\n",
global_title_name(&destination));
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_MSC_TEMP_NOTREACH);
return -GMM_CAUSE_MSC_TEMP_NOTREACH;
}
/* The outgoing message needs to be a separate msgb, because osmo_gsup_conn_send() takes ownership of it, an the
* gsup_client also does a msgb_free() after dispatching to this callback.
* We also need to strip the IPA header and have headroom. Just re-encode. */
gsup_copy = osmo_gsup_msgb_alloc("GSUP proxy to VLR");
if (osmo_gsup_encode(gsup_copy, &gsup)) {
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "Failed to re-encode GSUP message, cannot forward\n");
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_MSC_TEMP_NOTREACH);
return -GMM_CAUSE_MSC_TEMP_NOTREACH;
}
return osmo_gsup_conn_send(vlr_conn, gsup_copy);
return 0;
}
struct remote_hlr_pending_up {
struct llist_head entry;
remote_hlr_connect_result_cb_t connect_result_cb;
void *data;
};
static bool remote_hlr_up_down(struct osmo_gsup_client *gsupc, bool up)
{
struct remote_hlr *remote_hlr = gsupc->data;
struct remote_hlr_pending_up *p, *n;
if (!up) {
LOGP(DDGSM, LOGL_ERROR,
"link to remote HLR is down, removing GSUP client: " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
LOG_REMOTE_HLR(remote_hlr, LOGL_NOTICE, "link to remote HLR is down, removing GSUP client\n");
remote_hlr_destroy(remote_hlr);
return false;
}
dgsm_remote_hlr_up(remote_hlr);
LOG_REMOTE_HLR(remote_hlr, LOGL_NOTICE, "link up\n");
llist_for_each_entry_safe(p, n, &remote_hlr->pending_up_callbacks, entry) {
if (p->connect_result_cb)
p->connect_result_cb(&remote_hlr->addr, remote_hlr, p->data);
llist_del(&p->entry);
}
return true;
}
struct remote_hlr *remote_hlr_get(const struct osmo_sockaddr_str *addr, bool create)
bool remote_hlr_is_up(struct remote_hlr *remote_hlr)
{
struct remote_hlr *rh;
return remote_hlr && remote_hlr->gsupc && remote_hlr->gsupc->is_connected;
}
llist_for_each_entry(rh, &remote_hlrs, entry) {
if (!osmo_sockaddr_str_ip_cmp(&rh->addr, addr))
return rh;
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)
{
struct remote_hlr *rh = NULL;
struct remote_hlr *rh_i;
struct osmo_gsup_client_config cfg;
llist_for_each_entry(rh_i, &remote_hlrs, entry) {
if (!osmo_sockaddr_str_cmp(&rh_i->addr, addr)) {
rh = rh_i;
break;
}
}
if (!create)
if (rh)
goto add_result_cb;
if (!connect) {
if (connect_result_cb)
connect_result_cb(addr, NULL, data);
return NULL;
}
/* Doesn't exist yet, create a GSUP client to remote HLR. */
cfg = (struct osmo_gsup_client_config){
.ipa_dev = &g_hlr->gsup_unit_name,
.ip_addr = addr->ip,
.tcp_port = addr->port,
.oapc_config = NULL,
.read_cb = remote_hlr_rx,
.up_down_cb = remote_hlr_up_down,
.data = rh,
};
rh = talloc_zero(dgsm_ctx, struct remote_hlr);
OSMO_ASSERT(rh);
*rh = (struct remote_hlr){
.addr = *addr,
.gsupc = osmo_gsup_client_create3(rh, &g_hlr->gsup_unit_name,
addr->ip, addr->port,
NULL,
remote_hlr_rx,
remote_hlr_up_down,
rh),
.gsupc = osmo_gsup_client_create3(rh, &cfg),
};
INIT_LLIST_HEAD(&rh->pending_up_callbacks);
if (!rh->gsupc) {
LOGP(DDGSM, LOGL_ERROR,
"Failed to establish connection to remote HLR " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(addr));
talloc_free(rh);
if (connect_result_cb)
connect_result_cb(addr, NULL, data);
return NULL;
}
rh->gsupc->data = rh;
llist_add(&rh->entry, &remote_hlrs);
add_result_cb:
if (connect_result_cb) {
if (remote_hlr_is_up(rh)) {
connect_result_cb(addr, rh, data);
} else {
struct remote_hlr_pending_up *p;
p = talloc_zero(rh, struct remote_hlr_pending_up);
OSMO_ASSERT(p);
p->connect_result_cb = connect_result_cb;
p->data = data;
llist_add_tail(&p->entry, &rh->pending_up_callbacks);
}
}
return rh;
}
@@ -172,20 +218,33 @@ int remote_hlr_msgb_send(struct remote_hlr *remote_hlr, struct msgb *msg)
return rc;
}
void remote_hlr_gsup_forward(struct remote_hlr *remote_hlr, struct osmo_gsup_req *req)
/* A GSUP message was received from the MS/MSC side, forward it to the remote HLR. */
void remote_hlr_gsup_forward_to_remote_hlr(struct remote_hlr *remote_hlr, struct osmo_gsup_req *req,
struct osmo_gsup_message *modified_gsup)
{
int rc;
struct msgb *msg = osmo_gsup_msgb_alloc("GSUP proxy to remote HLR");
struct msgb *msg;
/* To forward to a remote HLR, we need to indicate the source MSC's name in the Source Name IE to make sure the
* reply can be routed back. Store the sender MSC in gsup->source_name -- the remote HLR is required to return
* this as gsup->destination_name so that the reply gets routed to the original MSC. */
struct osmo_gsup_message forward = req->gsup;
forward.source_name = req->source_name.val;
forward.source_name_len = req->source_name.len;
struct osmo_gsup_message forward;
if (modified_gsup)
forward = *modified_gsup;
else
forward = req->gsup;
if (req->source_name.type != OSMO_GSUP_PEER_ID_IPA_NAME) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Unsupported GSUP peer id type: %s",
osmo_gsup_peer_id_type_name(req->source_name.type));
return;
}
forward.source_name = req->source_name.ipa_name.val;
forward.source_name_len = req->source_name.ipa_name.len;
msg = osmo_gsup_msgb_alloc("GSUP proxy to remote HLR");
rc = osmo_gsup_encode(msg, &forward);
if (rc) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Failed to encode GSUP message for forwarding\n");
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Failed to encode GSUP message for forwarding");
return;
}
remote_hlr_msgb_send(remote_hlr, msg);

View File

@@ -1,27 +0,0 @@
#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 msgb;
#define LOG_GSUPC(gsupc, level, fmt, args...) \
LOGP(DDGSM, level, "HLR Proxy: GSUP from %s:%u: " fmt, (gsupc)->link->addr, (gsupc)->link->port, ##args)
#define LOG_GSUPC_MSG(gsupc, gsup_msg, level, fmt, args...) \
LOG_GSUPC(gsupc, 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 remote_hlr *remote_hlr_get(const struct osmo_sockaddr_str *addr, bool create);
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(struct remote_hlr *remote_hlr, struct osmo_gsup_req *req);

423
src/sms_over_gsup.c Normal file
View File

@@ -0,0 +1,423 @@
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/gsm48_ie.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/gsm/gsm0411_utils.h>
#include <osmocom/gsm/protocol/gsm_04_11.h>
#include <osmocom/mslookup/mslookup_client.h>
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/remote_hlr.h>
#include <osmocom/hlr/mslookup_server.h>
#include <osmocom/hlr/db.h>
#include <osmocom/hlr/sms_over_gsup.h>
static int sms_extract_destination_msisdn(char *msisdn, size_t msisdn_size, struct osmo_gsup_req *req)
{
int rc;
if (req->gsup.sm_rp_da_type == OSMO_GSUP_SMS_SM_RP_ODA_MSISDN
&& req->gsup.sm_rp_da_len > 0) {
LOG_GSUP_REQ(req, LOGL_INFO, "Extracting destination MSISDN from GSUP SM_RP_DA IE: %s\n",
osmo_hexdump(req->gsup.sm_rp_da, req->gsup.sm_rp_da_len));
rc = gsm48_decode_bcd_number2(msisdn, msisdn_size, req->gsup.sm_rp_da, req->gsup.sm_rp_da_len, 0);
if (!rc)
LOG_GSUP_REQ(req, LOGL_INFO, "success -> %s\n", msisdn);
else
LOG_GSUP_REQ(req, LOGL_ERROR, "fail %d\n", rc);
return rc;
}
/* The DA was not an MSISDN -- get from inside the SMS PDU */
if (req->gsup.sm_rp_ui_len > 3) {
const uint8_t *da = req->gsup.sm_rp_ui + 2;
uint8_t da_len = *da;
uint8_t da_len_bytes;
uint8_t address_lv[12] = {};
da_len_bytes = 2 + da_len/2 + da_len%2;
if (da_len_bytes < 4 || da_len_bytes > 12
|| da_len_bytes > req->gsup.sm_rp_ui_len - 2) {
LOG_GSUP_REQ(req, LOGL_ERROR, "Invalid da_len_bytes %u\n", da_len_bytes);
return -EINVAL;
}
memcpy(address_lv, da, da_len_bytes);
address_lv[0] = da_len_bytes - 1;
LOG_GSUP_REQ(req, LOGL_INFO, "Extracting destination MSISDN from SMS PDU DA: %s\n",
osmo_hexdump(address_lv, da_len_bytes));
rc = gsm48_decode_bcd_number2(msisdn, msisdn_size, address_lv, da_len_bytes, 1);
if (!rc)
LOG_GSUP_REQ(req, LOGL_INFO, "success -> %s\n", msisdn);
else
LOG_GSUP_REQ(req, LOGL_ERROR, "fail %d\n", rc);
return rc;
}
LOG_GSUP_REQ(req, LOGL_ERROR, "fail: no SM_RP_DA nor SMS PDU (sm_rp_ui_len > 3)\n");
return -ENOTSUP;
}
static int sms_extract_sender_msisdn(char *msisdn, size_t msisdn_size, struct osmo_gsup_req *req)
{
int rc;
if (req->gsup.sm_rp_oa_type == OSMO_GSUP_SMS_SM_RP_ODA_MSISDN
&& req->gsup.sm_rp_oa_len > 0) {
LOG_GSUP_REQ(req, LOGL_INFO, "Extracting sender MSISDN from GSUP SM_RP_OA IE: %s\n",
osmo_hexdump(req->gsup.sm_rp_oa, req->gsup.sm_rp_oa_len));
rc = gsm48_decode_bcd_number2(msisdn, msisdn_size, req->gsup.sm_rp_oa, req->gsup.sm_rp_oa_len, 0);
if (!rc)
LOG_GSUP_REQ(req, LOGL_INFO, "success -> %s\n", msisdn);
else
LOG_GSUP_REQ(req, LOGL_ERROR, "fail %d\n", rc);
return rc;
}
LOG_GSUP_REQ(req, LOGL_ERROR, "fail: no MSISDN obtained from SM_RP_OA\n");
return -ENOTSUP;
}
static struct msgb *sms_mo_pdu_to_mt_pdu(const uint8_t *mo_pdu, size_t mo_pdu_len, const char *sender_msisdn)
{
/* Hacky shortened copy-paste of osmo-msc's gsm340_rx_tpdu() */
uint8_t protocol_id;
uint8_t data_coding_scheme;
uint8_t user_data_len;
uint8_t user_data_octet_len;
const uint8_t *user_data;
uint8_t status_rep_req;
uint8_t ud_hdr_ind;
{
const uint8_t *smsp = mo_pdu;
enum sms_alphabet sms_alphabet;
uint8_t sms_vpf;
uint8_t da_len_bytes;
sms_vpf = (*smsp & 0x18) >> 3;
status_rep_req = (*smsp & 0x20) >> 5;
ud_hdr_ind = (*smsp & 0x40);
smsp += 2;
/* length in bytes of the destination address */
da_len_bytes = 2 + *smsp/2 + *smsp%2;
if (da_len_bytes < 4 || da_len_bytes > 12)
return NULL;
smsp += da_len_bytes;
protocol_id = *smsp++;
data_coding_scheme = *smsp++;
sms_alphabet = gsm338_get_sms_alphabet(data_coding_scheme);
if (sms_alphabet == 0xffffffff)
return NULL;
switch (sms_vpf) {
case GSM340_TP_VPF_RELATIVE:
smsp++;
break;
case GSM340_TP_VPF_ABSOLUTE:
case GSM340_TP_VPF_ENHANCED:
/* the additional functionality indicator... */
if (sms_vpf == GSM340_TP_VPF_ENHANCED && *smsp & (1<<7))
smsp++;
smsp += 7;
break;
case GSM340_TP_VPF_NONE:
break;
default:
return NULL;
}
/* As per 3GPP TS 03.40, section 9.2.3.16, TP-User-Data-Length (TP-UDL)
* may indicate either the number of septets, or the number of octets,
* depending on Data Coding Scheme. We store TP-UDL value as-is,
* so this should be kept in mind to avoid buffer overruns. */
user_data_len = *smsp++;
user_data = smsp;
if (user_data_len > 0) {
if (sms_alphabet == DCS_7BIT_DEFAULT) {
/* TP-UDL is indicated in septets (up to 160) */
if (user_data_len > GSM340_UDL_SPT_MAX) {
user_data_len = GSM340_UDL_SPT_MAX;
}
user_data_octet_len = gsm_get_octet_len(user_data_len);
} else {
/* TP-UDL is indicated in octets (up to 140) */
if (user_data_len > GSM340_UDL_OCT_MAX) {
user_data_len = GSM340_UDL_OCT_MAX;
}
user_data_octet_len = user_data_len;
}
}
}
{
/* The following is a hacky copy pasted and shortened version of osmo-msc's gsm340_gen_sms_deliver_tpdu() */
struct msgb *msg = gsm411_msgb_alloc();
uint8_t *smsp;
uint8_t oa[12]; /* max len per 03.40 */
int oa_len;
if (!msg)
return NULL;
/* generate first octet with masked bits */
smsp = msgb_put(msg, 1);
/* TP-MTI (message type indicator) */
*smsp = GSM340_SMS_DELIVER_SC2MS;
/* TP-MMS (more messages to send) */
if (0 /* FIXME */)
*smsp |= 0x04;
/* TP-SRI(deliver)/SRR(submit) */
if (status_rep_req)
*smsp |= 0x20;
/* TP-UDHI (indicating TP-UD contains a header) */
if (ud_hdr_ind)
*smsp |= 0x40;
/* generate originator address */
oa_len = gsm340_gen_oa(oa, sizeof(oa), 0, 0, sender_msisdn);
if (oa_len < 0) {
msgb_free(msg);
return NULL;
}
smsp = msgb_put(msg, oa_len);
memcpy(smsp, oa, oa_len);
/* generate TP-PID */
smsp = msgb_put(msg, 1);
*smsp = protocol_id;
/* generate TP-DCS */
smsp = msgb_put(msg, 1);
*smsp = data_coding_scheme;
/* generate TP-SCTS */
smsp = msgb_put(msg, 7);
gsm340_gen_scts(smsp, time(NULL));
/* generate TP-UDL */
smsp = msgb_put(msg, 1);
*smsp = user_data_len;
smsp = msgb_put(msg, user_data_octet_len);
memcpy(smsp, user_data, user_data_octet_len);
return msg;
}
}
static void sms_recipient_up_cb(const struct osmo_sockaddr_str *addr, struct remote_hlr *remote_hlr, void *data)
{
struct osmo_gsup_req *req = data;
struct osmo_gsup_message modified_gsup = req->gsup;
// struct msgb *mt_pdu = NULL;
if (!remote_hlr) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSC_TEMP_NOTREACH,
"Failed to connect to SMS recipient: " OSMO_SOCKADDR_STR_FMT,
OSMO_SOCKADDR_STR_FMT_ARGS(addr));
return;
}
/* We must not send out another MO request, to make sure we don't send the request in an infinite loop. */
#if 0
if (req->gsup.message_type == OSMO_GSUP_MSGT_MO_FORWARD_SM_REQUEST) {
char sender_msisdn[GSM23003_MSISDN_MAX_DIGITS+1];
if (sms_extract_sender_msisdn(sender_msisdn, sizeof(sender_msisdn), req)) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "Cannot find sender MSISDN");
return;
}
mt_pdu = sms_mo_pdu_to_mt_pdu(req->gsup.sm_rp_ui, req->gsup.sm_rp_ui_len, sender_msisdn);
if (!mt_pdu) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
"Cannot translate PDU to a DELIVER PDU");
return;
}
modified_gsup.message_type = OSMO_GSUP_MSGT_MT_FORWARD_SM_REQUEST;
modified_gsup.sm_rp_ui = mt_pdu->data;
modified_gsup.sm_rp_ui_len = mt_pdu->len;
}
#endif
LOG_GSUP_REQ(req, LOGL_INFO, "Forwarding to remote HLR " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
remote_hlr_gsup_forward_to_remote_hlr(remote_hlr, req, &modified_gsup);
}
static void sms_over_gsup_mt(struct osmo_gsup_req *req)
{
/* Find a locally connected MSC that knows this MSISDN. */
uint32_t lu_age;
struct osmo_gsup_peer_id local_msc_id;
struct osmo_mslookup_query query = {
.service = OSMO_MSLOOKUP_SERVICE_SMS_GSUP,
.id = {
.type = OSMO_MSLOOKUP_ID_MSISDN,
},
};
struct osmo_gsup_message modified_gsup = req->gsup;
struct msgb *mt_pdu = NULL;
if (sms_extract_destination_msisdn(query.id.msisdn, sizeof(query.id.msisdn), req)) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "invalid MSISDN");
return;
}
LOG_GSUP_REQ(req, LOGL_NOTICE, "SMS to MSISDN: %s\n", query.id.msisdn);
/* If a local attach is found, write the subscriber's IMSI to the modified_gsup buffer */
if (!subscriber_has_done_lu_here(&query, &lu_age, &local_msc_id.ipa_name,
modified_gsup.imsi, sizeof(modified_gsup.imsi))) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSC_TEMP_NOTREACH,
"SMS recipient not reachable: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, &query, NULL));
return;
}
local_msc_id.type = OSMO_GSUP_PEER_ID_IPA_NAME;
/* A local MSC indeed has an active subscription for the recipient. Deliver there. */
if (modified_gsup.message_type == OSMO_GSUP_MSGT_MO_FORWARD_SM_REQUEST) {
/* This is a direct local delivery, and sms_over_gsup_mo_directly_to_mt() just passed the MO request
* altough here we are on the MT side. We must not send out another MO request, to make sure we don't
* send the request in an infinite loop.
* Also patch in the recipient's IMSI.
*/
char sender_msisdn[GSM23003_MSISDN_MAX_DIGITS+1];
if (sms_extract_sender_msisdn(sender_msisdn, sizeof(sender_msisdn), req)) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "Cannot find sender MSISDN");
return;
}
mt_pdu = sms_mo_pdu_to_mt_pdu(req->gsup.sm_rp_ui, req->gsup.sm_rp_ui_len, sender_msisdn);
if (!mt_pdu) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
"Cannot translate PDU to a DELIVER PDU");
return;
}
modified_gsup.message_type = OSMO_GSUP_MSGT_MT_FORWARD_SM_REQUEST;
modified_gsup.sm_rp_ui = mt_pdu->data;
modified_gsup.sm_rp_ui_len = mt_pdu->len;
}
osmo_gsup_forward_to_local_peer(g_hlr->gs, &local_msc_id, req, &modified_gsup);
if (mt_pdu)
msgb_free(mt_pdu);
}
static void resolve_sms_recipient_cb(struct osmo_mslookup_client *client,
uint32_t request_handle,
const struct osmo_mslookup_query *query,
const struct osmo_mslookup_result *result)
{
struct osmo_gsup_req *req = query->priv;
const struct osmo_sockaddr_str *remote_hlr_addr = NULL;
const struct mslookup_service_host *local_gsup;
if (result->rc == OSMO_MSLOOKUP_RC_RESULT) {
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;
}
if (!remote_hlr_addr) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSC_TEMP_NOTREACH,
"Failed to resolve SMS recipient: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
return;
}
/* Possibly, this HLR here has responded to itself via mslookup. Don't make a GSUP connection to ourselves,
* instead go directly to the MT path. */
local_gsup = mslookup_server_get_local_gsup_addr();
LOG_GSUP_REQ(req, LOGL_NOTICE, "local_gsup " OSMO_SOCKADDR_STR_FMT " " OSMO_SOCKADDR_STR_FMT
" remote_hlr_addr " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&local_gsup->host_v4),
OSMO_SOCKADDR_STR_FMT_ARGS(&local_gsup->host_v6),
OSMO_SOCKADDR_STR_FMT_ARGS(remote_hlr_addr));
if (local_gsup
&& (!osmo_sockaddr_str_cmp(&local_gsup->host_v4, remote_hlr_addr)
|| !osmo_sockaddr_str_cmp(&local_gsup->host_v6, remote_hlr_addr))) {
sms_over_gsup_mt(req);
return;
}
remote_hlr_get_or_connect(remote_hlr_addr, true, sms_recipient_up_cb, req);
}
static void sms_over_gsup_mo_directly_to_mt(struct osmo_gsup_req *req)
{
/* Figure out the location of the SMS recipient by mslookup */
if (osmo_mslookup_client_active(g_hlr->mslookup.client.client)) {
/* D-GSM is active. Kick off an mslookup for the current location of the MSISDN. */
uint32_t request_handle;
struct osmo_mslookup_query_handling handling = {
.min_wait_milliseconds = g_hlr->mslookup.client.result_timeout_milliseconds,
.result_cb = resolve_sms_recipient_cb,
};
struct osmo_mslookup_query query = {
.id = {
.type = OSMO_MSLOOKUP_ID_MSISDN,
},
.priv = req,
};
if (sms_extract_destination_msisdn(query.id.msisdn, sizeof(query.id.msisdn), req)
|| !osmo_msisdn_str_valid(query.id.msisdn)) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "invalid MSISDN");
return;
}
OSMO_STRLCPY_ARRAY(query.service, OSMO_MSLOOKUP_SERVICE_SMS_GSUP);
request_handle = osmo_mslookup_client_request(g_hlr->mslookup.client.client, &query, &handling);
if (request_handle) {
/* Querying succeeded. Wait for resolve_sms_recipient_cb() to be called. */
return;
}
/* Querying failed. Try whether delivering to a locally connected MSC works out. */
LOG_DGSM(req->gsup.imsi, LOGL_ERROR,
"Error dispatching mslookup query for SMS: %s -- trying local delivery\n",
osmo_mslookup_result_name_c(OTC_SELECT, &query, NULL));
}
/* Attempt direct delivery */
sms_over_gsup_mt(req);
}
static void sms_over_gsup_mo(struct osmo_gsup_req *req)
{
if (!osmo_gsup_peer_id_is_empty(&g_hlr->sms_over_gsup.smsc)) {
/* Forward to SMSC */
/* FIXME actually use branch fixeria/sms for this */
osmo_gsup_forward_to_local_peer(g_hlr->gs, &g_hlr->sms_over_gsup.smsc, req, NULL);
return;
}
if (!g_hlr->sms_over_gsup.try_direct_delivery) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_PROTO_ERR_UNSPEC,
"cannot deliver SMS over GSUP: No SMSC (and direct delivery disabled)");
return;
}
sms_over_gsup_mo_directly_to_mt(req);
}
bool sms_over_gsup_check_handle_msg(struct osmo_gsup_req *req)
{
switch (req->gsup.message_type) {
case OSMO_GSUP_MSGT_MO_FORWARD_SM_REQUEST:
sms_over_gsup_mo(req);
return true;
case OSMO_GSUP_MSGT_MT_FORWARD_SM_REQUEST:
sms_over_gsup_mt(req);
return true;
default:
return false;
}
}

53
src/timestamp.c Normal file
View File

@@ -0,0 +1,53 @@
/* 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/timer.h>
#include <osmocom/hlr/timestamp.h>
/* Central implementation to set a timestamp to the current time, in case we want to modify this in the future. */
void timestamp_update(timestamp_t *timestamp)
{
struct timeval tv;
time_t raw;
struct tm utc;
/* The simpler way would be just time(&raw), but by using osmo_gettimeofday() we can also use
* osmo_gettimeofday_override for unit tests independent from real time. */
osmo_gettimeofday(&tv, NULL);
raw = tv.tv_sec;
gmtime_r(&raw, &utc);
*timestamp = mktime(&utc);
}
/* Calculate seconds since a given timestamp was taken. Return true for a valid age returned in age_p, return false if
* the timestamp is either in the future or the age surpasses uint32_t range. When false is returned, *age_p is set to
* UINT32_MAX. */
bool timestamp_age(const timestamp_t *timestamp, uint32_t *age_p)
{
int64_t age64;
timestamp_t now;
timestamp_update(&now);
age64 = (int64_t)now - (int64_t)(*timestamp);
if (age64 < 0 || age64 > UINT32_MAX) {
*age_p = UINT32_MAX;
return false;
}
*age_p = (uint32_t)age64;
return true;
}

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