dgsm Proxy Cache design WIP

Change-Id: Ifa322e84fadd3b04943c8c7024c0e2de4935bed0
This commit is contained in:
Neels Hofmeyr 2020-01-23 04:40:51 +01:00
parent b3c1726fb9
commit 1971b6731a
23 changed files with 1725 additions and 2 deletions

View File

@ -186,6 +186,7 @@ AC_OUTPUT(
Makefile
doc/Makefile
doc/examples/Makefile
doc/sequence_charts/Makefile
src/Makefile
src/gsupclient/Makefile
src/mslookup/Makefile

View File

@ -1,4 +1,5 @@
SUBDIRS = \
examples \
manuals \
sequence_charts \
$(NULL)

View File

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

View 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[]
----

View 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"];
}

View 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"];
}

View 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"];
}

View 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"];
}

View 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"];
}

View File

@ -26,6 +26,8 @@ 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[]

View 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

View 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]
}

View 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]
}

View 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]
}

View File

@ -14,6 +14,8 @@ noinst_HEADERS = \
mslookup_server.h \
mslookup_server_mdns.h \
proxy.h \
proxy_mm.h \
proxy_db.h \
rand.h \
remote_hlr.h \
timestamp.h \

View File

@ -20,8 +20,11 @@
#pragma once
#include <time.h>
#include <osmocom/gsm/protocol/gsm_23_003.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>
@ -65,6 +68,8 @@ struct proxy_subscr {
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);

View 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;
};

View 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);

View File

@ -87,6 +87,27 @@ CREATE TABLE ind (
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

View File

@ -54,6 +54,9 @@ osmo_hlr_SOURCES = \
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 \

View File

@ -403,6 +403,18 @@ static int proxy_acknowledge_gsup_from_remote_hlr(struct proxy *proxy, const str
);
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;
}

417
src/proxy_mm.c Normal file
View 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
View 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);
}