Compare commits

...

88 Commits
1.2.0 ... 1.4.0

Author SHA1 Message Date
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
44 changed files with 4881 additions and 190 deletions

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

@@ -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,26 @@ 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.8.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.8.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.8.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.8.0)
PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 1.3.0)
PKG_CHECK_MODULES(LIBOSMOSIGTRAN, libosmo-sigtran >= 1.7.0)
PKG_CHECK_MODULES(LIBOSMORUA, libosmo-rua >= 1.4.0)
PKG_CHECK_MODULES(LIBOSMORANAP, libosmo-ranap >= 1.4.0)
PKG_CHECK_MODULES(LIBOSMOHNBAP, libosmo-hnbap >= 1.4.0)
PKG_CHECK_MODULES(LIBOSMOMGCPCLIENT, libosmo-mgcp-client >= 1.11.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.2.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)
dnl checks for header files
AC_HEADER_STDC
@@ -113,13 +116,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 +150,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,6 +229,7 @@ AC_OUTPUT(
src/osmo-hnbgw/Makefile
tests/Makefile
tests/atlocal
tests/ranap_rab_ass/Makefile
doc/Makefile
doc/examples/Makefile
doc/manuals/Makefile

View File

@@ -33,8 +33,10 @@ osmo-build-dep.sh libosmocore "" --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-pfcp
osmo-build-dep.sh libasn1c
osmo-build-dep.sh osmo-iuh
osmo-build-dep.sh osmo-mgw
# Additional configure options and depends
CONFIG=""

View File

@@ -24,7 +24,6 @@ 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
@@ -33,18 +32,20 @@ BuildRequires: pkgconfig >= 0.20
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(libosmo-mgcp-client) >= 1.11.0
BuildRequires: pkgconfig(libosmo-netif) >= 1.3.0
BuildRequires: pkgconfig(libosmo-sigtran) >= 1.7.0
BuildRequires: pkgconfig(libosmoabis) >= 1.4.0
BuildRequires: pkgconfig(libosmotrau) >= 1.4.0
BuildRequires: pkgconfig(libosmocore) >= 1.8.0
BuildRequires: pkgconfig(libosmoctrl) >= 1.8.0
BuildRequires: pkgconfig(libosmogb) >= 1.8.0
BuildRequires: pkgconfig(libosmogsm) >= 1.8.0
BuildRequires: pkgconfig(libosmovty) >= 1.8.0
BuildRequires: pkgconfig(libosmo-hnbap) >= 1.4.0
BuildRequires: pkgconfig(libosmo-ranap) >= 1.4.0
BuildRequires: pkgconfig(libosmo-rua) >= 1.4.0
BuildRequires: pkgconfig(libosmo-pfcp) >= 0.2.0
BuildRequires: pkgconfig(talloc)
BuildRequires: pkgconfig(libasn1c) >= 0.9.30
%{?systemd_requires}
@@ -60,7 +61,8 @@ echo "%{version}" >.tarball-version
autoreconf -fi
%configure \
--docdir=%{_docdir}/%{name} \
--with-systemdsystemunitdir=%{_unitdir}
--with-systemdsystemunitdir=%{_unitdir} \
--enable-pfcp
make %{?_smp_mflags}
%install
@@ -90,6 +92,7 @@ make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
%dir %{_docdir}/%{name}/examples
%dir %{_docdir}/%{name}/examples/osmo-hnbgw
%{_docdir}/%{name}/examples/osmo-hnbgw/osmo-hnbgw.cfg
%{_docdir}/%{name}/examples/osmo-hnbgw/osmo-hnbgw-pfcp.cfg
%dir %{_sysconfdir}/osmocom
%config(noreplace) %{_sysconfdir}/osmocom/osmo-hnbgw.cfg
%{_unitdir}/%{name}.service

View File

@@ -4,6 +4,8 @@ Description=Osmocom Home Nodeb Gateway (OsmoHNBGW)
[Service]
Type=simple
Restart=always
StateDirectory=osmocom
WorkingDirectory=%S/osmocom
ExecStart=/usr/bin/osmo-hnbgw -c /etc/osmocom/osmo-hnbgw.cfg
RestartSec=2

124
debian/changelog vendored
View File

@@ -1,3 +1,127 @@
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

23
debian/control vendored
View File

@@ -6,7 +6,6 @@ Build-Depends: debhelper (>=9),
dh-autoreconf,
autotools-dev,
autoconf,
autoconf-archive,
automake,
libtool,
pkg-config,
@@ -14,17 +13,19 @@ 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.8.0),
libosmo-sigtran-dev (>= 1.7.0),
libosmo-abis-dev (>= 1.4.0),
libosmo-netif-dev (>= 1.3.0),
libosmo-mgcp-client-dev (>= 1.11.0),
libosmo-hnbap-dev (>= 1.4.0),
libosmo-ranap-dev (>= 1.4.0),
libosmo-rua-dev (>= 1.4.0),
libosmo-pfcp-dev (>= 0.2.0),
osmo-gsm-manuals-dev (>= 1.4.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>

View File

@@ -2,3 +2,4 @@ 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

1
debian/rules vendored
View File

@@ -46,6 +46,7 @@
# debmake generated override targets
CONFIGURE_FLAGS += --with-systemdsystemunitdir=/lib/systemd/system --enable-manuals
CONFIGURE_FLAGS += --enable-pfcp
override_dh_auto_configure:
dh_auto_configure -- $(CONFIGURE_FLAGS)
#

View File

@@ -0,0 +1,21 @@
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
hnbgw
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/*
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 level 1
logging print category 1
logging timestamp 1
logging print category-hex 0
logging print file basename last
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 level set-all notice
hnbgw
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,6 +69,8 @@ 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>>.
@@ -117,3 +119,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

@@ -24,6 +24,8 @@ include::./common/chapters/logging.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,9 @@
noinst_HEADERS = \
vty.h \
context_map.h hnbgw.h hnbgw_cn.h \
hnbgw_hnbap.h hnbgw_rua.h hnbgw_ranap.h
hnbgw_hnbap.h hnbgw_rua.h hnbgw_ranap.h \
ranap_rab_ass.h mgw_fsm.h tdefs.h \
hnbgw_pfcp.h \
ps_rab_ass_fsm.h \
ps_rab_fsm.h \
$(NULL)

View File

@@ -2,6 +2,16 @@
#include <stdint.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/rua/RUA_CN-DomainIndicator.h>
struct msgb;
#define LOG_MAP(HNB_CTX_MAP, SUBSYS, LEVEL, FMT, ARGS...) \
LOGHNB((HNB_CTX_MAP) ? (HNB_CTX_MAP)->hnb_ctx : NULL, \
SUBSYS, LEVEL, "RUA-%u %s: " FMT, \
(HNB_CTX_MAP) ? (HNB_CTX_MAP)->rua_ctx_id : 0, \
(HNB_CTX_MAP) ? ((HNB_CTX_MAP)->is_ps ? "PS" : "CS") : "NULL", \
##ARGS) \
enum hnbgw_context_map_state {
MAP_S_NULL,
@@ -33,8 +43,32 @@ struct hnbgw_context_map {
bool is_ps;
/* SCCP User SAP connection ID */
uint32_t scu_conn_id;
/* Set to true on SCCP Conn Conf, set to false when an OSMO_SCU_PRIM_N_DISCONNECT has been sent for the SCCP
* User SAP conn. Useful to avoid leaking SCCP connections: guarantee that an OSMO_SCU_PRIM_N_DISCONNECT gets
* sent, even when RUA fails to gracefully disconnect. */
bool scu_conn_active;
/* Pending data to be sent: when we send an "empty" SCCP CR first, the initial RANAP message will be sent in a
* separate DT once the CR is confirmed. This caches the initial RANAP message. */
struct msgb *cached_msg;
enum hnbgw_context_map_state state;
/* FSM instance for the MGW */
struct osmo_fsm_inst *mgw_fi;
/* FSMs handling RANAP RAB assignments for PS. list of struct ps_rab_ass. For PS RAB Assignment, each Request
* and 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 list hnbgw_context_map.ps_rabs, 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;
/* 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_rabs;
};
@@ -46,6 +80,8 @@ context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id,
struct hnbgw_context_map *
context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id);
int context_map_send_cached_msg(struct hnbgw_context_map *map);
void context_map_deactivate(struct hnbgw_context_map *map);
int context_map_init(struct hnb_gw *gw);

View File

@@ -10,16 +10,19 @@
#define DEBUG
#include <osmocom/core/logging.h>
#include <osmocom/mgcp_client/mgcp_client.h>
#include <osmocom/mgcp_client/mgcp_client_pool.h>
enum {
DMAIN,
DHNBAP,
DRUA,
DRANAP,
DMGW,
};
#define LOGHNB(x, ss, lvl, fmt, args ...) \
LOGP(ss, lvl, "%s " fmt, hnb_context_name(x), ## args)
#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,
@@ -83,6 +86,7 @@ struct hnbgw_cnlink {
struct llist_head map_list;
};
/* 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;
@@ -100,8 +104,7 @@ struct hnb_context {
/*! 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. */
/*! True if a HNB-REGISTER-REQ from this HNB has been accepted. */
bool hnb_registered;
/* linked list of hnbgw_context_map */
@@ -133,6 +136,14 @@ struct hnb_gw {
bool hnbap_allow_tmsi;
/*! print hnb-id (true) or MCC-MNC-LAC-RAC-SAC (false) in logs */
bool log_prefix_hnb_id;
unsigned int max_sccp_cr_payload_len;
struct mgcp_client_conf *mgcp_client;
struct {
char *local_addr;
uint16_t local_port;
char *remote_addr;
uint16_t remote_port;
} pfcp;
} config;
/*! SCTP listen socket for incoming connections */
struct osmo_stream_srv_link *iuh;
@@ -151,6 +162,14 @@ struct hnb_gw {
struct osmo_sccp_addr iucs_remote_addr;
struct osmo_sccp_addr iups_remote_addr;
} 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;
} pfcp;
};
extern void *talloc_asn1_ctx;
@@ -172,3 +191,15 @@ void hnb_context_release(struct hnb_context *ctx);
void hnbgw_vty_init(struct hnb_gw *gw, void *tall_ctx);
int hnbgw_vty_go_parent(struct vty *vty);
bool hnbgw_requires_empty_sccp_cr(struct hnb_gw *gw, unsigned int ranap_msg_len);
/* 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(const struct hnb_gw *gw)
{
return gw->config.pfcp.remote_addr != NULL;
}

View File

@@ -0,0 +1,5 @@
#pragma once
struct hnb_gw;
int hnbgw_pfcp_init(struct hnb_gw *hnb_gw);

View File

@@ -2,6 +2,7 @@
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/rua/RUA_Cause.h>
#include <osmocom/rua/RUA_CN-DomainIndicator.h>
int hnbgw_rua_rx(struct hnb_context *hnb, struct msgb *msg);
int hnbgw_rua_init(void);
@@ -11,3 +12,9 @@ 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);
int rua_to_scu(struct hnb_context *hnb,
RUA_CN_DomainIndicator_t cN_DomainIndicator,
enum osmo_scu_prim_type type,
uint32_t context_id, uint32_t cause,
const uint8_t *data, unsigned int len);

View File

@@ -0,0 +1,7 @@
#pragma once
#include <osmocom/ranap/ranap_ies_defs.h>
int handle_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message);
int mgw_fsm_handle_rab_ass_resp(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message);
int mgw_fsm_release(struct hnbgw_context_map *map);

View File

@@ -0,0 +1,14 @@
#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_FAIL,
};
int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message);
int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message);
void hnbgw_gtpmap_release(struct hnbgw_context_map *map);

View File

@@ -0,0 +1,102 @@
#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;
/* backpointer */
struct hnb_gw *hnb_gw;
/* 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 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(struct hnb_gw *hnb_gw, 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 ps_T_defs[];
extern struct osmo_tdef_group hnbgw_tdef_group[];

View File

@@ -7,5 +7,8 @@ enum osmo_iuh_vty_node {
IUH_NODE,
IUCS_NODE,
IUPS_NODE,
MGCP_NODE,
MGW_NODE,
PFCP_NODE,
};

View File

@@ -19,6 +19,7 @@ AM_CFLAGS = \
$(LIBOSMORUA_CFLAGS) \
$(LIBOSMORANAP_CFLAGS) \
$(LIBOSMOHNBAP_CFLAGS) \
$(LIBOSMOMGCPCLIENT_CFLAGS) \
$(NULL)
AM_LDFLAGS = \
@@ -37,6 +38,9 @@ osmo_hnbgw_SOURCES = \
hnbgw_vty.c \
context_map.c \
hnbgw_cn.c \
ranap_rab_ass.c \
mgw_fsm.c \
tdefs.c \
$(NULL)
osmo_hnbgw_LDADD = \
@@ -52,4 +56,21 @@ osmo_hnbgw_LDADD = \
$(LIBOSMORANAP_LIBS) \
$(LIBOSMOHNBAP_LIBS) \
$(LIBSCTP_LIBS) \
$(LIBOSMOMGCPCLIENT_LIBS) \
$(NULL)
if ENABLE_PFCP
AM_CFLAGS += \
$(LIBOSMOGTLV_CFLAGS) \
$(LIBOSMOPFCP_CFLAGS) \
$(NULL)
osmo_hnbgw_LDADD += \
$(LIBOSMOGTLV_LIBS) \
$(LIBOSMOPFCP_LIBS) \
$(NULL)
osmo_hnbgw_SOURCES += \
hnbgw_pfcp.c \
ps_rab_ass_fsm.c \
ps_rab_fsm.c \
$(NULL)
endif

View File

@@ -19,13 +19,20 @@
*
*/
#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/sigtran/sccp_helpers.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_rua.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"},
@@ -53,8 +60,21 @@ 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++) {
/* SUA: RFC3868 sec 3.10.4:
* The source reference number is a 4 octet long integer.
* This is allocated by the source SUA instance.
* M3UA/SCCP: ITU-T Q.713 sec 3.3:
* The "source local reference" parameter field is a three-octet field containing a
* reference number which is generated and used by the local node to identify the
* connection section after the connection section is set up.
* The coding "all ones" is reserved for future use.
* Hence, let's simply use 24 bit ids to fit all link types (excluding 0x00ffffff).
*/
for (i = 0; i < 0x00ffffff; i++) {
id = cn->next_conn_id++;
if (cn->next_conn_id == 0x00ffffff)
cn->next_conn_id = 0;
if (!cn_id_in_use(cn, id)) {
*id_out = id;
return 1;
@@ -100,6 +120,8 @@ context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id,
map->rua_ctx_id = rua_ctx_id;
map->is_ps = is_ps;
map->scu_conn_id = new_scu_conn_id;
INIT_LLIST_HEAD(&map->ps_rab_ass);
INIT_LLIST_HEAD(&map->ps_rabs);
/* put it into both lists */
llist_add_tail(&map->hnb_list, &hnb->map_list);
@@ -129,14 +151,47 @@ context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id)
return NULL;
}
int context_map_send_cached_msg(struct hnbgw_context_map *map)
{
int rc;
if (!map || !map->cached_msg)
return 0;
rc = rua_to_scu(map->hnb_ctx,
map->is_ps ? RUA_CN_DomainIndicator_ps_domain : RUA_CN_DomainIndicator_cs_domain,
OSMO_SCU_PRIM_N_DATA, map->rua_ctx_id, 0,
msgb_data(map->cached_msg), msgb_length(map->cached_msg));
msgb_free(map->cached_msg);
map->cached_msg = NULL;
return rc;
}
void context_map_deactivate(struct hnbgw_context_map *map)
{
LOG_MAP(map, DMAIN, LOGL_INFO, "Deactivating\n");
/* 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 */
if (map->state != MAP_S_RESERVED2)
map->state = MAP_S_RESERVED1;
/* Is SCCP still active and needs to be disconnected ungracefully? */
if (map->scu_conn_active) {
osmo_sccp_tx_disconn(map->hnb_ctx->gw->sccp.cnlink->sccp_user, map->scu_conn_id, NULL, 0);
map->scu_conn_active = false;
}
/* a possibly still existing MGW FSM must be terminated when the context
* map is deactivated. (this is a cornercase) */
if (map->mgw_fi) {
mgw_fsm_release(map);
OSMO_ASSERT(map->mgw_fi == NULL);
}
#if ENABLE_PFCP
hnbgw_gtpmap_release(map);
#endif
}
static struct osmo_timer_list context_map_tmr;
@@ -158,6 +213,7 @@ static void context_map_tmr_cb(void *data)
case MAP_S_RESERVED2:
/* second time we see this reserved
* entry: remove it */
LOG_MAP(map, DMAIN, LOGL_INFO, "Deallocating\n");
map->state = MAP_S_NULL;
llist_del(&map->cn_list);
llist_del(&map->hnb_list);

View File

@@ -49,9 +49,12 @@
#include <osmocom/ctrl/ports.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/misc.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/ports.h>
#include <osmocom/mgcp_client/mgcp_client.h>
#include <osmocom/netif/stream.h>
#include <osmocom/ranap/ranap_common.h>
@@ -59,10 +62,15 @@
#include <osmocom/sigtran/protocol/m3ua.h>
#include <osmocom/sigtran/sccp_sap.h>
#if ENABLE_PFCP
#include <osmocom/pfcp/pfcp_proto.h>
#endif
#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/hnbgw_pfcp.h>
#include <osmocom/hnbgw/context_map.h>
static const char * const osmo_hnbgw_copyright =
@@ -86,12 +94,24 @@ static struct hnb_gw *hnb_gw_create(void *ctx)
gw->config.iuh_local_port = IUH_DEFAULT_SCTP_PORT;
gw->config.log_prefix_hnb_id = true;
/* No limit by default, always include the initial RANAP message in the SCCP CR towards the CN.
* 999999 is the maximum value in hnbgw_vty.c */
gw->config.max_sccp_cr_payload_len = 999999;
gw->next_ue_ctx_id = 23;
INIT_LLIST_HEAD(&gw->hnb_list);
INIT_LLIST_HEAD(&gw->ue_list);
context_map_init(gw);
gw->mgw_pool = mgcp_client_pool_alloc(gw);
gw->config.mgcp_client = talloc_zero(tall_hnb_ctx, struct mgcp_client_conf);
mgcp_client_conf_init(gw->config.mgcp_client);
#if ENABLE_PFCP
gw->config.pfcp.remote_port = OSMO_PFCP_PORT;
#endif
return gw;
}
@@ -220,30 +240,71 @@ void ue_context_free(struct ue_context *ue)
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 varoius downstream processing functions */
* benefit of various downstream processing functions */
msg->dst = hnb;
rc = osmo_stream_srv_recv(conn, msg);
if (rc == -EAGAIN) {
/* Notification received */
msgb_free(msg);
return 0;
/* 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;
/* 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()\n");
/* FIXME: clean up after disappeared HNB */
hnb_context_release(hnb);
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) {
hnb_context_release(hnb);
rc = -1;
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);
@@ -255,6 +316,10 @@ static int hnb_read_cb(struct osmo_stream_srv *conn)
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;
@@ -275,6 +340,21 @@ out:
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;
}
struct hnb_context *hnb_context_alloc(struct hnb_gw *gw, struct osmo_stream_srv_link *link, int new_fd)
{
struct hnb_context *ctx;
@@ -285,7 +365,7 @@ struct hnb_context *hnb_context_alloc(struct hnb_gw *gw, struct osmo_stream_srv_
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);
ctx->conn = osmo_stream_srv_create(tall_hnb_ctx, 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);
@@ -319,6 +399,8 @@ void hnb_context_release(struct hnb_context *ctx)
{
struct hnbgw_context_map *map, *map2;
LOGHNB(ctx, DMAIN, LOGL_INFO, "Releasing HNB context\n");
/* remove from the list of HNB contexts */
llist_del(&ctx->list);
@@ -333,17 +415,31 @@ void hnb_context_release(struct hnb_context *ctx)
}
ue_context_free_by_hnb(ctx->gw, ctx);
osmo_stream_srv_destroy(ctx->conn);
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 */
talloc_free(ctx);
}
bool hnbgw_requires_empty_sccp_cr(struct hnb_gw *gw, unsigned int ranap_msg_len)
{
return ranap_msg_len > gw->config.max_sccp_cr_payload_len;
}
/*! 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;
LOGP(DMAIN, LOGL_INFO, "New HNB SCTP connection %s\n",
osmo_sock_get_name2(fd));
ctx = hnb_context_alloc(gw, srv, fd);
if (!ctx)
return -ENOMEM;
@@ -372,6 +468,11 @@ 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",
},
};
static const struct log_info hnbgw_log_info = {
@@ -573,6 +674,41 @@ static int hnb_ctrl_node_lookup(void *data, vector vline, int *node_type, void *
return 1;
}
static int hnbgw_mgw_setup(void)
{
struct mgcp_client *mgcp_client_single;
unsigned int pool_members_initalized;
/* 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. */
pool_members_initalized = mgcp_client_pool_connect(g_hnb_gw->mgw_pool);
if (pool_members_initalized) {
LOGP(DMGW, LOGL_NOTICE,
"MGW pool with %u pool members configured, (ignoring MGW configuration in VTY node 'mgcp').\n",
pool_members_initalized);
return 0;
}
/* 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(tall_hnb_ctx, g_hnb_gw->config.mgcp_client);
if (!mgcp_client_single) {
LOGP(DMGW, LOGL_ERROR, "MGW (single) client initalization failed\n");
return -EINVAL;
}
if (mgcp_client_connect(mgcp_client_single)) {
LOGP(DMGW, LOGL_ERROR, "MGW (single) connect failed at (%s:%u)\n",
g_hnb_gw->config.mgcp_client->remote_addr,
g_hnb_gw->config.mgcp_client->remote_port);
return -EINVAL;
}
mgcp_client_pool_register_single(g_hnb_gw->mgw_pool, mgcp_client_single);
return 0;
}
int main(int argc, char **argv)
{
struct osmo_stream_srv_link *srv;
@@ -595,6 +731,7 @@ int main(int argc, char **argv)
exit(1);
}
vty_info.tall_ctx = tall_hnb_ctx;
vty_info.copyright = osmo_hnbgw_copyright;
vty_init(&vty_info);
@@ -603,6 +740,7 @@ int main(int argc, char **argv)
hnbgw_vty_init(g_hnb_gw, tall_hnb_ctx);
ctrl_vty_init(tall_hnb_ctx);
logging_vty_add_cmds();
osmo_talloc_vty_add_cmds();
/* Handle options after vty_init(), for --version */
handle_options(argc, argv);
@@ -629,14 +767,14 @@ int main(int argc, char **argv)
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);
rc = telnet_init_default(tall_hnb_ctx, g_hnb_gw, 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);
g_hnb_gw->ctrl = ctrl_interface_setup2(g_hnb_gw, 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);
@@ -681,6 +819,15 @@ int main(int argc, char **argv)
}
g_hnb_gw->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(g_hnb_gw);
#endif
if (hnbgw_cmdline_config.daemonize) {
rc = osmo_daemonize();
if (rc < 0) {
@@ -690,7 +837,7 @@ int main(int argc, char **argv)
}
while (1) {
rc = osmo_select_main(0);
rc = osmo_select_main_ctx(0);
if (rc < 0)
exit(3);
}

View File

@@ -18,6 +18,8 @@
*
*/
#include "config.h"
#include <arpa/inet.h>
#include <errno.h>
@@ -29,11 +31,21 @@
#include <osmocom/sigtran/sccp_sap.h>
#include <osmocom/sigtran/sccp_helpers.h>
#if ENABLE_PFCP
#include <osmocom/pfcp/pfcp_cp_peer.h>
#endif
#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/context_map.h>
#include <osmocom/hnbgw/mgw_fsm.h>
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
#include <osmocom/ranap/RANAP_ProcedureCode.h>
#include <osmocom/ranap/ranap_common.h>
#include <osmocom/ranap/ranap_common_ran.h>
#include <osmocom/ranap/RANAP_RANAP-PDU.h>
/***********************************************************************
* Outbound RANAP RESET to CN
@@ -266,7 +278,7 @@ static int handle_cn_ranap(struct hnbgw_cnlink *cnlink, const struct osmo_scu_un
int rc;
memset(pdu, 0, sizeof(*pdu));
dec_ret = aper_decode(NULL,&asn_DEF_RANAP_RANAP_PDU, (void **) &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");
@@ -324,18 +336,29 @@ 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 */
struct osmo_ss7_instance *ss7 = osmo_sccp_get_ss7(cnlink->gw->sccp.client);
struct hnbgw_context_map *map;
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));
LOGP(DMAIN, LOGL_DEBUG, "handle_cn_conn_conf() conn_id=%d, addrs: called=%s calling=%s responding=%s\n",
param->conn_id,
osmo_sccp_addr_to_str_c(OTC_SELECT, ss7, &param->called_addr),
osmo_sccp_addr_to_str_c(OTC_SELECT, ss7, &param->calling_addr),
osmo_sccp_addr_to_str_c(OTC_SELECT, ss7, &param->responding_addr));
/* Nothing needs to happen for RUA, RUA towards the HNB doesn't seem to know any confirmations to its CONNECT
* operation. */
map = context_map_by_cn(cnlink, param->conn_id);
if (!map)
return 0;
/* SCCP connection is confirmed. Mark conn as active, i.e. requires a DISCONNECT to clean up the SCCP
* connection. */
map->scu_conn_active = true;
/* If our initial SCCP CR was sent without data payload, then the initial RANAP message is cached and waiting to
* be sent as soon as the SCCP connection is confirmed. See if that is the case, send cached data. */
context_map_send_cached_msg(map);
return 0;
}
@@ -345,10 +368,13 @@ static int handle_cn_data_ind(struct hnbgw_cnlink *cnlink,
struct osmo_prim_hdr *oph)
{
struct hnbgw_context_map *map;
ranap_message *message;
int rc;
/* connection-oriented data is always passed transparently
* towards the specific HNB, via a RUA connection identified by
* conn_id */
/* Usually connection-oriented data is always passed transparently towards the specific HNB, via a RUA
* connection identified by conn_id. An exception is made for RANAP RAB AssignmentRequest and
* RANAP RAB AssignmentResponse, since those messages contain transport layer information (RTP stream IP/Port),
* which is rewritten by the FSM that controls the co-located media gateway. */
map = context_map_by_cn(cnlink, param->conn_id);
if (!map) {
@@ -356,6 +382,63 @@ static int handle_cn_data_ind(struct hnbgw_cnlink *cnlink,
return 0;
}
/* Intercept RAB Assignment Request, to map RTP and GTP between access and core */
if (!map->is_ps) {
/* Circuit-Switched. Set up mapping of RTP ports via MGW */
message = talloc_zero(map, ranap_message);
rc = ranap_ran_rx_co_decode(map, message, msgb_l2(oph->msg), msgb_l2len(oph->msg));
if (rc == 0) {
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* mgw_fsm_alloc_and_handle_rab_ass_req() takes ownership of (ranap) message */
return handle_rab_ass_req(map, oph, 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 the rua_tx_dt() call below. */
mgw_fsm_release(map);
break;
}
ranap_ran_rx_co_free(message);
}
talloc_free(message);
#if ENABLE_PFCP
} else {
struct hnb_gw *hnb_gw = cnlink->gw;
/* Packet-Switched. Set up mapping of GTP ports via UPF */
message = talloc_zero(map, ranap_message);
rc = ranap_ran_rx_co_decode(map, message, msgb_l2(oph->msg), msgb_l2len(oph->msg));
if (rc == 0) {
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(hnb_gw)) {
LOGP(DMAIN, LOGL_DEBUG,
"RAB Assignment: setting up GTP tunnel mapping via UPF %s\n",
osmo_sockaddr_to_str_c(OTC_SELECT, &hnb_gw->pfcp.cp_peer->remote_addr));
return hnbgw_gtpmap_rx_rab_ass_req(map, oph, message);
}
/* If no UPF is configured, directly forward the message as-is (no GTP mapping). */
LOGP(DMAIN, 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 the rua_tx_dt() call below. */
hnbgw_gtpmap_release(map);
break;
}
ranap_ran_rx_co_free(message);
}
talloc_free(message);
#endif
}
return rua_tx_dt(map->hnb_ctx, map->is_ps, map->rua_ctx_id,
msgb_l2(oph->msg), msgb_l2len(oph->msg));
}
@@ -510,6 +593,8 @@ int hnbgw_cnlink_init(struct hnb_gw *gw, const char *stp_host, uint16_t stp_port
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);
if (!ss7)
return -1;
ss7->cfg.primary_pc = (23 << 3) + 5;
}

View File

@@ -69,7 +69,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,
@@ -111,7 +111,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,
@@ -284,6 +284,7 @@ static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
uint32_t ctx_id;
uint32_t tmsi = 0;
struct ue_context *ue;
struct ue_context *ue_allocated = NULL;
int rc;
memset(&accept, 0, sizeof(accept));
@@ -331,14 +332,19 @@ static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
ue = ue_context_by_tmsi(hnb->gw, tmsi);
if (!ue)
ue = ue_context_alloc(hnb, NULL, tmsi);
ue = ue_allocated = ue_context_alloc(hnb, NULL, tmsi);
asn1_u24_to_bitstring(&accept.context_ID, &ctx_id, ue->context_id);
memset(&accept_out, 0, sizeof(accept_out));
rc = hnbap_encode_ueregisteraccepties(&accept_out, &accept);
if (rc < 0)
if (rc < 0) {
/* If we allocated the UE context but the UE REGISTER fails, get rid of it again: there will likely
* never be a UE DE-REGISTER for this UE from the HNB, and the ue_context would linger forever. */
if (ue_allocated)
ue_context_free(ue_allocated);
return rc;
}
msg = hnbap_generate_successful_outcome(HNBAP_ProcedureCode_id_UERegister,
HNBAP_Criticality_reject,
@@ -388,21 +394,26 @@ static int hnbgw_rx_hnb_deregister(struct hnb_context *ctx, ANY_t *in)
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 int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
{
struct hnb_context *hnb;
struct hnb_context *hnb, *tmp;
HNBAP_HNBRegisterRequestIEs_t ies;
int rc;
struct osmo_plmn_id plmn;
struct osmo_fd *ofd = osmo_stream_srv_get_ofd(ctx->conn);
char name[OSMO_SOCK_NAME_MAXLEN];
osmo_sock_get_name_buf(name, sizeof(name), ofd->fd);
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);
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failure to decode HNB-REGISTER-REQ %s from %s: rc=%d\n",
ctx->identity_info, name, rc);
return rc;
}
@@ -413,23 +424,52 @@ static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
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, &plmn);
ctx->id.mcc = plmn.mcc;
ctx->id.mnc = plmn.mnc;
llist_for_each_entry(hnb, &ctx->gw->hnb_list, list) {
llist_for_each_entry_safe(hnb, tmp, &ctx->gw->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);
/* 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 = {};
struct osmo_sockaddr cur_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;
}
len = sizeof(cur_osa);
if (getpeername(ofd->fd, &cur_osa.u.sa, &len) < 0) {
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Error getpeername(): %s\n", strerror(errno));
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 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 "
"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);
hnbap_free_hnbregisterrequesties(&ies);
return hnbgw_tx_hnb_register_rej(ctx);
}
}
ctx->hnb_registered = true;
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "HNB-REGISTER-REQ %s MCC=%u,MNC=%u,LAC=%u,RAC=%u,SAC=%u,CID=%u from %s%s\n",
ctx->identity_info, ctx->id.mcc, ctx->id.mnc, ctx->id.lac, ctx->id.rac, ctx->id.sac, ctx->id.cid,
name, ctx->hnb_registered ? " (duplicated)" : "");
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "HNB-REGISTER-REQ from %s\n", ctx->identity_info);
ctx->hnb_registered = true;
/* Send HNBRegisterAccept */
rc = hnbgw_tx_hnb_register_acc(ctx);
@@ -441,6 +481,7 @@ static int hnbgw_rx_ue_register_req(struct hnb_context *ctx, ANY_t *in)
{
HNBAP_UERegisterRequestIEs_t ies;
struct ue_context *ue;
struct ue_context *ue_allocated = NULL;
char imsi[16];
int rc;
@@ -482,11 +523,18 @@ static int hnbgw_rx_ue_register_req(struct hnb_context *ctx, ANY_t *in)
ue = ue_context_by_imsi(ctx->gw, imsi);
if (!ue)
ue = ue_context_alloc(ctx, imsi, 0);
ue = ue_allocated = 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(ue);
if (rc < 0) {
/* If we allocated the UE context but the UE REGISTER fails, get rid of it again: there will likely
* never be a UE DE-REGISTER for this UE from the HNB, and the ue_context would linger forever. */
if (ue_allocated)
ue_context_free(ue_allocated);
}
return rc;
}
static int hnbgw_rx_ue_deregister(struct hnb_context *ctx, ANY_t *in)
@@ -531,32 +579,47 @@ static int hnbgw_rx_initiating_msg(struct hnb_context *hnb, HNBAP_InitiatingMess
{
int rc = 0;
switch (imsg->procedureCode) {
case HNBAP_ProcedureCode_id_HNBRegister: /* 8.2 */
rc = hnbgw_rx_hnb_register_req(hnb, &imsg->value);
break;
case HNBAP_ProcedureCode_id_HNBDe_Register: /* 8.3 */
rc = hnbgw_rx_hnb_deregister(hnb, &imsg->value);
break;
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 */
rc = hnbgw_rx_ue_deregister(hnb, &imsg->value);
break;
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_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);
break;
default:
LOGHNB(hnb, DHNBAP, LOGL_NOTICE, "Unknown HNBAP Procedure %ld\n", imsg->procedureCode);
break;
if (!hnb->hnb_registered) {
switch (imsg->procedureCode) {
case HNBAP_ProcedureCode_id_HNBRegister: /* 8.2 */
rc = hnbgw_rx_hnb_register_req(hnb, &imsg->value);
break;
case HNBAP_ProcedureCode_id_HNBDe_Register: /* 8.3 */
rc = hnbgw_rx_hnb_deregister(hnb, &imsg->value);
break;
default:
LOGHNB(hnb, DHNBAP, LOGL_NOTICE, "HNBAP Procedure %ld not permitted for de-registered HNB\n",
imsg->procedureCode);
break;
}
} else {
switch (imsg->procedureCode) {
case HNBAP_ProcedureCode_id_HNBRegister: /* 8.2.4: Abnormal Condition, Accept. */
rc = hnbgw_rx_hnb_register_req(hnb, &imsg->value);
break;
case HNBAP_ProcedureCode_id_HNBDe_Register: /* 8.3 */
rc = hnbgw_rx_hnb_deregister(hnb, &imsg->value);
break;
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 */
rc = hnbgw_rx_ue_deregister(hnb, &imsg->value);
break;
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_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);
break;
default:
LOGHNB(hnb, DHNBAP, LOGL_NOTICE, "Unknown HNBAP Procedure %ld\n", imsg->procedureCode);
break;
}
}
return rc;

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

@@ -0,0 +1,148 @@
/* 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/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>
static void pfcp_set_msg_ctx(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req)
{
struct hnb_gw *hnb_gw = osmo_pfcp_endpoint_get_priv(ep);
if (!m->ctx.peer_fi)
osmo_pfcp_cp_peer_set_msg_ctx(hnb_gw->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(hnb_gw, 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;
}
}
int hnbgw_pfcp_init(struct hnb_gw *hnb_gw)
{
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(hnb_gw)) {
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", hnb_gw, hnb_gw->config.pfcp.remote_addr);
if (!hnb_gw->config.pfcp.local_addr) {
LOGP(DLPFCP, LOGL_ERROR, "Configuration error: missing local PFCP address, required for Node Id\n");
return -1;
}
cfg = (struct osmo_pfcp_endpoint_cfg){
.set_msg_ctx_cb = pfcp_set_msg_ctx,
.rx_msg_cb = pfcp_rx_msg,
.priv = hnb_gw,
};
/* 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, hnb_gw->config.pfcp.local_addr, hnb_gw->config.pfcp.local_port)) {
LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP local IP: %s\n",
osmo_quote_str_c(OTC_SELECT, hnb_gw->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, hnb_gw->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, hnb_gw->config.pfcp.local_addr, -1));
return -1;
}
hnb_gw->pfcp.ep = ep = osmo_pfcp_endpoint_create(hnb_gw, &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, hnb_gw->config.pfcp.remote_addr, hnb_gw->config.pfcp.remote_port)) {
LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP remote IP: %s\n",
osmo_quote_str_c(OTC_SELECT, hnb_gw->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, hnb_gw->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 */
hnb_gw->pfcp.cp_peer = osmo_pfcp_cp_peer_alloc(hnb_gw, ep, &upf_addr);
if (!hnb_gw->pfcp.cp_peer) {
LOGP(DLPFCP, LOGL_ERROR, "Cannot allocate PFCP CP Peer FSM\n");
return -1;
}
if (osmo_pfcp_cp_peer_associate(hnb_gw->pfcp.cp_peer)) {
LOGP(DLPFCP, LOGL_ERROR, "Cannot start PFCP CP Peer FSM\n");
return -1;
}
return 0;
}

View File

@@ -18,6 +18,7 @@
*
*/
#include "config.h"
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
@@ -38,6 +39,11 @@
#include <osmocom/rua/rua_ies_defs.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbap/HNBAP_CN-DomainIndicator.h>
#include <osmocom/hnbgw/mgw_fsm.h>
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
#include <osmocom/ranap/RANAP_ProcedureCode.h>
#include <osmocom/ranap/ranap_common.h>
#include <osmocom/ranap/ranap_common_cn.h>
static const char *cn_domain_indicator_to_str(RUA_CN_DomainIndicator_t cN_DomainIndicator)
{
@@ -173,11 +179,11 @@ int rua_tx_disc(struct hnb_context *hnb, int is_ps, uint32_t context_id,
/* forward a RUA message to the SCCP User API to SCCP */
static int rua_to_scu(struct hnb_context *hnb,
RUA_CN_DomainIndicator_t cN_DomainIndicator,
enum osmo_scu_prim_type type,
uint32_t context_id, uint32_t cause,
const uint8_t *data, unsigned int len)
int rua_to_scu(struct hnb_context *hnb,
RUA_CN_DomainIndicator_t cN_DomainIndicator,
enum osmo_scu_prim_type type,
uint32_t context_id, uint32_t cause,
const uint8_t *data, unsigned int len)
{
struct msgb *msg;
struct osmo_scu_prim *prim;
@@ -186,6 +192,7 @@ static int rua_to_scu(struct hnb_context *hnb,
struct osmo_sccp_addr *remote_addr;
bool is_ps;
bool release_context_map = false;
ranap_message *message;
int rc;
switch (cN_DomainIndicator) {
@@ -220,9 +227,9 @@ static int rua_to_scu(struct hnb_context *hnb,
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",
LOGHNB(hnb, DRUA, LOGL_DEBUG, "rua_to_scu() %s to %s, rua_ctx_id %u scu_conn_id %u data-len %u\n",
cn_domain_indicator_to_str(cN_DomainIndicator), osmo_sccp_addr_dump(remote_addr),
map->rua_ctx_id, map->scu_conn_id);
map->rua_ctx_id, map->scu_conn_id, len);
}
/* add primitive header */
@@ -245,6 +252,8 @@ static int rua_to_scu(struct hnb_context *hnb,
prim->u.disconnect.conn_id = map->scu_conn_id;
prim->u.disconnect.cause = cause;
release_context_map = true;
/* Mark SCCP conn as gracefully disconnected */
map->scu_conn_active = false;
break;
case OSMO_SCU_PRIM_N_UNITDATA:
prim->u.unitdata.called_addr = *remote_addr;
@@ -259,12 +268,50 @@ static int rua_to_scu(struct hnb_context *hnb,
return -EINVAL;
}
/* add optional data section, if needed */
/* If there is RANAP data, include it in the msgb. Usually there is data, but this could also be an SCCP CR
* a.k.a. OSMO_SCU_PRIM_N_CONNECT without RANAP payload. */
if (data && len) {
msg->l2h = msgb_put(msg, len);
memcpy(msg->l2h, data, len);
}
/* If there is data, see if it is a RAB Assignment message where we need to change the user plane information,
* for RTP mapping via MGW (soon also GTP mapping via UPF). */
if (data && len && map && !release_context_map) {
if (!map->is_ps) {
message = talloc_zero(map, ranap_message);
rc = ranap_cn_rx_co_decode(map, message, msgb_l2(prim->oph.msg), msgb_l2len(prim->oph.msg));
if (rc == 0) {
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* mgw_fsm_handle_rab_ass_resp() takes ownership of prim->oph and (ranap) message */
return mgw_fsm_handle_rab_ass_resp(map, &prim->oph, message);
}
ranap_cn_rx_co_free(message);
}
talloc_free(message);
#if ENABLE_PFCP
} else if (hnb_gw_is_gtp_mapping_enabled(hnb->gw)) {
/* map->is_ps == true and PFCP is enabled in osmo-hnbgw.cfg */
message = talloc_zero(map, ranap_message);
rc = ranap_cn_rx_co_decode(map, message, msgb_l2(prim->oph.msg), msgb_l2len(prim->oph.msg));
if (rc == 0) {
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* ps_rab_ass_fsm takes ownership of prim->oph and RANAP message */
return hnbgw_gtpmap_rx_rab_ass_resp(map, &prim->oph, message);
}
ranap_cn_rx_co_free(message);
}
talloc_free(message);
#endif
}
}
rc = osmo_sccp_user_sap_down(cn->sccp_user, &prim->oph);
if (map && release_context_map)
@@ -341,21 +388,69 @@ static int rua_rx_init_connect(struct msgb *msg, ANY_t *in)
struct hnb_context *hnb = msg->dst;
uint32_t context_id;
int rc;
const uint8_t *data;
unsigned int data_len;
rc = rua_decode_connecties(&ies, in);
if (rc < 0)
return rc;
context_id = asn1bitstr_to_u24(&ies.context_ID);
data = ies.ranaP_Message.buf;
data_len = ies.ranaP_Message.size;
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA %s Connect.req(ctx=0x%x, %s)\n",
cn_domain_indicator_to_str(ies.cN_DomainIndicator), context_id,
ies.establishment_Cause == RUA_Establishment_Cause_emergency_call ? "emergency" : "normal");
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA %s Connect.req(ctx=0x%x, %s, RANAP.size=%u)\n",
cn_domain_indicator_to_str(ies.cN_DomainIndicator), context_id,
ies.establishment_Cause == RUA_Establishment_Cause_emergency_call ? "emergency" : "normal",
data_len);
if (hnbgw_requires_empty_sccp_cr(hnb->gw, data_len)) {
/* Do not include data in the SCCP CR, to avoid hitting a message size limit at the remote end that may
* lead to rejection. */
bool is_ps;
struct osmo_sccp_addr *remote_addr;
struct hnbgw_context_map *map;
switch (ies.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:
LOGHNB(hnb, DRUA, LOGL_ERROR, "Unsupported Domain %ld\n", ies.cN_DomainIndicator);
rua_free_connecties(&ies);
return -1;
}
if (!hnb->gw->sccp.cnlink) {
LOGHNB(hnb, DRUA, LOGL_NOTICE, "CN=NULL, discarding message\n");
rua_free_connecties(&ies);
return 0;
}
map = context_map_alloc_by_hnb(hnb, context_id, is_ps, hnb->gw->sccp.cnlink);
OSMO_ASSERT(map);
OSMO_ASSERT(map->is_ps == is_ps);
LOGHNB(hnb, DRUA, LOGL_DEBUG, "rua_rx_init_connect() %s to %s, rua_ctx_id %u scu_conn_id %u;"
" Sending SCCP CR without payload, caching %u octets\n",
cn_domain_indicator_to_str(ies.cN_DomainIndicator), osmo_sccp_addr_dump(remote_addr),
map->rua_ctx_id, map->scu_conn_id, data_len);
map->cached_msg = msgb_alloc_c(map, data_len, "map.cached_msg");
OSMO_ASSERT(map->cached_msg);
memcpy(msgb_put(map->cached_msg, data_len), data, data_len);
/* Data is cached for after CR is confirmed, send SCCP CR but omit payload. */
data = NULL;
data_len = 0;
}
rc = rua_to_scu(hnb, ies.cN_DomainIndicator, OSMO_SCU_PRIM_N_CONNECT,
context_id, 0, ies.ranaP_Message.buf,
ies.ranaP_Message.size);
context_id, 0, data, data_len);
rua_free_connecties(&ies);
return rc;

View File

@@ -18,19 +18,25 @@
*
*/
#include "config.h"
#include <string.h>
#include <osmocom/core/socket.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/tdef_vty.h>
#include <osmocom/hnbgw/vty.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/tdefs.h>
#include <osmocom/sigtran/protocol/sua.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <osmocom/netif/stream.h>
#include <osmocom/mgcp_client/mgcp_client.h>
static void *tall_hnb_ctx = NULL;
static struct hnb_gw *g_hnb_gw = NULL;
@@ -86,6 +92,19 @@ DEFUN(cfg_hnbgw_iups, cfg_hnbgw_iups_cmd,
return CMD_SUCCESS;
}
static struct cmd_node mgcp_node = {
MGCP_NODE,
"%s(config-hnbgw-mgcp)# ",
1,
};
DEFUN(cfg_hnbgw_mgcp, cfg_hnbgw_mgcp_cmd,
"mgcp", "Configure MGCP client")
{
vty->node = MGCP_NODE;
return CMD_SUCCESS;
}
int hnbgw_vty_go_parent(struct vty *vty)
{
switch (vty->node) {
@@ -95,6 +114,10 @@ int hnbgw_vty_go_parent(struct vty *vty)
vty->node = HNBGW_NODE;
vty->index = NULL;
break;
case MGCP_NODE:
vty->node = HNBGW_NODE;
vty->index = NULL;
break;
case HNBGW_NODE:
vty->node = CONFIG_NODE;
vty->index = NULL;
@@ -309,6 +332,18 @@ DEFUN(cfg_hnbgw_log_prefix, cfg_hnbgw_log_prefix_cmd,
return CMD_SUCCESS;
}
DEFUN(cfg_hnbgw_max_sccp_cr_payload_len, cfg_hnbgw_max_sccp_cr_payload_len_cmd,
"sccp cr max-payload-len <0-999999>",
"Configure SCCP behavior\n"
"Configure SCCP Connection Request\n"
"Set an upper bound for payload data length included directly in the CR. If an initial RUA message has a"
" RANAP payload larger than this value (octets), send an SCCP CR without data, followed by an SCCP DT."
" This may be necessary if the remote component has a size limit on valid SCCP CR messages.\n")
{
g_hnb_gw->config.max_sccp_cr_payload_len = atoi(argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_hnbgw_iucs_remote_addr,
cfg_hnbgw_iucs_remote_addr_cmd,
"remote-addr NAME",
@@ -329,11 +364,59 @@ DEFUN(cfg_hnbgw_iups_remote_addr,
return CMD_SUCCESS;
}
#if ENABLE_PFCP
static struct cmd_node pfcp_node = {
PFCP_NODE,
"%s(config-hnbgw-pfcp)# ",
1,
};
DEFUN(cfg_hnbgw_pfcp, cfg_hnbgw_pfcp_cmd,
"pfcp", "Configure PFCP for GTP tunnel mapping")
{
vty->node = PFCP_NODE;
return CMD_SUCCESS;
}
DEFUN(cfg_pfcp_remote_addr, cfg_pfcp_remote_addr_cmd,
"remote-addr IP_ADDR",
"Remote UPF's listen IP address; where to send PFCP requests\n"
"IP address\n")
{
osmo_talloc_replace_string(g_hnb_gw, &g_hnb_gw->config.pfcp.remote_addr, argv[0]);
LOGP(DLPFCP, LOGL_NOTICE, "%p cfg: pfcp remote-addr %s\n", g_hnb_gw, g_hnb_gw->config.pfcp.remote_addr);
return CMD_SUCCESS;
}
DEFUN(cfg_pfcp_local_addr, cfg_pfcp_local_addr_cmd,
"local-addr IP_ADDR",
"Local address for PFCP\n"
"IP address\n")
{
osmo_talloc_replace_string(g_hnb_gw, &g_hnb_gw->config.pfcp.local_addr, argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_pfcp_local_port, cfg_pfcp_local_port_cmd,
"local-port <1-65535>",
"Local port for PFCP\n"
"IP port\n")
{
g_hnb_gw->config.pfcp.local_port = atoi(argv[0]);
return CMD_SUCCESS;
}
#endif /* ENABLE_PFCP */
static int config_write_hnbgw(struct vty *vty)
{
vty_out(vty, "hnbgw%s", VTY_NEWLINE);
vty_out(vty, " log-prefix %s%s", g_hnb_gw->config.log_prefix_hnb_id ? "hnb-id" : "umts-cell-id",
VTY_NEWLINE);
if (g_hnb_gw->config.max_sccp_cr_payload_len != 999999)
vty_out(vty, " sccp cr max-payload-len %u%s", g_hnb_gw->config.max_sccp_cr_payload_len, VTY_NEWLINE);
osmo_tdef_vty_groups_write(vty, " ");
return CMD_SUCCESS;
}
@@ -382,6 +465,19 @@ static int config_write_hnbgw_iups(struct vty *vty)
return CMD_SUCCESS;
}
#if ENABLE_PFCP
static int config_write_hnbgw_pfcp(struct vty *vty)
{
vty_out(vty, " pfcp%s", VTY_NEWLINE);
if (g_hnb_gw->config.pfcp.local_addr)
vty_out(vty, " local-addr %s%s", g_hnb_gw->config.pfcp.local_addr, VTY_NEWLINE);
if (g_hnb_gw->config.pfcp.remote_addr)
vty_out(vty, " remote-addr %s%s", g_hnb_gw->config.pfcp.remote_addr, VTY_NEWLINE);
return CMD_SUCCESS;
}
#endif
void hnbgw_vty_init(struct hnb_gw *gw, void *tall_ctx)
{
g_hnb_gw = gw;
@@ -392,6 +488,7 @@ void hnbgw_vty_init(struct hnb_gw *gw, void *tall_ctx)
install_element(HNBGW_NODE, &cfg_hnbgw_rnc_id_cmd);
install_element(HNBGW_NODE, &cfg_hnbgw_log_prefix_cmd);
install_element(HNBGW_NODE, &cfg_hnbgw_max_sccp_cr_payload_len_cmd);
install_element(HNBGW_NODE, &cfg_hnbgw_iuh_cmd);
install_node(&iuh_node, config_write_hnbgw_iuh);
@@ -415,4 +512,21 @@ void hnbgw_vty_init(struct hnb_gw *gw, void *tall_ctx)
install_element_ve(&show_one_hnb_cmd);
install_element_ve(&show_ue_cmd);
install_element_ve(&show_talloc_cmd);
install_element(HNBGW_NODE, &cfg_hnbgw_mgcp_cmd);
/* Deprecated: Old MGCP config without pooling support in MSC node: */
install_node(&mgcp_node, NULL);
mgcp_client_vty_init(tall_hnb_ctx, MGCP_NODE, g_hnb_gw->config.mgcp_client);
mgcp_client_pool_vty_init(HNBGW_NODE, MGW_NODE, " ", g_hnb_gw->mgw_pool);
#if ENABLE_PFCP
install_node(&pfcp_node, config_write_hnbgw_pfcp);
install_element(HNBGW_NODE, &cfg_hnbgw_pfcp_cmd);
install_element(PFCP_NODE, &cfg_pfcp_local_addr_cmd);
install_element(PFCP_NODE, &cfg_pfcp_local_port_cmd);
install_element(PFCP_NODE, &cfg_pfcp_remote_addr_cmd);
#endif
osmo_tdef_vty_groups_init(HNBGW_NODE, hnbgw_tdef_group);
}

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

@@ -0,0 +1,792 @@
/* (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 General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#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/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 hnbgw 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 hnb_context *hnb = map->hnb_ctx;
struct hnbgw_cnlink *cn = hnb->gw->sccp.cnlink;
struct msgb *msg;
struct osmo_scu_prim *prim;
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;
prim = (struct osmo_scu_prim *)msgb_push(msg, sizeof(*prim));
prim->u.data.conn_id = map->scu_conn_id;
osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST, msg);
osmo_sccp_user_sap_down(cn->sccp_user, &prim->oph);
}
#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 osmo_prim_hdr *ranap_rab_ass_resp_oph;
/* 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;
char msc_rtp_addr[INET6_ADDRSTRLEN];
uint16_t msc_rtp_port;
};
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 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_LOOPBACK,
};
mgw_info.codecs[0] = CODEC_IUFP;
mgw_info.codecs_len = 1;
mgw_fsm_priv->mgcpc = mgcp_client_pool_get(map->hnb_ctx->gw->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 addr;
struct osmo_sockaddr_str addr_str;
RANAP_RAB_AssignmentRequestIEs_t *ies;
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;
}
ies = &mgw_fsm_priv->ranap_rab_ass_req_message->msg.raB_AssignmentRequestIEs;
rc = ranap_rab_ass_req_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-AssignmentRequest\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;
ies = &mgw_fsm_priv->ranap_rab_ass_req_message->msg.raB_AssignmentRequestIEs;
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");
rua_tx_dt(map->hnb_ctx, map->is_ps, map->rua_ctx_id, msg->data, msg->len);
msgb_free(msg);
}
static void mgw_fsm_assign(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case MGW_EV_RAB_ASS_RESP:
mgw_fsm_state_chg(fi, MGW_ST_MDCX_HNB);
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 hnb_context *hnb = map->hnb_ctx;
struct hnbgw_cnlink *cn = hnb->gw->sccp.cnlink;
struct mgcp_conn_peer mgw_info;
struct osmo_sockaddr addr;
struct osmo_sockaddr_str addr_str;
RANAP_RAB_AssignmentResponseIEs_t *ies;
int rc;
bool rab_failed_at_hnb;
LOGPFSML(fi, LOGL_DEBUG, "RAB-AssignmentResponse received, completing HNB side call-leg on MGW...\n");
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;
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) {
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");
rc = osmo_sccp_user_sap_down(cn->sccp_user, mgw_fsm_priv->ranap_rab_ass_resp_oph);
mgw_fsm_priv->ranap_rab_ass_resp_oph = NULL;
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;
}
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-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;
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;
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;
}
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_oph->msg) +
msgb_tailroom(mgw_fsm_priv->ranap_rab_ass_resp_oph->msg);
rc = msgb_resize_area(mgw_fsm_priv->ranap_rab_ass_resp_oph->msg,
mgw_fsm_priv->ranap_rab_ass_resp_oph->msg->l2h,
msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_oph->msg), msg_max_len);
OSMO_ASSERT(rc == 0);
rc = ranap_rab_ass_resp_encode(msgb_l2(mgw_fsm_priv->ranap_rab_ass_resp_oph->msg),
msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_oph->msg), 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_oph->msg,
mgw_fsm_priv->ranap_rab_ass_resp_oph->msg->l2h,
msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_oph->msg), 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 osmo_prim_hdr *oph = mgw_fsm_priv->ranap_rab_ass_resp_oph;
struct hnb_context *hnb = map->hnb_ctx;
struct hnbgw_cnlink *cn = hnb->gw->sccp.cnlink;
int rc;
LOGPFSML(fi, LOGL_DEBUG, "forwarding modified RAB-AssignmentResponse to MSC\n");
rc = osmo_sccp_user_sap_down(cn->sccp_user, oph);
mgw_fsm_priv->ranap_rab_ass_resp_oph = NULL;
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");
}
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;
struct osmo_scu_prim *scu_prim;
struct msgb *scu_msg;
if (mgw_fsm_priv->ranap_rab_ass_req_message) {
ranap_ran_rx_co_free(mgw_fsm_priv->ranap_rab_ass_req_message);
talloc_free(mgw_fsm_priv->ranap_rab_ass_req_message);
mgw_fsm_priv->ranap_rab_ass_req_message = NULL;
}
if (mgw_fsm_priv->ranap_rab_ass_resp_message) {
ranap_cn_rx_co_free(mgw_fsm_priv->ranap_rab_ass_resp_message);
talloc_free(mgw_fsm_priv->ranap_rab_ass_resp_message);
mgw_fsm_priv->ranap_rab_ass_resp_message = NULL;
}
if (mgw_fsm_priv->ranap_rab_ass_resp_oph) {
scu_prim = (struct osmo_scu_prim *)mgw_fsm_priv->ranap_rab_ass_resp_oph;
scu_msg = scu_prim->oph.msg;
msgb_free(scu_msg);
mgw_fsm_priv->ranap_rab_ass_resp_oph = NULL;
}
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_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_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 handle_rab_release(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message)
{
bool rab_release_req;
struct osmo_fsm_inst *fi = map->mgw_fi;
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
int rc;
/* 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)
return -EINVAL;
LOGPFSML(map->mgw_fi, LOGL_NOTICE, "MSC asked to release RAB-ID %u\n", mgw_fsm_priv->rab_id);
/* 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 = rua_tx_dt(map->hnb_ctx, map->is_ps, map->rua_ctx_id, msgb_l2(oph->msg), msgb_l2len(oph->msg));
/* Release the FSM normally */
osmo_fsm_inst_state_chg(fi, MGW_ST_RELEASE, 0, 0);
return rc;
}
/*! Allocate MGW FSM and handle RANAP RAB AssignmentRequest).
* \ptmap[in] map hanbgw context map that is responsible for this call.
* \ptmap[in] oph osmo prim header with RANAP RAB AssignmentResponse (function takes no ownership).
* \ptmap[in] message ranap message container (function takes ownership).
* \returns 0 on success; negative on error. */
int handle_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message)
{
static bool initialized = false;
struct mgw_fsm_priv *mgw_fsm_priv;
char fsm_name[255];
int rc;
/* Initialize FSM if not done yet */
if (!initialized) {
OSMO_ASSERT(osmo_fsm_register(&mgw_fsm) == 0);
initialized = true;
}
/* The RTP stream negotiation usually begins with a RAB-AssignmentRequest and ends with an IU-Release, however
* it may also be thet 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) {
/* A RAB Release might be in progress, handle it */
rc = handle_rab_release(map, oph, message);
if (rc >= 0)
return rc;
LOGPFSML(map->mgw_fi, LOGL_ERROR,
"mgw_fsm_alloc_and_handle_rab_ass_req() unable to handle RAB-AssignmentRequest!\n");
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) {
LOGP(DMGW, LOGL_ERROR,
"mgw_fsm_alloc_and_handle_rab_ass_req() rua_ctx_id=%d, RAB-AssignmentRequest with more than one RAB assignment -- abort!\n",
map->rua_ctx_id);
tx_release_req(map);
return -1;
}
mgw_fsm_priv = talloc_zero(map, struct mgw_fsm_priv);
mgw_fsm_priv->map = map;
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).
* \ptmap[in] map hanbgw context map that is responsible for this call.
* \ptmap[in] oph osmo prim header with RANAP RAB AssignmentResponse (function takes ownership).
* \ptmap[in] message ranap message container with decoded ranap message (function takes ownership).
* \returns 0 on success; negative on error. */
int mgw_fsm_handle_rab_ass_resp(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message)
{
struct mgw_fsm_priv *mgw_fsm_priv;
struct osmo_scu_prim *prim;
struct msgb *msg;
OSMO_ASSERT(oph);
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. */
LOGP(DMGW, LOGL_ERROR,
"mgw_fsm_handle_rab_ass_resp() rua_ctx_id=%d, no MGW fsm -- sending Iu-Release-Request!\n",
map->rua_ctx_id);
/* Cleanup ranap message */
ranap_cn_rx_co_free(message);
talloc_free(message);
/* Toss RAB-AssignmentResponse */
prim = (struct osmo_scu_prim *)oph;
msg = prim->oph.msg;
msgb_free(msg);
/* Send a release request, to make sure that the MSC is aware of the problem. */
tx_release_req(map);
return -1;
}
mgw_fsm_priv = map->mgw_fi->priv;
mgw_fsm_priv->ranap_rab_ass_resp_oph = oph;
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 hanbgw context map that is responsible for this call.
* \returns 0 on success; negative on error. */
int mgw_fsm_release(struct hnbgw_context_map *map)
{
if (!map->mgw_fi)
return -EINVAL;
osmo_fsm_inst_dispatch(map->mgw_fi, MGW_EV_RELEASE, NULL);
return 0;
}

View File

@@ -0,0 +1,687 @@
/* 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 General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#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_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)
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_FAIL,
};
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_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 of PS RAB Assignment procedures
* - ps_rab_ass_fsm: one RANAP PS RAB Assignment procedure
* - ...
* - .ps_rabs: 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 osmo_prim_hdr *ranap_rab_ass_resp_oph;
/* 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;
};
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(state) \
osmo_tdef_fsm_inst_state_chg(fi, state, ps_rab_ass_fsm_timeouts, ps_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);
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;
}
int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message)
{
RANAP_RAB_AssignmentRequestIEs_t *ies = &message->msg.raB_AssignmentRequestIEs;
int i;
struct hnb_gw *hnb_gw = map->hnb_ctx->gw;
struct ps_rab_ass *rab_ass;
struct osmo_fsm_inst *fi;
rab_ass = ps_rab_ass_alloc(map);
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(hnb_gw->pfcp.cp_peer)) {
LOG_MAP(map, DLPFCP, LOGL_ERROR, "PFCP is not associated, cannot set up GTP mapping\n");
goto no_rab;
}
/* Make sure we indeed deal with a setup-or-modify list */
if (!(ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT)) {
LOG_MAP(map, DLPFCP, LOGL_ERROR, "RANAP PS RAB AssignmentRequest lacks setup-or-modify list\n");
goto no_rab;
}
/* Multiple RABs may be set up, assemble in list rab_ass->ps_rabs. */
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;
}
/* Got all RABs' state and their Core side GTP info in map->ps_rabs. 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. */
fi = rab_ass->fi;
return ps_rab_ass_fsm_state_chg(PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS);
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;
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);
}
/* 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;
}
rua_tx_dt(rab_ass->map->hnb_ctx, rab_ass->map->is_ps, rab_ass->map->rua_ctx_id, msg->data, msg->len);
msgb_free(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 osmo_prim_hdr *oph, 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.
*/
int rc;
int i;
struct ps_rab_ass *rab_ass;
struct osmo_fsm_inst *fi;
RANAP_RAB_AssignmentResponseIEs_t *ies;
struct hnb_gw *hnb_gw = map->hnb_ctx->gw;
/* Make sure we indeed deal with a setup-or-modify list */
ies = &message->msg.raB_AssignmentResponseIEs;
if (!(ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_SETUPORMODIFIEDLIST_PRESENT)) {
LOG_MAP(map, DRUA, LOGL_ERROR, "RANAP PS RAB AssignmentResponse lacks setup-or-modify list\n");
return -1;
}
rab_ass = ps_rab_ass_alloc(map);
rab_ass->ranap_rab_ass_resp_message = message;
rab_ass->ranap_rab_ass_resp_oph = oph;
/* Now rab_ass owns message and will clean it up */
if (!osmo_pfcp_cp_peer_is_associated(hnb_gw->pfcp.cp_peer)) {
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "PFCP is not associated, cannot set up GTP mapping\n");
ps_rab_ass_failure(rab_ass);
return -1;
}
LOG_PS_RAB_ASS(rab_ass, LOGL_NOTICE, "PS RAB-AssignmentResponse received, updating RABs\n");
/* Multiple RABs may be set up, bump matching FSMs in list rab_ass->ps_rabs. */
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);
}
/* Got all RABs' state and updated their Access side GTP info in map->ps_rabs. 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. */
fi = rab_ass->fi;
return ps_rab_ass_fsm_state_chg(PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED);
}
static void ps_rab_ass_resp_send_if_ready(struct ps_rab_ass *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++;
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;
}
/* All RABs have succeeded, ready to forward */
ps_rab_ass_resp_send_if_ready(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;
struct hnbgw_cnlink *cn = rab_ass->map->cn_link;
RANAP_RAB_AssignmentResponseIEs_t *ies = &rab_ass->ranap_rab_ass_resp_message->msg.raB_AssignmentResponseIEs;
/* 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 */
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;
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);
}
/* 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(rab_ass->ranap_rab_ass_resp_oph->msg),
msgb_l2len(rab_ass->ranap_rab_ass_resp_oph->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_NOTICE, "Sending RANAP PS RAB-AssignmentResponse with mapped GTP info\n");
rc = osmo_sccp_user_sap_down(cn->sccp_user, rab_ass->ranap_rab_ass_resp_oph);
rab_ass->ranap_rab_ass_resp_oph = NULL;
if (rc < 0) {
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Sending RANAP PS RAB-AssignmentResponse failed\n");
ps_rab_ass_failure(rab_ass);
}
/* 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 osmo_scu_prim *scu_prim;
struct msgb *scu_msg;
struct ps_rab *rab;
if (rab_ass->ranap_rab_ass_req_message) {
ranap_ran_rx_co_free(rab_ass->ranap_rab_ass_req_message);
talloc_free(rab_ass->ranap_rab_ass_req_message);
rab_ass->ranap_rab_ass_req_message = NULL;
}
if (rab_ass->ranap_rab_ass_resp_message) {
ranap_cn_rx_co_free(rab_ass->ranap_rab_ass_resp_message);
talloc_free(rab_ass->ranap_rab_ass_resp_message);
rab_ass->ranap_rab_ass_resp_message = NULL;
}
if (rab_ass->ranap_rab_ass_resp_oph) {
scu_prim = (struct osmo_scu_prim *)rab_ass->ranap_rab_ass_resp_oph;
scu_msg = scu_prim->oph.msg;
msgb_free(scu_msg);
rab_ass->ranap_rab_ass_resp_oph = NULL;
}
llist_for_each_entry(rab, &rab_ass->map->ps_rabs, entry) {
if (rab->req_fi == fi)
rab->req_fi = NULL;
if (rab->resp_fi == fi)
rab->resp_fi = NULL;
}
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_rabs, entry) {
ps_rab_release(rab);
}
llist_for_each_entry_safe(rab_ass, next, &map->ps_rab_ass, 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_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);
}

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

@@ -0,0 +1,819 @@
/* 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 General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#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, ps_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, map->hnb_ctx->gw, 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,
.hnb_gw = map->hnb_ctx->gw,
.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_rabs);
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(struct hnb_gw *hnb_gw, uint64_t seid, bool is_cp_seid)
{
struct hnb_context *hnb;
llist_for_each_entry(hnb, &hnb_gw->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_rabs, 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 hnb_gw *hnb_gw = rab->hnb_gw;
struct osmo_pfcp_msg *m = osmo_pfcp_cp_peer_new_req(hnb_gw->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_rabs, 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);
}
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;
}
/* 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,
};
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,
};
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);
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 hnb_gw *hnb_gw = rab->hnb_gw;
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_next_seid(&hnb_gw->pfcp.cp_peer->next_seid_state);
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(hnb_gw->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(hnb_gw->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)
{
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);
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. */
struct ps_rab *rab = fi->priv;
struct hnb_gw *hnb_gw = rab->hnb_gw;
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_ACCESS_TO_CORE, &rab->access.remote)) {
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(hnb_gw->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 hnb_gw *hnb_gw = rab->hnb_gw;
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(hnb_gw->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;
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)
{
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);
}
void ps_rab_release(struct ps_rab *rab)
{
struct osmo_fsm_inst *fi = rab->fi;
ps_rab_forget_map(rab);
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,627 @@
/* (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 General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* 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;
RANAP_RAB_AssignmentRequest_t *rab_assignment_request = &_rab_assignment_request;
memset(rab_assignment_request, 0, sizeof(*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;
RANAP_RAB_AssignmentResponse_t *rab_assignment_response = &_rab_assignment_response;
memset(data, 0, len);
memset(rab_assignment_response, 0, sizeof(*rab_assignment_response));
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 cann now release the input 'out' */
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 and when found, decode the result into items_ies */
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;
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;
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;
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;
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;
RANAP_RAB_FailedItemIEs_t *rab_failed_items_ies = &_rab_failed_items_ies;
int rc;
bool result = true;
/* If we can get a failed item 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)
result = false;
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_FailedItem, rab_failed_items_ies);
return result;
}
/*! 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;
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;
}

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

@@ -0,0 +1,46 @@
/* (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 General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#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 ps_T_defs[] = {
{.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 = "ps", .tdefs = ps_T_defs, .desc = "timers for Packet Switched domain" },
#if ENABLE_PFCP
{.name = "pfcp", .tdefs = osmo_pfcp_tdefs, .desc = "PFCP timers" },
#endif
{ }
};

View File

@@ -1,4 +1,5 @@
SUBDIRS = \
ranap_rab_ass \
$(NULL)
# The `:;' works around a Bash 3.2 bug when the output is not writeable.

View File

@@ -0,0 +1,43 @@
AM_CPPFLAGS = \
$(all_includes) \
-I$(top_srcdir)/include \
$(NULL)
AM_CFLAGS = \
-Wall \
-ggdb3 \
$(LIBASN1C_CFLAGS) \
$(LIBOSMOCORE_CFLAGS) \
$(LIBOSMOVTY_CFLAGS) \
$(LIBOSMORANAP_CFLAGS) \
$(LIBOSMOSIGTRAN_CFLAGS) \
$(LIBOSMOMGCPCLIENT_CFLAGS) \
$(COVERAGE_CFLAGS) \
$(NULL)
EXTRA_DIST = \
ranap_rab_ass_test.ok \
$(NULL)
noinst_PROGRAMS = \
ranap_rab_ass_test \
$(NULL)
ranap_rab_ass_test_SOURCES = \
ranap_rab_ass_test.c \
$(NULL)
ranap_rab_ass_test_LDADD = \
$(LIBASN1C_LIBS) \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOVTY_LIBS) \
$(LIBOSMORANAP_LIBS) \
$(LIBOSMOSIGTRAN_LIBS) \
$(LIBOSMOMGCPCLIENT_LIBS) \
$(COVERAGE_LDFLAGS) \
$(top_builddir)/src/osmo-hnbgw/ranap_rab_ass.o \
$(NULL)
.PHONY: update_exp
update_exp:
$(builddir)/ranap_rab_ass_test >$(srcdir)/ranap_rab_ass_test.ok

View File

@@ -0,0 +1,427 @@
/* (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 General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/core/application.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/ranap/iu_helpers.h>
#include <osmocom/hnbgw/ranap_rab_ass.h>
#include <osmocom/ranap/ranap_common.h>
#include <osmocom/ranap/ranap_common_cn.h>
#include <osmocom/ranap/ranap_common_ran.h>
static void *tall_hnb_ctx;
static void *msgb_ctx;
extern void *talloc_asn1_ctx;
void test_ranap_rab_ass_req_decode_encode(void)
{
int rc;
ranap_message message;
uint8_t testvec[] = {
0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x01, 0x00,
0x36, 0x40, 0x52, 0x00, 0x00, 0x01, 0x00, 0x35,
0x00, 0x48, 0x78, 0x22, 0xcd, 0x80, 0x10, 0x2f,
0xa7, 0x20, 0x1a, 0x2c, 0x00, 0x00, 0xf4, 0x4c,
0x08, 0x0a, 0x02, 0x80, 0x00, 0x51, 0x40, 0x00,
0x27, 0x20, 0x28, 0x14, 0x00, 0x67, 0x40, 0x00,
0x00, 0x22, 0x28, 0x14, 0x00, 0x3c, 0x40, 0x00,
0x00, 0x00, 0x50, 0x3d, 0x02, 0x00, 0x02, 0x27,
0xc0, 0x35, 0x00, 0x01, 0x0a, 0x09, 0x01, 0xa2,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x1f, 0x76,
0x00, 0x00, 0x40, 0x01, 0x00
};
struct msgb *encoded;
rc = ranap_ran_rx_co_decode(talloc_asn1_ctx, &message, testvec, sizeof(testvec));
OSMO_ASSERT(rc == 0);
encoded = ranap_rab_ass_req_encode(&message.msg.raB_AssignmentRequestIEs);
OSMO_ASSERT(encoded != NULL);
printf("INPUT: %s\n", osmo_hexdump_nospc(testvec, sizeof(testvec)));
printf("RESULT: %s\n", osmo_hexdump_nospc(encoded->data, encoded->len));
OSMO_ASSERT(encoded->len == sizeof(testvec));
OSMO_ASSERT(memcmp(testvec, encoded->data, sizeof(testvec)) == 0);
ranap_ran_rx_co_free(&message);
msgb_free(encoded);
}
void test_ranap_rab_ass_resp_decode_encode(void)
{
int rc;
ranap_message message;
uint8_t testvec[] = {
0x60, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x01, 0x00,
0x34, 0x40, 0x23, 0x00, 0x00, 0x01, 0x00, 0x33,
0x40, 0x1c, 0x60, 0x3a, 0x7c, 0x35, 0x00, 0x01,
0x0a, 0x09, 0x01, 0xa4, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x40, 0x04, 0x0a, 0x00, 0x00
};
uint8_t encoded[sizeof(testvec)];
rc = ranap_cn_rx_co_decode(talloc_asn1_ctx, &message, testvec, sizeof(testvec));
OSMO_ASSERT(rc == 0);
rc = ranap_rab_ass_resp_encode(encoded, sizeof(encoded), &message.msg.raB_AssignmentResponseIEs);
printf("ranap_rab_ass_resp_encode rc=%d\n", rc);
printf("INPUT: %s\n", osmo_hexdump_nospc(testvec, sizeof(testvec)));
printf("RESULT: %s\n", osmo_hexdump_nospc(encoded, sizeof(encoded)));
OSMO_ASSERT(memcmp(testvec, encoded, sizeof(testvec)) == 0);
ranap_cn_rx_co_free(&message);
}
void test_ranap_rab_ass_req_ies_extract_inet_addr(void)
{
int rc;
struct osmo_sockaddr addr;
struct osmo_sockaddr_str addr_str;
uint8_t rab_id;
ranap_message message;
uint8_t testvec[] = {
0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x01, 0x00,
0x36, 0x40, 0x52, 0x00, 0x00, 0x01, 0x00, 0x35,
0x00, 0x48, 0x78, 0x22, 0xcd, 0x80, 0x10, 0x2f,
0xa7, 0x20, 0x1a, 0x2c, 0x00, 0x00, 0xf4, 0x4c,
0x08, 0x0a, 0x02, 0x80, 0x00, 0x51, 0x40, 0x00,
0x27, 0x20, 0x28, 0x14, 0x00, 0x67, 0x40, 0x00,
0x00, 0x22, 0x28, 0x14, 0x00, 0x3c, 0x40, 0x00,
0x00, 0x00, 0x50, 0x3d, 0x02, 0x00, 0x02, 0x27,
0xc0, 0x35, 0x00, 0x01, 0x0a, 0x09, 0x01, 0xa2,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x1f, 0x76,
0x00, 0x00, 0x40, 0x01, 0x00, 0x00
};
rc = ranap_ran_rx_co_decode(talloc_asn1_ctx, &message, testvec, sizeof(testvec));
OSMO_ASSERT(rc == 0);
rc = ranap_rab_ass_req_ies_extract_inet_addr(&addr, &rab_id, &message.msg.raB_AssignmentRequestIEs, 0);
osmo_sockaddr_str_from_sockaddr(&addr_str, &addr.u.sas);
printf("ranap_rab_ass_req_extract_inet_addr rc=%d\n", rc);
printf("RESULT: addr=%s, port=%u, rab-id=%02x\n", addr_str.ip, addr_str.port, rab_id);
ranap_ran_rx_co_free(&message);
}
void test_ranap_rab_ass_resp_ies_extract_inet_addr(void)
{
int rc;
struct osmo_sockaddr addr;
struct osmo_sockaddr_str addr_str;
ranap_message message;
uint8_t testvec[] = {
0x60, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x01, 0x00,
0x34, 0x40, 0x23, 0x00, 0x00, 0x01, 0x00, 0x33,
0x40, 0x1c, 0x60, 0x3a, 0x7c, 0x35, 0x00, 0x01,
0x0a, 0x09, 0x01, 0xa4, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x40, 0x04, 0x0a, 0x00, 0x00
};
rc = ranap_cn_rx_co_decode(talloc_asn1_ctx, &message, testvec, sizeof(testvec));
OSMO_ASSERT(rc == 0);
rc = ranap_rab_ass_resp_ies_extract_inet_addr(&addr, &message.msg.raB_AssignmentResponseIEs, 7);
osmo_sockaddr_str_from_sockaddr(&addr_str, &addr.u.sas);
printf("ranap_rab_ass_resp_extract_inet_addr rc=%d\n", rc);
printf("RESULT: addr=%s, port=%u\n", addr_str.ip, addr_str.port);
ranap_cn_rx_co_free(&message);
}
void test_ranap_rab_ass_req_ies_replace_inet_addr(void)
{
int rc;
struct osmo_sockaddr addr;
struct osmo_sockaddr_str addr_str;
ranap_message message;
struct msgb *encoded;
uint8_t rab_id;
uint8_t testvec_in[] = {
0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x01, 0x00,
0x36, 0x40, 0x52, 0x00, 0x00, 0x01, 0x00, 0x35,
0x00, 0x48, 0x78, 0x4e, 0xcd, 0x80, 0x10, 0x2f,
0xa7, 0x20, 0x1a, 0x2c, 0x00, 0x00, 0xf4, 0x4c,
0x08, 0x0a, 0x02, 0x80, 0x00, 0x51, 0x40, 0x00,
0x27, 0x20, 0x28, 0x14, 0x00, 0x67, 0x40, 0x00,
0x00, 0x22, 0x28, 0x14, 0x00, 0x3c, 0x40, 0x00,
0x00, 0x00, 0x50, 0x3d, 0x02, 0x00, 0x02, 0x27,
0xc0, 0x35, 0x00, 0x01, 0x0a, 0x09, 0x01, 0xa2,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x1f, 0xba,
0x00, 0x00, 0x40, 0x01, 0x00
};
uint8_t testvec_expected_out[] = {
0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x01, 0x00,
0x36, 0x40, 0x52, 0x00, 0x00, 0x01, 0x00, 0x35,
0x00, 0x48, 0x78, 0x4e, 0xcd, 0x80, 0x10, 0x2f,
0xa7, 0x20, 0x1a, 0x2c, 0x00, 0x00, 0xf4, 0x4c,
0x08, 0x0a, 0x02, 0x80, 0x00, 0x51, 0x40, 0x00,
0x27, 0x20, 0x28, 0x14, 0x00, 0x67, 0x40, 0x00,
0x00, 0x22, 0x28, 0x14, 0x00, 0x3c, 0x40, 0x00,
0x00, 0x00, 0x50, 0x3d, 0x02, 0x00, 0x02, 0x27,
0xc0, 0x35, 0x00, 0x01, 0x01, 0x02, 0x03, 0x04,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x04, 0xd2,
0x00, 0x00, 0x40, 0x01, 0x00
};
rc = ranap_ran_rx_co_decode(talloc_asn1_ctx, &message, testvec_in, sizeof(testvec_in));
OSMO_ASSERT(rc == 0);
rc = ranap_rab_ass_req_ies_extract_inet_addr(&addr, &rab_id, &message.msg.raB_AssignmentRequestIEs, 0);
OSMO_ASSERT(rc == 0);
osmo_sockaddr_str_from_sockaddr(&addr_str, &addr.u.sas);
printf("before: addr=%s, port=%u, rab_id=%u\n", addr_str.ip, addr_str.port, rab_id);
memset(&addr_str, 0, sizeof(addr_str));
addr_str.af = AF_INET;
addr_str.port = 1234;
osmo_strlcpy(addr_str.ip, "1.2.3.4", sizeof(addr_str.ip));
osmo_sockaddr_str_to_sockaddr(&addr_str, &addr.u.sas);
rc = ranap_rab_ass_req_ies_replace_inet_addr(&message.msg.raB_AssignmentRequestIEs, &addr, rab_id);
printf("ranap_rab_ass_req_replace_inet_addr rc=%d\n", rc);
rc = ranap_rab_ass_req_ies_extract_inet_addr(&addr, &rab_id, &message.msg.raB_AssignmentRequestIEs, 0);
OSMO_ASSERT(rc == 0);
osmo_sockaddr_str_from_sockaddr(&addr_str, &addr.u.sas);
printf("after: addr=%s, port=%u, rab_id=%u\n", addr_str.ip, addr_str.port, rab_id);
encoded = ranap_rab_ass_req_encode(&message.msg.raB_AssignmentRequestIEs);
OSMO_ASSERT(encoded != NULL);
OSMO_ASSERT(encoded->len == sizeof(testvec_expected_out));
OSMO_ASSERT(memcmp(encoded->data, testvec_expected_out, encoded->len) == 0);
ranap_ran_rx_co_free(&message);
msgb_free(encoded);
}
void test_ranap_rab_ass_resp_ies_replace_inet_addr(void)
{
int rc;
struct osmo_sockaddr addr;
struct osmo_sockaddr_str addr_str;
ranap_message message;
uint8_t testvec_in[] = {
0x60, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x01, 0x00,
0x34, 0x40, 0x23, 0x00, 0x00, 0x01, 0x00, 0x33,
0x40, 0x1c, 0x60, 0x32, 0x7c, 0x35, 0x00, 0x01,
0x0a, 0x09, 0x01, 0xa4, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x40, 0x04, 0x0a, 0x00, 0x00
};
uint8_t testvec_expected_out[] = {
0x60, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x01, 0x00,
0x34, 0x40, 0x23, 0x00, 0x00, 0x01, 0x00, 0x33,
0x40, 0x1c, 0x60, 0x32, 0x7c, 0x35, 0x00, 0x01,
0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x40, 0x04, 0xd2, 0x00, 0x00
};
rc = ranap_cn_rx_co_decode(talloc_asn1_ctx, &message, testvec_in, sizeof(testvec_in));
OSMO_ASSERT(rc == 0);
rc = ranap_rab_ass_resp_ies_extract_inet_addr(&addr, &message.msg.raB_AssignmentResponseIEs, 6);
OSMO_ASSERT(rc == 0);
osmo_sockaddr_str_from_sockaddr(&addr_str, &addr.u.sas);
printf("before: addr=%s, port=%u\n", addr_str.ip, addr_str.port);
memset(&addr_str, 0, sizeof(addr_str));
addr_str.af = AF_INET;
addr_str.port = 1234;
osmo_strlcpy(addr_str.ip, "1.2.3.4", sizeof(addr_str.ip));
osmo_sockaddr_str_to_sockaddr(&addr_str, &addr.u.sas);
rc = ranap_rab_ass_resp_ies_replace_inet_addr(&message.msg.raB_AssignmentResponseIEs, &addr, 6);
printf("ranap_rab_ass_resp_replace_inet_addr rc=%d\n", rc);
rc = ranap_rab_ass_resp_ies_extract_inet_addr(&addr, &message.msg.raB_AssignmentResponseIEs, 6);
OSMO_ASSERT(rc == 0);
osmo_sockaddr_str_from_sockaddr(&addr_str, &addr.u.sas);
printf("after: addr=%s, port=%u\n", addr_str.ip, addr_str.port);
rc = ranap_rab_ass_resp_encode(testvec_in, sizeof(testvec_in), &message.msg.raB_AssignmentResponseIEs);
OSMO_ASSERT(rc == sizeof(testvec_in));
OSMO_ASSERT(memcmp(testvec_in, testvec_expected_out, sizeof(testvec_in)) == 0);
ranap_cn_rx_co_free(&message);
}
void test_ranap_rab_ass_resp_ies_check_failure(void)
{
int rc;
ranap_message message;
bool rab_failed_at_hnb;
uint8_t testvec[] = {
0x60, 0x00, 0x00, 0x11, 0x00, 0x00, 0x01, 0x00,
0x23, 0x40, 0x0a, 0x00, 0x00, 0x01, 0x00, 0x22,
0x40, 0x03, 0x05, 0xd0, 0x00
};
rc = ranap_cn_rx_co_decode(talloc_asn1_ctx, &message, testvec, sizeof(testvec));
OSMO_ASSERT(rc == 0);
rab_failed_at_hnb =
ranap_rab_ass_resp_ies_check_failure(&message.msg.raB_AssignmentResponseIEs, 23);
printf("ranap_rab_ass_resp_ies_check_failure rab_failed_at_hnb=%u (RAB ID 23)\n", rab_failed_at_hnb);
rab_failed_at_hnb =
ranap_rab_ass_resp_ies_check_failure(&message.msg.raB_AssignmentResponseIEs, 44);
printf("ranap_rab_ass_resp_ies_check_failure rab_failed_at_hnb=%u (RAB ID 44, which is not in the message)\n",
rab_failed_at_hnb);
ranap_cn_rx_co_free(&message);
}
void test_ranap_rab_ass_req_ies_check_release(void)
{
int rc;
ranap_message message;
bool rab_release_req;
uint8_t testvec[] = {
0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x01, 0x00,
0x29, 0x40, 0x0a, 0x00, 0x00, 0x01, 0x00, 0x28,
0x40, 0x03, 0x05, 0xd0, 0x00
};
rc = ranap_ran_rx_co_decode(talloc_asn1_ctx, &message, testvec, sizeof(testvec));
OSMO_ASSERT(rc == 0);
rab_release_req =
ranap_rab_ass_req_ies_check_release(&message.msg.raB_AssignmentRequestIEs, 23);
printf("ranap_rab_ass_req_ies_check_release rab_release_req=%u (RAB ID 23)\n", rab_release_req);
rab_release_req =
ranap_rab_ass_req_ies_check_release(&message.msg.raB_AssignmentRequestIEs, 44);
printf("ranap_rab_ass_req_ies_check_release rab_release_req=%u (RAB ID 44, which is not in the message)\n", rab_release_req);
ranap_ran_rx_co_free(&message);
}
void test_ranap_rab_ass_req_ies_get_count(void)
{
int rc;
ranap_message message;
uint8_t testvec[] = {
0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x01, 0x00,
0x36, 0x40, 0x52, 0x00, 0x00, 0x01, 0x00, 0x35,
0x00, 0x48, 0x78, 0x22, 0xcd, 0x80, 0x10, 0x2f,
0xa7, 0x20, 0x1a, 0x2c, 0x00, 0x00, 0xf4, 0x4c,
0x08, 0x0a, 0x02, 0x80, 0x00, 0x51, 0x40, 0x00,
0x27, 0x20, 0x28, 0x14, 0x00, 0x67, 0x40, 0x00,
0x00, 0x22, 0x28, 0x14, 0x00, 0x3c, 0x40, 0x00,
0x00, 0x00, 0x50, 0x3d, 0x02, 0x00, 0x02, 0x27,
0xc0, 0x35, 0x00, 0x01, 0x0a, 0x09, 0x01, 0xa2,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x1f, 0x76,
0x00, 0x00, 0x40, 0x01, 0x00, 0x00
};
rc = ranap_ran_rx_co_decode(talloc_asn1_ctx, &message, testvec, sizeof(testvec));
OSMO_ASSERT(rc == 0);
rc = ranap_rab_ass_req_ies_get_count(&message.msg.raB_AssignmentRequestIEs);
printf("ranap_rab_ass_req_ies_get_count rc=%d\n", rc);
ranap_ran_rx_co_free(&message);
}
static const struct log_info_cat log_cat[] = {
[DRANAP] = {
.name = "RANAP", .loglevel = LOGL_DEBUG, .enabled = 1,
.color = "",
.description = "RAN Application Part",
},
};
static const struct log_info test_log_info = {
.cat = log_cat,
.num_cat = ARRAY_SIZE(log_cat),
};
int test_init(void)
{
int rc;
tall_hnb_ctx = talloc_named_const(NULL, 0, "hnb_context");
msgb_ctx = msgb_talloc_ctx_init(NULL, 0);
talloc_asn1_ctx = talloc_named_const(NULL, 0, "asn1_context");
rc = osmo_init_logging2(tall_hnb_ctx, &test_log_info);
if (rc < 0)
exit(1);
log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
log_set_use_color(osmo_stderr_target, 0);
log_set_print_category(osmo_stderr_target, 0);
log_set_print_category_hex(osmo_stderr_target, 0);
return rc;
}
void test_cleanup(void)
{
if (talloc_total_blocks(msgb_ctx) != 1 || talloc_total_size(msgb_ctx) != 0)
talloc_report_full(msgb_ctx, stderr);
OSMO_ASSERT(talloc_total_blocks(msgb_ctx) == 1);
OSMO_ASSERT(talloc_total_size(msgb_ctx) == 0);
talloc_free(msgb_ctx);
if (talloc_total_blocks(talloc_asn1_ctx) != 1 || talloc_total_size(talloc_asn1_ctx) != 0)
talloc_report_full(talloc_asn1_ctx, stderr);
OSMO_ASSERT(talloc_total_blocks(talloc_asn1_ctx) == 1);
OSMO_ASSERT(talloc_total_size(talloc_asn1_ctx) == 0);
talloc_free(talloc_asn1_ctx);
}
int main(int argc, char **argv)
{
test_init();
test_ranap_rab_ass_req_decode_encode();
test_ranap_rab_ass_resp_decode_encode();
test_ranap_rab_ass_req_ies_extract_inet_addr();
test_ranap_rab_ass_resp_ies_extract_inet_addr();
test_ranap_rab_ass_req_ies_replace_inet_addr();
test_ranap_rab_ass_resp_ies_replace_inet_addr();
test_ranap_rab_ass_resp_ies_check_failure();
test_ranap_rab_ass_req_ies_check_release();
test_ranap_rab_ass_req_ies_get_count();
test_cleanup();
return 0;
}
/* Stub */
const char *hnb_context_name(struct hnb_context *ctx)
{
return "TEST";
}

View File

@@ -0,0 +1,20 @@
INPUT: 0000005900000100364052000001003500487822cd80102fa7201a2c0000f44c080a028000514000272028140067400000222814003c40000000503d02000227c03500010a0901a200000000000000000000000000401f760000400100
RESULT: 0000005900000100364052000001003500487822cd80102fa7201a2c0000f44c080a028000514000272028140067400000222814003c40000000503d02000227c03500010a0901a200000000000000000000000000401f760000400100
ranap_rab_ass_resp_encode rc=46
INPUT: 6000002a000001003440230000010033401c603a7c3500010a0901a40000000000000000000000000040040a0000
RESULT: 6000002a000001003440230000010033401c603a7c3500010a0901a40000000000000000000000000040040a0000
ranap_rab_ass_req_extract_inet_addr rc=0
RESULT: addr=10.9.1.162, port=8054, rab-id=11
ranap_rab_ass_resp_extract_inet_addr rc=0
RESULT: addr=10.9.1.164, port=1034
before: addr=10.9.1.162, port=8122, rab_id=39
ranap_rab_ass_req_replace_inet_addr rc=0
after: addr=1.2.3.4, port=1234, rab_id=39
before: addr=10.9.1.164, port=1034
ranap_rab_ass_resp_replace_inet_addr rc=0
after: addr=1.2.3.4, port=1234
ranap_rab_ass_resp_ies_check_failure rab_failed_at_hnb=1 (RAB ID 23)
ranap_rab_ass_resp_ies_check_failure rab_failed_at_hnb=0 (RAB ID 44, which is not in the message)
ranap_rab_ass_req_ies_check_release rab_release_req=1 (RAB ID 23)
ranap_rab_ass_req_ies_check_release rab_release_req=0 (RAB ID 44, which is not in the message)
ranap_rab_ass_req_ies_get_count rc=1

View File

@@ -1,8 +1,8 @@
AT_INIT
AT_BANNER([Regression tests.])
#AT_SETUP([foobar])
#AT_KEYWORDS([foobar])
#cat $abs_srcdir/foobar/foobar_test.ok > expout
#AT_CHECK([$abs_top_builddir/tests/foobar/foobar_test], [], [expout], [ignore])
#AT_CLEANUP
AT_SETUP([ranap_rab_ass])
AT_KEYWORDS([ranap_rab_ass])
cat $abs_srcdir/ranap_rab_ass/ranap_rab_ass_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/ranap_rab_ass/ranap_rab_ass_test], [0], [expout], [ignore])
AT_CLEANUP