mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-hlr.git
synced 2025-10-23 08:22:12 +00:00
Compare commits
20 Commits
6784ed14b7
...
neels/dgsm
Author | SHA1 | Date | |
---|---|---|---|
|
1971b6731a | ||
|
b3c1726fb9 | ||
|
81ac78dfac | ||
|
dd73ccfc99 | ||
|
40d3a96f9d | ||
|
1e635ab550 | ||
|
43d5cfc13b | ||
|
61917ed51d | ||
|
43de5efb3d | ||
|
c992d85068 | ||
|
d29b2f236f | ||
|
647c106d1c | ||
|
a75d1c2c94 | ||
|
32cc07ad06 | ||
|
bf8b614c0e | ||
|
bcb5dca1c9 | ||
|
f6c8f04c79 | ||
|
82112ea835 | ||
|
0d28d85168 | ||
|
b2553ebb4a |
@@ -186,6 +186,7 @@ AC_OUTPUT(
|
||||
Makefile
|
||||
doc/Makefile
|
||||
doc/examples/Makefile
|
||||
doc/sequence_charts/Makefile
|
||||
src/Makefile
|
||||
src/gsupclient/Makefile
|
||||
src/mslookup/Makefile
|
||||
@@ -204,6 +205,7 @@ AC_OUTPUT(
|
||||
tests/auc/Makefile
|
||||
tests/auc/gen_ts_55_205_test_sets/Makefile
|
||||
tests/gsup_server/Makefile
|
||||
tests/gsup/Makefile
|
||||
tests/db/Makefile
|
||||
tests/db_upgrade/Makefile
|
||||
tests/mslookup/Makefile
|
||||
|
@@ -100,6 +100,9 @@ def rx_deliver_sm(pdu):
|
||||
time.sleep(args.sleep)
|
||||
logging.info("Sleep done")
|
||||
|
||||
if args.always_fail is not None:
|
||||
return args.always_fail
|
||||
|
||||
result = query_mslookup("smpp.sms", msisdn)
|
||||
if 'v4' not in result or not result['v4']:
|
||||
logging.info('No IPv4 result from mslookup! This example only'
|
||||
@@ -147,12 +150,35 @@ def main():
|
||||
parser.add_argument('--sleep', default=0, type=float,
|
||||
help='sleep time in seconds before forwarding an SMS,'
|
||||
' to test multithreading (default: 0)')
|
||||
parser.add_argument('--always-fail', default=None, metavar='SMPP_ESME_ERRCODE',
|
||||
help='test delivery failure: always return an error code on Deliver-SM,'
|
||||
' pass an smpplib error code name like RDELIVERYFAILURE (see smpplib/consts.py),'
|
||||
' or an SMPP error code in hex digits')
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='[%(asctime)s]'
|
||||
' (%(threadName)s) %(message)s', datefmt="%H:%M:%S")
|
||||
|
||||
if args.always_fail:
|
||||
resolved = None
|
||||
name = 'SMPP_ESME_' + args.always_fail
|
||||
if hasattr(smpplib.consts, name):
|
||||
resolved = getattr(smpplib.consts, name)
|
||||
if resolved is None:
|
||||
try:
|
||||
resolved = int(args.always_fail, 16)
|
||||
except ValueError:
|
||||
resolved = None
|
||||
if resolved is None:
|
||||
print('Invalid argument for --always-fail: %r' % args.always_fail)
|
||||
exit(1)
|
||||
args.always_fail = resolved
|
||||
logging.info('--always-fail: returning error code %s to all Deliver-SM' % hex(args.always_fail))
|
||||
|
||||
smpp_bind()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
# vim: expandtab tabstop=4 shiftwidth=4
|
||||
|
@@ -1,4 +1,5 @@
|
||||
SUBDIRS = \
|
||||
examples \
|
||||
manuals \
|
||||
sequence_charts \
|
||||
$(NULL)
|
||||
|
22
doc/examples/osmo-hlr-dgsm.cfg
Normal file
22
doc/examples/osmo-hlr-dgsm.cfg
Normal file
@@ -0,0 +1,22 @@
|
||||
# OsmoHLR example configuration for Distributed GSM (mslookup)
|
||||
hlr
|
||||
gsup
|
||||
# For D-GSM roaming, osmo-hlr's GSUP must listen on a public IP:
|
||||
bind ip 10.9.8.7
|
||||
# Each HLR should identify with a distinct name
|
||||
ipa-name hlr-23
|
||||
mslookup
|
||||
# Bind mslookup mDNS server and client on default multicast address and port:
|
||||
# 239.192.23.42 port 4266
|
||||
mdns bind
|
||||
# Tell the mslookup server in osmo-hlr which IP+ports to return when a
|
||||
# remote voice call or SMS wants to deliver to a local subscriber:
|
||||
server
|
||||
# local osmo-sip-connector SIP port
|
||||
service sip.voice at 10.9.8.7 5060
|
||||
# local osmo-msc SMPP port
|
||||
service smpp.sms at 10.9.8.7 2775
|
||||
# experimental: SMS-over-GSUP: this HLR's GSUP port
|
||||
service gsup.sms at 10.9.8.7 4222
|
||||
# only required if different from above 'gsup'/'bind ip':
|
||||
#service gsup.hlr at 10.9.8.7 4222
|
@@ -4,13 +4,18 @@ EXTRA_DIST = example_subscriber_add_update_delete.vty \
|
||||
osmohlr-usermanual.adoc \
|
||||
osmohlr-usermanual-docinfo.xml \
|
||||
osmohlr-vty-reference.xml \
|
||||
chapters/proxy_cache_attach.msc \
|
||||
chapters/proxy_cache_more_tuples.msc \
|
||||
chapters/proxy_cache_periodic_lu.msc \
|
||||
chapters/proxy_cache_tuple_cache_dry.msc \
|
||||
chapters/proxy_cache_umts_aka_resync.msc \
|
||||
regen_doc.sh \
|
||||
chapters \
|
||||
vty
|
||||
|
||||
if BUILD_MANUALS
|
||||
ASCIIDOC = osmohlr-usermanual.adoc
|
||||
ASCIIDOC_DEPS = $(srcdir)/chapters/*.adoc $(srcdir)/*.vty $(srcdir)/*.ctrl
|
||||
ASCIIDOC_DEPS = $(srcdir)/chapters/*.adoc $(srcdir)/*.vty $(srcdir)/*.ctrl $(srcdir)/chapters/*.msc
|
||||
include $(OSMO_GSM_MANUALS_DIR)/build/Makefile.asciidoc.inc
|
||||
|
||||
VTY_REFERENCE = osmohlr-vty-reference.xml
|
||||
|
20
doc/manuals/chapters/Makefile
Normal file
20
doc/manuals/chapters/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
all: \
|
||||
proxy_cache_attach.msc \
|
||||
proxy_cache_more_tuples.msc \
|
||||
proxy_cache_tuple_cache_dry.msc \
|
||||
proxy_cache_periodic_lu.msc \
|
||||
proxy_cache_umts_aka_resync.msc \
|
||||
$(NULL)
|
||||
|
||||
png: \
|
||||
proxy_cache.png \
|
||||
proxy_cache_unused.png \
|
||||
lu_attach_and_periodic.png \
|
||||
$(NULL)
|
||||
|
||||
%.png: %.msc
|
||||
mscgen -T png -o $@ $<
|
||||
|
||||
%.msc: %.ladder
|
||||
@which ladder_to_msc.py || (echo 'PLEASE POINT YOUR $$PATH AT libosmocore/contrib/ladder_to_msc.py' && false)
|
||||
ladder_to_msc.py -i $< -o $@
|
491
doc/manuals/chapters/dgsm.adoc
Normal file
491
doc/manuals/chapters/dgsm.adoc
Normal file
@@ -0,0 +1,491 @@
|
||||
== Distributed GSM / Multicast MS Lookup
|
||||
|
||||
Distributed GSM (D-GSM) allows independent mobile core network stacks to provide voice, SMS and Roaming services to each
|
||||
other, without the need for centralised entities or administration authority, and in a way that is resilient against
|
||||
unstable network links between sites.
|
||||
|
||||
D-GSM aims at communal networks, where several independent sites, let's call them villages, each have a full mobile core
|
||||
network infrastructure. It elegantly provides ad-hoc service for subscribers moving across villages, and allows villages
|
||||
to dynamically join or leave the cooperative network without the need for configuration changes at other sites.
|
||||
|
||||
A challenge for linking separate sites is to find the current location of a subscriber. Typically, in mobile networks, a
|
||||
centralized entity keeps track of where to Page for subscribers. Running several fully independent sites with unreliable
|
||||
links between them makes it hard to provide such centralisation.
|
||||
|
||||
D-GSM finds subscribers by mslookup, a service provided by OsmoHLR, typically using multicast DNS queries. This allows
|
||||
routing Location Updating requests, calls, and SMS to the right site without administrative delay nor the need for a
|
||||
reliable link to a central database.
|
||||
|
||||
D-GSM is highly resilient against single sites or links becoming temporarily unavailable. Service between still
|
||||
reachable sites simply continues; Service to a disconnected site resumes as soon as it becomes reachable again.
|
||||
|
||||
This brings an entirely new paradigm to mobile core network infrastructure: as sites become reachable on the IP network
|
||||
and join the common IP multicast group, services between them become available immediately. Basically, the only premise
|
||||
is that IP routing and multicast works across sites, and that each site uses unique IPA names in the GSUP config.
|
||||
|
||||
This chapter describes how D-GSM and mslookup work, and how to configure sites to use D-GSM, using Osmocom core network
|
||||
infrastructure.
|
||||
|
||||
=== Finding Subscribers: mslookup Clients
|
||||
|
||||
There are two fundamentally distinct subscriber lookups provided by the mslookup service.
|
||||
|
||||
==== Find the Current Location of an MSISDN
|
||||
|
||||
[[fig_dgsm_connect]]
|
||||
.mslookup for connecting subscribers: Alice is visiting village C; a phone call gets routed directly to her current location independently from her resident village infrastructure
|
||||
[graphviz]
|
||||
----
|
||||
digraph G {
|
||||
rankdir=LR
|
||||
|
||||
subgraph cluster_village_b {
|
||||
label="Village B"
|
||||
ms_bob [label="Bob\n(from village B)",shape=box]
|
||||
pbx_b [label="SIP B"]
|
||||
}
|
||||
|
||||
subgraph cluster_village_c {
|
||||
label="Village C"
|
||||
ms_alice [label="Alice\n(from village A)",shape=box]
|
||||
msc_c [label="MSC C"]
|
||||
hlr_c [label="HLR C"]
|
||||
sip_c [label="SIP C"]
|
||||
}
|
||||
|
||||
ms_alice -> msc_c [style=dashed,arrowhead=none]
|
||||
msc_c -> hlr_c [label="attached",style=dashed,arrowhead=none]
|
||||
ms_bob -> pbx_b [label="call Alice"]
|
||||
pbx_b -> hlr_c [label="mslookup by MSISDN",style=dotted,dir=both]
|
||||
pbx_b -> sip_c -> msc_c -> ms_alice [label="call"]
|
||||
}
|
||||
----
|
||||
|
||||
For example, if a subscriber is currently visiting another village, establish a phone call / send SMS towards that
|
||||
village.
|
||||
|
||||
- To deliver a phone call, a SIP agent integrates an mslookup client to request the SIP service of an MSISDN's current
|
||||
location (example: <<dgsm_conf_dialplan>>). It receives an IP address and port to send the SIP Invite to.
|
||||
|
||||
- To deliver an SMS, an ESME integrates an mslookup client to request the SMPP service of an MSISDN's current location
|
||||
(example: <<dgsm_conf_esme_smpp>>).
|
||||
|
||||
The current location of a subscriber may change at any time, and, when moving across locations, a subscriber may
|
||||
suddenly lose reception to the previous location without explicitly detaching. Hence an mslookup request for the current
|
||||
location of an MSISDN may get numerous responses. To find the currently valid location, mslookup includes the age of the
|
||||
subscriber record, i.e. how long ago the subscriber was last reached. The one response with the youngest age reflects
|
||||
the current location.
|
||||
|
||||
In order to evaluate several responses, mslookup always waits for a fixed amount of time (1 second), and then evaluates
|
||||
the available responses.
|
||||
|
||||
Services are not limited to SIP and SMPP, arbitrarily named services can be added to the mslookup configuration.
|
||||
|
||||
.Message sequence for locating an MSISDN to deliver a voice call
|
||||
["mscgen"]
|
||||
----
|
||||
msc {
|
||||
hscale="2";
|
||||
moms[label="MS,BSS\nvillage A"],momsc[label="MSC,MGW\nvillage A"],mosipcon[label="osmo-sip-connector\nvillage A"],mopbx[label="PBX\nvillage A"],mthlr[label="OsmoHLR\nvillage B"],mtsipcon[label="osmo-sip-connector\nvillage B"],mtmsc[label="MGW,MSC\nvillage B"],mtms[label="RAN,MS\nvillage B"];
|
||||
|
||||
moms =>> momsc [label="CC Setup"];
|
||||
momsc =>> mosipcon [label="MNCC_SETUP_IND"];
|
||||
mosipcon =>> mopbx [label="SIP INVITE"];
|
||||
mopbx rbox mopbx [label="dialplan: launch mslookup by MSISDN"];
|
||||
--- [label="multicast-DNS query to all connected sites"];
|
||||
...;
|
||||
mopbx <<= mthlr [label="mDNS response\n(age)"];
|
||||
mopbx rbox mopbx [label="wait ~ 1s for more mDNS responses"];
|
||||
...;
|
||||
mopbx =>> mtsipcon [label="SIP INVITE (MT)"];
|
||||
mtmsc <<= mtsipcon [label="MNCC_SETUP_REQ"];
|
||||
mtms <<= mtmsc [label="Paging (CC)"];
|
||||
moms rbox mtms [label="voice call commences"];
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
==== Find the Home HLR for an IMSI
|
||||
|
||||
[[fig_dgsm_roaming]]
|
||||
.mslookup for Roaming: Alice visits village B; she can attach to the local mobile network, which proxies HLR administration to her home village.
|
||||
[graphviz]
|
||||
----
|
||||
digraph G {
|
||||
rankdir=LR
|
||||
|
||||
subgraph cluster_village_b {
|
||||
label="Village B"
|
||||
|
||||
ms_alice [label="Alice\n(from village A)",shape=box]
|
||||
msc_b [label="MSC B"]
|
||||
hlr_b [label="HLR B"]
|
||||
}
|
||||
|
||||
subgraph cluster_village_a {
|
||||
label="Village A"
|
||||
hlr_alice [label="Alice's home HLR"]
|
||||
}
|
||||
|
||||
ms_alice -> msc_b -> hlr_b [label="Location\nUpdating"]
|
||||
hlr_b -> hlr_alice [label="mslookup by IMSI",style=dotted,dir=both]
|
||||
hlr_b -> hlr_alice [label="GSUP proxy forwarding"]
|
||||
}
|
||||
----
|
||||
|
||||
For example, when attaching to a local network, a local resident gets serviced directly by the local village's HLR,
|
||||
while a visitor from another village gets serviced by the remote village's HLR (Roaming).
|
||||
|
||||
A home HLR typically stays the same for a given IMSI. If the home site is reachable, there should be exactly one
|
||||
response to an mslookup request asking for it. The age of such a home-HLR response is always sent as zero.
|
||||
|
||||
If a response's age is zero, mslookup does not wait for further responses and immediately uses the result.
|
||||
|
||||
If there were more than one HLR accepting service for an IMSI, the one with the shortest response latency is used.
|
||||
|
||||
=== mslookup Configuration
|
||||
|
||||
OsmoHLR the main mslookup agent. It provides the responses for both current location services as well as for locating
|
||||
the fixed home-HLR. But naturally, depending on the mslookup request's purpose, different OsmoHLR instances will respond
|
||||
for a given subscriber.
|
||||
|
||||
- When querying the home HLR, it is always the (typically single) home HLR instance that sends the mslookup response. As
|
||||
soon as it finds the queried IMSI in the local HLR database, an OsmoHLR will respond to home-HLR requests.
|
||||
In <<fig_dgsm_roaming>>, Alice's home HLR responds to the Roaming request ("where is the home HLR?").
|
||||
|
||||
- When querying the location of an MSISDN, it is always the HLR proxy nearest to the servicing MSC that sends the
|
||||
mslookup response. Even though the home HLR keeps the Location Updating record also for Roaming cases, it will only
|
||||
respond to an mslookup service request if the subscriber has attached at a directly connected MSC. If attached at a
|
||||
remote MSC, that MSC's remote HLR will be the GSUP proxy for the home HLR, and the remote HLR is responsible for
|
||||
responding to service requests.
|
||||
In <<fig_dgsm_roaming>>, HLR B is the nearest proxy and will answer all service requests ("where is this MSISDN?").
|
||||
Alice's home HLR will not answer service requests, because it detects that the servicing MSC is connected via another
|
||||
HLR proxy.
|
||||
|
||||
[[dgsm_example_config]]
|
||||
==== Example
|
||||
|
||||
Here is an osmo-hlr.cfg mslookup configuration example for one site, which is explained in subsequent chapters.
|
||||
|
||||
hlr
|
||||
gsup
|
||||
bind ip 10.9.8.7
|
||||
ipa-name hlr-23
|
||||
mslookup
|
||||
mdns bind
|
||||
server
|
||||
service sip.voice at 10.9.8.7 5060
|
||||
service smpp.sms at 10.9.8.7 2775
|
||||
|
||||
OsmoHLR has both an mslookup server and a client.
|
||||
|
||||
- The server responds to incoming service and home-HLR requests, when the local HLR is responsible.
|
||||
- The client is used as GSUP proxy to a remote home HLR (found by mslookup upon a locally unknown IMSI).
|
||||
- The client may also be used for forwarding SMS-over-GSUP.
|
||||
|
||||
The mslookup service can be implemented by various methods.
|
||||
At the time of writing, the only method implemented is mDNS.
|
||||
|
||||
==== mDNS
|
||||
|
||||
The stock mslookup method is mDNS, multicast DNS. It consists of standard DNS encoding according to <<ietf-rfc1035>> and
|
||||
<<ietf-rfc3596>>, but sent and received on IP multicast. In the response, standard A and AAAA records return the
|
||||
service's IP address, while additional TXT records provide the service's port number and the MS attach age.
|
||||
|
||||
TIP: To watch D-GSM mDNS conversations in wireshark, select "udp.port == 4266" (the default mslookup mDNS port
|
||||
number), right click on the packet to "Decode as...", and select "DNS".
|
||||
|
||||
In OsmoHLR, the mDNS server and client are typically both enabled at the same time:
|
||||
|
||||
mslookup
|
||||
mdns bind
|
||||
|
||||
Server and client can also be enabled/disabled individually:
|
||||
|
||||
mslookup
|
||||
server
|
||||
mdns bind
|
||||
client
|
||||
mdns bind
|
||||
|
||||
These examples use the default mslookup multicast IP address and port. It is possible to configure custom IP address and
|
||||
port, but beware that the IP address must be from a multicast range, see <<ietf-rfc5771>>:
|
||||
|
||||
mslookup
|
||||
mdns bind 239.192.23.42 4266
|
||||
|
||||
Domain names generated from mslookup queries (e.g. "sip.voice.123.msisdn") should not collide with IANA permitted
|
||||
domains. Therefore we add the "mdns.osmocom.org" suffix. It can be overridden as follows:
|
||||
|
||||
mslookup
|
||||
mdns domain-suffix mdns.osmocom.org
|
||||
|
||||
==== Server: Site Services
|
||||
|
||||
The mslookup server requires a list of service addresses provided at the local site, in order to respond to service
|
||||
requests matching locally attached subscribers.
|
||||
|
||||
mslookup
|
||||
server
|
||||
service sip.voice at 10.9.8.7 5060
|
||||
service smpp.sms at 10.9.8.7 2775
|
||||
|
||||
In this example:
|
||||
|
||||
- "10.9.8.7 5060" are the IP address and port on which the local site's osmo-sip-connector is bound to receive SIP
|
||||
Invite requests.
|
||||
- "10.9.8.7 2775" are the local site's OsmoMSC SMPP bind address and port.
|
||||
|
||||
Obviously, these IP addresses must be routable back to this site from all other sites. Using link-local or "ANY"
|
||||
addresses, like 127.0.0.1 or 0.0.0.0, will not work here. Instead, each service config requires a public IP address that
|
||||
all remote requestors are able to reach (not necessarily on the host that osmo-hlr is running on).
|
||||
|
||||
If a site has more than one MSC, services can also be configured for each MSC individually, keyed by the IPA unit name
|
||||
that each MSC sends on the GSUP link:
|
||||
|
||||
mslookup
|
||||
server
|
||||
msc ipa-name msc-262-42-0
|
||||
service sip.voice at 10.11.12.13 5060
|
||||
service smpp.sms at 10.11.12.13 2775
|
||||
msc ipa-name msc-901-70-0
|
||||
service sip.voice at 10.9.8.7 5060
|
||||
service smpp.sms at 10.9.8.7 2775
|
||||
|
||||
Here, "msc-262-42-0" is the IPA name of a local OsmoMSC instance. To configure an OsmoMSC's IPA name on the GSUP link,
|
||||
see osmo-msc.cfg, setting `hlr` / `ipa-name`.
|
||||
|
||||
For mslookup service responses, only Location Updatings in the Circuit Switched domain are relevant. OsmoHLR does manage
|
||||
IMSIs attaching in the Packet Switched domain (via an SGSN) similarly to Circuit Switched (via an MSC), but mslookup
|
||||
completely ignores the Packet Switched attach status.
|
||||
|
||||
==== Server: Own GSUP Address
|
||||
|
||||
When responding to home-HLR requests, OsmoHLR implicitly by default responds with its locally configured GSUP bind
|
||||
address (setting `hlr` / `gsup` / `bind ip`). If required, an explicit local GSUP address and port can be configured,
|
||||
for example:
|
||||
|
||||
hlr
|
||||
gsup
|
||||
bind ip 0.0.0.0
|
||||
ipa-name hlr-23
|
||||
mslookup
|
||||
server
|
||||
# osmo-hlr's own GSUP address to send in mslookup responses:
|
||||
service gsup.hlr at 10.9.8.7 4222
|
||||
|
||||
The gsup.hlr service can only be configured globally (because requests come from arbitrary mDNS clients, before a
|
||||
Location Updating has associated the IMSI with the requesting MSC).
|
||||
|
||||
==== Client IPA Naming
|
||||
|
||||
For reliable GSUP proxy routing to a remote HLR (Roaming), it is important that each GSUP client, i.e. each HLR, MSC and
|
||||
SGSN instance, has a unique IPA name.
|
||||
|
||||
Example for configuring an OsmoHLR instance's IPA name:
|
||||
|
||||
hlr
|
||||
gsup
|
||||
ipa-name hlr-23
|
||||
|
||||
Here, "hlr-23" is the unique identification of this OsmoHLR instance across all potentially connected D-GSM sites.
|
||||
|
||||
Furthermore, each MSC and SGSN must have a uniquely distinct IPA name across all sites (here "msc-262-42-0" and
|
||||
"msc-901-70-0" are used as example IPA names for local MSCs).
|
||||
|
||||
When this OsmoHLR connects to a remote HLR, be it for GSUP proxying or SMS-over-GSUP, it communicates its own IPA name
|
||||
(on GSUP link-up) as well as the IPA name of the requesting client MSC/SGSN (as Source Name in each message) to the
|
||||
remote OsmoHLR GSUP server. These names are used to route GSUP responses back to the respective requesting peer.
|
||||
|
||||
If two MSCs were accidentally configured with identical names, a problem will occur as soon as both MSCs attempt to
|
||||
attach to the same OsmoHLR (either directly or via GSUP proxying). The MSC that shows up first will work normally, but
|
||||
any duplicate that shows up later will be rejected, since a route for its name already exists.
|
||||
|
||||
=== Queries
|
||||
|
||||
In URL notation, typical mslookup queries look like:
|
||||
|
||||
gsup.hlr.123456789.imsi
|
||||
sip.voice.123.msisdn
|
||||
smpp.sms.123.msisdn
|
||||
|
||||
A query consists of
|
||||
|
||||
- a service name ("gsup.hlr"),
|
||||
- an id ("123456789"),
|
||||
- the id type ("imsi").
|
||||
|
||||
The calling client also defines a timeout to wait for responses.
|
||||
|
||||
The mslookup ID types are fixed, while service names can be chosen arbitrarily.
|
||||
|
||||
.mslookup ID types, no other ID types are understood by mslookup
|
||||
[options="header",width="100%",cols="20%,80%"]
|
||||
|===
|
||||
|ID Type|Description
|
||||
|imsi|An IMSI as existing in an OsmoHLR subscriber database
|
||||
|msisdn|A phone number as configured in an OsmoHLR subscriber database
|
||||
|===
|
||||
|
||||
.mslookup service name conventions, arbitrary service names can be added as required
|
||||
[options="header",width="100%",cols="20%,20%,60%"]
|
||||
|===
|
||||
|Service Name|Protocol|Description
|
||||
|gsup.hlr | GSUP | Home HLR's GSUP server, to handle Location Updating related procedures
|
||||
|sip.voice | SIP | OsmoSIPConnector, to receive a SIP Invite (MT side of a call)
|
||||
|smpp.sms | SMPP | Destination OsmoMSC (or other SMPP server) to deliver an SMS to the recipient
|
||||
|gsup.sms | GSUP | GSUP peer to deliver an SMS to the recipient using SMS-over-GSUP
|
||||
|===
|
||||
|
||||
Arbitrarily named services can be added to the mslookup configuration and queried by mslookup clients; as soon as a
|
||||
service name is present in osmo-hlr.cfg, it can be queried from any mslookup client.
|
||||
|
||||
Service names should consist of a protocol name (like "sip", "gsup", "english") and an intended action/entity (like
|
||||
"voice", "hlr", "greeting").
|
||||
|
||||
=== Service Client Implementation
|
||||
|
||||
In principle, arbitrary services could query target addresses via mslookup, leaving it up to any and all kinds of
|
||||
clients to find their respective destination addresses. But of course, mslookup was designed with specific services in
|
||||
mind, namely:
|
||||
|
||||
- SIP call agents and
|
||||
- SMS delivery (an ESME or SMSC)
|
||||
|
||||
The following chapters describe examples of setting up a working distributed core network providing SIP voice calls and
|
||||
SMS forwarding across sites.
|
||||
|
||||
==== mslookup Library
|
||||
|
||||
The OsmoHLR provides an mslookup client C library, libosmo-mslookup. Service lookups can be integrated directly
|
||||
in client programs using this library. However, its mDNS implementation requires the libosmocore select() loop, which
|
||||
can be challenging to integrate in practice. An alternative solution is the osmo-mslookup-client tool.
|
||||
|
||||
[[dgsm_osmo_mslookup_client]]
|
||||
==== osmo-mslookup-client
|
||||
|
||||
The mslookup C library is available, but often, a simpler approach for client implementations is desirable:
|
||||
|
||||
- When querying for a service address, the client is typically interested in the single final best result (youngest age
|
||||
/ first responding home HLR).
|
||||
- Voice call and SMS clients typically would block until an mslookup result is known. For example, the FreeSwitch
|
||||
dialplan integration expects a result synchronously, i.e. without waiting for mslookup responses via a select() loop.
|
||||
- Integrating the libosmocore select() loop required for mDNS can break the already existing socket handling in the
|
||||
client program.
|
||||
|
||||
The osmo-mslookup-client cmdline tool provides a trivial way to synchronously acquire the single result for an mslookup
|
||||
request. The service client can invoke an osmo-mslookup-client process per request and read the result from stdout.
|
||||
|
||||
Each invocation obviously spawns a separate process and opens a multicast socket for mDNS. For better scalability,
|
||||
osmo-mslookup-client can also be run as a daemon, providing results via a unix domain socket. Using synchronous write()
|
||||
and recv() allows blocking until a result is received without interfering with the client program's select() setup.
|
||||
|
||||
By itself, osmo-mslookup-client is also helpful as a diagnostic tool:
|
||||
|
||||
----
|
||||
$ osmo-mslookup-client sip.voice.1001.msisdn
|
||||
sip.voice.1001.msisdn ok 10.9.8.7 5060
|
||||
|
||||
$ osmo-mslookup-client gsup.hlr.901700000014701.imsi
|
||||
gsup.hlr.901700000014701.imsi ok 10.9.8.7 4222
|
||||
|
||||
$ osmo-mslookup-client gsup.hlr.111111.imsi
|
||||
gsup.hlr.111111.imsi not-found
|
||||
|
||||
$ osmo-mslookup-client gsup.hlr.1001.msisdn sip.voice.1001.msisdn smpp.sms.1001.msisdn foo.1001.msisdn
|
||||
gsup.hlr.1001.msisdn ok 10.9.8.7 4222
|
||||
foo.1001.msisdn not-found
|
||||
smpp.sms.1001.msisdn ok 10.9.8.7 2775
|
||||
sip.voice.1001.msisdn ok 10.9.8.7 5060
|
||||
|
||||
$ osmo-mslookup-client --csv-headers gsup.hlr.901700000014701.imsi
|
||||
QUERY RESULT V4_IP V4_PORT V6_IP V6_PORT
|
||||
gsup.hlr.901700000014701.imsi ok 10.9.8.7 4222
|
||||
|
||||
$ osmo-mslookup-client -f json gsup.hlr.901700000014701.imsi
|
||||
{"query": "gsup.hlr.901700000014701.imsi", "result": "ok", "v4": ["10.9.8.7", "4222"]}
|
||||
----
|
||||
|
||||
For full help including example client invocations in Python, see the output of:
|
||||
|
||||
osmo-mslookup-client -h
|
||||
|
||||
==== SIP Service Client
|
||||
|
||||
[[dgsm_conf_dialplan]]
|
||||
===== FreeSwitch dialplan.py
|
||||
|
||||
The FreeSWITCH PBX software <<freeswitch_pbx>> offers a Python integration to determine a SIP call recipient by a custom
|
||||
dialplan implementation. An example dialplan implementation for FreeSWITCH that uses D-GSM mslookup is provided in the
|
||||
osmo-hlr source tree under `contrib`, called `freeswitch_dialplan_dgsm.py`.
|
||||
|
||||
To integrate it with your FREESWITCH setup, add a new `extension` block to your `dialplan/public.xml`:
|
||||
|
||||
----
|
||||
<extension name="outbound">
|
||||
<condition field="destination_number" expression=".*">
|
||||
<action application="set" data="hangup_after_bridge=true"/>
|
||||
<action application="set" data="session_in_hangup_hook=true"/>
|
||||
<action application="set" data="ringback=%(2000, 4000, 440.0, 480.0)"/>
|
||||
<action application="python" data="freeswitch_dialplan_dgsm"/>
|
||||
</condition>
|
||||
</extension>
|
||||
----
|
||||
|
||||
Make sure that the dir containing `freeswitch_dialplan_dgsm.py` is in your `PYTHONPATH` environment variable, and start
|
||||
the server:
|
||||
|
||||
----
|
||||
$ export PYTHONPATH="$PYTHONPATH:/home/user/code/osmo-hlr/contrib/dgsm"
|
||||
$ freeswitch -nf -nonat -nonatmap -nocal -nort -c
|
||||
----
|
||||
|
||||
==== SMS Service Client
|
||||
|
||||
[[dgsm_conf_esme_smpp]]
|
||||
===== SMS via SMPP Port
|
||||
|
||||
An example ESME using D-GSM mslookup, `esme_dgsm.py`, is provided in the osmo-hlr source tree under `contrib`. It
|
||||
attaches to OsmoMSC's SMPP port to send SMS to recipients determined by mslookup.
|
||||
|
||||
OsmoMSC should be configured as "smpp-first", so that all SMS routing is determined by mslookup. If configured without
|
||||
smpp-first, OsmoMSC may try to deliver an SMS locally, even though the recipient has recently moved to a different site.
|
||||
|
||||
An example OsmoMSC configuration to work with esme_dgsm.py:
|
||||
|
||||
----
|
||||
smpp
|
||||
local-tcp-ip 127.0.0.1 2775
|
||||
system-id test-msc
|
||||
policy closed
|
||||
smpp-first
|
||||
# outgoing to esme_dgsm.py
|
||||
esme OSMPP
|
||||
no alert-notifications
|
||||
password foo
|
||||
default-route
|
||||
# incoming from esme_dgsm.py
|
||||
esme ISMPP
|
||||
no alert-notifications
|
||||
password foo
|
||||
----
|
||||
|
||||
Launch esme_dgsm.py alongside OsmoMSC:
|
||||
|
||||
----
|
||||
./esme_dgsm.py --src-host 127.0.0.1
|
||||
----
|
||||
|
||||
esme_dgsm.py will be notified via SMPP for each SMS to be delivered, and will forward them either to a remote
|
||||
recipient, or back to the same OsmoMSC, depending on the mslookup result. If the MSISDN is not reachable (or
|
||||
esme_dgsm.py can't handle the message for other reasons), it returns the RSYSERR code back to OsmoMSC.
|
||||
|
||||
Note that the esme_dgsm.py is a proof of concept and should not be used in production. It has several limitations, such
|
||||
as not supporting multipart SMS messages.
|
||||
|
||||
===== SMS-Over-GSUP
|
||||
|
||||
The GSUP protocol defines SMS delivery messages. When OsmoMSC is configured to deliver SMS via GSUP, MO SMS are directly
|
||||
forwarded to the HLR, which will determine where to forward the SMS-over-GSUP messages using its mslookup client.
|
||||
|
||||
FIXME implement this
|
333
doc/manuals/chapters/proxy_cache.adoc
Normal file
333
doc/manuals/chapters/proxy_cache.adoc
Normal file
@@ -0,0 +1,333 @@
|
||||
== Distributed GSM / GSUP Proxy Cache: Remedy Temporary Link Failure to Home HLR
|
||||
|
||||
The aim of the Proxy Cache is to still provide service to roaming subscribers even if the GSUP link to the home HLR is
|
||||
temporarily down or unresponsive.
|
||||
|
||||
If a subscriber from a remote site is currently roaming at this local site, and the link to the subscriber's home HLR
|
||||
has succeeded before, the GSUP proxy cache can try to bridge the time of temporary link failure to that home HLR.
|
||||
|
||||
Tasks to take over from an unreachable home HLR:
|
||||
|
||||
- Cache and send auth tuples on Send Auth Info Request.
|
||||
- Acknowledge periodic Location Updating.
|
||||
- ...?
|
||||
|
||||
=== Design Considerations
|
||||
|
||||
==== Authentication
|
||||
|
||||
The most critical role of the home HLR is providing the Authentication and Key Agreement (AKA) tuples. If the home HLR
|
||||
is not reachable, the lack of fresh authentication challenges would normally cause the subscriber to be rejected. To
|
||||
avoid that, a proxying HLR needs to be able to provide AKA tuples on behalf of the home HLR.
|
||||
|
||||
In short, the strategy of the D-GSM proxy cache is:
|
||||
|
||||
- Try to keep a certain number of unused full UMTS AKA tuples in the proxy cache at all times.
|
||||
- When the MSC requests more tuples, dispense some from the cache, and fill it back up later on, as soon as a good link
|
||||
is available.
|
||||
- When the tuple cache in the proxy HLR runs dry, 3G RAN becomes unusable. But 2G RAN may fall back to GSM AKA, if the
|
||||
proxy HLR configuration permits it: resend previously used GSM AKA auth tuples to the MSC, omitting UMTS AKA items
|
||||
from the Send Auth Info Result, to force the MSC to send a GSM AKA challenge on 2G.
|
||||
|
||||
The remaining part of this section provides detailed reasoning for this strategy.
|
||||
|
||||
The aim is to attach a subscriber without immediate access to the authentication key data.
|
||||
|
||||
Completely switching off authentication would be an option on GERAN (2G), but this would mean complete lack of
|
||||
encryption on the air interface, and is not recommended. On 3G and later, authentication is always mandatory.
|
||||
|
||||
The key data is known only to the USIM and the home HLR. The HLR generates distinct authentication tuples, each
|
||||
containing a cryptographic challenge (RAND, AUTN) and its expected response (SRES, XRES). The MSC consumes one tuple
|
||||
per authentication: it sends the challenge to the subscriber, and compares the response received.
|
||||
|
||||
The proxy could generate fresh tuples if the cryptographic key data (Ki,K,OP/OPC) from the home HLR was shared with the
|
||||
proxy HLR. Distributed GSM does not do this, because:
|
||||
|
||||
- The key data is cryptographically very valuable. If it were leaked, any and all authentication challenges would be
|
||||
fully compromised.
|
||||
|
||||
- In D-GSM, each home site shall retain exclusive authority over the user data. It should not be necessary to share the
|
||||
secret keys with any remote site.
|
||||
|
||||
So, how about resending already used auth tuples to the MSC when no fresh ones are available? Resending identical
|
||||
authentication challenges makes the system vulnerable to relatively trivial replay-attacks, but this may be an
|
||||
acceptable fallback in situations of failing links, if it means being able to provide reliable roaming.
|
||||
|
||||
But, even if a proxying HLR is willing to compromise cryptographic security to improve service, this can only work with
|
||||
GSM AKA:
|
||||
|
||||
- In GSM AKA (so-called 2G auth), tuples may be re-used any amount of times without a strict need to generate more
|
||||
authentication challenges. The SIM will merely calculate the (same) SRES response again, and authentication will
|
||||
succeed. It is bad security to do so, but it is a choice the core network is free to make.
|
||||
|
||||
- UMTS AKA (Milenage or so-called 3G auth, but also used on 2G GERAN) adds mutual authentication, i.e. the core network
|
||||
must prove that it is authentic. Specifically to thwart replay-attacks that would spoof a core network, UMTS AKA
|
||||
contains an ongoing sequence number (SQN) that is woven into the authentication challenge. An SQN may skip forward by
|
||||
a certain number of counts, but it can never move backwards. If a USIM detects a stale SQN, it will request an
|
||||
authentication re-synchronisation (by passing AUTS in an Authentication Failure message), after which a freshly
|
||||
generated UMTS AKA challenge is strictly required -- not possible with an unresponsive home HLR.
|
||||
|
||||
Post-R99 (1999) 2G GERAN networks are capable of UMTS AKA, so, not only 3G, but also the vast majority of 2G networks
|
||||
today use UMTS AKA -- and so does Osmocom, typically. Hence it is desirable to fully support UMTS AKA in D-GSM.
|
||||
|
||||
[options="header"]
|
||||
|===
|
||||
| RAN | authentication is... 2+| available AKA types
|
||||
| GERAN (2G) | optional | GSM AKA | UMTS AKA
|
||||
| UTRAN (3G) | mandatory | - | UMTS AKA
|
||||
|===
|
||||
|
||||
UMTS AKA will not allow re-sending previously used authentication tuples. But a UMTS capable SIM will fall back to GSM
|
||||
AKA if the network sent only a GSM AKA challenge. If the proxy HLR sends only GSM AKA tuples, then the MSC will request
|
||||
GSM authentication, and re-sending old tuples is again possible. However, a USIM will only fall back to GSM AKA if the
|
||||
phone is attaching on a 2G network. For 3G RAN and later, UMTS AKA is mandatory. So, as soon as a site uses 3G or newer
|
||||
RAN technology, there is simply no way to resend previously used authentication tuples.
|
||||
|
||||
The only way to have unused UMTS AKA tuples in the proxy HLR is to already have them stored from an earlier time. The
|
||||
idea is to request more auth tuples in advance whenever the link is available, and cache them in the proxy. When the MSC
|
||||
uses up some tuples from the proxy HLR, the proxy cache can fill up again in its own time, by requesting more tuples
|
||||
from the home HLR at a time of good link. Then, the next time the subscriber needs immediate action, it does not matter
|
||||
whether the home HLR is directly reachable or not.
|
||||
|
||||
In an aside, since OsmoMSC already caches a number of authentication tuples, one option would be to implement this in
|
||||
OsmoMSC, and not in the proxy HLR: the MSC could request new tuples long before its tuple cache runs dry. However, the
|
||||
OsmoMSC VLR state is volatile, and a power cycle of the system would lose the tuple cache; if the home HLR is
|
||||
unreachable at the same time of the power cycle, roaming service would be interrupted. The proxy cache in the HLR is
|
||||
persistent, so roaming can continue immediately after a power cycle, even if the home HLR link is down.
|
||||
|
||||
==== Location Updating
|
||||
|
||||
Any attached subscriber periodically repeats a Location Updating procedure, typically every 15 minutes. If a home HLR is
|
||||
unreachable at the time of the periodic Location Updating, a roaming subscriber would assume that it is detached from
|
||||
the network, even though the local site it is roaming at is still fully operational.
|
||||
|
||||
The aim of D-GSM is to keep subscribers attached even if the remote home HLR is temporarily unreachable. The simplest
|
||||
way to achieve that is by directly responding with a Update Location Result to the MSC.
|
||||
|
||||
In addition to accepting an Update Location, a proxy HLR should also start an Insert Subscriber Data procedure, as a
|
||||
home HLR would do. For a periodic Location Updating, the MSC should already know all of the information that an Insert
|
||||
Subscriber Data would convey (i.e. the MSISDN), and there would be no strict need to resend this data. But if a
|
||||
subscriber quickly detaches and re-attaches (e.g. the device rebooted), the MSC has discarded the subscriber info from
|
||||
the VLR, and hence the proxy HLR should also always perform an Insert Subscriber Data. (On the GSUP wire, a periodic LU
|
||||
is indistinguishable from an IMSI-Attach LU.)
|
||||
|
||||
Furthermore, the longer the proxy HLR's cache keeps a roaming subscriber's data after an IMSI Detach, the longer it is
|
||||
possible for the subscriber to immediately re-attach despite the home HLR being temporarily unreachable.
|
||||
|
||||
If a subscriber has carried out a GSUP Update Location with the proxy HLR while the home HLR was unreachable, it is not
|
||||
strictly necessary to repeat that Update Location message to the home HLR later. The home HLR does keep a timestamped
|
||||
record of an Update Location from a proxy HLR if seen, but that has no visible effect on serving the subscriber:
|
||||
|
||||
- If the home HLR still thinks that the subscriber is currently attached at the home site, it will respond to mslookup
|
||||
requests. But the actual site the subscriber is roaming at will have a younger age, and its mslookup responses will
|
||||
win.
|
||||
|
||||
- If the home HLR has no record of the subscriber being attached recently, or has a record of being attached at another
|
||||
remote site, it does not respond to mslookup requests for that subscriber. If it records the new proxy LU, it still
|
||||
does not respond to mslookup requests since the subscriber is attached remotely, i.e. there is no difference.
|
||||
|
||||
It is thinkable to always handle an Update Location in the proxy HLR, and never even attempt to involve the home HLR in
|
||||
case the proxy cache already has data for a given subscriber, but then the proxy HLR would never notice a changed MSISDN
|
||||
or authorization status for this subscriber. It is best practice to involve the home HLR whenever possible.
|
||||
|
||||
==== IMSI Detach
|
||||
|
||||
If a GSUP client reports a detaching IMSI when the home HLR is not reachable, simply respond with an ack.
|
||||
|
||||
It is not required to signal the home HLR with a detach once the link is back up. A home HLR anyway flags a remotely
|
||||
roaming subscriber as attached-at-a-proxy, and there is literally no difference between telling a home HLR about a
|
||||
detach or not.
|
||||
|
||||
(TODO: is there even a GSUP message that a VLR should send on IMSI Detach? see OS#4374)
|
||||
|
||||
[[proxy_cache_umts_aka_resync]]
|
||||
==== UMTS AKA Resync
|
||||
|
||||
When the SQN between USIM and AUC (subscriber and home HLR) have diverged, the Send Authentication Info Request from the
|
||||
MSC contains an AUTS IE. This means that a resynchronization between USIM and AUC (the home HLR) is necessary. All of
|
||||
the UMTS AKA tuples in the proxy cache are now unusable, and the home HLR must respond with fresh tuples after doing a
|
||||
resync. This also means that either the home HLR must be reachable immediately, or GSM AKA fallback must be allowed for
|
||||
the subscriber to remain in roaming service.
|
||||
|
||||
In short:
|
||||
|
||||
- A UMTS AKA resync is handled similarly to the attaching of a so far unknown subscriber.
|
||||
- With the exception that previous GSM AKA tuples may be available to try a fallback to re-using older tuples.
|
||||
|
||||
Needless to say that avoiding the need for UMTS AKA resynchronization is an important aspect of D-GSM's resilience
|
||||
against unreliable links.
|
||||
|
||||
In UMTS AKA, there is not one single SQN, but there are a number SQN slots, called IND slots or IND buckets. The IND
|
||||
bitlen configured on the USIM determines the amount of slots available. The IND bitlen is usually 5, i.e. 2^5^ = 32
|
||||
slots. Monotonously rising SQN are only strictly enforced within each slot, so that each site should maintain a
|
||||
different IND slot. OsmoHLR determines distinct IND slots based on the IPA unit name. As soon as more than 16 sites
|
||||
(with an MSC and SGSN each) are maintained, IND slots may be shared between distinct sites, and administrative care
|
||||
should be taken to choose wisely which sites share the same slots: those that least share a common user group.
|
||||
|
||||
On 2G RAN, it may be possible to fall back to GSM AKA after a UMTS AKA resync request.
|
||||
TODO: test this
|
||||
|
||||
Either way, the AUTS that was received from the MSC definitely needs to find its way to the home HLR, and, ideally, the
|
||||
immediately returned auth tuples from the home HLR should be used to attach the subscriber.
|
||||
|
||||
=== CS and PS
|
||||
|
||||
Each subscriber may have multiple HLR subscriptions from distinct CN Domain VLRs at any time: Circuit Switched (MSC) and
|
||||
Packet Switched (SGSN) attach separately and perform Update Location Requests that are completely orthogonal, as far as
|
||||
the HLR is concerned.
|
||||
|
||||
Particularly the UMTS AKA tuples, which use distinct IND slots per VLR, need to be cached separately per CN Domain.
|
||||
|
||||
Hence it is not enough to maintain one cache per subscriber. A separate auth tuple cache and Mobility Management state
|
||||
has to be kept for each VLR that is requesting roaming service for a given subscriber.
|
||||
|
||||
=== Intercepting GSUP Conversations
|
||||
|
||||
Taking over GSUP conversations in the proxy HLR is not as trivial as it may sound. Here are potential problems and how
|
||||
to fix them.
|
||||
|
||||
[[proxy_cache_gsup_mm_messages]]
|
||||
==== Which GSUP Conversations to Intercept
|
||||
|
||||
For the purpose of providing highly available roaming despite unreliable links to the home HLR, it suffices to intercept
|
||||
Mobility Management (MM) related GSUP messages, only:
|
||||
|
||||
- Send Auth Info Request / Result
|
||||
- Update Location Request / Result
|
||||
- Insert Subscriber Data Request / Result
|
||||
- PurgeMS Request / Result (?)
|
||||
|
||||
An interesting feature would be to also intercept specific USSD requests, like returning the own MSISDN or IMSI more
|
||||
reliably, or handling services that only make sense when served by the local site. At the time of writing, this is seen
|
||||
as a future extension of D-GSM and not considered for implementation.
|
||||
|
||||
==== Determining Whether a Home HLR is Responsive
|
||||
|
||||
Normally, all GSUP messages are merely routed via the proxy HLR and are handled by the home HLR. The idea is that the
|
||||
proxy HLR jumps in and saves a GSUP conversation when the home HLR is not answering properly.
|
||||
|
||||
The simplest method to decide whether a home HLR is currently connected would be to look at the GSUP client state.
|
||||
However, a local flag that indicates an established GSUP connection does not necessarily mean a reliable link.
|
||||
There are keep-alive messages on the GSUP/IPA link, and a lost connection should reflect in the client state, so that a
|
||||
lost GSUP link definitely indicates an unresponsive home HLR. But for various reasons (e.g. packet loss), the link might
|
||||
look intact, but still a given GSUP message fails to get a response from the home HLR.
|
||||
|
||||
A more resilient method to decide whether a home HLR is responsive is to keep track of every MM related GSUP
|
||||
conversation for each subscriber, and to jump in and take over the GSUP conversation as soon as the response is taking
|
||||
too long to arrive. However, choosing an inadequate timeout period would either mean responding after the MSC has
|
||||
already timed out (too slow), or completely cutting off all responses from a high-latency home HLR (too fast).
|
||||
|
||||
Also, if the proxy HLR has already responded to the MSC, but a slow home HLR's response arrives shortly after,
|
||||
forwarding this late message to the MSC on top of the earlier response to the same request would confuse the GSUP
|
||||
conversation.
|
||||
|
||||
So, the proxy HLR just jumping into the GSUP conversation when a specific delay has passed is fairly complex and error
|
||||
prone. A better idea is to always intercept MM related GSUP conversations:
|
||||
|
||||
[[proxy_cache_gsup_conversations]]
|
||||
==== Solution: Distinct GSUP Conversations
|
||||
|
||||
A solution that avoids all of the above problems is to *always* take over *all* MM related conversations (see
|
||||
<<proxy_cache_gsup_mm_messages>>), as soon as the proxy has sufficient data to service them by itself; at the same time,
|
||||
the proxy HLR should also relay the same requests to the home HLR, and acknowledge its responses, after the fact.
|
||||
|
||||
If the proxy cache already has a complete record of a subscriber, the proxy HLR can always directly accept an Update
|
||||
Location Request, including an Insert Subscriber Data. A prompt response ensures that the MSC does not timeout its GSUP
|
||||
request, and reduces waiting time for the subscriber.
|
||||
|
||||
To ensure that the proxy HLR's data on the subscriber doesn't become stale and diverge from the home HLR, the proxy
|
||||
asynchronously also forwards an Update Location Request to the home HLR. In most normal cases, there will be no
|
||||
surprises, and the home HLR will continue with an Insert Subscriber Data Request containing already known data, and an
|
||||
Update Location Result accepting the LU.
|
||||
|
||||
If the home HLR does not respond, the proxy HLR ignores that fact -- the home HLR is not reachable, and the aim is to
|
||||
continue to service the subscriber for the time being.
|
||||
|
||||
But, should the home HLR's Insert Subscriber Data Request send different data than the proxy cache sees on record, the
|
||||
proxy HLR can trigger another Insert Subscriber Data Request to the MSC, to correct the stale data sent before.
|
||||
|
||||
Similarly, if the home HLR rejects the Update Location Request completely, the proxy HLR can tell the MSC to detach the
|
||||
subscriber with a Cancel Location Request message, as soon as it notices the rejection.
|
||||
|
||||
Note that a UMTS AKA resynchronization request invalidates the entire auth tuple cache and needs to either be sent to
|
||||
the home HLR immediately, if available, or the AUTS from the USIM must later reach the home HLR to obtain fresh UMTS AKA
|
||||
tuples for the cache. See <<proxy_cache_umts_aka_resync>>.
|
||||
|
||||
=== Message Sequences
|
||||
|
||||
==== Normal Roaming Attach
|
||||
|
||||
On first attaching via a proxy HLR, when there is no proxy state for the subscriber yet, the home HLR must be reachable.
|
||||
|
||||
The normal procedure takes place without modification, except that he proxy HLR keeps a copy of the first auth tuples it
|
||||
forwards from the home HLR back to the MSC (marked as used) (1). This is to have auth tuples available for resending
|
||||
already used tuples in a fallback to GSM AKA, in case this is enabled in the proxy HLR config.
|
||||
|
||||
After the Location Updating has completed successfully, the proxy HLR fills up its auth tuple cache by additional Send
|
||||
Auth Info Requests (2). As soon as unused auth tuples become available, the proxy HLR can discard already used tuples
|
||||
from (1).
|
||||
|
||||
.Normal attaching of a subscriber that is roaming here
|
||||
["mscgen"]
|
||||
----
|
||||
include::proxy_cache_attach.msc[]
|
||||
----
|
||||
|
||||
==== MSC Requests More Auth Tuples
|
||||
|
||||
As soon as the MSC has run out of fresh auth tuples, it will ask the HLR proxy for more. Without proxy caching, this
|
||||
request would be directly forwarded to the home HLR. Instead, the proxy HLR finds unused auth tuples in the cache and
|
||||
directly sends those (3). Even if there is a reliable link, the home HLR is not contacted at this point.
|
||||
|
||||
Directly after completing the Send Auth Info Result, the proxy HLR finds that less tuples than requested by the D-GSM
|
||||
configuration are cached, and asks the home HLR for more tuples, to fill up the cache (4). If there currently is no
|
||||
reliable link, this will fail, and the proxy HLR will retry periodically (5) / upon GSUP reconnect.
|
||||
|
||||
.When the MSC has used up all of its auth tuples, but the proxy HLR still has unused auth tuples in the cache
|
||||
["mscgen"]
|
||||
----
|
||||
include::proxy_cache_more_tuples.msc[]
|
||||
----
|
||||
|
||||
==== Running Out of Auth Tuples
|
||||
|
||||
When all fresh tuples from the proxy HLR have been used up, and the home HLR remains unreachable, the proxy HLR normally
|
||||
fails and rejects the subscriber (default configuration).
|
||||
|
||||
If explicitly enabled in the configuration, the proxy HLR will attempt to fall back to GSM AKA and resend already spent
|
||||
tuples, deliberately omitting UMTS AKA parts (6).
|
||||
|
||||
Note that an attempt to refill the tuple cache in the proxy HLR always happens asynchronously. If there are no tuples,
|
||||
that means the link to the home HLR is currently broken, and there is no point in trying to contact it now. Tuples will
|
||||
be obtained as soon as the link is established again.
|
||||
|
||||
.When the MSC has used up all of its auth tuples and the proxy HLR tuple cache is dry
|
||||
["mscgen"]
|
||||
----
|
||||
include::proxy_cache_tuple_cache_dry.msc[]
|
||||
----
|
||||
|
||||
==== Periodic Location Updating
|
||||
|
||||
Each subscriber performs periodic Location Updating to ensure that it is not implicitly detached from the network. When
|
||||
the proxy HLR already has a proxy cache for this subscriber, all information to complete the periodic Location Updating
|
||||
is already known in the proxy HLR. If the link to the home HLR is unresponsive, the proxy HLR mimicks the Insert
|
||||
Subscriber Data Request that the home HLR would normally send, using the cached MSISDN, and then sends the Update
|
||||
Location Result. The subscriber remains attached without a responsive link to the home HLR being required.
|
||||
|
||||
.Periodic Location Updating when the MSC still has unused auth tuples
|
||||
["mscgen"]
|
||||
----
|
||||
include::proxy_cache_periodic_lu.msc[]
|
||||
----
|
||||
|
||||
==== UMTS AKA Resync
|
||||
|
||||
The AUTS from a UMTS AKA resync needs to reach the home HLR sooner or later, and a resync renders all UMTS AKA tuples in
|
||||
the cache stale.
|
||||
|
||||
.Cached tuples become unusable from a UMTS AKA resynchronisation request from the USIM.
|
||||
["mscgen"]
|
||||
----
|
||||
include::proxy_cache_umts_aka_resync.msc[]
|
||||
----
|
39
doc/manuals/chapters/proxy_cache_attach.ladder
Normal file
39
doc/manuals/chapters/proxy_cache_attach.ladder
Normal file
@@ -0,0 +1,39 @@
|
||||
{hscale=2}
|
||||
ms = MS,BSS
|
||||
msc = MSC
|
||||
hlr = HLR proxy
|
||||
home = Home HLR
|
||||
|
||||
ms -> msc Location Updating Request (IMSI Attach)
|
||||
msc -> hlr Send Auth Info Request
|
||||
hlr <> hlr No proxy cache data available for this subscriber
|
||||
hlr () home mslookup finds the home HLR
|
||||
hlr -> home Send Auth Info Request
|
||||
hlr <- home Send Auth Info Result
|
||||
with 5 auth tuples
|
||||
hlr () . (1) Keep a copy of the auth tuples
|
||||
msc <- hlr Send Auth Info Result
|
||||
msc () . MSC stores 5 auth tuples,
|
||||
uses the first one now,
|
||||
and keeps the rest for later requests
|
||||
ms () msc Authentication
|
||||
msc -> hlr Update Location Request
|
||||
hlr -> home Update Location Request
|
||||
hlr <- home Insert Subscriber Data Request
|
||||
(with subscriber's MSISDN)
|
||||
hlr () . proxy HLR caches the MSISDN
|
||||
msc <- hlr Insert Subscriber Data Request
|
||||
msc -> hlr Insert Subscriber Data Result
|
||||
hlr -> home Insert Subscriber Data Result
|
||||
hlr <- home Update Location Result
|
||||
msc <- hlr Update Location Result
|
||||
ms <- msc Location Updating Accept
|
||||
hlr <> . After successful Update Location, check the cache
|
||||
hlr () . (2) Ask for more auth tuples to cache
|
||||
(amount of tuples configurable)
|
||||
hlr -> home Send Auth Info Request
|
||||
hlr <- home Send Auth Info Result
|
||||
hlr () . store 5 more tuples
|
||||
hlr -> home Send Auth Info Request
|
||||
hlr <- home Send Auth Info Result
|
||||
hlr () . store yet 5 more tuples
|
33
doc/manuals/chapters/proxy_cache_attach.msc
Normal file
33
doc/manuals/chapters/proxy_cache_attach.msc
Normal file
@@ -0,0 +1,33 @@
|
||||
msc {
|
||||
hscale="2";
|
||||
ms[label="MS,BSS"],__msc[label="MSC"],hlr[label="HLR proxy"],home[label="Home HLR"];
|
||||
|
||||
ms => __msc [label="Location Updating Request (IMSI Attach)"];
|
||||
__msc => hlr [label="Send Auth Info Request"];
|
||||
hlr abox hlr [label="No proxy cache data available for this subscriber"];
|
||||
hlr rbox home [label="mslookup finds the home HLR"];
|
||||
hlr => home [label="Send Auth Info Request"];
|
||||
hlr <= home [label="Send Auth Info Result\nwith 5 auth tuples"];
|
||||
hlr rbox hlr [label="(1) Keep a copy of the auth tuples"];
|
||||
__msc <= hlr [label="Send Auth Info Result"];
|
||||
__msc rbox __msc [label="MSC stores 5 auth tuples,\nuses the first one now,\nand keeps the rest for later requests"];
|
||||
ms rbox __msc [label="Authentication"];
|
||||
__msc => hlr [label="Update Location Request"];
|
||||
hlr => home [label="Update Location Request"];
|
||||
hlr <= home [label="Insert Subscriber Data Request\n(with subscriber's MSISDN)"];
|
||||
hlr rbox hlr [label="proxy HLR caches the MSISDN"];
|
||||
__msc <= hlr [label="Insert Subscriber Data Request"];
|
||||
__msc => hlr [label="Insert Subscriber Data Result"];
|
||||
hlr => home [label="Insert Subscriber Data Result"];
|
||||
hlr <= home [label="Update Location Result"];
|
||||
__msc <= hlr [label="Update Location Result"];
|
||||
ms <= __msc [label="Location Updating Accept"];
|
||||
hlr abox hlr [label="After successful Update Location, check the cache"];
|
||||
hlr rbox hlr [label="(2) Ask for more auth tuples to cache\n(amount of tuples configurable)"];
|
||||
hlr => home [label="Send Auth Info Request"];
|
||||
hlr <= home [label="Send Auth Info Result"];
|
||||
hlr rbox hlr [label="store 5 more tuples"];
|
||||
hlr => home [label="Send Auth Info Request"];
|
||||
hlr <= home [label="Send Auth Info Result"];
|
||||
hlr rbox hlr [label="store yet 5 more tuples"];
|
||||
}
|
31
doc/manuals/chapters/proxy_cache_more_tuples.ladder
Normal file
31
doc/manuals/chapters/proxy_cache_more_tuples.ladder
Normal file
@@ -0,0 +1,31 @@
|
||||
{hscale=2}
|
||||
ms = MS,BSS
|
||||
msc = MSC
|
||||
hlr = HLR proxy
|
||||
home = Home HLR
|
||||
|
||||
ms -> msc CM Service Request / Paging Response
|
||||
msc -> hlr Send Auth Info Request
|
||||
hlr () . Use already set up proxy path
|
||||
hlr <> . there still are unsent auth tuples
|
||||
in the cache
|
||||
hlr () . (3) Send cached, fresh tuples
|
||||
msc <- hlr Send Auth Info Result
|
||||
containing auth tuples
|
||||
from the proxy cache
|
||||
ms () msc Authentication
|
||||
ms () msc Continue the CM Service / Paging action
|
||||
hlr <> . Note that there are no/few unused tuples in the cache, fill up again
|
||||
hlr () . (4) Ask for more auth tuples to cache
|
||||
hlr -> home Send Auth Info Request
|
||||
--- If the home HLR link is not working
|
||||
hlr <> . no link
|
||||
or
|
||||
response timeout
|
||||
hlr () . (5) Set up a timer to retry SAI
|
||||
(a few minutes?)
|
||||
hlr <> . Timer triggers
|
||||
hlr -> home Send Auth Info Request
|
||||
--- If the home HLR link is functional
|
||||
hlr <- home Send Auth Info Result
|
||||
hlr () . store 5 more tuples
|
24
doc/manuals/chapters/proxy_cache_more_tuples.msc
Normal file
24
doc/manuals/chapters/proxy_cache_more_tuples.msc
Normal file
@@ -0,0 +1,24 @@
|
||||
msc {
|
||||
hscale="2";
|
||||
ms[label="MS,BSS"],__msc[label="MSC"],hlr[label="HLR proxy"],home[label="Home HLR"];
|
||||
|
||||
ms => __msc [label="CM Service Request / Paging Response"];
|
||||
__msc => hlr [label="Send Auth Info Request"];
|
||||
hlr rbox hlr [label="Use already set up proxy path"];
|
||||
hlr abox hlr [label="there still are unsent auth tuples\nin the cache"];
|
||||
hlr rbox hlr [label="(3) Send cached, fresh tuples"];
|
||||
__msc <= hlr [label="Send Auth Info Result\ncontaining auth tuples\nfrom the proxy cache"];
|
||||
ms rbox __msc [label="Authentication"];
|
||||
ms rbox __msc [label="Continue the CM Service / Paging action"];
|
||||
hlr abox hlr [label="Note that there are no/few unused tuples in the cache, fill up again"];
|
||||
hlr rbox hlr [label="(4) Ask for more auth tuples to cache"];
|
||||
hlr => home [label="Send Auth Info Request"];
|
||||
--- [label="If the home HLR link is not working"];
|
||||
hlr abox hlr [label="no link\nor\nresponse timeout"];
|
||||
hlr rbox hlr [label="(5) Set up a timer to retry SAI\n(a few minutes?)"];
|
||||
hlr abox hlr [label="Timer triggers"];
|
||||
hlr => home [label="Send Auth Info Request"];
|
||||
--- [label="If the home HLR link is functional"];
|
||||
hlr <= home [label="Send Auth Info Result"];
|
||||
hlr rbox hlr [label="store 5 more tuples"];
|
||||
}
|
47
doc/manuals/chapters/proxy_cache_periodic_lu.ladder
Normal file
47
doc/manuals/chapters/proxy_cache_periodic_lu.ladder
Normal file
@@ -0,0 +1,47 @@
|
||||
{hscale=2}
|
||||
ms = MS,BSS
|
||||
msc = MSC
|
||||
hlr = HLR proxy
|
||||
home = Home HLR
|
||||
|
||||
ms -> msc Location Updating Request (Periodic)
|
||||
ms () msc Authentication,
|
||||
using the next of 5 auth tuples the MSC has stored
|
||||
msc -> hlr Update Location Request
|
||||
hlr () . Use already set up proxy path
|
||||
hlr <> . (8) proxy cache already has all information to answer
|
||||
msc <- hlr Insert Subscriber Data Request
|
||||
msc -> hlr Insert Subscriber Data Result
|
||||
msc <- hlr Update Location Result
|
||||
ms <- msc Location Updating Accept
|
||||
hlr () . (9) Verify Update Location with home HLR
|
||||
|||
|
||||
--- if the home HLR has no changes and accepts
|
||||
hlr -> home Update Location Request
|
||||
hlr <- home Insert Subscriber Data Request
|
||||
hlr -> home Insert Subscriber Data Result
|
||||
hlr <> . Notice identical MSISDN
|
||||
hlr <- home Update Location Result
|
||||
|||
|
||||
--- if the home HLR is unreachable
|
||||
hlr -> home Update Location Request
|
||||
hlr <> . no link
|
||||
or
|
||||
response timeout
|
||||
hlr () . Don't care, carry on
|
||||
|||
|
||||
--- if the home HLR has a modified MSISDN, and accepts
|
||||
hlr -> home Update Location Request
|
||||
hlr <- home Insert Subscriber Data Request
|
||||
hlr -> home Insert Subscriber Data Result
|
||||
hlr <> . Notice changed MSISDN
|
||||
msc <- hlr Insert Subscriber Data Request
|
||||
msc -> hlr Insert Subscriber Data Result
|
||||
hlr <- home Update Location Result
|
||||
|||
|
||||
--- if the home HLR rejects
|
||||
hlr -> home Update Location Request
|
||||
hlr <- home Update Location Error
|
||||
msc <- hlr Cancel Location Request
|
||||
msc -> hlr Cancel Location Result
|
||||
hlr () . Clear subscriber cache
|
43
doc/manuals/chapters/proxy_cache_periodic_lu.msc
Normal file
43
doc/manuals/chapters/proxy_cache_periodic_lu.msc
Normal file
@@ -0,0 +1,43 @@
|
||||
msc {
|
||||
hscale="2";
|
||||
ms[label="MS,BSS"],__msc[label="MSC"],hlr[label="HLR proxy"],home[label="Home HLR"];
|
||||
|
||||
ms => __msc [label="Location Updating Request (Periodic)"];
|
||||
ms rbox __msc [label="Authentication,\nusing the next of 5 auth tuples the MSC has stored"];
|
||||
__msc => hlr [label="Update Location Request"];
|
||||
hlr rbox hlr [label="Use already set up proxy path"];
|
||||
hlr abox hlr [label="(8) proxy cache already has all information to answer"];
|
||||
__msc <= hlr [label="Insert Subscriber Data Request"];
|
||||
__msc => hlr [label="Insert Subscriber Data Result"];
|
||||
__msc <= hlr [label="Update Location Result"];
|
||||
ms <= __msc [label="Location Updating Accept"];
|
||||
hlr rbox hlr [label="(9) Verify Update Location with home HLR"];
|
||||
|||;
|
||||
--- [label="if the home HLR has no changes and accepts"];
|
||||
hlr => home [label="Update Location Request"];
|
||||
hlr <= home [label="Insert Subscriber Data Request"];
|
||||
hlr => home [label="Insert Subscriber Data Result"];
|
||||
hlr abox hlr [label="Notice identical MSISDN"];
|
||||
hlr <= home [label="Update Location Result"];
|
||||
|||;
|
||||
--- [label="if the home HLR is unreachable"];
|
||||
hlr => home [label="Update Location Request"];
|
||||
hlr abox hlr [label="no link\nor\nresponse timeout"];
|
||||
hlr rbox hlr [label="Don't care, carry on"];
|
||||
|||;
|
||||
--- [label="if the home HLR has a modified MSISDN, and accepts"];
|
||||
hlr => home [label="Update Location Request"];
|
||||
hlr <= home [label="Insert Subscriber Data Request"];
|
||||
hlr => home [label="Insert Subscriber Data Result"];
|
||||
hlr abox hlr [label="Notice changed MSISDN"];
|
||||
__msc <= hlr [label="Insert Subscriber Data Request"];
|
||||
__msc => hlr [label="Insert Subscriber Data Result"];
|
||||
hlr <= home [label="Update Location Result"];
|
||||
|||;
|
||||
--- [label="if the home HLR rejects"];
|
||||
hlr => home [label="Update Location Request"];
|
||||
hlr <= home [label="Update Location Error"];
|
||||
__msc <= hlr [label="Cancel Location Request"];
|
||||
__msc => hlr [label="Cancel Location Result"];
|
||||
hlr rbox hlr [label="Clear subscriber cache"];
|
||||
}
|
21
doc/manuals/chapters/proxy_cache_tuple_cache_dry.ladder
Normal file
21
doc/manuals/chapters/proxy_cache_tuple_cache_dry.ladder
Normal file
@@ -0,0 +1,21 @@
|
||||
{hscale=2}
|
||||
ms = MS,BSS
|
||||
msc = MSC
|
||||
hlr = HLR proxy
|
||||
home = Home HLR
|
||||
|
||||
ms -> msc CM Service Request / Paging Response
|
||||
msc -> hlr Send Auth Info Request
|
||||
hlr () . Use already set up proxy path
|
||||
hlr <> . All cached auth tuples have been sent to the MSC before
|
||||
--- If no GSM AKA fallback is allowed
|
||||
msc <- hlr Send Auth Info Error
|
||||
ms () msc Detach
|
||||
--- If GSM AKA fallback is allowed
|
||||
hlr () . (6) Resend only GSM AKA tuples, already sent earlier
|
||||
msc <- hlr Send Auth Info Result
|
||||
containing GSM AKA auth tuples
|
||||
from the proxy cache
|
||||
ms () msc 2G: Authentication
|
||||
3G: Detach
|
||||
ms () msc Continue the CM Service / Paging action
|
17
doc/manuals/chapters/proxy_cache_tuple_cache_dry.msc
Normal file
17
doc/manuals/chapters/proxy_cache_tuple_cache_dry.msc
Normal file
@@ -0,0 +1,17 @@
|
||||
msc {
|
||||
hscale="2";
|
||||
ms[label="MS,BSS"],__msc[label="MSC"],hlr[label="HLR proxy"],home[label="Home HLR"];
|
||||
|
||||
ms => __msc [label="CM Service Request / Paging Response"];
|
||||
__msc => hlr [label="Send Auth Info Request"];
|
||||
hlr rbox hlr [label="Use already set up proxy path"];
|
||||
hlr abox hlr [label="All cached auth tuples have been sent to the MSC before"];
|
||||
--- [label="If no GSM AKA fallback is allowed"];
|
||||
__msc <= hlr [label="Send Auth Info Error"];
|
||||
ms rbox __msc [label="Detach"];
|
||||
--- [label="If GSM AKA fallback is allowed"];
|
||||
hlr rbox hlr [label="(6) Resend only GSM AKA tuples, already sent earlier"];
|
||||
__msc <= hlr [label="Send Auth Info Result\ncontaining GSM AKA auth tuples\nfrom the proxy cache"];
|
||||
ms rbox __msc [label="2G: Authentication\n3G: Detach"];
|
||||
ms rbox __msc [label="Continue the CM Service / Paging action"];
|
||||
}
|
55
doc/manuals/chapters/proxy_cache_umts_aka_resync.ladder
Normal file
55
doc/manuals/chapters/proxy_cache_umts_aka_resync.ladder
Normal file
@@ -0,0 +1,55 @@
|
||||
{hscale=2}
|
||||
ms = MS,BSS
|
||||
msc = MSC
|
||||
hlr = HLR proxy
|
||||
home = Home HLR
|
||||
|
||||
ms -> msc CM Service Request / Paging Response
|
||||
msc -> hlr Send Auth Info Request
|
||||
hlr <> . there still are unsent auth tuples
|
||||
in the cache
|
||||
hlr () . Send cached, fresh tuples
|
||||
msc <- hlr Send Auth Info Result
|
||||
containing auth tuples
|
||||
from the proxy cache
|
||||
ms <- msc Authentication Request
|
||||
ms -> msc USIM requests UMTS AKA resync
|
||||
Auth Failure with AUTS
|
||||
msc -> hlr Send Auth Info Request
|
||||
containing AUTS IE
|
||||
hlr () . Mark all UMTS AKA tuples as stale
|
||||
--- If the home HLR responds in time
|
||||
hlr -> home Send Auth Info Request
|
||||
hlr <- home Send Auth Info Result
|
||||
with 5 auth tuples
|
||||
hlr () . clear tuple cache, store new tuples
|
||||
msc <- hlr Send Auth Info Result
|
||||
ms () msc Authentication
|
||||
hlr () . fill up the tuple cache as necessary
|
||||
hlr -> home Send Auth Info Request
|
||||
hlr <- home Send Auth Info Result
|
||||
...
|
||||
--- If the home HLR is unreachable
|
||||
hlr -> home Send Auth Info Request
|
||||
hlr <> . no link
|
||||
or
|
||||
response timeout
|
||||
hlr () . Store the AUTS received earlier,
|
||||
and set up a timer to retry SAI
|
||||
(a few minutes?)
|
||||
--- If no GSM AKA fallback is allowed
|
||||
msc <- hlr Send Auth Info Error
|
||||
ms () msc Detach
|
||||
--- If GSM AKA fallback is allowed
|
||||
hlr () . Resend only GSM AKA tuples, already sent earlier
|
||||
msc <- hlr Send Auth Info Result
|
||||
containing GSM AKA auth tuples
|
||||
from the proxy cache
|
||||
ms () msc 2G: Authentication
|
||||
3G: Detach
|
||||
---
|
||||
hlr <> . AUTS timer triggers
|
||||
hlr -> home Send Auth Info Request
|
||||
hlr <- home Send Auth Info Result
|
||||
hlr () . clear tuple cache, store new tuples
|
||||
hlr () . continue to fill up the cache as necessary
|
41
doc/manuals/chapters/proxy_cache_umts_aka_resync.msc
Normal file
41
doc/manuals/chapters/proxy_cache_umts_aka_resync.msc
Normal file
@@ -0,0 +1,41 @@
|
||||
msc {
|
||||
hscale="2";
|
||||
ms[label="MS,BSS"],__msc[label="MSC"],hlr[label="HLR proxy"],home[label="Home HLR"];
|
||||
|
||||
ms => __msc [label="CM Service Request / Paging Response"];
|
||||
__msc => hlr [label="Send Auth Info Request"];
|
||||
hlr abox hlr [label="there still are unsent auth tuples\nin the cache"];
|
||||
hlr rbox hlr [label="Send cached, fresh tuples"];
|
||||
__msc <= hlr [label="Send Auth Info Result\ncontaining auth tuples\nfrom the proxy cache"];
|
||||
ms <= __msc [label="Authentication Request"];
|
||||
ms => __msc [label="USIM requests UMTS AKA resync\nAuth Failure with AUTS"];
|
||||
__msc => hlr [label="Send Auth Info Request\ncontaining AUTS IE"];
|
||||
hlr rbox hlr [label="Mark all UMTS AKA tuples as stale"];
|
||||
--- [label="If the home HLR responds in time"];
|
||||
hlr => home [label="Send Auth Info Request"];
|
||||
hlr <= home [label="Send Auth Info Result\nwith 5 auth tuples"];
|
||||
hlr rbox hlr [label="clear tuple cache, store new tuples"];
|
||||
__msc <= hlr [label="Send Auth Info Result"];
|
||||
ms rbox __msc [label="Authentication"];
|
||||
hlr rbox hlr [label="fill up the tuple cache as necessary"];
|
||||
hlr => home [label="Send Auth Info Request"];
|
||||
hlr <= home [label="Send Auth Info Result"];
|
||||
...;
|
||||
--- [label="If the home HLR is unreachable"];
|
||||
hlr => home [label="Send Auth Info Request"];
|
||||
hlr abox hlr [label="no link\nor\nresponse timeout"];
|
||||
hlr rbox hlr [label="Store the AUTS received earlier,\nand set up a timer to retry SAI\n(a few minutes?)"];
|
||||
--- [label="If no GSM AKA fallback is allowed"];
|
||||
__msc <= hlr [label="Send Auth Info Error"];
|
||||
ms rbox __msc [label="Detach"];
|
||||
--- [label="If GSM AKA fallback is allowed"];
|
||||
hlr rbox hlr [label="Resend only GSM AKA tuples, already sent earlier"];
|
||||
__msc <= hlr [label="Send Auth Info Result\ncontaining GSM AKA auth tuples\nfrom the proxy cache"];
|
||||
ms rbox __msc [label="2G: Authentication\n3G: Detach"];
|
||||
---;
|
||||
hlr abox hlr [label="AUTS timer triggers"];
|
||||
hlr => home [label="Send Auth Info Request"];
|
||||
hlr <= home [label="Send Auth Info Result"];
|
||||
hlr rbox hlr [label="clear tuple cache, store new tuples"];
|
||||
hlr rbox hlr [label="continue to fill up the cache as necessary"];
|
||||
}
|
@@ -24,6 +24,10 @@ include::{srcdir}/chapters/control.adoc[]
|
||||
|
||||
include::./common/chapters/control_if.adoc[]
|
||||
|
||||
include::{srcdir}/chapters/dgsm.adoc[]
|
||||
|
||||
include::{srcdir}/chapters/proxy_cache.adoc[]
|
||||
|
||||
include::./common/chapters/gsup.adoc[]
|
||||
|
||||
include::./common/chapters/port_numbers.adoc[]
|
||||
|
39
doc/sequence_charts/Makefile.am
Normal file
39
doc/sequence_charts/Makefile.am
Normal file
@@ -0,0 +1,39 @@
|
||||
all:
|
||||
echo "built only on manual invocation, needs mscgen and dot (graphviz) programs: invoke 'make charts'"
|
||||
|
||||
charts: msc dot
|
||||
|
||||
EXTRA_DIST = \
|
||||
proxy_cache.dot \
|
||||
proxy_cache__mm_fsm.dot \
|
||||
proxy_cache__to_home_hlr_fsm.dot \
|
||||
$(NULL)
|
||||
|
||||
CLEANFILES = \
|
||||
proxy_cache.png \
|
||||
proxy_cache__mm_fsm.png \
|
||||
proxy_cache__to_home_hlr_fsm.png \
|
||||
$(NULL)
|
||||
|
||||
msc: \
|
||||
$(NULL)
|
||||
|
||||
dot: \
|
||||
$(builddir)/proxy_cache.png \
|
||||
$(builddir)/proxy_cache__mm_fsm.png \
|
||||
$(builddir)/proxy_cache__to_home_hlr_fsm.png \
|
||||
$(NULL)
|
||||
|
||||
$(builddir)/%.png: %.msc
|
||||
mscgen -T png -o $@ $<
|
||||
|
||||
$(builddir)/%.msc: $(srcdir)/%.ladder
|
||||
@which ladder_to_msc.py || (echo 'PLEASE POINT YOUR $$PATH AT libosmocore/contrib/ladder_to_msc.py' && false)
|
||||
ladder_to_msc.py -i $< -o $@
|
||||
|
||||
$(builddir)/%.png: $(srcdir)/%.dot
|
||||
dot -Tpng $< > $@
|
||||
|
||||
.PHONY: poll
|
||||
poll:
|
||||
while true; do $(MAKE) msc dot; sleep 1; done
|
19
doc/sequence_charts/proxy_cache.dot
Normal file
19
doc/sequence_charts/proxy_cache.dot
Normal file
@@ -0,0 +1,19 @@
|
||||
digraph G {
|
||||
rankdir=LR
|
||||
labelloc=t;
|
||||
|
||||
msc [label="MS/BSS/MSC"]
|
||||
subgraph cluster_proxy {
|
||||
label="HLR Proxy"
|
||||
proxy_mm [label="Proxy Mobility Management FSM",shape=box3d]
|
||||
proxy_home [label="Proxy to Home HLR FSM",shape=box3d]
|
||||
proxy [label="GSUP Proxy"]
|
||||
proxy_mm -> proxy_home [constraint=false,dir=both,label="(FSM events)"]
|
||||
}
|
||||
hlr [label="Home HLR"]
|
||||
|
||||
msc -> proxy_mm [dir=both,label="MM related GSUP\n (immediate response\n if possible)"]
|
||||
proxy_home -> hlr [dir=both,label="MM related GSUP\n (delayed)"]
|
||||
|
||||
msc -> proxy -> hlr [dir=both,label="non-MM GSUP",style=dashed]
|
||||
}
|
78
doc/sequence_charts/proxy_cache__mm_fsm.dot
Normal file
78
doc/sequence_charts/proxy_cache__mm_fsm.dot
Normal file
@@ -0,0 +1,78 @@
|
||||
digraph G {
|
||||
rankdir=TB
|
||||
labelloc=t; label="HLR Proxy MM FSM"
|
||||
|
||||
top,top2,top3[shape=invtriangle,label="(1)"]
|
||||
|
||||
top -> READY
|
||||
|
||||
new [label="proxy_cache_subscr_new()\n/ proxy_cache_subscr_from_db()",shape=box]
|
||||
READY [style=bold]
|
||||
HIBERNATE [shape=note,label="Hibernate\n (keep in DB)"]
|
||||
CLEAR [shape=box,label="Clear DB entry\n (discard completely)"]
|
||||
WAIT_AUTH_TUPLES [style=bold]
|
||||
WAIT_SUBSCR_DATA [style=bold]
|
||||
WAIT_GSUP_ISD_RESULT [style=bold]
|
||||
|
||||
home_fsm [label="Proxy to Home HLR FSM",shape=box3d]
|
||||
{rank=same;READY,home_fsm}
|
||||
|
||||
|
||||
new -> {READY,home_fsm}
|
||||
|
||||
READY -> {event_lu_req,event_auth_info_req} [arrowhead=none]
|
||||
|
||||
event_auth_info_req [shape=rarrow,label="Rx GSUP\nSend Auth Info Request\nfrom MSC"]
|
||||
event_auth_info_req -> junction_send_auth_info_req
|
||||
junction_send_auth_info_req [shape=diamond,label="Unused\nauth tuples\navailable?"]
|
||||
junction_send_auth_info_req -> action_send_auth_info [label="yes"]
|
||||
junction_send_auth_info_req -> emit_need_tuples [label="no"]
|
||||
emit_need_tuples [shape=lpromoter,label="emit\n HOME_EV_CHECK_TUPLES\n to Home FSM"]
|
||||
emit_need_tuples->WAIT_AUTH_TUPLES
|
||||
WAIT_AUTH_TUPLES -> rx_ev_rx_auth_tuples [arrowhead=none]
|
||||
rx_ev_rx_auth_tuples [shape=rpromoter,label="receive\n MM_EV_RX_AUTH_TUPLES"]
|
||||
rx_ev_rx_auth_tuples -> action_send_auth_info
|
||||
action_send_auth_info [shape=larrow,label="Tx GSUP\nSend Auth Info Result\nwith fresh auth tuples\n to MSC"]
|
||||
action_send_auth_info -> emit_check_tuples
|
||||
emit_check_tuples [shape=lpromoter,label="emit\n HOME_EV_CHECK_TUPLES\n to Home FSM"]
|
||||
emit_check_tuples -> top2
|
||||
WAIT_AUTH_TUPLES -> junction_check_auth_fallback [label="Timeout",style=dashed]
|
||||
junction_check_auth_fallback -> action_do_auth_fallback [label="yes",style=dashed]
|
||||
action_do_auth_fallback [shape=larrow,label="Tx GSUP\nSend Auth Info Result\nwith recycled auth tuple\n(GSM AKA only)"]
|
||||
junction_check_auth_fallback [shape=diamond,label="Re-usable\nauth tuples\navailable?"]
|
||||
junction_check_auth_fallback -> action_fail_auth [label="no",style=dashed]
|
||||
action_fail_auth [shape=larrow,label="Tx GSUP\nSend Auth Info Error\npending re-connection to\nthe home HLR"]
|
||||
{action_do_auth_fallback,action_fail_auth} -> top2 [style=dashed]
|
||||
|
||||
event_lu_req [shape=rarrow,label="Rx GSUP\nUpdate Location Request\nfrom MSC"]
|
||||
event_lu_req -> emit_lu_req
|
||||
emit_lu_req [shape=lpromoter,label="emit\n HOME_EV_CONFIRM_LU"];
|
||||
emit_lu_req -> junction_check_subscriber_data
|
||||
junction_check_subscriber_data [shape=diamond,label="Subscriber\nData\nknown?"]
|
||||
junction_check_subscriber_data -> WAIT_SUBSCR_DATA [label=no]
|
||||
WAIT_SUBSCR_DATA -> rx_ev_subscr_data [arrowhead=none]
|
||||
rx_ev_subscr_data [shape=rpromoter,label="receive\n MM_EV_RX_SUBSCR_DATA"];
|
||||
rx_ev_subscr_data -> action_subscr_data_req
|
||||
junction_check_subscriber_data -> action_subscr_data_req [label="yes"]
|
||||
action_subscr_data_req [shape=larrow,label="Tx GSUP\n Insert Subscriber Data\n Request to MSC"]
|
||||
action_subscr_data_req -> WAIT_GSUP_ISD_RESULT
|
||||
WAIT_GSUP_ISD_RESULT -> tx_gsup_isd_res [arrowhead=none]
|
||||
tx_gsup_isd_res [shape=rarrow,label="Rx GSUP\n Insert Subscriber Data Result\nfrom MSC"]
|
||||
tx_gsup_isd_res -> top3
|
||||
|
||||
{WAIT_GSUP_ISD_RESULT,WAIT_SUBSCR_DATA} -> action_lu_reject [label="Timeout",style=dashed]
|
||||
action_lu_reject [shape=larrow,label="Tx GSUP\nUpdate Location Error\nto MSC\npending reconnect of home HLR"]
|
||||
action_lu_reject -> top3 [style=dashed]
|
||||
|
||||
READY -> HIBERNATE [label="Timeout"]
|
||||
READY -> rx_ev_subscr_invalid [arrowhead=none]
|
||||
rx_ev_subscr_invalid[shape=rpromoter,label="receive\n MM_EV_SUBSCR_INVALID"]
|
||||
rx_ev_subscr_invalid -> tx_purge_req
|
||||
tx_purge_req [shape=larrow,label="Tx GSUP\nPurge MS Request"]
|
||||
tx_purge_req -> note_purge [style=dotted]
|
||||
note_purge [shape=note,label="Don't care about\nPurge MS Result"]
|
||||
tx_purge_req -> CLEAR
|
||||
{CLEAR,HIBERNATE} -> TERM
|
||||
TERM[shape=octagon][style=bold]
|
||||
|
||||
}
|
97
doc/sequence_charts/proxy_cache__to_home_hlr_fsm.dot
Normal file
97
doc/sequence_charts/proxy_cache__to_home_hlr_fsm.dot
Normal file
@@ -0,0 +1,97 @@
|
||||
digraph G {
|
||||
rankdir=TB
|
||||
labelloc=t; label="HLR Proxy to Home HLR FSM"
|
||||
|
||||
top,to_top1,to_top2,to_top3,to_top4,to_top5[shape=invtriangle,label="(A)"]
|
||||
top->junction_resolve_home_hlr
|
||||
|
||||
at_clear,to_clear1,to_clear2 [shape=invtriangle,label="(X)"]
|
||||
|
||||
mm_fsm [shape=box3d,label="Proxy MM FSM"]
|
||||
mm_fsm -> junction_resolve_home_hlr [style=invisible,arrowhead=none]
|
||||
|
||||
WAIT_HOME_HLR_RESOLVED [style=bold]
|
||||
WAIT_UPDATE_LOCATION_RESULT [style=bold]
|
||||
WAIT_SEND_AUTH_INFO_RESULT [style=bold]
|
||||
IDLE [style=bold]
|
||||
CLEAR [style=bold]
|
||||
|
||||
new [label="proxy_cache_subscr_new()\n/ proxy_cache_subscr_from_db()",shape=box]
|
||||
{
|
||||
rank=same;
|
||||
junction_resolve_home_hlr [shape=diamond,label="Home HLR\n known?"]
|
||||
junction_confirm_home_hlr [shape=diamond,label="mslookup\n for home HLR\n very old?"]
|
||||
junction_update_location [shape=diamond,label="Did MM FSM emit another\n HOME_EV_CONFIRM_LU?"]
|
||||
junction_auth_info [shape=diamond,label="Auth tuple\ncache full of\nfresh tuples?"]
|
||||
}
|
||||
|
||||
new -> {junction_resolve_home_hlr, mm_fsm}
|
||||
|
||||
junction_resolve_home_hlr -> junction_confirm_home_hlr [label="known"]
|
||||
junction_resolve_home_hlr -> action_mslookup [label="wat"]
|
||||
action_mslookup [shape=larrow,label="start mslookup"]
|
||||
action_mslookup -> WAIT_HOME_HLR_RESOLVED
|
||||
WAIT_HOME_HLR_RESOLVED -> rx_mslookup_res [arrowhead=none]
|
||||
rx_mslookup_res [shape=rarrow,label="home HLR\n resolved"]
|
||||
rx_mslookup_res -> to_top1
|
||||
WAIT_HOME_HLR_RESOLVED -> CLEAR [style=dashed,label=Timeout]
|
||||
|
||||
junction_update_location -> action_lu_req [label="need data\n/\nneed to confirm LU"]
|
||||
action_lu_req [shape=larrow,label="Tx GSUP Update Location Req\nto home HLR"]
|
||||
action_lu_req -> WAIT_UPDATE_LOCATION_RESULT
|
||||
WAIT_UPDATE_LOCATION_RESULT->rx_isd [arrowhead=none]
|
||||
rx_isd [shape=rarrow,label="Rx GSUP\n Insert Subscriber\n Data Request\nfrom home HLR"]
|
||||
rx_isd -> emit_rx_subscr_data
|
||||
emit_rx_subscr_data [shape=lpromoter,label="emit\n MM_EV_RX_SUBSCR_DATA"]
|
||||
emit_rx_subscr_data->action_tx_isd_res
|
||||
action_tx_isd_res -> WAIT_UPDATE_LOCATION_RESULT
|
||||
action_tx_isd_res [shape=larrow,label="Tx GSUP Insert Subscriber Data Result\nto home HLR"]
|
||||
WAIT_UPDATE_LOCATION_RESULT -> rx_lu_res [arrowhead=none]
|
||||
rx_lu_res [shape=rarrow,label="Rx GSUP Update\n Location Result\nfrom home HLR"]
|
||||
rx_lu_res -> to_top2
|
||||
junction_update_location -> junction_auth_info [label="data known,\nLU confirmed"]
|
||||
WAIT_UPDATE_LOCATION_RESULT->junction_lu_failed [label="Timeout "]
|
||||
junction_lu_failed [shape=diamond,label="has home HLR\never confirmed\na LU before?"];
|
||||
junction_lu_failed -> to_clear2 [label="never\nconfirmed",style=dashed]
|
||||
junction_lu_failed -> note_lu_failed [label="has\nconfirmed\nbefore"]
|
||||
note_lu_failed [shape=note,label="Home HLR is\ntemporarily unreachable,\n just carry on."]
|
||||
note_lu_failed -> to_top3
|
||||
|
||||
junction_confirm_home_hlr -> action_mslookup_confirm [label="very old"]
|
||||
junction_confirm_home_hlr -> junction_update_location [label="fair enough"]
|
||||
action_mslookup_confirm [shape=larrow,label="start mslookup"]
|
||||
note_mslookup_confirm [shape=note,label="Result evaluated in IDLE state.\nIf no mslookup result comes\n back, the home HLR is\ntemporarily unreachable:\ncontinue anyway.\nThis is sanity checking for\na *modified* home HLR"]
|
||||
action_mslookup_confirm -> note_mslookup_confirm [arrowhead=none,style=dotted]
|
||||
action_mslookup_confirm -> junction_update_location
|
||||
|
||||
junction_auth_info -> action_sai_req [label="need more tuples"]
|
||||
junction_auth_info -> set_idle_timeout [label="cache is fresh"]
|
||||
action_sai_req [shape=larrow,label="Tx GSUP\n Send Auth Info Request\n to home HLR"]
|
||||
action_sai_req -> WAIT_SEND_AUTH_INFO_RESULT
|
||||
WAIT_SEND_AUTH_INFO_RESULT->rx_sai_res[arrowhead=none]
|
||||
rx_sai_res[shape=rarrow,label="Rx GSUP\n Send Auth Info\n Result\nfrom home HLR"]
|
||||
rx_sai_res -> emit_rx_tuples
|
||||
emit_rx_tuples [shape=lpromoter,label="emit\n MM_EV_RX_AUTH_TUPLES"]
|
||||
emit_rx_tuples -> to_top4
|
||||
WAIT_SEND_AUTH_INFO_RESULT->set_idle_timeout[label="Timeout",style=dashed]
|
||||
set_idle_timeout [shape=diamond,label="Select IDLE\n timeout:\nmin(\nretry tuples,\n confirm mslookup)"]
|
||||
set_idle_timeout -> IDLE
|
||||
|
||||
IDLE -> rx_mslookup_confirm_res [arrowhead=none]
|
||||
rx_mslookup_confirm_res [shape=rarrow,label="Rx mslookup result"]
|
||||
rx_mslookup_confirm_res -> junction_compare_home_hlr
|
||||
junction_compare_home_hlr [shape=diamond,label="mslookup result\n matches home HLR\n on record?"]
|
||||
junction_compare_home_hlr -> set_idle_timeout [label="matches"]
|
||||
junction_compare_home_hlr -> to_clear1 [label="mismatch",style=dashed]
|
||||
|
||||
IDLE -> to_top5 [label="Timeout"]
|
||||
IDLE -> {rx_ev_check_tuples,rx_ev_confirm_lu} [arrowhead=none]
|
||||
rx_ev_check_tuples [shape=rpromoter,label="receive\n HOME_EV_CHECK_TUPLES"]
|
||||
rx_ev_confirm_lu [shape=rpromoter,label="receive\n HOME_EV_CONFIRM_LU"]
|
||||
{rx_ev_check_tuples,rx_ev_confirm_lu} -> to_top5
|
||||
|
||||
at_clear -> CLEAR
|
||||
CLEAR -> emit_subscr_invalid -> TERM
|
||||
emit_subscr_invalid [shape=lpromoter,label="emit\n MM_EV_SUBSCR_INVALID"]
|
||||
TERM[shape=octagon][style=bold]
|
||||
}
|
@@ -1,7 +1,9 @@
|
||||
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 \
|
||||
|
@@ -38,6 +38,8 @@ struct osmo_gsup_client;
|
||||
/* Expects message in msg->l2h */
|
||||
typedef int (*osmo_gsup_client_read_cb_t)(struct osmo_gsup_client *gsupc, struct msgb *msg);
|
||||
|
||||
typedef bool (*osmo_gsup_client_up_down_cb_t)(struct osmo_gsup_client *gsupc, bool up);
|
||||
|
||||
struct osmo_gsup_client {
|
||||
const char *unit_name; /* same as ipa_dev->unit_name, for backwards compat */
|
||||
|
||||
@@ -53,8 +55,31 @@ struct osmo_gsup_client {
|
||||
int got_ipa_pong;
|
||||
|
||||
struct ipaccess_unit *ipa_dev; /* identification information sent to IPA server */
|
||||
|
||||
osmo_gsup_client_up_down_cb_t up_down_cb;
|
||||
};
|
||||
|
||||
struct osmo_gsup_client_config {
|
||||
/*! IP access unit which contains client identification information; must be allocated in talloc_ctx as well to
|
||||
* ensure it lives throughout the lifetime of the connection. */
|
||||
struct ipaccess_unit *ipa_dev;
|
||||
/*! GSUP server IP address to connect to. */
|
||||
const char *ip_addr;
|
||||
/*! GSUP server TCP port to connect to. */
|
||||
unsigned int tcp_port;
|
||||
/*! OPA client configuration, or NULL. */
|
||||
struct osmo_oap_client_config *oapc_config;
|
||||
/*! callback for reading from the GSUP connection. */
|
||||
osmo_gsup_client_read_cb_t read_cb;
|
||||
/*! Invoked when the GSUP link is ready for communication, and when the link drops. */
|
||||
osmo_gsup_client_up_down_cb_t up_down_cb;
|
||||
/*! User data stored in the returned gsupc->data, as context for the callbacks. */
|
||||
void *data;
|
||||
/*! Marker for future extension, always pass this as false. */
|
||||
bool more;
|
||||
};
|
||||
struct osmo_gsup_client *osmo_gsup_client_create3(void *talloc_ctx, struct osmo_gsup_client_config *config);
|
||||
|
||||
struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx,
|
||||
struct ipaccess_unit *ipa_dev,
|
||||
const char *ip_addr,
|
||||
|
66
include/osmocom/gsupclient/gsup_peer_id.h
Normal file
66
include/osmocom/gsupclient/gsup_peer_id.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
/*! IPA Name: Arbitrary length blob, not necessarily zero-terminated.
|
||||
* In osmo-hlr, struct hlr_subscriber is mostly used as static reference and cannot serve as talloc context, which is
|
||||
* why this is also implemented as a fixed-maximum-size buffer instead of a talloc'd arbitrary sized buffer.
|
||||
* NOTE: The length of val may be extended in the future if it becomes necessary.
|
||||
* At the time of writing, this holds IPA unit name strings of very limited length.
|
||||
*/
|
||||
struct osmo_ipa_name {
|
||||
size_t len;
|
||||
uint8_t val[128];
|
||||
};
|
||||
|
||||
bool osmo_ipa_name_is_empty(struct osmo_ipa_name *ipa_name);
|
||||
int osmo_ipa_name_set(struct osmo_ipa_name *ipa_name, const uint8_t *val, size_t len);
|
||||
int osmo_ipa_name_set_str(struct osmo_ipa_name *ipa_name, const char *str_fmt, ...);
|
||||
int osmo_ipa_name_cmp(const struct osmo_ipa_name *a, const struct osmo_ipa_name *b);
|
||||
const char *osmo_ipa_name_to_str_c(void *ctx, const struct osmo_ipa_name *ipa_name);
|
||||
const char *osmo_ipa_name_to_str(const struct osmo_ipa_name *ipa_name);
|
||||
|
||||
enum osmo_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);
|
||||
const char *osmo_gsup_peer_id_to_str_c(void *ctx, const struct osmo_gsup_peer_id *gsup_peer_id);
|
115
include/osmocom/gsupclient/gsup_req.h
Normal file
115
include/osmocom/gsupclient/gsup_req.h
Normal 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);
|
@@ -2,6 +2,7 @@ noinst_HEADERS = \
|
||||
auc.h \
|
||||
ctrl.h \
|
||||
db.h \
|
||||
dgsm.h \
|
||||
gsup_router.h \
|
||||
gsup_server.h \
|
||||
hlr.h \
|
||||
@@ -9,6 +10,13 @@ noinst_HEADERS = \
|
||||
hlr_vty.h \
|
||||
hlr_vty_subscr.h \
|
||||
logging.h \
|
||||
luop.h \
|
||||
lu_fsm.h \
|
||||
mslookup_server.h \
|
||||
mslookup_server_mdns.h \
|
||||
proxy.h \
|
||||
proxy_mm.h \
|
||||
proxy_db.h \
|
||||
rand.h \
|
||||
remote_hlr.h \
|
||||
timestamp.h \
|
||||
$(NULL)
|
||||
|
@@ -3,6 +3,9 @@
|
||||
#include <stdbool.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <osmocom/gsupclient/gsup_peer_id.h>
|
||||
#include <osmocom/gsm/gsup.h>
|
||||
|
||||
struct hlr;
|
||||
|
||||
enum stmt_idx {
|
||||
@@ -31,6 +34,9 @@ enum stmt_idx {
|
||||
DB_STMT_SET_LAST_LU_SEEN_PS,
|
||||
DB_STMT_EXISTS_BY_IMSI,
|
||||
DB_STMT_EXISTS_BY_MSISDN,
|
||||
DB_STMT_IND_ADD,
|
||||
DB_STMT_IND_SELECT,
|
||||
DB_STMT_IND_DEL,
|
||||
_NUM_DB_STMT
|
||||
};
|
||||
|
||||
@@ -49,6 +55,7 @@ void db_remove_reset(sqlite3_stmt *stmt);
|
||||
bool db_bind_text(sqlite3_stmt *stmt, const char *param_name, const char *text);
|
||||
bool db_bind_int(sqlite3_stmt *stmt, const char *param_name, int nr);
|
||||
bool db_bind_int64(sqlite3_stmt *stmt, const char *param_name, int64_t nr);
|
||||
bool db_bind_null(sqlite3_stmt *stmt, const char *param_name);
|
||||
void db_close(struct db_context *dbc);
|
||||
struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite3_logging, bool allow_upgrades);
|
||||
|
||||
@@ -97,6 +104,9 @@ struct hlr_subscriber {
|
||||
bool ms_purged_ps;
|
||||
time_t last_lu_seen;
|
||||
time_t last_lu_seen_ps;
|
||||
/* talloc'd IPA unit name */
|
||||
struct osmo_ipa_name vlr_via_proxy;
|
||||
struct osmo_ipa_name sgsn_via_proxy;
|
||||
};
|
||||
|
||||
/* A format string for use with strptime(3). This format string is
|
||||
@@ -151,12 +161,14 @@ 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 char *vlr_or_sgsn_number, bool is_ps);
|
||||
const struct osmo_ipa_name *vlr_name, bool is_ps,
|
||||
const struct osmo_ipa_name *via_proxy);
|
||||
|
||||
int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
|
||||
bool purge_val, bool is_ps);
|
||||
|
||||
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps);
|
||||
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);
|
||||
|
||||
/*! Call sqlite3_column_text() and copy result to a char[].
|
||||
* \param[out] buf A char[] used as sizeof() arg(!) and osmo_strlcpy() target.
|
||||
@@ -168,3 +180,14 @@ int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val,
|
||||
const char *_txt = (const char *) sqlite3_column_text(stmt, idx); \
|
||||
osmo_strlcpy(buf, _txt, sizeof(buf)); \
|
||||
} while (0)
|
||||
|
||||
/*! Call sqlite3_column_text() and copy result to a struct osmo_ipa_name.
|
||||
* \param[out] ipa_name A struct osmo_ipa_name* to write to.
|
||||
* \param[in] stmt An sqlite3_stmt*.
|
||||
* \param[in] idx Index in stmt's returned columns.
|
||||
*/
|
||||
#define copy_sqlite3_text_to_ipa_name(ipa_name, stmt, idx) \
|
||||
do { \
|
||||
const char *_txt = (const char *) sqlite3_column_text(stmt, idx); \
|
||||
osmo_ipa_name_set_str(ipa_name, _txt); \
|
||||
} while (0)
|
||||
|
46
include/osmocom/hlr/dgsm.h
Normal file
46
include/osmocom/hlr/dgsm.h
Normal 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);
|
@@ -3,6 +3,8 @@
|
||||
#include <stdint.h>
|
||||
#include <osmocom/hlr/gsup_server.h>
|
||||
|
||||
struct osmo_ipa_name;
|
||||
|
||||
struct gsup_route {
|
||||
struct llist_head list;
|
||||
|
||||
@@ -12,10 +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_by_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name);
|
||||
|
||||
struct gsup_route *gsup_route_find_by_conn(const struct osmo_gsup_conn *conn);
|
||||
|
||||
/* add a new route for the given address to the given conn */
|
||||
int gsup_route_add_ipa_name(struct osmo_gsup_conn *conn, const struct osmo_ipa_name *ipa_name);
|
||||
int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addrlen);
|
||||
|
||||
/* delete all routes for the given connection */
|
||||
@@ -24,3 +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_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);
|
||||
|
@@ -5,6 +5,8 @@
|
||||
#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 */
|
||||
@@ -22,12 +24,12 @@ struct osmo_gsup_server {
|
||||
/* list of osmo_gsup_conn */
|
||||
struct llist_head clients;
|
||||
|
||||
/* lu_operations list */
|
||||
struct llist_head *luop;
|
||||
|
||||
struct ipa_server_link *link;
|
||||
osmo_gsup_read_cb_t read_cb;
|
||||
struct llist_head routes;
|
||||
|
||||
/* Proxy requests from this server's clients to remote GSUP servers. */
|
||||
struct proxy *proxy;
|
||||
};
|
||||
|
||||
|
||||
@@ -45,10 +47,15 @@ struct osmo_gsup_conn {
|
||||
/* Set when Location Update is received: */
|
||||
bool supports_cs; /* client supports OSMO_GSUP_CN_DOMAIN_CS */
|
||||
bool supports_ps; /* client supports OSMO_GSUP_CN_DOMAIN_PS */
|
||||
|
||||
/* The IPA unit name received on this link. Routes with more unit names serviced by this link may exist in
|
||||
* osmo_gsup_server->routes, but this is the name the immediate peer identified as in the IPA handshake. */
|
||||
struct osmo_ipa_name peer_name;
|
||||
};
|
||||
|
||||
struct msgb *osmo_gsup_msgb_alloc(const char *label);
|
||||
|
||||
struct osmo_gsup_req *osmo_gsup_conn_rx(struct osmo_gsup_conn *conn, struct msgb *msg);
|
||||
int osmo_gsup_conn_send(struct osmo_gsup_conn *conn, struct msgb *msg);
|
||||
int osmo_gsup_conn_ccm_get(const struct osmo_gsup_conn *clnt, uint8_t **addr,
|
||||
uint8_t tag);
|
||||
@@ -57,7 +64,6 @@ struct osmo_gsup_server *osmo_gsup_server_create(void *ctx,
|
||||
const char *ip_addr,
|
||||
uint16_t tcp_port,
|
||||
osmo_gsup_read_cb_t read_cb,
|
||||
struct llist_head *lu_op_lst,
|
||||
void *priv);
|
||||
|
||||
void osmo_gsup_server_destroy(struct osmo_gsup_server *gsups);
|
||||
@@ -68,3 +74,5 @@ int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup,
|
||||
uint8_t *msisdn_enc, size_t msisdn_enc_size,
|
||||
uint8_t *apn_buf, size_t apn_buf_size,
|
||||
enum osmo_gsup_cn_domain cn_domain);
|
||||
int osmo_gsup_forward_to_local_peer(struct osmo_gsup_server *server, const struct osmo_gsup_peer_id *to_peer,
|
||||
struct osmo_gsup_req *req, struct osmo_gsup_message *modified_gsup);
|
||||
|
@@ -24,10 +24,19 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/gsm/ipa.h>
|
||||
#include <osmocom/core/tdef.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
|
||||
#include <osmocom/hlr/dgsm.h>
|
||||
|
||||
#define HLR_DEFAULT_DB_FILE_PATH "hlr.db"
|
||||
|
||||
struct hlr_euse;
|
||||
struct osmo_gsup_conn;
|
||||
enum osmo_gsup_message_type;
|
||||
|
||||
extern struct osmo_tdef g_hlr_tdefs[];
|
||||
|
||||
struct hlr {
|
||||
/* GSUP server pointer */
|
||||
@@ -43,6 +52,7 @@ struct hlr {
|
||||
|
||||
/* Local bind addr */
|
||||
char *gsup_bind_addr;
|
||||
struct ipaccess_unit gsup_unit_name;
|
||||
|
||||
struct llist_head euse_list;
|
||||
struct hlr_euse *euse_default;
|
||||
@@ -60,6 +70,47 @@ struct hlr {
|
||||
/* Bitmask of DB_SUBSCR_FLAG_* */
|
||||
uint8_t subscr_create_on_demand_flags;
|
||||
unsigned int subscr_create_on_demand_rand_msisdn_len;
|
||||
|
||||
struct {
|
||||
bool allow_startup;
|
||||
struct {
|
||||
/* Whether the mslookup server should be active in general (all lookup methods) */
|
||||
bool enable;
|
||||
uint32_t local_attach_max_age;
|
||||
struct llist_head local_site_services;
|
||||
struct {
|
||||
/* Whether the mDNS method of the mslookup server should be active. */
|
||||
bool enable;
|
||||
/* The mDNS bind address and domain suffix as set by the VTY, not necessarily in use. */
|
||||
struct osmo_sockaddr_str bind_addr;
|
||||
char *domain_suffix;
|
||||
struct osmo_mslookup_server_mdns *running;
|
||||
} mdns;
|
||||
} server;
|
||||
|
||||
/* The mslookup client in osmo-hlr is used to find out which remote HLRs service a locally unknown IMSI.
|
||||
* (It may also be used to resolve recipients for SMS-over-GSUP in the future.) */
|
||||
struct {
|
||||
/* Whether to proxy/forward to remote HLRs */
|
||||
bool enable;
|
||||
|
||||
/* If this is set, all GSUP for unknown IMSIs is forwarded directly to this GSUP address,
|
||||
* unconditionally. */
|
||||
struct osmo_sockaddr_str gsup_gateway_proxy;
|
||||
|
||||
/* mslookup client request handling */
|
||||
unsigned int result_timeout_milliseconds;
|
||||
|
||||
struct osmo_mslookup_client *client;
|
||||
struct {
|
||||
/* Whether to use mDNS for IMSI MS Lookup */
|
||||
bool enable;
|
||||
struct osmo_sockaddr_str query_addr;
|
||||
char *domain_suffix;
|
||||
struct osmo_mslookup_client_method *running;
|
||||
} mdns;
|
||||
} client;
|
||||
} mslookup;
|
||||
};
|
||||
|
||||
extern struct hlr *g_hlr;
|
||||
@@ -67,3 +118,4 @@ extern struct hlr *g_hlr;
|
||||
struct hlr_subscriber;
|
||||
|
||||
void osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr);
|
||||
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps);
|
||||
|
@@ -46,8 +46,8 @@ struct hlr_ussd_route *ussd_route_prefix_alloc_ext(struct hlr *hlr, const char *
|
||||
struct hlr_euse *euse);
|
||||
void ussd_route_del(struct hlr_ussd_route *rt);
|
||||
|
||||
int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);
|
||||
int rx_proc_ss_error(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);
|
||||
void rx_proc_ss_req(struct osmo_gsup_req *req);
|
||||
void rx_proc_ss_error(struct osmo_gsup_req *req);
|
||||
|
||||
struct ss_session;
|
||||
struct ss_request;
|
||||
@@ -56,6 +56,5 @@ struct ss_request;
|
||||
struct hlr_iuse {
|
||||
const char *name;
|
||||
/* call-back to be called for any incoming USSD messages for this IUSE */
|
||||
int (*handle_ussd)(struct osmo_gsup_conn *conn, struct ss_session *ss,
|
||||
const struct osmo_gsup_message *gsup, const struct ss_request *req);
|
||||
int (*handle_ussd)(struct ss_session *ss, const struct osmo_gsup_message *gsup, const struct ss_request *req);
|
||||
};
|
||||
|
@@ -31,8 +31,13 @@ enum hlr_vty_node {
|
||||
HLR_NODE = _LAST_OSMOVTY_NODE + 1,
|
||||
GSUP_NODE,
|
||||
EUSE_NODE,
|
||||
MSLOOKUP_NODE,
|
||||
MSLOOKUP_SERVER_NODE,
|
||||
MSLOOKUP_SERVER_MSC_NODE,
|
||||
MSLOOKUP_CLIENT_NODE,
|
||||
};
|
||||
|
||||
int hlr_vty_is_config_node(struct vty *vty, int node);
|
||||
int hlr_vty_go_parent(struct vty *vty);
|
||||
void hlr_vty_init(void);
|
||||
void dgsm_vty_init(void);
|
||||
|
@@ -9,6 +9,8 @@ enum {
|
||||
DAUC,
|
||||
DSS,
|
||||
DMSLOOKUP,
|
||||
DLU,
|
||||
DDGSM,
|
||||
};
|
||||
|
||||
extern const struct log_info hlr_log_info;
|
||||
|
22
include/osmocom/hlr/lu_fsm.h
Normal file
22
include/osmocom/hlr/lu_fsm.h
Normal 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);
|
@@ -1,81 +0,0 @@
|
||||
/* OsmoHLR TX/RX lu operations */
|
||||
|
||||
/* (C) 2017 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/gsm/gsup.h>
|
||||
|
||||
#include <osmocom/hlr/db.h>
|
||||
#include <osmocom/hlr/gsup_server.h>
|
||||
|
||||
#define CANCEL_TIMEOUT_SECS 30
|
||||
#define ISD_TIMEOUT_SECS 30
|
||||
|
||||
enum lu_state {
|
||||
LU_S_NULL,
|
||||
LU_S_LU_RECEIVED,
|
||||
LU_S_CANCEL_SENT,
|
||||
LU_S_CANCEL_ACK_RECEIVED,
|
||||
LU_S_ISD_SENT,
|
||||
LU_S_ISD_ACK_RECEIVED,
|
||||
LU_S_COMPLETE,
|
||||
};
|
||||
|
||||
extern const struct value_string lu_state_names[];
|
||||
|
||||
struct lu_operation {
|
||||
/*! entry in global list of location update operations */
|
||||
struct llist_head list;
|
||||
/*! to which gsup_server do we belong */
|
||||
struct osmo_gsup_server *gsup_server;
|
||||
/*! state of the location update */
|
||||
enum lu_state state;
|
||||
/*! CS (false) or PS (true) Location Update? */
|
||||
bool is_ps;
|
||||
/*! currently running timer */
|
||||
struct osmo_timer_list timer;
|
||||
|
||||
/*! subscriber related to this operation */
|
||||
struct hlr_subscriber subscr;
|
||||
/*! peer VLR/SGSN starting the request */
|
||||
uint8_t *peer;
|
||||
};
|
||||
|
||||
|
||||
struct lu_operation *lu_op_alloc(struct osmo_gsup_server *srv);
|
||||
struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn);
|
||||
void lu_op_statechg(struct lu_operation *luop, enum lu_state new_state);
|
||||
bool lu_op_fill_subscr(struct lu_operation *luop, struct db_context *dbc,
|
||||
const char *imsi);
|
||||
struct lu_operation *lu_op_by_imsi(const char *imsi,
|
||||
const struct llist_head *lst);
|
||||
|
||||
void lu_op_tx_error(struct lu_operation *luop, enum gsm48_gmm_cause cause);
|
||||
void lu_op_tx_ack(struct lu_operation *luop);
|
||||
void lu_op_tx_cancel_old(struct lu_operation *luop);
|
||||
void lu_op_tx_insert_subscr_data(struct lu_operation *luop);
|
||||
void lu_op_tx_del_subscr_data(struct lu_operation *luop);
|
||||
|
||||
void lu_op_free(struct lu_operation *luop);
|
72
include/osmocom/hlr/mslookup_server.h
Normal file
72
include/osmocom/hlr/mslookup_server.h
Normal 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);
|
36
include/osmocom/hlr/mslookup_server_mdns.h
Normal file
36
include/osmocom/hlr/mslookup_server_mdns.h
Normal 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();
|
100
include/osmocom/hlr/proxy.h
Normal file
100
include/osmocom/hlr/proxy.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/* 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/core/sockaddr_str.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/gsm/protocol/gsm_23_003.h>
|
||||
#include <osmocom/gsm/gsup.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;
|
||||
struct osmo_fsm_inst *mm_fi;
|
||||
struct osmo_fsm_inst *to_home_fi;
|
||||
};
|
||||
|
||||
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);
|
37
include/osmocom/hlr/proxy_broken_link_cache.h
Normal file
37
include/osmocom/hlr/proxy_broken_link_cache.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/* Copyright 2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
*
|
||||
* Author: 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 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* If a subscriber from a remote site has successfully attached at this local site, and the link to the subscriber's
|
||||
* home HLR has succeeded, this will try to bridge the time of temporary link failure to that home HLR.
|
||||
* Tasks to take over from the unreachable home HLR:
|
||||
* - Resend known auth tuples on OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST.
|
||||
* - ...?
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/* Data stored per subscriber */
|
||||
struct proxy_broken_link_cache {
|
||||
struct osmo_auth_vector auth_vectors[OSMO_GSUP_MAX_NUM_AUTH_INFO];
|
||||
size_t num_auth_vectors;
|
||||
|
||||
timestamp_t last_update;
|
||||
};
|
54
include/osmocom/hlr/proxy_mm.h
Normal file
54
include/osmocom/hlr/proxy_mm.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/hlr/proxy.h>
|
||||
|
||||
enum proxy_mm_fsm_event {
|
||||
PROXY_MM_EV_SUBSCR_INVALID,
|
||||
PROXY_MM_EV_RX_GSUP_LU,
|
||||
PROXY_MM_EV_RX_GSUP_SAI,
|
||||
PROXY_MM_EV_RX_SUBSCR_DATA,
|
||||
PROXY_MM_EV_RX_GSUP_ISD_RESULT,
|
||||
PROXY_MM_EV_RX_AUTH_TUPLES,
|
||||
};
|
||||
|
||||
enum proxy_to_home_fsm_event {
|
||||
PROXY_TO_HOME_EV_HOME_HLR_RESOLVED,
|
||||
PROXY_TO_HOME_EV_RX_INSERT_SUBSCRIBER_DATA_REQ,
|
||||
PROXY_TO_HOME_EV_RX_UPDATE_LOCATION_RESULT,
|
||||
PROXY_TO_HOME_EV_RX_SEND_AUTH_INFO_RESULT,
|
||||
PROXY_TO_HOME_EV_CHECK_TUPLES,
|
||||
PROXY_TO_HOME_EV_CONFIRM_LU,
|
||||
};
|
||||
|
||||
extern struct llist_head proxy_mm_list;
|
||||
|
||||
struct proxy_mm_auth_cache {
|
||||
struct llist_head entry;
|
||||
uint64_t db_id;
|
||||
struct osmo_auth_vector auth_vectors[OSMO_GSUP_MAX_NUM_AUTH_INFO];
|
||||
size_t num_auth_vectors;
|
||||
unsigned int sent_to_vlr_count;
|
||||
};
|
||||
|
||||
struct proxy_mm {
|
||||
struct llist_head entry;
|
||||
struct osmo_gsup_peer_id vlr_name;
|
||||
char imsi[GSM23003_IMSI_MAX_DIGITS+1];
|
||||
bool is_ps;
|
||||
struct osmo_fsm_inst *mm_fi;
|
||||
struct osmo_fsm_inst *to_home_fi;
|
||||
struct llist_head auth_cache;
|
||||
};
|
||||
|
||||
struct proxy_mm *proxy_mm_alloc(const struct osmo_gsup_peer_id *vlr_name,
|
||||
bool is_ps,
|
||||
const char *imsi);
|
||||
|
||||
void proxy_mm_add_auth_vectors(struct proxy_mm *proxy_mm,
|
||||
const struct osmo_auth_vector *auth_vectors, size_t num_auth_vectors);
|
||||
struct proxy_mm_auth_cache *proxy_mm_get_auth_vectors(struct proxy_mm *proxy_mm);
|
||||
void proxy_mm_use_auth_vectors(struct proxy_mm *proxy_mm, struct proxy_mm_auth_cache *ac);
|
||||
void proxy_mm_discard_auth_vectors(struct proxy_mm *proxy_mm, struct proxy_mm_auth_cache *ac);
|
||||
|
||||
bool proxy_mm_subscriber_data_known(const struct proxy_mm *proxy_mm);
|
59
include/osmocom/hlr/remote_hlr.h
Normal file
59
include/osmocom/hlr/remote_hlr.h
Normal 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);
|
28
include/osmocom/hlr/timestamp.h
Normal file
28
include/osmocom/hlr/timestamp.h
Normal 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);
|
38
sql/hlr.sql
38
sql/hlr.sql
@@ -43,7 +43,12 @@ 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_lu_seen_ps TIMESTAMP default NULL,
|
||||
|
||||
-- When a LU was received via a proxy, that proxy's hlr_number is stored here,
|
||||
-- while vlr_number reflects the MSC on the far side of that proxy.
|
||||
vlr_via_proxy VARCHAR,
|
||||
sgsn_via_proxy VARCHAR
|
||||
);
|
||||
|
||||
CREATE TABLE subscriber_apn (
|
||||
@@ -74,8 +79,37 @@ 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)
|
||||
);
|
||||
|
||||
CREATE TABLE proxy_auth_cache {
|
||||
id INTEGER PRIMARY KEY,
|
||||
vlr TEXT NOT NULL,
|
||||
imsi TEXT NOT NULL,
|
||||
sent_count INTEGER
|
||||
};
|
||||
|
||||
CREATE TABLE proxy_auth_cache_vector {
|
||||
-- belongs to this proxy_auth_cache entry
|
||||
proxy_auth_cache_id INTEGER,
|
||||
rand VARCHAR(32),
|
||||
autn VARCHAR(32),
|
||||
ck VARCHAR(32),
|
||||
ik VARCHAR(32),
|
||||
res VARCHAR(32),
|
||||
kc[8] VARCHAR(16),
|
||||
sres[4] VARCHAR(8),
|
||||
-- enum osmo_sub_auth_type bitmask
|
||||
auth_types INTEGER
|
||||
};
|
||||
|
||||
CREATE UNIQUE INDEX idx_subscr_imsi ON subscriber (imsi);
|
||||
|
||||
-- Set HLR database schema version number
|
||||
-- Note: This constant is currently duplicated in src/db.c and must be kept in sync!
|
||||
PRAGMA user_version = 4;
|
||||
PRAGMA user_version = 6;
|
||||
|
@@ -9,6 +9,7 @@ AM_CFLAGS = \
|
||||
$(LIBOSMOGSM_CFLAGS) \
|
||||
$(LIBOSMOVTY_CFLAGS) \
|
||||
$(LIBOSMOCTRL_CFLAGS) \
|
||||
$(LIBOSMOMSLOOKUP_CFLAGS) \
|
||||
$(LIBOSMOABIS_CFLAGS) \
|
||||
$(SQLITE3_CFLAGS) \
|
||||
$(NULL)
|
||||
@@ -41,7 +42,6 @@ osmo_hlr_SOURCES = \
|
||||
auc.c \
|
||||
ctrl.c \
|
||||
db.c \
|
||||
luop.c \
|
||||
db_auc.c \
|
||||
db_hlr.c \
|
||||
gsup_router.c \
|
||||
@@ -53,13 +53,27 @@ osmo_hlr_SOURCES = \
|
||||
hlr_vty_subscr.c \
|
||||
gsup_send.c \
|
||||
hlr_ussd.c \
|
||||
proxy.c \
|
||||
proxy_mm.c \
|
||||
proxy_to_home.c \
|
||||
proxy_db.c \
|
||||
dgsm.c \
|
||||
remote_hlr.c \
|
||||
lu_fsm.c \
|
||||
timestamp.c \
|
||||
mslookup_server.c \
|
||||
mslookup_server_mdns.c \
|
||||
dgsm_vty.c \
|
||||
$(NULL)
|
||||
|
||||
osmo_hlr_LDADD = \
|
||||
$(top_builddir)/src/gsupclient/libosmo-gsup-client.la \
|
||||
$(top_builddir)/src/mslookup/libosmo-mslookup.la \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
$(LIBOSMOVTY_LIBS) \
|
||||
$(LIBOSMOCTRL_LIBS) \
|
||||
$(LIBOSMOMSLOOKUP_LIBS) \
|
||||
$(LIBOSMOABIS_LIBS) \
|
||||
$(SQLITE3_LIBS) \
|
||||
$(NULL)
|
||||
@@ -71,6 +85,7 @@ osmo_hlr_db_tool_SOURCES = \
|
||||
logging.c \
|
||||
rand_urandom.c \
|
||||
dbd_decode_binary.c \
|
||||
$(srcdir)/gsupclient/gsup_peer_id.c \
|
||||
$(NULL)
|
||||
|
||||
osmo_hlr_db_tool_LDADD = \
|
||||
|
74
src/db.c
74
src/db.c
@@ -28,7 +28,7 @@
|
||||
#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 6
|
||||
|
||||
#define SEL_COLUMNS \
|
||||
"id," \
|
||||
@@ -46,15 +46,17 @@
|
||||
"ms_purged_cs," \
|
||||
"ms_purged_ps," \
|
||||
"last_lu_seen," \
|
||||
"last_lu_seen_ps" \
|
||||
"last_lu_seen_ps," \
|
||||
"vlr_via_proxy," \
|
||||
"sgsn_via_proxy"
|
||||
|
||||
static const char *stmt_sql[] = {
|
||||
[DB_STMT_SEL_BY_IMSI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imsi = ?",
|
||||
[DB_STMT_SEL_BY_MSISDN] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE msisdn = ?",
|
||||
[DB_STMT_SEL_BY_ID] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE id = ?",
|
||||
[DB_STMT_SEL_BY_IMEI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imei = ?",
|
||||
[DB_STMT_UPD_VLR_BY_ID] = "UPDATE subscriber SET vlr_number = $number WHERE id = $subscriber_id",
|
||||
[DB_STMT_UPD_SGSN_BY_ID] = "UPDATE subscriber SET sgsn_number = $number WHERE id = $subscriber_id",
|
||||
[DB_STMT_UPD_VLR_BY_ID] = "UPDATE subscriber SET vlr_number = $number, vlr_via_proxy = $proxy WHERE id = $subscriber_id",
|
||||
[DB_STMT_UPD_SGSN_BY_ID] = "UPDATE subscriber SET sgsn_number = $number, sgsn_via_proxy = $proxy WHERE id = $subscriber_id",
|
||||
[DB_STMT_UPD_IMEI_BY_IMSI] = "UPDATE subscriber SET imei = $imei WHERE imsi = $imsi",
|
||||
[DB_STMT_AUC_BY_IMSI] =
|
||||
"SELECT id, algo_id_2g, ki, algo_id_3g, k, op, opc, sqn, ind_bitlen"
|
||||
@@ -83,6 +85,9 @@ static const char *stmt_sql[] = {
|
||||
[DB_STMT_SET_LAST_LU_SEEN_PS] = "UPDATE subscriber SET last_lu_seen_ps = datetime($val, 'unixepoch') WHERE id = $subscriber_id",
|
||||
[DB_STMT_EXISTS_BY_IMSI] = "SELECT 1 FROM subscriber WHERE imsi = $imsi",
|
||||
[DB_STMT_EXISTS_BY_MSISDN] = "SELECT 1 FROM subscriber WHERE msisdn = $msisdn",
|
||||
[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",
|
||||
};
|
||||
|
||||
static void sql3_error_log_cb(void *arg, int err_code, const char *msg)
|
||||
@@ -183,6 +188,25 @@ bool db_bind_int64(sqlite3_stmt *stmt, const char *param_name, int64_t nr)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool db_bind_null(sqlite3_stmt *stmt, const char *param_name)
|
||||
{
|
||||
int rc;
|
||||
int idx = param_name ? sqlite3_bind_parameter_index(stmt, param_name) : 1;
|
||||
if (idx < 1) {
|
||||
LOGP(DDB, LOGL_ERROR, "Error composing SQL, cannot bind parameter '%s'\n",
|
||||
param_name);
|
||||
return false;
|
||||
}
|
||||
rc = sqlite3_bind_null(stmt, idx);
|
||||
if (rc != SQLITE_OK) {
|
||||
LOGP(DDB, LOGL_ERROR, "Error binding NULL to SQL parameter %s: %d\n",
|
||||
param_name ? param_name : "#1", rc);
|
||||
db_remove_reset(stmt);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void db_close(struct db_context *dbc)
|
||||
{
|
||||
unsigned int i;
|
||||
@@ -441,12 +465,54 @@ 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;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
static int db_get_user_version(struct db_context *dbc)
|
||||
|
17
src/db_auc.c
17
src/db_auc.c
@@ -200,15 +200,16 @@ 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 */
|
||||
|
160
src/db_hlr.c
160
src/db_hlr.c
@@ -28,6 +28,7 @@
|
||||
#include <time.h>
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/crypt/auth.h>
|
||||
#include <osmocom/gsm/gsm23003.h>
|
||||
|
||||
@@ -36,8 +37,7 @@
|
||||
#include <osmocom/hlr/logging.h>
|
||||
#include <osmocom/hlr/hlr.h>
|
||||
#include <osmocom/hlr/db.h>
|
||||
#include <osmocom/hlr/gsup_server.h>
|
||||
#include <osmocom/hlr/luop.h>
|
||||
#include <osmocom/gsupclient/gsup_peer_id.h>
|
||||
|
||||
#define LOGHLR(imsi, level, fmt, args ...) LOGP(DAUC, level, "IMSI='%s': " fmt, imsi, ## args)
|
||||
|
||||
@@ -505,6 +505,8 @@ static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscri
|
||||
subscr->imsi, "CS");
|
||||
parse_last_lu_seen(&subscr->last_lu_seen_ps, (const char *)sqlite3_column_text(stmt, 15),
|
||||
subscr->imsi, "PS");
|
||||
copy_sqlite3_text_to_ipa_name(&subscr->vlr_via_proxy, stmt, 16);
|
||||
copy_sqlite3_text_to_ipa_name(&subscr->sgsn_via_proxy, stmt, 17);
|
||||
|
||||
out:
|
||||
db_remove_reset(stmt);
|
||||
@@ -566,7 +568,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;
|
||||
@@ -617,7 +619,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;
|
||||
@@ -641,7 +643,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;
|
||||
@@ -664,7 +666,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;
|
||||
}
|
||||
@@ -734,7 +736,8 @@ out:
|
||||
* -EIO on database errors.
|
||||
*/
|
||||
int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
|
||||
const char *vlr_or_sgsn_number, bool is_ps)
|
||||
const struct osmo_ipa_name *vlr_name, bool is_ps,
|
||||
const struct osmo_ipa_name *via_proxy)
|
||||
{
|
||||
sqlite3_stmt *stmt;
|
||||
int rc, ret = 0;
|
||||
@@ -746,9 +749,17 @@ int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
|
||||
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
|
||||
return -EIO;
|
||||
|
||||
if (!db_bind_text(stmt, "$number", vlr_or_sgsn_number))
|
||||
if (!db_bind_text(stmt, "$number", (char*)vlr_name->val))
|
||||
return -EIO;
|
||||
|
||||
if (via_proxy && via_proxy->len) {
|
||||
if (!db_bind_text(stmt, "$proxy", (char*)via_proxy->val))
|
||||
return -EIO;
|
||||
} else {
|
||||
if (!db_bind_null(stmt, "$proxy"))
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* execute the statement */
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
@@ -874,50 +885,105 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients.
|
||||
* \param[in,out] hlr Global hlr context.
|
||||
* \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call.
|
||||
* \param[in] nam_val True to enable CS/PS, false to disable.
|
||||
* \param[in] is_ps True to enable/disable PS, false for CS.
|
||||
* \returns 0 on success, ENOEXEC if there is no need to change, a negative
|
||||
* value on error.
|
||||
*/
|
||||
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps)
|
||||
static int _db_ind_run(struct db_context *dbc, sqlite3_stmt *stmt, const char *vlr, bool reset)
|
||||
{
|
||||
int rc;
|
||||
struct lu_operation *luop;
|
||||
struct osmo_gsup_conn *co;
|
||||
bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs;
|
||||
|
||||
if (is_val == nam_val) {
|
||||
LOGHLR(subscr->imsi, LOGL_DEBUG, "Already has the requested value when asked to %s %s\n",
|
||||
nam_val ? "enable" : "disable", is_ps ? "PS" : "CS");
|
||||
return ENOEXEC;
|
||||
}
|
||||
if (!db_bind_text(stmt, "$vlr", vlr))
|
||||
return -EIO;
|
||||
|
||||
rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps);
|
||||
if (rc)
|
||||
return rc > 0? -rc : rc;
|
||||
/* execute the statement */
|
||||
rc = sqlite3_step(stmt);
|
||||
if (reset)
|
||||
db_remove_reset(stmt);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* If we're disabling, send a notice out to the GSUP client that is
|
||||
* responsible. Otherwise no need. */
|
||||
if (nam_val)
|
||||
return 0;
|
||||
|
||||
/* FIXME: only send to single SGSN where latest update for IMSI came from */
|
||||
llist_for_each_entry(co, &hlr->gs->clients, list) {
|
||||
luop = lu_op_alloc_conn(co);
|
||||
if (!luop) {
|
||||
LOGHLR(subscr->imsi, LOGL_ERROR,
|
||||
"Cannot notify GSUP client, cannot allocate lu_operation,"
|
||||
" for %s:%u\n",
|
||||
co && co->conn && co->conn->server? co->conn->server->addr : "unset",
|
||||
co && co->conn && co->conn->server? co->conn->server->port : 0);
|
||||
continue;
|
||||
}
|
||||
luop->subscr = *subscr;
|
||||
lu_op_tx_del_subscr_data(luop);
|
||||
lu_op_free(luop);
|
||||
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);
|
||||
}
|
||||
|
247
src/dgsm.c
Normal file
247
src/dgsm.c
Normal file
@@ -0,0 +1,247 @@
|
||||
/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/mslookup/mslookup_client.h>
|
||||
#include <osmocom/mslookup/mslookup_client_mdns.h>
|
||||
#include <osmocom/gsupclient/gsup_client.h>
|
||||
#include <osmocom/gsupclient/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;
|
||||
|
||||
static void resolve_hlr_result_cb(struct osmo_mslookup_client *client,
|
||||
uint32_t request_handle,
|
||||
const struct osmo_mslookup_query *query,
|
||||
const struct osmo_mslookup_result *result)
|
||||
{
|
||||
struct proxy *proxy = g_hlr->gs->proxy;
|
||||
struct proxy_subscr proxy_subscr;
|
||||
const struct osmo_sockaddr_str *remote_hlr_addr;
|
||||
|
||||
/* A remote HLR is answering back, indicating that it is the home HLR for a given IMSI.
|
||||
* There should be a mostly empty proxy entry for that IMSI.
|
||||
* Add the remote address data in the proxy. */
|
||||
if (query->id.type != OSMO_MSLOOKUP_ID_IMSI) {
|
||||
LOGP(DDGSM, LOGL_ERROR, "Expected IMSI ID type in mslookup query+result: %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
|
||||
return;
|
||||
}
|
||||
|
||||
if (result->rc != OSMO_MSLOOKUP_RC_RESULT) {
|
||||
LOG_DGSM(query->id.imsi, LOGL_ERROR, "Failed to resolve remote HLR: %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
|
||||
proxy_subscr_del(proxy, query->id.imsi);
|
||||
return;
|
||||
}
|
||||
|
||||
if (osmo_sockaddr_str_is_nonzero(&result->host_v4))
|
||||
remote_hlr_addr = &result->host_v4;
|
||||
else if (osmo_sockaddr_str_is_nonzero(&result->host_v6))
|
||||
remote_hlr_addr = &result->host_v6;
|
||||
else {
|
||||
LOG_DGSM(query->id.imsi, LOGL_ERROR, "Invalid address for remote HLR: %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
|
||||
proxy_subscr_del(proxy, query->id.imsi);
|
||||
return;
|
||||
}
|
||||
|
||||
if (proxy_subscr_get_by_imsi(&proxy_subscr, proxy, query->id.imsi)) {
|
||||
LOG_DGSM(query->id.imsi, LOGL_ERROR, "No proxy entry for mslookup result: %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
|
||||
return;
|
||||
}
|
||||
|
||||
proxy_subscr_remote_hlr_resolved(proxy, &proxy_subscr, remote_hlr_addr);
|
||||
}
|
||||
|
||||
/* Return true when the message has been handled by D-GSM. */
|
||||
bool dgsm_check_forward_gsup_msg(struct osmo_gsup_req *req)
|
||||
{
|
||||
struct proxy_subscr proxy_subscr;
|
||||
struct proxy *proxy = g_hlr->gs->proxy;
|
||||
struct osmo_mslookup_query query;
|
||||
struct osmo_mslookup_query_handling handling;
|
||||
uint32_t request_handle;
|
||||
|
||||
/* If the IMSI is known in the local HLR, then we won't proxy. */
|
||||
if (db_subscr_exists_by_imsi(g_hlr->dbc, req->gsup.imsi) == 0)
|
||||
return false;
|
||||
|
||||
/* Are we already forwarding this IMSI to a remote HLR? */
|
||||
if (proxy_subscr_get_by_imsi(&proxy_subscr, proxy, req->gsup.imsi) == 0) {
|
||||
proxy_subscr_forward_to_remote_hlr(proxy, &proxy_subscr, req);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* The IMSI is not known locally, so we want to proxy to a remote HLR, but no proxy entry exists yet. We need to
|
||||
* look up the subscriber in remote HLRs via D-GSM mslookup, forward GSUP and reply once a result is back from
|
||||
* there. Defer message and kick off MS lookup. */
|
||||
|
||||
/* Add a proxy entry without a remote address to indicate that we are busy querying for a remote HLR. */
|
||||
proxy_subscr = (struct proxy_subscr){};
|
||||
OSMO_STRLCPY_ARRAY(proxy_subscr.imsi, req->gsup.imsi);
|
||||
if (proxy_subscr_create_or_update(proxy, &proxy_subscr)) {
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Failed to create proxy entry\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Is a fixed gateway proxy configured? */
|
||||
if (osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy)) {
|
||||
proxy_subscr_remote_hlr_resolved(proxy, &proxy_subscr, &g_hlr->mslookup.client.gsup_gateway_proxy);
|
||||
|
||||
/* Proxy database modified, update info */
|
||||
if (proxy_subscr_get_by_imsi(&proxy_subscr, proxy, req->gsup.imsi)) {
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Internal proxy error\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
proxy_subscr_forward_to_remote_hlr(proxy, &proxy_subscr, req);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Kick off an mslookup for the remote HLR? This check could be up first on the top, but do it only now so that
|
||||
* if the mslookup client disconnected, we still continue to service open proxy entries. */
|
||||
if (!osmo_mslookup_client_active(g_hlr->mslookup.client.client)) {
|
||||
LOG_GSUP_REQ(req, LOGL_DEBUG, "mslookup client not running, cannot query remote home HLR\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* First spool message, then kick off mslookup. If the proxy denies this message type, then don't do anything. */
|
||||
if (proxy_subscr_forward_to_remote_hlr(proxy, &proxy_subscr, req)) {
|
||||
/* If the proxy denied forwarding, an error response was already generated. */
|
||||
return true;
|
||||
}
|
||||
|
||||
query = (struct osmo_mslookup_query){
|
||||
.id = {
|
||||
.type = OSMO_MSLOOKUP_ID_IMSI,
|
||||
},
|
||||
};
|
||||
OSMO_STRLCPY_ARRAY(query.id.imsi, req->gsup.imsi);
|
||||
OSMO_STRLCPY_ARRAY(query.service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP);
|
||||
handling = (struct osmo_mslookup_query_handling){
|
||||
.min_wait_milliseconds = g_hlr->mslookup.client.result_timeout_milliseconds,
|
||||
.result_cb = resolve_hlr_result_cb,
|
||||
};
|
||||
request_handle = osmo_mslookup_client_request(g_hlr->mslookup.client.client, &query, &handling);
|
||||
if (!request_handle) {
|
||||
LOG_DGSM(req->gsup.imsi, LOGL_ERROR, "Error dispatching mslookup query for home HLR: %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, &query, NULL));
|
||||
proxy_subscr_del(proxy, req->gsup.imsi);
|
||||
/* mslookup seems to not be working. Try handling it locally. */
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void dgsm_init(void *ctx)
|
||||
{
|
||||
dgsm_ctx = talloc_named_const(ctx, 0, "dgsm");
|
||||
INIT_LLIST_HEAD(&g_hlr->mslookup.server.local_site_services);
|
||||
|
||||
g_hlr->mslookup.server.local_attach_max_age = 60 * 60;
|
||||
|
||||
g_hlr->mslookup.client.result_timeout_milliseconds = 2000;
|
||||
|
||||
g_hlr->gsup_unit_name.unit_name = "HLR";
|
||||
g_hlr->gsup_unit_name.serno = "unnamed-HLR";
|
||||
g_hlr->gsup_unit_name.swversion = PACKAGE_NAME "-" PACKAGE_VERSION;
|
||||
|
||||
osmo_sockaddr_str_from_str(&g_hlr->mslookup.server.mdns.bind_addr,
|
||||
OSMO_MSLOOKUP_MDNS_IP4, OSMO_MSLOOKUP_MDNS_PORT);
|
||||
osmo_sockaddr_str_from_str(&g_hlr->mslookup.client.mdns.query_addr,
|
||||
OSMO_MSLOOKUP_MDNS_IP4, OSMO_MSLOOKUP_MDNS_PORT);
|
||||
}
|
||||
|
||||
void dgsm_start(void *ctx)
|
||||
{
|
||||
g_hlr->mslookup.client.client = osmo_mslookup_client_new(dgsm_ctx);
|
||||
OSMO_ASSERT(g_hlr->mslookup.client.client);
|
||||
g_hlr->mslookup.allow_startup = true;
|
||||
mslookup_server_mdns_config_apply();
|
||||
dgsm_mdns_client_config_apply();
|
||||
}
|
||||
|
||||
void dgsm_stop()
|
||||
{
|
||||
g_hlr->mslookup.allow_startup = false;
|
||||
mslookup_server_mdns_config_apply();
|
||||
dgsm_mdns_client_config_apply();
|
||||
}
|
||||
|
||||
void dgsm_mdns_client_config_apply(void)
|
||||
{
|
||||
/* Check whether to start/stop/restart mDNS client */
|
||||
const struct osmo_sockaddr_str *current_bind_addr;
|
||||
const char *current_domain_suffix;
|
||||
current_bind_addr = osmo_mslookup_client_method_mdns_get_bind_addr(g_hlr->mslookup.client.mdns.running);
|
||||
current_domain_suffix = osmo_mslookup_client_method_mdns_get_domain_suffix(g_hlr->mslookup.client.mdns.running);
|
||||
|
||||
bool should_run = g_hlr->mslookup.allow_startup
|
||||
&& g_hlr->mslookup.client.enable && g_hlr->mslookup.client.mdns.enable;
|
||||
|
||||
bool should_stop = g_hlr->mslookup.client.mdns.running &&
|
||||
(!should_run
|
||||
|| osmo_sockaddr_str_cmp(&g_hlr->mslookup.client.mdns.query_addr,
|
||||
current_bind_addr)
|
||||
|| strcmp(g_hlr->mslookup.client.mdns.domain_suffix,
|
||||
current_domain_suffix));
|
||||
|
||||
if (should_stop) {
|
||||
osmo_mslookup_client_method_del(g_hlr->mslookup.client.client, g_hlr->mslookup.client.mdns.running);
|
||||
g_hlr->mslookup.client.mdns.running = NULL;
|
||||
LOGP(DDGSM, LOGL_NOTICE, "Stopped mslookup mDNS client\n");
|
||||
}
|
||||
|
||||
if (should_run && !g_hlr->mslookup.client.mdns.running) {
|
||||
g_hlr->mslookup.client.mdns.running =
|
||||
osmo_mslookup_client_add_mdns(g_hlr->mslookup.client.client,
|
||||
g_hlr->mslookup.client.mdns.query_addr.ip,
|
||||
g_hlr->mslookup.client.mdns.query_addr.port,
|
||||
-1,
|
||||
g_hlr->mslookup.client.mdns.domain_suffix);
|
||||
if (!g_hlr->mslookup.client.mdns.running)
|
||||
LOGP(DDGSM, LOGL_ERROR, "Failed to start mslookup mDNS client with target "
|
||||
OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.client.mdns.query_addr));
|
||||
else
|
||||
LOGP(DDGSM, LOGL_NOTICE, "Started mslookup mDNS client, sending mDNS requests to multicast "
|
||||
OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.client.mdns.query_addr));
|
||||
}
|
||||
|
||||
if (g_hlr->mslookup.client.enable && osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy))
|
||||
LOGP(DDGSM, LOGL_NOTICE,
|
||||
"mslookup client: all GSUP requests for unknown IMSIs will be forwarded to"
|
||||
" gateway-proxy " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.client.gsup_gateway_proxy));
|
||||
}
|
580
src/dgsm_vty.c
Normal file
580
src/dgsm_vty.c
Normal file
@@ -0,0 +1,580 @@
|
||||
/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <osmocom/vty/vty.h>
|
||||
#include <osmocom/vty/command.h>
|
||||
#include <osmocom/mslookup/mslookup_client_mdns.h>
|
||||
#include <osmocom/mslookup/mdns.h>
|
||||
#include <osmocom/hlr/hlr_vty.h>
|
||||
#include <osmocom/hlr/mslookup_server.h>
|
||||
#include <osmocom/hlr/mslookup_server_mdns.h>
|
||||
#include <osmocom/gsupclient/gsup_peer_id.h>
|
||||
|
||||
struct cmd_node mslookup_node = {
|
||||
MSLOOKUP_NODE,
|
||||
"%s(config-mslookup)# ",
|
||||
1,
|
||||
};
|
||||
|
||||
DEFUN(cfg_mslookup,
|
||||
cfg_mslookup_cmd,
|
||||
"mslookup",
|
||||
"Configure Distributed GSM mslookup")
|
||||
{
|
||||
vty->node = MSLOOKUP_NODE;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
static int mslookup_server_mdns_bind(struct vty *vty, int argc, const char **argv)
|
||||
{
|
||||
const char *ip_str = argc > 0? argv[0] : g_hlr->mslookup.server.mdns.bind_addr.ip;
|
||||
const char *port_str = argc > 1? argv[1] : NULL;
|
||||
uint16_t port_nr = port_str ? atoi(port_str) : g_hlr->mslookup.server.mdns.bind_addr.port;
|
||||
struct osmo_sockaddr_str addr;
|
||||
if (osmo_sockaddr_str_from_str(&addr, ip_str, port_nr)
|
||||
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
|
||||
vty_out(vty, "%% mslookup server: Invalid mDNS bind address: %s %u%s",
|
||||
ip_str, port_nr, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
g_hlr->mslookup.server.mdns.bind_addr = addr;
|
||||
g_hlr->mslookup.server.mdns.enable = true;
|
||||
g_hlr->mslookup.server.enable = true;
|
||||
mslookup_server_mdns_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
static int mslookup_client_mdns_to(struct vty *vty, int argc, const char **argv)
|
||||
{
|
||||
const char *ip_str = argc > 0? argv[0] : g_hlr->mslookup.client.mdns.query_addr.ip;
|
||||
const char *port_str = argc > 1? argv[1] : NULL;
|
||||
uint16_t port_nr = port_str ? atoi(port_str) : g_hlr->mslookup.client.mdns.query_addr.port;
|
||||
struct osmo_sockaddr_str addr;
|
||||
if (osmo_sockaddr_str_from_str(&addr, ip_str, port_nr)
|
||||
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
|
||||
vty_out(vty, "%% mslookup client: Invalid mDNS target address: %s %u%s",
|
||||
ip_str, port_nr, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
g_hlr->mslookup.client.mdns.query_addr = addr;
|
||||
g_hlr->mslookup.client.mdns.enable = true;
|
||||
g_hlr->mslookup.client.enable = true;
|
||||
dgsm_mdns_client_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#define MDNS_STR "Multicast DNS related configuration\n"
|
||||
#define MDNS_IP46_STR "multicast IPv4 address like " OSMO_MSLOOKUP_MDNS_IP4 \
|
||||
" or IPv6 address like " OSMO_MSLOOKUP_MDNS_IP6 "\n"
|
||||
#define MDNS_PORT_STR "mDNS UDP Port number\n"
|
||||
#define MDNS_DOMAIN_SUFFIX_STR "mDNS domain suffix (default: " OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT "). This is appended" \
|
||||
" and stripped from mDNS packets during encoding/decoding, so we don't collide with" \
|
||||
" top-level domains administrated by IANA\n"
|
||||
#define IP46_STR "IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1\n"
|
||||
#define PORT_STR "Service-specific port number\n"
|
||||
|
||||
DEFUN(cfg_mslookup_mdns,
|
||||
cfg_mslookup_mdns_cmd,
|
||||
"mdns bind [IP] [<1-65535>]",
|
||||
MDNS_STR
|
||||
"Convenience shortcut: enable and configure both server and client for mDNS mslookup\n"
|
||||
MDNS_IP46_STR MDNS_PORT_STR)
|
||||
{
|
||||
int rc1 = mslookup_server_mdns_bind(vty, argc, argv);
|
||||
int rc2 = mslookup_client_mdns_to(vty, argc, argv);
|
||||
if (rc1 != CMD_SUCCESS)
|
||||
return rc1;
|
||||
return rc2;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_mdns_domain_suffix,
|
||||
cfg_mslookup_mdns_domain_suffix_cmd,
|
||||
"mdns domain-suffix DOMAIN_SUFFIX",
|
||||
MDNS_STR MDNS_DOMAIN_SUFFIX_STR MDNS_DOMAIN_SUFFIX_STR)
|
||||
{
|
||||
osmo_talloc_replace_string(g_hlr, &g_hlr->mslookup.server.mdns.domain_suffix, argv[0]);
|
||||
osmo_talloc_replace_string(g_hlr, &g_hlr->mslookup.client.mdns.domain_suffix, argv[0]);
|
||||
mslookup_server_mdns_config_apply();
|
||||
dgsm_mdns_client_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_no_mdns,
|
||||
cfg_mslookup_no_mdns_cmd,
|
||||
"no mdns bind",
|
||||
NO_STR "Disable both server and client for mDNS mslookup\n")
|
||||
{
|
||||
g_hlr->mslookup.server.mdns.enable = false;
|
||||
g_hlr->mslookup.client.mdns.enable = false;
|
||||
mslookup_server_mdns_config_apply();
|
||||
dgsm_mdns_client_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
struct cmd_node mslookup_server_node = {
|
||||
MSLOOKUP_SERVER_NODE,
|
||||
"%s(config-mslookup-server)# ",
|
||||
1,
|
||||
};
|
||||
|
||||
DEFUN(cfg_mslookup_server,
|
||||
cfg_mslookup_server_cmd,
|
||||
"server",
|
||||
"Enable and configure Distributed GSM mslookup server")
|
||||
{
|
||||
vty->node = MSLOOKUP_SERVER_NODE;
|
||||
g_hlr->mslookup.server.enable = true;
|
||||
mslookup_server_mdns_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_no_server,
|
||||
cfg_mslookup_no_server_cmd,
|
||||
"no server",
|
||||
NO_STR "Disable Distributed GSM mslookup server")
|
||||
{
|
||||
g_hlr->mslookup.server.enable = false;
|
||||
mslookup_server_mdns_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_server_mdns_bind,
|
||||
cfg_mslookup_server_mdns_bind_cmd,
|
||||
"mdns bind [IP] [<1-65535>]",
|
||||
MDNS_STR
|
||||
"Configure where the mDNS server listens for mslookup requests\n"
|
||||
MDNS_IP46_STR MDNS_PORT_STR)
|
||||
{
|
||||
return mslookup_server_mdns_bind(vty, argc, argv);
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_server_mdns_domain_suffix,
|
||||
cfg_mslookup_server_mdns_domain_suffix_cmd,
|
||||
"mdns domain-suffix DOMAIN_SUFFIX",
|
||||
MDNS_STR
|
||||
MDNS_DOMAIN_SUFFIX_STR
|
||||
MDNS_DOMAIN_SUFFIX_STR)
|
||||
{
|
||||
osmo_talloc_replace_string(g_hlr, &g_hlr->mslookup.server.mdns.domain_suffix, argv[0]);
|
||||
mslookup_server_mdns_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_server_no_mdns_bind,
|
||||
cfg_mslookup_server_no_mdns_bind_cmd,
|
||||
"no mdns bind",
|
||||
NO_STR "Disable server for mDNS mslookup (do not answer remote requests)\n")
|
||||
{
|
||||
g_hlr->mslookup.server.mdns.enable = false;
|
||||
mslookup_server_mdns_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
struct cmd_node mslookup_server_msc_node = {
|
||||
MSLOOKUP_SERVER_MSC_NODE,
|
||||
"%s(config-mslookup-server-msc)# ",
|
||||
1,
|
||||
};
|
||||
|
||||
DEFUN(cfg_mslookup_server_msc,
|
||||
cfg_mslookup_server_msc_cmd,
|
||||
"msc ipa-name .IPA_NAME",
|
||||
"Configure services for individual local MSCs\n"
|
||||
"Identify locally connected MSC by IPA Unit Name\n"
|
||||
"IPA Unit Name of the local MSC to configure\n")
|
||||
{
|
||||
struct osmo_ipa_name msc_name;
|
||||
struct mslookup_server_msc_cfg *msc;
|
||||
osmo_ipa_name_set_str(&msc_name, argv_concat(argv, argc, 0));
|
||||
|
||||
msc = mslookup_server_msc_get(&msc_name, true);
|
||||
if (!msc) {
|
||||
vty_out(vty, "%% Error creating MSC %s%s", osmo_ipa_name_to_str(&msc_name), VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
vty->node = MSLOOKUP_SERVER_MSC_NODE;
|
||||
vty->index = msc;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#define SERVICE_NAME_STR \
|
||||
"mslookup service name, e.g. sip.voice or smpp.sms\n"
|
||||
|
||||
static struct mslookup_server_msc_cfg *msc_from_node(struct vty *vty)
|
||||
{
|
||||
switch (vty->node) {
|
||||
case MSLOOKUP_SERVER_NODE:
|
||||
/* On the mslookup.server node, set services on the wildcard msc, without a particular name. */
|
||||
return mslookup_server_msc_get(&mslookup_server_msc_wildcard, true);
|
||||
case MSLOOKUP_SERVER_MSC_NODE:
|
||||
return vty->index;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_server_msc_service,
|
||||
cfg_mslookup_server_msc_service_cmd,
|
||||
"service NAME at IP <1-65535>",
|
||||
"Configure addresses of local services, as sent in replies to remote mslookup requests.\n"
|
||||
SERVICE_NAME_STR "at\n" IP46_STR PORT_STR)
|
||||
{
|
||||
/* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
|
||||
* MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
|
||||
struct mslookup_server_msc_cfg *msc = msc_from_node(vty);
|
||||
const char *service = argv[0];
|
||||
const char *ip_str = argv[1];
|
||||
const char *port_str = argv[2];
|
||||
struct osmo_sockaddr_str addr;
|
||||
|
||||
if (!msc) {
|
||||
vty_out(vty, "%% Error: no MSC object on this node%s", VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (osmo_sockaddr_str_from_str(&addr, ip_str, atoi(port_str))
|
||||
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
|
||||
vty_out(vty, "%% mslookup server: Invalid address for service %s: %s %s%s",
|
||||
service, ip_str, port_str, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (mslookup_server_msc_service_set(msc, service, &addr)) {
|
||||
vty_out(vty, "%% mslookup server: Error setting service %s to %s %s%s",
|
||||
service, ip_str, port_str, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#define NO_SERVICE_AND_NAME_STR NO_STR "Remove one or more service address entries\n" SERVICE_NAME_STR
|
||||
|
||||
DEFUN(cfg_mslookup_server_msc_no_service,
|
||||
cfg_mslookup_server_msc_no_service_cmd,
|
||||
"no service NAME",
|
||||
NO_SERVICE_AND_NAME_STR)
|
||||
{
|
||||
/* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
|
||||
* MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
|
||||
struct mslookup_server_msc_cfg *msc = msc_from_node(vty);
|
||||
const char *service = argv[0];
|
||||
|
||||
if (!msc) {
|
||||
vty_out(vty, "%% Error: no MSC object on this node%s", VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (mslookup_server_msc_service_del(msc, service, NULL) < 1) {
|
||||
vty_out(vty, "%% mslookup server: cannot remove service '%s'%s",
|
||||
service, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_server_msc_no_service_addr,
|
||||
cfg_mslookup_server_msc_no_service_addr_cmd,
|
||||
"no service NAME at IP <1-65535>",
|
||||
NO_SERVICE_AND_NAME_STR "at\n" IP46_STR PORT_STR)
|
||||
{
|
||||
/* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
|
||||
* MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
|
||||
struct mslookup_server_msc_cfg *msc = msc_from_node(vty);
|
||||
const char *service = argv[0];
|
||||
const char *ip_str = argv[1];
|
||||
const char *port_str = argv[2];
|
||||
struct osmo_sockaddr_str addr;
|
||||
|
||||
if (!msc) {
|
||||
vty_out(vty, "%% Error: no MSC object on this node%s", VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (osmo_sockaddr_str_from_str(&addr, ip_str, atoi(port_str))
|
||||
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
|
||||
vty_out(vty, "%% mslookup server: Invalid address for 'no service' %s: %s %s%s",
|
||||
service, ip_str, port_str, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (mslookup_server_msc_service_del(msc, service, &addr) < 1) {
|
||||
vty_out(vty, "%% mslookup server: cannot remove service '%s' to %s %s%s",
|
||||
service, ip_str, port_str, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
struct cmd_node mslookup_client_node = {
|
||||
MSLOOKUP_CLIENT_NODE,
|
||||
"%s(config-mslookup-client)# ",
|
||||
1,
|
||||
};
|
||||
|
||||
DEFUN(cfg_mslookup_client,
|
||||
cfg_mslookup_client_cmd,
|
||||
"client",
|
||||
"Enable and configure Distributed GSM mslookup client")
|
||||
{
|
||||
vty->node = MSLOOKUP_CLIENT_NODE;
|
||||
g_hlr->mslookup.client.enable = true;
|
||||
dgsm_mdns_client_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_no_client,
|
||||
cfg_mslookup_no_client_cmd,
|
||||
"no client",
|
||||
NO_STR "Disable Distributed GSM mslookup client")
|
||||
{
|
||||
g_hlr->mslookup.client.enable = false;
|
||||
dgsm_mdns_client_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_client_timeout,
|
||||
cfg_mslookup_client_timeout_cmd,
|
||||
"timeout <1-100000>",
|
||||
"How long should the mslookup client wait for remote responses before evaluating received results\n"
|
||||
"timeout in milliseconds\n")
|
||||
{
|
||||
uint32_t val = atol(argv[0]);
|
||||
g_hlr->mslookup.client.result_timeout_milliseconds = val;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#define EXIT_HINT() \
|
||||
if (vty->type != VTY_FILE) \
|
||||
vty_out(vty, "%% 'exit' this node to apply changes%s", VTY_NEWLINE)
|
||||
|
||||
|
||||
DEFUN(cfg_mslookup_client_mdns_bind,
|
||||
cfg_mslookup_client_mdns_bind_cmd,
|
||||
"mdns bind [IP] [<1-65535>]",
|
||||
MDNS_STR
|
||||
"Enable mDNS client, and configure multicast address to send mDNS mslookup requests to\n"
|
||||
MDNS_IP46_STR MDNS_PORT_STR)
|
||||
{
|
||||
return mslookup_client_mdns_to(vty, argc, argv);
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_client_mdns_domain_suffix,
|
||||
cfg_mslookup_client_mdns_domain_suffix_cmd,
|
||||
"mdns domain-suffix DOMAIN_SUFFIX",
|
||||
MDNS_STR
|
||||
MDNS_DOMAIN_SUFFIX_STR
|
||||
MDNS_DOMAIN_SUFFIX_STR)
|
||||
{
|
||||
osmo_talloc_replace_string(g_hlr, &g_hlr->mslookup.client.mdns.domain_suffix, argv[0]);
|
||||
dgsm_mdns_client_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_client_no_mdns_bind,
|
||||
cfg_mslookup_client_no_mdns_bind_cmd,
|
||||
"no mdns bind",
|
||||
NO_STR "Disable mDNS client, do not query remote services by mDNS\n")
|
||||
{
|
||||
g_hlr->mslookup.client.mdns.enable = false;
|
||||
dgsm_mdns_client_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
void config_write_msc_services(struct vty *vty, const char *indent, struct mslookup_server_msc_cfg *msc)
|
||||
{
|
||||
struct mslookup_service_host *e;
|
||||
|
||||
llist_for_each_entry(e, &msc->service_hosts, entry) {
|
||||
if (osmo_sockaddr_str_is_nonzero(&e->host_v4))
|
||||
vty_out(vty, "%sservice %s at %s %u%s", indent, e->service, e->host_v4.ip, e->host_v4.port,
|
||||
VTY_NEWLINE);
|
||||
if (osmo_sockaddr_str_is_nonzero(&e->host_v6))
|
||||
vty_out(vty, "%sservice %s at %s %u%s", indent, e->service, e->host_v6.ip, e->host_v6.port,
|
||||
VTY_NEWLINE);
|
||||
}
|
||||
}
|
||||
|
||||
int config_write_mslookup(struct vty *vty)
|
||||
{
|
||||
if (!g_hlr->mslookup.server.enable
|
||||
&& llist_empty(&g_hlr->mslookup.server.local_site_services)
|
||||
&& !g_hlr->mslookup.client.enable)
|
||||
return CMD_SUCCESS;
|
||||
|
||||
vty_out(vty, "mslookup%s", VTY_NEWLINE);
|
||||
|
||||
if (g_hlr->mslookup.server.enable || !llist_empty(&g_hlr->mslookup.server.local_site_services)) {
|
||||
struct mslookup_server_msc_cfg *msc;
|
||||
|
||||
vty_out(vty, " server%s", VTY_NEWLINE);
|
||||
|
||||
if (g_hlr->mslookup.server.mdns.enable) {
|
||||
vty_out(vty, " mdns bind");
|
||||
if (osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.server.mdns.bind_addr)) {
|
||||
vty_out(vty, " %s %u",
|
||||
g_hlr->mslookup.server.mdns.bind_addr.ip,
|
||||
g_hlr->mslookup.server.mdns.bind_addr.port);
|
||||
}
|
||||
vty_out(vty, "%s", VTY_NEWLINE);
|
||||
}
|
||||
if (strcmp(g_hlr->mslookup.server.mdns.domain_suffix, OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT))
|
||||
vty_out(vty, " mdns domain-suffix %s%s",
|
||||
g_hlr->mslookup.server.mdns.domain_suffix,
|
||||
VTY_NEWLINE);
|
||||
|
||||
msc = mslookup_server_msc_get(&mslookup_server_msc_wildcard, false);
|
||||
if (msc)
|
||||
config_write_msc_services(vty, " ", msc);
|
||||
|
||||
llist_for_each_entry(msc, &g_hlr->mslookup.server.local_site_services, entry) {
|
||||
if (!osmo_ipa_name_cmp(&mslookup_server_msc_wildcard, &msc->name))
|
||||
continue;
|
||||
vty_out(vty, " msc %s%s", osmo_ipa_name_to_str(&msc->name), VTY_NEWLINE);
|
||||
config_write_msc_services(vty, " ", msc);
|
||||
}
|
||||
|
||||
/* If the server is disabled, still output the above to not lose the service config. */
|
||||
if (!g_hlr->mslookup.server.enable)
|
||||
vty_out(vty, " no server%s", VTY_NEWLINE);
|
||||
}
|
||||
|
||||
if (g_hlr->mslookup.client.enable) {
|
||||
vty_out(vty, " client%s", VTY_NEWLINE);
|
||||
|
||||
if (osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy))
|
||||
vty_out(vty, " gateway-proxy %s %u%s",
|
||||
g_hlr->mslookup.client.gsup_gateway_proxy.ip,
|
||||
g_hlr->mslookup.client.gsup_gateway_proxy.port,
|
||||
VTY_NEWLINE);
|
||||
|
||||
if (g_hlr->mslookup.client.mdns.enable
|
||||
&& osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.mdns.query_addr))
|
||||
vty_out(vty, " mdns bind %s %u%s",
|
||||
g_hlr->mslookup.client.mdns.query_addr.ip,
|
||||
g_hlr->mslookup.client.mdns.query_addr.port,
|
||||
VTY_NEWLINE);
|
||||
if (strcmp(g_hlr->mslookup.client.mdns.domain_suffix, OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT))
|
||||
vty_out(vty, " mdns domain-suffix %s%s",
|
||||
g_hlr->mslookup.client.mdns.domain_suffix,
|
||||
VTY_NEWLINE);
|
||||
}
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_client_gateway_proxy,
|
||||
cfg_mslookup_client_gateway_proxy_cmd,
|
||||
"gateway-proxy IP [<1-65535>]",
|
||||
"Configure a fixed IP address to send all GSUP requests for unknown IMSIs to, without invoking a lookup for IMSI\n"
|
||||
"IP address of the remote HLR\n" "GSUP port number (omit for default " OSMO_STRINGIFY_VAL(OSMO_GSUP_PORT) ")\n")
|
||||
{
|
||||
const char *ip_str = argv[0];
|
||||
const char *port_str = argc > 1 ? argv[1] : NULL;
|
||||
struct osmo_sockaddr_str addr;
|
||||
|
||||
if (osmo_sockaddr_str_from_str(&addr, ip_str, port_str ? atoi(port_str) : OSMO_GSUP_PORT)
|
||||
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
|
||||
vty_out(vty, "%% mslookup client: Invalid address for gateway-proxy: %s %s%s",
|
||||
ip_str, port_str ? : "", VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
g_hlr->mslookup.client.gsup_gateway_proxy = addr;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_client_no_gateway_proxy,
|
||||
cfg_mslookup_client_no_gateway_proxy_cmd,
|
||||
"no gateway-proxy",
|
||||
NO_STR "Disable gateway proxy for GSUP with unknown IMSIs\n")
|
||||
{
|
||||
g_hlr->mslookup.client.gsup_gateway_proxy = (struct osmo_sockaddr_str){};
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(do_mslookup_show_services,
|
||||
do_mslookup_show_services_cmd,
|
||||
"show mslookup services",
|
||||
SHOW_STR "Distributed GSM / mslookup related information\n"
|
||||
"List configured service addresses as sent to remote mslookup requests\n")
|
||||
{
|
||||
struct mslookup_server_msc_cfg *msc;
|
||||
const struct mslookup_service_host *local_hlr = mslookup_server_get_local_gsup_addr();
|
||||
|
||||
vty_out(vty, "Local GSUP HLR address returned in mslookup responses for local IMSIs:");
|
||||
if (osmo_sockaddr_str_is_nonzero(&local_hlr->host_v4))
|
||||
vty_out(vty, " " OSMO_SOCKADDR_STR_FMT,
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&local_hlr->host_v4));
|
||||
if (osmo_sockaddr_str_is_nonzero(&local_hlr->host_v6))
|
||||
vty_out(vty, " " OSMO_SOCKADDR_STR_FMT,
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&local_hlr->host_v6));
|
||||
vty_out(vty, "%s", VTY_NEWLINE);
|
||||
|
||||
msc = mslookup_server_msc_get(&mslookup_server_msc_wildcard, false);
|
||||
if (msc)
|
||||
config_write_msc_services(vty, "", msc);
|
||||
|
||||
llist_for_each_entry(msc, &g_hlr->mslookup.server.local_site_services, entry) {
|
||||
if (!osmo_ipa_name_cmp(&mslookup_server_msc_wildcard, &msc->name))
|
||||
continue;
|
||||
vty_out(vty, "msc ipa-name %s%s", osmo_ipa_name_to_str(&msc->name), VTY_NEWLINE);
|
||||
config_write_msc_services(vty, " ", msc);
|
||||
}
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
void dgsm_vty_init(void)
|
||||
{
|
||||
install_element(CONFIG_NODE, &cfg_mslookup_cmd);
|
||||
|
||||
install_node(&mslookup_node, config_write_mslookup);
|
||||
install_element(MSLOOKUP_NODE, &cfg_mslookup_mdns_cmd);
|
||||
install_element(MSLOOKUP_NODE, &cfg_mslookup_mdns_domain_suffix_cmd);
|
||||
install_element(MSLOOKUP_NODE, &cfg_mslookup_no_mdns_cmd);
|
||||
install_element(MSLOOKUP_NODE, &cfg_mslookup_server_cmd);
|
||||
install_element(MSLOOKUP_NODE, &cfg_mslookup_no_server_cmd);
|
||||
|
||||
install_node(&mslookup_server_node, NULL);
|
||||
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_mdns_bind_cmd);
|
||||
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_mdns_domain_suffix_cmd);
|
||||
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_no_mdns_bind_cmd);
|
||||
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_service_cmd);
|
||||
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_no_service_cmd);
|
||||
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_no_service_addr_cmd);
|
||||
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_cmd);
|
||||
|
||||
install_node(&mslookup_server_msc_node, NULL);
|
||||
install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_service_cmd);
|
||||
install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_no_service_cmd);
|
||||
install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_no_service_addr_cmd);
|
||||
|
||||
install_element(MSLOOKUP_NODE, &cfg_mslookup_client_cmd);
|
||||
install_element(MSLOOKUP_NODE, &cfg_mslookup_no_client_cmd);
|
||||
install_node(&mslookup_client_node, NULL);
|
||||
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_timeout_cmd);
|
||||
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_mdns_bind_cmd);
|
||||
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_mdns_domain_suffix_cmd);
|
||||
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_no_mdns_bind_cmd);
|
||||
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_gateway_proxy_cmd);
|
||||
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_no_gateway_proxy_cmd);
|
||||
|
||||
install_element_ve(&do_mslookup_show_services_cmd);
|
||||
}
|
@@ -47,6 +47,11 @@ struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct osmo_gsup_conn *gsup_route_find_by_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name)
|
||||
{
|
||||
return gsup_route_find(gs, ipa_name->val, ipa_name->len);
|
||||
}
|
||||
|
||||
/*! Find a GSUP connection's route (to read the IPA address from the route).
|
||||
* \param[in] conn GSUP connection
|
||||
* \return GSUP route
|
||||
@@ -67,10 +72,15 @@ struct gsup_route *gsup_route_find_by_conn(const struct osmo_gsup_conn *conn)
|
||||
int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addrlen)
|
||||
{
|
||||
struct gsup_route *gr;
|
||||
struct osmo_gsup_conn *exists_on_conn;
|
||||
|
||||
/* Check if we already have a route for this address */
|
||||
if (gsup_route_find(conn->server, addr, addrlen))
|
||||
return -EEXIST;
|
||||
exists_on_conn = gsup_route_find(conn->server, addr, addrlen);
|
||||
if (exists_on_conn) {
|
||||
if (exists_on_conn != conn)
|
||||
return -EEXIST;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* allocate new route and populate it */
|
||||
gr = talloc_zero(conn->server, struct gsup_route);
|
||||
@@ -86,6 +96,11 @@ int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addr
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gsup_route_add_ipa_name(struct osmo_gsup_conn *conn, const struct osmo_ipa_name *ipa_name)
|
||||
{
|
||||
return gsup_route_add(conn, ipa_name->val, ipa_name->len);
|
||||
}
|
||||
|
||||
/* delete all routes for the given connection */
|
||||
int gsup_route_del_conn(struct osmo_gsup_conn *conn)
|
||||
{
|
||||
@@ -95,7 +110,7 @@ int gsup_route_del_conn(struct osmo_gsup_conn *conn)
|
||||
llist_for_each_entry_safe(gr, gr2, &conn->server->routes, list) {
|
||||
if (gr->conn == conn) {
|
||||
LOGP(DMAIN, LOGL_INFO, "Removing GSUP route for %s (GSUP disconnect)\n",
|
||||
gr->addr);
|
||||
osmo_quote_str_c(OTC_SELECT, (char*)gr->addr, talloc_total_size(gr->addr)));
|
||||
llist_del(&gr->list);
|
||||
talloc_free(gr);
|
||||
num_deleted++;
|
||||
|
@@ -42,7 +42,8 @@ int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
|
||||
|
||||
conn = gsup_route_find(gs, addr, addrlen);
|
||||
if (!conn) {
|
||||
DEBUGP(DLGSUP, "Cannot find route for addr %s\n", osmo_quote_str((const char*)addr, addrlen));
|
||||
LOGP(DLGSUP, LOGL_ERROR,
|
||||
"Cannot find route for addr %s\n", osmo_quote_str((const char*)addr, addrlen));
|
||||
msgb_free(msg);
|
||||
return -ENODEV;
|
||||
}
|
||||
@@ -50,3 +51,41 @@ int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
|
||||
return osmo_gsup_conn_send(conn, msg);
|
||||
}
|
||||
|
||||
/*! Send a msgb to a given address using routing.
|
||||
* \param[in] gs gsup server
|
||||
* \param[in] ipa_name IPA unit name of the client (SGSN, MSC/VLR, proxy).
|
||||
* \param[in] msg message buffer
|
||||
*/
|
||||
int osmo_gsup_send_to_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name, struct msgb *msg)
|
||||
{
|
||||
if (ipa_name->val[ipa_name->len - 1]) {
|
||||
/* Is not nul terminated. But for legacy reasons we (still) require that. */
|
||||
if (ipa_name->len >= sizeof(ipa_name->val)) {
|
||||
LOGP(DLGSUP, LOGL_ERROR, "IPA unit name is too long: %s\n",
|
||||
osmo_ipa_name_to_str(ipa_name));
|
||||
return -EINVAL;
|
||||
}
|
||||
struct osmo_ipa_name ipa_name2 = *ipa_name;
|
||||
ipa_name2.val[ipa_name->len] = '\0';
|
||||
ipa_name2.len++;
|
||||
return osmo_gsup_addr_send(gs, ipa_name2.val, ipa_name2.len, msg);
|
||||
}
|
||||
return osmo_gsup_addr_send(gs, ipa_name->val, ipa_name->len, msg);
|
||||
}
|
||||
|
||||
int osmo_gsup_enc_send_to_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name,
|
||||
const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
struct msgb *msg = osmo_gsup_msgb_alloc("GSUP Tx");
|
||||
int rc;
|
||||
rc = osmo_gsup_encode(msg, gsup);
|
||||
if (rc) {
|
||||
LOGP(DLGSUP, LOGL_ERROR, "IMSI-%s: Cannot encode GSUP: %s\n",
|
||||
gsup->imsi, osmo_gsup_message_type_name(gsup->message_type));
|
||||
msgb_free(msg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOGP(DLGSUP, LOGL_DEBUG, "IMSI-%s: Tx: %s\n", gsup->imsi, osmo_gsup_message_type_name(gsup->message_type));
|
||||
return osmo_gsup_send_to_ipa_name(gs, ipa_name, msg);
|
||||
}
|
||||
|
@@ -26,13 +26,18 @@
|
||||
#include <osmocom/abis/ipaccess.h>
|
||||
#include <osmocom/gsm/gsm48_ie.h>
|
||||
#include <osmocom/gsm/apn.h>
|
||||
#include <osmocom/gsm/gsm23003.h>
|
||||
|
||||
#include <osmocom/hlr/gsup_server.h>
|
||||
#include <osmocom/hlr/gsup_router.h>
|
||||
|
||||
#define LOG_GSUP_CONN(conn, level, fmt, args...) \
|
||||
LOGP(DLGSUP, level, "GSUP peer %s: " fmt, \
|
||||
(conn) ? osmo_ipa_name_to_str(&(conn)->peer_name) : "NULL", ##args)
|
||||
|
||||
struct msgb *osmo_gsup_msgb_alloc(const char *label)
|
||||
{
|
||||
struct msgb *msg = msgb_alloc_headroom(1024+16, 16, label);
|
||||
struct msgb *msg = msgb_alloc_headroom(1024+512, 512, label);
|
||||
OSMO_ASSERT(msg);
|
||||
return msg;
|
||||
}
|
||||
@@ -57,6 +62,91 @@ int osmo_gsup_conn_send(struct osmo_gsup_conn *conn, struct msgb *msg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gsup_server_send_req_response(struct osmo_gsup_req *req, struct osmo_gsup_message *response)
|
||||
{
|
||||
struct osmo_gsup_server *server = req->cb_data;
|
||||
struct osmo_gsup_peer_id *routing;
|
||||
struct osmo_gsup_conn *conn = NULL;
|
||||
struct msgb *msg = osmo_gsup_msgb_alloc("GSUP Tx");
|
||||
int rc;
|
||||
|
||||
if (response->message_type == OSMO_GSUP_MSGT_ROUTING_ERROR
|
||||
&& !osmo_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;
|
||||
}
|
||||
|
||||
rc = osmo_gsup_conn_send(conn, msg);
|
||||
if (rc)
|
||||
LOG_GSUP_CONN(conn, LOGL_ERROR, "Unable to send: %s\n", osmo_gsup_message_to_str_c(OTC_SELECT, response));
|
||||
}
|
||||
|
||||
struct osmo_gsup_req *osmo_gsup_conn_rx(struct osmo_gsup_conn *conn, struct msgb *msg)
|
||||
{
|
||||
struct osmo_gsup_req *req;
|
||||
struct osmo_gsup_peer_id gpi = {
|
||||
.type = OSMO_GSUP_PEER_ID_IPA_NAME,
|
||||
.ipa_name = conn->peer_name,
|
||||
};
|
||||
|
||||
req = osmo_gsup_req_new(conn->server, &gpi, msg, gsup_server_send_req_response, conn->server, NULL);
|
||||
if (!req)
|
||||
return NULL;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
static int osmo_gsup_conn_oap_handle(struct osmo_gsup_conn *conn,
|
||||
struct msgb *msg_rx)
|
||||
{
|
||||
@@ -202,10 +292,18 @@ static int osmo_gsup_server_ccm_cb(struct ipa_server_conn *conn,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
gsup_route_add(clnt, addr, addr_len);
|
||||
osmo_ipa_name_set(&clnt->peer_name, addr, addr_len);
|
||||
gsup_route_add_ipa_name(clnt, &clnt->peer_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void osmo_gsup_conn_free(struct osmo_gsup_conn *conn)
|
||||
{
|
||||
gsup_route_del_conn(conn);
|
||||
llist_del(&conn->list);
|
||||
talloc_free(conn);
|
||||
}
|
||||
|
||||
static int osmo_gsup_server_closed_cb(struct ipa_server_conn *conn)
|
||||
{
|
||||
struct osmo_gsup_conn *clnt = (struct osmo_gsup_conn *)conn->data;
|
||||
@@ -213,10 +311,7 @@ static int osmo_gsup_server_closed_cb(struct ipa_server_conn *conn)
|
||||
LOGP(DLGSUP, LOGL_INFO, "Lost GSUP client %s:%d\n",
|
||||
conn->addr, conn->port);
|
||||
|
||||
gsup_route_del_conn(clnt);
|
||||
llist_del(&clnt->list);
|
||||
talloc_free(clnt);
|
||||
|
||||
osmo_gsup_conn_free(clnt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -298,8 +393,7 @@ failed:
|
||||
|
||||
struct osmo_gsup_server *
|
||||
osmo_gsup_server_create(void *ctx, const char *ip_addr, uint16_t tcp_port,
|
||||
osmo_gsup_read_cb_t read_cb,
|
||||
struct llist_head *lu_op_lst, void *priv)
|
||||
osmo_gsup_read_cb_t read_cb, void *priv)
|
||||
{
|
||||
struct osmo_gsup_server *gsups;
|
||||
int rc;
|
||||
@@ -325,8 +419,6 @@ osmo_gsup_server_create(void *ctx, const char *ip_addr, uint16_t tcp_port,
|
||||
if (rc < 0)
|
||||
goto failed;
|
||||
|
||||
gsups->luop = lu_op_lst;
|
||||
|
||||
return gsups;
|
||||
|
||||
failed:
|
||||
@@ -390,8 +482,10 @@ int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup,
|
||||
int len;
|
||||
|
||||
OSMO_ASSERT(gsup);
|
||||
*gsup = (struct osmo_gsup_message){
|
||||
.message_type = OSMO_GSUP_MSGT_INSERT_DATA_REQUEST,
|
||||
};
|
||||
|
||||
gsup->message_type = OSMO_GSUP_MSGT_INSERT_DATA_REQUEST;
|
||||
osmo_strlcpy(gsup->imsi, imsi, sizeof(gsup->imsi));
|
||||
|
||||
if (msisdn_enc_size < OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN)
|
||||
@@ -419,3 +513,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;
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -97,6 +97,12 @@ static void connect_timer_cb(void *gsupc_)
|
||||
if (gsupc->is_connected)
|
||||
return;
|
||||
|
||||
if (gsupc->up_down_cb) {
|
||||
/* When the up_down_cb() returns false, the user asks us not to retry connecting. */
|
||||
if (!gsupc->up_down_cb(gsupc, false))
|
||||
return;
|
||||
}
|
||||
|
||||
gsup_client_connect(gsupc);
|
||||
}
|
||||
|
||||
@@ -139,9 +145,18 @@ static void gsup_client_updown_cb(struct ipa_client_conn *link, int up)
|
||||
gsup_client_oap_register(gsupc);
|
||||
|
||||
osmo_timer_del(&gsupc->connect_timer);
|
||||
|
||||
if (gsupc->up_down_cb)
|
||||
gsupc->up_down_cb(gsupc, true);
|
||||
} else {
|
||||
osmo_timer_del(&gsupc->ping_timer);
|
||||
|
||||
if (gsupc->up_down_cb) {
|
||||
/* When the up_down_cb() returns false, the user asks us not to retry connecting. */
|
||||
if (!gsupc->up_down_cb(gsupc, false))
|
||||
return;
|
||||
}
|
||||
|
||||
osmo_timer_schedule(&gsupc->connect_timer,
|
||||
OSMO_GSUP_CLIENT_RECONNECT_INTERVAL, 0);
|
||||
}
|
||||
@@ -263,31 +278,28 @@ static void start_test_procedure(struct osmo_gsup_client *gsupc)
|
||||
* Use the provided ipaccess unit as the client-side identifier; ipa_dev should
|
||||
* be allocated in talloc_ctx talloc_ctx as well.
|
||||
* \param[in] talloc_ctx talloc context.
|
||||
* \param[in] ipa_dev IP access unit which contains client identification information; must be allocated
|
||||
* in talloc_ctx as well to ensure it lives throughout the lifetime of the connection.
|
||||
* \param[in] ip_addr GSUP server IP address.
|
||||
* \param[in] tcp_port GSUP server TCP port.
|
||||
* \param[in] read_cb callback for reading from the GSUP connection.
|
||||
* \param[in] oapc_config OPA client configuration.
|
||||
* \returns a GSUP client connection or NULL on failure.
|
||||
* \param[in] config Parameters for setting up the GSUP client.
|
||||
* \return a GSUP client connection, or NULL on failure.
|
||||
*/
|
||||
struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx,
|
||||
struct ipaccess_unit *ipa_dev,
|
||||
const char *ip_addr,
|
||||
unsigned int tcp_port,
|
||||
osmo_gsup_client_read_cb_t read_cb,
|
||||
struct osmo_oap_client_config *oapc_config)
|
||||
struct osmo_gsup_client *osmo_gsup_client_create3(void *talloc_ctx, struct osmo_gsup_client_config *config)
|
||||
{
|
||||
struct osmo_gsup_client *gsupc;
|
||||
int rc;
|
||||
|
||||
OSMO_ASSERT(config->ipa_dev->unit_name);
|
||||
|
||||
gsupc = talloc_zero(talloc_ctx, struct osmo_gsup_client);
|
||||
OSMO_ASSERT(gsupc);
|
||||
gsupc->unit_name = (const char *)ipa_dev->unit_name; /* API backwards compat */
|
||||
gsupc->ipa_dev = ipa_dev;
|
||||
*gsupc = (struct osmo_gsup_client){
|
||||
.unit_name = (const char *)config->ipa_dev->unit_name, /* API backwards compat */
|
||||
.ipa_dev = config->ipa_dev,
|
||||
.read_cb = config->read_cb,
|
||||
.up_down_cb = config->up_down_cb,
|
||||
.data = config->data,
|
||||
};
|
||||
|
||||
/* a NULL oapc_config will mark oap_state disabled. */
|
||||
rc = osmo_oap_client_init(oapc_config, &gsupc->oap_state);
|
||||
rc = osmo_oap_client_init(config->oapc_config, &gsupc->oap_state);
|
||||
if (rc != 0)
|
||||
goto failed;
|
||||
|
||||
@@ -295,7 +307,7 @@ struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx,
|
||||
/* no e1inp */ NULL,
|
||||
0,
|
||||
/* no specific local IP:port */ NULL, 0,
|
||||
ip_addr, tcp_port,
|
||||
config->ip_addr, config->tcp_port,
|
||||
gsup_client_updown_cb,
|
||||
gsup_client_read_cb,
|
||||
/* default write_cb */ NULL,
|
||||
@@ -309,9 +321,6 @@ struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx,
|
||||
|
||||
if (rc < 0)
|
||||
goto failed;
|
||||
|
||||
gsupc->read_cb = read_cb;
|
||||
|
||||
return gsupc;
|
||||
|
||||
failed:
|
||||
@@ -319,6 +328,26 @@ failed:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*! Like osmo_gsup_client_create3() but without the up_down_cb and data arguments, and with the oapc_config argument in
|
||||
* a different position.
|
||||
*/
|
||||
struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx,
|
||||
struct ipaccess_unit *ipa_dev,
|
||||
const char *ip_addr,
|
||||
unsigned int tcp_port,
|
||||
osmo_gsup_client_read_cb_t read_cb,
|
||||
struct osmo_oap_client_config *oapc_config)
|
||||
{
|
||||
struct osmo_gsup_client_config cfg = {
|
||||
.ipa_dev = ipa_dev,
|
||||
.ip_addr = ip_addr,
|
||||
.tcp_port = tcp_port,
|
||||
.oapc_config = oapc_config,
|
||||
.read_cb = read_cb,
|
||||
};
|
||||
return osmo_gsup_client_create3(talloc_ctx, &cfg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like osmo_gsup_client_create2() except it expects a unit name instead
|
||||
* of a full-blown ipacess_unit as the client-side identifier.
|
||||
|
187
src/gsupclient/gsup_peer_id.c
Normal file
187
src/gsupclient/gsup_peer_id.c
Normal file
@@ -0,0 +1,187 @@
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Call osmo_ipa_name_to_str_c with OTC_SELECT. */
|
||||
const char *osmo_ipa_name_to_str(const struct osmo_ipa_name *ipa_name)
|
||||
{
|
||||
return osmo_ipa_name_to_str_c(OTC_SELECT, ipa_name);
|
||||
}
|
||||
|
||||
/* Return an unquoted string, not including the terminating zero. Used for writing VTY config. */
|
||||
const char *osmo_ipa_name_to_str_c(void *ctx, const struct osmo_ipa_name *ipa_name)
|
||||
{
|
||||
size_t len = ipa_name->len;
|
||||
if (!len)
|
||||
return talloc_strdup(ctx, "");
|
||||
if (ipa_name->val[len-1] == '\0')
|
||||
len--;
|
||||
return osmo_escape_str_c(ctx, (char*)ipa_name->val, len);
|
||||
}
|
||||
|
||||
bool osmo_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" },
|
||||
{}
|
||||
};
|
||||
|
||||
/* Call osmo_gsup_peer_id_to_str_c with OTC_SELECT */
|
||||
const char *osmo_gsup_peer_id_to_str(const struct osmo_gsup_peer_id *gpi)
|
||||
{
|
||||
return osmo_gsup_peer_id_to_str_c(OTC_SELECT, gpi);
|
||||
}
|
||||
|
||||
/* Return an unquoted string, not including the terminating zero. Used for writing VTY config. */
|
||||
const char *osmo_gsup_peer_id_to_str_c(void *ctx, const struct osmo_gsup_peer_id *gpi)
|
||||
{
|
||||
switch (gpi->type) {
|
||||
case OSMO_GSUP_PEER_ID_IPA_NAME:
|
||||
return osmo_ipa_name_to_str_c(ctx, &gpi->ipa_name);
|
||||
default:
|
||||
return talloc_strdup(ctx, osmo_gsup_peer_id_type_name(gpi->type));
|
||||
}
|
||||
}
|
312
src/gsupclient/gsup_req.c
Normal file
312
src/gsupclient/gsup_req.c
Normal 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)
|
||||
}
|
522
src/hlr.c
522
src/hlr.c
@@ -35,8 +35,10 @@
|
||||
#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 <osmocom/gsupclient/gsup_peer_id.h>
|
||||
#include <osmocom/hlr/db.h>
|
||||
#include <osmocom/hlr/hlr.h>
|
||||
#include <osmocom/hlr/ctrl.h>
|
||||
@@ -44,14 +46,23 @@
|
||||
#include <osmocom/hlr/gsup_server.h>
|
||||
#include <osmocom/hlr/gsup_router.h>
|
||||
#include <osmocom/hlr/rand.h>
|
||||
#include <osmocom/hlr/luop.h>
|
||||
#include <osmocom/hlr/hlr_vty.h>
|
||||
#include <osmocom/hlr/hlr_ussd.h>
|
||||
#include <osmocom/hlr/dgsm.h>
|
||||
#include <osmocom/hlr/proxy.h>
|
||||
#include <osmocom/hlr/lu_fsm.h>
|
||||
#include <osmocom/mslookup/mdns.h>
|
||||
|
||||
struct hlr *g_hlr;
|
||||
static void *hlr_ctx = NULL;
|
||||
static int quit = 0;
|
||||
|
||||
struct osmo_tdef g_hlr_tdefs[] = {
|
||||
/* 4222 is also the OSMO_GSUP_PORT */
|
||||
{ .T = -4222, .default_val = 30, .desc = "GSUP Update Location timeout" },
|
||||
{}
|
||||
};
|
||||
|
||||
/* Trigger 'Insert Subscriber Data' messages to all connected GSUP clients.
|
||||
*
|
||||
* \param[in] subscr A subscriber we have new data to send for.
|
||||
@@ -69,6 +80,8 @@ osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr)
|
||||
return;
|
||||
}
|
||||
|
||||
/* FIXME: send only to current vlr_number and sgsn_number */
|
||||
|
||||
llist_for_each_entry(co, &g_hlr->gs->clients, list) {
|
||||
struct osmo_gsup_message gsup = { };
|
||||
uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
|
||||
@@ -222,145 +235,111 @@ static int subscr_create_on_demand(const char *imsi)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients.
|
||||
* \param[in,out] hlr Global hlr context.
|
||||
* \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call.
|
||||
* \param[in] nam_val True to enable CS/PS, false to disable.
|
||||
* \param[in] is_ps True to enable/disable PS, false for CS.
|
||||
* \returns 0 on success, ENOEXEC if there is no need to change, a negative
|
||||
* value on error.
|
||||
*/
|
||||
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps)
|
||||
{
|
||||
int rc;
|
||||
bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs;
|
||||
struct osmo_ipa_name vlr_name;
|
||||
struct osmo_gsup_message gsup_del_data = {
|
||||
.message_type = OSMO_GSUP_MSGT_DELETE_DATA_REQUEST,
|
||||
};
|
||||
OSMO_STRLCPY_ARRAY(gsup_del_data.imsi, subscr->imsi);
|
||||
|
||||
if (is_val == nam_val) {
|
||||
LOGP(DAUC, LOGL_DEBUG, "IMSI-%s: Already has the requested value when asked to %s %s\n",
|
||||
subscr->imsi, nam_val ? "enable" : "disable", is_ps ? "PS" : "CS");
|
||||
return ENOEXEC;
|
||||
}
|
||||
|
||||
rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps);
|
||||
if (rc)
|
||||
return rc > 0? -rc : rc;
|
||||
|
||||
/* If we're disabling, send a notice out to the GSUP client that is
|
||||
* responsible. Otherwise no need. */
|
||||
if (nam_val)
|
||||
return 0;
|
||||
|
||||
if (subscr->vlr_number && 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;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* Send Auth Info handling
|
||||
***********************************************************************/
|
||||
|
||||
/* process an incoming SAI request */
|
||||
static int rx_send_auth_info(struct osmo_gsup_conn *conn,
|
||||
const struct osmo_gsup_message *gsup,
|
||||
struct db_context *dbc)
|
||||
static int rx_send_auth_info(struct osmo_gsup_req *req)
|
||||
{
|
||||
struct osmo_gsup_message gsup_out;
|
||||
struct msgb *msg_out;
|
||||
struct osmo_gsup_message gsup_out = {
|
||||
.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT,
|
||||
};
|
||||
bool separation_bit = false;
|
||||
int num_auth_vectors = OSMO_GSUP_MAX_NUM_AUTH_INFO;
|
||||
unsigned int auc_3g_ind;
|
||||
int rc;
|
||||
|
||||
subscr_create_on_demand(gsup->imsi);
|
||||
subscr_create_on_demand(req->gsup.imsi);
|
||||
|
||||
/* initialize return message structure */
|
||||
memset(&gsup_out, 0, sizeof(gsup_out));
|
||||
memcpy(&gsup_out.imsi, &gsup->imsi, sizeof(gsup_out.imsi));
|
||||
|
||||
if (gsup->current_rat_type == OSMO_RAT_EUTRAN_SGS)
|
||||
if (req->gsup.current_rat_type == OSMO_RAT_EUTRAN_SGS)
|
||||
separation_bit = true;
|
||||
|
||||
if (gsup->num_auth_vectors > 0 &&
|
||||
gsup->num_auth_vectors <= OSMO_GSUP_MAX_NUM_AUTH_INFO)
|
||||
num_auth_vectors = gsup->num_auth_vectors;
|
||||
if (req->gsup.num_auth_vectors > 0 &&
|
||||
req->gsup.num_auth_vectors <= OSMO_GSUP_MAX_NUM_AUTH_INFO)
|
||||
num_auth_vectors = req->gsup.num_auth_vectors;
|
||||
rc = db_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(dbc, gsup->imsi, conn->auc_3g_ind,
|
||||
rc = db_get_auc(g_hlr->dbc, req->gsup.imsi, auc_3g_ind,
|
||||
gsup_out.auth_vectors,
|
||||
num_auth_vectors,
|
||||
gsup->rand, gsup->auts, separation_bit);
|
||||
req->gsup.rand, req->gsup.auts, separation_bit);
|
||||
|
||||
if (rc <= 0) {
|
||||
gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR;
|
||||
switch (rc) {
|
||||
case 0:
|
||||
/* 0 means "0 tuples generated", which shouldn't happen.
|
||||
* Treat the same as "no auth data". */
|
||||
case -ENOKEY:
|
||||
LOGP(DAUC, LOGL_NOTICE, "%s: IMSI known, but has no auth data;"
|
||||
" Returning slightly inaccurate cause 'IMSI Unknown' via GSUP\n",
|
||||
gsup->imsi);
|
||||
gsup_out.cause = GMM_CAUSE_IMSI_UNKNOWN;
|
||||
break;
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN,
|
||||
"IMSI known, but has no auth data;"
|
||||
" Returning slightly inaccurate cause 'IMSI Unknown' via GSUP");
|
||||
return rc;
|
||||
case -ENOENT:
|
||||
LOGP(DAUC, LOGL_NOTICE, "%s: IMSI not known\n", gsup->imsi);
|
||||
gsup_out.cause = GMM_CAUSE_IMSI_UNKNOWN;
|
||||
break;
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "IMSI unknown");
|
||||
return rc;
|
||||
default:
|
||||
LOGP(DAUC, LOGL_ERROR, "%s: failure to look up IMSI in db\n", gsup->imsi);
|
||||
gsup_out.cause = GMM_CAUSE_NET_FAIL;
|
||||
break;
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "failure to look up IMSI in db");
|
||||
return rc;
|
||||
}
|
||||
} else {
|
||||
gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT;
|
||||
gsup_out.num_auth_vectors = rc;
|
||||
}
|
||||
gsup_out.num_auth_vectors = rc;
|
||||
|
||||
msg_out = osmo_gsup_msgb_alloc("GSUP AUC response");
|
||||
osmo_gsup_encode(msg_out, &gsup_out);
|
||||
return osmo_gsup_conn_send(conn, msg_out);
|
||||
osmo_gsup_req_respond(req, &gsup_out, false, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* LU Operation State / Structure
|
||||
***********************************************************************/
|
||||
|
||||
static LLIST_HEAD(g_lu_ops);
|
||||
|
||||
/*! Receive Cancel Location Result from old VLR/SGSN */
|
||||
void lu_op_rx_cancel_old_ack(struct lu_operation *luop,
|
||||
const struct osmo_gsup_message *gsup)
|
||||
/*! Receive Update Location Request, creates new lu_operation */
|
||||
static int rx_upd_loc_req(struct osmo_gsup_conn *conn, struct osmo_gsup_req *req)
|
||||
{
|
||||
OSMO_ASSERT(luop->state == LU_S_CANCEL_SENT);
|
||||
/* FIXME: Check for spoofing */
|
||||
|
||||
osmo_timer_del(&luop->timer);
|
||||
|
||||
/* FIXME */
|
||||
|
||||
lu_op_tx_insert_subscr_data(luop);
|
||||
}
|
||||
|
||||
/*! Receive Insert Subscriber Data Result from new VLR/SGSN */
|
||||
static void lu_op_rx_insert_subscr_data_ack(struct lu_operation *luop,
|
||||
const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
OSMO_ASSERT(luop->state == LU_S_ISD_SENT);
|
||||
/* FIXME: Check for spoofing */
|
||||
|
||||
osmo_timer_del(&luop->timer);
|
||||
|
||||
/* Subscriber_Present_HLR */
|
||||
/* CS only: Check_SS_required? -> MAP-FW-CHECK_SS_IND.req */
|
||||
|
||||
/* Send final ACK towards inquiring VLR/SGSN */
|
||||
lu_op_tx_ack(luop);
|
||||
}
|
||||
|
||||
/*! Receive GSUP message for given \ref lu_operation */
|
||||
void lu_op_rx_gsup(struct lu_operation *luop,
|
||||
const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
switch (gsup->message_type) {
|
||||
case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
|
||||
/* FIXME */
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
|
||||
lu_op_rx_insert_subscr_data_ack(luop, gsup);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR:
|
||||
/* FIXME */
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT:
|
||||
lu_op_rx_cancel_old_ack(luop, gsup);
|
||||
break;
|
||||
default:
|
||||
LOGP(DMAIN, LOGL_ERROR, "Unhandled GSUP msg_type 0x%02x\n",
|
||||
gsup->message_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Receive Update Location Request, creates new \ref lu_operation */
|
||||
static int rx_upd_loc_req(struct osmo_gsup_conn *conn,
|
||||
const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
struct hlr_subscriber *subscr;
|
||||
struct lu_operation *luop = lu_op_alloc_conn(conn);
|
||||
if (!luop) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "LU REQ from conn without addr?\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
subscr = &luop->subscr;
|
||||
|
||||
lu_op_statechg(luop, LU_S_LU_RECEIVED);
|
||||
|
||||
switch (gsup->cn_domain) {
|
||||
switch (req->gsup.cn_domain) {
|
||||
case OSMO_GSUP_CN_DOMAIN_CS:
|
||||
conn->supports_cs = true;
|
||||
break;
|
||||
@@ -371,143 +350,64 @@ static int rx_upd_loc_req(struct osmo_gsup_conn *conn,
|
||||
* a request, the PS Domain is assumed." */
|
||||
case OSMO_GSUP_CN_DOMAIN_PS:
|
||||
conn->supports_ps = true;
|
||||
luop->is_ps = true;
|
||||
break;
|
||||
}
|
||||
llist_add(&luop->list, &g_lu_ops);
|
||||
|
||||
subscr_create_on_demand(gsup->imsi);
|
||||
|
||||
/* Roughly follwing "Process Update_Location_HLR" of TS 09.02 */
|
||||
|
||||
/* check if subscriber is known at all */
|
||||
if (!lu_op_fill_subscr(luop, g_hlr->dbc, gsup->imsi)) {
|
||||
/* Send Error back: Subscriber Unknown in HLR */
|
||||
osmo_strlcpy(luop->subscr.imsi, gsup->imsi, sizeof(luop->subscr.imsi));
|
||||
lu_op_tx_error(luop, GMM_CAUSE_IMSI_UNKNOWN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check if subscriber is generally permitted on CS or PS
|
||||
* service (as requested) */
|
||||
if (!luop->is_ps && !luop->subscr.nam_cs) {
|
||||
lu_op_tx_error(luop, GMM_CAUSE_PLMN_NOTALLOWED);
|
||||
return 0;
|
||||
} else if (luop->is_ps && !luop->subscr.nam_ps) {
|
||||
lu_op_tx_error(luop, GMM_CAUSE_GPRS_NOTALLOWED);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO: Set subscriber tracing = deactive in VLR/SGSN */
|
||||
|
||||
#if 0
|
||||
/* Cancel in old VLR/SGSN, if new VLR/SGSN differs from old (FIXME: OS#4491) */
|
||||
if (luop->is_ps == false &&
|
||||
strcmp(subscr->vlr_number, vlr_number)) {
|
||||
lu_op_tx_cancel_old(luop);
|
||||
} else if (luop->is_ps == true &&
|
||||
strcmp(subscr->sgsn_number, sgsn_number)) {
|
||||
lu_op_tx_cancel_old(luop);
|
||||
} else
|
||||
#endif
|
||||
|
||||
/* Store the VLR / SGSN number with the subscriber, so we know where it was last seen. */
|
||||
LOGP(DAUC, LOGL_DEBUG, "IMSI='%s': storing %s = %s\n",
|
||||
subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number",
|
||||
osmo_quote_str((const char*)luop->peer, -1));
|
||||
if (db_subscr_lu(g_hlr->dbc, subscr->id, (const char *)luop->peer, luop->is_ps))
|
||||
LOGP(DAUC, LOGL_ERROR, "IMSI='%s': Cannot update %s in the database\n",
|
||||
subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number");
|
||||
|
||||
/* TODO: Subscriber allowed to roam in PLMN? */
|
||||
/* TODO: Update RoutingInfo */
|
||||
/* TODO: Reset Flag MS Purged (cs/ps) */
|
||||
/* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */
|
||||
lu_op_tx_insert_subscr_data(luop);
|
||||
subscr_create_on_demand(req->gsup.imsi);
|
||||
|
||||
lu_rx_gsup(req);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rx_purge_ms_req(struct osmo_gsup_conn *conn,
|
||||
const struct osmo_gsup_message *gsup)
|
||||
static int rx_purge_ms_req(struct osmo_gsup_req *req)
|
||||
{
|
||||
struct osmo_gsup_message gsup_reply = {0};
|
||||
struct msgb *msg_out;
|
||||
bool is_ps = false;
|
||||
bool is_ps = (req->gsup.cn_domain != OSMO_GSUP_CN_DOMAIN_CS);
|
||||
int rc;
|
||||
|
||||
LOGP(DAUC, LOGL_INFO, "%s: Purge MS (%s)\n", gsup->imsi,
|
||||
is_ps ? "PS" : "CS");
|
||||
|
||||
memcpy(gsup_reply.imsi, gsup->imsi, sizeof(gsup_reply.imsi));
|
||||
|
||||
if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS)
|
||||
is_ps = true;
|
||||
LOG_GSUP_REQ_CAT(req, DAUC, LOGL_INFO, "Purge MS (%s)\n", is_ps ? "PS" : "CS");
|
||||
|
||||
/* FIXME: check if the VLR that sends the purge is the same that
|
||||
* we have on record. Only update if yes */
|
||||
|
||||
/* Perform the actual update of the DB */
|
||||
rc = db_subscr_purge(g_hlr->dbc, gsup->imsi, true, is_ps);
|
||||
rc = db_subscr_purge(g_hlr->dbc, req->gsup.imsi, true, is_ps);
|
||||
|
||||
if (rc == 0)
|
||||
gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_RESULT;
|
||||
else if (rc == -ENOENT) {
|
||||
gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR;
|
||||
gsup_reply.cause = GMM_CAUSE_IMSI_UNKNOWN;
|
||||
} else {
|
||||
gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR;
|
||||
gsup_reply.cause = GMM_CAUSE_NET_FAIL;
|
||||
}
|
||||
|
||||
msg_out = osmo_gsup_msgb_alloc("GSUP AUC response");
|
||||
osmo_gsup_encode(msg_out, &gsup_reply);
|
||||
return osmo_gsup_conn_send(conn, msg_out);
|
||||
osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_PURGE_MS_RESULT, true);
|
||||
else if (rc == -ENOENT)
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "IMSI unknown");
|
||||
else
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "db error");
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int gsup_send_err_reply(struct osmo_gsup_conn *conn, const char *imsi,
|
||||
enum osmo_gsup_message_type type_in, uint8_t err_cause)
|
||||
static int rx_check_imei_req(struct osmo_gsup_req *req)
|
||||
{
|
||||
int type_err = OSMO_GSUP_TO_MSGT_ERROR(type_in);
|
||||
struct osmo_gsup_message gsup_reply = {0};
|
||||
struct msgb *msg_out;
|
||||
|
||||
OSMO_STRLCPY_ARRAY(gsup_reply.imsi, imsi);
|
||||
gsup_reply.message_type = type_err;
|
||||
gsup_reply.cause = err_cause;
|
||||
msg_out = osmo_gsup_msgb_alloc("GSUP ERR response");
|
||||
osmo_gsup_encode(msg_out, &gsup_reply);
|
||||
LOGP(DMAIN, LOGL_NOTICE, "Tx %s\n", osmo_gsup_message_type_name(type_err));
|
||||
return osmo_gsup_conn_send(conn, msg_out);
|
||||
}
|
||||
|
||||
static int rx_check_imei_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
struct osmo_gsup_message gsup_reply = {0};
|
||||
struct msgb *msg_out;
|
||||
struct osmo_gsup_message gsup_reply;
|
||||
char imei[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1] = {0};
|
||||
const struct osmo_gsup_message *gsup = &req->gsup;
|
||||
int rc;
|
||||
|
||||
/* Require IMEI */
|
||||
if (!gsup->imei_enc) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "%s: missing IMEI\n", gsup->imsi);
|
||||
gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "missing IMEI");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Decode IMEI (fails if IMEI is too long) */
|
||||
rc = gsm48_decode_bcd_number2(imei, sizeof(imei), gsup->imei_enc, gsup->imei_enc_len, 0);
|
||||
if (rc < 0) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "%s: failed to decode IMEI (rc: %i)\n", gsup->imsi, rc);
|
||||
gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
|
||||
"failed to decode IMEI %s (rc: %d)",
|
||||
osmo_hexdump_c(OTC_SELECT, gsup->imei_enc, gsup->imei_enc_len),
|
||||
rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check if IMEI is too short */
|
||||
if (strlen(imei) != GSM23003_IMEI_NUM_DIGITS_NO_CHK) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "%s: wrong encoded IMEI length (IMEI: '%s', %lu, %i)\n", gsup->imsi, imei,
|
||||
strlen(imei), GSM23003_IMEI_NUM_DIGITS_NO_CHK);
|
||||
gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
|
||||
if (!osmo_imei_str_valid(imei, false)) {
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
|
||||
"invalid IMEI: %s", osmo_quote_str_c(OTC_SELECT, imei, -1));
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -517,7 +417,7 @@ static int rx_check_imei_req(struct osmo_gsup_conn *conn, const struct osmo_gsup
|
||||
if (g_hlr->store_imei) {
|
||||
LOGP(DAUC, LOGL_DEBUG, "IMSI='%s': storing IMEI = %s\n", gsup->imsi, imei);
|
||||
if (db_subscr_update_imei_by_imsi(g_hlr->dbc, gsup->imsi, imei) < 0) {
|
||||
gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "Failed to store IMEI in HLR db");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
@@ -525,18 +425,17 @@ static int rx_check_imei_req(struct osmo_gsup_conn *conn, const struct osmo_gsup
|
||||
LOGP(DMAIN, LOGL_INFO, "IMSI='%s': has IMEI = %s (consider setting 'store-imei')\n", gsup->imsi, imei);
|
||||
struct hlr_subscriber subscr;
|
||||
if (db_subscr_get_by_imsi(g_hlr->dbc, gsup->imsi, &subscr) < 0) {
|
||||
gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "IMSI unknown");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Accept all IMEIs */
|
||||
gsup_reply.imei_result = OSMO_GSUP_IMEI_RESULT_ACK;
|
||||
gsup_reply.message_type = OSMO_GSUP_MSGT_CHECK_IMEI_RESULT;
|
||||
msg_out = osmo_gsup_msgb_alloc("GSUP Check_IMEI response");
|
||||
memcpy(gsup_reply.imsi, gsup->imsi, sizeof(gsup_reply.imsi));
|
||||
osmo_gsup_encode(msg_out, &gsup_reply);
|
||||
return osmo_gsup_conn_send(conn, msg_out);
|
||||
gsup_reply = (struct osmo_gsup_message){
|
||||
.message_type = OSMO_GSUP_MSGT_CHECK_IMEI_RESULT,
|
||||
.imei_result = OSMO_GSUP_IMEI_RESULT_ACK,
|
||||
};
|
||||
return osmo_gsup_req_respond(req, &gsup_reply, false, true);
|
||||
}
|
||||
|
||||
static char namebuf[255];
|
||||
@@ -549,151 +448,122 @@ static char namebuf[255];
|
||||
osmo_quote_str_buf2(namebuf, sizeof(namebuf), (const char *)(gsup)->destination_name, (gsup)->destination_name_len), \
|
||||
## args)
|
||||
|
||||
static int read_cb_forward(struct osmo_gsup_conn *conn, struct msgb *msg, const struct osmo_gsup_message *gsup)
|
||||
static int read_cb_forward(struct osmo_gsup_req *req)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
struct osmo_gsup_message *gsup_err;
|
||||
|
||||
/* FIXME: it would be better if the msgb never were deallocated immediately by osmo_gsup_addr_send(), which a
|
||||
* select-loop volatile talloc context could facilitate. Then we would still be able to access gsup-> members
|
||||
* (pointing into the msgb) even after sending failed, and we wouldn't need to copy this data before sending: */
|
||||
/* Prepare error message (before IEs get deallocated) */
|
||||
gsup_err = talloc_zero(hlr_ctx, struct osmo_gsup_message);
|
||||
OSMO_STRLCPY_ARRAY(gsup_err->imsi, gsup->imsi);
|
||||
gsup_err->message_class = gsup->message_class;
|
||||
gsup_err->destination_name = talloc_memdup(gsup_err, gsup->destination_name, gsup->destination_name_len);
|
||||
gsup_err->destination_name_len = gsup->destination_name_len;
|
||||
gsup_err->message_type = gsup->message_type;
|
||||
gsup_err->session_state = gsup->session_state;
|
||||
gsup_err->session_id = gsup->session_id;
|
||||
gsup_err->source_name = talloc_memdup(gsup_err, gsup->source_name, gsup->source_name_len);
|
||||
gsup_err->source_name_len = gsup->source_name_len;
|
||||
const struct osmo_gsup_message *gsup = &req->gsup;
|
||||
struct osmo_gsup_message gsup_err;
|
||||
struct msgb *forward_msg;
|
||||
struct osmo_ipa_name destination_name;
|
||||
|
||||
/* Check for routing IEs */
|
||||
if (!gsup->source_name || !gsup->source_name_len || !gsup->destination_name || !gsup->destination_name_len) {
|
||||
LOGP_GSUP_FWD(gsup, LOGL_ERROR, "missing routing IEs\n");
|
||||
goto end;
|
||||
if (!req->gsup.source_name || !req->gsup.source_name_len
|
||||
|| !req->gsup.destination_name || !req->gsup.destination_name_len) {
|
||||
LOGP_GSUP_FWD(&req->gsup, LOGL_ERROR, "missing routing IEs\n");
|
||||
goto routing_error;
|
||||
}
|
||||
|
||||
/* Verify source name (e.g. "MSC-00-00-00-00-00-00") */
|
||||
if (gsup_route_find(conn->server, gsup->source_name, gsup->source_name_len) != conn) {
|
||||
LOGP_GSUP_FWD(gsup, LOGL_ERROR, "mismatching source name\n");
|
||||
goto end;
|
||||
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;
|
||||
}
|
||||
|
||||
/* Forward message without re-encoding (so we don't remove unknown IEs) */
|
||||
LOGP_GSUP_FWD(gsup, LOGL_INFO, "checks passed, forwarding\n");
|
||||
LOG_GSUP_REQ(req, LOGL_INFO, "Forwarding to %s\n", osmo_ipa_name_to_str(&destination_name));
|
||||
|
||||
/* Remove incoming IPA header to be able to prepend an outgoing IPA header */
|
||||
msgb_pull_to_l2(msg);
|
||||
ret = osmo_gsup_addr_send(g_hlr->gs, gsup->destination_name, gsup->destination_name_len, msg);
|
||||
/* AT THIS POINT, THE msg MAY BE DEALLOCATED and the data like gsup->imsi, gsup->source_name etc may all be
|
||||
* invalid and cause segfaults. */
|
||||
msg = NULL;
|
||||
gsup = NULL;
|
||||
if (ret == -ENODEV)
|
||||
LOGP_GSUP_FWD(gsup_err, LOGL_ERROR, "destination not connected\n");
|
||||
else if (ret)
|
||||
LOGP_GSUP_FWD(gsup_err, LOGL_ERROR, "unknown error %i\n", ret);
|
||||
|
||||
end:
|
||||
/* Send error back to source */
|
||||
/* Forward message without re-encoding (so we don't remove unknown IEs).
|
||||
* Copy GSUP part to forward, removing incoming IPA header to be able to prepend an outgoing IPA header */
|
||||
forward_msg = osmo_gsup_msgb_alloc("GSUP forward");
|
||||
forward_msg->l2h = msgb_put(forward_msg, msgb_l2len(req->msg));
|
||||
memcpy(forward_msg->l2h, msgb_l2(req->msg), msgb_l2len(req->msg));
|
||||
ret = osmo_gsup_send_to_ipa_name(g_hlr->gs, &destination_name, forward_msg);
|
||||
if (ret) {
|
||||
struct msgb *msg_err = osmo_gsup_msgb_alloc("GSUP forward ERR response");
|
||||
gsup_err->message_type = OSMO_GSUP_MSGT_E_ROUTING_ERROR;
|
||||
osmo_gsup_encode(msg_err, gsup_err);
|
||||
LOGP_GSUP_FWD(gsup_err, LOGL_NOTICE, "Tx %s\n", osmo_gsup_message_type_name(gsup_err->message_type));
|
||||
osmo_gsup_conn_send(conn, msg_err);
|
||||
LOGP_GSUP_FWD(gsup, LOGL_ERROR, "%s (rc=%d)\n",
|
||||
ret == -ENODEV ? "destination not connected" : "unknown error",
|
||||
ret);
|
||||
goto routing_error;
|
||||
}
|
||||
talloc_free(gsup_err);
|
||||
if (msg)
|
||||
msgb_free(msg);
|
||||
return ret;
|
||||
osmo_gsup_req_free(req);
|
||||
return 0;
|
||||
|
||||
routing_error:
|
||||
gsup_err = (struct osmo_gsup_message){
|
||||
.message_type = OSMO_GSUP_MSGT_ROUTING_ERROR,
|
||||
.source_name = gsup->destination_name,
|
||||
.source_name_len = gsup->destination_name_len,
|
||||
};
|
||||
osmo_gsup_req_respond(req, &gsup_err, true, true);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
|
||||
{
|
||||
static struct osmo_gsup_message gsup;
|
||||
int rc;
|
||||
|
||||
if (!msgb_l2(msg) || !msgb_l2len(msg)) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "missing or empty L2 data\n");
|
||||
msgb_free(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 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);
|
||||
}
|
||||
}
|
||||
|
||||
rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
|
||||
if (rc < 0) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "error in GSUP decode: %d\n", rc);
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
/* Distributed GSM: check whether to proxy for / lookup a remote HLR.
|
||||
* It would require less database hits to do this only if a local-only operation fails with "unknown IMSI", but
|
||||
* it becomes semantically easier if we do this once-off ahead of time. */
|
||||
if (osmo_mslookup_client_active(g_hlr->mslookup.client.client)
|
||||
|| osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy)) {
|
||||
if (dgsm_check_forward_gsup_msg(req))
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 3GPP TS 23.003 Section 2.2 clearly states that an IMSI with less than 5
|
||||
* digits is impossible. Even 5 digits is a highly theoretical case */
|
||||
if (strlen(gsup.imsi) < 5) { /* TODO: move this check to libosmogsm/gsup.c? */
|
||||
LOGP(DMAIN, LOGL_ERROR, "IMSI too short: %s\n", osmo_quote_str(gsup.imsi, -1));
|
||||
gsup_send_err_reply(conn, gsup.imsi, gsup.message_type, GMM_CAUSE_INV_MAND_INFO);
|
||||
msgb_free(msg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (gsup.destination_name_len)
|
||||
return read_cb_forward(conn, msg, &gsup);
|
||||
|
||||
switch (gsup.message_type) {
|
||||
/* HLR related messages that are handled at this HLR instance */
|
||||
switch (req->gsup.message_type) {
|
||||
/* requests sent to us */
|
||||
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST:
|
||||
rx_send_auth_info(conn, &gsup, g_hlr->dbc);
|
||||
rx_send_auth_info(req);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST:
|
||||
rx_upd_loc_req(conn, &gsup);
|
||||
rx_upd_loc_req(conn, req);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_PURGE_MS_REQUEST:
|
||||
rx_purge_ms_req(conn, &gsup);
|
||||
rx_purge_ms_req(req);
|
||||
break;
|
||||
/* responses to requests sent by us */
|
||||
case OSMO_GSUP_MSGT_DELETE_DATA_ERROR:
|
||||
LOGP(DMAIN, LOGL_ERROR, "Error while deleting subscriber data "
|
||||
"for IMSI %s\n", gsup.imsi);
|
||||
LOG_GSUP_REQ(req, LOGL_ERROR, "Peer responds with: Error while deleting subscriber data\n");
|
||||
osmo_gsup_req_free(req);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_DELETE_DATA_RESULT:
|
||||
LOGP(DMAIN, LOGL_ERROR, "Deleting subscriber data for IMSI %s\n",
|
||||
gsup.imsi);
|
||||
LOG_GSUP_REQ(req, LOGL_DEBUG, "Peer responds with: Subscriber data deleted\n");
|
||||
osmo_gsup_req_free(req);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_PROC_SS_REQUEST:
|
||||
case OSMO_GSUP_MSGT_PROC_SS_RESULT:
|
||||
rx_proc_ss_req(conn, &gsup);
|
||||
rx_proc_ss_req(req);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_PROC_SS_ERROR:
|
||||
rx_proc_ss_error(conn, &gsup);
|
||||
rx_proc_ss_error(req);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
|
||||
case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
|
||||
case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR:
|
||||
case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT:
|
||||
{
|
||||
struct lu_operation *luop = lu_op_by_imsi(gsup.imsi,
|
||||
&g_lu_ops);
|
||||
if (!luop) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "GSUP message %s for "
|
||||
"unknown IMSI %s\n",
|
||||
osmo_gsup_message_type_name(gsup.message_type),
|
||||
gsup.imsi);
|
||||
break;
|
||||
}
|
||||
lu_op_rx_gsup(luop, &gsup);
|
||||
}
|
||||
lu_rx_gsup(req);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_CHECK_IMEI_REQUEST:
|
||||
rx_check_imei_req(conn, &gsup);
|
||||
rx_check_imei_req(req);
|
||||
break;
|
||||
default:
|
||||
LOGP(DMAIN, LOGL_DEBUG, "Unhandled GSUP message type %s\n",
|
||||
osmo_gsup_message_type_name(gsup.message_type));
|
||||
osmo_gsup_message_type_name(req->gsup.message_type));
|
||||
osmo_gsup_req_free(req);
|
||||
break;
|
||||
}
|
||||
msgb_free(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -848,7 +718,10 @@ int main(int argc, char **argv)
|
||||
INIT_LLIST_HEAD(&g_hlr->euse_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;
|
||||
@@ -859,11 +732,15 @@ int main(int argc, char **argv)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Set up llists and objects, startup is happening from VTY commands. */
|
||||
dgsm_init(hlr_ctx);
|
||||
|
||||
osmo_stats_init(hlr_ctx);
|
||||
vty_init(&vty_info);
|
||||
ctrl_vty_init(hlr_ctx);
|
||||
handle_options(argc, argv);
|
||||
hlr_vty_init();
|
||||
dgsm_vty_init();
|
||||
|
||||
rc = vty_read_config_file(cmdline_opts.config_file, NULL);
|
||||
if (rc < 0) {
|
||||
@@ -908,15 +785,18 @@ int main(int argc, char **argv)
|
||||
|
||||
|
||||
g_hlr->gs = osmo_gsup_server_create(hlr_ctx, g_hlr->gsup_bind_addr, OSMO_GSUP_PORT,
|
||||
read_cb, &g_lu_ops, g_hlr);
|
||||
read_cb, g_hlr);
|
||||
if (!g_hlr->gs) {
|
||||
LOGP(DMAIN, LOGL_FATAL, "Error starting GSUP server\n");
|
||||
exit(1);
|
||||
}
|
||||
proxy_init(g_hlr->gs);
|
||||
|
||||
g_hlr->ctrl_bind_addr = ctrl_vty_get_bind_addr();
|
||||
g_hlr->ctrl = hlr_controlif_setup(g_hlr);
|
||||
|
||||
dgsm_start(hlr_ctx);
|
||||
|
||||
osmo_init_ignore_signals();
|
||||
signal(SIGINT, &signal_hdlr);
|
||||
signal(SIGTERM, &signal_hdlr);
|
||||
@@ -931,7 +811,9 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
while (!quit)
|
||||
osmo_select_main(0);
|
||||
osmo_select_main_ctx(0);
|
||||
|
||||
dgsm_stop();
|
||||
|
||||
osmo_gsup_server_destroy(g_hlr->gs);
|
||||
db_close(g_hlr->dbc);
|
||||
|
214
src/hlr_ussd.c
214
src/hlr_ussd.c
@@ -170,12 +170,14 @@ struct ss_session {
|
||||
/* subscriber's vlr_number
|
||||
* MO USSD: originating MSC's vlr_number
|
||||
* MT USSD: looked up once per session and cached here */
|
||||
uint8_t *vlr_number;
|
||||
size_t vlr_number_len;
|
||||
struct osmo_ipa_name vlr_name;
|
||||
|
||||
/* we don't keep a pointer to the osmo_gsup_{route,conn} towards the MSC/VLR here,
|
||||
* as this might change during inter-VLR hand-over, and we simply look-up the serving MSC/VLR
|
||||
* every time we receive an USSD component from the EUSE */
|
||||
|
||||
struct osmo_gsup_req *initial_req_from_ms;
|
||||
struct osmo_gsup_req *initial_req_from_euse;
|
||||
};
|
||||
|
||||
struct ss_session *ss_session_find(struct hlr *hlr, const char *imsi, uint32_t session_id)
|
||||
@@ -191,6 +193,10 @@ struct ss_session *ss_session_find(struct hlr *hlr, const char *imsi, uint32_t s
|
||||
void ss_session_free(struct ss_session *ss)
|
||||
{
|
||||
osmo_timer_del(&ss->timeout);
|
||||
if (ss->initial_req_from_ms)
|
||||
osmo_gsup_req_free(ss->initial_req_from_ms);
|
||||
if (ss->initial_req_from_euse)
|
||||
osmo_gsup_req_free(ss->initial_req_from_euse);
|
||||
llist_del(&ss->list);
|
||||
talloc_free(ss);
|
||||
}
|
||||
@@ -230,32 +236,46 @@ struct ss_session *ss_session_alloc(struct hlr *hlr, const char *imsi, uint32_t
|
||||
***********************************************************************/
|
||||
|
||||
/* Resolve the target MSC by ss->imsi and send GSUP message. */
|
||||
static int ss_gsup_send(struct ss_session *ss, struct osmo_gsup_server *gs, struct msgb *msg)
|
||||
static int ss_gsup_send_to_ms(struct ss_session *ss, struct osmo_gsup_server *gs, struct osmo_gsup_message *gsup)
|
||||
{
|
||||
struct hlr_subscriber subscr = {};
|
||||
struct msgb *msg;
|
||||
int rc;
|
||||
|
||||
if (ss->initial_req_from_ms) {
|
||||
/* Use non-final osmo_gsup_req_respond() to not deallocate the ss->initial_req_from_ms */
|
||||
osmo_gsup_req_respond(ss->initial_req_from_ms, gsup, false, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
msg = osmo_gsup_msgb_alloc("GSUP USSD FW");
|
||||
rc = osmo_gsup_encode(msg, gsup);
|
||||
if (rc) {
|
||||
LOGPSS(ss, LOGL_ERROR, "Failed to encode GSUP message\n");
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Use vlr_number as looked up by the caller, or look up now. */
|
||||
if (!ss->vlr_number) {
|
||||
if (!ss->vlr_name.len) {
|
||||
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
|
||||
if (rc < 0) {
|
||||
LOGPSS(ss, LOGL_ERROR, "Cannot find subscriber, cannot route GSUP message\n");
|
||||
msgb_free(msg);
|
||||
return -EINVAL;
|
||||
}
|
||||
ss->vlr_number = (uint8_t *)talloc_strdup(ss, subscr.vlr_number);
|
||||
ss->vlr_number_len = strlen(subscr.vlr_number) + 1;
|
||||
osmo_ipa_name_set_str(&ss->vlr_name, subscr.vlr_number);
|
||||
}
|
||||
|
||||
/* Check for empty string (all vlr_number strings end in "\0", because otherwise gsup_route_find() fails) */
|
||||
if (ss->vlr_number_len == 1) {
|
||||
if (ss->vlr_name.len <= 1) {
|
||||
LOGPSS(ss, LOGL_ERROR, "Cannot send GSUP message, no VLR number stored for subscriber\n");
|
||||
msgb_free(msg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOGPSS(ss, LOGL_DEBUG, "Tx SS/USSD to VLR %s\n", osmo_quote_str((char *)ss->vlr_number, ss->vlr_number_len));
|
||||
return osmo_gsup_addr_send(gs, ss->vlr_number, ss->vlr_number_len, 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,
|
||||
@@ -263,7 +283,7 @@ static int ss_tx_to_ms(struct ss_session *ss, enum osmo_gsup_message_type gsup_m
|
||||
|
||||
{
|
||||
struct osmo_gsup_message resp = {0};
|
||||
struct msgb *resp_msg;
|
||||
int rc;
|
||||
|
||||
resp.message_type = gsup_msg_type;
|
||||
OSMO_STRLCPY_ARRAY(resp.imsi, ss->imsi);
|
||||
@@ -277,12 +297,10 @@ static int ss_tx_to_ms(struct ss_session *ss, enum osmo_gsup_message_type gsup_m
|
||||
resp.ss_info_len = msgb_length(ss_msg);
|
||||
}
|
||||
|
||||
resp_msg = msgb_alloc_headroom(4000, 64, __func__);
|
||||
OSMO_ASSERT(resp_msg);
|
||||
osmo_gsup_encode(resp_msg, &resp);
|
||||
msgb_free(ss_msg);
|
||||
rc = ss_gsup_send_to_ms(ss, g_hlr->gs, &resp);
|
||||
|
||||
return ss_gsup_send(ss, g_hlr->gs, resp_msg);
|
||||
msgb_free(ss_msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#if 0
|
||||
@@ -297,7 +315,7 @@ static int ss_tx_reject(struct ss_session *ss, int invoke_id, uint8_t problem_ta
|
||||
}
|
||||
#endif
|
||||
|
||||
static int ss_tx_error(struct ss_session *ss, uint8_t invoke_id, uint8_t error_code)
|
||||
static int ss_tx_to_ms_error(struct ss_session *ss, uint8_t invoke_id, uint8_t error_code)
|
||||
{
|
||||
struct msgb *msg = gsm0480_gen_return_error(invoke_id, error_code);
|
||||
LOGPSS(ss, LOGL_NOTICE, "Tx ReturnError(%u, 0x%02x)\n", invoke_id, error_code);
|
||||
@@ -305,7 +323,7 @@ static int ss_tx_error(struct ss_session *ss, uint8_t invoke_id, uint8_t error_c
|
||||
return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, true, msg);
|
||||
}
|
||||
|
||||
static int ss_tx_ussd_7bit(struct ss_session *ss, bool final, uint8_t invoke_id, const char *text)
|
||||
static int ss_tx_to_ms_ussd_7bit(struct ss_session *ss, bool final, uint8_t invoke_id, const char *text)
|
||||
{
|
||||
struct msgb *msg = gsm0480_gen_ussd_resp_7bit(invoke_id, text);
|
||||
LOGPSS(ss, LOGL_INFO, "Tx USSD '%s'\n", text);
|
||||
@@ -319,7 +337,7 @@ static int ss_tx_ussd_7bit(struct ss_session *ss, bool final, uint8_t invoke_id,
|
||||
|
||||
#include <osmocom/hlr/db.h>
|
||||
|
||||
static int handle_ussd_own_msisdn(struct osmo_gsup_conn *conn, struct ss_session *ss,
|
||||
static int handle_ussd_own_msisdn(struct ss_session *ss,
|
||||
const struct osmo_gsup_message *gsup, const struct ss_request *req)
|
||||
{
|
||||
struct hlr_subscriber subscr;
|
||||
@@ -333,25 +351,25 @@ static int handle_ussd_own_msisdn(struct osmo_gsup_conn *conn, struct ss_session
|
||||
snprintf(buf, sizeof(buf), "You have no MSISDN!");
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "Your extension is %s", subscr.msisdn);
|
||||
ss_tx_ussd_7bit(ss, true, req->invoke_id, buf);
|
||||
ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id, buf);
|
||||
break;
|
||||
case -ENOENT:
|
||||
ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
|
||||
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
|
||||
break;
|
||||
case -EIO:
|
||||
default:
|
||||
ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
|
||||
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_ussd_own_imsi(struct osmo_gsup_conn *conn, struct ss_session *ss,
|
||||
static int handle_ussd_own_imsi(struct ss_session *ss,
|
||||
const struct osmo_gsup_message *gsup, const struct ss_request *req)
|
||||
{
|
||||
char buf[GSM0480_USSD_7BIT_STRING_LEN+1];
|
||||
snprintf(buf, sizeof(buf), "Your IMSI is %s", ss->imsi);
|
||||
ss_tx_ussd_7bit(ss, true, req->invoke_id, buf);
|
||||
ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id, buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -398,37 +416,28 @@ static bool ss_op_is_ussd(uint8_t opcode)
|
||||
}
|
||||
|
||||
/* is this GSUP connection an EUSE (true) or not (false)? */
|
||||
static bool conn_is_euse(struct osmo_gsup_conn *conn)
|
||||
static bool peer_name_is_euse(const struct osmo_gsup_peer_id *peer_name)
|
||||
{
|
||||
int rc;
|
||||
uint8_t *addr;
|
||||
|
||||
rc = osmo_gsup_conn_ccm_get(conn, &addr, IPAC_IDTAG_SERNR);
|
||||
if (rc <= 5)
|
||||
if (peer_name->type != OSMO_GSUP_PEER_ID_IPA_NAME)
|
||||
return false;
|
||||
if (!strncmp((char *)addr, "EUSE-", 5))
|
||||
return true;
|
||||
else
|
||||
if (peer_name->ipa_name.len <= 5)
|
||||
return false;
|
||||
return strncmp((char *)(peer_name->ipa_name.val), "EUSE-", 5) == 0;
|
||||
}
|
||||
|
||||
static struct hlr_euse *euse_by_conn(struct osmo_gsup_conn *conn)
|
||||
static struct hlr_euse *euse_by_name(const struct osmo_gsup_peer_id *peer_name)
|
||||
{
|
||||
int rc;
|
||||
char *addr;
|
||||
struct hlr *hlr = conn->server->priv;
|
||||
|
||||
rc = osmo_gsup_conn_ccm_get(conn, (uint8_t **) &addr, IPAC_IDTAG_SERNR);
|
||||
if (rc <= 5)
|
||||
return NULL;
|
||||
if (strncmp(addr, "EUSE-", 5))
|
||||
if (!peer_name_is_euse(peer_name))
|
||||
return NULL;
|
||||
|
||||
return euse_find(hlr, addr+5);
|
||||
/* above peer_name_is_euse() ensures this: */
|
||||
OSMO_ASSERT(peer_name->type == OSMO_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, const struct osmo_gsup_message *gsup,
|
||||
const struct ss_request *req)
|
||||
static int handle_ss(struct ss_session *ss, bool is_euse_originated, const struct osmo_gsup_message *gsup,
|
||||
const struct ss_request *req)
|
||||
{
|
||||
uint8_t comp_type = gsup->ss_info[0];
|
||||
|
||||
@@ -441,17 +450,16 @@ static int handle_ss(struct ss_session *ss, const struct osmo_gsup_message *gsup
|
||||
* we don't handle "structured" SS requests at all.
|
||||
*/
|
||||
LOGPSS(ss, LOGL_NOTICE, "Structured SS requests are not supported, rejecting...\n");
|
||||
ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_FACILITY_NOT_SUPPORTED);
|
||||
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_FACILITY_NOT_SUPPORTED);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/* Handle a USSD GSUP message for a given SS Session received from VLR or EUSE */
|
||||
static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss,
|
||||
const struct osmo_gsup_message *gsup, const struct ss_request *req)
|
||||
static int handle_ussd(struct ss_session *ss, bool is_euse_originated, const struct osmo_gsup_message *gsup,
|
||||
const struct ss_request *req)
|
||||
{
|
||||
uint8_t comp_type = gsup->ss_info[0];
|
||||
struct msgb *msg_out;
|
||||
bool is_euse_originated = conn_is_euse(conn);
|
||||
|
||||
LOGPSS(ss, LOGL_INFO, "USSD CompType=%s, OpCode=%s '%s'\n",
|
||||
gsm0480_comp_type_name(comp_type), gsm0480_op_code_name(req->opcode),
|
||||
@@ -459,26 +467,27 @@ static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss,
|
||||
|
||||
if ((ss->is_external && !ss->u.euse) || !ss->u.iuse) {
|
||||
LOGPSS(ss, LOGL_NOTICE, "USSD for unknown code '%s'\n", req->ussd_text);
|
||||
ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SS_NOT_AVAILABLE);
|
||||
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SS_NOT_AVAILABLE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (is_euse_originated) {
|
||||
msg_out = osmo_gsup_msgb_alloc("GSUP USSD FW");
|
||||
/* Received from EUSE, Forward to VLR */
|
||||
osmo_gsup_encode(msg_out, gsup);
|
||||
ss_gsup_send(ss, conn->server, msg_out);
|
||||
/* Need a non-const osmo_gsup_message, because sending might modify some (routing related?) parts. */
|
||||
struct osmo_gsup_message forward = *gsup;
|
||||
ss_gsup_send_to_ms(ss, g_hlr->gs, &forward);
|
||||
} else {
|
||||
/* Received from VLR (MS) */
|
||||
if (ss->is_external) {
|
||||
/* Forward to EUSE */
|
||||
char addr[128];
|
||||
strcpy(addr, "EUSE-");
|
||||
osmo_strlcpy(addr+5, ss->u.euse->name, sizeof(addr)-5);
|
||||
conn = gsup_route_find(conn->server, (uint8_t *)addr, strlen(addr)+1);
|
||||
struct osmo_ipa_name euse_name;
|
||||
struct osmo_gsup_conn *conn;
|
||||
osmo_ipa_name_set_str(&euse_name, "EUSE-%s", ss->u.euse->name);
|
||||
conn = gsup_route_find_by_ipa_name(g_hlr->gs, &euse_name);
|
||||
if (!conn) {
|
||||
LOGPSS(ss, LOGL_ERROR, "Cannot find conn for EUSE %s\n", addr);
|
||||
ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
|
||||
LOGPSS(ss, LOGL_ERROR, "Cannot find conn for EUSE %s\n",
|
||||
osmo_ipa_name_to_str(&euse_name));
|
||||
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
|
||||
} else {
|
||||
msg_out = osmo_gsup_msgb_alloc("GSUP USSD FW");
|
||||
osmo_gsup_encode(msg_out, gsup);
|
||||
@@ -486,7 +495,7 @@ static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss,
|
||||
}
|
||||
} else {
|
||||
/* Handle internally */
|
||||
ss->u.iuse->handle_ussd(conn, ss, gsup, req);
|
||||
ss->u.iuse->handle_ussd(ss, gsup, req);
|
||||
/* Release session immediately */
|
||||
ss_session_free(ss);
|
||||
}
|
||||
@@ -498,30 +507,43 @@ static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss,
|
||||
|
||||
/* this function is called for any SS_REQ/SS_RESP messages from both the MSC/VLR side as well
|
||||
* as from the EUSE side */
|
||||
int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
|
||||
void rx_proc_ss_req(struct osmo_gsup_req *gsup_req)
|
||||
{
|
||||
struct hlr *hlr = conn->server->priv;
|
||||
struct hlr *hlr = g_hlr;
|
||||
struct ss_session *ss;
|
||||
struct ss_request req = {0};
|
||||
struct gsup_route *gsup_rt;
|
||||
const struct osmo_gsup_message *gsup = &gsup_req->gsup;
|
||||
/* Remember whether this function should free the incoming gsup_req: if it is placed as ss->initial_req_from_*,
|
||||
* do not free it here. If not, free it here. */
|
||||
struct osmo_gsup_req *free_gsup_req = gsup_req;
|
||||
bool is_euse_originated = peer_name_is_euse(&gsup_req->source_name);
|
||||
|
||||
LOGP(DSS, LOGL_DEBUG, "%s/0x%08x: Process SS (%s)\n", gsup->imsi, gsup->session_id,
|
||||
osmo_gsup_session_state_name(gsup->session_state));
|
||||
|
||||
if (gsup_req->source_name.type != OSMO_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)) {
|
||||
LOGP(DSS, LOGL_ERROR, "%s/0x%082x: Unable to parse SS request: %s\n",
|
||||
gsup->imsi, gsup->session_id,
|
||||
osmo_hexdump(gsup->ss_info, gsup->ss_info_len));
|
||||
/* FIXME: Send a Reject component? */
|
||||
goto out_err;
|
||||
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "error parsing SS request");
|
||||
return;
|
||||
}
|
||||
} else if (gsup->session_state != OSMO_GSUP_SESSION_STATE_END) {
|
||||
LOGP(DSS, LOGL_ERROR, "%s/0x%082x: Missing SS payload for '%s'\n",
|
||||
gsup->imsi, gsup->session_id,
|
||||
osmo_gsup_session_state_name(gsup->session_state));
|
||||
goto out_err;
|
||||
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "missing SS payload");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (gsup->session_state) {
|
||||
@@ -530,32 +552,30 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
|
||||
if (ss_session_find(hlr, gsup->imsi, gsup->session_id)) {
|
||||
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: BEGIN with non-unique session ID!\n",
|
||||
gsup->imsi, gsup->session_id);
|
||||
goto out_err;
|
||||
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "BEGIN with non-unique session ID");
|
||||
return;
|
||||
}
|
||||
ss = ss_session_alloc(hlr, gsup->imsi, gsup->session_id);
|
||||
if (!ss) {
|
||||
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: Unable to allocate SS session\n",
|
||||
gsup->imsi, gsup->session_id);
|
||||
goto out_err;
|
||||
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_NET_FAIL, "Unable to allocate SS session");
|
||||
return;
|
||||
}
|
||||
/* Get IPA name from VLR conn and save as ss->vlr_number */
|
||||
if (!conn_is_euse(conn)) {
|
||||
gsup_rt = gsup_route_find_by_conn(conn);
|
||||
if (gsup_rt) {
|
||||
ss->vlr_number = (uint8_t *)talloc_strdup(ss, (const char *)gsup_rt->addr);
|
||||
ss->vlr_number_len = strlen((const char *)gsup_rt->addr) + 1;
|
||||
LOGPSS(ss, LOGL_DEBUG, "Destination IPA name retrieved from GSUP route: %s\n",
|
||||
osmo_quote_str((const char *)ss->vlr_number, ss->vlr_number_len));
|
||||
} else {
|
||||
LOGPSS(ss, LOGL_NOTICE, "Could not find GSUP route, therefore can't set the destination"
|
||||
" IPA name. We'll try to look it up later, but this should not"
|
||||
" have happened.\n");
|
||||
}
|
||||
if (!is_euse_originated) {
|
||||
ss->initial_req_from_ms = gsup_req;
|
||||
free_gsup_req = NULL;
|
||||
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;
|
||||
}
|
||||
if (ss_op_is_ussd(req.opcode)) {
|
||||
if (conn_is_euse(conn)) {
|
||||
if (is_euse_originated) {
|
||||
/* EUSE->VLR: MT USSD. EUSE is known ('conn'), VLR is to be resolved */
|
||||
ss->u.euse = euse_by_conn(conn);
|
||||
ss->u.euse = euse_by_name(&gsup_req->source_name);
|
||||
} else {
|
||||
/* VLR->EUSE: MO USSD. VLR is known ('conn'), EUSE is to be resolved */
|
||||
struct hlr_ussd_route *rt;
|
||||
@@ -576,10 +596,10 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
|
||||
}
|
||||
}
|
||||
/* dispatch unstructured SS to routing */
|
||||
handle_ussd(conn, ss, gsup, &req);
|
||||
handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req);
|
||||
} else {
|
||||
/* dispatch non-call SS to internal code */
|
||||
handle_ss(ss, gsup, &req);
|
||||
handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req);
|
||||
}
|
||||
break;
|
||||
case OSMO_GSUP_SESSION_STATE_CONTINUE:
|
||||
@@ -587,7 +607,8 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
|
||||
if (!ss) {
|
||||
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: CONTINUE for unknown SS session\n",
|
||||
gsup->imsi, gsup->session_id);
|
||||
goto out_err;
|
||||
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "CONTINUE for unknown SS session");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Reschedule self-destruction timer */
|
||||
@@ -596,10 +617,10 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
|
||||
|
||||
if (ss_op_is_ussd(req.opcode)) {
|
||||
/* dispatch unstructured SS to routing */
|
||||
handle_ussd(conn, ss, gsup, &req);
|
||||
handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req);
|
||||
} else {
|
||||
/* dispatch non-call SS to internal code */
|
||||
handle_ss(ss, gsup, &req);
|
||||
handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req);
|
||||
}
|
||||
break;
|
||||
case OSMO_GSUP_SESSION_STATE_END:
|
||||
@@ -607,17 +628,17 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
|
||||
if (!ss) {
|
||||
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: END for unknown SS session\n",
|
||||
gsup->imsi, gsup->session_id);
|
||||
goto out_err;
|
||||
return;
|
||||
}
|
||||
|
||||
/* SS payload is optional for END */
|
||||
if (gsup->ss_info && gsup->ss_info_len) {
|
||||
if (ss_op_is_ussd(req.opcode)) {
|
||||
/* dispatch unstructured SS to routing */
|
||||
handle_ussd(conn, ss, gsup, &req);
|
||||
handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req);
|
||||
} else {
|
||||
/* dispatch non-call SS to internal code */
|
||||
handle_ss(ss, gsup, &req);
|
||||
handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,18 +647,15 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
|
||||
default:
|
||||
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: Unknown SS State %d\n", gsup->imsi,
|
||||
gsup->session_id, gsup->session_state);
|
||||
goto out_err;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_err:
|
||||
return 0;
|
||||
if (free_gsup_req)
|
||||
osmo_gsup_req_free(free_gsup_req);
|
||||
}
|
||||
|
||||
int rx_proc_ss_error(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
|
||||
void rx_proc_ss_error(struct osmo_gsup_req *req)
|
||||
{
|
||||
LOGP(DSS, LOGL_NOTICE, "%s/0x%08x: Process SS ERROR (%s)\n", gsup->imsi, gsup->session_id,
|
||||
osmo_gsup_session_state_name(gsup->session_state));
|
||||
return 0;
|
||||
LOGP(DSS, LOGL_NOTICE, "%s/0x%08x: Process SS ERROR (%s)\n", req->gsup.imsi, req->gsup.session_id,
|
||||
osmo_gsup_session_state_name(req->gsup.session_state));
|
||||
}
|
||||
|
@@ -146,6 +146,24 @@ DEFUN(cfg_hlr_gsup_bind_ip,
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_hlr_gsup_ipa_name,
|
||||
cfg_hlr_gsup_ipa_name_cmd,
|
||||
"ipa-name NAME",
|
||||
"Set the IPA name of this HLR, for proxying to remote HLRs\n"
|
||||
"A globally unique name for this HLR. For example: PLMN + redundancy server number: HLR-901-70-0. "
|
||||
"This name is used for GSUP routing and must be set if multiple HLRs interconnect (e.g. mslookup "
|
||||
"for Distributed GSM).\n")
|
||||
{
|
||||
if (vty->type != VTY_FILE) {
|
||||
vty_out(vty, "gsup/ipa-name: The GSUP IPA name cannot be changed at run-time; "
|
||||
"It can only be set in the configuraton file.%s", VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
g_hlr->gsup_unit_name.serno = talloc_strdup(g_hlr, argv[0]);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* USSD Entity
|
||||
***********************************************************************/
|
||||
@@ -444,6 +462,7 @@ void hlr_vty_init(void)
|
||||
install_node(&gsup_node, config_write_hlr_gsup);
|
||||
|
||||
install_element(GSUP_NODE, &cfg_hlr_gsup_bind_ip_cmd);
|
||||
install_element(GSUP_NODE, &cfg_hlr_gsup_ipa_name_cmd);
|
||||
|
||||
install_element(HLR_NODE, &cfg_database_cmd);
|
||||
|
||||
|
@@ -30,30 +30,43 @@
|
||||
|
||||
#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);
|
||||
|
||||
/* Strip trailing newline. */
|
||||
p = strchr(s, '\n');
|
||||
if (p)
|
||||
*p = '\0';
|
||||
return s;
|
||||
struct tm tm;
|
||||
gmtime_r(t, &tm);
|
||||
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)
|
||||
{
|
||||
char datebuf[26]; /* for ctime_r(3) */
|
||||
uint32_t age;
|
||||
char datebuf[32];
|
||||
if (!last_lu_seen)
|
||||
return;
|
||||
vty_out(vty, " last LU seen on %s: %s UTC%s", domain_label, get_datestr(&last_lu_seen, datebuf),
|
||||
VTY_NEWLINE);
|
||||
vty_out(vty, " last LU seen on %s: %s", domain_label, get_datestr(&last_lu_seen, datebuf, sizeof(datebuf)));
|
||||
if (!timestamp_age(&last_lu_seen, &age))
|
||||
vty_out(vty, " (invalid timestamp)%s", VTY_NEWLINE);
|
||||
else {
|
||||
vty_out(vty, " (");
|
||||
#define UNIT_AGO(UNITNAME, UNITVAL) \
|
||||
if (age >= (UNITVAL)) { \
|
||||
vty_out(vty, "%u%s", age / (UNITVAL), UNITNAME); \
|
||||
age = age % (UNITVAL); \
|
||||
}
|
||||
UNIT_AGO("d", 60*60*24);
|
||||
UNIT_AGO("h", 60*60);
|
||||
UNIT_AGO("m", 60);
|
||||
UNIT_AGO("s", 1);
|
||||
vty_out(vty, " ago)%s", VTY_NEWLINE);
|
||||
#undef UNIT_AGO
|
||||
}
|
||||
}
|
||||
|
||||
static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
|
||||
|
@@ -31,6 +31,18 @@ const struct log_info_cat hlr_log_info_cat[] = {
|
||||
.color = "\033[1;35m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
[DLU] = {
|
||||
.name = "DLU",
|
||||
.description = "Location Updating",
|
||||
.color = "\033[1;33m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
[DDGSM] = {
|
||||
.name = "DDGSM",
|
||||
.description = "Distributed GSM: MS lookup and proxy",
|
||||
.color = "\033[1;35m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
};
|
||||
|
||||
const struct log_info hlr_log_info = {
|
||||
|
320
src/lu_fsm.c
Normal file
320
src/lu_fsm.c
Normal file
@@ -0,0 +1,320 @@
|
||||
/* Roughly following "Process Update_Location_HLR" of TS 09.02 */
|
||||
|
||||
/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/tdef.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
#include <osmocom/gsm/apn.h>
|
||||
#include <osmocom/gsm/gsm48_ie.h>
|
||||
|
||||
#include <osmocom/gsupclient/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 <osmocom/hlr/db.h>
|
||||
|
||||
#define LOG_LU(lu, level, fmt, args...) \
|
||||
LOGPFSML((lu)? (lu)->fi : NULL, level, fmt, ##args)
|
||||
|
||||
#define LOG_LU_REQ(lu, req, level, fmt, args...) \
|
||||
LOGPFSML((lu)? (lu)->fi : NULL, level, "%s:" fmt, \
|
||||
osmo_gsup_message_type_name((req)->gsup.message_type), ##args)
|
||||
|
||||
struct lu {
|
||||
struct llist_head entry;
|
||||
struct osmo_fsm_inst *fi;
|
||||
|
||||
struct osmo_gsup_req *update_location_req;
|
||||
|
||||
/* Subscriber state at time of initial Update Location Request */
|
||||
struct hlr_subscriber subscr;
|
||||
bool is_ps;
|
||||
|
||||
/* VLR requesting the LU. */
|
||||
struct osmo_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 osmo_gsup_peer_id via_proxy;
|
||||
};
|
||||
LLIST_HEAD(g_all_lu);
|
||||
|
||||
enum lu_fsm_event {
|
||||
LU_EV_RX_GSUP,
|
||||
};
|
||||
|
||||
enum lu_fsm_state {
|
||||
LU_ST_UNVALIDATED,
|
||||
LU_ST_WAIT_INSERT_DATA_RESULT,
|
||||
LU_ST_WAIT_LOCATION_CANCEL_RESULT,
|
||||
};
|
||||
|
||||
static const struct value_string lu_fsm_event_names[] = {
|
||||
OSMO_VALUE_STRING(LU_EV_RX_GSUP),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct osmo_tdef_state_timeout lu_fsm_timeouts[32] = {
|
||||
[LU_ST_WAIT_INSERT_DATA_RESULT] = { .T = -4222 },
|
||||
[LU_ST_WAIT_LOCATION_CANCEL_RESULT] = { .T = -4222 },
|
||||
};
|
||||
|
||||
#define lu_state_chg(lu, state) \
|
||||
osmo_tdef_fsm_inst_state_chg((lu)->fi, state, lu_fsm_timeouts, g_hlr_tdefs, 5)
|
||||
|
||||
static void lu_success(struct lu *lu)
|
||||
{
|
||||
if (!lu->update_location_req)
|
||||
LOG_LU(lu, LOGL_ERROR, "No request for this LU\n");
|
||||
else
|
||||
osmo_gsup_req_respond_msgt(lu->update_location_req, OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT, true);
|
||||
lu->update_location_req = NULL;
|
||||
osmo_fsm_inst_term(lu->fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
}
|
||||
|
||||
#define lu_failure(LU, CAUSE, log_msg, args...) do { \
|
||||
if (!(LU)->update_location_req) \
|
||||
LOG_LU(LU, LOGL_ERROR, "No request for this LU\n"); \
|
||||
else \
|
||||
osmo_gsup_req_respond_err((LU)->update_location_req, CAUSE, log_msg, ##args); \
|
||||
(LU)->update_location_req = NULL; \
|
||||
osmo_fsm_inst_term((LU)->fi, OSMO_FSM_TERM_REGULAR, NULL); \
|
||||
} while(0)
|
||||
|
||||
static struct osmo_fsm lu_fsm;
|
||||
|
||||
static void lu_start(struct osmo_gsup_req *update_location_req)
|
||||
{
|
||||
struct osmo_fsm_inst *fi;
|
||||
struct lu *lu;
|
||||
|
||||
OSMO_ASSERT(update_location_req);
|
||||
OSMO_ASSERT(update_location_req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST);
|
||||
|
||||
fi = osmo_fsm_inst_alloc(&lu_fsm, g_hlr, NULL, LOGL_DEBUG, update_location_req->gsup.imsi);
|
||||
OSMO_ASSERT(fi);
|
||||
|
||||
lu = talloc(fi, struct lu);
|
||||
OSMO_ASSERT(lu);
|
||||
fi->priv = lu;
|
||||
*lu = (struct lu){
|
||||
.fi = fi,
|
||||
.update_location_req = update_location_req,
|
||||
.vlr_name = update_location_req->source_name,
|
||||
.via_proxy = update_location_req->via_proxy,
|
||||
/* According to GSUP specs, OSMO_GSUP_CN_DOMAIN_PS is the default. */
|
||||
.is_ps = (update_location_req->gsup.cn_domain != OSMO_GSUP_CN_DOMAIN_CS),
|
||||
};
|
||||
llist_add(&lu->entry, &g_all_lu);
|
||||
|
||||
osmo_fsm_inst_update_id_f_sanitize(fi, '_', "%s:IMSI-%s", lu->is_ps ? "PS" : "CS", update_location_req->gsup.imsi);
|
||||
|
||||
if (osmo_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");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if subscriber is generally permitted on CS or PS
|
||||
* service (as requested) */
|
||||
if (!lu->is_ps && !lu->subscr.nam_cs) {
|
||||
lu_failure(lu, GMM_CAUSE_PLMN_NOTALLOWED, "nam_cs == false");
|
||||
return;
|
||||
}
|
||||
if (lu->is_ps && !lu->subscr.nam_ps) {
|
||||
lu_failure(lu, GMM_CAUSE_GPRS_NOTALLOWED, "nam_ps == false");
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: Set subscriber tracing = deactive in VLR/SGSN */
|
||||
|
||||
#if 0
|
||||
/* Cancel in old VLR/SGSN, if new VLR/SGSN differs from old (FIXME: OS#4491) */
|
||||
if (!lu->is_ps && strcmp(subscr->vlr_number, vlr_number)) {
|
||||
lu_op_tx_cancel_old(lu);
|
||||
} else if (lu->is_ps && strcmp(subscr->sgsn_number, sgsn_number)) {
|
||||
lu_op_tx_cancel_old(lu);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Store the VLR / SGSN number with the subscriber, so we know where it was last seen. */
|
||||
if (!osmo_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",
|
||||
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",
|
||||
osmo_gsup_peer_id_to_str(&lu->vlr_name));
|
||||
}
|
||||
|
||||
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)) {
|
||||
lu_failure(lu, GMM_CAUSE_NET_FAIL, "Cannot update %s in the database",
|
||||
lu->is_ps ? "SGSN number" : "VLR number");
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: Subscriber allowed to roam in PLMN? */
|
||||
/* TODO: Update RoutingInfo */
|
||||
/* TODO: Reset Flag MS Purged (cs/ps) */
|
||||
/* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */
|
||||
|
||||
lu_state_chg(lu, LU_ST_WAIT_INSERT_DATA_RESULT);
|
||||
}
|
||||
|
||||
void lu_rx_gsup(struct osmo_gsup_req *req)
|
||||
{
|
||||
struct lu *lu;
|
||||
if (req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST)
|
||||
return lu_start(req);
|
||||
|
||||
llist_for_each_entry(lu, &g_all_lu, entry) {
|
||||
if (strcmp(lu->subscr.imsi, req->gsup.imsi))
|
||||
continue;
|
||||
if (osmo_fsm_inst_dispatch(lu->fi, LU_EV_RX_GSUP, req)) {
|
||||
LOG_LU_REQ(lu, req, LOGL_ERROR, "Cannot receive GSUP messages in this state\n");
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE,
|
||||
"LU does not accept GSUP rx");
|
||||
}
|
||||
return;
|
||||
}
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "No Location Updating in progress for this IMSI");
|
||||
}
|
||||
|
||||
static int lu_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
struct lu *lu = fi->priv;
|
||||
lu_failure(lu, GSM_CAUSE_NET_FAIL, "Timeout");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lu_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
||||
{
|
||||
struct lu *lu = fi->priv;
|
||||
if (lu->update_location_req)
|
||||
osmo_gsup_req_respond_err(lu->update_location_req, GSM_CAUSE_NET_FAIL, "LU aborted");
|
||||
lu->update_location_req = NULL;
|
||||
llist_del(&lu->entry);
|
||||
}
|
||||
|
||||
static void lu_fsm_wait_insert_data_result_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
/* Transmit Insert Data Request to the VLR */
|
||||
struct lu *lu = fi->priv;
|
||||
struct hlr_subscriber *subscr = &lu->subscr;
|
||||
struct osmo_gsup_message gsup;
|
||||
uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
|
||||
uint8_t apn[APN_MAXLEN];
|
||||
|
||||
if (osmo_gsup_create_insert_subscriber_data_msg(&gsup, subscr->imsi,
|
||||
subscr->msisdn, msisdn_enc, sizeof(msisdn_enc),
|
||||
apn, sizeof(apn),
|
||||
lu->is_ps? OSMO_GSUP_CN_DOMAIN_PS : OSMO_GSUP_CN_DOMAIN_CS)) {
|
||||
lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot encode Insert Subscriber Data message");
|
||||
return;
|
||||
}
|
||||
|
||||
if (osmo_gsup_req_respond(lu->update_location_req, &gsup, false, false))
|
||||
lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot send %s", osmo_gsup_message_type_name(gsup.message_type));
|
||||
}
|
||||
|
||||
void lu_fsm_wait_insert_data_result(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct lu *lu = fi->priv;
|
||||
struct osmo_gsup_req *req;
|
||||
|
||||
switch (event) {
|
||||
case LU_EV_RX_GSUP:
|
||||
req = data;
|
||||
break;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
|
||||
switch (req->gsup.message_type) {
|
||||
case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
|
||||
osmo_gsup_req_free(req);
|
||||
lu_success(lu);
|
||||
break;
|
||||
|
||||
case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
|
||||
lu_failure(lu, GMM_CAUSE_NET_FAIL, "Rx %s", osmo_gsup_message_type_name(req->gsup.message_type));
|
||||
break;
|
||||
|
||||
default:
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "unexpected message type in this state");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#define S(x) (1 << (x))
|
||||
|
||||
static const struct osmo_fsm_state lu_fsm_states[] = {
|
||||
[LU_ST_UNVALIDATED] = {
|
||||
.name = "UNVALIDATED",
|
||||
.out_state_mask = 0
|
||||
| S(LU_ST_WAIT_INSERT_DATA_RESULT)
|
||||
,
|
||||
},
|
||||
[LU_ST_WAIT_INSERT_DATA_RESULT] = {
|
||||
.name = "WAIT_INSERT_DATA_RESULT",
|
||||
.in_event_mask = 0
|
||||
| S(LU_EV_RX_GSUP)
|
||||
,
|
||||
.onenter = lu_fsm_wait_insert_data_result_onenter,
|
||||
.action = lu_fsm_wait_insert_data_result,
|
||||
},
|
||||
};
|
||||
|
||||
static struct osmo_fsm lu_fsm = {
|
||||
.name = "lu",
|
||||
.states = lu_fsm_states,
|
||||
.num_states = ARRAY_SIZE(lu_fsm_states),
|
||||
.log_subsys = DLU,
|
||||
.event_names = lu_fsm_event_names,
|
||||
.timer_cb = lu_fsm_timer_cb,
|
||||
.cleanup = lu_fsm_cleanup,
|
||||
};
|
||||
|
||||
static __attribute__((constructor)) void lu_fsm_init()
|
||||
{
|
||||
OSMO_ASSERT(osmo_fsm_register(&lu_fsm) == 0);
|
||||
}
|
258
src/luop.c
258
src/luop.c
@@ -1,258 +0,0 @@
|
||||
/* OsmoHLR TX/RX lu operations */
|
||||
|
||||
/* (C) 2017 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/gsm/gsup.h>
|
||||
#include <osmocom/gsm/apn.h>
|
||||
|
||||
#include <osmocom/hlr/gsup_server.h>
|
||||
#include <osmocom/hlr/gsup_router.h>
|
||||
#include <osmocom/hlr/logging.h>
|
||||
#include <osmocom/hlr/luop.h>
|
||||
|
||||
const struct value_string lu_state_names[] = {
|
||||
{ LU_S_NULL, "NULL" },
|
||||
{ LU_S_LU_RECEIVED, "LU RECEIVED" },
|
||||
{ LU_S_CANCEL_SENT, "CANCEL SENT" },
|
||||
{ LU_S_CANCEL_ACK_RECEIVED, "CANCEL-ACK RECEIVED" },
|
||||
{ LU_S_ISD_SENT, "ISD SENT" },
|
||||
{ LU_S_ISD_ACK_RECEIVED, "ISD-ACK RECEIVED" },
|
||||
{ LU_S_COMPLETE, "COMPLETE" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
/* Transmit a given GSUP message for the given LU operation */
|
||||
static void _luop_tx_gsup(struct lu_operation *luop,
|
||||
const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
struct msgb *msg_out;
|
||||
|
||||
msg_out = osmo_gsup_msgb_alloc("GSUP LUOP");
|
||||
osmo_gsup_encode(msg_out, gsup);
|
||||
|
||||
osmo_gsup_addr_send(luop->gsup_server, luop->peer,
|
||||
talloc_total_size(luop->peer),
|
||||
msg_out);
|
||||
}
|
||||
|
||||
static inline void fill_gsup_msg(struct osmo_gsup_message *out,
|
||||
const struct lu_operation *lu,
|
||||
enum osmo_gsup_message_type mt)
|
||||
{
|
||||
memset(out, 0, sizeof(struct osmo_gsup_message));
|
||||
if (lu)
|
||||
osmo_strlcpy(out->imsi, lu->subscr.imsi,
|
||||
GSM23003_IMSI_MAX_DIGITS + 1);
|
||||
out->message_type = mt;
|
||||
}
|
||||
|
||||
/* timer call-back in case LU operation doesn't receive an response */
|
||||
static void lu_op_timer_cb(void *data)
|
||||
{
|
||||
struct lu_operation *luop = data;
|
||||
|
||||
DEBUGP(DMAIN, "LU OP timer expired in state %s\n",
|
||||
get_value_string(lu_state_names, luop->state));
|
||||
|
||||
switch (luop->state) {
|
||||
case LU_S_CANCEL_SENT:
|
||||
break;
|
||||
case LU_S_ISD_SENT:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
lu_op_tx_error(luop, GMM_CAUSE_NET_FAIL);
|
||||
}
|
||||
|
||||
bool lu_op_fill_subscr(struct lu_operation *luop, struct db_context *dbc,
|
||||
const char *imsi)
|
||||
{
|
||||
struct hlr_subscriber *subscr = &luop->subscr;
|
||||
|
||||
if (db_subscr_get_by_imsi(dbc, imsi, subscr) < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct lu_operation *lu_op_alloc(struct osmo_gsup_server *srv)
|
||||
{
|
||||
struct lu_operation *luop;
|
||||
|
||||
luop = talloc_zero(srv, struct lu_operation);
|
||||
OSMO_ASSERT(luop);
|
||||
luop->gsup_server = srv;
|
||||
osmo_timer_setup(&luop->timer, lu_op_timer_cb, luop);
|
||||
|
||||
return luop;
|
||||
}
|
||||
|
||||
void lu_op_free(struct lu_operation *luop)
|
||||
{
|
||||
/* Only attempt to remove when it was ever added to a list. */
|
||||
if (luop->list.next)
|
||||
llist_del(&luop->list);
|
||||
|
||||
/* Delete timer just in case it is still pending. */
|
||||
osmo_timer_del(&luop->timer);
|
||||
|
||||
talloc_free(luop);
|
||||
}
|
||||
|
||||
struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn)
|
||||
{
|
||||
uint8_t *peer_addr;
|
||||
struct lu_operation *luop = lu_op_alloc(conn->server);
|
||||
int rc = osmo_gsup_conn_ccm_get(conn, &peer_addr, IPAC_IDTAG_SERNR);
|
||||
if (rc < 0) {
|
||||
lu_op_free(luop);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
luop->peer = talloc_memdup(luop, peer_addr, rc);
|
||||
|
||||
return luop;
|
||||
}
|
||||
|
||||
/* FIXME: this doesn't seem to work at all */
|
||||
struct lu_operation *lu_op_by_imsi(const char *imsi,
|
||||
const struct llist_head *lst)
|
||||
{
|
||||
struct lu_operation *luop;
|
||||
|
||||
llist_for_each_entry(luop, lst, list) {
|
||||
if (!strcmp(imsi, luop->subscr.imsi))
|
||||
return luop;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void lu_op_statechg(struct lu_operation *luop, enum lu_state new_state)
|
||||
{
|
||||
enum lu_state old_state = luop->state;
|
||||
|
||||
DEBUGP(DMAIN, "LU OP state change: %s -> ",
|
||||
get_value_string(lu_state_names, old_state));
|
||||
DEBUGPC(DMAIN, "%s\n",
|
||||
get_value_string(lu_state_names, new_state));
|
||||
|
||||
luop->state = new_state;
|
||||
}
|
||||
|
||||
/*! Transmit UPD_LOC_ERROR and destroy lu_operation */
|
||||
void lu_op_tx_error(struct lu_operation *luop, enum gsm48_gmm_cause cause)
|
||||
{
|
||||
struct osmo_gsup_message gsup;
|
||||
|
||||
DEBUGP(DMAIN, "%s: LU OP Tx Error (cause %s)\n",
|
||||
luop->subscr.imsi, get_value_string(gsm48_gmm_cause_names,
|
||||
cause));
|
||||
|
||||
fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR);
|
||||
gsup.cause = cause;
|
||||
|
||||
_luop_tx_gsup(luop, &gsup);
|
||||
|
||||
lu_op_free(luop);
|
||||
}
|
||||
|
||||
/*! Transmit UPD_LOC_RESULT and destroy lu_operation */
|
||||
void lu_op_tx_ack(struct lu_operation *luop)
|
||||
{
|
||||
struct osmo_gsup_message gsup;
|
||||
|
||||
fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT);
|
||||
//FIXME gsup.hlr_enc;
|
||||
|
||||
_luop_tx_gsup(luop, &gsup);
|
||||
|
||||
lu_op_free(luop);
|
||||
}
|
||||
|
||||
/*! Send Cancel Location to old VLR/SGSN (FIXME: OS#4491) */
|
||||
void lu_op_tx_cancel_old(struct lu_operation *luop)
|
||||
{
|
||||
struct osmo_gsup_message gsup;
|
||||
|
||||
OSMO_ASSERT(luop->state == LU_S_LU_RECEIVED);
|
||||
|
||||
fill_gsup_msg(&gsup, NULL, OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST);
|
||||
//gsup.cause = FIXME;
|
||||
//gsup.cancel_type = FIXME;
|
||||
|
||||
_luop_tx_gsup(luop, &gsup);
|
||||
|
||||
lu_op_statechg(luop, LU_S_CANCEL_SENT);
|
||||
osmo_timer_schedule(&luop->timer, CANCEL_TIMEOUT_SECS, 0);
|
||||
}
|
||||
|
||||
/*! Transmit Insert Subscriber Data to new VLR/SGSN */
|
||||
void lu_op_tx_insert_subscr_data(struct lu_operation *luop)
|
||||
{
|
||||
struct hlr_subscriber *subscr = &luop->subscr;
|
||||
struct osmo_gsup_message gsup = { };
|
||||
uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
|
||||
uint8_t apn[APN_MAXLEN];
|
||||
enum osmo_gsup_cn_domain cn_domain;
|
||||
|
||||
OSMO_ASSERT(luop->state == LU_S_LU_RECEIVED ||
|
||||
luop->state == LU_S_CANCEL_ACK_RECEIVED);
|
||||
|
||||
if (luop->is_ps)
|
||||
cn_domain = OSMO_GSUP_CN_DOMAIN_PS;
|
||||
else
|
||||
cn_domain = OSMO_GSUP_CN_DOMAIN_CS;
|
||||
|
||||
if (osmo_gsup_create_insert_subscriber_data_msg(&gsup, subscr->imsi, subscr->msisdn, msisdn_enc,
|
||||
sizeof(msisdn_enc), apn, sizeof(apn), cn_domain) != 0) {
|
||||
LOGP(DMAIN, LOGL_ERROR,
|
||||
"IMSI='%s': Cannot notify GSUP client; could not create gsup message "
|
||||
"for %s\n", subscr->imsi, luop->peer);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Send ISD to new VLR/SGSN */
|
||||
_luop_tx_gsup(luop, &gsup);
|
||||
|
||||
lu_op_statechg(luop, LU_S_ISD_SENT);
|
||||
osmo_timer_schedule(&luop->timer, ISD_TIMEOUT_SECS, 0);
|
||||
}
|
||||
|
||||
/*! Transmit Delete Subscriber Data to new VLR/SGSN.
|
||||
* The luop is not freed. */
|
||||
void lu_op_tx_del_subscr_data(struct lu_operation *luop)
|
||||
{
|
||||
struct osmo_gsup_message gsup;
|
||||
|
||||
fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_DELETE_DATA_REQUEST);
|
||||
|
||||
gsup.cn_domain = OSMO_GSUP_CN_DOMAIN_PS;
|
||||
|
||||
/* Send ISD to new VLR/SGSN */
|
||||
_luop_tx_gsup(luop, &gsup);
|
||||
}
|
459
src/mslookup_server.c
Normal file
459
src/mslookup_server.c
Normal file
@@ -0,0 +1,459 @@
|
||||
/* 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 <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 mslookup_service_host *service_host,
|
||||
uint32_t age)
|
||||
{
|
||||
if (!osmo_sockaddr_str_is_nonzero(&service_host->host_v4)
|
||||
&& !osmo_sockaddr_str_is_nonzero(&service_host->host_v6)) {
|
||||
*result = not_found;
|
||||
return;
|
||||
}
|
||||
result->rc = OSMO_MSLOOKUP_RC_RESULT;
|
||||
result->host_v4 = service_host->host_v4;
|
||||
result->host_v6 = service_host->host_v6;
|
||||
result->age = age;
|
||||
}
|
||||
|
||||
const struct mslookup_service_host *mslookup_server_get_local_gsup_addr()
|
||||
{
|
||||
static struct mslookup_service_host gsup_bind = {};
|
||||
struct mslookup_service_host *host;
|
||||
|
||||
/* Find a HLR/GSUP service set for the server (no VLR unit name) */
|
||||
host = mslookup_server_service_get(&mslookup_server_msc_wildcard, OSMO_MSLOOKUP_SERVICE_HLR_GSUP);
|
||||
if (host)
|
||||
return host;
|
||||
|
||||
/* Try to use the locally configured GSUP bind address */
|
||||
osmo_sockaddr_str_from_str(&gsup_bind.host_v4, g_hlr->gsup_bind_addr, OSMO_GSUP_PORT);
|
||||
if (gsup_bind.host_v4.af == AF_INET6) {
|
||||
gsup_bind.host_v6 = gsup_bind.host_v4;
|
||||
gsup_bind.host_v4 = (struct osmo_sockaddr_str){};
|
||||
}
|
||||
return &gsup_bind;
|
||||
}
|
||||
|
||||
struct mslookup_server_msc_cfg *mslookup_server_msc_get(const struct osmo_ipa_name *msc_name, bool create)
|
||||
{
|
||||
struct llist_head *c = &g_hlr->mslookup.server.local_site_services;
|
||||
struct mslookup_server_msc_cfg *msc;
|
||||
|
||||
if (!msc_name)
|
||||
return NULL;
|
||||
|
||||
llist_for_each_entry(msc, c, entry) {
|
||||
if (osmo_ipa_name_cmp(&msc->name, msc_name))
|
||||
continue;
|
||||
return msc;
|
||||
}
|
||||
if (!create)
|
||||
return NULL;
|
||||
|
||||
msc = talloc_zero(g_hlr, struct mslookup_server_msc_cfg);
|
||||
OSMO_ASSERT(msc);
|
||||
INIT_LLIST_HEAD(&msc->service_hosts);
|
||||
msc->name = *msc_name;
|
||||
llist_add_tail(&msc->entry, c);
|
||||
return msc;
|
||||
}
|
||||
|
||||
struct mslookup_service_host *mslookup_server_msc_service_get(struct mslookup_server_msc_cfg *msc, const char *service,
|
||||
bool create)
|
||||
{
|
||||
struct mslookup_service_host *e;
|
||||
if (!msc)
|
||||
return NULL;
|
||||
|
||||
llist_for_each_entry(e, &msc->service_hosts, entry) {
|
||||
if (!strcmp(e->service, service))
|
||||
return e;
|
||||
}
|
||||
|
||||
if (!create)
|
||||
return NULL;
|
||||
|
||||
e = talloc_zero(msc, struct mslookup_service_host);
|
||||
OSMO_ASSERT(e);
|
||||
OSMO_STRLCPY_ARRAY(e->service, service);
|
||||
llist_add_tail(&e->entry, &msc->service_hosts);
|
||||
return e;
|
||||
}
|
||||
|
||||
struct mslookup_service_host *mslookup_server_service_get(const struct osmo_ipa_name *msc_name, const char *service)
|
||||
{
|
||||
struct mslookup_server_msc_cfg *msc = mslookup_server_msc_get(msc_name, false);
|
||||
if (!msc)
|
||||
return NULL;
|
||||
return mslookup_server_msc_service_get(msc, service, false);
|
||||
}
|
||||
|
||||
int mslookup_server_msc_service_set(struct mslookup_server_msc_cfg *msc, const char *service,
|
||||
const struct osmo_sockaddr_str *addr)
|
||||
{
|
||||
struct mslookup_service_host *e;
|
||||
|
||||
if (!service || !service[0]
|
||||
|| strlen(service) > OSMO_MSLOOKUP_SERVICE_MAXLEN)
|
||||
return -EINVAL;
|
||||
if (!addr || !osmo_sockaddr_str_is_nonzero(addr))
|
||||
return -EINVAL;
|
||||
|
||||
e = mslookup_server_msc_service_get(msc, service, true);
|
||||
if (!e)
|
||||
return -EINVAL;
|
||||
|
||||
switch (addr->af) {
|
||||
case AF_INET:
|
||||
e->host_v4 = *addr;
|
||||
break;
|
||||
case AF_INET6:
|
||||
e->host_v6 = *addr;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mslookup_server_msc_service_del(struct mslookup_server_msc_cfg *msc, const char *service,
|
||||
const struct osmo_sockaddr_str *addr)
|
||||
{
|
||||
struct mslookup_service_host *e, *n;
|
||||
int deleted = 0;
|
||||
|
||||
if (!msc)
|
||||
return -ENOENT;
|
||||
|
||||
llist_for_each_entry_safe(e, n, &msc->service_hosts, entry) {
|
||||
if (service && strcmp(service, e->service))
|
||||
continue;
|
||||
|
||||
if (addr) {
|
||||
if (!osmo_sockaddr_str_cmp(addr, &e->host_v4)) {
|
||||
e->host_v4 = (struct osmo_sockaddr_str){};
|
||||
/* Removed one addr. If the other is still there, keep the entry. */
|
||||
if (osmo_sockaddr_str_is_nonzero(&e->host_v6))
|
||||
continue;
|
||||
} else if (!osmo_sockaddr_str_cmp(addr, &e->host_v6)) {
|
||||
e->host_v6 = (struct osmo_sockaddr_str){};
|
||||
/* Removed one addr. If the other is still there, keep the entry. */
|
||||
if (osmo_sockaddr_str_is_nonzero(&e->host_v4))
|
||||
continue;
|
||||
} else
|
||||
/* No addr match, keep the entry. */
|
||||
continue;
|
||||
/* Addr matched and none is left. Delete. */
|
||||
}
|
||||
llist_del(&e->entry);
|
||||
talloc_free(e);
|
||||
deleted++;
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/* A remote entity is asking us whether we are the home HLR of the given subscriber. */
|
||||
static void mslookup_server_rx_hlr_gsup(const struct osmo_mslookup_query *query,
|
||||
struct osmo_mslookup_result *result)
|
||||
{
|
||||
const struct mslookup_service_host *host;
|
||||
int rc;
|
||||
switch (query->id.type) {
|
||||
case OSMO_MSLOOKUP_ID_IMSI:
|
||||
rc = db_subscr_exists_by_imsi(g_hlr->dbc, query->id.imsi);
|
||||
break;
|
||||
case OSMO_MSLOOKUP_ID_MSISDN:
|
||||
rc = db_subscr_exists_by_msisdn(g_hlr->dbc, query->id.msisdn);
|
||||
break;
|
||||
default:
|
||||
LOGP(DMSLOOKUP, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type);
|
||||
*result = not_found;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: does not exist in local HLR\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
|
||||
*result = not_found;
|
||||
return;
|
||||
}
|
||||
|
||||
LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: found in local HLR\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
|
||||
|
||||
host = mslookup_server_get_local_gsup_addr();
|
||||
|
||||
set_result(result, host, 0);
|
||||
if (result->rc != OSMO_MSLOOKUP_RC_RESULT) {
|
||||
LOGP(DMSLOOKUP, LOGL_ERROR,
|
||||
"Subscriber found, but error in service '" OSMO_MSLOOKUP_SERVICE_HLR_GSUP "' config:"
|
||||
" v4: " OSMO_SOCKADDR_STR_FMT " v6: " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&host->host_v4),
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&host->host_v6));
|
||||
}
|
||||
}
|
||||
|
||||
/* Look in the local HLR record: If the subscriber is "at home" in this HLR and is also currently located at a local
|
||||
* VLR, we will find a valid location updating with vlr_number, and no vlr_via_proxy entry. */
|
||||
static bool subscriber_has_done_lu_here_hlr(const struct osmo_mslookup_query *query,
|
||||
uint32_t *lu_age,
|
||||
struct osmo_ipa_name *local_msc_name,
|
||||
struct hlr_subscriber *ret_subscr)
|
||||
{
|
||||
struct hlr_subscriber _subscr;
|
||||
int rc;
|
||||
uint32_t age;
|
||||
|
||||
struct hlr_subscriber *subscr = ret_subscr ? : &_subscr;
|
||||
|
||||
switch (query->id.type) {
|
||||
case OSMO_MSLOOKUP_ID_IMSI:
|
||||
rc = db_subscr_get_by_imsi(g_hlr->dbc, query->id.imsi, subscr);
|
||||
break;
|
||||
case OSMO_MSLOOKUP_ID_MSISDN:
|
||||
rc = db_subscr_get_by_msisdn(g_hlr->dbc, query->id.msisdn, subscr);
|
||||
break;
|
||||
default:
|
||||
LOGP(DMSLOOKUP, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: does not exist in local HLR\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!subscr->vlr_number[0]) {
|
||||
LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: not attached (vlr_number unset)\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (subscr->vlr_via_proxy.len) {
|
||||
/* The VLR is behind a proxy, the subscriber is not attached to a local VLR but a remote one. That
|
||||
* remote proxy should instead respond to the service lookup request. */
|
||||
LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: last attach is not at local VLR, but at VLR '%s' via proxy %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
subscr->vlr_number,
|
||||
osmo_ipa_name_to_str(&subscr->vlr_via_proxy));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!timestamp_age(&subscr->last_lu_seen, &age)) {
|
||||
LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: Invalid last_lu_seen timestamp for subscriber\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
|
||||
return false;
|
||||
}
|
||||
if (age > g_hlr->mslookup.server.local_attach_max_age) {
|
||||
LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: last attach was here, but too long ago: %us > %us\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
age, g_hlr->mslookup.server.local_attach_max_age);
|
||||
return false;
|
||||
}
|
||||
|
||||
*lu_age = age;
|
||||
osmo_ipa_name_set_str(local_msc_name, subscr->vlr_number);
|
||||
LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: attached %u seconds ago at local VLR %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
age, osmo_ipa_name_to_str(local_msc_name));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* Determine whether the subscriber with the given ID has routed a Location Updating via this HLR as first hop. Return
|
||||
* true if it is attached at a local VLR, and we are serving as proxy for a remote home HLR.
|
||||
*/
|
||||
static bool subscriber_has_done_lu_here_proxy(const struct osmo_mslookup_query *query,
|
||||
uint32_t *lu_age,
|
||||
struct osmo_ipa_name *local_msc_name,
|
||||
struct proxy_subscr *ret_proxy_subscr)
|
||||
{
|
||||
struct proxy_subscr proxy_subscr;
|
||||
uint32_t age;
|
||||
int rc;
|
||||
|
||||
/* See the local HLR record. If the subscriber is "at home" in this HLR and is also currently located here, we
|
||||
* will find a valid location updating and no vlr_via_proxy entry. */
|
||||
switch (query->id.type) {
|
||||
case OSMO_MSLOOKUP_ID_IMSI:
|
||||
rc = proxy_subscr_get_by_imsi(&proxy_subscr, g_hlr->gs->proxy, query->id.imsi);
|
||||
break;
|
||||
case OSMO_MSLOOKUP_ID_MSISDN:
|
||||
rc = proxy_subscr_get_by_msisdn(&proxy_subscr, g_hlr->gs->proxy, query->id.msisdn);
|
||||
break;
|
||||
default:
|
||||
LOGP(DDGSM, LOGL_ERROR, "%s: unknown ID type\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
LOGP(DDGSM, LOGL_DEBUG, "%s: does not exist in GSUP proxy\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* We only need to care about CS LU, since only CS services need D-GSM routing. */
|
||||
if (!timestamp_age(&proxy_subscr.cs.last_lu, &age)
|
||||
|| age > g_hlr->mslookup.server.local_attach_max_age) {
|
||||
LOGP(DDGSM, LOGL_ERROR,
|
||||
"%s: last attach was at local VLR (proxying for remote HLR), but too long ago: %us > %us\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
age, g_hlr->mslookup.server.local_attach_max_age);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (proxy_subscr.cs.vlr_via_proxy.len) {
|
||||
LOGP(DDGSM, LOGL_DEBUG, "%s: last attach is not at local VLR, but at VLR '%s' via proxy '%s'\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
osmo_ipa_name_to_str(&proxy_subscr.cs.vlr_name),
|
||||
osmo_ipa_name_to_str(&proxy_subscr.cs.vlr_via_proxy));
|
||||
return false;
|
||||
}
|
||||
|
||||
*lu_age = age;
|
||||
*local_msc_name = proxy_subscr.cs.vlr_name;
|
||||
LOGP(DDGSM, LOGL_DEBUG, "%s: attached %u seconds ago at local VLR %s; proxying for remote HLR "
|
||||
OSMO_SOCKADDR_STR_FMT "\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
age, osmo_ipa_name_to_str(local_msc_name),
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr.remote_hlr_addr));
|
||||
|
||||
if (ret_proxy_subscr)
|
||||
*ret_proxy_subscr = proxy_subscr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool subscriber_has_done_lu_here(const struct osmo_mslookup_query *query,
|
||||
uint32_t *lu_age_p, struct osmo_ipa_name *local_msc_name,
|
||||
char *ret_imsi, size_t ret_imsi_len)
|
||||
{
|
||||
bool attached_here;
|
||||
uint32_t lu_age = 0;
|
||||
struct osmo_ipa_name msc_name = {};
|
||||
bool attached_here_proxy;
|
||||
uint32_t proxy_lu_age = 0;
|
||||
struct osmo_ipa_name proxy_msc_name = {};
|
||||
struct proxy_subscr proxy_subscr;
|
||||
struct hlr_subscriber db_subscr;
|
||||
|
||||
|
||||
/* First ask the local HLR db, but if the local proxy record indicates a more recent LU, use that instead.
|
||||
* For all usual cases, only one of these will reflect a LU, even if a subscriber had more than one home HLR:
|
||||
* - if the subscriber is known here, we will never proxy.
|
||||
* - if the subscriber is not known here, this local HLR db will never record a LU.
|
||||
* However, if a subscriber was being proxied to a remote home HLR, and if then the subscriber was also added to
|
||||
* the local HLR database, there might occur a situation where both reflect a LU. So, to be safe against all
|
||||
* situations, compare the two entries.
|
||||
*/
|
||||
attached_here = subscriber_has_done_lu_here_hlr(query, &lu_age, &msc_name, &db_subscr);
|
||||
attached_here_proxy = subscriber_has_done_lu_here_proxy(query, &proxy_lu_age, &proxy_msc_name, &proxy_subscr);
|
||||
|
||||
/* If proxy has a younger lu, replace. */
|
||||
if (attached_here_proxy && (!attached_here || (proxy_lu_age < lu_age))) {
|
||||
attached_here = true;
|
||||
lu_age = proxy_lu_age;
|
||||
msc_name = proxy_msc_name;
|
||||
if (ret_imsi)
|
||||
osmo_strlcpy(ret_imsi, proxy_subscr.imsi, ret_imsi_len);
|
||||
} else if (attached_here) {
|
||||
if (ret_imsi)
|
||||
osmo_strlcpy(ret_imsi, db_subscr.imsi, ret_imsi_len);
|
||||
}
|
||||
|
||||
if (attached_here && !msc_name.len) {
|
||||
LOGP(DMSLOOKUP, LOGL_ERROR, "%s: attached here, but no VLR name known\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!attached_here) {
|
||||
/* Already logged "not attached" for both local-db and proxy attach */
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGP(DMSLOOKUP, LOGL_INFO, "%s: attached here, at VLR %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
osmo_ipa_name_to_str(&msc_name));
|
||||
*lu_age_p = lu_age;
|
||||
*local_msc_name = msc_name;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* A remote entity is asking us whether we are providing the given service for the given subscriber. */
|
||||
void mslookup_server_rx(const struct osmo_mslookup_query *query,
|
||||
struct osmo_mslookup_result *result)
|
||||
{
|
||||
const struct mslookup_service_host *service_host;
|
||||
uint32_t age;
|
||||
struct osmo_ipa_name msc_name;
|
||||
|
||||
/* A request for a home HLR: answer exactly if this is the subscriber's home HLR, i.e. the IMSI is listed in the
|
||||
* HLR database. */
|
||||
if (strcmp(query->service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP) == 0)
|
||||
return mslookup_server_rx_hlr_gsup(query, result);
|
||||
|
||||
/* All other service types: answer when the subscriber has done a LU that is either listed in the local HLR or
|
||||
* in the GSUP proxy database: i.e. if the subscriber has done a Location Updating at an VLR belonging to this
|
||||
* HLR. Respond with whichever services are configured in the osmo-hlr.cfg. */
|
||||
if (!subscriber_has_done_lu_here(query, &age, &msc_name, NULL, 0)) {
|
||||
*result = not_found;
|
||||
return;
|
||||
}
|
||||
|
||||
/* We've detected a LU here. The VLR where the LU happened is stored in msc_unit_name, and the LU age is stored
|
||||
* in 'age'. Figure out the address configured for that VLR and service name. */
|
||||
service_host = mslookup_server_service_get(&msc_name, query->service);
|
||||
|
||||
if (!service_host) {
|
||||
/* Find such service set globally (no VLR unit name) */
|
||||
service_host = mslookup_server_service_get(&mslookup_server_msc_wildcard, query->service);
|
||||
}
|
||||
|
||||
if (!service_host) {
|
||||
LOGP(DMSLOOKUP, LOGL_ERROR,
|
||||
"%s: subscriber found, but no service %s configured, cannot service lookup request\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
osmo_quote_str_c(OTC_SELECT, query->service, -1));
|
||||
*result = not_found;
|
||||
return;
|
||||
}
|
||||
|
||||
set_result(result, service_host, age);
|
||||
}
|
157
src/mslookup_server_mdns.c
Normal file
157
src/mslookup_server_mdns.c
Normal file
@@ -0,0 +1,157 @@
|
||||
/* 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 <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,
|
||||
uint16_t packet_id,
|
||||
const struct osmo_mslookup_query *query,
|
||||
const struct osmo_mslookup_result *result)
|
||||
{
|
||||
struct msgb *msg;
|
||||
const char *errmsg = NULL;
|
||||
void *ctx = talloc_named_const(server, 0, __func__);
|
||||
|
||||
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(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(uint16_t packet_id,
|
||||
struct osmo_mslookup_server_mdns *server,
|
||||
const struct osmo_mslookup_query *query)
|
||||
{
|
||||
struct osmo_mslookup_result result;
|
||||
|
||||
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, 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_mslookup_query *query;
|
||||
uint16_t packet_id;
|
||||
int n;
|
||||
uint8_t buffer[1024];
|
||||
void *ctx;
|
||||
|
||||
/* Parse the message and print it */
|
||||
n = read(osmo_fd->fd, buffer, sizeof(buffer));
|
||||
if (n < 0)
|
||||
return n;
|
||||
|
||||
ctx = talloc_named_const(server, 0, __func__);
|
||||
query = osmo_mdns_query_decode(ctx, buffer, n, &packet_id, server->domain_suffix);
|
||||
if (!query) {
|
||||
talloc_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
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,
|
||||
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,
|
||||
osmo_mslookup_server_mdns_rx,
|
||||
server, 0);
|
||||
if (!server->sock) {
|
||||
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);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
void osmo_mslookup_server_mdns_stop(struct osmo_mslookup_server_mdns *server)
|
||||
{
|
||||
if (!server)
|
||||
return;
|
||||
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.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));
|
||||
}
|
||||
}
|
566
src/proxy.c
Normal file
566
src/proxy.c
Normal file
@@ -0,0 +1,566 @@
|
||||
/* 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 <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 <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>
|
||||
|
||||
#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;
|
||||
timestamp_t last_update;
|
||||
struct proxy_subscr data;
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
static void proxy_pending_req_remote_hlr_connect_result(struct osmo_gsup_req *req, struct remote_hlr *remote_hlr)
|
||||
{
|
||||
if (!remote_hlr || !remote_hlr_is_up(remote_hlr)) {
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "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)
|
||||
{
|
||||
if (!proxy_subscr || !imsi)
|
||||
return false;
|
||||
return strcmp(proxy_subscr->imsi, imsi) == 0;
|
||||
}
|
||||
|
||||
static bool proxy_subscr_matches_msisdn(const struct proxy_subscr *proxy_subscr, const char *msisdn)
|
||||
{
|
||||
if (!proxy_subscr || !msisdn)
|
||||
return false;
|
||||
return strcmp(proxy_subscr->msisdn, msisdn) == 0;
|
||||
}
|
||||
|
||||
static struct proxy_subscr_listentry *_proxy_get_by_imsi(struct proxy *proxy, const char *imsi)
|
||||
{
|
||||
struct proxy_subscr_listentry *e;
|
||||
if (!proxy)
|
||||
return NULL;
|
||||
llist_for_each_entry(e, &proxy->subscr_list, entry) {
|
||||
if (proxy_subscr_matches_imsi(&e->data, imsi))
|
||||
return e;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct proxy_subscr_listentry *_proxy_get_by_msisdn(struct proxy *proxy, const char *msisdn)
|
||||
{
|
||||
struct proxy_subscr_listentry *e;
|
||||
if (!proxy)
|
||||
return NULL;
|
||||
llist_for_each_entry(e, &proxy->subscr_list, entry) {
|
||||
if (proxy_subscr_matches_msisdn(&e->data, msisdn))
|
||||
return e;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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 -ENOENT;
|
||||
*dst = e->data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 -ENOENT;
|
||||
*dst = e->data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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) {
|
||||
/* Does not exist yet */
|
||||
e = talloc_zero(proxy, struct proxy_subscr_listentry);
|
||||
llist_add(&e->entry, &proxy->subscr_list);
|
||||
}
|
||||
e->data = *proxy_subscr;
|
||||
timestamp_update(&e->last_update);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _proxy_subscr_del(struct proxy_subscr_listentry *e)
|
||||
{
|
||||
llist_del(&e->entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int proxy_subscr_del(struct proxy *proxy, const char *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);
|
||||
}
|
||||
|
||||
/* Discard stale proxy entries. */
|
||||
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, &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)
|
||||
osmo_timer_schedule(&proxy->gc_timer, proxy->gc_period, 0);
|
||||
else
|
||||
LOGP(DDGSM, LOGL_NOTICE, "Proxy cleanup is switched off (gc_period == 0)\n");
|
||||
}
|
||||
|
||||
void proxy_set_gc_period(struct proxy *proxy, uint32_t gc_period)
|
||||
{
|
||||
proxy->gc_period = gc_period;
|
||||
proxy_cleanup(proxy);
|
||||
}
|
||||
|
||||
void proxy_init(struct osmo_gsup_server *gsup_server_to_vlr)
|
||||
{
|
||||
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);
|
||||
gsup_server_to_vlr->proxy = proxy;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT:
|
||||
/* Remember the auth tuples: if the remote HLR becomes unreachable for an intermediate period, we can
|
||||
* still re-use this auth information a number of times and keep the subscriber attached (on this
|
||||
* roaming/proxy HLR). */
|
||||
#if 0
|
||||
for (i = 0; i < gsup->num_auth_vectors; i++)
|
||||
proxy_subscr_new.auth_vectors[i] = gsup->auth_vectors[i];
|
||||
proxy_subscr_new.num_auth_vectors = gsup->num_auth_vectors;
|
||||
rc = proxy_subscr_create_or_update(proxy, &proxy_subscr_new);
|
||||
#endif
|
||||
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);
|
||||
}
|
417
src/proxy_mm.c
Normal file
417
src/proxy_mm.c
Normal file
@@ -0,0 +1,417 @@
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
#include <osmocom/core/tdef.h>
|
||||
#include <osmocom/gsm/apn.h>
|
||||
#include <osmocom/hlr/hlr.h>
|
||||
#include <osmocom/hlr/proxy_mm.h>
|
||||
#include <osmocom/hlr/proxy_db.h>
|
||||
|
||||
enum proxy_mm_fsm_state {
|
||||
PROXY_MM_ST_READY,
|
||||
PROXY_MM_ST_WAIT_SUBSCR_DATA,
|
||||
PROXY_MM_ST_WAIT_GSUP_ISD_RESULT,
|
||||
PROXY_MM_ST_WAIT_AUTH_TUPLES,
|
||||
};
|
||||
|
||||
static const struct value_string proxy_mm_fsm_event_names[] = {
|
||||
OSMO_VALUE_STRING(PROXY_MM_EV_SUBSCR_INVALID),
|
||||
OSMO_VALUE_STRING(PROXY_MM_EV_RX_GSUP_LU),
|
||||
OSMO_VALUE_STRING(PROXY_MM_EV_RX_GSUP_SAI),
|
||||
OSMO_VALUE_STRING(PROXY_MM_EV_RX_SUBSCR_DATA),
|
||||
OSMO_VALUE_STRING(PROXY_MM_EV_RX_GSUP_ISD_RESULT),
|
||||
OSMO_VALUE_STRING(PROXY_MM_EV_RX_AUTH_TUPLES),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct osmo_fsm proxy_mm_fsm;
|
||||
static struct osmo_fsm proxy_to_home_fsm;
|
||||
|
||||
struct osmo_tdef proxy_mm_tdefs[] = {
|
||||
// FIXME
|
||||
{ .T=-1, .default_val=5, .desc="proxy_mm ready timeout" },
|
||||
{ .T=-2, .default_val=5, .desc="proxy_mm wait_subscr_data timeout" },
|
||||
{ .T=-3, .default_val=5, .desc="proxy_mm wait_gsup_isd_result timeout" },
|
||||
{ .T=-4, .default_val=5, .desc="proxy_mm wait_auth_tuples timeout" },
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct osmo_tdef_state_timeout proxy_mm_fsm_timeouts[32] = {
|
||||
// FIXME
|
||||
[PROXY_MM_ST_READY] = { .T=-1 },
|
||||
[PROXY_MM_ST_WAIT_SUBSCR_DATA] = { .T=-2 },
|
||||
[PROXY_MM_ST_WAIT_GSUP_ISD_RESULT] = { .T=-3 },
|
||||
[PROXY_MM_ST_WAIT_AUTH_TUPLES] = { .T=-4 },
|
||||
};
|
||||
|
||||
#define proxy_mm_fsm_state_chg(state) \
|
||||
osmo_tdef_fsm_inst_state_chg(mm_fi, state, \
|
||||
proxy_mm_fsm_timeouts, \
|
||||
proxy_mm_tdefs, \
|
||||
5)
|
||||
|
||||
LLIST_HEAD(proxy_mm_list);
|
||||
|
||||
struct proxy_mm *proxy_mm_alloc(const struct osmo_gsup_peer_id *vlr_name,
|
||||
bool is_ps,
|
||||
const char *imsi)
|
||||
{
|
||||
struct proxy_mm *proxy_mm;
|
||||
|
||||
struct osmo_fsm_inst *mm_fi = osmo_fsm_inst_alloc(&proxy_mm_fsm, g_hlr, NULL, LOGL_DEBUG, imsi);
|
||||
OSMO_ASSERT(mm_fi);
|
||||
|
||||
proxy_mm = talloc(mm_fi, struct proxy_mm);
|
||||
OSMO_ASSERT(proxy_mm);
|
||||
mm_fi->priv = proxy_mm;
|
||||
*proxy_mm = (struct proxy_mm){
|
||||
.mm_fi = mm_fi,
|
||||
.is_ps = is_ps,
|
||||
};
|
||||
OSMO_STRLCPY_ARRAY(proxy_mm->imsi, imsi);
|
||||
INIT_LLIST_HEAD(&proxy_mm->auth_cache);
|
||||
|
||||
llist_add(&proxy_mm->entry, &proxy_mm_list);
|
||||
|
||||
proxy_mm->to_home_fi = osmo_fsm_inst_alloc_child(&proxy_to_home_fsm, mm_fi, PROXY_MM_EV_SUBSCR_INVALID);
|
||||
proxy_mm->to_home_fi->priv = proxy_mm;
|
||||
|
||||
/* Do a state change to activate timeout */
|
||||
proxy_mm_fsm_state_chg(PROXY_MM_ST_READY);
|
||||
|
||||
return proxy_mm;
|
||||
}
|
||||
|
||||
void proxy_mm_add_auth_vectors(struct proxy_mm *proxy_mm,
|
||||
const struct osmo_auth_vector *auth_vectors, size_t num_auth_vectors)
|
||||
{
|
||||
struct proxy_mm_auth_cache *ac = talloc_zero(proxy_mm, struct proxy_mm_auth_cache);
|
||||
int i;
|
||||
OSMO_ASSERT(ac);
|
||||
ac->num_auth_vectors = num_auth_vectors;
|
||||
for (i = 0; i < num_auth_vectors; i++)
|
||||
ac->auth_vectors[i] = auth_vectors[i];
|
||||
if (proxy_db_add_auth_vectors(&proxy_mm->vlr_name, ac)) {
|
||||
talloc_free(ac);
|
||||
return;
|
||||
}
|
||||
llist_add(&ac->entry, &proxy_mm->auth_cache);
|
||||
}
|
||||
|
||||
struct proxy_mm_auth_cache *proxy_mm_get_auth_vectors(struct proxy_mm *proxy_mm)
|
||||
{
|
||||
struct proxy_mm_auth_cache *i;
|
||||
struct proxy_mm_auth_cache *ac = NULL;
|
||||
|
||||
llist_for_each_entry(i, &proxy_mm->auth_cache, entry) {
|
||||
if (!ac || i->sent_to_vlr_count < ac->sent_to_vlr_count) {
|
||||
ac = i;
|
||||
}
|
||||
}
|
||||
|
||||
/* ac now points to (one of) the least used auth cache entries (or NULL if none). */
|
||||
return ac;
|
||||
}
|
||||
|
||||
void proxy_mm_discard_auth_vectors(struct proxy_mm *proxy_mm, struct proxy_mm_auth_cache *ac)
|
||||
{
|
||||
proxy_db_drop_auth_vectors(ac->db_id);
|
||||
llist_del(&ac->entry);
|
||||
talloc_free(ac);
|
||||
}
|
||||
|
||||
/* Mark given auth cache entries as sent to the VLR and clean up if necessary. */
|
||||
void proxy_mm_use_auth_vectors(struct proxy_mm *proxy_mm, struct proxy_mm_auth_cache *ac)
|
||||
{
|
||||
struct proxy_mm_auth_cache *i, *i_next;
|
||||
bool found_fresh_ac = false;
|
||||
|
||||
/* The aim is to keep at least one set of already used auth tuples in the cache. If there are still fresh ones,
|
||||
* all used auth vectors can be discarded. If there are no fresh ones left, keep only this last set. */
|
||||
|
||||
llist_for_each_entry_safe(i, i_next, &proxy_mm->auth_cache, entry) {
|
||||
if (i == ac)
|
||||
continue;
|
||||
if (i->sent_to_vlr_count) {
|
||||
/* An auth entry other than this freshly used one, which has been used before.
|
||||
* No need to keep it. */
|
||||
proxy_mm_discard_auth_vectors(proxy_mm, i);
|
||||
continue;
|
||||
}
|
||||
if (!i->sent_to_vlr_count)
|
||||
found_fresh_ac = true;
|
||||
}
|
||||
|
||||
if (found_fresh_ac) {
|
||||
/* There are still other, fresh auth vectors. */
|
||||
proxy_mm_discard_auth_vectors(proxy_mm, ac);
|
||||
} else {
|
||||
/* else, only this ac remains in the list */
|
||||
ac->sent_to_vlr_count++;
|
||||
proxy_db_auth_vectors_update_sent_count(ac);
|
||||
}
|
||||
}
|
||||
|
||||
static void proxy_mm_ready_action(struct osmo_fsm_inst *mm_fi, uint32_t event, void *data)
|
||||
{
|
||||
struct proxy *proxy = g_hlr->gs->proxy;
|
||||
struct proxy_mm *proxy_mm = mm_fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case PROXY_MM_EV_SUBSCR_INVALID:
|
||||
/* Home HLR has responded and rejected a Location Updating, or no home HLR could be found. The
|
||||
* subscriber is invalid, remove it from the cache. */
|
||||
proxy_subscr_del(proxy, proxy_mm->imsi);
|
||||
osmo_fsm_inst_term(mm_fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
break;
|
||||
|
||||
case PROXY_MM_EV_RX_GSUP_LU:
|
||||
/* The MSC asks for a LU. If we don't know details about this subscriber, then we'll have to wait for the
|
||||
* home HLR to respond. If we already know details about the subscriber, we respond immediately (with
|
||||
* Insert Subscriber Data and accept the LU), but also ask the home HLR to confirm the LU later. */
|
||||
osmo_fsm_inst_dispatch(proxy_mm->to_home_fi, PROXY_TO_HOME_EV_CONFIRM_LU, NULL);
|
||||
|
||||
if (proxy_mm_subscriber_data_known(proxy_mm))
|
||||
proxy_mm_fsm_state_chg(PROXY_MM_ST_WAIT_GSUP_ISD_RESULT);
|
||||
else
|
||||
proxy_mm_fsm_state_chg(PROXY_MM_ST_WAIT_SUBSCR_DATA);
|
||||
break;
|
||||
|
||||
case PROXY_MM_EV_RX_GSUP_SAI:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static int proxy_mm_ready_timeout(struct osmo_fsm_inst *mm_fi)
|
||||
{
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
return 1;
|
||||
}
|
||||
|
||||
void proxy_mm_wait_subscr_data_onenter(struct osmo_fsm_inst *mm_fi, uint32_t prev_state)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = mm_fi->priv;
|
||||
// FIXME
|
||||
}
|
||||
|
||||
static void proxy_mm_wait_subscr_data_action(struct osmo_fsm_inst *mm_fi, uint32_t event, void *data)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = mm_fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case PROXY_MM_EV_RX_SUBSCR_DATA:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static int proxy_mm_wait_subscr_data_timeout(struct osmo_fsm_inst *mm_fi)
|
||||
{
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void proxy_mm_lu_error(struct osmo_fsm_inst *mm_fi)
|
||||
{
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_ROAMING_NOTALLOWED,
|
||||
"LU does not accept GSUP rx");
|
||||
|
||||
}
|
||||
|
||||
void proxy_mm_wait_gsup_isd_result_onenter(struct osmo_fsm_inst *mm_fi, uint32_t prev_state)
|
||||
{
|
||||
struct proxy_mm *proxy_mm = mm_fi->priv;
|
||||
struct proxy_subscr proxy_subscr;
|
||||
struct osmo_gsup_message isd_req;
|
||||
|
||||
uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
|
||||
uint8_t apn[APN_MAXLEN];
|
||||
|
||||
isd_req.message_type = OSMO_GSUP_MSGT_INSERT_DATA_REQUEST;
|
||||
|
||||
if (proxy_subscr_get_by_imsi(&proxy_subscr, g_hlr->gs->proxy, proxy_mm->imsi)) {
|
||||
LOGPFSML(mm_fi, LOGL_ERROR,
|
||||
"Proxy: trying to send cached Subscriber Data, but there is no proxy entry\n");
|
||||
proxy_mm_lu_error(mm_fi);
|
||||
return;
|
||||
}
|
||||
|
||||
if (proxy_subscr.msisdn[0] == '\0') {
|
||||
LOGPFSML(mm_fi, LOGL_ERROR,
|
||||
"Proxy: trying to send cached Subscriber Data, but subscriber has no MSISDN in proxy cache\n");
|
||||
proxy_mm_lu_error(mm_fi);
|
||||
return;
|
||||
}
|
||||
|
||||
if (osmo_gsup_create_insert_subscriber_data_msg(&isd_req, proxy_mm->imsi,
|
||||
proxy_subscr->msisdn, msisdn_enc, sizeof(msisdn_enc),
|
||||
apn, sizeof(apn),
|
||||
proxy_mm->is_ps? OSMO_GSUP_CN_DOMAIN_PS : OSMO_GSUP_CN_DOMAIN_CS)) {
|
||||
LOGPFSML(mm_fi, LOGL_ERROR, "Proxy: failed to send cached Subscriber Data\n");
|
||||
proxy_mm_lu_error(mm_fi);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void proxy_mm_wait_gsup_isd_result_action(struct osmo_fsm_inst *mm_fi, uint32_t event, void *data)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = mm_fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case PROXY_MM_EV_RX_GSUP_ISD_RESULT:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static int proxy_mm_wait_gsup_isd_result_timeout(struct osmo_fsm_inst *mm_fi)
|
||||
{
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
return 1;
|
||||
}
|
||||
|
||||
void proxy_mm_wait_auth_tuples_onenter(struct osmo_fsm_inst *mm_fi, uint32_t prev_state)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = mm_fi->priv;
|
||||
// FIXME
|
||||
}
|
||||
|
||||
static void proxy_mm_wait_auth_tuples_action(struct osmo_fsm_inst *mm_fi, uint32_t event, void *data)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = mm_fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case PROXY_MM_EV_RX_AUTH_TUPLES:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static int proxy_mm_wait_auth_tuples_timeout(struct osmo_fsm_inst *mm_fi)
|
||||
{
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define S(x) (1 << (x))
|
||||
|
||||
static const struct osmo_fsm_state proxy_mm_fsm_states[] = {
|
||||
[PROXY_MM_ST_READY] = {
|
||||
.name = "ready",
|
||||
.in_event_mask = 0
|
||||
| S(PROXY_MM_EV_SUBSCR_INVALID)
|
||||
| S(PROXY_MM_EV_RX_GSUP_LU)
|
||||
| S(PROXY_MM_EV_RX_GSUP_SAI)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(PROXY_MM_ST_READY)
|
||||
| S(PROXY_MM_ST_WAIT_SUBSCR_DATA)
|
||||
| S(PROXY_MM_ST_WAIT_GSUP_ISD_RESULT)
|
||||
| S(PROXY_MM_ST_WAIT_AUTH_TUPLES)
|
||||
,
|
||||
.action = proxy_mm_ready_action,
|
||||
},
|
||||
[PROXY_MM_ST_WAIT_SUBSCR_DATA] = {
|
||||
.name = "wait_subscr_data",
|
||||
.in_event_mask = 0
|
||||
| S(PROXY_MM_EV_RX_SUBSCR_DATA)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(PROXY_MM_ST_WAIT_GSUP_ISD_RESULT)
|
||||
| S(PROXY_MM_ST_READY)
|
||||
,
|
||||
.onenter = proxy_mm_wait_subscr_data_onenter,
|
||||
.action = proxy_mm_wait_subscr_data_action,
|
||||
},
|
||||
[PROXY_MM_ST_WAIT_GSUP_ISD_RESULT] = {
|
||||
.name = "wait_gsup_isd_result",
|
||||
.in_event_mask = 0
|
||||
| S(PROXY_MM_EV_RX_GSUP_ISD_RESULT)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(PROXY_MM_ST_READY)
|
||||
,
|
||||
.onenter = proxy_mm_wait_gsup_isd_result_onenter,
|
||||
.action = proxy_mm_wait_gsup_isd_result_action,
|
||||
},
|
||||
[PROXY_MM_ST_WAIT_AUTH_TUPLES] = {
|
||||
.name = "wait_auth_tuples",
|
||||
.in_event_mask = 0
|
||||
| S(PROXY_MM_EV_RX_AUTH_TUPLES)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(PROXY_MM_ST_READY)
|
||||
,
|
||||
.onenter = proxy_mm_wait_auth_tuples_onenter,
|
||||
.action = proxy_mm_wait_auth_tuples_action,
|
||||
},
|
||||
};
|
||||
|
||||
static int proxy_mm_fsm_timer_cb(struct osmo_fsm_inst *mm_fi)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = mm_fi->priv;
|
||||
switch (mm_fi->state) {
|
||||
|
||||
case PROXY_MM_ST_READY:
|
||||
return proxy_mm_ready_timeout(mm_fi);
|
||||
|
||||
case PROXY_MM_ST_WAIT_SUBSCR_DATA:
|
||||
return proxy_mm_wait_subscr_data_timeout(mm_fi);
|
||||
|
||||
case PROXY_MM_ST_WAIT_GSUP_ISD_RESULT:
|
||||
return proxy_mm_wait_gsup_isd_result_timeout(mm_fi);
|
||||
|
||||
case PROXY_MM_ST_WAIT_AUTH_TUPLES:
|
||||
return proxy_mm_wait_auth_tuples_timeout(mm_fi);
|
||||
|
||||
default:
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void proxy_mm_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
||||
{
|
||||
struct proxy_mm *proxy_mm = fi->priv;
|
||||
llist_del(&proxy_mm->entry);
|
||||
}
|
||||
|
||||
static struct osmo_fsm proxy_mm_fsm = {
|
||||
.name = "proxy_mm",
|
||||
.states = proxy_mm_fsm_states,
|
||||
.num_states = ARRAY_SIZE(proxy_mm_fsm_states),
|
||||
.log_subsys = DLGLOBAL, // FIXME
|
||||
.event_names = proxy_mm_fsm_event_names,
|
||||
.timer_cb = proxy_mm_fsm_timer_cb,
|
||||
.cleanup = proxy_mm_fsm_cleanup,
|
||||
};
|
||||
|
||||
static __attribute__((constructor)) void proxy_mm_fsm_register(void)
|
||||
{
|
||||
OSMO_ASSERT(osmo_fsm_register(&proxy_mm_fsm) == 0);
|
||||
}
|
||||
|
||||
bool proxy_mm_subscriber_data_known(const struct proxy_mm *proxy_mm)
|
||||
{
|
||||
struct proxy_subscr proxy_subscr;
|
||||
if (proxy_subscr_get_by_imsi(&proxy_subscr, g_hlr->gs->proxy, proxy_mm->imsi))
|
||||
return false;
|
||||
return proxy_subscr.msisdn[0] != '\0';
|
||||
}
|
439
src/proxy_to_home.c
Normal file
439
src/proxy_to_home.c
Normal file
@@ -0,0 +1,439 @@
|
||||
|
||||
#include <osmocom/hlr/proxy_mm.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
#include <osmocom/core/tdef.h>
|
||||
|
||||
enum proxy_to_home_fsm_state {
|
||||
PROXY_TO_HOME_ST_WAIT_HOME_HLR_RESOLVED,
|
||||
PROXY_TO_HOME_ST_WAIT_UPDATE_LOCATION_RESULT,
|
||||
PROXY_TO_HOME_ST_WAIT_SEND_AUTH_INFO_RESULT,
|
||||
PROXY_TO_HOME_ST_IDLE,
|
||||
PROXY_TO_HOME_ST_CLEAR,
|
||||
};
|
||||
|
||||
static const struct value_string proxy_to_home_fsm_event_names[] = {
|
||||
OSMO_VALUE_STRING(PROXY_TO_HOME_EV_HOME_HLR_RESOLVED),
|
||||
OSMO_VALUE_STRING(PROXY_TO_HOME_EV_RX_INSERT_SUBSCRIBER_DATA_REQ),
|
||||
OSMO_VALUE_STRING(PROXY_TO_HOME_EV_RX_UPDATE_LOCATION_RESULT),
|
||||
OSMO_VALUE_STRING(PROXY_TO_HOME_EV_RX_SEND_AUTH_INFO_RESULT),
|
||||
OSMO_VALUE_STRING(PROXY_TO_HOME_EV_CHECK_TUPLES),
|
||||
OSMO_VALUE_STRING(PROXY_TO_HOME_EV_CONFIRM_LU),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct osmo_fsm proxy_to_home_fsm;
|
||||
|
||||
struct osmo_tdef proxy_to_home_tdefs[] = {
|
||||
// FIXME
|
||||
{ .T=-1, .default_val=5, .desc="proxy_to_home wait_home_hlr_resolved timeout" },
|
||||
{ .T=-2, .default_val=5, .desc="proxy_to_home wait_update_location_result timeout" },
|
||||
{ .T=-3, .default_val=5, .desc="proxy_to_home wait_send_auth_info_result timeout" },
|
||||
{ .T=-4, .default_val=5, .desc="proxy_to_home idle timeout" },
|
||||
{ .T=-5, .default_val=5, .desc="proxy_to_home clear timeout" },
|
||||
{}
|
||||
};
|
||||
|
||||
#if 0
|
||||
static const struct osmo_tdef_state_timeout proxy_to_home_fsm_timeouts[32] = {
|
||||
// FIXME
|
||||
[PROXY_TO_HOME_ST_WAIT_HOME_HLR_RESOLVED] = { .T=-1 },
|
||||
[PROXY_TO_HOME_ST_WAIT_UPDATE_LOCATION_RESULT] = { .T=-2 },
|
||||
[PROXY_TO_HOME_ST_WAIT_SEND_AUTH_INFO_RESULT] = { .T=-3 },
|
||||
[PROXY_TO_HOME_ST_IDLE] = { .T=-4 },
|
||||
[PROXY_TO_HOME_ST_CLEAR] = { .T=-5 },
|
||||
};
|
||||
#endif
|
||||
|
||||
#define proxy_to_home_fsm_state_chg(state) \
|
||||
osmo_tdef_fsm_inst_state_chg(fi, state, \
|
||||
proxy_to_home_fsm_timeouts, \
|
||||
proxy_to_home_tdefs, \
|
||||
5)
|
||||
|
||||
void proxy_to_home_wait_home_hlr_resolved_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = fi->priv;
|
||||
// FIXME
|
||||
}
|
||||
|
||||
static void proxy_to_home_wait_home_hlr_resolved_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case PROXY_TO_HOME_EV_HOME_HLR_RESOLVED:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_INSERT_SUBSCRIBER_DATA_REQ:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_UPDATE_LOCATION_RESULT:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_SEND_AUTH_INFO_RESULT:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_CHECK_TUPLES:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_CONFIRM_LU:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static int proxy_to_home_wait_home_hlr_resolved_timeout(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
return 1;
|
||||
}
|
||||
|
||||
void proxy_to_home_wait_update_location_result_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = fi->priv;
|
||||
// FIXME
|
||||
}
|
||||
|
||||
static void proxy_to_home_wait_update_location_result_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case PROXY_TO_HOME_EV_HOME_HLR_RESOLVED:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_INSERT_SUBSCRIBER_DATA_REQ:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_UPDATE_LOCATION_RESULT:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_SEND_AUTH_INFO_RESULT:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_CHECK_TUPLES:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_CONFIRM_LU:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static int proxy_to_home_wait_update_location_result_timeout(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
return 1;
|
||||
}
|
||||
|
||||
void proxy_to_home_wait_send_auth_info_result_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = fi->priv;
|
||||
// FIXME
|
||||
}
|
||||
|
||||
static void proxy_to_home_wait_send_auth_info_result_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case PROXY_TO_HOME_EV_HOME_HLR_RESOLVED:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_INSERT_SUBSCRIBER_DATA_REQ:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_UPDATE_LOCATION_RESULT:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_SEND_AUTH_INFO_RESULT:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_CHECK_TUPLES:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_CONFIRM_LU:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static int proxy_to_home_wait_send_auth_info_result_timeout(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
return 1;
|
||||
}
|
||||
|
||||
void proxy_to_home_idle_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = fi->priv;
|
||||
// FIXME
|
||||
}
|
||||
|
||||
static void proxy_to_home_idle_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case PROXY_TO_HOME_EV_HOME_HLR_RESOLVED:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_INSERT_SUBSCRIBER_DATA_REQ:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_UPDATE_LOCATION_RESULT:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_SEND_AUTH_INFO_RESULT:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_CHECK_TUPLES:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_CONFIRM_LU:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static int proxy_to_home_idle_timeout(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
return 1;
|
||||
}
|
||||
|
||||
void proxy_to_home_clear_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = fi->priv;
|
||||
// FIXME
|
||||
}
|
||||
|
||||
static void proxy_to_home_clear_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case PROXY_TO_HOME_EV_HOME_HLR_RESOLVED:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_INSERT_SUBSCRIBER_DATA_REQ:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_UPDATE_LOCATION_RESULT:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_RX_SEND_AUTH_INFO_RESULT:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_CHECK_TUPLES:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case PROXY_TO_HOME_EV_CONFIRM_LU:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static int proxy_to_home_clear_timeout(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define S(x) (1 << (x))
|
||||
|
||||
static const struct osmo_fsm_state proxy_to_home_fsm_states[] = {
|
||||
[PROXY_TO_HOME_ST_WAIT_HOME_HLR_RESOLVED] = {
|
||||
.name = "wait_home_hlr_resolved",
|
||||
.in_event_mask = 0
|
||||
| S(PROXY_TO_HOME_EV_HOME_HLR_RESOLVED)
|
||||
| S(PROXY_TO_HOME_EV_RX_INSERT_SUBSCRIBER_DATA_REQ)
|
||||
| S(PROXY_TO_HOME_EV_RX_UPDATE_LOCATION_RESULT)
|
||||
| S(PROXY_TO_HOME_EV_RX_SEND_AUTH_INFO_RESULT)
|
||||
| S(PROXY_TO_HOME_EV_CHECK_TUPLES)
|
||||
| S(PROXY_TO_HOME_EV_CONFIRM_LU)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(PROXY_TO_HOME_ST_WAIT_HOME_HLR_RESOLVED)
|
||||
| S(PROXY_TO_HOME_ST_WAIT_UPDATE_LOCATION_RESULT)
|
||||
| S(PROXY_TO_HOME_ST_WAIT_SEND_AUTH_INFO_RESULT)
|
||||
| S(PROXY_TO_HOME_ST_IDLE)
|
||||
| S(PROXY_TO_HOME_ST_CLEAR)
|
||||
,
|
||||
.onenter = proxy_to_home_wait_home_hlr_resolved_onenter,
|
||||
.action = proxy_to_home_wait_home_hlr_resolved_action,
|
||||
},
|
||||
[PROXY_TO_HOME_ST_WAIT_UPDATE_LOCATION_RESULT] = {
|
||||
.name = "wait_update_location_result",
|
||||
.in_event_mask = 0
|
||||
| S(PROXY_TO_HOME_EV_HOME_HLR_RESOLVED)
|
||||
| S(PROXY_TO_HOME_EV_RX_INSERT_SUBSCRIBER_DATA_REQ)
|
||||
| S(PROXY_TO_HOME_EV_RX_UPDATE_LOCATION_RESULT)
|
||||
| S(PROXY_TO_HOME_EV_RX_SEND_AUTH_INFO_RESULT)
|
||||
| S(PROXY_TO_HOME_EV_CHECK_TUPLES)
|
||||
| S(PROXY_TO_HOME_EV_CONFIRM_LU)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(PROXY_TO_HOME_ST_WAIT_HOME_HLR_RESOLVED)
|
||||
| S(PROXY_TO_HOME_ST_WAIT_UPDATE_LOCATION_RESULT)
|
||||
| S(PROXY_TO_HOME_ST_WAIT_SEND_AUTH_INFO_RESULT)
|
||||
| S(PROXY_TO_HOME_ST_IDLE)
|
||||
| S(PROXY_TO_HOME_ST_CLEAR)
|
||||
,
|
||||
.onenter = proxy_to_home_wait_update_location_result_onenter,
|
||||
.action = proxy_to_home_wait_update_location_result_action,
|
||||
},
|
||||
[PROXY_TO_HOME_ST_WAIT_SEND_AUTH_INFO_RESULT] = {
|
||||
.name = "wait_send_auth_info_result",
|
||||
.in_event_mask = 0
|
||||
| S(PROXY_TO_HOME_EV_HOME_HLR_RESOLVED)
|
||||
| S(PROXY_TO_HOME_EV_RX_INSERT_SUBSCRIBER_DATA_REQ)
|
||||
| S(PROXY_TO_HOME_EV_RX_UPDATE_LOCATION_RESULT)
|
||||
| S(PROXY_TO_HOME_EV_RX_SEND_AUTH_INFO_RESULT)
|
||||
| S(PROXY_TO_HOME_EV_CHECK_TUPLES)
|
||||
| S(PROXY_TO_HOME_EV_CONFIRM_LU)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(PROXY_TO_HOME_ST_WAIT_HOME_HLR_RESOLVED)
|
||||
| S(PROXY_TO_HOME_ST_WAIT_UPDATE_LOCATION_RESULT)
|
||||
| S(PROXY_TO_HOME_ST_WAIT_SEND_AUTH_INFO_RESULT)
|
||||
| S(PROXY_TO_HOME_ST_IDLE)
|
||||
| S(PROXY_TO_HOME_ST_CLEAR)
|
||||
,
|
||||
.onenter = proxy_to_home_wait_send_auth_info_result_onenter,
|
||||
.action = proxy_to_home_wait_send_auth_info_result_action,
|
||||
},
|
||||
[PROXY_TO_HOME_ST_IDLE] = {
|
||||
.name = "idle",
|
||||
.in_event_mask = 0
|
||||
| S(PROXY_TO_HOME_EV_HOME_HLR_RESOLVED)
|
||||
| S(PROXY_TO_HOME_EV_RX_INSERT_SUBSCRIBER_DATA_REQ)
|
||||
| S(PROXY_TO_HOME_EV_RX_UPDATE_LOCATION_RESULT)
|
||||
| S(PROXY_TO_HOME_EV_RX_SEND_AUTH_INFO_RESULT)
|
||||
| S(PROXY_TO_HOME_EV_CHECK_TUPLES)
|
||||
| S(PROXY_TO_HOME_EV_CONFIRM_LU)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(PROXY_TO_HOME_ST_WAIT_HOME_HLR_RESOLVED)
|
||||
| S(PROXY_TO_HOME_ST_WAIT_UPDATE_LOCATION_RESULT)
|
||||
| S(PROXY_TO_HOME_ST_WAIT_SEND_AUTH_INFO_RESULT)
|
||||
| S(PROXY_TO_HOME_ST_IDLE)
|
||||
| S(PROXY_TO_HOME_ST_CLEAR)
|
||||
,
|
||||
.onenter = proxy_to_home_idle_onenter,
|
||||
.action = proxy_to_home_idle_action,
|
||||
},
|
||||
[PROXY_TO_HOME_ST_CLEAR] = {
|
||||
.name = "clear",
|
||||
.in_event_mask = 0
|
||||
| S(PROXY_TO_HOME_EV_HOME_HLR_RESOLVED)
|
||||
| S(PROXY_TO_HOME_EV_RX_INSERT_SUBSCRIBER_DATA_REQ)
|
||||
| S(PROXY_TO_HOME_EV_RX_UPDATE_LOCATION_RESULT)
|
||||
| S(PROXY_TO_HOME_EV_RX_SEND_AUTH_INFO_RESULT)
|
||||
| S(PROXY_TO_HOME_EV_CHECK_TUPLES)
|
||||
| S(PROXY_TO_HOME_EV_CONFIRM_LU)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(PROXY_TO_HOME_ST_WAIT_HOME_HLR_RESOLVED)
|
||||
| S(PROXY_TO_HOME_ST_WAIT_UPDATE_LOCATION_RESULT)
|
||||
| S(PROXY_TO_HOME_ST_WAIT_SEND_AUTH_INFO_RESULT)
|
||||
| S(PROXY_TO_HOME_ST_IDLE)
|
||||
| S(PROXY_TO_HOME_ST_CLEAR)
|
||||
,
|
||||
.onenter = proxy_to_home_clear_onenter,
|
||||
.action = proxy_to_home_clear_action,
|
||||
},
|
||||
};
|
||||
|
||||
static int proxy_to_home_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = fi->priv;
|
||||
switch (fi->state) {
|
||||
|
||||
case PROXY_TO_HOME_ST_WAIT_HOME_HLR_RESOLVED:
|
||||
return proxy_to_home_wait_home_hlr_resolved_timeout(fi);
|
||||
|
||||
case PROXY_TO_HOME_ST_WAIT_UPDATE_LOCATION_RESULT:
|
||||
return proxy_to_home_wait_update_location_result_timeout(fi);
|
||||
|
||||
case PROXY_TO_HOME_ST_WAIT_SEND_AUTH_INFO_RESULT:
|
||||
return proxy_to_home_wait_send_auth_info_result_timeout(fi);
|
||||
|
||||
case PROXY_TO_HOME_ST_IDLE:
|
||||
return proxy_to_home_idle_timeout(fi);
|
||||
|
||||
case PROXY_TO_HOME_ST_CLEAR:
|
||||
return proxy_to_home_clear_timeout(fi);
|
||||
|
||||
default:
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void proxy_to_home_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
||||
{
|
||||
//struct proxy_mm *proxy_mm = fi->priv;
|
||||
// FIXME
|
||||
}
|
||||
|
||||
static struct osmo_fsm proxy_to_home_fsm = {
|
||||
.name = "proxy_to_home",
|
||||
.states = proxy_to_home_fsm_states,
|
||||
.num_states = ARRAY_SIZE(proxy_to_home_fsm_states),
|
||||
.log_subsys = DLGLOBAL, // FIXME
|
||||
.event_names = proxy_to_home_fsm_event_names,
|
||||
.timer_cb = proxy_to_home_fsm_timer_cb,
|
||||
.cleanup = proxy_to_home_fsm_cleanup,
|
||||
};
|
||||
|
||||
static __attribute__((constructor)) void proxy_to_home_fsm_register(void)
|
||||
{
|
||||
OSMO_ASSERT(osmo_fsm_register(&proxy_to_home_fsm) == 0);
|
||||
}
|
252
src/remote_hlr.c
Normal file
252
src/remote_hlr.c
Normal file
@@ -0,0 +1,252 @@
|
||||
/* 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 <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);
|
||||
|
||||
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;
|
||||
|
||||
/* 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_STRLCPY_ARRAY(gsup_reply.imsi, gsup_orig->imsi);
|
||||
|
||||
/* For SS/USSD, it's important to keep both session state and ID IEs */
|
||||
if (gsup_orig->session_state != OSMO_GSUP_SESSION_STATE_NONE) {
|
||||
gsup_reply.session_state = OSMO_GSUP_SESSION_STATE_END;
|
||||
gsup_reply.session_id = gsup_orig->session_id;
|
||||
}
|
||||
|
||||
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 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;
|
||||
int rc;
|
||||
|
||||
rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
|
||||
if (rc < 0) {
|
||||
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 (!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 (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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
LOG_REMOTE_HLR(remote_hlr, LOGL_NOTICE, "link to remote HLR is down, removing GSUP client\n");
|
||||
remote_hlr_destroy(remote_hlr);
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool remote_hlr_is_up(struct remote_hlr *remote_hlr)
|
||||
{
|
||||
return remote_hlr && remote_hlr->gsupc && remote_hlr->gsupc->is_connected;
|
||||
}
|
||||
|
||||
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 (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, &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;
|
||||
}
|
||||
|
||||
void remote_hlr_destroy(struct remote_hlr *remote_hlr)
|
||||
{
|
||||
osmo_gsup_client_destroy(remote_hlr->gsupc);
|
||||
remote_hlr->gsupc = NULL;
|
||||
llist_del(&remote_hlr->entry);
|
||||
talloc_free(remote_hlr);
|
||||
}
|
||||
|
||||
/* This function takes ownership of the msg, do not free it after passing to this function. */
|
||||
int remote_hlr_msgb_send(struct remote_hlr *remote_hlr, struct msgb *msg)
|
||||
{
|
||||
int rc = osmo_gsup_client_send(remote_hlr->gsupc, msg);
|
||||
if (rc) {
|
||||
LOGP(DDGSM, LOGL_ERROR, "Failed to send GSUP message to " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
/* 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;
|
||||
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");
|
||||
return;
|
||||
}
|
||||
remote_hlr_msgb_send(remote_hlr, msg);
|
||||
osmo_gsup_req_free(req);
|
||||
}
|
53
src/timestamp.c
Normal file
53
src/timestamp.c
Normal 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;
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ SUBDIRS = \
|
||||
auc \
|
||||
gsup_server \
|
||||
db \
|
||||
gsup \
|
||||
db_upgrade \
|
||||
mslookup \
|
||||
$(NULL)
|
||||
|
@@ -30,6 +30,7 @@ db_test_LDADD = \
|
||||
$(top_builddir)/src/db_auc.o \
|
||||
$(top_builddir)/src/db_hlr.o \
|
||||
$(top_builddir)/src/db.o \
|
||||
$(top_builddir)/src/gsup_peer_id.o \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
$(LIBOSMOABIS_LIBS) \
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
|
||||
#include <osmocom/gsupclient/gsup_peer_id.h>
|
||||
#include <osmocom/hlr/db.h>
|
||||
#include <osmocom/hlr/logging.h>
|
||||
|
||||
@@ -145,6 +146,8 @@ void dump_subscr(struct hlr_subscriber *subscr)
|
||||
#define Ps(name) \
|
||||
if (*subscr->name) \
|
||||
Pfo(name, "'%s'", subscr)
|
||||
#define Pgt(name) \
|
||||
Pfv(name, "%s", osmo_ipa_name_to_str(&subscr->name))
|
||||
#define Pd(name) \
|
||||
Pfv(name, "%"PRId64, (int64_t)subscr->name)
|
||||
#define Pd_nonzero(name) \
|
||||
@@ -235,6 +238,14 @@ static const char *imsi2 = "123456789000002";
|
||||
static const char *short_imsi = "123456";
|
||||
static const char *unknown_imsi = "999999999";
|
||||
|
||||
static int db_subscr_lu_str(struct db_context *dbc, int64_t subscr_id,
|
||||
const char *vlr_or_sgsn_number, bool is_ps)
|
||||
{
|
||||
struct osmo_ipa_name vlr_nr;
|
||||
osmo_ipa_name_set_str(&vlr_nr, vlr_or_sgsn_number);
|
||||
return db_subscr_lu(dbc, subscr_id, &vlr_nr, is_ps, NULL);
|
||||
}
|
||||
|
||||
static void test_subscr_create_update_sel_delete()
|
||||
{
|
||||
int64_t id0, id1, id2, id_short;
|
||||
@@ -386,39 +397,39 @@ static void test_subscr_create_update_sel_delete()
|
||||
|
||||
comment("Record LU for PS and CS (SGSN and VLR names)");
|
||||
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "5952", true), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "5952", true), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "712", false), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "712", false), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
|
||||
comment("Record LU for PS and CS (SGSN and VLR names) *again*");
|
||||
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
|
||||
comment("Unset LU info for PS and CS (SGSN and VLR names)");
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "", true), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "", true), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "", false), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "", false), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, NULL, true), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, NULL, true), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, NULL, false), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, NULL, false), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
|
||||
comment("Record LU for non-existent ID");
|
||||
ASSERT_RC(db_subscr_lu(dbc, 99999, "5952", true), -ENOENT);
|
||||
ASSERT_RC(db_subscr_lu(dbc, 99999, "712", false), -ENOENT);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, 99999, "5952", true), -ENOENT);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, 99999, "712", false), -ENOENT);
|
||||
ASSERT_SEL(id, 99999, -ENOENT);
|
||||
|
||||
comment("Purge and un-purge PS and CS");
|
||||
@@ -907,6 +918,50 @@ static void test_subscr_sqn()
|
||||
comment_end();
|
||||
}
|
||||
|
||||
static void test_ind()
|
||||
{
|
||||
comment_start();
|
||||
|
||||
#define ASSERT_IND(VLR, IND) do { \
|
||||
unsigned int ind; \
|
||||
struct osmo_gsup_peer_id vlr; \
|
||||
OSMO_ASSERT(!osmo_gsup_peer_id_set_str(&vlr, OSMO_GSUP_PEER_ID_IPA_NAME, VLR)); \
|
||||
ASSERT_RC(db_ind(dbc, &vlr, &ind), 0); \
|
||||
fprintf(stderr, "%s ind = %u\n\n", osmo_quote_str((char*)vlr.ipa_name.val, vlr.ipa_name.len), ind); \
|
||||
if (ind != (IND)) \
|
||||
fprintf(stderr, " ERROR: expected " #IND "\n"); \
|
||||
} while (0)
|
||||
#define IND_DEL(VLR) do { \
|
||||
struct osmo_gsup_peer_id vlr; \
|
||||
OSMO_ASSERT(!osmo_gsup_peer_id_set_str(&vlr, OSMO_GSUP_PEER_ID_IPA_NAME, VLR)); \
|
||||
ASSERT_RC(db_ind_del(dbc, &vlr), 0); \
|
||||
fprintf(stderr, "%s ind deleted\n\n", osmo_quote_str((char*)vlr.ipa_name.val, vlr.ipa_name.len)); \
|
||||
} while (0)
|
||||
|
||||
ASSERT_IND("msc-23", 1);
|
||||
ASSERT_IND("sgsn-11", 2);
|
||||
ASSERT_IND("msc-42", 3);
|
||||
ASSERT_IND("sgsn-22", 4);
|
||||
ASSERT_IND("msc-0x17", 5);
|
||||
ASSERT_IND("sgsn-0xaa", 6);
|
||||
ASSERT_IND("msc-42", 3);
|
||||
ASSERT_IND("sgsn-22", 4);
|
||||
ASSERT_IND("msc-0x17", 5);
|
||||
ASSERT_IND("sgsn-0xaa", 6);
|
||||
ASSERT_IND("sgsn-0xbb", 7);
|
||||
ASSERT_IND("msc-0x2a", 8);
|
||||
ASSERT_IND("msc-42", 3);
|
||||
ASSERT_IND("sgsn-22", 4);
|
||||
ASSERT_IND("msc-23", 1);
|
||||
ASSERT_IND("sgsn-11", 2);
|
||||
|
||||
IND_DEL("msc-0x17"); /* dropped IND == 5 */
|
||||
ASSERT_IND("msc-0x2a", 8); /* known CS remains where it is */
|
||||
ASSERT_IND("any-unknown", 9); /* new VLR takes a new IND from the end */
|
||||
|
||||
comment_end();
|
||||
}
|
||||
|
||||
static struct {
|
||||
bool verbose;
|
||||
} cmdline_opts = {
|
||||
@@ -987,6 +1042,7 @@ int main(int argc, char **argv)
|
||||
test_subscr_aud();
|
||||
test_subscr_aud_invalid_len();
|
||||
test_subscr_sqn();
|
||||
test_ind();
|
||||
|
||||
printf("Done\n");
|
||||
db_close(dbc);
|
||||
|
@@ -64,25 +64,21 @@ db_subscr_create(dbc, "123456789 000003", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG
|
||||
DAUC Cannot create subscriber: invalid IMSI: '123456789 000003'
|
||||
|
||||
db_subscr_get_by_imsi(dbc, "123456789000003", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123456789000003': No such subscriber
|
||||
|
||||
db_subscr_create(dbc, "123456789000002123456", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> -EINVAL
|
||||
DAUC Cannot create subscriber: invalid IMSI: '123456789000002123456'
|
||||
|
||||
db_subscr_get_by_imsi(dbc, "123456789000002123456", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123456789000002123456': No such subscriber
|
||||
|
||||
db_subscr_create(dbc, "foobar123", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> -EINVAL
|
||||
DAUC Cannot create subscriber: invalid IMSI: 'foobar123'
|
||||
|
||||
db_subscr_get_by_imsi(dbc, "foobar123", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='foobar123': No such subscriber
|
||||
|
||||
db_subscr_create(dbc, "123", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> -EINVAL
|
||||
DAUC Cannot create subscriber: invalid IMSI: '123'
|
||||
|
||||
db_subscr_get_by_imsi(dbc, "123", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123': No such subscriber
|
||||
|
||||
db_subscr_create(dbc, short_imsi, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> 0
|
||||
|
||||
@@ -142,7 +138,6 @@ struct hlr_subscriber {
|
||||
}
|
||||
|
||||
db_subscr_get_by_msisdn(dbc, "54321012345678912345678", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: MSISDN='54321012345678912345678': No such subscriber
|
||||
|
||||
db_subscr_update_msisdn_by_imsi(dbc, imsi0, "543 21") --> -EINVAL
|
||||
DAUC IMSI='123456789000000': Cannot update subscriber: invalid MSISDN: '543 21'
|
||||
@@ -155,7 +150,6 @@ struct hlr_subscriber {
|
||||
}
|
||||
|
||||
db_subscr_get_by_msisdn(dbc, "543 21", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: MSISDN='543 21': No such subscriber
|
||||
|
||||
db_subscr_update_msisdn_by_imsi(dbc, imsi0, "foobar123") --> -EINVAL
|
||||
DAUC IMSI='123456789000000': Cannot update subscriber: invalid MSISDN: 'foobar123'
|
||||
@@ -168,7 +162,6 @@ struct hlr_subscriber {
|
||||
}
|
||||
|
||||
db_subscr_get_by_msisdn(dbc, "foobar123", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: MSISDN='foobar123': No such subscriber
|
||||
|
||||
db_subscr_update_msisdn_by_imsi(dbc, imsi0, "5") --> 0
|
||||
|
||||
@@ -187,7 +180,6 @@ struct hlr_subscriber {
|
||||
}
|
||||
|
||||
db_subscr_get_by_msisdn(dbc, "54321", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: MSISDN='54321': No such subscriber
|
||||
|
||||
db_subscr_update_msisdn_by_imsi(dbc, imsi0, "543210123456789") --> 0
|
||||
|
||||
@@ -216,7 +208,6 @@ struct hlr_subscriber {
|
||||
}
|
||||
|
||||
db_subscr_get_by_msisdn(dbc, "5432101234567891", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: MSISDN='5432101234567891': No such subscriber
|
||||
|
||||
|
||||
--- Check if subscriber exists (by MSISDN)
|
||||
@@ -232,13 +223,11 @@ db_subscr_update_msisdn_by_imsi(dbc, unknown_imsi, "99") --> -ENOENT
|
||||
DAUC Cannot update MSISDN: no such subscriber: IMSI='999999999'
|
||||
|
||||
db_subscr_get_by_msisdn(dbc, "99", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: MSISDN='99': No such subscriber
|
||||
|
||||
db_subscr_update_msisdn_by_imsi(dbc, "foobar", "99") --> -ENOENT
|
||||
DAUC Cannot update MSISDN: no such subscriber: IMSI='foobar'
|
||||
|
||||
db_subscr_get_by_msisdn(dbc, "99", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: MSISDN='99': No such subscriber
|
||||
|
||||
|
||||
--- Set valid / invalid IMEI
|
||||
@@ -265,7 +254,6 @@ struct hlr_subscriber {
|
||||
}
|
||||
|
||||
db_subscr_get_by_imei(dbc, "123456789012345", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMEI=123456789012345: No such subscriber
|
||||
|
||||
|
||||
--- Set the same IMEI again
|
||||
@@ -286,7 +274,6 @@ struct hlr_subscriber {
|
||||
db_subscr_update_imei_by_imsi(dbc, imsi0, NULL) --> 0
|
||||
|
||||
db_subscr_get_by_imei(dbc, "12345678901234", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMEI=12345678901234: No such subscriber
|
||||
|
||||
|
||||
--- Set / unset nam_cs and nam_ps
|
||||
@@ -424,7 +411,6 @@ db_subscr_nam(dbc, unknown_imsi, false, false) --> -ENOENT
|
||||
DAUC Cannot disable CS: no such subscriber: IMSI='999999999'
|
||||
|
||||
db_subscr_get_by_imsi(dbc, unknown_imsi, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='999999999': No such subscriber
|
||||
|
||||
db_subscr_nam(dbc, "foobar", false, true) --> -ENOENT
|
||||
DAUC Cannot disable PS: no such subscriber: IMSI='foobar'
|
||||
@@ -435,7 +421,7 @@ DAUC Cannot disable CS: no such subscriber: IMSI='foobar'
|
||||
|
||||
--- Record LU for PS and CS (SGSN and VLR names)
|
||||
|
||||
db_subscr_lu(dbc, id0, "5952", true) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "5952", true) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -445,7 +431,7 @@ struct hlr_subscriber {
|
||||
.sgsn_number = '5952',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, "712", false) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "712", false) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -459,7 +445,7 @@ struct hlr_subscriber {
|
||||
|
||||
--- Record LU for PS and CS (SGSN and VLR names) *again*
|
||||
|
||||
db_subscr_lu(dbc, id0, "111", true) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "111", true) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -470,7 +456,7 @@ struct hlr_subscriber {
|
||||
.sgsn_number = '111',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, "111", true) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "111", true) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -481,7 +467,7 @@ struct hlr_subscriber {
|
||||
.sgsn_number = '111',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, "222", false) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "222", false) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -492,7 +478,7 @@ struct hlr_subscriber {
|
||||
.sgsn_number = '111',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, "222", false) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "222", false) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -506,7 +492,7 @@ struct hlr_subscriber {
|
||||
|
||||
--- Unset LU info for PS and CS (SGSN and VLR names)
|
||||
|
||||
db_subscr_lu(dbc, id0, "", true) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "", true) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -516,7 +502,7 @@ struct hlr_subscriber {
|
||||
.vlr_number = '222',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, "", false) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "", false) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -525,9 +511,9 @@ struct hlr_subscriber {
|
||||
.msisdn = '543210123456789',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, "111", true) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "111", true) --> 0
|
||||
|
||||
db_subscr_lu(dbc, id0, "222", false) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "222", false) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -538,7 +524,7 @@ struct hlr_subscriber {
|
||||
.sgsn_number = '111',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, NULL, true) --> 0
|
||||
db_subscr_lu_str(dbc, id0, NULL, true) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -548,7 +534,7 @@ struct hlr_subscriber {
|
||||
.vlr_number = '222',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, NULL, false) --> 0
|
||||
db_subscr_lu_str(dbc, id0, NULL, false) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -560,14 +546,13 @@ struct hlr_subscriber {
|
||||
|
||||
--- Record LU for non-existent ID
|
||||
|
||||
db_subscr_lu(dbc, 99999, "5952", true) --> -ENOENT
|
||||
db_subscr_lu_str(dbc, 99999, "5952", true) --> -ENOENT
|
||||
DAUC Cannot update SGSN number for subscriber ID=99999: no such subscriber
|
||||
|
||||
db_subscr_lu(dbc, 99999, "712", false) --> -ENOENT
|
||||
db_subscr_lu_str(dbc, 99999, "712", false) --> -ENOENT
|
||||
DAUC Cannot update VLR number for subscriber ID=99999: no such subscriber
|
||||
|
||||
db_subscr_get_by_id(dbc, 99999, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: ID=99999: No such subscriber
|
||||
|
||||
|
||||
--- Purge and un-purge PS and CS
|
||||
@@ -698,13 +683,11 @@ db_subscr_purge(dbc, unknown_imsi, true, true) --> -ENOENT
|
||||
DAUC Cannot purge PS: no such subscriber: IMSI='999999999'
|
||||
|
||||
db_subscr_get_by_imsi(dbc, unknown_imsi, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='999999999': No such subscriber
|
||||
|
||||
db_subscr_purge(dbc, unknown_imsi, true, false) --> -ENOENT
|
||||
DAUC Cannot purge CS: no such subscriber: IMSI='999999999'
|
||||
|
||||
db_subscr_get_by_imsi(dbc, unknown_imsi, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='999999999': No such subscriber
|
||||
|
||||
|
||||
--- Delete non-existent / invalid IDs
|
||||
@@ -728,7 +711,6 @@ struct hlr_subscriber {
|
||||
db_subscr_delete_by_id(dbc, id0) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123456789000000': No such subscriber
|
||||
|
||||
db_subscr_delete_by_id(dbc, id0) --> -ENOENT
|
||||
DAUC Cannot delete: no such subscriber: ID=1
|
||||
@@ -742,7 +724,6 @@ struct hlr_subscriber {
|
||||
db_subscr_delete_by_id(dbc, id1) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi1, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123456789000001': No such subscriber
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi2, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -753,7 +734,6 @@ struct hlr_subscriber {
|
||||
db_subscr_delete_by_id(dbc, id2) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi2, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123456789000002': No such subscriber
|
||||
|
||||
db_subscr_get_by_imsi(dbc, short_imsi, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -764,7 +744,6 @@ struct hlr_subscriber {
|
||||
db_subscr_delete_by_id(dbc, id_short) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, short_imsi, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123456': No such subscriber
|
||||
|
||||
|
||||
--- Create and delete subscribers with non-default nam_cs and nam_ps
|
||||
@@ -1305,7 +1284,6 @@ struct hlr_subscriber {
|
||||
db_subscr_delete_by_id(dbc, id) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123456789000000': No such subscriber
|
||||
|
||||
|
||||
--- Re-add subscriber and verify auth data didn't come back
|
||||
@@ -1330,7 +1308,6 @@ DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
db_subscr_delete_by_id(dbc, id) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123456789000000': No such subscriber
|
||||
|
||||
db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL, false) --> -2
|
||||
DAUC IMSI='123456789000000': No such subscriber
|
||||
@@ -1431,13 +1408,11 @@ db_update_sqn(dbc, 99, 999) --> -ENOENT
|
||||
DAUC Cannot update SQN for subscriber ID=99: no auc_3g entry for such subscriber
|
||||
|
||||
db_subscr_get_by_id(dbc, 99, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: ID=99: No such subscriber
|
||||
|
||||
db_update_sqn(dbc, 9999, 99) --> -ENOENT
|
||||
DAUC Cannot update SQN for subscriber ID=9999: no auc_3g entry for such subscriber
|
||||
|
||||
db_subscr_get_by_id(dbc, 9999, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: ID=9999: No such subscriber
|
||||
|
||||
|
||||
--- Create subscriber
|
||||
@@ -1635,7 +1610,86 @@ struct hlr_subscriber {
|
||||
db_subscr_delete_by_id(dbc, id) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123456789000000': No such subscriber
|
||||
|
||||
===== test_subscr_sqn: SUCCESS
|
||||
|
||||
|
||||
===== test_ind
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"msc-23\0" ind = 1
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"sgsn-11\0" ind = 2
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"msc-42\0" ind = 3
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"sgsn-22\0" ind = 4
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"msc-0x17\0" ind = 5
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"sgsn-0xaa\0" ind = 6
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"msc-42\0" ind = 3
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"sgsn-22\0" ind = 4
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"msc-0x17\0" ind = 5
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"sgsn-0xaa\0" ind = 6
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"sgsn-0xbb\0" ind = 7
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"msc-0x2a\0" ind = 8
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"msc-42\0" ind = 3
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"sgsn-22\0" ind = 4
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"msc-23\0" ind = 1
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"sgsn-11\0" ind = 2
|
||||
|
||||
db_ind_del(dbc, &vlr) --> 0
|
||||
|
||||
"msc-0x17\0" ind deleted
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"msc-0x2a\0" ind = 8
|
||||
|
||||
db_ind(dbc, &vlr, &ind) --> 0
|
||||
|
||||
"any-unknown\0" ind = 9
|
||||
|
||||
===== test_ind: SUCCESS
|
||||
|
||||
|
@@ -84,6 +84,8 @@ DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 1
|
||||
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 2
|
||||
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 3
|
||||
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 4
|
||||
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 5
|
||||
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 6
|
||||
DMAIN Cmdline option --db-check: Database was opened successfully, quitting.
|
||||
|
||||
Resulting db:
|
||||
@@ -116,6 +118,13 @@ algo_id_3g|ind_bitlen|k|op|opc|sqn|subscriber_id
|
||||
5|5|44444444444444444444444444444444|44444444444444444444444444444444||0|5
|
||||
5|5|55555555555555555555555555555555||55555555555555555555555555555555|0|6
|
||||
|
||||
Table: ind
|
||||
name|type|notnull|dflt_value|pk
|
||||
ind|INTEGER|0||1
|
||||
vlr|TEXT|1||0
|
||||
|
||||
Table ind contents:
|
||||
|
||||
Table: subscriber
|
||||
name|type|notnull|dflt_value|pk
|
||||
ggsn_number|VARCHAR(15)|0||0
|
||||
@@ -137,17 +146,19 @@ periodic_lu_tmr|INTEGER|0||0
|
||||
periodic_rau_tau_tmr|INTEGER|0||0
|
||||
sgsn_address|VARCHAR|0||0
|
||||
sgsn_number|VARCHAR(15)|0||0
|
||||
sgsn_via_proxy|VARCHAR|0||0
|
||||
smsc_number|VARCHAR(15)|0||0
|
||||
vlr_number|VARCHAR(15)|0||0
|
||||
vlr_via_proxy|VARCHAR|0||0
|
||||
|
||||
Table subscriber contents:
|
||||
ggsn_number|gmlc_number|id|imei|imeisv|imsi|last_lu_seen|last_lu_seen_ps|lmsi|ms_purged_cs|ms_purged_ps|msc_number|msisdn|nam_cs|nam_ps|periodic_lu_tmr|periodic_rau_tau_tmr|sgsn_address|sgsn_number|smsc_number|vlr_number
|
||||
||1|||123456789012345||||0|0||098765432109876|1|1||||||MSC-1
|
||||
||2|||111111111||||1|0|||1|1||||||
|
||||
||3|||222222222||||0|1||22222|1|1||||||
|
||||
||4|||333333||||0|0||3|0|1||||||
|
||||
||5|||444444444444444||||0|0||4444|1|0||||||
|
||||
||6|||5555555||||0|0||55555555555555|0|0||||||
|
||||
ggsn_number|gmlc_number|id|imei|imeisv|imsi|last_lu_seen|last_lu_seen_ps|lmsi|ms_purged_cs|ms_purged_ps|msc_number|msisdn|nam_cs|nam_ps|periodic_lu_tmr|periodic_rau_tau_tmr|sgsn_address|sgsn_number|sgsn_via_proxy|smsc_number|vlr_number|vlr_via_proxy
|
||||
||1|||123456789012345||||0|0||098765432109876|1|1|||||||MSC-1|
|
||||
||2|||111111111||||1|0|||1|1||||||||
|
||||
||3|||222222222||||0|1||22222|1|1||||||||
|
||||
||4|||333333||||0|0||3|0|1||||||||
|
||||
||5|||444444444444444||||0|0||4444|1|0||||||||
|
||||
||6|||5555555||||0|0||55555555555555|0|0||||||||
|
||||
|
||||
Table: subscriber_apn
|
||||
name|type|notnull|dflt_value|pk
|
||||
@@ -168,5 +179,5 @@ osmo-hlr --database $db --db-check --config-file $srcdir/osmo-hlr.cfg
|
||||
rc = 0
|
||||
DMAIN hlr starting
|
||||
DDB using database: <PATH>test.db
|
||||
DDB Database <PATH>test.db' has HLR DB schema version 4
|
||||
DDB Database <PATH>test.db' has HLR DB schema version 6
|
||||
DMAIN Cmdline option --db-check: Database was opened successfully, quitting.
|
||||
|
36
tests/gsup/Makefile.am
Normal file
36
tests/gsup/Makefile.am
Normal file
@@ -0,0 +1,36 @@
|
||||
AM_CPPFLAGS = \
|
||||
$(all_includes) \
|
||||
$(NULL)
|
||||
|
||||
AM_CFLAGS = \
|
||||
-I$(top_srcdir)/include \
|
||||
$(LIBOSMOCORE_CFLAGS) \
|
||||
$(LIBOSMOGSM_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
AM_LDFLAGS = \
|
||||
-no-install \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_DIST = \
|
||||
gsup_test.ok \
|
||||
gsup_test.err \
|
||||
$(NULL)
|
||||
|
||||
noinst_PROGRAMS = \
|
||||
gsup_test \
|
||||
$(NULL)
|
||||
|
||||
gsup_test_SOURCES = \
|
||||
gsup_test.c \
|
||||
$(NULL)
|
||||
|
||||
gsup_test_LDADD = \
|
||||
$(top_builddir)/src/gsupclient/libosmo-gsup-client.la \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
$(NULL)
|
||||
|
||||
.PHONY: update_exp
|
||||
update_exp:
|
||||
$(builddir)/gsup_test >"$(srcdir)/gsup_test.ok" 2>"$(srcdir)/gsup_test.err"
|
113
tests/gsup/gsup_test.c
Normal file
113
tests/gsup/gsup_test.c
Normal file
@@ -0,0 +1,113 @@
|
||||
/* (C) 2018 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 <osmocom/core/logging.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/application.h>
|
||||
#include <osmocom/gsupclient/gsup_req.h>
|
||||
|
||||
void *ctx = NULL;
|
||||
|
||||
static void test_gsup_make_response(void)
|
||||
{
|
||||
char *source_name = "incoming-source-name";
|
||||
char *destination_name = "preset-destination-name";
|
||||
uint8_t sm_rp_mr = 23;
|
||||
uint8_t other_sm_rp_mr = 17;
|
||||
struct osmo_gsup_message rx = {
|
||||
.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST,
|
||||
.imsi = "1234567",
|
||||
.message_class = OSMO_GSUP_MESSAGE_CLASS_SUBSCRIBER_MANAGEMENT,
|
||||
.source_name = (uint8_t*)source_name,
|
||||
.source_name_len = strlen(source_name) + 1,
|
||||
.sm_rp_mr = &sm_rp_mr,
|
||||
.session_id = 42,
|
||||
.session_state = OSMO_GSUP_SESSION_STATE_BEGIN,
|
||||
};
|
||||
struct osmo_gsup_message nonempty = {
|
||||
.message_type = OSMO_GSUP_MSGT_ROUTING_ERROR,
|
||||
.imsi = "987654321",
|
||||
.message_class = OSMO_GSUP_MESSAGE_CLASS_INTER_MSC,
|
||||
.destination_name = (uint8_t*)destination_name,
|
||||
.destination_name_len = strlen(destination_name) + 1,
|
||||
.sm_rp_mr = &other_sm_rp_mr,
|
||||
.session_id = 11,
|
||||
.session_state = OSMO_GSUP_SESSION_STATE_END,
|
||||
};
|
||||
void *name_ctx = talloc_named_const(ctx, 0, __func__);
|
||||
int error;
|
||||
int final;
|
||||
char *nonempty_str;
|
||||
int rc;
|
||||
|
||||
printf("\n%s()\n", __func__);
|
||||
printf("rx = %s\n", osmo_gsup_message_to_str_c(name_ctx, &rx));
|
||||
|
||||
printf("\nwriting to an empty struct osmo_gsup_message should populate values as needed:\n");
|
||||
for (error = 0; error <= 1; error++) {
|
||||
for (final = 0; final <= 1; final++) {
|
||||
struct osmo_gsup_message target = {};
|
||||
printf("- args (error=%d, final=%d)\n", error, final);
|
||||
rc = osmo_gsup_make_response(&target, &rx, error, final);
|
||||
printf(" %s\n", osmo_gsup_message_to_str_c(name_ctx, &target));
|
||||
printf(" rc = %d\n", rc);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\nwriting to an already populated struct osmo_gsup_message, should have no effect:\n");
|
||||
nonempty_str = osmo_gsup_message_to_str_c(name_ctx, &nonempty);
|
||||
for (error = 0; error <= 1; error++) {
|
||||
for (final = 0; final <= 1; final++) {
|
||||
struct osmo_gsup_message target = nonempty;
|
||||
char *result;
|
||||
printf("- args (error=%d, final=%d)\n", error, final);
|
||||
rc = osmo_gsup_make_response(&target, &rx, error, final);
|
||||
result = osmo_gsup_message_to_str_c(name_ctx, &target);
|
||||
printf(" %s\n", result);
|
||||
if (strcmp(result, nonempty_str))
|
||||
printf(" ERROR: expected: %s\n", nonempty_str);
|
||||
printf(" rc = %d\n", rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const struct log_info_cat default_categories[] = {
|
||||
};
|
||||
|
||||
static struct log_info info = {
|
||||
.cat = default_categories,
|
||||
.num_cat = ARRAY_SIZE(default_categories),
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
ctx = talloc_named_const(NULL, 0, "gsup_test");
|
||||
osmo_init_logging2(ctx, &info);
|
||||
log_set_print_filename(osmo_stderr_target, 0);
|
||||
log_set_print_timestamp(osmo_stderr_target, 0);
|
||||
log_set_use_color(osmo_stderr_target, 0);
|
||||
log_set_print_category(osmo_stderr_target, 1);
|
||||
|
||||
test_gsup_make_response();
|
||||
|
||||
printf("Done.\n");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
0
tests/gsup/gsup_test.err
Normal file
0
tests/gsup/gsup_test.err
Normal file
32
tests/gsup/gsup_test.ok
Normal file
32
tests/gsup/gsup_test.ok
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
test_gsup_make_response()
|
||||
rx = Subscriber-Management OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST: imsi="1234567" source_name="incoming-source-name\0" session_id=42 session_state=BEGIN sm_rp_mr=23
|
||||
|
||||
writing to an empty struct osmo_gsup_message should populate values as needed:
|
||||
- args (error=0, final=0)
|
||||
Subscriber-Management OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT: imsi="1234567" destination_name="incoming-source-name\0" session_id=42 session_state=CONTINUE sm_rp_mr=23
|
||||
rc = 0
|
||||
- args (error=0, final=1)
|
||||
Subscriber-Management OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT: imsi="1234567" destination_name="incoming-source-name\0" session_id=42 session_state=END sm_rp_mr=23
|
||||
rc = 0
|
||||
- args (error=1, final=0)
|
||||
Subscriber-Management OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR: imsi="1234567" destination_name="incoming-source-name\0" session_id=42 session_state=CONTINUE sm_rp_mr=23
|
||||
rc = 0
|
||||
- args (error=1, final=1)
|
||||
Subscriber-Management OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR: imsi="1234567" destination_name="incoming-source-name\0" session_id=42 session_state=END sm_rp_mr=23
|
||||
rc = 0
|
||||
|
||||
writing to an already populated struct osmo_gsup_message, should have no effect:
|
||||
- args (error=0, final=0)
|
||||
Inter-MSC OSMO_GSUP_MSGT_ROUTING_ERROR: imsi="987654321" destination_name="preset-destination-name\0" session_id=11 session_state=END sm_rp_mr=17
|
||||
rc = 15
|
||||
- args (error=0, final=1)
|
||||
Inter-MSC OSMO_GSUP_MSGT_ROUTING_ERROR: imsi="987654321" destination_name="preset-destination-name\0" session_id=11 session_state=END sm_rp_mr=17
|
||||
rc = 15
|
||||
- args (error=1, final=0)
|
||||
Inter-MSC OSMO_GSUP_MSGT_ROUTING_ERROR: imsi="987654321" destination_name="preset-destination-name\0" session_id=11 session_state=END sm_rp_mr=17
|
||||
rc = 15
|
||||
- args (error=1, final=1)
|
||||
Inter-MSC OSMO_GSUP_MSGT_ROUTING_ERROR: imsi="987654321" destination_name="preset-destination-name\0" session_id=11 session_state=END sm_rp_mr=17
|
||||
rc = 15
|
||||
Done.
|
@@ -31,6 +31,9 @@ gsup_server_test_SOURCES = \
|
||||
gsup_server_test_LDADD = \
|
||||
$(top_srcdir)/src/gsup_server.c \
|
||||
$(top_srcdir)/src/gsup_router.c \
|
||||
$(top_srcdir)/src/gsup_send.c \
|
||||
$(top_srcdir)/src/gsupclient/gsup_peer_id.c \
|
||||
$(top_srcdir)/src/gsupclient/gsup_req.c \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
$(LIBOSMOABIS_LIBS) \
|
||||
|
@@ -11,83 +11,26 @@ OsmoHLR> ?
|
||||
logp Print a message on all log outputs; useful for placing markers in test logs
|
||||
subscriber Subscriber management commands
|
||||
OsmoHLR> list
|
||||
show version
|
||||
show online-help
|
||||
list
|
||||
exit
|
||||
help
|
||||
enable
|
||||
terminal length <0-512>
|
||||
terminal no length
|
||||
who
|
||||
show history
|
||||
logging enable
|
||||
...
|
||||
show logging vty
|
||||
show alarms
|
||||
show talloc-context (application|all) (full|brief|DEPTH)
|
||||
show talloc-context (application|all) (full|brief|DEPTH) tree ADDRESS
|
||||
show talloc-context (application|all) (full|brief|DEPTH) filter REGEXP
|
||||
show stats
|
||||
show stats level (global|peer|subscriber)
|
||||
show asciidoc counters
|
||||
show rate-counters
|
||||
show gsup-connections
|
||||
subscriber (imsi|msisdn|id|imei) IDENT show
|
||||
show subscriber (imsi|msisdn|id|imei) IDENT
|
||||
show mslookup services
|
||||
|
||||
OsmoHLR> enable
|
||||
OsmoHLR# ?
|
||||
help Description of the interactive help system
|
||||
list Print command list
|
||||
write Write running configuration to memory, network, or terminal
|
||||
show Show running system information
|
||||
exit Exit current mode and down to previous mode
|
||||
disable Turn off privileged mode command
|
||||
configure Configuration from vty interface
|
||||
copy Copy configuration
|
||||
terminal Set terminal line parameters
|
||||
who Display who is on vty
|
||||
logging Configure logging
|
||||
no Negate a command or set its defaults
|
||||
logp Print a message on all log outputs; useful for placing markers in test logs
|
||||
...
|
||||
subscriber Subscriber management commands
|
||||
OsmoHLR# list
|
||||
help
|
||||
...
|
||||
exit
|
||||
disable
|
||||
configure terminal
|
||||
copy running-config startup-config
|
||||
show startup-config
|
||||
show version
|
||||
show online-help
|
||||
terminal length <0-512>
|
||||
terminal no length
|
||||
who
|
||||
show history
|
||||
terminal monitor
|
||||
terminal no monitor
|
||||
logging enable
|
||||
...
|
||||
|
||||
OsmoHLR# configure terminal
|
||||
OsmoHLR(config)# ?
|
||||
...
|
||||
banner Set banner string
|
||||
service Set up miscellaneous service
|
||||
line Configure a terminal line
|
||||
ctrl Configure the Control Interface
|
||||
log Configure logging sub-system
|
||||
stats Configure stats sub-system
|
||||
hlr Configure the HLR
|
||||
mslookup Configure Distributed GSM mslookup
|
||||
OsmoHLR(config)# list
|
||||
help
|
||||
...
|
||||
exit
|
||||
end
|
||||
...
|
||||
hlr
|
||||
mslookup
|
||||
|
||||
OsmoHLR(config)# hlr
|
||||
OsmoHLR(config-hlr)# ?
|
||||
@@ -99,10 +42,7 @@ OsmoHLR(config-hlr)# ?
|
||||
store-imei Save the IMEI in the database when receiving Check IMEI requests. Note that an MSC does not necessarily send Check IMEI requests (for OsmoMSC, you may want to set 'check-imei-rqd 1').
|
||||
subscriber-create-on-demand Make a new record when a subscriber is first seen.
|
||||
OsmoHLR(config-hlr)# list
|
||||
help
|
||||
...
|
||||
exit
|
||||
end
|
||||
gsup
|
||||
database PATH
|
||||
euse NAME
|
||||
@@ -121,10 +61,12 @@ OsmoHLR(config-hlr)# list
|
||||
OsmoHLR(config-hlr)# gsup
|
||||
OsmoHLR(config-hlr-gsup)# ?
|
||||
...
|
||||
bind Listen/Bind related socket option
|
||||
bind Listen/Bind related socket option
|
||||
ipa-name Set the IPA name of this HLR, for proxying to remote HLRs
|
||||
OsmoHLR(config-hlr-gsup)# list
|
||||
...
|
||||
bind ip A.B.C.D
|
||||
ipa-name NAME
|
||||
|
||||
OsmoHLR(config-hlr-gsup)# exit
|
||||
OsmoHLR(config-hlr)# exit
|
||||
@@ -148,6 +90,9 @@ log stderr
|
||||
logging level db notice
|
||||
logging level auc notice
|
||||
logging level ss info
|
||||
logging level mslookup notice
|
||||
logging level lu notice
|
||||
logging level dgsm notice
|
||||
...
|
||||
hlr
|
||||
store-imei
|
||||
@@ -157,3 +102,338 @@ hlr
|
||||
ussd route prefix *#100# internal own-msisdn
|
||||
ussd route prefix *#101# internal own-imsi
|
||||
end
|
||||
|
||||
OsmoHLR# configure terminal
|
||||
|
||||
OsmoHLR(config)# mslookup
|
||||
OsmoHLR(config-mslookup)# ?
|
||||
...
|
||||
mdns Multicast DNS related configuration
|
||||
no Negate a command or set its defaults
|
||||
server Enable and configure Distributed GSM mslookup server
|
||||
client Enable and configure Distributed GSM mslookup client
|
||||
OsmoHLR(config-mslookup)# list
|
||||
...
|
||||
mdns bind [IP] [<1-65535>]
|
||||
mdns domain-suffix DOMAIN_SUFFIX
|
||||
no mdns bind
|
||||
server
|
||||
no server
|
||||
client
|
||||
no client
|
||||
|
||||
OsmoHLR(config-mslookup)# ?
|
||||
...
|
||||
mdns Multicast DNS related configuration
|
||||
no Negate a command or set its defaults
|
||||
server Enable and configure Distributed GSM mslookup server
|
||||
client Enable and configure Distributed GSM mslookup client
|
||||
OsmoHLR(config-mslookup)# no?
|
||||
no Negate a command or set its defaults
|
||||
OsmoHLR(config-mslookup)# no ?
|
||||
mdns Disable both server and client for mDNS mslookup
|
||||
server Disable Distributed GSM mslookup server
|
||||
client Disable Distributed GSM mslookup client
|
||||
OsmoHLR(config-mslookup)# mdns ?
|
||||
bind Convenience shortcut: enable and configure both server and client for mDNS mslookup
|
||||
domain-suffix mDNS domain suffix (default: mdns.osmocom.org). This is appended and stripped from mDNS packets during encoding/decoding, so we don't collide with top-level domains administrated by IANA
|
||||
OsmoHLR(config-mslookup)# mdns bind ?
|
||||
[IP] multicast IPv4 address like 239.192.23.42 or IPv6 address like ff08::23:42
|
||||
OsmoHLR(config-mslookup)# mdns bind 1.2.3.4 ?
|
||||
[<1-65535>] mDNS UDP Port number
|
||||
OsmoHLR(config-mslookup)# mdns domain-suffix ?
|
||||
DOMAIN_SUFFIX mDNS domain suffix (default: mdns.osmocom.org). This is appended and stripped from mDNS packets during encoding/decoding, so we don't collide with top-level domains administrated by IANA
|
||||
|
||||
OsmoHLR(config-mslookup)# server
|
||||
OsmoHLR(config-mslookup-server)# ?
|
||||
...
|
||||
mdns Multicast DNS related configuration
|
||||
no Negate a command or set its defaults
|
||||
service Configure addresses of local services, as sent in replies to remote mslookup requests.
|
||||
msc Configure services for individual local MSCs
|
||||
OsmoHLR(config-mslookup-server)# list
|
||||
...
|
||||
mdns bind [IP] [<1-65535>]
|
||||
mdns domain-suffix DOMAIN_SUFFIX
|
||||
no mdns bind
|
||||
service NAME at IP <1-65535>
|
||||
no service NAME
|
||||
no service NAME at IP <1-65535>
|
||||
msc ipa-name .IPA_NAME
|
||||
|
||||
OsmoHLR(config-mslookup-server)# mdns ?
|
||||
bind Configure where the mDNS server listens for mslookup requests
|
||||
domain-suffix mDNS domain suffix (default: mdns.osmocom.org). This is appended and stripped from mDNS packets during encoding/decoding, so we don't collide with top-level domains administrated by IANA
|
||||
OsmoHLR(config-mslookup-server)# mdns bind ?
|
||||
[IP] multicast IPv4 address like 239.192.23.42 or IPv6 address like ff08::23:42
|
||||
OsmoHLR(config-mslookup-server)# mdns bind 1.2.3.4 ?
|
||||
[<1-65535>] mDNS UDP Port number
|
||||
|
||||
OsmoHLR(config-mslookup-server)# service?
|
||||
service Configure addresses of local services, as sent in replies to remote mslookup requests.
|
||||
OsmoHLR(config-mslookup-server)# service ?
|
||||
NAME mslookup service name, e.g. sip.voice or smpp.sms
|
||||
OsmoHLR(config-mslookup-server)# service foo ?
|
||||
at at
|
||||
OsmoHLR(config-mslookup-server)# service foo at ?
|
||||
IP IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1
|
||||
OsmoHLR(config-mslookup-server)# service foo at 1.2.3.4 ?
|
||||
<1-65535> Service-specific port number
|
||||
|
||||
OsmoHLR(config-mslookup-server)# no ?
|
||||
mdns Disable server for mDNS mslookup (do not answer remote requests)
|
||||
service Remove one or more service address entries
|
||||
OsmoHLR(config-mslookup-server)# no service ?
|
||||
NAME mslookup service name, e.g. sip.voice or smpp.sms
|
||||
OsmoHLR(config-mslookup-server)# no service foo ?
|
||||
at at
|
||||
<cr>
|
||||
OsmoHLR(config-mslookup-server)# no service foo at ?
|
||||
IP IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1
|
||||
OsmoHLR(config-mslookup-server)# no service foo at 1.2.3.4 ?
|
||||
<1-65535> Service-specific port number
|
||||
|
||||
OsmoHLR(config-mslookup-server)# msc?
|
||||
msc Configure services for individual local MSCs
|
||||
OsmoHLR(config-mslookup-server)# msc ?
|
||||
ipa-name Identify locally connected MSC by IPA Unit Name
|
||||
OsmoHLR(config-mslookup-server)# msc ipa-name ?
|
||||
IPA_NAME IPA Unit Name of the local MSC to configure
|
||||
|
||||
OsmoHLR(config-mslookup-server)# msc ipa-name MSC-1
|
||||
OsmoHLR(config-mslookup-server-msc)# ?
|
||||
...
|
||||
service Configure addresses of local services, as sent in replies to remote mslookup requests.
|
||||
no Negate a command or set its defaults
|
||||
OsmoHLR(config-mslookup-server-msc)# list
|
||||
...
|
||||
service NAME at IP <1-65535>
|
||||
no service NAME
|
||||
no service NAME at IP <1-65535>
|
||||
|
||||
OsmoHLR(config-mslookup-server-msc)# service?
|
||||
service Configure addresses of local services, as sent in replies to remote mslookup requests.
|
||||
OsmoHLR(config-mslookup-server-msc)# service ?
|
||||
NAME mslookup service name, e.g. sip.voice or smpp.sms
|
||||
OsmoHLR(config-mslookup-server-msc)# service foo ?
|
||||
at at
|
||||
OsmoHLR(config-mslookup-server-msc)# service foo at ?
|
||||
IP IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1
|
||||
OsmoHLR(config-mslookup-server-msc)# service foo at 1.2.3.4 ?
|
||||
<1-65535> Service-specific port number
|
||||
|
||||
OsmoHLR(config-mslookup-server-msc)# no ?
|
||||
service Remove one or more service address entries
|
||||
OsmoHLR(config-mslookup-server-msc)# no service ?
|
||||
NAME mslookup service name, e.g. sip.voice or smpp.sms
|
||||
OsmoHLR(config-mslookup-server-msc)# no service foo ?
|
||||
at at
|
||||
<cr>
|
||||
OsmoHLR(config-mslookup-server-msc)# no service foo at ?
|
||||
IP IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1
|
||||
OsmoHLR(config-mslookup-server-msc)# no service foo at 1.2.3.4 ?
|
||||
<1-65535> Service-specific port number
|
||||
|
||||
OsmoHLR(config-mslookup-server-msc)# exit
|
||||
OsmoHLR(config-mslookup-server)# exit
|
||||
OsmoHLR(config-mslookup)# client
|
||||
OsmoHLR(config-mslookup-client)# ?
|
||||
...
|
||||
timeout How long should the mslookup client wait for remote responses before evaluating received results
|
||||
mdns Multicast DNS related configuration
|
||||
no Negate a command or set its defaults
|
||||
gateway-proxy Configure a fixed IP address to send all GSUP requests for unknown IMSIs to, without invoking a lookup for IMSI
|
||||
OsmoHLR(config-mslookup-client)# list
|
||||
...
|
||||
timeout <1-100000>
|
||||
mdns bind [IP] [<1-65535>]
|
||||
mdns domain-suffix DOMAIN_SUFFIX
|
||||
no mdns bind
|
||||
gateway-proxy IP [<1-65535>]
|
||||
no gateway-proxy
|
||||
|
||||
OsmoHLR(config-mslookup-client)# timeout?
|
||||
timeout How long should the mslookup client wait for remote responses before evaluating received results
|
||||
OsmoHLR(config-mslookup-client)# timeout ?
|
||||
<1-100000> timeout in milliseconds
|
||||
|
||||
OsmoHLR(config-mslookup-client)# mdns?
|
||||
mdns Multicast DNS related configuration
|
||||
OsmoHLR(config-mslookup-client)# mdns bind?
|
||||
bind Enable mDNS client, and configure multicast address to send mDNS mslookup requests to
|
||||
OsmoHLR(config-mslookup-client)# mdns bind ?
|
||||
[IP] multicast IPv4 address like 239.192.23.42 or IPv6 address like ff08::23:42
|
||||
OsmoHLR(config-mslookup-client)# mdns bind 1.2.3.4 ?
|
||||
[<1-65535>] mDNS UDP Port number
|
||||
OsmoHLR(config-mslookup-client)# mdns domain-suffix?
|
||||
domain-suffix mDNS domain suffix (default: mdns.osmocom.org). This is appended and stripped from mDNS packets during encoding/decoding, so we don't collide with top-level domains administrated by IANA
|
||||
OsmoHLR(config-mslookup-client)# mdns domain-suffix ?
|
||||
DOMAIN_SUFFIX mDNS domain suffix (default: mdns.osmocom.org). This is appended and stripped from mDNS packets during encoding/decoding, so we don't collide with top-level domains administrated by IANA
|
||||
|
||||
|
||||
OsmoHLR(config-mslookup-client)# gateway-proxy?
|
||||
gateway-proxy Configure a fixed IP address to send all GSUP requests for unknown IMSIs to, without invoking a lookup for IMSI
|
||||
OsmoHLR(config-mslookup-client)# gateway-proxy ?
|
||||
IP IP address of the remote HLR
|
||||
OsmoHLR(config-mslookup-client)# gateway-proxy 1.2.3.4 ?
|
||||
[<1-65535>] GSUP port number (omit for default 4222)
|
||||
|
||||
OsmoHLR(config-mslookup-client)# no?
|
||||
no Negate a command or set its defaults
|
||||
OsmoHLR(config-mslookup-client)# no ?
|
||||
mdns Disable mDNS client, do not query remote services by mDNS
|
||||
gateway-proxy Disable gateway proxy for GSUP with unknown IMSIs
|
||||
|
||||
OsmoHLR(config-mslookup-client)# gateway-proxy ?
|
||||
IP IP address of the remote HLR
|
||||
OsmoHLR(config-mslookup-client)# gateway-proxy 1.2.3.4 ?
|
||||
[<1-65535>] GSUP port number (omit for default 4222)
|
||||
|
||||
OsmoHLR(config-mslookup-client)# do show mslookup?
|
||||
mslookup Distributed GSM / mslookup related information
|
||||
OsmoHLR(config-mslookup-client)# do show mslookup ?
|
||||
services List configured service addresses as sent to remote mslookup requests
|
||||
|
||||
OsmoHLR(config-mslookup-client)# gateway-proxy 1.2.3.4
|
||||
|
||||
OsmoHLR(config-mslookup-client)# exit
|
||||
|
||||
OsmoHLR(config-mslookup)# mdns bind
|
||||
OsmoHLR(config-mslookup)# server
|
||||
OsmoHLR(config-mslookup-server)# service qwert at 123.45.67.89 qwert
|
||||
% Unknown command.
|
||||
OsmoHLR(config-mslookup-server)# service qwert at qwert 1234
|
||||
% mslookup server: Invalid address for service qwert: qwert 1234
|
||||
OsmoHLR(config-mslookup-server)# service foo.bar at 123.45.67.89 1011
|
||||
OsmoHLR(config-mslookup-server)# service baz.bar at 121.31.41.5 1617
|
||||
OsmoHLR(config-mslookup-server)# service baz.bar at a:b:c::d 1819
|
||||
OsmoHLR(config-mslookup-server)# msc ipa-name msc-901-70-23
|
||||
OsmoHLR(config-mslookup-server-msc)# service foo.bar at 76.54.32.10 1234
|
||||
OsmoHLR(config-mslookup-server-msc)# service baz.bar at 12.11.10.98 7654
|
||||
OsmoHLR(config-mslookup-server-msc)# service baz.bar at 999:999:999::999 9999
|
||||
OsmoHLR(config-mslookup-server-msc)# service baz.bar at dd:cc:bb::a 3210
|
||||
OsmoHLR(config-mslookup-server-msc)# exit
|
||||
OsmoHLR(config-mslookup-server)# msc ipa-name msc-901-70-42
|
||||
OsmoHLR(config-mslookup-server-msc)# service foo.bar at 1.1.1.1 1111
|
||||
OsmoHLR(config-mslookup-server-msc)# service baz.bar at 2.2.2.2 2222
|
||||
OsmoHLR(config-mslookup-server-msc)# service baz.bar at 2222:2222:2222::2 2222
|
||||
OsmoHLR(config-mslookup-server-msc)# do show mslookup services
|
||||
Local GSUP HLR address returned in mslookup responses for local IMSIs: 127.0.0.1:4222
|
||||
service foo.bar at 123.45.67.89 1011
|
||||
service baz.bar at 121.31.41.5 1617
|
||||
service baz.bar at a:b:c::d 1819
|
||||
msc ipa-name MSC-1
|
||||
msc ipa-name msc-901-70-23
|
||||
service foo.bar at 76.54.32.10 1234
|
||||
service baz.bar at 12.11.10.98 7654
|
||||
service baz.bar at dd:cc:bb::a 3210
|
||||
msc ipa-name msc-901-70-42
|
||||
service foo.bar at 1.1.1.1 1111
|
||||
service baz.bar at 2.2.2.2 2222
|
||||
service baz.bar at 2222:2222:2222::2 2222
|
||||
|
||||
OsmoHLR(config-mslookup-server-msc)# show running-config
|
||||
...
|
||||
mslookup
|
||||
server
|
||||
mdns bind 239.192.23.42 4266
|
||||
service foo.bar at 123.45.67.89 1011
|
||||
service baz.bar at 121.31.41.5 1617
|
||||
service baz.bar at a:b:c::d 1819
|
||||
msc MSC-1
|
||||
msc msc-901-70-23
|
||||
service foo.bar at 76.54.32.10 1234
|
||||
service baz.bar at 12.11.10.98 7654
|
||||
service baz.bar at dd:cc:bb::a 3210
|
||||
msc msc-901-70-42
|
||||
service foo.bar at 1.1.1.1 1111
|
||||
service baz.bar at 2.2.2.2 2222
|
||||
service baz.bar at 2222:2222:2222::2 2222
|
||||
client
|
||||
gateway-proxy 1.2.3.4 4222
|
||||
mdns bind 239.192.23.42 4266
|
||||
...
|
||||
|
||||
OsmoHLR(config-mslookup-server-msc)# no service baz.bar
|
||||
OsmoHLR(config-mslookup-server-msc)# no service asdf
|
||||
% mslookup server: cannot remove service 'asdf'
|
||||
OsmoHLR(config-mslookup-server-msc)# exit
|
||||
OsmoHLR(config-mslookup-server)# msc ipa-name msc-901-70-23
|
||||
OsmoHLR(config-mslookup-server-msc)# no service baz.bar at dd:cc:bb::a 3210
|
||||
% mslookup server: cannot remove service 'baz.bar' to dd:cc:bb::a 3210
|
||||
OsmoHLR(config-mslookup-server-msc)# no service asdf at asdf asdf
|
||||
% Unknown command.
|
||||
OsmoHLR(config-mslookup-server-msc)# no service asdf at asdf 3210
|
||||
% mslookup server: Invalid address for 'no service' asdf: asdf 3210
|
||||
OsmoHLR(config-mslookup-server-msc)# no service asdf at dd:cc:bb::a 3210
|
||||
% mslookup server: cannot remove service 'asdf' to dd:cc:bb::a 3210
|
||||
OsmoHLR(config-mslookup-server-msc)# exit
|
||||
OsmoHLR(config-mslookup-server)# no service baz.bar at 2.2.2.2 2222
|
||||
% mslookup server: cannot remove service 'baz.bar' to 2.2.2.2 2222
|
||||
OsmoHLR(config-mslookup-server)# no service baz.bar at a:b:c::d 1819
|
||||
% mslookup server: cannot remove service 'baz.bar' to a:b:c::d 1819
|
||||
|
||||
OsmoHLR(config-mslookup-server)# exit
|
||||
OsmoHLR(config-mslookup)# client
|
||||
OsmoHLR(config-mslookup-client)# no gateway-proxy
|
||||
|
||||
OsmoHLR(config-mslookup-client)# do show mslookup services
|
||||
Local GSUP HLR address returned in mslookup responses for local IMSIs: 127.0.0.1:4222
|
||||
service foo.bar at 123.45.67.89 1011
|
||||
service baz.bar at 121.31.41.5 1617
|
||||
msc ipa-name MSC-1
|
||||
msc ipa-name msc-901-70-23
|
||||
service foo.bar at 76.54.32.10 1234
|
||||
service baz.bar at 12.11.10.98 7654
|
||||
msc ipa-name msc-901-70-42
|
||||
service foo.bar at 1.1.1.1 1111
|
||||
|
||||
OsmoHLR(config-mslookup-client)# show running-config
|
||||
...
|
||||
mslookup
|
||||
server
|
||||
mdns bind 239.192.23.42 4266
|
||||
service foo.bar at 123.45.67.89 1011
|
||||
service baz.bar at 121.31.41.5 1617
|
||||
msc MSC-1
|
||||
msc msc-901-70-23
|
||||
service foo.bar at 76.54.32.10 1234
|
||||
service baz.bar at 12.11.10.98 7654
|
||||
msc msc-901-70-42
|
||||
service foo.bar at 1.1.1.1 1111
|
||||
client
|
||||
mdns bind 239.192.23.42 4266
|
||||
...
|
||||
|
||||
OsmoHLR(config-mslookup-client)# exit
|
||||
OsmoHLR(config-mslookup)# server
|
||||
OsmoHLR(config-mslookup-server)# service gsup.hlr at 23.42.17.11 4223
|
||||
OsmoHLR(config-mslookup-server)# do show mslookup services
|
||||
Local GSUP HLR address returned in mslookup responses for local IMSIs: 23.42.17.11:4223
|
||||
service foo.bar at 123.45.67.89 1011
|
||||
service baz.bar at 121.31.41.5 1617
|
||||
service gsup.hlr at 23.42.17.11 4223
|
||||
msc ipa-name MSC-1
|
||||
msc ipa-name msc-901-70-23
|
||||
service foo.bar at 76.54.32.10 1234
|
||||
service baz.bar at 12.11.10.98 7654
|
||||
msc ipa-name msc-901-70-42
|
||||
service foo.bar at 1.1.1.1 1111
|
||||
|
||||
OsmoHLR(config-mslookup-server)# show running-config
|
||||
...
|
||||
mslookup
|
||||
server
|
||||
mdns bind 239.192.23.42 4266
|
||||
service foo.bar at 123.45.67.89 1011
|
||||
service baz.bar at 121.31.41.5 1617
|
||||
service gsup.hlr at 23.42.17.11 4223
|
||||
msc MSC-1
|
||||
msc msc-901-70-23
|
||||
service foo.bar at 76.54.32.10 1234
|
||||
service baz.bar at 12.11.10.98 7654
|
||||
msc msc-901-70-42
|
||||
service foo.bar at 1.1.1.1 1111
|
||||
client
|
||||
mdns bind 239.192.23.42 4266
|
||||
...
|
||||
|
@@ -13,6 +13,7 @@ OsmoHLR# list
|
||||
subscriber (imsi|msisdn|id|imei) IDENT update aud3g milenage k K (op|opc) OP_C [ind-bitlen] [<0-28>]
|
||||
subscriber (imsi|msisdn|id|imei) IDENT update imei (none|IMEI)
|
||||
subscriber (imsi|msisdn|id|imei) IDENT update network-access-mode (none|cs|ps|cs+ps)
|
||||
show mslookup services
|
||||
|
||||
OsmoHLR# subscriber?
|
||||
subscriber Subscriber management commands
|
||||
|
@@ -15,6 +15,13 @@ cat $abs_srcdir/auc/auc_ts_55_205_test_sets.err > experr
|
||||
AT_CHECK([$abs_top_builddir/tests/auc/auc_ts_55_205_test_sets], [], [expout], [experr])
|
||||
AT_CLEANUP
|
||||
|
||||
AT_SETUP([gsup])
|
||||
AT_KEYWORDS([gsup])
|
||||
cat $abs_srcdir/gsup/gsup_test.ok > expout
|
||||
cat $abs_srcdir/gsup/gsup_test.err > experr
|
||||
AT_CHECK([$abs_top_builddir/tests/gsup/gsup_test], [], [expout], [experr])
|
||||
AT_CLEANUP
|
||||
|
||||
AT_SETUP([gsup_server])
|
||||
AT_KEYWORDS([gsup_server])
|
||||
cat $abs_srcdir/gsup_server/gsup_server_test.ok > expout
|
||||
|
Reference in New Issue
Block a user