Compare commits

..

2 Commits

Author SHA1 Message Date
Oliver Smith
4c27997735 WIP: run external tests in qemu
Related: OS#6425
Change-Id: I4e3cfc5547d9e6464db58d7229ec4d8f655788e5
2024-04-05 11:40:48 +02:00
Neels Janosch Hofmeyr
838e9184c1 per-HNB GTP-U traffic counters via nft
Add external dependency libnftables.

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

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

This patch reads counters from the nftables response using "manual"
string parsing. See also Id4e7fa017c31945388a010d8581715d71482116b which
modifies this to full JSON parsing.

Related: SYS#6773
Depends: libosmocore I0df84b4bb8cb5d8434b735fa3a38e7f95be43e91
Change-Id: I35b7e97fd039e36633dfde1317170527c82f9f68
2024-04-04 12:08:47 +02:00
69 changed files with 3519 additions and 5909 deletions

1
.gitignore vendored
View File

@@ -57,6 +57,7 @@ tests/atlocal
tests/package.m4 tests/package.m4
tests/testsuite tests/testsuite
tests/testsuite.log tests/testsuite.log
tests/qemu/_*
writtenconfig/ writtenconfig/

View File

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

View File

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

View File

@@ -49,22 +49,22 @@ AC_SEARCH_LIBS([sctp_recvmsg], [sctp], [
LIBS=$old_LIBS LIBS=$old_LIBS
PKG_CHECK_MODULES(LIBASN1C, libasn1c >= 0.9.30) PKG_CHECK_MODULES(LIBASN1C, libasn1c >= 0.9.30)
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.11.0) PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore > 1.9.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.11.0) PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.9.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.11.0) PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.9.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.11.0) PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.9.0)
PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 1.6.0) PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 1.4.0)
PKG_CHECK_MODULES(LIBOSMOSIGTRAN, libosmo-sigtran >= 2.1.0) PKG_CHECK_MODULES(LIBOSMOSIGTRAN, libosmo-sigtran >= 1.8.0)
PKG_CHECK_MODULES(LIBOSMORUA, libosmo-rua >= 1.7.0) PKG_CHECK_MODULES(LIBOSMORUA, libosmo-rua >= 1.5.0)
PKG_CHECK_MODULES(LIBOSMORANAP, libosmo-ranap >= 1.7.0) PKG_CHECK_MODULES(LIBOSMORANAP, libosmo-ranap >= 1.5.0)
PKG_CHECK_MODULES(LIBOSMOHNBAP, libosmo-hnbap >= 1.7.0) PKG_CHECK_MODULES(LIBOSMOHNBAP, libosmo-hnbap >= 1.5.0)
PKG_CHECK_MODULES(LIBOSMOMGCPCLIENT, libosmo-mgcp-client >= 1.14.0) PKG_CHECK_MODULES(LIBOSMOMGCPCLIENT, libosmo-mgcp-client >= 1.12.0)
# Enable PFCP support for GTP tunnel mapping via UPF # 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])], 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"]) [osmo_ac_pfcp="$enableval"],[osmo_ac_pfcp="no"])
if test "x$osmo_ac_pfcp" = "xyes" ; then if test "x$osmo_ac_pfcp" = "xyes" ; then
PKG_CHECK_MODULES(LIBOSMOPFCP, libosmo-pfcp >= 0.5.0) PKG_CHECK_MODULES(LIBOSMOPFCP, libosmo-pfcp >= 0.3.0)
AC_DEFINE(ENABLE_PFCP, 1, [Define to build with PFCP support]) AC_DEFINE(ENABLE_PFCP, 1, [Define to build with PFCP support])
fi fi
AM_CONDITIONAL(ENABLE_PFCP, test "x$osmo_ac_pfcp" = "xyes") AM_CONDITIONAL(ENABLE_PFCP, test "x$osmo_ac_pfcp" = "xyes")
@@ -167,6 +167,17 @@ AC_MSG_CHECKING([whether to enable VTY/CTRL tests])
AC_MSG_RESULT([$enable_ext_tests]) AC_MSG_RESULT([$enable_ext_tests])
AM_CONDITIONAL(ENABLE_EXT_TESTS, test "x$enable_ext_tests" = "xyes") AM_CONDITIONAL(ENABLE_EXT_TESTS, test "x$enable_ext_tests" = "xyes")
AC_ARG_ENABLE([external_tests_qemu],
AC_HELP_STRING([--enable-external-tests-qemu],
[Run VTY/CTRL tests in QEMU [default=no]]),
[enable_ext_tests_qemu="$enableval"],[enable_ext_tests_qemu="no"])
AC_MSG_CHECKING([whether to run VTY/CTRL tests in QEMU])
AC_MSG_RESULT([$enable_ext_tests_qemu])
AM_CONDITIONAL(ENABLE_EXT_TESTS_QEMU, test "x$enable_ext_tests_qemu" = "xyes")
if test x"$exnable_ext_tests_qemu" = x"yes" && ! $srcdir/tests/qemu/check-depends.sh; then
AC_MSG_ERROR([missing programs for --enable-external-tests-qemu])
fi
# Generate manuals # Generate manuals
AC_ARG_ENABLE(manuals, AC_ARG_ENABLE(manuals,
[AS_HELP_STRING( [AS_HELP_STRING(
@@ -240,11 +251,11 @@ AC_OUTPUT(
tests/Makefile tests/Makefile
tests/atlocal tests/atlocal
tests/ranap_rab_ass/Makefile tests/ranap_rab_ass/Makefile
tests/umts_cell_id/Makefile
doc/Makefile doc/Makefile
doc/examples/Makefile doc/examples/Makefile
doc/manuals/Makefile doc/manuals/Makefile
doc/charts/Makefile doc/charts/Makefile
contrib/Makefile contrib/Makefile
contrib/systemd/Makefile contrib/systemd/Makefile
contrib/osmo-hnbgw.spec
Makefile) Makefile)

View File

@@ -34,9 +34,9 @@ export LD_LIBRARY_PATH="$inst/lib"
export PATH="$inst/bin:$PATH" export PATH="$inst/bin:$PATH"
osmo-build-dep.sh libosmocore "" --disable-doxygen osmo-build-dep.sh libosmocore "" --disable-doxygen
osmo-build-dep.sh libosmo-netif "" --disable-doxygen
osmo-build-dep.sh libosmo-abis osmo-build-dep.sh libosmo-abis
osmo-build-dep.sh libosmo-sigtran "" --disable-doxygen osmo-build-dep.sh libosmo-netif
osmo-build-dep.sh libosmo-sccp
osmo-build-dep.sh libasn1c osmo-build-dep.sh libasn1c
osmo-build-dep.sh osmo-iuh osmo-build-dep.sh osmo-iuh
osmo-build-dep.sh osmo-mgw osmo-build-dep.sh osmo-mgw
@@ -48,7 +48,7 @@ if [ "$PFCP" = "1" ]; then
CONFIG="$CONFIG --enable-pfcp" CONFIG="$CONFIG --enable-pfcp"
fi fi
if [ "$NFTABLES" = "1" ]; then if [ "$NFTABLES" = "1" ]; then
CONFIG="$CONFIG --enable-nftables" CONFIG="$CONFIG --enable-nftables --enable-external-tests-qemu"
fi fi
if [ "$WITH_MANUALS" = "1" ]; then if [ "$WITH_MANUALS" = "1" ]; then
CONFIG="$CONFIG --enable-manuals" CONFIG="$CONFIG --enable-manuals"
@@ -64,7 +64,7 @@ set -x
cd "$base" cd "$base"
autoreconf --install --force autoreconf --install --force
./configure --enable-sanitize --enable-external-tests --enable-werror $CONFIG ./configure --enable-sanitize --enable-external-tests $CONFIG
$MAKE $PARALLEL_MAKE $MAKE $PARALLEL_MAKE
LD_LIBRARY_PATH="$inst/lib" $MAKE check \ LD_LIBRARY_PATH="$inst/lib" $MAKE check \
|| cat-testlogs.sh || cat-testlogs.sh

102
contrib/osmo-hnbgw.spec.in Normal file
View File

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

View File

@@ -9,8 +9,6 @@ Restart=always
LimitNOFILE=65536 LimitNOFILE=65536
StateDirectory=osmocom StateDirectory=osmocom
WorkingDirectory=%S/osmocom WorkingDirectory=%S/osmocom
User=osmocom
Group=osmocom
ExecStart=/usr/bin/osmo-hnbgw -c /etc/osmocom/osmo-hnbgw.cfg ExecStart=/usr/bin/osmo-hnbgw -c /etc/osmocom/osmo-hnbgw.cfg
RestartSec=2 RestartSec=2

134
debian/changelog vendored
View File

@@ -1,137 +1,3 @@
osmo-hnbgw (1.7.0) unstable; urgency=medium
[ Neels Janosch Hofmeyr ]
* drop config.vty tests from make check
* coverity CID#358071
* coverity CID#358072
* coverity CID#358070
* coverity CID#358069
* on RUA Connect failure, respond with RUA Disconnect
* on RUA DT for unknown context, respond with RUA Disconnect
[ Harald Welte ]
* hnb_persistent: Use incrementing counter for rate_ctr + stat_item index
* Revert "hnb_persistent: Use incrementing counter for rate_ctr + stat_item index"
[ Oliver Smith ]
* contrib/jenkins: libosmo-sccp -> libosmo-sigtran
[ Pau Espin Pedrol ]
* Log cn_disconnect.ind cause
* Fix SCCP RLSD not sent upon rx RUA Disconnect due to error condition
* ps_rab_ass_fsm: Fix uninitialized ptr access
* Add PFCP stats item group
* hnbgw: Avoid using struct osmo_pfcp_cp_peer fields directly
* hnbgw: Fix wrong map object retrieved from hashtable
* Use new libosmo-sigtran APIs to access osmo_ss7_instance
* jenkins.sh: libosmo-netif no longer depends on libosmo-abis
* jenkins.sh: No need to build libosmo-sigtran with doxygen
* map_sccp: Fix crash during ev MAP_SCCP_EV_RAN_DISC in st MAP_SCCP_ST_WAIT_CC
* peek_l3_ul_nas: Split parsing logic based on PS/CS ctx
* peek_l3_ul_nas: Improve RAU req parsing checks
* peek_l3_ul_nas: Improve GMM Attach Req parsing checks
* peek_l3_ul_nas: Parse PTMSI MI from GMM RAU req
-- Oliver Smith <osmith@sysmocom.de> Wed, 12 Feb 2025 14:37:35 +0100
osmo-hnbgw (1.6.0) unstable; urgency=medium
[ Pau Espin Pedrol ]
* hnbgw_cn: Remove assert hit due to wrong assumption
* Increase default X31 val from 5 to 15 seconds
* context_map_sccp: Fix assert hit due to missing ev handling
* tests/ranap_rab_ass: Test RAB-Ass.req with X.213 IPv4 address len=7
* mgw_fsm: Assume IuUP HNB IP addr = Iuh during MGCP CRCX on RAN side
* mgw_fsm: Modify RAB on HNB if IuUP local IP addr at MGW changes during MDCX
* hnbap: Avoid calling duplicate getpeername() for each registered HNB
[ Neels Janosch Hofmeyr ]
* X31: fix vty doc
* allow (second) CS RAB Assignment Request without RTP info
* systemd,manual: set LimitNOFILE=65536
* rua: validate correct RUA ctx state per RUA message type
* rua: move from ERROR to DEBUG log: stray RUA Disconnect
* pfcp: fix modification of wrong FAR ID
* tweak vty doc: "UDP port"
* add tests/pfcp_cfg.vty.with_pfcp
* pfcp: fix missing vty_write of pfcp local-port
* pfcp: implement sending Network Instance IEs
* fix null deref on hnb_context_release
* drop legacy hack: do not start MGW endp in loopback mode
* fixup: compilation error: unused var in map_rua_init_action()
* fix rate_ctr leak in hnb_persistent_free()
* per-HNB GTP-U traffic counters via nft
* add hnb_persistent hashtable: optimize lookup by cell id
* fix stat_item leak in hnb_persistent_free()
* use osmo_jhash for the hnb_persistent hashtable
* rename to umts_cell_id_to_str()
* umts_cell_id: add umts_cell_id_to_str_buf()
* add umts_cell_id_test.c
* 3-digit MNC: use osmo_plmn_id in struct umts_cell_id
* add LOG_HNBP() for hnb_persistent
* hnb_persistent: introduce disconnected timeout
* prevent use-after-free after FSM instance termination
* improve HNBAP error logging
* nft-kpi: log errors of counter retrieval
* nft-kpi: remove X34 drifting: adjust delay by elapsed time
* drop list of HNBAP UE Context
* dbg log: nft kpi: clarify nr of rate ctrs vs nr of hnbp
* fix MGCP compat with osmo-mgw <= 1.12.2: CRCX in recvonly
[ Andreas Eversberg ]
* Use uniform log format for default config files
[ Harald Welte ]
* Display RANAP state during 'show cnlink'
* Fix license headers: Should have been AGPLv3+, not GPLv2+
* [cosmetic] re-order hnbgw.c to group code in major blocks
* umts_cell_id_name: Use 3-digit MCC and 2/3-digit MNC based on VTY config
* osmo_hnbgw_main: Install our usual SIGUSR1/SIGUSR2/SIGABRT handlers
* Introduce umts_cell_id_from_str() as inverse of umts_cell_id_name()
* Introduce concept of per-HNB persistent data structure
* Set persistent->ctx pointer on HNB REGISTER REQ
* New per-hnb rate_ctr and stat_item groups; track uptime
* Various new per-hnb RANAP and RUA counters
* stats: Introduce CNLINK counter macros
* stats: Introduce CNPOOL counter macro
* stats: Introduce basic counters for RANAP unit-data from CN links
* cosmetic: Fix comment in mgw_fsm.c: One RAB per context_map, not hnb!
* cosmetic: talk about cs_rab_ass_* when working in CS domain
* cosmetic: Rename hnbgw_rx_ranap and friends to *_rx_ranap_udt_ul
* rename hnbgw_peek_l3 to hnbgw_peek_l3_ul as it's uplink only
* don't forward paging requests to HNB's not yet registered
* vty: Print the uptime during 'show hnb' output
* Introduce counter for per-hnb cumulative active CS RAB duration
* stats: Add per-hnb paging:{cs,ps}:attempted counter
* cosmetic: align downlink RANAP unitdata function names with uplink
* mgw_fsm: Add some OSMO_ASSERT() to ensure only CS maps passed
* RAB activation/modification/release statistics
* context_map_{rua,sccp}: Re-order to always process KPIs
* generalize hnbgw_tx_ue_register_rej_tmsi() to hnbgw_tx_ue_register_rej()
* HNBAP: Use proper cause values during HNB-REGISTER-REQ processing
* HNBAP: Send HNB-REGISTER-REJ on ASN.1 decoding error
* HNBAP: Always respond to UE-REGISTER-REQUEST
* HNBAP: Make sure to respond with correct "reject"
* HNBAP: Transmit ErrorIndication in more situations
* HNBAP: use GSM23003_IMSI_MAX_DIGITS instead of magic number
* HNBAP: Support IMSI identity type in hnbgw_tx_ue_register_rej()
* counters: Distinguish between normal and abnormal release cause
* KPI: Add initial set of DTAP message type rate counters
* kpi_ranap: Avoid null pointer de-ref during HNB shutdown
[ Oliver Smith ]
* gitignore: add *.la, hnbgw_vty_reference.xml
* debian: fix having no manuals in osmo-hnbgw-doc
* .deb/.rpm: various fixes related to non-root
* contrib: remove rpm spec file
* debian/postinst: add checks, be verbose
* contrib/jenkins: set --enable-werror
[ Max ]
* .deb/.rpm: add osmocom user during package install
-- Oliver Smith <osmith@sysmocom.de> Thu, 25 Jul 2024 10:05:58 +0200
osmo-hnbgw (1.5.0) unstable; urgency=medium osmo-hnbgw (1.5.0) unstable; urgency=medium
[ Neels Janosch Hofmeyr ] [ Neels Janosch Hofmeyr ]

20
debian/control vendored
View File

@@ -13,17 +13,17 @@ Build-Depends: debhelper (>= 10),
libtalloc-dev, libtalloc-dev,
libasn1c-dev (>= 0.9.30), libasn1c-dev (>= 0.9.30),
libsctp-dev, libsctp-dev,
libosmocore-dev (>= 1.11.0), libosmocore-dev (>= 1.9.0),
libosmo-sigtran-dev (>= 2.1.0), libosmo-sigtran-dev (>= 1.8.0),
libosmo-abis-dev (>= 2.0.0), libosmo-abis-dev (>= 1.5.0),
libosmo-netif-dev (>= 1.6.0), libosmo-netif-dev (>= 1.4.0),
libosmo-mgcp-client-dev (>= 1.14.0), libosmo-mgcp-client-dev (>= 1.12.0),
libosmo-hnbap-dev (>= 1.7.0), libosmo-hnbap-dev (>= 1.5.0),
libosmo-ranap-dev (>= 1.7.0), libosmo-ranap-dev (>= 1.5.0),
libosmo-rua-dev (>= 1.7.0), libosmo-rua-dev (>= 1.5.0),
libosmo-pfcp-dev (>= 0.5.0), libosmo-pfcp-dev (>= 0.3.0),
libnftables-dev, libnftables-dev,
osmo-gsm-manuals-dev (>= 1.6.0) osmo-gsm-manuals-dev (>= 1.5.0)
Standards-Version: 3.9.8 Standards-Version: 3.9.8
Vcs-Git: https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw Vcs-Git: https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw
Vcs-Browser: https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw Vcs-Browser: https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw

38
debian/postinst vendored
View File

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

View File

@@ -13,6 +13,7 @@ hnbgw
plmn 001 01 plmn 001 01
iuh iuh
local-ip 0.0.0.0 local-ip 0.0.0.0
hnbap-allow-tmsi 1
msc 0 msc 0
remote-addr my-msc remote-addr my-msc
sgsn 0 sgsn 0

View File

@@ -11,6 +11,7 @@ hnbgw
plmn 001 01 plmn 001 01
iuh iuh
local-ip 0.0.0.0 local-ip 0.0.0.0
hnbap-allow-tmsi 1
mgw 0 mgw 0
remote-ip 127.0.0.1 remote-ip 127.0.0.1
local-port 2729 local-port 2729

View File

@@ -11,6 +11,7 @@ hnbgw
plmn 001 01 plmn 001 01
iuh iuh
local-ip 0.0.0.0 local-ip 0.0.0.0
hnbap-allow-tmsi 1
mgw 0 mgw 0
remote-ip 127.0.0.1 remote-ip 127.0.0.1
local-port 2729 local-port 2729

View File

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

View File

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

View File

@@ -2,8 +2,6 @@
#include <stdint.h> #include <stdint.h>
#include <osmocom/core/linuxlist.h> #include <osmocom/core/linuxlist.h>
#include <osmocom/core/utils.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnbgw.h> #include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/gsm/gsm48.h> #include <osmocom/gsm/gsm48.h>
@@ -30,17 +28,13 @@
* - The RANAP message shall be at msgb_l2(). * - The RANAP message shall be at msgb_l2().
*/ */
enum map_rua_fsm_event { enum map_rua_fsm_event {
/* Receiving a RUA Connect from HNB. /* Receiving a RUA Connect from HNB. */
* Parameter: struct msgb *ranap_msg */
MAP_RUA_EV_RX_CONNECT, MAP_RUA_EV_RX_CONNECT,
/* Receiving some data from HNB via RUA, to forward via SCCP to CN. /* Receiving some data from HNB via RUA, to forward via SCCP to CN. */
* Parameter: struct msgb *ranap_msg */
MAP_RUA_EV_RX_DIRECT_TRANSFER, MAP_RUA_EV_RX_DIRECT_TRANSFER,
/* Receiving a RUA Disconnect from HNB. /* Receiving a RUA Disconnect from HNB. */
* Parameter: struct msgb *ranap_msg (can be NULL) */
MAP_RUA_EV_RX_DISCONNECT, MAP_RUA_EV_RX_DISCONNECT,
/* SCCP has received some data from CN to forward via RUA to HNB. /* SCCP has received some data from CN to forward via RUA to HNB. */
* Parameter: struct msgb *ranap_msg */
MAP_RUA_EV_TX_DIRECT_TRANSFER, MAP_RUA_EV_TX_DIRECT_TRANSFER,
/* The CN side is disconnected (e.g. received an SCCP Released), that means we are going gracefully disconnect /* The CN side is disconnected (e.g. received an SCCP Released), that means we are going gracefully disconnect
* RUA, too. */ * RUA, too. */
@@ -55,41 +49,25 @@ enum map_rua_fsm_event {
* MAP_SCCP_EV_TX_DATA_REQUEST, MAP_SCCP_EV_RAN_DISC. * MAP_SCCP_EV_TX_DATA_REQUEST, MAP_SCCP_EV_RAN_DISC.
*/ */
enum map_sccp_fsm_event { enum map_sccp_fsm_event {
/* Receiving an SCCP CC from CN. /* Receiving an SCCP CC from CN. */
* Parameter: struct msgb *ranap_msg, may be NULL or empty. */
MAP_SCCP_EV_RX_CONNECTION_CONFIRM, MAP_SCCP_EV_RX_CONNECTION_CONFIRM,
/* Receiving some data from CN via SCCP, to forward via RUA to HNB. /* Receiving some data from CN via SCCP, to forward via RUA to HNB. */
* Parameter: struct msgb *ranap_msg, may be NULL or empty. */
MAP_SCCP_EV_RX_DATA_INDICATION, MAP_SCCP_EV_RX_DATA_INDICATION,
/* RUA has received some data from HNB to forward via SCCP to CN. /* RUA has received some data from HNB to forward via SCCP to CN. */
* Parameter: struct msgb *ranap_msg. */
MAP_SCCP_EV_TX_DATA_REQUEST, MAP_SCCP_EV_TX_DATA_REQUEST,
/* 3GPP TS 25.468 9.1.5: The RAN side received a RUA Disconnect. /* The RAN side received a Disconnect, that means we are going to expect SCCP to disconnect too.
* - Under normal conditions (cause=Normal) the RUA Disconnect contains a RANAP Iu-ReleaseComplete. * CN should have received an Iu-ReleaseComplete with or before this, give CN a chance to send an SCCP RLSD;
* On SCCP, the Iu-ReleaseComplete should still be forwarded as N-Data SCCP Data Form 1), * after a timeout we will send a non-standard RLSD to the CN instead. */
* and we will expect the CN to send an SCCP RLSD soon. Hence, give CN a chance to send an SCCP RLSD;
* after a timeout we will send a non-standard RLSD to the CN instead.
* - Under error conditions, cause!=Normal and there's no RANAP message.
* In that case, we need to tear down the associated SCCP link towards CN with an RLSD,
* which in turn will tear down the upper layer Iu conn.
*
* Parameter: bool rua_disconnect_err_condition, whether the disconnect
* happened under error or normal conditions, as per the above.
*/
MAP_SCCP_EV_RAN_DISC, MAP_SCCP_EV_RAN_DISC,
/* The RAN released ungracefully. We will directly disconnect the SCCP connection, too. /* The RAN released ungracefully. We will directly disconnect the SCCP connection, too. */
* Parameter: no parameter, NULL. */
MAP_SCCP_EV_RAN_LINK_LOST, MAP_SCCP_EV_RAN_LINK_LOST,
/* Receiving an SCCP RLSD from CN, or libosmo-sigtran tells us about SCCP connection timeout. All done. /* Receiving an SCCP RLSD from CN, or libosmo-sigtran tells us about SCCP connection timeout. All done. */
* Parameter: struct msgb *ranap_msg, may be NULL or empty. */
MAP_SCCP_EV_RX_RELEASED, MAP_SCCP_EV_RX_RELEASED,
/* The human admin asks to drop the current SCCP connection, by telnet VTY 'apply sccp' in presence of SCCP /* The human admin asks to drop the current SCCP connection, by telnet VTY 'apply sccp' in presence of SCCP
* config changes. * config changes. */
* Parameter: no parameter, NULL. */
MAP_SCCP_EV_USER_ABORT, MAP_SCCP_EV_USER_ABORT,
/* The CN link can no longer work, for example a RANAP RESET was received from the cnlink that hosts this /* The CN link can no longer work, for example a RANAP RESET was received from the cnlink that hosts this
* context map. * context map. */
* Parameter: no parameter, NULL. */
MAP_SCCP_EV_CN_LINK_LOST, MAP_SCCP_EV_CN_LINK_LOST,
}; };
@@ -130,10 +108,6 @@ enum rab_state {
RAB_STATE_ACTIVE, RAB_STATE_ACTIVE,
RAB_STATE_REL_REQ, RAB_STATE_REL_REQ,
}; };
extern const struct value_string hnbgw_rab_state_names[];
static inline const char *hnbgw_rab_state_name(enum rab_state val)
{ return get_value_string(hnbgw_rab_state_names, val); }
struct hnbgw_context_map { struct hnbgw_context_map {
/* entry in the per-CN list of mappings */ /* entry in the per-CN list of mappings */
@@ -153,28 +127,12 @@ struct hnbgw_context_map {
/* FSM handling the RUA state for rua_ctx_id. */ /* FSM handling the RUA state for rua_ctx_id. */
struct osmo_fsm_inst *rua_fi; struct osmo_fsm_inst *rua_fi;
/* State context related to field rua_fi above: */
struct {
/* Whether RUA Disconnect received from HNB happened as a normal condition or an error/abnormal condition.
* This is known based on cause and/or RANAP message included in the RUA
* Disconnect message, and tells us whether we should immediately
* terminate the related SCCP session or wait for CN to finish it.
* Defaults to false, only set to true explicitly when needed. */
bool rua_disconnect_err_condition;
} rua_fi_ctx;
/* Pointer to CN, to transceive SCCP. */ /* Pointer to CN, to transceive SCCP. */
struct hnbgw_cnlink *cnlink; struct hnbgw_cnlink *cnlink;
/* SCCP User SAP connection ID used in SCCP messages to/from the cn_link. */ /* SCCP User SAP connection ID used in SCCP messages to/from the cn_link. */
uint32_t scu_conn_id; uint32_t scu_conn_id;
/* FSM handling the SCCP state for scu_conn_id. */ /* FSM handling the SCCP state for scu_conn_id. */
struct osmo_fsm_inst *sccp_fi; struct osmo_fsm_inst *sccp_fi;
/* State context related to field sccp_fi above: */
struct {
/* List of cached packets received from RUA and to be forwarded
once SCCP CReq is CC'ed and move to CONNECTED state. */
struct llist_head wait_cc_tx_msg_list;
} sccp_fi_ctx;
/* False for CS, true for PS */ /* False for CS, true for PS */
bool is_ps; bool is_ps;
@@ -200,15 +158,15 @@ struct hnbgw_context_map {
* and Request and Response don't necessarily match the RAB IDs contained. In practice I only ever see a single * 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. * RAB matching in Request and Response, but we cannot rely on that to always be true.
* *
* The state of each RAB's PFCP negotiation is kept separately in the field ps_rab_list, and as soon as all RABs * The state of each RAB's PFCP negotiation is kept separately in the list 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 * 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. * RAB IDs and forward the RAB Assignment Request to HNB / the RAB Assignment Response to CN.
*/ */
struct llist_head ps_rab_ass_list; 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 /* 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. */ * communication for one particular RAB ID. */
struct llist_head ps_rab_list; struct llist_head ps_rabs;
/* RAB state tracking. As RAB-ID is an 8-bit integer, we need 256 elements in the array */ /* RAB state tracking. As RAB-ID is an 8-bit integer, we need 256 elements in the array */
uint8_t rab_state[256]; uint8_t rab_state[256];

View File

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

View File

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

View File

@@ -8,6 +8,7 @@
#include <osmocom/core/rate_ctr.h> #include <osmocom/core/rate_ctr.h>
#include <osmocom/core/sockaddr_str.h> #include <osmocom/core/sockaddr_str.h>
#include <osmocom/gsm/gsm23003.h> #include <osmocom/gsm/gsm23003.h>
#include <osmocom/sigtran/sccp_sap.h>
#include <osmocom/sigtran/osmo_ss7.h> #include <osmocom/sigtran/osmo_ss7.h>
#include <osmocom/ctrl/control_if.h> #include <osmocom/ctrl/control_if.h>
#include <osmocom/ranap/RANAP_CN-DomainIndicator.h> #include <osmocom/ranap/RANAP_CN-DomainIndicator.h>
@@ -19,10 +20,9 @@
#include <osmocom/mgcp_client/mgcp_client_pool.h> #include <osmocom/mgcp_client/mgcp_client_pool.h>
#include <osmocom/hnbgw/nft_kpi.h> #include <osmocom/hnbgw/nft_kpi.h>
#include <osmocom/hnbgw/cnlink.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#define STORE_UPTIME_INTERVAL 10 /* seconds */ #define STORE_UPTIME_INTERVAL 10 /* seconds */
#define HNB_STORE_RAB_DURATIONS_INTERVAL 1 /* seconds */
enum { enum {
DMAIN, DMAIN,
@@ -38,6 +38,9 @@ enum {
extern const struct log_info hnbgw_log_info; extern const struct log_info hnbgw_log_info;
extern struct vty_app_info hnbgw_vty_info; extern struct vty_app_info hnbgw_vty_info;
#define LOGHNB(HNB_CTX, ss, lvl, fmt, args ...) \
LOGP(ss, lvl, "(%s) " fmt, hnb_context_name(HNB_CTX), ## args)
#define DOMAIN_CS RANAP_CN_DomainIndicator_cs_domain #define DOMAIN_CS RANAP_CN_DomainIndicator_cs_domain
#define DOMAIN_PS RANAP_CN_DomainIndicator_ps_domain #define DOMAIN_PS RANAP_CN_DomainIndicator_ps_domain
@@ -47,6 +50,11 @@ static inline const char *ranap_domain_name(RANAP_CN_DomainIndicator_t domain)
return get_value_string(ranap_domain_names, domain); return get_value_string(ranap_domain_names, domain);
} }
enum hnb_ctrl_node {
CTRL_NODE_HNB = _LAST_CTRL_NODE,
_LAST_CTRL_NODE_HNB
};
#define HNBGW_LOCAL_IP_DEFAULT "0.0.0.0" #define HNBGW_LOCAL_IP_DEFAULT "0.0.0.0"
/* TODO: CS and PS now both connect to OsmoSTP, i.e. that's always going to be the same address. Drop the /* TODO: CS and PS now both connect to OsmoSTP, i.e. that's always going to be the same address. Drop the
* duplicity. */ * duplicity. */
@@ -56,8 +64,6 @@ static inline const char *ranap_domain_name(RANAP_CN_DomainIndicator_t domain)
#define DEFAULT_PC_HNBGW ((23 << 3) + 5) #define DEFAULT_PC_HNBGW ((23 << 3) + 5)
#define DEFAULT_PC_MSC ((23 << 3) + 1) #define DEFAULT_PC_MSC ((23 << 3) + 1)
#define DEFAULT_PC_SGSN ((23 << 3) + 4) #define DEFAULT_PC_SGSN ((23 << 3) + 4)
#define DEFAULT_ADDR_NAME_MSC "addr-dyn-msc-default"
#define DEFAULT_ADDR_NAME_SGSN "addr-dyn-sgsn-default"
/* 25.467 Section 7.1 */ /* 25.467 Section 7.1 */
#define IUH_DEFAULT_SCTP_PORT 29169 #define IUH_DEFAULT_SCTP_PORT 29169
@@ -71,8 +77,220 @@ static inline const char *ranap_domain_name(RANAP_CN_DomainIndicator_t domain)
#define IUH_MSGB_SIZE 2048 #define IUH_MSGB_SIZE 2048
enum hnb_rate_ctr {
HNB_CTR_IUH_ESTABLISHED,
HNB_CTR_RANAP_PS_ERR_IND_UL,
HNB_CTR_RANAP_CS_ERR_IND_UL,
HNB_CTR_RANAP_PS_RESET_REQ_UL,
HNB_CTR_RANAP_CS_RESET_REQ_UL,
HNB_CTR_RANAP_PS_RAB_ACT_REQ,
HNB_CTR_RANAP_CS_RAB_ACT_REQ,
HNB_CTR_RANAP_PS_RAB_ACT_CNF,
HNB_CTR_RANAP_CS_RAB_ACT_CNF,
HNB_CTR_RANAP_PS_RAB_ACT_FAIL,
HNB_CTR_RANAP_CS_RAB_ACT_FAIL,
HNB_CTR_RANAP_PS_RAB_MOD_REQ,
HNB_CTR_RANAP_CS_RAB_MOD_REQ,
HNB_CTR_RANAP_PS_RAB_MOD_CNF,
HNB_CTR_RANAP_CS_RAB_MOD_CNF,
HNB_CTR_RANAP_PS_RAB_MOD_FAIL,
HNB_CTR_RANAP_CS_RAB_MOD_FAIL,
HNB_CTR_RANAP_PS_RAB_REL_REQ,
HNB_CTR_RANAP_CS_RAB_REL_REQ,
HNB_CTR_RANAP_PS_RAB_REL_CNF,
HNB_CTR_RANAP_CS_RAB_REL_CNF,
HNB_CTR_RANAP_PS_RAB_REL_FAIL,
HNB_CTR_RANAP_CS_RAB_REL_FAIL,
HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT,
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT,
HNB_CTR_RUA_ERR_IND,
HNB_CTR_RUA_PS_CONNECT_UL,
HNB_CTR_RUA_CS_CONNECT_UL,
HNB_CTR_RUA_PS_DISCONNECT_UL,
HNB_CTR_RUA_CS_DISCONNECT_UL,
HNB_CTR_RUA_PS_DISCONNECT_DL,
HNB_CTR_RUA_CS_DISCONNECT_DL,
HNB_CTR_RUA_PS_DT_UL,
HNB_CTR_RUA_CS_DT_UL,
HNB_CTR_RUA_PS_DT_DL,
HNB_CTR_RUA_CS_DT_DL,
HNB_CTR_RUA_UDT_UL,
HNB_CTR_RUA_UDT_DL,
HNB_CTR_PS_PAGING_ATTEMPTED,
HNB_CTR_CS_PAGING_ATTEMPTED,
HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL,
HNB_CTR_GTPU_PACKETS_UL,
HNB_CTR_GTPU_TOTAL_BYTES_UL,
HNB_CTR_GTPU_PACKETS_DL,
HNB_CTR_GTPU_TOTAL_BYTES_DL,
};
enum hnb_stat {
HNB_STAT_UPTIME_SECONDS,
};
struct umts_cell_id {
uint16_t mcc; /*!< Mobile Country Code (0-999) */
uint16_t mnc; /*!< Mobile Network Code (0-999) */
uint16_t lac; /*!< Locaton Area Code (1-65534) */
uint16_t rac; /*!< Routing Area Code (0-255) */
uint16_t sac; /*!< Service Area Code */
uint32_t cid; /*!< Cell ID */
};
const char *umts_cell_id_name(const struct umts_cell_id *ucid);
int umts_cell_id_from_str(struct umts_cell_id *ucid, const char *instr);
/*! are both given umts_cell_id euqal? */
static inline bool umts_cell_id_equal(const struct umts_cell_id *a, const struct umts_cell_id *b)
{
if (a->mcc != b->mcc)
return false;
if (a->mnc != b->mnc)
return false;
if (a->lac != b->lac)
return false;
if (a->rac != b->rac)
return false;
if (a->sac != b->sac)
return false;
if (a->cid != b->cid)
return false;
return true;
}
struct hnbgw_context_map; struct hnbgw_context_map;
/* osmo-hnbgw keeps a single hnbgw_sccp_user per osmo_sccp_instance, for the local point-code and SSN == RANAP.
* This relates the (opaque) osmo_sccp_user to osmo-hnbgw's per-ss7 state. */
struct hnbgw_sccp_user {
/* entry in g_hnbgw->sccp.users */
struct llist_head entry;
/* logging context */
char *name;
/* Which 'cs7 instance' is this for? Below sccp_user is registered at the osmo_sccp_instance ss7->sccp. */
struct osmo_ss7_instance *ss7;
/* Local address: cs7 instance's primary PC if present, else the default HNBGW PC; with SSN == RANAP. */
struct osmo_sccp_addr local_addr;
/* osmo_sccp API state for above local address on above ss7 instance. */
struct osmo_sccp_user *sccp_user;
/* Fast access to the hnbgw_context_map responsible for a given SCCP conn_id of the ss7->sccp instance.
* hlist_node: hnbgw_context_map->hnbgw_sccp_user_entry. */
DECLARE_HASHTABLE(hnbgw_context_map_by_conn_id, 6);
};
#define LOG_HSI(HNBGW_SCCP_INST, SUBSYS, LEVEL, FMT, ARGS...) \
LOGP(SUBSYS, LEVEL, "(%s) " FMT, (HNBGW_SCCP_INST) ? (HNBGW_SCCP_INST)->name : "null", ##ARGS)
/* User provided configuration for struct hnbgw_cnpool. */
struct hnbgw_cnpool_cfg {
uint8_t nri_bitlen;
struct osmo_nri_ranges *null_nri_ranges;
};
/* User provided configuration for struct hnbgw_cnlink. */
struct hnbgw_cnlink_cfg {
/* cs7 address book entry to indicate both the remote point-code of the peer, as well as which cs7 instance to
* use. */
char *remote_addr_name;
struct osmo_nri_ranges *nri_ranges;
};
/* Collection of CN peers to distribute UE connections across. MSCs for DOMAIN_CS, SGSNs for DOMAIN_PS. */
struct hnbgw_cnpool {
RANAP_CN_DomainIndicator_t domain;
/* CN pool string used in VTY config and logging, "iucs" or "iups". */
const char *pool_name;
/* CN peer string used in VTY config and logging, "msc" or "sgsn". */
const char *peer_name;
/* What we use as the remote MSC/SGSN point-code if the user does not configure any address. */
uint32_t default_remote_pc;
struct hnbgw_cnpool_cfg vty;
struct hnbgw_cnpool_cfg use;
/* List of struct hnbgw_cnlink */
struct llist_head cnlinks;
unsigned int round_robin_next_nr;
/* Emergency calls potentially select a different set of MSCs, so to not mess up the normal round-robin
* behavior, emergency calls need a separate round-robin counter. */
unsigned int round_robin_next_emerg_nr;
/* rate counter group that child hnbgw_cnlinks should use (points to msc_ctrg_desc or sgsn_ctrg_desc) */
const struct rate_ctr_group_desc *cnlink_ctrg_desc;
/* Running counters for this pool */
struct rate_ctr_group *ctrs;
};
#define CNPOOL_CTR_INC(cnpool, x) rate_ctr_inc2((cnpool)->ctrs, x)
/* A CN peer, like 'msc 0' or 'sgsn 23' */
struct hnbgw_cnlink {
struct llist_head entry;
/* backpointer to CS or PS CN pool. */
struct hnbgw_cnpool *pool;
struct osmo_fsm_inst *fi;
int nr;
struct hnbgw_cnlink_cfg vty;
struct hnbgw_cnlink_cfg use;
/* To print in logging/VTY */
char *name;
/* Copy of the address book entry use.remote_addr_name. */
struct osmo_sccp_addr remote_addr;
/* The SCCP instance for the cs7 instance indicated by remote_addr_name. (Multiple hnbgw_cnlinks may use the
* same hnbgw_sccp_user -- there is exactly one hnbgw_sccp_user per configured cs7 instance.) */
struct hnbgw_sccp_user *hnbgw_sccp_user;
/* linked list of hnbgw_context_map */
struct llist_head map_list;
bool allow_attach;
bool allow_emerg;
struct llist_head paging;
struct rate_ctr_group *ctrs;
};
#define LOG_CNLINK(CNLINK, SUBSYS, LEVEL, FMT, ARGS...) \
LOGP(SUBSYS, LEVEL, "(%s) " FMT, (CNLINK) ? (CNLINK)->name : "null", ##ARGS)
#define CNLINK_CTR_INC(cnlink, x) rate_ctr_inc2((cnlink)->ctrs, x)
struct hnbgw_cnlink *cnlink_get_nr(struct hnbgw_cnpool *cnpool, int nr, bool create_if_missing);
static inline bool cnlink_is_cs(const struct hnbgw_cnlink *cnlink) static inline bool cnlink_is_cs(const struct hnbgw_cnlink *cnlink)
{ {
return cnlink && cnlink->pool->domain == DOMAIN_CS; return cnlink && cnlink->pool->domain == DOMAIN_CS;
@@ -83,6 +301,88 @@ static inline bool cnlink_is_ps(const struct hnbgw_cnlink *cnlink)
return cnlink && cnlink->pool->domain == DOMAIN_PS; return cnlink && cnlink->pool->domain == DOMAIN_PS;
} }
static inline struct osmo_sccp_instance *cnlink_sccp(const struct hnbgw_cnlink *cnlink)
{
if (!cnlink)
return NULL;
if (!cnlink->hnbgw_sccp_user)
return NULL;
if (!cnlink->hnbgw_sccp_user->ss7)
return NULL;
return cnlink->hnbgw_sccp_user->ss7->sccp;
}
/* The lifecycle of the hnb_context object is the same as its conn */
struct hnb_context {
/*! Entry in HNB-global list of HNB */
struct llist_head list;
/*! SCTP socket + write queue for Iuh to this specific HNB */
struct osmo_stream_srv *conn;
/*! copied from HNB-Identity-Info IE */
char identity_info[256];
/*! copied from Cell Identity IE */
struct umts_cell_id id;
/*! SCTP stream ID for HNBAP */
uint16_t hnbap_stream;
/*! SCTP stream ID for RUA */
uint16_t rua_stream;
/*! True if a HNB-REGISTER-REQ from this HNB has been accepted. */
bool hnb_registered;
/* linked list of hnbgw_context_map */
struct llist_head map_list;
/*! pointer to the associated hnb persistent state. Always present after HNB-Register */
struct hnb_persistent *persistent;
};
#define HNBP_CTR(hnbp, x) rate_ctr_group_get_ctr((hnbp)->ctrs, x)
#define HNBP_CTR_INC(hnbp, x) rate_ctr_inc(HNBP_CTR(hnbp, x))
#define HNBP_CTR_ADD(hnbp, x, y) rate_ctr_add2((hnbp)->ctrs, x, y)
#define HNBP_STAT(hbp, x) osmo_stat_item_group_get_item((hnbp)->statg, x)
#define HNBP_STAT_SET(hnbp, x, val) osmo_stat_item_set(HNBP_STAT(hnbp, x), val)
/* persistent data for one HNB. This continues to exist even as conn / hnb_context is deleted on disconnect */
struct hnb_persistent {
/*! Entry in HNBGW-global list of hnb_persistent */
struct llist_head list;
/*! back-pointer to hnb_context. Can be NULL if no context at this point */
struct hnb_context *ctx;
/*! unique cell identity; copied from HNB REGISTER REQ */
struct umts_cell_id id;
/*! stringified version of the cell identiy above (for printing/naming) */
const char *id_str;
/*! copied from HNB-Identity-Info IE */
time_t updowntime;
struct rate_ctr_group *ctrs;
struct osmo_stat_item_group *statg;
struct {
struct osmo_sockaddr_str addr_remote;
struct {
struct nft_kpi_val ul;
struct nft_kpi_val dl;
} last;
} nft_kpi;
};
struct ue_context {
/*! Entry in the HNB-global list of UE */
struct llist_head list;
/*! Unique Context ID for this UE */
uint32_t context_id;
char imsi[16+1];
uint32_t tmsi;
/*! UE is serviced via this HNB */
struct hnb_context *hnb;
};
struct hnbgw { struct hnbgw {
struct { struct {
const char *iuh_local_ip; const char *iuh_local_ip;
@@ -108,24 +408,16 @@ struct hnbgw {
char *core; char *core;
} netinst; } netinst;
} pfcp; } pfcp;
struct {
bool enable;
/* The table name as used in nftables for the ruleset owned by this process. It is "osmo-hnbgw"
* by default. */
char *table_name;
} nft_kpi;
} config; } config;
/*! SCTP listen socket for incoming connections */ /*! SCTP listen socket for incoming connections */
struct osmo_stream_srv_link *iuh; struct osmo_stream_srv_link *iuh;
/* list of struct hnb_context */ /* list of struct hnb_context */
struct llist_head hnb_list; struct llist_head hnb_list;
/* list of struct hnb_persistent */ /* list of struct hnb_persistent */
struct llist_head hnb_persistent_list; struct llist_head hnb_persistent_list;
/* optimized lookup for hnb_persistent, by cell id string */
DECLARE_HASHTABLE(hnb_persistent_by_id, 5);
struct osmo_timer_list store_uptime_timer; struct osmo_timer_list store_uptime_timer;
/* list of struct ue_context */
struct llist_head ue_list;
/* next availble UE Context ID */ /* next availble UE Context ID */
uint32_t next_ue_ctx_id; uint32_t next_ue_ctx_id;
struct ctrl_handle *ctrl; struct ctrl_handle *ctrl;
@@ -135,9 +427,9 @@ struct hnbgw {
struct llist_head users; struct llist_head users;
/* Pool of core network peers: MSCs for IuCS */ /* Pool of core network peers: MSCs for IuCS */
struct hnbgw_cnpool *cnpool_iucs; struct hnbgw_cnpool cnpool_iucs;
/* Pool of core network peers: SGSNs for IuPS */ /* Pool of core network peers: SGSNs for IuPS */
struct hnbgw_cnpool *cnpool_iups; struct hnbgw_cnpool cnpool_iups;
} sccp; } sccp;
/* MGW pool, also includes the single MGCP client as fallback if no /* MGW pool, also includes the single MGCP client as fallback if no
* pool is configured. */ * pool is configured. */
@@ -146,17 +438,9 @@ struct hnbgw {
struct { struct {
struct osmo_pfcp_endpoint *ep; struct osmo_pfcp_endpoint *ep;
struct osmo_pfcp_cp_peer *cp_peer; struct osmo_pfcp_cp_peer *cp_peer;
/* Running counters for the PFCP conn */
struct osmo_stat_item_group *statg;
} pfcp; } pfcp;
struct osmo_timer_list hnb_store_rab_durations_timer; struct osmo_timer_list hnb_store_rab_durations_timer;
struct {
bool active;
struct osmo_timer_list get_counters_timer;
struct timespec next_timer;
} nft_kpi;
}; };
extern struct hnbgw *g_hnbgw; extern struct hnbgw *g_hnbgw;
@@ -165,8 +449,29 @@ extern void *talloc_asn1_ctx;
void g_hnbgw_alloc(void *ctx); void g_hnbgw_alloc(void *ctx);
int hnbgw_rua_accept_cb(struct osmo_stream_srv_link *srv, int fd); int hnbgw_rua_accept_cb(struct osmo_stream_srv_link *srv, int fd);
int hnb_ctrl_cmds_install(void);
int hnb_ctrl_node_lookup(void *data, vector vline, int *node_type, void **node_data, int *i);
int hnbgw_mgw_setup(void); int hnbgw_mgw_setup(void);
struct hnb_context *hnb_context_by_identity_info(const char *identity_info);
const char *hnb_context_name(struct hnb_context *ctx);
struct ue_context *ue_context_by_id(uint32_t id);
struct ue_context *ue_context_by_imsi(const char *imsi);
struct ue_context *ue_context_by_tmsi(uint32_t tmsi);
struct ue_context *ue_context_alloc(struct hnb_context *hnb, const char *imsi,
uint32_t tmsi);
void ue_context_free(struct ue_context *ue);
void hnb_context_release(struct hnb_context *ctx);
void hnb_context_release_ue_state(struct hnb_context *ctx);
struct hnb_persistent *hnb_persistent_alloc(const struct umts_cell_id *id);
struct hnb_persistent *hnb_persistent_find_by_id(const struct umts_cell_id *id);
struct hnb_persistent *hnb_persistent_find_by_id_str(const char *id_str);
void hnb_persistent_update_addr(struct hnb_persistent *hnbp, int new_fd);
void hnb_persistent_free(struct hnb_persistent *hnbp);
void hnbgw_vty_init(void); void hnbgw_vty_init(void);
int hnbgw_vty_go_parent(struct vty *vty); int hnbgw_vty_go_parent(struct vty *vty);
@@ -184,4 +489,4 @@ struct msgb *hnbgw_ranap_msg_alloc(const char *name);
int hnbgw_peek_l3_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg); int hnbgw_peek_l3_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg);
uint32_t get_next_ue_ctx_id(void); unsigned long long hnb_get_updowntime(const struct hnb_context *ctx);

View File

@@ -1,13 +1,25 @@
#pragma once #pragma once
#include <stdint.h>
#include <osmocom/core/rate_ctr.h> #include <osmocom/core/rate_ctr.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/sigtran/sccp_sap.h> struct hnbgw_cnlink *cnlink_alloc(struct hnbgw_cnpool *cnpool, int nr);
struct hnbgw_cnlink *hnbgw_cnlink_find_by_addr(const struct hnbgw_sccp_user *hsu,
const struct osmo_sccp_addr *remote_addr);
struct hnbgw_cnlink *hnbgw_cnlink_select(struct hnbgw_context_map *map);
#include <osmocom/ranap/RANAP_CN-DomainIndicator.h> void hnbgw_cnpool_start(struct hnbgw_cnpool *cnpool);
void hnbgw_cnpool_apply_cfg(struct hnbgw_cnpool *cnpool);
void hnbgw_cnpool_cnlinks_start_or_restart(struct hnbgw_cnpool *cnpool);
int hnbgw_cnlink_start_or_restart(struct hnbgw_cnlink *cnlink);
struct hnbgw_context_map; char *cnlink_sccp_addr_to_str(struct hnbgw_cnlink *cnlink, const struct osmo_sccp_addr *addr);
bool cnlink_is_conn_ready(const struct hnbgw_cnlink *cnlink);
void cnlink_rx_reset_cmd(struct hnbgw_cnlink *cnlink);
void cnlink_rx_reset_ack(struct hnbgw_cnlink *cnlink);
void cnlink_resend_reset(struct hnbgw_cnlink *cnlink);
void cnlink_set_disconnected(struct hnbgw_cnlink *cnlink);
enum hnbgw_cnpool_ctr { enum hnbgw_cnpool_ctr {
/* TODO: basic counters completely missing /* TODO: basic counters completely missing
@@ -19,46 +31,33 @@ enum hnbgw_cnpool_ctr {
CNPOOL_CTR_EMERG_FORWARDED, CNPOOL_CTR_EMERG_FORWARDED,
CNPOOL_CTR_EMERG_LOST, CNPOOL_CTR_EMERG_LOST,
}; };
#define CNPOOL_CTR_INC(cnpool, x) rate_ctr_inc2((cnpool)->ctrs, x)
/* User provided configuration for struct hnbgw_cnpool. */ extern const struct rate_ctr_group_desc iucs_ctrg_desc;
struct hnbgw_cnpool_cfg { extern const struct rate_ctr_group_desc iups_ctrg_desc;
uint8_t nri_bitlen;
struct osmo_nri_ranges *null_nri_ranges; enum hnbgw_cnlink_ctr {
/* TODO: basic counters completely missing
* ...
*/
CNLINK_CTR_RANAP_RX_UDT_RESET,
CNLINK_CTR_RANAP_RX_UDT_RESET_ACK,
CNLINK_CTR_RANAP_RX_UDT_PAGING,
CNLINK_CTR_RANAP_RX_UDT_UNKNOWN,
CNLINK_CTR_RANAP_RX_UDT_UNSUPPORTED,
CNLINK_CTR_RANAP_RX_UDT_OVERLOAD_IND,
CNLINK_CTR_RANAP_RX_UDT_ERROR_IND,
CNLINK_CTR_RANAP_TX_UDT_RESET,
CNLINK_CTR_RANAP_TX_UDT_RESET_ACK,
/* Counters related to link selection from a CN pool. */
CNLINK_CTR_CNPOOL_SUBSCR_NEW,
CNLINK_CTR_CNPOOL_SUBSCR_REATTACH,
CNLINK_CTR_CNPOOL_SUBSCR_KNOWN,
CNLINK_CTR_CNPOOL_SUBSCR_PAGED,
CNLINK_CTR_CNPOOL_SUBSCR_ATTACH_LOST,
CNLINK_CTR_CNPOOL_EMERG_FORWARDED,
}; };
/* Collection of CN peers to distribute UE connections across. MSCs for DOMAIN_CS, SGSNs for DOMAIN_PS. */ extern const struct rate_ctr_group_desc msc_ctrg_desc;
struct hnbgw_cnpool { extern const struct rate_ctr_group_desc sgsn_ctrg_desc;
RANAP_CN_DomainIndicator_t domain;
/* CN pool string used in VTY config and logging, "iucs" or "iups". */
const char *pool_name;
/* CN peer string used in VTY config and logging, "msc" or "sgsn". */
const char *peer_name;
/* What we use as the remote MSC/SGSN point-code if the user does not configure any address. */
uint32_t default_remote_pc;
const char *default_addr_name;
struct hnbgw_cnpool_cfg vty;
struct hnbgw_cnpool_cfg use;
/* List of struct hnbgw_cnlink */
struct llist_head cnlinks;
unsigned int round_robin_next_nr;
/* Emergency calls potentially select a different set of MSCs, so to not mess up the normal round-robin
* behavior, emergency calls need a separate round-robin counter. */
unsigned int round_robin_next_emerg_nr;
/* Running counters for this pool */
struct rate_ctr_group *ctrs;
};
struct hnbgw_cnpool *hnbgw_cnpool_alloc(RANAP_CN_DomainIndicator_t domain);
struct hnbgw_cnlink *hnbgw_cnlink_select(struct hnbgw_context_map *map);
void hnbgw_cnpool_start(struct hnbgw_cnpool *cnpool);
void hnbgw_cnpool_cnlinks_start_or_restart(struct hnbgw_cnpool *cnpool);
struct hnbgw_cnlink *cnlink_get_nr(struct hnbgw_cnpool *cnpool, int nr, bool create_if_missing);
void hnbgw_cnpool_apply_cfg(struct hnbgw_cnpool *cnpool);

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,8 @@
/* RUA, 3GPP TS 25.468 */
#pragma once #pragma once
#include <osmocom/hnbgw/hnbgw.h> #include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/rua/RUA_Cause.h> #include <osmocom/rua/RUA_Cause.h>
#include <osmocom/ranap/ranap_ies_defs.h>
struct hnb_context;
int hnbgw_rua_rx(struct hnb_context *hnb, struct msgb *msg); int hnbgw_rua_rx(struct hnb_context *hnb, struct msgb *msg);
@@ -14,3 +12,4 @@ int rua_tx_dt(struct hnb_context *hnb, int is_ps, uint32_t context_id,
int rua_tx_disc(struct hnb_context *hnb, int is_ps, uint32_t context_id, 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); const RUA_Cause_t *cause, const uint8_t *data, unsigned int len);
ranap_message *hnbgw_decode_ranap_co(struct msgb *ranap_msg);

View File

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

View File

@@ -6,6 +6,3 @@
void kpi_ranap_process_ul(struct hnbgw_context_map *map, ranap_message *ranap); void kpi_ranap_process_ul(struct hnbgw_context_map *map, ranap_message *ranap);
void kpi_ranap_process_dl(struct hnbgw_context_map *map, ranap_message *ranap); void kpi_ranap_process_dl(struct hnbgw_context_map *map, ranap_message *ranap);
void kpi_dtap_process_ul(struct hnbgw_context_map *map, const uint8_t *buf, unsigned int len, uint8_t sapi);
void kpi_dtap_process_dl(struct hnbgw_context_map *map, const uint8_t *buf, unsigned int len, uint8_t sapi);

View File

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

View File

@@ -6,7 +6,6 @@ enum ps_rab_ass_fsm_event {
PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX, PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX,
PS_RAB_ASS_EV_RAB_ASS_RESP, PS_RAB_ASS_EV_RAB_ASS_RESP,
PS_RAB_ASS_EV_RAB_ESTABLISHED, PS_RAB_ASS_EV_RAB_ESTABLISHED,
PS_RAB_ASS_EV_RAB_RELEASED,
PS_RAB_ASS_EV_RAB_FAIL, PS_RAB_ASS_EV_RAB_FAIL,
}; };

View File

@@ -85,7 +85,7 @@ struct ps_rab *ps_rab_start(struct hnbgw_context_map *map, uint8_t rab_id,
struct ps_rab *ps_rab_get(struct hnbgw_context_map *map, uint8_t rab_id); 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); bool ps_rab_is_established(const struct ps_rab *rab);
void ps_rab_release(struct ps_rab *rab, struct osmo_fsm_inst *notify_fi); void ps_rab_release(struct ps_rab *rab);
struct ps_rab_rx_args { struct ps_rab_rx_args {
struct addr_teid f_teid; struct addr_teid f_teid;

View File

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

View File

@@ -32,28 +32,21 @@ noinst_LTLIBRARIES = \
$(NULL) $(NULL)
libhnbgw_la_SOURCES = \ libhnbgw_la_SOURCES = \
hnb.c \
hnb_persistent.c \
hnbgw.c \ hnbgw.c \
hnbgw_hnbap.c \ hnbgw_hnbap.c \
hnbgw_l3.c \ hnbgw_l3.c \
hnbgw_rua.c \ hnbgw_rua.c \
hnbgw_ranap.c \ hnbgw_ranap.c \
hnbgw_sccp.c \
hnbgw_vty.c \ hnbgw_vty.c \
context_map.c \ context_map.c \
context_map_rua.c \ context_map_rua.c \
context_map_sccp.c \ context_map_sccp.c \
hnbgw_cn.c \ hnbgw_cn.c \
cnlink.c \ cnlink.c \
cnlink_fsm.c \
cnlink_paging.c \
ranap_rab_ass.c \ ranap_rab_ass.c \
mgw_fsm.c \ mgw_fsm.c \
kpi_dtap.c \
kpi_ranap.c \ kpi_ranap.c \
tdefs.c \ tdefs.c \
umts_cell_id.c \
nft_kpi.c \ nft_kpi.c \
$(NULL) $(NULL)

View File

@@ -20,8 +20,6 @@
#include <osmocom/core/fsm.h> #include <osmocom/core/fsm.h>
#include <osmocom/core/tdef.h> #include <osmocom/core/tdef.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/stat_item.h>
#include <osmocom/gsm/gsm23236.h> #include <osmocom/gsm/gsm23236.h>
@@ -36,255 +34,94 @@
#include <osmocom/hnbgw/tdefs.h> #include <osmocom/hnbgw/tdefs.h>
#include <osmocom/hnbgw/context_map.h> #include <osmocom/hnbgw/context_map.h>
static const struct rate_ctr_desc cnlink_ctr_description[] = { static struct osmo_fsm cnlink_fsm;
[CNLINK_CTR_RANAP_RX_UDT_RESET] = {
"ranap:rx:udt:reset",
"RANAP Unitdata RESET messages received"
},
[CNLINK_CTR_RANAP_RX_UDT_RESET_ACK] = {
"ranap:rx:udt:reset_ack",
"RANAP Unitdata RESET ACK messages received",
},
[CNLINK_CTR_RANAP_RX_UDT_PAGING] = {
"ranap:rx:udt:paging",
"RANAP Unitdata PAGING messages received",
},
[CNLINK_CTR_RANAP_RX_UDT_UNKNOWN] = {
"ranap:rx:udt:unknown",
"Unknown RANAP Unitdata messages received",
},
[CNLINK_CTR_RANAP_RX_UDT_UNSUPPORTED] = {
"ranap:rx:udt:unsupported",
"Unsupported RANAP Unitdata messages received",
},
[CNLINK_CTR_RANAP_RX_UDT_OVERLOAD_IND] = {
"ranap:rx:udt:overload_ind",
"RANAP Unitdata Overload Indications received",
},
[CNLINK_CTR_RANAP_RX_UDT_ERROR_IND] = {
"ranap:rx:udt:error_ind",
"RANAP Unitdata Error Indications received",
},
[CNLINK_CTR_RANAP_TX_UDT_RESET] = { enum cnlink_fsm_state {
"ranap:tx:udt:reset", CNLINK_ST_DISC,
"RANAP Unitdata RESET messages transmitted", CNLINK_ST_CONN,
},
[CNLINK_CTR_RANAP_TX_UDT_RESET_ACK] = {
"ranap:tx:udt:reset_ack",
"RANAP Unitdata RESET ACK messages transmitted",
},
/* SCCP Counters: */
[CNLINK_CTR_SCCP_N_UNITDATA_REQ] = {
"sccp:n_unit_data:req",
"Submit SCCP N-UNITDATA.req (UL)"
},
[CNLINK_CTR_SCCP_N_UNITDATA_IND] = {
"sccp:n_unit_data:ind",
"Received SCCP N-UNITDATA.ind (DL)"
},
[CNLINK_CTR_SCCP_N_NOTICE_IND] = {
"sccp:n_notice:ind",
"Received SCCP N-NOTICE.ind"
},
[CNLINK_CTR_SCCP_N_CONNECT_REQ] = {
"sccp:n_connect:req",
"Submit SCCP N-CONNECT.req (UL SCCP CR)"
},
[CNLINK_CTR_SCCP_N_CONNECT_CNF] = {
"sccp:n_connect:cnf",
"Received SCCP N-CONNECT.cnf (DL SCCP CC)"
},
[CNLINK_CTR_SCCP_N_DATA_REQ] = {
"sccp:n_data:req",
"SUBMIT SCCP N-DATA.req (UL)"
},
[CNLINK_CTR_SCCP_N_DATA_IND] = {
"sccp:n_data:ind",
"Received SCCP N-DATA.ind (DL)"
},
[CNLINK_CTR_SCCP_N_DISCONNECT_REQ] = {
"sccp:n_disconnect:req",
"Submit SCCP N-DISCONNECT.req (UL SCCP RLC)"
},
[CNLINK_CTR_SCCP_N_DISCONNECT_IND] = {
"sccp:n_disconnect:ind",
"Received SCCP N-DISCONNECT.ind (DL SCCP RLSD)"
},
[CNLINK_CTR_SCCP_N_PCSTATE_IND] = {
"sccp:n_pcstate:ind",
"Received SCCP N-PCSTATE.ind"
},
[CNLINK_CTR_SCCP_RLSD_CN_ORIGIN] = {
"sccp:rlsd_cn_origin",
"Received unexpected SCCP RSLD originated unilaterally by CN"
},
/* Indicators for CN pool usage */
[CNLINK_CTR_CNPOOL_SUBSCR_NEW] = {
"cnpool:subscr:new",
"Complete Layer 3 requests assigned to this CN link by round-robin (no NRI was assigned yet).",
},
[CNLINK_CTR_CNPOOL_SUBSCR_REATTACH] = {
"cnpool:subscr:reattach",
"Complete Layer 3 requests assigned to this CN link by round-robin because the subscriber indicates a"
" NULL-NRI (previously assigned by another CN link).",
},
[CNLINK_CTR_CNPOOL_SUBSCR_KNOWN] = {
"cnpool:subscr:known",
"Complete Layer 3 requests directed to this CN link because the subscriber indicates an NRI of this CN link.",
},
[CNLINK_CTR_CNPOOL_SUBSCR_PAGED] = {
"cnpool:subscr:paged",
"Paging Response directed to this CN link because the subscriber was recently paged by this CN link.",
},
[CNLINK_CTR_CNPOOL_SUBSCR_ATTACH_LOST] = {
"cnpool:subscr:attach_lost",
"A subscriber indicates an NRI value matching this CN link, but the CN link is not connected:"
" a re-attach to another CN link (if available) was forced, with possible service failure.",
},
[CNLINK_CTR_CNPOOL_EMERG_FORWARDED] = {
"cnpool:emerg:forwarded",
"Emergency call requests forwarded to this CN link.",
},
}; };
static const struct rate_ctr_group_desc msc_ctrg_desc = { enum cnlink_fsm_event {
"msc", CNLINK_EV_RX_RESET,
"MSC", CNLINK_EV_RX_RESET_ACK,
OSMO_STATS_CLASS_GLOBAL,
ARRAY_SIZE(cnlink_ctr_description),
cnlink_ctr_description,
}; };
static const struct rate_ctr_group_desc sgsn_ctrg_desc = { static const struct value_string cnlink_fsm_event_names[] = {
"sgsn", OSMO_VALUE_STRING(CNLINK_EV_RX_RESET),
"SGSN", OSMO_VALUE_STRING(CNLINK_EV_RX_RESET_ACK),
OSMO_STATS_CLASS_GLOBAL, {}
ARRAY_SIZE(cnlink_ctr_description),
cnlink_ctr_description,
}; };
static const struct osmo_stat_item_desc cnlink_stat_desc[] = { static const struct osmo_tdef_state_timeout cnlink_timeouts[32] = {
[CNLINK_STAT_CONNECTED] = { "connected", "Connected (1) or disconnected (0)", NULL, 60, 0 }, [CNLINK_ST_DISC] = { .T = 4 },
}; };
const struct osmo_stat_item_group_desc msc_statg_desc = { #define cnlink_fsm_state_chg(FI, STATE) \
.group_name_prefix = "msc", osmo_tdef_fsm_inst_state_chg(FI, STATE, \
.group_description = "MSC", cnlink_timeouts, \
.class_id = OSMO_STATS_CLASS_GLOBAL, hnbgw_T_defs, \
.num_items = ARRAY_SIZE(cnlink_stat_desc), -1)
.item_desc = cnlink_stat_desc,
};
const struct osmo_stat_item_group_desc sgsn_statg_desc = { struct hnbgw_cnlink *cnlink_alloc(struct hnbgw_cnpool *cnpool, int nr)
.group_name_prefix = "sgsn",
.group_description = "SGSN",
.class_id = OSMO_STATS_CLASS_GLOBAL,
.num_items = ARRAY_SIZE(cnlink_stat_desc),
.item_desc = cnlink_stat_desc,
};
struct hnbgw_cnlink *hnbgw_cnlink_alloc(struct hnbgw_cnpool *cnpool, int nr)
{ {
struct osmo_fsm_inst *fi;
struct hnbgw_cnlink *cnlink; struct hnbgw_cnlink *cnlink;
const struct rate_ctr_group_desc *ctrg_desc;
const struct osmo_stat_item_group_desc *statg_desc;
OSMO_ASSERT(cnpool); char *name = talloc_asprintf(OTC_SELECT, "%s-%d", cnpool->peer_name, nr);
switch (cnpool->domain) { fi = osmo_fsm_inst_alloc(&cnlink_fsm, g_hnbgw, NULL, LOGL_DEBUG, name);
case DOMAIN_CS: OSMO_ASSERT(fi);
ctrg_desc = &msc_ctrg_desc; cnlink = talloc_zero(g_hnbgw, struct hnbgw_cnlink);
statg_desc = &msc_statg_desc; fi->priv = cnlink;
break;
case DOMAIN_PS:
ctrg_desc = &sgsn_ctrg_desc;
statg_desc = &sgsn_statg_desc;
break;
default:
OSMO_ASSERT(0);
}
cnlink = talloc_zero(cnpool, struct hnbgw_cnlink);
OSMO_ASSERT(cnlink);
*cnlink = (struct hnbgw_cnlink){ *cnlink = (struct hnbgw_cnlink){
.name = name,
.pool = cnpool, .pool = cnpool,
.fi = fi,
.nr = nr, .nr = nr,
.vty = { .vty = {
/* VTY config defaults for the new cnlink */ /* VTY config defaults for the new cnlink */
.nri_ranges = osmo_nri_ranges_alloc(cnlink), .nri_ranges = osmo_nri_ranges_alloc(cnlink),
}, },
.allow_attach = true, .allow_attach = true,
.ctrs = rate_ctr_group_alloc(cnlink, ctrg_desc, nr), .ctrs = rate_ctr_group_alloc(g_hnbgw, cnpool->cnlink_ctrg_desc, nr),
.statg = osmo_stat_item_group_alloc(cnlink, statg_desc, nr),
}; };
cnlink->name = talloc_asprintf(cnlink, "%s-%d", cnpool->peer_name, nr); talloc_steal(cnlink, name);
INIT_LLIST_HEAD(&cnlink->map_list); INIT_LLIST_HEAD(&cnlink->map_list);
INIT_LLIST_HEAD(&cnlink->paging); INIT_LLIST_HEAD(&cnlink->paging);
cnlink->fi = osmo_fsm_inst_alloc(&cnlink_fsm, cnlink, cnlink, LOGL_DEBUG, cnlink->name);
OSMO_ASSERT(cnlink->fi);
llist_add_tail(&cnlink->entry, &cnpool->cnlinks); llist_add_tail(&cnlink->entry, &cnpool->cnlinks);
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "allocated\n"); LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "allocated\n");
cnlink_resend_reset(cnlink); /* Immediately (1ms) kick off reset sending mechanism */
osmo_fsm_inst_state_chg_ms(fi, CNLINK_ST_DISC, 1, 0);
return cnlink; return cnlink;
} }
int hnbgw_cnlink_set_name(struct hnbgw_cnlink *cnlink, const char *name) void cnlink_term_and_free(struct hnbgw_cnlink *cnlink)
{
talloc_free(cnlink->name);
cnlink->name = talloc_strdup(cnlink, name);
osmo_fsm_inst_update_id_f_sanitize(cnlink->fi, '-', cnlink->name);
/* Update rate_ctr/stats to report by name instead of index: */
rate_ctr_group_set_name(cnlink->ctrs, cnlink->name);
osmo_stat_item_group_set_name(cnlink->statg, cnlink->name);
return 0;
}
void hnbgw_cnlink_drop_sccp(struct hnbgw_cnlink *cnlink)
{
struct hnbgw_context_map *map, *map2;
struct hnbgw_sccp_user *hsu;
llist_for_each_entry_safe(map, map2, &cnlink->map_list, hnbgw_cnlink_entry) {
map_sccp_dispatch(map, MAP_SCCP_EV_USER_ABORT, NULL);
}
OSMO_ASSERT(cnlink->hnbgw_sccp_user);
hsu = cnlink->hnbgw_sccp_user;
cnlink->hnbgw_sccp_user = NULL;
hnbgw_sccp_user_put(hsu, HSU_USE_CNLINK);
}
void hnbgw_cnlink_term_and_free(struct hnbgw_cnlink *cnlink)
{ {
if (!cnlink) if (!cnlink)
return; return;
if (cnlink->hnbgw_sccp_user)
hnbgw_cnlink_drop_sccp(cnlink);
osmo_fsm_inst_term(cnlink->fi, OSMO_FSM_TERM_REQUEST, NULL); osmo_fsm_inst_term(cnlink->fi, OSMO_FSM_TERM_REQUEST, NULL);
cnlink->fi = NULL;
osmo_stat_item_group_free(cnlink->statg);
rate_ctr_group_free(cnlink->ctrs);
llist_del(&cnlink->entry);
talloc_free(cnlink); talloc_free(cnlink);
} }
static int hnbgw_cnlink_tx_sccp_unitdata_req(struct hnbgw_cnlink *cnlink, struct msgb *msg) static void link_up(struct hnbgw_cnlink *cnlink)
{ {
CNLINK_CTR_INC(cnlink, CNLINK_CTR_SCCP_N_UNITDATA_REQ); LOGPFSML(cnlink->fi, LOGL_NOTICE, "link up\n");
return hnbgw_sccp_user_tx_unitdata_req(cnlink->hnbgw_sccp_user,
&cnlink->remote_addr,
msg);
} }
int hnbgw_cnlink_tx_ranap_reset(struct hnbgw_cnlink *cnlink) static void link_lost(struct hnbgw_cnlink *cnlink)
{
struct hnbgw_context_map *map, *map2;
LOGPFSML(cnlink->fi, LOGL_NOTICE, "link lost\n");
llist_for_each_entry_safe(map, map2, &cnlink->map_list, hnbgw_cnlink_entry)
context_map_cnlink_lost(map);
}
static void tx_reset(struct hnbgw_cnlink *cnlink)
{ {
struct msgb *msg; struct msgb *msg;
RANAP_Cause_t cause = { RANAP_Cause_t cause = {
@@ -296,7 +133,7 @@ int hnbgw_cnlink_tx_ranap_reset(struct hnbgw_cnlink *cnlink)
uint8_t plmn_buf[3]; uint8_t plmn_buf[3];
if (!cnlink) if (!cnlink)
return -1; return;
/* We need to have chosen an SCCP instance, and the remote SCCP address needs to be set. /* We need to have chosen an SCCP instance, and the remote SCCP address needs to be set.
* Only check the remote_addr, allowing use.remote_addr_name to be NULL: if the user has not set an explicit * Only check the remote_addr, allowing use.remote_addr_name to be NULL: if the user has not set an explicit
@@ -304,12 +141,12 @@ int hnbgw_cnlink_tx_ranap_reset(struct hnbgw_cnlink *cnlink)
if (!cnlink->hnbgw_sccp_user if (!cnlink->hnbgw_sccp_user
|| !osmo_sccp_check_addr(&cnlink->remote_addr, OSMO_SCCP_ADDR_T_PC | OSMO_SCCP_ADDR_T_SSN)) { || !osmo_sccp_check_addr(&cnlink->remote_addr, OSMO_SCCP_ADDR_T_PC | OSMO_SCCP_ADDR_T_SSN)) {
LOG_CNLINK(cnlink, DRANAP, LOGL_DEBUG, "not yet configured, not sending RANAP RESET\n"); LOG_CNLINK(cnlink, DRANAP, LOGL_DEBUG, "not yet configured, not sending RANAP RESET\n");
return -1; return;
} }
LOG_CNLINK(cnlink, DRANAP, LOGL_DEBUG, "Tx RANAP RESET to %s %s\n", LOG_CNLINK(cnlink, DRANAP, LOGL_DEBUG, "Tx RANAP RESET to %s %s\n",
cnlink_is_cs(cnlink) ? "IuCS" : "IuPS", cnlink_is_cs(cnlink) ? "IuCS" : "IuPS",
hnbgw_cnlink_sccp_addr_to_str(cnlink, &cnlink->remote_addr)); cnlink_sccp_addr_to_str(cnlink, &cnlink->remote_addr));
if (g_hnbgw->config.plmn.mcc) { if (g_hnbgw->config.plmn.mcc) {
osmo_plmn_to_bcd(plmn_buf, &g_hnbgw->config.plmn); osmo_plmn_to_bcd(plmn_buf, &g_hnbgw->config.plmn);
@@ -338,26 +175,29 @@ int hnbgw_cnlink_tx_ranap_reset(struct hnbgw_cnlink *cnlink)
msg = ranap_new_msg_reset2(cnlink->pool->domain, &cause, use_grnc_id); msg = ranap_new_msg_reset2(cnlink->pool->domain, &cause, use_grnc_id);
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_TX_UDT_RESET); CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_TX_UDT_RESET);
return hnbgw_cnlink_tx_sccp_unitdata_req(cnlink, msg); osmo_sccp_tx_unitdata_msg(cnlink->hnbgw_sccp_user->sccp_user,
&cnlink->hnbgw_sccp_user->local_addr,
&cnlink->remote_addr,
msg);
} }
int hnbgw_cnlink_tx_ranap_reset_ack(struct hnbgw_cnlink *cnlink) static void tx_reset_ack(struct hnbgw_cnlink *cnlink)
{ {
struct msgb *msg; struct msgb *msg;
struct osmo_sccp_instance *sccp = hnbgw_cnlink_sccp(cnlink); struct osmo_sccp_instance *sccp = cnlink_sccp(cnlink);
RANAP_GlobalRNC_ID_t grnc_id; RANAP_GlobalRNC_ID_t grnc_id;
RANAP_GlobalRNC_ID_t *use_grnc_id = NULL; RANAP_GlobalRNC_ID_t *use_grnc_id = NULL;
uint8_t plmn_buf[3]; uint8_t plmn_buf[3];
if (!sccp) { if (!sccp) {
LOG_CNLINK(cnlink, DRANAP, LOGL_ERROR, "cannot send RANAP RESET ACK: no CN link\n"); LOG_CNLINK(cnlink, DRANAP, LOGL_ERROR, "cannot send RANAP RESET ACK: no CN link\n");
return -1; return;
} }
LOG_CNLINK(cnlink, DRANAP, LOGL_NOTICE, "Tx RANAP RESET ACK %s %s --> %s\n", LOG_CNLINK(cnlink, DRANAP, LOGL_NOTICE, "Tx RANAP RESET ACK %s %s --> %s\n",
cnlink_is_cs(cnlink) ? "IuCS" : "IuPS", cnlink_is_cs(cnlink) ? "IuCS" : "IuPS",
hnbgw_cnlink_sccp_addr_to_str(cnlink, &cnlink->hnbgw_sccp_user->local_addr), cnlink_sccp_addr_to_str(cnlink, &cnlink->hnbgw_sccp_user->local_addr),
hnbgw_cnlink_sccp_addr_to_str(cnlink, &cnlink->remote_addr)); cnlink_sccp_addr_to_str(cnlink, &cnlink->remote_addr));
if (g_hnbgw->config.plmn.mcc) { if (g_hnbgw->config.plmn.mcc) {
osmo_plmn_to_bcd(plmn_buf, &g_hnbgw->config.plmn); osmo_plmn_to_bcd(plmn_buf, &g_hnbgw->config.plmn);
@@ -386,169 +226,149 @@ int hnbgw_cnlink_tx_ranap_reset_ack(struct hnbgw_cnlink *cnlink)
msg = ranap_new_msg_reset_ack(cnlink->pool->domain, use_grnc_id); msg = ranap_new_msg_reset_ack(cnlink->pool->domain, use_grnc_id);
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_TX_UDT_RESET_ACK); CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_TX_UDT_RESET_ACK);
return hnbgw_cnlink_tx_sccp_unitdata_req(cnlink, msg); osmo_sccp_tx_unitdata_msg(cnlink->hnbgw_sccp_user->sccp_user,
&cnlink->hnbgw_sccp_user->local_addr,
&cnlink->remote_addr,
msg);
} }
/* Return address found in sccp address-book, and fill in missing fields in the static void cnlink_disc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
* entry with default values. */
static struct osmo_ss7_instance *sccp_addr_by_name_filled(struct osmo_sccp_addr *dest, const char *addr_name, uint32_t default_pc)
{ {
struct osmo_ss7_instance *s7i; struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
s7i = osmo_sccp_addr_by_name(dest, addr_name); if (prev_state == CNLINK_ST_CONN)
if (!s7i) link_lost(cnlink);
return NULL;
/* Address exists in address-book but may not be filled entirely: */
if (!dest->presence)
osmo_sccp_make_addr_pc_ssn(dest, default_pc, OSMO_SCCP_SSN_RANAP);
else if (!(dest->presence & OSMO_SCCP_ADDR_T_SSN))
osmo_sccp_addr_set_ssn(dest, OSMO_SCCP_SSN_RANAP);
return s7i;
} }
char *hnbgw_cnlink_sccp_addr_to_str(struct hnbgw_cnlink *cnlink, const struct osmo_sccp_addr *addr) static void cnlink_disc_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{ {
struct osmo_sccp_instance *sccp = hnbgw_cnlink_sccp(cnlink); struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
if (!sccp) switch (event) {
return osmo_sccp_addr_dump(addr);
return osmo_sccp_inst_addr_to_str_c(OTC_SELECT, sccp, addr);
}
static void hnbgw_cnlink_cfg_copy(struct hnbgw_cnlink *cnlink) case CNLINK_EV_RX_RESET:
{ tx_reset_ack(cnlink);
struct osmo_nri_range *r; cnlink_fsm_state_chg(fi, CNLINK_ST_CONN);
break;
osmo_talloc_replace_string(cnlink, &cnlink->use.remote_addr_name, cnlink->vty.remote_addr_name); case CNLINK_EV_RX_RESET_ACK:
cnlink_fsm_state_chg(fi, CNLINK_ST_CONN);
break;
osmo_nri_ranges_free(cnlink->use.nri_ranges); default:
cnlink->use.nri_ranges = osmo_nri_ranges_alloc(cnlink); OSMO_ASSERT(false);
llist_for_each_entry(r, &cnlink->vty.nri_ranges->entries, entry)
osmo_nri_ranges_add(cnlink->use.nri_ranges, r);
}
static bool hnbgw_cnlink_sccp_cfg_changed(struct hnbgw_cnlink *cnlink)
{
bool changed = false;
if (cnlink->vty.remote_addr_name && cnlink->use.remote_addr_name) {
struct osmo_ss7_instance *s7i;
struct osmo_sccp_addr remote_addr = {};
/* Instead of comparing whether the address book entry names are different, actually resolve the
* resulting SCCP address, and only restart the cnlink if the resulting address changed. */
s7i = sccp_addr_by_name_filled(&remote_addr, cnlink->vty.remote_addr_name,
cnlink->pool->default_remote_pc);
if (!s7i)
return true;
if (osmo_sccp_addr_cmp(&remote_addr, &cnlink->remote_addr,
OSMO_SCCP_ADDR_T_PC | OSMO_SCCP_ADDR_T_SSN))
changed = true;
} else if (cnlink->vty.remote_addr_name != cnlink->use.remote_addr_name) {
/* One of them is NULL, the other is not. */
changed = true;
} }
/* if more cnlink configuration is added in the future, it needs to be compared here. */
return changed;
} }
static void hnbgw_cnlink_log_self(struct hnbgw_cnlink *cnlink) static void cnlink_conn_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{ {
struct osmo_ss7_instance *ss7 = cnlink->hnbgw_sccp_user->ss7; struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, "using: cs7-%u %s <-> %s %s %s\n", if (prev_state != CNLINK_ST_CONN)
osmo_ss7_instance_get_id(ss7), link_up(cnlink);
/* printing the entire SCCP address is quite long, rather just print the point-code */
osmo_ss7_pointcode_print(ss7, cnlink->hnbgw_sccp_user->local_addr.pc),
osmo_ss7_pointcode_print2(ss7, cnlink->remote_addr.pc),
cnlink->name, cnlink->use.remote_addr_name ? : "(default remote point-code)");
} }
/* If not present yet, set up all of osmo_ss7_instance, osmo_sccp_instance and hnbgw_sccp_user for the given cnlink. static void cnlink_conn_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
* The cs7 instance nr to use is determined by cnlink->remote_addr_name, or cs7 instance 0 if that is not present.
* Set cnlink->hnbgw_sccp_user to the new SCCP instance. Return 0 on success, negative on error. */
int hnbgw_cnlink_start_or_restart(struct hnbgw_cnlink *cnlink)
{ {
struct osmo_ss7_instance *s7i = NULL; struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
struct hnbgw_sccp_user *hsu;
uint32_t ss7_id;
int rc;
/* If a hnbgw_sccp_user has already been set up, use that. */ switch (event) {
if (cnlink->hnbgw_sccp_user) {
if (!hnbgw_cnlink_sccp_cfg_changed(cnlink)) { case CNLINK_EV_RX_RESET:
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "SCCP instance already set up, using %s\n", /* We were connected, but the remote side has restarted. */
cnlink->hnbgw_sccp_user->name); link_lost(cnlink);
return 0; tx_reset_ack(cnlink);
} link_up(cnlink);
LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, "config changed, restarting SCCP\n"); break;
hnbgw_cnlink_drop_sccp(cnlink);
} else { case CNLINK_EV_RX_RESET_ACK:
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "no SCCP instance selected yet\n"); LOGPFSML(fi, LOGL_INFO, "Link is already up, ignoring RESET ACK\n");
break;
default:
OSMO_ASSERT(false);
} }
}
/* Copy the current configuration: cnlink->use = cnlink->vty */ static int cnlink_fsm_timer_cb(struct osmo_fsm_inst *fi)
hnbgw_cnlink_cfg_copy(cnlink); {
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
if (!cnlink->use.remote_addr_name) { tx_reset(cnlink);
/* No remote address configured in VTY, set a default one and
* make sure it becomes registered in the sccp address-book: */
cnlink->use.remote_addr_name = talloc_strdup(cnlink, cnlink->pool->default_addr_name);
s7i = osmo_sccp_addr_by_name(&cnlink->remote_addr, cnlink->use.remote_addr_name);
if (!s7i) {
LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, "To auto-configure cnlink, creating cs7 instance 0 implicitly\n");
s7i = osmo_ss7_instance_find_or_create(g_hnbgw, 0);
OSMO_ASSERT(s7i);
osmo_sccp_make_addr_pc_ssn(&cnlink->remote_addr,
cnlink->pool->default_remote_pc,
OSMO_SCCP_SSN_RANAP);
rc = osmo_sccp_addr_create(s7i, cnlink->use.remote_addr_name, &cnlink->remote_addr);
if (rc < 0) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Failed adding address '%s' to sccp address-book!\n",
cnlink->use.remote_addr_name);
return -EINVAL;
}
}
/* Update VTY config to show & point to the address dynamically added to address-book: */
cnlink->vty.remote_addr_name = talloc_strdup(cnlink, cnlink->use.remote_addr_name);
} else {
s7i = sccp_addr_by_name_filled(&cnlink->remote_addr, cnlink->use.remote_addr_name, cnlink->pool->default_remote_pc);
if (!s7i) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "cannot initialize SCCP: there is no SCCP address named '%s'\n",
cnlink->use.remote_addr_name);
return -ENOENT;
}
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "remote-addr is '%s', using cs7 instance %u\n",
cnlink->use.remote_addr_name, osmo_ss7_instance_get_id(s7i));
/* Address exists in address-book but may not be filled entirely: */
rc = osmo_sccp_addr_update(s7i, cnlink->use.remote_addr_name, &cnlink->remote_addr);
if (rc < 0) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Failed updating address '%s' in sccp address-book!\n",
cnlink->use.remote_addr_name);
return -EINVAL;
}
}
ss7_id = osmo_ss7_instance_get_id(s7i); /* (re-)enter disconnect state to resend RESET after timeout. */
cnlink_fsm_state_chg(fi, CNLINK_ST_DISC);
/* Has another cnlink already set up an SCCP instance for this s7i? */ /* Return 0 to not terminate the fsm */
llist_for_each_entry(hsu, &g_hnbgw->sccp.users, entry) {
if (hsu->ss7 != s7i)
continue;
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "using existing SCCP instance %s on cs7 instance %u\n",
hsu->name, ss7_id);
cnlink->hnbgw_sccp_user = hsu;
hnbgw_sccp_user_get(cnlink->hnbgw_sccp_user, HSU_USE_CNLINK);
hnbgw_cnlink_log_self(cnlink);
return 0;
}
/* else cnlink->hnbgw_sccp_user stays NULL and is set up below. */
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "cs7 instance %u has no configured SCCP instance yet\n", ss7_id);
/* No SCCP instance yet for this ss7. Create it. If no address name is given that resolves to a
* particular cs7 instance above, use 'cs7 instance 0'. */
cnlink->hnbgw_sccp_user = hnbgw_sccp_user_alloc(ss7_id);
hnbgw_sccp_user_get(cnlink->hnbgw_sccp_user, HSU_USE_CNLINK);
hnbgw_cnlink_log_self(cnlink);
return 0; return 0;
} }
#define S(x) (1 << (x))
static struct osmo_fsm_state cnlink_fsm_states[] = {
[CNLINK_ST_DISC] = {
.name = "DISCONNECTED",
.in_event_mask = 0
| S(CNLINK_EV_RX_RESET)
| S(CNLINK_EV_RX_RESET_ACK)
,
.out_state_mask = 0
| S(CNLINK_ST_DISC)
| S(CNLINK_ST_CONN)
,
.onenter = cnlink_disc_onenter,
.action = cnlink_disc_action,
},
[CNLINK_ST_CONN] = {
.name = "CONNECTED",
.in_event_mask = 0
| S(CNLINK_EV_RX_RESET)
| S(CNLINK_EV_RX_RESET_ACK)
,
.out_state_mask = 0
| S(CNLINK_ST_DISC)
| S(CNLINK_ST_CONN)
,
.onenter = cnlink_conn_onenter,
.action = cnlink_conn_action,
},
};
static struct osmo_fsm cnlink_fsm = {
.name = "cnlink",
.states = cnlink_fsm_states,
.num_states = ARRAY_SIZE(cnlink_fsm_states),
.log_subsys = DRANAP,
.timer_cb = cnlink_fsm_timer_cb,
.event_names = cnlink_fsm_event_names,
};
bool cnlink_is_conn_ready(const struct hnbgw_cnlink *cnlink)
{
return cnlink->fi->state == CNLINK_ST_CONN;
}
void cnlink_resend_reset(struct hnbgw_cnlink *cnlink)
{
/* Immediately (1ms) kick off reset sending mechanism */
osmo_fsm_inst_state_chg_ms(cnlink->fi, CNLINK_ST_DISC, 1, 0);
}
void cnlink_set_disconnected(struct hnbgw_cnlink *cnlink)
{
/* Go to disconnected state, with the normal RESET timeout to re-send RESET. */
cnlink_fsm_state_chg(cnlink->fi, CNLINK_ST_DISC);
}
static __attribute__((constructor)) void cnlink_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&cnlink_fsm) == 0);
}
void cnlink_rx_reset_cmd(struct hnbgw_cnlink *cnlink)
{
osmo_fsm_inst_dispatch(cnlink->fi, CNLINK_EV_RX_RESET, NULL);
}
void cnlink_rx_reset_ack(struct hnbgw_cnlink *cnlink)
{
osmo_fsm_inst_dispatch(cnlink->fi, CNLINK_EV_RX_RESET_ACK, NULL);
}

View File

@@ -1,220 +0,0 @@
/* (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/core/fsm.h>
#include <osmocom/core/tdef.h>
#include <osmocom/gsm/gsm23236.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <asn1c/asn1helpers.h>
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/ranap/ranap_msg_factory.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/tdefs.h>
#include <osmocom/hnbgw/context_map.h>
enum cnlink_fsm_state {
CNLINK_ST_DISC,
CNLINK_ST_CONN,
};
enum cnlink_fsm_event {
CNLINK_EV_RX_RESET,
CNLINK_EV_RX_RESET_ACK,
};
static const struct value_string cnlink_fsm_event_names[] = {
OSMO_VALUE_STRING(CNLINK_EV_RX_RESET),
OSMO_VALUE_STRING(CNLINK_EV_RX_RESET_ACK),
{}
};
static const struct osmo_tdef_state_timeout cnlink_timeouts[32] = {
[CNLINK_ST_DISC] = { .T = 4 },
};
#define cnlink_fsm_state_chg(FI, STATE) \
osmo_tdef_fsm_inst_state_chg(FI, STATE, \
cnlink_timeouts, \
hnbgw_T_defs, \
-1)
static void link_up(struct hnbgw_cnlink *cnlink)
{
LOGPFSML(cnlink->fi, LOGL_NOTICE, "link up\n");
CNLINK_STAT_SET(cnlink, CNLINK_STAT_CONNECTED, 1);
}
static void link_lost(struct hnbgw_cnlink *cnlink)
{
struct hnbgw_context_map *map, *map2;
LOGPFSML(cnlink->fi, LOGL_NOTICE, "link lost\n");
CNLINK_STAT_SET(cnlink, CNLINK_STAT_CONNECTED, 0);
llist_for_each_entry_safe(map, map2, &cnlink->map_list, hnbgw_cnlink_entry)
context_map_cnlink_lost(map);
}
static void cnlink_disc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
if (prev_state == CNLINK_ST_CONN)
link_lost(cnlink);
}
static void cnlink_disc_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
switch (event) {
case CNLINK_EV_RX_RESET:
hnbgw_cnlink_tx_ranap_reset_ack(cnlink);
cnlink_fsm_state_chg(fi, CNLINK_ST_CONN);
break;
case CNLINK_EV_RX_RESET_ACK:
cnlink_fsm_state_chg(fi, CNLINK_ST_CONN);
break;
default:
OSMO_ASSERT(false);
}
}
static void cnlink_conn_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
if (prev_state != CNLINK_ST_CONN)
link_up(cnlink);
}
static void cnlink_conn_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
switch (event) {
case CNLINK_EV_RX_RESET:
/* We were connected, but the remote side has restarted. */
link_lost(cnlink);
hnbgw_cnlink_tx_ranap_reset_ack(cnlink);
link_up(cnlink);
break;
case CNLINK_EV_RX_RESET_ACK:
LOGPFSML(fi, LOGL_INFO, "Link is already up, ignoring RESET ACK\n");
break;
default:
OSMO_ASSERT(false);
}
}
static int cnlink_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
hnbgw_cnlink_tx_ranap_reset(cnlink);
/* (re-)enter disconnect state to resend RESET after timeout. */
cnlink_fsm_state_chg(fi, CNLINK_ST_DISC);
/* Return 0 to not terminate the fsm */
return 0;
}
#define S(x) (1 << (x))
static struct osmo_fsm_state cnlink_fsm_states[] = {
[CNLINK_ST_DISC] = {
.name = "DISCONNECTED",
.in_event_mask = 0
| S(CNLINK_EV_RX_RESET)
| S(CNLINK_EV_RX_RESET_ACK)
,
.out_state_mask = 0
| S(CNLINK_ST_DISC)
| S(CNLINK_ST_CONN)
,
.onenter = cnlink_disc_onenter,
.action = cnlink_disc_action,
},
[CNLINK_ST_CONN] = {
.name = "CONNECTED",
.in_event_mask = 0
| S(CNLINK_EV_RX_RESET)
| S(CNLINK_EV_RX_RESET_ACK)
,
.out_state_mask = 0
| S(CNLINK_ST_DISC)
| S(CNLINK_ST_CONN)
,
.onenter = cnlink_conn_onenter,
.action = cnlink_conn_action,
},
};
struct osmo_fsm cnlink_fsm = {
.name = "cnlink",
.states = cnlink_fsm_states,
.num_states = ARRAY_SIZE(cnlink_fsm_states),
.log_subsys = DRANAP,
.timer_cb = cnlink_fsm_timer_cb,
.event_names = cnlink_fsm_event_names,
};
bool cnlink_is_conn_ready(const struct hnbgw_cnlink *cnlink)
{
return cnlink->fi->state == CNLINK_ST_CONN;
}
void cnlink_resend_reset(struct hnbgw_cnlink *cnlink)
{
/* Immediately (1ms) kick off reset sending mechanism */
osmo_fsm_inst_state_chg_ms(cnlink->fi, CNLINK_ST_DISC, 1, 0);
}
void cnlink_set_disconnected(struct hnbgw_cnlink *cnlink)
{
/* Go to disconnected state, with the normal RESET timeout to re-send RESET. */
cnlink_fsm_state_chg(cnlink->fi, CNLINK_ST_DISC);
}
static __attribute__((constructor)) void cnlink_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&cnlink_fsm) == 0);
}
void cnlink_rx_reset_cmd(struct hnbgw_cnlink *cnlink)
{
osmo_fsm_inst_dispatch(cnlink->fi, CNLINK_EV_RX_RESET, NULL);
}
void cnlink_rx_reset_ack(struct hnbgw_cnlink *cnlink)
{
osmo_fsm_inst_dispatch(cnlink->fi, CNLINK_EV_RX_RESET_ACK, NULL);
}

View File

@@ -1,230 +0,0 @@
/* RANAP Paging of HNB-GW */
/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
* (C) 2025 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <errno.h>
#include <sys/types.h>
#include <asn1c/asn1helpers.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/ranap/iu_helpers.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/tdefs.h>
/***************
* This module manages the list of "struct cnlink_paging" items in (struct
* hnbgw_cnlink *)->paging.
* Every time a new RANAP Paging Cmd arrives from some cnlink,
* cnlink_paging_add_ranap() is called to potentially store the paging command
* for a while.
* When a paging response is received from HNB, cnlink_find_by_paging_mi() is
* called to obtain the cnlink it should be routed back to.
*/
struct cnlink_paging {
struct llist_head entry;
struct osmo_mobile_identity mi;
struct osmo_mobile_identity mi2;
time_t timestamp;
};
static int cnlink_paging_destructor(struct cnlink_paging *p)
{
llist_del(&p->entry);
return 0;
}
/* Return current timestamp in *timestamp, and the oldest still valid timestamp according to T3113 timeout. */
static const char *cnlink_paging_gettime(time_t *timestamp_p, time_t *timeout_p)
{
struct timespec now;
time_t timestamp;
/* get timestamp */
if (osmo_clock_gettime(CLOCK_MONOTONIC, &now) != 0)
return "cannot get timestamp";
timestamp = now.tv_sec;
if (timestamp_p)
*timestamp_p = timestamp;
if (timeout_p)
*timeout_p = timestamp - osmo_tdef_get(hnbgw_T_defs, 3113, OSMO_TDEF_S, 15);
return NULL;
}
static const char *cnlink_paging_add(struct hnbgw_cnlink *cnlink, const struct osmo_mobile_identity *mi,
const struct osmo_mobile_identity *mi2)
{
struct cnlink_paging *p, *p2;
time_t timestamp;
time_t timeout;
const char *errmsg;
errmsg = cnlink_paging_gettime(&timestamp, &timeout);
if (errmsg)
return errmsg;
/* Prune all paging records that are older than the configured timeout. */
llist_for_each_entry_safe(p, p2, &cnlink->paging, entry) {
if (p->timestamp >= timeout)
continue;
talloc_free(p);
}
/* Add new entry */
p = talloc_zero(cnlink, struct cnlink_paging);
*p = (struct cnlink_paging){
.timestamp = timestamp,
.mi = *mi,
.mi2 = *mi2,
};
llist_add_tail(&p->entry, &cnlink->paging);
talloc_set_destructor(p, cnlink_paging_destructor);
LOG_CNLINK(cnlink, DCN, LOGL_INFO, "Rx Paging from CN for %s %s\n",
osmo_mobile_identity_to_str_c(OTC_SELECT, mi),
osmo_mobile_identity_to_str_c(OTC_SELECT, mi2));
return NULL;
}
static const char *omi_from_ranap_ue_id(struct osmo_mobile_identity *mi, const RANAP_PermanentNAS_UE_ID_t *ranap_mi)
{
if (!ranap_mi)
return "null UE ID";
if (ranap_mi->present != RANAP_PermanentNAS_UE_ID_PR_iMSI)
return talloc_asprintf(OTC_SELECT, "unsupported UE ID type %u in RANAP Paging", ranap_mi->present);
if (ranap_mi->choice.iMSI.size > sizeof(mi->imsi))
return talloc_asprintf(OTC_SELECT, "invalid IMSI size %d > %zu",
ranap_mi->choice.iMSI.size, sizeof(mi->imsi));
*mi = (struct osmo_mobile_identity){
.type = GSM_MI_TYPE_IMSI,
};
ranap_bcd_decode(mi->imsi, sizeof(mi->imsi), ranap_mi->choice.iMSI.buf, ranap_mi->choice.iMSI.size);
LOGP(DCN, LOGL_DEBUG, "ranap MI %s = %s\n", osmo_hexdump(ranap_mi->choice.iMSI.buf, ranap_mi->choice.iMSI.size),
mi->imsi);
return NULL;
}
static const char *omi_from_ranap_temp_ue_id(struct osmo_mobile_identity *mi, const RANAP_TemporaryUE_ID_t *ranap_tmsi)
{
const OCTET_STRING_t *tmsi_str;
if (!ranap_tmsi)
return "null UE ID";
switch (ranap_tmsi->present) {
case RANAP_TemporaryUE_ID_PR_tMSI:
tmsi_str = &ranap_tmsi->choice.tMSI;
break;
case RANAP_TemporaryUE_ID_PR_p_TMSI:
tmsi_str = &ranap_tmsi->choice.p_TMSI;
break;
default:
return talloc_asprintf(OTC_SELECT, "unsupported Temporary UE ID type %u in RANAP Paging", ranap_tmsi->present);
}
*mi = (struct osmo_mobile_identity){
.type = GSM_MI_TYPE_TMSI,
.tmsi = asn1str_to_u32(tmsi_str),
};
LOGP(DCN, LOGL_DEBUG, "ranap temp UE ID = %s\n", osmo_mobile_identity_to_str_c(OTC_SELECT, mi));
return NULL;
}
const char *cnlink_paging_add_ranap(struct hnbgw_cnlink *cnlink, const RANAP_PagingIEs_t *paging_ies)
{
struct osmo_mobile_identity mi = {};
struct osmo_mobile_identity mi2 = {};
RANAP_CN_DomainIndicator_t domain;
const char *errmsg;
domain = paging_ies->cN_DomainIndicator;
errmsg = omi_from_ranap_ue_id(&mi, &paging_ies->permanentNAS_UE_ID);
if (!errmsg && (paging_ies->presenceMask & PAGINGIES_RANAP_TEMPORARYUE_ID_PRESENT))
errmsg = omi_from_ranap_temp_ue_id(&mi2, &paging_ies->temporaryUE_ID);
LOG_CNLINK(cnlink, DCN, errmsg ? LOGL_NOTICE : LOGL_DEBUG,
"Decoded Paging: %s %s %s%s%s\n",
ranap_domain_name(domain),
osmo_mobile_identity_to_str_c(OTC_SELECT, &mi),
mi2.type ? osmo_mobile_identity_to_str_c(OTC_SELECT, &mi2) : "-",
errmsg ? " -- MI error: " : "",
errmsg ? : "");
if (errmsg)
return errmsg;
return cnlink_paging_add(cnlink, &mi, &mi2);
}
/* If this cnlink has a recent Paging for the given MI, return true and drop the Paging record.
* Else return false. */
static bool cnlink_match_paging_mi(struct hnbgw_cnlink *cnlink, const struct osmo_mobile_identity *mi, time_t timeout)
{
struct cnlink_paging *p, *p2;
llist_for_each_entry_safe(p, p2, &cnlink->paging, entry) {
if (p->timestamp < timeout) {
talloc_free(p);
continue;
}
if (osmo_mobile_identity_cmp(&p->mi, mi)
&& osmo_mobile_identity_cmp(&p->mi2, mi))
continue;
talloc_free(p);
return true;
}
return false;
}
struct hnbgw_cnlink *cnlink_find_by_paging_mi(struct hnbgw_cnpool *cnpool, const struct osmo_mobile_identity *mi)
{
struct hnbgw_cnlink *cnlink;
time_t timeout = 0;
const char *errmsg;
errmsg = cnlink_paging_gettime(NULL, &timeout);
if (errmsg)
LOGP(DCN, LOGL_ERROR, "%s\n", errmsg);
llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) {
if (!cnlink_match_paging_mi(cnlink, mi, timeout))
continue;
return cnlink;
}
return NULL;
}

View File

@@ -30,7 +30,6 @@
#include <osmocom/sigtran/sccp_helpers.h> #include <osmocom/sigtran/sccp_helpers.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnbgw_cn.h> #include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h> #include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/mgw_fsm.h> #include <osmocom/hnbgw/mgw_fsm.h>
@@ -88,8 +87,8 @@ struct hnbgw_context_map *context_map_alloc(struct hnb_context *hnb, uint32_t ru
map->hnb_ctx = hnb; map->hnb_ctx = hnb;
map->rua_ctx_id = rua_ctx_id; map->rua_ctx_id = rua_ctx_id;
map->is_ps = is_ps; map->is_ps = is_ps;
INIT_LLIST_HEAD(&map->ps_rab_ass_list); INIT_LLIST_HEAD(&map->ps_rab_ass);
INIT_LLIST_HEAD(&map->ps_rab_list); INIT_LLIST_HEAD(&map->ps_rabs);
map_rua_fsm_alloc(map); map_rua_fsm_alloc(map);
@@ -112,7 +111,7 @@ int context_map_set_cnlink(struct hnbgw_context_map *map, struct hnbgw_cnlink *c
return -EIO; return -EIO;
} }
new_scu_conn_id = osmo_sccp_instance_next_conn_id(osmo_ss7_get_sccp(hsu->ss7)); new_scu_conn_id = osmo_sccp_instance_next_conn_id(hsu->ss7->sccp);
if (new_scu_conn_id < 0) { if (new_scu_conn_id < 0) {
LOG_MAP(map, DCN, LOGL_ERROR, "Unable to allocate SCCP conn ID on %s\n", hsu->name); LOG_MAP(map, DCN, LOGL_ERROR, "Unable to allocate SCCP conn ID on %s\n", hsu->name);
return new_scu_conn_id; return new_scu_conn_id;
@@ -126,7 +125,7 @@ int context_map_set_cnlink(struct hnbgw_context_map *map, struct hnbgw_cnlink *c
hash_add(hsu->hnbgw_context_map_by_conn_id, &map->hnbgw_sccp_user_entry, new_scu_conn_id); hash_add(hsu->hnbgw_context_map_by_conn_id, &map->hnbgw_sccp_user_entry, new_scu_conn_id);
LOGP(DRUA, LOGL_INFO, "New conn: %s '%s' RUA-%u <-> SCCP-%u %s%s%s %s l=%s<->r=%s\n", LOGP(DRUA, LOGL_NOTICE, "New conn: %s '%s' RUA-%u <-> SCCP-%u %s%s%s %s l=%s<->r=%s\n",
osmo_sock_get_name2_c(OTC_SELECT, osmo_stream_srv_get_ofd(map->hnb_ctx->conn)->fd), osmo_sock_get_name2_c(OTC_SELECT, osmo_stream_srv_get_ofd(map->hnb_ctx->conn)->fd),
hnb_context_name(map->hnb_ctx), map->rua_ctx_id, hnb_context_name(map->hnb_ctx), map->rua_ctx_id,
new_scu_conn_id, new_scu_conn_id,
@@ -187,7 +186,7 @@ void context_map_hnb_released(struct hnbgw_context_map *map)
void context_map_cnlink_lost(struct hnbgw_context_map *map) void context_map_cnlink_lost(struct hnbgw_context_map *map)
{ {
map_sccp_dispatch(map, MAP_SCCP_EV_CN_LINK_LOST, NULL); map_sccp_dispatch(map, MAP_SCCP_EV_RAN_LINK_LOST, NULL);
} }
void context_map_free(struct hnbgw_context_map *map) void context_map_free(struct hnbgw_context_map *map)

View File

@@ -24,12 +24,19 @@
#include <osmocom/core/utils.h> #include <osmocom/core/utils.h>
#include <osmocom/core/fsm.h> #include <osmocom/core/fsm.h>
#include <osmocom/hnbgw/hnb.h> #include <osmocom/ranap/ranap_common_cn.h>
#if ENABLE_PFCP
#include <osmocom/pfcp/pfcp_cp_peer.h>
#endif
#include <osmocom/hnbgw/hnbgw.h> #include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h> #include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/tdefs.h> #include <osmocom/hnbgw/tdefs.h>
#include <osmocom/hnbgw/hnbgw_rua.h> #include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbgw/hnbgw_ranap.h> #include <osmocom/hnbgw/mgw_fsm.h>
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
#include <osmocom/hnbgw/kpi.h>
enum map_rua_fsm_state { enum map_rua_fsm_state {
MAP_RUA_ST_INIT, MAP_RUA_ST_INIT,
@@ -121,19 +128,70 @@ static int map_rua_fsm_timer_cb(struct osmo_fsm_inst *fi)
} }
} }
static int destruct_ranap_cn_rx_co_ies(ranap_message *ranap_message_p)
{
ranap_cn_rx_co_free(ranap_message_p);
return 0;
}
/* Decode RANAP message with convenient memory freeing: just talloc_free() the returned pointer..
* Allocate a ranap_message from OTC_SELECT, decode RANAP msgb into it, attach a talloc destructor that calls
* ranap_cn_rx_co_free() upon talloc_free(), and return the decoded ranap_message. */
ranap_message *hnbgw_decode_ranap_co(struct msgb *ranap_msg)
{
int rc;
ranap_message *message;
if (!msg_has_l2_data(ranap_msg))
return NULL;
message = talloc_zero(OTC_SELECT, ranap_message);
rc = ranap_cn_rx_co_decode2(message, msgb_l2(ranap_msg), msgb_l2len(ranap_msg));
if (rc != 0) {
talloc_free(message);
return NULL;
}
talloc_set_destructor(message, destruct_ranap_cn_rx_co_ies);
return message;
}
/* Dispatch RANAP message to SCCP, if any. */ /* Dispatch RANAP message to SCCP, if any. */
static int handle_rx_rua(struct osmo_fsm_inst *fi, struct msgb *ranap_msg) static int handle_rx_rua(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
{ {
struct hnbgw_context_map *map = fi->priv; struct hnbgw_context_map *map = fi->priv;
/* If the FSM instance has already terminated, don't dispatch anything. */
if (fi->proc.terminating)
return 0;
if (!msg_has_l2_data(ranap_msg)) if (!msg_has_l2_data(ranap_msg))
return 0; return 0;
return hnbgw_ranap_rx_data_ul(map, ranap_msg); ranap_message *message = hnbgw_decode_ranap_co(ranap_msg);
if (message) {
LOGPFSML(fi, LOGL_DEBUG, "rx from RUA: RANAP %s\n",
get_value_string(ranap_procedure_code_vals, message->procedureCode));
kpi_ranap_process_ul(map, message);
if (!map->is_ps) {
/* See if it is a RAB Assignment Response message from RUA to SCCP, where we need to change the user plane
* information, for RTP mapping via MGW, or GTP mapping via UPF. */
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* mgw_fsm_handle_rab_ass_resp() takes ownership of prim->oph and (ranap) message */
return mgw_fsm_handle_cs_rab_ass_resp(map, ranap_msg, message);
}
} else {
#if ENABLE_PFCP
if (hnb_gw_is_gtp_mapping_enabled()) {
/* map->is_ps == true and PFCP is enabled in osmo-hnbgw.cfg */
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* ps_rab_ass_fsm takes ownership of prim->oph and RANAP message */
return hnbgw_gtpmap_rx_rab_ass_resp(map, ranap_msg, message);
}
}
#endif
}
}
/* It was not a RAB Assignment Response that needed to be intercepted. Forward as-is to SCCP. */
return map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg);
} }
static int forward_ranap_to_rua(struct hnbgw_context_map *map, struct msgb *ranap_msg) static int forward_ranap_to_rua(struct hnbgw_context_map *map, struct msgb *ranap_msg)
@@ -156,14 +214,16 @@ static int forward_ranap_to_rua(struct hnbgw_context_map *map, struct msgb *rana
static void map_rua_init_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) static void map_rua_init_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{ {
struct hnbgw_context_map *map = fi->priv;
struct msgb *ranap_msg = data; struct msgb *ranap_msg = data;
switch (event) { switch (event) {
case MAP_RUA_EV_RX_CONNECT: case MAP_RUA_EV_RX_CONNECT:
/* not needed for RAB assignment scanning, but for KPI scanning */
handle_rx_rua(fi, ranap_msg);
map_rua_fsm_state_chg(MAP_RUA_ST_CONNECTED); map_rua_fsm_state_chg(MAP_RUA_ST_CONNECTED);
/* The Connect will never be a RAB Assignment response, so no need for handle_rx_rua() (which decodes
* the RANAP message to detect a RAB Assignment response). Just forward to SCCP as is. */
map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg);
return; return;
case MAP_RUA_EV_RX_DISCONNECT: case MAP_RUA_EV_RX_DISCONNECT:
@@ -224,21 +284,15 @@ static void map_rua_connected_action(struct osmo_fsm_inst *fi, uint32_t event, v
return; return;
case MAP_RUA_EV_RX_DISCONNECT: case MAP_RUA_EV_RX_DISCONNECT:
/* 3GPP TS 25.468 9.1.5: RUA has disconnected. /* received Disconnect from RUA. forward any payload to SCCP, and change state. */
* - Under normal conditions (cause=Normal) the RUA Disconnect contains a RANAP Iu-ReleaseComplete. if (!map_sccp_is_active(map)) {
* On SCCP, the Iu-ReleaseComplete should still be forwarded as N-Data SCCP Data Form 1), /* If, unlikely, the SCCP is already gone, changing to MAP_RUA_ST_DISCONNECTED frees the
* and we will expect the CN to send an SCCP RLSD soon. * hnbgw_context_map. Avoid a use-after-free. */
* - Under error conditions, cause!=Normal and there's no RANAP message. map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED);
* In that case, we need to tear down the associated SCCP link towards CN, return;
* which in turn will tear down the upper layer Iu conn.
*/
if (msg_has_l2_data(ranap_msg)) {
/* Forward any payload to SCCP before Disconnect. */
handle_rx_rua(fi, ranap_msg);
} else {
map->rua_fi_ctx.rua_disconnect_err_condition = true;
} }
map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED); map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED);
handle_rx_rua(fi, ranap_msg);
return; return;
case MAP_RUA_EV_HNB_LINK_LOST: case MAP_RUA_EV_HNB_LINK_LOST:
@@ -258,13 +312,13 @@ static void map_rua_connected_action(struct osmo_fsm_inst *fi, uint32_t event, v
} }
} }
static void map_rua_free_if_done(struct hnbgw_context_map *map, uint32_t sccp_event, void *ev_data) static void map_rua_free_if_done(struct hnbgw_context_map *map, uint32_t sccp_event)
{ {
/* From RUA's POV, we can now free the hnbgw_context_map. /* From RUA's POV, we can now free the hnbgw_context_map.
* If SCCP is still active, tell it to disconnect -- in that case the SCCP side will call context_map_free(). * If SCCP is still active, tell it to disconnect -- in that case the SCCP side will call context_map_free().
* If SCCP is no longer active, free this map. */ * If SCCP is no longer active, free this map. */
if (map_sccp_is_active(map)) if (map_sccp_is_active(map))
map_sccp_dispatch(map, sccp_event, ev_data); map_sccp_dispatch(map, sccp_event, NULL);
else else
context_map_free(map); context_map_free(map);
} }
@@ -272,38 +326,21 @@ static void map_rua_free_if_done(struct hnbgw_context_map *map, uint32_t sccp_ev
static void map_rua_disconnected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) static void map_rua_disconnected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{ {
struct hnbgw_context_map *map = fi->priv; struct hnbgw_context_map *map = fi->priv;
map_rua_free_if_done(map, MAP_SCCP_EV_RAN_DISC, (void *)map->rua_fi_ctx.rua_disconnect_err_condition); map_rua_free_if_done(map, MAP_SCCP_EV_RAN_DISC);
} }
static void map_rua_disconnected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) static void map_rua_disconnected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{ {
struct msgb *ranap_msg; struct msgb *ranap_msg = data;
if (msg_has_l2_data(ranap_msg))
switch (event) { LOGPFSML(fi, LOGL_ERROR, "RUA not connected, cannot dispatch RANAP message\n");
/* Ignore all events. */
case MAP_RUA_EV_TX_DIRECT_TRANSFER:
/* This can happen if CN is buggy, or in general if there was a race
* condition between us forwarding the release towards CN (SCCP Release
* or RANAP Iu-ReleaseComplete) and CN sendig whatever to us. */
ranap_msg = data;
if (msg_has_l2_data(ranap_msg)) {
LOGPFSML(fi, LOGL_NOTICE, "RUA already disconnected, skip forwarding DL RANAP msg (%u bytes)\n",
msgb_l2len(ranap_msg));
LOGPFSML(fi, LOGL_DEBUG, "%s\n", osmo_hexdump(msgb_l2(ranap_msg), msgb_l2len(ranap_msg)));
}
break;
case MAP_RUA_EV_CN_DISC:
case MAP_RUA_EV_HNB_LINK_LOST:
/* Ignore events. */
break;
}
} }
static void map_rua_disrupted_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) static void map_rua_disrupted_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{ {
struct hnbgw_context_map *map = fi->priv; struct hnbgw_context_map *map = fi->priv;
map_rua_free_if_done(map, MAP_SCCP_EV_RAN_LINK_LOST, NULL); map_rua_free_if_done(map, MAP_SCCP_EV_RAN_LINK_LOST);
} }
void map_rua_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) void map_rua_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
@@ -349,7 +386,6 @@ static const struct osmo_fsm_state map_rua_fsm_states[] = {
[MAP_RUA_ST_DISCONNECTED] = { [MAP_RUA_ST_DISCONNECTED] = {
.name = "disconnected", .name = "disconnected",
.in_event_mask = 0 .in_event_mask = 0
| S(MAP_RUA_EV_TX_DIRECT_TRANSFER)
| S(MAP_RUA_EV_CN_DISC) | S(MAP_RUA_EV_CN_DISC)
| S(MAP_RUA_EV_HNB_LINK_LOST) | S(MAP_RUA_EV_HNB_LINK_LOST)
, ,

View File

@@ -27,11 +27,18 @@
#include <osmocom/sigtran/sccp_helpers.h> #include <osmocom/sigtran/sccp_helpers.h>
#include <osmocom/hnbgw/hnb.h> #include <osmocom/ranap/ranap_common_ran.h>
#if ENABLE_PFCP
#include <osmocom/pfcp/pfcp_cp_peer.h>
#endif
#include <osmocom/hnbgw/hnbgw_cn.h> #include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h> #include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/hnbgw_ranap.h>
#include <osmocom/hnbgw/tdefs.h> #include <osmocom/hnbgw/tdefs.h>
#include <osmocom/hnbgw/mgw_fsm.h>
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
#include <osmocom/hnbgw/kpi.h>
enum map_sccp_fsm_state { enum map_sccp_fsm_state {
MAP_SCCP_ST_INIT, MAP_SCCP_ST_INIT,
@@ -81,7 +88,7 @@ void map_sccp_fsm_alloc(struct hnbgw_context_map *map)
OSMO_ASSERT(map->sccp_fi == NULL); OSMO_ASSERT(map->sccp_fi == NULL);
map->sccp_fi = fi; map->sccp_fi = fi;
INIT_LLIST_HEAD(&map->sccp_fi_ctx.wait_cc_tx_msg_list);
/* trigger the timeout */ /* trigger the timeout */
map_sccp_fsm_state_chg(MAP_SCCP_ST_INIT); map_sccp_fsm_state_chg(MAP_SCCP_ST_INIT);
} }
@@ -118,8 +125,10 @@ bool map_sccp_is_active(struct hnbgw_context_map *map)
static int tx_sccp_cr(struct osmo_fsm_inst *fi, struct msgb *ranap_msg) static int tx_sccp_cr(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
{ {
struct hnbgw_context_map *map = fi->priv; struct hnbgw_context_map *map = fi->priv;
struct osmo_scu_prim *prim;
int rc;
if (!map->cnlink) { if (!map->cnlink || !map->cnlink->hnbgw_sccp_user) {
LOGPFSML(fi, LOGL_ERROR, "Failed to send SCCP Connection Request: no CN link\n"); LOGPFSML(fi, LOGL_ERROR, "Failed to send SCCP Connection Request: no CN link\n");
return -1; return -1;
} }
@@ -129,83 +138,134 @@ static int tx_sccp_cr(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
ranap_msg = hnbgw_ranap_msg_alloc("SCCP-CR-empty"); ranap_msg = hnbgw_ranap_msg_alloc("SCCP-CR-empty");
} }
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_N_CONNECT_REQ); prim = (struct osmo_scu_prim *)msgb_push(ranap_msg, sizeof(*prim));
return hnbgw_sccp_user_tx_connect_req(map->cnlink->hnbgw_sccp_user, osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST, ranap_msg);
&map->cnlink->remote_addr, prim->u.connect.called_addr = map->cnlink->remote_addr;
map->scu_conn_id, prim->u.connect.calling_addr = map->cnlink->hnbgw_sccp_user->local_addr;
ranap_msg); prim->u.connect.sccp_class = 2;
prim->u.connect.conn_id = map->scu_conn_id;
rc = osmo_sccp_user_sap_down_nofree(map->cnlink->hnbgw_sccp_user->sccp_user, &prim->oph);
if (rc)
LOGPFSML(fi, LOGL_ERROR, "Failed to send SCCP Connection Request to CN\n");
return rc;
} }
static int tx_sccp_df1(struct osmo_fsm_inst *fi, struct msgb *ranap_msg) static int tx_sccp_df1(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
{ {
struct hnbgw_context_map *map = fi->priv; struct hnbgw_context_map *map = fi->priv;
struct osmo_scu_prim *prim;
int rc;
if (!msg_has_l2_data(ranap_msg)) if (!msg_has_l2_data(ranap_msg))
return 0; return 0;
if (!map->cnlink) { if (!map->cnlink || !map->cnlink->hnbgw_sccp_user) {
LOGPFSML(fi, LOGL_ERROR, "Failed to send SCCP Data Form 1: no CN link\n"); LOGPFSML(fi, LOGL_ERROR, "Failed to send SCCP Data Form 1: no CN link\n");
return -1; return -1;
} }
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_N_DATA_REQ); prim = (struct osmo_scu_prim *)msgb_push(ranap_msg, sizeof(*prim));
return hnbgw_sccp_user_tx_data_req(map->cnlink->hnbgw_sccp_user, osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST, ranap_msg);
map->scu_conn_id, prim->u.data.conn_id = map->scu_conn_id;
ranap_msg);
rc = osmo_sccp_user_sap_down_nofree(map->cnlink->hnbgw_sccp_user->sccp_user, &prim->oph);
if (rc)
LOGPFSML(fi, LOGL_ERROR, "Failed to send SCCP Data Form 1 to CN\n");
return rc;
} }
static int tx_sccp_rlsd(struct osmo_fsm_inst *fi) static int tx_sccp_rlsd(struct osmo_fsm_inst *fi)
{ {
struct hnbgw_context_map *map = fi->priv; struct hnbgw_context_map *map = fi->priv;
if (!map->cnlink) { if (!map->cnlink || !map->cnlink->hnbgw_sccp_user) {
LOGPFSML(fi, LOGL_ERROR, "Failed to send SCCP RLSD: no CN link\n"); LOGPFSML(fi, LOGL_ERROR, "Failed to send SCCP RLSD: no CN link\n");
return -1; return -1;
} }
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_N_DISCONNECT_REQ); return osmo_sccp_tx_disconn(map->cnlink->hnbgw_sccp_user->sccp_user, map->scu_conn_id, NULL, 0);
return hnbgw_sccp_user_tx_disconnect_req(map->cnlink->hnbgw_sccp_user, }
map->scu_conn_id);
static int destruct_ranap_ran_rx_co_ies(ranap_message *ranap_message_p)
{
ranap_ran_rx_co_free(ranap_message_p);
return 0;
} }
static int handle_rx_sccp(struct osmo_fsm_inst *fi, struct msgb *ranap_msg) static int handle_rx_sccp(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
{ {
struct hnbgw_context_map *map = fi->priv; struct hnbgw_context_map *map = fi->priv;
int rc;
/* If the FSM instance has already terminated, don't dispatch anything. */
if (fi->proc.terminating)
return 0;
/* When there was no message received along with the received event, then there is nothing to forward to RUA. */ /* When there was no message received along with the received event, then there is nothing to forward to RUA. */
if (!msg_has_l2_data(ranap_msg)) if (!msg_has_l2_data(ranap_msg))
return 0; return 0;
return hnbgw_ranap_rx_data_dl(map, ranap_msg); /* See if it is a RAB Assignment Request message from SCCP to RUA, where we need to change the user plane
} * information, for RTP mapping via MGW, or GTP mapping via UPF. */
ranap_message *message;
message = talloc_zero(OTC_SELECT, ranap_message);
rc = ranap_ran_rx_co_decode(message, message, msgb_l2(ranap_msg), msgb_l2len(ranap_msg));
if (rc == 0) {
talloc_set_destructor(message, destruct_ranap_ran_rx_co_ies);
static void wait_cc_tx_msg_list_enqueue(struct hnbgw_context_map *map, struct msgb *ranap_msg) LOGPFSML(fi, LOGL_DEBUG, "rx from SCCP: RANAP %s\n",
{ get_value_string(ranap_procedure_code_vals, message->procedureCode));
talloc_steal(map, ranap_msg);
msgb_enqueue(&map->sccp_fi_ctx.wait_cc_tx_msg_list, ranap_msg);
}
static struct msgb *wait_cc_tx_msg_list_dequeue(struct hnbgw_context_map *map) kpi_ranap_process_dl(map, message);
{
struct msgb *ranap_msg = msgb_dequeue(&map->sccp_fi_ctx.wait_cc_tx_msg_list); if (!map->is_ps) {
if (ranap_msg) /* Circuit-Switched. Set up mapping of RTP ports via MGW */
talloc_steal(OTC_SELECT, ranap_msg);
return ranap_msg; switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* mgw_fsm_alloc_and_handle_rab_ass_req() takes ownership of (ranap) message */
return handle_cs_rab_ass_req(map, ranap_msg, message);
case RANAP_ProcedureCode_id_Iu_Release:
/* Any IU Release will terminate the MGW FSM, the message itsself is not passed to the
* FSM code. It is just forwarded normally by map_rua_tx_dt() below. */
mgw_fsm_release(map);
break;
}
#if ENABLE_PFCP
} else {
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* If a UPF is configured, handle the RAB Assignment via ps_rab_ass_fsm, and replace the
* GTP F-TEIDs in the RAB Assignment message before passing it on to RUA. */
if (hnb_gw_is_gtp_mapping_enabled()) {
LOGP(DMAIN, LOGL_DEBUG,
"RAB Assignment: setting up GTP tunnel mapping via UPF %s\n",
osmo_sockaddr_to_str_c(OTC_SELECT, &g_hnbgw->pfcp.cp_peer->remote_addr));
return hnbgw_gtpmap_rx_rab_ass_req(map, ranap_msg, message);
}
/* If no UPF is configured, directly forward the message as-is (no GTP mapping). */
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 map_rua_tx_dt() below. */
hnbgw_gtpmap_release(map);
break;
}
#endif
}
}
/* It was not a RAB Assignment Request that needed to be intercepted. Forward as-is to RUA. */
return map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, ranap_msg);
} }
static void map_sccp_init_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) static void map_sccp_init_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{ {
struct msgb *ranap_msg = NULL;
struct hnbgw_context_map *map = fi->priv; struct hnbgw_context_map *map = fi->priv;
struct msgb *ranap_msg = data;
switch (event) { switch (event) {
case MAP_SCCP_EV_TX_DATA_REQUEST: case MAP_SCCP_EV_TX_DATA_REQUEST:
ranap_msg = data;
/* In the INIT state, the first MAP_SCCP_EV_TX_DATA_REQUEST will be the RANAP message received from the /* In the INIT state, the first MAP_SCCP_EV_TX_DATA_REQUEST will be the RANAP message received from the
* RUA Connect message. Send the SCCP CR and transition to WAIT_CC. */ * RUA Connect message. Send the SCCP CR and transition to WAIT_CC. */
if (tx_sccp_cr(fi, ranap_msg) == 0) if (tx_sccp_cr(fi, ranap_msg) == 0)
@@ -213,22 +273,18 @@ static void map_sccp_init_action(struct osmo_fsm_inst *fi, uint32_t event, void
return; return;
case MAP_SCCP_EV_RAN_LINK_LOST: case MAP_SCCP_EV_RAN_LINK_LOST:
case MAP_SCCP_EV_RAN_DISC:
case MAP_SCCP_EV_USER_ABORT: case MAP_SCCP_EV_USER_ABORT:
case MAP_SCCP_EV_CN_LINK_LOST: case MAP_SCCP_EV_CN_LINK_LOST:
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); /* No CR has been sent yet, just go to disconnected state. */
return; if (msg_has_l2_data(ranap_msg))
LOG_MAP(map, DLSCCP, LOGL_ERROR, "SCCP not connected, cannot dispatch RANAP message\n");
case MAP_SCCP_EV_RAN_DISC:
/* bool rua_disconnect_err_condition = !!data; */
/* 3GPP TS 25.468 9.1.5: RUA has disconnected.
* In this state we didn't send an SCCP CR yet, so nothing to be torn down on CN side. */
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return; return;
case MAP_SCCP_EV_RX_RELEASED: case MAP_SCCP_EV_RX_RELEASED:
/* SCCP RLSD received from CN. This will never happen since we haven't even asked for a connection, but /* SCCP RLSD received from CN. This will never happen since we haven't even asked for a connection, but
* for completeness: */ * for completeness: */
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_RLSD_CN_ORIGIN);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return; return;
@@ -240,41 +296,33 @@ static void map_sccp_init_action(struct osmo_fsm_inst *fi, uint32_t event, void
static void map_sccp_wait_cc_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) static void map_sccp_wait_cc_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{ {
struct hnbgw_context_map *map = fi->priv; struct hnbgw_context_map *map = fi->priv;
struct msgb *ranap_msg = NULL; struct msgb *ranap_msg = data;
switch (event) { switch (event) {
case MAP_SCCP_EV_RX_CONNECTION_CONFIRM: case MAP_SCCP_EV_RX_CONNECTION_CONFIRM:
ranap_msg = data;
map_sccp_fsm_state_chg(MAP_SCCP_ST_CONNECTED); map_sccp_fsm_state_chg(MAP_SCCP_ST_CONNECTED);
/* Usually doesn't but if the SCCP CC contained data, forward it to RUA */ /* Usually doesn't but if the SCCP CC contained data, forward it to RUA */
handle_rx_sccp(fi, ranap_msg); handle_rx_sccp(fi, ranap_msg);
return; return;
case MAP_SCCP_EV_TX_DATA_REQUEST: case MAP_SCCP_EV_TX_DATA_REQUEST:
ranap_msg = data; LOGPFSML(fi, LOGL_ERROR, "Connection not yet confirmed, cannot forward RANAP to CN\n");
LOGPFSML(fi, LOGL_INFO, "Caching RANAP msg from RUA while waiting for SCCP CC\n");
wait_cc_tx_msg_list_enqueue(map, ranap_msg);
return; return;
case MAP_SCCP_EV_RAN_LINK_LOST: case MAP_SCCP_EV_RAN_LINK_LOST:
case MAP_SCCP_EV_RAN_DISC:
case MAP_SCCP_EV_USER_ABORT: case MAP_SCCP_EV_USER_ABORT:
case MAP_SCCP_EV_CN_LINK_LOST: case MAP_SCCP_EV_CN_LINK_LOST:
map->please_disconnect = true; /* RUA connection was terminated. First wait for the CC before releasing the SCCP conn. */
return; if (msg_has_l2_data(ranap_msg))
LOGPFSML(fi, LOGL_ERROR, "Connection not yet confirmed, cannot forward RANAP to CN\n");
case MAP_SCCP_EV_RAN_DISC:
/* bool rua_disconnect_err_condition = !!data; */
/* 3GPP TS 25.468 9.1.5: RUA has disconnected.
* In this state we didn't send an SCCP CR yet, so nothing to be torn down on CN side. */
map->please_disconnect = true; map->please_disconnect = true;
return; return;
case MAP_SCCP_EV_RX_RELEASED: case MAP_SCCP_EV_RX_RELEASED:
ranap_msg = data;
/* SCCP RLSD received from CN. This will never happen since we haven't even received a Connection /* SCCP RLSD received from CN. This will never happen since we haven't even received a Connection
* Confirmed, but for completeness: */ * Confirmed, but for completeness: */
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_RLSD_CN_ORIGIN);
handle_rx_sccp(fi, ranap_msg); handle_rx_sccp(fi, ranap_msg);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return; return;
@@ -287,15 +335,9 @@ static void map_sccp_wait_cc_action(struct osmo_fsm_inst *fi, uint32_t event, vo
static void map_sccp_connected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) static void map_sccp_connected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{ {
struct hnbgw_context_map *map = fi->priv; struct hnbgw_context_map *map = fi->priv;
struct msgb *ranap_msg;
/* Now that SCCP conn is confirmed, forward pending msgs received from RUA side: */
while ((ranap_msg = wait_cc_tx_msg_list_dequeue(map)))
tx_sccp_df1(fi, ranap_msg);
if (map->please_disconnect) { if (map->please_disconnect) {
/* SCCP has already been asked to disconnect, so disconnect now that the /* SCCP has already been asked to disconnect, so disconnect now that the CC has been received. Send RLSD
* CC has been received. Send RLSD to SCCP (without RANAP data) */ * to SCCP (without RANAP data) */
tx_sccp_rlsd(fi); tx_sccp_rlsd(fi);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
} }
@@ -303,41 +345,26 @@ static void map_sccp_connected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_s
static void map_sccp_connected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) static void map_sccp_connected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{ {
struct hnbgw_context_map *map = fi->priv; struct msgb *ranap_msg = data;
struct msgb *ranap_msg = NULL;
bool rua_disconnect_err_condition;
switch (event) { switch (event) {
case MAP_SCCP_EV_RX_DATA_INDICATION: case MAP_SCCP_EV_RX_DATA_INDICATION:
ranap_msg = data;
/* forward RANAP from SCCP to RUA */ /* forward RANAP from SCCP to RUA */
handle_rx_sccp(fi, ranap_msg); handle_rx_sccp(fi, ranap_msg);
return; return;
case MAP_SCCP_EV_TX_DATA_REQUEST: case MAP_SCCP_EV_TX_DATA_REQUEST:
ranap_msg = data;
/* Someone (usually the RUA side) wants us to send a RANAP payload to CN via SCCP */ /* Someone (usually the RUA side) wants us to send a RANAP payload to CN via SCCP */
tx_sccp_df1(fi, ranap_msg); tx_sccp_df1(fi, ranap_msg);
return; return;
case MAP_SCCP_EV_RAN_DISC: case MAP_SCCP_EV_RAN_DISC:
rua_disconnect_err_condition = !!data; /* RUA has disconnected, and usually has sent an Iu-ReleaseComplete along with its RUA Disconnect. On
/* 3GPP TS 25.468 9.1.5: RUA has disconnected. * SCCP, the Iu-ReleaseComplete should still be forwarded as N-Data (SCCP Data Form 1), and we will
* - Under normal conditions (cause=Normal) the RUA Disconnect * expect the CN to send an SCCP RLSD soon. */
* contained a RANAP Iu-ReleaseComplete which we already map_sccp_fsm_state_chg(MAP_SCCP_ST_WAIT_RLSD);
* handled here through MAP_SCCP_EV_TX_DATA_REQUEST. tx_sccp_df1(fi, ranap_msg);
* On SCCP, We will expect the CN to send an SCCP RLSD soon.
* - Under error conditions, cause!=Normal and there was no RANAP message.
* In that case, we need to tear down the associated SCCP link towards CN,
* which in turn will tear down the upper layer Iu conn.
*/
if (rua_disconnect_err_condition) {
tx_sccp_rlsd(fi);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
} else {
map_sccp_fsm_state_chg(MAP_SCCP_ST_WAIT_RLSD);
}
return; return;
case MAP_SCCP_EV_RAN_LINK_LOST: case MAP_SCCP_EV_RAN_LINK_LOST:
@@ -347,20 +374,23 @@ static void map_sccp_connected_action(struct osmo_fsm_inst *fi, uint32_t event,
/* The user is asking for disconnection, so there is no Iu Release in progress. Disconnect now. */ /* The user is asking for disconnection, so there is no Iu Release in progress. Disconnect now. */
case MAP_SCCP_EV_CN_LINK_LOST: case MAP_SCCP_EV_CN_LINK_LOST:
/* The CN peer has sent a RANAP RESET, so the old link that this map ran on is lost */ /* The CN peer has sent a RANAP RESET, so the old link that this map ran on is lost */
/* There won't be any ranap_msg, but if a caller wants to dispatch a msg, forward it before
* disconnecting. */
tx_sccp_df1(fi, ranap_msg);
tx_sccp_rlsd(fi); tx_sccp_rlsd(fi);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return; return;
case MAP_SCCP_EV_RX_RELEASED: case MAP_SCCP_EV_RX_RELEASED:
ranap_msg = data; /* The CN sends an N-Disconnect (SCCP Released) out of the usual sequence. Not what we expected, but
/* The CN sends an N-Disconnect (SCCP Released). */ * handle it. */
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_RLSD_CN_ORIGIN); LOGPFSML(fi, LOGL_ERROR, "CN sends SCCP Released sooner than expected\n");
handle_rx_sccp(fi, ranap_msg); handle_rx_sccp(fi, ranap_msg);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return; return;
case MAP_SCCP_EV_RX_CONNECTION_CONFIRM: case MAP_SCCP_EV_RX_CONNECTION_CONFIRM:
ranap_msg = data;
/* Already connected. Unusual, but if there is data just forward it. */ /* Already connected. Unusual, but if there is data just forward it. */
LOGPFSML(fi, LOGL_ERROR, "Already connected, but received SCCP CC again\n"); LOGPFSML(fi, LOGL_ERROR, "Already connected, but received SCCP CC again\n");
handle_rx_sccp(fi, ranap_msg); handle_rx_sccp(fi, ranap_msg);
@@ -381,12 +411,11 @@ static void map_sccp_wait_rlsd_onenter(struct osmo_fsm_inst *fi, uint32_t prev_s
static void map_sccp_wait_rlsd_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) static void map_sccp_wait_rlsd_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{ {
struct msgb *ranap_msg = NULL; struct msgb *ranap_msg = data;
switch (event) { switch (event) {
case MAP_SCCP_EV_RX_RELEASED: case MAP_SCCP_EV_RX_RELEASED:
ranap_msg = data;
/* The CN sends the expected SCCP RLSD. /* The CN sends the expected SCCP RLSD.
* Usually there is no data, but if there is just forward it. * Usually there is no data, but if there is just forward it.
* Usually RUA is already disconnected, but let the RUA FSM decide about that. */ * Usually RUA is already disconnected, but let the RUA FSM decide about that. */
@@ -395,13 +424,12 @@ static void map_sccp_wait_rlsd_action(struct osmo_fsm_inst *fi, uint32_t event,
return; return;
case MAP_SCCP_EV_RX_DATA_INDICATION: case MAP_SCCP_EV_RX_DATA_INDICATION:
ranap_msg = data;
/* RUA is probably already disconnected, but let the RUA FSM decide about that. */ /* RUA is probably already disconnected, but let the RUA FSM decide about that. */
handle_rx_sccp(fi, ranap_msg); handle_rx_sccp(fi, ranap_msg);
return; return;
case MAP_SCCP_EV_TX_DATA_REQUEST: case MAP_SCCP_EV_TX_DATA_REQUEST:
ranap_msg = data; case MAP_SCCP_EV_RAN_DISC:
/* Normally, RUA would already disconnected, but since SCCP is officially still connected, we can still /* Normally, RUA would already disconnected, but since SCCP is officially still connected, we can still
* forward messages there. Already waiting for CN to send the SCCP RLSD. If there is a message, forward * forward messages there. Already waiting for CN to send the SCCP RLSD. If there is a message, forward
* it, and just continue to time out on the SCCP RLSD. */ * it, and just continue to time out on the SCCP RLSD. */
@@ -409,7 +437,6 @@ static void map_sccp_wait_rlsd_action(struct osmo_fsm_inst *fi, uint32_t event,
return; return;
case MAP_SCCP_EV_RX_CONNECTION_CONFIRM: case MAP_SCCP_EV_RX_CONNECTION_CONFIRM:
ranap_msg = data;
/* Already connected. Unusual, but if there is data just forward it. */ /* Already connected. Unusual, but if there is data just forward it. */
LOGPFSML(fi, LOGL_ERROR, "Already connected, but received SCCP CC\n"); LOGPFSML(fi, LOGL_ERROR, "Already connected, but received SCCP CC\n");
handle_rx_sccp(fi, ranap_msg); handle_rx_sccp(fi, ranap_msg);
@@ -418,7 +445,6 @@ static void map_sccp_wait_rlsd_action(struct osmo_fsm_inst *fi, uint32_t event,
case MAP_SCCP_EV_RAN_LINK_LOST: case MAP_SCCP_EV_RAN_LINK_LOST:
case MAP_SCCP_EV_USER_ABORT: case MAP_SCCP_EV_USER_ABORT:
case MAP_SCCP_EV_CN_LINK_LOST: case MAP_SCCP_EV_CN_LINK_LOST:
case MAP_SCCP_EV_RAN_DISC:
/* Stop waiting for RLSD, send RLSD now. */ /* Stop waiting for RLSD, send RLSD now. */
tx_sccp_rlsd(fi); tx_sccp_rlsd(fi);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
@@ -483,7 +509,6 @@ void map_sccp_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cau
{ {
struct hnbgw_context_map *map = fi->priv; struct hnbgw_context_map *map = fi->priv;
map->sccp_fi = NULL; map->sccp_fi = NULL;
msgb_queue_free(&map->sccp_fi_ctx.wait_cc_tx_msg_list);
} }
#define S(x) (1 << (x)) #define S(x) (1 << (x))

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
/* kitchen sink for OsmoHNBGW implementation */ /* kitchen sink for OsmoHNBGW implementation */
/* (C) 2015,2024 by Harald Welte <laforge@gnumonks.org> /* (C) 2015,2024 by Harald Welte <laforge@gnumonks.org>
* (C) 2016-2025 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> * (C) 2016-2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved * All Rights Reserved
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@@ -19,15 +19,12 @@
* *
*/ */
#include <inttypes.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <netinet/sctp.h> #include <netinet/sctp.h>
#include <osmocom/core/stats.h> #include <osmocom/core/stats.h>
#include <osmocom/core/rate_ctr.h> #include <osmocom/core/rate_ctr.h>
#include <osmocom/core/stat_item.h> #include <osmocom/core/stat_item.h>
#include <osmocom/core/jhash.h>
#include <osmocom/vty/vty.h> #include <osmocom/vty/vty.h>
@@ -40,15 +37,12 @@
#include <osmocom/pfcp/pfcp_proto.h> #include <osmocom/pfcp/pfcp_proto.h>
#endif #endif
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnb_persistent.h>
#include <osmocom/hnbgw/hnbgw.h> #include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_hnbap.h> #include <osmocom/hnbgw/hnbgw_hnbap.h>
#include <osmocom/hnbgw/hnbgw_rua.h> #include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbgw/hnbgw_cn.h> #include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h> #include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/mgw_fsm.h> #include <osmocom/hnbgw/mgw_fsm.h>
#include <osmocom/hnbgw/tdefs.h>
struct hnbgw *g_hnbgw = NULL; struct hnbgw *g_hnbgw = NULL;
@@ -58,17 +52,25 @@ const struct value_string ranap_domain_names[] = {
{} {}
}; };
/* update the active RAB duration rate_ctr for given HNB */
/* timer call-back: Update the HNB_STAT_UPTIME_SECONDS stat item of each hnb_persistent */ static void hnb_store_rab_durations(struct hnb_context *hnb)
static void hnbgw_store_hnb_uptime(void *data)
{ {
struct hnb_persistent *hnbp; struct hnbgw_context_map *map;
struct timespec now;
uint64_t elapsed_cs_rab_ms = 0;
llist_for_each_entry(hnbp, &g_hnbgw->hnb_persistent_list, list) { osmo_clock_gettime(CLOCK_MONOTONIC, &now);
HNBP_STAT_SET(hnbp, HNB_STAT_UPTIME_SECONDS, hnbp->ctx != NULL ? hnbp_get_updowntime(hnbp) : 0);
/* iterate over all context_maps (subscribers) */
llist_for_each_entry(map, &hnb->map_list, hnb_list) {
/* skip any PS maps, we care about CS RABs only here */
if (map->is_ps)
continue;
elapsed_cs_rab_ms += mgw_fsm_get_elapsed_ms(map, &now);
} }
osmo_timer_schedule(&g_hnbgw->store_uptime_timer, STORE_UPTIME_INTERVAL, 0); /* Export to rate countes. */
rate_ctr_add(HNBP_CTR(hnb->persistent, HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL), elapsed_cs_rab_ms);
} }
static void hnbgw_store_hnb_rab_durations(void *data) static void hnbgw_store_hnb_rab_durations(void *data)
@@ -85,13 +87,728 @@ static void hnbgw_store_hnb_rab_durations(void *data)
osmo_timer_schedule(&g_hnbgw->hnb_store_rab_durations_timer, HNB_STORE_RAB_DURATIONS_INTERVAL, 0); osmo_timer_schedule(&g_hnbgw->hnb_store_rab_durations_timer, HNB_STORE_RAB_DURATIONS_INTERVAL, 0);
} }
/*********************************************************************** /***********************************************************************
* UE Context * UE Context
***********************************************************************/ ***********************************************************************/
uint32_t get_next_ue_ctx_id(void) struct ue_context *ue_context_by_id(uint32_t id)
{ {
return g_hnbgw->next_ue_ctx_id++; struct ue_context *ue;
llist_for_each_entry(ue, &g_hnbgw->ue_list, list) {
if (ue->context_id == id)
return ue;
}
return NULL;
}
struct ue_context *ue_context_by_imsi(const char *imsi)
{
struct ue_context *ue;
llist_for_each_entry(ue, &g_hnbgw->ue_list, list) {
if (!strcmp(ue->imsi, imsi))
return ue;
}
return NULL;
}
struct ue_context *ue_context_by_tmsi(uint32_t tmsi)
{
struct ue_context *ue;
llist_for_each_entry(ue, &g_hnbgw->ue_list, list) {
if (ue->tmsi == tmsi)
return ue;
}
return NULL;
}
static void ue_context_free_by_hnb(const struct hnb_context *hnb)
{
struct ue_context *ue, *tmp;
llist_for_each_entry_safe(ue, tmp, &g_hnbgw->ue_list, list) {
if (ue->hnb == hnb)
ue_context_free(ue);
}
}
static uint32_t get_next_ue_ctx_id(void)
{
uint32_t id;
do {
id = g_hnbgw->next_ue_ctx_id++;
} while (ue_context_by_id(id));
return id;
}
struct ue_context *ue_context_alloc(struct hnb_context *hnb, const char *imsi,
uint32_t tmsi)
{
struct ue_context *ue;
ue = talloc_zero(g_hnbgw, struct ue_context);
if (!ue)
return NULL;
ue->hnb = hnb;
if (imsi)
OSMO_STRLCPY_ARRAY(ue->imsi, imsi);
else
ue->imsi[0] = '\0';
ue->tmsi = tmsi;
ue->context_id = get_next_ue_ctx_id();
llist_add_tail(&ue->list, &g_hnbgw->ue_list);
LOGP(DHNBAP, LOGL_INFO, "created UE context: id 0x%x, imsi %s, tmsi 0x%x\n",
ue->context_id, imsi? imsi : "-", tmsi);
return ue;
}
void ue_context_free(struct ue_context *ue)
{
llist_del(&ue->list);
talloc_free(ue);
}
/***********************************************************************
* HNB Context
***********************************************************************/
/* look-up HNB context by id. Used from CTRL */
static struct hnb_context *hnb_context_by_id(uint32_t cid)
{
struct hnb_context *hnb;
llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) {
if (hnb->id.cid == cid)
return hnb;
}
return NULL;
}
/* look-up HNB context by identity_info. Used from VTY */
struct hnb_context *hnb_context_by_identity_info(const char *identity_info)
{
struct hnb_context *hnb;
llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) {
if (strcmp(identity_info, hnb->identity_info) == 0)
return hnb;
}
return NULL;
}
static int hnb_read_cb(struct osmo_stream_srv *conn);
static int hnb_closed_cb(struct osmo_stream_srv *conn);
static struct hnb_context *hnb_context_alloc(struct osmo_stream_srv_link *link, int new_fd)
{
struct hnb_context *ctx;
ctx = talloc_zero(g_hnbgw, struct hnb_context);
if (!ctx)
return NULL;
INIT_LLIST_HEAD(&ctx->map_list);
ctx->conn = osmo_stream_srv_create(g_hnbgw, link, new_fd, hnb_read_cb, hnb_closed_cb, ctx);
if (!ctx->conn) {
LOGP(DMAIN, LOGL_INFO, "error while creating connection\n");
talloc_free(ctx);
return NULL;
}
llist_add_tail(&ctx->list, &g_hnbgw->hnb_list);
return ctx;
}
const char *umts_cell_id_name(const struct umts_cell_id *ucid)
{
const char *fmtstr = "%03u-%02u-L%u-R%u-S%u-C%u";
if (g_hnbgw->config.plmn.mnc_3_digits)
fmtstr = "%03u-%03u-L%u-R%u-S%u-C%u";
return talloc_asprintf(OTC_SELECT, fmtstr, ucid->mcc, ucid->mnc, ucid->lac, ucid->rac,
ucid->sac, ucid->cid);
}
/* parse a string representation of an umts_cell_id into its decoded representation */
int umts_cell_id_from_str(struct umts_cell_id *ucid, const char *instr)
{
int rc = sscanf(instr, "%hu-%hu-L%hu-R%hu-S%hu-C%u", &ucid->mcc, &ucid->mnc, &ucid->lac, &ucid->rac, &ucid->sac, &ucid->cid);
if (rc < 0)
return -errno;
if (rc != 6)
return -EINVAL;
if (ucid->mcc > 999)
return -EINVAL;
if (ucid->mnc > 999)
return -EINVAL;
if (ucid->lac == 0 || ucid->lac == 0xffff)
return -EINVAL;
/* CellIdentity in the ASN.1 syntax is a bit-string of 28 bits length */
if (ucid->cid >= (1 << 28))
return -EINVAL;
return 0;
}
const char *hnb_context_name(struct hnb_context *ctx)
{
char *result;
if (!ctx)
return "NULL";
if (ctx->conn) {
char hostbuf_r[INET6_ADDRSTRLEN];
char portbuf_r[6];
int fd = osmo_stream_srv_get_ofd(ctx->conn)->fd;
/* get remote addr */
if (osmo_sock_get_ip_and_port(fd, hostbuf_r, sizeof(hostbuf_r), portbuf_r, sizeof(portbuf_r), false) == 0)
result = talloc_asprintf(OTC_SELECT, "%s:%s", hostbuf_r, portbuf_r);
else
result = "?";
} else {
result = "disconnected";
}
if (g_hnbgw->config.log_prefix_hnb_id)
result = talloc_asprintf(OTC_SELECT, "%s %s", result, ctx->identity_info);
else
result = talloc_asprintf(OTC_SELECT, "%s %s", result, umts_cell_id_name(&ctx->id));
return result;
}
void hnb_context_release_ue_state(struct hnb_context *ctx)
{
struct hnbgw_context_map *map, *map2;
/* deactivate all context maps */
llist_for_each_entry_safe(map, map2, &ctx->map_list, hnb_list) {
context_map_hnb_released(map);
/* hnbgw_context_map will remove itself from lists when it is ready. */
}
ue_context_free_by_hnb(ctx);
}
void hnb_context_release(struct hnb_context *ctx)
{
struct hnbgw_context_map *map;
LOGHNB(ctx, DMAIN, LOGL_INFO, "Releasing HNB context\n");
if (ctx->persistent) {
struct timespec tp;
int rc;
rc = osmo_clock_gettime(CLOCK_MONOTONIC, &tp);
ctx->persistent->updowntime = (rc < 0) ? 0 : tp.tv_sec;
}
/* remove from the list of HNB contexts */
llist_del(&ctx->list);
hnb_context_release_ue_state(ctx);
if (ctx->conn) { /* we own a conn, we must free it: */
LOGHNB(ctx, DMAIN, LOGL_INFO, "Closing HNB SCTP connection %s\n",
osmo_sock_get_name2(osmo_stream_srv_get_ofd(ctx->conn)->fd));
/* Avoid our closed_cb calling hnb_context_release() again: */
osmo_stream_srv_set_data(ctx->conn, NULL);
osmo_stream_srv_destroy(ctx->conn);
} /* else: we are called from closed_cb, so conn is being freed separately */
/* hnbgw_context_map are still listed in ctx->map_list, but we are freeing ctx. Remove all entries from the
* list, but keep the hnbgw_context_map around for graceful release. They are also listed under
* hnbgw_cnlink->map_list, and will remove themselves when ready. */
while ((map = llist_first_entry_or_null(&ctx->map_list, struct hnbgw_context_map, hnb_list))) {
llist_del(&map->hnb_list);
map->hnb_ctx = NULL;
}
/* remove back reference from hnb_persistent to context */
if (ctx->persistent) {
hnb_nft_kpi_end(ctx->persistent);
ctx->persistent->ctx = NULL;
}
talloc_free(ctx);
}
/***********************************************************************
* HNB Persistent Data
***********************************************************************/
const struct rate_ctr_desc hnb_ctr_description[] = {
[HNB_CTR_IUH_ESTABLISHED] = {
"iuh:established", "Number of times Iuh link was established" },
[HNB_CTR_RANAP_PS_ERR_IND_UL] = {
"ranap:ps:error_ind:ul", "Received ERROR Indications in Uplink (PS Domain)" },
[HNB_CTR_RANAP_CS_ERR_IND_UL] = {
"ranap:cs:error_ind:ul", "Received ERROR Indications in Uplink (PS Domain)" },
[HNB_CTR_RANAP_PS_RESET_REQ_UL] = {
"ranap:ps:reset_req:ul", "Received RESET Requests in Uplink (PS Domain)" },
[HNB_CTR_RANAP_CS_RESET_REQ_UL] = {
"ranap:cs:reset_req:ul", "Received RESET Requests in Uplink (CS Domain)" },
[HNB_CTR_RANAP_PS_RAB_ACT_REQ] = {
"ranap:ps:rab_act:req", "PS RAB Activations requested" },
[HNB_CTR_RANAP_CS_RAB_ACT_REQ] = {
"ranap:cs:rab_act:req", "CS RAB Activations requested" },
[HNB_CTR_RANAP_PS_RAB_ACT_CNF] = {
"ranap:ps:rab_act:cnf", "PS RAB Activations confirmed" },
[HNB_CTR_RANAP_CS_RAB_ACT_CNF] = {
"ranap:cs:rab_act:cnf", "CS RAB Activations confirmed" },
[HNB_CTR_RANAP_PS_RAB_ACT_FAIL] = {
"ranap:ps:rab_act:fail", "PS RAB Activations failed" },
[HNB_CTR_RANAP_CS_RAB_ACT_FAIL] = {
"ranap:cs:rab_act:fail", "CS RAB Activations failed" },
[HNB_CTR_RANAP_PS_RAB_MOD_REQ] = {
"ranap:ps:rab_mod:req", "PS RAB Modifications requested" },
[HNB_CTR_RANAP_CS_RAB_MOD_REQ] = {
"ranap:cs:rab_mod:req", "CS RAB Modifications requested" },
[HNB_CTR_RANAP_PS_RAB_MOD_CNF] = {
"ranap:ps:rab_mod:cnf", "PS RAB Modifications confirmed" },
[HNB_CTR_RANAP_CS_RAB_MOD_CNF] = {
"ranap:cs:rab_mod:cnf", "CS RAB Modifications confirmed" },
[HNB_CTR_RANAP_PS_RAB_MOD_FAIL] = {
"ranap:ps:rab_mod:fail", "PS RAB Modifications failed" },
[HNB_CTR_RANAP_CS_RAB_MOD_FAIL] = {
"ranap:cs:rab_mod:fail", "CS RAB Modifications failed" },
[HNB_CTR_RANAP_PS_RAB_REL_REQ] = {
"ranap:ps:rab_rel:req", "PS RAB Release requested" },
[HNB_CTR_RANAP_CS_RAB_REL_REQ] = {
"ranap:cs:rab_rel:req", "CS RAB Release requested" },
[HNB_CTR_RANAP_PS_RAB_REL_CNF] = {
"ranap:ps:rab_rel:cnf", "PS RAB Release confirmed" },
[HNB_CTR_RANAP_CS_RAB_REL_CNF] = {
"ranap:cs:rab_rel:cnf", "CS RAB Release confirmed" },
[HNB_CTR_RANAP_PS_RAB_REL_FAIL] = {
"ranap:ps:rab_rel:fail", "PS RAB Release failed" },
[HNB_CTR_RANAP_CS_RAB_REL_FAIL] = {
"ranap:cs:rab_rel:fail", "CS RAB Release failed" },
[HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT] = {
"ranap:ps:rab_rel:implicit", "PS RAB Release implicit (during Iu Release)" },
[HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT] = {
"ranap:cs:rab_rel:implicit", "CS RAB Release implicit (during Iu Release)" },
[HNB_CTR_RUA_ERR_IND] = {
"rua:error_ind", "Received RUA Error Indications" },
[HNB_CTR_RUA_PS_CONNECT_UL] = {
"rua:ps:connect:ul", "Received RUA Connect requests (PS Domain)" },
[HNB_CTR_RUA_CS_CONNECT_UL] = {
"rua:cs:connect:ul", "Received RUA Connect requests (CS Domain)" },
[HNB_CTR_RUA_PS_DISCONNECT_UL] = {
"rua:ps:disconnect:ul", "Received RUA Disconnect requests in uplink (PS Domain)" },
[HNB_CTR_RUA_CS_DISCONNECT_UL] = {
"rua:cs:disconnect:ul", "Received RUA Disconnect requests in uplink (CS Domain)" },
[HNB_CTR_RUA_PS_DISCONNECT_DL] = {
"rua:ps:disconnect:dl", "Transmitted RUA Disconnect requests in downlink (PS Domain)" },
[HNB_CTR_RUA_CS_DISCONNECT_DL] = {
"rua:cs:disconnect:dl", "Transmitted RUA Disconnect requests in downlink (CS Domain)" },
[HNB_CTR_RUA_PS_DT_UL] = {
"rua:ps:direct_transfer:ul", "Received RUA DirectTransfer in uplink (PS Domain)" },
[HNB_CTR_RUA_CS_DT_UL] = {
"rua:cs:direct_transfer:ul", "Received RUA DirectTransfer in uplink (CS Domain)" },
[HNB_CTR_RUA_PS_DT_DL] = {
"rua:ps:direct_transfer:dl", "Transmitted RUA DirectTransfer in downlink (PS Domain)" },
[HNB_CTR_RUA_CS_DT_DL] = {
"rua:cs:direct_transfer:dl", "Transmitted RUA DirectTransfer in downlink (CS Domain)" },
[HNB_CTR_RUA_UDT_UL] = {
"rua:unit_data:ul", "Received RUA UnitData (UDT) in uplink" },
[HNB_CTR_RUA_UDT_DL] = {
"rua:unit_data:dl", "Transmitted RUA UnitData (UDT) in downlink" },
[HNB_CTR_PS_PAGING_ATTEMPTED] = {
"paging:ps:attempted", "Transmitted PS Paging requests" },
[HNB_CTR_CS_PAGING_ATTEMPTED] = {
"paging:cs:attempted", "Transmitted CS Paging requests" },
[HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL] = {
"rab:cs:active_milliseconds:total", "Cumulative number of milliseconds of CS RAB activity" },
[HNB_CTR_GTPU_PACKETS_UL] = {
"gtpu:packets:ul",
"Count of GTP-U packets received from the HNB",
},
[HNB_CTR_GTPU_TOTAL_BYTES_UL] = {
"gtpu:total_bytes:ul",
"Count of total GTP-U bytes received from the HNB, including the GTP-U/UDP/IP headers",
},
[HNB_CTR_GTPU_PACKETS_DL] = {
"gtpu:packets:dl",
"Count of GTP-U packets sent to the HNB",
},
[HNB_CTR_GTPU_TOTAL_BYTES_DL] = {
"gtpu:total_bytes:dl",
"Count of total GTP-U bytes sent to the HNB, including the GTP-U/UDP/IP headers",
},
};
const struct rate_ctr_group_desc hnb_ctrg_desc = {
"hnb",
"hNodeB",
OSMO_STATS_CLASS_GLOBAL,
ARRAY_SIZE(hnb_ctr_description),
hnb_ctr_description,
};
const struct osmo_stat_item_desc hnb_stat_desc[] = {
[HNB_STAT_UPTIME_SECONDS] = { "uptime:seconds", "Seconds of uptime", "s", 60, 0 },
};
const struct osmo_stat_item_group_desc hnb_statg_desc = {
.group_name_prefix = "hnb",
.group_description = "hNodeB",
.class_id = OSMO_STATS_CLASS_GLOBAL,
.num_items = ARRAY_SIZE(hnb_stat_desc),
.item_desc = hnb_stat_desc,
};
struct hnb_persistent *hnb_persistent_alloc(const struct umts_cell_id *id)
{
struct hnb_persistent *hnbp = talloc_zero(g_hnbgw, struct hnb_persistent);
if (!hnbp)
return NULL;
hnbp->id = *id;
hnbp->id_str = talloc_strdup(hnbp, umts_cell_id_name(id));
hnbp->ctrs = rate_ctr_group_alloc(hnbp, &hnb_ctrg_desc, 0);
if (!hnbp->ctrs)
goto out_free;
rate_ctr_group_set_name(hnbp->ctrs, hnbp->id_str);
hnbp->statg = osmo_stat_item_group_alloc(hnbp, &hnb_statg_desc, 0);
if (!hnbp->statg)
goto out_free_ctrs;
osmo_stat_item_group_set_name(hnbp->statg, hnbp->id_str);
llist_add(&hnbp->list, &g_hnbgw->hnb_persistent_list);
return hnbp;
out_free_ctrs:
rate_ctr_group_free(hnbp->ctrs);
out_free:
talloc_free(hnbp);
return NULL;
}
struct hnb_persistent *hnb_persistent_find_by_id(const struct umts_cell_id *id)
{
struct hnb_persistent *hnbp;
llist_for_each_entry(hnbp, &g_hnbgw->hnb_persistent_list, list) {
if (umts_cell_id_equal(&hnbp->id, id))
return hnbp;
}
return NULL;
}
struct hnb_persistent *hnb_persistent_find_by_id_str(const char *id_str)
{
struct hnb_persistent *hnbp;
llist_for_each_entry (hnbp, &g_hnbgw->hnb_persistent_list, list) {
if (strcmp(hnbp->id_str, id_str))
continue;
return hnbp;
}
return NULL;
}
/* Read the peer's remote IP address from the Iuh conn's fd, and set up GTP-U counters for that remote address. */
void hnb_persistent_update_addr(struct hnb_persistent *hnbp, int new_fd)
{
int rc;
socklen_t socklen;
struct osmo_sockaddr osa;
struct osmo_sockaddr_str remote_str;
socklen = sizeof(struct osmo_sockaddr);
rc = getpeername(new_fd, &osa.u.sa, &socklen);
if (!rc)
rc = osmo_sockaddr_str_from_osa(&remote_str, &osa);
if (rc < 0) {
LOGP(DHNB, LOGL_ERROR, "cannot read remote hNodeB address from Iuh file descriptor\n");
return;
}
/* We got the remote address from the RUA link, and now we are blatantly assuming that the hNodeB has its GTP
* endpoint on the same IP address, just with UDP port 2152 (the fixed GTP port as per 3GPP spec). */
remote_str.port = 2152;
hnb_nft_kpi_start(hnbp, &remote_str);
}
void hnb_persistent_free(struct hnb_persistent *hnbp)
{
/* FIXME: check if in use? */
hnb_nft_kpi_end(hnbp);
llist_del(&hnbp->list);
talloc_free(hnbp);
}
/* return the amount of time the HNB is up (hnbp->ctx != NULL) or down (hnbp->ctx == NULL) */
static unsigned long long hnbp_get_updowntime(const struct hnb_persistent *hnbp)
{
struct timespec tp;
if (!hnbp->updowntime)
return 0;
if (osmo_clock_gettime(CLOCK_MONOTONIC, &tp) != 0)
return 0;
return difftime(tp.tv_sec, hnbp->updowntime);
}
unsigned long long hnb_get_updowntime(const struct hnb_context *ctx)
{
if (!ctx->persistent)
return 0;
return hnbp_get_updowntime(ctx->persistent);
}
/* timer call-back: Update the HNB_STAT_UPTIME_SECONDS stat item of each hnb_persistent */
static void hnbgw_store_hnb_uptime(void *data)
{
struct hnb_persistent *hnbp;
llist_for_each_entry(hnbp, &g_hnbgw->hnb_persistent_list, list) {
HNBP_STAT_SET(hnbp, HNB_STAT_UPTIME_SECONDS, hnbp->ctx != NULL ? hnbp_get_updowntime(hnbp) : 0);
}
osmo_timer_schedule(&g_hnbgw->store_uptime_timer, STORE_UPTIME_INTERVAL, 0);
}
/***********************************************************************
* SCTP Socket / stream handling
***********************************************************************/
static int hnb_read_cb(struct osmo_stream_srv *conn)
{
struct hnb_context *hnb = osmo_stream_srv_get_data(conn);
struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn);
struct msgb *msg = msgb_alloc(IUH_MSGB_SIZE, "Iuh rx");
int rc;
if (!msg)
return -ENOMEM;
OSMO_ASSERT(hnb);
/* we store a reference to the HomeNodeB in the msg->dest for the
* benefit of various downstream processing functions */
msg->dst = hnb;
rc = osmo_stream_srv_recv(conn, msg);
/* Notification received */
if (msgb_sctp_msg_flags(msg) & OSMO_STREAM_SCTP_MSG_FLAGS_NOTIFICATION) {
union sctp_notification *notif = (union sctp_notification *)msgb_data(msg);
rc = 0;
switch (notif->sn_header.sn_type) {
case SCTP_ASSOC_CHANGE:
switch (notif->sn_assoc_change.sac_state) {
case SCTP_COMM_LOST:
LOGHNB(hnb, DMAIN, LOGL_NOTICE,
"sctp_recvmsg(%s) = SCTP_COMM_LOST, closing conn\n",
osmo_sock_get_name2(ofd->fd));
osmo_stream_srv_destroy(conn);
rc = -EBADF;
break;
case SCTP_RESTART:
LOGHNB(hnb, DMAIN, LOGL_NOTICE, "HNB SCTP conn RESTARTed, marking as HNBAP-unregistered\n");
hnb->hnb_registered = false;
hnb_context_release_ue_state(hnb);
/* The tx queue may be quite full after an SCTP RESTART: (SYS#6113)
* The link may have been flaky (a possible reason for the peer restarting the conn) and
* hence the kernel socket Tx queue may be full (no ACKs coming back) and our own userspace
* queue may contain plenty of oldish messages to be sent. Since the HNB will re-register after
* this, we simply drop all those old messages: */
osmo_stream_srv_clear_tx_queue(conn);
break;
}
break;
case SCTP_SHUTDOWN_EVENT:
LOGHNB(hnb, DMAIN, LOGL_NOTICE,
"sctp_recvmsg(%s) = SCTP_SHUTDOWN_EVENT, closing conn\n",
osmo_sock_get_name2(ofd->fd));
osmo_stream_srv_destroy(conn);
rc = -EBADF;
break;
}
goto out;
} else if (rc == -EAGAIN) {
/* Older versions of osmo_stream_srv_recv() not supporting
* msgb_sctp_msg_flags() may still return -EAGAIN when an sctp
* notification is received. */
rc = 0;
goto out;
} else if (rc < 0) {
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Error during sctp_recvmsg(%s)\n",
osmo_sock_get_name2(ofd->fd));
osmo_stream_srv_destroy(conn);
rc = -EBADF;
goto out;
} else if (rc == 0) {
LOGHNB(hnb, DMAIN, LOGL_NOTICE, "Connection closed sctp_recvmsg(%s) = 0\n",
osmo_sock_get_name2(ofd->fd));
osmo_stream_srv_destroy(conn);
rc = -EBADF;
goto out;
} else {
msgb_put(msg, rc);
}
switch (msgb_sctp_ppid(msg)) {
case IUH_PPI_HNBAP:
hnb->hnbap_stream = msgb_sctp_stream(msg);
rc = hnbgw_hnbap_rx(hnb, msg);
break;
case IUH_PPI_RUA:
if (!hnb->hnb_registered) {
LOGHNB(hnb, DMAIN, LOGL_NOTICE, "Discarding RUA as HNB is not registered\n");
goto out;
}
hnb->rua_stream = msgb_sctp_stream(msg);
rc = hnbgw_rua_rx(hnb, msg);
break;
case IUH_PPI_SABP:
case IUH_PPI_RNA:
case IUH_PPI_PUA:
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unimplemented SCTP PPID=%lu received\n", msgb_sctp_ppid(msg));
rc = 0;
break;
default:
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unknown SCTP PPID=%lu received\n", msgb_sctp_ppid(msg));
rc = 0;
break;
}
out:
msgb_free(msg);
return rc;
}
static int hnb_closed_cb(struct osmo_stream_srv *conn)
{
struct hnb_context *hnb = osmo_stream_srv_get_data(conn);
if (!hnb)
return 0; /* hnb_context is being freed, nothing do be done */
/* hnb: conn became broken, let's release the associated hnb.
* conn object is being freed after closed_cb(), so unassign it from hnb
* if available to avoid it freeing it again: */
hnb->conn = NULL;
hnb_context_release(hnb);
return 0;
}
/*! call-back when the listen FD has something to read */
int hnbgw_rua_accept_cb(struct osmo_stream_srv_link *srv, int fd)
{
struct hnb_context *ctx;
LOGP(DMAIN, LOGL_INFO, "New HNB SCTP connection %s\n",
osmo_sock_get_name2(fd));
ctx = hnb_context_alloc(srv, fd);
if (!ctx)
return -ENOMEM;
return 0;
}
CTRL_CMD_DEFINE_RO(hnb_info, "info");
static int get_hnb_info(struct ctrl_cmd *cmd, void *data)
{
struct hnb_context *hnb = data;
cmd->reply = talloc_strdup(cmd, hnb->identity_info);
return CTRL_CMD_REPLY;
}
CTRL_CMD_DEFINE_RO(hnbs, "num-hnb");
static int get_hnbs(struct ctrl_cmd *cmd, void *data)
{
cmd->reply = talloc_asprintf(cmd, "%u", llist_count(&g_hnbgw->hnb_list));
return CTRL_CMD_REPLY;
}
int hnb_ctrl_cmds_install(void)
{
int rc = 0;
rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_hnbs);
rc |= ctrl_cmd_install(CTRL_NODE_HNB, &cmd_hnb_info);
return rc;
}
int hnb_ctrl_node_lookup(void *data, vector vline, int *node_type, void **node_data, int *i)
{
const char *token = vector_slot(vline, *i);
struct hnb_context *hnb;
long num;
switch (*node_type) {
case CTRL_NODE_ROOT:
if (strcmp(token, "hnb") != 0)
return 0;
(*i)++;
if (!ctrl_parse_get_num(vline, *i, &num))
return -ERANGE;
hnb = hnb_context_by_id(num);
if (!hnb)
return -ENODEV;
*node_data = hnb;
*node_type = CTRL_NODE_HNB;
break;
default:
return 0;
}
return 1;
} }
int hnbgw_mgw_setup(void) int hnbgw_mgw_setup(void)
@@ -213,7 +930,6 @@ void g_hnbgw_alloc(void *ctx)
/* strdup so we can easily talloc_free in the VTY code */ /* strdup so we can easily talloc_free in the VTY code */
g_hnbgw->config.iuh_local_ip = talloc_strdup(g_hnbgw, HNBGW_LOCAL_IP_DEFAULT); g_hnbgw->config.iuh_local_ip = talloc_strdup(g_hnbgw, HNBGW_LOCAL_IP_DEFAULT);
g_hnbgw->config.iuh_local_port = IUH_DEFAULT_SCTP_PORT; g_hnbgw->config.iuh_local_port = IUH_DEFAULT_SCTP_PORT;
g_hnbgw->config.hnbap_allow_tmsi = true;
g_hnbgw->config.log_prefix_hnb_id = true; g_hnbgw->config.log_prefix_hnb_id = true;
g_hnbgw->config.accept_all_hnb = true; g_hnbgw->config.accept_all_hnb = true;
@@ -222,10 +938,8 @@ void g_hnbgw_alloc(void *ctx)
g_hnbgw->next_ue_ctx_id = 23; g_hnbgw->next_ue_ctx_id = 23;
INIT_LLIST_HEAD(&g_hnbgw->hnb_list); INIT_LLIST_HEAD(&g_hnbgw->hnb_list);
INIT_LLIST_HEAD(&g_hnbgw->hnb_persistent_list); INIT_LLIST_HEAD(&g_hnbgw->hnb_persistent_list);
hash_init(g_hnbgw->hnb_persistent_by_id); INIT_LLIST_HEAD(&g_hnbgw->ue_list);
INIT_LLIST_HEAD(&g_hnbgw->sccp.users); INIT_LLIST_HEAD(&g_hnbgw->sccp.users);
g_hnbgw->mgw_pool = mgcp_client_pool_alloc(g_hnbgw); g_hnbgw->mgw_pool = mgcp_client_pool_alloc(g_hnbgw);
@@ -235,8 +949,35 @@ void g_hnbgw_alloc(void *ctx)
g_hnbgw->config.pfcp.remote_port = OSMO_PFCP_PORT; g_hnbgw->config.pfcp.remote_port = OSMO_PFCP_PORT;
#endif #endif
g_hnbgw->sccp.cnpool_iucs = hnbgw_cnpool_alloc(DOMAIN_CS); g_hnbgw->sccp.cnpool_iucs = (struct hnbgw_cnpool){
g_hnbgw->sccp.cnpool_iups = hnbgw_cnpool_alloc(DOMAIN_PS); .domain = DOMAIN_CS,
.pool_name = "iucs",
.peer_name = "msc",
.default_remote_pc = DEFAULT_PC_MSC,
.vty = {
.nri_bitlen = OSMO_NRI_BITLEN_DEFAULT,
.null_nri_ranges = osmo_nri_ranges_alloc(g_hnbgw),
},
.cnlink_ctrg_desc = &msc_ctrg_desc,
.ctrs = rate_ctr_group_alloc(g_hnbgw, &iucs_ctrg_desc, 0),
};
INIT_LLIST_HEAD(&g_hnbgw->sccp.cnpool_iucs.cnlinks);
g_hnbgw->sccp.cnpool_iups = (struct hnbgw_cnpool){
.domain = DOMAIN_PS,
.pool_name = "iups",
.peer_name = "sgsn",
.default_remote_pc = DEFAULT_PC_SGSN,
.vty = {
.nri_bitlen = OSMO_NRI_BITLEN_DEFAULT,
.null_nri_ranges = osmo_nri_ranges_alloc(g_hnbgw),
},
.cnlink_ctrg_desc = &sgsn_ctrg_desc,
.ctrs = rate_ctr_group_alloc(g_hnbgw, &iups_ctrg_desc, 0),
};
INIT_LLIST_HEAD(&g_hnbgw->sccp.cnpool_iups.cnlinks);
osmo_timer_setup(&g_hnbgw->store_uptime_timer, hnbgw_store_hnb_uptime, g_hnbgw); osmo_timer_setup(&g_hnbgw->store_uptime_timer, hnbgw_store_hnb_uptime, g_hnbgw);
osmo_timer_schedule(&g_hnbgw->store_uptime_timer, STORE_UPTIME_INTERVAL, 0); osmo_timer_schedule(&g_hnbgw->store_uptime_timer, STORE_UPTIME_INTERVAL, 0);

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
/* hnb-gw specific code for HNBAP, 3GPP TS 25.469 */ /* hnb-gw specific code for HNBAP */
/* (C) 2015 by Harald Welte <laforge@gnumonks.org> /* (C) 2015 by Harald Welte <laforge@gnumonks.org>
* All Rights Reserved * All Rights Reserved
@@ -33,8 +33,6 @@
#include <osmocom/hnbap/hnbap_common.h> #include <osmocom/hnbap/hnbap_common.h>
#include <osmocom/ranap/iu_helpers.h> #include <osmocom/ranap/iu_helpers.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnb_persistent.h>
#include <osmocom/hnbgw/hnbgw.h> #include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbap/hnbap_ies_defs.h> #include <osmocom/hnbap/hnbap_ies_defs.h>
@@ -173,7 +171,7 @@ static int hnbgw_tx_hnb_register_acc(struct hnb_context *ctx)
} }
static int hnbgw_tx_ue_register_acc(struct hnb_context *hnb, const char *imsi, uint32_t context_id) static int hnbgw_tx_ue_register_acc(struct ue_context *ue)
{ {
HNBAP_UERegisterAccept_t accept_out; HNBAP_UERegisterAccept_t accept_out;
HNBAP_UERegisterAcceptIEs_t accept; HNBAP_UERegisterAcceptIEs_t accept;
@@ -184,20 +182,18 @@ static int hnbgw_tx_ue_register_acc(struct hnb_context *hnb, const char *imsi, u
int rc; int rc;
encoded_imsi_len = ranap_imsi_encode(encoded_imsi, encoded_imsi_len = ranap_imsi_encode(encoded_imsi,
sizeof(encoded_imsi), imsi); sizeof(encoded_imsi), ue->imsi);
memset(&accept, 0, sizeof(accept)); memset(&accept, 0, sizeof(accept));
accept.uE_Identity.present = HNBAP_UE_Identity_PR_iMSI; accept.uE_Identity.present = HNBAP_UE_Identity_PR_iMSI;
OCTET_STRING_fromBuf(&accept.uE_Identity.choice.iMSI, OCTET_STRING_fromBuf(&accept.uE_Identity.choice.iMSI,
(const char *)encoded_imsi, encoded_imsi_len); (const char *)encoded_imsi, encoded_imsi_len);
asn1_u24_to_bitstring(&accept.context_ID, &ctx_id, context_id); asn1_u24_to_bitstring(&accept.context_ID, &ctx_id, ue->context_id);
memset(&accept_out, 0, sizeof(accept_out)); memset(&accept_out, 0, sizeof(accept_out));
rc = hnbap_encode_ueregisteraccepties(&accept_out, &accept); rc = hnbap_encode_ueregisteraccepties(&accept_out, &accept);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, &accept.uE_Identity.choice.iMSI); ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, &accept.uE_Identity.choice.iMSI);
if (rc < 0) { if (rc < 0) {
LOGHNB(hnb, DHNBAP, LOGL_ERROR,
"Failed to encode HNBAP UE Register Accept message for UE IMSI-%s\n", imsi);
return rc; return rc;
} }
@@ -208,18 +204,13 @@ static int hnbgw_tx_ue_register_acc(struct hnb_context *hnb, const char *imsi, u
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_UERegisterAccept, &accept_out); ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_UERegisterAccept, &accept_out);
rc = hnbgw_hnbap_tx(hnb, msg); return hnbgw_hnbap_tx(ue->hnb, msg);
if (rc)
LOGHNB(hnb, DHNBAP, LOGL_ERROR,
"Failed to enqueue HNBAP UE Register Accept message for UE IMSI-%s\n", imsi);
return rc;
} }
static int hnbgw_tx_ue_register_rej(struct hnb_context *hnb, HNBAP_UE_Identity_t *ue_id, const struct HNBAP_Cause *cause) static int hnbgw_tx_ue_register_rej(struct hnb_context *hnb, HNBAP_UE_Identity_t *ue_id, const struct HNBAP_Cause *cause)
{ {
HNBAP_UERegisterReject_t reject_out; HNBAP_UERegisterReject_t reject_out;
HNBAP_UERegisterRejectIEs_t reject; HNBAP_UERegisterRejectIEs_t reject;
char imsi[GSM23003_IMSI_MAX_DIGITS+1];
struct msgb *msg; struct msgb *msg;
int rc; int rc;
@@ -280,14 +271,6 @@ static int hnbgw_tx_ue_register_rej(struct hnb_context *hnb, HNBAP_UE_Identity_t
ue_id->choice.pTMSIRAI.rAI.rAC.size); ue_id->choice.pTMSIRAI.rAI.rAC.size);
break; break;
case HNBAP_UE_Identity_PR_iMSI:
ranap_bcd_decode(imsi, sizeof(imsi), ue_id->choice.iMSI.buf, ue_id->choice.iMSI.size);
LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "REJ UE_Id IMSI %s\n", imsi);
OCTET_STRING_fromBuf(&reject.uE_Identity.choice.iMSI,
(const char *)ue_id->choice.iMSI.buf, ue_id->choice.iMSI.size);
break;
default: default:
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Cannot compose UE Register Reject:" LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Cannot compose UE Register Reject:"
" unsupported UE ID (present=%d)\n", ue_id->present); " unsupported UE ID (present=%d)\n", ue_id->present);
@@ -329,9 +312,6 @@ static int hnbgw_tx_ue_register_rej(struct hnb_context *hnb, HNBAP_UE_Identity_t
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING,
&reject.uE_Identity.choice.pTMSIRAI.rAI.rAC); &reject.uE_Identity.choice.pTMSIRAI.rAI.rAC);
break; break;
case HNBAP_UE_Identity_PR_iMSI:
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING,
&reject.uE_Identity.choice.iMSI);
default: default:
/* should never happen after above switch() */ /* should never happen after above switch() */
@@ -343,13 +323,15 @@ static int hnbgw_tx_ue_register_rej(struct hnb_context *hnb, HNBAP_UE_Identity_t
return hnbgw_hnbap_tx(hnb, msg); return hnbgw_hnbap_tx(hnb, msg);
} }
static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Identity_t *ue_id, uint32_t context_id) static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Identity_t *ue_id)
{ {
HNBAP_UERegisterAccept_t accept_out; HNBAP_UERegisterAccept_t accept_out;
HNBAP_UERegisterAcceptIEs_t accept; HNBAP_UERegisterAcceptIEs_t accept;
struct msgb *msg; struct msgb *msg;
uint32_t ctx_id; uint32_t ctx_id;
uint32_t tmsi = 0; uint32_t tmsi = 0;
struct ue_context *ue;
struct ue_context *ue_allocated = NULL;
int rc; int rc;
memset(&accept, 0, sizeof(accept)); memset(&accept, 0, sizeof(accept));
@@ -395,7 +377,11 @@ static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
tmsi = ntohl(tmsi); tmsi = ntohl(tmsi);
LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "HNBAP register with TMSI %x\n", tmsi); LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "HNBAP register with TMSI %x\n", tmsi);
asn1_u24_to_bitstring(&accept.context_ID, &ctx_id, context_id); ue = ue_context_by_tmsi(tmsi);
if (!ue)
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)); memset(&accept_out, 0, sizeof(accept_out));
rc = hnbap_encode_ueregisteraccepties(&accept_out, &accept); rc = hnbap_encode_ueregisteraccepties(&accept_out, &accept);
@@ -428,8 +414,11 @@ static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
} }
if (rc < 0) { if (rc < 0) {
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Failed to encode HNBAP UE Register Accept for TMSI 0x%08x\n", tmsi);
/* Encoding failed. Nothing in 'accept_out'. */ /* Encoding failed. Nothing in 'accept_out'. */
/* 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; return rc;
} }
@@ -440,15 +429,13 @@ static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
&accept_out); &accept_out);
rc = hnbgw_hnbap_tx(hnb, msg); rc = hnbgw_hnbap_tx(hnb, msg);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_UERegisterAccept, &accept_out); ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_UERegisterAccept, &accept_out);
if (rc)
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Failed to transmit HNBAP UE Register Accept for TMSI 0x%08x\n", tmsi);
return rc; return rc;
} }
static int hnbgw_rx_hnb_deregister(struct hnb_context *ctx, ANY_t *in) static int hnbgw_rx_hnb_deregister(struct hnb_context *ctx, ANY_t *in)
{ {
HNBAP_HNBDe_RegisterIEs_t ies; HNBAP_HNBDe_RegisterIEs_t ies;
HNBAP_Cause_t cause = {}; HNBAP_Cause_t cause;
int rc; int rc;
rc = hnbap_decode_hnbde_registeries(&ies, in); rc = hnbap_decode_hnbde_registeries(&ies, in);
@@ -473,13 +460,12 @@ static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
struct hnb_context *hnb, *tmp; struct hnb_context *hnb, *tmp;
HNBAP_HNBRegisterRequestIEs_t ies; HNBAP_HNBRegisterRequestIEs_t ies;
int rc; int rc;
struct osmo_plmn_id plmn;
struct osmo_fd *ofd = osmo_stream_srv_get_ofd(ctx->conn); struct osmo_fd *ofd = osmo_stream_srv_get_ofd(ctx->conn);
char identity_str[256]; char identity_str[256];
const char *cell_id_str; const char *cell_id_str;
struct timespec tp; struct timespec tp;
HNBAP_Cause_t cause = {}; HNBAP_Cause_t cause;
struct osmo_sockaddr cur_osa = {};
socklen_t len = sizeof(cur_osa);
rc = hnbap_decode_hnbregisterrequesties(&ies, in); rc = hnbap_decode_hnbregisterrequesties(&ies, in);
if (rc < 0) { if (rc < 0) {
@@ -492,25 +478,14 @@ static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
/* copy all identity parameters from the message to ctx */ /* copy all identity parameters from the message to ctx */
OSMO_STRLCPY_ARRAY(ctx->identity_info, identity_str); OSMO_STRLCPY_ARRAY(ctx->identity_info, identity_str);
/* We want to use struct umts_cell_id as hashtable key. If it ever happens to contain any padding bytes, make
* sure everything is deterministically zero. */
memset(&ctx->id, 0, sizeof(ctx->id));
ctx->id.lac = asn1str_to_u16(&ies.lac); ctx->id.lac = asn1str_to_u16(&ies.lac);
ctx->id.sac = asn1str_to_u16(&ies.sac); ctx->id.sac = asn1str_to_u16(&ies.sac);
ctx->id.rac = asn1str_to_u8(&ies.rac); ctx->id.rac = asn1str_to_u8(&ies.rac);
ctx->id.cid = asn1bitstr_to_u28(&ies.cellIdentity); ctx->id.cid = asn1bitstr_to_u28(&ies.cellIdentity);
osmo_plmn_from_bcd(ies.plmNidentity.buf, &ctx->id.plmn); osmo_plmn_from_bcd(ies.plmNidentity.buf, &plmn);
cell_id_str = umts_cell_id_to_str(&ctx->id); ctx->id.mcc = plmn.mcc;
ctx->id.mnc = plmn.mnc;
if (getpeername(ofd->fd, &cur_osa.u.sa, &len) < 0) { cell_id_str = umts_cell_id_name(&ctx->id);
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "HNB-REGISTER-REQ %s: rejecting due to getpeername() error: %s\n",
cell_id_str, strerror(errno));
hnbap_free_hnbregisterrequesties(&ies);
cause.present = HNBAP_Cause_PR_radioNetwork;
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_hNB_parameter_mismatch;
return hnbgw_tx_hnb_register_rej(ctx, &cause);
}
hnbp = hnb_persistent_find_by_id(&ctx->id); hnbp = hnb_persistent_find_by_id(&ctx->id);
if (!hnbp && g_hnbgw->config.accept_all_hnb) if (!hnbp && g_hnbgw->config.accept_all_hnb)
@@ -523,7 +498,6 @@ static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_unauthorised_HNB; cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_unauthorised_HNB;
return hnbgw_tx_hnb_register_rej(ctx, &cause); return hnbgw_tx_hnb_register_rej(ctx, &cause);
} }
ctx->persistent = hnbp; ctx->persistent = hnbp;
hnbp->ctx = ctx; hnbp->ctx = ctx;
@@ -537,13 +511,22 @@ static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
* fault (bug), and we release the old context to keep going... */ * 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_fd *other_fd = osmo_stream_srv_get_ofd(hnb->conn);
struct osmo_sockaddr other_osa = {}; struct osmo_sockaddr other_osa = {};
struct osmo_sockaddr cur_osa = {};
socklen_t len = sizeof(other_osa); socklen_t len = sizeof(other_osa);
if (getpeername(other_fd->fd, &other_osa.u.sa, &len) < 0) { 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"); LOGHNB(ctx, DHNBAP, LOGL_ERROR, "BUG! Found old registered HNB with invalid socket, releasing it\n");
hnb_context_release(hnb); hnb_context_release(hnb);
continue; continue;
} }
if (osmo_sockaddr_cmp(&cur_osa, &other_osa) == 0) { 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"); LOGHNB(ctx, DHNBAP, LOGL_ERROR, "BUG! Found old registered HNB with same remote address, releasing it\n");
hnb_context_release(hnb); hnb_context_release(hnb);
continue; continue;
@@ -570,7 +553,7 @@ static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
ctx->hnb_registered = true; ctx->hnb_registered = true;
hnb_persistent_registered(ctx->persistent); hnb_persistent_update_addr(ctx->persistent, osmo_stream_srv_get_fd(ctx->conn));
/* Send HNBRegisterAccept */ /* Send HNBRegisterAccept */
rc = hnbgw_tx_hnb_register_acc(ctx); rc = hnbgw_tx_hnb_register_acc(ctx);
@@ -581,7 +564,9 @@ static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
static int hnbgw_rx_ue_register_req(struct hnb_context *ctx, ANY_t *in) static int hnbgw_rx_ue_register_req(struct hnb_context *ctx, ANY_t *in)
{ {
HNBAP_UERegisterRequestIEs_t ies; HNBAP_UERegisterRequestIEs_t ies;
HNBAP_Cause_t cause = {}; HNBAP_Cause_t cause;
struct ue_context *ue;
struct ue_context *ue_allocated = NULL;
char imsi[GSM23003_IMSI_MAX_DIGITS+1]; char imsi[GSM23003_IMSI_MAX_DIGITS+1];
int rc; int rc;
@@ -618,7 +603,7 @@ static int hnbgw_rx_ue_register_req(struct hnb_context *ctx, ANY_t *in)
case HNBAP_UE_Identity_PR_tMSILAI: case HNBAP_UE_Identity_PR_tMSILAI:
case HNBAP_UE_Identity_PR_pTMSIRAI: case HNBAP_UE_Identity_PR_pTMSIRAI:
if (g_hnbgw->config.hnbap_allow_tmsi) { if (g_hnbgw->config.hnbap_allow_tmsi) {
rc = hnbgw_tx_ue_register_acc_tmsi(ctx, &ies.uE_Identity, get_next_ue_ctx_id()); rc = hnbgw_tx_ue_register_acc_tmsi(ctx, &ies.uE_Identity);
} else { } else {
cause.present = HNBAP_Cause_PR_radioNetwork; cause.present = HNBAP_Cause_PR_radioNetwork;
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_invalid_UE_identity; cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_invalid_UE_identity;
@@ -638,10 +623,18 @@ static int hnbgw_rx_ue_register_req(struct hnb_context *ctx, ANY_t *in)
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "UE-REGISTER-REQ ID_type=%d imsi=%s cause=%ld\n", LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "UE-REGISTER-REQ ID_type=%d imsi=%s cause=%ld\n",
ies.uE_Identity.present, imsi, ies.registration_Cause); ies.uE_Identity.present, imsi, ies.registration_Cause);
ue = ue_context_by_imsi(imsi);
if (!ue)
ue = ue_allocated = ue_context_alloc(ctx, imsi, 0);
/* Send UERegisterAccept */ /* Send UERegisterAccept */
rc = hnbgw_tx_ue_register_acc(ctx, imsi, get_next_ue_ctx_id()); rc = hnbgw_tx_ue_register_acc(ue);
if (rc < 0) if (rc < 0) {
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failed to transmit HNBAP UE Register Accept for IMSI %s\n", imsi); /* 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);
}
free_and_return_rc: free_and_return_rc:
hnbap_free_ueregisterrequesties(&ies); hnbap_free_ueregisterrequesties(&ies);
return rc; return rc;
@@ -650,7 +643,8 @@ free_and_return_rc:
static int hnbgw_rx_ue_deregister(struct hnb_context *ctx, ANY_t *in) static int hnbgw_rx_ue_deregister(struct hnb_context *ctx, ANY_t *in)
{ {
HNBAP_UEDe_RegisterIEs_t ies; HNBAP_UEDe_RegisterIEs_t ies;
HNBAP_Cause_t cause = {}; HNBAP_Cause_t cause;
struct ue_context *ue;
int rc; int rc;
uint32_t ctxid; uint32_t ctxid;
@@ -673,6 +667,9 @@ static int hnbgw_rx_ue_deregister(struct hnb_context *ctx, ANY_t *in)
HNBAP_Criticality_ignore, HNBAP_TriggeringMessage_initiating_message); HNBAP_Criticality_ignore, HNBAP_TriggeringMessage_initiating_message);
} else { } else {
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "UE-DE-REGISTER context=%u cause=%s\n", ctxid, hnbap_cause_str(&ies.cause)); LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "UE-DE-REGISTER context=%u cause=%s\n", ctxid, hnbap_cause_str(&ies.cause));
ue = ue_context_by_id(ctxid);
if (ue)
ue_context_free(ue);
} }
hnbap_free_uede_registeries(&ies); hnbap_free_uede_registeries(&ies);
@@ -745,7 +742,8 @@ static int hnbgw_rx_unsuccessful_outcome_msg(struct hnb_context *hnb, HNBAP_Unsu
{ {
/* We don't care much about HNBAP */ /* We don't care much about HNBAP */
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Received Unsuccessful Outcome, procedureCode %ld, criticality %ld," LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Received Unsuccessful Outcome, procedureCode %ld, criticality %ld,"
" cell %s\n", msg->procedureCode, msg->criticality, umts_cell_id_to_str(&hnb->id)); " cell mcc %u mnc %u lac %u rac %u sac %u cid %u\n", msg->procedureCode, msg->criticality,
hnb->id.mcc, hnb->id.mnc, hnb->id.lac, hnb->id.rac, hnb->id.sac, hnb->id.cid);
return 0; return 0;
} }

View File

@@ -28,7 +28,7 @@
#include <osmocom/gsm/protocol/gsm_04_08_gprs.h> #include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
#include <osmocom/hnbgw/hnbgw.h> #include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_ranap.h> #include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbgw/context_map.h> #include <osmocom/hnbgw/context_map.h>
#include <osmocom/ranap/ranap_ies_defs.h> #include <osmocom/ranap/ranap_ies_defs.h>
@@ -50,8 +50,10 @@ static const struct tlv_definition gsm48_gmm_att_tlvdef = {
}, },
}; };
static void decode_gmm_tlv(struct osmo_mobile_identity *mi, int *nri, static void decode_gmm_tlv(struct osmo_mobile_identity *mi,
const uint8_t *tlv_data, size_t tlv_len, bool allow_hex) struct osmo_routing_area_id *old_ra,
int *nri,
const uint8_t *tlv_data, size_t tlv_len, bool allow_hex)
{ {
struct tlv_parsed tp; struct tlv_parsed tp;
struct tlv_p_entry *e; struct tlv_p_entry *e;
@@ -64,14 +66,8 @@ static void decode_gmm_tlv(struct osmo_mobile_identity *mi, int *nri,
*nri <<= 2; *nri <<= 2;
*nri |= e->val[1] >> 6; *nri |= e->val[1] >> 6;
} }
/* RAU Req: 9.4.14.5 P-TMSI (Iu mode only): "This IE shall be included by the MS." */
e = TLVP_GET(&tp, GSM48_IE_GMM_ALLOC_PTMSI);
if (mi && e)
osmo_mobile_identity_decode(mi, e->val, e->len, allow_hex);
} }
/* Parse 3GPP TS 24.008 § 9.4.1 Attach request */
static int mobile_identity_decode_from_gmm_att_req(struct osmo_mobile_identity *mi, static int mobile_identity_decode_from_gmm_att_req(struct osmo_mobile_identity *mi,
struct osmo_routing_area_id *old_ra, struct osmo_routing_area_id *old_ra,
int *nri, int *nri,
@@ -86,16 +82,11 @@ static int mobile_identity_decode_from_gmm_att_req(struct osmo_mobile_identity *
uint8_t ms_ra_acc_cap_len; uint8_t ms_ra_acc_cap_len;
int rc; int rc;
if (l3_len < 26)
return -ENOSPC;
/* MS network capability 10.5.5.12 */ /* MS network capability 10.5.5.12 */
msnc_len = *cur++; msnc_len = *cur++;
if (l3_len < (msnc_len + (cur - l3_data)))
return -ENOSPC;
cur += msnc_len; cur += msnc_len;
/* aTTACH Type 10.5.5.2 + Ciphering key sequence number 10.5.1.2 */ /* aTTACH Type 10.5.5.2 */
cur++; cur++;
/* DRX parameter 10.5.5.6 */ /* DRX parameter 10.5.5.6 */
@@ -104,10 +95,11 @@ static int mobile_identity_decode_from_gmm_att_req(struct osmo_mobile_identity *
/* Mobile Identity (P-TMSI or IMSI) 10.5.1.4 */ /* Mobile Identity (P-TMSI or IMSI) 10.5.1.4 */
mi_len = *cur++; mi_len = *cur++;
mi_data = cur; mi_data = cur;
if (l3_len < (mi_len + (cur - l3_data)))
return -ENOSPC;
cur += mi_len; cur += mi_len;
if (cur >= end)
return -ENOSPC;
rc = osmo_mobile_identity_decode(mi, mi_data, mi_len, allow_hex); rc = osmo_mobile_identity_decode(mi, mi_data, mi_len, allow_hex);
if (rc) if (rc)
return rc; return rc;
@@ -120,19 +112,15 @@ static int mobile_identity_decode_from_gmm_att_req(struct osmo_mobile_identity *
/* MS Radio Access Capability 10.5.5.12a */ /* MS Radio Access Capability 10.5.5.12a */
ms_ra_acc_cap_len = *cur++; ms_ra_acc_cap_len = *cur++;
if (l3_len < (ms_ra_acc_cap_len + (cur - l3_data)))
return -ENOSPC;
cur += ms_ra_acc_cap_len; cur += ms_ra_acc_cap_len;
if (l3_len == (cur - l3_data)) if (cur > end)
return 0; /* No Optional TLV section */ return -ENOSPC;
/* Mobile identity = NULL: already obtained from Mandatory IE above.*/ decode_gmm_tlv(mi, old_ra, nri, cur, end - cur, allow_hex);
decode_gmm_tlv(NULL, nri, cur, end - cur, allow_hex);
return 0; return 0;
} }
/* Parse 24.008 9.4.14 RAU Request */
static int mobile_identity_decode_from_gmm_rau_req(struct osmo_mobile_identity *mi, static int mobile_identity_decode_from_gmm_rau_req(struct osmo_mobile_identity *mi,
struct osmo_routing_area_id *old_ra, struct osmo_routing_area_id *old_ra,
int *nri, int *nri,
@@ -144,15 +132,12 @@ static int mobile_identity_decode_from_gmm_rau_req(struct osmo_mobile_identity *
uint8_t ms_ra_acc_cap_len; uint8_t ms_ra_acc_cap_len;
int rc; int rc;
/* all mandatory fields + variable length MS Radio Cap (min value) would be 15 bytes. /* Update Type 10.5.5.18 */
* But even short radio capabilities we should handle with 14 bytes */ cur++;
if (l3_len < 14) if (cur >= end)
return -ENOSPC; return -ENOSPC;
/* V: Update Type 10.5.5.18 */ /* Old routing area identification 10.5.5.15 */
cur++;
/* V: Old routing area identification 10.5.5.15 */
rc = osmo_routing_area_id_decode(old_ra, cur, end - cur); rc = osmo_routing_area_id_decode(old_ra, cur, end - cur);
if (rc < 0) if (rc < 0)
return rc; return rc;
@@ -160,46 +145,71 @@ static int mobile_identity_decode_from_gmm_rau_req(struct osmo_mobile_identity *
if (cur >= end) if (cur >= end)
return -ENOSPC; return -ENOSPC;
/* LV: MS Radio Access Capability 10.5.5.12a */ /* MS Radio Access Capability 10.5.5.12a */
ms_ra_acc_cap_len = *cur++; ms_ra_acc_cap_len = *cur++;
if (l3_len < (ms_ra_acc_cap_len + (cur - l3_data)))
return -ENOSPC;
cur += ms_ra_acc_cap_len; cur += ms_ra_acc_cap_len;
if (l3_len == (cur - l3_data)) if (cur > end)
return 0; /* No Optional TLV section */ return -ENOSPC;
decode_gmm_tlv(mi, nri, cur, end - cur, allow_hex);
decode_gmm_tlv(mi, old_ra, nri, cur, end - cur, allow_hex);
return 0; return 0;
} }
/* CS MM: Determine mobile identity, from_other_plmn, is_emerg. */ static int peek_l3_ul_nas(struct hnbgw_context_map *map, const uint8_t *nas_pdu, size_t len,
static int peek_l3_ul_nas_cs(struct hnbgw_context_map *map, const uint8_t *nas_pdu, size_t len, const struct osmo_plmn_id *local_plmn)
const struct osmo_plmn_id *local_plmn)
{ {
const struct gsm48_hdr *gh = (const struct gsm48_hdr *)nas_pdu; const struct gsm48_hdr *gh;
struct osmo_location_area_id old_lai; int8_t pdisc;
uint8_t mtype;
const struct gsm48_loc_upd_req *lu; const struct gsm48_loc_upd_req *lu;
struct gsm48_service_request *cm; struct gsm48_service_request *cm;
struct osmo_location_area_id old_lai;
struct osmo_routing_area_id old_ra = {};
int nri = -1;
osmo_mobile_identity_decode_from_l3_buf(&map->l3.mi, nas_pdu, len, false); map->l3 = (struct hnbgw_l3_peek){
.gmm_nri_container = -1,
};
switch (map->l3.gsm48_pdisc) { /* Get the mobile identity from CS MM -- the PS GMM happens further down.
* This will return an error for GMM messages, ignore that. */
if (!map->is_ps)
osmo_mobile_identity_decode_from_l3_buf(&map->l3.mi, nas_pdu, len, false);
/* Get is_emerg and from_other_plmn */
if (len < sizeof(*gh)) {
LOGP(DCN, LOGL_ERROR, "Layer 3 message too short for header\n");
return -EINVAL;
}
gh = (void *)nas_pdu;
pdisc = gsm48_hdr_pdisc(gh);
mtype = gsm48_hdr_msg_type(gh);
map->l3.gsm48_pdisc = pdisc;
map->l3.gsm48_msg_type = mtype;
/* Determine from_other_plmn and is_emerg */
switch (pdisc) {
case GSM48_PDISC_MM: case GSM48_PDISC_MM:
/* Get is_emerg and from_other_plmn */
switch (map->l3.gsm48_msg_type) { switch (mtype) {
case GSM48_MT_MM_LOC_UPD_REQUEST: case GSM48_MT_MM_LOC_UPD_REQUEST:
if (len < sizeof(*gh) + sizeof(*lu)) { if (len < sizeof(*gh) + sizeof(*lu)) {
LOGP(DCN, LOGL_ERROR, "LU Req message too short\n"); LOGP(DCN, LOGL_ERROR, "LU Req message too short\n");
break; break;
} }
lu = (struct gsm48_loc_upd_req *)gh->data; lu = (struct gsm48_loc_upd_req *)gh->data;
gsm48_decode_lai2(&lu->lai, &old_lai); gsm48_decode_lai2(&lu->lai, &old_lai);
map->l3.from_other_plmn = (osmo_plmn_cmp(&old_lai.plmn, local_plmn) != 0); map->l3.from_other_plmn = (osmo_plmn_cmp(&old_lai.plmn, local_plmn) != 0);
if (map->l3.from_other_plmn) if (map->l3.from_other_plmn)
LOGP(DRUA, LOGL_INFO, "LU from other PLMN: old LAI=%s my PLMN=%s\n", LOGP(DRUA, LOGL_INFO, "LU from other PLMN: old LAI=%s my PLMN=%s\n",
osmo_plmn_name_c(OTC_SELECT, &old_lai.plmn), osmo_plmn_name_c(OTC_SELECT, &old_lai.plmn),
osmo_plmn_name_c(OTC_SELECT, local_plmn)); osmo_plmn_name_c(OTC_SELECT, local_plmn));
return 0; break;
case GSM48_MT_MM_CM_SERV_REQ: case GSM48_MT_MM_CM_SERV_REQ:
if (len < sizeof(*gh) + sizeof(*cm)) { if (len < sizeof(*gh) + sizeof(*cm)) {
@@ -209,88 +219,64 @@ static int peek_l3_ul_nas_cs(struct hnbgw_context_map *map, const uint8_t *nas_p
cm = (struct gsm48_service_request *)&gh->data[0]; cm = (struct gsm48_service_request *)&gh->data[0];
map->l3.is_emerg = (cm->cm_service_type == GSM48_CMSERV_EMERGENCY); map->l3.is_emerg = (cm->cm_service_type == GSM48_CMSERV_EMERGENCY);
LOGP(DRUA, LOGL_DEBUG, "CM Service is_emerg=%d\n", map->l3.is_emerg); LOGP(DRUA, LOGL_DEBUG, "CM Service is_emerg=%d\n", map->l3.is_emerg);
return 0; break;
default:
break;
} }
break; break;
}
return 0;
}
/* PS GMM: Determine mobile identity, gmm_nri_container, from_other_plmn and is_emerg */
static int peek_l3_ul_nas_ps(struct hnbgw_context_map *map, const uint8_t *nas_pdu, size_t len,
const struct osmo_plmn_id *local_plmn)
{
struct osmo_routing_area_id old_ra = {};
int nri = -1;
switch (map->l3.gsm48_pdisc) {
case GSM48_PDISC_MM_GPRS: case GSM48_PDISC_MM_GPRS:
switch (map->l3.gsm48_msg_type) { switch (mtype) {
case GSM48_MT_GMM_ATTACH_REQ: case GSM48_MT_GMM_ATTACH_REQ:
mobile_identity_decode_from_gmm_att_req(&map->l3.mi, &old_ra, &nri, nas_pdu, len, false); mobile_identity_decode_from_gmm_att_req(&map->l3.mi, &old_ra, &nri, nas_pdu, len, false);
LOGP(DRUA, LOGL_DEBUG, "GMM Attach Req mi=%s old_ra=%s nri:%d=0x%x\n", LOGP(DRUA, LOGL_DEBUG, "GMM Attach Req mi=%s old_ra=%s nri:%d=0x%x\n",
osmo_mobile_identity_to_str_c(OTC_SELECT, &map->l3.mi), osmo_mobile_identity_to_str_c(OTC_SELECT, &map->l3.mi),
osmo_rai_name2_c(OTC_SELECT, &old_ra), osmo_rai_name2_c(OTC_SELECT, &old_ra),
nri, nri); nri, nri);
if (old_ra.lac.plmn.mcc && osmo_plmn_cmp(&old_ra.lac.plmn, local_plmn)) { if (old_ra.lac.plmn.mcc && osmo_plmn_cmp(&old_ra.lac.plmn, local_plmn)) {
map->l3.from_other_plmn = true; map->l3.from_other_plmn = true;
LOGP(DRUA, LOGL_INFO, "GMM Attach Req from other PLMN: old RAI=%s my PLMN=%s\n", LOGP(DRUA, LOGL_INFO, "GMM Attach Req from other PLMN: old RAI=%s my PLMN=%s\n",
osmo_rai_name2_c(OTC_SELECT, &old_ra), osmo_rai_name2_c(OTC_SELECT, &old_ra),
osmo_plmn_name_c(OTC_SELECT, local_plmn)); osmo_plmn_name_c(OTC_SELECT, local_plmn));
} }
if (nri >= 0) if (nri >= 0)
map->l3.gmm_nri_container = nri; map->l3.gmm_nri_container = nri;
return 0;
break;
case GSM48_MT_GMM_RA_UPD_REQ: case GSM48_MT_GMM_RA_UPD_REQ:
mobile_identity_decode_from_gmm_rau_req(&map->l3.mi, &old_ra, &nri, nas_pdu, len, false); mobile_identity_decode_from_gmm_rau_req(&map->l3.mi, &old_ra, &nri, nas_pdu, len, false);
LOGP(DRUA, LOGL_DEBUG, "GMM Routing Area Upd Req mi=%s old_ra=%s nri:%d=0x%x\n", LOGP(DRUA, LOGL_DEBUG, "GMM Routing Area Upd Req mi=%s old_ra=%s nri:%d=0x%x\n",
osmo_mobile_identity_to_str_c(OTC_SELECT, &map->l3.mi), osmo_mobile_identity_to_str_c(OTC_SELECT, &map->l3.mi),
osmo_rai_name2_c(OTC_SELECT, &old_ra), osmo_rai_name2_c(OTC_SELECT, &old_ra),
nri, nri); nri, nri);
if (old_ra.lac.plmn.mcc && osmo_plmn_cmp(&old_ra.lac.plmn, local_plmn)) { if (old_ra.lac.plmn.mcc && osmo_plmn_cmp(&old_ra.lac.plmn, local_plmn)) {
map->l3.from_other_plmn = true; map->l3.from_other_plmn = true;
LOGP(DRUA, LOGL_INFO, "GMM Routing Area Upd Req from other PLMN: old RAI=%s my PLMN=%s\n", LOGP(DRUA, LOGL_INFO, "GMM Routing Area Upd Req from other PLMN: old RAI=%s my PLMN=%s\n",
osmo_rai_name2_c(OTC_SELECT, &old_ra), osmo_rai_name2_c(OTC_SELECT, &old_ra),
osmo_plmn_name_c(OTC_SELECT, local_plmn)); osmo_plmn_name_c(OTC_SELECT, local_plmn));
} }
if (nri >= 0) if (nri >= 0)
map->l3.gmm_nri_container = nri; map->l3.gmm_nri_container = nri;
return 0;
break;
} }
break; break;
default:
break;
} }
return 0; return 0;
} }
static int peek_l3_ul_nas(struct hnbgw_context_map *map, const uint8_t *nas_pdu, size_t len,
const struct osmo_plmn_id *local_plmn)
{
const struct gsm48_hdr *gh = (const struct gsm48_hdr *)nas_pdu;
map->l3 = (struct hnbgw_l3_peek){
.gmm_nri_container = -1,
.mi = {
.type = GSM_MI_TYPE_NONE,
.tmsi = GSM_RESERVED_TMSI,
},
};
if (len < sizeof(*gh)) {
LOGP(DCN, LOGL_ERROR, "Layer 3 message too short for header\n");
return -EINVAL;
}
map->l3.gsm48_pdisc = gsm48_hdr_pdisc(gh);
map->l3.gsm48_msg_type = gsm48_hdr_msg_type(gh);
if (map->is_ps)
return peek_l3_ul_nas_ps(map, nas_pdu, len, local_plmn);
return peek_l3_ul_nas_cs(map, nas_pdu, len, local_plmn);
}
static int peek_l3_ul_initial_ue(struct hnbgw_context_map *map, const RANAP_InitialUE_MessageIEs_t *ies) static int peek_l3_ul_initial_ue(struct hnbgw_context_map *map, const RANAP_InitialUE_MessageIEs_t *ies)
{ {
struct osmo_plmn_id local_plmn; struct osmo_plmn_id local_plmn;
@@ -314,7 +300,7 @@ static int peek_l3_ul_initial_ue(struct hnbgw_context_map *map, const RANAP_Init
* This is relevant for CN pooling, to decide which CN link to map the RUA context to. */ * This is relevant for CN pooling, to decide which CN link to map the RUA context to. */
int hnbgw_peek_l3_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg) int hnbgw_peek_l3_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg)
{ {
ranap_message *message = hnbgw_decode_ranap_cn_co(ranap_msg); ranap_message *message = hnbgw_decode_ranap_co(ranap_msg);
if (!message) { if (!message) {
LOGP(DCN, LOGL_ERROR, "Failed to decode RANAP PDU\n"); LOGP(DCN, LOGL_ERROR, "Failed to decode RANAP PDU\n");
return -EINVAL; return -EINVAL;

View File

@@ -20,27 +20,12 @@
*/ */
#include <osmocom/core/sockaddr_str.h> #include <osmocom/core/sockaddr_str.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/stat_item.h>
#include <osmocom/pfcp/pfcp_endpoint.h> #include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/pfcp/pfcp_cp_peer.h> #include <osmocom/pfcp/pfcp_cp_peer.h>
#include <osmocom/hnbgw/hnbgw.h> #include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h> #include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/ps_rab_fsm.h> #include <osmocom/hnbgw/ps_rab_fsm.h>
#include <osmocom/hnbgw/hnbgw_pfcp.h>
static const struct osmo_stat_item_desc hnbgw_upf_stat_item_description[] = {
[HNBGW_UPF_STAT_ASSOCIATED] = { "pfcp_associated", "Associated to UPF through PFCP", OSMO_STAT_ITEM_NO_UNIT, 16, 0},
};
static const struct osmo_stat_item_group_desc hnbgw_upf_statg_desc = {
"upf",
"UPF Peer Statistics",
OSMO_STATS_CLASS_PEER,
ARRAY_SIZE(hnbgw_upf_stat_item_description),
hnbgw_upf_stat_item_description,
};
static void pfcp_set_msg_ctx(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req) static void pfcp_set_msg_ctx(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req)
{ {
@@ -77,12 +62,6 @@ static void pfcp_rx_msg(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m,
} }
} }
static void pfcp_cp_peer_assoc_cb(struct osmo_pfcp_cp_peer *cp_peer, bool associated)
{
LOGP(DLPFCP, LOGL_NOTICE, "PFCP Peer associated: %s\n", associated ? "true" : "false");
HNBGW_UPF_STAT_SET(HNBGW_UPF_STAT_ASSOCIATED, associated ? 1 : 0);
}
int hnbgw_pfcp_init(void) int hnbgw_pfcp_init(void)
{ {
struct osmo_pfcp_endpoint_cfg cfg; struct osmo_pfcp_endpoint_cfg cfg;
@@ -102,12 +81,6 @@ int hnbgw_pfcp_init(void)
return -1; return -1;
} }
g_hnbgw->pfcp.statg = osmo_stat_item_group_alloc(g_hnbgw, &hnbgw_upf_statg_desc, 0);
if (!g_hnbgw->pfcp.statg) {
LOGP(DLPFCP, LOGL_ERROR, "Failed creating UPF stats item group\n");
return -1;
}
cfg = (struct osmo_pfcp_endpoint_cfg){ cfg = (struct osmo_pfcp_endpoint_cfg){
.set_msg_ctx_cb = pfcp_set_msg_ctx, .set_msg_ctx_cb = pfcp_set_msg_ctx,
.rx_msg_cb = pfcp_rx_msg, .rx_msg_cb = pfcp_rx_msg,
@@ -163,11 +136,6 @@ int hnbgw_pfcp_init(void)
LOGP(DLPFCP, LOGL_ERROR, "Cannot allocate PFCP CP Peer FSM\n"); LOGP(DLPFCP, LOGL_ERROR, "Cannot allocate PFCP CP Peer FSM\n");
return -1; return -1;
} }
if (osmo_pfcp_cp_peer_set_associated_cb(g_hnbgw->pfcp.cp_peer, pfcp_cp_peer_assoc_cb)) {
LOGP(DLPFCP, LOGL_ERROR, "Cannot Set PFCP CP Peer associated callback\n");
return -1;
}
if (osmo_pfcp_cp_peer_associate(g_hnbgw->pfcp.cp_peer)) { if (osmo_pfcp_cp_peer_associate(g_hnbgw->pfcp.cp_peer)) {
LOGP(DLPFCP, LOGL_ERROR, "Cannot start PFCP CP Peer FSM\n"); LOGP(DLPFCP, LOGL_ERROR, "Cannot start PFCP CP Peer FSM\n");
return -1; return -1;
@@ -175,10 +143,3 @@ int hnbgw_pfcp_init(void)
return 0; return 0;
} }
void hnbgw_pfcp_release(void)
{
if (!hnb_gw_is_gtp_mapping_enabled())
return;
osmo_stat_item_group_free(g_hnbgw->pfcp.statg);
}

View File

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

View File

@@ -1,4 +1,4 @@
/* hnb-gw specific code for RUA (Ranap User Adaption), 3GPP TS 25.468 */ /* hnb-gw specific code for RUA (Ranap User Adaption) */
/* (C) 2015 by Harald Welte <laforge@gnumonks.org> /* (C) 2015 by Harald Welte <laforge@gnumonks.org>
* All Rights Reserved * All Rights Reserved
@@ -33,8 +33,6 @@
#include "asn1helpers.h" #include "asn1helpers.h"
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnb_persistent.h>
#include <osmocom/hnbgw/hnbgw_cn.h> #include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/hnbgw_ranap.h> #include <osmocom/hnbgw/hnbgw_ranap.h>
#include <osmocom/rua/rua_common.h> #include <osmocom/rua/rua_common.h>
@@ -42,6 +40,7 @@
#include <osmocom/hnbgw/context_map.h> #include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/hnbgw_rua.h> #include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbap/HNBAP_CN-DomainIndicator.h> #include <osmocom/hnbap/HNBAP_CN-DomainIndicator.h>
#include <osmocom/ranap/ranap_ies_defs.h>
static int hnbgw_rua_tx(struct hnb_context *ctx, struct msgb *msg) static int hnbgw_rua_tx(struct hnb_context *ctx, struct msgb *msg)
{ {
@@ -167,20 +166,6 @@ int rua_tx_disc(struct hnb_context *hnb, int is_ps, uint32_t context_id,
return hnbgw_rua_tx(hnb, msg); return hnbgw_rua_tx(hnb, msg);
} }
/* Send Disconnect to RUA without RANAP data */
static void rua_tx_disc_conn_fail(struct hnb_context *hnb, bool is_ps, uint32_t context_id)
{
RUA_Cause_t rua_cause = {
.present = RUA_Cause_PR_radioNetwork,
.choice.radioNetwork = RUA_CauseRadioNetwork_connect_failed,
};
LOG_HNBP(hnb->persistent, LOGL_INFO, "Tx RUA Disconnect\n");
if (rua_tx_disc(hnb, is_ps, context_id, &rua_cause, NULL, 0))
LOG_HNBP(hnb->persistent, LOGL_ERROR, "Failed to send Disconnect to RUA\n");
}
static struct value_string rua_procedure_code_names[] = { static struct value_string rua_procedure_code_names[] = {
{ RUA_ProcedureCode_id_Connect, "Connect" }, { RUA_ProcedureCode_id_Connect, "Connect" },
{ RUA_ProcedureCode_id_DirectTransfer, "DirectTransfer" }, { RUA_ProcedureCode_id_DirectTransfer, "DirectTransfer" },
@@ -243,6 +228,7 @@ static int rua_to_scu(struct hnb_context *hnb,
struct msgb *ranap_msg = NULL; struct msgb *ranap_msg = NULL;
struct hnbgw_context_map *map = NULL; struct hnbgw_context_map *map = NULL;
bool is_ps; bool is_ps;
int logl;
switch (cN_DomainIndicator) { switch (cN_DomainIndicator) {
case RUA_CN_DomainIndicator_cs_domain: case RUA_CN_DomainIndicator_cs_domain:
@@ -267,13 +253,14 @@ static int rua_to_scu(struct hnb_context *hnb,
} }
map = context_map_find_by_rua_ctx_id(hnb, context_id, is_ps); map = context_map_find_by_rua_ctx_id(hnb, context_id, is_ps);
logl = LOGL_ERROR;
switch (rua_procedure) { switch (rua_procedure) {
case RUA_ProcedureCode_id_Connect: case RUA_ProcedureCode_id_Connect:
/* A Connect message can only be the first message for an unused RUA context */ /* A Connect message can only be the first message for an unused RUA context */
if (map) { if (map) {
/* Already established this RUA context. But then how can it be a Connect message. */ /* Already established this RUA context. But then how can it be a Connect message. */
LOGHNB(hnb, DRUA, LOGL_NOTICE, "rx RUA %s for already active RUA context %u\n", LOGHNB(hnb, DRUA, LOGL_ERROR, "rx RUA %s for already active RUA context %u\n",
rua_procedure_code_name(rua_procedure), context_id); rua_procedure_code_name(rua_procedure), context_id);
return -EINVAL; return -EINVAL;
} }
@@ -283,7 +270,6 @@ static int rua_to_scu(struct hnb_context *hnb,
LOGHNB(hnb, DRUA, LOGL_ERROR, LOGHNB(hnb, DRUA, LOGL_ERROR,
"Failed to create context map for %s: rx RUA %s with %u bytes RANAP data\n", "Failed to create context map for %s: rx RUA %s with %u bytes RANAP data\n",
is_ps ? "IuPS" : "IuCS", rua_procedure_code_name(rua_procedure), data ? len : 0); is_ps ? "IuPS" : "IuCS", rua_procedure_code_name(rua_procedure), data ? len : 0);
rua_tx_disc_conn_fail(hnb, is_ps, context_id);
return -EINVAL; return -EINVAL;
} }
break; break;
@@ -292,19 +278,13 @@ static int rua_to_scu(struct hnb_context *hnb,
/* For RUA Disconnect, do not spam the ERROR log. It is just a stray Disconnect, no harm done. /* For RUA Disconnect, do not spam the ERROR log. It is just a stray Disconnect, no harm done.
* Context: some CN are known to rapidly tear down SCCP without waiting for RUA to disconnect gracefully * Context: some CN are known to rapidly tear down SCCP without waiting for RUA to disconnect gracefully
* (IU Release Complete). Such CN would cause ERROR logging for each and every released context map. */ * (IU Release Complete). Such CN would cause ERROR logging for each and every released context map. */
if (!map) { logl = LOGL_DEBUG;
LOGHNB(hnb, DRUA, LOGL_DEBUG, "rx RUA %s for unknown RUA context %u\n", /* fall thru */
rua_procedure_code_name(rua_procedure), context_id);
return -EINVAL;
}
break;
default: default:
/* Any message other than Connect must have a valid RUA context */ /* Any message other than Connect must have a valid RUA context */
if (!map) { if (!map) {
LOGHNB(hnb, DRUA, LOGL_NOTICE, "rx RUA %s for unknown RUA context %u\n", LOGHNB(hnb, DRUA, logl, "rx RUA %s for unknown RUA context %u\n",
rua_procedure_code_name(rua_procedure), context_id); rua_procedure_code_name(rua_procedure), context_id);
rua_tx_disc_conn_fail(hnb, is_ps, context_id);
return -EINVAL; return -EINVAL;
} }
break; break;

View File

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

View File

@@ -31,8 +31,6 @@
#include <osmocom/hnbgw/vty.h> #include <osmocom/hnbgw/vty.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnb_persistent.h>
#include <osmocom/hnbgw/hnbgw.h> #include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_cn.h> #include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h> #include <osmocom/hnbgw/context_map.h>
@@ -86,7 +84,7 @@ DEFUN(cfg_hnbgw_iucs, cfg_hnbgw_iucs_cmd,
"iucs", "Configure IuCS options") "iucs", "Configure IuCS options")
{ {
vty->node = IUCS_NODE; vty->node = IUCS_NODE;
vty->index = g_hnbgw->sccp.cnpool_iucs; vty->index = &g_hnbgw->sccp.cnpool_iucs;
return CMD_SUCCESS; return CMD_SUCCESS;
} }
@@ -100,7 +98,7 @@ DEFUN(cfg_hnbgw_iups, cfg_hnbgw_iups_cmd,
"iups", "Configure IuPS options") "iups", "Configure IuPS options")
{ {
vty->node = IUPS_NODE; vty->node = IUPS_NODE;
vty->index = g_hnbgw->sccp.cnpool_iups; vty->index = &g_hnbgw->sccp.cnpool_iups;
return CMD_SUCCESS; return CMD_SUCCESS;
} }
@@ -149,13 +147,12 @@ static void _show_cnlink(struct vty *vty, struct hnbgw_cnlink *cnlink)
return; return;
} }
vty_out(vty, "%s: %s <->", vty_out(vty, "%s <->",
cnlink->name,
osmo_sccp_user_name(cnlink->hnbgw_sccp_user->sccp_user)); osmo_sccp_user_name(cnlink->hnbgw_sccp_user->sccp_user));
vty_out(vty, " %s%s%s%s", vty_out(vty, " %s%s%s%s",
cnlink->use.remote_addr_name ? : "", cnlink->use.remote_addr_name ? : "",
cnlink->use.remote_addr_name ? "=" : "", cnlink->use.remote_addr_name ? "=" : "",
hnbgw_cnlink_sccp_addr_to_str(cnlink, &cnlink->remote_addr), cnlink_sccp_addr_to_str(cnlink, &cnlink->remote_addr),
VTY_NEWLINE); VTY_NEWLINE);
rt = osmo_ss7_route_lookup(ss7, cnlink->remote_addr.pc); rt = osmo_ss7_route_lookup(ss7, cnlink->remote_addr.pc);
@@ -168,10 +165,10 @@ DEFUN(show_cnlink, show_cnlink_cmd, "show cnlink",
{ {
struct hnbgw_cnlink *cnlink; struct hnbgw_cnlink *cnlink;
vty_out(vty, "IuCS: "); vty_out(vty, "IuCS: ");
llist_for_each_entry(cnlink, &g_hnbgw->sccp.cnpool_iucs->cnlinks, entry) llist_for_each_entry(cnlink, &g_hnbgw->sccp.cnpool_iucs.cnlinks, entry)
_show_cnlink(vty, cnlink); _show_cnlink(vty, cnlink);
vty_out(vty, "IuPS: "); vty_out(vty, "IuPS: ");
llist_for_each_entry(cnlink, &g_hnbgw->sccp.cnpool_iups->cnlinks, entry) llist_for_each_entry(cnlink, &g_hnbgw->sccp.cnpool_iups.cnlinks, entry)
_show_cnlink(vty, cnlink); _show_cnlink(vty, cnlink);
return CMD_SUCCESS; return CMD_SUCCESS;
} }
@@ -213,9 +210,8 @@ static void vty_dump_hnb_info(struct vty *vty, struct hnb_context *hnb)
vty_out(vty, "HNB "); vty_out(vty, "HNB ");
vty_out_ofd_addr(vty, hnb->conn? osmo_stream_srv_get_ofd(hnb->conn) : NULL); vty_out_ofd_addr(vty, hnb->conn? osmo_stream_srv_get_ofd(hnb->conn) : NULL);
vty_out(vty, " \"%s\"%s", hnb->identity_info, VTY_NEWLINE); vty_out(vty, " \"%s\"%s", hnb->identity_info, VTY_NEWLINE);
vty_out(vty, " MCC %s MNC %s LAC %u RAC %u SAC %u CID %u SCTP-stream:HNBAP=%u,RUA=%u%s", vty_out(vty, " MCC %u MNC %u LAC %u RAC %u SAC %u CID %u SCTP-stream:HNBAP=%u,RUA=%u%s",
osmo_mcc_name(hnb->id.plmn.mcc), osmo_mnc_name(hnb->id.plmn.mnc, hnb->id.plmn.mnc_3_digits), hnb->id.mcc, hnb->id.mnc, hnb->id.lac, hnb->id.rac, hnb->id.sac, hnb->id.cid,
hnb->id.lac, hnb->id.rac, hnb->id.sac, hnb->id.cid,
hnb->hnbap_stream, hnb->rua_stream, VTY_NEWLINE); hnb->hnbap_stream, hnb->rua_stream, VTY_NEWLINE);
llist_for_each_entry(map, &hnb->map_list, hnb_list) { llist_for_each_entry(map, &hnb->map_list, hnb_list) {
@@ -232,6 +228,12 @@ static void vty_dump_hnb_info(struct vty *vty, struct hnb_context *hnb)
} }
} }
static void vty_dump_ue_info(struct vty *vty, struct ue_context *ue)
{
vty_out(vty, "UE IMSI \"%s\" context ID %u HNB %s%s", ue->imsi, ue->context_id,
hnb_context_name(ue->hnb), VTY_NEWLINE);
}
#define SHOW_HNB_STR SHOW_STR "Display information about HNB\n" #define SHOW_HNB_STR SHOW_STR "Display information about HNB\n"
DEFUN(show_hnb, show_hnb_cmd, "show hnb all", DEFUN(show_hnb, show_hnb_cmd, "show hnb all",
@@ -276,6 +278,18 @@ DEFUN(show_one_hnb, show_one_hnb_cmd, "show hnb NAME ",
return CMD_SUCCESS; return CMD_SUCCESS;
} }
DEFUN(show_ue, show_ue_cmd, "show ue all",
SHOW_STR "Display HNBAP information about UE\n" "All UE\n")
{
struct ue_context *ue;
llist_for_each_entry(ue, &g_hnbgw->ue_list, list) {
vty_dump_ue_info(vty, ue);
}
return CMD_SUCCESS;
}
DEFUN(show_talloc, show_talloc_cmd, "show talloc", SHOW_STR "Display talloc info") DEFUN(show_talloc, show_talloc_cmd, "show talloc", SHOW_STR "Display talloc info")
{ {
talloc_report_full(g_hnbgw, stderr); talloc_report_full(g_hnbgw, stderr);
@@ -337,7 +351,7 @@ DEFUN(cfg_hnbgw_iuh_hnbap_allow_tmsi, cfg_hnbgw_iuh_hnbap_allow_tmsi_cmd,
"hnbap-allow-tmsi (0|1)", "hnbap-allow-tmsi (0|1)",
"Allow HNBAP UE Register messages with TMSI or PTMSI identity\n" "Allow HNBAP UE Register messages with TMSI or PTMSI identity\n"
"Only accept IMSI identity, reject TMSI or PTMSI\n" "Only accept IMSI identity, reject TMSI or PTMSI\n"
"Accept IMSI, TMSI or PTMSI as UE identity (default)\n") "Accept IMSI, TMSI or PTMSI as UE identity\n")
{ {
g_hnbgw->config.hnbap_allow_tmsi = (*argv[0] == '1'); g_hnbgw->config.hnbap_allow_tmsi = (*argv[0] == '1');
return CMD_SUCCESS; return CMD_SUCCESS;
@@ -512,7 +526,7 @@ DEFUN(cfg_msc_nr, cfg_msc_nr_cmd,
"Configure an IuCS link to an MSC\n" "Configure an IuCS link to an MSC\n"
"MSC nr\n") "MSC nr\n")
{ {
return cnlink_nr(vty, g_hnbgw->sccp.cnpool_iucs, argc, argv); return cnlink_nr(vty, &g_hnbgw->sccp.cnpool_iucs, argc, argv);
} }
/* 'sgsn 0' */ /* 'sgsn 0' */
@@ -521,24 +535,12 @@ DEFUN(cfg_sgsn_nr, cfg_sgsn_nr_cmd,
"Configure an IuPS link to an SGSN\n" "Configure an IuPS link to an SGSN\n"
"SGSN nr\n") "SGSN nr\n")
{ {
return cnlink_nr(vty, g_hnbgw->sccp.cnpool_iups, argc, argv); return cnlink_nr(vty, &g_hnbgw->sccp.cnpool_iups, argc, argv);
} }
/* 'msc 0' / 'remote-addr my-msc' and /* 'msc 0' / 'remote-addr my-msc' and
* 'sgsn 0' / 'remote-addr my-sgsn' * 'sgsn 0' / 'remote-addr my-sgsn'
*/ */
DEFUN(cfg_cnlink_name,
cfg_cnlink_name_cmd,
"name NAME",
"Set user defined name for this msc/sgsn\n"
"The user defined name to be set for this msc/sgsn\n")
{
struct hnbgw_cnlink *cnlink = vty->index;
if (hnbgw_cnlink_set_name(cnlink, argv[0]) < 0)
return CMD_WARNING;
return CMD_SUCCESS;
}
DEFUN(cfg_cnlink_remote_addr, DEFUN(cfg_cnlink_remote_addr,
cfg_cnlink_remote_addr_cmd, cfg_cnlink_remote_addr_cmd,
"remote-addr NAME", "remote-addr NAME",
@@ -729,14 +731,14 @@ DEFUN(show_nri, show_nri_cmd,
* nri null add ... * nri null add ...
*/ */
vty_out(vty, "hnbgw%s", VTY_NEWLINE); vty_out(vty, "hnbgw%s", VTY_NEWLINE);
cnpool_write_nri(vty, g_hnbgw->sccp.cnpool_iucs, true); cnpool_write_nri(vty, &g_hnbgw->sccp.cnpool_iucs, true);
cnpool_write_nri(vty, g_hnbgw->sccp.cnpool_iups, true); cnpool_write_nri(vty, &g_hnbgw->sccp.cnpool_iups, true);
/* msc 0 /* msc 0
* nri add ... * nri add ...
*/ */
cnlinks_write_nri(vty, g_hnbgw->sccp.cnpool_iucs, true); cnlinks_write_nri(vty, &g_hnbgw->sccp.cnpool_iucs, true);
cnlinks_write_nri(vty, g_hnbgw->sccp.cnpool_iups, true); cnlinks_write_nri(vty, &g_hnbgw->sccp.cnpool_iups, true);
return CMD_SUCCESS; return CMD_SUCCESS;
} }
@@ -752,9 +754,9 @@ DEFUN_HIDDEN(cnpool_roundrobin_next, cnpool_roundrobin_next_cmd,
{ {
struct hnbgw_cnpool *cnpool; struct hnbgw_cnpool *cnpool;
if (!strcmp("msc", argv[0])) if (!strcmp("msc", argv[0]))
cnpool = g_hnbgw->sccp.cnpool_iucs; cnpool = &g_hnbgw->sccp.cnpool_iucs;
else else
cnpool = g_hnbgw->sccp.cnpool_iups; cnpool = &g_hnbgw->sccp.cnpool_iups;
cnpool->round_robin_next_nr = atoi(argv[1]); cnpool->round_robin_next_nr = atoi(argv[1]);
return CMD_SUCCESS; return CMD_SUCCESS;
} }
@@ -773,9 +775,9 @@ DEFUN(cnlink_ranap_reset, cnlink_ranap_reset_cmd,
int nr = atoi(argv[1]); int nr = atoi(argv[1]);
if (!strcmp("msc", msc_sgsn)) if (!strcmp("msc", msc_sgsn))
cnpool = g_hnbgw->sccp.cnpool_iucs; cnpool = &g_hnbgw->sccp.cnpool_iucs;
else else
cnpool = g_hnbgw->sccp.cnpool_iups; cnpool = &g_hnbgw->sccp.cnpool_iups;
cnlink = cnlink_get_nr(cnpool, nr, false); cnlink = cnlink_get_nr(cnpool, nr, false);
if (!cnlink) { if (!cnlink) {
@@ -814,11 +816,11 @@ DEFUN(cfg_config_apply_sccp, cfg_config_apply_sccp_cmd,
{ {
struct hnbgw_cnpool *cnpool; struct hnbgw_cnpool *cnpool;
cnpool = g_hnbgw->sccp.cnpool_iucs; cnpool = &g_hnbgw->sccp.cnpool_iucs;
hnbgw_cnpool_apply_cfg(cnpool); hnbgw_cnpool_apply_cfg(cnpool);
hnbgw_cnpool_cnlinks_start_or_restart(cnpool); hnbgw_cnpool_cnlinks_start_or_restart(cnpool);
cnpool = g_hnbgw->sccp.cnpool_iups; cnpool = &g_hnbgw->sccp.cnpool_iups;
hnbgw_cnpool_apply_cfg(cnpool); hnbgw_cnpool_apply_cfg(cnpool);
hnbgw_cnpool_cnlinks_start_or_restart(cnpool); hnbgw_cnpool_cnlinks_start_or_restart(cnpool);
@@ -877,36 +879,6 @@ DEFUN(cfg_hnbgw_no_hnb, cfg_hnbgw_no_hnb_cmd,
return CMD_SUCCESS; return CMD_SUCCESS;
} }
#define NFT_KPI_STR "Retrieve traffic counters from nftables\n"
DEFUN(cfg_hnbgw_nft_kpi, cfg_hnbgw_nft_kpi_cmd,
"nft-kpi [TABLE_NAME]",
NFT_KPI_STR
"Set a custom nft table name to use, instead of 'osmo-hnbgw'\n")
{
const char *set_table_name = NULL;
if (argc > 0)
set_table_name = argv[0];
if (vty->type == VTY_TERM)
vty_out(vty, "%% WARNING: nft configuration changes need a restart of osmo-hnbgw%s", VTY_NEWLINE);
g_hnbgw->config.nft_kpi.enable = true;
osmo_talloc_replace_string(g_hnbgw, &g_hnbgw->config.nft_kpi.table_name, set_table_name);
return CMD_SUCCESS;
}
DEFUN(cfg_hnbgw_no_nft_kpi, cfg_hnbgw_no_nft_kpi_cmd,
"no nft-kpi",
NO_STR NFT_KPI_STR)
{
if (vty->type == VTY_TERM)
vty_out(vty, "%% WARNING: nft configuration changes need a restart of osmo-hnbgw%s", VTY_NEWLINE);
g_hnbgw->config.nft_kpi.enable = false;
return CMD_SUCCESS;
}
#if ENABLE_PFCP #if ENABLE_PFCP
static struct cmd_node pfcp_node = { static struct cmd_node pfcp_node = {
@@ -1027,14 +999,8 @@ static int config_write_hnbgw(struct vty *vty)
llist_for_each_entry(hnbp, &g_hnbgw->hnb_persistent_list, list) llist_for_each_entry(hnbp, &g_hnbgw->hnb_persistent_list, list)
write_one_hnbp(vty, hnbp); write_one_hnbp(vty, hnbp);
_config_write_cnpool(vty, g_hnbgw->sccp.cnpool_iucs); _config_write_cnpool(vty, &g_hnbgw->sccp.cnpool_iucs);
_config_write_cnpool(vty, g_hnbgw->sccp.cnpool_iups); _config_write_cnpool(vty, &g_hnbgw->sccp.cnpool_iups);
if (g_hnbgw->config.nft_kpi.enable)
vty_out(vty, " nft-kpi%s%s%s",
g_hnbgw->config.nft_kpi.table_name ? " " : "",
g_hnbgw->config.nft_kpi.table_name ? : "",
VTY_NEWLINE);
return CMD_SUCCESS; return CMD_SUCCESS;
} }
@@ -1054,8 +1020,8 @@ static int config_write_hnbgw_iuh(struct vty *vty)
if (port && port != IUH_DEFAULT_SCTP_PORT) if (port && port != IUH_DEFAULT_SCTP_PORT)
vty_out(vty, " local-port %u%s", port, VTY_NEWLINE); vty_out(vty, " local-port %u%s", port, VTY_NEWLINE);
if (!g_hnbgw->config.hnbap_allow_tmsi) if (g_hnbgw->config.hnbap_allow_tmsi)
vty_out(vty, " hnbap-allow-tmsi 0%s", VTY_NEWLINE); vty_out(vty, " hnbap-allow-tmsi 1%s", VTY_NEWLINE);
return CMD_SUCCESS; return CMD_SUCCESS;
} }
@@ -1070,7 +1036,6 @@ static void _config_write_cnlink(struct vty *vty, struct hnbgw_cnpool *cnpool)
llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) { llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) {
vty_out(vty, "%s %d%s", cnpool->peer_name, cnlink->nr, VTY_NEWLINE); vty_out(vty, "%s %d%s", cnpool->peer_name, cnlink->nr, VTY_NEWLINE);
vty_out(vty, " name %s%s", cnlink->name, VTY_NEWLINE);
if (cnlink->vty.remote_addr_name) if (cnlink->vty.remote_addr_name)
vty_out(vty, " remote-addr %s%s", cnlink->vty.remote_addr_name, VTY_NEWLINE); vty_out(vty, " remote-addr %s%s", cnlink->vty.remote_addr_name, VTY_NEWLINE);
cnlink_write_nri(vty, cnlink, false); cnlink_write_nri(vty, cnlink, false);
@@ -1079,13 +1044,13 @@ static void _config_write_cnlink(struct vty *vty, struct hnbgw_cnpool *cnpool)
static int config_write_msc(struct vty *vty) static int config_write_msc(struct vty *vty)
{ {
_config_write_cnlink(vty, g_hnbgw->sccp.cnpool_iucs); _config_write_cnlink(vty, &g_hnbgw->sccp.cnpool_iucs);
return CMD_SUCCESS; return CMD_SUCCESS;
} }
static int config_write_sgsn(struct vty *vty) static int config_write_sgsn(struct vty *vty)
{ {
_config_write_cnlink(vty, g_hnbgw->sccp.cnpool_iups); _config_write_cnlink(vty, &g_hnbgw->sccp.cnpool_iups);
return CMD_SUCCESS; return CMD_SUCCESS;
} }
@@ -1112,7 +1077,6 @@ static int config_write_hnbgw_pfcp(struct vty *vty)
static void install_cnlink_elements(int node) static void install_cnlink_elements(int node)
{ {
install_element(node, &cfg_cnlink_name_cmd);
install_element(node, &cfg_cnlink_remote_addr_cmd); install_element(node, &cfg_cnlink_remote_addr_cmd);
install_element(node, &cfg_cnlink_nri_add_cmd); install_element(node, &cfg_cnlink_nri_add_cmd);
install_element(node, &cfg_cnlink_nri_del_cmd); install_element(node, &cfg_cnlink_nri_del_cmd);
@@ -1162,12 +1126,10 @@ void hnbgw_vty_init(void)
install_element(HNBGW_NODE, &cfg_hnbgw_no_hnb_cmd); install_element(HNBGW_NODE, &cfg_hnbgw_no_hnb_cmd);
install_node(&hnb_node, NULL); install_node(&hnb_node, NULL);
install_element(HNBGW_NODE, &cfg_hnbgw_nft_kpi_cmd);
install_element(HNBGW_NODE, &cfg_hnbgw_no_nft_kpi_cmd);
install_element_ve(&show_cnlink_cmd); install_element_ve(&show_cnlink_cmd);
install_element_ve(&show_hnb_cmd); install_element_ve(&show_hnb_cmd);
install_element_ve(&show_one_hnb_cmd); install_element_ve(&show_one_hnb_cmd);
install_element_ve(&show_ue_cmd);
install_element_ve(&show_talloc_cmd); install_element_ve(&show_talloc_cmd);
install_element(HNBGW_NODE, &cfg_hnbgw_mgcp_cmd); install_element(HNBGW_NODE, &cfg_hnbgw_mgcp_cmd);

View File

@@ -1,112 +0,0 @@
/* KPI (statistics, counters) at DTAP level */
/* (C) 2024 by Harald Welte <laforge@osmocom.org>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <osmocom/core/utils.h>
#include <osmocom/ranap/ranap_common_ran.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
#include <osmocom/hnbgw/hnb_persistent.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/kpi.h>
/***********************************************************************
* DOWNLINK messages
***********************************************************************/
void kpi_dtap_process_dl(struct hnbgw_context_map *map, const uint8_t *buf, unsigned int len,
uint8_t sapi)
{
struct hnb_persistent *hnbp = map->hnb_ctx->persistent;
const struct gsm48_hdr *gh = (const struct gsm48_hdr *)buf;
if (len < sizeof(*gh))
return;
/* if you make use of any data beyond the fixed-size gsm48_hdr, you must make sure the underlying
* buffer length is actually long enough! */
if (map->is_ps) {
/* Packet Switched Domain (from SGSN) */
switch (gsm48_hdr_msg_type(gh)) {
case GSM48_MT_GMM_ATTACH_ACK:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_ATT_ACK);
break;
case GSM48_MT_GMM_ATTACH_REJ:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_ATT_REJ);
break;
case GSM48_MT_GMM_RA_UPD_ACK:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_RAU_ACK);
break;
case GSM48_MT_GMM_RA_UPD_REJ:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_RAU_REJ);
break;
}
} else {
/* Circuit Switched Domain (from MSC) */
switch (gsm48_hdr_msg_type(gh)) {
case GSM48_MT_MM_LOC_UPD_ACCEPT:
/* FIXME: many LU are acknwoeldged implicitly with TMSI allocation */
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_CS_LU_ACC);
break;
case GSM48_MT_MM_LOC_UPD_REJECT:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_CS_LU_REJ);
break;
}
}
}
/***********************************************************************
* UPLINK messages
***********************************************************************/
void kpi_dtap_process_ul(struct hnbgw_context_map *map, const uint8_t *buf, unsigned int len,
uint8_t sapi)
{
struct hnb_persistent *hnbp = map->hnb_ctx->persistent;
const struct gsm48_hdr *gh = (const struct gsm48_hdr *)buf;
if (len < sizeof(*gh))
return;
/* if you make use of any data beyond the fixed-size gsm48_hdr, you must make sure the underlying
* buffer length is actually long enough! */
if (map->is_ps) {
/* Packet Switched Domain (to SGSN) */
switch (gsm48_hdr_msg_type(gh)) {
case GSM48_MT_GMM_ATTACH_REQ:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_ATT_REQ);
break;
case GSM48_MT_GMM_RA_UPD_REQ:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_RAU_REQ);
break;
}
} else {
/* Circuit Switched Domain (to MSC) */
switch (gsm48_hdr_msg_type(gh)) {
case GSM48_MT_MM_LOC_UPD_REQUEST:
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_CS_LU_REQ);
break;
}
}
}

View File

@@ -24,18 +24,10 @@
#include <osmocom/ranap/ranap_common_ran.h> #include <osmocom/ranap/ranap_common_ran.h>
#include <osmocom/hnbgw/hnb_persistent.h>
#include <osmocom/hnbgw/hnbgw_cn.h> #include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h> #include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/kpi.h> #include <osmocom/hnbgw/kpi.h>
const struct value_string hnbgw_rab_state_names[] = {
{ RAB_STATE_INACTIVE, "INACTIVE" },
{ RAB_STATE_ACT_REQ, "ACT_REQ" },
{ RAB_STATE_ACTIVE, "ACTIVE" },
{ RAB_STATE_REL_REQ, "REL_REQ" },
{}
};
/*********************************************************************** /***********************************************************************
* DOWNLINK messages * DOWNLINK messages
@@ -44,24 +36,14 @@ const struct value_string hnbgw_rab_state_names[] = {
static void kpi_ranap_process_dl_iu_rel_cmd(struct hnbgw_context_map *map, const ranap_message *ranap) static void kpi_ranap_process_dl_iu_rel_cmd(struct hnbgw_context_map *map, const ranap_message *ranap)
{ {
struct hnb_persistent *hnbp = map->hnb_ctx->persistent; struct hnb_persistent *hnbp = map->hnb_ctx->persistent;
const RANAP_Cause_t *cause;
OSMO_ASSERT(ranap->procedureCode == RANAP_ProcedureCode_id_Iu_Release); OSMO_ASSERT(ranap->procedureCode == RANAP_ProcedureCode_id_Iu_Release);
cause = &ranap->msg.iu_ReleaseCommandIEs.cause;
/* When Iu is released, all RABs are released implicitly */ /* When Iu is released, all RABs are released implicitly */
for (unsigned int i = 0; i < ARRAY_SIZE(map->rab_state); i++) { for (unsigned int i = 0; i < ARRAY_SIZE(map->rab_state); i++) {
switch (map->rab_state[i]) { switch (map->rab_state[i]) {
case RAB_STATE_ACTIVE: case RAB_STATE_ACTIVE:
if (cause->present == RANAP_Cause_PR_nAS || HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT : HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT);
cause->choice.nAS == RANAP_CauseNAS_normal_release) {
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT :
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT);
} else {
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT_ABNORMAL :
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT_ABNORMAL);
}
break; break;
} }
} }
@@ -106,20 +88,15 @@ static void kpi_ranap_process_dl_rab_ass_req(struct hnbgw_context_map *map, rana
* new, it is a setup */ * new, it is a setup */
switch (map->rab_state[rab_id]) { switch (map->rab_state[rab_id]) {
case RAB_STATE_ACTIVE: case RAB_STATE_ACTIVE:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_REQ : HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_REQ : HNB_CTR_RANAP_CS_RAB_MOD_REQ);
HNB_CTR_RANAP_CS_RAB_MOD_REQ);
break; break;
case RAB_STATE_INACTIVE: case RAB_STATE_INACTIVE:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_REQ : HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_REQ : HNB_CTR_RANAP_CS_RAB_ACT_REQ);
HNB_CTR_RANAP_CS_RAB_ACT_REQ);
map->rab_state[rab_id] = RAB_STATE_ACT_REQ; map->rab_state[rab_id] = RAB_STATE_ACT_REQ;
break; break;
default: default:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_REQ_UNEXP :
HNB_CTR_RANAP_CS_RAB_ACT_REQ_UNEXP);
LOG_MAP(map, DRANAP, LOGL_NOTICE, LOG_MAP(map, DRANAP, LOGL_NOTICE,
"Unexpected RAB Activation/Modification Req for RAB in state %s\n", "Unexpected RAB Activation/Modification Req for RAB in state %u\n", map->rab_state[rab_id]);
hnbgw_rab_state_name(map->rab_state[rab_id]));
break; break;
} }
@@ -129,6 +106,10 @@ static void kpi_ranap_process_dl_rab_ass_req(struct hnbgw_context_map *map, rana
if (ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_RELEASELIST_PRESENT) { if (ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_RELEASELIST_PRESENT) {
RANAP_RAB_ReleaseList_t *r_list = &ies->raB_ReleaseList; RANAP_RAB_ReleaseList_t *r_list = &ies->raB_ReleaseList;
/* increment number of released RABs, we don't need to do that individually during iteration */
HNBP_CTR_ADD(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_REQ : HNB_CTR_RANAP_CS_RAB_REL_REQ,
r_list->raB_ReleaseList_ies.list.count);
for (unsigned int i = 0; i < r_list->raB_ReleaseList_ies.list.count; i++) { for (unsigned int i = 0; i < r_list->raB_ReleaseList_ies.list.count; i++) {
RANAP_IE_t *release_list_ie = r_list->raB_ReleaseList_ies.list.array[i]; RANAP_IE_t *release_list_ie = r_list->raB_ReleaseList_ies.list.array[i];
RANAP_RAB_ReleaseItemIEs_t _rab_rel_item_ies = {}; RANAP_RAB_ReleaseItemIEs_t _rab_rel_item_ies = {};
@@ -152,21 +133,10 @@ static void kpi_ranap_process_dl_rab_ass_req(struct hnbgw_context_map *map, rana
switch (map->rab_state[rab_id]) { switch (map->rab_state[rab_id]) {
case RAB_STATE_ACTIVE: case RAB_STATE_ACTIVE:
if (rab_rel_item->cause.present == RANAP_Cause_PR_nAS &&
rab_rel_item->cause.choice.nAS == RANAP_CauseNAS_normal_release) {
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_REQ :
HNB_CTR_RANAP_CS_RAB_REL_REQ);
} else {
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_REQ_ABNORMAL :
HNB_CTR_RANAP_CS_RAB_REL_REQ_ABNORMAL);
}
break; break;
default: default:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_REQ_UNEXP :
HNB_CTR_RANAP_CS_RAB_REL_REQ_UNEXP);
LOG_MAP(map, DRANAP, LOGL_NOTICE, LOG_MAP(map, DRANAP, LOGL_NOTICE,
"Unexpected RAB Release Req in state %s\n", "Unexpected RAB Release Req in state %u\n", map->rab_state[rab_id]);
hnbgw_rab_state_name(map->rab_state[rab_id]));
break; break;
} }
/* mark that RAB as release requested */ /* mark that RAB as release requested */
@@ -177,27 +147,8 @@ static void kpi_ranap_process_dl_rab_ass_req(struct hnbgw_context_map *map, rana
} }
} }
static void kpi_ranap_process_dl_direct_transfer(struct hnbgw_context_map *map, ranap_message *ranap)
{
const RANAP_DirectTransferIEs_t *dt_ies = &ranap->msg.directTransferIEs;
uint8_t sapi = 0;
if (dt_ies->presenceMask & DIRECTTRANSFERIES_RANAP_SAPI_PRESENT) {
if (dt_ies->sapi == RANAP_SAPI_sapi_3)
sapi = 3;
}
kpi_dtap_process_dl(map, dt_ies->nas_pdu.buf, dt_ies->nas_pdu.size, sapi);
}
void kpi_ranap_process_dl(struct hnbgw_context_map *map, ranap_message *ranap) void kpi_ranap_process_dl(struct hnbgw_context_map *map, ranap_message *ranap)
{ {
if (map->hnb_ctx == NULL) {
/* This can happen if the HNB has disconnected and we are processing downlink messages
* from the CN which were already in flight before the CN side has realized the HNB
* is gone. */
return;
}
switch (ranap->procedureCode) { switch (ranap->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment: /* RAB ASSIGNMENT REQ (8.2) */ case RANAP_ProcedureCode_id_RAB_Assignment: /* RAB ASSIGNMENT REQ (8.2) */
kpi_ranap_process_dl_rab_ass_req(map, ranap); kpi_ranap_process_dl_rab_ass_req(map, ranap);
@@ -205,9 +156,6 @@ void kpi_ranap_process_dl(struct hnbgw_context_map *map, ranap_message *ranap)
case RANAP_ProcedureCode_id_Iu_Release: case RANAP_ProcedureCode_id_Iu_Release:
kpi_ranap_process_dl_iu_rel_cmd(map, ranap); /* IU RELEASE CMD (8.5) */ kpi_ranap_process_dl_iu_rel_cmd(map, ranap); /* IU RELEASE CMD (8.5) */
break; break;
case RANAP_ProcedureCode_id_DirectTransfer:
kpi_ranap_process_dl_direct_transfer(map, ranap);
break;
default: default:
break; break;
} }
@@ -253,20 +201,15 @@ static void kpi_ranap_process_ul_rab_ass_resp(struct hnbgw_context_map *map, ran
/* differentiate modify / activate */ /* differentiate modify / activate */
switch (map->rab_state[rab_id]) { switch (map->rab_state[rab_id]) {
case RAB_STATE_ACT_REQ: case RAB_STATE_ACT_REQ:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_CNF : HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_CNF : HNB_CTR_RANAP_CS_RAB_ACT_CNF);
HNB_CTR_RANAP_CS_RAB_ACT_CNF);
map->rab_state[rab_id] = RAB_STATE_ACTIVE; map->rab_state[rab_id] = RAB_STATE_ACTIVE;
break; break;
case RAB_STATE_ACTIVE: case RAB_STATE_ACTIVE:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_CNF : HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_CNF : HNB_CTR_RANAP_CS_RAB_MOD_CNF);
HNB_CTR_RANAP_CS_RAB_MOD_CNF);
break; break;
default: default:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_CNF_UNEXP :
HNB_CTR_RANAP_CS_RAB_ACT_CNF_UNEXP);
LOG_MAP(map, DRANAP, LOGL_NOTICE, LOG_MAP(map, DRANAP, LOGL_NOTICE,
"Unexpected RAB Activation/Modification Conf for RAB in state %s\n", "Unexpected RAB Activation/Modification Conf for RAB in state %u\n", map->rab_state[rab_id]);
hnbgw_rab_state_name(map->rab_state[rab_id]));
break; break;
} }
@@ -277,6 +220,8 @@ static void kpi_ranap_process_ul_rab_ass_resp(struct hnbgw_context_map *map, ran
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_RELEASEDLIST_PRESENT) { if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_RELEASEDLIST_PRESENT) {
RANAP_RAB_ReleasedList_t *r_list = &ies->raB_ReleasedList; RANAP_RAB_ReleasedList_t *r_list = &ies->raB_ReleasedList;
/* increment number of released RABs, we don't need to do that individually during iteration */ /* increment number of released RABs, we don't need to do that individually during iteration */
HNBP_CTR_ADD(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_CNF : HNB_CTR_RANAP_CS_RAB_REL_CNF,
r_list->raB_ReleasedList_ies.list.count);
for (unsigned int i = 0; i < r_list->raB_ReleasedList_ies.list.count; i++) { for (unsigned int i = 0; i < r_list->raB_ReleasedList_ies.list.count; i++) {
RANAP_IE_t *released_list_ie = r_list->raB_ReleasedList_ies.list.array[i]; RANAP_IE_t *released_list_ie = r_list->raB_ReleasedList_ies.list.array[i];
RANAP_RAB_ReleasedItemIEs_t _rab_rel_item_ies = {}; RANAP_RAB_ReleasedItemIEs_t _rab_rel_item_ies = {};
@@ -300,15 +245,10 @@ static void kpi_ranap_process_ul_rab_ass_resp(struct hnbgw_context_map *map, ran
switch (map->rab_state[rab_id]) { switch (map->rab_state[rab_id]) {
case RAB_STATE_REL_REQ: case RAB_STATE_REL_REQ:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_CNF :
HNB_CTR_RANAP_CS_RAB_REL_CNF);
break; break;
default: default:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_CNF_UNEXP :
HNB_CTR_RANAP_CS_RAB_REL_CNF_UNEXP);
LOG_MAP(map, DRANAP, LOGL_NOTICE, LOG_MAP(map, DRANAP, LOGL_NOTICE,
"Unexpected RAB Release Conf for RAB in state %s\n", "Unexpected RAB Release Conf for RAB in state %u\n", map->rab_state[rab_id]);
hnbgw_rab_state_name(map->rab_state[rab_id]));
break; break;
} }
/* mark that RAB as released */ /* mark that RAB as released */
@@ -347,21 +287,16 @@ static void kpi_ranap_process_ul_rab_ass_resp(struct hnbgw_context_map *map, ran
/* differentiate modify / activate */ /* differentiate modify / activate */
switch (map->rab_state[rab_id]) { switch (map->rab_state[rab_id]) {
case RAB_STATE_ACT_REQ: case RAB_STATE_ACT_REQ:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_FAIL : HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_FAIL : HNB_CTR_RANAP_CS_RAB_ACT_FAIL);
HNB_CTR_RANAP_CS_RAB_ACT_FAIL);
map->rab_state[rab_id] = RAB_STATE_INACTIVE; map->rab_state[rab_id] = RAB_STATE_INACTIVE;
break; break;
case RAB_STATE_ACTIVE: case RAB_STATE_ACTIVE:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_FAIL : HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_FAIL : HNB_CTR_RANAP_CS_RAB_MOD_FAIL);
HNB_CTR_RANAP_CS_RAB_MOD_FAIL);
// FIXME: does it remain active after modification failure? // FIXME: does it remain active after modification failure?
break; break;
default: default:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_FAIL_UNEXP :
HNB_CTR_RANAP_CS_RAB_ACT_FAIL_UNEXP);
LOG_MAP(map, DRANAP, LOGL_NOTICE, LOG_MAP(map, DRANAP, LOGL_NOTICE,
"Unexpected RAB Activation/Modification Failed for RAB in state %s\n", "Unexpected RAB Activation/Modification Failed for RAB in state %u\n", map->rab_state[rab_id]);
hnbgw_rab_state_name(map->rab_state[rab_id]));
break; break;
} }
@@ -372,6 +307,8 @@ static void kpi_ranap_process_ul_rab_ass_resp(struct hnbgw_context_map *map, ran
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_RELEASEFAILEDLIST_PRESENT) { if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_RELEASEFAILEDLIST_PRESENT) {
RANAP_RAB_ReleaseFailedList_t *rf_list = &ies->raB_ReleaseFailedList; RANAP_RAB_ReleaseFailedList_t *rf_list = &ies->raB_ReleaseFailedList;
/* increment number of released RABs, we don't need to do that individually during iteration */ /* increment number of released RABs, we don't need to do that individually during iteration */
HNBP_CTR_ADD(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_FAIL : HNB_CTR_RANAP_CS_RAB_REL_FAIL,
rf_list->raB_FailedList_ies.list.count);
for (unsigned int i = 0; i < rf_list->raB_FailedList_ies.list.count; i++) { for (unsigned int i = 0; i < rf_list->raB_FailedList_ies.list.count; i++) {
RANAP_IE_t *failed_list_ie = rf_list->raB_FailedList_ies.list.array[i]; RANAP_IE_t *failed_list_ie = rf_list->raB_FailedList_ies.list.array[i];
RANAP_RAB_FailedItemIEs_t _rab_failed_item_ies = {}; RANAP_RAB_FailedItemIEs_t _rab_failed_item_ies = {};
@@ -396,21 +333,16 @@ static void kpi_ranap_process_ul_rab_ass_resp(struct hnbgw_context_map *map, ran
/* differentiate modify / activate */ /* differentiate modify / activate */
switch (map->rab_state[rab_id]) { switch (map->rab_state[rab_id]) {
case RAB_STATE_ACT_REQ: case RAB_STATE_ACT_REQ:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_FAIL : HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_FAIL : HNB_CTR_RANAP_CS_RAB_ACT_FAIL);
HNB_CTR_RANAP_CS_RAB_REL_FAIL);
map->rab_state[rab_id] = RAB_STATE_INACTIVE; map->rab_state[rab_id] = RAB_STATE_INACTIVE;
break; break;
case RAB_STATE_ACTIVE: case RAB_STATE_ACTIVE:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_FAIL : HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_FAIL : HNB_CTR_RANAP_CS_RAB_MOD_FAIL);
HNB_CTR_RANAP_CS_RAB_REL_FAIL);
// FIXME: does it remain active after modification failure? // FIXME: does it remain active after modification failure?
break; break;
default: default:
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_FAIL_UNEXP :
HNB_CTR_RANAP_CS_RAB_REL_FAIL_UNEXP);
LOG_MAP(map, DRANAP, LOGL_NOTICE, LOG_MAP(map, DRANAP, LOGL_NOTICE,
"Unexpected RAB Release Failed for RAB in state %s\n", "Unexpected RAB Release Failed for RAB in state %u\n", map->rab_state[rab_id]);
hnbgw_rab_state_name(map->rab_state[rab_id]));
break; break;
} }
@@ -420,29 +352,8 @@ static void kpi_ranap_process_ul_rab_ass_resp(struct hnbgw_context_map *map, ran
} }
} }
static void kpi_ranap_process_ul_initial_ue(struct hnbgw_context_map *map, ranap_message *ranap)
{
const RANAP_InitialUE_MessageIEs_t *iue_ies = &ranap->msg.initialUE_MessageIEs;
kpi_dtap_process_ul(map, iue_ies->nas_pdu.buf, iue_ies->nas_pdu.size, 0);
}
static void kpi_ranap_process_ul_direct_transfer(struct hnbgw_context_map *map, ranap_message *ranap)
{
const RANAP_DirectTransferIEs_t *dt_ies = &ranap->msg.directTransferIEs;
uint8_t sapi = 0;
if (dt_ies->presenceMask & DIRECTTRANSFERIES_RANAP_SAPI_PRESENT) {
if (dt_ies->sapi == RANAP_SAPI_sapi_3)
sapi = 3;
}
kpi_dtap_process_ul(map, dt_ies->nas_pdu.buf, dt_ies->nas_pdu.size, sapi);
}
void kpi_ranap_process_ul(struct hnbgw_context_map *map, ranap_message *ranap) void kpi_ranap_process_ul(struct hnbgw_context_map *map, ranap_message *ranap)
{ {
/* we should never be processing uplink messages from a non-existant HNB */
OSMO_ASSERT(map->hnb_ctx);
switch (ranap->procedureCode) { switch (ranap->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment: /* RAB ASSIGNMENT REQ (8.2) */ case RANAP_ProcedureCode_id_RAB_Assignment: /* RAB ASSIGNMENT REQ (8.2) */
kpi_ranap_process_ul_rab_ass_resp(map, ranap); kpi_ranap_process_ul_rab_ass_resp(map, ranap);
@@ -453,12 +364,6 @@ void kpi_ranap_process_ul(struct hnbgw_context_map *map, ranap_message *ranap)
* processing of the downlink Iu Release Command. It's not like the RNC/HNB has any way to * processing of the downlink Iu Release Command. It's not like the RNC/HNB has any way to
* refuse the release anyway. */ * refuse the release anyway. */
break; break;
case RANAP_ProcedureCode_id_InitialUE_Message:
kpi_ranap_process_ul_initial_ue(map, ranap);
break;
case RANAP_ProcedureCode_id_DirectTransfer:
kpi_ranap_process_ul_direct_transfer(map, ranap);
break;
default: default:
break; break;
} }

View File

@@ -175,7 +175,7 @@ static void mgw_fsm_crcx_hnb_onenter(struct osmo_fsm_inst *fi, uint32_t prev_sta
mgw_info = (struct mgcp_conn_peer) { mgw_info = (struct mgcp_conn_peer) {
.call_id = (map->rua_ctx_id << 8) | mgw_fsm_priv->rab_id, .call_id = (map->rua_ctx_id << 8) | mgw_fsm_priv->rab_id,
.ptime = 20, .ptime = 20,
.conn_mode = MGCP_CONN_RECV_ONLY, .conn_mode = MGCP_CONN_LOOPBACK,
}; };
mgw_info.codecs[0] = CODEC_IUFP; mgw_info.codecs[0] = CODEC_IUFP;
mgw_info.codecs_len = 1; mgw_info.codecs_len = 1;
@@ -846,9 +846,9 @@ int handle_cs_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg,
* into account under the assumption that voice calls typically require a single RAB only. Nevertheless, we * 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. */ * 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) { if (ranap_rab_ass_req_ies_get_count(&message->msg.raB_AssignmentRequestIEs) != 1) {
LOG_MAP(map, DMGW, LOGL_ERROR, LOGP(DMGW, LOGL_ERROR,
"%s() RAB-AssignmentRequest with more than one RAB assignment -- abort!\n", "%s() rua_ctx_id=%d, RAB-AssignmentRequest with more than one RAB assignment -- abort!\n",
__func__); __func__, map->rua_ctx_id);
tx_release_req(map); tx_release_req(map);
return -1; return -1;
} }
@@ -886,9 +886,9 @@ int mgw_fsm_handle_cs_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *r
/* NOTE: This situation is a corner-case. We may end up here when the co-located MGW caused a problem /* 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. */ * on the way between RANAP RAB Assignment Request and RANAP RAB Assignment Response. */
LOG_MAP(map, DMGW, LOGL_ERROR, LOGP(DMGW, LOGL_ERROR,
"%s() no MGW fsm -- sending Iu-Release-Request!\n", "%s() rua_ctx_id=%d, no MGW fsm -- sending Iu-Release-Request!\n",
__func__); __func__, map->rua_ctx_id);
/* Send a release request, to make sure that the MSC is aware of the problem. */ /* Send a release request, to make sure that the MSC is aware of the problem. */
tx_release_req(map); tx_release_req(map);

File diff suppressed because it is too large Load Diff

View File

@@ -45,7 +45,6 @@
#include <osmocom/ranap/ranap_common.h> #include <osmocom/ranap/ranap_common.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnbgw.h> #include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_cn.h> #include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/hnbgw_pfcp.h> #include <osmocom/hnbgw/hnbgw_pfcp.h>
@@ -223,7 +222,6 @@ int main(int argc, char **argv)
rc = osmo_init_logging2(g_hnbgw, &hnbgw_log_info); rc = osmo_init_logging2(g_hnbgw, &hnbgw_log_info);
if (rc < 0) if (rc < 0)
exit(1); exit(1);
log_enable_multithread();
osmo_stats_init(g_hnbgw); osmo_stats_init(g_hnbgw);
rc = rate_ctr_init(g_hnbgw); rc = rate_ctr_init(g_hnbgw);
@@ -330,23 +328,17 @@ int main(int argc, char **argv)
/* If UPF is configured, set up PFCP socket and send Association Setup Request to UPF */ /* If UPF is configured, set up PFCP socket and send Association Setup Request to UPF */
hnbgw_pfcp_init(); hnbgw_pfcp_init();
#endif #endif
#if ENABLE_NFTABLES
/* If nftables is enabled, initialize the nft table now or fail startup. This is important to immediately let /* If nftables is enabled, initialize the nft table now or fail startup. This is important to immediately let
* the user know if cap_net_admin privileges are missing, and not only when the first hNodeB connects. */ * the user know if cap_net_admin privileges are missing, and not only when the first hNodeB connects. */
if (g_hnbgw->config.nft_kpi.enable) { if (nft_kpi_init()) {
#if ENABLE_NFTABLES perror("Failed to initialize nft KPI, probably missing cap_net_admin");
nft_kpi_init(g_hnbgw->config.nft_kpi.table_name);
/* There is no direct error handling here, because nftables initialization happens asynchronously.
* See nft_kpi.c nft_thread_t2m_cb(), case NFT_THREAD_INIT_TABLE to see what happens when initializing
* nftables failed. */
#else
fprintf(stderr, "ERROR: Cannot enable nft KPI, this binary was built without nftables support\n");
exit(1); exit(1);
#endif
} }
#endif
hnbgw_cnpool_start(g_hnbgw->sccp.cnpool_iucs); hnbgw_cnpool_start(&g_hnbgw->sccp.cnpool_iucs);
hnbgw_cnpool_start(g_hnbgw->sccp.cnpool_iups); hnbgw_cnpool_start(&g_hnbgw->sccp.cnpool_iups);
if (hnbgw_cmdline_config.daemonize) { if (hnbgw_cmdline_config.daemonize) {
rc = osmo_daemonize(); rc = osmo_daemonize();
@@ -361,8 +353,6 @@ int main(int argc, char **argv)
signal(SIGUSR2, &signal_handler); signal(SIGUSR2, &signal_handler);
osmo_init_ignore_signals(); osmo_init_ignore_signals();
osmo_fsm_set_dealloc_ctx(OTC_SELECT);
while (1) { while (1) {
rc = osmo_select_main_ctx(0); rc = osmo_select_main_ctx(0);
if (rc < 0) if (rc < 0)
@@ -370,8 +360,5 @@ int main(int argc, char **argv)
} }
/* not reached */ /* not reached */
#if ENABLE_PFCP
hnbgw_pfcp_release();
#endif
exit(0); exit(0);
} }

View File

@@ -61,7 +61,6 @@ 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_LOCAL_F_TEIDS_RX),
OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_ASS_RESP), 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_ESTABLISHED),
OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_RELEASED),
OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_FAIL), OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_FAIL),
{} {}
}; };
@@ -82,10 +81,10 @@ enum ps_rab_ass_state {
* This structure manages the RAB Assignment procedures, and the currently set up RABs: * This structure manages the RAB Assignment procedures, and the currently set up RABs:
* *
* - hnbgw_context_map * - hnbgw_context_map
* - .ps_rab_ass_list: list of PS RAB Assignment procedures * - .ps_rab_ass: list of PS RAB Assignment procedures
* - ps_rab_ass_fsm: one RANAP PS RAB Assignment procedure * - ps_rab_ass_fsm: one RANAP PS RAB Assignment procedure
* - ... * - ...
* - .ps_rab_list: list of individual PS RABs * - .ps_rabs: list of individual PS RABs
* - ps_rab_fsm: one GTP mapping with PFCP session to the UPF, for a single RAB * - ps_rab_fsm: one GTP mapping with PFCP session to the UPF, for a single RAB
* - ... * - ...
* *
@@ -112,9 +111,6 @@ struct ps_rab_ass {
* ps_rab_fsms, without iterating the RAB Assignment message every time (minor optimisation). */ * ps_rab_fsms, without iterating the RAB Assignment message every time (minor optimisation). */
int rabs_count; int rabs_count;
int rabs_done_count; int rabs_done_count;
unsigned int rabs_rel_count;
unsigned int rabs_rel_done_count;
}; };
struct osmo_tdef_state_timeout ps_rab_ass_fsm_timeouts[32] = { struct osmo_tdef_state_timeout ps_rab_ass_fsm_timeouts[32] = {
@@ -122,7 +118,7 @@ struct osmo_tdef_state_timeout ps_rab_ass_fsm_timeouts[32] = {
/* PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED is terminated by PFCP timeouts via ps_rab_fsm */ /* PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED is terminated by PFCP timeouts via ps_rab_fsm */
}; };
#define ps_rab_ass_fsm_state_chg(fi, state) \ #define ps_rab_ass_fsm_state_chg(state) \
osmo_tdef_fsm_inst_state_chg(fi, state, ps_rab_ass_fsm_timeouts, hnbgw_T_defs, -1) osmo_tdef_fsm_inst_state_chg(fi, state, ps_rab_ass_fsm_timeouts, hnbgw_T_defs, -1)
static struct osmo_fsm ps_rab_ass_fsm; static struct osmo_fsm ps_rab_ass_fsm;
@@ -144,7 +140,7 @@ static struct ps_rab_ass *ps_rab_ass_alloc(struct hnbgw_context_map *map)
}; };
fi->priv = rab_ass; fi->priv = rab_ass;
llist_add_tail(&rab_ass->entry, &map->ps_rab_ass_list); llist_add_tail(&rab_ass->entry, &map->ps_rab_ass);
return rab_ass; return rab_ass;
} }
@@ -223,9 +219,12 @@ int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct msgb *rana
{ {
RANAP_RAB_AssignmentRequestIEs_t *ies = &message->msg.raB_AssignmentRequestIEs; RANAP_RAB_AssignmentRequestIEs_t *ies = &message->msg.raB_AssignmentRequestIEs;
int i; int i;
struct ps_rab_ass *rab_ass; struct ps_rab_ass *rab_ass;
struct osmo_fsm_inst *fi;
rab_ass = ps_rab_ass_alloc(map); rab_ass = ps_rab_ass_alloc(map);
talloc_steal(rab_ass, message); talloc_steal(rab_ass, message);
rab_ass->ranap_rab_ass_req_message = message; rab_ass->ranap_rab_ass_req_message = message;
/* Now rab_ass owns message and will clean it up */ /* Now rab_ass owns message and will clean it up */
@@ -235,39 +234,33 @@ int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct msgb *rana
goto no_rab; goto no_rab;
} }
/* setup-or-modify list */ /* Make sure we indeed deal with a setup-or-modify list */
if (ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT) { if (!(ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT)) {
/* Multiple RABs may be set up, assemble in list map->ps_rab_list. */ LOG_MAP(map, DLPFCP, LOGL_ERROR, "RANAP PS RAB AssignmentRequest lacks setup-or-modify list\n");
for (i = 0; i < ies->raB_SetupOrModifyList.list.count; i++) { goto no_rab;
RANAP_ProtocolIE_ContainerPair_t *protocol_ie_container_pair;
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
protocol_ie_container_pair = ies->raB_SetupOrModifyList.list.array[i];
protocol_ie_field_pair = protocol_ie_container_pair->list.array[0];
if (!protocol_ie_field_pair)
goto no_rab;
if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem)
goto no_rab;
if (ps_rab_setup_core_remote(rab_ass, protocol_ie_field_pair))
goto no_rab;
}
} }
/* Note: ReleaseList is handled at RAB Assign Response time. */ /* 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;
if (rab_ass->rabs_count > 0) { protocol_ie_container_pair = ies->raB_SetupOrModifyList.list.array[i];
/* Got all RABs' state and their Core side GTP info in map->ps_rab_list. protocol_ie_field_pair = protocol_ie_container_pair->list.array[0];
* For each, a ps_rab_fsm has been started and each will call back with if (!protocol_ie_field_pair)
* PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX or PS_RAB_ASS_EV_RAB_FAIL. */ goto no_rab;
return ps_rab_ass_fsm_state_chg(rab_ass->fi, PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS); 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;
} }
/* No Setup-or-modify, only Release RABs. Forward the message as is, /* Got all RABs' state and their Core side GTP info in map->ps_rabs. For each, a ps_rab_fsm has been started and
* RABs will be released at RAB ASS Response time. */ * each will call back with PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX or PS_RAB_ASS_EV_RAB_FAIL. */
map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, ranap_msg); fi = rab_ass->fi;
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL); return ps_rab_ass_fsm_state_chg(PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS);
return 0;
no_rab: no_rab:
ps_rab_ass_failure(rab_ass); ps_rab_ass_failure(rab_ass);
return -1; return -1;
@@ -301,7 +294,7 @@ static void ps_rab_ass_fsm_wait_local_f_teids(struct osmo_fsm_inst *fi, uint32_t
/* See whether all information is in so that we can forward the modified RAB Assignment Request to RUA. */ /* 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) static void ps_rab_ass_req_check_local_f_teids(struct ps_rab_ass *rab_ass)
{ {
struct ps_rab *rab = NULL; struct ps_rab *rab;
RANAP_RAB_AssignmentRequestIEs_t *ies = &rab_ass->ranap_rab_ass_req_message->msg.raB_AssignmentRequestIEs; RANAP_RAB_AssignmentRequestIEs_t *ies = &rab_ass->ranap_rab_ass_req_message->msg.raB_AssignmentRequestIEs;
int i; int i;
struct msgb *msg; struct msgb *msg;
@@ -353,12 +346,6 @@ continue_cleanloop:
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, &first); ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, &first);
} }
if (!rab) {
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Lookup PS RAB Assignment Request failed\n");
ps_rab_ass_failure(rab_ass);
return;
}
/* Send the modified RAB Assignment Request to the hNodeB, wait for the RAB Assignment Response */ /* Send the modified RAB Assignment Request to the hNodeB, wait for the RAB Assignment Response */
msg = ranap_rab_ass_req_encode(ies); msg = ranap_rab_ass_req_encode(ies);
if (!msg) { if (!msg) {
@@ -420,127 +407,70 @@ int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ran
* each other, we are able to handle mismatching RAB IDs in request and response messages. * each other, we are able to handle mismatching RAB IDs in request and response messages.
*/ */
RANAP_RAB_AssignmentResponseIEs_t *ies = &message->msg.raB_AssignmentResponseIEs;
int rc; int rc;
int i; int i;
struct ps_rab_ass *rab_ass; struct ps_rab_ass *rab_ass;
struct osmo_fsm_inst *fi;
RANAP_RAB_AssignmentResponseIEs_t *ies;
/* 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 = ps_rab_ass_alloc(map);
talloc_steal(rab_ass, message); talloc_steal(rab_ass, message);
rab_ass->ranap_rab_ass_resp_message = message; rab_ass->ranap_rab_ass_resp_message = message;
talloc_steal(rab_ass, ranap_msg); talloc_steal(rab_ass, ranap_msg);
rab_ass->ranap_rab_ass_resp_msgb = ranap_msg; rab_ass->ranap_rab_ass_resp_msgb = ranap_msg;
/* Now rab_ass owns message and will clean it up */ /* Now rab_ass owns message and will clean it up */
if (!osmo_pfcp_cp_peer_is_associated(g_hnbgw->pfcp.cp_peer)) { if (!osmo_pfcp_cp_peer_is_associated(g_hnbgw->pfcp.cp_peer)) {
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "PFCP is not associated, cannot set up GTP mapping\n"); LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "PFCP is not associated, cannot set up GTP mapping\n");
goto no_rab; ps_rab_ass_failure(rab_ass);
return -1;
} }
/* setup-or-modify list */ LOG_PS_RAB_ASS(rab_ass, LOGL_NOTICE, "PS RAB-AssignmentResponse received, updating RABs\n");
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_SETUPORMODIFIEDLIST_PRESENT) {
LOG_PS_RAB_ASS(rab_ass, LOGL_INFO, "PS RAB-AssignmentResponse received, updating RABs\n"); /* Multiple RABs may be set up, bump matching FSMs in list 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;
/* Multiple RABs may be set up, bump matching FSMs in list map->ps_rab_list. */ list_ie = ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.array[i];
for (i = 0; i < ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.count; i++) { if (!list_ie)
RANAP_IE_t *list_ie; continue;
RANAP_RAB_SetupOrModifiedItemIEs_t item_ies;
list_ie = ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.array[i]; rc = ranap_decode_rab_setupormodifieditemies_fromlist(&item_ies,
if (!list_ie) &list_ie->value);
continue; 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;
}
rc = ranap_decode_rab_setupormodifieditemies_fromlist(&item_ies, if (ps_rab_setup_access_remote(rab_ass, &item_ies.raB_SetupOrModifiedItem))
&list_ie->value); LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Failed to apply PS RAB-AssignmentResponse"
if (rc < 0) { " SetupOrModifiedItemIEs with list index %d\n", i);
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Failed to decode PS RAB-AssignmentResponse" rab_ass->rabs_count++;
" 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: continue_cleanloop:
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies); ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies);
}
} }
/* release list */ /* Got all RABs' state and updated their Access side GTP info in map->ps_rabs. For each RAB ID, the matching
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_RELEASEDLIST_PRESENT) { * ps_rab_fsm has been instructed to tell the UPF about the Access Remote GTP F-TEID. Each will call back with
/* Multiple RABs may be set up, assemble in list map->ps_rab_list. */ * PS_RAB_ASS_EV_RAB_ESTABLISHED or PS_RAB_ASS_EV_RAB_FAIL. */
RANAP_RAB_ReleasedList_t *r_list = &ies->raB_ReleasedList; fi = rab_ass->fi;
for (i = 0; i < r_list->raB_ReleasedList_ies.list.count; i++) { return ps_rab_ass_fsm_state_chg(PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED);
RANAP_IE_t *released_list_ie = r_list->raB_ReleasedList_ies.list.array[i];
RANAP_RAB_ReleasedItemIEs_t _rab_rel_item_ies = {};
RANAP_RAB_ReleasedItemIEs_t *rab_rel_item_ies = &_rab_rel_item_ies;
RANAP_RAB_ReleasedItem_t *rab_rel_item;
uint8_t rab_id;
struct ps_rab *rab;
if (!released_list_ie)
goto no_rab;
if (released_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_ReleasedItem)
goto no_rab;
rc = ranap_decode_rab_releaseditemies_fromlist(rab_rel_item_ies, &released_list_ie->value);
if (rc < 0) {
LOG_MAP(map, DLPFCP, LOGL_NOTICE, "Rx RAB ASS REQ (REL): Failed decoding ReleaseItem %u\n", i);
goto no_rab;
}
rab_rel_item = &rab_rel_item_ies->raB_ReleasedItem;
/* The RAB-ID is defined as a bitstring with a size of 8 (1 byte),
* See also RANAP-IEs.asn, RAB-ID ::= BIT STRING (SIZE (8)) */
rab_id = rab_rel_item->rAB_ID.buf[0];
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_ReleasedItem, rab_rel_item_ies);
rab = ps_rab_get(map, rab_id);
if (rab) {
rab_ass->rabs_rel_count++;
ps_rab_release(rab, rab_ass->fi);
} else {
LOG_MAP(map, DLPFCP, LOGL_NOTICE, "Rx RAB ASS REQ (REL) for unknown rab_id %u\n", rab_id);
}
}
}
if (rab_ass->rabs_count > 0 || rab_ass->rabs_rel_count > 0) {
/* Got all RABs' state and updated their Access side GTP info in map->ps_rab_list.
* For each RAB ID, the matching ps_rab_fsm has been instructed to tell the UPF about
* the Access Remote GTP F-TEID. Each will call back with PS_RAB_ASS_EV_RAB_ESTABLISHED
* or PS_RAB_ASS_EV_RAB_FAIL. */
return ps_rab_ass_fsm_state_chg(rab_ass->fi, PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED);
}
/* No Setup-or-modify nor Release RABs. Forward the message as is. */
rc = map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg);
if (rc < 0) {
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Sending RANAP PS RAB-AssignmentResponse failed\n");
goto no_rab;
}
/* The request message has been forwarded. We are done. */
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
return 0;
no_rab:
ps_rab_ass_failure(rab_ass);
return -1;
} }
static void ps_rab_ass_resp_send_if_ready(struct ps_rab_ass *rab_ass); static void ps_rab_ass_resp_send_if_ready(struct ps_rab_ass *rab_ass);
static void ps_rab_ass_resp_send_if_ready_quick(struct ps_rab_ass *rab_ass)
{
/* some RABs are still pending, postpone going through the message until all are done. */
if (rab_ass->rabs_done_count < rab_ass->rabs_count)
return;
if (rab_ass->rabs_rel_done_count < rab_ass->rabs_rel_count)
return;
ps_rab_ass_resp_send_if_ready(rab_ass);
}
static void ps_rab_ass_fsm_wait_rabs_established(struct osmo_fsm_inst *fi, uint32_t event, void *data) static void ps_rab_ass_fsm_wait_rabs_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{ {
@@ -549,13 +479,12 @@ static void ps_rab_ass_fsm_wait_rabs_established(struct osmo_fsm_inst *fi, uint3
switch (event) { switch (event) {
case PS_RAB_ASS_EV_RAB_ESTABLISHED: case PS_RAB_ASS_EV_RAB_ESTABLISHED:
rab_ass->rabs_done_count++; 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 */ /* All RABs have succeeded, ready to forward */
ps_rab_ass_resp_send_if_ready_quick(rab_ass); ps_rab_ass_resp_send_if_ready(rab_ass);
return;
case PS_RAB_ASS_EV_RAB_RELEASED:
rab_ass->rabs_rel_done_count++;
ps_rab_ass_resp_send_if_ready_quick(rab_ass);
return; return;
case PS_RAB_ASS_EV_RAB_FAIL: case PS_RAB_ASS_EV_RAB_FAIL:
@@ -666,14 +595,14 @@ continue_cleanloop:
return; return;
} }
LOG_PS_RAB_ASS(rab_ass, LOGL_INFO, "Sending RANAP PS RAB-AssignmentResponse with mapped GTP info\n"); LOG_PS_RAB_ASS(rab_ass, LOGL_NOTICE, "Sending RANAP PS RAB-AssignmentResponse with mapped GTP info\n");
rc = map_sccp_dispatch(rab_ass->map, MAP_SCCP_EV_TX_DATA_REQUEST, msg); rc = map_sccp_dispatch(rab_ass->map, MAP_SCCP_EV_TX_DATA_REQUEST, msg);
if (rc < 0) { if (rc < 0) {
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Sending RANAP PS RAB-AssignmentResponse failed\n"); LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Sending RANAP PS RAB-AssignmentResponse failed\n");
ps_rab_ass_failure(rab_ass); ps_rab_ass_failure(rab_ass);
return;
} }
/* The request message has been forwarded. We are done. */ /* The request message has been forwarded. We are done. */
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL); osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
} }
@@ -683,14 +612,14 @@ static void ps_rab_ass_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_
struct ps_rab_ass *rab_ass = fi->priv; struct ps_rab_ass *rab_ass = fi->priv;
struct ps_rab *rab; struct ps_rab *rab;
llist_for_each_entry(rab, &rab_ass->map->ps_rab_list, entry) { llist_for_each_entry(rab, &rab_ass->map->ps_rabs, entry) {
if (rab->req_fi == fi) if (rab->req_fi == fi)
rab->req_fi = NULL; rab->req_fi = NULL;
if (rab->resp_fi == fi) if (rab->resp_fi == fi)
rab->resp_fi = NULL; rab->resp_fi = NULL;
} }
/* remove from map->ps_rab_ass_list */ /* remove from map->ps_rab_ass */
llist_del(&rab_ass->entry); llist_del(&rab_ass->entry);
} }
@@ -698,10 +627,10 @@ void hnbgw_gtpmap_release(struct hnbgw_context_map *map)
{ {
struct ps_rab_ass *rab_ass, *next; struct ps_rab_ass *rab_ass, *next;
struct ps_rab *rab, *next2; struct ps_rab *rab, *next2;
llist_for_each_entry_safe(rab, next2, &map->ps_rab_list, entry) { llist_for_each_entry_safe(rab, next2, &map->ps_rabs, entry) {
ps_rab_release(rab, NULL); ps_rab_release(rab);
} }
llist_for_each_entry_safe(rab_ass, next, &map->ps_rab_ass_list, entry) { 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); osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
} }
} }
@@ -729,7 +658,6 @@ static const struct osmo_fsm_state ps_rab_ass_fsm_states[] = {
.action = ps_rab_ass_fsm_wait_rabs_established, .action = ps_rab_ass_fsm_wait_rabs_established,
.in_event_mask = 0 .in_event_mask = 0
| S(PS_RAB_ASS_EV_RAB_ESTABLISHED) | S(PS_RAB_ASS_EV_RAB_ESTABLISHED)
| S(PS_RAB_ASS_EV_RAB_RELEASED)
| S(PS_RAB_ASS_EV_RAB_FAIL) | S(PS_RAB_ASS_EV_RAB_FAIL)
, ,
}, },

View File

@@ -107,7 +107,7 @@ static struct ps_rab *ps_rab_alloc(struct hnbgw_context_map *map, uint8_t rab_id
OSMO_ASSERT(osmo_use_count_get_put(&rab->use_count, PS_RAB_USE_ACTIVE, 1) == 0); OSMO_ASSERT(osmo_use_count_get_put(&rab->use_count, PS_RAB_USE_ACTIVE, 1) == 0);
llist_add_tail(&rab->entry, &map->ps_rab_list); llist_add_tail(&rab->entry, &map->ps_rabs);
return rab; return rab;
} }
@@ -121,7 +121,7 @@ struct ps_rab *ps_rab_find_by_seid(uint64_t seid, bool is_cp_seid)
struct hnbgw_context_map *map; struct hnbgw_context_map *map;
llist_for_each_entry(map, &hnb->map_list, hnb_list) { llist_for_each_entry(map, &hnb->map_list, hnb_list) {
struct ps_rab *rab; struct ps_rab *rab;
llist_for_each_entry(rab, &map->ps_rab_list, entry) { llist_for_each_entry(rab, &map->ps_rabs, entry) {
uint64_t rab_seid = is_cp_seid ? rab->cp_seid : rab->up_f_seid.seid; uint64_t rab_seid = is_cp_seid ? rab->cp_seid : rab->up_f_seid.seid;
if (rab_seid == seid) if (rab_seid == seid)
return rab; return rab;
@@ -154,7 +154,7 @@ static struct osmo_pfcp_msg *ps_rab_new_pfcp_msg_req(struct ps_rab *rab, enum os
struct ps_rab *ps_rab_get(struct hnbgw_context_map *map, uint8_t rab_id) struct ps_rab *ps_rab_get(struct hnbgw_context_map *map, uint8_t rab_id)
{ {
struct ps_rab *rab; struct ps_rab *rab;
llist_for_each_entry(rab, &map->ps_rab_list, entry) { llist_for_each_entry(rab, &map->ps_rabs, entry) {
if (rab->rab_id != rab_id) if (rab->rab_id != rab_id)
continue; continue;
return rab; return rab;
@@ -173,7 +173,7 @@ void ps_rab_failure(struct ps_rab *rab)
osmo_fsm_inst_dispatch(rab->req_fi, PS_RAB_ASS_EV_RAB_FAIL, rab); osmo_fsm_inst_dispatch(rab->req_fi, PS_RAB_ASS_EV_RAB_FAIL, rab);
if (rab->resp_fi) if (rab->resp_fi)
osmo_fsm_inst_dispatch(rab->resp_fi, PS_RAB_ASS_EV_RAB_FAIL, rab); osmo_fsm_inst_dispatch(rab->resp_fi, PS_RAB_ASS_EV_RAB_FAIL, rab);
ps_rab_release(rab, NULL); ps_rab_release(rab);
} }
struct ps_rab *ps_rab_start(struct hnbgw_context_map *map, uint8_t rab_id, struct ps_rab *ps_rab_start(struct hnbgw_context_map *map, uint8_t rab_id,
@@ -320,7 +320,7 @@ static void ps_rab_fsm_wait_pfcp_est_resp_onenter(struct osmo_fsm_inst *fi, uint
m->h.seid = 0; m->h.seid = 0;
/* Make a new CP-SEID, our local reference for the PFCP session. */ /* Make a new CP-SEID, our local reference for the PFCP session. */
rab->cp_seid = osmo_pfcp_cp_peer_next_seid(g_hnbgw->pfcp.cp_peer); rab->cp_seid = osmo_pfcp_next_seid(&g_hnbgw->pfcp.cp_peer->next_seid_state);
cp_f_seid = (struct osmo_pfcp_ie_f_seid){ cp_f_seid = (struct osmo_pfcp_ie_f_seid){
.seid = rab->cp_seid, .seid = rab->cp_seid,
}; };
@@ -672,8 +672,6 @@ static int ps_rab_fsm_use_cb(struct osmo_use_count_entry *e, int32_t old_use_cou
static void ps_rab_fsm_wait_use_count_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) static void ps_rab_fsm_wait_use_count_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{ {
struct ps_rab *rab = fi->priv; struct ps_rab *rab = fi->priv;
if (rab->resp_fi)
osmo_fsm_inst_dispatch(rab->resp_fi, PS_RAB_ASS_EV_RAB_RELEASED, rab);
OSMO_ASSERT(osmo_use_count_get_put(&rab->use_count, PS_RAB_USE_ACTIVE, -1) == 0); OSMO_ASSERT(osmo_use_count_get_put(&rab->use_count, PS_RAB_USE_ACTIVE, -1) == 0);
} }
@@ -694,7 +692,7 @@ static void ps_rab_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event,
static void ps_rab_forget_map(struct ps_rab *rab) static void ps_rab_forget_map(struct ps_rab *rab)
{ {
/* remove from map->ps_rab_list */ /* remove from map->ps_rabs */
if (rab->map) if (rab->map)
llist_del(&rab->entry); llist_del(&rab->entry);
rab->map = NULL; rab->map = NULL;
@@ -706,12 +704,10 @@ static void ps_rab_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_caus
ps_rab_forget_map(rab); ps_rab_forget_map(rab);
} }
/* notify_fi can be NULL, in which case no event PS_RAB_ASS_EV_RAB_RELEASED event will be dispatched */ void ps_rab_release(struct ps_rab *rab)
void ps_rab_release(struct ps_rab *rab, struct osmo_fsm_inst *notify_fi)
{ {
struct osmo_fsm_inst *fi = rab->fi; struct osmo_fsm_inst *fi = rab->fi;
ps_rab_forget_map(rab); ps_rab_forget_map(rab);
rab->resp_fi = notify_fi;
switch (fi->state) { switch (fi->state) {
case PS_RAB_ST_RX_CORE_REMOTE_F_TEID: case PS_RAB_ST_RX_CORE_REMOTE_F_TEID:
/* No session requested yet. Nothing to be deleted. */ /* No session requested yet. Nothing to be deleted. */

View File

@@ -35,14 +35,8 @@ struct osmo_tdef hnbgw_T_defs[] = {
{.T = 3113, .default_val = 15, .desc = "Time to keep Paging record, for CN pools with more than one link" }, {.T = 3113, .default_val = 15, .desc = "Time to keep Paging record, for CN pools with more than one link" },
{.T = 4, .default_val = 5, .desc = "Timeout to receive RANAP RESET ACKNOWLEDGE from an MSC/SGSN" }, {.T = 4, .default_val = 5, .desc = "Timeout to receive RANAP RESET ACKNOWLEDGE from an MSC/SGSN" },
{.T = -31, .default_val = 15, .desc = "Timeout for establishing and releasing context maps (RUA <-> SCCP)" }, {.T = -31, .default_val = 15, .desc = "Timeout for establishing and releasing context maps (RUA <-> SCCP)" },
{.T = -34, .default_val = 1000, .unit = OSMO_TDEF_MS, .desc = "Period to query network traffic stats from netfilter" },
{
.T = -35,
.default_val = 60*60*24*7,
.desc = "Clean up all hNodeB persistent state after this time of the hNodeB being disconnected."
" Set to zero to never clear hNodeB persistent state. (default is 60*60*24*27 = a week)",
},
{.T = -1002, .default_val = 10, .desc = "Timeout for the HNB to respond to PS RAB Assignment Request" }, {.T = -1002, .default_val = 10, .desc = "Timeout for the HNB to respond to PS RAB Assignment Request" },
{.T = -34, .default_val = 1, .desc = "Period to query network traffic stats from netfilter" },
{ } { }
}; };

View File

@@ -1,107 +0,0 @@
/* UMTS Cell ID */
/* (C) 2015,2024 by Harald Welte <laforge@gnumonks.org>
* (C) 2016-2025 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <unistd.h>
#include <errno.h>
#include <inttypes.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/jhash.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/hnbgw/umts_cell_id.h>
int umts_cell_id_to_str_buf(char *buf, size_t buflen, const struct umts_cell_id *ucid)
{
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
OSMO_STRBUF_APPEND_NOLEN(sb, osmo_plmn_name_buf, &ucid->plmn);
OSMO_STRBUF_PRINTF(sb, "-L%u-R%u-S%u-C%u", ucid->lac, ucid->rac, ucid->sac, ucid->cid);
return sb.chars_needed;
}
char *umts_cell_id_to_str_c(void *ctx, const struct umts_cell_id *ucid)
{
OSMO_NAME_C_IMPL(ctx, 64, "ERROR", umts_cell_id_to_str_buf, ucid)
}
const char *umts_cell_id_to_str(const struct umts_cell_id *ucid)
{
return umts_cell_id_to_str_c(OTC_SELECT, ucid);
}
/* Useful to index a hash table by struct umts_cell_id. */
uint32_t umts_cell_id_hash(const struct umts_cell_id *ucid)
{
return osmo_jhash(ucid, sizeof(*ucid), 0x423423);
}
/* parse a string representation of an umts_cell_id into its decoded representation */
int umts_cell_id_from_str(struct umts_cell_id *ucid, const char *instr)
{
int rc;
char buf[4];
const char *pos = instr;
const char *end;
/* We want to use struct umts_cell_id as hashtable key. If it ever happens to contain any padding bytes, make
* sure everything is deterministically zero. */
memset(ucid, 0, sizeof(*ucid));
/* read MCC */
end = strchr(pos, '-');
if (!end || end <= pos || (end - pos) >= sizeof(buf))
return -EINVAL;
osmo_strlcpy(buf, pos, end - pos + 1);
if (osmo_mcc_from_str(buf, &ucid->plmn.mcc))
return -EINVAL;
pos = end + 1;
/* read MNC -- here the number of leading zeros matters. */
end = strchr(pos, '-');
if (!end || end == pos || (end - pos) >= sizeof(buf))
return -EINVAL;
osmo_strlcpy(buf, pos, end - pos + 1);
if (osmo_mnc_from_str(buf, &ucid->plmn.mnc, &ucid->plmn.mnc_3_digits))
return -EINVAL;
pos = end + 1;
/* parse the rest, where leading zeros do not matter */
rc = sscanf(pos, "L%" SCNu16 "-R%" SCNu8 "-S%" SCNu16 "-C%" SCNu32 "",
&ucid->lac, &ucid->rac, &ucid->sac, &ucid->cid);
if (rc < 0)
return -errno;
if (rc != 4)
return -EINVAL;
if (ucid->lac == 0 || ucid->lac == 0xffff)
return -EINVAL;
/* CellIdentity in the ASN.1 syntax is a bit-string of 28 bits length */
if (ucid->cid >= (1 << 28))
return -EINVAL;
return 0;
}

View File

@@ -1,6 +1,5 @@
SUBDIRS = \ SUBDIRS = \
ranap_rab_ass \ ranap_rab_ass \
umts_cell_id \
$(NULL) $(NULL)
# The `:;' works around a Bash 3.2 bug when the output is not writeable. # The `:;' works around a Bash 3.2 bug when the output is not writeable.
@@ -40,11 +39,16 @@ DISTCLEANFILES = \
atconfig \ atconfig \
$(NULL) $(NULL)
if ENABLE_EXT_TESTS_QEMU
QEMU_EXT_TEST_WRAPPER = BUILDDIR=$(abs_top_builddir) $(abs_top_srcdir)/tests/qemu/ext_test_wrapper.sh
endif
if ENABLE_EXT_TESTS if ENABLE_EXT_TESTS
python-tests: python-tests:
$(MAKE) vty-test $(MAKE) vty-test
osmotestvty.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v $(MAKE) config-tests
osmotestconfig.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v $(QEMU_EXT_TEST_WRAPPER) osmotestvty.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
$(QEMU_EXT_TEST_WRAPPER) osmotestconfig.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
else else
python-tests: python-tests:
echo "Not running python-based tests (determined at configure-time)" echo "Not running python-based tests (determined at configure-time)"
@@ -61,23 +65,21 @@ endif
# pass -u to vty_script_runner.py by doing: # pass -u to vty_script_runner.py by doing:
# make vty-test U=-u # make vty-test U=-u
vty-test: $(top_builddir)/src/osmo-hnbgw/osmo-hnbgw vty-test: $(top_builddir)/src/osmo-hnbgw/osmo-hnbgw
osmo_verify_transcript_vty.py -v \ $(QEMU_EXT_TEST_WRAPPER) osmo_verify_transcript_vty.py -v \
-n OsmoHNBGW -p 4261 \ -n OsmoHNBGW -p 4261 \
-r "$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw \ -r "$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw \
-c $(srcdir)/osmo-hnbgw-vty-test.cfg" \ -c $(srcdir)/osmo-hnbgw-vty-test.cfg" \
$(U) $(srcdir)/$(VTY_TEST) $(U) $(srcdir)/$(VTY_TEST)
# Test each config/*.cfg file with the corresponding config/*.vty transcript test. # Test each config/*.cfg file with the corresponding config/*.vty transcript test.
#
# To be invoked manually only: This is not part of 'make check' because the
# output may change when libosmo-sccp changes its VTY appearance, which can
# cause annoying test fallout.
#
# Each config test runs an osmo-hnbgw process to talk to, so they must not run concurrently. # Each config test runs an osmo-hnbgw process to talk to, so they must not run concurrently.
# To prevent 'make -j N' from running these tests in parallel, call a script with a linear for-loop. # To prevent 'make -j N' from running these tests in parallel, call a script with a linear for-loop.
.PHONY: config-tests .PHONY: config-tests
config-tests: config-tests:
$(srcdir)/config/run_tests.sh "$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw" "$(srcdir)/config/" $U $(QEMU_EXT_TEST_WRAPPER) $(srcdir)/config/run_tests.sh \
"$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw" \
"$(srcdir)/config/" \
$U
# Run a specific test with: 'make ctrl-test CTRL_TEST=osmo-hnbgw.ctrl' # Run a specific test with: 'make ctrl-test CTRL_TEST=osmo-hnbgw.ctrl'
CTRL_TEST ?= *.ctrl CTRL_TEST ?= *.ctrl
@@ -86,7 +88,7 @@ CTRL_TEST ?= *.ctrl
# pass -u to ctrl_script_runner.py by doing: # pass -u to ctrl_script_runner.py by doing:
# make ctrl-test U=-u # make ctrl-test U=-u
ctrl-test: $(top_builddir)/src/osmo-hnbgw/osmo-hnbgw ctrl-test: $(top_builddir)/src/osmo-hnbgw/osmo-hnbgw
osmo_verify_transcript_ctrl.py -v \ $(QEMU_EXT_TEST_WRAPPER) osmo_verify_transcript_ctrl.py -v \
-p 4262 \ -p 4262 \
-r "$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw -c $(top_srcdir)/doc/examples/osmo-hnbgw/osmo-hnbgw-cnpool.cfg" \ -r "$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw -c $(top_srcdir)/doc/examples/osmo-hnbgw/osmo-hnbgw-cnpool.cfg" \
$(U) $(srcdir)/$(CTRL_TEST) $(U) $(srcdir)/$(CTRL_TEST)

View File

@@ -134,23 +134,8 @@ OsmoHNBGW(config)# sgsn ?
OsmoHNBGW(config)# ### The config file has no 'msc' or 'sgsn', so defaults have been set up OsmoHNBGW(config)# ### The config file has no 'msc' or 'sgsn', so defaults have been set up
OsmoHNBGW(config)# show running-config OsmoHNBGW(config)# show running-config
... ...
cs7 instance 0
point-code 0.23.5
...
sccp-address addr-dyn-msc-default
routing-indicator PC
point-code 0.23.1
subsystem-number 142
sccp-address addr-dyn-sgsn-default
routing-indicator PC
point-code 0.23.4
...
msc 0 msc 0
name msc-0
remote-addr addr-dyn-msc-default
sgsn 0 sgsn 0
name sgsn-0
remote-addr addr-dyn-sgsn-default
... ...
OsmoHNBGW(config)# msc 1 OsmoHNBGW(config)# msc 1
@@ -239,15 +224,9 @@ OsmoHNBGW(config)# ### Just by entering the nodes above, 'msc 1' and 'sgsn 1' no
OsmoHNBGW(config)# show running-config OsmoHNBGW(config)# show running-config
... ...
msc 0 msc 0
name msc-0
remote-addr addr-dyn-msc-default
msc 1 msc 1
name msc-1
sgsn 0 sgsn 0
name sgsn-0
remote-addr addr-dyn-sgsn-default
sgsn 1 sgsn 1
name sgsn-1
... ...
OsmoHNBGW(config)# ### Add {msc,sgsn}x{2,3} OsmoHNBGW(config)# ### Add {msc,sgsn}x{2,3}
@@ -273,28 +252,18 @@ OsmoHNBGW(config-sgsn)# exit
OsmoHNBGW(config)# show running-config OsmoHNBGW(config)# show running-config
... ...
msc 0 msc 0
name msc-0
remote-addr addr-dyn-msc-default
msc 1 msc 1
name msc-1
msc 2 msc 2
name msc-2
remote-addr addr-msc2 remote-addr addr-msc2
no allow-attach no allow-attach
msc 3 msc 3
name msc-3
remote-addr addr-msc3 remote-addr addr-msc3
allow-emergency allow-emergency
sgsn 0 sgsn 0
name sgsn-0
remote-addr addr-dyn-sgsn-default
sgsn 1 sgsn 1
name sgsn-1
sgsn 2 sgsn 2
name sgsn-2
remote-addr addr-sgsn2 remote-addr addr-sgsn2
sgsn 3 sgsn 3
name sgsn-3
remote-addr addr-sgsn3 remote-addr addr-sgsn3
... ...
@@ -313,26 +282,16 @@ OsmoHNBGW(config-sgsn)# exit
OsmoHNBGW(config)# show running-config OsmoHNBGW(config)# show running-config
... ...
msc 0 msc 0
name msc-0
remote-addr addr-dyn-msc-default
msc 1 msc 1
name msc-1
msc 2 msc 2
name msc-2
remote-addr addr-msc2 remote-addr addr-msc2
msc 3 msc 3
name msc-3
remote-addr addr-msc4 remote-addr addr-msc4
sgsn 0 sgsn 0
name sgsn-0
remote-addr addr-dyn-sgsn-default
sgsn 1 sgsn 1
name sgsn-1
sgsn 2 sgsn 2
name sgsn-2
remote-addr addr-sgsn4 remote-addr addr-sgsn4
sgsn 3 sgsn 3
name sgsn-3
remote-addr addr-sgsn3 remote-addr addr-sgsn3
... ...
@@ -362,26 +321,18 @@ OsmoHNBGW(config-hnbgw)# show running-config
hnbgw hnbgw
... ...
msc 0 msc 0
name msc-0
remote-addr addr-msc0 remote-addr addr-msc0
msc 1 msc 1
name msc-1
msc 2 msc 2
name msc-2
remote-addr addr-msc2 remote-addr addr-msc2
msc 3 msc 3
name msc-3
remote-addr addr-msc4 remote-addr addr-msc4
sgsn 0 sgsn 0
name sgsn-0
remote-addr addr-sgsn0 remote-addr addr-sgsn0
sgsn 1 sgsn 1
name sgsn-1
sgsn 2 sgsn 2
name sgsn-2
remote-addr addr-sgsn4 remote-addr addr-sgsn4
sgsn 3 sgsn 3
name sgsn-3
remote-addr addr-sgsn3 remote-addr addr-sgsn3
... ...
@@ -397,36 +348,3 @@ hnbgw
... ...
msc 0 msc 0
... ...
OsmoHNBGW(config-hnbgw)# exit
OsmoHNBGW(config)# ### Apply all SCCP changes:
OsmoHNBGW(config)# apply sccp
OsmoHNBGW(config)# show running-config
...
hnbgw
...
msc 0
name msc-0
remote-addr addr-msc0
msc 1
name msc-1
remote-addr addr-dyn-msc-default
msc 2
name msc-2
remote-addr addr-msc2
msc 3
name msc-3
remote-addr addr-msc4
sgsn 0
name sgsn-0
remote-addr addr-sgsn0
sgsn 1
name sgsn-1
remote-addr addr-dyn-sgsn-default
sgsn 2
name sgsn-2
remote-addr addr-sgsn4
sgsn 3
name sgsn-3
remote-addr addr-sgsn3
...

View File

@@ -32,6 +32,7 @@ cs7 instance 1
hnbgw hnbgw
iuh iuh
local-ip 0.0.0.0 local-ip 0.0.0.0
hnbap-allow-tmsi 1
mgw 0 mgw 0
remote-ip 127.0.0.1 remote-ip 127.0.0.1
local-port 2729 local-port 2729

View File

@@ -20,7 +20,6 @@ OsmoHNBGW(config-hnbgw)# list
iups iups
hnb UMTS_CELL_ID hnb UMTS_CELL_ID
no hnb IDENTITY_INFO no hnb IDENTITY_INFO
nft-kpi [TABLE_NAME]
... ...
OsmoHNBGW(config-hnbgw)# plmn? OsmoHNBGW(config-hnbgw)# plmn?
@@ -83,39 +82,3 @@ hnbgw
... ...
rnc-id 42 rnc-id 42
... ...
OsmoHNBGW(config-hnbgw)# nft-kpi?
nft-kpi Retrieve traffic counters from nftables
OsmoHNBGW(config-hnbgw)# nft-kpi ?
[TABLE_NAME] Set a custom nft table name to use, instead of 'osmo-hnbgw'
OsmoHNBGW(config-hnbgw)# show running-config
... !nft-kpi
OsmoHNBGW(config-hnbgw)# nft-kpi
% WARNING: nft configuration changes need a restart of osmo-hnbgw
OsmoHNBGW(config-hnbgw)# show running-config
...
hnbgw
...
nft-kpi
...
OsmoHNBGW(config-hnbgw)# no nft-kpi
% WARNING: nft configuration changes need a restart of osmo-hnbgw
OsmoHNBGW(config-hnbgw)# show running-config
... !nft-kpi
OsmoHNBGW(config-hnbgw)# nft-kpi maple
% WARNING: nft configuration changes need a restart of osmo-hnbgw
OsmoHNBGW(config-hnbgw)# show running-config
...
hnbgw
...
nft-kpi maple
...
OsmoHNBGW(config-hnbgw)# no nft-kpi
% WARNING: nft configuration changes need a restart of osmo-hnbgw
OsmoHNBGW(config-hnbgw)# show running-config
... !nft-kpi

18
tests/qemu/check-depends.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/sh
RET=0
require_program() {
if [ -z "$(command -v "$1")" ]; then
RET=1
echo "ERROR: missing program: $1"
fi
}
require_program busybox
require_program cpio
require_program find
require_program gzip
require_program lddtree
require_program qemu-system-x86_64
exit "$RET"

75
tests/qemu/ext_test_wrapper.sh Executable file
View File

@@ -0,0 +1,75 @@
#!/bin/sh -ex
DIR="$(cd "$(dirname "$0")" && pwd)"
cat << EOF > "$DIR"/_testcmd.sh
#!/bin/sh -ex
cd "$PWD"
EOF
chmod +x "$DIR"/_testcmd.sh
for i in "$@"; do
echo -n "\"$i\" " >> "$DIR"/_testcmd.sh
done
echo >> "$DIR"/_testcmd.sh
cat << EOF > "$DIR"/_init.sh
#!/usr/bin/busybox sh
echo "Running _init.sh"
set -x
/usr/bin/busybox --install -s
hostname qemu
for i in \$(cat /modules); do
modprobe "\$i"
done
ip link set lo up
ip a
mkdir /mnt/
mount \
-t 9p \
-o trans=virtio \
fs0 \
/mnt/
# osmotestconfig.py tries to write to builddir
cp -r /mnt/$BUILDDIR /tmp/builddir
mount --bind /tmp/builddir /mnt/$BUILDDIR
if chroot /mnt "$DIR"/_testcmd.sh; then
echo "QEMU_TEST_SUCCESSFUL"
fi
poweroff -f
EOF
chmod +x "$DIR"/_init.sh
if ! [ -e "$DIR"/_linux ]; then
cp /boot/vmlinuz "$DIR"/_linux
fi
$DIR/initrd-build.sh
KERNEL_CMDLINE="root=/dev/ram0 console=ttyS0 panic=-1 init=/init"
qemu-system-x86_64 \
$MACHINE_ARG \
-smp 1 \
-m 512M \
-no-user-config -nodefaults -display none \
-gdb unix:"$DIR"/_gdb.pipe,server=on,wait=off \
-no-reboot \
-kernel "$DIR"/_linux \
-initrd "$DIR"/_initrd.gz \
-append "${KERNEL_CMDLINE}" \
-serial stdio \
-chardev socket,id=charserial1,path="$DIR"/_gdb-serial.pipe,server=on,wait=off \
-device isa-serial,chardev=charserial1,id=serial1 \
-fsdev local,security_model=passthrough,id=fsdev-fs0,multidevs=remap,path=/ \
-device virtio-9p-pci,id=fs0,fsdev=fsdev-fs0,mount_tag=fs0 \
2>&1 | tee "$DIR/_output"
grep -q QEMU_TEST_SUCCESSFUL "$DIR/_output"

114
tests/qemu/initrd-build.sh Executable file
View File

@@ -0,0 +1,114 @@
#!/bin/sh -e
DIR="$(cd "$(dirname "$0")" && pwd)"
DIR_INITRD="$DIR/_initrd"
DIR_MODULES="$(find /lib/modules/* -type d -prune | sort -r | head -n 1)"
# Add one or more files to the initramfs, with parent directories.
# usr-merge: resolve symlinks for /lib -> /usr/lib etc. so "cp --parents" does
# not fail with "cp: cannot make directory '/tmp/initrd/lib': File exists"
# $@: path to files
initrd_add_file() {
local i
for i in "$@"; do
case "$i" in
/bin/*|/sbin/*|/lib/*|/lib64/*)
cp -a --parents "$i" "$DIR_INITRD"/usr
;;
*)
cp -a --parents "$i" "$DIR_INITRD"
;;
esac
done
}
# Add kernel module files with dependencies
# $@: kernel module names
initrd_add_mod() {
local i
local kernel="$(basename "$DIR_MODULES")"
local files="$(modprobe \
-a \
--dry-run \
--show-depends \
--set-version="$kernel" \
"$@" \
| grep -v "^builtin" \
| sort -u \
| cut -d ' ' -f 2)"
initrd_add_file $files
# Save the list of modules
for i in $@; do
echo "$i" >> "$DIR_INITRD"/modules
done
}
# Add binaries with depending libraries
# $@: paths to binaries
initrd_add_bin() {
local bin
local bin_path
local file
for bin in "$@"; do
local bin_path="$(which "$bin")"
if [ -z "$bin_path" ]; then
echo "ERROR: file not found: $bin"
exit 1
fi
lddtree_out="$(lddtree -l "$bin_path")"
if [ -z "$lddtree_out" ]; then
echo "ERROR: lddtree failed on '$bin_path'"
exit 1
fi
for file in $lddtree_out; do
initrd_add_file "$file"
# Copy resolved symlink
if [ -L "$file" ]; then
initrd_add_file "$(realpath "$file")"
fi
done
done
}
rm -rf "$DIR_INITRD"
mkdir -p "$DIR_INITRD"
cd "$DIR_INITRD"
for dir in bin sbin lib lib64; do
ln -s usr/"$dir" "$dir"
done
mkdir -p \
dev/net \
proc \
run \
sys \
tmp \
usr/bin \
usr/sbin
initrd_add_bin \
busybox
initrd_add_mod \
9p \
9pnet \
9pnet_virtio \
nf_tables \
nfnetlink \
sctp
initrd_add_file \
"$DIR_MODULES"/modules.dep
cp "$DIR"/_init.sh init
find . -print0 \
| cpio --quiet -o -0 -H newc \
| gzip -1 > "$DIR"/_initrd.gz

View File

@@ -24,18 +24,15 @@
#include <osmocom/core/utils.h> #include <osmocom/core/utils.h>
#include <osmocom/core/socket.h> #include <osmocom/core/socket.h>
#include <osmocom/core/sockaddr_str.h> #include <osmocom/core/sockaddr_str.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/ranap/ranap_ies_defs.h> #include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/ranap/iu_helpers.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.h>
#include <osmocom/ranap/ranap_common_cn.h> #include <osmocom/ranap/ranap_common_cn.h>
#include <osmocom/ranap/ranap_common_ran.h> #include <osmocom/ranap/ranap_common_ran.h>
#include <osmocom/hnbgw/hnb.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/ranap_rab_ass.h>
static void *msgb_ctx; static void *msgb_ctx;
extern void *talloc_asn1_ctx; extern void *talloc_asn1_ctx;

View File

@@ -5,10 +5,4 @@ AT_SETUP([ranap_rab_ass])
AT_KEYWORDS([ranap_rab_ass]) AT_KEYWORDS([ranap_rab_ass])
cat $abs_srcdir/ranap_rab_ass/ranap_rab_ass_test.ok > expout 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_CHECK([$abs_top_builddir/tests/ranap_rab_ass/ranap_rab_ass_test], [0], [expout], [ignore])
AT_CLEANUP AT_CLEANUP
AT_SETUP([umts_cell_id])
AT_KEYWORDS([umts_cell_id])
cat $abs_srcdir/umts_cell_id/umts_cell_id_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/umts_cell_id/umts_cell_id_test], [0], [expout], [ignore])
AT_CLEANUP

View File

@@ -1,38 +0,0 @@
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)
AM_LDFLAGS = -no-install
EXTRA_DIST = \
umts_cell_id_test.ok \
$(NULL)
check_PROGRAMS = \
umts_cell_id_test \
$(NULL)
umts_cell_id_test_SOURCES = \
umts_cell_id_test.c \
$(NULL)
umts_cell_id_test_LDADD = \
$(top_builddir)/src/osmo-hnbgw/libhnbgw.la \
$(NULL)
.PHONY: update_exp
update_exp:
$(builddir)/umts_cell_id_test >$(srcdir)/umts_cell_id_test.ok

View File

@@ -1,163 +0,0 @@
#include <stdio.h>
#include <errno.h>
#include <osmocom/core/utils.h>
#include <osmocom/hnbgw/umts_cell_id.h>
struct test {
const char *id_str;
int expect_rc;
struct umts_cell_id id;
};
struct test tests[] = {
{
.id_str = "001-01-L1-R1-S1-C1",
.id = {
.plmn = {
.mcc = 1,
.mnc = 1,
},
.lac = 1,
.rac = 1,
.sac = 1,
.cid = 1,
},
},
/* ensure that a 3-digit MNC with leading zeroes is kept separate from two-digit MNC */
{
.id_str = "001-001-L1-R1-S1-C1",
.id = {
.plmn = {
.mcc = 1,
.mnc = 1,
.mnc_3_digits = true,
},
.lac = 1,
.rac = 1,
.sac = 1,
.cid = 1,
},
},
{
.id_str = "001-099-L1-R1-S1-C1",
.id = {
.plmn = {
.mcc = 1,
.mnc = 99,
.mnc_3_digits = true,
},
.lac = 1,
.rac = 1,
.sac = 1,
.cid = 1,
},
},
{
.id_str = "001-99-L1-R1-S1-C1",
.id = {
.plmn = {
.mcc = 1,
.mnc = 99,
.mnc_3_digits = false,
},
.lac = 1,
.rac = 1,
.sac = 1,
.cid = 1,
},
},
{
.id_str = "999-999-L65534-R255-S65535-C268435455",
.id = {
.plmn = {
.mcc = 999,
.mnc = 999,
.mnc_3_digits = true,
},
.lac = 65534,
.rac = 255,
.sac = 65535,
.cid = (1 << 28) - 1,
},
},
{
.id_str = "1000-001-L1-R1-S1-C1",
.expect_rc = -EINVAL,
},
{
.id_str = "001-001-L65535-R1-S1-C1",
.expect_rc = -EINVAL,
},
/* TODO? There is no bounds checking on RAC and SAC.
{
.id_str = "001-001-L1-R256-S1-C1",
.expect_rc = -EINVAL,
},
{
.id_str = "001-001-L1-R1-S65536-C1",
.expect_rc = -EINVAL,
},
*/
{
.id_str = "001-001-L1-R1-S1-C268435456",
.expect_rc = -EINVAL,
},
};
int main(void)
{
struct test *t;
for (t = tests; (t - tests) < ARRAY_SIZE(tests); t++) {
int rc;
struct umts_cell_id parsed;
char to_str[128] = {};
printf("\"%s\"\n", t->id_str);
memset(&parsed, 0x2b, sizeof(parsed));
rc = umts_cell_id_from_str(&parsed, t->id_str);
if (rc != t->expect_rc) {
printf(" ERROR: umts_cell_id_from_str(): expected rc == %d, got %d\n",
t->expect_rc, rc);
continue;
}
if (rc) {
if (rc == t->expect_rc)
printf(" expected rc != 0: ok\n");
continue;
}
printf(" -> umts_cell_id_from_str(): ok\n");
rc = umts_cell_id_to_str_buf(to_str, sizeof(to_str), &parsed);
if (rc <= 0) {
printf(" ERROR: umts_cell_id_to_str_buf(): expected rc == 0, got %d\n", rc);
continue;
} else {
printf(" -> umts_cell_id_to_str_buf(): ok\n");
if (strcmp(t->id_str, to_str))
printf(" ERROR: conversion to umts_cell_id and back to string doesn't return the original string\n");
printf(" -> \"%s\"\n", to_str);
}
if (umts_cell_id_equal(&t->id, &parsed)) {
printf(" umts_cell_id_equal(expected, parsed): ok\n");
} else {
char to_str_expect[128] = {};
umts_cell_id_to_str_buf(to_str_expect, sizeof(to_str_expect), &t->id);
printf(" ERROR: umts_cell_id_equal(expected, parsed) == false\n");
printf(" expected %s\n", to_str_expect);
printf(" got %s\n", to_str);
printf(" expected %s\n", osmo_hexdump((void *)&t->id, sizeof(t->id)));
printf(" got %s\n", osmo_hexdump((void *)&parsed, sizeof(t->id)));
}
}
return 0;
}

View File

@@ -1,31 +0,0 @@
"001-01-L1-R1-S1-C1"
-> umts_cell_id_from_str(): ok
-> umts_cell_id_to_str_buf(): ok
-> "001-01-L1-R1-S1-C1"
umts_cell_id_equal(expected, parsed): ok
"001-001-L1-R1-S1-C1"
-> umts_cell_id_from_str(): ok
-> umts_cell_id_to_str_buf(): ok
-> "001-001-L1-R1-S1-C1"
umts_cell_id_equal(expected, parsed): ok
"001-099-L1-R1-S1-C1"
-> umts_cell_id_from_str(): ok
-> umts_cell_id_to_str_buf(): ok
-> "001-099-L1-R1-S1-C1"
umts_cell_id_equal(expected, parsed): ok
"001-99-L1-R1-S1-C1"
-> umts_cell_id_from_str(): ok
-> umts_cell_id_to_str_buf(): ok
-> "001-99-L1-R1-S1-C1"
umts_cell_id_equal(expected, parsed): ok
"999-999-L65534-R255-S65535-C268435455"
-> umts_cell_id_from_str(): ok
-> umts_cell_id_to_str_buf(): ok
-> "999-999-L65534-R255-S65535-C268435455"
umts_cell_id_equal(expected, parsed): ok
"1000-001-L1-R1-S1-C1"
expected rc != 0: ok
"001-001-L65535-R1-S1-C1"
expected rc != 0: ok
"001-001-L1-R1-S1-C268435456"
expected rc != 0: ok