Compare commits

...

366 Commits

Author SHA1 Message Date
Neels Janosch Hofmeyr
4da67bb587 hnbgw_rx_hnb_register_req(): guard against asn.1 parsing errors
I considered putting these checks into
hnbap_decode_hnbregisterrequesties() itself, but that code is generated
by asn1tostruct.py, so it is non-trivial.

So I decided to add this code bit, to guard against potential NULL deref
induced by input from the wire, which coverity complains about.

Related: CID#465551
Change-Id: I555f11cadc23ea231821bd48f7cd62953b022e9e
2025-10-02 03:05:32 +02:00
Pau Espin Pedrol
2c88bdb492 Use proper SCCP release cause enum value instead of hardcoded value
There's no behavior change since the enum item has value 0.

Change-Id: I9de79db8e16cf425919b678d837b46f24ffe9bbe
2025-07-16 18:32:36 +02:00
Pau Espin Pedrol
2601d9b3f0 sccp: Handle N-NOTICE.ind (Routing Failure of SCCP CL messages)
ITU Q.714 2.8:
"""
When an end node is informed of a routing failure, this information
is forwarded towards the SCCP user by using the N-DISCONNECT primitive
(refer to reason for release in 2.1.1.2.4/Q.711) or the N-NOTICE primitive
(refer to reason for return in 2.2.2.2.4/Q.711)
"""

We are already handling N-DISCONNECT.ind for CO messages, but
N-NOTICE.ind for CL messages was not being handled.

If CL messages are not arriving to the peer (cnlink), then reset the link
and mark the peer as disconnected, until a new RESET can successfully fo
through.

Related: OS#5917
Change-Id: Ie5f643ba9ac8c443ce95c7f0a89a198fb804c94d
2025-07-16 17:24:38 +02:00
Pau Espin Pedrol
315874927e sigtran: Log N-PCSTATE.ind PC with configured format
Change-Id: I4049853a9eb4b0df5d998e18464ad27c47fdc942
2025-07-11 13:58:20 +02:00
Pau Espin Pedrol
cb4962f7f0 Make sure MSC/SGSN PC are added to sccp-addressbook
This way they can used by libosmo-sigtran to figure out interesting
remote PCs it should track availability for.

Related: OS#5917
Related: SYS#7501
Depends: libosmo-sigtran.git Change-Id I9d2452d9cc8e443050927a047b7ebacd2b2f37e1
Change-Id: I6a537adca27dd2bbad33d0f2be71eb41d6ad3a27
2025-07-08 18:40:35 +02:00
Pau Espin Pedrol
46a2c0afc2 sccp: Avoid setting default local HNBGW PC as default remote PC
It makes no sense to set the remote PC of MSC/SGSN to the default local
HNBGW PC. Instead, set it to the default remote PC of the pool (sgsn vs msc),
as already done in "else" code path in hnbgw_cnlink_start_or_restart()
calling osmo_sccp_make_addr_pc_ssn().

Change-Id: I43b9ab7a06bcaa8bea91f9f54fca0993a67340d7
2025-07-08 17:09:11 +02:00
Pau Espin Pedrol
6679c49133 kpi: count RAB actions received in unexpected state
This allows figuring out that something may be wrong in the network,
with both sides (HNB vs CN) going out of sync, or HNBGW ending up in a
bad state due to eg. a bug where a RAB is lost.

While at it, fix some formatting issues on related counters.

Related: SYS#7455
Change-Id: If3521ba601b9d7448226f2303ed2ef402d861bef
2025-05-12 14:10:31 +02:00
Pau Espin Pedrol
4db21846e7 kpi: Fix wrong counters incremented in RAB Release Failed List
Thouse counters are already incremented somewhere else, when
Setup-or-modify is answered with a failure.
This list is for RABs which failed to release, and the correct counter
is already being incremented above.

Change-Id: I0bcb70ca18078bb9201ce6cbf4ea81c05ae4ea68
2025-05-12 14:08:04 +02:00
Pau Espin Pedrol
883048efd4 kpi: Increment counters using enum field explicitly
I was first tricked by the increment of ctr_num.
This is wrong for several reasons:
* Assumes one counter will always be immediatelly after the other, and
  there's no static assert about that.
* Makes it difficult to grep for users of the counter in the code.

Change-Id: I126973b93e86784adaccbcba49c6322c261f4746
2025-05-12 14:07:13 +02:00
Pau Espin Pedrol
33cbc694f5 Support tearing down PFCP upon PS-RAB-ASS-REQ (REL)
Before this patch, RAB Assign Request containing only a ReleaseList
where being discarded with an error.

This commit adds logic to allow passing those messages, and upon RAB
Assign Response, tear down the related PFCP sessions before forwarding
it back to the CN.

This is scenario is tested in TTCN-3 testsuite
HNBGW_Tests.TC_ps_rab_release.

Related: SYS#7451
Change-Id: Iea7f2cea4aa1fbab12cb8bf0275cd43d9e0713c2
2025-05-12 13:40:52 +02:00
Pau Espin Pedrol
563f574178 ps_rab_ass_fsm: Fix potential use-after-free if Tx RAB-ASS-RESP over SCCP fails
ps_rab_ass_failure() is already calling
osmo_fsm_inst_term(rab_ass->fi(), which will free "fi" and its child
talloc struct "rab_ass".
Hence, return early as done everywher else in order to avoid accessing
the struct again.

Change-Id: Id605f2b279a4d886399de27f6a94622ad7bf982b
2025-05-12 13:40:52 +02:00
Pau Espin Pedrol
d144d7b497 Clean up calls to ps_rab_ass_fsm_state_chg()
Change-Id: I7c9a67ebe192ba30fa474f84eaee1db2a718fa35
2025-05-12 13:40:52 +02:00
Pau Espin Pedrol
35a834078f context_map: Rename ps_rab(_ass) to denote it is a list
This allows also easily grepping to figure out where it is used,
otherwise it was named like the struct, fsm, etc.

Change-Id: I6ac029e6a820a031a443dae8c83e44f6ae9fb5fa
2025-05-12 13:40:52 +02:00
Pau Espin Pedrol
160b372bf9 vty: Allow setting cnlink msc/sgsn name
This is useful to identify each individual CN link towards a specific
MSC/SGSN, be it checking VTY config, looking at logs, reporting over
statsd, etc.

Change-Id: Ic557f20f40fec49ce2c83a73f6e5c8b027d57ef5
2025-05-12 13:40:52 +02:00
Pau Espin Pedrol
225a215b09 cnlink: Introduce stats group with connected state
Change-Id: I9c78c6b78b4ee8d6836df8d90c5c57103aa4d762
2025-05-12 13:40:52 +02:00
Pau Espin Pedrol
5eb95cf42a Introduce func helper hnbgw_sccp_user_get_sccp_instance()
This way we keep struct field access within the header file.

Change-Id: I1684fa1871098ab6df7c79c9c0e0daf6c6f35b09
2025-05-12 13:40:52 +02:00
Pau Espin Pedrol
f91e32fc01 cnlink: Cleanup talloc tree
Previous logic allocating structs was a big convoluted regarding order
of assignments, which then even required a taloc_steal.
Since recently the struct hnbgw_cnpool is a talloc context, and struct
hnbgw_cnlink are allocated as being part of a cnpool, so clean up the
talloc tree by having cnlink under cnpool.
Finally, put the cnlink_fsm and the cnlink counters under the hnbgw_cnlink
talloc context, where they belong.

Change-Id: I90c22f2a2932ede7103c66f2263e72ac2fdae497
2025-05-12 13:38:01 +02:00
Pau Espin Pedrol
37b9a98a10 Move cnlink funcs from hnbgw_cn.c to cnlink.c
This way we finally end up with hnbgw_cnpool related funcionalities in
hnbgw_cn.c, and cnlink related ones in cnlink.c.

Change-Id: Ic9a962877ea000aa1bca6c2c282baac6e53f6489
2025-05-12 10:52:28 +02:00
Pau Espin Pedrol
1f8fb9ae8c Get rid of cnpool->cnlink_ctrg_desc
There's no need to store that pointer in the cnpool, since the domain
can be obtained from cnpool at cnlink creation time.

Move the per-cnlink rate counters to the file where they belong.

Change-Id: Iaa36bb52f8135ef2421931f1ea6a40885b588e1d
2025-05-12 10:52:28 +02:00
Pau Espin Pedrol
6a254f08c7 Convert struct hnbgw_cnpool to be a talloc context
We currently have some memory corruption somewhere in osmo-hnbgw in production
which we need to figure out. The previous talloc context hierarchy was
really messy, and it was probably a left over of previous states of the code
where we didn't have support for several sccp clients, etc.
This patch is a step towards clarifying that hierarchy.

Change-Id: I62a511e3ea0254d0327f2664d504b9195416bc76
2025-05-12 10:50:41 +02:00
Pau Espin Pedrol
4e88e2b4ee cnlink: Fix several funcs missing prefix
Change-Id: If20890bb99885e8c18732588376f8fdaa326d639
2025-05-08 13:29:36 +02:00
Pau Espin Pedrol
894a3a455e Split cnlink FSM code to its own file
As usual, it makes it a lot easier to read an FSM by having all of it
enclosed in one file.

Change-Id: I7e40beb13c2174e01d89915a28395a364709b321
2025-05-08 11:43:44 +02:00
Pau Espin Pedrol
135a8b079f Split hnb_context,hnb_persistent,umts_cell_id to their own files
It's really disturbing having to cope with all HNB related code while
for instance focusing on CN side of things.
Properly split the HNB related code to its own files so it can be left
out easily when needed, or stuff found more easily when required.
It also makes it easier to extend it later.

umts_cell_id is split to its own module since it's needed both in
hnb_context and hnb_persitent, and it's really independent of everything
else in the code.

Change-Id: I4ff65d14f94d99a57316c2483c92244078bb5dd9
2025-05-07 22:26:40 +02:00
Pau Espin Pedrol
df7dc3b6f4 Move hnbgw_cnlink to cnlink.h and hnbgw_cnpool to hnbgw_cn.h
Simplify mastodontic hnbgw.h and properly split per object/entity.
We actually have that in C files (cnlink.c and hnbgw_cn.c), so do the
same here.

This makes it easier to spot where stuff is, and extend it later (like
adding stats, etc.).

Change-Id: I75988112cff6c8c17d2bdb166a0934934c523dfc
2025-05-07 22:00:40 +02:00
Pau Espin Pedrol
8a573665c4 sccp: Introduce UL-related cnlink rate counters
Change-Id: I1878fcc8c265dc4d457332e16556d3ece769ea3e
2025-05-07 20:31:04 +02:00
Pau Espin Pedrol
72c96d4792 sccp: Unify path submitting N-*.req
This allows having clearer code paths and adding rate counters.

Change-Id: I37b414cf769a16b7c7f596df3bb91a6a91a06298
2025-05-07 20:28:37 +02:00
Pau Espin Pedrol
b561656431 sccp: Introduce rate counter CNLINK_CTR_SCCP_RLSD_CN_ORIGIN
In the general usual case we expect the CN to orderly tear down the conn
by sending a SCCP-DT[RANAP-Iu-ReleaseCommand], which HNB will answer
with RUA-Disconnect[RANAP-Iu-ReleaseComplete], which will trigger a RLC
HNBGW->CN and finally (in state WAIT_RLSD in context_map_sccp) we will
receive a RLSD.

The fact that the CN is unilaterally  and directly tearing down the conn
at SCCP level may be useful to spot malfunctions at the CN (eg. SCCP
conn at CN timing out because it never answered the RANAP/DTAP on top,
etc), so it's a good thing to count.

Related: SYS#7454
Change-Id: Icc6d29192574536d3c954a06645f178fa66bff50
2025-05-07 19:29:26 +02:00
Pau Espin Pedrol
8aef494af5 sccp: Introduce DL-related cnlink rate counters
Change-Id: Ib4a87aec3d7bb01be833dd33c597a33d9bbad859
2025-05-07 19:29:21 +02:00
Pau Espin Pedrol
718d71d3db Fix lifecycle of struct hnbgw_sccp_user
There's one struct hnbgw_sccp_user per cs7 instance configured in the
hnbgw process. Multiple cnlinks configured on the same cs7 instance will
be referencing/using the same struct hnbgw_sccp_user pointer.
Hence, it makes no sense to allocate the struct using a talloc context
of a cnlink as a parent, nor to keep any kind of specific 1-1 reference
to it.
Instead, we need to keep it alive in a ref-count basis, and unbind it
from libosmo-sigtran sccp SAP and destroy it once no more cnlinks use
it.

Change-Id: I524f6da8e5936135b2153def103d83cec6549f94
2025-05-07 19:03:57 +02:00
Pau Espin Pedrol
24c850f8f0 sccp: Use proper wording s/LOG_HSI/LOG_HSU
Change-Id: I9b543416488a8396c3b548c63169374f999f32fc
2025-05-07 18:51:46 +02:00
Pau Espin Pedrol
cb03fc80c4 Move SCCP prim handling to its own hnbgw_sccp.{c,h} file
Similar to what we already have for other protocols; helps properly
splitting logic per layer.

Change-Id: Idb45a25f7ff4bcd38ffd27bf1d3360b9d34149b3
2025-05-07 17:27:43 +02:00
Pau Espin Pedrol
a3cceea45a Split struct hnbgw_sccp_user allocation & setup to its own function
Preparation for follow-up commit splitting whole SCCP handling to its
own file.

Change-Id: I04c36bc0193554fcaf6be5e20573052c421af49d
2025-05-07 17:26:41 +02:00
Pau Espin Pedrol
1386a0da73 Makefile.am: format to one file per line
Take the chance to order them alphabetically.

Change-Id: I132fa290e3ef06e72d86a78310f33ddff094a9a4
2025-05-07 15:56:57 +02:00
Pau Espin Pedrol
5ebcace219 sccp: Drop error logging for expected scenario
The CN is free to release the SCCP conn at any point through an RLSD,
eg. because the end user on the other side decided to do so.
This was spotted in a osmo-hnbgw deployed in production, where the CN is
sometimes not answering the SCCP-CR[RANAP["Activate PDP Context Req"]]
(only answering with CC but never answering the DTAP on the upper layers).
As a result, most probably the peer times out and decides to
unilaterally release the SCCP conn.

Related: SYS#7454
Change-Id: Ia0941574b05256d6e49babe58df59d938cf637a2
2025-05-06 15:57:59 +02:00
Pau Espin Pedrol
bb3f454c8b context_map_sccp: Queue RUA->SCCP RANAP msgs while in SCCP WAIT_CC state
HNB transmits the first RANAP PDU over a RUA Connect msg, which creates
the session without need for any confirmation from HNBGW.
As a result, HNB is allowed to transmit more RANAP PDUs after that.
On the other side HNBGW, SCCP conn establishment towards CN consists of
a CREQ msg and then wait until CC (or CREF) is received. HNBGW is unable
to transmit any more data during that time, only the one appended to the
CREQ SCCP msg.
If the RAN transmits any more RANAP PDU over RUA while SCCP side is
still waiting for CC, we were discarding the message. Instead, queue it
and transmit it once finally the CC is received from the peer.

Related: SYS#7453
Change-Id: I307ded905901421f8228fab720b3989a2f94412b
2025-04-30 19:49:58 +02:00
Pau Espin Pedrol
ca8fc60231 Fix MAP_SCCP_EV_CN_LINK_LOST never dispatched
Commit introducing event MAP_SCCP_EV_CN_LINK_LOST into the FSM actually
never dispatched it, and instead dispatched MAP_SCCP_EV_RAN_LINK_LOST,
probably a copy-paste typo.
At the same time, it becomes clear no param is passed to the FSM when
dispatching it, so update related code paths since it becomes clear no
ranap_msg is passed.

Regarding comment in map_sccp_connected_action() about sending a RANAP
RESET, that should be done previously through a
MAP_SCCP_EV_RX_DATA_INDICATION prior to dispatching the CN_LINK_LOST
event if needed, similar to how's done in the context_map_rua FSM.

Fixes: b1c0bb19e2
Change-Id: I3a37522da16d9a06764f58c2da9579b397d8a7f2
2025-04-30 14:49:54 +02:00
Pau Espin Pedrol
3ef79eb800 cosmetic: context_map.h: Document parameters of all map_sccp_fsm_event values
Change-Id: I53603ade5157ddbccb49864a2edab09376fe8979
2025-04-29 19:13:26 +02:00
Pau Espin Pedrol
5676dfbb75 rua: Handle event TX_DIRECT_TRANSFER in disconnected state discarding msg
Related: SYS#7452
Change-Id: Id68f219ce776fbbfaa80d3b3ed976f48bef4f883
2025-04-29 18:58:04 +02:00
Neels Janosch Hofmeyr
05dd0272d0 fix recently introduced indenting blunder
Related: 0b4374bb9f
	== I556e01b26a71b43432c623adb7ea5af2114981e3
Change-Id: Ia9c7da4d69ab2521ff726579d11df51af0e73388
2025-04-26 01:19:30 +02:00
Pau Espin Pedrol
0b4374bb9f cosmetic: Improve ranap_msg passing ownership documentation
In general ranap_msg is made owned by OTC_SELECT, and under RAB
Assignment where PFCP or MGCP is involved the msg ownership is
temporarily stolen to the related FSM instance.

Change-Id: I556e01b26a71b43432c623adb7ea5af2114981e3
2025-04-25 18:13:16 +02:00
Pau Espin Pedrol
f5b5c75d2b mgw_fsm: use LOG_MAP() to log error
Change-Id: Ic543a3c17eddf4b1da51172223eccaa62a441fd3
2025-04-25 18:13:16 +02:00
Pau Espin Pedrol
ca09bc0c44 Change level ERROR->NOTICE trying to transmit msg if RUA disconnected
Change-Id: I22210e1699709853a9a8bc828cbe5d6379e79e08
2025-04-25 17:50:55 +02:00
Pau Espin Pedrol
70e2ee8a0a ranap: No need to set destructor function twice
talloc destructor function is already being set in
hnbgw_decode_ranap_ran_co(), no need to do it twice. This is a leftover
from recent refactoring.

Fixes: 79c5600bb6
Change-Id: I9caaff3bd6435cde51006cd14837f3824c77c68b
2025-04-25 17:46:07 +02:00
Pau Espin Pedrol
edc7baa3b8 change log level patching GTP info NOTICE->INFO
Change-Id: I93b8634ff4de52fb7e3f2bfd915552173f27a09a
2025-04-25 16:18:33 +02:00
Pau Espin Pedrol
050704c562 Change log level new conn created NOTICE->INFO
Change-Id: Ie8a50312abc3dd4a438556f6f626d069259cc551
2025-04-25 16:11:53 +02:00
Pau Espin Pedrol
d25fa3723b rua: Change log err level ERROR->NOTICE regarding received unexpected RUA context
This is not really an internal error but can actually happen if the HNB
or the HNBGW crashed and state is no longer the same in both.

Change-Id: Ib588bb71ccbe716a495afec27d38f48881e7e1bc
2025-04-25 14:34:11 +02:00
Pau Espin Pedrol
bad546b431 Improve paging to support Paging Area ID
Avoid broadcasting to all HNBs if CN requests paging on a specific
LAI/RAI.

The overall logic is also improved, eg. avoid adding a record if we
didn't forward any paging request at all...

Change-Id: I915254a39ee04898aa740674b4632a328f281955
2025-04-11 12:50:52 +00:00
Pau Espin Pedrol
af0a6aeb39 Set 'hnbap-allow-tmsi 1' by default
Due to historical reasons [0], we didn't allow UE Registration with
(P)TMSI by default, only with IMSI. The historical reasons were fixed a
long time ago, and hence why virtually every osmo-hnbgw you can find
nowadays (see doc/examples/ dir) contained an explicit "hnbap-allow-tmsi
1" to override the default behavior.

However, the fact that this was not the default behavior causes some
problems on production network which didn't have this config set
explicitly. Hence, enable it by default.

[0] Original osmo-hnbgw code in osmo-iuh.git
12181a937ff5658af49e12c57cb08ecba859e1f1 where this feature was added
explains with more detail the problem. If UE registered with TMSI, then
the HNBGW at the time didn't have information about its IMSI and hence
could not page the UE from IMSI. Since ~5.5 years ago (Sep 2019 in
osmo-sgsn.git 10b3d70fea9628eecb58f07e1bce31f63823edfb), osmo-sgsn
already sends a Common Id to the HNBGW containing the UE IMSI when it
registers, so the HNBGW has no related problem anymore.
The VTY command is left available in the event somebody wants to operate
osmo-hnbgw with a broken MSC/SGSN (like older mentioned osmo-sgsn).

We also removed tons of ue-context related logic from osmo-hnbgw in
osmo-hnbgw.git 15e552f232, and we have
plenty of ttcn3 tests validating paging scenarios, so it doesn't seem we
have problems anymore with current code base.

Related: SYS#7430
Change-Id: I3c4c0decf34959fc2d5683b02224f27d204aa5e9
2025-04-10 15:22:46 +00:00
Pau Espin Pedrol
78fd0b97a9 Drop unused struct ue_context
Commit submitted 2 years ago dropping list of HNBAP UE Context
actually forgot to drop the struct declaration too.

Fixes: 15e552f232
Change-Id: I0c887ab6e9674d7f123668ab38dee8cc6e66469d
2025-04-10 12:25:36 +00:00
Pau Espin Pedrol
580411823d Fix RAC size in struct umts_cell_id
RANAP_IEs.asn:
"""
RAC					::= OCTET STRING (SIZE (1))
"""

Change-Id: Ide7dfd5d0ae87826cb7759d283be6da81dae90f4
2025-04-09 21:20:27 +02:00
Pau Espin Pedrol
5d91358114 Log paging response not matching any request by MI
Change-Id: I9c775901b5042b33382e56ac4e2c301524b734a1
2025-04-09 17:58:42 +02:00
Pau Espin Pedrol
d8b9a53a44 Move UL CO RANAP processing to its proper layer file
Change-Id: I0d16f6c1c303aa1275322f12faf332de8d7c3147
2025-04-08 21:14:38 +02:00
Pau Espin Pedrol
804ebe1593 Move DL CO RANAP processing to its proper layer file
Change-Id: Ifcb8a23becb57ccad6c9aa4769b6f27d31a71c96
2025-04-08 21:03:45 +02:00
Pau Espin Pedrol
8897a7764a Move DL CL RANAP processing logic to its proper layer file
This way we effectively split lower layers (SCCP and below) related to
the link towards CN from the RANAP payload we need to process and
forward.

Change-Id: Icf18fe66d0483165fe487fe850b25830faf90150
2025-04-08 21:02:24 +02:00
Pau Espin Pedrol
7f3a50c619 Move paging logic to its own file
Paging logic contains quite a lot of logic which is really specific to
paging topic and which can be put together and encapsulated easily.

Furthermore, this logic will mostly grow in the future since right now
we only offer minimal paging features by broadcasting to all HNBs.

Finally, this also simplifies hnbgw_cn.c

Change-Id: I402fdcdc261626798904e7ee2eb5db80d30c4c34
2025-04-08 20:20:48 +02:00
Pau Espin Pedrol
79c5600bb6 Add function helper hnbgw_decode_ranap_ran_co()
Similar to already existing counterpart hnbgw_decode_ranap_cn_co().
This helps simplifying logic into smaller chunks, and will make both UL
and DL look more similar.

Change-Id: Ifbb058d5340a6cb3a1703415e49c0de04fa6aec1
2025-04-08 19:48:14 +02:00
Pau Espin Pedrol
aa2cefb173 Rename function s/hnbgw_decode_ranap_co/hnbgw_decode_ranap_cn_co/g
This function is only used to decode UL RANAP messages. Mark it as "CN"
like most other related APIs.

Change-Id: Ia4e5a21a9724ebf759b62c13759a776189b09e91
2025-04-08 18:56:41 +02:00
Pau Espin Pedrol
d37f3dc20c hnbap: Make hnbgw_tx_ue_register_acc_tmsi() signature similar to hnbgw_tx_ue_register_acc()
Make those functions look more similar by calling them with
get_next_ue_ctx_id() as param in hnbgw_rx_ue_register_req().

Change-Id: I9ce01babda7cb7e415cb7514c26f10a1773166fa
2025-04-08 17:46:31 +02:00
Pau Espin Pedrol
f063d6bab8 cosmetic: Add spec references for RUA, RANAP, HNBAP
Change-Id: I0e638befaa1e5093dc0c95e77414bfd9d9179a66
2025-04-08 16:29:36 +02:00
Pau Espin Pedrol
619b81e5dc Log RAB_STATE name instead of value
Related: SYS#7338
Change-Id: I24593a1db1334052f3a79f59a521228393fc1ed7
2025-02-21 17:32:38 +01:00
Oliver Smith
a824ebd8c2 Bump version: 1.6.0.24-71ad-dirty → 1.7.0
Change-Id: Ibc2242eb7e72b6732160eebf6a1f95861d95c827
2025-02-12 14:37:35 +01:00
Pau Espin Pedrol
71ad913d99 peek_l3_ul_nas: Parse PTMSI MI from GMM RAU req
old_ra param in decode_gmm_tlv() is dropped since it was not really
used, and all callers already parse old_ra in the manual mandatory
parsing step.

Related: OS#6717
Change-Id: I570eb31dcee94a3ebef25e15ac7cd7199ec9a79e
2025-02-07 14:06:46 +01:00
Pau Espin Pedrol
dbbfefad2c peek_l3_ul_nas: Improve GMM Attach Req parsing checks
Related: OS#6717
Change-Id: Ic9815215d40d7bb59709dcbffa0a4673f1e0e710
2025-02-07 14:06:46 +01:00
Pau Espin Pedrol
167f7aaac8 peek_l3_ul_nas: Improve RAU req parsing checks
Used recently updated osmo-sgsn gprs_gmm_parse_ra_upd_req() as a guide.

Related: OS#6717
Change-Id: I1a6c642e7017e159a2035869c75475d0de7fbb75
2025-02-07 14:06:46 +01:00
Pau Espin Pedrol
4ed837cba8 peek_l3_ul_nas: Split parsing logic based on PS/CS ctx
It makes no sense to receive an MM message over PS context or a GMM
message over a CS context, hence better clearly split the parsing code
paths to avoid unintended logic execution.

Related: OS#6717
Change-Id: I198f2fd14952362c4d189663d067c7f9dae2ed05
2025-02-07 14:06:41 +01:00
Pau Espin Pedrol
7784eb4e3b map_sccp: Fix crash during ev MAP_SCCP_EV_RAN_DISC in st MAP_SCCP_ST_WAIT_CC
Recent commit changed event MAP_SCCP_EV_RAN_DISC to have a bool
parameter instead of a msgb pointer, however only code in state
MAP_SCCP_ST_CONNECTED was updated to account for the change; other
states were left checking the data ptr as if it was a msgb and crashed
while accessing mem address 0x1 (bool true) in msg_has_l2_data().

Fixes: 7958955624
Related: SYS#7208
Change-Id: If53dbd6b5a8abffcf94e6d666209954f17e9f9d7
2024-12-11 14:18:09 +01:00
Pau Espin Pedrol
1a15261931 jenkins.sh: No need to build libosmo-sigtran with doxygen
Change-Id: I71a1808ed0f5cbd1a27d3bf2f2e0f7fdcab9eeda
2024-12-10 17:00:21 +01:00
Pau Espin Pedrol
fb450888b6 jenkins.sh: libosmo-netif no longer depends on libosmo-abis
Depends: libosmo-abis.git Change-Id I079dc3999de508301dd37ed03e399356a58d3cab
Depends: libosmo-netif.git Change-Id I13d6e88158f6d9ce017986283183ee9c2cc68cae
Change-Id: I27a938f2a178a5ff59f371ec4d19b4405737eb51
2024-11-21 16:55:56 +01:00
Pau Espin Pedrol
a3c0a00573 Use new libosmo-sigtran APIs to access osmo_ss7_instance
Depends: libsomo-sigtran.git Change-Id I8f09aff41666822f759f99fa91c13e3b9d89d530
Change-Id: I3549ff3ace641f0b0574a9ddec14120299446434
2024-11-08 15:58:05 +00:00
Pau Espin Pedrol
e6d87ff967 hnbgw: Fix wrong map object retrieved from hashtable
If several map objects (mapping between RUA and SCCP transports for a
given Iu connection) ended up in the same hashtable bucket, then the
first one was always returned by map_from_conn_id().

As a result, when such collisions happened (e.g. when a big number of
connections were inserted) a user could see a Iu message coming from SCCP
connection "A" end up being forwarded to an unrelated RUA connection
"B".

Related: SYS#6602
Fixes: f3caea850b
Change-Id: I107f461bf5bcb92262422c893b23d190659f6f72
2024-11-06 16:47:24 +01:00
Pau Espin Pedrol
1ac1d1277b hnbgw: Avoid using struct osmo_pfcp_cp_peer fields directly
Use new APIs available in libosmo-pfcp, since those fields will become
private.

Depends: libosmo-pfcp.git Change-Id Ic8a5d5ec4237aa8520d426252c924c9617d1d08a
Change-Id: I2f587cfb7fd556bcfc43bf70fc93bc79a3c211ca
2024-11-04 18:07:22 +01:00
Pau Espin Pedrol
1d4811e38c Add PFCP stats item group
This will be used by TTCN3 testsuite to figure out whether it should
expect a PFCP Assoc Setup from HNBGW.

Depends: libosmo-pfcp.git Change-Id Ibc8047856ddcc9c71f2b4cf30f577862b6b414ca
Change-Id: Ic71df8df83e97f4015077677e426c803f84d31ea
2024-11-04 18:06:58 +01:00
Pau Espin Pedrol
89504050ea ps_rab_ass_fsm: Fix uninitialized ptr access
/git/osmo-hnbgw/include/osmocom/hnbgw/context_map.h:211:9: error: 'rab' may be used uninitialized [-Werror=maybe-uninitialized]
  211 |         _map_rua_dispatch(MAP, EVENT, MSGB, __FILE__, __LINE__)
      |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/git/osmo-hnbgw/src/osmo-hnbgw/ps_rab_ass_fsm.c:358:9: note: in expansion of macro 'map_rua_dispatch'
  358 |         map_rua_dispatch(rab->map, MAP_RUA_EV_TX_DIRECT_TRANSFER, msg);
      |         ^~~~~~~~~~~~~~~~
/git/osmo-hnbgw/src/osmo-hnbgw/ps_rab_ass_fsm.c:297:24: note: 'rab' was declared here
  297 |         struct ps_rab *rab;
      |                        ^~~

Change-Id: Ifdcd4c369b35820c5c2beb695392d0b82d4ea34c
2024-11-04 12:35:18 +01:00
Pau Espin Pedrol
7958955624 Fix SCCP RLSD not sent upon rx RUA Disconnect due to error condition
Scenario: HNB tears down the RUA/Iu conn by first sending
RUA_DATA[RANAP IuReleaseReq], followed shortly by a RUA Disconnect with no
RANAP and Cause!=Normal. HNBGW should release the SCCP connection through
RLSD since it's impossible to terminate the Iu connection in a normal
condition.

Validated with TTCN-3 HNBGW_Tests.TC_cs_iu_release_req_rua_disconnect.

Related: osmo-ttcn3-hacks.git Change-Id I6782920c4a86d3311eb54239ab13a18e393c1ec0
Related: SYS#6602
Change-Id: I1b7f72034eaf40bfa60d552d434847467ac4e97a
2024-10-30 18:37:32 +01:00
Pau Espin Pedrol
ca6f6fded9 Log cn_disconnect.ind cause
Change-Id: I64cd596e55b102924f39264cd2eae7a41f5f3212
2024-10-26 01:13:46 +02:00
Neels Janosch Hofmeyr
3140381337 on RUA DT for unknown context, respond with RUA Disconnect
If we receive a DirectTransfer for an unknown RUA context, respond with
a RUA Disconnect to make sure the peer knows there is no such context.
(Do not respond to RUA Disconnect though.)

Related: OS#6283
Tweaked-by: Oliver Smith <osmith@sysmocom.de>
Change-Id: If0b9a999649c9822f55b42b0cae8408af668e1b8
2024-10-24 13:46:32 +02:00
Neels Janosch Hofmeyr
6f0197878a on RUA Connect failure, respond with RUA Disconnect
Related: OS#6283
Related: osmo-ttcn3-hacks I2b5c024a395e736538c0c37db40d34f3b8d99991
Tweaked-by: Oliver Smith <osmith@sysmocom.de>
Change-Id: Iafb30c31a4c5db53ecdda99a0e0b5937b71e362c
2024-10-24 13:43:31 +02:00
Harald Welte
3fb99d143a Revert "hnb_persistent: Use incrementing counter for rate_ctr + stat_item index"
This reverts commit 61e278a452 as it
created errors in our test suite.  Let's live with the cosmetic error
message but have working tests instead.  We should probably consider
removing the printing of the message from libosmocore, at least in case
the counters also have names set?

Closes: OS#6557
Change-Id: I2f7d3f3c88775e2926661e4760ec1d8723c0400c
2024-10-07 16:51:48 +00:00
Oliver Smith
30068ad391 contrib/jenkins: libosmo-sccp -> libosmo-sigtran
Change-Id: I4eeda27c331399a1971ce8d5a398861d4dac8403
2024-09-16 12:32:53 +02:00
Neels Janosch Hofmeyr
1eb2a759ad coverity CID#358069
Change-Id: Ib2ed76c1bdae96457f61c2e015846566f100b8d6
2024-09-03 05:50:31 +02:00
Neels Janosch Hofmeyr
280f9e6ca9 coverity CID#358070
Change-Id: I742e95fa97f41ca4efe77055c5f29f73e48826d6
2024-09-03 05:50:31 +02:00
Neels Janosch Hofmeyr
2fc8abeecf coverity CID#358072
Change-Id: I90b0c73530fa3ba8b0bf74728d5b215957ef7184
2024-09-03 05:50:31 +02:00
Neels Janosch Hofmeyr
71f3169140 coverity CID#358071
Change-Id: I8237bf7d4985e993bb10aaaa9370cde2ece3d812
2024-09-03 05:50:31 +02:00
Harald Welte
61e278a452 hnb_persistent: Use incrementing counter for rate_ctr + stat_item index
This will remove the annoying log messages like

DLGLOBAL ERROR counter group 'hnb' already exists for index 0, instead using index 1. This is a software bug that needs fixing. (rate_ctr.c:221)

Closes: OS#6433
Change-Id: I0bb6a679fb05a6aba917f089401fe3765a5d1fc3
2024-08-12 17:46:30 +00:00
Neels Janosch Hofmeyr
4468d8728e drop config.vty tests from make check
The intention of those tests was to test various osmo-hnbgw startup
scenarios. It seemed the fastest/simplest way to do it at the time, but
the output depends on another library, regularly causing test fallout.

Keep the tests around for reference. They can still be invoked manually
with 'make config-tests'.

Related: OS#6380
Change-Id: I9c1540904b13d25db1c7933a88f6cc4b028fdd2b
2024-08-03 03:15:19 +02:00
Oliver Smith
0081cd7b00 Bump version: 1.5.0.82-610a → 1.6.0
Change-Id: I61af4064f83792aedeb4b9ba1b45c7e29da3705e
2024-07-25 10:05:59 +02:00
Harald Welte
610aae4d65 kpi_ranap: Avoid null pointer de-ref during HNB shutdown
In the downlink path, we cannot assume map->hnb_ctx is always
non-NULL.  If the HNB has just disconnected, it might be NULL,
while we're still processing downlink messages from the CN which
were sent by it before it realized that HNB was gone.

Closes: SYS#7010
Change-Id: I9a304b9e0cbc18dbf7b699f4aae6b91ca0c16173
2024-07-19 09:34:58 +02:00
Neels Janosch Hofmeyr
3ef65760ff fix MGCP compat with osmo-mgw <= 1.12.2: CRCX in recvonly
Fix a recently introduced problem with MGCP to osmo-mgw:
Send the first CRCX in recvonly mode, not sendrecv. osmo-hnbgw always
sends an additional MDCX including sendrecv mode anyway.

osmo-mgw currently forbids sending an initial CRCX in connection mode
'sendrecv', with this error message:

  DLMGCP ERROR endpoint:rtpbridge/2@mgw CI:7F4C8EDD CRCX: selected connection mode type requires an opposite end! (mgcp_protocol.c:1090)

I am submitting an osmo-mgw patch to not fail there, but we want to and
can easily be compatible with current and earlier osmo-mgw:

Sending the initial CRCX in sendrecv was introduced in commit:

	"drop legacy hack: do not start MGW endp in loopback mode"
	da7d33e284
	I0eca75d7abf66f8b9fde9c68ec10d4265f64a189
	This patch has not been part of a release yet.

The intention of that commit was to get away from loopback mode. The
logical mode to pick instead indeed is sendrecv, but by that osmo-hnbgw
triggers above osmo-mgw error.

Related: SYS#6974 SYS#6907
Related: osmo-mgw Ic089485543c5c97a35c7ae24fe0f622bf57d1976
Change-Id: I004f96ae36774ceb33f177c9f58f820fefa3ca14
2024-06-24 03:28:55 +02:00
Neels Janosch Hofmeyr
00655d45dd dbg log: nft kpi: clarify nr of rate ctrs vs nr of hnbp
Related: SYS#6773
Change-Id: I84346d3151f3040967f39a3a6e6db2e29bc1e2ec
2024-06-15 02:53:14 +00:00
Neels Janosch Hofmeyr
15e552f232 drop list of HNBAP UE Context
Last year, I have fixed some more ue_context leak situations.
But since we don't really use ue_context for anything, we could also
just drop this completely.

On HNBAP UE Register, we collect the ue_contexts in a ue_list. But we
never do anything with this list, at all. Furthermore, users are
reporting the list of ue_context growing indefinitely.

Simply drop the ue_context listing. Simply acknowledge all HNBAP UE
Register and DeRegister requests without storing any context IDs.

Change-Id: Ida7eadf36abcf465ae40003725c49e8e321a28c9
2024-06-15 04:14:05 +02:00
Neels Janosch Hofmeyr
e29816eccc nft-kpi: remove X34 drifting: adjust delay by elapsed time
Related: SYS#6773
Change-Id: I04f572890c04a48bb19c59f613a492ef96624baa
2024-06-12 04:24:31 +02:00
Neels Janosch Hofmeyr
fc0e505330 nft-kpi: log errors of counter retrieval
Make sure errors of getting counters from nft are logged.

Some context: we'll try again each X34 period, hence this is only a
problem when the error persists.

For example, when the get-counters thread is faster than the maintenance
thread can even create the nft table initially, this error will show.

We could add a definite check whether the maintenance thread has created
the tables yet, and wait for that event. But this complexity is not
really needed: it is fine to just fail getting counters once or twice.

Related: SYS#6773
Change-Id: I84340482e4a5bfcac158a21c9378a9511fa5ea10
2024-06-12 04:24:31 +02:00
Neels Janosch Hofmeyr
3da951d5c3 improve HNBAP error logging
We have a situation where HNBAP is not answered by osmo-hnbgw, and the
log is all silent. Add logging to a lot more of the possible HNBAP
failure paths to find out what is wrong.

Related: SYS#6810
Change-Id: I17d2809f59087d32e7c11a3ada1d3fadf6f0b660
2024-06-11 00:40:42 +00:00
Neels Janosch Hofmeyr
2a4af66669 prevent use-after-free after FSM instance termination
- Set osmo_fsm_set_dealloc_ctx(OTC_SELECT) in osmo-hnbgw's main().
- Only dispatch RANAP when FSM instances aren't terminated.

This way we possibly pre-empt use-after-free crashes for deallocating
FSM "nests" for obscure corner cases.

Use-after-free is a general problem for FSM design. For this, we created
osmo_fsm_set_dealloc_ctx(): When an FSM is terminated, move it to a
separate talloc context, instead of being deallocated.

An actual use-after-free was observed as described in OS#6484, but that
needs a separate, orthogonal fix:

When the Iuh link is lost while the CN link is waiting for SCCP CC or
CREF -- the better solution is described in OS#6085: don't wait for CC
at all, just dispatch DISCONN to SCCP-SCOC.

So even though the code where a crash was observed will be removed, this
patch is a general safeguard against corner case crashes, improving
general stability.

Related: OS#6484
Change-Id: Ib41e1a996aaa03221e73643636140947ac8f99e2
2024-06-11 00:04:58 +00:00
Neels Janosch Hofmeyr
62da064eda hnb_persistent: introduce disconnected timeout
Add timer hnbgw X35: Clean up all hNodeB persistent state after this
time of the hNodeB being disconnected. Set to zero to never clear hNodeB
persistent state. (default is 60*60*24*27 = a week).

The idea is to bound memory usage at sites where hNodeB do not stay on
one fixed cell id, but use a different cell id after each Iuh
reconnection.

Related: SYS#6773
Related: osmo-ttcn3-hacks Ibec009203d38f65714561b7c28edbdbd8b34e704
Change-Id: Ic819d7cbc03fb39e98c204b70d016c5170dc6307
2024-06-06 07:26:44 +02:00
Neels Janosch Hofmeyr
938b5ac777 add LOG_HNBP() for hnb_persistent
Add macro for the pattern LOGP(DHNB, ..., "%s: ...", hnbp->id_str)
and use where applicable.

Change-Id: I23334124074491fb3b91e720b67c97181c16bc21
2024-06-06 07:19:05 +02:00
Neels Janosch Hofmeyr
a503bd4eb2 3-digit MNC: use osmo_plmn_id in struct umts_cell_id
Properly represent the mnc_3_digits flag in umts_cell_id, and preserve
the three digit indicator as received on the wire.

Before this patch, the indicator for a three digit MNC received on the
wire was discarded, and instead g_hnbgw->config.plmn.mnc_3_digits was
used to convert any PLMN to string, whether it had 3 digits or not.

== hnb_persistent_list:

The cell id is used as primary key in the list of hnb_persistent
instances. This patch prevents any collisions between 2-digit and
3-digit MNCs (however unlikely in practice this may be).

== nft_kpi.c:

Just like the cell ids in hnb_persistent, the ids' strings are used as
primary key in nftables rulesets in nft_kpi.c -- also prevent MNC
collisions there:

Properly transport the 3-digit property in conversions:
  struct umts_cell_id <-> string
Uncouple to_str conversion from the PLMN set in the hnbgw VTY cfg.

Related: OS#6457
Change-Id: Id9a91c80cd2745424a916aef4736993bb7cd8ba0
2024-06-06 07:19:05 +02:00
Neels Janosch Hofmeyr
da8a042ead add umts_cell_id_test.c
Prepare for adding proper mnc_3_digits support to struct umts_cell_id.

Show current behavior of the umts_cell_id <-> string conversions.

Show two expected errors in umts_cell_id_test.ok: the three-digit MNC
with leading zeros is lost (because the g_hnbgw->config.plmn has
mnc_3_digits == false).

The expected errors will be fixed in upcoming patch
Id9a91c80cd2745424a916aef4736993bb7cd8ba0

Related: SYS#6773
Change-Id: Ibbb61a2c53a11dea794f451d3074bc9ba50862fe
2024-06-06 07:19:05 +02:00
Neels Janosch Hofmeyr
b3b2e2b20d umts_cell_id: add umts_cell_id_to_str_buf()
Prepare for umts_cell_id_test.c: allow testing the to_str conversion
without using talloc.

Change-Id: I3cc0169593c73c2e658637e4116ddd578f83df6d
2024-06-06 07:19:05 +02:00
Neels Janosch Hofmeyr
9da60749ca rename to umts_cell_id_to_str()
A subsequent patch adds umts_cell_id_to_str_buf(), and before the old
foo_name() pattern spreads further, I'd rather rename it.

Rationale:
- There is a umts_cell_id_from_str() function.
- The foo_name() is an old pattern, we prefer foo_to_str() now.
- Contained within osmo-hnbgw, no API problems.

Change-Id: I3124d1f5e634bc895ec347cb1a9816789fd9ab69
2024-06-06 07:19:05 +02:00
Neels Janosch Hofmeyr
02d1d51966 use osmo_jhash for the hnb_persistent hashtable
Use hashing like the linux kernel.

Related: SYS#6773
Depends: I0c9652bbc9e2a18b1200e7d63bb6f64ded7d75fa
Change-Id: I5441db4293dc6b57a1c606ef830656fa9fa01943
2024-06-06 07:19:05 +02:00
Neels Janosch Hofmeyr
9a7e520a95 fix stat_item leak in hnb_persistent_free()
Add missing stat_item free in hnb_persistent_free().

We recently fixed a rate_ctr leak in
I14e050bfb91b993f194e3800eacdc0d10f2b1a4e, but missed the also leaking
stat_item.

Particularly relevant with upcoming patch
Ic819d7cbc03fb39e98c204b70d016c5170dc6307 -- testing that patch revealed
the leak.

Related: osmo-ttcn3-hacks Ibec009203d38f65714561b7c28edbdbd8b34e704
Change-Id: I7326c53d595dce7b442eced89ff8f4b972bd2a82
2024-06-06 07:18:49 +02:00
Neels Janosch Hofmeyr
20b710bfc2 add hnb_persistent hashtable: optimize lookup by cell id
Mainly the new nft counters do a lot of hnb_persistent lookups by cell
id.

Add a hashtable to optimize looking up hnb_persistent instances. So far
we iterate the linear list of hnb_persistent for each and every counter
returned from nft_kpi.c.

Also improves lookups for HNBAP HNB* operations (rare).

A follow-up patch uses a better hash function from libosmocore
(jhash.h).

Related: SYS#6773
Change-Id: Iecb81eba28263ecf90a09c108995f6fb6f5f81f2
2024-05-28 01:32:09 +02:00
Neels Janosch Hofmeyr
64a2debb9a per-HNB GTP-U traffic counters via nft
Add optional feature: retrieve GTP-U traffic counters per hNodeB (not
per individual subscriber!) using nftables, to provide new rate_ctr
stats.

This is a "workaround" to get performance indicators per hNodeB, without
needing a UPF that supports URR.

When an hNodeB registers, set up nftables rules to count GTP-U packets
(UDP port 2152) to and from that hNodeB's address -- we are assuming
that it is the same address that Iuh is connecting from.

From the per-hNodeB packet and byte counters from nftables, also derive
a "UE bytes" counter, which is counting only the GTP-U payload. Assume
IP header of 20 bytes; UDP and GTP-U headers are 8 bytes each:

  ue_bytes = total_bytes - packets * (20 + 8 + 8)

Query these periodically, as configurable by new timer X34. Default is
one second of wait time between querying counters (excluding the time it
takes to retrieve and update the counters).

Add compile-time switch --enable-nftables, to build with/without
external dependency libnftables. Default is without, as before.

Add jenkins axis NFTABLES to switch --enable-nftables.

Add cfg file option 'hnbgw' / 'nft-kpi' to enable use of nftables.
This requires osmo-hnbgw to be run with cap_net_admin.

The VTY config commands are always visible -- simplifies VTY testing.
Refuse to start osmo-hnbgw when the user is requesting nft-kpi in the
config but when built without --enable-nftables.

Do nft commands in 2 separate threads. Run the same request queue
implementation twice, with two thread workers to handle them:
- one thread receives all requests to init the nft table, add and remove
  hNodeB counters, and start and stop counting for a specific hNodeB.
- Another thread handles all retrieval and parsing of counters from nft.

The main() thread hence never blocks for nftables commands, and services
the responses from nft when they are ready, via an osmo_it_q registered
in the main() select loop.

Persistently keep an nftables named counter for each seen hNodeB cell id
in the nftables ruleset, for the lifetime of a hnb_persistent instance
that holds the target rate_ctrs.

Add the rules to feed into these persistent counters to the ruleset when
the particular cell attaches and detaches via HNBAP HNB (De-)Register.

On hnb_persistent_free(), remove all items relating to this cell id from
nftables, including the persistent named counters.

Loosely related: upcoming patches will implement
- a hashtable for faster cell id lookup (important for updating
  counters)
  Iecb81eba28263ecf90a09c108995f6fb6f5f81f2
- proper MNC-3-digit support in cell ids (better have a 100% correct
  primary key).
  Id9a91c80cd2745424a916aef4736993bb7cd8ba0
- idle timeout for disconnected hnbp, so we are sure stale state does
  not build up for eternity.
  Ic819d7cbc03fb39e98c204b70d016c5170dc6307

Related: SYS#6773
Related: OS#6425
Change-Id: Ib2f0a9252715ea4b2fe9c367aa65f771357768ca
2024-05-23 17:07:08 +02:00
Neels Janosch Hofmeyr
e4bd96a841 fix rate_ctr leak in hnb_persistent_free()
Change-Id: I14e050bfb91b993f194e3800eacdc0d10f2b1a4e
2024-05-23 04:17:36 +02:00
Oliver Smith
eccff1abe8 contrib/jenkins: set --enable-werror
Fixes: OS#6460
Change-Id: I6d85b077f57a86b0bad29cb868f1f4f848506f46
2024-05-22 08:44:06 +02:00
Neels Janosch Hofmeyr
a121f82f33 fixup: compilation error: unused var in map_rua_init_action()
The variable was left unused by recent patch
I3e1ad7a2aa71674a22a27c31512600f2de139032 aka
a5974d7906

Change-Id: I871bc43f6f47d4b78fbf88826615f2dbb8e1f807
2024-05-21 17:57:38 +02:00
Harald Welte
a5974d7906 KPI: Add initial set of DTAP message type rate counters
From an operational perspective, it may be interesting to know how many
LU/RAU/Attach attempts, rejects and accepts are happening in a given
hNodeB.  Let's add some common infrastructure for DTAP related
statistics as well as some initial counters.

Related: SYS#6885
Depends: osmo-iuh.git Change-Id I7dea74102da8b610ff2a310c5814f5c89f08e7a6
Change-Id: I3e1ad7a2aa71674a22a27c31512600f2de139032
2024-05-15 15:37:39 +00:00
Oliver Smith
0d2d966c15 debian/postinst: add checks, be verbose
Do not attempt to change permissions/ownership if the package gets
upgraded from a version higher than the next release.

Do not fail if the user deleted the config file.

Be verbose when changing permissions.

Related: OS#4107
Change-Id: I1bcbe414fd18101e4d875a16539deab7baf9cb5f
2024-05-14 15:21:05 +02:00
Oliver Smith
de99af3aaa contrib: remove rpm spec file
Related: https://osmocom.org/news/255
Related: OS#6446
Change-Id: Idd67d52ca736c4e145387ea8d4030f9cf4b9596d
2024-05-08 14:40:59 +02:00
Oliver Smith
ba81785b71 .deb/.rpm: various fixes related to non-root
* Explicitly chown /var/lib/osmocom to osmocom:osmocom, instead of
  relying on systemd to do it when the service starts up. This does not
  work with the systemd versions in debian 10 and almalinux 8.
* deb: Use "useradd" instead of the interactive "adduser" perl script
  from Debian. This makes it consistent with how we do it in rpm, and
  avoids the dependency on "adduser".
* deb: Consistently use tabs through the file, instead of mixing tabs
  and spaces.
* deb: Remove support for the "dpkg-statoverride --list" logic. This
  seems to be a rather obscure feature to override permissions for
  certain files or directories, for which it does not seem to be a good
  idea to make the postinst script less maintainable. Something similar
  can be achieved by using your own Osmocom config file in a different
  path with different permissions.

Related: OS#4107
Change-Id: I6dd0205fb65d4ad5a79821c111865e67fb293a73
2024-05-08 12:03:05 +00:00
Neels Janosch Hofmeyr
da7d33e284 drop legacy hack: do not start MGW endp in loopback mode
We used to tell osmo-mgw to create an IuUP endpoint in loopback mode, in
order to hack it into responding to an IuUP Initialization. The loopback
mode here in osmo-hnbgw is a leftover from that hack. Drop it.

Change-Id: I0eca75d7abf66f8b9fde9c68ec10d4265f64a189
2024-05-06 18:59:04 +00:00
Max
7449635520 .deb/.rpm: add osmocom user during package install
Create osmocom user & group during package installation.
Fix the configuration dir/files permission to match.

Related: OS#4107
Tweaked-By: Oliver Smith <osmith@sysmocom.de>
Change-Id: Ife9433291ae03392ae114ebda418bce8cc93fe3b
2024-04-24 11:52:19 +02:00
Pau Espin Pedrol
8fd95f3e74 hnbap: Avoid calling duplicate getpeername() for each registered HNB
Change-Id: I980de31d1296c3b956146a461609bec76ed3d430
2024-04-16 14:42:37 +02:00
Harald Welte
cd58308915 counters: Distinguish between normal and abnormal release cause
It is interesting to know if a release was normal (as expected/requested
by the NAS layer, typically indicating a user-requested call end) or
abnormal (radio failure, pre-emption or whatever other event that the
user did not expect).

Related: SYS#6773
Change-Id: Idd2f845b7db448064b693ac1efdc8db006a47a11
2024-04-09 08:12:03 +00:00
Harald Welte
e3cc5ddf1d HNBAP: Support IMSI identity type in hnbgw_tx_ue_register_rej()
Change-Id: I2e00968cbf686f78f5c9655e899963f2b84dd78b
2024-03-29 12:00:06 +01:00
Harald Welte
f64cdf85d1 HNBAP: use GSM23003_IMSI_MAX_DIGITS instead of magic number
Change-Id: I2ea0a6a93194da2efb768cd4245767301411eb41
2024-03-29 11:58:26 +01:00
Harald Welte
060619b811 HNBAP: Transmit ErrorIndication in more situations
Whenever we receive a message and cannot decode the most basic IEs,
or receive an unknown/unsupported procedure code, we should respond
with an ErrorIndication in order to inform the peer.

Change-Id: I7aaa66f83f62ee1b5ba5204248e9f4cc754263ed
2024-03-27 21:46:59 +01:00
Harald Welte
4b274a88ea HNBAP: Make sure to respond with correct "reject"
If we receive a procedure (like UE-REGISTER) in a state
where it's not permitted (e.g. HNB not registered), we should
send a UE-REGISTER-REJ with proper cause value, rather than not
sending any response at all.

Change-Id: I300db368a3d1d2fb5967f69f2ed4ac90ecf85e75
2024-03-27 21:46:59 +01:00
Harald Welte
b57afbe1a6 HNBAP: Always respond to UE-REGISTER-REQUEST
We always should respond to a UE-REGISTER-REQ. Either it's an ACK
or we must send a REJ.  There should not be any "quiet" error cases
where we don't respond at all:

* send general Error Indication at a point where we cannot decode the
  UE-Identity-IE (which is mandatory in UE-REGISTER-REJECT)
* send UE-REGISTER-REJECT with matching cause value whenever we have
  the decoded UE Identity around

Change-Id: I5338a1324545b2c6d31fb45f1e69fee45842e207
2024-03-27 19:07:01 +01:00
Harald Welte
0a991f9a71 HNBAP: Send HNB-REGISTER-REJ on ASN.1 decoding error
Change-Id: Ic4a40966194a57cccc0eb056233f7e7426d6e8f9
2024-03-27 19:05:36 +01:00
Harald Welte
cf75e50314 HNBAP: Use proper cause values during HNB-REGISTER-REQ processing
When we reject the HNB-REGISTER-REQ, let's use an as specific as possible
cause value to let the peer know why we rejected registration.

Change-Id: Iadddd26b751a9fd80c829068792aa93cd538c43d
2024-03-27 19:05:29 +01:00
Harald Welte
5fbd72f80e generalize hnbgw_tx_ue_register_rej_tmsi() to hnbgw_tx_ue_register_rej()
This way the caller can provide the cause value to be used during
reject.

Requires: osmo-iuh I7db92b51847c282d23d568970dfd2bedecdea486
Change-Id: Ic83674523c0326a7ae51fb176bddfd6641ed3ac4
2024-03-27 19:03:26 +01:00
Harald Welte
a3c02651db context_map_{rua,sccp}: Re-order to always process KPIs
Prior to this patch we always decoded CS RANAP, but only decoded PS
RANAP in case PFCP support was enabled.  This meant that PS KPIs
were only counted when PFCP was enabled, too.

Let's move to a mode where we unconditionally decode RANAP and always
call the KPI module for updating the rate counters.

Change-Id: I6054b6efcc202ebd71cd6e135e49c279ba616a01
2024-03-27 18:22:10 +01:00
Neels Janosch Hofmeyr
5329c44e0e fix null deref on hnb_context_release
Usually, a hnb_context still has a hnb_persistent associated at release
time. But that is not guaranteed.

See also further below, where the function tests for ctx->persistent
correctly.

Change-Id: I77ddd627ebfe96c7674c6a197af8b2c4b1a4024c
2024-03-23 12:51:56 +01:00
Harald Welte
6917c9b2cd RAB activation/modification/release statistics
Add rate_ctr based statistics for RAB activation, deactivation and
failures.  This requires us to parse RANAP in both uplink and downlink
and to iterate deep into the setup/modify/release/failure lists.

Given the way how the protocol works, the only way to distinguish an
activation from a modification is because sender and recipient know
whether a given RAB is already active at the time of the message.  So we
also need to track the activation state of each RAB.

Depends: osmo-iuh.git Change-Id I328819c650fc6cefe735093a846277b4f03e6b29
Change-Id: I198fa37699e22380909764de6a0522ac79aa1d39
2024-03-18 13:50:36 +01:00
Harald Welte
ffae71bc14 mgw_fsm: Add some OSMO_ASSERT() to ensure only CS maps passed
The mgw_fsm only supports CS RABs in the CS domain; let's add some
ASSERTs to make sure the impossible doesn't happen.

Change-Id: I264c4b3da17b6f59ebcdd02031318402a483041a
2024-03-13 12:11:01 +01:00
Harald Welte
5a2cf382ef cosmetic: align downlink RANAP unitdata function names with uplink
In uplink we use *ranap_rx_udt_ul*, so let's use the same naming
pattern for processing dowlink unit-data messages to make things more
consistent.  Also, make sure udt is always part of functions that only
handle unitdata - not to be confused with connection-oriented messages.

Change-Id: I1792e4c2cdce145ae906c181898163bcda36328d
2024-03-13 12:11:01 +01:00
Harald Welte
ea15086584 stats: Add per-hnb paging:{cs,ps}:attempted counter
Adding counters for number of paging succeeded is much harder,
as we currently don't parse connection-oriented DL RANAP and/or any
L3 NAS in it.

Change-Id: I7648caa410dba8474d57121a8128579ba200b18f
2024-03-13 12:10:36 +01:00
Harald Welte
14c36591fc Introduce counter for per-hnb cumulative active CS RAB duration
This counter can be used to determine the traffic in Erlangs per HNB.

Change-Id: Iffb6a3f38239094551a12c872cd8474d02a5ad56
2024-03-13 12:04:20 +01:00
Harald Welte
bceef626b5 vty: Print the uptime during 'show hnb' output
Change-Id: Ic414fe301b3fa5c357bd1cd8a7842b7d6333c709
2024-03-12 23:30:19 +00:00
Harald Welte
1cd9402663 don't forward paging requests to HNB's not yet registered
Change-Id: I22ddfea1c6e13e91acecaf731b446e2de0976892
2024-03-12 23:28:50 +00:00
Harald Welte
cae5bf33e8 rename hnbgw_peek_l3 to hnbgw_peek_l3_ul as it's uplink only
Let's not make the functions appear more generic than they are: They
all explicitly only support uplink so far.

Change-Id: I7db0d933a8f17f8c410141f43dab12b8c19fc8ae
2024-03-12 23:28:50 +00:00
Harald Welte
51fb6b5578 cosmetic: Rename hnbgw_rx_ranap and friends to *_rx_ranap_udt_ul
Those functions have always been handling only unit-data in uplink
direction, so let's reflect that in the function name to prevent
anyone assuming they process connection-oriented RANAP and/or
the downlink direction.

Change-Id: I29e8176ac19b2e7390e5950b8d0944c8961e491f
2024-03-12 23:28:50 +00:00
Harald Welte
2b771919dc cosmetic: talk about cs_rab_ass_* when working in CS domain
There's RAB assignment for CS and PS, let's not name functions
too generic to avoid confusion.

Change-Id: Id49ef931e11958315d612d764bbc488ef43f4290
2024-03-12 20:47:59 +00:00
Harald Welte
b43660685d cosmetic: Fix comment in mgw_fsm.c: One RAB per context_map, not hnb!
Change-Id: I0cecccd94ba9c1b14de083265a394a210b758b92
2024-03-12 20:47:55 +00:00
Harald Welte
31178cd2cb stats: Introduce basic counters for RANAP unit-data from CN links
Let's keep track of the various unit-data messages we receive from
CN-link peers: RESET, RESET-ACK, PAGING, UNKNOWN, UNSUPPORTED,
OVERLOAD_IND, ERROR_IND.

Change-Id: Ibe4c73b0288ea20ca3d54519b42bc7cb0e9e61b2
2024-03-12 20:47:50 +00:00
Harald Welte
f43fbc9c40 stats: Introduce CNPOOL counter macro
Ever since the ancient days of OpenBSC, we've always had macros to
abbreviate the cumbersome 'rate_ctr_inc(rate_ctr_group_get_ctr(...))'
construct.  Somehow this was missed here.

Also, since 2018 (libosmocore.git 175a4ae93aaf1068b61041dca12962059d65ed55)
we have rate_ctr_inc2() to make it even simpler...

Change-Id: I5b9e6b2069eed533c472cea8483a370f7fcc8e74
2024-03-12 20:47:40 +00:00
Harald Welte
e874946bfb stats: Introduce CNLINK counter macros
Ever since the ancient days of OpenBSC, we've always had macros to
abbreviate the cumbersome 'rate_ctr_inc(rate_ctr_group_get_ctr(...))'
construct.  Somehow this was missed here.

Also, since 2018 (libosmocore.git 175a4ae93aaf1068b61041dca12962059d65ed55)
we have rate_ctr_inc2() to make it even simpler...

Change-Id: I2754d7aa9d6c3e333bd729bc6e924c502b40cdad
2024-03-12 20:47:35 +00:00
Harald Welte
30691e7b90 Various new per-hnb RANAP and RUA counters
example output while running HNBGW_Tests.ttcn:

OsmoHNBGW# show rate-counters skip-zero
hNodeB 0 (001-01-L2-R3-S4-C1):
          iuh:established:         17 (0/s 4/m 0/h 0/d) Number of times Iuh link was established
        rua:ps:connect:ul:          3 (0/s 0/m 0/h 0/d) Received RUA Connect requests (PS Domain)
        rua:cs:connect:ul:          9 (0/s 4/m 0/h 0/d) Received RUA Connect requests (CS Domain)
     rua:cs:disconnect:ul:          1 (0/s 0/m 0/h 0/d) Received RUA Disconnect requests in uplink (CS Domain)
     rua:ps:disconnect:dl:          2 (0/s 0/m 0/h 0/d) Transmitted RUA Disconnect requests in downlink (PS Domain)
     rua:cs:disconnect:dl:          6 (0/s 3/m 0/h 0/d) Transmitted RUA Disconnect requests in downlink (CS Domain)
rua:ps:direct_transfer:ul:          1 (0/s 0/m 0/h 0/d) Received RUA DirectTransfer in uplink (PS Domain)
rua:cs:direct_transfer:ul:         10 (0/s 5/m 0/h 0/d) Received RUA DirectTransfer in uplink (CS Domain)
rua:ps:direct_transfer:dl:          2 (0/s 0/m 0/h 0/d) Transmitted RUA DirectTransfer in downlink (PS Domain)
rua:cs:direct_transfer:dl:         12 (0/s 6/m 0/h 0/d) Transmitted RUA DirectTransfer in downlink (CS Domain)

Related: SYS#6773
Change-Id: I61bd4f51ec88fd93d8640d39228ac85f5ac5b69b
2024-03-12 21:35:40 +01:00
Harald Welte
e4d0951a86 New per-hnb rate_ctr and stat_item groups; track uptime
Let's add a per-hnb rate_ctr and stat_item group.  Only one initial
counter (number of Iuh establishments) and one initial stat_item
(uptime/downtime) is implemented.

Related: SYS#6773
Change-Id: I26d7c3657cdaf7c6ba5aa10a0677381ab099f8dd
2024-03-12 21:35:40 +01:00
Harald Welte
101a9c7e21 Set persistent->ctx pointer on HNB REGISTER REQ
The previous commit was missing the assignment of hnb_persistent->ctx
during successful HNB REGISTER REQ.

Related: SYS#6773
Fixes: Change-Id Ife89a7a206836bd89334d19d3cf8c92969dd74de
Change-Id: I18fe0e5aa968a1095c72e6bf32d08b031b342ac6
2024-03-12 21:35:39 +01:00
Harald Welte
7ec5cb7280 Introduce concept of per-HNB persistent data structure
This allows us to add a new "hnb-policy closed", which means we do not
accept any random HNB connection anymore, but only those whose identity
is pre-configured explicitly using administrative means.

Furthermore, this new data structure will allow us (in future patches)
to keep persistent data like uptime / downtime of each HNB.

Related: SYS#6773
Change-Id: Ife89a7a206836bd89334d19d3cf8c92969dd74de
2024-03-12 08:18:12 +00:00
Harald Welte
fa4da8f980 Introduce umts_cell_id_from_str() as inverse of umts_cell_id_name()
We are about to introduce the stringified UMTS cell identifier to the
VTY, and for that we need to not only print but also parse the related
string.

Related: SYS#6773
Change-Id: I6da947d1f2316241e44e53bb6aaec4221cfaa2c0
2024-03-07 18:24:09 +01:00
Harald Welte
05605354ef osmo_hnbgw_main: Install our usual SIGUSR1/SIGUSR2/SIGABRT handlers
I just wanted to get a talloc report from osmo-hnbgw by sending SIGUSR1
and the process terminated.  Clearly not the desired behaviour...

Change-Id: I1209a2fadacf62afd5027480426285f527249788
2024-03-07 18:24:09 +01:00
Harald Welte
046ba3d27d umts_cell_id_name: Use 3-digit MCC and 2/3-digit MNC based on VTY config
Printing the PLMN 001-01 as "1-1" like the existing code is just weird,
and also doesn't differentiate between 2-digit and 3-digit MNC in the
output.

Change-Id: I015ad84a6f61b4420f6bfdaa60e8e1b53a71589c
2024-03-07 18:24:09 +01:00
Harald Welte
e01a873cff [cosmetic] re-order hnbgw.c to group code in major blocks
Change-Id: Ia7ce60e6f80d10b7712de1aa6d8a30dd61690dcc
2024-03-07 18:24:08 +01:00
Harald Welte
1ee44400a4 Fix license headers: Should have been AGPLv3+, not GPLv2+
I'm not sure why so many files (particularly written by Philipp)
did contain a GPLv2+ header, instead of the AGPLv3+ which is the
actual overall project license.  I consider it a mistake.

In any case, any copyrightable contribution to those files was done by
sysmocom employees, so I as managing directory can legally make a
license change, whther or not it was a mistake early on or not.

Change-Id: I42ea544ae4e5c4d3bedad12ddb55cf3c26a30919
2024-02-17 10:28:41 +01:00
Neels Janosch Hofmeyr
eba9cbf9d1 pfcp: implement sending Network Instance IEs
Allow configuring specific Network Instance names for the Core and
Access sides, to send to the UPF, to allow the UPF to pick the proper
network interface to create GTP tunnels on.

Add VTY cfg 'hnbgw' / 'pfcp' / 'netinst (access|core) NAME' to allow
configuring Network Interface values to send in PFCP. These are "dotted"
domain name strings, as in APN.

Add these Network Interface names to the PFCP messages' detection as
well as forwarding rules, each one indicating the side that it is
detecting on or forwarding to.

This helps lift osmo-hnbgw's PFCP support out of lab situations to a
proper production scenario, where the core and access networks are in
separate subnets, with osmo-hnbgw + UPF as the gateway.

For example, in osmo-hnbgw, configure

  hnbgw
   pfcp
    netinst access my-ran
    netinst core my-core

and in osmo-upf, configure

  netinst
   add my-ran 10.9.8.7
   add my-core 1.2.3.4

In effect, all GTP tunnel endpoints towards the Access side will be
bound on 10.9.8.7, and all GTP tunnel endpoints towards the Core side
will be bound on 1.2.3.4.

Related: SYS#5895
Change-Id: Ief53dbfacf1645c32a07847d590c4884d4c8ca56
2024-01-21 02:48:38 +01:00
Neels Janosch Hofmeyr
5fd2aa5dc4 pfcp: fix missing vty_write of pfcp local-port
Change-Id: I180e67b8abdd3c0d91b30f1e7668b02f1a323809
2024-01-21 02:29:45 +01:00
Neels Janosch Hofmeyr
4ec1c77f9c add tests/pfcp_cfg.vty.with_pfcp
So far, we had no pfcp.vty test, because PFCP support is built
conditionally, and the pfcp.vty test would always fail without PFCP
support.

Add a pfcp test for the VTY, conditionally: use suffix .vty.with_pfcp to
identify tests that need PFCP support, and run those only when
configured with --enable-pfcp.

Related: SYS#5895
Change-Id: Ibb1797bb43a18f26fc693e0c8920cfd1f5fb9ede
2024-01-21 02:29:31 +01:00
Neels Janosch Hofmeyr
ec10455aed tweak vty doc: "UDP port"
Change-Id: I24e52143a8f3d0c9f92985f6bb0420b34926eb33
2024-01-20 08:34:54 +01:00
Neels Janosch Hofmeyr
6582c9a06c pfcp: fix modification of wrong FAR ID
Do not update the Core-facing Forwarding Action Rule with the Access
side's remote TEID, update the Access-facing FAR as we should.

This is a seemingly small but very grave bug in osmo-hnbgw's PFCP
implementation, and proof that no-one anywhere has tested osmo-hnbgw's
PFCP support properly yet.

Related: SYS#5895
Change-Id: I596f1785d280d7e53e0cef649d6bb5df01ebf648
2024-01-20 08:34:54 +01:00
Oliver Smith
1237ae48d1 debian: fix having no manuals in osmo-hnbgw-doc
Fixes: OS#6326
Change-Id: I8daeba6b267b2f7793a26a0e1fbc74ab17401cde
2024-01-19 14:40:49 +01:00
Oliver Smith
770d62f91b gitignore: add *.la, hnbgw_vty_reference.xml
Change-Id: I5c9ec6cf01d75b10a86098755064ad187de815c1
2024-01-19 14:40:42 +01:00
Harald Welte
67f9de4148 Display RANAP state during 'show cnlink'
This adds a line to the output showing DISCONNECTED or CONNNECTED
state for each cnlink during the 'show cnlink' VTY command.

Closes: SYS#6741
Change-Id: I6ab7d08fcd0631d31a12418f950c5901a93db43a
2024-01-15 13:56:38 +01:00
Neels Janosch Hofmeyr
406d95139b rua: move from ERROR to DEBUG log: stray RUA Disconnect
Related: SYS#6602
Change-Id: Ib55c254190f46f24981e4394d8d5cf017070118d
2024-01-05 05:12:03 +01:00
Neels Janosch Hofmeyr
c17b2850b7 rua: validate correct RUA ctx state per RUA message type
It helps in a pretty complex situation seen in the field.
A third-party MSC releases SCCP in one fell swoop, not waiting for the
Iu Release Complete to come back from RAN as the specs would suggest.

The result is this odd sequence, where late rx of RUA Disconnect
actually causes a new SCCP connection to be established and torn down
again:

    RAN                 HNBGW                MSC
     |--active-RUA-ctx----|--active-sccp------|
     |                    |<--IU-Release-Cmd--|
     |<--IU-Release-Cmd---|                   |
     |                    |<--SCCP-RLSD-------| (too soon)
     |...<-RUA-Disconnect-|                   x (the consequence)
     |                    x
     |--RUA-Disconnect--->|                     (IU Release Complete)
     x              <create-new-ctx>
                          |-SCCP-CR---------->|
                          |-IU-Release-Compl->|
                          |<--CREF------------|
                          x                   x

This patch is a relatively simple practical improvement of above
situation that is logically obvious:

Validate the correct message type for creating a new RUA-to-SCCP
context: RUA Connect.

That means the IU Release Complete is ignored:

    RAN                 HNBGW                MSC
     |--active-RUA-ctx----|--active-sccp------|
     |                    |<--IU-Release-Cmd--|
     |<--IU-Release-Cmd---|                   |
     |                    |<--SCCP-RLSD-------| (too soon)
     |...<-RUA-Disconnect-|                   x (the consequence)
     |                    x
     |--RUA-Disconnect--->|                     (IU Release Complete)
     x                 <error>
                          x

Related: SYS#6602
Change-Id: Ie359fcada98fb19f56015cf462e6d8c039f5fce5
2023-12-12 02:02:08 +01:00
Pau Espin Pedrol
5755268f9b mgw_fsm: Modify RAB on HNB if IuUP local IP addr at MGW changes during MDCX
Allow IP address renegotation between HNB and MGW:
* Upon MGCP MDCX ACK received from the RAN-side conn, if the IP address/port
  changes, then restart the RAB-Ass-Req+Resp procedure on Iuh.
* Upon RAB-Ass-Resp received from the HNB, if the IP address/port changes,
  then go through another MDCX + MDCX ACK prcoedure on MGCP.

An MDCX counter is introduced to avoid infinite loops where the HNB and
the MGW keep changing their IP address triggered by the change on the
other side, eg. due to incorrect network/routing setup.
The counter is also used to track count in order to make sure that
always at least 1 MDCX is transmitted, in order to change conn_mode to
SEND_RECV.

Related: OS#6127
Change-Id: I936a50fed38a201c4a8da99b40f07082049e5157
2023-12-04 13:13:49 +01:00
Pau Espin Pedrol
656d1d2778 mgw_fsm: Assume IuUP HNB IP addr = Iuh during MGCP CRCX on RAN side
In general, the HNBs end up binding its IuUP/RTP streams to the same IP
address used for Iuh signalling.

When receiving a RAB-Ass-Req from CN, osmo-hnbgw creates an endpoint in
its co-located MGW and creates 2 MGCP conns on it, one facing the RAN
side and one facing the CN side. At that point, the remote CN IuUP IP address
is known (was include din the received RAB-Ass-Req), but the RAN one is
not yet known. Hence, the CRCX on the RAN-side conn contained no remote
IP address, which means MGW has to blindly guess a good local IP address
if "ip probing" is enabled.
As a result, when RAB-Ass-Resp comes back from the hNodeB containing the
allocated remote IuUP IP address and MGW is updated through MGCP MDCX,
it may happen that MGW rebinds to a better fitting local IP address and
returns it in MDCX. This has several downfalls:
1- We don't yet support updating the hNodeB about the changed IP address
(see mgw_fsm_mdcx_hnb). For that, we'd need to send a RAB-Modify-Req+Resp.
2- Even if we supported it, in the general case we'd be delaying call
establishment because an extra roundtrip is needed to update
RAB-Modify-Req+Resp.

In general, the HNBs end up binding its IuUP/RTP streams to the same IP
address used for the Iuh signalling. Hence, use this assumption to
announce the Iuh IP address as a remote IuUP IP address when
transmitting the MGCP CRCX at the RAN-side conn to the MGW. This way
the MGW can potentially select a proper local IuUP IP address from the
start, so no RAB-Modify-Req is required later on.

The logic to transmit RAB-Modify-Req is left unimplemented in this
commit and is left as a later improvement.

Related: SYS#6657
Change-Id: Icf43e3a1cde809d844f17ef2d0600efe64bc3dfe
2023-12-04 12:07:05 +00:00
Neels Janosch Hofmeyr
90928fb246 systemd,manual: set LimitNOFILE=65536
A typical OS imposed limit is 1024 open FD, which is too low when there
are thousands of HNB.

In systemd service file, set a super high limit of 65536.

In osmo-hnbgw's user manual, add section 'Configure limits' describing
this in detail.

Related: OS#6256
Related: osmo-bsc I26c4058484b11ff1d035a919bf88824c3af14e71
Change-Id: I5333765199cf9e3e5a570f85b85d2b7423d34a4d
2023-12-03 02:21:00 +00:00
Andreas Eversberg
fadc67b8fb Use uniform log format for default config files
Related: OS#6272
Change-Id: I10bfc4150e10cac048f320641c040fa300f734c4
2023-12-01 11:53:41 +01:00
Pau Espin Pedrol
cd09569bc8 tests/ranap_rab_ass: Test RAB-Ass.req with X.213 IPv4 address len=7
It was found in the field that some peers sends an X.213 IP address
consisting of 7 bytes (1byte IDP/AFI, 2byte ICP, 4 byte IPv4 address) insetad
of 20 bytes. This was until recently failing in osmo-hnbgw due to
missing decoding functionalitit in osmo-iuh.git. Cover it here.

Related: SYS#6623
Depends: osmo-iuh.git I507fb1605d976bd8573162e4fa81721245330184
Change-Id: I71323018d79a4f5778dc6e49488d75ae7c2c4cdc
2023-11-27 14:51:13 +00:00
Neels Janosch Hofmeyr
3abd523faa allow (second) CS RAB Assignment Request without RTP info
In the field, we've encountered a so far unsupported scenario: after a
RAB Assignment Request and Response have successfully set up an RTP
stream, the CN sends *another* RAB Assignment Request to modify some SDU
parameters, omitting the transportLayerInformation IE (not modifying the
RTP address).

Directly forward all RAB Assignment Requests that have no RTP
information. This covers above use case, and also generally applies: no
RTP info means nothing to tell the MGW about.

Do not (yet?) support modifying the RTP address/port.

Related: osmo-ttcn3-hacks Iadaba0e5e82ad6d163ad509904ede213e2462d5c
Related: SYS#6624
Change-Id: I25bf19981cd75a87a7ceb3382dae1ec626ae475c
2023-11-07 04:34:35 +01:00
Neels Janosch Hofmeyr
81105a4d7a X31: fix vty doc
The actual scope is larger than just a stale release. Every RUA and SCCP
context map FSM state that has a timeout uses X31. (Seems I intended to
add more timers but forgot to follow up when I wrote it.)

Related: SYS#6602
Change-Id: I75a25f9065bc651e7ba8feda6db03c49a3b75c5e
2023-11-03 19:42:40 +00:00
Pau Espin Pedrol
0e987d7f3c context_map_sccp: Fix assert hit due to missing ev handling
This was being triggered recently by
HNBGW_Tests.TC_ranap_cs_mo_disconnect in osmo-ttcn3-hacks.git.

Change-Id: Idaad11eaa3c2e56de792f80bab1f1d8435ef9b68
2023-11-02 12:09:06 +01:00
Pau Espin Pedrol
b64afa8631 Increase default X31 val from 5 to 15 seconds
It was spotted that in some deploys with satellite links and heavy load,
the roundtrip times for CR and CC can go slightly over 12 seconds, so
better increase the default value to 15 seconds to be on a safer place
and avoid operation problems with default configuration.

Related: SYS#6602, SYS#6616
Change-Id: I24225cfc0addf326c239ec658a27b93b83a3e751
2023-10-31 12:46:22 +01:00
Pau Espin Pedrol
8514b73bff hnbgw_cn: Remove assert hit due to wrong assumption
The HNBGW in correctly assumes that no ss7->sccp instance is allocated
until the same function calls osmo_sccp_simple_client_on_ss7_id().
This assumption is wrong, since ss7 may create its own ss7->sccp
instance internally as a result of vty configuration, eg. when "sccp
max-optional-data 124" is placed in osmo-hnbgw.cfg file.

In this scenario, simply removing the assert is enough, since
osmo_sccp_simple_client_on_ss7_id() just calls osmo_ss7_ensure_sccp(),
the same that the libmoso-sccp code called to allocate the pointer.

Related: SYS#6566
Fixes: f3caea850b
Change-Id: I1221c165156e9625324cf0080836a8ed2bad4e9c
2023-09-19 12:36:38 +02:00
Pau Espin Pedrol
92340d2131 Bump version: 1.4.0.109-89fe-dirty → 1.5.0
Change-Id: I7816b6554ce733207302c373d745b52146e0a995
2023-09-12 17:18:44 +02:00
Pau Espin Pedrol
89fe80525b Tear down call if local IuUP MGW address changed during MDCX
Due to ip probing used at MGW and specific/complex routing in place
between RAN and CN, it may happen that, on the RAN-side endpoint connection,
the MGW first selects a local IP address.

Then, after the HNBGW signalling it to the HNB with the patched RabAssReq
and receiving the HNB local IP address through RabAssResp, then applying
it to MGW through MDCX, it can happen that the MDCX ACK contains a changed
local IP address at the MGW, due to IP probing after learning the remote
address.

This change is so far not announced to the HNB in anyway, ending up in the
HNB sending IuUP packets to the wrong IP/port, which is rejected with ICMP
due to osmo-mgw having no socket open there anymore.

We should at least, in osmo-hnbgw, if detecting the local MGW IP address
changed during MDCX, tear down the call setup. This patch does precisely
that.

Related: OS#6127
Change-Id: I32c4a7f838ceb5077bec7945e7976ce455d6b025
2023-08-01 19:21:38 +02:00
Philipp Maier
267a92a580 ranap_rab_ass: be sure to initialize memory with 0
When we use the ASN.1 decoder functions, we often reserve some memory on
the stack to store the results. (e.g. RANAP_RAB_xy_t _RANAP_RAB_xy;).
Then we assign the memory location to a pointer variable (e.g.
RANAP_RAB_xy = &_RANAP_RAB_xy). We do this for cosmetic reasons but we
may end up with an uninitialized buffer, which may cause trouble
lateron. Let's be consistent and make sure that those buffers are
initialized with zeros.

Change-Id: I7a8a951ccd8c9ae3923261468c0755192894a84b
2023-06-23 13:11:05 +00:00
Philipp Maier
b77d944961 ranap_rab_ass: do not free never allocated FieldItems
When we check for an assignment failure in a RAB AssignmnetResponse, we
use decode_rab_flitms_from_resp_ies to search through the list of RAB
FailedItemsIEs for the RAB id we want to check. In case of failure, we
get a positive index back from this function. We then know that the RAB
that we checked for has failed bcause it was in the FailedItemsList.

In that case, we also must free the RAB FailedItemsIEs that
decode_rab_flitms_from_resp_ies has decoded. At the moment we also free
the RAB FailedItemsIEs also when decode_rab_flitms_from_resp_ies returns
with a negative return code, which may mean that the RAB was not found
or some other error occured. In this case we must not free since then
no valid RAB FailedItemsIEs are allocated.

Related: OS#6062
Change-Id: I755ba6599079810a048bf5b6d8947b5a9ffa622d
2023-06-23 13:11:05 +00:00
Neels Janosch Hofmeyr
067f1a0948 coverity: hnbgw_cn: avoid NULL deref in LOGP
Related: CID#321281
Change-Id: Icc2b45c8df8d92cfb42582615313d83389cf1621
2023-06-22 16:28:13 +02:00
Neels Janosch Hofmeyr
3a084af8f7 RUA: log tweak
I noticed that the log is awfully silent about disconnecting RUA, even
with DEBUG turned on. It is an important item that should show in the
logs.

Change-Id: I87592cbf197d5d3d2a22b04d9f6b64422af65ded
2023-06-21 04:16:13 +02:00
Neels Janosch Hofmeyr
d58d15636f include Global RNC-ID in RESET-ACK
According to 3GPP TS 25.413 8.26.2.1, "The RNC shall include the Global
RNC-ID IE in the RESET ACKNOWLEDGE message."

Related: SYS#6441
Change-Id: I49d351e90dfe4a7c4dfdd26542565f2d9bd3d605
2023-06-21 04:16:13 +02:00
Neels Janosch Hofmeyr
7867adc490 include Global RNC-ID in RESET
According to 3GPP TS 25.413 8.26.2.2, "The RNC shall include the Global
RNC-ID IE in the RESET message."

Related: SYS#6441
Change-Id: I2cd5d7ea86a6ed0916befe219dbf21373afbd95b
2023-06-21 04:16:13 +02:00
Neels Janosch Hofmeyr
d482f8666c cfg: add 'hnbgw' / 'plmn MCC MNC'
According to 3GPP TS 25.413 8.26.2.2, "The RNC shall include the Global
RNC-ID IE in the RESET message." To be able to do that, osmo-hnbgw needs
to know the local PLMN.

Introduce explicit knowledge of the local PLMN by config, and use this
configured local PLMN in places where the local PLMN was so far derived
otherwise.

Subsequent patches will separately add the RNC-ID to the RANAP RESET
messages.

Since the PLMN is required to send correct RESET and RESET ACK, include
the new 'plmn' config item in all example configurations.

Related: SYS#6441
Change-Id: If404c3859acdfba274c719c7cb7d16f97d831a2a
2023-06-21 04:16:13 +02:00
Neels Janosch Hofmeyr
1b45792cbd cnpool: return Paging Resp to the exact CN link that Paged
Implement Paging record, remembering which CN link paged for which
mobile identity, for a set time period roundabout 15 seconds.

When receiving a Paging Response from a UE, connect it to the CN link
that sent the Paging.

Related: SYS#6412
Change-Id: I907dbcaeb442ca5630146f8cad40601c448fc40e
2023-06-21 04:16:13 +02:00
Neels Janosch Hofmeyr
5aeacc4500 use cnlink state in cnpool decisions
Change-Id: I28490a4a27bcda8fd689db8b8652e451103e3a9d
2023-06-21 04:16:13 +02:00
Neels Janosch Hofmeyr
e14d3bd6fe detect in/active CN links by RANAP RESET
Implement cnlink FSM. This is a copy of osmo-bsc/bssmap_reset.c, except
for the connection success/failure counting.

The reason to exclude the CONN_CFM_FAILURE/_SUCCESS: it is a rather
experimental feature and should probably rather be covered by PCSTATE
notifications and protocol layer timeout tuning (e.g. SCCP ia timers).

Related: SYS#6412
Change-Id: Id3eefdea889a736fd5957b80280fa45b9547b792
2023-06-21 04:16:13 +02:00
Neels Janosch Hofmeyr
fb627de4bb add ranap_domain_name()
As simple as it may seem, turning the RANAP CS/PS domain indicator to a
string so far is cumbersome. It is usually only CS or PS, but to be
accurate we should also expect invalid values. After the umpteenth
switch statement breaking up otherwise trivial code, I decided that yes,
after all, a value_string[] is a good idea even for just two enum values
that have only two-letter names.

Callers will follow in upcoming patches.

Related: SYS#6412
Change-Id: Ib3c5d772ccea8c854eec007de5c996d1b6640bc0
2023-06-21 04:16:13 +02:00
Neels Janosch Hofmeyr
b1c0bb19e2 cnpool: add context_map_cnlink_lost() handling
When proper RANAP RESET handling is in place [1], a specific CN link may
be detected to be disconnected or reconnected at any point.
[1] Id3eefdea889a736fd5957b80280fa45b9547b792

When that happens, all related context maps should disconnect RUA and
SCCP links immediately. Add the context map side of this, so that
context_map_cnlink_lost() is ready to be dispatched in [1].

Related: SYS#6412
Change-Id: Ic0a6fcfb747dc093528ca2bd12a269ad390d465c
2023-06-21 04:16:10 +02:00
Neels Janosch Hofmeyr
72f0884ee0 ctrl test: also test msc 1
Change-Id: I957dca863b6cdc8b077f2ec2b2894cebe7613736
2023-06-16 04:25:31 +02:00
Neels Janosch Hofmeyr
11fa15e4d9 tweak lots of logging
Make lots of small tweaks in logging around RUA to SCCP context maps, CN
link selection and HNBAP:

- remove duplicate log context (e.g. LOGHNB() already shows the HNB Id)
- bring more sense into logging categories and levels / reduce noise
- add and clarify the details being logged at what points in time

Related: SYS#6412
Change-Id: I41275d8c3e272177976a9302795884666c35cd06
2023-06-16 04:25:31 +02:00
Neels Janosch Hofmeyr
15e79a2b7a make public: umts_cell_id_name()
Upcoming logging tweaks will call this from hnbgw_hnbap.c as well.
See I41275d8c3e272177976a9302795884666c35cd06

Related: SYS#6412
Change-Id: I499d18876685062d066a9a66d2def827efb77640
2023-06-16 04:07:24 +02:00
Neels Janosch Hofmeyr
afb6298727 fix umts_cell_id_name(): show CID
The Cell ID is an important part to distinguish HNB in logging, add it
to the output string.

Use OTC_SELECT buffer instead of static buffer.

Related: SYS#6412
Change-Id: Id903b8579ded8b908e644808e440d373bcca8da4
2023-06-16 04:07:24 +02:00
Neels Janosch Hofmeyr
6563e11731 doc/examples/osmo-hnbgw/osmo-hnbgw-cnpool.cfg
Change-Id: I932eca7489f29eab23d180cd6d1216d26c62dd30
2023-06-16 04:07:24 +02:00
Neels Janosch Hofmeyr
36b5a74658 cnpool: select CN link from pool by NRI or round robin
This is the bulk of the CN pooling logic but is not working properly
without these future patches:

- detect in/active CN links by RANAP RESET
  Id3eefdea889a736fd5957b80280fa45b9547b792

- return Paging Resp to the exact CN link that Paged
  I907dbcaeb442ca5630146f8cad40601c448fc40e

Change-Id: I66fba27cfbe6e2b27ee3443718846ecfbbd8a974
2023-06-16 04:07:24 +02:00
Neels Janosch Hofmeyr
685c276ce1 add CTRL transcript tests for cnpool rate ctrs
Related: SYS#6412
Change-Id: I5a9a23c10e3ac85dabbb1d367351bb7c5dfe2526
2023-06-16 04:07:24 +02:00
Neels Janosch Hofmeyr
e513ffb983 add rate_ctr infra; add rate_ctrs for cnpool
Introduce rate counter stats to osmo-hnbgw.

Add the first rate counters -- they will be fed in upcoming commit
"cnpool: select CN link from pool by NRI or round robin"
I66fba27cfbe6e2b27ee3443718846ecfbbd8a974

Related: SYS#6412
Change-Id: I0132d053223a38e5756cede74106019c47ddcd94
2023-06-16 04:07:24 +02:00
Neels Janosch Hofmeyr
56323378db cnpool: extract Mobile Identity from RANAP payload
For InitialUE messages, decode the RANAP and NAS PDU to obtain the
Mobile Identity. Store it in map->l3.

A following patch will use this mobile identity to decide on which CN
links to pick from the CN pools for this subscriber.

Add the new information to LOG_MAP() as ubiquitous logging context.

Depends: libosmocore Ic5e0406d9e20b0d4e1372fa30ba11a1e69f5cc94
Related: SYS#6412
Change-Id: I373d665c9684b607207f68094188eab63209db51
2023-06-16 04:07:24 +02:00
Neels Janosch Hofmeyr
a0321315e1 startup config tests: show default 'msc 0', 'sgsn 0'
Even when the config file did not configure any, the 'show
running-config' shows the default 'msc 0' and 'sgsn 0' that were
created. Let's also show that in the vty scripts.

Change-Id: Ia4e43fa9b5d9fda1ae374d3d31965aafe85d493d
2023-06-16 04:05:45 +02:00
Pau Espin Pedrol
ee1a647547 Use new mgcp_client_conf_alloc() API to alloc mgcp_client_conf
Depends: osmo-mgw.git Change-Id Iba0853ed099a32cf1dde78c17e1b34343db41cfc
Change-Id: I0f142e7af180136e8dac142672d38d75a76ef123
2023-06-14 14:08:44 +02:00
Pau Espin Pedrol
d6b0217c33 tests: Update *.vty after libosmo-sccp VTY improvements
After recent change, libosmo-sccp is always printing the "role" config.

Depends: libosmo-sccp.git Change-Id Ie81987265118e48173211f88b27a006115dc62d4
Change-Id: I316cd26ceb97f2ccfd1065d65177e7d3926625b0
2023-06-08 19:31:42 +02:00
Neels Janosch Hofmeyr
d5fe76dec8 fixup for 'cnpool: split up context_map_find_...'
Fix: missing allocation of SCCP FSM instance.

When submitting patch [1], i failed to notice that a small but important
fix, already present later on my branch, was needed for this patch to
work.

[1]
'cnpool: split up context_map_find_or_create_by_rua_ctx_id()'
Ifc5242547260154eb5aecd3a9d9c2aac8419e8db
c6393a1f80

Change-Id: I447f989f1d58b666bf9f1ab49ce6ba4fc5cdda80
2023-06-07 03:41:38 +02:00
Neels Janosch Hofmeyr
83dce9f7da add hnbgw_decode_ranap_co()
An upcoming patch will re-use this pattern of decoding RANAP and
attaching a talloc destructor to the result for the third time. The
caller will be in hnbgw_rua.c, so make this a "public" function.

Change-Id: I3695babfb083ed1f5fff700dcb7646fa95496a52
2023-06-02 17:11:25 +02:00
Neels Janosch Hofmeyr
c957f4efd5 tdefs; combine timer groups 'ps' and 'cmap' to 'hnbgw'
Makes no sense to have groups with one member each.
Soon I will add a new timer that matches neither 'cmap' nor 'ps', and I
would have to open a third group with a single member.

Add a shim that redirects 'ps' to 'hnbgw'.
The group 'cmap' has not yet been released, so just drop that one
without a shim.

Change-Id: Ica6063fae4588b51897e5574d975b3185fa9ae80
2023-06-02 17:11:25 +02:00
Neels Janosch Hofmeyr
9ee468f5b2 cnpool: make NRI mappings VTY configurable
Implement only the VTY configuration part, applying NRI to select CN
links follows in I66fba27cfbe6e2b27ee3443718846ecfbbd8a974.

Use the osmo_nri_ranges API to manage each cnlink's NRI ranges by VTY
configuration.

Analogous to osmo-bsc
 MSC pooling: make NRI mappings VTY configurable
 4099f1db504c401e3d7211d9761b034d62d15f7c
 I6c251f2744d7be26fc4ad74adefc96a6a3fe08b0
plus
 vty: fix doc: default value for 'nri bitlen'
 f15d236e3eee8e7cbdc184d401f33b062d1b1aac
 I8af840a4589f47eaca6bf10e37e0859f9ae53dfa

Related: SYS#6412
Change-Id: Ifb87e01e5971962e5cfe5e127871af4a67806de1
2023-06-02 17:11:25 +02:00
Neels Janosch Hofmeyr
a49b265226 cnpool: examples and config tests: use 'msc 0'/'sgsn 0' syntax
After recent introduction of multiple 'msc' and 'sgsn' nodes in the
VTY config, switch cfg files to the new syntax:
- in doc/examples
- for 'make config-tests'
  - have one test in old config syntax to test backwards compat:
    'legacy', an exact copy of 'one_cs7_with_addrs'.

Related: SYS#6412
Change-Id: If999b71a8a8237699f6ccfcaa31d1885e66c0518
2023-06-02 17:11:25 +02:00
Neels Janosch Hofmeyr
9901316371 cnpool: add multiple 'msc' and 'sgsn' cfg (use only the first)
Allow configuring multiple MSCs and SGSNs. Show this in new transcript
test cnpool.vty and in sccp.dot chart.

Still use only the first CN link; actual CN link selection from the pool
follows in I66fba27cfbe6e2b27ee3443718846ecfbbd8a974.

Config examples and VTY tests cfg files will be adjusted to the new cfg
syntax in patch If999b71a8a8237699f6ccfcaa31d1885e66c0518. Only the VTY
write() changes are visible here, to pass all transcript tests.

Related: SYS#6412
Change-Id: I5479eded786ec26062d49403a8be12967f113cdb
2023-06-02 17:11:22 +02:00
Neels Janosch Hofmeyr
bd5901d3f9 cnpool prep: add SCCP_EV_USER_ABORT
To ease patch review, I decided to submit this separately from the
caller, which follows in subsequent patch
I5479eded786ec26062d49403a8be12967f113cdb

The new event will be dispatched when there are SCCP config changes
pending, and the human user types 'apply sccp' on the telnet VTY. The
aim is to disconnect all connections so we can create a new SCCP User.

Related: SYS#6412
Change-Id: Idff8e09b5328c904b175e4d4df7d4044a34f4a20
2023-06-02 16:46:12 +02:00
Neels Janosch Hofmeyr
c6393a1f80 cnpool: split up context_map_find_or_create_by_rua_ctx_id()
Have separate find, alloc, set_cnlink functions instead of all in one.

For CN pooling, I need these operations to be separate:
- we need to decode the RANAP and L3 PDU only if no existing context_map
  was found (see I373d665c9684b607207f68094188eab63209db51 ).
- while decoding RANAP and L3 PDU, and while deciding which cnlink to
  use, I already want to use the logging context of LOG_MAP(). So I want
  to allocate a map first, and connect it to the selected cnlink later.

Related: SYS#6412
Change-Id: Ifc5242547260154eb5aecd3a9d9c2aac8419e8db
2023-06-02 16:46:10 +02:00
Neels Janosch Hofmeyr
2ce280f232 coverity: fix type of local var
osmo_sccp_instance_next_conn_id() may return negative on error, so use
int instead of uint.

Fixup for recent 548d3d2f66
'use new osmo_sccp_instance_next_conn_id()'

Related: CID#316694
Change-Id: I2623c06c23691acb75f6ee6ff3a42ac7d10a4b1f
2023-06-02 16:44:10 +02:00
Neels Janosch Hofmeyr
f3caea850b cnpool: allow separate cs7 for IuPS and IuCS
Prepare for CN pooling; allow using separate SS7 instances for IuCS and
IuPS.

New struct hnbgw_sccp_user describes a RANAP SCCP user, one per cs7.
Limit struct hnbgw_cnlink to describing a CN peer, using whichever SCCP
instance that is indicated by hnbgw_sccp_user.

Chart sccp.dot shows the changes made.

Related: SYS#6412
Change-Id: Iea1824f1c586723d989c80a909bae16bd2866e08
2023-06-01 01:41:35 +02:00
Neels Janosch Hofmeyr
e96b52b158 charts: add sccp.dot
Show current use of SCCP and SS7 by osmo-hnbgw, which is about to
change (along with this chart).

Related: SYS#6422
Change-Id: I109948758de998326a5e9f0dcdc84d5f11dfba02
2023-05-31 04:45:13 +02:00
Neels Janosch Hofmeyr
28cb20e1be add startup config tests
Get a handle on how osmo-hnbgw currently auto-configures itself, by
running vty tests with a couple of config files that have various levels
of omitted config items that are to be auto-configured.

These tests serve to ensure continuity and compatibility by upcoming
cnpool patches. They will show how the configuration changes / doesn't
change in all the right ways.

Uncover one bug in osmo-hnbgw: local point-code setting is discarded by
osmo-hnbgw startup when there are no SCCP address book entries in use.
The error is fixed as side effect of an upcoming patch:
Iea1824f1c586723d989c80a909bae16bd2866e08

Related: SYS#6412
Change-Id: Ic8bb30e1dd73753c2ff255566382e241918414f7
2023-05-31 03:44:15 +02:00
Neels Janosch Hofmeyr
7e5ea9d40e add separate cfg file only for VTY tests
Instead of doc/examples/osmo-hnbgw.cfg, use a separate location
tests/osmo-hnbgw-vty-test.cfg for VTY transcript tests, and add a bunch
of SCCP address book entries that upcoming VTY tests will use.

Declaring address book entries within transcript tests is possible, but
cluttered with node 'exit' and VTY prompts -- these addresses are much
easier to read and maintain in form of a cfg file.

First user of these address book entries will be cnpool.vty in
I5479eded786ec26062d49403a8be12967f113cdb

Related: SYS#6412
Change-Id: Idcdf0e8660e8180fbe02282bc94e623ff89e848a
2023-05-26 23:23:21 +02:00
Neels Janosch Hofmeyr
ab9d5364cb add doc/examples/osmo-hnbgw/osmo-hnbgw-cs7.cfg
We have not a single example yet showing point code and SCCP address
configuration. Add this example, which will also show how the config
file syntax changes while introducing CN pooling in upcoming patches.

Related: SYS#6412
Change-Id: I42c3b434a7339cc3efb27b43c893cfb734de9ca4
2023-05-26 23:23:21 +02:00
Neels Janosch Hofmeyr
548d3d2f66 use new osmo_sccp_instance_next_conn_id()
Change-Id: I0fc6e486ce5d68b06d0bc9869292f082ec7a67f0
2023-05-26 23:23:21 +02:00
Neels Janosch Hofmeyr
aa99546d23 immediately SCCP RLSD on HNB re-register
The RUA side may disconnect
a) gracefully (RUA Disconnect with RANAP IU Release Complete) or
b) by detecting that the HNB is gone / has restarted

For a), the SCCP side waits for a RLSD from the CN that follows the IU
Release Complete.
For b), we so far also wait for a RLSD from CN, which will never come,
and only after a timeout send an SCCP RLSD to the CN.

Instead, for b), immediately send an SCCP RLSD.

To distinguish between the graceful release (a) and the disruptive
release (b), add new state MAP_RUA_ST_DISRUPTED on the RUA side,
and add new event MAP_SCCP_EV_RAN_LINK_LOST for the SCCP side.

Any non-graceful disconnect of RUA enters ST_DISRUPTED.
disrupted_onenter() dispatches EV_RAN_LINK_LOST to SCCP,
and SCCP directly sends RLSD to the CN without timeout.

These changes are also shown in doc/charts/hnbgw_context_map.msc.

Change-Id: I4e78617ad39bb73fe92097c8a1a8069da6a7f6a1
2023-05-26 23:23:21 +02:00
Neels Janosch Hofmeyr
e966e73fd2 vty_go_parent(): remove legacy cruft
We do not need to restore the parent node on vty_go_parent() anymore,
only call the ss7 go-parent callback to trigger actions on vty node
exit.

Change-Id: Id3ecc53885436294d2217c52130910872dde550e
2023-05-26 23:23:21 +02:00
Neels Janosch Hofmeyr
164f3a67f1 unbloat: drop context_map_check_released()
When I implemented it, I thought context_map_check_released() would help
clarify context map deallocation, but instead it just bloats. Simplify
and tweak related comment.

Change-Id: I535780de0d3b09893ba89d66804e5e36b26db049
2023-05-26 23:23:21 +02:00
Neels Janosch Hofmeyr
b42428b473 remove obsolete context_map_check_released() call
When in map_rua_fsm_cleanup(), we are always already deallocating the
context map, i.e. neither RUA nor SCCP are active anymore. No need to
call context_map_check_released() again.

Change-Id: I4bf9a9de7861296ec38c3a37de0254ad9a41ef1b
2023-05-26 23:23:21 +02:00
Neels Janosch Hofmeyr
ade986280e drop dead code: cnlink.T_RafC
This looks like working RANAP RESET handling, but is in fact dead code.
Instead of testing and completing this implementation, I will copy the
RESET handling FSM from osmo-bsc, which is proven to work well.

See Id3eefdea889a736fd5957b80280fa45b9547b792
"detect in/active CN links by RANAP RESET"

The future patch will start to use transmit_rst(). So instead of
dropping now and resurrecting later, let's keep the code -- but it has
to be '#if 0'-ed to avoid compiler complaints about the unused function.

Related: SYS#6412
Change-Id: I7cacaec631051cf5420202f2f0dd9665a5565b17
2023-05-26 23:23:21 +02:00
Oliver Smith
725c588f1a systemd: depend on networking-online.target
Related: SYS#6400
Change-Id: I29e547242b2ed1cfc4750c7d7e5f8636c2e8f3dc
2023-05-26 15:31:30 +00:00
Pau Espin Pedrol
8111b89fd9 manuals: include osmo-gsm-manual's sigtran.adoc
Prepend it to the cs7 config .adoc, so that it introduces some concepts
and specifies some limitations of current implementation, etc.

Change-Id: I9faef2dd15b62814e474299c675c2cb05b779c44
2023-05-26 14:03:48 +02:00
Vadim Yanitskiy
866386d9c3 copyright: fix typo: sysmocom s/s.m.f.c./s.f.m.c./ GmbH
Change-Id: I2f8069c17d75a2c456485fc1ed22fec130c968d0
2023-05-19 08:49:02 +00:00
Neels Janosch Hofmeyr
07c8b72455 fix missing write-back of rnc-id
Change-Id: Ide006379b26949e34371bd316a22c130ea09da82
2023-05-17 21:21:18 +02:00
Neels Janosch Hofmeyr
54c4c66c59 vty test: show missing write-back of 'rnc-id'
Change-Id: I9eecda8678c32abbeaf39344ccdb14ed5a0828ce
2023-05-16 13:55:17 +02:00
Neels Janosch Hofmeyr
0e6b5b99a7 vty: make legacy 'hnbgw'/'sccp cr max...' fatal
Make this deprecated command fatal when in a .cfg file:

 hnbgw
  sccp cr max-payload-len N

Because we now have:

 cs7 instance N
  sccp max-optional-data N

from libosmo-sigtran to replace it.

Context: The old command currently has no effect, because we had
implemented the specified fixed 130 byte data limit in libosmo-sigtran.
Recent libosmo-sigtran adds a variable limit configuration.

I considered implementing a shim to redirect the legacy command to the
new implementation, but that would be quite ugly: the old command is
under the 'hnbgw' node and would have to apply to all 'cs7' instances.
But we cannot assume that all 'cs7' are above or below the 'hnbgw' node
in the .cfg file. So I'd have to add global state to remember the legacy
config's value and apply that everywhere else. IMHO this is too much
complexity to support an obsoleted command that has a replacement.

To rule out all confusion that this situation may otherwise create,
abort osmo-hnbgw startup in presence of the legacy VTY command, logging
an error that hints at the new cfg item.

Related: SYS#6423
Change-Id: I71f82efe07af2c32f2aa01084bc8da6ce5c6cd1a
2023-05-14 21:54:51 +00:00
Oliver Smith
59e846c61e hnbgw: put copyright into define
Fix the following compile error, seen on ubuntu 18.04 with GCC 7.5.0,
opensuse 15.4 and our OE builds:

  hnbgw.c:549:15: error: initializer element is not constant
    .copyright = hnbgw_copyright,
                 ^~~~~~~~~~~~~~~

Fixes: 04844415 ("move main() to separate file")
Change-Id: I13b2569a369724e0298b064a0876b95d6dafd9d0
2023-05-10 12:32:37 +02:00
Oliver Smith
8cd268ef8a contrib/jenkins.sh: add PFCP variable
Related: OS#6013
Change-Id: Iacc6a0267d4896d0149f5e00d77951cdfc281e4e
2023-05-10 08:47:05 +00:00
Neels Janosch Hofmeyr
63f3abf54c add non-installed libhnbgw.la for test linkage
To ease linking C tests, introduce a libhnbgw.la that contains all of
osmo-hnbgw except the main() and friends.

(I wrote a test that I was going to add, but it turned out to be
obsolete -- now at least we may keep this preparation to add C tests
more easily.)

Change-Id: Id2a706a30fb459005c676bb29c196cf3a582fa01
2023-05-08 15:29:03 +02:00
Neels Janosch Hofmeyr
0484441530 move main() to separate file
Conform to recent osmocom practice by having an osmo_hnbgw_main.c file
for main() and its immediate infrastructure.

Prepares for adding a non-installed libhnbgw.la -- which must not
contain a main() function -- so that linking regression tests is easy.

Separating to a new file seems appropriate because hnbgw.c has gathered
a bunch of stuff that is not strictly main() related / might be useful
to access in a C test.

Change-Id: I5f26a6b68f0d380617d73371f40f3b9055cac362
2023-05-08 15:20:26 +02:00
Neels Janosch Hofmeyr
d12aecf33d ranap_rab_ass_test.c: clarify talloc contexts
Change-Id: I4256e0d94a7e7fbba236aa92c44229c12cf17313
2023-05-07 00:19:17 +02:00
Neels Janosch Hofmeyr
f1d4e3b7dd simplify: one g_hnbgw as global state and root ctx
So far we jump through hoops everywhere to pass around osmo-hnbgw's
global singleton state to all code paths. Some files choose to spawn a
static "global" pointer. And we also have the talloc root tall_hnb_ctx.

Simplify:
- Have a single global g_hnbgw pointer. Drop all function args and
  backpointers to the global state.
- Use that global g_hnbgw as talloc root context, instead of passing a
  separate tall_hnb_ctx around.

(Cosmetic preparation for separate osmo_hnbgw_main.c file)

Change-Id: I3d54a5bb30c16a990c6def2a0ed207f60a95ef5d
2023-05-07 00:18:34 +02:00
Neels Janosch Hofmeyr
28e8d8a0b2 comment typo fix in ranap_rab_ass.c
Change-Id: I28b4c966c399acbff46faa36f0f05cda7356e44e
2023-05-07 00:08:20 +02:00
Neels Janosch Hofmeyr
6639eb910a drop ctrl_test_runner.py
Unused and contains no actual tests.

Change-Id: If3d6614334b692e49efcc45d1e4fb29a00c68602
2023-05-07 00:08:18 +02:00
Neels Janosch Hofmeyr
c204f7e809 actually run vty-tests: osmotestconfig.py
Change-Id: Id60e5dfb22b7cea33c53388d339838add101de44
2023-05-06 05:56:02 +02:00
Neels Janosch Hofmeyr
3d26d73a1c vty: fix doc strings for 'show {hnb,ue}'
Fixes: OS#5987
Change-Id: I79f8b13a9f22fb8311017005cc0a3ac3a7e78983
2023-05-06 05:55:48 +02:00
Neels Janosch Hofmeyr
0b2983d1af eliminate function hnb_contexts()
It's really just a bloated llist_count() wrapper.
(Cosmetic preparation for separate osmo_hnbgw_main.c file)

Change-Id: Icd4100ddeb98f4dc2e2ec6de2d44a4b861a66078
2023-05-06 03:53:20 +00:00
Neels Janosch Hofmeyr
52f9e327bb drop empty hnbgw_rua_init()
Change-Id: I8e3ff452242d8baa527c2a61d182bbe9786341e3
2023-05-06 03:53:20 +00:00
Neels Janosch Hofmeyr
83697f832e ps_rab_ass_fsm.h: fix dup of FSM event enum
Change-Id: I20d5fc6d52d08b185ef3aa4f7a59fa0ebb202b18
2023-05-06 03:53:20 +00:00
Neels Janosch Hofmeyr
fdc78ce29b rab_ass_fsm.c: fix asn1 memleak when replacing GTP address
This fix is really weird: by *removing* a FREE() call, fix a leak of the
replaced transportLayerAddress. (An in-code comment says some more.)

Also remove the other FREE() call that turns out to not be necessary.

This has been verified with a ttcn3 test that is not yet submitted,
which ensures an empty talloc_asn1_ctx at the end of every test
(osmo-ttcn3-hacks I2948ee6f167369a2252f85b493e9653b93c7e4e9 ).

Change-Id: I315d04a07b7dfd4dce26e5b5f871318e27e2bdf6
2023-05-06 03:53:20 +00:00
Neels Janosch Hofmeyr
fc800ca49b actually run vty-tests: osmotestvty.py
Change-Id: Iab84af37488b5ec1000162d945f909f9ab05dad8
2023-05-05 02:23:11 +02:00
Neels Janosch Hofmeyr
ece0f81e30 actually run vty-tests: VTY transcript tests
There have been VTY transcript tests around for a long time, but we are
not running them.

Change-Id: Ie9fc8f29b84e1dffea000fea11e0afe285c1df39
2023-05-05 02:23:11 +02:00
Neels Janosch Hofmeyr
cc3cf24322 fix log msg typo 'Unsupportedccept'
Change-Id: I8e334ec84809b69bfff36f95adf21f6e6ae041e1
2023-05-01 00:25:36 +00:00
Neels Janosch Hofmeyr
929ac4efdf less code dup in mem free of hnbgw_rx_ue_register_req()
Related: SYS#6297
Change-Id: I433127f90bf6f82baf33c516f327f84d081ad69c
2023-05-01 00:25:36 +00:00
Neels Janosch Hofmeyr
3f92103c62 fix asn1 leak in hnbgw_rua_rx()
Related: SYS#6297
Change-Id: I7392e0bb07c2cb02c1a12f8e19acb142cd101f77
2023-04-27 16:39:06 +00:00
Neels Janosch Hofmeyr
0d2251ef7d fix asn1 leaks in ps_rab_ass_resp_send_if_ready()
Related: SYS#6297
Change-Id: Icdb6eef0d1fde849c0b9121cc96d8221e72d45c8
2023-04-27 16:39:06 +00:00
Neels Janosch Hofmeyr
5437aeaca4 fix asn1 leak in error path of hnbgw_tx_ue_register_acc_tmsi()
Related: SYS#6297
Change-Id: Iad1519aea36d8b6fef51fedb22a861ad1bc462fc
2023-04-27 16:39:06 +00:00
Neels Janosch Hofmeyr
e33d7321df fix asn1 leak in error path of hnbgw_tx_ue_register_acc()
Related: SYS#6297
Change-Id: Iaaf7a393ef1fcad619687e2eb2dcc31f0aec0e96
2023-04-27 16:39:06 +00:00
Neels Janosch Hofmeyr
7f219856c9 fix asn1 leak in handle_cn_ranap()
Related: SYS#6297
Change-Id: I307f65ae083aba16f75510bcd0a7d1f124e62be4
2023-04-27 16:39:06 +00:00
Neels Janosch Hofmeyr
0188391a19 place asn1 context under hnb_ctx for better visibility
Include talloc_asn1_context in 'show talloc-context application'. It
belongs there IMO.

Related: SYS#6297
Change-Id: Iffc320c64713b225b57211da4fb95a868bf1f369
2023-04-27 16:39:06 +00:00
Neels Janosch Hofmeyr
4a4a675fec fix vty: show talloc-context all
Without "null tracking" enabled, the VTY 'show talloc-ctx all' shows
nothing at all.

Since the talloc_asn1_ctx is "created in NULL ctx", it was not visible
in any talloc reports before this patch.

This patch uncovers a slur of leaks, which accumulate in
talloc_asn1_ctx. Fixes follow.

Related: SYS#6297
Change-Id: I5cb4e9a3b393100877f03d68a09acf5817c5a878
2023-04-27 16:39:06 +00:00
Oliver Smith
39683d0c6f debian: set compat level to 10
Related: OS#5958
Change-Id: I5d26ab03aacf3b8ef8c1c4c669c12090fd0b7899
2023-04-25 16:48:22 +02:00
Oliver Smith
8fd4159ea9 hnbgw_mgw_setup: use mgcp_client_pool_empty()
Don't fall back to the legacy config if the pool is configured but no
connection to any pool member can be established.

Depends: osmo-mgw I009483ac9dfd6627e414f14d43b89f40ea4644db
Related: OS#5993
Change-Id: Icf3f3f0524c9d27f645c68851aaf9c6f33842927
2023-04-04 16:32:37 +02:00
Vadim Yanitskiy
612d3255b8 tests: do not depend on undefined $(BUILT_SOURCES)
Change-Id: I7536be8aa3f0ab39f183e00cf4382636038db4b7
2023-03-30 02:55:06 +07:00
Vadim Yanitskiy
3ebdd301c9 tests: make 'vty-test' target depend on osmo-hnbgw binary
Change-Id: Iaaa1dd622fde460a250c11eb97cf0b25469e55c4
2023-03-30 02:53:41 +07:00
Vadim Yanitskiy
a973a93527 tests: use -no-install libtool flag to avoid ./lt-* scripts
This option should be used for any executables which are used only
for testing, or for generating other files and are consequently never
installed.  By specifying this option, we are telling Libtool that
the executable it links will only ever be executed from where it is
built in the build tree.  Libtool is usually able to considerably
speed up the link process for such executables.

Change-Id: Id5f88c331cf1dfd3f38120b4ef59ced9a563d4f0
2023-03-30 02:51:40 +07:00
Vadim Yanitskiy
a65c57a159 tests: use check_PROGRAMS for tests, not noinst_PROGRAMS
Change-Id: I616ed6920af98502424e4eab6cffde5d39d9698e
2023-03-30 02:50:26 +07:00
Neels Janosch Hofmeyr
0b10b5799f vty: 'show ue': show which HNB the UE is registered on
Change-Id: Ife54a105a5a011678d6a03d0032c7622c3079a28
2023-03-24 04:14:59 +01:00
Neels Janosch Hofmeyr
bc92608e60 log: in new RUA,SCCP FSM IDs, indicate CS/PS
During tests without an SGSN, I noticed SCCP connections failing, and I
also noticed that I couldn't tell if they were CS or PS related. Add a
"CS"/"PS" indicator to FSM instance IDs for RUA and SCCP.

Change-Id: I5b8242196af3b08eaf64ca5ac1c257a97a5d02cb
2023-03-24 04:14:59 +01:00
Neels Janosch Hofmeyr
be7df7c1a7 release UE Contexts on SCTP_RESTART
When receiving SCTP_RESTART for a given HNB, directly clear the UE
Contexts.

(The HNB typically connects via HNBAP shortly after this causing a UE
Context clearing too, but UE state should always be cleared on
SCTP_RESTART, no matter what.)

Change-Id: I583922193ba73e17ab85152005535188c2762b85
2023-03-24 04:14:59 +01:00
Neels Janosch Hofmeyr
01389592b2 release UE Contexts on HNB (Re-)Register
Whenever a HNB reconnects, we want to discard all previous UE state and
any active connections.

In one HNB reconnect scenario, we receive SCTP_RESTART. This sets
hnb_registered == false, which in turn skipped the UE cleanup step in
hnbgw_rx_hnb_register_req(), leaking UE Contexts.

Remove all weird conditions like that, and simply clear out all UE state
whenever a HNB registers, period.

Change-Id: I370966d2d76fd263714e727918fcc1ea2f2315fa
2023-03-24 04:14:59 +01:00
Neels Janosch Hofmeyr
9425640d7f map_sccp: on timeout during WAIT_CC, send N-DISCONNECT to SCCP-SCOC
When waiting for CC expires, we should tell the SCCP-SCOC that we've
stopped waiting: send N-DISCONNECT, instead of nothing.

Change-Id: Ie94fcee4e2507a55449050aab96307199aed99a2
2023-03-24 04:14:56 +01:00
Philipp Maier
c6cef24546 mgw_fsm: refactor helper function handle_rab_release()
the function handle_rab_release() is difficult to read and it is not
immediately clear what happens and why. Lets split this up and add some
comments to improve this.

Related: OS#5916
Change-Id: I3595502b98ea5febbde7f2fab3999e2533766b48
2023-03-21 09:46:33 +01:00
Philipp Maier
065a719294 mgw_fsm: use __func__ to mention function name in log line
Change-Id: I23050fc5f644340dfbd0323eef6309cff6fc4515
2023-03-21 09:46:33 +01:00
Philipp Maier
cc7d2b1313 mgw_fsm: fix log line
The function name mentioned in the log line does not match the actual
function name.

Change-Id: Iefc005f10e3c8f165c5686781747460a10ada1e0
2023-03-21 09:43:36 +01:00
Philipp Maier
1c4b05d026 mgw_fsm: fix typo
Change-Id: I16ee37bbfda01b541ad7a6f5269689c4b9e92c8c
2023-03-20 16:43:42 +01:00
Neels Janosch Hofmeyr
9e051548d6 debug log: log received RANAP message types on DCN, DHNB
Change-Id: Ib295e49f2c53164c5787002797145b3e6de5546b
2023-03-09 03:24:30 +01:00
Neels Janosch Hofmeyr
7992ce0163 tweak LOGHNB()
Add braces around context part.

In the HNBGW_Tests.ttcn output, I see this:

 DRUA DEBUG TTCN3 HNodeB transmitting RUA DirectTransfer

which reads like the hNodeB would transmit a RUA to us. Instead, this is
us sending RUA to the hNodeB, which is much clearer like this:

 DRUA DEBUG (TTCN3 HNodeB) transmitting RUA DirectTransfer

This matches the way we typically show context info in osmo logging.

Change-Id: If6f0c3ae81c737b7488fa93c435179dcf27a5c94
2023-03-09 03:24:30 +01:00
Neels Janosch Hofmeyr
b5dfba79ca fix regression: unbreak PFCP: PS RAB via UPF proxying
Fix PS RAB Assignment operation:
- add #include "config.h"
- fix uncompilable code in ENABLE_PFCP sections.

A series of oversights made me completely break PFCP / UPF operation for
PS RAB Assignments in this commit:
 'context map: introduce RUA and SCCP FSMs to fix leaks'
 ed424d5be4
 I6ff7e36532ff57c6f2d3e7e419dd22ef27dafd19

- In my HNBGW_Tests.ttcn, I used osmo-hnbgw-with-pfcp.cfg, but failed to
  set mp_enable_pfcp_tests := true in HNBGW_Tests.cfg.

- Hence I was under the impression that I was testing PFCP via UPF when
  really I wasn't. So I did not notice that:

- in the new files context_map_rua.c and context_map_sccp.c, the
  ENABLE_PFCP macro was always unset because I forgot to
  #include "config.h".

- Hence I did not notice that the moved PFCP RAB code was never once
  compiled or run.

My bad, that was really dumb.

Related: SYS#6297
Change-Id: I5df5073f0092ecdfd73d5d08207ca4042154eab1
2023-03-09 03:24:30 +01:00
Neels Janosch Hofmeyr
0fdb5c3f09 sccp_sap_up(): ignore PCSTATE.ind
Silence the error log about "unknown" PCSTATE prim.

Todo / coming up: instead of ignoring, detect a Destination Unavailable
from prim->u.pcstate and disconnect all conns with that particular CN
link.

Depends: libosmo-sccp If381f537ab91af1feef7f0e51921217f27e18e6a
Change-Id: I547387a5cc14ccb506be04ac785e6807fc4e6a96
2023-02-24 15:19:24 +01:00
Neels Janosch Hofmeyr
311bfb5983 log osmo_fsm timeouts
set osmo_fsm_log_timeouts(true);

Change-Id: Ic1ca03f06fbdef5a3fbe503e4414a780eb3e0fcc
2023-02-24 15:19:24 +01:00
Neels Janosch Hofmeyr
ed424d5be4 context map: introduce RUA and SCCP FSMs to fix leaks
Refactor the entire RUA <-> SCCP connection-oriented message forwarding:
- conquer confusion about hnbgw_context_map release behavior, and
- eradicate SCCP connection leaks.

Finer points:

== Context map state ==
So far, we had a single context map state and some flags to keep track
of both the RUA and the SCCP connections. It was easy to miss connection
cleanup steps, especially on the SCCP side.
Instead, the two FSMs clearly define the RUA and SCCP conn states
separately, and each side takes care of its own release needs for all
possible scenarios.
- When both RUA and SCCP are released, the context map is discarded.
- A context map can stay around to wait for proper SCCP release, even if
  the RUA side has lost the HNB connection.
- Completely drop the async "context mapper garbage collection", because
  the FSMs clarify the release and free steps, synchronously.
- We still keep a (simplified) enum for global context map state, but
  this is only used so that VTY reporting remains mostly unchanged.

== Context map cleanup confusion ==
The function context_map_hnb_released() was the general cleanup function
for a context map. Instead, add separate context_map_free().

== Free context maps separately from HNB ==
When a HNB releases, talloc_steal() the context maps out of the HNB
specific hnb_ctx, so that they are not freed along with the HNB state,
possibly leaving SCCP connections afloat.
(It is still nice to normally keep context maps as talloc children of
their respective hnb_ctx, so talloc reports show which belongs to
which.)

So far, context map handling found the global hnb_gw pointer via
map->hnb_ctx->gw. But in fact, a HNB may disappear at any point in time.
Instead, use a separate hnb_gw pointer in map->gw.

== RUA procedure codes vs. SCCP prims ==
So far, the RUA rx side composed SCCP prims to pass on:

 RUA rx ---SCCP-prim--> RANAP handling ---SCCP-prim--> SCCP tx

That is a source of confusion: a RUA procedure code should not translate
1:1 to SCCP prims, especially for RUA id-Disconnect (see release charts
below).
Instead, move SCCP prim composition over to the SCCP side, using FSM
events to forward:

 RUA rx --event--> RUA FSM --event--> SCCP FSM --SCCP-prim--> SCCP tx
         +RANAP             +RANAP              +RANAP

 RUA tx <--RUA---- RUA FSM <--event-- SCCP FSM <--event-- SCCP rx
          +RANAP             +RANAP              +RANAP

Hence choose the correct prim according to the SCCP FSM state.
- in hnbgw_rua.c, use RUA procedure codes, not prim types.
- via the new FSM events' data args, pass msgb containing RANAP PDUs.

== Fix SCCP Release behavior ==
So far, the normal conn release behavior was

 HNB                 HNBGW                   CN
  | --id-Disconnect--> | ---SCCP-Released--> |  Iu-ReleaseComplete
  |                    | <--SCCP-RLC-------- |  (no data)

Instead, the SCCP release is now in accordance with 3GPP TS 48.006 9.2
'Connection release':

 The MSC sends a SCCP released message. This message shall not contain
 any user data field.

i.e.:

 HNB                 HNBGW                    CN
  | --id-Disconnect--> | ---Data-Form-1(!)--> |  Iu-ReleaseComplete
  |                    | <--SCCP-Released---- |  (no data)
  |                    | ---SCCP-RLC--------> |  (no data)

(Side note, the final SCCP Release Confirm step is taken care of
implicitly by libosmo-sigtran's sccp_scoc.c FSM.)

If the CN fails to respond with SCCP-Released, on new X31 timeout,
osmo-hnbgw will send an SCCP Released to the CN as fallback.

== Memory model for message dispatch ==
So far, an osmo_scu_prim aka "oph" was passed between RUA and SCCP
handling code, and the final dispatch freed it. Every error path had to
take care not to leak any oph.
Instead, use a much easier and much more leakage proof memory model,
inspired by fixeria:
- on rx, dispatch RANAP msgb that live in OTC_SELECT.
- no code path needs to msgb_free() -- the msgb is discarded via
  OTC_SELECT when handling is done, error or no error.
- any code path may also choose to store the msgb for async dispatch,
  using talloc_steal(). The user plane mapping via MGW and UPF do that.
- if any code path does msgb_free(), that would be no problem either
  (but none do so now, for simplicity).

== Layer separation ==
Dispatch *all* connection-oriented RUA tx via the RUA FSM and SCCP tx
via the SCCP FSM, do not call rua_tx_dt() or osmo_sccp_user_sap_down()
directly.

== Memory model for decoded ranap_message IEs ==
Use a talloc destructor to make sure that the ranap_message IEs are
always implicitly freed upon talloc_free(), so that no code path can
possibly forget to do so.

== Implicit cleanup by talloc ==
Use talloc scoping to remove a bunch of explicit cleanup code. For
example, make a chached message a talloc child of its handler:
  talloc_steal(mgw_fsm_priv, message);
  mgw_fsm_priv->ranap_rab_ass_req_message = message;
and later implicitly free 'message' by only freeing the handler:
  talloc_free(mgw_fsm_priv)

Related: SYS#6297
Change-Id: I6ff7e36532ff57c6f2d3e7e419dd22ef27dafd19
2023-02-24 15:19:24 +01:00
Neels Janosch Hofmeyr
e67c4b324c add design charts for new context map FSMs
Planning new connection-oriented RUA and SCCP FSMs to
- conquer confusion about hnbgw_context_map release behavior, and
- eradicate SCCP connection leaks.

Related: SYS#6297
Change-Id: I661bf65d79972a732c52732934095e8bfcd99694
2023-02-23 02:03:20 +01:00
Neels Janosch Hofmeyr
29f82a7b48 cosmetic: regroup members of hnbgw_context_map
Change the order in the struct definition so that the items for RUA and
for SCCP are grouped together. Tweak comments.

Change-Id: I84424617996d8e1a6814aa122e63454c0666b724
2023-02-23 02:03:07 +01:00
Neels Janosch Hofmeyr
52b2a9e6b0 use RUA procedure code for rua_to_scu() arg
Instead of osmo_scu_prim_type, use RUA_ProcedureCode to tell
rua_to_scu() the RUA message type being handled.

Cosmetically prepare for upcoming patch that completely moves
osmo_scu_prim composition from rua_to_scu() to a new context map SCCP
FSM.

Related: I6ff7e36532ff57c6f2d3e7e419dd22ef27dafd19
Change-Id: I28735945844f732e7bd7f09db1f453c608415b84
2023-02-23 01:57:21 +01:00
Neels Janosch Hofmeyr
62d50a1fe8 rua_to_scu(): drop dead code for connection-less
From rua_to_scu(), drop dead code for connection-less messages. In fact,
rua_to_scu() is only used for connection-oriented messages. Clarify
that.

Change-Id: Id03c412cde066a6b2bbc26cb6b87053aa2408cba
2023-02-23 01:43:16 +01:00
Neels Janosch Hofmeyr
1e277db2cc fix deprecation: use ranap_cn_rx_co_decode2()
Change-Id: I867cbf174beab1eefddc682c1420737e49f9e50d
2023-02-23 01:17:11 +01:00
Neels Janosch Hofmeyr
60a52e4121 cosmetic: rename context_map_deactivate
Rename context_map_deactivate() to context_map_hnb_released().

This function is called only when the HNB is released / lost.

Change-Id: I6dcb26c94558fff28faf8f823e490967a9baf2ec
2023-02-23 01:15:54 +01:00
Neels Janosch Hofmeyr
95d5273967 log tweak
Change-Id: Ib6f8c46355d4748089e59c80fc1c8c736c887a26
2023-02-22 14:52:36 +01:00
Neels Janosch Hofmeyr
43cc12bac3 various comment tweaks
Change-Id: Ie40aa672948062282c566c90300f6e96963e05ec
2023-02-21 00:57:03 +01:00
Neels Janosch Hofmeyr
f05d15e160 drop obsolete fixme comment
msgb will be freed by osmo_stream_srv_send(), called via hnbgw_rua_tx().

Change-Id: If35f4cedf6f37be797dcd48e816461d3b1d9db54
2023-02-21 00:57:03 +01:00
Neels Janosch Hofmeyr
9ffebe784d cosmetic: drop stray backslash
Change-Id: I311b62ff393c6520d72fc6aee21d86723487133f
2023-02-21 00:57:03 +01:00
Neels Janosch Hofmeyr
3f4d645890 Deprecate 'sccp cr max-payload-len', remove SCCP CR limit code
The SCCP CR payload length limit is now implemented in libosmo-sigtran
v1.7.0. The limit no longer needs to be enforced in osmo-hnbgw.

This reverts commit 2c91bd66a1,
except for keeping the cfg option, marked deprecated, and not doing
anything.

Fixes: OS#5906
Related: SYS#5968
Related: OS#5579
Depends: I174b2ce06a31daa5a129c8a39099fe8962092df8 (osmo-ttcn3-hacks)
Change-Id: I18dece84b33bbefce8617fbb0b2d79a7e5adb263
2023-02-15 03:34:43 +01:00
Neels Janosch Hofmeyr
8eefcbee92 UE state leak: when HNB re-registers, discard previous UE state
User reports show leaked UE contexts over time, in a scenario where HNB
regularly disconnect and reconnect.

So far, when we receive a HNB REGISTER REQ, we log as "duplicated" and
continue to use the hnb_context unchanged. But it seems obvious that a
HNB that registers does not expect any UE state to remain. It will
obviously not tear down UE contexts (HNBAP or RUA) that have been
established before the HNB REGISTER REQUEST. These hence remain in
osmo-hnbgw indefinitely.

When receiving a HNB REGISTER REQUEST, release all its previous UE
state. Allow the HNB to register with a clean slate.

The aim is to alleviate the observed build-up of apparently orphaned UE
contexts, in order to avoid gradual memory exhaustion.

Related: SYS#6297
Change-Id: I7fa8a04cc3b2dfba263bda5b410961c77fbed332
2023-02-11 03:32:34 +01:00
Pau Espin Pedrol
a3c7f750a2 Bump version: 1.3.0.59-d4111-dirty → 1.4.0
Change-Id: I0e45a9ba7437f800da6115da135cc80fb9e97bfe
2023-02-07 18:05:47 +01:00
Neels Janosch Hofmeyr
d41112fbcc coverity: hnbgw_rua.c: remove redundant check
'map' is already guaranteed to be not NULL for that message type by the
preceding switch().

Related: CID#307434
Change-Id: Id30f459616391187aa2f8ad400c316e2144154c6
2023-02-01 16:40:40 +01:00
Neels Janosch Hofmeyr
87ecf69b55 fix SCCP conn leak on non-graceful HNB shutdown
Clean up SCCP connections when a HNB disconnects.

When a HNB disconnects, we clean up all RUA <-> SCCP connection state
for that HNB. In that cleanup, discarding the SCCP connection is so far
missing.

Add a flag indicating true between SCCP CC and DISCONNECT. Hence we can
tell during context_map_deactivate() whether the cleanup is graceful
(DISCONNECT already sent) or non-graceful (need to DISCONNECT).

Change-Id: Icc2db9f6c0b2d0a814ff1110ffbe5e8f7f629222
2023-01-20 20:30:03 +01:00
Neels Janosch Hofmeyr
07d01d50a5 fix possible leak of ue_context on UE REGISTER error
Deallocate a recently allocated UE context if the UE REGISTER procedure
fails internally, in two places.

The UE REGISTER error is rather unlikely to happen in practice: it only
occurs when encoding the HNBAP response fails, which only gets checked
input and thus is unlikely to fail.

The same code paths also possibly leak asn1c allocations -- leave those
for another patch.

Related: SYS#6297
Change-Id: Icf4b82f9a904d56332c567f7ccfb24231ee66270
2023-01-18 18:42:50 +01:00
Neels Janosch Hofmeyr
a08b8a595a fix msgb leak for RANAP RAB Ass. Req.
Fix leaked msgb introduced by the MGW support recently added, and from
there copied to the UPF support added after that.

Fixes leaked "RANAP Tx" msgb, one per RAB Assignment that involves an
MGW or UPF proxying of user data.

Related: SYS#6297
Change-Id: Ie30e880301346ffca72f98f8c467e56d622fb03f
2023-01-17 23:40:00 +01:00
Neels Janosch Hofmeyr
28619961a9 fix segfault on MGCP timeout
bisect shows that the segfault was introduced by using the MGCP client
pool:

 e62af4d46a is the first bad commit
 Author: Pau Espin Pedrol <pespin@sysmocom.de>
    Introduce support for libosmo-mgcp-client MGW pooling
    Change-Id I371dc773b58788ee21037dc25d77f556c89c6b61

The segfault:

 20230117224550365 DLMGCP DEBUG MGCP_CONN(to-HNB)[0x612000003ca0]{ST_CRCX_RESP}: Timeout of T1 (fsm.c:317)
 [...]
 20230117224550366 DLMGCP DEBUG mgw-endp(mgw-fsm-14429752-0)[0x612000003b20]{WAIT_MGW_RESPONSE}: Deallocated (fsm.c:568)
 20230117224550366 DMGW DEBUG mgw(mgw-fsm-14429752-0)[0x612000003820]{MGW_ST_CRCX_HNB}: Received Event MGW_EV_MGCP_TERM (mgcp_client_endpoint_fsm.c:869)
 =================================================================
 ==255699==ERROR: AddressSanitizer: heap-use-after-free on address 0x62b000000260 at pc 0x7f282a6ee143 bp 0x7fff0d9bcae0 sp 0x7fff0d9bcad8
 READ of size 8 at 0x62b000000260 thread T0
     #0 0x7f282a6ee142 in osmo_mgcpc_ep_client ../../../../src/osmo-mgw/src/libosmo-mgcp-client/mgcp_client_endpoint_fsm.c:223
     #1 0x55e2a84f1889 in mgw_fsm_allstate_action ../../../../src/osmo-hnbgw/src/osmo-hnbgw/mgw_fsm.c:504
     #2 0x7f2829d50c56 in _osmo_fsm_inst_dispatch ../../../src/libosmocore/src/fsm.c:863
     #3 0x7f2829d55a08 in _osmo_fsm_inst_term ../../../src/libosmocore/src/fsm.c:962
     #4 0x7f282a72679a in osmo_mgcpc_ep_fsm_check_state_chg_after_response ../../../../src/osmo-mgw/src/libosmo-mgcp-client/mgcp_client_endpoint_fsm.c:869
     #5 0x7f282a6f1869 in on_failure ../../../../src/osmo-mgw/src/libosmo-mgcp-client/mgcp_client_endpoint_fsm.c:414
     #6 0x7f282a727ac6 in osmo_mgcpc_ep_fsm_handle_ci_events ../../../../src/osmo-mgw/src/libosmo-mgcp-client/mgcp_client_endpoint_fsm.c:935
 [...]

When a CRCX times out, MGCP_CONN fsm terminates (libosmo-mgcp-client).
In turn the parent mgw-endp fsm terminates (libosmo-mgcp-client).
This generates an MGW_EV_MGCP_TERM event to the mgw_fsm (osmo-ttcn3-hacks).
This attempts to retrieve a pointer from mgw_fsm state:
mgw_fsm_priv->mgcpc_ep->mgcp_client
where the middle one, mgcpc_ep, is the 'mgw-endp' that already deallocated above.

To fix, add to /osmo-hnbgw/mgw_fsm.c a separate pointer to the
mgcp_client, to call mgcp_client_pool_put() on it. Do not use mgcpc_ep
to get the mgcp_client, because mgcpc_ep deallocates independently.

Related: OS#5862
Change-Id: I460d7249f4fc7edcfd94f6084fc8f933b491520c
2023-01-17 23:39:46 +01:00
Neels Janosch Hofmeyr
9bc7649b95 drop bogus error log 'no MGW fsm'
Looking at a customer's log, these error logs got my attention. There
seems to be no point in logging this at all.

Change-Id: I89dd4fb6913bfb84b6667b159e09968734e2102a
2023-01-03 00:29:58 +01:00
Pau Espin Pedrol
a1c8653bf9 context_map: Lower loglevel to INFO when deallocating context IDs
Change-Id: Iefe13934d097d646db232127040feb02db37bc38
2022-12-23 15:23:00 +00:00
arehbein
76c4203552 osmo-hnbgw: Transition to use of 'telnet_init_default'
Related: OS#5809
Change-Id: Id3256d09f62e802cc62fa9ba8aaafd403ccbb53e
2022-12-23 11:13:46 +00:00
Max
a7fcbe100c ctrl: take both address and port from vty config
Change-Id: If5b80364c28fb1ca2bc00f4ece851de64c8ce6b1
2022-12-17 21:24:13 +03:00
Pau Espin Pedrol
61021881ac hnbgw: Avoid allocating SCCP conn id >0x00fffffe
This fixes bug in use of M3UA/SCCP after 2**24 connection IDs have been
allocated.

Related: SYS#6211
Change-Id: I03bad960f65fbff6e467def5bba60fefb328f962
2022-11-24 12:13:10 +01:00
Pau Espin Pedrol
0589c3ecf1 vty: Fix timers not printed when dumping running-config
Change-Id: I129bf412cd4b74e9f515411ef8f812a8261e57b2
2022-11-02 20:12:54 +01:00
Pau Espin Pedrol
0e03070789 doc: Include mgwpool.adoc from osmo-gsm-manuals
This way we document the recently gained support for MGW pooling.

Related: SYS#5987
Depends: osmo-gsm-manuals.git Change-Id Ieda0d4bfe6fc90da6e19c791d8ec2da89427ba3b
Change-Id: I3dc8a4b50f13ad50390ba82e64fe4ebe0b97d4e5
2022-10-20 17:15:16 +02:00
Pau Espin Pedrol
e62af4d46a Introduce support for libosmo-mgcp-client MGW pooling
Large RAN installations may benefit from distributing the RTP voice
stream load over multiple media gateways.

libosmo-mgcp-client supports MGW pooling since version 1.8.0 (more than
one year ago). OsmoBSC has already been making use of it since then (see
osmo-bsc.git 8d22e6870637ed6d392a8a77aeaebc51b23a8a50); lets use this
feature in osmo-hngw too.

This commit is also part of a series of patches cleaning up
libosmo-mgcp-client and slowly getting rid of the old non-mgw-pooled VTY
configuration, in order to keep only 1 way to configure
libosmo-mgcp-client through VTY.

Related: SYS#5091
Related: SYS#5987
Change-Id: I371dc773b58788ee21037dc25d77f556c89c6b61
2022-10-20 17:03:06 +02:00
Pau Espin Pedrol
bef2c345df Makefile.am: Drop duplicated LIBOSMOMGCPCLIENT_LIBS
Change-Id: Ie52f27bcacca60cc16b49142edb79a3e58dff131
2022-10-19 15:56:52 +02:00
Pau Espin Pedrol
b9be0ea93e Clear SCTP tx queue upon SCTP RESTART notification
Depends: libosmo-netif.git Change-Id Iecb0a4bc281647673d2930d1f1586a2df231af52
Related: SYS#6113
Change-Id: I60adf35e5b5713d38c4584615e059875dcb74bd7
2022-10-17 13:57:17 +02:00
Pau Espin Pedrol
bbad8dec36 hnb_read_cb(): -EBADF must be returned if conn is freed to avoid use-after-free
Otherwise the libosmo-netif stream API may continue accessing the conn
after returning if the socket has the WRITE flag active in the same main
loop iteration.

Change-Id: I628c59a88d94d299f432f405b37fbe602381d47e
2022-10-01 21:21:24 +02:00
Pau Espin Pedrol
c923d19b7b hnb_read_cb: use local var to reduce get_ofd() calls
Change-Id: Ic7058b5a05b0d34b80617006d4e929a523212221
2022-10-01 21:21:24 +02:00
Pau Espin Pedrol
5f19597b02 Close conn when receiving SCTP_ASSOC_CHANGE notification
It was seen on a real pcap trace (sctp & gsmtap_log) that the kernel
stack may decide to kill the connection (sending an ABORT) if it fails
to transmit some data after a while:
ABORT Cause code: "Protocol violation (0x000d)",
      Cause Information: "Association exceeded its max_retrans count".
When this occurs, the kernel sends the
MSG_NOTIFICATION,SCTP_ASSOC_CHANGE,SCTP_COMM_LOST notification when
reading from the socket with sctp_recvmsg(). This basically signals that
the socket conn is dead, and subsequent writes to it will result in
send() failures (and receive SCTP_SEND_FAILED notification upon follow
up reads).
It's important to notice that after those events, there's no other sort
of different event like SHUTDOWN coming in, so that's the time at which
we must tell the user to close the socket.
Hence, let's signal the caller that the socket is dead by returning 0,
to comply with usual recv() API.

Related: SYS#6113
Change-Id: If35efd404405f926a4a6cc45862eeadd1b04e08c
2022-10-01 21:21:06 +02:00
Pau Espin Pedrol
1906a30ca9 Fix handling of sctp SCTP_SHUTDOWN_EVENT notification
SCTP_SHUTDOWN_EVENT is a first class event, and not a subtype of
SCTP_ASSOC_CHANGE (such as SCTP_SHUTDOWN_COMP).

Related: SYS#6113
Change-Id: I7fa648142c07f63c55091d2a15b9d7312bcd4cec
2022-09-30 14:43:06 +02:00
Pau Espin Pedrol
12bc4afab3 Workaround bug where old hnb_context from same remote addr+port is kept
Under some circumstancies not yet fully known, which seems to
involve bad link quality and high latencies and some specific hNodeB
which reuse its local IP addr+port, it is seen that a 2nd SCTP
connection is created from the same HNB while locally we still keep the
old SCTP connection and its related hnb_context. Hence, when the hNodeB
tries to register again with this new conn, it is rejected all the time
by the HNBGW.

Related: SYS#6113
Change-Id: I33ae901cc37646eca90bf06953e44fcc25f4d6c6
2022-09-29 17:18:56 +02:00
Pau Espin Pedrol
25cd41f3b5 hnbap: Improve logging around HNBAP HNB Register Request
Change-Id: I279ef563b38fb0dd3e6a72162db91d8503f91af8
2022-09-27 14:57:05 +02:00
Pau Espin Pedrol
55239c2cca hnbap: Accept duplicated HNB Register Request on same conn
As per what's indicated in 3GPP TS 25.469 8.2.4 Abnormal Conditions:
"""
If the HNB-GW receives a duplicate HNB REGISTER REQUEST (i.e. for an already registered HNB identified by the
unique HNB identity), then the new HNB REGISTER REQUEST shall override the existing registration and the
handling of the new HNB REGISTER REQUEST is according to section 8.2.
"""

Related: SYS#6113
Change-Id: I0250350a14a87498a2c68cd0c726ee2f1e72419d
2022-09-27 14:45:07 +02:00
Pau Espin Pedrol
3bf5395102 hnbgw: Fix recent regression not closing conn upon rx of SCTP_SHUTDOWN_EVENT
Before handling of OSMO_STREAM_SCTP_MSG_FLAGS_NOTIFICATION in recent
commit (see Fixes: below), osmo_stream_srv_recv() and
internal _sctp_recvmsg_wrapper() in libosmo-netif would return either
-EAGAIN or 0 when an sctp notification was received from the kernel.

After adding handling of OSMO_STREAM_SCTP_MSG_FLAGS_NOTIFICATION, the
code paths for "rc == -EAGAIN" and "rc == 0" would not be executed
anymore since the first branch takes preference in the if-else
tree. For "rc == -EAGAIN" it's fine because the new branch superseeds
what's done on the "rc == -EAGAIN" branch. However, for the "rc == 0",
we forgot to actually destroy the connection. The "rc == 0" branch was
basically reached when SCTP_SHUTDOWN_EVENT was received because
osmo_stream_srv_recv() tried to resemble the interface of regular
recv(); let's hence check for that explicitly and destroy the conn
object (and the related hnb context in the process) when we receive
that event.

Fixes: 1de2091515
Related: SYS#6113
Change-Id: I11b6af51a58dc250975a696b98d0c0c9ff3df9e0
2022-09-22 16:39:51 +02:00
Pau Espin Pedrol
1de2091515 hnbgw: Unregister HNB if SCTP link is restarted
Sometimes an hNodeB may reconnect (SCTP INIT) using same SCTP tuple without
closing the previous conn. This is handled by the SCTP stack by means of
pushing a RESET notification up the stack to the sctp_recvmsg() user.
Let's handle this by marking the HNB as unregistered, since most
probably a HNB Register Req comes next as the upper layer state is
considered lost.

Depends: libosmo-netif.git Change-Id I0ee94846a15a23950b9d70eaaef1251267296bdd
Related: SYS#6113
Change-Id: Ib22881b1a34b1c3dd350912b3de8904917cf34ef
2022-09-19 14:58:11 +02:00
Pau Espin Pedrol
d046306b63 Change log level about conn becoming closed to NOTICE
Change-Id: I8973990e2cc435422e62dd2a38192e7a6da4a716
2022-09-16 10:37:00 +00:00
Neels Janosch Hofmeyr
6bcd615d10 do not depend on libosmo-gtlv
Depending on libosmo-pfcp implies libosmo-gtlv, no need to explicitly
add this dependency.

Change-Id: I39eb59520231bcfed724060d3fda4ba919f2199d
2022-09-14 13:27:27 +02:00
Pau Espin Pedrol
f9825cbd4a Improve logging around hnb_context and sctp conn lifecycle
Change-Id: I44c79d86924ead84246b3d4937a6becae5d29185
2022-09-14 12:16:38 +02:00
Pau Espin Pedrol
930ed702b6 hnb_context_release(): Make sure assigned conn is freed
Otherwise, some paths calling hnb_context_release() (like failing to
transmit HNB-REGISTER-REJECT) would end up with a conn object alive with
no assigned hnb_context, which is something not wanted.

This way an alive conn object always has an associated hnb_context, and
they are only disassociated during synchronous release path.

Related: OS#5676
Change-Id: I44fea7ec74f14e0458861c92da4acf685ff695c1
2022-09-14 12:16:18 +02:00
Harald Welte
96c712570a Don't permit anything but HNB (de)registration until HNB is registered
UE registration or other HNBAP procedures should only happen once the
HNB is registered.

Change-Id: Iaa62ce89f4ffbff868309bfb8b1df7ebcca5c44a
2022-09-13 13:00:01 +02:00
Harald Welte
fe7c34737d Don't process RUA messages if HNB is not registered
Related: OS#5676
Change-Id: I85442e8adfefadc3bf3ed795eaef7677eb0b36e9
2022-09-13 13:00:01 +02:00
Harald Welte
d3382ae952 hnbgw_rx_hnb_deregister: Don't call hnb_context_release()
Don't release the HNB context as there's plenty of code that
assumes there's always a HNB context associated with a SCTP connection.

Instead, simply unset the hnb_registered flag in the context when
processing a HNB_DE-REGISTER.

Related: OS#5676

Change-Id: Id5c4f5c900ea049f54afbf58edb84b4dc00b1dcb
2022-09-13 12:59:57 +02:00
Harald Welte
c971c657c5 Abort if processing SCTP connection without HNB context
It was observed that under some circumstances (after HNBAP
HNB-De-Register) we end up crashing because a connection has no HNB
assigned to it. Let's explicitly assert if that happens, in order
clarify and avoid same sort of thing happening without clear view on
what's going on.
The issue will be fixed in a follow-up patch.

Closes: OS#5676
Change-Id: I1eedab6f3ac974e942b02eaae41556f87dd8b6ba
2022-09-13 11:31:46 +02:00
Pau Espin Pedrol
eadf523393 hnbgw: Log new SCTP HNB connections
Change-Id: I07b98ff4c3199eeab11a8c1cfd9ce44ab99bca85
2022-09-13 11:31:46 +02:00
Pau Espin Pedrol
419e832473 cosmetic: Fix typo in log and whitespace
Change-Id: Ie2be6937bb0f44ea66397c905c5d380caa2d4cef
2022-09-13 11:31:40 +02:00
Harald Welte
d28771a1b5 cosmetic: Fix typos
it's "successful", not" "successfull".

Change-Id: Ic421ed6835a9ffca6af34779f0ea648bb12e2fe1
2022-09-12 08:15:58 +02:00
Max
0c5878fa9d Set working directory in systemd service file
By default systemd will execute service with root directory (or home directory for user instance) which might result in
attempts to create files in unexpected place. Let's set it to 'osmocom' subdir of state directory (/var/lib for system instance) instead.

Related: OS#4821
Change-Id: I3133dc7a687550901841755461db6020ee96d6b1
2022-08-30 19:48:37 +07:00
Neels Janosch Hofmeyr
9ea431123d fix regression: in RUA, do PFCP only when enabled
Tested in ttcn3, by test cases not ready for submission (would require
enabling/disabling PFCP while osmo-hnbgw is running).
ttcn3 tests in I511e758807e0512c18f3f9e0a8c4699b9a3f5992

Related: SYS#6093
Change-Id: I39b9632f8524a9f3455c1a2d7611bfe8ba07c2fd
2022-08-29 16:57:28 +02:00
Neels Hofmeyr
b08b19c990 debian,RPM: package with PFCP support
- depend on libosmo-pfcp
- configure --enable-pfcp

Related: SYS#5895
Change-Id: I54dfe600d45541fecbb4c05bf75f147934c230f0
2022-08-27 16:02:51 +00:00
Harald Welte
791babf40e packate the new osmo-hnbgw-pfcp.cfg example config file
In I62c4935bcc7f684bfe850f88f1b80e8970e0e098 we added a new example
config file, but didn't actually package it in dpkg or rpm packages
yet.  This also lead to package build failures like:

[   39s] RPM build errors:
[   39s]     Installed (but unpackaged) file(s) found:
[   39s]    /usr/share/doc/osmo-hnbgw/examples/osmo-hnbgw/osmo-hnbgw-pfcp.cfg

Change-Id: I7c5c346f67f003b2cc5d74e812441c3704b133ef
2022-08-26 11:53:33 +00:00
Daniel Willmann
d129e0c86e hnbgw_hnbap: Fix memory leaks in HNBAP handling
* Use osmo_stream closed_cb to call hnb_context_release() in all cases
* Also call hnbap_free_hnbregisterrequesties() when sending hnb register
  reject

Related: OS#5656
Change-Id: I3ba02b0939413c67bc8088ea1a8f2252fc2bda31
2022-08-23 18:15:02 +02:00
Daniel Willmann
2dfeb1e218 Install show talloc-context VTY commands
Related: OS#5656
Change-Id: Ia4b0023028405ce065f618f536c92ea2bcd0ce15
2022-08-23 17:51:51 +02:00
Oliver Smith
0a5e2b3643 rpm spec: add osmo-hnbgw-pfcp.cfg
Fix for:
  Installed (but unpackaged) file(s) found:
  /usr/share/doc/osmo-hnbgw/examples/osmo-hnbgw/osmo-hnbgw-pfcp.cfg

Related: OS#5654
Change-Id: Ia05323a627719a7fff7c232aa3e5cc8766f9a8e1
2022-08-22 12:04:11 +02:00
Neels Hofmeyr
9f654da0aa example cfg: tweak logging
Change-Id: Idaf75d64d28264a29b67439c6bbcae8ad6981f7e
2022-08-18 16:17:53 +02:00
Neels Hofmeyr
f7df74fc48 manual: explain the PFCP port
Change-Id: I383befb226caa49e4a2577657806aef1ee11faa3
2022-08-18 16:17:53 +02:00
Neels Hofmeyr
a0d528ef31 manual: update IuCS/IuPS protocol stack chart
We use SCCP/M3UA now, not SUA.

Change-Id: I4496a6ffdda511875208bebbe68dbc9e69541fc0
2022-08-18 16:17:53 +02:00
Neels Hofmeyr
d8de11b430 manual: update overview chart with PFCP
Change from ascii art to the dotty chart, taken from the osmocom.org
wiki. No need to keep a separate representation here.

Change-Id: Ifd8843aeb8ff28fec53323c8fb37b10d4d1f2f9b
2022-08-18 16:17:53 +02:00
Neels Hofmeyr
598ebb6943 manual: add missing bit on the MGCP port
Change-Id: Ic18180793f0c1497b020e5f4a8cd34d6b519b85f
2022-08-18 16:15:36 +02:00
Neels Hofmeyr
1ce5148996 add example osmo-hnbgw-pfcp.cfg
Change-Id: I62c4935bcc7f684bfe850f88f1b80e8970e0e098
2022-08-18 16:15:36 +02:00
Neels Hofmeyr
941008e785 ps_rab_fsm: check use cb success
Related: CID#275413 CID#275410 CID#275409
Related: SYS#5895
Change-Id: Idcb3d6796326b059280b0e552eb36067ba15b3ac
2022-08-17 14:33:33 +00:00
Neels Hofmeyr
ca2c5b9067 optimize: decode PS msgs only when PFCP is enabled
For the benefit of skipping decoding of all PS RANAP and RUA messages,
introduce code dup: decode CS and PS separately.

Related: SYS#5895
Change-Id: Ifb57bad6a0d5ff263e4c6c3facc51620e110e7d2
2022-08-17 14:33:33 +00:00
Vadim Yanitskiy
c5b7106f8d configure.ac: do not require unused dlopen
Change-Id: Ia23cee40fd63f708e7a7391417ec8604f51a20a7
2022-08-16 23:44:08 +07:00
Neels Hofmeyr
e6201765cf build: add --enable-pfcp, make PFCP dep optional
Related: SYS#5895
Change-Id: I6d50c60bccda767910217243bdfb4a6fad1e39c1
2022-08-09 17:57:43 +02:00
Neels Hofmeyr
44f5a336a8 reduce code dup in handle_cn_data_ind()
Change-Id: I4bca25d1643693cf3a9d3c49f35b29ff1dce0859
2022-08-08 20:20:34 +00:00
Neels Hofmeyr
1496498713 add ps_rab_ass FSM to map GTP via UPF
Related: SYS#5895
Depends: If80c35c6a942bf9593781b5a6bc28ba37323ce5e (libosmo-pfcp)
Change-Id: Ic9bc30f322c4c6c6e82462d1da50cb15b336c63a
2022-08-08 20:20:34 +00:00
Vadim Yanitskiy
b7ff03e5be tests/ranap_rab_ass: fix potential NULL pointer dereferences
Change-Id: I16fea7b2a8cb1d693e01c91d7633550e2e599ceb
Related: CID#275345
2022-07-30 05:48:41 +07:00
Neels Hofmeyr
223aeda282 ranap_rab_ass_req_encode(): return msgb
ranap_rab_ass_req_encode() forms a msgb, then copies the data to a
buffer provided by the caller. Instead, just return the msgb. This
removes one unnecessary memcpy() and simplifies some code.

In ranap_rab_ass_test.c, actually ensure the correct size of the
returned data. See also the fix of expected test data in patch
Ifb98a52e56db1227a834c0d7b7a260314d9f547e

Related: SYS#5895
Change-Id: I85e715326e1d8f4f301f82f78da109f1a7a92f30
2022-07-27 15:45:18 +02:00
Neels Hofmeyr
a82c8d2425 fix test_ranap_rab_ass_resp_decode_encode
There is an extra zero octet at the end of the test data, which does not
get encoded back. The test currently does not detect this, but will in
upcoming patch I85e715326e1d8f4f301f82f78da109f1a7a92f30.

Related: SYS#5895
Change-Id: Ifb98a52e56db1227a834c0d7b7a260314d9f547e
2022-07-27 15:45:16 +02:00
Neels Hofmeyr
05aaccc42d mgw_fsm: move MGCP timeout to mgw_fsm_T_defs
For the tdefs used by libosmo-mgcp-client, passed via
osmo_mgcpc_ep_alloc(), do not use the separate mgw_tdefs. Instead, move
X2427 to mgw_fsm_T_defs. This makes X2427 VTY configurable.

Related: SYS#5895
Change-Id: I2aa67121c20dc3da5fd937a02b6747468622f317
2022-07-27 15:44:51 +02:00
Pau Espin Pedrol
7eb89ec9fe hnbgw_cn.c: Guard against null ss7 ptr during init
Fixes: Coverity CID#272989
Change-Id: Ic787d52a3c2e73ac272735a33b20bb94e29fad95
2022-06-29 18:50:30 +02:00
Pau Espin Pedrol
44dfe698fa Bump version: 1.2.1.25-7893-dirty → 1.3.0
Change-Id: I5283bfcdcee218d2db25cd10b9a17ffe2129efb6
2022-06-29 12:42:35 +02:00
Harald Welte
7893028ef6 update URLs (git -> https; gitea)
Change-Id: Ic9da2fbbc473b1ac5bc4e29c8dd77533455930d4
2022-06-18 14:02:41 +02:00
Pau Espin Pedrol
62fb1dea61 mgw_fsm: Simplify cleanup paths
Let's have a unified way of freeing the FSM instance once it was
allocated, otherwise it's far more difficult to understand and maintain.

Change-Id: I8883e737fa112cff57834abae7ef272388a54edb
2022-06-15 11:55:21 +02:00
Pau Espin Pedrol
304f7646c9 mgw_fsm: Fix error path accessing uninitialized fsm ptr
The error handling of the error path was wrong. Let's remove the "fi"
variable to avoid more of such errors. Furthermore, add an assert to
clarify for the reader that the map->mgw_fi will be freed before
allocating a new FSM instance below.

Change-Id: I9d3bca552bfa77f5e18f75bedad8d422f74df1f8
2022-06-14 18:41:48 +02:00
Pau Espin Pedrol
87e03208af mgw_fsm: Change macro to not use local variables implicitly
This is misleading for readers since it may access variables which may
be uninitialized or in a wrong state. Furthermore, we want to pass some
other variable name in a follow up patch.

This effectively allows the compiler to warn about uninitialized used of
a fi var in line 661.

Change-Id: Id694f51bb2918fd27da87b3f4a905727cd7f5de6
2022-06-14 18:40:11 +02:00
Pau Espin Pedrol
de8b170d1a cosmetic: mgw_fsm: Fix typo in log
Change-Id: I80aa61a288ab37c51510af67c784498f5949fc50
2022-06-14 18:07:52 +02:00
Pau Espin Pedrol
1d1839a34b mgw_fsm: Improve logging
Change-Id: I14785b6bc798c3bae8c552bccb55ca4fa9f2f416
2022-06-14 18:07:32 +02:00
Pau Espin Pedrol
8c7aae87b0 mgw_fsm: Mark structs as static const
Change-Id: Ie62f28587c08296429c0dabda7b6add67ffa010c
2022-06-14 17:46:56 +02:00
Neels Hofmeyr
ff2fbdf998 fix segfault in error handling for mgw_fi == NULL
In mgw_fsm_handle_rab_ass_resp(), a NULL mgw_fi is handled as error,
but the error handling fails to return. The function continues to
dereference mgw_fi. Add missing return.

Related: SYS#5995
Change-Id: I3e98dc3a00145ec1f71c678bbf45debfd4276237
2022-06-10 11:40:33 +02:00
Neels Hofmeyr
2c91bd66a1 add option to send SCCP CR without payload
It is reported that a third-party SGSN is rejecting SCCP CR when the
SCCP message part exceeds a certain length. The solution is to first
send an SCCP CR without payload, and send the payload in a DT later.

Add config option

  hnbgw
   sccp cr max-payload-len <0-999999>

If the RANAP payload surpasses the given length, osmo-hnbgw will first
send an SCCP CR without payload, cache the RANAP payload, and put that
in an SCCP DT once the SCCP CC is received.

The original idea was to limit the size of the entire SCCP part of the
message, but I'm currently not sure how to determine that without
copying much of the osmo_sccp code. I figured using a limit on the RANAP
payload is sufficient. To avoid the error with above third-party SGSN,
the easy solution is to set max-payload-len to 0, so that we always get
a separate SCCP CR without payload.

Related: SYS#5968
Related: I827e081eaacfb8e76684ed1560603e6c8f896c38 (osmo-ttcn3-hacks)
Change-Id: If0c5c0a76e5230bf22871f527dcb2dbdf34d7328
2022-06-07 22:51:26 +02:00
Neels Hofmeyr
afbcae6366 tweak comments in rua_to_scu()
Change-Id: I227a5e6b869da453fa72ff0eebaa1e95aa9625e6
2022-06-07 22:51:25 +02:00
Neels Hofmeyr
da9d08c94e allow calling rua_to_scu() without data
There can be SCCP primitives without payload data, e.g. an "empty" SCCP
Connection Request.

Patch 'mgw_fsm: add MGW support to osmo-hnbgw' added RANAP message
decoding that lacks a guard against NULL data. Fix that: do not try to
decode NULL data.

Related: SYS#5968
Change-Id: Id755e769e82ace7203460ea1b3c847c2c90d41bf
2022-06-07 22:50:37 +02:00
Neels Hofmeyr
0ca9567fb2 use osmo_select_main_ctx(), tweak log in handle_cn_conn_conf()
Upcoming patch adds to this function. Let me first combine those four
LOGP() to a single one, use proper osmo_sccp_addr_to_str_c(OTC_SELECT).

To be able to use OTC_SELECT, switch hnbgw.c to osmo_select_main_ctx().

Related: SYS#5968
Change-Id: I1e0ea0a883e8cf65e6cfb45ed9b6f3d8fb7c59eb
2022-06-07 18:09:19 +02:00
Philipp Maier
be9ed71631 ranap_rab_ass: check for more than one RAB assignment req
The spec permits RAB AssignmentRequests with multiple RABs at a time.
Even though one voice call is assigned only one RAB in practice. Since
the current FSM implementation only supports a 1:1 scenario, lets check
if the MSC really assigns only one RAB and block RAB Assignments that do
not fit in this scheme.

Change-Id: I0f1d868fd0b4dc413533d6fcc5482862825181be
Related: OS#5152
2022-02-28 10:22:16 +01:00
Philipp Maier
d1f4b9b9a1 mgw_fsm: release call when FSM is not created
While the FSM is created the RAB Assignment Requests is checked and
parsed. In case of failure the context is freed, but the CN is not
informed about the problem. The RAB AssignmentRequest will then most
likely time out. However, lets make sure the call is released by re
requesting an IU Release.

Change-Id: I1904f7e95d86bbcecee14f8721bd4075d0e33ab4
Related: OS#5152
2022-02-25 15:12:18 +01:00
Philipp Maier
b5508f98ef osmo-hnbgw.cfg: use local port 2729 as default for MGCP client
There are several osmo processes that talk to osmo-mgw via
osmo-mgcp-client. The sample config for osmo-bsc suggest 2727 and the
sample config for osmo-msc suggests 2728 as local port default. To make
it less likely for users to get port collisions whlie setting up their
networks we should use a different port for osmo-hnbgw. Lets use 2729.

Change-Id: I55179c2bff3e6ef0e54fee6b1b90fc76f541e58e
2022-02-25 15:12:18 +01:00
Philipp Maier
9e46544486 running.adoc: explain MGW configuration
OsmoHNBGW now requires a co-located OsmoMGW instance. Lets add add some
info on how to configure it.

Change-Id: Id47f4f365cee78ce28d1534c4e3e98a59bdb0621
Related: OS#5152
2022-02-24 11:29:09 +01:00
Philipp Maier
7dd3a61b57 overview.adoc: update network diagram
The network diagram in the manual lacks the recently added
co located MGW.

Change-Id: Ica1782a407f6b944aa26748b54055168f5d250c3
Related: OS#5152
2022-02-24 11:28:50 +01:00
Philipp Maier
81f1751896 mgw_fsm: add MGW support to osmo-hnbgw
osmo-hnbgw lacks support for an co-located media gateway. This makes it
virtually impossible to isolate the HNB from the core network properly.

Lets add MGCP support to osmo-hnbgw so that it can control a co-located
media gateway to relay the RTP streams between HNB and core network.

Change-Id: Ib9b62e0145184b91c56ce5d8870760bfa49cc5a4
Related: OS#5152
2022-02-24 10:51:30 +01:00
Philipp Maier
e7c66defc2 ranap_rab_ass_test: cosmetic: correct test function names
The test function naming follows an older scheme, lets use the current
scheme.

Change-Id: Ib9db9d86e01551c8d9d8f8c4933025ca20ce5624
Related: OS#5152
2022-02-23 15:50:43 +01:00
Philipp Maier
f5742a3bed ranap_rab_ass: add function to check if RAB is in ReleaseList
A RANAP RAB-AssignmentRelease may contain a ReleaseList. In order to
detect that a RAB is about to be released we need to be able to check if
the RAB we are dealing with is contained in such a ReleaseList.

Change-Id: I5b67cc2d35d11de7a09e66c181a1fdd5a58c75bb
Related: OS#5152
2022-02-23 15:50:43 +01:00
Philipp Maier
efe4850e75 ranap_rab_ass: add function to check if RAB is in FailureList
A RANAP RAB-AssignmentResponse may contain a FailedList. In order to
detect that a RAB Assignment failed at the HNB we need to be able to
check if the RAB we are dealing with is contained an such a FailedList.

Change-Id: I4319f7caa45ea758ccd792cc8570521df075cf45
Related: OS#5152
2022-02-23 15:38:17 +01:00
Philipp Maier
0c465b0f68 ranap_rab_ass: ensure specific rab_id
The parser functions currently ignore the rab_id. An exception is
ranap_rab_ass_req_ies_extract_inet_addr, which extracts the rab_id
of the first RAB. To make the handling of the RAB assignment parsing
more robust lets add a rab_id parameter to the other functions. This
parameter can then be used to ensure thet the correct RAB is handled.

Change-Id: I2259ffce9f4b508c555d60618c5983ac6294e0ae
Related: OS#5152
2022-02-11 11:00:35 +01:00
Philipp Maier
7daa502a2d ranap_rab_ass: add decoder and rewrite functions for RAB-AssignmentRequest/Response
The RANAP RAB AssignmentRequest and AssignmentResponse contains the
IP-Address and the IP-Port for the RTP voice stream. In the comming MGCP
implementation we will have to extract and replace this information.
Lets add functions that do that in a convinient way.

Change-Id: I58b542bf23ff5e1db2ccf6833fec91d9ba332837
Related: OS#5152
2022-02-02 10:51:38 +01:00
Philipp Maier
dddafa60ea hbgw_hnbap: use osmo_plmn_from_bcd instead of gsm48_mcc_mnc_from_bcd
The function gsm48_mcc_mnc_from_bcd is deprecated. Lets use
osmo_plmn_from_bcd instead.

Change-Id: I01406a4cf86d4f3ed162c9df55880941e54d654e
2022-01-14 17:31:25 +01:00
Philipp Maier
60008b1457 hnbgw_cn.c: fix sourcecode formatting
Change-Id: I50ec3a4a5c2f65c85adc177f83123991f949e9cf
2022-01-12 16:59:45 +01:00
Pau Espin Pedrol
dd29038a8f Bump version: 1.2.0.1-6d8e → 1.2.1
Change-Id: I99981bf8c9a340accc80623b56be5de58626648a
2022-01-11 18:55:50 +01:00
Pau Espin Pedrol
6d8e37f610 Do not turn some compiler warnings into errors by default
We build with --enable-werror during development and in CI. If the code
is built with a different compiler that throws additional warnings, it
should not stop the build.

This patch also effectively removes dependency on autoconf-archive.

Related: OS#5289
Related: SYS#5789
Change-Id: I7512a4230a5bbba6c67172c2572c98b9ab20c923
2022-01-11 18:30:13 +01:00
110 changed files with 16108 additions and 2116 deletions

2
.gitignore vendored
View File

@@ -2,6 +2,7 @@ hnbgwdebian/*.log
*.o
*.lo
*.a
*.la
.deps
Makefile
Makefile.in
@@ -67,6 +68,7 @@ doc/manuals/*__*.png
doc/manuals/*.check
doc/manuals/generated/
doc/manuals/osmohnbgw-usermanual.xml
doc/manuals/vty/hnbgw_vty_reference.xml
doc/manuals/common
doc/manuals/build

View File

@@ -19,7 +19,6 @@ SUBDIRS = \
BUILT_SOURCES = $(top_srcdir)/.version
EXTRA_DIST = \
.version \
contrib/osmo-hnbgw.spec.in \
debian \
git-version-gen \
osmoappdesc.py \

View File

@@ -19,9 +19,9 @@ GIT Repository
You can clone from the official osmo-hnbgw.git repository using
git clone git://git.osmocom.org/osmo-hnbgw.git
git clone https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw
There is a cgit interface at https://git.osmocom.org/osmo-hnbgw/
There is a web interface at https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw
Documentation
-------------

View File

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

View File

@@ -36,14 +36,7 @@ if test "x$PKG_CONFIG_INSTALLED" = "xno"; then
fi
PKG_PROG_PKG_CONFIG([0.20])
dnl check for AX_CHECK_COMPILE_FLAG
m4_ifdef([AX_CHECK_COMPILE_FLAG], [], [
AC_MSG_ERROR([Please install autoconf-archive; re-run 'autoreconf -fi' for it to take effect.])
])
dnl checks for libraries
AC_SEARCH_LIBS([dlopen], [dl dld], [LIBRARY_DL="$LIBS";LIBS=""])
AC_SUBST(LIBRARY_DL)
old_LIBS=$LIBS
AC_SEARCH_LIBS([sctp_recvmsg], [sctp], [
AC_DEFINE(HAVE_LIBSCTP, 1, [Define 1 to enable SCTP support])
@@ -56,16 +49,36 @@ AC_SEARCH_LIBS([sctp_recvmsg], [sctp], [
LIBS=$old_LIBS
PKG_CHECK_MODULES(LIBASN1C, libasn1c >= 0.9.30)
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.6.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.6.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.6.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.6.0)
PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 1.1.0)
PKG_CHECK_MODULES(LIBOSMOSIGTRAN, libosmo-sigtran >= 1.5.0)
PKG_CHECK_MODULES(LIBOSMORUA, libosmo-rua >= 1.1.0)
PKG_CHECK_MODULES(LIBOSMORANAP, libosmo-ranap >= 1.1.0)
PKG_CHECK_MODULES(LIBOSMOHNBAP, libosmo-hnbap >= 1.1.0)
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.11.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.11.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.11.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.11.0)
PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 1.6.0)
PKG_CHECK_MODULES(LIBOSMOSIGTRAN, libosmo-sigtran >= 2.1.0)
PKG_CHECK_MODULES(LIBOSMORUA, libosmo-rua >= 1.7.0)
PKG_CHECK_MODULES(LIBOSMORANAP, libosmo-ranap >= 1.7.0)
PKG_CHECK_MODULES(LIBOSMOHNBAP, libosmo-hnbap >= 1.7.0)
PKG_CHECK_MODULES(LIBOSMOMGCPCLIENT, libosmo-mgcp-client >= 1.14.0)
# Enable PFCP support for GTP tunnel mapping via UPF
AC_ARG_ENABLE([pfcp], [AS_HELP_STRING([--enable-pfcp], [Build with PFCP support, for GTP tunnel mapping via UPF])],
[osmo_ac_pfcp="$enableval"],[osmo_ac_pfcp="no"])
if test "x$osmo_ac_pfcp" = "xyes" ; then
PKG_CHECK_MODULES(LIBOSMOPFCP, libosmo-pfcp >= 0.5.0)
AC_DEFINE(ENABLE_PFCP, 1, [Define to build with PFCP support])
fi
AM_CONDITIONAL(ENABLE_PFCP, test "x$osmo_ac_pfcp" = "xyes")
AC_SUBST(osmo_ac_pfcp)
# Enable libnftables support for traffic counters using nft
AC_ARG_ENABLE([nftables], [AS_HELP_STRING([--enable-nftables], [Build with libnftables support, for traffic counters using nft])],
[osmo_ac_nftables="$enableval"],[osmo_ac_nftables="no"])
if test "x$osmo_ac_nftables" = "xyes" ; then
PKG_CHECK_MODULES(LIBNFTABLES, libnftables >= 1.0.2)
AC_DEFINE(ENABLE_NFTABLES, 1, [Define to build with libnftables support])
fi
AM_CONDITIONAL(ENABLE_NFTABLES, test "x$osmo_ac_nftables" = "xyes")
AC_SUBST(osmo_ac_nftables)
dnl checks for header files
AC_HEADER_STDC
@@ -113,13 +126,6 @@ AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])],
CFLAGS="$saved_CFLAGS"
AC_SUBST(SYMBOL_VISIBILITY)
AX_CHECK_COMPILE_FLAG([-Werror=implicit], [CFLAGS="$CFLAGS -Werror=implicit"])
AX_CHECK_COMPILE_FLAG([-Werror=maybe-uninitialized], [CFLAGS="$CFLAGS -Werror=maybe-uninitialized"])
AX_CHECK_COMPILE_FLAG([-Werror=memset-transposed-args], [CFLAGS="$CFLAGS -Werror=memset-transposed-args"])
AX_CHECK_COMPILE_FLAG([-Wnull-dereference], [CFLAGS="$CFLAGS -Wnull-dereference"])
AX_CHECK_COMPILE_FLAG([-Werror=sizeof-array-argument], [CFLAGS="$CFLAGS -Werror=sizeof-array-argument"])
AX_CHECK_COMPILE_FLAG([-Werror=sizeof-pointer-memaccess], [CFLAGS="$CFLAGS -Werror=sizeof-pointer-memaccess"])
# Coverage build taken from WebKit's configure.in
AC_MSG_CHECKING([whether to enable code coverage support])
AC_ARG_ENABLE(coverage,
@@ -154,7 +160,7 @@ if test "x$enable_ext_tests" = "xyes" ; then
fi
AC_CHECK_PROG(OSMOTESTEXT_CHECK,osmotestvty.py,yes)
if test "x$OSMOTESTEXT_CHECK" != "xyes" ; then
AC_MSG_ERROR([Please install git://osmocom.org/python/osmo-python-tests to run the VTY/CTRL tests.])
AC_MSG_ERROR([Please install https://gitea.osmocom.org/cellular-infrastructure/osmo-python-tests to run the VTY/CTRL tests.])
fi
fi
AC_MSG_CHECKING([whether to enable VTY/CTRL tests])
@@ -233,10 +239,12 @@ AC_OUTPUT(
src/osmo-hnbgw/Makefile
tests/Makefile
tests/atlocal
tests/ranap_rab_ass/Makefile
tests/umts_cell_id/Makefile
doc/Makefile
doc/examples/Makefile
doc/manuals/Makefile
doc/charts/Makefile
contrib/Makefile
contrib/systemd/Makefile
contrib/osmo-hnbgw.spec
Makefile)

View File

@@ -2,9 +2,13 @@
# jenkins build helper script for osmo-hnbgw. This is how we build on jenkins.osmocom.org
#
# environment variables:
# * PFCP: configure PFCP support if set to "1" (default)
# * WITH_MANUALS: build manual PDFs if set to "1"
# * NFTABLES: configure nftables support if set to "1" (default)
# * PUBLISH: upload manuals after building if set to "1" (ignored without WITH_MANUALS = "1")
#
PFCP=${PFCP:-1}
NFTABLES=${NFTABLES:-1}
if ! [ -x "$(command -v osmo-build-dep.sh)" ]; then
echo "Error: We need to have scripts/osmo-deps.sh from http://git.osmocom.org/osmo-ci/ in PATH !"
@@ -30,16 +34,24 @@ export LD_LIBRARY_PATH="$inst/lib"
export PATH="$inst/bin:$PATH"
osmo-build-dep.sh libosmocore "" --disable-doxygen
osmo-build-dep.sh libosmo-netif "" --disable-doxygen
osmo-build-dep.sh libosmo-abis
osmo-build-dep.sh libosmo-netif
osmo-build-dep.sh libosmo-sccp
osmo-build-dep.sh libosmo-sigtran "" --disable-doxygen
osmo-build-dep.sh libasn1c
osmo-build-dep.sh osmo-iuh
osmo-build-dep.sh osmo-mgw
# Additional configure options and depends
CONFIG=""
if [ "$PFCP" = "1" ]; then
osmo-build-dep.sh libosmo-pfcp
CONFIG="$CONFIG --enable-pfcp"
fi
if [ "$NFTABLES" = "1" ]; then
CONFIG="$CONFIG --enable-nftables"
fi
if [ "$WITH_MANUALS" = "1" ]; then
CONFIG="--enable-manuals"
CONFIG="$CONFIG --enable-manuals"
fi
set +x
@@ -52,7 +64,7 @@ set -x
cd "$base"
autoreconf --install --force
./configure --enable-sanitize --enable-external-tests $CONFIG
./configure --enable-sanitize --enable-external-tests --enable-werror $CONFIG
$MAKE $PARALLEL_MAKE
LD_LIBRARY_PATH="$inst/lib" $MAKE check \
|| cat-testlogs.sh

View File

@@ -1,97 +0,0 @@
#
# spec file for package osmo-hnbgw
#
# Copyright (c) 2017, Martin Hauke <mardnh@gmx.de>
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
## Disable LTO for now since it breaks compilation of the tests
## https://osmocom.org/issues/4113
%define _lto_cflags %{nil}
Name: osmo-hnbgw
Version: @VERSION@
Release: 0
Summary: OsmoHNBGW: Osmocom's Base Station Controller for 2G CS mobile networks
License: AGPL-3.0-or-later AND GPL-2.0-or-later
Group: Hardware/Mobile
URL: https://osmocom.org/projects/osmohnbgw
Source: %{name}-%{version}.tar.xz
BuildRequires: autoconf-archive
BuildRequires: automake >= 1.9
BuildRequires: libtool >= 2
BuildRequires: lksctp-tools-devel
BuildRequires: pkgconfig >= 0.20
%if 0%{?suse_version}
BuildRequires: systemd-rpm-macros
%endif
BuildRequires: pkgconfig(libcrypto) >= 0.9.5
BuildRequires: pkgconfig(libosmo-netif) >= 1.1.0
BuildRequires: pkgconfig(libosmo-sigtran) >= 1.5.0
BuildRequires: pkgconfig(libosmoabis) >= 1.2.0
BuildRequires: pkgconfig(libosmotrau) >= 1.2.0
BuildRequires: pkgconfig(libosmocore) >= 1.6.0
BuildRequires: pkgconfig(libosmoctrl) >= 1.6.0
BuildRequires: pkgconfig(libosmogb) >= 1.6.0
BuildRequires: pkgconfig(libosmogsm) >= 1.6.0
BuildRequires: pkgconfig(libosmovty) >= 1.6.0
BuildRequires: pkgconfig(libosmo-hnbap) >= 1.1.0
BuildRequires: pkgconfig(libosmo-ranap) >= 1.1.0
BuildRequires: pkgconfig(libosmo-rua) >= 1.1.0
BuildRequires: pkgconfig(talloc)
BuildRequires: pkgconfig(libasn1c) >= 0.9.30
%{?systemd_requires}
%description
OsmoHNBGW: Osmocom's Home NodeB for 3G mobile networks.
%prep
%setup -q
%build
echo "%{version}" >.tarball-version
autoreconf -fi
%configure \
--docdir=%{_docdir}/%{name} \
--with-systemdsystemunitdir=%{_unitdir}
make %{?_smp_mflags}
%install
%make_install
%if 0%{?suse_version}
%preun
%service_del_preun %{name}.service
%postun
%service_del_postun %{name}.service
%pre
%service_add_pre %{name}.service
%post
%service_add_post %{name}.service
%endif
%check
make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
%files
%license COPYING
%doc AUTHORS README.md
%{_bindir}/osmo-hnbgw
%dir %{_docdir}/%{name}/examples
%dir %{_docdir}/%{name}/examples/osmo-hnbgw
%{_docdir}/%{name}/examples/osmo-hnbgw/osmo-hnbgw.cfg
%dir %{_sysconfdir}/osmocom
%config(noreplace) %{_sysconfdir}/osmocom/osmo-hnbgw.cfg
%{_unitdir}/%{name}.service
%changelog

View File

@@ -1,9 +1,16 @@
[Unit]
Description=Osmocom Home Nodeb Gateway (OsmoHNBGW)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
Restart=always
LimitNOFILE=65536
StateDirectory=osmocom
WorkingDirectory=%S/osmocom
User=osmocom
Group=osmocom
ExecStart=/usr/bin/osmo-hnbgw -c /etc/osmocom/osmo-hnbgw.cfg
RestartSec=2

381
debian/changelog vendored
View File

@@ -1,3 +1,384 @@
osmo-hnbgw (1.7.0) unstable; urgency=medium
[ Neels Janosch Hofmeyr ]
* drop config.vty tests from make check
* coverity CID#358071
* coverity CID#358072
* coverity CID#358070
* coverity CID#358069
* on RUA Connect failure, respond with RUA Disconnect
* on RUA DT for unknown context, respond with RUA Disconnect
[ Harald Welte ]
* hnb_persistent: Use incrementing counter for rate_ctr + stat_item index
* Revert "hnb_persistent: Use incrementing counter for rate_ctr + stat_item index"
[ Oliver Smith ]
* contrib/jenkins: libosmo-sccp -> libosmo-sigtran
[ Pau Espin Pedrol ]
* Log cn_disconnect.ind cause
* Fix SCCP RLSD not sent upon rx RUA Disconnect due to error condition
* ps_rab_ass_fsm: Fix uninitialized ptr access
* Add PFCP stats item group
* hnbgw: Avoid using struct osmo_pfcp_cp_peer fields directly
* hnbgw: Fix wrong map object retrieved from hashtable
* Use new libosmo-sigtran APIs to access osmo_ss7_instance
* jenkins.sh: libosmo-netif no longer depends on libosmo-abis
* jenkins.sh: No need to build libosmo-sigtran with doxygen
* map_sccp: Fix crash during ev MAP_SCCP_EV_RAN_DISC in st MAP_SCCP_ST_WAIT_CC
* peek_l3_ul_nas: Split parsing logic based on PS/CS ctx
* peek_l3_ul_nas: Improve RAU req parsing checks
* peek_l3_ul_nas: Improve GMM Attach Req parsing checks
* peek_l3_ul_nas: Parse PTMSI MI from GMM RAU req
-- Oliver Smith <osmith@sysmocom.de> Wed, 12 Feb 2025 14:37:35 +0100
osmo-hnbgw (1.6.0) unstable; urgency=medium
[ Pau Espin Pedrol ]
* hnbgw_cn: Remove assert hit due to wrong assumption
* Increase default X31 val from 5 to 15 seconds
* context_map_sccp: Fix assert hit due to missing ev handling
* tests/ranap_rab_ass: Test RAB-Ass.req with X.213 IPv4 address len=7
* mgw_fsm: Assume IuUP HNB IP addr = Iuh during MGCP CRCX on RAN side
* mgw_fsm: Modify RAB on HNB if IuUP local IP addr at MGW changes during MDCX
* hnbap: Avoid calling duplicate getpeername() for each registered HNB
[ Neels Janosch Hofmeyr ]
* X31: fix vty doc
* allow (second) CS RAB Assignment Request without RTP info
* systemd,manual: set LimitNOFILE=65536
* rua: validate correct RUA ctx state per RUA message type
* rua: move from ERROR to DEBUG log: stray RUA Disconnect
* pfcp: fix modification of wrong FAR ID
* tweak vty doc: "UDP port"
* add tests/pfcp_cfg.vty.with_pfcp
* pfcp: fix missing vty_write of pfcp local-port
* pfcp: implement sending Network Instance IEs
* fix null deref on hnb_context_release
* drop legacy hack: do not start MGW endp in loopback mode
* fixup: compilation error: unused var in map_rua_init_action()
* fix rate_ctr leak in hnb_persistent_free()
* per-HNB GTP-U traffic counters via nft
* add hnb_persistent hashtable: optimize lookup by cell id
* fix stat_item leak in hnb_persistent_free()
* use osmo_jhash for the hnb_persistent hashtable
* rename to umts_cell_id_to_str()
* umts_cell_id: add umts_cell_id_to_str_buf()
* add umts_cell_id_test.c
* 3-digit MNC: use osmo_plmn_id in struct umts_cell_id
* add LOG_HNBP() for hnb_persistent
* hnb_persistent: introduce disconnected timeout
* prevent use-after-free after FSM instance termination
* improve HNBAP error logging
* nft-kpi: log errors of counter retrieval
* nft-kpi: remove X34 drifting: adjust delay by elapsed time
* drop list of HNBAP UE Context
* dbg log: nft kpi: clarify nr of rate ctrs vs nr of hnbp
* fix MGCP compat with osmo-mgw <= 1.12.2: CRCX in recvonly
[ Andreas Eversberg ]
* Use uniform log format for default config files
[ Harald Welte ]
* Display RANAP state during 'show cnlink'
* Fix license headers: Should have been AGPLv3+, not GPLv2+
* [cosmetic] re-order hnbgw.c to group code in major blocks
* umts_cell_id_name: Use 3-digit MCC and 2/3-digit MNC based on VTY config
* osmo_hnbgw_main: Install our usual SIGUSR1/SIGUSR2/SIGABRT handlers
* Introduce umts_cell_id_from_str() as inverse of umts_cell_id_name()
* Introduce concept of per-HNB persistent data structure
* Set persistent->ctx pointer on HNB REGISTER REQ
* New per-hnb rate_ctr and stat_item groups; track uptime
* Various new per-hnb RANAP and RUA counters
* stats: Introduce CNLINK counter macros
* stats: Introduce CNPOOL counter macro
* stats: Introduce basic counters for RANAP unit-data from CN links
* cosmetic: Fix comment in mgw_fsm.c: One RAB per context_map, not hnb!
* cosmetic: talk about cs_rab_ass_* when working in CS domain
* cosmetic: Rename hnbgw_rx_ranap and friends to *_rx_ranap_udt_ul
* rename hnbgw_peek_l3 to hnbgw_peek_l3_ul as it's uplink only
* don't forward paging requests to HNB's not yet registered
* vty: Print the uptime during 'show hnb' output
* Introduce counter for per-hnb cumulative active CS RAB duration
* stats: Add per-hnb paging:{cs,ps}:attempted counter
* cosmetic: align downlink RANAP unitdata function names with uplink
* mgw_fsm: Add some OSMO_ASSERT() to ensure only CS maps passed
* RAB activation/modification/release statistics
* context_map_{rua,sccp}: Re-order to always process KPIs
* generalize hnbgw_tx_ue_register_rej_tmsi() to hnbgw_tx_ue_register_rej()
* HNBAP: Use proper cause values during HNB-REGISTER-REQ processing
* HNBAP: Send HNB-REGISTER-REJ on ASN.1 decoding error
* HNBAP: Always respond to UE-REGISTER-REQUEST
* HNBAP: Make sure to respond with correct "reject"
* HNBAP: Transmit ErrorIndication in more situations
* HNBAP: use GSM23003_IMSI_MAX_DIGITS instead of magic number
* HNBAP: Support IMSI identity type in hnbgw_tx_ue_register_rej()
* counters: Distinguish between normal and abnormal release cause
* KPI: Add initial set of DTAP message type rate counters
* kpi_ranap: Avoid null pointer de-ref during HNB shutdown
[ Oliver Smith ]
* gitignore: add *.la, hnbgw_vty_reference.xml
* debian: fix having no manuals in osmo-hnbgw-doc
* .deb/.rpm: various fixes related to non-root
* contrib: remove rpm spec file
* debian/postinst: add checks, be verbose
* contrib/jenkins: set --enable-werror
[ Max ]
* .deb/.rpm: add osmocom user during package install
-- Oliver Smith <osmith@sysmocom.de> Thu, 25 Jul 2024 10:05:58 +0200
osmo-hnbgw (1.5.0) unstable; urgency=medium
[ Neels Janosch Hofmeyr ]
* UE state leak: when HNB re-registers, discard previous UE state
* Deprecate 'sccp cr max-payload-len', remove SCCP CR limit code
* cosmetic: drop stray backslash
* drop obsolete fixme comment
* various comment tweaks
* log tweak
* cosmetic: rename context_map_deactivate
* fix deprecation: use ranap_cn_rx_co_decode2()
* rua_to_scu(): drop dead code for connection-less
* use RUA procedure code for rua_to_scu() arg
* cosmetic: regroup members of hnbgw_context_map
* add design charts for new context map FSMs
* context map: introduce RUA and SCCP FSMs to fix leaks
* log osmo_fsm timeouts
* sccp_sap_up(): ignore PCSTATE.ind
* fix regression: unbreak PFCP: PS RAB via UPF proxying
* tweak LOGHNB()
* debug log: log received RANAP message types on DCN, DHNB
* map_sccp: on timeout during WAIT_CC, send N-DISCONNECT to SCCP-SCOC
* release UE Contexts on HNB (Re-)Register
* release UE Contexts on SCTP_RESTART
* log: in new RUA,SCCP FSM IDs, indicate CS/PS
* vty: 'show ue': show which HNB the UE is registered on
* fix vty: show talloc-context all
* place asn1 context under hnb_ctx for better visibility
* fix asn1 leak in handle_cn_ranap()
* fix asn1 leak in error path of hnbgw_tx_ue_register_acc()
* fix asn1 leak in error path of hnbgw_tx_ue_register_acc_tmsi()
* fix asn1 leaks in ps_rab_ass_resp_send_if_ready()
* fix asn1 leak in hnbgw_rua_rx()
* less code dup in mem free of hnbgw_rx_ue_register_req()
* fix log msg typo 'Unsupportedccept'
* actually run vty-tests: VTY transcript tests
* actually run vty-tests: osmotestvty.py
* rab_ass_fsm.c: fix asn1 memleak when replacing GTP address
* ps_rab_ass_fsm.h: fix dup of FSM event enum
* drop empty hnbgw_rua_init()
* eliminate function hnb_contexts()
* vty: fix doc strings for 'show {hnb,ue}'
* actually run vty-tests: osmotestconfig.py
* drop ctrl_test_runner.py
* comment typo fix in ranap_rab_ass.c
* simplify: one g_hnbgw as global state and root ctx
* ranap_rab_ass_test.c: clarify talloc contexts
* move main() to separate file
* add non-installed libhnbgw.la for test linkage
* vty: make legacy 'hnbgw'/'sccp cr max...' fatal
* vty test: show missing write-back of 'rnc-id'
* fix missing write-back of rnc-id
* drop dead code: cnlink.T_RafC
* remove obsolete context_map_check_released() call
* unbloat: drop context_map_check_released()
* vty_go_parent(): remove legacy cruft
* immediately SCCP RLSD on HNB re-register
* use new osmo_sccp_instance_next_conn_id()
* add doc/examples/osmo-hnbgw/osmo-hnbgw-cs7.cfg
* add separate cfg file only for VTY tests
* add startup config tests
* charts: add sccp.dot
* cnpool: allow separate cs7 for IuPS and IuCS
* coverity: fix type of local var
* cnpool: split up context_map_find_or_create_by_rua_ctx_id()
* cnpool prep: add SCCP_EV_USER_ABORT
* cnpool: add multiple 'msc' and 'sgsn' cfg (use only the first)
* cnpool: examples and config tests: use 'msc 0'/'sgsn 0' syntax
* cnpool: make NRI mappings VTY configurable
* tdefs; combine timer groups 'ps' and 'cmap' to 'hnbgw'
* add hnbgw_decode_ranap_co()
* fixup for 'cnpool: split up context_map_find_...'
* startup config tests: show default 'msc 0', 'sgsn 0'
* cnpool: extract Mobile Identity from RANAP payload
* add rate_ctr infra; add rate_ctrs for cnpool
* add CTRL transcript tests for cnpool rate ctrs
* cnpool: select CN link from pool by NRI or round robin
* doc/examples/osmo-hnbgw/osmo-hnbgw-cnpool.cfg
* fix umts_cell_id_name(): show CID
* make public: umts_cell_id_name()
* tweak lots of logging
* ctrl test: also test msc 1
* cnpool: add context_map_cnlink_lost() handling
* add ranap_domain_name()
* detect in/active CN links by RANAP RESET
* use cnlink state in cnpool decisions
* cnpool: return Paging Resp to the exact CN link that Paged
* cfg: add 'hnbgw' / 'plmn MCC MNC'
* include Global RNC-ID in RESET
* include Global RNC-ID in RESET-ACK
* RUA: log tweak
* coverity: hnbgw_cn: avoid NULL deref in LOGP
[ Philipp Maier ]
* mgw_fsm: fix typo
* mgw_fsm: fix log line
* mgw_fsm: use __func__ to mention function name in log line
* mgw_fsm: refactor helper function handle_rab_release()
* ranap_rab_ass: do not free never allocated FieldItems
* ranap_rab_ass: be sure to initialize memory with 0
[ Vadim Yanitskiy ]
* tests: use check_PROGRAMS for tests, not noinst_PROGRAMS
* tests: use -no-install libtool flag to avoid ./lt-* scripts
* tests: make 'vty-test' target depend on osmo-hnbgw binary
* tests: do not depend on undefined $(BUILT_SOURCES)
* copyright: fix typo: sysmocom s/s.m.f.c./s.f.m.c./ GmbH
[ Oliver Smith ]
* hnbgw_mgw_setup: use mgcp_client_pool_empty()
* debian: set compat level to 10
* contrib/jenkins.sh: add PFCP variable
* hnbgw: put copyright into define
* systemd: depend on networking-online.target
[ Pau Espin Pedrol ]
* manuals: include osmo-gsm-manual's sigtran.adoc
* tests: Update *.vty after libosmo-sccp VTY improvements
* Use new mgcp_client_conf_alloc() API to alloc mgcp_client_conf
* Tear down call if local IuUP MGW address changed during MDCX
-- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 12 Sep 2023 17:18:43 +0200
osmo-hnbgw (1.4.0) unstable; urgency=medium
[ Pau Espin Pedrol ]
* hnbgw_cn.c: Guard against null ss7 ptr during init
* cosmetic: Fix typo in log and whitespace
* hnbgw: Log new SCTP HNB connections
* hnb_context_release(): Make sure assigned conn is freed
* Improve logging around hnb_context and sctp conn lifecycle
* Change log level about conn becoming closed to NOTICE
* hnbgw: Unregister HNB if SCTP link is restarted
* hnbgw: Fix recent regression not closing conn upon rx of SCTP_SHUTDOWN_EVENT
* hnbap: Accept duplicated HNB Register Request on same conn
* hnbap: Improve logging around HNBAP HNB Register Request
* Workaround bug where old hnb_context from same remote addr+port is kept
* Fix handling of sctp SCTP_SHUTDOWN_EVENT notification
* Close conn when receiving SCTP_ASSOC_CHANGE notification
* hnb_read_cb: use local var to reduce get_ofd() calls
* hnb_read_cb(): -EBADF must be returned if conn is freed to avoid use-after-free
* Clear SCTP tx queue upon SCTP RESTART notification
* Makefile.am: Drop duplicated LIBOSMOMGCPCLIENT_LIBS
* Introduce support for libosmo-mgcp-client MGW pooling
* doc: Include mgwpool.adoc from osmo-gsm-manuals
* vty: Fix timers not printed when dumping running-config
* hnbgw: Avoid allocating SCCP conn id >0x00fffffe
* context_map: Lower loglevel to INFO when deallocating context IDs
[ Neels Hofmeyr ]
* mgw_fsm: move MGCP timeout to mgw_fsm_T_defs
* fix test_ranap_rab_ass_resp_decode_encode
* ranap_rab_ass_req_encode(): return msgb
* add ps_rab_ass FSM to map GTP via UPF
* reduce code dup in handle_cn_data_ind()
* build: add --enable-pfcp, make PFCP dep optional
* optimize: decode PS msgs only when PFCP is enabled
* ps_rab_fsm: check use cb success
* add example osmo-hnbgw-pfcp.cfg
* manual: add missing bit on the MGCP port
* manual: update overview chart with PFCP
* manual: update IuCS/IuPS protocol stack chart
* manual: explain the PFCP port
* example cfg: tweak logging
* debian,RPM: package with PFCP support
[ Vadim Yanitskiy ]
* tests/ranap_rab_ass: fix potential NULL pointer dereferences
* configure.ac: do not require unused dlopen
[ Oliver Smith ]
* rpm spec: add osmo-hnbgw-pfcp.cfg
[ Daniel Willmann ]
* Install show talloc-context VTY commands
* hnbgw_hnbap: Fix memory leaks in HNBAP handling
[ Harald Welte ]
* packate the new osmo-hnbgw-pfcp.cfg example config file
* cosmetic: Fix typos
* Abort if processing SCTP connection without HNB context
* hnbgw_rx_hnb_deregister: Don't call hnb_context_release()
* Don't process RUA messages if HNB is not registered
* Don't permit anything but HNB (de)registration until HNB is registered
[ Neels Janosch Hofmeyr ]
* fix regression: in RUA, do PFCP only when enabled
* do not depend on libosmo-gtlv
* drop bogus error log 'no MGW fsm'
* fix segfault on MGCP timeout
* fix msgb leak for RANAP RAB Ass. Req.
* fix possible leak of ue_context on UE REGISTER error
* fix SCCP conn leak on non-graceful HNB shutdown
* coverity: hnbgw_rua.c: remove redundant check
[ Max ]
* Set working directory in systemd service file
* ctrl: take both address and port from vty config
[ arehbein ]
* osmo-hnbgw: Transition to use of 'telnet_init_default'
-- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 07 Feb 2023 18:05:46 +0100
osmo-hnbgw (1.3.0) unstable; urgency=medium
[ Philipp Maier ]
* hnbgw_cn.c: fix sourcecode formatting
* hbgw_hnbap: use osmo_plmn_from_bcd instead of gsm48_mcc_mnc_from_bcd
* ranap_rab_ass: add decoder and rewrite functions for RAB-AssignmentRequest/Response
* ranap_rab_ass: ensure specific rab_id
* ranap_rab_ass: add function to check if RAB is in FailureList
* ranap_rab_ass: add function to check if RAB is in ReleaseList
* ranap_rab_ass_test: cosmetic: correct test function names
* mgw_fsm: add MGW support to osmo-hnbgw
* overview.adoc: update network diagram
* running.adoc: explain MGW configuration
* osmo-hnbgw.cfg: use local port 2729 as default for MGCP client
* mgw_fsm: release call when FSM is not created
* ranap_rab_ass: check for more than one RAB assignment req
[ Neels Hofmeyr ]
* use osmo_select_main_ctx(), tweak log in handle_cn_conn_conf()
* allow calling rua_to_scu() without data
* tweak comments in rua_to_scu()
* add option to send SCCP CR without payload
* fix segfault in error handling for mgw_fi == NULL
[ Pau Espin Pedrol ]
* mgw_fsm: Mark structs as static const
* mgw_fsm: Improve logging
* cosmetic: mgw_fsm: Fix typo in log
* mgw_fsm: Change macro to not use local variables implicitly
* mgw_fsm: Fix error path accessing uninitialized fsm ptr
* mgw_fsm: Simplify cleanup paths
[ Harald Welte ]
* update URLs (git -> https; gitea)
-- Pau Espin Pedrol <pespin@sysmocom.de> Wed, 29 Jun 2022 12:42:35 +0200
osmo-hnbgw (1.2.1) unstable; urgency=medium
* Do not turn some compiler warnings into errors by default
-- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 11 Jan 2022 18:55:50 +0100
osmo-hnbgw (1.2.0) unstable; urgency=medium
* Initial structure + import code from osmo-iuh.git

2
debian/compat vendored
View File

@@ -1 +1 @@
9
10

26
debian/control vendored
View File

@@ -2,11 +2,10 @@ Source: osmo-hnbgw
Section: net
Priority: extra
Maintainer: Osmocom team <openbsc@lists.osmocom.org>
Build-Depends: debhelper (>=9),
Build-Depends: debhelper (>= 10),
dh-autoreconf,
autotools-dev,
autoconf,
autoconf-archive,
automake,
libtool,
pkg-config,
@@ -14,17 +13,20 @@ Build-Depends: debhelper (>=9),
libtalloc-dev,
libasn1c-dev (>= 0.9.30),
libsctp-dev,
libosmocore-dev (>= 1.6.0),
libosmo-sigtran-dev (>= 1.5.0),
libosmo-abis-dev (>= 1.2.0),
libosmo-netif-dev (>= 1.1.0),
libosmo-hnbap-dev (>= 1.1.0),
libosmo-ranap-dev (>= 1.1.0),
libosmo-rua-dev (>= 1.1.0),
osmo-gsm-manuals-dev (>= 1.2.0)
libosmocore-dev (>= 1.11.0),
libosmo-sigtran-dev (>= 2.1.0),
libosmo-abis-dev (>= 2.0.0),
libosmo-netif-dev (>= 1.6.0),
libosmo-mgcp-client-dev (>= 1.14.0),
libosmo-hnbap-dev (>= 1.7.0),
libosmo-ranap-dev (>= 1.7.0),
libosmo-rua-dev (>= 1.7.0),
libosmo-pfcp-dev (>= 0.5.0),
libnftables-dev,
osmo-gsm-manuals-dev (>= 1.6.0)
Standards-Version: 3.9.8
Vcs-Git: git://git.osmocom.org/osmo-hnbgw.git
Vcs-Browser: https://git.osmocom.org/osmo-hnbgw/
Vcs-Git: https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw
Vcs-Browser: https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw
Homepage: https://projects.osmocom.org/projects/osmo-hnbgw
Package: osmo-hnbgw

2
debian/copyright vendored
View File

@@ -1,6 +1,6 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: osmo-hnbgw
Source: git://git.osmocom.org/osmo-hnbgw
Source: https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw
Files: *
Copyright: 2021 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>

2
debian/osmo-hnbgw-doc.install vendored Normal file
View File

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

View File

@@ -2,3 +2,6 @@ etc/osmocom/osmo-hnbgw.cfg
lib/systemd/system/osmo-hnbgw.service
usr/bin/osmo-hnbgw
usr/share/doc/osmo-hnbgw/examples/osmo-hnbgw/osmo-hnbgw.cfg usr/share/doc/osmo-hnbgw/examples
usr/share/doc/osmo-hnbgw/examples/osmo-hnbgw/osmo-hnbgw-pfcp.cfg usr/share/doc/osmo-hnbgw/examples
usr/share/doc/osmo-hnbgw/examples/osmo-hnbgw/osmo-hnbgw-cs7.cfg usr/share/doc/osmo-hnbgw/examples
usr/share/doc/osmo-hnbgw/examples/osmo-hnbgw/osmo-hnbgw-cnpool.cfg usr/share/doc/osmo-hnbgw/examples

38
debian/postinst vendored Executable file
View File

@@ -0,0 +1,38 @@
#!/bin/sh -e
case "$1" in
configure)
# Create the osmocom group and user (if it doesn't exist yet)
if ! getent group osmocom >/dev/null; then
groupadd --system osmocom
fi
if ! getent passwd osmocom >/dev/null; then
useradd \
--system \
--gid osmocom \
--home-dir /var/lib/osmocom \
--shell /sbin/nologin \
--comment "Open Source Mobile Communications" \
osmocom
fi
# Fix permissions of previous (root-owned) install (OS#4107)
if dpkg --compare-versions "$2" le "1.6.0"; then
if [ -e /etc/osmocom/osmo-hnbgw.cfg ]; then
chown -v osmocom:osmocom /etc/osmocom/osmo-hnbgw.cfg
chmod -v 0660 /etc/osmocom/osmo-hnbgw.cfg
fi
if [ -d /etc/osmocom ]; then
chown -v root:osmocom /etc/osmocom
chmod -v 2775 /etc/osmocom
fi
mkdir -p /var/lib/osmocom
chown -R -v osmocom:osmocom /var/lib/osmocom
fi
;;
esac
# dh_installdeb(1) will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#

14
debian/rules vendored
View File

@@ -44,10 +44,18 @@
%:
dh $@ --with autoreconf
# debmake generated override targets
CONFIGURE_FLAGS += --with-systemdsystemunitdir=/lib/systemd/system --enable-manuals
# libnftables is too old in Debian 10 (OS#6425)
override_dh_auto_configure:
dh_auto_configure -- $(CONFIGURE_FLAGS)
CONFIGURE_FLAGS=" \
--enable-manuals \
--enable-pfcp \
--with-systemdsystemunitdir=/lib/systemd/system \
"; \
if pkg-config --exists libnftables --atleast-version=1.0.2; then \
CONFIGURE_FLAGS="$$CONFIGURE_FLAGS --enable-nftables"; \
fi; \
dh_auto_configure -- $$CONFIGURE_FLAGS
#
# Do not install libtool archive, python .pyc .pyo
#override_dh_install:

View File

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

20
doc/charts/Makefile.am Normal file
View File

@@ -0,0 +1,20 @@
msc: \
$(builddir)/hnbgw_context_map.png \
$(NULL)
dot: \
$(builddir)/hnbgw_context_map_fsm.png \
$(builddir)/sccp.png \
$(NULL)
$(builddir)/%.png: $(srcdir)/%.msc
mscgen -T png -o $@ $<
$(builddir)/%.png: $(srcdir)/%.dot
dot -Tpng $< > $@
.PHONY: poll
# remember current dir: in case the build process removes and recreates the dir, re-enter the new dir
W := "$(PWD)"
poll:
while true; do $(MAKE) -C $W msc dot; sleep 1; done

View File

@@ -0,0 +1,107 @@
msc {
hscale="2";
hnb[label="HNB"],rua[label="OsmoHNBGW\nRUA FSM / HNBAP"],sccp[label="OsmoHNBGW\nSCCP FSM"],cn[label="CN"];
hnb <=> rua [label="RANAP/RUA\n(Iuh)"];
rua <=> sccp [label="FSM events"];
sccp <=> cn [label="RANAP/SCCP/M3UA\n(IuCS, IuPS)"];
...;
...;
--- [label="Create a new context map (always from HNB)"];
hnb => rua [label="RUA Connect\nRANAP InitialUE-Message\nDTAP Location Updating Request"];
rua rbox sccp [label="hnbgw_context_map_alloc()"];
rua abox rua [label="MAP_RUA_ST_INIT"];
sccp abox sccp [label="MAP_SCCP_ST_INIT"];
rua rbox rua [label="MAP_RUA_EV_RX_CONNECT\ndata = ranap_msg"];
rua abox rua [label="MAP_RUA_ST_CONNECTED"];
rua => sccp [label="MAP_SCCP_EV_TX_RANAP_MSG\ndata = ranap_msg"];
sccp abox sccp [label="MAP_SCCP_ST_WAIT_CC"];
sccp => cn [label="SCCP Connection Request\nRANAP InitialUE-Message\nDTAP Location Updating Request"];
...;
sccp <= cn [label="SCCP Connection Confirm"];
sccp abox sccp [label="MAP_SCCP_ST_CONNECTED"];
--- [label="if SCCP CC with payload"];
rua <= sccp [label="MAP_RUA_EV_TX_RANAP_MSG\ndata = ranap_msg"];
hnb <= rua [label="RUA DirectTransfer"];
...;
...;
--- [label="Layer 3 communication"];
sccp <= cn [label="SCCP Data Form 1"];
rua <= sccp [label="MAP_RUA_EV_TX_RANAP_MSG\ndata = ranap_msg"];
hnb <= rua [label="RUA DirectTransfer"];
...;
hnb => rua [label="RUA DirectTransfer"];
rua => sccp [label="MAP_SCCP_EV_TX_RANAP_MSG\ndata = ranap_msg"];
sccp => cn [label="SCCP Data Form 1"];
...;
...;
--- [label="Usual release"];
sccp <= cn [label="SCCP Data Form 1\nIu-ReleaseCommand"];
rua <= sccp [label="MAP_RUA_EV_TX_RANAP_MSG\ndata = ranap_msg"];
hnb <= rua [label="RUA DirectTransfer\nIu-ReleaseCommand"];
...;
hnb => rua [label="RUA Disconnect\nIu-ReleaseComplete"];
rua rbox rua [label="MAP_RUA_EV_RX_DISCONNECT"];
rua abox rua [label="MAP_RUA_ST_DISCONNECTED"];
rua => sccp [label="MAP_SCCP_EV_RAN_DISC\ndata = ranap_msg"];
sccp => cn [label="SCCP Data Form 1\nIu-ReleaseComplete\nNOT 'SCCP Released'! See 3GPP TS 48.006 9.2"];
sccp abox sccp [label="MAP_SCCP_ST_WAIT_RLSD"];
...;
sccp <= cn [label="SCCP Released"];
sccp abox sccp [label="MAP_SCCP_ST_DISCONNECTED"];
rua rbox sccp [label="map_check_released()"];
rua rbox sccp [label="hnbgw_context_map_free()"];
...;
...;
--- [label="Ungraceful release from CN"];
sccp <= cn [label="SCCP Released"];
sccp => cn [label="SCCP Release Complete\n(implicit by libosmo-sigtran)"];
sccp rbox sccp [label="MAP_SCCP_EV_RX_RELEASED"];
sccp note sccp [label="In SCCP there is no \"link loss\".\nIn every case, we will be notified\nvia N-Disconnect prim per SCCP conn."];
sccp abox sccp [label="MAP_SCCP_ST_DISCONNECTED"];
rua <= sccp [label="MAP_RUA_EV_CN_DISC"];
hnb <= rua [label="RUA Disconnect"];
rua abox rua [label="MAP_RUA_ST_DISRUPTED"];
rua rbox sccp [label="map_check_released()"];
rua rbox sccp [label="hnbgw_context_map_free()"];
...;
...;
...;
--- [label="PCSTATE handling"];
sccp << cn [label="N-PCSTATE.indication\npoint-code unreachable"];
sccp note sccp [label="currently ignored"];
...;
...;
--- [label="Ungraceful release from HNB"];
hnb -x rua [label="link loss"];
rua rbox rua [label="MAP_RUA_EV_HNB_LINK_LOST"];
rua abox rua [label="MAP_RUA_ST_DISRUPTED"];
rua => sccp [label="MAP_SCCP_EV_RAN_LINK_LOST"];
sccp => cn [label="SCCP Released"];
sccp abox sccp [label="MAP_SCCP_ST_DISCONNECTED"];
rua rbox sccp [label="map_check_released()"];
rua rbox sccp [label="hnbgw_context_map_free()"];
...;
...;
--- [label="Ungraceful release from HNB De-Register"];
hnb => rua [label="HNBAP HNB De-Register"];
--- [label="or"];
hnb => rua [label="HNBAP HNB Register\n(HNB restarted)"];
---;
rua rbox rua [label="MAP_RUA_EV_HNB_LINK_LOST"];
hnb <= rua [label="RUA Disconnect"];
hnb note rua [label="TODO: does it make sense to send\nRUA Disconnect per context when HNB is/was gone?\nOr has the HNB implicitly discarded these?"];
rua abox rua [label="MAP_RUA_ST_DISRUPTED"];
rua => sccp [label="MAP_SCCP_EV_RAN_LINK_LOST\nfrom map_rua_disrupted_onenter()"];
sccp => cn [label="SCCP Released"];
sccp abox sccp [label="MAP_SCCP_ST_DISCONNECTED"];
rua rbox sccp [label="map_check_released()"];
rua rbox sccp [label="hnbgw_context_map_free()"];
}

View File

@@ -0,0 +1,62 @@
digraph G {
rankdir=TB
labelloc=t
label="hnb_context_map\nRUA and SCCP FSMs"
HNB_connect [label="HNB",shape=box3d]
rua_to_scu_connect [label="rua_to_scu()",shape=box]
HNB_DT [label="HNB",shape=box3d]
rua_to_scu_DT [label="rua_to_scu()",shape=box]
HNB_DT2 [label="HNB",shape=box3d]
HNB_disconnect [label="HNB",shape=box3d]
rua_to_scu_disconnect [label="rua_to_scu()",shape=box]
CN [shape=box3d]
CN2 [label="CN",shape=box3d]
CN3 [label="CN",shape=box3d]
CN4 [label="CN",shape=box3d]
HNB_connect -> rua_to_scu_connect [label="RUA Connect",style=dotted]
rua_to_scu_connect -> create [label="(1)",style=dotted]
create [label="hnbgw_context_map_alloc()",shape=box]
create -> {RUA_INIT,SCCP_INIT} [label="(1)",style="dashed,bold"]
rua_to_scu_connect -> RUA_CONNECTED [label="(2)\nMAP_RUA_EV_RX_CONNECT",style=dashed]
RUA_CONNECTED -> SCCP_WAIT_CC [label="(2)\nMAP_SCCP_EV_TX_RANAP_MSG",style=dashed]
RUA_INIT -> RUA_CONNECTED -> RUA_DISCONNECTED [style=bold]
SCCP_INIT -> SCCP_WAIT_CC -> SCCP_CONNECTED -> SCCP_WAIT_RLSD -> SCCP_DISCONNECTED [style=bold]
SCCP_CONNECTED -> SCCP_DISCONNECTED [label="SCCP RLSD"]
SCCP_WAIT_CC -> CN [label="(2) SCCP CR",style=dotted]
CN -> SCCP_CONNECTED [label="(3) SCCP CC",style=dashed]
CN4 -> SCCP_CONNECTED [label="(4) SCCP\nData Form 1",style=dotted]
SCCP_CONNECTED -> RUA_CONNECTED [label="(4)\nMAP_RUA_EV_TX_RANAP_MSG",style=dotted]
RUA_CONNECTED -> HNB_DT2 [label="(4)\nRUA DirectTransfer",style=dotted]
HNB_DT -> rua_to_scu_DT [label="(5)\nRUA DirectTransfer",style=dotted]
rua_to_scu_DT -> RUA_CONNECTED [label="(5)\nMAP_RUA_EV_RX_DIRECT_TRANSFER",style=dotted]
RUA_CONNECTED -> SCCP_CONNECTED [label="(5)\nMAP_SCCP_EV_TX_RANAP_MSG",style=dotted]
SCCP_CONNECTED -> CN3 [label="(5) SCCP\nData Form 1",style=dotted]
HNB_disconnect -> rua_to_scu_disconnect [label="(6) RUA Id-Disconnect",style=dotted]
rua_to_scu_disconnect -> RUA_DISCONNECTED [label="(6) MAP_RUA_EV_RX_DISCONNECT",style=dashed]
RUA_DISCONNECTED -> SCCP_WAIT_RLSD [label="(6) MAP_SCCP_EV_RAN_DISC",style=dashed]
CN2 -> SCCP_DISCONNECTED [label="(7) SCCP RLSD",style=dashed]
{RUA_DISCONNECTED,SCCP_DISCONNECTED} -> release [label="onenter",style="dotted,bold"]
release [label="map_check_released()",shape=box]
release -> terminate [label="when both RUA and\nSCCP disconnected",style=dotted]
terminate [label="hnbgw_context_map_free()",shape=octagon]
subgraph cluster_legend {
label=""
style=invisible
A, B, C, D [style=invisible]
A -> B [label="event without\nstate change",style=dotted]
C -> D [label="event changes\nstate to...",style=dashed]
}
}

78
doc/charts/sccp.dot Normal file
View File

@@ -0,0 +1,78 @@
digraph G {
rankdir=TB
labelloc=t; label="SCCP/SS7 use in OsmoHNBGW"
subgraph cluster_ss0 {
label=""
ss0 [label="cs7 instance 0\n local pc: 1.1.1"]
ss0ab [label="address book"]
ss0 -> ss0ab
msc0a [label="2.2.2"]
sgsn0a [label="3.3.3"]
msc1a [label="5.5.5"]
ss0ab -> msc0a,sgsn0a,msc1a
}
sccp0 [label="hnbgw_sccp_user for '0'\n .osmo_sccp_instance\n .osmo_ss7_user SSN: RANAP"];
ss0 -> sccp0 [dir=back]
msc0 [label="hnbgw_cnlink 'msc0'\n .domain = IuCS\n .sccp_addr -> 2.2.2"]
sccp0 -> msc0 [dir=back]
msc0a -> msc0 [dir=back]
cs0 [label="UE CS conn\n hnbgw_context_map"]
cs1 [label="UE CS conn\n hnbgw_context_map"]
msc0 -> cs0,cs1
sgsn0 [label="hnbgw_cnlink 'sgsn0'\n .domain = IuPS\n .sccp_addr -> 3.3.3"]
sccp0 -> sgsn0 [dir=back]
sgsn0a -> sgsn0 [dir=back]
ps0 [label="UE PS conn\n hnbgw_context_map"]
sgsn0 -> ps0
subgraph cluster_ss1 {
label=""
ss1 [label="cs7 instance 1\n local pc: 4.4.4"]
ss1ab [label="address book"]
ss1 -> ss1ab
sgsn1a [label="6.6.6"]
ss1ab -> sgsn1a
}
sccp1 [label="hnbgw_sccp_user for '1'\n .osmo_sccp_instance\n .osmo_ss7_user SSN: RANAP"];
ss1 -> sccp1 [dir=back]
msc1 [label="hnbgw_cnlink 'msc1'\n .domain = IuCS\n .sccp_addr -> 5.5.5"]
sccp0 -> msc1 [dir=back]
msc1a -> msc1 [dir=back]
cs2 [label="UE CS conn\n hnbgw_context_map"]
msc1 -> cs2
sgsn1 [label="hnbgw_cnlink 'sgsn1'\n .domain = IuPS\n .sccp_addr -> 6.6.6"]
sccp1 -> sgsn1 [dir=back]
sgsn1a -> sgsn1 [dir=back]
ps2 [label="UE PS conn\n hnbgw_context_map"]
sgsn1 -> ps2
subgraph cluster_sccp_inst {
label="global sccp.users (llist)"
sccp0
sccp1
}
subgraph cluster_cnpool_iucs {
label="global cnpool_iucs"
msc0
msc1
}
subgraph cluster_cnpool_iups {
label="global cnpool_iups"
sgsn0
sgsn1
}
}

View File

@@ -0,0 +1,40 @@
log stderr
logging filter all 1
logging color 1
logging print category-hex 0
logging print category 1
logging timestamp 0
logging print file basename last
logging print level 1
logging level set-all notice
cs7 instance 0
point-code 0.42.0
sccp-address my-msc-0
point-code 0.1.0
sccp-address my-msc-1
point-code 0.1.1
sccp-address my-sgsn-0
point-code 0.2.0
hnbgw
plmn 001 01
iucs
nri bitlen 10
nri null add 0 7
iups
nri bitlen 8
nri null add 0 5
msc 0
remote-addr my-msc-0
nri add 128 255
msc 1
remote-addr my-msc-1
nri add 256 511
sgsn 0
remote-addr my-sgsn-0
nri add 128 255

View File

@@ -0,0 +1,29 @@
cs7 instance 0
point-code 0.3.0
asp asp-clnt-hnbgw-0 2905 0 m3ua
remote-ip 127.0.0.1
role asp
sctp-role client
sccp-address my-msc
point-code 1.2.3
sccp-address my-sgsn
point-code 1.4.2
hnbgw
plmn 001 01
iuh
local-ip 0.0.0.0
msc 0
remote-addr my-msc
sgsn 0
remote-addr my-sgsn
log stderr
logging filter all 1
logging color 1
logging print category-hex 0
logging print category 1
logging timestamp 0
logging print file basename last
logging print level 1
logging level set-all notice

View File

@@ -0,0 +1,21 @@
log stderr
logging filter all 1
logging color 1
logging print category-hex 0
logging print category 1
logging timestamp 0
logging print file basename last
logging print level 1
logging level set-all notice
hnbgw
plmn 001 01
iuh
local-ip 0.0.0.0
mgw 0
remote-ip 127.0.0.1
local-port 2729
remote-port 2427
reset-endpoint rtpbridge/*
pfcp
remote-addr 127.0.0.2
local-addr 127.0.0.1

View File

@@ -1,25 +1,19 @@
!
! OsmoHNBGW (0) configuration saved from vty
!!
!
log stderr
logging filter all 1
logging color 1
logging print category-hex 0
logging print category 1
logging timestamp 1
logging print extended-timestamp 1
logging level all debug
logging level lglobal notice
logging level llapd notice
logging level linp notice
logging level lmux notice
logging level lmi notice
logging level lmib notice
logging level lsms notice
logging level lctrl notice
logging level lgtp notice
logging level lstats notice
logging timestamp 0
logging print file basename last
logging print level 1
logging level set-all notice
hnbgw
plmn 001 01
iuh
local-ip 0.0.0.0
hnbap-allow-tmsi 1
mgw 0
remote-ip 127.0.0.1
local-port 2729
remote-port 2427
reset-endpoint rtpbridge/*

View File

@@ -9,22 +9,62 @@ OsmoHNBGW implements the Home NodeB Gateway function in the 3G network architect
as a gateway between the classic 3G core network (CN) domain with its IuCS and IuPS interface
and the femtocell based RAN.
A typical 3G network consisting of Osmocom components will look as illustrated in the following
A typical 3G network consisting of Osmocom components is illustrated in the following
diagram:
[[fig-3g]]
.Typical 3G network architecture used with OsmoHNBGW
[graphviz]
----
+------------+ +--------+ +----------+ +---------+
UE <-->| hNodeB |<--Iuh---->| HNB-GW |<--IuCS-->| OsmoMSC |<--GSUP-->| OsmoHLR |
UE <-->| femto cell | ...-->| | ...-->| | | |
| | | | +----------+ +---------|
+------------+<--GTP-U | |
\ | | +------+ +------+
| | |<--IuPS-->| SGSN |<--GTP-C-->| GGSN |
| +--------+ ...-->| | GTP-U-->| |
| +------+ / +------+
\_______________________________/
digraph G {
rankdir = LR;
UE [label="UE\n(3G phone)"]
PBX [label="PBX\nAsterisk, FreeSwitch,\nKamailio, Yate, ..."]
subgraph cluster_msc_mgw {
style=dotted
MSC
MGW1 [label="MGW"]
MSC -> MGW1 [label="MGCP",constraint=false]
}
subgraph cluster_hnbgw_mgw_upf {
style=dotted
MGW3 [label="MGW"]
UPF
HNBGW [label=HNBGW,style=bold]
HNBGW -> MGW3 [label="MGCP",constraint=false]
HNBGW -> UPF [label="PFCP",constraint=false]
}
hNodeB [shape="box",label="hNodeB\n(3G femto cell)"]
MSC -> HLR [label="\nGSUP",style=bold]
SGSN -> HLR [label="GSUP",style="dashed,bold"]
UE -> hNodeB [label="Uu",style=bold]
UE -> hNodeB [style="dashed,bold"]
hNodeB -> HNBGW [label="Iuh",style="bold"]
STP2 [label="STP\n(SCCP/M3UA)"]
HNBGW -> STP2 -> SGSN [label="IuPS",style="dashed,bold"]
HNBGW -> STP2 -> MSC [label="IuCS",style="bold"]
SGSN -> GGSN [label="GTP-C",style="dashed,bold"]
hNodeB -> UPF -> GGSN [label="GTP-U(3G)",style="dashed"]
GGSN -> internet [label="tun",style="dashed"]
hNodeB -> MGW3 [label="IuUP/RTP",constraint=false]
MGW3 -> MGW1 [label="IuUP/RTP"]
MSC -> SIPConnector [label="MNCC socket",style=bold]
SIPConnector -> PBX [label="SIP",style=bold]
MGW1 -> PBX [label="RTP"]
A, B, C, D [style="invisible"]
A -> B [label="data (PS)",style="dashed"]
C -> D [label="voice/SMS/USSD (CS)"]
}
----
The HNB-GW performs a translation interface between the IuCS/IuPS interfaces on the one hand
@@ -32,17 +72,18 @@ side, and the Iuh interface on the or ther hand:
----
Iuh IuCS/IuPS
NAS +----+----+ +----+----+
Non-Access Stratum | CC | MM | | CC | MM |
- - - - - - - - - - - +----+----+-------+ +----+----+
| RANAP | | H | RANAP |
Access Stratum +---------+ HNBAP | N +---------+ - - SCCP USER SAP
| RUA | | B | SUA | \
+---------+-------+ - +---------+ |
| SCTP | G | SCTP | } SIGTRAN
+-----------------+ W +---------+ |
| IP | | IP | /
+----+----+
| CC | MM |
NAS +----+----+ . . +----+----+
Non-Access Stratum | CC | MM | . | RANAP |
- - - - - - - - - - - +----+----+-------+ +---------+
| RANAP | | H | SCCP |
Access Stratum +---------+ HNBAP | N +---------+
| RUA | | B | M3UA |
+---------+-------+ - +---------+
| SCTP | G | SCTP |
+-----------------+ W +---------+
| IP | | IP |
+-----------------+ +---------+
----

View File

@@ -69,11 +69,30 @@ specific interface, and will hence not encounter conflicts for multiple instance
running on the same interface:
- The SCCP/M3UA links are established by OsmoHNBGW contacting an STP.
- The MGCP link is established by OsmoHNBGW contacting an MGW.
- The PFCP link is established by OsmoHNBGW contacting a UPF.
To run multiple OsmoHNBGW instances on the same SCCP routing, each HNBGW has to
configure a distinct point-code, see <<configure_iucs_iups>>.
=== Configure limits
When connecting hundreds of HNB to OsmoHNBGW, it may be necessary to adjust the
operating system's limit on open file descriptors for the osmo-hnbgw process. A
typical default limit imposed by operating systems is 1024; this would be
exceeded by, for example, about 1024 HNB on Iuh, sockets for other interfaces
not considered yet.
It should be ok to set an OS limit on open file descriptors as high as 65536
for osmo-hnbgw, which practically rules out failure from running out of file
descriptors anywhere (<50,000 HNB).
When using systemd, the file descriptor limit may be adjusted in the service
file by the `LimitNOFILE` setting ("Number of Open FILE descriptors").
OsmoHNBGW ships a systemd service file with a high LimitNOFILE setting.
=== Configuring Primary Links
[[configure_iucs_iups]]
@@ -117,3 +136,75 @@ hnbgw
local-ip 10.9.8.7
local-port 29169
----
==== Configure co-located media gateway
OsmoHNBGW requires a co-located OsmoMGW instance. The purpose of the co-located
media gateway is to relay the RTP traffic between hNodeB and the core network.
For security reasons the RAN network is kept separate and isolated from the
core network. Both networks will usually have no transparent routing in between
them. The co-located media gateway provides an interface between hNodeB and core
network across this boundary.
The configuration is done under the hnbgw node along with `iucs` and `iups`.
An example configuration for OsmoHNBGW's MGCP client:
----
hnbgw
mgw 0
remote-ip 127.0.0.1
remote-port 2427
reset-endpoint rtpbridge/* <1>
----
<1> The 'reset-endpoint' setting instructs the OsmoMGW to send a wildcarded
DLCX to the media gateway. This helps to clear lingering calls from the
media gateway when the OsmoHNBGW is restarted.
OsmoHNBGW is also able to handle a pool of media gateways for load
distribution. See also <<mgw_pooling>>.
[NOTE]
====
Previous versions of OsmoHNBGW didn't have the 'mgw' VTY node and
hence didn't support the MGW pooling feature. Therefore, historically the MGW
related commands where placed under the `mgcp` VTY node. The MGW related commands
under the `mgcp` VTY are still parsed and used but its use is deprecated and
hence discouraged in favour of the new `mgw` node. Writing the config to a file
from within OsmoHNBGW will automatically convert the config to use the new `mgw`
node.
====
==== Configure co-located User Plane Function
OsmoHNBGW optionally supports relaying the GTP user plane via a co-located UPF,
which is controlled by the PFCP protocol.
PFCP support is optional at compile time, as well as run time. To use a co-located UPF,
* osmo-hnbgw needs to be compiled with 'configure --enable-pfcp',
* and osmo-hnbgw.cfg needs to configure a 'pfcp' / 'remote-addr' and
'local-addr'.
The following example configures OsmoHNBGW to associate via PFCP with a UPF
listening on UDP 127.0.0.2:8805, ready to setup GTP tunnel relays.
----
hnbgw
pfcp
remote-addr 127.0.0.2
local-addr 127.0.0.1
----
3GPP TS 29.244 4.2.2 specifies that PFCP Request messages shall be sent to UDP
port 8805, i.e. the PFCP port is fixed as 8805 and currently not configurable in
osmo-hnbgw.
Setting a 'local-addr' is required: the PFCP protocol features a Node ID, which
uniquely identifies PFCP peers across different interfaces. According to the
PFCP specification, the Node ID can be a fully-qualified domain name (FQDN) or
an IP address. Currently, osmo-hnbgw has no support for using an FQDN as Node
ID, and so far uses the 'local-addr' as local Node ID -- hence the 'local-addr'
must not be "0.0.0.0", which is an unfortunate consequence. This is likely to
improve in the future, see https://osmocom.org/issues/5647 .

View File

@@ -22,8 +22,12 @@ include::./common/chapters/vty.adoc[]
include::./common/chapters/logging.adoc[]
include::./common/chapters/sigtran.adoc[]
include::./common/chapters/cs7-config.adoc[]
include::./common/chapters/mgwpool.adoc[]
// include::{srcdir}/chapters/net.adoc[]
// include::./common/chapters/control_if.adoc[]

View File

@@ -1,4 +1,22 @@
noinst_HEADERS = \
cnlink.h \
context_map.h \
hnb.h \
hnb_persistent.h \
hnbgw.h \
hnbgw_cn.h \
hnbgw_hnbap.h \
hnbgw_pfcp.h \
hnbgw_ranap.h \
hnbgw_rua.h \
hnbgw_sccp.h \
kpi.h \
mgw_fsm.h \
nft_kpi.h \
ps_rab_ass_fsm.h \
ps_rab_fsm.h \
ranap_rab_ass.h \
tdefs.h \
umts_cell_id.h \
vty.h \
context_map.h hnbgw.h hnbgw_cn.h \
hnbgw_hnbap.h hnbgw_rua.h hnbgw_ranap.h
$(NULL)

View File

@@ -0,0 +1,138 @@
#pragma once
#include <stdbool.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/stat_item.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/sigtran/sccp_sap.h>
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/hnbgw/hnbgw_sccp.h>
struct hnbgw_cnpool;
enum hnbgw_cnlink_ctr {
/* TODO: basic counters completely missing
* ...
*/
CNLINK_CTR_RANAP_RX_UDT_RESET,
CNLINK_CTR_RANAP_RX_UDT_RESET_ACK,
CNLINK_CTR_RANAP_RX_UDT_PAGING,
CNLINK_CTR_RANAP_RX_UDT_UNKNOWN,
CNLINK_CTR_RANAP_RX_UDT_UNSUPPORTED,
CNLINK_CTR_RANAP_RX_UDT_OVERLOAD_IND,
CNLINK_CTR_RANAP_RX_UDT_ERROR_IND,
CNLINK_CTR_RANAP_TX_UDT_RESET,
CNLINK_CTR_RANAP_TX_UDT_RESET_ACK,
/* SCCP Counters: */
CNLINK_CTR_SCCP_N_UNITDATA_REQ,
CNLINK_CTR_SCCP_N_UNITDATA_IND,
CNLINK_CTR_SCCP_N_NOTICE_IND,
CNLINK_CTR_SCCP_N_CONNECT_REQ,
CNLINK_CTR_SCCP_N_CONNECT_CNF,
CNLINK_CTR_SCCP_N_DATA_REQ,
CNLINK_CTR_SCCP_N_DATA_IND,
CNLINK_CTR_SCCP_N_DISCONNECT_REQ,
CNLINK_CTR_SCCP_N_DISCONNECT_IND,
CNLINK_CTR_SCCP_N_PCSTATE_IND,
CNLINK_CTR_SCCP_RLSD_CN_ORIGIN,
/* Counters related to link selection from a CN pool. */
CNLINK_CTR_CNPOOL_SUBSCR_NEW,
CNLINK_CTR_CNPOOL_SUBSCR_REATTACH,
CNLINK_CTR_CNPOOL_SUBSCR_KNOWN,
CNLINK_CTR_CNPOOL_SUBSCR_PAGED,
CNLINK_CTR_CNPOOL_SUBSCR_ATTACH_LOST,
CNLINK_CTR_CNPOOL_EMERG_FORWARDED,
};
#define CNLINK_CTR_INC(cnlink, x) rate_ctr_inc2((cnlink)->ctrs, x)
enum cnlink_stat {
CNLINK_STAT_CONNECTED,
};
#define CNLINK_STAT(cnlink, x) osmo_stat_item_group_get_item((cnlink)->statg, x)
#define CNLINK_STAT_SET(cnlink, x, val) osmo_stat_item_set(CNLINK_STAT(cnlink, x), val)
/* User provided configuration for struct hnbgw_cnlink. */
struct hnbgw_cnlink_cfg {
/* cs7 address book entry to indicate both the remote point-code of the peer, as well as which cs7 instance to
* use. */
char *remote_addr_name;
struct osmo_nri_ranges *nri_ranges;
};
/* A CN peer, like 'msc 0' or 'sgsn 23' */
struct hnbgw_cnlink {
struct llist_head entry;
/* backpointer to CS or PS CN pool. */
struct hnbgw_cnpool *pool;
struct osmo_fsm_inst *fi;
int nr;
struct hnbgw_cnlink_cfg vty;
struct hnbgw_cnlink_cfg use;
/* To print in logging/VTY */
char *name;
/* Copy of the address book entry use.remote_addr_name. */
struct osmo_sccp_addr remote_addr;
/* The SCCP instance for the cs7 instance indicated by remote_addr_name. (Multiple hnbgw_cnlinks may use the
* same hnbgw_sccp_user -- there is exactly one hnbgw_sccp_user per configured cs7 instance.) */
struct hnbgw_sccp_user *hnbgw_sccp_user;
/* linked list of hnbgw_context_map */
struct llist_head map_list;
bool allow_attach;
bool allow_emerg;
struct llist_head paging;
struct rate_ctr_group *ctrs;
struct osmo_stat_item_group *statg;
};
struct hnbgw_cnlink *hnbgw_cnlink_alloc(struct hnbgw_cnpool *cnpool, int nr);
void hnbgw_cnlink_term_and_free(struct hnbgw_cnlink *cnlink);
void hnbgw_cnlink_drop_sccp(struct hnbgw_cnlink *cnlink);
int hnbgw_cnlink_set_name(struct hnbgw_cnlink *cnlink, const char *name);
int hnbgw_cnlink_tx_ranap_reset(struct hnbgw_cnlink *cnlink);
int hnbgw_cnlink_tx_ranap_reset_ack(struct hnbgw_cnlink *cnlink);
int hnbgw_cnlink_start_or_restart(struct hnbgw_cnlink *cnlink);
char *hnbgw_cnlink_sccp_addr_to_str(struct hnbgw_cnlink *cnlink, const struct osmo_sccp_addr *addr);
static inline struct osmo_sccp_instance *hnbgw_cnlink_sccp(const struct hnbgw_cnlink *cnlink)
{
if (!cnlink)
return NULL;
if (!cnlink->hnbgw_sccp_user)
return NULL;
return hnbgw_sccp_user_get_sccp_instance(cnlink->hnbgw_sccp_user);
}
/* cnlink_fsm.c related: */
extern struct osmo_fsm cnlink_fsm;
bool cnlink_is_conn_ready(const struct hnbgw_cnlink *cnlink);
void cnlink_rx_reset_cmd(struct hnbgw_cnlink *cnlink);
void cnlink_rx_reset_ack(struct hnbgw_cnlink *cnlink);
void cnlink_resend_reset(struct hnbgw_cnlink *cnlink);
void cnlink_set_disconnected(struct hnbgw_cnlink *cnlink);
/* cnlink_paging.c related: */
const char *cnlink_paging_add_ranap(struct hnbgw_cnlink *cnlink, const RANAP_PagingIEs_t *paging_ies);
struct hnbgw_cnlink *cnlink_find_by_paging_mi(struct hnbgw_cnpool *cnpool, const struct osmo_mobile_identity *mi);
#define LOG_CNLINK(CNLINK, SUBSYS, LEVEL, FMT, ARGS...) \
LOGP(SUBSYS, LEVEL, "(%s) " FMT, (CNLINK) ? (CNLINK)->name : "null", ##ARGS)

View File

@@ -2,13 +2,103 @@
#include <stdint.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/utils.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/gsm/gsm48.h>
#define LOG_MAP(HNB_CTX_MAP, SUBSYS, LEVEL, FMT, ARGS...) \
LOGHNB((HNB_CTX_MAP) ? (HNB_CTX_MAP)->hnb_ctx : NULL, \
SUBSYS, LEVEL, "RUA-%u SCCP-%u %s MI=%s%s%s: " FMT, \
(HNB_CTX_MAP) ? (HNB_CTX_MAP)->rua_ctx_id : 0, \
(HNB_CTX_MAP) ? (HNB_CTX_MAP)->scu_conn_id : 0, \
(HNB_CTX_MAP) ? \
((HNB_CTX_MAP)->cnlink ? (HNB_CTX_MAP)->cnlink->name \
: ((HNB_CTX_MAP)->is_ps ? "PS" : "CS")) \
: "NULL", \
(HNB_CTX_MAP) ? osmo_mobile_identity_to_str_c(OTC_SELECT, &(HNB_CTX_MAP)->l3.mi) : "null", \
(HNB_CTX_MAP) && (HNB_CTX_MAP)->l3.from_other_plmn ? " (from other PLMN)" : "", \
(HNB_CTX_MAP) && (HNB_CTX_MAP)->l3.is_emerg ? " EMERGENCY" : "", \
##ARGS)
/* All these events' data argument may either be NULL, or point to a RANAP msgb.
* - The msgb shall be in the OTC_SELECT talloc pool, so that they will be deallocated automatically. Some events
* processing will store the msgb for later, in which case it will take over ownership of the msgb by means of
* talloc_steal().
* - For events that may send a RANAP message towards CN via SCCP, the msgb shall have reserved headroom to fit a struct
* osmo_scu_prim. These are: MAP_RUA_EV_RX_*.
* - The RANAP message shall be at msgb_l2().
*/
enum map_rua_fsm_event {
/* Receiving a RUA Connect from HNB.
* Parameter: struct msgb *ranap_msg */
MAP_RUA_EV_RX_CONNECT,
/* Receiving some data from HNB via RUA, to forward via SCCP to CN.
* Parameter: struct msgb *ranap_msg */
MAP_RUA_EV_RX_DIRECT_TRANSFER,
/* Receiving a RUA Disconnect from HNB.
* Parameter: struct msgb *ranap_msg (can be NULL) */
MAP_RUA_EV_RX_DISCONNECT,
/* SCCP has received some data from CN to forward via RUA to HNB.
* Parameter: struct msgb *ranap_msg */
MAP_RUA_EV_TX_DIRECT_TRANSFER,
/* The CN side is disconnected (e.g. received an SCCP Released), that means we are going gracefully disconnect
* RUA, too. */
MAP_RUA_EV_CN_DISC,
/* All of a sudden, there is no RUA link. For example, HNB vanished / restarted, or SCTP SHUTDOWN on the RUA
* link. Skip RUA disconnect. */
MAP_RUA_EV_HNB_LINK_LOST,
};
/* All these events' data argument is identical to enum map_rua_fsm_event, with this specialisation:
* - The events that may send a RANAP message towards CN via SCCP and hence require a headroom for an osmo_scu_prim are:
* MAP_SCCP_EV_TX_DATA_REQUEST, MAP_SCCP_EV_RAN_DISC.
*/
enum map_sccp_fsm_event {
/* Receiving an SCCP CC from CN.
* Parameter: struct msgb *ranap_msg, may be NULL or empty. */
MAP_SCCP_EV_RX_CONNECTION_CONFIRM,
/* Receiving some data from CN via SCCP, to forward via RUA to HNB.
* Parameter: struct msgb *ranap_msg, may be NULL or empty. */
MAP_SCCP_EV_RX_DATA_INDICATION,
/* RUA has received some data from HNB to forward via SCCP to CN.
* Parameter: struct msgb *ranap_msg. */
MAP_SCCP_EV_TX_DATA_REQUEST,
/* 3GPP TS 25.468 9.1.5: The RAN side received a RUA Disconnect.
* - Under normal conditions (cause=Normal) the RUA Disconnect contains a RANAP Iu-ReleaseComplete.
* On SCCP, the Iu-ReleaseComplete should still be forwarded as N-Data SCCP Data Form 1),
* and we will expect the CN to send an SCCP RLSD soon. Hence, give CN a chance to send an SCCP RLSD;
* after a timeout we will send a non-standard RLSD to the CN instead.
* - Under error conditions, cause!=Normal and there's no RANAP message.
* In that case, we need to tear down the associated SCCP link towards CN with an RLSD,
* which in turn will tear down the upper layer Iu conn.
*
* Parameter: bool rua_disconnect_err_condition, whether the disconnect
* happened under error or normal conditions, as per the above.
*/
MAP_SCCP_EV_RAN_DISC,
/* The RAN released ungracefully. We will directly disconnect the SCCP connection, too.
* Parameter: no parameter, NULL. */
MAP_SCCP_EV_RAN_LINK_LOST,
/* Receiving an SCCP RLSD from CN, or libosmo-sigtran tells us about SCCP connection timeout. All done.
* Parameter: struct msgb *ranap_msg, may be NULL or empty. */
MAP_SCCP_EV_RX_RELEASED,
/* The human admin asks to drop the current SCCP connection, by telnet VTY 'apply sccp' in presence of SCCP
* config changes.
* Parameter: no parameter, NULL. */
MAP_SCCP_EV_USER_ABORT,
/* The CN link can no longer work, for example a RANAP RESET was received from the cnlink that hosts this
* context map.
* Parameter: no parameter, NULL. */
MAP_SCCP_EV_CN_LINK_LOST,
};
/* For context_map_get_state(), to combine the RUA and SCCP states, for VTY reporting only. */
enum hnbgw_context_map_state {
MAP_S_NULL,
MAP_S_ACTIVE, /* currently active map */
MAP_S_RESERVED1, /* just disconnected, still resrved */
MAP_S_RESERVED2, /* still reserved */
MAP_S_NUM_STATES /* Number of states, keep this at the end */
MAP_S_CONNECTING, /* not active yet; effectively waiting for SCCP CC */
MAP_S_ACTIVE, /* both RUA and SCCP are connected */
MAP_S_DISCONNECTING, /* not active anymore; effectively waiting for SCCP RLSD */
MAP_S_NUM_STATES /* Number of states, keep this at the end */
};
extern const struct value_string hnbgw_context_map_state_names[];
@@ -18,34 +108,141 @@ static inline const char *hnbgw_context_map_state_name(enum hnbgw_context_map_st
struct hnb_context;
struct hnbgw_cnlink;
struct hnbgw_context_map {
/* entry in the per-CN list of mappings */
struct llist_head cn_list;
/* entry in the per-HNB list of mappings */
struct llist_head hnb_list;
/* pointer to HNB */
struct hnb_context *hnb_ctx;
/* pointer to CN */
struct hnbgw_cnlink *cn_link;
/* RUA contxt ID */
uint32_t rua_ctx_id;
/* False for CS, true for PS */
bool is_ps;
/* SCCP User SAP connection ID */
uint32_t scu_conn_id;
enum hnbgw_context_map_state state;
struct hnbgw_l3_peek {
/* L3 message type, like GSM48_PDISC_MM+GSM48_MT_MM_LOC_UPD_REQUEST... / GSM48_PDISC_MM_GPRS+GSM48_MT_GMM_ATTACH_REQ... */
uint8_t gsm48_pdisc;
uint8_t gsm48_msg_type;
/* The Mobile Identity from MM and GMM messages */
struct osmo_mobile_identity mi;
/* On PS, the "TMSI Based NRI Container", 10 bit integer, or -1 if not present.
* This is only for PS -- for CS, the NRI is in the TMSI obtained from 'mi' above. */
int gmm_nri_container;
/* For a CM Service Request for voice call, true if this is for an Emergency Call, false otherwise. */
bool is_emerg;
/* True if the NAS PDU indicates that the UE was previously attached to a different PLMN than the local PLMN. */
bool from_other_plmn;
};
/* used in hnbgw_context_map.rab_state[] */
enum rab_state {
RAB_STATE_INACTIVE,
RAB_STATE_ACT_REQ,
RAB_STATE_ACTIVE,
RAB_STATE_REL_REQ,
};
extern const struct value_string hnbgw_rab_state_names[];
static inline const char *hnbgw_rab_state_name(enum rab_state val)
{ return get_value_string(hnbgw_rab_state_names, val); }
struct hnbgw_context_map *
context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id,
bool is_ps,
struct hnbgw_cnlink *cn_if_new);
struct hnbgw_context_map *
context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id);
struct hnbgw_context_map {
/* entry in the per-CN list of mappings */
struct llist_head hnbgw_cnlink_entry;
/* entry in the per-HNB list of mappings. If hnb_ctx == NULL, then this llist entry has been llist_del()eted and
* must not be used. */
struct llist_head hnb_list;
void context_map_deactivate(struct hnbgw_context_map *map);
/* entry in the per-SCCP-conn-id hashtable */
struct hlist_node hnbgw_sccp_user_entry;
int context_map_init(struct hnb_gw *gw);
/* Pointer to HNB for this map, to transceive RUA. If the HNB has disconnected without releasing the RUA
* context, this is NULL. */
struct hnb_context *hnb_ctx;
/* RUA context ID used in RUA messages to/from the hnb_gw. */
uint32_t rua_ctx_id;
/* FSM handling the RUA state for rua_ctx_id. */
struct osmo_fsm_inst *rua_fi;
/* State context related to field rua_fi above: */
struct {
/* Whether RUA Disconnect received from HNB happened as a normal condition or an error/abnormal condition.
* This is known based on cause and/or RANAP message included in the RUA
* Disconnect message, and tells us whether we should immediately
* terminate the related SCCP session or wait for CN to finish it.
* Defaults to false, only set to true explicitly when needed. */
bool rua_disconnect_err_condition;
} rua_fi_ctx;
/* Pointer to CN, to transceive SCCP. */
struct hnbgw_cnlink *cnlink;
/* SCCP User SAP connection ID used in SCCP messages to/from the cn_link. */
uint32_t scu_conn_id;
/* FSM handling the SCCP state for scu_conn_id. */
struct osmo_fsm_inst *sccp_fi;
/* State context related to field sccp_fi above: */
struct {
/* List of cached packets received from RUA and to be forwarded
once SCCP CReq is CC'ed and move to CONNECTED state. */
struct llist_head wait_cc_tx_msg_list;
} sccp_fi_ctx;
/* False for CS, true for PS */
bool is_ps;
/* Information extracted from RUA Connect's RANAP InitialUE message */
struct hnbgw_l3_peek l3;
/* When an FSM is asked to disconnect but must still wait for a response, it may set this flag, to continue to
* disconnect once the response is in. In particular, when SCCP is asked to disconnect after an SCCP Connection
* Request was already sent and while waiting for a Connection Confirmed, we should still wait for the SCCP CC
* and immediately release it after that, to not leak the connection. */
bool please_disconnect;
/* FSM instance for the MGW, handles the async MGCP communication necessary to intercept CS RAB Assignment and
* redirect the RTP via the MGW. */
struct osmo_fsm_inst *mgw_fi;
/* FSMs handling RANAP RAB assignments for PS, list of struct ps_rab_ass. They handle the async PFCP
* communication necessary to intercept PS RAB Assignment and redirect the GTP via the UPF.
*
* For PS RAB Assignment, each Request gets one ps_rab_ass FSM and each Response gets one ps_rab_ass FSM.
* The reason is that theoretically, each such message can contain any number and any combination of RAB IDs,
* and Request and Response don't necessarily match the RAB IDs contained. In practice I only ever see a single
* RAB matching in Request and Response, but we cannot rely on that to always be true.
*
* The state of each RAB's PFCP negotiation is kept separately in the field ps_rab_list, and as soon as all RABs
* appearing in a PS RAB Assignment message have completed their PFCP setup, we can replace the GTP info for the
* RAB IDs and forward the RAB Assignment Request to HNB / the RAB Assignment Response to CN.
*/
struct llist_head ps_rab_ass_list;
/* All PS RABs and their GTP tunnel mappings. list of struct ps_rab. Each ps_rab FSM handles the PFCP
* communication for one particular RAB ID. */
struct llist_head ps_rab_list;
/* RAB state tracking. As RAB-ID is an 8-bit integer, we need 256 elements in the array */
uint8_t rab_state[256];
/* Flag to prevent calling context_map_free() from cleanup code paths triggered by context_map_free() itself. */
bool deallocating;
};
enum hnbgw_context_map_state context_map_get_state(struct hnbgw_context_map *map);
enum hnbgw_context_map_state map_rua_get_state(struct hnbgw_context_map *map);
enum hnbgw_context_map_state map_sccp_get_state(struct hnbgw_context_map *map);
struct hnbgw_context_map *context_map_find_by_rua_ctx_id(struct hnb_context *hnb, uint32_t rua_ctx_id, bool is_ps);
struct hnbgw_context_map *context_map_alloc(struct hnb_context *hnb, uint32_t rua_ctx_id, bool is_ps);
int context_map_set_cnlink(struct hnbgw_context_map *map, struct hnbgw_cnlink *cnlink_selected);
void map_rua_fsm_alloc(struct hnbgw_context_map *map);
void map_sccp_fsm_alloc(struct hnbgw_context_map *map);
void context_map_hnb_released(struct hnbgw_context_map *map);
#define map_rua_dispatch(MAP, EVENT, MSGB) \
_map_rua_dispatch(MAP, EVENT, MSGB, __FILE__, __LINE__)
int _map_rua_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg,
const char *file, int line);
#define map_sccp_dispatch(MAP, EVENT, MSGB) \
_map_sccp_dispatch(MAP, EVENT, MSGB, __FILE__, __LINE__)
int _map_sccp_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg,
const char *file, int line);
bool map_rua_is_active(struct hnbgw_context_map *map);
bool map_sccp_is_active(struct hnbgw_context_map *map);
void context_map_cnlink_lost(struct hnbgw_context_map *map);
void context_map_free(struct hnbgw_context_map *map);
unsigned int msg_has_l2_data(const struct msgb *msg);

View File

@@ -0,0 +1,76 @@
#pragma once
#include <osmocom/core/select.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/hashtable.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/sigtran/osmo_ss7.h>
#include <osmocom/ctrl/control_if.h>
#include <osmocom/ranap/RANAP_CN-DomainIndicator.h>
#define DEBUG
#include <osmocom/core/logging.h>
#include <osmocom/netif/stream.h>
#include <osmocom/mgcp_client/mgcp_client.h>
#include <osmocom/mgcp_client/mgcp_client_pool.h>
#include <osmocom/hnbgw/umts_cell_id.h>
#include <osmocom/hnbgw/nft_kpi.h>
#include <osmocom/hnbgw/cnlink.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#define HNB_STORE_RAB_DURATIONS_INTERVAL 1 /* seconds */
#define LOGHNB(HNB_CTX, ss, lvl, fmt, args ...) \
LOGP(ss, lvl, "(%s) " fmt, hnb_context_name(HNB_CTX), ## args)
enum hnb_ctrl_node {
CTRL_NODE_HNB = _LAST_CTRL_NODE,
_LAST_CTRL_NODE_HNB
};
/* The lifecycle of the hnb_context object is the same as its conn */
struct hnb_context {
/*! Entry in HNB-global list of HNB */
struct llist_head list;
/*! SCTP socket + write queue for Iuh to this specific HNB */
struct osmo_stream_srv *conn;
/*! copied from HNB-Identity-Info IE */
char identity_info[256];
/*! copied from Cell Identity IE */
struct umts_cell_id id;
/*! SCTP stream ID for HNBAP */
uint16_t hnbap_stream;
/*! SCTP stream ID for RUA */
uint16_t rua_stream;
/*! True if a HNB-REGISTER-REQ from this HNB has been accepted. */
bool hnb_registered;
/* linked list of hnbgw_context_map */
struct llist_head map_list;
/*! pointer to the associated hnb persistent state. Always present after HNB-Register */
struct hnb_persistent *persistent;
};
int hnbgw_rua_accept_cb(struct osmo_stream_srv_link *srv, int fd);
int hnb_ctrl_cmds_install(void);
int hnb_ctrl_node_lookup(void *data, vector vline, int *node_type, void **node_data, int *i);
struct hnb_context *hnb_context_by_identity_info(const char *identity_info);
const char *hnb_context_name(struct hnb_context *ctx);
void hnb_context_release(struct hnb_context *ctx);
void hnb_context_release_ue_state(struct hnb_context *ctx);
unsigned long long hnb_get_updowntime(const struct hnb_context *ctx);
void hnb_store_rab_durations(struct hnb_context *hnb);

View File

@@ -0,0 +1,193 @@
#pragma once
#include <osmocom/core/select.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/hashtable.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/sigtran/osmo_ss7.h>
#include <osmocom/ctrl/control_if.h>
#include <osmocom/ranap/RANAP_CN-DomainIndicator.h>
#define DEBUG
#include <osmocom/core/logging.h>
#include <osmocom/mgcp_client/mgcp_client.h>
#include <osmocom/mgcp_client/mgcp_client_pool.h>
#include <osmocom/hnbgw/umts_cell_id.h>
#include <osmocom/hnbgw/nft_kpi.h>
#include <osmocom/hnbgw/cnlink.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#define LOG_HNBP(HNBP, lvl, fmt, args...) \
LOGP(DHNB, lvl, "(%s) " fmt, \
(HNBP) ? \
(((HNBP)->id_str && *(HNBP)->id_str) ? (HNBP)->id_str : "no-cell-id") \
: "null", ## args)
enum hnb_rate_ctr {
HNB_CTR_IUH_ESTABLISHED,
HNB_CTR_RANAP_PS_ERR_IND_UL,
HNB_CTR_RANAP_CS_ERR_IND_UL,
HNB_CTR_RANAP_PS_RESET_REQ_UL,
HNB_CTR_RANAP_CS_RESET_REQ_UL,
HNB_CTR_RANAP_PS_RAB_ACT_REQ,
HNB_CTR_RANAP_CS_RAB_ACT_REQ,
HNB_CTR_RANAP_PS_RAB_ACT_REQ_UNEXP,
HNB_CTR_RANAP_CS_RAB_ACT_REQ_UNEXP,
HNB_CTR_RANAP_PS_RAB_ACT_CNF,
HNB_CTR_RANAP_CS_RAB_ACT_CNF,
HNB_CTR_RANAP_PS_RAB_ACT_CNF_UNEXP,
HNB_CTR_RANAP_CS_RAB_ACT_CNF_UNEXP,
HNB_CTR_RANAP_PS_RAB_ACT_FAIL,
HNB_CTR_RANAP_CS_RAB_ACT_FAIL,
HNB_CTR_RANAP_PS_RAB_ACT_FAIL_UNEXP,
HNB_CTR_RANAP_CS_RAB_ACT_FAIL_UNEXP,
HNB_CTR_RANAP_PS_RAB_MOD_REQ,
HNB_CTR_RANAP_CS_RAB_MOD_REQ,
HNB_CTR_RANAP_PS_RAB_MOD_CNF,
HNB_CTR_RANAP_CS_RAB_MOD_CNF,
HNB_CTR_RANAP_PS_RAB_MOD_FAIL,
HNB_CTR_RANAP_CS_RAB_MOD_FAIL,
HNB_CTR_RANAP_PS_RAB_REL_REQ,
HNB_CTR_RANAP_CS_RAB_REL_REQ,
HNB_CTR_RANAP_PS_RAB_REL_REQ_ABNORMAL,
HNB_CTR_RANAP_CS_RAB_REL_REQ_ABNORMAL,
HNB_CTR_RANAP_PS_RAB_REL_REQ_UNEXP,
HNB_CTR_RANAP_CS_RAB_REL_REQ_UNEXP,
HNB_CTR_RANAP_PS_RAB_REL_CNF,
HNB_CTR_RANAP_CS_RAB_REL_CNF,
HNB_CTR_RANAP_PS_RAB_REL_CNF_UNEXP,
HNB_CTR_RANAP_CS_RAB_REL_CNF_UNEXP,
HNB_CTR_RANAP_PS_RAB_REL_FAIL,
HNB_CTR_RANAP_CS_RAB_REL_FAIL,
HNB_CTR_RANAP_PS_RAB_REL_FAIL_UNEXP,
HNB_CTR_RANAP_CS_RAB_REL_FAIL_UNEXP,
HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT,
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT,
HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT_ABNORMAL,
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT_ABNORMAL,
HNB_CTR_RUA_ERR_IND,
HNB_CTR_RUA_PS_CONNECT_UL,
HNB_CTR_RUA_CS_CONNECT_UL,
HNB_CTR_RUA_PS_DISCONNECT_UL,
HNB_CTR_RUA_CS_DISCONNECT_UL,
HNB_CTR_RUA_PS_DISCONNECT_DL,
HNB_CTR_RUA_CS_DISCONNECT_DL,
HNB_CTR_RUA_PS_DT_UL,
HNB_CTR_RUA_CS_DT_UL,
HNB_CTR_RUA_PS_DT_DL,
HNB_CTR_RUA_CS_DT_DL,
HNB_CTR_RUA_UDT_UL,
HNB_CTR_RUA_UDT_DL,
HNB_CTR_PS_PAGING_ATTEMPTED,
HNB_CTR_CS_PAGING_ATTEMPTED,
HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL,
HNB_CTR_DTAP_CS_LU_REQ,
HNB_CTR_DTAP_CS_LU_ACC,
HNB_CTR_DTAP_CS_LU_REJ,
HNB_CTR_DTAP_PS_ATT_REQ,
HNB_CTR_DTAP_PS_ATT_ACK,
HNB_CTR_DTAP_PS_ATT_REJ,
HNB_CTR_DTAP_PS_RAU_REQ,
HNB_CTR_DTAP_PS_RAU_ACK,
HNB_CTR_DTAP_PS_RAU_REJ,
HNB_CTR_GTPU_PACKETS_UL,
HNB_CTR_GTPU_TOTAL_BYTES_UL,
HNB_CTR_GTPU_UE_BYTES_UL,
HNB_CTR_GTPU_PACKETS_DL,
HNB_CTR_GTPU_TOTAL_BYTES_DL,
HNB_CTR_GTPU_UE_BYTES_DL,
};
enum hnb_stat {
HNB_STAT_UPTIME_SECONDS,
};
#define HNBP_CTR(hnbp, x) rate_ctr_group_get_ctr((hnbp)->ctrs, x)
#define HNBP_CTR_INC(hnbp, x) rate_ctr_inc(HNBP_CTR(hnbp, x))
#define HNBP_CTR_ADD(hnbp, x, y) rate_ctr_add2((hnbp)->ctrs, x, y)
#define HNBP_STAT(hbp, x) osmo_stat_item_group_get_item((hnbp)->statg, x)
#define HNBP_STAT_SET(hnbp, x, val) osmo_stat_item_set(HNBP_STAT(hnbp, x), val)
/* persistent data for one HNB. This continues to exist even as conn / hnb_context is deleted on disconnect */
struct hnb_persistent {
/*! Entry in HNBGW-global list of hnb_persistent */
struct llist_head list;
/*! Entry in hash table g_hnbgw->hnb_persistent_by_id. */
struct hlist_node node_by_id;
/*! back-pointer to hnb_context. Can be NULL if no context at this point */
struct hnb_context *ctx;
/*! unique cell identity; copied from HNB REGISTER REQ */
struct umts_cell_id id;
/*! stringified version of the cell identiy above (for printing/naming) */
const char *id_str;
/*! copied from HNB-Identity-Info IE */
time_t updowntime;
struct rate_ctr_group *ctrs;
struct osmo_stat_item_group *statg;
/* State that the main thread needs in order to know what was requested from the nft worker threads and what
* still needs to be requested. */
struct {
/* Whether a persistent named counter was added in nftables for this cell id. */
bool persistent_counter_added;
/* The last hNodeB GTP-U address we asked the nft maintenance thread to set up.
* osmo_sockaddr_str_is_nonzero(addr_remote) == false when no rules were added yet, and when
* we asked the nft maintenance thread to remove the rules for this hNodeB because it has
* disconnected. */
struct osmo_sockaddr_str addr_remote;
/* the nft handles needed to clean up the UL and DL rules when the hNodeB disconnects,
* and the last seen counter value gotten from nft. */
struct {
struct nft_kpi_handle h;
struct nft_kpi_val v;
} ul;
struct {
struct nft_kpi_handle h;
struct nft_kpi_val v;
} dl;
} nft_kpi;
struct osmo_timer_list disconnected_timeout;
};
struct hnb_persistent *hnb_persistent_alloc(const struct umts_cell_id *id);
struct hnb_persistent *hnb_persistent_find_by_id(const struct umts_cell_id *id);
void hnb_persistent_registered(struct hnb_persistent *hnbp);
void hnb_persistent_deregistered(struct hnb_persistent *hnbp);
void hnb_persistent_free(struct hnb_persistent *hnbp);
unsigned long long hnbp_get_updowntime(const struct hnb_persistent *hnbp);

View File

@@ -2,29 +2,50 @@
#include <osmocom/core/select.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/hashtable.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/core/timer.h>
#include <osmocom/sigtran/sccp_sap.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/sigtran/osmo_ss7.h>
#include <osmocom/ctrl/control_if.h>
#include <osmocom/ranap/RANAP_CN-DomainIndicator.h>
#define DEBUG
#include <osmocom/core/logging.h>
#include <osmocom/mgcp_client/mgcp_client.h>
#include <osmocom/mgcp_client/mgcp_client_pool.h>
#include <osmocom/hnbgw/nft_kpi.h>
#include <osmocom/hnbgw/cnlink.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#define STORE_UPTIME_INTERVAL 10 /* seconds */
enum {
DMAIN,
DHNBAP,
DRUA,
DRANAP,
DMGW,
DHNB,
DCN,
DNFT,
};
#define LOGHNB(x, ss, lvl, fmt, args ...) \
LOGP(ss, lvl, "%s " fmt, hnb_context_name(x), ## args)
extern const struct log_info hnbgw_log_info;
extern struct vty_app_info hnbgw_vty_info;
enum hnb_ctrl_node {
CTRL_NODE_HNB = _LAST_CTRL_NODE,
_LAST_CTRL_NODE_HNB
};
#define DOMAIN_CS RANAP_CN_DomainIndicator_cs_domain
#define DOMAIN_PS RANAP_CN_DomainIndicator_ps_domain
extern const struct value_string ranap_domain_names[];
static inline const char *ranap_domain_name(RANAP_CN_DomainIndicator_t domain)
{
return get_value_string(ranap_domain_names, domain);
}
#define HNBGW_LOCAL_IP_DEFAULT "0.0.0.0"
/* TODO: CS and PS now both connect to OsmoSTP, i.e. that's always going to be the same address. Drop the
@@ -32,6 +53,12 @@ enum hnb_ctrl_node {
#define HNBGW_IUCS_REMOTE_IP_DEFAULT "127.0.0.1"
#define HNBGW_IUPS_REMOTE_IP_DEFAULT "127.0.0.1"
#define DEFAULT_PC_HNBGW ((23 << 3) + 5)
#define DEFAULT_PC_MSC ((23 << 3) + 1)
#define DEFAULT_PC_SGSN ((23 << 3) + 4)
#define DEFAULT_ADDR_NAME_MSC "addr-dyn-msc-default"
#define DEFAULT_ADDR_NAME_SGSN "addr-dyn-sgsn-default"
/* 25.467 Section 7.1 */
#define IUH_DEFAULT_SCTP_PORT 29169
#define RNA_DEFAULT_SCTP_PORT 25471
@@ -44,82 +71,19 @@ enum hnb_ctrl_node {
#define IUH_MSGB_SIZE 2048
struct umts_cell_id {
uint16_t mcc; /*!< Mobile Country Code */
uint16_t mnc; /*!< Mobile Network Code */
uint16_t lac; /*!< Locaton Area Code */
uint16_t rac; /*!< Routing Area Code */
uint16_t sac; /*!< Service Area Code */
uint32_t cid; /*!< Cell ID */
};
struct hnbgw_context_map;
struct hnb_gw;
static inline bool cnlink_is_cs(const struct hnbgw_cnlink *cnlink)
{
return cnlink && cnlink->pool->domain == DOMAIN_CS;
}
enum hnbgw_cnlink_state {
/* we have just been initialized or were disconnected */
CNLINK_S_NULL,
/* establishment of the SUA/SCCP link is pending */
CNLINK_S_EST_PEND,
/* establishment of the SUA/SCCP link was confirmed */
CNLINK_S_EST_CONF,
/* we have esnt the RANAP RESET and wait for the ACK */
CNLINK_S_EST_RST_TX_WAIT_ACK,
/* we have received the RANAP RESET ACK and are active */
CNLINK_S_EST_ACTIVE,
};
static inline bool cnlink_is_ps(const struct hnbgw_cnlink *cnlink)
{
return cnlink && cnlink->pool->domain == DOMAIN_PS;
}
struct hnbgw_cnlink {
struct llist_head list;
enum hnbgw_cnlink_state state;
struct hnb_gw *gw;
/* timer for re-transmitting the RANAP Reset */
struct osmo_timer_list T_RafC;
/* reference to the SCCP User SAP by which we communicate */
struct osmo_sccp_instance *sccp;
struct osmo_sccp_user *sccp_user;
uint32_t next_conn_id;
/* linked list of hnbgw_context_map */
struct llist_head map_list;
};
struct hnb_context {
/*! Entry in HNB-global list of HNB */
struct llist_head list;
/*! HNB-GW we are part of */
struct hnb_gw *gw;
/*! SCTP socket + write queue for Iuh to this specific HNB */
struct osmo_stream_srv *conn;
/*! copied from HNB-Identity-Info IE */
char identity_info[256];
/*! copied from Cell Identity IE */
struct umts_cell_id id;
/*! SCTP stream ID for HNBAP */
uint16_t hnbap_stream;
/*! SCTP stream ID for RUA */
uint16_t rua_stream;
/*! True if a HNB-REGISTER-REQ from this HNB has been accepted. Note that
* this entire data structure is freed if the HNB sends HNB-DE-REGISTER-REQ. */
bool hnb_registered;
/* linked list of hnbgw_context_map */
struct llist_head map_list;
};
struct ue_context {
/*! Entry in the HNB-global list of UE */
struct llist_head list;
/*! Unique Context ID for this UE */
uint32_t context_id;
char imsi[16+1];
uint32_t tmsi;
/*! UE is serviced via this HNB */
struct hnb_context *hnb;
};
struct hnb_gw {
struct hnbgw {
struct {
const char *iuh_local_ip;
/*! SCTP port for Iuh listening */
@@ -127,48 +91,97 @@ struct hnb_gw {
/*! The UDP port where we receive multiplexed CS user
* plane traffic from HNBs */
uint16_t iuh_cs_mux_port;
const char *iucs_remote_addr_name;
const char *iups_remote_addr_name;
struct osmo_plmn_id plmn;
uint16_t rnc_id;
bool hnbap_allow_tmsi;
/*! print hnb-id (true) or MCC-MNC-LAC-RAC-SAC (false) in logs */
bool log_prefix_hnb_id;
bool accept_all_hnb;
struct mgcp_client_conf *mgcp_client;
struct {
char *local_addr;
uint16_t local_port;
char *remote_addr;
uint16_t remote_port;
struct {
char *access;
char *core;
} netinst;
} pfcp;
struct {
bool enable;
/* The table name as used in nftables for the ruleset owned by this process. It is "osmo-hnbgw"
* by default. */
char *table_name;
} nft_kpi;
} config;
/*! SCTP listen socket for incoming connections */
struct osmo_stream_srv_link *iuh;
/* list of struct hnb_context */
struct llist_head hnb_list;
/* list of struct ue_context */
struct llist_head ue_list;
/* list of struct hnb_persistent */
struct llist_head hnb_persistent_list;
/* optimized lookup for hnb_persistent, by cell id string */
DECLARE_HASHTABLE(hnb_persistent_by_id, 5);
struct osmo_timer_list store_uptime_timer;
/* next availble UE Context ID */
uint32_t next_ue_ctx_id;
struct ctrl_handle *ctrl;
/* currently active CN links for CS and PS */
struct {
struct osmo_sccp_instance *client;
struct hnbgw_cnlink *cnlink;
struct osmo_sccp_addr local_addr;
struct osmo_sccp_addr iucs_remote_addr;
struct osmo_sccp_addr iups_remote_addr;
/* List of hnbgw_sccp_user */
struct llist_head users;
/* Pool of core network peers: MSCs for IuCS */
struct hnbgw_cnpool *cnpool_iucs;
/* Pool of core network peers: SGSNs for IuPS */
struct hnbgw_cnpool *cnpool_iups;
} sccp;
/* MGW pool, also includes the single MGCP client as fallback if no
* pool is configured. */
struct mgcp_client_pool *mgw_pool;
struct {
struct osmo_pfcp_endpoint *ep;
struct osmo_pfcp_cp_peer *cp_peer;
/* Running counters for the PFCP conn */
struct osmo_stat_item_group *statg;
} pfcp;
struct osmo_timer_list hnb_store_rab_durations_timer;
struct {
bool active;
struct osmo_timer_list get_counters_timer;
struct timespec next_timer;
} nft_kpi;
};
extern struct hnbgw *g_hnbgw;
extern void *talloc_asn1_ctx;
struct hnb_context *hnb_context_by_id(struct hnb_gw *gw, uint32_t cid);
struct hnb_context *hnb_context_by_identity_info(struct hnb_gw *gw, const char *identity_info);
const char *hnb_context_name(struct hnb_context *ctx);
unsigned hnb_contexts(const struct hnb_gw *gw);
void g_hnbgw_alloc(void *ctx);
struct ue_context *ue_context_by_id(struct hnb_gw *gw, uint32_t id);
struct ue_context *ue_context_by_imsi(struct hnb_gw *gw, const char *imsi);
struct ue_context *ue_context_by_tmsi(struct hnb_gw *gw, uint32_t tmsi);
struct ue_context *ue_context_alloc(struct hnb_context *hnb, const char *imsi,
uint32_t tmsi);
void ue_context_free(struct ue_context *ue);
int hnbgw_rua_accept_cb(struct osmo_stream_srv_link *srv, int fd);
int hnbgw_mgw_setup(void);
struct hnb_context *hnb_context_alloc(struct hnb_gw *gw, struct osmo_stream_srv_link *link, int new_fd);
void hnb_context_release(struct hnb_context *ctx);
void hnbgw_vty_init(struct hnb_gw *gw, void *tall_ctx);
void hnbgw_vty_init(void);
int hnbgw_vty_go_parent(struct vty *vty);
/* Return true when the user configured GTP mapping to be enabled, by configuring a PFCP link to a UPF.
* Return false when the user configured to skip GTP mapping and RANAP PS RAB Requests/Responses should be passed thru
* 1:1.
* GTP mapping means that there are two GTP tunnels, one towards HNB and one towards CN, and we forward payloads between
* the two tunnels, mapping the TEIDs and GTP addresses. */
static inline bool hnb_gw_is_gtp_mapping_enabled(void)
{
return g_hnbgw->config.pfcp.remote_addr != NULL;
}
struct msgb *hnbgw_ranap_msg_alloc(const char *name);
int hnbgw_peek_l3_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg);
uint32_t get_next_ue_ctx_id(void);

View File

@@ -1,5 +1,64 @@
#pragma once
#include <osmocom/hnbgw/hnbgw.h>
#include <stdint.h>
#include <osmocom/core/rate_ctr.h>
int hnbgw_cnlink_init(struct hnb_gw *gw, const char *stp_host, uint16_t stp_port, const char *local_ip);
#include <osmocom/sigtran/sccp_sap.h>
#include <osmocom/ranap/RANAP_CN-DomainIndicator.h>
struct hnbgw_context_map;
enum hnbgw_cnpool_ctr {
/* TODO: basic counters completely missing
* ...
*/
/* Counters related to link selection from a CN pool. */
CNPOOL_CTR_SUBSCR_NO_CNLINK,
CNPOOL_CTR_EMERG_FORWARDED,
CNPOOL_CTR_EMERG_LOST,
};
#define CNPOOL_CTR_INC(cnpool, x) rate_ctr_inc2((cnpool)->ctrs, x)
/* User provided configuration for struct hnbgw_cnpool. */
struct hnbgw_cnpool_cfg {
uint8_t nri_bitlen;
struct osmo_nri_ranges *null_nri_ranges;
};
/* Collection of CN peers to distribute UE connections across. MSCs for DOMAIN_CS, SGSNs for DOMAIN_PS. */
struct hnbgw_cnpool {
RANAP_CN_DomainIndicator_t domain;
/* CN pool string used in VTY config and logging, "iucs" or "iups". */
const char *pool_name;
/* CN peer string used in VTY config and logging, "msc" or "sgsn". */
const char *peer_name;
/* What we use as the remote MSC/SGSN point-code if the user does not configure any address. */
uint32_t default_remote_pc;
const char *default_addr_name;
struct hnbgw_cnpool_cfg vty;
struct hnbgw_cnpool_cfg use;
/* List of struct hnbgw_cnlink */
struct llist_head cnlinks;
unsigned int round_robin_next_nr;
/* Emergency calls potentially select a different set of MSCs, so to not mess up the normal round-robin
* behavior, emergency calls need a separate round-robin counter. */
unsigned int round_robin_next_emerg_nr;
/* Running counters for this pool */
struct rate_ctr_group *ctrs;
};
struct hnbgw_cnpool *hnbgw_cnpool_alloc(RANAP_CN_DomainIndicator_t domain);
struct hnbgw_cnlink *hnbgw_cnlink_select(struct hnbgw_context_map *map);
void hnbgw_cnpool_start(struct hnbgw_cnpool *cnpool);
void hnbgw_cnpool_cnlinks_start_or_restart(struct hnbgw_cnpool *cnpool);
struct hnbgw_cnlink *cnlink_get_nr(struct hnbgw_cnpool *cnpool, int nr, bool create_if_missing);
void hnbgw_cnpool_apply_cfg(struct hnbgw_cnpool *cnpool);

View File

@@ -1,6 +1,9 @@
/* HNBAP, 3GPP TS 25.469 */
#pragma once
#include <osmocom/hnbgw/hnbgw.h>
struct hnb_context;
int hnbgw_hnbap_rx(struct hnb_context *hnb, struct msgb *msg);
int hnbgw_hnbap_init(void);

View File

@@ -0,0 +1,9 @@
#pragma once
enum hnbgw_upf_stats {
HNBGW_UPF_STAT_ASSOCIATED,
};
#define HNBGW_UPF_STAT_SET(stat, val) osmo_stat_item_set(osmo_stat_item_group_get_item(g_hnbgw->pfcp.statg, (stat)), (val))
int hnbgw_pfcp_init(void);
void hnbgw_pfcp_release(void);

View File

@@ -1,6 +1,18 @@
/* RANAP, 3GPP TS 25.413 */
#pragma once
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/hnbgw/hnbgw.h>
int hnbgw_ranap_rx(struct msgb *msg, uint8_t *data, size_t len);
struct osmo_scu_unitdata_param;
ranap_message *hnbgw_decode_ranap_cn_co(struct msgb *ranap_msg);
int hnbgw_ranap_rx_udt_ul(struct msgb *msg, uint8_t *data, size_t len);
int hnbgw_ranap_rx_data_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg);
int hnbgw_ranap_rx_udt_dl(struct hnbgw_cnlink *cnlink, const struct osmo_scu_unitdata_param *unitdata,
const uint8_t *data, unsigned int len);
int hnbgw_ranap_rx_data_dl(struct hnbgw_context_map *map, struct msgb *ranap_msg);
int hnbgw_ranap_init(void);

View File

@@ -1,13 +1,16 @@
/* RUA, 3GPP TS 25.468 */
#pragma once
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/rua/RUA_Cause.h>
struct hnb_context;
int hnbgw_rua_rx(struct hnb_context *hnb, struct msgb *msg);
int hnbgw_rua_init(void);
int rua_tx_udt(struct hnb_context *hnb, const uint8_t *data, unsigned int len);
int rua_tx_dt(struct hnb_context *hnb, int is_ps, uint32_t context_id,
const uint8_t *data, unsigned int len);
int rua_tx_disc(struct hnb_context *hnb, int is_ps, uint32_t context_id,
const RUA_Cause_t *cause, const uint8_t *data, unsigned int len);

View File

@@ -0,0 +1,66 @@
/* SCCP, ITU Q.711 - Q.714 */
#pragma once
#include <stdint.h>
#include <osmocom/core/hashtable.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/prim.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/use_count.h>
#include <osmocom/sigtran/sccp_sap.h>
struct hnbgw_cnlink;
/* osmo-hnbgw keeps a single hnbgw_sccp_user per osmo_sccp_instance, for the local point-code and SSN == RANAP.
* This relates the (opaque) osmo_sccp_user to osmo-hnbgw's per-ss7 state. */
struct hnbgw_sccp_user {
/* entry in g_hnbgw->sccp.users */
struct llist_head entry;
/* logging context */
char *name;
/* Which 'cs7 instance' is this for? Below sccp_user is registered at the osmo_sccp_instance ss7->sccp. */
struct osmo_ss7_instance *ss7;
/* Local address: cs7 instance's primary PC if present, else the default HNBGW PC; with SSN == RANAP. */
struct osmo_sccp_addr local_addr;
/* osmo_sccp API state for above local address on above ss7 instance. */
struct osmo_sccp_user *sccp_user;
/* Ref count of users of this struct, ie.referencing it in cnlink->hnbgw_sccp_user */
struct osmo_use_count use_count;
/* Fast access to the hnbgw_context_map responsible for a given SCCP conn_id of the ss7->sccp instance.
* hlist_node: hnbgw_context_map->hnbgw_sccp_user_entry. */
DECLARE_HASHTABLE(hnbgw_context_map_by_conn_id, 6);
};
#define LOG_HSU(HSU, SUBSYS, LEVEL, FMT, ARGS...) \
LOGP(SUBSYS, LEVEL, "(%s) " FMT, (HSU) ? (HSU)->name : "null", ##ARGS)
#define HSU_USE_CNLINK "cnlink"
#define hnbgw_sccp_user_get(hsu, use) \
OSMO_ASSERT(osmo_use_count_get_put(&(hsu)->use_count, use, 1) == 0)
#define hnbgw_sccp_user_put(hsu, use) \
OSMO_ASSERT(osmo_use_count_get_put(&(hsu)->use_count, use, -1) == 0)
struct hnbgw_sccp_user *hnbgw_sccp_user_alloc(int ss7_inst_id);
int hnbgw_sccp_user_tx_unitdata_req(struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *called_addr,
struct msgb *ranap_msg);
int hnbgw_sccp_user_tx_connect_req(struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *called_addr,
uint32_t scu_conn_id, struct msgb *ranap_msg);
int hnbgw_sccp_user_tx_data_req(struct hnbgw_sccp_user *hsu, uint32_t scu_conn_id,
struct msgb *ranap_msg);
int hnbgw_sccp_user_tx_disconnect_req(struct hnbgw_sccp_user *hsu, uint32_t scu_conn_id);
static inline struct osmo_sccp_instance *hnbgw_sccp_user_get_sccp_instance(const struct hnbgw_sccp_user *hsu)
{
if (!hsu->ss7)
return NULL;
return osmo_ss7_get_sccp(hsu->ss7);
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/hnbgw/hnbgw.h>
void kpi_ranap_process_ul(struct hnbgw_context_map *map, ranap_message *ranap);
void kpi_ranap_process_dl(struct hnbgw_context_map *map, ranap_message *ranap);
void kpi_dtap_process_ul(struct hnbgw_context_map *map, const uint8_t *buf, unsigned int len, uint8_t sapi);
void kpi_dtap_process_dl(struct hnbgw_context_map *map, const uint8_t *buf, unsigned int len, uint8_t sapi);

View File

@@ -0,0 +1,9 @@
#pragma once
#include <osmocom/ranap/ranap_ies_defs.h>
int handle_cs_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message);
int mgw_fsm_handle_cs_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message);
int mgw_fsm_release(struct hnbgw_context_map *map);
uint64_t mgw_fsm_get_elapsed_ms(struct hnbgw_context_map *map, const struct timespec *now);

View File

@@ -0,0 +1,25 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
struct hnb_persistent;
/* A "handle" that nftables returns for chains and rules -- a plain number. Deleting an unnamed rule can only be done by
* such a handle. */
struct nft_kpi_handle {
bool handle_present;
int64_t handle;
};
/* One GTP-U packet and byte counter cache, i.e. for one UL/DL direction of one hNodeB. */
struct nft_kpi_val {
uint64_t packets;
uint64_t total_bytes;
uint64_t ue_bytes;
};
void nft_kpi_init(const char *table_name);
void nft_kpi_hnb_persistent_add(struct hnb_persistent *hnbp);
void nft_kpi_hnb_persistent_remove(struct hnb_persistent *hnbp);
int nft_kpi_hnb_start(struct hnb_persistent *hnbp, const struct osmo_sockaddr_str *gtpu_remote);
void nft_kpi_hnb_stop(struct hnb_persistent *hnbp);

View File

@@ -0,0 +1,15 @@
#pragma once
#include <osmocom/ranap/ranap_ies_defs.h>
enum ps_rab_ass_fsm_event {
PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX,
PS_RAB_ASS_EV_RAB_ASS_RESP,
PS_RAB_ASS_EV_RAB_ESTABLISHED,
PS_RAB_ASS_EV_RAB_RELEASED,
PS_RAB_ASS_EV_RAB_FAIL,
};
int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message);
int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message);
void hnbgw_gtpmap_release(struct hnbgw_context_map *map);

View File

@@ -0,0 +1,99 @@
#pragma once
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/use_count.h>
#include <osmocom/pfcp/pfcp_msg.h>
/* A GTP tunnel has two endpoints, each endpoint has an IP address and a Tunnel Endpoint ID. So two struct addr_teid
* identify one GTP tunnel. For GTP mapping between HNB and CN, we have two tunnels, see also struct half_gtp_map. The
* combination of IP address and TEID is also known as F-TEID (fully qualified TEID). */
struct addr_teid {
bool present;
struct osmo_sockaddr addr;
uint32_t teid;
};
/* One half_gtp_map represents one GTP tunnel, either on the HNB side or on the CN side. Two struct half_gtp_map make up
* a GTP mapping between HNB and CN. One half_gtp_map for the Access (HNB) side, one for the Core (CN) side. The PFCP
* PDR (Packet Detection Rule) identifies packets coming in on the GTP tunnel the half_gtp_map represents, while the
* PFCP FAR (Forwarding Action Rule) identifies the GTP destination, i.e. the other side's GTP tunnel. So a
* half_gtp_map.far_id is closely tied to the other half_gtp_map, and makes little sense on its own.
*
* half_gtp_map | half_gtp_map
* Access HNBGW+UPF Core
* remote local | local remote
* -->PDR-FAR-->|
* |<--FAR-PDR<--
*
* See ps_rab.core, ps_rab.access.
*/
struct half_gtp_map {
/* GTP endpoint, obtained from incoming RAB Assignment Request/Response.
* This is the remote side as seen from the UPF's point of view.
* For example, ps_rab.core.remote is the CN GTP that the RAB Assignment Request told us.
* ps_rab.access.remote is the HNB GTP that RAB Assignment Response told us. */
struct addr_teid remote;
/* UPF GTP endpoint, obtained from PFCP Session Establishment Response. */
struct addr_teid local;
/* PFCP Packet Detection Rule id that detects GTP-U packets coming from Core/Access */
uint16_t pdr_id;
/* PFCP Forward Action Rule id that forwards GTP-U packets to Access/Core */
uint32_t far_id;
/* Whether the RANAP message this RAB's remote address was obtained from had the address encoded in x213_nsap */
bool use_x213_nsap;
};
/* A PS RAB's PFCP state. For the related RANAP state, see struct ps_rab_ass instead. */
struct ps_rab {
/* Instance of ps_rab_fsm. */
struct osmo_fsm_inst *fi;
/* List entry and backpointer.
* If map == NULL, do not call llist_del(&entry): the hnbgw_context_map may deallocate before the PFCP release
* is complete, in which case it sets map = NULL. */
struct llist_head entry;
struct hnbgw_context_map *map;
/* RAB-ID used in RANAP RAB AssignmentRequest and Response messages */
uint8_t rab_id;
/* Backpointer to the ps_rab_ass_fsm for the RAB Assignment Request from Core that created this RAB.
* There are two separate RAB Assignment FSMs responsible for this RAB, one for the Request message and one for
* the Response message. Each RAB Assignment FSM may be responsible for N other RABs besides this one. */
struct osmo_fsm_inst *req_fi;
/* Backpointer to the ps_rab_ass_fsm for the RAB Assignment Response from Access that confirmed this RAB. */
struct osmo_fsm_inst *resp_fi;
/* PFCP session controlling the GTP mapping for this RAB */
uint64_t cp_seid;
struct osmo_pfcp_ie_f_seid up_f_seid;
bool release_requested;
/* 'local' and 'remote' refer to the GTP information from the UPF's point of view:
* HNB UPF CN
* access.remote <---> access.local | core.local <---> core.remote
*/
struct half_gtp_map core;
struct half_gtp_map access;
struct osmo_use_count use_count;
};
struct ps_rab *ps_rab_start(struct hnbgw_context_map *map, uint8_t rab_id,
const struct addr_teid *core_f_teid, bool use_x213_nsap,
struct osmo_fsm_inst *req_fi);
struct ps_rab *ps_rab_get(struct hnbgw_context_map *map, uint8_t rab_id);
bool ps_rab_is_established(const struct ps_rab *rab);
void ps_rab_release(struct ps_rab *rab, struct osmo_fsm_inst *notify_fi);
struct ps_rab_rx_args {
struct addr_teid f_teid;
bool use_x213_nsap;
struct osmo_fsm_inst *notify_fi;
};
int ps_rab_rx_access_remote_f_teid(struct hnbgw_context_map *map, uint8_t rab_id,
const struct ps_rab_rx_args *args);
struct ps_rab *ps_rab_find_by_seid(uint64_t seid, bool is_cp_seid);
void ps_rab_pfcp_set_msg_ctx(struct ps_rab *rab, struct osmo_pfcp_msg *m);

View File

@@ -0,0 +1,20 @@
#pragma once
struct msgb *ranap_rab_ass_req_encode(RANAP_RAB_AssignmentRequestIEs_t *rab_assignment_request_ies);
int ranap_rab_ass_resp_encode(uint8_t *data, unsigned int len,
RANAP_RAB_AssignmentResponseIEs_t *rab_assignment_response_ies);
int ranap_rab_ass_req_ies_extract_inet_addr(struct osmo_sockaddr *addr, uint8_t *rab_id,
RANAP_RAB_AssignmentRequestIEs_t *ies, unsigned int index);
int ranap_rab_ass_resp_ies_extract_inet_addr(struct osmo_sockaddr *addr, RANAP_RAB_AssignmentResponseIEs_t *ies,
uint8_t rab_id);
int ranap_rab_ass_req_ies_replace_inet_addr(RANAP_RAB_AssignmentRequestIEs_t *ies, struct osmo_sockaddr *addr,
uint8_t rab_id);
int ranap_rab_ass_resp_ies_replace_inet_addr(RANAP_RAB_AssignmentResponseIEs_t *ies, struct osmo_sockaddr *addr,
uint8_t rab_id);
bool ranap_rab_ass_req_ies_check_release(RANAP_RAB_AssignmentRequestIEs_t *ies, uint8_t rab_id);
bool ranap_rab_ass_resp_ies_check_failure(RANAP_RAB_AssignmentResponseIEs_t *ies, uint8_t rab_id);
int ranap_rab_ass_req_ies_get_count(RANAP_RAB_AssignmentRequestIEs_t *ies);

View File

@@ -0,0 +1,7 @@
#pragma once
#include <osmocom/core/tdef.h>
extern struct osmo_tdef mgw_fsm_T_defs[];
extern struct osmo_tdef hnbgw_T_defs[];
extern struct osmo_tdef_group hnbgw_tdef_group[];

View File

@@ -0,0 +1,34 @@
#pragma once
#include <stdint.h>
#include <unistd.h>
#include <osmocom/gsm/gsm23003.h>
struct umts_cell_id {
struct osmo_plmn_id plmn; /*!< Mobile Country Code and Mobile Network Code (000-00 to 999-999) */
uint16_t lac; /*!< Locaton Area Code (1-65534) */
uint8_t rac; /*!< Routing Area Code (0-255) */
uint16_t sac; /*!< Service Area Code */
uint32_t cid; /*!< Cell ID */
};
int umts_cell_id_to_str_buf(char *buf, size_t buflen, const struct umts_cell_id *ucid);
char *umts_cell_id_to_str_c(void *ctx, const struct umts_cell_id *ucid);
const char *umts_cell_id_to_str(const struct umts_cell_id *ucid);
int umts_cell_id_from_str(struct umts_cell_id *ucid, const char *instr);
uint32_t umts_cell_id_hash(const struct umts_cell_id *ucid);
/*! are both given umts_cell_id euqal? */
static inline bool umts_cell_id_equal(const struct umts_cell_id *a, const struct umts_cell_id *b)
{
if (osmo_plmn_cmp(&a->plmn, &b->plmn))
return false;
if (a->lac != b->lac)
return false;
if (a->rac != b->rac)
return false;
if (a->sac != b->sac)
return false;
if (a->cid != b->cid)
return false;
return true;
}

View File

@@ -4,8 +4,14 @@
enum osmo_iuh_vty_node {
HNBGW_NODE = _LAST_OSMOVTY_NODE + 1,
HNB_NODE,
IUH_NODE,
IUCS_NODE,
IUPS_NODE,
MGCP_NODE,
MGW_NODE,
PFCP_NODE,
MSC_NODE,
SGSN_NODE,
};

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# (C) 2021 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
# (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
# 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 3 of the License, or

View File

@@ -19,27 +19,45 @@ AM_CFLAGS = \
$(LIBOSMORUA_CFLAGS) \
$(LIBOSMORANAP_CFLAGS) \
$(LIBOSMOHNBAP_CFLAGS) \
$(LIBOSMOMGCPCLIENT_CFLAGS) \
$(LIBNFTABLES_CFLAGS) \
$(NULL)
AM_LDFLAGS = \
$(COVERAGE_LDFLAGS) \
$(NULL)
bin_PROGRAMS = \
osmo-hnbgw \
noinst_LTLIBRARIES = \
libhnbgw.la \
$(NULL)
osmo_hnbgw_SOURCES = \
libhnbgw_la_SOURCES = \
hnb.c \
hnb_persistent.c \
hnbgw.c \
hnbgw_hnbap.c \
hnbgw_l3.c \
hnbgw_rua.c \
hnbgw_ranap.c \
hnbgw_sccp.c \
hnbgw_vty.c \
context_map.c \
context_map_rua.c \
context_map_sccp.c \
hnbgw_cn.c \
cnlink.c \
cnlink_fsm.c \
cnlink_paging.c \
ranap_rab_ass.c \
mgw_fsm.c \
kpi_dtap.c \
kpi_ranap.c \
tdefs.c \
umts_cell_id.c \
nft_kpi.c \
$(NULL)
osmo_hnbgw_LDADD = \
libhnbgw_la_LIBADD = \
$(LIBASN1C_LIBS) \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
@@ -52,4 +70,32 @@ osmo_hnbgw_LDADD = \
$(LIBOSMORANAP_LIBS) \
$(LIBOSMOHNBAP_LIBS) \
$(LIBSCTP_LIBS) \
$(LIBOSMOMGCPCLIENT_LIBS) \
$(LIBNFTABLES_LIBS) \
$(NULL)
if ENABLE_PFCP
AM_CFLAGS += \
$(LIBOSMOPFCP_CFLAGS) \
$(NULL)
libhnbgw_la_LIBADD += \
$(LIBOSMOPFCP_LIBS) \
$(NULL)
libhnbgw_la_SOURCES += \
hnbgw_pfcp.c \
ps_rab_ass_fsm.c \
ps_rab_fsm.c \
$(NULL)
endif
bin_PROGRAMS = \
osmo-hnbgw \
$(NULL)
osmo_hnbgw_SOURCES = \
osmo_hnbgw_main.c \
$(NULL)
osmo_hnbgw_LDADD = \
libhnbgw.la \
$(NULL)

554
src/osmo-hnbgw/cnlink.c Normal file
View File

@@ -0,0 +1,554 @@
/* (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr
*
* 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/fsm.h>
#include <osmocom/core/tdef.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/stat_item.h>
#include <osmocom/gsm/gsm23236.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <asn1c/asn1helpers.h>
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/ranap/ranap_msg_factory.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/tdefs.h>
#include <osmocom/hnbgw/context_map.h>
static const struct rate_ctr_desc cnlink_ctr_description[] = {
[CNLINK_CTR_RANAP_RX_UDT_RESET] = {
"ranap:rx:udt:reset",
"RANAP Unitdata RESET messages received"
},
[CNLINK_CTR_RANAP_RX_UDT_RESET_ACK] = {
"ranap:rx:udt:reset_ack",
"RANAP Unitdata RESET ACK messages received",
},
[CNLINK_CTR_RANAP_RX_UDT_PAGING] = {
"ranap:rx:udt:paging",
"RANAP Unitdata PAGING messages received",
},
[CNLINK_CTR_RANAP_RX_UDT_UNKNOWN] = {
"ranap:rx:udt:unknown",
"Unknown RANAP Unitdata messages received",
},
[CNLINK_CTR_RANAP_RX_UDT_UNSUPPORTED] = {
"ranap:rx:udt:unsupported",
"Unsupported RANAP Unitdata messages received",
},
[CNLINK_CTR_RANAP_RX_UDT_OVERLOAD_IND] = {
"ranap:rx:udt:overload_ind",
"RANAP Unitdata Overload Indications received",
},
[CNLINK_CTR_RANAP_RX_UDT_ERROR_IND] = {
"ranap:rx:udt:error_ind",
"RANAP Unitdata Error Indications received",
},
[CNLINK_CTR_RANAP_TX_UDT_RESET] = {
"ranap:tx:udt:reset",
"RANAP Unitdata RESET messages transmitted",
},
[CNLINK_CTR_RANAP_TX_UDT_RESET_ACK] = {
"ranap:tx:udt:reset_ack",
"RANAP Unitdata RESET ACK messages transmitted",
},
/* SCCP Counters: */
[CNLINK_CTR_SCCP_N_UNITDATA_REQ] = {
"sccp:n_unit_data:req",
"Submit SCCP N-UNITDATA.req (UL)"
},
[CNLINK_CTR_SCCP_N_UNITDATA_IND] = {
"sccp:n_unit_data:ind",
"Received SCCP N-UNITDATA.ind (DL)"
},
[CNLINK_CTR_SCCP_N_NOTICE_IND] = {
"sccp:n_notice:ind",
"Received SCCP N-NOTICE.ind"
},
[CNLINK_CTR_SCCP_N_CONNECT_REQ] = {
"sccp:n_connect:req",
"Submit SCCP N-CONNECT.req (UL SCCP CR)"
},
[CNLINK_CTR_SCCP_N_CONNECT_CNF] = {
"sccp:n_connect:cnf",
"Received SCCP N-CONNECT.cnf (DL SCCP CC)"
},
[CNLINK_CTR_SCCP_N_DATA_REQ] = {
"sccp:n_data:req",
"SUBMIT SCCP N-DATA.req (UL)"
},
[CNLINK_CTR_SCCP_N_DATA_IND] = {
"sccp:n_data:ind",
"Received SCCP N-DATA.ind (DL)"
},
[CNLINK_CTR_SCCP_N_DISCONNECT_REQ] = {
"sccp:n_disconnect:req",
"Submit SCCP N-DISCONNECT.req (UL SCCP RLC)"
},
[CNLINK_CTR_SCCP_N_DISCONNECT_IND] = {
"sccp:n_disconnect:ind",
"Received SCCP N-DISCONNECT.ind (DL SCCP RLSD)"
},
[CNLINK_CTR_SCCP_N_PCSTATE_IND] = {
"sccp:n_pcstate:ind",
"Received SCCP N-PCSTATE.ind"
},
[CNLINK_CTR_SCCP_RLSD_CN_ORIGIN] = {
"sccp:rlsd_cn_origin",
"Received unexpected SCCP RSLD originated unilaterally by CN"
},
/* Indicators for CN pool usage */
[CNLINK_CTR_CNPOOL_SUBSCR_NEW] = {
"cnpool:subscr:new",
"Complete Layer 3 requests assigned to this CN link by round-robin (no NRI was assigned yet).",
},
[CNLINK_CTR_CNPOOL_SUBSCR_REATTACH] = {
"cnpool:subscr:reattach",
"Complete Layer 3 requests assigned to this CN link by round-robin because the subscriber indicates a"
" NULL-NRI (previously assigned by another CN link).",
},
[CNLINK_CTR_CNPOOL_SUBSCR_KNOWN] = {
"cnpool:subscr:known",
"Complete Layer 3 requests directed to this CN link because the subscriber indicates an NRI of this CN link.",
},
[CNLINK_CTR_CNPOOL_SUBSCR_PAGED] = {
"cnpool:subscr:paged",
"Paging Response directed to this CN link because the subscriber was recently paged by this CN link.",
},
[CNLINK_CTR_CNPOOL_SUBSCR_ATTACH_LOST] = {
"cnpool:subscr:attach_lost",
"A subscriber indicates an NRI value matching this CN link, but the CN link is not connected:"
" a re-attach to another CN link (if available) was forced, with possible service failure.",
},
[CNLINK_CTR_CNPOOL_EMERG_FORWARDED] = {
"cnpool:emerg:forwarded",
"Emergency call requests forwarded to this CN link.",
},
};
static const struct rate_ctr_group_desc msc_ctrg_desc = {
"msc",
"MSC",
OSMO_STATS_CLASS_GLOBAL,
ARRAY_SIZE(cnlink_ctr_description),
cnlink_ctr_description,
};
static const struct rate_ctr_group_desc sgsn_ctrg_desc = {
"sgsn",
"SGSN",
OSMO_STATS_CLASS_GLOBAL,
ARRAY_SIZE(cnlink_ctr_description),
cnlink_ctr_description,
};
static const struct osmo_stat_item_desc cnlink_stat_desc[] = {
[CNLINK_STAT_CONNECTED] = { "connected", "Connected (1) or disconnected (0)", NULL, 60, 0 },
};
const struct osmo_stat_item_group_desc msc_statg_desc = {
.group_name_prefix = "msc",
.group_description = "MSC",
.class_id = OSMO_STATS_CLASS_GLOBAL,
.num_items = ARRAY_SIZE(cnlink_stat_desc),
.item_desc = cnlink_stat_desc,
};
const struct osmo_stat_item_group_desc sgsn_statg_desc = {
.group_name_prefix = "sgsn",
.group_description = "SGSN",
.class_id = OSMO_STATS_CLASS_GLOBAL,
.num_items = ARRAY_SIZE(cnlink_stat_desc),
.item_desc = cnlink_stat_desc,
};
struct hnbgw_cnlink *hnbgw_cnlink_alloc(struct hnbgw_cnpool *cnpool, int nr)
{
struct hnbgw_cnlink *cnlink;
const struct rate_ctr_group_desc *ctrg_desc;
const struct osmo_stat_item_group_desc *statg_desc;
OSMO_ASSERT(cnpool);
switch (cnpool->domain) {
case DOMAIN_CS:
ctrg_desc = &msc_ctrg_desc;
statg_desc = &msc_statg_desc;
break;
case DOMAIN_PS:
ctrg_desc = &sgsn_ctrg_desc;
statg_desc = &sgsn_statg_desc;
break;
default:
OSMO_ASSERT(0);
}
cnlink = talloc_zero(cnpool, struct hnbgw_cnlink);
OSMO_ASSERT(cnlink);
*cnlink = (struct hnbgw_cnlink){
.pool = cnpool,
.nr = nr,
.vty = {
/* VTY config defaults for the new cnlink */
.nri_ranges = osmo_nri_ranges_alloc(cnlink),
},
.allow_attach = true,
.ctrs = rate_ctr_group_alloc(cnlink, ctrg_desc, nr),
.statg = osmo_stat_item_group_alloc(cnlink, statg_desc, nr),
};
cnlink->name = talloc_asprintf(cnlink, "%s-%d", cnpool->peer_name, nr);
INIT_LLIST_HEAD(&cnlink->map_list);
INIT_LLIST_HEAD(&cnlink->paging);
cnlink->fi = osmo_fsm_inst_alloc(&cnlink_fsm, cnlink, cnlink, LOGL_DEBUG, cnlink->name);
OSMO_ASSERT(cnlink->fi);
llist_add_tail(&cnlink->entry, &cnpool->cnlinks);
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "allocated\n");
cnlink_resend_reset(cnlink);
return cnlink;
}
int hnbgw_cnlink_set_name(struct hnbgw_cnlink *cnlink, const char *name)
{
talloc_free(cnlink->name);
cnlink->name = talloc_strdup(cnlink, name);
osmo_fsm_inst_update_id_f_sanitize(cnlink->fi, '-', cnlink->name);
/* Update rate_ctr/stats to report by name instead of index: */
rate_ctr_group_set_name(cnlink->ctrs, cnlink->name);
osmo_stat_item_group_set_name(cnlink->statg, cnlink->name);
return 0;
}
void hnbgw_cnlink_drop_sccp(struct hnbgw_cnlink *cnlink)
{
struct hnbgw_context_map *map, *map2;
struct hnbgw_sccp_user *hsu;
llist_for_each_entry_safe(map, map2, &cnlink->map_list, hnbgw_cnlink_entry) {
map_sccp_dispatch(map, MAP_SCCP_EV_USER_ABORT, NULL);
}
OSMO_ASSERT(cnlink->hnbgw_sccp_user);
hsu = cnlink->hnbgw_sccp_user;
cnlink->hnbgw_sccp_user = NULL;
hnbgw_sccp_user_put(hsu, HSU_USE_CNLINK);
}
void hnbgw_cnlink_term_and_free(struct hnbgw_cnlink *cnlink)
{
if (!cnlink)
return;
if (cnlink->hnbgw_sccp_user)
hnbgw_cnlink_drop_sccp(cnlink);
osmo_fsm_inst_term(cnlink->fi, OSMO_FSM_TERM_REQUEST, NULL);
cnlink->fi = NULL;
osmo_stat_item_group_free(cnlink->statg);
rate_ctr_group_free(cnlink->ctrs);
llist_del(&cnlink->entry);
talloc_free(cnlink);
}
static int hnbgw_cnlink_tx_sccp_unitdata_req(struct hnbgw_cnlink *cnlink, struct msgb *msg)
{
CNLINK_CTR_INC(cnlink, CNLINK_CTR_SCCP_N_UNITDATA_REQ);
return hnbgw_sccp_user_tx_unitdata_req(cnlink->hnbgw_sccp_user,
&cnlink->remote_addr,
msg);
}
int hnbgw_cnlink_tx_ranap_reset(struct hnbgw_cnlink *cnlink)
{
struct msgb *msg;
RANAP_Cause_t cause = {
.present = RANAP_Cause_PR_transmissionNetwork,
.choice. transmissionNetwork = RANAP_CauseTransmissionNetwork_signalling_transport_resource_failure,
};
RANAP_GlobalRNC_ID_t grnc_id;
RANAP_GlobalRNC_ID_t *use_grnc_id = NULL;
uint8_t plmn_buf[3];
if (!cnlink)
return -1;
/* We need to have chosen an SCCP instance, and the remote SCCP address needs to be set.
* Only check the remote_addr, allowing use.remote_addr_name to be NULL: if the user has not set an explicit
* remote address book entry, auto-configuration may still have chosen a default remote point-code. */
if (!cnlink->hnbgw_sccp_user
|| !osmo_sccp_check_addr(&cnlink->remote_addr, OSMO_SCCP_ADDR_T_PC | OSMO_SCCP_ADDR_T_SSN)) {
LOG_CNLINK(cnlink, DRANAP, LOGL_DEBUG, "not yet configured, not sending RANAP RESET\n");
return -1;
}
LOG_CNLINK(cnlink, DRANAP, LOGL_DEBUG, "Tx RANAP RESET to %s %s\n",
cnlink_is_cs(cnlink) ? "IuCS" : "IuPS",
hnbgw_cnlink_sccp_addr_to_str(cnlink, &cnlink->remote_addr));
if (g_hnbgw->config.plmn.mcc) {
osmo_plmn_to_bcd(plmn_buf, &g_hnbgw->config.plmn);
grnc_id = (RANAP_GlobalRNC_ID_t){
.pLMNidentity = {
.buf = plmn_buf,
.size = 3,
},
.rNC_ID = g_hnbgw->config.rnc_id,
};
use_grnc_id = &grnc_id;
} else {
/* If no PLMN is configured, omit the Global RNC Id from the RESET message.
*
* According to 3GPP TS 25.413 8.26.2.2, "The RNC shall include the Global RNC-ID IE in the RESET
* message", so it should be considered a mandatory IE when coming from us, the RNC.
*
* But osmo-hnbgw < v1.5 worked well with osmo-hnbgw.cfg files that have no PLMN configured, and we are
* trying to stay backwards compatible for those users. Such a site should still work, but they should
* now see these error logs and can adjust the config.
*/
LOG_CNLINK(cnlink, DRANAP, LOGL_ERROR,
"No local PLMN is configured, so outgoing RESET messages omit the mandatory Global RNC-ID"
" IE. You should set a 'hnbgw' / 'plmn' in your config file (since v1.5)\n");
}
msg = ranap_new_msg_reset2(cnlink->pool->domain, &cause, use_grnc_id);
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_TX_UDT_RESET);
return hnbgw_cnlink_tx_sccp_unitdata_req(cnlink, msg);
}
int hnbgw_cnlink_tx_ranap_reset_ack(struct hnbgw_cnlink *cnlink)
{
struct msgb *msg;
struct osmo_sccp_instance *sccp = hnbgw_cnlink_sccp(cnlink);
RANAP_GlobalRNC_ID_t grnc_id;
RANAP_GlobalRNC_ID_t *use_grnc_id = NULL;
uint8_t plmn_buf[3];
if (!sccp) {
LOG_CNLINK(cnlink, DRANAP, LOGL_ERROR, "cannot send RANAP RESET ACK: no CN link\n");
return -1;
}
LOG_CNLINK(cnlink, DRANAP, LOGL_NOTICE, "Tx RANAP RESET ACK %s %s --> %s\n",
cnlink_is_cs(cnlink) ? "IuCS" : "IuPS",
hnbgw_cnlink_sccp_addr_to_str(cnlink, &cnlink->hnbgw_sccp_user->local_addr),
hnbgw_cnlink_sccp_addr_to_str(cnlink, &cnlink->remote_addr));
if (g_hnbgw->config.plmn.mcc) {
osmo_plmn_to_bcd(plmn_buf, &g_hnbgw->config.plmn);
grnc_id = (RANAP_GlobalRNC_ID_t){
.pLMNidentity = {
.buf = plmn_buf,
.size = 3,
},
.rNC_ID = g_hnbgw->config.rnc_id,
};
use_grnc_id = &grnc_id;
} else {
/* If no PLMN is configured, omit the Global RNC Id from the RESET ACK message.
*
* According to 3GPP TS 25.413 8.26.2.1, "The RNC shall include the Global RNC-ID IE in the RESET
* ACKNOWLEDGE message", so it should be considered a mandatory IE when coming from us, the RNC.
*
* But osmo-hnbgw < v1.5 worked well with osmo-hnbgw.cfg files that have no PLMN configured, and we are
* trying to stay backwards compatible for those users. Such a site should still work, but they should
* now see these error logs and can adjust the config.
*/
LOG_CNLINK(cnlink, DRANAP, LOGL_ERROR,
"No local PLMN is configured, so outgoing RESET ACKNOWLEDGE messages omit the mandatory"
" Global RNC-ID IE. You should set a 'hnbgw' / 'plmn' in your config file (since v1.5)\n");
}
msg = ranap_new_msg_reset_ack(cnlink->pool->domain, use_grnc_id);
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_TX_UDT_RESET_ACK);
return hnbgw_cnlink_tx_sccp_unitdata_req(cnlink, msg);
}
/* Return address found in sccp address-book, and fill in missing fields in the
* entry with default values. */
static struct osmo_ss7_instance *sccp_addr_by_name_filled(struct osmo_sccp_addr *dest, const char *addr_name, uint32_t default_pc)
{
struct osmo_ss7_instance *s7i;
s7i = osmo_sccp_addr_by_name(dest, addr_name);
if (!s7i)
return NULL;
/* Address exists in address-book but may not be filled entirely: */
if (!dest->presence)
osmo_sccp_make_addr_pc_ssn(dest, default_pc, OSMO_SCCP_SSN_RANAP);
else if (!(dest->presence & OSMO_SCCP_ADDR_T_SSN))
osmo_sccp_addr_set_ssn(dest, OSMO_SCCP_SSN_RANAP);
return s7i;
}
char *hnbgw_cnlink_sccp_addr_to_str(struct hnbgw_cnlink *cnlink, const struct osmo_sccp_addr *addr)
{
struct osmo_sccp_instance *sccp = hnbgw_cnlink_sccp(cnlink);
if (!sccp)
return osmo_sccp_addr_dump(addr);
return osmo_sccp_inst_addr_to_str_c(OTC_SELECT, sccp, addr);
}
static void hnbgw_cnlink_cfg_copy(struct hnbgw_cnlink *cnlink)
{
struct osmo_nri_range *r;
osmo_talloc_replace_string(cnlink, &cnlink->use.remote_addr_name, cnlink->vty.remote_addr_name);
osmo_nri_ranges_free(cnlink->use.nri_ranges);
cnlink->use.nri_ranges = osmo_nri_ranges_alloc(cnlink);
llist_for_each_entry(r, &cnlink->vty.nri_ranges->entries, entry)
osmo_nri_ranges_add(cnlink->use.nri_ranges, r);
}
static bool hnbgw_cnlink_sccp_cfg_changed(struct hnbgw_cnlink *cnlink)
{
bool changed = false;
if (cnlink->vty.remote_addr_name && cnlink->use.remote_addr_name) {
struct osmo_ss7_instance *s7i;
struct osmo_sccp_addr remote_addr = {};
/* Instead of comparing whether the address book entry names are different, actually resolve the
* resulting SCCP address, and only restart the cnlink if the resulting address changed. */
s7i = sccp_addr_by_name_filled(&remote_addr, cnlink->vty.remote_addr_name,
cnlink->pool->default_remote_pc);
if (!s7i)
return true;
if (osmo_sccp_addr_cmp(&remote_addr, &cnlink->remote_addr,
OSMO_SCCP_ADDR_T_PC | OSMO_SCCP_ADDR_T_SSN))
changed = true;
} else if (cnlink->vty.remote_addr_name != cnlink->use.remote_addr_name) {
/* One of them is NULL, the other is not. */
changed = true;
}
/* if more cnlink configuration is added in the future, it needs to be compared here. */
return changed;
}
static void hnbgw_cnlink_log_self(struct hnbgw_cnlink *cnlink)
{
struct osmo_ss7_instance *ss7 = cnlink->hnbgw_sccp_user->ss7;
LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, "using: cs7-%u %s <-> %s %s %s\n",
osmo_ss7_instance_get_id(ss7),
/* printing the entire SCCP address is quite long, rather just print the point-code */
osmo_ss7_pointcode_print(ss7, cnlink->hnbgw_sccp_user->local_addr.pc),
osmo_ss7_pointcode_print2(ss7, cnlink->remote_addr.pc),
cnlink->name, cnlink->use.remote_addr_name ? : "(default remote point-code)");
}
/* If not present yet, set up all of osmo_ss7_instance, osmo_sccp_instance and hnbgw_sccp_user for the given cnlink.
* The cs7 instance nr to use is determined by cnlink->remote_addr_name, or cs7 instance 0 if that is not present.
* Set cnlink->hnbgw_sccp_user to the new SCCP instance. Return 0 on success, negative on error. */
int hnbgw_cnlink_start_or_restart(struct hnbgw_cnlink *cnlink)
{
struct osmo_ss7_instance *s7i = NULL;
struct hnbgw_sccp_user *hsu;
uint32_t ss7_id;
int rc;
/* If a hnbgw_sccp_user has already been set up, use that. */
if (cnlink->hnbgw_sccp_user) {
if (!hnbgw_cnlink_sccp_cfg_changed(cnlink)) {
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "SCCP instance already set up, using %s\n",
cnlink->hnbgw_sccp_user->name);
return 0;
}
LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, "config changed, restarting SCCP\n");
hnbgw_cnlink_drop_sccp(cnlink);
} else {
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "no SCCP instance selected yet\n");
}
/* Copy the current configuration: cnlink->use = cnlink->vty */
hnbgw_cnlink_cfg_copy(cnlink);
if (!cnlink->use.remote_addr_name) {
/* No remote address configured in VTY, set a default one and
* make sure it becomes registered in the sccp address-book: */
cnlink->use.remote_addr_name = talloc_strdup(cnlink, cnlink->pool->default_addr_name);
s7i = osmo_sccp_addr_by_name(&cnlink->remote_addr, cnlink->use.remote_addr_name);
if (!s7i) {
LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, "To auto-configure cnlink, creating cs7 instance 0 implicitly\n");
s7i = osmo_ss7_instance_find_or_create(g_hnbgw, 0);
OSMO_ASSERT(s7i);
osmo_sccp_make_addr_pc_ssn(&cnlink->remote_addr,
cnlink->pool->default_remote_pc,
OSMO_SCCP_SSN_RANAP);
rc = osmo_sccp_addr_create(s7i, cnlink->use.remote_addr_name, &cnlink->remote_addr);
if (rc < 0) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Failed adding address '%s' to sccp address-book!\n",
cnlink->use.remote_addr_name);
return -EINVAL;
}
}
/* Update VTY config to show & point to the address dynamically added to address-book: */
cnlink->vty.remote_addr_name = talloc_strdup(cnlink, cnlink->use.remote_addr_name);
} else {
s7i = sccp_addr_by_name_filled(&cnlink->remote_addr, cnlink->use.remote_addr_name, cnlink->pool->default_remote_pc);
if (!s7i) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "cannot initialize SCCP: there is no SCCP address named '%s'\n",
cnlink->use.remote_addr_name);
return -ENOENT;
}
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "remote-addr is '%s', using cs7 instance %u\n",
cnlink->use.remote_addr_name, osmo_ss7_instance_get_id(s7i));
/* Address exists in address-book but may not be filled entirely: */
rc = osmo_sccp_addr_update(s7i, cnlink->use.remote_addr_name, &cnlink->remote_addr);
if (rc < 0) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Failed updating address '%s' in sccp address-book!\n",
cnlink->use.remote_addr_name);
return -EINVAL;
}
}
ss7_id = osmo_ss7_instance_get_id(s7i);
/* Has another cnlink already set up an SCCP instance for this s7i? */
llist_for_each_entry(hsu, &g_hnbgw->sccp.users, entry) {
if (hsu->ss7 != s7i)
continue;
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "using existing SCCP instance %s on cs7 instance %u\n",
hsu->name, ss7_id);
cnlink->hnbgw_sccp_user = hsu;
hnbgw_sccp_user_get(cnlink->hnbgw_sccp_user, HSU_USE_CNLINK);
hnbgw_cnlink_log_self(cnlink);
return 0;
}
/* else cnlink->hnbgw_sccp_user stays NULL and is set up below. */
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "cs7 instance %u has no configured SCCP instance yet\n", ss7_id);
/* No SCCP instance yet for this ss7. Create it. If no address name is given that resolves to a
* particular cs7 instance above, use 'cs7 instance 0'. */
cnlink->hnbgw_sccp_user = hnbgw_sccp_user_alloc(ss7_id);
hnbgw_sccp_user_get(cnlink->hnbgw_sccp_user, HSU_USE_CNLINK);
hnbgw_cnlink_log_self(cnlink);
return 0;
}

220
src/osmo-hnbgw/cnlink_fsm.c Normal file
View File

@@ -0,0 +1,220 @@
/* (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr
*
* 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/fsm.h>
#include <osmocom/core/tdef.h>
#include <osmocom/gsm/gsm23236.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <asn1c/asn1helpers.h>
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/ranap/ranap_msg_factory.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/tdefs.h>
#include <osmocom/hnbgw/context_map.h>
enum cnlink_fsm_state {
CNLINK_ST_DISC,
CNLINK_ST_CONN,
};
enum cnlink_fsm_event {
CNLINK_EV_RX_RESET,
CNLINK_EV_RX_RESET_ACK,
};
static const struct value_string cnlink_fsm_event_names[] = {
OSMO_VALUE_STRING(CNLINK_EV_RX_RESET),
OSMO_VALUE_STRING(CNLINK_EV_RX_RESET_ACK),
{}
};
static const struct osmo_tdef_state_timeout cnlink_timeouts[32] = {
[CNLINK_ST_DISC] = { .T = 4 },
};
#define cnlink_fsm_state_chg(FI, STATE) \
osmo_tdef_fsm_inst_state_chg(FI, STATE, \
cnlink_timeouts, \
hnbgw_T_defs, \
-1)
static void link_up(struct hnbgw_cnlink *cnlink)
{
LOGPFSML(cnlink->fi, LOGL_NOTICE, "link up\n");
CNLINK_STAT_SET(cnlink, CNLINK_STAT_CONNECTED, 1);
}
static void link_lost(struct hnbgw_cnlink *cnlink)
{
struct hnbgw_context_map *map, *map2;
LOGPFSML(cnlink->fi, LOGL_NOTICE, "link lost\n");
CNLINK_STAT_SET(cnlink, CNLINK_STAT_CONNECTED, 0);
llist_for_each_entry_safe(map, map2, &cnlink->map_list, hnbgw_cnlink_entry)
context_map_cnlink_lost(map);
}
static void cnlink_disc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
if (prev_state == CNLINK_ST_CONN)
link_lost(cnlink);
}
static void cnlink_disc_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
switch (event) {
case CNLINK_EV_RX_RESET:
hnbgw_cnlink_tx_ranap_reset_ack(cnlink);
cnlink_fsm_state_chg(fi, CNLINK_ST_CONN);
break;
case CNLINK_EV_RX_RESET_ACK:
cnlink_fsm_state_chg(fi, CNLINK_ST_CONN);
break;
default:
OSMO_ASSERT(false);
}
}
static void cnlink_conn_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
if (prev_state != CNLINK_ST_CONN)
link_up(cnlink);
}
static void cnlink_conn_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
switch (event) {
case CNLINK_EV_RX_RESET:
/* We were connected, but the remote side has restarted. */
link_lost(cnlink);
hnbgw_cnlink_tx_ranap_reset_ack(cnlink);
link_up(cnlink);
break;
case CNLINK_EV_RX_RESET_ACK:
LOGPFSML(fi, LOGL_INFO, "Link is already up, ignoring RESET ACK\n");
break;
default:
OSMO_ASSERT(false);
}
}
static int cnlink_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
hnbgw_cnlink_tx_ranap_reset(cnlink);
/* (re-)enter disconnect state to resend RESET after timeout. */
cnlink_fsm_state_chg(fi, CNLINK_ST_DISC);
/* Return 0 to not terminate the fsm */
return 0;
}
#define S(x) (1 << (x))
static struct osmo_fsm_state cnlink_fsm_states[] = {
[CNLINK_ST_DISC] = {
.name = "DISCONNECTED",
.in_event_mask = 0
| S(CNLINK_EV_RX_RESET)
| S(CNLINK_EV_RX_RESET_ACK)
,
.out_state_mask = 0
| S(CNLINK_ST_DISC)
| S(CNLINK_ST_CONN)
,
.onenter = cnlink_disc_onenter,
.action = cnlink_disc_action,
},
[CNLINK_ST_CONN] = {
.name = "CONNECTED",
.in_event_mask = 0
| S(CNLINK_EV_RX_RESET)
| S(CNLINK_EV_RX_RESET_ACK)
,
.out_state_mask = 0
| S(CNLINK_ST_DISC)
| S(CNLINK_ST_CONN)
,
.onenter = cnlink_conn_onenter,
.action = cnlink_conn_action,
},
};
struct osmo_fsm cnlink_fsm = {
.name = "cnlink",
.states = cnlink_fsm_states,
.num_states = ARRAY_SIZE(cnlink_fsm_states),
.log_subsys = DRANAP,
.timer_cb = cnlink_fsm_timer_cb,
.event_names = cnlink_fsm_event_names,
};
bool cnlink_is_conn_ready(const struct hnbgw_cnlink *cnlink)
{
return cnlink->fi->state == CNLINK_ST_CONN;
}
void cnlink_resend_reset(struct hnbgw_cnlink *cnlink)
{
/* Immediately (1ms) kick off reset sending mechanism */
osmo_fsm_inst_state_chg_ms(cnlink->fi, CNLINK_ST_DISC, 1, 0);
}
void cnlink_set_disconnected(struct hnbgw_cnlink *cnlink)
{
/* Go to disconnected state, with the normal RESET timeout to re-send RESET. */
cnlink_fsm_state_chg(cnlink->fi, CNLINK_ST_DISC);
}
static __attribute__((constructor)) void cnlink_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&cnlink_fsm) == 0);
}
void cnlink_rx_reset_cmd(struct hnbgw_cnlink *cnlink)
{
osmo_fsm_inst_dispatch(cnlink->fi, CNLINK_EV_RX_RESET, NULL);
}
void cnlink_rx_reset_ack(struct hnbgw_cnlink *cnlink)
{
osmo_fsm_inst_dispatch(cnlink->fi, CNLINK_EV_RX_RESET_ACK, NULL);
}

View File

@@ -0,0 +1,230 @@
/* RANAP Paging of HNB-GW */
/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
* (C) 2025 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 "config.h"
#include <errno.h>
#include <sys/types.h>
#include <asn1c/asn1helpers.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/ranap/iu_helpers.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/tdefs.h>
/***************
* This module manages the list of "struct cnlink_paging" items in (struct
* hnbgw_cnlink *)->paging.
* Every time a new RANAP Paging Cmd arrives from some cnlink,
* cnlink_paging_add_ranap() is called to potentially store the paging command
* for a while.
* When a paging response is received from HNB, cnlink_find_by_paging_mi() is
* called to obtain the cnlink it should be routed back to.
*/
struct cnlink_paging {
struct llist_head entry;
struct osmo_mobile_identity mi;
struct osmo_mobile_identity mi2;
time_t timestamp;
};
static int cnlink_paging_destructor(struct cnlink_paging *p)
{
llist_del(&p->entry);
return 0;
}
/* Return current timestamp in *timestamp, and the oldest still valid timestamp according to T3113 timeout. */
static const char *cnlink_paging_gettime(time_t *timestamp_p, time_t *timeout_p)
{
struct timespec now;
time_t timestamp;
/* get timestamp */
if (osmo_clock_gettime(CLOCK_MONOTONIC, &now) != 0)
return "cannot get timestamp";
timestamp = now.tv_sec;
if (timestamp_p)
*timestamp_p = timestamp;
if (timeout_p)
*timeout_p = timestamp - osmo_tdef_get(hnbgw_T_defs, 3113, OSMO_TDEF_S, 15);
return NULL;
}
static const char *cnlink_paging_add(struct hnbgw_cnlink *cnlink, const struct osmo_mobile_identity *mi,
const struct osmo_mobile_identity *mi2)
{
struct cnlink_paging *p, *p2;
time_t timestamp;
time_t timeout;
const char *errmsg;
errmsg = cnlink_paging_gettime(&timestamp, &timeout);
if (errmsg)
return errmsg;
/* Prune all paging records that are older than the configured timeout. */
llist_for_each_entry_safe(p, p2, &cnlink->paging, entry) {
if (p->timestamp >= timeout)
continue;
talloc_free(p);
}
/* Add new entry */
p = talloc_zero(cnlink, struct cnlink_paging);
*p = (struct cnlink_paging){
.timestamp = timestamp,
.mi = *mi,
.mi2 = *mi2,
};
llist_add_tail(&p->entry, &cnlink->paging);
talloc_set_destructor(p, cnlink_paging_destructor);
LOG_CNLINK(cnlink, DCN, LOGL_INFO, "Rx Paging from CN for %s %s\n",
osmo_mobile_identity_to_str_c(OTC_SELECT, mi),
osmo_mobile_identity_to_str_c(OTC_SELECT, mi2));
return NULL;
}
static const char *omi_from_ranap_ue_id(struct osmo_mobile_identity *mi, const RANAP_PermanentNAS_UE_ID_t *ranap_mi)
{
if (!ranap_mi)
return "null UE ID";
if (ranap_mi->present != RANAP_PermanentNAS_UE_ID_PR_iMSI)
return talloc_asprintf(OTC_SELECT, "unsupported UE ID type %u in RANAP Paging", ranap_mi->present);
if (ranap_mi->choice.iMSI.size > sizeof(mi->imsi))
return talloc_asprintf(OTC_SELECT, "invalid IMSI size %d > %zu",
ranap_mi->choice.iMSI.size, sizeof(mi->imsi));
*mi = (struct osmo_mobile_identity){
.type = GSM_MI_TYPE_IMSI,
};
ranap_bcd_decode(mi->imsi, sizeof(mi->imsi), ranap_mi->choice.iMSI.buf, ranap_mi->choice.iMSI.size);
LOGP(DCN, LOGL_DEBUG, "ranap MI %s = %s\n", osmo_hexdump(ranap_mi->choice.iMSI.buf, ranap_mi->choice.iMSI.size),
mi->imsi);
return NULL;
}
static const char *omi_from_ranap_temp_ue_id(struct osmo_mobile_identity *mi, const RANAP_TemporaryUE_ID_t *ranap_tmsi)
{
const OCTET_STRING_t *tmsi_str;
if (!ranap_tmsi)
return "null UE ID";
switch (ranap_tmsi->present) {
case RANAP_TemporaryUE_ID_PR_tMSI:
tmsi_str = &ranap_tmsi->choice.tMSI;
break;
case RANAP_TemporaryUE_ID_PR_p_TMSI:
tmsi_str = &ranap_tmsi->choice.p_TMSI;
break;
default:
return talloc_asprintf(OTC_SELECT, "unsupported Temporary UE ID type %u in RANAP Paging", ranap_tmsi->present);
}
*mi = (struct osmo_mobile_identity){
.type = GSM_MI_TYPE_TMSI,
.tmsi = asn1str_to_u32(tmsi_str),
};
LOGP(DCN, LOGL_DEBUG, "ranap temp UE ID = %s\n", osmo_mobile_identity_to_str_c(OTC_SELECT, mi));
return NULL;
}
const char *cnlink_paging_add_ranap(struct hnbgw_cnlink *cnlink, const RANAP_PagingIEs_t *paging_ies)
{
struct osmo_mobile_identity mi = {};
struct osmo_mobile_identity mi2 = {};
RANAP_CN_DomainIndicator_t domain;
const char *errmsg;
domain = paging_ies->cN_DomainIndicator;
errmsg = omi_from_ranap_ue_id(&mi, &paging_ies->permanentNAS_UE_ID);
if (!errmsg && (paging_ies->presenceMask & PAGINGIES_RANAP_TEMPORARYUE_ID_PRESENT))
errmsg = omi_from_ranap_temp_ue_id(&mi2, &paging_ies->temporaryUE_ID);
LOG_CNLINK(cnlink, DCN, errmsg ? LOGL_NOTICE : LOGL_DEBUG,
"Decoded Paging: %s %s %s%s%s\n",
ranap_domain_name(domain),
osmo_mobile_identity_to_str_c(OTC_SELECT, &mi),
mi2.type ? osmo_mobile_identity_to_str_c(OTC_SELECT, &mi2) : "-",
errmsg ? " -- MI error: " : "",
errmsg ? : "");
if (errmsg)
return errmsg;
return cnlink_paging_add(cnlink, &mi, &mi2);
}
/* If this cnlink has a recent Paging for the given MI, return true and drop the Paging record.
* Else return false. */
static bool cnlink_match_paging_mi(struct hnbgw_cnlink *cnlink, const struct osmo_mobile_identity *mi, time_t timeout)
{
struct cnlink_paging *p, *p2;
llist_for_each_entry_safe(p, p2, &cnlink->paging, entry) {
if (p->timestamp < timeout) {
talloc_free(p);
continue;
}
if (osmo_mobile_identity_cmp(&p->mi, mi)
&& osmo_mobile_identity_cmp(&p->mi2, mi))
continue;
talloc_free(p);
return true;
}
return false;
}
struct hnbgw_cnlink *cnlink_find_by_paging_mi(struct hnbgw_cnpool *cnpool, const struct osmo_mobile_identity *mi)
{
struct hnbgw_cnlink *cnlink;
time_t timeout = 0;
const char *errmsg;
errmsg = cnlink_paging_gettime(NULL, &timeout);
if (errmsg)
LOGP(DCN, LOGL_ERROR, "%s\n", errmsg);
llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) {
if (!cnlink_match_paging_mi(cnlink, mi, timeout))
continue;
return cnlink;
}
return NULL;
}

View File

@@ -19,163 +19,210 @@
*
*/
#include "config.h"
/* an expired mapping is destroyed after 1..2 * EXPIRY_TIMER_SECS */
#define EXPIRY_TIMER_SECS 23
#include <osmocom/core/timer.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/netif/stream.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/mgw_fsm.h>
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
const struct value_string hnbgw_context_map_state_names[] = {
{MAP_S_NULL , "not-initialized"},
{MAP_S_ACTIVE , "active"},
{MAP_S_RESERVED1, "inactive-reserved"},
{MAP_S_RESERVED2, "inactive-discard"},
{0, NULL}
{ MAP_S_CONNECTING, "connecting" },
{ MAP_S_ACTIVE, "active" },
{ MAP_S_DISCONNECTING, "disconnecting" },
{}
};
/* is a given SCCP USER SAP Connection ID in use for a given CN link? */
static int cn_id_in_use(struct hnbgw_cnlink *cn, uint32_t id)
/* Combine the RUA and SCCP states, for VTY reporting only. */
enum hnbgw_context_map_state context_map_get_state(struct hnbgw_context_map *map)
{
struct hnbgw_context_map *map;
llist_for_each_entry(map, &cn->map_list, cn_list) {
if (map->scu_conn_id == id)
return 1;
}
return 0;
}
/* try to allocate a new SCCP User SAP Connection ID */
static int alloc_cn_conn_id(struct hnbgw_cnlink *cn, uint32_t *id_out)
{
uint32_t i;
uint32_t id;
for (i = 0; i < 0xffffffff; i++) {
id = cn->next_conn_id++;
if (!cn_id_in_use(cn, id)) {
*id_out = id;
return 1;
}
}
return -1;
enum hnbgw_context_map_state rua = map_rua_get_state(map);
enum hnbgw_context_map_state sccp = map_sccp_get_state(map);
if (rua == MAP_S_ACTIVE && sccp == MAP_S_ACTIVE)
return MAP_S_ACTIVE;
if (rua == MAP_S_DISCONNECTING || sccp == MAP_S_DISCONNECTING)
return MAP_S_DISCONNECTING;
return MAP_S_CONNECTING;
}
/* Map from a HNB + ContextID to the SCCP-side Connection ID */
struct hnbgw_context_map *
context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id,
bool is_ps,
struct hnbgw_cnlink *cn_if_new)
struct hnbgw_context_map *context_map_find_by_rua_ctx_id(struct hnb_context *hnb, uint32_t rua_ctx_id, bool is_ps)
{
struct hnbgw_context_map *map;
uint32_t new_scu_conn_id;
llist_for_each_entry(map, &hnb->map_list, hnb_list) {
if (map->state != MAP_S_ACTIVE)
if (map->is_ps != is_ps)
continue;
if (map->cn_link != cn_if_new) {
/* Matching on RUA context id -- only match for RUA context that has not been disconnected yet. If an
* inactive context map for a rua_ctx_id is still around, we may have two entries for the same
* rua_ctx_id around at the same time. That should only stay until its SCCP side is done releasing. */
if (!map_rua_is_active(map))
continue;
}
if (map->rua_ctx_id == rua_ctx_id
&& map->is_ps == is_ps) {
return map;
}
}
if (alloc_cn_conn_id(cn_if_new, &new_scu_conn_id) < 0) {
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unable to allocate CN connection ID\n");
return NULL;
}
LOGHNB(hnb, DMAIN, LOGL_INFO, "Creating new Mapping RUA CTX %p/%u <-> SCU Conn ID %p/%u\n",
hnb, rua_ctx_id, cn_if_new, new_scu_conn_id);
/* alloate a new map entry */
map = talloc_zero(hnb, struct hnbgw_context_map);
map->state = MAP_S_NULL;
map->cn_link = cn_if_new;
map->hnb_ctx = hnb;
map->rua_ctx_id = rua_ctx_id;
map->is_ps = is_ps;
map->scu_conn_id = new_scu_conn_id;
/* put it into both lists */
llist_add_tail(&map->hnb_list, &hnb->map_list);
llist_add_tail(&map->cn_list, &cn_if_new->map_list);
map->state = MAP_S_ACTIVE;
return map;
}
/* Map from a CN + Connection ID to HNB + Context ID */
struct hnbgw_context_map *
context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id)
{
struct hnbgw_context_map *map;
llist_for_each_entry(map, &cn->map_list, cn_list) {
if (map->state != MAP_S_ACTIVE)
if (map->rua_ctx_id != rua_ctx_id)
continue;
if (map->scu_conn_id == scu_conn_id) {
return map;
}
/* Already exists */
return map;
}
/* we don't allocate new mappings in the CN->HNB
* direction, as the RUA=SCCP=SUA connections are always
* established from HNB towards CN. */
LOGP(DMAIN, LOGL_NOTICE, "Unable to resolve map for CN " "connection ID %p/%u\n", cn, scu_conn_id);
return NULL;
}
void context_map_deactivate(struct hnbgw_context_map *map)
struct hnbgw_context_map *context_map_alloc(struct hnb_context *hnb, uint32_t rua_ctx_id, bool is_ps)
{
/* set the state to reserved. We still show up in the list and
* avoid re-allocation of the context-id until we are cleaned up
* by the context_map garbage collector timer */
struct hnbgw_context_map *map;
if (map->state != MAP_S_RESERVED2)
map->state = MAP_S_RESERVED1;
/* allocate a new map entry now, so we have logging context */
map = talloc_zero(hnb, struct hnbgw_context_map);
map->hnb_ctx = hnb;
map->rua_ctx_id = rua_ctx_id;
map->is_ps = is_ps;
INIT_LLIST_HEAD(&map->ps_rab_ass_list);
INIT_LLIST_HEAD(&map->ps_rab_list);
map_rua_fsm_alloc(map);
llist_add_tail(&map->hnb_list, &hnb->map_list);
LOG_MAP(map, DRUA, LOGL_DEBUG, "New RUA CTX\n");
return map;
}
static struct osmo_timer_list context_map_tmr;
static void context_map_tmr_cb(void *data)
int context_map_set_cnlink(struct hnbgw_context_map *map, struct hnbgw_cnlink *cnlink_selected)
{
struct hnb_gw *gw = data;
struct hnbgw_cnlink *cn = gw->sccp.cnlink;
struct hnbgw_context_map *map, *next_map;
int new_scu_conn_id;
struct hnbgw_sccp_user *hsu;
DEBUGP(DMAIN, "Running context mapper garbage collection\n");
llist_for_each_entry_safe(map, next_map, &cn->map_list, cn_list) {
switch (map->state) {
case MAP_S_RESERVED1:
/* first time we see this reserved
* entry: mark it for stage 2 */
map->state = MAP_S_RESERVED2;
break;
case MAP_S_RESERVED2:
/* second time we see this reserved
* entry: remove it */
map->state = MAP_S_NULL;
llist_del(&map->cn_list);
llist_del(&map->hnb_list);
talloc_free(map);
break;
default:
break;
}
/* Allocate new SCCP conn id on the SCCP instance the cnlink is on. */
hsu = cnlink_selected->hnbgw_sccp_user;
if (!hsu) {
LOG_MAP(map, DCN, LOGL_ERROR, "Cannot map RUA context to SCCP: No SCCP instance for CN link %s\n",
cnlink_selected->name);
return -EIO;
}
/* re-schedule this timer */
osmo_timer_schedule(&context_map_tmr, EXPIRY_TIMER_SECS, 0);
}
int context_map_init(struct hnb_gw *gw)
{
context_map_tmr.cb = context_map_tmr_cb;
context_map_tmr.data = gw;
osmo_timer_schedule(&context_map_tmr, EXPIRY_TIMER_SECS, 0);
new_scu_conn_id = osmo_sccp_instance_next_conn_id(osmo_ss7_get_sccp(hsu->ss7));
if (new_scu_conn_id < 0) {
LOG_MAP(map, DCN, LOGL_ERROR, "Unable to allocate SCCP conn ID on %s\n", hsu->name);
return new_scu_conn_id;
}
map->cnlink = cnlink_selected;
map->scu_conn_id = new_scu_conn_id;
map_sccp_fsm_alloc(map);
llist_add_tail(&map->hnbgw_cnlink_entry, &cnlink_selected->map_list);
hash_add(hsu->hnbgw_context_map_by_conn_id, &map->hnbgw_sccp_user_entry, new_scu_conn_id);
LOGP(DRUA, LOGL_INFO, "New conn: %s '%s' RUA-%u <-> SCCP-%u %s%s%s %s l=%s<->r=%s\n",
osmo_sock_get_name2_c(OTC_SELECT, osmo_stream_srv_get_ofd(map->hnb_ctx->conn)->fd),
hnb_context_name(map->hnb_ctx), map->rua_ctx_id,
new_scu_conn_id,
cnlink_selected->name,
cnlink_selected->use.remote_addr_name ? " " : "",
cnlink_selected->use.remote_addr_name ? : "",
hsu->name,
/* printing the entire SCCP address is quite long, rather just print the point-code */
osmo_ss7_pointcode_print(hsu->ss7, hsu->local_addr.pc),
osmo_ss7_pointcode_print2(hsu->ss7, cnlink_selected->remote_addr.pc)
);
return 0;
}
int _map_rua_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg,
const char *file, int line)
{
OSMO_ASSERT(map);
if (!map->rua_fi) {
LOG_MAP(map, DRUA, LOGL_ERROR, "not ready to receive RUA events\n");
return -EINVAL;
}
return _osmo_fsm_inst_dispatch(map->rua_fi, event, ranap_msg, file, line);
}
int _map_sccp_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg,
const char *file, int line)
{
OSMO_ASSERT(map);
if (!map->sccp_fi) {
LOG_MAP(map, DRUA, LOGL_ERROR, "not ready to receive SCCP events\n");
return -EINVAL;
}
return _osmo_fsm_inst_dispatch(map->sccp_fi, event, ranap_msg, file, line);
}
unsigned int msg_has_l2_data(const struct msgb *msg)
{
return msg && msgb_l2(msg) ? msgb_l2len(msg) : 0;
}
void context_map_hnb_released(struct hnbgw_context_map *map)
{
/* When a HNB disconnects from RUA, the hnb_context will be freed. This hnbgw_context_map was allocated as a
* child of the hnb_context and would also be deallocated along with the hnb_context. However, the SCCP side for
* this hnbgw_context_map may still be waiting for a graceful release (SCCP RLC). Move this hnbgw_context_map to
* the global hnb_gw talloc ctx, so it can stay around for graceful release / for SCCP timeout.
*
* We could also always allocate hnbgw_context_map under hnb_gw, but it is nice to see which hnb_context owns
* which hnbgw_context_map in a talloc report.
*/
talloc_steal(g_hnbgw, map);
/* Tell RUA that the HNB is gone. SCCP release will follow via FSM events. */
map_rua_dispatch(map, MAP_RUA_EV_HNB_LINK_LOST, NULL);
}
void context_map_cnlink_lost(struct hnbgw_context_map *map)
{
map_sccp_dispatch(map, MAP_SCCP_EV_CN_LINK_LOST, NULL);
}
void context_map_free(struct hnbgw_context_map *map)
{
/* guard against FSM termination infinitely looping back here */
if (map->deallocating) {
LOG_MAP(map, DMAIN, LOGL_DEBUG, "context_map_free(): already deallocating\n");
return;
}
map->deallocating = true;
if (map->rua_fi)
osmo_fsm_inst_term(map->rua_fi, OSMO_FSM_TERM_REGULAR, NULL);
OSMO_ASSERT(map->rua_fi == NULL);
if (map->sccp_fi)
osmo_fsm_inst_term(map->sccp_fi, OSMO_FSM_TERM_REGULAR, NULL);
OSMO_ASSERT(map->sccp_fi == NULL);
if (map->mgw_fi) {
mgw_fsm_release(map);
OSMO_ASSERT(map->mgw_fi == NULL);
}
#if ENABLE_PFCP
hnbgw_gtpmap_release(map);
#endif
if (map->cnlink) {
llist_del(&map->hnbgw_cnlink_entry);
hash_del(&map->hnbgw_sccp_user_entry);
}
if (map->hnb_ctx)
llist_del(&map->hnb_list);
LOG_MAP(map, DMAIN, LOGL_INFO, "Deallocating\n");
talloc_free(map);
}

View File

@@ -0,0 +1,384 @@
/* RUA side FSM of hnbgw_context_map */
/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
*
* 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 "config.h"
#include <osmocom/core/utils.h>
#include <osmocom/core/fsm.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/tdefs.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbgw/hnbgw_ranap.h>
enum map_rua_fsm_state {
MAP_RUA_ST_INIT,
MAP_RUA_ST_CONNECTED,
MAP_RUA_ST_DISCONNECTED,
MAP_RUA_ST_DISRUPTED,
};
static const struct value_string map_rua_fsm_event_names[] = {
OSMO_VALUE_STRING(MAP_RUA_EV_RX_CONNECT),
OSMO_VALUE_STRING(MAP_RUA_EV_RX_DIRECT_TRANSFER),
OSMO_VALUE_STRING(MAP_RUA_EV_RX_DISCONNECT),
OSMO_VALUE_STRING(MAP_RUA_EV_TX_DIRECT_TRANSFER),
OSMO_VALUE_STRING(MAP_RUA_EV_CN_DISC),
OSMO_VALUE_STRING(MAP_RUA_EV_HNB_LINK_LOST),
{}
};
static struct osmo_fsm map_rua_fsm;
static const struct osmo_tdef_state_timeout map_rua_fsm_timeouts[32] = {
[MAP_RUA_ST_INIT] = { .T = -31 },
[MAP_RUA_ST_DISCONNECTED] = { .T = -31 },
[MAP_RUA_ST_DISRUPTED] = { .T = -31 },
};
/* Transition to a state, using the T timer defined in map_rua_fsm_timeouts.
* Assumes local variable fi exists. */
#define map_rua_fsm_state_chg(state) \
OSMO_ASSERT(osmo_tdef_fsm_inst_state_chg(fi, state, \
map_rua_fsm_timeouts, \
hnbgw_T_defs, \
5) == 0)
void map_rua_fsm_alloc(struct hnbgw_context_map *map)
{
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&map_rua_fsm, map, map, LOGL_DEBUG, NULL);
OSMO_ASSERT(fi);
osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-%s-RUA-%u", hnb_context_name(map->hnb_ctx),
map->is_ps ? "PS" : "CS", map->rua_ctx_id);
OSMO_ASSERT(map->rua_fi == NULL);
map->rua_fi = fi;
/* trigger the timeout */
map_rua_fsm_state_chg(MAP_RUA_ST_INIT);
}
enum hnbgw_context_map_state map_rua_get_state(struct hnbgw_context_map *map)
{
if (!map || !map->rua_fi)
return MAP_S_DISCONNECTING;
switch (map->rua_fi->state) {
case MAP_RUA_ST_INIT:
return MAP_S_CONNECTING;
case MAP_RUA_ST_CONNECTED:
return MAP_S_ACTIVE;
default:
case MAP_RUA_ST_DISCONNECTED:
case MAP_RUA_ST_DISRUPTED:
return MAP_S_DISCONNECTING;
}
}
bool map_rua_is_active(struct hnbgw_context_map *map)
{
if (!map || !map->rua_fi)
return false;
switch (map->rua_fi->state) {
case MAP_RUA_ST_DISCONNECTED:
case MAP_RUA_ST_DISRUPTED:
return false;
default:
return true;
}
}
static int map_rua_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
/* Return 1 to terminate FSM instance, 0 to keep running */
switch (fi->state) {
default:
map_rua_fsm_state_chg(MAP_RUA_ST_DISRUPTED);
return 0;
case MAP_RUA_ST_DISCONNECTED:
case MAP_RUA_ST_DISRUPTED:
return 1;
}
}
/* Dispatch RANAP message to SCCP, if any. */
static int handle_rx_rua(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
{
struct hnbgw_context_map *map = fi->priv;
/* If the FSM instance has already terminated, don't dispatch anything. */
if (fi->proc.terminating)
return 0;
if (!msg_has_l2_data(ranap_msg))
return 0;
return hnbgw_ranap_rx_data_ul(map, ranap_msg);
}
static int forward_ranap_to_rua(struct hnbgw_context_map *map, struct msgb *ranap_msg)
{
int rc;
if (!msg_has_l2_data(ranap_msg))
return 0;
if (!map->hnb_ctx) {
LOGPFSML(map->rua_fi, LOGL_ERROR, "Cannot transmit RUA DirectTransfer: HNB has disconnected\n");
return -ENOTCONN;
}
rc = rua_tx_dt(map->hnb_ctx, map->is_ps, map->rua_ctx_id, msgb_l2(ranap_msg), msgb_l2len(ranap_msg));
if (rc)
LOGPFSML(map->rua_fi, LOGL_ERROR, "Failed to transmit RUA DirectTransfer to HNB\n");
return rc;
}
static void map_rua_init_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msgb *ranap_msg = data;
switch (event) {
case MAP_RUA_EV_RX_CONNECT:
/* not needed for RAB assignment scanning, but for KPI scanning */
handle_rx_rua(fi, ranap_msg);
map_rua_fsm_state_chg(MAP_RUA_ST_CONNECTED);
return;
case MAP_RUA_EV_RX_DISCONNECT:
/* Unlikely that SCCP is active, but let the SCCP FSM decide about that. */
handle_rx_rua(fi, ranap_msg);
/* There is a reason to shut down this RUA connection. Super unlikely, we haven't even processed the
* MAP_RUA_EV_RX_CONNECT that created this FSM. Semantically, RUA is not connected, so we can
* directly go to MAP_RUA_ST_DISCONNECTED. */
map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED);
break;
case MAP_RUA_EV_CN_DISC:
case MAP_RUA_EV_HNB_LINK_LOST:
map_rua_fsm_state_chg(MAP_RUA_ST_DISRUPTED);
break;
default:
OSMO_ASSERT(false);
}
}
static void map_rua_tx_disconnect(struct osmo_fsm_inst *fi)
{
struct hnbgw_context_map *map = fi->priv;
RUA_Cause_t rua_cause;
if (!map->hnb_ctx || !map->hnb_ctx->conn) {
/* HNB already disconnected, nothing to do. */
LOGPFSML(fi, LOGL_NOTICE, "HNB vanished, this RUA context cannot disconnect gracefully\n");
return;
}
/* Send Disconnect to RUA without RANAP data. */
rua_cause = (RUA_Cause_t){
.present = RUA_Cause_PR_radioNetwork,
.choice.radioNetwork = RUA_CauseRadioNetwork_network_release,
};
LOGPFSML(fi, LOGL_INFO, "Tx RUA Disconnect\n");
if (rua_tx_disc(map->hnb_ctx, map->is_ps, map->rua_ctx_id, &rua_cause, NULL, 0))
LOGPFSML(fi, LOGL_ERROR, "Failed to send Disconnect to RUA\n");
}
static void map_rua_connected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct hnbgw_context_map *map = fi->priv;
struct msgb *ranap_msg = data;
switch (event) {
case MAP_RUA_EV_RX_DIRECT_TRANSFER:
/* received DirectTransfer from RUA, forward to SCCP */
handle_rx_rua(fi, ranap_msg);
return;
case MAP_RUA_EV_TX_DIRECT_TRANSFER:
/* Someone (usually the SCCP side) wants us to send a RANAP payload to HNB via RUA */
forward_ranap_to_rua(map, ranap_msg);
return;
case MAP_RUA_EV_RX_DISCONNECT:
/* 3GPP TS 25.468 9.1.5: RUA has disconnected.
* - Under normal conditions (cause=Normal) the RUA Disconnect contains a RANAP Iu-ReleaseComplete.
* On SCCP, the Iu-ReleaseComplete should still be forwarded as N-Data SCCP Data Form 1),
* and we will expect the CN to send an SCCP RLSD soon.
* - Under error conditions, cause!=Normal and there's no RANAP message.
* In that case, we need to tear down the associated SCCP link towards CN,
* which in turn will tear down the upper layer Iu conn.
*/
if (msg_has_l2_data(ranap_msg)) {
/* Forward any payload to SCCP before Disconnect. */
handle_rx_rua(fi, ranap_msg);
} else {
map->rua_fi_ctx.rua_disconnect_err_condition = true;
}
map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED);
return;
case MAP_RUA_EV_HNB_LINK_LOST:
/* The HNB is gone. Cannot gracefully cleanup the RUA connection, just be gone. */
map_rua_fsm_state_chg(MAP_RUA_ST_DISRUPTED);
return;
case MAP_RUA_EV_CN_DISC:
/* There is a disruptive reason to shut down this RUA connection, HNB is still there */
OSMO_ASSERT(data == NULL);
map_rua_tx_disconnect(fi);
map_rua_fsm_state_chg(MAP_RUA_ST_DISRUPTED);
return;
default:
OSMO_ASSERT(false);
}
}
static void map_rua_free_if_done(struct hnbgw_context_map *map, uint32_t sccp_event, void *ev_data)
{
/* From RUA's POV, we can now free the hnbgw_context_map.
* If SCCP is still active, tell it to disconnect -- in that case the SCCP side will call context_map_free().
* If SCCP is no longer active, free this map. */
if (map_sccp_is_active(map))
map_sccp_dispatch(map, sccp_event, ev_data);
else
context_map_free(map);
}
static void map_rua_disconnected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct hnbgw_context_map *map = fi->priv;
map_rua_free_if_done(map, MAP_SCCP_EV_RAN_DISC, (void *)map->rua_fi_ctx.rua_disconnect_err_condition);
}
static void map_rua_disconnected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msgb *ranap_msg;
switch (event) {
case MAP_RUA_EV_TX_DIRECT_TRANSFER:
/* This can happen if CN is buggy, or in general if there was a race
* condition between us forwarding the release towards CN (SCCP Release
* or RANAP Iu-ReleaseComplete) and CN sendig whatever to us. */
ranap_msg = data;
if (msg_has_l2_data(ranap_msg)) {
LOGPFSML(fi, LOGL_NOTICE, "RUA already disconnected, skip forwarding DL RANAP msg (%u bytes)\n",
msgb_l2len(ranap_msg));
LOGPFSML(fi, LOGL_DEBUG, "%s\n", osmo_hexdump(msgb_l2(ranap_msg), msgb_l2len(ranap_msg)));
}
break;
case MAP_RUA_EV_CN_DISC:
case MAP_RUA_EV_HNB_LINK_LOST:
/* Ignore events. */
break;
}
}
static void map_rua_disrupted_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct hnbgw_context_map *map = fi->priv;
map_rua_free_if_done(map, MAP_SCCP_EV_RAN_LINK_LOST, NULL);
}
void map_rua_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct hnbgw_context_map *map = fi->priv;
map->rua_fi = NULL;
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state map_rua_fsm_states[] = {
[MAP_RUA_ST_INIT] = {
.name = "init",
.in_event_mask = 0
| S(MAP_RUA_EV_RX_CONNECT)
| S(MAP_RUA_EV_RX_DISCONNECT)
| S(MAP_RUA_EV_CN_DISC)
| S(MAP_RUA_EV_HNB_LINK_LOST)
,
.out_state_mask = 0
| S(MAP_RUA_ST_INIT)
| S(MAP_RUA_ST_CONNECTED)
| S(MAP_RUA_ST_DISCONNECTED)
| S(MAP_RUA_ST_DISRUPTED)
,
.action = map_rua_init_action,
},
[MAP_RUA_ST_CONNECTED] = {
.name = "connected",
.in_event_mask = 0
| S(MAP_RUA_EV_RX_DIRECT_TRANSFER)
| S(MAP_RUA_EV_TX_DIRECT_TRANSFER)
| S(MAP_RUA_EV_RX_DISCONNECT)
| S(MAP_RUA_EV_CN_DISC)
| S(MAP_RUA_EV_HNB_LINK_LOST)
,
.out_state_mask = 0
| S(MAP_RUA_ST_DISCONNECTED)
| S(MAP_RUA_ST_DISRUPTED)
,
.action = map_rua_connected_action,
},
[MAP_RUA_ST_DISCONNECTED] = {
.name = "disconnected",
.in_event_mask = 0
| S(MAP_RUA_EV_TX_DIRECT_TRANSFER)
| S(MAP_RUA_EV_CN_DISC)
| S(MAP_RUA_EV_HNB_LINK_LOST)
,
.onenter = map_rua_disconnected_onenter,
.action = map_rua_disconnected_action,
},
[MAP_RUA_ST_DISRUPTED] = {
.name = "disrupted",
.in_event_mask = 0
| S(MAP_RUA_EV_CN_DISC)
| S(MAP_RUA_EV_HNB_LINK_LOST)
,
.onenter = map_rua_disrupted_onenter,
/* same as MAP_RUA_ST_DISCONNECTED: */
.action = map_rua_disconnected_action,
},
};
static struct osmo_fsm map_rua_fsm = {
.name = "map_rua",
.states = map_rua_fsm_states,
.num_states = ARRAY_SIZE(map_rua_fsm_states),
.log_subsys = DHNB,
.event_names = map_rua_fsm_event_names,
.timer_cb = map_rua_fsm_timer_cb,
.cleanup = map_rua_fsm_cleanup,
};
static __attribute__((constructor)) void map_rua_fsm_register(void)
{
OSMO_ASSERT(osmo_fsm_register(&map_rua_fsm) == 0);
}

View File

@@ -0,0 +1,590 @@
/* SCCP side FSM of hnbgw_context_map */
/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
*
* 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 "config.h"
#include <osmocom/core/utils.h>
#include <osmocom/core/fsm.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/hnbgw_ranap.h>
#include <osmocom/hnbgw/tdefs.h>
enum map_sccp_fsm_state {
MAP_SCCP_ST_INIT,
MAP_SCCP_ST_WAIT_CC,
MAP_SCCP_ST_CONNECTED,
MAP_SCCP_ST_WAIT_RLSD,
MAP_SCCP_ST_DISCONNECTED,
};
static const struct value_string map_sccp_fsm_event_names[] = {
OSMO_VALUE_STRING(MAP_SCCP_EV_RX_CONNECTION_CONFIRM),
OSMO_VALUE_STRING(MAP_SCCP_EV_RX_DATA_INDICATION),
OSMO_VALUE_STRING(MAP_SCCP_EV_TX_DATA_REQUEST),
OSMO_VALUE_STRING(MAP_SCCP_EV_RAN_DISC),
OSMO_VALUE_STRING(MAP_SCCP_EV_RAN_LINK_LOST),
OSMO_VALUE_STRING(MAP_SCCP_EV_RX_RELEASED),
OSMO_VALUE_STRING(MAP_SCCP_EV_USER_ABORT),
OSMO_VALUE_STRING(MAP_SCCP_EV_CN_LINK_LOST),
{}
};
static struct osmo_fsm map_sccp_fsm;
static const struct osmo_tdef_state_timeout map_sccp_fsm_timeouts[32] = {
[MAP_SCCP_ST_INIT] = { .T = -31 },
[MAP_SCCP_ST_WAIT_CC] = { .T = -31 },
[MAP_SCCP_ST_CONNECTED] = { .T = 0 },
[MAP_SCCP_ST_WAIT_RLSD] = { .T = -31 },
[MAP_SCCP_ST_DISCONNECTED] = { .T = -31 },
};
/* Transition to a state, using the T timer defined in map_sccp_fsm_timeouts.
* Assumes local variable fi exists. */
#define map_sccp_fsm_state_chg(state) \
OSMO_ASSERT(osmo_tdef_fsm_inst_state_chg(fi, state, \
map_sccp_fsm_timeouts, \
hnbgw_T_defs, \
5) == 0)
void map_sccp_fsm_alloc(struct hnbgw_context_map *map)
{
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&map_sccp_fsm, map, map, LOGL_DEBUG, NULL);
OSMO_ASSERT(fi);
osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-%s-SCCP-%u", hnb_context_name(map->hnb_ctx),
map->cnlink ? map->cnlink->name : (map->is_ps ? "PS" : "CS"),
map->scu_conn_id);
OSMO_ASSERT(map->sccp_fi == NULL);
map->sccp_fi = fi;
INIT_LLIST_HEAD(&map->sccp_fi_ctx.wait_cc_tx_msg_list);
/* trigger the timeout */
map_sccp_fsm_state_chg(MAP_SCCP_ST_INIT);
}
enum hnbgw_context_map_state map_sccp_get_state(struct hnbgw_context_map *map)
{
if (!map || !map->sccp_fi)
return MAP_S_DISCONNECTING;
switch (map->sccp_fi->state) {
case MAP_SCCP_ST_INIT:
case MAP_SCCP_ST_WAIT_CC:
return MAP_S_CONNECTING;
case MAP_SCCP_ST_CONNECTED:
return MAP_S_ACTIVE;
default:
case MAP_SCCP_ST_WAIT_RLSD:
case MAP_SCCP_ST_DISCONNECTED:
return MAP_S_DISCONNECTING;
}
}
bool map_sccp_is_active(struct hnbgw_context_map *map)
{
if (!map || !map->sccp_fi)
return false;
switch (map->sccp_fi->state) {
case MAP_SCCP_ST_DISCONNECTED:
return false;
default:
return true;
}
}
static int tx_sccp_cr(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
{
struct hnbgw_context_map *map = fi->priv;
if (!map->cnlink) {
LOGPFSML(fi, LOGL_ERROR, "Failed to send SCCP Connection Request: no CN link\n");
return -1;
}
if (!ranap_msg) {
/* prepare a msgb to send an empty N-Connect prim (but this should never happen in practice) */
ranap_msg = hnbgw_ranap_msg_alloc("SCCP-CR-empty");
}
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_N_CONNECT_REQ);
return hnbgw_sccp_user_tx_connect_req(map->cnlink->hnbgw_sccp_user,
&map->cnlink->remote_addr,
map->scu_conn_id,
ranap_msg);
}
static int tx_sccp_df1(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
{
struct hnbgw_context_map *map = fi->priv;
if (!msg_has_l2_data(ranap_msg))
return 0;
if (!map->cnlink) {
LOGPFSML(fi, LOGL_ERROR, "Failed to send SCCP Data Form 1: no CN link\n");
return -1;
}
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_N_DATA_REQ);
return hnbgw_sccp_user_tx_data_req(map->cnlink->hnbgw_sccp_user,
map->scu_conn_id,
ranap_msg);
}
static int tx_sccp_rlsd(struct osmo_fsm_inst *fi)
{
struct hnbgw_context_map *map = fi->priv;
if (!map->cnlink) {
LOGPFSML(fi, LOGL_ERROR, "Failed to send SCCP RLSD: no CN link\n");
return -1;
}
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_N_DISCONNECT_REQ);
return hnbgw_sccp_user_tx_disconnect_req(map->cnlink->hnbgw_sccp_user,
map->scu_conn_id);
}
static int handle_rx_sccp(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
{
struct hnbgw_context_map *map = fi->priv;
/* If the FSM instance has already terminated, don't dispatch anything. */
if (fi->proc.terminating)
return 0;
/* When there was no message received along with the received event, then there is nothing to forward to RUA. */
if (!msg_has_l2_data(ranap_msg))
return 0;
return hnbgw_ranap_rx_data_dl(map, ranap_msg);
}
static void wait_cc_tx_msg_list_enqueue(struct hnbgw_context_map *map, struct msgb *ranap_msg)
{
talloc_steal(map, ranap_msg);
msgb_enqueue(&map->sccp_fi_ctx.wait_cc_tx_msg_list, ranap_msg);
}
static struct msgb *wait_cc_tx_msg_list_dequeue(struct hnbgw_context_map *map)
{
struct msgb *ranap_msg = msgb_dequeue(&map->sccp_fi_ctx.wait_cc_tx_msg_list);
if (ranap_msg)
talloc_steal(OTC_SELECT, ranap_msg);
return ranap_msg;
}
static void map_sccp_init_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msgb *ranap_msg = NULL;
struct hnbgw_context_map *map = fi->priv;
switch (event) {
case MAP_SCCP_EV_TX_DATA_REQUEST:
ranap_msg = data;
/* In the INIT state, the first MAP_SCCP_EV_TX_DATA_REQUEST will be the RANAP message received from the
* RUA Connect message. Send the SCCP CR and transition to WAIT_CC. */
if (tx_sccp_cr(fi, ranap_msg) == 0)
map_sccp_fsm_state_chg(MAP_SCCP_ST_WAIT_CC);
return;
case MAP_SCCP_EV_RAN_LINK_LOST:
case MAP_SCCP_EV_USER_ABORT:
case MAP_SCCP_EV_CN_LINK_LOST:
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return;
case MAP_SCCP_EV_RAN_DISC:
/* bool rua_disconnect_err_condition = !!data; */
/* 3GPP TS 25.468 9.1.5: RUA has disconnected.
* In this state we didn't send an SCCP CR yet, so nothing to be torn down on CN side. */
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return;
case MAP_SCCP_EV_RX_RELEASED:
/* SCCP RLSD received from CN. This will never happen since we haven't even asked for a connection, but
* for completeness: */
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_RLSD_CN_ORIGIN);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return;
default:
OSMO_ASSERT(false);
}
}
static void map_sccp_wait_cc_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct hnbgw_context_map *map = fi->priv;
struct msgb *ranap_msg = NULL;
switch (event) {
case MAP_SCCP_EV_RX_CONNECTION_CONFIRM:
ranap_msg = data;
map_sccp_fsm_state_chg(MAP_SCCP_ST_CONNECTED);
/* Usually doesn't but if the SCCP CC contained data, forward it to RUA */
handle_rx_sccp(fi, ranap_msg);
return;
case MAP_SCCP_EV_TX_DATA_REQUEST:
ranap_msg = data;
LOGPFSML(fi, LOGL_INFO, "Caching RANAP msg from RUA while waiting for SCCP CC\n");
wait_cc_tx_msg_list_enqueue(map, ranap_msg);
return;
case MAP_SCCP_EV_RAN_LINK_LOST:
case MAP_SCCP_EV_USER_ABORT:
case MAP_SCCP_EV_CN_LINK_LOST:
map->please_disconnect = true;
return;
case MAP_SCCP_EV_RAN_DISC:
/* bool rua_disconnect_err_condition = !!data; */
/* 3GPP TS 25.468 9.1.5: RUA has disconnected.
* In this state we didn't send an SCCP CR yet, so nothing to be torn down on CN side. */
map->please_disconnect = true;
return;
case MAP_SCCP_EV_RX_RELEASED:
ranap_msg = data;
/* SCCP RLSD received from CN. This will never happen since we haven't even received a Connection
* Confirmed, but for completeness: */
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_RLSD_CN_ORIGIN);
handle_rx_sccp(fi, ranap_msg);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return;
default:
OSMO_ASSERT(false);
}
}
static void map_sccp_connected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct hnbgw_context_map *map = fi->priv;
struct msgb *ranap_msg;
/* Now that SCCP conn is confirmed, forward pending msgs received from RUA side: */
while ((ranap_msg = wait_cc_tx_msg_list_dequeue(map)))
tx_sccp_df1(fi, ranap_msg);
if (map->please_disconnect) {
/* SCCP has already been asked to disconnect, so disconnect now that the
* CC has been received. Send RLSD to SCCP (without RANAP data) */
tx_sccp_rlsd(fi);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
}
}
static void map_sccp_connected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct hnbgw_context_map *map = fi->priv;
struct msgb *ranap_msg = NULL;
bool rua_disconnect_err_condition;
switch (event) {
case MAP_SCCP_EV_RX_DATA_INDICATION:
ranap_msg = data;
/* forward RANAP from SCCP to RUA */
handle_rx_sccp(fi, ranap_msg);
return;
case MAP_SCCP_EV_TX_DATA_REQUEST:
ranap_msg = data;
/* Someone (usually the RUA side) wants us to send a RANAP payload to CN via SCCP */
tx_sccp_df1(fi, ranap_msg);
return;
case MAP_SCCP_EV_RAN_DISC:
rua_disconnect_err_condition = !!data;
/* 3GPP TS 25.468 9.1.5: RUA has disconnected.
* - Under normal conditions (cause=Normal) the RUA Disconnect
* contained a RANAP Iu-ReleaseComplete which we already
* handled here through MAP_SCCP_EV_TX_DATA_REQUEST.
* On SCCP, We will expect the CN to send an SCCP RLSD soon.
* - Under error conditions, cause!=Normal and there was no RANAP message.
* In that case, we need to tear down the associated SCCP link towards CN,
* which in turn will tear down the upper layer Iu conn.
*/
if (rua_disconnect_err_condition) {
tx_sccp_rlsd(fi);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
} else {
map_sccp_fsm_state_chg(MAP_SCCP_ST_WAIT_RLSD);
}
return;
case MAP_SCCP_EV_RAN_LINK_LOST:
/* RUA has disconnected ungracefully, so there is no Iu Release that told the CN to disconnect.
* Disconnect on the SCCP layer, ungracefully. */
case MAP_SCCP_EV_USER_ABORT:
/* The user is asking for disconnection, so there is no Iu Release in progress. Disconnect now. */
case MAP_SCCP_EV_CN_LINK_LOST:
/* The CN peer has sent a RANAP RESET, so the old link that this map ran on is lost */
tx_sccp_rlsd(fi);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return;
case MAP_SCCP_EV_RX_RELEASED:
ranap_msg = data;
/* The CN sends an N-Disconnect (SCCP Released). */
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_RLSD_CN_ORIGIN);
handle_rx_sccp(fi, ranap_msg);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return;
case MAP_SCCP_EV_RX_CONNECTION_CONFIRM:
ranap_msg = data;
/* Already connected. Unusual, but if there is data just forward it. */
LOGPFSML(fi, LOGL_ERROR, "Already connected, but received SCCP CC again\n");
handle_rx_sccp(fi, ranap_msg);
return;
default:
OSMO_ASSERT(false);
}
}
static void map_sccp_wait_rlsd_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct hnbgw_context_map *map = fi->priv;
/* For sanity, always tell RUA to disconnect, if it hasn't done so. */
if (map_rua_is_active(map))
map_rua_dispatch(map, MAP_RUA_EV_CN_DISC, NULL);
}
static void map_sccp_wait_rlsd_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msgb *ranap_msg = NULL;
switch (event) {
case MAP_SCCP_EV_RX_RELEASED:
ranap_msg = data;
/* The CN sends the expected SCCP RLSD.
* Usually there is no data, but if there is just forward it.
* Usually RUA is already disconnected, but let the RUA FSM decide about that. */
handle_rx_sccp(fi, ranap_msg);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return;
case MAP_SCCP_EV_RX_DATA_INDICATION:
ranap_msg = data;
/* RUA is probably already disconnected, but let the RUA FSM decide about that. */
handle_rx_sccp(fi, ranap_msg);
return;
case MAP_SCCP_EV_TX_DATA_REQUEST:
ranap_msg = data;
/* Normally, RUA would already disconnected, but since SCCP is officially still connected, we can still
* forward messages there. Already waiting for CN to send the SCCP RLSD. If there is a message, forward
* it, and just continue to time out on the SCCP RLSD. */
tx_sccp_df1(fi, ranap_msg);
return;
case MAP_SCCP_EV_RX_CONNECTION_CONFIRM:
ranap_msg = data;
/* Already connected. Unusual, but if there is data just forward it. */
LOGPFSML(fi, LOGL_ERROR, "Already connected, but received SCCP CC\n");
handle_rx_sccp(fi, ranap_msg);
return;
case MAP_SCCP_EV_RAN_LINK_LOST:
case MAP_SCCP_EV_USER_ABORT:
case MAP_SCCP_EV_CN_LINK_LOST:
case MAP_SCCP_EV_RAN_DISC:
/* Stop waiting for RLSD, send RLSD now. */
tx_sccp_rlsd(fi);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return;
default:
OSMO_ASSERT(false);
}
}
static void map_sccp_disconnected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct hnbgw_context_map *map = fi->priv;
/* From SCCP's POV, we can now free the hnbgw_context_map.
* If RUA is still active, tell it to disconnect -- in that case the RUA side will call context_map_free().
* If RUA is no longer active, free this map. */
if (map_rua_is_active(map))
map_rua_dispatch(map, MAP_RUA_EV_CN_DISC, NULL);
else
context_map_free(map);
}
static void map_sccp_disconnected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msgb *ranap_msg = data;
if (msg_has_l2_data(ranap_msg))
LOGPFSML(fi, LOGL_ERROR, "SCCP not connected, cannot dispatch RANAP message\n");
}
static int map_sccp_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct hnbgw_context_map *map = fi->priv;
/* Return 1 to terminate FSM instance, 0 to keep running */
switch (fi->state) {
case MAP_SCCP_ST_INIT:
/* cannot sent SCCP RLSD, because we haven't set up an SCCP link */
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return 0;
case MAP_SCCP_ST_WAIT_CC:
/* send N-DISCONNECT. libosmo-sigtran/sccp_scoc.c will do the SCCP connection cleanup, like waiting a
* bit whether the SCCP CC might still arrive, and cleanup the conn if not. */
case MAP_SCCP_ST_CONNECTED:
case MAP_SCCP_ST_WAIT_RLSD:
/* send SCCP RLSD. libosmo-sigtran/sccp_scoc.c will do the SCCP connection cleanup.
* (It will repeatedly send SCCP RLSD until the peer responded with SCCP RLC, or until the
* sccp_connection->t_int timer expires, and the sccp_connection is freed.) */
if (map->cnlink)
tx_sccp_rlsd(fi);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return 0;
default:
case MAP_SCCP_ST_DISCONNECTED:
return 1;
}
}
void map_sccp_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct hnbgw_context_map *map = fi->priv;
map->sccp_fi = NULL;
msgb_queue_free(&map->sccp_fi_ctx.wait_cc_tx_msg_list);
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state map_sccp_fsm_states[] = {
[MAP_SCCP_ST_INIT] = {
.name = "init",
.in_event_mask = 0
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
| S(MAP_SCCP_EV_RAN_DISC)
| S(MAP_SCCP_EV_RAN_LINK_LOST)
| S(MAP_SCCP_EV_RX_RELEASED)
| S(MAP_SCCP_EV_USER_ABORT)
| S(MAP_SCCP_EV_CN_LINK_LOST)
,
.out_state_mask = 0
| S(MAP_SCCP_ST_INIT)
| S(MAP_SCCP_ST_WAIT_CC)
| S(MAP_SCCP_ST_DISCONNECTED)
,
.action = map_sccp_init_action,
},
[MAP_SCCP_ST_WAIT_CC] = {
.name = "wait_cc",
.in_event_mask = 0
| S(MAP_SCCP_EV_RX_CONNECTION_CONFIRM)
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
| S(MAP_SCCP_EV_RAN_DISC)
| S(MAP_SCCP_EV_RAN_LINK_LOST)
| S(MAP_SCCP_EV_RX_RELEASED)
| S(MAP_SCCP_EV_USER_ABORT)
| S(MAP_SCCP_EV_CN_LINK_LOST)
,
.out_state_mask = 0
| S(MAP_SCCP_ST_CONNECTED)
| S(MAP_SCCP_ST_DISCONNECTED)
,
.action = map_sccp_wait_cc_action,
},
[MAP_SCCP_ST_CONNECTED] = {
.name = "connected",
.in_event_mask = 0
| S(MAP_SCCP_EV_RX_DATA_INDICATION)
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
| S(MAP_SCCP_EV_RAN_DISC)
| S(MAP_SCCP_EV_RAN_LINK_LOST)
| S(MAP_SCCP_EV_RX_RELEASED)
| S(MAP_SCCP_EV_RX_CONNECTION_CONFIRM)
| S(MAP_SCCP_EV_USER_ABORT)
| S(MAP_SCCP_EV_CN_LINK_LOST)
,
.out_state_mask = 0
| S(MAP_SCCP_ST_WAIT_RLSD)
| S(MAP_SCCP_ST_DISCONNECTED)
,
.onenter = map_sccp_connected_onenter,
.action = map_sccp_connected_action,
},
[MAP_SCCP_ST_WAIT_RLSD] = {
.name = "wait_rlsd",
.in_event_mask = 0
| S(MAP_SCCP_EV_RX_RELEASED)
| S(MAP_SCCP_EV_RX_DATA_INDICATION)
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
| S(MAP_SCCP_EV_RAN_DISC)
| S(MAP_SCCP_EV_RAN_LINK_LOST)
| S(MAP_SCCP_EV_RX_CONNECTION_CONFIRM)
| S(MAP_SCCP_EV_USER_ABORT)
| S(MAP_SCCP_EV_CN_LINK_LOST)
,
.out_state_mask = 0
| S(MAP_SCCP_ST_DISCONNECTED)
,
.onenter = map_sccp_wait_rlsd_onenter,
.action = map_sccp_wait_rlsd_action,
},
[MAP_SCCP_ST_DISCONNECTED] = {
.name = "disconnected",
.in_event_mask = 0
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
| S(MAP_SCCP_EV_RAN_DISC)
| S(MAP_SCCP_EV_RAN_LINK_LOST)
| S(MAP_SCCP_EV_USER_ABORT)
| S(MAP_SCCP_EV_CN_LINK_LOST)
,
.onenter = map_sccp_disconnected_onenter,
.action = map_sccp_disconnected_action,
},
};
static struct osmo_fsm map_sccp_fsm = {
.name = "map_sccp",
.states = map_sccp_fsm_states,
.num_states = ARRAY_SIZE(map_sccp_fsm_states),
.log_subsys = DCN,
.event_names = map_sccp_fsm_event_names,
.timer_cb = map_sccp_fsm_timer_cb,
.cleanup = map_sccp_fsm_cleanup,
};
static __attribute__((constructor)) void map_sccp_fsm_register(void)
{
OSMO_ASSERT(osmo_fsm_register(&map_sccp_fsm) == 0);
}

401
src/osmo-hnbgw/hnb.c Normal file
View File

@@ -0,0 +1,401 @@
/* HNB related code */
/* (C) 2015,2024 by Harald Welte <laforge@gnumonks.org>
* (C) 2016-2025 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 "config.h"
#include <inttypes.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/stat_item.h>
#include <osmocom/gsm/gsm23236.h>
#include <osmocom/netif/stream.h>
#include <osmocom/hnbgw/umts_cell_id.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnb_persistent.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_hnbap.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbgw/tdefs.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/mgw_fsm.h>
/* update the active RAB duration rate_ctr for given HNB */
void hnb_store_rab_durations(struct hnb_context *hnb)
{
struct hnbgw_context_map *map;
struct timespec now;
uint64_t elapsed_cs_rab_ms = 0;
osmo_clock_gettime(CLOCK_MONOTONIC, &now);
/* iterate over all context_maps (subscribers) */
llist_for_each_entry(map, &hnb->map_list, hnb_list) {
/* skip any PS maps, we care about CS RABs only here */
if (map->is_ps)
continue;
elapsed_cs_rab_ms += mgw_fsm_get_elapsed_ms(map, &now);
}
/* Export to rate countes. */
rate_ctr_add(HNBP_CTR(hnb->persistent, HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL), elapsed_cs_rab_ms);
}
/***********************************************************************
* HNB Context
***********************************************************************/
/* look-up HNB context by id. Used from CTRL */
static struct hnb_context *hnb_context_by_id(uint32_t cid)
{
struct hnb_context *hnb;
llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) {
if (hnb->id.cid == cid)
return hnb;
}
return NULL;
}
/* look-up HNB context by identity_info. Used from VTY */
struct hnb_context *hnb_context_by_identity_info(const char *identity_info)
{
struct hnb_context *hnb;
llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) {
if (strcmp(identity_info, hnb->identity_info) == 0)
return hnb;
}
return NULL;
}
static int hnb_read_cb(struct osmo_stream_srv *conn);
static int hnb_closed_cb(struct osmo_stream_srv *conn);
static struct hnb_context *hnb_context_alloc(struct osmo_stream_srv_link *link, int new_fd)
{
struct hnb_context *ctx;
ctx = talloc_zero(g_hnbgw, struct hnb_context);
if (!ctx)
return NULL;
INIT_LLIST_HEAD(&ctx->map_list);
ctx->conn = osmo_stream_srv_create(g_hnbgw, link, new_fd, hnb_read_cb, hnb_closed_cb, ctx);
if (!ctx->conn) {
LOGP(DMAIN, LOGL_INFO, "error while creating connection\n");
talloc_free(ctx);
return NULL;
}
llist_add_tail(&ctx->list, &g_hnbgw->hnb_list);
return ctx;
}
const char *hnb_context_name(struct hnb_context *ctx)
{
char *result;
if (!ctx)
return "NULL";
if (ctx->conn) {
char hostbuf_r[INET6_ADDRSTRLEN];
char portbuf_r[6];
int fd = osmo_stream_srv_get_ofd(ctx->conn)->fd;
/* get remote addr */
if (osmo_sock_get_ip_and_port(fd, hostbuf_r, sizeof(hostbuf_r), portbuf_r, sizeof(portbuf_r), false) == 0)
result = talloc_asprintf(OTC_SELECT, "%s:%s", hostbuf_r, portbuf_r);
else
result = "?";
} else {
result = "disconnected";
}
if (g_hnbgw->config.log_prefix_hnb_id)
result = talloc_asprintf(OTC_SELECT, "%s %s", result, ctx->identity_info);
else
result = talloc_asprintf(OTC_SELECT, "%s %s", result, umts_cell_id_to_str(&ctx->id));
return result;
}
void hnb_context_release_ue_state(struct hnb_context *ctx)
{
struct hnbgw_context_map *map, *map2;
/* deactivate all context maps */
llist_for_each_entry_safe(map, map2, &ctx->map_list, hnb_list) {
context_map_hnb_released(map);
/* hnbgw_context_map will remove itself from lists when it is ready. */
}
}
void hnb_context_release(struct hnb_context *ctx)
{
struct hnbgw_context_map *map;
LOGHNB(ctx, DMAIN, LOGL_INFO, "Releasing HNB context\n");
if (ctx->persistent) {
struct timespec tp;
int rc;
rc = osmo_clock_gettime(CLOCK_MONOTONIC, &tp);
ctx->persistent->updowntime = (rc < 0) ? 0 : tp.tv_sec;
}
/* remove from the list of HNB contexts */
llist_del(&ctx->list);
hnb_context_release_ue_state(ctx);
if (ctx->conn) { /* we own a conn, we must free it: */
LOGHNB(ctx, DMAIN, LOGL_INFO, "Closing HNB SCTP connection %s\n",
osmo_sock_get_name2(osmo_stream_srv_get_ofd(ctx->conn)->fd));
/* Avoid our closed_cb calling hnb_context_release() again: */
osmo_stream_srv_set_data(ctx->conn, NULL);
osmo_stream_srv_destroy(ctx->conn);
} /* else: we are called from closed_cb, so conn is being freed separately */
/* hnbgw_context_map are still listed in ctx->map_list, but we are freeing ctx. Remove all entries from the
* list, but keep the hnbgw_context_map around for graceful release. They are also listed under
* hnbgw_cnlink->map_list, and will remove themselves when ready. */
while ((map = llist_first_entry_or_null(&ctx->map_list, struct hnbgw_context_map, hnb_list))) {
llist_del(&map->hnb_list);
map->hnb_ctx = NULL;
}
/* remove back reference from hnb_persistent to context */
if (ctx->persistent)
hnb_persistent_deregistered(ctx->persistent);
talloc_free(ctx);
}
unsigned long long hnb_get_updowntime(const struct hnb_context *ctx)
{
if (!ctx->persistent)
return 0;
return hnbp_get_updowntime(ctx->persistent);
}
/***********************************************************************
* SCTP Socket / stream handling
***********************************************************************/
static int hnb_read_cb(struct osmo_stream_srv *conn)
{
struct hnb_context *hnb = osmo_stream_srv_get_data(conn);
struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn);
struct msgb *msg = msgb_alloc(IUH_MSGB_SIZE, "Iuh rx");
int rc;
if (!msg)
return -ENOMEM;
OSMO_ASSERT(hnb);
/* we store a reference to the HomeNodeB in the msg->dest for the
* benefit of various downstream processing functions */
msg->dst = hnb;
rc = osmo_stream_srv_recv(conn, msg);
/* Notification received */
if (msgb_sctp_msg_flags(msg) & OSMO_STREAM_SCTP_MSG_FLAGS_NOTIFICATION) {
union sctp_notification *notif = (union sctp_notification *)msgb_data(msg);
rc = 0;
switch (notif->sn_header.sn_type) {
case SCTP_ASSOC_CHANGE:
switch (notif->sn_assoc_change.sac_state) {
case SCTP_COMM_LOST:
LOGHNB(hnb, DMAIN, LOGL_NOTICE,
"sctp_recvmsg(%s) = SCTP_COMM_LOST, closing conn\n",
osmo_sock_get_name2(ofd->fd));
osmo_stream_srv_destroy(conn);
rc = -EBADF;
break;
case SCTP_RESTART:
LOGHNB(hnb, DMAIN, LOGL_NOTICE, "HNB SCTP conn RESTARTed, marking as HNBAP-unregistered\n");
hnb->hnb_registered = false;
hnb_context_release_ue_state(hnb);
/* The tx queue may be quite full after an SCTP RESTART: (SYS#6113)
* The link may have been flaky (a possible reason for the peer restarting the conn) and
* hence the kernel socket Tx queue may be full (no ACKs coming back) and our own userspace
* queue may contain plenty of oldish messages to be sent. Since the HNB will re-register after
* this, we simply drop all those old messages: */
osmo_stream_srv_clear_tx_queue(conn);
break;
}
break;
case SCTP_SHUTDOWN_EVENT:
LOGHNB(hnb, DMAIN, LOGL_NOTICE,
"sctp_recvmsg(%s) = SCTP_SHUTDOWN_EVENT, closing conn\n",
osmo_sock_get_name2(ofd->fd));
osmo_stream_srv_destroy(conn);
rc = -EBADF;
break;
}
goto out;
} else if (rc == -EAGAIN) {
/* Older versions of osmo_stream_srv_recv() not supporting
* msgb_sctp_msg_flags() may still return -EAGAIN when an sctp
* notification is received. */
rc = 0;
goto out;
} else if (rc < 0) {
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Error during sctp_recvmsg(%s)\n",
osmo_sock_get_name2(ofd->fd));
osmo_stream_srv_destroy(conn);
rc = -EBADF;
goto out;
} else if (rc == 0) {
LOGHNB(hnb, DMAIN, LOGL_NOTICE, "Connection closed sctp_recvmsg(%s) = 0\n",
osmo_sock_get_name2(ofd->fd));
osmo_stream_srv_destroy(conn);
rc = -EBADF;
goto out;
} else {
msgb_put(msg, rc);
}
switch (msgb_sctp_ppid(msg)) {
case IUH_PPI_HNBAP:
hnb->hnbap_stream = msgb_sctp_stream(msg);
rc = hnbgw_hnbap_rx(hnb, msg);
break;
case IUH_PPI_RUA:
if (!hnb->hnb_registered) {
LOGHNB(hnb, DMAIN, LOGL_NOTICE, "Discarding RUA as HNB is not registered\n");
goto out;
}
hnb->rua_stream = msgb_sctp_stream(msg);
rc = hnbgw_rua_rx(hnb, msg);
break;
case IUH_PPI_SABP:
case IUH_PPI_RNA:
case IUH_PPI_PUA:
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unimplemented SCTP PPID=%lu received\n", msgb_sctp_ppid(msg));
rc = 0;
break;
default:
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unknown SCTP PPID=%lu received\n", msgb_sctp_ppid(msg));
rc = 0;
break;
}
out:
msgb_free(msg);
return rc;
}
static int hnb_closed_cb(struct osmo_stream_srv *conn)
{
struct hnb_context *hnb = osmo_stream_srv_get_data(conn);
if (!hnb)
return 0; /* hnb_context is being freed, nothing do be done */
/* hnb: conn became broken, let's release the associated hnb.
* conn object is being freed after closed_cb(), so unassign it from hnb
* if available to avoid it freeing it again: */
hnb->conn = NULL;
hnb_context_release(hnb);
return 0;
}
/*! call-back when the listen FD has something to read */
int hnbgw_rua_accept_cb(struct osmo_stream_srv_link *srv, int fd)
{
struct hnb_context *ctx;
LOGP(DMAIN, LOGL_INFO, "New HNB SCTP connection %s\n",
osmo_sock_get_name2(fd));
ctx = hnb_context_alloc(srv, fd);
if (!ctx)
return -ENOMEM;
return 0;
}
CTRL_CMD_DEFINE_RO(hnb_info, "info");
static int get_hnb_info(struct ctrl_cmd *cmd, void *data)
{
struct hnb_context *hnb = data;
cmd->reply = talloc_strdup(cmd, hnb->identity_info);
return CTRL_CMD_REPLY;
}
CTRL_CMD_DEFINE_RO(hnbs, "num-hnb");
static int get_hnbs(struct ctrl_cmd *cmd, void *data)
{
cmd->reply = talloc_asprintf(cmd, "%u", llist_count(&g_hnbgw->hnb_list));
return CTRL_CMD_REPLY;
}
int hnb_ctrl_cmds_install(void)
{
int rc = 0;
rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_hnbs);
rc |= ctrl_cmd_install(CTRL_NODE_HNB, &cmd_hnb_info);
return rc;
}
int hnb_ctrl_node_lookup(void *data, vector vline, int *node_type, void **node_data, int *i)
{
const char *token = vector_slot(vline, *i);
struct hnb_context *hnb;
long num;
switch (*node_type) {
case CTRL_NODE_ROOT:
if (strcmp(token, "hnb") != 0)
return 0;
(*i)++;
if (!ctrl_parse_get_num(vline, *i, &num))
return -ERANGE;
hnb = hnb_context_by_id(num);
if (!hnb)
return -ENODEV;
*node_data = hnb;
*node_type = CTRL_NODE_HNB;
break;
default:
return 0;
}
return 1;
}

View File

@@ -0,0 +1,403 @@
/* HNB persistent related code */
/* (C) 2015,2024 by Harald Welte <laforge@gnumonks.org>
* (C) 2016-2025 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 "config.h"
#include <inttypes.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/stat_item.h>
#include <osmocom/core/jhash.h>
#include <osmocom/gsm/gsm23236.h>
#include <osmocom/netif/stream.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnb_persistent.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/tdefs.h>
/***********************************************************************
* HNB Persistent Data
***********************************************************************/
const struct rate_ctr_desc hnb_ctr_description[] = {
[HNB_CTR_IUH_ESTABLISHED] = {
"iuh:established", "Number of times Iuh link was established" },
[HNB_CTR_RANAP_PS_ERR_IND_UL] = {
"ranap:ps:error_ind:ul", "Received ERROR Indications in Uplink (PS Domain)" },
[HNB_CTR_RANAP_CS_ERR_IND_UL] = {
"ranap:cs:error_ind:ul", "Received ERROR Indications in Uplink (PS Domain)" },
[HNB_CTR_RANAP_PS_RESET_REQ_UL] = {
"ranap:ps:reset_req:ul", "Received RESET Requests in Uplink (PS Domain)" },
[HNB_CTR_RANAP_CS_RESET_REQ_UL] = {
"ranap:cs:reset_req:ul", "Received RESET Requests in Uplink (CS Domain)" },
[HNB_CTR_RANAP_PS_RAB_ACT_REQ] = {
"ranap:ps:rab_act:req", "PS RAB Activations requested" },
[HNB_CTR_RANAP_CS_RAB_ACT_REQ] = {
"ranap:cs:rab_act:req", "CS RAB Activations requested" },
[HNB_CTR_RANAP_PS_RAB_ACT_REQ_UNEXP] = {
"ranap:ps:rab_act:req_unexp", "PS RAB Activations requested in unexpected state" },
[HNB_CTR_RANAP_CS_RAB_ACT_REQ_UNEXP] = {
"ranap:cs:rab_act:req_unexp", "CS RAB Activations requested in unexpected state" },
[HNB_CTR_RANAP_PS_RAB_ACT_CNF] = {
"ranap:ps:rab_act:cnf", "PS RAB Activations confirmed" },
[HNB_CTR_RANAP_CS_RAB_ACT_CNF] = {
"ranap:cs:rab_act:cnf", "CS RAB Activations confirmed" },
[HNB_CTR_RANAP_PS_RAB_ACT_CNF_UNEXP] = {
"ranap:ps:rab_act:cnf_unexp", "PS RAB Activations confirmed in unexpected state" },
[HNB_CTR_RANAP_CS_RAB_ACT_CNF_UNEXP] = {
"ranap:cs:rab_act:cnf_unexp", "CS RAB Activations confirmed in unexpected state" },
[HNB_CTR_RANAP_PS_RAB_ACT_FAIL] = {
"ranap:ps:rab_act:fail", "PS RAB Activations failed" },
[HNB_CTR_RANAP_CS_RAB_ACT_FAIL] = {
"ranap:cs:rab_act:fail", "CS RAB Activations failed" },
[HNB_CTR_RANAP_PS_RAB_ACT_FAIL_UNEXP] = {
"ranap:ps:rab_act:fail_unexp", "PS RAB Activations failed in unexpected state" },
[HNB_CTR_RANAP_CS_RAB_ACT_FAIL_UNEXP] = {
"ranap:cs:rab_act:fail_unexp", "CS RAB Activations failed in unexpected state" },
[HNB_CTR_RANAP_PS_RAB_MOD_REQ] = {
"ranap:ps:rab_mod:req", "PS RAB Modifications requested" },
[HNB_CTR_RANAP_CS_RAB_MOD_REQ] = {
"ranap:cs:rab_mod:req", "CS RAB Modifications requested" },
[HNB_CTR_RANAP_PS_RAB_MOD_CNF] = {
"ranap:ps:rab_mod:cnf", "PS RAB Modifications confirmed" },
[HNB_CTR_RANAP_CS_RAB_MOD_CNF] = {
"ranap:cs:rab_mod:cnf", "CS RAB Modifications confirmed" },
[HNB_CTR_RANAP_PS_RAB_MOD_FAIL] = {
"ranap:ps:rab_mod:fail", "PS RAB Modifications failed" },
[HNB_CTR_RANAP_CS_RAB_MOD_FAIL] = {
"ranap:cs:rab_mod:fail", "CS RAB Modifications failed" },
[HNB_CTR_RANAP_PS_RAB_REL_REQ] = {
"ranap:ps:rab_rel:req:normal", "PS RAB Release requested (by CN), normal" },
[HNB_CTR_RANAP_CS_RAB_REL_REQ] = {
"ranap:cs:rab_rel:req:normal", "CS RAB Release requested (by CN), normal" },
[HNB_CTR_RANAP_PS_RAB_REL_REQ_ABNORMAL] = {
"ranap:ps:rab_rel:req:abnormal", "PS RAB Release requested (by CN), abnormal" },
[HNB_CTR_RANAP_CS_RAB_REL_REQ_ABNORMAL] = {
"ranap:cs:rab_rel:req:abnormal", "CS RAB Release requested (by CN), abnormal" },
[HNB_CTR_RANAP_PS_RAB_REL_REQ_UNEXP] = {
"ranap:ps:rab_rel:req:unexp", "PS RAB Release requested (by CN) in unexpected state" },
[HNB_CTR_RANAP_CS_RAB_REL_REQ_UNEXP] = {
"ranap:cs:rab_rel:req:unexp", "CS RAB Release requested (by CN) in unexpected state" },
[HNB_CTR_RANAP_PS_RAB_REL_CNF] = {
"ranap:ps:rab_rel:cnf", "PS RAB Release confirmed" },
[HNB_CTR_RANAP_CS_RAB_REL_CNF] = {
"ranap:cs:rab_rel:cnf", "CS RAB Release confirmed" },
[HNB_CTR_RANAP_PS_RAB_REL_CNF_UNEXP] = {
"ranap:ps:rab_rel:cnf_unexp", "PS RAB Release confirmed in unexpected state" },
[HNB_CTR_RANAP_CS_RAB_REL_CNF_UNEXP] = {
"ranap:cs:rab_rel:cnf_unexp", "CS RAB Release confirmed in unexpected state" },
[HNB_CTR_RANAP_PS_RAB_REL_FAIL] = {
"ranap:ps:rab_rel:fail", "PS RAB Release failed" },
[HNB_CTR_RANAP_CS_RAB_REL_FAIL] = {
"ranap:cs:rab_rel:fail", "CS RAB Release failed" },
[HNB_CTR_RANAP_PS_RAB_REL_FAIL_UNEXP] = {
"ranap:ps:rab_rel:fail_unexp", "PS RAB Release failed in unexpected state" },
[HNB_CTR_RANAP_CS_RAB_REL_FAIL_UNEXP] = {
"ranap:cs:rab_rel:fail_unexp", "CS RAB Release failed in unexpected state" },
[HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT] = {
"ranap:ps:rab_rel:implicit:normal", "PS RAB Release implicit (during Iu Release), normal" },
[HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT] = {
"ranap:cs:rab_rel:implicit:normal", "CS RAB Release implicit (during Iu Release), normal" },
[HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT_ABNORMAL] = {
"ranap:ps:rab_rel:implicit:abnormal", "PS RAB Release implicit (during Iu Release), abnormal" },
[HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT_ABNORMAL] = {
"ranap:cs:rab_rel:implicit:abnormal", "CS RAB Release implicit (during Iu Release), abnormal" },
[HNB_CTR_RUA_ERR_IND] = {
"rua:error_ind", "Received RUA Error Indications" },
[HNB_CTR_RUA_PS_CONNECT_UL] = {
"rua:ps:connect:ul", "Received RUA Connect requests (PS Domain)" },
[HNB_CTR_RUA_CS_CONNECT_UL] = {
"rua:cs:connect:ul", "Received RUA Connect requests (CS Domain)" },
[HNB_CTR_RUA_PS_DISCONNECT_UL] = {
"rua:ps:disconnect:ul", "Received RUA Disconnect requests in uplink (PS Domain)" },
[HNB_CTR_RUA_CS_DISCONNECT_UL] = {
"rua:cs:disconnect:ul", "Received RUA Disconnect requests in uplink (CS Domain)" },
[HNB_CTR_RUA_PS_DISCONNECT_DL] = {
"rua:ps:disconnect:dl", "Transmitted RUA Disconnect requests in downlink (PS Domain)" },
[HNB_CTR_RUA_CS_DISCONNECT_DL] = {
"rua:cs:disconnect:dl", "Transmitted RUA Disconnect requests in downlink (CS Domain)" },
[HNB_CTR_RUA_PS_DT_UL] = {
"rua:ps:direct_transfer:ul", "Received RUA DirectTransfer in uplink (PS Domain)" },
[HNB_CTR_RUA_CS_DT_UL] = {
"rua:cs:direct_transfer:ul", "Received RUA DirectTransfer in uplink (CS Domain)" },
[HNB_CTR_RUA_PS_DT_DL] = {
"rua:ps:direct_transfer:dl", "Transmitted RUA DirectTransfer in downlink (PS Domain)" },
[HNB_CTR_RUA_CS_DT_DL] = {
"rua:cs:direct_transfer:dl", "Transmitted RUA DirectTransfer in downlink (CS Domain)" },
[HNB_CTR_RUA_UDT_UL] = {
"rua:unit_data:ul", "Received RUA UnitData (UDT) in uplink" },
[HNB_CTR_RUA_UDT_DL] = {
"rua:unit_data:dl", "Transmitted RUA UnitData (UDT) in downlink" },
[HNB_CTR_PS_PAGING_ATTEMPTED] = {
"paging:ps:attempted", "Transmitted PS Paging requests" },
[HNB_CTR_CS_PAGING_ATTEMPTED] = {
"paging:cs:attempted", "Transmitted CS Paging requests" },
[HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL] = {
"rab:cs:active_milliseconds:total", "Cumulative number of milliseconds of CS RAB activity" },
[HNB_CTR_DTAP_CS_LU_REQ] = { "dtap:cs:location_update:req", "CS Location Update Requests" },
[HNB_CTR_DTAP_CS_LU_ACC] = { "dtap:cs:location_update:accept", "CS Location Update Accepts" },
[HNB_CTR_DTAP_CS_LU_REJ] = { "dtap:cs:location_update:reject", "CS Location Update Rejects" },
[HNB_CTR_DTAP_PS_ATT_REQ] = { "dtap:ps:attach:req", "PS Attach Requests" },
[HNB_CTR_DTAP_PS_ATT_ACK] = { "dtap:ps:attach:accept", "PS Attach Accepts" },
[HNB_CTR_DTAP_PS_ATT_REJ] = { "dtap:ps:attach:reject", "PS Attach Rejects" },
[HNB_CTR_DTAP_PS_RAU_REQ] = { "dtap:ps:routing_area_update:req", "PS Routing Area Update Requests" },
[HNB_CTR_DTAP_PS_RAU_ACK] = { "dtap:ps:routing_area_update:accept", "PS Routing Area Update Accepts" },
[HNB_CTR_DTAP_PS_RAU_REJ] = { "dtap:ps:routing_area_update:reject", "PS Routing Area Update Rejects" },
[HNB_CTR_GTPU_PACKETS_UL] = {
"gtpu:packets:ul",
"Count of GTP-U packets received from the HNB",
},
[HNB_CTR_GTPU_TOTAL_BYTES_UL] = {
"gtpu:total_bytes:ul",
"Count of total GTP-U bytes received from the HNB, including the GTP-U/UDP/IP headers",
},
[HNB_CTR_GTPU_UE_BYTES_UL] = {
"gtpu:ue_bytes:ul",
"Assuming an IP header length of 20 bytes, GTP-U bytes received from the HNB, excluding the GTP-U/UDP/IP headers",
},
[HNB_CTR_GTPU_PACKETS_DL] = {
"gtpu:packets:dl",
"Count of GTP-U packets sent to the HNB",
},
[HNB_CTR_GTPU_TOTAL_BYTES_DL] = {
"gtpu:total_bytes:dl",
"Count of total GTP-U bytes sent to the HNB, including the GTP-U/UDP/IP headers",
},
[HNB_CTR_GTPU_UE_BYTES_DL] = {
"gtpu:ue_bytes:dl",
"Assuming an IP header length of 20 bytes, GTP-U bytes sent to the HNB, excluding the GTP-U/UDP/IP headers",
},
};
const struct rate_ctr_group_desc hnb_ctrg_desc = {
"hnb",
"hNodeB",
OSMO_STATS_CLASS_GLOBAL,
ARRAY_SIZE(hnb_ctr_description),
hnb_ctr_description,
};
const struct osmo_stat_item_desc hnb_stat_desc[] = {
[HNB_STAT_UPTIME_SECONDS] = { "uptime:seconds", "Seconds of uptime", "s", 60, 0 },
};
const struct osmo_stat_item_group_desc hnb_statg_desc = {
.group_name_prefix = "hnb",
.group_description = "hNodeB",
.class_id = OSMO_STATS_CLASS_GLOBAL,
.num_items = ARRAY_SIZE(hnb_stat_desc),
.item_desc = hnb_stat_desc,
};
static void hnb_persistent_disconnected_timeout_cb(void *data)
{
hnb_persistent_free(data);
}
static void hnb_persistent_disconnected_timeout_schedule(struct hnb_persistent *hnbp)
{
unsigned long period_s = osmo_tdef_get(hnbgw_T_defs, -35, OSMO_TDEF_S, 60*60*24*7);
if (period_s < 1) {
LOG_HNBP(hnbp, LOGL_INFO,
"timer X35 is zero, not setting a disconnected timeout for this hnb-persistent instance.\n");
return;
}
/* It is fine if the timer is already active, osmo_timer_del() is done implicitly by the osmo_timer API. */
osmo_timer_setup(&hnbp->disconnected_timeout, hnb_persistent_disconnected_timeout_cb, hnbp);
osmo_timer_schedule(&hnbp->disconnected_timeout, period_s, 0);
}
struct hnb_persistent *hnb_persistent_alloc(const struct umts_cell_id *id)
{
struct hnb_persistent *hnbp = talloc_zero(g_hnbgw, struct hnb_persistent);
if (!hnbp)
return NULL;
hnbp->id = *id;
hnbp->id_str = talloc_strdup(hnbp, umts_cell_id_to_str(id));
hnbp->ctrs = rate_ctr_group_alloc(hnbp, &hnb_ctrg_desc, 0);
if (!hnbp->ctrs)
goto out_free;
rate_ctr_group_set_name(hnbp->ctrs, hnbp->id_str);
hnbp->statg = osmo_stat_item_group_alloc(hnbp, &hnb_statg_desc, 0);
if (!hnbp->statg)
goto out_free_ctrs;
osmo_stat_item_group_set_name(hnbp->statg, hnbp->id_str);
llist_add(&hnbp->list, &g_hnbgw->hnb_persistent_list);
hash_add(g_hnbgw->hnb_persistent_by_id, &hnbp->node_by_id, umts_cell_id_hash(&hnbp->id));
if (g_hnbgw->nft_kpi.active)
nft_kpi_hnb_persistent_add(hnbp);
/* Normally the disconnected timer runs only when the hNodeB is not currently connected on Iuh. This here is paranoia:
* In case we have to HNBAP HNB Register Reject, the disconnected timer should be active on this unused hnbp.
* On success, hnb_persistent_registered() will stop the disconnected timer directly after this. */
hnb_persistent_disconnected_timeout_schedule(hnbp);
return hnbp;
out_free_ctrs:
rate_ctr_group_free(hnbp->ctrs);
out_free:
talloc_free(hnbp);
return NULL;
}
struct hnb_persistent *hnb_persistent_find_by_id(const struct umts_cell_id *id)
{
struct hnb_persistent *hnbp;
uint32_t id_hash = umts_cell_id_hash(id);
hash_for_each_possible(g_hnbgw->hnb_persistent_by_id, hnbp, node_by_id, id_hash) {
if (umts_cell_id_equal(&hnbp->id, id))
return hnbp;
}
return NULL;
}
/* Read the peer's remote IP address from the Iuh conn's fd, and set up GTP-U counters for that remote address. */
static void hnb_persistent_update_remote_addr(struct hnb_persistent *hnbp)
{
socklen_t socklen;
struct osmo_sockaddr osa;
struct osmo_sockaddr_str remote_str;
int fd;
fd = osmo_stream_srv_get_fd(hnbp->ctx->conn);
if (fd < 0) {
LOG_HNBP(hnbp, LOGL_ERROR, "no active socket fd, cannot set up traffic counters\n");
return;
}
socklen = sizeof(struct osmo_sockaddr);
if (getpeername(fd, &osa.u.sa, &socklen)) {
LOG_HNBP(hnbp, LOGL_ERROR, "cannot read remote address, cannot set up traffic counters\n");
return;
}
if (osmo_sockaddr_str_from_osa(&remote_str, &osa)) {
LOG_HNBP(hnbp, LOGL_ERROR, "cannot parse remote address, cannot set up traffic counters\n");
return;
}
/* We got the remote address from the Iuh link (RUA), and now we are blatantly assuming that the hNodeB has its
* GTP endpoint on the same IP address, just with UDP port 2152 (the fixed GTP port as per 3GPP spec). */
remote_str.port = 2152;
if (nft_kpi_hnb_start(hnbp, &remote_str))
LOG_HNBP(hnbp, LOGL_ERROR, "failed to set up traffic counters\n");
}
/* Whenever HNBAP registers a HNB, hnbgw_hnbap.c calls this function to let the hnb_persistent update its state to the
* (new) remote address being active. When calling this function, a hnbp->ctx should be present, with an active
* osmo_stream_srv conn. */
void hnb_persistent_registered(struct hnb_persistent *hnbp)
{
if (!hnbp->ctx) {
LOG_HNBP(hnbp, LOGL_ERROR, "hnb_persistent_registered() invoked, but there is no hnb_ctx\n");
return;
}
/* The hNodeB is now connected, i.e. not disconnected. */
osmo_timer_del(&hnbp->disconnected_timeout);
/* start counting traffic */
if (g_hnbgw->nft_kpi.active)
hnb_persistent_update_remote_addr(hnbp);
}
/* Whenever a HNB is regarded as no longer registered (HNBAP HNB De-Register, or the Iuh link drops), this function is
* called to to let the hnb_persistent update its state to the hNodeB being disconnected. Clear the ctx->persistent and
* hnbp->ctx relations; do not delete the hnb_persistent instance. */
void hnb_persistent_deregistered(struct hnb_persistent *hnbp)
{
/* clear out cross references of hnb_context and hnb_persistent */
if (hnbp->ctx) {
if (hnbp->ctx->persistent == hnbp)
hnbp->ctx->persistent = NULL;
hnbp->ctx = NULL;
}
/* stop counting traffic */
nft_kpi_hnb_stop(hnbp);
/* The hNodeB is now disconnected. Clear out hnb_persistent when the disconnected timeout has passed. */
hnb_persistent_disconnected_timeout_schedule(hnbp);
}
void hnb_persistent_free(struct hnb_persistent *hnbp)
{
/* FIXME: check if in use? */
osmo_timer_del(&hnbp->disconnected_timeout);
nft_kpi_hnb_stop(hnbp);
nft_kpi_hnb_persistent_remove(hnbp);
osmo_stat_item_group_free(hnbp->statg);
rate_ctr_group_free(hnbp->ctrs);
llist_del(&hnbp->list);
hash_del(&hnbp->node_by_id);
talloc_free(hnbp);
}
/* return the amount of time the HNB is up (hnbp->ctx != NULL) or down (hnbp->ctx == NULL) */
unsigned long long hnbp_get_updowntime(const struct hnb_persistent *hnbp)
{
struct timespec tp;
if (!hnbp->updowntime)
return 0;
if (osmo_clock_gettime(CLOCK_MONOTONIC, &tp) != 0)
return 0;
return difftime(tp.tv_sec, hnbp->updowntime);
}

View File

@@ -1,7 +1,7 @@
/* main application for hnb-gw part of osmo-iuh */
/* kitchen sink for OsmoHNBGW implementation */
/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
/* (C) 2015,2024 by Harald Welte <laforge@gnumonks.org>
* (C) 2016-2025 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
@@ -19,339 +19,138 @@
*
*/
#include "config.h"
#include <inttypes.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
#include <osmocom/core/application.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/ctrl/control_if.h>
#include <osmocom/ctrl/control_cmd.h>
#include <osmocom/ctrl/control_vty.h>
#include <osmocom/ctrl/ports.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/ports.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/stat_item.h>
#include <osmocom/core/jhash.h>
#include <osmocom/vty/vty.h>
#include <osmocom/gsm/gsm23236.h>
#include <osmocom/netif/stream.h>
#include <osmocom/ranap/ranap_common.h>
#include <osmocom/sigtran/protocol/m3ua.h>
#include <osmocom/sigtran/sccp_sap.h>
#include "config.h"
#if ENABLE_PFCP
#include <osmocom/pfcp/pfcp_proto.h>
#endif
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnb_persistent.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_hnbap.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/mgw_fsm.h>
#include <osmocom/hnbgw/tdefs.h>
static const char * const osmo_hnbgw_copyright =
"OsmoHNBGW - Osmocom Home Node B Gateway implementation\r\n"
"Copyright (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>\r\n"
"Contributions by Daniel Willmann, Harald Welte, Neels Hofmeyr\r\n"
"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
"This is free software: you are free to change and redistribute it.\r\n"
"There is NO WARRANTY, to the extent permitted by law.\r\n";
struct hnbgw *g_hnbgw = NULL;
static void *tall_hnb_ctx;
const struct value_string ranap_domain_names[] = {
{ DOMAIN_CS, "CS" },
{ DOMAIN_PS, "PS" },
{}
};
static struct hnb_gw *g_hnb_gw;
static struct hnb_gw *hnb_gw_create(void *ctx)
/* timer call-back: Update the HNB_STAT_UPTIME_SECONDS stat item of each hnb_persistent */
static void hnbgw_store_hnb_uptime(void *data)
{
struct hnb_gw *gw = talloc_zero(ctx, struct hnb_gw);
struct hnb_persistent *hnbp;
/* strdup so we can easily talloc_free in the VTY code */
gw->config.iuh_local_ip = talloc_strdup(gw, HNBGW_LOCAL_IP_DEFAULT);
gw->config.iuh_local_port = IUH_DEFAULT_SCTP_PORT;
gw->config.log_prefix_hnb_id = true;
llist_for_each_entry(hnbp, &g_hnbgw->hnb_persistent_list, list) {
HNBP_STAT_SET(hnbp, HNB_STAT_UPTIME_SECONDS, hnbp->ctx != NULL ? hnbp_get_updowntime(hnbp) : 0);
}
gw->next_ue_ctx_id = 23;
INIT_LLIST_HEAD(&gw->hnb_list);
INIT_LLIST_HEAD(&gw->ue_list);
context_map_init(gw);
return gw;
osmo_timer_schedule(&g_hnbgw->store_uptime_timer, STORE_UPTIME_INTERVAL, 0);
}
struct hnb_context *hnb_context_by_id(struct hnb_gw *gw, uint32_t cid)
static void hnbgw_store_hnb_rab_durations(void *data)
{
struct hnb_context *hnb;
llist_for_each_entry(hnb, &gw->hnb_list, list) {
if (hnb->id.cid == cid)
return hnb;
llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) {
if (!hnb->persistent)
continue;
hnb_store_rab_durations(hnb);
}
return NULL;
/* Keep this timer ticking */
osmo_timer_schedule(&g_hnbgw->hnb_store_rab_durations_timer, HNB_STORE_RAB_DURATIONS_INTERVAL, 0);
}
struct hnb_context *hnb_context_by_identity_info(struct hnb_gw *gw, const char *identity_info)
/***********************************************************************
* UE Context
***********************************************************************/
uint32_t get_next_ue_ctx_id(void)
{
struct hnb_context *hnb;
llist_for_each_entry(hnb, &gw->hnb_list, list) {
if (strcmp(identity_info, hnb->identity_info) == 0)
return hnb;
}
return NULL;
return g_hnbgw->next_ue_ctx_id++;
}
unsigned hnb_contexts(const struct hnb_gw *gw)
int hnbgw_mgw_setup(void)
{
unsigned num_ctx = 0;
struct hnb_context *hnb;
struct mgcp_client *mgcp_client_single;
unsigned int pool_members_initalized;
llist_for_each_entry(hnb, &gw->hnb_list, list) {
num_ctx++;
}
return num_ctx;
}
struct ue_context *ue_context_by_id(struct hnb_gw *gw, uint32_t id)
{
struct ue_context *ue;
llist_for_each_entry(ue, &gw->ue_list, list) {
if (ue->context_id == id)
return ue;
}
return NULL;
}
struct ue_context *ue_context_by_imsi(struct hnb_gw *gw, const char *imsi)
{
struct ue_context *ue;
llist_for_each_entry(ue, &gw->ue_list, list) {
if (!strcmp(ue->imsi, imsi))
return ue;
}
return NULL;
}
struct ue_context *ue_context_by_tmsi(struct hnb_gw *gw, uint32_t tmsi)
{
struct ue_context *ue;
llist_for_each_entry(ue, &gw->ue_list, list) {
if (ue->tmsi == tmsi)
return ue;
}
return NULL;
}
void ue_context_free_by_hnb(struct hnb_gw *gw, const struct hnb_context *hnb)
{
struct ue_context *ue, *tmp;
llist_for_each_entry_safe(ue, tmp, &gw->ue_list, list) {
if (ue->hnb == hnb)
ue_context_free(ue);
}
}
static uint32_t get_next_ue_ctx_id(struct hnb_gw *gw)
{
uint32_t id;
do {
id = gw->next_ue_ctx_id++;
} while (ue_context_by_id(gw, id));
return id;
}
struct ue_context *ue_context_alloc(struct hnb_context *hnb, const char *imsi,
uint32_t tmsi)
{
struct ue_context *ue;
ue = talloc_zero(tall_hnb_ctx, struct ue_context);
if (!ue)
return NULL;
ue->hnb = hnb;
if (imsi)
OSMO_STRLCPY_ARRAY(ue->imsi, imsi);
else
ue->imsi[0] = '\0';
ue->tmsi = tmsi;
ue->context_id = get_next_ue_ctx_id(hnb->gw);
llist_add_tail(&ue->list, &hnb->gw->ue_list);
LOGP(DHNBAP, LOGL_INFO, "created UE context: id 0x%x, imsi %s, tmsi 0x%x\n",
ue->context_id, imsi? imsi : "-", tmsi);
return ue;
}
void ue_context_free(struct ue_context *ue)
{
llist_del(&ue->list);
talloc_free(ue);
}
static int hnb_read_cb(struct osmo_stream_srv *conn)
{
struct hnb_context *hnb = osmo_stream_srv_get_data(conn);
struct msgb *msg = msgb_alloc(IUH_MSGB_SIZE, "Iuh rx");
int rc;
if (!msg)
return -ENOMEM;
/* we store a reference to the HomeNodeB in the msg->dest for the
* benefit of varoius downstream processing functions */
msg->dst = hnb;
rc = osmo_stream_srv_recv(conn, msg);
if (rc == -EAGAIN) {
/* Notification received */
msgb_free(msg);
/* Initialize MGW pool. This initalizes and connects all MGCP clients that are currently configured in
* the pool. Adding additional MGCP clients to the pool is possible but the user has to configure and
* (re)connect them manually from the VTY. */
if (!mgcp_client_pool_empty(g_hnbgw->mgw_pool)) {
pool_members_initalized = mgcp_client_pool_connect(g_hnbgw->mgw_pool);
if (!pool_members_initalized) {
LOGP(DMGW, LOGL_ERROR, "MGW pool failed to initialize any pool members\n");
return -EINVAL;
}
LOGP(DMGW, LOGL_NOTICE,
"MGW pool with %u pool members configured, (ignoring MGW configuration in VTY node 'mgcp').\n",
pool_members_initalized);
return 0;
} else if (rc < 0) {
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Error during sctp_recvmsg()\n");
/* FIXME: clean up after disappeared HNB */
hnb_context_release(hnb);
goto out;
} else if (rc == 0) {
hnb_context_release(hnb);
rc = -1;
goto out;
} else {
msgb_put(msg, rc);
}
switch (msgb_sctp_ppid(msg)) {
case IUH_PPI_HNBAP:
hnb->hnbap_stream = msgb_sctp_stream(msg);
rc = hnbgw_hnbap_rx(hnb, msg);
break;
case IUH_PPI_RUA:
hnb->rua_stream = msgb_sctp_stream(msg);
rc = hnbgw_rua_rx(hnb, msg);
break;
case IUH_PPI_SABP:
case IUH_PPI_RNA:
case IUH_PPI_PUA:
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unimplemented SCTP PPID=%lu received\n", msgb_sctp_ppid(msg));
rc = 0;
break;
default:
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unknown SCTP PPID=%lu received\n", msgb_sctp_ppid(msg));
rc = 0;
break;
/* Initialize and connect a single MGCP client. This MGCP client will appear as the one and only pool
* member if there is no MGW pool configured. */
LOGP(DMGW, LOGL_NOTICE, "No MGW pool configured, using MGW configuration in VTY node 'mgcp'\n");
mgcp_client_single = mgcp_client_init(g_hnbgw, g_hnbgw->config.mgcp_client);
if (!mgcp_client_single) {
LOGP(DMGW, LOGL_ERROR, "MGW (single) client initalization failed\n");
return -EINVAL;
}
out:
msgb_free(msg);
return rc;
}
struct hnb_context *hnb_context_alloc(struct hnb_gw *gw, struct osmo_stream_srv_link *link, int new_fd)
{
struct hnb_context *ctx;
ctx = talloc_zero(tall_hnb_ctx, struct hnb_context);
if (!ctx)
return NULL;
INIT_LLIST_HEAD(&ctx->map_list);
ctx->gw = gw;
ctx->conn = osmo_stream_srv_create(tall_hnb_ctx, link, new_fd, hnb_read_cb, NULL, ctx);
if (!ctx->conn) {
LOGP(DMAIN, LOGL_INFO, "error while creating connection\n");
talloc_free(ctx);
return NULL;
if (mgcp_client_connect(mgcp_client_single)) {
LOGP(DMGW, LOGL_ERROR, "MGW (single) connect failed at (%s:%u)\n",
g_hnbgw->config.mgcp_client->remote_addr,
g_hnbgw->config.mgcp_client->remote_port);
return -EINVAL;
}
llist_add_tail(&ctx->list, &gw->hnb_list);
return ctx;
}
static const char *umts_cell_id_name(const struct umts_cell_id *ucid)
{
static __thread char buf[40];
snprintf(buf, sizeof(buf), "%u-%u-L%u-R%u-S%u", ucid->mcc, ucid->mnc, ucid->lac, ucid->rac, ucid->sac);
return buf;
}
const char *hnb_context_name(struct hnb_context *ctx)
{
if (!ctx)
return "NULL";
if (ctx->gw->config.log_prefix_hnb_id)
return ctx->identity_info;
else
return umts_cell_id_name(&ctx->id);
}
void hnb_context_release(struct hnb_context *ctx)
{
struct hnbgw_context_map *map, *map2;
/* remove from the list of HNB contexts */
llist_del(&ctx->list);
/* deactivate all context maps */
llist_for_each_entry_safe(map, map2, &ctx->map_list, hnb_list) {
/* remove it from list, as HNB context will soon be
* gone. Let's hope the second osmo_llist_del in the
* map garbage collector works fine? */
llist_del(&map->hnb_list);
llist_del(&map->cn_list);
context_map_deactivate(map);
}
ue_context_free_by_hnb(ctx->gw, ctx);
osmo_stream_srv_destroy(ctx->conn);
talloc_free(ctx);
}
/*! call-back when the listen FD has something to read */
static int accept_cb(struct osmo_stream_srv_link *srv, int fd)
{
struct hnb_gw *gw = osmo_stream_srv_link_get_data(srv);
struct hnb_context *ctx;
ctx = hnb_context_alloc(gw, srv, fd);
if (!ctx)
return -ENOMEM;
mgcp_client_pool_register_single(g_hnbgw->mgw_pool, mgcp_client_single);
return 0;
}
static const struct log_info_cat log_cat[] = {
struct msgb *hnbgw_ranap_msg_alloc(const char *name)
{
struct msgb *ranap_msg;
ranap_msg = msgb_alloc_c(OTC_SELECT, sizeof(struct osmo_scu_prim) + 1500, name);
msgb_reserve(ranap_msg, sizeof(struct osmo_scu_prim));
ranap_msg->l2h = ranap_msg->data;
return ranap_msg;
}
#define HNBGW_COPYRIGHT \
"OsmoHNBGW - Osmocom Home Node B Gateway implementation\r\n" \
"Copyright (C) 2016-2024 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>\r\n" \
"Contributions by Daniel Willmann, Harald Welte, Neels Hofmeyr\r\n" \
"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n" \
"This is free software: you are free to change and redistribute it.\r\n" \
"There is NO WARRANTY, to the extent permitted by law.\r\n"
static const struct log_info_cat hnbgw_log_cat[] = {
[DMAIN] = {
.name = "DMAIN", .loglevel = LOGL_NOTICE, .enabled = 1,
.color = "",
@@ -372,329 +171,76 @@ static const struct log_info_cat log_cat[] = {
.color = "",
.description = "RAN Application Part",
},
[DMGW] = {
.name = "DMGW", .loglevel = LOGL_NOTICE, .enabled = 1,
.color = "\033[1;33m",
.description = "Media Gateway",
},
[DHNB] = {
.name = "DHNB", .loglevel = LOGL_NOTICE, .enabled = 1,
.color = OSMO_LOGCOLOR_CYAN,
.description = "HNB side (via RUA)",
},
[DCN] = {
.name = "DCN", .loglevel = LOGL_NOTICE, .enabled = 1,
.color = OSMO_LOGCOLOR_DARKYELLOW,
.description = "Core Network side (via SCCP)",
},
[DNFT] = {
.name = "DNFT", .loglevel = LOGL_NOTICE, .enabled = 1,
.color = OSMO_LOGCOLOR_BLUE,
.description = "nftables interaction for retrieving stats",
},
};
static const struct log_info hnbgw_log_info = {
.cat = log_cat,
.num_cat = ARRAY_SIZE(log_cat),
const struct log_info hnbgw_log_info = {
.cat = hnbgw_log_cat,
.num_cat = ARRAY_SIZE(hnbgw_log_cat),
};
static struct vty_app_info vty_info = {
.name = "OsmoHNBGW",
.version = PACKAGE_VERSION,
.go_parent_cb = hnbgw_vty_go_parent,
struct vty_app_info hnbgw_vty_info = {
.name = "OsmoHNBGW",
.version = PACKAGE_VERSION,
.go_parent_cb = hnbgw_vty_go_parent,
.copyright = HNBGW_COPYRIGHT,
};
static struct {
int daemonize;
const char *config_file;
bool log_disable_color;
bool log_enable_timestamp;
int log_level;
const char *log_category_mask;
} hnbgw_cmdline_config = {
0,
"osmo-hnbgw.cfg",
false,
false,
0,
NULL,
};
static void print_usage()
void g_hnbgw_alloc(void *ctx)
{
printf("Usage: osmo-hnbgw\n");
}
static void print_help()
{
printf(" -h --help This text.\n");
printf(" -d option --debug=DHNBAP:DRUA:DRANAP:DMAIN Enable debugging.\n");
printf(" -D --daemonize Fork the process into a background daemon.\n");
printf(" -c --config-file filename The config file to use.\n");
printf(" -s --disable-color\n");
printf(" -T --timestamp Prefix every log line with a timestamp.\n");
printf(" -V --version Print the version of OsmoHNBGW.\n");
printf(" -e --log-level number Set a global loglevel.\n");
printf("\nVTY reference generation:\n");
printf(" --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n");
printf(" --vty-ref-xml Generate the VTY reference XML output and exit.\n");
}
static void handle_long_options(const char *prog_name, const int long_option)
{
static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT;
switch (long_option) {
case 1:
vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg);
if (vty_ref_mode < 0) {
fprintf(stderr, "%s: Unknown VTY reference generation "
"mode '%s'\n", prog_name, optarg);
exit(2);
}
break;
case 2:
fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
get_value_string(vty_ref_gen_mode_names, vty_ref_mode),
get_value_string(vty_ref_gen_mode_desc, vty_ref_mode));
vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode);
exit(0);
default:
fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
exit(2);
}
}
static void handle_options(int argc, char **argv)
{
while (1) {
int option_index = 0, c;
static int long_option = 0;
static struct option long_options[] = {
{"help", 0, 0, 'h'},
{"debug", 1, 0, 'd'},
{"daemonize", 0, 0, 'D'},
{"config-file", 1, 0, 'c'},
{"disable-color", 0, 0, 's'},
{"timestamp", 0, 0, 'T'},
{"version", 0, 0, 'V' },
{"log-level", 1, 0, 'e'},
{"vty-ref-mode", 1, &long_option, 1},
{"vty-ref-xml", 0, &long_option, 2},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, "hd:Dc:sTVe:",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 0:
handle_long_options(argv[0], long_option);
break;
case 'h':
print_usage();
print_help();
exit(0);
case 's':
hnbgw_cmdline_config.log_disable_color = true;
break;
case 'd':
hnbgw_cmdline_config.log_category_mask = optarg;
break;
case 'D':
hnbgw_cmdline_config.daemonize = 1;
break;
case 'c':
hnbgw_cmdline_config.config_file = optarg;
break;
case 'T':
hnbgw_cmdline_config.log_enable_timestamp = true;
break;
case 'e':
hnbgw_cmdline_config.log_level = atoi(optarg);
break;
case 'V':
print_version(1);
exit(0);
break;
default:
/* catch unknown options *as well as* missing arguments. */
fprintf(stderr, "Error in command line options. Exiting.\n");
exit(-1);
break;
}
}
if (argc > optind) {
fprintf(stderr, "Unsupported positional arguments on command line\n");
exit(2);
}
}
CTRL_CMD_DEFINE_RO(hnb_info, "info");
static int get_hnb_info(struct ctrl_cmd *cmd, void *data)
{
struct hnb_context *hnb = data;
cmd->reply = talloc_strdup(cmd, hnb->identity_info);
return CTRL_CMD_REPLY;
}
CTRL_CMD_DEFINE_RO(hnbs, "num-hnb");
static int get_hnbs(struct ctrl_cmd *cmd, void *data)
{
cmd->reply = talloc_asprintf(cmd, "%u", hnb_contexts(data));
return CTRL_CMD_REPLY;
}
int hnb_ctrl_cmds_install()
{
int rc = 0;
rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_hnbs);
rc |= ctrl_cmd_install(CTRL_NODE_HNB, &cmd_hnb_info);
return rc;
}
static int hnb_ctrl_node_lookup(void *data, vector vline, int *node_type, void **node_data, int *i)
{
const char *token = vector_slot(vline, *i);
struct hnb_context *hnb;
long num;
switch (*node_type) {
case CTRL_NODE_ROOT:
if (strcmp(token, "hnb") != 0)
return 0;
(*i)++;
if (!ctrl_parse_get_num(vline, *i, &num))
return -ERANGE;
hnb = hnb_context_by_id(data, num);
if (!hnb)
return -ENODEV;
*node_data = hnb;
*node_type = CTRL_NODE_HNB;
break;
default:
return 0;
}
return 1;
}
int main(int argc, char **argv)
{
struct osmo_stream_srv_link *srv;
int rc;
tall_hnb_ctx = talloc_named_const(NULL, 0, "hnb_context");
talloc_asn1_ctx = talloc_named_const(NULL, 1, "asn1_context");
msgb_talloc_ctx_init(tall_hnb_ctx, 0);
g_hnb_gw = hnb_gw_create(tall_hnb_ctx);
g_hnb_gw->config.rnc_id = 23;
rc = osmo_init_logging2(tall_hnb_ctx, &hnbgw_log_info);
if (rc < 0)
exit(1);
rc = osmo_ss7_init();
if (rc < 0) {
LOGP(DMAIN, LOGL_FATAL, "osmo_ss7_init() failed with rc=%d\n", rc);
exit(1);
}
vty_info.copyright = osmo_hnbgw_copyright;
vty_init(&vty_info);
osmo_ss7_vty_init_asp(tall_hnb_ctx);
osmo_sccp_vty_init();
hnbgw_vty_init(g_hnb_gw, tall_hnb_ctx);
ctrl_vty_init(tall_hnb_ctx);
logging_vty_add_cmds();
/* Handle options after vty_init(), for --version */
handle_options(argc, argv);
rc = vty_read_config_file(hnbgw_cmdline_config.config_file, NULL);
if (rc < 0) {
LOGP(DMAIN, LOGL_FATAL, "Failed to parse the config file: '%s'\n",
hnbgw_cmdline_config.config_file);
return 1;
}
/*
* cmdline options take precedence over config file, but if no options
* were passed we must not override the config file.
*/
if (hnbgw_cmdline_config.log_disable_color)
log_set_use_color(osmo_stderr_target, 0);
if (hnbgw_cmdline_config.log_category_mask)
log_parse_category_mask(osmo_stderr_target,
hnbgw_cmdline_config.log_category_mask);
if (hnbgw_cmdline_config.log_enable_timestamp)
log_set_print_timestamp(osmo_stderr_target, 1);
if (hnbgw_cmdline_config.log_level)
log_set_log_level(osmo_stderr_target,
hnbgw_cmdline_config.log_level);
rc = telnet_init_dynif(tall_hnb_ctx, g_hnb_gw, vty_get_bind_addr(), OSMO_VTY_PORT_HNBGW);
if (rc < 0) {
perror("Error binding VTY port");
exit(1);
}
g_hnb_gw->ctrl = ctrl_interface_setup_dynip2(g_hnb_gw, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_HNBGW,
hnb_ctrl_node_lookup, _LAST_CTRL_NODE_HNB);
if (!g_hnb_gw->ctrl) {
LOGP(DMAIN, LOGL_ERROR, "Failed to create CTRL interface on %s:%u\n",
ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_HNBGW);
exit(1);
} else {
rc = hnb_ctrl_cmds_install();
if (rc) {
LOGP(DMAIN, LOGL_ERROR, "Failed to install CTRL interface commands\n");
return 2;
}
}
ranap_set_log_area(DRANAP);
rc = hnbgw_cnlink_init(g_hnb_gw, "localhost", M3UA_PORT, "localhost");
if (rc < 0) {
LOGP(DMAIN, LOGL_ERROR, "Failed to initialize SCCP link to CN\n");
exit(1);
}
LOGP(DHNBAP, LOGL_NOTICE, "Using RNC-Id %u\n", g_hnb_gw->config.rnc_id);
OSMO_ASSERT(g_hnb_gw->config.iuh_local_ip);
LOGP(DMAIN, LOGL_NOTICE, "Listening for Iuh at %s %d\n",
g_hnb_gw->config.iuh_local_ip,
g_hnb_gw->config.iuh_local_port);
srv = osmo_stream_srv_link_create(tall_hnb_ctx);
if (!srv) {
perror("cannot create server");
exit(1);
}
osmo_stream_srv_link_set_data(srv, g_hnb_gw);
osmo_stream_srv_link_set_proto(srv, IPPROTO_SCTP);
osmo_stream_srv_link_set_nodelay(srv, true);
osmo_stream_srv_link_set_addr(srv, g_hnb_gw->config.iuh_local_ip);
osmo_stream_srv_link_set_port(srv, g_hnb_gw->config.iuh_local_port);
osmo_stream_srv_link_set_accept_cb(srv, accept_cb);
if (osmo_stream_srv_link_open(srv) < 0) {
perror("Cannot open server");
exit(1);
}
g_hnb_gw->iuh = srv;
if (hnbgw_cmdline_config.daemonize) {
rc = osmo_daemonize();
if (rc < 0) {
perror("Error during daemonize");
exit(1);
}
}
while (1) {
rc = osmo_select_main(0);
if (rc < 0)
exit(3);
}
/* not reached */
exit(0);
OSMO_ASSERT(!g_hnbgw);
g_hnbgw = talloc_zero(ctx, struct hnbgw);
/* strdup so we can easily talloc_free in the VTY code */
g_hnbgw->config.iuh_local_ip = talloc_strdup(g_hnbgw, HNBGW_LOCAL_IP_DEFAULT);
g_hnbgw->config.iuh_local_port = IUH_DEFAULT_SCTP_PORT;
g_hnbgw->config.hnbap_allow_tmsi = true;
g_hnbgw->config.log_prefix_hnb_id = true;
g_hnbgw->config.accept_all_hnb = true;
/* Set zero PLMN to detect a missing PLMN when transmitting RESET */
g_hnbgw->config.plmn = (struct osmo_plmn_id){ 0, 0, false };
g_hnbgw->next_ue_ctx_id = 23;
INIT_LLIST_HEAD(&g_hnbgw->hnb_list);
INIT_LLIST_HEAD(&g_hnbgw->hnb_persistent_list);
hash_init(g_hnbgw->hnb_persistent_by_id);
INIT_LLIST_HEAD(&g_hnbgw->sccp.users);
g_hnbgw->mgw_pool = mgcp_client_pool_alloc(g_hnbgw);
g_hnbgw->config.mgcp_client = mgcp_client_conf_alloc(g_hnbgw);
#if ENABLE_PFCP
g_hnbgw->config.pfcp.remote_port = OSMO_PFCP_PORT;
#endif
g_hnbgw->sccp.cnpool_iucs = hnbgw_cnpool_alloc(DOMAIN_CS);
g_hnbgw->sccp.cnpool_iups = hnbgw_cnpool_alloc(DOMAIN_PS);
osmo_timer_setup(&g_hnbgw->store_uptime_timer, hnbgw_store_hnb_uptime, g_hnbgw);
osmo_timer_schedule(&g_hnbgw->store_uptime_timer, STORE_UPTIME_INTERVAL, 0);
osmo_timer_setup(&g_hnbgw->hnb_store_rab_durations_timer, hnbgw_store_hnb_rab_durations, g_hnbgw);
osmo_timer_schedule(&g_hnbgw->hnb_store_rab_durations_timer, HNB_STORE_RAB_DURATIONS_INTERVAL, 0);
}

View File

@@ -18,542 +18,313 @@
*
*/
#include "config.h"
#include <arpa/inet.h>
#include <errno.h>
#include <asn1c/asn1helpers.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/stats.h>
#include <osmocom/gsm/gsm23236.h>
#include <osmocom/sigtran/protocol/m3ua.h>
#include <osmocom/sigtran/protocol/sua.h>
#include <osmocom/sigtran/sccp_sap.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/ranap/ranap_msg_factory.h>
#include <osmocom/hnbgw/hnbgw_sccp.h>
#include <osmocom/hnbgw/hnbgw_ranap.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h>
/***********************************************************************
* Outbound RANAP RESET to CN
***********************************************************************/
void hnbgw_cnlink_change_state(struct hnbgw_cnlink *cnlink, enum hnbgw_cnlink_state state);
static int transmit_rst(struct hnb_gw *gw, RANAP_CN_DomainIndicator_t domain,
struct osmo_sccp_addr *remote_addr)
void hnbgw_cnpool_apply_cfg(struct hnbgw_cnpool *cnpool)
{
struct msgb *msg;
RANAP_Cause_t cause = {
.present = RANAP_Cause_PR_transmissionNetwork,
.choice. transmissionNetwork = RANAP_CauseTransmissionNetwork_signalling_transport_resource_failure,
};
struct osmo_nri_range *r;
LOGP(DRANAP, LOGL_NOTICE, "Tx RESET to %s %s\n",
domain == RANAP_CN_DomainIndicator_cs_domain ? "IuCS" : "IuPS",
osmo_sccp_inst_addr_name(gw->sccp.cnlink->sccp, remote_addr));
cnpool->use.nri_bitlen = cnpool->vty.nri_bitlen;
msg = ranap_new_msg_reset(domain, &cause);
return osmo_sccp_tx_unitdata_msg(gw->sccp.cnlink->sccp_user,
&gw->sccp.local_addr,
remote_addr,
msg);
osmo_nri_ranges_free(cnpool->use.null_nri_ranges);
cnpool->use.null_nri_ranges = osmo_nri_ranges_alloc(cnpool);
llist_for_each_entry(r, &cnpool->vty.null_nri_ranges->entries, entry)
osmo_nri_ranges_add(cnpool->use.null_nri_ranges, r);
}
static int transmit_reset_ack(struct hnb_gw *gw, RANAP_CN_DomainIndicator_t domain,
const struct osmo_sccp_addr *remote_addr)
void hnbgw_cnpool_cnlinks_start_or_restart(struct hnbgw_cnpool *cnpool)
{
struct msgb *msg;
LOGP(DRANAP, LOGL_NOTICE, "Tx RESET ACK to %s %s\n",
domain == RANAP_CN_DomainIndicator_cs_domain ? "IuCS" : "IuPS",
osmo_sccp_inst_addr_name(gw->sccp.cnlink->sccp, remote_addr));
msg = ranap_new_msg_reset_ack(domain, NULL);
return osmo_sccp_tx_unitdata_msg(gw->sccp.cnlink->sccp_user,
&gw->sccp.local_addr,
remote_addr,
msg);
}
/* Timer callback once T_RafC expires */
static void cnlink_trafc_cb(void *data)
{
struct hnb_gw *gw = data;
transmit_rst(gw, RANAP_CN_DomainIndicator_cs_domain, &gw->sccp.iucs_remote_addr);
transmit_rst(gw, RANAP_CN_DomainIndicator_ps_domain, &gw->sccp.iups_remote_addr);
hnbgw_cnlink_change_state(gw->sccp.cnlink, CNLINK_S_EST_RST_TX_WAIT_ACK);
/* The spec states that we should abandon after a configurable
* number of times. We decide to simply continue trying */
}
/* change the state of a CN Link */
void hnbgw_cnlink_change_state(struct hnbgw_cnlink *cnlink, enum hnbgw_cnlink_state state)
{
switch (state) {
case CNLINK_S_NULL:
case CNLINK_S_EST_PEND:
break;
case CNLINK_S_EST_CONF:
cnlink_trafc_cb(cnlink->gw);
break;
case CNLINK_S_EST_RST_TX_WAIT_ACK:
osmo_timer_schedule(&cnlink->T_RafC, 5, 0);
break;
case CNLINK_S_EST_ACTIVE:
osmo_timer_del(&cnlink->T_RafC);
break;
}
}
/***********************************************************************
* Incoming primitives from SCCP User SAP
***********************************************************************/
static int cn_ranap_rx_reset_cmd(struct hnbgw_cnlink *cnlink,
const struct osmo_scu_unitdata_param *unitdata,
RANAP_InitiatingMessage_t *imsg)
{
RANAP_CN_DomainIndicator_t domain;
RANAP_ResetIEs_t ies;
int rc;
rc = ranap_decode_reseties(&ies, &imsg->value);
domain = ies.cN_DomainIndicator;
ranap_free_reseties(&ies);
LOGP(DRANAP, LOGL_NOTICE, "Rx RESET from %s %s, returning ACK\n",
domain == RANAP_CN_DomainIndicator_cs_domain ? "IuCS" : "IuPS",
osmo_sccp_inst_addr_name(cnlink->sccp, &unitdata->calling_addr));
/* FIXME: actually reset connections, if any */
if (transmit_reset_ack(cnlink->gw, domain, &unitdata->calling_addr))
LOGP(DRANAP, LOGL_ERROR, "Error: cannot send RESET ACK to %s %s\n",
domain == RANAP_CN_DomainIndicator_cs_domain ? "IuCS" : "IuPS",
osmo_sccp_inst_addr_name(cnlink->sccp, &unitdata->calling_addr));
return rc;
}
static int cn_ranap_rx_reset_ack(struct hnbgw_cnlink *cnlink,
RANAP_SuccessfulOutcome_t *omsg)
{
RANAP_ResetAcknowledgeIEs_t ies;
int rc;
rc = ranap_decode_resetacknowledgeies(&ies, &omsg->value);
hnbgw_cnlink_change_state(cnlink, CNLINK_S_EST_ACTIVE);
ranap_free_resetacknowledgeies(&ies);
return rc;
}
static int cn_ranap_rx_paging_cmd(struct hnbgw_cnlink *cnlink,
RANAP_InitiatingMessage_t *imsg,
const uint8_t *data, unsigned int len)
{
struct hnb_gw *gw = cnlink->gw;
struct hnb_context *hnb;
RANAP_PagingIEs_t ies;
int rc;
rc = ranap_decode_pagingies(&ies, &imsg->value);
if (rc < 0)
return rc;
/* FIXME: determine which HNBs to send this Paging command,
* rather than broadcasting to all HNBs */
llist_for_each_entry(hnb, &gw->hnb_list, list) {
rc = rua_tx_udt(hnb, data, len);
}
ranap_free_pagingies(&ies);
return 0;
}
static int cn_ranap_rx_initiating_msg(struct hnbgw_cnlink *cnlink,
const struct osmo_scu_unitdata_param *unitdata,
RANAP_InitiatingMessage_t *imsg,
const uint8_t *data, unsigned int len)
{
switch (imsg->procedureCode) {
case RANAP_ProcedureCode_id_Reset:
return cn_ranap_rx_reset_cmd(cnlink, unitdata, imsg);
case RANAP_ProcedureCode_id_Paging:
return cn_ranap_rx_paging_cmd(cnlink, imsg, data, len);
case RANAP_ProcedureCode_id_OverloadControl: /* Overload ind */
break;
case RANAP_ProcedureCode_id_ErrorIndication: /* Error ind */
break;
case RANAP_ProcedureCode_id_ResetResource: /* request */
case RANAP_ProcedureCode_id_InformationTransfer:
case RANAP_ProcedureCode_id_DirectInformationTransfer:
case RANAP_ProcedureCode_id_UplinkInformationExchange:
LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP "
"Procedure %ld from CN, ignoring\n", imsg->procedureCode);
break;
default:
LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP "
"Procedure %ld from CN, ignoring\n", imsg->procedureCode);
break;
}
return 0;
}
static int cn_ranap_rx_successful_msg(struct hnbgw_cnlink *cnlink,
RANAP_SuccessfulOutcome_t *omsg)
{
switch (omsg->procedureCode) {
case RANAP_ProcedureCode_id_Reset: /* Reset acknowledge */
return cn_ranap_rx_reset_ack(cnlink, omsg);
case RANAP_ProcedureCode_id_ResetResource: /* response */
case RANAP_ProcedureCode_id_InformationTransfer:
case RANAP_ProcedureCode_id_DirectInformationTransfer:
case RANAP_ProcedureCode_id_UplinkInformationExchange:
LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP "
"Procedure %ld from CN, ignoring\n", omsg->procedureCode);
break;
default:
LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP "
"Procedure %ld from CN, ignoring\n", omsg->procedureCode);
break;
}
return 0;
}
static int _cn_ranap_rx(struct hnbgw_cnlink *cnlink,
const struct osmo_scu_unitdata_param *unitdata,
RANAP_RANAP_PDU_t *pdu, const uint8_t *data, unsigned int len)
{
int rc;
switch (pdu->present) {
case RANAP_RANAP_PDU_PR_initiatingMessage:
rc = cn_ranap_rx_initiating_msg(cnlink, unitdata, &pdu->choice.initiatingMessage,
data, len);
break;
case RANAP_RANAP_PDU_PR_successfulOutcome:
rc = cn_ranap_rx_successful_msg(cnlink, &pdu->choice.successfulOutcome);
break;
case RANAP_RANAP_PDU_PR_unsuccessfulOutcome:
LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP "
"unsuccessful outcome procedure %ld from CN, ignoring\n",
pdu->choice.unsuccessfulOutcome.procedureCode);
rc = -ENOTSUP;
break;
default:
LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP "
"presence %u from CN, ignoring\n", pdu->present);
rc = -EINVAL;
break;
}
return rc;
}
static int handle_cn_ranap(struct hnbgw_cnlink *cnlink, const struct osmo_scu_unitdata_param *unitdata,
const uint8_t *data, unsigned int len)
{
RANAP_RANAP_PDU_t _pdu, *pdu = &_pdu;
asn_dec_rval_t dec_ret;
int rc;
memset(pdu, 0, sizeof(*pdu));
dec_ret = aper_decode(NULL,&asn_DEF_RANAP_RANAP_PDU, (void **) &pdu,
data, len, 0, 0);
if (dec_ret.code != RC_OK) {
LOGP(DRANAP, LOGL_ERROR, "Error in RANAP ASN.1 decode\n");
return -1;
}
rc = _cn_ranap_rx(cnlink, unitdata, pdu, data, len);
return rc;
}
static bool pc_and_ssn_match(const struct osmo_sccp_addr *a, const struct osmo_sccp_addr *b)
{
return (a == b)
|| ((a->pc == b->pc)
&& (a->ssn == b->ssn));
}
static int classify_cn_remote_addr(const struct hnb_gw *gw,
const struct osmo_sccp_addr *cn_remote_addr,
bool *is_ps)
{
if (pc_and_ssn_match(cn_remote_addr, &gw->sccp.iucs_remote_addr)) {
if (is_ps)
*is_ps = false;
return 0;
}
if (pc_and_ssn_match(cn_remote_addr, &gw->sccp.iups_remote_addr)) {
if (is_ps)
*is_ps = true;
return 0;
}
LOGP(DMAIN, LOGL_ERROR, "Unexpected remote address, matches neither CS nor PS address: %s\n",
osmo_sccp_addr_dump(cn_remote_addr));
return -1;
}
static int handle_cn_unitdata(struct hnbgw_cnlink *cnlink,
const struct osmo_scu_unitdata_param *param,
struct osmo_prim_hdr *oph)
{
if (param->called_addr.ssn != OSMO_SCCP_SSN_RANAP) {
LOGP(DMAIN, LOGL_NOTICE, "N-UNITDATA.ind for unknown SSN %u\n",
param->called_addr.ssn);
return -1;
}
if (classify_cn_remote_addr(cnlink->gw, &param->calling_addr, NULL) < 0)
return -1;
return handle_cn_ranap(cnlink, param, msgb_l2(oph->msg), msgb_l2len(oph->msg));
}
static int handle_cn_conn_conf(struct hnbgw_cnlink *cnlink,
const struct osmo_scu_connect_param *param,
struct osmo_prim_hdr *oph)
{
/* we don't actually need to do anything, as RUA towards the HNB
* doesn't seem to know any confirmations to its CONNECT
* operation */
LOGP(DMAIN, LOGL_DEBUG, "handle_cn_conn_conf() conn_id=%d\n",
param->conn_id);
LOGP(DMAIN, LOGL_DEBUG, "handle_cn_conn_conf() called_addr=%s\n",
inet_ntoa(param->called_addr.ip.v4));
LOGP(DMAIN, LOGL_DEBUG, "handle_cn_conn_conf() calling_addr=%s\n",
inet_ntoa(param->calling_addr.ip.v4));
LOGP(DMAIN, LOGL_DEBUG, "handle_cn_conn_conf() responding_addr=%s\n",
inet_ntoa(param->responding_addr.ip.v4));
return 0;
}
static int handle_cn_data_ind(struct hnbgw_cnlink *cnlink,
const struct osmo_scu_data_param *param,
struct osmo_prim_hdr *oph)
{
struct hnbgw_context_map *map;
/* connection-oriented data is always passed transparently
* towards the specific HNB, via a RUA connection identified by
* conn_id */
map = context_map_by_cn(cnlink, param->conn_id);
if (!map) {
/* FIXME: Return an error / released primitive */
return 0;
}
return rua_tx_dt(map->hnb_ctx, map->is_ps, map->rua_ctx_id,
msgb_l2(oph->msg), msgb_l2len(oph->msg));
}
static int handle_cn_disc_ind(struct hnbgw_cnlink *cnlink,
const struct osmo_scu_disconn_param *param,
struct osmo_prim_hdr *oph)
{
struct hnbgw_context_map *map;
LOGP(DMAIN, LOGL_DEBUG, "handle_cn_disc_ind() conn_id=%d originator=%d\n",
param->conn_id, param->originator);
LOGP(DMAIN, LOGL_DEBUG, "handle_cn_disc_ind() responding_addr=%s\n",
inet_ntoa(param->responding_addr.ip.v4));
RUA_Cause_t rua_cause = {
.present = RUA_Cause_PR_NOTHING,
/* FIXME: Convert incoming SCCP cause to RUA cause */
};
/* we need to notify the HNB associated with this connection via
* a RUA DISCONNECT */
map = context_map_by_cn(cnlink, param->conn_id);
if (!map) {
/* FIXME: Return an error / released primitive */
return 0;
}
return rua_tx_disc(map->hnb_ctx, map->is_ps, map->rua_ctx_id,
&rua_cause, msgb_l2(oph->msg), msgb_l2len(oph->msg));
}
/* Entry point for primitives coming up from SCCP User SAP */
static int sccp_sap_up(struct osmo_prim_hdr *oph, void *ctx)
{
struct osmo_sccp_user *scu = ctx;
struct hnbgw_cnlink *cnlink;
struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph;
int rc = 0;
LOGP(DMAIN, LOGL_DEBUG, "sccp_sap_up(%s)\n", osmo_scu_prim_name(oph));
if (!scu) {
LOGP(DMAIN, LOGL_ERROR,
"sccp_sap_up(): NULL osmo_sccp_user, cannot send prim (sap %u prim %u op %d)\n",
oph->sap, oph->primitive, oph->operation);
return -1;
hnbgw_cnpool_apply_cfg(cnpool);
llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) {
hnbgw_cnlink_start_or_restart(cnlink);
}
cnlink = osmo_sccp_user_get_priv(scu);
if (!cnlink) {
LOGP(DMAIN, LOGL_ERROR,
"sccp_sap_up(): NULL hnbgw_cnlink, cannot send prim (sap %u prim %u op %d)\n",
oph->sap, oph->primitive, oph->operation);
return -1;
}
switch (OSMO_PRIM_HDR(oph)) {
case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION):
rc = handle_cn_unitdata(cnlink, &prim->u.unitdata, oph);
break;
case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM):
rc = handle_cn_conn_conf(cnlink, &prim->u.connect, oph);
break;
case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION):
rc = handle_cn_data_ind(cnlink, &prim->u.data, oph);
break;
case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION):
rc = handle_cn_disc_ind(cnlink, &prim->u.disconnect, oph);
break;
default:
LOGP(DMAIN, LOGL_ERROR,
"Received unknown prim %u from SCCP USER SAP\n",
OSMO_PRIM_HDR(oph));
break;
}
msgb_free(oph->msg);
return rc;
}
static bool addr_has_pc_and_ssn(const struct osmo_sccp_addr *addr)
void hnbgw_cnpool_start(struct hnbgw_cnpool *cnpool)
{
if (!(addr->presence & OSMO_SCCP_ADDR_T_SSN))
/* Legacy compat: when there is no 'msc N' at all in the config file, set up 'msc 0' with default values (or
* 'sgsn' depending on cnpool). */
if (llist_empty(&cnpool->cnlinks))
cnlink_get_nr(cnpool, 0, true);
hnbgw_cnpool_cnlinks_start_or_restart(cnpool);
}
struct hnbgw_cnlink *cnlink_get_nr(struct hnbgw_cnpool *cnpool, int nr, bool create_if_missing)
{
struct hnbgw_cnlink *cnlink;
llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) {
if (cnlink->nr == nr)
return cnlink;
}
if (!create_if_missing)
return NULL;
return hnbgw_cnlink_alloc(cnpool, nr);
}
static bool is_cnlink_usable(struct hnbgw_cnlink *cnlink, bool is_emerg)
{
if (is_emerg && !cnlink->allow_emerg)
return false;
if (!(addr->presence & OSMO_SCCP_ADDR_T_PC))
if (!cnlink->hnbgw_sccp_user || !cnlink->hnbgw_sccp_user->sccp_user)
return false;
if (!cnlink_is_conn_ready(cnlink))
return false;
return true;
}
static int resolve_addr_name(struct osmo_sccp_addr *dest, struct osmo_ss7_instance **ss7,
const char *addr_name, const char *label,
uint32_t default_pc)
/* Decide which MSC/SGSN to forward this Complete Layer 3 request to. The current Layer 3 Info is passed in map->l3.
* a) If the subscriber was previously paged from a particular CN link, that CN link shall receive the Paging Response.
* b) If the message contains an NRI indicating a particular CN link that is currently connected, that CN link shall
* handle this conn.
* c) All other cases distribute the messages across connected CN links in a round-robin fashion.
*/
struct hnbgw_cnlink *hnbgw_cnlink_select(struct hnbgw_context_map *map)
{
struct osmo_ss7_instance *ss7_tmp;
struct hnbgw_cnpool *cnpool = map->is_ps ? g_hnbgw->sccp.cnpool_iups : g_hnbgw->sccp.cnpool_iucs;
struct hnbgw_cnlink *cnlink;
struct hnbgw_cnlink *round_robin_next = NULL;
struct hnbgw_cnlink *round_robin_first = NULL;
unsigned int round_robin_next_nr;
int16_t nri_v = -1;
bool is_null_nri = false;
uint8_t nri_bitlen = cnpool->use.nri_bitlen;
if (!addr_name) {
osmo_sccp_make_addr_pc_ssn(dest, default_pc, OSMO_SCCP_SSN_RANAP);
LOGP(DMAIN, LOGL_INFO, "%s remote addr not configured, using default: %s\n", label,
osmo_sccp_addr_name(*ss7, dest));
return 0;
/* Match IMSI with previous Paging */
if (map->l3.gsm48_msg_type == GSM48_MT_RR_PAG_RESP) {
cnlink = cnlink_find_by_paging_mi(cnpool, &map->l3.mi);
if (cnlink) {
LOG_MAP(map, DCN, LOGL_INFO, "CN link paging response record selects %s %d\n", cnpool->peer_name,
cnlink->nr);
CNLINK_CTR_INC(cnlink, CNLINK_CTR_CNPOOL_SUBSCR_PAGED);
return cnlink;
}
LOG_MAP(map, DCN, LOGL_INFO, "CN link paging response didn't match any record on %s\n", cnpool->peer_name);
/* If there is no match, go on with other ways */
}
ss7_tmp = osmo_sccp_addr_by_name(dest, addr_name);
if (!ss7_tmp) {
LOGP(DMAIN, LOGL_ERROR, "%s remote addr: no such SCCP address book entry: '%s'\n",
label, addr_name);
return -1;
#define LOG_NRI(LOGLEVEL, FORMAT, ARGS...) \
LOG_MAP(map, DCN, LOGLEVEL, "%s NRI(%dbit)=0x%x=%d: " FORMAT, osmo_mobile_identity_to_str_c(OTC_SELECT, &map->l3.mi), \
nri_bitlen, nri_v, nri_v, ##ARGS)
/* Get the NRI bits either from map->l3.nri, or extract NRI bits from TMSI.
* The NRI possibly indicates which MSC is responsible. */
if (map->l3.gmm_nri_container >= 0) {
nri_v = map->l3.gmm_nri_container;
/* The 'TMSI based NRI container' is always 10 bits long. If the relevant NRI length is configured to be
* less than that, ignore the lower bits. */
if (nri_bitlen < 10)
nri_v >>= 10 - nri_bitlen;
} else if (map->l3.mi.type == GSM_MI_TYPE_TMSI) {
if (osmo_tmsi_nri_v_get(&nri_v, map->l3.mi.tmsi, nri_bitlen)) {
LOG_NRI(LOGL_ERROR, "Unable to retrieve NRI from TMSI 0x%x, nri_bitlen == %u\n", map->l3.mi.tmsi,
nri_bitlen);
nri_v = -1;
}
}
if (*ss7 && (*ss7 != ss7_tmp)) {
LOGP(DMAIN, LOGL_ERROR, "IuCS and IuPS cannot be served from separate CS7 instances,"
" cs7 instance %d != %d\n", (*ss7)->cfg.id, ss7_tmp->cfg.id);
return -1;
if (map->l3.from_other_plmn && nri_v >= 0) {
/* If a subscriber was previously attached to a different PLMN, it might still send the other
* PLMN's TMSI identity in an IMSI Attach. The LU sends a LAI indicating the previous PLMN. If
* it mismatches our PLMN, ignore the NRI. */
LOG_NRI(LOGL_DEBUG,
"This Complete Layer 3 message indicates a switch from another PLMN. Ignoring the NRI.\n");
nri_v = -1;
}
*ss7 = ss7_tmp;
if (nri_v >= 0)
is_null_nri = osmo_nri_v_matches_ranges(nri_v, cnpool->use.null_nri_ranges);
if (is_null_nri)
LOG_NRI(LOGL_DEBUG, "this is a NULL-NRI\n");
osmo_sccp_addr_set_ssn(dest, OSMO_SCCP_SSN_RANAP);
/* Iterate CN links to find one that matches the extracted NRI, and the next round-robin target for the case no
* NRI match is found. */
round_robin_next_nr = (map->l3.is_emerg ? cnpool->round_robin_next_emerg_nr : cnpool->round_robin_next_nr);
llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) {
bool nri_matches_cnlink = (nri_v >= 0 && osmo_nri_v_matches_ranges(nri_v, cnlink->use.nri_ranges));
if (!addr_has_pc_and_ssn(dest)) {
LOGP(DMAIN, LOGL_ERROR, "Invalid/incomplete %s remote-addr: %s\n",
label, osmo_sccp_addr_name(*ss7, dest));
return -1;
if (!is_cnlink_usable(cnlink, map->l3.is_emerg)) {
if (nri_matches_cnlink) {
LOG_NRI(LOGL_DEBUG, "NRI matches %s %d, but this %s is currently not connected\n",
cnpool->peer_name, cnlink->nr, cnpool->peer_name);
CNLINK_CTR_INC(cnlink, CNLINK_CTR_CNPOOL_SUBSCR_ATTACH_LOST);
}
continue;
}
/* Return CN link if it matches this NRI, with some debug logging. */
if (nri_matches_cnlink) {
if (is_null_nri) {
LOG_NRI(LOGL_DEBUG, "NRI matches %s %d, but this NRI is also configured as NULL-NRI\n",
cnpool->peer_name, cnlink->nr);
} else {
LOG_NRI(LOGL_INFO, "NRI match selects %s %d\n", cnpool->peer_name, cnlink->nr);
CNLINK_CTR_INC(cnlink, CNLINK_CTR_CNPOOL_SUBSCR_KNOWN);
if (map->l3.is_emerg) {
CNLINK_CTR_INC(cnlink, CNLINK_CTR_CNPOOL_EMERG_FORWARDED);
CNPOOL_CTR_INC(cnpool, CNPOOL_CTR_EMERG_FORWARDED);
}
return cnlink;
}
}
/* Figure out the next round-robin MSC. The MSCs may appear unsorted in net->mscs. Make sure to linearly
* round robin the MSCs by number: pick the lowest msc->nr >= round_robin_next_nr, and also remember the
* lowest available msc->nr to wrap back to that in case no next MSC is left.
*
* MSCs configured with `no allow-attach` do not accept new subscribers and hence must not be picked by
* round-robin. Such an MSC still provides service for already attached subscribers: those that
* successfully performed IMSI-Attach and have a TMSI with an NRI pointing at that MSC. We only avoid
* adding IMSI-Attach of new subscribers. The idea is that the MSC is in a mode of off-loading
* subscribers, and the MSC decides when each subscriber is off-loaded, by assigning the NULL-NRI in a
* new TMSI (at the next periodical LU). So until the MSC decides to offload, an attached subscriber
* remains attached to that MSC and is free to use its services.
*/
if (!cnlink->allow_attach)
continue;
/* Find the allowed cnlink with the lowest nr */
if (!round_robin_first || cnlink->nr < round_robin_first->nr)
round_robin_first = cnlink;
/* Find the allowed cnlink with the lowest nr >= round_robin_next_nr */
if (cnlink->nr >= round_robin_next_nr
&& (!round_robin_next || cnlink->nr < round_robin_next->nr))
round_robin_next = cnlink;
}
LOGP(DRANAP, LOGL_NOTICE, "Remote %s SCCP addr: %s\n",
label, osmo_sccp_addr_name(*ss7, dest));
return 0;
if (nri_v >= 0 && !is_null_nri)
LOG_NRI(LOGL_DEBUG, "No %s found for this NRI, doing round-robin\n", cnpool->peer_name);
/* No dedicated CN link found. Choose by round-robin.
* If round_robin_next is NULL, there are either no more CN links at/after round_robin_next_nr, or none of
* them are usable -- wrap to the start. */
cnlink = round_robin_next ? : round_robin_first;
if (!cnlink) {
CNPOOL_CTR_INC(cnpool, CNPOOL_CTR_SUBSCR_NO_CNLINK);
if (map->l3.is_emerg)
CNPOOL_CTR_INC(cnpool, CNPOOL_CTR_EMERG_LOST);
return NULL;
}
LOG_MAP(map, DCN, LOGL_INFO, "CN link round-robin selects %s %d\n", cnpool->peer_name, cnlink->nr);
if (is_null_nri)
CNLINK_CTR_INC(cnlink, CNLINK_CTR_CNPOOL_SUBSCR_REATTACH);
else
CNLINK_CTR_INC(cnlink, CNLINK_CTR_CNPOOL_SUBSCR_NEW);
if (map->l3.is_emerg) {
CNLINK_CTR_INC(cnlink, CNLINK_CTR_CNPOOL_EMERG_FORWARDED);
CNPOOL_CTR_INC(cnpool, CNPOOL_CTR_EMERG_FORWARDED);
}
/* A CN link was picked by round-robin, so update the next round-robin nr to pick */
if (map->l3.is_emerg)
cnpool->round_robin_next_emerg_nr = cnlink->nr + 1;
else
cnpool->round_robin_next_nr = cnlink->nr + 1;
return cnlink;
#undef LOG_NRI
}
int hnbgw_cnlink_init(struct hnb_gw *gw, const char *stp_host, uint16_t stp_port, const char *local_ip)
static const struct rate_ctr_desc cnpool_ctr_description[] = {
[CNPOOL_CTR_SUBSCR_NO_CNLINK] = {
"cnpool:subscr:no_cnlink",
"Complete Layer 3 requests lost because no connected CN link is found available",
},
[CNPOOL_CTR_EMERG_FORWARDED] = {
"cnpool:emerg:forwarded",
"Emergency call requests forwarded to a CN link (see also per-CN-link counters)",
},
[CNPOOL_CTR_EMERG_LOST] = {
"cnpool:emerg:lost",
"Emergency call requests lost because no CN link was found available",
},
};
const struct rate_ctr_group_desc iucs_ctrg_desc = {
"iucs",
"IuCS",
OSMO_STATS_CLASS_GLOBAL,
ARRAY_SIZE(cnpool_ctr_description),
cnpool_ctr_description,
};
const struct rate_ctr_group_desc iups_ctrg_desc = {
"iups",
"IuPS",
OSMO_STATS_CLASS_GLOBAL,
ARRAY_SIZE(cnpool_ctr_description),
cnpool_ctr_description,
};
static int hnbgw_cnpool_talloc_destructor(struct hnbgw_cnpool *cnpool)
{
struct hnbgw_cnlink *cnlink;
struct osmo_ss7_instance *ss7;
uint32_t local_pc;
OSMO_ASSERT(!gw->sccp.client);
OSMO_ASSERT(!gw->sccp.cnlink);
ss7 = NULL;
if (resolve_addr_name(&gw->sccp.iucs_remote_addr, &ss7,
gw->config.iucs_remote_addr_name, "IuCS", (23 << 3) + 1))
return -1;
if (resolve_addr_name(&gw->sccp.iups_remote_addr, &ss7,
gw->config.iups_remote_addr_name, "IuPS", (23 << 3) + 4))
return -1;
if (!ss7) {
LOGP(DRANAP, LOGL_NOTICE, "No cs7 instance configured for IuCS nor IuPS,"
" creating default instance\n");
ss7 = osmo_ss7_instance_find_or_create(gw, 0);
ss7->cfg.primary_pc = (23 << 3) + 5;
}
if (!osmo_ss7_pc_is_valid(ss7->cfg.primary_pc)) {
LOGP(DMAIN, LOGL_ERROR, "IuCS/IuPS uplink cannot be setup: CS7 instance %d has no point-code set\n",
ss7->cfg.id);
return -1;
}
local_pc = ss7->cfg.primary_pc;
osmo_sccp_make_addr_pc_ssn(&gw->sccp.local_addr, local_pc, OSMO_SCCP_SSN_RANAP);
LOGP(DRANAP, LOGL_NOTICE, "Local SCCP addr: %s\n", osmo_sccp_addr_name(ss7, &gw->sccp.local_addr));
gw->sccp.client = osmo_sccp_simple_client_on_ss7_id(gw, ss7->cfg.id, "OsmoHNBGW",
local_pc, OSMO_SS7_ASP_PROT_M3UA,
0, local_ip, stp_port, stp_host);
if (!gw->sccp.client) {
LOGP(DMAIN, LOGL_ERROR, "Failed to init SCCP Client\n");
return -1;
}
cnlink = talloc_zero(gw, struct hnbgw_cnlink);
cnlink->gw = gw;
INIT_LLIST_HEAD(&cnlink->map_list);
cnlink->T_RafC.cb = cnlink_trafc_cb;
cnlink->T_RafC.data = gw;
cnlink->next_conn_id = 1000;
cnlink->sccp_user = osmo_sccp_user_bind_pc(gw->sccp.client, "OsmoHNBGW", sccp_sap_up,
OSMO_SCCP_SSN_RANAP, gw->sccp.local_addr.pc);
if (!cnlink->sccp_user) {
LOGP(DMAIN, LOGL_ERROR, "Failed to init SCCP User\n");
return -1;
}
LOGP(DRANAP, LOGL_NOTICE, "Remote SCCP addr: IuCS: %s\n",
osmo_sccp_addr_name(ss7, &gw->sccp.iucs_remote_addr));
LOGP(DRANAP, LOGL_NOTICE, "Remote SCCP addr: IuPS: %s\n",
osmo_sccp_addr_name(ss7, &gw->sccp.iups_remote_addr));
/* In sccp_sap_up() we expect the cnlink in the user's priv. */
osmo_sccp_user_set_priv(cnlink->sccp_user, cnlink);
gw->sccp.cnlink = cnlink;
osmo_nri_ranges_free(cnpool->vty.null_nri_ranges);
cnpool->vty.null_nri_ranges = NULL;
while ((cnlink = llist_first_entry_or_null(&cnpool->cnlinks, struct hnbgw_cnlink, entry)))
hnbgw_cnlink_term_and_free(cnlink);
return 0;
}
struct hnbgw_cnpool *hnbgw_cnpool_alloc(RANAP_CN_DomainIndicator_t domain)
{
struct hnbgw_cnpool *cnpool = talloc_zero(g_hnbgw, struct hnbgw_cnpool);
OSMO_ASSERT(cnpool);
cnpool->domain = domain;
cnpool->vty = (struct hnbgw_cnpool_cfg){
.nri_bitlen = OSMO_NRI_BITLEN_DEFAULT,
.null_nri_ranges = osmo_nri_ranges_alloc(cnpool),
};
OSMO_ASSERT(cnpool->vty.null_nri_ranges);
INIT_LLIST_HEAD(&cnpool->cnlinks);
talloc_set_destructor(cnpool, hnbgw_cnpool_talloc_destructor);
switch (domain) {
case DOMAIN_CS:
cnpool->pool_name = "iucs";
cnpool->peer_name = "msc";
cnpool->default_remote_pc = DEFAULT_PC_MSC;
cnpool->default_addr_name = DEFAULT_ADDR_NAME_MSC;
cnpool->ctrs = rate_ctr_group_alloc(cnpool, &iucs_ctrg_desc, 0);
break;
case DOMAIN_PS:
cnpool->pool_name = "iups";
cnpool->peer_name = "sgsn";
cnpool->default_remote_pc = DEFAULT_PC_SGSN;
cnpool->default_addr_name = DEFAULT_ADDR_NAME_SGSN;
cnpool->ctrs = rate_ctr_group_alloc(cnpool, &iups_ctrg_desc, 0);
break;
default:
OSMO_ASSERT(0);
}
return cnpool;
}

View File

@@ -1,4 +1,4 @@
/* hnb-gw specific code for HNBAP */
/* hnb-gw specific code for HNBAP, 3GPP TS 25.469 */
/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
* All Rights Reserved
@@ -22,6 +22,7 @@
#include <osmocom/core/utils.h>
#include <osmocom/core/socket.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/gsm/protocol/gsm_23_003.h>
#include <osmocom/netif/stream.h>
#include <unistd.h>
@@ -32,6 +33,8 @@
#include <osmocom/hnbap/hnbap_common.h>
#include <osmocom/ranap/iu_helpers.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnb_persistent.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbap/hnbap_ies_defs.h>
@@ -49,16 +52,61 @@ static int hnbgw_hnbap_tx(struct hnb_context *ctx, struct msgb *msg)
return 0;
}
static int hnbgw_tx_hnb_register_rej(struct hnb_context *ctx)
static int hnbgw_tx_error_ind(struct hnb_context *ctx, const HNBAP_Cause_t *cause,
HNBAP_ProcedureCode_t proc_code, HNBAP_Criticality_t criticality,
HNBAP_TriggeringMessage_t trig_msg)
{
HNBAP_ErrorIndication_t err_ind;
HNBAP_ErrorIndicationIEs_t err_ind_ies;
struct msgb *msg;
int rc;
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Tx ErrorIndication cause=%s\n", hnbap_cause_str(cause));
err_ind_ies.presenceMask = 0;
err_ind_ies.cause = *cause;
if (proc_code != -1 || trig_msg != -1 || criticality != -1) {
err_ind_ies.presenceMask |= ERRORINDICATIONIES_HNBAP_CRITICALITYDIAGNOSTICS_PRESENT;
if (proc_code != -1)
err_ind_ies.criticalityDiagnostics.procedureCode = &proc_code;
if (trig_msg != -1)
err_ind_ies.criticalityDiagnostics.triggeringMessage = &trig_msg;
if (criticality != -1)
err_ind_ies.criticalityDiagnostics.procedureCriticality = &criticality;
}
memset(&err_ind, 0, sizeof(err_ind));
rc = hnbap_encode_errorindicationies(&err_ind, &err_ind_ies);
if (rc < 0) {
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failure to encode ERROR-INDICATION to %s: rc=%d\n",
ctx->identity_info, rc);
return rc;
}
msg = hnbap_generate_initiating_message(HNBAP_ProcedureCode_id_ErrorIndication,
HNBAP_Criticality_ignore,
&asn_DEF_HNBAP_ErrorIndication,
&err_ind);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_ErrorIndication, &err_ind);
return hnbgw_hnbap_tx(ctx, msg);
}
static int hnbgw_tx_hnb_register_rej(struct hnb_context *ctx, const HNBAP_Cause_t *cause)
{
HNBAP_HNBRegisterReject_t reject_out;
HNBAP_HNBRegisterRejectIEs_t reject;
struct msgb *msg;
int rc;
reject.presenceMask = 0,
reject.cause.present = HNBAP_Cause_PR_radioNetwork;
reject.cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_unspecified;
OSMO_ASSERT(cause);
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Rejecting HNB Register Request cause=%s\n", hnbap_cause_str(cause));
reject.presenceMask = 0;
reject.cause = *cause;
/* encode the Information Elements */
memset(&reject_out, 0, sizeof(reject_out));
@@ -69,7 +117,7 @@ static int hnbgw_tx_hnb_register_rej(struct hnb_context *ctx)
return rc;
}
/* generate a successfull outcome PDU */
/* generate a unsuccessful outcome PDU */
msg = hnbap_generate_unsuccessful_outcome(HNBAP_ProcedureCode_id_HNBRegister,
HNBAP_Criticality_reject,
&asn_DEF_HNBAP_HNBRegisterReject,
@@ -99,7 +147,7 @@ static int hnbgw_tx_hnb_register_acc(struct hnb_context *ctx)
/* Single required response IE: RNC-ID */
HNBAP_HNBRegisterAcceptIEs_t accept = {
.rnc_id = ctx->gw->config.rnc_id
.rnc_id = g_hnbgw->config.rnc_id
};
/* encode the Information Elements */
@@ -111,7 +159,7 @@ static int hnbgw_tx_hnb_register_acc(struct hnb_context *ctx)
return rc;
}
/* generate a successfull outcome PDU */
/* generate a successful outcome PDU */
msg = hnbap_generate_successful_outcome(HNBAP_ProcedureCode_id_HNBRegister,
HNBAP_Criticality_reject,
&asn_DEF_HNBAP_HNBRegisterAccept,
@@ -119,13 +167,13 @@ static int hnbgw_tx_hnb_register_acc(struct hnb_context *ctx)
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_HNBRegisterAccept, &accept_out);
LOGHNB(ctx, DHNBAP, LOGL_NOTICE, "Accepting HNB-REGISTER-REQ from %s\n", ctx->identity_info);
LOGHNB(ctx, DHNBAP, LOGL_NOTICE, "Accepting HNB-REGISTER-REQ\n");
return hnbgw_hnbap_tx(ctx, msg);
}
static int hnbgw_tx_ue_register_acc(struct ue_context *ue)
static int hnbgw_tx_ue_register_acc(struct hnb_context *hnb, const char *imsi, uint32_t context_id)
{
HNBAP_UERegisterAccept_t accept_out;
HNBAP_UERegisterAcceptIEs_t accept;
@@ -136,17 +184,20 @@ static int hnbgw_tx_ue_register_acc(struct ue_context *ue)
int rc;
encoded_imsi_len = ranap_imsi_encode(encoded_imsi,
sizeof(encoded_imsi), ue->imsi);
sizeof(encoded_imsi), imsi);
memset(&accept, 0, sizeof(accept));
accept.uE_Identity.present = HNBAP_UE_Identity_PR_iMSI;
OCTET_STRING_fromBuf(&accept.uE_Identity.choice.iMSI,
(const char *)encoded_imsi, encoded_imsi_len);
asn1_u24_to_bitstring(&accept.context_ID, &ctx_id, ue->context_id);
asn1_u24_to_bitstring(&accept.context_ID, &ctx_id, context_id);
memset(&accept_out, 0, sizeof(accept_out));
rc = hnbap_encode_ueregisteraccepties(&accept_out, &accept);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, &accept.uE_Identity.choice.iMSI);
if (rc < 0) {
LOGHNB(hnb, DHNBAP, LOGL_ERROR,
"Failed to encode HNBAP UE Register Accept message for UE IMSI-%s\n", imsi);
return rc;
}
@@ -155,19 +206,25 @@ static int hnbgw_tx_ue_register_acc(struct ue_context *ue)
&asn_DEF_HNBAP_UERegisterAccept,
&accept_out);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, &accept.uE_Identity.choice.iMSI);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_UERegisterAccept, &accept_out);
return hnbgw_hnbap_tx(ue->hnb, msg);
rc = hnbgw_hnbap_tx(hnb, msg);
if (rc)
LOGHNB(hnb, DHNBAP, LOGL_ERROR,
"Failed to enqueue HNBAP UE Register Accept message for UE IMSI-%s\n", imsi);
return rc;
}
static int hnbgw_tx_ue_register_rej_tmsi(struct hnb_context *hnb, HNBAP_UE_Identity_t *ue_id)
static int hnbgw_tx_ue_register_rej(struct hnb_context *hnb, HNBAP_UE_Identity_t *ue_id, const struct HNBAP_Cause *cause)
{
HNBAP_UERegisterReject_t reject_out;
HNBAP_UERegisterRejectIEs_t reject;
char imsi[GSM23003_IMSI_MAX_DIGITS+1];
struct msgb *msg;
int rc;
OSMO_ASSERT(cause);
memset(&reject, 0, sizeof(reject));
reject.uE_Identity.present = ue_id->present;
@@ -223,16 +280,23 @@ static int hnbgw_tx_ue_register_rej_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
ue_id->choice.pTMSIRAI.rAI.rAC.size);
break;
case HNBAP_UE_Identity_PR_iMSI:
ranap_bcd_decode(imsi, sizeof(imsi), ue_id->choice.iMSI.buf, ue_id->choice.iMSI.size);
LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "REJ UE_Id IMSI %s\n", imsi);
OCTET_STRING_fromBuf(&reject.uE_Identity.choice.iMSI,
(const char *)ue_id->choice.iMSI.buf, ue_id->choice.iMSI.size);
break;
default:
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Cannot compose UE Register Reject:"
" unsupported UE ID (present=%d)\n", ue_id->present);
return -1;
}
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Rejecting UE Register Request: TMSI identity registration is switched off\n");
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Rejecting UE Register Request cause=%s\n", hnbap_cause_str(cause));
reject.cause.present = HNBAP_Cause_PR_radioNetwork;
reject.cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_invalid_UE_identity;
reject.cause = *cause;
memset(&reject_out, 0, sizeof(reject_out));
rc = hnbap_encode_ueregisterrejecties(&reject_out, &reject);
@@ -265,6 +329,9 @@ static int hnbgw_tx_ue_register_rej_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING,
&reject.uE_Identity.choice.pTMSIRAI.rAI.rAC);
break;
case HNBAP_UE_Identity_PR_iMSI:
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING,
&reject.uE_Identity.choice.iMSI);
default:
/* should never happen after above switch() */
@@ -276,14 +343,13 @@ static int hnbgw_tx_ue_register_rej_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
return hnbgw_hnbap_tx(hnb, msg);
}
static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Identity_t *ue_id)
static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Identity_t *ue_id, uint32_t context_id)
{
HNBAP_UERegisterAccept_t accept_out;
HNBAP_UERegisterAcceptIEs_t accept;
struct msgb *msg;
uint32_t ctx_id;
uint32_t tmsi = 0;
struct ue_context *ue;
int rc;
memset(&accept, 0, sizeof(accept));
@@ -322,29 +388,19 @@ static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
break;
default:
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Unsupportedccept UE ID (present=%d)\n", ue_id->present);
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Unsupported HNBAP UE Identity type (present=%d)\n", ue_id->present);
return -1;
}
tmsi = ntohl(tmsi);
LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "HNBAP register with TMSI %x\n", tmsi);
ue = ue_context_by_tmsi(hnb->gw, tmsi);
if (!ue)
ue = ue_context_alloc(hnb, NULL, tmsi);
asn1_u24_to_bitstring(&accept.context_ID, &ctx_id, ue->context_id);
asn1_u24_to_bitstring(&accept.context_ID, &ctx_id, context_id);
memset(&accept_out, 0, sizeof(accept_out));
rc = hnbap_encode_ueregisteraccepties(&accept_out, &accept);
if (rc < 0)
return rc;
msg = hnbap_generate_successful_outcome(HNBAP_ProcedureCode_id_UERegister,
HNBAP_Criticality_reject,
&asn_DEF_HNBAP_UERegisterAccept,
&accept_out);
/* free 'accept', now encoded in 'accept_out' */
switch (ue_id->present) {
case HNBAP_UE_Identity_PR_tMSILAI:
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_BIT_STRING,
@@ -371,65 +427,166 @@ static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
break;
}
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_UERegisterAccept, &accept_out);
if (rc < 0) {
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Failed to encode HNBAP UE Register Accept for TMSI 0x%08x\n", tmsi);
/* Encoding failed. Nothing in 'accept_out'. */
return rc;
}
return hnbgw_hnbap_tx(hnb, msg);
/* Encoding successful, transmit, then free 'accept_out'. */
msg = hnbap_generate_successful_outcome(HNBAP_ProcedureCode_id_UERegister,
HNBAP_Criticality_reject,
&asn_DEF_HNBAP_UERegisterAccept,
&accept_out);
rc = hnbgw_hnbap_tx(hnb, msg);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_UERegisterAccept, &accept_out);
if (rc)
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Failed to transmit HNBAP UE Register Accept for TMSI 0x%08x\n", tmsi);
return rc;
}
static int hnbgw_rx_hnb_deregister(struct hnb_context *ctx, ANY_t *in)
{
HNBAP_HNBDe_RegisterIEs_t ies;
HNBAP_Cause_t cause = {};
int rc;
rc = hnbap_decode_hnbde_registeries(&ies, in);
if (rc < 0)
return rc;
if (rc < 0) {
cause.present = HNBAP_Cause_PR_protocol;
cause.choice.radioNetwork = HNBAP_CauseProtocol_unspecified;
return hnbgw_tx_error_ind(ctx, &cause, HNBAP_ProcedureCode_id_HNBDe_Register, HNBAP_Criticality_ignore,
HNBAP_TriggeringMessage_initiating_message);
}
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "HNB-DE-REGISTER cause=%s\n", hnbap_cause_str(&ies.cause));
hnbap_free_hnbde_registeries(&ies);
hnb_context_release(ctx);
ctx->hnb_registered = false;
return 0;
}
static bool is_asn1_octet_string_empty(const OCTET_STRING_t *val)
{
return !val || !val->buf || !val->size;
}
static bool is_asn1_bit_string_empty(const BIT_STRING_t *val)
{
return !val || !val->buf || !val->size;
}
static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
{
struct hnb_context *hnb;
struct hnb_persistent *hnbp;
struct hnb_context *hnb, *tmp;
HNBAP_HNBRegisterRequestIEs_t ies;
int rc;
struct osmo_fd *ofd = osmo_stream_srv_get_ofd(ctx->conn);
char identity_str[256];
const char *cell_id_str;
struct timespec tp;
HNBAP_Cause_t cause = {};
struct osmo_sockaddr cur_osa = {};
socklen_t len = sizeof(cur_osa);
rc = hnbap_decode_hnbregisterrequesties(&ies, in);
if (rc < 0) {
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failure to decode HNB-REGISTER-REQ from %s: rc=%d\n",
ctx->identity_info, rc);
return rc;
if (rc < 0
/* CID#465551: make sure that actual values ended up in the asn1 octet strings: */
|| is_asn1_octet_string_empty(&ies.lac)
|| is_asn1_octet_string_empty(&ies.sac)
|| is_asn1_octet_string_empty(&ies.rac)
|| is_asn1_bit_string_empty(&ies.cellIdentity)
|| is_asn1_octet_string_empty(&ies.plmNidentity)) {
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failure to decode HNB-REGISTER-REQ: rc=%d\n", rc);
cause.present = HNBAP_Cause_PR_protocol;
cause.choice.radioNetwork = HNBAP_CauseProtocol_unspecified;
return hnbgw_tx_hnb_register_rej(ctx, &cause);
}
asn1_strncpy(identity_str, &ies.hnB_Identity.hNB_Identity_Info, sizeof(identity_str));
/* copy all identity parameters from the message to ctx */
asn1_strncpy(ctx->identity_info, &ies.hnB_Identity.hNB_Identity_Info,
sizeof(ctx->identity_info));
OSMO_STRLCPY_ARRAY(ctx->identity_info, identity_str);
/* We want to use struct umts_cell_id as hashtable key. If it ever happens to contain any padding bytes, make
* sure everything is deterministically zero. */
memset(&ctx->id, 0, sizeof(ctx->id));
ctx->id.lac = asn1str_to_u16(&ies.lac);
ctx->id.sac = asn1str_to_u16(&ies.sac);
ctx->id.rac = asn1str_to_u8(&ies.rac);
ctx->id.cid = asn1bitstr_to_u28(&ies.cellIdentity);
gsm48_mcc_mnc_from_bcd(ies.plmNidentity.buf, &ctx->id.mcc, &ctx->id.mnc);
osmo_plmn_from_bcd(ies.plmNidentity.buf, &ctx->id.plmn);
cell_id_str = umts_cell_id_to_str(&ctx->id);
llist_for_each_entry(hnb, &ctx->gw->hnb_list, list) {
if (getpeername(ofd->fd, &cur_osa.u.sa, &len) < 0) {
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "HNB-REGISTER-REQ %s: rejecting due to getpeername() error: %s\n",
cell_id_str, strerror(errno));
hnbap_free_hnbregisterrequesties(&ies);
cause.present = HNBAP_Cause_PR_radioNetwork;
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_hNB_parameter_mismatch;
return hnbgw_tx_hnb_register_rej(ctx, &cause);
}
hnbp = hnb_persistent_find_by_id(&ctx->id);
if (!hnbp && g_hnbgw->config.accept_all_hnb)
hnbp = hnb_persistent_alloc(&ctx->id);
if (!hnbp) {
LOGHNB(ctx, DHNBAP, LOGL_NOTICE, "Rejecting unknonwn HNB with identity %s\n", identity_str);
hnbap_free_hnbregisterrequesties(&ies);
cause.present = HNBAP_Cause_PR_radioNetwork;
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_unauthorised_HNB;
return hnbgw_tx_hnb_register_rej(ctx, &cause);
}
ctx->persistent = hnbp;
hnbp->ctx = ctx;
HNBP_CTR_INC(hnbp, HNB_CTR_IUH_ESTABLISHED);
rc = osmo_clock_gettime(CLOCK_MONOTONIC, &tp);
hnbp->updowntime = (rc < 0) ? 0 : tp.tv_sec;
llist_for_each_entry_safe(hnb, tmp, &g_hnbgw->hnb_list, list) {
if (hnb->hnb_registered && ctx != hnb && memcmp(&ctx->id, &hnb->id, sizeof(ctx->id)) == 0) {
struct osmo_fd *ofd = osmo_stream_srv_get_ofd(ctx->conn);
char *name = osmo_sock_get_name(ctx, ofd->fd);
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "rejecting HNB-REGISTER-REQ with duplicate cell identity "
"MCC=%u,MNC=%u,LAC=%u,RAC=%u,SAC=%u,CID=%u from %s\n",
ctx->id.mcc, ctx->id.mnc, ctx->id.lac, ctx->id.rac, ctx->id.sac, ctx->id.cid, name);
talloc_free(name);
return hnbgw_tx_hnb_register_rej(ctx);
/* If it's coming from the same remote IP addr+port, then it must be our internal
* fault (bug), and we release the old context to keep going... */
struct osmo_fd *other_fd = osmo_stream_srv_get_ofd(hnb->conn);
struct osmo_sockaddr other_osa = {};
socklen_t len = sizeof(other_osa);
if (getpeername(other_fd->fd, &other_osa.u.sa, &len) < 0) {
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "BUG! Found old registered HNB with invalid socket, releasing it\n");
hnb_context_release(hnb);
continue;
}
if (osmo_sockaddr_cmp(&cur_osa, &other_osa) == 0) {
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "BUG! Found old registered HNB with same remote address, releasing it\n");
hnb_context_release(hnb);
continue;
} /* else: addresses are different, we continue below */
/* If new conn registering same HNB is from anoter remote addr+port, let's reject it to avoid
* misconfigurations or someone trying to impersonate an already working HNB: */
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "rejecting HNB-REGISTER-REQ with duplicate cell identity %s\n", cell_id_str);
hnbap_free_hnbregisterrequesties(&ies);
cause.present = HNBAP_Cause_PR_radioNetwork;
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_hNB_parameter_mismatch;
return hnbgw_tx_hnb_register_rej(ctx, &cause);
}
}
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "HNB-REGISTER-REQ %s %s%s\n",
ctx->identity_info, cell_id_str, ctx->hnb_registered ? " (re-connecting)" : "");
/* The HNB is already registered, and we are seeing a new HNB Register Request. The HNB has restarted
* without us noticing. Clearly, the HNB does not expect any UE state to be active here, so discard any
* UE contexts and SCCP connections associated with this HNB. */
LOGHNB(ctx, DHNBAP, LOGL_NOTICE, "HNB (re)connecting, discarding all previous UE state\n");
hnb_context_release_ue_state(ctx);
ctx->hnb_registered = true;
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "HNB-REGISTER-REQ from %s\n", ctx->identity_info);
hnb_persistent_registered(ctx->persistent);
/* Send HNBRegisterAccept */
rc = hnbgw_tx_hnb_register_acc(ctx);
@@ -440,13 +597,26 @@ static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
static int hnbgw_rx_ue_register_req(struct hnb_context *ctx, ANY_t *in)
{
HNBAP_UERegisterRequestIEs_t ies;
struct ue_context *ue;
char imsi[16];
HNBAP_Cause_t cause = {};
char imsi[GSM23003_IMSI_MAX_DIGITS+1];
int rc;
rc = hnbap_decode_ueregisterrequesties(&ies, in);
if (rc < 0)
return rc;
if (rc < 0) {
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failure to decode UE-REGISTER-REQ: rc=%d\n", rc);
cause.present = HNBAP_Cause_PR_protocol;
cause.choice.radioNetwork = HNBAP_CauseProtocol_unspecified;
return hnbgw_tx_error_ind(ctx, &cause, HNBAP_ProcedureCode_id_UERegister, HNBAP_Criticality_reject,
HNBAP_TriggeringMessage_initiating_message);
}
if (!ctx->hnb_registered) {
/* UE registration requires prior HNB registration; reject with proper cause */
cause.present = HNBAP_Cause_PR_radioNetwork;
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_hNB_not_registered;
rc = hnbgw_tx_ue_register_rej(ctx, &ies.uE_Identity, &cause);
goto free_and_return_rc;
}
switch (ies.uE_Identity.present) {
case HNBAP_UE_Identity_PR_iMSI:
@@ -463,50 +633,63 @@ static int hnbgw_rx_ue_register_req(struct hnb_context *ctx, ANY_t *in)
break;
case HNBAP_UE_Identity_PR_tMSILAI:
case HNBAP_UE_Identity_PR_pTMSIRAI:
if (ctx->gw->config.hnbap_allow_tmsi)
rc = hnbgw_tx_ue_register_acc_tmsi(ctx, &ies.uE_Identity);
else
rc = hnbgw_tx_ue_register_rej_tmsi(ctx, &ies.uE_Identity);
if (g_hnbgw->config.hnbap_allow_tmsi) {
rc = hnbgw_tx_ue_register_acc_tmsi(ctx, &ies.uE_Identity, get_next_ue_ctx_id());
} else {
cause.present = HNBAP_Cause_PR_radioNetwork;
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_invalid_UE_identity;
rc = hnbgw_tx_ue_register_rej(ctx, &ies.uE_Identity, &cause);
}
/* all has been handled by TMSI, skip the IMSI code below */
hnbap_free_ueregisterrequesties(&ies);
return rc;
goto free_and_return_rc;
default:
LOGHNB(ctx, DHNBAP, LOGL_NOTICE, "UE-REGISTER-REQ with unsupported UE Id type %d\n",
ies.uE_Identity.present);
hnbap_free_ueregisterrequesties(&ies);
return rc;
cause.present = HNBAP_Cause_PR_radioNetwork;
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_invalid_UE_identity;
rc = hnbgw_tx_ue_register_rej(ctx, &ies.uE_Identity, &cause);
goto free_and_return_rc;
}
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "UE-REGISTER-REQ ID_type=%d imsi=%s cause=%ld\n",
ies.uE_Identity.present, imsi, ies.registration_Cause);
ue = ue_context_by_imsi(ctx->gw, imsi);
if (!ue)
ue = ue_context_alloc(ctx, imsi, 0);
hnbap_free_ueregisterrequesties(&ies);
/* Send UERegisterAccept */
return hnbgw_tx_ue_register_acc(ue);
rc = hnbgw_tx_ue_register_acc(ctx, imsi, get_next_ue_ctx_id());
if (rc < 0)
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failed to transmit HNBAP UE Register Accept for IMSI %s\n", imsi);
free_and_return_rc:
hnbap_free_ueregisterrequesties(&ies);
return rc;
}
static int hnbgw_rx_ue_deregister(struct hnb_context *ctx, ANY_t *in)
{
HNBAP_UEDe_RegisterIEs_t ies;
struct ue_context *ue;
HNBAP_Cause_t cause = {};
int rc;
uint32_t ctxid;
rc = hnbap_decode_uede_registeries(&ies, in);
if (rc < 0)
return rc;
if (rc < 0) {
cause.present = HNBAP_Cause_PR_protocol;
cause.choice.protocol = HNBAP_CauseProtocol_unspecified;
return hnbgw_tx_error_ind(ctx, &cause, HNBAP_ProcedureCode_id_UEDe_Register,
HNBAP_Criticality_ignore, HNBAP_TriggeringMessage_initiating_message);
}
ctxid = asn1bitstr_to_u24(&ies.context_ID);
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "UE-DE-REGISTER context=%u cause=%s\n", ctxid, hnbap_cause_str(&ies.cause));
ue = ue_context_by_id(ctx->gw, ctxid);
if (ue)
ue_context_free(ue);
if (!ctx->hnb_registered) {
LOGHNB(ctx, DHNBAP, LOGL_NOTICE, "UE-DE-REGISTER context=%u cause=%s not permitted; HNB not registered\n",
ctxid, hnbap_cause_str(&ies.cause));
cause.present = HNBAP_Cause_PR_radioNetwork;
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_hNB_not_registered;
hnbgw_tx_error_ind(ctx, &cause, HNBAP_ProcedureCode_id_UEDe_Register,
HNBAP_Criticality_ignore, HNBAP_TriggeringMessage_initiating_message);
} else {
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "UE-DE-REGISTER context=%u cause=%s\n", ctxid, hnbap_cause_str(&ies.cause));
}
hnbap_free_uede_registeries(&ies);
return 0;
@@ -529,6 +712,10 @@ static int hnbgw_rx_err_ind(struct hnb_context *hnb, ANY_t *in)
static int hnbgw_rx_initiating_msg(struct hnb_context *hnb, HNBAP_InitiatingMessage_t *imsg)
{
HNBAP_Cause_t cause = {
.present = HNBAP_Cause_PR_protocol,
.choice.protocol = HNBAP_CauseProtocol_unspecified,
};
int rc = 0;
switch (imsg->procedureCode) {
@@ -538,7 +725,7 @@ static int hnbgw_rx_initiating_msg(struct hnb_context *hnb, HNBAP_InitiatingMess
case HNBAP_ProcedureCode_id_HNBDe_Register: /* 8.3 */
rc = hnbgw_rx_hnb_deregister(hnb, &imsg->value);
break;
case HNBAP_ProcedureCode_id_UERegister: /* 8.4 */
case HNBAP_ProcedureCode_id_UERegister: /* 8.4 */
rc = hnbgw_rx_ue_register_req(hnb, &imsg->value);
break;
case HNBAP_ProcedureCode_id_UEDe_Register: /* 8.5 */
@@ -547,15 +734,17 @@ static int hnbgw_rx_initiating_msg(struct hnb_context *hnb, HNBAP_InitiatingMess
case HNBAP_ProcedureCode_id_ErrorIndication: /* 8.6 */
rc = hnbgw_rx_err_ind(hnb, &imsg->value);
break;
case HNBAP_ProcedureCode_id_TNLUpdate: /* 8.9 */
case HNBAP_ProcedureCode_id_TNLUpdate: /* 8.9 */
case HNBAP_ProcedureCode_id_HNBConfigTransfer: /* 8.10 */
case HNBAP_ProcedureCode_id_RelocationComplete: /* 8.11 */
case HNBAP_ProcedureCode_id_U_RNTIQuery: /* 8.12 */
case HNBAP_ProcedureCode_id_privateMessage:
LOGHNB(hnb, DHNBAP, LOGL_NOTICE, "Unimplemented HNBAP Procedure %ld\n", imsg->procedureCode);
rc = hnbgw_tx_error_ind(hnb, &cause, imsg->procedureCode, -1, HNBAP_TriggeringMessage_initiating_message);
break;
default:
LOGHNB(hnb, DHNBAP, LOGL_NOTICE, "Unknown HNBAP Procedure %ld\n", imsg->procedureCode);
rc = hnbgw_tx_error_ind(hnb, &cause, imsg->procedureCode, -1, HNBAP_TriggeringMessage_initiating_message);
break;
}
@@ -572,8 +761,7 @@ static int hnbgw_rx_unsuccessful_outcome_msg(struct hnb_context *hnb, HNBAP_Unsu
{
/* We don't care much about HNBAP */
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Received Unsuccessful Outcome, procedureCode %ld, criticality %ld,"
" cell mcc %u mnc %u lac %u rac %u sac %u cid %u\n", msg->procedureCode, msg->criticality,
hnb->id.mcc, hnb->id.mnc, hnb->id.lac, hnb->id.rac, hnb->id.sac, hnb->id.cid);
" cell %s\n", msg->procedureCode, msg->criticality, umts_cell_id_to_str(&hnb->id));
return 0;
}

331
src/osmo-hnbgw/hnbgw_l3.c Normal file
View File

@@ -0,0 +1,331 @@
/* OsmoHNBGW implementation of CS and PS Level3 message decoding (NAS PDU) */
/* Copyright 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "asn1helpers.h"
#include <osmocom/gsm/gsm48.h>
#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_ranap.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/ranap/ranap_ies_defs.h>
static const struct tlv_definition gsm48_gmm_att_tlvdef = {
.def = {
[GSM48_IE_GMM_CIPH_CKSN] = { TLV_TYPE_FIXED, 1 },
[GSM48_IE_GMM_TIMER_READY] = { TLV_TYPE_TV, 1 },
[GSM48_IE_GMM_TMSI_BASED_NRI_C] = { TLV_TYPE_TLV },
[GSM48_IE_GMM_ALLOC_PTMSI] = { TLV_TYPE_TLV, 0 },
[GSM48_IE_GMM_PTMSI_SIG] = { TLV_TYPE_FIXED, 3 },
[GSM48_IE_GMM_AUTH_RAND] = { TLV_TYPE_FIXED, 16 },
[GSM48_IE_GMM_AUTH_SRES] = { TLV_TYPE_FIXED, 4 },
[GSM48_IE_GMM_IMEISV] = { TLV_TYPE_TLV, 0 },
[GSM48_IE_GMM_DRX_PARAM] = { TLV_TYPE_FIXED, 2 },
[GSM48_IE_GMM_MS_NET_CAPA] = { TLV_TYPE_TLV, 0 },
[GSM48_IE_GMM_PDP_CTX_STATUS] = { TLV_TYPE_TLV, 0 },
[GSM48_IE_GMM_PS_LCS_CAPA] = { TLV_TYPE_TLV, 0 },
[GSM48_IE_GMM_GMM_MBMS_CTX_ST] = { TLV_TYPE_TLV, 0 },
},
};
static void decode_gmm_tlv(struct osmo_mobile_identity *mi, int *nri,
const uint8_t *tlv_data, size_t tlv_len, bool allow_hex)
{
struct tlv_parsed tp;
struct tlv_p_entry *e;
tlv_parse(&tp, &gsm48_gmm_att_tlvdef, tlv_data, tlv_len, 0, 0);
e = TLVP_GET(&tp, GSM48_IE_GMM_TMSI_BASED_NRI_C);
if (e) {
*nri = e->val[0];
*nri <<= 2;
*nri |= e->val[1] >> 6;
}
/* RAU Req: 9.4.14.5 P-TMSI (Iu mode only): "This IE shall be included by the MS." */
e = TLVP_GET(&tp, GSM48_IE_GMM_ALLOC_PTMSI);
if (mi && e)
osmo_mobile_identity_decode(mi, e->val, e->len, allow_hex);
}
/* Parse 3GPP TS 24.008 § 9.4.1 Attach request */
static int mobile_identity_decode_from_gmm_att_req(struct osmo_mobile_identity *mi,
struct osmo_routing_area_id *old_ra,
int *nri,
const uint8_t *l3_data, size_t l3_len, bool allow_hex)
{
const struct gsm48_hdr *gh = (void *)l3_data;
const uint8_t *cur = gh->data;
const uint8_t *end = l3_data + l3_len;
const uint8_t *mi_data;
uint8_t mi_len;
uint8_t msnc_len;
uint8_t ms_ra_acc_cap_len;
int rc;
if (l3_len < 26)
return -ENOSPC;
/* MS network capability 10.5.5.12 */
msnc_len = *cur++;
if (l3_len < (msnc_len + (cur - l3_data)))
return -ENOSPC;
cur += msnc_len;
/* aTTACH Type 10.5.5.2 + Ciphering key sequence number 10.5.1.2 */
cur++;
/* DRX parameter 10.5.5.6 */
cur += 2;
/* Mobile Identity (P-TMSI or IMSI) 10.5.1.4 */
mi_len = *cur++;
mi_data = cur;
if (l3_len < (mi_len + (cur - l3_data)))
return -ENOSPC;
cur += mi_len;
rc = osmo_mobile_identity_decode(mi, mi_data, mi_len, allow_hex);
if (rc)
return rc;
/* Old routing area identification 10.5.5.15. */
rc = osmo_routing_area_id_decode(old_ra, cur, end - cur);
if (rc < 0)
return rc;
cur += rc;
/* MS Radio Access Capability 10.5.5.12a */
ms_ra_acc_cap_len = *cur++;
if (l3_len < (ms_ra_acc_cap_len + (cur - l3_data)))
return -ENOSPC;
cur += ms_ra_acc_cap_len;
if (l3_len == (cur - l3_data))
return 0; /* No Optional TLV section */
/* Mobile identity = NULL: already obtained from Mandatory IE above.*/
decode_gmm_tlv(NULL, nri, cur, end - cur, allow_hex);
return 0;
}
/* Parse 24.008 9.4.14 RAU Request */
static int mobile_identity_decode_from_gmm_rau_req(struct osmo_mobile_identity *mi,
struct osmo_routing_area_id *old_ra,
int *nri,
const uint8_t *l3_data, size_t l3_len, bool allow_hex)
{
const struct gsm48_hdr *gh = (void *)l3_data;
const uint8_t *cur = gh->data;
const uint8_t *end = l3_data + l3_len;
uint8_t ms_ra_acc_cap_len;
int rc;
/* all mandatory fields + variable length MS Radio Cap (min value) would be 15 bytes.
* But even short radio capabilities we should handle with 14 bytes */
if (l3_len < 14)
return -ENOSPC;
/* V: Update Type 10.5.5.18 */
cur++;
/* V: Old routing area identification 10.5.5.15 */
rc = osmo_routing_area_id_decode(old_ra, cur, end - cur);
if (rc < 0)
return rc;
cur += rc;
if (cur >= end)
return -ENOSPC;
/* LV: MS Radio Access Capability 10.5.5.12a */
ms_ra_acc_cap_len = *cur++;
if (l3_len < (ms_ra_acc_cap_len + (cur - l3_data)))
return -ENOSPC;
cur += ms_ra_acc_cap_len;
if (l3_len == (cur - l3_data))
return 0; /* No Optional TLV section */
decode_gmm_tlv(mi, nri, cur, end - cur, allow_hex);
return 0;
}
/* CS MM: Determine mobile identity, from_other_plmn, is_emerg. */
static int peek_l3_ul_nas_cs(struct hnbgw_context_map *map, const uint8_t *nas_pdu, size_t len,
const struct osmo_plmn_id *local_plmn)
{
const struct gsm48_hdr *gh = (const struct gsm48_hdr *)nas_pdu;
struct osmo_location_area_id old_lai;
const struct gsm48_loc_upd_req *lu;
struct gsm48_service_request *cm;
osmo_mobile_identity_decode_from_l3_buf(&map->l3.mi, nas_pdu, len, false);
switch (map->l3.gsm48_pdisc) {
case GSM48_PDISC_MM:
/* Get is_emerg and from_other_plmn */
switch (map->l3.gsm48_msg_type) {
case GSM48_MT_MM_LOC_UPD_REQUEST:
if (len < sizeof(*gh) + sizeof(*lu)) {
LOGP(DCN, LOGL_ERROR, "LU Req message too short\n");
break;
}
lu = (struct gsm48_loc_upd_req *)gh->data;
gsm48_decode_lai2(&lu->lai, &old_lai);
map->l3.from_other_plmn = (osmo_plmn_cmp(&old_lai.plmn, local_plmn) != 0);
if (map->l3.from_other_plmn)
LOGP(DRUA, LOGL_INFO, "LU from other PLMN: old LAI=%s my PLMN=%s\n",
osmo_plmn_name_c(OTC_SELECT, &old_lai.plmn),
osmo_plmn_name_c(OTC_SELECT, local_plmn));
return 0;
case GSM48_MT_MM_CM_SERV_REQ:
if (len < sizeof(*gh) + sizeof(*cm)) {
LOGP(DRUA, LOGL_ERROR, "CM Service Req message too short\n");
break;
}
cm = (struct gsm48_service_request *)&gh->data[0];
map->l3.is_emerg = (cm->cm_service_type == GSM48_CMSERV_EMERGENCY);
LOGP(DRUA, LOGL_DEBUG, "CM Service is_emerg=%d\n", map->l3.is_emerg);
return 0;
}
break;
}
return 0;
}
/* PS GMM: Determine mobile identity, gmm_nri_container, from_other_plmn and is_emerg */
static int peek_l3_ul_nas_ps(struct hnbgw_context_map *map, const uint8_t *nas_pdu, size_t len,
const struct osmo_plmn_id *local_plmn)
{
struct osmo_routing_area_id old_ra = {};
int nri = -1;
switch (map->l3.gsm48_pdisc) {
case GSM48_PDISC_MM_GPRS:
switch (map->l3.gsm48_msg_type) {
case GSM48_MT_GMM_ATTACH_REQ:
mobile_identity_decode_from_gmm_att_req(&map->l3.mi, &old_ra, &nri, nas_pdu, len, false);
LOGP(DRUA, LOGL_DEBUG, "GMM Attach Req mi=%s old_ra=%s nri:%d=0x%x\n",
osmo_mobile_identity_to_str_c(OTC_SELECT, &map->l3.mi),
osmo_rai_name2_c(OTC_SELECT, &old_ra),
nri, nri);
if (old_ra.lac.plmn.mcc && osmo_plmn_cmp(&old_ra.lac.plmn, local_plmn)) {
map->l3.from_other_plmn = true;
LOGP(DRUA, LOGL_INFO, "GMM Attach Req from other PLMN: old RAI=%s my PLMN=%s\n",
osmo_rai_name2_c(OTC_SELECT, &old_ra),
osmo_plmn_name_c(OTC_SELECT, local_plmn));
}
if (nri >= 0)
map->l3.gmm_nri_container = nri;
return 0;
case GSM48_MT_GMM_RA_UPD_REQ:
mobile_identity_decode_from_gmm_rau_req(&map->l3.mi, &old_ra, &nri, nas_pdu, len, false);
LOGP(DRUA, LOGL_DEBUG, "GMM Routing Area Upd Req mi=%s old_ra=%s nri:%d=0x%x\n",
osmo_mobile_identity_to_str_c(OTC_SELECT, &map->l3.mi),
osmo_rai_name2_c(OTC_SELECT, &old_ra),
nri, nri);
if (old_ra.lac.plmn.mcc && osmo_plmn_cmp(&old_ra.lac.plmn, local_plmn)) {
map->l3.from_other_plmn = true;
LOGP(DRUA, LOGL_INFO, "GMM Routing Area Upd Req from other PLMN: old RAI=%s my PLMN=%s\n",
osmo_rai_name2_c(OTC_SELECT, &old_ra),
osmo_plmn_name_c(OTC_SELECT, local_plmn));
}
if (nri >= 0)
map->l3.gmm_nri_container = nri;
return 0;
}
break;
}
return 0;
}
static int peek_l3_ul_nas(struct hnbgw_context_map *map, const uint8_t *nas_pdu, size_t len,
const struct osmo_plmn_id *local_plmn)
{
const struct gsm48_hdr *gh = (const struct gsm48_hdr *)nas_pdu;
map->l3 = (struct hnbgw_l3_peek){
.gmm_nri_container = -1,
.mi = {
.type = GSM_MI_TYPE_NONE,
.tmsi = GSM_RESERVED_TMSI,
},
};
if (len < sizeof(*gh)) {
LOGP(DCN, LOGL_ERROR, "Layer 3 message too short for header\n");
return -EINVAL;
}
map->l3.gsm48_pdisc = gsm48_hdr_pdisc(gh);
map->l3.gsm48_msg_type = gsm48_hdr_msg_type(gh);
if (map->is_ps)
return peek_l3_ul_nas_ps(map, nas_pdu, len, local_plmn);
return peek_l3_ul_nas_cs(map, nas_pdu, len, local_plmn);
}
static int peek_l3_ul_initial_ue(struct hnbgw_context_map *map, const RANAP_InitialUE_MessageIEs_t *ies)
{
struct osmo_plmn_id local_plmn;
if (g_hnbgw->config.plmn.mcc) {
/* The user has configured a PLMN */
local_plmn = g_hnbgw->config.plmn;
} else {
/* The user has not configured a PLMN, guess from the InitialUE message's LAI IE's PLMN */
if (ies->lai.pLMNidentity.size < 3) {
LOGP(DCN, LOGL_ERROR, "Missing PLMN in RANAP InitialUE message\n");
return -EINVAL;
}
osmo_plmn_from_bcd(ies->lai.pLMNidentity.buf, &local_plmn);
}
return peek_l3_ul_nas(map, ies->nas_pdu.buf, ies->nas_pdu.size, &local_plmn);
}
/* Extract a Layer 3 message (NAS PDU) from the uplink RANAP message, and put the info obtained in map->l3.
* This is relevant for CN pooling, to decide which CN link to map the RUA context to. */
int hnbgw_peek_l3_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg)
{
ranap_message *message = hnbgw_decode_ranap_cn_co(ranap_msg);
if (!message) {
LOGP(DCN, LOGL_ERROR, "Failed to decode RANAP PDU\n");
return -EINVAL;
}
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_InitialUE_Message:
return peek_l3_ul_initial_ue(map, &message->msg.initialUE_MessageIEs);
default:
LOGP(DCN, LOGL_ERROR, "unexpected RANAP PDU in RUA Connect message: %s\n",
get_value_string(ranap_procedure_code_vals, message->procedureCode));
return -ENOTSUP;
}
}

184
src/osmo-hnbgw/hnbgw_pfcp.c Normal file
View File

@@ -0,0 +1,184 @@
/* PFCP link to UPF for osmo-hnbgw */
/* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/stat_item.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/pfcp/pfcp_cp_peer.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/ps_rab_fsm.h>
#include <osmocom/hnbgw/hnbgw_pfcp.h>
static const struct osmo_stat_item_desc hnbgw_upf_stat_item_description[] = {
[HNBGW_UPF_STAT_ASSOCIATED] = { "pfcp_associated", "Associated to UPF through PFCP", OSMO_STAT_ITEM_NO_UNIT, 16, 0},
};
static const struct osmo_stat_item_group_desc hnbgw_upf_statg_desc = {
"upf",
"UPF Peer Statistics",
OSMO_STATS_CLASS_PEER,
ARRAY_SIZE(hnbgw_upf_stat_item_description),
hnbgw_upf_stat_item_description,
};
static void pfcp_set_msg_ctx(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req)
{
if (!m->ctx.peer_fi)
osmo_pfcp_cp_peer_set_msg_ctx(g_hnbgw->pfcp.cp_peer, m);
/* If this is a response to an earlier request, just take the msg context from the request message.
* In osmo-hnbgw, a session_fi always points at a ps_rab FSM. */
if (!m->ctx.session_fi && req && req->ctx.session_fi)
ps_rab_pfcp_set_msg_ctx(req->ctx.session_fi->priv, m);
/* Otherwise iterate all PS RABs in all hnb contexts matching on the SEID. This rarely happens at all: for tx,
* ps_rab_new_pfcp_msg_tx() already sets the msg ctx, and for rx, we only expect to receive PFCP Responses,
* which are handled above. The only time this will happen is when the UPF shuts down and sends a Deletion. */
if (!m->ctx.session_fi && m->h.seid_present && m->h.seid != 0) {
struct ps_rab *rab = ps_rab_find_by_seid(m->h.seid, m->rx);
if (rab)
ps_rab_pfcp_set_msg_ctx(rab, m);
}
}
static void pfcp_rx_msg(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req)
{
switch (m->h.message_type) {
/* We only expect responses to requests. Those are handled by osmo_pfcp_msg.ctx.resp_cb. */
/* TODO: handle graceful shutdown from UPF (Session Modification? Deletion?) */
default:
LOGP(DLPFCP, LOGL_ERROR, "rx unexpected PFCP message: %s\n",
osmo_pfcp_message_type_str(m->h.message_type));
return;
}
}
static void pfcp_cp_peer_assoc_cb(struct osmo_pfcp_cp_peer *cp_peer, bool associated)
{
LOGP(DLPFCP, LOGL_NOTICE, "PFCP Peer associated: %s\n", associated ? "true" : "false");
HNBGW_UPF_STAT_SET(HNBGW_UPF_STAT_ASSOCIATED, associated ? 1 : 0);
}
int hnbgw_pfcp_init(void)
{
struct osmo_pfcp_endpoint_cfg cfg;
struct osmo_pfcp_endpoint *ep;
struct osmo_sockaddr_str local_addr_str;
struct osmo_sockaddr_str upf_addr_str;
struct osmo_sockaddr upf_addr;
if (!hnb_gw_is_gtp_mapping_enabled()) {
LOGP(DLPFCP, LOGL_NOTICE, "No UPF configured, NOT setting up PFCP, NOT mapping GTP via UPF\n");
return 0;
}
LOGP(DLPFCP, LOGL_DEBUG, "%p cfg: pfcp remote-addr %s\n", g_hnbgw, g_hnbgw->config.pfcp.remote_addr);
if (!g_hnbgw->config.pfcp.local_addr) {
LOGP(DLPFCP, LOGL_ERROR, "Configuration error: missing local PFCP address, required for Node Id\n");
return -1;
}
g_hnbgw->pfcp.statg = osmo_stat_item_group_alloc(g_hnbgw, &hnbgw_upf_statg_desc, 0);
if (!g_hnbgw->pfcp.statg) {
LOGP(DLPFCP, LOGL_ERROR, "Failed creating UPF stats item group\n");
return -1;
}
cfg = (struct osmo_pfcp_endpoint_cfg){
.set_msg_ctx_cb = pfcp_set_msg_ctx,
.rx_msg_cb = pfcp_rx_msg,
};
/* Set up PFCP endpoint's local node id from local IP address. Parse address string into local_addr_str... */
if (osmo_sockaddr_str_from_str(&local_addr_str, g_hnbgw->config.pfcp.local_addr, g_hnbgw->config.pfcp.local_port)) {
LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP local IP: %s\n",
osmo_quote_str_c(OTC_SELECT, g_hnbgw->config.pfcp.local_addr, -1));
return -1;
}
/* ...and convert to osmo_sockaddr, write to ep->cfg */
if (osmo_sockaddr_str_to_sockaddr(&local_addr_str, &cfg.local_addr.u.sas)) {
LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP local IP: %s\n",
osmo_quote_str_c(OTC_SELECT, g_hnbgw->config.pfcp.local_addr, -1));
return -1;
}
/* also store the local addr as local Node ID */
if (osmo_pfcp_ie_node_id_from_osmo_sockaddr(&cfg.local_node_id, &cfg.local_addr)) {
LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP local IP: %s\n",
osmo_quote_str_c(OTC_SELECT, g_hnbgw->config.pfcp.local_addr, -1));
return -1;
}
g_hnbgw->pfcp.ep = ep = osmo_pfcp_endpoint_create(g_hnbgw, &cfg);
if (!ep) {
LOGP(DLPFCP, LOGL_ERROR, "Failed to allocate PFCP endpoint\n");
return -1;
}
/* Set up remote PFCP address to reach UPF at. First parse the string into upf_addr_str. */
if (osmo_sockaddr_str_from_str(&upf_addr_str, g_hnbgw->config.pfcp.remote_addr, g_hnbgw->config.pfcp.remote_port)) {
LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP remote IP: %s\n",
osmo_quote_str_c(OTC_SELECT, g_hnbgw->config.pfcp.remote_addr, -1));
return -1;
}
/* then convert upf_addr_str to osmo_sockaddr */
if (osmo_sockaddr_str_to_sockaddr(&upf_addr_str, &upf_addr.u.sas)) {
LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP remote IP: %s\n",
osmo_quote_str_c(OTC_SELECT, g_hnbgw->config.pfcp.remote_addr, -1));
return -1;
}
/* Start the socket */
if (osmo_pfcp_endpoint_bind(ep)) {
LOGP(DLPFCP, LOGL_ERROR, "Cannot bind PFCP endpoint\n");
return -1;
}
/* Associate with UPF */
g_hnbgw->pfcp.cp_peer = osmo_pfcp_cp_peer_alloc(g_hnbgw, ep, &upf_addr);
if (!g_hnbgw->pfcp.cp_peer) {
LOGP(DLPFCP, LOGL_ERROR, "Cannot allocate PFCP CP Peer FSM\n");
return -1;
}
if (osmo_pfcp_cp_peer_set_associated_cb(g_hnbgw->pfcp.cp_peer, pfcp_cp_peer_assoc_cb)) {
LOGP(DLPFCP, LOGL_ERROR, "Cannot Set PFCP CP Peer associated callback\n");
return -1;
}
if (osmo_pfcp_cp_peer_associate(g_hnbgw->pfcp.cp_peer)) {
LOGP(DLPFCP, LOGL_ERROR, "Cannot start PFCP CP Peer FSM\n");
return -1;
}
return 0;
}
void hnbgw_pfcp_release(void)
{
if (!hnb_gw_is_gtp_mapping_enabled())
return;
osmo_stat_item_group_free(g_hnbgw->pfcp.statg);
}

View File

@@ -1,4 +1,4 @@
/* hnb-gw specific code for RANAP */
/* hnb-gw specific code for RANAP, 3GPP TS 25.413 */
/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
* All Rights Reserved
@@ -18,10 +18,7 @@
*
*/
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
#include "config.h"
#include <unistd.h>
#include <errno.h>
@@ -29,14 +26,34 @@
#include "asn1helpers.h"
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
#include <osmocom/ranap/ranap_common.h>
#include <osmocom/ranap/ranap_common_ran.h>
#include <osmocom/ranap/ranap_common_cn.h>
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/ranap/ranap_msg_factory.h>
static int ranap_tx_reset_ack(struct hnb_context *hnb,
RANAP_CN_DomainIndicator_t domain)
#if ENABLE_PFCP
#include <osmocom/pfcp/pfcp_cp_peer.h>
#endif
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnb_persistent.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/mgw_fsm.h>
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
#include <osmocom/hnbgw/kpi.h>
/*****************************************************************************
* Processing of RANAP from the endpoint towards RAN (hNodeB), acting as CN
*****************************************************************************/
static int ranap_tx_udt_dl_reset_ack(struct hnb_context *hnb, RANAP_CN_DomainIndicator_t domain)
{
struct msgb *msg;
int rc;
@@ -52,7 +69,7 @@ static int ranap_tx_reset_ack(struct hnb_context *hnb,
return rc;
}
static int ranap_rx_init_reset(struct hnb_context *hnb, ANY_t *in)
static int ranap_rx_udt_ul_init_reset(struct hnb_context *hnb, ANY_t *in)
{
RANAP_ResetIEs_t ies;
int rc, is_ps = 0;
@@ -66,32 +83,38 @@ static int ranap_rx_init_reset(struct hnb_context *hnb, ANY_t *in)
LOGHNB(hnb, DRANAP, LOGL_INFO, "Rx RESET.req(%s,%s)\n", is_ps ? "ps" : "cs",
ranap_cause_str(&ies.cause));
HNBP_CTR_INC(hnb->persistent, is_ps ? HNB_CTR_RANAP_PS_RESET_REQ_UL : HNB_CTR_RANAP_CS_RESET_REQ_UL);
/* FIXME: Actually we have to wait for some guard time? */
/* FIXME: Reset all resources related to this HNB/RNC */
ranap_tx_reset_ack(hnb, ies.cN_DomainIndicator);
ranap_tx_udt_dl_reset_ack(hnb, ies.cN_DomainIndicator);
return 0;
}
static int ranap_rx_error_ind(struct hnb_context *hnb, ANY_t *in)
static int ranap_rx_udt_ul_error_ind(struct hnb_context *hnb, ANY_t *in)
{
RANAP_ErrorIndicationIEs_t ies;
int rc;
bool is_ps = false;
rc = ranap_decode_errorindicationies(&ies, in);
if (rc < 0)
return rc;
if (ies.cN_DomainIndicator == RANAP_CN_DomainIndicator_ps_domain)
is_ps = true;
if (ies.presenceMask & ERRORINDICATIONIES_RANAP_CAUSE_PRESENT) {
LOGHNB(hnb, DRANAP, LOGL_ERROR, "Rx ERROR.ind(%s)\n", ranap_cause_str(&ies.cause));
} else
LOGHNB(hnb, DRANAP, LOGL_ERROR, "Rx ERROR.ind\n");
HNBP_CTR_INC(hnb->persistent, is_ps ? HNB_CTR_RANAP_PS_ERR_IND_UL : HNB_CTR_RANAP_CS_ERR_IND_UL);
return 0;
}
static int ranap_rx_initiating_msg(struct hnb_context *hnb, RANAP_InitiatingMessage_t *imsg)
static int ranap_rx_udt_ul_initiating_msg(struct hnb_context *hnb, RANAP_InitiatingMessage_t *imsg)
{
int rc = 0;
@@ -105,12 +128,12 @@ static int ranap_rx_initiating_msg(struct hnb_context *hnb, RANAP_InitiatingMess
switch (imsg->procedureCode) {
case RANAP_ProcedureCode_id_Reset:
/* Reset request */
rc = ranap_rx_init_reset(hnb, &imsg->value);
rc = ranap_rx_udt_ul_init_reset(hnb, &imsg->value);
break;
case RANAP_ProcedureCode_id_OverloadControl: /* Overload ind */
break;
case RANAP_ProcedureCode_id_ErrorIndication: /* Error ind */
rc = ranap_rx_error_ind(hnb, &imsg->value);
rc = ranap_rx_udt_ul_error_ind(hnb, &imsg->value);
break;
case RANAP_ProcedureCode_id_ResetResource: /* request */
case RANAP_ProcedureCode_id_InformationTransfer:
@@ -128,7 +151,7 @@ static int ranap_rx_initiating_msg(struct hnb_context *hnb, RANAP_InitiatingMess
return rc;
}
static int ranap_rx_successful_msg(struct hnb_context *hnb, RANAP_SuccessfulOutcome_t *imsg)
static int ranap_rx_udt_ul_successful_msg(struct hnb_context *hnb, RANAP_SuccessfulOutcome_t *imsg)
{
/* according tot the spec, we can primarily receive Overload,
* Reset, Reset ACK, Error Indication, reset Resource, Reset
@@ -158,16 +181,16 @@ static int ranap_rx_successful_msg(struct hnb_context *hnb, RANAP_SuccessfulOutc
static int _hnbgw_ranap_rx(struct hnb_context *hnb, RANAP_RANAP_PDU_t *pdu)
static int _hnbgw_ranap_rx_udt_ul(struct hnb_context *hnb, RANAP_RANAP_PDU_t *pdu)
{
int rc = 0;
switch (pdu->present) {
case RANAP_RANAP_PDU_PR_initiatingMessage:
rc = ranap_rx_initiating_msg(hnb, &pdu->choice.initiatingMessage);
rc = ranap_rx_udt_ul_initiating_msg(hnb, &pdu->choice.initiatingMessage);
break;
case RANAP_RANAP_PDU_PR_successfulOutcome:
rc = ranap_rx_successful_msg(hnb, &pdu->choice.successfulOutcome);
rc = ranap_rx_udt_ul_successful_msg(hnb, &pdu->choice.successfulOutcome);
break;
case RANAP_RANAP_PDU_PR_unsuccessfulOutcome:
LOGHNB(hnb, DRANAP, LOGL_NOTICE, "Received unsupported RANAP "
@@ -183,8 +206,8 @@ static int _hnbgw_ranap_rx(struct hnb_context *hnb, RANAP_RANAP_PDU_t *pdu)
return rc;
}
int hnbgw_ranap_rx(struct msgb *msg, uint8_t *data, size_t len)
/* receive a RNAAP Unit-Data message in uplink direction */
int hnbgw_ranap_rx_udt_ul(struct msgb *msg, uint8_t *data, size_t len)
{
RANAP_RANAP_PDU_t _pdu, *pdu = &_pdu;
struct hnb_context *hnb = msg->dst;
@@ -199,11 +222,460 @@ int hnbgw_ranap_rx(struct msgb *msg, uint8_t *data, size_t len)
return -1;
}
rc = _hnbgw_ranap_rx(hnb, pdu);
rc = _hnbgw_ranap_rx_udt_ul(hnb, pdu);
return rc;
}
static int destruct_ranap_cn_rx_co_ies(ranap_message *ranap_message_p)
{
ranap_cn_rx_co_free(ranap_message_p);
return 0;
}
/* Decode UL RANAP message with convenient memory freeing: just talloc_free() the returned pointer..
* Allocate a ranap_message from OTC_SELECT, decode RANAP msgb into it, attach a talloc destructor that calls
* ranap_cn_rx_co_free() upon talloc_free(), and return the decoded ranap_message. */
ranap_message *hnbgw_decode_ranap_cn_co(struct msgb *ranap_msg)
{
int rc;
ranap_message *message;
if (!msg_has_l2_data(ranap_msg))
return NULL;
message = talloc_zero(OTC_SELECT, ranap_message);
rc = ranap_cn_rx_co_decode2(message, msgb_l2(ranap_msg), msgb_l2len(ranap_msg));
if (rc != 0) {
talloc_free(message);
return NULL;
}
talloc_set_destructor(message, destruct_ranap_cn_rx_co_ies);
return message;
}
/* Process a received RANAP PDU through SCCP DATA.ind coming from CN (MSC/SGSN)
* ranap_msg is owned by OTC_SELECT. */
int hnbgw_ranap_rx_data_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg)
{
OSMO_ASSERT(map);
OSMO_ASSERT(msg_has_l2_data(ranap_msg));
ranap_message *message = hnbgw_decode_ranap_cn_co(ranap_msg);
if (message) {
LOG_MAP(map, DHNB, LOGL_DEBUG, "rx from RUA: RANAP %s\n",
get_value_string(ranap_procedure_code_vals, message->procedureCode));
kpi_ranap_process_ul(map, message);
if (!map->is_ps) {
/* See if it is a RAB Assignment Response message from RUA to SCCP, where we need to change the user plane
* information, for RTP mapping via MGW, or GTP mapping via UPF. */
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* mgw_fsm_handle_rab_ass_resp() may take ownership of "ranap_msg" (prim->oph) and "message" */
return mgw_fsm_handle_cs_rab_ass_resp(map, ranap_msg, message);
}
} else {
#if ENABLE_PFCP
if (hnb_gw_is_gtp_mapping_enabled()) {
/* map->is_ps == true and PFCP is enabled in osmo-hnbgw.cfg */
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* ps_rab_ass_fsm() may take ownership of "ranap_msg" (prim->oph) and "message" */
return hnbgw_gtpmap_rx_rab_ass_resp(map, ranap_msg, message);
}
}
#endif
}
}
/* It was not a RAB Assignment Response that needed to be intercepted. Forward as-is to SCCP. */
return map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg);
}
/*****************************************************************************
* Processing of RANAP from the endpoint towards CN (MSC/SGSN), acting as RAN
*****************************************************************************/
static int cn_ranap_rx_reset_cmd(struct hnbgw_cnlink *cnlink,
const struct osmo_scu_unitdata_param *unitdata,
RANAP_InitiatingMessage_t *imsg)
{
RANAP_CN_DomainIndicator_t domain;
RANAP_ResetIEs_t ies;
int rc;
rc = ranap_decode_reseties(&ies, &imsg->value);
domain = ies.cN_DomainIndicator;
ranap_free_reseties(&ies);
if (rc) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Rx RESET: cannot decode IEs\n");
return -1;
}
if (cnlink->pool->domain != domain) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Rx RESET indicates domain %s, but this is %s on domain %s\n",
ranap_domain_name(domain), cnlink->name, ranap_domain_name(cnlink->pool->domain));
return -1;
}
cnlink_rx_reset_cmd(cnlink);
return 0;
}
static const struct value_string ranap_paging_area_id_names[] = {
{ RANAP_PagingAreaID_PR_NOTHING, "NOTHING" },
{ RANAP_PagingAreaID_PR_lAI, "LAI" },
{ RANAP_PagingAreaID_PR_rAI, "RAI" },
{ 0, NULL }
};
static bool hnb_paging_area_id_match(const struct hnb_context *hnb,
enum RANAP_PagingAreaID_PR t,
const struct osmo_routing_area_id *rai)
{
switch (t) {
case RANAP_PagingAreaID_PR_NOTHING:
return true;
case RANAP_PagingAreaID_PR_rAI:
if (hnb->id.rac != rai->rac)
return false;
/* fall through */
case RANAP_PagingAreaID_PR_lAI:
if (hnb->id.lac != rai->lac.lac)
return false;
if (osmo_plmn_cmp(&hnb->id.plmn, &rai->lac.plmn))
return false;
/* fall through */
}
return true;
}
static int lai_from_RANAP_RANAP_LAI(struct osmo_location_area_id *lai, const RANAP_LAI_t *ranap_lai)
{
if (ranap_lai->pLMNidentity.size < 3)
return -EINVAL;
osmo_plmn_from_bcd(ranap_lai->pLMNidentity.buf, &lai->plmn);
lai->lac = asn1str_to_u16(&ranap_lai->lAC);
return 0;
}
static int rai_from_RANAP_PagingAreaID(struct osmo_routing_area_id *rai, const RANAP_PagingAreaID_t *paid)
{
switch (paid->present) {
case RANAP_PagingAreaID_PR_NOTHING:
break;
case RANAP_PagingAreaID_PR_lAI:
return lai_from_RANAP_RANAP_LAI(&rai->lac, &paid->choice.lAI);
case RANAP_PagingAreaID_PR_rAI:
rai->rac = asn1str_to_u8(&paid->choice.rAI.rAC);
return lai_from_RANAP_RANAP_LAI(&rai->lac, &paid->choice.rAI.lAI);
}
return 0;
}
/* 3GPP TS 25.413 8.15 */
static int cn_ranap_rx_paging_cmd(struct hnbgw_cnlink *cnlink,
RANAP_InitiatingMessage_t *imsg,
const uint8_t *data, unsigned int len)
{
RANAP_PagingIEs_t ies;
RANAP_CN_DomainIndicator_t domain;
const char *errmsg;
struct hnb_context *hnb;
bool is_ps = cnlink->pool->domain == DOMAIN_PS;
bool forwarded = false;
bool page_area_present;
struct osmo_routing_area_id page_rai = {};
if (ranap_decode_pagingies(&ies, &imsg->value) < 0) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR,
"Rx Paging from CN: decoding RANAP IEs failed\n");
return -1;
}
domain = ies.cN_DomainIndicator;
page_area_present = (ies.presenceMask & PAGINGIES_RANAP_PAGINGAREAID_PRESENT);
if (cnlink->pool->domain != domain) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR,
"Rx Paging from CN: message indicates domain %s, but cnlink is on domain %s\n",
ranap_domain_name(domain),
ranap_domain_name(cnlink->pool->domain));
goto free_ies_ret;
}
if (page_area_present) {
if (rai_from_RANAP_PagingAreaID(&page_rai, &ies.pagingAreaID) < 0) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR,
"Rx Paging from CN: decoding RANAP IE Paging Area ID failed, broadcasting to all HNBs\n");
/* fail over to broadcast... */
page_area_present = false;
}
}
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG,
"Rx Paging from CN: %s PagingAreaID: %s %s\n",
ranap_domain_name(domain),
page_area_present ?
get_value_string(ranap_paging_area_id_names, ies.pagingAreaID.present) :
"NOT_PRESENT",
osmo_rai_name2(&page_rai)
);
llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) {
if (!hnb->hnb_registered)
continue;
if (page_area_present &&
!hnb_paging_area_id_match(hnb, ies.pagingAreaID.present, &page_rai))
continue;
if (is_ps)
HNBP_CTR_INC(hnb->persistent, HNB_CTR_PS_PAGING_ATTEMPTED);
else
HNBP_CTR_INC(hnb->persistent, HNB_CTR_CS_PAGING_ATTEMPTED);
if (rua_tx_udt(hnb, data, len) == 0)
forwarded = true;
}
if (forwarded) {
/* If Paging command was forwarded anywhere, store a record for it, to match paging response: */
errmsg = cnlink_paging_add_ranap(cnlink, &ies);
if (errmsg) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR,
"Rx Paging from CN: %s. Skip storing paging record."
" Later on, the Paging Response may be forwarded to the wrong CN peer.\n",
errmsg);
goto free_ies_ret;
}
}
ranap_free_pagingies(&ies);
return 0;
free_ies_ret:
ranap_free_pagingies(&ies);
return -1;
}
static int ranap_rx_udt_dl_initiating_msg(struct hnbgw_cnlink *cnlink,
const struct osmo_scu_unitdata_param *unitdata,
RANAP_InitiatingMessage_t *imsg,
const uint8_t *data, unsigned int len)
{
switch (imsg->procedureCode) {
case RANAP_ProcedureCode_id_Reset:
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_RESET);
return cn_ranap_rx_reset_cmd(cnlink, unitdata, imsg);
case RANAP_ProcedureCode_id_Paging:
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_PAGING);
return cn_ranap_rx_paging_cmd(cnlink, imsg, data, len);
case RANAP_ProcedureCode_id_OverloadControl: /* Overload ind */
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_OVERLOAD_IND);
break;
case RANAP_ProcedureCode_id_ErrorIndication: /* Error ind */
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_ERROR_IND);
break;
case RANAP_ProcedureCode_id_ResetResource: /* request */
case RANAP_ProcedureCode_id_InformationTransfer:
case RANAP_ProcedureCode_id_DirectInformationTransfer:
case RANAP_ProcedureCode_id_UplinkInformationExchange:
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_UNSUPPORTED);
LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP "
"Procedure %ld from CN, ignoring\n", imsg->procedureCode);
break;
default:
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_UNKNOWN);
LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP "
"Procedure %ld from CN, ignoring\n", imsg->procedureCode);
break;
}
return 0;
}
static int cn_ranap_rx_reset_ack(struct hnbgw_cnlink *cnlink,
RANAP_SuccessfulOutcome_t *omsg)
{
RANAP_CN_DomainIndicator_t domain;
RANAP_ResetAcknowledgeIEs_t ies;
int rc;
rc = ranap_decode_resetacknowledgeies(&ies, &omsg->value);
domain = ies.cN_DomainIndicator;
ranap_free_resetacknowledgeies(&ies);
if (rc) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Rx RESET ACK: cannot decode IEs\n");
return -1;
}
if (cnlink->pool->domain != domain) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Rx RESET ACK indicates domain %s, but this is %s on domain %s\n",
ranap_domain_name(domain), cnlink->name, ranap_domain_name(cnlink->pool->domain));
return -1;
}
cnlink_rx_reset_ack(cnlink);
return 0;
}
static int ranap_rx_udt_dl_successful_msg(struct hnbgw_cnlink *cnlink,
RANAP_SuccessfulOutcome_t *omsg)
{
switch (omsg->procedureCode) {
case RANAP_ProcedureCode_id_Reset: /* Reset acknowledge */
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_RESET);
return cn_ranap_rx_reset_ack(cnlink, omsg);
case RANAP_ProcedureCode_id_ResetResource: /* response */
case RANAP_ProcedureCode_id_InformationTransfer:
case RANAP_ProcedureCode_id_DirectInformationTransfer:
case RANAP_ProcedureCode_id_UplinkInformationExchange:
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_UNSUPPORTED);
LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP "
"Procedure %ld from CN, ignoring\n", omsg->procedureCode);
break;
default:
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_UNKNOWN);
LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP "
"Procedure %ld from CN, ignoring\n", omsg->procedureCode);
break;
}
return 0;
}
static int _hnbgw_ranap_rx_udt_dl(struct hnbgw_cnlink *cnlink,
const struct osmo_scu_unitdata_param *unitdata,
RANAP_RANAP_PDU_t *pdu, const uint8_t *data, unsigned int len)
{
int rc;
switch (pdu->present) {
case RANAP_RANAP_PDU_PR_initiatingMessage:
rc = ranap_rx_udt_dl_initiating_msg(cnlink, unitdata, &pdu->choice.initiatingMessage, data, len);
break;
case RANAP_RANAP_PDU_PR_successfulOutcome:
rc = ranap_rx_udt_dl_successful_msg(cnlink, &pdu->choice.successfulOutcome);
break;
case RANAP_RANAP_PDU_PR_unsuccessfulOutcome:
LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP "
"unsuccessful outcome procedure %ld from CN, ignoring\n",
pdu->choice.unsuccessfulOutcome.procedureCode);
rc = -ENOTSUP;
break;
default:
LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP "
"presence %u from CN, ignoring\n", pdu->present);
rc = -EINVAL;
break;
}
return rc;
}
int hnbgw_ranap_rx_udt_dl(struct hnbgw_cnlink *cnlink, const struct osmo_scu_unitdata_param *unitdata,
const uint8_t *data, unsigned int len)
{
RANAP_RANAP_PDU_t _pdu, *pdu = &_pdu;
asn_dec_rval_t dec_ret;
int rc;
memset(pdu, 0, sizeof(*pdu));
dec_ret = aper_decode(NULL, &asn_DEF_RANAP_RANAP_PDU, (void **) &pdu,
data, len, 0, 0);
if (dec_ret.code != RC_OK) {
LOGP(DRANAP, LOGL_ERROR, "Error in RANAP ASN.1 decode\n");
return -1;
}
rc = _hnbgw_ranap_rx_udt_dl(cnlink, unitdata, pdu, data, len);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RANAP_PDU, pdu);
return rc;
}
static int destruct_ranap_ran_rx_co_ies(ranap_message *ranap_message_p)
{
ranap_ran_rx_co_free(ranap_message_p);
return 0;
}
/* Decode DL RANAP message with convenient memory freeing: just talloc_free() the returned pointer..
* Allocate a ranap_message from OTC_SELECT, decode RANAP msgb into it, attach a talloc destructor that calls
* ranap_cn_rx_co_free() upon talloc_free(), and return the decoded ranap_message. */
static ranap_message *hnbgw_decode_ranap_ran_co(struct msgb *ranap_msg)
{
int rc;
ranap_message *message;
if (!msg_has_l2_data(ranap_msg))
return NULL;
message = talloc_zero(OTC_SELECT, ranap_message);
rc = ranap_ran_rx_co_decode(NULL, message, msgb_l2(ranap_msg), msgb_l2len(ranap_msg));
if (rc != 0) {
talloc_free(message);
return NULL;
}
talloc_set_destructor(message, destruct_ranap_ran_rx_co_ies);
return message;
}
/* Process a received RANAP PDU through SCCP DATA.ind coming from CN (MSC/SGSN)
* ranap_msg is owned by OTC_SELECT. */
int hnbgw_ranap_rx_data_dl(struct hnbgw_context_map *map, struct msgb *ranap_msg)
{
OSMO_ASSERT(map);
OSMO_ASSERT(msg_has_l2_data(ranap_msg));
/* See if it is a RAB Assignment Request message from SCCP to RUA, where we need to change the user plane
* information, for RTP mapping via MGW, or GTP mapping via UPF. */
ranap_message *message = hnbgw_decode_ranap_ran_co(ranap_msg);
if (message) {
LOG_MAP(map, DCN, LOGL_DEBUG, "rx from SCCP: RANAP %s\n",
get_value_string(ranap_procedure_code_vals, message->procedureCode));
kpi_ranap_process_dl(map, message);
if (!map->is_ps) {
/* Circuit-Switched. Set up mapping of RTP ports via MGW */
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* mgw_fsm_alloc_and_handle_rab_ass_req() takes ownership of (ranap) message */
return handle_cs_rab_ass_req(map, ranap_msg, message);
case RANAP_ProcedureCode_id_Iu_Release:
/* Any IU Release will terminate the MGW FSM, the message itsself is not passed to the
* FSM code. It is just forwarded normally by map_rua_tx_dt() below. */
mgw_fsm_release(map);
break;
}
#if ENABLE_PFCP
} else {
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* If a UPF is configured, handle the RAB Assignment via ps_rab_ass_fsm, and replace the
* GTP F-TEIDs in the RAB Assignment message before passing it on to RUA. */
if (hnb_gw_is_gtp_mapping_enabled()) {
LOG_MAP(map, DCN, LOGL_DEBUG,
"RAB Assignment: setting up GTP tunnel mapping via UPF %s\n",
osmo_sockaddr_to_str_c(OTC_SELECT, osmo_pfcp_cp_peer_get_remote_addr(g_hnbgw->pfcp.cp_peer)));
return hnbgw_gtpmap_rx_rab_ass_req(map, ranap_msg, message);
}
/* If no UPF is configured, directly forward the message as-is (no GTP mapping). */
LOG_MAP(map, DCN, LOGL_DEBUG, "RAB Assignment: no UPF configured, forwarding as-is\n");
break;
case RANAP_ProcedureCode_id_Iu_Release:
/* Any IU Release will terminate the MGW FSM, the message itsself is not passed to the
* FSM code. It is just forwarded normally by map_rua_tx_dt() below. */
hnbgw_gtpmap_release(map);
break;
}
#endif
}
}
/* It was not a RAB Assignment Request that needed to be intercepted. Forward as-is to RUA. */
return map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, ranap_msg);
}
int hnbgw_ranap_init(void)
{
return 0;

View File

@@ -1,4 +1,4 @@
/* hnb-gw specific code for RUA (Ranap User Adaption) */
/* hnb-gw specific code for RUA (Ranap User Adaption), 3GPP TS 25.468 */
/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
* All Rights Reserved
@@ -18,6 +18,7 @@
*
*/
#include "config.h"
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
@@ -32,30 +33,26 @@
#include "asn1helpers.h"
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnb_persistent.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/hnbgw_ranap.h>
#include <osmocom/rua/rua_common.h>
#include <osmocom/rua/rua_ies_defs.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbap/HNBAP_CN-DomainIndicator.h>
static const char *cn_domain_indicator_to_str(RUA_CN_DomainIndicator_t cN_DomainIndicator)
{
switch (cN_DomainIndicator) {
case RUA_CN_DomainIndicator_cs_domain:
return "IuCS";
case RUA_CN_DomainIndicator_ps_domain:
return "IuPS";
default:
return "(unknown-domain)";
}
}
static int hnbgw_rua_tx(struct hnb_context *ctx, struct msgb *msg)
{
if (!msg)
return -EINVAL;
if (!ctx || !ctx->conn) {
LOGHNB(ctx, DRUA, LOGL_ERROR, "RUA context to this HNB is not connected, cannot transmit message\n");
return -ENOTCONN;
}
msgb_sctp_ppid(msg) = IUH_PPI_RUA;
osmo_stream_srv_send(ctx->conn, msg);
@@ -87,6 +84,7 @@ int rua_tx_udt(struct hnb_context *hnb, const uint8_t *data, unsigned int len)
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RUA_ConnectionlessTransfer, &out);
LOGHNB(hnb, DRUA, LOGL_DEBUG, "transmitting RUA payload of %u bytes\n", msgb_length(msg));
HNBP_CTR_INC(hnb->persistent, HNB_CTR_RUA_UDT_DL);
return hnbgw_rua_tx(hnb, msg);
}
@@ -122,8 +120,9 @@ int rua_tx_dt(struct hnb_context *hnb, int is_ps, uint32_t context_id,
&out);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RUA_DirectTransfer, &out);
LOGHNB(hnb, DRUA, LOGL_DEBUG, "transmitting RUA (cn=%s) payload of %u bytes\n",
LOGHNB(hnb, DRUA, LOGL_DEBUG, "transmitting RUA DirectTransfer (cn=%s) payload of %u bytes\n",
is_ps ? "ps" : "cs", msgb_length(msg));
HNBP_CTR_INC(hnb->persistent, is_ps ? HNB_CTR_RUA_PS_DT_DL : HNB_CTR_RUA_CS_DT_DL);
return hnbgw_rua_tx(hnb, msg);
}
@@ -150,8 +149,6 @@ int rua_tx_disc(struct hnb_context *hnb, int is_ps, uint32_t context_id,
ies.ranaP_Message.size = len;
}
/* FIXME: msgb_free(msg)? ownership not yet clear */
memset(&out, 0, sizeof(out));
rc = rua_encode_disconnecties(&out, &ies);
if (rc < 0)
@@ -163,38 +160,95 @@ int rua_tx_disc(struct hnb_context *hnb, int is_ps, uint32_t context_id,
&out);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RUA_Disconnect, &out);
LOGHNB(hnb, DRUA, LOGL_DEBUG, "transmitting RUA (cn=%s) payload of %u bytes\n",
is_ps ? "ps" : "cs", msgb_length(msg));
LOGHNB(hnb, DRUA, LOGL_DEBUG, "transmitting RUA Disconnect (cn=%s) payload of %u bytes\n",
is_ps ? "ps" : "cs", msgb_length(msg));
HNBP_CTR_INC(hnb->persistent, is_ps ? HNB_CTR_RUA_PS_DISCONNECT_DL : HNB_CTR_RUA_CS_DISCONNECT_DL);
return hnbgw_rua_tx(hnb, msg);
}
/* Send Disconnect to RUA without RANAP data */
static void rua_tx_disc_conn_fail(struct hnb_context *hnb, bool is_ps, uint32_t context_id)
{
RUA_Cause_t rua_cause = {
.present = RUA_Cause_PR_radioNetwork,
.choice.radioNetwork = RUA_CauseRadioNetwork_connect_failed,
};
LOG_HNBP(hnb->persistent, LOGL_INFO, "Tx RUA Disconnect\n");
/* forward a RUA message to the SCCP User API to SCCP */
if (rua_tx_disc(hnb, is_ps, context_id, &rua_cause, NULL, 0))
LOG_HNBP(hnb->persistent, LOGL_ERROR, "Failed to send Disconnect to RUA\n");
}
static struct value_string rua_procedure_code_names[] = {
{ RUA_ProcedureCode_id_Connect, "Connect" },
{ RUA_ProcedureCode_id_DirectTransfer, "DirectTransfer" },
{ RUA_ProcedureCode_id_Disconnect, "Disconnect" },
{ RUA_ProcedureCode_id_ConnectionlessTransfer, "ConnectionlessTransfer" },
{ RUA_ProcedureCode_id_ErrorIndication, "ErrorIndication" },
{ RUA_ProcedureCode_id_privateMessage, "PrivateMessage" },
{}
};
static inline const char *rua_procedure_code_name(enum RUA_ProcedureCode val)
{
return get_value_string(rua_procedure_code_names, val);
}
static struct hnbgw_context_map *create_context_map(struct hnb_context *hnb, uint32_t rua_ctx_id, bool is_ps,
struct msgb *ranap_msg)
{
struct hnbgw_context_map *map;
struct hnbgw_cnlink *cnlink;
/* Establish a new context map. From the RUA Connect, extract a mobile identity, if any, and select a CN link
* based on an NRI found in the mobile identity, if any. */
/* Allocate a map for logging context */
map = context_map_alloc(hnb, rua_ctx_id, is_ps);
OSMO_ASSERT(map);
if (hnbgw_peek_l3_ul(map, ranap_msg))
LOGP(DCN, LOGL_NOTICE, "Failed to extract Mobile Identity from RUA Connect message's RANAP payload\n");
/* map->l3 now contains all the interesting information from the NAS PDU, if any.
* If no useful information could be decoded, still continue to select a hopefully adequate link by round robin.
*/
cnlink = hnbgw_cnlink_select(map);
if (!cnlink) {
LOG_MAP(map, DCN, LOGL_ERROR, "Failed to select %s link\n", is_ps ? "IuPS" : "IuCS");
context_map_free(map);
return NULL;
}
if (context_map_set_cnlink(map, cnlink)) {
LOG_MAP(map, DCN, LOGL_ERROR, "Failed to establish link to %s\n", cnlink->name);
context_map_free(map);
return NULL;
}
return map;
}
/* dispatch a RUA connection-oriented message received from a HNB to a context mapping's RUA FSM, so that it is
* forwarded to the CN via SCCP connection-oriented messages.
* Connectionless messages are handled in hnbgw_ranap_rx_udt_ul() instead, not here. */
static int rua_to_scu(struct hnb_context *hnb,
RUA_CN_DomainIndicator_t cN_DomainIndicator,
enum osmo_scu_prim_type type,
enum RUA_ProcedureCode rua_procedure,
uint32_t context_id, uint32_t cause,
const uint8_t *data, unsigned int len)
{
struct msgb *msg;
struct osmo_scu_prim *prim;
struct msgb *ranap_msg = NULL;
struct hnbgw_context_map *map = NULL;
struct hnbgw_cnlink *cn = hnb->gw->sccp.cnlink;
struct osmo_sccp_addr *remote_addr;
bool is_ps;
bool release_context_map = false;
int rc;
switch (cN_DomainIndicator) {
case RUA_CN_DomainIndicator_cs_domain:
remote_addr = &hnb->gw->sccp.iucs_remote_addr;
is_ps = false;
break;
case RUA_CN_DomainIndicator_ps_domain:
remote_addr = &hnb->gw->sccp.iups_remote_addr;
is_ps = true;
break;
default:
@@ -202,75 +256,78 @@ static int rua_to_scu(struct hnb_context *hnb,
return -1;
}
if (!cn) {
LOGHNB(hnb, DRUA, LOGL_NOTICE, "CN=NULL, discarding message\n");
return 0;
}
msg = msgb_alloc(1500, "rua_to_sccp");
prim = (struct osmo_scu_prim *) msgb_put(msg, sizeof(*prim));
osmo_prim_init(&prim->oph, SCCP_SAP_USER, type, PRIM_OP_REQUEST, msg);
switch (type) {
case OSMO_SCU_PRIM_N_UNITDATA:
LOGHNB(hnb, DRUA, LOGL_DEBUG, "rua_to_scu() %s to %s, rua_ctx_id %u (unitdata, no scu_conn_id)\n",
cn_domain_indicator_to_str(cN_DomainIndicator), osmo_sccp_addr_dump(remote_addr), context_id);
break;
default:
map = context_map_alloc_by_hnb(hnb, context_id, is_ps, cn);
OSMO_ASSERT(map);
LOGHNB(hnb, DRUA, LOGL_DEBUG, "rua_to_scu() %s to %s, rua_ctx_id %u scu_conn_id %u\n",
cn_domain_indicator_to_str(cN_DomainIndicator), osmo_sccp_addr_dump(remote_addr),
map->rua_ctx_id, map->scu_conn_id);
}
/* add primitive header */
switch (type) {
case OSMO_SCU_PRIM_N_CONNECT:
prim->u.connect.called_addr = *remote_addr;
prim->u.connect.calling_addr = cn->gw->sccp.local_addr;
prim->u.connect.sccp_class = 2;
prim->u.connect.conn_id = map->scu_conn_id;
/* Two separate logs because of osmo_sccp_addr_dump(). */
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA to SCCP N_CONNECT: called_addr:%s\n",
osmo_sccp_addr_dump(&prim->u.connect.called_addr));
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA to SCCP N_CONNECT: calling_addr:%s\n",
osmo_sccp_addr_dump(&prim->u.connect.calling_addr));
break;
case OSMO_SCU_PRIM_N_DATA:
prim->u.data.conn_id = map->scu_conn_id;
break;
case OSMO_SCU_PRIM_N_DISCONNECT:
prim->u.disconnect.conn_id = map->scu_conn_id;
prim->u.disconnect.cause = cause;
release_context_map = true;
break;
case OSMO_SCU_PRIM_N_UNITDATA:
prim->u.unitdata.called_addr = *remote_addr;
prim->u.unitdata.calling_addr = cn->gw->sccp.local_addr;
/* Two separate logs because of osmo_sccp_addr_dump(). */
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA to SCCP N_UNITDATA: called_addr:%s\n",
osmo_sccp_addr_dump(&prim->u.unitdata.called_addr));
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA to SCCP N_UNITDATA: calling_addr:%s\n",
osmo_sccp_addr_dump(&prim->u.unitdata.calling_addr));
break;
default:
return -EINVAL;
}
/* add optional data section, if needed */
/* If there is RANAP data, include it in the msgb. In RUA there is always data in practice, but theoretically it
* could be an empty Connect or Disconnect. */
if (data && len) {
msg->l2h = msgb_put(msg, len);
memcpy(msg->l2h, data, len);
/* According to API doc of map_rua_fsm_event: allocate msgb for RANAP data from OTC_SELECT, reserve
* headroom for an osmo_scu_prim. Point l2h at the RANAP data. */
ranap_msg = hnbgw_ranap_msg_alloc("RANAP_from_RUA");
ranap_msg->l2h = msgb_put(ranap_msg, len);
memcpy(ranap_msg->l2h, data, len);
}
rc = osmo_sccp_user_sap_down(cn->sccp_user, &prim->oph);
map = context_map_find_by_rua_ctx_id(hnb, context_id, is_ps);
if (map && release_context_map)
context_map_deactivate(map);
switch (rua_procedure) {
case RUA_ProcedureCode_id_Connect:
/* A Connect message can only be the first message for an unused RUA context */
if (map) {
/* Already established this RUA context. But then how can it be a Connect message. */
LOGHNB(hnb, DRUA, LOGL_NOTICE, "rx RUA %s for already active RUA context %u\n",
rua_procedure_code_name(rua_procedure), context_id);
return -EINVAL;
}
/* ok, this RUA context does not exist yet, so create one. */
map = create_context_map(hnb, context_id, is_ps, ranap_msg);
if (!map) {
LOGHNB(hnb, DRUA, LOGL_ERROR,
"Failed to create context map for %s: rx RUA %s with %u bytes RANAP data\n",
is_ps ? "IuPS" : "IuCS", rua_procedure_code_name(rua_procedure), data ? len : 0);
rua_tx_disc_conn_fail(hnb, is_ps, context_id);
return -EINVAL;
}
break;
return rc;
case RUA_ProcedureCode_id_Disconnect:
/* For RUA Disconnect, do not spam the ERROR log. It is just a stray Disconnect, no harm done.
* Context: some CN are known to rapidly tear down SCCP without waiting for RUA to disconnect gracefully
* (IU Release Complete). Such CN would cause ERROR logging for each and every released context map. */
if (!map) {
LOGHNB(hnb, DRUA, LOGL_DEBUG, "rx RUA %s for unknown RUA context %u\n",
rua_procedure_code_name(rua_procedure), context_id);
return -EINVAL;
}
break;
default:
/* Any message other than Connect must have a valid RUA context */
if (!map) {
LOGHNB(hnb, DRUA, LOGL_NOTICE, "rx RUA %s for unknown RUA context %u\n",
rua_procedure_code_name(rua_procedure), context_id);
rua_tx_disc_conn_fail(hnb, is_ps, context_id);
return -EINVAL;
}
break;
}
LOG_MAP(map, DRUA, LOGL_DEBUG, "rx RUA %s with %u bytes RANAP data\n",
rua_procedure_code_name(rua_procedure), data ? len : 0);
switch (rua_procedure) {
case RUA_ProcedureCode_id_Connect:
return map_rua_dispatch(map, MAP_RUA_EV_RX_CONNECT, ranap_msg);
case RUA_ProcedureCode_id_DirectTransfer:
return map_rua_dispatch(map, MAP_RUA_EV_RX_DIRECT_TRANSFER, ranap_msg);
case RUA_ProcedureCode_id_Disconnect:
return map_rua_dispatch(map, MAP_RUA_EV_RX_DISCONNECT, ranap_msg);
default:
/* No caller may ever pass a different RUA procedure code */
OSMO_ASSERT(false);
}
}
static uint32_t rua_to_scu_cause(RUA_Cause_t *in)
@@ -349,10 +406,12 @@ static int rua_rx_init_connect(struct msgb *msg, ANY_t *in)
context_id = asn1bitstr_to_u24(&ies.context_ID);
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA %s Connect.req(ctx=0x%x, %s)\n",
cn_domain_indicator_to_str(ies.cN_DomainIndicator), context_id,
ranap_domain_name(ies.cN_DomainIndicator), context_id,
ies.establishment_Cause == RUA_Establishment_Cause_emergency_call ? "emergency" : "normal");
HNBP_CTR_INC(hnb->persistent, ies.cN_DomainIndicator == DOMAIN_PS ?
HNB_CTR_RUA_PS_CONNECT_UL : HNB_CTR_RUA_CS_CONNECT_UL);
rc = rua_to_scu(hnb, ies.cN_DomainIndicator, OSMO_SCU_PRIM_N_CONNECT,
rc = rua_to_scu(hnb, ies.cN_DomainIndicator, RUA_ProcedureCode_id_Connect,
context_id, 0, ies.ranaP_Message.buf,
ies.ranaP_Message.size);
@@ -380,6 +439,8 @@ static int rua_rx_init_disconnect(struct msgb *msg, ANY_t *in)
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA Disconnect.req(ctx=0x%x,cause=%s)\n", context_id,
rua_cause_str(&ies.cause));
HNBP_CTR_INC(hnb->persistent, ies.cN_DomainIndicator == DOMAIN_PS ?
HNB_CTR_RUA_PS_DISCONNECT_UL : HNB_CTR_RUA_CS_DISCONNECT_UL);
if (ies.presenceMask & DISCONNECTIES_RUA_RANAP_MESSAGE_PRESENT) {
ranap_data = ies.ranaP_Message.buf;
@@ -387,7 +448,7 @@ static int rua_rx_init_disconnect(struct msgb *msg, ANY_t *in)
}
rc = rua_to_scu(hnb, ies.cN_DomainIndicator,
OSMO_SCU_PRIM_N_DISCONNECT,
RUA_ProcedureCode_id_Disconnect,
context_id, scu_cause, ranap_data, ranap_len);
rua_free_disconnecties(&ies);
@@ -409,10 +470,12 @@ static int rua_rx_init_dt(struct msgb *msg, ANY_t *in)
context_id = asn1bitstr_to_u24(&ies.context_ID);
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA Data.req(ctx=0x%x)\n", context_id);
HNBP_CTR_INC(hnb->persistent, ies.cN_DomainIndicator == DOMAIN_PS ?
HNB_CTR_RUA_PS_DT_UL : HNB_CTR_RUA_CS_DT_UL);
rc = rua_to_scu(hnb,
ies.cN_DomainIndicator,
OSMO_SCU_PRIM_N_DATA,
RUA_ProcedureCode_id_DirectTransfer,
context_id, 0, ies.ranaP_Message.buf,
ies.ranaP_Message.size);
@@ -432,6 +495,7 @@ static int rua_rx_init_udt(struct msgb *msg, ANY_t *in)
return rc;
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA UData.req()\n");
HNBP_CTR_INC(hnb->persistent, HNB_CTR_RUA_UDT_UL);
/* according tot the spec, we can primarily receive Overload,
* Reset, Reset ACK, Error Indication, reset Resource, Reset
@@ -440,7 +504,7 @@ static int rua_rx_init_udt(struct msgb *msg, ANY_t *in)
* Information Transfer and Uplink Information Trnansfer that we
* can ignore. In either case, it is RANAP that we need to
* decode... */
rc = hnbgw_ranap_rx(msg, ies.ranaP_Message.buf, ies.ranaP_Message.size);
rc = hnbgw_ranap_rx_udt_ul(msg, ies.ranaP_Message.buf, ies.ranaP_Message.size);
rua_free_connectionlesstransferies(&ies);
return rc;
@@ -458,6 +522,7 @@ static int rua_rx_init_err_ind(struct msgb *msg, ANY_t *in)
return rc;
LOGHNB(hnb, DRUA, LOGL_ERROR, "RUA UData.ErrorInd(%s)\n", rua_cause_str(&ies.cause));
HNBP_CTR_INC(hnb->persistent, HNB_CTR_RUA_ERR_IND);
rua_free_errorindicationies(&ies);
return rc;
@@ -544,6 +609,10 @@ int hnbgw_rua_rx(struct hnb_context *hnb, struct msgb *msg)
asn_dec_rval_t dec_ret;
int rc;
/* RUA is only processed after HNB registration, and as soon as the HNB is registered,
* it should have a persistent config associated with it */
OSMO_ASSERT(hnb->persistent);
/* decode and handle to _hnbgw_hnbap_rx() */
memset(pdu, 0, sizeof(*pdu));
@@ -556,11 +625,7 @@ int hnbgw_rua_rx(struct hnb_context *hnb, struct msgb *msg)
rc = _hnbgw_rua_rx(msg, pdu);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RUA_RUA_PDU, pdu);
return rc;
}
int hnbgw_rua_init(void)
{
return 0;
}

566
src/osmo-hnbgw/hnbgw_sccp.c Normal file
View File

@@ -0,0 +1,566 @@
/* hnb-gw specific code for SCCP, ITU Q.711 - Q.714 */
/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
* (C) 2025 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 "config.h"
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
#include <osmocom/netif/stream.h>
#include <osmocom/sigtran/sccp_sap.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <osmocom/sigtran/protocol/sua.h>
#include <osmocom/sccp/sccp_types.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/hnbgw_ranap.h>
/***********************************************************************
* Incoming primitives from SCCP User SAP
***********************************************************************/
static bool cnlink_matches(const struct hnbgw_cnlink *cnlink, const struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *remote_addr)
{
if (cnlink->hnbgw_sccp_user != hsu)
return false;
if (osmo_sccp_addr_cmp(&cnlink->remote_addr, remote_addr, OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC))
return false;
return true;
}
static struct hnbgw_cnlink *hnbgw_cnlink_find_by_addr(const struct hnbgw_sccp_user *hsu,
const struct osmo_sccp_addr *remote_addr)
{
struct hnbgw_cnlink *cnlink;
llist_for_each_entry(cnlink, &g_hnbgw->sccp.cnpool_iucs->cnlinks, entry) {
if (cnlink_matches(cnlink, hsu, remote_addr))
return cnlink;
}
llist_for_each_entry(cnlink, &g_hnbgw->sccp.cnpool_iups->cnlinks, entry) {
if (cnlink_matches(cnlink, hsu, remote_addr))
return cnlink;
}
return NULL;
}
static struct hnbgw_cnlink *cnlink_from_addr(struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *calling_addr,
const struct osmo_prim_hdr *oph)
{
struct hnbgw_cnlink *cnlink = NULL;
cnlink = hnbgw_cnlink_find_by_addr(hsu, calling_addr);
if (!cnlink) {
LOG_HSU(hsu, DRANAP, LOGL_ERROR, "Rx from unknown SCCP peer: %s: %s\n",
osmo_sccp_inst_addr_name(osmo_ss7_get_sccp(hsu->ss7), calling_addr),
osmo_scu_prim_hdr_name_c(OTC_SELECT, oph));
return NULL;
}
return cnlink;
}
static struct hnbgw_context_map *map_from_conn_id(struct hnbgw_sccp_user *hsu, uint32_t conn_id,
const struct osmo_prim_hdr *oph)
{
struct hnbgw_context_map *map;
hash_for_each_possible(hsu->hnbgw_context_map_by_conn_id, map, hnbgw_sccp_user_entry, conn_id) {
if (map->scu_conn_id == conn_id)
return map;
}
LOGP(DRANAP, LOGL_ERROR, "Rx for unknown SCCP connection ID: %u: %s\n",
conn_id, osmo_scu_prim_hdr_name_c(OTC_SELECT, oph));
return NULL;
}
static int handle_cn_unitdata(struct hnbgw_sccp_user *hsu,
const struct osmo_scu_unitdata_param *param,
struct osmo_prim_hdr *oph)
{
struct hnbgw_cnlink *cnlink = cnlink_from_addr(hsu, &param->calling_addr, oph);
if (!cnlink)
return -ENOENT;
CNLINK_CTR_INC(cnlink, CNLINK_CTR_SCCP_N_UNITDATA_IND);
if (param->called_addr.ssn != OSMO_SCCP_SSN_RANAP) {
LOGP(DCN, LOGL_NOTICE, "N-UNITDATA.ind for unknown SSN %u\n",
param->called_addr.ssn);
return -1;
}
return hnbgw_ranap_rx_udt_dl(cnlink, param, msgb_l2(oph->msg), msgb_l2len(oph->msg));
}
static void handle_notice_ind(struct hnbgw_sccp_user *hsu,
const struct osmo_scu_notice_param *param,
const struct osmo_prim_hdr *oph)
{
struct hnbgw_cnlink *cnlink;
cnlink = cnlink_from_addr(hsu, &param->calling_addr, oph);
if (!cnlink) {
LOGP(DCN, LOGL_DEBUG, "(calling_addr=%s) N-NOTICE.ind cause=%u='%s' importance=%u didn't match any cnlink, ignoring\n",
osmo_sccp_addr_dump(&param->calling_addr),
param->cause, osmo_sccp_return_cause_name(param->cause),
param->importance);
return;
}
LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, "N-NOTICE.ind cause=%u='%s' importance=%u\n",
param->cause, osmo_sccp_return_cause_name(param->cause),
param->importance);
CNLINK_CTR_INC(cnlink, CNLINK_CTR_SCCP_N_NOTICE_IND);
switch (param->cause) {
case SCCP_RETURN_CAUSE_SUBSYSTEM_CONGESTION:
case SCCP_RETURN_CAUSE_NETWORK_CONGESTION:
/* Transient failures (hopefully), keep going. */
return;
default:
break;
}
/* Messages are not arriving to destination of cnlink. Kick it back to DISC state. */
cnlink_set_disconnected(cnlink);
}
static int handle_cn_conn_conf(struct hnbgw_sccp_user *hsu,
const struct osmo_scu_connect_param *param,
struct osmo_prim_hdr *oph)
{
struct hnbgw_context_map *map;
map = map_from_conn_id(hsu, param->conn_id, oph);
if (!map || !map->cnlink)
return -ENOENT;
LOGP(DCN, LOGL_DEBUG, "handle_cn_conn_conf() conn_id=%d, addrs: called=%s calling=%s responding=%s\n",
param->conn_id,
hnbgw_cnlink_sccp_addr_to_str(map->cnlink, &param->called_addr),
hnbgw_cnlink_sccp_addr_to_str(map->cnlink, &param->calling_addr),
hnbgw_cnlink_sccp_addr_to_str(map->cnlink, &param->responding_addr));
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_N_CONNECT_CNF);
map_sccp_dispatch(map, MAP_SCCP_EV_RX_CONNECTION_CONFIRM, oph->msg);
return 0;
}
static int handle_cn_data_ind(struct hnbgw_sccp_user *hsu,
const struct osmo_scu_data_param *param,
struct osmo_prim_hdr *oph)
{
struct hnbgw_context_map *map;
map = map_from_conn_id(hsu, param->conn_id, oph);
if (!map || !map->cnlink)
return -ENOENT;
return map_sccp_dispatch(map, MAP_SCCP_EV_RX_DATA_INDICATION, oph->msg);
}
static int handle_cn_disc_ind(struct hnbgw_sccp_user *hsu,
const struct osmo_scu_disconn_param *param,
struct osmo_prim_hdr *oph)
{
struct hnbgw_context_map *map;
char cause_buf[128];
map = map_from_conn_id(hsu, param->conn_id, oph);
if (!map || !map->cnlink)
return -ENOENT;
LOGP(DCN, LOGL_DEBUG, "handle_cn_disc_ind() conn_id=%u responding_addr=%s cause=%s\n",
param->conn_id,
hnbgw_cnlink_sccp_addr_to_str(map->cnlink, &param->responding_addr),
osmo_sua_sccp_cause_name(param->cause, cause_buf, sizeof(cause_buf)));
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_N_DISCONNECT_IND);
return map_sccp_dispatch(map, MAP_SCCP_EV_RX_RELEASED, oph->msg);
}
static struct hnbgw_cnlink *_cnlink_find_by_remote_pc(struct hnbgw_cnpool *cnpool, struct osmo_ss7_instance *cs7, uint32_t pc)
{
struct hnbgw_cnlink *cnlink;
llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) {
if (!cnlink->hnbgw_sccp_user)
continue;
if (cnlink->hnbgw_sccp_user->ss7 != cs7)
continue;
if ((cnlink->remote_addr.presence & OSMO_SCCP_ADDR_T_PC) == 0)
continue;
if (cnlink->remote_addr.pc != pc)
continue;
return cnlink;
}
return NULL;
}
/* Find a cnlink by its remote sigtran point code on a given cs7 instance. */
static struct hnbgw_cnlink *cnlink_find_by_remote_pc(struct osmo_ss7_instance *cs7, uint32_t pc)
{
struct hnbgw_cnlink *cnlink;
cnlink = _cnlink_find_by_remote_pc(g_hnbgw->sccp.cnpool_iucs, cs7, pc);
if (!cnlink)
cnlink = _cnlink_find_by_remote_pc(g_hnbgw->sccp.cnpool_iups, cs7, pc);
return cnlink;
}
static void handle_pcstate_ind(struct hnbgw_sccp_user *hsu, const struct osmo_scu_pcstate_param *pcst)
{
struct hnbgw_cnlink *cnlink;
bool connected;
bool disconnected;
struct osmo_ss7_instance *cs7 = hsu->ss7;
LOGP(DCN, LOGL_DEBUG, "N-PCSTATE ind: affected_pc=%u=%s sp_status=%s remote_sccp_status=%s\n",
pcst->affected_pc, osmo_ss7_pointcode_print(cs7, pcst->affected_pc),
osmo_sccp_sp_status_name(pcst->sp_status),
osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status));
/* If we don't care about that point-code, ignore PCSTATE. */
cnlink = cnlink_find_by_remote_pc(cs7, pcst->affected_pc);
if (!cnlink)
return;
CNLINK_CTR_INC(cnlink, CNLINK_CTR_SCCP_N_PCSTATE_IND);
/* See if this marks the point code to have become available, or to have been lost.
*
* I want to detect two events:
* - connection event (both indicators say PC is reachable).
* - disconnection event (at least one indicator says the PC is not reachable).
*
* There are two separate incoming indicators with various possible values -- the incoming events can be:
*
* - neither connection nor disconnection indicated -- just indicating congestion
* connected == false, disconnected == false --> do nothing.
* - both incoming values indicate that we are connected
* --> trigger connected
* - both indicate we are disconnected
* --> trigger disconnected
* - one value indicates 'connected', the other indicates 'disconnected'
* --> trigger disconnected
*
* Congestion could imply that we're connected, but it does not indicate that a PC's reachability changed, so no need to
* trigger on that.
*/
connected = false;
disconnected = false;
switch (pcst->sp_status) {
case OSMO_SCCP_SP_S_ACCESSIBLE:
connected = true;
break;
case OSMO_SCCP_SP_S_INACCESSIBLE:
disconnected = true;
break;
default:
case OSMO_SCCP_SP_S_CONGESTED:
/* Neither connecting nor disconnecting */
break;
}
switch (pcst->remote_sccp_status) {
case OSMO_SCCP_REM_SCCP_S_AVAILABLE:
if (!disconnected)
connected = true;
break;
case OSMO_SCCP_REM_SCCP_S_UNAVAILABLE_UNKNOWN:
case OSMO_SCCP_REM_SCCP_S_UNEQUIPPED:
case OSMO_SCCP_REM_SCCP_S_INACCESSIBLE:
disconnected = true;
connected = false;
break;
default:
case OSMO_SCCP_REM_SCCP_S_CONGESTED:
/* Neither connecting nor disconnecting */
break;
}
if (disconnected && cnlink_is_conn_ready(cnlink)) {
LOG_CNLINK(cnlink, DCN, LOGL_NOTICE,
"now unreachable: N-PCSTATE ind: pc=%u=%s sp_status=%s remote_sccp_status=%s\n",
pcst->affected_pc, osmo_ss7_pointcode_print(cs7, pcst->affected_pc),
osmo_sccp_sp_status_name(pcst->sp_status),
osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status));
/* A previously usable cnlink has disconnected. Kick it back to DISC state. */
cnlink_set_disconnected(cnlink);
} else if (connected && !cnlink_is_conn_ready(cnlink)) {
LOG_CNLINK(cnlink, DCN, LOGL_NOTICE,
"now available: N-PCSTATE ind: pc=%u=%s sp_status=%s remote_sccp_status=%s\n",
pcst->affected_pc, osmo_ss7_pointcode_print(cs7, pcst->affected_pc),
osmo_sccp_sp_status_name(pcst->sp_status),
osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status));
/* A previously unusable cnlink has become reachable. Trigger immediate RANAP RESET -- we would resend a
* RESET either way, but we might as well do it now to speed up connecting. */
cnlink_resend_reset(cnlink);
}
}
/* Entry point for primitives coming up from SCCP User SAP.
* Ownership of oph->msg is transferred to us. */
static int sccp_sap_up(struct osmo_prim_hdr *oph, void *ctx)
{
struct osmo_sccp_user *scu = ctx;
struct hnbgw_sccp_user *hsu;
struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph;
int rc = 0;
LOGP(DCN, LOGL_DEBUG, "sccp_sap_up(%s)\n", osmo_scu_prim_name(oph));
if (!scu) {
LOGP(DCN, LOGL_ERROR,
"sccp_sap_up(): NULL osmo_sccp_user, cannot send prim (sap %u prim %u op %d)\n",
oph->sap, oph->primitive, oph->operation);
return -1;
}
hsu = osmo_sccp_user_get_priv(scu);
if (!hsu) {
LOGP(DCN, LOGL_ERROR,
"sccp_sap_up(): NULL hnbgw_sccp_user, cannot send prim (sap %u prim %u op %d)\n",
oph->sap, oph->primitive, oph->operation);
return -1;
}
talloc_steal(OTC_SELECT, oph->msg);
switch (OSMO_PRIM_HDR(oph)) {
case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION):
rc = handle_cn_unitdata(hsu, &prim->u.unitdata, oph);
break;
case OSMO_PRIM(OSMO_SCU_PRIM_N_NOTICE, PRIM_OP_INDICATION):
handle_notice_ind(hsu, &prim->u.notice, oph);
break;
case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM):
rc = handle_cn_conn_conf(hsu, &prim->u.connect, oph);
break;
case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION):
rc = handle_cn_data_ind(hsu, &prim->u.data, oph);
break;
case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION):
rc = handle_cn_disc_ind(hsu, &prim->u.disconnect, oph);
break;
case OSMO_PRIM(OSMO_SCU_PRIM_N_PCSTATE, PRIM_OP_INDICATION):
handle_pcstate_ind(hsu, &prim->u.pcstate);
break;
default:
LOGP(DCN, LOGL_ERROR,
"Received unknown prim %u from SCCP USER SAP\n",
OSMO_PRIM_HDR(oph));
break;
}
return rc;
}
/***********************************************************************
* Submit primitives to SCCP User SAP
***********************************************************************/
int hnbgw_sccp_user_tx_unitdata_req(struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *called_addr, struct msgb *ranap_msg)
{
if (!hsu) {
LOGP(DCN, LOGL_ERROR, "Failed to send SCCP N-UNITDATA.req: no SCCP User\n");
return -1;
}
OSMO_ASSERT(called_addr);
return osmo_sccp_tx_unitdata_msg(hsu->sccp_user,
&hsu->local_addr,
called_addr,
ranap_msg);
}
int hnbgw_sccp_user_tx_connect_req(struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *called_addr, uint32_t scu_conn_id, struct msgb *ranap_msg)
{
struct osmo_scu_prim *prim;
int rc;
if (!hsu) {
LOGP(DCN, LOGL_ERROR, "Failed to send SCCP N-CONNECT.req(%u): no SCCP User\n", scu_conn_id);
return -1;
}
OSMO_ASSERT(called_addr);
prim = (struct osmo_scu_prim *)msgb_push(ranap_msg, sizeof(*prim));
osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST, ranap_msg);
prim->u.connect.called_addr = *called_addr;
prim->u.connect.calling_addr = hsu->local_addr;
prim->u.connect.sccp_class = 2;
prim->u.connect.conn_id = scu_conn_id;
rc = osmo_sccp_user_sap_down_nofree(hsu->sccp_user, &prim->oph);
if (rc)
LOG_HSU(hsu, DCN, LOGL_ERROR, "Failed to send SCCP Connection Request to CN\n");
return rc;
}
int hnbgw_sccp_user_tx_data_req(struct hnbgw_sccp_user *hsu, uint32_t scu_conn_id, struct msgb *ranap_msg)
{
struct osmo_scu_prim *prim;
int rc;
if (!hsu) {
LOGP(DCN, LOGL_ERROR, "Failed to send SCCP N-DATA.req(%u): no SCCP User\n", scu_conn_id);
return -1;
}
prim = (struct osmo_scu_prim *)msgb_push(ranap_msg, sizeof(*prim));
osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST, ranap_msg);
prim->u.data.conn_id = scu_conn_id;
rc = osmo_sccp_user_sap_down_nofree(hsu->sccp_user, &prim->oph);
if (rc)
LOG_HSU(hsu, DCN, LOGL_ERROR, "Failed to send SCCP N-DATA.req(%u)\n", scu_conn_id);
return rc;
}
int hnbgw_sccp_user_tx_disconnect_req(struct hnbgw_sccp_user *hsu, uint32_t scu_conn_id)
{
int rc;
if (!hsu) {
LOGP(DCN, LOGL_ERROR, "Failed to send SCCP N-DISCONNECT.req(%u): no SCCP User\n", scu_conn_id);
return -1;
}
rc = osmo_sccp_tx_disconn(hsu->sccp_user, scu_conn_id, NULL,
SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
if (rc)
LOG_HSU(hsu, DCN, LOGL_ERROR, "Failed to send SCCP N-DISCONNECT.req(%u)\n", scu_conn_id);
return rc;
}
/***********************************************************************
* struct hnbgw_sccp_user lifecycle:
***********************************************************************/
static int hnbgw_sccp_user_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line)
{
struct hnbgw_sccp_user *hsu = e->use_count->talloc_object;
int32_t total;
int level;
if (!e->use)
return -EINVAL;
total = osmo_use_count_total(&hsu->use_count);
if (total == 0
|| (total == 1 && old_use_count == 0 && e->count == 1))
level = LOGL_INFO;
else
level = LOGL_DEBUG;
LOGPSRC(DCN, level, file, line,
"%s: %s %s: now used by %s\n",
hsu->name,
(e->count - old_use_count) > 0 ? "+" : "-",
e->use,
osmo_use_count_to_str_c(OTC_SELECT, &hsu->use_count));
if (e->count < 0)
return -ERANGE;
if (total == 0)
talloc_free(hsu);
return 0;
}
static int hnbgw_sccp_user_talloc_destructor(struct hnbgw_sccp_user *hsu)
{
if (hsu->sccp_user) {
osmo_sccp_user_unbind(hsu->sccp_user);
hsu->sccp_user = NULL;
}
llist_del(&hsu->entry);
return 0;
}
struct hnbgw_sccp_user *hnbgw_sccp_user_alloc(int ss7_id)
{
struct osmo_sccp_instance *sccp;
uint32_t local_pc;
struct hnbgw_sccp_user *hsu;
hsu = talloc_zero(g_hnbgw, struct hnbgw_sccp_user);
OSMO_ASSERT(hsu);
*hsu = (struct hnbgw_sccp_user){
.name = talloc_asprintf(hsu, "cs7-%u-sccp-OsmoHNBGW", ss7_id),
.use_count = {
.talloc_object = hsu,
.use_cb = hnbgw_sccp_user_use_cb,
},
};
hash_init(hsu->hnbgw_context_map_by_conn_id);
llist_add_tail(&hsu->entry, &g_hnbgw->sccp.users);
talloc_set_destructor(hsu, hnbgw_sccp_user_talloc_destructor);
sccp = osmo_sccp_simple_client_on_ss7_id(g_hnbgw,
ss7_id,
hsu->name,
DEFAULT_PC_HNBGW,
OSMO_SS7_ASP_PROT_M3UA,
0,
"localhost",
-1,
"localhost");
if (!sccp) {
LOG_HSU(hsu, DCN, LOGL_ERROR, "Failed to configure SCCP on 'cs7 instance %u'\n",
ss7_id);
goto free_hsu_ret;
}
hsu->ss7 = osmo_sccp_get_ss7(sccp);
LOG_HSU(hsu, DCN, LOGL_NOTICE, "created SCCP instance on cs7 instance %u\n", osmo_ss7_instance_get_id(hsu->ss7));
/* Bind the SCCP user, using the cs7 instance's default point-code if one is configured, or osmo-hnbgw's default
* local PC. */
local_pc = osmo_ss7_instance_get_primary_pc(hsu->ss7);
if (!osmo_ss7_pc_is_valid(local_pc))
local_pc = DEFAULT_PC_HNBGW;
LOG_HSU(hsu, DCN, LOGL_DEBUG, "binding OsmoHNBGW user to cs7 instance %u, local PC %u = %s\n",
osmo_ss7_instance_get_id(hsu->ss7), local_pc, osmo_ss7_pointcode_print(hsu->ss7, local_pc));
char *sccp_user_name = talloc_asprintf(hsu, "%s-RANAP", hsu->name);
hsu->sccp_user = osmo_sccp_user_bind_pc(sccp, sccp_user_name, sccp_sap_up, OSMO_SCCP_SSN_RANAP, local_pc);
talloc_free(sccp_user_name);
if (!hsu->sccp_user) {
LOG_HSU(hsu, DCN, LOGL_ERROR, "Failed to init SCCP User\n");
goto free_hsu_ret;
}
osmo_sccp_make_addr_pc_ssn(&hsu->local_addr, local_pc, OSMO_SCCP_SSN_RANAP);
osmo_sccp_user_set_priv(hsu->sccp_user, hsu);
return hsu;
free_hsu_ret:
talloc_free(hsu);
return NULL;
}

File diff suppressed because it is too large Load Diff

112
src/osmo-hnbgw/kpi_dtap.c Normal file
View File

@@ -0,0 +1,112 @@
/* KPI (statistics, counters) at DTAP level */
/* (C) 2024 by Harald Welte <laforge@osmocom.org>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* 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 "config.h"
#include <osmocom/core/utils.h>
#include <osmocom/ranap/ranap_common_ran.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
#include <osmocom/hnbgw/hnb_persistent.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/kpi.h>
/***********************************************************************
* DOWNLINK messages
***********************************************************************/
void kpi_dtap_process_dl(struct hnbgw_context_map *map, const uint8_t *buf, unsigned int len,
uint8_t sapi)
{
struct hnb_persistent *hnbp = map->hnb_ctx->persistent;
const struct gsm48_hdr *gh = (const struct gsm48_hdr *)buf;
if (len < sizeof(*gh))
return;
/* if you make use of any data beyond the fixed-size gsm48_hdr, you must make sure the underlying
* buffer length is actually long enough! */
if (map->is_ps) {
/* Packet Switched Domain (from SGSN) */
switch (gsm48_hdr_msg_type(gh)) {
case GSM48_MT_GMM_ATTACH_ACK:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_ATT_ACK);
break;
case GSM48_MT_GMM_ATTACH_REJ:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_ATT_REJ);
break;
case GSM48_MT_GMM_RA_UPD_ACK:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_RAU_ACK);
break;
case GSM48_MT_GMM_RA_UPD_REJ:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_RAU_REJ);
break;
}
} else {
/* Circuit Switched Domain (from MSC) */
switch (gsm48_hdr_msg_type(gh)) {
case GSM48_MT_MM_LOC_UPD_ACCEPT:
/* FIXME: many LU are acknwoeldged implicitly with TMSI allocation */
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_CS_LU_ACC);
break;
case GSM48_MT_MM_LOC_UPD_REJECT:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_CS_LU_REJ);
break;
}
}
}
/***********************************************************************
* UPLINK messages
***********************************************************************/
void kpi_dtap_process_ul(struct hnbgw_context_map *map, const uint8_t *buf, unsigned int len,
uint8_t sapi)
{
struct hnb_persistent *hnbp = map->hnb_ctx->persistent;
const struct gsm48_hdr *gh = (const struct gsm48_hdr *)buf;
if (len < sizeof(*gh))
return;
/* if you make use of any data beyond the fixed-size gsm48_hdr, you must make sure the underlying
* buffer length is actually long enough! */
if (map->is_ps) {
/* Packet Switched Domain (to SGSN) */
switch (gsm48_hdr_msg_type(gh)) {
case GSM48_MT_GMM_ATTACH_REQ:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_ATT_REQ);
break;
case GSM48_MT_GMM_RA_UPD_REQ:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_RAU_REQ);
break;
}
} else {
/* Circuit Switched Domain (to MSC) */
switch (gsm48_hdr_msg_type(gh)) {
case GSM48_MT_MM_LOC_UPD_REQUEST:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_CS_LU_REQ);
break;
}
}
}

465
src/osmo-hnbgw/kpi_ranap.c Normal file
View File

@@ -0,0 +1,465 @@
/* KPI (statistics, counters) at RANAP level */
/* (C) 2024 by Harald Welte <laforge@osmocom.org>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* 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 "config.h"
#include <osmocom/core/utils.h>
#include <osmocom/ranap/ranap_common_ran.h>
#include <osmocom/hnbgw/hnb_persistent.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/kpi.h>
const struct value_string hnbgw_rab_state_names[] = {
{ RAB_STATE_INACTIVE, "INACTIVE" },
{ RAB_STATE_ACT_REQ, "ACT_REQ" },
{ RAB_STATE_ACTIVE, "ACTIVE" },
{ RAB_STATE_REL_REQ, "REL_REQ" },
{}
};
/***********************************************************************
* DOWNLINK messages
***********************************************************************/
static void kpi_ranap_process_dl_iu_rel_cmd(struct hnbgw_context_map *map, const ranap_message *ranap)
{
struct hnb_persistent *hnbp = map->hnb_ctx->persistent;
const RANAP_Cause_t *cause;
OSMO_ASSERT(ranap->procedureCode == RANAP_ProcedureCode_id_Iu_Release);
cause = &ranap->msg.iu_ReleaseCommandIEs.cause;
/* When Iu is released, all RABs are released implicitly */
for (unsigned int i = 0; i < ARRAY_SIZE(map->rab_state); i++) {
switch (map->rab_state[i]) {
case RAB_STATE_ACTIVE:
if (cause->present == RANAP_Cause_PR_nAS ||
cause->choice.nAS == RANAP_CauseNAS_normal_release) {
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT :
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT);
} else {
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT_ABNORMAL :
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT_ABNORMAL);
}
break;
}
}
/* clear all RAB state */
memset(map->rab_state, 0, sizeof(map->rab_state));
}
static void kpi_ranap_process_dl_rab_ass_req(struct hnbgw_context_map *map, ranap_message *ranap)
{
struct hnb_persistent *hnbp = map->hnb_ctx->persistent;
RANAP_RAB_AssignmentRequestIEs_t *ies;
int rc;
OSMO_ASSERT(ranap->procedureCode == RANAP_ProcedureCode_id_RAB_Assignment);
ies = &ranap->msg.raB_AssignmentRequestIEs;
if (ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT) {
RANAP_RAB_SetupOrModifyList_t *som_list = &ies->raB_SetupOrModifyList;
for (unsigned int i = 0; i < som_list->list.count; i++) {
RANAP_ProtocolIE_ContainerPair_t *container_pair = som_list->list.array[i];
RANAP_ProtocolIE_FieldPair_t *field_pair = container_pair->list.array[0];
RANAP_RAB_SetupOrModifyItemFirst_t _rab_setup_or_modify_item_first = {};
RANAP_RAB_SetupOrModifyItemFirst_t *rab_setup_or_modify_item_first = &_rab_setup_or_modify_item_first;
uint8_t rab_id;
if (!field_pair)
continue;
if (field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem)
continue;
rc = ranap_decode_rab_setupormodifyitemfirst(rab_setup_or_modify_item_first, &field_pair->firstValue);
if (rc < 0)
continue;
/* RAB-ID is an 8-bit bit-string, so it's the first byte */
rab_id = rab_setup_or_modify_item_first->rAB_ID.buf[0];
/* the only way to distinguish a "setup" from a "modify" is to know which RABs are
* already established. If it's already established, it is a modification; if it's
* new, it is a setup */
switch (map->rab_state[rab_id]) {
case RAB_STATE_ACTIVE:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_REQ :
HNB_CTR_RANAP_CS_RAB_MOD_REQ);
break;
case RAB_STATE_INACTIVE:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_REQ :
HNB_CTR_RANAP_CS_RAB_ACT_REQ);
map->rab_state[rab_id] = RAB_STATE_ACT_REQ;
break;
default:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_REQ_UNEXP :
HNB_CTR_RANAP_CS_RAB_ACT_REQ_UNEXP);
LOG_MAP(map, DRANAP, LOGL_NOTICE,
"Unexpected RAB Activation/Modification Req for RAB in state %s\n",
hnbgw_rab_state_name(map->rab_state[rab_id]));
break;
}
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, rab_setup_or_modify_item_first);
}
}
if (ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_RELEASELIST_PRESENT) {
RANAP_RAB_ReleaseList_t *r_list = &ies->raB_ReleaseList;
for (unsigned int i = 0; i < r_list->raB_ReleaseList_ies.list.count; i++) {
RANAP_IE_t *release_list_ie = r_list->raB_ReleaseList_ies.list.array[i];
RANAP_RAB_ReleaseItemIEs_t _rab_rel_item_ies = {};
RANAP_RAB_ReleaseItemIEs_t *rab_rel_item_ies = &_rab_rel_item_ies;
RANAP_RAB_ReleaseItem_t *rab_rel_item;
uint8_t rab_id;
if (!release_list_ie)
continue;
if (release_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_ReleaseItem)
continue;
rc = ranap_decode_rab_releaseitemies_fromlist(rab_rel_item_ies, &release_list_ie->value);
if (rc < 0)
continue;
rab_rel_item = &rab_rel_item_ies->raB_ReleaseItem;
/* RAB-ID is an 8-bit bit-string, so it's the first byte */
rab_id = rab_rel_item->rAB_ID.buf[0];
switch (map->rab_state[rab_id]) {
case RAB_STATE_ACTIVE:
if (rab_rel_item->cause.present == RANAP_Cause_PR_nAS &&
rab_rel_item->cause.choice.nAS == RANAP_CauseNAS_normal_release) {
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_REQ :
HNB_CTR_RANAP_CS_RAB_REL_REQ);
} else {
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_REQ_ABNORMAL :
HNB_CTR_RANAP_CS_RAB_REL_REQ_ABNORMAL);
}
break;
default:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_REQ_UNEXP :
HNB_CTR_RANAP_CS_RAB_REL_REQ_UNEXP);
LOG_MAP(map, DRANAP, LOGL_NOTICE,
"Unexpected RAB Release Req in state %s\n",
hnbgw_rab_state_name(map->rab_state[rab_id]));
break;
}
/* mark that RAB as release requested */
map->rab_state[rab_id] = RAB_STATE_REL_REQ;
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_ReleaseItem, rab_rel_item_ies);
}
}
}
static void kpi_ranap_process_dl_direct_transfer(struct hnbgw_context_map *map, ranap_message *ranap)
{
const RANAP_DirectTransferIEs_t *dt_ies = &ranap->msg.directTransferIEs;
uint8_t sapi = 0;
if (dt_ies->presenceMask & DIRECTTRANSFERIES_RANAP_SAPI_PRESENT) {
if (dt_ies->sapi == RANAP_SAPI_sapi_3)
sapi = 3;
}
kpi_dtap_process_dl(map, dt_ies->nas_pdu.buf, dt_ies->nas_pdu.size, sapi);
}
void kpi_ranap_process_dl(struct hnbgw_context_map *map, ranap_message *ranap)
{
if (map->hnb_ctx == NULL) {
/* This can happen if the HNB has disconnected and we are processing downlink messages
* from the CN which were already in flight before the CN side has realized the HNB
* is gone. */
return;
}
switch (ranap->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment: /* RAB ASSIGNMENT REQ (8.2) */
kpi_ranap_process_dl_rab_ass_req(map, ranap);
break;
case RANAP_ProcedureCode_id_Iu_Release:
kpi_ranap_process_dl_iu_rel_cmd(map, ranap); /* IU RELEASE CMD (8.5) */
break;
case RANAP_ProcedureCode_id_DirectTransfer:
kpi_ranap_process_dl_direct_transfer(map, ranap);
break;
default:
break;
}
}
/***********************************************************************
* UPLINK messages
***********************************************************************/
static void kpi_ranap_process_ul_rab_ass_resp(struct hnbgw_context_map *map, ranap_message *ranap)
{
struct hnb_persistent *hnbp = map->hnb_ctx->persistent;
RANAP_RAB_AssignmentResponseIEs_t *ies;
int rc;
OSMO_ASSERT(ranap->procedureCode == RANAP_ProcedureCode_id_RAB_Assignment);
ies = &ranap->msg.raB_AssignmentResponseIEs;
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_SETUPORMODIFIEDLIST_PRESENT) {
RANAP_RAB_SetupOrModifiedList_t *som_list = &ies->raB_SetupOrModifiedList;
for (unsigned int i = 0; i < som_list->raB_SetupOrModifiedList_ies.list.count; i++) {
RANAP_IE_t *som_list_ie = som_list->raB_SetupOrModifiedList_ies.list.array[i];
RANAP_RAB_SetupOrModifiedItemIEs_t _rab_som_item_ies = {};
RANAP_RAB_SetupOrModifiedItemIEs_t *rab_som_item_ies = &_rab_som_item_ies;
RANAP_RAB_SetupOrModifiedItem_t *rab_som_item;
uint8_t rab_id;
if (!som_list_ie)
continue;
if (som_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifiedItem)
continue;
rc = ranap_decode_rab_setupormodifieditemies_fromlist(rab_som_item_ies, &som_list_ie->value);
if (rc < 0)
continue;
rab_som_item = &rab_som_item_ies->raB_SetupOrModifiedItem;
/* RAB-ID is an 8-bit bit-string, so it's the first byte */
rab_id = rab_som_item->rAB_ID.buf[0];
/* differentiate modify / activate */
switch (map->rab_state[rab_id]) {
case RAB_STATE_ACT_REQ:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_CNF :
HNB_CTR_RANAP_CS_RAB_ACT_CNF);
map->rab_state[rab_id] = RAB_STATE_ACTIVE;
break;
case RAB_STATE_ACTIVE:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_CNF :
HNB_CTR_RANAP_CS_RAB_MOD_CNF);
break;
default:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_CNF_UNEXP :
HNB_CTR_RANAP_CS_RAB_ACT_CNF_UNEXP);
LOG_MAP(map, DRANAP, LOGL_NOTICE,
"Unexpected RAB Activation/Modification Conf for RAB in state %s\n",
hnbgw_rab_state_name(map->rab_state[rab_id]));
break;
}
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, rab_som_item_ies);
}
}
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_RELEASEDLIST_PRESENT) {
RANAP_RAB_ReleasedList_t *r_list = &ies->raB_ReleasedList;
/* increment number of released RABs, we don't need to do that individually during iteration */
for (unsigned int i = 0; i < r_list->raB_ReleasedList_ies.list.count; i++) {
RANAP_IE_t *released_list_ie = r_list->raB_ReleasedList_ies.list.array[i];
RANAP_RAB_ReleasedItemIEs_t _rab_rel_item_ies = {};
RANAP_RAB_ReleasedItemIEs_t *rab_rel_item_ies = &_rab_rel_item_ies;
RANAP_RAB_ReleasedItem_t *rab_rel_item;
uint8_t rab_id;
if (!released_list_ie)
continue;
if (released_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_ReleasedItem)
continue;
rc = ranap_decode_rab_releaseditemies_fromlist(rab_rel_item_ies, &released_list_ie->value);
if (rc < 0)
continue;
rab_rel_item = &rab_rel_item_ies->raB_ReleasedItem;
/* RAB-ID is an 8-bit bit-string, so it's the first byte */
rab_id = rab_rel_item->rAB_ID.buf[0];
switch (map->rab_state[rab_id]) {
case RAB_STATE_REL_REQ:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_CNF :
HNB_CTR_RANAP_CS_RAB_REL_CNF);
break;
default:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_CNF_UNEXP :
HNB_CTR_RANAP_CS_RAB_REL_CNF_UNEXP);
LOG_MAP(map, DRANAP, LOGL_NOTICE,
"Unexpected RAB Release Conf for RAB in state %s\n",
hnbgw_rab_state_name(map->rab_state[rab_id]));
break;
}
/* mark that RAB as released */
map->rab_state[rab_id] = RAB_STATE_INACTIVE;
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_ReleasedItem, rab_rel_item_ies);
}
}
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_QUEUEDLIST_PRESENT)
LOG_MAP(map, DRANAP, LOGL_NOTICE, "RAB Activation has been queued; we don't support KPIs for this\n");
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_FAILEDLIST_PRESENT) {
RANAP_RAB_FailedList_t *f_list = &ies->raB_FailedList;
for (unsigned int i = 0; i < f_list->raB_FailedList_ies.list.count; i++) {
RANAP_IE_t *failed_list_ie = f_list->raB_FailedList_ies.list.array[i];
RANAP_RAB_FailedItemIEs_t _rab_failed_item_ies = {};
RANAP_RAB_FailedItemIEs_t *rab_failed_item_ies = &_rab_failed_item_ies;
RANAP_RAB_FailedItem_t *rab_failed_item;
uint8_t rab_id;
if (!failed_list_ie)
continue;
if (failed_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_FailedItem)
continue;
rc = ranap_decode_rab_faileditemies_fromlist(rab_failed_item_ies, &failed_list_ie->value);
if (rc < 0)
continue;
rab_failed_item = &rab_failed_item_ies->raB_FailedItem;
/* RAB-ID is an 8-bit bit-string, so it's the first byte */
rab_id = rab_failed_item->rAB_ID.buf[0];
/* differentiate modify / activate */
switch (map->rab_state[rab_id]) {
case RAB_STATE_ACT_REQ:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_FAIL :
HNB_CTR_RANAP_CS_RAB_ACT_FAIL);
map->rab_state[rab_id] = RAB_STATE_INACTIVE;
break;
case RAB_STATE_ACTIVE:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_FAIL :
HNB_CTR_RANAP_CS_RAB_MOD_FAIL);
// FIXME: does it remain active after modification failure?
break;
default:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_FAIL_UNEXP :
HNB_CTR_RANAP_CS_RAB_ACT_FAIL_UNEXP);
LOG_MAP(map, DRANAP, LOGL_NOTICE,
"Unexpected RAB Activation/Modification Failed for RAB in state %s\n",
hnbgw_rab_state_name(map->rab_state[rab_id]));
break;
}
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_FailedItem, rab_failed_item_ies);
}
}
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_RELEASEFAILEDLIST_PRESENT) {
RANAP_RAB_ReleaseFailedList_t *rf_list = &ies->raB_ReleaseFailedList;
/* increment number of released RABs, we don't need to do that individually during iteration */
for (unsigned int i = 0; i < rf_list->raB_FailedList_ies.list.count; i++) {
RANAP_IE_t *failed_list_ie = rf_list->raB_FailedList_ies.list.array[i];
RANAP_RAB_FailedItemIEs_t _rab_failed_item_ies = {};
RANAP_RAB_FailedItemIEs_t *rab_failed_item_ies = &_rab_failed_item_ies;
RANAP_RAB_FailedItem_t *rab_failed_item;
uint8_t rab_id;
if (!failed_list_ie)
continue;
if (failed_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_FailedItem)
continue;
rc = ranap_decode_rab_faileditemies_fromlist(rab_failed_item_ies, &failed_list_ie->value);
if (rc < 0)
continue;
rab_failed_item = &rab_failed_item_ies->raB_FailedItem;
/* RAB-ID is an 8-bit bit-string, so it's the first byte */
rab_id = rab_failed_item->rAB_ID.buf[0];
/* differentiate modify / activate */
switch (map->rab_state[rab_id]) {
case RAB_STATE_ACT_REQ:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_FAIL :
HNB_CTR_RANAP_CS_RAB_REL_FAIL);
map->rab_state[rab_id] = RAB_STATE_INACTIVE;
break;
case RAB_STATE_ACTIVE:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_FAIL :
HNB_CTR_RANAP_CS_RAB_REL_FAIL);
// FIXME: does it remain active after modification failure?
break;
default:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_FAIL_UNEXP :
HNB_CTR_RANAP_CS_RAB_REL_FAIL_UNEXP);
LOG_MAP(map, DRANAP, LOGL_NOTICE,
"Unexpected RAB Release Failed for RAB in state %s\n",
hnbgw_rab_state_name(map->rab_state[rab_id]));
break;
}
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_FailedItem, rab_failed_item_ies);
}
}
}
static void kpi_ranap_process_ul_initial_ue(struct hnbgw_context_map *map, ranap_message *ranap)
{
const RANAP_InitialUE_MessageIEs_t *iue_ies = &ranap->msg.initialUE_MessageIEs;
kpi_dtap_process_ul(map, iue_ies->nas_pdu.buf, iue_ies->nas_pdu.size, 0);
}
static void kpi_ranap_process_ul_direct_transfer(struct hnbgw_context_map *map, ranap_message *ranap)
{
const RANAP_DirectTransferIEs_t *dt_ies = &ranap->msg.directTransferIEs;
uint8_t sapi = 0;
if (dt_ies->presenceMask & DIRECTTRANSFERIES_RANAP_SAPI_PRESENT) {
if (dt_ies->sapi == RANAP_SAPI_sapi_3)
sapi = 3;
}
kpi_dtap_process_ul(map, dt_ies->nas_pdu.buf, dt_ies->nas_pdu.size, sapi);
}
void kpi_ranap_process_ul(struct hnbgw_context_map *map, ranap_message *ranap)
{
/* we should never be processing uplink messages from a non-existant HNB */
OSMO_ASSERT(map->hnb_ctx);
switch (ranap->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment: /* RAB ASSIGNMENT REQ (8.2) */
kpi_ranap_process_ul_rab_ass_resp(map, ranap);
break;
case RANAP_ProcedureCode_id_Iu_Release:
/* TODO: We might want to parse the list of released RABs here and then mark each of those as
* released. For now we simply assume that all RABs are released in IU RELEASE during
* processing of the downlink Iu Release Command. It's not like the RNC/HNB has any way to
* refuse the release anyway. */
break;
case RANAP_ProcedureCode_id_InitialUE_Message:
kpi_ranap_process_ul_initial_ue(map, ranap);
break;
case RANAP_ProcedureCode_id_DirectTransfer:
kpi_ranap_process_ul_direct_transfer(map, ranap);
break;
default:
break;
}
}

958
src/osmo-hnbgw/mgw_fsm.c Normal file
View File

@@ -0,0 +1,958 @@
/* (C) 2021-2024 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Philipp Maier
*
* 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.
*/
#include <errno.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/prim.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/byteswap.h>
#include <arpa/inet.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/netif/stream.h>
#include <osmocom/ranap/ranap_common.h>
#include <osmocom/ranap/ranap_common_cn.h>
#include <osmocom/ranap/ranap_common_ran.h>
#include <osmocom/ranap/ranap_msg_factory.h>
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/ranap/iu_helpers.h>
#include <asn1c/asn1helpers.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/ranap_rab_ass.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/core/tdef.h>
#include <osmocom/hnbgw/tdefs.h>
#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
/* NOTE: This implementation can only handle one RAB per context_map (== SCCP connection == RUA connection == UE
* context). This simplification was made because usually a voice call will require only one RAB at a time. An exception
* may be corner cases like video calls, which we do not support at the moment. */
/* Send Iu Release Request, this is done in erroneous cases from which we cannot recover */
static void tx_release_req(struct hnbgw_context_map *map)
{
struct msgb *msg;
static const struct RANAP_Cause cause = {
.present = RANAP_Cause_PR_transmissionNetwork,
.choice.transmissionNetwork =
RANAP_CauseTransmissionNetwork_iu_transport_connection_failed_to_establish,
};
msg = ranap_new_msg_iu_rel_req(&cause);
msg->l2h = msg->data;
talloc_steal(OTC_SELECT, msg);
map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, msg);
}
#define S(x) (1 << (x))
extern int asn1_xer_print;
enum mgw_fsm_event {
MGW_EV_MGCP_OK,
MGW_EV_MGCP_FAIL,
MGW_EV_MGCP_TERM,
MGW_EV_RAB_ASS_RESP,
MGW_EV_RELEASE,
};
static const struct value_string mgw_fsm_event_names[] = {
OSMO_VALUE_STRING(MGW_EV_MGCP_OK),
OSMO_VALUE_STRING(MGW_EV_MGCP_FAIL),
OSMO_VALUE_STRING(MGW_EV_MGCP_TERM),
OSMO_VALUE_STRING(MGW_EV_RAB_ASS_RESP),
OSMO_VALUE_STRING(MGW_EV_RELEASE),
{}
};
enum mgw_fsm_state {
MGW_ST_CRCX_HNB,
MGW_ST_ASSIGN,
MGW_ST_MDCX_HNB,
MGW_ST_CRCX_MSC,
MGW_ST_ESTABLISHED,
MGW_ST_RELEASE,
MGW_ST_FAILURE,
};
struct mgw_fsm_priv {
/* Backpointer to HNBGW context */
struct hnbgw_context_map *map;
/* RAB-ID from RANAP RAB AssignmentRequest message */
uint8_t rab_id;
/* Pointers to messages and prim header we take ownership of */
ranap_message *ranap_rab_ass_req_message;
ranap_message *ranap_rab_ass_resp_message;
struct msgb *ranap_rab_ass_resp_msgb;
/* IP address contained in ranap_rab_ass_resp_msgb/message: */
struct osmo_sockaddr hnb_rtp_addr;
/* Number of MDCX transmitted. Used to detect current mgw conn_mode and
* detect modify infinite loops: */
unsigned int mdcx_tx_cnt;
/* MGW context */
struct mgcp_client *mgcpc;
struct osmo_mgcpc_ep *mgcpc_ep;
struct osmo_mgcpc_ep_ci *mgcpc_ep_ci_hnb;
struct osmo_mgcpc_ep_ci *mgcpc_ep_ci_msc;
struct osmo_sockaddr ci_hnb_crcx_ack_addr;
char msc_rtp_addr[INET6_ADDRSTRLEN];
uint16_t msc_rtp_port;
/* Timestamps to track active duration */
struct timespec active_start;
struct timespec active_stored;
};
struct osmo_tdef_state_timeout mgw_fsm_timeouts[32] = {
[MGW_ST_CRCX_HNB] = {.T = -1001 },
[MGW_ST_ASSIGN] = {.T = -1002 },
[MGW_ST_MDCX_HNB] = {.T = -1003 },
[MGW_ST_CRCX_MSC] = {.T = -1004 },
};
#define mgw_fsm_state_chg(fi, state) \
osmo_tdef_fsm_inst_state_chg(fi, state, mgw_fsm_timeouts, mgw_fsm_T_defs, -1)
static void mgw_fsm_crcx_hnb_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
struct hnbgw_context_map *map = mgw_fsm_priv->map;
struct hnb_context *hnb_ctx = map->hnb_ctx;
struct osmo_sockaddr addr;
struct osmo_sockaddr_str addr_str;
RANAP_RAB_AssignmentRequestIEs_t *ies;
const char *epname;
struct mgcp_conn_peer mgw_info;
int rc;
LOGPFSML(fi, LOGL_DEBUG, "RAB-AssignmentRequest received, creating HNB side call-leg on MGW...\n");
/* Parse the RAB Assignment Request now */
ies = &mgw_fsm_priv->ranap_rab_ass_req_message->msg.raB_AssignmentRequestIEs;
rc = ranap_rab_ass_req_ies_extract_inet_addr(&addr, &mgw_fsm_priv->rab_id, ies, 0);
if (rc < 0) {
LOGPFSML(fi, LOGL_ERROR, "Invalid RAB-AssignmentRequest -- abort\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
rc = osmo_sockaddr_str_from_sockaddr(&addr_str, &addr.u.sas);
if (rc < 0) {
LOGPFSML(fi, LOGL_ERROR,
"Invalid RTP IP-address or port in RAB-AssignmentRequest -- abort\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
osmo_strlcpy(mgw_fsm_priv->msc_rtp_addr, addr_str.ip, sizeof(mgw_fsm_priv->msc_rtp_addr));
mgw_fsm_priv->msc_rtp_port = addr_str.port;
mgw_info = (struct mgcp_conn_peer) {
.call_id = (map->rua_ctx_id << 8) | mgw_fsm_priv->rab_id,
.ptime = 20,
.conn_mode = MGCP_CONN_RECV_ONLY,
};
mgw_info.codecs[0] = CODEC_IUFP;
mgw_info.codecs_len = 1;
/* The HNB IuUP IP address & port is not yet known here (Rx RAB-Ass-Req Tx CRCX (RAN) time):
* Assume and announce "remote IuUP IP address" == "remote Iuh signalling IP address" to the MGW
* here, so that it can most probably select a proper IuUP local IP address to be used from the
* start. In the event we receive a "remote IuUP IP address" != "remote Iuh signalling IP address"
* later on during RAB-Ass-Resp, we'll update IP addr at the MGW through MDCX and if MGW decides
* to use another IuUP local IP address as a result, it will be updated at the HNB through
* RAB-Modify-Req. */
if (hnb_ctx && hnb_ctx->conn &&
(rc = osmo_stream_srv_get_fd(hnb_ctx->conn)) >= 0) {
if (osmo_sock_get_remote_ip(rc, mgw_info.addr, sizeof(mgw_info.addr)) < 0)
LOGPFSML(fi, LOGL_ERROR, "Invalid Iuh IP Address\n");
}
mgw_fsm_priv->mgcpc = mgcp_client_pool_get(g_hnbgw->mgw_pool);
if (!mgw_fsm_priv->mgcpc) {
LOGPFSML(fi, LOGL_ERROR,
"cannot ensure MGW endpoint -- no MGW configured, check configuration!\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
epname = mgcp_client_rtpbridge_wildcard(mgw_fsm_priv->mgcpc);
mgw_fsm_priv->mgcpc_ep =
osmo_mgcpc_ep_alloc(fi, MGW_EV_MGCP_TERM, mgw_fsm_priv->mgcpc, mgw_fsm_T_defs, fi->id, "%s", epname);
mgw_fsm_priv->mgcpc_ep_ci_hnb = osmo_mgcpc_ep_ci_add(mgw_fsm_priv->mgcpc_ep, "to-HNB");
osmo_mgcpc_ep_ci_request(mgw_fsm_priv->mgcpc_ep_ci_hnb, MGCP_VERB_CRCX, &mgw_info, fi, MGW_EV_MGCP_OK,
MGW_EV_MGCP_FAIL, NULL);
}
static void mgw_fsm_crcx_hnb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
const struct mgcp_conn_peer *mgw_info;
struct osmo_sockaddr_str addr_str;
int rc;
switch (event) {
case MGW_EV_MGCP_OK:
mgw_info = osmo_mgcpc_ep_ci_get_rtp_info(mgw_fsm_priv->mgcpc_ep_ci_hnb);
if (!mgw_info) {
LOGPFSML(fi, LOGL_ERROR, "Got no RTP info response from MGW\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
if (strchr(mgw_info->addr, '.'))
addr_str.af = AF_INET;
else
addr_str.af = AF_INET6;
addr_str.port = mgw_info->port;
osmo_strlcpy(addr_str.ip, mgw_info->addr, sizeof(addr_str.ip));
rc = osmo_sockaddr_str_to_sockaddr(&addr_str, &mgw_fsm_priv->ci_hnb_crcx_ack_addr.u.sas);
if (rc < 0) {
LOGPFSML(fi, LOGL_ERROR,
"Failed to convert RTP IP-address (%s) and Port (%u) to its binary representation\n",
mgw_info->addr, mgw_info->port);
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
mgw_fsm_state_chg(fi, MGW_ST_ASSIGN);
return;
default:
OSMO_ASSERT(false);
}
}
static void mgw_fsm_assign_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
struct hnbgw_context_map *map = mgw_fsm_priv->map;
RANAP_RAB_AssignmentRequestIEs_t *ies;
struct msgb *msg;
int rc;
ies = &mgw_fsm_priv->ranap_rab_ass_req_message->msg.raB_AssignmentRequestIEs;
rc = ranap_rab_ass_req_ies_replace_inet_addr(ies, &mgw_fsm_priv->ci_hnb_crcx_ack_addr, mgw_fsm_priv->rab_id);
if (rc < 0) {
LOGPFSML(fi, LOGL_ERROR, "Failed to replace RTP IP-address and Port in RAB-AssignmentRequest\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
msg = ranap_rab_ass_req_encode(ies);
if (!msg) {
LOGPFSML(fi, LOGL_ERROR, "failed to re-encode RAB-AssignmentRequest message\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
LOGPFSML(fi, LOGL_DEBUG, "forwarding modified RAB-AssignmentRequest to HNB\n");
msg->l2h = msg->data;
talloc_steal(OTC_SELECT, msg);
map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, msg);
}
static void mgw_fsm_assign(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
struct hnbgw_context_map *map = mgw_fsm_priv->map;
RANAP_RAB_AssignmentResponseIEs_t *ies;
bool rab_failed_at_hnb;
struct osmo_sockaddr addr;
enum mgw_fsm_state next_st;
int rc;
switch (event) {
case MGW_EV_RAB_ASS_RESP:
LOGPFSML(fi, LOGL_DEBUG, "RAB-AssignmentResponse received, completing HNB side call-leg on MGW...\n");
ies = &mgw_fsm_priv->ranap_rab_ass_resp_message->msg.raB_AssignmentResponseIEs;
rc = ranap_rab_ass_resp_ies_extract_inet_addr(&addr, ies, mgw_fsm_priv->rab_id);
if (rc < 0) {
rab_failed_at_hnb = ranap_rab_ass_resp_ies_check_failure(ies, mgw_fsm_priv->rab_id);
if (rab_failed_at_hnb) {
struct msgb *msg;
LOGPFSML(fi, LOGL_ERROR,
"The RAB-AssignmentResponse contains a RAB-FailedList, RAB-Assignment (%u) failed.\n",
mgw_fsm_priv->rab_id);
/* Forward the RAB-AssignmentResponse transparently. This will ensure that the MSC
* is informed about the problem. */
LOGPFSML(fi, LOGL_DEBUG, "forwarding unmodified RAB-AssignmentResponse to MSC\n");
msg = mgw_fsm_priv->ranap_rab_ass_resp_msgb;
mgw_fsm_priv->ranap_rab_ass_resp_msgb = NULL;
talloc_steal(OTC_SELECT, msg);
rc = map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, msg);
if (rc < 0) {
LOGPFSML(fi, LOGL_DEBUG, "failed to forward RAB-AssignmentResponse message\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
}
/* Even though this is a failure situation, we still release normally as the error is located
* at the HNB. */
osmo_fsm_inst_state_chg(fi, MGW_ST_RELEASE, 0, 0);
return;
}
/* The RAB-ID we are dealing with is not on an FailedList and we were unable to parse the response
* normally. This is a situation we cannot recover from. */
LOGPFSML(fi, LOGL_ERROR, "Failed to extract RTP IP-address and Port from RAB-AssignmentResponse\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
/* Break infinite loops modifications between HNB and our MGW: */
if (mgw_fsm_priv->mdcx_tx_cnt > 3) {
LOGPFSML(fi, LOGL_ERROR, "IuUP addr modification infinite loop detected between HNB and MGW, "
"check your network routing config -- abort!\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_RELEASE, 0, 0);
return;
}
/* Send at least 1 MDCX in order to change conn_mode to SEND_RECV.
* From there on, MDCX is only needed if HNB IP/Port changed: */
if (mgw_fsm_priv->mdcx_tx_cnt == 0 ||
osmo_sockaddr_cmp(&addr, &mgw_fsm_priv->hnb_rtp_addr) != 0) {
next_st = MGW_ST_MDCX_HNB;
} else {
LOGPFSML(fi, LOGL_DEBUG, "RAB-AssignmentResponse received with unchanged IuUP attributes, skipping MDCX...\n");
next_st = MGW_ST_CRCX_MSC;
}
mgw_fsm_priv->hnb_rtp_addr = addr;
mgw_fsm_state_chg(fi, next_st);
return;
default:
OSMO_ASSERT(false);
}
}
static void mgw_fsm_mdcx_hnb_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
struct hnbgw_context_map *map = mgw_fsm_priv->map;
struct mgcp_conn_peer mgw_info;
struct osmo_sockaddr_str addr_str;
int rc;
mgw_info = (struct mgcp_conn_peer) {
.call_id = map->rua_ctx_id,
.ptime = 20,
.conn_mode = MGCP_CONN_RECV_SEND,
};
mgw_info.codecs[0] = CODEC_IUFP;
mgw_info.codecs_len = 1;
rc = osmo_sockaddr_str_from_sockaddr(&addr_str, &mgw_fsm_priv->hnb_rtp_addr.u.sas);
if (rc < 0) {
LOGPFSML(fi, LOGL_ERROR, "Invalid RTP IP-address or Port in RAB-AssignmentResponse\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
osmo_strlcpy(mgw_info.addr, addr_str.ip, sizeof(mgw_info.addr));
mgw_info.port = addr_str.port;
mgw_fsm_priv->mdcx_tx_cnt++;
osmo_mgcpc_ep_ci_request(mgw_fsm_priv->mgcpc_ep_ci_hnb, MGCP_VERB_MDCX, &mgw_info, fi, MGW_EV_MGCP_OK,
MGW_EV_MGCP_FAIL, NULL);
}
static void mgw_fsm_mdcx_hnb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
const struct mgcp_conn_peer *mgw_info;
struct osmo_sockaddr_str addr_str;
struct osmo_sockaddr addr;
int rc;
switch (event) {
case MGW_EV_MGCP_OK:
mgw_info = osmo_mgcpc_ep_ci_get_rtp_info(mgw_fsm_priv->mgcpc_ep_ci_hnb);
if (!mgw_info) {
LOGPFSML(fi, LOGL_ERROR, "Got no RTP info response from MGW\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
if (strchr(mgw_info->addr, '.'))
addr_str.af = AF_INET;
else
addr_str.af = AF_INET6;
addr_str.port = mgw_info->port;
osmo_strlcpy(addr_str.ip, mgw_info->addr, sizeof(addr_str.ip));
rc = osmo_sockaddr_str_to_sockaddr(&addr_str, &addr.u.sas);
if (rc < 0) {
LOGPFSML(fi, LOGL_ERROR,
"Failed to convert RTP IP-address (%s) and Port (%u) to its binary representation\n",
mgw_info->addr, mgw_info->port);
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
if (osmo_sockaddr_cmp(&mgw_fsm_priv->ci_hnb_crcx_ack_addr, &addr) != 0) {
char addr_buf[INET6_ADDRSTRLEN + 8];
LOGPFSML(fi, LOGL_INFO, "Local MGW IuUP IP address %s changed to %s during MDCX."
" Modifying RAB on HNB.\n",
osmo_sockaddr_to_str(&mgw_fsm_priv->ci_hnb_crcx_ack_addr),
osmo_sockaddr_to_str_buf(addr_buf, sizeof(addr_buf), &addr));
/* Modify RAB on the HNB with the new local IuUP address (OS#6127): */
mgw_fsm_priv->ci_hnb_crcx_ack_addr = addr;
mgw_fsm_state_chg(fi, MGW_ST_ASSIGN);
return;
}
mgw_fsm_state_chg(fi, MGW_ST_CRCX_MSC);
return;
default:
OSMO_ASSERT(false);
}
}
static void mgw_fsm_crcx_msc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
struct hnbgw_context_map *map = mgw_fsm_priv->map;
struct mgcp_conn_peer mgw_info;
LOGPFSML(fi, LOGL_DEBUG, "creating MSC side call-leg on MGW...\n");
mgw_info = (struct mgcp_conn_peer) {
.call_id = (map->rua_ctx_id << 8) | mgw_fsm_priv->rab_id,
.ptime = 20,
.port = mgw_fsm_priv->msc_rtp_port,
};
osmo_strlcpy(mgw_info.addr, mgw_fsm_priv->msc_rtp_addr, sizeof(mgw_info.addr));
mgw_info.codecs[0] = CODEC_IUFP;
mgw_info.codecs_len = 1;
mgw_fsm_priv->mgcpc_ep_ci_msc = osmo_mgcpc_ep_ci_add(mgw_fsm_priv->mgcpc_ep, "to-MSC");
osmo_mgcpc_ep_ci_request(mgw_fsm_priv->mgcpc_ep_ci_msc, MGCP_VERB_CRCX, &mgw_info, fi, MGW_EV_MGCP_OK,
MGW_EV_MGCP_FAIL, NULL);
}
static void mgw_fsm_crcx_msc(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
const struct mgcp_conn_peer *mgw_info;
struct osmo_sockaddr addr;
struct osmo_sockaddr_str addr_str;
int rc;
int msg_max_len;
RANAP_RAB_AssignmentResponseIEs_t *ies;
switch (event) {
case MGW_EV_MGCP_OK:
ies = &mgw_fsm_priv->ranap_rab_ass_resp_message->msg.raB_AssignmentResponseIEs;
mgw_info = osmo_mgcpc_ep_ci_get_rtp_info(mgw_fsm_priv->mgcpc_ep_ci_msc);
if (!mgw_info) {
LOGPFSML(fi, LOGL_ERROR, "Got no response from MGW\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
/* Replace RTP IP-Address/Port in ranap message container */
if (strchr(mgw_info->addr, '.'))
addr_str.af = AF_INET;
else
addr_str.af = AF_INET6;
addr_str.port = mgw_info->port;
osmo_strlcpy(addr_str.ip, mgw_info->addr, sizeof(addr_str.ip));
rc = osmo_sockaddr_str_to_sockaddr(&addr_str, &addr.u.sas);
if (rc < 0) {
LOGPFSML(fi, LOGL_ERROR,
"Failed to convert RTP IP-address (%s) and Port (%u) to its binary representation\n",
mgw_info->addr, mgw_info->port);
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
rc = ranap_rab_ass_resp_ies_replace_inet_addr(ies, &addr, mgw_fsm_priv->rab_id);
if (rc < 0) {
LOGPFSML(fi, LOGL_ERROR,
"Failed to replace RTP IP-address (%s) and Port (%u) in RAB-AssignmentResponse\n",
mgw_info->addr, mgw_info->port);
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
/* When the modified ranap message container is re-encoded, the resulting message might be larger then
* the original message. Ensure that there is enough room in l2h to grow. (The current implementation
* should yield a message with the same size, but there is no guarantee for that) */
msg_max_len =
msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_msgb) +
msgb_tailroom(mgw_fsm_priv->ranap_rab_ass_resp_msgb);
rc = msgb_resize_area(mgw_fsm_priv->ranap_rab_ass_resp_msgb,
mgw_fsm_priv->ranap_rab_ass_resp_msgb->l2h,
msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_msgb), msg_max_len);
OSMO_ASSERT(rc == 0);
rc = ranap_rab_ass_resp_encode(msgb_l2(mgw_fsm_priv->ranap_rab_ass_resp_msgb),
msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_msgb), ies);
if (rc < 0) {
LOGPFSML(fi, LOGL_ERROR, "failed to re-encode RAB-AssignmentResponse message\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
}
/* Resize l2h back to the actual message length */
rc = msgb_resize_area(mgw_fsm_priv->ranap_rab_ass_resp_msgb,
mgw_fsm_priv->ranap_rab_ass_resp_msgb->l2h,
msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_msgb), rc);
OSMO_ASSERT(rc == 0);
/* When the established state is entered, the modified RAB AssignmentResponse is forwarded to the MSC.
* The call is then established any way may stay for an indefinate amount of time in this state until
* there is an IU Release happening. */
osmo_fsm_inst_state_chg(fi, MGW_ST_ESTABLISHED, 0, 0);
return;
default:
OSMO_ASSERT(false);
}
}
static void mgw_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
struct hnbgw_context_map *map = mgw_fsm_priv->map;
struct msgb *ranap_msg;
int rc;
LOGPFSML(fi, LOGL_DEBUG, "forwarding modified RAB-AssignmentResponse to MSC\n");
ranap_msg = mgw_fsm_priv->ranap_rab_ass_resp_msgb;
mgw_fsm_priv->ranap_rab_ass_resp_msgb = NULL;
talloc_steal(OTC_SELECT, ranap_msg);
rc = map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg);
if (rc < 0) {
LOGPFSML(fi, LOGL_DEBUG, "failed to forward RAB-AssignmentResponse message\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
}
LOGPFSML(fi, LOGL_DEBUG, "HNB and MSC side call-legs completed!\n");
osmo_clock_gettime(CLOCK_MONOTONIC, &mgw_fsm_priv->active_start);
mgw_fsm_priv->active_stored = mgw_fsm_priv->active_start;
}
static void mgw_fsm_release_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
}
static void mgw_fsm_failure_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
tx_release_req(mgw_fsm_priv->map);
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
}
static void mgw_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
switch (event) {
case MGW_EV_MGCP_TERM:
/* Put MGCP client back into MGW pool */
if (mgw_fsm_priv->mgcpc) {
mgcp_client_pool_put(mgw_fsm_priv->mgcpc);
mgw_fsm_priv->mgcpc = NULL;
}
mgw_fsm_priv->mgcpc_ep = NULL;
LOGPFSML(fi, LOGL_ERROR, "Media gateway failed\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
case MGW_EV_MGCP_FAIL:
LOGPFSML(fi, LOGL_ERROR, "Media gateway failed to switch RTP streams\n");
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
return;
case MGW_EV_RELEASE:
osmo_fsm_inst_state_chg(fi, MGW_ST_RELEASE, 0, 0);
return;
default:
OSMO_ASSERT(false);
}
}
static int mgw_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
return 0;
}
static void mgw_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
talloc_free(mgw_fsm_priv);
}
static void mgw_fsm_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
struct hnbgw_context_map *map = mgw_fsm_priv->map;
if (mgw_fsm_priv->mgcpc_ep) {
/* Put MGCP client back into MGW pool */
struct mgcp_client *mgcp_client = osmo_mgcpc_ep_client(mgw_fsm_priv->mgcpc_ep);
mgcp_client_pool_put(mgcp_client);
osmo_mgcpc_ep_clear(mgw_fsm_priv->mgcpc_ep);
mgw_fsm_priv->mgcpc_ep = NULL;
}
/* Remove FSM from the context map. This will make this FSM unreachable for events coming from outside */
map->mgw_fi = NULL;
}
static const struct osmo_fsm_state mgw_fsm_states[] = {
[MGW_ST_CRCX_HNB] = {
.name = "MGW_ST_CRCX_HNB",
.onenter = mgw_fsm_crcx_hnb_onenter,
.action = mgw_fsm_crcx_hnb,
.in_event_mask =
S(MGW_EV_MGCP_OK),
.out_state_mask =
S(MGW_ST_ASSIGN) |
S(MGW_ST_FAILURE) |
S(MGW_ST_RELEASE) |
S(MGW_ST_CRCX_HNB),
},
[MGW_ST_ASSIGN] = {
.name = "MGW_ST_ASSIGN",
.onenter = mgw_fsm_assign_onenter,
.action = mgw_fsm_assign,
.in_event_mask = S(MGW_EV_RAB_ASS_RESP),
.out_state_mask =
S(MGW_ST_MDCX_HNB) |
S(MGW_ST_CRCX_MSC) |
S(MGW_ST_FAILURE) |
S(MGW_ST_RELEASE),
},
[MGW_ST_MDCX_HNB] = {
.name = "MGW_ST_MDCX_HNB",
.onenter = mgw_fsm_mdcx_hnb_onenter,
.action = mgw_fsm_mdcx_hnb,
.in_event_mask =
S(MGW_EV_MGCP_OK),
.out_state_mask =
S(MGW_ST_ASSIGN) |
S(MGW_ST_CRCX_MSC) |
S(MGW_ST_FAILURE) |
S(MGW_ST_RELEASE),
},
[MGW_ST_CRCX_MSC] = {
.name = "MGW_ST_CRCX_MSC",
.onenter = mgw_fsm_crcx_msc_onenter,
.action = mgw_fsm_crcx_msc,
.in_event_mask =
S(MGW_EV_MGCP_OK),
.out_state_mask =
S(MGW_ST_ESTABLISHED) |
S(MGW_ST_FAILURE) |
S(MGW_ST_RELEASE),
},
[MGW_ST_ESTABLISHED] = {
.name = "MGW_ST_ESTABLISHED",
.onenter = mgw_fsm_established_onenter,
.in_event_mask = 0,
.out_state_mask =
S(MGW_ST_FAILURE) |
S(MGW_ST_RELEASE),
},
[MGW_ST_RELEASE] = {
.name = "MGW_ST_RELEASE",
.onenter = mgw_fsm_release_onenter,
.in_event_mask = 0,
.out_state_mask = 0,
},
[MGW_ST_FAILURE] = {
.name = "MGW_ST_FAILURE",
.onenter = mgw_fsm_failure_onenter,
.in_event_mask = 0,
.out_state_mask = 0,
},
};
static struct osmo_fsm mgw_fsm = {
.name = "mgw",
.states = mgw_fsm_states,
.num_states = ARRAY_SIZE(mgw_fsm_states),
.log_subsys = DMGW,
.event_names = mgw_fsm_event_names,
.allstate_action = mgw_fsm_allstate_action,
.allstate_event_mask = S(MGW_EV_MGCP_TERM) | S(MGW_EV_RELEASE) | S(MGW_EV_MGCP_FAIL),
.timer_cb = mgw_fsm_timer_cb,
.cleanup = mgw_fsm_cleanup,
.pre_term = mgw_fsm_pre_term,
};
/* The MSC may ask to release a specific RAB within a RAB-AssignmentRequest */
static int release_mgw_fsm(struct hnbgw_context_map *map, struct msgb *ranap_msg)
{
struct osmo_fsm_inst *fi = map->mgw_fi;
int rc;
/* Forward the unmodifed RAB-AssignmentRequest to HNB, so that the HNB is informed about the RAB release as
* well */
LOGPFSML(fi, LOGL_DEBUG, "forwarding unmodified RAB-AssignmentRequest to HNB\n");
rc = map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, ranap_msg);
if (rc < 0) {
LOGPFSML(fi, LOGL_DEBUG, "cannot forward RAB-AssignmentRequest to HNB\n");
return -EINVAL;
}
/* Release the FSM normally */
osmo_fsm_inst_state_chg(fi, MGW_ST_RELEASE, 0, 0);
return 0;
}
static bool is_rab_ass_without_tli(struct hnbgw_context_map *map, ranap_message *message)
{
RANAP_RAB_AssignmentRequestIEs_t *ies;
RANAP_ProtocolIE_ContainerPair_t *protocol_ie_container_pair;
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
RANAP_RAB_SetupOrModifyItemFirst_t _rab_setup_or_modify_item_first = { 0 };
RANAP_RAB_SetupOrModifyItemFirst_t *rab_setup_or_modify_item_first = &_rab_setup_or_modify_item_first;
int rc;
bool ret;
ies = &message->msg.raB_AssignmentRequestIEs;
if (!(ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT))
return false;
/* Detect the end of the list */
if (ies->raB_SetupOrModifyList.list.count < 1)
return false;
protocol_ie_container_pair = ies->raB_SetupOrModifyList.list.array[0];
protocol_ie_field_pair = protocol_ie_container_pair->list.array[0];
if (!protocol_ie_field_pair)
return false;
if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem) {
RANAP_DEBUG
("Decoding failed, the protocol IE field-pair is not of type RANAP RAB setup-or-modify-item!\n");
return false;
}
rc = ranap_decode_rab_setupormodifyitemfirst(rab_setup_or_modify_item_first,
&protocol_ie_field_pair->firstValue);
if (rc < 0)
return false;
ret = rab_setup_or_modify_item_first->transportLayerInformation == NULL;
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, rab_setup_or_modify_item_first);
return ret;
}
/* Check if the message contains a RAB-ReleaseItem that matches the RAB-ID that is managed by the given context map */
static bool is_our_rab_release(struct hnbgw_context_map *map, ranap_message *message)
{
bool rab_release_req;
struct osmo_fsm_inst *fi = map->mgw_fi;
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
/* Check if the RAB that is handled by this FSM is addressed by the release request */
rab_release_req = ranap_rab_ass_req_ies_check_release(&message->msg.raB_AssignmentRequestIEs,
mgw_fsm_priv->rab_id);
if (!rab_release_req) {
LOGPFSML(map->mgw_fi, LOGL_ERROR, "RAB-AssignmentRequest does not contain any RAB-RelaseItem with RAB-ID %u\n", mgw_fsm_priv->rab_id);
return false;
}
LOGPFSML(map->mgw_fi, LOGL_NOTICE, "MSC asked to release RAB-ID %u\n", mgw_fsm_priv->rab_id);
return true;
}
/*! Allocate MGW FSM and handle RANAP RAB AssignmentRequest.
* \param[in] map hnbgw context map that is responsible for this call.
* \param[in] ranap_msg msgb containing RANAP RAB AssignmentRequest at msgb_l2(), allocated in OTC_SELECT.
* This function may talloc_steal(ranap_msg) to keep it for later.
* \param[in] message decoded RANAP message container, allocated in OTC_SELECT.
* This function may talloc_steal(message) to keep it for later.
* \returns 0 on success; negative on error. */
int handle_cs_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message)
{
static bool initialized = false;
struct mgw_fsm_priv *mgw_fsm_priv;
char fsm_name[255];
OSMO_ASSERT(!map->is_ps);
/* Initialize FSM if not done yet */
if (!initialized) {
OSMO_ASSERT(osmo_fsm_register(&mgw_fsm) == 0);
initialized = true;
}
/* There may be CS RAB Assignment Requests without actual RTP information: after a normal RAB Assignment,
* another RAB Assignment may follow, modifying some RAB parameters. When there is no RTP info in the message,
* there is no RTP to redirect via MGW, hence just forward as-is. */
if (is_rab_ass_without_tli(map, message)) {
LOG_MAP(map, DCN, LOGL_INFO,
"Rx RAB Assignment Request without transportLayerInformation, forwarding as-is\n");
return map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, ranap_msg);
}
/* The RTP stream negotiation usually begins with a RAB-AssignmentRequest and ends with an IU-Release, however
* it may also be that the MSC decides to release the RAB with a dedicated RAB-AssignmentRequest that contains
* a ReleaseList. In this case an FSM will already be present. */
if (map->mgw_fi) {
/* Check if the RAB-AssignmentRequest contains a RAB-ReleaseItem that matches the RAB-ID we are
* managing in this HNBGW context map. */
if (is_our_rab_release(map, message))
return release_mgw_fsm(map, ranap_msg);
/* The RAB-ReleaseItem in the incoming message should match the RAB ID we are managing. A mismatch may
* mean that there is an inconsistency between the HNBGW and the MSC state and the MGW FSM on the HNBGW
* side may serve an abandonned connection, which we will now close. However we must also assume that
* the incoming message may still contain a RAB-Assignment for a new RTP stream, so we still must
* continue with the message evaluation. */
osmo_fsm_inst_state_chg(map->mgw_fi, MGW_ST_FAILURE, 0, 0);
OSMO_ASSERT(map->mgw_fi == NULL);
}
/* This FSM only supports RAB assignments with a single RAB assignment only. This limitation has been taken
* into account under the assumption that voice calls typically require a single RAB only. Nevertheless, we
* will block all incoming RAB assignments that try to assign more (or less) than one RAB. */
if (ranap_rab_ass_req_ies_get_count(&message->msg.raB_AssignmentRequestIEs) != 1) {
LOG_MAP(map, DMGW, LOGL_ERROR,
"%s() RAB-AssignmentRequest with more than one RAB assignment -- abort!\n",
__func__);
tx_release_req(map);
return -1;
}
mgw_fsm_priv = talloc_zero(map, struct mgw_fsm_priv);
mgw_fsm_priv->map = map;
talloc_steal(mgw_fsm_priv, message);
mgw_fsm_priv->ranap_rab_ass_req_message = message;
/* Allocate FSM */
snprintf(fsm_name, sizeof(fsm_name), "mgw-fsm-%u-%u", map->rua_ctx_id, mgw_fsm_priv->rab_id);
map->mgw_fi = osmo_fsm_inst_alloc(&mgw_fsm, map, mgw_fsm_priv, LOGL_DEBUG, fsm_name);
/* Start the FSM */
mgw_fsm_state_chg(map->mgw_fi, MGW_ST_CRCX_HNB);
return 0;
}
/*! Handlie RANAP RAB AssignmentResponse (deliver message, complete RTP stream switching).
* \param[in] map hnbgw context map that is responsible for this call.
* \param[in] ranap_msg msgb containing RANAP RAB AssignmentResponse at msgb_l2(), allocated in OTC_SELECT.
* This function may talloc_steal(ranap_msg) to keep it for later.
* \param[in] message decoded RANAP message container, allocated in OTC_SELECT.
* This function may talloc_steal(message) to keep it for later.
* \returns 0 on success; negative on error. */
int mgw_fsm_handle_cs_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message)
{
struct mgw_fsm_priv *mgw_fsm_priv;
OSMO_ASSERT(ranap_msg);
OSMO_ASSERT(!map->is_ps);
if (!map->mgw_fi) {
/* NOTE: This situation is a corner-case. We may end up here when the co-located MGW caused a problem
* on the way between RANAP RAB Assignment Request and RANAP RAB Assignment Response. */
LOG_MAP(map, DMGW, LOGL_ERROR,
"%s() no MGW fsm -- sending Iu-Release-Request!\n",
__func__);
/* Send a release request, to make sure that the MSC is aware of the problem. */
tx_release_req(map);
return -1;
}
if (map->mgw_fi->state == MGW_ST_ESTABLISHED) {
/* This is a response to a second RAB Assignment Request, which only modified some RAB config on top of
* an earlier RAB Assignment. Just forward the response as-is, we already have our MGW set up and need
* no info from it. (i.e. we don't support modifying the RTP address.) */
return map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg);
}
mgw_fsm_priv = map->mgw_fi->priv;
talloc_steal(mgw_fsm_priv, ranap_msg);
mgw_fsm_priv->ranap_rab_ass_resp_msgb = ranap_msg;
talloc_steal(mgw_fsm_priv, message);
mgw_fsm_priv->ranap_rab_ass_resp_message = message;
osmo_fsm_inst_dispatch(map->mgw_fi, MGW_EV_RAB_ASS_RESP, NULL);
return 0;
}
/*! Release the FSM and clear its associated RTP streams.
* \ptmap[in] map hnbgw context map that is responsible for this call.
* \returns 0 on success; negative on error. */
int mgw_fsm_release(struct hnbgw_context_map *map)
{
OSMO_ASSERT(!map->is_ps);
if (!map->mgw_fi)
return -EINVAL;
osmo_fsm_inst_dispatch(map->mgw_fi, MGW_EV_RELEASE, NULL);
return 0;
}
/* determine the number of elapsed active RAB milli-seconds since last call */
uint64_t mgw_fsm_get_elapsed_ms(struct hnbgw_context_map *map, const struct timespec *now)
{
struct mgw_fsm_priv *mgw_fsm_priv;
struct timespec elapsed;
uint64_t elapsed_ms;
OSMO_ASSERT(!map->is_ps);
if (!map->mgw_fi)
return 0;
OSMO_ASSERT(map->mgw_fi->fsm == &mgw_fsm);
mgw_fsm_priv = map->mgw_fi->priv;
/* Ignore RABs whose activation timestamps are not yet set. */
if (!timespecisset(&mgw_fsm_priv->active_stored))
return 0;
/* Calculate elapsed time since last storage */
timespecsub(now, &mgw_fsm_priv->active_stored, &elapsed);
elapsed_ms = elapsed.tv_sec * 1000 + elapsed.tv_nsec / 1000000;
/* Update storage time */
mgw_fsm_priv->active_stored = *now;
return elapsed_ms;
}

1046
src/osmo-hnbgw/nft_kpi.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,377 @@
/* OsmoHNBGW main routine */
/* (C) 2015-2024 by Harald Welte <laforge@gnumonks.org>
* (C) 2016-2023 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 <signal.h>
#include <getopt.h>
#include "config.h"
#include <osmocom/core/application.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/stats.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/misc.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/ports.h>
#include <osmocom/vty/stats.h>
#include <osmocom/ctrl/control_vty.h>
#include <osmocom/ctrl/ports.h>
#include <osmocom/netif/stream.h>
#include <osmocom/sigtran/protocol/m3ua.h>
#include <osmocom/ranap/ranap_common.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/hnbgw_pfcp.h>
static struct {
int daemonize;
const char *config_file;
bool log_disable_color;
bool log_enable_timestamp;
int log_level;
const char *log_category_mask;
} hnbgw_cmdline_config = {
0,
"osmo-hnbgw.cfg",
false,
false,
0,
NULL,
};
static void signal_handler(int signum)
{
fprintf(stdout, "signal %u received\n", signum);
switch (signum) {
case SIGABRT:
/* in case of abort, we want to obtain a talloc report and
* then run default SIGABRT handler, who will generate coredump
* and abort the process. abort() should do this for us after we
* return, but program wouldn't exit if an external SIGABRT is
* received.
*/
talloc_report(tall_vty_ctx, stderr);
talloc_report_full(g_hnbgw, stderr);
signal(SIGABRT, SIG_DFL);
raise(SIGABRT);
break;
case SIGUSR1:
talloc_report(tall_vty_ctx, stderr);
talloc_report_full(g_hnbgw, stderr);
break;
default:
break;
}
}
static void print_usage(void)
{
printf("Usage: osmo-hnbgw\n");
}
static void print_help(void)
{
printf(" -h --help This text.\n");
printf(" -d option --debug=DHNBAP:DRUA:DRANAP:DMAIN Enable debugging.\n");
printf(" -D --daemonize Fork the process into a background daemon.\n");
printf(" -c --config-file filename The config file to use.\n");
printf(" -s --disable-color\n");
printf(" -T --timestamp Prefix every log line with a timestamp.\n");
printf(" -V --version Print the version of OsmoHNBGW.\n");
printf(" -e --log-level number Set a global loglevel.\n");
printf("\nVTY reference generation:\n");
printf(" --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n");
printf(" --vty-ref-xml Generate the VTY reference XML output and exit.\n");
}
static void handle_long_options(const char *prog_name, const int long_option)
{
static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT;
switch (long_option) {
case 1:
vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg);
if (vty_ref_mode < 0) {
fprintf(stderr, "%s: Unknown VTY reference generation "
"mode '%s'\n", prog_name, optarg);
exit(2);
}
break;
case 2:
fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
get_value_string(vty_ref_gen_mode_names, vty_ref_mode),
get_value_string(vty_ref_gen_mode_desc, vty_ref_mode));
vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode);
exit(0);
default:
fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
exit(2);
}
}
static void handle_options(int argc, char **argv)
{
while (1) {
int option_index = 0, c;
static int long_option = 0;
static struct option long_options[] = {
{"help", 0, 0, 'h'},
{"debug", 1, 0, 'd'},
{"daemonize", 0, 0, 'D'},
{"config-file", 1, 0, 'c'},
{"disable-color", 0, 0, 's'},
{"timestamp", 0, 0, 'T'},
{"version", 0, 0, 'V' },
{"log-level", 1, 0, 'e'},
{"vty-ref-mode", 1, &long_option, 1},
{"vty-ref-xml", 0, &long_option, 2},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, "hd:Dc:sTVe:",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 0:
handle_long_options(argv[0], long_option);
break;
case 'h':
print_usage();
print_help();
exit(0);
case 's':
hnbgw_cmdline_config.log_disable_color = true;
break;
case 'd':
hnbgw_cmdline_config.log_category_mask = optarg;
break;
case 'D':
hnbgw_cmdline_config.daemonize = 1;
break;
case 'c':
hnbgw_cmdline_config.config_file = optarg;
break;
case 'T':
hnbgw_cmdline_config.log_enable_timestamp = true;
break;
case 'e':
hnbgw_cmdline_config.log_level = atoi(optarg);
break;
case 'V':
print_version(1);
exit(0);
break;
default:
/* catch unknown options *as well as* missing arguments. */
fprintf(stderr, "Error in command line options. Exiting.\n");
exit(-1);
break;
}
}
if (argc > optind) {
fprintf(stderr, "Unsupported positional arguments on command line\n");
exit(2);
}
}
int main(int argc, char **argv)
{
struct osmo_stream_srv_link *srv;
int rc;
talloc_enable_null_tracking();
/* g_hnbgw serves as the root talloc ctx, so allocate with NULL parent */
g_hnbgw_alloc(NULL);
g_hnbgw->config.rnc_id = 23;
talloc_asn1_ctx = talloc_named_const(g_hnbgw, 1, "asn1_context");
msgb_talloc_ctx_init(g_hnbgw, 0);
rc = osmo_init_logging2(g_hnbgw, &hnbgw_log_info);
if (rc < 0)
exit(1);
log_enable_multithread();
osmo_stats_init(g_hnbgw);
rc = rate_ctr_init(g_hnbgw);
if (rc) {
LOGP(DMAIN, LOGL_FATAL, "rate_ctr_init() failed with rc=%d\n", rc);
exit(1);
}
osmo_fsm_log_timeouts(true);
rc = osmo_ss7_init();
if (rc < 0) {
LOGP(DMAIN, LOGL_FATAL, "osmo_ss7_init() failed with rc=%d\n", rc);
exit(1);
}
hnbgw_vty_info.tall_ctx = g_hnbgw;
vty_init(&hnbgw_vty_info);
osmo_ss7_vty_init_asp(g_hnbgw);
osmo_sccp_vty_init();
hnbgw_vty_init();
ctrl_vty_init(g_hnbgw);
logging_vty_add_cmds();
osmo_talloc_vty_add_cmds();
osmo_stats_vty_add_cmds();
/* Handle options after vty_init(), for --version */
handle_options(argc, argv);
rc = vty_read_config_file(hnbgw_cmdline_config.config_file, NULL);
if (rc < 0) {
LOGP(DMAIN, LOGL_FATAL, "Failed to parse the config file: '%s'\n",
hnbgw_cmdline_config.config_file);
return 1;
}
/*
* cmdline options take precedence over config file, but if no options
* were passed we must not override the config file.
*/
if (hnbgw_cmdline_config.log_disable_color)
log_set_use_color(osmo_stderr_target, 0);
if (hnbgw_cmdline_config.log_category_mask)
log_parse_category_mask(osmo_stderr_target,
hnbgw_cmdline_config.log_category_mask);
if (hnbgw_cmdline_config.log_enable_timestamp)
log_set_print_timestamp(osmo_stderr_target, 1);
if (hnbgw_cmdline_config.log_level)
log_set_log_level(osmo_stderr_target,
hnbgw_cmdline_config.log_level);
rc = telnet_init_default(g_hnbgw, g_hnbgw, OSMO_VTY_PORT_HNBGW);
if (rc < 0) {
perror("Error binding VTY port");
exit(1);
}
g_hnbgw->ctrl = ctrl_interface_setup2(g_hnbgw, OSMO_CTRL_PORT_HNBGW, hnb_ctrl_node_lookup,
_LAST_CTRL_NODE_HNB);
if (!g_hnbgw->ctrl) {
LOGP(DMAIN, LOGL_ERROR, "Failed to create CTRL interface on %s:%u\n",
ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_HNBGW);
exit(1);
} else {
rc = hnb_ctrl_cmds_install();
if (rc) {
LOGP(DMAIN, LOGL_ERROR, "Failed to install CTRL interface commands\n");
return 2;
}
}
ranap_set_log_area(DRANAP);
LOGP(DHNBAP, LOGL_NOTICE, "Using RNC-Id %u\n", g_hnbgw->config.rnc_id);
OSMO_ASSERT(g_hnbgw->config.iuh_local_ip);
LOGP(DMAIN, LOGL_NOTICE, "Listening for Iuh at %s %d\n",
g_hnbgw->config.iuh_local_ip,
g_hnbgw->config.iuh_local_port);
srv = osmo_stream_srv_link_create(g_hnbgw);
if (!srv) {
perror("cannot create server");
exit(1);
}
osmo_stream_srv_link_set_data(srv, g_hnbgw);
osmo_stream_srv_link_set_proto(srv, IPPROTO_SCTP);
osmo_stream_srv_link_set_nodelay(srv, true);
osmo_stream_srv_link_set_addr(srv, g_hnbgw->config.iuh_local_ip);
osmo_stream_srv_link_set_port(srv, g_hnbgw->config.iuh_local_port);
osmo_stream_srv_link_set_accept_cb(srv, hnbgw_rua_accept_cb);
if (osmo_stream_srv_link_open(srv) < 0) {
perror("Cannot open server");
exit(1);
}
g_hnbgw->iuh = srv;
/* Initialize and connect MGCP client. */
if (hnbgw_mgw_setup() != 0)
return -EINVAL;
#if ENABLE_PFCP
/* If UPF is configured, set up PFCP socket and send Association Setup Request to UPF */
hnbgw_pfcp_init();
#endif
/* If nftables is enabled, initialize the nft table now or fail startup. This is important to immediately let
* the user know if cap_net_admin privileges are missing, and not only when the first hNodeB connects. */
if (g_hnbgw->config.nft_kpi.enable) {
#if ENABLE_NFTABLES
nft_kpi_init(g_hnbgw->config.nft_kpi.table_name);
/* There is no direct error handling here, because nftables initialization happens asynchronously.
* See nft_kpi.c nft_thread_t2m_cb(), case NFT_THREAD_INIT_TABLE to see what happens when initializing
* nftables failed. */
#else
fprintf(stderr, "ERROR: Cannot enable nft KPI, this binary was built without nftables support\n");
exit(1);
#endif
}
hnbgw_cnpool_start(g_hnbgw->sccp.cnpool_iucs);
hnbgw_cnpool_start(g_hnbgw->sccp.cnpool_iups);
if (hnbgw_cmdline_config.daemonize) {
rc = osmo_daemonize();
if (rc < 0) {
perror("Error during daemonize");
exit(1);
}
}
signal(SIGABRT, &signal_handler);
signal(SIGUSR1, &signal_handler);
signal(SIGUSR2, &signal_handler);
osmo_init_ignore_signals();
osmo_fsm_set_dealloc_ctx(OTC_SELECT);
while (1) {
rc = osmo_select_main_ctx(0);
if (rc < 0)
exit(3);
}
/* not reached */
#if ENABLE_PFCP
hnbgw_pfcp_release();
#endif
exit(0);
}

View File

@@ -0,0 +1,759 @@
/* Handle RANAP PS RAB Assignment */
/* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*/
#include <errno.h>
#include <asn1c/asn1helpers.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/prim.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/byteswap.h>
#include <arpa/inet.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/core/tdef.h>
#include <osmocom/ranap/ranap_common.h>
#include <osmocom/ranap/ranap_common_cn.h>
#include <osmocom/ranap/ranap_common_ran.h>
#include <osmocom/ranap/ranap_msg_factory.h>
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/ranap/iu_helpers.h>
#include <osmocom/pfcp/pfcp_msg.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/pfcp/pfcp_cp_peer.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/ranap_rab_ass.h>
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
#include <osmocom/hnbgw/ps_rab_fsm.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbgw/tdefs.h>
#define PORT_GTP1_U 2152
#define LOG_PS_RAB_ASS(RAB_ASS, LOGL, FMT, ARGS...) \
LOGPFSML((RAB_ASS) ? (RAB_ASS)->fi : NULL, LOGL, FMT, ##ARGS)
static const struct value_string ps_rab_ass_fsm_event_names[] = {
OSMO_VALUE_STRING(PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX),
OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_ASS_RESP),
OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_ESTABLISHED),
OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_RELEASED),
OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_FAIL),
{}
};
enum ps_rab_ass_state {
PS_RAB_ASS_ST_RX_RAB_ASS_MSG,
PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS,
PS_RAB_ASS_ST_RX_RAB_ASS_RESP,
PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED,
};
/* Represents one RANAP PS RAB Assignment Request and Response dialog.
* There may be any number of PS RAB Assignment Requests, each with any number of RABs being established. We need to
* manage these asynchronously and flexibly:
* - RABs may be assigned in a group and released one by one, or vice versa;
* - we can only forward a RAB Assignment Request / Response when all RABs appearing in it have been set up by the UPF.
*
* This structure manages the RAB Assignment procedures, and the currently set up RABs:
*
* - hnbgw_context_map
* - .ps_rab_ass_list: list of PS RAB Assignment procedures
* - ps_rab_ass_fsm: one RANAP PS RAB Assignment procedure
* - ...
* - .ps_rab_list: list of individual PS RABs
* - ps_rab_fsm: one GTP mapping with PFCP session to the UPF, for a single RAB
* - ...
*
* This ps_rab_ass_fsm lives from a received RAB Assignment Request up to the sent RAB Assignment Response; it
* deallocates when all the RABs have been set up.
*
* The ps_rab_ass_fsm sets up ps_rab_fsm instances, which live longer: up until a RAB or conn release is performed.
*/
struct ps_rab_ass {
struct llist_head entry;
struct osmo_fsm_inst *fi;
/* backpointer */
struct hnbgw_context_map *map;
ranap_message *ranap_rab_ass_req_message;
ranap_message *ranap_rab_ass_resp_message;
struct msgb *ranap_rab_ass_resp_msgb;
/* A RAB Assignment may contain more than one RAB. Each RAB sets up a distinct ps_rab_fsm (aka PFCP session) and
* reports back about local F-TEIDs assigned by the UPF. This gives the nr of RAB events we expect from
* ps_rab_fsms, without iterating the RAB Assignment message every time (minor optimisation). */
int rabs_count;
int rabs_done_count;
unsigned int rabs_rel_count;
unsigned int rabs_rel_done_count;
};
struct osmo_tdef_state_timeout ps_rab_ass_fsm_timeouts[32] = {
/* PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS is terminated by PFCP timeouts via ps_rab_fsm */
/* PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED is terminated by PFCP timeouts via ps_rab_fsm */
};
#define ps_rab_ass_fsm_state_chg(fi, state) \
osmo_tdef_fsm_inst_state_chg(fi, state, ps_rab_ass_fsm_timeouts, hnbgw_T_defs, -1)
static struct osmo_fsm ps_rab_ass_fsm;
static struct ps_rab_ass *ps_rab_ass_alloc(struct hnbgw_context_map *map)
{
struct ps_rab_ass *rab_ass;
struct osmo_fsm_inst *fi;
fi = osmo_fsm_inst_alloc(&ps_rab_ass_fsm, map, map, LOGL_DEBUG, NULL);
OSMO_ASSERT(fi);
osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-RUA-%u", hnb_context_name(map->hnb_ctx), map->rua_ctx_id);
rab_ass = talloc(fi, struct ps_rab_ass);
OSMO_ASSERT(rab_ass);
*rab_ass = (struct ps_rab_ass){
.fi = fi,
.map = map,
};
fi->priv = rab_ass;
llist_add_tail(&rab_ass->entry, &map->ps_rab_ass_list);
return rab_ass;
}
static void ps_rab_ass_failure(struct ps_rab_ass *rab_ass)
{
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "PS RAB Assignment failed\n");
/* TODO: send unsuccessful RAB Assignment Response to Core? */
/* TODO: remove RAB from Access? */
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
}
/* Add a single RAB from a RANAP PS RAB Assignment Request's list of RABs */
static int ps_rab_setup_core_remote(struct ps_rab_ass *rab_ass, RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair)
{
struct hnbgw_context_map *map = rab_ass->map;
uint8_t rab_id;
struct addr_teid f_teid = {};
bool use_x213_nsap;
struct ps_rab *rab;
RANAP_RAB_SetupOrModifyItemFirst_t first;
RANAP_TransportLayerAddress_t *transp_layer_addr;
RANAP_TransportLayerInformation_t *tli;
int rc;
if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem)
return -1;
/* Extract information about the GTP Core side */
rc = ranap_decode_rab_setupormodifyitemfirst(&first,
&protocol_ie_field_pair->firstValue);
if (rc < 0)
goto error_exit;
rab_id = first.rAB_ID.buf[0];
/* Decode GTP endpoint IP-Address */
tli = first.transportLayerInformation;
transp_layer_addr = &tli->transportLayerAddress;
rc = ranap_transp_layer_addr_decode2(&f_teid.addr, &use_x213_nsap, transp_layer_addr);
if (rc < 0)
goto error_exit;
osmo_sockaddr_set_port(&f_teid.addr.u.sa, PORT_GTP1_U);
/* Decode the GTP remote TEID */
if (tli->iuTransportAssociation.present != RANAP_IuTransportAssociation_PR_gTP_TEI) {
rc = -1;
goto error_exit;
}
f_teid.teid = osmo_load32be(tli->iuTransportAssociation.choice.gTP_TEI.buf);
f_teid.present = true;
rab_ass->rabs_count++;
rab = ps_rab_start(map, rab_id, &f_teid, use_x213_nsap, rab_ass->fi);
if (!rab) {
rc = -1;
goto error_exit;
}
rc = 0;
error_exit:
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, &first);
return rc;
}
/*! Allocate ps_rab_ass_fsm and handle PS RANAP RAB AssignmentRequest.
* \param[in] map hnbgw context map that is responsible for this conn.
* \param[in] ranap_msg msgb containing RANAP RAB AssignmentRequest at msgb_l2(), allocated in OTC_SELECT.
* This function may talloc_steal(ranap_msg) to keep it for later.
* \param[in] message decoded RANAP message container, allocated in OTC_SELECT.
* This function may talloc_steal(message) to keep it for later.
* \returns 0 on success; negative on error. */
int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message)
{
RANAP_RAB_AssignmentRequestIEs_t *ies = &message->msg.raB_AssignmentRequestIEs;
int i;
struct ps_rab_ass *rab_ass;
rab_ass = ps_rab_ass_alloc(map);
talloc_steal(rab_ass, message);
rab_ass->ranap_rab_ass_req_message = message;
/* Now rab_ass owns message and will clean it up */
if (!osmo_pfcp_cp_peer_is_associated(g_hnbgw->pfcp.cp_peer)) {
LOG_MAP(map, DLPFCP, LOGL_ERROR, "PFCP is not associated, cannot set up GTP mapping\n");
goto no_rab;
}
/* setup-or-modify list */
if (ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT) {
/* Multiple RABs may be set up, assemble in list map->ps_rab_list. */
for (i = 0; i < ies->raB_SetupOrModifyList.list.count; i++) {
RANAP_ProtocolIE_ContainerPair_t *protocol_ie_container_pair;
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
protocol_ie_container_pair = ies->raB_SetupOrModifyList.list.array[i];
protocol_ie_field_pair = protocol_ie_container_pair->list.array[0];
if (!protocol_ie_field_pair)
goto no_rab;
if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem)
goto no_rab;
if (ps_rab_setup_core_remote(rab_ass, protocol_ie_field_pair))
goto no_rab;
}
}
/* Note: ReleaseList is handled at RAB Assign Response time. */
if (rab_ass->rabs_count > 0) {
/* Got all RABs' state and their Core side GTP info in map->ps_rab_list.
* For each, a ps_rab_fsm has been started and each will call back with
* PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX or PS_RAB_ASS_EV_RAB_FAIL. */
return ps_rab_ass_fsm_state_chg(rab_ass->fi, PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS);
}
/* No Setup-or-modify, only Release RABs. Forward the message as is,
* RABs will be released at RAB ASS Response time. */
map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, ranap_msg);
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
return 0;
no_rab:
ps_rab_ass_failure(rab_ass);
return -1;
}
static void ps_rab_ass_req_check_local_f_teids(struct ps_rab_ass *rab_ass);
static void ps_rab_ass_fsm_wait_local_f_teids(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct ps_rab_ass *rab_ass = fi->priv;
switch (event) {
case PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX:
rab_ass->rabs_done_count++;
if (rab_ass->rabs_done_count < rab_ass->rabs_count) {
/* some RABs are still pending, postpone going through the message until all are done. */
return;
}
ps_rab_ass_req_check_local_f_teids(rab_ass);
return;
case PS_RAB_ASS_EV_RAB_FAIL:
ps_rab_ass_failure(rab_ass);
return;
default:
OSMO_ASSERT(false);
}
}
/* See whether all information is in so that we can forward the modified RAB Assignment Request to RUA. */
static void ps_rab_ass_req_check_local_f_teids(struct ps_rab_ass *rab_ass)
{
struct ps_rab *rab = NULL;
RANAP_RAB_AssignmentRequestIEs_t *ies = &rab_ass->ranap_rab_ass_req_message->msg.raB_AssignmentRequestIEs;
int i;
struct msgb *msg;
/* Go through all RABs in the RAB Assignment Request message and replace with the F-TEID that the UPF assigned,
* verifying that we indeed have local F-TEIDs for all RABs contained in this message. */
for (i = 0; i < ies->raB_SetupOrModifyList.list.count; i++) {
RANAP_ProtocolIE_ContainerPair_t *protocol_ie_container_pair;
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
RANAP_RAB_SetupOrModifyItemFirst_t first;
uint8_t rab_id;
int rc;
protocol_ie_container_pair = ies->raB_SetupOrModifyList.list.array[i];
protocol_ie_field_pair = protocol_ie_container_pair->list.array[0];
if (!protocol_ie_field_pair)
continue;
if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem)
continue;
/* Get to the information about the GTP Core side */
rc = ranap_decode_rab_setupormodifyitemfirst(&first,
&protocol_ie_field_pair->firstValue);
if (rc < 0)
goto continue_cleanloop;
rab_id = first.rAB_ID.buf[0];
/* Find struct ps_rab for this rab_id */
rab = ps_rab_get(rab_ass->map, rab_id);
if (!rab || !rab->access.local.present) {
/* Not ready to send on the RAB Assignment Request to RUA, a local F-TEID is missing. */
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, &first);
return;
}
/* Replace GTP endpoint */
ASN_STRUCT_FREE(asn_DEF_RANAP_TransportLayerInformation, first.transportLayerInformation);
first.transportLayerInformation = ranap_new_transp_info_gtp(&rab->access.local.addr,
rab->access.local.teid,
rab->core.use_x213_nsap);
/* Reencode to update transport-layer-information */
rc = ANY_fromType_aper(&protocol_ie_field_pair->firstValue, &asn_DEF_RANAP_RAB_SetupOrModifyItemFirst,
&first);
if (rc < 0)
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB Assignment Request failed\n");
continue_cleanloop:
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, &first);
}
if (!rab) {
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Lookup PS RAB Assignment Request failed\n");
ps_rab_ass_failure(rab_ass);
return;
}
/* Send the modified RAB Assignment Request to the hNodeB, wait for the RAB Assignment Response */
msg = ranap_rab_ass_req_encode(ies);
if (!msg) {
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB Assignment Request failed\n");
ps_rab_ass_failure(rab_ass);
return;
}
talloc_steal(OTC_SELECT, msg);
msg->l2h = msg->data;
map_rua_dispatch(rab->map, MAP_RUA_EV_TX_DIRECT_TRANSFER, msg);
/* The request message has been forwarded. The response will be handled by a new FSM instance.
* We are done. */
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
}
/* Add a single RAB from a RANAP/RUA RAB Assignment Response's list of RABs */
static int ps_rab_setup_access_remote(struct ps_rab_ass *rab_ass,
RANAP_RAB_SetupOrModifiedItem_t *rab_item)
{
struct hnbgw_context_map *map = rab_ass->map;
uint8_t rab_id;
int rc;
struct ps_rab_rx_args args = {};
rab_id = rab_item->rAB_ID.buf[0];
rc = ranap_transp_layer_addr_decode2(&args.f_teid.addr, &args.use_x213_nsap, rab_item->transportLayerAddress);
if (rc < 0)
return rc;
/* Decode the GTP remote TEID */
if (!rab_item->iuTransportAssociation
|| rab_item->iuTransportAssociation->present != RANAP_IuTransportAssociation_PR_gTP_TEI)
return -1;
args.f_teid.teid = osmo_load32be(rab_item->iuTransportAssociation->choice.gTP_TEI.buf);
args.f_teid.present = true;
args.notify_fi = rab_ass->fi;
return ps_rab_rx_access_remote_f_teid(map, rab_id, &args);
}
int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message)
{
/* hNodeB responds with its own F-TEIDs. Need to tell the UPF about those to complete the GTP mapping.
* 1. here, extract the F-TEIDs (one per RAB),
* trigger each ps_rab_fsm to do a PFCP Session Modification.
* 2. after all ps_rab_fsms responded with success, insert our Core side local F-TEIDs and send on the RAB
* Assignment Response to IuPS. (We already know the local F-TEIDs assigned by the UPF and could send on the
* RAB Assignment Response immediately, but rather wait for the PFCP mod req to succeed first.)
*
* To wait for all the RABs in this response message to complete, create a *separate* rab_ass_fsm instance from
* the one created for the earlier RAB Assignment Request message. The reason is that technically we cannot
* assume that the request and the response have exactly matching RAB IDs contained in them.
*
* In the vast majority of practical cases, there will be only one RAB Assignment Request message pending, but
* for interop, by treating each RAB on its own and by treating request and response message separately from
* each other, we are able to handle mismatching RAB IDs in request and response messages.
*/
RANAP_RAB_AssignmentResponseIEs_t *ies = &message->msg.raB_AssignmentResponseIEs;
int rc;
int i;
struct ps_rab_ass *rab_ass;
rab_ass = ps_rab_ass_alloc(map);
talloc_steal(rab_ass, message);
rab_ass->ranap_rab_ass_resp_message = message;
talloc_steal(rab_ass, ranap_msg);
rab_ass->ranap_rab_ass_resp_msgb = ranap_msg;
/* Now rab_ass owns message and will clean it up */
if (!osmo_pfcp_cp_peer_is_associated(g_hnbgw->pfcp.cp_peer)) {
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "PFCP is not associated, cannot set up GTP mapping\n");
goto no_rab;
}
/* setup-or-modify list */
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_SETUPORMODIFIEDLIST_PRESENT) {
LOG_PS_RAB_ASS(rab_ass, LOGL_INFO, "PS RAB-AssignmentResponse received, updating RABs\n");
/* Multiple RABs may be set up, bump matching FSMs in list map->ps_rab_list. */
for (i = 0; i < ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.count; i++) {
RANAP_IE_t *list_ie;
RANAP_RAB_SetupOrModifiedItemIEs_t item_ies;
list_ie = ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.array[i];
if (!list_ie)
continue;
rc = ranap_decode_rab_setupormodifieditemies_fromlist(&item_ies,
&list_ie->value);
if (rc < 0) {
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Failed to decode PS RAB-AssignmentResponse"
" SetupOrModifiedItemIEs with list index %d\n", i);
goto continue_cleanloop;
}
if (ps_rab_setup_access_remote(rab_ass, &item_ies.raB_SetupOrModifiedItem))
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Failed to apply PS RAB-AssignmentResponse"
" SetupOrModifiedItemIEs with list index %d\n", i);
rab_ass->rabs_count++;
continue_cleanloop:
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies);
}
}
/* release list */
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_RELEASEDLIST_PRESENT) {
/* Multiple RABs may be set up, assemble in list map->ps_rab_list. */
RANAP_RAB_ReleasedList_t *r_list = &ies->raB_ReleasedList;
for (i = 0; i < r_list->raB_ReleasedList_ies.list.count; i++) {
RANAP_IE_t *released_list_ie = r_list->raB_ReleasedList_ies.list.array[i];
RANAP_RAB_ReleasedItemIEs_t _rab_rel_item_ies = {};
RANAP_RAB_ReleasedItemIEs_t *rab_rel_item_ies = &_rab_rel_item_ies;
RANAP_RAB_ReleasedItem_t *rab_rel_item;
uint8_t rab_id;
struct ps_rab *rab;
if (!released_list_ie)
goto no_rab;
if (released_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_ReleasedItem)
goto no_rab;
rc = ranap_decode_rab_releaseditemies_fromlist(rab_rel_item_ies, &released_list_ie->value);
if (rc < 0) {
LOG_MAP(map, DLPFCP, LOGL_NOTICE, "Rx RAB ASS REQ (REL): Failed decoding ReleaseItem %u\n", i);
goto no_rab;
}
rab_rel_item = &rab_rel_item_ies->raB_ReleasedItem;
/* The RAB-ID is defined as a bitstring with a size of 8 (1 byte),
* See also RANAP-IEs.asn, RAB-ID ::= BIT STRING (SIZE (8)) */
rab_id = rab_rel_item->rAB_ID.buf[0];
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_ReleasedItem, rab_rel_item_ies);
rab = ps_rab_get(map, rab_id);
if (rab) {
rab_ass->rabs_rel_count++;
ps_rab_release(rab, rab_ass->fi);
} else {
LOG_MAP(map, DLPFCP, LOGL_NOTICE, "Rx RAB ASS REQ (REL) for unknown rab_id %u\n", rab_id);
}
}
}
if (rab_ass->rabs_count > 0 || rab_ass->rabs_rel_count > 0) {
/* Got all RABs' state and updated their Access side GTP info in map->ps_rab_list.
* For each RAB ID, the matching ps_rab_fsm has been instructed to tell the UPF about
* the Access Remote GTP F-TEID. Each will call back with PS_RAB_ASS_EV_RAB_ESTABLISHED
* or PS_RAB_ASS_EV_RAB_FAIL. */
return ps_rab_ass_fsm_state_chg(rab_ass->fi, PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED);
}
/* No Setup-or-modify nor Release RABs. Forward the message as is. */
rc = map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg);
if (rc < 0) {
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Sending RANAP PS RAB-AssignmentResponse failed\n");
goto no_rab;
}
/* The request message has been forwarded. We are done. */
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
return 0;
no_rab:
ps_rab_ass_failure(rab_ass);
return -1;
}
static void ps_rab_ass_resp_send_if_ready(struct ps_rab_ass *rab_ass);
static void ps_rab_ass_resp_send_if_ready_quick(struct ps_rab_ass *rab_ass)
{
/* some RABs are still pending, postpone going through the message until all are done. */
if (rab_ass->rabs_done_count < rab_ass->rabs_count)
return;
if (rab_ass->rabs_rel_done_count < rab_ass->rabs_rel_count)
return;
ps_rab_ass_resp_send_if_ready(rab_ass);
}
static void ps_rab_ass_fsm_wait_rabs_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct ps_rab_ass *rab_ass = fi->priv;
switch (event) {
case PS_RAB_ASS_EV_RAB_ESTABLISHED:
rab_ass->rabs_done_count++;
/* All RABs have succeeded, ready to forward */
ps_rab_ass_resp_send_if_ready_quick(rab_ass);
return;
case PS_RAB_ASS_EV_RAB_RELEASED:
rab_ass->rabs_rel_done_count++;
ps_rab_ass_resp_send_if_ready_quick(rab_ass);
return;
case PS_RAB_ASS_EV_RAB_FAIL:
ps_rab_ass_failure(rab_ass);
return;
default:
OSMO_ASSERT(false);
}
}
/* See whether all RABs are done establishing, and replace GTP info in the RAB Assignment Response message, so that we
* can forward the modified RAB Assignment Request to M3UA. */
static void ps_rab_ass_resp_send_if_ready(struct ps_rab_ass *rab_ass)
{
int i;
int rc;
RANAP_RAB_AssignmentResponseIEs_t *ies = &rab_ass->ranap_rab_ass_resp_message->msg.raB_AssignmentResponseIEs;
struct msgb *msg;
/* Go through all RABs in the RAB Assignment Response message and replace with the F-TEID that the UPF assigned,
* verifying that instructing the UPF has succeeded. */
for (i = 0; i < ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.count; i++) {
RANAP_IE_t *list_ie;
RANAP_RAB_SetupOrModifiedItemIEs_t item_ies;
RANAP_RAB_SetupOrModifiedItem_t *rab_item;
int rc;
uint8_t rab_id;
uint32_t teid_be;
struct ps_rab *rab;
list_ie = ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.array[i];
if (!list_ie)
continue;
rc = ranap_decode_rab_setupormodifieditemies_fromlist(&item_ies,
&list_ie->value);
if (rc < 0) {
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Failed to decode PS RAB-AssignmentResponse"
" SetupOrModifiedItemIEs with list index %d\n", i);
goto continue_cleanloop;
}
rab_item = &item_ies.raB_SetupOrModifiedItem;
rab_id = rab_item->rAB_ID.buf[0];
/* Find struct ps_rab for this rab_id */
rab = ps_rab_get(rab_ass->map, rab_id);
if (!ps_rab_is_established(rab)) {
/* Not ready to send on the RAB Assignment Response to M3UA, still waiting for it to be
* established */
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies);
return;
}
/* Replace GTP endpoint.
* memory: ranap_new_transp_layer_addr() frees previous buffer in transportLayerAddress, if any. The
* entire rab_item is freed along with item_ies.
*
* BTW, If I free this explicitly below with
* ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_BIT_STRING, &rab_item->transportLayerAddress);
* that causes a leak in talloc_asn1_ctx: "iu_helpers.c:205 contains 20 bytes in 1 blocks".
* I couldn't figure out why, but things are freed properly when leaving it all up to item_ies.
*/
if (ranap_new_transp_layer_addr(rab_item->transportLayerAddress, &rab->core.local.addr,
rab->access.use_x213_nsap) < 0) {
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies);
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB-AssignmentResponse failed\n");
ps_rab_ass_failure(rab_ass);
return;
}
LOG_PS_RAB_ASS(rab_ass, LOGL_DEBUG, "Re-encoding RANAP PS RAB-AssignmentResponse: RAB %u:"
" RUA sent F-TEID %s-0x%x; replacing with %s-0x%x\n",
rab_id,
osmo_sockaddr_to_str_c(OTC_SELECT, &rab->access.remote.addr), rab->access.remote.teid,
osmo_sockaddr_to_str_c(OTC_SELECT, &rab->core.local.addr), rab->core.local.teid);
teid_be = htonl(rab->core.local.teid);
rab_item->iuTransportAssociation->present = RANAP_IuTransportAssociation_PR_gTP_TEI;
/* memory: OCTET_STRING_fromBuf() frees previous buffer in gTP_TEI, if any. The entire rab_item is freed
* along with item_ies. */
OCTET_STRING_fromBuf(&rab_item->iuTransportAssociation->choice.gTP_TEI,
(const char *)&teid_be, sizeof(teid_be));
/* Reencode this list item in the RANAP message */
rc = ANY_fromType_aper(&list_ie->value, &asn_DEF_RANAP_RAB_SetupOrModifiedItem, rab_item);
if (rc < 0) {
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies);
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB-AssignmentResponse failed\n");
ps_rab_ass_failure(rab_ass);
return;
}
continue_cleanloop:
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies);
}
msg = rab_ass->ranap_rab_ass_resp_msgb;
rab_ass->ranap_rab_ass_resp_msgb = NULL;
talloc_steal(OTC_SELECT, msg);
/* Replaced all the GTP info, re-encode the message. Since we are replacing data 1:1, taking care to use the
* same IP address encoding, the resulting message size must be identical to the original message size. */
rc = ranap_rab_ass_resp_encode(msgb_l2(msg), msgb_l2len(msg), ies);
if (rc < 0) {
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB-AssignmentResponse failed\n");
ps_rab_ass_failure(rab_ass);
return;
}
LOG_PS_RAB_ASS(rab_ass, LOGL_INFO, "Sending RANAP PS RAB-AssignmentResponse with mapped GTP info\n");
rc = map_sccp_dispatch(rab_ass->map, MAP_SCCP_EV_TX_DATA_REQUEST, msg);
if (rc < 0) {
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Sending RANAP PS RAB-AssignmentResponse failed\n");
ps_rab_ass_failure(rab_ass);
return;
}
/* The request message has been forwarded. We are done. */
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
}
static void ps_rab_ass_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct ps_rab_ass *rab_ass = fi->priv;
struct ps_rab *rab;
llist_for_each_entry(rab, &rab_ass->map->ps_rab_list, entry) {
if (rab->req_fi == fi)
rab->req_fi = NULL;
if (rab->resp_fi == fi)
rab->resp_fi = NULL;
}
/* remove from map->ps_rab_ass_list */
llist_del(&rab_ass->entry);
}
void hnbgw_gtpmap_release(struct hnbgw_context_map *map)
{
struct ps_rab_ass *rab_ass, *next;
struct ps_rab *rab, *next2;
llist_for_each_entry_safe(rab, next2, &map->ps_rab_list, entry) {
ps_rab_release(rab, NULL);
}
llist_for_each_entry_safe(rab_ass, next, &map->ps_rab_ass_list, entry) {
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
}
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state ps_rab_ass_fsm_states[] = {
[PS_RAB_ASS_ST_RX_RAB_ASS_MSG] = {
.name = "RX_RAB_ASS_MSG",
.out_state_mask = 0
| S(PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS)
| S(PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED)
,
},
[PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS] = {
.name = "WAIT_LOCAL_F_TEIDS",
.action = ps_rab_ass_fsm_wait_local_f_teids,
.in_event_mask = 0
| S(PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX)
| S(PS_RAB_ASS_EV_RAB_FAIL)
,
},
[PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED] = {
.name = "WAIT_RABS_ESTABLISHED",
.action = ps_rab_ass_fsm_wait_rabs_established,
.in_event_mask = 0
| S(PS_RAB_ASS_EV_RAB_ESTABLISHED)
| S(PS_RAB_ASS_EV_RAB_RELEASED)
| S(PS_RAB_ASS_EV_RAB_FAIL)
,
},
};
int ps_rab_ass_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct ps_rab_ass *rab_ass = fi->priv;
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Timeout of " OSMO_T_FMT "\n", OSMO_T_FMT_ARGS(fi->T));
/* terminate */
return 1;
}
static struct osmo_fsm ps_rab_ass_fsm = {
.name = "ps_rab_ass",
.states = ps_rab_ass_fsm_states,
.num_states = ARRAY_SIZE(ps_rab_ass_fsm_states),
.log_subsys = DRANAP,
.event_names = ps_rab_ass_fsm_event_names,
.cleanup = ps_rab_ass_fsm_cleanup,
.timer_cb = ps_rab_ass_fsm_timer_cb,
};
static __attribute__((constructor)) void ps_rab_ass_fsm_register(void)
{
OSMO_ASSERT(osmo_fsm_register(&ps_rab_ass_fsm) == 0);
}

833
src/osmo-hnbgw/ps_rab_fsm.c Normal file
View File

@@ -0,0 +1,833 @@
/* Handle PFCP communication with the UPF for a single RAB. */
/* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*/
#include <errno.h>
#include <osmocom/core/tdef.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/pfcp/pfcp_cp_peer.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/tdefs.h>
#include <osmocom/hnbgw/ps_rab_fsm.h>
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
#define LOG_PS_RAB(RAB, LOGL, FMT, ARGS...) \
LOGPFSML((RAB) ? (RAB)->fi : NULL, LOGL, FMT, ##ARGS)
enum ps_rab_state {
PS_RAB_ST_RX_CORE_REMOTE_F_TEID,
PS_RAB_ST_WAIT_PFCP_EST_RESP,
PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID,
PS_RAB_ST_WAIT_PFCP_MOD_RESP,
PS_RAB_ST_ESTABLISHED,
PS_RAB_ST_WAIT_PFCP_DEL_RESP,
PS_RAB_ST_WAIT_USE_COUNT,
};
enum ps_rab_event {
PS_RAB_EV_PFCP_EST_RESP,
PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID,
PS_RAB_EV_PFCP_MOD_RESP,
PS_RAB_EV_PFCP_DEL_RESP,
PS_RAB_EV_USE_COUNT_ZERO,
};
static const struct value_string ps_rab_fsm_event_names[] = {
OSMO_VALUE_STRING(PS_RAB_EV_PFCP_EST_RESP),
OSMO_VALUE_STRING(PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID),
OSMO_VALUE_STRING(PS_RAB_EV_PFCP_MOD_RESP),
OSMO_VALUE_STRING(PS_RAB_EV_PFCP_DEL_RESP),
OSMO_VALUE_STRING(PS_RAB_EV_USE_COUNT_ZERO),
{}
};
struct osmo_tdef_state_timeout ps_rab_fsm_timeouts[32] = {
/* PS_RAB_ST_WAIT_PFCP_EST_RESP is terminated by PFCP timeouts via resp_cb() */
/* PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID is terminated by ps_rab_ass_fsm */
/* PS_RAB_ST_WAIT_PFCP_MOD_RESP is terminated by PFCP timeouts via resp_cb() */
/* PS_RAB_ST_WAIT_PFCP_DEL_RESP is terminated by PFCP timeouts via resp_cb() */
};
enum pdr_far_id {
ID_CORE_TO_ACCESS = 1,
ID_ACCESS_TO_CORE = 2,
};
#define ps_rab_fsm_state_chg(state) \
osmo_tdef_fsm_inst_state_chg(fi, state, ps_rab_fsm_timeouts, hnbgw_T_defs, -1)
#define PS_RAB_USE_ACTIVE "active"
static struct osmo_fsm ps_rab_fsm;
static int ps_rab_fsm_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line);
static struct ps_rab *ps_rab_alloc(struct hnbgw_context_map *map, uint8_t rab_id)
{
struct osmo_fsm_inst *fi;
struct ps_rab *rab;
/* Allocate with the global hnb_gw, so that we can gracefully handle PFCP release even if a hnb_ctx gets
* deallocated. */
fi = osmo_fsm_inst_alloc(&ps_rab_fsm, g_hnbgw, NULL, LOGL_DEBUG, NULL);
OSMO_ASSERT(fi);
osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-RUA-%u-RAB-%u", hnb_context_name(map->hnb_ctx), map->rua_ctx_id,
rab_id);
rab = talloc(fi, struct ps_rab);
OSMO_ASSERT(rab);
*rab = (struct ps_rab){
.fi = fi,
.map = map,
.rab_id = rab_id,
.use_count = {
.talloc_object = rab,
.use_cb = ps_rab_fsm_use_cb,
},
};
fi->priv = rab;
OSMO_ASSERT(osmo_use_count_get_put(&rab->use_count, PS_RAB_USE_ACTIVE, 1) == 0);
llist_add_tail(&rab->entry, &map->ps_rab_list);
return rab;
}
/* Iterate all ps_rab instances of all context maps and return the one matching the given SEID.
* If is_cp_seid == true, match seid with rab->cp_seid (e.g. for received PFCP messages).
* Otherwise match seid with rab->up_f_seid.seid (e.g. for sent PFCP messages). */
struct ps_rab *ps_rab_find_by_seid(uint64_t seid, bool is_cp_seid)
{
struct hnb_context *hnb;
llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) {
struct hnbgw_context_map *map;
llist_for_each_entry(map, &hnb->map_list, hnb_list) {
struct ps_rab *rab;
llist_for_each_entry(rab, &map->ps_rab_list, entry) {
uint64_t rab_seid = is_cp_seid ? rab->cp_seid : rab->up_f_seid.seid;
if (rab_seid == seid)
return rab;
}
}
}
return NULL;
}
void ps_rab_pfcp_set_msg_ctx(struct ps_rab *rab, struct osmo_pfcp_msg *m)
{
if (m->ctx.session_fi)
return;
m->ctx.session_fi = rab->fi;
m->ctx.session_use_count = &rab->use_count;
m->ctx.session_use_token = "PFCP_MSG";
OSMO_ASSERT(osmo_use_count_get_put(m->ctx.session_use_count, m->ctx.session_use_token, 1) == 0);
}
static struct osmo_pfcp_msg *ps_rab_new_pfcp_msg_req(struct ps_rab *rab, enum osmo_pfcp_message_type msg_type)
{
struct osmo_pfcp_msg *m = osmo_pfcp_cp_peer_new_req(g_hnbgw->pfcp.cp_peer, msg_type);
m->h.seid_present = true;
m->h.seid = rab->up_f_seid.seid;
ps_rab_pfcp_set_msg_ctx(rab, m);
return m;
}
struct ps_rab *ps_rab_get(struct hnbgw_context_map *map, uint8_t rab_id)
{
struct ps_rab *rab;
llist_for_each_entry(rab, &map->ps_rab_list, entry) {
if (rab->rab_id != rab_id)
continue;
return rab;
}
return NULL;
}
bool ps_rab_is_established(const struct ps_rab *rab)
{
return rab && rab->fi->state == PS_RAB_ST_ESTABLISHED;
}
void ps_rab_failure(struct ps_rab *rab)
{
if (rab->req_fi)
osmo_fsm_inst_dispatch(rab->req_fi, PS_RAB_ASS_EV_RAB_FAIL, rab);
if (rab->resp_fi)
osmo_fsm_inst_dispatch(rab->resp_fi, PS_RAB_ASS_EV_RAB_FAIL, rab);
ps_rab_release(rab, NULL);
}
struct ps_rab *ps_rab_start(struct hnbgw_context_map *map, uint8_t rab_id,
const struct addr_teid *core_f_teid, bool use_x213_nsap,
struct osmo_fsm_inst *req_fi)
{
struct osmo_fsm_inst *fi;
struct ps_rab *rab;
rab = ps_rab_alloc(map, rab_id);
fi = rab->fi;
rab->req_fi = req_fi;
rab->core.remote = *core_f_teid;
rab->core.use_x213_nsap = use_x213_nsap;
/* Got the RAB's Core side GTP info. Route the GTP for via the local UPF.
* Establish a PFCP session with the UPF: tell it about the Core side GTP endpoint and request local F-TEIDs. */
if (ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_PFCP_EST_RESP)) {
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
return NULL;
}
return rab;
}
#define set_netinst(NETINST_MEMBER, STRING) do { \
if ((STRING) && *(STRING)) { \
NETINST_MEMBER##_present = true; \
OSMO_STRLCPY_ARRAY(NETINST_MEMBER.str, STRING); \
} \
} while (0)
/* Add two PDR and two FAR to the PFCP Session Establishment Request message, according to the information found in rab.
*/
static int rab_to_pfcp_session_est_req(struct osmo_pfcp_msg_session_est_req *ser, struct ps_rab *rab)
{
if (ser->create_pdr_count + 2 > ARRAY_SIZE(ser->create_pdr)
|| ser->create_far_count + 2 > ARRAY_SIZE(ser->create_far)) {
LOG_PS_RAB(rab, LOGL_ERROR, "insufficient space for Create PDR / Create FAR IEs\n");
return -1;
}
/* Core to Access:
* - UPF should return an F-TEID for the PDR, to be forwarded back to Core later in
* RANAP RAB Assignment Response.
* - we don't know the Access side GTP address yet, so set FAR to DROP.
*/
ser->create_pdr[ser->create_pdr_count] = (struct osmo_pfcp_ie_create_pdr){
.pdr_id = ID_CORE_TO_ACCESS,
.precedence = 255,
.pdi = {
.source_iface = OSMO_PFCP_SOURCE_IFACE_CORE,
.local_f_teid_present = true,
.local_f_teid = {
.choose_flag = true,
.choose = {
.ipv4_addr = true,
},
},
},
.outer_header_removal_present = true,
.outer_header_removal = {
.desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4,
},
.far_id_present = true,
.far_id = ID_CORE_TO_ACCESS,
};
set_netinst(ser->create_pdr[ser->create_pdr_count].pdi.network_inst, g_hnbgw->config.pfcp.netinst.core);
ser->create_pdr_count++;
ser->create_far[ser->create_far_count] = (struct osmo_pfcp_ie_create_far){
.far_id = ID_CORE_TO_ACCESS,
};
osmo_pfcp_bits_set(ser->create_far[ser->create_far_count].apply_action.bits,
OSMO_PFCP_APPLY_ACTION_DROP, true);
ser->create_far_count++;
/* Access to Core:
* - UPF should return an F-TEID for the PDR, to be forwarded to Access in the modified
* RANAP RAB Assignment Request.
* - we already know the Core's GTP endpoint F-TEID, so fully set up this FAR.
*/
ser->create_pdr[ser->create_pdr_count] = (struct osmo_pfcp_ie_create_pdr){
.pdr_id = ID_ACCESS_TO_CORE,
.precedence = 255,
.pdi = {
.source_iface = OSMO_PFCP_SOURCE_IFACE_ACCESS,
.local_f_teid_present = true,
.local_f_teid = {
.choose_flag = true,
.choose = {
.ipv4_addr = true,
},
},
},
.outer_header_removal_present = true,
.outer_header_removal = {
.desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4,
},
.far_id_present = true,
.far_id = ID_ACCESS_TO_CORE,
};
set_netinst(ser->create_pdr[ser->create_pdr_count].pdi.network_inst, g_hnbgw->config.pfcp.netinst.access);
ser->create_pdr_count++;
ser->create_far[ser->create_far_count] = (struct osmo_pfcp_ie_create_far){
.far_id = ID_ACCESS_TO_CORE,
.forw_params_present = true,
.forw_params = {
.destination_iface = OSMO_PFCP_DEST_IFACE_CORE,
.outer_header_creation_present = true,
.outer_header_creation = {
.teid_present = true,
.teid = rab->core.remote.teid,
.ip_addr.v4_present = true,
.ip_addr.v4 = rab->core.remote.addr,
},
},
};
osmo_pfcp_bits_set(ser->create_far[ser->create_far_count].forw_params.outer_header_creation.desc_bits,
OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true);
osmo_pfcp_bits_set(ser->create_far[ser->create_far_count].apply_action.bits,
OSMO_PFCP_APPLY_ACTION_FORW, true);
set_netinst(ser->create_far[ser->create_far_count].forw_params.network_inst, g_hnbgw->config.pfcp.netinst.core);
ser->create_far_count++;
return 0;
}
static int on_pfcp_est_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg);
static void ps_rab_fsm_wait_pfcp_est_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct ps_rab *rab = fi->priv;
struct osmo_pfcp_msg *m;
struct osmo_pfcp_ie_f_seid cp_f_seid;
struct osmo_pfcp_msg_session_est_req *ser;
/* So far we have the rab->core.remote information. Send that to the UPF.
* Also request all local GTP endpoints from UPF (rab->{core,access}.local) */
m = ps_rab_new_pfcp_msg_req(rab, OSMO_PFCP_MSGT_SESSION_EST_REQ);
/* Send UP-SEID as zero, the UPF has yet to assign a SEID for itself remotely */
m->h.seid = 0;
/* Make a new CP-SEID, our local reference for the PFCP session. */
rab->cp_seid = osmo_pfcp_cp_peer_next_seid(g_hnbgw->pfcp.cp_peer);
cp_f_seid = (struct osmo_pfcp_ie_f_seid){
.seid = rab->cp_seid,
};
osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, &osmo_pfcp_endpoint_get_cfg(g_hnbgw->pfcp.ep)->local_addr);
m->ies.session_est_req = (struct osmo_pfcp_msg_session_est_req){
.node_id = m->ies.session_est_req.node_id,
.cp_f_seid_present = true,
.cp_f_seid = cp_f_seid,
};
ser = &m->ies.session_est_req;
/* Create PDR+FAR pairs */
if (rab_to_pfcp_session_est_req(ser, rab)) {
LOG_PS_RAB(rab, LOGL_ERROR, "Failed to compose PFCP message\n");
osmo_pfcp_msg_free(m);
ps_rab_failure(rab);
return;
}
/* Send PFCP Session Establishment Request to UPF, wait for response. */
m->ctx.resp_cb = on_pfcp_est_resp;
if (osmo_pfcp_endpoint_tx(g_hnbgw->pfcp.ep, m)) {
LOG_PS_RAB(rab, LOGL_ERROR, "Failed to send PFCP message\n");
ps_rab_failure(rab);
}
}
static int on_pfcp_est_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg)
{
struct ps_rab *rab = req->ctx.session_fi->priv;
/* Send as FSM event to ensure this step is currently allowed */
osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_PFCP_EST_RESP, rx_resp);
/* By returning 0 here, the rx_resp message is not dispatched "again" to pfcp_ep->rx_msg(). We've handled it
* here already. */
return 0;
}
static void ps_rab_rx_pfcp_est_resp(struct osmo_fsm_inst *fi, struct osmo_pfcp_msg *rx);
static void ps_rab_fsm_wait_pfcp_est_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case PS_RAB_EV_PFCP_EST_RESP:
ps_rab_rx_pfcp_est_resp(fi, data);
break;
default:
OSMO_ASSERT(false);
}
}
/* Look for dst->local.pdr_id in ser->created_pdr[], and copy the GTP endpoint info to dst->local.addr_teid, if found. */
static int get_local_f_teid_from_created_pdr(struct half_gtp_map *dst, struct osmo_pfcp_msg_session_est_resp *ser,
uint8_t pdr_id)
{
int i;
for (i = 0; i < ser->created_pdr_count; i++) {
struct osmo_pfcp_ie_created_pdr *cpdr = &ser->created_pdr[i];
if (cpdr->pdr_id != pdr_id)
continue;
if (!cpdr->local_f_teid_present)
continue;
if (cpdr->local_f_teid.choose_flag)
continue;
if (!cpdr->local_f_teid.fixed.ip_addr.v4_present)
continue;
dst->local.addr = cpdr->local_f_teid.fixed.ip_addr.v4;
dst->local.teid = cpdr->local_f_teid.fixed.teid;
dst->local.present = true;
return 0;
}
return -1;
}
static void ps_rab_rx_pfcp_est_resp(struct osmo_fsm_inst *fi, struct osmo_pfcp_msg *rx)
{
struct ps_rab *rab = fi->priv;
enum osmo_pfcp_cause *cause;
struct osmo_pfcp_msg_session_est_resp *ser;
if (!rx) {
/* This happens when no response has arrived after all PFCP timeouts and retransmissions. */
LOG_PS_RAB(rab, LOGL_ERROR, "No response to PFCP Session Establishment Request\n");
goto pfcp_session_est_failed;
}
ser = &rx->ies.session_est_resp;
cause = osmo_pfcp_msg_cause(rx);
if (!cause || *cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) {
LOG_PS_RAB(rab, LOGL_ERROR, "PFCP Session Establishment Response was not successful: %s\n",
cause ? osmo_pfcp_cause_str(*cause) : "NULL");
goto pfcp_session_est_failed;
}
/* Get the UPF's SEID for future messages for this PFCP session */
if (!ser->up_f_seid_present) {
LOG_PS_RAB(rab, LOGL_ERROR, "PFCP Session Establishment Response lacks a UP F-SEID\n");
goto pfcp_session_est_failed;
}
rab->up_f_seid = ser->up_f_seid;
if (rab->release_requested) {
/* The UE conn or the entire HNB has released while we were waiting for a PFCP response. Now that there
* is a remote SEID, we can finally delete the session that we asked for earlier. */
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_PFCP_DEL_RESP);
return;
}
/* Get the UPF's local F-TEIDs for both Core and Access */
if (get_local_f_teid_from_created_pdr(&rab->core, ser, ID_CORE_TO_ACCESS)
|| get_local_f_teid_from_created_pdr(&rab->access, ser, ID_ACCESS_TO_CORE)) {
LOG_PS_RAB(rab, LOGL_ERROR, "Missing F-TEID in PFCP Session Establishment Response\n");
ps_rab_failure(rab);
return;
}
if (rab->req_fi)
osmo_fsm_inst_dispatch(rab->req_fi, PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX, rab);
/* The RAB Assignment Response will yield the hNodeB's F-TEID, i.e. the F-TEID we are supposed to send to Access
* in outgoing GTP packets. */
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID);
return;
pfcp_session_est_failed:
if (rab->release_requested) {
/* the RAB was released and we were waiting for some PFCP responsewhile waiting for a response, and now
* we know that no session has been created. No PFCP left, deallocate. */
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_USE_COUNT);
return;
}
ps_rab_failure(rab);
}
int ps_rab_rx_access_remote_f_teid(struct hnbgw_context_map *map, uint8_t rab_id,
const struct ps_rab_rx_args *args)
{
int rc;
struct ps_rab *rab = ps_rab_get(map, rab_id);
if (!rab) {
LOG_MAP(map, DLPFCP, LOGL_ERROR, "There is no RAB with id %u\n", rab_id);
return -ENOENT;
}
/* Dispatch as event to make sure this is currently allowed */
rc = osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID, (void *)args);
if (rc)
return rc;
return 0;
}
static void ps_rab_fsm_wait_access_remote_f_teid(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct ps_rab *rab = fi->priv;
const struct ps_rab_rx_args *args;
switch (event) {
case PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID:
args = data;
rab->resp_fi = args->notify_fi;
rab->access.use_x213_nsap = args->use_x213_nsap;
rab->access.remote = args->f_teid;
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_PFCP_MOD_RESP);
return;
default:
OSMO_ASSERT(false);
}
}
/* Add an Update FAR to the PFCP Session Modification Request message, updating a remote F-TEID. */
static int rab_to_pfcp_session_mod_req_upd_far(struct osmo_pfcp_msg_session_mod_req *smr,
uint32_t far_id, const struct addr_teid *remote_f_teid,
const char *far_netinst)
{
if (smr->upd_far_count + 1 > ARRAY_SIZE(smr->upd_far))
return -1;
smr->upd_far[smr->upd_far_count] = (struct osmo_pfcp_ie_upd_far){
.far_id = far_id,
.apply_action_present = true,
/* apply_action.bits set below */
.upd_forw_params_present = true,
.upd_forw_params = {
.outer_header_creation_present = true,
.outer_header_creation = {
/* desc_bits set below */
.teid_present = true,
.teid = remote_f_teid->teid,
.ip_addr.v4_present = true,
.ip_addr.v4 = remote_f_teid->addr,
},
},
};
osmo_pfcp_bits_set(smr->upd_far[smr->upd_far_count].apply_action.bits,
OSMO_PFCP_APPLY_ACTION_FORW, true);
osmo_pfcp_bits_set(smr->upd_far[smr->upd_far_count].upd_forw_params.outer_header_creation.desc_bits,
OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true);
set_netinst(smr->upd_far[smr->upd_far_count].upd_forw_params.network_inst, far_netinst);
smr->upd_far_count++;
return 0;
}
static int on_pfcp_mod_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg);
static void ps_rab_fsm_wait_pfcp_mod_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
/* We have been given the Access side's remote F-TEID, now in rab->access.remote, and we need to tell the UPF
* about it. This affects the Core to Access direction: now we know where to forward payloads coming from Core.
*/
struct ps_rab *rab = fi->priv;
struct osmo_pfcp_msg *m;
if (!(rab->up_f_seid.ip_addr.v4_present /* || rab->up_f_seid.ip_addr.v6_present */)) {
LOG_PS_RAB(rab, LOGL_ERROR, "no valid PFCP session\n");
ps_rab_failure(rab);
return;
}
m = ps_rab_new_pfcp_msg_req(rab, OSMO_PFCP_MSGT_SESSION_MOD_REQ);
if (rab_to_pfcp_session_mod_req_upd_far(&m->ies.session_mod_req, ID_CORE_TO_ACCESS, &rab->access.remote,
g_hnbgw->config.pfcp.netinst.access)) {
LOG_PS_RAB(rab, LOGL_ERROR, "error composing Update FAR IE in PFCP msg\n");
ps_rab_failure(rab);
return;
}
m->ctx.resp_cb = on_pfcp_mod_resp;
if (osmo_pfcp_endpoint_tx(g_hnbgw->pfcp.ep, m)) {
LOG_PS_RAB(rab, LOGL_ERROR, "Failed to send PFCP message\n");
ps_rab_failure(rab);
}
}
static int on_pfcp_mod_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg)
{
struct ps_rab *rab = req->ctx.session_fi->priv;
/* Send as FSM event to ensure this step is currently allowed */
osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_PFCP_MOD_RESP, rx_resp);
/* By returning 0 here, the rx_resp message is not dispatched "again" to pfcp_ep->rx_msg(). We've handled it
* here already. */
return 0;
}
static void ps_rab_rx_pfcp_mod_resp(struct osmo_fsm_inst *fi, struct osmo_pfcp_msg *rx);
static void ps_rab_fsm_wait_pfcp_mod_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case PS_RAB_EV_PFCP_MOD_RESP:
ps_rab_rx_pfcp_mod_resp(fi, data);
return;
default:
OSMO_ASSERT(false);
}
}
static void ps_rab_rx_pfcp_mod_resp(struct osmo_fsm_inst *fi, struct osmo_pfcp_msg *rx)
{
struct ps_rab *rab = fi->priv;
enum osmo_pfcp_cause *cause;
if (!rx) {
LOG_PS_RAB(rab, LOGL_ERROR, "No response to PFCP Session Modification Request\n");
ps_rab_failure(rab);
return;
}
cause = osmo_pfcp_msg_cause(rx);
if (!cause || *cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) {
LOG_PS_RAB(rab, LOGL_ERROR, "PFCP Session Modification Response was not successful: %s\n",
cause ? osmo_pfcp_cause_str(*cause) : "NULL");
ps_rab_failure(rab);
return;
}
/* This RAB is now complete. Everything went as expected, now we can forward the RAB Assignment Response to the
* CN. */
ps_rab_fsm_state_chg(PS_RAB_ST_ESTABLISHED);
}
static void ps_rab_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct ps_rab *rab = fi->priv;
if (rab->resp_fi)
osmo_fsm_inst_dispatch(rab->resp_fi, PS_RAB_ASS_EV_RAB_ESTABLISHED, rab);
}
static int on_pfcp_del_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg);
static void ps_rab_fsm_wait_pfcp_del_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
/* If a PFCP session has been established, send a Session Deletion Request and wait for the response.
* If no session is established, just terminate. */
struct ps_rab *rab = fi->priv;
struct osmo_pfcp_msg *m;
if (!(rab->up_f_seid.ip_addr.v4_present /* || rab->up_f_seid.ip_addr.v6_present */)) {
/* There is no valid PFCP session, so no need to send a Session Deletion Request */
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_USE_COUNT);
return;
}
m = ps_rab_new_pfcp_msg_req(rab, OSMO_PFCP_MSGT_SESSION_DEL_REQ);
m->ctx.resp_cb = on_pfcp_del_resp;
if (osmo_pfcp_endpoint_tx(g_hnbgw->pfcp.ep, m)) {
LOG_PS_RAB(rab, LOGL_ERROR, "Failed to send PFCP message\n");
ps_rab_failure(rab);
}
}
static int on_pfcp_del_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg)
{
struct ps_rab *rab = req->ctx.session_fi->priv;
if (errmsg)
LOG_PS_RAB(rab, LOGL_ERROR, "PFCP Session Deletion Response: %s\n", errmsg);
osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_PFCP_DEL_RESP, rx_resp);
/* By returning 0 here, the rx_resp message is not dispatched "again" to pfcp_ep->rx_msg(). We've handled it
* here already. */
return 0;
}
static void ps_rab_fsm_wait_pfcp_del_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case PS_RAB_EV_PFCP_DEL_RESP:
/* All done, terminate. Even if the Session Deletion failed, there's nothing we can do about it. */
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_USE_COUNT);
return;
default:
OSMO_ASSERT(false);
}
}
static int ps_rab_fsm_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line)
{
struct ps_rab *rab = e->use_count->talloc_object;
if (!osmo_use_count_total(&rab->use_count))
osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_USE_COUNT_ZERO, NULL);
return 0;
}
static void ps_rab_fsm_wait_use_count_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct ps_rab *rab = fi->priv;
if (rab->resp_fi)
osmo_fsm_inst_dispatch(rab->resp_fi, PS_RAB_ASS_EV_RAB_RELEASED, rab);
OSMO_ASSERT(osmo_use_count_get_put(&rab->use_count, PS_RAB_USE_ACTIVE, -1) == 0);
}
static void ps_rab_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case PS_RAB_EV_USE_COUNT_ZERO:
if (fi->state == PS_RAB_ST_WAIT_USE_COUNT)
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
/* else, ignore. */
return;
default:
OSMO_ASSERT(false);
}
}
static void ps_rab_forget_map(struct ps_rab *rab)
{
/* remove from map->ps_rab_list */
if (rab->map)
llist_del(&rab->entry);
rab->map = NULL;
}
static void ps_rab_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct ps_rab *rab = fi->priv;
ps_rab_forget_map(rab);
}
/* notify_fi can be NULL, in which case no event PS_RAB_ASS_EV_RAB_RELEASED event will be dispatched */
void ps_rab_release(struct ps_rab *rab, struct osmo_fsm_inst *notify_fi)
{
struct osmo_fsm_inst *fi = rab->fi;
ps_rab_forget_map(rab);
rab->resp_fi = notify_fi;
switch (fi->state) {
case PS_RAB_ST_RX_CORE_REMOTE_F_TEID:
/* No session requested yet. Nothing to be deleted. */
LOG_PS_RAB(rab, LOGL_NOTICE, "RAB release before PFCP Session Establishment Request, terminating\n");
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_USE_COUNT);
return;
case PS_RAB_ST_WAIT_PFCP_EST_RESP:
/* Session was requested via PFCP, but we only know the SEID to send in a deletion when the PFCP Session
* Establishment Response arrives. */
rab->release_requested = true;
LOG_PS_RAB(rab, LOGL_ERROR, "RAB release while waiting for PFCP Session Establishment Response\n");
return;
default:
/* Session has been established (and we know the SEID). Initiate deletion. */
LOG_PS_RAB(rab, LOGL_INFO, "RAB release, deleting PFCP session\n");
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_PFCP_DEL_RESP);
return;
case PS_RAB_ST_WAIT_PFCP_DEL_RESP:
/* Already requested a PFCP Session Deletion. Nothing else to do, wait for the Deletion Response (or
* timeout). */
LOG_PS_RAB(rab, LOGL_INFO, "RAB release while waiting for PFCP Session Deletion Response\n");
return;
case PS_RAB_ST_WAIT_USE_COUNT:
/* Already released, just wait for the last users (queued PFCP messages) to expire. */
LOG_PS_RAB(rab, LOGL_INFO, "RAB release, already waiting for deallocation\n");
return;
}
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state ps_rab_fsm_states[] = {
[PS_RAB_ST_RX_CORE_REMOTE_F_TEID] = {
.name = "RX_CORE_REMOTE_F_TEID",
.out_state_mask = 0
| S(PS_RAB_ST_WAIT_PFCP_EST_RESP)
| S(PS_RAB_ST_WAIT_USE_COUNT)
,
},
[PS_RAB_ST_WAIT_PFCP_EST_RESP] = {
.name = "WAIT_PFCP_EST_RESP",
.onenter = ps_rab_fsm_wait_pfcp_est_resp_onenter,
.action = ps_rab_fsm_wait_pfcp_est_resp,
.in_event_mask = 0
| S(PS_RAB_EV_PFCP_EST_RESP)
,
.out_state_mask = 0
| S(PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID)
| S(PS_RAB_ST_WAIT_USE_COUNT)
,
},
[PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID] = {
.name = "WAIT_ACCESS_REMOTE_F_TEID",
.action = ps_rab_fsm_wait_access_remote_f_teid,
.in_event_mask = 0
| S(PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID)
,
.out_state_mask = 0
| S(PS_RAB_ST_WAIT_PFCP_MOD_RESP)
| S(PS_RAB_ST_WAIT_PFCP_DEL_RESP)
| S(PS_RAB_ST_WAIT_USE_COUNT)
,
},
[PS_RAB_ST_WAIT_PFCP_MOD_RESP] = {
.name = "WAIT_PFCP_MOD_RESP",
.onenter = ps_rab_fsm_wait_pfcp_mod_resp_onenter,
.action = ps_rab_fsm_wait_pfcp_mod_resp,
.in_event_mask = 0
| S(PS_RAB_EV_PFCP_MOD_RESP)
,
.out_state_mask = 0
| S(PS_RAB_ST_ESTABLISHED)
| S(PS_RAB_ST_WAIT_PFCP_DEL_RESP)
| S(PS_RAB_ST_WAIT_USE_COUNT)
,
},
[PS_RAB_ST_ESTABLISHED] = {
.name = "ESTABLISHED",
.onenter = ps_rab_fsm_established_onenter,
.out_state_mask = 0
| S(PS_RAB_ST_WAIT_PFCP_DEL_RESP)
| S(PS_RAB_ST_WAIT_USE_COUNT)
,
},
[PS_RAB_ST_WAIT_PFCP_DEL_RESP] = {
.name = "WAIT_PFCP_DEL_RESP",
.onenter = ps_rab_fsm_wait_pfcp_del_resp_onenter,
.action = ps_rab_fsm_wait_pfcp_del_resp,
.in_event_mask = 0
| S(PS_RAB_EV_PFCP_DEL_RESP)
,
.out_state_mask = 0
| S(PS_RAB_ST_WAIT_USE_COUNT)
,
},
[PS_RAB_ST_WAIT_USE_COUNT] = {
.name = "WAIT_USE_COUNT",
.onenter = ps_rab_fsm_wait_use_count_onenter,
.in_event_mask = 0
| S(PS_RAB_EV_USE_COUNT_ZERO)
,
},
};
static struct osmo_fsm ps_rab_fsm = {
.name = "ps_rab",
.states = ps_rab_fsm_states,
.num_states = ARRAY_SIZE(ps_rab_fsm_states),
.log_subsys = DLPFCP,
.event_names = ps_rab_fsm_event_names,
.cleanup = ps_rab_fsm_cleanup,
.allstate_event_mask = S(PS_RAB_EV_USE_COUNT_ZERO),
.allstate_action = ps_rab_fsm_allstate_action,
};
static __attribute__((constructor)) void ps_rab_fsm_register(void)
{
OSMO_ASSERT(osmo_fsm_register(&ps_rab_fsm) == 0);
}

View File

@@ -0,0 +1,625 @@
/* (C) 2021 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Philipp Maier
*
* 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/>.
*
*/
/* Note: This files contains tools to decode and re-encode the RAB-AssignmentRequest. This set of tools is used by
* mgcp_fsm.c to extract and manipulate the transportLayerAddress. */
#include <errno.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/ranap/ranap_common.h>
#include <osmocom/ranap/ranap_common_cn.h>
#include <osmocom/ranap/ranap_common_ran.h>
#include <osmocom/ranap/iu_helpers.h>
#include <asn1c/asn1helpers.h>
/*! Encode RABAP RAB AssignmentRequest from RANAP_RAB_AssignmentRequestIEs.
* \ptmap[out] data user provided memory to store resulting ASN.1 encoded message.
* \ptmap[in] len length of user provided memory to store resulting ASN.1 encoded message.
* \ptmap[in] ies user provided memory with RANAP_RAB_AssignmentRequestIEs.
* \returns resulting message length on success; negative on error. */
struct msgb *ranap_rab_ass_req_encode(RANAP_RAB_AssignmentRequestIEs_t *rab_assignment_request_ies)
{
int rc;
struct msgb *msg;
RANAP_RAB_AssignmentRequest_t _rab_assignment_request = { 0 };
RANAP_RAB_AssignmentRequest_t *rab_assignment_request = &_rab_assignment_request;
rc = ranap_encode_rab_assignmentrequesties(rab_assignment_request, rab_assignment_request_ies);
if (rc < 0)
return NULL;
/* generate an Initiating Mesasage */
msg = ranap_generate_initiating_message(RANAP_ProcedureCode_id_RAB_Assignment,
RANAP_Criticality_reject,
&asn_DEF_RANAP_RAB_AssignmentRequest, rab_assignment_request);
/* 'msg' has been generated, we cann now release the input 'out' */
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_AssignmentRequest, rab_assignment_request);
return msg;
}
/*! Encode RABAP RAB AssignmentRequest from RANAP_RAB_AssignmentResponseIEs.
* \ptmap[out] data user provided memory to store resulting ASN.1 encoded message.
* \ptmap[in] len length of user provided memory to store resulting ASN.1 encoded message.
* \ptmap[in] ies user provided memory with RANAP_RAB_AssignmentResponseIEs.
* \returns resulting message length on success; negative on error. */
int ranap_rab_ass_resp_encode(uint8_t *data, unsigned int len,
RANAP_RAB_AssignmentResponseIEs_t *rab_assignment_response_ies)
{
int rc;
struct msgb *msg;
RANAP_RAB_AssignmentResponse_t _rab_assignment_response = { 0 };
RANAP_RAB_AssignmentResponse_t *rab_assignment_response = &_rab_assignment_response;
memset(data, 0, len);
rc = ranap_encode_rab_assignmentresponseies(rab_assignment_response, rab_assignment_response_ies);
if (rc < 0)
return -EINVAL;
/* generate an outcome mesasage */
msg = ranap_generate_outcome(RANAP_ProcedureCode_id_RAB_Assignment,
RANAP_Criticality_reject,
&asn_DEF_RANAP_RAB_AssignmentResponse, rab_assignment_response);
/* 'msg' has been generated, we can now release the input 'rab_assignment_response' */
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_AssignmentResponse, rab_assignment_response);
if (!msg)
return -EINVAL;
if (msg->len > len)
return -EINVAL;
memcpy(data, msg->data, msg->len);
rc = msg->len;
msgb_free(msg);
return rc;
}
/* Pick the indexed item from the RAB setup-or-modify list and return the first protocol-ie-field-pair. */
static RANAP_ProtocolIE_FieldPair_t *prot_ie_field_pair_from_ass_req_ies(const RANAP_RAB_AssignmentRequestIEs_t *ies,
unsigned int index)
{
RANAP_ProtocolIE_ContainerPair_t *protocol_ie_container_pair;
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
/* Make sure we indeed deal with a setup-or-modify list */
if (!(ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT)) {
RANAP_DEBUG
("Decoding failed, the RANAP RAB AssignmentRequest did not contain a setup-or-modify list!\n");
return NULL;
}
/* Detect the end of the list */
if (index >= ies->raB_SetupOrModifyList.list.count)
return NULL;
protocol_ie_container_pair = ies->raB_SetupOrModifyList.list.array[index];
protocol_ie_field_pair = protocol_ie_container_pair->list.array[0];
return protocol_ie_field_pair;
}
/* Pick the indexed item from the RAB release-list list and return a pointer to it */
static RANAP_IE_t *release_item_from_ass_req_ies(const RANAP_RAB_AssignmentRequestIEs_t *ies, unsigned int index)
{
/* Make sure we indeed deal with a setup-or-modify list */
if (!(ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_RELEASELIST_PRESENT)) {
RANAP_DEBUG
("Decoding failed, the RANAP RAB AssignmentRequest did not contain a release list!\n");
return NULL;
}
/* Detect the end of the list */
if (index >= ies->raB_ReleaseList.raB_ReleaseList_ies.list.count)
return NULL;
return ies->raB_ReleaseList.raB_ReleaseList_ies.list.array[index];
}
/* Pick the indexed item from the RAB setup-or-modified list and return a pointer to it */
static RANAP_IE_t *setup_or_modif_item_from_rab_ass_resp(const RANAP_RAB_AssignmentResponseIEs_t *ies,
unsigned int index)
{
/* Make sure we indeed deal with a setup-or-modified list */
if (!(ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_SETUPORMODIFIEDLIST_PRESENT)) {
RANAP_DEBUG("RANAP RAB AssignmentResponse did not contain a setup-or-modified list!\n");
return NULL;
}
/* Detect the end of the list */
if (index >= ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.count)
return NULL;
return ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.array[index];
}
/* Pick the indexed item from the RAB failed list and return a pointer to it */
static RANAP_IE_t *failed_list_item_from_rab_ass_resp(const RANAP_RAB_AssignmentResponseIEs_t *ies,
unsigned int index)
{
/* Make sure we indeed deal with a failed list */
if (!(ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_FAILEDLIST_PRESENT)) {
RANAP_DEBUG("RANAP RAB AssignmentResponse did not contain a failed list!\n");
return NULL;
}
/* Detect the end of the list */
if (index >= ies->raB_FailedList.raB_FailedList_ies.list.count)
return NULL;
return ies->raB_FailedList.raB_FailedList_ies.list.array[index];
}
/* Find the RAB specified by rab_id in ies, decode the result into items_ies and return a positive index.
* The caller is responsible for freeing the contents in items_ies. In case of failure, the return code
* will be negative. */
static int decode_rab_smditms_from_resp_ies(RANAP_RAB_SetupOrModifiedItemIEs_t *items_ies,
RANAP_RAB_AssignmentResponseIEs_t *ies, uint8_t rab_id)
{
RANAP_IE_t *setup_or_modified_list_ie;
RANAP_RAB_SetupOrModifiedItem_t *rab_setup_or_modified_item;
int rc;
uint8_t rab_id_decoded;
unsigned int index = 0;
while (1) {
setup_or_modified_list_ie = setup_or_modif_item_from_rab_ass_resp(ies, index);
if (!setup_or_modified_list_ie)
return -EINVAL;
rc = ranap_decode_rab_setupormodifieditemies_fromlist(items_ies, &setup_or_modified_list_ie->value);
if (rc < 0)
return -EINVAL;
rab_setup_or_modified_item = &items_ies->raB_SetupOrModifiedItem;
/* The RAB-ID is defined as a bitstring with a size of 8 (1 byte),
* See also RANAP-IEs.asn, RAB-ID ::= BIT STRING (SIZE (8)) */
rab_id_decoded = rab_setup_or_modified_item->rAB_ID.buf[0];
if (rab_id_decoded == rab_id)
return index;
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, items_ies);
index++;
}
}
/* See comment above decode_rab_smditms_from_resp_ies() */
static int decode_rab_flitms_from_resp_ies(RANAP_RAB_FailedItemIEs_t *items_ies,
RANAP_RAB_AssignmentResponseIEs_t *ies, uint8_t rab_id)
{
RANAP_IE_t *failed_list_ie;
RANAP_RAB_FailedItem_t *rab_failed_item;
int rc;
uint8_t rab_id_decoded;
unsigned int index = 0;
while (1) {
failed_list_ie = failed_list_item_from_rab_ass_resp(ies, index);
if (!failed_list_ie)
return -EINVAL;
rc = ranap_decode_rab_faileditemies_fromlist(items_ies, &failed_list_ie->value);
if (rc < 0)
return -EINVAL;
rab_failed_item = &items_ies->raB_FailedItem;
/* The RAB-ID is defined as a bitstring with a size of 8 (1 byte),
* See also RANAP-IEs.asn, RAB-ID ::= BIT STRING (SIZE (8)) */
rab_id_decoded = rab_failed_item->rAB_ID.buf[0];
if (rab_id_decoded == rab_id)
return index;
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_FailedItem, items_ies);
index++;
}
}
/* find the RAB specified by rab_id in ies and when found, decode the result into item */
static int decode_rab_smditms_from_req_ies(RANAP_RAB_SetupOrModifyItemFirst_t *item,
RANAP_RAB_AssignmentRequestIEs_t *ies, uint8_t rab_id)
{
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
int rc;
uint8_t rab_id_decoded;
unsigned int index = 0;
while (1) {
protocol_ie_field_pair = prot_ie_field_pair_from_ass_req_ies(ies, index);
if (!protocol_ie_field_pair)
return -EINVAL;
if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem) {
RANAP_DEBUG
("Decoding failed, the protocol IE field-pair is not of type RANAP RAB setup-or-modify-item!\n");
return -EINVAL;
}
rc = ranap_decode_rab_setupormodifyitemfirst(item, &protocol_ie_field_pair->firstValue);
if (rc < 0)
return -EINVAL;
rab_id_decoded = item->rAB_ID.buf[0];
if (rab_id_decoded == rab_id)
return index;
}
}
static int decode_rab_relitms_from_req_ies(RANAP_RAB_ReleaseItemIEs_t *items_ies,
RANAP_RAB_AssignmentRequestIEs_t *ies, uint8_t rab_id)
{
RANAP_IE_t *release_list_ie;
RANAP_RAB_ReleaseItem_t *rab_release_item;
int rc;
uint8_t rab_id_decoded;
unsigned int index = 0;
while (1) {
release_list_ie = release_item_from_ass_req_ies(ies, index);
if (!release_list_ie)
return -EINVAL;
if (release_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_ReleaseItem) {
RANAP_DEBUG("Decoding failed, the protocol IE is not of type RANAP RAB ReleaseItem!\n");
return -EINVAL;
}
rc = ranap_decode_rab_releaseitemies_fromlist(items_ies, &release_list_ie->value);
if (rc < 0)
return -EINVAL;
rab_release_item = &items_ies->raB_ReleaseItem;
/* The RAB-ID is defined as a bitstring with a size of 8 (1 byte),
* See also RANAP-IEs.asn, RAB-ID ::= BIT STRING (SIZE (8)) */
rab_id_decoded = rab_release_item->rAB_ID.buf[0];
if (rab_id_decoded == rab_id)
return index;
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_ReleaseItem, items_ies);
index++;
}
}
/*! Extract IP address and port from RANAP_RAB_AssignmentRequestIEs.
* \ptmap[out] addr user provided memory to store extracted RTP stream IP-Address and port number.
* \ptmap[out] rab_id pointer to store RAB-ID (optional, can be NULL).
* \ptmap[in] ies user provided memory with RANAP_RAB_AssignmentRequestIEs.
* \ptmap[in] index index of the SetupOrModifyItem (e.g. 0 for the first list item).
* \returns 0 on success; negative on error. */
int ranap_rab_ass_req_ies_extract_inet_addr(struct osmo_sockaddr *addr, uint8_t *rab_id,
RANAP_RAB_AssignmentRequestIEs_t *ies, unsigned int index)
{
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
RANAP_RAB_SetupOrModifyItemFirst_t _rab_setup_or_modify_item_first = { 0 };
RANAP_RAB_SetupOrModifyItemFirst_t *rab_setup_or_modify_item_first = &_rab_setup_or_modify_item_first;
RANAP_TransportLayerAddress_t *trasp_layer_addr;
RANAP_IuTransportAssociation_t *transp_assoc;
uint16_t port;
int rc;
protocol_ie_field_pair = prot_ie_field_pair_from_ass_req_ies(ies, index);
if (!protocol_ie_field_pair)
return -EINVAL;
if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem) {
RANAP_DEBUG
("Decoding failed, the protocol IE field-pair is not of type RANAP RAB setup-or-modify-item!\n");
return -EINVAL;
}
rc = ranap_decode_rab_setupormodifyitemfirst(rab_setup_or_modify_item_first,
&protocol_ie_field_pair->firstValue);
if (rc < 0)
return -EINVAL;
if (rab_id) {
/* The RAB-ID is defined as a bitstring with a size of 8 (1 byte),
* See also RANAP-IEs.asn, RAB-ID ::= BIT STRING (SIZE (8)) */
*rab_id = rab_setup_or_modify_item_first->rAB_ID.buf[0];
}
/* Decode IP-Address */
trasp_layer_addr = &rab_setup_or_modify_item_first->transportLayerInformation->transportLayerAddress;
rc = ranap_transp_layer_addr_decode2(addr, NULL, trasp_layer_addr);
if (rc < 0) {
rc = -EINVAL;
goto error;
}
/* Decode port number */
transp_assoc = &rab_setup_or_modify_item_first->transportLayerInformation->iuTransportAssociation;
rc = ranap_transp_assoc_decode(&port, transp_assoc);
if (rc < 0) {
rc = -EINVAL;
goto error;
}
switch (addr->u.sin.sin_family) {
case AF_INET:
addr->u.sin.sin_port = htons(port);
break;
case AF_INET6:
addr->u.sin6.sin6_port = htons(port);
break;
default:
rc = -EINVAL;
goto error;
}
rc = 0;
error:
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, rab_setup_or_modify_item_first);
return rc;
}
/*! Extract IP address and port from RANAP_RAB_AssignmentResponseIEs.
* \ptmap[out] addr user provided memory to store extracted RTP stream IP-Address and port number.
* \ptmap[in] ies user provided memory with RANAP_RAB_AssignmentResponseIEs.
* \ptmap[in] rab_id expected rab id to look for.
* \returns 0 on success; negative on error. */
int ranap_rab_ass_resp_ies_extract_inet_addr(struct osmo_sockaddr *addr, RANAP_RAB_AssignmentResponseIEs_t *ies, uint8_t rab_id)
{
RANAP_RAB_SetupOrModifiedItemIEs_t _rab_setup_or_modified_items_ies = { 0 };
RANAP_RAB_SetupOrModifiedItemIEs_t *rab_setup_or_modified_items_ies = &_rab_setup_or_modified_items_ies;
RANAP_RAB_SetupOrModifiedItem_t *rab_setup_or_modified_item;
uint16_t port;
int rc;
rc = decode_rab_smditms_from_resp_ies(rab_setup_or_modified_items_ies, ies, rab_id);
if (rc < 0)
return -EINVAL;
rab_setup_or_modified_item = &rab_setup_or_modified_items_ies->raB_SetupOrModifiedItem;
/* Decode IP-Address */
rc = ranap_transp_layer_addr_decode2(addr, NULL, rab_setup_or_modified_item->transportLayerAddress);
if (rc < 0) {
rc = -EINVAL;
goto error;
}
/* Decode port number */
rc = ranap_transp_assoc_decode(&port, rab_setup_or_modified_item->iuTransportAssociation);
if (rc < 0) {
rc = -EINVAL;
goto error;
}
switch (addr->u.sin.sin_family) {
case AF_INET:
addr->u.sin.sin_port = htons(port);
break;
case AF_INET6:
addr->u.sin6.sin6_port = htons(port);
break;
default:
rc = -EINVAL;
goto error;
}
rc = 0;
error:
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, rab_setup_or_modified_items_ies);
return rc;
}
/*! Replace IP address and port in RANAP_RAB_AssignmentRequestIEs.
* \ptmap[inout] ies user provided memory with RANAP_RAB_AssignmentRequestIEs.
* \ptmap[in] addr user provided memory that contains the new RTP stream IP-Address and port number.
* \ptmap[in] rab_id expected rab id to look for.
* \returns 0 on success; negative on error. */
int ranap_rab_ass_req_ies_replace_inet_addr(RANAP_RAB_AssignmentRequestIEs_t *ies, struct osmo_sockaddr *addr, uint8_t rab_id)
{
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
RANAP_RAB_SetupOrModifyItemFirst_t _rab_setup_or_modify_item_first = { 0 };
RANAP_RAB_SetupOrModifyItemFirst_t *rab_setup_or_modify_item_first = &_rab_setup_or_modify_item_first;
RANAP_TransportLayerInformation_t *old_transport_layer_information = NULL;
RANAP_TransportLayerInformation_t *new_transport_layer_information = NULL;
struct osmo_sockaddr addr_old;
bool uses_x213_nsap;
int rc;
int index;
index = decode_rab_smditms_from_req_ies(rab_setup_or_modify_item_first, ies, rab_id);
if (index < 0)
return -EINVAL;
/* Replace transport-layer-information */
if (rab_setup_or_modify_item_first->transportLayerInformation->iuTransportAssociation.present ==
RANAP_IuTransportAssociation_PR_bindingID) {
old_transport_layer_information = rab_setup_or_modify_item_first->transportLayerInformation;
/* Before we can re-encode the transport layer information, we need to know the format it was
* encoded in. */
rc = ranap_transp_layer_addr_decode2(&addr_old, &uses_x213_nsap,
&old_transport_layer_information->transportLayerAddress);
if (rc < 0) {
rc = -EINVAL;
goto error;
}
/* Encode a new transport layer information field */
new_transport_layer_information = ranap_new_transp_info_rtp(addr, uses_x213_nsap);
if (!new_transport_layer_information) {
rc = -EINVAL;
goto error;
}
rab_setup_or_modify_item_first->transportLayerInformation = new_transport_layer_information;
} else {
RANAP_DEBUG("Rewriting transport layer information failed, no bindingID (port)!\n");
rc = -EINVAL;
goto error;
}
/* Reencode transport-layer-information */
protocol_ie_field_pair = prot_ie_field_pair_from_ass_req_ies(ies, index);
rc = ANY_fromType_aper(&protocol_ie_field_pair->firstValue, &asn_DEF_RANAP_RAB_SetupOrModifyItemFirst,
rab_setup_or_modify_item_first);
if (rc < 0) {
RANAP_DEBUG("Rewriting transport layer information failed, could not reencode\n");
rc = -EINVAL;
goto error;
}
error:
/* Restore original state of the modified ASN.1 struct so that the asn1c free mechanisms can work properly */
if (old_transport_layer_information)
rab_setup_or_modify_item_first->transportLayerInformation = old_transport_layer_information;
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, rab_setup_or_modify_item_first);
if (new_transport_layer_information)
ASN_STRUCT_FREE(asn_DEF_RANAP_TransportLayerInformation, new_transport_layer_information);
return rc;
}
/*! Replace IP address and port in RANAP_RAB_AssignmentResponseIEs.
* \ptmap[inout] ies user provided memory with RANAP_RAB_AssignmentResponseIEs.
* \ptmap[in] addr user provided memory that contains the new RTP stream IP-Address and port number.
* \ptmap[in] rab_id expected rab id to look for.
* \returns 0 on success; negative on error. */
int ranap_rab_ass_resp_ies_replace_inet_addr(RANAP_RAB_AssignmentResponseIEs_t *ies, struct osmo_sockaddr *addr, uint8_t rab_id)
{
RANAP_IE_t *setup_or_modified_list_ie;
RANAP_RAB_SetupOrModifiedItemIEs_t _rab_setup_or_modified_items_ies = { 0 };
RANAP_RAB_SetupOrModifiedItemIEs_t *rab_setup_or_modified_items_ies = &_rab_setup_or_modified_items_ies;
RANAP_RAB_SetupOrModifiedItem_t *rab_setup_or_modified_item;
RANAP_TransportLayerInformation_t *temp_transport_layer_information = NULL;
RANAP_TransportLayerAddress_t *old_transport_layer_address = NULL;
RANAP_IuTransportAssociation_t *old_iu_transport_association = NULL;
struct osmo_sockaddr addr_old;
bool uses_x213_nsap;
int rc;
int index;
index = decode_rab_smditms_from_resp_ies(rab_setup_or_modified_items_ies, ies, rab_id);
if (index < 0)
return -EINVAL;
rab_setup_or_modified_item = &rab_setup_or_modified_items_ies->raB_SetupOrModifiedItem;
/* Before we can re-encode the transport layer address, we need to know the format it was encoded in. */
rc = ranap_transp_layer_addr_decode2(&addr_old, &uses_x213_nsap,
rab_setup_or_modified_item->transportLayerAddress);
if (rc < 0) {
rc = -EINVAL;
goto error;
}
/* Generate a temporary transport layer information, from which we can use the transport layer address and
* the iu transport association to update the setup or modified item */
temp_transport_layer_information = ranap_new_transp_info_rtp(addr, uses_x213_nsap);
if (!temp_transport_layer_information) {
rc = -EINVAL;
goto error;
}
/* Replace transport layer address and iu transport association */
old_transport_layer_address = rab_setup_or_modified_item->transportLayerAddress;
old_iu_transport_association = rab_setup_or_modified_item->iuTransportAssociation;
rab_setup_or_modified_item->transportLayerAddress = &temp_transport_layer_information->transportLayerAddress;
rab_setup_or_modified_item->iuTransportAssociation = &temp_transport_layer_information->iuTransportAssociation;
/* Reencode modified setup or modified list */
setup_or_modified_list_ie = setup_or_modif_item_from_rab_ass_resp(ies, index);
rc = ANY_fromType_aper(&setup_or_modified_list_ie->value, &asn_DEF_RANAP_RAB_SetupOrModifiedItem,
rab_setup_or_modified_items_ies);
if (rc < 0) {
RANAP_DEBUG("Rewriting transport layer address failed, could not reencode\n");
rc = -EINVAL;
goto error;
}
error:
/* Restore original state of the modified ASN.1 struct so that the asn1c free mechanisms can work properly */
if (old_transport_layer_address)
rab_setup_or_modified_item->transportLayerAddress = old_transport_layer_address;
if (old_iu_transport_association)
rab_setup_or_modified_item->iuTransportAssociation = old_iu_transport_association;
if (temp_transport_layer_information)
ASN_STRUCT_FREE(asn_DEF_RANAP_TransportLayerInformation, temp_transport_layer_information);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, rab_setup_or_modified_items_ies);
return rc;
}
/*! Check if a specific RAB is present in an RAB-Failed-Item-List inside RANAP_RAB_AssignmentResponseIEs.
* \ptmap[in] ies user provided memory with RANAP_RAB_AssignmentResponseIEs.
* \ptmap[in] rab_id expected rab id to look for.
* \returns true when RAB could be identified as failed; false otherwise */
bool ranap_rab_ass_resp_ies_check_failure(RANAP_RAB_AssignmentResponseIEs_t *ies, uint8_t rab_id)
{
RANAP_RAB_FailedItemIEs_t _rab_failed_items_ies = { 0 };
RANAP_RAB_FailedItemIEs_t *rab_failed_items_ies = &_rab_failed_items_ies;
int rc;
/* If we can get a failed item (rc >= 0) for the specified RAB ID, then we know that the
* HNB reported the RAB Assignment as failed */
rc = decode_rab_flitms_from_resp_ies(rab_failed_items_ies, ies, rab_id);
if (rc < 0)
return false;
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_FailedItem, rab_failed_items_ies);
return true;
}
/*! Check if a specific RAB is present in an RAB-ReleaseList inside RANAP_RAB_AssignmentRequestIEs.
* \ptmap[in] ies user provided memory with RANAP_RAB_AssignmentRequestIEs.
* \ptmap[in] rab_id expected rab id to look for.
* \returns true when RAB is intended for release; false otherwise */
bool ranap_rab_ass_req_ies_check_release(RANAP_RAB_AssignmentRequestIEs_t *ies, uint8_t rab_id)
{
RANAP_RAB_ReleaseItemIEs_t _rab_release_items_ies = { 0 };
RANAP_RAB_ReleaseItemIEs_t *rab_release_items_ies = &_rab_release_items_ies;
int rc;
bool result = true;
/* If we can get a rlease list item for the specified RAB ID, then we know that the
* MSC intends to release the specified RAB */
rc = decode_rab_relitms_from_req_ies(rab_release_items_ies, ies, rab_id);
if (rc < 0)
result = false;
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_ReleaseItem, rab_release_items_ies);
return result;
}
/*! Find out how many RAB items are present in a RAB-SetupOrModifyList inside RANAP_RAB_AssignmentRequestIEs.
* \ptmap[in] ies user provided memory with RANAP_RAB_AssignmentRequestIEs.
* \returns number of RAB items, -1 on failure. */
int ranap_rab_ass_req_ies_get_count(RANAP_RAB_AssignmentRequestIEs_t *ies)
{
/* Make sure we indeed deal with a setup-or-modify list */
if (!(ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT)) {
RANAP_DEBUG
("Decoding failed, the RANAP RAB AssignmentRequest did not contain a setup-or-modify list!\n");
return -1;
}
return ies->raB_SetupOrModifyList.list.count;
}

56
src/osmo-hnbgw/tdefs.c Normal file
View File

@@ -0,0 +1,56 @@
/* (C) 2021 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Philipp Maier
*
* 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.
*/
#include "config.h"
#include <osmocom/hnbgw/tdefs.h>
#if ENABLE_PFCP
#include <osmocom/pfcp/pfcp_endpoint.h>
#endif
struct osmo_tdef mgw_fsm_T_defs[] = {
{.T = -1001, .default_val = 5, .desc = "Timeout for HNB side call-leg (to-HNB) creation" },
{.T = -1002, .default_val = 10, .desc = "Timeout for the HNB to respond to RAB Assignment Request" },
{.T = -1003, .default_val = 5, .desc = "Timeout for HNB side call-leg (to-HNB) completion" },
{.T = -1004, .default_val = 5, .desc = "Timeout for MSC side call-leg (to-MSC) completion" },
{.T = -2427, .default_val = 5, .desc = "timeout for MGCP response from MGW" },
{ }
};
struct osmo_tdef hnbgw_T_defs[] = {
{.T = 3113, .default_val = 15, .desc = "Time to keep Paging record, for CN pools with more than one link" },
{.T = 4, .default_val = 5, .desc = "Timeout to receive RANAP RESET ACKNOWLEDGE from an MSC/SGSN" },
{.T = -31, .default_val = 15, .desc = "Timeout for establishing and releasing context maps (RUA <-> SCCP)" },
{.T = -34, .default_val = 1000, .unit = OSMO_TDEF_MS, .desc = "Period to query network traffic stats from netfilter" },
{
.T = -35,
.default_val = 60*60*24*7,
.desc = "Clean up all hNodeB persistent state after this time of the hNodeB being disconnected."
" Set to zero to never clear hNodeB persistent state. (default is 60*60*24*27 = a week)",
},
{.T = -1002, .default_val = 10, .desc = "Timeout for the HNB to respond to PS RAB Assignment Request" },
{ }
};
struct osmo_tdef_group hnbgw_tdef_group[] = {
{.name = "mgw", .tdefs = mgw_fsm_T_defs, .desc = "MGW (Media Gateway) interface" },
{.name = "hnbgw", .tdefs = hnbgw_T_defs, .desc = "Timers" },
#if ENABLE_PFCP
{.name = "pfcp", .tdefs = osmo_pfcp_tdefs, .desc = "PFCP timers" },
#endif
{ }
};

View File

@@ -0,0 +1,107 @@
/* UMTS Cell ID */
/* (C) 2015,2024 by Harald Welte <laforge@gnumonks.org>
* (C) 2016-2025 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 "config.h"
#include <unistd.h>
#include <errno.h>
#include <inttypes.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/jhash.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/hnbgw/umts_cell_id.h>
int umts_cell_id_to_str_buf(char *buf, size_t buflen, const struct umts_cell_id *ucid)
{
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
OSMO_STRBUF_APPEND_NOLEN(sb, osmo_plmn_name_buf, &ucid->plmn);
OSMO_STRBUF_PRINTF(sb, "-L%u-R%u-S%u-C%u", ucid->lac, ucid->rac, ucid->sac, ucid->cid);
return sb.chars_needed;
}
char *umts_cell_id_to_str_c(void *ctx, const struct umts_cell_id *ucid)
{
OSMO_NAME_C_IMPL(ctx, 64, "ERROR", umts_cell_id_to_str_buf, ucid)
}
const char *umts_cell_id_to_str(const struct umts_cell_id *ucid)
{
return umts_cell_id_to_str_c(OTC_SELECT, ucid);
}
/* Useful to index a hash table by struct umts_cell_id. */
uint32_t umts_cell_id_hash(const struct umts_cell_id *ucid)
{
return osmo_jhash(ucid, sizeof(*ucid), 0x423423);
}
/* parse a string representation of an umts_cell_id into its decoded representation */
int umts_cell_id_from_str(struct umts_cell_id *ucid, const char *instr)
{
int rc;
char buf[4];
const char *pos = instr;
const char *end;
/* We want to use struct umts_cell_id as hashtable key. If it ever happens to contain any padding bytes, make
* sure everything is deterministically zero. */
memset(ucid, 0, sizeof(*ucid));
/* read MCC */
end = strchr(pos, '-');
if (!end || end <= pos || (end - pos) >= sizeof(buf))
return -EINVAL;
osmo_strlcpy(buf, pos, end - pos + 1);
if (osmo_mcc_from_str(buf, &ucid->plmn.mcc))
return -EINVAL;
pos = end + 1;
/* read MNC -- here the number of leading zeros matters. */
end = strchr(pos, '-');
if (!end || end == pos || (end - pos) >= sizeof(buf))
return -EINVAL;
osmo_strlcpy(buf, pos, end - pos + 1);
if (osmo_mnc_from_str(buf, &ucid->plmn.mnc, &ucid->plmn.mnc_3_digits))
return -EINVAL;
pos = end + 1;
/* parse the rest, where leading zeros do not matter */
rc = sscanf(pos, "L%" SCNu16 "-R%" SCNu8 "-S%" SCNu16 "-C%" SCNu32 "",
&ucid->lac, &ucid->rac, &ucid->sac, &ucid->cid);
if (rc < 0)
return -errno;
if (rc != 4)
return -EINVAL;
if (ucid->lac == 0 || ucid->lac == 0xffff)
return -EINVAL;
/* CellIdentity in the ASN.1 syntax is a bit-string of 28 bits length */
if (ucid->cid >= (1 << 28))
return -EINVAL;
return 0;
}

View File

@@ -1,4 +1,6 @@
SUBDIRS = \
ranap_rab_ass \
umts_cell_id \
$(NULL)
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
@@ -23,8 +25,13 @@ EXTRA_DIST = \
testsuite.at \
$(srcdir)/package.m4 \
$(TESTSUITE) \
ctrl_test_runner.py \
osmo-hnbgw.vty \
osmo-hnbgw-vty-test.cfg \
$(srcdir)/*.vty \
$(srcdir)/*.vty.with_pfcp \
$(srcdir)/*.ctrl \
$(srcdir)/config/*.cfg \
$(srcdir)/config/*.vty \
$(srcdir)/config/run_tests.sh \
$(NULL)
TESTSUITE = $(srcdir)/testsuite
@@ -34,30 +41,56 @@ DISTCLEANFILES = \
$(NULL)
if ENABLE_EXT_TESTS
python-tests: $(BUILT_SOURCES)
echo ""
# TODO: Enable once we have a VTY/CTRL interface:
# $(MAKE) vty-test
# osmotestvty.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
# osmotestconfig.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
# $(srcdir)/ctrl_test_runner.py -w $(abs_top_builddir) -v
python-tests:
$(MAKE) vty-test
osmotestvty.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
osmotestconfig.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
else
python-tests: $(BUILT_SOURCES)
python-tests:
echo "Not running python-based tests (determined at configure-time)"
endif
# Run a specific test with: 'make vty-test VTY_TEST=osmo-hnbgw.vty'
if ENABLE_PFCP
VTY_TEST ?= *.vty*
else
VTY_TEST ?= *.vty
endif
# To update the VTY script from current application behavior,
# pass -u to vty_script_runner.py by doing:
# make vty-test U=-u
vty-test:
vty-test: $(top_builddir)/src/osmo-hnbgw/osmo-hnbgw
osmo_verify_transcript_vty.py -v \
-n OsmoHNBGW -p 4261 \
-r "$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw -c $(top_srcdir)/doc/examples/osmo-hnbgw/osmo-hnbgw.cfg" \
-r "$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw \
-c $(srcdir)/osmo-hnbgw-vty-test.cfg" \
$(U) $(srcdir)/$(VTY_TEST)
# Test each config/*.cfg file with the corresponding config/*.vty transcript test.
#
# To be invoked manually only: This is not part of 'make check' because the
# output may change when libosmo-sccp changes its VTY appearance, which can
# cause annoying test fallout.
#
# Each config test runs an osmo-hnbgw process to talk to, so they must not run concurrently.
# To prevent 'make -j N' from running these tests in parallel, call a script with a linear for-loop.
.PHONY: config-tests
config-tests:
$(srcdir)/config/run_tests.sh "$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw" "$(srcdir)/config/" $U
# Run a specific test with: 'make ctrl-test CTRL_TEST=osmo-hnbgw.ctrl'
CTRL_TEST ?= *.ctrl
# To update the CTRL script from current application behavior,
# pass -u to ctrl_script_runner.py by doing:
# make ctrl-test U=-u
ctrl-test: $(top_builddir)/src/osmo-hnbgw/osmo-hnbgw
osmo_verify_transcript_ctrl.py -v \
-p 4262 \
-r "$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw -c $(top_srcdir)/doc/examples/osmo-hnbgw/osmo-hnbgw-cnpool.cfg" \
$(U) $(srcdir)/$(CTRL_TEST)
check-local: atconfig $(TESTSUITE)
$(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS)
$(MAKE) $(AM_MAKEFLAGS) python-tests

432
tests/cnpool.vty Normal file
View File

@@ -0,0 +1,432 @@
OsmoHNBGW> enable
OsmoHNBGW# list
...
(msc|sgsn) <0-1000> ranap reset
...
OsmoHNBGW# msc?
msc Manipulate an IuCS link to an MSC
OsmoHNBGW# msc ?
<0-1000> MSC/SGSN nr
OsmoHNBGW# msc 0 ?
ranap Manipulate RANAP layer of Iu-interface
OsmoHNBGW# msc 0 ranap ?
reset Flip this CN link to disconnected state and re-send RANAP RESET
OsmoHNBGW# sgsn?
sgsn Manipulate an IuPS link to an SGSN
OsmoHNBGW# sgsn ?
<0-1000> MSC/SGSN nr
OsmoHNBGW# sgsn 0 ?
ranap Manipulate RANAP layer of Iu-interface
OsmoHNBGW# sgsn 0 ranap ?
reset Flip this CN link to disconnected state and re-send RANAP RESET
OsmoHNBGW# configure terminal
OsmoHNBGW(config)# ### cnpool doc strings
OsmoHNBGW(config)# list
...
apply sccp
...
OsmoHNBGW(config)# apply?
apply Immediately use configuration modified via telnet VTY, and restart components as needed.
OsmoHNBGW(config)# apply ?
sccp For telnet VTY: apply all SCCP and NRI config changes made to any CN pools and CN links in the running osmo-hnbgw process. If 'remote-addr' changed, related SCCP links will be restarted, possibly dropping active UE contexts. This is run implicitly on program startup, only useful to apply changes made later via telnet VTY.
OsmoHNBGW(config)# hnbgw
OsmoHNBGW(config-hnbgw)# list
...
iucs
iups
...
OsmoHNBGW(config-hnbgw)# iucs?
iucs Configure IuCS options
OsmoHNBGW(config-hnbgw)# iups?
iups Configure IuPS options
OsmoHNBGW(config-hnbgw)# iucs
OsmoHNBGW(config-hnbgw-iucs)# list
...
nri bitlen <1-15>
nri null add <0-32767> [<0-32767>]
nri null del <0-32767> [<0-32767>]
...
OsmoHNBGW(config-hnbgw-iucs)# nri?
nri Mapping of Network Resource Indicators to this CN peer, for CN pooling
OsmoHNBGW(config-hnbgw-iucs)# nri ?
bitlen Set number of bits that an NRI has, to extract from TMSI identities (always starting just after the TMSI's most significant octet).
null Define NULL-NRI values that cause re-assignment of an MS to a different CN peer, for CN pooling.
OsmoHNBGW(config-hnbgw-iucs)# nri bitlen ?
<1-15> bit count (default: 10)
OsmoHNBGW(config-hnbgw-iucs)# nri null ?
add Add NULL-NRI value (or range)
del Remove NRI value or range from the NRI mapping for this CN link
OsmoHNBGW(config-hnbgw-iucs)# nri null add ?
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
OsmoHNBGW(config-hnbgw-iucs)# nri null add 0 ?
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
OsmoHNBGW(config-hnbgw-iucs)# nri null del ?
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
OsmoHNBGW(config-hnbgw-iucs)# nri null del 0 ?
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
OsmoHNBGW(config-hnbgw-iucs)# exit
OsmoHNBGW(config-hnbgw)# iups
OsmoHNBGW(config-hnbgw-iups)# list
...
nri bitlen <1-15>
nri null add <0-32767> [<0-32767>]
nri null del <0-32767> [<0-32767>]
...
OsmoHNBGW(config-hnbgw-iups)# nri?
nri Mapping of Network Resource Indicators to this CN peer, for CN pooling
OsmoHNBGW(config-hnbgw-iups)# nri ?
bitlen Set number of bits that an NRI has, to extract from TMSI identities (always starting just after the TMSI's most significant octet).
null Define NULL-NRI values that cause re-assignment of an MS to a different CN peer, for CN pooling.
OsmoHNBGW(config-hnbgw-iups)# nri bitlen ?
<1-15> bit count (default: 10)
OsmoHNBGW(config-hnbgw-iups)# nri null ?
add Add NULL-NRI value (or range)
del Remove NRI value or range from the NRI mapping for this CN link
OsmoHNBGW(config-hnbgw-iups)# nri null add ?
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
OsmoHNBGW(config-hnbgw-iups)# nri null add 0 ?
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
OsmoHNBGW(config-hnbgw-iups)# nri null del ?
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
OsmoHNBGW(config-hnbgw-iups)# nri null del 0 ?
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
OsmoHNBGW(config-hnbgw-iups)# exit
OsmoHNBGW(config-hnbgw)# exit
OsmoHNBGW(config)# msc?
msc Configure an IuCS link to an MSC
OsmoHNBGW(config)# msc ?
<0-1000> MSC nr
OsmoHNBGW(config)# sgsn?
sgsn Configure an IuPS link to an SGSN
OsmoHNBGW(config)# sgsn ?
<0-1000> SGSN nr
OsmoHNBGW(config)# ### The config file has no 'msc' or 'sgsn', so defaults have been set up
OsmoHNBGW(config)# show running-config
...
cs7 instance 0
point-code 0.23.5
...
sccp-address addr-dyn-msc-default
routing-indicator PC
point-code 0.23.1
subsystem-number 142
sccp-address addr-dyn-sgsn-default
routing-indicator PC
point-code 0.23.4
...
msc 0
name msc-0
remote-addr addr-dyn-msc-default
sgsn 0
name sgsn-0
remote-addr addr-dyn-sgsn-default
...
OsmoHNBGW(config)# msc 1
OsmoHNBGW(config-msc)# list
...
remote-addr NAME
nri add <0-32767> [<0-32767>]
nri del <0-32767> [<0-32767>]
show nri
apply sccp
allow-attach
no allow-attach
allow-emergency
no allow-emergency
...
OsmoHNBGW(config-msc)# remote-addr?
remote-addr SCCP address to send RANAP/SCCP to
OsmoHNBGW(config-msc)# remote-addr ?
NAME SCCP address book entry name (see 'cs7 instance' / 'sccp-address')
OsmoHNBGW(config-msc)# show nri?
nri Mapping of Network Resource Indicators to this CN peer, for CN pooling
OsmoHNBGW(config-msc)# apply?
apply Immediately use configuration modified via telnet VTY, and restart components as needed.
OsmoHNBGW(config-msc)# apply ?
sccp For telnet VTY: apply SCCP and NRI config changes made to this CN link in the running osmo-hnbgw process. If 'remote-addr' changed, related SCCP links will be restarted, possibly dropping active UE contexts. This is run implicitly on program startup, only useful to apply changes made later via telnet VTY.
OsmoHNBGW(config-msc)# allow-attach?
allow-attach Allow this CN link to attach new subscribers (default).
OsmoHNBGW(config-msc)# allow-emergency?
allow-emergency Allow CM ServiceRequests with type emergency on this CN link
OsmoHNBGW(config-msc)# no ?
allow-attach Do not assign new subscribers to this CN link. Useful if an CN link in an CN link pool is configured to off-load subscribers. The CN link will still be operational for already IMSI-Attached subscribers, but the NAS node selection function will skip this CN link for new subscribers
allow-emergency Do not serve CM ServiceRequests with type emergency on this CN link
OsmoHNBGW(config-msc)# exit
OsmoHNBGW(config)# sgsn 1
OsmoHNBGW(config-sgsn)# list
...
remote-addr NAME
nri add <0-32767> [<0-32767>]
nri del <0-32767> [<0-32767>]
show nri
apply sccp
allow-attach
no allow-attach
allow-emergency
no allow-emergency
...
OsmoHNBGW(config-sgsn)# remote-addr?
remote-addr SCCP address to send RANAP/SCCP to
OsmoHNBGW(config-sgsn)# remote-addr ?
NAME SCCP address book entry name (see 'cs7 instance' / 'sccp-address')
OsmoHNBGW(config-sgsn)# show nri?
nri Mapping of Network Resource Indicators to this CN peer, for CN pooling
OsmoHNBGW(config-sgsn)# apply?
apply Immediately use configuration modified via telnet VTY, and restart components as needed.
OsmoHNBGW(config-sgsn)# apply ?
sccp For telnet VTY: apply SCCP and NRI config changes made to this CN link in the running osmo-hnbgw process. If 'remote-addr' changed, related SCCP links will be restarted, possibly dropping active UE contexts. This is run implicitly on program startup, only useful to apply changes made later via telnet VTY.
OsmoHNBGW(config-sgsn)# allow-attach?
allow-attach Allow this CN link to attach new subscribers (default).
OsmoHNBGW(config-sgsn)# allow-emergency?
allow-emergency Allow CM ServiceRequests with type emergency on this CN link
OsmoHNBGW(config-sgsn)# no ?
allow-attach Do not assign new subscribers to this CN link. Useful if an CN link in an CN link pool is configured to off-load subscribers. The CN link will still be operational for already IMSI-Attached subscribers, but the NAS node selection function will skip this CN link for new subscribers
allow-emergency Do not serve CM ServiceRequests with type emergency on this CN link
OsmoHNBGW(config-sgsn)# exit
OsmoHNBGW(config)# ### Just by entering the nodes above, 'msc 1' and 'sgsn 1' now exist
OsmoHNBGW(config)# show running-config
...
msc 0
name msc-0
remote-addr addr-dyn-msc-default
msc 1
name msc-1
sgsn 0
name sgsn-0
remote-addr addr-dyn-sgsn-default
sgsn 1
name sgsn-1
...
OsmoHNBGW(config)# ### Add {msc,sgsn}x{2,3}
OsmoHNBGW(config)# msc 2
OsmoHNBGW(config-msc)# remote-addr addr-msc2
OsmoHNBGW(config-msc)# no allow-attach
OsmoHNBGW(config-msc)# exit
OsmoHNBGW(config)# msc 3
OsmoHNBGW(config-msc)# remote-addr addr-msc3
OsmoHNBGW(config-msc)# allow-emergency
OsmoHNBGW(config-msc)# exit
OsmoHNBGW(config)# sgsn 2
OsmoHNBGW(config-sgsn)# remote-addr addr-sgsn2
OsmoHNBGW(config-sgsn)# exit
OsmoHNBGW(config)# sgsn 3
OsmoHNBGW(config-sgsn)# remote-addr addr-sgsn3
OsmoHNBGW(config-sgsn)# exit
OsmoHNBGW(config)# show running-config
...
msc 0
name msc-0
remote-addr addr-dyn-msc-default
msc 1
name msc-1
msc 2
name msc-2
remote-addr addr-msc2
no allow-attach
msc 3
name msc-3
remote-addr addr-msc3
allow-emergency
sgsn 0
name sgsn-0
remote-addr addr-dyn-sgsn-default
sgsn 1
name sgsn-1
sgsn 2
name sgsn-2
remote-addr addr-sgsn2
sgsn 3
name sgsn-3
remote-addr addr-sgsn3
...
OsmoHNBGW(config)# ### Re-entering existing nodes works (does not create new ones)
OsmoHNBGW(config)# msc 2
OsmoHNBGW(config-msc)# allow-attach
OsmoHNBGW(config-msc)# exit
OsmoHNBGW(config)# msc 3
OsmoHNBGW(config-msc)# remote-addr addr-msc4
OsmoHNBGW(config-msc)# no allow-emergency
OsmoHNBGW(config-msc)# exit
OsmoHNBGW(config)# sgsn 2
OsmoHNBGW(config-sgsn)# remote-addr addr-sgsn4
OsmoHNBGW(config-sgsn)# exit
OsmoHNBGW(config)# show running-config
...
msc 0
name msc-0
remote-addr addr-dyn-msc-default
msc 1
name msc-1
msc 2
name msc-2
remote-addr addr-msc2
msc 3
name msc-3
remote-addr addr-msc4
sgsn 0
name sgsn-0
remote-addr addr-dyn-sgsn-default
sgsn 1
name sgsn-1
sgsn 2
name sgsn-2
remote-addr addr-sgsn4
sgsn 3
name sgsn-3
remote-addr addr-sgsn3
...
OsmoHNBGW(config)# ### Legacy configuration {'iups','iucs'} / 'remote-addr' redirects to {'msc 0','sgsn 0'}:
OsmoHNBGW(config)# hnbgw
OsmoHNBGW(config-hnbgw)# iucs
OsmoHNBGW(config-hnbgw-iucs)# remote-addr?
% There is no matched command.
OsmoHNBGW(config-hnbgw-iucs)# remote-addr ?
% There is no matched command.
OsmoHNBGW(config-hnbgw-iucs)# remote-addr addr-msc0
% Deprecated: instead of hnbgw/iucs/remote-addr, use 'msc 0'/remote-addr
OsmoHNBGW(config-hnbgw-iucs)# exit
OsmoHNBGW(config-hnbgw)# iups
OsmoHNBGW(config-hnbgw-iups)# remote-addr?
% There is no matched command.
OsmoHNBGW(config-hnbgw-iups)# remote-addr ?
% There is no matched command.
OsmoHNBGW(config-hnbgw-iups)# remote-addr addr-sgsn0
% Deprecated: instead of hnbgw/iups/remote-addr, use 'sgsn 0'/remote-addr
OsmoHNBGW(config-hnbgw-iups)# exit
OsmoHNBGW(config-hnbgw)# show running-config
...
hnbgw
...
msc 0
name msc-0
remote-addr addr-msc0
msc 1
name msc-1
msc 2
name msc-2
remote-addr addr-msc2
msc 3
name msc-3
remote-addr addr-msc4
sgsn 0
name sgsn-0
remote-addr addr-sgsn0
sgsn 1
name sgsn-1
sgsn 2
name sgsn-2
remote-addr addr-sgsn4
sgsn 3
name sgsn-3
remote-addr addr-sgsn3
...
OsmoHNBGW(config-hnbgw)# ### placement of the new 'msc' and 'sgsn' nodes must be below 'pfcp' and 'mgw' nodes
OsmoHNBGW(config-hnbgw)# show running-config
...
hnbgw
... !msc
iuh
... !msc
mgw 0
...
msc 0
...
OsmoHNBGW(config-hnbgw)# exit
OsmoHNBGW(config)# ### Apply all SCCP changes:
OsmoHNBGW(config)# apply sccp
OsmoHNBGW(config)# show running-config
...
hnbgw
...
msc 0
name msc-0
remote-addr addr-msc0
msc 1
name msc-1
remote-addr addr-dyn-msc-default
msc 2
name msc-2
remote-addr addr-msc2
msc 3
name msc-3
remote-addr addr-msc4
sgsn 0
name sgsn-0
remote-addr addr-sgsn0
sgsn 1
name sgsn-1
remote-addr addr-dyn-sgsn-default
sgsn 2
name sgsn-2
remote-addr addr-sgsn4
sgsn 3
name sgsn-3
remote-addr addr-sgsn3
...

View File

@@ -0,0 +1 @@
# configure nothing, to get purely the implicit default config

37
tests/config/defaults.vty Normal file
View File

@@ -0,0 +1,37 @@
OsmoHNBGW> ### Config file was empty, everything we see here is the default auto configuration
OsmoHNBGW> enable
OsmoHNBGW# show cs7 config
cs7 instance 0
point-code 0.23.5
asp asp-clnt-msc-0 2905 0 m3ua
local-ip localhost
remote-ip localhost
role asp
sctp-role client
as as-clnt-msc-0 m3ua
asp asp-clnt-msc-0
routing-key 0 0.23.5
OsmoHNBGW# show cnlink
IuCS: OsmoHNBGW:RI=SSN_PC,PC=0.23.5,SSN=RANAP <-> RI=SSN_PC,PC=0.23.1,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
IuPS: OsmoHNBGW:RI=SSN_PC,PC=0.23.5,SSN=RANAP <-> RI=SSN_PC,PC=0.23.4,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
OsmoHNBGW# show running-config
...
cs7 instance 0
point-code 0.23.5
hnbgw
rnc-id 23
log-prefix hnb-id
iuh
mgw 0
...
msc 0
sgsn 0
...

12
tests/config/legacy.cfg Normal file
View File

@@ -0,0 +1,12 @@
# explicitly point at cs7 instance 0 via two address book entries for CS and PS. Leave ASP and AS to auto-configuration.
cs7 instance 0
point-code 1.1.1
sccp-address my-msc
point-code 1.2.3
sccp-address my-sgsn
point-code 1.4.2
hnbgw
iucs
remote-addr my-msc
iups
remote-addr my-sgsn

50
tests/config/legacy.vty Normal file
View File

@@ -0,0 +1,50 @@
OsmoHNBGW> enable
OsmoHNBGW# ### ASP and AS were auto-configured
OsmoHNBGW# show cs7 config
cs7 instance 0
point-code 1.1.1
asp asp-clnt-msc-0 2905 0 m3ua
local-ip localhost
remote-ip localhost
role asp
sctp-role client
as as-clnt-msc-0 m3ua
asp asp-clnt-msc-0
routing-key 0 1.1.1
sccp-address my-msc
routing-indicator PC
point-code 1.2.3
sccp-address my-sgsn
routing-indicator PC
point-code 1.4.2
OsmoHNBGW# ### IuCS and IuPS were explicitly pointed to specific remote point-codes, using cs7 instance 0 addresses
OsmoHNBGW# show cnlink
IuCS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> my-msc=RI=SSN_PC,PC=1.2.3,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
IuPS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> my-sgsn=RI=SSN_PC,PC=1.4.2,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
OsmoHNBGW# show running-config
...
cs7 instance 0
point-code 1.1.1
sccp-address my-msc
routing-indicator PC
point-code 1.2.3
sccp-address my-sgsn
routing-indicator PC
point-code 1.4.2
hnbgw
rnc-id 23
log-prefix hnb-id
iuh
...
msc 0
remote-addr my-msc
sgsn 0
remote-addr my-sgsn
...

3
tests/config/one_cs7.cfg Normal file
View File

@@ -0,0 +1,3 @@
# auto-configuration will pick up cs7 instance 0. Configure only the local point-code.
cs7 instance 0
point-code 1.1.1

31
tests/config/one_cs7.vty Normal file
View File

@@ -0,0 +1,31 @@
OsmoHNBGW> enable
OsmoHNBGW# show cs7 config
cs7 instance 0
point-code 1.1.1
asp asp-clnt-msc-0 2905 0 m3ua
local-ip localhost
remote-ip localhost
role asp
sctp-role client
as as-clnt-msc-0 m3ua
asp asp-clnt-msc-0
routing-key 0 1.1.1
OsmoHNBGW# show cnlink
IuCS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> RI=SSN_PC,PC=0.23.1,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
IuPS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> RI=SSN_PC,PC=0.23.4,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
OsmoHNBGW# show running-config
...
cs7 instance 0
point-code 1.1.1
hnbgw
...
msc 0
sgsn 0
...

View File

@@ -0,0 +1,11 @@
# explicitly point at cs7 instance 0 via two address book entries for CS and PS. Leave ASP and AS to auto-configuration.
cs7 instance 0
point-code 1.1.1
sccp-address my-msc
point-code 1.2.3
sccp-address my-sgsn
point-code 1.4.2
msc 0
remote-addr my-msc
sgsn 0
remote-addr my-sgsn

View File

@@ -0,0 +1,50 @@
OsmoHNBGW> enable
OsmoHNBGW# ### ASP and AS were auto-configured
OsmoHNBGW# show cs7 config
cs7 instance 0
point-code 1.1.1
asp asp-clnt-msc-0 2905 0 m3ua
local-ip localhost
remote-ip localhost
role asp
sctp-role client
as as-clnt-msc-0 m3ua
asp asp-clnt-msc-0
routing-key 0 1.1.1
sccp-address my-msc
routing-indicator PC
point-code 1.2.3
sccp-address my-sgsn
routing-indicator PC
point-code 1.4.2
OsmoHNBGW# ### IuCS and IuPS were explicitly pointed to specific remote point-codes, using cs7 instance 0 addresses
OsmoHNBGW# show cnlink
IuCS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> my-msc=RI=SSN_PC,PC=1.2.3,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
IuPS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> my-sgsn=RI=SSN_PC,PC=1.4.2,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
OsmoHNBGW# show running-config
...
cs7 instance 0
point-code 1.1.1
sccp-address my-msc
routing-indicator PC
point-code 1.2.3
sccp-address my-sgsn
routing-indicator PC
point-code 1.4.2
hnbgw
rnc-id 23
log-prefix hnb-id
iuh
...
msc 0
remote-addr my-msc
sgsn 0
remote-addr my-sgsn
...

View File

@@ -0,0 +1,7 @@
# only set an explicit remote address for IuCS, let IuPS be auto-configured
cs7 instance 0
point-code 1.1.1
sccp-address my-msc
point-code 1.2.3
msc 0
remote-addr my-msc

View File

@@ -0,0 +1,42 @@
OsmoHNBGW> enable
OsmoHNBGW# show cs7 config
cs7 instance 0
point-code 1.1.1
asp asp-clnt-msc-0 2905 0 m3ua
local-ip localhost
remote-ip localhost
role asp
sctp-role client
as as-clnt-msc-0 m3ua
asp asp-clnt-msc-0
routing-key 0 1.1.1
sccp-address my-msc
routing-indicator PC
point-code 1.2.3
OsmoHNBGW# ### only IuCS was configured to go to remote-PC = 1.2.3, IuPS auto-configured to remote-PC = 0.23.4
OsmoHNBGW# show cnlink
IuCS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> my-msc=RI=SSN_PC,PC=1.2.3,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
IuPS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> RI=SSN_PC,PC=0.23.4,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
OsmoHNBGW# show running-config
...
cs7 instance 0
point-code 1.1.1
sccp-address my-msc
routing-indicator PC
point-code 1.2.3
hnbgw
rnc-id 23
log-prefix hnb-id
iuh
...
msc 0
remote-addr my-msc
sgsn 0
... !remote-addr

View File

@@ -0,0 +1,7 @@
# only set an explicit remote address for IuPS, let IuCS be auto-configured
cs7 instance 0
point-code 1.1.1
sccp-address my-sgsn
point-code 1.4.2
sgsn 0
remote-addr my-sgsn

View File

@@ -0,0 +1,42 @@
OsmoHNBGW> enable
OsmoHNBGW# show cs7 config
cs7 instance 0
point-code 1.1.1
asp asp-clnt-msc-0 2905 0 m3ua
local-ip localhost
remote-ip localhost
role asp
sctp-role client
as as-clnt-msc-0 m3ua
asp asp-clnt-msc-0
routing-key 0 1.1.1
sccp-address my-sgsn
routing-indicator PC
point-code 1.4.2
OsmoHNBGW# ### only IuPS was configured to go to remote-PC = 1.4.2, IuCS auto-configured to remote-PC = 0.23.1
OsmoHNBGW# show cnlink
IuCS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> RI=SSN_PC,PC=0.23.1,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
IuPS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> my-sgsn=RI=SSN_PC,PC=1.4.2,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
OsmoHNBGW# show running-config
...
cs7 instance 0
point-code 1.1.1
sccp-address my-sgsn
routing-indicator PC
point-code 1.4.2
hnbgw
rnc-id 23
log-prefix hnb-id
iuh
...
msc 0
sgsn 0
remote-addr my-sgsn
...

21
tests/config/run_tests.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
osmo_hnbgw="$1"
tests_src="$2"
shift
shift
if [ ! -x "$osmo_hnbgw" ]; then
echo "there seems to be no osmo-hnbgw executable at '$osmo_hnbgw'"
exit 1
fi
if [ ! -d "$tests_src" ]; then
echo "there seems to be no tests source dir at '$tests_src'"
exit 1
fi
set -e
for dot_vty in "$tests_src"/*.vty; do
dot_cfg="$(echo $dot_vty | sed 's/\.vty$/.cfg/')"
osmo_verify_transcript_vty.py -v -n OsmoHNBGW -p 4261 -r "$osmo_hnbgw -c $dot_cfg" "$dot_vty" $@
done

7
tests/config/two_cs7.cfg Normal file
View File

@@ -0,0 +1,7 @@
# there are two cs7 instances, but auto-configuration will only use instance 0.
cs7 instance 0
point-code 1.1.1
cs7 instance 1
point-code 2.2.2

37
tests/config/two_cs7.vty Normal file
View File

@@ -0,0 +1,37 @@
OsmoHNBGW> enable
OsmoHNBGW# ### there are two cs7 instances, but auto-configuration has only set up ASP and AS in instance 0.
OsmoHNBGW# show cs7 config
cs7 instance 0
point-code 1.1.1
asp asp-clnt-msc-0 2905 0 m3ua
local-ip localhost
remote-ip localhost
role asp
sctp-role client
as as-clnt-msc-0 m3ua
asp asp-clnt-msc-0
routing-key 0 1.1.1
cs7 instance 1
point-code 2.2.2
OsmoHNBGW# ### there are two cs7 instances, but auto-configuration will only use SCCP of instance 0 with local-PC = 1.1.1
OsmoHNBGW# show cnlink
IuCS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> RI=SSN_PC,PC=0.23.1,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
IuPS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> RI=SSN_PC,PC=0.23.4,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
OsmoHNBGW# show running-config
...
cs7 instance 0
point-code 1.1.1
cs7 instance 1
point-code 2.2.2
hnbgw
...
msc 0
sgsn 0
...

View File

@@ -0,0 +1,16 @@
# two cs7 instances, both will be auto-configured for ASP and AS because address book entries point at both.
cs7 instance 0
point-code 1.1.1
sccp-address my-msc
point-code 1.2.3
cs7 instance 1
point-code 2.2.2
sccp-address my-sgsn
point-code 2.4.2
msc 0
remote-addr my-msc
sgsn 0
remote-addr my-sgsn

View File

@@ -0,0 +1,62 @@
OsmoHNBGW> enable
OsmoHNBGW# ### two cs7 instances, both have been auto-configured for ASP and AS because address book entries point at both.
OsmoHNBGW# show cs7 config
cs7 instance 0
point-code 1.1.1
asp asp-clnt-msc-0 2905 0 m3ua
local-ip localhost
remote-ip localhost
role asp
sctp-role client
as as-clnt-msc-0 m3ua
asp asp-clnt-msc-0
routing-key 0 1.1.1
sccp-address my-msc
routing-indicator PC
point-code 1.2.3
cs7 instance 1
point-code 2.2.2
asp asp-clnt-sgsn-0 2905 0 m3ua
local-ip localhost
remote-ip localhost
role asp
sctp-role client
as as-clnt-sgsn-0 m3ua
asp asp-clnt-sgsn-0
routing-key 0 2.2.2
sccp-address my-sgsn
routing-indicator PC
point-code 2.4.2
OsmoHNBGW# ### IuCS on instance 0 with local-PC = 1.1.1, IuPS on instance 1 with local-PC = 2.2.2
OsmoHNBGW# show cnlink
IuCS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> my-msc=RI=SSN_PC,PC=1.2.3,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
RANAP state: DISCONNECTED
IuPS: OsmoHNBGW:RI=SSN_PC,PC=2.2.2,SSN=RANAP <-> my-sgsn=RI=SSN_PC,PC=2.4.2,SSN=RANAP
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-sgsn-0 proto=m3ua ASP asp-clnt-sgsn-0
RANAP state: DISCONNECTED
OsmoHNBGW# show running-config
...
cs7 instance 0
point-code 1.1.1
sccp-address my-msc
routing-indicator PC
point-code 1.2.3
cs7 instance 1
point-code 2.2.2
sccp-address my-sgsn
routing-indicator PC
point-code 2.4.2
hnbgw
rnc-id 23
log-prefix hnb-id
iuh
...
msc 0
remote-addr my-msc
sgsn 0
remote-addr my-sgsn
...

View File

@@ -1,207 +0,0 @@
#!/usr/bin/env python3
# (C) 2013 by Jacob Erlbeck <jerlbeck@sysmocom.de>
# (C) 2014 by Holger Hans Peter Freyther
# based on vty_test_runner.py:
# (C) 2013 by Katerina Barone-Adesi <kat.obsc@gmail.com>
# (C) 2013 by Holger Hans Peter Freyther
# based on bsc_control.py.
# 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 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 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/>.
import os
import time
import unittest
import socket
import sys
import struct
import osmopy.obscvty as obscvty
import osmopy.osmoutil as osmoutil
from osmopy.osmo_ipa import Ctrl, IPA
# to be able to find $top_srcdir/doc/...
confpath = os.path.join(sys.path[0], '..')
verbose = False
class TestCtrlBase(unittest.TestCase):
def ctrl_command(self):
raise Exception("Needs to be implemented by a subclass")
def ctrl_app(self):
raise Exception("Needs to be implemented by a subclass")
def setUp(self):
osmo_ctrl_cmd = self.ctrl_command()[:]
config_index = osmo_ctrl_cmd.index('-c')
if config_index:
cfi = config_index + 1
osmo_ctrl_cmd[cfi] = os.path.join(confpath, osmo_ctrl_cmd[cfi])
try:
self.proc = osmoutil.popen_devnull(osmo_ctrl_cmd)
except OSError:
print("Current directory: %s" % os.getcwd(), file=sys.stderr)
print("Consider setting -b", file=sys.stderr)
appstring = self.ctrl_app()[2]
appport = self.ctrl_app()[0]
self.connect("127.0.0.1", appport)
self.next_id = 1000
def tearDown(self):
self.disconnect()
osmoutil.end_proc(self.proc)
def disconnect(self):
if not (self.sock is None):
self.sock.close()
def connect(self, host, port):
if verbose:
print("Connecting to host %s:%i" % (host, port))
retries = 30
while True:
try:
sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sck.setblocking(1)
sck.connect((host, port))
except IOError:
retries -= 1
if retries <= 0:
raise
time.sleep(.1)
continue
break
self.sock = sck
return sck
def send(self, data):
if verbose:
print("Sending \"%s\"" %(data))
data = Ctrl().add_header(data)
return self.sock.send(data) == len(data)
def send_set(self, var, value, id):
setmsg = "SET %s %s %s" %(id, var, value)
return self.send(setmsg)
def send_get(self, var, id):
getmsg = "GET %s %s" %(id, var)
return self.send(getmsg)
def do_set(self, var, value):
id = self.next_id
self.next_id += 1
self.send_set(var, value, id)
return self.recv_msgs()[id]
def do_get(self, var):
id = self.next_id
self.next_id += 1
self.send_get(var, id)
return self.recv_msgs()[id]
def recv_msgs(self):
responses = {}
data = self.sock.recv(4096)
while (len(data)>0):
(head, data) = IPA().split_combined(data)
answer = Ctrl().rem_header(head).decode()
if verbose:
print("Got message:", answer)
(mtype, id, msg) = answer.split(None, 2)
id = int(id)
rsp = {'mtype': mtype, 'id': id}
if mtype == "ERROR":
rsp['error'] = msg
else:
split = msg.split(None, 1)
rsp['var'] = split[0]
if len(split) > 1:
rsp['value'] = split[1]
else:
rsp['value'] = None
responses[id] = rsp
if verbose:
print("Decoded replies: ", responses)
return responses
class TestCtrlHNB(TestCtrlBase):
def tearDown(self):
TestCtrlBase.tearDown(self)
os.unlink("tmp_dummy_sock")
def ctrl_command(self):
return ["./src/osmo-hnbgw/osmo-hnbgw", "-r", "tmp_dummy_sock", "-c",
"doc/examples/osmo-hnbgw/osmo-hnbgw.cfg"]
def ctrl_app(self):
return (4249, "./src/osmo-hnbgw/osmo-hnbgw", "OsmoHNBGW", "hnb")
def testCtrlErrs(self):
r = self.do_get('invalid')
self.assertEqual(r['mtype'], 'ERROR')
self.assertEqual(r['error'], 'Command not found')
r = self.do_get('hnbgw.999')
self.assertEqual(r['mtype'], 'ERROR')
self.assertEqual(r['error'], 'Error while resolving object')
def add_hnbgw_test(suite, workdir, klass):
if not os.path.isfile(os.path.join(workdir, "src/osmo-hnbgw/osmo-hnbgw")):
print("Skipping the HNBGW test")
return
test = unittest.TestLoader().loadTestsFromTestCase(klass)
suite.addTest(test)
if __name__ == '__main__':
import argparse
import sys
workdir = '.'
parser = argparse.ArgumentParser()
parser.add_argument("-v", "--verbose", dest="verbose",
action="store_true", help="verbose mode")
parser.add_argument("-p", "--pythonconfpath", dest="p",
help="searchpath for config")
parser.add_argument("-w", "--workdir", dest="w",
help="Working directory")
args = parser.parse_args()
verbose_level = 1
if args.verbose:
verbose_level = 2
verbose = True
if args.w:
workdir = args.w
if args.p:
confpath = args.p
print("confpath %s, workdir %s" % (confpath, workdir))
os.chdir(workdir)
print("Running tests for specific control commands")
suite = unittest.TestSuite()
add_hnbgw_test(suite, workdir, TestCtrlHNB)
res = unittest.TextTestRunner(verbosity=verbose_level).run(suite)
sys.exit(len(res.errors) + len(res.failures))

279
tests/nri_cfg.vty Normal file
View File

@@ -0,0 +1,279 @@
OsmoHNBGW> show nri
hnbgw
iucs
nri bitlen 10
% No NULL-NRI entries
iups
nri bitlen 10
% No NULL-NRI entries
msc 0
% no NRI mappings
sgsn 0
% no NRI mappings
OsmoHNBGW> enable
OsmoHNBGW# configure terminal
OsmoHNBGW(config)# msc 0
OsmoHNBGW(config-msc)# list
...
nri add <0-32767> [<0-32767>]
nri del <0-32767> [<0-32767>]
show nri
...
OsmoHNBGW(config-msc)# nri ?
add Add NRI value or range to the NRI mapping for this CN link
del Remove NRI value or range from the NRI mapping for this CN link
OsmoHNBGW(config-msc)# nri add ?
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
OsmoHNBGW(config-msc)# nri add 23 ?
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
OsmoHNBGW(config-msc)# nri add 23
OsmoHNBGW(config-msc)# nri add 256 511
OsmoHNBGW(config-msc)# nri add 100 200
OsmoHNBGW(config-msc)# nri add 1024 1024
% Warning: msc 0: Warning: NRI range surpasses current NRI bitlen: 1024..1024
OsmoHNBGW(config-msc)# show nri
msc 0
nri add 23
nri add 100 200
nri add 256 511
nri add 1024
OsmoHNBGW(config-msc)# exit
OsmoHNBGW(config)# ### Do msc 2 first, to see that the order of mscs in the internal list is not determined by the msc->nr,
OsmoHNBGW(config)# ### and whichever was configured first gets higher priority for overlaps.
OsmoHNBGW(config)# msc 2
OsmoHNBGW(config-msc)# nri add 200 300
% Warning: msc 2: NRI range [200..300] overlaps between msc 2 and msc 0. For overlaps, msc 0 has higher priority than msc 2
OsmoHNBGW(config-msc)# nri add 1024 1025
% Warning: msc 2: Warning: NRI range surpasses current NRI bitlen: 1024..1025
% Warning: msc 2: NRI range [1024..1025] overlaps between msc 2 and msc 0. For overlaps, msc 0 has higher priority than msc 2
OsmoHNBGW(config-msc)# exit
OsmoHNBGW(config)# msc 1
OsmoHNBGW(config-msc)# nri add 42
OsmoHNBGW(config-msc)# nri add 512 767
OsmoHNBGW(config-msc)# nri add 200 300
% Warning: msc 1: NRI range [200..300] overlaps between msc 1 and msc 0. For overlaps, msc 0 has higher priority than msc 1
% Warning: msc 1: NRI range [200..300] overlaps between msc 1 and msc 2. For overlaps, msc 2 has higher priority than msc 1
OsmoHNBGW(config-msc)# nri add 1024 1025
% Warning: msc 1: Warning: NRI range surpasses current NRI bitlen: 1024..1025
% Warning: msc 1: NRI range [1024..1025] overlaps between msc 1 and msc 0. For overlaps, msc 0 has higher priority than msc 1
% Warning: msc 1: NRI range [1024..1025] overlaps between msc 1 and msc 2. For overlaps, msc 2 has higher priority than msc 1
OsmoHNBGW(config-msc)# show nri
msc 1
nri add 42
nri add 200 300
nri add 512 767
nri add 1024 1025
OsmoHNBGW(config-msc)# exit
OsmoHNBGW(config)# do show nri
hnbgw
iucs
nri bitlen 10
% No NULL-NRI entries
iups
nri bitlen 10
% No NULL-NRI entries
msc 0
nri add 23
nri add 100 200
nri add 256 511
nri add 1024
msc 2
nri add 200 300
nri add 1024 1025
msc 1
nri add 42
nri add 200 300
nri add 512 767
nri add 1024 1025
sgsn 0
% no NRI mappings
OsmoHNBGW(config)# ### msc and sgsn have separate scopes of NRI, i.e. overlaps are no problem
OsmoHNBGW(config)# sgsn 0
OsmoHNBGW(config-sgsn)# nri add 0 1023
OsmoHNBGW(config-sgsn)# show nri
sgsn 0
nri add 0 1023
OsmoHNBGW(config-sgsn)# exit
OsmoHNBGW(config)# ### NULL-NRI config
OsmoHNBGW(config)# hnbgw
OsmoHNBGW(config-hnbgw)# iucs
OsmoHNBGW(config-hnbgw-iucs)# list
...
nri bitlen <1-15>
nri null add <0-32767> [<0-32767>]
nri null del <0-32767> [<0-32767>]
...
OsmoHNBGW(config-hnbgw-iucs)# nri ?
bitlen Set number of bits that an NRI has, to extract from TMSI identities (always starting just after the TMSI's most significant octet).
null Define NULL-NRI values that cause re-assignment of an MS to a different CN peer, for CN pooling.
OsmoHNBGW(config-hnbgw-iucs)# nri bitlen ?
<1-15> bit count (default: 10)
OsmoHNBGW(config-hnbgw-iucs)# nri bitlen 11
OsmoHNBGW(config-hnbgw-iucs)# show running-config
...
hnbgw
...
iucs
...
nri bitlen 11
...
OsmoHNBGW(config-hnbgw-iucs)# nri null ?
add Add NULL-NRI value (or range)
del Remove NRI value or range from the NRI mapping for this CN link
OsmoHNBGW(config-hnbgw-iucs)# nri null add ?
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
OsmoHNBGW(config-hnbgw-iucs)# nri null add 0 ?
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
OsmoHNBGW(config-hnbgw-iucs)# nri null del ?
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
OsmoHNBGW(config-hnbgw-iucs)# nri null del 0 ?
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
OsmoHNBGW(config-hnbgw-iucs)# exit
OsmoHNBGW(config-hnbgw)# iups
OsmoHNBGW(config-hnbgw-iups)# list
...
nri bitlen <1-15>
nri null add <0-32767> [<0-32767>]
nri null del <0-32767> [<0-32767>]
...
OsmoHNBGW(config-hnbgw-iups)# nri ?
bitlen Set number of bits that an NRI has, to extract from TMSI identities (always starting just after the TMSI's most significant octet).
null Define NULL-NRI values that cause re-assignment of an MS to a different CN peer, for CN pooling.
OsmoHNBGW(config-hnbgw-iups)# nri bitlen ?
<1-15> bit count (default: 10)
OsmoHNBGW(config-hnbgw-iups)# nri bitlen 9
OsmoHNBGW(config-hnbgw-iups)# show running-config
...
hnbgw
...
iups
...
nri bitlen 9
...
OsmoHNBGW(config-hnbgw-iups)# nri null ?
add Add NULL-NRI value (or range)
del Remove NRI value or range from the NRI mapping for this CN link
OsmoHNBGW(config-hnbgw-iups)# nri null add ?
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
OsmoHNBGW(config-hnbgw-iups)# nri null add 0 ?
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
OsmoHNBGW(config-hnbgw-iups)# nri null del ?
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
OsmoHNBGW(config-hnbgw-iups)# nri null del 0 ?
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
OsmoHNBGW(config-hnbgw-iups)# exit
OsmoHNBGW(config-hnbgw)# exit
OsmoHNBGW(config)# msc 0
OsmoHNBGW(config-msc)# nri del 0 10000
OsmoHNBGW(config-msc)# exit
OsmoHNBGW(config)# msc 1
OsmoHNBGW(config-msc)# nri del 0 10000
OsmoHNBGW(config-msc)# exit
OsmoHNBGW(config)# msc 2
OsmoHNBGW(config-msc)# nri del 0 10000
OsmoHNBGW(config-msc)# exit
OsmoHNBGW(config)# do show nri
hnbgw
iucs
nri bitlen 11
% No NULL-NRI entries
iups
nri bitlen 9
% No NULL-NRI entries
msc 0
% no NRI mappings
msc 2
% no NRI mappings
msc 1
% no NRI mappings
sgsn 0
nri add 0 1023
OsmoHNBGW(config)# msc 0
OsmoHNBGW(config-msc)# nri add 0 1000
OsmoHNBGW(config-msc)# show nri
msc 0
nri add 0 1000
OsmoHNBGW(config-msc)# nri del 23
OsmoHNBGW(config-msc)# nri del 200 300
OsmoHNBGW(config-msc)# nri del 1000 2000
OsmoHNBGW(config-msc)# show nri
msc 0
nri add 0 22
nri add 24 199
nri add 301 999
OsmoHNBGW(config-msc)# nri add 23
OsmoHNBGW(config-msc)# show nri
msc 0
nri add 0 199
nri add 301 999
OsmoHNBGW(config-msc)# nri add 200 300
OsmoHNBGW(config-msc)# show nri
msc 0
nri add 0 999
OsmoHNBGW(config-msc)# nri add 1000
OsmoHNBGW(config-msc)# show nri
msc 0
nri add 0 1000
OsmoHNBGW(config-msc)# show running-config
... ! no allow-attach
OsmoHNBGW(config-msc)# no allow-attach
OsmoHNBGW(config-msc)# show running-config
...
msc 0
...
nri add 0 1000
no allow-attach
... ! no allow-attach
OsmoHNBGW(config-msc)# exit
OsmoHNBGW(config)# sgsn 1
OsmoHNBGW(config-sgsn)# no allow-attach
OsmoHNBGW(config-sgsn)# show running-config
...
msc 0
...
nri add 0 1000
no allow-attach
... ! no allow-attach
sgsn 1
...
no allow-attach
...
OsmoHNBGW(config-sgsn)# allow-attach
OsmoHNBGW(config-sgsn)# exit
OsmoHNBGW(config)# msc 0
OsmoHNBGW(config-msc)# allow-attach
OsmoHNBGW(config-msc)# show running-config
... ! no allow-attach
OsmoHNBGW(config-msc)# exit

View File

@@ -0,0 +1,39 @@
log stderr
logging filter all 1
logging color 1
logging print level 1
logging print category 1
logging print category-hex 0
logging print file basename last
logging print extended-timestamp 1
logging level set-all notice
# Define some cs7 address book entries to play with
cs7 instance 0
sccp-address addr-msc0
point-code 0.1.0
sccp-address addr-msc2
point-code 0.1.2
sccp-address addr-sgsn3
point-code 0.2.3
sccp-address addr-sgsn4
point-code 0.2.4
cs7 instance 1
sccp-address addr-sgsn0
point-code 1.2.0
sccp-address addr-sgsn2
point-code 1.2.2
sccp-address addr-msc3
point-code 1.1.3
sccp-address addr-msc4
point-code 1.1.4
hnbgw
iuh
local-ip 0.0.0.0
mgw 0
remote-ip 127.0.0.1
local-port 2729
remote-port 2427
reset-endpoint rtpbridge/*

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