mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw.git
synced 2025-11-02 04:53:21 +00:00
Compare commits
364 Commits
1.2.1
...
4da67bb587
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4da67bb587 | ||
|
|
2c88bdb492 | ||
|
|
2601d9b3f0 | ||
|
|
315874927e | ||
|
|
cb4962f7f0 | ||
|
|
46a2c0afc2 | ||
|
|
6679c49133 | ||
|
|
4db21846e7 | ||
|
|
883048efd4 | ||
|
|
33cbc694f5 | ||
|
|
563f574178 | ||
|
|
d144d7b497 | ||
|
|
35a834078f | ||
|
|
160b372bf9 | ||
|
|
225a215b09 | ||
|
|
5eb95cf42a | ||
|
|
f91e32fc01 | ||
|
|
37b9a98a10 | ||
|
|
1f8fb9ae8c | ||
|
|
6a254f08c7 | ||
|
|
4e88e2b4ee | ||
|
|
894a3a455e | ||
|
|
135a8b079f | ||
|
|
df7dc3b6f4 | ||
|
|
8a573665c4 | ||
|
|
72c96d4792 | ||
|
|
b561656431 | ||
|
|
8aef494af5 | ||
|
|
718d71d3db | ||
|
|
24c850f8f0 | ||
|
|
cb03fc80c4 | ||
|
|
a3cceea45a | ||
|
|
1386a0da73 | ||
|
|
5ebcace219 | ||
|
|
bb3f454c8b | ||
|
|
ca8fc60231 | ||
|
|
3ef79eb800 | ||
|
|
5676dfbb75 | ||
|
|
05dd0272d0 | ||
|
|
0b4374bb9f | ||
|
|
f5b5c75d2b | ||
|
|
ca09bc0c44 | ||
|
|
70e2ee8a0a | ||
|
|
edc7baa3b8 | ||
|
|
050704c562 | ||
|
|
d25fa3723b | ||
|
|
bad546b431 | ||
|
|
af0a6aeb39 | ||
|
|
78fd0b97a9 | ||
|
|
580411823d | ||
|
|
5d91358114 | ||
|
|
d8b9a53a44 | ||
|
|
804ebe1593 | ||
|
|
8897a7764a | ||
|
|
7f3a50c619 | ||
|
|
79c5600bb6 | ||
|
|
aa2cefb173 | ||
|
|
d37f3dc20c | ||
|
|
f063d6bab8 | ||
|
|
619b81e5dc | ||
|
|
a824ebd8c2 | ||
|
|
71ad913d99 | ||
|
|
dbbfefad2c | ||
|
|
167f7aaac8 | ||
|
|
4ed837cba8 | ||
|
|
7784eb4e3b | ||
|
|
1a15261931 | ||
|
|
fb450888b6 | ||
|
|
a3c0a00573 | ||
|
|
e6d87ff967 | ||
|
|
1ac1d1277b | ||
|
|
1d4811e38c | ||
|
|
89504050ea | ||
|
|
7958955624 | ||
|
|
ca6f6fded9 | ||
|
|
3140381337 | ||
|
|
6f0197878a | ||
|
|
3fb99d143a | ||
|
|
30068ad391 | ||
|
|
1eb2a759ad | ||
|
|
280f9e6ca9 | ||
|
|
2fc8abeecf | ||
|
|
71f3169140 | ||
|
|
61e278a452 | ||
|
|
4468d8728e | ||
|
|
0081cd7b00 | ||
|
|
610aae4d65 | ||
|
|
3ef65760ff | ||
|
|
00655d45dd | ||
|
|
15e552f232 | ||
|
|
e29816eccc | ||
|
|
fc0e505330 | ||
|
|
3da951d5c3 | ||
|
|
2a4af66669 | ||
|
|
62da064eda | ||
|
|
938b5ac777 | ||
|
|
a503bd4eb2 | ||
|
|
da8a042ead | ||
|
|
b3b2e2b20d | ||
|
|
9da60749ca | ||
|
|
02d1d51966 | ||
|
|
9a7e520a95 | ||
|
|
20b710bfc2 | ||
|
|
64a2debb9a | ||
|
|
e4bd96a841 | ||
|
|
eccff1abe8 | ||
|
|
a121f82f33 | ||
|
|
a5974d7906 | ||
|
|
0d2d966c15 | ||
|
|
de99af3aaa | ||
|
|
ba81785b71 | ||
|
|
da7d33e284 | ||
|
|
7449635520 | ||
|
|
8fd95f3e74 | ||
|
|
cd58308915 | ||
|
|
e3cc5ddf1d | ||
|
|
f64cdf85d1 | ||
|
|
060619b811 | ||
|
|
4b274a88ea | ||
|
|
b57afbe1a6 | ||
|
|
0a991f9a71 | ||
|
|
cf75e50314 | ||
|
|
5fbd72f80e | ||
|
|
a3c02651db | ||
|
|
5329c44e0e | ||
|
|
6917c9b2cd | ||
|
|
ffae71bc14 | ||
|
|
5a2cf382ef | ||
|
|
ea15086584 | ||
|
|
14c36591fc | ||
|
|
bceef626b5 | ||
|
|
1cd9402663 | ||
|
|
cae5bf33e8 | ||
|
|
51fb6b5578 | ||
|
|
2b771919dc | ||
|
|
b43660685d | ||
|
|
31178cd2cb | ||
|
|
f43fbc9c40 | ||
|
|
e874946bfb | ||
|
|
30691e7b90 | ||
|
|
e4d0951a86 | ||
|
|
101a9c7e21 | ||
|
|
7ec5cb7280 | ||
|
|
fa4da8f980 | ||
|
|
05605354ef | ||
|
|
046ba3d27d | ||
|
|
e01a873cff | ||
|
|
1ee44400a4 | ||
|
|
eba9cbf9d1 | ||
|
|
5fd2aa5dc4 | ||
|
|
4ec1c77f9c | ||
|
|
ec10455aed | ||
|
|
6582c9a06c | ||
|
|
1237ae48d1 | ||
|
|
770d62f91b | ||
|
|
67f9de4148 | ||
|
|
406d95139b | ||
|
|
c17b2850b7 | ||
|
|
5755268f9b | ||
|
|
656d1d2778 | ||
|
|
90928fb246 | ||
|
|
fadc67b8fb | ||
|
|
cd09569bc8 | ||
|
|
3abd523faa | ||
|
|
81105a4d7a | ||
|
|
0e987d7f3c | ||
|
|
b64afa8631 | ||
|
|
8514b73bff | ||
|
|
92340d2131 | ||
|
|
89fe80525b | ||
|
|
267a92a580 | ||
|
|
b77d944961 | ||
|
|
067f1a0948 | ||
|
|
3a084af8f7 | ||
|
|
d58d15636f | ||
|
|
7867adc490 | ||
|
|
d482f8666c | ||
|
|
1b45792cbd | ||
|
|
5aeacc4500 | ||
|
|
e14d3bd6fe | ||
|
|
fb627de4bb | ||
|
|
b1c0bb19e2 | ||
|
|
72f0884ee0 | ||
|
|
11fa15e4d9 | ||
|
|
15e79a2b7a | ||
|
|
afb6298727 | ||
|
|
6563e11731 | ||
|
|
36b5a74658 | ||
|
|
685c276ce1 | ||
|
|
e513ffb983 | ||
|
|
56323378db | ||
|
|
a0321315e1 | ||
|
|
ee1a647547 | ||
|
|
d6b0217c33 | ||
|
|
d5fe76dec8 | ||
|
|
83dce9f7da | ||
|
|
c957f4efd5 | ||
|
|
9ee468f5b2 | ||
|
|
a49b265226 | ||
|
|
9901316371 | ||
|
|
bd5901d3f9 | ||
|
|
c6393a1f80 | ||
|
|
2ce280f232 | ||
|
|
f3caea850b | ||
|
|
e96b52b158 | ||
|
|
28cb20e1be | ||
|
|
7e5ea9d40e | ||
|
|
ab9d5364cb | ||
|
|
548d3d2f66 | ||
|
|
aa99546d23 | ||
|
|
e966e73fd2 | ||
|
|
164f3a67f1 | ||
|
|
b42428b473 | ||
|
|
ade986280e | ||
|
|
725c588f1a | ||
|
|
8111b89fd9 | ||
|
|
866386d9c3 | ||
|
|
07c8b72455 | ||
|
|
54c4c66c59 | ||
|
|
0e6b5b99a7 | ||
|
|
59e846c61e | ||
|
|
8cd268ef8a | ||
|
|
63f3abf54c | ||
|
|
0484441530 | ||
|
|
d12aecf33d | ||
|
|
f1d4e3b7dd | ||
|
|
28e8d8a0b2 | ||
|
|
6639eb910a | ||
|
|
c204f7e809 | ||
|
|
3d26d73a1c | ||
|
|
0b2983d1af | ||
|
|
52f9e327bb | ||
|
|
83697f832e | ||
|
|
fdc78ce29b | ||
|
|
fc800ca49b | ||
|
|
ece0f81e30 | ||
|
|
cc3cf24322 | ||
|
|
929ac4efdf | ||
|
|
3f92103c62 | ||
|
|
0d2251ef7d | ||
|
|
5437aeaca4 | ||
|
|
e33d7321df | ||
|
|
7f219856c9 | ||
|
|
0188391a19 | ||
|
|
4a4a675fec | ||
|
|
39683d0c6f | ||
|
|
8fd4159ea9 | ||
|
|
612d3255b8 | ||
|
|
3ebdd301c9 | ||
|
|
a973a93527 | ||
|
|
a65c57a159 | ||
|
|
0b10b5799f | ||
|
|
bc92608e60 | ||
|
|
be7df7c1a7 | ||
|
|
01389592b2 | ||
|
|
9425640d7f | ||
|
|
c6cef24546 | ||
|
|
065a719294 | ||
|
|
cc7d2b1313 | ||
|
|
1c4b05d026 | ||
|
|
9e051548d6 | ||
|
|
7992ce0163 | ||
|
|
b5dfba79ca | ||
|
|
0fdb5c3f09 | ||
|
|
311bfb5983 | ||
|
|
ed424d5be4 | ||
|
|
e67c4b324c | ||
|
|
29f82a7b48 | ||
|
|
52b2a9e6b0 | ||
|
|
62d50a1fe8 | ||
|
|
1e277db2cc | ||
|
|
60a52e4121 | ||
|
|
95d5273967 | ||
|
|
43cc12bac3 | ||
|
|
f05d15e160 | ||
|
|
9ffebe784d | ||
|
|
3f4d645890 | ||
|
|
8eefcbee92 | ||
|
|
a3c7f750a2 | ||
|
|
d41112fbcc | ||
|
|
87ecf69b55 | ||
|
|
07d01d50a5 | ||
|
|
a08b8a595a | ||
|
|
28619961a9 | ||
|
|
9bc7649b95 | ||
|
|
a1c8653bf9 | ||
|
|
76c4203552 | ||
|
|
a7fcbe100c | ||
|
|
61021881ac | ||
|
|
0589c3ecf1 | ||
|
|
0e03070789 | ||
|
|
e62af4d46a | ||
|
|
bef2c345df | ||
|
|
b9be0ea93e | ||
|
|
bbad8dec36 | ||
|
|
c923d19b7b | ||
|
|
5f19597b02 | ||
|
|
1906a30ca9 | ||
|
|
12bc4afab3 | ||
|
|
25cd41f3b5 | ||
|
|
55239c2cca | ||
|
|
3bf5395102 | ||
|
|
1de2091515 | ||
|
|
d046306b63 | ||
|
|
6bcd615d10 | ||
|
|
f9825cbd4a | ||
|
|
930ed702b6 | ||
|
|
96c712570a | ||
|
|
fe7c34737d | ||
|
|
d3382ae952 | ||
|
|
c971c657c5 | ||
|
|
eadf523393 | ||
|
|
419e832473 | ||
|
|
d28771a1b5 | ||
|
|
0c5878fa9d | ||
|
|
9ea431123d | ||
|
|
b08b19c990 | ||
|
|
791babf40e | ||
|
|
d129e0c86e | ||
|
|
2dfeb1e218 | ||
|
|
0a5e2b3643 | ||
|
|
9f654da0aa | ||
|
|
f7df74fc48 | ||
|
|
a0d528ef31 | ||
|
|
d8de11b430 | ||
|
|
598ebb6943 | ||
|
|
1ce5148996 | ||
|
|
941008e785 | ||
|
|
ca2c5b9067 | ||
|
|
c5b7106f8d | ||
|
|
e6201765cf | ||
|
|
44f5a336a8 | ||
|
|
1496498713 | ||
|
|
b7ff03e5be | ||
|
|
223aeda282 | ||
|
|
a82c8d2425 | ||
|
|
05aaccc42d | ||
|
|
7eb89ec9fe | ||
|
|
44dfe698fa | ||
|
|
7893028ef6 | ||
|
|
62fb1dea61 | ||
|
|
304f7646c9 | ||
|
|
87e03208af | ||
|
|
de8b170d1a | ||
|
|
1d1839a34b | ||
|
|
8c7aae87b0 | ||
|
|
ff2fbdf998 | ||
|
|
2c91bd66a1 | ||
|
|
afbcae6366 | ||
|
|
da9d08c94e | ||
|
|
0ca9567fb2 | ||
|
|
be9ed71631 | ||
|
|
d1f4b9b9a1 | ||
|
|
b5508f98ef | ||
|
|
9e46544486 | ||
|
|
7dd3a61b57 | ||
|
|
81f1751896 | ||
|
|
e7c66defc2 | ||
|
|
f5742a3bed | ||
|
|
efe4850e75 | ||
|
|
0c465b0f68 | ||
|
|
7daa502a2d | ||
|
|
dddafa60ea | ||
|
|
60008b1457 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,6 +2,7 @@ hnbgwdebian/*.log
|
||||
*.o
|
||||
*.lo
|
||||
*.a
|
||||
*.la
|
||||
.deps
|
||||
Makefile
|
||||
Makefile.in
|
||||
@@ -67,6 +68,7 @@ doc/manuals/*__*.png
|
||||
doc/manuals/*.check
|
||||
doc/manuals/generated/
|
||||
doc/manuals/osmohnbgw-usermanual.xml
|
||||
doc/manuals/vty/hnbgw_vty_reference.xml
|
||||
doc/manuals/common
|
||||
doc/manuals/build
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ SUBDIRS = \
|
||||
BUILT_SOURCES = $(top_srcdir)/.version
|
||||
EXTRA_DIST = \
|
||||
.version \
|
||||
contrib/osmo-hnbgw.spec.in \
|
||||
debian \
|
||||
git-version-gen \
|
||||
osmoappdesc.py \
|
||||
|
||||
@@ -19,9 +19,9 @@ GIT Repository
|
||||
|
||||
You can clone from the official osmo-hnbgw.git repository using
|
||||
|
||||
git clone git://git.osmocom.org/osmo-hnbgw.git
|
||||
git clone https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw
|
||||
|
||||
There is a cgit interface at https://git.osmocom.org/osmo-hnbgw/
|
||||
There is a web interface at https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# When cleaning up this file: bump API version in corresponding Makefile.am and rename corresponding debian/lib*.install
|
||||
# according to https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
|
||||
# In short:
|
||||
# according to https://osmocom.org/projects/cellular-infrastructure/wiki/Make_a_new_release
|
||||
# In short: https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
|
||||
# LIBVERSION=c:r:a
|
||||
# If the library source code has changed at all since the last update, then increment revision: c:r + 1:a.
|
||||
# If any interfaces have been added, removed, or changed since the last update: c + 1:0:0.
|
||||
# If any interfaces have been added, removed, or changed since the last update: c + 1:0:a.
|
||||
# If any interfaces have been added since the last public release: c:r:a + 1.
|
||||
# If any interfaces have been removed or changed since the last public release: c:r:0.
|
||||
#library what description / commit summary line
|
||||
#library what description / commit summary line
|
||||
libosmo-sigtran >2.1.0 osmo_sccp_addr_{create,update}()
|
||||
|
||||
46
configure.ac
46
configure.ac
@@ -37,8 +37,6 @@ fi
|
||||
PKG_PROG_PKG_CONFIG([0.20])
|
||||
|
||||
dnl checks for libraries
|
||||
AC_SEARCH_LIBS([dlopen], [dl dld], [LIBRARY_DL="$LIBS";LIBS=""])
|
||||
AC_SUBST(LIBRARY_DL)
|
||||
old_LIBS=$LIBS
|
||||
AC_SEARCH_LIBS([sctp_recvmsg], [sctp], [
|
||||
AC_DEFINE(HAVE_LIBSCTP, 1, [Define 1 to enable SCTP support])
|
||||
@@ -51,16 +49,36 @@ AC_SEARCH_LIBS([sctp_recvmsg], [sctp], [
|
||||
LIBS=$old_LIBS
|
||||
|
||||
PKG_CHECK_MODULES(LIBASN1C, libasn1c >= 0.9.30)
|
||||
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.6.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.6.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.6.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.6.0)
|
||||
PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 1.1.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOSIGTRAN, libosmo-sigtran >= 1.5.0)
|
||||
PKG_CHECK_MODULES(LIBOSMORUA, libosmo-rua >= 1.1.0)
|
||||
PKG_CHECK_MODULES(LIBOSMORANAP, libosmo-ranap >= 1.1.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOHNBAP, libosmo-hnbap >= 1.1.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.11.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.11.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.11.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.11.0)
|
||||
PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 1.6.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOSIGTRAN, libosmo-sigtran >= 2.1.0)
|
||||
PKG_CHECK_MODULES(LIBOSMORUA, libosmo-rua >= 1.7.0)
|
||||
PKG_CHECK_MODULES(LIBOSMORANAP, libosmo-ranap >= 1.7.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOHNBAP, libosmo-hnbap >= 1.7.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOMGCPCLIENT, libosmo-mgcp-client >= 1.14.0)
|
||||
|
||||
# Enable PFCP support for GTP tunnel mapping via UPF
|
||||
AC_ARG_ENABLE([pfcp], [AS_HELP_STRING([--enable-pfcp], [Build with PFCP support, for GTP tunnel mapping via UPF])],
|
||||
[osmo_ac_pfcp="$enableval"],[osmo_ac_pfcp="no"])
|
||||
if test "x$osmo_ac_pfcp" = "xyes" ; then
|
||||
PKG_CHECK_MODULES(LIBOSMOPFCP, libosmo-pfcp >= 0.5.0)
|
||||
AC_DEFINE(ENABLE_PFCP, 1, [Define to build with PFCP support])
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_PFCP, test "x$osmo_ac_pfcp" = "xyes")
|
||||
AC_SUBST(osmo_ac_pfcp)
|
||||
|
||||
# Enable libnftables support for traffic counters using nft
|
||||
AC_ARG_ENABLE([nftables], [AS_HELP_STRING([--enable-nftables], [Build with libnftables support, for traffic counters using nft])],
|
||||
[osmo_ac_nftables="$enableval"],[osmo_ac_nftables="no"])
|
||||
if test "x$osmo_ac_nftables" = "xyes" ; then
|
||||
PKG_CHECK_MODULES(LIBNFTABLES, libnftables >= 1.0.2)
|
||||
AC_DEFINE(ENABLE_NFTABLES, 1, [Define to build with libnftables support])
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_NFTABLES, test "x$osmo_ac_nftables" = "xyes")
|
||||
AC_SUBST(osmo_ac_nftables)
|
||||
|
||||
dnl checks for header files
|
||||
AC_HEADER_STDC
|
||||
@@ -142,7 +160,7 @@ if test "x$enable_ext_tests" = "xyes" ; then
|
||||
fi
|
||||
AC_CHECK_PROG(OSMOTESTEXT_CHECK,osmotestvty.py,yes)
|
||||
if test "x$OSMOTESTEXT_CHECK" != "xyes" ; then
|
||||
AC_MSG_ERROR([Please install git://osmocom.org/python/osmo-python-tests to run the VTY/CTRL tests.])
|
||||
AC_MSG_ERROR([Please install https://gitea.osmocom.org/cellular-infrastructure/osmo-python-tests to run the VTY/CTRL tests.])
|
||||
fi
|
||||
fi
|
||||
AC_MSG_CHECKING([whether to enable VTY/CTRL tests])
|
||||
@@ -221,10 +239,12 @@ AC_OUTPUT(
|
||||
src/osmo-hnbgw/Makefile
|
||||
tests/Makefile
|
||||
tests/atlocal
|
||||
tests/ranap_rab_ass/Makefile
|
||||
tests/umts_cell_id/Makefile
|
||||
doc/Makefile
|
||||
doc/examples/Makefile
|
||||
doc/manuals/Makefile
|
||||
doc/charts/Makefile
|
||||
contrib/Makefile
|
||||
contrib/systemd/Makefile
|
||||
contrib/osmo-hnbgw.spec
|
||||
Makefile)
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
# jenkins build helper script for osmo-hnbgw. This is how we build on jenkins.osmocom.org
|
||||
#
|
||||
# environment variables:
|
||||
# * PFCP: configure PFCP support if set to "1" (default)
|
||||
# * WITH_MANUALS: build manual PDFs if set to "1"
|
||||
# * NFTABLES: configure nftables support if set to "1" (default)
|
||||
# * PUBLISH: upload manuals after building if set to "1" (ignored without WITH_MANUALS = "1")
|
||||
#
|
||||
PFCP=${PFCP:-1}
|
||||
NFTABLES=${NFTABLES:-1}
|
||||
|
||||
if ! [ -x "$(command -v osmo-build-dep.sh)" ]; then
|
||||
echo "Error: We need to have scripts/osmo-deps.sh from http://git.osmocom.org/osmo-ci/ in PATH !"
|
||||
@@ -30,16 +34,24 @@ export LD_LIBRARY_PATH="$inst/lib"
|
||||
export PATH="$inst/bin:$PATH"
|
||||
|
||||
osmo-build-dep.sh libosmocore "" --disable-doxygen
|
||||
osmo-build-dep.sh libosmo-netif "" --disable-doxygen
|
||||
osmo-build-dep.sh libosmo-abis
|
||||
osmo-build-dep.sh libosmo-netif
|
||||
osmo-build-dep.sh libosmo-sccp
|
||||
osmo-build-dep.sh libosmo-sigtran "" --disable-doxygen
|
||||
osmo-build-dep.sh libasn1c
|
||||
osmo-build-dep.sh osmo-iuh
|
||||
osmo-build-dep.sh osmo-mgw
|
||||
|
||||
# Additional configure options and depends
|
||||
CONFIG=""
|
||||
if [ "$PFCP" = "1" ]; then
|
||||
osmo-build-dep.sh libosmo-pfcp
|
||||
CONFIG="$CONFIG --enable-pfcp"
|
||||
fi
|
||||
if [ "$NFTABLES" = "1" ]; then
|
||||
CONFIG="$CONFIG --enable-nftables"
|
||||
fi
|
||||
if [ "$WITH_MANUALS" = "1" ]; then
|
||||
CONFIG="--enable-manuals"
|
||||
CONFIG="$CONFIG --enable-manuals"
|
||||
fi
|
||||
|
||||
set +x
|
||||
@@ -52,7 +64,7 @@ set -x
|
||||
|
||||
cd "$base"
|
||||
autoreconf --install --force
|
||||
./configure --enable-sanitize --enable-external-tests $CONFIG
|
||||
./configure --enable-sanitize --enable-external-tests --enable-werror $CONFIG
|
||||
$MAKE $PARALLEL_MAKE
|
||||
LD_LIBRARY_PATH="$inst/lib" $MAKE check \
|
||||
|| cat-testlogs.sh
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
#
|
||||
# spec file for package osmo-hnbgw
|
||||
#
|
||||
# Copyright (c) 2017, Martin Hauke <mardnh@gmx.de>
|
||||
#
|
||||
# All modifications and additions to the file contributed by third parties
|
||||
# remain the property of their copyright owners, unless otherwise agreed
|
||||
# upon. The license for this file, and modifications and additions to the
|
||||
# file, is the same license as for the pristine package itself (unless the
|
||||
# license for the pristine package is not an Open Source License, in which
|
||||
# case the license is the MIT License). An "Open Source License" is a
|
||||
# license that conforms to the Open Source Definition (Version 1.9)
|
||||
# published by the Open Source Initiative.
|
||||
|
||||
## Disable LTO for now since it breaks compilation of the tests
|
||||
## https://osmocom.org/issues/4113
|
||||
%define _lto_cflags %{nil}
|
||||
|
||||
Name: osmo-hnbgw
|
||||
Version: @VERSION@
|
||||
Release: 0
|
||||
Summary: OsmoHNBGW: Osmocom's Base Station Controller for 2G CS mobile networks
|
||||
License: AGPL-3.0-or-later AND GPL-2.0-or-later
|
||||
Group: Hardware/Mobile
|
||||
URL: https://osmocom.org/projects/osmohnbgw
|
||||
Source: %{name}-%{version}.tar.xz
|
||||
BuildRequires: automake >= 1.9
|
||||
BuildRequires: libtool >= 2
|
||||
BuildRequires: lksctp-tools-devel
|
||||
BuildRequires: pkgconfig >= 0.20
|
||||
%if 0%{?suse_version}
|
||||
BuildRequires: systemd-rpm-macros
|
||||
%endif
|
||||
BuildRequires: pkgconfig(libcrypto) >= 0.9.5
|
||||
BuildRequires: pkgconfig(libosmo-netif) >= 1.1.0
|
||||
BuildRequires: pkgconfig(libosmo-sigtran) >= 1.5.0
|
||||
BuildRequires: pkgconfig(libosmoabis) >= 1.2.0
|
||||
BuildRequires: pkgconfig(libosmotrau) >= 1.2.0
|
||||
BuildRequires: pkgconfig(libosmocore) >= 1.6.0
|
||||
BuildRequires: pkgconfig(libosmoctrl) >= 1.6.0
|
||||
BuildRequires: pkgconfig(libosmogb) >= 1.6.0
|
||||
BuildRequires: pkgconfig(libosmogsm) >= 1.6.0
|
||||
BuildRequires: pkgconfig(libosmovty) >= 1.6.0
|
||||
BuildRequires: pkgconfig(libosmo-hnbap) >= 1.1.0
|
||||
BuildRequires: pkgconfig(libosmo-ranap) >= 1.1.0
|
||||
BuildRequires: pkgconfig(libosmo-rua) >= 1.1.0
|
||||
BuildRequires: pkgconfig(talloc)
|
||||
BuildRequires: pkgconfig(libasn1c) >= 0.9.30
|
||||
%{?systemd_requires}
|
||||
|
||||
%description
|
||||
OsmoHNBGW: Osmocom's Home NodeB for 3G mobile networks.
|
||||
|
||||
%prep
|
||||
%setup -q
|
||||
|
||||
%build
|
||||
echo "%{version}" >.tarball-version
|
||||
autoreconf -fi
|
||||
%configure \
|
||||
--docdir=%{_docdir}/%{name} \
|
||||
--with-systemdsystemunitdir=%{_unitdir}
|
||||
make %{?_smp_mflags}
|
||||
|
||||
%install
|
||||
%make_install
|
||||
|
||||
%if 0%{?suse_version}
|
||||
%preun
|
||||
%service_del_preun %{name}.service
|
||||
|
||||
%postun
|
||||
%service_del_postun %{name}.service
|
||||
|
||||
%pre
|
||||
%service_add_pre %{name}.service
|
||||
|
||||
%post
|
||||
%service_add_post %{name}.service
|
||||
%endif
|
||||
|
||||
%check
|
||||
make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
|
||||
|
||||
%files
|
||||
%license COPYING
|
||||
%doc AUTHORS README.md
|
||||
%{_bindir}/osmo-hnbgw
|
||||
%dir %{_docdir}/%{name}/examples
|
||||
%dir %{_docdir}/%{name}/examples/osmo-hnbgw
|
||||
%{_docdir}/%{name}/examples/osmo-hnbgw/osmo-hnbgw.cfg
|
||||
%dir %{_sysconfdir}/osmocom
|
||||
%config(noreplace) %{_sysconfdir}/osmocom/osmo-hnbgw.cfg
|
||||
%{_unitdir}/%{name}.service
|
||||
|
||||
%changelog
|
||||
@@ -1,9 +1,16 @@
|
||||
[Unit]
|
||||
Description=Osmocom Home Nodeb Gateway (OsmoHNBGW)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
LimitNOFILE=65536
|
||||
StateDirectory=osmocom
|
||||
WorkingDirectory=%S/osmocom
|
||||
User=osmocom
|
||||
Group=osmocom
|
||||
ExecStart=/usr/bin/osmo-hnbgw -c /etc/osmocom/osmo-hnbgw.cfg
|
||||
RestartSec=2
|
||||
|
||||
|
||||
375
debian/changelog
vendored
375
debian/changelog
vendored
@@ -1,3 +1,378 @@
|
||||
osmo-hnbgw (1.7.0) unstable; urgency=medium
|
||||
|
||||
[ Neels Janosch Hofmeyr ]
|
||||
* drop config.vty tests from make check
|
||||
* coverity CID#358071
|
||||
* coverity CID#358072
|
||||
* coverity CID#358070
|
||||
* coverity CID#358069
|
||||
* on RUA Connect failure, respond with RUA Disconnect
|
||||
* on RUA DT for unknown context, respond with RUA Disconnect
|
||||
|
||||
[ Harald Welte ]
|
||||
* hnb_persistent: Use incrementing counter for rate_ctr + stat_item index
|
||||
* Revert "hnb_persistent: Use incrementing counter for rate_ctr + stat_item index"
|
||||
|
||||
[ Oliver Smith ]
|
||||
* contrib/jenkins: libosmo-sccp -> libosmo-sigtran
|
||||
|
||||
[ Pau Espin Pedrol ]
|
||||
* Log cn_disconnect.ind cause
|
||||
* Fix SCCP RLSD not sent upon rx RUA Disconnect due to error condition
|
||||
* ps_rab_ass_fsm: Fix uninitialized ptr access
|
||||
* Add PFCP stats item group
|
||||
* hnbgw: Avoid using struct osmo_pfcp_cp_peer fields directly
|
||||
* hnbgw: Fix wrong map object retrieved from hashtable
|
||||
* Use new libosmo-sigtran APIs to access osmo_ss7_instance
|
||||
* jenkins.sh: libosmo-netif no longer depends on libosmo-abis
|
||||
* jenkins.sh: No need to build libosmo-sigtran with doxygen
|
||||
* map_sccp: Fix crash during ev MAP_SCCP_EV_RAN_DISC in st MAP_SCCP_ST_WAIT_CC
|
||||
* peek_l3_ul_nas: Split parsing logic based on PS/CS ctx
|
||||
* peek_l3_ul_nas: Improve RAU req parsing checks
|
||||
* peek_l3_ul_nas: Improve GMM Attach Req parsing checks
|
||||
* peek_l3_ul_nas: Parse PTMSI MI from GMM RAU req
|
||||
|
||||
-- Oliver Smith <osmith@sysmocom.de> Wed, 12 Feb 2025 14:37:35 +0100
|
||||
|
||||
osmo-hnbgw (1.6.0) unstable; urgency=medium
|
||||
|
||||
[ Pau Espin Pedrol ]
|
||||
* hnbgw_cn: Remove assert hit due to wrong assumption
|
||||
* Increase default X31 val from 5 to 15 seconds
|
||||
* context_map_sccp: Fix assert hit due to missing ev handling
|
||||
* tests/ranap_rab_ass: Test RAB-Ass.req with X.213 IPv4 address len=7
|
||||
* mgw_fsm: Assume IuUP HNB IP addr = Iuh during MGCP CRCX on RAN side
|
||||
* mgw_fsm: Modify RAB on HNB if IuUP local IP addr at MGW changes during MDCX
|
||||
* hnbap: Avoid calling duplicate getpeername() for each registered HNB
|
||||
|
||||
[ Neels Janosch Hofmeyr ]
|
||||
* X31: fix vty doc
|
||||
* allow (second) CS RAB Assignment Request without RTP info
|
||||
* systemd,manual: set LimitNOFILE=65536
|
||||
* rua: validate correct RUA ctx state per RUA message type
|
||||
* rua: move from ERROR to DEBUG log: stray RUA Disconnect
|
||||
* pfcp: fix modification of wrong FAR ID
|
||||
* tweak vty doc: "UDP port"
|
||||
* add tests/pfcp_cfg.vty.with_pfcp
|
||||
* pfcp: fix missing vty_write of pfcp local-port
|
||||
* pfcp: implement sending Network Instance IEs
|
||||
* fix null deref on hnb_context_release
|
||||
* drop legacy hack: do not start MGW endp in loopback mode
|
||||
* fixup: compilation error: unused var in map_rua_init_action()
|
||||
* fix rate_ctr leak in hnb_persistent_free()
|
||||
* per-HNB GTP-U traffic counters via nft
|
||||
* add hnb_persistent hashtable: optimize lookup by cell id
|
||||
* fix stat_item leak in hnb_persistent_free()
|
||||
* use osmo_jhash for the hnb_persistent hashtable
|
||||
* rename to umts_cell_id_to_str()
|
||||
* umts_cell_id: add umts_cell_id_to_str_buf()
|
||||
* add umts_cell_id_test.c
|
||||
* 3-digit MNC: use osmo_plmn_id in struct umts_cell_id
|
||||
* add LOG_HNBP() for hnb_persistent
|
||||
* hnb_persistent: introduce disconnected timeout
|
||||
* prevent use-after-free after FSM instance termination
|
||||
* improve HNBAP error logging
|
||||
* nft-kpi: log errors of counter retrieval
|
||||
* nft-kpi: remove X34 drifting: adjust delay by elapsed time
|
||||
* drop list of HNBAP UE Context
|
||||
* dbg log: nft kpi: clarify nr of rate ctrs vs nr of hnbp
|
||||
* fix MGCP compat with osmo-mgw <= 1.12.2: CRCX in recvonly
|
||||
|
||||
[ Andreas Eversberg ]
|
||||
* Use uniform log format for default config files
|
||||
|
||||
[ Harald Welte ]
|
||||
* Display RANAP state during 'show cnlink'
|
||||
* Fix license headers: Should have been AGPLv3+, not GPLv2+
|
||||
* [cosmetic] re-order hnbgw.c to group code in major blocks
|
||||
* umts_cell_id_name: Use 3-digit MCC and 2/3-digit MNC based on VTY config
|
||||
* osmo_hnbgw_main: Install our usual SIGUSR1/SIGUSR2/SIGABRT handlers
|
||||
* Introduce umts_cell_id_from_str() as inverse of umts_cell_id_name()
|
||||
* Introduce concept of per-HNB persistent data structure
|
||||
* Set persistent->ctx pointer on HNB REGISTER REQ
|
||||
* New per-hnb rate_ctr and stat_item groups; track uptime
|
||||
* Various new per-hnb RANAP and RUA counters
|
||||
* stats: Introduce CNLINK counter macros
|
||||
* stats: Introduce CNPOOL counter macro
|
||||
* stats: Introduce basic counters for RANAP unit-data from CN links
|
||||
* cosmetic: Fix comment in mgw_fsm.c: One RAB per context_map, not hnb!
|
||||
* cosmetic: talk about cs_rab_ass_* when working in CS domain
|
||||
* cosmetic: Rename hnbgw_rx_ranap and friends to *_rx_ranap_udt_ul
|
||||
* rename hnbgw_peek_l3 to hnbgw_peek_l3_ul as it's uplink only
|
||||
* don't forward paging requests to HNB's not yet registered
|
||||
* vty: Print the uptime during 'show hnb' output
|
||||
* Introduce counter for per-hnb cumulative active CS RAB duration
|
||||
* stats: Add per-hnb paging:{cs,ps}:attempted counter
|
||||
* cosmetic: align downlink RANAP unitdata function names with uplink
|
||||
* mgw_fsm: Add some OSMO_ASSERT() to ensure only CS maps passed
|
||||
* RAB activation/modification/release statistics
|
||||
* context_map_{rua,sccp}: Re-order to always process KPIs
|
||||
* generalize hnbgw_tx_ue_register_rej_tmsi() to hnbgw_tx_ue_register_rej()
|
||||
* HNBAP: Use proper cause values during HNB-REGISTER-REQ processing
|
||||
* HNBAP: Send HNB-REGISTER-REJ on ASN.1 decoding error
|
||||
* HNBAP: Always respond to UE-REGISTER-REQUEST
|
||||
* HNBAP: Make sure to respond with correct "reject"
|
||||
* HNBAP: Transmit ErrorIndication in more situations
|
||||
* HNBAP: use GSM23003_IMSI_MAX_DIGITS instead of magic number
|
||||
* HNBAP: Support IMSI identity type in hnbgw_tx_ue_register_rej()
|
||||
* counters: Distinguish between normal and abnormal release cause
|
||||
* KPI: Add initial set of DTAP message type rate counters
|
||||
* kpi_ranap: Avoid null pointer de-ref during HNB shutdown
|
||||
|
||||
[ Oliver Smith ]
|
||||
* gitignore: add *.la, hnbgw_vty_reference.xml
|
||||
* debian: fix having no manuals in osmo-hnbgw-doc
|
||||
* .deb/.rpm: various fixes related to non-root
|
||||
* contrib: remove rpm spec file
|
||||
* debian/postinst: add checks, be verbose
|
||||
* contrib/jenkins: set --enable-werror
|
||||
|
||||
[ Max ]
|
||||
* .deb/.rpm: add osmocom user during package install
|
||||
|
||||
-- Oliver Smith <osmith@sysmocom.de> Thu, 25 Jul 2024 10:05:58 +0200
|
||||
|
||||
osmo-hnbgw (1.5.0) unstable; urgency=medium
|
||||
|
||||
[ Neels Janosch Hofmeyr ]
|
||||
* UE state leak: when HNB re-registers, discard previous UE state
|
||||
* Deprecate 'sccp cr max-payload-len', remove SCCP CR limit code
|
||||
* cosmetic: drop stray backslash
|
||||
* drop obsolete fixme comment
|
||||
* various comment tweaks
|
||||
* log tweak
|
||||
* cosmetic: rename context_map_deactivate
|
||||
* fix deprecation: use ranap_cn_rx_co_decode2()
|
||||
* rua_to_scu(): drop dead code for connection-less
|
||||
* use RUA procedure code for rua_to_scu() arg
|
||||
* cosmetic: regroup members of hnbgw_context_map
|
||||
* add design charts for new context map FSMs
|
||||
* context map: introduce RUA and SCCP FSMs to fix leaks
|
||||
* log osmo_fsm timeouts
|
||||
* sccp_sap_up(): ignore PCSTATE.ind
|
||||
* fix regression: unbreak PFCP: PS RAB via UPF proxying
|
||||
* tweak LOGHNB()
|
||||
* debug log: log received RANAP message types on DCN, DHNB
|
||||
* map_sccp: on timeout during WAIT_CC, send N-DISCONNECT to SCCP-SCOC
|
||||
* release UE Contexts on HNB (Re-)Register
|
||||
* release UE Contexts on SCTP_RESTART
|
||||
* log: in new RUA,SCCP FSM IDs, indicate CS/PS
|
||||
* vty: 'show ue': show which HNB the UE is registered on
|
||||
* fix vty: show talloc-context all
|
||||
* place asn1 context under hnb_ctx for better visibility
|
||||
* fix asn1 leak in handle_cn_ranap()
|
||||
* fix asn1 leak in error path of hnbgw_tx_ue_register_acc()
|
||||
* fix asn1 leak in error path of hnbgw_tx_ue_register_acc_tmsi()
|
||||
* fix asn1 leaks in ps_rab_ass_resp_send_if_ready()
|
||||
* fix asn1 leak in hnbgw_rua_rx()
|
||||
* less code dup in mem free of hnbgw_rx_ue_register_req()
|
||||
* fix log msg typo 'Unsupportedccept'
|
||||
* actually run vty-tests: VTY transcript tests
|
||||
* actually run vty-tests: osmotestvty.py
|
||||
* rab_ass_fsm.c: fix asn1 memleak when replacing GTP address
|
||||
* ps_rab_ass_fsm.h: fix dup of FSM event enum
|
||||
* drop empty hnbgw_rua_init()
|
||||
* eliminate function hnb_contexts()
|
||||
* vty: fix doc strings for 'show {hnb,ue}'
|
||||
* actually run vty-tests: osmotestconfig.py
|
||||
* drop ctrl_test_runner.py
|
||||
* comment typo fix in ranap_rab_ass.c
|
||||
* simplify: one g_hnbgw as global state and root ctx
|
||||
* ranap_rab_ass_test.c: clarify talloc contexts
|
||||
* move main() to separate file
|
||||
* add non-installed libhnbgw.la for test linkage
|
||||
* vty: make legacy 'hnbgw'/'sccp cr max...' fatal
|
||||
* vty test: show missing write-back of 'rnc-id'
|
||||
* fix missing write-back of rnc-id
|
||||
* drop dead code: cnlink.T_RafC
|
||||
* remove obsolete context_map_check_released() call
|
||||
* unbloat: drop context_map_check_released()
|
||||
* vty_go_parent(): remove legacy cruft
|
||||
* immediately SCCP RLSD on HNB re-register
|
||||
* use new osmo_sccp_instance_next_conn_id()
|
||||
* add doc/examples/osmo-hnbgw/osmo-hnbgw-cs7.cfg
|
||||
* add separate cfg file only for VTY tests
|
||||
* add startup config tests
|
||||
* charts: add sccp.dot
|
||||
* cnpool: allow separate cs7 for IuPS and IuCS
|
||||
* coverity: fix type of local var
|
||||
* cnpool: split up context_map_find_or_create_by_rua_ctx_id()
|
||||
* cnpool prep: add SCCP_EV_USER_ABORT
|
||||
* cnpool: add multiple 'msc' and 'sgsn' cfg (use only the first)
|
||||
* cnpool: examples and config tests: use 'msc 0'/'sgsn 0' syntax
|
||||
* cnpool: make NRI mappings VTY configurable
|
||||
* tdefs; combine timer groups 'ps' and 'cmap' to 'hnbgw'
|
||||
* add hnbgw_decode_ranap_co()
|
||||
* fixup for 'cnpool: split up context_map_find_...'
|
||||
* startup config tests: show default 'msc 0', 'sgsn 0'
|
||||
* cnpool: extract Mobile Identity from RANAP payload
|
||||
* add rate_ctr infra; add rate_ctrs for cnpool
|
||||
* add CTRL transcript tests for cnpool rate ctrs
|
||||
* cnpool: select CN link from pool by NRI or round robin
|
||||
* doc/examples/osmo-hnbgw/osmo-hnbgw-cnpool.cfg
|
||||
* fix umts_cell_id_name(): show CID
|
||||
* make public: umts_cell_id_name()
|
||||
* tweak lots of logging
|
||||
* ctrl test: also test msc 1
|
||||
* cnpool: add context_map_cnlink_lost() handling
|
||||
* add ranap_domain_name()
|
||||
* detect in/active CN links by RANAP RESET
|
||||
* use cnlink state in cnpool decisions
|
||||
* cnpool: return Paging Resp to the exact CN link that Paged
|
||||
* cfg: add 'hnbgw' / 'plmn MCC MNC'
|
||||
* include Global RNC-ID in RESET
|
||||
* include Global RNC-ID in RESET-ACK
|
||||
* RUA: log tweak
|
||||
* coverity: hnbgw_cn: avoid NULL deref in LOGP
|
||||
|
||||
[ Philipp Maier ]
|
||||
* mgw_fsm: fix typo
|
||||
* mgw_fsm: fix log line
|
||||
* mgw_fsm: use __func__ to mention function name in log line
|
||||
* mgw_fsm: refactor helper function handle_rab_release()
|
||||
* ranap_rab_ass: do not free never allocated FieldItems
|
||||
* ranap_rab_ass: be sure to initialize memory with 0
|
||||
|
||||
[ Vadim Yanitskiy ]
|
||||
* tests: use check_PROGRAMS for tests, not noinst_PROGRAMS
|
||||
* tests: use -no-install libtool flag to avoid ./lt-* scripts
|
||||
* tests: make 'vty-test' target depend on osmo-hnbgw binary
|
||||
* tests: do not depend on undefined $(BUILT_SOURCES)
|
||||
* copyright: fix typo: sysmocom s/s.m.f.c./s.f.m.c./ GmbH
|
||||
|
||||
[ Oliver Smith ]
|
||||
* hnbgw_mgw_setup: use mgcp_client_pool_empty()
|
||||
* debian: set compat level to 10
|
||||
* contrib/jenkins.sh: add PFCP variable
|
||||
* hnbgw: put copyright into define
|
||||
* systemd: depend on networking-online.target
|
||||
|
||||
[ Pau Espin Pedrol ]
|
||||
* manuals: include osmo-gsm-manual's sigtran.adoc
|
||||
* tests: Update *.vty after libosmo-sccp VTY improvements
|
||||
* Use new mgcp_client_conf_alloc() API to alloc mgcp_client_conf
|
||||
* Tear down call if local IuUP MGW address changed during MDCX
|
||||
|
||||
-- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 12 Sep 2023 17:18:43 +0200
|
||||
|
||||
osmo-hnbgw (1.4.0) unstable; urgency=medium
|
||||
|
||||
[ Pau Espin Pedrol ]
|
||||
* hnbgw_cn.c: Guard against null ss7 ptr during init
|
||||
* cosmetic: Fix typo in log and whitespace
|
||||
* hnbgw: Log new SCTP HNB connections
|
||||
* hnb_context_release(): Make sure assigned conn is freed
|
||||
* Improve logging around hnb_context and sctp conn lifecycle
|
||||
* Change log level about conn becoming closed to NOTICE
|
||||
* hnbgw: Unregister HNB if SCTP link is restarted
|
||||
* hnbgw: Fix recent regression not closing conn upon rx of SCTP_SHUTDOWN_EVENT
|
||||
* hnbap: Accept duplicated HNB Register Request on same conn
|
||||
* hnbap: Improve logging around HNBAP HNB Register Request
|
||||
* Workaround bug where old hnb_context from same remote addr+port is kept
|
||||
* Fix handling of sctp SCTP_SHUTDOWN_EVENT notification
|
||||
* Close conn when receiving SCTP_ASSOC_CHANGE notification
|
||||
* hnb_read_cb: use local var to reduce get_ofd() calls
|
||||
* hnb_read_cb(): -EBADF must be returned if conn is freed to avoid use-after-free
|
||||
* Clear SCTP tx queue upon SCTP RESTART notification
|
||||
* Makefile.am: Drop duplicated LIBOSMOMGCPCLIENT_LIBS
|
||||
* Introduce support for libosmo-mgcp-client MGW pooling
|
||||
* doc: Include mgwpool.adoc from osmo-gsm-manuals
|
||||
* vty: Fix timers not printed when dumping running-config
|
||||
* hnbgw: Avoid allocating SCCP conn id >0x00fffffe
|
||||
* context_map: Lower loglevel to INFO when deallocating context IDs
|
||||
|
||||
[ Neels Hofmeyr ]
|
||||
* mgw_fsm: move MGCP timeout to mgw_fsm_T_defs
|
||||
* fix test_ranap_rab_ass_resp_decode_encode
|
||||
* ranap_rab_ass_req_encode(): return msgb
|
||||
* add ps_rab_ass FSM to map GTP via UPF
|
||||
* reduce code dup in handle_cn_data_ind()
|
||||
* build: add --enable-pfcp, make PFCP dep optional
|
||||
* optimize: decode PS msgs only when PFCP is enabled
|
||||
* ps_rab_fsm: check use cb success
|
||||
* add example osmo-hnbgw-pfcp.cfg
|
||||
* manual: add missing bit on the MGCP port
|
||||
* manual: update overview chart with PFCP
|
||||
* manual: update IuCS/IuPS protocol stack chart
|
||||
* manual: explain the PFCP port
|
||||
* example cfg: tweak logging
|
||||
* debian,RPM: package with PFCP support
|
||||
|
||||
[ Vadim Yanitskiy ]
|
||||
* tests/ranap_rab_ass: fix potential NULL pointer dereferences
|
||||
* configure.ac: do not require unused dlopen
|
||||
|
||||
[ Oliver Smith ]
|
||||
* rpm spec: add osmo-hnbgw-pfcp.cfg
|
||||
|
||||
[ Daniel Willmann ]
|
||||
* Install show talloc-context VTY commands
|
||||
* hnbgw_hnbap: Fix memory leaks in HNBAP handling
|
||||
|
||||
[ Harald Welte ]
|
||||
* packate the new osmo-hnbgw-pfcp.cfg example config file
|
||||
* cosmetic: Fix typos
|
||||
* Abort if processing SCTP connection without HNB context
|
||||
* hnbgw_rx_hnb_deregister: Don't call hnb_context_release()
|
||||
* Don't process RUA messages if HNB is not registered
|
||||
* Don't permit anything but HNB (de)registration until HNB is registered
|
||||
|
||||
[ Neels Janosch Hofmeyr ]
|
||||
* fix regression: in RUA, do PFCP only when enabled
|
||||
* do not depend on libosmo-gtlv
|
||||
* drop bogus error log 'no MGW fsm'
|
||||
* fix segfault on MGCP timeout
|
||||
* fix msgb leak for RANAP RAB Ass. Req.
|
||||
* fix possible leak of ue_context on UE REGISTER error
|
||||
* fix SCCP conn leak on non-graceful HNB shutdown
|
||||
* coverity: hnbgw_rua.c: remove redundant check
|
||||
|
||||
[ Max ]
|
||||
* Set working directory in systemd service file
|
||||
* ctrl: take both address and port from vty config
|
||||
|
||||
[ arehbein ]
|
||||
* osmo-hnbgw: Transition to use of 'telnet_init_default'
|
||||
|
||||
-- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 07 Feb 2023 18:05:46 +0100
|
||||
|
||||
osmo-hnbgw (1.3.0) unstable; urgency=medium
|
||||
|
||||
[ Philipp Maier ]
|
||||
* hnbgw_cn.c: fix sourcecode formatting
|
||||
* hbgw_hnbap: use osmo_plmn_from_bcd instead of gsm48_mcc_mnc_from_bcd
|
||||
* ranap_rab_ass: add decoder and rewrite functions for RAB-AssignmentRequest/Response
|
||||
* ranap_rab_ass: ensure specific rab_id
|
||||
* ranap_rab_ass: add function to check if RAB is in FailureList
|
||||
* ranap_rab_ass: add function to check if RAB is in ReleaseList
|
||||
* ranap_rab_ass_test: cosmetic: correct test function names
|
||||
* mgw_fsm: add MGW support to osmo-hnbgw
|
||||
* overview.adoc: update network diagram
|
||||
* running.adoc: explain MGW configuration
|
||||
* osmo-hnbgw.cfg: use local port 2729 as default for MGCP client
|
||||
* mgw_fsm: release call when FSM is not created
|
||||
* ranap_rab_ass: check for more than one RAB assignment req
|
||||
|
||||
[ Neels Hofmeyr ]
|
||||
* use osmo_select_main_ctx(), tweak log in handle_cn_conn_conf()
|
||||
* allow calling rua_to_scu() without data
|
||||
* tweak comments in rua_to_scu()
|
||||
* add option to send SCCP CR without payload
|
||||
* fix segfault in error handling for mgw_fi == NULL
|
||||
|
||||
[ Pau Espin Pedrol ]
|
||||
* mgw_fsm: Mark structs as static const
|
||||
* mgw_fsm: Improve logging
|
||||
* cosmetic: mgw_fsm: Fix typo in log
|
||||
* mgw_fsm: Change macro to not use local variables implicitly
|
||||
* mgw_fsm: Fix error path accessing uninitialized fsm ptr
|
||||
* mgw_fsm: Simplify cleanup paths
|
||||
|
||||
[ Harald Welte ]
|
||||
* update URLs (git -> https; gitea)
|
||||
|
||||
-- Pau Espin Pedrol <pespin@sysmocom.de> Wed, 29 Jun 2022 12:42:35 +0200
|
||||
|
||||
osmo-hnbgw (1.2.1) unstable; urgency=medium
|
||||
|
||||
* Do not turn some compiler warnings into errors by default
|
||||
|
||||
2
debian/compat
vendored
2
debian/compat
vendored
@@ -1 +1 @@
|
||||
9
|
||||
10
|
||||
|
||||
25
debian/control
vendored
25
debian/control
vendored
@@ -2,7 +2,7 @@ Source: osmo-hnbgw
|
||||
Section: net
|
||||
Priority: extra
|
||||
Maintainer: Osmocom team <openbsc@lists.osmocom.org>
|
||||
Build-Depends: debhelper (>=9),
|
||||
Build-Depends: debhelper (>= 10),
|
||||
dh-autoreconf,
|
||||
autotools-dev,
|
||||
autoconf,
|
||||
@@ -13,17 +13,20 @@ Build-Depends: debhelper (>=9),
|
||||
libtalloc-dev,
|
||||
libasn1c-dev (>= 0.9.30),
|
||||
libsctp-dev,
|
||||
libosmocore-dev (>= 1.6.0),
|
||||
libosmo-sigtran-dev (>= 1.5.0),
|
||||
libosmo-abis-dev (>= 1.2.0),
|
||||
libosmo-netif-dev (>= 1.1.0),
|
||||
libosmo-hnbap-dev (>= 1.1.0),
|
||||
libosmo-ranap-dev (>= 1.1.0),
|
||||
libosmo-rua-dev (>= 1.1.0),
|
||||
osmo-gsm-manuals-dev (>= 1.2.0)
|
||||
libosmocore-dev (>= 1.11.0),
|
||||
libosmo-sigtran-dev (>= 2.1.0),
|
||||
libosmo-abis-dev (>= 2.0.0),
|
||||
libosmo-netif-dev (>= 1.6.0),
|
||||
libosmo-mgcp-client-dev (>= 1.14.0),
|
||||
libosmo-hnbap-dev (>= 1.7.0),
|
||||
libosmo-ranap-dev (>= 1.7.0),
|
||||
libosmo-rua-dev (>= 1.7.0),
|
||||
libosmo-pfcp-dev (>= 0.5.0),
|
||||
libnftables-dev,
|
||||
osmo-gsm-manuals-dev (>= 1.6.0)
|
||||
Standards-Version: 3.9.8
|
||||
Vcs-Git: git://git.osmocom.org/osmo-hnbgw.git
|
||||
Vcs-Browser: https://git.osmocom.org/osmo-hnbgw/
|
||||
Vcs-Git: https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw
|
||||
Vcs-Browser: https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw
|
||||
Homepage: https://projects.osmocom.org/projects/osmo-hnbgw
|
||||
|
||||
Package: osmo-hnbgw
|
||||
|
||||
2
debian/copyright
vendored
2
debian/copyright
vendored
@@ -1,6 +1,6 @@
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: osmo-hnbgw
|
||||
Source: git://git.osmocom.org/osmo-hnbgw
|
||||
Source: https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw
|
||||
|
||||
Files: *
|
||||
Copyright: 2021 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
|
||||
2
debian/osmo-hnbgw-doc.install
vendored
Normal file
2
debian/osmo-hnbgw-doc.install
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
usr/share/doc/osmo-hnbgw-doc/*.pdf
|
||||
|
||||
3
debian/osmo-hnbgw.install
vendored
3
debian/osmo-hnbgw.install
vendored
@@ -2,3 +2,6 @@ etc/osmocom/osmo-hnbgw.cfg
|
||||
lib/systemd/system/osmo-hnbgw.service
|
||||
usr/bin/osmo-hnbgw
|
||||
usr/share/doc/osmo-hnbgw/examples/osmo-hnbgw/osmo-hnbgw.cfg usr/share/doc/osmo-hnbgw/examples
|
||||
usr/share/doc/osmo-hnbgw/examples/osmo-hnbgw/osmo-hnbgw-pfcp.cfg usr/share/doc/osmo-hnbgw/examples
|
||||
usr/share/doc/osmo-hnbgw/examples/osmo-hnbgw/osmo-hnbgw-cs7.cfg usr/share/doc/osmo-hnbgw/examples
|
||||
usr/share/doc/osmo-hnbgw/examples/osmo-hnbgw/osmo-hnbgw-cnpool.cfg usr/share/doc/osmo-hnbgw/examples
|
||||
|
||||
38
debian/postinst
vendored
Executable file
38
debian/postinst
vendored
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh -e
|
||||
case "$1" in
|
||||
configure)
|
||||
# Create the osmocom group and user (if it doesn't exist yet)
|
||||
if ! getent group osmocom >/dev/null; then
|
||||
groupadd --system osmocom
|
||||
fi
|
||||
if ! getent passwd osmocom >/dev/null; then
|
||||
useradd \
|
||||
--system \
|
||||
--gid osmocom \
|
||||
--home-dir /var/lib/osmocom \
|
||||
--shell /sbin/nologin \
|
||||
--comment "Open Source Mobile Communications" \
|
||||
osmocom
|
||||
fi
|
||||
|
||||
# Fix permissions of previous (root-owned) install (OS#4107)
|
||||
if dpkg --compare-versions "$2" le "1.6.0"; then
|
||||
if [ -e /etc/osmocom/osmo-hnbgw.cfg ]; then
|
||||
chown -v osmocom:osmocom /etc/osmocom/osmo-hnbgw.cfg
|
||||
chmod -v 0660 /etc/osmocom/osmo-hnbgw.cfg
|
||||
fi
|
||||
|
||||
if [ -d /etc/osmocom ]; then
|
||||
chown -v root:osmocom /etc/osmocom
|
||||
chmod -v 2775 /etc/osmocom
|
||||
fi
|
||||
|
||||
mkdir -p /var/lib/osmocom
|
||||
chown -R -v osmocom:osmocom /var/lib/osmocom
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# dh_installdeb(1) will replace this with shell code automatically
|
||||
# generated by other debhelper scripts.
|
||||
#DEBHELPER#
|
||||
14
debian/rules
vendored
14
debian/rules
vendored
@@ -44,10 +44,18 @@
|
||||
%:
|
||||
dh $@ --with autoreconf
|
||||
|
||||
# debmake generated override targets
|
||||
CONFIGURE_FLAGS += --with-systemdsystemunitdir=/lib/systemd/system --enable-manuals
|
||||
# libnftables is too old in Debian 10 (OS#6425)
|
||||
override_dh_auto_configure:
|
||||
dh_auto_configure -- $(CONFIGURE_FLAGS)
|
||||
CONFIGURE_FLAGS=" \
|
||||
--enable-manuals \
|
||||
--enable-pfcp \
|
||||
--with-systemdsystemunitdir=/lib/systemd/system \
|
||||
"; \
|
||||
if pkg-config --exists libnftables --atleast-version=1.0.2; then \
|
||||
CONFIGURE_FLAGS="$$CONFIGURE_FLAGS --enable-nftables"; \
|
||||
fi; \
|
||||
dh_auto_configure -- $$CONFIGURE_FLAGS
|
||||
|
||||
#
|
||||
# Do not install libtool archive, python .pyc .pyo
|
||||
#override_dh_install:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
SUBDIRS = \
|
||||
examples \
|
||||
manuals \
|
||||
charts \
|
||||
$(NULL)
|
||||
|
||||
20
doc/charts/Makefile.am
Normal file
20
doc/charts/Makefile.am
Normal file
@@ -0,0 +1,20 @@
|
||||
msc: \
|
||||
$(builddir)/hnbgw_context_map.png \
|
||||
$(NULL)
|
||||
|
||||
dot: \
|
||||
$(builddir)/hnbgw_context_map_fsm.png \
|
||||
$(builddir)/sccp.png \
|
||||
$(NULL)
|
||||
|
||||
$(builddir)/%.png: $(srcdir)/%.msc
|
||||
mscgen -T png -o $@ $<
|
||||
|
||||
$(builddir)/%.png: $(srcdir)/%.dot
|
||||
dot -Tpng $< > $@
|
||||
|
||||
.PHONY: poll
|
||||
# remember current dir: in case the build process removes and recreates the dir, re-enter the new dir
|
||||
W := "$(PWD)"
|
||||
poll:
|
||||
while true; do $(MAKE) -C $W msc dot; sleep 1; done
|
||||
107
doc/charts/hnbgw_context_map.msc
Normal file
107
doc/charts/hnbgw_context_map.msc
Normal file
@@ -0,0 +1,107 @@
|
||||
msc {
|
||||
hscale="2";
|
||||
hnb[label="HNB"],rua[label="OsmoHNBGW\nRUA FSM / HNBAP"],sccp[label="OsmoHNBGW\nSCCP FSM"],cn[label="CN"];
|
||||
|
||||
hnb <=> rua [label="RANAP/RUA\n(Iuh)"];
|
||||
rua <=> sccp [label="FSM events"];
|
||||
sccp <=> cn [label="RANAP/SCCP/M3UA\n(IuCS, IuPS)"];
|
||||
|
||||
...;
|
||||
...;
|
||||
--- [label="Create a new context map (always from HNB)"];
|
||||
hnb => rua [label="RUA Connect\nRANAP InitialUE-Message\nDTAP Location Updating Request"];
|
||||
rua rbox sccp [label="hnbgw_context_map_alloc()"];
|
||||
rua abox rua [label="MAP_RUA_ST_INIT"];
|
||||
sccp abox sccp [label="MAP_SCCP_ST_INIT"];
|
||||
rua rbox rua [label="MAP_RUA_EV_RX_CONNECT\ndata = ranap_msg"];
|
||||
rua abox rua [label="MAP_RUA_ST_CONNECTED"];
|
||||
rua => sccp [label="MAP_SCCP_EV_TX_RANAP_MSG\ndata = ranap_msg"];
|
||||
sccp abox sccp [label="MAP_SCCP_ST_WAIT_CC"];
|
||||
sccp => cn [label="SCCP Connection Request\nRANAP InitialUE-Message\nDTAP Location Updating Request"];
|
||||
...;
|
||||
sccp <= cn [label="SCCP Connection Confirm"];
|
||||
sccp abox sccp [label="MAP_SCCP_ST_CONNECTED"];
|
||||
--- [label="if SCCP CC with payload"];
|
||||
rua <= sccp [label="MAP_RUA_EV_TX_RANAP_MSG\ndata = ranap_msg"];
|
||||
hnb <= rua [label="RUA DirectTransfer"];
|
||||
|
||||
...;
|
||||
...;
|
||||
--- [label="Layer 3 communication"];
|
||||
sccp <= cn [label="SCCP Data Form 1"];
|
||||
rua <= sccp [label="MAP_RUA_EV_TX_RANAP_MSG\ndata = ranap_msg"];
|
||||
hnb <= rua [label="RUA DirectTransfer"];
|
||||
...;
|
||||
hnb => rua [label="RUA DirectTransfer"];
|
||||
rua => sccp [label="MAP_SCCP_EV_TX_RANAP_MSG\ndata = ranap_msg"];
|
||||
sccp => cn [label="SCCP Data Form 1"];
|
||||
|
||||
...;
|
||||
...;
|
||||
--- [label="Usual release"];
|
||||
sccp <= cn [label="SCCP Data Form 1\nIu-ReleaseCommand"];
|
||||
rua <= sccp [label="MAP_RUA_EV_TX_RANAP_MSG\ndata = ranap_msg"];
|
||||
hnb <= rua [label="RUA DirectTransfer\nIu-ReleaseCommand"];
|
||||
...;
|
||||
hnb => rua [label="RUA Disconnect\nIu-ReleaseComplete"];
|
||||
rua rbox rua [label="MAP_RUA_EV_RX_DISCONNECT"];
|
||||
rua abox rua [label="MAP_RUA_ST_DISCONNECTED"];
|
||||
rua => sccp [label="MAP_SCCP_EV_RAN_DISC\ndata = ranap_msg"];
|
||||
sccp => cn [label="SCCP Data Form 1\nIu-ReleaseComplete\nNOT 'SCCP Released'! See 3GPP TS 48.006 9.2"];
|
||||
sccp abox sccp [label="MAP_SCCP_ST_WAIT_RLSD"];
|
||||
...;
|
||||
sccp <= cn [label="SCCP Released"];
|
||||
sccp abox sccp [label="MAP_SCCP_ST_DISCONNECTED"];
|
||||
rua rbox sccp [label="map_check_released()"];
|
||||
rua rbox sccp [label="hnbgw_context_map_free()"];
|
||||
|
||||
...;
|
||||
...;
|
||||
--- [label="Ungraceful release from CN"];
|
||||
sccp <= cn [label="SCCP Released"];
|
||||
sccp => cn [label="SCCP Release Complete\n(implicit by libosmo-sigtran)"];
|
||||
sccp rbox sccp [label="MAP_SCCP_EV_RX_RELEASED"];
|
||||
sccp note sccp [label="In SCCP there is no \"link loss\".\nIn every case, we will be notified\nvia N-Disconnect prim per SCCP conn."];
|
||||
sccp abox sccp [label="MAP_SCCP_ST_DISCONNECTED"];
|
||||
rua <= sccp [label="MAP_RUA_EV_CN_DISC"];
|
||||
hnb <= rua [label="RUA Disconnect"];
|
||||
rua abox rua [label="MAP_RUA_ST_DISRUPTED"];
|
||||
rua rbox sccp [label="map_check_released()"];
|
||||
rua rbox sccp [label="hnbgw_context_map_free()"];
|
||||
|
||||
...;
|
||||
...;
|
||||
...;
|
||||
--- [label="PCSTATE handling"];
|
||||
sccp << cn [label="N-PCSTATE.indication\npoint-code unreachable"];
|
||||
sccp note sccp [label="currently ignored"];
|
||||
|
||||
...;
|
||||
...;
|
||||
--- [label="Ungraceful release from HNB"];
|
||||
hnb -x rua [label="link loss"];
|
||||
rua rbox rua [label="MAP_RUA_EV_HNB_LINK_LOST"];
|
||||
rua abox rua [label="MAP_RUA_ST_DISRUPTED"];
|
||||
rua => sccp [label="MAP_SCCP_EV_RAN_LINK_LOST"];
|
||||
sccp => cn [label="SCCP Released"];
|
||||
sccp abox sccp [label="MAP_SCCP_ST_DISCONNECTED"];
|
||||
rua rbox sccp [label="map_check_released()"];
|
||||
rua rbox sccp [label="hnbgw_context_map_free()"];
|
||||
|
||||
...;
|
||||
...;
|
||||
--- [label="Ungraceful release from HNB De-Register"];
|
||||
hnb => rua [label="HNBAP HNB De-Register"];
|
||||
--- [label="or"];
|
||||
hnb => rua [label="HNBAP HNB Register\n(HNB restarted)"];
|
||||
---;
|
||||
rua rbox rua [label="MAP_RUA_EV_HNB_LINK_LOST"];
|
||||
hnb <= rua [label="RUA Disconnect"];
|
||||
hnb note rua [label="TODO: does it make sense to send\nRUA Disconnect per context when HNB is/was gone?\nOr has the HNB implicitly discarded these?"];
|
||||
rua abox rua [label="MAP_RUA_ST_DISRUPTED"];
|
||||
rua => sccp [label="MAP_SCCP_EV_RAN_LINK_LOST\nfrom map_rua_disrupted_onenter()"];
|
||||
sccp => cn [label="SCCP Released"];
|
||||
sccp abox sccp [label="MAP_SCCP_ST_DISCONNECTED"];
|
||||
rua rbox sccp [label="map_check_released()"];
|
||||
rua rbox sccp [label="hnbgw_context_map_free()"];
|
||||
}
|
||||
62
doc/charts/hnbgw_context_map_fsm.dot
Normal file
62
doc/charts/hnbgw_context_map_fsm.dot
Normal file
@@ -0,0 +1,62 @@
|
||||
digraph G {
|
||||
rankdir=TB
|
||||
labelloc=t
|
||||
label="hnb_context_map\nRUA and SCCP FSMs"
|
||||
|
||||
HNB_connect [label="HNB",shape=box3d]
|
||||
rua_to_scu_connect [label="rua_to_scu()",shape=box]
|
||||
HNB_DT [label="HNB",shape=box3d]
|
||||
rua_to_scu_DT [label="rua_to_scu()",shape=box]
|
||||
HNB_DT2 [label="HNB",shape=box3d]
|
||||
HNB_disconnect [label="HNB",shape=box3d]
|
||||
rua_to_scu_disconnect [label="rua_to_scu()",shape=box]
|
||||
CN [shape=box3d]
|
||||
CN2 [label="CN",shape=box3d]
|
||||
CN3 [label="CN",shape=box3d]
|
||||
CN4 [label="CN",shape=box3d]
|
||||
|
||||
HNB_connect -> rua_to_scu_connect [label="RUA Connect",style=dotted]
|
||||
rua_to_scu_connect -> create [label="(1)",style=dotted]
|
||||
create [label="hnbgw_context_map_alloc()",shape=box]
|
||||
create -> {RUA_INIT,SCCP_INIT} [label="(1)",style="dashed,bold"]
|
||||
|
||||
rua_to_scu_connect -> RUA_CONNECTED [label="(2)\nMAP_RUA_EV_RX_CONNECT",style=dashed]
|
||||
RUA_CONNECTED -> SCCP_WAIT_CC [label="(2)\nMAP_SCCP_EV_TX_RANAP_MSG",style=dashed]
|
||||
|
||||
RUA_INIT -> RUA_CONNECTED -> RUA_DISCONNECTED [style=bold]
|
||||
|
||||
SCCP_INIT -> SCCP_WAIT_CC -> SCCP_CONNECTED -> SCCP_WAIT_RLSD -> SCCP_DISCONNECTED [style=bold]
|
||||
SCCP_CONNECTED -> SCCP_DISCONNECTED [label="SCCP RLSD"]
|
||||
|
||||
SCCP_WAIT_CC -> CN [label="(2) SCCP CR",style=dotted]
|
||||
CN -> SCCP_CONNECTED [label="(3) SCCP CC",style=dashed]
|
||||
|
||||
CN4 -> SCCP_CONNECTED [label="(4) SCCP\nData Form 1",style=dotted]
|
||||
SCCP_CONNECTED -> RUA_CONNECTED [label="(4)\nMAP_RUA_EV_TX_RANAP_MSG",style=dotted]
|
||||
RUA_CONNECTED -> HNB_DT2 [label="(4)\nRUA DirectTransfer",style=dotted]
|
||||
|
||||
HNB_DT -> rua_to_scu_DT [label="(5)\nRUA DirectTransfer",style=dotted]
|
||||
rua_to_scu_DT -> RUA_CONNECTED [label="(5)\nMAP_RUA_EV_RX_DIRECT_TRANSFER",style=dotted]
|
||||
RUA_CONNECTED -> SCCP_CONNECTED [label="(5)\nMAP_SCCP_EV_TX_RANAP_MSG",style=dotted]
|
||||
SCCP_CONNECTED -> CN3 [label="(5) SCCP\nData Form 1",style=dotted]
|
||||
|
||||
HNB_disconnect -> rua_to_scu_disconnect [label="(6) RUA Id-Disconnect",style=dotted]
|
||||
rua_to_scu_disconnect -> RUA_DISCONNECTED [label="(6) MAP_RUA_EV_RX_DISCONNECT",style=dashed]
|
||||
RUA_DISCONNECTED -> SCCP_WAIT_RLSD [label="(6) MAP_SCCP_EV_RAN_DISC",style=dashed]
|
||||
|
||||
CN2 -> SCCP_DISCONNECTED [label="(7) SCCP RLSD",style=dashed]
|
||||
|
||||
{RUA_DISCONNECTED,SCCP_DISCONNECTED} -> release [label="onenter",style="dotted,bold"]
|
||||
release [label="map_check_released()",shape=box]
|
||||
release -> terminate [label="when both RUA and\nSCCP disconnected",style=dotted]
|
||||
terminate [label="hnbgw_context_map_free()",shape=octagon]
|
||||
|
||||
subgraph cluster_legend {
|
||||
label=""
|
||||
style=invisible
|
||||
A, B, C, D [style=invisible]
|
||||
A -> B [label="event without\nstate change",style=dotted]
|
||||
C -> D [label="event changes\nstate to...",style=dashed]
|
||||
}
|
||||
|
||||
}
|
||||
78
doc/charts/sccp.dot
Normal file
78
doc/charts/sccp.dot
Normal file
@@ -0,0 +1,78 @@
|
||||
digraph G {
|
||||
rankdir=TB
|
||||
labelloc=t; label="SCCP/SS7 use in OsmoHNBGW"
|
||||
|
||||
subgraph cluster_ss0 {
|
||||
label=""
|
||||
ss0 [label="cs7 instance 0\n local pc: 1.1.1"]
|
||||
ss0ab [label="address book"]
|
||||
ss0 -> ss0ab
|
||||
|
||||
msc0a [label="2.2.2"]
|
||||
sgsn0a [label="3.3.3"]
|
||||
msc1a [label="5.5.5"]
|
||||
ss0ab -> msc0a,sgsn0a,msc1a
|
||||
}
|
||||
|
||||
sccp0 [label="hnbgw_sccp_user for '0'\n .osmo_sccp_instance\n .osmo_ss7_user SSN: RANAP"];
|
||||
ss0 -> sccp0 [dir=back]
|
||||
|
||||
msc0 [label="hnbgw_cnlink 'msc0'\n .domain = IuCS\n .sccp_addr -> 2.2.2"]
|
||||
sccp0 -> msc0 [dir=back]
|
||||
msc0a -> msc0 [dir=back]
|
||||
|
||||
cs0 [label="UE CS conn\n hnbgw_context_map"]
|
||||
cs1 [label="UE CS conn\n hnbgw_context_map"]
|
||||
msc0 -> cs0,cs1
|
||||
|
||||
sgsn0 [label="hnbgw_cnlink 'sgsn0'\n .domain = IuPS\n .sccp_addr -> 3.3.3"]
|
||||
sccp0 -> sgsn0 [dir=back]
|
||||
sgsn0a -> sgsn0 [dir=back]
|
||||
|
||||
ps0 [label="UE PS conn\n hnbgw_context_map"]
|
||||
sgsn0 -> ps0
|
||||
|
||||
subgraph cluster_ss1 {
|
||||
label=""
|
||||
ss1 [label="cs7 instance 1\n local pc: 4.4.4"]
|
||||
ss1ab [label="address book"]
|
||||
ss1 -> ss1ab
|
||||
|
||||
sgsn1a [label="6.6.6"]
|
||||
ss1ab -> sgsn1a
|
||||
}
|
||||
|
||||
sccp1 [label="hnbgw_sccp_user for '1'\n .osmo_sccp_instance\n .osmo_ss7_user SSN: RANAP"];
|
||||
ss1 -> sccp1 [dir=back]
|
||||
|
||||
msc1 [label="hnbgw_cnlink 'msc1'\n .domain = IuCS\n .sccp_addr -> 5.5.5"]
|
||||
sccp0 -> msc1 [dir=back]
|
||||
msc1a -> msc1 [dir=back]
|
||||
|
||||
cs2 [label="UE CS conn\n hnbgw_context_map"]
|
||||
msc1 -> cs2
|
||||
|
||||
sgsn1 [label="hnbgw_cnlink 'sgsn1'\n .domain = IuPS\n .sccp_addr -> 6.6.6"]
|
||||
sccp1 -> sgsn1 [dir=back]
|
||||
sgsn1a -> sgsn1 [dir=back]
|
||||
|
||||
ps2 [label="UE PS conn\n hnbgw_context_map"]
|
||||
sgsn1 -> ps2
|
||||
|
||||
subgraph cluster_sccp_inst {
|
||||
label="global sccp.users (llist)"
|
||||
sccp0
|
||||
sccp1
|
||||
}
|
||||
|
||||
subgraph cluster_cnpool_iucs {
|
||||
label="global cnpool_iucs"
|
||||
msc0
|
||||
msc1
|
||||
}
|
||||
subgraph cluster_cnpool_iups {
|
||||
label="global cnpool_iups"
|
||||
sgsn0
|
||||
sgsn1
|
||||
}
|
||||
}
|
||||
40
doc/examples/osmo-hnbgw/osmo-hnbgw-cnpool.cfg
Normal file
40
doc/examples/osmo-hnbgw/osmo-hnbgw-cnpool.cfg
Normal file
@@ -0,0 +1,40 @@
|
||||
log stderr
|
||||
logging filter all 1
|
||||
logging color 1
|
||||
logging print category-hex 0
|
||||
logging print category 1
|
||||
logging timestamp 0
|
||||
logging print file basename last
|
||||
logging print level 1
|
||||
logging level set-all notice
|
||||
|
||||
cs7 instance 0
|
||||
point-code 0.42.0
|
||||
|
||||
sccp-address my-msc-0
|
||||
point-code 0.1.0
|
||||
sccp-address my-msc-1
|
||||
point-code 0.1.1
|
||||
sccp-address my-sgsn-0
|
||||
point-code 0.2.0
|
||||
|
||||
hnbgw
|
||||
plmn 001 01
|
||||
iucs
|
||||
nri bitlen 10
|
||||
nri null add 0 7
|
||||
iups
|
||||
nri bitlen 8
|
||||
nri null add 0 5
|
||||
|
||||
msc 0
|
||||
remote-addr my-msc-0
|
||||
nri add 128 255
|
||||
|
||||
msc 1
|
||||
remote-addr my-msc-1
|
||||
nri add 256 511
|
||||
|
||||
sgsn 0
|
||||
remote-addr my-sgsn-0
|
||||
nri add 128 255
|
||||
29
doc/examples/osmo-hnbgw/osmo-hnbgw-cs7.cfg
Normal file
29
doc/examples/osmo-hnbgw/osmo-hnbgw-cs7.cfg
Normal file
@@ -0,0 +1,29 @@
|
||||
cs7 instance 0
|
||||
point-code 0.3.0
|
||||
asp asp-clnt-hnbgw-0 2905 0 m3ua
|
||||
remote-ip 127.0.0.1
|
||||
role asp
|
||||
sctp-role client
|
||||
sccp-address my-msc
|
||||
point-code 1.2.3
|
||||
sccp-address my-sgsn
|
||||
point-code 1.4.2
|
||||
|
||||
hnbgw
|
||||
plmn 001 01
|
||||
iuh
|
||||
local-ip 0.0.0.0
|
||||
msc 0
|
||||
remote-addr my-msc
|
||||
sgsn 0
|
||||
remote-addr my-sgsn
|
||||
|
||||
log stderr
|
||||
logging filter all 1
|
||||
logging color 1
|
||||
logging print category-hex 0
|
||||
logging print category 1
|
||||
logging timestamp 0
|
||||
logging print file basename last
|
||||
logging print level 1
|
||||
logging level set-all notice
|
||||
21
doc/examples/osmo-hnbgw/osmo-hnbgw-pfcp.cfg
Normal file
21
doc/examples/osmo-hnbgw/osmo-hnbgw-pfcp.cfg
Normal file
@@ -0,0 +1,21 @@
|
||||
log stderr
|
||||
logging filter all 1
|
||||
logging color 1
|
||||
logging print category-hex 0
|
||||
logging print category 1
|
||||
logging timestamp 0
|
||||
logging print file basename last
|
||||
logging print level 1
|
||||
logging level set-all notice
|
||||
hnbgw
|
||||
plmn 001 01
|
||||
iuh
|
||||
local-ip 0.0.0.0
|
||||
mgw 0
|
||||
remote-ip 127.0.0.1
|
||||
local-port 2729
|
||||
remote-port 2427
|
||||
reset-endpoint rtpbridge/*
|
||||
pfcp
|
||||
remote-addr 127.0.0.2
|
||||
local-addr 127.0.0.1
|
||||
@@ -1,25 +1,19 @@
|
||||
!
|
||||
! OsmoHNBGW (0) configuration saved from vty
|
||||
!!
|
||||
!
|
||||
log stderr
|
||||
logging filter all 1
|
||||
logging color 1
|
||||
logging print category-hex 0
|
||||
logging print category 1
|
||||
logging timestamp 1
|
||||
logging print extended-timestamp 1
|
||||
logging level all debug
|
||||
logging level lglobal notice
|
||||
logging level llapd notice
|
||||
logging level linp notice
|
||||
logging level lmux notice
|
||||
logging level lmi notice
|
||||
logging level lmib notice
|
||||
logging level lsms notice
|
||||
logging level lctrl notice
|
||||
logging level lgtp notice
|
||||
logging level lstats notice
|
||||
logging timestamp 0
|
||||
logging print file basename last
|
||||
logging print level 1
|
||||
logging level set-all notice
|
||||
hnbgw
|
||||
plmn 001 01
|
||||
iuh
|
||||
local-ip 0.0.0.0
|
||||
hnbap-allow-tmsi 1
|
||||
mgw 0
|
||||
remote-ip 127.0.0.1
|
||||
local-port 2729
|
||||
remote-port 2427
|
||||
reset-endpoint rtpbridge/*
|
||||
|
||||
|
||||
@@ -9,22 +9,62 @@ OsmoHNBGW implements the Home NodeB Gateway function in the 3G network architect
|
||||
as a gateway between the classic 3G core network (CN) domain with its IuCS and IuPS interface
|
||||
and the femtocell based RAN.
|
||||
|
||||
A typical 3G network consisting of Osmocom components will look as illustrated in the following
|
||||
A typical 3G network consisting of Osmocom components is illustrated in the following
|
||||
diagram:
|
||||
|
||||
[[fig-3g]]
|
||||
.Typical 3G network architecture used with OsmoHNBGW
|
||||
[graphviz]
|
||||
----
|
||||
+------------+ +--------+ +----------+ +---------+
|
||||
UE <-->| hNodeB |<--Iuh---->| HNB-GW |<--IuCS-->| OsmoMSC |<--GSUP-->| OsmoHLR |
|
||||
UE <-->| femto cell | ...-->| | ...-->| | | |
|
||||
| | | | +----------+ +---------|
|
||||
+------------+<--GTP-U | |
|
||||
\ | | +------+ +------+
|
||||
| | |<--IuPS-->| SGSN |<--GTP-C-->| GGSN |
|
||||
| +--------+ ...-->| | GTP-U-->| |
|
||||
| +------+ / +------+
|
||||
\_______________________________/
|
||||
digraph G {
|
||||
rankdir = LR;
|
||||
|
||||
UE [label="UE\n(3G phone)"]
|
||||
PBX [label="PBX\nAsterisk, FreeSwitch,\nKamailio, Yate, ..."]
|
||||
|
||||
subgraph cluster_msc_mgw {
|
||||
style=dotted
|
||||
MSC
|
||||
MGW1 [label="MGW"]
|
||||
MSC -> MGW1 [label="MGCP",constraint=false]
|
||||
}
|
||||
|
||||
subgraph cluster_hnbgw_mgw_upf {
|
||||
style=dotted
|
||||
MGW3 [label="MGW"]
|
||||
UPF
|
||||
HNBGW [label=HNBGW,style=bold]
|
||||
HNBGW -> MGW3 [label="MGCP",constraint=false]
|
||||
HNBGW -> UPF [label="PFCP",constraint=false]
|
||||
}
|
||||
|
||||
hNodeB [shape="box",label="hNodeB\n(3G femto cell)"]
|
||||
|
||||
MSC -> HLR [label="\nGSUP",style=bold]
|
||||
SGSN -> HLR [label="GSUP",style="dashed,bold"]
|
||||
UE -> hNodeB [label="Uu",style=bold]
|
||||
UE -> hNodeB [style="dashed,bold"]
|
||||
hNodeB -> HNBGW [label="Iuh",style="bold"]
|
||||
STP2 [label="STP\n(SCCP/M3UA)"]
|
||||
HNBGW -> STP2 -> SGSN [label="IuPS",style="dashed,bold"]
|
||||
HNBGW -> STP2 -> MSC [label="IuCS",style="bold"]
|
||||
SGSN -> GGSN [label="GTP-C",style="dashed,bold"]
|
||||
hNodeB -> UPF -> GGSN [label="GTP-U(3G)",style="dashed"]
|
||||
GGSN -> internet [label="tun",style="dashed"]
|
||||
|
||||
hNodeB -> MGW3 [label="IuUP/RTP",constraint=false]
|
||||
MGW3 -> MGW1 [label="IuUP/RTP"]
|
||||
|
||||
MSC -> SIPConnector [label="MNCC socket",style=bold]
|
||||
|
||||
SIPConnector -> PBX [label="SIP",style=bold]
|
||||
MGW1 -> PBX [label="RTP"]
|
||||
|
||||
A, B, C, D [style="invisible"]
|
||||
A -> B [label="data (PS)",style="dashed"]
|
||||
C -> D [label="voice/SMS/USSD (CS)"]
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
The HNB-GW performs a translation interface between the IuCS/IuPS interfaces on the one hand
|
||||
@@ -32,17 +72,18 @@ side, and the Iuh interface on the or ther hand:
|
||||
|
||||
----
|
||||
Iuh IuCS/IuPS
|
||||
|
||||
NAS +----+----+ +----+----+
|
||||
Non-Access Stratum | CC | MM | | CC | MM |
|
||||
- - - - - - - - - - - +----+----+-------+ +----+----+
|
||||
| RANAP | | H | RANAP |
|
||||
Access Stratum +---------+ HNBAP | N +---------+ - - SCCP USER SAP
|
||||
| RUA | | B | SUA | \
|
||||
+---------+-------+ - +---------+ |
|
||||
| SCTP | G | SCTP | } SIGTRAN
|
||||
+-----------------+ W +---------+ |
|
||||
| IP | | IP | /
|
||||
+----+----+
|
||||
| CC | MM |
|
||||
NAS +----+----+ . . +----+----+
|
||||
Non-Access Stratum | CC | MM | . | RANAP |
|
||||
- - - - - - - - - - - +----+----+-------+ +---------+
|
||||
| RANAP | | H | SCCP |
|
||||
Access Stratum +---------+ HNBAP | N +---------+
|
||||
| RUA | | B | M3UA |
|
||||
+---------+-------+ - +---------+
|
||||
| SCTP | G | SCTP |
|
||||
+-----------------+ W +---------+
|
||||
| IP | | IP |
|
||||
+-----------------+ +---------+
|
||||
----
|
||||
|
||||
|
||||
@@ -69,11 +69,30 @@ specific interface, and will hence not encounter conflicts for multiple instance
|
||||
running on the same interface:
|
||||
|
||||
- The SCCP/M3UA links are established by OsmoHNBGW contacting an STP.
|
||||
- The MGCP link is established by OsmoHNBGW contacting an MGW.
|
||||
- The PFCP link is established by OsmoHNBGW contacting a UPF.
|
||||
|
||||
To run multiple OsmoHNBGW instances on the same SCCP routing, each HNBGW has to
|
||||
configure a distinct point-code, see <<configure_iucs_iups>>.
|
||||
|
||||
|
||||
=== Configure limits
|
||||
|
||||
When connecting hundreds of HNB to OsmoHNBGW, it may be necessary to adjust the
|
||||
operating system's limit on open file descriptors for the osmo-hnbgw process. A
|
||||
typical default limit imposed by operating systems is 1024; this would be
|
||||
exceeded by, for example, about 1024 HNB on Iuh, sockets for other interfaces
|
||||
not considered yet.
|
||||
|
||||
It should be ok to set an OS limit on open file descriptors as high as 65536
|
||||
for osmo-hnbgw, which practically rules out failure from running out of file
|
||||
descriptors anywhere (<50,000 HNB).
|
||||
|
||||
When using systemd, the file descriptor limit may be adjusted in the service
|
||||
file by the `LimitNOFILE` setting ("Number of Open FILE descriptors").
|
||||
OsmoHNBGW ships a systemd service file with a high LimitNOFILE setting.
|
||||
|
||||
|
||||
=== Configuring Primary Links
|
||||
|
||||
[[configure_iucs_iups]]
|
||||
@@ -117,3 +136,75 @@ hnbgw
|
||||
local-ip 10.9.8.7
|
||||
local-port 29169
|
||||
----
|
||||
|
||||
==== Configure co-located media gateway
|
||||
|
||||
OsmoHNBGW requires a co-located OsmoMGW instance. The purpose of the co-located
|
||||
media gateway is to relay the RTP traffic between hNodeB and the core network.
|
||||
|
||||
For security reasons the RAN network is kept separate and isolated from the
|
||||
core network. Both networks will usually have no transparent routing in between
|
||||
them. The co-located media gateway provides an interface between hNodeB and core
|
||||
network across this boundary.
|
||||
|
||||
The configuration is done under the hnbgw node along with `iucs` and `iups`.
|
||||
|
||||
An example configuration for OsmoHNBGW's MGCP client:
|
||||
|
||||
----
|
||||
hnbgw
|
||||
mgw 0
|
||||
remote-ip 127.0.0.1
|
||||
remote-port 2427
|
||||
reset-endpoint rtpbridge/* <1>
|
||||
----
|
||||
<1> The 'reset-endpoint' setting instructs the OsmoMGW to send a wildcarded
|
||||
DLCX to the media gateway. This helps to clear lingering calls from the
|
||||
media gateway when the OsmoHNBGW is restarted.
|
||||
|
||||
OsmoHNBGW is also able to handle a pool of media gateways for load
|
||||
distribution. See also <<mgw_pooling>>.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Previous versions of OsmoHNBGW didn't have the 'mgw' VTY node and
|
||||
hence didn't support the MGW pooling feature. Therefore, historically the MGW
|
||||
related commands where placed under the `mgcp` VTY node. The MGW related commands
|
||||
under the `mgcp` VTY are still parsed and used but its use is deprecated and
|
||||
hence discouraged in favour of the new `mgw` node. Writing the config to a file
|
||||
from within OsmoHNBGW will automatically convert the config to use the new `mgw`
|
||||
node.
|
||||
====
|
||||
|
||||
==== Configure co-located User Plane Function
|
||||
|
||||
OsmoHNBGW optionally supports relaying the GTP user plane via a co-located UPF,
|
||||
which is controlled by the PFCP protocol.
|
||||
|
||||
PFCP support is optional at compile time, as well as run time. To use a co-located UPF,
|
||||
|
||||
* osmo-hnbgw needs to be compiled with 'configure --enable-pfcp',
|
||||
* and osmo-hnbgw.cfg needs to configure a 'pfcp' / 'remote-addr' and
|
||||
'local-addr'.
|
||||
|
||||
The following example configures OsmoHNBGW to associate via PFCP with a UPF
|
||||
listening on UDP 127.0.0.2:8805, ready to setup GTP tunnel relays.
|
||||
|
||||
----
|
||||
hnbgw
|
||||
pfcp
|
||||
remote-addr 127.0.0.2
|
||||
local-addr 127.0.0.1
|
||||
----
|
||||
|
||||
3GPP TS 29.244 4.2.2 specifies that PFCP Request messages shall be sent to UDP
|
||||
port 8805, i.e. the PFCP port is fixed as 8805 and currently not configurable in
|
||||
osmo-hnbgw.
|
||||
|
||||
Setting a 'local-addr' is required: the PFCP protocol features a Node ID, which
|
||||
uniquely identifies PFCP peers across different interfaces. According to the
|
||||
PFCP specification, the Node ID can be a fully-qualified domain name (FQDN) or
|
||||
an IP address. Currently, osmo-hnbgw has no support for using an FQDN as Node
|
||||
ID, and so far uses the 'local-addr' as local Node ID -- hence the 'local-addr'
|
||||
must not be "0.0.0.0", which is an unfortunate consequence. This is likely to
|
||||
improve in the future, see https://osmocom.org/issues/5647 .
|
||||
|
||||
@@ -22,8 +22,12 @@ include::./common/chapters/vty.adoc[]
|
||||
|
||||
include::./common/chapters/logging.adoc[]
|
||||
|
||||
include::./common/chapters/sigtran.adoc[]
|
||||
|
||||
include::./common/chapters/cs7-config.adoc[]
|
||||
|
||||
include::./common/chapters/mgwpool.adoc[]
|
||||
|
||||
// include::{srcdir}/chapters/net.adoc[]
|
||||
|
||||
// include::./common/chapters/control_if.adoc[]
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
noinst_HEADERS = \
|
||||
cnlink.h \
|
||||
context_map.h \
|
||||
hnb.h \
|
||||
hnb_persistent.h \
|
||||
hnbgw.h \
|
||||
hnbgw_cn.h \
|
||||
hnbgw_hnbap.h \
|
||||
hnbgw_pfcp.h \
|
||||
hnbgw_ranap.h \
|
||||
hnbgw_rua.h \
|
||||
hnbgw_sccp.h \
|
||||
kpi.h \
|
||||
mgw_fsm.h \
|
||||
nft_kpi.h \
|
||||
ps_rab_ass_fsm.h \
|
||||
ps_rab_fsm.h \
|
||||
ranap_rab_ass.h \
|
||||
tdefs.h \
|
||||
umts_cell_id.h \
|
||||
vty.h \
|
||||
context_map.h hnbgw.h hnbgw_cn.h \
|
||||
hnbgw_hnbap.h hnbgw_rua.h hnbgw_ranap.h
|
||||
$(NULL)
|
||||
|
||||
138
include/osmocom/hnbgw/cnlink.h
Normal file
138
include/osmocom/hnbgw/cnlink.h
Normal file
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <osmocom/core/rate_ctr.h>
|
||||
#include <osmocom/core/stat_item.h>
|
||||
#include <osmocom/gsm/gsm48.h>
|
||||
|
||||
#include <osmocom/sigtran/sccp_sap.h>
|
||||
|
||||
#include <osmocom/ranap/ranap_ies_defs.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw_sccp.h>
|
||||
|
||||
struct hnbgw_cnpool;
|
||||
|
||||
enum hnbgw_cnlink_ctr {
|
||||
/* TODO: basic counters completely missing
|
||||
* ...
|
||||
*/
|
||||
CNLINK_CTR_RANAP_RX_UDT_RESET,
|
||||
CNLINK_CTR_RANAP_RX_UDT_RESET_ACK,
|
||||
CNLINK_CTR_RANAP_RX_UDT_PAGING,
|
||||
CNLINK_CTR_RANAP_RX_UDT_UNKNOWN,
|
||||
CNLINK_CTR_RANAP_RX_UDT_UNSUPPORTED,
|
||||
CNLINK_CTR_RANAP_RX_UDT_OVERLOAD_IND,
|
||||
CNLINK_CTR_RANAP_RX_UDT_ERROR_IND,
|
||||
|
||||
CNLINK_CTR_RANAP_TX_UDT_RESET,
|
||||
CNLINK_CTR_RANAP_TX_UDT_RESET_ACK,
|
||||
|
||||
/* SCCP Counters: */
|
||||
CNLINK_CTR_SCCP_N_UNITDATA_REQ,
|
||||
CNLINK_CTR_SCCP_N_UNITDATA_IND,
|
||||
CNLINK_CTR_SCCP_N_NOTICE_IND,
|
||||
CNLINK_CTR_SCCP_N_CONNECT_REQ,
|
||||
CNLINK_CTR_SCCP_N_CONNECT_CNF,
|
||||
CNLINK_CTR_SCCP_N_DATA_REQ,
|
||||
CNLINK_CTR_SCCP_N_DATA_IND,
|
||||
CNLINK_CTR_SCCP_N_DISCONNECT_REQ,
|
||||
CNLINK_CTR_SCCP_N_DISCONNECT_IND,
|
||||
CNLINK_CTR_SCCP_N_PCSTATE_IND,
|
||||
CNLINK_CTR_SCCP_RLSD_CN_ORIGIN,
|
||||
|
||||
/* Counters related to link selection from a CN pool. */
|
||||
CNLINK_CTR_CNPOOL_SUBSCR_NEW,
|
||||
CNLINK_CTR_CNPOOL_SUBSCR_REATTACH,
|
||||
CNLINK_CTR_CNPOOL_SUBSCR_KNOWN,
|
||||
CNLINK_CTR_CNPOOL_SUBSCR_PAGED,
|
||||
CNLINK_CTR_CNPOOL_SUBSCR_ATTACH_LOST,
|
||||
CNLINK_CTR_CNPOOL_EMERG_FORWARDED,
|
||||
};
|
||||
#define CNLINK_CTR_INC(cnlink, x) rate_ctr_inc2((cnlink)->ctrs, x)
|
||||
|
||||
enum cnlink_stat {
|
||||
CNLINK_STAT_CONNECTED,
|
||||
};
|
||||
#define CNLINK_STAT(cnlink, x) osmo_stat_item_group_get_item((cnlink)->statg, x)
|
||||
#define CNLINK_STAT_SET(cnlink, x, val) osmo_stat_item_set(CNLINK_STAT(cnlink, x), val)
|
||||
|
||||
/* User provided configuration for struct hnbgw_cnlink. */
|
||||
struct hnbgw_cnlink_cfg {
|
||||
/* cs7 address book entry to indicate both the remote point-code of the peer, as well as which cs7 instance to
|
||||
* use. */
|
||||
char *remote_addr_name;
|
||||
|
||||
struct osmo_nri_ranges *nri_ranges;
|
||||
};
|
||||
|
||||
/* A CN peer, like 'msc 0' or 'sgsn 23' */
|
||||
struct hnbgw_cnlink {
|
||||
struct llist_head entry;
|
||||
|
||||
/* backpointer to CS or PS CN pool. */
|
||||
struct hnbgw_cnpool *pool;
|
||||
|
||||
struct osmo_fsm_inst *fi;
|
||||
|
||||
int nr;
|
||||
|
||||
struct hnbgw_cnlink_cfg vty;
|
||||
struct hnbgw_cnlink_cfg use;
|
||||
|
||||
/* To print in logging/VTY */
|
||||
char *name;
|
||||
|
||||
/* Copy of the address book entry use.remote_addr_name. */
|
||||
struct osmo_sccp_addr remote_addr;
|
||||
|
||||
/* The SCCP instance for the cs7 instance indicated by remote_addr_name. (Multiple hnbgw_cnlinks may use the
|
||||
* same hnbgw_sccp_user -- there is exactly one hnbgw_sccp_user per configured cs7 instance.) */
|
||||
struct hnbgw_sccp_user *hnbgw_sccp_user;
|
||||
|
||||
/* linked list of hnbgw_context_map */
|
||||
struct llist_head map_list;
|
||||
|
||||
bool allow_attach;
|
||||
bool allow_emerg;
|
||||
struct llist_head paging;
|
||||
|
||||
struct rate_ctr_group *ctrs;
|
||||
struct osmo_stat_item_group *statg;
|
||||
};
|
||||
|
||||
struct hnbgw_cnlink *hnbgw_cnlink_alloc(struct hnbgw_cnpool *cnpool, int nr);
|
||||
void hnbgw_cnlink_term_and_free(struct hnbgw_cnlink *cnlink);
|
||||
void hnbgw_cnlink_drop_sccp(struct hnbgw_cnlink *cnlink);
|
||||
int hnbgw_cnlink_set_name(struct hnbgw_cnlink *cnlink, const char *name);
|
||||
int hnbgw_cnlink_tx_ranap_reset(struct hnbgw_cnlink *cnlink);
|
||||
int hnbgw_cnlink_tx_ranap_reset_ack(struct hnbgw_cnlink *cnlink);
|
||||
|
||||
int hnbgw_cnlink_start_or_restart(struct hnbgw_cnlink *cnlink);
|
||||
|
||||
char *hnbgw_cnlink_sccp_addr_to_str(struct hnbgw_cnlink *cnlink, const struct osmo_sccp_addr *addr);
|
||||
|
||||
static inline struct osmo_sccp_instance *hnbgw_cnlink_sccp(const struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
if (!cnlink)
|
||||
return NULL;
|
||||
if (!cnlink->hnbgw_sccp_user)
|
||||
return NULL;
|
||||
return hnbgw_sccp_user_get_sccp_instance(cnlink->hnbgw_sccp_user);
|
||||
}
|
||||
|
||||
/* cnlink_fsm.c related: */
|
||||
extern struct osmo_fsm cnlink_fsm;
|
||||
bool cnlink_is_conn_ready(const struct hnbgw_cnlink *cnlink);
|
||||
void cnlink_rx_reset_cmd(struct hnbgw_cnlink *cnlink);
|
||||
void cnlink_rx_reset_ack(struct hnbgw_cnlink *cnlink);
|
||||
void cnlink_resend_reset(struct hnbgw_cnlink *cnlink);
|
||||
void cnlink_set_disconnected(struct hnbgw_cnlink *cnlink);
|
||||
|
||||
/* cnlink_paging.c related: */
|
||||
const char *cnlink_paging_add_ranap(struct hnbgw_cnlink *cnlink, const RANAP_PagingIEs_t *paging_ies);
|
||||
struct hnbgw_cnlink *cnlink_find_by_paging_mi(struct hnbgw_cnpool *cnpool, const struct osmo_mobile_identity *mi);
|
||||
|
||||
#define LOG_CNLINK(CNLINK, SUBSYS, LEVEL, FMT, ARGS...) \
|
||||
LOGP(SUBSYS, LEVEL, "(%s) " FMT, (CNLINK) ? (CNLINK)->name : "null", ##ARGS)
|
||||
@@ -2,13 +2,103 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/hnbgw/hnb.h>
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/gsm/gsm48.h>
|
||||
|
||||
#define LOG_MAP(HNB_CTX_MAP, SUBSYS, LEVEL, FMT, ARGS...) \
|
||||
LOGHNB((HNB_CTX_MAP) ? (HNB_CTX_MAP)->hnb_ctx : NULL, \
|
||||
SUBSYS, LEVEL, "RUA-%u SCCP-%u %s MI=%s%s%s: " FMT, \
|
||||
(HNB_CTX_MAP) ? (HNB_CTX_MAP)->rua_ctx_id : 0, \
|
||||
(HNB_CTX_MAP) ? (HNB_CTX_MAP)->scu_conn_id : 0, \
|
||||
(HNB_CTX_MAP) ? \
|
||||
((HNB_CTX_MAP)->cnlink ? (HNB_CTX_MAP)->cnlink->name \
|
||||
: ((HNB_CTX_MAP)->is_ps ? "PS" : "CS")) \
|
||||
: "NULL", \
|
||||
(HNB_CTX_MAP) ? osmo_mobile_identity_to_str_c(OTC_SELECT, &(HNB_CTX_MAP)->l3.mi) : "null", \
|
||||
(HNB_CTX_MAP) && (HNB_CTX_MAP)->l3.from_other_plmn ? " (from other PLMN)" : "", \
|
||||
(HNB_CTX_MAP) && (HNB_CTX_MAP)->l3.is_emerg ? " EMERGENCY" : "", \
|
||||
##ARGS)
|
||||
|
||||
/* All these events' data argument may either be NULL, or point to a RANAP msgb.
|
||||
* - The msgb shall be in the OTC_SELECT talloc pool, so that they will be deallocated automatically. Some events
|
||||
* processing will store the msgb for later, in which case it will take over ownership of the msgb by means of
|
||||
* talloc_steal().
|
||||
* - For events that may send a RANAP message towards CN via SCCP, the msgb shall have reserved headroom to fit a struct
|
||||
* osmo_scu_prim. These are: MAP_RUA_EV_RX_*.
|
||||
* - The RANAP message shall be at msgb_l2().
|
||||
*/
|
||||
enum map_rua_fsm_event {
|
||||
/* Receiving a RUA Connect from HNB.
|
||||
* Parameter: struct msgb *ranap_msg */
|
||||
MAP_RUA_EV_RX_CONNECT,
|
||||
/* Receiving some data from HNB via RUA, to forward via SCCP to CN.
|
||||
* Parameter: struct msgb *ranap_msg */
|
||||
MAP_RUA_EV_RX_DIRECT_TRANSFER,
|
||||
/* Receiving a RUA Disconnect from HNB.
|
||||
* Parameter: struct msgb *ranap_msg (can be NULL) */
|
||||
MAP_RUA_EV_RX_DISCONNECT,
|
||||
/* SCCP has received some data from CN to forward via RUA to HNB.
|
||||
* Parameter: struct msgb *ranap_msg */
|
||||
MAP_RUA_EV_TX_DIRECT_TRANSFER,
|
||||
/* The CN side is disconnected (e.g. received an SCCP Released), that means we are going gracefully disconnect
|
||||
* RUA, too. */
|
||||
MAP_RUA_EV_CN_DISC,
|
||||
/* All of a sudden, there is no RUA link. For example, HNB vanished / restarted, or SCTP SHUTDOWN on the RUA
|
||||
* link. Skip RUA disconnect. */
|
||||
MAP_RUA_EV_HNB_LINK_LOST,
|
||||
};
|
||||
|
||||
/* All these events' data argument is identical to enum map_rua_fsm_event, with this specialisation:
|
||||
* - The events that may send a RANAP message towards CN via SCCP and hence require a headroom for an osmo_scu_prim are:
|
||||
* MAP_SCCP_EV_TX_DATA_REQUEST, MAP_SCCP_EV_RAN_DISC.
|
||||
*/
|
||||
enum map_sccp_fsm_event {
|
||||
/* Receiving an SCCP CC from CN.
|
||||
* Parameter: struct msgb *ranap_msg, may be NULL or empty. */
|
||||
MAP_SCCP_EV_RX_CONNECTION_CONFIRM,
|
||||
/* Receiving some data from CN via SCCP, to forward via RUA to HNB.
|
||||
* Parameter: struct msgb *ranap_msg, may be NULL or empty. */
|
||||
MAP_SCCP_EV_RX_DATA_INDICATION,
|
||||
/* RUA has received some data from HNB to forward via SCCP to CN.
|
||||
* Parameter: struct msgb *ranap_msg. */
|
||||
MAP_SCCP_EV_TX_DATA_REQUEST,
|
||||
/* 3GPP TS 25.468 9.1.5: The RAN side received a RUA Disconnect.
|
||||
* - Under normal conditions (cause=Normal) the RUA Disconnect contains a RANAP Iu-ReleaseComplete.
|
||||
* On SCCP, the Iu-ReleaseComplete should still be forwarded as N-Data SCCP Data Form 1),
|
||||
* and we will expect the CN to send an SCCP RLSD soon. Hence, give CN a chance to send an SCCP RLSD;
|
||||
* after a timeout we will send a non-standard RLSD to the CN instead.
|
||||
* - Under error conditions, cause!=Normal and there's no RANAP message.
|
||||
* In that case, we need to tear down the associated SCCP link towards CN with an RLSD,
|
||||
* which in turn will tear down the upper layer Iu conn.
|
||||
*
|
||||
* Parameter: bool rua_disconnect_err_condition, whether the disconnect
|
||||
* happened under error or normal conditions, as per the above.
|
||||
*/
|
||||
MAP_SCCP_EV_RAN_DISC,
|
||||
/* The RAN released ungracefully. We will directly disconnect the SCCP connection, too.
|
||||
* Parameter: no parameter, NULL. */
|
||||
MAP_SCCP_EV_RAN_LINK_LOST,
|
||||
/* Receiving an SCCP RLSD from CN, or libosmo-sigtran tells us about SCCP connection timeout. All done.
|
||||
* Parameter: struct msgb *ranap_msg, may be NULL or empty. */
|
||||
MAP_SCCP_EV_RX_RELEASED,
|
||||
/* The human admin asks to drop the current SCCP connection, by telnet VTY 'apply sccp' in presence of SCCP
|
||||
* config changes.
|
||||
* Parameter: no parameter, NULL. */
|
||||
MAP_SCCP_EV_USER_ABORT,
|
||||
/* The CN link can no longer work, for example a RANAP RESET was received from the cnlink that hosts this
|
||||
* context map.
|
||||
* Parameter: no parameter, NULL. */
|
||||
MAP_SCCP_EV_CN_LINK_LOST,
|
||||
};
|
||||
|
||||
/* For context_map_get_state(), to combine the RUA and SCCP states, for VTY reporting only. */
|
||||
enum hnbgw_context_map_state {
|
||||
MAP_S_NULL,
|
||||
MAP_S_ACTIVE, /* currently active map */
|
||||
MAP_S_RESERVED1, /* just disconnected, still resrved */
|
||||
MAP_S_RESERVED2, /* still reserved */
|
||||
MAP_S_NUM_STATES /* Number of states, keep this at the end */
|
||||
MAP_S_CONNECTING, /* not active yet; effectively waiting for SCCP CC */
|
||||
MAP_S_ACTIVE, /* both RUA and SCCP are connected */
|
||||
MAP_S_DISCONNECTING, /* not active anymore; effectively waiting for SCCP RLSD */
|
||||
MAP_S_NUM_STATES /* Number of states, keep this at the end */
|
||||
};
|
||||
|
||||
extern const struct value_string hnbgw_context_map_state_names[];
|
||||
@@ -18,34 +108,141 @@ static inline const char *hnbgw_context_map_state_name(enum hnbgw_context_map_st
|
||||
struct hnb_context;
|
||||
struct hnbgw_cnlink;
|
||||
|
||||
struct hnbgw_context_map {
|
||||
/* entry in the per-CN list of mappings */
|
||||
struct llist_head cn_list;
|
||||
/* entry in the per-HNB list of mappings */
|
||||
struct llist_head hnb_list;
|
||||
/* pointer to HNB */
|
||||
struct hnb_context *hnb_ctx;
|
||||
/* pointer to CN */
|
||||
struct hnbgw_cnlink *cn_link;
|
||||
/* RUA contxt ID */
|
||||
uint32_t rua_ctx_id;
|
||||
/* False for CS, true for PS */
|
||||
bool is_ps;
|
||||
/* SCCP User SAP connection ID */
|
||||
uint32_t scu_conn_id;
|
||||
|
||||
enum hnbgw_context_map_state state;
|
||||
struct hnbgw_l3_peek {
|
||||
/* L3 message type, like GSM48_PDISC_MM+GSM48_MT_MM_LOC_UPD_REQUEST... / GSM48_PDISC_MM_GPRS+GSM48_MT_GMM_ATTACH_REQ... */
|
||||
uint8_t gsm48_pdisc;
|
||||
uint8_t gsm48_msg_type;
|
||||
/* The Mobile Identity from MM and GMM messages */
|
||||
struct osmo_mobile_identity mi;
|
||||
/* On PS, the "TMSI Based NRI Container", 10 bit integer, or -1 if not present.
|
||||
* This is only for PS -- for CS, the NRI is in the TMSI obtained from 'mi' above. */
|
||||
int gmm_nri_container;
|
||||
/* For a CM Service Request for voice call, true if this is for an Emergency Call, false otherwise. */
|
||||
bool is_emerg;
|
||||
/* True if the NAS PDU indicates that the UE was previously attached to a different PLMN than the local PLMN. */
|
||||
bool from_other_plmn;
|
||||
};
|
||||
|
||||
/* used in hnbgw_context_map.rab_state[] */
|
||||
enum rab_state {
|
||||
RAB_STATE_INACTIVE,
|
||||
RAB_STATE_ACT_REQ,
|
||||
RAB_STATE_ACTIVE,
|
||||
RAB_STATE_REL_REQ,
|
||||
};
|
||||
extern const struct value_string hnbgw_rab_state_names[];
|
||||
static inline const char *hnbgw_rab_state_name(enum rab_state val)
|
||||
{ return get_value_string(hnbgw_rab_state_names, val); }
|
||||
|
||||
struct hnbgw_context_map *
|
||||
context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id,
|
||||
bool is_ps,
|
||||
struct hnbgw_cnlink *cn_if_new);
|
||||
|
||||
struct hnbgw_context_map *
|
||||
context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id);
|
||||
struct hnbgw_context_map {
|
||||
/* entry in the per-CN list of mappings */
|
||||
struct llist_head hnbgw_cnlink_entry;
|
||||
/* entry in the per-HNB list of mappings. If hnb_ctx == NULL, then this llist entry has been llist_del()eted and
|
||||
* must not be used. */
|
||||
struct llist_head hnb_list;
|
||||
|
||||
void context_map_deactivate(struct hnbgw_context_map *map);
|
||||
/* entry in the per-SCCP-conn-id hashtable */
|
||||
struct hlist_node hnbgw_sccp_user_entry;
|
||||
|
||||
int context_map_init(struct hnb_gw *gw);
|
||||
/* Pointer to HNB for this map, to transceive RUA. If the HNB has disconnected without releasing the RUA
|
||||
* context, this is NULL. */
|
||||
struct hnb_context *hnb_ctx;
|
||||
/* RUA context ID used in RUA messages to/from the hnb_gw. */
|
||||
uint32_t rua_ctx_id;
|
||||
/* FSM handling the RUA state for rua_ctx_id. */
|
||||
struct osmo_fsm_inst *rua_fi;
|
||||
|
||||
/* State context related to field rua_fi above: */
|
||||
struct {
|
||||
/* Whether RUA Disconnect received from HNB happened as a normal condition or an error/abnormal condition.
|
||||
* This is known based on cause and/or RANAP message included in the RUA
|
||||
* Disconnect message, and tells us whether we should immediately
|
||||
* terminate the related SCCP session or wait for CN to finish it.
|
||||
* Defaults to false, only set to true explicitly when needed. */
|
||||
bool rua_disconnect_err_condition;
|
||||
} rua_fi_ctx;
|
||||
|
||||
/* Pointer to CN, to transceive SCCP. */
|
||||
struct hnbgw_cnlink *cnlink;
|
||||
/* SCCP User SAP connection ID used in SCCP messages to/from the cn_link. */
|
||||
uint32_t scu_conn_id;
|
||||
/* FSM handling the SCCP state for scu_conn_id. */
|
||||
struct osmo_fsm_inst *sccp_fi;
|
||||
/* State context related to field sccp_fi above: */
|
||||
struct {
|
||||
/* List of cached packets received from RUA and to be forwarded
|
||||
once SCCP CReq is CC'ed and move to CONNECTED state. */
|
||||
struct llist_head wait_cc_tx_msg_list;
|
||||
} sccp_fi_ctx;
|
||||
|
||||
/* False for CS, true for PS */
|
||||
bool is_ps;
|
||||
|
||||
/* Information extracted from RUA Connect's RANAP InitialUE message */
|
||||
struct hnbgw_l3_peek l3;
|
||||
|
||||
/* When an FSM is asked to disconnect but must still wait for a response, it may set this flag, to continue to
|
||||
* disconnect once the response is in. In particular, when SCCP is asked to disconnect after an SCCP Connection
|
||||
* Request was already sent and while waiting for a Connection Confirmed, we should still wait for the SCCP CC
|
||||
* and immediately release it after that, to not leak the connection. */
|
||||
bool please_disconnect;
|
||||
|
||||
/* FSM instance for the MGW, handles the async MGCP communication necessary to intercept CS RAB Assignment and
|
||||
* redirect the RTP via the MGW. */
|
||||
struct osmo_fsm_inst *mgw_fi;
|
||||
|
||||
/* FSMs handling RANAP RAB assignments for PS, list of struct ps_rab_ass. They handle the async PFCP
|
||||
* communication necessary to intercept PS RAB Assignment and redirect the GTP via the UPF.
|
||||
*
|
||||
* For PS RAB Assignment, each Request gets one ps_rab_ass FSM and each Response gets one ps_rab_ass FSM.
|
||||
* The reason is that theoretically, each such message can contain any number and any combination of RAB IDs,
|
||||
* and Request and Response don't necessarily match the RAB IDs contained. In practice I only ever see a single
|
||||
* RAB matching in Request and Response, but we cannot rely on that to always be true.
|
||||
*
|
||||
* The state of each RAB's PFCP negotiation is kept separately in the field ps_rab_list, and as soon as all RABs
|
||||
* appearing in a PS RAB Assignment message have completed their PFCP setup, we can replace the GTP info for the
|
||||
* RAB IDs and forward the RAB Assignment Request to HNB / the RAB Assignment Response to CN.
|
||||
*/
|
||||
struct llist_head ps_rab_ass_list;
|
||||
|
||||
/* All PS RABs and their GTP tunnel mappings. list of struct ps_rab. Each ps_rab FSM handles the PFCP
|
||||
* communication for one particular RAB ID. */
|
||||
struct llist_head ps_rab_list;
|
||||
|
||||
/* RAB state tracking. As RAB-ID is an 8-bit integer, we need 256 elements in the array */
|
||||
uint8_t rab_state[256];
|
||||
|
||||
/* Flag to prevent calling context_map_free() from cleanup code paths triggered by context_map_free() itself. */
|
||||
bool deallocating;
|
||||
};
|
||||
|
||||
enum hnbgw_context_map_state context_map_get_state(struct hnbgw_context_map *map);
|
||||
enum hnbgw_context_map_state map_rua_get_state(struct hnbgw_context_map *map);
|
||||
enum hnbgw_context_map_state map_sccp_get_state(struct hnbgw_context_map *map);
|
||||
|
||||
struct hnbgw_context_map *context_map_find_by_rua_ctx_id(struct hnb_context *hnb, uint32_t rua_ctx_id, bool is_ps);
|
||||
struct hnbgw_context_map *context_map_alloc(struct hnb_context *hnb, uint32_t rua_ctx_id, bool is_ps);
|
||||
int context_map_set_cnlink(struct hnbgw_context_map *map, struct hnbgw_cnlink *cnlink_selected);
|
||||
|
||||
void map_rua_fsm_alloc(struct hnbgw_context_map *map);
|
||||
void map_sccp_fsm_alloc(struct hnbgw_context_map *map);
|
||||
|
||||
void context_map_hnb_released(struct hnbgw_context_map *map);
|
||||
|
||||
#define map_rua_dispatch(MAP, EVENT, MSGB) \
|
||||
_map_rua_dispatch(MAP, EVENT, MSGB, __FILE__, __LINE__)
|
||||
int _map_rua_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg,
|
||||
const char *file, int line);
|
||||
|
||||
#define map_sccp_dispatch(MAP, EVENT, MSGB) \
|
||||
_map_sccp_dispatch(MAP, EVENT, MSGB, __FILE__, __LINE__)
|
||||
int _map_sccp_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg,
|
||||
const char *file, int line);
|
||||
|
||||
bool map_rua_is_active(struct hnbgw_context_map *map);
|
||||
bool map_sccp_is_active(struct hnbgw_context_map *map);
|
||||
void context_map_cnlink_lost(struct hnbgw_context_map *map);
|
||||
void context_map_free(struct hnbgw_context_map *map);
|
||||
|
||||
unsigned int msg_has_l2_data(const struct msgb *msg);
|
||||
|
||||
76
include/osmocom/hnbgw/hnb.h
Normal file
76
include/osmocom/hnbgw/hnb.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/hashtable.h>
|
||||
#include <osmocom/core/write_queue.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/core/rate_ctr.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
#include <osmocom/gsm/gsm23003.h>
|
||||
#include <osmocom/sigtran/osmo_ss7.h>
|
||||
#include <osmocom/ctrl/control_if.h>
|
||||
#include <osmocom/ranap/RANAP_CN-DomainIndicator.h>
|
||||
|
||||
#define DEBUG
|
||||
#include <osmocom/core/logging.h>
|
||||
|
||||
#include <osmocom/netif/stream.h>
|
||||
|
||||
#include <osmocom/mgcp_client/mgcp_client.h>
|
||||
#include <osmocom/mgcp_client/mgcp_client_pool.h>
|
||||
|
||||
#include <osmocom/hnbgw/umts_cell_id.h>
|
||||
#include <osmocom/hnbgw/nft_kpi.h>
|
||||
#include <osmocom/hnbgw/cnlink.h>
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
|
||||
#define HNB_STORE_RAB_DURATIONS_INTERVAL 1 /* seconds */
|
||||
|
||||
#define LOGHNB(HNB_CTX, ss, lvl, fmt, args ...) \
|
||||
LOGP(ss, lvl, "(%s) " fmt, hnb_context_name(HNB_CTX), ## args)
|
||||
|
||||
enum hnb_ctrl_node {
|
||||
CTRL_NODE_HNB = _LAST_CTRL_NODE,
|
||||
_LAST_CTRL_NODE_HNB
|
||||
};
|
||||
|
||||
/* The lifecycle of the hnb_context object is the same as its conn */
|
||||
struct hnb_context {
|
||||
/*! Entry in HNB-global list of HNB */
|
||||
struct llist_head list;
|
||||
/*! SCTP socket + write queue for Iuh to this specific HNB */
|
||||
struct osmo_stream_srv *conn;
|
||||
/*! copied from HNB-Identity-Info IE */
|
||||
char identity_info[256];
|
||||
/*! copied from Cell Identity IE */
|
||||
struct umts_cell_id id;
|
||||
|
||||
/*! SCTP stream ID for HNBAP */
|
||||
uint16_t hnbap_stream;
|
||||
/*! SCTP stream ID for RUA */
|
||||
uint16_t rua_stream;
|
||||
|
||||
/*! True if a HNB-REGISTER-REQ from this HNB has been accepted. */
|
||||
bool hnb_registered;
|
||||
|
||||
/* linked list of hnbgw_context_map */
|
||||
struct llist_head map_list;
|
||||
|
||||
/*! pointer to the associated hnb persistent state. Always present after HNB-Register */
|
||||
struct hnb_persistent *persistent;
|
||||
};
|
||||
|
||||
|
||||
int hnbgw_rua_accept_cb(struct osmo_stream_srv_link *srv, int fd);
|
||||
int hnb_ctrl_cmds_install(void);
|
||||
int hnb_ctrl_node_lookup(void *data, vector vline, int *node_type, void **node_data, int *i);
|
||||
|
||||
struct hnb_context *hnb_context_by_identity_info(const char *identity_info);
|
||||
const char *hnb_context_name(struct hnb_context *ctx);
|
||||
|
||||
void hnb_context_release(struct hnb_context *ctx);
|
||||
void hnb_context_release_ue_state(struct hnb_context *ctx);
|
||||
|
||||
unsigned long long hnb_get_updowntime(const struct hnb_context *ctx);
|
||||
void hnb_store_rab_durations(struct hnb_context *hnb);
|
||||
193
include/osmocom/hnbgw/hnb_persistent.h
Normal file
193
include/osmocom/hnbgw/hnb_persistent.h
Normal file
@@ -0,0 +1,193 @@
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/hashtable.h>
|
||||
#include <osmocom/core/write_queue.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/core/rate_ctr.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
#include <osmocom/gsm/gsm23003.h>
|
||||
#include <osmocom/sigtran/osmo_ss7.h>
|
||||
#include <osmocom/ctrl/control_if.h>
|
||||
#include <osmocom/ranap/RANAP_CN-DomainIndicator.h>
|
||||
|
||||
#define DEBUG
|
||||
#include <osmocom/core/logging.h>
|
||||
|
||||
#include <osmocom/mgcp_client/mgcp_client.h>
|
||||
#include <osmocom/mgcp_client/mgcp_client_pool.h>
|
||||
|
||||
#include <osmocom/hnbgw/umts_cell_id.h>
|
||||
#include <osmocom/hnbgw/nft_kpi.h>
|
||||
#include <osmocom/hnbgw/cnlink.h>
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
|
||||
#define LOG_HNBP(HNBP, lvl, fmt, args...) \
|
||||
LOGP(DHNB, lvl, "(%s) " fmt, \
|
||||
(HNBP) ? \
|
||||
(((HNBP)->id_str && *(HNBP)->id_str) ? (HNBP)->id_str : "no-cell-id") \
|
||||
: "null", ## args)
|
||||
|
||||
|
||||
enum hnb_rate_ctr {
|
||||
HNB_CTR_IUH_ESTABLISHED,
|
||||
HNB_CTR_RANAP_PS_ERR_IND_UL,
|
||||
HNB_CTR_RANAP_CS_ERR_IND_UL,
|
||||
HNB_CTR_RANAP_PS_RESET_REQ_UL,
|
||||
HNB_CTR_RANAP_CS_RESET_REQ_UL,
|
||||
|
||||
HNB_CTR_RANAP_PS_RAB_ACT_REQ,
|
||||
HNB_CTR_RANAP_CS_RAB_ACT_REQ,
|
||||
HNB_CTR_RANAP_PS_RAB_ACT_REQ_UNEXP,
|
||||
HNB_CTR_RANAP_CS_RAB_ACT_REQ_UNEXP,
|
||||
|
||||
HNB_CTR_RANAP_PS_RAB_ACT_CNF,
|
||||
HNB_CTR_RANAP_CS_RAB_ACT_CNF,
|
||||
HNB_CTR_RANAP_PS_RAB_ACT_CNF_UNEXP,
|
||||
HNB_CTR_RANAP_CS_RAB_ACT_CNF_UNEXP,
|
||||
|
||||
HNB_CTR_RANAP_PS_RAB_ACT_FAIL,
|
||||
HNB_CTR_RANAP_CS_RAB_ACT_FAIL,
|
||||
HNB_CTR_RANAP_PS_RAB_ACT_FAIL_UNEXP,
|
||||
HNB_CTR_RANAP_CS_RAB_ACT_FAIL_UNEXP,
|
||||
|
||||
HNB_CTR_RANAP_PS_RAB_MOD_REQ,
|
||||
HNB_CTR_RANAP_CS_RAB_MOD_REQ,
|
||||
|
||||
HNB_CTR_RANAP_PS_RAB_MOD_CNF,
|
||||
HNB_CTR_RANAP_CS_RAB_MOD_CNF,
|
||||
|
||||
HNB_CTR_RANAP_PS_RAB_MOD_FAIL,
|
||||
HNB_CTR_RANAP_CS_RAB_MOD_FAIL,
|
||||
|
||||
HNB_CTR_RANAP_PS_RAB_REL_REQ,
|
||||
HNB_CTR_RANAP_CS_RAB_REL_REQ,
|
||||
HNB_CTR_RANAP_PS_RAB_REL_REQ_ABNORMAL,
|
||||
HNB_CTR_RANAP_CS_RAB_REL_REQ_ABNORMAL,
|
||||
HNB_CTR_RANAP_PS_RAB_REL_REQ_UNEXP,
|
||||
HNB_CTR_RANAP_CS_RAB_REL_REQ_UNEXP,
|
||||
|
||||
HNB_CTR_RANAP_PS_RAB_REL_CNF,
|
||||
HNB_CTR_RANAP_CS_RAB_REL_CNF,
|
||||
HNB_CTR_RANAP_PS_RAB_REL_CNF_UNEXP,
|
||||
HNB_CTR_RANAP_CS_RAB_REL_CNF_UNEXP,
|
||||
|
||||
HNB_CTR_RANAP_PS_RAB_REL_FAIL,
|
||||
HNB_CTR_RANAP_CS_RAB_REL_FAIL,
|
||||
HNB_CTR_RANAP_PS_RAB_REL_FAIL_UNEXP,
|
||||
HNB_CTR_RANAP_CS_RAB_REL_FAIL_UNEXP,
|
||||
|
||||
HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT,
|
||||
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT,
|
||||
HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT_ABNORMAL,
|
||||
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT_ABNORMAL,
|
||||
|
||||
HNB_CTR_RUA_ERR_IND,
|
||||
|
||||
HNB_CTR_RUA_PS_CONNECT_UL,
|
||||
HNB_CTR_RUA_CS_CONNECT_UL,
|
||||
|
||||
HNB_CTR_RUA_PS_DISCONNECT_UL,
|
||||
HNB_CTR_RUA_CS_DISCONNECT_UL,
|
||||
HNB_CTR_RUA_PS_DISCONNECT_DL,
|
||||
HNB_CTR_RUA_CS_DISCONNECT_DL,
|
||||
|
||||
HNB_CTR_RUA_PS_DT_UL,
|
||||
HNB_CTR_RUA_CS_DT_UL,
|
||||
HNB_CTR_RUA_PS_DT_DL,
|
||||
HNB_CTR_RUA_CS_DT_DL,
|
||||
|
||||
HNB_CTR_RUA_UDT_UL,
|
||||
HNB_CTR_RUA_UDT_DL,
|
||||
|
||||
HNB_CTR_PS_PAGING_ATTEMPTED,
|
||||
HNB_CTR_CS_PAGING_ATTEMPTED,
|
||||
|
||||
HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL,
|
||||
|
||||
HNB_CTR_DTAP_CS_LU_REQ,
|
||||
HNB_CTR_DTAP_CS_LU_ACC,
|
||||
HNB_CTR_DTAP_CS_LU_REJ,
|
||||
|
||||
HNB_CTR_DTAP_PS_ATT_REQ,
|
||||
HNB_CTR_DTAP_PS_ATT_ACK,
|
||||
HNB_CTR_DTAP_PS_ATT_REJ,
|
||||
|
||||
HNB_CTR_DTAP_PS_RAU_REQ,
|
||||
HNB_CTR_DTAP_PS_RAU_ACK,
|
||||
HNB_CTR_DTAP_PS_RAU_REJ,
|
||||
|
||||
HNB_CTR_GTPU_PACKETS_UL,
|
||||
HNB_CTR_GTPU_TOTAL_BYTES_UL,
|
||||
HNB_CTR_GTPU_UE_BYTES_UL,
|
||||
HNB_CTR_GTPU_PACKETS_DL,
|
||||
HNB_CTR_GTPU_TOTAL_BYTES_DL,
|
||||
HNB_CTR_GTPU_UE_BYTES_DL,
|
||||
};
|
||||
|
||||
enum hnb_stat {
|
||||
HNB_STAT_UPTIME_SECONDS,
|
||||
};
|
||||
|
||||
#define HNBP_CTR(hnbp, x) rate_ctr_group_get_ctr((hnbp)->ctrs, x)
|
||||
#define HNBP_CTR_INC(hnbp, x) rate_ctr_inc(HNBP_CTR(hnbp, x))
|
||||
#define HNBP_CTR_ADD(hnbp, x, y) rate_ctr_add2((hnbp)->ctrs, x, y)
|
||||
|
||||
#define HNBP_STAT(hbp, x) osmo_stat_item_group_get_item((hnbp)->statg, x)
|
||||
#define HNBP_STAT_SET(hnbp, x, val) osmo_stat_item_set(HNBP_STAT(hnbp, x), val)
|
||||
|
||||
/* persistent data for one HNB. This continues to exist even as conn / hnb_context is deleted on disconnect */
|
||||
struct hnb_persistent {
|
||||
/*! Entry in HNBGW-global list of hnb_persistent */
|
||||
struct llist_head list;
|
||||
/*! Entry in hash table g_hnbgw->hnb_persistent_by_id. */
|
||||
struct hlist_node node_by_id;
|
||||
/*! back-pointer to hnb_context. Can be NULL if no context at this point */
|
||||
struct hnb_context *ctx;
|
||||
|
||||
/*! unique cell identity; copied from HNB REGISTER REQ */
|
||||
struct umts_cell_id id;
|
||||
/*! stringified version of the cell identiy above (for printing/naming) */
|
||||
const char *id_str;
|
||||
|
||||
/*! copied from HNB-Identity-Info IE */
|
||||
time_t updowntime;
|
||||
|
||||
struct rate_ctr_group *ctrs;
|
||||
struct osmo_stat_item_group *statg;
|
||||
|
||||
/* State that the main thread needs in order to know what was requested from the nft worker threads and what
|
||||
* still needs to be requested. */
|
||||
struct {
|
||||
/* Whether a persistent named counter was added in nftables for this cell id. */
|
||||
bool persistent_counter_added;
|
||||
|
||||
/* The last hNodeB GTP-U address we asked the nft maintenance thread to set up.
|
||||
* osmo_sockaddr_str_is_nonzero(addr_remote) == false when no rules were added yet, and when
|
||||
* we asked the nft maintenance thread to remove the rules for this hNodeB because it has
|
||||
* disconnected. */
|
||||
struct osmo_sockaddr_str addr_remote;
|
||||
|
||||
/* the nft handles needed to clean up the UL and DL rules when the hNodeB disconnects,
|
||||
* and the last seen counter value gotten from nft. */
|
||||
struct {
|
||||
struct nft_kpi_handle h;
|
||||
struct nft_kpi_val v;
|
||||
} ul;
|
||||
struct {
|
||||
struct nft_kpi_handle h;
|
||||
struct nft_kpi_val v;
|
||||
} dl;
|
||||
} nft_kpi;
|
||||
|
||||
struct osmo_timer_list disconnected_timeout;
|
||||
};
|
||||
|
||||
struct hnb_persistent *hnb_persistent_alloc(const struct umts_cell_id *id);
|
||||
struct hnb_persistent *hnb_persistent_find_by_id(const struct umts_cell_id *id);
|
||||
void hnb_persistent_registered(struct hnb_persistent *hnbp);
|
||||
void hnb_persistent_deregistered(struct hnb_persistent *hnbp);
|
||||
void hnb_persistent_free(struct hnb_persistent *hnbp);
|
||||
|
||||
unsigned long long hnbp_get_updowntime(const struct hnb_persistent *hnbp);
|
||||
@@ -2,29 +2,50 @@
|
||||
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/hashtable.h>
|
||||
#include <osmocom/core/write_queue.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/sigtran/sccp_sap.h>
|
||||
#include <osmocom/core/rate_ctr.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
#include <osmocom/gsm/gsm23003.h>
|
||||
#include <osmocom/sigtran/osmo_ss7.h>
|
||||
#include <osmocom/ctrl/control_if.h>
|
||||
#include <osmocom/ranap/RANAP_CN-DomainIndicator.h>
|
||||
|
||||
#define DEBUG
|
||||
#include <osmocom/core/logging.h>
|
||||
|
||||
#include <osmocom/mgcp_client/mgcp_client.h>
|
||||
#include <osmocom/mgcp_client/mgcp_client_pool.h>
|
||||
|
||||
#include <osmocom/hnbgw/nft_kpi.h>
|
||||
#include <osmocom/hnbgw/cnlink.h>
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
|
||||
#define STORE_UPTIME_INTERVAL 10 /* seconds */
|
||||
|
||||
enum {
|
||||
DMAIN,
|
||||
DHNBAP,
|
||||
DRUA,
|
||||
DRANAP,
|
||||
DMGW,
|
||||
DHNB,
|
||||
DCN,
|
||||
DNFT,
|
||||
};
|
||||
|
||||
#define LOGHNB(x, ss, lvl, fmt, args ...) \
|
||||
LOGP(ss, lvl, "%s " fmt, hnb_context_name(x), ## args)
|
||||
extern const struct log_info hnbgw_log_info;
|
||||
extern struct vty_app_info hnbgw_vty_info;
|
||||
|
||||
enum hnb_ctrl_node {
|
||||
CTRL_NODE_HNB = _LAST_CTRL_NODE,
|
||||
_LAST_CTRL_NODE_HNB
|
||||
};
|
||||
#define DOMAIN_CS RANAP_CN_DomainIndicator_cs_domain
|
||||
#define DOMAIN_PS RANAP_CN_DomainIndicator_ps_domain
|
||||
|
||||
extern const struct value_string ranap_domain_names[];
|
||||
static inline const char *ranap_domain_name(RANAP_CN_DomainIndicator_t domain)
|
||||
{
|
||||
return get_value_string(ranap_domain_names, domain);
|
||||
}
|
||||
|
||||
#define HNBGW_LOCAL_IP_DEFAULT "0.0.0.0"
|
||||
/* TODO: CS and PS now both connect to OsmoSTP, i.e. that's always going to be the same address. Drop the
|
||||
@@ -32,6 +53,12 @@ enum hnb_ctrl_node {
|
||||
#define HNBGW_IUCS_REMOTE_IP_DEFAULT "127.0.0.1"
|
||||
#define HNBGW_IUPS_REMOTE_IP_DEFAULT "127.0.0.1"
|
||||
|
||||
#define DEFAULT_PC_HNBGW ((23 << 3) + 5)
|
||||
#define DEFAULT_PC_MSC ((23 << 3) + 1)
|
||||
#define DEFAULT_PC_SGSN ((23 << 3) + 4)
|
||||
#define DEFAULT_ADDR_NAME_MSC "addr-dyn-msc-default"
|
||||
#define DEFAULT_ADDR_NAME_SGSN "addr-dyn-sgsn-default"
|
||||
|
||||
/* 25.467 Section 7.1 */
|
||||
#define IUH_DEFAULT_SCTP_PORT 29169
|
||||
#define RNA_DEFAULT_SCTP_PORT 25471
|
||||
@@ -44,82 +71,19 @@ enum hnb_ctrl_node {
|
||||
|
||||
#define IUH_MSGB_SIZE 2048
|
||||
|
||||
struct umts_cell_id {
|
||||
uint16_t mcc; /*!< Mobile Country Code */
|
||||
uint16_t mnc; /*!< Mobile Network Code */
|
||||
uint16_t lac; /*!< Locaton Area Code */
|
||||
uint16_t rac; /*!< Routing Area Code */
|
||||
uint16_t sac; /*!< Service Area Code */
|
||||
uint32_t cid; /*!< Cell ID */
|
||||
};
|
||||
struct hnbgw_context_map;
|
||||
|
||||
struct hnb_gw;
|
||||
static inline bool cnlink_is_cs(const struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
return cnlink && cnlink->pool->domain == DOMAIN_CS;
|
||||
}
|
||||
|
||||
enum hnbgw_cnlink_state {
|
||||
/* we have just been initialized or were disconnected */
|
||||
CNLINK_S_NULL,
|
||||
/* establishment of the SUA/SCCP link is pending */
|
||||
CNLINK_S_EST_PEND,
|
||||
/* establishment of the SUA/SCCP link was confirmed */
|
||||
CNLINK_S_EST_CONF,
|
||||
/* we have esnt the RANAP RESET and wait for the ACK */
|
||||
CNLINK_S_EST_RST_TX_WAIT_ACK,
|
||||
/* we have received the RANAP RESET ACK and are active */
|
||||
CNLINK_S_EST_ACTIVE,
|
||||
};
|
||||
static inline bool cnlink_is_ps(const struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
return cnlink && cnlink->pool->domain == DOMAIN_PS;
|
||||
}
|
||||
|
||||
struct hnbgw_cnlink {
|
||||
struct llist_head list;
|
||||
enum hnbgw_cnlink_state state;
|
||||
struct hnb_gw *gw;
|
||||
/* timer for re-transmitting the RANAP Reset */
|
||||
struct osmo_timer_list T_RafC;
|
||||
/* reference to the SCCP User SAP by which we communicate */
|
||||
struct osmo_sccp_instance *sccp;
|
||||
struct osmo_sccp_user *sccp_user;
|
||||
uint32_t next_conn_id;
|
||||
|
||||
/* linked list of hnbgw_context_map */
|
||||
struct llist_head map_list;
|
||||
};
|
||||
|
||||
struct hnb_context {
|
||||
/*! Entry in HNB-global list of HNB */
|
||||
struct llist_head list;
|
||||
/*! HNB-GW we are part of */
|
||||
struct hnb_gw *gw;
|
||||
/*! SCTP socket + write queue for Iuh to this specific HNB */
|
||||
struct osmo_stream_srv *conn;
|
||||
/*! copied from HNB-Identity-Info IE */
|
||||
char identity_info[256];
|
||||
/*! copied from Cell Identity IE */
|
||||
struct umts_cell_id id;
|
||||
|
||||
/*! SCTP stream ID for HNBAP */
|
||||
uint16_t hnbap_stream;
|
||||
/*! SCTP stream ID for RUA */
|
||||
uint16_t rua_stream;
|
||||
|
||||
/*! True if a HNB-REGISTER-REQ from this HNB has been accepted. Note that
|
||||
* this entire data structure is freed if the HNB sends HNB-DE-REGISTER-REQ. */
|
||||
bool hnb_registered;
|
||||
|
||||
/* linked list of hnbgw_context_map */
|
||||
struct llist_head map_list;
|
||||
};
|
||||
|
||||
struct ue_context {
|
||||
/*! Entry in the HNB-global list of UE */
|
||||
struct llist_head list;
|
||||
/*! Unique Context ID for this UE */
|
||||
uint32_t context_id;
|
||||
char imsi[16+1];
|
||||
uint32_t tmsi;
|
||||
/*! UE is serviced via this HNB */
|
||||
struct hnb_context *hnb;
|
||||
};
|
||||
|
||||
struct hnb_gw {
|
||||
struct hnbgw {
|
||||
struct {
|
||||
const char *iuh_local_ip;
|
||||
/*! SCTP port for Iuh listening */
|
||||
@@ -127,48 +91,97 @@ struct hnb_gw {
|
||||
/*! The UDP port where we receive multiplexed CS user
|
||||
* plane traffic from HNBs */
|
||||
uint16_t iuh_cs_mux_port;
|
||||
const char *iucs_remote_addr_name;
|
||||
const char *iups_remote_addr_name;
|
||||
struct osmo_plmn_id plmn;
|
||||
uint16_t rnc_id;
|
||||
bool hnbap_allow_tmsi;
|
||||
/*! print hnb-id (true) or MCC-MNC-LAC-RAC-SAC (false) in logs */
|
||||
bool log_prefix_hnb_id;
|
||||
bool accept_all_hnb;
|
||||
struct mgcp_client_conf *mgcp_client;
|
||||
struct {
|
||||
char *local_addr;
|
||||
uint16_t local_port;
|
||||
char *remote_addr;
|
||||
uint16_t remote_port;
|
||||
struct {
|
||||
char *access;
|
||||
char *core;
|
||||
} netinst;
|
||||
} pfcp;
|
||||
struct {
|
||||
bool enable;
|
||||
/* The table name as used in nftables for the ruleset owned by this process. It is "osmo-hnbgw"
|
||||
* by default. */
|
||||
char *table_name;
|
||||
} nft_kpi;
|
||||
} config;
|
||||
/*! SCTP listen socket for incoming connections */
|
||||
struct osmo_stream_srv_link *iuh;
|
||||
/* list of struct hnb_context */
|
||||
struct llist_head hnb_list;
|
||||
/* list of struct ue_context */
|
||||
struct llist_head ue_list;
|
||||
|
||||
/* list of struct hnb_persistent */
|
||||
struct llist_head hnb_persistent_list;
|
||||
/* optimized lookup for hnb_persistent, by cell id string */
|
||||
DECLARE_HASHTABLE(hnb_persistent_by_id, 5);
|
||||
|
||||
struct osmo_timer_list store_uptime_timer;
|
||||
/* next availble UE Context ID */
|
||||
uint32_t next_ue_ctx_id;
|
||||
struct ctrl_handle *ctrl;
|
||||
/* currently active CN links for CS and PS */
|
||||
struct {
|
||||
struct osmo_sccp_instance *client;
|
||||
struct hnbgw_cnlink *cnlink;
|
||||
struct osmo_sccp_addr local_addr;
|
||||
struct osmo_sccp_addr iucs_remote_addr;
|
||||
struct osmo_sccp_addr iups_remote_addr;
|
||||
/* List of hnbgw_sccp_user */
|
||||
struct llist_head users;
|
||||
|
||||
/* Pool of core network peers: MSCs for IuCS */
|
||||
struct hnbgw_cnpool *cnpool_iucs;
|
||||
/* Pool of core network peers: SGSNs for IuPS */
|
||||
struct hnbgw_cnpool *cnpool_iups;
|
||||
} sccp;
|
||||
/* MGW pool, also includes the single MGCP client as fallback if no
|
||||
* pool is configured. */
|
||||
struct mgcp_client_pool *mgw_pool;
|
||||
|
||||
struct {
|
||||
struct osmo_pfcp_endpoint *ep;
|
||||
struct osmo_pfcp_cp_peer *cp_peer;
|
||||
/* Running counters for the PFCP conn */
|
||||
struct osmo_stat_item_group *statg;
|
||||
} pfcp;
|
||||
|
||||
struct osmo_timer_list hnb_store_rab_durations_timer;
|
||||
|
||||
struct {
|
||||
bool active;
|
||||
struct osmo_timer_list get_counters_timer;
|
||||
struct timespec next_timer;
|
||||
} nft_kpi;
|
||||
};
|
||||
|
||||
extern struct hnbgw *g_hnbgw;
|
||||
extern void *talloc_asn1_ctx;
|
||||
|
||||
struct hnb_context *hnb_context_by_id(struct hnb_gw *gw, uint32_t cid);
|
||||
struct hnb_context *hnb_context_by_identity_info(struct hnb_gw *gw, const char *identity_info);
|
||||
const char *hnb_context_name(struct hnb_context *ctx);
|
||||
unsigned hnb_contexts(const struct hnb_gw *gw);
|
||||
void g_hnbgw_alloc(void *ctx);
|
||||
|
||||
struct ue_context *ue_context_by_id(struct hnb_gw *gw, uint32_t id);
|
||||
struct ue_context *ue_context_by_imsi(struct hnb_gw *gw, const char *imsi);
|
||||
struct ue_context *ue_context_by_tmsi(struct hnb_gw *gw, uint32_t tmsi);
|
||||
struct ue_context *ue_context_alloc(struct hnb_context *hnb, const char *imsi,
|
||||
uint32_t tmsi);
|
||||
void ue_context_free(struct ue_context *ue);
|
||||
int hnbgw_rua_accept_cb(struct osmo_stream_srv_link *srv, int fd);
|
||||
int hnbgw_mgw_setup(void);
|
||||
|
||||
struct hnb_context *hnb_context_alloc(struct hnb_gw *gw, struct osmo_stream_srv_link *link, int new_fd);
|
||||
void hnb_context_release(struct hnb_context *ctx);
|
||||
|
||||
void hnbgw_vty_init(struct hnb_gw *gw, void *tall_ctx);
|
||||
void hnbgw_vty_init(void);
|
||||
int hnbgw_vty_go_parent(struct vty *vty);
|
||||
|
||||
/* Return true when the user configured GTP mapping to be enabled, by configuring a PFCP link to a UPF.
|
||||
* Return false when the user configured to skip GTP mapping and RANAP PS RAB Requests/Responses should be passed thru
|
||||
* 1:1.
|
||||
* GTP mapping means that there are two GTP tunnels, one towards HNB and one towards CN, and we forward payloads between
|
||||
* the two tunnels, mapping the TEIDs and GTP addresses. */
|
||||
static inline bool hnb_gw_is_gtp_mapping_enabled(void)
|
||||
{
|
||||
return g_hnbgw->config.pfcp.remote_addr != NULL;
|
||||
}
|
||||
|
||||
struct msgb *hnbgw_ranap_msg_alloc(const char *name);
|
||||
|
||||
int hnbgw_peek_l3_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg);
|
||||
|
||||
uint32_t get_next_ue_ctx_id(void);
|
||||
|
||||
@@ -1,5 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <stdint.h>
|
||||
#include <osmocom/core/rate_ctr.h>
|
||||
|
||||
int hnbgw_cnlink_init(struct hnb_gw *gw, const char *stp_host, uint16_t stp_port, const char *local_ip);
|
||||
#include <osmocom/sigtran/sccp_sap.h>
|
||||
|
||||
#include <osmocom/ranap/RANAP_CN-DomainIndicator.h>
|
||||
|
||||
struct hnbgw_context_map;
|
||||
|
||||
enum hnbgw_cnpool_ctr {
|
||||
/* TODO: basic counters completely missing
|
||||
* ...
|
||||
*/
|
||||
|
||||
/* Counters related to link selection from a CN pool. */
|
||||
CNPOOL_CTR_SUBSCR_NO_CNLINK,
|
||||
CNPOOL_CTR_EMERG_FORWARDED,
|
||||
CNPOOL_CTR_EMERG_LOST,
|
||||
};
|
||||
#define CNPOOL_CTR_INC(cnpool, x) rate_ctr_inc2((cnpool)->ctrs, x)
|
||||
|
||||
/* User provided configuration for struct hnbgw_cnpool. */
|
||||
struct hnbgw_cnpool_cfg {
|
||||
uint8_t nri_bitlen;
|
||||
struct osmo_nri_ranges *null_nri_ranges;
|
||||
};
|
||||
|
||||
/* Collection of CN peers to distribute UE connections across. MSCs for DOMAIN_CS, SGSNs for DOMAIN_PS. */
|
||||
struct hnbgw_cnpool {
|
||||
RANAP_CN_DomainIndicator_t domain;
|
||||
|
||||
/* CN pool string used in VTY config and logging, "iucs" or "iups". */
|
||||
const char *pool_name;
|
||||
/* CN peer string used in VTY config and logging, "msc" or "sgsn". */
|
||||
const char *peer_name;
|
||||
/* What we use as the remote MSC/SGSN point-code if the user does not configure any address. */
|
||||
uint32_t default_remote_pc;
|
||||
const char *default_addr_name;
|
||||
|
||||
struct hnbgw_cnpool_cfg vty;
|
||||
struct hnbgw_cnpool_cfg use;
|
||||
|
||||
/* List of struct hnbgw_cnlink */
|
||||
struct llist_head cnlinks;
|
||||
|
||||
unsigned int round_robin_next_nr;
|
||||
/* Emergency calls potentially select a different set of MSCs, so to not mess up the normal round-robin
|
||||
* behavior, emergency calls need a separate round-robin counter. */
|
||||
unsigned int round_robin_next_emerg_nr;
|
||||
|
||||
/* Running counters for this pool */
|
||||
struct rate_ctr_group *ctrs;
|
||||
};
|
||||
|
||||
struct hnbgw_cnpool *hnbgw_cnpool_alloc(RANAP_CN_DomainIndicator_t domain);
|
||||
|
||||
struct hnbgw_cnlink *hnbgw_cnlink_select(struct hnbgw_context_map *map);
|
||||
|
||||
void hnbgw_cnpool_start(struct hnbgw_cnpool *cnpool);
|
||||
void hnbgw_cnpool_cnlinks_start_or_restart(struct hnbgw_cnpool *cnpool);
|
||||
struct hnbgw_cnlink *cnlink_get_nr(struct hnbgw_cnpool *cnpool, int nr, bool create_if_missing);
|
||||
void hnbgw_cnpool_apply_cfg(struct hnbgw_cnpool *cnpool);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/* HNBAP, 3GPP TS 25.469 */
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
|
||||
struct hnb_context;
|
||||
|
||||
int hnbgw_hnbap_rx(struct hnb_context *hnb, struct msgb *msg);
|
||||
int hnbgw_hnbap_init(void);
|
||||
|
||||
9
include/osmocom/hnbgw/hnbgw_pfcp.h
Normal file
9
include/osmocom/hnbgw/hnbgw_pfcp.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
enum hnbgw_upf_stats {
|
||||
HNBGW_UPF_STAT_ASSOCIATED,
|
||||
};
|
||||
#define HNBGW_UPF_STAT_SET(stat, val) osmo_stat_item_set(osmo_stat_item_group_get_item(g_hnbgw->pfcp.statg, (stat)), (val))
|
||||
|
||||
int hnbgw_pfcp_init(void);
|
||||
void hnbgw_pfcp_release(void);
|
||||
@@ -1,6 +1,18 @@
|
||||
/* RANAP, 3GPP TS 25.413 */
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/ranap/ranap_ies_defs.h>
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
|
||||
int hnbgw_ranap_rx(struct msgb *msg, uint8_t *data, size_t len);
|
||||
struct osmo_scu_unitdata_param;
|
||||
|
||||
ranap_message *hnbgw_decode_ranap_cn_co(struct msgb *ranap_msg);
|
||||
|
||||
int hnbgw_ranap_rx_udt_ul(struct msgb *msg, uint8_t *data, size_t len);
|
||||
int hnbgw_ranap_rx_data_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg);
|
||||
|
||||
int hnbgw_ranap_rx_udt_dl(struct hnbgw_cnlink *cnlink, const struct osmo_scu_unitdata_param *unitdata,
|
||||
const uint8_t *data, unsigned int len);
|
||||
int hnbgw_ranap_rx_data_dl(struct hnbgw_context_map *map, struct msgb *ranap_msg);
|
||||
int hnbgw_ranap_init(void);
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
/* RUA, 3GPP TS 25.468 */
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/rua/RUA_Cause.h>
|
||||
|
||||
struct hnb_context;
|
||||
|
||||
int hnbgw_rua_rx(struct hnb_context *hnb, struct msgb *msg);
|
||||
int hnbgw_rua_init(void);
|
||||
|
||||
int rua_tx_udt(struct hnb_context *hnb, const uint8_t *data, unsigned int len);
|
||||
int rua_tx_dt(struct hnb_context *hnb, int is_ps, uint32_t context_id,
|
||||
const uint8_t *data, unsigned int len);
|
||||
int rua_tx_disc(struct hnb_context *hnb, int is_ps, uint32_t context_id,
|
||||
const RUA_Cause_t *cause, const uint8_t *data, unsigned int len);
|
||||
|
||||
|
||||
66
include/osmocom/hnbgw/hnbgw_sccp.h
Normal file
66
include/osmocom/hnbgw/hnbgw_sccp.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/* SCCP, ITU Q.711 - Q.714 */
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <osmocom/core/hashtable.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/prim.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/use_count.h>
|
||||
|
||||
#include <osmocom/sigtran/sccp_sap.h>
|
||||
|
||||
struct hnbgw_cnlink;
|
||||
|
||||
/* osmo-hnbgw keeps a single hnbgw_sccp_user per osmo_sccp_instance, for the local point-code and SSN == RANAP.
|
||||
* This relates the (opaque) osmo_sccp_user to osmo-hnbgw's per-ss7 state. */
|
||||
struct hnbgw_sccp_user {
|
||||
/* entry in g_hnbgw->sccp.users */
|
||||
struct llist_head entry;
|
||||
|
||||
/* logging context */
|
||||
char *name;
|
||||
|
||||
/* Which 'cs7 instance' is this for? Below sccp_user is registered at the osmo_sccp_instance ss7->sccp. */
|
||||
struct osmo_ss7_instance *ss7;
|
||||
|
||||
/* Local address: cs7 instance's primary PC if present, else the default HNBGW PC; with SSN == RANAP. */
|
||||
struct osmo_sccp_addr local_addr;
|
||||
|
||||
/* osmo_sccp API state for above local address on above ss7 instance. */
|
||||
struct osmo_sccp_user *sccp_user;
|
||||
|
||||
/* Ref count of users of this struct, ie.referencing it in cnlink->hnbgw_sccp_user */
|
||||
struct osmo_use_count use_count;
|
||||
|
||||
/* Fast access to the hnbgw_context_map responsible for a given SCCP conn_id of the ss7->sccp instance.
|
||||
* hlist_node: hnbgw_context_map->hnbgw_sccp_user_entry. */
|
||||
DECLARE_HASHTABLE(hnbgw_context_map_by_conn_id, 6);
|
||||
};
|
||||
|
||||
#define LOG_HSU(HSU, SUBSYS, LEVEL, FMT, ARGS...) \
|
||||
LOGP(SUBSYS, LEVEL, "(%s) " FMT, (HSU) ? (HSU)->name : "null", ##ARGS)
|
||||
|
||||
#define HSU_USE_CNLINK "cnlink"
|
||||
#define hnbgw_sccp_user_get(hsu, use) \
|
||||
OSMO_ASSERT(osmo_use_count_get_put(&(hsu)->use_count, use, 1) == 0)
|
||||
#define hnbgw_sccp_user_put(hsu, use) \
|
||||
OSMO_ASSERT(osmo_use_count_get_put(&(hsu)->use_count, use, -1) == 0)
|
||||
|
||||
struct hnbgw_sccp_user *hnbgw_sccp_user_alloc(int ss7_inst_id);
|
||||
|
||||
int hnbgw_sccp_user_tx_unitdata_req(struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *called_addr,
|
||||
struct msgb *ranap_msg);
|
||||
int hnbgw_sccp_user_tx_connect_req(struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *called_addr,
|
||||
uint32_t scu_conn_id, struct msgb *ranap_msg);
|
||||
int hnbgw_sccp_user_tx_data_req(struct hnbgw_sccp_user *hsu, uint32_t scu_conn_id,
|
||||
struct msgb *ranap_msg);
|
||||
int hnbgw_sccp_user_tx_disconnect_req(struct hnbgw_sccp_user *hsu, uint32_t scu_conn_id);
|
||||
|
||||
static inline struct osmo_sccp_instance *hnbgw_sccp_user_get_sccp_instance(const struct hnbgw_sccp_user *hsu)
|
||||
{
|
||||
if (!hsu->ss7)
|
||||
return NULL;
|
||||
return osmo_ss7_get_sccp(hsu->ss7);
|
||||
}
|
||||
11
include/osmocom/hnbgw/kpi.h
Normal file
11
include/osmocom/hnbgw/kpi.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/ranap/ranap_ies_defs.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
|
||||
void kpi_ranap_process_ul(struct hnbgw_context_map *map, ranap_message *ranap);
|
||||
void kpi_ranap_process_dl(struct hnbgw_context_map *map, ranap_message *ranap);
|
||||
|
||||
void kpi_dtap_process_ul(struct hnbgw_context_map *map, const uint8_t *buf, unsigned int len, uint8_t sapi);
|
||||
void kpi_dtap_process_dl(struct hnbgw_context_map *map, const uint8_t *buf, unsigned int len, uint8_t sapi);
|
||||
9
include/osmocom/hnbgw/mgw_fsm.h
Normal file
9
include/osmocom/hnbgw/mgw_fsm.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/ranap/ranap_ies_defs.h>
|
||||
|
||||
int handle_cs_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message);
|
||||
int mgw_fsm_handle_cs_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message);
|
||||
int mgw_fsm_release(struct hnbgw_context_map *map);
|
||||
|
||||
uint64_t mgw_fsm_get_elapsed_ms(struct hnbgw_context_map *map, const struct timespec *now);
|
||||
25
include/osmocom/hnbgw/nft_kpi.h
Normal file
25
include/osmocom/hnbgw/nft_kpi.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct hnb_persistent;
|
||||
|
||||
/* A "handle" that nftables returns for chains and rules -- a plain number. Deleting an unnamed rule can only be done by
|
||||
* such a handle. */
|
||||
struct nft_kpi_handle {
|
||||
bool handle_present;
|
||||
int64_t handle;
|
||||
};
|
||||
|
||||
/* One GTP-U packet and byte counter cache, i.e. for one UL/DL direction of one hNodeB. */
|
||||
struct nft_kpi_val {
|
||||
uint64_t packets;
|
||||
uint64_t total_bytes;
|
||||
uint64_t ue_bytes;
|
||||
};
|
||||
|
||||
void nft_kpi_init(const char *table_name);
|
||||
void nft_kpi_hnb_persistent_add(struct hnb_persistent *hnbp);
|
||||
void nft_kpi_hnb_persistent_remove(struct hnb_persistent *hnbp);
|
||||
int nft_kpi_hnb_start(struct hnb_persistent *hnbp, const struct osmo_sockaddr_str *gtpu_remote);
|
||||
void nft_kpi_hnb_stop(struct hnb_persistent *hnbp);
|
||||
15
include/osmocom/hnbgw/ps_rab_ass_fsm.h
Normal file
15
include/osmocom/hnbgw/ps_rab_ass_fsm.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/ranap/ranap_ies_defs.h>
|
||||
|
||||
enum ps_rab_ass_fsm_event {
|
||||
PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX,
|
||||
PS_RAB_ASS_EV_RAB_ASS_RESP,
|
||||
PS_RAB_ASS_EV_RAB_ESTABLISHED,
|
||||
PS_RAB_ASS_EV_RAB_RELEASED,
|
||||
PS_RAB_ASS_EV_RAB_FAIL,
|
||||
};
|
||||
|
||||
int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message);
|
||||
int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message);
|
||||
void hnbgw_gtpmap_release(struct hnbgw_context_map *map);
|
||||
99
include/osmocom/hnbgw/ps_rab_fsm.h
Normal file
99
include/osmocom/hnbgw/ps_rab_fsm.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/core/use_count.h>
|
||||
#include <osmocom/pfcp/pfcp_msg.h>
|
||||
|
||||
/* A GTP tunnel has two endpoints, each endpoint has an IP address and a Tunnel Endpoint ID. So two struct addr_teid
|
||||
* identify one GTP tunnel. For GTP mapping between HNB and CN, we have two tunnels, see also struct half_gtp_map. The
|
||||
* combination of IP address and TEID is also known as F-TEID (fully qualified TEID). */
|
||||
struct addr_teid {
|
||||
bool present;
|
||||
struct osmo_sockaddr addr;
|
||||
uint32_t teid;
|
||||
};
|
||||
|
||||
/* One half_gtp_map represents one GTP tunnel, either on the HNB side or on the CN side. Two struct half_gtp_map make up
|
||||
* a GTP mapping between HNB and CN. One half_gtp_map for the Access (HNB) side, one for the Core (CN) side. The PFCP
|
||||
* PDR (Packet Detection Rule) identifies packets coming in on the GTP tunnel the half_gtp_map represents, while the
|
||||
* PFCP FAR (Forwarding Action Rule) identifies the GTP destination, i.e. the other side's GTP tunnel. So a
|
||||
* half_gtp_map.far_id is closely tied to the other half_gtp_map, and makes little sense on its own.
|
||||
*
|
||||
* half_gtp_map | half_gtp_map
|
||||
* Access HNBGW+UPF Core
|
||||
* remote local | local remote
|
||||
* -->PDR-FAR-->|
|
||||
* |<--FAR-PDR<--
|
||||
*
|
||||
* See ps_rab.core, ps_rab.access.
|
||||
*/
|
||||
struct half_gtp_map {
|
||||
/* GTP endpoint, obtained from incoming RAB Assignment Request/Response.
|
||||
* This is the remote side as seen from the UPF's point of view.
|
||||
* For example, ps_rab.core.remote is the CN GTP that the RAB Assignment Request told us.
|
||||
* ps_rab.access.remote is the HNB GTP that RAB Assignment Response told us. */
|
||||
struct addr_teid remote;
|
||||
/* UPF GTP endpoint, obtained from PFCP Session Establishment Response. */
|
||||
struct addr_teid local;
|
||||
/* PFCP Packet Detection Rule id that detects GTP-U packets coming from Core/Access */
|
||||
uint16_t pdr_id;
|
||||
/* PFCP Forward Action Rule id that forwards GTP-U packets to Access/Core */
|
||||
uint32_t far_id;
|
||||
/* Whether the RANAP message this RAB's remote address was obtained from had the address encoded in x213_nsap */
|
||||
bool use_x213_nsap;
|
||||
};
|
||||
|
||||
/* A PS RAB's PFCP state. For the related RANAP state, see struct ps_rab_ass instead. */
|
||||
struct ps_rab {
|
||||
/* Instance of ps_rab_fsm. */
|
||||
struct osmo_fsm_inst *fi;
|
||||
|
||||
/* List entry and backpointer.
|
||||
* If map == NULL, do not call llist_del(&entry): the hnbgw_context_map may deallocate before the PFCP release
|
||||
* is complete, in which case it sets map = NULL. */
|
||||
struct llist_head entry;
|
||||
struct hnbgw_context_map *map;
|
||||
|
||||
/* RAB-ID used in RANAP RAB AssignmentRequest and Response messages */
|
||||
uint8_t rab_id;
|
||||
/* Backpointer to the ps_rab_ass_fsm for the RAB Assignment Request from Core that created this RAB.
|
||||
* There are two separate RAB Assignment FSMs responsible for this RAB, one for the Request message and one for
|
||||
* the Response message. Each RAB Assignment FSM may be responsible for N other RABs besides this one. */
|
||||
struct osmo_fsm_inst *req_fi;
|
||||
/* Backpointer to the ps_rab_ass_fsm for the RAB Assignment Response from Access that confirmed this RAB. */
|
||||
struct osmo_fsm_inst *resp_fi;
|
||||
|
||||
/* PFCP session controlling the GTP mapping for this RAB */
|
||||
uint64_t cp_seid;
|
||||
struct osmo_pfcp_ie_f_seid up_f_seid;
|
||||
bool release_requested;
|
||||
|
||||
/* 'local' and 'remote' refer to the GTP information from the UPF's point of view:
|
||||
* HNB UPF CN
|
||||
* access.remote <---> access.local | core.local <---> core.remote
|
||||
*/
|
||||
struct half_gtp_map core;
|
||||
struct half_gtp_map access;
|
||||
|
||||
struct osmo_use_count use_count;
|
||||
};
|
||||
|
||||
struct ps_rab *ps_rab_start(struct hnbgw_context_map *map, uint8_t rab_id,
|
||||
const struct addr_teid *core_f_teid, bool use_x213_nsap,
|
||||
struct osmo_fsm_inst *req_fi);
|
||||
|
||||
struct ps_rab *ps_rab_get(struct hnbgw_context_map *map, uint8_t rab_id);
|
||||
bool ps_rab_is_established(const struct ps_rab *rab);
|
||||
void ps_rab_release(struct ps_rab *rab, struct osmo_fsm_inst *notify_fi);
|
||||
|
||||
struct ps_rab_rx_args {
|
||||
struct addr_teid f_teid;
|
||||
bool use_x213_nsap;
|
||||
struct osmo_fsm_inst *notify_fi;
|
||||
};
|
||||
int ps_rab_rx_access_remote_f_teid(struct hnbgw_context_map *map, uint8_t rab_id,
|
||||
const struct ps_rab_rx_args *args);
|
||||
|
||||
struct ps_rab *ps_rab_find_by_seid(uint64_t seid, bool is_cp_seid);
|
||||
void ps_rab_pfcp_set_msg_ctx(struct ps_rab *rab, struct osmo_pfcp_msg *m);
|
||||
20
include/osmocom/hnbgw/ranap_rab_ass.h
Normal file
20
include/osmocom/hnbgw/ranap_rab_ass.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
struct msgb *ranap_rab_ass_req_encode(RANAP_RAB_AssignmentRequestIEs_t *rab_assignment_request_ies);
|
||||
int ranap_rab_ass_resp_encode(uint8_t *data, unsigned int len,
|
||||
RANAP_RAB_AssignmentResponseIEs_t *rab_assignment_response_ies);
|
||||
|
||||
int ranap_rab_ass_req_ies_extract_inet_addr(struct osmo_sockaddr *addr, uint8_t *rab_id,
|
||||
RANAP_RAB_AssignmentRequestIEs_t *ies, unsigned int index);
|
||||
int ranap_rab_ass_resp_ies_extract_inet_addr(struct osmo_sockaddr *addr, RANAP_RAB_AssignmentResponseIEs_t *ies,
|
||||
uint8_t rab_id);
|
||||
|
||||
int ranap_rab_ass_req_ies_replace_inet_addr(RANAP_RAB_AssignmentRequestIEs_t *ies, struct osmo_sockaddr *addr,
|
||||
uint8_t rab_id);
|
||||
int ranap_rab_ass_resp_ies_replace_inet_addr(RANAP_RAB_AssignmentResponseIEs_t *ies, struct osmo_sockaddr *addr,
|
||||
uint8_t rab_id);
|
||||
|
||||
bool ranap_rab_ass_req_ies_check_release(RANAP_RAB_AssignmentRequestIEs_t *ies, uint8_t rab_id);
|
||||
bool ranap_rab_ass_resp_ies_check_failure(RANAP_RAB_AssignmentResponseIEs_t *ies, uint8_t rab_id);
|
||||
|
||||
int ranap_rab_ass_req_ies_get_count(RANAP_RAB_AssignmentRequestIEs_t *ies);
|
||||
7
include/osmocom/hnbgw/tdefs.h
Normal file
7
include/osmocom/hnbgw/tdefs.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/core/tdef.h>
|
||||
|
||||
extern struct osmo_tdef mgw_fsm_T_defs[];
|
||||
extern struct osmo_tdef hnbgw_T_defs[];
|
||||
extern struct osmo_tdef_group hnbgw_tdef_group[];
|
||||
34
include/osmocom/hnbgw/umts_cell_id.h
Normal file
34
include/osmocom/hnbgw/umts_cell_id.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <osmocom/gsm/gsm23003.h>
|
||||
|
||||
struct umts_cell_id {
|
||||
struct osmo_plmn_id plmn; /*!< Mobile Country Code and Mobile Network Code (000-00 to 999-999) */
|
||||
uint16_t lac; /*!< Locaton Area Code (1-65534) */
|
||||
uint8_t rac; /*!< Routing Area Code (0-255) */
|
||||
uint16_t sac; /*!< Service Area Code */
|
||||
uint32_t cid; /*!< Cell ID */
|
||||
};
|
||||
int umts_cell_id_to_str_buf(char *buf, size_t buflen, const struct umts_cell_id *ucid);
|
||||
char *umts_cell_id_to_str_c(void *ctx, const struct umts_cell_id *ucid);
|
||||
const char *umts_cell_id_to_str(const struct umts_cell_id *ucid);
|
||||
int umts_cell_id_from_str(struct umts_cell_id *ucid, const char *instr);
|
||||
uint32_t umts_cell_id_hash(const struct umts_cell_id *ucid);
|
||||
|
||||
/*! are both given umts_cell_id euqal? */
|
||||
static inline bool umts_cell_id_equal(const struct umts_cell_id *a, const struct umts_cell_id *b)
|
||||
{
|
||||
if (osmo_plmn_cmp(&a->plmn, &b->plmn))
|
||||
return false;
|
||||
if (a->lac != b->lac)
|
||||
return false;
|
||||
if (a->rac != b->rac)
|
||||
return false;
|
||||
if (a->sac != b->sac)
|
||||
return false;
|
||||
if (a->cid != b->cid)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@@ -4,8 +4,14 @@
|
||||
|
||||
enum osmo_iuh_vty_node {
|
||||
HNBGW_NODE = _LAST_OSMOVTY_NODE + 1,
|
||||
HNB_NODE,
|
||||
IUH_NODE,
|
||||
IUCS_NODE,
|
||||
IUPS_NODE,
|
||||
MGCP_NODE,
|
||||
MGW_NODE,
|
||||
PFCP_NODE,
|
||||
MSC_NODE,
|
||||
SGSN_NODE,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# (C) 2021 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
|
||||
# (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
|
||||
@@ -19,27 +19,45 @@ AM_CFLAGS = \
|
||||
$(LIBOSMORUA_CFLAGS) \
|
||||
$(LIBOSMORANAP_CFLAGS) \
|
||||
$(LIBOSMOHNBAP_CFLAGS) \
|
||||
$(LIBOSMOMGCPCLIENT_CFLAGS) \
|
||||
$(LIBNFTABLES_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
AM_LDFLAGS = \
|
||||
$(COVERAGE_LDFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
osmo-hnbgw \
|
||||
noinst_LTLIBRARIES = \
|
||||
libhnbgw.la \
|
||||
$(NULL)
|
||||
|
||||
osmo_hnbgw_SOURCES = \
|
||||
libhnbgw_la_SOURCES = \
|
||||
hnb.c \
|
||||
hnb_persistent.c \
|
||||
hnbgw.c \
|
||||
hnbgw_hnbap.c \
|
||||
hnbgw_l3.c \
|
||||
hnbgw_rua.c \
|
||||
hnbgw_ranap.c \
|
||||
hnbgw_sccp.c \
|
||||
hnbgw_vty.c \
|
||||
context_map.c \
|
||||
context_map_rua.c \
|
||||
context_map_sccp.c \
|
||||
hnbgw_cn.c \
|
||||
cnlink.c \
|
||||
cnlink_fsm.c \
|
||||
cnlink_paging.c \
|
||||
ranap_rab_ass.c \
|
||||
mgw_fsm.c \
|
||||
kpi_dtap.c \
|
||||
kpi_ranap.c \
|
||||
tdefs.c \
|
||||
umts_cell_id.c \
|
||||
nft_kpi.c \
|
||||
$(NULL)
|
||||
|
||||
osmo_hnbgw_LDADD = \
|
||||
libhnbgw_la_LIBADD = \
|
||||
$(LIBASN1C_LIBS) \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
@@ -52,4 +70,32 @@ osmo_hnbgw_LDADD = \
|
||||
$(LIBOSMORANAP_LIBS) \
|
||||
$(LIBOSMOHNBAP_LIBS) \
|
||||
$(LIBSCTP_LIBS) \
|
||||
$(LIBOSMOMGCPCLIENT_LIBS) \
|
||||
$(LIBNFTABLES_LIBS) \
|
||||
$(NULL)
|
||||
|
||||
if ENABLE_PFCP
|
||||
AM_CFLAGS += \
|
||||
$(LIBOSMOPFCP_CFLAGS) \
|
||||
$(NULL)
|
||||
libhnbgw_la_LIBADD += \
|
||||
$(LIBOSMOPFCP_LIBS) \
|
||||
$(NULL)
|
||||
libhnbgw_la_SOURCES += \
|
||||
hnbgw_pfcp.c \
|
||||
ps_rab_ass_fsm.c \
|
||||
ps_rab_fsm.c \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
bin_PROGRAMS = \
|
||||
osmo-hnbgw \
|
||||
$(NULL)
|
||||
|
||||
osmo_hnbgw_SOURCES = \
|
||||
osmo_hnbgw_main.c \
|
||||
$(NULL)
|
||||
|
||||
osmo_hnbgw_LDADD = \
|
||||
libhnbgw.la \
|
||||
$(NULL)
|
||||
|
||||
554
src/osmo-hnbgw/cnlink.c
Normal file
554
src/osmo-hnbgw/cnlink.c
Normal file
@@ -0,0 +1,554 @@
|
||||
/* (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Neels Hofmeyr
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <osmocom/core/fsm.h>
|
||||
#include <osmocom/core/tdef.h>
|
||||
#include <osmocom/core/stats.h>
|
||||
#include <osmocom/core/stat_item.h>
|
||||
|
||||
#include <osmocom/gsm/gsm23236.h>
|
||||
|
||||
#include <osmocom/sigtran/sccp_helpers.h>
|
||||
|
||||
#include <asn1c/asn1helpers.h>
|
||||
#include <osmocom/ranap/ranap_ies_defs.h>
|
||||
#include <osmocom/ranap/ranap_msg_factory.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
#include <osmocom/hnbgw/tdefs.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
|
||||
static const struct rate_ctr_desc cnlink_ctr_description[] = {
|
||||
[CNLINK_CTR_RANAP_RX_UDT_RESET] = {
|
||||
"ranap:rx:udt:reset",
|
||||
"RANAP Unitdata RESET messages received"
|
||||
},
|
||||
[CNLINK_CTR_RANAP_RX_UDT_RESET_ACK] = {
|
||||
"ranap:rx:udt:reset_ack",
|
||||
"RANAP Unitdata RESET ACK messages received",
|
||||
},
|
||||
[CNLINK_CTR_RANAP_RX_UDT_PAGING] = {
|
||||
"ranap:rx:udt:paging",
|
||||
"RANAP Unitdata PAGING messages received",
|
||||
},
|
||||
[CNLINK_CTR_RANAP_RX_UDT_UNKNOWN] = {
|
||||
"ranap:rx:udt:unknown",
|
||||
"Unknown RANAP Unitdata messages received",
|
||||
},
|
||||
[CNLINK_CTR_RANAP_RX_UDT_UNSUPPORTED] = {
|
||||
"ranap:rx:udt:unsupported",
|
||||
"Unsupported RANAP Unitdata messages received",
|
||||
},
|
||||
[CNLINK_CTR_RANAP_RX_UDT_OVERLOAD_IND] = {
|
||||
"ranap:rx:udt:overload_ind",
|
||||
"RANAP Unitdata Overload Indications received",
|
||||
},
|
||||
[CNLINK_CTR_RANAP_RX_UDT_ERROR_IND] = {
|
||||
"ranap:rx:udt:error_ind",
|
||||
"RANAP Unitdata Error Indications received",
|
||||
},
|
||||
|
||||
[CNLINK_CTR_RANAP_TX_UDT_RESET] = {
|
||||
"ranap:tx:udt:reset",
|
||||
"RANAP Unitdata RESET messages transmitted",
|
||||
},
|
||||
[CNLINK_CTR_RANAP_TX_UDT_RESET_ACK] = {
|
||||
"ranap:tx:udt:reset_ack",
|
||||
"RANAP Unitdata RESET ACK messages transmitted",
|
||||
},
|
||||
|
||||
/* SCCP Counters: */
|
||||
[CNLINK_CTR_SCCP_N_UNITDATA_REQ] = {
|
||||
"sccp:n_unit_data:req",
|
||||
"Submit SCCP N-UNITDATA.req (UL)"
|
||||
},
|
||||
[CNLINK_CTR_SCCP_N_UNITDATA_IND] = {
|
||||
"sccp:n_unit_data:ind",
|
||||
"Received SCCP N-UNITDATA.ind (DL)"
|
||||
},
|
||||
[CNLINK_CTR_SCCP_N_NOTICE_IND] = {
|
||||
"sccp:n_notice:ind",
|
||||
"Received SCCP N-NOTICE.ind"
|
||||
},
|
||||
[CNLINK_CTR_SCCP_N_CONNECT_REQ] = {
|
||||
"sccp:n_connect:req",
|
||||
"Submit SCCP N-CONNECT.req (UL SCCP CR)"
|
||||
},
|
||||
[CNLINK_CTR_SCCP_N_CONNECT_CNF] = {
|
||||
"sccp:n_connect:cnf",
|
||||
"Received SCCP N-CONNECT.cnf (DL SCCP CC)"
|
||||
},
|
||||
[CNLINK_CTR_SCCP_N_DATA_REQ] = {
|
||||
"sccp:n_data:req",
|
||||
"SUBMIT SCCP N-DATA.req (UL)"
|
||||
},
|
||||
[CNLINK_CTR_SCCP_N_DATA_IND] = {
|
||||
"sccp:n_data:ind",
|
||||
"Received SCCP N-DATA.ind (DL)"
|
||||
},
|
||||
[CNLINK_CTR_SCCP_N_DISCONNECT_REQ] = {
|
||||
"sccp:n_disconnect:req",
|
||||
"Submit SCCP N-DISCONNECT.req (UL SCCP RLC)"
|
||||
},
|
||||
[CNLINK_CTR_SCCP_N_DISCONNECT_IND] = {
|
||||
"sccp:n_disconnect:ind",
|
||||
"Received SCCP N-DISCONNECT.ind (DL SCCP RLSD)"
|
||||
},
|
||||
[CNLINK_CTR_SCCP_N_PCSTATE_IND] = {
|
||||
"sccp:n_pcstate:ind",
|
||||
"Received SCCP N-PCSTATE.ind"
|
||||
},
|
||||
[CNLINK_CTR_SCCP_RLSD_CN_ORIGIN] = {
|
||||
"sccp:rlsd_cn_origin",
|
||||
"Received unexpected SCCP RSLD originated unilaterally by CN"
|
||||
},
|
||||
|
||||
/* Indicators for CN pool usage */
|
||||
[CNLINK_CTR_CNPOOL_SUBSCR_NEW] = {
|
||||
"cnpool:subscr:new",
|
||||
"Complete Layer 3 requests assigned to this CN link by round-robin (no NRI was assigned yet).",
|
||||
},
|
||||
[CNLINK_CTR_CNPOOL_SUBSCR_REATTACH] = {
|
||||
"cnpool:subscr:reattach",
|
||||
"Complete Layer 3 requests assigned to this CN link by round-robin because the subscriber indicates a"
|
||||
" NULL-NRI (previously assigned by another CN link).",
|
||||
},
|
||||
[CNLINK_CTR_CNPOOL_SUBSCR_KNOWN] = {
|
||||
"cnpool:subscr:known",
|
||||
"Complete Layer 3 requests directed to this CN link because the subscriber indicates an NRI of this CN link.",
|
||||
},
|
||||
[CNLINK_CTR_CNPOOL_SUBSCR_PAGED] = {
|
||||
"cnpool:subscr:paged",
|
||||
"Paging Response directed to this CN link because the subscriber was recently paged by this CN link.",
|
||||
},
|
||||
[CNLINK_CTR_CNPOOL_SUBSCR_ATTACH_LOST] = {
|
||||
"cnpool:subscr:attach_lost",
|
||||
"A subscriber indicates an NRI value matching this CN link, but the CN link is not connected:"
|
||||
" a re-attach to another CN link (if available) was forced, with possible service failure.",
|
||||
},
|
||||
[CNLINK_CTR_CNPOOL_EMERG_FORWARDED] = {
|
||||
"cnpool:emerg:forwarded",
|
||||
"Emergency call requests forwarded to this CN link.",
|
||||
},
|
||||
};
|
||||
|
||||
static const struct rate_ctr_group_desc msc_ctrg_desc = {
|
||||
"msc",
|
||||
"MSC",
|
||||
OSMO_STATS_CLASS_GLOBAL,
|
||||
ARRAY_SIZE(cnlink_ctr_description),
|
||||
cnlink_ctr_description,
|
||||
};
|
||||
|
||||
static const struct rate_ctr_group_desc sgsn_ctrg_desc = {
|
||||
"sgsn",
|
||||
"SGSN",
|
||||
OSMO_STATS_CLASS_GLOBAL,
|
||||
ARRAY_SIZE(cnlink_ctr_description),
|
||||
cnlink_ctr_description,
|
||||
};
|
||||
|
||||
static const struct osmo_stat_item_desc cnlink_stat_desc[] = {
|
||||
[CNLINK_STAT_CONNECTED] = { "connected", "Connected (1) or disconnected (0)", NULL, 60, 0 },
|
||||
};
|
||||
|
||||
const struct osmo_stat_item_group_desc msc_statg_desc = {
|
||||
.group_name_prefix = "msc",
|
||||
.group_description = "MSC",
|
||||
.class_id = OSMO_STATS_CLASS_GLOBAL,
|
||||
.num_items = ARRAY_SIZE(cnlink_stat_desc),
|
||||
.item_desc = cnlink_stat_desc,
|
||||
};
|
||||
|
||||
const struct osmo_stat_item_group_desc sgsn_statg_desc = {
|
||||
.group_name_prefix = "sgsn",
|
||||
.group_description = "SGSN",
|
||||
.class_id = OSMO_STATS_CLASS_GLOBAL,
|
||||
.num_items = ARRAY_SIZE(cnlink_stat_desc),
|
||||
.item_desc = cnlink_stat_desc,
|
||||
};
|
||||
|
||||
struct hnbgw_cnlink *hnbgw_cnlink_alloc(struct hnbgw_cnpool *cnpool, int nr)
|
||||
{
|
||||
struct hnbgw_cnlink *cnlink;
|
||||
const struct rate_ctr_group_desc *ctrg_desc;
|
||||
const struct osmo_stat_item_group_desc *statg_desc;
|
||||
|
||||
OSMO_ASSERT(cnpool);
|
||||
|
||||
switch (cnpool->domain) {
|
||||
case DOMAIN_CS:
|
||||
ctrg_desc = &msc_ctrg_desc;
|
||||
statg_desc = &msc_statg_desc;
|
||||
break;
|
||||
case DOMAIN_PS:
|
||||
ctrg_desc = &sgsn_ctrg_desc;
|
||||
statg_desc = &sgsn_statg_desc;
|
||||
break;
|
||||
default:
|
||||
OSMO_ASSERT(0);
|
||||
}
|
||||
|
||||
cnlink = talloc_zero(cnpool, struct hnbgw_cnlink);
|
||||
OSMO_ASSERT(cnlink);
|
||||
*cnlink = (struct hnbgw_cnlink){
|
||||
.pool = cnpool,
|
||||
.nr = nr,
|
||||
.vty = {
|
||||
/* VTY config defaults for the new cnlink */
|
||||
.nri_ranges = osmo_nri_ranges_alloc(cnlink),
|
||||
},
|
||||
.allow_attach = true,
|
||||
.ctrs = rate_ctr_group_alloc(cnlink, ctrg_desc, nr),
|
||||
.statg = osmo_stat_item_group_alloc(cnlink, statg_desc, nr),
|
||||
};
|
||||
cnlink->name = talloc_asprintf(cnlink, "%s-%d", cnpool->peer_name, nr);
|
||||
INIT_LLIST_HEAD(&cnlink->map_list);
|
||||
INIT_LLIST_HEAD(&cnlink->paging);
|
||||
|
||||
cnlink->fi = osmo_fsm_inst_alloc(&cnlink_fsm, cnlink, cnlink, LOGL_DEBUG, cnlink->name);
|
||||
OSMO_ASSERT(cnlink->fi);
|
||||
|
||||
llist_add_tail(&cnlink->entry, &cnpool->cnlinks);
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "allocated\n");
|
||||
|
||||
cnlink_resend_reset(cnlink);
|
||||
return cnlink;
|
||||
}
|
||||
|
||||
int hnbgw_cnlink_set_name(struct hnbgw_cnlink *cnlink, const char *name)
|
||||
{
|
||||
talloc_free(cnlink->name);
|
||||
cnlink->name = talloc_strdup(cnlink, name);
|
||||
osmo_fsm_inst_update_id_f_sanitize(cnlink->fi, '-', cnlink->name);
|
||||
/* Update rate_ctr/stats to report by name instead of index: */
|
||||
rate_ctr_group_set_name(cnlink->ctrs, cnlink->name);
|
||||
osmo_stat_item_group_set_name(cnlink->statg, cnlink->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hnbgw_cnlink_drop_sccp(struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
struct hnbgw_context_map *map, *map2;
|
||||
struct hnbgw_sccp_user *hsu;
|
||||
|
||||
llist_for_each_entry_safe(map, map2, &cnlink->map_list, hnbgw_cnlink_entry) {
|
||||
map_sccp_dispatch(map, MAP_SCCP_EV_USER_ABORT, NULL);
|
||||
}
|
||||
|
||||
OSMO_ASSERT(cnlink->hnbgw_sccp_user);
|
||||
hsu = cnlink->hnbgw_sccp_user;
|
||||
cnlink->hnbgw_sccp_user = NULL;
|
||||
hnbgw_sccp_user_put(hsu, HSU_USE_CNLINK);
|
||||
}
|
||||
|
||||
void hnbgw_cnlink_term_and_free(struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
if (!cnlink)
|
||||
return;
|
||||
|
||||
if (cnlink->hnbgw_sccp_user)
|
||||
hnbgw_cnlink_drop_sccp(cnlink);
|
||||
|
||||
osmo_fsm_inst_term(cnlink->fi, OSMO_FSM_TERM_REQUEST, NULL);
|
||||
cnlink->fi = NULL;
|
||||
osmo_stat_item_group_free(cnlink->statg);
|
||||
rate_ctr_group_free(cnlink->ctrs);
|
||||
llist_del(&cnlink->entry);
|
||||
talloc_free(cnlink);
|
||||
}
|
||||
|
||||
static int hnbgw_cnlink_tx_sccp_unitdata_req(struct hnbgw_cnlink *cnlink, struct msgb *msg)
|
||||
{
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_SCCP_N_UNITDATA_REQ);
|
||||
return hnbgw_sccp_user_tx_unitdata_req(cnlink->hnbgw_sccp_user,
|
||||
&cnlink->remote_addr,
|
||||
msg);
|
||||
}
|
||||
|
||||
int hnbgw_cnlink_tx_ranap_reset(struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
struct msgb *msg;
|
||||
RANAP_Cause_t cause = {
|
||||
.present = RANAP_Cause_PR_transmissionNetwork,
|
||||
.choice. transmissionNetwork = RANAP_CauseTransmissionNetwork_signalling_transport_resource_failure,
|
||||
};
|
||||
RANAP_GlobalRNC_ID_t grnc_id;
|
||||
RANAP_GlobalRNC_ID_t *use_grnc_id = NULL;
|
||||
uint8_t plmn_buf[3];
|
||||
|
||||
if (!cnlink)
|
||||
return -1;
|
||||
|
||||
/* We need to have chosen an SCCP instance, and the remote SCCP address needs to be set.
|
||||
* Only check the remote_addr, allowing use.remote_addr_name to be NULL: if the user has not set an explicit
|
||||
* remote address book entry, auto-configuration may still have chosen a default remote point-code. */
|
||||
if (!cnlink->hnbgw_sccp_user
|
||||
|| !osmo_sccp_check_addr(&cnlink->remote_addr, OSMO_SCCP_ADDR_T_PC | OSMO_SCCP_ADDR_T_SSN)) {
|
||||
LOG_CNLINK(cnlink, DRANAP, LOGL_DEBUG, "not yet configured, not sending RANAP RESET\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_CNLINK(cnlink, DRANAP, LOGL_DEBUG, "Tx RANAP RESET to %s %s\n",
|
||||
cnlink_is_cs(cnlink) ? "IuCS" : "IuPS",
|
||||
hnbgw_cnlink_sccp_addr_to_str(cnlink, &cnlink->remote_addr));
|
||||
|
||||
if (g_hnbgw->config.plmn.mcc) {
|
||||
osmo_plmn_to_bcd(plmn_buf, &g_hnbgw->config.plmn);
|
||||
grnc_id = (RANAP_GlobalRNC_ID_t){
|
||||
.pLMNidentity = {
|
||||
.buf = plmn_buf,
|
||||
.size = 3,
|
||||
},
|
||||
.rNC_ID = g_hnbgw->config.rnc_id,
|
||||
};
|
||||
use_grnc_id = &grnc_id;
|
||||
} else {
|
||||
/* If no PLMN is configured, omit the Global RNC Id from the RESET message.
|
||||
*
|
||||
* According to 3GPP TS 25.413 8.26.2.2, "The RNC shall include the Global RNC-ID IE in the RESET
|
||||
* message", so it should be considered a mandatory IE when coming from us, the RNC.
|
||||
*
|
||||
* But osmo-hnbgw < v1.5 worked well with osmo-hnbgw.cfg files that have no PLMN configured, and we are
|
||||
* trying to stay backwards compatible for those users. Such a site should still work, but they should
|
||||
* now see these error logs and can adjust the config.
|
||||
*/
|
||||
LOG_CNLINK(cnlink, DRANAP, LOGL_ERROR,
|
||||
"No local PLMN is configured, so outgoing RESET messages omit the mandatory Global RNC-ID"
|
||||
" IE. You should set a 'hnbgw' / 'plmn' in your config file (since v1.5)\n");
|
||||
}
|
||||
|
||||
msg = ranap_new_msg_reset2(cnlink->pool->domain, &cause, use_grnc_id);
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_TX_UDT_RESET);
|
||||
return hnbgw_cnlink_tx_sccp_unitdata_req(cnlink, msg);
|
||||
}
|
||||
|
||||
int hnbgw_cnlink_tx_ranap_reset_ack(struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
struct msgb *msg;
|
||||
struct osmo_sccp_instance *sccp = hnbgw_cnlink_sccp(cnlink);
|
||||
RANAP_GlobalRNC_ID_t grnc_id;
|
||||
RANAP_GlobalRNC_ID_t *use_grnc_id = NULL;
|
||||
uint8_t plmn_buf[3];
|
||||
|
||||
if (!sccp) {
|
||||
LOG_CNLINK(cnlink, DRANAP, LOGL_ERROR, "cannot send RANAP RESET ACK: no CN link\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_CNLINK(cnlink, DRANAP, LOGL_NOTICE, "Tx RANAP RESET ACK %s %s --> %s\n",
|
||||
cnlink_is_cs(cnlink) ? "IuCS" : "IuPS",
|
||||
hnbgw_cnlink_sccp_addr_to_str(cnlink, &cnlink->hnbgw_sccp_user->local_addr),
|
||||
hnbgw_cnlink_sccp_addr_to_str(cnlink, &cnlink->remote_addr));
|
||||
|
||||
if (g_hnbgw->config.plmn.mcc) {
|
||||
osmo_plmn_to_bcd(plmn_buf, &g_hnbgw->config.plmn);
|
||||
grnc_id = (RANAP_GlobalRNC_ID_t){
|
||||
.pLMNidentity = {
|
||||
.buf = plmn_buf,
|
||||
.size = 3,
|
||||
},
|
||||
.rNC_ID = g_hnbgw->config.rnc_id,
|
||||
};
|
||||
use_grnc_id = &grnc_id;
|
||||
} else {
|
||||
/* If no PLMN is configured, omit the Global RNC Id from the RESET ACK message.
|
||||
*
|
||||
* According to 3GPP TS 25.413 8.26.2.1, "The RNC shall include the Global RNC-ID IE in the RESET
|
||||
* ACKNOWLEDGE message", so it should be considered a mandatory IE when coming from us, the RNC.
|
||||
*
|
||||
* But osmo-hnbgw < v1.5 worked well with osmo-hnbgw.cfg files that have no PLMN configured, and we are
|
||||
* trying to stay backwards compatible for those users. Such a site should still work, but they should
|
||||
* now see these error logs and can adjust the config.
|
||||
*/
|
||||
LOG_CNLINK(cnlink, DRANAP, LOGL_ERROR,
|
||||
"No local PLMN is configured, so outgoing RESET ACKNOWLEDGE messages omit the mandatory"
|
||||
" Global RNC-ID IE. You should set a 'hnbgw' / 'plmn' in your config file (since v1.5)\n");
|
||||
}
|
||||
|
||||
msg = ranap_new_msg_reset_ack(cnlink->pool->domain, use_grnc_id);
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_TX_UDT_RESET_ACK);
|
||||
return hnbgw_cnlink_tx_sccp_unitdata_req(cnlink, msg);
|
||||
}
|
||||
|
||||
/* Return address found in sccp address-book, and fill in missing fields in the
|
||||
* entry with default values. */
|
||||
static struct osmo_ss7_instance *sccp_addr_by_name_filled(struct osmo_sccp_addr *dest, const char *addr_name, uint32_t default_pc)
|
||||
{
|
||||
struct osmo_ss7_instance *s7i;
|
||||
s7i = osmo_sccp_addr_by_name(dest, addr_name);
|
||||
if (!s7i)
|
||||
return NULL;
|
||||
|
||||
/* Address exists in address-book but may not be filled entirely: */
|
||||
if (!dest->presence)
|
||||
osmo_sccp_make_addr_pc_ssn(dest, default_pc, OSMO_SCCP_SSN_RANAP);
|
||||
else if (!(dest->presence & OSMO_SCCP_ADDR_T_SSN))
|
||||
osmo_sccp_addr_set_ssn(dest, OSMO_SCCP_SSN_RANAP);
|
||||
|
||||
return s7i;
|
||||
}
|
||||
|
||||
char *hnbgw_cnlink_sccp_addr_to_str(struct hnbgw_cnlink *cnlink, const struct osmo_sccp_addr *addr)
|
||||
{
|
||||
struct osmo_sccp_instance *sccp = hnbgw_cnlink_sccp(cnlink);
|
||||
if (!sccp)
|
||||
return osmo_sccp_addr_dump(addr);
|
||||
return osmo_sccp_inst_addr_to_str_c(OTC_SELECT, sccp, addr);
|
||||
}
|
||||
|
||||
static void hnbgw_cnlink_cfg_copy(struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
struct osmo_nri_range *r;
|
||||
|
||||
osmo_talloc_replace_string(cnlink, &cnlink->use.remote_addr_name, cnlink->vty.remote_addr_name);
|
||||
|
||||
osmo_nri_ranges_free(cnlink->use.nri_ranges);
|
||||
cnlink->use.nri_ranges = osmo_nri_ranges_alloc(cnlink);
|
||||
llist_for_each_entry(r, &cnlink->vty.nri_ranges->entries, entry)
|
||||
osmo_nri_ranges_add(cnlink->use.nri_ranges, r);
|
||||
}
|
||||
|
||||
static bool hnbgw_cnlink_sccp_cfg_changed(struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
if (cnlink->vty.remote_addr_name && cnlink->use.remote_addr_name) {
|
||||
struct osmo_ss7_instance *s7i;
|
||||
struct osmo_sccp_addr remote_addr = {};
|
||||
|
||||
/* Instead of comparing whether the address book entry names are different, actually resolve the
|
||||
* resulting SCCP address, and only restart the cnlink if the resulting address changed. */
|
||||
s7i = sccp_addr_by_name_filled(&remote_addr, cnlink->vty.remote_addr_name,
|
||||
cnlink->pool->default_remote_pc);
|
||||
if (!s7i)
|
||||
return true;
|
||||
if (osmo_sccp_addr_cmp(&remote_addr, &cnlink->remote_addr,
|
||||
OSMO_SCCP_ADDR_T_PC | OSMO_SCCP_ADDR_T_SSN))
|
||||
changed = true;
|
||||
} else if (cnlink->vty.remote_addr_name != cnlink->use.remote_addr_name) {
|
||||
/* One of them is NULL, the other is not. */
|
||||
changed = true;
|
||||
}
|
||||
|
||||
/* if more cnlink configuration is added in the future, it needs to be compared here. */
|
||||
return changed;
|
||||
}
|
||||
|
||||
static void hnbgw_cnlink_log_self(struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
struct osmo_ss7_instance *ss7 = cnlink->hnbgw_sccp_user->ss7;
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, "using: cs7-%u %s <-> %s %s %s\n",
|
||||
osmo_ss7_instance_get_id(ss7),
|
||||
/* printing the entire SCCP address is quite long, rather just print the point-code */
|
||||
osmo_ss7_pointcode_print(ss7, cnlink->hnbgw_sccp_user->local_addr.pc),
|
||||
osmo_ss7_pointcode_print2(ss7, cnlink->remote_addr.pc),
|
||||
cnlink->name, cnlink->use.remote_addr_name ? : "(default remote point-code)");
|
||||
}
|
||||
|
||||
/* If not present yet, set up all of osmo_ss7_instance, osmo_sccp_instance and hnbgw_sccp_user for the given cnlink.
|
||||
* The cs7 instance nr to use is determined by cnlink->remote_addr_name, or cs7 instance 0 if that is not present.
|
||||
* Set cnlink->hnbgw_sccp_user to the new SCCP instance. Return 0 on success, negative on error. */
|
||||
int hnbgw_cnlink_start_or_restart(struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
struct osmo_ss7_instance *s7i = NULL;
|
||||
struct hnbgw_sccp_user *hsu;
|
||||
uint32_t ss7_id;
|
||||
int rc;
|
||||
|
||||
/* If a hnbgw_sccp_user has already been set up, use that. */
|
||||
if (cnlink->hnbgw_sccp_user) {
|
||||
if (!hnbgw_cnlink_sccp_cfg_changed(cnlink)) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "SCCP instance already set up, using %s\n",
|
||||
cnlink->hnbgw_sccp_user->name);
|
||||
return 0;
|
||||
}
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, "config changed, restarting SCCP\n");
|
||||
hnbgw_cnlink_drop_sccp(cnlink);
|
||||
} else {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "no SCCP instance selected yet\n");
|
||||
}
|
||||
|
||||
/* Copy the current configuration: cnlink->use = cnlink->vty */
|
||||
hnbgw_cnlink_cfg_copy(cnlink);
|
||||
|
||||
if (!cnlink->use.remote_addr_name) {
|
||||
/* No remote address configured in VTY, set a default one and
|
||||
* make sure it becomes registered in the sccp address-book: */
|
||||
cnlink->use.remote_addr_name = talloc_strdup(cnlink, cnlink->pool->default_addr_name);
|
||||
s7i = osmo_sccp_addr_by_name(&cnlink->remote_addr, cnlink->use.remote_addr_name);
|
||||
if (!s7i) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, "To auto-configure cnlink, creating cs7 instance 0 implicitly\n");
|
||||
s7i = osmo_ss7_instance_find_or_create(g_hnbgw, 0);
|
||||
OSMO_ASSERT(s7i);
|
||||
osmo_sccp_make_addr_pc_ssn(&cnlink->remote_addr,
|
||||
cnlink->pool->default_remote_pc,
|
||||
OSMO_SCCP_SSN_RANAP);
|
||||
rc = osmo_sccp_addr_create(s7i, cnlink->use.remote_addr_name, &cnlink->remote_addr);
|
||||
if (rc < 0) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Failed adding address '%s' to sccp address-book!\n",
|
||||
cnlink->use.remote_addr_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
/* Update VTY config to show & point to the address dynamically added to address-book: */
|
||||
cnlink->vty.remote_addr_name = talloc_strdup(cnlink, cnlink->use.remote_addr_name);
|
||||
} else {
|
||||
s7i = sccp_addr_by_name_filled(&cnlink->remote_addr, cnlink->use.remote_addr_name, cnlink->pool->default_remote_pc);
|
||||
if (!s7i) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "cannot initialize SCCP: there is no SCCP address named '%s'\n",
|
||||
cnlink->use.remote_addr_name);
|
||||
return -ENOENT;
|
||||
}
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "remote-addr is '%s', using cs7 instance %u\n",
|
||||
cnlink->use.remote_addr_name, osmo_ss7_instance_get_id(s7i));
|
||||
/* Address exists in address-book but may not be filled entirely: */
|
||||
rc = osmo_sccp_addr_update(s7i, cnlink->use.remote_addr_name, &cnlink->remote_addr);
|
||||
if (rc < 0) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Failed updating address '%s' in sccp address-book!\n",
|
||||
cnlink->use.remote_addr_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
ss7_id = osmo_ss7_instance_get_id(s7i);
|
||||
|
||||
/* Has another cnlink already set up an SCCP instance for this s7i? */
|
||||
llist_for_each_entry(hsu, &g_hnbgw->sccp.users, entry) {
|
||||
if (hsu->ss7 != s7i)
|
||||
continue;
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "using existing SCCP instance %s on cs7 instance %u\n",
|
||||
hsu->name, ss7_id);
|
||||
cnlink->hnbgw_sccp_user = hsu;
|
||||
hnbgw_sccp_user_get(cnlink->hnbgw_sccp_user, HSU_USE_CNLINK);
|
||||
hnbgw_cnlink_log_self(cnlink);
|
||||
return 0;
|
||||
}
|
||||
/* else cnlink->hnbgw_sccp_user stays NULL and is set up below. */
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "cs7 instance %u has no configured SCCP instance yet\n", ss7_id);
|
||||
|
||||
/* No SCCP instance yet for this ss7. Create it. If no address name is given that resolves to a
|
||||
* particular cs7 instance above, use 'cs7 instance 0'. */
|
||||
cnlink->hnbgw_sccp_user = hnbgw_sccp_user_alloc(ss7_id);
|
||||
hnbgw_sccp_user_get(cnlink->hnbgw_sccp_user, HSU_USE_CNLINK);
|
||||
hnbgw_cnlink_log_self(cnlink);
|
||||
return 0;
|
||||
}
|
||||
220
src/osmo-hnbgw/cnlink_fsm.c
Normal file
220
src/osmo-hnbgw/cnlink_fsm.c
Normal file
@@ -0,0 +1,220 @@
|
||||
/* (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Neels Hofmeyr
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <osmocom/core/fsm.h>
|
||||
#include <osmocom/core/tdef.h>
|
||||
|
||||
#include <osmocom/gsm/gsm23236.h>
|
||||
|
||||
#include <osmocom/sigtran/sccp_helpers.h>
|
||||
|
||||
#include <asn1c/asn1helpers.h>
|
||||
#include <osmocom/ranap/ranap_ies_defs.h>
|
||||
#include <osmocom/ranap/ranap_msg_factory.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
#include <osmocom/hnbgw/tdefs.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
|
||||
enum cnlink_fsm_state {
|
||||
CNLINK_ST_DISC,
|
||||
CNLINK_ST_CONN,
|
||||
};
|
||||
|
||||
enum cnlink_fsm_event {
|
||||
CNLINK_EV_RX_RESET,
|
||||
CNLINK_EV_RX_RESET_ACK,
|
||||
};
|
||||
|
||||
static const struct value_string cnlink_fsm_event_names[] = {
|
||||
OSMO_VALUE_STRING(CNLINK_EV_RX_RESET),
|
||||
OSMO_VALUE_STRING(CNLINK_EV_RX_RESET_ACK),
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct osmo_tdef_state_timeout cnlink_timeouts[32] = {
|
||||
[CNLINK_ST_DISC] = { .T = 4 },
|
||||
};
|
||||
|
||||
#define cnlink_fsm_state_chg(FI, STATE) \
|
||||
osmo_tdef_fsm_inst_state_chg(FI, STATE, \
|
||||
cnlink_timeouts, \
|
||||
hnbgw_T_defs, \
|
||||
-1)
|
||||
|
||||
static void link_up(struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
LOGPFSML(cnlink->fi, LOGL_NOTICE, "link up\n");
|
||||
CNLINK_STAT_SET(cnlink, CNLINK_STAT_CONNECTED, 1);
|
||||
}
|
||||
|
||||
static void link_lost(struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
struct hnbgw_context_map *map, *map2;
|
||||
|
||||
LOGPFSML(cnlink->fi, LOGL_NOTICE, "link lost\n");
|
||||
CNLINK_STAT_SET(cnlink, CNLINK_STAT_CONNECTED, 0);
|
||||
|
||||
llist_for_each_entry_safe(map, map2, &cnlink->map_list, hnbgw_cnlink_entry)
|
||||
context_map_cnlink_lost(map);
|
||||
}
|
||||
|
||||
|
||||
static void cnlink_disc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
|
||||
if (prev_state == CNLINK_ST_CONN)
|
||||
link_lost(cnlink);
|
||||
}
|
||||
|
||||
static void cnlink_disc_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
|
||||
switch (event) {
|
||||
|
||||
case CNLINK_EV_RX_RESET:
|
||||
hnbgw_cnlink_tx_ranap_reset_ack(cnlink);
|
||||
cnlink_fsm_state_chg(fi, CNLINK_ST_CONN);
|
||||
break;
|
||||
|
||||
case CNLINK_EV_RX_RESET_ACK:
|
||||
cnlink_fsm_state_chg(fi, CNLINK_ST_CONN);
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void cnlink_conn_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
|
||||
if (prev_state != CNLINK_ST_CONN)
|
||||
link_up(cnlink);
|
||||
}
|
||||
|
||||
static void cnlink_conn_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case CNLINK_EV_RX_RESET:
|
||||
/* We were connected, but the remote side has restarted. */
|
||||
link_lost(cnlink);
|
||||
hnbgw_cnlink_tx_ranap_reset_ack(cnlink);
|
||||
link_up(cnlink);
|
||||
break;
|
||||
|
||||
case CNLINK_EV_RX_RESET_ACK:
|
||||
LOGPFSML(fi, LOGL_INFO, "Link is already up, ignoring RESET ACK\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static int cnlink_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
struct hnbgw_cnlink *cnlink = (struct hnbgw_cnlink *)fi->priv;
|
||||
|
||||
hnbgw_cnlink_tx_ranap_reset(cnlink);
|
||||
|
||||
/* (re-)enter disconnect state to resend RESET after timeout. */
|
||||
cnlink_fsm_state_chg(fi, CNLINK_ST_DISC);
|
||||
|
||||
/* Return 0 to not terminate the fsm */
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define S(x) (1 << (x))
|
||||
|
||||
static struct osmo_fsm_state cnlink_fsm_states[] = {
|
||||
[CNLINK_ST_DISC] = {
|
||||
.name = "DISCONNECTED",
|
||||
.in_event_mask = 0
|
||||
| S(CNLINK_EV_RX_RESET)
|
||||
| S(CNLINK_EV_RX_RESET_ACK)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(CNLINK_ST_DISC)
|
||||
| S(CNLINK_ST_CONN)
|
||||
,
|
||||
.onenter = cnlink_disc_onenter,
|
||||
.action = cnlink_disc_action,
|
||||
},
|
||||
[CNLINK_ST_CONN] = {
|
||||
.name = "CONNECTED",
|
||||
.in_event_mask = 0
|
||||
| S(CNLINK_EV_RX_RESET)
|
||||
| S(CNLINK_EV_RX_RESET_ACK)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(CNLINK_ST_DISC)
|
||||
| S(CNLINK_ST_CONN)
|
||||
,
|
||||
.onenter = cnlink_conn_onenter,
|
||||
.action = cnlink_conn_action,
|
||||
},
|
||||
};
|
||||
|
||||
struct osmo_fsm cnlink_fsm = {
|
||||
.name = "cnlink",
|
||||
.states = cnlink_fsm_states,
|
||||
.num_states = ARRAY_SIZE(cnlink_fsm_states),
|
||||
.log_subsys = DRANAP,
|
||||
.timer_cb = cnlink_fsm_timer_cb,
|
||||
.event_names = cnlink_fsm_event_names,
|
||||
};
|
||||
|
||||
bool cnlink_is_conn_ready(const struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
return cnlink->fi->state == CNLINK_ST_CONN;
|
||||
}
|
||||
|
||||
void cnlink_resend_reset(struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
/* Immediately (1ms) kick off reset sending mechanism */
|
||||
osmo_fsm_inst_state_chg_ms(cnlink->fi, CNLINK_ST_DISC, 1, 0);
|
||||
}
|
||||
|
||||
void cnlink_set_disconnected(struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
/* Go to disconnected state, with the normal RESET timeout to re-send RESET. */
|
||||
cnlink_fsm_state_chg(cnlink->fi, CNLINK_ST_DISC);
|
||||
}
|
||||
|
||||
static __attribute__((constructor)) void cnlink_fsm_init(void)
|
||||
{
|
||||
OSMO_ASSERT(osmo_fsm_register(&cnlink_fsm) == 0);
|
||||
}
|
||||
|
||||
void cnlink_rx_reset_cmd(struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
osmo_fsm_inst_dispatch(cnlink->fi, CNLINK_EV_RX_RESET, NULL);
|
||||
}
|
||||
|
||||
void cnlink_rx_reset_ack(struct hnbgw_cnlink *cnlink)
|
||||
{
|
||||
osmo_fsm_inst_dispatch(cnlink->fi, CNLINK_EV_RX_RESET_ACK, NULL);
|
||||
}
|
||||
|
||||
230
src/osmo-hnbgw/cnlink_paging.c
Normal file
230
src/osmo-hnbgw/cnlink_paging.c
Normal file
@@ -0,0 +1,230 @@
|
||||
/* RANAP Paging of HNB-GW */
|
||||
/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
|
||||
* (C) 2025 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <asn1c/asn1helpers.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/gsm/gsm48.h>
|
||||
|
||||
#include <osmocom/ranap/ranap_ies_defs.h>
|
||||
#include <osmocom/ranap/iu_helpers.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/tdefs.h>
|
||||
|
||||
/***************
|
||||
* This module manages the list of "struct cnlink_paging" items in (struct
|
||||
* hnbgw_cnlink *)->paging.
|
||||
* Every time a new RANAP Paging Cmd arrives from some cnlink,
|
||||
* cnlink_paging_add_ranap() is called to potentially store the paging command
|
||||
* for a while.
|
||||
* When a paging response is received from HNB, cnlink_find_by_paging_mi() is
|
||||
* called to obtain the cnlink it should be routed back to.
|
||||
*/
|
||||
|
||||
struct cnlink_paging {
|
||||
struct llist_head entry;
|
||||
|
||||
struct osmo_mobile_identity mi;
|
||||
struct osmo_mobile_identity mi2;
|
||||
time_t timestamp;
|
||||
};
|
||||
|
||||
static int cnlink_paging_destructor(struct cnlink_paging *p)
|
||||
{
|
||||
llist_del(&p->entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Return current timestamp in *timestamp, and the oldest still valid timestamp according to T3113 timeout. */
|
||||
static const char *cnlink_paging_gettime(time_t *timestamp_p, time_t *timeout_p)
|
||||
{
|
||||
struct timespec now;
|
||||
time_t timestamp;
|
||||
|
||||
/* get timestamp */
|
||||
if (osmo_clock_gettime(CLOCK_MONOTONIC, &now) != 0)
|
||||
return "cannot get timestamp";
|
||||
timestamp = now.tv_sec;
|
||||
|
||||
if (timestamp_p)
|
||||
*timestamp_p = timestamp;
|
||||
if (timeout_p)
|
||||
*timeout_p = timestamp - osmo_tdef_get(hnbgw_T_defs, 3113, OSMO_TDEF_S, 15);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *cnlink_paging_add(struct hnbgw_cnlink *cnlink, const struct osmo_mobile_identity *mi,
|
||||
const struct osmo_mobile_identity *mi2)
|
||||
{
|
||||
struct cnlink_paging *p, *p2;
|
||||
time_t timestamp;
|
||||
time_t timeout;
|
||||
const char *errmsg;
|
||||
|
||||
errmsg = cnlink_paging_gettime(×tamp, &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;
|
||||
}
|
||||
|
||||
@@ -19,163 +19,210 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
/* an expired mapping is destroyed after 1..2 * EXPIRY_TIMER_SECS */
|
||||
#define EXPIRY_TIMER_SECS 23
|
||||
|
||||
#include <osmocom/core/timer.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/netif/stream.h>
|
||||
|
||||
#include <osmocom/sigtran/sccp_helpers.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnb.h>
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/mgw_fsm.h>
|
||||
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
|
||||
|
||||
const struct value_string hnbgw_context_map_state_names[] = {
|
||||
{MAP_S_NULL , "not-initialized"},
|
||||
{MAP_S_ACTIVE , "active"},
|
||||
{MAP_S_RESERVED1, "inactive-reserved"},
|
||||
{MAP_S_RESERVED2, "inactive-discard"},
|
||||
{0, NULL}
|
||||
{ MAP_S_CONNECTING, "connecting" },
|
||||
{ MAP_S_ACTIVE, "active" },
|
||||
{ MAP_S_DISCONNECTING, "disconnecting" },
|
||||
{}
|
||||
};
|
||||
|
||||
/* is a given SCCP USER SAP Connection ID in use for a given CN link? */
|
||||
static int cn_id_in_use(struct hnbgw_cnlink *cn, uint32_t id)
|
||||
/* Combine the RUA and SCCP states, for VTY reporting only. */
|
||||
enum hnbgw_context_map_state context_map_get_state(struct hnbgw_context_map *map)
|
||||
{
|
||||
struct hnbgw_context_map *map;
|
||||
|
||||
llist_for_each_entry(map, &cn->map_list, cn_list) {
|
||||
if (map->scu_conn_id == id)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* try to allocate a new SCCP User SAP Connection ID */
|
||||
static int alloc_cn_conn_id(struct hnbgw_cnlink *cn, uint32_t *id_out)
|
||||
{
|
||||
uint32_t i;
|
||||
uint32_t id;
|
||||
|
||||
for (i = 0; i < 0xffffffff; i++) {
|
||||
id = cn->next_conn_id++;
|
||||
if (!cn_id_in_use(cn, id)) {
|
||||
*id_out = id;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
enum hnbgw_context_map_state rua = map_rua_get_state(map);
|
||||
enum hnbgw_context_map_state sccp = map_sccp_get_state(map);
|
||||
if (rua == MAP_S_ACTIVE && sccp == MAP_S_ACTIVE)
|
||||
return MAP_S_ACTIVE;
|
||||
if (rua == MAP_S_DISCONNECTING || sccp == MAP_S_DISCONNECTING)
|
||||
return MAP_S_DISCONNECTING;
|
||||
return MAP_S_CONNECTING;
|
||||
}
|
||||
|
||||
/* Map from a HNB + ContextID to the SCCP-side Connection ID */
|
||||
struct hnbgw_context_map *
|
||||
context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id,
|
||||
bool is_ps,
|
||||
struct hnbgw_cnlink *cn_if_new)
|
||||
struct hnbgw_context_map *context_map_find_by_rua_ctx_id(struct hnb_context *hnb, uint32_t rua_ctx_id, bool is_ps)
|
||||
{
|
||||
struct hnbgw_context_map *map;
|
||||
uint32_t new_scu_conn_id;
|
||||
|
||||
llist_for_each_entry(map, &hnb->map_list, hnb_list) {
|
||||
if (map->state != MAP_S_ACTIVE)
|
||||
if (map->is_ps != is_ps)
|
||||
continue;
|
||||
if (map->cn_link != cn_if_new) {
|
||||
|
||||
/* Matching on RUA context id -- only match for RUA context that has not been disconnected yet. If an
|
||||
* inactive context map for a rua_ctx_id is still around, we may have two entries for the same
|
||||
* rua_ctx_id around at the same time. That should only stay until its SCCP side is done releasing. */
|
||||
if (!map_rua_is_active(map))
|
||||
continue;
|
||||
}
|
||||
if (map->rua_ctx_id == rua_ctx_id
|
||||
&& map->is_ps == is_ps) {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
if (alloc_cn_conn_id(cn_if_new, &new_scu_conn_id) < 0) {
|
||||
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unable to allocate CN connection ID\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOGHNB(hnb, DMAIN, LOGL_INFO, "Creating new Mapping RUA CTX %p/%u <-> SCU Conn ID %p/%u\n",
|
||||
hnb, rua_ctx_id, cn_if_new, new_scu_conn_id);
|
||||
|
||||
/* alloate a new map entry */
|
||||
map = talloc_zero(hnb, struct hnbgw_context_map);
|
||||
map->state = MAP_S_NULL;
|
||||
map->cn_link = cn_if_new;
|
||||
map->hnb_ctx = hnb;
|
||||
map->rua_ctx_id = rua_ctx_id;
|
||||
map->is_ps = is_ps;
|
||||
map->scu_conn_id = new_scu_conn_id;
|
||||
|
||||
/* put it into both lists */
|
||||
llist_add_tail(&map->hnb_list, &hnb->map_list);
|
||||
llist_add_tail(&map->cn_list, &cn_if_new->map_list);
|
||||
map->state = MAP_S_ACTIVE;
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/* Map from a CN + Connection ID to HNB + Context ID */
|
||||
struct hnbgw_context_map *
|
||||
context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id)
|
||||
{
|
||||
struct hnbgw_context_map *map;
|
||||
|
||||
llist_for_each_entry(map, &cn->map_list, cn_list) {
|
||||
if (map->state != MAP_S_ACTIVE)
|
||||
if (map->rua_ctx_id != rua_ctx_id)
|
||||
continue;
|
||||
if (map->scu_conn_id == scu_conn_id) {
|
||||
return map;
|
||||
}
|
||||
|
||||
/* Already exists */
|
||||
return map;
|
||||
}
|
||||
/* we don't allocate new mappings in the CN->HNB
|
||||
* direction, as the RUA=SCCP=SUA connections are always
|
||||
* established from HNB towards CN. */
|
||||
LOGP(DMAIN, LOGL_NOTICE, "Unable to resolve map for CN " "connection ID %p/%u\n", cn, scu_conn_id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void context_map_deactivate(struct hnbgw_context_map *map)
|
||||
struct hnbgw_context_map *context_map_alloc(struct hnb_context *hnb, uint32_t rua_ctx_id, bool is_ps)
|
||||
{
|
||||
/* set the state to reserved. We still show up in the list and
|
||||
* avoid re-allocation of the context-id until we are cleaned up
|
||||
* by the context_map garbage collector timer */
|
||||
struct hnbgw_context_map *map;
|
||||
|
||||
if (map->state != MAP_S_RESERVED2)
|
||||
map->state = MAP_S_RESERVED1;
|
||||
/* allocate a new map entry now, so we have logging context */
|
||||
map = talloc_zero(hnb, struct hnbgw_context_map);
|
||||
map->hnb_ctx = hnb;
|
||||
map->rua_ctx_id = rua_ctx_id;
|
||||
map->is_ps = is_ps;
|
||||
INIT_LLIST_HEAD(&map->ps_rab_ass_list);
|
||||
INIT_LLIST_HEAD(&map->ps_rab_list);
|
||||
|
||||
map_rua_fsm_alloc(map);
|
||||
|
||||
llist_add_tail(&map->hnb_list, &hnb->map_list);
|
||||
|
||||
LOG_MAP(map, DRUA, LOGL_DEBUG, "New RUA CTX\n");
|
||||
return map;
|
||||
}
|
||||
|
||||
static struct osmo_timer_list context_map_tmr;
|
||||
|
||||
static void context_map_tmr_cb(void *data)
|
||||
int context_map_set_cnlink(struct hnbgw_context_map *map, struct hnbgw_cnlink *cnlink_selected)
|
||||
{
|
||||
struct hnb_gw *gw = data;
|
||||
struct hnbgw_cnlink *cn = gw->sccp.cnlink;
|
||||
struct hnbgw_context_map *map, *next_map;
|
||||
int new_scu_conn_id;
|
||||
struct hnbgw_sccp_user *hsu;
|
||||
|
||||
DEBUGP(DMAIN, "Running context mapper garbage collection\n");
|
||||
llist_for_each_entry_safe(map, next_map, &cn->map_list, cn_list) {
|
||||
switch (map->state) {
|
||||
case MAP_S_RESERVED1:
|
||||
/* first time we see this reserved
|
||||
* entry: mark it for stage 2 */
|
||||
map->state = MAP_S_RESERVED2;
|
||||
break;
|
||||
case MAP_S_RESERVED2:
|
||||
/* second time we see this reserved
|
||||
* entry: remove it */
|
||||
map->state = MAP_S_NULL;
|
||||
llist_del(&map->cn_list);
|
||||
llist_del(&map->hnb_list);
|
||||
talloc_free(map);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/* Allocate new SCCP conn id on the SCCP instance the cnlink is on. */
|
||||
hsu = cnlink_selected->hnbgw_sccp_user;
|
||||
if (!hsu) {
|
||||
LOG_MAP(map, DCN, LOGL_ERROR, "Cannot map RUA context to SCCP: No SCCP instance for CN link %s\n",
|
||||
cnlink_selected->name);
|
||||
return -EIO;
|
||||
}
|
||||
/* re-schedule this timer */
|
||||
osmo_timer_schedule(&context_map_tmr, EXPIRY_TIMER_SECS, 0);
|
||||
}
|
||||
|
||||
int context_map_init(struct hnb_gw *gw)
|
||||
{
|
||||
context_map_tmr.cb = context_map_tmr_cb;
|
||||
context_map_tmr.data = gw;
|
||||
osmo_timer_schedule(&context_map_tmr, EXPIRY_TIMER_SECS, 0);
|
||||
new_scu_conn_id = osmo_sccp_instance_next_conn_id(osmo_ss7_get_sccp(hsu->ss7));
|
||||
if (new_scu_conn_id < 0) {
|
||||
LOG_MAP(map, DCN, LOGL_ERROR, "Unable to allocate SCCP conn ID on %s\n", hsu->name);
|
||||
return new_scu_conn_id;
|
||||
}
|
||||
|
||||
map->cnlink = cnlink_selected;
|
||||
map->scu_conn_id = new_scu_conn_id;
|
||||
map_sccp_fsm_alloc(map);
|
||||
|
||||
llist_add_tail(&map->hnbgw_cnlink_entry, &cnlink_selected->map_list);
|
||||
|
||||
hash_add(hsu->hnbgw_context_map_by_conn_id, &map->hnbgw_sccp_user_entry, new_scu_conn_id);
|
||||
|
||||
LOGP(DRUA, LOGL_INFO, "New conn: %s '%s' RUA-%u <-> SCCP-%u %s%s%s %s l=%s<->r=%s\n",
|
||||
osmo_sock_get_name2_c(OTC_SELECT, osmo_stream_srv_get_ofd(map->hnb_ctx->conn)->fd),
|
||||
hnb_context_name(map->hnb_ctx), map->rua_ctx_id,
|
||||
new_scu_conn_id,
|
||||
cnlink_selected->name,
|
||||
cnlink_selected->use.remote_addr_name ? " " : "",
|
||||
cnlink_selected->use.remote_addr_name ? : "",
|
||||
hsu->name,
|
||||
/* printing the entire SCCP address is quite long, rather just print the point-code */
|
||||
osmo_ss7_pointcode_print(hsu->ss7, hsu->local_addr.pc),
|
||||
osmo_ss7_pointcode_print2(hsu->ss7, cnlink_selected->remote_addr.pc)
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _map_rua_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg,
|
||||
const char *file, int line)
|
||||
{
|
||||
OSMO_ASSERT(map);
|
||||
if (!map->rua_fi) {
|
||||
LOG_MAP(map, DRUA, LOGL_ERROR, "not ready to receive RUA events\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
return _osmo_fsm_inst_dispatch(map->rua_fi, event, ranap_msg, file, line);
|
||||
}
|
||||
|
||||
int _map_sccp_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg,
|
||||
const char *file, int line)
|
||||
{
|
||||
OSMO_ASSERT(map);
|
||||
if (!map->sccp_fi) {
|
||||
LOG_MAP(map, DRUA, LOGL_ERROR, "not ready to receive SCCP events\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
return _osmo_fsm_inst_dispatch(map->sccp_fi, event, ranap_msg, file, line);
|
||||
}
|
||||
|
||||
unsigned int msg_has_l2_data(const struct msgb *msg)
|
||||
{
|
||||
return msg && msgb_l2(msg) ? msgb_l2len(msg) : 0;
|
||||
}
|
||||
|
||||
void context_map_hnb_released(struct hnbgw_context_map *map)
|
||||
{
|
||||
/* When a HNB disconnects from RUA, the hnb_context will be freed. This hnbgw_context_map was allocated as a
|
||||
* child of the hnb_context and would also be deallocated along with the hnb_context. However, the SCCP side for
|
||||
* this hnbgw_context_map may still be waiting for a graceful release (SCCP RLC). Move this hnbgw_context_map to
|
||||
* the global hnb_gw talloc ctx, so it can stay around for graceful release / for SCCP timeout.
|
||||
*
|
||||
* We could also always allocate hnbgw_context_map under hnb_gw, but it is nice to see which hnb_context owns
|
||||
* which hnbgw_context_map in a talloc report.
|
||||
*/
|
||||
talloc_steal(g_hnbgw, map);
|
||||
|
||||
/* Tell RUA that the HNB is gone. SCCP release will follow via FSM events. */
|
||||
map_rua_dispatch(map, MAP_RUA_EV_HNB_LINK_LOST, NULL);
|
||||
}
|
||||
|
||||
void context_map_cnlink_lost(struct hnbgw_context_map *map)
|
||||
{
|
||||
map_sccp_dispatch(map, MAP_SCCP_EV_CN_LINK_LOST, NULL);
|
||||
}
|
||||
|
||||
void context_map_free(struct hnbgw_context_map *map)
|
||||
{
|
||||
/* guard against FSM termination infinitely looping back here */
|
||||
if (map->deallocating) {
|
||||
LOG_MAP(map, DMAIN, LOGL_DEBUG, "context_map_free(): already deallocating\n");
|
||||
return;
|
||||
}
|
||||
map->deallocating = true;
|
||||
|
||||
if (map->rua_fi)
|
||||
osmo_fsm_inst_term(map->rua_fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
OSMO_ASSERT(map->rua_fi == NULL);
|
||||
|
||||
if (map->sccp_fi)
|
||||
osmo_fsm_inst_term(map->sccp_fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
OSMO_ASSERT(map->sccp_fi == NULL);
|
||||
|
||||
if (map->mgw_fi) {
|
||||
mgw_fsm_release(map);
|
||||
OSMO_ASSERT(map->mgw_fi == NULL);
|
||||
}
|
||||
|
||||
#if ENABLE_PFCP
|
||||
hnbgw_gtpmap_release(map);
|
||||
#endif
|
||||
|
||||
if (map->cnlink) {
|
||||
llist_del(&map->hnbgw_cnlink_entry);
|
||||
hash_del(&map->hnbgw_sccp_user_entry);
|
||||
}
|
||||
if (map->hnb_ctx)
|
||||
llist_del(&map->hnb_list);
|
||||
|
||||
LOG_MAP(map, DMAIN, LOGL_INFO, "Deallocating\n");
|
||||
talloc_free(map);
|
||||
}
|
||||
|
||||
384
src/osmo-hnbgw/context_map_rua.c
Normal file
384
src/osmo-hnbgw/context_map_rua.c
Normal file
@@ -0,0 +1,384 @@
|
||||
/* RUA side FSM of hnbgw_context_map */
|
||||
/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0+
|
||||
*
|
||||
* Author: Neels Hofmeyr
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "config.h"
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnb.h>
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/tdefs.h>
|
||||
#include <osmocom/hnbgw/hnbgw_rua.h>
|
||||
#include <osmocom/hnbgw/hnbgw_ranap.h>
|
||||
|
||||
enum map_rua_fsm_state {
|
||||
MAP_RUA_ST_INIT,
|
||||
MAP_RUA_ST_CONNECTED,
|
||||
MAP_RUA_ST_DISCONNECTED,
|
||||
MAP_RUA_ST_DISRUPTED,
|
||||
};
|
||||
|
||||
static const struct value_string map_rua_fsm_event_names[] = {
|
||||
OSMO_VALUE_STRING(MAP_RUA_EV_RX_CONNECT),
|
||||
OSMO_VALUE_STRING(MAP_RUA_EV_RX_DIRECT_TRANSFER),
|
||||
OSMO_VALUE_STRING(MAP_RUA_EV_RX_DISCONNECT),
|
||||
OSMO_VALUE_STRING(MAP_RUA_EV_TX_DIRECT_TRANSFER),
|
||||
OSMO_VALUE_STRING(MAP_RUA_EV_CN_DISC),
|
||||
OSMO_VALUE_STRING(MAP_RUA_EV_HNB_LINK_LOST),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct osmo_fsm map_rua_fsm;
|
||||
|
||||
static const struct osmo_tdef_state_timeout map_rua_fsm_timeouts[32] = {
|
||||
[MAP_RUA_ST_INIT] = { .T = -31 },
|
||||
[MAP_RUA_ST_DISCONNECTED] = { .T = -31 },
|
||||
[MAP_RUA_ST_DISRUPTED] = { .T = -31 },
|
||||
};
|
||||
|
||||
/* Transition to a state, using the T timer defined in map_rua_fsm_timeouts.
|
||||
* Assumes local variable fi exists. */
|
||||
#define map_rua_fsm_state_chg(state) \
|
||||
OSMO_ASSERT(osmo_tdef_fsm_inst_state_chg(fi, state, \
|
||||
map_rua_fsm_timeouts, \
|
||||
hnbgw_T_defs, \
|
||||
5) == 0)
|
||||
|
||||
void map_rua_fsm_alloc(struct hnbgw_context_map *map)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&map_rua_fsm, map, map, LOGL_DEBUG, NULL);
|
||||
OSMO_ASSERT(fi);
|
||||
osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-%s-RUA-%u", hnb_context_name(map->hnb_ctx),
|
||||
map->is_ps ? "PS" : "CS", map->rua_ctx_id);
|
||||
|
||||
OSMO_ASSERT(map->rua_fi == NULL);
|
||||
map->rua_fi = fi;
|
||||
|
||||
/* trigger the timeout */
|
||||
map_rua_fsm_state_chg(MAP_RUA_ST_INIT);
|
||||
}
|
||||
|
||||
enum hnbgw_context_map_state map_rua_get_state(struct hnbgw_context_map *map)
|
||||
{
|
||||
if (!map || !map->rua_fi)
|
||||
return MAP_S_DISCONNECTING;
|
||||
switch (map->rua_fi->state) {
|
||||
case MAP_RUA_ST_INIT:
|
||||
return MAP_S_CONNECTING;
|
||||
case MAP_RUA_ST_CONNECTED:
|
||||
return MAP_S_ACTIVE;
|
||||
default:
|
||||
case MAP_RUA_ST_DISCONNECTED:
|
||||
case MAP_RUA_ST_DISRUPTED:
|
||||
return MAP_S_DISCONNECTING;
|
||||
}
|
||||
}
|
||||
|
||||
bool map_rua_is_active(struct hnbgw_context_map *map)
|
||||
{
|
||||
if (!map || !map->rua_fi)
|
||||
return false;
|
||||
switch (map->rua_fi->state) {
|
||||
case MAP_RUA_ST_DISCONNECTED:
|
||||
case MAP_RUA_ST_DISRUPTED:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static int map_rua_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
switch (fi->state) {
|
||||
default:
|
||||
map_rua_fsm_state_chg(MAP_RUA_ST_DISRUPTED);
|
||||
return 0;
|
||||
|
||||
case MAP_RUA_ST_DISCONNECTED:
|
||||
case MAP_RUA_ST_DISRUPTED:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dispatch RANAP message to SCCP, if any. */
|
||||
static int handle_rx_rua(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
|
||||
/* If the FSM instance has already terminated, don't dispatch anything. */
|
||||
if (fi->proc.terminating)
|
||||
return 0;
|
||||
|
||||
if (!msg_has_l2_data(ranap_msg))
|
||||
return 0;
|
||||
|
||||
return hnbgw_ranap_rx_data_ul(map, ranap_msg);
|
||||
}
|
||||
|
||||
static int forward_ranap_to_rua(struct hnbgw_context_map *map, struct msgb *ranap_msg)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (!msg_has_l2_data(ranap_msg))
|
||||
return 0;
|
||||
|
||||
if (!map->hnb_ctx) {
|
||||
LOGPFSML(map->rua_fi, LOGL_ERROR, "Cannot transmit RUA DirectTransfer: HNB has disconnected\n");
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
rc = rua_tx_dt(map->hnb_ctx, map->is_ps, map->rua_ctx_id, msgb_l2(ranap_msg), msgb_l2len(ranap_msg));
|
||||
if (rc)
|
||||
LOGPFSML(map->rua_fi, LOGL_ERROR, "Failed to transmit RUA DirectTransfer to HNB\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void map_rua_init_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct msgb *ranap_msg = data;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case MAP_RUA_EV_RX_CONNECT:
|
||||
/* not needed for RAB assignment scanning, but for KPI scanning */
|
||||
handle_rx_rua(fi, ranap_msg);
|
||||
map_rua_fsm_state_chg(MAP_RUA_ST_CONNECTED);
|
||||
return;
|
||||
|
||||
case MAP_RUA_EV_RX_DISCONNECT:
|
||||
/* Unlikely that SCCP is active, but let the SCCP FSM decide about that. */
|
||||
handle_rx_rua(fi, ranap_msg);
|
||||
/* There is a reason to shut down this RUA connection. Super unlikely, we haven't even processed the
|
||||
* MAP_RUA_EV_RX_CONNECT that created this FSM. Semantically, RUA is not connected, so we can
|
||||
* directly go to MAP_RUA_ST_DISCONNECTED. */
|
||||
map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED);
|
||||
break;
|
||||
|
||||
case MAP_RUA_EV_CN_DISC:
|
||||
case MAP_RUA_EV_HNB_LINK_LOST:
|
||||
map_rua_fsm_state_chg(MAP_RUA_ST_DISRUPTED);
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void map_rua_tx_disconnect(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
RUA_Cause_t rua_cause;
|
||||
|
||||
if (!map->hnb_ctx || !map->hnb_ctx->conn) {
|
||||
/* HNB already disconnected, nothing to do. */
|
||||
LOGPFSML(fi, LOGL_NOTICE, "HNB vanished, this RUA context cannot disconnect gracefully\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Send Disconnect to RUA without RANAP data. */
|
||||
rua_cause = (RUA_Cause_t){
|
||||
.present = RUA_Cause_PR_radioNetwork,
|
||||
.choice.radioNetwork = RUA_CauseRadioNetwork_network_release,
|
||||
};
|
||||
LOGPFSML(fi, LOGL_INFO, "Tx RUA Disconnect\n");
|
||||
if (rua_tx_disc(map->hnb_ctx, map->is_ps, map->rua_ctx_id, &rua_cause, NULL, 0))
|
||||
LOGPFSML(fi, LOGL_ERROR, "Failed to send Disconnect to RUA\n");
|
||||
}
|
||||
|
||||
static void map_rua_connected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
struct msgb *ranap_msg = data;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case MAP_RUA_EV_RX_DIRECT_TRANSFER:
|
||||
/* received DirectTransfer from RUA, forward to SCCP */
|
||||
handle_rx_rua(fi, ranap_msg);
|
||||
return;
|
||||
|
||||
case MAP_RUA_EV_TX_DIRECT_TRANSFER:
|
||||
/* Someone (usually the SCCP side) wants us to send a RANAP payload to HNB via RUA */
|
||||
forward_ranap_to_rua(map, ranap_msg);
|
||||
return;
|
||||
|
||||
case MAP_RUA_EV_RX_DISCONNECT:
|
||||
/* 3GPP TS 25.468 9.1.5: RUA has disconnected.
|
||||
* - Under normal conditions (cause=Normal) the RUA Disconnect contains a RANAP Iu-ReleaseComplete.
|
||||
* On SCCP, the Iu-ReleaseComplete should still be forwarded as N-Data SCCP Data Form 1),
|
||||
* and we will expect the CN to send an SCCP RLSD soon.
|
||||
* - Under error conditions, cause!=Normal and there's no RANAP message.
|
||||
* In that case, we need to tear down the associated SCCP link towards CN,
|
||||
* which in turn will tear down the upper layer Iu conn.
|
||||
*/
|
||||
if (msg_has_l2_data(ranap_msg)) {
|
||||
/* Forward any payload to SCCP before Disconnect. */
|
||||
handle_rx_rua(fi, ranap_msg);
|
||||
} else {
|
||||
map->rua_fi_ctx.rua_disconnect_err_condition = true;
|
||||
}
|
||||
map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED);
|
||||
return;
|
||||
|
||||
case MAP_RUA_EV_HNB_LINK_LOST:
|
||||
/* The HNB is gone. Cannot gracefully cleanup the RUA connection, just be gone. */
|
||||
map_rua_fsm_state_chg(MAP_RUA_ST_DISRUPTED);
|
||||
return;
|
||||
|
||||
case MAP_RUA_EV_CN_DISC:
|
||||
/* There is a disruptive reason to shut down this RUA connection, HNB is still there */
|
||||
OSMO_ASSERT(data == NULL);
|
||||
map_rua_tx_disconnect(fi);
|
||||
map_rua_fsm_state_chg(MAP_RUA_ST_DISRUPTED);
|
||||
return;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void map_rua_free_if_done(struct hnbgw_context_map *map, uint32_t sccp_event, void *ev_data)
|
||||
{
|
||||
/* From RUA's POV, we can now free the hnbgw_context_map.
|
||||
* If SCCP is still active, tell it to disconnect -- in that case the SCCP side will call context_map_free().
|
||||
* If SCCP is no longer active, free this map. */
|
||||
if (map_sccp_is_active(map))
|
||||
map_sccp_dispatch(map, sccp_event, ev_data);
|
||||
else
|
||||
context_map_free(map);
|
||||
}
|
||||
|
||||
static void map_rua_disconnected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
map_rua_free_if_done(map, MAP_SCCP_EV_RAN_DISC, (void *)map->rua_fi_ctx.rua_disconnect_err_condition);
|
||||
}
|
||||
|
||||
static void map_rua_disconnected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct msgb *ranap_msg;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case MAP_RUA_EV_TX_DIRECT_TRANSFER:
|
||||
/* This can happen if CN is buggy, or in general if there was a race
|
||||
* condition between us forwarding the release towards CN (SCCP Release
|
||||
* or RANAP Iu-ReleaseComplete) and CN sendig whatever to us. */
|
||||
ranap_msg = data;
|
||||
if (msg_has_l2_data(ranap_msg)) {
|
||||
LOGPFSML(fi, LOGL_NOTICE, "RUA already disconnected, skip forwarding DL RANAP msg (%u bytes)\n",
|
||||
msgb_l2len(ranap_msg));
|
||||
LOGPFSML(fi, LOGL_DEBUG, "%s\n", osmo_hexdump(msgb_l2(ranap_msg), msgb_l2len(ranap_msg)));
|
||||
}
|
||||
break;
|
||||
|
||||
case MAP_RUA_EV_CN_DISC:
|
||||
case MAP_RUA_EV_HNB_LINK_LOST:
|
||||
/* Ignore events. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void map_rua_disrupted_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
map_rua_free_if_done(map, MAP_SCCP_EV_RAN_LINK_LOST, NULL);
|
||||
}
|
||||
|
||||
void map_rua_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
map->rua_fi = NULL;
|
||||
}
|
||||
|
||||
#define S(x) (1 << (x))
|
||||
|
||||
static const struct osmo_fsm_state map_rua_fsm_states[] = {
|
||||
[MAP_RUA_ST_INIT] = {
|
||||
.name = "init",
|
||||
.in_event_mask = 0
|
||||
| S(MAP_RUA_EV_RX_CONNECT)
|
||||
| S(MAP_RUA_EV_RX_DISCONNECT)
|
||||
| S(MAP_RUA_EV_CN_DISC)
|
||||
| S(MAP_RUA_EV_HNB_LINK_LOST)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(MAP_RUA_ST_INIT)
|
||||
| S(MAP_RUA_ST_CONNECTED)
|
||||
| S(MAP_RUA_ST_DISCONNECTED)
|
||||
| S(MAP_RUA_ST_DISRUPTED)
|
||||
,
|
||||
.action = map_rua_init_action,
|
||||
},
|
||||
[MAP_RUA_ST_CONNECTED] = {
|
||||
.name = "connected",
|
||||
.in_event_mask = 0
|
||||
| S(MAP_RUA_EV_RX_DIRECT_TRANSFER)
|
||||
| S(MAP_RUA_EV_TX_DIRECT_TRANSFER)
|
||||
| S(MAP_RUA_EV_RX_DISCONNECT)
|
||||
| S(MAP_RUA_EV_CN_DISC)
|
||||
| S(MAP_RUA_EV_HNB_LINK_LOST)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(MAP_RUA_ST_DISCONNECTED)
|
||||
| S(MAP_RUA_ST_DISRUPTED)
|
||||
,
|
||||
.action = map_rua_connected_action,
|
||||
},
|
||||
[MAP_RUA_ST_DISCONNECTED] = {
|
||||
.name = "disconnected",
|
||||
.in_event_mask = 0
|
||||
| S(MAP_RUA_EV_TX_DIRECT_TRANSFER)
|
||||
| S(MAP_RUA_EV_CN_DISC)
|
||||
| S(MAP_RUA_EV_HNB_LINK_LOST)
|
||||
,
|
||||
.onenter = map_rua_disconnected_onenter,
|
||||
.action = map_rua_disconnected_action,
|
||||
},
|
||||
[MAP_RUA_ST_DISRUPTED] = {
|
||||
.name = "disrupted",
|
||||
.in_event_mask = 0
|
||||
| S(MAP_RUA_EV_CN_DISC)
|
||||
| S(MAP_RUA_EV_HNB_LINK_LOST)
|
||||
,
|
||||
.onenter = map_rua_disrupted_onenter,
|
||||
/* same as MAP_RUA_ST_DISCONNECTED: */
|
||||
.action = map_rua_disconnected_action,
|
||||
},
|
||||
};
|
||||
|
||||
static struct osmo_fsm map_rua_fsm = {
|
||||
.name = "map_rua",
|
||||
.states = map_rua_fsm_states,
|
||||
.num_states = ARRAY_SIZE(map_rua_fsm_states),
|
||||
.log_subsys = DHNB,
|
||||
.event_names = map_rua_fsm_event_names,
|
||||
.timer_cb = map_rua_fsm_timer_cb,
|
||||
.cleanup = map_rua_fsm_cleanup,
|
||||
};
|
||||
|
||||
static __attribute__((constructor)) void map_rua_fsm_register(void)
|
||||
{
|
||||
OSMO_ASSERT(osmo_fsm_register(&map_rua_fsm) == 0);
|
||||
}
|
||||
590
src/osmo-hnbgw/context_map_sccp.c
Normal file
590
src/osmo-hnbgw/context_map_sccp.c
Normal file
@@ -0,0 +1,590 @@
|
||||
/* SCCP side FSM of hnbgw_context_map */
|
||||
/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0+
|
||||
*
|
||||
* Author: Neels Hofmeyr
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
|
||||
#include <osmocom/sigtran/sccp_helpers.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnb.h>
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/hnbgw_ranap.h>
|
||||
#include <osmocom/hnbgw/tdefs.h>
|
||||
|
||||
enum map_sccp_fsm_state {
|
||||
MAP_SCCP_ST_INIT,
|
||||
MAP_SCCP_ST_WAIT_CC,
|
||||
MAP_SCCP_ST_CONNECTED,
|
||||
MAP_SCCP_ST_WAIT_RLSD,
|
||||
MAP_SCCP_ST_DISCONNECTED,
|
||||
};
|
||||
|
||||
static const struct value_string map_sccp_fsm_event_names[] = {
|
||||
OSMO_VALUE_STRING(MAP_SCCP_EV_RX_CONNECTION_CONFIRM),
|
||||
OSMO_VALUE_STRING(MAP_SCCP_EV_RX_DATA_INDICATION),
|
||||
OSMO_VALUE_STRING(MAP_SCCP_EV_TX_DATA_REQUEST),
|
||||
OSMO_VALUE_STRING(MAP_SCCP_EV_RAN_DISC),
|
||||
OSMO_VALUE_STRING(MAP_SCCP_EV_RAN_LINK_LOST),
|
||||
OSMO_VALUE_STRING(MAP_SCCP_EV_RX_RELEASED),
|
||||
OSMO_VALUE_STRING(MAP_SCCP_EV_USER_ABORT),
|
||||
OSMO_VALUE_STRING(MAP_SCCP_EV_CN_LINK_LOST),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct osmo_fsm map_sccp_fsm;
|
||||
|
||||
static const struct osmo_tdef_state_timeout map_sccp_fsm_timeouts[32] = {
|
||||
[MAP_SCCP_ST_INIT] = { .T = -31 },
|
||||
[MAP_SCCP_ST_WAIT_CC] = { .T = -31 },
|
||||
[MAP_SCCP_ST_CONNECTED] = { .T = 0 },
|
||||
[MAP_SCCP_ST_WAIT_RLSD] = { .T = -31 },
|
||||
[MAP_SCCP_ST_DISCONNECTED] = { .T = -31 },
|
||||
};
|
||||
|
||||
/* Transition to a state, using the T timer defined in map_sccp_fsm_timeouts.
|
||||
* Assumes local variable fi exists. */
|
||||
#define map_sccp_fsm_state_chg(state) \
|
||||
OSMO_ASSERT(osmo_tdef_fsm_inst_state_chg(fi, state, \
|
||||
map_sccp_fsm_timeouts, \
|
||||
hnbgw_T_defs, \
|
||||
5) == 0)
|
||||
|
||||
void map_sccp_fsm_alloc(struct hnbgw_context_map *map)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&map_sccp_fsm, map, map, LOGL_DEBUG, NULL);
|
||||
OSMO_ASSERT(fi);
|
||||
osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-%s-SCCP-%u", hnb_context_name(map->hnb_ctx),
|
||||
map->cnlink ? map->cnlink->name : (map->is_ps ? "PS" : "CS"),
|
||||
map->scu_conn_id);
|
||||
|
||||
OSMO_ASSERT(map->sccp_fi == NULL);
|
||||
map->sccp_fi = fi;
|
||||
INIT_LLIST_HEAD(&map->sccp_fi_ctx.wait_cc_tx_msg_list);
|
||||
/* trigger the timeout */
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_INIT);
|
||||
}
|
||||
|
||||
enum hnbgw_context_map_state map_sccp_get_state(struct hnbgw_context_map *map)
|
||||
{
|
||||
if (!map || !map->sccp_fi)
|
||||
return MAP_S_DISCONNECTING;
|
||||
switch (map->sccp_fi->state) {
|
||||
case MAP_SCCP_ST_INIT:
|
||||
case MAP_SCCP_ST_WAIT_CC:
|
||||
return MAP_S_CONNECTING;
|
||||
case MAP_SCCP_ST_CONNECTED:
|
||||
return MAP_S_ACTIVE;
|
||||
default:
|
||||
case MAP_SCCP_ST_WAIT_RLSD:
|
||||
case MAP_SCCP_ST_DISCONNECTED:
|
||||
return MAP_S_DISCONNECTING;
|
||||
}
|
||||
}
|
||||
|
||||
bool map_sccp_is_active(struct hnbgw_context_map *map)
|
||||
{
|
||||
if (!map || !map->sccp_fi)
|
||||
return false;
|
||||
switch (map->sccp_fi->state) {
|
||||
case MAP_SCCP_ST_DISCONNECTED:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static int tx_sccp_cr(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
|
||||
if (!map->cnlink) {
|
||||
LOGPFSML(fi, LOGL_ERROR, "Failed to send SCCP Connection Request: no CN link\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!ranap_msg) {
|
||||
/* prepare a msgb to send an empty N-Connect prim (but this should never happen in practice) */
|
||||
ranap_msg = hnbgw_ranap_msg_alloc("SCCP-CR-empty");
|
||||
}
|
||||
|
||||
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_N_CONNECT_REQ);
|
||||
return hnbgw_sccp_user_tx_connect_req(map->cnlink->hnbgw_sccp_user,
|
||||
&map->cnlink->remote_addr,
|
||||
map->scu_conn_id,
|
||||
ranap_msg);
|
||||
}
|
||||
|
||||
static int tx_sccp_df1(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
|
||||
if (!msg_has_l2_data(ranap_msg))
|
||||
return 0;
|
||||
|
||||
if (!map->cnlink) {
|
||||
LOGPFSML(fi, LOGL_ERROR, "Failed to send SCCP Data Form 1: no CN link\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_N_DATA_REQ);
|
||||
return hnbgw_sccp_user_tx_data_req(map->cnlink->hnbgw_sccp_user,
|
||||
map->scu_conn_id,
|
||||
ranap_msg);
|
||||
}
|
||||
|
||||
static int tx_sccp_rlsd(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
|
||||
if (!map->cnlink) {
|
||||
LOGPFSML(fi, LOGL_ERROR, "Failed to send SCCP RLSD: no CN link\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_N_DISCONNECT_REQ);
|
||||
return hnbgw_sccp_user_tx_disconnect_req(map->cnlink->hnbgw_sccp_user,
|
||||
map->scu_conn_id);
|
||||
}
|
||||
|
||||
static int handle_rx_sccp(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
|
||||
/* If the FSM instance has already terminated, don't dispatch anything. */
|
||||
if (fi->proc.terminating)
|
||||
return 0;
|
||||
|
||||
/* When there was no message received along with the received event, then there is nothing to forward to RUA. */
|
||||
if (!msg_has_l2_data(ranap_msg))
|
||||
return 0;
|
||||
|
||||
return hnbgw_ranap_rx_data_dl(map, ranap_msg);
|
||||
}
|
||||
|
||||
static void wait_cc_tx_msg_list_enqueue(struct hnbgw_context_map *map, struct msgb *ranap_msg)
|
||||
{
|
||||
talloc_steal(map, ranap_msg);
|
||||
msgb_enqueue(&map->sccp_fi_ctx.wait_cc_tx_msg_list, ranap_msg);
|
||||
}
|
||||
|
||||
static struct msgb *wait_cc_tx_msg_list_dequeue(struct hnbgw_context_map *map)
|
||||
{
|
||||
struct msgb *ranap_msg = msgb_dequeue(&map->sccp_fi_ctx.wait_cc_tx_msg_list);
|
||||
if (ranap_msg)
|
||||
talloc_steal(OTC_SELECT, ranap_msg);
|
||||
return ranap_msg;
|
||||
}
|
||||
|
||||
static void map_sccp_init_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct msgb *ranap_msg = NULL;
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case MAP_SCCP_EV_TX_DATA_REQUEST:
|
||||
ranap_msg = data;
|
||||
/* In the INIT state, the first MAP_SCCP_EV_TX_DATA_REQUEST will be the RANAP message received from the
|
||||
* RUA Connect message. Send the SCCP CR and transition to WAIT_CC. */
|
||||
if (tx_sccp_cr(fi, ranap_msg) == 0)
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_WAIT_CC);
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_RAN_LINK_LOST:
|
||||
case MAP_SCCP_EV_USER_ABORT:
|
||||
case MAP_SCCP_EV_CN_LINK_LOST:
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_RAN_DISC:
|
||||
/* bool rua_disconnect_err_condition = !!data; */
|
||||
/* 3GPP TS 25.468 9.1.5: RUA has disconnected.
|
||||
* In this state we didn't send an SCCP CR yet, so nothing to be torn down on CN side. */
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_RX_RELEASED:
|
||||
/* SCCP RLSD received from CN. This will never happen since we haven't even asked for a connection, but
|
||||
* for completeness: */
|
||||
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_RLSD_CN_ORIGIN);
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
|
||||
return;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void map_sccp_wait_cc_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
struct msgb *ranap_msg = NULL;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case MAP_SCCP_EV_RX_CONNECTION_CONFIRM:
|
||||
ranap_msg = data;
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_CONNECTED);
|
||||
/* Usually doesn't but if the SCCP CC contained data, forward it to RUA */
|
||||
handle_rx_sccp(fi, ranap_msg);
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_TX_DATA_REQUEST:
|
||||
ranap_msg = data;
|
||||
LOGPFSML(fi, LOGL_INFO, "Caching RANAP msg from RUA while waiting for SCCP CC\n");
|
||||
wait_cc_tx_msg_list_enqueue(map, ranap_msg);
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_RAN_LINK_LOST:
|
||||
case MAP_SCCP_EV_USER_ABORT:
|
||||
case MAP_SCCP_EV_CN_LINK_LOST:
|
||||
map->please_disconnect = true;
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_RAN_DISC:
|
||||
/* bool rua_disconnect_err_condition = !!data; */
|
||||
/* 3GPP TS 25.468 9.1.5: RUA has disconnected.
|
||||
* In this state we didn't send an SCCP CR yet, so nothing to be torn down on CN side. */
|
||||
map->please_disconnect = true;
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_RX_RELEASED:
|
||||
ranap_msg = data;
|
||||
/* SCCP RLSD received from CN. This will never happen since we haven't even received a Connection
|
||||
* Confirmed, but for completeness: */
|
||||
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_RLSD_CN_ORIGIN);
|
||||
handle_rx_sccp(fi, ranap_msg);
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
|
||||
return;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void map_sccp_connected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
struct msgb *ranap_msg;
|
||||
|
||||
/* Now that SCCP conn is confirmed, forward pending msgs received from RUA side: */
|
||||
while ((ranap_msg = wait_cc_tx_msg_list_dequeue(map)))
|
||||
tx_sccp_df1(fi, ranap_msg);
|
||||
|
||||
if (map->please_disconnect) {
|
||||
/* SCCP has already been asked to disconnect, so disconnect now that the
|
||||
* CC has been received. Send RLSD to SCCP (without RANAP data) */
|
||||
tx_sccp_rlsd(fi);
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
static void map_sccp_connected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
struct msgb *ranap_msg = NULL;
|
||||
bool rua_disconnect_err_condition;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case MAP_SCCP_EV_RX_DATA_INDICATION:
|
||||
ranap_msg = data;
|
||||
/* forward RANAP from SCCP to RUA */
|
||||
handle_rx_sccp(fi, ranap_msg);
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_TX_DATA_REQUEST:
|
||||
ranap_msg = data;
|
||||
/* Someone (usually the RUA side) wants us to send a RANAP payload to CN via SCCP */
|
||||
tx_sccp_df1(fi, ranap_msg);
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_RAN_DISC:
|
||||
rua_disconnect_err_condition = !!data;
|
||||
/* 3GPP TS 25.468 9.1.5: RUA has disconnected.
|
||||
* - Under normal conditions (cause=Normal) the RUA Disconnect
|
||||
* contained a RANAP Iu-ReleaseComplete which we already
|
||||
* handled here through MAP_SCCP_EV_TX_DATA_REQUEST.
|
||||
* On SCCP, We will expect the CN to send an SCCP RLSD soon.
|
||||
* - Under error conditions, cause!=Normal and there was no RANAP message.
|
||||
* In that case, we need to tear down the associated SCCP link towards CN,
|
||||
* which in turn will tear down the upper layer Iu conn.
|
||||
*/
|
||||
if (rua_disconnect_err_condition) {
|
||||
tx_sccp_rlsd(fi);
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
|
||||
} else {
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_WAIT_RLSD);
|
||||
}
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_RAN_LINK_LOST:
|
||||
/* RUA has disconnected ungracefully, so there is no Iu Release that told the CN to disconnect.
|
||||
* Disconnect on the SCCP layer, ungracefully. */
|
||||
case MAP_SCCP_EV_USER_ABORT:
|
||||
/* The user is asking for disconnection, so there is no Iu Release in progress. Disconnect now. */
|
||||
case MAP_SCCP_EV_CN_LINK_LOST:
|
||||
/* The CN peer has sent a RANAP RESET, so the old link that this map ran on is lost */
|
||||
tx_sccp_rlsd(fi);
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_RX_RELEASED:
|
||||
ranap_msg = data;
|
||||
/* The CN sends an N-Disconnect (SCCP Released). */
|
||||
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_RLSD_CN_ORIGIN);
|
||||
handle_rx_sccp(fi, ranap_msg);
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_RX_CONNECTION_CONFIRM:
|
||||
ranap_msg = data;
|
||||
/* Already connected. Unusual, but if there is data just forward it. */
|
||||
LOGPFSML(fi, LOGL_ERROR, "Already connected, but received SCCP CC again\n");
|
||||
handle_rx_sccp(fi, ranap_msg);
|
||||
return;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void map_sccp_wait_rlsd_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
/* For sanity, always tell RUA to disconnect, if it hasn't done so. */
|
||||
if (map_rua_is_active(map))
|
||||
map_rua_dispatch(map, MAP_RUA_EV_CN_DISC, NULL);
|
||||
}
|
||||
|
||||
static void map_sccp_wait_rlsd_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct msgb *ranap_msg = NULL;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case MAP_SCCP_EV_RX_RELEASED:
|
||||
ranap_msg = data;
|
||||
/* The CN sends the expected SCCP RLSD.
|
||||
* Usually there is no data, but if there is just forward it.
|
||||
* Usually RUA is already disconnected, but let the RUA FSM decide about that. */
|
||||
handle_rx_sccp(fi, ranap_msg);
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_RX_DATA_INDICATION:
|
||||
ranap_msg = data;
|
||||
/* RUA is probably already disconnected, but let the RUA FSM decide about that. */
|
||||
handle_rx_sccp(fi, ranap_msg);
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_TX_DATA_REQUEST:
|
||||
ranap_msg = data;
|
||||
/* Normally, RUA would already disconnected, but since SCCP is officially still connected, we can still
|
||||
* forward messages there. Already waiting for CN to send the SCCP RLSD. If there is a message, forward
|
||||
* it, and just continue to time out on the SCCP RLSD. */
|
||||
tx_sccp_df1(fi, ranap_msg);
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_RX_CONNECTION_CONFIRM:
|
||||
ranap_msg = data;
|
||||
/* Already connected. Unusual, but if there is data just forward it. */
|
||||
LOGPFSML(fi, LOGL_ERROR, "Already connected, but received SCCP CC\n");
|
||||
handle_rx_sccp(fi, ranap_msg);
|
||||
return;
|
||||
|
||||
case MAP_SCCP_EV_RAN_LINK_LOST:
|
||||
case MAP_SCCP_EV_USER_ABORT:
|
||||
case MAP_SCCP_EV_CN_LINK_LOST:
|
||||
case MAP_SCCP_EV_RAN_DISC:
|
||||
/* Stop waiting for RLSD, send RLSD now. */
|
||||
tx_sccp_rlsd(fi);
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
|
||||
return;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void map_sccp_disconnected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
/* From SCCP's POV, we can now free the hnbgw_context_map.
|
||||
* If RUA is still active, tell it to disconnect -- in that case the RUA side will call context_map_free().
|
||||
* If RUA is no longer active, free this map. */
|
||||
if (map_rua_is_active(map))
|
||||
map_rua_dispatch(map, MAP_RUA_EV_CN_DISC, NULL);
|
||||
else
|
||||
context_map_free(map);
|
||||
}
|
||||
|
||||
static void map_sccp_disconnected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct msgb *ranap_msg = data;
|
||||
|
||||
if (msg_has_l2_data(ranap_msg))
|
||||
LOGPFSML(fi, LOGL_ERROR, "SCCP not connected, cannot dispatch RANAP message\n");
|
||||
}
|
||||
|
||||
static int map_sccp_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
switch (fi->state) {
|
||||
case MAP_SCCP_ST_INIT:
|
||||
/* cannot sent SCCP RLSD, because we haven't set up an SCCP link */
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
|
||||
return 0;
|
||||
|
||||
case MAP_SCCP_ST_WAIT_CC:
|
||||
/* send N-DISCONNECT. libosmo-sigtran/sccp_scoc.c will do the SCCP connection cleanup, like waiting a
|
||||
* bit whether the SCCP CC might still arrive, and cleanup the conn if not. */
|
||||
case MAP_SCCP_ST_CONNECTED:
|
||||
case MAP_SCCP_ST_WAIT_RLSD:
|
||||
/* send SCCP RLSD. libosmo-sigtran/sccp_scoc.c will do the SCCP connection cleanup.
|
||||
* (It will repeatedly send SCCP RLSD until the peer responded with SCCP RLC, or until the
|
||||
* sccp_connection->t_int timer expires, and the sccp_connection is freed.) */
|
||||
if (map->cnlink)
|
||||
tx_sccp_rlsd(fi);
|
||||
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
case MAP_SCCP_ST_DISCONNECTED:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void map_sccp_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
||||
{
|
||||
struct hnbgw_context_map *map = fi->priv;
|
||||
map->sccp_fi = NULL;
|
||||
msgb_queue_free(&map->sccp_fi_ctx.wait_cc_tx_msg_list);
|
||||
}
|
||||
|
||||
#define S(x) (1 << (x))
|
||||
|
||||
static const struct osmo_fsm_state map_sccp_fsm_states[] = {
|
||||
[MAP_SCCP_ST_INIT] = {
|
||||
.name = "init",
|
||||
.in_event_mask = 0
|
||||
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
|
||||
| S(MAP_SCCP_EV_RAN_DISC)
|
||||
| S(MAP_SCCP_EV_RAN_LINK_LOST)
|
||||
| S(MAP_SCCP_EV_RX_RELEASED)
|
||||
| S(MAP_SCCP_EV_USER_ABORT)
|
||||
| S(MAP_SCCP_EV_CN_LINK_LOST)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(MAP_SCCP_ST_INIT)
|
||||
| S(MAP_SCCP_ST_WAIT_CC)
|
||||
| S(MAP_SCCP_ST_DISCONNECTED)
|
||||
,
|
||||
.action = map_sccp_init_action,
|
||||
},
|
||||
[MAP_SCCP_ST_WAIT_CC] = {
|
||||
.name = "wait_cc",
|
||||
.in_event_mask = 0
|
||||
| S(MAP_SCCP_EV_RX_CONNECTION_CONFIRM)
|
||||
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
|
||||
| S(MAP_SCCP_EV_RAN_DISC)
|
||||
| S(MAP_SCCP_EV_RAN_LINK_LOST)
|
||||
| S(MAP_SCCP_EV_RX_RELEASED)
|
||||
| S(MAP_SCCP_EV_USER_ABORT)
|
||||
| S(MAP_SCCP_EV_CN_LINK_LOST)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(MAP_SCCP_ST_CONNECTED)
|
||||
| S(MAP_SCCP_ST_DISCONNECTED)
|
||||
,
|
||||
.action = map_sccp_wait_cc_action,
|
||||
},
|
||||
[MAP_SCCP_ST_CONNECTED] = {
|
||||
.name = "connected",
|
||||
.in_event_mask = 0
|
||||
| S(MAP_SCCP_EV_RX_DATA_INDICATION)
|
||||
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
|
||||
| S(MAP_SCCP_EV_RAN_DISC)
|
||||
| S(MAP_SCCP_EV_RAN_LINK_LOST)
|
||||
| S(MAP_SCCP_EV_RX_RELEASED)
|
||||
| S(MAP_SCCP_EV_RX_CONNECTION_CONFIRM)
|
||||
| S(MAP_SCCP_EV_USER_ABORT)
|
||||
| S(MAP_SCCP_EV_CN_LINK_LOST)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(MAP_SCCP_ST_WAIT_RLSD)
|
||||
| S(MAP_SCCP_ST_DISCONNECTED)
|
||||
,
|
||||
.onenter = map_sccp_connected_onenter,
|
||||
.action = map_sccp_connected_action,
|
||||
},
|
||||
[MAP_SCCP_ST_WAIT_RLSD] = {
|
||||
.name = "wait_rlsd",
|
||||
.in_event_mask = 0
|
||||
| S(MAP_SCCP_EV_RX_RELEASED)
|
||||
| S(MAP_SCCP_EV_RX_DATA_INDICATION)
|
||||
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
|
||||
| S(MAP_SCCP_EV_RAN_DISC)
|
||||
| S(MAP_SCCP_EV_RAN_LINK_LOST)
|
||||
| S(MAP_SCCP_EV_RX_CONNECTION_CONFIRM)
|
||||
| S(MAP_SCCP_EV_USER_ABORT)
|
||||
| S(MAP_SCCP_EV_CN_LINK_LOST)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(MAP_SCCP_ST_DISCONNECTED)
|
||||
,
|
||||
.onenter = map_sccp_wait_rlsd_onenter,
|
||||
.action = map_sccp_wait_rlsd_action,
|
||||
},
|
||||
[MAP_SCCP_ST_DISCONNECTED] = {
|
||||
.name = "disconnected",
|
||||
.in_event_mask = 0
|
||||
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
|
||||
| S(MAP_SCCP_EV_RAN_DISC)
|
||||
| S(MAP_SCCP_EV_RAN_LINK_LOST)
|
||||
| S(MAP_SCCP_EV_USER_ABORT)
|
||||
| S(MAP_SCCP_EV_CN_LINK_LOST)
|
||||
,
|
||||
.onenter = map_sccp_disconnected_onenter,
|
||||
.action = map_sccp_disconnected_action,
|
||||
},
|
||||
};
|
||||
|
||||
static struct osmo_fsm map_sccp_fsm = {
|
||||
.name = "map_sccp",
|
||||
.states = map_sccp_fsm_states,
|
||||
.num_states = ARRAY_SIZE(map_sccp_fsm_states),
|
||||
.log_subsys = DCN,
|
||||
.event_names = map_sccp_fsm_event_names,
|
||||
.timer_cb = map_sccp_fsm_timer_cb,
|
||||
.cleanup = map_sccp_fsm_cleanup,
|
||||
};
|
||||
|
||||
static __attribute__((constructor)) void map_sccp_fsm_register(void)
|
||||
{
|
||||
OSMO_ASSERT(osmo_fsm_register(&map_sccp_fsm) == 0);
|
||||
}
|
||||
401
src/osmo-hnbgw/hnb.c
Normal file
401
src/osmo-hnbgw/hnb.c
Normal file
@@ -0,0 +1,401 @@
|
||||
/* HNB related code */
|
||||
|
||||
/* (C) 2015,2024 by Harald Welte <laforge@gnumonks.org>
|
||||
* (C) 2016-2025 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/sctp.h>
|
||||
|
||||
#include <osmocom/core/stats.h>
|
||||
#include <osmocom/core/rate_ctr.h>
|
||||
#include <osmocom/core/stat_item.h>
|
||||
|
||||
#include <osmocom/gsm/gsm23236.h>
|
||||
|
||||
#include <osmocom/netif/stream.h>
|
||||
|
||||
#include <osmocom/hnbgw/umts_cell_id.h>
|
||||
#include <osmocom/hnbgw/hnb.h>
|
||||
#include <osmocom/hnbgw/hnb_persistent.h>
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/hnbgw_hnbap.h>
|
||||
#include <osmocom/hnbgw/hnbgw_rua.h>
|
||||
#include <osmocom/hnbgw/tdefs.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/mgw_fsm.h>
|
||||
|
||||
/* update the active RAB duration rate_ctr for given HNB */
|
||||
void hnb_store_rab_durations(struct hnb_context *hnb)
|
||||
{
|
||||
struct hnbgw_context_map *map;
|
||||
struct timespec now;
|
||||
uint64_t elapsed_cs_rab_ms = 0;
|
||||
|
||||
osmo_clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
|
||||
/* iterate over all context_maps (subscribers) */
|
||||
llist_for_each_entry(map, &hnb->map_list, hnb_list) {
|
||||
/* skip any PS maps, we care about CS RABs only here */
|
||||
if (map->is_ps)
|
||||
continue;
|
||||
elapsed_cs_rab_ms += mgw_fsm_get_elapsed_ms(map, &now);
|
||||
}
|
||||
|
||||
/* Export to rate countes. */
|
||||
rate_ctr_add(HNBP_CTR(hnb->persistent, HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL), elapsed_cs_rab_ms);
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* HNB Context
|
||||
***********************************************************************/
|
||||
|
||||
/* look-up HNB context by id. Used from CTRL */
|
||||
static struct hnb_context *hnb_context_by_id(uint32_t cid)
|
||||
{
|
||||
struct hnb_context *hnb;
|
||||
|
||||
llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) {
|
||||
if (hnb->id.cid == cid)
|
||||
return hnb;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* look-up HNB context by identity_info. Used from VTY */
|
||||
struct hnb_context *hnb_context_by_identity_info(const char *identity_info)
|
||||
{
|
||||
struct hnb_context *hnb;
|
||||
|
||||
llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) {
|
||||
if (strcmp(identity_info, hnb->identity_info) == 0)
|
||||
return hnb;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int hnb_read_cb(struct osmo_stream_srv *conn);
|
||||
static int hnb_closed_cb(struct osmo_stream_srv *conn);
|
||||
|
||||
static struct hnb_context *hnb_context_alloc(struct osmo_stream_srv_link *link, int new_fd)
|
||||
{
|
||||
struct hnb_context *ctx;
|
||||
|
||||
ctx = talloc_zero(g_hnbgw, struct hnb_context);
|
||||
if (!ctx)
|
||||
return NULL;
|
||||
INIT_LLIST_HEAD(&ctx->map_list);
|
||||
|
||||
ctx->conn = osmo_stream_srv_create(g_hnbgw, link, new_fd, hnb_read_cb, hnb_closed_cb, ctx);
|
||||
if (!ctx->conn) {
|
||||
LOGP(DMAIN, LOGL_INFO, "error while creating connection\n");
|
||||
talloc_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
llist_add_tail(&ctx->list, &g_hnbgw->hnb_list);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
const char *hnb_context_name(struct hnb_context *ctx)
|
||||
{
|
||||
char *result;
|
||||
if (!ctx)
|
||||
return "NULL";
|
||||
|
||||
if (ctx->conn) {
|
||||
char hostbuf_r[INET6_ADDRSTRLEN];
|
||||
char portbuf_r[6];
|
||||
int fd = osmo_stream_srv_get_ofd(ctx->conn)->fd;
|
||||
|
||||
/* get remote addr */
|
||||
if (osmo_sock_get_ip_and_port(fd, hostbuf_r, sizeof(hostbuf_r), portbuf_r, sizeof(portbuf_r), false) == 0)
|
||||
result = talloc_asprintf(OTC_SELECT, "%s:%s", hostbuf_r, portbuf_r);
|
||||
else
|
||||
result = "?";
|
||||
} else {
|
||||
result = "disconnected";
|
||||
}
|
||||
|
||||
if (g_hnbgw->config.log_prefix_hnb_id)
|
||||
result = talloc_asprintf(OTC_SELECT, "%s %s", result, ctx->identity_info);
|
||||
else
|
||||
result = talloc_asprintf(OTC_SELECT, "%s %s", result, umts_cell_id_to_str(&ctx->id));
|
||||
return result;
|
||||
}
|
||||
|
||||
void hnb_context_release_ue_state(struct hnb_context *ctx)
|
||||
{
|
||||
struct hnbgw_context_map *map, *map2;
|
||||
|
||||
/* deactivate all context maps */
|
||||
llist_for_each_entry_safe(map, map2, &ctx->map_list, hnb_list) {
|
||||
context_map_hnb_released(map);
|
||||
/* hnbgw_context_map will remove itself from lists when it is ready. */
|
||||
}
|
||||
}
|
||||
|
||||
void hnb_context_release(struct hnb_context *ctx)
|
||||
{
|
||||
struct hnbgw_context_map *map;
|
||||
|
||||
LOGHNB(ctx, DMAIN, LOGL_INFO, "Releasing HNB context\n");
|
||||
|
||||
if (ctx->persistent) {
|
||||
struct timespec tp;
|
||||
int rc;
|
||||
rc = osmo_clock_gettime(CLOCK_MONOTONIC, &tp);
|
||||
ctx->persistent->updowntime = (rc < 0) ? 0 : tp.tv_sec;
|
||||
}
|
||||
|
||||
/* remove from the list of HNB contexts */
|
||||
llist_del(&ctx->list);
|
||||
|
||||
hnb_context_release_ue_state(ctx);
|
||||
|
||||
if (ctx->conn) { /* we own a conn, we must free it: */
|
||||
LOGHNB(ctx, DMAIN, LOGL_INFO, "Closing HNB SCTP connection %s\n",
|
||||
osmo_sock_get_name2(osmo_stream_srv_get_ofd(ctx->conn)->fd));
|
||||
/* Avoid our closed_cb calling hnb_context_release() again: */
|
||||
osmo_stream_srv_set_data(ctx->conn, NULL);
|
||||
osmo_stream_srv_destroy(ctx->conn);
|
||||
} /* else: we are called from closed_cb, so conn is being freed separately */
|
||||
|
||||
/* hnbgw_context_map are still listed in ctx->map_list, but we are freeing ctx. Remove all entries from the
|
||||
* list, but keep the hnbgw_context_map around for graceful release. They are also listed under
|
||||
* hnbgw_cnlink->map_list, and will remove themselves when ready. */
|
||||
while ((map = llist_first_entry_or_null(&ctx->map_list, struct hnbgw_context_map, hnb_list))) {
|
||||
llist_del(&map->hnb_list);
|
||||
map->hnb_ctx = NULL;
|
||||
}
|
||||
|
||||
/* remove back reference from hnb_persistent to context */
|
||||
if (ctx->persistent)
|
||||
hnb_persistent_deregistered(ctx->persistent);
|
||||
|
||||
talloc_free(ctx);
|
||||
}
|
||||
|
||||
unsigned long long hnb_get_updowntime(const struct hnb_context *ctx)
|
||||
{
|
||||
if (!ctx->persistent)
|
||||
return 0;
|
||||
return hnbp_get_updowntime(ctx->persistent);
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* SCTP Socket / stream handling
|
||||
***********************************************************************/
|
||||
|
||||
static int hnb_read_cb(struct osmo_stream_srv *conn)
|
||||
{
|
||||
struct hnb_context *hnb = osmo_stream_srv_get_data(conn);
|
||||
struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn);
|
||||
struct msgb *msg = msgb_alloc(IUH_MSGB_SIZE, "Iuh rx");
|
||||
int rc;
|
||||
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
OSMO_ASSERT(hnb);
|
||||
/* we store a reference to the HomeNodeB in the msg->dest for the
|
||||
* benefit of various downstream processing functions */
|
||||
msg->dst = hnb;
|
||||
|
||||
rc = osmo_stream_srv_recv(conn, msg);
|
||||
/* Notification received */
|
||||
if (msgb_sctp_msg_flags(msg) & OSMO_STREAM_SCTP_MSG_FLAGS_NOTIFICATION) {
|
||||
union sctp_notification *notif = (union sctp_notification *)msgb_data(msg);
|
||||
rc = 0;
|
||||
switch (notif->sn_header.sn_type) {
|
||||
case SCTP_ASSOC_CHANGE:
|
||||
switch (notif->sn_assoc_change.sac_state) {
|
||||
case SCTP_COMM_LOST:
|
||||
LOGHNB(hnb, DMAIN, LOGL_NOTICE,
|
||||
"sctp_recvmsg(%s) = SCTP_COMM_LOST, closing conn\n",
|
||||
osmo_sock_get_name2(ofd->fd));
|
||||
osmo_stream_srv_destroy(conn);
|
||||
rc = -EBADF;
|
||||
break;
|
||||
case SCTP_RESTART:
|
||||
LOGHNB(hnb, DMAIN, LOGL_NOTICE, "HNB SCTP conn RESTARTed, marking as HNBAP-unregistered\n");
|
||||
hnb->hnb_registered = false;
|
||||
hnb_context_release_ue_state(hnb);
|
||||
/* The tx queue may be quite full after an SCTP RESTART: (SYS#6113)
|
||||
* The link may have been flaky (a possible reason for the peer restarting the conn) and
|
||||
* hence the kernel socket Tx queue may be full (no ACKs coming back) and our own userspace
|
||||
* queue may contain plenty of oldish messages to be sent. Since the HNB will re-register after
|
||||
* this, we simply drop all those old messages: */
|
||||
osmo_stream_srv_clear_tx_queue(conn);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SCTP_SHUTDOWN_EVENT:
|
||||
LOGHNB(hnb, DMAIN, LOGL_NOTICE,
|
||||
"sctp_recvmsg(%s) = SCTP_SHUTDOWN_EVENT, closing conn\n",
|
||||
osmo_sock_get_name2(ofd->fd));
|
||||
osmo_stream_srv_destroy(conn);
|
||||
rc = -EBADF;
|
||||
break;
|
||||
}
|
||||
goto out;
|
||||
} else if (rc == -EAGAIN) {
|
||||
/* Older versions of osmo_stream_srv_recv() not supporting
|
||||
* msgb_sctp_msg_flags() may still return -EAGAIN when an sctp
|
||||
* notification is received. */
|
||||
rc = 0;
|
||||
goto out;
|
||||
} else if (rc < 0) {
|
||||
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Error during sctp_recvmsg(%s)\n",
|
||||
osmo_sock_get_name2(ofd->fd));
|
||||
osmo_stream_srv_destroy(conn);
|
||||
rc = -EBADF;
|
||||
goto out;
|
||||
} else if (rc == 0) {
|
||||
LOGHNB(hnb, DMAIN, LOGL_NOTICE, "Connection closed sctp_recvmsg(%s) = 0\n",
|
||||
osmo_sock_get_name2(ofd->fd));
|
||||
osmo_stream_srv_destroy(conn);
|
||||
rc = -EBADF;
|
||||
goto out;
|
||||
} else {
|
||||
msgb_put(msg, rc);
|
||||
}
|
||||
|
||||
switch (msgb_sctp_ppid(msg)) {
|
||||
case IUH_PPI_HNBAP:
|
||||
hnb->hnbap_stream = msgb_sctp_stream(msg);
|
||||
rc = hnbgw_hnbap_rx(hnb, msg);
|
||||
break;
|
||||
case IUH_PPI_RUA:
|
||||
if (!hnb->hnb_registered) {
|
||||
LOGHNB(hnb, DMAIN, LOGL_NOTICE, "Discarding RUA as HNB is not registered\n");
|
||||
goto out;
|
||||
}
|
||||
hnb->rua_stream = msgb_sctp_stream(msg);
|
||||
rc = hnbgw_rua_rx(hnb, msg);
|
||||
break;
|
||||
case IUH_PPI_SABP:
|
||||
case IUH_PPI_RNA:
|
||||
case IUH_PPI_PUA:
|
||||
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unimplemented SCTP PPID=%lu received\n", msgb_sctp_ppid(msg));
|
||||
rc = 0;
|
||||
break;
|
||||
default:
|
||||
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unknown SCTP PPID=%lu received\n", msgb_sctp_ppid(msg));
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int hnb_closed_cb(struct osmo_stream_srv *conn)
|
||||
{
|
||||
struct hnb_context *hnb = osmo_stream_srv_get_data(conn);
|
||||
if (!hnb)
|
||||
return 0; /* hnb_context is being freed, nothing do be done */
|
||||
|
||||
/* hnb: conn became broken, let's release the associated hnb.
|
||||
* conn object is being freed after closed_cb(), so unassign it from hnb
|
||||
* if available to avoid it freeing it again: */
|
||||
hnb->conn = NULL;
|
||||
hnb_context_release(hnb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! call-back when the listen FD has something to read */
|
||||
int hnbgw_rua_accept_cb(struct osmo_stream_srv_link *srv, int fd)
|
||||
{
|
||||
struct hnb_context *ctx;
|
||||
|
||||
LOGP(DMAIN, LOGL_INFO, "New HNB SCTP connection %s\n",
|
||||
osmo_sock_get_name2(fd));
|
||||
|
||||
ctx = hnb_context_alloc(srv, fd);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
CTRL_CMD_DEFINE_RO(hnb_info, "info");
|
||||
static int get_hnb_info(struct ctrl_cmd *cmd, void *data)
|
||||
{
|
||||
struct hnb_context *hnb = data;
|
||||
|
||||
cmd->reply = talloc_strdup(cmd, hnb->identity_info);
|
||||
|
||||
return CTRL_CMD_REPLY;
|
||||
}
|
||||
|
||||
CTRL_CMD_DEFINE_RO(hnbs, "num-hnb");
|
||||
static int get_hnbs(struct ctrl_cmd *cmd, void *data)
|
||||
{
|
||||
cmd->reply = talloc_asprintf(cmd, "%u", llist_count(&g_hnbgw->hnb_list));
|
||||
|
||||
return CTRL_CMD_REPLY;
|
||||
}
|
||||
|
||||
int hnb_ctrl_cmds_install(void)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_hnbs);
|
||||
rc |= ctrl_cmd_install(CTRL_NODE_HNB, &cmd_hnb_info);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int hnb_ctrl_node_lookup(void *data, vector vline, int *node_type, void **node_data, int *i)
|
||||
{
|
||||
const char *token = vector_slot(vline, *i);
|
||||
struct hnb_context *hnb;
|
||||
long num;
|
||||
|
||||
switch (*node_type) {
|
||||
case CTRL_NODE_ROOT:
|
||||
if (strcmp(token, "hnb") != 0)
|
||||
return 0;
|
||||
|
||||
(*i)++;
|
||||
|
||||
if (!ctrl_parse_get_num(vline, *i, &num))
|
||||
return -ERANGE;
|
||||
|
||||
hnb = hnb_context_by_id(num);
|
||||
if (!hnb)
|
||||
return -ENODEV;
|
||||
|
||||
*node_data = hnb;
|
||||
*node_type = CTRL_NODE_HNB;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
403
src/osmo-hnbgw/hnb_persistent.c
Normal file
403
src/osmo-hnbgw/hnb_persistent.c
Normal file
@@ -0,0 +1,403 @@
|
||||
/* HNB persistent related code */
|
||||
|
||||
/* (C) 2015,2024 by Harald Welte <laforge@gnumonks.org>
|
||||
* (C) 2016-2025 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/sctp.h>
|
||||
|
||||
#include <osmocom/core/stats.h>
|
||||
#include <osmocom/core/rate_ctr.h>
|
||||
#include <osmocom/core/stat_item.h>
|
||||
#include <osmocom/core/jhash.h>
|
||||
|
||||
#include <osmocom/gsm/gsm23236.h>
|
||||
|
||||
#include <osmocom/netif/stream.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnb.h>
|
||||
#include <osmocom/hnbgw/hnb_persistent.h>
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/tdefs.h>
|
||||
|
||||
/***********************************************************************
|
||||
* HNB Persistent Data
|
||||
***********************************************************************/
|
||||
|
||||
const struct rate_ctr_desc hnb_ctr_description[] = {
|
||||
[HNB_CTR_IUH_ESTABLISHED] = {
|
||||
"iuh:established", "Number of times Iuh link was established" },
|
||||
|
||||
[HNB_CTR_RANAP_PS_ERR_IND_UL] = {
|
||||
"ranap:ps:error_ind:ul", "Received ERROR Indications in Uplink (PS Domain)" },
|
||||
[HNB_CTR_RANAP_CS_ERR_IND_UL] = {
|
||||
"ranap:cs:error_ind:ul", "Received ERROR Indications in Uplink (PS Domain)" },
|
||||
|
||||
[HNB_CTR_RANAP_PS_RESET_REQ_UL] = {
|
||||
"ranap:ps:reset_req:ul", "Received RESET Requests in Uplink (PS Domain)" },
|
||||
[HNB_CTR_RANAP_CS_RESET_REQ_UL] = {
|
||||
"ranap:cs:reset_req:ul", "Received RESET Requests in Uplink (CS Domain)" },
|
||||
|
||||
|
||||
[HNB_CTR_RANAP_PS_RAB_ACT_REQ] = {
|
||||
"ranap:ps:rab_act:req", "PS RAB Activations requested" },
|
||||
[HNB_CTR_RANAP_CS_RAB_ACT_REQ] = {
|
||||
"ranap:cs:rab_act:req", "CS RAB Activations requested" },
|
||||
[HNB_CTR_RANAP_PS_RAB_ACT_REQ_UNEXP] = {
|
||||
"ranap:ps:rab_act:req_unexp", "PS RAB Activations requested in unexpected state" },
|
||||
[HNB_CTR_RANAP_CS_RAB_ACT_REQ_UNEXP] = {
|
||||
"ranap:cs:rab_act:req_unexp", "CS RAB Activations requested in unexpected state" },
|
||||
|
||||
[HNB_CTR_RANAP_PS_RAB_ACT_CNF] = {
|
||||
"ranap:ps:rab_act:cnf", "PS RAB Activations confirmed" },
|
||||
[HNB_CTR_RANAP_CS_RAB_ACT_CNF] = {
|
||||
"ranap:cs:rab_act:cnf", "CS RAB Activations confirmed" },
|
||||
[HNB_CTR_RANAP_PS_RAB_ACT_CNF_UNEXP] = {
|
||||
"ranap:ps:rab_act:cnf_unexp", "PS RAB Activations confirmed in unexpected state" },
|
||||
[HNB_CTR_RANAP_CS_RAB_ACT_CNF_UNEXP] = {
|
||||
"ranap:cs:rab_act:cnf_unexp", "CS RAB Activations confirmed in unexpected state" },
|
||||
|
||||
[HNB_CTR_RANAP_PS_RAB_ACT_FAIL] = {
|
||||
"ranap:ps:rab_act:fail", "PS RAB Activations failed" },
|
||||
[HNB_CTR_RANAP_CS_RAB_ACT_FAIL] = {
|
||||
"ranap:cs:rab_act:fail", "CS RAB Activations failed" },
|
||||
[HNB_CTR_RANAP_PS_RAB_ACT_FAIL_UNEXP] = {
|
||||
"ranap:ps:rab_act:fail_unexp", "PS RAB Activations failed in unexpected state" },
|
||||
[HNB_CTR_RANAP_CS_RAB_ACT_FAIL_UNEXP] = {
|
||||
"ranap:cs:rab_act:fail_unexp", "CS RAB Activations failed in unexpected state" },
|
||||
|
||||
|
||||
[HNB_CTR_RANAP_PS_RAB_MOD_REQ] = {
|
||||
"ranap:ps:rab_mod:req", "PS RAB Modifications requested" },
|
||||
[HNB_CTR_RANAP_CS_RAB_MOD_REQ] = {
|
||||
"ranap:cs:rab_mod:req", "CS RAB Modifications requested" },
|
||||
|
||||
[HNB_CTR_RANAP_PS_RAB_MOD_CNF] = {
|
||||
"ranap:ps:rab_mod:cnf", "PS RAB Modifications confirmed" },
|
||||
[HNB_CTR_RANAP_CS_RAB_MOD_CNF] = {
|
||||
"ranap:cs:rab_mod:cnf", "CS RAB Modifications confirmed" },
|
||||
|
||||
[HNB_CTR_RANAP_PS_RAB_MOD_FAIL] = {
|
||||
"ranap:ps:rab_mod:fail", "PS RAB Modifications failed" },
|
||||
[HNB_CTR_RANAP_CS_RAB_MOD_FAIL] = {
|
||||
"ranap:cs:rab_mod:fail", "CS RAB Modifications failed" },
|
||||
|
||||
[HNB_CTR_RANAP_PS_RAB_REL_REQ] = {
|
||||
"ranap:ps:rab_rel:req:normal", "PS RAB Release requested (by CN), normal" },
|
||||
[HNB_CTR_RANAP_CS_RAB_REL_REQ] = {
|
||||
"ranap:cs:rab_rel:req:normal", "CS RAB Release requested (by CN), normal" },
|
||||
[HNB_CTR_RANAP_PS_RAB_REL_REQ_ABNORMAL] = {
|
||||
"ranap:ps:rab_rel:req:abnormal", "PS RAB Release requested (by CN), abnormal" },
|
||||
[HNB_CTR_RANAP_CS_RAB_REL_REQ_ABNORMAL] = {
|
||||
"ranap:cs:rab_rel:req:abnormal", "CS RAB Release requested (by CN), abnormal" },
|
||||
[HNB_CTR_RANAP_PS_RAB_REL_REQ_UNEXP] = {
|
||||
"ranap:ps:rab_rel:req:unexp", "PS RAB Release requested (by CN) in unexpected state" },
|
||||
[HNB_CTR_RANAP_CS_RAB_REL_REQ_UNEXP] = {
|
||||
"ranap:cs:rab_rel:req:unexp", "CS RAB Release requested (by CN) in unexpected state" },
|
||||
|
||||
[HNB_CTR_RANAP_PS_RAB_REL_CNF] = {
|
||||
"ranap:ps:rab_rel:cnf", "PS RAB Release confirmed" },
|
||||
[HNB_CTR_RANAP_CS_RAB_REL_CNF] = {
|
||||
"ranap:cs:rab_rel:cnf", "CS RAB Release confirmed" },
|
||||
[HNB_CTR_RANAP_PS_RAB_REL_CNF_UNEXP] = {
|
||||
"ranap:ps:rab_rel:cnf_unexp", "PS RAB Release confirmed in unexpected state" },
|
||||
[HNB_CTR_RANAP_CS_RAB_REL_CNF_UNEXP] = {
|
||||
"ranap:cs:rab_rel:cnf_unexp", "CS RAB Release confirmed in unexpected state" },
|
||||
|
||||
[HNB_CTR_RANAP_PS_RAB_REL_FAIL] = {
|
||||
"ranap:ps:rab_rel:fail", "PS RAB Release failed" },
|
||||
[HNB_CTR_RANAP_CS_RAB_REL_FAIL] = {
|
||||
"ranap:cs:rab_rel:fail", "CS RAB Release failed" },
|
||||
[HNB_CTR_RANAP_PS_RAB_REL_FAIL_UNEXP] = {
|
||||
"ranap:ps:rab_rel:fail_unexp", "PS RAB Release failed in unexpected state" },
|
||||
[HNB_CTR_RANAP_CS_RAB_REL_FAIL_UNEXP] = {
|
||||
"ranap:cs:rab_rel:fail_unexp", "CS RAB Release failed in unexpected state" },
|
||||
|
||||
[HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT] = {
|
||||
"ranap:ps:rab_rel:implicit:normal", "PS RAB Release implicit (during Iu Release), normal" },
|
||||
[HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT] = {
|
||||
"ranap:cs:rab_rel:implicit:normal", "CS RAB Release implicit (during Iu Release), normal" },
|
||||
[HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT_ABNORMAL] = {
|
||||
"ranap:ps:rab_rel:implicit:abnormal", "PS RAB Release implicit (during Iu Release), abnormal" },
|
||||
[HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT_ABNORMAL] = {
|
||||
"ranap:cs:rab_rel:implicit:abnormal", "CS RAB Release implicit (during Iu Release), abnormal" },
|
||||
|
||||
[HNB_CTR_RUA_ERR_IND] = {
|
||||
"rua:error_ind", "Received RUA Error Indications" },
|
||||
|
||||
[HNB_CTR_RUA_PS_CONNECT_UL] = {
|
||||
"rua:ps:connect:ul", "Received RUA Connect requests (PS Domain)" },
|
||||
[HNB_CTR_RUA_CS_CONNECT_UL] = {
|
||||
"rua:cs:connect:ul", "Received RUA Connect requests (CS Domain)" },
|
||||
|
||||
[HNB_CTR_RUA_PS_DISCONNECT_UL] = {
|
||||
"rua:ps:disconnect:ul", "Received RUA Disconnect requests in uplink (PS Domain)" },
|
||||
[HNB_CTR_RUA_CS_DISCONNECT_UL] = {
|
||||
"rua:cs:disconnect:ul", "Received RUA Disconnect requests in uplink (CS Domain)" },
|
||||
[HNB_CTR_RUA_PS_DISCONNECT_DL] = {
|
||||
"rua:ps:disconnect:dl", "Transmitted RUA Disconnect requests in downlink (PS Domain)" },
|
||||
[HNB_CTR_RUA_CS_DISCONNECT_DL] = {
|
||||
"rua:cs:disconnect:dl", "Transmitted RUA Disconnect requests in downlink (CS Domain)" },
|
||||
|
||||
[HNB_CTR_RUA_PS_DT_UL] = {
|
||||
"rua:ps:direct_transfer:ul", "Received RUA DirectTransfer in uplink (PS Domain)" },
|
||||
[HNB_CTR_RUA_CS_DT_UL] = {
|
||||
"rua:cs:direct_transfer:ul", "Received RUA DirectTransfer in uplink (CS Domain)" },
|
||||
[HNB_CTR_RUA_PS_DT_DL] = {
|
||||
"rua:ps:direct_transfer:dl", "Transmitted RUA DirectTransfer in downlink (PS Domain)" },
|
||||
[HNB_CTR_RUA_CS_DT_DL] = {
|
||||
"rua:cs:direct_transfer:dl", "Transmitted RUA DirectTransfer in downlink (CS Domain)" },
|
||||
|
||||
[HNB_CTR_RUA_UDT_UL] = {
|
||||
"rua:unit_data:ul", "Received RUA UnitData (UDT) in uplink" },
|
||||
[HNB_CTR_RUA_UDT_DL] = {
|
||||
"rua:unit_data:dl", "Transmitted RUA UnitData (UDT) in downlink" },
|
||||
|
||||
[HNB_CTR_PS_PAGING_ATTEMPTED] = {
|
||||
"paging:ps:attempted", "Transmitted PS Paging requests" },
|
||||
[HNB_CTR_CS_PAGING_ATTEMPTED] = {
|
||||
"paging:cs:attempted", "Transmitted CS Paging requests" },
|
||||
|
||||
[HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL] = {
|
||||
"rab:cs:active_milliseconds:total", "Cumulative number of milliseconds of CS RAB activity" },
|
||||
|
||||
[HNB_CTR_DTAP_CS_LU_REQ] = { "dtap:cs:location_update:req", "CS Location Update Requests" },
|
||||
[HNB_CTR_DTAP_CS_LU_ACC] = { "dtap:cs:location_update:accept", "CS Location Update Accepts" },
|
||||
[HNB_CTR_DTAP_CS_LU_REJ] = { "dtap:cs:location_update:reject", "CS Location Update Rejects" },
|
||||
|
||||
[HNB_CTR_DTAP_PS_ATT_REQ] = { "dtap:ps:attach:req", "PS Attach Requests" },
|
||||
[HNB_CTR_DTAP_PS_ATT_ACK] = { "dtap:ps:attach:accept", "PS Attach Accepts" },
|
||||
[HNB_CTR_DTAP_PS_ATT_REJ] = { "dtap:ps:attach:reject", "PS Attach Rejects" },
|
||||
|
||||
[HNB_CTR_DTAP_PS_RAU_REQ] = { "dtap:ps:routing_area_update:req", "PS Routing Area Update Requests" },
|
||||
[HNB_CTR_DTAP_PS_RAU_ACK] = { "dtap:ps:routing_area_update:accept", "PS Routing Area Update Accepts" },
|
||||
[HNB_CTR_DTAP_PS_RAU_REJ] = { "dtap:ps:routing_area_update:reject", "PS Routing Area Update Rejects" },
|
||||
|
||||
[HNB_CTR_GTPU_PACKETS_UL] = {
|
||||
"gtpu:packets:ul",
|
||||
"Count of GTP-U packets received from the HNB",
|
||||
},
|
||||
[HNB_CTR_GTPU_TOTAL_BYTES_UL] = {
|
||||
"gtpu:total_bytes:ul",
|
||||
"Count of total GTP-U bytes received from the HNB, including the GTP-U/UDP/IP headers",
|
||||
},
|
||||
[HNB_CTR_GTPU_UE_BYTES_UL] = {
|
||||
"gtpu:ue_bytes:ul",
|
||||
"Assuming an IP header length of 20 bytes, GTP-U bytes received from the HNB, excluding the GTP-U/UDP/IP headers",
|
||||
},
|
||||
[HNB_CTR_GTPU_PACKETS_DL] = {
|
||||
"gtpu:packets:dl",
|
||||
"Count of GTP-U packets sent to the HNB",
|
||||
},
|
||||
[HNB_CTR_GTPU_TOTAL_BYTES_DL] = {
|
||||
"gtpu:total_bytes:dl",
|
||||
"Count of total GTP-U bytes sent to the HNB, including the GTP-U/UDP/IP headers",
|
||||
},
|
||||
[HNB_CTR_GTPU_UE_BYTES_DL] = {
|
||||
"gtpu:ue_bytes:dl",
|
||||
"Assuming an IP header length of 20 bytes, GTP-U bytes sent to the HNB, excluding the GTP-U/UDP/IP headers",
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
const struct rate_ctr_group_desc hnb_ctrg_desc = {
|
||||
"hnb",
|
||||
"hNodeB",
|
||||
OSMO_STATS_CLASS_GLOBAL,
|
||||
ARRAY_SIZE(hnb_ctr_description),
|
||||
hnb_ctr_description,
|
||||
};
|
||||
|
||||
const struct osmo_stat_item_desc hnb_stat_desc[] = {
|
||||
[HNB_STAT_UPTIME_SECONDS] = { "uptime:seconds", "Seconds of uptime", "s", 60, 0 },
|
||||
};
|
||||
|
||||
const struct osmo_stat_item_group_desc hnb_statg_desc = {
|
||||
.group_name_prefix = "hnb",
|
||||
.group_description = "hNodeB",
|
||||
.class_id = OSMO_STATS_CLASS_GLOBAL,
|
||||
.num_items = ARRAY_SIZE(hnb_stat_desc),
|
||||
.item_desc = hnb_stat_desc,
|
||||
};
|
||||
|
||||
static void hnb_persistent_disconnected_timeout_cb(void *data)
|
||||
{
|
||||
hnb_persistent_free(data);
|
||||
}
|
||||
|
||||
static void hnb_persistent_disconnected_timeout_schedule(struct hnb_persistent *hnbp)
|
||||
{
|
||||
unsigned long period_s = osmo_tdef_get(hnbgw_T_defs, -35, OSMO_TDEF_S, 60*60*24*7);
|
||||
if (period_s < 1) {
|
||||
LOG_HNBP(hnbp, LOGL_INFO,
|
||||
"timer X35 is zero, not setting a disconnected timeout for this hnb-persistent instance.\n");
|
||||
return;
|
||||
}
|
||||
/* It is fine if the timer is already active, osmo_timer_del() is done implicitly by the osmo_timer API. */
|
||||
osmo_timer_setup(&hnbp->disconnected_timeout, hnb_persistent_disconnected_timeout_cb, hnbp);
|
||||
osmo_timer_schedule(&hnbp->disconnected_timeout, period_s, 0);
|
||||
}
|
||||
|
||||
struct hnb_persistent *hnb_persistent_alloc(const struct umts_cell_id *id)
|
||||
{
|
||||
struct hnb_persistent *hnbp = talloc_zero(g_hnbgw, struct hnb_persistent);
|
||||
if (!hnbp)
|
||||
return NULL;
|
||||
|
||||
hnbp->id = *id;
|
||||
hnbp->id_str = talloc_strdup(hnbp, umts_cell_id_to_str(id));
|
||||
hnbp->ctrs = rate_ctr_group_alloc(hnbp, &hnb_ctrg_desc, 0);
|
||||
if (!hnbp->ctrs)
|
||||
goto out_free;
|
||||
rate_ctr_group_set_name(hnbp->ctrs, hnbp->id_str);
|
||||
hnbp->statg = osmo_stat_item_group_alloc(hnbp, &hnb_statg_desc, 0);
|
||||
if (!hnbp->statg)
|
||||
goto out_free_ctrs;
|
||||
osmo_stat_item_group_set_name(hnbp->statg, hnbp->id_str);
|
||||
|
||||
llist_add(&hnbp->list, &g_hnbgw->hnb_persistent_list);
|
||||
hash_add(g_hnbgw->hnb_persistent_by_id, &hnbp->node_by_id, umts_cell_id_hash(&hnbp->id));
|
||||
|
||||
if (g_hnbgw->nft_kpi.active)
|
||||
nft_kpi_hnb_persistent_add(hnbp);
|
||||
|
||||
/* Normally the disconnected timer runs only when the hNodeB is not currently connected on Iuh. This here is paranoia:
|
||||
* In case we have to HNBAP HNB Register Reject, the disconnected timer should be active on this unused hnbp.
|
||||
* On success, hnb_persistent_registered() will stop the disconnected timer directly after this. */
|
||||
hnb_persistent_disconnected_timeout_schedule(hnbp);
|
||||
|
||||
return hnbp;
|
||||
|
||||
out_free_ctrs:
|
||||
rate_ctr_group_free(hnbp->ctrs);
|
||||
out_free:
|
||||
talloc_free(hnbp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct hnb_persistent *hnb_persistent_find_by_id(const struct umts_cell_id *id)
|
||||
{
|
||||
struct hnb_persistent *hnbp;
|
||||
uint32_t id_hash = umts_cell_id_hash(id);
|
||||
hash_for_each_possible(g_hnbgw->hnb_persistent_by_id, hnbp, node_by_id, id_hash) {
|
||||
if (umts_cell_id_equal(&hnbp->id, id))
|
||||
return hnbp;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Read the peer's remote IP address from the Iuh conn's fd, and set up GTP-U counters for that remote address. */
|
||||
static void hnb_persistent_update_remote_addr(struct hnb_persistent *hnbp)
|
||||
{
|
||||
socklen_t socklen;
|
||||
struct osmo_sockaddr osa;
|
||||
struct osmo_sockaddr_str remote_str;
|
||||
int fd;
|
||||
|
||||
fd = osmo_stream_srv_get_fd(hnbp->ctx->conn);
|
||||
if (fd < 0) {
|
||||
LOG_HNBP(hnbp, LOGL_ERROR, "no active socket fd, cannot set up traffic counters\n");
|
||||
return;
|
||||
}
|
||||
|
||||
socklen = sizeof(struct osmo_sockaddr);
|
||||
if (getpeername(fd, &osa.u.sa, &socklen)) {
|
||||
LOG_HNBP(hnbp, LOGL_ERROR, "cannot read remote address, cannot set up traffic counters\n");
|
||||
return;
|
||||
}
|
||||
if (osmo_sockaddr_str_from_osa(&remote_str, &osa)) {
|
||||
LOG_HNBP(hnbp, LOGL_ERROR, "cannot parse remote address, cannot set up traffic counters\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* We got the remote address from the Iuh link (RUA), and now we are blatantly assuming that the hNodeB has its
|
||||
* GTP endpoint on the same IP address, just with UDP port 2152 (the fixed GTP port as per 3GPP spec). */
|
||||
remote_str.port = 2152;
|
||||
|
||||
if (nft_kpi_hnb_start(hnbp, &remote_str))
|
||||
LOG_HNBP(hnbp, LOGL_ERROR, "failed to set up traffic counters\n");
|
||||
}
|
||||
|
||||
/* Whenever HNBAP registers a HNB, hnbgw_hnbap.c calls this function to let the hnb_persistent update its state to the
|
||||
* (new) remote address being active. When calling this function, a hnbp->ctx should be present, with an active
|
||||
* osmo_stream_srv conn. */
|
||||
void hnb_persistent_registered(struct hnb_persistent *hnbp)
|
||||
{
|
||||
if (!hnbp->ctx) {
|
||||
LOG_HNBP(hnbp, LOGL_ERROR, "hnb_persistent_registered() invoked, but there is no hnb_ctx\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* The hNodeB is now connected, i.e. not disconnected. */
|
||||
osmo_timer_del(&hnbp->disconnected_timeout);
|
||||
|
||||
/* start counting traffic */
|
||||
if (g_hnbgw->nft_kpi.active)
|
||||
hnb_persistent_update_remote_addr(hnbp);
|
||||
}
|
||||
|
||||
/* Whenever a HNB is regarded as no longer registered (HNBAP HNB De-Register, or the Iuh link drops), this function is
|
||||
* called to to let the hnb_persistent update its state to the hNodeB being disconnected. Clear the ctx->persistent and
|
||||
* hnbp->ctx relations; do not delete the hnb_persistent instance. */
|
||||
void hnb_persistent_deregistered(struct hnb_persistent *hnbp)
|
||||
{
|
||||
/* clear out cross references of hnb_context and hnb_persistent */
|
||||
if (hnbp->ctx) {
|
||||
if (hnbp->ctx->persistent == hnbp)
|
||||
hnbp->ctx->persistent = NULL;
|
||||
hnbp->ctx = NULL;
|
||||
}
|
||||
|
||||
/* stop counting traffic */
|
||||
nft_kpi_hnb_stop(hnbp);
|
||||
|
||||
/* The hNodeB is now disconnected. Clear out hnb_persistent when the disconnected timeout has passed. */
|
||||
hnb_persistent_disconnected_timeout_schedule(hnbp);
|
||||
}
|
||||
|
||||
void hnb_persistent_free(struct hnb_persistent *hnbp)
|
||||
{
|
||||
/* FIXME: check if in use? */
|
||||
osmo_timer_del(&hnbp->disconnected_timeout);
|
||||
nft_kpi_hnb_stop(hnbp);
|
||||
nft_kpi_hnb_persistent_remove(hnbp);
|
||||
osmo_stat_item_group_free(hnbp->statg);
|
||||
rate_ctr_group_free(hnbp->ctrs);
|
||||
llist_del(&hnbp->list);
|
||||
hash_del(&hnbp->node_by_id);
|
||||
talloc_free(hnbp);
|
||||
}
|
||||
|
||||
/* return the amount of time the HNB is up (hnbp->ctx != NULL) or down (hnbp->ctx == NULL) */
|
||||
unsigned long long hnbp_get_updowntime(const struct hnb_persistent *hnbp)
|
||||
{
|
||||
struct timespec tp;
|
||||
|
||||
if (!hnbp->updowntime)
|
||||
return 0;
|
||||
|
||||
if (osmo_clock_gettime(CLOCK_MONOTONIC, &tp) != 0)
|
||||
return 0;
|
||||
|
||||
return difftime(tp.tv_sec, hnbp->updowntime);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/* main application for hnb-gw part of osmo-iuh */
|
||||
/* kitchen sink for OsmoHNBGW implementation */
|
||||
|
||||
/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
|
||||
* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
/* (C) 2015,2024 by Harald Welte <laforge@gnumonks.org>
|
||||
* (C) 2016-2025 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -19,339 +19,138 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/sctp.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <osmocom/core/application.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/write_queue.h>
|
||||
#include <osmocom/ctrl/control_if.h>
|
||||
#include <osmocom/ctrl/control_cmd.h>
|
||||
#include <osmocom/ctrl/control_vty.h>
|
||||
#include <osmocom/ctrl/ports.h>
|
||||
#include <osmocom/vty/telnet_interface.h>
|
||||
#include <osmocom/vty/logging.h>
|
||||
#include <osmocom/vty/command.h>
|
||||
#include <osmocom/vty/ports.h>
|
||||
#include <osmocom/core/stats.h>
|
||||
#include <osmocom/core/rate_ctr.h>
|
||||
#include <osmocom/core/stat_item.h>
|
||||
#include <osmocom/core/jhash.h>
|
||||
|
||||
#include <osmocom/vty/vty.h>
|
||||
|
||||
#include <osmocom/gsm/gsm23236.h>
|
||||
|
||||
#include <osmocom/netif/stream.h>
|
||||
|
||||
#include <osmocom/ranap/ranap_common.h>
|
||||
|
||||
#include <osmocom/sigtran/protocol/m3ua.h>
|
||||
#include <osmocom/sigtran/sccp_sap.h>
|
||||
#include "config.h"
|
||||
#if ENABLE_PFCP
|
||||
#include <osmocom/pfcp/pfcp_proto.h>
|
||||
#endif
|
||||
|
||||
#include <osmocom/hnbgw/hnb.h>
|
||||
#include <osmocom/hnbgw/hnb_persistent.h>
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/hnbgw_hnbap.h>
|
||||
#include <osmocom/hnbgw/hnbgw_rua.h>
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/mgw_fsm.h>
|
||||
#include <osmocom/hnbgw/tdefs.h>
|
||||
|
||||
static const char * const osmo_hnbgw_copyright =
|
||||
"OsmoHNBGW - Osmocom Home Node B Gateway implementation\r\n"
|
||||
"Copyright (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>\r\n"
|
||||
"Contributions by Daniel Willmann, Harald Welte, Neels Hofmeyr\r\n"
|
||||
"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
|
||||
"This is free software: you are free to change and redistribute it.\r\n"
|
||||
"There is NO WARRANTY, to the extent permitted by law.\r\n";
|
||||
struct hnbgw *g_hnbgw = NULL;
|
||||
|
||||
static void *tall_hnb_ctx;
|
||||
const struct value_string ranap_domain_names[] = {
|
||||
{ DOMAIN_CS, "CS" },
|
||||
{ DOMAIN_PS, "PS" },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct hnb_gw *g_hnb_gw;
|
||||
|
||||
static struct hnb_gw *hnb_gw_create(void *ctx)
|
||||
/* timer call-back: Update the HNB_STAT_UPTIME_SECONDS stat item of each hnb_persistent */
|
||||
static void hnbgw_store_hnb_uptime(void *data)
|
||||
{
|
||||
struct hnb_gw *gw = talloc_zero(ctx, struct hnb_gw);
|
||||
struct hnb_persistent *hnbp;
|
||||
|
||||
/* strdup so we can easily talloc_free in the VTY code */
|
||||
gw->config.iuh_local_ip = talloc_strdup(gw, HNBGW_LOCAL_IP_DEFAULT);
|
||||
gw->config.iuh_local_port = IUH_DEFAULT_SCTP_PORT;
|
||||
gw->config.log_prefix_hnb_id = true;
|
||||
llist_for_each_entry(hnbp, &g_hnbgw->hnb_persistent_list, list) {
|
||||
HNBP_STAT_SET(hnbp, HNB_STAT_UPTIME_SECONDS, hnbp->ctx != NULL ? hnbp_get_updowntime(hnbp) : 0);
|
||||
}
|
||||
|
||||
gw->next_ue_ctx_id = 23;
|
||||
INIT_LLIST_HEAD(&gw->hnb_list);
|
||||
INIT_LLIST_HEAD(&gw->ue_list);
|
||||
|
||||
context_map_init(gw);
|
||||
|
||||
return gw;
|
||||
osmo_timer_schedule(&g_hnbgw->store_uptime_timer, STORE_UPTIME_INTERVAL, 0);
|
||||
}
|
||||
|
||||
struct hnb_context *hnb_context_by_id(struct hnb_gw *gw, uint32_t cid)
|
||||
static void hnbgw_store_hnb_rab_durations(void *data)
|
||||
{
|
||||
struct hnb_context *hnb;
|
||||
|
||||
llist_for_each_entry(hnb, &gw->hnb_list, list) {
|
||||
if (hnb->id.cid == cid)
|
||||
return hnb;
|
||||
llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) {
|
||||
if (!hnb->persistent)
|
||||
continue;
|
||||
hnb_store_rab_durations(hnb);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
/* Keep this timer ticking */
|
||||
osmo_timer_schedule(&g_hnbgw->hnb_store_rab_durations_timer, HNB_STORE_RAB_DURATIONS_INTERVAL, 0);
|
||||
}
|
||||
|
||||
struct hnb_context *hnb_context_by_identity_info(struct hnb_gw *gw, const char *identity_info)
|
||||
/***********************************************************************
|
||||
* UE Context
|
||||
***********************************************************************/
|
||||
|
||||
uint32_t get_next_ue_ctx_id(void)
|
||||
{
|
||||
struct hnb_context *hnb;
|
||||
|
||||
llist_for_each_entry(hnb, &gw->hnb_list, list) {
|
||||
if (strcmp(identity_info, hnb->identity_info) == 0)
|
||||
return hnb;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return g_hnbgw->next_ue_ctx_id++;
|
||||
}
|
||||
|
||||
|
||||
unsigned hnb_contexts(const struct hnb_gw *gw)
|
||||
int hnbgw_mgw_setup(void)
|
||||
{
|
||||
unsigned num_ctx = 0;
|
||||
struct hnb_context *hnb;
|
||||
struct mgcp_client *mgcp_client_single;
|
||||
unsigned int pool_members_initalized;
|
||||
|
||||
llist_for_each_entry(hnb, &gw->hnb_list, list) {
|
||||
num_ctx++;
|
||||
}
|
||||
|
||||
return num_ctx;
|
||||
}
|
||||
|
||||
struct ue_context *ue_context_by_id(struct hnb_gw *gw, uint32_t id)
|
||||
{
|
||||
struct ue_context *ue;
|
||||
|
||||
llist_for_each_entry(ue, &gw->ue_list, list) {
|
||||
if (ue->context_id == id)
|
||||
return ue;
|
||||
}
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
struct ue_context *ue_context_by_imsi(struct hnb_gw *gw, const char *imsi)
|
||||
{
|
||||
struct ue_context *ue;
|
||||
|
||||
llist_for_each_entry(ue, &gw->ue_list, list) {
|
||||
if (!strcmp(ue->imsi, imsi))
|
||||
return ue;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct ue_context *ue_context_by_tmsi(struct hnb_gw *gw, uint32_t tmsi)
|
||||
{
|
||||
struct ue_context *ue;
|
||||
|
||||
llist_for_each_entry(ue, &gw->ue_list, list) {
|
||||
if (ue->tmsi == tmsi)
|
||||
return ue;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ue_context_free_by_hnb(struct hnb_gw *gw, const struct hnb_context *hnb)
|
||||
{
|
||||
struct ue_context *ue, *tmp;
|
||||
|
||||
llist_for_each_entry_safe(ue, tmp, &gw->ue_list, list) {
|
||||
if (ue->hnb == hnb)
|
||||
ue_context_free(ue);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t get_next_ue_ctx_id(struct hnb_gw *gw)
|
||||
{
|
||||
uint32_t id;
|
||||
|
||||
do {
|
||||
id = gw->next_ue_ctx_id++;
|
||||
} while (ue_context_by_id(gw, id));
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
struct ue_context *ue_context_alloc(struct hnb_context *hnb, const char *imsi,
|
||||
uint32_t tmsi)
|
||||
{
|
||||
struct ue_context *ue;
|
||||
|
||||
ue = talloc_zero(tall_hnb_ctx, struct ue_context);
|
||||
if (!ue)
|
||||
return NULL;
|
||||
|
||||
ue->hnb = hnb;
|
||||
if (imsi)
|
||||
OSMO_STRLCPY_ARRAY(ue->imsi, imsi);
|
||||
else
|
||||
ue->imsi[0] = '\0';
|
||||
ue->tmsi = tmsi;
|
||||
ue->context_id = get_next_ue_ctx_id(hnb->gw);
|
||||
llist_add_tail(&ue->list, &hnb->gw->ue_list);
|
||||
|
||||
LOGP(DHNBAP, LOGL_INFO, "created UE context: id 0x%x, imsi %s, tmsi 0x%x\n",
|
||||
ue->context_id, imsi? imsi : "-", tmsi);
|
||||
|
||||
return ue;
|
||||
}
|
||||
|
||||
void ue_context_free(struct ue_context *ue)
|
||||
{
|
||||
llist_del(&ue->list);
|
||||
talloc_free(ue);
|
||||
}
|
||||
|
||||
static int hnb_read_cb(struct osmo_stream_srv *conn)
|
||||
{
|
||||
struct hnb_context *hnb = osmo_stream_srv_get_data(conn);
|
||||
struct msgb *msg = msgb_alloc(IUH_MSGB_SIZE, "Iuh rx");
|
||||
int rc;
|
||||
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
/* we store a reference to the HomeNodeB in the msg->dest for the
|
||||
* benefit of varoius downstream processing functions */
|
||||
msg->dst = hnb;
|
||||
|
||||
rc = osmo_stream_srv_recv(conn, msg);
|
||||
if (rc == -EAGAIN) {
|
||||
/* Notification received */
|
||||
msgb_free(msg);
|
||||
/* Initialize MGW pool. This initalizes and connects all MGCP clients that are currently configured in
|
||||
* the pool. Adding additional MGCP clients to the pool is possible but the user has to configure and
|
||||
* (re)connect them manually from the VTY. */
|
||||
if (!mgcp_client_pool_empty(g_hnbgw->mgw_pool)) {
|
||||
pool_members_initalized = mgcp_client_pool_connect(g_hnbgw->mgw_pool);
|
||||
if (!pool_members_initalized) {
|
||||
LOGP(DMGW, LOGL_ERROR, "MGW pool failed to initialize any pool members\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
LOGP(DMGW, LOGL_NOTICE,
|
||||
"MGW pool with %u pool members configured, (ignoring MGW configuration in VTY node 'mgcp').\n",
|
||||
pool_members_initalized);
|
||||
return 0;
|
||||
} else if (rc < 0) {
|
||||
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Error during sctp_recvmsg()\n");
|
||||
/* FIXME: clean up after disappeared HNB */
|
||||
hnb_context_release(hnb);
|
||||
goto out;
|
||||
} else if (rc == 0) {
|
||||
hnb_context_release(hnb);
|
||||
rc = -1;
|
||||
|
||||
goto out;
|
||||
} else {
|
||||
msgb_put(msg, rc);
|
||||
}
|
||||
|
||||
switch (msgb_sctp_ppid(msg)) {
|
||||
case IUH_PPI_HNBAP:
|
||||
hnb->hnbap_stream = msgb_sctp_stream(msg);
|
||||
rc = hnbgw_hnbap_rx(hnb, msg);
|
||||
break;
|
||||
case IUH_PPI_RUA:
|
||||
hnb->rua_stream = msgb_sctp_stream(msg);
|
||||
rc = hnbgw_rua_rx(hnb, msg);
|
||||
break;
|
||||
case IUH_PPI_SABP:
|
||||
case IUH_PPI_RNA:
|
||||
case IUH_PPI_PUA:
|
||||
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unimplemented SCTP PPID=%lu received\n", msgb_sctp_ppid(msg));
|
||||
rc = 0;
|
||||
break;
|
||||
default:
|
||||
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unknown SCTP PPID=%lu received\n", msgb_sctp_ppid(msg));
|
||||
rc = 0;
|
||||
break;
|
||||
/* Initialize and connect a single MGCP client. This MGCP client will appear as the one and only pool
|
||||
* member if there is no MGW pool configured. */
|
||||
LOGP(DMGW, LOGL_NOTICE, "No MGW pool configured, using MGW configuration in VTY node 'mgcp'\n");
|
||||
mgcp_client_single = mgcp_client_init(g_hnbgw, g_hnbgw->config.mgcp_client);
|
||||
if (!mgcp_client_single) {
|
||||
LOGP(DMGW, LOGL_ERROR, "MGW (single) client initalization failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
out:
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct hnb_context *hnb_context_alloc(struct hnb_gw *gw, struct osmo_stream_srv_link *link, int new_fd)
|
||||
{
|
||||
struct hnb_context *ctx;
|
||||
|
||||
ctx = talloc_zero(tall_hnb_ctx, struct hnb_context);
|
||||
if (!ctx)
|
||||
return NULL;
|
||||
INIT_LLIST_HEAD(&ctx->map_list);
|
||||
|
||||
ctx->gw = gw;
|
||||
ctx->conn = osmo_stream_srv_create(tall_hnb_ctx, link, new_fd, hnb_read_cb, NULL, ctx);
|
||||
if (!ctx->conn) {
|
||||
LOGP(DMAIN, LOGL_INFO, "error while creating connection\n");
|
||||
talloc_free(ctx);
|
||||
return NULL;
|
||||
if (mgcp_client_connect(mgcp_client_single)) {
|
||||
LOGP(DMGW, LOGL_ERROR, "MGW (single) connect failed at (%s:%u)\n",
|
||||
g_hnbgw->config.mgcp_client->remote_addr,
|
||||
g_hnbgw->config.mgcp_client->remote_port);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
llist_add_tail(&ctx->list, &gw->hnb_list);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static const char *umts_cell_id_name(const struct umts_cell_id *ucid)
|
||||
{
|
||||
static __thread char buf[40];
|
||||
|
||||
snprintf(buf, sizeof(buf), "%u-%u-L%u-R%u-S%u", ucid->mcc, ucid->mnc, ucid->lac, ucid->rac, ucid->sac);
|
||||
return buf;
|
||||
}
|
||||
|
||||
const char *hnb_context_name(struct hnb_context *ctx)
|
||||
{
|
||||
if (!ctx)
|
||||
return "NULL";
|
||||
|
||||
if (ctx->gw->config.log_prefix_hnb_id)
|
||||
return ctx->identity_info;
|
||||
else
|
||||
return umts_cell_id_name(&ctx->id);
|
||||
}
|
||||
|
||||
void hnb_context_release(struct hnb_context *ctx)
|
||||
{
|
||||
struct hnbgw_context_map *map, *map2;
|
||||
|
||||
/* remove from the list of HNB contexts */
|
||||
llist_del(&ctx->list);
|
||||
|
||||
/* deactivate all context maps */
|
||||
llist_for_each_entry_safe(map, map2, &ctx->map_list, hnb_list) {
|
||||
/* remove it from list, as HNB context will soon be
|
||||
* gone. Let's hope the second osmo_llist_del in the
|
||||
* map garbage collector works fine? */
|
||||
llist_del(&map->hnb_list);
|
||||
llist_del(&map->cn_list);
|
||||
context_map_deactivate(map);
|
||||
}
|
||||
ue_context_free_by_hnb(ctx->gw, ctx);
|
||||
|
||||
osmo_stream_srv_destroy(ctx->conn);
|
||||
|
||||
talloc_free(ctx);
|
||||
}
|
||||
|
||||
/*! call-back when the listen FD has something to read */
|
||||
static int accept_cb(struct osmo_stream_srv_link *srv, int fd)
|
||||
{
|
||||
struct hnb_gw *gw = osmo_stream_srv_link_get_data(srv);
|
||||
struct hnb_context *ctx;
|
||||
|
||||
ctx = hnb_context_alloc(gw, srv, fd);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
mgcp_client_pool_register_single(g_hnbgw->mgw_pool, mgcp_client_single);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct log_info_cat log_cat[] = {
|
||||
struct msgb *hnbgw_ranap_msg_alloc(const char *name)
|
||||
{
|
||||
struct msgb *ranap_msg;
|
||||
ranap_msg = msgb_alloc_c(OTC_SELECT, sizeof(struct osmo_scu_prim) + 1500, name);
|
||||
msgb_reserve(ranap_msg, sizeof(struct osmo_scu_prim));
|
||||
ranap_msg->l2h = ranap_msg->data;
|
||||
return ranap_msg;
|
||||
}
|
||||
|
||||
#define HNBGW_COPYRIGHT \
|
||||
"OsmoHNBGW - Osmocom Home Node B Gateway implementation\r\n" \
|
||||
"Copyright (C) 2016-2024 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>\r\n" \
|
||||
"Contributions by Daniel Willmann, Harald Welte, Neels Hofmeyr\r\n" \
|
||||
"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n" \
|
||||
"This is free software: you are free to change and redistribute it.\r\n" \
|
||||
"There is NO WARRANTY, to the extent permitted by law.\r\n"
|
||||
|
||||
static const struct log_info_cat hnbgw_log_cat[] = {
|
||||
[DMAIN] = {
|
||||
.name = "DMAIN", .loglevel = LOGL_NOTICE, .enabled = 1,
|
||||
.color = "",
|
||||
@@ -372,329 +171,76 @@ static const struct log_info_cat log_cat[] = {
|
||||
.color = "",
|
||||
.description = "RAN Application Part",
|
||||
},
|
||||
[DMGW] = {
|
||||
.name = "DMGW", .loglevel = LOGL_NOTICE, .enabled = 1,
|
||||
.color = "\033[1;33m",
|
||||
.description = "Media Gateway",
|
||||
},
|
||||
[DHNB] = {
|
||||
.name = "DHNB", .loglevel = LOGL_NOTICE, .enabled = 1,
|
||||
.color = OSMO_LOGCOLOR_CYAN,
|
||||
.description = "HNB side (via RUA)",
|
||||
},
|
||||
[DCN] = {
|
||||
.name = "DCN", .loglevel = LOGL_NOTICE, .enabled = 1,
|
||||
.color = OSMO_LOGCOLOR_DARKYELLOW,
|
||||
.description = "Core Network side (via SCCP)",
|
||||
},
|
||||
[DNFT] = {
|
||||
.name = "DNFT", .loglevel = LOGL_NOTICE, .enabled = 1,
|
||||
.color = OSMO_LOGCOLOR_BLUE,
|
||||
.description = "nftables interaction for retrieving stats",
|
||||
},
|
||||
};
|
||||
|
||||
static const struct log_info hnbgw_log_info = {
|
||||
.cat = log_cat,
|
||||
.num_cat = ARRAY_SIZE(log_cat),
|
||||
const struct log_info hnbgw_log_info = {
|
||||
.cat = hnbgw_log_cat,
|
||||
.num_cat = ARRAY_SIZE(hnbgw_log_cat),
|
||||
};
|
||||
|
||||
static struct vty_app_info vty_info = {
|
||||
.name = "OsmoHNBGW",
|
||||
.version = PACKAGE_VERSION,
|
||||
.go_parent_cb = hnbgw_vty_go_parent,
|
||||
struct vty_app_info hnbgw_vty_info = {
|
||||
.name = "OsmoHNBGW",
|
||||
.version = PACKAGE_VERSION,
|
||||
.go_parent_cb = hnbgw_vty_go_parent,
|
||||
.copyright = HNBGW_COPYRIGHT,
|
||||
};
|
||||
|
||||
static struct {
|
||||
int daemonize;
|
||||
const char *config_file;
|
||||
bool log_disable_color;
|
||||
bool log_enable_timestamp;
|
||||
int log_level;
|
||||
const char *log_category_mask;
|
||||
} hnbgw_cmdline_config = {
|
||||
0,
|
||||
"osmo-hnbgw.cfg",
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void print_usage()
|
||||
void g_hnbgw_alloc(void *ctx)
|
||||
{
|
||||
printf("Usage: osmo-hnbgw\n");
|
||||
}
|
||||
|
||||
static void print_help()
|
||||
{
|
||||
printf(" -h --help This text.\n");
|
||||
printf(" -d option --debug=DHNBAP:DRUA:DRANAP:DMAIN Enable debugging.\n");
|
||||
printf(" -D --daemonize Fork the process into a background daemon.\n");
|
||||
printf(" -c --config-file filename The config file to use.\n");
|
||||
printf(" -s --disable-color\n");
|
||||
printf(" -T --timestamp Prefix every log line with a timestamp.\n");
|
||||
printf(" -V --version Print the version of OsmoHNBGW.\n");
|
||||
printf(" -e --log-level number Set a global loglevel.\n");
|
||||
|
||||
printf("\nVTY reference generation:\n");
|
||||
printf(" --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n");
|
||||
printf(" --vty-ref-xml Generate the VTY reference XML output and exit.\n");
|
||||
}
|
||||
|
||||
static void handle_long_options(const char *prog_name, const int long_option)
|
||||
{
|
||||
static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT;
|
||||
|
||||
switch (long_option) {
|
||||
case 1:
|
||||
vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg);
|
||||
if (vty_ref_mode < 0) {
|
||||
fprintf(stderr, "%s: Unknown VTY reference generation "
|
||||
"mode '%s'\n", prog_name, optarg);
|
||||
exit(2);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
|
||||
get_value_string(vty_ref_gen_mode_names, vty_ref_mode),
|
||||
get_value_string(vty_ref_gen_mode_desc, vty_ref_mode));
|
||||
vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode);
|
||||
exit(0);
|
||||
default:
|
||||
fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_options(int argc, char **argv)
|
||||
{
|
||||
while (1) {
|
||||
int option_index = 0, c;
|
||||
static int long_option = 0;
|
||||
static struct option long_options[] = {
|
||||
{"help", 0, 0, 'h'},
|
||||
{"debug", 1, 0, 'd'},
|
||||
{"daemonize", 0, 0, 'D'},
|
||||
{"config-file", 1, 0, 'c'},
|
||||
{"disable-color", 0, 0, 's'},
|
||||
{"timestamp", 0, 0, 'T'},
|
||||
{"version", 0, 0, 'V' },
|
||||
{"log-level", 1, 0, 'e'},
|
||||
{"vty-ref-mode", 1, &long_option, 1},
|
||||
{"vty-ref-xml", 0, &long_option, 2},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "hd:Dc:sTVe:",
|
||||
long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
switch (c) {
|
||||
case 0:
|
||||
handle_long_options(argv[0], long_option);
|
||||
break;
|
||||
case 'h':
|
||||
print_usage();
|
||||
print_help();
|
||||
exit(0);
|
||||
case 's':
|
||||
hnbgw_cmdline_config.log_disable_color = true;
|
||||
break;
|
||||
case 'd':
|
||||
hnbgw_cmdline_config.log_category_mask = optarg;
|
||||
break;
|
||||
case 'D':
|
||||
hnbgw_cmdline_config.daemonize = 1;
|
||||
break;
|
||||
case 'c':
|
||||
hnbgw_cmdline_config.config_file = optarg;
|
||||
break;
|
||||
case 'T':
|
||||
hnbgw_cmdline_config.log_enable_timestamp = true;
|
||||
break;
|
||||
case 'e':
|
||||
hnbgw_cmdline_config.log_level = atoi(optarg);
|
||||
break;
|
||||
case 'V':
|
||||
print_version(1);
|
||||
exit(0);
|
||||
break;
|
||||
default:
|
||||
/* catch unknown options *as well as* missing arguments. */
|
||||
fprintf(stderr, "Error in command line options. Exiting.\n");
|
||||
exit(-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (argc > optind) {
|
||||
fprintf(stderr, "Unsupported positional arguments on command line\n");
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
CTRL_CMD_DEFINE_RO(hnb_info, "info");
|
||||
static int get_hnb_info(struct ctrl_cmd *cmd, void *data)
|
||||
{
|
||||
struct hnb_context *hnb = data;
|
||||
|
||||
cmd->reply = talloc_strdup(cmd, hnb->identity_info);
|
||||
|
||||
return CTRL_CMD_REPLY;
|
||||
}
|
||||
|
||||
CTRL_CMD_DEFINE_RO(hnbs, "num-hnb");
|
||||
static int get_hnbs(struct ctrl_cmd *cmd, void *data)
|
||||
{
|
||||
cmd->reply = talloc_asprintf(cmd, "%u", hnb_contexts(data));
|
||||
|
||||
return CTRL_CMD_REPLY;
|
||||
}
|
||||
|
||||
int hnb_ctrl_cmds_install()
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_hnbs);
|
||||
rc |= ctrl_cmd_install(CTRL_NODE_HNB, &cmd_hnb_info);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int hnb_ctrl_node_lookup(void *data, vector vline, int *node_type, void **node_data, int *i)
|
||||
{
|
||||
const char *token = vector_slot(vline, *i);
|
||||
struct hnb_context *hnb;
|
||||
long num;
|
||||
|
||||
switch (*node_type) {
|
||||
case CTRL_NODE_ROOT:
|
||||
if (strcmp(token, "hnb") != 0)
|
||||
return 0;
|
||||
|
||||
(*i)++;
|
||||
|
||||
if (!ctrl_parse_get_num(vline, *i, &num))
|
||||
return -ERANGE;
|
||||
|
||||
hnb = hnb_context_by_id(data, num);
|
||||
if (!hnb)
|
||||
return -ENODEV;
|
||||
|
||||
*node_data = hnb;
|
||||
*node_type = CTRL_NODE_HNB;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct osmo_stream_srv_link *srv;
|
||||
int rc;
|
||||
|
||||
tall_hnb_ctx = talloc_named_const(NULL, 0, "hnb_context");
|
||||
talloc_asn1_ctx = talloc_named_const(NULL, 1, "asn1_context");
|
||||
msgb_talloc_ctx_init(tall_hnb_ctx, 0);
|
||||
|
||||
g_hnb_gw = hnb_gw_create(tall_hnb_ctx);
|
||||
g_hnb_gw->config.rnc_id = 23;
|
||||
|
||||
rc = osmo_init_logging2(tall_hnb_ctx, &hnbgw_log_info);
|
||||
if (rc < 0)
|
||||
exit(1);
|
||||
|
||||
rc = osmo_ss7_init();
|
||||
if (rc < 0) {
|
||||
LOGP(DMAIN, LOGL_FATAL, "osmo_ss7_init() failed with rc=%d\n", rc);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
vty_info.copyright = osmo_hnbgw_copyright;
|
||||
vty_init(&vty_info);
|
||||
|
||||
osmo_ss7_vty_init_asp(tall_hnb_ctx);
|
||||
osmo_sccp_vty_init();
|
||||
hnbgw_vty_init(g_hnb_gw, tall_hnb_ctx);
|
||||
ctrl_vty_init(tall_hnb_ctx);
|
||||
logging_vty_add_cmds();
|
||||
|
||||
/* Handle options after vty_init(), for --version */
|
||||
handle_options(argc, argv);
|
||||
|
||||
rc = vty_read_config_file(hnbgw_cmdline_config.config_file, NULL);
|
||||
if (rc < 0) {
|
||||
LOGP(DMAIN, LOGL_FATAL, "Failed to parse the config file: '%s'\n",
|
||||
hnbgw_cmdline_config.config_file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* cmdline options take precedence over config file, but if no options
|
||||
* were passed we must not override the config file.
|
||||
*/
|
||||
if (hnbgw_cmdline_config.log_disable_color)
|
||||
log_set_use_color(osmo_stderr_target, 0);
|
||||
if (hnbgw_cmdline_config.log_category_mask)
|
||||
log_parse_category_mask(osmo_stderr_target,
|
||||
hnbgw_cmdline_config.log_category_mask);
|
||||
if (hnbgw_cmdline_config.log_enable_timestamp)
|
||||
log_set_print_timestamp(osmo_stderr_target, 1);
|
||||
if (hnbgw_cmdline_config.log_level)
|
||||
log_set_log_level(osmo_stderr_target,
|
||||
hnbgw_cmdline_config.log_level);
|
||||
|
||||
rc = telnet_init_dynif(tall_hnb_ctx, g_hnb_gw, vty_get_bind_addr(), OSMO_VTY_PORT_HNBGW);
|
||||
if (rc < 0) {
|
||||
perror("Error binding VTY port");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
g_hnb_gw->ctrl = ctrl_interface_setup_dynip2(g_hnb_gw, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_HNBGW,
|
||||
hnb_ctrl_node_lookup, _LAST_CTRL_NODE_HNB);
|
||||
if (!g_hnb_gw->ctrl) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "Failed to create CTRL interface on %s:%u\n",
|
||||
ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_HNBGW);
|
||||
exit(1);
|
||||
} else {
|
||||
rc = hnb_ctrl_cmds_install();
|
||||
if (rc) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "Failed to install CTRL interface commands\n");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
ranap_set_log_area(DRANAP);
|
||||
|
||||
rc = hnbgw_cnlink_init(g_hnb_gw, "localhost", M3UA_PORT, "localhost");
|
||||
if (rc < 0) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "Failed to initialize SCCP link to CN\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
LOGP(DHNBAP, LOGL_NOTICE, "Using RNC-Id %u\n", g_hnb_gw->config.rnc_id);
|
||||
|
||||
OSMO_ASSERT(g_hnb_gw->config.iuh_local_ip);
|
||||
LOGP(DMAIN, LOGL_NOTICE, "Listening for Iuh at %s %d\n",
|
||||
g_hnb_gw->config.iuh_local_ip,
|
||||
g_hnb_gw->config.iuh_local_port);
|
||||
srv = osmo_stream_srv_link_create(tall_hnb_ctx);
|
||||
if (!srv) {
|
||||
perror("cannot create server");
|
||||
exit(1);
|
||||
}
|
||||
osmo_stream_srv_link_set_data(srv, g_hnb_gw);
|
||||
osmo_stream_srv_link_set_proto(srv, IPPROTO_SCTP);
|
||||
osmo_stream_srv_link_set_nodelay(srv, true);
|
||||
osmo_stream_srv_link_set_addr(srv, g_hnb_gw->config.iuh_local_ip);
|
||||
osmo_stream_srv_link_set_port(srv, g_hnb_gw->config.iuh_local_port);
|
||||
osmo_stream_srv_link_set_accept_cb(srv, accept_cb);
|
||||
|
||||
if (osmo_stream_srv_link_open(srv) < 0) {
|
||||
perror("Cannot open server");
|
||||
exit(1);
|
||||
}
|
||||
g_hnb_gw->iuh = srv;
|
||||
|
||||
if (hnbgw_cmdline_config.daemonize) {
|
||||
rc = osmo_daemonize();
|
||||
if (rc < 0) {
|
||||
perror("Error during daemonize");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
while (1) {
|
||||
rc = osmo_select_main(0);
|
||||
if (rc < 0)
|
||||
exit(3);
|
||||
}
|
||||
|
||||
/* not reached */
|
||||
exit(0);
|
||||
OSMO_ASSERT(!g_hnbgw);
|
||||
g_hnbgw = talloc_zero(ctx, struct hnbgw);
|
||||
|
||||
/* strdup so we can easily talloc_free in the VTY code */
|
||||
g_hnbgw->config.iuh_local_ip = talloc_strdup(g_hnbgw, HNBGW_LOCAL_IP_DEFAULT);
|
||||
g_hnbgw->config.iuh_local_port = IUH_DEFAULT_SCTP_PORT;
|
||||
g_hnbgw->config.hnbap_allow_tmsi = true;
|
||||
g_hnbgw->config.log_prefix_hnb_id = true;
|
||||
g_hnbgw->config.accept_all_hnb = true;
|
||||
|
||||
/* Set zero PLMN to detect a missing PLMN when transmitting RESET */
|
||||
g_hnbgw->config.plmn = (struct osmo_plmn_id){ 0, 0, false };
|
||||
|
||||
g_hnbgw->next_ue_ctx_id = 23;
|
||||
INIT_LLIST_HEAD(&g_hnbgw->hnb_list);
|
||||
|
||||
INIT_LLIST_HEAD(&g_hnbgw->hnb_persistent_list);
|
||||
hash_init(g_hnbgw->hnb_persistent_by_id);
|
||||
|
||||
INIT_LLIST_HEAD(&g_hnbgw->sccp.users);
|
||||
|
||||
g_hnbgw->mgw_pool = mgcp_client_pool_alloc(g_hnbgw);
|
||||
g_hnbgw->config.mgcp_client = mgcp_client_conf_alloc(g_hnbgw);
|
||||
|
||||
#if ENABLE_PFCP
|
||||
g_hnbgw->config.pfcp.remote_port = OSMO_PFCP_PORT;
|
||||
#endif
|
||||
|
||||
g_hnbgw->sccp.cnpool_iucs = hnbgw_cnpool_alloc(DOMAIN_CS);
|
||||
g_hnbgw->sccp.cnpool_iups = hnbgw_cnpool_alloc(DOMAIN_PS);
|
||||
|
||||
osmo_timer_setup(&g_hnbgw->store_uptime_timer, hnbgw_store_hnb_uptime, g_hnbgw);
|
||||
osmo_timer_schedule(&g_hnbgw->store_uptime_timer, STORE_UPTIME_INTERVAL, 0);
|
||||
|
||||
osmo_timer_setup(&g_hnbgw->hnb_store_rab_durations_timer, hnbgw_store_hnb_rab_durations, g_hnbgw);
|
||||
osmo_timer_schedule(&g_hnbgw->hnb_store_rab_durations_timer, HNB_STORE_RAB_DURATIONS_INTERVAL, 0);
|
||||
}
|
||||
|
||||
@@ -18,542 +18,313 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <asn1c/asn1helpers.h>
|
||||
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/core/stats.h>
|
||||
|
||||
#include <osmocom/gsm/gsm23236.h>
|
||||
|
||||
#include <osmocom/sigtran/protocol/m3ua.h>
|
||||
#include <osmocom/sigtran/protocol/sua.h>
|
||||
#include <osmocom/sigtran/sccp_sap.h>
|
||||
#include <osmocom/sigtran/sccp_helpers.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/hnbgw_rua.h>
|
||||
#include <osmocom/ranap/ranap_ies_defs.h>
|
||||
#include <osmocom/ranap/ranap_msg_factory.h>
|
||||
#include <osmocom/hnbgw/hnbgw_sccp.h>
|
||||
#include <osmocom/hnbgw/hnbgw_ranap.h>
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
|
||||
/***********************************************************************
|
||||
* Outbound RANAP RESET to CN
|
||||
***********************************************************************/
|
||||
|
||||
void hnbgw_cnlink_change_state(struct hnbgw_cnlink *cnlink, enum hnbgw_cnlink_state state);
|
||||
|
||||
static int transmit_rst(struct hnb_gw *gw, RANAP_CN_DomainIndicator_t domain,
|
||||
struct osmo_sccp_addr *remote_addr)
|
||||
void hnbgw_cnpool_apply_cfg(struct hnbgw_cnpool *cnpool)
|
||||
{
|
||||
struct msgb *msg;
|
||||
RANAP_Cause_t cause = {
|
||||
.present = RANAP_Cause_PR_transmissionNetwork,
|
||||
.choice. transmissionNetwork = RANAP_CauseTransmissionNetwork_signalling_transport_resource_failure,
|
||||
};
|
||||
struct osmo_nri_range *r;
|
||||
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Tx RESET to %s %s\n",
|
||||
domain == RANAP_CN_DomainIndicator_cs_domain ? "IuCS" : "IuPS",
|
||||
osmo_sccp_inst_addr_name(gw->sccp.cnlink->sccp, remote_addr));
|
||||
cnpool->use.nri_bitlen = cnpool->vty.nri_bitlen;
|
||||
|
||||
msg = ranap_new_msg_reset(domain, &cause);
|
||||
|
||||
return osmo_sccp_tx_unitdata_msg(gw->sccp.cnlink->sccp_user,
|
||||
&gw->sccp.local_addr,
|
||||
remote_addr,
|
||||
msg);
|
||||
osmo_nri_ranges_free(cnpool->use.null_nri_ranges);
|
||||
cnpool->use.null_nri_ranges = osmo_nri_ranges_alloc(cnpool);
|
||||
llist_for_each_entry(r, &cnpool->vty.null_nri_ranges->entries, entry)
|
||||
osmo_nri_ranges_add(cnpool->use.null_nri_ranges, r);
|
||||
}
|
||||
|
||||
static int transmit_reset_ack(struct hnb_gw *gw, RANAP_CN_DomainIndicator_t domain,
|
||||
const struct osmo_sccp_addr *remote_addr)
|
||||
void hnbgw_cnpool_cnlinks_start_or_restart(struct hnbgw_cnpool *cnpool)
|
||||
{
|
||||
struct msgb *msg;
|
||||
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Tx RESET ACK to %s %s\n",
|
||||
domain == RANAP_CN_DomainIndicator_cs_domain ? "IuCS" : "IuPS",
|
||||
osmo_sccp_inst_addr_name(gw->sccp.cnlink->sccp, remote_addr));
|
||||
|
||||
msg = ranap_new_msg_reset_ack(domain, NULL);
|
||||
|
||||
return osmo_sccp_tx_unitdata_msg(gw->sccp.cnlink->sccp_user,
|
||||
&gw->sccp.local_addr,
|
||||
remote_addr,
|
||||
msg);
|
||||
}
|
||||
|
||||
/* Timer callback once T_RafC expires */
|
||||
static void cnlink_trafc_cb(void *data)
|
||||
{
|
||||
struct hnb_gw *gw = data;
|
||||
|
||||
transmit_rst(gw, RANAP_CN_DomainIndicator_cs_domain, &gw->sccp.iucs_remote_addr);
|
||||
transmit_rst(gw, RANAP_CN_DomainIndicator_ps_domain, &gw->sccp.iups_remote_addr);
|
||||
hnbgw_cnlink_change_state(gw->sccp.cnlink, CNLINK_S_EST_RST_TX_WAIT_ACK);
|
||||
/* The spec states that we should abandon after a configurable
|
||||
* number of times. We decide to simply continue trying */
|
||||
}
|
||||
|
||||
/* change the state of a CN Link */
|
||||
void hnbgw_cnlink_change_state(struct hnbgw_cnlink *cnlink, enum hnbgw_cnlink_state state)
|
||||
{
|
||||
switch (state) {
|
||||
case CNLINK_S_NULL:
|
||||
case CNLINK_S_EST_PEND:
|
||||
break;
|
||||
case CNLINK_S_EST_CONF:
|
||||
cnlink_trafc_cb(cnlink->gw);
|
||||
break;
|
||||
case CNLINK_S_EST_RST_TX_WAIT_ACK:
|
||||
osmo_timer_schedule(&cnlink->T_RafC, 5, 0);
|
||||
break;
|
||||
case CNLINK_S_EST_ACTIVE:
|
||||
osmo_timer_del(&cnlink->T_RafC);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* Incoming primitives from SCCP User SAP
|
||||
***********************************************************************/
|
||||
|
||||
static int cn_ranap_rx_reset_cmd(struct hnbgw_cnlink *cnlink,
|
||||
const struct osmo_scu_unitdata_param *unitdata,
|
||||
RANAP_InitiatingMessage_t *imsg)
|
||||
{
|
||||
RANAP_CN_DomainIndicator_t domain;
|
||||
RANAP_ResetIEs_t ies;
|
||||
int rc;
|
||||
|
||||
rc = ranap_decode_reseties(&ies, &imsg->value);
|
||||
domain = ies.cN_DomainIndicator;
|
||||
ranap_free_reseties(&ies);
|
||||
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Rx RESET from %s %s, returning ACK\n",
|
||||
domain == RANAP_CN_DomainIndicator_cs_domain ? "IuCS" : "IuPS",
|
||||
osmo_sccp_inst_addr_name(cnlink->sccp, &unitdata->calling_addr));
|
||||
|
||||
/* FIXME: actually reset connections, if any */
|
||||
|
||||
if (transmit_reset_ack(cnlink->gw, domain, &unitdata->calling_addr))
|
||||
LOGP(DRANAP, LOGL_ERROR, "Error: cannot send RESET ACK to %s %s\n",
|
||||
domain == RANAP_CN_DomainIndicator_cs_domain ? "IuCS" : "IuPS",
|
||||
osmo_sccp_inst_addr_name(cnlink->sccp, &unitdata->calling_addr));
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int cn_ranap_rx_reset_ack(struct hnbgw_cnlink *cnlink,
|
||||
RANAP_SuccessfulOutcome_t *omsg)
|
||||
{
|
||||
RANAP_ResetAcknowledgeIEs_t ies;
|
||||
int rc;
|
||||
|
||||
rc = ranap_decode_resetacknowledgeies(&ies, &omsg->value);
|
||||
|
||||
hnbgw_cnlink_change_state(cnlink, CNLINK_S_EST_ACTIVE);
|
||||
|
||||
ranap_free_resetacknowledgeies(&ies);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int cn_ranap_rx_paging_cmd(struct hnbgw_cnlink *cnlink,
|
||||
RANAP_InitiatingMessage_t *imsg,
|
||||
const uint8_t *data, unsigned int len)
|
||||
{
|
||||
struct hnb_gw *gw = cnlink->gw;
|
||||
struct hnb_context *hnb;
|
||||
RANAP_PagingIEs_t ies;
|
||||
int rc;
|
||||
|
||||
rc = ranap_decode_pagingies(&ies, &imsg->value);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* FIXME: determine which HNBs to send this Paging command,
|
||||
* rather than broadcasting to all HNBs */
|
||||
llist_for_each_entry(hnb, &gw->hnb_list, list) {
|
||||
rc = rua_tx_udt(hnb, data, len);
|
||||
}
|
||||
|
||||
ranap_free_pagingies(&ies);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cn_ranap_rx_initiating_msg(struct hnbgw_cnlink *cnlink,
|
||||
const struct osmo_scu_unitdata_param *unitdata,
|
||||
RANAP_InitiatingMessage_t *imsg,
|
||||
const uint8_t *data, unsigned int len)
|
||||
{
|
||||
switch (imsg->procedureCode) {
|
||||
case RANAP_ProcedureCode_id_Reset:
|
||||
return cn_ranap_rx_reset_cmd(cnlink, unitdata, imsg);
|
||||
case RANAP_ProcedureCode_id_Paging:
|
||||
return cn_ranap_rx_paging_cmd(cnlink, imsg, data, len);
|
||||
case RANAP_ProcedureCode_id_OverloadControl: /* Overload ind */
|
||||
break;
|
||||
case RANAP_ProcedureCode_id_ErrorIndication: /* Error ind */
|
||||
break;
|
||||
case RANAP_ProcedureCode_id_ResetResource: /* request */
|
||||
case RANAP_ProcedureCode_id_InformationTransfer:
|
||||
case RANAP_ProcedureCode_id_DirectInformationTransfer:
|
||||
case RANAP_ProcedureCode_id_UplinkInformationExchange:
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP "
|
||||
"Procedure %ld from CN, ignoring\n", imsg->procedureCode);
|
||||
break;
|
||||
default:
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP "
|
||||
"Procedure %ld from CN, ignoring\n", imsg->procedureCode);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cn_ranap_rx_successful_msg(struct hnbgw_cnlink *cnlink,
|
||||
RANAP_SuccessfulOutcome_t *omsg)
|
||||
{
|
||||
switch (omsg->procedureCode) {
|
||||
case RANAP_ProcedureCode_id_Reset: /* Reset acknowledge */
|
||||
return cn_ranap_rx_reset_ack(cnlink, omsg);
|
||||
case RANAP_ProcedureCode_id_ResetResource: /* response */
|
||||
case RANAP_ProcedureCode_id_InformationTransfer:
|
||||
case RANAP_ProcedureCode_id_DirectInformationTransfer:
|
||||
case RANAP_ProcedureCode_id_UplinkInformationExchange:
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP "
|
||||
"Procedure %ld from CN, ignoring\n", omsg->procedureCode);
|
||||
break;
|
||||
default:
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP "
|
||||
"Procedure %ld from CN, ignoring\n", omsg->procedureCode);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int _cn_ranap_rx(struct hnbgw_cnlink *cnlink,
|
||||
const struct osmo_scu_unitdata_param *unitdata,
|
||||
RANAP_RANAP_PDU_t *pdu, const uint8_t *data, unsigned int len)
|
||||
{
|
||||
int rc;
|
||||
|
||||
switch (pdu->present) {
|
||||
case RANAP_RANAP_PDU_PR_initiatingMessage:
|
||||
rc = cn_ranap_rx_initiating_msg(cnlink, unitdata, &pdu->choice.initiatingMessage,
|
||||
data, len);
|
||||
break;
|
||||
case RANAP_RANAP_PDU_PR_successfulOutcome:
|
||||
rc = cn_ranap_rx_successful_msg(cnlink, &pdu->choice.successfulOutcome);
|
||||
break;
|
||||
case RANAP_RANAP_PDU_PR_unsuccessfulOutcome:
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP "
|
||||
"unsuccessful outcome procedure %ld from CN, ignoring\n",
|
||||
pdu->choice.unsuccessfulOutcome.procedureCode);
|
||||
rc = -ENOTSUP;
|
||||
break;
|
||||
default:
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP "
|
||||
"presence %u from CN, ignoring\n", pdu->present);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int handle_cn_ranap(struct hnbgw_cnlink *cnlink, const struct osmo_scu_unitdata_param *unitdata,
|
||||
const uint8_t *data, unsigned int len)
|
||||
{
|
||||
RANAP_RANAP_PDU_t _pdu, *pdu = &_pdu;
|
||||
asn_dec_rval_t dec_ret;
|
||||
int rc;
|
||||
|
||||
memset(pdu, 0, sizeof(*pdu));
|
||||
dec_ret = aper_decode(NULL,&asn_DEF_RANAP_RANAP_PDU, (void **) &pdu,
|
||||
data, len, 0, 0);
|
||||
if (dec_ret.code != RC_OK) {
|
||||
LOGP(DRANAP, LOGL_ERROR, "Error in RANAP ASN.1 decode\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = _cn_ranap_rx(cnlink, unitdata, pdu, data, len);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static bool pc_and_ssn_match(const struct osmo_sccp_addr *a, const struct osmo_sccp_addr *b)
|
||||
{
|
||||
return (a == b)
|
||||
|| ((a->pc == b->pc)
|
||||
&& (a->ssn == b->ssn));
|
||||
}
|
||||
|
||||
static int classify_cn_remote_addr(const struct hnb_gw *gw,
|
||||
const struct osmo_sccp_addr *cn_remote_addr,
|
||||
bool *is_ps)
|
||||
{
|
||||
if (pc_and_ssn_match(cn_remote_addr, &gw->sccp.iucs_remote_addr)) {
|
||||
if (is_ps)
|
||||
*is_ps = false;
|
||||
return 0;
|
||||
}
|
||||
if (pc_and_ssn_match(cn_remote_addr, &gw->sccp.iups_remote_addr)) {
|
||||
if (is_ps)
|
||||
*is_ps = true;
|
||||
return 0;
|
||||
}
|
||||
LOGP(DMAIN, LOGL_ERROR, "Unexpected remote address, matches neither CS nor PS address: %s\n",
|
||||
osmo_sccp_addr_dump(cn_remote_addr));
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int handle_cn_unitdata(struct hnbgw_cnlink *cnlink,
|
||||
const struct osmo_scu_unitdata_param *param,
|
||||
struct osmo_prim_hdr *oph)
|
||||
{
|
||||
if (param->called_addr.ssn != OSMO_SCCP_SSN_RANAP) {
|
||||
LOGP(DMAIN, LOGL_NOTICE, "N-UNITDATA.ind for unknown SSN %u\n",
|
||||
param->called_addr.ssn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (classify_cn_remote_addr(cnlink->gw, ¶m->calling_addr, NULL) < 0)
|
||||
return -1;
|
||||
|
||||
return handle_cn_ranap(cnlink, param, msgb_l2(oph->msg), msgb_l2len(oph->msg));
|
||||
}
|
||||
|
||||
static int handle_cn_conn_conf(struct hnbgw_cnlink *cnlink,
|
||||
const struct osmo_scu_connect_param *param,
|
||||
struct osmo_prim_hdr *oph)
|
||||
{
|
||||
/* we don't actually need to do anything, as RUA towards the HNB
|
||||
* doesn't seem to know any confirmations to its CONNECT
|
||||
* operation */
|
||||
|
||||
LOGP(DMAIN, LOGL_DEBUG, "handle_cn_conn_conf() conn_id=%d\n",
|
||||
param->conn_id);
|
||||
LOGP(DMAIN, LOGL_DEBUG, "handle_cn_conn_conf() called_addr=%s\n",
|
||||
inet_ntoa(param->called_addr.ip.v4));
|
||||
LOGP(DMAIN, LOGL_DEBUG, "handle_cn_conn_conf() calling_addr=%s\n",
|
||||
inet_ntoa(param->calling_addr.ip.v4));
|
||||
LOGP(DMAIN, LOGL_DEBUG, "handle_cn_conn_conf() responding_addr=%s\n",
|
||||
inet_ntoa(param->responding_addr.ip.v4));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_cn_data_ind(struct hnbgw_cnlink *cnlink,
|
||||
const struct osmo_scu_data_param *param,
|
||||
struct osmo_prim_hdr *oph)
|
||||
{
|
||||
struct hnbgw_context_map *map;
|
||||
|
||||
/* connection-oriented data is always passed transparently
|
||||
* towards the specific HNB, via a RUA connection identified by
|
||||
* conn_id */
|
||||
|
||||
map = context_map_by_cn(cnlink, param->conn_id);
|
||||
if (!map) {
|
||||
/* FIXME: Return an error / released primitive */
|
||||
return 0;
|
||||
}
|
||||
|
||||
return rua_tx_dt(map->hnb_ctx, map->is_ps, map->rua_ctx_id,
|
||||
msgb_l2(oph->msg), msgb_l2len(oph->msg));
|
||||
}
|
||||
|
||||
static int handle_cn_disc_ind(struct hnbgw_cnlink *cnlink,
|
||||
const struct osmo_scu_disconn_param *param,
|
||||
struct osmo_prim_hdr *oph)
|
||||
{
|
||||
struct hnbgw_context_map *map;
|
||||
|
||||
LOGP(DMAIN, LOGL_DEBUG, "handle_cn_disc_ind() conn_id=%d originator=%d\n",
|
||||
param->conn_id, param->originator);
|
||||
LOGP(DMAIN, LOGL_DEBUG, "handle_cn_disc_ind() responding_addr=%s\n",
|
||||
inet_ntoa(param->responding_addr.ip.v4));
|
||||
|
||||
RUA_Cause_t rua_cause = {
|
||||
.present = RUA_Cause_PR_NOTHING,
|
||||
/* FIXME: Convert incoming SCCP cause to RUA cause */
|
||||
};
|
||||
|
||||
/* we need to notify the HNB associated with this connection via
|
||||
* a RUA DISCONNECT */
|
||||
|
||||
map = context_map_by_cn(cnlink, param->conn_id);
|
||||
if (!map) {
|
||||
/* FIXME: Return an error / released primitive */
|
||||
return 0;
|
||||
}
|
||||
|
||||
return rua_tx_disc(map->hnb_ctx, map->is_ps, map->rua_ctx_id,
|
||||
&rua_cause, msgb_l2(oph->msg), msgb_l2len(oph->msg));
|
||||
}
|
||||
|
||||
/* Entry point for primitives coming up from SCCP User SAP */
|
||||
static int sccp_sap_up(struct osmo_prim_hdr *oph, void *ctx)
|
||||
{
|
||||
struct osmo_sccp_user *scu = ctx;
|
||||
struct hnbgw_cnlink *cnlink;
|
||||
struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph;
|
||||
int rc = 0;
|
||||
|
||||
LOGP(DMAIN, LOGL_DEBUG, "sccp_sap_up(%s)\n", osmo_scu_prim_name(oph));
|
||||
|
||||
if (!scu) {
|
||||
LOGP(DMAIN, LOGL_ERROR,
|
||||
"sccp_sap_up(): NULL osmo_sccp_user, cannot send prim (sap %u prim %u op %d)\n",
|
||||
oph->sap, oph->primitive, oph->operation);
|
||||
return -1;
|
||||
hnbgw_cnpool_apply_cfg(cnpool);
|
||||
llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) {
|
||||
hnbgw_cnlink_start_or_restart(cnlink);
|
||||
}
|
||||
|
||||
cnlink = osmo_sccp_user_get_priv(scu);
|
||||
if (!cnlink) {
|
||||
LOGP(DMAIN, LOGL_ERROR,
|
||||
"sccp_sap_up(): NULL hnbgw_cnlink, cannot send prim (sap %u prim %u op %d)\n",
|
||||
oph->sap, oph->primitive, oph->operation);
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (OSMO_PRIM_HDR(oph)) {
|
||||
case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION):
|
||||
rc = handle_cn_unitdata(cnlink, &prim->u.unitdata, oph);
|
||||
break;
|
||||
case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM):
|
||||
rc = handle_cn_conn_conf(cnlink, &prim->u.connect, oph);
|
||||
break;
|
||||
case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION):
|
||||
rc = handle_cn_data_ind(cnlink, &prim->u.data, oph);
|
||||
break;
|
||||
case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION):
|
||||
rc = handle_cn_disc_ind(cnlink, &prim->u.disconnect, oph);
|
||||
break;
|
||||
default:
|
||||
LOGP(DMAIN, LOGL_ERROR,
|
||||
"Received unknown prim %u from SCCP USER SAP\n",
|
||||
OSMO_PRIM_HDR(oph));
|
||||
break;
|
||||
}
|
||||
|
||||
msgb_free(oph->msg);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static bool addr_has_pc_and_ssn(const struct osmo_sccp_addr *addr)
|
||||
void hnbgw_cnpool_start(struct hnbgw_cnpool *cnpool)
|
||||
{
|
||||
if (!(addr->presence & OSMO_SCCP_ADDR_T_SSN))
|
||||
/* Legacy compat: when there is no 'msc N' at all in the config file, set up 'msc 0' with default values (or
|
||||
* 'sgsn' depending on cnpool). */
|
||||
if (llist_empty(&cnpool->cnlinks))
|
||||
cnlink_get_nr(cnpool, 0, true);
|
||||
hnbgw_cnpool_cnlinks_start_or_restart(cnpool);
|
||||
}
|
||||
|
||||
struct hnbgw_cnlink *cnlink_get_nr(struct hnbgw_cnpool *cnpool, int nr, bool create_if_missing)
|
||||
{
|
||||
struct hnbgw_cnlink *cnlink;
|
||||
llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) {
|
||||
if (cnlink->nr == nr)
|
||||
return cnlink;
|
||||
}
|
||||
|
||||
if (!create_if_missing)
|
||||
return NULL;
|
||||
|
||||
return hnbgw_cnlink_alloc(cnpool, nr);
|
||||
}
|
||||
|
||||
static bool is_cnlink_usable(struct hnbgw_cnlink *cnlink, bool is_emerg)
|
||||
{
|
||||
if (is_emerg && !cnlink->allow_emerg)
|
||||
return false;
|
||||
if (!(addr->presence & OSMO_SCCP_ADDR_T_PC))
|
||||
if (!cnlink->hnbgw_sccp_user || !cnlink->hnbgw_sccp_user->sccp_user)
|
||||
return false;
|
||||
if (!cnlink_is_conn_ready(cnlink))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int resolve_addr_name(struct osmo_sccp_addr *dest, struct osmo_ss7_instance **ss7,
|
||||
const char *addr_name, const char *label,
|
||||
uint32_t default_pc)
|
||||
/* Decide which MSC/SGSN to forward this Complete Layer 3 request to. The current Layer 3 Info is passed in map->l3.
|
||||
* a) If the subscriber was previously paged from a particular CN link, that CN link shall receive the Paging Response.
|
||||
* b) If the message contains an NRI indicating a particular CN link that is currently connected, that CN link shall
|
||||
* handle this conn.
|
||||
* c) All other cases distribute the messages across connected CN links in a round-robin fashion.
|
||||
*/
|
||||
struct hnbgw_cnlink *hnbgw_cnlink_select(struct hnbgw_context_map *map)
|
||||
{
|
||||
struct osmo_ss7_instance *ss7_tmp;
|
||||
struct hnbgw_cnpool *cnpool = map->is_ps ? g_hnbgw->sccp.cnpool_iups : g_hnbgw->sccp.cnpool_iucs;
|
||||
struct hnbgw_cnlink *cnlink;
|
||||
struct hnbgw_cnlink *round_robin_next = NULL;
|
||||
struct hnbgw_cnlink *round_robin_first = NULL;
|
||||
unsigned int round_robin_next_nr;
|
||||
int16_t nri_v = -1;
|
||||
bool is_null_nri = false;
|
||||
uint8_t nri_bitlen = cnpool->use.nri_bitlen;
|
||||
|
||||
if (!addr_name) {
|
||||
osmo_sccp_make_addr_pc_ssn(dest, default_pc, OSMO_SCCP_SSN_RANAP);
|
||||
LOGP(DMAIN, LOGL_INFO, "%s remote addr not configured, using default: %s\n", label,
|
||||
osmo_sccp_addr_name(*ss7, dest));
|
||||
return 0;
|
||||
/* Match IMSI with previous Paging */
|
||||
if (map->l3.gsm48_msg_type == GSM48_MT_RR_PAG_RESP) {
|
||||
cnlink = cnlink_find_by_paging_mi(cnpool, &map->l3.mi);
|
||||
if (cnlink) {
|
||||
LOG_MAP(map, DCN, LOGL_INFO, "CN link paging response record selects %s %d\n", cnpool->peer_name,
|
||||
cnlink->nr);
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_CNPOOL_SUBSCR_PAGED);
|
||||
return cnlink;
|
||||
}
|
||||
LOG_MAP(map, DCN, LOGL_INFO, "CN link paging response didn't match any record on %s\n", cnpool->peer_name);
|
||||
/* If there is no match, go on with other ways */
|
||||
}
|
||||
|
||||
ss7_tmp = osmo_sccp_addr_by_name(dest, addr_name);
|
||||
if (!ss7_tmp) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "%s remote addr: no such SCCP address book entry: '%s'\n",
|
||||
label, addr_name);
|
||||
return -1;
|
||||
#define LOG_NRI(LOGLEVEL, FORMAT, ARGS...) \
|
||||
LOG_MAP(map, DCN, LOGLEVEL, "%s NRI(%dbit)=0x%x=%d: " FORMAT, osmo_mobile_identity_to_str_c(OTC_SELECT, &map->l3.mi), \
|
||||
nri_bitlen, nri_v, nri_v, ##ARGS)
|
||||
|
||||
/* Get the NRI bits either from map->l3.nri, or extract NRI bits from TMSI.
|
||||
* The NRI possibly indicates which MSC is responsible. */
|
||||
if (map->l3.gmm_nri_container >= 0) {
|
||||
nri_v = map->l3.gmm_nri_container;
|
||||
/* The 'TMSI based NRI container' is always 10 bits long. If the relevant NRI length is configured to be
|
||||
* less than that, ignore the lower bits. */
|
||||
if (nri_bitlen < 10)
|
||||
nri_v >>= 10 - nri_bitlen;
|
||||
} else if (map->l3.mi.type == GSM_MI_TYPE_TMSI) {
|
||||
if (osmo_tmsi_nri_v_get(&nri_v, map->l3.mi.tmsi, nri_bitlen)) {
|
||||
LOG_NRI(LOGL_ERROR, "Unable to retrieve NRI from TMSI 0x%x, nri_bitlen == %u\n", map->l3.mi.tmsi,
|
||||
nri_bitlen);
|
||||
nri_v = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (*ss7 && (*ss7 != ss7_tmp)) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "IuCS and IuPS cannot be served from separate CS7 instances,"
|
||||
" cs7 instance %d != %d\n", (*ss7)->cfg.id, ss7_tmp->cfg.id);
|
||||
return -1;
|
||||
if (map->l3.from_other_plmn && nri_v >= 0) {
|
||||
/* If a subscriber was previously attached to a different PLMN, it might still send the other
|
||||
* PLMN's TMSI identity in an IMSI Attach. The LU sends a LAI indicating the previous PLMN. If
|
||||
* it mismatches our PLMN, ignore the NRI. */
|
||||
LOG_NRI(LOGL_DEBUG,
|
||||
"This Complete Layer 3 message indicates a switch from another PLMN. Ignoring the NRI.\n");
|
||||
nri_v = -1;
|
||||
}
|
||||
|
||||
*ss7 = ss7_tmp;
|
||||
if (nri_v >= 0)
|
||||
is_null_nri = osmo_nri_v_matches_ranges(nri_v, cnpool->use.null_nri_ranges);
|
||||
if (is_null_nri)
|
||||
LOG_NRI(LOGL_DEBUG, "this is a NULL-NRI\n");
|
||||
|
||||
osmo_sccp_addr_set_ssn(dest, OSMO_SCCP_SSN_RANAP);
|
||||
/* Iterate CN links to find one that matches the extracted NRI, and the next round-robin target for the case no
|
||||
* NRI match is found. */
|
||||
round_robin_next_nr = (map->l3.is_emerg ? cnpool->round_robin_next_emerg_nr : cnpool->round_robin_next_nr);
|
||||
llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) {
|
||||
bool nri_matches_cnlink = (nri_v >= 0 && osmo_nri_v_matches_ranges(nri_v, cnlink->use.nri_ranges));
|
||||
|
||||
if (!addr_has_pc_and_ssn(dest)) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "Invalid/incomplete %s remote-addr: %s\n",
|
||||
label, osmo_sccp_addr_name(*ss7, dest));
|
||||
return -1;
|
||||
if (!is_cnlink_usable(cnlink, map->l3.is_emerg)) {
|
||||
if (nri_matches_cnlink) {
|
||||
LOG_NRI(LOGL_DEBUG, "NRI matches %s %d, but this %s is currently not connected\n",
|
||||
cnpool->peer_name, cnlink->nr, cnpool->peer_name);
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_CNPOOL_SUBSCR_ATTACH_LOST);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Return CN link if it matches this NRI, with some debug logging. */
|
||||
if (nri_matches_cnlink) {
|
||||
if (is_null_nri) {
|
||||
LOG_NRI(LOGL_DEBUG, "NRI matches %s %d, but this NRI is also configured as NULL-NRI\n",
|
||||
cnpool->peer_name, cnlink->nr);
|
||||
} else {
|
||||
LOG_NRI(LOGL_INFO, "NRI match selects %s %d\n", cnpool->peer_name, cnlink->nr);
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_CNPOOL_SUBSCR_KNOWN);
|
||||
if (map->l3.is_emerg) {
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_CNPOOL_EMERG_FORWARDED);
|
||||
CNPOOL_CTR_INC(cnpool, CNPOOL_CTR_EMERG_FORWARDED);
|
||||
}
|
||||
return cnlink;
|
||||
}
|
||||
}
|
||||
|
||||
/* Figure out the next round-robin MSC. The MSCs may appear unsorted in net->mscs. Make sure to linearly
|
||||
* round robin the MSCs by number: pick the lowest msc->nr >= round_robin_next_nr, and also remember the
|
||||
* lowest available msc->nr to wrap back to that in case no next MSC is left.
|
||||
*
|
||||
* MSCs configured with `no allow-attach` do not accept new subscribers and hence must not be picked by
|
||||
* round-robin. Such an MSC still provides service for already attached subscribers: those that
|
||||
* successfully performed IMSI-Attach and have a TMSI with an NRI pointing at that MSC. We only avoid
|
||||
* adding IMSI-Attach of new subscribers. The idea is that the MSC is in a mode of off-loading
|
||||
* subscribers, and the MSC decides when each subscriber is off-loaded, by assigning the NULL-NRI in a
|
||||
* new TMSI (at the next periodical LU). So until the MSC decides to offload, an attached subscriber
|
||||
* remains attached to that MSC and is free to use its services.
|
||||
*/
|
||||
if (!cnlink->allow_attach)
|
||||
continue;
|
||||
/* Find the allowed cnlink with the lowest nr */
|
||||
if (!round_robin_first || cnlink->nr < round_robin_first->nr)
|
||||
round_robin_first = cnlink;
|
||||
/* Find the allowed cnlink with the lowest nr >= round_robin_next_nr */
|
||||
if (cnlink->nr >= round_robin_next_nr
|
||||
&& (!round_robin_next || cnlink->nr < round_robin_next->nr))
|
||||
round_robin_next = cnlink;
|
||||
}
|
||||
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Remote %s SCCP addr: %s\n",
|
||||
label, osmo_sccp_addr_name(*ss7, dest));
|
||||
return 0;
|
||||
if (nri_v >= 0 && !is_null_nri)
|
||||
LOG_NRI(LOGL_DEBUG, "No %s found for this NRI, doing round-robin\n", cnpool->peer_name);
|
||||
|
||||
/* No dedicated CN link found. Choose by round-robin.
|
||||
* If round_robin_next is NULL, there are either no more CN links at/after round_robin_next_nr, or none of
|
||||
* them are usable -- wrap to the start. */
|
||||
cnlink = round_robin_next ? : round_robin_first;
|
||||
if (!cnlink) {
|
||||
CNPOOL_CTR_INC(cnpool, CNPOOL_CTR_SUBSCR_NO_CNLINK);
|
||||
if (map->l3.is_emerg)
|
||||
CNPOOL_CTR_INC(cnpool, CNPOOL_CTR_EMERG_LOST);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOG_MAP(map, DCN, LOGL_INFO, "CN link round-robin selects %s %d\n", cnpool->peer_name, cnlink->nr);
|
||||
|
||||
if (is_null_nri)
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_CNPOOL_SUBSCR_REATTACH);
|
||||
else
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_CNPOOL_SUBSCR_NEW);
|
||||
|
||||
if (map->l3.is_emerg) {
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_CNPOOL_EMERG_FORWARDED);
|
||||
CNPOOL_CTR_INC(cnpool, CNPOOL_CTR_EMERG_FORWARDED);
|
||||
}
|
||||
|
||||
/* A CN link was picked by round-robin, so update the next round-robin nr to pick */
|
||||
if (map->l3.is_emerg)
|
||||
cnpool->round_robin_next_emerg_nr = cnlink->nr + 1;
|
||||
else
|
||||
cnpool->round_robin_next_nr = cnlink->nr + 1;
|
||||
return cnlink;
|
||||
#undef LOG_NRI
|
||||
}
|
||||
|
||||
int hnbgw_cnlink_init(struct hnb_gw *gw, const char *stp_host, uint16_t stp_port, const char *local_ip)
|
||||
static const struct rate_ctr_desc cnpool_ctr_description[] = {
|
||||
[CNPOOL_CTR_SUBSCR_NO_CNLINK] = {
|
||||
"cnpool:subscr:no_cnlink",
|
||||
"Complete Layer 3 requests lost because no connected CN link is found available",
|
||||
},
|
||||
[CNPOOL_CTR_EMERG_FORWARDED] = {
|
||||
"cnpool:emerg:forwarded",
|
||||
"Emergency call requests forwarded to a CN link (see also per-CN-link counters)",
|
||||
},
|
||||
[CNPOOL_CTR_EMERG_LOST] = {
|
||||
"cnpool:emerg:lost",
|
||||
"Emergency call requests lost because no CN link was found available",
|
||||
},
|
||||
};
|
||||
|
||||
const struct rate_ctr_group_desc iucs_ctrg_desc = {
|
||||
"iucs",
|
||||
"IuCS",
|
||||
OSMO_STATS_CLASS_GLOBAL,
|
||||
ARRAY_SIZE(cnpool_ctr_description),
|
||||
cnpool_ctr_description,
|
||||
};
|
||||
|
||||
const struct rate_ctr_group_desc iups_ctrg_desc = {
|
||||
"iups",
|
||||
"IuPS",
|
||||
OSMO_STATS_CLASS_GLOBAL,
|
||||
ARRAY_SIZE(cnpool_ctr_description),
|
||||
cnpool_ctr_description,
|
||||
};
|
||||
|
||||
static int hnbgw_cnpool_talloc_destructor(struct hnbgw_cnpool *cnpool)
|
||||
{
|
||||
struct hnbgw_cnlink *cnlink;
|
||||
struct osmo_ss7_instance *ss7;
|
||||
uint32_t local_pc;
|
||||
|
||||
OSMO_ASSERT(!gw->sccp.client);
|
||||
OSMO_ASSERT(!gw->sccp.cnlink);
|
||||
|
||||
ss7 = NULL;
|
||||
if (resolve_addr_name(&gw->sccp.iucs_remote_addr, &ss7,
|
||||
gw->config.iucs_remote_addr_name, "IuCS", (23 << 3) + 1))
|
||||
return -1;
|
||||
if (resolve_addr_name(&gw->sccp.iups_remote_addr, &ss7,
|
||||
gw->config.iups_remote_addr_name, "IuPS", (23 << 3) + 4))
|
||||
return -1;
|
||||
|
||||
if (!ss7) {
|
||||
LOGP(DRANAP, LOGL_NOTICE, "No cs7 instance configured for IuCS nor IuPS,"
|
||||
" creating default instance\n");
|
||||
ss7 = osmo_ss7_instance_find_or_create(gw, 0);
|
||||
ss7->cfg.primary_pc = (23 << 3) + 5;
|
||||
}
|
||||
|
||||
if (!osmo_ss7_pc_is_valid(ss7->cfg.primary_pc)) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "IuCS/IuPS uplink cannot be setup: CS7 instance %d has no point-code set\n",
|
||||
ss7->cfg.id);
|
||||
return -1;
|
||||
}
|
||||
local_pc = ss7->cfg.primary_pc;
|
||||
|
||||
osmo_sccp_make_addr_pc_ssn(&gw->sccp.local_addr, local_pc, OSMO_SCCP_SSN_RANAP);
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Local SCCP addr: %s\n", osmo_sccp_addr_name(ss7, &gw->sccp.local_addr));
|
||||
|
||||
gw->sccp.client = osmo_sccp_simple_client_on_ss7_id(gw, ss7->cfg.id, "OsmoHNBGW",
|
||||
local_pc, OSMO_SS7_ASP_PROT_M3UA,
|
||||
0, local_ip, stp_port, stp_host);
|
||||
if (!gw->sccp.client) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "Failed to init SCCP Client\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cnlink = talloc_zero(gw, struct hnbgw_cnlink);
|
||||
cnlink->gw = gw;
|
||||
INIT_LLIST_HEAD(&cnlink->map_list);
|
||||
cnlink->T_RafC.cb = cnlink_trafc_cb;
|
||||
cnlink->T_RafC.data = gw;
|
||||
cnlink->next_conn_id = 1000;
|
||||
|
||||
cnlink->sccp_user = osmo_sccp_user_bind_pc(gw->sccp.client, "OsmoHNBGW", sccp_sap_up,
|
||||
OSMO_SCCP_SSN_RANAP, gw->sccp.local_addr.pc);
|
||||
if (!cnlink->sccp_user) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "Failed to init SCCP User\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Remote SCCP addr: IuCS: %s\n",
|
||||
osmo_sccp_addr_name(ss7, &gw->sccp.iucs_remote_addr));
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Remote SCCP addr: IuPS: %s\n",
|
||||
osmo_sccp_addr_name(ss7, &gw->sccp.iups_remote_addr));
|
||||
|
||||
/* In sccp_sap_up() we expect the cnlink in the user's priv. */
|
||||
osmo_sccp_user_set_priv(cnlink->sccp_user, cnlink);
|
||||
|
||||
gw->sccp.cnlink = cnlink;
|
||||
osmo_nri_ranges_free(cnpool->vty.null_nri_ranges);
|
||||
cnpool->vty.null_nri_ranges = NULL;
|
||||
|
||||
while ((cnlink = llist_first_entry_or_null(&cnpool->cnlinks, struct hnbgw_cnlink, entry)))
|
||||
hnbgw_cnlink_term_and_free(cnlink);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct hnbgw_cnpool *hnbgw_cnpool_alloc(RANAP_CN_DomainIndicator_t domain)
|
||||
{
|
||||
struct hnbgw_cnpool *cnpool = talloc_zero(g_hnbgw, struct hnbgw_cnpool);
|
||||
OSMO_ASSERT(cnpool);
|
||||
|
||||
cnpool->domain = domain;
|
||||
cnpool->vty = (struct hnbgw_cnpool_cfg){
|
||||
.nri_bitlen = OSMO_NRI_BITLEN_DEFAULT,
|
||||
.null_nri_ranges = osmo_nri_ranges_alloc(cnpool),
|
||||
};
|
||||
OSMO_ASSERT(cnpool->vty.null_nri_ranges);
|
||||
INIT_LLIST_HEAD(&cnpool->cnlinks);
|
||||
|
||||
talloc_set_destructor(cnpool, hnbgw_cnpool_talloc_destructor);
|
||||
|
||||
switch (domain) {
|
||||
case DOMAIN_CS:
|
||||
cnpool->pool_name = "iucs";
|
||||
cnpool->peer_name = "msc";
|
||||
cnpool->default_remote_pc = DEFAULT_PC_MSC;
|
||||
cnpool->default_addr_name = DEFAULT_ADDR_NAME_MSC;
|
||||
cnpool->ctrs = rate_ctr_group_alloc(cnpool, &iucs_ctrg_desc, 0);
|
||||
break;
|
||||
case DOMAIN_PS:
|
||||
cnpool->pool_name = "iups";
|
||||
cnpool->peer_name = "sgsn";
|
||||
cnpool->default_remote_pc = DEFAULT_PC_SGSN;
|
||||
cnpool->default_addr_name = DEFAULT_ADDR_NAME_SGSN;
|
||||
cnpool->ctrs = rate_ctr_group_alloc(cnpool, &iups_ctrg_desc, 0);
|
||||
break;
|
||||
default:
|
||||
OSMO_ASSERT(0);
|
||||
}
|
||||
|
||||
return cnpool;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* hnb-gw specific code for HNBAP */
|
||||
/* hnb-gw specific code for HNBAP, 3GPP TS 25.469 */
|
||||
|
||||
/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
|
||||
* All Rights Reserved
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/gsm/gsm48.h>
|
||||
#include <osmocom/gsm/protocol/gsm_23_003.h>
|
||||
#include <osmocom/netif/stream.h>
|
||||
|
||||
#include <unistd.h>
|
||||
@@ -32,6 +33,8 @@
|
||||
#include <osmocom/hnbap/hnbap_common.h>
|
||||
#include <osmocom/ranap/iu_helpers.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnb.h>
|
||||
#include <osmocom/hnbgw/hnb_persistent.h>
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbap/hnbap_ies_defs.h>
|
||||
|
||||
@@ -49,16 +52,61 @@ static int hnbgw_hnbap_tx(struct hnb_context *ctx, struct msgb *msg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hnbgw_tx_hnb_register_rej(struct hnb_context *ctx)
|
||||
static int hnbgw_tx_error_ind(struct hnb_context *ctx, const HNBAP_Cause_t *cause,
|
||||
HNBAP_ProcedureCode_t proc_code, HNBAP_Criticality_t criticality,
|
||||
HNBAP_TriggeringMessage_t trig_msg)
|
||||
{
|
||||
HNBAP_ErrorIndication_t err_ind;
|
||||
HNBAP_ErrorIndicationIEs_t err_ind_ies;
|
||||
struct msgb *msg;
|
||||
int rc;
|
||||
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Tx ErrorIndication cause=%s\n", hnbap_cause_str(cause));
|
||||
|
||||
err_ind_ies.presenceMask = 0;
|
||||
err_ind_ies.cause = *cause;
|
||||
|
||||
if (proc_code != -1 || trig_msg != -1 || criticality != -1) {
|
||||
err_ind_ies.presenceMask |= ERRORINDICATIONIES_HNBAP_CRITICALITYDIAGNOSTICS_PRESENT;
|
||||
if (proc_code != -1)
|
||||
err_ind_ies.criticalityDiagnostics.procedureCode = &proc_code;
|
||||
if (trig_msg != -1)
|
||||
err_ind_ies.criticalityDiagnostics.triggeringMessage = &trig_msg;
|
||||
if (criticality != -1)
|
||||
err_ind_ies.criticalityDiagnostics.procedureCriticality = &criticality;
|
||||
}
|
||||
|
||||
memset(&err_ind, 0, sizeof(err_ind));
|
||||
rc = hnbap_encode_errorindicationies(&err_ind, &err_ind_ies);
|
||||
if (rc < 0) {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failure to encode ERROR-INDICATION to %s: rc=%d\n",
|
||||
ctx->identity_info, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
msg = hnbap_generate_initiating_message(HNBAP_ProcedureCode_id_ErrorIndication,
|
||||
HNBAP_Criticality_ignore,
|
||||
&asn_DEF_HNBAP_ErrorIndication,
|
||||
&err_ind);
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_ErrorIndication, &err_ind);
|
||||
|
||||
return hnbgw_hnbap_tx(ctx, msg);
|
||||
}
|
||||
|
||||
static int hnbgw_tx_hnb_register_rej(struct hnb_context *ctx, const HNBAP_Cause_t *cause)
|
||||
{
|
||||
HNBAP_HNBRegisterReject_t reject_out;
|
||||
HNBAP_HNBRegisterRejectIEs_t reject;
|
||||
struct msgb *msg;
|
||||
int rc;
|
||||
|
||||
reject.presenceMask = 0,
|
||||
reject.cause.present = HNBAP_Cause_PR_radioNetwork;
|
||||
reject.cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_unspecified;
|
||||
OSMO_ASSERT(cause);
|
||||
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Rejecting HNB Register Request cause=%s\n", hnbap_cause_str(cause));
|
||||
|
||||
reject.presenceMask = 0;
|
||||
reject.cause = *cause;
|
||||
|
||||
/* encode the Information Elements */
|
||||
memset(&reject_out, 0, sizeof(reject_out));
|
||||
@@ -69,7 +117,7 @@ static int hnbgw_tx_hnb_register_rej(struct hnb_context *ctx)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* generate a successfull outcome PDU */
|
||||
/* generate a unsuccessful outcome PDU */
|
||||
msg = hnbap_generate_unsuccessful_outcome(HNBAP_ProcedureCode_id_HNBRegister,
|
||||
HNBAP_Criticality_reject,
|
||||
&asn_DEF_HNBAP_HNBRegisterReject,
|
||||
@@ -99,7 +147,7 @@ static int hnbgw_tx_hnb_register_acc(struct hnb_context *ctx)
|
||||
|
||||
/* Single required response IE: RNC-ID */
|
||||
HNBAP_HNBRegisterAcceptIEs_t accept = {
|
||||
.rnc_id = ctx->gw->config.rnc_id
|
||||
.rnc_id = g_hnbgw->config.rnc_id
|
||||
};
|
||||
|
||||
/* encode the Information Elements */
|
||||
@@ -111,7 +159,7 @@ static int hnbgw_tx_hnb_register_acc(struct hnb_context *ctx)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* generate a successfull outcome PDU */
|
||||
/* generate a successful outcome PDU */
|
||||
msg = hnbap_generate_successful_outcome(HNBAP_ProcedureCode_id_HNBRegister,
|
||||
HNBAP_Criticality_reject,
|
||||
&asn_DEF_HNBAP_HNBRegisterAccept,
|
||||
@@ -119,13 +167,13 @@ static int hnbgw_tx_hnb_register_acc(struct hnb_context *ctx)
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_HNBRegisterAccept, &accept_out);
|
||||
|
||||
LOGHNB(ctx, DHNBAP, LOGL_NOTICE, "Accepting HNB-REGISTER-REQ from %s\n", ctx->identity_info);
|
||||
LOGHNB(ctx, DHNBAP, LOGL_NOTICE, "Accepting HNB-REGISTER-REQ\n");
|
||||
|
||||
return hnbgw_hnbap_tx(ctx, msg);
|
||||
}
|
||||
|
||||
|
||||
static int hnbgw_tx_ue_register_acc(struct ue_context *ue)
|
||||
static int hnbgw_tx_ue_register_acc(struct hnb_context *hnb, const char *imsi, uint32_t context_id)
|
||||
{
|
||||
HNBAP_UERegisterAccept_t accept_out;
|
||||
HNBAP_UERegisterAcceptIEs_t accept;
|
||||
@@ -136,17 +184,20 @@ static int hnbgw_tx_ue_register_acc(struct ue_context *ue)
|
||||
int rc;
|
||||
|
||||
encoded_imsi_len = ranap_imsi_encode(encoded_imsi,
|
||||
sizeof(encoded_imsi), ue->imsi);
|
||||
sizeof(encoded_imsi), imsi);
|
||||
|
||||
memset(&accept, 0, sizeof(accept));
|
||||
accept.uE_Identity.present = HNBAP_UE_Identity_PR_iMSI;
|
||||
OCTET_STRING_fromBuf(&accept.uE_Identity.choice.iMSI,
|
||||
(const char *)encoded_imsi, encoded_imsi_len);
|
||||
asn1_u24_to_bitstring(&accept.context_ID, &ctx_id, ue->context_id);
|
||||
asn1_u24_to_bitstring(&accept.context_ID, &ctx_id, context_id);
|
||||
|
||||
memset(&accept_out, 0, sizeof(accept_out));
|
||||
rc = hnbap_encode_ueregisteraccepties(&accept_out, &accept);
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, &accept.uE_Identity.choice.iMSI);
|
||||
if (rc < 0) {
|
||||
LOGHNB(hnb, DHNBAP, LOGL_ERROR,
|
||||
"Failed to encode HNBAP UE Register Accept message for UE IMSI-%s\n", imsi);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -155,19 +206,25 @@ static int hnbgw_tx_ue_register_acc(struct ue_context *ue)
|
||||
&asn_DEF_HNBAP_UERegisterAccept,
|
||||
&accept_out);
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, &accept.uE_Identity.choice.iMSI);
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_UERegisterAccept, &accept_out);
|
||||
|
||||
return hnbgw_hnbap_tx(ue->hnb, msg);
|
||||
rc = hnbgw_hnbap_tx(hnb, msg);
|
||||
if (rc)
|
||||
LOGHNB(hnb, DHNBAP, LOGL_ERROR,
|
||||
"Failed to enqueue HNBAP UE Register Accept message for UE IMSI-%s\n", imsi);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int hnbgw_tx_ue_register_rej_tmsi(struct hnb_context *hnb, HNBAP_UE_Identity_t *ue_id)
|
||||
static int hnbgw_tx_ue_register_rej(struct hnb_context *hnb, HNBAP_UE_Identity_t *ue_id, const struct HNBAP_Cause *cause)
|
||||
{
|
||||
HNBAP_UERegisterReject_t reject_out;
|
||||
HNBAP_UERegisterRejectIEs_t reject;
|
||||
char imsi[GSM23003_IMSI_MAX_DIGITS+1];
|
||||
struct msgb *msg;
|
||||
int rc;
|
||||
|
||||
OSMO_ASSERT(cause);
|
||||
|
||||
memset(&reject, 0, sizeof(reject));
|
||||
reject.uE_Identity.present = ue_id->present;
|
||||
|
||||
@@ -223,16 +280,23 @@ static int hnbgw_tx_ue_register_rej_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
|
||||
ue_id->choice.pTMSIRAI.rAI.rAC.size);
|
||||
break;
|
||||
|
||||
case HNBAP_UE_Identity_PR_iMSI:
|
||||
ranap_bcd_decode(imsi, sizeof(imsi), ue_id->choice.iMSI.buf, ue_id->choice.iMSI.size);
|
||||
LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "REJ UE_Id IMSI %s\n", imsi);
|
||||
|
||||
OCTET_STRING_fromBuf(&reject.uE_Identity.choice.iMSI,
|
||||
(const char *)ue_id->choice.iMSI.buf, ue_id->choice.iMSI.size);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Cannot compose UE Register Reject:"
|
||||
" unsupported UE ID (present=%d)\n", ue_id->present);
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Rejecting UE Register Request: TMSI identity registration is switched off\n");
|
||||
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Rejecting UE Register Request cause=%s\n", hnbap_cause_str(cause));
|
||||
|
||||
reject.cause.present = HNBAP_Cause_PR_radioNetwork;
|
||||
reject.cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_invalid_UE_identity;
|
||||
reject.cause = *cause;
|
||||
|
||||
memset(&reject_out, 0, sizeof(reject_out));
|
||||
rc = hnbap_encode_ueregisterrejecties(&reject_out, &reject);
|
||||
@@ -265,6 +329,9 @@ static int hnbgw_tx_ue_register_rej_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING,
|
||||
&reject.uE_Identity.choice.pTMSIRAI.rAI.rAC);
|
||||
break;
|
||||
case HNBAP_UE_Identity_PR_iMSI:
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING,
|
||||
&reject.uE_Identity.choice.iMSI);
|
||||
|
||||
default:
|
||||
/* should never happen after above switch() */
|
||||
@@ -276,14 +343,13 @@ static int hnbgw_tx_ue_register_rej_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
|
||||
return hnbgw_hnbap_tx(hnb, msg);
|
||||
}
|
||||
|
||||
static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Identity_t *ue_id)
|
||||
static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Identity_t *ue_id, uint32_t context_id)
|
||||
{
|
||||
HNBAP_UERegisterAccept_t accept_out;
|
||||
HNBAP_UERegisterAcceptIEs_t accept;
|
||||
struct msgb *msg;
|
||||
uint32_t ctx_id;
|
||||
uint32_t tmsi = 0;
|
||||
struct ue_context *ue;
|
||||
int rc;
|
||||
|
||||
memset(&accept, 0, sizeof(accept));
|
||||
@@ -322,29 +388,19 @@ static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
|
||||
break;
|
||||
|
||||
default:
|
||||
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Unsupportedccept UE ID (present=%d)\n", ue_id->present);
|
||||
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Unsupported HNBAP UE Identity type (present=%d)\n", ue_id->present);
|
||||
return -1;
|
||||
}
|
||||
|
||||
tmsi = ntohl(tmsi);
|
||||
LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "HNBAP register with TMSI %x\n", tmsi);
|
||||
|
||||
ue = ue_context_by_tmsi(hnb->gw, tmsi);
|
||||
if (!ue)
|
||||
ue = ue_context_alloc(hnb, NULL, tmsi);
|
||||
|
||||
asn1_u24_to_bitstring(&accept.context_ID, &ctx_id, ue->context_id);
|
||||
asn1_u24_to_bitstring(&accept.context_ID, &ctx_id, context_id);
|
||||
|
||||
memset(&accept_out, 0, sizeof(accept_out));
|
||||
rc = hnbap_encode_ueregisteraccepties(&accept_out, &accept);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
msg = hnbap_generate_successful_outcome(HNBAP_ProcedureCode_id_UERegister,
|
||||
HNBAP_Criticality_reject,
|
||||
&asn_DEF_HNBAP_UERegisterAccept,
|
||||
&accept_out);
|
||||
|
||||
/* free 'accept', now encoded in 'accept_out' */
|
||||
switch (ue_id->present) {
|
||||
case HNBAP_UE_Identity_PR_tMSILAI:
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_BIT_STRING,
|
||||
@@ -371,65 +427,166 @@ static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Ident
|
||||
break;
|
||||
}
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_UERegisterAccept, &accept_out);
|
||||
if (rc < 0) {
|
||||
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Failed to encode HNBAP UE Register Accept for TMSI 0x%08x\n", tmsi);
|
||||
/* Encoding failed. Nothing in 'accept_out'. */
|
||||
return rc;
|
||||
}
|
||||
|
||||
return hnbgw_hnbap_tx(hnb, msg);
|
||||
/* Encoding successful, transmit, then free 'accept_out'. */
|
||||
msg = hnbap_generate_successful_outcome(HNBAP_ProcedureCode_id_UERegister,
|
||||
HNBAP_Criticality_reject,
|
||||
&asn_DEF_HNBAP_UERegisterAccept,
|
||||
&accept_out);
|
||||
rc = hnbgw_hnbap_tx(hnb, msg);
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_UERegisterAccept, &accept_out);
|
||||
if (rc)
|
||||
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Failed to transmit HNBAP UE Register Accept for TMSI 0x%08x\n", tmsi);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int hnbgw_rx_hnb_deregister(struct hnb_context *ctx, ANY_t *in)
|
||||
{
|
||||
HNBAP_HNBDe_RegisterIEs_t ies;
|
||||
HNBAP_Cause_t cause = {};
|
||||
int rc;
|
||||
|
||||
rc = hnbap_decode_hnbde_registeries(&ies, in);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
if (rc < 0) {
|
||||
cause.present = HNBAP_Cause_PR_protocol;
|
||||
cause.choice.radioNetwork = HNBAP_CauseProtocol_unspecified;
|
||||
return hnbgw_tx_error_ind(ctx, &cause, HNBAP_ProcedureCode_id_HNBDe_Register, HNBAP_Criticality_ignore,
|
||||
HNBAP_TriggeringMessage_initiating_message);
|
||||
}
|
||||
|
||||
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "HNB-DE-REGISTER cause=%s\n", hnbap_cause_str(&ies.cause));
|
||||
|
||||
hnbap_free_hnbde_registeries(&ies);
|
||||
hnb_context_release(ctx);
|
||||
ctx->hnb_registered = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool is_asn1_octet_string_empty(const OCTET_STRING_t *val)
|
||||
{
|
||||
return !val || !val->buf || !val->size;
|
||||
}
|
||||
|
||||
static bool is_asn1_bit_string_empty(const BIT_STRING_t *val)
|
||||
{
|
||||
return !val || !val->buf || !val->size;
|
||||
}
|
||||
|
||||
static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
|
||||
{
|
||||
struct hnb_context *hnb;
|
||||
struct hnb_persistent *hnbp;
|
||||
struct hnb_context *hnb, *tmp;
|
||||
HNBAP_HNBRegisterRequestIEs_t ies;
|
||||
int rc;
|
||||
struct osmo_fd *ofd = osmo_stream_srv_get_ofd(ctx->conn);
|
||||
char identity_str[256];
|
||||
const char *cell_id_str;
|
||||
struct timespec tp;
|
||||
HNBAP_Cause_t cause = {};
|
||||
struct osmo_sockaddr cur_osa = {};
|
||||
socklen_t len = sizeof(cur_osa);
|
||||
|
||||
rc = hnbap_decode_hnbregisterrequesties(&ies, in);
|
||||
if (rc < 0) {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failure to decode HNB-REGISTER-REQ from %s: rc=%d\n",
|
||||
ctx->identity_info, rc);
|
||||
return rc;
|
||||
if (rc < 0
|
||||
/* CID#465551: make sure that actual values ended up in the asn1 octet strings: */
|
||||
|| is_asn1_octet_string_empty(&ies.lac)
|
||||
|| is_asn1_octet_string_empty(&ies.sac)
|
||||
|| is_asn1_octet_string_empty(&ies.rac)
|
||||
|| is_asn1_bit_string_empty(&ies.cellIdentity)
|
||||
|| is_asn1_octet_string_empty(&ies.plmNidentity)) {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failure to decode HNB-REGISTER-REQ: rc=%d\n", rc);
|
||||
cause.present = HNBAP_Cause_PR_protocol;
|
||||
cause.choice.radioNetwork = HNBAP_CauseProtocol_unspecified;
|
||||
return hnbgw_tx_hnb_register_rej(ctx, &cause);
|
||||
}
|
||||
asn1_strncpy(identity_str, &ies.hnB_Identity.hNB_Identity_Info, sizeof(identity_str));
|
||||
|
||||
/* copy all identity parameters from the message to ctx */
|
||||
asn1_strncpy(ctx->identity_info, &ies.hnB_Identity.hNB_Identity_Info,
|
||||
sizeof(ctx->identity_info));
|
||||
OSMO_STRLCPY_ARRAY(ctx->identity_info, identity_str);
|
||||
|
||||
/* We want to use struct umts_cell_id as hashtable key. If it ever happens to contain any padding bytes, make
|
||||
* sure everything is deterministically zero. */
|
||||
memset(&ctx->id, 0, sizeof(ctx->id));
|
||||
ctx->id.lac = asn1str_to_u16(&ies.lac);
|
||||
ctx->id.sac = asn1str_to_u16(&ies.sac);
|
||||
ctx->id.rac = asn1str_to_u8(&ies.rac);
|
||||
ctx->id.cid = asn1bitstr_to_u28(&ies.cellIdentity);
|
||||
gsm48_mcc_mnc_from_bcd(ies.plmNidentity.buf, &ctx->id.mcc, &ctx->id.mnc);
|
||||
osmo_plmn_from_bcd(ies.plmNidentity.buf, &ctx->id.plmn);
|
||||
cell_id_str = umts_cell_id_to_str(&ctx->id);
|
||||
|
||||
llist_for_each_entry(hnb, &ctx->gw->hnb_list, list) {
|
||||
if (getpeername(ofd->fd, &cur_osa.u.sa, &len) < 0) {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "HNB-REGISTER-REQ %s: rejecting due to getpeername() error: %s\n",
|
||||
cell_id_str, strerror(errno));
|
||||
hnbap_free_hnbregisterrequesties(&ies);
|
||||
cause.present = HNBAP_Cause_PR_radioNetwork;
|
||||
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_hNB_parameter_mismatch;
|
||||
return hnbgw_tx_hnb_register_rej(ctx, &cause);
|
||||
}
|
||||
|
||||
hnbp = hnb_persistent_find_by_id(&ctx->id);
|
||||
if (!hnbp && g_hnbgw->config.accept_all_hnb)
|
||||
hnbp = hnb_persistent_alloc(&ctx->id);
|
||||
|
||||
if (!hnbp) {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_NOTICE, "Rejecting unknonwn HNB with identity %s\n", identity_str);
|
||||
hnbap_free_hnbregisterrequesties(&ies);
|
||||
cause.present = HNBAP_Cause_PR_radioNetwork;
|
||||
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_unauthorised_HNB;
|
||||
return hnbgw_tx_hnb_register_rej(ctx, &cause);
|
||||
}
|
||||
|
||||
ctx->persistent = hnbp;
|
||||
hnbp->ctx = ctx;
|
||||
|
||||
HNBP_CTR_INC(hnbp, HNB_CTR_IUH_ESTABLISHED);
|
||||
rc = osmo_clock_gettime(CLOCK_MONOTONIC, &tp);
|
||||
hnbp->updowntime = (rc < 0) ? 0 : tp.tv_sec;
|
||||
|
||||
llist_for_each_entry_safe(hnb, tmp, &g_hnbgw->hnb_list, list) {
|
||||
if (hnb->hnb_registered && ctx != hnb && memcmp(&ctx->id, &hnb->id, sizeof(ctx->id)) == 0) {
|
||||
struct osmo_fd *ofd = osmo_stream_srv_get_ofd(ctx->conn);
|
||||
char *name = osmo_sock_get_name(ctx, ofd->fd);
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "rejecting HNB-REGISTER-REQ with duplicate cell identity "
|
||||
"MCC=%u,MNC=%u,LAC=%u,RAC=%u,SAC=%u,CID=%u from %s\n",
|
||||
ctx->id.mcc, ctx->id.mnc, ctx->id.lac, ctx->id.rac, ctx->id.sac, ctx->id.cid, name);
|
||||
talloc_free(name);
|
||||
return hnbgw_tx_hnb_register_rej(ctx);
|
||||
/* If it's coming from the same remote IP addr+port, then it must be our internal
|
||||
* fault (bug), and we release the old context to keep going... */
|
||||
struct osmo_fd *other_fd = osmo_stream_srv_get_ofd(hnb->conn);
|
||||
struct osmo_sockaddr other_osa = {};
|
||||
socklen_t len = sizeof(other_osa);
|
||||
if (getpeername(other_fd->fd, &other_osa.u.sa, &len) < 0) {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "BUG! Found old registered HNB with invalid socket, releasing it\n");
|
||||
hnb_context_release(hnb);
|
||||
continue;
|
||||
}
|
||||
if (osmo_sockaddr_cmp(&cur_osa, &other_osa) == 0) {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "BUG! Found old registered HNB with same remote address, releasing it\n");
|
||||
hnb_context_release(hnb);
|
||||
continue;
|
||||
} /* else: addresses are different, we continue below */
|
||||
|
||||
/* If new conn registering same HNB is from anoter remote addr+port, let's reject it to avoid
|
||||
* misconfigurations or someone trying to impersonate an already working HNB: */
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "rejecting HNB-REGISTER-REQ with duplicate cell identity %s\n", cell_id_str);
|
||||
hnbap_free_hnbregisterrequesties(&ies);
|
||||
cause.present = HNBAP_Cause_PR_radioNetwork;
|
||||
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_hNB_parameter_mismatch;
|
||||
return hnbgw_tx_hnb_register_rej(ctx, &cause);
|
||||
}
|
||||
}
|
||||
|
||||
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "HNB-REGISTER-REQ %s %s%s\n",
|
||||
ctx->identity_info, cell_id_str, ctx->hnb_registered ? " (re-connecting)" : "");
|
||||
|
||||
/* The HNB is already registered, and we are seeing a new HNB Register Request. The HNB has restarted
|
||||
* without us noticing. Clearly, the HNB does not expect any UE state to be active here, so discard any
|
||||
* UE contexts and SCCP connections associated with this HNB. */
|
||||
LOGHNB(ctx, DHNBAP, LOGL_NOTICE, "HNB (re)connecting, discarding all previous UE state\n");
|
||||
hnb_context_release_ue_state(ctx);
|
||||
|
||||
ctx->hnb_registered = true;
|
||||
|
||||
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "HNB-REGISTER-REQ from %s\n", ctx->identity_info);
|
||||
hnb_persistent_registered(ctx->persistent);
|
||||
|
||||
/* Send HNBRegisterAccept */
|
||||
rc = hnbgw_tx_hnb_register_acc(ctx);
|
||||
@@ -440,13 +597,26 @@ static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
|
||||
static int hnbgw_rx_ue_register_req(struct hnb_context *ctx, ANY_t *in)
|
||||
{
|
||||
HNBAP_UERegisterRequestIEs_t ies;
|
||||
struct ue_context *ue;
|
||||
char imsi[16];
|
||||
HNBAP_Cause_t cause = {};
|
||||
char imsi[GSM23003_IMSI_MAX_DIGITS+1];
|
||||
int rc;
|
||||
|
||||
rc = hnbap_decode_ueregisterrequesties(&ies, in);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
if (rc < 0) {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failure to decode UE-REGISTER-REQ: rc=%d\n", rc);
|
||||
cause.present = HNBAP_Cause_PR_protocol;
|
||||
cause.choice.radioNetwork = HNBAP_CauseProtocol_unspecified;
|
||||
return hnbgw_tx_error_ind(ctx, &cause, HNBAP_ProcedureCode_id_UERegister, HNBAP_Criticality_reject,
|
||||
HNBAP_TriggeringMessage_initiating_message);
|
||||
}
|
||||
|
||||
if (!ctx->hnb_registered) {
|
||||
/* UE registration requires prior HNB registration; reject with proper cause */
|
||||
cause.present = HNBAP_Cause_PR_radioNetwork;
|
||||
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_hNB_not_registered;
|
||||
rc = hnbgw_tx_ue_register_rej(ctx, &ies.uE_Identity, &cause);
|
||||
goto free_and_return_rc;
|
||||
}
|
||||
|
||||
switch (ies.uE_Identity.present) {
|
||||
case HNBAP_UE_Identity_PR_iMSI:
|
||||
@@ -463,50 +633,63 @@ static int hnbgw_rx_ue_register_req(struct hnb_context *ctx, ANY_t *in)
|
||||
break;
|
||||
case HNBAP_UE_Identity_PR_tMSILAI:
|
||||
case HNBAP_UE_Identity_PR_pTMSIRAI:
|
||||
if (ctx->gw->config.hnbap_allow_tmsi)
|
||||
rc = hnbgw_tx_ue_register_acc_tmsi(ctx, &ies.uE_Identity);
|
||||
else
|
||||
rc = hnbgw_tx_ue_register_rej_tmsi(ctx, &ies.uE_Identity);
|
||||
if (g_hnbgw->config.hnbap_allow_tmsi) {
|
||||
rc = hnbgw_tx_ue_register_acc_tmsi(ctx, &ies.uE_Identity, get_next_ue_ctx_id());
|
||||
} else {
|
||||
cause.present = HNBAP_Cause_PR_radioNetwork;
|
||||
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_invalid_UE_identity;
|
||||
rc = hnbgw_tx_ue_register_rej(ctx, &ies.uE_Identity, &cause);
|
||||
}
|
||||
/* all has been handled by TMSI, skip the IMSI code below */
|
||||
hnbap_free_ueregisterrequesties(&ies);
|
||||
return rc;
|
||||
goto free_and_return_rc;
|
||||
default:
|
||||
LOGHNB(ctx, DHNBAP, LOGL_NOTICE, "UE-REGISTER-REQ with unsupported UE Id type %d\n",
|
||||
ies.uE_Identity.present);
|
||||
hnbap_free_ueregisterrequesties(&ies);
|
||||
return rc;
|
||||
cause.present = HNBAP_Cause_PR_radioNetwork;
|
||||
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_invalid_UE_identity;
|
||||
rc = hnbgw_tx_ue_register_rej(ctx, &ies.uE_Identity, &cause);
|
||||
goto free_and_return_rc;
|
||||
}
|
||||
|
||||
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "UE-REGISTER-REQ ID_type=%d imsi=%s cause=%ld\n",
|
||||
ies.uE_Identity.present, imsi, ies.registration_Cause);
|
||||
|
||||
ue = ue_context_by_imsi(ctx->gw, imsi);
|
||||
if (!ue)
|
||||
ue = ue_context_alloc(ctx, imsi, 0);
|
||||
|
||||
hnbap_free_ueregisterrequesties(&ies);
|
||||
/* Send UERegisterAccept */
|
||||
return hnbgw_tx_ue_register_acc(ue);
|
||||
rc = hnbgw_tx_ue_register_acc(ctx, imsi, get_next_ue_ctx_id());
|
||||
if (rc < 0)
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failed to transmit HNBAP UE Register Accept for IMSI %s\n", imsi);
|
||||
free_and_return_rc:
|
||||
hnbap_free_ueregisterrequesties(&ies);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int hnbgw_rx_ue_deregister(struct hnb_context *ctx, ANY_t *in)
|
||||
{
|
||||
HNBAP_UEDe_RegisterIEs_t ies;
|
||||
struct ue_context *ue;
|
||||
HNBAP_Cause_t cause = {};
|
||||
int rc;
|
||||
uint32_t ctxid;
|
||||
|
||||
rc = hnbap_decode_uede_registeries(&ies, in);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
if (rc < 0) {
|
||||
cause.present = HNBAP_Cause_PR_protocol;
|
||||
cause.choice.protocol = HNBAP_CauseProtocol_unspecified;
|
||||
return hnbgw_tx_error_ind(ctx, &cause, HNBAP_ProcedureCode_id_UEDe_Register,
|
||||
HNBAP_Criticality_ignore, HNBAP_TriggeringMessage_initiating_message);
|
||||
}
|
||||
|
||||
ctxid = asn1bitstr_to_u24(&ies.context_ID);
|
||||
|
||||
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "UE-DE-REGISTER context=%u cause=%s\n", ctxid, hnbap_cause_str(&ies.cause));
|
||||
|
||||
ue = ue_context_by_id(ctx->gw, ctxid);
|
||||
if (ue)
|
||||
ue_context_free(ue);
|
||||
if (!ctx->hnb_registered) {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_NOTICE, "UE-DE-REGISTER context=%u cause=%s not permitted; HNB not registered\n",
|
||||
ctxid, hnbap_cause_str(&ies.cause));
|
||||
cause.present = HNBAP_Cause_PR_radioNetwork;
|
||||
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_hNB_not_registered;
|
||||
hnbgw_tx_error_ind(ctx, &cause, HNBAP_ProcedureCode_id_UEDe_Register,
|
||||
HNBAP_Criticality_ignore, HNBAP_TriggeringMessage_initiating_message);
|
||||
} else {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "UE-DE-REGISTER context=%u cause=%s\n", ctxid, hnbap_cause_str(&ies.cause));
|
||||
}
|
||||
|
||||
hnbap_free_uede_registeries(&ies);
|
||||
return 0;
|
||||
@@ -529,6 +712,10 @@ static int hnbgw_rx_err_ind(struct hnb_context *hnb, ANY_t *in)
|
||||
|
||||
static int hnbgw_rx_initiating_msg(struct hnb_context *hnb, HNBAP_InitiatingMessage_t *imsg)
|
||||
{
|
||||
HNBAP_Cause_t cause = {
|
||||
.present = HNBAP_Cause_PR_protocol,
|
||||
.choice.protocol = HNBAP_CauseProtocol_unspecified,
|
||||
};
|
||||
int rc = 0;
|
||||
|
||||
switch (imsg->procedureCode) {
|
||||
@@ -538,7 +725,7 @@ static int hnbgw_rx_initiating_msg(struct hnb_context *hnb, HNBAP_InitiatingMess
|
||||
case HNBAP_ProcedureCode_id_HNBDe_Register: /* 8.3 */
|
||||
rc = hnbgw_rx_hnb_deregister(hnb, &imsg->value);
|
||||
break;
|
||||
case HNBAP_ProcedureCode_id_UERegister: /* 8.4 */
|
||||
case HNBAP_ProcedureCode_id_UERegister: /* 8.4 */
|
||||
rc = hnbgw_rx_ue_register_req(hnb, &imsg->value);
|
||||
break;
|
||||
case HNBAP_ProcedureCode_id_UEDe_Register: /* 8.5 */
|
||||
@@ -547,15 +734,17 @@ static int hnbgw_rx_initiating_msg(struct hnb_context *hnb, HNBAP_InitiatingMess
|
||||
case HNBAP_ProcedureCode_id_ErrorIndication: /* 8.6 */
|
||||
rc = hnbgw_rx_err_ind(hnb, &imsg->value);
|
||||
break;
|
||||
case HNBAP_ProcedureCode_id_TNLUpdate: /* 8.9 */
|
||||
case HNBAP_ProcedureCode_id_TNLUpdate: /* 8.9 */
|
||||
case HNBAP_ProcedureCode_id_HNBConfigTransfer: /* 8.10 */
|
||||
case HNBAP_ProcedureCode_id_RelocationComplete: /* 8.11 */
|
||||
case HNBAP_ProcedureCode_id_U_RNTIQuery: /* 8.12 */
|
||||
case HNBAP_ProcedureCode_id_privateMessage:
|
||||
LOGHNB(hnb, DHNBAP, LOGL_NOTICE, "Unimplemented HNBAP Procedure %ld\n", imsg->procedureCode);
|
||||
rc = hnbgw_tx_error_ind(hnb, &cause, imsg->procedureCode, -1, HNBAP_TriggeringMessage_initiating_message);
|
||||
break;
|
||||
default:
|
||||
LOGHNB(hnb, DHNBAP, LOGL_NOTICE, "Unknown HNBAP Procedure %ld\n", imsg->procedureCode);
|
||||
rc = hnbgw_tx_error_ind(hnb, &cause, imsg->procedureCode, -1, HNBAP_TriggeringMessage_initiating_message);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -572,8 +761,7 @@ static int hnbgw_rx_unsuccessful_outcome_msg(struct hnb_context *hnb, HNBAP_Unsu
|
||||
{
|
||||
/* We don't care much about HNBAP */
|
||||
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Received Unsuccessful Outcome, procedureCode %ld, criticality %ld,"
|
||||
" cell mcc %u mnc %u lac %u rac %u sac %u cid %u\n", msg->procedureCode, msg->criticality,
|
||||
hnb->id.mcc, hnb->id.mnc, hnb->id.lac, hnb->id.rac, hnb->id.sac, hnb->id.cid);
|
||||
" cell %s\n", msg->procedureCode, msg->criticality, umts_cell_id_to_str(&hnb->id));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
331
src/osmo-hnbgw/hnbgw_l3.c
Normal file
331
src/osmo-hnbgw/hnbgw_l3.c
Normal file
@@ -0,0 +1,331 @@
|
||||
/* OsmoHNBGW implementation of CS and PS Level3 message decoding (NAS PDU) */
|
||||
|
||||
/* Copyright 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "asn1helpers.h"
|
||||
|
||||
#include <osmocom/gsm/gsm48.h>
|
||||
#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/hnbgw_ranap.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/ranap/ranap_ies_defs.h>
|
||||
|
||||
static const struct tlv_definition gsm48_gmm_att_tlvdef = {
|
||||
.def = {
|
||||
[GSM48_IE_GMM_CIPH_CKSN] = { TLV_TYPE_FIXED, 1 },
|
||||
[GSM48_IE_GMM_TIMER_READY] = { TLV_TYPE_TV, 1 },
|
||||
[GSM48_IE_GMM_TMSI_BASED_NRI_C] = { TLV_TYPE_TLV },
|
||||
[GSM48_IE_GMM_ALLOC_PTMSI] = { TLV_TYPE_TLV, 0 },
|
||||
[GSM48_IE_GMM_PTMSI_SIG] = { TLV_TYPE_FIXED, 3 },
|
||||
[GSM48_IE_GMM_AUTH_RAND] = { TLV_TYPE_FIXED, 16 },
|
||||
[GSM48_IE_GMM_AUTH_SRES] = { TLV_TYPE_FIXED, 4 },
|
||||
[GSM48_IE_GMM_IMEISV] = { TLV_TYPE_TLV, 0 },
|
||||
[GSM48_IE_GMM_DRX_PARAM] = { TLV_TYPE_FIXED, 2 },
|
||||
[GSM48_IE_GMM_MS_NET_CAPA] = { TLV_TYPE_TLV, 0 },
|
||||
[GSM48_IE_GMM_PDP_CTX_STATUS] = { TLV_TYPE_TLV, 0 },
|
||||
[GSM48_IE_GMM_PS_LCS_CAPA] = { TLV_TYPE_TLV, 0 },
|
||||
[GSM48_IE_GMM_GMM_MBMS_CTX_ST] = { TLV_TYPE_TLV, 0 },
|
||||
},
|
||||
};
|
||||
|
||||
static void decode_gmm_tlv(struct osmo_mobile_identity *mi, int *nri,
|
||||
const uint8_t *tlv_data, size_t tlv_len, bool allow_hex)
|
||||
{
|
||||
struct tlv_parsed tp;
|
||||
struct tlv_p_entry *e;
|
||||
|
||||
tlv_parse(&tp, &gsm48_gmm_att_tlvdef, tlv_data, tlv_len, 0, 0);
|
||||
|
||||
e = TLVP_GET(&tp, GSM48_IE_GMM_TMSI_BASED_NRI_C);
|
||||
if (e) {
|
||||
*nri = e->val[0];
|
||||
*nri <<= 2;
|
||||
*nri |= e->val[1] >> 6;
|
||||
}
|
||||
|
||||
/* RAU Req: 9.4.14.5 P-TMSI (Iu mode only): "This IE shall be included by the MS." */
|
||||
e = TLVP_GET(&tp, GSM48_IE_GMM_ALLOC_PTMSI);
|
||||
if (mi && e)
|
||||
osmo_mobile_identity_decode(mi, e->val, e->len, allow_hex);
|
||||
}
|
||||
|
||||
/* Parse 3GPP TS 24.008 § 9.4.1 Attach request */
|
||||
static int mobile_identity_decode_from_gmm_att_req(struct osmo_mobile_identity *mi,
|
||||
struct osmo_routing_area_id *old_ra,
|
||||
int *nri,
|
||||
const uint8_t *l3_data, size_t l3_len, bool allow_hex)
|
||||
{
|
||||
const struct gsm48_hdr *gh = (void *)l3_data;
|
||||
const uint8_t *cur = gh->data;
|
||||
const uint8_t *end = l3_data + l3_len;
|
||||
const uint8_t *mi_data;
|
||||
uint8_t mi_len;
|
||||
uint8_t msnc_len;
|
||||
uint8_t ms_ra_acc_cap_len;
|
||||
int rc;
|
||||
|
||||
if (l3_len < 26)
|
||||
return -ENOSPC;
|
||||
|
||||
/* MS network capability 10.5.5.12 */
|
||||
msnc_len = *cur++;
|
||||
if (l3_len < (msnc_len + (cur - l3_data)))
|
||||
return -ENOSPC;
|
||||
cur += msnc_len;
|
||||
|
||||
/* aTTACH Type 10.5.5.2 + Ciphering key sequence number 10.5.1.2 */
|
||||
cur++;
|
||||
|
||||
/* DRX parameter 10.5.5.6 */
|
||||
cur += 2;
|
||||
|
||||
/* Mobile Identity (P-TMSI or IMSI) 10.5.1.4 */
|
||||
mi_len = *cur++;
|
||||
mi_data = cur;
|
||||
if (l3_len < (mi_len + (cur - l3_data)))
|
||||
return -ENOSPC;
|
||||
cur += mi_len;
|
||||
|
||||
rc = osmo_mobile_identity_decode(mi, mi_data, mi_len, allow_hex);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Old routing area identification 10.5.5.15. */
|
||||
rc = osmo_routing_area_id_decode(old_ra, cur, end - cur);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
cur += rc;
|
||||
|
||||
/* MS Radio Access Capability 10.5.5.12a */
|
||||
ms_ra_acc_cap_len = *cur++;
|
||||
if (l3_len < (ms_ra_acc_cap_len + (cur - l3_data)))
|
||||
return -ENOSPC;
|
||||
cur += ms_ra_acc_cap_len;
|
||||
|
||||
if (l3_len == (cur - l3_data))
|
||||
return 0; /* No Optional TLV section */
|
||||
|
||||
/* Mobile identity = NULL: already obtained from Mandatory IE above.*/
|
||||
decode_gmm_tlv(NULL, nri, cur, end - cur, allow_hex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse 24.008 9.4.14 RAU Request */
|
||||
static int mobile_identity_decode_from_gmm_rau_req(struct osmo_mobile_identity *mi,
|
||||
struct osmo_routing_area_id *old_ra,
|
||||
int *nri,
|
||||
const uint8_t *l3_data, size_t l3_len, bool allow_hex)
|
||||
{
|
||||
const struct gsm48_hdr *gh = (void *)l3_data;
|
||||
const uint8_t *cur = gh->data;
|
||||
const uint8_t *end = l3_data + l3_len;
|
||||
uint8_t ms_ra_acc_cap_len;
|
||||
int rc;
|
||||
|
||||
/* all mandatory fields + variable length MS Radio Cap (min value) would be 15 bytes.
|
||||
* But even short radio capabilities we should handle with 14 bytes */
|
||||
if (l3_len < 14)
|
||||
return -ENOSPC;
|
||||
|
||||
/* V: Update Type 10.5.5.18 */
|
||||
cur++;
|
||||
|
||||
/* V: Old routing area identification 10.5.5.15 */
|
||||
rc = osmo_routing_area_id_decode(old_ra, cur, end - cur);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
cur += rc;
|
||||
if (cur >= end)
|
||||
return -ENOSPC;
|
||||
|
||||
/* LV: MS Radio Access Capability 10.5.5.12a */
|
||||
ms_ra_acc_cap_len = *cur++;
|
||||
if (l3_len < (ms_ra_acc_cap_len + (cur - l3_data)))
|
||||
return -ENOSPC;
|
||||
cur += ms_ra_acc_cap_len;
|
||||
|
||||
if (l3_len == (cur - l3_data))
|
||||
return 0; /* No Optional TLV section */
|
||||
decode_gmm_tlv(mi, nri, cur, end - cur, allow_hex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* CS MM: Determine mobile identity, from_other_plmn, is_emerg. */
|
||||
static int peek_l3_ul_nas_cs(struct hnbgw_context_map *map, const uint8_t *nas_pdu, size_t len,
|
||||
const struct osmo_plmn_id *local_plmn)
|
||||
{
|
||||
const struct gsm48_hdr *gh = (const struct gsm48_hdr *)nas_pdu;
|
||||
struct osmo_location_area_id old_lai;
|
||||
const struct gsm48_loc_upd_req *lu;
|
||||
struct gsm48_service_request *cm;
|
||||
|
||||
osmo_mobile_identity_decode_from_l3_buf(&map->l3.mi, nas_pdu, len, false);
|
||||
|
||||
switch (map->l3.gsm48_pdisc) {
|
||||
case GSM48_PDISC_MM:
|
||||
/* Get is_emerg and from_other_plmn */
|
||||
switch (map->l3.gsm48_msg_type) {
|
||||
case GSM48_MT_MM_LOC_UPD_REQUEST:
|
||||
if (len < sizeof(*gh) + sizeof(*lu)) {
|
||||
LOGP(DCN, LOGL_ERROR, "LU Req message too short\n");
|
||||
break;
|
||||
}
|
||||
lu = (struct gsm48_loc_upd_req *)gh->data;
|
||||
gsm48_decode_lai2(&lu->lai, &old_lai);
|
||||
map->l3.from_other_plmn = (osmo_plmn_cmp(&old_lai.plmn, local_plmn) != 0);
|
||||
if (map->l3.from_other_plmn)
|
||||
LOGP(DRUA, LOGL_INFO, "LU from other PLMN: old LAI=%s my PLMN=%s\n",
|
||||
osmo_plmn_name_c(OTC_SELECT, &old_lai.plmn),
|
||||
osmo_plmn_name_c(OTC_SELECT, local_plmn));
|
||||
return 0;
|
||||
|
||||
case GSM48_MT_MM_CM_SERV_REQ:
|
||||
if (len < sizeof(*gh) + sizeof(*cm)) {
|
||||
LOGP(DRUA, LOGL_ERROR, "CM Service Req message too short\n");
|
||||
break;
|
||||
}
|
||||
cm = (struct gsm48_service_request *)&gh->data[0];
|
||||
map->l3.is_emerg = (cm->cm_service_type == GSM48_CMSERV_EMERGENCY);
|
||||
LOGP(DRUA, LOGL_DEBUG, "CM Service is_emerg=%d\n", map->l3.is_emerg);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* PS GMM: Determine mobile identity, gmm_nri_container, from_other_plmn and is_emerg */
|
||||
static int peek_l3_ul_nas_ps(struct hnbgw_context_map *map, const uint8_t *nas_pdu, size_t len,
|
||||
const struct osmo_plmn_id *local_plmn)
|
||||
{
|
||||
struct osmo_routing_area_id old_ra = {};
|
||||
int nri = -1;
|
||||
|
||||
switch (map->l3.gsm48_pdisc) {
|
||||
case GSM48_PDISC_MM_GPRS:
|
||||
switch (map->l3.gsm48_msg_type) {
|
||||
case GSM48_MT_GMM_ATTACH_REQ:
|
||||
mobile_identity_decode_from_gmm_att_req(&map->l3.mi, &old_ra, &nri, nas_pdu, len, false);
|
||||
LOGP(DRUA, LOGL_DEBUG, "GMM Attach Req mi=%s old_ra=%s nri:%d=0x%x\n",
|
||||
osmo_mobile_identity_to_str_c(OTC_SELECT, &map->l3.mi),
|
||||
osmo_rai_name2_c(OTC_SELECT, &old_ra),
|
||||
nri, nri);
|
||||
if (old_ra.lac.plmn.mcc && osmo_plmn_cmp(&old_ra.lac.plmn, local_plmn)) {
|
||||
map->l3.from_other_plmn = true;
|
||||
LOGP(DRUA, LOGL_INFO, "GMM Attach Req from other PLMN: old RAI=%s my PLMN=%s\n",
|
||||
osmo_rai_name2_c(OTC_SELECT, &old_ra),
|
||||
osmo_plmn_name_c(OTC_SELECT, local_plmn));
|
||||
}
|
||||
if (nri >= 0)
|
||||
map->l3.gmm_nri_container = nri;
|
||||
return 0;
|
||||
|
||||
case GSM48_MT_GMM_RA_UPD_REQ:
|
||||
mobile_identity_decode_from_gmm_rau_req(&map->l3.mi, &old_ra, &nri, nas_pdu, len, false);
|
||||
LOGP(DRUA, LOGL_DEBUG, "GMM Routing Area Upd Req mi=%s old_ra=%s nri:%d=0x%x\n",
|
||||
osmo_mobile_identity_to_str_c(OTC_SELECT, &map->l3.mi),
|
||||
osmo_rai_name2_c(OTC_SELECT, &old_ra),
|
||||
nri, nri);
|
||||
if (old_ra.lac.plmn.mcc && osmo_plmn_cmp(&old_ra.lac.plmn, local_plmn)) {
|
||||
map->l3.from_other_plmn = true;
|
||||
LOGP(DRUA, LOGL_INFO, "GMM Routing Area Upd Req from other PLMN: old RAI=%s my PLMN=%s\n",
|
||||
osmo_rai_name2_c(OTC_SELECT, &old_ra),
|
||||
osmo_plmn_name_c(OTC_SELECT, local_plmn));
|
||||
}
|
||||
if (nri >= 0)
|
||||
map->l3.gmm_nri_container = nri;
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int peek_l3_ul_nas(struct hnbgw_context_map *map, const uint8_t *nas_pdu, size_t len,
|
||||
const struct osmo_plmn_id *local_plmn)
|
||||
{
|
||||
const struct gsm48_hdr *gh = (const struct gsm48_hdr *)nas_pdu;
|
||||
|
||||
map->l3 = (struct hnbgw_l3_peek){
|
||||
.gmm_nri_container = -1,
|
||||
.mi = {
|
||||
.type = GSM_MI_TYPE_NONE,
|
||||
.tmsi = GSM_RESERVED_TMSI,
|
||||
},
|
||||
};
|
||||
|
||||
if (len < sizeof(*gh)) {
|
||||
LOGP(DCN, LOGL_ERROR, "Layer 3 message too short for header\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
map->l3.gsm48_pdisc = gsm48_hdr_pdisc(gh);
|
||||
map->l3.gsm48_msg_type = gsm48_hdr_msg_type(gh);
|
||||
|
||||
if (map->is_ps)
|
||||
return peek_l3_ul_nas_ps(map, nas_pdu, len, local_plmn);
|
||||
return peek_l3_ul_nas_cs(map, nas_pdu, len, local_plmn);
|
||||
}
|
||||
|
||||
static int peek_l3_ul_initial_ue(struct hnbgw_context_map *map, const RANAP_InitialUE_MessageIEs_t *ies)
|
||||
{
|
||||
struct osmo_plmn_id local_plmn;
|
||||
|
||||
if (g_hnbgw->config.plmn.mcc) {
|
||||
/* The user has configured a PLMN */
|
||||
local_plmn = g_hnbgw->config.plmn;
|
||||
} else {
|
||||
/* The user has not configured a PLMN, guess from the InitialUE message's LAI IE's PLMN */
|
||||
if (ies->lai.pLMNidentity.size < 3) {
|
||||
LOGP(DCN, LOGL_ERROR, "Missing PLMN in RANAP InitialUE message\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
osmo_plmn_from_bcd(ies->lai.pLMNidentity.buf, &local_plmn);
|
||||
}
|
||||
|
||||
return peek_l3_ul_nas(map, ies->nas_pdu.buf, ies->nas_pdu.size, &local_plmn);
|
||||
}
|
||||
|
||||
/* Extract a Layer 3 message (NAS PDU) from the uplink RANAP message, and put the info obtained in map->l3.
|
||||
* This is relevant for CN pooling, to decide which CN link to map the RUA context to. */
|
||||
int hnbgw_peek_l3_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg)
|
||||
{
|
||||
ranap_message *message = hnbgw_decode_ranap_cn_co(ranap_msg);
|
||||
if (!message) {
|
||||
LOGP(DCN, LOGL_ERROR, "Failed to decode RANAP PDU\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (message->procedureCode) {
|
||||
case RANAP_ProcedureCode_id_InitialUE_Message:
|
||||
return peek_l3_ul_initial_ue(map, &message->msg.initialUE_MessageIEs);
|
||||
default:
|
||||
LOGP(DCN, LOGL_ERROR, "unexpected RANAP PDU in RUA Connect message: %s\n",
|
||||
get_value_string(ranap_procedure_code_vals, message->procedureCode));
|
||||
return -ENOTSUP;
|
||||
}
|
||||
}
|
||||
184
src/osmo-hnbgw/hnbgw_pfcp.c
Normal file
184
src/osmo-hnbgw/hnbgw_pfcp.c
Normal file
@@ -0,0 +1,184 @@
|
||||
/* PFCP link to UPF for osmo-hnbgw */
|
||||
/* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
#include <osmocom/core/stats.h>
|
||||
#include <osmocom/core/stat_item.h>
|
||||
#include <osmocom/pfcp/pfcp_endpoint.h>
|
||||
#include <osmocom/pfcp/pfcp_cp_peer.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/ps_rab_fsm.h>
|
||||
#include <osmocom/hnbgw/hnbgw_pfcp.h>
|
||||
|
||||
static const struct osmo_stat_item_desc hnbgw_upf_stat_item_description[] = {
|
||||
[HNBGW_UPF_STAT_ASSOCIATED] = { "pfcp_associated", "Associated to UPF through PFCP", OSMO_STAT_ITEM_NO_UNIT, 16, 0},
|
||||
};
|
||||
|
||||
static const struct osmo_stat_item_group_desc hnbgw_upf_statg_desc = {
|
||||
"upf",
|
||||
"UPF Peer Statistics",
|
||||
OSMO_STATS_CLASS_PEER,
|
||||
ARRAY_SIZE(hnbgw_upf_stat_item_description),
|
||||
hnbgw_upf_stat_item_description,
|
||||
};
|
||||
|
||||
static void pfcp_set_msg_ctx(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req)
|
||||
{
|
||||
if (!m->ctx.peer_fi)
|
||||
osmo_pfcp_cp_peer_set_msg_ctx(g_hnbgw->pfcp.cp_peer, m);
|
||||
|
||||
/* If this is a response to an earlier request, just take the msg context from the request message.
|
||||
* In osmo-hnbgw, a session_fi always points at a ps_rab FSM. */
|
||||
if (!m->ctx.session_fi && req && req->ctx.session_fi)
|
||||
ps_rab_pfcp_set_msg_ctx(req->ctx.session_fi->priv, m);
|
||||
|
||||
/* Otherwise iterate all PS RABs in all hnb contexts matching on the SEID. This rarely happens at all: for tx,
|
||||
* ps_rab_new_pfcp_msg_tx() already sets the msg ctx, and for rx, we only expect to receive PFCP Responses,
|
||||
* which are handled above. The only time this will happen is when the UPF shuts down and sends a Deletion. */
|
||||
if (!m->ctx.session_fi && m->h.seid_present && m->h.seid != 0) {
|
||||
struct ps_rab *rab = ps_rab_find_by_seid(m->h.seid, m->rx);
|
||||
if (rab)
|
||||
ps_rab_pfcp_set_msg_ctx(rab, m);
|
||||
}
|
||||
}
|
||||
|
||||
static void pfcp_rx_msg(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req)
|
||||
{
|
||||
switch (m->h.message_type) {
|
||||
|
||||
/* We only expect responses to requests. Those are handled by osmo_pfcp_msg.ctx.resp_cb. */
|
||||
|
||||
/* TODO: handle graceful shutdown from UPF (Session Modification? Deletion?) */
|
||||
|
||||
default:
|
||||
LOGP(DLPFCP, LOGL_ERROR, "rx unexpected PFCP message: %s\n",
|
||||
osmo_pfcp_message_type_str(m->h.message_type));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void pfcp_cp_peer_assoc_cb(struct osmo_pfcp_cp_peer *cp_peer, bool associated)
|
||||
{
|
||||
LOGP(DLPFCP, LOGL_NOTICE, "PFCP Peer associated: %s\n", associated ? "true" : "false");
|
||||
HNBGW_UPF_STAT_SET(HNBGW_UPF_STAT_ASSOCIATED, associated ? 1 : 0);
|
||||
}
|
||||
|
||||
int hnbgw_pfcp_init(void)
|
||||
{
|
||||
struct osmo_pfcp_endpoint_cfg cfg;
|
||||
struct osmo_pfcp_endpoint *ep;
|
||||
struct osmo_sockaddr_str local_addr_str;
|
||||
struct osmo_sockaddr_str upf_addr_str;
|
||||
struct osmo_sockaddr upf_addr;
|
||||
|
||||
if (!hnb_gw_is_gtp_mapping_enabled()) {
|
||||
LOGP(DLPFCP, LOGL_NOTICE, "No UPF configured, NOT setting up PFCP, NOT mapping GTP via UPF\n");
|
||||
return 0;
|
||||
}
|
||||
LOGP(DLPFCP, LOGL_DEBUG, "%p cfg: pfcp remote-addr %s\n", g_hnbgw, g_hnbgw->config.pfcp.remote_addr);
|
||||
|
||||
if (!g_hnbgw->config.pfcp.local_addr) {
|
||||
LOGP(DLPFCP, LOGL_ERROR, "Configuration error: missing local PFCP address, required for Node Id\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_hnbgw->pfcp.statg = osmo_stat_item_group_alloc(g_hnbgw, &hnbgw_upf_statg_desc, 0);
|
||||
if (!g_hnbgw->pfcp.statg) {
|
||||
LOGP(DLPFCP, LOGL_ERROR, "Failed creating UPF stats item group\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cfg = (struct osmo_pfcp_endpoint_cfg){
|
||||
.set_msg_ctx_cb = pfcp_set_msg_ctx,
|
||||
.rx_msg_cb = pfcp_rx_msg,
|
||||
};
|
||||
|
||||
/* Set up PFCP endpoint's local node id from local IP address. Parse address string into local_addr_str... */
|
||||
if (osmo_sockaddr_str_from_str(&local_addr_str, g_hnbgw->config.pfcp.local_addr, g_hnbgw->config.pfcp.local_port)) {
|
||||
LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP local IP: %s\n",
|
||||
osmo_quote_str_c(OTC_SELECT, g_hnbgw->config.pfcp.local_addr, -1));
|
||||
return -1;
|
||||
}
|
||||
/* ...and convert to osmo_sockaddr, write to ep->cfg */
|
||||
if (osmo_sockaddr_str_to_sockaddr(&local_addr_str, &cfg.local_addr.u.sas)) {
|
||||
LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP local IP: %s\n",
|
||||
osmo_quote_str_c(OTC_SELECT, g_hnbgw->config.pfcp.local_addr, -1));
|
||||
return -1;
|
||||
}
|
||||
/* also store the local addr as local Node ID */
|
||||
if (osmo_pfcp_ie_node_id_from_osmo_sockaddr(&cfg.local_node_id, &cfg.local_addr)) {
|
||||
LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP local IP: %s\n",
|
||||
osmo_quote_str_c(OTC_SELECT, g_hnbgw->config.pfcp.local_addr, -1));
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_hnbgw->pfcp.ep = ep = osmo_pfcp_endpoint_create(g_hnbgw, &cfg);
|
||||
if (!ep) {
|
||||
LOGP(DLPFCP, LOGL_ERROR, "Failed to allocate PFCP endpoint\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set up remote PFCP address to reach UPF at. First parse the string into upf_addr_str. */
|
||||
if (osmo_sockaddr_str_from_str(&upf_addr_str, g_hnbgw->config.pfcp.remote_addr, g_hnbgw->config.pfcp.remote_port)) {
|
||||
LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP remote IP: %s\n",
|
||||
osmo_quote_str_c(OTC_SELECT, g_hnbgw->config.pfcp.remote_addr, -1));
|
||||
return -1;
|
||||
}
|
||||
/* then convert upf_addr_str to osmo_sockaddr */
|
||||
if (osmo_sockaddr_str_to_sockaddr(&upf_addr_str, &upf_addr.u.sas)) {
|
||||
LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP remote IP: %s\n",
|
||||
osmo_quote_str_c(OTC_SELECT, g_hnbgw->config.pfcp.remote_addr, -1));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Start the socket */
|
||||
if (osmo_pfcp_endpoint_bind(ep)) {
|
||||
LOGP(DLPFCP, LOGL_ERROR, "Cannot bind PFCP endpoint\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Associate with UPF */
|
||||
g_hnbgw->pfcp.cp_peer = osmo_pfcp_cp_peer_alloc(g_hnbgw, ep, &upf_addr);
|
||||
if (!g_hnbgw->pfcp.cp_peer) {
|
||||
LOGP(DLPFCP, LOGL_ERROR, "Cannot allocate PFCP CP Peer FSM\n");
|
||||
return -1;
|
||||
}
|
||||
if (osmo_pfcp_cp_peer_set_associated_cb(g_hnbgw->pfcp.cp_peer, pfcp_cp_peer_assoc_cb)) {
|
||||
LOGP(DLPFCP, LOGL_ERROR, "Cannot Set PFCP CP Peer associated callback\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (osmo_pfcp_cp_peer_associate(g_hnbgw->pfcp.cp_peer)) {
|
||||
LOGP(DLPFCP, LOGL_ERROR, "Cannot start PFCP CP Peer FSM\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hnbgw_pfcp_release(void)
|
||||
{
|
||||
if (!hnb_gw_is_gtp_mapping_enabled())
|
||||
return;
|
||||
osmo_stat_item_group_free(g_hnbgw->pfcp.statg);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* hnb-gw specific code for RANAP */
|
||||
/* hnb-gw specific code for RANAP, 3GPP TS 25.413 */
|
||||
|
||||
/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
|
||||
* All Rights Reserved
|
||||
@@ -18,10 +18,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include "config.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
@@ -29,14 +26,34 @@
|
||||
|
||||
#include "asn1helpers.h"
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/hnbgw_rua.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include <osmocom/ranap/ranap_common.h>
|
||||
#include <osmocom/ranap/ranap_common_ran.h>
|
||||
#include <osmocom/ranap/ranap_common_cn.h>
|
||||
#include <osmocom/ranap/ranap_ies_defs.h>
|
||||
#include <osmocom/ranap/ranap_msg_factory.h>
|
||||
|
||||
static int ranap_tx_reset_ack(struct hnb_context *hnb,
|
||||
RANAP_CN_DomainIndicator_t domain)
|
||||
#if ENABLE_PFCP
|
||||
#include <osmocom/pfcp/pfcp_cp_peer.h>
|
||||
#endif
|
||||
|
||||
#include <osmocom/hnbgw/hnb.h>
|
||||
#include <osmocom/hnbgw/hnb_persistent.h>
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/hnbgw_rua.h>
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/mgw_fsm.h>
|
||||
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
|
||||
#include <osmocom/hnbgw/kpi.h>
|
||||
|
||||
/*****************************************************************************
|
||||
* Processing of RANAP from the endpoint towards RAN (hNodeB), acting as CN
|
||||
*****************************************************************************/
|
||||
|
||||
static int ranap_tx_udt_dl_reset_ack(struct hnb_context *hnb, RANAP_CN_DomainIndicator_t domain)
|
||||
{
|
||||
struct msgb *msg;
|
||||
int rc;
|
||||
@@ -52,7 +69,7 @@ static int ranap_tx_reset_ack(struct hnb_context *hnb,
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int ranap_rx_init_reset(struct hnb_context *hnb, ANY_t *in)
|
||||
static int ranap_rx_udt_ul_init_reset(struct hnb_context *hnb, ANY_t *in)
|
||||
{
|
||||
RANAP_ResetIEs_t ies;
|
||||
int rc, is_ps = 0;
|
||||
@@ -66,32 +83,38 @@ static int ranap_rx_init_reset(struct hnb_context *hnb, ANY_t *in)
|
||||
|
||||
LOGHNB(hnb, DRANAP, LOGL_INFO, "Rx RESET.req(%s,%s)\n", is_ps ? "ps" : "cs",
|
||||
ranap_cause_str(&ies.cause));
|
||||
HNBP_CTR_INC(hnb->persistent, is_ps ? HNB_CTR_RANAP_PS_RESET_REQ_UL : HNB_CTR_RANAP_CS_RESET_REQ_UL);
|
||||
|
||||
/* FIXME: Actually we have to wait for some guard time? */
|
||||
/* FIXME: Reset all resources related to this HNB/RNC */
|
||||
ranap_tx_reset_ack(hnb, ies.cN_DomainIndicator);
|
||||
ranap_tx_udt_dl_reset_ack(hnb, ies.cN_DomainIndicator);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ranap_rx_error_ind(struct hnb_context *hnb, ANY_t *in)
|
||||
static int ranap_rx_udt_ul_error_ind(struct hnb_context *hnb, ANY_t *in)
|
||||
{
|
||||
RANAP_ErrorIndicationIEs_t ies;
|
||||
int rc;
|
||||
bool is_ps = false;
|
||||
|
||||
rc = ranap_decode_errorindicationies(&ies, in);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
if (ies.cN_DomainIndicator == RANAP_CN_DomainIndicator_ps_domain)
|
||||
is_ps = true;
|
||||
|
||||
if (ies.presenceMask & ERRORINDICATIONIES_RANAP_CAUSE_PRESENT) {
|
||||
LOGHNB(hnb, DRANAP, LOGL_ERROR, "Rx ERROR.ind(%s)\n", ranap_cause_str(&ies.cause));
|
||||
} else
|
||||
LOGHNB(hnb, DRANAP, LOGL_ERROR, "Rx ERROR.ind\n");
|
||||
HNBP_CTR_INC(hnb->persistent, is_ps ? HNB_CTR_RANAP_PS_ERR_IND_UL : HNB_CTR_RANAP_CS_ERR_IND_UL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ranap_rx_initiating_msg(struct hnb_context *hnb, RANAP_InitiatingMessage_t *imsg)
|
||||
static int ranap_rx_udt_ul_initiating_msg(struct hnb_context *hnb, RANAP_InitiatingMessage_t *imsg)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
@@ -105,12 +128,12 @@ static int ranap_rx_initiating_msg(struct hnb_context *hnb, RANAP_InitiatingMess
|
||||
switch (imsg->procedureCode) {
|
||||
case RANAP_ProcedureCode_id_Reset:
|
||||
/* Reset request */
|
||||
rc = ranap_rx_init_reset(hnb, &imsg->value);
|
||||
rc = ranap_rx_udt_ul_init_reset(hnb, &imsg->value);
|
||||
break;
|
||||
case RANAP_ProcedureCode_id_OverloadControl: /* Overload ind */
|
||||
break;
|
||||
case RANAP_ProcedureCode_id_ErrorIndication: /* Error ind */
|
||||
rc = ranap_rx_error_ind(hnb, &imsg->value);
|
||||
rc = ranap_rx_udt_ul_error_ind(hnb, &imsg->value);
|
||||
break;
|
||||
case RANAP_ProcedureCode_id_ResetResource: /* request */
|
||||
case RANAP_ProcedureCode_id_InformationTransfer:
|
||||
@@ -128,7 +151,7 @@ static int ranap_rx_initiating_msg(struct hnb_context *hnb, RANAP_InitiatingMess
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int ranap_rx_successful_msg(struct hnb_context *hnb, RANAP_SuccessfulOutcome_t *imsg)
|
||||
static int ranap_rx_udt_ul_successful_msg(struct hnb_context *hnb, RANAP_SuccessfulOutcome_t *imsg)
|
||||
{
|
||||
/* according tot the spec, we can primarily receive Overload,
|
||||
* Reset, Reset ACK, Error Indication, reset Resource, Reset
|
||||
@@ -158,16 +181,16 @@ static int ranap_rx_successful_msg(struct hnb_context *hnb, RANAP_SuccessfulOutc
|
||||
|
||||
|
||||
|
||||
static int _hnbgw_ranap_rx(struct hnb_context *hnb, RANAP_RANAP_PDU_t *pdu)
|
||||
static int _hnbgw_ranap_rx_udt_ul(struct hnb_context *hnb, RANAP_RANAP_PDU_t *pdu)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
switch (pdu->present) {
|
||||
case RANAP_RANAP_PDU_PR_initiatingMessage:
|
||||
rc = ranap_rx_initiating_msg(hnb, &pdu->choice.initiatingMessage);
|
||||
rc = ranap_rx_udt_ul_initiating_msg(hnb, &pdu->choice.initiatingMessage);
|
||||
break;
|
||||
case RANAP_RANAP_PDU_PR_successfulOutcome:
|
||||
rc = ranap_rx_successful_msg(hnb, &pdu->choice.successfulOutcome);
|
||||
rc = ranap_rx_udt_ul_successful_msg(hnb, &pdu->choice.successfulOutcome);
|
||||
break;
|
||||
case RANAP_RANAP_PDU_PR_unsuccessfulOutcome:
|
||||
LOGHNB(hnb, DRANAP, LOGL_NOTICE, "Received unsupported RANAP "
|
||||
@@ -183,8 +206,8 @@ static int _hnbgw_ranap_rx(struct hnb_context *hnb, RANAP_RANAP_PDU_t *pdu)
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int hnbgw_ranap_rx(struct msgb *msg, uint8_t *data, size_t len)
|
||||
/* receive a RNAAP Unit-Data message in uplink direction */
|
||||
int hnbgw_ranap_rx_udt_ul(struct msgb *msg, uint8_t *data, size_t len)
|
||||
{
|
||||
RANAP_RANAP_PDU_t _pdu, *pdu = &_pdu;
|
||||
struct hnb_context *hnb = msg->dst;
|
||||
@@ -199,11 +222,460 @@ int hnbgw_ranap_rx(struct msgb *msg, uint8_t *data, size_t len)
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = _hnbgw_ranap_rx(hnb, pdu);
|
||||
rc = _hnbgw_ranap_rx_udt_ul(hnb, pdu);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int destruct_ranap_cn_rx_co_ies(ranap_message *ranap_message_p)
|
||||
{
|
||||
ranap_cn_rx_co_free(ranap_message_p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Decode UL RANAP message with convenient memory freeing: just talloc_free() the returned pointer..
|
||||
* Allocate a ranap_message from OTC_SELECT, decode RANAP msgb into it, attach a talloc destructor that calls
|
||||
* ranap_cn_rx_co_free() upon talloc_free(), and return the decoded ranap_message. */
|
||||
ranap_message *hnbgw_decode_ranap_cn_co(struct msgb *ranap_msg)
|
||||
{
|
||||
int rc;
|
||||
ranap_message *message;
|
||||
|
||||
if (!msg_has_l2_data(ranap_msg))
|
||||
return NULL;
|
||||
message = talloc_zero(OTC_SELECT, ranap_message);
|
||||
rc = ranap_cn_rx_co_decode2(message, msgb_l2(ranap_msg), msgb_l2len(ranap_msg));
|
||||
if (rc != 0) {
|
||||
talloc_free(message);
|
||||
return NULL;
|
||||
}
|
||||
talloc_set_destructor(message, destruct_ranap_cn_rx_co_ies);
|
||||
return message;
|
||||
}
|
||||
|
||||
/* Process a received RANAP PDU through SCCP DATA.ind coming from CN (MSC/SGSN)
|
||||
* ranap_msg is owned by OTC_SELECT. */
|
||||
int hnbgw_ranap_rx_data_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg)
|
||||
{
|
||||
OSMO_ASSERT(map);
|
||||
OSMO_ASSERT(msg_has_l2_data(ranap_msg));
|
||||
|
||||
ranap_message *message = hnbgw_decode_ranap_cn_co(ranap_msg);
|
||||
if (message) {
|
||||
LOG_MAP(map, DHNB, LOGL_DEBUG, "rx from RUA: RANAP %s\n",
|
||||
get_value_string(ranap_procedure_code_vals, message->procedureCode));
|
||||
|
||||
kpi_ranap_process_ul(map, message);
|
||||
|
||||
if (!map->is_ps) {
|
||||
/* See if it is a RAB Assignment Response message from RUA to SCCP, where we need to change the user plane
|
||||
* information, for RTP mapping via MGW, or GTP mapping via UPF. */
|
||||
switch (message->procedureCode) {
|
||||
case RANAP_ProcedureCode_id_RAB_Assignment:
|
||||
/* mgw_fsm_handle_rab_ass_resp() may take ownership of "ranap_msg" (prim->oph) and "message" */
|
||||
return mgw_fsm_handle_cs_rab_ass_resp(map, ranap_msg, message);
|
||||
}
|
||||
} else {
|
||||
#if ENABLE_PFCP
|
||||
if (hnb_gw_is_gtp_mapping_enabled()) {
|
||||
/* map->is_ps == true and PFCP is enabled in osmo-hnbgw.cfg */
|
||||
switch (message->procedureCode) {
|
||||
case RANAP_ProcedureCode_id_RAB_Assignment:
|
||||
/* ps_rab_ass_fsm() may take ownership of "ranap_msg" (prim->oph) and "message" */
|
||||
return hnbgw_gtpmap_rx_rab_ass_resp(map, ranap_msg, message);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/* It was not a RAB Assignment Response that needed to be intercepted. Forward as-is to SCCP. */
|
||||
return map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Processing of RANAP from the endpoint towards CN (MSC/SGSN), acting as RAN
|
||||
*****************************************************************************/
|
||||
|
||||
static int cn_ranap_rx_reset_cmd(struct hnbgw_cnlink *cnlink,
|
||||
const struct osmo_scu_unitdata_param *unitdata,
|
||||
RANAP_InitiatingMessage_t *imsg)
|
||||
{
|
||||
RANAP_CN_DomainIndicator_t domain;
|
||||
RANAP_ResetIEs_t ies;
|
||||
int rc;
|
||||
|
||||
rc = ranap_decode_reseties(&ies, &imsg->value);
|
||||
domain = ies.cN_DomainIndicator;
|
||||
ranap_free_reseties(&ies);
|
||||
|
||||
if (rc) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Rx RESET: cannot decode IEs\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (cnlink->pool->domain != domain) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Rx RESET indicates domain %s, but this is %s on domain %s\n",
|
||||
ranap_domain_name(domain), cnlink->name, ranap_domain_name(cnlink->pool->domain));
|
||||
return -1;
|
||||
}
|
||||
|
||||
cnlink_rx_reset_cmd(cnlink);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct value_string ranap_paging_area_id_names[] = {
|
||||
{ RANAP_PagingAreaID_PR_NOTHING, "NOTHING" },
|
||||
{ RANAP_PagingAreaID_PR_lAI, "LAI" },
|
||||
{ RANAP_PagingAreaID_PR_rAI, "RAI" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static bool hnb_paging_area_id_match(const struct hnb_context *hnb,
|
||||
enum RANAP_PagingAreaID_PR t,
|
||||
const struct osmo_routing_area_id *rai)
|
||||
{
|
||||
switch (t) {
|
||||
case RANAP_PagingAreaID_PR_NOTHING:
|
||||
return true;
|
||||
case RANAP_PagingAreaID_PR_rAI:
|
||||
if (hnb->id.rac != rai->rac)
|
||||
return false;
|
||||
/* fall through */
|
||||
case RANAP_PagingAreaID_PR_lAI:
|
||||
if (hnb->id.lac != rai->lac.lac)
|
||||
return false;
|
||||
if (osmo_plmn_cmp(&hnb->id.plmn, &rai->lac.plmn))
|
||||
return false;
|
||||
/* fall through */
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int lai_from_RANAP_RANAP_LAI(struct osmo_location_area_id *lai, const RANAP_LAI_t *ranap_lai)
|
||||
{
|
||||
if (ranap_lai->pLMNidentity.size < 3)
|
||||
return -EINVAL;
|
||||
osmo_plmn_from_bcd(ranap_lai->pLMNidentity.buf, &lai->plmn);
|
||||
lai->lac = asn1str_to_u16(&ranap_lai->lAC);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rai_from_RANAP_PagingAreaID(struct osmo_routing_area_id *rai, const RANAP_PagingAreaID_t *paid)
|
||||
{
|
||||
switch (paid->present) {
|
||||
case RANAP_PagingAreaID_PR_NOTHING:
|
||||
break;
|
||||
case RANAP_PagingAreaID_PR_lAI:
|
||||
return lai_from_RANAP_RANAP_LAI(&rai->lac, &paid->choice.lAI);
|
||||
case RANAP_PagingAreaID_PR_rAI:
|
||||
rai->rac = asn1str_to_u8(&paid->choice.rAI.rAC);
|
||||
return lai_from_RANAP_RANAP_LAI(&rai->lac, &paid->choice.rAI.lAI);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 3GPP TS 25.413 8.15 */
|
||||
static int cn_ranap_rx_paging_cmd(struct hnbgw_cnlink *cnlink,
|
||||
RANAP_InitiatingMessage_t *imsg,
|
||||
const uint8_t *data, unsigned int len)
|
||||
{
|
||||
RANAP_PagingIEs_t ies;
|
||||
RANAP_CN_DomainIndicator_t domain;
|
||||
const char *errmsg;
|
||||
struct hnb_context *hnb;
|
||||
bool is_ps = cnlink->pool->domain == DOMAIN_PS;
|
||||
bool forwarded = false;
|
||||
bool page_area_present;
|
||||
struct osmo_routing_area_id page_rai = {};
|
||||
|
||||
if (ranap_decode_pagingies(&ies, &imsg->value) < 0) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_ERROR,
|
||||
"Rx Paging from CN: decoding RANAP IEs failed\n");
|
||||
return -1;
|
||||
}
|
||||
domain = ies.cN_DomainIndicator;
|
||||
page_area_present = (ies.presenceMask & PAGINGIES_RANAP_PAGINGAREAID_PRESENT);
|
||||
|
||||
if (cnlink->pool->domain != domain) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_ERROR,
|
||||
"Rx Paging from CN: message indicates domain %s, but cnlink is on domain %s\n",
|
||||
ranap_domain_name(domain),
|
||||
ranap_domain_name(cnlink->pool->domain));
|
||||
goto free_ies_ret;
|
||||
}
|
||||
|
||||
if (page_area_present) {
|
||||
if (rai_from_RANAP_PagingAreaID(&page_rai, &ies.pagingAreaID) < 0) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_ERROR,
|
||||
"Rx Paging from CN: decoding RANAP IE Paging Area ID failed, broadcasting to all HNBs\n");
|
||||
/* fail over to broadcast... */
|
||||
page_area_present = false;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG,
|
||||
"Rx Paging from CN: %s PagingAreaID: %s %s\n",
|
||||
ranap_domain_name(domain),
|
||||
page_area_present ?
|
||||
get_value_string(ranap_paging_area_id_names, ies.pagingAreaID.present) :
|
||||
"NOT_PRESENT",
|
||||
osmo_rai_name2(&page_rai)
|
||||
);
|
||||
|
||||
llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) {
|
||||
if (!hnb->hnb_registered)
|
||||
continue;
|
||||
if (page_area_present &&
|
||||
!hnb_paging_area_id_match(hnb, ies.pagingAreaID.present, &page_rai))
|
||||
continue;
|
||||
|
||||
if (is_ps)
|
||||
HNBP_CTR_INC(hnb->persistent, HNB_CTR_PS_PAGING_ATTEMPTED);
|
||||
else
|
||||
HNBP_CTR_INC(hnb->persistent, HNB_CTR_CS_PAGING_ATTEMPTED);
|
||||
if (rua_tx_udt(hnb, data, len) == 0)
|
||||
forwarded = true;
|
||||
}
|
||||
|
||||
if (forwarded) {
|
||||
/* If Paging command was forwarded anywhere, store a record for it, to match paging response: */
|
||||
errmsg = cnlink_paging_add_ranap(cnlink, &ies);
|
||||
if (errmsg) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_ERROR,
|
||||
"Rx Paging from CN: %s. Skip storing paging record."
|
||||
" Later on, the Paging Response may be forwarded to the wrong CN peer.\n",
|
||||
errmsg);
|
||||
goto free_ies_ret;
|
||||
}
|
||||
}
|
||||
ranap_free_pagingies(&ies);
|
||||
return 0;
|
||||
|
||||
free_ies_ret:
|
||||
ranap_free_pagingies(&ies);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ranap_rx_udt_dl_initiating_msg(struct hnbgw_cnlink *cnlink,
|
||||
const struct osmo_scu_unitdata_param *unitdata,
|
||||
RANAP_InitiatingMessage_t *imsg,
|
||||
const uint8_t *data, unsigned int len)
|
||||
{
|
||||
switch (imsg->procedureCode) {
|
||||
case RANAP_ProcedureCode_id_Reset:
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_RESET);
|
||||
return cn_ranap_rx_reset_cmd(cnlink, unitdata, imsg);
|
||||
case RANAP_ProcedureCode_id_Paging:
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_PAGING);
|
||||
return cn_ranap_rx_paging_cmd(cnlink, imsg, data, len);
|
||||
case RANAP_ProcedureCode_id_OverloadControl: /* Overload ind */
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_OVERLOAD_IND);
|
||||
break;
|
||||
case RANAP_ProcedureCode_id_ErrorIndication: /* Error ind */
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_ERROR_IND);
|
||||
break;
|
||||
case RANAP_ProcedureCode_id_ResetResource: /* request */
|
||||
case RANAP_ProcedureCode_id_InformationTransfer:
|
||||
case RANAP_ProcedureCode_id_DirectInformationTransfer:
|
||||
case RANAP_ProcedureCode_id_UplinkInformationExchange:
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_UNSUPPORTED);
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP "
|
||||
"Procedure %ld from CN, ignoring\n", imsg->procedureCode);
|
||||
break;
|
||||
default:
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_UNKNOWN);
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP "
|
||||
"Procedure %ld from CN, ignoring\n", imsg->procedureCode);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cn_ranap_rx_reset_ack(struct hnbgw_cnlink *cnlink,
|
||||
RANAP_SuccessfulOutcome_t *omsg)
|
||||
{
|
||||
RANAP_CN_DomainIndicator_t domain;
|
||||
RANAP_ResetAcknowledgeIEs_t ies;
|
||||
int rc;
|
||||
|
||||
rc = ranap_decode_resetacknowledgeies(&ies, &omsg->value);
|
||||
domain = ies.cN_DomainIndicator;
|
||||
ranap_free_resetacknowledgeies(&ies);
|
||||
|
||||
if (rc) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Rx RESET ACK: cannot decode IEs\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (cnlink->pool->domain != domain) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Rx RESET ACK indicates domain %s, but this is %s on domain %s\n",
|
||||
ranap_domain_name(domain), cnlink->name, ranap_domain_name(cnlink->pool->domain));
|
||||
return -1;
|
||||
}
|
||||
|
||||
cnlink_rx_reset_ack(cnlink);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ranap_rx_udt_dl_successful_msg(struct hnbgw_cnlink *cnlink,
|
||||
RANAP_SuccessfulOutcome_t *omsg)
|
||||
{
|
||||
switch (omsg->procedureCode) {
|
||||
case RANAP_ProcedureCode_id_Reset: /* Reset acknowledge */
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_RESET);
|
||||
return cn_ranap_rx_reset_ack(cnlink, omsg);
|
||||
case RANAP_ProcedureCode_id_ResetResource: /* response */
|
||||
case RANAP_ProcedureCode_id_InformationTransfer:
|
||||
case RANAP_ProcedureCode_id_DirectInformationTransfer:
|
||||
case RANAP_ProcedureCode_id_UplinkInformationExchange:
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_UNSUPPORTED);
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP "
|
||||
"Procedure %ld from CN, ignoring\n", omsg->procedureCode);
|
||||
break;
|
||||
default:
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_RANAP_RX_UDT_UNKNOWN);
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP "
|
||||
"Procedure %ld from CN, ignoring\n", omsg->procedureCode);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _hnbgw_ranap_rx_udt_dl(struct hnbgw_cnlink *cnlink,
|
||||
const struct osmo_scu_unitdata_param *unitdata,
|
||||
RANAP_RANAP_PDU_t *pdu, const uint8_t *data, unsigned int len)
|
||||
{
|
||||
int rc;
|
||||
|
||||
switch (pdu->present) {
|
||||
case RANAP_RANAP_PDU_PR_initiatingMessage:
|
||||
rc = ranap_rx_udt_dl_initiating_msg(cnlink, unitdata, &pdu->choice.initiatingMessage, data, len);
|
||||
break;
|
||||
case RANAP_RANAP_PDU_PR_successfulOutcome:
|
||||
rc = ranap_rx_udt_dl_successful_msg(cnlink, &pdu->choice.successfulOutcome);
|
||||
break;
|
||||
case RANAP_RANAP_PDU_PR_unsuccessfulOutcome:
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP "
|
||||
"unsuccessful outcome procedure %ld from CN, ignoring\n",
|
||||
pdu->choice.unsuccessfulOutcome.procedureCode);
|
||||
rc = -ENOTSUP;
|
||||
break;
|
||||
default:
|
||||
LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP "
|
||||
"presence %u from CN, ignoring\n", pdu->present);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int hnbgw_ranap_rx_udt_dl(struct hnbgw_cnlink *cnlink, const struct osmo_scu_unitdata_param *unitdata,
|
||||
const uint8_t *data, unsigned int len)
|
||||
{
|
||||
RANAP_RANAP_PDU_t _pdu, *pdu = &_pdu;
|
||||
asn_dec_rval_t dec_ret;
|
||||
int rc;
|
||||
|
||||
memset(pdu, 0, sizeof(*pdu));
|
||||
dec_ret = aper_decode(NULL, &asn_DEF_RANAP_RANAP_PDU, (void **) &pdu,
|
||||
data, len, 0, 0);
|
||||
if (dec_ret.code != RC_OK) {
|
||||
LOGP(DRANAP, LOGL_ERROR, "Error in RANAP ASN.1 decode\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = _hnbgw_ranap_rx_udt_dl(cnlink, unitdata, pdu, data, len);
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RANAP_PDU, pdu);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int destruct_ranap_ran_rx_co_ies(ranap_message *ranap_message_p)
|
||||
{
|
||||
ranap_ran_rx_co_free(ranap_message_p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Decode DL RANAP message with convenient memory freeing: just talloc_free() the returned pointer..
|
||||
* Allocate a ranap_message from OTC_SELECT, decode RANAP msgb into it, attach a talloc destructor that calls
|
||||
* ranap_cn_rx_co_free() upon talloc_free(), and return the decoded ranap_message. */
|
||||
static ranap_message *hnbgw_decode_ranap_ran_co(struct msgb *ranap_msg)
|
||||
{
|
||||
int rc;
|
||||
ranap_message *message;
|
||||
|
||||
if (!msg_has_l2_data(ranap_msg))
|
||||
return NULL;
|
||||
message = talloc_zero(OTC_SELECT, ranap_message);
|
||||
rc = ranap_ran_rx_co_decode(NULL, message, msgb_l2(ranap_msg), msgb_l2len(ranap_msg));
|
||||
if (rc != 0) {
|
||||
talloc_free(message);
|
||||
return NULL;
|
||||
}
|
||||
talloc_set_destructor(message, destruct_ranap_ran_rx_co_ies);
|
||||
return message;
|
||||
}
|
||||
|
||||
/* Process a received RANAP PDU through SCCP DATA.ind coming from CN (MSC/SGSN)
|
||||
* ranap_msg is owned by OTC_SELECT. */
|
||||
int hnbgw_ranap_rx_data_dl(struct hnbgw_context_map *map, struct msgb *ranap_msg)
|
||||
{
|
||||
OSMO_ASSERT(map);
|
||||
OSMO_ASSERT(msg_has_l2_data(ranap_msg));
|
||||
|
||||
/* See if it is a RAB Assignment Request message from SCCP to RUA, where we need to change the user plane
|
||||
* information, for RTP mapping via MGW, or GTP mapping via UPF. */
|
||||
ranap_message *message = hnbgw_decode_ranap_ran_co(ranap_msg);
|
||||
if (message) {
|
||||
LOG_MAP(map, DCN, LOGL_DEBUG, "rx from SCCP: RANAP %s\n",
|
||||
get_value_string(ranap_procedure_code_vals, message->procedureCode));
|
||||
|
||||
kpi_ranap_process_dl(map, message);
|
||||
|
||||
if (!map->is_ps) {
|
||||
/* Circuit-Switched. Set up mapping of RTP ports via MGW */
|
||||
switch (message->procedureCode) {
|
||||
case RANAP_ProcedureCode_id_RAB_Assignment:
|
||||
/* mgw_fsm_alloc_and_handle_rab_ass_req() takes ownership of (ranap) message */
|
||||
return handle_cs_rab_ass_req(map, ranap_msg, message);
|
||||
case RANAP_ProcedureCode_id_Iu_Release:
|
||||
/* Any IU Release will terminate the MGW FSM, the message itsself is not passed to the
|
||||
* FSM code. It is just forwarded normally by map_rua_tx_dt() below. */
|
||||
mgw_fsm_release(map);
|
||||
break;
|
||||
}
|
||||
#if ENABLE_PFCP
|
||||
} else {
|
||||
switch (message->procedureCode) {
|
||||
case RANAP_ProcedureCode_id_RAB_Assignment:
|
||||
/* If a UPF is configured, handle the RAB Assignment via ps_rab_ass_fsm, and replace the
|
||||
* GTP F-TEIDs in the RAB Assignment message before passing it on to RUA. */
|
||||
if (hnb_gw_is_gtp_mapping_enabled()) {
|
||||
LOG_MAP(map, DCN, LOGL_DEBUG,
|
||||
"RAB Assignment: setting up GTP tunnel mapping via UPF %s\n",
|
||||
osmo_sockaddr_to_str_c(OTC_SELECT, osmo_pfcp_cp_peer_get_remote_addr(g_hnbgw->pfcp.cp_peer)));
|
||||
return hnbgw_gtpmap_rx_rab_ass_req(map, ranap_msg, message);
|
||||
}
|
||||
/* If no UPF is configured, directly forward the message as-is (no GTP mapping). */
|
||||
LOG_MAP(map, DCN, LOGL_DEBUG, "RAB Assignment: no UPF configured, forwarding as-is\n");
|
||||
break;
|
||||
|
||||
case RANAP_ProcedureCode_id_Iu_Release:
|
||||
/* Any IU Release will terminate the MGW FSM, the message itsself is not passed to the
|
||||
* FSM code. It is just forwarded normally by map_rua_tx_dt() below. */
|
||||
hnbgw_gtpmap_release(map);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/* It was not a RAB Assignment Request that needed to be intercepted. Forward as-is to RUA. */
|
||||
return map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, ranap_msg);
|
||||
}
|
||||
|
||||
int hnbgw_ranap_init(void)
|
||||
{
|
||||
return 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* hnb-gw specific code for RUA (Ranap User Adaption) */
|
||||
/* hnb-gw specific code for RUA (Ranap User Adaption), 3GPP TS 25.468 */
|
||||
|
||||
/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
|
||||
* All Rights Reserved
|
||||
@@ -18,6 +18,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
@@ -32,30 +33,26 @@
|
||||
|
||||
#include "asn1helpers.h"
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/hnb.h>
|
||||
#include <osmocom/hnbgw/hnb_persistent.h>
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
#include <osmocom/hnbgw/hnbgw_ranap.h>
|
||||
#include <osmocom/rua/rua_common.h>
|
||||
#include <osmocom/rua/rua_ies_defs.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/hnbgw_rua.h>
|
||||
#include <osmocom/hnbap/HNBAP_CN-DomainIndicator.h>
|
||||
|
||||
static const char *cn_domain_indicator_to_str(RUA_CN_DomainIndicator_t cN_DomainIndicator)
|
||||
{
|
||||
switch (cN_DomainIndicator) {
|
||||
case RUA_CN_DomainIndicator_cs_domain:
|
||||
return "IuCS";
|
||||
case RUA_CN_DomainIndicator_ps_domain:
|
||||
return "IuPS";
|
||||
default:
|
||||
return "(unknown-domain)";
|
||||
}
|
||||
}
|
||||
|
||||
static int hnbgw_rua_tx(struct hnb_context *ctx, struct msgb *msg)
|
||||
{
|
||||
if (!msg)
|
||||
return -EINVAL;
|
||||
|
||||
if (!ctx || !ctx->conn) {
|
||||
LOGHNB(ctx, DRUA, LOGL_ERROR, "RUA context to this HNB is not connected, cannot transmit message\n");
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
msgb_sctp_ppid(msg) = IUH_PPI_RUA;
|
||||
osmo_stream_srv_send(ctx->conn, msg);
|
||||
|
||||
@@ -87,6 +84,7 @@ int rua_tx_udt(struct hnb_context *hnb, const uint8_t *data, unsigned int len)
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RUA_ConnectionlessTransfer, &out);
|
||||
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "transmitting RUA payload of %u bytes\n", msgb_length(msg));
|
||||
HNBP_CTR_INC(hnb->persistent, HNB_CTR_RUA_UDT_DL);
|
||||
|
||||
return hnbgw_rua_tx(hnb, msg);
|
||||
}
|
||||
@@ -122,8 +120,9 @@ int rua_tx_dt(struct hnb_context *hnb, int is_ps, uint32_t context_id,
|
||||
&out);
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RUA_DirectTransfer, &out);
|
||||
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "transmitting RUA (cn=%s) payload of %u bytes\n",
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "transmitting RUA DirectTransfer (cn=%s) payload of %u bytes\n",
|
||||
is_ps ? "ps" : "cs", msgb_length(msg));
|
||||
HNBP_CTR_INC(hnb->persistent, is_ps ? HNB_CTR_RUA_PS_DT_DL : HNB_CTR_RUA_CS_DT_DL);
|
||||
|
||||
return hnbgw_rua_tx(hnb, msg);
|
||||
}
|
||||
@@ -150,8 +149,6 @@ int rua_tx_disc(struct hnb_context *hnb, int is_ps, uint32_t context_id,
|
||||
ies.ranaP_Message.size = len;
|
||||
}
|
||||
|
||||
/* FIXME: msgb_free(msg)? ownership not yet clear */
|
||||
|
||||
memset(&out, 0, sizeof(out));
|
||||
rc = rua_encode_disconnecties(&out, &ies);
|
||||
if (rc < 0)
|
||||
@@ -163,38 +160,95 @@ int rua_tx_disc(struct hnb_context *hnb, int is_ps, uint32_t context_id,
|
||||
&out);
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RUA_Disconnect, &out);
|
||||
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "transmitting RUA (cn=%s) payload of %u bytes\n",
|
||||
is_ps ? "ps" : "cs", msgb_length(msg));
|
||||
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "transmitting RUA Disconnect (cn=%s) payload of %u bytes\n",
|
||||
is_ps ? "ps" : "cs", msgb_length(msg));
|
||||
HNBP_CTR_INC(hnb->persistent, is_ps ? HNB_CTR_RUA_PS_DISCONNECT_DL : HNB_CTR_RUA_CS_DISCONNECT_DL);
|
||||
|
||||
return hnbgw_rua_tx(hnb, msg);
|
||||
}
|
||||
|
||||
/* Send Disconnect to RUA without RANAP data */
|
||||
static void rua_tx_disc_conn_fail(struct hnb_context *hnb, bool is_ps, uint32_t context_id)
|
||||
{
|
||||
RUA_Cause_t rua_cause = {
|
||||
.present = RUA_Cause_PR_radioNetwork,
|
||||
.choice.radioNetwork = RUA_CauseRadioNetwork_connect_failed,
|
||||
};
|
||||
|
||||
LOG_HNBP(hnb->persistent, LOGL_INFO, "Tx RUA Disconnect\n");
|
||||
|
||||
/* forward a RUA message to the SCCP User API to SCCP */
|
||||
if (rua_tx_disc(hnb, is_ps, context_id, &rua_cause, NULL, 0))
|
||||
LOG_HNBP(hnb->persistent, LOGL_ERROR, "Failed to send Disconnect to RUA\n");
|
||||
}
|
||||
|
||||
static struct value_string rua_procedure_code_names[] = {
|
||||
{ RUA_ProcedureCode_id_Connect, "Connect" },
|
||||
{ RUA_ProcedureCode_id_DirectTransfer, "DirectTransfer" },
|
||||
{ RUA_ProcedureCode_id_Disconnect, "Disconnect" },
|
||||
{ RUA_ProcedureCode_id_ConnectionlessTransfer, "ConnectionlessTransfer" },
|
||||
{ RUA_ProcedureCode_id_ErrorIndication, "ErrorIndication" },
|
||||
{ RUA_ProcedureCode_id_privateMessage, "PrivateMessage" },
|
||||
{}
|
||||
};
|
||||
|
||||
static inline const char *rua_procedure_code_name(enum RUA_ProcedureCode val)
|
||||
{
|
||||
return get_value_string(rua_procedure_code_names, val);
|
||||
}
|
||||
|
||||
static struct hnbgw_context_map *create_context_map(struct hnb_context *hnb, uint32_t rua_ctx_id, bool is_ps,
|
||||
struct msgb *ranap_msg)
|
||||
{
|
||||
struct hnbgw_context_map *map;
|
||||
struct hnbgw_cnlink *cnlink;
|
||||
|
||||
/* Establish a new context map. From the RUA Connect, extract a mobile identity, if any, and select a CN link
|
||||
* based on an NRI found in the mobile identity, if any. */
|
||||
|
||||
/* Allocate a map for logging context */
|
||||
map = context_map_alloc(hnb, rua_ctx_id, is_ps);
|
||||
OSMO_ASSERT(map);
|
||||
|
||||
if (hnbgw_peek_l3_ul(map, ranap_msg))
|
||||
LOGP(DCN, LOGL_NOTICE, "Failed to extract Mobile Identity from RUA Connect message's RANAP payload\n");
|
||||
/* map->l3 now contains all the interesting information from the NAS PDU, if any.
|
||||
* If no useful information could be decoded, still continue to select a hopefully adequate link by round robin.
|
||||
*/
|
||||
|
||||
cnlink = hnbgw_cnlink_select(map);
|
||||
if (!cnlink) {
|
||||
LOG_MAP(map, DCN, LOGL_ERROR, "Failed to select %s link\n", is_ps ? "IuPS" : "IuCS");
|
||||
context_map_free(map);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (context_map_set_cnlink(map, cnlink)) {
|
||||
LOG_MAP(map, DCN, LOGL_ERROR, "Failed to establish link to %s\n", cnlink->name);
|
||||
context_map_free(map);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/* dispatch a RUA connection-oriented message received from a HNB to a context mapping's RUA FSM, so that it is
|
||||
* forwarded to the CN via SCCP connection-oriented messages.
|
||||
* Connectionless messages are handled in hnbgw_ranap_rx_udt_ul() instead, not here. */
|
||||
static int rua_to_scu(struct hnb_context *hnb,
|
||||
RUA_CN_DomainIndicator_t cN_DomainIndicator,
|
||||
enum osmo_scu_prim_type type,
|
||||
enum RUA_ProcedureCode rua_procedure,
|
||||
uint32_t context_id, uint32_t cause,
|
||||
const uint8_t *data, unsigned int len)
|
||||
{
|
||||
struct msgb *msg;
|
||||
struct osmo_scu_prim *prim;
|
||||
struct msgb *ranap_msg = NULL;
|
||||
struct hnbgw_context_map *map = NULL;
|
||||
struct hnbgw_cnlink *cn = hnb->gw->sccp.cnlink;
|
||||
struct osmo_sccp_addr *remote_addr;
|
||||
bool is_ps;
|
||||
bool release_context_map = false;
|
||||
int rc;
|
||||
|
||||
switch (cN_DomainIndicator) {
|
||||
case RUA_CN_DomainIndicator_cs_domain:
|
||||
remote_addr = &hnb->gw->sccp.iucs_remote_addr;
|
||||
is_ps = false;
|
||||
break;
|
||||
case RUA_CN_DomainIndicator_ps_domain:
|
||||
remote_addr = &hnb->gw->sccp.iups_remote_addr;
|
||||
is_ps = true;
|
||||
break;
|
||||
default:
|
||||
@@ -202,75 +256,78 @@ static int rua_to_scu(struct hnb_context *hnb,
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!cn) {
|
||||
LOGHNB(hnb, DRUA, LOGL_NOTICE, "CN=NULL, discarding message\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
msg = msgb_alloc(1500, "rua_to_sccp");
|
||||
|
||||
prim = (struct osmo_scu_prim *) msgb_put(msg, sizeof(*prim));
|
||||
osmo_prim_init(&prim->oph, SCCP_SAP_USER, type, PRIM_OP_REQUEST, msg);
|
||||
|
||||
switch (type) {
|
||||
case OSMO_SCU_PRIM_N_UNITDATA:
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "rua_to_scu() %s to %s, rua_ctx_id %u (unitdata, no scu_conn_id)\n",
|
||||
cn_domain_indicator_to_str(cN_DomainIndicator), osmo_sccp_addr_dump(remote_addr), context_id);
|
||||
break;
|
||||
default:
|
||||
map = context_map_alloc_by_hnb(hnb, context_id, is_ps, cn);
|
||||
OSMO_ASSERT(map);
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "rua_to_scu() %s to %s, rua_ctx_id %u scu_conn_id %u\n",
|
||||
cn_domain_indicator_to_str(cN_DomainIndicator), osmo_sccp_addr_dump(remote_addr),
|
||||
map->rua_ctx_id, map->scu_conn_id);
|
||||
}
|
||||
|
||||
/* add primitive header */
|
||||
switch (type) {
|
||||
case OSMO_SCU_PRIM_N_CONNECT:
|
||||
prim->u.connect.called_addr = *remote_addr;
|
||||
prim->u.connect.calling_addr = cn->gw->sccp.local_addr;
|
||||
prim->u.connect.sccp_class = 2;
|
||||
prim->u.connect.conn_id = map->scu_conn_id;
|
||||
/* Two separate logs because of osmo_sccp_addr_dump(). */
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA to SCCP N_CONNECT: called_addr:%s\n",
|
||||
osmo_sccp_addr_dump(&prim->u.connect.called_addr));
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA to SCCP N_CONNECT: calling_addr:%s\n",
|
||||
osmo_sccp_addr_dump(&prim->u.connect.calling_addr));
|
||||
break;
|
||||
case OSMO_SCU_PRIM_N_DATA:
|
||||
prim->u.data.conn_id = map->scu_conn_id;
|
||||
break;
|
||||
case OSMO_SCU_PRIM_N_DISCONNECT:
|
||||
prim->u.disconnect.conn_id = map->scu_conn_id;
|
||||
prim->u.disconnect.cause = cause;
|
||||
release_context_map = true;
|
||||
break;
|
||||
case OSMO_SCU_PRIM_N_UNITDATA:
|
||||
prim->u.unitdata.called_addr = *remote_addr;
|
||||
prim->u.unitdata.calling_addr = cn->gw->sccp.local_addr;
|
||||
/* Two separate logs because of osmo_sccp_addr_dump(). */
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA to SCCP N_UNITDATA: called_addr:%s\n",
|
||||
osmo_sccp_addr_dump(&prim->u.unitdata.called_addr));
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA to SCCP N_UNITDATA: calling_addr:%s\n",
|
||||
osmo_sccp_addr_dump(&prim->u.unitdata.calling_addr));
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* add optional data section, if needed */
|
||||
/* If there is RANAP data, include it in the msgb. In RUA there is always data in practice, but theoretically it
|
||||
* could be an empty Connect or Disconnect. */
|
||||
if (data && len) {
|
||||
msg->l2h = msgb_put(msg, len);
|
||||
memcpy(msg->l2h, data, len);
|
||||
/* According to API doc of map_rua_fsm_event: allocate msgb for RANAP data from OTC_SELECT, reserve
|
||||
* headroom for an osmo_scu_prim. Point l2h at the RANAP data. */
|
||||
ranap_msg = hnbgw_ranap_msg_alloc("RANAP_from_RUA");
|
||||
ranap_msg->l2h = msgb_put(ranap_msg, len);
|
||||
memcpy(ranap_msg->l2h, data, len);
|
||||
}
|
||||
|
||||
rc = osmo_sccp_user_sap_down(cn->sccp_user, &prim->oph);
|
||||
map = context_map_find_by_rua_ctx_id(hnb, context_id, is_ps);
|
||||
|
||||
if (map && release_context_map)
|
||||
context_map_deactivate(map);
|
||||
switch (rua_procedure) {
|
||||
case RUA_ProcedureCode_id_Connect:
|
||||
/* A Connect message can only be the first message for an unused RUA context */
|
||||
if (map) {
|
||||
/* Already established this RUA context. But then how can it be a Connect message. */
|
||||
LOGHNB(hnb, DRUA, LOGL_NOTICE, "rx RUA %s for already active RUA context %u\n",
|
||||
rua_procedure_code_name(rua_procedure), context_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
/* ok, this RUA context does not exist yet, so create one. */
|
||||
map = create_context_map(hnb, context_id, is_ps, ranap_msg);
|
||||
if (!map) {
|
||||
LOGHNB(hnb, DRUA, LOGL_ERROR,
|
||||
"Failed to create context map for %s: rx RUA %s with %u bytes RANAP data\n",
|
||||
is_ps ? "IuPS" : "IuCS", rua_procedure_code_name(rua_procedure), data ? len : 0);
|
||||
rua_tx_disc_conn_fail(hnb, is_ps, context_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
|
||||
return rc;
|
||||
case RUA_ProcedureCode_id_Disconnect:
|
||||
/* For RUA Disconnect, do not spam the ERROR log. It is just a stray Disconnect, no harm done.
|
||||
* Context: some CN are known to rapidly tear down SCCP without waiting for RUA to disconnect gracefully
|
||||
* (IU Release Complete). Such CN would cause ERROR logging for each and every released context map. */
|
||||
if (!map) {
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "rx RUA %s for unknown RUA context %u\n",
|
||||
rua_procedure_code_name(rua_procedure), context_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Any message other than Connect must have a valid RUA context */
|
||||
if (!map) {
|
||||
LOGHNB(hnb, DRUA, LOGL_NOTICE, "rx RUA %s for unknown RUA context %u\n",
|
||||
rua_procedure_code_name(rua_procedure), context_id);
|
||||
rua_tx_disc_conn_fail(hnb, is_ps, context_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_MAP(map, DRUA, LOGL_DEBUG, "rx RUA %s with %u bytes RANAP data\n",
|
||||
rua_procedure_code_name(rua_procedure), data ? len : 0);
|
||||
|
||||
switch (rua_procedure) {
|
||||
|
||||
case RUA_ProcedureCode_id_Connect:
|
||||
return map_rua_dispatch(map, MAP_RUA_EV_RX_CONNECT, ranap_msg);
|
||||
|
||||
case RUA_ProcedureCode_id_DirectTransfer:
|
||||
return map_rua_dispatch(map, MAP_RUA_EV_RX_DIRECT_TRANSFER, ranap_msg);
|
||||
|
||||
case RUA_ProcedureCode_id_Disconnect:
|
||||
return map_rua_dispatch(map, MAP_RUA_EV_RX_DISCONNECT, ranap_msg);
|
||||
|
||||
default:
|
||||
/* No caller may ever pass a different RUA procedure code */
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t rua_to_scu_cause(RUA_Cause_t *in)
|
||||
@@ -349,10 +406,12 @@ static int rua_rx_init_connect(struct msgb *msg, ANY_t *in)
|
||||
context_id = asn1bitstr_to_u24(&ies.context_ID);
|
||||
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA %s Connect.req(ctx=0x%x, %s)\n",
|
||||
cn_domain_indicator_to_str(ies.cN_DomainIndicator), context_id,
|
||||
ranap_domain_name(ies.cN_DomainIndicator), context_id,
|
||||
ies.establishment_Cause == RUA_Establishment_Cause_emergency_call ? "emergency" : "normal");
|
||||
HNBP_CTR_INC(hnb->persistent, ies.cN_DomainIndicator == DOMAIN_PS ?
|
||||
HNB_CTR_RUA_PS_CONNECT_UL : HNB_CTR_RUA_CS_CONNECT_UL);
|
||||
|
||||
rc = rua_to_scu(hnb, ies.cN_DomainIndicator, OSMO_SCU_PRIM_N_CONNECT,
|
||||
rc = rua_to_scu(hnb, ies.cN_DomainIndicator, RUA_ProcedureCode_id_Connect,
|
||||
context_id, 0, ies.ranaP_Message.buf,
|
||||
ies.ranaP_Message.size);
|
||||
|
||||
@@ -380,6 +439,8 @@ static int rua_rx_init_disconnect(struct msgb *msg, ANY_t *in)
|
||||
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA Disconnect.req(ctx=0x%x,cause=%s)\n", context_id,
|
||||
rua_cause_str(&ies.cause));
|
||||
HNBP_CTR_INC(hnb->persistent, ies.cN_DomainIndicator == DOMAIN_PS ?
|
||||
HNB_CTR_RUA_PS_DISCONNECT_UL : HNB_CTR_RUA_CS_DISCONNECT_UL);
|
||||
|
||||
if (ies.presenceMask & DISCONNECTIES_RUA_RANAP_MESSAGE_PRESENT) {
|
||||
ranap_data = ies.ranaP_Message.buf;
|
||||
@@ -387,7 +448,7 @@ static int rua_rx_init_disconnect(struct msgb *msg, ANY_t *in)
|
||||
}
|
||||
|
||||
rc = rua_to_scu(hnb, ies.cN_DomainIndicator,
|
||||
OSMO_SCU_PRIM_N_DISCONNECT,
|
||||
RUA_ProcedureCode_id_Disconnect,
|
||||
context_id, scu_cause, ranap_data, ranap_len);
|
||||
|
||||
rua_free_disconnecties(&ies);
|
||||
@@ -409,10 +470,12 @@ static int rua_rx_init_dt(struct msgb *msg, ANY_t *in)
|
||||
context_id = asn1bitstr_to_u24(&ies.context_ID);
|
||||
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA Data.req(ctx=0x%x)\n", context_id);
|
||||
HNBP_CTR_INC(hnb->persistent, ies.cN_DomainIndicator == DOMAIN_PS ?
|
||||
HNB_CTR_RUA_PS_DT_UL : HNB_CTR_RUA_CS_DT_UL);
|
||||
|
||||
rc = rua_to_scu(hnb,
|
||||
ies.cN_DomainIndicator,
|
||||
OSMO_SCU_PRIM_N_DATA,
|
||||
RUA_ProcedureCode_id_DirectTransfer,
|
||||
context_id, 0, ies.ranaP_Message.buf,
|
||||
ies.ranaP_Message.size);
|
||||
|
||||
@@ -432,6 +495,7 @@ static int rua_rx_init_udt(struct msgb *msg, ANY_t *in)
|
||||
return rc;
|
||||
|
||||
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA UData.req()\n");
|
||||
HNBP_CTR_INC(hnb->persistent, HNB_CTR_RUA_UDT_UL);
|
||||
|
||||
/* according tot the spec, we can primarily receive Overload,
|
||||
* Reset, Reset ACK, Error Indication, reset Resource, Reset
|
||||
@@ -440,7 +504,7 @@ static int rua_rx_init_udt(struct msgb *msg, ANY_t *in)
|
||||
* Information Transfer and Uplink Information Trnansfer that we
|
||||
* can ignore. In either case, it is RANAP that we need to
|
||||
* decode... */
|
||||
rc = hnbgw_ranap_rx(msg, ies.ranaP_Message.buf, ies.ranaP_Message.size);
|
||||
rc = hnbgw_ranap_rx_udt_ul(msg, ies.ranaP_Message.buf, ies.ranaP_Message.size);
|
||||
rua_free_connectionlesstransferies(&ies);
|
||||
|
||||
return rc;
|
||||
@@ -458,6 +522,7 @@ static int rua_rx_init_err_ind(struct msgb *msg, ANY_t *in)
|
||||
return rc;
|
||||
|
||||
LOGHNB(hnb, DRUA, LOGL_ERROR, "RUA UData.ErrorInd(%s)\n", rua_cause_str(&ies.cause));
|
||||
HNBP_CTR_INC(hnb->persistent, HNB_CTR_RUA_ERR_IND);
|
||||
|
||||
rua_free_errorindicationies(&ies);
|
||||
return rc;
|
||||
@@ -544,6 +609,10 @@ int hnbgw_rua_rx(struct hnb_context *hnb, struct msgb *msg)
|
||||
asn_dec_rval_t dec_ret;
|
||||
int rc;
|
||||
|
||||
/* RUA is only processed after HNB registration, and as soon as the HNB is registered,
|
||||
* it should have a persistent config associated with it */
|
||||
OSMO_ASSERT(hnb->persistent);
|
||||
|
||||
/* decode and handle to _hnbgw_hnbap_rx() */
|
||||
|
||||
memset(pdu, 0, sizeof(*pdu));
|
||||
@@ -556,11 +625,7 @@ int hnbgw_rua_rx(struct hnb_context *hnb, struct msgb *msg)
|
||||
|
||||
rc = _hnbgw_rua_rx(msg, pdu);
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RUA_RUA_PDU, pdu);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int hnbgw_rua_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
566
src/osmo-hnbgw/hnbgw_sccp.c
Normal file
566
src/osmo-hnbgw/hnbgw_sccp.c
Normal file
@@ -0,0 +1,566 @@
|
||||
/* hnb-gw specific code for SCCP, ITU Q.711 - Q.714 */
|
||||
|
||||
/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
|
||||
* (C) 2025 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/netif/stream.h>
|
||||
|
||||
#include <osmocom/sigtran/sccp_sap.h>
|
||||
#include <osmocom/sigtran/sccp_helpers.h>
|
||||
#include <osmocom/sigtran/protocol/sua.h>
|
||||
#include <osmocom/sccp/sccp_types.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/hnbgw_ranap.h>
|
||||
|
||||
/***********************************************************************
|
||||
* Incoming primitives from SCCP User SAP
|
||||
***********************************************************************/
|
||||
|
||||
static bool cnlink_matches(const struct hnbgw_cnlink *cnlink, const struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *remote_addr)
|
||||
{
|
||||
if (cnlink->hnbgw_sccp_user != hsu)
|
||||
return false;
|
||||
if (osmo_sccp_addr_cmp(&cnlink->remote_addr, remote_addr, OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct hnbgw_cnlink *hnbgw_cnlink_find_by_addr(const struct hnbgw_sccp_user *hsu,
|
||||
const struct osmo_sccp_addr *remote_addr)
|
||||
{
|
||||
struct hnbgw_cnlink *cnlink;
|
||||
llist_for_each_entry(cnlink, &g_hnbgw->sccp.cnpool_iucs->cnlinks, entry) {
|
||||
if (cnlink_matches(cnlink, hsu, remote_addr))
|
||||
return cnlink;
|
||||
}
|
||||
llist_for_each_entry(cnlink, &g_hnbgw->sccp.cnpool_iups->cnlinks, entry) {
|
||||
if (cnlink_matches(cnlink, hsu, remote_addr))
|
||||
return cnlink;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct hnbgw_cnlink *cnlink_from_addr(struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *calling_addr,
|
||||
const struct osmo_prim_hdr *oph)
|
||||
{
|
||||
struct hnbgw_cnlink *cnlink = NULL;
|
||||
cnlink = hnbgw_cnlink_find_by_addr(hsu, calling_addr);
|
||||
if (!cnlink) {
|
||||
LOG_HSU(hsu, DRANAP, LOGL_ERROR, "Rx from unknown SCCP peer: %s: %s\n",
|
||||
osmo_sccp_inst_addr_name(osmo_ss7_get_sccp(hsu->ss7), calling_addr),
|
||||
osmo_scu_prim_hdr_name_c(OTC_SELECT, oph));
|
||||
return NULL;
|
||||
}
|
||||
return cnlink;
|
||||
}
|
||||
|
||||
static struct hnbgw_context_map *map_from_conn_id(struct hnbgw_sccp_user *hsu, uint32_t conn_id,
|
||||
const struct osmo_prim_hdr *oph)
|
||||
{
|
||||
struct hnbgw_context_map *map;
|
||||
hash_for_each_possible(hsu->hnbgw_context_map_by_conn_id, map, hnbgw_sccp_user_entry, conn_id) {
|
||||
if (map->scu_conn_id == conn_id)
|
||||
return map;
|
||||
}
|
||||
LOGP(DRANAP, LOGL_ERROR, "Rx for unknown SCCP connection ID: %u: %s\n",
|
||||
conn_id, osmo_scu_prim_hdr_name_c(OTC_SELECT, oph));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int handle_cn_unitdata(struct hnbgw_sccp_user *hsu,
|
||||
const struct osmo_scu_unitdata_param *param,
|
||||
struct osmo_prim_hdr *oph)
|
||||
{
|
||||
struct hnbgw_cnlink *cnlink = cnlink_from_addr(hsu, ¶m->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, ¶m->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(¶m->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, ¶m->called_addr),
|
||||
hnbgw_cnlink_sccp_addr_to_str(map->cnlink, ¶m->calling_addr),
|
||||
hnbgw_cnlink_sccp_addr_to_str(map->cnlink, ¶m->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, ¶m->responding_addr),
|
||||
osmo_sua_sccp_cause_name(param->cause, cause_buf, sizeof(cause_buf)));
|
||||
|
||||
CNLINK_CTR_INC(map->cnlink, CNLINK_CTR_SCCP_N_DISCONNECT_IND);
|
||||
|
||||
return map_sccp_dispatch(map, MAP_SCCP_EV_RX_RELEASED, oph->msg);
|
||||
}
|
||||
|
||||
static struct hnbgw_cnlink *_cnlink_find_by_remote_pc(struct hnbgw_cnpool *cnpool, struct osmo_ss7_instance *cs7, uint32_t pc)
|
||||
{
|
||||
struct hnbgw_cnlink *cnlink;
|
||||
llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) {
|
||||
if (!cnlink->hnbgw_sccp_user)
|
||||
continue;
|
||||
if (cnlink->hnbgw_sccp_user->ss7 != cs7)
|
||||
continue;
|
||||
if ((cnlink->remote_addr.presence & OSMO_SCCP_ADDR_T_PC) == 0)
|
||||
continue;
|
||||
if (cnlink->remote_addr.pc != pc)
|
||||
continue;
|
||||
return cnlink;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find a cnlink by its remote sigtran point code on a given cs7 instance. */
|
||||
static struct hnbgw_cnlink *cnlink_find_by_remote_pc(struct osmo_ss7_instance *cs7, uint32_t pc)
|
||||
{
|
||||
struct hnbgw_cnlink *cnlink;
|
||||
cnlink = _cnlink_find_by_remote_pc(g_hnbgw->sccp.cnpool_iucs, cs7, pc);
|
||||
if (!cnlink)
|
||||
cnlink = _cnlink_find_by_remote_pc(g_hnbgw->sccp.cnpool_iups, cs7, pc);
|
||||
return cnlink;
|
||||
}
|
||||
|
||||
static void handle_pcstate_ind(struct hnbgw_sccp_user *hsu, const struct osmo_scu_pcstate_param *pcst)
|
||||
{
|
||||
struct hnbgw_cnlink *cnlink;
|
||||
bool connected;
|
||||
bool disconnected;
|
||||
struct osmo_ss7_instance *cs7 = hsu->ss7;
|
||||
|
||||
LOGP(DCN, LOGL_DEBUG, "N-PCSTATE ind: affected_pc=%u=%s sp_status=%s remote_sccp_status=%s\n",
|
||||
pcst->affected_pc, osmo_ss7_pointcode_print(cs7, pcst->affected_pc),
|
||||
osmo_sccp_sp_status_name(pcst->sp_status),
|
||||
osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status));
|
||||
|
||||
/* If we don't care about that point-code, ignore PCSTATE. */
|
||||
cnlink = cnlink_find_by_remote_pc(cs7, pcst->affected_pc);
|
||||
if (!cnlink)
|
||||
return;
|
||||
|
||||
CNLINK_CTR_INC(cnlink, CNLINK_CTR_SCCP_N_PCSTATE_IND);
|
||||
|
||||
/* See if this marks the point code to have become available, or to have been lost.
|
||||
*
|
||||
* I want to detect two events:
|
||||
* - connection event (both indicators say PC is reachable).
|
||||
* - disconnection event (at least one indicator says the PC is not reachable).
|
||||
*
|
||||
* There are two separate incoming indicators with various possible values -- the incoming events can be:
|
||||
*
|
||||
* - neither connection nor disconnection indicated -- just indicating congestion
|
||||
* connected == false, disconnected == false --> do nothing.
|
||||
* - both incoming values indicate that we are connected
|
||||
* --> trigger connected
|
||||
* - both indicate we are disconnected
|
||||
* --> trigger disconnected
|
||||
* - one value indicates 'connected', the other indicates 'disconnected'
|
||||
* --> trigger disconnected
|
||||
*
|
||||
* Congestion could imply that we're connected, but it does not indicate that a PC's reachability changed, so no need to
|
||||
* trigger on that.
|
||||
*/
|
||||
connected = false;
|
||||
disconnected = false;
|
||||
|
||||
switch (pcst->sp_status) {
|
||||
case OSMO_SCCP_SP_S_ACCESSIBLE:
|
||||
connected = true;
|
||||
break;
|
||||
case OSMO_SCCP_SP_S_INACCESSIBLE:
|
||||
disconnected = true;
|
||||
break;
|
||||
default:
|
||||
case OSMO_SCCP_SP_S_CONGESTED:
|
||||
/* Neither connecting nor disconnecting */
|
||||
break;
|
||||
}
|
||||
|
||||
switch (pcst->remote_sccp_status) {
|
||||
case OSMO_SCCP_REM_SCCP_S_AVAILABLE:
|
||||
if (!disconnected)
|
||||
connected = true;
|
||||
break;
|
||||
case OSMO_SCCP_REM_SCCP_S_UNAVAILABLE_UNKNOWN:
|
||||
case OSMO_SCCP_REM_SCCP_S_UNEQUIPPED:
|
||||
case OSMO_SCCP_REM_SCCP_S_INACCESSIBLE:
|
||||
disconnected = true;
|
||||
connected = false;
|
||||
break;
|
||||
default:
|
||||
case OSMO_SCCP_REM_SCCP_S_CONGESTED:
|
||||
/* Neither connecting nor disconnecting */
|
||||
break;
|
||||
}
|
||||
|
||||
if (disconnected && cnlink_is_conn_ready(cnlink)) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_NOTICE,
|
||||
"now unreachable: N-PCSTATE ind: pc=%u=%s sp_status=%s remote_sccp_status=%s\n",
|
||||
pcst->affected_pc, osmo_ss7_pointcode_print(cs7, pcst->affected_pc),
|
||||
osmo_sccp_sp_status_name(pcst->sp_status),
|
||||
osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status));
|
||||
/* A previously usable cnlink has disconnected. Kick it back to DISC state. */
|
||||
cnlink_set_disconnected(cnlink);
|
||||
} else if (connected && !cnlink_is_conn_ready(cnlink)) {
|
||||
LOG_CNLINK(cnlink, DCN, LOGL_NOTICE,
|
||||
"now available: N-PCSTATE ind: pc=%u=%s sp_status=%s remote_sccp_status=%s\n",
|
||||
pcst->affected_pc, osmo_ss7_pointcode_print(cs7, pcst->affected_pc),
|
||||
osmo_sccp_sp_status_name(pcst->sp_status),
|
||||
osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status));
|
||||
/* A previously unusable cnlink has become reachable. Trigger immediate RANAP RESET -- we would resend a
|
||||
* RESET either way, but we might as well do it now to speed up connecting. */
|
||||
cnlink_resend_reset(cnlink);
|
||||
}
|
||||
}
|
||||
|
||||
/* Entry point for primitives coming up from SCCP User SAP.
|
||||
* Ownership of oph->msg is transferred to us. */
|
||||
static int sccp_sap_up(struct osmo_prim_hdr *oph, void *ctx)
|
||||
{
|
||||
struct osmo_sccp_user *scu = ctx;
|
||||
struct hnbgw_sccp_user *hsu;
|
||||
struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph;
|
||||
int rc = 0;
|
||||
|
||||
LOGP(DCN, LOGL_DEBUG, "sccp_sap_up(%s)\n", osmo_scu_prim_name(oph));
|
||||
|
||||
if (!scu) {
|
||||
LOGP(DCN, LOGL_ERROR,
|
||||
"sccp_sap_up(): NULL osmo_sccp_user, cannot send prim (sap %u prim %u op %d)\n",
|
||||
oph->sap, oph->primitive, oph->operation);
|
||||
return -1;
|
||||
}
|
||||
|
||||
hsu = osmo_sccp_user_get_priv(scu);
|
||||
if (!hsu) {
|
||||
LOGP(DCN, LOGL_ERROR,
|
||||
"sccp_sap_up(): NULL hnbgw_sccp_user, cannot send prim (sap %u prim %u op %d)\n",
|
||||
oph->sap, oph->primitive, oph->operation);
|
||||
return -1;
|
||||
}
|
||||
|
||||
talloc_steal(OTC_SELECT, oph->msg);
|
||||
|
||||
switch (OSMO_PRIM_HDR(oph)) {
|
||||
case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION):
|
||||
rc = handle_cn_unitdata(hsu, &prim->u.unitdata, oph);
|
||||
break;
|
||||
case OSMO_PRIM(OSMO_SCU_PRIM_N_NOTICE, PRIM_OP_INDICATION):
|
||||
handle_notice_ind(hsu, &prim->u.notice, oph);
|
||||
break;
|
||||
case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM):
|
||||
rc = handle_cn_conn_conf(hsu, &prim->u.connect, oph);
|
||||
break;
|
||||
case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION):
|
||||
rc = handle_cn_data_ind(hsu, &prim->u.data, oph);
|
||||
break;
|
||||
case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION):
|
||||
rc = handle_cn_disc_ind(hsu, &prim->u.disconnect, oph);
|
||||
break;
|
||||
case OSMO_PRIM(OSMO_SCU_PRIM_N_PCSTATE, PRIM_OP_INDICATION):
|
||||
handle_pcstate_ind(hsu, &prim->u.pcstate);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOGP(DCN, LOGL_ERROR,
|
||||
"Received unknown prim %u from SCCP USER SAP\n",
|
||||
OSMO_PRIM_HDR(oph));
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* Submit primitives to SCCP User SAP
|
||||
***********************************************************************/
|
||||
|
||||
int hnbgw_sccp_user_tx_unitdata_req(struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *called_addr, struct msgb *ranap_msg)
|
||||
{
|
||||
if (!hsu) {
|
||||
LOGP(DCN, LOGL_ERROR, "Failed to send SCCP N-UNITDATA.req: no SCCP User\n");
|
||||
return -1;
|
||||
}
|
||||
OSMO_ASSERT(called_addr);
|
||||
return osmo_sccp_tx_unitdata_msg(hsu->sccp_user,
|
||||
&hsu->local_addr,
|
||||
called_addr,
|
||||
ranap_msg);
|
||||
}
|
||||
|
||||
int hnbgw_sccp_user_tx_connect_req(struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *called_addr, uint32_t scu_conn_id, struct msgb *ranap_msg)
|
||||
{
|
||||
struct osmo_scu_prim *prim;
|
||||
int rc;
|
||||
|
||||
if (!hsu) {
|
||||
LOGP(DCN, LOGL_ERROR, "Failed to send SCCP N-CONNECT.req(%u): no SCCP User\n", scu_conn_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
OSMO_ASSERT(called_addr);
|
||||
|
||||
prim = (struct osmo_scu_prim *)msgb_push(ranap_msg, sizeof(*prim));
|
||||
osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST, ranap_msg);
|
||||
prim->u.connect.called_addr = *called_addr;
|
||||
prim->u.connect.calling_addr = hsu->local_addr;
|
||||
prim->u.connect.sccp_class = 2;
|
||||
prim->u.connect.conn_id = scu_conn_id;
|
||||
|
||||
rc = osmo_sccp_user_sap_down_nofree(hsu->sccp_user, &prim->oph);
|
||||
if (rc)
|
||||
LOG_HSU(hsu, DCN, LOGL_ERROR, "Failed to send SCCP Connection Request to CN\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
int hnbgw_sccp_user_tx_data_req(struct hnbgw_sccp_user *hsu, uint32_t scu_conn_id, struct msgb *ranap_msg)
|
||||
{
|
||||
struct osmo_scu_prim *prim;
|
||||
int rc;
|
||||
|
||||
if (!hsu) {
|
||||
LOGP(DCN, LOGL_ERROR, "Failed to send SCCP N-DATA.req(%u): no SCCP User\n", scu_conn_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
prim = (struct osmo_scu_prim *)msgb_push(ranap_msg, sizeof(*prim));
|
||||
osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST, ranap_msg);
|
||||
prim->u.data.conn_id = scu_conn_id;
|
||||
|
||||
rc = osmo_sccp_user_sap_down_nofree(hsu->sccp_user, &prim->oph);
|
||||
if (rc)
|
||||
LOG_HSU(hsu, DCN, LOGL_ERROR, "Failed to send SCCP N-DATA.req(%u)\n", scu_conn_id);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int hnbgw_sccp_user_tx_disconnect_req(struct hnbgw_sccp_user *hsu, uint32_t scu_conn_id)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (!hsu) {
|
||||
LOGP(DCN, LOGL_ERROR, "Failed to send SCCP N-DISCONNECT.req(%u): no SCCP User\n", scu_conn_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = osmo_sccp_tx_disconn(hsu->sccp_user, scu_conn_id, NULL,
|
||||
SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
|
||||
if (rc)
|
||||
LOG_HSU(hsu, DCN, LOGL_ERROR, "Failed to send SCCP N-DISCONNECT.req(%u)\n", scu_conn_id);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* struct hnbgw_sccp_user lifecycle:
|
||||
***********************************************************************/
|
||||
|
||||
static int hnbgw_sccp_user_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line)
|
||||
{
|
||||
struct hnbgw_sccp_user *hsu = e->use_count->talloc_object;
|
||||
int32_t total;
|
||||
int level;
|
||||
|
||||
if (!e->use)
|
||||
return -EINVAL;
|
||||
|
||||
total = osmo_use_count_total(&hsu->use_count);
|
||||
|
||||
if (total == 0
|
||||
|| (total == 1 && old_use_count == 0 && e->count == 1))
|
||||
level = LOGL_INFO;
|
||||
else
|
||||
level = LOGL_DEBUG;
|
||||
|
||||
LOGPSRC(DCN, level, file, line,
|
||||
"%s: %s %s: now used by %s\n",
|
||||
hsu->name,
|
||||
(e->count - old_use_count) > 0 ? "+" : "-",
|
||||
e->use,
|
||||
osmo_use_count_to_str_c(OTC_SELECT, &hsu->use_count));
|
||||
|
||||
if (e->count < 0)
|
||||
return -ERANGE;
|
||||
|
||||
if (total == 0)
|
||||
talloc_free(hsu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hnbgw_sccp_user_talloc_destructor(struct hnbgw_sccp_user *hsu)
|
||||
{
|
||||
if (hsu->sccp_user) {
|
||||
osmo_sccp_user_unbind(hsu->sccp_user);
|
||||
hsu->sccp_user = NULL;
|
||||
}
|
||||
llist_del(&hsu->entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct hnbgw_sccp_user *hnbgw_sccp_user_alloc(int ss7_id)
|
||||
{
|
||||
struct osmo_sccp_instance *sccp;
|
||||
uint32_t local_pc;
|
||||
struct hnbgw_sccp_user *hsu;
|
||||
|
||||
hsu = talloc_zero(g_hnbgw, struct hnbgw_sccp_user);
|
||||
OSMO_ASSERT(hsu);
|
||||
*hsu = (struct hnbgw_sccp_user){
|
||||
.name = talloc_asprintf(hsu, "cs7-%u-sccp-OsmoHNBGW", ss7_id),
|
||||
.use_count = {
|
||||
.talloc_object = hsu,
|
||||
.use_cb = hnbgw_sccp_user_use_cb,
|
||||
},
|
||||
};
|
||||
hash_init(hsu->hnbgw_context_map_by_conn_id);
|
||||
llist_add_tail(&hsu->entry, &g_hnbgw->sccp.users);
|
||||
talloc_set_destructor(hsu, hnbgw_sccp_user_talloc_destructor);
|
||||
|
||||
sccp = osmo_sccp_simple_client_on_ss7_id(g_hnbgw,
|
||||
ss7_id,
|
||||
hsu->name,
|
||||
DEFAULT_PC_HNBGW,
|
||||
OSMO_SS7_ASP_PROT_M3UA,
|
||||
0,
|
||||
"localhost",
|
||||
-1,
|
||||
"localhost");
|
||||
if (!sccp) {
|
||||
LOG_HSU(hsu, DCN, LOGL_ERROR, "Failed to configure SCCP on 'cs7 instance %u'\n",
|
||||
ss7_id);
|
||||
goto free_hsu_ret;
|
||||
}
|
||||
hsu->ss7 = osmo_sccp_get_ss7(sccp);
|
||||
LOG_HSU(hsu, DCN, LOGL_NOTICE, "created SCCP instance on cs7 instance %u\n", osmo_ss7_instance_get_id(hsu->ss7));
|
||||
|
||||
/* Bind the SCCP user, using the cs7 instance's default point-code if one is configured, or osmo-hnbgw's default
|
||||
* local PC. */
|
||||
local_pc = osmo_ss7_instance_get_primary_pc(hsu->ss7);
|
||||
if (!osmo_ss7_pc_is_valid(local_pc))
|
||||
local_pc = DEFAULT_PC_HNBGW;
|
||||
|
||||
LOG_HSU(hsu, DCN, LOGL_DEBUG, "binding OsmoHNBGW user to cs7 instance %u, local PC %u = %s\n",
|
||||
osmo_ss7_instance_get_id(hsu->ss7), local_pc, osmo_ss7_pointcode_print(hsu->ss7, local_pc));
|
||||
|
||||
char *sccp_user_name = talloc_asprintf(hsu, "%s-RANAP", hsu->name);
|
||||
hsu->sccp_user = osmo_sccp_user_bind_pc(sccp, sccp_user_name, sccp_sap_up, OSMO_SCCP_SSN_RANAP, local_pc);
|
||||
talloc_free(sccp_user_name);
|
||||
if (!hsu->sccp_user) {
|
||||
LOG_HSU(hsu, DCN, LOGL_ERROR, "Failed to init SCCP User\n");
|
||||
goto free_hsu_ret;
|
||||
}
|
||||
|
||||
osmo_sccp_make_addr_pc_ssn(&hsu->local_addr, local_pc, OSMO_SCCP_SSN_RANAP);
|
||||
osmo_sccp_user_set_priv(hsu->sccp_user, hsu);
|
||||
|
||||
return hsu;
|
||||
|
||||
free_hsu_ret:
|
||||
talloc_free(hsu);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
112
src/osmo-hnbgw/kpi_dtap.c
Normal file
112
src/osmo-hnbgw/kpi_dtap.c
Normal file
@@ -0,0 +1,112 @@
|
||||
/* KPI (statistics, counters) at DTAP level */
|
||||
/* (C) 2024 by Harald Welte <laforge@osmocom.org>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include <osmocom/ranap/ranap_common_ran.h>
|
||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
|
||||
#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnb_persistent.h>
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/kpi.h>
|
||||
|
||||
/***********************************************************************
|
||||
* DOWNLINK messages
|
||||
***********************************************************************/
|
||||
|
||||
void kpi_dtap_process_dl(struct hnbgw_context_map *map, const uint8_t *buf, unsigned int len,
|
||||
uint8_t sapi)
|
||||
{
|
||||
struct hnb_persistent *hnbp = map->hnb_ctx->persistent;
|
||||
const struct gsm48_hdr *gh = (const struct gsm48_hdr *)buf;
|
||||
if (len < sizeof(*gh))
|
||||
return;
|
||||
|
||||
/* if you make use of any data beyond the fixed-size gsm48_hdr, you must make sure the underlying
|
||||
* buffer length is actually long enough! */
|
||||
|
||||
if (map->is_ps) {
|
||||
/* Packet Switched Domain (from SGSN) */
|
||||
switch (gsm48_hdr_msg_type(gh)) {
|
||||
case GSM48_MT_GMM_ATTACH_ACK:
|
||||
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_ATT_ACK);
|
||||
break;
|
||||
case GSM48_MT_GMM_ATTACH_REJ:
|
||||
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_ATT_REJ);
|
||||
break;
|
||||
case GSM48_MT_GMM_RA_UPD_ACK:
|
||||
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_RAU_ACK);
|
||||
break;
|
||||
case GSM48_MT_GMM_RA_UPD_REJ:
|
||||
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_RAU_REJ);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* Circuit Switched Domain (from MSC) */
|
||||
switch (gsm48_hdr_msg_type(gh)) {
|
||||
case GSM48_MT_MM_LOC_UPD_ACCEPT:
|
||||
/* FIXME: many LU are acknwoeldged implicitly with TMSI allocation */
|
||||
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_CS_LU_ACC);
|
||||
break;
|
||||
case GSM48_MT_MM_LOC_UPD_REJECT:
|
||||
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_CS_LU_REJ);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* UPLINK messages
|
||||
***********************************************************************/
|
||||
|
||||
void kpi_dtap_process_ul(struct hnbgw_context_map *map, const uint8_t *buf, unsigned int len,
|
||||
uint8_t sapi)
|
||||
{
|
||||
struct hnb_persistent *hnbp = map->hnb_ctx->persistent;
|
||||
const struct gsm48_hdr *gh = (const struct gsm48_hdr *)buf;
|
||||
if (len < sizeof(*gh))
|
||||
return;
|
||||
|
||||
/* if you make use of any data beyond the fixed-size gsm48_hdr, you must make sure the underlying
|
||||
* buffer length is actually long enough! */
|
||||
|
||||
if (map->is_ps) {
|
||||
/* Packet Switched Domain (to SGSN) */
|
||||
switch (gsm48_hdr_msg_type(gh)) {
|
||||
case GSM48_MT_GMM_ATTACH_REQ:
|
||||
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_ATT_REQ);
|
||||
break;
|
||||
case GSM48_MT_GMM_RA_UPD_REQ:
|
||||
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_PS_RAU_REQ);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* Circuit Switched Domain (to MSC) */
|
||||
switch (gsm48_hdr_msg_type(gh)) {
|
||||
case GSM48_MT_MM_LOC_UPD_REQUEST:
|
||||
HNBP_CTR_INC(hnbp, HNB_CTR_DTAP_CS_LU_REQ);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
465
src/osmo-hnbgw/kpi_ranap.c
Normal file
465
src/osmo-hnbgw/kpi_ranap.c
Normal file
@@ -0,0 +1,465 @@
|
||||
/* KPI (statistics, counters) at RANAP level */
|
||||
/* (C) 2024 by Harald Welte <laforge@osmocom.org>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include <osmocom/ranap/ranap_common_ran.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnb_persistent.h>
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/kpi.h>
|
||||
|
||||
const struct value_string hnbgw_rab_state_names[] = {
|
||||
{ RAB_STATE_INACTIVE, "INACTIVE" },
|
||||
{ RAB_STATE_ACT_REQ, "ACT_REQ" },
|
||||
{ RAB_STATE_ACTIVE, "ACTIVE" },
|
||||
{ RAB_STATE_REL_REQ, "REL_REQ" },
|
||||
{}
|
||||
};
|
||||
|
||||
/***********************************************************************
|
||||
* DOWNLINK messages
|
||||
***********************************************************************/
|
||||
|
||||
static void kpi_ranap_process_dl_iu_rel_cmd(struct hnbgw_context_map *map, const ranap_message *ranap)
|
||||
{
|
||||
struct hnb_persistent *hnbp = map->hnb_ctx->persistent;
|
||||
const RANAP_Cause_t *cause;
|
||||
|
||||
OSMO_ASSERT(ranap->procedureCode == RANAP_ProcedureCode_id_Iu_Release);
|
||||
|
||||
cause = &ranap->msg.iu_ReleaseCommandIEs.cause;
|
||||
|
||||
/* When Iu is released, all RABs are released implicitly */
|
||||
for (unsigned int i = 0; i < ARRAY_SIZE(map->rab_state); i++) {
|
||||
switch (map->rab_state[i]) {
|
||||
case RAB_STATE_ACTIVE:
|
||||
if (cause->present == RANAP_Cause_PR_nAS ||
|
||||
cause->choice.nAS == RANAP_CauseNAS_normal_release) {
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT :
|
||||
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT);
|
||||
} else {
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT_ABNORMAL :
|
||||
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT_ABNORMAL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* clear all RAB state */
|
||||
memset(map->rab_state, 0, sizeof(map->rab_state));
|
||||
}
|
||||
|
||||
static void kpi_ranap_process_dl_rab_ass_req(struct hnbgw_context_map *map, ranap_message *ranap)
|
||||
{
|
||||
struct hnb_persistent *hnbp = map->hnb_ctx->persistent;
|
||||
RANAP_RAB_AssignmentRequestIEs_t *ies;
|
||||
int rc;
|
||||
|
||||
OSMO_ASSERT(ranap->procedureCode == RANAP_ProcedureCode_id_RAB_Assignment);
|
||||
|
||||
ies = &ranap->msg.raB_AssignmentRequestIEs;
|
||||
|
||||
if (ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT) {
|
||||
RANAP_RAB_SetupOrModifyList_t *som_list = &ies->raB_SetupOrModifyList;
|
||||
for (unsigned int i = 0; i < som_list->list.count; i++) {
|
||||
RANAP_ProtocolIE_ContainerPair_t *container_pair = som_list->list.array[i];
|
||||
RANAP_ProtocolIE_FieldPair_t *field_pair = container_pair->list.array[0];
|
||||
RANAP_RAB_SetupOrModifyItemFirst_t _rab_setup_or_modify_item_first = {};
|
||||
RANAP_RAB_SetupOrModifyItemFirst_t *rab_setup_or_modify_item_first = &_rab_setup_or_modify_item_first;
|
||||
uint8_t rab_id;
|
||||
|
||||
if (!field_pair)
|
||||
continue;
|
||||
|
||||
if (field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem)
|
||||
continue;
|
||||
|
||||
rc = ranap_decode_rab_setupormodifyitemfirst(rab_setup_or_modify_item_first, &field_pair->firstValue);
|
||||
if (rc < 0)
|
||||
continue;
|
||||
|
||||
/* RAB-ID is an 8-bit bit-string, so it's the first byte */
|
||||
rab_id = rab_setup_or_modify_item_first->rAB_ID.buf[0];
|
||||
|
||||
/* the only way to distinguish a "setup" from a "modify" is to know which RABs are
|
||||
* already established. If it's already established, it is a modification; if it's
|
||||
* new, it is a setup */
|
||||
switch (map->rab_state[rab_id]) {
|
||||
case RAB_STATE_ACTIVE:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_REQ :
|
||||
HNB_CTR_RANAP_CS_RAB_MOD_REQ);
|
||||
break;
|
||||
case RAB_STATE_INACTIVE:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_REQ :
|
||||
HNB_CTR_RANAP_CS_RAB_ACT_REQ);
|
||||
map->rab_state[rab_id] = RAB_STATE_ACT_REQ;
|
||||
break;
|
||||
default:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_REQ_UNEXP :
|
||||
HNB_CTR_RANAP_CS_RAB_ACT_REQ_UNEXP);
|
||||
LOG_MAP(map, DRANAP, LOGL_NOTICE,
|
||||
"Unexpected RAB Activation/Modification Req for RAB in state %s\n",
|
||||
hnbgw_rab_state_name(map->rab_state[rab_id]));
|
||||
break;
|
||||
}
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, rab_setup_or_modify_item_first);
|
||||
}
|
||||
}
|
||||
|
||||
if (ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_RELEASELIST_PRESENT) {
|
||||
RANAP_RAB_ReleaseList_t *r_list = &ies->raB_ReleaseList;
|
||||
for (unsigned int i = 0; i < r_list->raB_ReleaseList_ies.list.count; i++) {
|
||||
RANAP_IE_t *release_list_ie = r_list->raB_ReleaseList_ies.list.array[i];
|
||||
RANAP_RAB_ReleaseItemIEs_t _rab_rel_item_ies = {};
|
||||
RANAP_RAB_ReleaseItemIEs_t *rab_rel_item_ies = &_rab_rel_item_ies;
|
||||
RANAP_RAB_ReleaseItem_t *rab_rel_item;
|
||||
uint8_t rab_id;
|
||||
|
||||
if (!release_list_ie)
|
||||
continue;
|
||||
|
||||
if (release_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_ReleaseItem)
|
||||
continue;
|
||||
|
||||
rc = ranap_decode_rab_releaseitemies_fromlist(rab_rel_item_ies, &release_list_ie->value);
|
||||
if (rc < 0)
|
||||
continue;
|
||||
|
||||
rab_rel_item = &rab_rel_item_ies->raB_ReleaseItem;
|
||||
/* RAB-ID is an 8-bit bit-string, so it's the first byte */
|
||||
rab_id = rab_rel_item->rAB_ID.buf[0];
|
||||
|
||||
switch (map->rab_state[rab_id]) {
|
||||
case RAB_STATE_ACTIVE:
|
||||
if (rab_rel_item->cause.present == RANAP_Cause_PR_nAS &&
|
||||
rab_rel_item->cause.choice.nAS == RANAP_CauseNAS_normal_release) {
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_REQ :
|
||||
HNB_CTR_RANAP_CS_RAB_REL_REQ);
|
||||
} else {
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_REQ_ABNORMAL :
|
||||
HNB_CTR_RANAP_CS_RAB_REL_REQ_ABNORMAL);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_REQ_UNEXP :
|
||||
HNB_CTR_RANAP_CS_RAB_REL_REQ_UNEXP);
|
||||
LOG_MAP(map, DRANAP, LOGL_NOTICE,
|
||||
"Unexpected RAB Release Req in state %s\n",
|
||||
hnbgw_rab_state_name(map->rab_state[rab_id]));
|
||||
break;
|
||||
}
|
||||
/* mark that RAB as release requested */
|
||||
map->rab_state[rab_id] = RAB_STATE_REL_REQ;
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_ReleaseItem, rab_rel_item_ies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void kpi_ranap_process_dl_direct_transfer(struct hnbgw_context_map *map, ranap_message *ranap)
|
||||
{
|
||||
const RANAP_DirectTransferIEs_t *dt_ies = &ranap->msg.directTransferIEs;
|
||||
uint8_t sapi = 0;
|
||||
|
||||
if (dt_ies->presenceMask & DIRECTTRANSFERIES_RANAP_SAPI_PRESENT) {
|
||||
if (dt_ies->sapi == RANAP_SAPI_sapi_3)
|
||||
sapi = 3;
|
||||
}
|
||||
kpi_dtap_process_dl(map, dt_ies->nas_pdu.buf, dt_ies->nas_pdu.size, sapi);
|
||||
}
|
||||
|
||||
void kpi_ranap_process_dl(struct hnbgw_context_map *map, ranap_message *ranap)
|
||||
{
|
||||
if (map->hnb_ctx == NULL) {
|
||||
/* This can happen if the HNB has disconnected and we are processing downlink messages
|
||||
* from the CN which were already in flight before the CN side has realized the HNB
|
||||
* is gone. */
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ranap->procedureCode) {
|
||||
case RANAP_ProcedureCode_id_RAB_Assignment: /* RAB ASSIGNMENT REQ (8.2) */
|
||||
kpi_ranap_process_dl_rab_ass_req(map, ranap);
|
||||
break;
|
||||
case RANAP_ProcedureCode_id_Iu_Release:
|
||||
kpi_ranap_process_dl_iu_rel_cmd(map, ranap); /* IU RELEASE CMD (8.5) */
|
||||
break;
|
||||
case RANAP_ProcedureCode_id_DirectTransfer:
|
||||
kpi_ranap_process_dl_direct_transfer(map, ranap);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* UPLINK messages
|
||||
***********************************************************************/
|
||||
|
||||
static void kpi_ranap_process_ul_rab_ass_resp(struct hnbgw_context_map *map, ranap_message *ranap)
|
||||
{
|
||||
struct hnb_persistent *hnbp = map->hnb_ctx->persistent;
|
||||
RANAP_RAB_AssignmentResponseIEs_t *ies;
|
||||
int rc;
|
||||
|
||||
OSMO_ASSERT(ranap->procedureCode == RANAP_ProcedureCode_id_RAB_Assignment);
|
||||
|
||||
ies = &ranap->msg.raB_AssignmentResponseIEs;
|
||||
|
||||
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_SETUPORMODIFIEDLIST_PRESENT) {
|
||||
RANAP_RAB_SetupOrModifiedList_t *som_list = &ies->raB_SetupOrModifiedList;
|
||||
for (unsigned int i = 0; i < som_list->raB_SetupOrModifiedList_ies.list.count; i++) {
|
||||
RANAP_IE_t *som_list_ie = som_list->raB_SetupOrModifiedList_ies.list.array[i];
|
||||
RANAP_RAB_SetupOrModifiedItemIEs_t _rab_som_item_ies = {};
|
||||
RANAP_RAB_SetupOrModifiedItemIEs_t *rab_som_item_ies = &_rab_som_item_ies;
|
||||
RANAP_RAB_SetupOrModifiedItem_t *rab_som_item;
|
||||
uint8_t rab_id;
|
||||
|
||||
if (!som_list_ie)
|
||||
continue;
|
||||
|
||||
if (som_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifiedItem)
|
||||
continue;
|
||||
|
||||
rc = ranap_decode_rab_setupormodifieditemies_fromlist(rab_som_item_ies, &som_list_ie->value);
|
||||
if (rc < 0)
|
||||
continue;
|
||||
|
||||
rab_som_item = &rab_som_item_ies->raB_SetupOrModifiedItem;
|
||||
/* RAB-ID is an 8-bit bit-string, so it's the first byte */
|
||||
rab_id = rab_som_item->rAB_ID.buf[0];
|
||||
|
||||
/* differentiate modify / activate */
|
||||
switch (map->rab_state[rab_id]) {
|
||||
case RAB_STATE_ACT_REQ:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_CNF :
|
||||
HNB_CTR_RANAP_CS_RAB_ACT_CNF);
|
||||
map->rab_state[rab_id] = RAB_STATE_ACTIVE;
|
||||
break;
|
||||
case RAB_STATE_ACTIVE:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_CNF :
|
||||
HNB_CTR_RANAP_CS_RAB_MOD_CNF);
|
||||
break;
|
||||
default:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_CNF_UNEXP :
|
||||
HNB_CTR_RANAP_CS_RAB_ACT_CNF_UNEXP);
|
||||
LOG_MAP(map, DRANAP, LOGL_NOTICE,
|
||||
"Unexpected RAB Activation/Modification Conf for RAB in state %s\n",
|
||||
hnbgw_rab_state_name(map->rab_state[rab_id]));
|
||||
break;
|
||||
}
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, rab_som_item_ies);
|
||||
}
|
||||
}
|
||||
|
||||
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_RELEASEDLIST_PRESENT) {
|
||||
RANAP_RAB_ReleasedList_t *r_list = &ies->raB_ReleasedList;
|
||||
/* increment number of released RABs, we don't need to do that individually during iteration */
|
||||
for (unsigned int i = 0; i < r_list->raB_ReleasedList_ies.list.count; i++) {
|
||||
RANAP_IE_t *released_list_ie = r_list->raB_ReleasedList_ies.list.array[i];
|
||||
RANAP_RAB_ReleasedItemIEs_t _rab_rel_item_ies = {};
|
||||
RANAP_RAB_ReleasedItemIEs_t *rab_rel_item_ies = &_rab_rel_item_ies;
|
||||
RANAP_RAB_ReleasedItem_t *rab_rel_item;
|
||||
uint8_t rab_id;
|
||||
|
||||
if (!released_list_ie)
|
||||
continue;
|
||||
|
||||
if (released_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_ReleasedItem)
|
||||
continue;
|
||||
|
||||
rc = ranap_decode_rab_releaseditemies_fromlist(rab_rel_item_ies, &released_list_ie->value);
|
||||
if (rc < 0)
|
||||
continue;
|
||||
|
||||
rab_rel_item = &rab_rel_item_ies->raB_ReleasedItem;
|
||||
/* RAB-ID is an 8-bit bit-string, so it's the first byte */
|
||||
rab_id = rab_rel_item->rAB_ID.buf[0];
|
||||
|
||||
switch (map->rab_state[rab_id]) {
|
||||
case RAB_STATE_REL_REQ:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_CNF :
|
||||
HNB_CTR_RANAP_CS_RAB_REL_CNF);
|
||||
break;
|
||||
default:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_CNF_UNEXP :
|
||||
HNB_CTR_RANAP_CS_RAB_REL_CNF_UNEXP);
|
||||
LOG_MAP(map, DRANAP, LOGL_NOTICE,
|
||||
"Unexpected RAB Release Conf for RAB in state %s\n",
|
||||
hnbgw_rab_state_name(map->rab_state[rab_id]));
|
||||
break;
|
||||
}
|
||||
/* mark that RAB as released */
|
||||
map->rab_state[rab_id] = RAB_STATE_INACTIVE;
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_ReleasedItem, rab_rel_item_ies);
|
||||
}
|
||||
}
|
||||
|
||||
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_QUEUEDLIST_PRESENT)
|
||||
LOG_MAP(map, DRANAP, LOGL_NOTICE, "RAB Activation has been queued; we don't support KPIs for this\n");
|
||||
|
||||
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_FAILEDLIST_PRESENT) {
|
||||
RANAP_RAB_FailedList_t *f_list = &ies->raB_FailedList;
|
||||
for (unsigned int i = 0; i < f_list->raB_FailedList_ies.list.count; i++) {
|
||||
RANAP_IE_t *failed_list_ie = f_list->raB_FailedList_ies.list.array[i];
|
||||
RANAP_RAB_FailedItemIEs_t _rab_failed_item_ies = {};
|
||||
RANAP_RAB_FailedItemIEs_t *rab_failed_item_ies = &_rab_failed_item_ies;
|
||||
RANAP_RAB_FailedItem_t *rab_failed_item;
|
||||
uint8_t rab_id;
|
||||
|
||||
if (!failed_list_ie)
|
||||
continue;
|
||||
|
||||
if (failed_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_FailedItem)
|
||||
continue;
|
||||
|
||||
rc = ranap_decode_rab_faileditemies_fromlist(rab_failed_item_ies, &failed_list_ie->value);
|
||||
if (rc < 0)
|
||||
continue;
|
||||
|
||||
rab_failed_item = &rab_failed_item_ies->raB_FailedItem;
|
||||
/* RAB-ID is an 8-bit bit-string, so it's the first byte */
|
||||
rab_id = rab_failed_item->rAB_ID.buf[0];
|
||||
|
||||
/* differentiate modify / activate */
|
||||
switch (map->rab_state[rab_id]) {
|
||||
case RAB_STATE_ACT_REQ:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_FAIL :
|
||||
HNB_CTR_RANAP_CS_RAB_ACT_FAIL);
|
||||
map->rab_state[rab_id] = RAB_STATE_INACTIVE;
|
||||
break;
|
||||
case RAB_STATE_ACTIVE:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_MOD_FAIL :
|
||||
HNB_CTR_RANAP_CS_RAB_MOD_FAIL);
|
||||
// FIXME: does it remain active after modification failure?
|
||||
break;
|
||||
default:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_ACT_FAIL_UNEXP :
|
||||
HNB_CTR_RANAP_CS_RAB_ACT_FAIL_UNEXP);
|
||||
LOG_MAP(map, DRANAP, LOGL_NOTICE,
|
||||
"Unexpected RAB Activation/Modification Failed for RAB in state %s\n",
|
||||
hnbgw_rab_state_name(map->rab_state[rab_id]));
|
||||
break;
|
||||
}
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_FailedItem, rab_failed_item_ies);
|
||||
}
|
||||
}
|
||||
|
||||
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_RELEASEFAILEDLIST_PRESENT) {
|
||||
RANAP_RAB_ReleaseFailedList_t *rf_list = &ies->raB_ReleaseFailedList;
|
||||
/* increment number of released RABs, we don't need to do that individually during iteration */
|
||||
for (unsigned int i = 0; i < rf_list->raB_FailedList_ies.list.count; i++) {
|
||||
RANAP_IE_t *failed_list_ie = rf_list->raB_FailedList_ies.list.array[i];
|
||||
RANAP_RAB_FailedItemIEs_t _rab_failed_item_ies = {};
|
||||
RANAP_RAB_FailedItemIEs_t *rab_failed_item_ies = &_rab_failed_item_ies;
|
||||
RANAP_RAB_FailedItem_t *rab_failed_item;
|
||||
uint8_t rab_id;
|
||||
|
||||
if (!failed_list_ie)
|
||||
continue;
|
||||
|
||||
if (failed_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_FailedItem)
|
||||
continue;
|
||||
|
||||
rc = ranap_decode_rab_faileditemies_fromlist(rab_failed_item_ies, &failed_list_ie->value);
|
||||
if (rc < 0)
|
||||
continue;
|
||||
|
||||
rab_failed_item = &rab_failed_item_ies->raB_FailedItem;
|
||||
/* RAB-ID is an 8-bit bit-string, so it's the first byte */
|
||||
rab_id = rab_failed_item->rAB_ID.buf[0];
|
||||
|
||||
/* differentiate modify / activate */
|
||||
switch (map->rab_state[rab_id]) {
|
||||
case RAB_STATE_ACT_REQ:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_FAIL :
|
||||
HNB_CTR_RANAP_CS_RAB_REL_FAIL);
|
||||
map->rab_state[rab_id] = RAB_STATE_INACTIVE;
|
||||
break;
|
||||
case RAB_STATE_ACTIVE:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_FAIL :
|
||||
HNB_CTR_RANAP_CS_RAB_REL_FAIL);
|
||||
// FIXME: does it remain active after modification failure?
|
||||
break;
|
||||
default:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_FAIL_UNEXP :
|
||||
HNB_CTR_RANAP_CS_RAB_REL_FAIL_UNEXP);
|
||||
LOG_MAP(map, DRANAP, LOGL_NOTICE,
|
||||
"Unexpected RAB Release Failed for RAB in state %s\n",
|
||||
hnbgw_rab_state_name(map->rab_state[rab_id]));
|
||||
break;
|
||||
}
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_FailedItem, rab_failed_item_ies);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void kpi_ranap_process_ul_initial_ue(struct hnbgw_context_map *map, ranap_message *ranap)
|
||||
{
|
||||
const RANAP_InitialUE_MessageIEs_t *iue_ies = &ranap->msg.initialUE_MessageIEs;
|
||||
kpi_dtap_process_ul(map, iue_ies->nas_pdu.buf, iue_ies->nas_pdu.size, 0);
|
||||
}
|
||||
|
||||
static void kpi_ranap_process_ul_direct_transfer(struct hnbgw_context_map *map, ranap_message *ranap)
|
||||
{
|
||||
const RANAP_DirectTransferIEs_t *dt_ies = &ranap->msg.directTransferIEs;
|
||||
uint8_t sapi = 0;
|
||||
|
||||
if (dt_ies->presenceMask & DIRECTTRANSFERIES_RANAP_SAPI_PRESENT) {
|
||||
if (dt_ies->sapi == RANAP_SAPI_sapi_3)
|
||||
sapi = 3;
|
||||
}
|
||||
kpi_dtap_process_ul(map, dt_ies->nas_pdu.buf, dt_ies->nas_pdu.size, sapi);
|
||||
}
|
||||
|
||||
void kpi_ranap_process_ul(struct hnbgw_context_map *map, ranap_message *ranap)
|
||||
{
|
||||
/* we should never be processing uplink messages from a non-existant HNB */
|
||||
OSMO_ASSERT(map->hnb_ctx);
|
||||
|
||||
switch (ranap->procedureCode) {
|
||||
case RANAP_ProcedureCode_id_RAB_Assignment: /* RAB ASSIGNMENT REQ (8.2) */
|
||||
kpi_ranap_process_ul_rab_ass_resp(map, ranap);
|
||||
break;
|
||||
case RANAP_ProcedureCode_id_Iu_Release:
|
||||
/* TODO: We might want to parse the list of released RABs here and then mark each of those as
|
||||
* released. For now we simply assume that all RABs are released in IU RELEASE during
|
||||
* processing of the downlink Iu Release Command. It's not like the RNC/HNB has any way to
|
||||
* refuse the release anyway. */
|
||||
break;
|
||||
case RANAP_ProcedureCode_id_InitialUE_Message:
|
||||
kpi_ranap_process_ul_initial_ue(map, ranap);
|
||||
break;
|
||||
case RANAP_ProcedureCode_id_DirectTransfer:
|
||||
kpi_ranap_process_ul_direct_transfer(map, ranap);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
958
src/osmo-hnbgw/mgw_fsm.c
Normal file
958
src/osmo-hnbgw/mgw_fsm.c
Normal file
@@ -0,0 +1,958 @@
|
||||
/* (C) 2021-2024 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Philipp Maier
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/prim.h>
|
||||
|
||||
#include <osmocom/core/fsm.h>
|
||||
#include <osmocom/core/byteswap.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
|
||||
#include <osmocom/netif/stream.h>
|
||||
|
||||
#include <osmocom/ranap/ranap_common.h>
|
||||
#include <osmocom/ranap/ranap_common_cn.h>
|
||||
#include <osmocom/ranap/ranap_common_ran.h>
|
||||
#include <osmocom/ranap/ranap_msg_factory.h>
|
||||
|
||||
#include <osmocom/ranap/ranap_ies_defs.h>
|
||||
#include <osmocom/ranap/iu_helpers.h>
|
||||
#include <asn1c/asn1helpers.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/ranap_rab_ass.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw_rua.h>
|
||||
|
||||
#include <osmocom/core/tdef.h>
|
||||
#include <osmocom/hnbgw/tdefs.h>
|
||||
#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
|
||||
|
||||
/* NOTE: This implementation can only handle one RAB per context_map (== SCCP connection == RUA connection == UE
|
||||
* context). This simplification was made because usually a voice call will require only one RAB at a time. An exception
|
||||
* may be corner cases like video calls, which we do not support at the moment. */
|
||||
|
||||
/* Send Iu Release Request, this is done in erroneous cases from which we cannot recover */
|
||||
static void tx_release_req(struct hnbgw_context_map *map)
|
||||
{
|
||||
struct msgb *msg;
|
||||
static const struct RANAP_Cause cause = {
|
||||
.present = RANAP_Cause_PR_transmissionNetwork,
|
||||
.choice.transmissionNetwork =
|
||||
RANAP_CauseTransmissionNetwork_iu_transport_connection_failed_to_establish,
|
||||
};
|
||||
|
||||
msg = ranap_new_msg_iu_rel_req(&cause);
|
||||
msg->l2h = msg->data;
|
||||
talloc_steal(OTC_SELECT, msg);
|
||||
map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, msg);
|
||||
}
|
||||
|
||||
#define S(x) (1 << (x))
|
||||
|
||||
extern int asn1_xer_print;
|
||||
|
||||
enum mgw_fsm_event {
|
||||
MGW_EV_MGCP_OK,
|
||||
MGW_EV_MGCP_FAIL,
|
||||
MGW_EV_MGCP_TERM,
|
||||
MGW_EV_RAB_ASS_RESP,
|
||||
MGW_EV_RELEASE,
|
||||
};
|
||||
|
||||
static const struct value_string mgw_fsm_event_names[] = {
|
||||
OSMO_VALUE_STRING(MGW_EV_MGCP_OK),
|
||||
OSMO_VALUE_STRING(MGW_EV_MGCP_FAIL),
|
||||
OSMO_VALUE_STRING(MGW_EV_MGCP_TERM),
|
||||
OSMO_VALUE_STRING(MGW_EV_RAB_ASS_RESP),
|
||||
OSMO_VALUE_STRING(MGW_EV_RELEASE),
|
||||
{}
|
||||
};
|
||||
|
||||
enum mgw_fsm_state {
|
||||
MGW_ST_CRCX_HNB,
|
||||
MGW_ST_ASSIGN,
|
||||
MGW_ST_MDCX_HNB,
|
||||
MGW_ST_CRCX_MSC,
|
||||
MGW_ST_ESTABLISHED,
|
||||
MGW_ST_RELEASE,
|
||||
MGW_ST_FAILURE,
|
||||
};
|
||||
|
||||
struct mgw_fsm_priv {
|
||||
/* Backpointer to HNBGW context */
|
||||
struct hnbgw_context_map *map;
|
||||
|
||||
/* RAB-ID from RANAP RAB AssignmentRequest message */
|
||||
uint8_t rab_id;
|
||||
|
||||
/* Pointers to messages and prim header we take ownership of */
|
||||
ranap_message *ranap_rab_ass_req_message;
|
||||
ranap_message *ranap_rab_ass_resp_message;
|
||||
struct msgb *ranap_rab_ass_resp_msgb;
|
||||
/* IP address contained in ranap_rab_ass_resp_msgb/message: */
|
||||
struct osmo_sockaddr hnb_rtp_addr;
|
||||
/* Number of MDCX transmitted. Used to detect current mgw conn_mode and
|
||||
* detect modify infinite loops: */
|
||||
unsigned int mdcx_tx_cnt;
|
||||
|
||||
/* MGW context */
|
||||
struct mgcp_client *mgcpc;
|
||||
struct osmo_mgcpc_ep *mgcpc_ep;
|
||||
struct osmo_mgcpc_ep_ci *mgcpc_ep_ci_hnb;
|
||||
struct osmo_mgcpc_ep_ci *mgcpc_ep_ci_msc;
|
||||
struct osmo_sockaddr ci_hnb_crcx_ack_addr;
|
||||
char msc_rtp_addr[INET6_ADDRSTRLEN];
|
||||
uint16_t msc_rtp_port;
|
||||
|
||||
/* Timestamps to track active duration */
|
||||
struct timespec active_start;
|
||||
struct timespec active_stored;
|
||||
};
|
||||
|
||||
struct osmo_tdef_state_timeout mgw_fsm_timeouts[32] = {
|
||||
[MGW_ST_CRCX_HNB] = {.T = -1001 },
|
||||
[MGW_ST_ASSIGN] = {.T = -1002 },
|
||||
[MGW_ST_MDCX_HNB] = {.T = -1003 },
|
||||
[MGW_ST_CRCX_MSC] = {.T = -1004 },
|
||||
};
|
||||
|
||||
#define mgw_fsm_state_chg(fi, state) \
|
||||
osmo_tdef_fsm_inst_state_chg(fi, state, mgw_fsm_timeouts, mgw_fsm_T_defs, -1)
|
||||
|
||||
static void mgw_fsm_crcx_hnb_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
|
||||
struct hnbgw_context_map *map = mgw_fsm_priv->map;
|
||||
struct hnb_context *hnb_ctx = map->hnb_ctx;
|
||||
struct osmo_sockaddr addr;
|
||||
struct osmo_sockaddr_str addr_str;
|
||||
RANAP_RAB_AssignmentRequestIEs_t *ies;
|
||||
const char *epname;
|
||||
struct mgcp_conn_peer mgw_info;
|
||||
int rc;
|
||||
|
||||
LOGPFSML(fi, LOGL_DEBUG, "RAB-AssignmentRequest received, creating HNB side call-leg on MGW...\n");
|
||||
|
||||
/* Parse the RAB Assignment Request now */
|
||||
ies = &mgw_fsm_priv->ranap_rab_ass_req_message->msg.raB_AssignmentRequestIEs;
|
||||
rc = ranap_rab_ass_req_ies_extract_inet_addr(&addr, &mgw_fsm_priv->rab_id, ies, 0);
|
||||
if (rc < 0) {
|
||||
LOGPFSML(fi, LOGL_ERROR, "Invalid RAB-AssignmentRequest -- abort\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
rc = osmo_sockaddr_str_from_sockaddr(&addr_str, &addr.u.sas);
|
||||
if (rc < 0) {
|
||||
LOGPFSML(fi, LOGL_ERROR,
|
||||
"Invalid RTP IP-address or port in RAB-AssignmentRequest -- abort\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
osmo_strlcpy(mgw_fsm_priv->msc_rtp_addr, addr_str.ip, sizeof(mgw_fsm_priv->msc_rtp_addr));
|
||||
mgw_fsm_priv->msc_rtp_port = addr_str.port;
|
||||
|
||||
mgw_info = (struct mgcp_conn_peer) {
|
||||
.call_id = (map->rua_ctx_id << 8) | mgw_fsm_priv->rab_id,
|
||||
.ptime = 20,
|
||||
.conn_mode = MGCP_CONN_RECV_ONLY,
|
||||
};
|
||||
mgw_info.codecs[0] = CODEC_IUFP;
|
||||
mgw_info.codecs_len = 1;
|
||||
|
||||
/* The HNB IuUP IP address & port is not yet known here (Rx RAB-Ass-Req Tx CRCX (RAN) time):
|
||||
* Assume and announce "remote IuUP IP address" == "remote Iuh signalling IP address" to the MGW
|
||||
* here, so that it can most probably select a proper IuUP local IP address to be used from the
|
||||
* start. In the event we receive a "remote IuUP IP address" != "remote Iuh signalling IP address"
|
||||
* later on during RAB-Ass-Resp, we'll update IP addr at the MGW through MDCX and if MGW decides
|
||||
* to use another IuUP local IP address as a result, it will be updated at the HNB through
|
||||
* RAB-Modify-Req. */
|
||||
if (hnb_ctx && hnb_ctx->conn &&
|
||||
(rc = osmo_stream_srv_get_fd(hnb_ctx->conn)) >= 0) {
|
||||
if (osmo_sock_get_remote_ip(rc, mgw_info.addr, sizeof(mgw_info.addr)) < 0)
|
||||
LOGPFSML(fi, LOGL_ERROR, "Invalid Iuh IP Address\n");
|
||||
}
|
||||
|
||||
mgw_fsm_priv->mgcpc = mgcp_client_pool_get(g_hnbgw->mgw_pool);
|
||||
if (!mgw_fsm_priv->mgcpc) {
|
||||
LOGPFSML(fi, LOGL_ERROR,
|
||||
"cannot ensure MGW endpoint -- no MGW configured, check configuration!\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
epname = mgcp_client_rtpbridge_wildcard(mgw_fsm_priv->mgcpc);
|
||||
mgw_fsm_priv->mgcpc_ep =
|
||||
osmo_mgcpc_ep_alloc(fi, MGW_EV_MGCP_TERM, mgw_fsm_priv->mgcpc, mgw_fsm_T_defs, fi->id, "%s", epname);
|
||||
mgw_fsm_priv->mgcpc_ep_ci_hnb = osmo_mgcpc_ep_ci_add(mgw_fsm_priv->mgcpc_ep, "to-HNB");
|
||||
|
||||
osmo_mgcpc_ep_ci_request(mgw_fsm_priv->mgcpc_ep_ci_hnb, MGCP_VERB_CRCX, &mgw_info, fi, MGW_EV_MGCP_OK,
|
||||
MGW_EV_MGCP_FAIL, NULL);
|
||||
}
|
||||
|
||||
static void mgw_fsm_crcx_hnb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
|
||||
const struct mgcp_conn_peer *mgw_info;
|
||||
struct osmo_sockaddr_str addr_str;
|
||||
int rc;
|
||||
|
||||
switch (event) {
|
||||
case MGW_EV_MGCP_OK:
|
||||
mgw_info = osmo_mgcpc_ep_ci_get_rtp_info(mgw_fsm_priv->mgcpc_ep_ci_hnb);
|
||||
if (!mgw_info) {
|
||||
LOGPFSML(fi, LOGL_ERROR, "Got no RTP info response from MGW\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strchr(mgw_info->addr, '.'))
|
||||
addr_str.af = AF_INET;
|
||||
else
|
||||
addr_str.af = AF_INET6;
|
||||
addr_str.port = mgw_info->port;
|
||||
osmo_strlcpy(addr_str.ip, mgw_info->addr, sizeof(addr_str.ip));
|
||||
rc = osmo_sockaddr_str_to_sockaddr(&addr_str, &mgw_fsm_priv->ci_hnb_crcx_ack_addr.u.sas);
|
||||
if (rc < 0) {
|
||||
LOGPFSML(fi, LOGL_ERROR,
|
||||
"Failed to convert RTP IP-address (%s) and Port (%u) to its binary representation\n",
|
||||
mgw_info->addr, mgw_info->port);
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
mgw_fsm_state_chg(fi, MGW_ST_ASSIGN);
|
||||
return;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void mgw_fsm_assign_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
|
||||
struct hnbgw_context_map *map = mgw_fsm_priv->map;
|
||||
RANAP_RAB_AssignmentRequestIEs_t *ies;
|
||||
struct msgb *msg;
|
||||
int rc;
|
||||
|
||||
ies = &mgw_fsm_priv->ranap_rab_ass_req_message->msg.raB_AssignmentRequestIEs;
|
||||
rc = ranap_rab_ass_req_ies_replace_inet_addr(ies, &mgw_fsm_priv->ci_hnb_crcx_ack_addr, mgw_fsm_priv->rab_id);
|
||||
if (rc < 0) {
|
||||
LOGPFSML(fi, LOGL_ERROR, "Failed to replace RTP IP-address and Port in RAB-AssignmentRequest\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
msg = ranap_rab_ass_req_encode(ies);
|
||||
if (!msg) {
|
||||
LOGPFSML(fi, LOGL_ERROR, "failed to re-encode RAB-AssignmentRequest message\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGPFSML(fi, LOGL_DEBUG, "forwarding modified RAB-AssignmentRequest to HNB\n");
|
||||
msg->l2h = msg->data;
|
||||
talloc_steal(OTC_SELECT, msg);
|
||||
map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, msg);
|
||||
}
|
||||
|
||||
static void mgw_fsm_assign(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
|
||||
struct hnbgw_context_map *map = mgw_fsm_priv->map;
|
||||
RANAP_RAB_AssignmentResponseIEs_t *ies;
|
||||
bool rab_failed_at_hnb;
|
||||
struct osmo_sockaddr addr;
|
||||
enum mgw_fsm_state next_st;
|
||||
int rc;
|
||||
|
||||
switch (event) {
|
||||
case MGW_EV_RAB_ASS_RESP:
|
||||
LOGPFSML(fi, LOGL_DEBUG, "RAB-AssignmentResponse received, completing HNB side call-leg on MGW...\n");
|
||||
ies = &mgw_fsm_priv->ranap_rab_ass_resp_message->msg.raB_AssignmentResponseIEs;
|
||||
rc = ranap_rab_ass_resp_ies_extract_inet_addr(&addr, ies, mgw_fsm_priv->rab_id);
|
||||
if (rc < 0) {
|
||||
rab_failed_at_hnb = ranap_rab_ass_resp_ies_check_failure(ies, mgw_fsm_priv->rab_id);
|
||||
if (rab_failed_at_hnb) {
|
||||
struct msgb *msg;
|
||||
|
||||
LOGPFSML(fi, LOGL_ERROR,
|
||||
"The RAB-AssignmentResponse contains a RAB-FailedList, RAB-Assignment (%u) failed.\n",
|
||||
mgw_fsm_priv->rab_id);
|
||||
|
||||
/* Forward the RAB-AssignmentResponse transparently. This will ensure that the MSC
|
||||
* is informed about the problem. */
|
||||
LOGPFSML(fi, LOGL_DEBUG, "forwarding unmodified RAB-AssignmentResponse to MSC\n");
|
||||
|
||||
msg = mgw_fsm_priv->ranap_rab_ass_resp_msgb;
|
||||
mgw_fsm_priv->ranap_rab_ass_resp_msgb = NULL;
|
||||
talloc_steal(OTC_SELECT, msg);
|
||||
|
||||
rc = map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, msg);
|
||||
if (rc < 0) {
|
||||
LOGPFSML(fi, LOGL_DEBUG, "failed to forward RAB-AssignmentResponse message\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
}
|
||||
|
||||
/* Even though this is a failure situation, we still release normally as the error is located
|
||||
* at the HNB. */
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_RELEASE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* The RAB-ID we are dealing with is not on an FailedList and we were unable to parse the response
|
||||
* normally. This is a situation we cannot recover from. */
|
||||
LOGPFSML(fi, LOGL_ERROR, "Failed to extract RTP IP-address and Port from RAB-AssignmentResponse\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Break infinite loops modifications between HNB and our MGW: */
|
||||
if (mgw_fsm_priv->mdcx_tx_cnt > 3) {
|
||||
LOGPFSML(fi, LOGL_ERROR, "IuUP addr modification infinite loop detected between HNB and MGW, "
|
||||
"check your network routing config -- abort!\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_RELEASE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Send at least 1 MDCX in order to change conn_mode to SEND_RECV.
|
||||
* From there on, MDCX is only needed if HNB IP/Port changed: */
|
||||
if (mgw_fsm_priv->mdcx_tx_cnt == 0 ||
|
||||
osmo_sockaddr_cmp(&addr, &mgw_fsm_priv->hnb_rtp_addr) != 0) {
|
||||
next_st = MGW_ST_MDCX_HNB;
|
||||
} else {
|
||||
LOGPFSML(fi, LOGL_DEBUG, "RAB-AssignmentResponse received with unchanged IuUP attributes, skipping MDCX...\n");
|
||||
next_st = MGW_ST_CRCX_MSC;
|
||||
}
|
||||
mgw_fsm_priv->hnb_rtp_addr = addr;
|
||||
mgw_fsm_state_chg(fi, next_st);
|
||||
return;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void mgw_fsm_mdcx_hnb_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
|
||||
struct hnbgw_context_map *map = mgw_fsm_priv->map;
|
||||
struct mgcp_conn_peer mgw_info;
|
||||
struct osmo_sockaddr_str addr_str;
|
||||
int rc;
|
||||
|
||||
mgw_info = (struct mgcp_conn_peer) {
|
||||
.call_id = map->rua_ctx_id,
|
||||
.ptime = 20,
|
||||
.conn_mode = MGCP_CONN_RECV_SEND,
|
||||
};
|
||||
mgw_info.codecs[0] = CODEC_IUFP;
|
||||
mgw_info.codecs_len = 1;
|
||||
|
||||
rc = osmo_sockaddr_str_from_sockaddr(&addr_str, &mgw_fsm_priv->hnb_rtp_addr.u.sas);
|
||||
if (rc < 0) {
|
||||
LOGPFSML(fi, LOGL_ERROR, "Invalid RTP IP-address or Port in RAB-AssignmentResponse\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
osmo_strlcpy(mgw_info.addr, addr_str.ip, sizeof(mgw_info.addr));
|
||||
mgw_info.port = addr_str.port;
|
||||
|
||||
mgw_fsm_priv->mdcx_tx_cnt++;
|
||||
osmo_mgcpc_ep_ci_request(mgw_fsm_priv->mgcpc_ep_ci_hnb, MGCP_VERB_MDCX, &mgw_info, fi, MGW_EV_MGCP_OK,
|
||||
MGW_EV_MGCP_FAIL, NULL);
|
||||
}
|
||||
|
||||
static void mgw_fsm_mdcx_hnb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
|
||||
const struct mgcp_conn_peer *mgw_info;
|
||||
struct osmo_sockaddr_str addr_str;
|
||||
struct osmo_sockaddr addr;
|
||||
int rc;
|
||||
|
||||
switch (event) {
|
||||
case MGW_EV_MGCP_OK:
|
||||
mgw_info = osmo_mgcpc_ep_ci_get_rtp_info(mgw_fsm_priv->mgcpc_ep_ci_hnb);
|
||||
if (!mgw_info) {
|
||||
LOGPFSML(fi, LOGL_ERROR, "Got no RTP info response from MGW\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strchr(mgw_info->addr, '.'))
|
||||
addr_str.af = AF_INET;
|
||||
else
|
||||
addr_str.af = AF_INET6;
|
||||
addr_str.port = mgw_info->port;
|
||||
osmo_strlcpy(addr_str.ip, mgw_info->addr, sizeof(addr_str.ip));
|
||||
rc = osmo_sockaddr_str_to_sockaddr(&addr_str, &addr.u.sas);
|
||||
if (rc < 0) {
|
||||
LOGPFSML(fi, LOGL_ERROR,
|
||||
"Failed to convert RTP IP-address (%s) and Port (%u) to its binary representation\n",
|
||||
mgw_info->addr, mgw_info->port);
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (osmo_sockaddr_cmp(&mgw_fsm_priv->ci_hnb_crcx_ack_addr, &addr) != 0) {
|
||||
char addr_buf[INET6_ADDRSTRLEN + 8];
|
||||
LOGPFSML(fi, LOGL_INFO, "Local MGW IuUP IP address %s changed to %s during MDCX."
|
||||
" Modifying RAB on HNB.\n",
|
||||
osmo_sockaddr_to_str(&mgw_fsm_priv->ci_hnb_crcx_ack_addr),
|
||||
osmo_sockaddr_to_str_buf(addr_buf, sizeof(addr_buf), &addr));
|
||||
/* Modify RAB on the HNB with the new local IuUP address (OS#6127): */
|
||||
mgw_fsm_priv->ci_hnb_crcx_ack_addr = addr;
|
||||
mgw_fsm_state_chg(fi, MGW_ST_ASSIGN);
|
||||
return;
|
||||
}
|
||||
|
||||
mgw_fsm_state_chg(fi, MGW_ST_CRCX_MSC);
|
||||
return;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void mgw_fsm_crcx_msc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
|
||||
struct hnbgw_context_map *map = mgw_fsm_priv->map;
|
||||
struct mgcp_conn_peer mgw_info;
|
||||
|
||||
LOGPFSML(fi, LOGL_DEBUG, "creating MSC side call-leg on MGW...\n");
|
||||
|
||||
mgw_info = (struct mgcp_conn_peer) {
|
||||
.call_id = (map->rua_ctx_id << 8) | mgw_fsm_priv->rab_id,
|
||||
.ptime = 20,
|
||||
.port = mgw_fsm_priv->msc_rtp_port,
|
||||
};
|
||||
|
||||
osmo_strlcpy(mgw_info.addr, mgw_fsm_priv->msc_rtp_addr, sizeof(mgw_info.addr));
|
||||
mgw_info.codecs[0] = CODEC_IUFP;
|
||||
mgw_info.codecs_len = 1;
|
||||
|
||||
mgw_fsm_priv->mgcpc_ep_ci_msc = osmo_mgcpc_ep_ci_add(mgw_fsm_priv->mgcpc_ep, "to-MSC");
|
||||
osmo_mgcpc_ep_ci_request(mgw_fsm_priv->mgcpc_ep_ci_msc, MGCP_VERB_CRCX, &mgw_info, fi, MGW_EV_MGCP_OK,
|
||||
MGW_EV_MGCP_FAIL, NULL);
|
||||
}
|
||||
|
||||
static void mgw_fsm_crcx_msc(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
|
||||
const struct mgcp_conn_peer *mgw_info;
|
||||
struct osmo_sockaddr addr;
|
||||
struct osmo_sockaddr_str addr_str;
|
||||
int rc;
|
||||
int msg_max_len;
|
||||
RANAP_RAB_AssignmentResponseIEs_t *ies;
|
||||
|
||||
switch (event) {
|
||||
case MGW_EV_MGCP_OK:
|
||||
ies = &mgw_fsm_priv->ranap_rab_ass_resp_message->msg.raB_AssignmentResponseIEs;
|
||||
|
||||
mgw_info = osmo_mgcpc_ep_ci_get_rtp_info(mgw_fsm_priv->mgcpc_ep_ci_msc);
|
||||
if (!mgw_info) {
|
||||
LOGPFSML(fi, LOGL_ERROR, "Got no response from MGW\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Replace RTP IP-Address/Port in ranap message container */
|
||||
if (strchr(mgw_info->addr, '.'))
|
||||
addr_str.af = AF_INET;
|
||||
else
|
||||
addr_str.af = AF_INET6;
|
||||
addr_str.port = mgw_info->port;
|
||||
osmo_strlcpy(addr_str.ip, mgw_info->addr, sizeof(addr_str.ip));
|
||||
rc = osmo_sockaddr_str_to_sockaddr(&addr_str, &addr.u.sas);
|
||||
if (rc < 0) {
|
||||
LOGPFSML(fi, LOGL_ERROR,
|
||||
"Failed to convert RTP IP-address (%s) and Port (%u) to its binary representation\n",
|
||||
mgw_info->addr, mgw_info->port);
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
rc = ranap_rab_ass_resp_ies_replace_inet_addr(ies, &addr, mgw_fsm_priv->rab_id);
|
||||
if (rc < 0) {
|
||||
LOGPFSML(fi, LOGL_ERROR,
|
||||
"Failed to replace RTP IP-address (%s) and Port (%u) in RAB-AssignmentResponse\n",
|
||||
mgw_info->addr, mgw_info->port);
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* When the modified ranap message container is re-encoded, the resulting message might be larger then
|
||||
* the original message. Ensure that there is enough room in l2h to grow. (The current implementation
|
||||
* should yield a message with the same size, but there is no guarantee for that) */
|
||||
msg_max_len =
|
||||
msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_msgb) +
|
||||
msgb_tailroom(mgw_fsm_priv->ranap_rab_ass_resp_msgb);
|
||||
rc = msgb_resize_area(mgw_fsm_priv->ranap_rab_ass_resp_msgb,
|
||||
mgw_fsm_priv->ranap_rab_ass_resp_msgb->l2h,
|
||||
msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_msgb), msg_max_len);
|
||||
OSMO_ASSERT(rc == 0);
|
||||
|
||||
rc = ranap_rab_ass_resp_encode(msgb_l2(mgw_fsm_priv->ranap_rab_ass_resp_msgb),
|
||||
msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_msgb), ies);
|
||||
if (rc < 0) {
|
||||
LOGPFSML(fi, LOGL_ERROR, "failed to re-encode RAB-AssignmentResponse message\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Resize l2h back to the actual message length */
|
||||
rc = msgb_resize_area(mgw_fsm_priv->ranap_rab_ass_resp_msgb,
|
||||
mgw_fsm_priv->ranap_rab_ass_resp_msgb->l2h,
|
||||
msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_msgb), rc);
|
||||
OSMO_ASSERT(rc == 0);
|
||||
|
||||
/* When the established state is entered, the modified RAB AssignmentResponse is forwarded to the MSC.
|
||||
* The call is then established any way may stay for an indefinate amount of time in this state until
|
||||
* there is an IU Release happening. */
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_ESTABLISHED, 0, 0);
|
||||
return;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void mgw_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
|
||||
struct hnbgw_context_map *map = mgw_fsm_priv->map;
|
||||
struct msgb *ranap_msg;
|
||||
int rc;
|
||||
|
||||
LOGPFSML(fi, LOGL_DEBUG, "forwarding modified RAB-AssignmentResponse to MSC\n");
|
||||
|
||||
ranap_msg = mgw_fsm_priv->ranap_rab_ass_resp_msgb;
|
||||
mgw_fsm_priv->ranap_rab_ass_resp_msgb = NULL;
|
||||
talloc_steal(OTC_SELECT, ranap_msg);
|
||||
|
||||
rc = map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg);
|
||||
if (rc < 0) {
|
||||
LOGPFSML(fi, LOGL_DEBUG, "failed to forward RAB-AssignmentResponse message\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
}
|
||||
|
||||
LOGPFSML(fi, LOGL_DEBUG, "HNB and MSC side call-legs completed!\n");
|
||||
|
||||
osmo_clock_gettime(CLOCK_MONOTONIC, &mgw_fsm_priv->active_start);
|
||||
mgw_fsm_priv->active_stored = mgw_fsm_priv->active_start;
|
||||
}
|
||||
|
||||
static void mgw_fsm_release_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
}
|
||||
|
||||
static void mgw_fsm_failure_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
|
||||
tx_release_req(mgw_fsm_priv->map);
|
||||
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
|
||||
}
|
||||
|
||||
static void mgw_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
case MGW_EV_MGCP_TERM:
|
||||
/* Put MGCP client back into MGW pool */
|
||||
if (mgw_fsm_priv->mgcpc) {
|
||||
mgcp_client_pool_put(mgw_fsm_priv->mgcpc);
|
||||
mgw_fsm_priv->mgcpc = NULL;
|
||||
}
|
||||
mgw_fsm_priv->mgcpc_ep = NULL;
|
||||
LOGPFSML(fi, LOGL_ERROR, "Media gateway failed\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
case MGW_EV_MGCP_FAIL:
|
||||
LOGPFSML(fi, LOGL_ERROR, "Media gateway failed to switch RTP streams\n");
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0);
|
||||
return;
|
||||
case MGW_EV_RELEASE:
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_RELEASE, 0, 0);
|
||||
return;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static int mgw_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mgw_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
|
||||
talloc_free(mgw_fsm_priv);
|
||||
}
|
||||
|
||||
static void mgw_fsm_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
|
||||
struct hnbgw_context_map *map = mgw_fsm_priv->map;
|
||||
|
||||
if (mgw_fsm_priv->mgcpc_ep) {
|
||||
/* Put MGCP client back into MGW pool */
|
||||
struct mgcp_client *mgcp_client = osmo_mgcpc_ep_client(mgw_fsm_priv->mgcpc_ep);
|
||||
mgcp_client_pool_put(mgcp_client);
|
||||
|
||||
osmo_mgcpc_ep_clear(mgw_fsm_priv->mgcpc_ep);
|
||||
mgw_fsm_priv->mgcpc_ep = NULL;
|
||||
}
|
||||
|
||||
/* Remove FSM from the context map. This will make this FSM unreachable for events coming from outside */
|
||||
map->mgw_fi = NULL;
|
||||
}
|
||||
|
||||
static const struct osmo_fsm_state mgw_fsm_states[] = {
|
||||
[MGW_ST_CRCX_HNB] = {
|
||||
.name = "MGW_ST_CRCX_HNB",
|
||||
.onenter = mgw_fsm_crcx_hnb_onenter,
|
||||
.action = mgw_fsm_crcx_hnb,
|
||||
.in_event_mask =
|
||||
S(MGW_EV_MGCP_OK),
|
||||
.out_state_mask =
|
||||
S(MGW_ST_ASSIGN) |
|
||||
S(MGW_ST_FAILURE) |
|
||||
S(MGW_ST_RELEASE) |
|
||||
S(MGW_ST_CRCX_HNB),
|
||||
},
|
||||
[MGW_ST_ASSIGN] = {
|
||||
.name = "MGW_ST_ASSIGN",
|
||||
.onenter = mgw_fsm_assign_onenter,
|
||||
.action = mgw_fsm_assign,
|
||||
.in_event_mask = S(MGW_EV_RAB_ASS_RESP),
|
||||
.out_state_mask =
|
||||
S(MGW_ST_MDCX_HNB) |
|
||||
S(MGW_ST_CRCX_MSC) |
|
||||
S(MGW_ST_FAILURE) |
|
||||
S(MGW_ST_RELEASE),
|
||||
},
|
||||
[MGW_ST_MDCX_HNB] = {
|
||||
.name = "MGW_ST_MDCX_HNB",
|
||||
.onenter = mgw_fsm_mdcx_hnb_onenter,
|
||||
.action = mgw_fsm_mdcx_hnb,
|
||||
.in_event_mask =
|
||||
S(MGW_EV_MGCP_OK),
|
||||
.out_state_mask =
|
||||
S(MGW_ST_ASSIGN) |
|
||||
S(MGW_ST_CRCX_MSC) |
|
||||
S(MGW_ST_FAILURE) |
|
||||
S(MGW_ST_RELEASE),
|
||||
},
|
||||
[MGW_ST_CRCX_MSC] = {
|
||||
.name = "MGW_ST_CRCX_MSC",
|
||||
.onenter = mgw_fsm_crcx_msc_onenter,
|
||||
.action = mgw_fsm_crcx_msc,
|
||||
.in_event_mask =
|
||||
S(MGW_EV_MGCP_OK),
|
||||
.out_state_mask =
|
||||
S(MGW_ST_ESTABLISHED) |
|
||||
S(MGW_ST_FAILURE) |
|
||||
S(MGW_ST_RELEASE),
|
||||
},
|
||||
[MGW_ST_ESTABLISHED] = {
|
||||
.name = "MGW_ST_ESTABLISHED",
|
||||
.onenter = mgw_fsm_established_onenter,
|
||||
.in_event_mask = 0,
|
||||
.out_state_mask =
|
||||
S(MGW_ST_FAILURE) |
|
||||
S(MGW_ST_RELEASE),
|
||||
},
|
||||
[MGW_ST_RELEASE] = {
|
||||
.name = "MGW_ST_RELEASE",
|
||||
.onenter = mgw_fsm_release_onenter,
|
||||
.in_event_mask = 0,
|
||||
.out_state_mask = 0,
|
||||
},
|
||||
[MGW_ST_FAILURE] = {
|
||||
.name = "MGW_ST_FAILURE",
|
||||
.onenter = mgw_fsm_failure_onenter,
|
||||
.in_event_mask = 0,
|
||||
.out_state_mask = 0,
|
||||
},
|
||||
};
|
||||
|
||||
static struct osmo_fsm mgw_fsm = {
|
||||
.name = "mgw",
|
||||
.states = mgw_fsm_states,
|
||||
.num_states = ARRAY_SIZE(mgw_fsm_states),
|
||||
.log_subsys = DMGW,
|
||||
.event_names = mgw_fsm_event_names,
|
||||
.allstate_action = mgw_fsm_allstate_action,
|
||||
.allstate_event_mask = S(MGW_EV_MGCP_TERM) | S(MGW_EV_RELEASE) | S(MGW_EV_MGCP_FAIL),
|
||||
.timer_cb = mgw_fsm_timer_cb,
|
||||
.cleanup = mgw_fsm_cleanup,
|
||||
.pre_term = mgw_fsm_pre_term,
|
||||
};
|
||||
|
||||
/* The MSC may ask to release a specific RAB within a RAB-AssignmentRequest */
|
||||
static int release_mgw_fsm(struct hnbgw_context_map *map, struct msgb *ranap_msg)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = map->mgw_fi;
|
||||
int rc;
|
||||
|
||||
/* Forward the unmodifed RAB-AssignmentRequest to HNB, so that the HNB is informed about the RAB release as
|
||||
* well */
|
||||
LOGPFSML(fi, LOGL_DEBUG, "forwarding unmodified RAB-AssignmentRequest to HNB\n");
|
||||
rc = map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, ranap_msg);
|
||||
if (rc < 0) {
|
||||
LOGPFSML(fi, LOGL_DEBUG, "cannot forward RAB-AssignmentRequest to HNB\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Release the FSM normally */
|
||||
osmo_fsm_inst_state_chg(fi, MGW_ST_RELEASE, 0, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool is_rab_ass_without_tli(struct hnbgw_context_map *map, ranap_message *message)
|
||||
{
|
||||
RANAP_RAB_AssignmentRequestIEs_t *ies;
|
||||
RANAP_ProtocolIE_ContainerPair_t *protocol_ie_container_pair;
|
||||
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
|
||||
RANAP_RAB_SetupOrModifyItemFirst_t _rab_setup_or_modify_item_first = { 0 };
|
||||
RANAP_RAB_SetupOrModifyItemFirst_t *rab_setup_or_modify_item_first = &_rab_setup_or_modify_item_first;
|
||||
int rc;
|
||||
bool ret;
|
||||
|
||||
ies = &message->msg.raB_AssignmentRequestIEs;
|
||||
|
||||
if (!(ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT))
|
||||
return false;
|
||||
|
||||
/* Detect the end of the list */
|
||||
if (ies->raB_SetupOrModifyList.list.count < 1)
|
||||
return false;
|
||||
|
||||
protocol_ie_container_pair = ies->raB_SetupOrModifyList.list.array[0];
|
||||
protocol_ie_field_pair = protocol_ie_container_pair->list.array[0];
|
||||
|
||||
if (!protocol_ie_field_pair)
|
||||
return false;
|
||||
|
||||
if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem) {
|
||||
RANAP_DEBUG
|
||||
("Decoding failed, the protocol IE field-pair is not of type RANAP RAB setup-or-modify-item!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = ranap_decode_rab_setupormodifyitemfirst(rab_setup_or_modify_item_first,
|
||||
&protocol_ie_field_pair->firstValue);
|
||||
if (rc < 0)
|
||||
return false;
|
||||
|
||||
ret = rab_setup_or_modify_item_first->transportLayerInformation == NULL;
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, rab_setup_or_modify_item_first);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Check if the message contains a RAB-ReleaseItem that matches the RAB-ID that is managed by the given context map */
|
||||
static bool is_our_rab_release(struct hnbgw_context_map *map, ranap_message *message)
|
||||
{
|
||||
bool rab_release_req;
|
||||
struct osmo_fsm_inst *fi = map->mgw_fi;
|
||||
struct mgw_fsm_priv *mgw_fsm_priv = fi->priv;
|
||||
|
||||
/* Check if the RAB that is handled by this FSM is addressed by the release request */
|
||||
rab_release_req = ranap_rab_ass_req_ies_check_release(&message->msg.raB_AssignmentRequestIEs,
|
||||
mgw_fsm_priv->rab_id);
|
||||
if (!rab_release_req) {
|
||||
LOGPFSML(map->mgw_fi, LOGL_ERROR, "RAB-AssignmentRequest does not contain any RAB-RelaseItem with RAB-ID %u\n", mgw_fsm_priv->rab_id);
|
||||
return false;
|
||||
}
|
||||
LOGPFSML(map->mgw_fi, LOGL_NOTICE, "MSC asked to release RAB-ID %u\n", mgw_fsm_priv->rab_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*! Allocate MGW FSM and handle RANAP RAB AssignmentRequest.
|
||||
* \param[in] map hnbgw context map that is responsible for this call.
|
||||
* \param[in] ranap_msg msgb containing RANAP RAB AssignmentRequest at msgb_l2(), allocated in OTC_SELECT.
|
||||
* This function may talloc_steal(ranap_msg) to keep it for later.
|
||||
* \param[in] message decoded RANAP message container, allocated in OTC_SELECT.
|
||||
* This function may talloc_steal(message) to keep it for later.
|
||||
* \returns 0 on success; negative on error. */
|
||||
int handle_cs_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message)
|
||||
{
|
||||
static bool initialized = false;
|
||||
struct mgw_fsm_priv *mgw_fsm_priv;
|
||||
char fsm_name[255];
|
||||
|
||||
OSMO_ASSERT(!map->is_ps);
|
||||
|
||||
/* Initialize FSM if not done yet */
|
||||
if (!initialized) {
|
||||
OSMO_ASSERT(osmo_fsm_register(&mgw_fsm) == 0);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/* There may be CS RAB Assignment Requests without actual RTP information: after a normal RAB Assignment,
|
||||
* another RAB Assignment may follow, modifying some RAB parameters. When there is no RTP info in the message,
|
||||
* there is no RTP to redirect via MGW, hence just forward as-is. */
|
||||
if (is_rab_ass_without_tli(map, message)) {
|
||||
LOG_MAP(map, DCN, LOGL_INFO,
|
||||
"Rx RAB Assignment Request without transportLayerInformation, forwarding as-is\n");
|
||||
return map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, ranap_msg);
|
||||
}
|
||||
|
||||
/* The RTP stream negotiation usually begins with a RAB-AssignmentRequest and ends with an IU-Release, however
|
||||
* it may also be that the MSC decides to release the RAB with a dedicated RAB-AssignmentRequest that contains
|
||||
* a ReleaseList. In this case an FSM will already be present. */
|
||||
if (map->mgw_fi) {
|
||||
/* Check if the RAB-AssignmentRequest contains a RAB-ReleaseItem that matches the RAB-ID we are
|
||||
* managing in this HNBGW context map. */
|
||||
if (is_our_rab_release(map, message))
|
||||
return release_mgw_fsm(map, ranap_msg);
|
||||
|
||||
/* The RAB-ReleaseItem in the incoming message should match the RAB ID we are managing. A mismatch may
|
||||
* mean that there is an inconsistency between the HNBGW and the MSC state and the MGW FSM on the HNBGW
|
||||
* side may serve an abandonned connection, which we will now close. However we must also assume that
|
||||
* the incoming message may still contain a RAB-Assignment for a new RTP stream, so we still must
|
||||
* continue with the message evaluation. */
|
||||
osmo_fsm_inst_state_chg(map->mgw_fi, MGW_ST_FAILURE, 0, 0);
|
||||
OSMO_ASSERT(map->mgw_fi == NULL);
|
||||
}
|
||||
|
||||
/* This FSM only supports RAB assignments with a single RAB assignment only. This limitation has been taken
|
||||
* into account under the assumption that voice calls typically require a single RAB only. Nevertheless, we
|
||||
* will block all incoming RAB assignments that try to assign more (or less) than one RAB. */
|
||||
if (ranap_rab_ass_req_ies_get_count(&message->msg.raB_AssignmentRequestIEs) != 1) {
|
||||
LOG_MAP(map, DMGW, LOGL_ERROR,
|
||||
"%s() RAB-AssignmentRequest with more than one RAB assignment -- abort!\n",
|
||||
__func__);
|
||||
tx_release_req(map);
|
||||
return -1;
|
||||
}
|
||||
|
||||
mgw_fsm_priv = talloc_zero(map, struct mgw_fsm_priv);
|
||||
mgw_fsm_priv->map = map;
|
||||
|
||||
talloc_steal(mgw_fsm_priv, message);
|
||||
mgw_fsm_priv->ranap_rab_ass_req_message = message;
|
||||
|
||||
/* Allocate FSM */
|
||||
snprintf(fsm_name, sizeof(fsm_name), "mgw-fsm-%u-%u", map->rua_ctx_id, mgw_fsm_priv->rab_id);
|
||||
map->mgw_fi = osmo_fsm_inst_alloc(&mgw_fsm, map, mgw_fsm_priv, LOGL_DEBUG, fsm_name);
|
||||
|
||||
/* Start the FSM */
|
||||
mgw_fsm_state_chg(map->mgw_fi, MGW_ST_CRCX_HNB);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Handlie RANAP RAB AssignmentResponse (deliver message, complete RTP stream switching).
|
||||
* \param[in] map hnbgw context map that is responsible for this call.
|
||||
* \param[in] ranap_msg msgb containing RANAP RAB AssignmentResponse at msgb_l2(), allocated in OTC_SELECT.
|
||||
* This function may talloc_steal(ranap_msg) to keep it for later.
|
||||
* \param[in] message decoded RANAP message container, allocated in OTC_SELECT.
|
||||
* This function may talloc_steal(message) to keep it for later.
|
||||
* \returns 0 on success; negative on error. */
|
||||
int mgw_fsm_handle_cs_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv;
|
||||
|
||||
OSMO_ASSERT(ranap_msg);
|
||||
OSMO_ASSERT(!map->is_ps);
|
||||
|
||||
if (!map->mgw_fi) {
|
||||
/* NOTE: This situation is a corner-case. We may end up here when the co-located MGW caused a problem
|
||||
* on the way between RANAP RAB Assignment Request and RANAP RAB Assignment Response. */
|
||||
|
||||
LOG_MAP(map, DMGW, LOGL_ERROR,
|
||||
"%s() no MGW fsm -- sending Iu-Release-Request!\n",
|
||||
__func__);
|
||||
|
||||
/* Send a release request, to make sure that the MSC is aware of the problem. */
|
||||
tx_release_req(map);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (map->mgw_fi->state == MGW_ST_ESTABLISHED) {
|
||||
/* This is a response to a second RAB Assignment Request, which only modified some RAB config on top of
|
||||
* an earlier RAB Assignment. Just forward the response as-is, we already have our MGW set up and need
|
||||
* no info from it. (i.e. we don't support modifying the RTP address.) */
|
||||
return map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg);
|
||||
}
|
||||
|
||||
mgw_fsm_priv = map->mgw_fi->priv;
|
||||
|
||||
talloc_steal(mgw_fsm_priv, ranap_msg);
|
||||
mgw_fsm_priv->ranap_rab_ass_resp_msgb = ranap_msg;
|
||||
|
||||
talloc_steal(mgw_fsm_priv, message);
|
||||
mgw_fsm_priv->ranap_rab_ass_resp_message = message;
|
||||
|
||||
osmo_fsm_inst_dispatch(map->mgw_fi, MGW_EV_RAB_ASS_RESP, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Release the FSM and clear its associated RTP streams.
|
||||
* \ptmap[in] map hnbgw context map that is responsible for this call.
|
||||
* \returns 0 on success; negative on error. */
|
||||
int mgw_fsm_release(struct hnbgw_context_map *map)
|
||||
{
|
||||
OSMO_ASSERT(!map->is_ps);
|
||||
|
||||
if (!map->mgw_fi)
|
||||
return -EINVAL;
|
||||
|
||||
osmo_fsm_inst_dispatch(map->mgw_fi, MGW_EV_RELEASE, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* determine the number of elapsed active RAB milli-seconds since last call */
|
||||
uint64_t mgw_fsm_get_elapsed_ms(struct hnbgw_context_map *map, const struct timespec *now)
|
||||
{
|
||||
struct mgw_fsm_priv *mgw_fsm_priv;
|
||||
struct timespec elapsed;
|
||||
uint64_t elapsed_ms;
|
||||
|
||||
OSMO_ASSERT(!map->is_ps);
|
||||
|
||||
if (!map->mgw_fi)
|
||||
return 0;
|
||||
|
||||
OSMO_ASSERT(map->mgw_fi->fsm == &mgw_fsm);
|
||||
mgw_fsm_priv = map->mgw_fi->priv;
|
||||
|
||||
/* Ignore RABs whose activation timestamps are not yet set. */
|
||||
if (!timespecisset(&mgw_fsm_priv->active_stored))
|
||||
return 0;
|
||||
|
||||
/* Calculate elapsed time since last storage */
|
||||
timespecsub(now, &mgw_fsm_priv->active_stored, &elapsed);
|
||||
elapsed_ms = elapsed.tv_sec * 1000 + elapsed.tv_nsec / 1000000;
|
||||
|
||||
/* Update storage time */
|
||||
mgw_fsm_priv->active_stored = *now;
|
||||
|
||||
return elapsed_ms;
|
||||
}
|
||||
1046
src/osmo-hnbgw/nft_kpi.c
Normal file
1046
src/osmo-hnbgw/nft_kpi.c
Normal file
File diff suppressed because it is too large
Load Diff
377
src/osmo-hnbgw/osmo_hnbgw_main.c
Normal file
377
src/osmo-hnbgw/osmo_hnbgw_main.c
Normal file
@@ -0,0 +1,377 @@
|
||||
/* OsmoHNBGW main routine */
|
||||
|
||||
/* (C) 2015-2024 by Harald Welte <laforge@gnumonks.org>
|
||||
* (C) 2016-2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <osmocom/core/application.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/stats.h>
|
||||
|
||||
#include <osmocom/vty/vty.h>
|
||||
#include <osmocom/vty/command.h>
|
||||
#include <osmocom/vty/logging.h>
|
||||
#include <osmocom/vty/misc.h>
|
||||
#include <osmocom/vty/telnet_interface.h>
|
||||
#include <osmocom/vty/ports.h>
|
||||
#include <osmocom/vty/stats.h>
|
||||
|
||||
#include <osmocom/ctrl/control_vty.h>
|
||||
#include <osmocom/ctrl/ports.h>
|
||||
|
||||
#include <osmocom/netif/stream.h>
|
||||
|
||||
#include <osmocom/sigtran/protocol/m3ua.h>
|
||||
|
||||
#include <osmocom/ranap/ranap_common.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnb.h>
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
#include <osmocom/hnbgw/hnbgw_pfcp.h>
|
||||
|
||||
static struct {
|
||||
int daemonize;
|
||||
const char *config_file;
|
||||
bool log_disable_color;
|
||||
bool log_enable_timestamp;
|
||||
int log_level;
|
||||
const char *log_category_mask;
|
||||
} hnbgw_cmdline_config = {
|
||||
0,
|
||||
"osmo-hnbgw.cfg",
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void signal_handler(int signum)
|
||||
{
|
||||
fprintf(stdout, "signal %u received\n", signum);
|
||||
|
||||
switch (signum) {
|
||||
case SIGABRT:
|
||||
/* in case of abort, we want to obtain a talloc report and
|
||||
* then run default SIGABRT handler, who will generate coredump
|
||||
* and abort the process. abort() should do this for us after we
|
||||
* return, but program wouldn't exit if an external SIGABRT is
|
||||
* received.
|
||||
*/
|
||||
talloc_report(tall_vty_ctx, stderr);
|
||||
talloc_report_full(g_hnbgw, stderr);
|
||||
signal(SIGABRT, SIG_DFL);
|
||||
raise(SIGABRT);
|
||||
break;
|
||||
case SIGUSR1:
|
||||
talloc_report(tall_vty_ctx, stderr);
|
||||
talloc_report_full(g_hnbgw, stderr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void print_usage(void)
|
||||
{
|
||||
printf("Usage: osmo-hnbgw\n");
|
||||
}
|
||||
|
||||
static void print_help(void)
|
||||
{
|
||||
printf(" -h --help This text.\n");
|
||||
printf(" -d option --debug=DHNBAP:DRUA:DRANAP:DMAIN Enable debugging.\n");
|
||||
printf(" -D --daemonize Fork the process into a background daemon.\n");
|
||||
printf(" -c --config-file filename The config file to use.\n");
|
||||
printf(" -s --disable-color\n");
|
||||
printf(" -T --timestamp Prefix every log line with a timestamp.\n");
|
||||
printf(" -V --version Print the version of OsmoHNBGW.\n");
|
||||
printf(" -e --log-level number Set a global loglevel.\n");
|
||||
|
||||
printf("\nVTY reference generation:\n");
|
||||
printf(" --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n");
|
||||
printf(" --vty-ref-xml Generate the VTY reference XML output and exit.\n");
|
||||
}
|
||||
|
||||
static void handle_long_options(const char *prog_name, const int long_option)
|
||||
{
|
||||
static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT;
|
||||
|
||||
switch (long_option) {
|
||||
case 1:
|
||||
vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg);
|
||||
if (vty_ref_mode < 0) {
|
||||
fprintf(stderr, "%s: Unknown VTY reference generation "
|
||||
"mode '%s'\n", prog_name, optarg);
|
||||
exit(2);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
|
||||
get_value_string(vty_ref_gen_mode_names, vty_ref_mode),
|
||||
get_value_string(vty_ref_gen_mode_desc, vty_ref_mode));
|
||||
vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode);
|
||||
exit(0);
|
||||
default:
|
||||
fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_options(int argc, char **argv)
|
||||
{
|
||||
while (1) {
|
||||
int option_index = 0, c;
|
||||
static int long_option = 0;
|
||||
static struct option long_options[] = {
|
||||
{"help", 0, 0, 'h'},
|
||||
{"debug", 1, 0, 'd'},
|
||||
{"daemonize", 0, 0, 'D'},
|
||||
{"config-file", 1, 0, 'c'},
|
||||
{"disable-color", 0, 0, 's'},
|
||||
{"timestamp", 0, 0, 'T'},
|
||||
{"version", 0, 0, 'V' },
|
||||
{"log-level", 1, 0, 'e'},
|
||||
{"vty-ref-mode", 1, &long_option, 1},
|
||||
{"vty-ref-xml", 0, &long_option, 2},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "hd:Dc:sTVe:",
|
||||
long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
switch (c) {
|
||||
case 0:
|
||||
handle_long_options(argv[0], long_option);
|
||||
break;
|
||||
case 'h':
|
||||
print_usage();
|
||||
print_help();
|
||||
exit(0);
|
||||
case 's':
|
||||
hnbgw_cmdline_config.log_disable_color = true;
|
||||
break;
|
||||
case 'd':
|
||||
hnbgw_cmdline_config.log_category_mask = optarg;
|
||||
break;
|
||||
case 'D':
|
||||
hnbgw_cmdline_config.daemonize = 1;
|
||||
break;
|
||||
case 'c':
|
||||
hnbgw_cmdline_config.config_file = optarg;
|
||||
break;
|
||||
case 'T':
|
||||
hnbgw_cmdline_config.log_enable_timestamp = true;
|
||||
break;
|
||||
case 'e':
|
||||
hnbgw_cmdline_config.log_level = atoi(optarg);
|
||||
break;
|
||||
case 'V':
|
||||
print_version(1);
|
||||
exit(0);
|
||||
break;
|
||||
default:
|
||||
/* catch unknown options *as well as* missing arguments. */
|
||||
fprintf(stderr, "Error in command line options. Exiting.\n");
|
||||
exit(-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (argc > optind) {
|
||||
fprintf(stderr, "Unsupported positional arguments on command line\n");
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct osmo_stream_srv_link *srv;
|
||||
int rc;
|
||||
|
||||
talloc_enable_null_tracking();
|
||||
|
||||
/* g_hnbgw serves as the root talloc ctx, so allocate with NULL parent */
|
||||
g_hnbgw_alloc(NULL);
|
||||
g_hnbgw->config.rnc_id = 23;
|
||||
|
||||
talloc_asn1_ctx = talloc_named_const(g_hnbgw, 1, "asn1_context");
|
||||
msgb_talloc_ctx_init(g_hnbgw, 0);
|
||||
|
||||
rc = osmo_init_logging2(g_hnbgw, &hnbgw_log_info);
|
||||
if (rc < 0)
|
||||
exit(1);
|
||||
log_enable_multithread();
|
||||
|
||||
osmo_stats_init(g_hnbgw);
|
||||
rc = rate_ctr_init(g_hnbgw);
|
||||
if (rc) {
|
||||
LOGP(DMAIN, LOGL_FATAL, "rate_ctr_init() failed with rc=%d\n", rc);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
osmo_fsm_log_timeouts(true);
|
||||
|
||||
rc = osmo_ss7_init();
|
||||
if (rc < 0) {
|
||||
LOGP(DMAIN, LOGL_FATAL, "osmo_ss7_init() failed with rc=%d\n", rc);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
hnbgw_vty_info.tall_ctx = g_hnbgw;
|
||||
vty_init(&hnbgw_vty_info);
|
||||
|
||||
osmo_ss7_vty_init_asp(g_hnbgw);
|
||||
osmo_sccp_vty_init();
|
||||
hnbgw_vty_init();
|
||||
ctrl_vty_init(g_hnbgw);
|
||||
logging_vty_add_cmds();
|
||||
osmo_talloc_vty_add_cmds();
|
||||
osmo_stats_vty_add_cmds();
|
||||
|
||||
/* Handle options after vty_init(), for --version */
|
||||
handle_options(argc, argv);
|
||||
|
||||
rc = vty_read_config_file(hnbgw_cmdline_config.config_file, NULL);
|
||||
if (rc < 0) {
|
||||
LOGP(DMAIN, LOGL_FATAL, "Failed to parse the config file: '%s'\n",
|
||||
hnbgw_cmdline_config.config_file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* cmdline options take precedence over config file, but if no options
|
||||
* were passed we must not override the config file.
|
||||
*/
|
||||
if (hnbgw_cmdline_config.log_disable_color)
|
||||
log_set_use_color(osmo_stderr_target, 0);
|
||||
if (hnbgw_cmdline_config.log_category_mask)
|
||||
log_parse_category_mask(osmo_stderr_target,
|
||||
hnbgw_cmdline_config.log_category_mask);
|
||||
if (hnbgw_cmdline_config.log_enable_timestamp)
|
||||
log_set_print_timestamp(osmo_stderr_target, 1);
|
||||
if (hnbgw_cmdline_config.log_level)
|
||||
log_set_log_level(osmo_stderr_target,
|
||||
hnbgw_cmdline_config.log_level);
|
||||
|
||||
rc = telnet_init_default(g_hnbgw, g_hnbgw, OSMO_VTY_PORT_HNBGW);
|
||||
if (rc < 0) {
|
||||
perror("Error binding VTY port");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
g_hnbgw->ctrl = ctrl_interface_setup2(g_hnbgw, OSMO_CTRL_PORT_HNBGW, hnb_ctrl_node_lookup,
|
||||
_LAST_CTRL_NODE_HNB);
|
||||
if (!g_hnbgw->ctrl) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "Failed to create CTRL interface on %s:%u\n",
|
||||
ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_HNBGW);
|
||||
exit(1);
|
||||
} else {
|
||||
rc = hnb_ctrl_cmds_install();
|
||||
if (rc) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "Failed to install CTRL interface commands\n");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
ranap_set_log_area(DRANAP);
|
||||
|
||||
LOGP(DHNBAP, LOGL_NOTICE, "Using RNC-Id %u\n", g_hnbgw->config.rnc_id);
|
||||
|
||||
OSMO_ASSERT(g_hnbgw->config.iuh_local_ip);
|
||||
LOGP(DMAIN, LOGL_NOTICE, "Listening for Iuh at %s %d\n",
|
||||
g_hnbgw->config.iuh_local_ip,
|
||||
g_hnbgw->config.iuh_local_port);
|
||||
srv = osmo_stream_srv_link_create(g_hnbgw);
|
||||
if (!srv) {
|
||||
perror("cannot create server");
|
||||
exit(1);
|
||||
}
|
||||
osmo_stream_srv_link_set_data(srv, g_hnbgw);
|
||||
osmo_stream_srv_link_set_proto(srv, IPPROTO_SCTP);
|
||||
osmo_stream_srv_link_set_nodelay(srv, true);
|
||||
osmo_stream_srv_link_set_addr(srv, g_hnbgw->config.iuh_local_ip);
|
||||
osmo_stream_srv_link_set_port(srv, g_hnbgw->config.iuh_local_port);
|
||||
osmo_stream_srv_link_set_accept_cb(srv, hnbgw_rua_accept_cb);
|
||||
|
||||
if (osmo_stream_srv_link_open(srv) < 0) {
|
||||
perror("Cannot open server");
|
||||
exit(1);
|
||||
}
|
||||
g_hnbgw->iuh = srv;
|
||||
|
||||
/* Initialize and connect MGCP client. */
|
||||
if (hnbgw_mgw_setup() != 0)
|
||||
return -EINVAL;
|
||||
|
||||
#if ENABLE_PFCP
|
||||
/* If UPF is configured, set up PFCP socket and send Association Setup Request to UPF */
|
||||
hnbgw_pfcp_init();
|
||||
#endif
|
||||
|
||||
/* If nftables is enabled, initialize the nft table now or fail startup. This is important to immediately let
|
||||
* the user know if cap_net_admin privileges are missing, and not only when the first hNodeB connects. */
|
||||
if (g_hnbgw->config.nft_kpi.enable) {
|
||||
#if ENABLE_NFTABLES
|
||||
nft_kpi_init(g_hnbgw->config.nft_kpi.table_name);
|
||||
/* There is no direct error handling here, because nftables initialization happens asynchronously.
|
||||
* See nft_kpi.c nft_thread_t2m_cb(), case NFT_THREAD_INIT_TABLE to see what happens when initializing
|
||||
* nftables failed. */
|
||||
#else
|
||||
fprintf(stderr, "ERROR: Cannot enable nft KPI, this binary was built without nftables support\n");
|
||||
exit(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
hnbgw_cnpool_start(g_hnbgw->sccp.cnpool_iucs);
|
||||
hnbgw_cnpool_start(g_hnbgw->sccp.cnpool_iups);
|
||||
|
||||
if (hnbgw_cmdline_config.daemonize) {
|
||||
rc = osmo_daemonize();
|
||||
if (rc < 0) {
|
||||
perror("Error during daemonize");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
signal(SIGABRT, &signal_handler);
|
||||
signal(SIGUSR1, &signal_handler);
|
||||
signal(SIGUSR2, &signal_handler);
|
||||
osmo_init_ignore_signals();
|
||||
|
||||
osmo_fsm_set_dealloc_ctx(OTC_SELECT);
|
||||
|
||||
while (1) {
|
||||
rc = osmo_select_main_ctx(0);
|
||||
if (rc < 0)
|
||||
exit(3);
|
||||
}
|
||||
|
||||
/* not reached */
|
||||
#if ENABLE_PFCP
|
||||
hnbgw_pfcp_release();
|
||||
#endif
|
||||
exit(0);
|
||||
}
|
||||
759
src/osmo-hnbgw/ps_rab_ass_fsm.c
Normal file
759
src/osmo-hnbgw/ps_rab_ass_fsm.c
Normal file
@@ -0,0 +1,759 @@
|
||||
/* Handle RANAP PS RAB Assignment */
|
||||
/* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <asn1c/asn1helpers.h>
|
||||
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/prim.h>
|
||||
|
||||
#include <osmocom/core/fsm.h>
|
||||
#include <osmocom/core/byteswap.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
#include <osmocom/core/tdef.h>
|
||||
|
||||
#include <osmocom/ranap/ranap_common.h>
|
||||
#include <osmocom/ranap/ranap_common_cn.h>
|
||||
#include <osmocom/ranap/ranap_common_ran.h>
|
||||
#include <osmocom/ranap/ranap_msg_factory.h>
|
||||
|
||||
#include <osmocom/ranap/ranap_ies_defs.h>
|
||||
#include <osmocom/ranap/iu_helpers.h>
|
||||
|
||||
#include <osmocom/pfcp/pfcp_msg.h>
|
||||
#include <osmocom/pfcp/pfcp_endpoint.h>
|
||||
#include <osmocom/pfcp/pfcp_cp_peer.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/ranap_rab_ass.h>
|
||||
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
|
||||
#include <osmocom/hnbgw/ps_rab_fsm.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw_rua.h>
|
||||
|
||||
#include <osmocom/hnbgw/tdefs.h>
|
||||
|
||||
#define PORT_GTP1_U 2152
|
||||
|
||||
#define LOG_PS_RAB_ASS(RAB_ASS, LOGL, FMT, ARGS...) \
|
||||
LOGPFSML((RAB_ASS) ? (RAB_ASS)->fi : NULL, LOGL, FMT, ##ARGS)
|
||||
|
||||
static const struct value_string ps_rab_ass_fsm_event_names[] = {
|
||||
OSMO_VALUE_STRING(PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX),
|
||||
OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_ASS_RESP),
|
||||
OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_ESTABLISHED),
|
||||
OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_RELEASED),
|
||||
OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_FAIL),
|
||||
{}
|
||||
};
|
||||
|
||||
enum ps_rab_ass_state {
|
||||
PS_RAB_ASS_ST_RX_RAB_ASS_MSG,
|
||||
PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS,
|
||||
PS_RAB_ASS_ST_RX_RAB_ASS_RESP,
|
||||
PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED,
|
||||
};
|
||||
|
||||
/* Represents one RANAP PS RAB Assignment Request and Response dialog.
|
||||
* There may be any number of PS RAB Assignment Requests, each with any number of RABs being established. We need to
|
||||
* manage these asynchronously and flexibly:
|
||||
* - RABs may be assigned in a group and released one by one, or vice versa;
|
||||
* - we can only forward a RAB Assignment Request / Response when all RABs appearing in it have been set up by the UPF.
|
||||
*
|
||||
* This structure manages the RAB Assignment procedures, and the currently set up RABs:
|
||||
*
|
||||
* - hnbgw_context_map
|
||||
* - .ps_rab_ass_list: list of PS RAB Assignment procedures
|
||||
* - ps_rab_ass_fsm: one RANAP PS RAB Assignment procedure
|
||||
* - ...
|
||||
* - .ps_rab_list: list of individual PS RABs
|
||||
* - ps_rab_fsm: one GTP mapping with PFCP session to the UPF, for a single RAB
|
||||
* - ...
|
||||
*
|
||||
* This ps_rab_ass_fsm lives from a received RAB Assignment Request up to the sent RAB Assignment Response; it
|
||||
* deallocates when all the RABs have been set up.
|
||||
*
|
||||
* The ps_rab_ass_fsm sets up ps_rab_fsm instances, which live longer: up until a RAB or conn release is performed.
|
||||
*/
|
||||
struct ps_rab_ass {
|
||||
struct llist_head entry;
|
||||
|
||||
struct osmo_fsm_inst *fi;
|
||||
|
||||
/* backpointer */
|
||||
struct hnbgw_context_map *map;
|
||||
|
||||
ranap_message *ranap_rab_ass_req_message;
|
||||
|
||||
ranap_message *ranap_rab_ass_resp_message;
|
||||
struct msgb *ranap_rab_ass_resp_msgb;
|
||||
|
||||
/* A RAB Assignment may contain more than one RAB. Each RAB sets up a distinct ps_rab_fsm (aka PFCP session) and
|
||||
* reports back about local F-TEIDs assigned by the UPF. This gives the nr of RAB events we expect from
|
||||
* ps_rab_fsms, without iterating the RAB Assignment message every time (minor optimisation). */
|
||||
int rabs_count;
|
||||
int rabs_done_count;
|
||||
|
||||
unsigned int rabs_rel_count;
|
||||
unsigned int rabs_rel_done_count;
|
||||
};
|
||||
|
||||
struct osmo_tdef_state_timeout ps_rab_ass_fsm_timeouts[32] = {
|
||||
/* PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS is terminated by PFCP timeouts via ps_rab_fsm */
|
||||
/* PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED is terminated by PFCP timeouts via ps_rab_fsm */
|
||||
};
|
||||
|
||||
#define ps_rab_ass_fsm_state_chg(fi, state) \
|
||||
osmo_tdef_fsm_inst_state_chg(fi, state, ps_rab_ass_fsm_timeouts, hnbgw_T_defs, -1)
|
||||
|
||||
static struct osmo_fsm ps_rab_ass_fsm;
|
||||
|
||||
static struct ps_rab_ass *ps_rab_ass_alloc(struct hnbgw_context_map *map)
|
||||
{
|
||||
struct ps_rab_ass *rab_ass;
|
||||
struct osmo_fsm_inst *fi;
|
||||
|
||||
fi = osmo_fsm_inst_alloc(&ps_rab_ass_fsm, map, map, LOGL_DEBUG, NULL);
|
||||
OSMO_ASSERT(fi);
|
||||
osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-RUA-%u", hnb_context_name(map->hnb_ctx), map->rua_ctx_id);
|
||||
|
||||
rab_ass = talloc(fi, struct ps_rab_ass);
|
||||
OSMO_ASSERT(rab_ass);
|
||||
*rab_ass = (struct ps_rab_ass){
|
||||
.fi = fi,
|
||||
.map = map,
|
||||
};
|
||||
fi->priv = rab_ass;
|
||||
|
||||
llist_add_tail(&rab_ass->entry, &map->ps_rab_ass_list);
|
||||
return rab_ass;
|
||||
}
|
||||
|
||||
static void ps_rab_ass_failure(struct ps_rab_ass *rab_ass)
|
||||
{
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "PS RAB Assignment failed\n");
|
||||
|
||||
/* TODO: send unsuccessful RAB Assignment Response to Core? */
|
||||
/* TODO: remove RAB from Access? */
|
||||
|
||||
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
}
|
||||
|
||||
/* Add a single RAB from a RANAP PS RAB Assignment Request's list of RABs */
|
||||
static int ps_rab_setup_core_remote(struct ps_rab_ass *rab_ass, RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair)
|
||||
{
|
||||
struct hnbgw_context_map *map = rab_ass->map;
|
||||
uint8_t rab_id;
|
||||
struct addr_teid f_teid = {};
|
||||
bool use_x213_nsap;
|
||||
struct ps_rab *rab;
|
||||
|
||||
RANAP_RAB_SetupOrModifyItemFirst_t first;
|
||||
RANAP_TransportLayerAddress_t *transp_layer_addr;
|
||||
RANAP_TransportLayerInformation_t *tli;
|
||||
int rc;
|
||||
|
||||
if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem)
|
||||
return -1;
|
||||
|
||||
/* Extract information about the GTP Core side */
|
||||
rc = ranap_decode_rab_setupormodifyitemfirst(&first,
|
||||
&protocol_ie_field_pair->firstValue);
|
||||
if (rc < 0)
|
||||
goto error_exit;
|
||||
|
||||
rab_id = first.rAB_ID.buf[0];
|
||||
|
||||
/* Decode GTP endpoint IP-Address */
|
||||
tli = first.transportLayerInformation;
|
||||
transp_layer_addr = &tli->transportLayerAddress;
|
||||
rc = ranap_transp_layer_addr_decode2(&f_teid.addr, &use_x213_nsap, transp_layer_addr);
|
||||
if (rc < 0)
|
||||
goto error_exit;
|
||||
osmo_sockaddr_set_port(&f_teid.addr.u.sa, PORT_GTP1_U);
|
||||
|
||||
/* Decode the GTP remote TEID */
|
||||
if (tli->iuTransportAssociation.present != RANAP_IuTransportAssociation_PR_gTP_TEI) {
|
||||
rc = -1;
|
||||
goto error_exit;
|
||||
}
|
||||
f_teid.teid = osmo_load32be(tli->iuTransportAssociation.choice.gTP_TEI.buf);
|
||||
f_teid.present = true;
|
||||
|
||||
rab_ass->rabs_count++;
|
||||
rab = ps_rab_start(map, rab_id, &f_teid, use_x213_nsap, rab_ass->fi);
|
||||
if (!rab) {
|
||||
rc = -1;
|
||||
goto error_exit;
|
||||
}
|
||||
rc = 0;
|
||||
|
||||
error_exit:
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, &first);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*! Allocate ps_rab_ass_fsm and handle PS RANAP RAB AssignmentRequest.
|
||||
* \param[in] map hnbgw context map that is responsible for this conn.
|
||||
* \param[in] ranap_msg msgb containing RANAP RAB AssignmentRequest at msgb_l2(), allocated in OTC_SELECT.
|
||||
* This function may talloc_steal(ranap_msg) to keep it for later.
|
||||
* \param[in] message decoded RANAP message container, allocated in OTC_SELECT.
|
||||
* This function may talloc_steal(message) to keep it for later.
|
||||
* \returns 0 on success; negative on error. */
|
||||
int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message)
|
||||
{
|
||||
RANAP_RAB_AssignmentRequestIEs_t *ies = &message->msg.raB_AssignmentRequestIEs;
|
||||
int i;
|
||||
struct ps_rab_ass *rab_ass;
|
||||
|
||||
rab_ass = ps_rab_ass_alloc(map);
|
||||
talloc_steal(rab_ass, message);
|
||||
rab_ass->ranap_rab_ass_req_message = message;
|
||||
/* Now rab_ass owns message and will clean it up */
|
||||
|
||||
if (!osmo_pfcp_cp_peer_is_associated(g_hnbgw->pfcp.cp_peer)) {
|
||||
LOG_MAP(map, DLPFCP, LOGL_ERROR, "PFCP is not associated, cannot set up GTP mapping\n");
|
||||
goto no_rab;
|
||||
}
|
||||
|
||||
/* setup-or-modify list */
|
||||
if (ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT) {
|
||||
/* Multiple RABs may be set up, assemble in list map->ps_rab_list. */
|
||||
for (i = 0; i < ies->raB_SetupOrModifyList.list.count; i++) {
|
||||
RANAP_ProtocolIE_ContainerPair_t *protocol_ie_container_pair;
|
||||
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
|
||||
|
||||
protocol_ie_container_pair = ies->raB_SetupOrModifyList.list.array[i];
|
||||
protocol_ie_field_pair = protocol_ie_container_pair->list.array[0];
|
||||
if (!protocol_ie_field_pair)
|
||||
goto no_rab;
|
||||
if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem)
|
||||
goto no_rab;
|
||||
|
||||
if (ps_rab_setup_core_remote(rab_ass, protocol_ie_field_pair))
|
||||
goto no_rab;
|
||||
}
|
||||
}
|
||||
|
||||
/* Note: ReleaseList is handled at RAB Assign Response time. */
|
||||
|
||||
if (rab_ass->rabs_count > 0) {
|
||||
/* Got all RABs' state and their Core side GTP info in map->ps_rab_list.
|
||||
* For each, a ps_rab_fsm has been started and each will call back with
|
||||
* PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX or PS_RAB_ASS_EV_RAB_FAIL. */
|
||||
return ps_rab_ass_fsm_state_chg(rab_ass->fi, PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS);
|
||||
}
|
||||
|
||||
/* No Setup-or-modify, only Release RABs. Forward the message as is,
|
||||
* RABs will be released at RAB ASS Response time. */
|
||||
map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, ranap_msg);
|
||||
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
return 0;
|
||||
no_rab:
|
||||
ps_rab_ass_failure(rab_ass);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ps_rab_ass_req_check_local_f_teids(struct ps_rab_ass *rab_ass);
|
||||
|
||||
static void ps_rab_ass_fsm_wait_local_f_teids(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct ps_rab_ass *rab_ass = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
case PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX:
|
||||
rab_ass->rabs_done_count++;
|
||||
if (rab_ass->rabs_done_count < rab_ass->rabs_count) {
|
||||
/* some RABs are still pending, postpone going through the message until all are done. */
|
||||
return;
|
||||
}
|
||||
ps_rab_ass_req_check_local_f_teids(rab_ass);
|
||||
return;
|
||||
|
||||
case PS_RAB_ASS_EV_RAB_FAIL:
|
||||
ps_rab_ass_failure(rab_ass);
|
||||
return;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
/* See whether all information is in so that we can forward the modified RAB Assignment Request to RUA. */
|
||||
static void ps_rab_ass_req_check_local_f_teids(struct ps_rab_ass *rab_ass)
|
||||
{
|
||||
struct ps_rab *rab = NULL;
|
||||
RANAP_RAB_AssignmentRequestIEs_t *ies = &rab_ass->ranap_rab_ass_req_message->msg.raB_AssignmentRequestIEs;
|
||||
int i;
|
||||
struct msgb *msg;
|
||||
|
||||
/* Go through all RABs in the RAB Assignment Request message and replace with the F-TEID that the UPF assigned,
|
||||
* verifying that we indeed have local F-TEIDs for all RABs contained in this message. */
|
||||
for (i = 0; i < ies->raB_SetupOrModifyList.list.count; i++) {
|
||||
RANAP_ProtocolIE_ContainerPair_t *protocol_ie_container_pair;
|
||||
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
|
||||
RANAP_RAB_SetupOrModifyItemFirst_t first;
|
||||
uint8_t rab_id;
|
||||
int rc;
|
||||
|
||||
protocol_ie_container_pair = ies->raB_SetupOrModifyList.list.array[i];
|
||||
protocol_ie_field_pair = protocol_ie_container_pair->list.array[0];
|
||||
if (!protocol_ie_field_pair)
|
||||
continue;
|
||||
if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem)
|
||||
continue;
|
||||
|
||||
/* Get to the information about the GTP Core side */
|
||||
rc = ranap_decode_rab_setupormodifyitemfirst(&first,
|
||||
&protocol_ie_field_pair->firstValue);
|
||||
if (rc < 0)
|
||||
goto continue_cleanloop;
|
||||
|
||||
rab_id = first.rAB_ID.buf[0];
|
||||
|
||||
/* Find struct ps_rab for this rab_id */
|
||||
rab = ps_rab_get(rab_ass->map, rab_id);
|
||||
if (!rab || !rab->access.local.present) {
|
||||
/* Not ready to send on the RAB Assignment Request to RUA, a local F-TEID is missing. */
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, &first);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Replace GTP endpoint */
|
||||
ASN_STRUCT_FREE(asn_DEF_RANAP_TransportLayerInformation, first.transportLayerInformation);
|
||||
first.transportLayerInformation = ranap_new_transp_info_gtp(&rab->access.local.addr,
|
||||
rab->access.local.teid,
|
||||
rab->core.use_x213_nsap);
|
||||
|
||||
/* Reencode to update transport-layer-information */
|
||||
rc = ANY_fromType_aper(&protocol_ie_field_pair->firstValue, &asn_DEF_RANAP_RAB_SetupOrModifyItemFirst,
|
||||
&first);
|
||||
if (rc < 0)
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB Assignment Request failed\n");
|
||||
continue_cleanloop:
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, &first);
|
||||
}
|
||||
|
||||
if (!rab) {
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Lookup PS RAB Assignment Request failed\n");
|
||||
ps_rab_ass_failure(rab_ass);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Send the modified RAB Assignment Request to the hNodeB, wait for the RAB Assignment Response */
|
||||
msg = ranap_rab_ass_req_encode(ies);
|
||||
if (!msg) {
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB Assignment Request failed\n");
|
||||
ps_rab_ass_failure(rab_ass);
|
||||
return;
|
||||
}
|
||||
talloc_steal(OTC_SELECT, msg);
|
||||
msg->l2h = msg->data;
|
||||
map_rua_dispatch(rab->map, MAP_RUA_EV_TX_DIRECT_TRANSFER, msg);
|
||||
|
||||
/* The request message has been forwarded. The response will be handled by a new FSM instance.
|
||||
* We are done. */
|
||||
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
}
|
||||
|
||||
/* Add a single RAB from a RANAP/RUA RAB Assignment Response's list of RABs */
|
||||
static int ps_rab_setup_access_remote(struct ps_rab_ass *rab_ass,
|
||||
RANAP_RAB_SetupOrModifiedItem_t *rab_item)
|
||||
{
|
||||
struct hnbgw_context_map *map = rab_ass->map;
|
||||
uint8_t rab_id;
|
||||
int rc;
|
||||
struct ps_rab_rx_args args = {};
|
||||
|
||||
rab_id = rab_item->rAB_ID.buf[0];
|
||||
|
||||
rc = ranap_transp_layer_addr_decode2(&args.f_teid.addr, &args.use_x213_nsap, rab_item->transportLayerAddress);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* Decode the GTP remote TEID */
|
||||
if (!rab_item->iuTransportAssociation
|
||||
|| rab_item->iuTransportAssociation->present != RANAP_IuTransportAssociation_PR_gTP_TEI)
|
||||
return -1;
|
||||
args.f_teid.teid = osmo_load32be(rab_item->iuTransportAssociation->choice.gTP_TEI.buf);
|
||||
args.f_teid.present = true;
|
||||
|
||||
args.notify_fi = rab_ass->fi;
|
||||
|
||||
return ps_rab_rx_access_remote_f_teid(map, rab_id, &args);
|
||||
}
|
||||
|
||||
int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message)
|
||||
{
|
||||
/* hNodeB responds with its own F-TEIDs. Need to tell the UPF about those to complete the GTP mapping.
|
||||
* 1. here, extract the F-TEIDs (one per RAB),
|
||||
* trigger each ps_rab_fsm to do a PFCP Session Modification.
|
||||
* 2. after all ps_rab_fsms responded with success, insert our Core side local F-TEIDs and send on the RAB
|
||||
* Assignment Response to IuPS. (We already know the local F-TEIDs assigned by the UPF and could send on the
|
||||
* RAB Assignment Response immediately, but rather wait for the PFCP mod req to succeed first.)
|
||||
*
|
||||
* To wait for all the RABs in this response message to complete, create a *separate* rab_ass_fsm instance from
|
||||
* the one created for the earlier RAB Assignment Request message. The reason is that technically we cannot
|
||||
* assume that the request and the response have exactly matching RAB IDs contained in them.
|
||||
*
|
||||
* In the vast majority of practical cases, there will be only one RAB Assignment Request message pending, but
|
||||
* for interop, by treating each RAB on its own and by treating request and response message separately from
|
||||
* each other, we are able to handle mismatching RAB IDs in request and response messages.
|
||||
*/
|
||||
|
||||
RANAP_RAB_AssignmentResponseIEs_t *ies = &message->msg.raB_AssignmentResponseIEs;
|
||||
int rc;
|
||||
int i;
|
||||
struct ps_rab_ass *rab_ass;
|
||||
|
||||
rab_ass = ps_rab_ass_alloc(map);
|
||||
talloc_steal(rab_ass, message);
|
||||
rab_ass->ranap_rab_ass_resp_message = message;
|
||||
talloc_steal(rab_ass, ranap_msg);
|
||||
rab_ass->ranap_rab_ass_resp_msgb = ranap_msg;
|
||||
/* Now rab_ass owns message and will clean it up */
|
||||
|
||||
if (!osmo_pfcp_cp_peer_is_associated(g_hnbgw->pfcp.cp_peer)) {
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "PFCP is not associated, cannot set up GTP mapping\n");
|
||||
goto no_rab;
|
||||
}
|
||||
|
||||
/* setup-or-modify list */
|
||||
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_SETUPORMODIFIEDLIST_PRESENT) {
|
||||
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_INFO, "PS RAB-AssignmentResponse received, updating RABs\n");
|
||||
|
||||
/* Multiple RABs may be set up, bump matching FSMs in list map->ps_rab_list. */
|
||||
for (i = 0; i < ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.count; i++) {
|
||||
RANAP_IE_t *list_ie;
|
||||
RANAP_RAB_SetupOrModifiedItemIEs_t item_ies;
|
||||
|
||||
list_ie = ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.array[i];
|
||||
if (!list_ie)
|
||||
continue;
|
||||
|
||||
rc = ranap_decode_rab_setupormodifieditemies_fromlist(&item_ies,
|
||||
&list_ie->value);
|
||||
if (rc < 0) {
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Failed to decode PS RAB-AssignmentResponse"
|
||||
" SetupOrModifiedItemIEs with list index %d\n", i);
|
||||
goto continue_cleanloop;
|
||||
}
|
||||
|
||||
if (ps_rab_setup_access_remote(rab_ass, &item_ies.raB_SetupOrModifiedItem))
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Failed to apply PS RAB-AssignmentResponse"
|
||||
" SetupOrModifiedItemIEs with list index %d\n", i);
|
||||
rab_ass->rabs_count++;
|
||||
|
||||
continue_cleanloop:
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies);
|
||||
}
|
||||
}
|
||||
|
||||
/* release list */
|
||||
if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_RELEASEDLIST_PRESENT) {
|
||||
/* Multiple RABs may be set up, assemble in list map->ps_rab_list. */
|
||||
RANAP_RAB_ReleasedList_t *r_list = &ies->raB_ReleasedList;
|
||||
for (i = 0; i < r_list->raB_ReleasedList_ies.list.count; i++) {
|
||||
RANAP_IE_t *released_list_ie = r_list->raB_ReleasedList_ies.list.array[i];
|
||||
RANAP_RAB_ReleasedItemIEs_t _rab_rel_item_ies = {};
|
||||
RANAP_RAB_ReleasedItemIEs_t *rab_rel_item_ies = &_rab_rel_item_ies;
|
||||
RANAP_RAB_ReleasedItem_t *rab_rel_item;
|
||||
uint8_t rab_id;
|
||||
struct ps_rab *rab;
|
||||
|
||||
if (!released_list_ie)
|
||||
goto no_rab;
|
||||
if (released_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_ReleasedItem)
|
||||
goto no_rab;
|
||||
|
||||
rc = ranap_decode_rab_releaseditemies_fromlist(rab_rel_item_ies, &released_list_ie->value);
|
||||
if (rc < 0) {
|
||||
LOG_MAP(map, DLPFCP, LOGL_NOTICE, "Rx RAB ASS REQ (REL): Failed decoding ReleaseItem %u\n", i);
|
||||
goto no_rab;
|
||||
}
|
||||
|
||||
rab_rel_item = &rab_rel_item_ies->raB_ReleasedItem;
|
||||
/* The RAB-ID is defined as a bitstring with a size of 8 (1 byte),
|
||||
* See also RANAP-IEs.asn, RAB-ID ::= BIT STRING (SIZE (8)) */
|
||||
rab_id = rab_rel_item->rAB_ID.buf[0];
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_ReleasedItem, rab_rel_item_ies);
|
||||
|
||||
rab = ps_rab_get(map, rab_id);
|
||||
if (rab) {
|
||||
rab_ass->rabs_rel_count++;
|
||||
ps_rab_release(rab, rab_ass->fi);
|
||||
} else {
|
||||
LOG_MAP(map, DLPFCP, LOGL_NOTICE, "Rx RAB ASS REQ (REL) for unknown rab_id %u\n", rab_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rab_ass->rabs_count > 0 || rab_ass->rabs_rel_count > 0) {
|
||||
/* Got all RABs' state and updated their Access side GTP info in map->ps_rab_list.
|
||||
* For each RAB ID, the matching ps_rab_fsm has been instructed to tell the UPF about
|
||||
* the Access Remote GTP F-TEID. Each will call back with PS_RAB_ASS_EV_RAB_ESTABLISHED
|
||||
* or PS_RAB_ASS_EV_RAB_FAIL. */
|
||||
return ps_rab_ass_fsm_state_chg(rab_ass->fi, PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED);
|
||||
}
|
||||
|
||||
/* No Setup-or-modify nor Release RABs. Forward the message as is. */
|
||||
rc = map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg);
|
||||
if (rc < 0) {
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Sending RANAP PS RAB-AssignmentResponse failed\n");
|
||||
goto no_rab;
|
||||
}
|
||||
/* The request message has been forwarded. We are done. */
|
||||
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
return 0;
|
||||
|
||||
no_rab:
|
||||
ps_rab_ass_failure(rab_ass);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ps_rab_ass_resp_send_if_ready(struct ps_rab_ass *rab_ass);
|
||||
static void ps_rab_ass_resp_send_if_ready_quick(struct ps_rab_ass *rab_ass)
|
||||
{
|
||||
/* some RABs are still pending, postpone going through the message until all are done. */
|
||||
if (rab_ass->rabs_done_count < rab_ass->rabs_count)
|
||||
return;
|
||||
if (rab_ass->rabs_rel_done_count < rab_ass->rabs_rel_count)
|
||||
return;
|
||||
ps_rab_ass_resp_send_if_ready(rab_ass);
|
||||
}
|
||||
|
||||
static void ps_rab_ass_fsm_wait_rabs_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct ps_rab_ass *rab_ass = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
case PS_RAB_ASS_EV_RAB_ESTABLISHED:
|
||||
rab_ass->rabs_done_count++;
|
||||
/* All RABs have succeeded, ready to forward */
|
||||
ps_rab_ass_resp_send_if_ready_quick(rab_ass);
|
||||
return;
|
||||
|
||||
case PS_RAB_ASS_EV_RAB_RELEASED:
|
||||
rab_ass->rabs_rel_done_count++;
|
||||
ps_rab_ass_resp_send_if_ready_quick(rab_ass);
|
||||
return;
|
||||
|
||||
case PS_RAB_ASS_EV_RAB_FAIL:
|
||||
ps_rab_ass_failure(rab_ass);
|
||||
return;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
/* See whether all RABs are done establishing, and replace GTP info in the RAB Assignment Response message, so that we
|
||||
* can forward the modified RAB Assignment Request to M3UA. */
|
||||
static void ps_rab_ass_resp_send_if_ready(struct ps_rab_ass *rab_ass)
|
||||
{
|
||||
int i;
|
||||
int rc;
|
||||
RANAP_RAB_AssignmentResponseIEs_t *ies = &rab_ass->ranap_rab_ass_resp_message->msg.raB_AssignmentResponseIEs;
|
||||
struct msgb *msg;
|
||||
|
||||
/* Go through all RABs in the RAB Assignment Response message and replace with the F-TEID that the UPF assigned,
|
||||
* verifying that instructing the UPF has succeeded. */
|
||||
for (i = 0; i < ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.count; i++) {
|
||||
RANAP_IE_t *list_ie;
|
||||
RANAP_RAB_SetupOrModifiedItemIEs_t item_ies;
|
||||
RANAP_RAB_SetupOrModifiedItem_t *rab_item;
|
||||
int rc;
|
||||
uint8_t rab_id;
|
||||
uint32_t teid_be;
|
||||
struct ps_rab *rab;
|
||||
|
||||
list_ie = ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.array[i];
|
||||
if (!list_ie)
|
||||
continue;
|
||||
|
||||
rc = ranap_decode_rab_setupormodifieditemies_fromlist(&item_ies,
|
||||
&list_ie->value);
|
||||
if (rc < 0) {
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Failed to decode PS RAB-AssignmentResponse"
|
||||
" SetupOrModifiedItemIEs with list index %d\n", i);
|
||||
goto continue_cleanloop;
|
||||
}
|
||||
|
||||
rab_item = &item_ies.raB_SetupOrModifiedItem;
|
||||
rab_id = rab_item->rAB_ID.buf[0];
|
||||
|
||||
/* Find struct ps_rab for this rab_id */
|
||||
rab = ps_rab_get(rab_ass->map, rab_id);
|
||||
if (!ps_rab_is_established(rab)) {
|
||||
/* Not ready to send on the RAB Assignment Response to M3UA, still waiting for it to be
|
||||
* established */
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Replace GTP endpoint.
|
||||
* memory: ranap_new_transp_layer_addr() frees previous buffer in transportLayerAddress, if any. The
|
||||
* entire rab_item is freed along with item_ies.
|
||||
*
|
||||
* BTW, If I free this explicitly below with
|
||||
* ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_BIT_STRING, &rab_item->transportLayerAddress);
|
||||
* that causes a leak in talloc_asn1_ctx: "iu_helpers.c:205 contains 20 bytes in 1 blocks".
|
||||
* I couldn't figure out why, but things are freed properly when leaving it all up to item_ies.
|
||||
*/
|
||||
if (ranap_new_transp_layer_addr(rab_item->transportLayerAddress, &rab->core.local.addr,
|
||||
rab->access.use_x213_nsap) < 0) {
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies);
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB-AssignmentResponse failed\n");
|
||||
ps_rab_ass_failure(rab_ass);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_DEBUG, "Re-encoding RANAP PS RAB-AssignmentResponse: RAB %u:"
|
||||
" RUA sent F-TEID %s-0x%x; replacing with %s-0x%x\n",
|
||||
rab_id,
|
||||
osmo_sockaddr_to_str_c(OTC_SELECT, &rab->access.remote.addr), rab->access.remote.teid,
|
||||
osmo_sockaddr_to_str_c(OTC_SELECT, &rab->core.local.addr), rab->core.local.teid);
|
||||
|
||||
teid_be = htonl(rab->core.local.teid);
|
||||
rab_item->iuTransportAssociation->present = RANAP_IuTransportAssociation_PR_gTP_TEI;
|
||||
/* memory: OCTET_STRING_fromBuf() frees previous buffer in gTP_TEI, if any. The entire rab_item is freed
|
||||
* along with item_ies. */
|
||||
OCTET_STRING_fromBuf(&rab_item->iuTransportAssociation->choice.gTP_TEI,
|
||||
(const char *)&teid_be, sizeof(teid_be));
|
||||
|
||||
/* Reencode this list item in the RANAP message */
|
||||
rc = ANY_fromType_aper(&list_ie->value, &asn_DEF_RANAP_RAB_SetupOrModifiedItem, rab_item);
|
||||
if (rc < 0) {
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies);
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB-AssignmentResponse failed\n");
|
||||
ps_rab_ass_failure(rab_ass);
|
||||
return;
|
||||
}
|
||||
|
||||
continue_cleanloop:
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies);
|
||||
}
|
||||
|
||||
msg = rab_ass->ranap_rab_ass_resp_msgb;
|
||||
rab_ass->ranap_rab_ass_resp_msgb = NULL;
|
||||
talloc_steal(OTC_SELECT, msg);
|
||||
|
||||
/* Replaced all the GTP info, re-encode the message. Since we are replacing data 1:1, taking care to use the
|
||||
* same IP address encoding, the resulting message size must be identical to the original message size. */
|
||||
rc = ranap_rab_ass_resp_encode(msgb_l2(msg), msgb_l2len(msg), ies);
|
||||
if (rc < 0) {
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB-AssignmentResponse failed\n");
|
||||
ps_rab_ass_failure(rab_ass);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_INFO, "Sending RANAP PS RAB-AssignmentResponse with mapped GTP info\n");
|
||||
|
||||
rc = map_sccp_dispatch(rab_ass->map, MAP_SCCP_EV_TX_DATA_REQUEST, msg);
|
||||
if (rc < 0) {
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Sending RANAP PS RAB-AssignmentResponse failed\n");
|
||||
ps_rab_ass_failure(rab_ass);
|
||||
return;
|
||||
}
|
||||
/* The request message has been forwarded. We are done. */
|
||||
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
}
|
||||
|
||||
static void ps_rab_ass_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
||||
{
|
||||
struct ps_rab_ass *rab_ass = fi->priv;
|
||||
struct ps_rab *rab;
|
||||
|
||||
llist_for_each_entry(rab, &rab_ass->map->ps_rab_list, entry) {
|
||||
if (rab->req_fi == fi)
|
||||
rab->req_fi = NULL;
|
||||
if (rab->resp_fi == fi)
|
||||
rab->resp_fi = NULL;
|
||||
}
|
||||
|
||||
/* remove from map->ps_rab_ass_list */
|
||||
llist_del(&rab_ass->entry);
|
||||
}
|
||||
|
||||
void hnbgw_gtpmap_release(struct hnbgw_context_map *map)
|
||||
{
|
||||
struct ps_rab_ass *rab_ass, *next;
|
||||
struct ps_rab *rab, *next2;
|
||||
llist_for_each_entry_safe(rab, next2, &map->ps_rab_list, entry) {
|
||||
ps_rab_release(rab, NULL);
|
||||
}
|
||||
llist_for_each_entry_safe(rab_ass, next, &map->ps_rab_ass_list, entry) {
|
||||
osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
#define S(x) (1 << (x))
|
||||
|
||||
static const struct osmo_fsm_state ps_rab_ass_fsm_states[] = {
|
||||
[PS_RAB_ASS_ST_RX_RAB_ASS_MSG] = {
|
||||
.name = "RX_RAB_ASS_MSG",
|
||||
.out_state_mask = 0
|
||||
| S(PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS)
|
||||
| S(PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED)
|
||||
,
|
||||
},
|
||||
[PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS] = {
|
||||
.name = "WAIT_LOCAL_F_TEIDS",
|
||||
.action = ps_rab_ass_fsm_wait_local_f_teids,
|
||||
.in_event_mask = 0
|
||||
| S(PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX)
|
||||
| S(PS_RAB_ASS_EV_RAB_FAIL)
|
||||
,
|
||||
},
|
||||
[PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED] = {
|
||||
.name = "WAIT_RABS_ESTABLISHED",
|
||||
.action = ps_rab_ass_fsm_wait_rabs_established,
|
||||
.in_event_mask = 0
|
||||
| S(PS_RAB_ASS_EV_RAB_ESTABLISHED)
|
||||
| S(PS_RAB_ASS_EV_RAB_RELEASED)
|
||||
| S(PS_RAB_ASS_EV_RAB_FAIL)
|
||||
,
|
||||
},
|
||||
};
|
||||
|
||||
int ps_rab_ass_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
struct ps_rab_ass *rab_ass = fi->priv;
|
||||
LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Timeout of " OSMO_T_FMT "\n", OSMO_T_FMT_ARGS(fi->T));
|
||||
/* terminate */
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct osmo_fsm ps_rab_ass_fsm = {
|
||||
.name = "ps_rab_ass",
|
||||
.states = ps_rab_ass_fsm_states,
|
||||
.num_states = ARRAY_SIZE(ps_rab_ass_fsm_states),
|
||||
.log_subsys = DRANAP,
|
||||
.event_names = ps_rab_ass_fsm_event_names,
|
||||
.cleanup = ps_rab_ass_fsm_cleanup,
|
||||
.timer_cb = ps_rab_ass_fsm_timer_cb,
|
||||
};
|
||||
|
||||
static __attribute__((constructor)) void ps_rab_ass_fsm_register(void)
|
||||
{
|
||||
OSMO_ASSERT(osmo_fsm_register(&ps_rab_ass_fsm) == 0);
|
||||
}
|
||||
833
src/osmo-hnbgw/ps_rab_fsm.c
Normal file
833
src/osmo-hnbgw/ps_rab_fsm.c
Normal file
@@ -0,0 +1,833 @@
|
||||
/* Handle PFCP communication with the UPF for a single RAB. */
|
||||
/* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocom/core/tdef.h>
|
||||
|
||||
#include <osmocom/pfcp/pfcp_endpoint.h>
|
||||
#include <osmocom/pfcp/pfcp_cp_peer.h>
|
||||
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/tdefs.h>
|
||||
#include <osmocom/hnbgw/ps_rab_fsm.h>
|
||||
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
|
||||
|
||||
#define LOG_PS_RAB(RAB, LOGL, FMT, ARGS...) \
|
||||
LOGPFSML((RAB) ? (RAB)->fi : NULL, LOGL, FMT, ##ARGS)
|
||||
|
||||
enum ps_rab_state {
|
||||
PS_RAB_ST_RX_CORE_REMOTE_F_TEID,
|
||||
PS_RAB_ST_WAIT_PFCP_EST_RESP,
|
||||
PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID,
|
||||
PS_RAB_ST_WAIT_PFCP_MOD_RESP,
|
||||
PS_RAB_ST_ESTABLISHED,
|
||||
PS_RAB_ST_WAIT_PFCP_DEL_RESP,
|
||||
PS_RAB_ST_WAIT_USE_COUNT,
|
||||
};
|
||||
|
||||
enum ps_rab_event {
|
||||
PS_RAB_EV_PFCP_EST_RESP,
|
||||
PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID,
|
||||
PS_RAB_EV_PFCP_MOD_RESP,
|
||||
PS_RAB_EV_PFCP_DEL_RESP,
|
||||
PS_RAB_EV_USE_COUNT_ZERO,
|
||||
};
|
||||
|
||||
static const struct value_string ps_rab_fsm_event_names[] = {
|
||||
OSMO_VALUE_STRING(PS_RAB_EV_PFCP_EST_RESP),
|
||||
OSMO_VALUE_STRING(PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID),
|
||||
OSMO_VALUE_STRING(PS_RAB_EV_PFCP_MOD_RESP),
|
||||
OSMO_VALUE_STRING(PS_RAB_EV_PFCP_DEL_RESP),
|
||||
OSMO_VALUE_STRING(PS_RAB_EV_USE_COUNT_ZERO),
|
||||
{}
|
||||
};
|
||||
|
||||
struct osmo_tdef_state_timeout ps_rab_fsm_timeouts[32] = {
|
||||
/* PS_RAB_ST_WAIT_PFCP_EST_RESP is terminated by PFCP timeouts via resp_cb() */
|
||||
/* PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID is terminated by ps_rab_ass_fsm */
|
||||
/* PS_RAB_ST_WAIT_PFCP_MOD_RESP is terminated by PFCP timeouts via resp_cb() */
|
||||
/* PS_RAB_ST_WAIT_PFCP_DEL_RESP is terminated by PFCP timeouts via resp_cb() */
|
||||
};
|
||||
|
||||
enum pdr_far_id {
|
||||
ID_CORE_TO_ACCESS = 1,
|
||||
ID_ACCESS_TO_CORE = 2,
|
||||
};
|
||||
|
||||
|
||||
#define ps_rab_fsm_state_chg(state) \
|
||||
osmo_tdef_fsm_inst_state_chg(fi, state, ps_rab_fsm_timeouts, hnbgw_T_defs, -1)
|
||||
|
||||
#define PS_RAB_USE_ACTIVE "active"
|
||||
|
||||
static struct osmo_fsm ps_rab_fsm;
|
||||
|
||||
static int ps_rab_fsm_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line);
|
||||
|
||||
static struct ps_rab *ps_rab_alloc(struct hnbgw_context_map *map, uint8_t rab_id)
|
||||
{
|
||||
struct osmo_fsm_inst *fi;
|
||||
struct ps_rab *rab;
|
||||
|
||||
/* Allocate with the global hnb_gw, so that we can gracefully handle PFCP release even if a hnb_ctx gets
|
||||
* deallocated. */
|
||||
fi = osmo_fsm_inst_alloc(&ps_rab_fsm, g_hnbgw, NULL, LOGL_DEBUG, NULL);
|
||||
OSMO_ASSERT(fi);
|
||||
osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-RUA-%u-RAB-%u", hnb_context_name(map->hnb_ctx), map->rua_ctx_id,
|
||||
rab_id);
|
||||
|
||||
rab = talloc(fi, struct ps_rab);
|
||||
OSMO_ASSERT(rab);
|
||||
*rab = (struct ps_rab){
|
||||
.fi = fi,
|
||||
.map = map,
|
||||
.rab_id = rab_id,
|
||||
.use_count = {
|
||||
.talloc_object = rab,
|
||||
.use_cb = ps_rab_fsm_use_cb,
|
||||
},
|
||||
};
|
||||
fi->priv = rab;
|
||||
|
||||
OSMO_ASSERT(osmo_use_count_get_put(&rab->use_count, PS_RAB_USE_ACTIVE, 1) == 0);
|
||||
|
||||
llist_add_tail(&rab->entry, &map->ps_rab_list);
|
||||
return rab;
|
||||
}
|
||||
|
||||
/* Iterate all ps_rab instances of all context maps and return the one matching the given SEID.
|
||||
* If is_cp_seid == true, match seid with rab->cp_seid (e.g. for received PFCP messages).
|
||||
* Otherwise match seid with rab->up_f_seid.seid (e.g. for sent PFCP messages). */
|
||||
struct ps_rab *ps_rab_find_by_seid(uint64_t seid, bool is_cp_seid)
|
||||
{
|
||||
struct hnb_context *hnb;
|
||||
llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) {
|
||||
struct hnbgw_context_map *map;
|
||||
llist_for_each_entry(map, &hnb->map_list, hnb_list) {
|
||||
struct ps_rab *rab;
|
||||
llist_for_each_entry(rab, &map->ps_rab_list, entry) {
|
||||
uint64_t rab_seid = is_cp_seid ? rab->cp_seid : rab->up_f_seid.seid;
|
||||
if (rab_seid == seid)
|
||||
return rab;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ps_rab_pfcp_set_msg_ctx(struct ps_rab *rab, struct osmo_pfcp_msg *m)
|
||||
{
|
||||
if (m->ctx.session_fi)
|
||||
return;
|
||||
m->ctx.session_fi = rab->fi;
|
||||
m->ctx.session_use_count = &rab->use_count;
|
||||
m->ctx.session_use_token = "PFCP_MSG";
|
||||
OSMO_ASSERT(osmo_use_count_get_put(m->ctx.session_use_count, m->ctx.session_use_token, 1) == 0);
|
||||
}
|
||||
|
||||
static struct osmo_pfcp_msg *ps_rab_new_pfcp_msg_req(struct ps_rab *rab, enum osmo_pfcp_message_type msg_type)
|
||||
{
|
||||
struct osmo_pfcp_msg *m = osmo_pfcp_cp_peer_new_req(g_hnbgw->pfcp.cp_peer, msg_type);
|
||||
|
||||
m->h.seid_present = true;
|
||||
m->h.seid = rab->up_f_seid.seid;
|
||||
ps_rab_pfcp_set_msg_ctx(rab, m);
|
||||
return m;
|
||||
}
|
||||
|
||||
struct ps_rab *ps_rab_get(struct hnbgw_context_map *map, uint8_t rab_id)
|
||||
{
|
||||
struct ps_rab *rab;
|
||||
llist_for_each_entry(rab, &map->ps_rab_list, entry) {
|
||||
if (rab->rab_id != rab_id)
|
||||
continue;
|
||||
return rab;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ps_rab_is_established(const struct ps_rab *rab)
|
||||
{
|
||||
return rab && rab->fi->state == PS_RAB_ST_ESTABLISHED;
|
||||
}
|
||||
|
||||
void ps_rab_failure(struct ps_rab *rab)
|
||||
{
|
||||
if (rab->req_fi)
|
||||
osmo_fsm_inst_dispatch(rab->req_fi, PS_RAB_ASS_EV_RAB_FAIL, rab);
|
||||
if (rab->resp_fi)
|
||||
osmo_fsm_inst_dispatch(rab->resp_fi, PS_RAB_ASS_EV_RAB_FAIL, rab);
|
||||
ps_rab_release(rab, NULL);
|
||||
}
|
||||
|
||||
struct ps_rab *ps_rab_start(struct hnbgw_context_map *map, uint8_t rab_id,
|
||||
const struct addr_teid *core_f_teid, bool use_x213_nsap,
|
||||
struct osmo_fsm_inst *req_fi)
|
||||
{
|
||||
struct osmo_fsm_inst *fi;
|
||||
struct ps_rab *rab;
|
||||
|
||||
rab = ps_rab_alloc(map, rab_id);
|
||||
fi = rab->fi;
|
||||
rab->req_fi = req_fi;
|
||||
rab->core.remote = *core_f_teid;
|
||||
rab->core.use_x213_nsap = use_x213_nsap;
|
||||
|
||||
/* Got the RAB's Core side GTP info. Route the GTP for via the local UPF.
|
||||
* Establish a PFCP session with the UPF: tell it about the Core side GTP endpoint and request local F-TEIDs. */
|
||||
if (ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_PFCP_EST_RESP)) {
|
||||
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return rab;
|
||||
}
|
||||
|
||||
#define set_netinst(NETINST_MEMBER, STRING) do { \
|
||||
if ((STRING) && *(STRING)) { \
|
||||
NETINST_MEMBER##_present = true; \
|
||||
OSMO_STRLCPY_ARRAY(NETINST_MEMBER.str, STRING); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* Add two PDR and two FAR to the PFCP Session Establishment Request message, according to the information found in rab.
|
||||
*/
|
||||
static int rab_to_pfcp_session_est_req(struct osmo_pfcp_msg_session_est_req *ser, struct ps_rab *rab)
|
||||
{
|
||||
if (ser->create_pdr_count + 2 > ARRAY_SIZE(ser->create_pdr)
|
||||
|| ser->create_far_count + 2 > ARRAY_SIZE(ser->create_far)) {
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "insufficient space for Create PDR / Create FAR IEs\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Core to Access:
|
||||
* - UPF should return an F-TEID for the PDR, to be forwarded back to Core later in
|
||||
* RANAP RAB Assignment Response.
|
||||
* - we don't know the Access side GTP address yet, so set FAR to DROP.
|
||||
*/
|
||||
ser->create_pdr[ser->create_pdr_count] = (struct osmo_pfcp_ie_create_pdr){
|
||||
.pdr_id = ID_CORE_TO_ACCESS,
|
||||
.precedence = 255,
|
||||
.pdi = {
|
||||
.source_iface = OSMO_PFCP_SOURCE_IFACE_CORE,
|
||||
.local_f_teid_present = true,
|
||||
.local_f_teid = {
|
||||
.choose_flag = true,
|
||||
.choose = {
|
||||
.ipv4_addr = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
.outer_header_removal_present = true,
|
||||
.outer_header_removal = {
|
||||
.desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4,
|
||||
},
|
||||
.far_id_present = true,
|
||||
.far_id = ID_CORE_TO_ACCESS,
|
||||
};
|
||||
set_netinst(ser->create_pdr[ser->create_pdr_count].pdi.network_inst, g_hnbgw->config.pfcp.netinst.core);
|
||||
ser->create_pdr_count++;
|
||||
|
||||
ser->create_far[ser->create_far_count] = (struct osmo_pfcp_ie_create_far){
|
||||
.far_id = ID_CORE_TO_ACCESS,
|
||||
};
|
||||
osmo_pfcp_bits_set(ser->create_far[ser->create_far_count].apply_action.bits,
|
||||
OSMO_PFCP_APPLY_ACTION_DROP, true);
|
||||
ser->create_far_count++;
|
||||
|
||||
/* Access to Core:
|
||||
* - UPF should return an F-TEID for the PDR, to be forwarded to Access in the modified
|
||||
* RANAP RAB Assignment Request.
|
||||
* - we already know the Core's GTP endpoint F-TEID, so fully set up this FAR.
|
||||
*/
|
||||
ser->create_pdr[ser->create_pdr_count] = (struct osmo_pfcp_ie_create_pdr){
|
||||
.pdr_id = ID_ACCESS_TO_CORE,
|
||||
.precedence = 255,
|
||||
.pdi = {
|
||||
.source_iface = OSMO_PFCP_SOURCE_IFACE_ACCESS,
|
||||
.local_f_teid_present = true,
|
||||
.local_f_teid = {
|
||||
.choose_flag = true,
|
||||
.choose = {
|
||||
.ipv4_addr = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
.outer_header_removal_present = true,
|
||||
.outer_header_removal = {
|
||||
.desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4,
|
||||
},
|
||||
.far_id_present = true,
|
||||
.far_id = ID_ACCESS_TO_CORE,
|
||||
};
|
||||
set_netinst(ser->create_pdr[ser->create_pdr_count].pdi.network_inst, g_hnbgw->config.pfcp.netinst.access);
|
||||
ser->create_pdr_count++;
|
||||
|
||||
ser->create_far[ser->create_far_count] = (struct osmo_pfcp_ie_create_far){
|
||||
.far_id = ID_ACCESS_TO_CORE,
|
||||
.forw_params_present = true,
|
||||
.forw_params = {
|
||||
.destination_iface = OSMO_PFCP_DEST_IFACE_CORE,
|
||||
.outer_header_creation_present = true,
|
||||
.outer_header_creation = {
|
||||
.teid_present = true,
|
||||
.teid = rab->core.remote.teid,
|
||||
.ip_addr.v4_present = true,
|
||||
.ip_addr.v4 = rab->core.remote.addr,
|
||||
},
|
||||
},
|
||||
};
|
||||
osmo_pfcp_bits_set(ser->create_far[ser->create_far_count].forw_params.outer_header_creation.desc_bits,
|
||||
OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true);
|
||||
osmo_pfcp_bits_set(ser->create_far[ser->create_far_count].apply_action.bits,
|
||||
OSMO_PFCP_APPLY_ACTION_FORW, true);
|
||||
set_netinst(ser->create_far[ser->create_far_count].forw_params.network_inst, g_hnbgw->config.pfcp.netinst.core);
|
||||
ser->create_far_count++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_pfcp_est_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg);
|
||||
|
||||
static void ps_rab_fsm_wait_pfcp_est_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct ps_rab *rab = fi->priv;
|
||||
struct osmo_pfcp_msg *m;
|
||||
struct osmo_pfcp_ie_f_seid cp_f_seid;
|
||||
struct osmo_pfcp_msg_session_est_req *ser;
|
||||
|
||||
/* So far we have the rab->core.remote information. Send that to the UPF.
|
||||
* Also request all local GTP endpoints from UPF (rab->{core,access}.local) */
|
||||
m = ps_rab_new_pfcp_msg_req(rab, OSMO_PFCP_MSGT_SESSION_EST_REQ);
|
||||
|
||||
/* Send UP-SEID as zero, the UPF has yet to assign a SEID for itself remotely */
|
||||
m->h.seid = 0;
|
||||
|
||||
/* Make a new CP-SEID, our local reference for the PFCP session. */
|
||||
rab->cp_seid = osmo_pfcp_cp_peer_next_seid(g_hnbgw->pfcp.cp_peer);
|
||||
cp_f_seid = (struct osmo_pfcp_ie_f_seid){
|
||||
.seid = rab->cp_seid,
|
||||
};
|
||||
osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, &osmo_pfcp_endpoint_get_cfg(g_hnbgw->pfcp.ep)->local_addr);
|
||||
|
||||
m->ies.session_est_req = (struct osmo_pfcp_msg_session_est_req){
|
||||
.node_id = m->ies.session_est_req.node_id,
|
||||
.cp_f_seid_present = true,
|
||||
.cp_f_seid = cp_f_seid,
|
||||
};
|
||||
ser = &m->ies.session_est_req;
|
||||
|
||||
/* Create PDR+FAR pairs */
|
||||
if (rab_to_pfcp_session_est_req(ser, rab)) {
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "Failed to compose PFCP message\n");
|
||||
osmo_pfcp_msg_free(m);
|
||||
ps_rab_failure(rab);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Send PFCP Session Establishment Request to UPF, wait for response. */
|
||||
m->ctx.resp_cb = on_pfcp_est_resp;
|
||||
if (osmo_pfcp_endpoint_tx(g_hnbgw->pfcp.ep, m)) {
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "Failed to send PFCP message\n");
|
||||
ps_rab_failure(rab);
|
||||
}
|
||||
}
|
||||
|
||||
static int on_pfcp_est_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg)
|
||||
{
|
||||
struct ps_rab *rab = req->ctx.session_fi->priv;
|
||||
|
||||
/* Send as FSM event to ensure this step is currently allowed */
|
||||
osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_PFCP_EST_RESP, rx_resp);
|
||||
|
||||
/* By returning 0 here, the rx_resp message is not dispatched "again" to pfcp_ep->rx_msg(). We've handled it
|
||||
* here already. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ps_rab_rx_pfcp_est_resp(struct osmo_fsm_inst *fi, struct osmo_pfcp_msg *rx);
|
||||
|
||||
static void ps_rab_fsm_wait_pfcp_est_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
switch (event) {
|
||||
case PS_RAB_EV_PFCP_EST_RESP:
|
||||
ps_rab_rx_pfcp_est_resp(fi, data);
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
/* Look for dst->local.pdr_id in ser->created_pdr[], and copy the GTP endpoint info to dst->local.addr_teid, if found. */
|
||||
static int get_local_f_teid_from_created_pdr(struct half_gtp_map *dst, struct osmo_pfcp_msg_session_est_resp *ser,
|
||||
uint8_t pdr_id)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < ser->created_pdr_count; i++) {
|
||||
struct osmo_pfcp_ie_created_pdr *cpdr = &ser->created_pdr[i];
|
||||
if (cpdr->pdr_id != pdr_id)
|
||||
continue;
|
||||
if (!cpdr->local_f_teid_present)
|
||||
continue;
|
||||
if (cpdr->local_f_teid.choose_flag)
|
||||
continue;
|
||||
if (!cpdr->local_f_teid.fixed.ip_addr.v4_present)
|
||||
continue;
|
||||
dst->local.addr = cpdr->local_f_teid.fixed.ip_addr.v4;
|
||||
dst->local.teid = cpdr->local_f_teid.fixed.teid;
|
||||
dst->local.present = true;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ps_rab_rx_pfcp_est_resp(struct osmo_fsm_inst *fi, struct osmo_pfcp_msg *rx)
|
||||
{
|
||||
struct ps_rab *rab = fi->priv;
|
||||
enum osmo_pfcp_cause *cause;
|
||||
struct osmo_pfcp_msg_session_est_resp *ser;
|
||||
|
||||
if (!rx) {
|
||||
/* This happens when no response has arrived after all PFCP timeouts and retransmissions. */
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "No response to PFCP Session Establishment Request\n");
|
||||
goto pfcp_session_est_failed;
|
||||
}
|
||||
|
||||
ser = &rx->ies.session_est_resp;
|
||||
|
||||
cause = osmo_pfcp_msg_cause(rx);
|
||||
if (!cause || *cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) {
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "PFCP Session Establishment Response was not successful: %s\n",
|
||||
cause ? osmo_pfcp_cause_str(*cause) : "NULL");
|
||||
goto pfcp_session_est_failed;
|
||||
}
|
||||
|
||||
/* Get the UPF's SEID for future messages for this PFCP session */
|
||||
if (!ser->up_f_seid_present) {
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "PFCP Session Establishment Response lacks a UP F-SEID\n");
|
||||
goto pfcp_session_est_failed;
|
||||
}
|
||||
rab->up_f_seid = ser->up_f_seid;
|
||||
|
||||
if (rab->release_requested) {
|
||||
/* The UE conn or the entire HNB has released while we were waiting for a PFCP response. Now that there
|
||||
* is a remote SEID, we can finally delete the session that we asked for earlier. */
|
||||
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_PFCP_DEL_RESP);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get the UPF's local F-TEIDs for both Core and Access */
|
||||
if (get_local_f_teid_from_created_pdr(&rab->core, ser, ID_CORE_TO_ACCESS)
|
||||
|| get_local_f_teid_from_created_pdr(&rab->access, ser, ID_ACCESS_TO_CORE)) {
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "Missing F-TEID in PFCP Session Establishment Response\n");
|
||||
ps_rab_failure(rab);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rab->req_fi)
|
||||
osmo_fsm_inst_dispatch(rab->req_fi, PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX, rab);
|
||||
|
||||
/* The RAB Assignment Response will yield the hNodeB's F-TEID, i.e. the F-TEID we are supposed to send to Access
|
||||
* in outgoing GTP packets. */
|
||||
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID);
|
||||
return;
|
||||
|
||||
pfcp_session_est_failed:
|
||||
if (rab->release_requested) {
|
||||
/* the RAB was released and we were waiting for some PFCP responsewhile waiting for a response, and now
|
||||
* we know that no session has been created. No PFCP left, deallocate. */
|
||||
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_USE_COUNT);
|
||||
return;
|
||||
}
|
||||
ps_rab_failure(rab);
|
||||
}
|
||||
|
||||
int ps_rab_rx_access_remote_f_teid(struct hnbgw_context_map *map, uint8_t rab_id,
|
||||
const struct ps_rab_rx_args *args)
|
||||
{
|
||||
int rc;
|
||||
struct ps_rab *rab = ps_rab_get(map, rab_id);
|
||||
if (!rab) {
|
||||
LOG_MAP(map, DLPFCP, LOGL_ERROR, "There is no RAB with id %u\n", rab_id);
|
||||
return -ENOENT;
|
||||
}
|
||||
/* Dispatch as event to make sure this is currently allowed */
|
||||
rc = osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID, (void *)args);
|
||||
if (rc)
|
||||
return rc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ps_rab_fsm_wait_access_remote_f_teid(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct ps_rab *rab = fi->priv;
|
||||
const struct ps_rab_rx_args *args;
|
||||
switch (event) {
|
||||
case PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID:
|
||||
args = data;
|
||||
rab->resp_fi = args->notify_fi;
|
||||
rab->access.use_x213_nsap = args->use_x213_nsap;
|
||||
rab->access.remote = args->f_teid;
|
||||
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_PFCP_MOD_RESP);
|
||||
return;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add an Update FAR to the PFCP Session Modification Request message, updating a remote F-TEID. */
|
||||
static int rab_to_pfcp_session_mod_req_upd_far(struct osmo_pfcp_msg_session_mod_req *smr,
|
||||
uint32_t far_id, const struct addr_teid *remote_f_teid,
|
||||
const char *far_netinst)
|
||||
{
|
||||
if (smr->upd_far_count + 1 > ARRAY_SIZE(smr->upd_far))
|
||||
return -1;
|
||||
|
||||
smr->upd_far[smr->upd_far_count] = (struct osmo_pfcp_ie_upd_far){
|
||||
.far_id = far_id,
|
||||
.apply_action_present = true,
|
||||
/* apply_action.bits set below */
|
||||
.upd_forw_params_present = true,
|
||||
.upd_forw_params = {
|
||||
.outer_header_creation_present = true,
|
||||
.outer_header_creation = {
|
||||
/* desc_bits set below */
|
||||
.teid_present = true,
|
||||
.teid = remote_f_teid->teid,
|
||||
.ip_addr.v4_present = true,
|
||||
.ip_addr.v4 = remote_f_teid->addr,
|
||||
},
|
||||
},
|
||||
};
|
||||
osmo_pfcp_bits_set(smr->upd_far[smr->upd_far_count].apply_action.bits,
|
||||
OSMO_PFCP_APPLY_ACTION_FORW, true);
|
||||
osmo_pfcp_bits_set(smr->upd_far[smr->upd_far_count].upd_forw_params.outer_header_creation.desc_bits,
|
||||
OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true);
|
||||
set_netinst(smr->upd_far[smr->upd_far_count].upd_forw_params.network_inst, far_netinst);
|
||||
smr->upd_far_count++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_pfcp_mod_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg);
|
||||
|
||||
static void ps_rab_fsm_wait_pfcp_mod_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
/* We have been given the Access side's remote F-TEID, now in rab->access.remote, and we need to tell the UPF
|
||||
* about it. This affects the Core to Access direction: now we know where to forward payloads coming from Core.
|
||||
*/
|
||||
struct ps_rab *rab = fi->priv;
|
||||
struct osmo_pfcp_msg *m;
|
||||
|
||||
if (!(rab->up_f_seid.ip_addr.v4_present /* || rab->up_f_seid.ip_addr.v6_present */)) {
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "no valid PFCP session\n");
|
||||
ps_rab_failure(rab);
|
||||
return;
|
||||
}
|
||||
|
||||
m = ps_rab_new_pfcp_msg_req(rab, OSMO_PFCP_MSGT_SESSION_MOD_REQ);
|
||||
|
||||
if (rab_to_pfcp_session_mod_req_upd_far(&m->ies.session_mod_req, ID_CORE_TO_ACCESS, &rab->access.remote,
|
||||
g_hnbgw->config.pfcp.netinst.access)) {
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "error composing Update FAR IE in PFCP msg\n");
|
||||
ps_rab_failure(rab);
|
||||
return;
|
||||
}
|
||||
|
||||
m->ctx.resp_cb = on_pfcp_mod_resp;
|
||||
if (osmo_pfcp_endpoint_tx(g_hnbgw->pfcp.ep, m)) {
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "Failed to send PFCP message\n");
|
||||
ps_rab_failure(rab);
|
||||
}
|
||||
}
|
||||
|
||||
static int on_pfcp_mod_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg)
|
||||
{
|
||||
struct ps_rab *rab = req->ctx.session_fi->priv;
|
||||
|
||||
/* Send as FSM event to ensure this step is currently allowed */
|
||||
osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_PFCP_MOD_RESP, rx_resp);
|
||||
|
||||
/* By returning 0 here, the rx_resp message is not dispatched "again" to pfcp_ep->rx_msg(). We've handled it
|
||||
* here already. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ps_rab_rx_pfcp_mod_resp(struct osmo_fsm_inst *fi, struct osmo_pfcp_msg *rx);
|
||||
|
||||
static void ps_rab_fsm_wait_pfcp_mod_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
switch (event) {
|
||||
case PS_RAB_EV_PFCP_MOD_RESP:
|
||||
ps_rab_rx_pfcp_mod_resp(fi, data);
|
||||
return;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void ps_rab_rx_pfcp_mod_resp(struct osmo_fsm_inst *fi, struct osmo_pfcp_msg *rx)
|
||||
{
|
||||
struct ps_rab *rab = fi->priv;
|
||||
enum osmo_pfcp_cause *cause;
|
||||
|
||||
if (!rx) {
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "No response to PFCP Session Modification Request\n");
|
||||
ps_rab_failure(rab);
|
||||
return;
|
||||
}
|
||||
|
||||
cause = osmo_pfcp_msg_cause(rx);
|
||||
if (!cause || *cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) {
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "PFCP Session Modification Response was not successful: %s\n",
|
||||
cause ? osmo_pfcp_cause_str(*cause) : "NULL");
|
||||
ps_rab_failure(rab);
|
||||
return;
|
||||
}
|
||||
|
||||
/* This RAB is now complete. Everything went as expected, now we can forward the RAB Assignment Response to the
|
||||
* CN. */
|
||||
ps_rab_fsm_state_chg(PS_RAB_ST_ESTABLISHED);
|
||||
}
|
||||
|
||||
static void ps_rab_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct ps_rab *rab = fi->priv;
|
||||
if (rab->resp_fi)
|
||||
osmo_fsm_inst_dispatch(rab->resp_fi, PS_RAB_ASS_EV_RAB_ESTABLISHED, rab);
|
||||
}
|
||||
|
||||
static int on_pfcp_del_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg);
|
||||
|
||||
static void ps_rab_fsm_wait_pfcp_del_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
/* If a PFCP session has been established, send a Session Deletion Request and wait for the response.
|
||||
* If no session is established, just terminate. */
|
||||
struct ps_rab *rab = fi->priv;
|
||||
struct osmo_pfcp_msg *m;
|
||||
|
||||
if (!(rab->up_f_seid.ip_addr.v4_present /* || rab->up_f_seid.ip_addr.v6_present */)) {
|
||||
/* There is no valid PFCP session, so no need to send a Session Deletion Request */
|
||||
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_USE_COUNT);
|
||||
return;
|
||||
}
|
||||
|
||||
m = ps_rab_new_pfcp_msg_req(rab, OSMO_PFCP_MSGT_SESSION_DEL_REQ);
|
||||
m->ctx.resp_cb = on_pfcp_del_resp;
|
||||
if (osmo_pfcp_endpoint_tx(g_hnbgw->pfcp.ep, m)) {
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "Failed to send PFCP message\n");
|
||||
ps_rab_failure(rab);
|
||||
}
|
||||
}
|
||||
|
||||
static int on_pfcp_del_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg)
|
||||
{
|
||||
struct ps_rab *rab = req->ctx.session_fi->priv;
|
||||
if (errmsg)
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "PFCP Session Deletion Response: %s\n", errmsg);
|
||||
osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_PFCP_DEL_RESP, rx_resp);
|
||||
|
||||
/* By returning 0 here, the rx_resp message is not dispatched "again" to pfcp_ep->rx_msg(). We've handled it
|
||||
* here already. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ps_rab_fsm_wait_pfcp_del_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
switch (event) {
|
||||
case PS_RAB_EV_PFCP_DEL_RESP:
|
||||
/* All done, terminate. Even if the Session Deletion failed, there's nothing we can do about it. */
|
||||
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_USE_COUNT);
|
||||
return;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static int ps_rab_fsm_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line)
|
||||
{
|
||||
struct ps_rab *rab = e->use_count->talloc_object;
|
||||
if (!osmo_use_count_total(&rab->use_count))
|
||||
osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_USE_COUNT_ZERO, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ps_rab_fsm_wait_use_count_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct ps_rab *rab = fi->priv;
|
||||
if (rab->resp_fi)
|
||||
osmo_fsm_inst_dispatch(rab->resp_fi, PS_RAB_ASS_EV_RAB_RELEASED, rab);
|
||||
OSMO_ASSERT(osmo_use_count_get_put(&rab->use_count, PS_RAB_USE_ACTIVE, -1) == 0);
|
||||
}
|
||||
|
||||
static void ps_rab_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
switch (event) {
|
||||
|
||||
case PS_RAB_EV_USE_COUNT_ZERO:
|
||||
if (fi->state == PS_RAB_ST_WAIT_USE_COUNT)
|
||||
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
/* else, ignore. */
|
||||
return;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void ps_rab_forget_map(struct ps_rab *rab)
|
||||
{
|
||||
/* remove from map->ps_rab_list */
|
||||
if (rab->map)
|
||||
llist_del(&rab->entry);
|
||||
rab->map = NULL;
|
||||
}
|
||||
|
||||
static void ps_rab_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
||||
{
|
||||
struct ps_rab *rab = fi->priv;
|
||||
ps_rab_forget_map(rab);
|
||||
}
|
||||
|
||||
/* notify_fi can be NULL, in which case no event PS_RAB_ASS_EV_RAB_RELEASED event will be dispatched */
|
||||
void ps_rab_release(struct ps_rab *rab, struct osmo_fsm_inst *notify_fi)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = rab->fi;
|
||||
ps_rab_forget_map(rab);
|
||||
rab->resp_fi = notify_fi;
|
||||
switch (fi->state) {
|
||||
case PS_RAB_ST_RX_CORE_REMOTE_F_TEID:
|
||||
/* No session requested yet. Nothing to be deleted. */
|
||||
LOG_PS_RAB(rab, LOGL_NOTICE, "RAB release before PFCP Session Establishment Request, terminating\n");
|
||||
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_USE_COUNT);
|
||||
return;
|
||||
case PS_RAB_ST_WAIT_PFCP_EST_RESP:
|
||||
/* Session was requested via PFCP, but we only know the SEID to send in a deletion when the PFCP Session
|
||||
* Establishment Response arrives. */
|
||||
rab->release_requested = true;
|
||||
LOG_PS_RAB(rab, LOGL_ERROR, "RAB release while waiting for PFCP Session Establishment Response\n");
|
||||
return;
|
||||
default:
|
||||
/* Session has been established (and we know the SEID). Initiate deletion. */
|
||||
LOG_PS_RAB(rab, LOGL_INFO, "RAB release, deleting PFCP session\n");
|
||||
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_PFCP_DEL_RESP);
|
||||
return;
|
||||
case PS_RAB_ST_WAIT_PFCP_DEL_RESP:
|
||||
/* Already requested a PFCP Session Deletion. Nothing else to do, wait for the Deletion Response (or
|
||||
* timeout). */
|
||||
LOG_PS_RAB(rab, LOGL_INFO, "RAB release while waiting for PFCP Session Deletion Response\n");
|
||||
return;
|
||||
case PS_RAB_ST_WAIT_USE_COUNT:
|
||||
/* Already released, just wait for the last users (queued PFCP messages) to expire. */
|
||||
LOG_PS_RAB(rab, LOGL_INFO, "RAB release, already waiting for deallocation\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#define S(x) (1 << (x))
|
||||
|
||||
static const struct osmo_fsm_state ps_rab_fsm_states[] = {
|
||||
[PS_RAB_ST_RX_CORE_REMOTE_F_TEID] = {
|
||||
.name = "RX_CORE_REMOTE_F_TEID",
|
||||
.out_state_mask = 0
|
||||
| S(PS_RAB_ST_WAIT_PFCP_EST_RESP)
|
||||
| S(PS_RAB_ST_WAIT_USE_COUNT)
|
||||
,
|
||||
},
|
||||
[PS_RAB_ST_WAIT_PFCP_EST_RESP] = {
|
||||
.name = "WAIT_PFCP_EST_RESP",
|
||||
.onenter = ps_rab_fsm_wait_pfcp_est_resp_onenter,
|
||||
.action = ps_rab_fsm_wait_pfcp_est_resp,
|
||||
.in_event_mask = 0
|
||||
| S(PS_RAB_EV_PFCP_EST_RESP)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID)
|
||||
| S(PS_RAB_ST_WAIT_USE_COUNT)
|
||||
,
|
||||
},
|
||||
[PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID] = {
|
||||
.name = "WAIT_ACCESS_REMOTE_F_TEID",
|
||||
.action = ps_rab_fsm_wait_access_remote_f_teid,
|
||||
.in_event_mask = 0
|
||||
| S(PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(PS_RAB_ST_WAIT_PFCP_MOD_RESP)
|
||||
| S(PS_RAB_ST_WAIT_PFCP_DEL_RESP)
|
||||
| S(PS_RAB_ST_WAIT_USE_COUNT)
|
||||
,
|
||||
},
|
||||
[PS_RAB_ST_WAIT_PFCP_MOD_RESP] = {
|
||||
.name = "WAIT_PFCP_MOD_RESP",
|
||||
.onenter = ps_rab_fsm_wait_pfcp_mod_resp_onenter,
|
||||
.action = ps_rab_fsm_wait_pfcp_mod_resp,
|
||||
.in_event_mask = 0
|
||||
| S(PS_RAB_EV_PFCP_MOD_RESP)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(PS_RAB_ST_ESTABLISHED)
|
||||
| S(PS_RAB_ST_WAIT_PFCP_DEL_RESP)
|
||||
| S(PS_RAB_ST_WAIT_USE_COUNT)
|
||||
,
|
||||
},
|
||||
[PS_RAB_ST_ESTABLISHED] = {
|
||||
.name = "ESTABLISHED",
|
||||
.onenter = ps_rab_fsm_established_onenter,
|
||||
.out_state_mask = 0
|
||||
| S(PS_RAB_ST_WAIT_PFCP_DEL_RESP)
|
||||
| S(PS_RAB_ST_WAIT_USE_COUNT)
|
||||
,
|
||||
},
|
||||
[PS_RAB_ST_WAIT_PFCP_DEL_RESP] = {
|
||||
.name = "WAIT_PFCP_DEL_RESP",
|
||||
.onenter = ps_rab_fsm_wait_pfcp_del_resp_onenter,
|
||||
.action = ps_rab_fsm_wait_pfcp_del_resp,
|
||||
.in_event_mask = 0
|
||||
| S(PS_RAB_EV_PFCP_DEL_RESP)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(PS_RAB_ST_WAIT_USE_COUNT)
|
||||
,
|
||||
},
|
||||
[PS_RAB_ST_WAIT_USE_COUNT] = {
|
||||
.name = "WAIT_USE_COUNT",
|
||||
.onenter = ps_rab_fsm_wait_use_count_onenter,
|
||||
.in_event_mask = 0
|
||||
| S(PS_RAB_EV_USE_COUNT_ZERO)
|
||||
,
|
||||
},
|
||||
};
|
||||
|
||||
static struct osmo_fsm ps_rab_fsm = {
|
||||
.name = "ps_rab",
|
||||
.states = ps_rab_fsm_states,
|
||||
.num_states = ARRAY_SIZE(ps_rab_fsm_states),
|
||||
.log_subsys = DLPFCP,
|
||||
.event_names = ps_rab_fsm_event_names,
|
||||
.cleanup = ps_rab_fsm_cleanup,
|
||||
.allstate_event_mask = S(PS_RAB_EV_USE_COUNT_ZERO),
|
||||
.allstate_action = ps_rab_fsm_allstate_action,
|
||||
};
|
||||
|
||||
static __attribute__((constructor)) void ps_rab_fsm_register(void)
|
||||
{
|
||||
OSMO_ASSERT(osmo_fsm_register(&ps_rab_fsm) == 0);
|
||||
}
|
||||
625
src/osmo-hnbgw/ranap_rab_ass.c
Normal file
625
src/osmo-hnbgw/ranap_rab_ass.c
Normal file
@@ -0,0 +1,625 @@
|
||||
/* (C) 2021 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Philipp Maier
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* Note: This files contains tools to decode and re-encode the RAB-AssignmentRequest. This set of tools is used by
|
||||
* mgcp_fsm.c to extract and manipulate the transportLayerAddress. */
|
||||
|
||||
#include <errno.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
#include <osmocom/ranap/ranap_common.h>
|
||||
#include <osmocom/ranap/ranap_common_cn.h>
|
||||
#include <osmocom/ranap/ranap_common_ran.h>
|
||||
#include <osmocom/ranap/iu_helpers.h>
|
||||
#include <asn1c/asn1helpers.h>
|
||||
|
||||
/*! Encode RABAP RAB AssignmentRequest from RANAP_RAB_AssignmentRequestIEs.
|
||||
* \ptmap[out] data user provided memory to store resulting ASN.1 encoded message.
|
||||
* \ptmap[in] len length of user provided memory to store resulting ASN.1 encoded message.
|
||||
* \ptmap[in] ies user provided memory with RANAP_RAB_AssignmentRequestIEs.
|
||||
* \returns resulting message length on success; negative on error. */
|
||||
struct msgb *ranap_rab_ass_req_encode(RANAP_RAB_AssignmentRequestIEs_t *rab_assignment_request_ies)
|
||||
{
|
||||
int rc;
|
||||
struct msgb *msg;
|
||||
RANAP_RAB_AssignmentRequest_t _rab_assignment_request = { 0 };
|
||||
RANAP_RAB_AssignmentRequest_t *rab_assignment_request = &_rab_assignment_request;
|
||||
|
||||
rc = ranap_encode_rab_assignmentrequesties(rab_assignment_request, rab_assignment_request_ies);
|
||||
if (rc < 0)
|
||||
return NULL;
|
||||
|
||||
/* generate an Initiating Mesasage */
|
||||
msg = ranap_generate_initiating_message(RANAP_ProcedureCode_id_RAB_Assignment,
|
||||
RANAP_Criticality_reject,
|
||||
&asn_DEF_RANAP_RAB_AssignmentRequest, rab_assignment_request);
|
||||
|
||||
/* 'msg' has been generated, we cann now release the input 'out' */
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_AssignmentRequest, rab_assignment_request);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/*! Encode RABAP RAB AssignmentRequest from RANAP_RAB_AssignmentResponseIEs.
|
||||
* \ptmap[out] data user provided memory to store resulting ASN.1 encoded message.
|
||||
* \ptmap[in] len length of user provided memory to store resulting ASN.1 encoded message.
|
||||
* \ptmap[in] ies user provided memory with RANAP_RAB_AssignmentResponseIEs.
|
||||
* \returns resulting message length on success; negative on error. */
|
||||
int ranap_rab_ass_resp_encode(uint8_t *data, unsigned int len,
|
||||
RANAP_RAB_AssignmentResponseIEs_t *rab_assignment_response_ies)
|
||||
{
|
||||
int rc;
|
||||
struct msgb *msg;
|
||||
|
||||
RANAP_RAB_AssignmentResponse_t _rab_assignment_response = { 0 };
|
||||
RANAP_RAB_AssignmentResponse_t *rab_assignment_response = &_rab_assignment_response;
|
||||
|
||||
memset(data, 0, len);
|
||||
|
||||
rc = ranap_encode_rab_assignmentresponseies(rab_assignment_response, rab_assignment_response_ies);
|
||||
if (rc < 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* generate an outcome mesasage */
|
||||
msg = ranap_generate_outcome(RANAP_ProcedureCode_id_RAB_Assignment,
|
||||
RANAP_Criticality_reject,
|
||||
&asn_DEF_RANAP_RAB_AssignmentResponse, rab_assignment_response);
|
||||
|
||||
/* 'msg' has been generated, we can now release the input 'rab_assignment_response' */
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_AssignmentResponse, rab_assignment_response);
|
||||
|
||||
if (!msg)
|
||||
return -EINVAL;
|
||||
if (msg->len > len)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(data, msg->data, msg->len);
|
||||
rc = msg->len;
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Pick the indexed item from the RAB setup-or-modify list and return the first protocol-ie-field-pair. */
|
||||
static RANAP_ProtocolIE_FieldPair_t *prot_ie_field_pair_from_ass_req_ies(const RANAP_RAB_AssignmentRequestIEs_t *ies,
|
||||
unsigned int index)
|
||||
{
|
||||
RANAP_ProtocolIE_ContainerPair_t *protocol_ie_container_pair;
|
||||
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
|
||||
|
||||
/* Make sure we indeed deal with a setup-or-modify list */
|
||||
if (!(ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT)) {
|
||||
RANAP_DEBUG
|
||||
("Decoding failed, the RANAP RAB AssignmentRequest did not contain a setup-or-modify list!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Detect the end of the list */
|
||||
if (index >= ies->raB_SetupOrModifyList.list.count)
|
||||
return NULL;
|
||||
|
||||
protocol_ie_container_pair = ies->raB_SetupOrModifyList.list.array[index];
|
||||
protocol_ie_field_pair = protocol_ie_container_pair->list.array[0];
|
||||
|
||||
return protocol_ie_field_pair;
|
||||
}
|
||||
|
||||
/* Pick the indexed item from the RAB release-list list and return a pointer to it */
|
||||
static RANAP_IE_t *release_item_from_ass_req_ies(const RANAP_RAB_AssignmentRequestIEs_t *ies, unsigned int index)
|
||||
{
|
||||
/* Make sure we indeed deal with a setup-or-modify list */
|
||||
if (!(ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_RELEASELIST_PRESENT)) {
|
||||
RANAP_DEBUG
|
||||
("Decoding failed, the RANAP RAB AssignmentRequest did not contain a release list!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Detect the end of the list */
|
||||
if (index >= ies->raB_ReleaseList.raB_ReleaseList_ies.list.count)
|
||||
return NULL;
|
||||
|
||||
return ies->raB_ReleaseList.raB_ReleaseList_ies.list.array[index];
|
||||
}
|
||||
|
||||
/* Pick the indexed item from the RAB setup-or-modified list and return a pointer to it */
|
||||
static RANAP_IE_t *setup_or_modif_item_from_rab_ass_resp(const RANAP_RAB_AssignmentResponseIEs_t *ies,
|
||||
unsigned int index)
|
||||
{
|
||||
/* Make sure we indeed deal with a setup-or-modified list */
|
||||
if (!(ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_SETUPORMODIFIEDLIST_PRESENT)) {
|
||||
RANAP_DEBUG("RANAP RAB AssignmentResponse did not contain a setup-or-modified list!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Detect the end of the list */
|
||||
if (index >= ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.count)
|
||||
return NULL;
|
||||
|
||||
return ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.array[index];
|
||||
}
|
||||
|
||||
/* Pick the indexed item from the RAB failed list and return a pointer to it */
|
||||
static RANAP_IE_t *failed_list_item_from_rab_ass_resp(const RANAP_RAB_AssignmentResponseIEs_t *ies,
|
||||
unsigned int index)
|
||||
{
|
||||
/* Make sure we indeed deal with a failed list */
|
||||
if (!(ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_FAILEDLIST_PRESENT)) {
|
||||
RANAP_DEBUG("RANAP RAB AssignmentResponse did not contain a failed list!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Detect the end of the list */
|
||||
if (index >= ies->raB_FailedList.raB_FailedList_ies.list.count)
|
||||
return NULL;
|
||||
|
||||
return ies->raB_FailedList.raB_FailedList_ies.list.array[index];
|
||||
}
|
||||
|
||||
/* Find the RAB specified by rab_id in ies, decode the result into items_ies and return a positive index.
|
||||
* The caller is responsible for freeing the contents in items_ies. In case of failure, the return code
|
||||
* will be negative. */
|
||||
static int decode_rab_smditms_from_resp_ies(RANAP_RAB_SetupOrModifiedItemIEs_t *items_ies,
|
||||
RANAP_RAB_AssignmentResponseIEs_t *ies, uint8_t rab_id)
|
||||
{
|
||||
RANAP_IE_t *setup_or_modified_list_ie;
|
||||
RANAP_RAB_SetupOrModifiedItem_t *rab_setup_or_modified_item;
|
||||
int rc;
|
||||
uint8_t rab_id_decoded;
|
||||
unsigned int index = 0;
|
||||
|
||||
while (1) {
|
||||
setup_or_modified_list_ie = setup_or_modif_item_from_rab_ass_resp(ies, index);
|
||||
if (!setup_or_modified_list_ie)
|
||||
return -EINVAL;
|
||||
|
||||
rc = ranap_decode_rab_setupormodifieditemies_fromlist(items_ies, &setup_or_modified_list_ie->value);
|
||||
if (rc < 0)
|
||||
return -EINVAL;
|
||||
|
||||
rab_setup_or_modified_item = &items_ies->raB_SetupOrModifiedItem;
|
||||
/* The RAB-ID is defined as a bitstring with a size of 8 (1 byte),
|
||||
* See also RANAP-IEs.asn, RAB-ID ::= BIT STRING (SIZE (8)) */
|
||||
rab_id_decoded = rab_setup_or_modified_item->rAB_ID.buf[0];
|
||||
if (rab_id_decoded == rab_id)
|
||||
return index;
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, items_ies);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
/* See comment above decode_rab_smditms_from_resp_ies() */
|
||||
static int decode_rab_flitms_from_resp_ies(RANAP_RAB_FailedItemIEs_t *items_ies,
|
||||
RANAP_RAB_AssignmentResponseIEs_t *ies, uint8_t rab_id)
|
||||
{
|
||||
RANAP_IE_t *failed_list_ie;
|
||||
RANAP_RAB_FailedItem_t *rab_failed_item;
|
||||
int rc;
|
||||
uint8_t rab_id_decoded;
|
||||
unsigned int index = 0;
|
||||
|
||||
while (1) {
|
||||
failed_list_ie = failed_list_item_from_rab_ass_resp(ies, index);
|
||||
if (!failed_list_ie)
|
||||
return -EINVAL;
|
||||
|
||||
rc = ranap_decode_rab_faileditemies_fromlist(items_ies, &failed_list_ie->value);
|
||||
if (rc < 0)
|
||||
return -EINVAL;
|
||||
|
||||
rab_failed_item = &items_ies->raB_FailedItem;
|
||||
/* The RAB-ID is defined as a bitstring with a size of 8 (1 byte),
|
||||
* See also RANAP-IEs.asn, RAB-ID ::= BIT STRING (SIZE (8)) */
|
||||
rab_id_decoded = rab_failed_item->rAB_ID.buf[0];
|
||||
if (rab_id_decoded == rab_id)
|
||||
return index;
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_FailedItem, items_ies);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
/* find the RAB specified by rab_id in ies and when found, decode the result into item */
|
||||
static int decode_rab_smditms_from_req_ies(RANAP_RAB_SetupOrModifyItemFirst_t *item,
|
||||
RANAP_RAB_AssignmentRequestIEs_t *ies, uint8_t rab_id)
|
||||
{
|
||||
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
|
||||
int rc;
|
||||
uint8_t rab_id_decoded;
|
||||
unsigned int index = 0;
|
||||
|
||||
while (1) {
|
||||
protocol_ie_field_pair = prot_ie_field_pair_from_ass_req_ies(ies, index);
|
||||
if (!protocol_ie_field_pair)
|
||||
return -EINVAL;
|
||||
|
||||
if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem) {
|
||||
RANAP_DEBUG
|
||||
("Decoding failed, the protocol IE field-pair is not of type RANAP RAB setup-or-modify-item!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = ranap_decode_rab_setupormodifyitemfirst(item, &protocol_ie_field_pair->firstValue);
|
||||
if (rc < 0)
|
||||
return -EINVAL;
|
||||
|
||||
rab_id_decoded = item->rAB_ID.buf[0];
|
||||
if (rab_id_decoded == rab_id)
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
static int decode_rab_relitms_from_req_ies(RANAP_RAB_ReleaseItemIEs_t *items_ies,
|
||||
RANAP_RAB_AssignmentRequestIEs_t *ies, uint8_t rab_id)
|
||||
{
|
||||
RANAP_IE_t *release_list_ie;
|
||||
RANAP_RAB_ReleaseItem_t *rab_release_item;
|
||||
int rc;
|
||||
uint8_t rab_id_decoded;
|
||||
unsigned int index = 0;
|
||||
|
||||
while (1) {
|
||||
release_list_ie = release_item_from_ass_req_ies(ies, index);
|
||||
if (!release_list_ie)
|
||||
return -EINVAL;
|
||||
|
||||
if (release_list_ie->id != RANAP_ProtocolIE_ID_id_RAB_ReleaseItem) {
|
||||
RANAP_DEBUG("Decoding failed, the protocol IE is not of type RANAP RAB ReleaseItem!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = ranap_decode_rab_releaseitemies_fromlist(items_ies, &release_list_ie->value);
|
||||
if (rc < 0)
|
||||
return -EINVAL;
|
||||
|
||||
rab_release_item = &items_ies->raB_ReleaseItem;
|
||||
/* The RAB-ID is defined as a bitstring with a size of 8 (1 byte),
|
||||
* See also RANAP-IEs.asn, RAB-ID ::= BIT STRING (SIZE (8)) */
|
||||
rab_id_decoded = rab_release_item->rAB_ID.buf[0];
|
||||
if (rab_id_decoded == rab_id)
|
||||
return index;
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_ReleaseItem, items_ies);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Extract IP address and port from RANAP_RAB_AssignmentRequestIEs.
|
||||
* \ptmap[out] addr user provided memory to store extracted RTP stream IP-Address and port number.
|
||||
* \ptmap[out] rab_id pointer to store RAB-ID (optional, can be NULL).
|
||||
* \ptmap[in] ies user provided memory with RANAP_RAB_AssignmentRequestIEs.
|
||||
* \ptmap[in] index index of the SetupOrModifyItem (e.g. 0 for the first list item).
|
||||
* \returns 0 on success; negative on error. */
|
||||
int ranap_rab_ass_req_ies_extract_inet_addr(struct osmo_sockaddr *addr, uint8_t *rab_id,
|
||||
RANAP_RAB_AssignmentRequestIEs_t *ies, unsigned int index)
|
||||
{
|
||||
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
|
||||
RANAP_RAB_SetupOrModifyItemFirst_t _rab_setup_or_modify_item_first = { 0 };
|
||||
RANAP_RAB_SetupOrModifyItemFirst_t *rab_setup_or_modify_item_first = &_rab_setup_or_modify_item_first;
|
||||
RANAP_TransportLayerAddress_t *trasp_layer_addr;
|
||||
RANAP_IuTransportAssociation_t *transp_assoc;
|
||||
uint16_t port;
|
||||
int rc;
|
||||
|
||||
protocol_ie_field_pair = prot_ie_field_pair_from_ass_req_ies(ies, index);
|
||||
if (!protocol_ie_field_pair)
|
||||
return -EINVAL;
|
||||
|
||||
if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem) {
|
||||
RANAP_DEBUG
|
||||
("Decoding failed, the protocol IE field-pair is not of type RANAP RAB setup-or-modify-item!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = ranap_decode_rab_setupormodifyitemfirst(rab_setup_or_modify_item_first,
|
||||
&protocol_ie_field_pair->firstValue);
|
||||
if (rc < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (rab_id) {
|
||||
/* The RAB-ID is defined as a bitstring with a size of 8 (1 byte),
|
||||
* See also RANAP-IEs.asn, RAB-ID ::= BIT STRING (SIZE (8)) */
|
||||
*rab_id = rab_setup_or_modify_item_first->rAB_ID.buf[0];
|
||||
}
|
||||
|
||||
/* Decode IP-Address */
|
||||
trasp_layer_addr = &rab_setup_or_modify_item_first->transportLayerInformation->transportLayerAddress;
|
||||
rc = ranap_transp_layer_addr_decode2(addr, NULL, trasp_layer_addr);
|
||||
if (rc < 0) {
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Decode port number */
|
||||
transp_assoc = &rab_setup_or_modify_item_first->transportLayerInformation->iuTransportAssociation;
|
||||
rc = ranap_transp_assoc_decode(&port, transp_assoc);
|
||||
if (rc < 0) {
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
switch (addr->u.sin.sin_family) {
|
||||
case AF_INET:
|
||||
addr->u.sin.sin_port = htons(port);
|
||||
break;
|
||||
case AF_INET6:
|
||||
addr->u.sin6.sin6_port = htons(port);
|
||||
break;
|
||||
default:
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
error:
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, rab_setup_or_modify_item_first);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*! Extract IP address and port from RANAP_RAB_AssignmentResponseIEs.
|
||||
* \ptmap[out] addr user provided memory to store extracted RTP stream IP-Address and port number.
|
||||
* \ptmap[in] ies user provided memory with RANAP_RAB_AssignmentResponseIEs.
|
||||
* \ptmap[in] rab_id expected rab id to look for.
|
||||
* \returns 0 on success; negative on error. */
|
||||
int ranap_rab_ass_resp_ies_extract_inet_addr(struct osmo_sockaddr *addr, RANAP_RAB_AssignmentResponseIEs_t *ies, uint8_t rab_id)
|
||||
{
|
||||
RANAP_RAB_SetupOrModifiedItemIEs_t _rab_setup_or_modified_items_ies = { 0 };
|
||||
RANAP_RAB_SetupOrModifiedItemIEs_t *rab_setup_or_modified_items_ies = &_rab_setup_or_modified_items_ies;
|
||||
RANAP_RAB_SetupOrModifiedItem_t *rab_setup_or_modified_item;
|
||||
uint16_t port;
|
||||
int rc;
|
||||
|
||||
rc = decode_rab_smditms_from_resp_ies(rab_setup_or_modified_items_ies, ies, rab_id);
|
||||
if (rc < 0)
|
||||
return -EINVAL;
|
||||
|
||||
rab_setup_or_modified_item = &rab_setup_or_modified_items_ies->raB_SetupOrModifiedItem;
|
||||
|
||||
/* Decode IP-Address */
|
||||
rc = ranap_transp_layer_addr_decode2(addr, NULL, rab_setup_or_modified_item->transportLayerAddress);
|
||||
if (rc < 0) {
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Decode port number */
|
||||
rc = ranap_transp_assoc_decode(&port, rab_setup_or_modified_item->iuTransportAssociation);
|
||||
if (rc < 0) {
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
switch (addr->u.sin.sin_family) {
|
||||
case AF_INET:
|
||||
addr->u.sin.sin_port = htons(port);
|
||||
break;
|
||||
case AF_INET6:
|
||||
addr->u.sin6.sin6_port = htons(port);
|
||||
break;
|
||||
default:
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
error:
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, rab_setup_or_modified_items_ies);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*! Replace IP address and port in RANAP_RAB_AssignmentRequestIEs.
|
||||
* \ptmap[inout] ies user provided memory with RANAP_RAB_AssignmentRequestIEs.
|
||||
* \ptmap[in] addr user provided memory that contains the new RTP stream IP-Address and port number.
|
||||
* \ptmap[in] rab_id expected rab id to look for.
|
||||
* \returns 0 on success; negative on error. */
|
||||
int ranap_rab_ass_req_ies_replace_inet_addr(RANAP_RAB_AssignmentRequestIEs_t *ies, struct osmo_sockaddr *addr, uint8_t rab_id)
|
||||
{
|
||||
RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair;
|
||||
RANAP_RAB_SetupOrModifyItemFirst_t _rab_setup_or_modify_item_first = { 0 };
|
||||
RANAP_RAB_SetupOrModifyItemFirst_t *rab_setup_or_modify_item_first = &_rab_setup_or_modify_item_first;
|
||||
RANAP_TransportLayerInformation_t *old_transport_layer_information = NULL;
|
||||
RANAP_TransportLayerInformation_t *new_transport_layer_information = NULL;
|
||||
struct osmo_sockaddr addr_old;
|
||||
bool uses_x213_nsap;
|
||||
int rc;
|
||||
int index;
|
||||
|
||||
index = decode_rab_smditms_from_req_ies(rab_setup_or_modify_item_first, ies, rab_id);
|
||||
if (index < 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Replace transport-layer-information */
|
||||
if (rab_setup_or_modify_item_first->transportLayerInformation->iuTransportAssociation.present ==
|
||||
RANAP_IuTransportAssociation_PR_bindingID) {
|
||||
old_transport_layer_information = rab_setup_or_modify_item_first->transportLayerInformation;
|
||||
|
||||
/* Before we can re-encode the transport layer information, we need to know the format it was
|
||||
* encoded in. */
|
||||
rc = ranap_transp_layer_addr_decode2(&addr_old, &uses_x213_nsap,
|
||||
&old_transport_layer_information->transportLayerAddress);
|
||||
if (rc < 0) {
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Encode a new transport layer information field */
|
||||
new_transport_layer_information = ranap_new_transp_info_rtp(addr, uses_x213_nsap);
|
||||
if (!new_transport_layer_information) {
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
rab_setup_or_modify_item_first->transportLayerInformation = new_transport_layer_information;
|
||||
} else {
|
||||
RANAP_DEBUG("Rewriting transport layer information failed, no bindingID (port)!\n");
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Reencode transport-layer-information */
|
||||
protocol_ie_field_pair = prot_ie_field_pair_from_ass_req_ies(ies, index);
|
||||
rc = ANY_fromType_aper(&protocol_ie_field_pair->firstValue, &asn_DEF_RANAP_RAB_SetupOrModifyItemFirst,
|
||||
rab_setup_or_modify_item_first);
|
||||
if (rc < 0) {
|
||||
RANAP_DEBUG("Rewriting transport layer information failed, could not reencode\n");
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
error:
|
||||
/* Restore original state of the modified ASN.1 struct so that the asn1c free mechanisms can work properly */
|
||||
if (old_transport_layer_information)
|
||||
rab_setup_or_modify_item_first->transportLayerInformation = old_transport_layer_information;
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, rab_setup_or_modify_item_first);
|
||||
if (new_transport_layer_information)
|
||||
ASN_STRUCT_FREE(asn_DEF_RANAP_TransportLayerInformation, new_transport_layer_information);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*! Replace IP address and port in RANAP_RAB_AssignmentResponseIEs.
|
||||
* \ptmap[inout] ies user provided memory with RANAP_RAB_AssignmentResponseIEs.
|
||||
* \ptmap[in] addr user provided memory that contains the new RTP stream IP-Address and port number.
|
||||
* \ptmap[in] rab_id expected rab id to look for.
|
||||
* \returns 0 on success; negative on error. */
|
||||
int ranap_rab_ass_resp_ies_replace_inet_addr(RANAP_RAB_AssignmentResponseIEs_t *ies, struct osmo_sockaddr *addr, uint8_t rab_id)
|
||||
{
|
||||
RANAP_IE_t *setup_or_modified_list_ie;
|
||||
RANAP_RAB_SetupOrModifiedItemIEs_t _rab_setup_or_modified_items_ies = { 0 };
|
||||
RANAP_RAB_SetupOrModifiedItemIEs_t *rab_setup_or_modified_items_ies = &_rab_setup_or_modified_items_ies;
|
||||
RANAP_RAB_SetupOrModifiedItem_t *rab_setup_or_modified_item;
|
||||
RANAP_TransportLayerInformation_t *temp_transport_layer_information = NULL;
|
||||
RANAP_TransportLayerAddress_t *old_transport_layer_address = NULL;
|
||||
RANAP_IuTransportAssociation_t *old_iu_transport_association = NULL;
|
||||
struct osmo_sockaddr addr_old;
|
||||
bool uses_x213_nsap;
|
||||
int rc;
|
||||
int index;
|
||||
|
||||
index = decode_rab_smditms_from_resp_ies(rab_setup_or_modified_items_ies, ies, rab_id);
|
||||
if (index < 0)
|
||||
return -EINVAL;
|
||||
|
||||
rab_setup_or_modified_item = &rab_setup_or_modified_items_ies->raB_SetupOrModifiedItem;
|
||||
|
||||
/* Before we can re-encode the transport layer address, we need to know the format it was encoded in. */
|
||||
rc = ranap_transp_layer_addr_decode2(&addr_old, &uses_x213_nsap,
|
||||
rab_setup_or_modified_item->transportLayerAddress);
|
||||
if (rc < 0) {
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Generate a temporary transport layer information, from which we can use the transport layer address and
|
||||
* the iu transport association to update the setup or modified item */
|
||||
temp_transport_layer_information = ranap_new_transp_info_rtp(addr, uses_x213_nsap);
|
||||
if (!temp_transport_layer_information) {
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Replace transport layer address and iu transport association */
|
||||
old_transport_layer_address = rab_setup_or_modified_item->transportLayerAddress;
|
||||
old_iu_transport_association = rab_setup_or_modified_item->iuTransportAssociation;
|
||||
rab_setup_or_modified_item->transportLayerAddress = &temp_transport_layer_information->transportLayerAddress;
|
||||
rab_setup_or_modified_item->iuTransportAssociation = &temp_transport_layer_information->iuTransportAssociation;
|
||||
|
||||
/* Reencode modified setup or modified list */
|
||||
setup_or_modified_list_ie = setup_or_modif_item_from_rab_ass_resp(ies, index);
|
||||
rc = ANY_fromType_aper(&setup_or_modified_list_ie->value, &asn_DEF_RANAP_RAB_SetupOrModifiedItem,
|
||||
rab_setup_or_modified_items_ies);
|
||||
if (rc < 0) {
|
||||
RANAP_DEBUG("Rewriting transport layer address failed, could not reencode\n");
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
error:
|
||||
/* Restore original state of the modified ASN.1 struct so that the asn1c free mechanisms can work properly */
|
||||
if (old_transport_layer_address)
|
||||
rab_setup_or_modified_item->transportLayerAddress = old_transport_layer_address;
|
||||
if (old_iu_transport_association)
|
||||
rab_setup_or_modified_item->iuTransportAssociation = old_iu_transport_association;
|
||||
if (temp_transport_layer_information)
|
||||
ASN_STRUCT_FREE(asn_DEF_RANAP_TransportLayerInformation, temp_transport_layer_information);
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, rab_setup_or_modified_items_ies);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*! Check if a specific RAB is present in an RAB-Failed-Item-List inside RANAP_RAB_AssignmentResponseIEs.
|
||||
* \ptmap[in] ies user provided memory with RANAP_RAB_AssignmentResponseIEs.
|
||||
* \ptmap[in] rab_id expected rab id to look for.
|
||||
* \returns true when RAB could be identified as failed; false otherwise */
|
||||
bool ranap_rab_ass_resp_ies_check_failure(RANAP_RAB_AssignmentResponseIEs_t *ies, uint8_t rab_id)
|
||||
{
|
||||
RANAP_RAB_FailedItemIEs_t _rab_failed_items_ies = { 0 };
|
||||
RANAP_RAB_FailedItemIEs_t *rab_failed_items_ies = &_rab_failed_items_ies;
|
||||
int rc;
|
||||
|
||||
/* If we can get a failed item (rc >= 0) for the specified RAB ID, then we know that the
|
||||
* HNB reported the RAB Assignment as failed */
|
||||
rc = decode_rab_flitms_from_resp_ies(rab_failed_items_ies, ies, rab_id);
|
||||
if (rc < 0)
|
||||
return false;
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_FailedItem, rab_failed_items_ies);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*! Check if a specific RAB is present in an RAB-ReleaseList inside RANAP_RAB_AssignmentRequestIEs.
|
||||
* \ptmap[in] ies user provided memory with RANAP_RAB_AssignmentRequestIEs.
|
||||
* \ptmap[in] rab_id expected rab id to look for.
|
||||
* \returns true when RAB is intended for release; false otherwise */
|
||||
bool ranap_rab_ass_req_ies_check_release(RANAP_RAB_AssignmentRequestIEs_t *ies, uint8_t rab_id)
|
||||
{
|
||||
RANAP_RAB_ReleaseItemIEs_t _rab_release_items_ies = { 0 };
|
||||
RANAP_RAB_ReleaseItemIEs_t *rab_release_items_ies = &_rab_release_items_ies;
|
||||
int rc;
|
||||
bool result = true;
|
||||
|
||||
/* If we can get a rlease list item for the specified RAB ID, then we know that the
|
||||
* MSC intends to release the specified RAB */
|
||||
rc = decode_rab_relitms_from_req_ies(rab_release_items_ies, ies, rab_id);
|
||||
if (rc < 0)
|
||||
result = false;
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_ReleaseItem, rab_release_items_ies);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*! Find out how many RAB items are present in a RAB-SetupOrModifyList inside RANAP_RAB_AssignmentRequestIEs.
|
||||
* \ptmap[in] ies user provided memory with RANAP_RAB_AssignmentRequestIEs.
|
||||
* \returns number of RAB items, -1 on failure. */
|
||||
int ranap_rab_ass_req_ies_get_count(RANAP_RAB_AssignmentRequestIEs_t *ies)
|
||||
{
|
||||
/* Make sure we indeed deal with a setup-or-modify list */
|
||||
if (!(ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT)) {
|
||||
RANAP_DEBUG
|
||||
("Decoding failed, the RANAP RAB AssignmentRequest did not contain a setup-or-modify list!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ies->raB_SetupOrModifyList.list.count;
|
||||
}
|
||||
56
src/osmo-hnbgw/tdefs.c
Normal file
56
src/osmo-hnbgw/tdefs.c
Normal file
@@ -0,0 +1,56 @@
|
||||
/* (C) 2021 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Philipp Maier
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <osmocom/hnbgw/tdefs.h>
|
||||
|
||||
#if ENABLE_PFCP
|
||||
#include <osmocom/pfcp/pfcp_endpoint.h>
|
||||
#endif
|
||||
|
||||
struct osmo_tdef mgw_fsm_T_defs[] = {
|
||||
{.T = -1001, .default_val = 5, .desc = "Timeout for HNB side call-leg (to-HNB) creation" },
|
||||
{.T = -1002, .default_val = 10, .desc = "Timeout for the HNB to respond to RAB Assignment Request" },
|
||||
{.T = -1003, .default_val = 5, .desc = "Timeout for HNB side call-leg (to-HNB) completion" },
|
||||
{.T = -1004, .default_val = 5, .desc = "Timeout for MSC side call-leg (to-MSC) completion" },
|
||||
{.T = -2427, .default_val = 5, .desc = "timeout for MGCP response from MGW" },
|
||||
{ }
|
||||
};
|
||||
|
||||
struct osmo_tdef hnbgw_T_defs[] = {
|
||||
{.T = 3113, .default_val = 15, .desc = "Time to keep Paging record, for CN pools with more than one link" },
|
||||
{.T = 4, .default_val = 5, .desc = "Timeout to receive RANAP RESET ACKNOWLEDGE from an MSC/SGSN" },
|
||||
{.T = -31, .default_val = 15, .desc = "Timeout for establishing and releasing context maps (RUA <-> SCCP)" },
|
||||
{.T = -34, .default_val = 1000, .unit = OSMO_TDEF_MS, .desc = "Period to query network traffic stats from netfilter" },
|
||||
{
|
||||
.T = -35,
|
||||
.default_val = 60*60*24*7,
|
||||
.desc = "Clean up all hNodeB persistent state after this time of the hNodeB being disconnected."
|
||||
" Set to zero to never clear hNodeB persistent state. (default is 60*60*24*27 = a week)",
|
||||
},
|
||||
{.T = -1002, .default_val = 10, .desc = "Timeout for the HNB to respond to PS RAB Assignment Request" },
|
||||
{ }
|
||||
};
|
||||
|
||||
struct osmo_tdef_group hnbgw_tdef_group[] = {
|
||||
{.name = "mgw", .tdefs = mgw_fsm_T_defs, .desc = "MGW (Media Gateway) interface" },
|
||||
{.name = "hnbgw", .tdefs = hnbgw_T_defs, .desc = "Timers" },
|
||||
#if ENABLE_PFCP
|
||||
{.name = "pfcp", .tdefs = osmo_pfcp_tdefs, .desc = "PFCP timers" },
|
||||
#endif
|
||||
{ }
|
||||
};
|
||||
107
src/osmo-hnbgw/umts_cell_id.c
Normal file
107
src/osmo-hnbgw/umts_cell_id.c
Normal file
@@ -0,0 +1,107 @@
|
||||
/* UMTS Cell ID */
|
||||
|
||||
/* (C) 2015,2024 by Harald Welte <laforge@gnumonks.org>
|
||||
* (C) 2016-2025 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/jhash.h>
|
||||
|
||||
#include <osmocom/gsm/gsm23003.h>
|
||||
|
||||
#include <osmocom/hnbgw/umts_cell_id.h>
|
||||
|
||||
int umts_cell_id_to_str_buf(char *buf, size_t buflen, const struct umts_cell_id *ucid)
|
||||
{
|
||||
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
|
||||
OSMO_STRBUF_APPEND_NOLEN(sb, osmo_plmn_name_buf, &ucid->plmn);
|
||||
OSMO_STRBUF_PRINTF(sb, "-L%u-R%u-S%u-C%u", ucid->lac, ucid->rac, ucid->sac, ucid->cid);
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
char *umts_cell_id_to_str_c(void *ctx, const struct umts_cell_id *ucid)
|
||||
{
|
||||
OSMO_NAME_C_IMPL(ctx, 64, "ERROR", umts_cell_id_to_str_buf, ucid)
|
||||
}
|
||||
|
||||
const char *umts_cell_id_to_str(const struct umts_cell_id *ucid)
|
||||
{
|
||||
return umts_cell_id_to_str_c(OTC_SELECT, ucid);
|
||||
}
|
||||
|
||||
/* Useful to index a hash table by struct umts_cell_id. */
|
||||
uint32_t umts_cell_id_hash(const struct umts_cell_id *ucid)
|
||||
{
|
||||
return osmo_jhash(ucid, sizeof(*ucid), 0x423423);
|
||||
}
|
||||
|
||||
/* parse a string representation of an umts_cell_id into its decoded representation */
|
||||
int umts_cell_id_from_str(struct umts_cell_id *ucid, const char *instr)
|
||||
{
|
||||
int rc;
|
||||
char buf[4];
|
||||
const char *pos = instr;
|
||||
const char *end;
|
||||
|
||||
/* We want to use struct umts_cell_id as hashtable key. If it ever happens to contain any padding bytes, make
|
||||
* sure everything is deterministically zero. */
|
||||
memset(ucid, 0, sizeof(*ucid));
|
||||
|
||||
/* read MCC */
|
||||
end = strchr(pos, '-');
|
||||
if (!end || end <= pos || (end - pos) >= sizeof(buf))
|
||||
return -EINVAL;
|
||||
osmo_strlcpy(buf, pos, end - pos + 1);
|
||||
if (osmo_mcc_from_str(buf, &ucid->plmn.mcc))
|
||||
return -EINVAL;
|
||||
pos = end + 1;
|
||||
|
||||
/* read MNC -- here the number of leading zeros matters. */
|
||||
end = strchr(pos, '-');
|
||||
if (!end || end == pos || (end - pos) >= sizeof(buf))
|
||||
return -EINVAL;
|
||||
osmo_strlcpy(buf, pos, end - pos + 1);
|
||||
if (osmo_mnc_from_str(buf, &ucid->plmn.mnc, &ucid->plmn.mnc_3_digits))
|
||||
return -EINVAL;
|
||||
pos = end + 1;
|
||||
|
||||
/* parse the rest, where leading zeros do not matter */
|
||||
rc = sscanf(pos, "L%" SCNu16 "-R%" SCNu8 "-S%" SCNu16 "-C%" SCNu32 "",
|
||||
&ucid->lac, &ucid->rac, &ucid->sac, &ucid->cid);
|
||||
if (rc < 0)
|
||||
return -errno;
|
||||
|
||||
if (rc != 4)
|
||||
return -EINVAL;
|
||||
|
||||
if (ucid->lac == 0 || ucid->lac == 0xffff)
|
||||
return -EINVAL;
|
||||
|
||||
/* CellIdentity in the ASN.1 syntax is a bit-string of 28 bits length */
|
||||
if (ucid->cid >= (1 << 28))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
SUBDIRS = \
|
||||
ranap_rab_ass \
|
||||
umts_cell_id \
|
||||
$(NULL)
|
||||
|
||||
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
|
||||
@@ -23,8 +25,13 @@ EXTRA_DIST = \
|
||||
testsuite.at \
|
||||
$(srcdir)/package.m4 \
|
||||
$(TESTSUITE) \
|
||||
ctrl_test_runner.py \
|
||||
osmo-hnbgw.vty \
|
||||
osmo-hnbgw-vty-test.cfg \
|
||||
$(srcdir)/*.vty \
|
||||
$(srcdir)/*.vty.with_pfcp \
|
||||
$(srcdir)/*.ctrl \
|
||||
$(srcdir)/config/*.cfg \
|
||||
$(srcdir)/config/*.vty \
|
||||
$(srcdir)/config/run_tests.sh \
|
||||
$(NULL)
|
||||
|
||||
TESTSUITE = $(srcdir)/testsuite
|
||||
@@ -34,30 +41,56 @@ DISTCLEANFILES = \
|
||||
$(NULL)
|
||||
|
||||
if ENABLE_EXT_TESTS
|
||||
python-tests: $(BUILT_SOURCES)
|
||||
echo ""
|
||||
# TODO: Enable once we have a VTY/CTRL interface:
|
||||
# $(MAKE) vty-test
|
||||
# osmotestvty.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
|
||||
# osmotestconfig.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
|
||||
# $(srcdir)/ctrl_test_runner.py -w $(abs_top_builddir) -v
|
||||
python-tests:
|
||||
$(MAKE) vty-test
|
||||
osmotestvty.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
|
||||
osmotestconfig.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
|
||||
else
|
||||
python-tests: $(BUILT_SOURCES)
|
||||
python-tests:
|
||||
echo "Not running python-based tests (determined at configure-time)"
|
||||
endif
|
||||
|
||||
# Run a specific test with: 'make vty-test VTY_TEST=osmo-hnbgw.vty'
|
||||
if ENABLE_PFCP
|
||||
VTY_TEST ?= *.vty*
|
||||
else
|
||||
VTY_TEST ?= *.vty
|
||||
endif
|
||||
|
||||
# To update the VTY script from current application behavior,
|
||||
# pass -u to vty_script_runner.py by doing:
|
||||
# make vty-test U=-u
|
||||
vty-test:
|
||||
vty-test: $(top_builddir)/src/osmo-hnbgw/osmo-hnbgw
|
||||
osmo_verify_transcript_vty.py -v \
|
||||
-n OsmoHNBGW -p 4261 \
|
||||
-r "$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw -c $(top_srcdir)/doc/examples/osmo-hnbgw/osmo-hnbgw.cfg" \
|
||||
-r "$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw \
|
||||
-c $(srcdir)/osmo-hnbgw-vty-test.cfg" \
|
||||
$(U) $(srcdir)/$(VTY_TEST)
|
||||
|
||||
# Test each config/*.cfg file with the corresponding config/*.vty transcript test.
|
||||
#
|
||||
# To be invoked manually only: This is not part of 'make check' because the
|
||||
# output may change when libosmo-sccp changes its VTY appearance, which can
|
||||
# cause annoying test fallout.
|
||||
#
|
||||
# Each config test runs an osmo-hnbgw process to talk to, so they must not run concurrently.
|
||||
# To prevent 'make -j N' from running these tests in parallel, call a script with a linear for-loop.
|
||||
.PHONY: config-tests
|
||||
config-tests:
|
||||
$(srcdir)/config/run_tests.sh "$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw" "$(srcdir)/config/" $U
|
||||
|
||||
# Run a specific test with: 'make ctrl-test CTRL_TEST=osmo-hnbgw.ctrl'
|
||||
CTRL_TEST ?= *.ctrl
|
||||
|
||||
# To update the CTRL script from current application behavior,
|
||||
# pass -u to ctrl_script_runner.py by doing:
|
||||
# make ctrl-test U=-u
|
||||
ctrl-test: $(top_builddir)/src/osmo-hnbgw/osmo-hnbgw
|
||||
osmo_verify_transcript_ctrl.py -v \
|
||||
-p 4262 \
|
||||
-r "$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw -c $(top_srcdir)/doc/examples/osmo-hnbgw/osmo-hnbgw-cnpool.cfg" \
|
||||
$(U) $(srcdir)/$(CTRL_TEST)
|
||||
|
||||
check-local: atconfig $(TESTSUITE)
|
||||
$(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS)
|
||||
$(MAKE) $(AM_MAKEFLAGS) python-tests
|
||||
|
||||
432
tests/cnpool.vty
Normal file
432
tests/cnpool.vty
Normal file
@@ -0,0 +1,432 @@
|
||||
OsmoHNBGW> enable
|
||||
OsmoHNBGW# list
|
||||
...
|
||||
(msc|sgsn) <0-1000> ranap reset
|
||||
...
|
||||
OsmoHNBGW# msc?
|
||||
msc Manipulate an IuCS link to an MSC
|
||||
OsmoHNBGW# msc ?
|
||||
<0-1000> MSC/SGSN nr
|
||||
OsmoHNBGW# msc 0 ?
|
||||
ranap Manipulate RANAP layer of Iu-interface
|
||||
OsmoHNBGW# msc 0 ranap ?
|
||||
reset Flip this CN link to disconnected state and re-send RANAP RESET
|
||||
|
||||
OsmoHNBGW# sgsn?
|
||||
sgsn Manipulate an IuPS link to an SGSN
|
||||
OsmoHNBGW# sgsn ?
|
||||
<0-1000> MSC/SGSN nr
|
||||
OsmoHNBGW# sgsn 0 ?
|
||||
ranap Manipulate RANAP layer of Iu-interface
|
||||
OsmoHNBGW# sgsn 0 ranap ?
|
||||
reset Flip this CN link to disconnected state and re-send RANAP RESET
|
||||
|
||||
OsmoHNBGW# configure terminal
|
||||
|
||||
OsmoHNBGW(config)# ### cnpool doc strings
|
||||
OsmoHNBGW(config)# list
|
||||
...
|
||||
apply sccp
|
||||
...
|
||||
|
||||
OsmoHNBGW(config)# apply?
|
||||
apply Immediately use configuration modified via telnet VTY, and restart components as needed.
|
||||
|
||||
OsmoHNBGW(config)# apply ?
|
||||
sccp For telnet VTY: apply all SCCP and NRI config changes made to any CN pools and CN links in the running osmo-hnbgw process. If 'remote-addr' changed, related SCCP links will be restarted, possibly dropping active UE contexts. This is run implicitly on program startup, only useful to apply changes made later via telnet VTY.
|
||||
|
||||
OsmoHNBGW(config)# hnbgw
|
||||
OsmoHNBGW(config-hnbgw)# list
|
||||
...
|
||||
iucs
|
||||
iups
|
||||
...
|
||||
OsmoHNBGW(config-hnbgw)# iucs?
|
||||
iucs Configure IuCS options
|
||||
OsmoHNBGW(config-hnbgw)# iups?
|
||||
iups Configure IuPS options
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# iucs
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# list
|
||||
...
|
||||
nri bitlen <1-15>
|
||||
nri null add <0-32767> [<0-32767>]
|
||||
nri null del <0-32767> [<0-32767>]
|
||||
...
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri?
|
||||
nri Mapping of Network Resource Indicators to this CN peer, for CN pooling
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri ?
|
||||
bitlen Set number of bits that an NRI has, to extract from TMSI identities (always starting just after the TMSI's most significant octet).
|
||||
null Define NULL-NRI values that cause re-assignment of an MS to a different CN peer, for CN pooling.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri bitlen ?
|
||||
<1-15> bit count (default: 10)
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri null ?
|
||||
add Add NULL-NRI value (or range)
|
||||
del Remove NRI value or range from the NRI mapping for this CN link
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri null add ?
|
||||
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri null add 0 ?
|
||||
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri null del ?
|
||||
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri null del 0 ?
|
||||
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# exit
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# iups
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# list
|
||||
...
|
||||
nri bitlen <1-15>
|
||||
nri null add <0-32767> [<0-32767>]
|
||||
nri null del <0-32767> [<0-32767>]
|
||||
...
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri?
|
||||
nri Mapping of Network Resource Indicators to this CN peer, for CN pooling
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri ?
|
||||
bitlen Set number of bits that an NRI has, to extract from TMSI identities (always starting just after the TMSI's most significant octet).
|
||||
null Define NULL-NRI values that cause re-assignment of an MS to a different CN peer, for CN pooling.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri bitlen ?
|
||||
<1-15> bit count (default: 10)
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri null ?
|
||||
add Add NULL-NRI value (or range)
|
||||
del Remove NRI value or range from the NRI mapping for this CN link
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri null add ?
|
||||
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri null add 0 ?
|
||||
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri null del ?
|
||||
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri null del 0 ?
|
||||
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# exit
|
||||
OsmoHNBGW(config-hnbgw)# exit
|
||||
|
||||
OsmoHNBGW(config)# msc?
|
||||
msc Configure an IuCS link to an MSC
|
||||
OsmoHNBGW(config)# msc ?
|
||||
<0-1000> MSC nr
|
||||
|
||||
OsmoHNBGW(config)# sgsn?
|
||||
sgsn Configure an IuPS link to an SGSN
|
||||
OsmoHNBGW(config)# sgsn ?
|
||||
<0-1000> SGSN nr
|
||||
|
||||
OsmoHNBGW(config)# ### The config file has no 'msc' or 'sgsn', so defaults have been set up
|
||||
OsmoHNBGW(config)# show running-config
|
||||
...
|
||||
cs7 instance 0
|
||||
point-code 0.23.5
|
||||
...
|
||||
sccp-address addr-dyn-msc-default
|
||||
routing-indicator PC
|
||||
point-code 0.23.1
|
||||
subsystem-number 142
|
||||
sccp-address addr-dyn-sgsn-default
|
||||
routing-indicator PC
|
||||
point-code 0.23.4
|
||||
...
|
||||
msc 0
|
||||
name msc-0
|
||||
remote-addr addr-dyn-msc-default
|
||||
sgsn 0
|
||||
name sgsn-0
|
||||
remote-addr addr-dyn-sgsn-default
|
||||
...
|
||||
|
||||
OsmoHNBGW(config)# msc 1
|
||||
|
||||
OsmoHNBGW(config-msc)# list
|
||||
...
|
||||
remote-addr NAME
|
||||
nri add <0-32767> [<0-32767>]
|
||||
nri del <0-32767> [<0-32767>]
|
||||
show nri
|
||||
apply sccp
|
||||
allow-attach
|
||||
no allow-attach
|
||||
allow-emergency
|
||||
no allow-emergency
|
||||
...
|
||||
|
||||
OsmoHNBGW(config-msc)# remote-addr?
|
||||
remote-addr SCCP address to send RANAP/SCCP to
|
||||
OsmoHNBGW(config-msc)# remote-addr ?
|
||||
NAME SCCP address book entry name (see 'cs7 instance' / 'sccp-address')
|
||||
|
||||
OsmoHNBGW(config-msc)# show nri?
|
||||
nri Mapping of Network Resource Indicators to this CN peer, for CN pooling
|
||||
|
||||
OsmoHNBGW(config-msc)# apply?
|
||||
apply Immediately use configuration modified via telnet VTY, and restart components as needed.
|
||||
|
||||
OsmoHNBGW(config-msc)# apply ?
|
||||
sccp For telnet VTY: apply SCCP and NRI config changes made to this CN link in the running osmo-hnbgw process. If 'remote-addr' changed, related SCCP links will be restarted, possibly dropping active UE contexts. This is run implicitly on program startup, only useful to apply changes made later via telnet VTY.
|
||||
|
||||
OsmoHNBGW(config-msc)# allow-attach?
|
||||
allow-attach Allow this CN link to attach new subscribers (default).
|
||||
|
||||
OsmoHNBGW(config-msc)# allow-emergency?
|
||||
allow-emergency Allow CM ServiceRequests with type emergency on this CN link
|
||||
|
||||
OsmoHNBGW(config-msc)# no ?
|
||||
allow-attach Do not assign new subscribers to this CN link. Useful if an CN link in an CN link pool is configured to off-load subscribers. The CN link will still be operational for already IMSI-Attached subscribers, but the NAS node selection function will skip this CN link for new subscribers
|
||||
allow-emergency Do not serve CM ServiceRequests with type emergency on this CN link
|
||||
|
||||
OsmoHNBGW(config-msc)# exit
|
||||
|
||||
OsmoHNBGW(config)# sgsn 1
|
||||
|
||||
OsmoHNBGW(config-sgsn)# list
|
||||
...
|
||||
remote-addr NAME
|
||||
nri add <0-32767> [<0-32767>]
|
||||
nri del <0-32767> [<0-32767>]
|
||||
show nri
|
||||
apply sccp
|
||||
allow-attach
|
||||
no allow-attach
|
||||
allow-emergency
|
||||
no allow-emergency
|
||||
...
|
||||
|
||||
OsmoHNBGW(config-sgsn)# remote-addr?
|
||||
remote-addr SCCP address to send RANAP/SCCP to
|
||||
OsmoHNBGW(config-sgsn)# remote-addr ?
|
||||
NAME SCCP address book entry name (see 'cs7 instance' / 'sccp-address')
|
||||
|
||||
OsmoHNBGW(config-sgsn)# show nri?
|
||||
nri Mapping of Network Resource Indicators to this CN peer, for CN pooling
|
||||
|
||||
OsmoHNBGW(config-sgsn)# apply?
|
||||
apply Immediately use configuration modified via telnet VTY, and restart components as needed.
|
||||
|
||||
OsmoHNBGW(config-sgsn)# apply ?
|
||||
sccp For telnet VTY: apply SCCP and NRI config changes made to this CN link in the running osmo-hnbgw process. If 'remote-addr' changed, related SCCP links will be restarted, possibly dropping active UE contexts. This is run implicitly on program startup, only useful to apply changes made later via telnet VTY.
|
||||
|
||||
OsmoHNBGW(config-sgsn)# allow-attach?
|
||||
allow-attach Allow this CN link to attach new subscribers (default).
|
||||
|
||||
OsmoHNBGW(config-sgsn)# allow-emergency?
|
||||
allow-emergency Allow CM ServiceRequests with type emergency on this CN link
|
||||
|
||||
OsmoHNBGW(config-sgsn)# no ?
|
||||
allow-attach Do not assign new subscribers to this CN link. Useful if an CN link in an CN link pool is configured to off-load subscribers. The CN link will still be operational for already IMSI-Attached subscribers, but the NAS node selection function will skip this CN link for new subscribers
|
||||
allow-emergency Do not serve CM ServiceRequests with type emergency on this CN link
|
||||
|
||||
OsmoHNBGW(config-sgsn)# exit
|
||||
|
||||
OsmoHNBGW(config)# ### Just by entering the nodes above, 'msc 1' and 'sgsn 1' now exist
|
||||
OsmoHNBGW(config)# show running-config
|
||||
...
|
||||
msc 0
|
||||
name msc-0
|
||||
remote-addr addr-dyn-msc-default
|
||||
msc 1
|
||||
name msc-1
|
||||
sgsn 0
|
||||
name sgsn-0
|
||||
remote-addr addr-dyn-sgsn-default
|
||||
sgsn 1
|
||||
name sgsn-1
|
||||
...
|
||||
|
||||
OsmoHNBGW(config)# ### Add {msc,sgsn}x{2,3}
|
||||
|
||||
OsmoHNBGW(config)# msc 2
|
||||
OsmoHNBGW(config-msc)# remote-addr addr-msc2
|
||||
OsmoHNBGW(config-msc)# no allow-attach
|
||||
OsmoHNBGW(config-msc)# exit
|
||||
|
||||
OsmoHNBGW(config)# msc 3
|
||||
OsmoHNBGW(config-msc)# remote-addr addr-msc3
|
||||
OsmoHNBGW(config-msc)# allow-emergency
|
||||
OsmoHNBGW(config-msc)# exit
|
||||
|
||||
OsmoHNBGW(config)# sgsn 2
|
||||
OsmoHNBGW(config-sgsn)# remote-addr addr-sgsn2
|
||||
OsmoHNBGW(config-sgsn)# exit
|
||||
|
||||
OsmoHNBGW(config)# sgsn 3
|
||||
OsmoHNBGW(config-sgsn)# remote-addr addr-sgsn3
|
||||
OsmoHNBGW(config-sgsn)# exit
|
||||
|
||||
OsmoHNBGW(config)# show running-config
|
||||
...
|
||||
msc 0
|
||||
name msc-0
|
||||
remote-addr addr-dyn-msc-default
|
||||
msc 1
|
||||
name msc-1
|
||||
msc 2
|
||||
name msc-2
|
||||
remote-addr addr-msc2
|
||||
no allow-attach
|
||||
msc 3
|
||||
name msc-3
|
||||
remote-addr addr-msc3
|
||||
allow-emergency
|
||||
sgsn 0
|
||||
name sgsn-0
|
||||
remote-addr addr-dyn-sgsn-default
|
||||
sgsn 1
|
||||
name sgsn-1
|
||||
sgsn 2
|
||||
name sgsn-2
|
||||
remote-addr addr-sgsn2
|
||||
sgsn 3
|
||||
name sgsn-3
|
||||
remote-addr addr-sgsn3
|
||||
...
|
||||
|
||||
OsmoHNBGW(config)# ### Re-entering existing nodes works (does not create new ones)
|
||||
OsmoHNBGW(config)# msc 2
|
||||
OsmoHNBGW(config-msc)# allow-attach
|
||||
OsmoHNBGW(config-msc)# exit
|
||||
OsmoHNBGW(config)# msc 3
|
||||
OsmoHNBGW(config-msc)# remote-addr addr-msc4
|
||||
OsmoHNBGW(config-msc)# no allow-emergency
|
||||
OsmoHNBGW(config-msc)# exit
|
||||
OsmoHNBGW(config)# sgsn 2
|
||||
OsmoHNBGW(config-sgsn)# remote-addr addr-sgsn4
|
||||
OsmoHNBGW(config-sgsn)# exit
|
||||
|
||||
OsmoHNBGW(config)# show running-config
|
||||
...
|
||||
msc 0
|
||||
name msc-0
|
||||
remote-addr addr-dyn-msc-default
|
||||
msc 1
|
||||
name msc-1
|
||||
msc 2
|
||||
name msc-2
|
||||
remote-addr addr-msc2
|
||||
msc 3
|
||||
name msc-3
|
||||
remote-addr addr-msc4
|
||||
sgsn 0
|
||||
name sgsn-0
|
||||
remote-addr addr-dyn-sgsn-default
|
||||
sgsn 1
|
||||
name sgsn-1
|
||||
sgsn 2
|
||||
name sgsn-2
|
||||
remote-addr addr-sgsn4
|
||||
sgsn 3
|
||||
name sgsn-3
|
||||
remote-addr addr-sgsn3
|
||||
...
|
||||
|
||||
OsmoHNBGW(config)# ### Legacy configuration {'iups','iucs'} / 'remote-addr' redirects to {'msc 0','sgsn 0'}:
|
||||
|
||||
OsmoHNBGW(config)# hnbgw
|
||||
OsmoHNBGW(config-hnbgw)# iucs
|
||||
OsmoHNBGW(config-hnbgw-iucs)# remote-addr?
|
||||
% There is no matched command.
|
||||
OsmoHNBGW(config-hnbgw-iucs)# remote-addr ?
|
||||
% There is no matched command.
|
||||
OsmoHNBGW(config-hnbgw-iucs)# remote-addr addr-msc0
|
||||
% Deprecated: instead of hnbgw/iucs/remote-addr, use 'msc 0'/remote-addr
|
||||
OsmoHNBGW(config-hnbgw-iucs)# exit
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# iups
|
||||
OsmoHNBGW(config-hnbgw-iups)# remote-addr?
|
||||
% There is no matched command.
|
||||
OsmoHNBGW(config-hnbgw-iups)# remote-addr ?
|
||||
% There is no matched command.
|
||||
OsmoHNBGW(config-hnbgw-iups)# remote-addr addr-sgsn0
|
||||
% Deprecated: instead of hnbgw/iups/remote-addr, use 'sgsn 0'/remote-addr
|
||||
OsmoHNBGW(config-hnbgw-iups)# exit
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# show running-config
|
||||
...
|
||||
hnbgw
|
||||
...
|
||||
msc 0
|
||||
name msc-0
|
||||
remote-addr addr-msc0
|
||||
msc 1
|
||||
name msc-1
|
||||
msc 2
|
||||
name msc-2
|
||||
remote-addr addr-msc2
|
||||
msc 3
|
||||
name msc-3
|
||||
remote-addr addr-msc4
|
||||
sgsn 0
|
||||
name sgsn-0
|
||||
remote-addr addr-sgsn0
|
||||
sgsn 1
|
||||
name sgsn-1
|
||||
sgsn 2
|
||||
name sgsn-2
|
||||
remote-addr addr-sgsn4
|
||||
sgsn 3
|
||||
name sgsn-3
|
||||
remote-addr addr-sgsn3
|
||||
...
|
||||
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# ### placement of the new 'msc' and 'sgsn' nodes must be below 'pfcp' and 'mgw' nodes
|
||||
OsmoHNBGW(config-hnbgw)# show running-config
|
||||
...
|
||||
hnbgw
|
||||
... !msc
|
||||
iuh
|
||||
... !msc
|
||||
mgw 0
|
||||
...
|
||||
msc 0
|
||||
...
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# exit
|
||||
OsmoHNBGW(config)# ### Apply all SCCP changes:
|
||||
OsmoHNBGW(config)# apply sccp
|
||||
OsmoHNBGW(config)# show running-config
|
||||
...
|
||||
hnbgw
|
||||
...
|
||||
msc 0
|
||||
name msc-0
|
||||
remote-addr addr-msc0
|
||||
msc 1
|
||||
name msc-1
|
||||
remote-addr addr-dyn-msc-default
|
||||
msc 2
|
||||
name msc-2
|
||||
remote-addr addr-msc2
|
||||
msc 3
|
||||
name msc-3
|
||||
remote-addr addr-msc4
|
||||
sgsn 0
|
||||
name sgsn-0
|
||||
remote-addr addr-sgsn0
|
||||
sgsn 1
|
||||
name sgsn-1
|
||||
remote-addr addr-dyn-sgsn-default
|
||||
sgsn 2
|
||||
name sgsn-2
|
||||
remote-addr addr-sgsn4
|
||||
sgsn 3
|
||||
name sgsn-3
|
||||
remote-addr addr-sgsn3
|
||||
...
|
||||
1
tests/config/defaults.cfg
Normal file
1
tests/config/defaults.cfg
Normal file
@@ -0,0 +1 @@
|
||||
# configure nothing, to get purely the implicit default config
|
||||
37
tests/config/defaults.vty
Normal file
37
tests/config/defaults.vty
Normal file
@@ -0,0 +1,37 @@
|
||||
OsmoHNBGW> ### Config file was empty, everything we see here is the default auto configuration
|
||||
|
||||
OsmoHNBGW> enable
|
||||
|
||||
OsmoHNBGW# show cs7 config
|
||||
cs7 instance 0
|
||||
point-code 0.23.5
|
||||
asp asp-clnt-msc-0 2905 0 m3ua
|
||||
local-ip localhost
|
||||
remote-ip localhost
|
||||
role asp
|
||||
sctp-role client
|
||||
as as-clnt-msc-0 m3ua
|
||||
asp asp-clnt-msc-0
|
||||
routing-key 0 0.23.5
|
||||
|
||||
OsmoHNBGW# show cnlink
|
||||
IuCS: OsmoHNBGW:RI=SSN_PC,PC=0.23.5,SSN=RANAP <-> RI=SSN_PC,PC=0.23.1,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
IuPS: OsmoHNBGW:RI=SSN_PC,PC=0.23.5,SSN=RANAP <-> RI=SSN_PC,PC=0.23.4,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
|
||||
OsmoHNBGW# show running-config
|
||||
...
|
||||
cs7 instance 0
|
||||
point-code 0.23.5
|
||||
hnbgw
|
||||
rnc-id 23
|
||||
log-prefix hnb-id
|
||||
iuh
|
||||
mgw 0
|
||||
...
|
||||
msc 0
|
||||
sgsn 0
|
||||
...
|
||||
12
tests/config/legacy.cfg
Normal file
12
tests/config/legacy.cfg
Normal file
@@ -0,0 +1,12 @@
|
||||
# explicitly point at cs7 instance 0 via two address book entries for CS and PS. Leave ASP and AS to auto-configuration.
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
sccp-address my-msc
|
||||
point-code 1.2.3
|
||||
sccp-address my-sgsn
|
||||
point-code 1.4.2
|
||||
hnbgw
|
||||
iucs
|
||||
remote-addr my-msc
|
||||
iups
|
||||
remote-addr my-sgsn
|
||||
50
tests/config/legacy.vty
Normal file
50
tests/config/legacy.vty
Normal file
@@ -0,0 +1,50 @@
|
||||
OsmoHNBGW> enable
|
||||
|
||||
OsmoHNBGW# ### ASP and AS were auto-configured
|
||||
OsmoHNBGW# show cs7 config
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
asp asp-clnt-msc-0 2905 0 m3ua
|
||||
local-ip localhost
|
||||
remote-ip localhost
|
||||
role asp
|
||||
sctp-role client
|
||||
as as-clnt-msc-0 m3ua
|
||||
asp asp-clnt-msc-0
|
||||
routing-key 0 1.1.1
|
||||
sccp-address my-msc
|
||||
routing-indicator PC
|
||||
point-code 1.2.3
|
||||
sccp-address my-sgsn
|
||||
routing-indicator PC
|
||||
point-code 1.4.2
|
||||
|
||||
OsmoHNBGW# ### IuCS and IuPS were explicitly pointed to specific remote point-codes, using cs7 instance 0 addresses
|
||||
OsmoHNBGW# show cnlink
|
||||
IuCS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> my-msc=RI=SSN_PC,PC=1.2.3,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
IuPS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> my-sgsn=RI=SSN_PC,PC=1.4.2,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
|
||||
OsmoHNBGW# show running-config
|
||||
...
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
sccp-address my-msc
|
||||
routing-indicator PC
|
||||
point-code 1.2.3
|
||||
sccp-address my-sgsn
|
||||
routing-indicator PC
|
||||
point-code 1.4.2
|
||||
hnbgw
|
||||
rnc-id 23
|
||||
log-prefix hnb-id
|
||||
iuh
|
||||
...
|
||||
msc 0
|
||||
remote-addr my-msc
|
||||
sgsn 0
|
||||
remote-addr my-sgsn
|
||||
...
|
||||
3
tests/config/one_cs7.cfg
Normal file
3
tests/config/one_cs7.cfg
Normal file
@@ -0,0 +1,3 @@
|
||||
# auto-configuration will pick up cs7 instance 0. Configure only the local point-code.
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
31
tests/config/one_cs7.vty
Normal file
31
tests/config/one_cs7.vty
Normal file
@@ -0,0 +1,31 @@
|
||||
OsmoHNBGW> enable
|
||||
|
||||
OsmoHNBGW# show cs7 config
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
asp asp-clnt-msc-0 2905 0 m3ua
|
||||
local-ip localhost
|
||||
remote-ip localhost
|
||||
role asp
|
||||
sctp-role client
|
||||
as as-clnt-msc-0 m3ua
|
||||
asp asp-clnt-msc-0
|
||||
routing-key 0 1.1.1
|
||||
|
||||
OsmoHNBGW# show cnlink
|
||||
IuCS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> RI=SSN_PC,PC=0.23.1,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
IuPS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> RI=SSN_PC,PC=0.23.4,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
|
||||
OsmoHNBGW# show running-config
|
||||
...
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
hnbgw
|
||||
...
|
||||
msc 0
|
||||
sgsn 0
|
||||
...
|
||||
11
tests/config/one_cs7_with_addrs.cfg
Normal file
11
tests/config/one_cs7_with_addrs.cfg
Normal file
@@ -0,0 +1,11 @@
|
||||
# explicitly point at cs7 instance 0 via two address book entries for CS and PS. Leave ASP and AS to auto-configuration.
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
sccp-address my-msc
|
||||
point-code 1.2.3
|
||||
sccp-address my-sgsn
|
||||
point-code 1.4.2
|
||||
msc 0
|
||||
remote-addr my-msc
|
||||
sgsn 0
|
||||
remote-addr my-sgsn
|
||||
50
tests/config/one_cs7_with_addrs.vty
Normal file
50
tests/config/one_cs7_with_addrs.vty
Normal file
@@ -0,0 +1,50 @@
|
||||
OsmoHNBGW> enable
|
||||
|
||||
OsmoHNBGW# ### ASP and AS were auto-configured
|
||||
OsmoHNBGW# show cs7 config
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
asp asp-clnt-msc-0 2905 0 m3ua
|
||||
local-ip localhost
|
||||
remote-ip localhost
|
||||
role asp
|
||||
sctp-role client
|
||||
as as-clnt-msc-0 m3ua
|
||||
asp asp-clnt-msc-0
|
||||
routing-key 0 1.1.1
|
||||
sccp-address my-msc
|
||||
routing-indicator PC
|
||||
point-code 1.2.3
|
||||
sccp-address my-sgsn
|
||||
routing-indicator PC
|
||||
point-code 1.4.2
|
||||
|
||||
OsmoHNBGW# ### IuCS and IuPS were explicitly pointed to specific remote point-codes, using cs7 instance 0 addresses
|
||||
OsmoHNBGW# show cnlink
|
||||
IuCS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> my-msc=RI=SSN_PC,PC=1.2.3,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
IuPS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> my-sgsn=RI=SSN_PC,PC=1.4.2,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
|
||||
OsmoHNBGW# show running-config
|
||||
...
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
sccp-address my-msc
|
||||
routing-indicator PC
|
||||
point-code 1.2.3
|
||||
sccp-address my-sgsn
|
||||
routing-indicator PC
|
||||
point-code 1.4.2
|
||||
hnbgw
|
||||
rnc-id 23
|
||||
log-prefix hnb-id
|
||||
iuh
|
||||
...
|
||||
msc 0
|
||||
remote-addr my-msc
|
||||
sgsn 0
|
||||
remote-addr my-sgsn
|
||||
...
|
||||
7
tests/config/one_cs7_with_iucs_addr.cfg
Normal file
7
tests/config/one_cs7_with_iucs_addr.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
# only set an explicit remote address for IuCS, let IuPS be auto-configured
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
sccp-address my-msc
|
||||
point-code 1.2.3
|
||||
msc 0
|
||||
remote-addr my-msc
|
||||
42
tests/config/one_cs7_with_iucs_addr.vty
Normal file
42
tests/config/one_cs7_with_iucs_addr.vty
Normal file
@@ -0,0 +1,42 @@
|
||||
OsmoHNBGW> enable
|
||||
|
||||
OsmoHNBGW# show cs7 config
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
asp asp-clnt-msc-0 2905 0 m3ua
|
||||
local-ip localhost
|
||||
remote-ip localhost
|
||||
role asp
|
||||
sctp-role client
|
||||
as as-clnt-msc-0 m3ua
|
||||
asp asp-clnt-msc-0
|
||||
routing-key 0 1.1.1
|
||||
sccp-address my-msc
|
||||
routing-indicator PC
|
||||
point-code 1.2.3
|
||||
|
||||
OsmoHNBGW# ### only IuCS was configured to go to remote-PC = 1.2.3, IuPS auto-configured to remote-PC = 0.23.4
|
||||
OsmoHNBGW# show cnlink
|
||||
IuCS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> my-msc=RI=SSN_PC,PC=1.2.3,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
IuPS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> RI=SSN_PC,PC=0.23.4,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
|
||||
OsmoHNBGW# show running-config
|
||||
...
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
sccp-address my-msc
|
||||
routing-indicator PC
|
||||
point-code 1.2.3
|
||||
hnbgw
|
||||
rnc-id 23
|
||||
log-prefix hnb-id
|
||||
iuh
|
||||
...
|
||||
msc 0
|
||||
remote-addr my-msc
|
||||
sgsn 0
|
||||
... !remote-addr
|
||||
7
tests/config/one_cs7_with_iups_addr.cfg
Normal file
7
tests/config/one_cs7_with_iups_addr.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
# only set an explicit remote address for IuPS, let IuCS be auto-configured
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
sccp-address my-sgsn
|
||||
point-code 1.4.2
|
||||
sgsn 0
|
||||
remote-addr my-sgsn
|
||||
42
tests/config/one_cs7_with_iups_addr.vty
Normal file
42
tests/config/one_cs7_with_iups_addr.vty
Normal file
@@ -0,0 +1,42 @@
|
||||
OsmoHNBGW> enable
|
||||
|
||||
OsmoHNBGW# show cs7 config
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
asp asp-clnt-msc-0 2905 0 m3ua
|
||||
local-ip localhost
|
||||
remote-ip localhost
|
||||
role asp
|
||||
sctp-role client
|
||||
as as-clnt-msc-0 m3ua
|
||||
asp asp-clnt-msc-0
|
||||
routing-key 0 1.1.1
|
||||
sccp-address my-sgsn
|
||||
routing-indicator PC
|
||||
point-code 1.4.2
|
||||
|
||||
OsmoHNBGW# ### only IuPS was configured to go to remote-PC = 1.4.2, IuCS auto-configured to remote-PC = 0.23.1
|
||||
OsmoHNBGW# show cnlink
|
||||
IuCS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> RI=SSN_PC,PC=0.23.1,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
IuPS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> my-sgsn=RI=SSN_PC,PC=1.4.2,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
|
||||
OsmoHNBGW# show running-config
|
||||
...
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
sccp-address my-sgsn
|
||||
routing-indicator PC
|
||||
point-code 1.4.2
|
||||
hnbgw
|
||||
rnc-id 23
|
||||
log-prefix hnb-id
|
||||
iuh
|
||||
...
|
||||
msc 0
|
||||
sgsn 0
|
||||
remote-addr my-sgsn
|
||||
...
|
||||
21
tests/config/run_tests.sh
Executable file
21
tests/config/run_tests.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
osmo_hnbgw="$1"
|
||||
tests_src="$2"
|
||||
shift
|
||||
shift
|
||||
|
||||
if [ ! -x "$osmo_hnbgw" ]; then
|
||||
echo "there seems to be no osmo-hnbgw executable at '$osmo_hnbgw'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$tests_src" ]; then
|
||||
echo "there seems to be no tests source dir at '$tests_src'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -e
|
||||
for dot_vty in "$tests_src"/*.vty; do
|
||||
dot_cfg="$(echo $dot_vty | sed 's/\.vty$/.cfg/')"
|
||||
osmo_verify_transcript_vty.py -v -n OsmoHNBGW -p 4261 -r "$osmo_hnbgw -c $dot_cfg" "$dot_vty" $@
|
||||
done
|
||||
7
tests/config/two_cs7.cfg
Normal file
7
tests/config/two_cs7.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
# there are two cs7 instances, but auto-configuration will only use instance 0.
|
||||
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
|
||||
cs7 instance 1
|
||||
point-code 2.2.2
|
||||
37
tests/config/two_cs7.vty
Normal file
37
tests/config/two_cs7.vty
Normal file
@@ -0,0 +1,37 @@
|
||||
OsmoHNBGW> enable
|
||||
|
||||
OsmoHNBGW# ### there are two cs7 instances, but auto-configuration has only set up ASP and AS in instance 0.
|
||||
OsmoHNBGW# show cs7 config
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
asp asp-clnt-msc-0 2905 0 m3ua
|
||||
local-ip localhost
|
||||
remote-ip localhost
|
||||
role asp
|
||||
sctp-role client
|
||||
as as-clnt-msc-0 m3ua
|
||||
asp asp-clnt-msc-0
|
||||
routing-key 0 1.1.1
|
||||
cs7 instance 1
|
||||
point-code 2.2.2
|
||||
|
||||
OsmoHNBGW# ### there are two cs7 instances, but auto-configuration will only use SCCP of instance 0 with local-PC = 1.1.1
|
||||
OsmoHNBGW# show cnlink
|
||||
IuCS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> RI=SSN_PC,PC=0.23.1,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
IuPS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> RI=SSN_PC,PC=0.23.4,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
|
||||
OsmoHNBGW# show running-config
|
||||
...
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
cs7 instance 1
|
||||
point-code 2.2.2
|
||||
hnbgw
|
||||
...
|
||||
msc 0
|
||||
sgsn 0
|
||||
...
|
||||
16
tests/config/two_cs7_with_addrs.cfg
Normal file
16
tests/config/two_cs7_with_addrs.cfg
Normal file
@@ -0,0 +1,16 @@
|
||||
# two cs7 instances, both will be auto-configured for ASP and AS because address book entries point at both.
|
||||
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
sccp-address my-msc
|
||||
point-code 1.2.3
|
||||
|
||||
cs7 instance 1
|
||||
point-code 2.2.2
|
||||
sccp-address my-sgsn
|
||||
point-code 2.4.2
|
||||
|
||||
msc 0
|
||||
remote-addr my-msc
|
||||
sgsn 0
|
||||
remote-addr my-sgsn
|
||||
62
tests/config/two_cs7_with_addrs.vty
Normal file
62
tests/config/two_cs7_with_addrs.vty
Normal file
@@ -0,0 +1,62 @@
|
||||
OsmoHNBGW> enable
|
||||
|
||||
OsmoHNBGW# ### two cs7 instances, both have been auto-configured for ASP and AS because address book entries point at both.
|
||||
OsmoHNBGW# show cs7 config
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
asp asp-clnt-msc-0 2905 0 m3ua
|
||||
local-ip localhost
|
||||
remote-ip localhost
|
||||
role asp
|
||||
sctp-role client
|
||||
as as-clnt-msc-0 m3ua
|
||||
asp asp-clnt-msc-0
|
||||
routing-key 0 1.1.1
|
||||
sccp-address my-msc
|
||||
routing-indicator PC
|
||||
point-code 1.2.3
|
||||
cs7 instance 1
|
||||
point-code 2.2.2
|
||||
asp asp-clnt-sgsn-0 2905 0 m3ua
|
||||
local-ip localhost
|
||||
remote-ip localhost
|
||||
role asp
|
||||
sctp-role client
|
||||
as as-clnt-sgsn-0 m3ua
|
||||
asp asp-clnt-sgsn-0
|
||||
routing-key 0 2.2.2
|
||||
sccp-address my-sgsn
|
||||
routing-indicator PC
|
||||
point-code 2.4.2
|
||||
|
||||
OsmoHNBGW# ### IuCS on instance 0 with local-PC = 1.1.1, IuPS on instance 1 with local-PC = 2.2.2
|
||||
OsmoHNBGW# show cnlink
|
||||
IuCS: OsmoHNBGW:RI=SSN_PC,PC=1.1.1,SSN=RANAP <-> my-msc=RI=SSN_PC,PC=1.2.3,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-msc-0 proto=m3ua ASP asp-clnt-msc-0
|
||||
RANAP state: DISCONNECTED
|
||||
IuPS: OsmoHNBGW:RI=SSN_PC,PC=2.2.2,SSN=RANAP <-> my-sgsn=RI=SSN_PC,PC=2.4.2,SSN=RANAP
|
||||
SS7 route: pc=0=0.0.0 mask=0x0=0.0.0 via AS as-clnt-sgsn-0 proto=m3ua ASP asp-clnt-sgsn-0
|
||||
RANAP state: DISCONNECTED
|
||||
|
||||
OsmoHNBGW# show running-config
|
||||
...
|
||||
cs7 instance 0
|
||||
point-code 1.1.1
|
||||
sccp-address my-msc
|
||||
routing-indicator PC
|
||||
point-code 1.2.3
|
||||
cs7 instance 1
|
||||
point-code 2.2.2
|
||||
sccp-address my-sgsn
|
||||
routing-indicator PC
|
||||
point-code 2.4.2
|
||||
hnbgw
|
||||
rnc-id 23
|
||||
log-prefix hnb-id
|
||||
iuh
|
||||
...
|
||||
msc 0
|
||||
remote-addr my-msc
|
||||
sgsn 0
|
||||
remote-addr my-sgsn
|
||||
...
|
||||
@@ -1,207 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# (C) 2013 by Jacob Erlbeck <jerlbeck@sysmocom.de>
|
||||
# (C) 2014 by Holger Hans Peter Freyther
|
||||
# based on vty_test_runner.py:
|
||||
# (C) 2013 by Katerina Barone-Adesi <kat.obsc@gmail.com>
|
||||
# (C) 2013 by Holger Hans Peter Freyther
|
||||
# based on bsc_control.py.
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import time
|
||||
import unittest
|
||||
import socket
|
||||
import sys
|
||||
import struct
|
||||
|
||||
import osmopy.obscvty as obscvty
|
||||
import osmopy.osmoutil as osmoutil
|
||||
from osmopy.osmo_ipa import Ctrl, IPA
|
||||
|
||||
# to be able to find $top_srcdir/doc/...
|
||||
confpath = os.path.join(sys.path[0], '..')
|
||||
verbose = False
|
||||
|
||||
class TestCtrlBase(unittest.TestCase):
|
||||
|
||||
def ctrl_command(self):
|
||||
raise Exception("Needs to be implemented by a subclass")
|
||||
|
||||
def ctrl_app(self):
|
||||
raise Exception("Needs to be implemented by a subclass")
|
||||
|
||||
def setUp(self):
|
||||
osmo_ctrl_cmd = self.ctrl_command()[:]
|
||||
config_index = osmo_ctrl_cmd.index('-c')
|
||||
if config_index:
|
||||
cfi = config_index + 1
|
||||
osmo_ctrl_cmd[cfi] = os.path.join(confpath, osmo_ctrl_cmd[cfi])
|
||||
|
||||
try:
|
||||
self.proc = osmoutil.popen_devnull(osmo_ctrl_cmd)
|
||||
except OSError:
|
||||
print("Current directory: %s" % os.getcwd(), file=sys.stderr)
|
||||
print("Consider setting -b", file=sys.stderr)
|
||||
|
||||
appstring = self.ctrl_app()[2]
|
||||
appport = self.ctrl_app()[0]
|
||||
self.connect("127.0.0.1", appport)
|
||||
self.next_id = 1000
|
||||
|
||||
def tearDown(self):
|
||||
self.disconnect()
|
||||
osmoutil.end_proc(self.proc)
|
||||
|
||||
def disconnect(self):
|
||||
if not (self.sock is None):
|
||||
self.sock.close()
|
||||
|
||||
def connect(self, host, port):
|
||||
if verbose:
|
||||
print("Connecting to host %s:%i" % (host, port))
|
||||
|
||||
retries = 30
|
||||
while True:
|
||||
try:
|
||||
sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sck.setblocking(1)
|
||||
sck.connect((host, port))
|
||||
except IOError:
|
||||
retries -= 1
|
||||
if retries <= 0:
|
||||
raise
|
||||
time.sleep(.1)
|
||||
continue
|
||||
break
|
||||
self.sock = sck
|
||||
return sck
|
||||
|
||||
def send(self, data):
|
||||
if verbose:
|
||||
print("Sending \"%s\"" %(data))
|
||||
data = Ctrl().add_header(data)
|
||||
return self.sock.send(data) == len(data)
|
||||
|
||||
def send_set(self, var, value, id):
|
||||
setmsg = "SET %s %s %s" %(id, var, value)
|
||||
return self.send(setmsg)
|
||||
|
||||
def send_get(self, var, id):
|
||||
getmsg = "GET %s %s" %(id, var)
|
||||
return self.send(getmsg)
|
||||
|
||||
def do_set(self, var, value):
|
||||
id = self.next_id
|
||||
self.next_id += 1
|
||||
self.send_set(var, value, id)
|
||||
return self.recv_msgs()[id]
|
||||
|
||||
def do_get(self, var):
|
||||
id = self.next_id
|
||||
self.next_id += 1
|
||||
self.send_get(var, id)
|
||||
return self.recv_msgs()[id]
|
||||
|
||||
def recv_msgs(self):
|
||||
responses = {}
|
||||
data = self.sock.recv(4096)
|
||||
while (len(data)>0):
|
||||
(head, data) = IPA().split_combined(data)
|
||||
answer = Ctrl().rem_header(head).decode()
|
||||
if verbose:
|
||||
print("Got message:", answer)
|
||||
(mtype, id, msg) = answer.split(None, 2)
|
||||
id = int(id)
|
||||
rsp = {'mtype': mtype, 'id': id}
|
||||
if mtype == "ERROR":
|
||||
rsp['error'] = msg
|
||||
else:
|
||||
split = msg.split(None, 1)
|
||||
rsp['var'] = split[0]
|
||||
if len(split) > 1:
|
||||
rsp['value'] = split[1]
|
||||
else:
|
||||
rsp['value'] = None
|
||||
responses[id] = rsp
|
||||
|
||||
if verbose:
|
||||
print("Decoded replies: ", responses)
|
||||
|
||||
return responses
|
||||
|
||||
|
||||
class TestCtrlHNB(TestCtrlBase):
|
||||
|
||||
def tearDown(self):
|
||||
TestCtrlBase.tearDown(self)
|
||||
os.unlink("tmp_dummy_sock")
|
||||
|
||||
def ctrl_command(self):
|
||||
return ["./src/osmo-hnbgw/osmo-hnbgw", "-r", "tmp_dummy_sock", "-c",
|
||||
"doc/examples/osmo-hnbgw/osmo-hnbgw.cfg"]
|
||||
|
||||
def ctrl_app(self):
|
||||
return (4249, "./src/osmo-hnbgw/osmo-hnbgw", "OsmoHNBGW", "hnb")
|
||||
|
||||
def testCtrlErrs(self):
|
||||
r = self.do_get('invalid')
|
||||
self.assertEqual(r['mtype'], 'ERROR')
|
||||
self.assertEqual(r['error'], 'Command not found')
|
||||
|
||||
r = self.do_get('hnbgw.999')
|
||||
self.assertEqual(r['mtype'], 'ERROR')
|
||||
self.assertEqual(r['error'], 'Error while resolving object')
|
||||
|
||||
def add_hnbgw_test(suite, workdir, klass):
|
||||
if not os.path.isfile(os.path.join(workdir, "src/osmo-hnbgw/osmo-hnbgw")):
|
||||
print("Skipping the HNBGW test")
|
||||
return
|
||||
test = unittest.TestLoader().loadTestsFromTestCase(klass)
|
||||
suite.addTest(test)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
workdir = '.'
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-v", "--verbose", dest="verbose",
|
||||
action="store_true", help="verbose mode")
|
||||
parser.add_argument("-p", "--pythonconfpath", dest="p",
|
||||
help="searchpath for config")
|
||||
parser.add_argument("-w", "--workdir", dest="w",
|
||||
help="Working directory")
|
||||
args = parser.parse_args()
|
||||
|
||||
verbose_level = 1
|
||||
if args.verbose:
|
||||
verbose_level = 2
|
||||
verbose = True
|
||||
|
||||
if args.w:
|
||||
workdir = args.w
|
||||
|
||||
if args.p:
|
||||
confpath = args.p
|
||||
|
||||
print("confpath %s, workdir %s" % (confpath, workdir))
|
||||
os.chdir(workdir)
|
||||
print("Running tests for specific control commands")
|
||||
suite = unittest.TestSuite()
|
||||
add_hnbgw_test(suite, workdir, TestCtrlHNB)
|
||||
res = unittest.TextTestRunner(verbosity=verbose_level).run(suite)
|
||||
sys.exit(len(res.errors) + len(res.failures))
|
||||
279
tests/nri_cfg.vty
Normal file
279
tests/nri_cfg.vty
Normal file
@@ -0,0 +1,279 @@
|
||||
OsmoHNBGW> show nri
|
||||
hnbgw
|
||||
iucs
|
||||
nri bitlen 10
|
||||
% No NULL-NRI entries
|
||||
iups
|
||||
nri bitlen 10
|
||||
% No NULL-NRI entries
|
||||
msc 0
|
||||
% no NRI mappings
|
||||
sgsn 0
|
||||
% no NRI mappings
|
||||
|
||||
OsmoHNBGW> enable
|
||||
OsmoHNBGW# configure terminal
|
||||
|
||||
OsmoHNBGW(config)# msc 0
|
||||
|
||||
OsmoHNBGW(config-msc)# list
|
||||
...
|
||||
nri add <0-32767> [<0-32767>]
|
||||
nri del <0-32767> [<0-32767>]
|
||||
show nri
|
||||
...
|
||||
OsmoHNBGW(config-msc)# nri ?
|
||||
add Add NRI value or range to the NRI mapping for this CN link
|
||||
del Remove NRI value or range from the NRI mapping for this CN link
|
||||
OsmoHNBGW(config-msc)# nri add ?
|
||||
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
|
||||
OsmoHNBGW(config-msc)# nri add 23 ?
|
||||
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
|
||||
|
||||
OsmoHNBGW(config-msc)# nri add 23
|
||||
OsmoHNBGW(config-msc)# nri add 256 511
|
||||
OsmoHNBGW(config-msc)# nri add 100 200
|
||||
OsmoHNBGW(config-msc)# nri add 1024 1024
|
||||
% Warning: msc 0: Warning: NRI range surpasses current NRI bitlen: 1024..1024
|
||||
OsmoHNBGW(config-msc)# show nri
|
||||
msc 0
|
||||
nri add 23
|
||||
nri add 100 200
|
||||
nri add 256 511
|
||||
nri add 1024
|
||||
OsmoHNBGW(config-msc)# exit
|
||||
|
||||
OsmoHNBGW(config)# ### Do msc 2 first, to see that the order of mscs in the internal list is not determined by the msc->nr,
|
||||
OsmoHNBGW(config)# ### and whichever was configured first gets higher priority for overlaps.
|
||||
|
||||
OsmoHNBGW(config)# msc 2
|
||||
OsmoHNBGW(config-msc)# nri add 200 300
|
||||
% Warning: msc 2: NRI range [200..300] overlaps between msc 2 and msc 0. For overlaps, msc 0 has higher priority than msc 2
|
||||
OsmoHNBGW(config-msc)# nri add 1024 1025
|
||||
% Warning: msc 2: Warning: NRI range surpasses current NRI bitlen: 1024..1025
|
||||
% Warning: msc 2: NRI range [1024..1025] overlaps between msc 2 and msc 0. For overlaps, msc 0 has higher priority than msc 2
|
||||
OsmoHNBGW(config-msc)# exit
|
||||
|
||||
OsmoHNBGW(config)# msc 1
|
||||
OsmoHNBGW(config-msc)# nri add 42
|
||||
OsmoHNBGW(config-msc)# nri add 512 767
|
||||
OsmoHNBGW(config-msc)# nri add 200 300
|
||||
% Warning: msc 1: NRI range [200..300] overlaps between msc 1 and msc 0. For overlaps, msc 0 has higher priority than msc 1
|
||||
% Warning: msc 1: NRI range [200..300] overlaps between msc 1 and msc 2. For overlaps, msc 2 has higher priority than msc 1
|
||||
OsmoHNBGW(config-msc)# nri add 1024 1025
|
||||
% Warning: msc 1: Warning: NRI range surpasses current NRI bitlen: 1024..1025
|
||||
% Warning: msc 1: NRI range [1024..1025] overlaps between msc 1 and msc 0. For overlaps, msc 0 has higher priority than msc 1
|
||||
% Warning: msc 1: NRI range [1024..1025] overlaps between msc 1 and msc 2. For overlaps, msc 2 has higher priority than msc 1
|
||||
OsmoHNBGW(config-msc)# show nri
|
||||
msc 1
|
||||
nri add 42
|
||||
nri add 200 300
|
||||
nri add 512 767
|
||||
nri add 1024 1025
|
||||
OsmoHNBGW(config-msc)# exit
|
||||
|
||||
OsmoHNBGW(config)# do show nri
|
||||
hnbgw
|
||||
iucs
|
||||
nri bitlen 10
|
||||
% No NULL-NRI entries
|
||||
iups
|
||||
nri bitlen 10
|
||||
% No NULL-NRI entries
|
||||
msc 0
|
||||
nri add 23
|
||||
nri add 100 200
|
||||
nri add 256 511
|
||||
nri add 1024
|
||||
msc 2
|
||||
nri add 200 300
|
||||
nri add 1024 1025
|
||||
msc 1
|
||||
nri add 42
|
||||
nri add 200 300
|
||||
nri add 512 767
|
||||
nri add 1024 1025
|
||||
sgsn 0
|
||||
% no NRI mappings
|
||||
|
||||
OsmoHNBGW(config)# ### msc and sgsn have separate scopes of NRI, i.e. overlaps are no problem
|
||||
OsmoHNBGW(config)# sgsn 0
|
||||
OsmoHNBGW(config-sgsn)# nri add 0 1023
|
||||
OsmoHNBGW(config-sgsn)# show nri
|
||||
sgsn 0
|
||||
nri add 0 1023
|
||||
OsmoHNBGW(config-sgsn)# exit
|
||||
|
||||
OsmoHNBGW(config)# ### NULL-NRI config
|
||||
OsmoHNBGW(config)# hnbgw
|
||||
OsmoHNBGW(config-hnbgw)# iucs
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# list
|
||||
...
|
||||
nri bitlen <1-15>
|
||||
nri null add <0-32767> [<0-32767>]
|
||||
nri null del <0-32767> [<0-32767>]
|
||||
...
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri ?
|
||||
bitlen Set number of bits that an NRI has, to extract from TMSI identities (always starting just after the TMSI's most significant octet).
|
||||
null Define NULL-NRI values that cause re-assignment of an MS to a different CN peer, for CN pooling.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri bitlen ?
|
||||
<1-15> bit count (default: 10)
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri bitlen 11
|
||||
OsmoHNBGW(config-hnbgw-iucs)# show running-config
|
||||
...
|
||||
hnbgw
|
||||
...
|
||||
iucs
|
||||
...
|
||||
nri bitlen 11
|
||||
...
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri null ?
|
||||
add Add NULL-NRI value (or range)
|
||||
del Remove NRI value or range from the NRI mapping for this CN link
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri null add ?
|
||||
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri null add 0 ?
|
||||
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri null del ?
|
||||
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
|
||||
OsmoHNBGW(config-hnbgw-iucs)# nri null del 0 ?
|
||||
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iucs)# exit
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# iups
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# list
|
||||
...
|
||||
nri bitlen <1-15>
|
||||
nri null add <0-32767> [<0-32767>]
|
||||
nri null del <0-32767> [<0-32767>]
|
||||
...
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri ?
|
||||
bitlen Set number of bits that an NRI has, to extract from TMSI identities (always starting just after the TMSI's most significant octet).
|
||||
null Define NULL-NRI values that cause re-assignment of an MS to a different CN peer, for CN pooling.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri bitlen ?
|
||||
<1-15> bit count (default: 10)
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri bitlen 9
|
||||
OsmoHNBGW(config-hnbgw-iups)# show running-config
|
||||
...
|
||||
hnbgw
|
||||
...
|
||||
iups
|
||||
...
|
||||
nri bitlen 9
|
||||
...
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri null ?
|
||||
add Add NULL-NRI value (or range)
|
||||
del Remove NRI value or range from the NRI mapping for this CN link
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri null add ?
|
||||
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri null add 0 ?
|
||||
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri null del ?
|
||||
<0-32767> First value of the NRI value range, should not surpass the configured 'nri bitlen'.
|
||||
OsmoHNBGW(config-hnbgw-iups)# nri null del 0 ?
|
||||
[<0-32767>] Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the first value; if omitted, apply only the first value.
|
||||
|
||||
OsmoHNBGW(config-hnbgw-iups)# exit
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# exit
|
||||
|
||||
OsmoHNBGW(config)# msc 0
|
||||
OsmoHNBGW(config-msc)# nri del 0 10000
|
||||
OsmoHNBGW(config-msc)# exit
|
||||
OsmoHNBGW(config)# msc 1
|
||||
OsmoHNBGW(config-msc)# nri del 0 10000
|
||||
OsmoHNBGW(config-msc)# exit
|
||||
OsmoHNBGW(config)# msc 2
|
||||
OsmoHNBGW(config-msc)# nri del 0 10000
|
||||
OsmoHNBGW(config-msc)# exit
|
||||
OsmoHNBGW(config)# do show nri
|
||||
hnbgw
|
||||
iucs
|
||||
nri bitlen 11
|
||||
% No NULL-NRI entries
|
||||
iups
|
||||
nri bitlen 9
|
||||
% No NULL-NRI entries
|
||||
msc 0
|
||||
% no NRI mappings
|
||||
msc 2
|
||||
% no NRI mappings
|
||||
msc 1
|
||||
% no NRI mappings
|
||||
sgsn 0
|
||||
nri add 0 1023
|
||||
|
||||
OsmoHNBGW(config)# msc 0
|
||||
OsmoHNBGW(config-msc)# nri add 0 1000
|
||||
OsmoHNBGW(config-msc)# show nri
|
||||
msc 0
|
||||
nri add 0 1000
|
||||
OsmoHNBGW(config-msc)# nri del 23
|
||||
OsmoHNBGW(config-msc)# nri del 200 300
|
||||
OsmoHNBGW(config-msc)# nri del 1000 2000
|
||||
OsmoHNBGW(config-msc)# show nri
|
||||
msc 0
|
||||
nri add 0 22
|
||||
nri add 24 199
|
||||
nri add 301 999
|
||||
OsmoHNBGW(config-msc)# nri add 23
|
||||
OsmoHNBGW(config-msc)# show nri
|
||||
msc 0
|
||||
nri add 0 199
|
||||
nri add 301 999
|
||||
OsmoHNBGW(config-msc)# nri add 200 300
|
||||
OsmoHNBGW(config-msc)# show nri
|
||||
msc 0
|
||||
nri add 0 999
|
||||
OsmoHNBGW(config-msc)# nri add 1000
|
||||
OsmoHNBGW(config-msc)# show nri
|
||||
msc 0
|
||||
nri add 0 1000
|
||||
|
||||
OsmoHNBGW(config-msc)# show running-config
|
||||
... ! no allow-attach
|
||||
OsmoHNBGW(config-msc)# no allow-attach
|
||||
OsmoHNBGW(config-msc)# show running-config
|
||||
...
|
||||
msc 0
|
||||
...
|
||||
nri add 0 1000
|
||||
no allow-attach
|
||||
... ! no allow-attach
|
||||
OsmoHNBGW(config-msc)# exit
|
||||
OsmoHNBGW(config)# sgsn 1
|
||||
OsmoHNBGW(config-sgsn)# no allow-attach
|
||||
OsmoHNBGW(config-sgsn)# show running-config
|
||||
...
|
||||
msc 0
|
||||
...
|
||||
nri add 0 1000
|
||||
no allow-attach
|
||||
... ! no allow-attach
|
||||
sgsn 1
|
||||
...
|
||||
no allow-attach
|
||||
...
|
||||
|
||||
OsmoHNBGW(config-sgsn)# allow-attach
|
||||
OsmoHNBGW(config-sgsn)# exit
|
||||
OsmoHNBGW(config)# msc 0
|
||||
OsmoHNBGW(config-msc)# allow-attach
|
||||
OsmoHNBGW(config-msc)# show running-config
|
||||
... ! no allow-attach
|
||||
OsmoHNBGW(config-msc)# exit
|
||||
39
tests/osmo-hnbgw-vty-test.cfg
Normal file
39
tests/osmo-hnbgw-vty-test.cfg
Normal file
@@ -0,0 +1,39 @@
|
||||
log stderr
|
||||
logging filter all 1
|
||||
logging color 1
|
||||
logging print level 1
|
||||
logging print category 1
|
||||
logging print category-hex 0
|
||||
logging print file basename last
|
||||
logging print extended-timestamp 1
|
||||
logging level set-all notice
|
||||
|
||||
# Define some cs7 address book entries to play with
|
||||
cs7 instance 0
|
||||
sccp-address addr-msc0
|
||||
point-code 0.1.0
|
||||
sccp-address addr-msc2
|
||||
point-code 0.1.2
|
||||
sccp-address addr-sgsn3
|
||||
point-code 0.2.3
|
||||
sccp-address addr-sgsn4
|
||||
point-code 0.2.4
|
||||
|
||||
cs7 instance 1
|
||||
sccp-address addr-sgsn0
|
||||
point-code 1.2.0
|
||||
sccp-address addr-sgsn2
|
||||
point-code 1.2.2
|
||||
sccp-address addr-msc3
|
||||
point-code 1.1.3
|
||||
sccp-address addr-msc4
|
||||
point-code 1.1.4
|
||||
|
||||
hnbgw
|
||||
iuh
|
||||
local-ip 0.0.0.0
|
||||
mgw 0
|
||||
remote-ip 127.0.0.1
|
||||
local-port 2729
|
||||
remote-port 2427
|
||||
reset-endpoint rtpbridge/*
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user