mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-hlr.git
synced 2025-11-02 21:23:30 +00:00
Compare commits
226 Commits
0.1.0
...
neels/dgsm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
045d7be024 | ||
|
|
adeb6e7b5f | ||
|
|
0f2cfe6c79 | ||
|
|
288a9ddd21 | ||
|
|
f3598c4b2c | ||
|
|
7ca9bc517a | ||
|
|
37ec56d39c | ||
|
|
f91e26ed02 | ||
|
|
2975bcbc2f | ||
|
|
749e42902d | ||
|
|
b0c00522b0 | ||
|
|
613e1b87f0 | ||
|
|
0a5eac957a | ||
|
|
82f4521319 | ||
|
|
c1863eb53b | ||
|
|
3e142ab230 | ||
|
|
774d114635 | ||
|
|
40b52d9b58 | ||
|
|
30c1590599 | ||
|
|
e8e2388ea7 | ||
|
|
f83c5a85e3 | ||
|
|
359bbcb9f7 | ||
|
|
5b65461d68 | ||
|
|
f8ad67e7fc | ||
|
|
c3d40326ec | ||
|
|
a9f8a4bdce | ||
|
|
f5459de2e2 | ||
|
|
7d2843df4c | ||
|
|
2b0bf31183 | ||
|
|
28f0774e34 | ||
|
|
b07f33df41 | ||
|
|
8b860e54be | ||
|
|
9cf0030b6a | ||
|
|
b9b224c7bd | ||
|
|
e49391bfc4 | ||
|
|
fbd736ef37 | ||
|
|
dc30154fdf | ||
|
|
37642177f9 | ||
|
|
6401b90574 | ||
|
|
5b5cac7e94 | ||
|
|
937f583a7e | ||
|
|
4ca7f6a17e | ||
|
|
b64cb27003 | ||
|
|
3b33b01fb0 | ||
|
|
78abea6a0e | ||
|
|
9ac494f486 | ||
|
|
d62d401d07 | ||
|
|
103c11bd24 | ||
|
|
63de00cfc1 | ||
|
|
1a1398ed54 | ||
|
|
a8253a54ba | ||
|
|
29f371fddf | ||
|
|
2e403d6c3f | ||
|
|
c41572330d | ||
|
|
c7f1787c18 | ||
|
|
c13599dc69 | ||
|
|
6b73fd9678 | ||
|
|
cd2af5ead7 | ||
|
|
e21b45aecd | ||
|
|
5857c595b3 | ||
|
|
d9724f4298 | ||
|
|
c69a18bb3d | ||
|
|
8625cdaf2a | ||
|
|
609978d0ab | ||
|
|
28f0af872e | ||
|
|
9f6e558215 | ||
|
|
633fe291f5 | ||
|
|
7d53ae1db8 | ||
|
|
95abc2be17 | ||
|
|
f1fe94c8ca | ||
|
|
f7d3251d9a | ||
|
|
3cf87fe22c | ||
|
|
ee7c0cb8d9 | ||
|
|
c5044cfd80 | ||
|
|
20ddfdbc53 | ||
|
|
227834b6bc | ||
|
|
44a2180009 | ||
|
|
f9cf180ebe | ||
|
|
02078b7d91 | ||
|
|
ef64b231dc | ||
|
|
851814aa7c | ||
|
|
81db389fd4 | ||
|
|
7943e26938 | ||
|
|
e0c6fe5921 | ||
|
|
f58f44543f | ||
|
|
15f624ec53 | ||
|
|
d4e0e4d503 | ||
|
|
2dc7d960a1 | ||
|
|
52c4aa09b2 | ||
|
|
66106c0992 | ||
|
|
783ac81b9c | ||
|
|
df8d454919 | ||
|
|
9ea9bbbc7f | ||
|
|
5c14c9ccca | ||
|
|
705b61bcb7 | ||
|
|
638ba8cc04 | ||
|
|
55f5efa568 | ||
|
|
e6ce52bbde | ||
|
|
d157a56361 | ||
|
|
9c8806acf5 | ||
|
|
4b8be4d12d | ||
|
|
bc9bead62a | ||
|
|
4655e6f1fe | ||
|
|
8f3a7cce80 | ||
|
|
a820ea1f67 | ||
|
|
8aa780bf80 | ||
|
|
f08da2459b | ||
|
|
62ce834fbf | ||
|
|
bf6b4eb0b9 | ||
|
|
79efdf3474 | ||
|
|
b41394a700 | ||
|
|
0c331abdbc | ||
|
|
25e716c849 | ||
|
|
92e49ef363 | ||
|
|
95380ab037 | ||
|
|
849bfd0bef | ||
|
|
7e2d3c7f4c | ||
|
|
8f725ae655 | ||
|
|
1ed4bb4ff1 | ||
|
|
25dd785157 | ||
|
|
4f5f6f83f3 | ||
|
|
e66e525e09 | ||
|
|
4a4bdcdf97 | ||
|
|
cb364bb429 | ||
|
|
d646207553 | ||
|
|
6cee799d5e | ||
|
|
c88bdab96d | ||
|
|
607ce5ca93 | ||
|
|
ccdb970c57 | ||
|
|
a5b36a0904 | ||
|
|
13000d8d14 | ||
|
|
7ebfd065da | ||
|
|
966fcb2ca8 | ||
|
|
6fe1c2220a | ||
|
|
0da9f2f19c | ||
|
|
1eb9869d81 | ||
|
|
3adb33de93 | ||
|
|
791ea72ee4 | ||
|
|
9f7b69a618 | ||
|
|
e6c839ed2d | ||
|
|
b93c44f32e | ||
|
|
a05efe8803 | ||
|
|
764514198b | ||
|
|
5198609a5e | ||
|
|
7c2f430fc5 | ||
|
|
633fddebcd | ||
|
|
7c5e930aa8 | ||
|
|
83df349045 | ||
|
|
05fe0233d2 | ||
|
|
2781bb767e | ||
|
|
f473c7b23c | ||
|
|
dab544e14b | ||
|
|
7d29d59292 | ||
|
|
55d32a1e3c | ||
|
|
95b96d4245 | ||
|
|
9b6bc9e479 | ||
|
|
7f32f5f3e6 | ||
|
|
7266731eca | ||
|
|
97bfb65eeb | ||
|
|
bb77939a86 | ||
|
|
4956ae1f70 | ||
|
|
d5807b8c87 | ||
|
|
21c14fc7f4 | ||
|
|
050eb1d803 | ||
|
|
dc17e05e28 | ||
|
|
953d27ce8f | ||
|
|
ec6915a771 | ||
|
|
9fdb854174 | ||
|
|
4793a7efc3 | ||
|
|
527d934807 | ||
|
|
6b274b95fc | ||
|
|
edca4f88a6 | ||
|
|
b9c1028cb0 | ||
|
|
1442e3a3d3 | ||
|
|
0b8f054b5f | ||
|
|
fa7ee333f7 | ||
|
|
8fbf82b83f | ||
|
|
bd72f1331d | ||
|
|
32acace879 | ||
|
|
b85f60477f | ||
|
|
a1d3b048fb | ||
|
|
f83432c25c | ||
|
|
78f4301025 | ||
|
|
1b8a1dc00a | ||
|
|
9d307ec7ae | ||
|
|
5aeb438194 | ||
|
|
cb360f06c8 | ||
|
|
1cb489231a | ||
|
|
93c5b10310 | ||
|
|
ec9440f1bc | ||
|
|
51530311a8 | ||
|
|
f162252a08 | ||
|
|
8f8401453c | ||
|
|
70e7f21cb1 | ||
|
|
2e78858756 | ||
|
|
3f2a9a2ab1 | ||
|
|
880a34d2ef | ||
|
|
7ee6e554af | ||
|
|
9214c6c7ef | ||
|
|
79fc6984ac | ||
|
|
ba1605afdc | ||
|
|
b6265d1a55 | ||
|
|
6c84da5942 | ||
|
|
84c2f43d00 | ||
|
|
63f68ccc4c | ||
|
|
b5d77012d1 | ||
|
|
fc96f688d4 | ||
|
|
43bd6069e8 | ||
|
|
2dee60ef44 | ||
|
|
ab4d509a83 | ||
|
|
bd1dca0859 | ||
|
|
33eeeef9dc | ||
|
|
33cbde9ced | ||
|
|
671db90ac3 | ||
|
|
d3814b936b | ||
|
|
6f3e8d6297 | ||
|
|
db5dae6c99 | ||
|
|
38f08770a3 | ||
|
|
aee7be901b | ||
|
|
8db490695d | ||
|
|
c82e6ad190 | ||
|
|
0959e8b354 | ||
|
|
4f3841c153 | ||
|
|
bd0d5bf5d8 | ||
|
|
87a04b6b95 | ||
|
|
85e8a64bb4 |
36
.gitignore
vendored
36
.gitignore
vendored
@@ -1,8 +1,13 @@
|
||||
*.o
|
||||
*.lo
|
||||
*.la
|
||||
*.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
*.pyc
|
||||
.*.sw?
|
||||
.version
|
||||
.tarball-version
|
||||
Makefile
|
||||
Makefile.in
|
||||
aclocal.m4
|
||||
@@ -22,10 +27,37 @@ m4
|
||||
missing
|
||||
.deps
|
||||
|
||||
src/osmo-hlr
|
||||
src/db_test
|
||||
*.pc
|
||||
.libs
|
||||
|
||||
src/db_test
|
||||
src/db_bootstrap.h
|
||||
src/osmo-hlr
|
||||
src/osmo-hlr-db-tool
|
||||
src/osmo-euse-demo
|
||||
src/gsupclient/gsup-test-client
|
||||
|
||||
tests/atconfig
|
||||
tests/testsuite
|
||||
tests/testsuite.log
|
||||
tests/testsuite.dir
|
||||
|
||||
tests/auc/auc_3g_test
|
||||
tests/auc/auc_ts_55_205_test_sets.c
|
||||
tests/auc/auc_ts_55_205_test_sets
|
||||
tests/auc/auc_test
|
||||
tests/gsup_server/gsup_server_test
|
||||
tests/gsup/gsup_test
|
||||
tests/db/db_test
|
||||
tests/hlr_vty_test.db*
|
||||
|
||||
# manuals
|
||||
doc/manuals/*.html
|
||||
doc/manuals/*.svg
|
||||
doc/manuals/*.pdf
|
||||
doc/manuals/*__*.png
|
||||
doc/manuals/*.check
|
||||
doc/manuals/generated/
|
||||
doc/manuals/osmomsc-usermanual.xml
|
||||
doc/manuals/common
|
||||
doc/manuals/build
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
AUTOMAKE_OPTIONS = foreign dist-bzip2
|
||||
|
||||
SUBDIRS = \
|
||||
doc \
|
||||
src \
|
||||
include \
|
||||
sql \
|
||||
contrib \
|
||||
tests \
|
||||
$(NULL)
|
||||
|
||||
@@ -10,6 +13,12 @@ EXTRA_DIST = \
|
||||
.version \
|
||||
$(NULL)
|
||||
|
||||
AM_DISTCHECK_CONFIGURE_FLAGS = \
|
||||
--with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
|
||||
|
||||
pkgconfigdir = $(libdir)/pkgconfig
|
||||
pkgconfig_DATA = libosmo-gsup-client.pc
|
||||
|
||||
@RELMAKE@
|
||||
|
||||
BUILT_SOURCES = $(top_srcdir)/.version
|
||||
|
||||
127
configure.ac
127
configure.ac
@@ -34,11 +34,12 @@ PKG_PROG_PKG_CONFIG([0.20])
|
||||
|
||||
PKG_CHECK_MODULES(TALLOC, [talloc >= 2.0.1])
|
||||
|
||||
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.9.5)
|
||||
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.9.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.9.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl)
|
||||
PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.3.2)
|
||||
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.2.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.2.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.2.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.2.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOMSLOOKUP, libosmomslookup >= 1.2.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.6.0)
|
||||
|
||||
PKG_CHECK_MODULES(SQLITE3, sqlite3)
|
||||
|
||||
@@ -47,6 +48,51 @@ AC_CONFIG_MACRO_DIR([m4])
|
||||
dnl checks for header files
|
||||
AC_HEADER_STDC
|
||||
|
||||
AC_ARG_ENABLE(sanitize,
|
||||
[AS_HELP_STRING(
|
||||
[--enable-sanitize],
|
||||
[Compile with address sanitizer enabled],
|
||||
)],
|
||||
[sanitize=$enableval], [sanitize="no"])
|
||||
if test x"$sanitize" = x"yes"
|
||||
then
|
||||
CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined"
|
||||
CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined"
|
||||
fi
|
||||
|
||||
AC_ARG_ENABLE([sqlite_talloc],
|
||||
AC_HELP_STRING([--enable-sqlite-talloc],
|
||||
[Configure SQLite3 to use talloc memory allocator [default=no]]),
|
||||
[sqlite_talloc="$enableval"],[sqlite_talloc="no"])
|
||||
if test "x$sqlite_talloc" = "xyes" ; then
|
||||
# Older versions of SQLite3 (at least 3.8.2) become unstable with talloc.
|
||||
# Feel free to relax to 3.24.0 > VER > 3.8.2 if it works for you.
|
||||
# FIXME: PKG_CHECK_MODULES() may return cached result here!
|
||||
PKG_CHECK_MODULES(SQLITE3, sqlite3 >= 3.24.0)
|
||||
AC_DEFINE([SQLITE_USE_TALLOC], 1, [Use talloc for SQLite3])
|
||||
fi
|
||||
AC_MSG_CHECKING([whether to use talloc for SQLite3])
|
||||
AC_MSG_RESULT([$sqlite_talloc])
|
||||
AM_CONDITIONAL([DB_SQLITE_DEBUG], [test "x$sqlite_talloc" = "xyes"])
|
||||
|
||||
AC_ARG_ENABLE(werror,
|
||||
[AS_HELP_STRING(
|
||||
[--enable-werror],
|
||||
[Turn all compiler warnings into errors, with exceptions:
|
||||
a) deprecation (allow upstream to mark deprecation without breaking builds);
|
||||
b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds)
|
||||
]
|
||||
)],
|
||||
[werror=$enableval], [werror="no"])
|
||||
if test x"$werror" = x"yes"
|
||||
then
|
||||
WERROR_FLAGS="-Werror"
|
||||
WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations"
|
||||
WERROR_FLAGS+=" -Wno-error=cpp" # "#warning"
|
||||
CFLAGS="$CFLAGS $WERROR_FLAGS"
|
||||
CPPFLAGS="$CPPFLAGS $WERROR_FLAGS"
|
||||
fi
|
||||
|
||||
AC_ARG_ENABLE([external_tests],
|
||||
AC_HELP_STRING([--enable-external-tests],
|
||||
[Include the VTY/CTRL tests in make check [default=no]]),
|
||||
@@ -62,13 +108,84 @@ AC_MSG_CHECKING([whether to enable VTY/CTRL tests])
|
||||
AC_MSG_RESULT([$enable_ext_tests])
|
||||
AM_CONDITIONAL(ENABLE_EXT_TESTS, test "x$enable_ext_tests" = "xyes")
|
||||
|
||||
# Generate manuals
|
||||
AC_ARG_ENABLE(manuals,
|
||||
[AS_HELP_STRING(
|
||||
[--enable-manuals],
|
||||
[Generate manual PDFs [default=no]],
|
||||
)],
|
||||
[osmo_ac_build_manuals=$enableval], [osmo_ac_build_manuals="no"])
|
||||
AM_CONDITIONAL([BUILD_MANUALS], [test x"$osmo_ac_build_manuals" = x"yes"])
|
||||
AC_ARG_VAR(OSMO_GSM_MANUALS_DIR, [path to common osmo-gsm-manuals files, overriding pkg-config and "../osmo-gsm-manuals"
|
||||
fallback])
|
||||
if test x"$osmo_ac_build_manuals" = x"yes"
|
||||
then
|
||||
# Find OSMO_GSM_MANUALS_DIR (env, pkg-conf, fallback)
|
||||
if test -n "$OSMO_GSM_MANUALS_DIR"; then
|
||||
echo "checking for OSMO_GSM_MANUALS_DIR... $OSMO_GSM_MANUALS_DIR (from env)"
|
||||
else
|
||||
OSMO_GSM_MANUALS_DIR="$($PKG_CONFIG osmo-gsm-manuals --variable=osmogsmmanualsdir 2>/dev/null)"
|
||||
if test -n "$OSMO_GSM_MANUALS_DIR"; then
|
||||
echo "checking for OSMO_GSM_MANUALS_DIR... $OSMO_GSM_MANUALS_DIR (from pkg-conf)"
|
||||
else
|
||||
OSMO_GSM_MANUALS_DIR="../osmo-gsm-manuals"
|
||||
echo "checking for OSMO_GSM_MANUALS_DIR... $OSMO_GSM_MANUALS_DIR (fallback)"
|
||||
fi
|
||||
fi
|
||||
if ! test -d "$OSMO_GSM_MANUALS_DIR"; then
|
||||
AC_MSG_ERROR("OSMO_GSM_MANUALS_DIR does not exist! Install osmo-gsm-manuals or set OSMO_GSM_MANUALS_DIR.")
|
||||
fi
|
||||
|
||||
# Find and run check-depends
|
||||
CHECK_DEPENDS="$OSMO_GSM_MANUALS_DIR/check-depends.sh"
|
||||
if ! test -x "$CHECK_DEPENDS"; then
|
||||
CHECK_DEPENDS="osmo-gsm-manuals-check-depends"
|
||||
fi
|
||||
if ! $CHECK_DEPENDS; then
|
||||
AC_MSG_ERROR("missing dependencies for --enable-manuals")
|
||||
fi
|
||||
|
||||
# Put in Makefile with absolute path
|
||||
OSMO_GSM_MANUALS_DIR="$(realpath "$OSMO_GSM_MANUALS_DIR")"
|
||||
AC_SUBST([OSMO_GSM_MANUALS_DIR])
|
||||
fi
|
||||
|
||||
# https://www.freedesktop.org/software/systemd/man/daemon.html
|
||||
AC_ARG_WITH([systemdsystemunitdir],
|
||||
[AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],,
|
||||
[with_systemdsystemunitdir=auto])
|
||||
AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [
|
||||
def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)
|
||||
|
||||
AS_IF([test "x$def_systemdsystemunitdir" = "x"],
|
||||
[AS_IF([test "x$with_systemdsystemunitdir" = "xyes"],
|
||||
[AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])])
|
||||
with_systemdsystemunitdir=no],
|
||||
[with_systemdsystemunitdir="$def_systemdsystemunitdir"])])
|
||||
AS_IF([test "x$with_systemdsystemunitdir" != "xno"],
|
||||
[AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])])
|
||||
AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"])
|
||||
|
||||
AC_MSG_RESULT([CFLAGS="$CFLAGS"])
|
||||
AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"])
|
||||
|
||||
AC_OUTPUT(
|
||||
Makefile
|
||||
doc/Makefile
|
||||
doc/examples/Makefile
|
||||
src/Makefile
|
||||
src/gsupclient/Makefile
|
||||
include/Makefile
|
||||
libosmo-gsup-client.pc
|
||||
sql/Makefile
|
||||
doc/manuals/Makefile
|
||||
contrib/Makefile
|
||||
contrib/systemd/Makefile
|
||||
tests/Makefile
|
||||
tests/auc/Makefile
|
||||
tests/auc/gen_ts_55_205_test_sets/Makefile
|
||||
tests/gsup_server/Makefile
|
||||
tests/db/Makefile
|
||||
tests/db_upgrade/Makefile
|
||||
tests/mslookup_manual_test/Makefile
|
||||
)
|
||||
|
||||
1
contrib/Makefile.am
Normal file
1
contrib/Makefile.am
Normal file
@@ -0,0 +1 @@
|
||||
SUBDIRS = systemd
|
||||
278
contrib/ipa.py
278
contrib/ipa.py
@@ -1,278 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- mode: python-mode; py-indent-tabs-mode: nil -*-
|
||||
"""
|
||||
/*
|
||||
* Copyright (C) 2016 sysmocom s.f.m.c. GmbH
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
"""
|
||||
|
||||
import struct, random, sys
|
||||
|
||||
class IPA(object):
|
||||
"""
|
||||
Stateless IPA protocol multiplexer: add/remove/parse (extended) header
|
||||
"""
|
||||
version = "0.0.5"
|
||||
TCP_PORT_OML = 3002
|
||||
TCP_PORT_RSL = 3003
|
||||
# OpenBSC extensions: OSMO, MGCP_OLD
|
||||
PROTO = dict(RSL=0x00, CCM=0xFE, SCCP=0xFD, OML=0xFF, OSMO=0xEE, MGCP_OLD=0xFC)
|
||||
# ...OML Router Control, GSUP GPRS extension, Osmocom Authn Protocol
|
||||
EXT = dict(CTRL=0, MGCP=1, LAC=2, SMSC=3, ORC=4, GSUP=5, OAP=6)
|
||||
# OpenBSC extension: SCCP_OLD
|
||||
MSGT = dict(PING=0x00, PONG=0x01, ID_GET=0x04, ID_RESP=0x05, ID_ACK=0x06, SCCP_OLD=0xFF)
|
||||
_IDTAG = dict(SERNR=0, UNITNAME=1, LOCATION=2, TYPE=3, EQUIPVERS=4, SWVERSION=5, IPADDR=6, MACADDR=7, UNIT=8)
|
||||
CTRL_GET = 'GET'
|
||||
CTRL_SET = 'SET'
|
||||
CTRL_REP = 'REPLY'
|
||||
CTRL_ERR = 'ERR'
|
||||
CTRL_TRAP = 'TRAP'
|
||||
|
||||
def _l(self, d, p):
|
||||
"""
|
||||
Reverse dictionary lookup: return key for a given value
|
||||
"""
|
||||
if p is None:
|
||||
return 'UNKNOWN'
|
||||
return list(d.keys())[list(d.values()).index(p)]
|
||||
|
||||
def _tag(self, t, v):
|
||||
"""
|
||||
Create TAG as TLV data
|
||||
"""
|
||||
return struct.pack(">HB", len(v) + 1, t) + v
|
||||
|
||||
def proto(self, p):
|
||||
"""
|
||||
Lookup protocol name
|
||||
"""
|
||||
return self._l(self.PROTO, p)
|
||||
|
||||
def ext(self, p):
|
||||
"""
|
||||
Lookup protocol extension name
|
||||
"""
|
||||
return self._l(self.EXT, p)
|
||||
|
||||
def msgt(self, p):
|
||||
"""
|
||||
Lookup message type name
|
||||
"""
|
||||
return self._l(self.MSGT, p)
|
||||
|
||||
def idtag(self, p):
|
||||
"""
|
||||
Lookup ID tag name
|
||||
"""
|
||||
return self._l(self._IDTAG, p)
|
||||
|
||||
def ext_name(self, proto, exten):
|
||||
"""
|
||||
Return proper extension byte name depending on the protocol used
|
||||
"""
|
||||
if self.PROTO['CCM'] == proto:
|
||||
return self.msgt(exten)
|
||||
if self.PROTO['OSMO'] == proto:
|
||||
return self.ext(exten)
|
||||
return None
|
||||
|
||||
def add_header(self, data, proto, ext=None):
|
||||
"""
|
||||
Add IPA header (with extension if necessary), data must be represented as bytes
|
||||
"""
|
||||
if ext is None:
|
||||
return struct.pack(">HB", len(data) + 1, proto) + data
|
||||
return struct.pack(">HBB", len(data) + 1, proto, ext) + data
|
||||
|
||||
def del_header(self, data):
|
||||
"""
|
||||
Strip IPA protocol header correctly removing extension if present
|
||||
Returns data length, IPA protocol, extension (or None if not defined for a give protocol) and the data without header
|
||||
"""
|
||||
if not len(data):
|
||||
return None, None, None, None
|
||||
(dlen, proto) = struct.unpack('>HB', data[:3])
|
||||
if self.PROTO['OSMO'] == proto or self.PROTO['CCM'] == proto: # there's extension which we have to unpack
|
||||
return struct.unpack('>HBB', data[:4]) + (data[4:], ) # length, protocol, extension, data
|
||||
return dlen, proto, None, data[3:] # length, protocol, _, data
|
||||
|
||||
def split_combined(self, data):
|
||||
"""
|
||||
Split the data which contains multiple concatenated IPA messages into tuple (first, rest) where rest contains remaining messages, first is the single IPA message
|
||||
"""
|
||||
(length, _, _, _) = self.del_header(data)
|
||||
return data[:(length + 3)], data[(length + 3):]
|
||||
|
||||
def tag_serial(self, data):
|
||||
"""
|
||||
Make TAG for serial number
|
||||
"""
|
||||
return self._tag(self._IDTAG['SERNR'], data)
|
||||
|
||||
def tag_name(self, data):
|
||||
"""
|
||||
Make TAG for unit name
|
||||
"""
|
||||
return self._tag(self._IDTAG['UNITNAME'], data)
|
||||
|
||||
def tag_loc(self, data):
|
||||
"""
|
||||
Make TAG for location
|
||||
"""
|
||||
return self._tag(self._IDTAG['LOCATION'], data)
|
||||
|
||||
def tag_type(self, data):
|
||||
"""
|
||||
Make TAG for unit type
|
||||
"""
|
||||
return self._tag(self._IDTAG['TYPE'], data)
|
||||
|
||||
def tag_equip(self, data):
|
||||
"""
|
||||
Make TAG for equipment version
|
||||
"""
|
||||
return self._tag(self._IDTAG['EQUIPVERS'], data)
|
||||
|
||||
def tag_sw(self, data):
|
||||
"""
|
||||
Make TAG for software version
|
||||
"""
|
||||
return self._tag(self._IDTAG['SWVERSION'], data)
|
||||
|
||||
def tag_ip(self, data):
|
||||
"""
|
||||
Make TAG for IP address
|
||||
"""
|
||||
return self._tag(self._IDTAG['IPADDR'], data)
|
||||
|
||||
def tag_mac(self, data):
|
||||
"""
|
||||
Make TAG for MAC address
|
||||
"""
|
||||
return self._tag(self._IDTAG['MACADDR'], data)
|
||||
|
||||
def tag_unit(self, data):
|
||||
"""
|
||||
Make TAG for unit ID
|
||||
"""
|
||||
return self._tag(self._IDTAG['UNIT'], data)
|
||||
|
||||
def identity(self, unit=b'', mac=b'', location=b'', utype=b'', equip=b'', sw=b'', name=b'', serial=b''):
|
||||
"""
|
||||
Make IPA IDENTITY tag list, by default returns empty concatenated bytes of tag list
|
||||
"""
|
||||
return self.tag_unit(unit) + self.tag_mac(mac) + self.tag_loc(location) + self.tag_type(utype) + self.tag_equip(equip) + self.tag_sw(sw) + self.tag_name(name) + self.tag_serial(serial)
|
||||
|
||||
def ping(self):
|
||||
"""
|
||||
Make PING message
|
||||
"""
|
||||
return self.add_header(b'', self.PROTO['CCM'], self.MSGT['PING'])
|
||||
|
||||
def pong(self):
|
||||
"""
|
||||
Make PONG message
|
||||
"""
|
||||
return self.add_header(b'', self.PROTO['CCM'], self.MSGT['PONG'])
|
||||
|
||||
def id_ack(self):
|
||||
"""
|
||||
Make ID_ACK CCM message
|
||||
"""
|
||||
return self.add_header(b'', self.PROTO['CCM'], self.MSGT['ID_ACK'])
|
||||
|
||||
def id_get(self):
|
||||
"""
|
||||
Make ID_GET CCM message
|
||||
"""
|
||||
return self.add_header(self.identity(), self.PROTO['CCM'], self.MSGT['ID_GET'])
|
||||
|
||||
def id_resp(self, data):
|
||||
"""
|
||||
Make ID_RESP CCM message
|
||||
"""
|
||||
return self.add_header(data, self.PROTO['CCM'], self.MSGT['ID_RESP'])
|
||||
|
||||
class Ctrl(IPA):
|
||||
"""
|
||||
Osmocom CTRL protocol implemented on top of IPA multiplexer
|
||||
"""
|
||||
def __init__(self):
|
||||
random.seed()
|
||||
|
||||
def add_header(self, data):
|
||||
"""
|
||||
Add CTRL header
|
||||
"""
|
||||
return super(Ctrl, self).add_header(data.encode('utf-8'), IPA.PROTO['OSMO'], IPA.EXT['CTRL'])
|
||||
|
||||
def rem_header(self, data):
|
||||
"""
|
||||
Remove CTRL header, check for appropriate protocol and extension
|
||||
"""
|
||||
(_, proto, ext, d) = super(Ctrl, self).del_header(data)
|
||||
if self.PROTO['OSMO'] != proto or self.EXT['CTRL'] != ext:
|
||||
return None
|
||||
return d
|
||||
|
||||
def parse(self, data, op=None):
|
||||
"""
|
||||
Parse Ctrl string returning (var, value) pair
|
||||
var could be None in case of ERROR message
|
||||
value could be None in case of GET message
|
||||
"""
|
||||
(s, i, v) = data.split(' ', 2)
|
||||
if s == self.CTRL_ERR:
|
||||
return None, v
|
||||
if s == self.CTRL_GET:
|
||||
return v, None
|
||||
(s, i, var, val) = data.split(' ', 3)
|
||||
if s == self.CTRL_TRAP and i != '0':
|
||||
return None, '%s with non-zero id %s' % (s, i)
|
||||
if op is not None and i != op:
|
||||
if s == self.CTRL_GET + '_' + self.CTRL_REP or s == self.CTRL_SET + '_' + self.CTRL_REP:
|
||||
return None, '%s with unexpected id %s' % (s, i)
|
||||
return var, val
|
||||
|
||||
def trap(self, var, val):
|
||||
"""
|
||||
Make TRAP message with given (vak, val) pair
|
||||
"""
|
||||
return self.add_header("%s 0 %s %s" % (self.CTRL_TRAP, var, val))
|
||||
|
||||
def cmd(self, var, val=None):
|
||||
"""
|
||||
Make SET/GET command message: returns (r, m) tuple where r is random operation id and m is assembled message
|
||||
"""
|
||||
r = random.randint(1, sys.maxsize)
|
||||
if val is not None:
|
||||
return r, self.add_header("%s %s %s %s" % (self.CTRL_SET, r, var, val))
|
||||
return r, self.add_header("%s %s %s" % (self.CTRL_GET, r, var))
|
||||
|
||||
def verify(self, reply, r, var, val=None):
|
||||
"""
|
||||
Verify reply to SET/GET command: returns (b, v) tuple where v is True/False verification result and v is the variable value
|
||||
"""
|
||||
(k, v) = self.parse(reply)
|
||||
if k != var or (val is not None and v != val):
|
||||
return False, v
|
||||
return True, v
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("IPA multiplexer v%s loaded." % IPA.version)
|
||||
@@ -1,5 +1,10 @@
|
||||
#!/bin/sh
|
||||
# jenkins build helper script for osmo-hlr. This is how we build on jenkins.osmocom.org
|
||||
#
|
||||
# environment variables:
|
||||
# * WITH_MANUALS: build manual PDFs if set to "1"
|
||||
# * PUBLISH: upload manuals after building if set to "1" (ignored without WITH_MANUALS = "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 !"
|
||||
@@ -22,10 +27,18 @@ verify_value_string_arrays_are_terminated.py $(find . -name "*.[hc]")
|
||||
|
||||
export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH"
|
||||
export LD_LIBRARY_PATH="$inst/lib"
|
||||
export PATH="$inst/bin:$PATH"
|
||||
|
||||
osmo-build-dep.sh libosmocore "" ac_cv_path_DOXYGEN=false
|
||||
osmo-build-dep.sh libosmo-abis
|
||||
|
||||
# Additional configure options and depends
|
||||
CONFIG=""
|
||||
if [ "$WITH_MANUALS" = "1" ]; then
|
||||
osmo-build-dep.sh osmo-gsm-manuals
|
||||
CONFIG="--enable-manuals"
|
||||
fi
|
||||
|
||||
set +x
|
||||
echo
|
||||
echo
|
||||
@@ -36,11 +49,14 @@ set -x
|
||||
|
||||
cd "$base"
|
||||
autoreconf --install --force
|
||||
./configure --enable-external-tests
|
||||
./configure --enable-sanitize --enable-external-tests --enable-werror $CONFIG
|
||||
$MAKE $PARALLEL_MAKE
|
||||
if [ "x$label" != "xFreeBSD_amd64" ]; then
|
||||
$MAKE check || cat-testlogs.sh
|
||||
$MAKE distcheck || cat-testlogs.sh
|
||||
$MAKE check || cat-testlogs.sh
|
||||
DISTCHECK_CONFIGURE_FLAGS="$CONFIG" $MAKE distcheck || cat-testlogs.sh
|
||||
|
||||
if [ "$WITH_MANUALS" = "1" ] && [ "$PUBLISH" = "1" ]; then
|
||||
make -C "$base/doc/manuals" publish
|
||||
fi
|
||||
|
||||
$MAKE maintainer-clean
|
||||
osmo-clean-workspace.sh
|
||||
|
||||
6
contrib/systemd/Makefile.am
Normal file
6
contrib/systemd/Makefile.am
Normal file
@@ -0,0 +1,6 @@
|
||||
EXTRA_DIST = osmo-hlr.service
|
||||
|
||||
if HAVE_SYSTEMD
|
||||
systemdsystemunit_DATA = \
|
||||
osmo-hlr.service
|
||||
endif
|
||||
@@ -1,5 +1,6 @@
|
||||
[Unit]
|
||||
Description=Osmocom Home Location Register (OsmoHLR)
|
||||
Documentation=https://osmocom.org/projects/osmo-hlr/wiki/OsmoHLR
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
|
||||
204
debian/changelog
vendored
204
debian/changelog
vendored
@@ -1,3 +1,207 @@
|
||||
osmo-hlr (1.1.0) unstable; urgency=medium
|
||||
|
||||
[ Oliver Smith ]
|
||||
* docs: running: document --db-upgrade
|
||||
* Add IMEI column to subscriber table
|
||||
* Optionally store IMEI in subscriber table
|
||||
* VTY tests: fill DB before running test
|
||||
* VTY: integrate IMEI
|
||||
* hlr.c: replace deprecated osmo_gsup_get_err_msg_type()
|
||||
* hlr.c: move hlr_ctx to the top
|
||||
* tests: use -no-install libtool flag to avoid ./lt-* scripts
|
||||
* Cosmetic: gsup_route_find: comment addr, addrlen
|
||||
* USSD: save MO USSD's originating MSC's vlr_number
|
||||
* USSD: don't use gsm0480_msgb_alloc_name()
|
||||
* hlr.c: forward GSUP messages between clients
|
||||
* db_hlr.c: db_subscr_create(): add flags argument
|
||||
* db_hlr.c: add db_subscr_exists_by_imsi()
|
||||
* Create subscribers on demand
|
||||
* Document subscribers create on demand feature
|
||||
* debian: create -doc subpackage with pdf manuals
|
||||
* db_test: set timezone to work around mktime bug
|
||||
* db_hlr: zero-initialize "struct tm"
|
||||
* rx_check_imei_req(): fix IMEI bounds checking
|
||||
* contrib/jenkins.sh: run "make maintainer-clean"
|
||||
* VTY: add subscriber update network-access-mode
|
||||
* manuals: improve subscribers create on demand
|
||||
* gitignore: ignore everything generated in db_test
|
||||
* db_auc.c: verify hex key sizes read from DB
|
||||
|
||||
[ Max ]
|
||||
* Log ip:port when adding GSUP routes
|
||||
* Add link to project wiki to .service file
|
||||
* Enable statsd support
|
||||
|
||||
[ Vadim Yanitskiy ]
|
||||
* hlr.c: properly terminate the process on SIGTERM
|
||||
* hlr.c: fix: also store the session state in read_cb_forward()
|
||||
* hlr.c: fix: properly print the original message type in read_cb_forward()
|
||||
* hlr.c: check the presence of msgb->l2h in read_cb()
|
||||
* hlr.c: fix possible msgb memleaks in read_cb()
|
||||
* db_hlr.c: add db_subscr_exists_by_msisdn()
|
||||
* src/db.h: use GSM23003_MSISDN_MAX_DIGITS for MSISDN buffer size
|
||||
* src/hlr.c: fix deprecation warning: use gsm48_decode_bcd_number2()
|
||||
* hlr_ussd.c: fix: properly pass invokeID in handle_ussd_own_msisdn()
|
||||
* hlr_ussd.c: rx_proc_ss_req(): fix NULL pointer dereference
|
||||
* build: fix mess with 'db_test_SOURCES' and 'db_test_LDADD'
|
||||
* tests/db_test: close the database when test is finished
|
||||
* src/db.c: integrate SQLite3 with talloc allocator
|
||||
|
||||
[ Neels Hofmeyr ]
|
||||
* USSD: fix routing to multiple MSC
|
||||
* fix error logging for GSUP route
|
||||
* add missing error log: invalid IMSI
|
||||
* osmo-hlr: allow configuring db path from cfg file
|
||||
* use new OSMO_IMSI_BUF_SIZE
|
||||
|
||||
[ Daniel Willmann ]
|
||||
* manuals: Add script to update vty/counter documentation from docker
|
||||
* manuals: Update vty documentation
|
||||
|
||||
[ Pau Espin Pedrol ]
|
||||
* Remove undefined param passed to logging_vty_add_cmds
|
||||
* configure.ac: Require libosmocore 1.2.0
|
||||
|
||||
-- Pau Espin Pedrol <pespin@sysmocom.de> Wed, 07 Aug 2019 16:14:23 +0200
|
||||
|
||||
osmo-hlr (1.0.0) unstable; urgency=medium
|
||||
|
||||
[ Stefan Sperling ]
|
||||
* move creation of insert subscriber data messages to a common function
|
||||
|
||||
[ Harald Welte ]
|
||||
* Return proper GSUP error in case of too short IMSI
|
||||
* disable blind subscriber insertion into every VLR/SGSN
|
||||
* gsup_server: Add "priv" pointer and make it point to 'struct hlr'
|
||||
* move osmo_gsup_addr_send() declaration from luop.h to gsup_router.h
|
||||
* gsup_router: Use "#pragma once" and add missing #includes
|
||||
* Add "show gsup-connections" VTY command
|
||||
* import gsup_client.c as new libosmo-gsup-client
|
||||
* gsup_client: rename gsup_client_* to osmo_gsup_client_*
|
||||
* GSUP: Log GSUP route add/remove
|
||||
* hlr: Export + Declare global g_hlr symbol
|
||||
* USSD: Add Core USSD handling + VTY routing config to HLR
|
||||
* USSD: Add basic dispatch + decode of GSUP-encapsulated SS/USSD
|
||||
* hlr_ussd: Introduce LOGPSS() macro
|
||||
* USSD: Send ReturnError component if USSD Code unknown / EUSE disconnected
|
||||
* USSD: Further unification of log output; Use LOGPSS when possible
|
||||
* osmo-hlr.cfg: Don't enable DEBUG logging by default
|
||||
* USSD: Add new "DSS" logging category and use it appropriately
|
||||
* USSD: fix null-pointer deref in "default-route" vty/config cmd
|
||||
* Add osmo-euse-demo as minimalistic test of a External USSD (EUSE) handler
|
||||
* USSD: Add support for internal USSD handlers
|
||||
* debian: Add sub-package for libosmo-gsup-client
|
||||
* pkg-config: Fix libosmo-gsup-client pkg-config file
|
||||
* gitignore: Add .tarball-version
|
||||
* debian: Make libosmo-gsup-client-dev depend on libosmo-gsup-client0
|
||||
* USSD: Fix "ussd default-route"
|
||||
* libosmo-gsup-client: License is GPLv2-or-later
|
||||
* osmo-hlr.cfg: Ensure well-formed config file example
|
||||
* test_nodes.vty: Since libosmocore 1.0.0, we only have one space
|
||||
|
||||
[ Martin Hauke ]
|
||||
* sql/Makefile.am: Make docsdir completely configurable
|
||||
* debian: Fix typo in package description
|
||||
|
||||
[ Pau Espin Pedrol ]
|
||||
* debian: Avoid installing duplicate cfg file in /etc
|
||||
* sql/Makefile: Install hlr_data.sql as example together with hlr.sql
|
||||
* sql/Makefile: Install sql files under doc/.../sql subdir
|
||||
* sql/Makefile: Create empty /var/lib/osmocom directory at install time
|
||||
* Install systemd services with autotools
|
||||
* Move doc/Makefile.am to doc/examples/Makefile.am
|
||||
* Install sample cfg file to /etc/osmocom
|
||||
|
||||
[ Vadim Yanitskiy ]
|
||||
* hlr.c: move deinitialization code from SIGINT handler
|
||||
* hlr.c: free root talloc context on exit
|
||||
* hlr.c: track the use of talloc NULL memory contexts
|
||||
* src/db.c: fix: make sure the database is properly closed
|
||||
* src/db.c: don't ignore the result of db_bootstrap()
|
||||
* hlr_vty_subscr.c: fix subscriber creation command help
|
||||
* Update .gitignore: add missing build products
|
||||
* tests/Makefile.am: also remove temporary sqlite files
|
||||
* hlr_ussd.h: add #pragma once include guard
|
||||
* hlr_ussd.h: use proper libc headers
|
||||
* Update .gitignore: ignore osmo-euse-demo
|
||||
* hlr_ussd.h: drop meaningless forward declaration
|
||||
* USSD/hlr_vty.c: print error if EUSE is not found
|
||||
* hlr_ussd.c: fix: properly print a EUSE / IUSE name
|
||||
* hlr_ussd.c: avoid using CR and NL in IUSE responses
|
||||
|
||||
[ Neels Hofmeyr ]
|
||||
* fix build: adjust test_nodes.vty to logging change
|
||||
* tweak example config
|
||||
* make: always allow running python tests manually
|
||||
|
||||
-- Harald Welte <laforge@gnumonks.org> Sun, 20 Jan 2019 19:29:58 +0100
|
||||
|
||||
osmo-hlr (0.2.1) unstable; urgency=medium
|
||||
|
||||
[ Neels Hofmeyr ]
|
||||
* fix luop crash: use buffer for APN that remains valid
|
||||
* add gsup_test to catch OS#3231
|
||||
* add error handling to osmo_gsup_configure_wildcard_apn()
|
||||
|
||||
-- Pau Espin Pedrol <pespin@sysmocom.de> Fri, 04 May 2018 18:41:35 +0200
|
||||
|
||||
osmo-hlr (0.2.0) unstable; urgency=medium
|
||||
|
||||
[ Neels Hofmeyr ]
|
||||
* vty: skip installing cmds now always installed by default
|
||||
* hlr_db_tool: fix error log strerror invocation
|
||||
* cosmetic: add comment on ignored return value
|
||||
* db-tool: add command 'create'
|
||||
* db-tool: cosmetic: tweak printf output
|
||||
* db-tool: error-exit on too many arguments
|
||||
* add --enable-sanitize config option
|
||||
* db_test: don't verify SQLite issued error messages, they might change
|
||||
* cosmetic: rx_send_auth_info(): decide error cause with switch()
|
||||
* return GMM_CAUSE_IMSI_UNKNOWN if there is no auth data
|
||||
* db_get_auth_data / db_get_auc: clarify return values
|
||||
* osmo-hlr: log details for unknown IMSI / no auth data / db error
|
||||
* db_test: also test db_get_auc() return values
|
||||
* fix test_subscriber_errors.ctrl after libosmocore change
|
||||
* fix debug log: put 'deriving 2G from 3G' in proper place
|
||||
* ctrl test: fix: adjust expectations after stricter ctrl parsing
|
||||
* fix build: db_test: missing LIBOSMOABIS_CFLAGS and _LIBS
|
||||
* configure: add --enable-werror
|
||||
* jenkins.sh: use --enable-werror configure flag, not CFLAGS
|
||||
|
||||
[ Harald Welte ]
|
||||
* hlr.c: Avoid overflow of lu_operation.subscr.imsi
|
||||
* Fix expected test output after new 'logging print file 1' vty command
|
||||
* osmo-hlr: Add talloc context introspection via VTY
|
||||
* vty: Don't print error if removing auth data while none present
|
||||
* Fix responses to PURGE MS
|
||||
|
||||
[ Alexander Couzens ]
|
||||
* debian: include systemd service osmo-hlr.service
|
||||
* doc: install example .cfg files to $(docdir)/examples/
|
||||
* debian: install osmo-hlr.cfg to /etc/osmocom
|
||||
|
||||
[ Max ]
|
||||
* Remove unused check
|
||||
* Remove unused ipa.py
|
||||
* Enable sanitize for CI tests
|
||||
|
||||
[ Pau Espin Pedrol ]
|
||||
* luop.c: Transform FIXME from warning to pragma message
|
||||
* contrib:jenkins.sh: Enable Werror
|
||||
* use osmo_init_logging2
|
||||
* Remove unused src/db_test.c
|
||||
|
||||
[ Alexander Huemer ]
|
||||
* Add missing build products in .gitignore
|
||||
|
||||
[ Stefan Sperling ]
|
||||
* more robust usage of osmo_timer API for osmo-hlr luop timer
|
||||
* notify GSUP clients when HLR subscriber information changes
|
||||
* rewrite subscriber_update_notify() without calls into luop
|
||||
* don't forget to mark luop as a packet switched connection
|
||||
|
||||
-- Pau Espin Pedrol <pespin@sysmocom.de> Thu, 03 May 2018 16:27:13 +0200
|
||||
|
||||
osmo-hlr (0.1.0) unstable; urgency=medium
|
||||
|
||||
[ Neels Hofmeyr ]
|
||||
|
||||
37
debian/control
vendored
37
debian/control
vendored
@@ -12,7 +12,8 @@ Build-Depends: debhelper (>= 9),
|
||||
libosmo-abis-dev,
|
||||
libosmo-netif-dev,
|
||||
libsqlite3-dev,
|
||||
sqlite3
|
||||
sqlite3,
|
||||
osmo-gsm-manuals-dev
|
||||
Standards-Version: 3.9.6
|
||||
Vcs-Browser: http://cgit.osmocom.org/osmo-hlr
|
||||
Vcs-Git: git://git.osmocom.org/osmo-hlr
|
||||
@@ -32,3 +33,37 @@ Priority: extra
|
||||
Depends: osmo-hlr (= ${binary:Version}), ${misc:Depends}
|
||||
Description: Debug symbols for the osmo-hlr
|
||||
Make debugging possible
|
||||
|
||||
Package: libosmo-gsup-client0
|
||||
Section: libs
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends}
|
||||
Pre-Depends: ${misc:Pre-Depends}
|
||||
Description: Osmocom GSUP (General Subscriber Update Protocol) client library
|
||||
This is a shared library that can be used to implement client programs for
|
||||
the GSUP protocol. The typical GSUP server is OsmoHLR, with OsmoMSC, OsmoSGSN
|
||||
and External USSD Entities (EUSEs) using this library to implement clients.
|
||||
|
||||
Package: libosmo-gsup-client-dev
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends: ${misc:Depends},
|
||||
libosmo-gsup-client0 (= ${binary:Version}),
|
||||
libosmocore-dev
|
||||
Description: Development headers of Osmocom GSUP client library
|
||||
This is a shared library that can be used to implement client programs for
|
||||
the GSUP protocol. The typical GSUP server is OsmoHLR, with OsmoMSC, OsmoSGSN
|
||||
and External USSD Entities (EUSEs) using this library to implement clients.
|
||||
.
|
||||
This package contains the development headers.
|
||||
|
||||
Package: osmo-hlr-doc
|
||||
Architecture: all
|
||||
Section: doc
|
||||
Priority: optional
|
||||
Depends: ${misc:Depends}
|
||||
Description: ${misc:Package} PDF documentation
|
||||
Various manuals: user manual, VTY reference manual and/or
|
||||
protocol/interface manuals.
|
||||
|
||||
5
debian/libosmo-gsup-client-dev.install
vendored
Normal file
5
debian/libosmo-gsup-client-dev.install
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
usr/include/osmocom/gsupclient
|
||||
usr/lib/*/libosmo-gsup-client*.a
|
||||
usr/lib/*/libosmo-gsup-client*.so
|
||||
usr/lib/*/libosmo-gsup-client*.la
|
||||
usr/lib/*/pkgconfig/libosmo-gsup-client.pc
|
||||
1
debian/libosmo-gsup-client0.install
vendored
Normal file
1
debian/libosmo-gsup-client0.install
vendored
Normal file
@@ -0,0 +1 @@
|
||||
usr/lib/*/libosmo-gsup-client*.so.*
|
||||
1
debian/osmo-hlr-doc.install
vendored
Normal file
1
debian/osmo-hlr-doc.install
vendored
Normal file
@@ -0,0 +1 @@
|
||||
usr/share/doc/osmo-hlr-doc/*.pdf
|
||||
7
debian/osmo-hlr.install
vendored
7
debian/osmo-hlr.install
vendored
@@ -1,3 +1,8 @@
|
||||
/etc/osmocom/osmo-hlr.cfg
|
||||
/lib/systemd/system/osmo-hlr.service
|
||||
/usr/bin/osmo-hlr
|
||||
/usr/bin/osmo-hlr-db-tool
|
||||
/usr/share/doc/osmo-hlr/hlr.sql
|
||||
/usr/share/doc/osmo-hlr/sql/hlr.sql
|
||||
/usr/share/doc/osmo-hlr/sql/hlr_data.sql
|
||||
/usr/share/doc/osmo-hlr/examples/osmo-hlr.cfg
|
||||
/var/lib/osmocom
|
||||
|
||||
7
debian/rules
vendored
7
debian/rules
vendored
@@ -15,3 +15,10 @@ override_dh_strip:
|
||||
# Print test results in case of a failure
|
||||
override_dh_auto_test:
|
||||
dh_auto_test || (find . -name testsuite.log -exec cat {} \; ; false)
|
||||
|
||||
override_dh_auto_configure:
|
||||
dh_auto_configure -- --with-systemdsystemunitdir=/lib/systemd/system --enable-manuals
|
||||
|
||||
# Don't create .pdf.gz files (barely saves space and they can't be opened directly by most pdf readers)
|
||||
override_dh_compress:
|
||||
dh_compress -X.pdf
|
||||
|
||||
4
doc/Makefile.am
Normal file
4
doc/Makefile.am
Normal file
@@ -0,0 +1,4 @@
|
||||
SUBDIRS = \
|
||||
examples \
|
||||
manuals \
|
||||
$(NULL)
|
||||
27
doc/examples/Makefile.am
Normal file
27
doc/examples/Makefile.am
Normal file
@@ -0,0 +1,27 @@
|
||||
osmoconfdir = $(sysconfdir)/osmocom
|
||||
osmoconf_DATA = osmo-hlr.cfg
|
||||
|
||||
EXTRA_DIST = osmo-hlr.cfg
|
||||
|
||||
CFG_FILES = find $(srcdir) -name '*.cfg*' | sed -e 's,^$(srcdir),,'
|
||||
|
||||
dist-hook:
|
||||
for f in $$($(CFG_FILES)); do \
|
||||
j="$(distdir)/$$f" && \
|
||||
mkdir -p "$$(dirname $$j)" && \
|
||||
$(INSTALL_DATA) $(srcdir)/$$f $$j; \
|
||||
done
|
||||
|
||||
install-data-hook:
|
||||
for f in $$($(CFG_FILES)); do \
|
||||
j="$(DESTDIR)$(docdir)/examples/$$f" && \
|
||||
mkdir -p "$$(dirname $$j)" && \
|
||||
$(INSTALL_DATA) $(srcdir)/$$f $$j; \
|
||||
done
|
||||
|
||||
uninstall-hook:
|
||||
@$(PRE_UNINSTALL)
|
||||
for f in $$($(CFG_FILES)); do \
|
||||
j="$(DESTDIR)$(docdir)/examples/$$f" && \
|
||||
$(RM) $$j; \
|
||||
done
|
||||
@@ -2,13 +2,18 @@
|
||||
! OsmoHLR example configuration
|
||||
!
|
||||
log stderr
|
||||
logging filter all 1
|
||||
logging color 1
|
||||
logging print category 1
|
||||
logging timestamp 1
|
||||
logging print extended-timestamp 1
|
||||
logging level all debug
|
||||
logging level linp error
|
||||
logging filter all 1
|
||||
logging color 1
|
||||
logging print category 1
|
||||
logging print category-hex 0
|
||||
logging print level 1
|
||||
logging print file basename last
|
||||
logging print extended-timestamp 1
|
||||
logging level main notice
|
||||
logging level db notice
|
||||
logging level auc notice
|
||||
logging level ss info
|
||||
logging level linp error
|
||||
!
|
||||
line vty
|
||||
bind 127.0.0.1
|
||||
@@ -17,3 +22,5 @@ ctrl
|
||||
hlr
|
||||
gsup
|
||||
bind ip 127.0.0.1
|
||||
ussd route prefix *#100# internal own-msisdn
|
||||
ussd route prefix *#101# internal own-imsi
|
||||
|
||||
61
doc/manuals/Makefile.am
Normal file
61
doc/manuals/Makefile.am
Normal file
@@ -0,0 +1,61 @@
|
||||
EXTRA_DIST = example_subscriber_add_update_delete.vty \
|
||||
example_subscriber_cs_ps_enabled.ctrl \
|
||||
example_subscriber_info.ctrl \
|
||||
osmohlr-usermanual.adoc \
|
||||
osmohlr-usermanual-docinfo.xml \
|
||||
osmohlr-vty-reference.xml \
|
||||
regen_doc.sh \
|
||||
chapters \
|
||||
vty
|
||||
|
||||
if BUILD_MANUALS
|
||||
ASCIIDOC = osmohlr-usermanual.adoc
|
||||
ASCIIDOC_DEPS = $(srcdir)/chapters/*.adoc $(srcdir)/*.vty $(srcdir)/*.ctrl
|
||||
include $(OSMO_GSM_MANUALS_DIR)/build/Makefile.asciidoc.inc
|
||||
|
||||
VTY_REFERENCE = osmohlr-vty-reference.xml
|
||||
include $(OSMO_GSM_MANUALS_DIR)/build/Makefile.vty-reference.inc
|
||||
|
||||
OSMO_REPOSITORY = osmo-hlr
|
||||
include $(OSMO_GSM_MANUALS_DIR)/build/Makefile.common.inc
|
||||
endif
|
||||
|
||||
TMP_DB = generated/hlr.db
|
||||
|
||||
update-examples: update-examples-ctrl update-examples-vty
|
||||
|
||||
.PHONY: found-update-deps
|
||||
found-update-deps:
|
||||
@if [ ! -f "$(top_srcdir)/sql/hlr.sql" ]; then \
|
||||
echo "You need to define OSMO_HLR_PATH to point at an osmo-hlr.git"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ -z "$(shell which osmo-hlr)" ]; then \
|
||||
echo "osmo-hlr needs to be installed / available in the PATH"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ -z "$(shell which osmo_verify_transcript_ctrl.py)" ]; then \
|
||||
echo "You need to install git.osmocom.org/python/osmo-python-tests.git"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ -z "$(shell which osmo_verify_transcript_vty.py)" ]; then \
|
||||
echo "You need to install git.osmocom.org/python/osmo-python-tests.git"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
update-examples-ctrl: found-update-deps
|
||||
mkdir -p generated
|
||||
rm -f "$(TMP_DB)"
|
||||
sqlite3 "$(TMP_DB)" < "$(top_srcdir)/sql/hlr.sql"
|
||||
sqlite3 "$(TMP_DB)" < "$(top_srcdir)/tests/test_subscriber.sql"
|
||||
osmo_verify_transcript_ctrl.py \
|
||||
-r "osmo-hlr -l $(TMP_DB) -c $(top_srcdir)/doc/examples/osmo-hlr.cfg" \
|
||||
-p 4259 --update *.ctrl
|
||||
|
||||
update-examples-vty: found-update-deps
|
||||
mkdir -p generated
|
||||
rm -f "$(TMP_DB)"
|
||||
sqlite3 "$(TMP_DB)" < "$(top_srcdir)/sql/hlr.sql"
|
||||
osmo_verify_transcript_vty.py \
|
||||
-r "osmo-hlr -l $(TMP_DB) -c $(top_srcdir)/doc/examples/osmo-hlr.cfg" \
|
||||
-p 4258 --update *.vty
|
||||
106
doc/manuals/chapters/control.adoc
Normal file
106
doc/manuals/chapters/control.adoc
Normal file
@@ -0,0 +1,106 @@
|
||||
[[hlr-ctrl]]
|
||||
== Control interface
|
||||
|
||||
The actual protocol is described in <<common-control-if>>, the variables common
|
||||
to all programs using it are described in <<ctrl_common_vars>>. This section
|
||||
describes the CTRL interface variables specific to OsmoHLR.
|
||||
|
||||
All subscriber variables are available by different selectors, which are freely
|
||||
interchangeable:
|
||||
|
||||
.Subscriber selectors available on OsmoHLR's Control interface
|
||||
[options="header",width="100%",cols="35%,65%"]
|
||||
|===
|
||||
|Selector|Comment
|
||||
|subscriber.*by-imsi-*'123456'.*|Subscriber selector by IMSI, replace "123456" with the actual IMSI
|
||||
|subscriber.*by-msisdn-*'123456'.*|Subscriber selector by MSISDN
|
||||
|subscriber.*by-id-*'123456'.*|Subscriber selector by database ID
|
||||
|===
|
||||
|
||||
Each of the above selectors feature all of these control variables:
|
||||
|
||||
.Subscriber variables available on OsmoHLR's Control interface
|
||||
[options="header",width="100%",cols="35%,8%,8%,8%,41%"]
|
||||
|===
|
||||
|Name|Access|Trap|Value|Comment
|
||||
|subscriber.by-\*.*info*|R|No||List (short) subscriber information
|
||||
|subscriber.by-\*.*info-aud*|R|No||List subscriber authentication tokens
|
||||
|subscriber.by-\*.*info-all*|R|No||List both 'info' and 'info-aud' in one
|
||||
|subscriber.by-\*.*cs-enabled*|RW|No|'1' or '0'|Enable/disable circuit-switched access
|
||||
|subscriber.by-\*.*ps-enabled*|RW|No|'1' or '0'|Enable/disable packet-switched access
|
||||
|===
|
||||
|
||||
=== subscriber.by-*.info, info-aud, info-all
|
||||
|
||||
Query the HLR database and return current subscriber record, in multiple lines
|
||||
of the format
|
||||
|
||||
----
|
||||
name<tab>value
|
||||
----
|
||||
|
||||
To keep the reply as short as possible, some values are omitted if they are
|
||||
empty. These are the returned values and their presence
|
||||
modalities; for their meaning, see <<subscriber-params>>:
|
||||
|
||||
.Returned values by OsmoHLR's 'info', 'info-all' and 'info-aud' commands
|
||||
[options="header",width="100%",cols="15%,15%,30%,40%"]
|
||||
|===
|
||||
|Returned by 'info-all' and|Name|Format|Presence
|
||||
|'info'|id|-9223372036854775808 .. 9223372036854775807 (usually not negative)|always
|
||||
|'info'|imsi|6 to 15 decimal digits|always
|
||||
|'info'|msisdn|1 to 15 decimal digits|when non-empty
|
||||
|'info'|nam_cs|'1' if CS is enabled, or '0'|always
|
||||
|'info'|nam_ps|'1' if PS is enabled, or '0'|always
|
||||
|'info'|vlr_number|up to 15 decimal digits|when non-empty
|
||||
|'info'|sgsn_number|up to 15 decimal digits|when non-empty
|
||||
|'info'|sgsn_address||when non-empty
|
||||
|'info'|ms_purged_cs|'1' if CS is purged, or '0'|always
|
||||
|'info'|ms_purged_ps|'1' if PS is purged, or '0'|always
|
||||
|'info'|periodic_lu_timer|0..4294967295|always
|
||||
|'info'|periodic_rau_tau_timer|0..4294967295|always
|
||||
|'info'|lmsi|8 hex digits|always
|
||||
|'info-aud'|aud2g.algo|one of 'comp128v1', 'comp128v2', 'comp128v3' or 'xor'|when valid 2G auth data is set
|
||||
|'info-aud'|aud2g.ki|32 hexadecimal digits|when valid 2G auth data is set
|
||||
|'info-aud'|aud3g.algo|so far always 'milenage'|when valid 3G auth data is set
|
||||
|'info-aud'|aud3g.k|32 hexadecimal digits|when valid 3G auth data is set
|
||||
|'info-aud'|aud3g.op|32 hexadecimal digits|when valid 3G auth data is set, *not* when OPC is set
|
||||
|'info-aud'|aud3g.opc|32 hexadecimal digits|when valid 3G auth data is set, *not* when OP is set
|
||||
|'info-aud'|aud3g.ind_bitlen|0..28|when valid 3G auth data is set
|
||||
|'info-aud'|aud3g.sqn|0 .. 18446744073709551615|when valid 3G auth data is set
|
||||
|===
|
||||
|
||||
This is an example Control Interface transcript that illustrates the various
|
||||
'info' commands:
|
||||
|
||||
----
|
||||
include::../example_subscriber_info.ctrl[]
|
||||
----
|
||||
|
||||
=== subscriber.by-*.ps-enabled, cs-enabled
|
||||
|
||||
Disable or enable packet-/circuit-switched access for the given IMSI;
|
||||
|
||||
* 'ps-enabled' switches access to GPRS or UMTS data services,
|
||||
* 'cs-enabled' switches access to voice services.
|
||||
|
||||
When disabled, the next time this subscriber attempts to do a Location Updating
|
||||
GSUP operation for the given domain (i.e. from the SGSN for 'ps-enabled', from
|
||||
the MSC/VLR for 'cs-enabled'), it will be rejected by OsmoHLR. Currently
|
||||
connected GSUP clients will be notified via GSUP when a subscriber is being
|
||||
disabled, so that the subscriber can be dropped in case it is currently
|
||||
attached.
|
||||
|
||||
The current 'ps-enabled'/'cs-enabled' status can be queried by 'GET' commands,
|
||||
and also by looking at 'nam_ps' and 'nam_cs' in a 'subscriber.by-*.info'
|
||||
response.
|
||||
|
||||
A value of "1" indicates that the given domain is enabled, which is the
|
||||
default; a value of "0" disables access.
|
||||
|
||||
This is an example transcript that illustrates 'ps-enabled' and 'cs-enabled'
|
||||
commands:
|
||||
|
||||
----
|
||||
include::../example_subscriber_cs_ps_enabled.ctrl[]
|
||||
----
|
||||
69
doc/manuals/chapters/overview.adoc
Normal file
69
doc/manuals/chapters/overview.adoc
Normal file
@@ -0,0 +1,69 @@
|
||||
[[overview]]
|
||||
== Overview
|
||||
|
||||
This manual should help you getting started with OsmoHLR. It will cover
|
||||
aspects of configuring and running the OsmoHLR.
|
||||
|
||||
[[intro_overview]]
|
||||
=== About OsmoHLR
|
||||
|
||||
OsmoHLR is Osmocom's minimal implementation of a Home Location Register (HLR)
|
||||
for 2G and 3G GSM and UMTS mobile core networks. Its interfaces are:
|
||||
|
||||
- GSUP, serving towards OsmoMSC and OsmoSGSN;
|
||||
- A local SQLite database;
|
||||
- The Osmocom typical telnet VTY and CTRL interfaces.
|
||||
|
||||
Originally, the OpenBSC project's OsmoNITB all-in-one implementation had an
|
||||
integrated HLR, managing subscribers and SMS in the same local database. Along
|
||||
with the separate OsmoMSC and its new VLR component, OsmoHLR was implemented
|
||||
from scratch to alleviate various shortcomings of the internal HLR:
|
||||
|
||||
- The separate HLR allows using centralized subscriber management for both
|
||||
circuit-switched and packet-switched domains (i.e. one OsmoHLR for both
|
||||
OsmoMSC and OsmoSGSN).
|
||||
|
||||
- VLR and HLR brought full UMTS AKA (Authentication and Key Agreement) support,
|
||||
i.e. Milenage authentication in both the full 3G variant as well as the
|
||||
backwards compatible 2G variant.
|
||||
|
||||
- In contrast to the OsmoNITB, the specific way the new OsmoMSC's VLR accesses
|
||||
OsmoHLR brings fully asynchronous subscriber database access.
|
||||
|
||||
Find the OsmoHLR issue tracker and wiki online at
|
||||
|
||||
- https://osmocom.org/projects/osmo-hlr
|
||||
- https://osmocom.org/projects/osmo-hlr/wiki
|
||||
|
||||
|
||||
[[fig-gsm]]
|
||||
.Typical GSM network architecture used with OsmoHLR
|
||||
[graphviz]
|
||||
----
|
||||
digraph G {
|
||||
rankdir=LR;
|
||||
subgraph cluster_hlr {
|
||||
label = "OsmoHLR";
|
||||
GSUP [label="GSUP server"]
|
||||
DB [label="SQLite DB"]
|
||||
GSUP->DB
|
||||
DB->CTRL [dir="back"]
|
||||
DB->VTY [dir="back"]
|
||||
}
|
||||
|
||||
Admin [label="Admin and\nMaintenance"]
|
||||
SW [label="3rd party software\nintegration"]
|
||||
VTY->Admin [dir="back"]
|
||||
CTRL->SW [dir="back"]
|
||||
|
||||
MSC [label="MSC/VLR"]
|
||||
MSC->GSUP [label="GSUP"]
|
||||
SGSN->GSUP [label="GSUP"]
|
||||
|
||||
BSC->MSC
|
||||
HNBGW->MSC
|
||||
HNBGW->SGSN
|
||||
PCU->SGSN
|
||||
}
|
||||
----
|
||||
|
||||
87
doc/manuals/chapters/running.adoc
Normal file
87
doc/manuals/chapters/running.adoc
Normal file
@@ -0,0 +1,87 @@
|
||||
== Running OsmoHLR
|
||||
|
||||
The OsmoHLR executable (`osmo-hlr`) offers the following command-line
|
||||
arguments:
|
||||
|
||||
=== SYNOPSIS
|
||||
|
||||
*osmo-hlr* [-h] [-c 'CONFIGFILE'] [-l 'DATABASE'] [-d 'DBGMASK'] [-D] [-s] [-T] [-e 'LOGLEVEL'] [-U] [-V]
|
||||
|
||||
=== OPTIONS
|
||||
|
||||
// Keep the order the same as in osmo-hlr --help!
|
||||
|
||||
*-h, --help*::
|
||||
Print a short help message about the supported options
|
||||
*-c, --config-file 'CONFIGFILE'*::
|
||||
Specify the file and path name of the configuration file to be
|
||||
used. If none is specified, use `osmo-hlr.cfg` in the current
|
||||
working directory.
|
||||
*-l, --database 'DATABASE'*::
|
||||
Specify the file name of the SQLite3 database to use as HLR/AUC
|
||||
storage
|
||||
*-d, --debug 'DBGMASK','DBGLEVELS'*::
|
||||
Set the log subsystems and levels for logging to stderr. This
|
||||
has mostly been superseded by VTY-based logging configuration,
|
||||
see <<logging>> for further information.
|
||||
*-D, --daemonize*::
|
||||
Fork the process as a daemon into background.
|
||||
*-s, --disable-color*::
|
||||
Disable colors for logging to stderr. This has mostly been
|
||||
deprecated by VTY based logging configuration, see <<logging>>
|
||||
for more information.
|
||||
*-T, --timestamp*::
|
||||
Enable time-stamping of log messages to stderr. This has mostly
|
||||
been deprecated by VTY based logging configuration, see
|
||||
<<logging>> for more information.
|
||||
*-e, --log-level 'LOGLEVEL'*::
|
||||
Set the global log level for logging to stderr. This has mostly
|
||||
been deprecated by VTY based logging configuration, see
|
||||
<<logging>> for more information.
|
||||
*-U, --db-upgrade*::
|
||||
Allow HLR database schema upgrades. If OsmoHLR was updated and
|
||||
requires a newer database schema, it will refuse to start unless
|
||||
this option is specified. The updated database can not be
|
||||
downgraded, make backups as necessary.
|
||||
*-V, --version*::
|
||||
Print the compile-time version number of the OsmoHLR program
|
||||
|
||||
=== Bootstrap the Database
|
||||
|
||||
If no database exists yet, OsmoHLR will automatically create and bootstrap a
|
||||
database file with empty tables. If no `-l` command-line option is provided,
|
||||
this database file will be created in the current working directory.
|
||||
|
||||
Alternatively, you may use the `osmo-hlr-db-tool`, which is installed along
|
||||
with `osmo-hlr`, to bootstrap an empty database, or to migrate subscriber data
|
||||
from an old 'OsmoNITB' database. See `osmo-hlr-db-tool --help`.
|
||||
|
||||
=== Multiple instances
|
||||
|
||||
Running multiple instances of `osmo-hlr` on the same computer is possible if
|
||||
all interfaces (VTY, CTRL) are separated using the appropriate configuration
|
||||
options. The IP based interfaces are binding to local host by default. In order
|
||||
to separate the processes, the user has to bind those services to specific but
|
||||
different IP addresses and/or ports.
|
||||
|
||||
The VTY and the Control interface can be bound to IP addresses from the loopback
|
||||
address range, for example:
|
||||
|
||||
----
|
||||
line vty
|
||||
bind 127.0.0.2
|
||||
ctrl
|
||||
bind 127.0.0.2
|
||||
----
|
||||
|
||||
The GSUP interface can be bound to a specific IP address by the following
|
||||
configuration options:
|
||||
|
||||
----
|
||||
hlr
|
||||
gsup
|
||||
bind ip 10.23.42.1
|
||||
----
|
||||
|
||||
NOTE: At the time of writing, OsmoHLR lacks a config option to change the GSUP
|
||||
port, which is by default TCP port 4222.
|
||||
129
doc/manuals/chapters/subscribers.adoc
Normal file
129
doc/manuals/chapters/subscribers.adoc
Normal file
@@ -0,0 +1,129 @@
|
||||
== Managing Subscribers
|
||||
|
||||
Subscribers are kept in a local SQLite database file and can be managed via VTY
|
||||
and CTRL interfaces.
|
||||
|
||||
This section provides some examples; also refer to the OsmoHLR VTY reference
|
||||
manual <<vty-ref-osmohlr>> as well as the Control interface described in
|
||||
<<hlr-ctrl>>.
|
||||
|
||||
=== Example: Add/Update/Delete Subscriber via VTY
|
||||
|
||||
The following telnet VTY session adds a subscriber complete with GSM (2G) and
|
||||
UMTS (3G and 2G) authentication tokens, and finally removes the subscriber
|
||||
again; it assumes that osmo-hlr is running and listening for telnet VTY
|
||||
connections on localhost:
|
||||
|
||||
----
|
||||
$ telnet localhost 4258
|
||||
include::../example_subscriber_add_update_delete.vty[]
|
||||
----
|
||||
|
||||
[[subscriber-params]]
|
||||
=== Subscriber Parameters
|
||||
|
||||
The following parameters are managed for each subscriber of the HLR, modelled
|
||||
roughly after 3GPP TS 23.008, version 13.3.0; note that not all of these
|
||||
parameters are necessarily in active use.
|
||||
|
||||
The `aud3g` table also applies to 2G networks: it provides UMTS AKA tokens for
|
||||
Milenage authentication, which is available both on 3G and 2G networks. On 2G,
|
||||
when both MS and network are R99 capable (like OsmoMSC and OsmoSGSN are), the
|
||||
full UMTS AKA with Milenage keys from `aud_3g`, using AUTN and extended RES
|
||||
tokens, is available. With pre-R99 MS or network configurations, the GSM AKA
|
||||
compatible variant of Milenage, still using the Milenage keys from `aud_3g` but
|
||||
transceiving only RAND and SRES, may be applicable. (See 3GPP TS 33.102, chapter
|
||||
6.8.1, Authentication and key agreement of UMTS subscribers.)
|
||||
|
||||
.OsmoHLR's subscriber parameters
|
||||
[options="header",width="100%",cols="20%,20%,60%"]
|
||||
|===
|
||||
|Name|Example|Description
|
||||
|imsi|901700000014701|identity of the SIM/USIM, 3GPP TS 23.008 chapter 2.1.1.1
|
||||
|msisdn|2342123|number to dial to reach this subscriber (multiple MSISDNs can be stored per subscriber), 3GPP TS 23.008 chapter 2.1.2
|
||||
|imeisv|4234234234234275|identity of the mobile device and software version, 3GPP TS 23.008 chapter 2.2.3
|
||||
|aud2g.algo|comp128v3|Authentication algorithm ID for GSM AKA, corresponds to enum osmo_auth_algo
|
||||
|aud2g.ki||Subscriber's secret key (128bit)
|
||||
|aud3g.algo|milenage|Authentication algorithm ID for UMTS AKA (applies to both 3G and 2G networks), corresponds to enum osmo_auth_algo
|
||||
|aud3g.k|(32 hexadecimal digits)|Subscriber's secret key (128bit)
|
||||
|aud3g.op|(32 hexadecimal digits)|Operator's secret key (128bit)
|
||||
|aud3g.opc|(32 hexadecimal digits)|Secret key derived from OP and K (128bit), alternative to using OP which does not disclose OP to subscribers
|
||||
|aud3g.sqn|123|Sequence number of last used key (64bit unsigned)
|
||||
|aud3g.ind_bitlen|5|Nr of index bits at lower SQN end
|
||||
|apn||
|
||||
|vlr_number||3GPP TS 23.008 chapter 2.4.5
|
||||
|msc_number||3GPP TS 23.008 chapter 2.4.6
|
||||
|sgsn_number||3GPP TS 23.008 chapter 2.4.8.1
|
||||
|sgsn_address||3GPP TS 23.008 chapter 2.13.10
|
||||
|ggsn_number||3GPP TS 23.008 chapter 2.4.8.2
|
||||
|gmlc_number||3GPP TS 23.008 chapter 2.4.9.2
|
||||
|smsc_number||3GPP TS 23.008 chapter 2.4.23
|
||||
|periodic_lu_tmr||3GPP TS 23.008 chapter 2.4.24
|
||||
|periodic_rau_tau_tmr||3GPP TS 23.008 chapter 2.13.115
|
||||
|nam_cs|1|Enable/disable voice access (3GPP TS 23.008 chapter 2.1.1.2: network access mode)
|
||||
|nam_ps|0|Enable/disable data access (3GPP TS 23.008 chapter 2.1.1.2: network access mode)
|
||||
|lmsi||3GPP TS 23.008 chapter 2.1.8
|
||||
|ms_purged_cs|0|3GPP TS 23.008 chapter 2.7.5
|
||||
|ms_purged_ps|1|3GPP TS 23.008 chapter 2.7.6
|
||||
|===
|
||||
|
||||
=== Configuring the Subscribers Create on Demand Feature
|
||||
|
||||
Usually a HLR will only allow mobile equipment (ME) on the network, if the HLR
|
||||
has a subscriber entry with the ME's IMSI. But OsmoHLR can also be configured to
|
||||
automatically create new entries for new IMSIs, with the
|
||||
`subscriber-create-on-demand` VTY option. The obvious use case is creating the
|
||||
new subscriber entry and then allowing the ME to use both the CS
|
||||
(Circuit Switched) and PS (Packet Switched) NAM (Network Access Mode).
|
||||
|
||||
.osmo-hlr.cfg
|
||||
----
|
||||
hlr
|
||||
subscriber-create-on-demand 5 cs+ps
|
||||
----
|
||||
|
||||
On the other hand, operators might only want to give network access to IMSIs, of
|
||||
which they know the owner. In order to do that, one can set the default NAM to
|
||||
`none` and manually approve new subscribers by changing the NAM (e.g. over the
|
||||
VTY, see the example below).
|
||||
|
||||
Oftentimes it is hard to know, which IMSI belongs to which ME, but the IMEI is
|
||||
readily available. If you configure your MSC to send IMEI checking requests to
|
||||
the HLR, before sending location update requests, the subscribers created on
|
||||
demand can also have the IMEI stored in the HLR database. With OsmoMSC, this
|
||||
is done by writing `check-imei-rqd early` in the `msc` section of osmo-msc.cfg.
|
||||
Then enable storing the IMEI when receiving check IMEI requests with
|
||||
`store-imei` in the OsmoHLR configuration.
|
||||
|
||||
.osmo-msc.cfg
|
||||
----
|
||||
msc
|
||||
check-imei-rqd early
|
||||
----
|
||||
|
||||
.osmo-hlr.cfg
|
||||
----
|
||||
hlr
|
||||
subscriber-create-on-demand 5 none
|
||||
store-imei
|
||||
----
|
||||
|
||||
.Example: Enabling CS and PS NAM via VTY for a known IMEI
|
||||
----
|
||||
OsmoHLR> enable
|
||||
OsmoHLR# subscriber imei 35761300444848 show
|
||||
ID: 1
|
||||
IMSI: 123456789023000
|
||||
MSISDN: 58192 <1>
|
||||
IMEI: 35761300444848
|
||||
CS disabled <2>
|
||||
PS disabled <2>
|
||||
OsmoHLR# subscriber imei 35761300444848 update network-access-mode cs+ps
|
||||
OsmoHLR# subscriber imei 35761300444848 show
|
||||
ID: 1
|
||||
IMSI: 123456789023000
|
||||
MSISDN: 58192
|
||||
IMEI: 35761300444848
|
||||
----
|
||||
<1> Randomly generated 5 digit MSISDN
|
||||
<2> Disabled CS and PS NAM prevent the subscriber from accessing the network
|
||||
78
doc/manuals/chapters/ussd.adoc
Normal file
78
doc/manuals/chapters/ussd.adoc
Normal file
@@ -0,0 +1,78 @@
|
||||
[[ussd]]
|
||||
== Unstructured Supplementary Services Data (USSD)
|
||||
|
||||
The _Unstructured Supplementary Services Data (USSD)_ is one service within
|
||||
2G/3G networks next to other services such as circuit-switched voice, packet-switched
|
||||
data and SMS (Short Message Service).
|
||||
|
||||
It is on an abstract level quite similar to SMS in that USSD can be used to send
|
||||
textual messages. However, there are the following differences:
|
||||
|
||||
* USSD is between the MS (phone) and an USSD application on the network, while
|
||||
SMS is primarily between two subscribers identified by their MSISDN
|
||||
* USSD is faster, as it doesn't suffer from the complicated three-layer CP/RP/TP
|
||||
protocol stack of SMS with it's acknowledgement of the acknowledged acknowledgement.
|
||||
* USSD is session-oriented, i.e. a dialogue/session between subscriber and application
|
||||
can persist for the transfer of more than one message. The dedicated radio channel
|
||||
on the RAN remains established throughout that dialogue.
|
||||
|
||||
=== USSD in Osmocom
|
||||
|
||||
Until August 2018, OsmoMSC contained some minimalistic internal USSD
|
||||
handling with no
|
||||
ability to attach/extend it with external USSD applications.
|
||||
|
||||
From August 2018 onwards, OsmoMSC doesn't contain any internal USSD
|
||||
handlers/applications anymore. Instead, all USSD is transported to/from
|
||||
OsmoHLR via the GSUP protocol.
|
||||
|
||||
OsmoHLR contains some intenal USSD handlers and can route USSD messages
|
||||
to any number of external USSD entities (EUSEs). The EUSE also use GSUP
|
||||
to communicate USSD from/to OsmoHLR.
|
||||
|
||||
Each EUSE is identified by its name. The name consists of a single-word
|
||||
string preceding a currently fixed ("-00-00-00-00-00-00") suffix.
|
||||
There is no authentication between EUSE and OsmoHLR: Any client program
|
||||
able to connect to the GSUP port of OsmoHLR can register as any EUSE
|
||||
(name).
|
||||
|
||||
NOTE:: We plan to remove the requirement for this suffix as soon as we
|
||||
are done resolving all more important issues.
|
||||
|
||||
=== USSD Configuration
|
||||
|
||||
USSD configuration in OsmoHLR happens within the `hlr` VTY node.
|
||||
|
||||
`euse foobar-00-00-00-00-00-00` defines an EUSE with the given name `foobar`
|
||||
|
||||
`ussd route prefix *123 external foobar-00-00-00-00-00-00` installs a
|
||||
prefix route to the named EUSE. All USSD short codes starting with *123 will be
|
||||
routed to the named EUSE.
|
||||
|
||||
`ussd route prefix *#100# internal own-msisdn` installs a prefix route
|
||||
to the named internal USSD handler. There above command will restore
|
||||
the old behavior, in which *#100# will return a text message containing
|
||||
the subscribers own phone number. There is one other handler called
|
||||
`own-imsi` which will return the IMSI instead of the MSISDN.
|
||||
|
||||
`ussd default-route external foobar-00-00-00-00-00-00` installs a
|
||||
default route to the named EUSE. This means that all USSD codes for
|
||||
which no more specific route exists will be routed to the named EUSE.
|
||||
|
||||
=== Example EUSE program
|
||||
|
||||
We have provided an example EUSE developed in C language using existing
|
||||
Osmocom libraries for GSUP protocol handling and USSD encoding/decoding.
|
||||
It will register as `foobar` EUSE to OsmoHLR on localhost. You can run
|
||||
it on a different machine by specifying e.g. `osmo-euse-demo 1.2.3.4 5678`
|
||||
to make it connect to OsmoHLR on IP address 1.2.3.4 and GSUP/TCP port
|
||||
5678.
|
||||
|
||||
The idea is that you can use this as a template to develop your own USSD
|
||||
applications, or any gateways to other protocols or interfaces.
|
||||
|
||||
You can find it in `osmo-hlr/src/osmo-euse-demo.c` or online by
|
||||
following the link to http://git.osmocom.org/osmo-hlr/tree/src/osmo-euse-demo.c
|
||||
|
||||
This demonstration program will echo back any USSD message sent/routed
|
||||
to it, quoted like _You sent "..."_.
|
||||
34
doc/manuals/example_subscriber_add_update_delete.vty
Normal file
34
doc/manuals/example_subscriber_add_update_delete.vty
Normal file
@@ -0,0 +1,34 @@
|
||||
OsmoHLR> enable
|
||||
OsmoHLR# subscriber imsi 123456789023000 create
|
||||
% Created subscriber 123456789023000
|
||||
ID: 1
|
||||
IMSI: 123456789023000
|
||||
MSISDN: none
|
||||
|
||||
OsmoHLR# subscriber imsi 123456789023000 update msisdn 423
|
||||
% Updated subscriber IMSI='123456789023000' to MSISDN='423'
|
||||
|
||||
OsmoHLR# subscriber msisdn 423 update aud3g milenage k deaf0ff1ced0d0dabbedd1ced1cef00d opc cededeffacedacefacedbadfadedbeef
|
||||
OsmoHLR# subscriber msisdn 423 show
|
||||
ID: 1
|
||||
IMSI: 123456789023000
|
||||
MSISDN: 423
|
||||
3G auth: MILENAGE
|
||||
K=deaf0ff1ced0d0dabbedd1ced1cef00d
|
||||
OPC=cededeffacedacefacedbadfadedbeef
|
||||
IND-bitlen=5
|
||||
|
||||
OsmoHLR# subscriber msisdn 423 update aud2g comp128v3 ki beefedcafefaceacedaddeddecadefee
|
||||
OsmoHLR# subscriber msisdn 423 show
|
||||
ID: 1
|
||||
IMSI: 123456789023000
|
||||
MSISDN: 423
|
||||
2G auth: COMP128v3
|
||||
KI=beefedcafefaceacedaddeddecadefee
|
||||
3G auth: MILENAGE
|
||||
K=deaf0ff1ced0d0dabbedd1ced1cef00d
|
||||
OPC=cededeffacedacefacedbadfadedbeef
|
||||
IND-bitlen=5
|
||||
|
||||
OsmoHLR# subscriber imsi 123456789023000 delete
|
||||
% Deleted subscriber for IMSI '123456789023000'
|
||||
71
doc/manuals/example_subscriber_cs_ps_enabled.ctrl
Normal file
71
doc/manuals/example_subscriber_cs_ps_enabled.ctrl
Normal file
@@ -0,0 +1,71 @@
|
||||
GET 1 subscriber.by-msisdn-103.info
|
||||
GET_REPLY 1 subscriber.by-msisdn-103.info
|
||||
id 3
|
||||
imsi 901990000000003
|
||||
msisdn 103
|
||||
nam_cs 1
|
||||
nam_ps 1
|
||||
ms_purged_cs 0
|
||||
ms_purged_ps 0
|
||||
periodic_lu_timer 0
|
||||
periodic_rau_tau_timer 0
|
||||
lmsi 00000000
|
||||
|
||||
GET 2 subscriber.by-msisdn-103.ps-enabled
|
||||
GET_REPLY 2 subscriber.by-msisdn-103.ps-enabled 1
|
||||
|
||||
SET 3 subscriber.by-msisdn-103.ps-enabled 0
|
||||
SET_REPLY 3 subscriber.by-msisdn-103.ps-enabled OK
|
||||
|
||||
GET 4 subscriber.by-msisdn-103.ps-enabled
|
||||
GET_REPLY 4 subscriber.by-msisdn-103.ps-enabled 0
|
||||
|
||||
GET 5 subscriber.by-msisdn-103.info
|
||||
GET_REPLY 5 subscriber.by-msisdn-103.info
|
||||
id 3
|
||||
imsi 901990000000003
|
||||
msisdn 103
|
||||
nam_cs 1
|
||||
nam_ps 0
|
||||
ms_purged_cs 0
|
||||
ms_purged_ps 0
|
||||
periodic_lu_timer 0
|
||||
periodic_rau_tau_timer 0
|
||||
lmsi 00000000
|
||||
|
||||
SET 6 subscriber.by-msisdn-103.cs-enabled 0
|
||||
SET_REPLY 6 subscriber.by-msisdn-103.cs-enabled OK
|
||||
|
||||
GET 7 subscriber.by-msisdn-103.cs-enabled
|
||||
GET_REPLY 7 subscriber.by-msisdn-103.cs-enabled 0
|
||||
|
||||
GET 8 subscriber.by-msisdn-103.info
|
||||
GET_REPLY 8 subscriber.by-msisdn-103.info
|
||||
id 3
|
||||
imsi 901990000000003
|
||||
msisdn 103
|
||||
nam_cs 0
|
||||
nam_ps 0
|
||||
ms_purged_cs 0
|
||||
ms_purged_ps 0
|
||||
periodic_lu_timer 0
|
||||
periodic_rau_tau_timer 0
|
||||
lmsi 00000000
|
||||
|
||||
SET 9 subscriber.by-msisdn-103.cs-enabled 1
|
||||
SET_REPLY 9 subscriber.by-msisdn-103.cs-enabled OK
|
||||
SET 10 subscriber.by-msisdn-103.ps-enabled 1
|
||||
SET_REPLY 10 subscriber.by-msisdn-103.ps-enabled OK
|
||||
|
||||
GET 11 subscriber.by-msisdn-103.info
|
||||
GET_REPLY 11 subscriber.by-msisdn-103.info
|
||||
id 3
|
||||
imsi 901990000000003
|
||||
msisdn 103
|
||||
nam_cs 1
|
||||
nam_ps 1
|
||||
ms_purged_cs 0
|
||||
ms_purged_ps 0
|
||||
periodic_lu_timer 0
|
||||
periodic_rau_tau_timer 0
|
||||
lmsi 00000000
|
||||
42
doc/manuals/example_subscriber_info.ctrl
Normal file
42
doc/manuals/example_subscriber_info.ctrl
Normal file
@@ -0,0 +1,42 @@
|
||||
GET 1 subscriber.by-imsi-901990000000003.info
|
||||
GET_REPLY 1 subscriber.by-imsi-901990000000003.info
|
||||
id 3
|
||||
imsi 901990000000003
|
||||
msisdn 103
|
||||
nam_cs 1
|
||||
nam_ps 1
|
||||
ms_purged_cs 0
|
||||
ms_purged_ps 0
|
||||
periodic_lu_timer 0
|
||||
periodic_rau_tau_timer 0
|
||||
lmsi 00000000
|
||||
|
||||
GET 2 subscriber.by-msisdn-103.info-aud
|
||||
GET_REPLY 2 subscriber.by-msisdn-103.info-aud
|
||||
aud2g.algo COMP128v1
|
||||
aud2g.ki 000102030405060708090a0b0c0d0e0f
|
||||
aud3g.algo MILENAGE
|
||||
aud3g.k 000102030405060708090a0b0c0d0e0f
|
||||
aud3g.opc 101112131415161718191a1b1c1d1e1f
|
||||
aud3g.ind_bitlen 5
|
||||
aud3g.sqn 0
|
||||
|
||||
GET 3 subscriber.by-id-3.info-all
|
||||
GET_REPLY 3 subscriber.by-id-3.info-all
|
||||
id 3
|
||||
imsi 901990000000003
|
||||
msisdn 103
|
||||
nam_cs 1
|
||||
nam_ps 1
|
||||
ms_purged_cs 0
|
||||
ms_purged_ps 0
|
||||
periodic_lu_timer 0
|
||||
periodic_rau_tau_timer 0
|
||||
lmsi 00000000
|
||||
aud2g.algo COMP128v1
|
||||
aud2g.ki 000102030405060708090a0b0c0d0e0f
|
||||
aud3g.algo MILENAGE
|
||||
aud3g.k 000102030405060708090a0b0c0d0e0f
|
||||
aud3g.opc 101112131415161718191a1b1c1d1e1f
|
||||
aud3g.ind_bitlen 5
|
||||
aud3g.sqn 0
|
||||
47
doc/manuals/osmohlr-usermanual-docinfo.xml
Normal file
47
doc/manuals/osmohlr-usermanual-docinfo.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>1</revnumber>
|
||||
<date>September 18th, 2017</date>
|
||||
<authorinitials>NH</authorinitials>
|
||||
<revremark>
|
||||
Initial version; based on OsmoNITB manual version 2.
|
||||
</revremark>
|
||||
</revision>
|
||||
</revhistory>
|
||||
|
||||
<authorgroup>
|
||||
<author>
|
||||
<firstname>Neels</firstname>
|
||||
<surname>Hofmeyr</surname>
|
||||
<email>nhofmeyr@sysmocom.de</email>
|
||||
<authorinitials>NH</authorinitials>
|
||||
<affiliation>
|
||||
<shortaffil>sysmocom</shortaffil>
|
||||
<orgname>sysmocom - s.f.m.c. GmbH</orgname>
|
||||
<jobtitle>Senior Developer</jobtitle>
|
||||
</affiliation>
|
||||
</author>
|
||||
</authorgroup>
|
||||
|
||||
<copyright>
|
||||
<year>2017</year>
|
||||
<holder>sysmocom - s.f.m.c. GmbH</holder>
|
||||
</copyright>
|
||||
|
||||
<legalnotice>
|
||||
<para>
|
||||
Permission is granted to copy, distribute and/or modify this
|
||||
document under the terms of the GNU Free Documentation License,
|
||||
Version 1.3 or any later version published by the Free Software
|
||||
Foundation; with the Invariant Sections being just 'Foreword',
|
||||
'Acknowledgements' and 'Preface', with no Front-Cover Texts,
|
||||
and no Back-Cover Texts. A copy of the license is included in
|
||||
the section entitled "GNU Free Documentation License".
|
||||
</para>
|
||||
<para>
|
||||
The Asciidoc source code of this manual can be found at
|
||||
<ulink url="http://git.osmocom.org/osmo-gsm-manuals/">
|
||||
http://git.osmocom.org/osmo-gsm-manuals/
|
||||
</ulink>
|
||||
</para>
|
||||
</legalnotice>
|
||||
36
doc/manuals/osmohlr-usermanual.adoc
Normal file
36
doc/manuals/osmohlr-usermanual.adoc
Normal file
@@ -0,0 +1,36 @@
|
||||
:gfdl-enabled:
|
||||
:program-name: OsmoHLR
|
||||
|
||||
OsmoHLR User Manual
|
||||
====================
|
||||
Neels Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
|
||||
|
||||
include::./common/chapters/preface.adoc[]
|
||||
|
||||
include::{srcdir}/chapters/overview.adoc[]
|
||||
|
||||
include::{srcdir}/chapters/running.adoc[]
|
||||
|
||||
include::{srcdir}/chapters/subscribers.adoc[]
|
||||
|
||||
include::{srcdir}/chapters/ussd.adoc[]
|
||||
|
||||
include::./common/chapters/vty.adoc[]
|
||||
|
||||
include::./common/chapters/logging.adoc[]
|
||||
|
||||
include::{srcdir}/chapters/control.adoc[]
|
||||
|
||||
include::./common/chapters/control_if.adoc[]
|
||||
|
||||
include::./common/chapters/gsup.adoc[]
|
||||
|
||||
include::./common/chapters/port_numbers.adoc[]
|
||||
|
||||
include::./common/chapters/bibliography.adoc[]
|
||||
|
||||
include::./common/chapters/glossary.adoc[]
|
||||
|
||||
include::./common/chapters/gfdl.adoc[]
|
||||
|
||||
38
doc/manuals/osmohlr-vty-reference.xml
Normal file
38
doc/manuals/osmohlr-vty-reference.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
ex:ts=2:sw=42sts=2:et
|
||||
-*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
|
||||
-->
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML 5.0//EN"
|
||||
"http://docbook.org/xml/5.0/dtd/docbook.dtd" [
|
||||
<!ENTITY chapter-vty SYSTEM "./common/chapters/vty.xml" >
|
||||
<!ENTITY sections-vty SYSTEM "generated/docbook_vty.xml" >
|
||||
]>
|
||||
|
||||
<book>
|
||||
<info>
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>v1</revnumber>
|
||||
<date>18th September 2017</date>
|
||||
<authorinitials>nh</authorinitials>
|
||||
<revremark>Initial</revremark>
|
||||
</revision>
|
||||
</revhistory>
|
||||
|
||||
<title>OsmoHLR VTY Reference</title>
|
||||
|
||||
<copyright>
|
||||
<year>2017</year>
|
||||
</copyright>
|
||||
|
||||
<legalnotice>
|
||||
<para>This work is copyright by <orgname>sysmocom - s.f.m.c. GmbH</orgname>. All rights reserved.
|
||||
</para>
|
||||
</legalnotice>
|
||||
</info>
|
||||
|
||||
<!-- Main chapters-->
|
||||
&chapter-vty;
|
||||
</book>
|
||||
|
||||
17
doc/manuals/regen_doc.sh
Executable file
17
doc/manuals/regen_doc.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh -x
|
||||
|
||||
if [ -z "$DOCKER_PLAYGROUND" ]; then
|
||||
echo "You need to set DOCKER_PLAYGROUND"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT=$(realpath "$0")
|
||||
MANUAL_DIR=$(dirname "$SCRIPT")
|
||||
|
||||
COMMIT=${COMMIT:-$(git log -1 --format=format:%H)}
|
||||
|
||||
cd "$DOCKER_PLAYGROUND/scripts" || exit 1
|
||||
|
||||
OSMO_HLR_BRANCH=$COMMIT ./regen_doc.sh osmo-hlr 4258 \
|
||||
"$MANUAL_DIR/chapters/counters_generated.adoc" \
|
||||
"$MANUAL_DIR/vty/hlr_vty_reference.xml"
|
||||
2
doc/manuals/vty/hlr_vty_additions.xml
Normal file
2
doc/manuals/vty/hlr_vty_additions.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<vtydoc xmlns='urn:osmocom:xml:libosmocore:vty:doc:1.0'>
|
||||
</vtydoc>
|
||||
1431
doc/manuals/vty/hlr_vty_reference.xml
Normal file
1431
doc/manuals/vty/hlr_vty_reference.xml
Normal file
File diff suppressed because it is too large
Load Diff
2
include/Makefile.am
Normal file
2
include/Makefile.am
Normal file
@@ -0,0 +1,2 @@
|
||||
nobase_include_HEADERS = osmocom/gsupclient/gsup_client.h
|
||||
|
||||
89
include/osmocom/gsupclient/gsup_client.h
Normal file
89
include/osmocom/gsupclient/gsup_client.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/* GPRS Subscriber Update Protocol client */
|
||||
|
||||
/* (C) 2014 by Sysmocom s.f.m.c. GmbH
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Jacob Erlbeck
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/gsm/oap_client.h>
|
||||
#include <osmocom/gsm/ipa.h>
|
||||
#include <osmocom/gsm/gsup.h>
|
||||
|
||||
/* a loss of GSUP between MSC and HLR is considered quite serious, let's try to recover as quickly as
|
||||
* possible. Even one new connection attempt per second should be quite acceptable until the link is
|
||||
* re-established */
|
||||
#define OSMO_GSUP_CLIENT_RECONNECT_INTERVAL 1
|
||||
#define OSMO_GSUP_CLIENT_PING_INTERVAL 20
|
||||
|
||||
struct msgb;
|
||||
struct ipa_client_conn;
|
||||
struct osmo_gsup_client;
|
||||
|
||||
/* Expects message in msg->l2h */
|
||||
typedef int (*osmo_gsup_client_read_cb_t)(struct osmo_gsup_client *gsupc, struct msgb *msg);
|
||||
|
||||
typedef bool (*osmo_gsup_client_up_down_cb_t)(struct osmo_gsup_client *gsupc, bool up);
|
||||
|
||||
struct osmo_gsup_client {
|
||||
const char *unit_name; /* same as ipa_dev->unit_name, for backwards compat */
|
||||
|
||||
struct ipa_client_conn *link;
|
||||
osmo_gsup_client_read_cb_t read_cb;
|
||||
void *data;
|
||||
|
||||
struct osmo_oap_client_state oap_state;
|
||||
|
||||
struct osmo_timer_list ping_timer;
|
||||
struct osmo_timer_list connect_timer;
|
||||
int is_connected;
|
||||
int got_ipa_pong;
|
||||
|
||||
struct ipaccess_unit *ipa_dev; /* identification information sent to IPA server */
|
||||
|
||||
osmo_gsup_client_up_down_cb_t up_down_cb;
|
||||
};
|
||||
|
||||
struct osmo_gsup_client *osmo_gsup_client_create3(void *talloc_ctx,
|
||||
struct ipaccess_unit *ipa_dev,
|
||||
const char *ip_addr,
|
||||
unsigned int tcp_port,
|
||||
struct osmo_oap_client_config *oapc_config,
|
||||
osmo_gsup_client_read_cb_t read_cb,
|
||||
osmo_gsup_client_up_down_cb_t up_down_cb,
|
||||
void *data);
|
||||
struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx,
|
||||
struct ipaccess_unit *ipa_dev,
|
||||
const char *ip_addr,
|
||||
unsigned int tcp_port,
|
||||
osmo_gsup_client_read_cb_t read_cb,
|
||||
struct osmo_oap_client_config *oapc_config);
|
||||
struct osmo_gsup_client *osmo_gsup_client_create(void *talloc_ctx,
|
||||
const char *unit_name,
|
||||
const char *ip_addr,
|
||||
unsigned int tcp_port,
|
||||
osmo_gsup_client_read_cb_t read_cb,
|
||||
struct osmo_oap_client_config *oapc_config);
|
||||
|
||||
void osmo_gsup_client_destroy(struct osmo_gsup_client *gsupc);
|
||||
int osmo_gsup_client_send(struct osmo_gsup_client *gsupc, struct msgb *msg);
|
||||
int osmo_gsup_client_enc_send(struct osmo_gsup_client *gsupc,
|
||||
const struct osmo_gsup_message *gsup_msg);
|
||||
struct msgb *osmo_gsup_client_msgb_alloc(void);
|
||||
|
||||
11
libosmo-gsup-client.pc.in
Normal file
11
libosmo-gsup-client.pc.in
Normal file
@@ -0,0 +1,11 @@
|
||||
prefix=@prefix@
|
||||
exec_prefix=@exec_prefix@
|
||||
libdir=@libdir@
|
||||
includedir=@includedir@
|
||||
|
||||
Name: Osmocom GSUP Client Library
|
||||
Description: C Utility Library
|
||||
Version: @VERSION@
|
||||
Libs: -L${libdir} -losmo-gsup-client
|
||||
Cflags: -I${includedir}/
|
||||
|
||||
@@ -3,5 +3,12 @@ EXTRA_DIST = \
|
||||
hlr.sql \
|
||||
$(NULL)
|
||||
|
||||
docsdir = $(datadir)/doc/osmo-hlr
|
||||
docs_DATA = $(srcdir)/hlr.sql
|
||||
sqldir = $(docdir)/sql
|
||||
sql_DATA = $(srcdir)/hlr.sql $(srcdir)/hlr_data.sql
|
||||
|
||||
|
||||
install-data-local:
|
||||
$(MKDIR_P) $(DESTDIR)$(localstatedir)/lib/osmocom
|
||||
|
||||
uninstall-hook:
|
||||
rm -rf $(DESTDIR)$(localstatedir)/lib/osmocom
|
||||
|
||||
35
sql/hlr.sql
35
sql/hlr.sql
@@ -1,16 +1,18 @@
|
||||
CREATE TABLE IF NOT EXISTS subscriber (
|
||||
CREATE TABLE subscriber (
|
||||
-- OsmoHLR's DB scheme is modelled roughly after TS 23.008 version 13.3.0
|
||||
id INTEGER PRIMARY KEY,
|
||||
-- Chapter 2.1.1.1
|
||||
imsi VARCHAR(15) UNIQUE NOT NULL,
|
||||
-- Chapter 2.1.2
|
||||
msisdn VARCHAR(15) UNIQUE,
|
||||
-- Chapter 2.2.3: Most recent / current IMEI
|
||||
-- Chapter 2.2.3: Most recent / current IMEISV
|
||||
imeisv VARCHAR,
|
||||
-- Chapter 2.1.9: Most recent / current IMEI
|
||||
imei VARCHAR(14),
|
||||
-- Chapter 2.4.5
|
||||
vlr_number VARCHAR(15),
|
||||
-- Chapter 2.4.6
|
||||
hlr_number VARCHAR(15),
|
||||
msc_number VARCHAR(15),
|
||||
-- Chapter 2.4.8.1
|
||||
sgsn_number VARCHAR(15),
|
||||
-- Chapter 2.13.10
|
||||
@@ -36,34 +38,47 @@ CREATE TABLE IF NOT EXISTS subscriber (
|
||||
-- Chapter 2.7.5
|
||||
ms_purged_cs BOOLEAN NOT NULL DEFAULT 0,
|
||||
-- Chapter 2.7.6
|
||||
ms_purged_ps BOOLEAN NOT NULL DEFAULT 0
|
||||
ms_purged_ps BOOLEAN NOT NULL DEFAULT 0,
|
||||
|
||||
-- Timestamp of last location update seen from subscriber
|
||||
-- The value is a string which encodes a UTC timestamp in granularity of seconds.
|
||||
last_lu_seen TIMESTAMP default NULL,
|
||||
-- When a LU was received via a proxy, that proxy's hlr_number is stored here,
|
||||
-- while vlr_number reflects the MSC on the far side of that proxy.
|
||||
vlr_via_proxy VARCHAR,
|
||||
sgsn_via_proxy VARCHAR
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS subscriber_apn (
|
||||
CREATE TABLE subscriber_apn (
|
||||
subscriber_id INTEGER, -- subscriber.id
|
||||
apn VARCHAR(256) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS subscriber_multi_msisdn (
|
||||
CREATE TABLE subscriber_multi_msisdn (
|
||||
-- Chapter 2.1.3
|
||||
subscriber_id INTEGER, -- subscriber.id
|
||||
msisdn VARCHAR(15) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auc_2g (
|
||||
CREATE TABLE auc_2g (
|
||||
subscriber_id INTEGER PRIMARY KEY, -- subscriber.id
|
||||
algo_id_2g INTEGER NOT NULL, -- enum osmo_auth_algo value
|
||||
ki VARCHAR(32) NOT NULL -- hex string: subscriber's secret key (128bit)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auc_3g (
|
||||
CREATE TABLE auc_3g (
|
||||
subscriber_id INTEGER PRIMARY KEY, -- subscriber.id
|
||||
algo_id_3g INTEGER NOT NULL, -- enum osmo_auth_algo value
|
||||
k VARCHAR(32) NOT NULL, -- hex string: subscriber's secret key (128bit)
|
||||
op VARCHAR(32), -- hex string: operator's secret key (128bit)
|
||||
opc VARCHAR(32), -- hex string: derived from OP and K (128bit)
|
||||
sqn INTEGER NOT NULL DEFAULT 0, -- sequence number of key usage
|
||||
ind_bitlen INTEGER NOT NULL DEFAULT 5 -- nr of index bits at lower SQN end
|
||||
-- nr of index bits at lower SQN end
|
||||
ind_bitlen INTEGER NOT NULL DEFAULT 5
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_subscr_imsi ON subscriber (imsi);
|
||||
CREATE UNIQUE INDEX idx_subscr_imsi ON subscriber (imsi);
|
||||
|
||||
-- Set HLR database schema version number
|
||||
-- Note: This constant is currently duplicated in src/db.c and must be kept in sync!
|
||||
PRAGMA user_version = 4;
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
SUBDIRS = gsupclient
|
||||
|
||||
AM_CFLAGS = \
|
||||
-Wall \
|
||||
$(LIBOSMOCORE_CFLAGS) \
|
||||
$(LIBOSMOGSM_CFLAGS) \
|
||||
$(LIBOSMOVTY_CFLAGS) \
|
||||
$(LIBOSMOCTRL_CFLAGS) \
|
||||
$(LIBOSMOMSLOOKUP_CFLAGS) \
|
||||
$(LIBOSMOABIS_CFLAGS) \
|
||||
$(SQLITE3_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/include \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_DIST = \
|
||||
populate_hlr_db.pl \
|
||||
db_bootstrap.sed \
|
||||
db_sql2c.sed \
|
||||
$(NULL)
|
||||
|
||||
BUILT_SOURCES = \
|
||||
@@ -22,7 +28,6 @@ noinst_HEADERS = \
|
||||
auc.h \
|
||||
db.h \
|
||||
hlr.h \
|
||||
luop.h \
|
||||
gsup_router.h \
|
||||
gsup_server.h \
|
||||
logging.h \
|
||||
@@ -30,23 +35,27 @@ noinst_HEADERS = \
|
||||
ctrl.h \
|
||||
hlr_vty.h \
|
||||
hlr_vty_subscr.h \
|
||||
hlr_ussd.h \
|
||||
db_bootstrap.h \
|
||||
proxy.h \
|
||||
dgsm.h \
|
||||
remote_hlr.h \
|
||||
global_title.h \
|
||||
mslookup_server.h \
|
||||
mslookup_server_mdns.h \
|
||||
lu_fsm.h \
|
||||
$(NULL)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
osmo-hlr \
|
||||
osmo-hlr-db-tool \
|
||||
$(NULL)
|
||||
|
||||
noinst_PROGRAMS = \
|
||||
db_test \
|
||||
osmo-euse-demo \
|
||||
$(NULL)
|
||||
|
||||
osmo_hlr_SOURCES = \
|
||||
auc.c \
|
||||
ctrl.c \
|
||||
db.c \
|
||||
luop.c \
|
||||
db_auc.c \
|
||||
db_hlr.c \
|
||||
gsup_router.c \
|
||||
@@ -56,13 +65,25 @@ osmo_hlr_SOURCES = \
|
||||
rand_urandom.c \
|
||||
hlr_vty.c \
|
||||
hlr_vty_subscr.c \
|
||||
gsup_send.c \
|
||||
hlr_ussd.c \
|
||||
proxy.c \
|
||||
dgsm.c \
|
||||
dgsm_vty.c \
|
||||
remote_hlr.c \
|
||||
mslookup_server.c \
|
||||
mslookup_server_mdns.c \
|
||||
global_title.c \
|
||||
lu_fsm.c \
|
||||
$(NULL)
|
||||
|
||||
osmo_hlr_LDADD = \
|
||||
$(top_builddir)/src/gsupclient/libosmo-gsup-client.la \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
$(LIBOSMOVTY_LIBS) \
|
||||
$(LIBOSMOCTRL_LIBS) \
|
||||
$(LIBOSMOMSLOOKUP_LIBS) \
|
||||
$(LIBOSMOABIS_LIBS) \
|
||||
$(SQLITE3_LIBS) \
|
||||
$(NULL)
|
||||
@@ -74,6 +95,7 @@ osmo_hlr_db_tool_SOURCES = \
|
||||
logging.c \
|
||||
rand_urandom.c \
|
||||
dbd_decode_binary.c \
|
||||
global_title.c \
|
||||
$(NULL)
|
||||
|
||||
osmo_hlr_db_tool_LDADD = \
|
||||
@@ -82,28 +104,28 @@ osmo_hlr_db_tool_LDADD = \
|
||||
$(SQLITE3_LIBS) \
|
||||
$(NULL)
|
||||
|
||||
db_test_SOURCES = \
|
||||
auc.c \
|
||||
db.c \
|
||||
db_auc.c \
|
||||
db_test.c \
|
||||
logging.c \
|
||||
rand_fake.c \
|
||||
osmo_euse_demo_SOURCES = \
|
||||
osmo-euse-demo.c \
|
||||
$(NULL)
|
||||
|
||||
db_test_LDADD = \
|
||||
osmo_euse_demo_LDADD = \
|
||||
$(top_builddir)/src/gsupclient/libosmo-gsup-client.la \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
$(SQLITE3_LIBS) \
|
||||
$(NULL)
|
||||
|
||||
if DB_SQLITE_DEBUG
|
||||
osmo_hlr_SOURCES += db_debug.c
|
||||
osmo_hlr_db_tool_SOURCES += db_debug.c
|
||||
endif
|
||||
|
||||
BOOTSTRAP_SQL = $(top_srcdir)/sql/hlr.sql
|
||||
|
||||
db_bootstrap.h: $(BOOTSTRAP_SQL) $(srcdir)/db_bootstrap.sed
|
||||
echo "/* DO NOT EDIT THIS FILE. It is generated from osmo-hlr.git/sql/hlr.sql */" > "$@"
|
||||
db_bootstrap.h: $(BOOTSTRAP_SQL) $(srcdir)/db_sql2c.sed
|
||||
echo "/* DO NOT EDIT THIS FILE. It is generated from files in osmo-hlr.git/sql/ */" > "$@"
|
||||
echo "#pragma once" >> "$@"
|
||||
echo "static const char *stmt_bootstrap_sql[] = {" >> "$@"
|
||||
cat "$(BOOTSTRAP_SQL)" \
|
||||
| sed -f "$(srcdir)/db_bootstrap.sed" \
|
||||
| sed -f "$(srcdir)/db_sql2c.sed" \
|
||||
>> "$@"
|
||||
echo "};" >> "$@"
|
||||
|
||||
@@ -144,6 +144,7 @@ int auc_compute_vectors(struct osmo_auth_vector *vec, unsigned int num_vec,
|
||||
|
||||
if (!aud2g) {
|
||||
/* use the 2G tokens from 3G keys */
|
||||
DBGP("vector [%u]: deriving 2G from 3G\n", i);
|
||||
DBGVB(kc);
|
||||
DBGVB(sres);
|
||||
DBGVV("0x%x", auth_types);
|
||||
@@ -151,7 +152,7 @@ int auc_compute_vectors(struct osmo_auth_vector *vec, unsigned int num_vec,
|
||||
}
|
||||
/* calculate 2G separately */
|
||||
|
||||
DBGP("vector [%u]: deriving 2G from 3G\n", i);
|
||||
DBGP("vector [%u]: calculating 2G separately\n", i);
|
||||
|
||||
rc = osmo_auth_gen_vec(&vtmp, aud2g, rand);
|
||||
if (rc < 0) {
|
||||
|
||||
24
src/ctrl.c
24
src/ctrl.c
@@ -95,7 +95,7 @@ static bool get_subscriber(struct db_context *dbc,
|
||||
cmd->reply = "No such subscriber.";
|
||||
return false;
|
||||
default:
|
||||
cmd->reply = "An unknown error has occured during get_subscriber().";
|
||||
cmd->reply = "An unknown error has occurred during get_subscriber().";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ static void print_subscr_info(struct ctrl_cmd *cmd,
|
||||
struct hlr_subscriber *subscr)
|
||||
{
|
||||
ctrl_cmd_reply_printf(cmd,
|
||||
"\nid\t%"PRIu64
|
||||
"\nid\t%" PRIu64
|
||||
FMT_S
|
||||
FMT_S
|
||||
FMT_BOOL
|
||||
@@ -189,7 +189,7 @@ static void print_subscr_info_aud3g(struct ctrl_cmd *cmd, struct osmo_sub_auth_d
|
||||
ctrl_cmd_reply_printf(cmd,
|
||||
"\naud3g.%s\t%s"
|
||||
"\naud3g.ind_bitlen\t%u"
|
||||
"\naud3g.sqn\t%"PRIu64
|
||||
"\naud3g.sqn\t%" PRIu64
|
||||
,
|
||||
aud->u.umts.opc_is_op? "op" : "opc",
|
||||
hexdump_buf(aud->u.umts.opc),
|
||||
@@ -228,11 +228,16 @@ static int get_subscr_info_aud(struct ctrl_cmd *cmd, void *data)
|
||||
|
||||
rc = db_get_auth_data(hlr->dbc, imsi, &aud2g, &aud3g, NULL);
|
||||
|
||||
if (rc == -ENOENT) {
|
||||
switch (rc) {
|
||||
case 0:
|
||||
break;
|
||||
case -ENOENT:
|
||||
case -ENOKEY:
|
||||
/* No auth data found, tell the print*() functions about it. */
|
||||
aud2g.algo = OSMO_AUTH_ALG_NONE;
|
||||
aud3g.algo = OSMO_AUTH_ALG_NONE;
|
||||
} else if (rc) {
|
||||
break;
|
||||
default:
|
||||
cmd->reply = "Error retrieving authentication data.";
|
||||
return CTRL_CMD_ERROR;
|
||||
}
|
||||
@@ -258,11 +263,16 @@ static int get_subscr_info_all(struct ctrl_cmd *cmd, void *data)
|
||||
|
||||
rc = db_get_auth_data(hlr->dbc, subscr.imsi, &aud2g, &aud3g, NULL);
|
||||
|
||||
if (rc == -ENOENT) {
|
||||
switch (rc) {
|
||||
case 0:
|
||||
break;
|
||||
case -ENOENT:
|
||||
case -ENOKEY:
|
||||
/* No auth data found, tell the print*() functions about it. */
|
||||
aud2g.algo = OSMO_AUTH_ALG_NONE;
|
||||
aud3g.algo = OSMO_AUTH_ALG_NONE;
|
||||
} else if (rc) {
|
||||
break;
|
||||
default:
|
||||
cmd->reply = "Error retrieving authentication data.";
|
||||
return CTRL_CMD_ERROR;
|
||||
}
|
||||
|
||||
418
src/db.c
418
src/db.c
@@ -22,15 +22,20 @@
|
||||
#include <stdbool.h>
|
||||
#include <sqlite3.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "logging.h"
|
||||
#include "db.h"
|
||||
#include "db_bootstrap.h"
|
||||
|
||||
/* This constant is currently duplicated in sql/hlr.sql and must be kept in sync! */
|
||||
#define CURRENT_SCHEMA_VERSION 4
|
||||
|
||||
#define SEL_COLUMNS \
|
||||
"id," \
|
||||
"imsi," \
|
||||
"msisdn," \
|
||||
"imei," \
|
||||
"vlr_number," \
|
||||
"sgsn_number," \
|
||||
"sgsn_address," \
|
||||
@@ -40,14 +45,19 @@
|
||||
"nam_ps," \
|
||||
"lmsi," \
|
||||
"ms_purged_cs," \
|
||||
"ms_purged_ps"
|
||||
"ms_purged_ps," \
|
||||
"last_lu_seen," \
|
||||
"vlr_via_proxy," \
|
||||
"sgsn_via_proxy"
|
||||
|
||||
static const char *stmt_sql[] = {
|
||||
[DB_STMT_SEL_BY_IMSI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imsi = ?",
|
||||
[DB_STMT_SEL_BY_MSISDN] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE msisdn = ?",
|
||||
[DB_STMT_SEL_BY_ID] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE id = ?",
|
||||
[DB_STMT_UPD_VLR_BY_ID] = "UPDATE subscriber SET vlr_number = $number WHERE id = $subscriber_id",
|
||||
[DB_STMT_UPD_SGSN_BY_ID] = "UPDATE subscriber SET sgsn_number = $number WHERE id = $subscriber_id",
|
||||
[DB_STMT_SEL_BY_IMEI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imei = ?",
|
||||
[DB_STMT_UPD_VLR_BY_ID] = "UPDATE subscriber SET vlr_number = $number, vlr_via_proxy = $proxy WHERE id = $subscriber_id",
|
||||
[DB_STMT_UPD_SGSN_BY_ID] = "UPDATE subscriber SET sgsn_number = $number, sgsn_via_proxy = $proxy WHERE id = $subscriber_id",
|
||||
[DB_STMT_UPD_IMEI_BY_IMSI] = "UPDATE subscriber SET imei = $imei WHERE imsi = $imsi",
|
||||
[DB_STMT_AUC_BY_IMSI] =
|
||||
"SELECT id, algo_id_2g, ki, algo_id_3g, k, op, opc, sqn, ind_bitlen"
|
||||
" FROM subscriber"
|
||||
@@ -59,9 +69,10 @@ static const char *stmt_sql[] = {
|
||||
[DB_STMT_UPD_PURGE_PS_BY_IMSI] = "UPDATE subscriber SET ms_purged_ps = $val WHERE imsi = $imsi",
|
||||
[DB_STMT_UPD_NAM_CS_BY_IMSI] = "UPDATE subscriber SET nam_cs = $val WHERE imsi = $imsi",
|
||||
[DB_STMT_UPD_NAM_PS_BY_IMSI] = "UPDATE subscriber SET nam_ps = $val WHERE imsi = $imsi",
|
||||
[DB_STMT_SUBSCR_CREATE] = "INSERT INTO subscriber (imsi) VALUES ($imsi)",
|
||||
[DB_STMT_SUBSCR_CREATE] = "INSERT INTO subscriber (imsi, nam_cs, nam_ps) VALUES ($imsi, $nam_cs, $nam_ps)",
|
||||
[DB_STMT_DEL_BY_ID] = "DELETE FROM subscriber WHERE id = $subscriber_id",
|
||||
[DB_STMT_SET_MSISDN_BY_IMSI] = "UPDATE subscriber SET msisdn = $msisdn WHERE imsi = $imsi",
|
||||
[DB_STMT_DELETE_MSISDN_BY_IMSI] = "UPDATE subscriber SET msisdn = NULL WHERE imsi = $imsi",
|
||||
[DB_STMT_AUC_2G_INSERT] =
|
||||
"INSERT INTO auc_2g (subscriber_id, algo_id_2g, ki)"
|
||||
" VALUES($subscriber_id, $algo_id_2g, $ki)",
|
||||
@@ -70,6 +81,16 @@ static const char *stmt_sql[] = {
|
||||
"INSERT INTO auc_3g (subscriber_id, algo_id_3g, k, op, opc, ind_bitlen)"
|
||||
" VALUES($subscriber_id, $algo_id_3g, $k, $op, $opc, $ind_bitlen)",
|
||||
[DB_STMT_AUC_3G_DELETE] = "DELETE FROM auc_3g WHERE subscriber_id = $subscriber_id",
|
||||
[DB_STMT_SET_LAST_LU_SEEN] = "UPDATE subscriber SET last_lu_seen = datetime($val, 'unixepoch') WHERE id = $subscriber_id",
|
||||
[DB_STMT_EXISTS_BY_IMSI] = "SELECT 1 FROM subscriber WHERE imsi = $imsi",
|
||||
[DB_STMT_EXISTS_BY_MSISDN] = "SELECT 1 FROM subscriber WHERE msisdn = $msisdn",
|
||||
|
||||
#if 0
|
||||
[DB_STMT_PROXY_UPDATE] = "INSERT OR REPLACE INTO"
|
||||
" proxy (imsi, remote_ip, remote_port)"
|
||||
" VALUES ($imsi, $remote_ip, $remote_port)",
|
||||
[DB_STMT_PROXY_GET_BY_IMSI] = "SELECT imsi, remote_ip, remote_port FROM proxy WHERE imsi = $imsi",
|
||||
#endif
|
||||
};
|
||||
|
||||
static void sql3_error_log_cb(void *arg, int err_code, const char *msg)
|
||||
@@ -99,6 +120,8 @@ static void sql3_sql_log_cb(void *arg, sqlite3 *s3, const char *stmt, int type)
|
||||
void db_remove_reset(sqlite3_stmt *stmt)
|
||||
{
|
||||
sqlite3_clear_bindings(stmt);
|
||||
/* sqlite3_reset() just repeats an error code already evaluated during sqlite3_step(). */
|
||||
/* coverity[CHECKED_RETURN] */
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
|
||||
@@ -168,58 +191,339 @@ bool db_bind_int64(sqlite3_stmt *stmt, const char *param_name, int64_t nr)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool db_bind_null(sqlite3_stmt *stmt, const char *param_name)
|
||||
{
|
||||
int rc;
|
||||
int idx = param_name ? sqlite3_bind_parameter_index(stmt, param_name) : 1;
|
||||
if (idx < 1) {
|
||||
LOGP(DDB, LOGL_ERROR, "Error composing SQL, cannot bind parameter '%s'\n",
|
||||
param_name);
|
||||
return false;
|
||||
}
|
||||
rc = sqlite3_bind_null(stmt, idx);
|
||||
if (rc != SQLITE_OK) {
|
||||
LOGP(DDB, LOGL_ERROR, "Error binding NULL to SQL parameter %s: %d\n",
|
||||
param_name ? param_name : "#1", rc);
|
||||
db_remove_reset(stmt);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void db_close(struct db_context *dbc)
|
||||
{
|
||||
unsigned int i;
|
||||
int rc;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dbc->stmt); i++) {
|
||||
/* it is ok to call finalize on NULL */
|
||||
sqlite3_finalize(dbc->stmt[i]);
|
||||
}
|
||||
sqlite3_close(dbc->db);
|
||||
|
||||
/* Ask sqlite3 to close DB */
|
||||
rc = sqlite3_close(dbc->db);
|
||||
if (rc != SQLITE_OK) { /* Make sure it's actually closed! */
|
||||
LOGP(DDB, LOGL_ERROR, "Couldn't close database: (rc=%d) %s\n",
|
||||
rc, sqlite3_errmsg(dbc->db));
|
||||
}
|
||||
|
||||
talloc_free(dbc);
|
||||
}
|
||||
|
||||
static int db_run_statements(struct db_context *dbc, const char **statements, size_t statements_count)
|
||||
{
|
||||
int rc;
|
||||
int i;
|
||||
for (i = 0; i < statements_count; i++) {
|
||||
const char *stmt_str = statements[i];
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
rc = sqlite3_prepare_v2(dbc->db, stmt_str, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", stmt_str);
|
||||
return rc;
|
||||
}
|
||||
rc = sqlite3_step(stmt);
|
||||
db_remove_reset(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
LOGP(DDB, LOGL_ERROR, "SQL error: (%d) %s, during stmt '%s'",
|
||||
rc, sqlite3_errmsg(dbc->db), stmt_str);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int db_bootstrap(struct db_context *dbc)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < ARRAY_SIZE(stmt_bootstrap_sql); i++) {
|
||||
int rc;
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
rc = sqlite3_prepare_v2(dbc->db, stmt_bootstrap_sql[i], -1,
|
||||
&stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n",
|
||||
stmt_bootstrap_sql[i]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* execute the statement */
|
||||
rc = sqlite3_step(stmt);
|
||||
db_remove_reset(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
LOGP(DDB, LOGL_ERROR, "Cannot bootstrap database: SQL error: (%d) %s,"
|
||||
" during stmt '%s'",
|
||||
rc, sqlite3_errmsg(dbc->db),
|
||||
stmt_bootstrap_sql[i]);
|
||||
return -1;
|
||||
}
|
||||
int rc = db_run_statements(dbc, stmt_bootstrap_sql, ARRAY_SIZE(stmt_bootstrap_sql));
|
||||
if (rc != SQLITE_DONE) {
|
||||
LOGP(DDB, LOGL_ERROR, "Cannot bootstrap database\n");
|
||||
return rc;
|
||||
}
|
||||
return 0;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
struct db_context *db_open(void *ctx, const char *fname)
|
||||
/* https://www.sqlite.org/fileformat2.html#storage_of_the_sql_database_schema */
|
||||
static bool db_table_exists(struct db_context *dbc, const char *table_name)
|
||||
{
|
||||
const char *table_exists_sql = "SELECT name FROM sqlite_master WHERE type='table' AND name=?";
|
||||
sqlite3_stmt *stmt;
|
||||
int rc;
|
||||
|
||||
rc = sqlite3_prepare_v2(dbc->db, table_exists_sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", table_exists_sql);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!db_bind_text(stmt, NULL, table_name))
|
||||
return false;
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
db_remove_reset(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
return (rc == SQLITE_ROW);
|
||||
}
|
||||
|
||||
/* Indicate whether the database is initialized with tables for schema version 0.
|
||||
* We only check for the 'subscriber' table here because Neels said so. */
|
||||
static bool db_is_bootstrapped_v0(struct db_context *dbc)
|
||||
{
|
||||
if (!db_table_exists(dbc, "subscriber")) {
|
||||
LOGP(DDB, LOGL_DEBUG, "Table 'subscriber' not found in database '%s'\n", dbc->fname);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
db_upgrade_v1(struct db_context *dbc)
|
||||
{
|
||||
int rc;
|
||||
const char *statements[] = {
|
||||
"ALTER TABLE subscriber ADD COLUMN last_lu_seen TIMESTAMP default NULL",
|
||||
"PRAGMA user_version = 1",
|
||||
};
|
||||
|
||||
rc = db_run_statements(dbc, statements, ARRAY_SIZE(statements));
|
||||
if (rc != SQLITE_DONE) {
|
||||
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 1\n");
|
||||
return rc;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int db_upgrade_v2(struct db_context *dbc)
|
||||
{
|
||||
int rc;
|
||||
const char *statements[] = {
|
||||
"ALTER TABLE subscriber ADD COLUMN imei VARCHAR(14)",
|
||||
"PRAGMA user_version = 2",
|
||||
};
|
||||
|
||||
rc = db_run_statements(dbc, statements, ARRAY_SIZE(statements));
|
||||
if (rc != SQLITE_DONE) {
|
||||
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 2\n");
|
||||
return rc;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int db_upgrade_v3(struct db_context *dbc)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* A newer SQLite version would allow simply 'ATLER TABLE subscriber RENAME COLUMN hlr_number TO msc_number'.
|
||||
* This is a really expensive workaround for that in order to cover earlier SQLite versions as well:
|
||||
* Create a new table with the new column name and copy the data over (https://www.sqlite.org/faq.html#q11).
|
||||
*/
|
||||
#define SUBSCR_V3_CREATE \
|
||||
"(\n" \
|
||||
"-- OsmoHLR's DB scheme is modelled roughly after TS 23.008 version 13.3.0\n" \
|
||||
" id INTEGER PRIMARY KEY,\n" \
|
||||
" -- Chapter 2.1.1.1\n" \
|
||||
" imsi VARCHAR(15) UNIQUE NOT NULL,\n" \
|
||||
" -- Chapter 2.1.2\n" \
|
||||
" msisdn VARCHAR(15) UNIQUE,\n" \
|
||||
" -- Chapter 2.2.3: Most recent / current IMEISV\n" \
|
||||
" imeisv VARCHAR,\n" \
|
||||
" -- Chapter 2.1.9: Most recent / current IMEI\n" \
|
||||
" imei VARCHAR(14),\n" \
|
||||
" -- Chapter 2.4.5\n" \
|
||||
" vlr_number VARCHAR(15),\n" \
|
||||
" -- Chapter 2.4.6\n" \
|
||||
" msc_number VARCHAR(15),\n" \
|
||||
" -- Chapter 2.4.8.1\n" \
|
||||
" sgsn_number VARCHAR(15),\n" \
|
||||
" -- Chapter 2.13.10\n" \
|
||||
" sgsn_address VARCHAR,\n" \
|
||||
" -- Chapter 2.4.8.2\n" \
|
||||
" ggsn_number VARCHAR(15),\n" \
|
||||
" -- Chapter 2.4.9.2\n" \
|
||||
" gmlc_number VARCHAR(15),\n" \
|
||||
" -- Chapter 2.4.23\n" \
|
||||
" smsc_number VARCHAR(15),\n" \
|
||||
" -- Chapter 2.4.24\n" \
|
||||
" periodic_lu_tmr INTEGER,\n" \
|
||||
" -- Chapter 2.13.115\n" \
|
||||
" periodic_rau_tau_tmr INTEGER,\n" \
|
||||
" -- Chapter 2.1.1.2: network access mode\n" \
|
||||
" nam_cs BOOLEAN NOT NULL DEFAULT 1,\n" \
|
||||
" nam_ps BOOLEAN NOT NULL DEFAULT 1,\n" \
|
||||
" -- Chapter 2.1.8\n" \
|
||||
" lmsi INTEGER,\n" \
|
||||
\
|
||||
" -- The below purged flags might not even be stored non-volatile,\n" \
|
||||
" -- refer to TS 23.012 Chapter 3.6.1.4\n" \
|
||||
" -- Chapter 2.7.5\n" \
|
||||
" ms_purged_cs BOOLEAN NOT NULL DEFAULT 0,\n" \
|
||||
" -- Chapter 2.7.6\n" \
|
||||
" ms_purged_ps BOOLEAN NOT NULL DEFAULT 0,\n" \
|
||||
\
|
||||
" -- Timestamp of last location update seen from subscriber\n" \
|
||||
" -- The value is a string which encodes a UTC timestamp in granularity of seconds.\n" \
|
||||
" last_lu_seen TIMESTAMP default NULL\n" \
|
||||
")\n"
|
||||
|
||||
#define SUBSCR_V2_COLUMN_NAMES \
|
||||
"id," \
|
||||
"imsi," \
|
||||
"msisdn," \
|
||||
"imeisv," \
|
||||
"imei," \
|
||||
"vlr_number," \
|
||||
"hlr_number," \
|
||||
"sgsn_number," \
|
||||
"sgsn_address," \
|
||||
"ggsn_number," \
|
||||
"gmlc_number," \
|
||||
"smsc_number," \
|
||||
"periodic_lu_tmr," \
|
||||
"periodic_rau_tau_tmr," \
|
||||
"nam_cs," \
|
||||
"nam_ps," \
|
||||
"lmsi," \
|
||||
"ms_purged_cs," \
|
||||
"ms_purged_ps," \
|
||||
"last_lu_seen"
|
||||
|
||||
#define SUBSCR_V3_COLUMN_NAMES \
|
||||
"id," \
|
||||
"imsi," \
|
||||
"msisdn," \
|
||||
"imeisv," \
|
||||
"imei," \
|
||||
"vlr_number," \
|
||||
"msc_number," \
|
||||
"sgsn_number," \
|
||||
"sgsn_address," \
|
||||
"ggsn_number," \
|
||||
"gmlc_number," \
|
||||
"smsc_number," \
|
||||
"periodic_lu_tmr," \
|
||||
"periodic_rau_tau_tmr," \
|
||||
"nam_cs," \
|
||||
"nam_ps," \
|
||||
"lmsi," \
|
||||
"ms_purged_cs," \
|
||||
"ms_purged_ps," \
|
||||
"last_lu_seen"
|
||||
|
||||
const char *statements[] = {
|
||||
"BEGIN TRANSACTION",
|
||||
"CREATE TEMPORARY TABLE subscriber_backup" SUBSCR_V3_CREATE,
|
||||
"INSERT INTO subscriber_backup SELECT " SUBSCR_V2_COLUMN_NAMES " FROM subscriber",
|
||||
"DROP TABLE subscriber",
|
||||
"CREATE TABLE subscriber" SUBSCR_V3_CREATE,
|
||||
"INSERT INTO subscriber SELECT " SUBSCR_V3_COLUMN_NAMES " FROM subscriber_backup",
|
||||
"DROP TABLE subscriber_backup",
|
||||
"COMMIT",
|
||||
"PRAGMA user_version = 3",
|
||||
};
|
||||
|
||||
rc = db_run_statements(dbc, statements, ARRAY_SIZE(statements));
|
||||
if (rc != SQLITE_DONE) {
|
||||
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 3\n");
|
||||
return rc;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int db_upgrade_v4(struct db_context *dbc)
|
||||
{
|
||||
int rc;
|
||||
const char *statements[] = {
|
||||
"ALTER TABLE subscriber ADD COLUMN vlr_via_proxy VARCHAR",
|
||||
"ALTER TABLE subscriber ADD COLUMN sgsn_via_proxy VARCHAR",
|
||||
"PRAGMA user_version = 4",
|
||||
};
|
||||
|
||||
rc = db_run_statements(dbc, statements, ARRAY_SIZE(statements));
|
||||
if (rc != SQLITE_DONE) {
|
||||
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 4\n");
|
||||
return rc;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
typedef int (*db_upgrade_func_t)(struct db_context *dbc);
|
||||
static db_upgrade_func_t db_upgrade_path[] = {
|
||||
db_upgrade_v1,
|
||||
db_upgrade_v2,
|
||||
db_upgrade_v3,
|
||||
db_upgrade_v4,
|
||||
};
|
||||
|
||||
static int db_get_user_version(struct db_context *dbc)
|
||||
{
|
||||
const char *user_version_sql = "PRAGMA user_version";
|
||||
sqlite3_stmt *stmt;
|
||||
int version, rc;
|
||||
|
||||
rc = sqlite3_prepare_v2(dbc->db, user_version_sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", user_version_sql);
|
||||
return -1;
|
||||
}
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc == SQLITE_ROW) {
|
||||
version = sqlite3_column_int(stmt, 0);
|
||||
} else {
|
||||
LOGP(DDB, LOGL_ERROR, "SQL statement '%s' failed: %d\n", user_version_sql, rc);
|
||||
version = -1;
|
||||
}
|
||||
|
||||
db_remove_reset(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
return version;
|
||||
}
|
||||
|
||||
struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite_logging, bool allow_upgrade)
|
||||
{
|
||||
struct db_context *dbc = talloc_zero(ctx, struct db_context);
|
||||
unsigned int i;
|
||||
int rc;
|
||||
bool has_sqlite_config_sqllog = false;
|
||||
int version;
|
||||
|
||||
LOGP(DDB, LOGL_NOTICE, "using database: %s\n", fname);
|
||||
LOGP(DDB, LOGL_INFO, "Compiled against SQLite3 lib version %s\n", SQLITE_VERSION);
|
||||
LOGP(DDB, LOGL_INFO, "Running with SQLite3 lib version %s\n", sqlite3_libversion());
|
||||
|
||||
#ifdef SQLITE_USE_TALLOC
|
||||
/* Configure SQLite3 to use talloc memory allocator */
|
||||
rc = db_sqlite3_use_talloc(ctx);
|
||||
if (rc == SQLITE_OK) {
|
||||
LOGP(DDB, LOGL_NOTICE, "SQLite3 is configured to use talloc\n");
|
||||
} else {
|
||||
LOGP(DDB, LOGL_ERROR, "Failed to configure SQLite3 "
|
||||
"to use talloc, using default memory allocator\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
dbc->fname = talloc_strdup(dbc, fname);
|
||||
|
||||
for (i = 0; i < 0xfffff; i++) {
|
||||
@@ -231,9 +535,11 @@ struct db_context *db_open(void *ctx, const char *fname)
|
||||
has_sqlite_config_sqllog = true;
|
||||
}
|
||||
|
||||
rc = sqlite3_config(SQLITE_CONFIG_LOG, sql3_error_log_cb, NULL);
|
||||
if (rc != SQLITE_OK)
|
||||
LOGP(DDB, LOGL_NOTICE, "Unable to set SQLite3 error log callback\n");
|
||||
if (enable_sqlite_logging) {
|
||||
rc = sqlite3_config(SQLITE_CONFIG_LOG, sql3_error_log_cb, NULL);
|
||||
if (rc != SQLITE_OK)
|
||||
LOGP(DDB, LOGL_NOTICE, "Unable to set SQLite3 error log callback\n");
|
||||
}
|
||||
|
||||
if (has_sqlite_config_sqllog) {
|
||||
rc = sqlite3_config(SQLITE_CONFIG_SQLLOG, sql3_sql_log_cb, NULL);
|
||||
@@ -261,7 +567,51 @@ struct db_context *db_open(void *ctx, const char *fname)
|
||||
LOGP(DDB, LOGL_ERROR, "Unable to set Write-Ahead Logging: %s\n",
|
||||
err_msg);
|
||||
|
||||
db_bootstrap(dbc);
|
||||
version = db_get_user_version(dbc);
|
||||
if (version < 0) {
|
||||
LOGP(DDB, LOGL_ERROR, "Unable to read user version number from database '%s'\n", dbc->fname);
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
/* An empty database will always report version zero. */
|
||||
if (version == 0 && !db_is_bootstrapped_v0(dbc)) {
|
||||
LOGP(DDB, LOGL_NOTICE, "Missing database tables detected; Bootstrapping database '%s'\n", dbc->fname);
|
||||
rc = db_bootstrap(dbc);
|
||||
if (rc != SQLITE_OK) {
|
||||
LOGP(DDB, LOGL_ERROR, "Failed to bootstrap DB: (rc=%d) %s\n",
|
||||
rc, sqlite3_errmsg(dbc->db));
|
||||
goto out_free;
|
||||
}
|
||||
version = CURRENT_SCHEMA_VERSION;
|
||||
}
|
||||
|
||||
LOGP(DDB, LOGL_NOTICE, "Database '%s' has HLR DB schema version %d\n", dbc->fname, version);
|
||||
|
||||
for (; allow_upgrade && (version < ARRAY_SIZE(db_upgrade_path)); version++) {
|
||||
db_upgrade_func_t upgrade_func = db_upgrade_path[version];
|
||||
rc = upgrade_func(dbc);
|
||||
if (rc != SQLITE_DONE) {
|
||||
LOGP(DDB, LOGL_ERROR, "Failed to upgrade HLR DB schema to version %d: (rc=%d) %s\n",
|
||||
version+1, rc, sqlite3_errmsg(dbc->db));
|
||||
goto out_free;
|
||||
}
|
||||
LOGP(DDB, LOGL_NOTICE, "Database '%s' has been upgraded to HLR DB schema version %d\n",
|
||||
dbc->fname, version+1);
|
||||
}
|
||||
|
||||
if (version != CURRENT_SCHEMA_VERSION) {
|
||||
if (version < CURRENT_SCHEMA_VERSION) {
|
||||
LOGP(DDB, LOGL_NOTICE, "HLR DB schema version %d is outdated\n", version);
|
||||
if (!allow_upgrade) {
|
||||
LOGP(DDB, LOGL_ERROR, "Not upgrading HLR database to schema version %d; "
|
||||
"use the --db-upgrade option to allow HLR database upgrades\n",
|
||||
CURRENT_SCHEMA_VERSION);
|
||||
}
|
||||
} else
|
||||
LOGP(DDB, LOGL_ERROR, "HLR DB schema version %d is unknown\n", version);
|
||||
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
/* prepare all SQL statements */
|
||||
for (i = 0; i < ARRAY_SIZE(dbc->stmt); i++) {
|
||||
|
||||
61
src/db.h
61
src/db.h
@@ -3,14 +3,18 @@
|
||||
#include <stdbool.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include "global_title.h"
|
||||
|
||||
struct hlr;
|
||||
|
||||
enum stmt_idx {
|
||||
DB_STMT_SEL_BY_IMSI,
|
||||
DB_STMT_SEL_BY_MSISDN,
|
||||
DB_STMT_SEL_BY_ID,
|
||||
DB_STMT_SEL_BY_IMEI,
|
||||
DB_STMT_UPD_VLR_BY_ID,
|
||||
DB_STMT_UPD_SGSN_BY_ID,
|
||||
DB_STMT_UPD_IMEI_BY_IMSI,
|
||||
DB_STMT_AUC_BY_IMSI,
|
||||
DB_STMT_AUC_UPD_SQN,
|
||||
DB_STMT_UPD_PURGE_CS_BY_IMSI,
|
||||
@@ -20,10 +24,18 @@ enum stmt_idx {
|
||||
DB_STMT_SUBSCR_CREATE,
|
||||
DB_STMT_DEL_BY_ID,
|
||||
DB_STMT_SET_MSISDN_BY_IMSI,
|
||||
DB_STMT_DELETE_MSISDN_BY_IMSI,
|
||||
DB_STMT_AUC_2G_INSERT,
|
||||
DB_STMT_AUC_2G_DELETE,
|
||||
DB_STMT_AUC_3G_INSERT,
|
||||
DB_STMT_AUC_3G_DELETE,
|
||||
DB_STMT_SET_LAST_LU_SEEN,
|
||||
DB_STMT_EXISTS_BY_IMSI,
|
||||
DB_STMT_EXISTS_BY_MSISDN,
|
||||
#if 0
|
||||
DB_STMT_PROXY_UPDATE,
|
||||
DB_STMT_PROXY_GET_BY_IMSI,
|
||||
#endif
|
||||
_NUM_DB_STMT
|
||||
};
|
||||
|
||||
@@ -33,12 +45,18 @@ struct db_context {
|
||||
sqlite3_stmt *stmt[_NUM_DB_STMT];
|
||||
};
|
||||
|
||||
/* Optional feature to make SQLite3 using talloc */
|
||||
#ifdef SQLITE_USE_TALLOC
|
||||
int db_sqlite3_use_talloc(void *ctx);
|
||||
#endif
|
||||
|
||||
void db_remove_reset(sqlite3_stmt *stmt);
|
||||
bool db_bind_text(sqlite3_stmt *stmt, const char *param_name, const char *text);
|
||||
bool db_bind_int(sqlite3_stmt *stmt, const char *param_name, int nr);
|
||||
bool db_bind_int64(sqlite3_stmt *stmt, const char *param_name, int64_t nr);
|
||||
bool db_bind_null(sqlite3_stmt *stmt, const char *param_name);
|
||||
void db_close(struct db_context *dbc);
|
||||
struct db_context *db_open(void *ctx, const char *fname);
|
||||
struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite3_logging, bool allow_upgrades);
|
||||
|
||||
#include <osmocom/crypt/auth.h>
|
||||
|
||||
@@ -67,10 +85,11 @@ struct hlr_subscriber {
|
||||
|
||||
int64_t id;
|
||||
char imsi[GSM23003_IMSI_MAX_DIGITS+1];
|
||||
char msisdn[GT_MAX_DIGITS+1];
|
||||
char msisdn[GSM23003_MSISDN_MAX_DIGITS+1];
|
||||
/* imeisv? */
|
||||
char vlr_number[GT_MAX_DIGITS+1];
|
||||
char sgsn_number[GT_MAX_DIGITS+1];
|
||||
char imei[GSM23003_IMEI_NUM_DIGITS+1];
|
||||
char vlr_number[32];
|
||||
char sgsn_number[32];
|
||||
char sgsn_address[GT_MAX_DIGITS+1];
|
||||
/* ggsn number + address */
|
||||
/* gmlc number */
|
||||
@@ -82,8 +101,16 @@ struct hlr_subscriber {
|
||||
uint32_t lmsi;
|
||||
bool ms_purged_cs;
|
||||
bool ms_purged_ps;
|
||||
time_t last_lu_seen;
|
||||
/* talloc'd IPA unit name */
|
||||
struct global_title vlr_via_proxy;
|
||||
};
|
||||
|
||||
/* A format string for use with strptime(3). This format string is
|
||||
* used to parse the last_lu_seen column stored in the HLR database.
|
||||
* See https://sqlite.org/lang_datefunc.html, function datetime(). */
|
||||
#define DB_LAST_LU_SEEN_FMT "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
/* Like struct osmo_sub_auth_data, but the keys are in hexdump representation.
|
||||
* This is useful because SQLite requires them in hexdump format, and callers
|
||||
* like the VTY and CTRL interface also have them available as hexdump to begin
|
||||
@@ -107,13 +134,20 @@ struct sub_auth_data_str {
|
||||
} u;
|
||||
};
|
||||
|
||||
int db_subscr_create(struct db_context *dbc, const char *imsi);
|
||||
#define DB_SUBSCR_FLAG_NAM_CS (1 << 1)
|
||||
#define DB_SUBSCR_FLAG_NAM_PS (1 << 2)
|
||||
|
||||
int db_subscr_create(struct db_context *dbc, const char *imsi, uint8_t flags);
|
||||
int db_subscr_delete_by_id(struct db_context *dbc, int64_t subscr_id);
|
||||
|
||||
int db_subscr_update_msisdn_by_imsi(struct db_context *dbc, const char *imsi,
|
||||
const char *msisdn);
|
||||
int db_subscr_update_aud_by_id(struct db_context *dbc, int64_t subscr_id,
|
||||
const struct sub_auth_data_str *aud);
|
||||
int db_subscr_update_imei_by_imsi(struct db_context *dbc, const char* imsi, const char *imei);
|
||||
|
||||
int db_subscr_exists_by_imsi(struct db_context *dbc, const char *imsi);
|
||||
int db_subscr_exists_by_msisdn(struct db_context *dbc, const char *msisdn);
|
||||
|
||||
int db_subscr_get_by_imsi(struct db_context *dbc, const char *imsi,
|
||||
struct hlr_subscriber *subscr);
|
||||
@@ -121,15 +155,15 @@ int db_subscr_get_by_msisdn(struct db_context *dbc, const char *msisdn,
|
||||
struct hlr_subscriber *subscr);
|
||||
int db_subscr_get_by_id(struct db_context *dbc, int64_t id,
|
||||
struct hlr_subscriber *subscr);
|
||||
int db_subscr_get_by_imei(struct db_context *dbc, const char *imei, struct hlr_subscriber *subscr);
|
||||
int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps);
|
||||
int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
|
||||
const char *vlr_or_sgsn_number, bool is_ps);
|
||||
const struct global_title *vlr_name, bool is_ps,
|
||||
const struct global_title *via_proxy);
|
||||
|
||||
int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
|
||||
bool purge_val, bool is_ps);
|
||||
|
||||
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps);
|
||||
|
||||
/*! Call sqlite3_column_text() and copy result to a char[].
|
||||
* \param[out] buf A char[] used as sizeof() arg(!) and osmo_strlcpy() target.
|
||||
* \param[in] stmt An sqlite3_stmt*.
|
||||
@@ -140,3 +174,14 @@ int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val,
|
||||
const char *_txt = (const char *) sqlite3_column_text(stmt, idx); \
|
||||
osmo_strlcpy(buf, _txt, sizeof(buf)); \
|
||||
} while (0)
|
||||
|
||||
/*! Call sqlite3_column_text() and copy result to a struct global_title.
|
||||
* \param[out] gt A struct global_title* to write to.
|
||||
* \param[in] stmt An sqlite3_stmt*.
|
||||
* \param[in] idx Index in stmt's returned columns.
|
||||
*/
|
||||
#define copy_sqlite3_text_to_gt(gt, stmt, idx) \
|
||||
do { \
|
||||
const char *_txt = (const char *) sqlite3_column_text(stmt, idx); \
|
||||
global_title_set_str(gt, _txt); \
|
||||
} while (0)
|
||||
|
||||
86
src/db_auc.c
86
src/db_auc.c
@@ -49,7 +49,7 @@ int db_update_sqn(struct db_context *dbc, int64_t subscr_id, uint64_t new_sqn)
|
||||
/* execute the statement */
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot update SQN for subscriber ID=%"PRId64
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot update SQN for subscriber ID=%" PRId64
|
||||
": SQL error: (%d) %s\n",
|
||||
subscr_id, rc, sqlite3_errmsg(dbc->db));
|
||||
ret = -EIO;
|
||||
@@ -59,11 +59,11 @@ int db_update_sqn(struct db_context *dbc, int64_t subscr_id, uint64_t new_sqn)
|
||||
/* verify execution result */
|
||||
rc = sqlite3_changes(dbc->db);
|
||||
if (!rc) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot update SQN for subscriber ID=%"PRId64
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot update SQN for subscriber ID=%" PRId64
|
||||
": no auc_3g entry for such subscriber\n", subscr_id);
|
||||
ret = -ENOENT;
|
||||
} else if (rc != 1) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Update SQN for subscriber ID=%"PRId64
|
||||
LOGP(DAUC, LOGL_ERROR, "Update SQN for subscriber ID=%" PRId64
|
||||
": SQL modified %d rows (expected 1)\n", subscr_id, rc);
|
||||
ret = -EIO;
|
||||
}
|
||||
@@ -73,8 +73,36 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* hexparse a specific column of a sqlite prepared statement into dst (with length check)
|
||||
* returns 0 for success, -EIO on error */
|
||||
static int hexparse_stmt(uint8_t *dst, size_t dst_len, sqlite3_stmt *stmt, int col, const char *col_name,
|
||||
const char *imsi)
|
||||
{
|
||||
const uint8_t *text;
|
||||
size_t col_len;
|
||||
|
||||
/* Bytes are stored as hex strings in database, hence divide length by two */
|
||||
col_len = sqlite3_column_bytes(stmt, col) / 2;
|
||||
|
||||
if (col_len != dst_len) {
|
||||
LOGAUC(imsi, LOGL_ERROR, "Error reading %s, expected length %lu but has length %lu\n", col_name,
|
||||
dst_len, col_len);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
text = sqlite3_column_text(stmt, col);
|
||||
if (!text) {
|
||||
LOGAUC(imsi, LOGL_ERROR, "Error reading %s\n", col_name);
|
||||
return -EIO;
|
||||
}
|
||||
osmo_hexparse((void *)text, dst, dst_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* obtain the authentication data for a given imsi
|
||||
* returns -1 in case of error, 0 for unknown IMSI, 1 for success */
|
||||
* returns 0 for success, negative value on error:
|
||||
* -ENOENT if the IMSI is not known, -ENOKEY if the IMSI is known but has no auth data,
|
||||
* -EIO on db failure */
|
||||
int db_get_auth_data(struct db_context *dbc, const char *imsi,
|
||||
struct osmo_sub_auth_data *aud2g,
|
||||
struct osmo_sub_auth_data *aud3g,
|
||||
@@ -111,49 +139,34 @@ int db_get_auth_data(struct db_context *dbc, const char *imsi,
|
||||
/* obtain result values using sqlite3_column_*() */
|
||||
if (sqlite3_column_type(stmt, 1) == SQLITE_INTEGER) {
|
||||
/* we do have some 2G authentication data */
|
||||
const uint8_t *ki;
|
||||
|
||||
aud2g->algo = sqlite3_column_int(stmt, 1);
|
||||
ki = sqlite3_column_text(stmt, 2);
|
||||
#if 0
|
||||
if (sqlite3_column_bytes(stmt, 2) != sizeof(aud2g->u.gsm.ki)) {
|
||||
LOGAUC(imsi, LOGL_ERROR, "Error reading Ki: %d\n", rc);
|
||||
if (hexparse_stmt(aud2g->u.gsm.ki, sizeof(aud2g->u.gsm.ki), stmt, 2, "Ki", imsi))
|
||||
goto end_2g;
|
||||
}
|
||||
#endif
|
||||
osmo_hexparse((void*)ki, (void*)&aud2g->u.gsm.ki, sizeof(aud2g->u.gsm.ki));
|
||||
aud2g->algo = sqlite3_column_int(stmt, 1);
|
||||
aud2g->type = OSMO_AUTH_TYPE_GSM;
|
||||
} else
|
||||
LOGAUC(imsi, LOGL_DEBUG, "No 2G Auth Data\n");
|
||||
//end_2g:
|
||||
end_2g:
|
||||
if (sqlite3_column_type(stmt, 3) == SQLITE_INTEGER) {
|
||||
/* we do have some 3G authentication data */
|
||||
const uint8_t *k, *op, *opc;
|
||||
|
||||
aud3g->algo = sqlite3_column_int(stmt, 3);
|
||||
k = sqlite3_column_text(stmt, 4);
|
||||
if (!k) {
|
||||
LOGAUC(imsi, LOGL_ERROR, "Error reading K: %d\n", rc);
|
||||
if (hexparse_stmt(aud3g->u.umts.k, sizeof(aud3g->u.umts.k), stmt, 4, "K", imsi)) {
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
osmo_hexparse((void*)k, (void*)&aud3g->u.umts.k, sizeof(aud3g->u.umts.k));
|
||||
aud3g->algo = sqlite3_column_int(stmt, 3);
|
||||
|
||||
/* UMTS Subscribers can have either OP or OPC */
|
||||
op = sqlite3_column_text(stmt, 5);
|
||||
if (!op) {
|
||||
opc = sqlite3_column_text(stmt, 6);
|
||||
if (!opc) {
|
||||
LOGAUC(imsi, LOGL_ERROR, "Error reading OPC: %d\n", rc);
|
||||
if (sqlite3_column_text(stmt, 5)) {
|
||||
if (hexparse_stmt(aud3g->u.umts.opc, sizeof(aud3g->u.umts.opc), stmt, 5, "OP", imsi)) {
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
osmo_hexparse((void*)opc, (void*)&aud3g->u.umts.opc,
|
||||
sizeof(aud3g->u.umts.opc));
|
||||
aud3g->u.umts.opc_is_op = 0;
|
||||
} else {
|
||||
osmo_hexparse((void*)op, (void*)&aud3g->u.umts.opc,
|
||||
sizeof(aud3g->u.umts.opc));
|
||||
aud3g->u.umts.opc_is_op = 1;
|
||||
} else {
|
||||
if (hexparse_stmt(aud3g->u.umts.opc, sizeof(aud3g->u.umts.opc), stmt, 6, "OPC", imsi)) {
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
aud3g->u.umts.opc_is_op = 0;
|
||||
}
|
||||
aud3g->u.umts.sqn = sqlite3_column_int64(stmt, 7);
|
||||
aud3g->u.umts.ind_bitlen = sqlite3_column_int(stmt, 8);
|
||||
@@ -163,15 +176,16 @@ int db_get_auth_data(struct db_context *dbc, const char *imsi,
|
||||
LOGAUC(imsi, LOGL_DEBUG, "No 3G Auth Data\n");
|
||||
|
||||
if (aud2g->type == 0 && aud3g->type == 0)
|
||||
ret = -ENOENT;
|
||||
ret = -ENOKEY;
|
||||
|
||||
out:
|
||||
db_remove_reset(stmt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* return -1 in case of error, 0 for unknown imsi, positive for number
|
||||
* of vectors generated */
|
||||
/* return number of vectors generated, negative value on error:
|
||||
* -ENOENT if the IMSI is not known, -ENOKEY if the IMSI is known but has no auth data,
|
||||
* -EIO on db failure */
|
||||
int db_get_auc(struct db_context *dbc, const char *imsi,
|
||||
unsigned int auc_3g_ind, struct osmo_auth_vector *vec,
|
||||
unsigned int num_vec, const uint8_t *rand_auts,
|
||||
|
||||
86
src/db_debug.c
Normal file
86
src/db_debug.c
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* libtalloc based memory allocator for SQLite3.
|
||||
*
|
||||
* (C) 2019 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* 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 <sqlite3.h>
|
||||
#include <talloc.h>
|
||||
#include <errno.h>
|
||||
|
||||
/* Dedicated talloc context for SQLite */
|
||||
static void *db_sqlite_ctx = NULL;
|
||||
|
||||
static void *tall_xMalloc(int size)
|
||||
{
|
||||
return talloc_size(db_sqlite_ctx, size);
|
||||
}
|
||||
|
||||
static void tall_xFree(void *ptr)
|
||||
{
|
||||
talloc_free(ptr);
|
||||
}
|
||||
|
||||
static void *tall_xRealloc(void *ptr, int size)
|
||||
{
|
||||
return talloc_realloc_fn(db_sqlite_ctx, ptr, size);
|
||||
}
|
||||
|
||||
static int tall_xSize(void *ptr)
|
||||
{
|
||||
return talloc_total_size(ptr);
|
||||
}
|
||||
|
||||
/* DUMMY: talloc doesn't round up the allocation size */
|
||||
static int tall_xRoundup(int size) { return size; }
|
||||
|
||||
/* DUMMY: nothing to initialize */
|
||||
static int tall_xInit(void *data) { return 0; }
|
||||
|
||||
/* DUMMY: nothing to deinitialize */
|
||||
static void tall_xShutdown(void *data) { }
|
||||
|
||||
/* Interface between SQLite and talloc memory allocator */
|
||||
static const struct sqlite3_mem_methods tall_sqlite_if = {
|
||||
/* Memory allocation function */
|
||||
.xMalloc = &tall_xMalloc,
|
||||
/* Free a prior allocation */
|
||||
.xFree = &tall_xFree,
|
||||
/* Resize an allocation */
|
||||
.xRealloc = &tall_xRealloc,
|
||||
/* Return the size of an allocation */
|
||||
.xSize = &tall_xSize,
|
||||
/* Round up request size to allocation size */
|
||||
.xRoundup = &tall_xRoundup,
|
||||
/* Initialize the memory allocator */
|
||||
.xInit = &tall_xInit,
|
||||
/* Deinitialize the memory allocator */
|
||||
.xShutdown = &tall_xShutdown,
|
||||
/* Argument to xInit() and xShutdown() */
|
||||
.pAppData = NULL,
|
||||
};
|
||||
|
||||
int db_sqlite3_use_talloc(void *ctx)
|
||||
{
|
||||
if (db_sqlite_ctx != NULL)
|
||||
return -EEXIST;
|
||||
|
||||
db_sqlite_ctx = talloc_named_const(ctx, 0, "SQLite3");
|
||||
return sqlite3_config(SQLITE_CONFIG_MALLOC, &tall_sqlite_if);
|
||||
}
|
||||
307
src/db_hlr.c
307
src/db_hlr.c
@@ -17,9 +17,15 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#define _POSIX_C_SOURCE 200809L /* for strptime(3) */
|
||||
/* These are needed as well due to the above _POSIX_C_SOURCE definition: */
|
||||
#define _DEFAULT_SOURCE /* for struct timezone */
|
||||
#define _XOPEN_SOURCE /* for clockid_t */
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/crypt/auth.h>
|
||||
@@ -31,16 +37,16 @@
|
||||
#include "hlr.h"
|
||||
#include "db.h"
|
||||
#include "gsup_server.h"
|
||||
#include "luop.h"
|
||||
|
||||
#define LOGHLR(imsi, level, fmt, args ...) LOGP(DAUC, level, "IMSI='%s': " fmt, imsi, ## args)
|
||||
|
||||
/*! Add new subscriber record to the HLR database.
|
||||
* \param[in,out] dbc database context.
|
||||
* \param[in] imsi ASCII string of IMSI digits, is validated.
|
||||
* \param[in] flags Bitmask of DB_SUBSCR_FLAG_*.
|
||||
* \returns 0 on success, -EINVAL on invalid IMSI, -EIO on database error.
|
||||
*/
|
||||
int db_subscr_create(struct db_context *dbc, const char *imsi)
|
||||
int db_subscr_create(struct db_context *dbc, const char *imsi, uint8_t flags)
|
||||
{
|
||||
sqlite3_stmt *stmt;
|
||||
int rc;
|
||||
@@ -55,6 +61,10 @@ int db_subscr_create(struct db_context *dbc, const char *imsi)
|
||||
|
||||
if (!db_bind_text(stmt, "$imsi", imsi))
|
||||
return -EIO;
|
||||
if (!db_bind_int(stmt, "$nam_cs", (flags & DB_SUBSCR_FLAG_NAM_CS) != 0))
|
||||
return -EIO;
|
||||
if (!db_bind_int(stmt, "$nam_ps", (flags & DB_SUBSCR_FLAG_NAM_PS) != 0))
|
||||
return -EIO;
|
||||
|
||||
/* execute the statement */
|
||||
rc = sqlite3_step(stmt);
|
||||
@@ -92,7 +102,7 @@ int db_subscr_delete_by_id(struct db_context *dbc, int64_t subscr_id)
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
LOGP(DAUC, LOGL_ERROR,
|
||||
"Cannot delete subscriber ID=%"PRId64": SQL error: (%d) %s\n",
|
||||
"Cannot delete subscriber ID=%" PRId64 ": SQL error: (%d) %s\n",
|
||||
subscr_id, rc, sqlite3_errmsg(dbc->db));
|
||||
db_remove_reset(stmt);
|
||||
return -EIO;
|
||||
@@ -101,11 +111,11 @@ int db_subscr_delete_by_id(struct db_context *dbc, int64_t subscr_id)
|
||||
/* verify execution result */
|
||||
rc = sqlite3_changes(dbc->db);
|
||||
if (!rc) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot delete: no such subscriber: ID=%"PRId64"\n",
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot delete: no such subscriber: ID=%" PRId64 "\n",
|
||||
subscr_id);
|
||||
ret = -ENOENT;
|
||||
} else if (rc != 1) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%"PRId64
|
||||
LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%" PRId64
|
||||
": SQL modified %d rows (expected 1)\n", subscr_id, rc);
|
||||
ret = -EIO;
|
||||
}
|
||||
@@ -135,8 +145,8 @@ int db_subscr_delete_by_id(struct db_context *dbc, int64_t subscr_id)
|
||||
|
||||
/*! Set a subscriber's MSISDN in the HLR database.
|
||||
* \param[in,out] dbc database context.
|
||||
* \param[in] imsi ASCII string of IMSI digits.
|
||||
* \param[in] msisdn ASCII string of MSISDN digits.
|
||||
* \param[in] imsi ASCII string of IMSI digits
|
||||
* \param[in] msisdn ASCII string of MSISDN digits, or NULL to remove the MSISDN.
|
||||
* \returns 0 on success, -EINVAL in case of invalid MSISDN string, -EIO on
|
||||
* database failure, -ENOENT if no such subscriber exists.
|
||||
*/
|
||||
@@ -146,19 +156,22 @@ int db_subscr_update_msisdn_by_imsi(struct db_context *dbc, const char *imsi,
|
||||
int rc;
|
||||
int ret = 0;
|
||||
|
||||
if (!osmo_msisdn_str_valid(msisdn)) {
|
||||
if (msisdn && !osmo_msisdn_str_valid(msisdn)) {
|
||||
LOGHLR(imsi, LOGL_ERROR,
|
||||
"Cannot update subscriber: invalid MSISDN: '%s'\n",
|
||||
msisdn);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SET_MSISDN_BY_IMSI];
|
||||
sqlite3_stmt *stmt = dbc->stmt[
|
||||
msisdn ? DB_STMT_SET_MSISDN_BY_IMSI : DB_STMT_DELETE_MSISDN_BY_IMSI];
|
||||
|
||||
if (!db_bind_text(stmt, "$imsi", imsi))
|
||||
return -EIO;
|
||||
if (!db_bind_text(stmt, "$msisdn", msisdn))
|
||||
return -EIO;
|
||||
if (msisdn) {
|
||||
if (!db_bind_text(stmt, "$msisdn", msisdn))
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* execute the statement */
|
||||
rc = sqlite3_step(stmt);
|
||||
@@ -312,7 +325,7 @@ int db_subscr_update_aud_by_id(struct db_context *dbc, int64_t subscr_id,
|
||||
* empty, and no entry is not an error then.*/
|
||||
ret = -ENOENT;
|
||||
else if (rc != 1) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%"PRId64
|
||||
LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%" PRId64
|
||||
" from %s: SQL modified %d rows (expected 1)\n",
|
||||
subscr_id, label, rc);
|
||||
ret = -EIO;
|
||||
@@ -377,12 +390,61 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*! Set a subscriber's IMEI in the HLR database.
|
||||
* \param[in,out] dbc database context.
|
||||
* \param[in] imsi ASCII string of IMSI digits
|
||||
* \param[in] imei ASCII string of identifier digits, or NULL to remove the IMEI.
|
||||
* \returns 0 on success, -ENOENT when the given subscriber does not exist,
|
||||
* -EIO on database errors.
|
||||
*/
|
||||
int db_subscr_update_imei_by_imsi(struct db_context *dbc, const char* imsi, const char *imei)
|
||||
{
|
||||
int rc, ret = 0;
|
||||
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_UPD_IMEI_BY_IMSI];
|
||||
|
||||
if (imei && !osmo_imei_str_valid(imei, false)) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot update subscriber IMSI='%s': invalid IMEI: '%s'\n", imsi, imei);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!db_bind_text(stmt, "$imsi", imsi))
|
||||
return -EIO;
|
||||
if (imei && !db_bind_text(stmt, "$imei", imei))
|
||||
return -EIO;
|
||||
|
||||
/* execute the statement */
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Update IMEI for subscriber IMSI='%s': SQL Error: %s\n", imsi,
|
||||
sqlite3_errmsg(dbc->db));
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* verify execution result */
|
||||
rc = sqlite3_changes(dbc->db);
|
||||
if (!rc) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot update IMEI for subscriber IMSI='%s': no such subscriber\n", imsi);
|
||||
ret = -ENOENT;
|
||||
} else if (rc != 1) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Update IMEI for subscriber IMSI='%s': SQL modified %d rows (expected 1)\n",
|
||||
imsi, rc);
|
||||
ret = -EIO;
|
||||
}
|
||||
|
||||
out:
|
||||
db_remove_reset(stmt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Common code for db_subscr_get_by_*() functions. */
|
||||
static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscriber *subscr,
|
||||
const char **err)
|
||||
{
|
||||
int rc;
|
||||
int ret = 0;
|
||||
const char *last_lu_seen_str;
|
||||
struct tm tm = {0};
|
||||
|
||||
/* execute the statement */
|
||||
rc = sqlite3_step(stmt);
|
||||
@@ -404,17 +466,33 @@ static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscri
|
||||
subscr->id = sqlite3_column_int64(stmt, 0);
|
||||
copy_sqlite3_text_to_buf(subscr->imsi, stmt, 1);
|
||||
copy_sqlite3_text_to_buf(subscr->msisdn, stmt, 2);
|
||||
copy_sqlite3_text_to_buf(subscr->imei, stmt, 3);
|
||||
/* FIXME: These should all be BLOBs as they might contain NUL */
|
||||
copy_sqlite3_text_to_buf(subscr->vlr_number, stmt, 3);
|
||||
copy_sqlite3_text_to_buf(subscr->sgsn_number, stmt, 4);
|
||||
copy_sqlite3_text_to_buf(subscr->sgsn_address, stmt, 5);
|
||||
subscr->periodic_lu_timer = sqlite3_column_int(stmt, 6);
|
||||
subscr->periodic_rau_tau_timer = sqlite3_column_int(stmt, 7);
|
||||
subscr->nam_cs = sqlite3_column_int(stmt, 8);
|
||||
subscr->nam_ps = sqlite3_column_int(stmt, 9);
|
||||
subscr->lmsi = sqlite3_column_int(stmt, 10);
|
||||
subscr->ms_purged_cs = sqlite3_column_int(stmt, 11);
|
||||
subscr->ms_purged_ps = sqlite3_column_int(stmt, 12);
|
||||
copy_sqlite3_text_to_buf(subscr->vlr_number, stmt, 4);
|
||||
copy_sqlite3_text_to_buf(subscr->sgsn_number, stmt, 5);
|
||||
copy_sqlite3_text_to_buf(subscr->sgsn_address, stmt, 6);
|
||||
subscr->periodic_lu_timer = sqlite3_column_int(stmt, 7);
|
||||
subscr->periodic_rau_tau_timer = sqlite3_column_int(stmt, 8);
|
||||
subscr->nam_cs = sqlite3_column_int(stmt, 9);
|
||||
subscr->nam_ps = sqlite3_column_int(stmt, 10);
|
||||
subscr->lmsi = sqlite3_column_int(stmt, 11);
|
||||
subscr->ms_purged_cs = sqlite3_column_int(stmt, 12);
|
||||
subscr->ms_purged_ps = sqlite3_column_int(stmt, 13);
|
||||
last_lu_seen_str = (const char *)sqlite3_column_text(stmt, 14);
|
||||
if (last_lu_seen_str && last_lu_seen_str[0] != '\0') {
|
||||
if (strptime(last_lu_seen_str, DB_LAST_LU_SEEN_FMT, &tm) == NULL) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot parse last LU timestamp '%s' of subscriber with IMSI='%s': %s\n",
|
||||
last_lu_seen_str, subscr->imsi, strerror(errno));
|
||||
} else {
|
||||
subscr->last_lu_seen = mktime(&tm);
|
||||
if (subscr->last_lu_seen == -1) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot convert LU timestamp '%s' to time_t: %s\n",
|
||||
last_lu_seen_str, strerror(errno));
|
||||
subscr->last_lu_seen = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
copy_sqlite3_text_to_gt(&subscr->vlr_via_proxy, stmt, 15);
|
||||
|
||||
out:
|
||||
db_remove_reset(stmt);
|
||||
@@ -433,6 +511,31 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*! Check if a subscriber exists in the HLR database.
|
||||
* \param[in, out] dbc database context.
|
||||
* \param[in] imsi ASCII string of IMSI digits.
|
||||
* \returns 0 if it exists, -ENOENT if it does not exist, -EIO on database error.
|
||||
*/
|
||||
int db_subscr_exists_by_imsi(struct db_context *dbc, const char *imsi) {
|
||||
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_EXISTS_BY_IMSI];
|
||||
const char *err;
|
||||
int rc;
|
||||
|
||||
if (!db_bind_text(stmt, NULL, imsi))
|
||||
return -EIO;
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
db_remove_reset(stmt);
|
||||
if (rc == SQLITE_ROW)
|
||||
return 0; /* exists */
|
||||
if (rc == SQLITE_DONE)
|
||||
return -ENOENT; /* does not exist */
|
||||
|
||||
err = sqlite3_errmsg(dbc->db);
|
||||
LOGP(DAUC, LOGL_ERROR, "Failed to check if subscriber exists by IMSI='%s': %s\n", imsi, err);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*! Retrieve subscriber data from the HLR database.
|
||||
* \param[in,out] dbc database context.
|
||||
* \param[in] imsi ASCII string of IMSI digits.
|
||||
@@ -457,6 +560,33 @@ int db_subscr_get_by_imsi(struct db_context *dbc, const char *imsi,
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*! Check if a subscriber exists in the HLR database.
|
||||
* \param[in, out] dbc database context.
|
||||
* \param[in] msisdn ASCII string of MSISDN digits.
|
||||
* \returns 0 if it exists, -ENOENT if it does not exist, -EIO on database error.
|
||||
*/
|
||||
int db_subscr_exists_by_msisdn(struct db_context *dbc, const char *msisdn)
|
||||
{
|
||||
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_EXISTS_BY_MSISDN];
|
||||
const char *err;
|
||||
int rc;
|
||||
|
||||
if (!db_bind_text(stmt, NULL, msisdn))
|
||||
return -EIO;
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
db_remove_reset(stmt);
|
||||
if (rc == SQLITE_ROW)
|
||||
return 0; /* exists */
|
||||
if (rc == SQLITE_DONE)
|
||||
return -ENOENT; /* does not exist */
|
||||
|
||||
err = sqlite3_errmsg(dbc->db);
|
||||
LOGP(DAUC, LOGL_ERROR, "Failed to check if subscriber exists "
|
||||
"by MSISDN='%s': %s\n", msisdn, err);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*! Retrieve subscriber data from the HLR database.
|
||||
* \param[in,out] dbc database context.
|
||||
* \param[in] msisdn ASCII string of MSISDN digits.
|
||||
@@ -500,11 +630,33 @@ int db_subscr_get_by_id(struct db_context *dbc, int64_t id,
|
||||
|
||||
rc = db_sel(dbc, stmt, subscr, &err);
|
||||
if (rc)
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: ID=%"PRId64": %s\n",
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: ID=%" PRId64 ": %s\n",
|
||||
id, err);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*! Retrieve subscriber data from the HLR database.
|
||||
* \param[in,out] dbc database context.
|
||||
* \param[in] imei ASCII string of identifier digits
|
||||
* \param[out] subscr place retrieved data in this struct.
|
||||
* \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
|
||||
* database error.
|
||||
*/
|
||||
int db_subscr_get_by_imei(struct db_context *dbc, const char *imei, struct hlr_subscriber *subscr)
|
||||
{
|
||||
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_IMEI];
|
||||
const char *err;
|
||||
int rc;
|
||||
|
||||
if (!db_bind_text(stmt, NULL, imei))
|
||||
return -EIO;
|
||||
|
||||
rc = db_sel(dbc, stmt, subscr, &err);
|
||||
if (rc)
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: IMEI=%s: %s\n", imei, err);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*! You should use hlr_subscr_nam() instead; enable or disable PS or CS for a
|
||||
* subscriber without notifying GSUP clients.
|
||||
* \param[in,out] dbc database context.
|
||||
@@ -570,10 +722,12 @@ out:
|
||||
* -EIO on database errors.
|
||||
*/
|
||||
int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
|
||||
const char *vlr_or_sgsn_number, bool is_ps)
|
||||
const struct global_title *vlr_name, bool is_ps,
|
||||
const struct global_title *via_proxy)
|
||||
{
|
||||
sqlite3_stmt *stmt;
|
||||
int rc, ret = 0;
|
||||
struct timespec localtime;
|
||||
|
||||
stmt = dbc->stmt[is_ps ? DB_STMT_UPD_SGSN_BY_ID
|
||||
: DB_STMT_UPD_VLR_BY_ID];
|
||||
@@ -581,13 +735,21 @@ int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
|
||||
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
|
||||
return -EIO;
|
||||
|
||||
if (!db_bind_text(stmt, "$number", vlr_or_sgsn_number))
|
||||
if (!db_bind_text(stmt, "$number", (char*)vlr_name->val))
|
||||
return -EIO;
|
||||
|
||||
if (via_proxy && via_proxy->len) {
|
||||
if (!db_bind_text(stmt, "$proxy", (char*)via_proxy->val))
|
||||
return -EIO;
|
||||
} else {
|
||||
if (!db_bind_null(stmt, "$proxy"))
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* execute the statement */
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%"PRId64": SQL Error: %s\n",
|
||||
LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%" PRId64 ": SQL Error: %s\n",
|
||||
is_ps? "SGSN" : "VLR", subscr_id, sqlite3_errmsg(dbc->db));
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
@@ -596,17 +758,58 @@ int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
|
||||
/* verify execution result */
|
||||
rc = sqlite3_changes(dbc->db);
|
||||
if (!rc) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot update %s number for subscriber ID=%"PRId64
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot update %s number for subscriber ID=%" PRId64
|
||||
": no such subscriber\n",
|
||||
is_ps? "SGSN" : "VLR", subscr_id);
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
} else if (rc != 1) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%"PRId64
|
||||
LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%" PRId64
|
||||
": SQL modified %d rows (expected 1)\n",
|
||||
is_ps? "SGSN" : "VLR", subscr_id, rc);
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
db_remove_reset(stmt);
|
||||
|
||||
if (osmo_clock_gettime(CLOCK_REALTIME, &localtime) != 0) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot get the current time: (%d) %s\n", errno, strerror(errno));
|
||||
ret = -errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
stmt = dbc->stmt[DB_STMT_SET_LAST_LU_SEEN];
|
||||
|
||||
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
|
||||
return -EIO;
|
||||
/* The timestamp will be converted to UTC by SQLite. */
|
||||
if (!db_bind_int64(stmt, "$val", (int64_t)localtime.tv_sec)) {
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
LOGP(DAUC, LOGL_ERROR,
|
||||
"Cannot update LU timestamp for subscriber ID=%" PRId64 ": SQL error: (%d) %s\n",
|
||||
subscr_id, rc, sqlite3_errmsg(dbc->db));
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* verify execution result */
|
||||
rc = sqlite3_changes(dbc->db);
|
||||
if (!rc) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Cannot update LU timestamp for subscriber ID=%" PRId64
|
||||
": no such subscriber\n", subscr_id);
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
} else if (rc != 1) {
|
||||
LOGP(DAUC, LOGL_ERROR, "Update LU timestamp for subscriber ID=%" PRId64
|
||||
": SQL modified %d rows (expected 1)\n", subscr_id, rc);
|
||||
ret = -EIO;
|
||||
}
|
||||
out:
|
||||
db_remove_reset(stmt);
|
||||
return ret;
|
||||
@@ -667,51 +870,3 @@ out:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients.
|
||||
* \param[in,out] hlr Global hlr context.
|
||||
* \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call.
|
||||
* \param[in] nam_val True to enable CS/PS, false to disable.
|
||||
* \param[in] is_ps True to enable/disable PS, false for CS.
|
||||
* \returns 0 on success, ENOEXEC if there is no need to change, a negative
|
||||
* value on error.
|
||||
*/
|
||||
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps)
|
||||
{
|
||||
int rc;
|
||||
struct lu_operation *luop;
|
||||
struct osmo_gsup_conn *co;
|
||||
bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs;
|
||||
|
||||
if (is_val == nam_val) {
|
||||
LOGHLR(subscr->imsi, LOGL_DEBUG, "Already has the requested value when asked to %s %s\n",
|
||||
nam_val ? "enable" : "disable", is_ps ? "PS" : "CS");
|
||||
return ENOEXEC;
|
||||
}
|
||||
|
||||
rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps);
|
||||
if (rc)
|
||||
return rc > 0? -rc : rc;
|
||||
|
||||
/* If we're disabling, send a notice out to the GSUP client that is
|
||||
* responsible. Otherwise no need. */
|
||||
if (nam_val)
|
||||
return 0;
|
||||
|
||||
/* FIXME: only send to single SGSN where latest update for IMSI came from */
|
||||
llist_for_each_entry(co, &hlr->gs->clients, list) {
|
||||
luop = lu_op_alloc_conn(co);
|
||||
if (!luop) {
|
||||
LOGHLR(subscr->imsi, LOGL_ERROR,
|
||||
"Cannot notify GSUP client, cannot allocate lu_operation,"
|
||||
" for %s:%u\n",
|
||||
co && co->conn && co->conn->server? co->conn->server->addr : "unset",
|
||||
co && co->conn && co->conn->server? co->conn->server->port : 0);
|
||||
continue;
|
||||
}
|
||||
luop->subscr = *subscr;
|
||||
lu_op_tx_del_subscr_data(luop);
|
||||
lu_op_free(luop);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Input to this is sql/hlr.sql.
|
||||
# Input to this are sql/*.sql files.
|
||||
#
|
||||
# We want each SQL statement line wrapped in "...\n", and each end (";") to
|
||||
# become a comma:
|
||||
@@ -1,87 +0,0 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/application.h>
|
||||
|
||||
#include "db.h"
|
||||
#include "hlr.h"
|
||||
#include "rand.h"
|
||||
#include "logging.h"
|
||||
|
||||
static struct hlr *g_hlr;
|
||||
|
||||
static int test(const char *imsi, struct db_context *dbc)
|
||||
{
|
||||
struct osmo_auth_vector vec[3];
|
||||
int rc, i;
|
||||
|
||||
/* initialize all vectors with a known token pattern */
|
||||
memset(vec, 0x55, sizeof(vec));
|
||||
for (i = 0; i < ARRAY_SIZE(vec); i++)
|
||||
vec[i].res_len = 0;
|
||||
|
||||
rc = db_get_auc(dbc, imsi, 0, vec, ARRAY_SIZE(vec), NULL, NULL);
|
||||
if (rc <= 0) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "Cannot obtain auth tuples for '%s'\n", imsi);
|
||||
return rc;
|
||||
}
|
||||
LOGP(DMAIN, LOGL_INFO, "Obtained %u tuples for subscriber IMSI %s\n",
|
||||
rc, imsi);
|
||||
|
||||
for (i = 0; i < rc; i++) {
|
||||
struct osmo_auth_vector *v = vec + i;
|
||||
LOGP(DMAIN, LOGL_DEBUG, "Tuple %u, auth_types=0x%x\n", i, v->auth_types);
|
||||
LOGP(DMAIN, LOGL_DEBUG, "RAND=%s\n", osmo_hexdump_nospc(v->rand, sizeof(v->rand)));
|
||||
LOGP(DMAIN, LOGL_DEBUG, "AUTN=%s\n", osmo_hexdump_nospc(v->autn, sizeof(v->autn)));
|
||||
LOGP(DMAIN, LOGL_DEBUG, "CK=%s\n", osmo_hexdump_nospc(v->ck, sizeof(v->ck)));
|
||||
LOGP(DMAIN, LOGL_DEBUG, "IK=%s\n", osmo_hexdump_nospc(v->ik, sizeof(v->ik)));
|
||||
LOGP(DMAIN, LOGL_DEBUG, "RES=%s\n", osmo_hexdump_nospc(v->res, v->res_len));
|
||||
LOGP(DMAIN, LOGL_DEBUG, "Kc=%s\n", osmo_hexdump_nospc(v->kc, sizeof(v->kc)));
|
||||
LOGP(DMAIN, LOGL_DEBUG, "SRES=%s\n", osmo_hexdump_nospc(v->sres, sizeof(v->sres)));
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int rc;
|
||||
|
||||
g_hlr = talloc_zero(NULL, struct hlr);
|
||||
|
||||
rc = osmo_init_logging(&hlr_log_info);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Error initializing logging\n");
|
||||
exit(1);
|
||||
}
|
||||
LOGP(DMAIN, LOGL_NOTICE, "hlr starting\n");
|
||||
|
||||
rc = rand_init();
|
||||
if (rc < 0) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "Error initializing random source\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
g_hlr->dbc = db_open(NULL, "hlr.db");
|
||||
if (!g_hlr->dbc) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "Error opening database\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* non-existing subscriber */
|
||||
rc = test("901990123456789", g_hlr->dbc);
|
||||
/* 2G only AUC data (COMP128v1 / MILENAGE) */
|
||||
rc = test("901990000000001", g_hlr->dbc);
|
||||
/* 2G + 3G AUC data (COMP128v1 / MILENAGE) */
|
||||
rc = test("901990000000002", g_hlr->dbc);
|
||||
/* 3G AUC data (MILENAGE) */
|
||||
rc = test("901990000000003", g_hlr->dbc);
|
||||
|
||||
LOGP(DMAIN, LOGL_NOTICE, "Exiting\n");
|
||||
|
||||
db_close(g_hlr->dbc);
|
||||
|
||||
log_fini();
|
||||
|
||||
exit(0);
|
||||
}
|
||||
515
src/dgsm.c
Normal file
515
src/dgsm.c
Normal file
@@ -0,0 +1,515 @@
|
||||
#include <errno.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/mslookup/mslookup_client.h>
|
||||
#include <osmocom/mslookup/mslookup_client_mdns.h>
|
||||
#include <osmocom/gsupclient/gsup_client.h>
|
||||
#include "logging.h"
|
||||
#include "hlr.h"
|
||||
#include "db.h"
|
||||
#include "gsup_router.h"
|
||||
#include "gsup_server.h"
|
||||
#include "dgsm.h"
|
||||
#include "proxy.h"
|
||||
#include "remote_hlr.h"
|
||||
#include "mslookup_server_mdns.h"
|
||||
#include "global_title.h"
|
||||
|
||||
void *dgsm_ctx = NULL;
|
||||
|
||||
const struct global_title dgsm_config_msc_wildcard = {};
|
||||
|
||||
struct dgsm_msc_config *dgsm_config_msc_get(const struct global_title *msc_name, bool create)
|
||||
{
|
||||
struct dgsm_msc_config *msc;
|
||||
|
||||
if (!msc_name)
|
||||
return NULL;
|
||||
|
||||
llist_for_each_entry(msc, &g_hlr->mslookup.vty.server.msc_configs, entry) {
|
||||
if (global_title_cmp(&msc->name, msc_name))
|
||||
continue;
|
||||
return msc;
|
||||
}
|
||||
if (!create)
|
||||
return NULL;
|
||||
|
||||
msc = talloc_zero(dgsm_ctx, struct dgsm_msc_config);
|
||||
OSMO_ASSERT(msc);
|
||||
INIT_LLIST_HEAD(&msc->service_hosts);
|
||||
msc->name = *msc_name;
|
||||
return msc;
|
||||
}
|
||||
|
||||
struct dgsm_service_host *dgsm_config_msc_service_get(struct dgsm_msc_config *msc, const char *service, bool create)
|
||||
{
|
||||
struct dgsm_service_host *e;
|
||||
if (!msc)
|
||||
return NULL;
|
||||
|
||||
llist_for_each_entry(e, &msc->service_hosts, entry) {
|
||||
if (!strcmp(e->service, service))
|
||||
return e;
|
||||
}
|
||||
|
||||
if (!create)
|
||||
return NULL;
|
||||
|
||||
e = talloc_zero(msc, struct dgsm_service_host);
|
||||
OSMO_ASSERT(e);
|
||||
OSMO_STRLCPY_ARRAY(e->service, service);
|
||||
llist_add_tail(&e->entry, &msc->service_hosts);
|
||||
return e;
|
||||
}
|
||||
|
||||
struct dgsm_service_host *dgsm_config_service_get(const struct global_title *msc_name, const char *service)
|
||||
{
|
||||
struct dgsm_msc_config *msc = dgsm_config_msc_get(msc_name, false);
|
||||
if (!msc)
|
||||
return NULL;
|
||||
return dgsm_config_msc_service_get(msc, service, false);
|
||||
}
|
||||
|
||||
int dgsm_config_msc_service_set(struct dgsm_msc_config *msc, const char *service, const struct osmo_sockaddr_str *addr)
|
||||
{
|
||||
struct dgsm_service_host *e;
|
||||
|
||||
if (!service || !service[0]
|
||||
|| strlen(service) > OSMO_MSLOOKUP_SERVICE_MAXLEN)
|
||||
return -EINVAL;
|
||||
if (!addr || !osmo_sockaddr_str_is_nonzero(addr))
|
||||
return -EINVAL;
|
||||
|
||||
e = dgsm_config_msc_service_get(msc, service, true);
|
||||
if (!e)
|
||||
return -EINVAL;
|
||||
|
||||
switch (addr->af) {
|
||||
case AF_INET:
|
||||
e->host_v4 = *addr;
|
||||
break;
|
||||
case AF_INET6:
|
||||
e->host_v6 = *addr;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dgsm_config_service_set(const struct global_title *msc_name, const char *service, const struct osmo_sockaddr_str *addr)
|
||||
{
|
||||
struct dgsm_msc_config *msc;
|
||||
|
||||
msc = dgsm_config_msc_get(msc_name, true);
|
||||
if (!msc)
|
||||
return -EINVAL;
|
||||
|
||||
return dgsm_config_msc_service_set(msc, service, addr);
|
||||
}
|
||||
|
||||
int dgsm_config_msc_service_del(struct dgsm_msc_config *msc, const char *service, const struct osmo_sockaddr_str *addr)
|
||||
{
|
||||
struct dgsm_service_host *e, *n;
|
||||
|
||||
if (!msc)
|
||||
return -ENOENT;
|
||||
|
||||
llist_for_each_entry_safe(e, n, &msc->service_hosts, entry) {
|
||||
if (service && strcmp(service, e->service))
|
||||
continue;
|
||||
|
||||
if (addr) {
|
||||
if (!osmo_sockaddr_str_cmp(addr, &e->host_v4)) {
|
||||
e->host_v4 = (struct osmo_sockaddr_str){};
|
||||
/* Removed one addr. If the other is still there, keep the entry. */
|
||||
if (osmo_sockaddr_str_is_nonzero(&e->host_v6))
|
||||
continue;
|
||||
} else if (!osmo_sockaddr_str_cmp(addr, &e->host_v6)) {
|
||||
e->host_v6 = (struct osmo_sockaddr_str){};
|
||||
/* Removed one addr. If the other is still there, keep the entry. */
|
||||
if (osmo_sockaddr_str_is_nonzero(&e->host_v4))
|
||||
continue;
|
||||
} else
|
||||
/* No addr match, keep the entry. */
|
||||
continue;
|
||||
/* Addr matched and none is left. Delete. */
|
||||
}
|
||||
llist_del(&e->entry);
|
||||
talloc_free(e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dgsm_config_service_del(const struct global_title *msc_name,
|
||||
const char *service, const struct osmo_sockaddr_str *addr)
|
||||
{
|
||||
return dgsm_config_msc_service_del(dgsm_config_msc_get(msc_name, false),
|
||||
service, addr);
|
||||
}
|
||||
|
||||
static void *dgsm_pending_messages_ctx = NULL;
|
||||
|
||||
struct pending_gsup_message {
|
||||
struct llist_head entry;
|
||||
struct osmo_gsup_req *req;
|
||||
struct timeval received_at;
|
||||
};
|
||||
static LLIST_HEAD(pending_gsup_messages);
|
||||
|
||||
/* Defer a GSUP message until we know a remote HLR to proxy to.
|
||||
* Where to send this GSUP message is indicated by its IMSI: as soon as an MS lookup has yielded the IMSI's home HLR,
|
||||
* that's where the message should go. */
|
||||
static void defer_gsup_req(struct osmo_gsup_req *req)
|
||||
{
|
||||
struct pending_gsup_message *m;
|
||||
|
||||
m = talloc_zero(dgsm_pending_messages_ctx, struct pending_gsup_message);
|
||||
OSMO_ASSERT(m);
|
||||
m->req = req;
|
||||
timestamp_update(&m->received_at);
|
||||
llist_add_tail(&m->entry, &pending_gsup_messages);
|
||||
}
|
||||
|
||||
/* Unable to resolve remote HLR for this IMSI, Answer with error back to the sender. */
|
||||
static void defer_gsup_message_err(struct pending_gsup_message *m)
|
||||
{
|
||||
osmo_gsup_req_respond_err(m->req, GMM_CAUSE_IMSI_UNKNOWN, "could not reach home HLR");
|
||||
m->req = NULL;
|
||||
}
|
||||
|
||||
/* Forward spooled message for this IMSI to remote HLR. */
|
||||
static void defer_gsup_message_send(struct pending_gsup_message *m, struct remote_hlr *remote_hlr)
|
||||
{
|
||||
LOG_GSUP_REQ(m->req, LOGL_INFO, "Forwarding deferred message to " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
|
||||
|
||||
/* If sending fails, still discard. */
|
||||
if (!remote_hlr->gsupc || !remote_hlr->gsupc->is_connected) {
|
||||
LOGP(DDGSM, LOGL_ERROR, "GSUP link to remote HLR is not connected: " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
|
||||
defer_gsup_message_err(m);
|
||||
return;
|
||||
}
|
||||
|
||||
remote_hlr_gsup_forward(remote_hlr, m->req);
|
||||
m->req = NULL;
|
||||
}
|
||||
|
||||
/* Result of looking for remote HLR. If it failed, pass remote_hlr as NULL. */
|
||||
static void defer_gsup_message_pop(const char *imsi, struct remote_hlr *remote_hlr)
|
||||
{
|
||||
struct pending_gsup_message *m, *n;
|
||||
|
||||
if (remote_hlr)
|
||||
LOG_DGSM(imsi, LOGL_DEBUG, "Sending spooled GSUP messages to remote HLR at " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
|
||||
else
|
||||
LOG_DGSM(imsi, LOGL_ERROR, "No remote HLR found, dropping spooled GSUP messages\n");
|
||||
|
||||
llist_for_each_entry_safe(m, n, &pending_gsup_messages, entry) {
|
||||
if (strcmp(m->req->gsup.imsi, imsi))
|
||||
continue;
|
||||
|
||||
if (!remote_hlr)
|
||||
defer_gsup_message_err(m);
|
||||
else
|
||||
defer_gsup_message_send(m, remote_hlr);
|
||||
|
||||
llist_del(&m->entry);
|
||||
talloc_free(m);
|
||||
}
|
||||
}
|
||||
|
||||
void dgsm_send_to_remote_hlr(const struct proxy_subscr *proxy_subscr, struct osmo_gsup_req *req)
|
||||
{
|
||||
struct remote_hlr *remote_hlr;
|
||||
|
||||
if (!osmo_sockaddr_str_is_nonzero(&proxy_subscr->remote_hlr_addr)) {
|
||||
/* We don't know the remote target yet. Still waiting for an MS lookup response. */
|
||||
LOG_GSUP_REQ(req, LOGL_DEBUG, "Proxy: deferring until remote HLR is known\n");
|
||||
defer_gsup_req(req);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_GSUP_REQ(req, LOGL_INFO, "Proxy: forwarding to " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr->remote_hlr_addr));
|
||||
|
||||
remote_hlr = remote_hlr_get(&proxy_subscr->remote_hlr_addr, true);
|
||||
if (!remote_hlr) {
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL,
|
||||
"Proxy: Failed to establish connection to remote HLR " OSMO_SOCKADDR_STR_FMT,
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr->remote_hlr_addr));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!remote_hlr->gsupc || !remote_hlr->gsupc->is_connected) {
|
||||
/* GSUP link is still busy establishing... */
|
||||
LOG_GSUP_REQ(req, LOGL_DEBUG, "Proxy: deferring until link to remote HLR is up\n");
|
||||
defer_gsup_req(req);
|
||||
return;
|
||||
}
|
||||
|
||||
remote_hlr_gsup_forward(remote_hlr, req);
|
||||
}
|
||||
|
||||
static void resolve_hlr_result_cb(struct osmo_mslookup_client *client,
|
||||
uint32_t request_handle,
|
||||
const struct osmo_mslookup_query *query,
|
||||
const struct osmo_mslookup_result *result)
|
||||
{
|
||||
struct proxy *proxy = g_hlr->proxy;
|
||||
const struct proxy_subscr *proxy_subscr;
|
||||
struct proxy_subscr proxy_subscr_new;
|
||||
struct remote_hlr *remote_hlr;
|
||||
|
||||
/* A remote HLR is answering back, indicating that it is the home HLR for a given IMSI.
|
||||
* There should be a mostly empty proxy entry for that IMSI.
|
||||
* Add the remote address data in the proxy. */
|
||||
if (query->id.type != OSMO_MSLOOKUP_ID_IMSI) {
|
||||
LOGP(DDGSM, LOGL_ERROR, "Expected IMSI ID type in mslookup query+result: %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
|
||||
return;
|
||||
}
|
||||
|
||||
proxy_subscr = proxy_subscr_get_by_imsi(proxy, query->id.imsi);
|
||||
if (!proxy_subscr) {
|
||||
LOG_DGSM(query->id.imsi, LOGL_ERROR, "No proxy entry for mslookup result: %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
|
||||
defer_gsup_message_pop(query->id.imsi, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result->rc != OSMO_MSLOOKUP_RC_OK) {
|
||||
LOG_DGSM(query->id.imsi, LOGL_ERROR, "Failed to resolve remote HLR: %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
|
||||
defer_gsup_message_pop(query->id.imsi, NULL);
|
||||
proxy_subscr_del(proxy, proxy_subscr->imsi);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Store the address. Make a copy to modify. */
|
||||
proxy_subscr_new = *proxy_subscr;
|
||||
proxy_subscr = &proxy_subscr_new;
|
||||
if (osmo_sockaddr_str_is_nonzero(&result->host_v4))
|
||||
proxy_subscr_new.remote_hlr_addr = result->host_v4;
|
||||
else if (osmo_sockaddr_str_is_nonzero(&result->host_v6))
|
||||
proxy_subscr_new.remote_hlr_addr = result->host_v6;
|
||||
else {
|
||||
LOG_DGSM(query->id.imsi, LOGL_ERROR, "Invalid address for remote HLR: %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
|
||||
defer_gsup_message_pop(query->id.imsi, NULL);
|
||||
proxy_subscr_del(proxy, proxy_subscr->imsi);
|
||||
return;
|
||||
}
|
||||
|
||||
if (proxy_subscr_update(proxy, proxy_subscr)) {
|
||||
LOG_DGSM(query->id.imsi, LOGL_ERROR, "Failed to store proxy entry for remote HLR: %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
|
||||
defer_gsup_message_pop(query->id.imsi, NULL);
|
||||
proxy_subscr_del(proxy, proxy_subscr->imsi);
|
||||
return;
|
||||
}
|
||||
LOG_DGSM(proxy_subscr->imsi, LOGL_DEBUG, "Stored remote hlr address for this IMSI: " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr->remote_hlr_addr));
|
||||
|
||||
remote_hlr = remote_hlr_get(&proxy_subscr->remote_hlr_addr, true);
|
||||
if (!remote_hlr) {
|
||||
defer_gsup_message_pop(query->id.imsi, NULL);
|
||||
proxy_subscr_del(proxy, proxy_subscr->imsi);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!remote_hlr->gsupc || !remote_hlr->gsupc->is_connected) {
|
||||
LOG_DGSM(query->id.imsi, LOGL_DEBUG, "Resolved remote HLR, waiting for link-up: %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DGSM(query->id.imsi, LOGL_DEBUG, "Resolved remote HLR, sending spooled GSUP messages: %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
|
||||
defer_gsup_message_pop(query->id.imsi, remote_hlr);
|
||||
}
|
||||
|
||||
static bool remote_hlr_up_yield(struct proxy *proxy, const struct proxy_subscr *proxy_subscr, void *data)
|
||||
{
|
||||
struct remote_hlr *remote_hlr = data;
|
||||
defer_gsup_message_pop(proxy_subscr->imsi, remote_hlr);
|
||||
return true;
|
||||
}
|
||||
|
||||
void dgsm_remote_hlr_up(struct remote_hlr *remote_hlr)
|
||||
{
|
||||
LOGP(DDGSM, LOGL_NOTICE, "link to remote HLR is up, sending spooled GSUP messages: " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
|
||||
/* Send all spooled GSUP messaged for IMSIs that are waiting for this link to establish. */
|
||||
proxy_subscrs_get_by_remote_hlr(g_hlr->proxy, &remote_hlr->addr, remote_hlr_up_yield, remote_hlr);
|
||||
}
|
||||
|
||||
/* Return true when the message has been handled by D-GSM. */
|
||||
bool dgsm_check_forward_gsup_msg(struct osmo_gsup_req *req)
|
||||
{
|
||||
const struct proxy_subscr *proxy_subscr;
|
||||
struct proxy_subscr proxy_subscr_new;
|
||||
struct proxy *proxy = g_hlr->proxy;
|
||||
struct osmo_mslookup_query query;
|
||||
struct osmo_mslookup_query_handling handling;
|
||||
uint32_t request_handle;
|
||||
|
||||
/* If the IMSI is known in the local HLR, then we won't proxy. */
|
||||
if (db_subscr_exists_by_imsi(g_hlr->dbc, req->gsup.imsi) == 0)
|
||||
return false;
|
||||
|
||||
/* Are we already forwarding this IMSI to a remote HLR? */
|
||||
proxy_subscr = proxy_subscr_get_by_imsi(proxy, req->gsup.imsi);
|
||||
if (proxy_subscr)
|
||||
goto yes_we_are_proxying;
|
||||
|
||||
/* The IMSI is not known locally, so we want to proxy to a remote HLR, but no proxy entry exists yet. We need to
|
||||
* look up the subscriber in remote HLRs via D-GSM mslookup, forward GSUP and reply once a result is back from
|
||||
* there. Defer message and kick off MS lookup. */
|
||||
|
||||
/* Kick off an mslookup for the remote HLR. */
|
||||
if (!g_hlr->mslookup.client.client) {
|
||||
LOG_GSUP_REQ(req, LOGL_DEBUG, "mslookup client not running, cannot query remote home HLR\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
query = (struct osmo_mslookup_query){
|
||||
.id = {
|
||||
.type = OSMO_MSLOOKUP_ID_IMSI,
|
||||
}
|
||||
};
|
||||
OSMO_STRLCPY_ARRAY(query.id.imsi, req->gsup.imsi);
|
||||
OSMO_STRLCPY_ARRAY(query.service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP);
|
||||
handling = (struct osmo_mslookup_query_handling){
|
||||
.result_timeout_milliseconds = g_hlr->mslookup.client.result_timeout_milliseconds,
|
||||
.result_cb = resolve_hlr_result_cb,
|
||||
};
|
||||
request_handle = osmo_mslookup_client_request(g_hlr->mslookup.client.client, &query, &handling);
|
||||
if (!request_handle) {
|
||||
LOG_DGSM(req->gsup.imsi, LOGL_ERROR, "Error dispatching mslookup query for home HLR: %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, &query, NULL));
|
||||
proxy_subscr_del(proxy, req->gsup.imsi);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Add a proxy entry without a remote address to indicate that we are busy querying for a remote HLR. */
|
||||
proxy_subscr_new = (struct proxy_subscr){};
|
||||
OSMO_STRLCPY_ARRAY(proxy_subscr_new.imsi, req->gsup.imsi);
|
||||
proxy_subscr = &proxy_subscr_new;
|
||||
proxy_subscr_update(proxy, proxy_subscr);
|
||||
|
||||
yes_we_are_proxying:
|
||||
OSMO_ASSERT(proxy_subscr);
|
||||
|
||||
/* If the remote HLR is already known, directly forward the GSUP message; otherwise, spool the GSUP message
|
||||
* until the remote HLR will respond / until timeout aborts. */
|
||||
dgsm_send_to_remote_hlr(proxy_subscr, req);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void dgsm_init(void *ctx)
|
||||
{
|
||||
dgsm_ctx = talloc_named_const(ctx, 0, "dgsm");
|
||||
dgsm_pending_messages_ctx = talloc_named_const(dgsm_ctx, 0, "dgsm_pending_messages");
|
||||
INIT_LLIST_HEAD(&g_hlr->mslookup.vty.server.msc_configs);
|
||||
|
||||
g_hlr->mslookup.server.max_age = 60 * 60;
|
||||
|
||||
g_hlr->mslookup.client.result_timeout_milliseconds = 2000;
|
||||
|
||||
g_hlr->gsup_unit_name.unit_name = "HLR";
|
||||
g_hlr->gsup_unit_name.serno = "unnamed-HLR";
|
||||
g_hlr->gsup_unit_name.swversion = PACKAGE_NAME "-" PACKAGE_VERSION;
|
||||
|
||||
osmo_sockaddr_str_from_str(&g_hlr->mslookup.vty.server.mdns.bind_addr,
|
||||
OSMO_MSLOOKUP_MDNS_IP4, OSMO_MSLOOKUP_MDNS_PORT);
|
||||
osmo_sockaddr_str_from_str(&g_hlr->mslookup.vty.client.mdns.query_addr,
|
||||
OSMO_MSLOOKUP_MDNS_IP4, OSMO_MSLOOKUP_MDNS_PORT);
|
||||
}
|
||||
|
||||
void dgsm_start(void *ctx)
|
||||
{
|
||||
g_hlr->mslookup.client.client = osmo_mslookup_client_new(dgsm_ctx);
|
||||
OSMO_ASSERT(g_hlr->mslookup.client.client);
|
||||
g_hlr->mslookup.allow_startup = true;
|
||||
dgsm_config_apply();
|
||||
}
|
||||
|
||||
static void dgsm_mdns_server_config_apply()
|
||||
{
|
||||
/* Check whether to start/stop/restart mDNS server */
|
||||
bool should_run;
|
||||
bool should_stop;
|
||||
if (!g_hlr->mslookup.allow_startup)
|
||||
return;
|
||||
|
||||
should_run = g_hlr->mslookup.vty.server.enable && g_hlr->mslookup.vty.server.mdns.enable;
|
||||
should_stop = g_hlr->mslookup.server.mdns
|
||||
&& (!should_run
|
||||
|| osmo_sockaddr_str_cmp(&g_hlr->mslookup.vty.server.mdns.bind_addr,
|
||||
&g_hlr->mslookup.server.mdns->bind_addr));
|
||||
|
||||
if (should_stop) {
|
||||
osmo_mslookup_server_mdns_stop(g_hlr->mslookup.server.mdns);
|
||||
g_hlr->mslookup.server.mdns = NULL;
|
||||
LOGP(DDGSM, LOGL_NOTICE, "Stopped mslookup mDNS server\n");
|
||||
}
|
||||
|
||||
if (should_run && !g_hlr->mslookup.server.mdns) {
|
||||
g_hlr->mslookup.server.mdns =
|
||||
osmo_mslookup_server_mdns_start(g_hlr, &g_hlr->mslookup.vty.server.mdns.bind_addr);
|
||||
if (!g_hlr->mslookup.server.mdns)
|
||||
LOGP(DDGSM, LOGL_ERROR, "Failed to start mslookup mDNS server on " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.server.mdns->bind_addr));
|
||||
else
|
||||
LOGP(DDGSM, LOGL_NOTICE, "Started mslookup mDNS server, receiving mDNS requests at multicast "
|
||||
OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.server.mdns->bind_addr));
|
||||
}
|
||||
}
|
||||
|
||||
static void dgsm_mdns_client_config_apply()
|
||||
{
|
||||
if (!g_hlr->mslookup.allow_startup)
|
||||
return;
|
||||
|
||||
/* Check whether to start/stop/restart mDNS client */
|
||||
const struct osmo_sockaddr_str *current_bind_addr;
|
||||
current_bind_addr = osmo_mslookup_client_method_mdns_get_bind_addr(g_hlr->mslookup.client.mdns);
|
||||
|
||||
bool should_run = g_hlr->mslookup.vty.client.enable && g_hlr->mslookup.vty.client.mdns.enable;
|
||||
bool should_stop = g_hlr->mslookup.client.mdns &&
|
||||
(!should_run
|
||||
|| osmo_sockaddr_str_cmp(&g_hlr->mslookup.vty.client.mdns.query_addr,
|
||||
current_bind_addr));
|
||||
|
||||
if (should_stop) {
|
||||
osmo_mslookup_client_method_del(g_hlr->mslookup.client.client, g_hlr->mslookup.client.mdns);
|
||||
g_hlr->mslookup.client.mdns = NULL;
|
||||
LOGP(DDGSM, LOGL_NOTICE, "Stopped mslookup mDNS client\n");
|
||||
}
|
||||
|
||||
if (should_run && !g_hlr->mslookup.client.mdns) {
|
||||
g_hlr->mslookup.client.mdns =
|
||||
osmo_mslookup_client_add_mdns(g_hlr->mslookup.client.client,
|
||||
g_hlr->mslookup.vty.client.mdns.query_addr.ip,
|
||||
g_hlr->mslookup.vty.client.mdns.query_addr.port,
|
||||
true);
|
||||
if (!g_hlr->mslookup.client.mdns)
|
||||
LOGP(DDGSM, LOGL_ERROR, "Failed to start mslookup mDNS client with target "
|
||||
OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.vty.client.mdns.query_addr));
|
||||
else
|
||||
LOGP(DDGSM, LOGL_NOTICE, "Started mslookup mDNS client, sending mDNS requests to multicast " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.vty.client.mdns.query_addr));
|
||||
}
|
||||
}
|
||||
|
||||
void dgsm_config_apply()
|
||||
{
|
||||
dgsm_mdns_server_config_apply();
|
||||
dgsm_mdns_client_config_apply();
|
||||
}
|
||||
|
||||
75
src/dgsm.h
Normal file
75
src/dgsm.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/mslookup/mslookup.h>
|
||||
#include "gsup_server.h"
|
||||
#include "global_title.h"
|
||||
|
||||
#define LOG_DGSM(imsi, level, fmt, args...) \
|
||||
LOGP(DDGSM, level, "(IMSI-%s) " fmt, imsi, ##args)
|
||||
|
||||
struct vty;
|
||||
struct remote_hlr;
|
||||
|
||||
extern void *dgsm_ctx;
|
||||
|
||||
struct dgsm_service_host {
|
||||
struct llist_head entry;
|
||||
char service[OSMO_MSLOOKUP_SERVICE_MAXLEN+1];
|
||||
struct osmo_sockaddr_str host_v4;
|
||||
struct osmo_sockaddr_str host_v6;
|
||||
};
|
||||
|
||||
struct dgsm_msc_config {
|
||||
struct llist_head entry;
|
||||
struct global_title name;
|
||||
struct llist_head service_hosts;
|
||||
};
|
||||
|
||||
/* "Sketch pad" where the VTY can store config items without yet applying. The changes will be applied by e.g.
|
||||
* dgsm_mdns_server_config_apply() and dgsm_mdns_client_config_apply(). */
|
||||
struct dgsm_config {
|
||||
struct {
|
||||
/* Whether to listen for incoming MS Lookup requests */
|
||||
bool enable;
|
||||
|
||||
struct {
|
||||
bool enable;
|
||||
struct osmo_sockaddr_str bind_addr;
|
||||
} mdns;
|
||||
|
||||
struct llist_head msc_configs;
|
||||
} server;
|
||||
|
||||
struct {
|
||||
/* Whether to ask remote HLRs via MS Lookup if an IMSI is not known locally. */
|
||||
bool enable;
|
||||
struct timeval timeout;
|
||||
|
||||
struct {
|
||||
/* Whether to use mDNS for IMSI MS Lookup */
|
||||
bool enable;
|
||||
struct osmo_sockaddr_str query_addr;
|
||||
} mdns;
|
||||
} client;
|
||||
};
|
||||
|
||||
void dgsm_config_apply();
|
||||
|
||||
struct dgsm_service_host *dgsm_config_service_get(const struct global_title *msc_name, const char *service);
|
||||
int dgsm_config_service_set(const struct global_title *msc_name, const char *service, const struct osmo_sockaddr_str *addr);
|
||||
int dgsm_config_service_del(const struct global_title *msc_name, const char *service, const struct osmo_sockaddr_str *addr);
|
||||
|
||||
struct dgsm_service_host *dgsm_config_msc_service_get(struct dgsm_msc_config *msc, const char *service, bool create);
|
||||
int dgsm_config_msc_service_set(struct dgsm_msc_config *msc, const char *service, const struct osmo_sockaddr_str *addr);
|
||||
int dgsm_config_msc_service_del(struct dgsm_msc_config *msc, const char *service, const struct osmo_sockaddr_str *addr);
|
||||
|
||||
extern const struct global_title dgsm_config_msc_wildcard;
|
||||
struct dgsm_msc_config *dgsm_config_msc_get(const struct global_title *msc_name, bool create);
|
||||
|
||||
void dgsm_init(void *ctx);
|
||||
void dgsm_start(void *ctx);
|
||||
bool dgsm_check_forward_gsup_msg(struct osmo_gsup_req *req);
|
||||
|
||||
void dgsm_vty_init();
|
||||
|
||||
void dgsm_remote_hlr_up(struct remote_hlr *remote_hlr);
|
||||
355
src/dgsm_vty.c
Normal file
355
src/dgsm_vty.c
Normal file
@@ -0,0 +1,355 @@
|
||||
#include <osmocom/vty/vty.h>
|
||||
#include <osmocom/vty/command.h>
|
||||
#include "hlr_vty.h"
|
||||
#include "dgsm.h"
|
||||
|
||||
struct cmd_node mslookup_node = {
|
||||
MSLOOKUP_NODE,
|
||||
"%s(config-mslookup)# ",
|
||||
1,
|
||||
};
|
||||
|
||||
DEFUN(cfg_mslookup,
|
||||
cfg_mslookup_cmd,
|
||||
"mslookup",
|
||||
"Configure Distributed GSM / multicast MS Lookup")
|
||||
{
|
||||
vty->node = MSLOOKUP_NODE;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_mdns,
|
||||
cfg_mslookup_mdns_cmd,
|
||||
"mdns",
|
||||
"Convenience shortcut: enable both server and client for DNS/mDNS MS Lookup with default config\n")
|
||||
{
|
||||
g_hlr->mslookup.vty.server.enable = true;
|
||||
g_hlr->mslookup.vty.server.mdns.enable = true;
|
||||
g_hlr->mslookup.vty.client.enable = true;
|
||||
g_hlr->mslookup.vty.client.mdns.enable = true;
|
||||
dgsm_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_no_mdns,
|
||||
cfg_mslookup_no_mdns_cmd,
|
||||
"no mdns",
|
||||
NO_STR "Disable both server and client for DNS/mDNS MS Lookup\n")
|
||||
{
|
||||
g_hlr->mslookup.vty.server.mdns.enable = false;
|
||||
g_hlr->mslookup.vty.client.mdns.enable = false;
|
||||
dgsm_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
struct cmd_node mslookup_server_node = {
|
||||
MSLOOKUP_SERVER_NODE,
|
||||
"%s(config-mslookup-server)# ",
|
||||
1,
|
||||
};
|
||||
|
||||
DEFUN(cfg_mslookup_server,
|
||||
cfg_mslookup_server_cmd,
|
||||
"server",
|
||||
"Enable and configure Distributed GSM / multicast MS Lookup server")
|
||||
{
|
||||
vty->node = MSLOOKUP_SERVER_NODE;
|
||||
g_hlr->mslookup.vty.server.enable = true;
|
||||
dgsm_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_no_server,
|
||||
cfg_mslookup_no_server_cmd,
|
||||
"no server",
|
||||
NO_STR "Disable Distributed GSM / multicast MS Lookup server")
|
||||
{
|
||||
g_hlr->mslookup.vty.server.enable = false;
|
||||
dgsm_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#define MDNS_STR "Configure mslookup by multicast DNS\n"
|
||||
#define MDNS_BIND_STR MDNS_STR "Configure where the mDNS server listens for MS Lookup requests\n"
|
||||
#define IP46_STR "IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1\n"
|
||||
#define PORT_STR "Port number\n"
|
||||
|
||||
DEFUN(cfg_mslookup_server_mdns_bind,
|
||||
cfg_mslookup_server_mdns_bind_cmd,
|
||||
"mdns [bind] [IP] [<1-65535>]",
|
||||
MDNS_BIND_STR IP46_STR PORT_STR)
|
||||
{
|
||||
const char *ip_str = argc > 1? argv[1] : g_hlr->mslookup.vty.server.mdns.bind_addr.ip;
|
||||
const char *port_str = argc > 2? argv[2] : NULL;
|
||||
uint16_t port_nr = port_str ? atoi(port_str) : g_hlr->mslookup.vty.server.mdns.bind_addr.port;
|
||||
struct osmo_sockaddr_str addr;
|
||||
if (osmo_sockaddr_str_from_str(&addr, ip_str, port_nr)
|
||||
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
|
||||
vty_out(vty, "%% MS Lookup server: Invalid mDNS bind address: %s %u%s",
|
||||
ip_str, port_nr, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
g_hlr->mslookup.vty.server.mdns.bind_addr = addr;
|
||||
g_hlr->mslookup.vty.server.mdns.enable = true;
|
||||
dgsm_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_server_no_mdns,
|
||||
cfg_mslookup_server_no_mdns_cmd,
|
||||
"no mdns",
|
||||
NO_STR "Disable server for DNS/mDNS MS Lookup (do not answer remote requests)\n")
|
||||
{
|
||||
g_hlr->mslookup.vty.server.mdns.enable = false;
|
||||
dgsm_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
struct cmd_node mslookup_server_msc_node = {
|
||||
MSLOOKUP_SERVER_MSC_NODE,
|
||||
"%s(config-mslookup-server-msc)# ",
|
||||
1,
|
||||
};
|
||||
|
||||
DEFUN(cfg_mslookup_server_msc,
|
||||
cfg_mslookup_server_msc_cmd,
|
||||
"msc .UNIT_NAME",
|
||||
"Configure services for individual local MSCs\n"
|
||||
"IPA Unit Name of the local MSC to configure\n")
|
||||
{
|
||||
struct global_title msc_name;
|
||||
struct dgsm_msc_config *msc;
|
||||
global_title_set_str(&msc_name, argv_concat(argv, argc, 0));
|
||||
|
||||
msc = dgsm_config_msc_get(&msc_name, true);
|
||||
if (!msc) {
|
||||
vty_out(vty, "%% Error creating MSC %s%s", global_title_name(&msc_name), VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
vty->node = MSLOOKUP_SERVER_MSC_NODE;
|
||||
vty->index = msc;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#define SERVICE_NAME_STR \
|
||||
"MS Lookup service name, e.g. " OSMO_MSLOOKUP_SERVICE_SIP " or " OSMO_MSLOOKUP_SERVICE_SMPP "\n"
|
||||
|
||||
#define SERVICE_AND_NAME_STR \
|
||||
"Configure addresses of local services, as sent in replies to remote MS Lookup requests.\n" \
|
||||
SERVICE_NAME_STR
|
||||
|
||||
|
||||
DEFUN(cfg_mslookup_server_msc_service,
|
||||
cfg_mslookup_server_msc_service_cmd,
|
||||
"service NAME at IP <1-65535>",
|
||||
SERVICE_AND_NAME_STR "at\n" IP46_STR PORT_STR)
|
||||
{
|
||||
/* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
|
||||
* MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
|
||||
struct dgsm_msc_config *msc = (vty->node == MSLOOKUP_SERVER_MSC_NODE) ? vty->index : NULL;
|
||||
const char *service = argv[0];
|
||||
const char *ip_str = argv[1];
|
||||
const char *port_str = argv[2];
|
||||
struct osmo_sockaddr_str addr;
|
||||
|
||||
/* On the mslookup.server node, set services on the wildcard msc, without a particular name. */
|
||||
if (vty->node == MSLOOKUP_SERVER_NODE)
|
||||
msc = dgsm_config_msc_get(&dgsm_config_msc_wildcard, true);
|
||||
|
||||
if (!msc) {
|
||||
vty_out(vty, "%% Error: no MSC object on this node%s", VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (osmo_sockaddr_str_from_str(&addr, ip_str, atoi(port_str))
|
||||
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
|
||||
vty_out(vty, "%% MS Lookup server: Invalid address for service %s: %s %s%s",
|
||||
service, ip_str, port_str, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (dgsm_config_msc_service_set(msc, service, &addr)) {
|
||||
vty_out(vty, "%% MS Lookup server: Error setting service %s to %s %s%s",
|
||||
service, ip_str, port_str, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#define NO_SERVICE_AND_NAME_STR NO_STR "Remove one or more service address entries\n" SERVICE_NAME_STR
|
||||
|
||||
DEFUN(cfg_mslookup_server_msc_no_service,
|
||||
cfg_mslookup_server_msc_no_service_cmd,
|
||||
"no service NAME",
|
||||
NO_SERVICE_AND_NAME_STR)
|
||||
{
|
||||
/* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
|
||||
* MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
|
||||
struct dgsm_msc_config *msc = (vty->node == MSLOOKUP_SERVER_MSC_NODE) ? vty->index : NULL;
|
||||
const char *service = argv[0];
|
||||
|
||||
if (dgsm_config_msc_service_del(msc, service, NULL)) {
|
||||
vty_out(vty, "%% MS Lookup server: Error removing service %s%s",
|
||||
service, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_server_msc_no_service_addr,
|
||||
cfg_mslookup_server_msc_no_service_addr_cmd,
|
||||
"no service NAME at IP <1-65535>",
|
||||
NO_SERVICE_AND_NAME_STR "at\n" IP46_STR PORT_STR)
|
||||
{
|
||||
/* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
|
||||
* MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
|
||||
struct dgsm_msc_config *msc = (vty->node == MSLOOKUP_SERVER_MSC_NODE) ? vty->index : NULL;
|
||||
const char *service = argv[0];
|
||||
const char *ip_str = argv[1];
|
||||
const char *port_str = argv[2];
|
||||
struct osmo_sockaddr_str addr;
|
||||
|
||||
if (osmo_sockaddr_str_from_str(&addr, ip_str, atoi(port_str))
|
||||
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
|
||||
vty_out(vty, "%% MS Lookup server: Invalid address for 'no service' %s: %s %s%s",
|
||||
service, ip_str, port_str, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (dgsm_config_service_del(&msc->name, service, &addr)) {
|
||||
vty_out(vty, "%% MS Lookup server: Error removing service %s to %s %s%s",
|
||||
service, ip_str, port_str, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
struct cmd_node mslookup_client_node = {
|
||||
MSLOOKUP_CLIENT_NODE,
|
||||
"%s(config-mslookup-client)# ",
|
||||
1,
|
||||
};
|
||||
|
||||
DEFUN(cfg_mslookup_client,
|
||||
cfg_mslookup_client_cmd,
|
||||
"client",
|
||||
"Enable and configure Distributed GSM / multicast MS Lookup client")
|
||||
{
|
||||
vty->node = MSLOOKUP_CLIENT_NODE;
|
||||
g_hlr->mslookup.vty.client.enable = true;
|
||||
dgsm_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_no_client,
|
||||
cfg_mslookup_no_client_cmd,
|
||||
"no client",
|
||||
NO_STR "Disable Distributed GSM / multicast MS Lookup client")
|
||||
{
|
||||
g_hlr->mslookup.vty.client.enable = false;
|
||||
dgsm_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#define MDNS_TO_STR MDNS_STR "Configure to which multicast address mDNS MS Lookup requests are sent\n"
|
||||
|
||||
DEFUN(cfg_mslookup_client_timeout,
|
||||
cfg_mslookup_client_timeout_cmd,
|
||||
"timeout <1-100000>",
|
||||
"How long should the mslookup client wait for remote responses before evaluating received results\n"
|
||||
"timeout in milliseconds\n")
|
||||
{
|
||||
uint32_t val = atol(argv[0]);
|
||||
g_hlr->mslookup.vty.client.timeout.tv_sec = val / 1000;
|
||||
g_hlr->mslookup.vty.client.timeout.tv_usec = (val % 1000) * 1000;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#define EXIT_HINT() \
|
||||
if (vty->type != VTY_FILE) \
|
||||
vty_out(vty, "%% 'exit' this node to apply changes%s", VTY_NEWLINE)
|
||||
|
||||
DEFUN(cfg_mslookup_client_mdns,
|
||||
cfg_mslookup_client_mdns_cmd,
|
||||
"mdns [to] [IP] [<1-65535>]",
|
||||
MDNS_STR "Configure multicast address to send mDNS mslookup requests to\n" IP46_STR PORT_STR)
|
||||
{
|
||||
const char *ip_str = argc > 1? argv[1] : g_hlr->mslookup.vty.client.mdns.query_addr.ip;
|
||||
const char *port_str = argc > 2? argv[2] : NULL;
|
||||
uint16_t port_nr = port_str ? atoi(port_str) : g_hlr->mslookup.vty.client.mdns.query_addr.port;
|
||||
struct osmo_sockaddr_str addr;
|
||||
if (osmo_sockaddr_str_from_str(&addr, ip_str, port_nr)
|
||||
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
|
||||
vty_out(vty, "%% MS Lookup client: Invalid mDNS target address: %s %u%s",
|
||||
ip_str, port_nr, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
g_hlr->mslookup.vty.client.mdns.query_addr = addr;
|
||||
g_hlr->mslookup.vty.client.mdns.enable = true;
|
||||
dgsm_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_mslookup_client_no_mdns,
|
||||
cfg_mslookup_client_no_mdns_cmd,
|
||||
"no mdns",
|
||||
NO_STR "Disable mDNS client, do not query remote services by mDNS\n")
|
||||
{
|
||||
g_hlr->mslookup.vty.client.mdns.enable = false;
|
||||
dgsm_config_apply();
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
int config_write_mslookup(struct vty *vty)
|
||||
{
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
int config_write_mslookup_server(struct vty *vty)
|
||||
{
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
int config_write_mslookup_server_msc(struct vty *vty)
|
||||
{
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
int config_write_mslookup_client(struct vty *vty)
|
||||
{
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
void dgsm_vty_init()
|
||||
{
|
||||
install_element(CONFIG_NODE, &cfg_mslookup_cmd);
|
||||
|
||||
install_node(&mslookup_node, config_write_mslookup);
|
||||
install_element(MSLOOKUP_NODE, &cfg_mslookup_mdns_cmd);
|
||||
install_element(MSLOOKUP_NODE, &cfg_mslookup_no_mdns_cmd);
|
||||
install_element(MSLOOKUP_NODE, &cfg_mslookup_server_cmd);
|
||||
install_element(MSLOOKUP_NODE, &cfg_mslookup_no_server_cmd);
|
||||
|
||||
install_node(&mslookup_server_node, config_write_mslookup_server);
|
||||
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_mdns_bind_cmd);
|
||||
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_no_mdns_cmd);
|
||||
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_service_cmd);
|
||||
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_no_service_cmd);
|
||||
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_no_service_addr_cmd);
|
||||
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_cmd);
|
||||
|
||||
install_node(&mslookup_server_msc_node, config_write_mslookup_server_msc);
|
||||
install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_service_cmd);
|
||||
install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_no_service_cmd);
|
||||
install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_no_service_addr_cmd);
|
||||
|
||||
install_element(MSLOOKUP_NODE, &cfg_mslookup_client_cmd);
|
||||
install_element(MSLOOKUP_NODE, &cfg_mslookup_no_client_cmd);
|
||||
install_node(&mslookup_client_node, config_write_mslookup_client);
|
||||
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_timeout_cmd);
|
||||
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_mdns_cmd);
|
||||
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_no_mdns_cmd);
|
||||
|
||||
}
|
||||
68
src/global_title.c
Normal file
68
src/global_title.c
Normal file
@@ -0,0 +1,68 @@
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include "global_title.h"
|
||||
|
||||
int global_title_set(struct global_title *gt, const uint8_t *val, size_t len)
|
||||
{
|
||||
if (!val || !len) {
|
||||
*gt = (struct global_title){};
|
||||
return 0;
|
||||
}
|
||||
if (len > sizeof(gt->val))
|
||||
return -ENOSPC;
|
||||
gt->len = len;
|
||||
memcpy(gt->val, val, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int global_title_set_str(struct global_title *gt, const char *str_fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
if (!str_fmt)
|
||||
return global_title_set(gt, NULL, 0);
|
||||
|
||||
va_start(ap, str_fmt);
|
||||
vsnprintf((char*)(gt->val), sizeof(gt->val), str_fmt, ap);
|
||||
va_end(ap);
|
||||
gt->len = strlen((char*)(gt->val))+1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int global_title_cmp(const struct global_title *a, const struct global_title *b)
|
||||
{
|
||||
int cmp;
|
||||
if (a == b)
|
||||
return 0;
|
||||
if (!a)
|
||||
return -1;
|
||||
if (!b)
|
||||
return 1;
|
||||
if (!a->len && !b->len)
|
||||
return 0;
|
||||
if (!a->len && b->len)
|
||||
return -1;
|
||||
if (!b->len && a->len)
|
||||
return 1;
|
||||
|
||||
if (a->len == b->len)
|
||||
return memcmp(a->val, b->val, a->len);
|
||||
else if (a->len < b->len) {
|
||||
cmp = memcmp(a->val, b->val, a->len);
|
||||
if (!cmp)
|
||||
cmp = -1;
|
||||
return cmp;
|
||||
} else {
|
||||
/* a->len > b->len */
|
||||
cmp = memcmp(a->val, b->val, b->len);
|
||||
if (!cmp)
|
||||
cmp = 1;
|
||||
return cmp;
|
||||
}
|
||||
}
|
||||
|
||||
const char *global_title_name(const struct global_title *gt)
|
||||
{
|
||||
return osmo_quote_str_c(OTC_SELECT, (char*)gt->val, gt->len);
|
||||
}
|
||||
|
||||
17
src/global_title.h
Normal file
17
src/global_title.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Arbitrary length blob, not necessarily zero-terminated.
|
||||
* In osmo-hlr, struct hlr_subscriber is mostly used as static reference and cannot serve as talloc context, which is
|
||||
* why this is also implemented as a fixed-maximum-size buffer instead of a talloc'd arbitrary sized buffer.
|
||||
*/
|
||||
struct global_title {
|
||||
size_t len;
|
||||
uint8_t val[128];
|
||||
};
|
||||
|
||||
int global_title_set(struct global_title *gt, const uint8_t *val, size_t len);
|
||||
int global_title_set_str(struct global_title *gt, const char *str_fmt, ...);
|
||||
int global_title_cmp(const struct global_title *a, const struct global_title *b);
|
||||
const char *global_title_name(const struct global_title *gt);
|
||||
@@ -23,16 +23,17 @@
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
|
||||
#include "logging.h"
|
||||
#include "gsup_server.h"
|
||||
#include "gsup_router.h"
|
||||
|
||||
struct gsup_route {
|
||||
struct llist_head list;
|
||||
|
||||
uint8_t *addr;
|
||||
struct osmo_gsup_conn *conn;
|
||||
};
|
||||
|
||||
/* find a route for the given address */
|
||||
/*! Find a route for the given address.
|
||||
* \param[in] gs gsup server
|
||||
* \param[in] addr IPA name of the client (SGSN, MSC/VLR). Although this is passed like a blob, together with the
|
||||
* length, it must be nul-terminated! This is for legacy reasons, see the discussion here:
|
||||
* https://gerrit.osmocom.org/#/c/osmo-hlr/+/13048/
|
||||
* \param[in] addrlen length of addr, *including the nul-byte* (strlen(addr) + 1).
|
||||
*/
|
||||
struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs,
|
||||
const uint8_t *addr, size_t addrlen)
|
||||
{
|
||||
@@ -46,20 +47,48 @@ struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct osmo_gsup_conn *gsup_route_find_gt(struct osmo_gsup_server *gs, const struct global_title *gt)
|
||||
{
|
||||
return gsup_route_find(gs, gt->val, gt->len);
|
||||
}
|
||||
|
||||
/*! Find a GSUP connection's route (to read the IPA address from the route).
|
||||
* \param[in] conn GSUP connection
|
||||
* \return GSUP route
|
||||
*/
|
||||
struct gsup_route *gsup_route_find_by_conn(const struct osmo_gsup_conn *conn)
|
||||
{
|
||||
struct gsup_route *gr;
|
||||
|
||||
llist_for_each_entry(gr, &conn->server->routes, list) {
|
||||
if (gr->conn == conn)
|
||||
return gr;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* add a new route for the given address to the given conn */
|
||||
int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addrlen)
|
||||
{
|
||||
struct gsup_route *gr;
|
||||
struct osmo_gsup_conn *exists_on_conn;
|
||||
|
||||
/* Check if we already have a route for this address */
|
||||
if (gsup_route_find(conn->server, addr, addrlen))
|
||||
return -EEXIST;
|
||||
exists_on_conn = gsup_route_find(conn->server, addr, addrlen);
|
||||
if (exists_on_conn) {
|
||||
if (exists_on_conn != conn)
|
||||
return -EEXIST;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* allocate new route and populate it */
|
||||
gr = talloc_zero(conn->server, struct gsup_route);
|
||||
if (!gr)
|
||||
return -ENOMEM;
|
||||
|
||||
LOGP(DMAIN, LOGL_INFO, "Adding GSUP route for %s via %s:%u\n", addr, conn->conn->addr, conn->conn->port);
|
||||
|
||||
gr->addr = talloc_memdup(gr, addr, addrlen);
|
||||
gr->conn = conn;
|
||||
llist_add_tail(&gr->list, &conn->server->routes);
|
||||
@@ -67,6 +96,11 @@ int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addr
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gsup_route_add_gt(struct osmo_gsup_conn *conn, const struct global_title *gt)
|
||||
{
|
||||
return gsup_route_add(conn, gt->val, gt->len);
|
||||
}
|
||||
|
||||
/* delete all routes for the given connection */
|
||||
int gsup_route_del_conn(struct osmo_gsup_conn *conn)
|
||||
{
|
||||
@@ -75,6 +109,8 @@ int gsup_route_del_conn(struct osmo_gsup_conn *conn)
|
||||
|
||||
llist_for_each_entry_safe(gr, gr2, &conn->server->routes, list) {
|
||||
if (gr->conn == conn) {
|
||||
LOGP(DMAIN, LOGL_INFO, "Removing GSUP route for %s (GSUP disconnect)\n",
|
||||
osmo_quote_str_c(OTC_SELECT, (char*)gr->addr, talloc_total_size(gr->addr)));
|
||||
llist_del(&gr->list);
|
||||
talloc_free(gr);
|
||||
num_deleted++;
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "global_title.h"
|
||||
#include "gsup_server.h"
|
||||
|
||||
struct gsup_route {
|
||||
struct llist_head list;
|
||||
|
||||
uint8_t *addr;
|
||||
struct osmo_gsup_conn *conn;
|
||||
};
|
||||
|
||||
struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs,
|
||||
const uint8_t *addr, size_t addrlen);
|
||||
struct osmo_gsup_conn *gsup_route_find_gt(struct osmo_gsup_server *gs, const struct global_title *gt);
|
||||
|
||||
struct gsup_route *gsup_route_find_by_conn(const struct osmo_gsup_conn *conn);
|
||||
|
||||
/* add a new route for the given address to the given conn */
|
||||
int gsup_route_add_gt(struct osmo_gsup_conn *conn, const struct global_title *gt);
|
||||
int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addrlen);
|
||||
|
||||
/* delete all routes for the given connection */
|
||||
int gsup_route_del_conn(struct osmo_gsup_conn *conn);
|
||||
|
||||
int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
|
||||
const uint8_t *addr, size_t addrlen,
|
||||
struct msgb *msg);
|
||||
int osmo_gsup_gt_send(struct osmo_gsup_server *gs, const struct global_title *gt, struct msgb *msg);
|
||||
int osmo_gsup_gt_enc_send(struct osmo_gsup_server *gs, const struct global_title *gt,
|
||||
const struct osmo_gsup_message *gsup);
|
||||
|
||||
91
src/gsup_send.c
Normal file
91
src/gsup_send.c
Normal file
@@ -0,0 +1,91 @@
|
||||
/* (C) 2018 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* This is kept separate to be able to override the actual sending functions from unit tests. */
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include "gsup_server.h"
|
||||
#include "gsup_router.h"
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
|
||||
/*! Send a msgb to a given address using routing.
|
||||
* \param[in] gs gsup server
|
||||
* \param[in] addr IPA name of the client (SGSN, MSC/VLR). Although this is passed like a blob, together with the
|
||||
* length, it must be nul-terminated! This is for legacy reasons, see the discussion here:
|
||||
* https://gerrit.osmocom.org/#/c/osmo-hlr/+/13048/
|
||||
* \param[in] addrlen length of addr, *including the nul-byte* (strlen(addr) + 1).
|
||||
* \param[in] msg message buffer
|
||||
*/
|
||||
int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
|
||||
const uint8_t *addr, size_t addrlen,
|
||||
struct msgb *msg)
|
||||
{
|
||||
struct osmo_gsup_conn *conn;
|
||||
|
||||
conn = gsup_route_find(gs, addr, addrlen);
|
||||
if (!conn) {
|
||||
LOGP(DLGSUP, LOGL_ERROR,
|
||||
"Cannot find route for addr %s\n", osmo_quote_str((const char*)addr, addrlen));
|
||||
msgb_free(msg);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return osmo_gsup_conn_send(conn, msg);
|
||||
}
|
||||
|
||||
/*! Send a msgb to a given address using routing.
|
||||
* \param[in] gs gsup server
|
||||
* \param[in] gt IPA unit name of the client (SGSN, MSC/VLR, proxy).
|
||||
* \param[in] msg message buffer
|
||||
*/
|
||||
int osmo_gsup_gt_send(struct osmo_gsup_server *gs, const struct global_title *gt, struct msgb *msg)
|
||||
{
|
||||
if (gt->val[gt->len - 1]) {
|
||||
/* Is not nul terminated. But for legacy reasons we (still) require that. */
|
||||
if (gt->len >= sizeof(gt->val)) {
|
||||
LOGP(DLGSUP, LOGL_ERROR, "Global title (IPA unit name) is too long: %s\n",
|
||||
global_title_name(gt));
|
||||
return -EINVAL;
|
||||
}
|
||||
struct global_title gt2 = *gt;
|
||||
gt2.val[gt->len] = '\0';
|
||||
gt2.len++;
|
||||
return osmo_gsup_addr_send(gs, gt2.val, gt2.len, msg);
|
||||
}
|
||||
return osmo_gsup_addr_send(gs, gt->val, gt->len, msg);
|
||||
}
|
||||
|
||||
int osmo_gsup_gt_enc_send(struct osmo_gsup_server *gs, const struct global_title *gt,
|
||||
const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
struct msgb *msg = osmo_gsup_msgb_alloc("GSUP Tx");
|
||||
int rc;
|
||||
rc = osmo_gsup_encode(msg, gsup);
|
||||
if (rc) {
|
||||
LOGP(DLGSUP, LOGL_ERROR, "IMSI-%s: Cannot encode GSUP: %s\n",
|
||||
gsup->imsi, osmo_gsup_message_type_name(gsup->message_type));
|
||||
msgb_free(msg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOGP(DLGSUP, LOGL_DEBUG, "IMSI-%s: Tx: %s\n", gsup->imsi, osmo_gsup_message_type_name(gsup->message_type));
|
||||
return osmo_gsup_gt_send(gs, gt, msg);
|
||||
}
|
||||
@@ -24,10 +24,21 @@
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/abis/ipa.h>
|
||||
#include <osmocom/abis/ipaccess.h>
|
||||
#include <osmocom/gsm/gsm48_ie.h>
|
||||
#include <osmocom/gsm/apn.h>
|
||||
#include <osmocom/gsm/gsm23003.h>
|
||||
|
||||
#include "hlr.h"
|
||||
#include "gsup_server.h"
|
||||
#include "gsup_router.h"
|
||||
|
||||
struct msgb *osmo_gsup_msgb_alloc(const char *label)
|
||||
{
|
||||
struct msgb *msg = msgb_alloc_headroom(1024+512, 512, label);
|
||||
OSMO_ASSERT(msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
static void osmo_gsup_server_send(struct osmo_gsup_conn *conn,
|
||||
int proto_ext, struct msgb *msg_tx)
|
||||
{
|
||||
@@ -48,6 +59,229 @@ int osmo_gsup_conn_send(struct osmo_gsup_conn *conn, struct msgb *msg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Only for requests originating here. When answering to a remote request, rather use osmo_gsup_req_respond() or
|
||||
* osmo_gsup_req_respond_err(). */
|
||||
int osmo_gsup_conn_enc_send(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
struct msgb *msg = osmo_gsup_msgb_alloc("GSUP Tx");
|
||||
int rc;
|
||||
rc = osmo_gsup_encode(msg, gsup);
|
||||
if (rc) {
|
||||
LOG_GSUP_CONN(conn, LOGL_ERROR, "Cannot encode GSUP: %s\n",
|
||||
osmo_gsup_message_type_name(gsup->message_type));
|
||||
msgb_free(msg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOG_GSUP_CONN(conn, LOGL_DEBUG, "Tx: %s\n", osmo_gsup_message_type_name(gsup->message_type));
|
||||
rc = osmo_gsup_conn_send(conn, msg);
|
||||
if (rc)
|
||||
LOG_GSUP_CONN(conn, LOGL_ERROR, "Unable to send: %s\n",
|
||||
osmo_gsup_message_type_name(gsup->message_type));
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct osmo_gsup_req *osmo_gsup_req_new(struct osmo_gsup_conn *conn, struct msgb *msg)
|
||||
{
|
||||
static unsigned int next_req_nr = 1;
|
||||
struct osmo_gsup_req *req;
|
||||
struct osmo_gsup_message gsup_err;
|
||||
int rc;
|
||||
|
||||
if (!msgb_l2(msg) || !msgb_l2len(msg)) {
|
||||
LOGP(DLGSUP, LOGL_ERROR, "Rx GSUP message: missing or empty L2 data\n");
|
||||
msgb_free(msg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
req = talloc_zero(conn->server, struct osmo_gsup_req);
|
||||
OSMO_ASSERT(req);
|
||||
req->nr = next_req_nr++;
|
||||
req->server = conn->server;
|
||||
req->msg = msg;
|
||||
req->source_name = conn->peer_name;
|
||||
|
||||
rc = osmo_gsup_decode(msgb_l2(req->msg), msgb_l2len(req->msg), (struct osmo_gsup_message*)&req->gsup);
|
||||
if (rc < 0) {
|
||||
LOGP(DLGSUP, LOGL_ERROR, "Rx GSUP message: cannot decode (rc=%d)\n", rc);
|
||||
osmo_gsup_req_free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOG_GSUP_REQ(req, LOGL_DEBUG, "new\n");
|
||||
|
||||
if (req->gsup.source_name_len) {
|
||||
if (global_title_set(&req->source_name, req->gsup.source_name, req->gsup.source_name_len)) {
|
||||
LOGP(DLGSUP, LOGL_ERROR, "cannot decode GSUP message's source_name, message is not routable\n");
|
||||
goto unroutable_error;
|
||||
}
|
||||
|
||||
if (global_title_cmp(&req->source_name, &conn->peer_name)) {
|
||||
/* The source of the GSUP message is not the immediate GSUP peer, but that peer is our proxy for that
|
||||
* source. Add it to the routes for this conn (so we can route responses back). */
|
||||
if (gsup_route_add_gt(conn, &req->source_name)) {
|
||||
LOGP(DLGSUP, LOGL_ERROR,
|
||||
"GSUP message received from %s via peer %s, but there already exists a different"
|
||||
" route to this source, message is not routable\n",
|
||||
global_title_name(&req->source_name),
|
||||
global_title_name(&conn->peer_name));
|
||||
goto unroutable_error;
|
||||
}
|
||||
|
||||
req->via_proxy = conn->peer_name;
|
||||
}
|
||||
}
|
||||
|
||||
if (!osmo_imsi_str_valid(req->gsup.imsi)) {
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "invalid IMSI: %s",
|
||||
osmo_quote_str(req->gsup.imsi, -1));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return req;
|
||||
|
||||
unroutable_error:
|
||||
gsup_err = (struct osmo_gsup_message){
|
||||
.message_type = OSMO_GSUP_MSGT_E_ROUTING_ERROR,
|
||||
.destination_name = req->gsup.destination_name,
|
||||
.destination_name_len = req->gsup.destination_name_len,
|
||||
.source_name = req->gsup.source_name,
|
||||
.source_name_len = req->gsup.source_name_len,
|
||||
};
|
||||
osmo_gsup_set_reply(&req->gsup, &gsup_err);
|
||||
osmo_gsup_conn_enc_send(conn, &gsup_err);
|
||||
osmo_gsup_req_free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void _osmo_gsup_req_free(struct osmo_gsup_req *req)
|
||||
{
|
||||
if (req->msg)
|
||||
msgb_free(req->msg);
|
||||
talloc_free(req);
|
||||
}
|
||||
|
||||
int osmo_gsup_req_respond_nonfinal(struct osmo_gsup_req *req, struct osmo_gsup_message *response)
|
||||
{
|
||||
struct osmo_gsup_conn *conn;
|
||||
struct msgb *msg;
|
||||
int rc;
|
||||
|
||||
conn = gsup_route_find_gt(req->server, &req->source_name);
|
||||
if (!conn) {
|
||||
LOG_GSUP_REQ(req, LOGL_ERROR, "GSUP client that sent this request was disconnected, cannot respond\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
osmo_gsup_set_reply(&req->gsup, response);
|
||||
|
||||
msg = osmo_gsup_msgb_alloc("GSUP response");
|
||||
rc = osmo_gsup_encode(msg, response);
|
||||
if (rc) {
|
||||
LOG_GSUP_REQ(req, LOGL_ERROR, "Unable to encode response: %s\n",
|
||||
osmo_gsup_message_type_name(response->message_type));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOG_GSUP_REQ(req, LOGL_DEBUG, "Tx response: %s\n", osmo_gsup_message_type_name(response->message_type));
|
||||
|
||||
rc = osmo_gsup_conn_send(conn, msg);
|
||||
if (rc)
|
||||
LOG_GSUP_REQ(req, LOGL_ERROR, "Unable to send response: %s\n",
|
||||
osmo_gsup_message_type_name(response->message_type));
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Make sure the response message contains all IEs that are required to be a valid response for the received GSUP
|
||||
* request, and send back to the requesting peer. */
|
||||
int osmo_gsup_req_respond(struct osmo_gsup_req *req, struct osmo_gsup_message *response)
|
||||
{
|
||||
int rc = osmo_gsup_req_respond_nonfinal(req, response);
|
||||
osmo_gsup_req_free(req);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int osmo_gsup_req_respond_msgt(struct osmo_gsup_req *req, enum osmo_gsup_message_type message_type)
|
||||
{
|
||||
struct osmo_gsup_message response = {
|
||||
.message_type = message_type,
|
||||
};
|
||||
return osmo_gsup_req_respond(req, &response);
|
||||
}
|
||||
|
||||
#define osmo_gsup_req_respond_err(REQ, CAUSE, FMT, args...) do { \
|
||||
LOG_GSUP_REQ(REQ, LOGL_ERROR, "%s: " FMT "\n", \
|
||||
get_value_string(gsm48_gmm_cause_names, CAUSE), ##args); \
|
||||
_osmo_gsup_req_respond_err(REQ, CAUSE); \
|
||||
} while(0)
|
||||
void _osmo_gsup_req_respond_err(struct osmo_gsup_req *req, enum gsm48_gmm_cause cause)
|
||||
{
|
||||
struct osmo_gsup_message response = {
|
||||
.cause = cause,
|
||||
.message_type = OSMO_GSUP_TO_MSGT_ERROR(req->gsup.message_type),
|
||||
};
|
||||
|
||||
/* No need to answer if we couldn't parse an ERROR message type, only REQUESTs need an error reply. */
|
||||
if (!OSMO_GSUP_IS_MSGT_REQUEST(req->gsup.message_type)) {
|
||||
osmo_gsup_req_free(req);
|
||||
return;
|
||||
}
|
||||
|
||||
if (req->gsup.session_state != OSMO_GSUP_SESSION_STATE_NONE)
|
||||
response.session_state = OSMO_GSUP_SESSION_STATE_END;
|
||||
|
||||
osmo_gsup_req_respond(req, &response);
|
||||
}
|
||||
|
||||
/* Encode an error reponse to the given GSUP message with the given cause.
|
||||
* Determine the error message type via OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type).
|
||||
* Only send an error response if the original message is a Request message.
|
||||
* On failure, log an error, but don't return anything: if an error occurs while trying to report an earlier error,
|
||||
* there is nothing we can do really except log the error (there are no callers that would use the return code).
|
||||
*/
|
||||
void osmo_gsup_conn_send_err_reply(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup_orig,
|
||||
enum gsm48_gmm_cause cause)
|
||||
{
|
||||
struct osmo_gsup_message gsup_reply;
|
||||
struct msgb *msg_out;
|
||||
int rc;
|
||||
|
||||
/* No need to answer if we couldn't parse an ERROR message type, only REQUESTs need an error reply. */
|
||||
if (!OSMO_GSUP_IS_MSGT_REQUEST(gsup_orig->message_type))
|
||||
return;
|
||||
|
||||
gsup_reply = (struct osmo_gsup_message){
|
||||
.cause = cause,
|
||||
.message_type = OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type),
|
||||
.message_class = gsup_orig->message_class,
|
||||
|
||||
/* RP-Message-Reference is mandatory for SM Service */
|
||||
.sm_rp_mr = gsup_orig->sm_rp_mr,
|
||||
};
|
||||
osmo_gsup_set_reply(gsup_orig, &gsup_reply);
|
||||
|
||||
if (gsup_orig->session_state != OSMO_GSUP_SESSION_STATE_NONE)
|
||||
gsup_reply.session_state = OSMO_GSUP_SESSION_STATE_END;
|
||||
|
||||
msg_out = osmo_gsup_msgb_alloc("GSUP ERR response");
|
||||
rc = osmo_gsup_encode(msg_out, &gsup_reply);
|
||||
if (rc) {
|
||||
LOGP(DLGSUP, LOGL_ERROR, "%s: Unable to encode error response %s (rc=%d)\n",
|
||||
osmo_quote_str(gsup_orig->imsi, -1), osmo_gsup_message_type_name(gsup_reply.message_type),
|
||||
rc);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGP(DLGSUP, LOGL_DEBUG, "%s: GSUP tx %s\n",
|
||||
osmo_quote_str(gsup_orig->imsi, -1), osmo_gsup_message_type_name(gsup_reply.message_type));
|
||||
|
||||
rc = osmo_gsup_conn_send(conn, msg_out);
|
||||
if (rc)
|
||||
LOGP(DLGSUP, LOGL_ERROR, "%s: Unable to send error response %s (rc=%d)\n",
|
||||
osmo_quote_str(gsup_orig->imsi, -1), osmo_gsup_message_type_name(gsup_reply.message_type),
|
||||
rc);
|
||||
}
|
||||
|
||||
static int osmo_gsup_conn_oap_handle(struct osmo_gsup_conn *conn,
|
||||
struct msgb *msg_rx)
|
||||
{
|
||||
@@ -193,10 +427,18 @@ static int osmo_gsup_server_ccm_cb(struct ipa_server_conn *conn,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
gsup_route_add(clnt, addr, addr_len);
|
||||
global_title_set(&clnt->peer_name, addr, addr_len);
|
||||
gsup_route_add_gt(clnt, &clnt->peer_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void osmo_gsup_conn_free(struct osmo_gsup_conn *conn)
|
||||
{
|
||||
gsup_route_del_conn(conn);
|
||||
llist_del(&conn->list);
|
||||
talloc_free(conn);
|
||||
}
|
||||
|
||||
static int osmo_gsup_server_closed_cb(struct ipa_server_conn *conn)
|
||||
{
|
||||
struct osmo_gsup_conn *clnt = (struct osmo_gsup_conn *)conn->data;
|
||||
@@ -204,10 +446,7 @@ static int osmo_gsup_server_closed_cb(struct ipa_server_conn *conn)
|
||||
LOGP(DLGSUP, LOGL_INFO, "Lost GSUP client %s:%d\n",
|
||||
conn->addr, conn->port);
|
||||
|
||||
gsup_route_del_conn(clnt);
|
||||
llist_del(&clnt->list);
|
||||
talloc_free(clnt);
|
||||
|
||||
osmo_gsup_conn_free(clnt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -289,8 +528,7 @@ failed:
|
||||
|
||||
struct osmo_gsup_server *
|
||||
osmo_gsup_server_create(void *ctx, const char *ip_addr, uint16_t tcp_port,
|
||||
osmo_gsup_read_cb_t read_cb,
|
||||
struct llist_head *lu_op_lst)
|
||||
osmo_gsup_read_cb_t read_cb, void *priv)
|
||||
{
|
||||
struct osmo_gsup_server *gsups;
|
||||
int rc;
|
||||
@@ -310,13 +548,12 @@ osmo_gsup_server_create(void *ctx, const char *ip_addr, uint16_t tcp_port,
|
||||
goto failed;
|
||||
|
||||
gsups->read_cb = read_cb;
|
||||
gsups->priv = priv;
|
||||
|
||||
rc = ipa_server_link_open(gsups->link);
|
||||
if (rc < 0)
|
||||
goto failed;
|
||||
|
||||
gsups->luop = lu_op_lst;
|
||||
|
||||
return gsups;
|
||||
|
||||
failed:
|
||||
@@ -333,3 +570,82 @@ void osmo_gsup_server_destroy(struct osmo_gsup_server *gsups)
|
||||
}
|
||||
talloc_free(gsups);
|
||||
}
|
||||
|
||||
/* Set GSUP message's pdp_infos[0] to a wildcard APN.
|
||||
* Use the provided apn_buf to store the produced APN data. This must remain valid until
|
||||
* osmo_gsup_encode() is done. Return 0 if an entry was added, -ENOMEM if the provided buffer is too
|
||||
* small. */
|
||||
int osmo_gsup_configure_wildcard_apn(struct osmo_gsup_message *gsup,
|
||||
uint8_t *apn_buf, size_t apn_buf_size)
|
||||
{
|
||||
int l;
|
||||
|
||||
l = osmo_apn_from_str(apn_buf, apn_buf_size, "*");
|
||||
if (l <= 0)
|
||||
return -ENOMEM;
|
||||
|
||||
gsup->pdp_infos[0].apn_enc = apn_buf;
|
||||
gsup->pdp_infos[0].apn_enc_len = l;
|
||||
gsup->pdp_infos[0].have_info = 1;
|
||||
gsup->num_pdp_infos = 1;
|
||||
/* FIXME: use real value: */
|
||||
gsup->pdp_infos[0].context_id = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Populate a gsup message structure with an Insert Subscriber Data Message.
|
||||
* All required memory buffers for data pointed to by pointers in struct omso_gsup_message
|
||||
* must be allocated by the caller and should have the same lifetime as the gsup parameter.
|
||||
*
|
||||
* \param[out] gsup The gsup message to populate.
|
||||
* \param[in] imsi The subscriber's IMSI.
|
||||
* \param[in] msisdn The subscriber's MSISDN.
|
||||
* \param[out] msisdn_enc A buffer large enough to store the MSISDN in encoded form.
|
||||
* \param[in] msisdn_enc_size Size of the buffer (must be >= OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN).
|
||||
* \param[out] apn_buf A buffer large enough to store an APN (required if cn_domain is OSMO_GSUP_CN_DOMAIN_PS).
|
||||
* \param[in] apn_buf_size Size of APN buffer (must be >= APN_MAXLEN).
|
||||
* \param[in] cn_domain The CN Domain of the subscriber connection.
|
||||
* \returns 0 on success, and negative on error.
|
||||
*/
|
||||
int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup, const char *imsi, const char *msisdn,
|
||||
uint8_t *msisdn_enc, size_t msisdn_enc_size,
|
||||
uint8_t *apn_buf, size_t apn_buf_size,
|
||||
enum osmo_gsup_cn_domain cn_domain)
|
||||
{
|
||||
int len;
|
||||
|
||||
OSMO_ASSERT(gsup);
|
||||
*gsup = (struct osmo_gsup_message){
|
||||
.message_type = OSMO_GSUP_MSGT_INSERT_DATA_REQUEST,
|
||||
};
|
||||
|
||||
osmo_strlcpy(gsup->imsi, imsi, sizeof(gsup->imsi));
|
||||
|
||||
if (msisdn_enc_size < OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN)
|
||||
return -ENOSPC;
|
||||
|
||||
OSMO_ASSERT(msisdn_enc);
|
||||
len = gsm48_encode_bcd_number(msisdn_enc, msisdn_enc_size, 0, msisdn);
|
||||
if (len < 1) {
|
||||
LOGP(DLGSUP, LOGL_ERROR, "%s: Error: cannot encode MSISDN '%s'\n", imsi, msisdn);
|
||||
return -ENOSPC;
|
||||
}
|
||||
gsup->msisdn_enc = msisdn_enc;
|
||||
gsup->msisdn_enc_len = len;
|
||||
|
||||
#pragma message "FIXME: deal with encoding the following data: gsup.hlr_enc"
|
||||
|
||||
gsup->cn_domain = cn_domain;
|
||||
if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS) {
|
||||
OSMO_ASSERT(apn_buf_size >= APN_MAXLEN);
|
||||
OSMO_ASSERT(apn_buf);
|
||||
/* FIXME: PDP infos - use more fine-grained access control
|
||||
instead of wildcard APN */
|
||||
osmo_gsup_configure_wildcard_apn(gsup, apn_buf, apn_buf_size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,41 @@
|
||||
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/abis/ipa.h>
|
||||
#include <osmocom/abis/ipaccess.h>
|
||||
#include <osmocom/gsm/gsup.h>
|
||||
#include "global_title.h"
|
||||
|
||||
#ifndef OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN
|
||||
#define OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN 43 /* TS 24.008 10.5.4.7 */
|
||||
#endif
|
||||
|
||||
#define LOG_GSUP_CONN(conn, level, fmt, args...) \
|
||||
LOGP(DLGSUP, level, "GSUP from %s: " fmt, \
|
||||
conn && conn->server && conn->server->link ? \
|
||||
osmo_sock_get_name2_c(OTC_SELECT, conn->server->link->ofd.fd) \
|
||||
: "?", \
|
||||
##args)
|
||||
|
||||
#if 0
|
||||
#define LOG_GSUP_CONN_MSG(conn, gsup_msg, level, fmt, args...) \
|
||||
LOG_GSUP_CONN(conn, level, "IMSI-%s %s: " fmt, (gsup_msg)->imsi, \
|
||||
osmo_gsup_message_type_name((gsup_msg)->message_type), \
|
||||
##args)
|
||||
|
||||
#endif
|
||||
|
||||
#define LOG_GSUP_REQ_CAT(req, subsys, level, fmt, args...) \
|
||||
LOGP(subsys, level, "GSUP %u: %s: IMSI-%s %s: " fmt, \
|
||||
(req) ? (req)->nr : 0, \
|
||||
(req) ? global_title_name(&(req)->source_name) : "NULL", \
|
||||
(req) ? (req)->gsup.imsi : "NULL", \
|
||||
(req) ? osmo_gsup_message_type_name((req)->gsup.message_type) : "NULL", \
|
||||
##args)
|
||||
|
||||
#define LOG_GSUP_REQ(req, level, fmt, args...) \
|
||||
LOG_GSUP_REQ_CAT(req, DLGSUP, level, fmt, ##args)
|
||||
|
||||
struct osmo_gsup_conn;
|
||||
|
||||
@@ -11,12 +44,12 @@ struct osmo_gsup_conn;
|
||||
typedef int (*osmo_gsup_read_cb_t)(struct osmo_gsup_conn *conn, struct msgb *msg);
|
||||
|
||||
struct osmo_gsup_server {
|
||||
/* private data of the application/user */
|
||||
void *priv;
|
||||
|
||||
/* list of osmo_gsup_conn */
|
||||
struct llist_head clients;
|
||||
|
||||
/* lu_operations list */
|
||||
struct llist_head *luop;
|
||||
|
||||
struct ipa_server_link *link;
|
||||
osmo_gsup_read_cb_t read_cb;
|
||||
struct llist_head routes;
|
||||
@@ -33,10 +66,61 @@ struct osmo_gsup_conn {
|
||||
struct tlv_parsed ccm;
|
||||
|
||||
unsigned int auc_3g_ind; /*!< IND index used for UMTS AKA SQN */
|
||||
|
||||
/* Set when Location Update is received: */
|
||||
bool supports_cs; /* client supports OSMO_GSUP_CN_DOMAIN_CS */
|
||||
bool supports_ps; /* client supports OSMO_GSUP_CN_DOMAIN_PS */
|
||||
|
||||
/* The IPA unit name received on this link. Routes with more unit names serviced by this link may exist in
|
||||
* osmo_gsup_server->routes, but this is the name the immediate peer identified as in the IPA handshake. */
|
||||
struct global_title peer_name;
|
||||
};
|
||||
|
||||
/* Keep track of an incoming request to which osmo-hlr composes (or routes back) a response.
|
||||
* Particularly, if a request contained a source_name, we need to add this as destination_name in the response for any
|
||||
* intermediate GSUP proxies to be able to route back to the initial requestor. */
|
||||
struct osmo_gsup_req {
|
||||
struct osmo_gsup_server *server;
|
||||
/* Identify this request by number, for logging. */
|
||||
unsigned int nr;
|
||||
|
||||
/* A decoded GSUP message still points into the received msgb. For a decoded osmo_gsup_message to remain valid,
|
||||
* we also need to keep the msgb. */
|
||||
struct msgb *msg;
|
||||
/* Decoded msg. */
|
||||
int decode_rc;
|
||||
const struct osmo_gsup_message gsup;
|
||||
/* The ultimate source of this message: the source_name form the GSUP message, or, if not present, then the
|
||||
* immediate GSUP peer. GSUP messages going via a proxy reflect the initial source in the source_name.
|
||||
* This source_name is implicitly added to the routes for the conn the message was received on. */
|
||||
struct global_title source_name;
|
||||
/* If the source_name is not an immediate GSUP peer, this is set to the closest intermediate peer between here
|
||||
* and source_name. */
|
||||
struct global_title via_proxy;
|
||||
};
|
||||
|
||||
struct osmo_gsup_req *osmo_gsup_req_new(struct osmo_gsup_conn *conn, struct msgb *msg);
|
||||
#define osmo_gsup_req_free(REQ) do { \
|
||||
LOG_GSUP_REQ(REQ, LOGL_DEBUG, "free\n"); \
|
||||
_osmo_gsup_req_free(REQ); \
|
||||
} while(0)
|
||||
void _osmo_gsup_req_free(struct osmo_gsup_req *req);
|
||||
|
||||
int osmo_gsup_req_respond(struct osmo_gsup_req *req, struct osmo_gsup_message *response);
|
||||
int osmo_gsup_req_respond_nonfinal(struct osmo_gsup_req *req, struct osmo_gsup_message *response);
|
||||
int osmo_gsup_req_respond_msgt(struct osmo_gsup_req *req, enum osmo_gsup_message_type message_type);
|
||||
|
||||
#define osmo_gsup_req_respond_err(REQ, CAUSE, FMT, args...) do { \
|
||||
LOG_GSUP_REQ(REQ, LOGL_ERROR, "%s: " FMT "\n", \
|
||||
get_value_string(gsm48_gmm_cause_names, CAUSE), ##args); \
|
||||
_osmo_gsup_req_respond_err(REQ, CAUSE); \
|
||||
} while(0)
|
||||
void _osmo_gsup_req_respond_err(struct osmo_gsup_req *req, enum gsm48_gmm_cause cause);
|
||||
|
||||
struct msgb *osmo_gsup_msgb_alloc(const char *label);
|
||||
|
||||
int osmo_gsup_conn_send(struct osmo_gsup_conn *conn, struct msgb *msg);
|
||||
int osmo_gsup_conn_enc_send(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);
|
||||
int osmo_gsup_conn_ccm_get(const struct osmo_gsup_conn *clnt, uint8_t **addr,
|
||||
uint8_t tag);
|
||||
|
||||
@@ -44,7 +128,13 @@ struct osmo_gsup_server *osmo_gsup_server_create(void *ctx,
|
||||
const char *ip_addr,
|
||||
uint16_t tcp_port,
|
||||
osmo_gsup_read_cb_t read_cb,
|
||||
struct llist_head *lu_op_lst);
|
||||
void *priv);
|
||||
|
||||
void osmo_gsup_server_destroy(struct osmo_gsup_server *gsups);
|
||||
|
||||
int osmo_gsup_configure_wildcard_apn(struct osmo_gsup_message *gsup,
|
||||
uint8_t *apn_buf, size_t apn_buf_size);
|
||||
int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup, const char *imsi, const char *msisdn,
|
||||
uint8_t *msisdn_enc, size_t msisdn_enc_size,
|
||||
uint8_t *apn_buf, size_t apn_buf_size,
|
||||
enum osmo_gsup_cn_domain cn_domain);
|
||||
|
||||
20
src/gsupclient/Makefile.am
Normal file
20
src/gsupclient/Makefile.am
Normal file
@@ -0,0 +1,20 @@
|
||||
# This is _NOT_ the library release version, it's an API version.
|
||||
# Please read chapter "Library interface versions" of the libtool documentation
|
||||
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
|
||||
LIBVERSION=0:0:0
|
||||
|
||||
AM_CFLAGS = -Wall $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include \
|
||||
$(TALLOC_CFLAGS) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOABIS_CFLAGS)
|
||||
|
||||
lib_LTLIBRARIES = libosmo-gsup-client.la
|
||||
|
||||
libosmo_gsup_client_la_SOURCES = gsup_client.c
|
||||
|
||||
libosmo_gsup_client_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
|
||||
libosmo_gsup_client_la_LIBADD = $(TALLOC_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOABIS_LIBS)
|
||||
|
||||
noinst_PROGRAMS = gsup-test-client
|
||||
|
||||
gsup_test_client_SOURCES = gsup_test_client.c
|
||||
gsup_test_client_LDADD = $(TALLOC_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) \
|
||||
libosmo-gsup-client.la
|
||||
434
src/gsupclient/gsup_client.c
Normal file
434
src/gsupclient/gsup_client.c
Normal file
@@ -0,0 +1,434 @@
|
||||
/* Generic Subscriber Update Protocol client */
|
||||
|
||||
/* (C) 2014-2016 by Sysmocom s.f.m.c. GmbH
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Jacob Erlbeck
|
||||
* Author: Neels Hofmeyr
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <osmocom/gsupclient/gsup_client.h>
|
||||
|
||||
#include <osmocom/abis/ipa.h>
|
||||
#include <osmocom/gsm/oap_client.h>
|
||||
#include <osmocom/gsm/protocol/ipaccess.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
static void start_test_procedure(struct osmo_gsup_client *gsupc);
|
||||
|
||||
static void gsup_client_send_ping(struct osmo_gsup_client *gsupc)
|
||||
{
|
||||
struct msgb *msg = osmo_gsup_client_msgb_alloc();
|
||||
|
||||
msg->l2h = msgb_put(msg, 1);
|
||||
msg->l2h[0] = IPAC_MSGT_PING;
|
||||
ipa_msg_push_header(msg, IPAC_PROTO_IPACCESS);
|
||||
ipa_client_conn_send(gsupc->link, msg);
|
||||
}
|
||||
|
||||
static int gsup_client_connect(struct osmo_gsup_client *gsupc)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (gsupc->is_connected)
|
||||
return 0;
|
||||
|
||||
if (osmo_timer_pending(&gsupc->connect_timer)) {
|
||||
LOGP(DLGSUP, LOGL_DEBUG,
|
||||
"GSUP connect: connect timer already running\n");
|
||||
osmo_timer_del(&gsupc->connect_timer);
|
||||
}
|
||||
|
||||
if (osmo_timer_pending(&gsupc->ping_timer)) {
|
||||
LOGP(DLGSUP, LOGL_DEBUG,
|
||||
"GSUP connect: ping timer already running\n");
|
||||
osmo_timer_del(&gsupc->ping_timer);
|
||||
}
|
||||
|
||||
if (ipa_client_conn_clear_queue(gsupc->link) > 0)
|
||||
LOGP(DLGSUP, LOGL_DEBUG, "GSUP connect: discarded stored messages\n");
|
||||
|
||||
rc = ipa_client_conn_open(gsupc->link);
|
||||
|
||||
if (rc >= 0) {
|
||||
LOGP(DLGSUP, LOGL_NOTICE, "GSUP connecting to %s:%d\n",
|
||||
gsupc->link->addr, gsupc->link->port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOGP(DLGSUP, LOGL_ERROR, "GSUP failed to connect to %s:%d: %s\n",
|
||||
gsupc->link->addr, gsupc->link->port, strerror(-rc));
|
||||
|
||||
if (rc == -EBADF || rc == -ENOTSOCK || rc == -EAFNOSUPPORT ||
|
||||
rc == -EINVAL)
|
||||
return rc;
|
||||
|
||||
osmo_timer_schedule(&gsupc->connect_timer,
|
||||
OSMO_GSUP_CLIENT_RECONNECT_INTERVAL, 0);
|
||||
|
||||
LOGP(DLGSUP, LOGL_INFO, "Scheduled timer to retry GSUP connect to %s:%d\n",
|
||||
gsupc->link->addr, gsupc->link->port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void connect_timer_cb(void *gsupc_)
|
||||
{
|
||||
struct osmo_gsup_client *gsupc = gsupc_;
|
||||
|
||||
if (gsupc->is_connected)
|
||||
return;
|
||||
|
||||
if (gsupc->up_down_cb) {
|
||||
/* When the up_down_cb() returns false, the user asks us not to retry connecting. */
|
||||
if (!gsupc->up_down_cb(gsupc, false))
|
||||
return;
|
||||
}
|
||||
|
||||
gsup_client_connect(gsupc);
|
||||
|
||||
}
|
||||
|
||||
static void client_send(struct osmo_gsup_client *gsupc, int proto_ext,
|
||||
struct msgb *msg_tx)
|
||||
{
|
||||
ipa_prepend_header_ext(msg_tx, proto_ext);
|
||||
ipa_msg_push_header(msg_tx, IPAC_PROTO_OSMO);
|
||||
ipa_client_conn_send(gsupc->link, msg_tx);
|
||||
/* msg_tx is now queued and will be freed. */
|
||||
}
|
||||
|
||||
static void gsup_client_oap_register(struct osmo_gsup_client *gsupc)
|
||||
{
|
||||
struct msgb *msg_tx;
|
||||
int rc;
|
||||
rc = osmo_oap_client_register(&gsupc->oap_state, &msg_tx);
|
||||
|
||||
if ((rc < 0) || (!msg_tx)) {
|
||||
LOGP(DLGSUP, LOGL_ERROR, "GSUP OAP set up, but cannot register.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
client_send(gsupc, IPAC_PROTO_EXT_OAP, msg_tx);
|
||||
}
|
||||
|
||||
static void gsup_client_updown_cb(struct ipa_client_conn *link, int up)
|
||||
{
|
||||
struct osmo_gsup_client *gsupc = link->data;
|
||||
|
||||
LOGP(DLGSUP, LOGL_INFO, "GSUP link to %s:%d %s\n",
|
||||
link->addr, link->port, up ? "UP" : "DOWN");
|
||||
|
||||
gsupc->is_connected = up;
|
||||
|
||||
if (up) {
|
||||
start_test_procedure(gsupc);
|
||||
|
||||
if (gsupc->oap_state.state == OSMO_OAP_INITIALIZED)
|
||||
gsup_client_oap_register(gsupc);
|
||||
|
||||
osmo_timer_del(&gsupc->connect_timer);
|
||||
|
||||
if (gsupc->up_down_cb)
|
||||
gsupc->up_down_cb(gsupc, true);
|
||||
} else {
|
||||
osmo_timer_del(&gsupc->ping_timer);
|
||||
|
||||
if (gsupc->up_down_cb) {
|
||||
/* When the up_down_cb() returns false, the user asks us not to retry connecting. */
|
||||
if (!gsupc->up_down_cb(gsupc, false))
|
||||
return;
|
||||
}
|
||||
|
||||
osmo_timer_schedule(&gsupc->connect_timer,
|
||||
OSMO_GSUP_CLIENT_RECONNECT_INTERVAL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static int gsup_client_oap_handle(struct osmo_gsup_client *gsupc, struct msgb *msg_rx)
|
||||
{
|
||||
int rc;
|
||||
struct msgb *msg_tx;
|
||||
|
||||
/* If the oap_state is disabled, this will reject the messages. */
|
||||
rc = osmo_oap_client_handle(&gsupc->oap_state, msg_rx, &msg_tx);
|
||||
msgb_free(msg_rx);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
if (msg_tx)
|
||||
client_send(gsupc, IPAC_PROTO_EXT_OAP, msg_tx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gsup_client_read_cb(struct ipa_client_conn *link, struct msgb *msg)
|
||||
{
|
||||
struct ipaccess_head *hh = (struct ipaccess_head *) msg->data;
|
||||
struct ipaccess_head_ext *he = (struct ipaccess_head_ext *) msgb_l2(msg);
|
||||
struct osmo_gsup_client *gsupc = (struct osmo_gsup_client *)link->data;
|
||||
int rc;
|
||||
|
||||
OSMO_ASSERT(gsupc->unit_name);
|
||||
|
||||
msg->l2h = &hh->data[0];
|
||||
|
||||
rc = ipaccess_bts_handle_ccm(link, gsupc->ipa_dev, msg);
|
||||
|
||||
if (rc < 0) {
|
||||
LOGP(DLGSUP, LOGL_NOTICE,
|
||||
"GSUP received an invalid IPA/CCM message from %s:%d\n",
|
||||
link->addr, link->port);
|
||||
/* Link has been closed */
|
||||
gsupc->is_connected = 0;
|
||||
msgb_free(msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rc == 1) {
|
||||
uint8_t msg_type = *(msg->l2h);
|
||||
/* CCM message */
|
||||
if (msg_type == IPAC_MSGT_PONG) {
|
||||
LOGP(DLGSUP, LOGL_DEBUG, "GSUP receiving PONG\n");
|
||||
gsupc->got_ipa_pong = 1;
|
||||
}
|
||||
|
||||
msgb_free(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (hh->proto != IPAC_PROTO_OSMO)
|
||||
goto invalid;
|
||||
|
||||
if (!he || msgb_l2len(msg) < sizeof(*he))
|
||||
goto invalid;
|
||||
|
||||
msg->l2h = &he->data[0];
|
||||
|
||||
if (he->proto == IPAC_PROTO_EXT_GSUP) {
|
||||
OSMO_ASSERT(gsupc->read_cb != NULL);
|
||||
gsupc->read_cb(gsupc, msg);
|
||||
/* expecting read_cb() to free msg */
|
||||
} else if (he->proto == IPAC_PROTO_EXT_OAP) {
|
||||
return gsup_client_oap_handle(gsupc, msg);
|
||||
/* gsup_client_oap_handle frees msg */
|
||||
} else
|
||||
goto invalid;
|
||||
|
||||
return 0;
|
||||
|
||||
invalid:
|
||||
LOGP(DLGSUP, LOGL_NOTICE,
|
||||
"GSUP received an invalid IPA message from %s:%d, size = %d\n",
|
||||
link->addr, link->port, msgb_length(msg));
|
||||
|
||||
msgb_free(msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ping_timer_cb(void *gsupc_)
|
||||
{
|
||||
struct osmo_gsup_client *gsupc = gsupc_;
|
||||
|
||||
LOGP(DLGSUP, LOGL_INFO, "GSUP ping callback (%s, %s PONG)\n",
|
||||
gsupc->is_connected ? "connected" : "not connected",
|
||||
gsupc->got_ipa_pong ? "got" : "didn't get");
|
||||
|
||||
if (gsupc->got_ipa_pong) {
|
||||
start_test_procedure(gsupc);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGP(DLGSUP, LOGL_NOTICE, "GSUP ping timed out, reconnecting\n");
|
||||
ipa_client_conn_close(gsupc->link);
|
||||
gsupc->is_connected = 0;
|
||||
|
||||
gsup_client_connect(gsupc);
|
||||
}
|
||||
|
||||
static void start_test_procedure(struct osmo_gsup_client *gsupc)
|
||||
{
|
||||
osmo_timer_setup(&gsupc->ping_timer, ping_timer_cb, gsupc);
|
||||
|
||||
gsupc->got_ipa_pong = 0;
|
||||
osmo_timer_schedule(&gsupc->ping_timer, OSMO_GSUP_CLIENT_PING_INTERVAL, 0);
|
||||
LOGP(DLGSUP, LOGL_DEBUG, "GSUP sending PING\n");
|
||||
gsup_client_send_ping(gsupc);
|
||||
}
|
||||
|
||||
struct osmo_gsup_client *osmo_gsup_client_create3(void *talloc_ctx,
|
||||
struct ipaccess_unit *ipa_dev,
|
||||
const char *ip_addr,
|
||||
unsigned int tcp_port,
|
||||
struct osmo_oap_client_config *oapc_config,
|
||||
osmo_gsup_client_read_cb_t read_cb,
|
||||
osmo_gsup_client_up_down_cb_t up_down_cb,
|
||||
void *data)
|
||||
{
|
||||
struct osmo_gsup_client *gsupc;
|
||||
int rc;
|
||||
|
||||
OSMO_ASSERT(ipa_dev->unit_name);
|
||||
|
||||
gsupc = talloc_zero(talloc_ctx, struct osmo_gsup_client);
|
||||
OSMO_ASSERT(gsupc);
|
||||
*gsupc = (struct osmo_gsup_client){
|
||||
.unit_name = (const char *)ipa_dev->unit_name, /* API backwards compat */
|
||||
.ipa_dev = ipa_dev,
|
||||
.read_cb = read_cb,
|
||||
.up_down_cb = up_down_cb,
|
||||
.data = data,
|
||||
};
|
||||
|
||||
/* a NULL oapc_config will mark oap_state disabled. */
|
||||
rc = osmo_oap_client_init(oapc_config, &gsupc->oap_state);
|
||||
if (rc != 0)
|
||||
goto failed;
|
||||
|
||||
gsupc->link = ipa_client_conn_create(gsupc,
|
||||
/* no e1inp */ NULL,
|
||||
0,
|
||||
ip_addr, tcp_port,
|
||||
gsup_client_updown_cb,
|
||||
gsup_client_read_cb,
|
||||
/* default write_cb */ NULL,
|
||||
gsupc);
|
||||
if (!gsupc->link)
|
||||
goto failed;
|
||||
|
||||
osmo_timer_setup(&gsupc->connect_timer, connect_timer_cb, gsupc);
|
||||
rc = gsup_client_connect(gsupc);
|
||||
if (rc < 0)
|
||||
goto failed;
|
||||
return gsupc;
|
||||
|
||||
failed:
|
||||
osmo_gsup_client_destroy(gsupc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Create a gsup client connecting to the specified IP address and TCP port.
|
||||
* Use the provided ipaccess unit as the client-side identifier; ipa_dev should
|
||||
* be allocated in talloc_ctx talloc_ctx as well.
|
||||
* \param[in] talloc_ctx talloc context.
|
||||
* \param[in] ipa_dev IP access unit which contains client identification information; must be allocated
|
||||
* in talloc_ctx as well to ensure it lives throughout the lifetime of the connection.
|
||||
* \param[in] ip_addr GSUP server IP address.
|
||||
* \param[in] tcp_port GSUP server TCP port.
|
||||
* \param[in] read_cb callback for reading from the GSUP connection.
|
||||
* \param[in] oapc_config OPA client configuration.
|
||||
* \returns a GSUP client connection or NULL on failure.
|
||||
*/
|
||||
struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx,
|
||||
struct ipaccess_unit *ipa_dev,
|
||||
const char *ip_addr,
|
||||
unsigned int tcp_port,
|
||||
osmo_gsup_client_read_cb_t read_cb,
|
||||
struct osmo_oap_client_config *oapc_config)
|
||||
{
|
||||
return osmo_gsup_client_create3(talloc_ctx, ipa_dev, ip_addr, tcp_port, oapc_config,
|
||||
read_cb, NULL, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like osmo_gsup_client_create2() except it expects a unit name instead
|
||||
* of a full-blown ipacess_unit as the client-side identifier.
|
||||
*/
|
||||
struct osmo_gsup_client *osmo_gsup_client_create(void *talloc_ctx,
|
||||
const char *unit_name,
|
||||
const char *ip_addr,
|
||||
unsigned int tcp_port,
|
||||
osmo_gsup_client_read_cb_t read_cb,
|
||||
struct osmo_oap_client_config *oapc_config)
|
||||
{
|
||||
struct ipaccess_unit *ipa_dev = talloc_zero(talloc_ctx, struct ipaccess_unit);
|
||||
ipa_dev->unit_name = talloc_strdup(ipa_dev, unit_name);
|
||||
return osmo_gsup_client_create2(talloc_ctx, ipa_dev, ip_addr, tcp_port, read_cb, oapc_config);
|
||||
}
|
||||
|
||||
void osmo_gsup_client_destroy(struct osmo_gsup_client *gsupc)
|
||||
{
|
||||
osmo_timer_del(&gsupc->connect_timer);
|
||||
osmo_timer_del(&gsupc->ping_timer);
|
||||
|
||||
if (gsupc->link) {
|
||||
ipa_client_conn_close(gsupc->link);
|
||||
ipa_client_conn_destroy(gsupc->link);
|
||||
gsupc->link = NULL;
|
||||
}
|
||||
talloc_free(gsupc);
|
||||
}
|
||||
|
||||
int osmo_gsup_client_send(struct osmo_gsup_client *gsupc, struct msgb *msg)
|
||||
{
|
||||
if (!gsupc || !gsupc->is_connected) {
|
||||
LOGP(DLGSUP, LOGL_ERROR, "GSUP not connected, unable to send %s\n", msgb_hexdump(msg));
|
||||
msgb_free(msg);
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
client_send(gsupc, IPAC_PROTO_EXT_GSUP, msg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Encode and send a GSUP message.
|
||||
* \param[in] gsupc GSUP client.
|
||||
* \param[in] gsup_msg GSUP message to be sent.
|
||||
* \returns 0 in case of success, negative on error.
|
||||
*/
|
||||
int osmo_gsup_client_enc_send(struct osmo_gsup_client *gsupc,
|
||||
const struct osmo_gsup_message *gsup_msg)
|
||||
{
|
||||
struct msgb *gsup_msgb;
|
||||
int rc;
|
||||
|
||||
gsup_msgb = osmo_gsup_client_msgb_alloc();
|
||||
if (!gsup_msgb) {
|
||||
LOGP(DLGSUP, LOGL_ERROR, "Couldn't allocate GSUP message\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rc = osmo_gsup_encode(gsup_msgb, gsup_msg);
|
||||
if (rc) {
|
||||
LOGP(DLGSUP, LOGL_ERROR, "Couldn't encode GSUP message\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = osmo_gsup_client_send(gsupc, gsup_msgb);
|
||||
if (rc) {
|
||||
LOGP(DLGSUP, LOGL_ERROR, "Couldn't send GSUP message\n");
|
||||
/* Do not free, osmo_gsup_client_send() already has. */
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
talloc_free(gsup_msgb);
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct msgb *osmo_gsup_client_msgb_alloc(void)
|
||||
{
|
||||
return msgb_alloc_headroom(4000, 64, __func__);
|
||||
}
|
||||
321
src/gsupclient/gsup_test_client.c
Normal file
321
src/gsupclient/gsup_test_client.c
Normal file
@@ -0,0 +1,321 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/application.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/gsm/gsup.h>
|
||||
|
||||
#include <osmocom/gsupclient/gsup_client.h>
|
||||
|
||||
static struct osmo_gsup_client *g_gc;
|
||||
|
||||
|
||||
/***********************************************************************
|
||||
* IMSI Operation
|
||||
***********************************************************************/
|
||||
static LLIST_HEAD(g_imsi_ops);
|
||||
|
||||
struct imsi_op_stats {
|
||||
uint32_t num_alloc;
|
||||
uint32_t num_released;
|
||||
uint32_t num_rx_success;
|
||||
uint32_t num_rx_error;
|
||||
uint32_t num_timeout;
|
||||
};
|
||||
|
||||
enum imsi_op_type {
|
||||
IMSI_OP_SAI,
|
||||
IMSI_OP_LU,
|
||||
IMSI_OP_ISD,
|
||||
_NUM_IMSI_OP
|
||||
};
|
||||
|
||||
static const struct value_string imsi_op_names[] = {
|
||||
{ IMSI_OP_SAI, "SAI" },
|
||||
{ IMSI_OP_LU, "LU" },
|
||||
{ IMSI_OP_ISD, "ISD" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static struct imsi_op_stats imsi_op_stats[_NUM_IMSI_OP];
|
||||
|
||||
struct imsi_op {
|
||||
struct llist_head list;
|
||||
char imsi[17];
|
||||
enum imsi_op_type type;
|
||||
struct osmo_timer_list timer;
|
||||
};
|
||||
|
||||
static struct imsi_op *imsi_op_find(const char *imsi,
|
||||
enum imsi_op_type type)
|
||||
{
|
||||
struct imsi_op *io;
|
||||
|
||||
llist_for_each_entry(io, &g_imsi_ops, list) {
|
||||
if (!strcmp(io->imsi, imsi) && io->type == type)
|
||||
return io;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void imsi_op_timer_cb(void *data);
|
||||
|
||||
static struct imsi_op *imsi_op_alloc(void *ctx, const char *imsi,
|
||||
enum imsi_op_type type)
|
||||
{
|
||||
struct imsi_op *io;
|
||||
|
||||
if (imsi_op_find(imsi, type))
|
||||
return NULL;
|
||||
|
||||
io = talloc_zero(ctx, struct imsi_op);
|
||||
OSMO_STRLCPY_ARRAY(io->imsi, imsi);
|
||||
io->type = type;
|
||||
osmo_timer_setup(&io->timer, imsi_op_timer_cb, io);
|
||||
llist_add(&io->list, &g_imsi_ops);
|
||||
imsi_op_stats[type].num_alloc++;
|
||||
|
||||
return io;
|
||||
}
|
||||
|
||||
static void imsi_op_release(struct imsi_op *io)
|
||||
{
|
||||
osmo_timer_del(&io->timer);
|
||||
llist_del(&io->list);
|
||||
imsi_op_stats[io->type].num_released++;
|
||||
talloc_free(io);
|
||||
}
|
||||
|
||||
static void imsi_op_timer_cb(void *data)
|
||||
{
|
||||
struct imsi_op *io = data;
|
||||
printf("%s: Timer expiration\n", io->imsi);
|
||||
imsi_op_stats[io->type].num_timeout++;
|
||||
imsi_op_release(io);
|
||||
}
|
||||
|
||||
/* allocate + generate + send Send-Auth-Info */
|
||||
static int req_auth_info(const char *imsi)
|
||||
{
|
||||
struct imsi_op *io = imsi_op_alloc(g_gc, imsi, IMSI_OP_SAI);
|
||||
struct osmo_gsup_message gsup = {0};
|
||||
struct msgb *msg = msgb_alloc_headroom(1200, 200, __func__);
|
||||
int rc;
|
||||
|
||||
OSMO_STRLCPY_ARRAY(gsup.imsi, io->imsi);
|
||||
gsup.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST;
|
||||
|
||||
rc = osmo_gsup_encode(msg, &gsup);
|
||||
if (rc < 0) {
|
||||
printf("%s: encoding failure (%s)\n", imsi, strerror(-rc));
|
||||
return rc;
|
||||
}
|
||||
|
||||
return osmo_gsup_client_send(g_gc, msg);
|
||||
}
|
||||
|
||||
/* allocate + generate + send Send-Auth-Info */
|
||||
static int req_loc_upd(const char *imsi)
|
||||
{
|
||||
struct imsi_op *io = imsi_op_alloc(g_gc, imsi, IMSI_OP_LU);
|
||||
struct osmo_gsup_message gsup = {0};
|
||||
struct msgb *msg = msgb_alloc_headroom(1200, 200, __func__);
|
||||
int rc;
|
||||
|
||||
OSMO_STRLCPY_ARRAY(gsup.imsi, io->imsi);
|
||||
gsup.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST;
|
||||
|
||||
rc = osmo_gsup_encode(msg, &gsup);
|
||||
if (rc < 0) {
|
||||
printf("%s: encoding failure (%s)\n", imsi, strerror(-rc));
|
||||
return rc;
|
||||
}
|
||||
|
||||
return osmo_gsup_client_send(g_gc, msg);
|
||||
}
|
||||
|
||||
static int resp_isd(struct imsi_op *io)
|
||||
{
|
||||
struct osmo_gsup_message gsup = {0};
|
||||
struct msgb *msg = msgb_alloc_headroom(1200, 200, __func__);
|
||||
int rc;
|
||||
|
||||
OSMO_STRLCPY_ARRAY(gsup.imsi, io->imsi);
|
||||
gsup.message_type = OSMO_GSUP_MSGT_INSERT_DATA_RESULT;
|
||||
|
||||
rc = osmo_gsup_encode(msg, &gsup);
|
||||
if (rc < 0) {
|
||||
printf("%s: encoding failure (%s)\n", io->imsi, strerror(-rc));
|
||||
return rc;
|
||||
}
|
||||
|
||||
imsi_op_release(io);
|
||||
|
||||
return osmo_gsup_client_send(g_gc, msg);
|
||||
}
|
||||
|
||||
/* receive an incoming GSUP message */
|
||||
static void imsi_op_rx_gsup(struct imsi_op *io, const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
int is_error = 0, rc;
|
||||
|
||||
if (OSMO_GSUP_IS_MSGT_ERROR(gsup->message_type)) {
|
||||
imsi_op_stats[io->type].num_rx_error++;
|
||||
is_error = 1;
|
||||
} else
|
||||
imsi_op_stats[io->type].num_rx_success++;
|
||||
|
||||
switch (io->type) {
|
||||
case IMSI_OP_SAI:
|
||||
printf("%s; SAI Response%s\n", io->imsi, is_error ? ": ERROR" : "");
|
||||
/* now that we have auth tuples, request LU */
|
||||
rc = req_loc_upd(io->imsi);
|
||||
if (rc < 0)
|
||||
printf("Failed to request Location Update for %s\n", io->imsi);
|
||||
imsi_op_release(io);
|
||||
break;
|
||||
case IMSI_OP_LU:
|
||||
printf("%s; LU Response%s\n", io->imsi, is_error ? ": ERROR" : "");
|
||||
imsi_op_release(io);
|
||||
break;
|
||||
case IMSI_OP_ISD:
|
||||
printf("%s; ISD Request%s\n", io->imsi, is_error ? ": ERROR" : "");
|
||||
rc = resp_isd(io);
|
||||
if (rc < 0)
|
||||
printf("Failed to insert subscriber data for %s\n", io->imsi);
|
||||
break;
|
||||
default:
|
||||
printf("%s: Unknown\n", io->imsi);
|
||||
imsi_op_release(io);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int op_type_by_gsup_msgt(enum osmo_gsup_message_type msg_type)
|
||||
{
|
||||
switch (msg_type) {
|
||||
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT:
|
||||
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR:
|
||||
return IMSI_OP_SAI;
|
||||
case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT:
|
||||
case OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR:
|
||||
return IMSI_OP_LU;
|
||||
case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST:
|
||||
return IMSI_OP_ISD;
|
||||
default:
|
||||
printf("Unknown GSUP msg_type %u\n", msg_type);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int gsupc_read_cb(struct osmo_gsup_client *gsupc, struct msgb *msg)
|
||||
{
|
||||
struct osmo_gsup_message gsup_msg = {0};
|
||||
struct imsi_op *io = NULL;
|
||||
int rc;
|
||||
|
||||
DEBUGP(DLGSUP, "Rx GSUP %s\n", msgb_hexdump(msg));
|
||||
|
||||
rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup_msg);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
if (!gsup_msg.imsi[0])
|
||||
return -1;
|
||||
|
||||
rc = op_type_by_gsup_msgt(gsup_msg.message_type);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
switch (rc) {
|
||||
case IMSI_OP_SAI:
|
||||
case IMSI_OP_LU:
|
||||
io = imsi_op_find(gsup_msg.imsi, rc);
|
||||
break;
|
||||
case IMSI_OP_ISD:
|
||||
/* ISD is an inbound transaction */
|
||||
io = imsi_op_alloc(g_gc, gsup_msg.imsi, IMSI_OP_ISD);
|
||||
break;
|
||||
}
|
||||
if (!io)
|
||||
return -1;
|
||||
|
||||
imsi_op_rx_gsup(io, &gsup_msg);
|
||||
msgb_free(msg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void print_report(void)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(imsi_op_stats); i++) {
|
||||
struct imsi_op_stats *st = &imsi_op_stats[i];
|
||||
const char *name = get_value_string(imsi_op_names, i);
|
||||
printf("%s: %u alloc, %u released, %u success, %u error , %u tout\n",
|
||||
name, st->num_alloc, st->num_released, st->num_rx_success,
|
||||
st->num_rx_error, st->num_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
static void sig_cb(int sig)
|
||||
{
|
||||
switch (sig) {
|
||||
case SIGINT:
|
||||
print_report();
|
||||
exit(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* default categories */
|
||||
static struct log_info_cat default_categories[] = {
|
||||
};
|
||||
|
||||
static const struct log_info gsup_test_client_log_info = {
|
||||
.cat = default_categories,
|
||||
.num_cat = ARRAY_SIZE(default_categories),
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
unsigned long long i;
|
||||
char *server_host = "127.0.0.1";
|
||||
uint16_t server_port = OSMO_GSUP_PORT;
|
||||
void *ctx = talloc_named_const(NULL, 0, "gsup_test_client");
|
||||
|
||||
osmo_init_logging2(ctx, &gsup_test_client_log_info);
|
||||
|
||||
g_gc = osmo_gsup_client_create(ctx, "GSUPTEST", server_host, server_port,
|
||||
gsupc_read_cb, NULL);
|
||||
|
||||
|
||||
signal(SIGINT, sig_cb);
|
||||
|
||||
for (i = 0; i < 10000; i++) {
|
||||
unsigned long long imsi = 901790000000000 + i;
|
||||
char imsi_buf[17] = { 0 };
|
||||
int rc;
|
||||
|
||||
snprintf(imsi_buf, sizeof(imsi_buf), "%015llu", imsi);
|
||||
rc = req_auth_info(imsi_buf);
|
||||
if (rc < 0)
|
||||
printf("Failed to request Auth Info for %s\n", imsi_buf);
|
||||
|
||||
osmo_select_main(0);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
osmo_select_main(0);
|
||||
}
|
||||
|
||||
print_report();
|
||||
exit(0);
|
||||
}
|
||||
757
src/hlr.c
757
src/hlr.c
@@ -23,16 +23,21 @@
|
||||
#include <getopt.h>
|
||||
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/stats.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/application.h>
|
||||
#include <osmocom/gsm/gsup.h>
|
||||
#include <osmocom/gsm/apn.h>
|
||||
#include <osmocom/gsm/gsm48_ie.h>
|
||||
#include <osmocom/vty/vty.h>
|
||||
#include <osmocom/vty/command.h>
|
||||
#include <osmocom/vty/telnet_interface.h>
|
||||
#include <osmocom/vty/ports.h>
|
||||
#include <osmocom/ctrl/control_vty.h>
|
||||
#include <osmocom/gsm/apn.h>
|
||||
#include <osmocom/gsm/gsm48_ie.h>
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
#include <osmocom/gsm/protocol/gsm_23_003.h>
|
||||
#include <osmocom/gsm/gsm23003.h>
|
||||
#include <osmocom/mslookup/mslookup_client.h>
|
||||
|
||||
#include "db.h"
|
||||
#include "hlr.h"
|
||||
@@ -41,257 +46,508 @@
|
||||
#include "gsup_server.h"
|
||||
#include "gsup_router.h"
|
||||
#include "rand.h"
|
||||
#include "luop.h"
|
||||
#include "hlr_vty.h"
|
||||
#include "hlr_ussd.h"
|
||||
#include "dgsm.h"
|
||||
#include "proxy.h"
|
||||
#include "global_title.h"
|
||||
#include "lu_fsm.h"
|
||||
|
||||
static struct hlr *g_hlr;
|
||||
struct hlr *g_hlr;
|
||||
static void *hlr_ctx = NULL;
|
||||
static int quit = 0;
|
||||
|
||||
#define RAN_TDEFS \
|
||||
|
||||
struct osmo_tdef g_hlr_tdefs[] = {
|
||||
/* 4222 is also the OSMO_GSUP_PORT */
|
||||
{ .T = -4222, .default_val = 30, .desc = "GSUP Update Location timeout" },
|
||||
{}
|
||||
};
|
||||
|
||||
/* Trigger 'Insert Subscriber Data' messages to all connected GSUP clients.
|
||||
*
|
||||
* \param[in] subscr A subscriber we have new data to send for.
|
||||
*/
|
||||
void
|
||||
osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr)
|
||||
{
|
||||
struct osmo_gsup_conn *co;
|
||||
|
||||
if (g_hlr->gs == NULL) {
|
||||
LOGP(DLGSUP, LOGL_DEBUG,
|
||||
"IMSI %s: NOT Notifying peers of subscriber data change,"
|
||||
" there is no GSUP server\n",
|
||||
subscr->imsi);
|
||||
return;
|
||||
}
|
||||
|
||||
/* FIXME: send only to current vlr_number and sgsn_number */
|
||||
|
||||
llist_for_each_entry(co, &g_hlr->gs->clients, list) {
|
||||
struct osmo_gsup_message gsup = { };
|
||||
uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
|
||||
uint8_t apn[APN_MAXLEN];
|
||||
struct msgb *msg_out;
|
||||
uint8_t *peer;
|
||||
int peer_len;
|
||||
size_t peer_strlen;
|
||||
const char *peer_compare;
|
||||
enum osmo_gsup_cn_domain cn_domain;
|
||||
|
||||
if (co->supports_ps) {
|
||||
cn_domain = OSMO_GSUP_CN_DOMAIN_PS;
|
||||
peer_compare = subscr->sgsn_number;
|
||||
} else if (co->supports_cs) {
|
||||
cn_domain = OSMO_GSUP_CN_DOMAIN_CS;
|
||||
peer_compare = subscr->vlr_number;
|
||||
} else {
|
||||
/* We have not yet received a location update from this GSUP client.*/
|
||||
continue;
|
||||
}
|
||||
|
||||
peer_len = osmo_gsup_conn_ccm_get(co, &peer, IPAC_IDTAG_SERNR);
|
||||
if (peer_len < 0) {
|
||||
LOGP(DLGSUP, LOGL_ERROR,
|
||||
"IMSI='%s': cannot get peer name for connection %s:%u\n", subscr->imsi,
|
||||
co && co->conn && co->conn->server? co->conn->server->addr : "unset",
|
||||
co && co->conn && co->conn->server? co->conn->server->port : 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
peer_strlen = strnlen((const char*)peer, peer_len);
|
||||
if (strlen(peer_compare) != peer_strlen || strncmp(peer_compare, (const char *)peer, peer_len)) {
|
||||
/* Mismatch. The subscriber is not subscribed with this GSUP client. */
|
||||
/* I hope peer is always nul terminated... */
|
||||
if (peer_strlen < peer_len)
|
||||
LOGP(DLGSUP, LOGL_DEBUG,
|
||||
"IMSI %s: subscriber change: skipping %s peer %s\n",
|
||||
subscr->imsi, cn_domain == OSMO_GSUP_CN_DOMAIN_PS ? "PS" : "CS",
|
||||
osmo_quote_str((char*)peer, -1));
|
||||
continue;
|
||||
}
|
||||
|
||||
LOGP(DLGSUP, LOGL_DEBUG,
|
||||
"IMSI %s: subscriber change: notifying %s peer %s\n",
|
||||
subscr->imsi, cn_domain == OSMO_GSUP_CN_DOMAIN_PS ? "PS" : "CS",
|
||||
osmo_quote_str(peer_compare, -1));
|
||||
|
||||
if (osmo_gsup_create_insert_subscriber_data_msg(&gsup, subscr->imsi, subscr->msisdn, msisdn_enc,
|
||||
sizeof(msisdn_enc), apn, sizeof(apn), cn_domain) != 0) {
|
||||
LOGP(DLGSUP, LOGL_ERROR,
|
||||
"IMSI='%s': Cannot notify GSUP client; could not create gsup message "
|
||||
"for %s:%u\n", subscr->imsi,
|
||||
co && co->conn && co->conn->server? co->conn->server->addr : "unset",
|
||||
co && co->conn && co->conn->server? co->conn->server->port : 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Send ISD to MSC/SGSN */
|
||||
msg_out = osmo_gsup_msgb_alloc("GSUP ISD UPDATE");
|
||||
if (msg_out == NULL) {
|
||||
LOGP(DLGSUP, LOGL_ERROR,
|
||||
"IMSI='%s': Cannot notify GSUP client; could not allocate msg buffer "
|
||||
"for %s:%u\n", subscr->imsi,
|
||||
co && co->conn && co->conn->server? co->conn->server->addr : "unset",
|
||||
co && co->conn && co->conn->server? co->conn->server->port : 0);
|
||||
continue;
|
||||
}
|
||||
osmo_gsup_encode(msg_out, &gsup);
|
||||
|
||||
if (osmo_gsup_addr_send(g_hlr->gs, peer, peer_len, msg_out) < 0) {
|
||||
LOGP(DMAIN, LOGL_ERROR,
|
||||
"IMSI='%s': Cannot notify GSUP client; send operation failed "
|
||||
"for %s:%u\n", subscr->imsi,
|
||||
co && co->conn && co->conn->server? co->conn->server->addr : "unset",
|
||||
co && co->conn && co->conn->server? co->conn->server->port : 0);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int generate_new_msisdn(char *msisdn, const char *imsi, unsigned int len)
|
||||
{
|
||||
int i, j, rc;
|
||||
uint8_t rand_buf[GSM23003_MSISDN_MAX_DIGITS];
|
||||
|
||||
OSMO_ASSERT(len <= sizeof(rand_buf));
|
||||
|
||||
/* Generate a random unique MSISDN (with retry) */
|
||||
for (i = 0; i < 10; i++) {
|
||||
/* Get a random number (with retry) */
|
||||
for (j = 0; j < 10; j++) {
|
||||
rc = osmo_get_rand_id(rand_buf, len);
|
||||
if (!rc)
|
||||
break;
|
||||
}
|
||||
if (rc) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "IMSI='%s': Failed to generate new MSISDN, random number generator"
|
||||
" failed (rc=%d)\n", imsi, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Shift 0x00 ... 0xff range to 30 ... 39 (ASCII numbers) */
|
||||
for (j = 0; j < len; j++)
|
||||
msisdn[j] = 48 + (rand_buf[j] % 10);
|
||||
msisdn[j] = '\0';
|
||||
|
||||
/* Ensure there is no subscriber with such MSISDN */
|
||||
if (db_subscr_exists_by_msisdn(g_hlr->dbc, msisdn) == -ENOENT)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Failure */
|
||||
LOGP(DMAIN, LOGL_ERROR, "IMSI='%s': Failed to generate a new MSISDN, consider increasing "
|
||||
"the length for the automatically assigned MSISDNs "
|
||||
"(see 'subscriber-create-on-demand' command)\n", imsi);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int subscr_create_on_demand(const char *imsi)
|
||||
{
|
||||
char msisdn[GSM23003_MSISDN_MAX_DIGITS + 1];
|
||||
int rc;
|
||||
unsigned int rand_msisdn_len = g_hlr->subscr_create_on_demand_rand_msisdn_len;
|
||||
|
||||
if (!g_hlr->subscr_create_on_demand)
|
||||
return -1;
|
||||
if (db_subscr_exists_by_imsi(g_hlr->dbc, imsi) == 0)
|
||||
return -1;
|
||||
if (rand_msisdn_len && generate_new_msisdn(msisdn, imsi, rand_msisdn_len) != 0)
|
||||
return -1;
|
||||
|
||||
LOGP(DMAIN, LOGL_INFO, "IMSI='%s': Creating subscriber on demand\n", imsi);
|
||||
rc = db_subscr_create(g_hlr->dbc, imsi, g_hlr->subscr_create_on_demand_flags);
|
||||
if (rc) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "Failed to create subscriber on demand (rc=%d): IMSI='%s'\n", rc, imsi);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (!rand_msisdn_len)
|
||||
return 0;
|
||||
|
||||
/* Update MSISDN of the new (just allocated) subscriber */
|
||||
rc = db_subscr_update_msisdn_by_imsi(g_hlr->dbc, imsi, msisdn);
|
||||
if (rc) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "IMSI='%s': Failed to assign MSISDN='%s' (rc=%d)\n", imsi, msisdn, rc);
|
||||
return rc;
|
||||
}
|
||||
LOGP(DMAIN, LOGL_INFO, "IMSI='%s': Successfully assigned MSISDN='%s'\n", imsi, msisdn);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients.
|
||||
* \param[in,out] hlr Global hlr context.
|
||||
* \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call.
|
||||
* \param[in] nam_val True to enable CS/PS, false to disable.
|
||||
* \param[in] is_ps True to enable/disable PS, false for CS.
|
||||
* \returns 0 on success, ENOEXEC if there is no need to change, a negative
|
||||
* value on error.
|
||||
*/
|
||||
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps)
|
||||
{
|
||||
int rc;
|
||||
bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs;
|
||||
struct global_title vlr_name;
|
||||
struct osmo_gsup_message gsup_del_data = {
|
||||
.message_type = OSMO_GSUP_MSGT_DELETE_DATA_REQUEST,
|
||||
};
|
||||
OSMO_STRLCPY_ARRAY(gsup_del_data.imsi, subscr->imsi);
|
||||
|
||||
if (is_val == nam_val) {
|
||||
LOGP(DAUC, LOGL_DEBUG, "IMSI-%s: Already has the requested value when asked to %s %s\n",
|
||||
subscr->imsi, nam_val ? "enable" : "disable", is_ps ? "PS" : "CS");
|
||||
return ENOEXEC;
|
||||
}
|
||||
|
||||
rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps);
|
||||
if (rc)
|
||||
return rc > 0? -rc : rc;
|
||||
|
||||
/* If we're disabling, send a notice out to the GSUP client that is
|
||||
* responsible. Otherwise no need. */
|
||||
if (nam_val)
|
||||
return 0;
|
||||
|
||||
if (subscr->vlr_number && global_title_set_str(&vlr_name, subscr->vlr_number))
|
||||
osmo_gsup_gt_enc_send(g_hlr->gs, &vlr_name, &gsup_del_data);
|
||||
if (subscr->sgsn_number && global_title_set_str(&vlr_name, subscr->sgsn_number))
|
||||
osmo_gsup_gt_enc_send(g_hlr->gs, &vlr_name, &gsup_del_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* Send Auth Info handling
|
||||
***********************************************************************/
|
||||
|
||||
/* process an incoming SAI request */
|
||||
static int rx_send_auth_info(struct osmo_gsup_conn *conn,
|
||||
const struct osmo_gsup_message *gsup,
|
||||
struct db_context *dbc)
|
||||
static int rx_send_auth_info(unsigned int auc_3g_ind, struct osmo_gsup_req *req)
|
||||
{
|
||||
struct osmo_gsup_message gsup_out;
|
||||
struct msgb *msg_out;
|
||||
struct osmo_gsup_message gsup_out = {
|
||||
.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT,
|
||||
};
|
||||
int rc;
|
||||
|
||||
/* initialize return message structure */
|
||||
memset(&gsup_out, 0, sizeof(gsup_out));
|
||||
memcpy(&gsup_out.imsi, &gsup->imsi, sizeof(gsup_out.imsi));
|
||||
subscr_create_on_demand(req->gsup.imsi);
|
||||
|
||||
rc = db_get_auc(dbc, gsup->imsi, conn->auc_3g_ind,
|
||||
rc = db_get_auc(g_hlr->dbc, req->gsup.imsi, auc_3g_ind,
|
||||
gsup_out.auth_vectors,
|
||||
ARRAY_SIZE(gsup_out.auth_vectors),
|
||||
gsup->rand, gsup->auts);
|
||||
if (rc < 0) {
|
||||
gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR;
|
||||
gsup_out.cause = GMM_CAUSE_NET_FAIL;
|
||||
} else if (rc == 0) {
|
||||
gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR;
|
||||
gsup_out.cause = GMM_CAUSE_IMSI_UNKNOWN;
|
||||
} else {
|
||||
gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT;
|
||||
gsup_out.num_auth_vectors = rc;
|
||||
req->gsup.rand, req->gsup.auts);
|
||||
if (rc <= 0) {
|
||||
switch (rc) {
|
||||
case 0:
|
||||
/* 0 means "0 tuples generated", which shouldn't happen.
|
||||
* Treat the same as "no auth data". */
|
||||
case -ENOKEY:
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN,
|
||||
"IMSI known, but has no auth data;"
|
||||
" Returning slightly inaccurate cause 'IMSI Unknown' via GSUP");
|
||||
return rc;
|
||||
case -ENOENT:
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "IMSI unknown");
|
||||
return rc;
|
||||
default:
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "failure to look up IMSI in db");
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP AUC response");
|
||||
osmo_gsup_encode(msg_out, &gsup_out);
|
||||
return osmo_gsup_conn_send(conn, msg_out);
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* LU Operation State / Structure
|
||||
***********************************************************************/
|
||||
|
||||
static LLIST_HEAD(g_lu_ops);
|
||||
|
||||
/*! Receive Cancel Location Result from old VLR/SGSN */
|
||||
void lu_op_rx_cancel_old_ack(struct lu_operation *luop,
|
||||
const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
OSMO_ASSERT(luop->state == LU_S_CANCEL_SENT);
|
||||
/* FIXME: Check for spoofing */
|
||||
|
||||
osmo_timer_del(&luop->timer);
|
||||
|
||||
/* FIXME */
|
||||
|
||||
lu_op_tx_insert_subscr_data(luop);
|
||||
}
|
||||
|
||||
/*! Receive Insert Subscriber Data Result from new VLR/SGSN */
|
||||
static void lu_op_rx_insert_subscr_data_ack(struct lu_operation *luop,
|
||||
const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
OSMO_ASSERT(luop->state == LU_S_ISD_SENT);
|
||||
/* FIXME: Check for spoofing */
|
||||
|
||||
osmo_timer_del(&luop->timer);
|
||||
|
||||
/* Subscriber_Present_HLR */
|
||||
/* CS only: Check_SS_required? -> MAP-FW-CHECK_SS_IND.req */
|
||||
|
||||
/* Send final ACK towards inquiring VLR/SGSN */
|
||||
lu_op_tx_ack(luop);
|
||||
}
|
||||
|
||||
/*! Receive GSUP message for given \ref lu_operation */
|
||||
void lu_op_rx_gsup(struct lu_operation *luop,
|
||||
const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
switch (gsup->message_type) {
|
||||
case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
|
||||
/* FIXME */
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
|
||||
lu_op_rx_insert_subscr_data_ack(luop, gsup);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR:
|
||||
/* FIXME */
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT:
|
||||
lu_op_rx_cancel_old_ack(luop, gsup);
|
||||
break;
|
||||
default:
|
||||
LOGP(DMAIN, LOGL_ERROR, "Unhandled GSUP msg_type 0x%02x\n",
|
||||
gsup->message_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Receive Update Location Request, creates new \ref lu_operation */
|
||||
static int rx_upd_loc_req(struct osmo_gsup_conn *conn,
|
||||
const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
struct lu_operation *luop = lu_op_alloc_conn(conn);
|
||||
if (!luop) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "LU REQ from conn without addr?\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
lu_op_statechg(luop, LU_S_LU_RECEIVED);
|
||||
|
||||
if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS)
|
||||
luop->is_ps = true;
|
||||
llist_add(&luop->list, &g_lu_ops);
|
||||
|
||||
/* Roughly follwing "Process Update_Location_HLR" of TS 09.02 */
|
||||
|
||||
/* check if subscriber is known at all */
|
||||
if (!lu_op_fill_subscr(luop, g_hlr->dbc, gsup->imsi)) {
|
||||
/* Send Error back: Subscriber Unknown in HLR */
|
||||
strcpy(luop->subscr.imsi, gsup->imsi);
|
||||
lu_op_tx_error(luop, GMM_CAUSE_IMSI_UNKNOWN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check if subscriber is generally permitted on CS or PS
|
||||
* service (as requested) */
|
||||
if (!luop->is_ps && !luop->subscr.nam_cs) {
|
||||
lu_op_tx_error(luop, GMM_CAUSE_PLMN_NOTALLOWED);
|
||||
return 0;
|
||||
} else if (luop->is_ps && !luop->subscr.nam_ps) {
|
||||
lu_op_tx_error(luop, GMM_CAUSE_GPRS_NOTALLOWED);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO: Set subscriber tracing = deactive in VLR/SGSN */
|
||||
|
||||
#if 0
|
||||
/* Cancel in old VLR/SGSN, if new VLR/SGSN differs from old */
|
||||
if (luop->is_ps == false &&
|
||||
strcmp(subscr->vlr_number, vlr_number)) {
|
||||
lu_op_tx_cancel_old(luop);
|
||||
} else if (luop->is_ps == true &&
|
||||
strcmp(subscr->sgsn_number, sgsn_number)) {
|
||||
lu_op_tx_cancel_old(luop);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
/* TODO: Subscriber allowed to roam in PLMN? */
|
||||
/* TODO: Update RoutingInfo */
|
||||
/* TODO: Reset Flag MS Purged (cs/ps) */
|
||||
/* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */
|
||||
lu_op_tx_insert_subscr_data(luop);
|
||||
}
|
||||
gsup_out.num_auth_vectors = rc;
|
||||
osmo_gsup_req_respond(req, &gsup_out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rx_purge_ms_req(struct osmo_gsup_conn *conn,
|
||||
const struct osmo_gsup_message *gsup)
|
||||
/*! Receive Update Location Request, creates new lu_operation */
|
||||
static int rx_upd_loc_req(struct osmo_gsup_conn *conn, struct osmo_gsup_req *req)
|
||||
{
|
||||
struct osmo_gsup_message gsup_reply = {0};
|
||||
struct msgb *msg_out;
|
||||
bool is_ps = false;
|
||||
switch (req->gsup.cn_domain) {
|
||||
case OSMO_GSUP_CN_DOMAIN_CS:
|
||||
conn->supports_cs = true;
|
||||
break;
|
||||
default:
|
||||
/* The client didn't send a CN_DOMAIN IE; assume packet-switched in
|
||||
* accordance with the GSUP spec in osmo-hlr's user manual (section
|
||||
* 11.6.15 "CN Domain" says "if no CN Domain IE is present within
|
||||
* a request, the PS Domain is assumed." */
|
||||
case OSMO_GSUP_CN_DOMAIN_PS:
|
||||
conn->supports_ps = true;
|
||||
break;
|
||||
}
|
||||
|
||||
subscr_create_on_demand(req->gsup.imsi);
|
||||
|
||||
lu_rx_gsup(req);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rx_purge_ms_req(struct osmo_gsup_req *req)
|
||||
{
|
||||
bool is_ps = (req->gsup.cn_domain != OSMO_GSUP_CN_DOMAIN_CS);
|
||||
int rc;
|
||||
|
||||
LOGP(DAUC, LOGL_INFO, "%s: Purge MS (%s)\n", gsup->imsi,
|
||||
is_ps ? "PS" : "CS");
|
||||
|
||||
memcpy(gsup_reply.imsi, gsup->imsi, sizeof(gsup_reply.imsi));
|
||||
|
||||
if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS)
|
||||
is_ps = true;
|
||||
LOG_GSUP_REQ_CAT(req, DAUC, LOGL_INFO, "Purge MS (%s)\n", is_ps ? "PS" : "CS");
|
||||
|
||||
/* FIXME: check if the VLR that sends the purge is the same that
|
||||
* we have on record. Only update if yes */
|
||||
|
||||
/* Perform the actual update of the DB */
|
||||
rc = db_subscr_purge(g_hlr->dbc, gsup->imsi, true, is_ps);
|
||||
rc = db_subscr_purge(g_hlr->dbc, req->gsup.imsi, true, is_ps);
|
||||
|
||||
if (rc == 1)
|
||||
gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_RESULT;
|
||||
else if (rc == 0) {
|
||||
gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR;
|
||||
gsup_reply.cause = GMM_CAUSE_IMSI_UNKNOWN;
|
||||
} else {
|
||||
gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR;
|
||||
gsup_reply.cause = GMM_CAUSE_NET_FAIL;
|
||||
if (rc == 0)
|
||||
osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_PURGE_MS_RESULT);
|
||||
else if (rc == -ENOENT)
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "IMSI unknown");
|
||||
else
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "db error");
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int rx_check_imei_req(struct osmo_gsup_req *req)
|
||||
{
|
||||
struct osmo_gsup_message gsup_reply;
|
||||
char imei[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1] = {0};
|
||||
const struct osmo_gsup_message *gsup = &req->gsup;
|
||||
int rc;
|
||||
|
||||
/* Require IMEI */
|
||||
if (!gsup->imei_enc) {
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "missing IMEI");
|
||||
return -1;
|
||||
}
|
||||
|
||||
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP AUC response");
|
||||
osmo_gsup_encode(msg_out, &gsup_reply);
|
||||
return osmo_gsup_conn_send(conn, msg_out);
|
||||
/* Decode IMEI (fails if IMEI is too long) */
|
||||
rc = gsm48_decode_bcd_number2(imei, sizeof(imei), gsup->imei_enc, gsup->imei_enc_len, 0);
|
||||
if (rc < 0) {
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
|
||||
"failed to decode IMEI %s (rc: %d)",
|
||||
osmo_hexdump_c(OTC_SELECT, gsup->imei_enc, gsup->imei_enc_len),
|
||||
rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check if IMEI is too short */
|
||||
if (!osmo_imei_str_valid(imei, false)) {
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
|
||||
"invalid IMEI: %s", osmo_quote_str_c(OTC_SELECT, imei, -1));
|
||||
return -1;
|
||||
}
|
||||
|
||||
subscr_create_on_demand(gsup->imsi);
|
||||
|
||||
/* Save in DB if desired */
|
||||
if (g_hlr->store_imei) {
|
||||
LOGP(DAUC, LOGL_DEBUG, "IMSI='%s': storing IMEI = %s\n", gsup->imsi, imei);
|
||||
if (db_subscr_update_imei_by_imsi(g_hlr->dbc, gsup->imsi, imei) < 0) {
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "Failed to store IMEI in HLR db");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
/* Check if subscriber exists and print IMEI */
|
||||
LOGP(DMAIN, LOGL_INFO, "IMSI='%s': has IMEI = %s (consider setting 'store-imei')\n", gsup->imsi, imei);
|
||||
struct hlr_subscriber subscr;
|
||||
if (db_subscr_get_by_imsi(g_hlr->dbc, gsup->imsi, &subscr) < 0) {
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "IMSI unknown");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Accept all IMEIs */
|
||||
gsup_reply = (struct osmo_gsup_message){
|
||||
.message_type = OSMO_GSUP_MSGT_CHECK_IMEI_RESULT,
|
||||
.imei_result = OSMO_GSUP_IMEI_RESULT_ACK,
|
||||
};
|
||||
return osmo_gsup_req_respond(req, &gsup_reply);
|
||||
}
|
||||
|
||||
static char namebuf[255];
|
||||
#define LOGP_GSUP_FWD(gsup, level, fmt, args ...) \
|
||||
LOGP(DMAIN, level, "Forward %s (class=%s, IMSI=%s, %s->%s): " fmt, \
|
||||
osmo_gsup_message_type_name((gsup)->message_type), \
|
||||
osmo_gsup_message_class_name((gsup)->message_class), \
|
||||
(gsup)->imsi, \
|
||||
osmo_quote_str((const char *)(gsup)->source_name, (gsup)->source_name_len), \
|
||||
osmo_quote_str_buf2(namebuf, sizeof(namebuf), (const char *)(gsup)->destination_name, (gsup)->destination_name_len), \
|
||||
## args)
|
||||
|
||||
static int read_cb_forward(struct osmo_gsup_req *req)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
const struct osmo_gsup_message *gsup = &req->gsup;
|
||||
struct osmo_gsup_message gsup_err;
|
||||
struct msgb *forward_msg;
|
||||
struct global_title destination_name;
|
||||
|
||||
/* Check for routing IEs */
|
||||
if (!req->gsup.source_name[0] || !req->gsup.source_name_len
|
||||
|| !req->gsup.destination_name[0] || !req->gsup.destination_name_len) {
|
||||
LOGP_GSUP_FWD(&req->gsup, LOGL_ERROR, "missing routing IEs\n");
|
||||
goto routing_error;
|
||||
}
|
||||
|
||||
if (global_title_set(&destination_name, req->gsup.destination_name, req->gsup.destination_name_len)) {
|
||||
LOGP_GSUP_FWD(&req->gsup, LOGL_ERROR, "invalid destination name\n");
|
||||
goto routing_error;
|
||||
}
|
||||
|
||||
LOG_GSUP_REQ(req, LOGL_INFO, "Forwarding to %s\n", global_title_name(&destination_name));
|
||||
|
||||
/* Forward message without re-encoding (so we don't remove unknown IEs).
|
||||
* Copy GSUP part to forward, removing incoming IPA header to be able to prepend an outgoing IPA header */
|
||||
forward_msg = osmo_gsup_msgb_alloc("GSUP forward");
|
||||
forward_msg->l2h = msgb_put(forward_msg, msgb_l2len(req->msg));
|
||||
memcpy(forward_msg->l2h, msgb_l2(req->msg), msgb_l2len(req->msg));
|
||||
ret = osmo_gsup_gt_send(g_hlr->gs, &destination_name, forward_msg);
|
||||
if (ret) {
|
||||
LOGP_GSUP_FWD(gsup, LOGL_ERROR, "%s (rc=%d)\n",
|
||||
ret == -ENODEV ? "destination not connected" : "unknown error",
|
||||
ret);
|
||||
goto routing_error;
|
||||
}
|
||||
osmo_gsup_req_free(req);
|
||||
return 0;
|
||||
|
||||
routing_error:
|
||||
gsup_err = (struct osmo_gsup_message){
|
||||
.message_type = OSMO_GSUP_MSGT_E_ROUTING_ERROR,
|
||||
.destination_name = gsup->destination_name,
|
||||
.destination_name_len = gsup->destination_name_len,
|
||||
.source_name = gsup->source_name,
|
||||
.source_name_len = gsup->source_name_len,
|
||||
};
|
||||
osmo_gsup_req_respond(req, &gsup_err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
|
||||
{
|
||||
static struct osmo_gsup_message gsup;
|
||||
int rc;
|
||||
struct osmo_gsup_req *req = osmo_gsup_req_new(conn, msg);
|
||||
|
||||
rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
|
||||
if (rc < 0) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "error in GSUP decode: %d\n", rc);
|
||||
return rc;
|
||||
if (!req)
|
||||
return -EINVAL;
|
||||
|
||||
/* If the GSUP recipient is other than this HLR, forward. */
|
||||
if (req->gsup.destination_name_len) {
|
||||
struct global_title destination_name;
|
||||
struct global_title my_name;
|
||||
global_title_set_str(&my_name, g_hlr->gsup_unit_name.serno);
|
||||
if (!global_title_set(&destination_name, req->gsup.destination_name, req->gsup.destination_name_len)
|
||||
&& global_title_cmp(&destination_name, &my_name)) {
|
||||
return read_cb_forward(req);
|
||||
}
|
||||
}
|
||||
|
||||
switch (gsup.message_type) {
|
||||
/* Distributed GSM: check whether to proxy for / lookup a remote HLR.
|
||||
* It would require less database hits to do this only if a local-only operation fails with "unknown IMSI", but
|
||||
* it becomes semantically easier if we do this once-off ahead of time. */
|
||||
if (osmo_mslookup_client_active(g_hlr->mslookup.client.client)) {
|
||||
if (dgsm_check_forward_gsup_msg(req))
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (req->gsup.message_type) {
|
||||
/* requests sent to us */
|
||||
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST:
|
||||
rx_send_auth_info(conn, &gsup, g_hlr->dbc);
|
||||
rx_send_auth_info(conn->auc_3g_ind, req);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST:
|
||||
rx_upd_loc_req(conn, &gsup);
|
||||
rx_upd_loc_req(conn, req);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_PURGE_MS_REQUEST:
|
||||
rx_purge_ms_req(conn, &gsup);
|
||||
rx_purge_ms_req(req);
|
||||
break;
|
||||
/* responses to requests sent by us */
|
||||
case OSMO_GSUP_MSGT_DELETE_DATA_ERROR:
|
||||
LOGP(DMAIN, LOGL_ERROR, "Error while deleting subscriber data "
|
||||
"for IMSI %s\n", gsup.imsi);
|
||||
LOG_GSUP_REQ(req, LOGL_ERROR, "Peer responds with: Error while deleting subscriber data\n");
|
||||
osmo_gsup_req_free(req);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_DELETE_DATA_RESULT:
|
||||
LOGP(DMAIN, LOGL_ERROR, "Deleting subscriber data for IMSI %s\n",
|
||||
gsup.imsi);
|
||||
LOG_GSUP_REQ(req, LOGL_DEBUG, "Peer responds with: Subscriber data deleted\n");
|
||||
osmo_gsup_req_free(req);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_PROC_SS_REQUEST:
|
||||
case OSMO_GSUP_MSGT_PROC_SS_RESULT:
|
||||
rx_proc_ss_req(req);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_PROC_SS_ERROR:
|
||||
rx_proc_ss_error(req);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
|
||||
case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
|
||||
case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR:
|
||||
case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT:
|
||||
{
|
||||
struct lu_operation *luop = lu_op_by_imsi(gsup.imsi,
|
||||
&g_lu_ops);
|
||||
if (!luop) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "GSUP message %s for "
|
||||
"unknown IMSI %s\n",
|
||||
osmo_gsup_message_type_name(gsup.message_type),
|
||||
gsup.imsi);
|
||||
break;
|
||||
}
|
||||
lu_op_rx_gsup(luop, &gsup);
|
||||
}
|
||||
lu_rx_gsup(req);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_CHECK_IMEI_REQUEST:
|
||||
rx_check_imei_req(req);
|
||||
break;
|
||||
default:
|
||||
LOGP(DMAIN, LOGL_DEBUG, "Unhandled GSUP message type %s\n",
|
||||
osmo_gsup_message_type_name(gsup.message_type));
|
||||
osmo_gsup_message_type_name(req->gsup.message_type));
|
||||
osmo_gsup_req_free(req);
|
||||
break;
|
||||
}
|
||||
msgb_free(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -310,6 +566,8 @@ static void print_help()
|
||||
printf(" -s --disable-color Do not print ANSI colors in the log\n");
|
||||
printf(" -T --timestamp Prefix every log line with a timestamp.\n");
|
||||
printf(" -e --log-level number Set a global loglevel.\n");
|
||||
printf(" -U --db-upgrade Allow HLR database schema upgrades.\n");
|
||||
printf(" -C --db-check Quit after opening (and upgrading) the database.\n");
|
||||
printf(" -V --version Print the version of OsmoHLR.\n");
|
||||
}
|
||||
|
||||
@@ -317,10 +575,13 @@ static struct {
|
||||
const char *config_file;
|
||||
const char *db_file;
|
||||
bool daemonize;
|
||||
bool db_upgrade;
|
||||
bool db_check;
|
||||
} cmdline_opts = {
|
||||
.config_file = "osmo-hlr.cfg",
|
||||
.db_file = "hlr.db",
|
||||
.db_file = NULL,
|
||||
.daemonize = false,
|
||||
.db_upgrade = false,
|
||||
};
|
||||
|
||||
static void handle_options(int argc, char **argv)
|
||||
@@ -336,11 +597,13 @@ static void handle_options(int argc, char **argv)
|
||||
{"disable-color", 0, 0, 's'},
|
||||
{"log-level", 1, 0, 'e'},
|
||||
{"timestamp", 0, 0, 'T'},
|
||||
{"db-upgrade", 0, 0, 'U' },
|
||||
{"db-check", 0, 0, 'C' },
|
||||
{"version", 0, 0, 'V' },
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "hc:l:d:Dse:TV",
|
||||
c = getopt_long(argc, argv, "hc:l:d:Dse:TUV",
|
||||
long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
@@ -371,6 +634,12 @@ static void handle_options(int argc, char **argv)
|
||||
case 'T':
|
||||
log_set_print_timestamp(osmo_stderr_target, 1);
|
||||
break;
|
||||
case 'U':
|
||||
cmdline_opts.db_upgrade = true;
|
||||
break;
|
||||
case 'C':
|
||||
cmdline_opts.db_check = true;
|
||||
break;
|
||||
case 'V':
|
||||
print_version(1);
|
||||
exit(0);
|
||||
@@ -384,18 +653,13 @@ static void handle_options(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
static void *hlr_ctx = NULL;
|
||||
|
||||
static void signal_hdlr(int signal)
|
||||
{
|
||||
switch (signal) {
|
||||
case SIGTERM:
|
||||
case SIGINT:
|
||||
LOGP(DMAIN, LOGL_NOTICE, "Terminating due to SIGINT\n");
|
||||
osmo_gsup_server_destroy(g_hlr->gs);
|
||||
db_close(g_hlr->dbc);
|
||||
log_fini();
|
||||
talloc_report_full(hlr_ctx, stderr);
|
||||
exit(0);
|
||||
LOGP(DMAIN, LOGL_NOTICE, "Terminating due to signal=%d\n", signal);
|
||||
quit++;
|
||||
break;
|
||||
case SIGUSR1:
|
||||
LOGP(DMAIN, LOGL_DEBUG, "Talloc Report due to SIGUSR1\n");
|
||||
@@ -422,21 +686,39 @@ int main(int argc, char **argv)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Track the use of talloc NULL memory contexts */
|
||||
talloc_enable_null_tracking();
|
||||
|
||||
hlr_ctx = talloc_named_const(NULL, 1, "OsmoHLR");
|
||||
msgb_talloc_ctx_init(hlr_ctx, 0);
|
||||
vty_info.tall_ctx = hlr_ctx;
|
||||
|
||||
g_hlr = talloc_zero(hlr_ctx, struct hlr);
|
||||
INIT_LLIST_HEAD(&g_hlr->euse_list);
|
||||
INIT_LLIST_HEAD(&g_hlr->iuse_list);
|
||||
INIT_LLIST_HEAD(&g_hlr->ss_sessions);
|
||||
INIT_LLIST_HEAD(&g_hlr->ussd_routes);
|
||||
g_hlr->db_file_path = talloc_strdup(g_hlr, HLR_DEFAULT_DB_FILE_PATH);
|
||||
|
||||
rc = osmo_init_logging(&hlr_log_info);
|
||||
/* Init default (call independent) SS session guard timeout value */
|
||||
g_hlr->ncss_guard_timeout = NCSS_GUARD_TIMEOUT_DEFAULT;
|
||||
|
||||
g_hlr->proxy = proxy_init(g_hlr);
|
||||
|
||||
rc = osmo_init_logging2(hlr_ctx, &hlr_log_info);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Error initializing logging\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Set up llists and objects, startup is happening from VTY commands. */
|
||||
dgsm_init(hlr_ctx);
|
||||
|
||||
osmo_stats_init(hlr_ctx);
|
||||
vty_init(&vty_info);
|
||||
ctrl_vty_init(hlr_ctx);
|
||||
handle_options(argc, argv);
|
||||
hlr_vty_init(g_hlr, &hlr_log_info);
|
||||
hlr_vty_init();
|
||||
|
||||
rc = vty_read_config_file(cmdline_opts.config_file, NULL);
|
||||
if (rc < 0) {
|
||||
@@ -446,12 +728,6 @@ int main(int argc, char **argv)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* start telnet after reading config for vty_get_bind_addr() */
|
||||
rc = telnet_init_dynif(hlr_ctx, NULL, vty_get_bind_addr(),
|
||||
OSMO_VTY_PORT_HLR);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
LOGP(DMAIN, LOGL_NOTICE, "hlr starting\n");
|
||||
|
||||
rc = rand_init();
|
||||
@@ -460,14 +736,34 @@ int main(int argc, char **argv)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
g_hlr->dbc = db_open(hlr_ctx, cmdline_opts.db_file);
|
||||
if (cmdline_opts.db_file)
|
||||
osmo_talloc_replace_string(g_hlr, &g_hlr->db_file_path, cmdline_opts.db_file);
|
||||
|
||||
g_hlr->dbc = db_open(hlr_ctx, g_hlr->db_file_path, true, cmdline_opts.db_upgrade);
|
||||
if (!g_hlr->dbc) {
|
||||
LOGP(DMAIN, LOGL_FATAL, "Error opening database\n");
|
||||
LOGP(DMAIN, LOGL_FATAL, "Error opening database %s\n", osmo_quote_str(g_hlr->db_file_path, -1));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (cmdline_opts.db_check) {
|
||||
LOGP(DMAIN, LOGL_NOTICE, "Cmdline option --db-check: Database was opened successfully, quitting.\n");
|
||||
db_close(g_hlr->dbc);
|
||||
log_fini();
|
||||
talloc_free(hlr_ctx);
|
||||
talloc_free(tall_vty_ctx);
|
||||
talloc_disable_null_tracking();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* start telnet after reading config for vty_get_bind_addr() */
|
||||
rc = telnet_init_dynif(hlr_ctx, NULL, vty_get_bind_addr(),
|
||||
OSMO_VTY_PORT_HLR);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
|
||||
g_hlr->gs = osmo_gsup_server_create(hlr_ctx, g_hlr->gsup_bind_addr, OSMO_GSUP_PORT,
|
||||
read_cb, &g_lu_ops);
|
||||
read_cb, g_hlr);
|
||||
if (!g_hlr->gs) {
|
||||
LOGP(DMAIN, LOGL_FATAL, "Error starting GSUP server\n");
|
||||
exit(1);
|
||||
@@ -476,8 +772,11 @@ int main(int argc, char **argv)
|
||||
g_hlr->ctrl_bind_addr = ctrl_vty_get_bind_addr();
|
||||
g_hlr->ctrl = hlr_controlif_setup(g_hlr);
|
||||
|
||||
dgsm_start(hlr_ctx);
|
||||
|
||||
osmo_init_ignore_signals();
|
||||
signal(SIGINT, &signal_hdlr);
|
||||
signal(SIGTERM, &signal_hdlr);
|
||||
signal(SIGUSR1, &signal_hdlr);
|
||||
|
||||
if (cmdline_opts.daemonize) {
|
||||
@@ -488,13 +787,29 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
while (1) {
|
||||
osmo_select_main(0);
|
||||
}
|
||||
while (!quit)
|
||||
osmo_select_main_ctx(0);
|
||||
|
||||
osmo_gsup_server_destroy(g_hlr->gs);
|
||||
db_close(g_hlr->dbc);
|
||||
|
||||
log_fini();
|
||||
|
||||
exit(0);
|
||||
/**
|
||||
* Report the heap state of root context, then free,
|
||||
* so both ASAN and Valgrind are happy...
|
||||
*/
|
||||
talloc_report_full(hlr_ctx, stderr);
|
||||
talloc_free(hlr_ctx);
|
||||
|
||||
/* FIXME: VTY code still uses NULL-context */
|
||||
talloc_free(tall_vty_ctx);
|
||||
|
||||
/**
|
||||
* Report the heap state of NULL context, then free,
|
||||
* so both ASAN and Valgrind are happy...
|
||||
*/
|
||||
talloc_report_full(NULL, stderr);
|
||||
talloc_disable_null_tracking();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
57
src/hlr.h
57
src/hlr.h
@@ -23,12 +23,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/gsm/ipa.h>
|
||||
#include <osmocom/core/tdef.h>
|
||||
#include "dgsm.h"
|
||||
|
||||
#define HLR_DEFAULT_DB_FILE_PATH "hlr.db"
|
||||
|
||||
struct hlr_euse;
|
||||
struct osmo_gsup_conn;
|
||||
enum osmo_gsup_message_type;
|
||||
|
||||
extern struct osmo_tdef g_hlr_tdefs[];
|
||||
|
||||
struct hlr {
|
||||
/* GSUP server pointer */
|
||||
struct osmo_gsup_server *gs;
|
||||
|
||||
/* DB context */
|
||||
char *db_file_path;
|
||||
struct db_context *dbc;
|
||||
|
||||
/* Control Interface */
|
||||
@@ -37,4 +50,48 @@ struct hlr {
|
||||
|
||||
/* Local bind addr */
|
||||
char *gsup_bind_addr;
|
||||
struct ipaccess_unit gsup_unit_name;
|
||||
|
||||
struct llist_head euse_list;
|
||||
struct hlr_euse *euse_default;
|
||||
struct llist_head iuse_list;
|
||||
|
||||
/* NCSS (call independent) session guard timeout value */
|
||||
int ncss_guard_timeout;
|
||||
|
||||
struct llist_head ussd_routes;
|
||||
|
||||
struct llist_head ss_sessions;
|
||||
|
||||
bool store_imei;
|
||||
|
||||
bool subscr_create_on_demand;
|
||||
/* Bitmask of DB_SUBSCR_FLAG_* */
|
||||
uint8_t subscr_create_on_demand_flags;
|
||||
unsigned int subscr_create_on_demand_rand_msisdn_len;
|
||||
|
||||
struct {
|
||||
bool allow_startup;
|
||||
struct dgsm_config vty;
|
||||
|
||||
struct {
|
||||
struct osmo_mslookup_server_mdns *mdns;
|
||||
uint32_t max_age;
|
||||
} server;
|
||||
|
||||
struct {
|
||||
unsigned int result_timeout_milliseconds;
|
||||
struct osmo_mslookup_client *client;
|
||||
struct osmo_mslookup_client_method *mdns;
|
||||
} client;
|
||||
} mslookup;
|
||||
|
||||
struct proxy *proxy;
|
||||
};
|
||||
|
||||
extern struct hlr *g_hlr;
|
||||
|
||||
struct hlr_subscriber;
|
||||
|
||||
void osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr);
|
||||
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps);
|
||||
|
||||
@@ -44,15 +44,16 @@ static struct {
|
||||
const char *db_file;
|
||||
bool bootstrap;
|
||||
const char *import_nitb_db;
|
||||
bool db_upgrade;
|
||||
} cmdline_opts = {
|
||||
.db_file = "hlr.db",
|
||||
.db_upgrade = false,
|
||||
};
|
||||
|
||||
static void print_help()
|
||||
{
|
||||
printf("\n");
|
||||
printf("Usage: osmo-hlr-db-tool [-l <hlr.db>] import-nitb-db <nitb.db>]\n");
|
||||
printf("Call without arguments to create a new empty ./hlr.db.\n");
|
||||
printf("Usage: osmo-hlr-db-tool [-l <hlr.db>] [create|import-nitb-db <nitb.db>]\n");
|
||||
printf(" -l --database db-name The OsmoHLR database to use, default '%s'.\n",
|
||||
cmdline_opts.db_file);
|
||||
printf(" -h --help This text.\n");
|
||||
@@ -60,9 +61,15 @@ static void print_help()
|
||||
printf(" -s --disable-color Do not print ANSI colors in the log\n");
|
||||
printf(" -T --timestamp Prefix every log line with a timestamp.\n");
|
||||
printf(" -e --log-level number Set a global loglevel.\n");
|
||||
printf(" -U --db-upgrade Allow HLR database schema upgrades.\n");
|
||||
printf(" -V --version Print the version of OsmoHLR-db-tool.\n");
|
||||
printf("\n");
|
||||
printf(" import-nitb-db db Add OsmoNITB db's subscribers to OsmoHLR db.\n");
|
||||
printf("Commands:\n");
|
||||
printf("\n");
|
||||
printf(" create Create an empty OsmoHLR database.\n");
|
||||
printf(" (All commands imply this if none exists yet.)\n");
|
||||
printf("\n");
|
||||
printf(" import-nitb-db <nitb.db> Add OsmoNITB db's subscribers to OsmoHLR db.\n");
|
||||
printf(" Be aware that the import is lossy, only the\n");
|
||||
printf(" IMSI, MSISDN, nam_cs/ps and 2G auth data are set.\n");
|
||||
}
|
||||
@@ -92,11 +99,12 @@ static void handle_options(int argc, char **argv)
|
||||
{"disable-color", 0, 0, 's'},
|
||||
{"timestamp", 0, 0, 'T'},
|
||||
{"log-level", 1, 0, 'e'},
|
||||
{"db-upgrade", 0, 0, 'U' },
|
||||
{"version", 0, 0, 'V' },
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "hl:d:sTe:V",
|
||||
c = getopt_long(argc, argv, "hl:d:sTe:UV",
|
||||
long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
@@ -120,6 +128,9 @@ static void handle_options(int argc, char **argv)
|
||||
case 'e':
|
||||
log_set_log_level(osmo_stderr_target, atoi(optarg));
|
||||
break;
|
||||
case 'U':
|
||||
cmdline_opts.db_upgrade = true;
|
||||
break;
|
||||
case 'V':
|
||||
print_version(1);
|
||||
exit(EXIT_SUCCESS);
|
||||
@@ -139,9 +150,11 @@ static void handle_options(int argc, char **argv)
|
||||
}
|
||||
|
||||
cmd = argv[optind++];
|
||||
printf("command '%s', %d extra arguments\n", cmd, argc - optind);
|
||||
|
||||
if (!strcmp(cmd, "import-nitb-db")) {
|
||||
if (!strcmp(cmd, "create")) {
|
||||
/* Nothing to do, just run the main program to open the database without running any
|
||||
* action, which will bootstrap all tables. */
|
||||
} else if (!strcmp(cmd, "import-nitb-db")) {
|
||||
if (argc - optind < 1) {
|
||||
fprintf(stderr, "You must specify an input db file\n");
|
||||
print_help();
|
||||
@@ -153,6 +166,12 @@ static void handle_options(int argc, char **argv)
|
||||
print_help();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (argc - optind > 0) {
|
||||
fprintf(stderr, "Too many arguments: '%s'\n", argv[optind]);
|
||||
print_help();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
static void signal_hdlr(int signal)
|
||||
@@ -281,15 +300,15 @@ void import_nitb_subscr(sqlite3 *nitb_db, sqlite3_stmt *stmt)
|
||||
|
||||
imsi = sqlite3_column_int64(stmt, 0);
|
||||
|
||||
snprintf(imsi_str, sizeof(imsi_str), "%"PRId64, imsi);
|
||||
snprintf(imsi_str, sizeof(imsi_str), "%" PRId64, imsi);
|
||||
|
||||
rc = db_subscr_create(dbc, imsi_str);
|
||||
if (rc) {
|
||||
rc = db_subscr_create(dbc, imsi_str, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS);
|
||||
if (rc < 0) {
|
||||
LOGP(DDB, LOGL_ERROR, "OsmoNITB DB import to %s: failed to create IMSI %s: %d: %s\n",
|
||||
dbc->fname,
|
||||
imsi_str,
|
||||
rc,
|
||||
strerror(rc));
|
||||
strerror(-rc));
|
||||
/* on error, still attempt to continue */
|
||||
}
|
||||
|
||||
@@ -303,13 +322,13 @@ void import_nitb_subscr(sqlite3 *nitb_db, sqlite3_stmt *stmt)
|
||||
|
||||
/* find the just created id */
|
||||
rc = db_subscr_get_by_imsi(dbc, imsi_str, &subscr);
|
||||
if (rc) {
|
||||
if (rc < 0) {
|
||||
LOGP(DDB, LOGL_ERROR, "OsmoNITB DB import to %s: created IMSI %s,"
|
||||
" but failed to get new subscriber id: %d: %s\n",
|
||||
dbc->fname,
|
||||
imsi_str,
|
||||
rc,
|
||||
strerror(rc));
|
||||
strerror(-rc));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -374,7 +393,7 @@ int main(int argc, char **argv)
|
||||
OSMO_ASSERT(g_hlr_db_tool_ctx);
|
||||
talloc_set_name_const(g_hlr_db_tool_ctx, "OsmoHLR-db-tool");
|
||||
|
||||
rc = osmo_init_logging(&hlr_log_info);
|
||||
rc = osmo_init_logging2(g_hlr_db_tool_ctx, &hlr_log_info);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Error initializing logging\n");
|
||||
exit(EXIT_FAILURE);
|
||||
@@ -387,7 +406,8 @@ int main(int argc, char **argv)
|
||||
goto too_many_actions;
|
||||
main_action = import_nitb_db;
|
||||
}
|
||||
/* Future: add more main_actions, besides --import-nitb-db, here. */
|
||||
/* Future: add more main_actions, besides import-nitb-db, here.
|
||||
* For command 'create', no action is required. */
|
||||
|
||||
/* Just in case any db actions need randomness */
|
||||
rc = rand_init();
|
||||
@@ -396,7 +416,7 @@ int main(int argc, char **argv)
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
g_hlr_db_tool_ctx->dbc = db_open(g_hlr_db_tool_ctx, cmdline_opts.db_file);
|
||||
g_hlr_db_tool_ctx->dbc = db_open(g_hlr_db_tool_ctx, cmdline_opts.db_file, true, cmdline_opts.db_upgrade);
|
||||
if (!g_hlr_db_tool_ctx->dbc) {
|
||||
LOGP(DMAIN, LOGL_FATAL, "Error opening database\n");
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
654
src/hlr_ussd.c
Normal file
654
src/hlr_ussd.c
Normal file
@@ -0,0 +1,654 @@
|
||||
/* OsmoHLR SS/USSD implementation */
|
||||
|
||||
/* (C) 2018 Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* 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 <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/gsm/gsup.h>
|
||||
#include <osmocom/gsm/gsm0480.h>
|
||||
#include <osmocom/gsm/protocol/gsm_04_80.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "hlr.h"
|
||||
#include "hlr_ussd.h"
|
||||
#include "gsup_server.h"
|
||||
#include "gsup_router.h"
|
||||
#include "logging.h"
|
||||
#include "db.h"
|
||||
|
||||
/***********************************************************************
|
||||
* core data structures expressing config from VTY
|
||||
***********************************************************************/
|
||||
|
||||
struct hlr_euse *euse_find(struct hlr *hlr, const char *name)
|
||||
{
|
||||
struct hlr_euse *euse;
|
||||
|
||||
llist_for_each_entry(euse, &hlr->euse_list, list) {
|
||||
if (!strcmp(euse->name, name))
|
||||
return euse;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct hlr_euse *euse_alloc(struct hlr *hlr, const char *name)
|
||||
{
|
||||
struct hlr_euse *euse = euse_find(hlr, name);
|
||||
if (euse)
|
||||
return NULL;
|
||||
|
||||
euse = talloc_zero(hlr, struct hlr_euse);
|
||||
euse->name = talloc_strdup(euse, name);
|
||||
euse->hlr = hlr;
|
||||
llist_add_tail(&euse->list, &hlr->euse_list);
|
||||
|
||||
return euse;
|
||||
}
|
||||
|
||||
void euse_del(struct hlr_euse *euse)
|
||||
{
|
||||
llist_del(&euse->list);
|
||||
talloc_free(euse);
|
||||
}
|
||||
|
||||
|
||||
struct hlr_ussd_route *ussd_route_find_prefix(struct hlr *hlr, const char *prefix)
|
||||
{
|
||||
struct hlr_ussd_route *rt;
|
||||
|
||||
llist_for_each_entry(rt, &hlr->ussd_routes, list) {
|
||||
if (!strcmp(rt->prefix, prefix))
|
||||
return rt;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct hlr_ussd_route *ussd_route_prefix_alloc_int(struct hlr *hlr, const char *prefix,
|
||||
const struct hlr_iuse *iuse)
|
||||
{
|
||||
struct hlr_ussd_route *rt;
|
||||
|
||||
if (ussd_route_find_prefix(hlr, prefix))
|
||||
return NULL;
|
||||
|
||||
rt = talloc_zero(hlr, struct hlr_ussd_route);
|
||||
rt->prefix = talloc_strdup(rt, prefix);
|
||||
rt->u.iuse = iuse;
|
||||
llist_add_tail(&rt->list, &hlr->ussd_routes);
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
struct hlr_ussd_route *ussd_route_prefix_alloc_ext(struct hlr *hlr, const char *prefix,
|
||||
struct hlr_euse *euse)
|
||||
{
|
||||
struct hlr_ussd_route *rt;
|
||||
|
||||
if (ussd_route_find_prefix(hlr, prefix))
|
||||
return NULL;
|
||||
|
||||
rt = talloc_zero(hlr, struct hlr_ussd_route);
|
||||
rt->prefix = talloc_strdup(rt, prefix);
|
||||
rt->is_external = true;
|
||||
rt->u.euse = euse;
|
||||
llist_add_tail(&rt->list, &hlr->ussd_routes);
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
void ussd_route_del(struct hlr_ussd_route *rt)
|
||||
{
|
||||
llist_del(&rt->list);
|
||||
talloc_free(rt);
|
||||
}
|
||||
|
||||
static struct hlr_ussd_route *ussd_route_lookup_7bit(struct hlr *hlr, const char *ussd_code)
|
||||
{
|
||||
struct hlr_ussd_route *rt;
|
||||
llist_for_each_entry(rt, &hlr->ussd_routes, list) {
|
||||
if (!strncmp(ussd_code, rt->prefix, strlen(rt->prefix))) {
|
||||
LOGP(DSS, LOGL_DEBUG, "Found %s '%s' (prefix '%s') for USSD "
|
||||
"Code '%s'\n", rt->is_external ? "EUSE" : "IUSE",
|
||||
rt->is_external ? rt->u.euse->name : rt->u.iuse->name,
|
||||
rt->prefix, ussd_code);
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
LOGP(DSS, LOGL_DEBUG, "Could not find Route for USSD Code '%s'\n", ussd_code);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* handling functions for individual GSUP messages
|
||||
***********************************************************************/
|
||||
|
||||
#define LOGPSS(ss, lvl, fmt, args...) \
|
||||
LOGP(DSS, lvl, "%s/0x%08x: " fmt, (ss)->imsi, (ss)->session_id, ## args)
|
||||
|
||||
struct ss_session {
|
||||
/* link us to hlr->ss_sessions */
|
||||
struct llist_head list;
|
||||
/* imsi of this session */
|
||||
char imsi[OSMO_IMSI_BUF_SIZE];
|
||||
/* ID of this session (unique per IMSI) */
|
||||
uint32_t session_id;
|
||||
/* state of the session */
|
||||
enum osmo_gsup_session_state state;
|
||||
/* time-out when we will delete the session */
|
||||
struct osmo_timer_list timeout;
|
||||
|
||||
/* is this USSD for an external handler (EUSE): true */
|
||||
bool is_external;
|
||||
union {
|
||||
/* external USSD Entity responsible for this session */
|
||||
struct hlr_euse *euse;
|
||||
/* internal USSD Entity responsible for this session */
|
||||
const struct hlr_iuse *iuse;
|
||||
} u;
|
||||
|
||||
/* subscriber's vlr_number
|
||||
* MO USSD: originating MSC's vlr_number
|
||||
* MT USSD: looked up once per session and cached here */
|
||||
struct global_title vlr_name;
|
||||
|
||||
/* we don't keep a pointer to the osmo_gsup_{route,conn} towards the MSC/VLR here,
|
||||
* as this might change during inter-VLR hand-over, and we simply look-up the serving MSC/VLR
|
||||
* every time we receive an USSD component from the EUSE */
|
||||
|
||||
struct osmo_gsup_req *initial_req_from_ms;
|
||||
struct osmo_gsup_req *initial_req_from_euse;
|
||||
};
|
||||
|
||||
struct ss_session *ss_session_find(struct hlr *hlr, const char *imsi, uint32_t session_id)
|
||||
{
|
||||
struct ss_session *ss;
|
||||
llist_for_each_entry(ss, &hlr->ss_sessions, list) {
|
||||
if (!strcmp(ss->imsi, imsi) && ss->session_id == session_id)
|
||||
return ss;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ss_session_free(struct ss_session *ss)
|
||||
{
|
||||
osmo_timer_del(&ss->timeout);
|
||||
if (ss->initial_req_from_ms)
|
||||
osmo_gsup_req_free(ss->initial_req_from_ms);
|
||||
if (ss->initial_req_from_euse)
|
||||
osmo_gsup_req_free(ss->initial_req_from_euse);
|
||||
llist_del(&ss->list);
|
||||
talloc_free(ss);
|
||||
}
|
||||
|
||||
static void ss_session_timeout(void *data)
|
||||
{
|
||||
struct ss_session *ss = data;
|
||||
|
||||
LOGPSS(ss, LOGL_NOTICE, "SS Session Timeout, destroying\n");
|
||||
/* FIXME: should we send a ReturnError component to the MS? */
|
||||
ss_session_free(ss);
|
||||
}
|
||||
|
||||
struct ss_session *ss_session_alloc(struct hlr *hlr, const char *imsi, uint32_t session_id)
|
||||
{
|
||||
struct ss_session *ss;
|
||||
|
||||
OSMO_ASSERT(!ss_session_find(hlr, imsi, session_id));
|
||||
|
||||
ss = talloc_zero(hlr, struct ss_session);
|
||||
OSMO_ASSERT(ss);
|
||||
|
||||
OSMO_STRLCPY_ARRAY(ss->imsi, imsi);
|
||||
ss->session_id = session_id;
|
||||
|
||||
/* Schedule self-destruction timer */
|
||||
osmo_timer_setup(&ss->timeout, ss_session_timeout, ss);
|
||||
if (g_hlr->ncss_guard_timeout > 0)
|
||||
osmo_timer_schedule(&ss->timeout, g_hlr->ncss_guard_timeout, 0);
|
||||
|
||||
llist_add_tail(&ss->list, &hlr->ss_sessions);
|
||||
return ss;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* handling functions for encoding SS messages + wrapping them in GSUP
|
||||
***********************************************************************/
|
||||
|
||||
/* Resolve the target MSC by ss->imsi and send GSUP message. */
|
||||
static int ss_gsup_send_to_ms(struct ss_session *ss, struct osmo_gsup_server *gs, struct osmo_gsup_message *gsup)
|
||||
{
|
||||
struct hlr_subscriber subscr = {};
|
||||
struct msgb *msg;
|
||||
int rc;
|
||||
|
||||
if (ss->initial_req_from_ms) {
|
||||
/* If this is a response to an incoming GSUP request from the MS, respond via osmo_gsup_req_respond() to
|
||||
* make sure that all required routing information is kept intact.
|
||||
* Use osmo_gsup_req_respond_nonfinal() to not deallocate the ss->initial_req_from_ms */
|
||||
osmo_gsup_req_respond_nonfinal(ss->initial_req_from_ms, gsup);
|
||||
return 0;
|
||||
}
|
||||
|
||||
msg = osmo_gsup_msgb_alloc("GSUP USSD FW");
|
||||
rc = osmo_gsup_encode(msg, gsup);
|
||||
if (rc) {
|
||||
LOGPSS(ss, LOGL_ERROR, "Failed to encode GSUP message\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Use vlr_number as looked up by the caller, or look up now. */
|
||||
if (!ss->vlr_name.len) {
|
||||
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
|
||||
if (rc < 0) {
|
||||
LOGPSS(ss, LOGL_ERROR, "Cannot find subscriber, cannot route GSUP message\n");
|
||||
msgb_free(msg);
|
||||
return -EINVAL;
|
||||
}
|
||||
global_title_set_str(&ss->vlr_name, subscr.vlr_number);
|
||||
}
|
||||
|
||||
/* Check for empty string (all vlr_number strings end in "\0", because otherwise gsup_route_find() fails) */
|
||||
if (ss->vlr_name.len <= 1) {
|
||||
LOGPSS(ss, LOGL_ERROR, "Cannot send GSUP message, no VLR number stored for subscriber\n");
|
||||
msgb_free(msg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOGPSS(ss, LOGL_DEBUG, "Tx SS/USSD to VLR %s\n", global_title_name(&ss->vlr_name));
|
||||
return osmo_gsup_gt_send(gs, &ss->vlr_name, msg);
|
||||
}
|
||||
|
||||
static int ss_tx_to_ms(struct ss_session *ss, enum osmo_gsup_message_type gsup_msg_type,
|
||||
bool final, struct msgb *ss_msg)
|
||||
|
||||
{
|
||||
struct osmo_gsup_message resp = {
|
||||
.message_type = gsup_msg_type,
|
||||
.session_id = ss->session_id,
|
||||
};
|
||||
int rc;
|
||||
|
||||
OSMO_STRLCPY_ARRAY(resp.imsi, ss->imsi);
|
||||
if (final)
|
||||
resp.session_state = OSMO_GSUP_SESSION_STATE_END;
|
||||
else
|
||||
resp.session_state = OSMO_GSUP_SESSION_STATE_CONTINUE;
|
||||
if (ss_msg) {
|
||||
resp.ss_info = msgb_data(ss_msg);
|
||||
resp.ss_info_len = msgb_length(ss_msg);
|
||||
}
|
||||
|
||||
rc = ss_gsup_send_to_ms(ss, g_hlr->gs, &resp);
|
||||
|
||||
msgb_free(ss_msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static int ss_tx_reject(struct ss_session *ss, int invoke_id, uint8_t problem_tag,
|
||||
uint8_t problem_code)
|
||||
{
|
||||
struct msgb *msg = gsm0480_gen_reject(invoke_id, problem_tag, problem_code);
|
||||
LOGPSS(ss, LOGL_NOTICE, "Tx Reject(%u, 0x%02x, 0x%02x)\n", invoke_id,
|
||||
problem_tag, problem_code);
|
||||
OSMO_ASSERT(msg);
|
||||
return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, true, msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int ss_tx_to_ms_error(struct ss_session *ss, uint8_t invoke_id, uint8_t error_code)
|
||||
{
|
||||
struct msgb *msg = gsm0480_gen_return_error(invoke_id, error_code);
|
||||
LOGPSS(ss, LOGL_NOTICE, "Tx ReturnError(%u, 0x%02x)\n", invoke_id, error_code);
|
||||
OSMO_ASSERT(msg);
|
||||
return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, true, msg);
|
||||
}
|
||||
|
||||
static int ss_tx_to_ms_ussd_7bit(struct ss_session *ss, bool final, uint8_t invoke_id, const char *text)
|
||||
{
|
||||
struct msgb *msg = gsm0480_gen_ussd_resp_7bit(invoke_id, text);
|
||||
LOGPSS(ss, LOGL_INFO, "Tx USSD '%s'\n", text);
|
||||
OSMO_ASSERT(msg);
|
||||
return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, final, msg);
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* Internal USSD Handlers
|
||||
***********************************************************************/
|
||||
|
||||
#include "db.h"
|
||||
|
||||
static int handle_ussd_own_msisdn(struct ss_session *ss,
|
||||
const struct osmo_gsup_message *gsup, const struct ss_request *req)
|
||||
{
|
||||
struct hlr_subscriber subscr;
|
||||
char buf[GSM0480_USSD_7BIT_STRING_LEN+1];
|
||||
int rc;
|
||||
|
||||
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
|
||||
switch (rc) {
|
||||
case 0:
|
||||
if (strlen(subscr.msisdn) == 0)
|
||||
snprintf(buf, sizeof(buf), "You have no MSISDN!");
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "Your extension is %s", subscr.msisdn);
|
||||
ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id, buf);
|
||||
break;
|
||||
case -ENOENT:
|
||||
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
|
||||
break;
|
||||
case -EIO:
|
||||
default:
|
||||
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_ussd_own_imsi(struct ss_session *ss,
|
||||
const struct osmo_gsup_message *gsup, const struct ss_request *req)
|
||||
{
|
||||
char buf[GSM0480_USSD_7BIT_STRING_LEN+1];
|
||||
snprintf(buf, sizeof(buf), "Your IMSI is %s", ss->imsi);
|
||||
ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id, buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct hlr_iuse hlr_iuses[] = {
|
||||
{
|
||||
.name = "own-msisdn",
|
||||
.handle_ussd = handle_ussd_own_msisdn,
|
||||
},
|
||||
{
|
||||
.name = "own-imsi",
|
||||
.handle_ussd = handle_ussd_own_imsi,
|
||||
},
|
||||
};
|
||||
|
||||
const struct hlr_iuse *iuse_find(const char *name)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(hlr_iuses); i++) {
|
||||
const struct hlr_iuse *iuse = &hlr_iuses[i];
|
||||
if (!strcmp(name, iuse->name))
|
||||
return iuse;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************
|
||||
* handling functions for individual GSUP messages
|
||||
***********************************************************************/
|
||||
|
||||
static bool ss_op_is_ussd(uint8_t opcode)
|
||||
{
|
||||
switch (opcode) {
|
||||
case GSM0480_OP_CODE_PROCESS_USS_DATA:
|
||||
case GSM0480_OP_CODE_PROCESS_USS_REQ:
|
||||
case GSM0480_OP_CODE_USS_REQUEST:
|
||||
case GSM0480_OP_CODE_USS_NOTIFY:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* is this GSUP connection an EUSE (true) or not (false)? */
|
||||
static bool peer_name_is_euse(const struct global_title *peer_name)
|
||||
{
|
||||
if (peer_name->len <= 5)
|
||||
return false;
|
||||
if (!strncmp((char *)(peer_name->val), "EUSE-", 5))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct hlr_euse *euse_by_name(const struct global_title *peer_name)
|
||||
{
|
||||
if (!peer_name_is_euse(peer_name))
|
||||
return NULL;
|
||||
|
||||
return euse_find(g_hlr, (const char*)(peer_name->val)+5);
|
||||
}
|
||||
|
||||
static int handle_ss(struct ss_session *ss, bool is_euse_originated, const struct osmo_gsup_message *gsup,
|
||||
const struct ss_request *req)
|
||||
{
|
||||
uint8_t comp_type = gsup->ss_info[0];
|
||||
|
||||
LOGPSS(ss, LOGL_INFO, "SS CompType=%s, OpCode=%s\n",
|
||||
gsm0480_comp_type_name(comp_type), gsm0480_op_code_name(req->opcode));
|
||||
|
||||
/**
|
||||
* FIXME: As we don't store any SS related information
|
||||
* (e.g. call forwarding preferences) in the database,
|
||||
* we don't handle "structured" SS requests at all.
|
||||
*/
|
||||
LOGPSS(ss, LOGL_NOTICE, "Structured SS requests are not supported, rejecting...\n");
|
||||
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_FACILITY_NOT_SUPPORTED);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/* Handle a USSD GSUP message for a given SS Session received from VLR or EUSE */
|
||||
static int handle_ussd(struct ss_session *ss, bool is_euse_originated, const struct osmo_gsup_message *gsup,
|
||||
const struct ss_request *req)
|
||||
{
|
||||
uint8_t comp_type = gsup->ss_info[0];
|
||||
struct msgb *msg_out;
|
||||
|
||||
LOGPSS(ss, LOGL_INFO, "USSD CompType=%s, OpCode=%s '%s'\n",
|
||||
gsm0480_comp_type_name(comp_type), gsm0480_op_code_name(req->opcode),
|
||||
req->ussd_text);
|
||||
|
||||
if ((ss->is_external && !ss->u.euse) || !ss->u.iuse) {
|
||||
LOGPSS(ss, LOGL_NOTICE, "USSD for unknown code '%s'\n", req->ussd_text);
|
||||
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SS_NOT_AVAILABLE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (is_euse_originated) {
|
||||
/* Received from EUSE, Forward to VLR */
|
||||
/* Need a non-const osmo_gsup_message, because sending might modify some (routing related?) parts. */
|
||||
struct osmo_gsup_message forward = *gsup;
|
||||
ss_gsup_send_to_ms(ss, g_hlr->gs, &forward);
|
||||
} else {
|
||||
/* Received from VLR (MS) */
|
||||
if (ss->is_external) {
|
||||
/* Forward to EUSE */
|
||||
struct global_title euse_name;
|
||||
struct osmo_gsup_conn *conn;
|
||||
global_title_set_str(&euse_name, "EUSE-%s", ss->u.euse->name);
|
||||
conn = gsup_route_find_gt(g_hlr->gs, &euse_name);
|
||||
if (!conn) {
|
||||
LOGPSS(ss, LOGL_ERROR, "Cannot find conn for EUSE %s\n",
|
||||
global_title_name(&euse_name));
|
||||
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
|
||||
} else {
|
||||
msg_out = osmo_gsup_msgb_alloc("GSUP USSD FW");
|
||||
OSMO_ASSERT(msg_out);
|
||||
osmo_gsup_encode(msg_out, gsup);
|
||||
osmo_gsup_conn_send(conn, msg_out);
|
||||
}
|
||||
} else {
|
||||
/* Handle internally */
|
||||
ss->u.iuse->handle_ussd(ss, gsup, req);
|
||||
/* Release session immediately */
|
||||
ss_session_free(ss);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* this function is called for any SS_REQ/SS_RESP messages from both the MSC/VLR side as well
|
||||
* as from the EUSE side */
|
||||
void rx_proc_ss_req(struct osmo_gsup_req *gsup_req)
|
||||
{
|
||||
struct hlr *hlr = g_hlr;
|
||||
struct ss_session *ss;
|
||||
struct ss_request req = {0};
|
||||
const struct osmo_gsup_message *gsup = &gsup_req->gsup;
|
||||
/* Remember whether this function should free the incoming gsup_req: if it is placed as ss->initial_req_from_*,
|
||||
* do not free it here. If not, free it here. */
|
||||
struct osmo_gsup_req *free_gsup_req = gsup_req;
|
||||
bool is_euse_originated = peer_name_is_euse(&gsup_req->source_name);
|
||||
|
||||
LOGP(DSS, LOGL_DEBUG, "%s/0x%08x: Process SS (%s)\n", gsup->imsi, gsup->session_id,
|
||||
osmo_gsup_session_state_name(gsup->session_state));
|
||||
|
||||
/* decode and find out what kind of SS message it is */
|
||||
if (gsup->ss_info && gsup->ss_info_len) {
|
||||
if (gsm0480_parse_facility_ie(gsup->ss_info, gsup->ss_info_len, &req)) {
|
||||
LOGP(DSS, LOGL_ERROR, "%s/0x%082x: Unable to parse SS request: %s\n",
|
||||
gsup->imsi, gsup->session_id,
|
||||
osmo_hexdump(gsup->ss_info, gsup->ss_info_len));
|
||||
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "error parsing SS request");
|
||||
return;
|
||||
}
|
||||
} else if (gsup->session_state != OSMO_GSUP_SESSION_STATE_END) {
|
||||
LOGP(DSS, LOGL_ERROR, "%s/0x%082x: Missing SS payload for '%s'\n",
|
||||
gsup->imsi, gsup->session_id,
|
||||
osmo_gsup_session_state_name(gsup->session_state));
|
||||
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "missing SS payload");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (gsup->session_state) {
|
||||
case OSMO_GSUP_SESSION_STATE_BEGIN:
|
||||
/* Check for overlapping Session ID usage */
|
||||
if (ss_session_find(hlr, gsup->imsi, gsup->session_id)) {
|
||||
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: BEGIN with non-unique session ID!\n",
|
||||
gsup->imsi, gsup->session_id);
|
||||
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "BEGIN with non-unique session ID");
|
||||
return;
|
||||
}
|
||||
ss = ss_session_alloc(hlr, gsup->imsi, gsup->session_id);
|
||||
if (!ss) {
|
||||
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: Unable to allocate SS session\n",
|
||||
gsup->imsi, gsup->session_id);
|
||||
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_NET_FAIL, "Unable to allocate SS session");
|
||||
return;
|
||||
}
|
||||
/* Get IPA name from VLR conn and save as ss->vlr_number */
|
||||
if (!is_euse_originated) {
|
||||
ss->initial_req_from_ms = gsup_req;
|
||||
free_gsup_req = NULL;
|
||||
ss->vlr_name = gsup_req->source_name;
|
||||
} else {
|
||||
ss->initial_req_from_euse = gsup_req;
|
||||
free_gsup_req = NULL;
|
||||
}
|
||||
if (ss_op_is_ussd(req.opcode)) {
|
||||
if (is_euse_originated) {
|
||||
/* EUSE->VLR: MT USSD. EUSE is known ('conn'), VLR is to be resolved */
|
||||
ss->u.euse = euse_by_name(&gsup_req->source_name);
|
||||
} else {
|
||||
/* VLR->EUSE: MO USSD. VLR is known ('conn'), EUSE is to be resolved */
|
||||
struct hlr_ussd_route *rt;
|
||||
rt = ussd_route_lookup_7bit(hlr, (const char *) req.ussd_text);
|
||||
if (rt) {
|
||||
if (rt->is_external) {
|
||||
ss->is_external = true;
|
||||
ss->u.euse = rt->u.euse;
|
||||
} else if (rt) {
|
||||
ss->is_external = false;
|
||||
ss->u.iuse = rt->u.iuse;
|
||||
}
|
||||
} else {
|
||||
if (hlr->euse_default) {
|
||||
ss->is_external = true;
|
||||
ss->u.euse = hlr->euse_default;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* dispatch unstructured SS to routing */
|
||||
handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req);
|
||||
} else {
|
||||
/* dispatch non-call SS to internal code */
|
||||
handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req);
|
||||
}
|
||||
break;
|
||||
case OSMO_GSUP_SESSION_STATE_CONTINUE:
|
||||
ss = ss_session_find(hlr, gsup->imsi, gsup->session_id);
|
||||
if (!ss) {
|
||||
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: CONTINUE for unknown SS session\n",
|
||||
gsup->imsi, gsup->session_id);
|
||||
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "CONTINUE for unknown SS session");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Reschedule self-destruction timer */
|
||||
if (g_hlr->ncss_guard_timeout > 0)
|
||||
osmo_timer_schedule(&ss->timeout, g_hlr->ncss_guard_timeout, 0);
|
||||
|
||||
if (ss_op_is_ussd(req.opcode)) {
|
||||
/* dispatch unstructured SS to routing */
|
||||
handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req);
|
||||
} else {
|
||||
/* dispatch non-call SS to internal code */
|
||||
handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req);
|
||||
}
|
||||
break;
|
||||
case OSMO_GSUP_SESSION_STATE_END:
|
||||
ss = ss_session_find(hlr, gsup->imsi, gsup->session_id);
|
||||
if (!ss) {
|
||||
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: END for unknown SS session\n",
|
||||
gsup->imsi, gsup->session_id);
|
||||
return;
|
||||
}
|
||||
|
||||
/* SS payload is optional for END */
|
||||
if (gsup->ss_info && gsup->ss_info_len) {
|
||||
if (ss_op_is_ussd(req.opcode)) {
|
||||
/* dispatch unstructured SS to routing */
|
||||
handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req);
|
||||
} else {
|
||||
/* dispatch non-call SS to internal code */
|
||||
handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req);
|
||||
}
|
||||
}
|
||||
|
||||
ss_session_free(ss);
|
||||
break;
|
||||
default:
|
||||
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: Unknown SS State %d\n", gsup->imsi,
|
||||
gsup->session_id, gsup->session_state);
|
||||
break;
|
||||
}
|
||||
|
||||
if (free_gsup_req)
|
||||
osmo_gsup_req_free(free_gsup_req);
|
||||
}
|
||||
|
||||
void rx_proc_ss_error(struct osmo_gsup_req *req)
|
||||
{
|
||||
LOGP(DSS, LOGL_NOTICE, "%s/0x%08x: Process SS ERROR (%s)\n", req->gsup.imsi, req->gsup.session_id,
|
||||
osmo_gsup_session_state_name(req->gsup.session_state));
|
||||
}
|
||||
60
src/hlr_ussd.h
Normal file
60
src/hlr_ussd.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/gsm/gsup.h>
|
||||
|
||||
#include "gsup_server.h"
|
||||
|
||||
#define NCSS_GUARD_TIMEOUT_DEFAULT 30
|
||||
|
||||
struct hlr_ussd_route {
|
||||
/* g_hlr.routes */
|
||||
struct llist_head list;
|
||||
const char *prefix;
|
||||
bool is_external;
|
||||
union {
|
||||
struct hlr_euse *euse;
|
||||
const struct hlr_iuse *iuse;
|
||||
} u;
|
||||
};
|
||||
|
||||
struct hlr_euse {
|
||||
/* list in the per-hlr list of EUSEs */
|
||||
struct llist_head list;
|
||||
struct hlr *hlr;
|
||||
/* name (must match the IPA ID tag) */
|
||||
const char *name;
|
||||
/* human-readable description */
|
||||
const char *description;
|
||||
|
||||
/* GSUP connection to the EUSE, if any */
|
||||
struct osmo_gsup_conn *conn;
|
||||
};
|
||||
|
||||
struct hlr_euse *euse_find(struct hlr *hlr, const char *name);
|
||||
struct hlr_euse *euse_alloc(struct hlr *hlr, const char *name);
|
||||
void euse_del(struct hlr_euse *euse);
|
||||
|
||||
const struct hlr_iuse *iuse_find(const char *name);
|
||||
|
||||
struct hlr_ussd_route *ussd_route_find_prefix(struct hlr *hlr, const char *prefix);
|
||||
struct hlr_ussd_route *ussd_route_prefix_alloc_int(struct hlr *hlr, const char *prefix,
|
||||
const struct hlr_iuse *iuse);
|
||||
struct hlr_ussd_route *ussd_route_prefix_alloc_ext(struct hlr *hlr, const char *prefix,
|
||||
struct hlr_euse *euse);
|
||||
void ussd_route_del(struct hlr_ussd_route *rt);
|
||||
|
||||
void rx_proc_ss_req(struct osmo_gsup_req *req);
|
||||
void rx_proc_ss_error(struct osmo_gsup_req *req);
|
||||
|
||||
struct ss_session;
|
||||
struct ss_request;
|
||||
|
||||
/* Internal USSD Handler */
|
||||
struct hlr_iuse {
|
||||
const char *name;
|
||||
/* call-back to be called for any incoming USSD messages for this IUSE */
|
||||
int (*handle_ussd)(struct ss_session *ss, const struct osmo_gsup_message *gsup, const struct ss_request *req);
|
||||
};
|
||||
374
src/hlr_vty.c
374
src/hlr_vty.c
@@ -1,9 +1,14 @@
|
||||
/* OsmoHLR VTY implementation */
|
||||
|
||||
/* (C) 2016 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
* (C) 2018 Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
* (C) 2018 Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* 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
|
||||
@@ -22,13 +27,19 @@
|
||||
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/vty/vty.h>
|
||||
#include <osmocom/vty/stats.h>
|
||||
#include <osmocom/vty/command.h>
|
||||
#include <osmocom/vty/logging.h>
|
||||
#include <osmocom/vty/misc.h>
|
||||
#include <osmocom/abis/ipa.h>
|
||||
|
||||
#include "db.h"
|
||||
#include "hlr.h"
|
||||
#include "hlr_vty.h"
|
||||
#include "hlr_vty_subscr.h"
|
||||
|
||||
static struct hlr *g_hlr = NULL;
|
||||
#include "hlr_ussd.h"
|
||||
#include "gsup_server.h"
|
||||
#include "dgsm.h"
|
||||
|
||||
struct cmd_node hlr_node = {
|
||||
HLR_NODE,
|
||||
@@ -63,6 +74,27 @@ DEFUN(cfg_gsup,
|
||||
static int config_write_hlr(struct vty *vty)
|
||||
{
|
||||
vty_out(vty, "hlr%s", VTY_NEWLINE);
|
||||
if (g_hlr->store_imei)
|
||||
vty_out(vty, " store-imei%s", VTY_NEWLINE);
|
||||
if (g_hlr->db_file_path && strcmp(g_hlr->db_file_path, HLR_DEFAULT_DB_FILE_PATH))
|
||||
vty_out(vty, " database %s%s", g_hlr->db_file_path, VTY_NEWLINE);
|
||||
if (g_hlr->subscr_create_on_demand) {
|
||||
const char *flags_str = "none";
|
||||
uint8_t flags = g_hlr->subscr_create_on_demand_flags;
|
||||
unsigned int rand_msisdn_len = g_hlr->subscr_create_on_demand_rand_msisdn_len;
|
||||
|
||||
if ((flags & DB_SUBSCR_FLAG_NAM_CS) && (flags & DB_SUBSCR_FLAG_NAM_PS))
|
||||
flags_str = "cs+ps";
|
||||
else if (flags & DB_SUBSCR_FLAG_NAM_CS)
|
||||
flags_str = "cs";
|
||||
else if (flags & DB_SUBSCR_FLAG_NAM_PS)
|
||||
flags_str = "ps";
|
||||
|
||||
if (rand_msisdn_len)
|
||||
vty_out(vty, " subscriber-create-on-demand %i %s%s", rand_msisdn_len, flags_str, VTY_NEWLINE);
|
||||
else
|
||||
vty_out(vty, " subscriber-create-on-demand no-msisdn %s%s", flags_str, VTY_NEWLINE);
|
||||
}
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -74,6 +106,33 @@ static int config_write_hlr_gsup(struct vty *vty)
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
static void show_one_conn(struct vty *vty, const struct osmo_gsup_conn *conn)
|
||||
{
|
||||
const struct ipa_server_conn *isc = conn->conn;
|
||||
char *name;
|
||||
int rc;
|
||||
|
||||
rc = osmo_gsup_conn_ccm_get(conn, (uint8_t **) &name, IPAC_IDTAG_SERNR);
|
||||
OSMO_ASSERT(rc);
|
||||
|
||||
vty_out(vty, " '%s' from %s:%5u, CS=%u, PS=%u, 3G_IND=%u%s",
|
||||
name, isc->addr, isc->port, conn->supports_cs, conn->supports_ps, conn->auc_3g_ind,
|
||||
VTY_NEWLINE);
|
||||
}
|
||||
|
||||
DEFUN(show_gsup_conn, show_gsup_conn_cmd,
|
||||
"show gsup-connections",
|
||||
SHOW_STR "GSUP Connections from VLRs, SGSNs, EUSEs\n")
|
||||
{
|
||||
struct osmo_gsup_server *gs = g_hlr->gs;
|
||||
struct osmo_gsup_conn *conn;
|
||||
|
||||
llist_for_each_entry(conn, &gs->clients, list)
|
||||
show_one_conn(vty, conn);
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_hlr_gsup_bind_ip,
|
||||
cfg_hlr_gsup_bind_ip_cmd,
|
||||
"bind ip A.B.C.D",
|
||||
@@ -88,12 +147,291 @@ DEFUN(cfg_hlr_gsup_bind_ip,
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_hlr_gsup_ipa_name,
|
||||
cfg_hlr_gsup_ipa_name_cmd,
|
||||
"ipa-name NAME",
|
||||
"Set the IPA name of this HLR, for proxying to remote HLRs\n"
|
||||
"A globally unique name for this HLR. For example: PLMN + redundancy server number: HLR-901-70-0. "
|
||||
"This name is used for GSUP routing and must be set if multiple HLRs interconnect (e.g. mslookup "
|
||||
"for Distributed GSM).\n")
|
||||
{
|
||||
if (vty->type != VTY_FILE) {
|
||||
vty_out(vty, "gsup/ipa-name: The GSUP IPA name cannot be changed at run-time; "
|
||||
"It can only be set in the configuraton file.%s", VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
g_hlr->gsup_unit_name.serno = talloc_strdup(g_hlr, argv[0]);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* USSD Entity
|
||||
***********************************************************************/
|
||||
|
||||
#include "hlr_ussd.h"
|
||||
|
||||
#define USSD_STR "USSD Configuration\n"
|
||||
#define UROUTE_STR "Routing Configuration\n"
|
||||
#define PREFIX_STR "Prefix-Matching Route\n" "USSD Prefix\n"
|
||||
|
||||
#define INT_CHOICE "(own-msisdn|own-imsi)"
|
||||
#define INT_STR "Internal USSD Handler\n" \
|
||||
"Respond with subscribers' own MSISDN\n" \
|
||||
"Respond with subscribers' own IMSI\n"
|
||||
|
||||
#define EXT_STR "External USSD Handler\n" \
|
||||
"Name of External USSD Handler (IPA CCM ID)\n"
|
||||
|
||||
DEFUN(cfg_ussd_route_pfx_int, cfg_ussd_route_pfx_int_cmd,
|
||||
"ussd route prefix PREFIX internal " INT_CHOICE,
|
||||
USSD_STR UROUTE_STR PREFIX_STR INT_STR)
|
||||
{
|
||||
const struct hlr_iuse *iuse = iuse_find(argv[1]);
|
||||
struct hlr_ussd_route *rt = ussd_route_find_prefix(g_hlr, argv[0]);
|
||||
if (rt) {
|
||||
vty_out(vty, "%% Cannot add [another?] route for prefix %s%s", argv[0], VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
ussd_route_prefix_alloc_int(g_hlr, argv[0], iuse);
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_ussd_route_pfx_ext, cfg_ussd_route_pfx_ext_cmd,
|
||||
"ussd route prefix PREFIX external EUSE",
|
||||
USSD_STR UROUTE_STR PREFIX_STR EXT_STR)
|
||||
{
|
||||
struct hlr_euse *euse = euse_find(g_hlr, argv[1]);
|
||||
struct hlr_ussd_route *rt = ussd_route_find_prefix(g_hlr, argv[0]);
|
||||
if (rt) {
|
||||
vty_out(vty, "%% Cannot add [another?] route for prefix %s%s", argv[0], VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
if (!euse) {
|
||||
vty_out(vty, "%% Cannot find euse '%s'%s", argv[1], VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
ussd_route_prefix_alloc_ext(g_hlr, argv[0], euse);
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_ussd_no_route_pfx, cfg_ussd_no_route_pfx_cmd,
|
||||
"no ussd route prefix PREFIX",
|
||||
NO_STR USSD_STR UROUTE_STR PREFIX_STR)
|
||||
{
|
||||
struct hlr_ussd_route *rt = ussd_route_find_prefix(g_hlr, argv[0]);
|
||||
if (!rt) {
|
||||
vty_out(vty, "%% Cannot find route for prefix %s%s", argv[0], VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
ussd_route_del(rt);
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_ussd_defaultroute, cfg_ussd_defaultroute_cmd,
|
||||
"ussd default-route external EUSE",
|
||||
USSD_STR "Configure default-route for all USSD to unknown destinations\n"
|
||||
EXT_STR)
|
||||
{
|
||||
struct hlr_euse *euse;
|
||||
|
||||
euse = euse_find(g_hlr, argv[0]);
|
||||
if (!euse) {
|
||||
vty_out(vty, "%% Cannot find EUSE %s%s", argv[0], VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (g_hlr->euse_default != euse) {
|
||||
vty_out(vty, "Switching default route from %s to %s%s",
|
||||
g_hlr->euse_default ? g_hlr->euse_default->name : "<none>",
|
||||
euse->name, VTY_NEWLINE);
|
||||
g_hlr->euse_default = euse;
|
||||
}
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_ussd_no_defaultroute, cfg_ussd_no_defaultroute_cmd,
|
||||
"no ussd default-route",
|
||||
NO_STR USSD_STR "Remove the default-route for all USSD to unknown destinations\n")
|
||||
{
|
||||
g_hlr->euse_default = NULL;
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_database, cfg_database_cmd,
|
||||
"database PATH",
|
||||
"Set the path to the HLR database file\n"
|
||||
"Relative or absolute file system path to the database file (default is '" HLR_DEFAULT_DB_FILE_PATH "')\n")
|
||||
{
|
||||
osmo_talloc_replace_string(g_hlr, &g_hlr->db_file_path, argv[0]);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
struct cmd_node euse_node = {
|
||||
EUSE_NODE,
|
||||
"%s(config-hlr-euse)# ",
|
||||
1,
|
||||
};
|
||||
|
||||
DEFUN(cfg_euse, cfg_euse_cmd,
|
||||
"euse NAME",
|
||||
"Configure a particular External USSD Entity\n"
|
||||
"Alphanumeric name of the External USSD Entity\n")
|
||||
{
|
||||
struct hlr_euse *euse;
|
||||
const char *id = argv[0];
|
||||
|
||||
euse = euse_find(g_hlr, id);
|
||||
if (!euse) {
|
||||
euse = euse_alloc(g_hlr, id);
|
||||
if (!euse)
|
||||
return CMD_WARNING;
|
||||
}
|
||||
vty->index = euse;
|
||||
vty->index_sub = &euse->description;
|
||||
vty->node = EUSE_NODE;
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_no_euse, cfg_no_euse_cmd,
|
||||
"no euse NAME",
|
||||
NO_STR "Remove a particular External USSD Entity\n"
|
||||
"Alphanumeric name of the External USSD Entity\n")
|
||||
{
|
||||
struct hlr_euse *euse = euse_find(g_hlr, argv[0]);
|
||||
if (!euse) {
|
||||
vty_out(vty, "%% Cannot remove non-existant EUSE %s%s", argv[0], VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
if (g_hlr->euse_default == euse) {
|
||||
vty_out(vty, "%% Cannot remove EUSE %s, it is the default route%s", argv[0], VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
euse_del(euse);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
static void dump_one_euse(struct vty *vty, struct hlr_euse *euse)
|
||||
{
|
||||
vty_out(vty, " euse %s%s", euse->name, VTY_NEWLINE);
|
||||
}
|
||||
|
||||
static int config_write_euse(struct vty *vty)
|
||||
{
|
||||
struct hlr_euse *euse;
|
||||
struct hlr_ussd_route *rt;
|
||||
|
||||
llist_for_each_entry(euse, &g_hlr->euse_list, list)
|
||||
dump_one_euse(vty, euse);
|
||||
|
||||
llist_for_each_entry(rt, &g_hlr->ussd_routes, list) {
|
||||
vty_out(vty, " ussd route prefix %s %s %s%s", rt->prefix,
|
||||
rt->is_external ? "external" : "internal",
|
||||
rt->is_external ? rt->u.euse->name : rt->u.iuse->name,
|
||||
VTY_NEWLINE);
|
||||
}
|
||||
|
||||
if (g_hlr->euse_default)
|
||||
vty_out(vty, " ussd default-route external %s%s", g_hlr->euse_default->name, VTY_NEWLINE);
|
||||
|
||||
if (g_hlr->ncss_guard_timeout != NCSS_GUARD_TIMEOUT_DEFAULT)
|
||||
vty_out(vty, " ncss-guard-timeout %i%s",
|
||||
g_hlr->ncss_guard_timeout, VTY_NEWLINE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFUN(cfg_ncss_guard_timeout, cfg_ncss_guard_timeout_cmd,
|
||||
"ncss-guard-timeout <0-255>",
|
||||
"Set guard timer for NCSS (call independent SS) session activity\n"
|
||||
"Guard timer value (sec.), or 0 to disable")
|
||||
{
|
||||
g_hlr->ncss_guard_timeout = atoi(argv[0]);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_store_imei, cfg_store_imei_cmd,
|
||||
"store-imei",
|
||||
"Save the IMEI in the database when receiving Check IMEI requests. Note that an MSC does not necessarily send"
|
||||
" Check IMEI requests (for OsmoMSC, you may want to set 'check-imei-rqd 1').")
|
||||
{
|
||||
g_hlr->store_imei = true;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_no_store_imei, cfg_no_store_imei_cmd,
|
||||
"no store-imei",
|
||||
"Do not save the IMEI in the database, when receiving Check IMEI requests.")
|
||||
{
|
||||
g_hlr->store_imei = false;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_subscr_create_on_demand, cfg_subscr_create_on_demand_cmd,
|
||||
"subscriber-create-on-demand (no-msisdn|<3-15>) (none|cs|ps|cs+ps)",
|
||||
"Make a new record when a subscriber is first seen.\n"
|
||||
"Do not automatically assign MSISDN.\n"
|
||||
"Length of an automatically assigned MSISDN.\n"
|
||||
"Do not allow any NAM (Network Access Mode) by default.\n"
|
||||
"Allow access to circuit switched NAM by default.\n"
|
||||
"Allow access to packet switched NAM by default.\n"
|
||||
"Allow access to circuit and packet switched NAM by default.\n")
|
||||
{
|
||||
unsigned int rand_msisdn_len = 0;
|
||||
uint8_t flags = 0x00;
|
||||
|
||||
if (strcmp(argv[0], "no-msisdn") != 0)
|
||||
rand_msisdn_len = atoi(argv[0]);
|
||||
|
||||
if (strstr(argv[1], "cs"))
|
||||
flags |= DB_SUBSCR_FLAG_NAM_CS;
|
||||
if (strstr(argv[1], "ps"))
|
||||
flags |= DB_SUBSCR_FLAG_NAM_PS;
|
||||
|
||||
g_hlr->subscr_create_on_demand = true;
|
||||
g_hlr->subscr_create_on_demand_rand_msisdn_len = rand_msisdn_len;
|
||||
g_hlr->subscr_create_on_demand_flags = flags;
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_no_subscr_create_on_demand, cfg_no_subscr_create_on_demand_cmd,
|
||||
"no subscriber-create-on-demand",
|
||||
"Do not make a new record when a subscriber is first seen.\n")
|
||||
{
|
||||
g_hlr->subscr_create_on_demand = false;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* Common Code
|
||||
***********************************************************************/
|
||||
|
||||
int hlr_vty_go_parent(struct vty *vty)
|
||||
{
|
||||
switch (vty->node) {
|
||||
case GSUP_NODE:
|
||||
case EUSE_NODE:
|
||||
vty->node = HLR_NODE;
|
||||
vty->index = NULL;
|
||||
vty->index_sub = NULL;
|
||||
break;
|
||||
case MSLOOKUP_CLIENT_NODE:
|
||||
case MSLOOKUP_SERVER_NODE:
|
||||
vty->node = CONFIG_NODE;
|
||||
vty->index = NULL;
|
||||
vty->index_sub = NULL;
|
||||
break;
|
||||
case MSLOOKUP_SERVER_MSC_NODE:
|
||||
vty->node = CONFIG_NODE;
|
||||
vty->index = NULL;
|
||||
vty->index_sub = NULL;
|
||||
break;
|
||||
default:
|
||||
case HLR_NODE:
|
||||
@@ -121,21 +459,39 @@ int hlr_vty_is_config_node(struct vty *vty, int node)
|
||||
}
|
||||
}
|
||||
|
||||
void hlr_vty_init(struct hlr *hlr, const struct log_info *cat)
|
||||
void hlr_vty_init(void)
|
||||
{
|
||||
g_hlr = hlr;
|
||||
logging_vty_add_cmds();
|
||||
osmo_talloc_vty_add_cmds();
|
||||
osmo_stats_vty_add_cmds();
|
||||
|
||||
logging_vty_add_cmds(cat);
|
||||
install_element_ve(&show_gsup_conn_cmd);
|
||||
|
||||
install_element(CONFIG_NODE, &cfg_hlr_cmd);
|
||||
install_node(&hlr_node, config_write_hlr);
|
||||
install_default(HLR_NODE);
|
||||
|
||||
install_element(HLR_NODE, &cfg_gsup_cmd);
|
||||
install_node(&gsup_node, config_write_hlr_gsup);
|
||||
install_default(GSUP_NODE);
|
||||
|
||||
install_element(GSUP_NODE, &cfg_hlr_gsup_bind_ip_cmd);
|
||||
install_element(GSUP_NODE, &cfg_hlr_gsup_ipa_name_cmd);
|
||||
|
||||
hlr_vty_subscriber_init(hlr);
|
||||
install_element(HLR_NODE, &cfg_database_cmd);
|
||||
|
||||
install_element(HLR_NODE, &cfg_euse_cmd);
|
||||
install_element(HLR_NODE, &cfg_no_euse_cmd);
|
||||
install_node(&euse_node, config_write_euse);
|
||||
install_element(HLR_NODE, &cfg_ussd_route_pfx_int_cmd);
|
||||
install_element(HLR_NODE, &cfg_ussd_route_pfx_ext_cmd);
|
||||
install_element(HLR_NODE, &cfg_ussd_no_route_pfx_cmd);
|
||||
install_element(HLR_NODE, &cfg_ussd_defaultroute_cmd);
|
||||
install_element(HLR_NODE, &cfg_ussd_no_defaultroute_cmd);
|
||||
install_element(HLR_NODE, &cfg_ncss_guard_timeout_cmd);
|
||||
install_element(HLR_NODE, &cfg_store_imei_cmd);
|
||||
install_element(HLR_NODE, &cfg_no_store_imei_cmd);
|
||||
install_element(HLR_NODE, &cfg_subscr_create_on_demand_cmd);
|
||||
install_element(HLR_NODE, &cfg_no_subscr_create_on_demand_cmd);
|
||||
|
||||
hlr_vty_subscriber_init();
|
||||
dgsm_vty_init();
|
||||
}
|
||||
|
||||
@@ -30,8 +30,13 @@
|
||||
enum hlr_vty_node {
|
||||
HLR_NODE = _LAST_OSMOVTY_NODE + 1,
|
||||
GSUP_NODE,
|
||||
EUSE_NODE,
|
||||
MSLOOKUP_NODE,
|
||||
MSLOOKUP_SERVER_NODE,
|
||||
MSLOOKUP_SERVER_MSC_NODE,
|
||||
MSLOOKUP_CLIENT_NODE,
|
||||
};
|
||||
|
||||
int hlr_vty_is_config_node(struct vty *vty, int node);
|
||||
int hlr_vty_go_parent(struct vty *vty);
|
||||
void hlr_vty_init(struct hlr *hlr, const struct log_info *cat);
|
||||
void hlr_vty_init(void);
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <osmocom/gsm/gsm23003.h>
|
||||
#include <osmocom/vty/vty.h>
|
||||
@@ -33,18 +35,38 @@ struct vty;
|
||||
|
||||
#define hexdump_buf(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf))
|
||||
|
||||
static struct hlr *g_hlr = NULL;
|
||||
static char *
|
||||
get_datestr(const time_t *t, char *datebuf)
|
||||
{
|
||||
char *p, *s = ctime_r(t, datebuf);
|
||||
|
||||
/* Strip trailing newline. */
|
||||
p = strchr(s, '\n');
|
||||
if (p)
|
||||
*p = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
|
||||
{
|
||||
int rc;
|
||||
struct osmo_sub_auth_data aud2g;
|
||||
struct osmo_sub_auth_data aud3g;
|
||||
char datebuf[26]; /* for ctime_r(3) */
|
||||
|
||||
vty_out(vty, " ID: %"PRIu64"%s", subscr->id, VTY_NEWLINE);
|
||||
|
||||
vty_out(vty, " IMSI: %s%s", *subscr->imsi ? subscr->imsi : "none", VTY_NEWLINE);
|
||||
vty_out(vty, " MSISDN: %s%s", *subscr->msisdn ? subscr->msisdn : "none", VTY_NEWLINE);
|
||||
|
||||
if (*subscr->imei) {
|
||||
char checksum = osmo_luhn(subscr->imei, 14);
|
||||
if (checksum == -EINVAL)
|
||||
vty_out(vty, " IMEI: %s (INVALID LENGTH!)%s", subscr->imei, VTY_NEWLINE);
|
||||
else
|
||||
vty_out(vty, " IMEI: %s%c%s", subscr->imei, checksum, VTY_NEWLINE);
|
||||
}
|
||||
|
||||
if (*subscr->vlr_number)
|
||||
vty_out(vty, " VLR number: %s%s", subscr->vlr_number, VTY_NEWLINE);
|
||||
if (*subscr->sgsn_number)
|
||||
@@ -65,6 +87,8 @@ static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
|
||||
vty_out(vty, " PS disabled%s", VTY_NEWLINE);
|
||||
if (subscr->ms_purged_ps)
|
||||
vty_out(vty, " PS purged%s", VTY_NEWLINE);
|
||||
if (subscr->last_lu_seen)
|
||||
vty_out(vty, " last LU seen: %s UTC%s", get_datestr(&subscr->last_lu_seen, datebuf), VTY_NEWLINE);
|
||||
|
||||
if (!*subscr->imsi)
|
||||
return;
|
||||
@@ -72,14 +96,17 @@ static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
|
||||
OSMO_ASSERT(g_hlr);
|
||||
rc = db_get_auth_data(g_hlr->dbc, subscr->imsi, &aud2g, &aud3g, NULL);
|
||||
|
||||
if (rc) {
|
||||
if (rc == -ENOENT) {
|
||||
aud2g.algo = OSMO_AUTH_ALG_NONE;
|
||||
aud3g.algo = OSMO_AUTH_ALG_NONE;
|
||||
} else {
|
||||
vty_out(vty, "%% Error retrieving data from database (%d)%s", rc, VTY_NEWLINE);
|
||||
return;
|
||||
}
|
||||
switch (rc) {
|
||||
case 0:
|
||||
break;
|
||||
case -ENOENT:
|
||||
case -ENOKEY:
|
||||
aud2g.algo = OSMO_AUTH_ALG_NONE;
|
||||
aud3g.algo = OSMO_AUTH_ALG_NONE;
|
||||
break;
|
||||
default:
|
||||
vty_out(vty, "%% Error retrieving data from database (%d)%s", rc, VTY_NEWLINE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (aud2g.type != OSMO_AUTH_TYPE_NONE && aud2g.type != OSMO_AUTH_TYPE_GSM) {
|
||||
@@ -113,6 +140,7 @@ static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
|
||||
|
||||
static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id, struct hlr_subscriber *subscr)
|
||||
{
|
||||
char imei_buf[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1];
|
||||
int rc = -1;
|
||||
if (strcmp(type, "imsi") == 0)
|
||||
rc = db_subscr_get_by_imsi(g_hlr->dbc, id, subscr);
|
||||
@@ -120,6 +148,17 @@ static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id,
|
||||
rc = db_subscr_get_by_msisdn(g_hlr->dbc, id, subscr);
|
||||
else if (strcmp(type, "id") == 0)
|
||||
rc = db_subscr_get_by_id(g_hlr->dbc, atoll(id), subscr);
|
||||
else if (strcmp(type, "imei") == 0) {
|
||||
/* Verify IMEI with checksum digit */
|
||||
if (osmo_imei_str_valid(id, true)) {
|
||||
/* Cut the checksum off */
|
||||
osmo_strlcpy(imei_buf, id, sizeof(imei_buf));
|
||||
id = imei_buf;
|
||||
vty_out(vty, "%% Checksum validated and stripped for search: imei = '%s'%s", id,
|
||||
VTY_NEWLINE);
|
||||
}
|
||||
rc = db_subscr_get_by_imei(g_hlr->dbc, id, subscr);
|
||||
}
|
||||
if (rc)
|
||||
vty_out(vty, "%% No subscriber for %s = '%s'%s",
|
||||
type, id, VTY_NEWLINE);
|
||||
@@ -129,18 +168,20 @@ static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id,
|
||||
#define SUBSCR_CMD "subscriber "
|
||||
#define SUBSCR_CMD_HELP "Subscriber management commands\n"
|
||||
|
||||
#define SUBSCR_ID "(imsi|msisdn|id) IDENT "
|
||||
#define SUBSCR_ID "(imsi|msisdn|id|imei) IDENT"
|
||||
#define SUBSCR_ID_HELP \
|
||||
"Identify subscriber by IMSI\n" \
|
||||
"Identify subscriber by MSISDN (phone number)\n" \
|
||||
"Identify subscriber by database ID\n" \
|
||||
"IMSI/MSISDN/ID of the subscriber\n"
|
||||
"Identify subscriber by IMEI\n" \
|
||||
"IMSI/MSISDN/ID/IMEI of the subscriber\n"
|
||||
|
||||
#define SUBSCR SUBSCR_CMD SUBSCR_ID
|
||||
#define SUBSCR SUBSCR_CMD SUBSCR_ID " "
|
||||
#define SUBSCR_HELP SUBSCR_CMD_HELP SUBSCR_ID_HELP
|
||||
|
||||
#define SUBSCR_UPDATE SUBSCR "update "
|
||||
#define SUBSCR_UPDATE_HELP SUBSCR_HELP "Set or update subscriber data\n"
|
||||
#define SUBSCR_MSISDN_HELP "Set MSISDN (phone number) of the subscriber\n"
|
||||
|
||||
DEFUN(subscriber_show,
|
||||
subscriber_show_cmd,
|
||||
@@ -158,12 +199,17 @@ DEFUN(subscriber_show,
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
ALIAS(subscriber_show, show_subscriber_cmd,
|
||||
"show " SUBSCR_CMD SUBSCR_ID,
|
||||
SHOW_STR SUBSCR_CMD_HELP SUBSCR_ID_HELP);
|
||||
|
||||
DEFUN(subscriber_create,
|
||||
subscriber_create_cmd,
|
||||
SUBSCR_CMD "imsi IDENT create",
|
||||
SUBSCR_CMD_HELP
|
||||
"Create subscriber by IMSI\n"
|
||||
"IMSI/MSISDN/ID of the subscriber\n")
|
||||
"Identify subscriber by IMSI\n"
|
||||
"IMSI/MSISDN/ID of the subscriber\n"
|
||||
"Create subscriber by IMSI\n")
|
||||
{
|
||||
int rc;
|
||||
struct hlr_subscriber subscr;
|
||||
@@ -174,7 +220,7 @@ DEFUN(subscriber_create,
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
rc = db_subscr_create(g_hlr->dbc, imsi);
|
||||
rc = db_subscr_create(g_hlr->dbc, imsi, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS);
|
||||
|
||||
if (rc) {
|
||||
if (rc == -EEXIST)
|
||||
@@ -222,9 +268,9 @@ DEFUN(subscriber_delete,
|
||||
|
||||
DEFUN(subscriber_msisdn,
|
||||
subscriber_msisdn_cmd,
|
||||
SUBSCR_UPDATE "msisdn MSISDN",
|
||||
SUBSCR_UPDATE_HELP
|
||||
"Set MSISDN (phone number) of the subscriber\n"
|
||||
SUBSCR_UPDATE "msisdn (none|MSISDN)",
|
||||
SUBSCR_UPDATE_HELP SUBSCR_MSISDN_HELP
|
||||
"Remove MSISDN (phone number)\n"
|
||||
"New MSISDN (phone number)\n")
|
||||
{
|
||||
struct hlr_subscriber subscr;
|
||||
@@ -232,15 +278,19 @@ DEFUN(subscriber_msisdn,
|
||||
const char *id = argv[1];
|
||||
const char *msisdn = argv[2];
|
||||
|
||||
if (strlen(msisdn) > sizeof(subscr.msisdn) - 1) {
|
||||
vty_out(vty, "%% MSISDN is too long, max. %zu characters are allowed%s",
|
||||
sizeof(subscr.msisdn)-1, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
if (strcmp(msisdn, "none") == 0)
|
||||
msisdn = NULL;
|
||||
else {
|
||||
if (strlen(msisdn) > sizeof(subscr.msisdn) - 1) {
|
||||
vty_out(vty, "%% MSISDN is too long, max. %zu characters are allowed%s",
|
||||
sizeof(subscr.msisdn)-1, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (!osmo_msisdn_str_valid(msisdn)) {
|
||||
vty_out(vty, "%% MSISDN invalid: '%s'%s", msisdn, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
if (!osmo_msisdn_str_valid(msisdn)) {
|
||||
vty_out(vty, "%% MSISDN invalid: '%s'%s", msisdn, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
}
|
||||
|
||||
if (get_subscr_by_argv(vty, id_type, id, &subscr))
|
||||
@@ -252,8 +302,19 @@ DEFUN(subscriber_msisdn,
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
vty_out(vty, "%% Updated subscriber IMSI='%s' to MSISDN='%s'%s",
|
||||
subscr.imsi, msisdn, VTY_NEWLINE);
|
||||
if (msisdn) {
|
||||
vty_out(vty, "%% Updated subscriber IMSI='%s' to MSISDN='%s'%s",
|
||||
subscr.imsi, msisdn, VTY_NEWLINE);
|
||||
|
||||
if (db_subscr_get_by_msisdn(g_hlr->dbc, msisdn, &subscr) == 0)
|
||||
osmo_hlr_subscriber_update_notify(&subscr);
|
||||
} else {
|
||||
vty_out(vty, "%% Updated subscriber IMSI='%s': removed MSISDN%s",
|
||||
subscr.imsi, VTY_NEWLINE);
|
||||
|
||||
osmo_hlr_subscriber_update_notify(&subscr);
|
||||
}
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -331,7 +392,7 @@ DEFUN(subscriber_no_aud2g,
|
||||
|
||||
rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
|
||||
|
||||
if (rc) {
|
||||
if (rc && rc != -ENOENT) {
|
||||
vty_out(vty, "%% Error: cannot disable 2G auth data for IMSI='%s'%s",
|
||||
subscr.imsi, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
@@ -402,7 +463,7 @@ DEFUN(subscriber_no_aud3g,
|
||||
|
||||
rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
|
||||
|
||||
if (rc) {
|
||||
if (rc && rc != -ENOENT) {
|
||||
vty_out(vty, "%% Error: cannot disable 3G auth data for IMSI='%s'%s",
|
||||
subscr.imsi, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
@@ -469,11 +530,85 @@ DEFUN(subscriber_aud3g,
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
void hlr_vty_subscriber_init(struct hlr *hlr)
|
||||
DEFUN(subscriber_imei,
|
||||
subscriber_imei_cmd,
|
||||
SUBSCR_UPDATE "imei (none|IMEI)",
|
||||
SUBSCR_UPDATE_HELP
|
||||
"Set IMEI of the subscriber (normally populated from MSC, no need to set this manually)\n"
|
||||
"Forget IMEI\n"
|
||||
"Set IMEI (use for debug only!)\n")
|
||||
{
|
||||
g_hlr = hlr;
|
||||
struct hlr_subscriber subscr;
|
||||
const char *id_type = argv[0];
|
||||
const char *id = argv[1];
|
||||
const char *imei = argv[2];
|
||||
char imei_buf[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1];
|
||||
|
||||
if (strcmp(imei, "none") == 0)
|
||||
imei = NULL;
|
||||
else {
|
||||
/* Verify IMEI with checksum digit */
|
||||
if (osmo_imei_str_valid(imei, true)) {
|
||||
/* Cut the checksum off */
|
||||
osmo_strlcpy(imei_buf, imei, sizeof(imei_buf));
|
||||
imei = imei_buf;
|
||||
} else if (!osmo_imei_str_valid(imei, false)) {
|
||||
vty_out(vty, "%% IMEI invalid: '%s'%s", imei, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
}
|
||||
|
||||
if (get_subscr_by_argv(vty, id_type, id, &subscr))
|
||||
return CMD_WARNING;
|
||||
|
||||
if (db_subscr_update_imei_by_imsi(g_hlr->dbc, subscr.imsi, imei)) {
|
||||
vty_out(vty, "%% Error: cannot update IMEI for subscriber IMSI='%s'%s",
|
||||
subscr.imsi, VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (imei)
|
||||
vty_out(vty, "%% Updated subscriber IMSI='%s' to IMEI='%s'%s",
|
||||
subscr.imsi, imei, VTY_NEWLINE);
|
||||
else
|
||||
vty_out(vty, "%% Updated subscriber IMSI='%s': removed IMEI%s",
|
||||
subscr.imsi, VTY_NEWLINE);
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(subscriber_nam,
|
||||
subscriber_nam_cmd,
|
||||
SUBSCR_UPDATE "network-access-mode (none|cs|ps|cs+ps)",
|
||||
SUBSCR_UPDATE_HELP
|
||||
"Set Network Access Mode (NAM) of the subscriber\n"
|
||||
"Do not allow access to circuit switched or packet switched services\n"
|
||||
"Allow access to circuit switched services only\n"
|
||||
"Allow access to packet switched services only\n"
|
||||
"Allow access to both circuit and packet switched services\n")
|
||||
{
|
||||
struct hlr_subscriber subscr;
|
||||
const char *id_type = argv[0];
|
||||
const char *id = argv[1];
|
||||
bool nam_cs = strstr(argv[2], "cs");
|
||||
bool nam_ps = strstr(argv[2], "ps");
|
||||
|
||||
if (get_subscr_by_argv(vty, id_type, id, &subscr))
|
||||
return CMD_WARNING;
|
||||
|
||||
if (nam_cs != subscr.nam_cs)
|
||||
hlr_subscr_nam(g_hlr, &subscr, nam_cs, 0);
|
||||
if (nam_ps != subscr.nam_ps)
|
||||
hlr_subscr_nam(g_hlr, &subscr, nam_ps, 1);
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
void hlr_vty_subscriber_init(void)
|
||||
{
|
||||
install_element_ve(&subscriber_show_cmd);
|
||||
install_element_ve(&show_subscriber_cmd);
|
||||
install_element(ENABLE_NODE, &subscriber_create_cmd);
|
||||
install_element(ENABLE_NODE, &subscriber_delete_cmd);
|
||||
install_element(ENABLE_NODE, &subscriber_msisdn_cmd);
|
||||
@@ -481,4 +616,6 @@ void hlr_vty_subscriber_init(struct hlr *hlr)
|
||||
install_element(ENABLE_NODE, &subscriber_aud2g_cmd);
|
||||
install_element(ENABLE_NODE, &subscriber_no_aud3g_cmd);
|
||||
install_element(ENABLE_NODE, &subscriber_aud3g_cmd);
|
||||
install_element(ENABLE_NODE, &subscriber_imei_cmd);
|
||||
install_element(ENABLE_NODE, &subscriber_nam_cmd);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void hlr_vty_subscriber_init(struct hlr *hlr);
|
||||
void hlr_vty_subscriber_init(void);
|
||||
|
||||
@@ -19,6 +19,25 @@ const struct log_info_cat hlr_log_info_cat[] = {
|
||||
.color = "\033[1;33m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
[DSS] = {
|
||||
.name = "DSS",
|
||||
.description = "Supplementary Services",
|
||||
.color = "\033[1;34m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
[DDGSM] = {
|
||||
.name = "DDGSM",
|
||||
.description = "Distributed GSM: MS lookup and proxy",
|
||||
.color = "\033[1;35m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
[DLU] = {
|
||||
.name = "DLU",
|
||||
.description = "Location Updating",
|
||||
.color = "\033[1;33m",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
const struct log_info hlr_log_info = {
|
||||
|
||||
@@ -7,6 +7,9 @@ enum {
|
||||
DDB,
|
||||
DGSUP,
|
||||
DAUC,
|
||||
DSS,
|
||||
DDGSM,
|
||||
DLU,
|
||||
};
|
||||
|
||||
extern const struct log_info hlr_log_info;
|
||||
|
||||
287
src/lu_fsm.c
Normal file
287
src/lu_fsm.c
Normal file
@@ -0,0 +1,287 @@
|
||||
/* Roughly follwing "Process Update_Location_HLR" of TS 09.02 */
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/tdef.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
#include <osmocom/gsm/apn.h>
|
||||
#include <osmocom/gsm/gsm48_ie.h>
|
||||
|
||||
#include "logging.h"
|
||||
#include "hlr.h"
|
||||
#include "gsup_server.h"
|
||||
|
||||
#include "db.h"
|
||||
|
||||
#define LOG_LU(lu, level, fmt, args...) \
|
||||
LOGPFSML((lu)? (lu)->fi : NULL, level, fmt, ##args)
|
||||
|
||||
#define LOG_LU_REQ(lu, req, level, fmt, args...) \
|
||||
LOGPFSML((lu)? (lu)->fi : NULL, level, "%s:" fmt, \
|
||||
osmo_gsup_message_type_name((req)->gsup.message_type), ##args)
|
||||
|
||||
struct lu {
|
||||
struct llist_head entry;
|
||||
struct osmo_fsm_inst *fi;
|
||||
|
||||
struct osmo_gsup_req *update_location_req;
|
||||
|
||||
/* Subscriber state at time of initial Update Location Request */
|
||||
struct hlr_subscriber subscr;
|
||||
bool is_ps;
|
||||
|
||||
/* VLR requesting the LU. */
|
||||
struct global_title vlr_name;
|
||||
|
||||
/* If the LU request was received via a proxy and not immediately from a local VLR, this indicates the closest
|
||||
* peer that forwarded the GSUP message. */
|
||||
struct global_title via_proxy;
|
||||
};
|
||||
LLIST_HEAD(g_all_lu);
|
||||
|
||||
enum lu_fsm_event {
|
||||
LU_EV_RX_GSUP,
|
||||
};
|
||||
|
||||
enum lu_fsm_state {
|
||||
LU_ST_UNVALIDATED,
|
||||
LU_ST_WAIT_INSERT_DATA_RESULT,
|
||||
LU_ST_WAIT_LOCATION_CANCEL_RESULT,
|
||||
};
|
||||
|
||||
static const struct value_string lu_fsm_event_names[] = {
|
||||
OSMO_VALUE_STRING(LU_EV_RX_GSUP),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct osmo_tdef_state_timeout lu_fsm_timeouts[32] = {
|
||||
[LU_ST_WAIT_INSERT_DATA_RESULT] = { .T = -4222 },
|
||||
[LU_ST_WAIT_LOCATION_CANCEL_RESULT] = { .T = -4222 },
|
||||
};
|
||||
|
||||
#define lu_state_chg(lu, state) \
|
||||
osmo_tdef_fsm_inst_state_chg((lu)->fi, state, lu_fsm_timeouts, g_hlr_tdefs, 5)
|
||||
|
||||
static void lu_success(struct lu *lu)
|
||||
{
|
||||
if (!lu->update_location_req)
|
||||
LOG_LU(lu, LOGL_ERROR, "No request for this LU\n");
|
||||
else
|
||||
osmo_gsup_req_respond_msgt(lu->update_location_req, OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT);
|
||||
lu->update_location_req = NULL;
|
||||
osmo_fsm_inst_term(lu->fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
}
|
||||
|
||||
#define lu_failure(LU, CAUSE, log_msg, args...) do { \
|
||||
if (!(LU)->update_location_req) \
|
||||
LOG_LU(LU, LOGL_ERROR, "No request for this LU\n"); \
|
||||
else \
|
||||
osmo_gsup_req_respond_err((LU)->update_location_req, CAUSE, log_msg, ##args); \
|
||||
(LU)->update_location_req = NULL; \
|
||||
osmo_fsm_inst_term((LU)->fi, OSMO_FSM_TERM_REGULAR, NULL); \
|
||||
} while(0)
|
||||
|
||||
static struct osmo_fsm lu_fsm;
|
||||
|
||||
static void lu_start(struct osmo_gsup_req *update_location_req)
|
||||
{
|
||||
struct osmo_fsm_inst *fi;
|
||||
struct lu *lu;
|
||||
|
||||
OSMO_ASSERT(update_location_req);
|
||||
OSMO_ASSERT(update_location_req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST);
|
||||
|
||||
fi = osmo_fsm_inst_alloc(&lu_fsm, g_hlr, NULL, LOGL_DEBUG, update_location_req->gsup.imsi);
|
||||
OSMO_ASSERT(fi);
|
||||
|
||||
lu = talloc(fi, struct lu);
|
||||
OSMO_ASSERT(lu);
|
||||
fi->priv = lu;
|
||||
*lu = (struct lu){
|
||||
.fi = fi,
|
||||
.update_location_req = update_location_req,
|
||||
.vlr_name = update_location_req->source_name,
|
||||
.via_proxy = update_location_req->via_proxy,
|
||||
/* According to GSUP specs, OSMO_GSUP_CN_DOMAIN_PS is the default. */
|
||||
.is_ps = (update_location_req->gsup.cn_domain != OSMO_GSUP_CN_DOMAIN_CS),
|
||||
};
|
||||
llist_add(&lu->entry, &g_all_lu);
|
||||
|
||||
osmo_fsm_inst_update_id_f_sanitize(fi, '_', "%s:IMSI-%s", lu->is_ps ? "PS" : "CS", update_location_req->gsup.imsi);
|
||||
|
||||
if (!lu->vlr_name.len) {
|
||||
lu_failure(lu, GMM_CAUSE_NET_FAIL, "LU without a VLR");
|
||||
return;
|
||||
}
|
||||
|
||||
if (db_subscr_get_by_imsi(g_hlr->dbc, update_location_req->gsup.imsi, &lu->subscr) < 0) {
|
||||
lu_failure(lu, GMM_CAUSE_IMSI_UNKNOWN, "Subscriber does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if subscriber is generally permitted on CS or PS
|
||||
* service (as requested) */
|
||||
if (!lu->is_ps && !lu->subscr.nam_cs) {
|
||||
lu_failure(lu, GMM_CAUSE_PLMN_NOTALLOWED, "nam_cs == false");
|
||||
return;
|
||||
}
|
||||
if (lu->is_ps && !lu->subscr.nam_ps) {
|
||||
lu_failure(lu, GMM_CAUSE_GPRS_NOTALLOWED, "nam_ps == false");
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: Set subscriber tracing = deactive in VLR/SGSN */
|
||||
|
||||
#if 0
|
||||
/* Cancel in old VLR/SGSN, if new VLR/SGSN differs from old */
|
||||
if (!lu->is_ps && strcmp(subscr->vlr_number, vlr_number)) {
|
||||
lu_op_tx_cancel_old(lu);
|
||||
} else if (lu->is_ps && strcmp(subscr->sgsn_number, sgsn_number)) {
|
||||
lu_op_tx_cancel_old(lu);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Store the VLR / SGSN number with the subscriber, so we know where it was last seen. */
|
||||
if (lu->via_proxy.len) {
|
||||
LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s, via proxy %s\n",
|
||||
lu->is_ps ? "SGSN number" : "VLR number",
|
||||
global_title_name(&lu->vlr_name),
|
||||
global_title_name(&lu->via_proxy));
|
||||
} else {
|
||||
LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s\n",
|
||||
lu->is_ps ? "SGSN number" : "VLR number",
|
||||
global_title_name(&lu->vlr_name));
|
||||
}
|
||||
|
||||
if (db_subscr_lu(g_hlr->dbc, lu->subscr.id, &lu->vlr_name, lu->is_ps, &lu->via_proxy)) {
|
||||
lu_failure(lu, GMM_CAUSE_NET_FAIL, "Cannot update %s in the database",
|
||||
lu->is_ps ? "SGSN number" : "VLR number");
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: Subscriber allowed to roam in PLMN? */
|
||||
/* TODO: Update RoutingInfo */
|
||||
/* TODO: Reset Flag MS Purged (cs/ps) */
|
||||
/* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */
|
||||
|
||||
lu_state_chg(lu, LU_ST_WAIT_INSERT_DATA_RESULT);
|
||||
}
|
||||
|
||||
void lu_rx_gsup(struct osmo_gsup_req *req)
|
||||
{
|
||||
struct lu *lu;
|
||||
if (req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST)
|
||||
return lu_start(req);
|
||||
|
||||
llist_for_each_entry(lu, &g_all_lu, entry) {
|
||||
if (strcmp(lu->subscr.imsi, req->gsup.imsi))
|
||||
continue;
|
||||
if (osmo_fsm_inst_dispatch(lu->fi, LU_EV_RX_GSUP, req)) {
|
||||
LOG_LU_REQ(lu, req, LOGL_ERROR, "Cannot receive GSUP messages in this state\n");
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE,
|
||||
"LU does not accept GSUP rx");
|
||||
}
|
||||
return;
|
||||
}
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "No Location Updating in progress for this IMSI");
|
||||
}
|
||||
|
||||
static int lu_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
struct lu *lu = fi->priv;
|
||||
lu_failure(lu, GSM_CAUSE_NET_FAIL, "Timeout");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lu_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
||||
{
|
||||
struct lu *lu = fi->priv;
|
||||
if (lu->update_location_req)
|
||||
osmo_gsup_req_respond_err(lu->update_location_req, GSM_CAUSE_NET_FAIL, "LU aborted");
|
||||
lu->update_location_req = NULL;
|
||||
llist_del(&lu->entry);
|
||||
}
|
||||
|
||||
static void lu_fsm_wait_insert_data_result_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
/* Transmit Insert Data Request to the VLR */
|
||||
struct lu *lu = fi->priv;
|
||||
struct hlr_subscriber *subscr = &lu->subscr;
|
||||
struct osmo_gsup_message gsup;
|
||||
uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
|
||||
uint8_t apn[APN_MAXLEN];
|
||||
|
||||
if (osmo_gsup_create_insert_subscriber_data_msg(&gsup, subscr->imsi,
|
||||
subscr->msisdn, msisdn_enc, sizeof(msisdn_enc),
|
||||
apn, sizeof(apn),
|
||||
lu->is_ps? OSMO_GSUP_CN_DOMAIN_PS : OSMO_GSUP_CN_DOMAIN_CS)) {
|
||||
lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot encode Insert Subscriber Data message");
|
||||
return;
|
||||
}
|
||||
|
||||
if (osmo_gsup_req_respond_nonfinal(lu->update_location_req, &gsup))
|
||||
lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot send %s", osmo_gsup_message_type_name(gsup.message_type));
|
||||
}
|
||||
|
||||
void lu_fsm_wait_insert_data_result(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct lu *lu = fi->priv;
|
||||
struct osmo_gsup_req *req;
|
||||
|
||||
switch (event) {
|
||||
case LU_EV_RX_GSUP:
|
||||
req = data;
|
||||
break;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
|
||||
switch (req->gsup.message_type) {
|
||||
case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
|
||||
osmo_gsup_req_free(req);
|
||||
lu_success(lu);
|
||||
break;
|
||||
|
||||
case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
|
||||
lu_failure(lu, GMM_CAUSE_NET_FAIL, "Rx %s", osmo_gsup_message_type_name(req->gsup.message_type));
|
||||
break;
|
||||
|
||||
default:
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "unexpected message type in this state");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#define S(x) (1 << (x))
|
||||
|
||||
static const struct osmo_fsm_state lu_fsm_states[] = {
|
||||
[LU_ST_UNVALIDATED] = {
|
||||
.name = "UNVALIDATED",
|
||||
.out_state_mask = 0
|
||||
| S(LU_ST_WAIT_INSERT_DATA_RESULT)
|
||||
,
|
||||
},
|
||||
[LU_ST_WAIT_INSERT_DATA_RESULT] = {
|
||||
.name = "WAIT_INSERT_DATA_RESULT",
|
||||
.in_event_mask = 0
|
||||
| S(LU_EV_RX_GSUP)
|
||||
,
|
||||
.onenter = lu_fsm_wait_insert_data_result_onenter,
|
||||
.action = lu_fsm_wait_insert_data_result,
|
||||
},
|
||||
};
|
||||
|
||||
static struct osmo_fsm lu_fsm = {
|
||||
.name = "lu",
|
||||
.states = lu_fsm_states,
|
||||
.num_states = ARRAY_SIZE(lu_fsm_states),
|
||||
.log_subsys = DLU,
|
||||
.event_names = lu_fsm_event_names,
|
||||
.timer_cb = lu_fsm_timer_cb,
|
||||
.cleanup = lu_fsm_cleanup,
|
||||
};
|
||||
|
||||
static __attribute__((constructor)) void lu_fsm_init()
|
||||
{
|
||||
OSMO_ASSERT(osmo_fsm_register(&lu_fsm) == 0);
|
||||
}
|
||||
2
src/lu_fsm.h
Normal file
2
src/lu_fsm.h
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
void lu_rx_gsup(struct osmo_gsup_req *req);
|
||||
290
src/luop.c
290
src/luop.c
@@ -1,290 +0,0 @@
|
||||
/* OsmoHLR TX/RX lu operations */
|
||||
|
||||
/* (C) 2017 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* 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 <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/gsm/gsm48_ie.h>
|
||||
#include <osmocom/gsm/gsup.h>
|
||||
#include <osmocom/gsm/apn.h>
|
||||
|
||||
#include "gsup_server.h"
|
||||
#include "gsup_router.h"
|
||||
#include "logging.h"
|
||||
#include "luop.h"
|
||||
|
||||
const struct value_string lu_state_names[] = {
|
||||
{ LU_S_NULL, "NULL" },
|
||||
{ LU_S_LU_RECEIVED, "LU RECEIVED" },
|
||||
{ LU_S_CANCEL_SENT, "CANCEL SENT" },
|
||||
{ LU_S_CANCEL_ACK_RECEIVED, "CANCEL-ACK RECEIVED" },
|
||||
{ LU_S_ISD_SENT, "ISD SENT" },
|
||||
{ LU_S_ISD_ACK_RECEIVED, "ISD-ACK RECEIVED" },
|
||||
{ LU_S_COMPLETE, "COMPLETE" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
/* Transmit a given GSUP message for the given LU operation */
|
||||
static void _luop_tx_gsup(struct lu_operation *luop,
|
||||
const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
struct msgb *msg_out;
|
||||
|
||||
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP LUOP");
|
||||
osmo_gsup_encode(msg_out, gsup);
|
||||
|
||||
osmo_gsup_addr_send(luop->gsup_server, luop->peer,
|
||||
talloc_total_size(luop->peer),
|
||||
msg_out);
|
||||
}
|
||||
|
||||
static inline void fill_gsup_msg(struct osmo_gsup_message *out,
|
||||
const struct lu_operation *lu,
|
||||
enum osmo_gsup_message_type mt)
|
||||
{
|
||||
memset(out, 0, sizeof(struct osmo_gsup_message));
|
||||
if (lu)
|
||||
osmo_strlcpy(out->imsi, lu->subscr.imsi,
|
||||
GSM23003_IMSI_MAX_DIGITS + 1);
|
||||
out->message_type = mt;
|
||||
}
|
||||
|
||||
/* timer call-back in case LU operation doesn't receive an response */
|
||||
static void lu_op_timer_cb(void *data)
|
||||
{
|
||||
struct lu_operation *luop = data;
|
||||
|
||||
DEBUGP(DMAIN, "LU OP timer expired in state %s\n",
|
||||
get_value_string(lu_state_names, luop->state));
|
||||
|
||||
switch (luop->state) {
|
||||
case LU_S_CANCEL_SENT:
|
||||
break;
|
||||
case LU_S_ISD_SENT:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
lu_op_tx_error(luop, GMM_CAUSE_NET_FAIL);
|
||||
}
|
||||
|
||||
bool lu_op_fill_subscr(struct lu_operation *luop, struct db_context *dbc,
|
||||
const char *imsi)
|
||||
{
|
||||
struct hlr_subscriber *subscr = &luop->subscr;
|
||||
|
||||
if (db_subscr_get_by_imsi(dbc, imsi, subscr) < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct lu_operation *lu_op_alloc(struct osmo_gsup_server *srv)
|
||||
{
|
||||
struct lu_operation *luop;
|
||||
|
||||
luop = talloc_zero(srv, struct lu_operation);
|
||||
OSMO_ASSERT(luop);
|
||||
luop->gsup_server = srv;
|
||||
luop->timer.cb = lu_op_timer_cb;
|
||||
luop->timer.data = luop;
|
||||
|
||||
return luop;
|
||||
}
|
||||
|
||||
void lu_op_free(struct lu_operation *luop)
|
||||
{
|
||||
/* Only attempt to remove when it was ever added to a list. */
|
||||
if (luop->list.next)
|
||||
llist_del(&luop->list);
|
||||
talloc_free(luop);
|
||||
}
|
||||
|
||||
struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn)
|
||||
{
|
||||
uint8_t *peer_addr;
|
||||
struct lu_operation *luop = lu_op_alloc(conn->server);
|
||||
int rc = osmo_gsup_conn_ccm_get(conn, &peer_addr, IPAC_IDTAG_SERNR);
|
||||
if (rc < 0) {
|
||||
lu_op_free(luop);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
luop->peer = talloc_memdup(luop, peer_addr, rc);
|
||||
|
||||
return luop;
|
||||
}
|
||||
|
||||
/* FIXME: this doesn't seem to work at all */
|
||||
struct lu_operation *lu_op_by_imsi(const char *imsi,
|
||||
const struct llist_head *lst)
|
||||
{
|
||||
struct lu_operation *luop;
|
||||
|
||||
llist_for_each_entry(luop, lst, list) {
|
||||
if (!strcmp(imsi, luop->subscr.imsi))
|
||||
return luop;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void lu_op_statechg(struct lu_operation *luop, enum lu_state new_state)
|
||||
{
|
||||
enum lu_state old_state = luop->state;
|
||||
|
||||
DEBUGP(DMAIN, "LU OP state change: %s -> ",
|
||||
get_value_string(lu_state_names, old_state));
|
||||
DEBUGPC(DMAIN, "%s\n",
|
||||
get_value_string(lu_state_names, new_state));
|
||||
|
||||
luop->state = new_state;
|
||||
}
|
||||
|
||||
/* Send a msgb to a given address using routing */
|
||||
int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
|
||||
const uint8_t *addr, size_t addrlen,
|
||||
struct msgb *msg)
|
||||
{
|
||||
struct osmo_gsup_conn *conn;
|
||||
|
||||
conn = gsup_route_find(gs, addr, addrlen);
|
||||
if (!conn) {
|
||||
DEBUGP(DMAIN, "Cannot find route for addr %s\n", addr);
|
||||
msgb_free(msg);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return osmo_gsup_conn_send(conn, msg);
|
||||
}
|
||||
|
||||
/*! Transmit UPD_LOC_ERROR and destroy lu_operation */
|
||||
void lu_op_tx_error(struct lu_operation *luop, enum gsm48_gmm_cause cause)
|
||||
{
|
||||
struct osmo_gsup_message gsup;
|
||||
|
||||
DEBUGP(DMAIN, "%s: LU OP Tx Error (cause %s)\n",
|
||||
luop->subscr.imsi, get_value_string(gsm48_gmm_cause_names,
|
||||
cause));
|
||||
|
||||
fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR);
|
||||
gsup.cause = cause;
|
||||
|
||||
_luop_tx_gsup(luop, &gsup);
|
||||
|
||||
lu_op_free(luop);
|
||||
}
|
||||
|
||||
/*! Transmit UPD_LOC_RESULT and destroy lu_operation */
|
||||
void lu_op_tx_ack(struct lu_operation *luop)
|
||||
{
|
||||
struct osmo_gsup_message gsup;
|
||||
|
||||
fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT);
|
||||
//FIXME gsup.hlr_enc;
|
||||
|
||||
_luop_tx_gsup(luop, &gsup);
|
||||
|
||||
lu_op_free(luop);
|
||||
}
|
||||
|
||||
/*! Send Cancel Location to old VLR/SGSN */
|
||||
void lu_op_tx_cancel_old(struct lu_operation *luop)
|
||||
{
|
||||
struct osmo_gsup_message gsup;
|
||||
|
||||
OSMO_ASSERT(luop->state == LU_S_LU_RECEIVED);
|
||||
|
||||
fill_gsup_msg(&gsup, NULL, OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST);
|
||||
//gsup.cause = FIXME;
|
||||
//gsup.cancel_type = FIXME;
|
||||
|
||||
_luop_tx_gsup(luop, &gsup);
|
||||
|
||||
lu_op_statechg(luop, LU_S_CANCEL_SENT);
|
||||
osmo_timer_schedule(&luop->timer, CANCEL_TIMEOUT_SECS, 0);
|
||||
}
|
||||
|
||||
/*! Transmit Insert Subscriber Data to new VLR/SGSN */
|
||||
void lu_op_tx_insert_subscr_data(struct lu_operation *luop)
|
||||
{
|
||||
struct osmo_gsup_message gsup;
|
||||
uint8_t apn[APN_MAXLEN];
|
||||
uint8_t msisdn_enc[43]; /* TODO use constant; TS 24.008 10.5.4.7 */
|
||||
int l;
|
||||
|
||||
OSMO_ASSERT(luop->state == LU_S_LU_RECEIVED ||
|
||||
luop->state == LU_S_CANCEL_ACK_RECEIVED);
|
||||
|
||||
fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_INSERT_DATA_REQUEST);
|
||||
|
||||
l = gsm48_encode_bcd_number(msisdn_enc, sizeof(msisdn_enc), 0,
|
||||
luop->subscr.msisdn);
|
||||
if (l < 1) {
|
||||
LOGP(DMAIN, LOGL_ERROR,
|
||||
"%s: Error: cannot encode MSISDN '%s'\n",
|
||||
luop->subscr.imsi, luop->subscr.msisdn);
|
||||
lu_op_tx_error(luop, GMM_CAUSE_PROTO_ERR_UNSPEC);
|
||||
return;
|
||||
}
|
||||
gsup.msisdn_enc = msisdn_enc;
|
||||
gsup.msisdn_enc_len = l;
|
||||
|
||||
/* FIXME: deal with encoding the following data */
|
||||
gsup.hlr_enc;
|
||||
|
||||
if (luop->is_ps) {
|
||||
/* FIXME: PDP infos - use more fine-grained access control
|
||||
instead of wildcard APN */
|
||||
l = osmo_apn_from_str(apn, sizeof(apn), "*");
|
||||
if (l > 0) {
|
||||
gsup.pdp_infos[0].apn_enc = apn;
|
||||
gsup.pdp_infos[0].apn_enc_len = l;
|
||||
gsup.pdp_infos[0].have_info = 1;
|
||||
gsup.num_pdp_infos = 1;
|
||||
/* FIXME: use real value: */
|
||||
gsup.pdp_infos[0].context_id = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Send ISD to new VLR/SGSN */
|
||||
_luop_tx_gsup(luop, &gsup);
|
||||
|
||||
lu_op_statechg(luop, LU_S_ISD_SENT);
|
||||
osmo_timer_schedule(&luop->timer, ISD_TIMEOUT_SECS, 0);
|
||||
}
|
||||
|
||||
/*! Transmit Delete Subscriber Data to new VLR/SGSN.
|
||||
* The luop is not freed. */
|
||||
void lu_op_tx_del_subscr_data(struct lu_operation *luop)
|
||||
{
|
||||
struct osmo_gsup_message gsup;
|
||||
|
||||
fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_DELETE_DATA_REQUEST);
|
||||
|
||||
gsup.cn_domain = OSMO_GSUP_CN_DOMAIN_PS;
|
||||
|
||||
/* Send ISD to new VLR/SGSN */
|
||||
_luop_tx_gsup(luop, &gsup);
|
||||
}
|
||||
83
src/luop.h
83
src/luop.h
@@ -1,83 +0,0 @@
|
||||
/* OsmoHLR TX/RX lu operations */
|
||||
|
||||
/* (C) 2017 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/gsm/gsup.h>
|
||||
|
||||
#include "db.h"
|
||||
|
||||
#define CANCEL_TIMEOUT_SECS 30
|
||||
#define ISD_TIMEOUT_SECS 30
|
||||
|
||||
enum lu_state {
|
||||
LU_S_NULL,
|
||||
LU_S_LU_RECEIVED,
|
||||
LU_S_CANCEL_SENT,
|
||||
LU_S_CANCEL_ACK_RECEIVED,
|
||||
LU_S_ISD_SENT,
|
||||
LU_S_ISD_ACK_RECEIVED,
|
||||
LU_S_COMPLETE,
|
||||
};
|
||||
|
||||
extern const struct value_string lu_state_names[];
|
||||
|
||||
struct lu_operation {
|
||||
/*! entry in global list of location update operations */
|
||||
struct llist_head list;
|
||||
/*! to which gsup_server do we belong */
|
||||
struct osmo_gsup_server *gsup_server;
|
||||
/*! state of the location update */
|
||||
enum lu_state state;
|
||||
/*! CS (false) or PS (true) Location Update? */
|
||||
bool is_ps;
|
||||
/*! currently running timer */
|
||||
struct osmo_timer_list timer;
|
||||
|
||||
/*! subscriber related to this operation */
|
||||
struct hlr_subscriber subscr;
|
||||
/*! peer VLR/SGSN starting the request */
|
||||
uint8_t *peer;
|
||||
};
|
||||
|
||||
int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
|
||||
const uint8_t *addr, size_t addrlen,
|
||||
struct msgb *msg);
|
||||
|
||||
struct lu_operation *lu_op_alloc(struct osmo_gsup_server *srv);
|
||||
struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn);
|
||||
void lu_op_statechg(struct lu_operation *luop, enum lu_state new_state);
|
||||
bool lu_op_fill_subscr(struct lu_operation *luop, struct db_context *dbc,
|
||||
const char *imsi);
|
||||
struct lu_operation *lu_op_by_imsi(const char *imsi,
|
||||
const struct llist_head *lst);
|
||||
|
||||
void lu_op_tx_error(struct lu_operation *luop, enum gsm48_gmm_cause cause);
|
||||
void lu_op_tx_ack(struct lu_operation *luop);
|
||||
void lu_op_tx_cancel_old(struct lu_operation *luop);
|
||||
void lu_op_tx_insert_subscr_data(struct lu_operation *luop);
|
||||
void lu_op_tx_del_subscr_data(struct lu_operation *luop);
|
||||
|
||||
void lu_op_free(struct lu_operation *luop);
|
||||
282
src/mslookup_server.c
Normal file
282
src/mslookup_server.c
Normal file
@@ -0,0 +1,282 @@
|
||||
#include <string.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
#include <osmocom/mslookup/mslookup.h>
|
||||
#include "logging.h"
|
||||
#include "hlr.h"
|
||||
#include "db.h"
|
||||
#include "dgsm.h"
|
||||
#include "mslookup_server.h"
|
||||
#include "proxy.h"
|
||||
|
||||
static const struct osmo_mslookup_result not_found = {
|
||||
.rc = OSMO_MSLOOKUP_RC_NOT_FOUND,
|
||||
};
|
||||
|
||||
static void set_result(struct osmo_mslookup_result *result,
|
||||
const struct dgsm_service_host *service_host,
|
||||
uint32_t age)
|
||||
{
|
||||
if (!osmo_sockaddr_str_is_nonzero(&service_host->host_v4)
|
||||
&& !osmo_sockaddr_str_is_nonzero(&service_host->host_v6)) {
|
||||
*result = not_found;
|
||||
return;
|
||||
}
|
||||
result->rc = OSMO_MSLOOKUP_RC_OK;
|
||||
result->host_v4 = service_host->host_v4;
|
||||
result->host_v6 = service_host->host_v6;
|
||||
result->age = age;
|
||||
}
|
||||
|
||||
/* A remote entity is asking us whether we are the home HLR of the given subscriber. */
|
||||
static void mslookup_server_rx_hlr_gsup(const struct osmo_mslookup_query *query,
|
||||
struct osmo_mslookup_result *result)
|
||||
{
|
||||
struct dgsm_service_host *host;
|
||||
int rc;
|
||||
switch (query->id.type) {
|
||||
case OSMO_MSLOOKUP_ID_IMSI:
|
||||
rc = db_subscr_exists_by_imsi(g_hlr->dbc, query->id.imsi);
|
||||
break;
|
||||
case OSMO_MSLOOKUP_ID_MSISDN:
|
||||
rc = db_subscr_exists_by_msisdn(g_hlr->dbc, query->id.msisdn);
|
||||
break;
|
||||
default:
|
||||
LOGP(DDGSM, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type);
|
||||
*result = not_found;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
LOGP(DDGSM, LOGL_DEBUG, "%s: does not exist in local HLR\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
|
||||
*result = not_found;
|
||||
return;
|
||||
}
|
||||
|
||||
LOGP(DDGSM, LOGL_DEBUG, "%s: found in local HLR\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
|
||||
|
||||
/* Find a HLR/GSUP service set for the server (no MSC unit name) */
|
||||
host = dgsm_config_service_get(&dgsm_config_msc_wildcard, OSMO_MSLOOKUP_SERVICE_HLR_GSUP);
|
||||
if (!host) {
|
||||
struct dgsm_service_host gsup_bind = {};
|
||||
/* Try to use the locally configured GSUP bind address */
|
||||
osmo_sockaddr_str_from_str(&gsup_bind.host_v4, g_hlr->gsup_bind_addr, OSMO_GSUP_PORT);
|
||||
if (gsup_bind.host_v4.af == AF_INET6) {
|
||||
gsup_bind.host_v6 = gsup_bind.host_v4;
|
||||
gsup_bind.host_v4 = (struct osmo_sockaddr_str){};
|
||||
}
|
||||
set_result(result, &gsup_bind, 0);
|
||||
if (result->rc != OSMO_MSLOOKUP_RC_OK) {
|
||||
LOGP(DDGSM, LOGL_ERROR,
|
||||
"%s: subscriber found, but no service '" OSMO_MSLOOKUP_SERVICE_HLR_GSUP "' configured,"
|
||||
" and cannot use configured GSUP bind address %s in mslookup response."
|
||||
" Cannot service HLR lookup request\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
osmo_quote_str(g_hlr->gsup_bind_addr, -1));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
set_result(result, host, 0);
|
||||
if (result->rc != OSMO_MSLOOKUP_RC_OK) {
|
||||
LOGP(DDGSM, LOGL_ERROR,
|
||||
"Subscriber found, but error in service '" OSMO_MSLOOKUP_SERVICE_HLR_GSUP "' config:"
|
||||
" v4: " OSMO_SOCKADDR_STR_FMT " v6: " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&host->host_v4),
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&host->host_v6));
|
||||
}
|
||||
}
|
||||
|
||||
/* Look in the local HLR record: If the subscriber is "at home" in this HLR and is also currently located at a local
|
||||
* MSC, we will find a valid location updating with vlr_number, and no vlr_via_proxy entry. */
|
||||
static bool subscriber_has_done_lu_here_hlr(const struct osmo_mslookup_query *query,
|
||||
uint32_t *lu_age,
|
||||
struct global_title *local_msc_name)
|
||||
{
|
||||
struct hlr_subscriber subscr;
|
||||
struct timeval age_tv;
|
||||
int rc;
|
||||
uint32_t age;
|
||||
|
||||
switch (query->id.type) {
|
||||
case OSMO_MSLOOKUP_ID_IMSI:
|
||||
rc = db_subscr_get_by_imsi(g_hlr->dbc, query->id.imsi, &subscr);
|
||||
break;
|
||||
case OSMO_MSLOOKUP_ID_MSISDN:
|
||||
rc = db_subscr_get_by_msisdn(g_hlr->dbc, query->id.msisdn, &subscr);
|
||||
break;
|
||||
default:
|
||||
LOGP(DDGSM, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
LOGP(DDGSM, LOGL_DEBUG, "%s: does not exist in local HLR\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!subscr.vlr_number[0]) {
|
||||
LOGP(DDGSM, LOGL_DEBUG, "%s: not attached (vlr_number unset)\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
|
||||
}
|
||||
|
||||
if (subscr.vlr_via_proxy.len) {
|
||||
/* The MSC is behind a proxy, the subscriber is not attached to a local MSC but a remote one. That
|
||||
* remote proxy should instead respond to the service lookup request. */
|
||||
LOGP(DDGSM, LOGL_DEBUG, "%s: last attach is not at local MSC, but via proxy %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
global_title_name(&subscr.vlr_via_proxy));
|
||||
return false;
|
||||
}
|
||||
|
||||
age_tv = (struct timeval){ .tv_sec = subscr.last_lu_seen };
|
||||
age = timestamp_age(&age_tv);
|
||||
|
||||
if (age > g_hlr->mslookup.server.max_age) {
|
||||
LOGP(DDGSM, LOGL_ERROR, "%s: last attach was here, but too long ago: %us > %us\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
age, g_hlr->mslookup.server.max_age);
|
||||
return false;
|
||||
}
|
||||
|
||||
*lu_age = age;
|
||||
global_title_set_str(local_msc_name, subscr.vlr_number);
|
||||
LOGP(DDGSM, LOGL_DEBUG, "%s: attached %u seconds ago at local MSC %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
age, global_title_name(local_msc_name));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* Determine whether the subscriber with the given ID has routed a Location Updating via this HLR as first hop. Return
|
||||
* true if it is attached at a local MSC, and we are serving as proxy for a remote home HLR.
|
||||
*/
|
||||
static bool subscriber_has_done_lu_here_proxy(const struct osmo_mslookup_query *query,
|
||||
uint32_t *lu_age,
|
||||
struct global_title *local_msc_name)
|
||||
{
|
||||
const struct proxy_subscr *subscr;
|
||||
uint32_t age;
|
||||
|
||||
/* See the local HLR record. If the subscriber is "at home" in this HLR and is also currently located here, we
|
||||
* will find a valid location updating and no vlr_via_proxy entry. */
|
||||
subscr = proxy_subscr_get_by_imsi(g_hlr->proxy, query->id.imsi);
|
||||
|
||||
if (!subscr) {
|
||||
LOGP(DDGSM, LOGL_DEBUG, "%s: does not exist in GSUP proxy\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* We only need to care about CS LU, since only CS services need D-GSM routing. */
|
||||
age = timestamp_age(&subscr->cs.last_lu);
|
||||
|
||||
if (age > g_hlr->mslookup.server.max_age) {
|
||||
LOGP(DDGSM, LOGL_ERROR, "%s: last attach was here (proxy), but too long ago: %us > %us\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
age, g_hlr->mslookup.server.max_age);
|
||||
return false;
|
||||
}
|
||||
|
||||
*lu_age = age;
|
||||
*local_msc_name = subscr->cs.vlr_name;
|
||||
LOGP(DDGSM, LOGL_DEBUG, "%s: attached %u seconds ago at local MSC %s; proxying for remote HLR "
|
||||
OSMO_SOCKADDR_STR_FMT "\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
age, global_title_name(local_msc_name),
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&subscr->remote_hlr_addr));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool subscriber_has_done_lu_here(const struct osmo_mslookup_query *query,
|
||||
uint32_t *lu_age_p,
|
||||
struct global_title *local_msc_name)
|
||||
{
|
||||
uint32_t lu_age = 0;
|
||||
struct global_title msc_name = {};
|
||||
uint32_t proxy_lu_age = 0;
|
||||
struct global_title proxy_msc_name = {};
|
||||
|
||||
/* First ask the local HLR db, but if the local proxy record indicates a more recent LU, use that instead.
|
||||
* For all usual cases, only one of these will reflect a LU, even if a subscriber had more than one home HLR:
|
||||
* - if the subscriber is known here, we will never proxy.
|
||||
* - if the subscriber is not known here, this local HLR db will never record a LU.
|
||||
* However, if a subscriber was being proxied to a remote home HLR, and if then the subscriber was also added to
|
||||
* the local HLR database, there might occur a situation where both reflect a LU. So, to be safe against all
|
||||
* situations, compare the two entries.
|
||||
*/
|
||||
if (!subscriber_has_done_lu_here_hlr(query, &lu_age, &msc_name))
|
||||
lu_age = 0;
|
||||
if (!subscriber_has_done_lu_here_proxy(query, &proxy_lu_age, &proxy_msc_name))
|
||||
proxy_lu_age = 0;
|
||||
if (lu_age && proxy_lu_age) {
|
||||
LOGP(DDGSM, LOGL_DEBUG,
|
||||
"%s: a LU is on record both in the local HLR (age %us) and the GSUP proxy (age %us)\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
lu_age, proxy_lu_age);
|
||||
}
|
||||
/* If proxy has a younger lu, replace. */
|
||||
if (proxy_lu_age && (!lu_age || (proxy_lu_age < lu_age))) {
|
||||
lu_age = proxy_lu_age;
|
||||
msc_name = proxy_msc_name;
|
||||
}
|
||||
|
||||
if (!lu_age || !msc_name.len) {
|
||||
LOGP(DDGSM, LOGL_DEBUG, "%s: not attached here\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGP(DDGSM, LOGL_DEBUG, "%s: attached here, at MSC %s\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
global_title_name(&msc_name));
|
||||
*lu_age_p = lu_age;
|
||||
*local_msc_name = msc_name;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* A remote entity is asking us whether we are providing the given service for the given subscriber. */
|
||||
void osmo_mslookup_server_rx(const struct osmo_mslookup_query *query,
|
||||
struct osmo_mslookup_result *result)
|
||||
{
|
||||
const struct dgsm_service_host *service_host;
|
||||
uint32_t age;
|
||||
struct global_title msc_name;
|
||||
|
||||
/* A request for a home HLR: answer exactly if this is the subscriber's home HLR, i.e. the IMSI is listed in the
|
||||
* HLR database. */
|
||||
if (strcmp(query->service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP) == 0)
|
||||
return mslookup_server_rx_hlr_gsup(query, result);
|
||||
|
||||
/* All other service types: answer when the subscriber has done a LU that is either listed in the local HLR or
|
||||
* in the GSUP proxy database: i.e. if the subscriber has done a Location Updating at an MSC belonging to this
|
||||
* HLR. Respond with whichever services are configured in the osmo-hlr.cfg. */
|
||||
if (!subscriber_has_done_lu_here(query, &age, &msc_name)) {
|
||||
*result = not_found;
|
||||
return;
|
||||
}
|
||||
|
||||
/* We've detected a LU here. The MSC where the LU happened is stored in msc_unit_name, and the LU age is stored
|
||||
* in 'age'. Figure out the address configured for that MSC and service name. */
|
||||
service_host = dgsm_config_service_get(&msc_name, query->service);
|
||||
|
||||
if (!service_host) {
|
||||
/* Find such service set globally (no MSC unit name) */
|
||||
service_host = dgsm_config_service_get(&dgsm_config_msc_wildcard, query->service);
|
||||
}
|
||||
|
||||
if (!service_host) {
|
||||
LOGP(DDGSM, LOGL_ERROR,
|
||||
"%s: subscriber found, but no service %s configured, cannot service lookup request\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
|
||||
osmo_quote_str_c(OTC_SELECT, query->service, -1));
|
||||
*result = not_found;
|
||||
return;
|
||||
}
|
||||
|
||||
set_result(result, service_host, age);
|
||||
}
|
||||
7
src/mslookup_server.h
Normal file
7
src/mslookup_server.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
struct osmo_mslookup_query;
|
||||
struct osmo_mslookup_result;
|
||||
|
||||
void osmo_mslookup_server_rx(const struct osmo_mslookup_query *query,
|
||||
struct osmo_mslookup_result *result);
|
||||
166
src/mslookup_server_mdns.c
Normal file
166
src/mslookup_server_mdns.c
Normal file
@@ -0,0 +1,166 @@
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <osmocom/mslookup/mslookup.h>
|
||||
#include <osmocom/mslookup/mdns.h>
|
||||
#include "logging.h"
|
||||
#include "mslookup_server.h"
|
||||
#include "mslookup_server_mdns.h"
|
||||
|
||||
static void osmo_mslookup_server_mdns_tx(struct osmo_mslookup_server_mdns *server,
|
||||
const struct osmo_mdns_request *req,
|
||||
const struct osmo_mslookup_query *query,
|
||||
const struct osmo_mslookup_result *result)
|
||||
{
|
||||
const char *errmsg = NULL;
|
||||
struct msgb *msg;
|
||||
struct osmo_mdns_answer ans;
|
||||
struct osmo_mdns_record *rec_age;
|
||||
struct osmo_mdns_record rec_ip_v4 = {};
|
||||
struct osmo_mdns_record *rec_ip_v4_port;
|
||||
struct osmo_mdns_record rec_ip_v6 = {};
|
||||
struct osmo_mdns_record *rec_ip_v6_port;
|
||||
uint32_t ip_v4;
|
||||
struct in6_addr ip_v6;
|
||||
|
||||
void *ctx = talloc_named_const(server, 0, __func__);
|
||||
|
||||
LOGP(DDGSM, LOGL_DEBUG, "%s: sending mDNS response\n",
|
||||
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
|
||||
|
||||
osmo_mdns_answer_init(&ans);
|
||||
ans.id = req->id;
|
||||
ans.domain = req->domain;
|
||||
|
||||
rec_age = osmo_mdns_encode_txt_record(ctx, "age", "%u", result->age);
|
||||
llist_add_tail(&rec_age->list, &ans.records);
|
||||
|
||||
if (osmo_sockaddr_str_is_nonzero(&result->host_v4)) {
|
||||
if (osmo_sockaddr_str_to_32n(&result->host_v4, &ip_v4)) {
|
||||
errmsg = "Error encoding IPv4 address";
|
||||
goto clean_and_exit;
|
||||
}
|
||||
rec_ip_v4.type = OSMO_MSLOOKUP_MDNS_RECORD_TYPE_A;
|
||||
rec_ip_v4.data = (void*)&ip_v4;
|
||||
rec_ip_v4.length = sizeof(ip_v4);
|
||||
llist_add_tail(&rec_ip_v4.list, &ans.records);
|
||||
|
||||
rec_ip_v4_port = osmo_mdns_encode_txt_record(ctx, "port", "%u", result->host_v4.port);
|
||||
if (!rec_ip_v4_port) {
|
||||
errmsg = "Error encoding IPv4 port";
|
||||
goto clean_and_exit;
|
||||
}
|
||||
llist_add_tail(&rec_ip_v4_port->list, &ans.records);
|
||||
}
|
||||
|
||||
if (osmo_sockaddr_str_is_nonzero(&result->host_v6)) {
|
||||
if (osmo_sockaddr_str_to_in6_addr(&result->host_v6, &ip_v6)) {
|
||||
errmsg = "Error encoding IPv6 address";
|
||||
goto clean_and_exit;
|
||||
}
|
||||
|
||||
rec_ip_v6.type = OSMO_MSLOOKUP_MDNS_RECORD_TYPE_AAAA;
|
||||
rec_ip_v6.data = (void*)&ip_v6;
|
||||
rec_ip_v6.length = sizeof(ip_v6);
|
||||
llist_add_tail(&rec_ip_v6.list, &ans.records);
|
||||
|
||||
rec_ip_v6_port = osmo_mdns_encode_txt_record(ctx, "port", "%u", result->host_v6.port);
|
||||
if (!rec_ip_v6_port) {
|
||||
errmsg = "Error encoding IPv6 port";
|
||||
goto clean_and_exit;
|
||||
}
|
||||
llist_add_tail(&rec_ip_v6_port->list, &ans.records);
|
||||
}
|
||||
|
||||
msg = msgb_alloc(1024, __func__);
|
||||
if (osmo_mdns_encode_answer(ctx, msg, &ans)) {
|
||||
errmsg = "Error encoding DNS answer packet";
|
||||
goto clean_and_exit;
|
||||
}
|
||||
|
||||
if (osmo_mdns_sock_send(server->sock, msg))
|
||||
errmsg = "Error sending DNS answer";
|
||||
|
||||
clean_and_exit:
|
||||
if (errmsg)
|
||||
LOGP(DDGSM, LOGL_ERROR, "%s: DNS: %s\n", osmo_mslookup_result_name_c(ctx, query, result), errmsg);
|
||||
talloc_free(ctx);
|
||||
}
|
||||
|
||||
static void osmo_mslookup_server_mdns_handle_request(struct osmo_mslookup_server_mdns *server,
|
||||
const struct osmo_mdns_request *req)
|
||||
{
|
||||
struct osmo_mslookup_query query;
|
||||
struct osmo_mslookup_result result;
|
||||
|
||||
if (osmo_mslookup_query_from_domain_str(&query, req->domain)) {
|
||||
LOGP(DDGSM, LOGL_ERROR, "mDNS mslookup server: unable to parse request domain string: %s\n",
|
||||
osmo_quote_str_c(OTC_SELECT, req->domain, -1));
|
||||
return;
|
||||
}
|
||||
|
||||
osmo_mslookup_server_rx(&query, &result);
|
||||
/* Error logging already happens in osmo_mslookup_server_rx() */
|
||||
if (result.rc != OSMO_MSLOOKUP_RC_OK)
|
||||
return;
|
||||
|
||||
osmo_mslookup_server_mdns_tx(server, req, &query, &result);
|
||||
}
|
||||
|
||||
static int osmo_mslookup_server_mdns_rx(struct osmo_fd *osmo_fd, unsigned int what)
|
||||
{
|
||||
struct osmo_mslookup_server_mdns *server = osmo_fd->data;
|
||||
struct osmo_mdns_request *req;
|
||||
int n;
|
||||
uint8_t buffer[1024];
|
||||
void *ctx;
|
||||
|
||||
/* Parse the message and print it */
|
||||
n = read(osmo_fd->fd, buffer, sizeof(buffer));
|
||||
if (n < 0)
|
||||
return n;
|
||||
|
||||
ctx = talloc_named_const(server, 0, __func__);
|
||||
req = osmo_mdns_decode_request(ctx, buffer, n);
|
||||
if (!req) {
|
||||
LOGP(DDGSM, LOGL_DEBUG, "mDNS rx: ignoring: not a request\n");
|
||||
talloc_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOGP(DDGSM, LOGL_DEBUG, "mDNS rx request: %s\n", osmo_quote_str_c(OTC_SELECT, req->domain, -1));
|
||||
osmo_mslookup_server_mdns_handle_request(server, req);
|
||||
talloc_free(ctx);
|
||||
return n;
|
||||
}
|
||||
|
||||
struct osmo_mslookup_server_mdns *osmo_mslookup_server_mdns_start(void *ctx, const struct osmo_sockaddr_str *bind_addr)
|
||||
{
|
||||
struct osmo_mslookup_server_mdns *server = talloc_zero(ctx, struct osmo_mslookup_server_mdns);
|
||||
OSMO_ASSERT(server);
|
||||
*server = (struct osmo_mslookup_server_mdns){
|
||||
.bind_addr = *bind_addr,
|
||||
};
|
||||
|
||||
server->sock = osmo_mdns_sock_init(server,
|
||||
bind_addr->ip, bind_addr->port, true,
|
||||
osmo_mslookup_server_mdns_rx,
|
||||
server, 0);
|
||||
if (!server->sock) {
|
||||
LOGP(DDGSM, LOGL_ERROR,
|
||||
"mslookup mDNS server: error initializing multicast bind on " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(bind_addr));
|
||||
talloc_free(server);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
void osmo_mslookup_server_mdns_stop(struct osmo_mslookup_server_mdns *server)
|
||||
{
|
||||
if (!server)
|
||||
return;
|
||||
osmo_mdns_sock_cleanup(server->sock);
|
||||
talloc_free(server);
|
||||
}
|
||||
14
src/mslookup_server_mdns.h
Normal file
14
src/mslookup_server_mdns.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
#include <osmocom/mslookup/mdns_sock.h>
|
||||
|
||||
struct osmo_mslookup_server_mdns {
|
||||
struct osmo_mslookup_server *mslookup;
|
||||
struct osmo_sockaddr_str bind_addr;
|
||||
struct osmo_mdns_sock *sock;
|
||||
};
|
||||
|
||||
struct osmo_mslookup_server_mdns *osmo_mslookup_server_mdns_start(void *ctx, const struct osmo_sockaddr_str *bind_addr);
|
||||
void osmo_mslookup_server_mdns_stop(struct osmo_mslookup_server_mdns *server);
|
||||
239
src/osmo-euse-demo.c
Normal file
239
src/osmo-euse-demo.c
Normal file
@@ -0,0 +1,239 @@
|
||||
/* osmo-demo-euse: An External USSD Entity (EUSE) for demo purpose */
|
||||
|
||||
/* (C) 2018 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This program illustrates how to implement an external USSD application using
|
||||
* the existing osmocom libraries, particularly libosmocore, libosmogsm and libosmo-gsup-client.
|
||||
*
|
||||
* It will receive any MS-originated USSD message that is routed to it via the HLR, and
|
||||
* simply respond it quoted in the following string: 'You sent "foobar"' (assuming the original
|
||||
* message was 'foobar').
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/application.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
|
||||
#include <osmocom/gsm/gsup.h>
|
||||
#include <osmocom/gsm/gsm0480.h>
|
||||
#include <osmocom/gsm/protocol/gsm_04_80.h>
|
||||
|
||||
#include <osmocom/gsupclient/gsup_client.h>
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
static struct osmo_gsup_client *g_gc;
|
||||
|
||||
/*! send a SS/USSD response to a given imsi/session.
|
||||
* \param[in] gsupc GSUP client connection through which to send
|
||||
* \param[in] imsi IMSI of the subscriber
|
||||
* \param[in] session_id Unique identifier of SS session for which this response is
|
||||
* \param[in] gsup_msg_type GSUP message type (OSMO_GSUP_MSGT_PROC_SS_{REQUEST,RESULT,ERROR})
|
||||
* \param[in] final Is this the final result (true=END) or an intermediate result (false=CONTINUE)
|
||||
* \param[in] msg Optional binary/BER encoded SS date (for FACILITY IE). Can be NULL. Freed in
|
||||
* this function call.
|
||||
*/
|
||||
static int euse_tx_ss(struct osmo_gsup_client *gsupc, const char *imsi, uint32_t session_id,
|
||||
enum osmo_gsup_message_type gsup_msg_type, bool final, struct msgb *ss_msg)
|
||||
{
|
||||
struct osmo_gsup_message resp = {0};
|
||||
struct msgb *resp_msg;
|
||||
|
||||
switch (gsup_msg_type) {
|
||||
case OSMO_GSUP_MSGT_PROC_SS_REQUEST:
|
||||
case OSMO_GSUP_MSGT_PROC_SS_RESULT:
|
||||
case OSMO_GSUP_MSGT_PROC_SS_ERROR:
|
||||
break;
|
||||
default:
|
||||
msgb_free(ss_msg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
resp.message_type = gsup_msg_type;
|
||||
OSMO_STRLCPY_ARRAY(resp.imsi, imsi);
|
||||
if (final)
|
||||
resp.session_state = OSMO_GSUP_SESSION_STATE_END;
|
||||
else
|
||||
resp.session_state = OSMO_GSUP_SESSION_STATE_CONTINUE;
|
||||
resp.session_id = session_id;
|
||||
if (ss_msg) {
|
||||
resp.ss_info = msgb_data(ss_msg);
|
||||
resp.ss_info_len = msgb_length(ss_msg);
|
||||
}
|
||||
|
||||
resp_msg = gsm0480_msgb_alloc_name(__func__);
|
||||
OSMO_ASSERT(resp_msg);
|
||||
osmo_gsup_encode(resp_msg, &resp);
|
||||
msgb_free(ss_msg);
|
||||
return osmo_gsup_client_send(gsupc, resp_msg);
|
||||
}
|
||||
|
||||
/*! send a SS/USSD reject to a given IMSI/session.
|
||||
* \param[in] gsupc GSUP client connection through which to send
|
||||
* \param[in] imsi IMSI of the subscriber
|
||||
* \param[in] session_id Unique identifier of SS session for which this response is
|
||||
* \param[in] invoke_id InvokeID of the request
|
||||
* \param[in] problem_tag Problem code tag (table 3.13)
|
||||
* \param[in] problem_code Problem code (table 3.14-3.17)
|
||||
*/
|
||||
static int euse_tx_ussd_reject(struct osmo_gsup_client *gsupc, const char *imsi, uint32_t session_id,
|
||||
int invoke_id, uint8_t problem_tag, uint8_t problem_code)
|
||||
{
|
||||
struct msgb *msg = gsm0480_gen_reject(invoke_id, problem_tag, problem_code);
|
||||
LOGP(DMAIN, LOGL_NOTICE, "Tx %s/0x%08x: Reject(%d, 0x%02x, 0x%02x)\n", imsi, session_id,
|
||||
invoke_id, problem_tag, problem_code);
|
||||
OSMO_ASSERT(msg);
|
||||
return euse_tx_ss(gsupc, imsi, session_id, OSMO_GSUP_MSGT_PROC_SS_RESULT, true, msg);
|
||||
}
|
||||
|
||||
/*! send a SS/USSD response in 7-bit GSM default alphabet o a given imsi/session.
|
||||
* \param[in] gsupc GSUP client connection through which to send
|
||||
* \param[in] imsi IMSI of the subscriber
|
||||
* \param[in] session_id Unique identifier of SS session for which this response is
|
||||
* \param[in] final Is this the final result (true=END) or an intermediate result
|
||||
* (false=CONTINUE)
|
||||
* \param[in] invoke_id InvokeID of the request
|
||||
*/
|
||||
static int euse_tx_ussd_resp_7bit(struct osmo_gsup_client *gsupc, const char *imsi, uint32_t session_id,
|
||||
bool final, uint8_t invoke_id, const char *text)
|
||||
{
|
||||
struct msgb *ss_msg;
|
||||
|
||||
/* encode response; remove L3 header */
|
||||
ss_msg = gsm0480_gen_ussd_resp_7bit(invoke_id, text);
|
||||
LOGP(DMAIN, LOGL_DEBUG, "Tx %s/0x%08x: USSD Result(%d, %s, '%s')\n", imsi, session_id,
|
||||
invoke_id, final ? "END" : "CONTINUE", text);
|
||||
OSMO_ASSERT(ss_msg);
|
||||
return euse_tx_ss(gsupc, imsi, session_id, OSMO_GSUP_MSGT_PROC_SS_RESULT, final, ss_msg);
|
||||
}
|
||||
|
||||
static int euse_rx_proc_ss_req(struct osmo_gsup_client *gsupc, const struct osmo_gsup_message *gsup)
|
||||
{
|
||||
char buf[GSM0480_USSD_7BIT_STRING_LEN+1];
|
||||
struct ss_request req = {0};
|
||||
|
||||
if (gsup->ss_info && gsup->ss_info_len) {
|
||||
if (gsm0480_parse_facility_ie(gsup->ss_info, gsup->ss_info_len, &req)) {
|
||||
return euse_tx_ussd_reject(gsupc, gsup->imsi, gsup->session_id, -1,
|
||||
GSM_0480_PROBLEM_CODE_TAG_GENERAL,
|
||||
GSM_0480_GEN_PROB_CODE_BAD_STRUCTURE);
|
||||
}
|
||||
}
|
||||
|
||||
LOGP(DMAIN, LOGL_INFO, "Rx %s/0x%08x: USSD SessionState=%s, OpCode=%s, '%s'\n", gsup->imsi,
|
||||
gsup->session_id, osmo_gsup_session_state_name(gsup->session_state),
|
||||
gsm0480_op_code_name(req.opcode), req.ussd_text);
|
||||
|
||||
/* we only handle single-request-response USSD in this demo */
|
||||
if (gsup->session_state != OSMO_GSUP_SESSION_STATE_BEGIN) {
|
||||
return euse_tx_ussd_reject(gsupc, gsup->imsi, gsup->session_id, req.invoke_id,
|
||||
GSM_0480_PROBLEM_CODE_TAG_GENERAL,
|
||||
GSM_0480_GEN_PROB_CODE_UNRECOGNISED);
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "You sent \"%s\"", req.ussd_text);
|
||||
return euse_tx_ussd_resp_7bit(gsupc, gsup->imsi, gsup->session_id, true, req.invoke_id, buf);
|
||||
}
|
||||
|
||||
static int gsupc_read_cb(struct osmo_gsup_client *gsupc, struct msgb *msg)
|
||||
{
|
||||
struct osmo_gsup_message gsup_msg = {0};
|
||||
int rc;
|
||||
|
||||
rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup_msg);
|
||||
if (rc < 0) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "Error decoding GSUP: %s\n", msgb_hexdump(msg));
|
||||
return rc;
|
||||
}
|
||||
DEBUGP(DMAIN, "Rx GSUP %s: %s\n", osmo_gsup_message_type_name(gsup_msg.message_type),
|
||||
msgb_hexdump(msg));
|
||||
|
||||
switch (gsup_msg.message_type) {
|
||||
case OSMO_GSUP_MSGT_PROC_SS_REQUEST:
|
||||
case OSMO_GSUP_MSGT_PROC_SS_RESULT:
|
||||
euse_rx_proc_ss_req(gsupc, &gsup_msg);
|
||||
break;
|
||||
case OSMO_GSUP_MSGT_PROC_SS_ERROR:
|
||||
break;
|
||||
default:
|
||||
LOGP(DMAIN, LOGL_DEBUG, "Unhandled GSUP message type %s\n",
|
||||
osmo_gsup_message_type_name(gsup_msg.message_type));
|
||||
break;
|
||||
}
|
||||
|
||||
msgb_free(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct log_info_cat default_categories[] = {
|
||||
[DMAIN] = {
|
||||
.name = "DMAIN",
|
||||
.description = "Main Program",
|
||||
.enabled = 1, .loglevel = LOGL_DEBUG,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct log_info gsup_log_info = {
|
||||
.cat = default_categories,
|
||||
.num_cat = ARRAY_SIZE(default_categories),
|
||||
};
|
||||
|
||||
static void print_usage(void)
|
||||
{
|
||||
printf("Usage: osmo-euse-demo [hlr-ip [hlr-gsup-port]]\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
char *server_host = "127.0.0.1";
|
||||
uint16_t server_port = OSMO_GSUP_PORT;
|
||||
void *ctx = talloc_named_const(NULL, 0, "demo-euse");
|
||||
|
||||
osmo_init_logging2(ctx, &gsup_log_info);
|
||||
|
||||
printf("argc=%d\n", argc);
|
||||
|
||||
if (argc > 1) {
|
||||
if (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) {
|
||||
print_usage();
|
||||
exit(0);
|
||||
} else
|
||||
server_host = argv[1];
|
||||
}
|
||||
if (argc > 2)
|
||||
server_port = atoi(argv[2]);
|
||||
|
||||
g_gc = osmo_gsup_client_create(ctx, "EUSE-foobar", server_host, server_port, gsupc_read_cb, NULL);
|
||||
|
||||
while (1) {
|
||||
osmo_select_main(0);
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
173
src/proxy.c
Normal file
173
src/proxy.c
Normal file
@@ -0,0 +1,173 @@
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <string.h>
|
||||
#include <talloc.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocom/core/timer.h>
|
||||
|
||||
#include "logging.h"
|
||||
#include "proxy.h"
|
||||
|
||||
/* Why have a separate struct to add an llist_head entry?
|
||||
* This is to keep the option open to store the proxy data in the database instead, without any visible effect outside
|
||||
* of proxy.c. */
|
||||
struct proxy_subscr_listentry {
|
||||
struct llist_head entry;
|
||||
struct timeval last_update;
|
||||
struct proxy_subscr data;
|
||||
};
|
||||
|
||||
/* Central implementation to set a timestamp to the current time, in case we want to modify this in the future. */
|
||||
void timestamp_update(struct timeval *tv)
|
||||
{
|
||||
osmo_gettimeofday(tv, NULL);
|
||||
}
|
||||
|
||||
time_t timestamp_age(const struct timeval *last_update)
|
||||
{
|
||||
struct timeval age;
|
||||
struct timeval now;
|
||||
timestamp_update(&now);
|
||||
timersub(&now, last_update, &age);
|
||||
return age.tv_sec;
|
||||
}
|
||||
|
||||
static bool proxy_subscr_matches_imsi(const struct proxy_subscr *proxy_subscr, const char *imsi)
|
||||
{
|
||||
if (!proxy_subscr || !imsi)
|
||||
return false;
|
||||
return strcmp(proxy_subscr->imsi, imsi) == 0;
|
||||
}
|
||||
|
||||
static bool proxy_subscr_matches_msisdn(const struct proxy_subscr *proxy_subscr, const char *msisdn)
|
||||
{
|
||||
if (!proxy_subscr || !msisdn)
|
||||
return false;
|
||||
return strcmp(proxy_subscr->msisdn, msisdn) == 0;
|
||||
}
|
||||
|
||||
static struct proxy_subscr_listentry *_proxy_get_by_imsi(struct proxy *proxy, const char *imsi)
|
||||
{
|
||||
struct proxy_subscr_listentry *e;
|
||||
if (!proxy)
|
||||
return NULL;
|
||||
llist_for_each_entry(e, &proxy->subscr_list, entry) {
|
||||
if (proxy_subscr_matches_imsi(&e->data, imsi))
|
||||
return e;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct proxy_subscr_listentry *_proxy_get_by_msisdn(struct proxy *proxy, const char *msisdn)
|
||||
{
|
||||
struct proxy_subscr_listentry *e;
|
||||
if (!proxy)
|
||||
return NULL;
|
||||
llist_for_each_entry(e, &proxy->subscr_list, entry) {
|
||||
if (proxy_subscr_matches_msisdn(&e->data, msisdn))
|
||||
return e;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct proxy_subscr *proxy_subscr_get_by_imsi(struct proxy *proxy, const char *imsi)
|
||||
{
|
||||
struct proxy_subscr_listentry *e = _proxy_get_by_imsi(proxy, imsi);
|
||||
if (!e)
|
||||
return NULL;
|
||||
return &e->data;
|
||||
}
|
||||
|
||||
const struct proxy_subscr *proxy_subscr_get_by_msisdn(struct proxy *proxy, const char *msisdn)
|
||||
{
|
||||
struct proxy_subscr_listentry *e = _proxy_get_by_msisdn(proxy, msisdn);
|
||||
if (!e)
|
||||
return NULL;
|
||||
return &e->data;
|
||||
}
|
||||
|
||||
void proxy_subscrs_get_by_remote_hlr(struct proxy *proxy, const struct osmo_sockaddr_str *remote_hlr_addr,
|
||||
bool (*yield)(struct proxy *proxy, const struct proxy_subscr *subscr, void *data),
|
||||
void *data)
|
||||
{
|
||||
struct proxy_subscr_listentry *e;
|
||||
if (!proxy)
|
||||
return;
|
||||
llist_for_each_entry(e, &proxy->subscr_list, entry) {
|
||||
if (!osmo_sockaddr_str_ip_cmp(remote_hlr_addr, &e->data.remote_hlr_addr)) {
|
||||
if (!yield(proxy, &e->data, data))
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int proxy_subscr_update(struct proxy *proxy, const struct proxy_subscr *proxy_subscr)
|
||||
{
|
||||
struct proxy_subscr_listentry *e = _proxy_get_by_imsi(proxy, proxy_subscr->imsi);
|
||||
if (!e) {
|
||||
/* Does not exist yet */
|
||||
e = talloc_zero(proxy, struct proxy_subscr_listentry);
|
||||
llist_add(&e->entry, &proxy->subscr_list);
|
||||
}
|
||||
e->data = *proxy_subscr;
|
||||
timestamp_update(&e->last_update);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _proxy_subscr_del(struct proxy_subscr_listentry *e)
|
||||
{
|
||||
llist_del(&e->entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int proxy_subscr_del(struct proxy *proxy, const char *imsi)
|
||||
{
|
||||
struct proxy_subscr_listentry *e = _proxy_get_by_imsi(proxy, imsi);
|
||||
if (!e)
|
||||
return -ENOENT;
|
||||
return _proxy_subscr_del(e);
|
||||
}
|
||||
|
||||
/* Discard stale proxy entries. */
|
||||
static void proxy_cleanup(void *proxy_v)
|
||||
{
|
||||
struct proxy *proxy = proxy_v;
|
||||
struct proxy_subscr_listentry *e, *n;
|
||||
llist_for_each_entry_safe(e, n, &proxy->subscr_list, entry) {
|
||||
if (timestamp_age(&e->last_update) <= proxy->fresh_time)
|
||||
continue;
|
||||
_proxy_subscr_del(e);
|
||||
}
|
||||
if (proxy->gc_period)
|
||||
osmo_timer_schedule(&proxy->gc_timer, proxy->gc_period, 0);
|
||||
else
|
||||
LOGP(DDGSM, LOGL_NOTICE, "Proxy cleanup is switched off (gc_period == 0)\n");
|
||||
}
|
||||
|
||||
void proxy_set_gc_period(struct proxy *proxy, uint32_t gc_period)
|
||||
{
|
||||
proxy->gc_period = gc_period;
|
||||
proxy_cleanup(proxy);
|
||||
}
|
||||
|
||||
struct proxy *proxy_init(void *ctx)
|
||||
{
|
||||
struct proxy *proxy = talloc_zero(ctx, struct proxy);
|
||||
*proxy = (struct proxy){
|
||||
.fresh_time = 60*60,
|
||||
.gc_period = 60,
|
||||
};
|
||||
INIT_LLIST_HEAD(&proxy->subscr_list);
|
||||
|
||||
osmo_timer_setup(&proxy->gc_timer, proxy_cleanup, proxy);
|
||||
/* Invoke to trigger the first timer schedule */
|
||||
proxy_set_gc_period(proxy, proxy->gc_period);
|
||||
return proxy;
|
||||
}
|
||||
|
||||
void proxy_del(struct proxy *proxy)
|
||||
{
|
||||
osmo_timer_del(&proxy->gc_timer);
|
||||
talloc_free(proxy);
|
||||
}
|
||||
56
src/proxy.h
Normal file
56
src/proxy.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <time.h>
|
||||
#include <osmocom/gsm/protocol/gsm_23_003.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
#include "global_title.h"
|
||||
|
||||
void timestamp_update(struct timeval *timestamp);
|
||||
time_t timestamp_age(const struct timeval *timestamp);
|
||||
|
||||
struct proxy {
|
||||
struct llist_head subscr_list;
|
||||
|
||||
/* How long to keep proxy entries without a refresh, in seconds. */
|
||||
uint32_t fresh_time;
|
||||
|
||||
/* How often to garbage collect the proxy cache, period in seconds.
|
||||
* To change this and take effect immediately, rather use proxy_set_gc_period(). */
|
||||
uint32_t gc_period;
|
||||
|
||||
struct osmo_timer_list gc_timer;
|
||||
};
|
||||
|
||||
struct proxy_subscr_domain_state {
|
||||
struct global_title vlr_name;
|
||||
struct timeval last_lu;
|
||||
|
||||
#if 0
|
||||
/* not needed unless we ever want a system with multiple proxy hops: */
|
||||
/* Set if this is a middle proxy, i.e. a proxy behind another proxy.
|
||||
* That is mostly to know whether the MS is attached at a local MSC/SGSN or further away. */
|
||||
struct global_title vlr_via_proxy;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct proxy_subscr {
|
||||
char imsi[GSM23003_IMSI_MAX_DIGITS+1];
|
||||
char msisdn[GSM23003_MSISDN_MAX_DIGITS+1];
|
||||
struct osmo_sockaddr_str remote_hlr_addr;
|
||||
struct proxy_subscr_domain_state cs, ps;
|
||||
};
|
||||
|
||||
struct proxy *proxy_init(void *ctx);
|
||||
void proxy_del(struct proxy *proxy);
|
||||
void proxy_set_gc_period(struct proxy *proxy, uint32_t gc_period);
|
||||
|
||||
/* The API to access / modify proxy entries keeps the implementation opaque, to make sure that we can easily move proxy
|
||||
* storage to SQLite db. */
|
||||
const struct proxy_subscr *proxy_subscr_get_by_imsi(struct proxy *proxy, const char *imsi);
|
||||
const struct proxy_subscr *proxy_subscr_get_by_msisdn(struct proxy *proxy, const char *msisdn);
|
||||
void proxy_subscrs_get_by_remote_hlr(struct proxy *proxy, const struct osmo_sockaddr_str *remote_hlr_addr,
|
||||
bool (*yield)(struct proxy *proxy, const struct proxy_subscr *subscr, void *data),
|
||||
void *data);
|
||||
const struct proxy_subscr *proxy_subscr_get_by_imsi(struct proxy *proxy, const char *imsi);
|
||||
int proxy_subscr_update(struct proxy *proxy, const struct proxy_subscr *proxy_subscr);
|
||||
int proxy_subscr_del(struct proxy *proxy, const char *imsi);
|
||||
193
src/remote_hlr.c
Normal file
193
src/remote_hlr.c
Normal file
@@ -0,0 +1,193 @@
|
||||
#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
|
||||
#include <osmocom/gsm/gsup.h>
|
||||
#include <osmocom/abis/ipa.h>
|
||||
#include <osmocom/gsupclient/gsup_client.h>
|
||||
#include "logging.h"
|
||||
#include "hlr.h"
|
||||
#include "gsup_server.h"
|
||||
#include "gsup_router.h"
|
||||
#include "dgsm.h"
|
||||
#include "remote_hlr.h"
|
||||
#include "proxy.h"
|
||||
|
||||
static LLIST_HEAD(remote_hlrs);
|
||||
|
||||
void remote_hlr_err_reply(struct osmo_gsup_client *gsupc, const struct osmo_gsup_message *gsup_orig,
|
||||
enum gsm48_gmm_cause cause)
|
||||
{
|
||||
struct osmo_gsup_message gsup_reply;
|
||||
|
||||
/* No need to answer if we couldn't parse an ERROR message type, only REQUESTs need an error reply. */
|
||||
if (!OSMO_GSUP_IS_MSGT_REQUEST(gsup_orig->message_type))
|
||||
return;
|
||||
|
||||
gsup_reply = (struct osmo_gsup_message){
|
||||
.cause = cause,
|
||||
.message_type = OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type),
|
||||
.message_class = gsup_orig->message_class,
|
||||
|
||||
/* RP-Message-Reference is mandatory for SM Service */
|
||||
.sm_rp_mr = gsup_orig->sm_rp_mr,
|
||||
};
|
||||
|
||||
OSMO_STRLCPY_ARRAY(gsup_reply.imsi, gsup_orig->imsi);
|
||||
|
||||
/* For SS/USSD, it's important to keep both session state and ID IEs */
|
||||
if (gsup_orig->session_state != OSMO_GSUP_SESSION_STATE_NONE) {
|
||||
gsup_reply.session_state = OSMO_GSUP_SESSION_STATE_END;
|
||||
gsup_reply.session_id = gsup_orig->session_id;
|
||||
}
|
||||
|
||||
if (osmo_gsup_client_enc_send(gsupc, &gsup_reply))
|
||||
LOGP(DLGSUP, LOGL_ERROR, "Failed to send Error reply (imsi=%s)\n",
|
||||
osmo_quote_str(gsup_orig->imsi, -1));
|
||||
}
|
||||
|
||||
/* We are receiving back a GSUP message from a remote HLR to go back to a local MSC.
|
||||
* The local MSC shall be indicated by gsup.destination_name. */
|
||||
static int remote_hlr_rx(struct osmo_gsup_client *gsupc, struct msgb *msg)
|
||||
{
|
||||
struct osmo_gsup_message gsup;
|
||||
struct osmo_gsup_conn *vlr_conn;
|
||||
const struct proxy_subscr *proxy_subscr;
|
||||
struct global_title destination;
|
||||
struct msgb *gsup_copy;
|
||||
int rc;
|
||||
|
||||
rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
|
||||
if (rc < 0) {
|
||||
LOG_GSUPC(gsupc, LOGL_ERROR, "Failed to decode GSUP message: '%s' (%d) [ %s]\n",
|
||||
get_value_string(gsm48_gmm_cause_names, -rc), -rc, osmo_hexdump(msg->data, msg->len));
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (!gsup.imsi[0]) {
|
||||
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "Failed to decode GSUP message: missing IMSI\n");
|
||||
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_INV_MAND_INFO);
|
||||
return -GMM_CAUSE_INV_MAND_INFO;
|
||||
}
|
||||
|
||||
if (!gsup.destination_name || !gsup.destination_name_len
|
||||
|| global_title_set(&destination, gsup.destination_name, gsup.destination_name_len)) {
|
||||
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "no valid Destination Name IE, cannot route to VLR.\n");
|
||||
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_INV_MAND_INFO);
|
||||
return -GMM_CAUSE_INV_MAND_INFO;
|
||||
}
|
||||
|
||||
proxy_subscr = proxy_subscr_get_by_imsi(g_hlr->proxy, gsup.imsi);
|
||||
if (!proxy_subscr) {
|
||||
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "Cannot route, no GSUP proxy record for this IMSI\n");
|
||||
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_IMSI_UNKNOWN);
|
||||
return -GMM_CAUSE_IMSI_UNKNOWN;
|
||||
}
|
||||
|
||||
/* Route to MSC that we're proxying for */
|
||||
vlr_conn = gsup_route_find_gt(g_hlr->gs, &destination);
|
||||
if (!vlr_conn) {
|
||||
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "Destination VLR unreachable: %s\n",
|
||||
global_title_name(&destination));
|
||||
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_MSC_TEMP_NOTREACH);
|
||||
return -GMM_CAUSE_MSC_TEMP_NOTREACH;
|
||||
}
|
||||
|
||||
/* The outgoing message needs to be a separate msgb, because osmo_gsup_conn_send() takes ownership of it, an the
|
||||
* gsup_client also does a msgb_free() after dispatching to this callback.
|
||||
* We also need to strip the IPA header and have headroom. Just re-encode. */
|
||||
gsup_copy = osmo_gsup_msgb_alloc("GSUP proxy to VLR");
|
||||
if (osmo_gsup_encode(gsup_copy, &gsup)) {
|
||||
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "Failed to re-encode GSUP message, cannot forward\n");
|
||||
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_MSC_TEMP_NOTREACH);
|
||||
return -GMM_CAUSE_MSC_TEMP_NOTREACH;
|
||||
}
|
||||
return osmo_gsup_conn_send(vlr_conn, gsup_copy);
|
||||
}
|
||||
|
||||
static bool remote_hlr_up_down(struct osmo_gsup_client *gsupc, bool up)
|
||||
{
|
||||
struct remote_hlr *remote_hlr = gsupc->data;
|
||||
if (!up) {
|
||||
LOGP(DDGSM, LOGL_ERROR,
|
||||
"link to remote HLR is down, removing GSUP client: " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
|
||||
remote_hlr_destroy(remote_hlr);
|
||||
return false;
|
||||
}
|
||||
|
||||
dgsm_remote_hlr_up(remote_hlr);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct remote_hlr *remote_hlr_get(const struct osmo_sockaddr_str *addr, bool create)
|
||||
{
|
||||
struct remote_hlr *rh;
|
||||
|
||||
llist_for_each_entry(rh, &remote_hlrs, entry) {
|
||||
if (!osmo_sockaddr_str_ip_cmp(&rh->addr, addr))
|
||||
return rh;
|
||||
}
|
||||
|
||||
if (!create)
|
||||
return NULL;
|
||||
|
||||
/* Doesn't exist yet, create a GSUP client to remote HLR. */
|
||||
rh = talloc_zero(dgsm_ctx, struct remote_hlr);
|
||||
OSMO_ASSERT(rh);
|
||||
*rh = (struct remote_hlr){
|
||||
.addr = *addr,
|
||||
.gsupc = osmo_gsup_client_create3(rh, &g_hlr->gsup_unit_name,
|
||||
addr->ip, addr->port,
|
||||
NULL,
|
||||
remote_hlr_rx,
|
||||
remote_hlr_up_down,
|
||||
rh),
|
||||
};
|
||||
if (!rh->gsupc) {
|
||||
LOGP(DDGSM, LOGL_ERROR,
|
||||
"Failed to establish connection to remote HLR " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(addr));
|
||||
talloc_free(rh);
|
||||
return NULL;
|
||||
}
|
||||
rh->gsupc->data = rh;
|
||||
llist_add(&rh->entry, &remote_hlrs);
|
||||
return rh;
|
||||
}
|
||||
|
||||
void remote_hlr_destroy(struct remote_hlr *remote_hlr)
|
||||
{
|
||||
osmo_gsup_client_destroy(remote_hlr->gsupc);
|
||||
remote_hlr->gsupc = NULL;
|
||||
llist_del(&remote_hlr->entry);
|
||||
talloc_free(remote_hlr);
|
||||
}
|
||||
|
||||
/* This function takes ownership of the msg, do not free it after passing to this function. */
|
||||
int remote_hlr_msgb_send(struct remote_hlr *remote_hlr, struct msgb *msg)
|
||||
{
|
||||
int rc = osmo_gsup_client_send(remote_hlr->gsupc, msg);
|
||||
if (rc) {
|
||||
LOGP(DDGSM, LOGL_ERROR, "Failed to send GSUP message to " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
void remote_hlr_gsup_forward(struct remote_hlr *remote_hlr, struct osmo_gsup_req *req)
|
||||
{
|
||||
int rc;
|
||||
struct msgb *msg = osmo_gsup_msgb_alloc("GSUP proxy to remote HLR");
|
||||
/* To forward to a remote HLR, we need to indicate the source MSC's name in the Source Name IE to make sure the
|
||||
* reply can be routed back. Store the sender MSC in gsup->source_name -- the remote HLR is required to return
|
||||
* this as gsup->destination_name so that the reply gets routed to the original MSC. */
|
||||
struct osmo_gsup_message forward = req->gsup;
|
||||
forward.source_name = req->source_name.val;
|
||||
forward.source_name_len = req->source_name.len;
|
||||
|
||||
rc = osmo_gsup_encode(msg, &forward);
|
||||
if (rc) {
|
||||
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Failed to encode GSUP message for forwarding\n");
|
||||
return;
|
||||
}
|
||||
remote_hlr_msgb_send(remote_hlr, msg);
|
||||
osmo_gsup_req_free(req);
|
||||
}
|
||||
27
src/remote_hlr.h
Normal file
27
src/remote_hlr.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
|
||||
struct osmo_gsup_client;
|
||||
struct osmo_gsup_message;
|
||||
struct msgb;
|
||||
|
||||
#define LOG_GSUPC(gsupc, level, fmt, args...) \
|
||||
LOGP(DDGSM, level, "HLR Proxy: GSUP from %s:%u: " fmt, (gsupc)->link->addr, (gsupc)->link->port, ##args)
|
||||
|
||||
#define LOG_GSUPC_MSG(gsupc, gsup_msg, level, fmt, args...) \
|
||||
LOG_GSUPC(gsupc, level, "%s: " fmt, osmo_gsup_message_type_name((gsup_msg)->message_type), ##args)
|
||||
|
||||
/* GSUP client link for proxying to a remote HLR. */
|
||||
struct remote_hlr {
|
||||
struct llist_head entry;
|
||||
struct osmo_sockaddr_str addr;
|
||||
struct osmo_gsup_client *gsupc;
|
||||
};
|
||||
|
||||
struct remote_hlr *remote_hlr_get(const struct osmo_sockaddr_str *addr, bool create);
|
||||
void remote_hlr_destroy(struct remote_hlr *remote_hlr);
|
||||
int remote_hlr_msgb_send(struct remote_hlr *remote_hlr, struct msgb *msg);
|
||||
void remote_hlr_gsup_forward(struct remote_hlr *remote_hlr, struct osmo_gsup_req *req);
|
||||
@@ -2,6 +2,8 @@ SUBDIRS = \
|
||||
auc \
|
||||
gsup_server \
|
||||
db \
|
||||
db_upgrade \
|
||||
mslookup_manual_test \
|
||||
$(NULL)
|
||||
|
||||
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
|
||||
@@ -43,6 +45,11 @@ python-tests:
|
||||
# don't run vty and ctrl tests concurrently so that the ports don't conflict
|
||||
$(MAKE) vty-test
|
||||
$(MAKE) ctrl-test
|
||||
$(MAKE) db-upgrade-equivalence-test
|
||||
else
|
||||
python-tests:
|
||||
echo "Not running python-based external tests (determined at configure-time)"
|
||||
endif
|
||||
|
||||
VTY_TEST_DB = hlr_vty_test.db
|
||||
|
||||
@@ -51,11 +58,14 @@ VTY_TEST_DB = hlr_vty_test.db
|
||||
# make vty-test U=-u
|
||||
vty-test:
|
||||
-rm -f $(VTY_TEST_DB)
|
||||
sqlite3 $(VTY_TEST_DB) < $(top_srcdir)/sql/hlr.sql
|
||||
sqlite3 $(VTY_TEST_DB) < $(srcdir)/test_subscriber.vty.sql
|
||||
osmo_verify_transcript_vty.py -v \
|
||||
-n OsmoHLR -p 4258 \
|
||||
-r "$(top_builddir)/src/osmo-hlr -c $(top_srcdir)/doc/examples/osmo-hlr.cfg -l $(VTY_TEST_DB)" \
|
||||
$(U) $(srcdir)/*.vty
|
||||
-rm -f $(VTY_TEST_DB)
|
||||
-rm $(VTY_TEST_DB)-*
|
||||
|
||||
CTRL_TEST_DB = hlr_ctrl_test.db
|
||||
|
||||
@@ -71,11 +81,10 @@ ctrl-test:
|
||||
-r "$(top_builddir)/src/osmo-hlr -c $(top_srcdir)/doc/examples/osmo-hlr.cfg -l $(CTRL_TEST_DB)" \
|
||||
$(U) $(srcdir)/*.ctrl
|
||||
-rm -f $(CTRL_TEST_DB)
|
||||
-rm $(CTRL_TEST_DB)-*
|
||||
|
||||
else
|
||||
python-tests:
|
||||
echo "Not running python-based tests (determined at configure-time)"
|
||||
endif
|
||||
db-upgrade-equivalence-test:
|
||||
$(MAKE) -C db_upgrade upgrade-equivalence-test
|
||||
|
||||
check-local: atconfig $(TESTSUITE)
|
||||
$(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS)
|
||||
|
||||
@@ -13,6 +13,7 @@ AM_CFLAGS = \
|
||||
$(NULL)
|
||||
|
||||
AM_LDFLAGS = \
|
||||
-no-install \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_DIST = \
|
||||
|
||||
@@ -610,7 +610,9 @@ int main(int argc, char **argv)
|
||||
|
||||
handle_options(argc, argv);
|
||||
|
||||
osmo_init_logging(&hlr_log_info);
|
||||
void *tall_ctx = talloc_named_const(NULL, 1, "auc_test");
|
||||
|
||||
osmo_init_logging2(tall_ctx, &hlr_log_info);
|
||||
log_set_print_filename(osmo_stderr_target, cmdline_opts.verbose);
|
||||
log_set_print_timestamp(osmo_stderr_target, 0);
|
||||
log_set_use_color(osmo_stderr_target, 0);
|
||||
|
||||
@@ -35,7 +35,7 @@ DAUC vector [0]: ck = f64735036e5871319c679f4742a75ea1
|
||||
DAUC vector [0]: ik = 27497388b6cb044648f396aa155b95ef
|
||||
DAUC vector [0]: res = e229c19e791f2e410000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: calculating 2G separately
|
||||
DAUC vector [0]: kc = 241a5b16aeb8e400
|
||||
DAUC vector [0]: sres = 429d5b27
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -55,7 +55,7 @@ DAUC vector [0]: ck = f64735036e5871319c679f4742a75ea1
|
||||
DAUC vector [0]: ik = 27497388b6cb044648f396aa155b95ef
|
||||
DAUC vector [0]: res = e229c19e791f2e410000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: calculating 2G separately
|
||||
DAUC vector [0]: kc = 241a5b16aeb8e400
|
||||
DAUC vector [0]: sres = 429d5b27
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -78,6 +78,7 @@ DAUC vector [0]: ck = f64735036e5871319c679f4742a75ea1
|
||||
DAUC vector [0]: ik = 27497388b6cb044648f396aa155b95ef
|
||||
DAUC vector [0]: res = e229c19e791f2e410000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 059a4f668f6fbe39
|
||||
DAUC vector [0]: sres = 9b36efdf
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -96,6 +97,7 @@ DAUC vector [0]: ck = f64735036e5871319c679f4742a75ea1
|
||||
DAUC vector [0]: ik = 27497388b6cb044648f396aa155b95ef
|
||||
DAUC vector [0]: res = e229c19e791f2e410000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 059a4f668f6fbe39
|
||||
DAUC vector [0]: sres = 9b36efdf
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -117,6 +119,7 @@ DAUC vector [0]: ck = e9922bd036718ed9e40bd1d02c3b81a5
|
||||
DAUC vector [0]: ik = f19c20ca863137f8892326d959ec5e01
|
||||
DAUC vector [0]: res = 9af5a557902d2db80000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 7526fc13c5976685
|
||||
DAUC vector [0]: sres = 0ad888ef
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -137,6 +140,7 @@ DAUC vector [0]: ck = e9922bd036718ed9e40bd1d02c3b81a5
|
||||
DAUC vector [0]: ik = f19c20ca863137f8892326d959ec5e01
|
||||
DAUC vector [0]: res = 9af5a557902d2db80000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 7526fc13c5976685
|
||||
DAUC vector [0]: sres = 0ad888ef
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -147,6 +151,7 @@ DAUC vector [1]: ck = 3686f05df057d1899c66ae4eb18cf941
|
||||
DAUC vector [1]: ik = 79f21ed53bcb47787de57d136ff803a5
|
||||
DAUC vector [1]: res = 43023475cb29292c0000000000000000
|
||||
DAUC vector [1]: res_len = 8
|
||||
DAUC vector [1]: deriving 2G from 3G
|
||||
DAUC vector [1]: kc = aef73dd515e86c15
|
||||
DAUC vector [1]: sres = 882b1d59
|
||||
DAUC vector [1]: auth_types = 0x3
|
||||
@@ -157,6 +162,7 @@ DAUC vector [2]: ck = d86c3191a36fc0602e48202ef2080964
|
||||
DAUC vector [2]: ik = 648dab72016181406243420649e63dc9
|
||||
DAUC vector [2]: res = 010cab11cc63a6e40000000000000000
|
||||
DAUC vector [2]: res_len = 8
|
||||
DAUC vector [2]: deriving 2G from 3G
|
||||
DAUC vector [2]: kc = f0eaf8cb19e0758d
|
||||
DAUC vector [2]: sres = cd6f0df5
|
||||
DAUC vector [2]: auth_types = 0x3
|
||||
@@ -179,6 +185,7 @@ DAUC vector [0]: ck = e9922bd036718ed9e40bd1d02c3b81a5
|
||||
DAUC vector [0]: ik = f19c20ca863137f8892326d959ec5e01
|
||||
DAUC vector [0]: res = 9af5a557902d2db80000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 7526fc13c5976685
|
||||
DAUC vector [0]: sres = 0ad888ef
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -189,6 +196,7 @@ DAUC vector [1]: ck = 3686f05df057d1899c66ae4eb18cf941
|
||||
DAUC vector [1]: ik = 79f21ed53bcb47787de57d136ff803a5
|
||||
DAUC vector [1]: res = 43023475cb29292c0000000000000000
|
||||
DAUC vector [1]: res_len = 8
|
||||
DAUC vector [1]: deriving 2G from 3G
|
||||
DAUC vector [1]: kc = aef73dd515e86c15
|
||||
DAUC vector [1]: sres = 882b1d59
|
||||
DAUC vector [1]: auth_types = 0x3
|
||||
@@ -199,6 +207,7 @@ DAUC vector [2]: ck = d86c3191a36fc0602e48202ef2080964
|
||||
DAUC vector [2]: ik = 648dab72016181406243420649e63dc9
|
||||
DAUC vector [2]: res = 010cab11cc63a6e40000000000000000
|
||||
DAUC vector [2]: res_len = 8
|
||||
DAUC vector [2]: deriving 2G from 3G
|
||||
DAUC vector [2]: kc = f0eaf8cb19e0758d
|
||||
DAUC vector [2]: sres = cd6f0df5
|
||||
DAUC vector [2]: auth_types = 0x3
|
||||
|
||||
@@ -12,6 +12,7 @@ DAUC vector [0]: ck = b40ba9a3c58b2a05bbf0d987b21bf8cb
|
||||
DAUC vector [0]: ik = f769bcd751044604127672711c6d3441
|
||||
DAUC vector [0]: res = a54211d5e3ba50bf0000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = eae4be823af9a08b
|
||||
DAUC vector [0]: sres = 46f8416a
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -34,6 +35,7 @@ DAUC vector [0]: ck = 5dbdbb2954e8f3cde665b046179a5098
|
||||
DAUC vector [0]: ik = 59a92d3b476a0443487055cf88b2307b
|
||||
DAUC vector [0]: res = 8011c48c0c214ed20000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = aa01739b8caa976d
|
||||
DAUC vector [0]: sres = 8c308a5e
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -56,6 +58,7 @@ DAUC vector [0]: ck = e203edb3971574f5a94b0d61b816345d
|
||||
DAUC vector [0]: ik = 0c4524adeac041c4dd830d20854fc46b
|
||||
DAUC vector [0]: res = f365cd683cd92e960000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 9a8ec95f408cc507
|
||||
DAUC vector [0]: sres = cfbce3fe
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -78,6 +81,7 @@ DAUC vector [0]: ck = 7657766b373d1c2138f307e3de9242f9
|
||||
DAUC vector [0]: ik = 1c42e960d89b8fa99f2744e0708ccb53
|
||||
DAUC vector [0]: res = 5860fc1bce351e7e0000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = cdc1dc0841b81a22
|
||||
DAUC vector [0]: sres = 9655e265
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -100,6 +104,7 @@ DAUC vector [0]: ck = 3f8c7587fe8e4b233af676aede30ba3b
|
||||
DAUC vector [0]: ik = a7466cc1e6b2a1337d49d3b66e95d7b4
|
||||
DAUC vector [0]: res = 16c8233f05a0ac280000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = df75bc5ea899879f
|
||||
DAUC vector [0]: sres = 13688f17
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -122,6 +127,7 @@ DAUC vector [0]: ck = 4cd0846020f8fa0731dd47cbdc6be411
|
||||
DAUC vector [0]: ik = 88ab80a415f15c73711254a1d388f696
|
||||
DAUC vector [0]: res = 8c25a16cd918a1df0000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 84b417ae3aeab4f3
|
||||
DAUC vector [0]: sres = 553d00b3
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -144,6 +150,7 @@ DAUC vector [0]: ck = 10f05bab75a99a5fbb98a9c287679c3b
|
||||
DAUC vector [0]: ik = f9ec0865eb32f22369cade40c59c3a44
|
||||
DAUC vector [0]: res = a63241e1ffc3e5ab0000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 3b4e244cdc60ce03
|
||||
DAUC vector [0]: sres = 59f1a44a
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -166,6 +173,7 @@ DAUC vector [0]: ck = 71236b7129f9b22ab77ea7a54c96da22
|
||||
DAUC vector [0]: ik = 90527ebaa5588968db41727325a04d9e
|
||||
DAUC vector [0]: res = 4a90b2171ac83a760000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 8d4ec01de597acfe
|
||||
DAUC vector [0]: sres = 50588861
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -188,6 +196,7 @@ DAUC vector [0]: ck = 08cef6d004ec61471a3c3cda048137fa
|
||||
DAUC vector [0]: ik = ed0318ca5deb9206272f6e8fa64ba411
|
||||
DAUC vector [0]: res = 4bc2212d8624910a0000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = d8debc4ffbcd60aa
|
||||
DAUC vector [0]: sres = cde6b027
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -210,6 +219,7 @@ DAUC vector [0]: ck = 69b1cae7c7429d975e245cacb05a517c
|
||||
DAUC vector [0]: ik = 74f24e8c26df58e1b38d7dcd4f1b7fbd
|
||||
DAUC vector [0]: res = 6fc30fee6d1235230000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = f0eaa50a1edcebb7
|
||||
DAUC vector [0]: sres = 02d13acd
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -232,6 +242,7 @@ DAUC vector [0]: ck = 908c43f0569cb8f74bc971e706c36c5f
|
||||
DAUC vector [0]: ik = c251df0d888dd9329bcf46655b226e40
|
||||
DAUC vector [0]: res = aefa357beac2a87a0000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 82dbab7f83f063da
|
||||
DAUC vector [0]: sres = 44389d01
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -254,6 +265,7 @@ DAUC vector [0]: ck = 44c0f23c5493cfd241e48f197e1d1012
|
||||
DAUC vector [0]: ik = 0c9fb81613884c2535dd0eabf3b440d8
|
||||
DAUC vector [0]: res = 98dbbd099b3b408d0000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 3c66cb98cab2d33d
|
||||
DAUC vector [0]: sres = 03e0fd84
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -276,6 +288,7 @@ DAUC vector [0]: ck = 5af86b80edb70df5292cc1121cbad50c
|
||||
DAUC vector [0]: ik = 7f4d6ae7440e18789a8b75ad3f42f03a
|
||||
DAUC vector [0]: res = af4a411e1139f2c20000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 9612b5d88a4130bb
|
||||
DAUC vector [0]: sres = be73b3dc
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -298,6 +311,7 @@ DAUC vector [0]: ck = 3f8c3f3ccf7625bf77fc94bcfd22fd26
|
||||
DAUC vector [0]: ik = abcbae8fd46115e9961a55d0da5f2078
|
||||
DAUC vector [0]: res = 7bffa5c2f41fbc050000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 75a150df3c6aed08
|
||||
DAUC vector [0]: sres = 8fe019c7
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -320,6 +334,7 @@ DAUC vector [0]: ck = d42b2d615e49a03ac275a5aef97af892
|
||||
DAUC vector [0]: ik = 0b3f8d024fe6bfafaa982b8f82e319c2
|
||||
DAUC vector [0]: res = 7e3f44c7591f6f450000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = b7f92e426a36fec5
|
||||
DAUC vector [0]: sres = 27202b82
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -342,6 +357,7 @@ DAUC vector [0]: ck = 6edaf99e5bd9f85d5f36d91c1272fb4b
|
||||
DAUC vector [0]: ik = d61c853c280dd9c46f297baec386de17
|
||||
DAUC vector [0]: res = 70f6bdb9ad21525f0000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 88d9de10a22004c5
|
||||
DAUC vector [0]: sres = ddd7efe6
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -364,6 +380,7 @@ DAUC vector [0]: ck = 66195dbed0313274c5ca7766615fa25e
|
||||
DAUC vector [0]: ik = 66bec707eb2afc476d7408a8f2927b36
|
||||
DAUC vector [0]: res = 479dd25c20792d630000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = a819e577a8d6175b
|
||||
DAUC vector [0]: sres = 67e4ff3f
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -386,6 +403,7 @@ DAUC vector [0]: ck = 5349fbe098649f948f5d2e973a81c00f
|
||||
DAUC vector [0]: ik = 9744871ad32bf9bbd1dd5ce54e3e2e5a
|
||||
DAUC vector [0]: res = 28d7b0f2a2ec3de50000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = 9a8d0e883ff0887a
|
||||
DAUC vector [0]: sres = 8a3b8d17
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
@@ -408,6 +426,7 @@ DAUC vector [0]: ck = b5f2da03883b69f96bf52e029ed9ac45
|
||||
DAUC vector [0]: ik = b4721368bc16ea67875c5598688bb0ef
|
||||
DAUC vector [0]: res = a95100e2760952cd0000000000000000
|
||||
DAUC vector [0]: res_len = 8
|
||||
DAUC vector [0]: deriving 2G from 3G
|
||||
DAUC vector [0]: kc = ed29b2f1c27f9f34
|
||||
DAUC vector [0]: sres = df58522f
|
||||
DAUC vector [0]: auth_types = 0x3
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <osmocom/core/application.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
|
||||
#include <osmocom/crypt/auth.h>
|
||||
|
||||
@@ -102,7 +103,9 @@ FUNCTIONS
|
||||
int main()
|
||||
{
|
||||
printf("3GPP TS 55.205 Test Sets\n");
|
||||
osmo_init_logging(&hlr_log_info);
|
||||
void *tall_ctx = talloc_named_const(NULL, 1, "test");
|
||||
msgb_talloc_ctx_init(tall_ctx, 0);
|
||||
osmo_init_logging2(tall_ctx, &hlr_log_info);
|
||||
log_set_print_filename(osmo_stderr_target, 0);
|
||||
log_set_print_timestamp(osmo_stderr_target, 0);
|
||||
log_set_use_color(osmo_stderr_target, 0);
|
||||
|
||||
@@ -6,9 +6,14 @@ AM_CFLAGS = \
|
||||
-ggdb3 \
|
||||
$(LIBOSMOCORE_CFLAGS) \
|
||||
$(LIBOSMOGSM_CFLAGS) \
|
||||
$(LIBOSMOABIS_CFLAGS) \
|
||||
$(SQLITE3_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
AM_LDFLAGS = \
|
||||
-no-install \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_DIST = \
|
||||
db_test.ok \
|
||||
db_test.err \
|
||||
@@ -21,15 +26,21 @@ db_test_SOURCES = \
|
||||
$(NULL)
|
||||
|
||||
db_test_LDADD = \
|
||||
$(top_srcdir)/src/db.c \
|
||||
$(top_srcdir)/src/db_hlr.c \
|
||||
$(top_srcdir)/src/db_auc.c \
|
||||
$(top_srcdir)/src/logging.c \
|
||||
$(top_builddir)/src/logging.o \
|
||||
$(top_builddir)/src/db_auc.o \
|
||||
$(top_builddir)/src/db_hlr.o \
|
||||
$(top_builddir)/src/db.o \
|
||||
$(top_builddir)/src/global_title.o \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
$(LIBOSMOABIS_LIBS) \
|
||||
$(SQLITE3_LIBS) \
|
||||
$(NULL)
|
||||
|
||||
if DB_SQLITE_DEBUG
|
||||
db_test_LDADD += $(top_builddir)/src/db_debug.o
|
||||
endif
|
||||
|
||||
.PHONY: db_test.db update_exp manual manual-nonverbose manual-gdb
|
||||
db_test.db:
|
||||
rm -f db_test.db
|
||||
|
||||
@@ -51,7 +51,12 @@ static void _fill_invalid(void *dest, size_t size)
|
||||
* The return code is then available in g_rc. */
|
||||
#define ASSERT_RC(call, expect_rc) \
|
||||
do { \
|
||||
fprintf(stderr, #call " --> " #expect_rc "\n"); \
|
||||
if ((expect_rc) == -ENOKEY) \
|
||||
fprintf(stderr, #call " --> -ENOKEY\n"); \
|
||||
else if ((expect_rc) == -ENOTSUP) \
|
||||
fprintf(stderr, #call " --> -ENOTSUP\n"); \
|
||||
else \
|
||||
fprintf(stderr, #call " --> " #expect_rc "\n"); \
|
||||
g_rc = call; \
|
||||
if (g_rc != (expect_rc)) \
|
||||
fprintf(stderr, " MISMATCH: got rc = %d, expected: " \
|
||||
@@ -67,7 +72,12 @@ static void _fill_invalid(void *dest, size_t size)
|
||||
do { \
|
||||
int rc; \
|
||||
fill_invalid(g_subscr); \
|
||||
fprintf(stderr, "db_subscr_get_by_" #by "(dbc, " #val ", &g_subscr) --> " \
|
||||
if ((expect_rc) == -ENOKEY) \
|
||||
fprintf(stderr, "db_subscr_get_by_" #by "(dbc, " #val ", &g_subscr) --> -ENOKEY \n"); \
|
||||
else if ((expect_rc) == -ENOTSUP) \
|
||||
fprintf(stderr, "db_subscr_get_by_" #by "(dbc, " #val ", &g_subscr) --> -ENOTSUP \n"); \
|
||||
else \
|
||||
fprintf(stderr, "db_subscr_get_by_" #by "(dbc, " #val ", &g_subscr) --> " \
|
||||
#expect_rc "\n"); \
|
||||
rc = db_subscr_get_by_##by(dbc, val, &g_subscr); \
|
||||
if (rc != (expect_rc)) \
|
||||
@@ -100,6 +110,22 @@ static void _fill_invalid(void *dest, size_t size)
|
||||
fprintf(stderr, "\n"); \
|
||||
} while (0)
|
||||
|
||||
#define N_VECTORS 3
|
||||
|
||||
#define ASSERT_DB_GET_AUC(imsi, expect_rc) \
|
||||
do { \
|
||||
struct osmo_auth_vector vec[N_VECTORS]; \
|
||||
ASSERT_RC(db_get_auc(dbc, imsi, 3, vec, N_VECTORS, NULL, NULL), expect_rc); \
|
||||
} while (0)
|
||||
|
||||
/* Not linking the real auc_compute_vectors(), just returning num_vec.
|
||||
* This gets called by db_get_auc(), but we're only interested in its rc. */
|
||||
int auc_compute_vectors(struct osmo_auth_vector *vec, unsigned int num_vec,
|
||||
struct osmo_sub_auth_data *aud2g,
|
||||
struct osmo_sub_auth_data *aud3g,
|
||||
const uint8_t *rand_auts, const uint8_t *auts)
|
||||
{ return num_vec; }
|
||||
|
||||
static struct db_context *dbc = NULL;
|
||||
static void *ctx = NULL;
|
||||
static struct hlr_subscriber g_subscr;
|
||||
@@ -119,6 +145,8 @@ void dump_subscr(struct hlr_subscriber *subscr)
|
||||
#define Ps(name) \
|
||||
if (*subscr->name) \
|
||||
Pfo(name, "'%s'", subscr)
|
||||
#define Pgt(name) \
|
||||
Pfv(name, "%s", global_title_name(&subscr->name))
|
||||
#define Pd(name) \
|
||||
Pfv(name, "%"PRId64, (int64_t)subscr->name)
|
||||
#define Pd_nonzero(name) \
|
||||
@@ -132,6 +160,7 @@ void dump_subscr(struct hlr_subscriber *subscr)
|
||||
Pd(id);
|
||||
Ps(imsi);
|
||||
Ps(msisdn);
|
||||
Ps(imei);
|
||||
Ps(vlr_number);
|
||||
Ps(sgsn_number);
|
||||
Ps(sgsn_address);
|
||||
@@ -191,12 +220,31 @@ void dump_aud(const char *label, struct osmo_sub_auth_data *aud)
|
||||
#undef Phex
|
||||
}
|
||||
|
||||
void db_raw_sql(struct db_context *dbc, const char *sql)
|
||||
{
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
fprintf(stderr, "raw SQL: %s\n", sql);
|
||||
ASSERT_RC(sqlite3_prepare_v2(dbc->db, sql, -1, &stmt, NULL), SQLITE_OK);
|
||||
ASSERT_RC(sqlite3_step(stmt), SQLITE_DONE);
|
||||
db_remove_reset(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
static const char *imsi0 = "123456789000000";
|
||||
static const char *imsi1 = "123456789000001";
|
||||
static const char *imsi2 = "123456789000002";
|
||||
static const char *short_imsi = "123456";
|
||||
static const char *unknown_imsi = "999999999";
|
||||
|
||||
static int db_subscr_lu_str(struct db_context *dbc, int64_t subscr_id,
|
||||
const char *vlr_or_sgsn_number, bool is_ps)
|
||||
{
|
||||
struct global_title vlr_nr;
|
||||
global_title_set_str(&vlr_nr, vlr_or_sgsn_number);
|
||||
return db_subscr_lu(dbc, subscr_id, &vlr_nr, is_ps, NULL);
|
||||
}
|
||||
|
||||
static void test_subscr_create_update_sel_delete()
|
||||
{
|
||||
int64_t id0, id1, id2, id_short;
|
||||
@@ -204,40 +252,45 @@ static void test_subscr_create_update_sel_delete()
|
||||
|
||||
comment("Create with valid / invalid IMSI");
|
||||
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi0), 0);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
|
||||
ASSERT_SEL(imsi, imsi0, 0);
|
||||
id0 = g_subscr.id;
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi1), 0);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi1, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
|
||||
ASSERT_SEL(imsi, imsi1, 0);
|
||||
id1 = g_subscr.id;
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi2), 0);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi2, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
|
||||
ASSERT_SEL(imsi, imsi2, 0);
|
||||
id2 = g_subscr.id;
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi0), -EIO);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EIO);
|
||||
ASSERT_SEL(imsi, imsi0, 0);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi1), -EIO);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi1), -EIO);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi1, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EIO);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi1, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EIO);
|
||||
ASSERT_SEL(imsi, imsi1, 0);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi2), -EIO);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi2), -EIO);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi2, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EIO);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi2, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EIO);
|
||||
ASSERT_SEL(imsi, imsi2, 0);
|
||||
|
||||
ASSERT_RC(db_subscr_create(dbc, "123456789 000003"), -EINVAL);
|
||||
ASSERT_RC(db_subscr_create(dbc, "123456789 000003", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EINVAL);
|
||||
ASSERT_SEL(imsi, "123456789000003", -ENOENT);
|
||||
|
||||
ASSERT_RC(db_subscr_create(dbc, "123456789000002123456"), -EINVAL);
|
||||
ASSERT_RC(db_subscr_create(dbc, "123456789000002123456", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS),
|
||||
-EINVAL);
|
||||
ASSERT_SEL(imsi, "123456789000002123456", -ENOENT);
|
||||
|
||||
ASSERT_RC(db_subscr_create(dbc, "foobar123"), -EINVAL);
|
||||
ASSERT_RC(db_subscr_create(dbc, "foobar123", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EINVAL);
|
||||
ASSERT_SEL(imsi, "foobar123", -ENOENT);
|
||||
|
||||
ASSERT_RC(db_subscr_create(dbc, "123"), -EINVAL);
|
||||
ASSERT_RC(db_subscr_create(dbc, "123", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EINVAL);
|
||||
ASSERT_SEL(imsi, "123", -ENOENT);
|
||||
|
||||
ASSERT_RC(db_subscr_create(dbc, short_imsi), 0);
|
||||
ASSERT_RC(db_subscr_create(dbc, short_imsi, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
|
||||
ASSERT_SEL(imsi, short_imsi, 0);
|
||||
id_short = g_subscr.id;
|
||||
|
||||
comment("Check if subscriber exists (by IMSI)");
|
||||
|
||||
ASSERT_RC(db_subscr_exists_by_imsi(dbc, imsi0), 0);
|
||||
ASSERT_RC(db_subscr_exists_by_imsi(dbc, unknown_imsi), -ENOENT);
|
||||
|
||||
comment("Set valid / invalid MSISDN");
|
||||
|
||||
@@ -272,6 +325,11 @@ static void test_subscr_create_update_sel_delete()
|
||||
ASSERT_SEL(imsi, imsi0, 0);
|
||||
ASSERT_SEL(msisdn, "5432101234567891", -ENOENT);
|
||||
|
||||
comment("Check if subscriber exists (by MSISDN)");
|
||||
|
||||
ASSERT_RC(db_subscr_exists_by_msisdn(dbc, "543210123456789"), 0);
|
||||
ASSERT_RC(db_subscr_exists_by_msisdn(dbc, "5432101234567891"), -ENOENT);
|
||||
|
||||
comment("Set MSISDN on non-existent / invalid IMSI");
|
||||
|
||||
ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, unknown_imsi, "99"), -ENOENT);
|
||||
@@ -280,6 +338,23 @@ static void test_subscr_create_update_sel_delete()
|
||||
ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, "foobar", "99"), -ENOENT);
|
||||
ASSERT_SEL(msisdn, "99", -ENOENT);
|
||||
|
||||
comment("Set valid / invalid IMEI");
|
||||
|
||||
ASSERT_RC(db_subscr_update_imei_by_imsi(dbc, imsi0, "12345678901234"), 0);
|
||||
ASSERT_SEL(imei, "12345678901234", 0);
|
||||
|
||||
ASSERT_RC(db_subscr_update_imei_by_imsi(dbc, imsi0, "123456789012345"), -EINVAL); /* too long */
|
||||
ASSERT_SEL(imei, "12345678901234", 0);
|
||||
ASSERT_SEL(imei, "123456789012345", -ENOENT);
|
||||
|
||||
comment("Set the same IMEI again");
|
||||
ASSERT_RC(db_subscr_update_imei_by_imsi(dbc, imsi0, "12345678901234"), 0);
|
||||
ASSERT_SEL(imei, "12345678901234", 0);
|
||||
|
||||
comment("Remove IMEI");
|
||||
ASSERT_RC(db_subscr_update_imei_by_imsi(dbc, imsi0, NULL), 0);
|
||||
ASSERT_SEL(imei, "12345678901234", -ENOENT);
|
||||
|
||||
comment("Set / unset nam_cs and nam_ps");
|
||||
|
||||
/* nam_val, is_ps */
|
||||
@@ -321,39 +396,39 @@ static void test_subscr_create_update_sel_delete()
|
||||
|
||||
comment("Record LU for PS and CS (SGSN and VLR names)");
|
||||
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "5952", true), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "5952", true), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "712", false), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "712", false), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
|
||||
comment("Record LU for PS and CS (SGSN and VLR names) *again*");
|
||||
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
|
||||
comment("Unset LU info for PS and CS (SGSN and VLR names)");
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "", true), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "", true), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "", false), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "", false), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, NULL, true), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, NULL, true), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
ASSERT_RC(db_subscr_lu(dbc, id0, NULL, false), 0);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, id0, NULL, false), 0);
|
||||
ASSERT_SEL(id, id0, 0);
|
||||
|
||||
comment("Record LU for non-existent ID");
|
||||
ASSERT_RC(db_subscr_lu(dbc, 99999, "5952", true), -ENOENT);
|
||||
ASSERT_RC(db_subscr_lu(dbc, 99999, "712", false), -ENOENT);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, 99999, "5952", true), -ENOENT);
|
||||
ASSERT_RC(db_subscr_lu_str(dbc, 99999, "712", false), -ENOENT);
|
||||
ASSERT_SEL(id, 99999, -ENOENT);
|
||||
|
||||
comment("Purge and un-purge PS and CS");
|
||||
@@ -418,6 +493,22 @@ static void test_subscr_create_update_sel_delete()
|
||||
ASSERT_RC(db_subscr_delete_by_id(dbc, id_short), 0);
|
||||
ASSERT_SEL(imsi, short_imsi, -ENOENT);
|
||||
|
||||
comment("Create and delete subscribers with non-default nam_cs and nam_ps");
|
||||
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi0, 0x00), 0);
|
||||
ASSERT_SEL(imsi, imsi0, 0);
|
||||
id0 = g_subscr.id;
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi1, DB_SUBSCR_FLAG_NAM_CS), 0);
|
||||
ASSERT_SEL(imsi, imsi1, 0);
|
||||
id1 = g_subscr.id;
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi2, DB_SUBSCR_FLAG_NAM_PS), 0);
|
||||
ASSERT_SEL(imsi, imsi2, 0);
|
||||
id2 = g_subscr.id;
|
||||
|
||||
ASSERT_RC(db_subscr_delete_by_id(dbc, id0), 0);
|
||||
ASSERT_RC(db_subscr_delete_by_id(dbc, id1), 0);
|
||||
ASSERT_RC(db_subscr_delete_by_id(dbc, id2), 0);
|
||||
|
||||
comment_end();
|
||||
}
|
||||
|
||||
@@ -457,14 +548,16 @@ static void test_subscr_aud()
|
||||
|
||||
comment("Get auth data for non-existent subscriber");
|
||||
ASSERT_SEL_AUD(unknown_imsi, -ENOENT, 0);
|
||||
ASSERT_DB_GET_AUC(imsi0, -ENOENT);
|
||||
|
||||
comment("Create subscriber");
|
||||
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi0), 0);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
|
||||
ASSERT_SEL(imsi, imsi0, 0);
|
||||
|
||||
id = g_subscr.id;
|
||||
ASSERT_SEL_AUD(imsi0, -ENOENT, id);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
|
||||
ASSERT_DB_GET_AUC(imsi0, -ENOKEY);
|
||||
|
||||
|
||||
comment("Set auth data, 2G only");
|
||||
@@ -473,6 +566,7 @@ static void test_subscr_aud()
|
||||
mk_aud_2g(OSMO_AUTH_ALG_COMP128v1, "0123456789abcdef0123456789abcdef")),
|
||||
0);
|
||||
ASSERT_SEL_AUD(imsi0, 0, id);
|
||||
ASSERT_DB_GET_AUC(imsi0, N_VECTORS);
|
||||
|
||||
/* same again */
|
||||
ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
|
||||
@@ -500,7 +594,8 @@ static void test_subscr_aud()
|
||||
ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
|
||||
mk_aud_2g(OSMO_AUTH_ALG_NONE, NULL)),
|
||||
0);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOENT, id);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
|
||||
ASSERT_DB_GET_AUC(imsi0, -ENOKEY);
|
||||
|
||||
/* Removing nothing results in -ENOENT */
|
||||
ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
|
||||
@@ -515,7 +610,8 @@ static void test_subscr_aud()
|
||||
ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
|
||||
mk_aud_2g(OSMO_AUTH_ALG_NONE, "f000000000000f00000000000f000000")),
|
||||
0);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOENT, id);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
|
||||
ASSERT_DB_GET_AUC(imsi0, -ENOKEY);
|
||||
|
||||
|
||||
comment("Set auth data, 3G only");
|
||||
@@ -526,6 +622,7 @@ static void test_subscr_aud()
|
||||
"C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)),
|
||||
0);
|
||||
ASSERT_SEL_AUD(imsi0, 0, id);
|
||||
ASSERT_DB_GET_AUC(imsi0, N_VECTORS);
|
||||
|
||||
/* same again */
|
||||
ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
|
||||
@@ -562,7 +659,8 @@ static void test_subscr_aud()
|
||||
ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
|
||||
mk_aud_3g(OSMO_AUTH_ALG_NONE, NULL, false, NULL, 0)),
|
||||
0);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOENT, id);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
|
||||
ASSERT_DB_GET_AUC(imsi0, -ENOKEY);
|
||||
|
||||
/* Removing nothing results in -ENOENT */
|
||||
ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
|
||||
@@ -575,13 +673,15 @@ static void test_subscr_aud()
|
||||
"BeefedCafeFaceAcedAddedDecadeFee", 5)),
|
||||
0);
|
||||
ASSERT_SEL_AUD(imsi0, 0, id);
|
||||
ASSERT_DB_GET_AUC(imsi0, N_VECTORS);
|
||||
|
||||
ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
|
||||
mk_aud_3g(OSMO_AUTH_ALG_NONE,
|
||||
"asdfasdfasd", false,
|
||||
"asdfasdfasdf", 99999)),
|
||||
0);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOENT, id);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
|
||||
ASSERT_DB_GET_AUC(imsi0, -ENOKEY);
|
||||
|
||||
|
||||
comment("Set auth data, 2G and 3G");
|
||||
@@ -595,6 +695,7 @@ static void test_subscr_aud()
|
||||
"DeafBeddedBabeAcceededFadedDecaf", 5)),
|
||||
0);
|
||||
ASSERT_SEL_AUD(imsi0, 0, id);
|
||||
ASSERT_DB_GET_AUC(imsi0, N_VECTORS);
|
||||
|
||||
|
||||
comment("Set invalid auth data");
|
||||
@@ -663,20 +764,86 @@ static void test_subscr_aud()
|
||||
|
||||
comment("Re-add subscriber and verify auth data didn't come back");
|
||||
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi0), 0);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
|
||||
ASSERT_SEL(imsi, imsi0, 0);
|
||||
|
||||
/* For this test to work, we want to get the same subscriber ID back,
|
||||
* and make sure there are no auth data leftovers for this ID. */
|
||||
OSMO_ASSERT(id == g_subscr.id);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOENT, id);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
|
||||
ASSERT_DB_GET_AUC(imsi0, -ENOKEY);
|
||||
|
||||
ASSERT_RC(db_subscr_delete_by_id(dbc, id), 0);
|
||||
ASSERT_SEL(imsi, imsi0, -ENOENT);
|
||||
ASSERT_DB_GET_AUC(imsi0, -ENOENT);
|
||||
|
||||
comment_end();
|
||||
}
|
||||
|
||||
/* Make each key too short in this test. Note that we can't set them longer than the allowed size without changing the
|
||||
* table structure. */
|
||||
static void test_subscr_aud_invalid_len()
|
||||
{
|
||||
int64_t id;
|
||||
|
||||
comment_start();
|
||||
comment("Create subscriber");
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
|
||||
ASSERT_SEL(imsi, imsi0, 0);
|
||||
id = g_subscr.id;
|
||||
|
||||
|
||||
/* Invalid Ki length */
|
||||
comment("Set auth data, 2G only, with invalid Ki length");
|
||||
ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
|
||||
mk_aud_2g(OSMO_AUTH_ALG_COMP128v1, "0123456789abcdef0123456789abcdef")),
|
||||
0);
|
||||
/* Use raw SQL to avoid length check in db_subscr_update_aud_by_id(). This changes all rows in the table, which
|
||||
* is fine for this test (implicit WHERE 1). */
|
||||
db_raw_sql(dbc, "UPDATE auc_2g SET ki = '0123456789abcdef0123456789abcde'");
|
||||
ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
|
||||
|
||||
comment("Remove 2G auth data");
|
||||
ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
|
||||
mk_aud_2g(OSMO_AUTH_ALG_NONE, NULL)),
|
||||
0);
|
||||
|
||||
/* Invalid K length */
|
||||
comment("Set auth data, 3G only, with invalid K length");
|
||||
ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
|
||||
mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
|
||||
"BeefedCafeFaceAcedAddedDecadeFee", true,
|
||||
"C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)),
|
||||
0);
|
||||
db_raw_sql(dbc, "UPDATE auc_3g SET k = 'C01ffedC1cadaeAc1d1f1edAcac1aB0'");
|
||||
ASSERT_SEL_AUD(imsi0, -EIO, id);
|
||||
|
||||
/* Invalid OP length */
|
||||
comment("Set auth data, 3G only, with invalid OP length");
|
||||
ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
|
||||
mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
|
||||
"BeefedCafeFaceAcedAddedDecadeFee", true,
|
||||
"C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)),
|
||||
0);
|
||||
db_raw_sql(dbc, "UPDATE auc_3g SET op = 'BeefedCafeFaceAcedAddedDecadeFe'");
|
||||
ASSERT_SEL_AUD(imsi0, -EIO, id);
|
||||
|
||||
/* Invalid OPC length */
|
||||
comment("Set auth data, 3G only, with invalid OPC length");
|
||||
ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
|
||||
mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
|
||||
"BeefedCafeFaceAcedAddedDecadeFee", false,
|
||||
"C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)),
|
||||
0);
|
||||
db_raw_sql(dbc, "UPDATE auc_3g SET opc = 'BeefedCafeFaceAcedAddedDecadeFe'");
|
||||
ASSERT_SEL_AUD(imsi0, -EIO, id);
|
||||
|
||||
|
||||
comment("Delete subscriber");
|
||||
ASSERT_RC(db_subscr_delete_by_id(dbc, id), 0);
|
||||
comment_end();
|
||||
}
|
||||
|
||||
static void test_subscr_sqn()
|
||||
{
|
||||
int64_t id;
|
||||
@@ -693,19 +860,19 @@ static void test_subscr_sqn()
|
||||
|
||||
comment("Create subscriber");
|
||||
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi0), 0);
|
||||
ASSERT_RC(db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
|
||||
ASSERT_SEL(imsi, imsi0, 0);
|
||||
|
||||
id = g_subscr.id;
|
||||
ASSERT_SEL_AUD(imsi0, -ENOENT, id);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
|
||||
|
||||
comment("Set SQN, but no 3G auth data present");
|
||||
|
||||
ASSERT_RC(db_update_sqn(dbc, id, 123), -ENOENT);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOENT, id);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
|
||||
|
||||
ASSERT_RC(db_update_sqn(dbc, id, 543), -ENOENT);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOENT, id);
|
||||
ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
|
||||
|
||||
comment("Set auth 3G data");
|
||||
|
||||
@@ -811,7 +978,7 @@ int main(int argc, char **argv)
|
||||
|
||||
handle_options(argc, argv);
|
||||
|
||||
osmo_init_logging(&hlr_log_info);
|
||||
osmo_init_logging2(ctx, &hlr_log_info);
|
||||
log_set_print_filename(osmo_stderr_target, cmdline_opts.verbose);
|
||||
log_set_print_timestamp(osmo_stderr_target, 0);
|
||||
log_set_use_color(osmo_stderr_target, 0);
|
||||
@@ -820,24 +987,23 @@ int main(int argc, char **argv)
|
||||
|
||||
/* omit the SQLite version and compilation flags from test output */
|
||||
log_set_log_level(osmo_stderr_target, LOGL_ERROR);
|
||||
dbc = db_open(ctx, "db_test.db");
|
||||
/* Disable SQLite logging so that we're not vulnerable on SQLite error messages changing across
|
||||
* library versions. */
|
||||
dbc = db_open(ctx, "db_test.db", false, false);
|
||||
log_set_log_level(osmo_stderr_target, 0);
|
||||
OSMO_ASSERT(dbc);
|
||||
|
||||
test_subscr_create_update_sel_delete();
|
||||
test_subscr_aud();
|
||||
test_subscr_aud_invalid_len();
|
||||
test_subscr_sqn();
|
||||
|
||||
printf("Done\n");
|
||||
db_close(dbc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* stubs */
|
||||
int auc_compute_vectors(struct osmo_auth_vector *vec, unsigned int num_vec,
|
||||
struct osmo_sub_auth_data *aud2g,
|
||||
struct osmo_sub_auth_data *aud3g,
|
||||
const uint8_t *rand_auts, const uint8_t *auts)
|
||||
{ OSMO_ASSERT(false); return -1; }
|
||||
void *lu_op_alloc_conn(void *conn)
|
||||
{ OSMO_ASSERT(false); return NULL; }
|
||||
void lu_op_tx_del_subscr_data(void *luop)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
--- Create with valid / invalid IMSI
|
||||
|
||||
db_subscr_create(dbc, imsi0) --> 0
|
||||
db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -11,7 +11,7 @@ struct hlr_subscriber {
|
||||
.imsi = '123456789000000',
|
||||
}
|
||||
|
||||
db_subscr_create(dbc, imsi1) --> 0
|
||||
db_subscr_create(dbc, imsi1, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi1, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -19,7 +19,7 @@ struct hlr_subscriber {
|
||||
.imsi = '123456789000001',
|
||||
}
|
||||
|
||||
db_subscr_create(dbc, imsi2) --> 0
|
||||
db_subscr_create(dbc, imsi2, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi2, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -27,8 +27,7 @@ struct hlr_subscriber {
|
||||
.imsi = '123456789000002',
|
||||
}
|
||||
|
||||
db_subscr_create(dbc, imsi0) --> -EIO
|
||||
DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi
|
||||
db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> -EIO
|
||||
DAUC IMSI='123456789000000': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
|
||||
@@ -37,12 +36,10 @@ struct hlr_subscriber {
|
||||
.imsi = '123456789000000',
|
||||
}
|
||||
|
||||
db_subscr_create(dbc, imsi1) --> -EIO
|
||||
DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi
|
||||
db_subscr_create(dbc, imsi1, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> -EIO
|
||||
DAUC IMSI='123456789000001': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi
|
||||
|
||||
db_subscr_create(dbc, imsi1) --> -EIO
|
||||
DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi
|
||||
db_subscr_create(dbc, imsi1, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> -EIO
|
||||
DAUC IMSI='123456789000001': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi1, &g_subscr) --> 0
|
||||
@@ -51,12 +48,10 @@ struct hlr_subscriber {
|
||||
.imsi = '123456789000001',
|
||||
}
|
||||
|
||||
db_subscr_create(dbc, imsi2) --> -EIO
|
||||
DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi
|
||||
db_subscr_create(dbc, imsi2, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> -EIO
|
||||
DAUC IMSI='123456789000002': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi
|
||||
|
||||
db_subscr_create(dbc, imsi2) --> -EIO
|
||||
DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi
|
||||
db_subscr_create(dbc, imsi2, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> -EIO
|
||||
DAUC IMSI='123456789000002': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi2, &g_subscr) --> 0
|
||||
@@ -65,31 +60,31 @@ struct hlr_subscriber {
|
||||
.imsi = '123456789000002',
|
||||
}
|
||||
|
||||
db_subscr_create(dbc, "123456789 000003") --> -EINVAL
|
||||
db_subscr_create(dbc, "123456789 000003", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> -EINVAL
|
||||
DAUC Cannot create subscriber: invalid IMSI: '123456789 000003'
|
||||
|
||||
db_subscr_get_by_imsi(dbc, "123456789000003", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123456789000003': No such subscriber
|
||||
|
||||
db_subscr_create(dbc, "123456789000002123456") --> -EINVAL
|
||||
db_subscr_create(dbc, "123456789000002123456", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> -EINVAL
|
||||
DAUC Cannot create subscriber: invalid IMSI: '123456789000002123456'
|
||||
|
||||
db_subscr_get_by_imsi(dbc, "123456789000002123456", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123456789000002123456': No such subscriber
|
||||
|
||||
db_subscr_create(dbc, "foobar123") --> -EINVAL
|
||||
db_subscr_create(dbc, "foobar123", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> -EINVAL
|
||||
DAUC Cannot create subscriber: invalid IMSI: 'foobar123'
|
||||
|
||||
db_subscr_get_by_imsi(dbc, "foobar123", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='foobar123': No such subscriber
|
||||
|
||||
db_subscr_create(dbc, "123") --> -EINVAL
|
||||
db_subscr_create(dbc, "123", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> -EINVAL
|
||||
DAUC Cannot create subscriber: invalid IMSI: '123'
|
||||
|
||||
db_subscr_get_by_imsi(dbc, "123", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123': No such subscriber
|
||||
|
||||
db_subscr_create(dbc, short_imsi) --> 0
|
||||
db_subscr_create(dbc, short_imsi, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, short_imsi, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -98,6 +93,13 @@ struct hlr_subscriber {
|
||||
}
|
||||
|
||||
|
||||
--- Check if subscriber exists (by IMSI)
|
||||
|
||||
db_subscr_exists_by_imsi(dbc, imsi0) --> 0
|
||||
|
||||
db_subscr_exists_by_imsi(dbc, unknown_imsi) --> -ENOENT
|
||||
|
||||
|
||||
--- Set valid / invalid MSISDN
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
|
||||
@@ -217,6 +219,13 @@ db_subscr_get_by_msisdn(dbc, "5432101234567891", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: MSISDN='5432101234567891': No such subscriber
|
||||
|
||||
|
||||
--- Check if subscriber exists (by MSISDN)
|
||||
|
||||
db_subscr_exists_by_msisdn(dbc, "543210123456789") --> 0
|
||||
|
||||
db_subscr_exists_by_msisdn(dbc, "5432101234567891") --> -ENOENT
|
||||
|
||||
|
||||
--- Set MSISDN on non-existent / invalid IMSI
|
||||
|
||||
db_subscr_update_msisdn_by_imsi(dbc, unknown_imsi, "99") --> -ENOENT
|
||||
@@ -232,6 +241,54 @@ db_subscr_get_by_msisdn(dbc, "99", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: MSISDN='99': No such subscriber
|
||||
|
||||
|
||||
--- Set valid / invalid IMEI
|
||||
|
||||
db_subscr_update_imei_by_imsi(dbc, imsi0, "12345678901234") --> 0
|
||||
|
||||
db_subscr_get_by_imei(dbc, "12345678901234", &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
.id = 1,
|
||||
.imsi = '123456789000000',
|
||||
.msisdn = '543210123456789',
|
||||
.imei = '12345678901234',
|
||||
}
|
||||
|
||||
db_subscr_update_imei_by_imsi(dbc, imsi0, "123456789012345") --> -EINVAL
|
||||
DAUC Cannot update subscriber IMSI='123456789000000': invalid IMEI: '123456789012345'
|
||||
|
||||
db_subscr_get_by_imei(dbc, "12345678901234", &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
.id = 1,
|
||||
.imsi = '123456789000000',
|
||||
.msisdn = '543210123456789',
|
||||
.imei = '12345678901234',
|
||||
}
|
||||
|
||||
db_subscr_get_by_imei(dbc, "123456789012345", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMEI=123456789012345: No such subscriber
|
||||
|
||||
|
||||
--- Set the same IMEI again
|
||||
|
||||
db_subscr_update_imei_by_imsi(dbc, imsi0, "12345678901234") --> 0
|
||||
|
||||
db_subscr_get_by_imei(dbc, "12345678901234", &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
.id = 1,
|
||||
.imsi = '123456789000000',
|
||||
.msisdn = '543210123456789',
|
||||
.imei = '12345678901234',
|
||||
}
|
||||
|
||||
|
||||
--- Remove IMEI
|
||||
|
||||
db_subscr_update_imei_by_imsi(dbc, imsi0, NULL) --> 0
|
||||
|
||||
db_subscr_get_by_imei(dbc, "12345678901234", &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMEI=12345678901234: No such subscriber
|
||||
|
||||
|
||||
--- Set / unset nam_cs and nam_ps
|
||||
|
||||
db_subscr_nam(dbc, imsi0, false, true) --> 0
|
||||
@@ -378,7 +435,7 @@ DAUC Cannot disable CS: no such subscriber: IMSI='foobar'
|
||||
|
||||
--- Record LU for PS and CS (SGSN and VLR names)
|
||||
|
||||
db_subscr_lu(dbc, id0, "5952", true) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "5952", true) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -388,7 +445,7 @@ struct hlr_subscriber {
|
||||
.sgsn_number = '5952',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, "712", false) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "712", false) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -402,7 +459,7 @@ struct hlr_subscriber {
|
||||
|
||||
--- Record LU for PS and CS (SGSN and VLR names) *again*
|
||||
|
||||
db_subscr_lu(dbc, id0, "111", true) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "111", true) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -413,7 +470,7 @@ struct hlr_subscriber {
|
||||
.sgsn_number = '111',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, "111", true) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "111", true) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -424,7 +481,7 @@ struct hlr_subscriber {
|
||||
.sgsn_number = '111',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, "222", false) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "222", false) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -435,7 +492,7 @@ struct hlr_subscriber {
|
||||
.sgsn_number = '111',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, "222", false) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "222", false) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -449,7 +506,7 @@ struct hlr_subscriber {
|
||||
|
||||
--- Unset LU info for PS and CS (SGSN and VLR names)
|
||||
|
||||
db_subscr_lu(dbc, id0, "", true) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "", true) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -459,7 +516,7 @@ struct hlr_subscriber {
|
||||
.vlr_number = '222',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, "", false) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "", false) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -468,9 +525,9 @@ struct hlr_subscriber {
|
||||
.msisdn = '543210123456789',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, "111", true) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "111", true) --> 0
|
||||
|
||||
db_subscr_lu(dbc, id0, "222", false) --> 0
|
||||
db_subscr_lu_str(dbc, id0, "222", false) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -481,7 +538,7 @@ struct hlr_subscriber {
|
||||
.sgsn_number = '111',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, NULL, true) --> 0
|
||||
db_subscr_lu_str(dbc, id0, NULL, true) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -491,7 +548,7 @@ struct hlr_subscriber {
|
||||
.vlr_number = '222',
|
||||
}
|
||||
|
||||
db_subscr_lu(dbc, id0, NULL, false) --> 0
|
||||
db_subscr_lu_str(dbc, id0, NULL, false) --> 0
|
||||
|
||||
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -503,10 +560,10 @@ struct hlr_subscriber {
|
||||
|
||||
--- Record LU for non-existent ID
|
||||
|
||||
db_subscr_lu(dbc, 99999, "5952", true) --> -ENOENT
|
||||
db_subscr_lu_str(dbc, 99999, "5952", true) --> -ENOENT
|
||||
DAUC Cannot update SGSN number for subscriber ID=99999: no such subscriber
|
||||
|
||||
db_subscr_lu(dbc, 99999, "712", false) --> -ENOENT
|
||||
db_subscr_lu_str(dbc, 99999, "712", false) --> -ENOENT
|
||||
DAUC Cannot update VLR number for subscriber ID=99999: no such subscriber
|
||||
|
||||
db_subscr_get_by_id(dbc, 99999, &g_subscr) --> -ENOENT
|
||||
@@ -709,6 +766,43 @@ db_subscr_delete_by_id(dbc, id_short) --> 0
|
||||
db_subscr_get_by_imsi(dbc, short_imsi, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123456': No such subscriber
|
||||
|
||||
|
||||
--- Create and delete subscribers with non-default nam_cs and nam_ps
|
||||
|
||||
db_subscr_create(dbc, imsi0, 0x00) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
.id = 1,
|
||||
.imsi = '123456789000000',
|
||||
.nam_cs = false,
|
||||
.nam_ps = false,
|
||||
}
|
||||
|
||||
db_subscr_create(dbc, imsi1, DB_SUBSCR_FLAG_NAM_CS) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi1, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
.id = 2,
|
||||
.imsi = '123456789000001',
|
||||
.nam_ps = false,
|
||||
}
|
||||
|
||||
db_subscr_create(dbc, imsi2, DB_SUBSCR_FLAG_NAM_PS) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi2, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
.id = 3,
|
||||
.imsi = '123456789000002',
|
||||
.nam_cs = false,
|
||||
}
|
||||
|
||||
db_subscr_delete_by_id(dbc, id0) --> 0
|
||||
|
||||
db_subscr_delete_by_id(dbc, id1) --> 0
|
||||
|
||||
db_subscr_delete_by_id(dbc, id2) --> 0
|
||||
|
||||
===== test_subscr_create_update_sel_delete: SUCCESS
|
||||
|
||||
|
||||
@@ -720,10 +814,13 @@ db_get_auth_data(dbc, unknown_imsi, &g_aud2g, &g_aud3g, &g_id) --> -2
|
||||
DAUC IMSI='999999999': No such subscriber
|
||||
|
||||
|
||||
db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -2
|
||||
DAUC IMSI='123456789000000': No such subscriber
|
||||
|
||||
|
||||
--- Create subscriber
|
||||
|
||||
db_subscr_create(dbc, imsi0) --> 0
|
||||
db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -731,11 +828,15 @@ struct hlr_subscriber {
|
||||
.imsi = '123456789000000',
|
||||
}
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
|
||||
db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
|
||||
--- Set auth data, 2G only
|
||||
|
||||
@@ -751,6 +852,11 @@ DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
}
|
||||
3G: none
|
||||
|
||||
db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> 3
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
DAUC IMSI='123456789000000': Calling to generate 3 vectors
|
||||
DAUC IMSI='123456789000000': Generated 3 vectors
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_COMP128v1, "0123456789abcdef0123456789abcdef")) --> 0
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0
|
||||
@@ -804,11 +910,15 @@ DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_NONE, NULL)) --> 0
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
|
||||
db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_NONE, NULL)) --> -ENOENT
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_XOR, "CededEffacedAceFacedBadFadedBeef")) --> 0
|
||||
@@ -825,11 +935,15 @@ DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_NONE, "f000000000000f00000000000f000000")) --> 0
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
|
||||
db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
|
||||
--- Set auth data, 3G only
|
||||
|
||||
@@ -849,6 +963,12 @@ DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
.u.umts.ind_bitlen = 5,
|
||||
}
|
||||
|
||||
db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> 3
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': Calling to generate 3 vectors
|
||||
DAUC IMSI='123456789000000': Generated 3 vectors
|
||||
DAUC IMSI='123456789000000': Updating SQN=0 in DB
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "BeefedCafeFaceAcedAddedDecadeFee", true, "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)) --> 0
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0
|
||||
@@ -917,11 +1037,15 @@ DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_NONE, NULL, false, NULL, 0)) --> 0
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
|
||||
db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_NONE, NULL, false, NULL, 0)) --> -ENOENT
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "CededEffacedAceFacedBadFadedBeef", false, "BeefedCafeFaceAcedAddedDecadeFee", 5)) --> 0
|
||||
@@ -940,13 +1064,23 @@ DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
.u.umts.ind_bitlen = 5,
|
||||
}
|
||||
|
||||
db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> 3
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': Calling to generate 3 vectors
|
||||
DAUC IMSI='123456789000000': Generated 3 vectors
|
||||
DAUC IMSI='123456789000000': Updating SQN=0 in DB
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_NONE, "asdfasdfasd", false, "asdfasdfasdf", 99999)) --> 0
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
|
||||
db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
|
||||
--- Set auth data, 2G and 3G
|
||||
|
||||
@@ -971,6 +1105,11 @@ db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0
|
||||
.u.umts.ind_bitlen = 5,
|
||||
}
|
||||
|
||||
db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> 3
|
||||
DAUC IMSI='123456789000000': Calling to generate 3 vectors
|
||||
DAUC IMSI='123456789000000': Generated 3 vectors
|
||||
DAUC IMSI='123456789000000': Updating SQN=0 in DB
|
||||
|
||||
|
||||
--- Set invalid auth data
|
||||
|
||||
@@ -1171,7 +1310,7 @@ DAUC Cannot read subscriber from db: IMSI='123456789000000': No such subscriber
|
||||
|
||||
--- Re-add subscriber and verify auth data didn't come back
|
||||
|
||||
db_subscr_create(dbc, imsi0) --> 0
|
||||
db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -1179,19 +1318,111 @@ struct hlr_subscriber {
|
||||
.imsi = '123456789000000',
|
||||
}
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
|
||||
db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
db_subscr_delete_by_id(dbc, id) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> -ENOENT
|
||||
DAUC Cannot read subscriber from db: IMSI='123456789000000': No such subscriber
|
||||
|
||||
db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -2
|
||||
DAUC IMSI='123456789000000': No such subscriber
|
||||
|
||||
===== test_subscr_aud: SUCCESS
|
||||
|
||||
|
||||
===== test_subscr_aud_invalid_len
|
||||
|
||||
--- Create subscriber
|
||||
|
||||
db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
.id = 1,
|
||||
.imsi = '123456789000000',
|
||||
}
|
||||
|
||||
|
||||
--- Set auth data, 2G only, with invalid Ki length
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_COMP128v1, "0123456789abcdef0123456789abcdef")) --> 0
|
||||
|
||||
raw SQL: UPDATE auc_2g SET ki = '0123456789abcdef0123456789abcde'
|
||||
sqlite3_prepare_v2(dbc->db, sql, -1, &stmt, NULL) --> SQLITE_OK
|
||||
|
||||
sqlite3_step(stmt) --> SQLITE_DONE
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': Error reading Ki, expected length 16 but has length 15
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
|
||||
|
||||
--- Remove 2G auth data
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_NONE, NULL)) --> 0
|
||||
|
||||
|
||||
--- Set auth data, 3G only, with invalid K length
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "BeefedCafeFaceAcedAddedDecadeFee", true, "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)) --> 0
|
||||
|
||||
raw SQL: UPDATE auc_3g SET k = 'C01ffedC1cadaeAc1d1f1edAcac1aB0'
|
||||
sqlite3_prepare_v2(dbc->db, sql, -1, &stmt, NULL) --> SQLITE_OK
|
||||
|
||||
sqlite3_step(stmt) --> SQLITE_DONE
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -5
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': Error reading K, expected length 16 but has length 15
|
||||
|
||||
|
||||
|
||||
--- Set auth data, 3G only, with invalid OP length
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "BeefedCafeFaceAcedAddedDecadeFee", true, "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)) --> 0
|
||||
|
||||
raw SQL: UPDATE auc_3g SET op = 'BeefedCafeFaceAcedAddedDecadeFe'
|
||||
sqlite3_prepare_v2(dbc->db, sql, -1, &stmt, NULL) --> SQLITE_OK
|
||||
|
||||
sqlite3_step(stmt) --> SQLITE_DONE
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -5
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': Error reading OP, expected length 16 but has length 15
|
||||
|
||||
|
||||
|
||||
--- Set auth data, 3G only, with invalid OPC length
|
||||
|
||||
db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "BeefedCafeFaceAcedAddedDecadeFee", false, "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)) --> 0
|
||||
|
||||
raw SQL: UPDATE auc_3g SET opc = 'BeefedCafeFaceAcedAddedDecadeFe'
|
||||
sqlite3_prepare_v2(dbc->db, sql, -1, &stmt, NULL) --> SQLITE_OK
|
||||
|
||||
sqlite3_step(stmt) --> SQLITE_DONE
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -5
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': Error reading OPC, expected length 16 but has length 15
|
||||
|
||||
|
||||
|
||||
--- Delete subscriber
|
||||
|
||||
db_subscr_delete_by_id(dbc, id) --> 0
|
||||
|
||||
===== test_subscr_aud_invalid_len: SUCCESS
|
||||
|
||||
|
||||
===== test_subscr_sqn
|
||||
|
||||
--- Set SQN for unknown subscriber
|
||||
@@ -1211,7 +1442,7 @@ DAUC Cannot read subscriber from db: ID=9999: No such subscriber
|
||||
|
||||
--- Create subscriber
|
||||
|
||||
db_subscr_create(dbc, imsi0) --> 0
|
||||
db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS) --> 0
|
||||
|
||||
db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
|
||||
struct hlr_subscriber {
|
||||
@@ -1219,7 +1450,7 @@ struct hlr_subscriber {
|
||||
.imsi = '123456789000000',
|
||||
}
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
@@ -1230,7 +1461,7 @@ DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
db_update_sqn(dbc, id, 123) --> -ENOENT
|
||||
DAUC Cannot update SQN for subscriber ID=1: no auc_3g entry for such subscriber
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
@@ -1238,7 +1469,7 @@ DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
db_update_sqn(dbc, id, 543) --> -ENOENT
|
||||
DAUC Cannot update SQN for subscriber ID=1: no auc_3g entry for such subscriber
|
||||
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2
|
||||
db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY
|
||||
DAUC IMSI='123456789000000': No 2G Auth Data
|
||||
DAUC IMSI='123456789000000': No 3G Auth Data
|
||||
|
||||
|
||||
14
tests/db_upgrade/Makefile.am
Normal file
14
tests/db_upgrade/Makefile.am
Normal file
@@ -0,0 +1,14 @@
|
||||
EXTRA_DIST = \
|
||||
db_upgrade_test.sh \
|
||||
db_upgrade_test.err \
|
||||
db_upgrade_test.ok \
|
||||
hlr_db_v0.sql \
|
||||
osmo-hlr.cfg \
|
||||
create_subscribers.vty \
|
||||
$(NULL)
|
||||
|
||||
update_exp:
|
||||
$(srcdir)/db_upgrade_test.sh $(srcdir) $(builddir) >"$(srcdir)/db_upgrade_test.ok" 2>"$(srcdir)/db_upgrade_test.err"
|
||||
|
||||
upgrade-equivalence-test:
|
||||
$(srcdir)/db_upgrade_test.sh $(srcdir) $(builddir) do-equivalence-test
|
||||
47
tests/db_upgrade/create_subscribers.vty
Normal file
47
tests/db_upgrade/create_subscribers.vty
Normal file
@@ -0,0 +1,47 @@
|
||||
OsmoHLR> enable
|
||||
OsmoHLR# subscriber imsi 123456789012345 create
|
||||
% Created subscriber 123456789012345
|
||||
ID: 1
|
||||
IMSI: 123456789012345
|
||||
MSISDN: none
|
||||
OsmoHLR# subscriber imsi 123456789012345 update msisdn 098765432109876
|
||||
% Updated subscriber IMSI='123456789012345' to MSISDN='098765432109876'
|
||||
OsmoHLR# subscriber imsi 123456789012345 update aud2g comp128v1 ki BeefedCafeFaceAcedAddedDecadeFee
|
||||
OsmoHLR# subscriber imsi 123456789012345 update aud3g milenage k C01ffedC1cadaeAc1d1f1edAcac1aB0a opc CededEffacedAceFacedBadFadedBeef
|
||||
OsmoHLR# subscriber imsi 111111111 create
|
||||
% Created subscriber 111111111
|
||||
ID: 2
|
||||
IMSI: 111111111
|
||||
MSISDN: none
|
||||
OsmoHLR# subscriber imsi 222222222 create
|
||||
% Created subscriber 222222222
|
||||
ID: 3
|
||||
IMSI: 222222222
|
||||
MSISDN: none
|
||||
OsmoHLR# subscriber imsi 222222222 update msisdn 22222
|
||||
% Updated subscriber IMSI='222222222' to MSISDN='22222'
|
||||
OsmoHLR# subscriber imsi 333333 create
|
||||
% Created subscriber 333333
|
||||
ID: 4
|
||||
IMSI: 333333
|
||||
MSISDN: none
|
||||
OsmoHLR# subscriber imsi 333333 update msisdn 3
|
||||
% Updated subscriber IMSI='333333' to MSISDN='3'
|
||||
OsmoHLR# subscriber imsi 333333 update aud2g comp128v2 ki 33333333333333333333333333333333
|
||||
OsmoHLR# subscriber imsi 444444444444444 create
|
||||
% Created subscriber 444444444444444
|
||||
ID: 5
|
||||
IMSI: 444444444444444
|
||||
MSISDN: none
|
||||
OsmoHLR# subscriber imsi 444444444444444 update msisdn 4444
|
||||
% Updated subscriber IMSI='444444444444444' to MSISDN='4444'
|
||||
OsmoHLR# subscriber imsi 444444444444444 update aud3g milenage k 44444444444444444444444444444444 op 44444444444444444444444444444444
|
||||
OsmoHLR# subscriber imsi 5555555 create
|
||||
% Created subscriber 5555555
|
||||
ID: 6
|
||||
IMSI: 5555555
|
||||
MSISDN: none
|
||||
OsmoHLR# subscriber imsi 5555555 update msisdn 55555555555555
|
||||
% Updated subscriber IMSI='5555555' to MSISDN='55555555555555'
|
||||
OsmoHLR# subscriber imsi 5555555 update aud2g xor ki 55555555555555555555555555555555
|
||||
OsmoHLR# subscriber imsi 5555555 update aud3g milenage k 55555555555555555555555555555555 opc 55555555555555555555555555555555
|
||||
6
tests/db_upgrade/create_subscribers_step2.sql
Normal file
6
tests/db_upgrade/create_subscribers_step2.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
update subscriber set vlr_number = 'MSC-1' where id = 1;
|
||||
update subscriber set ms_purged_cs = 1 where id = 2;
|
||||
update subscriber set ms_purged_ps = 1 where id = 3;
|
||||
update subscriber set nam_cs = 0 where id = 4;
|
||||
update subscriber set nam_ps = 0 where id = 5;
|
||||
update subscriber set nam_cs = 0, nam_ps = 0 where id = 6;
|
||||
0
tests/db_upgrade/db_upgrade_test.err
Normal file
0
tests/db_upgrade/db_upgrade_test.err
Normal file
173
tests/db_upgrade/db_upgrade_test.ok
Normal file
173
tests/db_upgrade/db_upgrade_test.ok
Normal file
@@ -0,0 +1,173 @@
|
||||
Creating db in schema version 0
|
||||
|
||||
Version 0 db:
|
||||
|
||||
Table: auc_2g
|
||||
name|type|notnull|dflt_value|pk
|
||||
algo_id_2g|INTEGER|1||0
|
||||
ki|VARCHAR(32)|1||0
|
||||
subscriber_id|INTEGER|0||1
|
||||
|
||||
Table auc_2g contents:
|
||||
algo_id_2g|ki|subscriber_id
|
||||
1|BeefedCafeFaceAcedAddedDecadeFee|1
|
||||
2|33333333333333333333333333333333|4
|
||||
4|55555555555555555555555555555555|6
|
||||
|
||||
Table: auc_3g
|
||||
name|type|notnull|dflt_value|pk
|
||||
algo_id_3g|INTEGER|1||0
|
||||
ind_bitlen|INTEGER|1|5|0
|
||||
k|VARCHAR(32)|1||0
|
||||
op|VARCHAR(32)|0||0
|
||||
opc|VARCHAR(32)|0||0
|
||||
sqn|INTEGER|1|0|0
|
||||
subscriber_id|INTEGER|0||1
|
||||
|
||||
Table auc_3g contents:
|
||||
algo_id_3g|ind_bitlen|k|op|opc|sqn|subscriber_id
|
||||
5|5|C01ffedC1cadaeAc1d1f1edAcac1aB0a||CededEffacedAceFacedBadFadedBeef|0|1
|
||||
5|5|44444444444444444444444444444444|44444444444444444444444444444444||0|5
|
||||
5|5|55555555555555555555555555555555||55555555555555555555555555555555|0|6
|
||||
|
||||
Table: subscriber
|
||||
name|type|notnull|dflt_value|pk
|
||||
ggsn_number|VARCHAR(15)|0||0
|
||||
gmlc_number|VARCHAR(15)|0||0
|
||||
hlr_number|VARCHAR(15)|0||0
|
||||
id|INTEGER|0||1
|
||||
imeisv|VARCHAR|0||0
|
||||
imsi|VARCHAR(15)|1||0
|
||||
lmsi|INTEGER|0||0
|
||||
ms_purged_cs|BOOLEAN|1|0|0
|
||||
ms_purged_ps|BOOLEAN|1|0|0
|
||||
msisdn|VARCHAR(15)|0||0
|
||||
nam_cs|BOOLEAN|1|1|0
|
||||
nam_ps|BOOLEAN|1|1|0
|
||||
periodic_lu_tmr|INTEGER|0||0
|
||||
periodic_rau_tau_tmr|INTEGER|0||0
|
||||
sgsn_address|VARCHAR|0||0
|
||||
sgsn_number|VARCHAR(15)|0||0
|
||||
smsc_number|VARCHAR(15)|0||0
|
||||
vlr_number|VARCHAR(15)|0||0
|
||||
|
||||
Table subscriber contents:
|
||||
ggsn_number|gmlc_number|hlr_number|id|imeisv|imsi|lmsi|ms_purged_cs|ms_purged_ps|msisdn|nam_cs|nam_ps|periodic_lu_tmr|periodic_rau_tau_tmr|sgsn_address|sgsn_number|smsc_number|vlr_number
|
||||
|||1||123456789012345||0|0|098765432109876|1|1||||||MSC-1
|
||||
|||2||111111111||1|0||1|1||||||
|
||||
|||3||222222222||0|1|22222|1|1||||||
|
||||
|||4||333333||0|0|3|0|1||||||
|
||||
|||5||444444444444444||0|0|4444|1|0||||||
|
||||
|||6||5555555||0|0|55555555555555|0|0||||||
|
||||
|
||||
Table: subscriber_apn
|
||||
name|type|notnull|dflt_value|pk
|
||||
apn|VARCHAR(256)|1||0
|
||||
subscriber_id|INTEGER|0||0
|
||||
|
||||
Table subscriber_apn contents:
|
||||
|
||||
Table: subscriber_multi_msisdn
|
||||
name|type|notnull|dflt_value|pk
|
||||
msisdn|VARCHAR(15)|1||0
|
||||
subscriber_id|INTEGER|0||0
|
||||
|
||||
Table subscriber_multi_msisdn contents:
|
||||
|
||||
Launching osmo-hlr to upgrade db:
|
||||
osmo-hlr --database $db --db-upgrade --db-check --config-file $srcdir/osmo-hlr.cfg
|
||||
rc = 0
|
||||
DMAIN hlr starting
|
||||
DDB using database: <PATH>test.db
|
||||
DDB Database <PATH>test.db' has HLR DB schema version 0
|
||||
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 1
|
||||
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 2
|
||||
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 3
|
||||
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 4
|
||||
DMAIN Cmdline option --db-check: Database was opened successfully, quitting.
|
||||
|
||||
Resulting db:
|
||||
|
||||
Table: auc_2g
|
||||
name|type|notnull|dflt_value|pk
|
||||
algo_id_2g|INTEGER|1||0
|
||||
ki|VARCHAR(32)|1||0
|
||||
subscriber_id|INTEGER|0||1
|
||||
|
||||
Table auc_2g contents:
|
||||
algo_id_2g|ki|subscriber_id
|
||||
1|BeefedCafeFaceAcedAddedDecadeFee|1
|
||||
2|33333333333333333333333333333333|4
|
||||
4|55555555555555555555555555555555|6
|
||||
|
||||
Table: auc_3g
|
||||
name|type|notnull|dflt_value|pk
|
||||
algo_id_3g|INTEGER|1||0
|
||||
ind_bitlen|INTEGER|1|5|0
|
||||
k|VARCHAR(32)|1||0
|
||||
op|VARCHAR(32)|0||0
|
||||
opc|VARCHAR(32)|0||0
|
||||
sqn|INTEGER|1|0|0
|
||||
subscriber_id|INTEGER|0||1
|
||||
|
||||
Table auc_3g contents:
|
||||
algo_id_3g|ind_bitlen|k|op|opc|sqn|subscriber_id
|
||||
5|5|C01ffedC1cadaeAc1d1f1edAcac1aB0a||CededEffacedAceFacedBadFadedBeef|0|1
|
||||
5|5|44444444444444444444444444444444|44444444444444444444444444444444||0|5
|
||||
5|5|55555555555555555555555555555555||55555555555555555555555555555555|0|6
|
||||
|
||||
Table: subscriber
|
||||
name|type|notnull|dflt_value|pk
|
||||
ggsn_number|VARCHAR(15)|0||0
|
||||
gmlc_number|VARCHAR(15)|0||0
|
||||
id|INTEGER|0||1
|
||||
imei|VARCHAR(14)|0||0
|
||||
imeisv|VARCHAR|0||0
|
||||
imsi|VARCHAR(15)|1||0
|
||||
last_lu_seen|TIMESTAMP|0|NULL|0
|
||||
lmsi|INTEGER|0||0
|
||||
ms_purged_cs|BOOLEAN|1|0|0
|
||||
ms_purged_ps|BOOLEAN|1|0|0
|
||||
msc_number|VARCHAR(15)|0||0
|
||||
msisdn|VARCHAR(15)|0||0
|
||||
nam_cs|BOOLEAN|1|1|0
|
||||
nam_ps|BOOLEAN|1|1|0
|
||||
periodic_lu_tmr|INTEGER|0||0
|
||||
periodic_rau_tau_tmr|INTEGER|0||0
|
||||
sgsn_address|VARCHAR|0||0
|
||||
sgsn_number|VARCHAR(15)|0||0
|
||||
sgsn_via_proxy|VARCHAR|0||0
|
||||
smsc_number|VARCHAR(15)|0||0
|
||||
vlr_number|VARCHAR(15)|0||0
|
||||
vlr_via_proxy|VARCHAR|0||0
|
||||
|
||||
Table subscriber contents:
|
||||
ggsn_number|gmlc_number|id|imei|imeisv|imsi|last_lu_seen|lmsi|ms_purged_cs|ms_purged_ps|msc_number|msisdn|nam_cs|nam_ps|periodic_lu_tmr|periodic_rau_tau_tmr|sgsn_address|sgsn_number|sgsn_via_proxy|smsc_number|vlr_number|vlr_via_proxy
|
||||
||1|||123456789012345|||0|0||098765432109876|1|1|||||||MSC-1|
|
||||
||2|||111111111|||1|0|||1|1||||||||
|
||||
||3|||222222222|||0|1||22222|1|1||||||||
|
||||
||4|||333333|||0|0||3|0|1||||||||
|
||||
||5|||444444444444444|||0|0||4444|1|0||||||||
|
||||
||6|||5555555|||0|0||55555555555555|0|0||||||||
|
||||
|
||||
Table: subscriber_apn
|
||||
name|type|notnull|dflt_value|pk
|
||||
apn|VARCHAR(256)|1||0
|
||||
subscriber_id|INTEGER|0||0
|
||||
|
||||
Table subscriber_apn contents:
|
||||
|
||||
Table: subscriber_multi_msisdn
|
||||
name|type|notnull|dflt_value|pk
|
||||
msisdn|VARCHAR(15)|1||0
|
||||
subscriber_id|INTEGER|0||0
|
||||
|
||||
Table subscriber_multi_msisdn contents:
|
||||
|
||||
Verify that osmo-hlr can open it:
|
||||
osmo-hlr --database $db --db-check --config-file $srcdir/osmo-hlr.cfg
|
||||
rc = 0
|
||||
DMAIN hlr starting
|
||||
DDB using database: <PATH>test.db
|
||||
DDB Database <PATH>test.db' has HLR DB schema version 4
|
||||
DMAIN Cmdline option --db-check: Database was opened successfully, quitting.
|
||||
83
tests/db_upgrade/db_upgrade_test.sh
Executable file
83
tests/db_upgrade/db_upgrade_test.sh
Executable file
@@ -0,0 +1,83 @@
|
||||
#!/bin/sh
|
||||
srcdir="${1:-.}"
|
||||
builddir="${2:-.}"
|
||||
do_equivalence_test="$3"
|
||||
|
||||
set -e
|
||||
|
||||
db="$builddir/test.db"
|
||||
osmo_hlr="$builddir/../../src/osmo-hlr"
|
||||
cfg="$srcdir/osmo-hlr.cfg"
|
||||
|
||||
dump_sorted_schema(){
|
||||
db_file="$1"
|
||||
tables="$(sqlite3 -batch -noheader "$db_file" "SELECT name FROM sqlite_master WHERE type = 'table' order by name")"
|
||||
for table in $tables; do
|
||||
echo
|
||||
echo "Table: $table"
|
||||
sqlite3 -batch -header "$db_file" "SELECT name,type,\"notnull\",dflt_value,pk FROM PRAGMA_TABLE_INFO('$table') order by name;"
|
||||
echo
|
||||
echo "Table $table contents:"
|
||||
columns="$(sqlite3 -batch -noheader "$db_file" "SELECT name FROM PRAGMA_TABLE_INFO('$table') order by name;")"
|
||||
sqlite3 -batch -header "$db_file" "SELECT $(echo $columns | sed 's/ /,/g') from $table;"
|
||||
done
|
||||
}
|
||||
|
||||
rm -f "$db"
|
||||
echo "Creating db in schema version 0"
|
||||
sqlite3 -batch "$db" < "$srcdir/hlr_db_v0.sql"
|
||||
|
||||
echo
|
||||
echo "Version 0 db:"
|
||||
dump_sorted_schema "$db"
|
||||
|
||||
set +e
|
||||
|
||||
echo
|
||||
echo "Launching osmo-hlr to upgrade db:"
|
||||
echo osmo-hlr --database '$db' --db-upgrade --db-check --config-file '$srcdir/osmo-hlr.cfg'
|
||||
"$osmo_hlr" --database "$db" --db-upgrade --db-check --config-file "$cfg" >log 2>&1
|
||||
echo "rc = $?"
|
||||
cat log | sed 's@[^ "]*/@<PATH>@g'
|
||||
|
||||
echo
|
||||
echo "Resulting db:"
|
||||
dump_sorted_schema "$db"
|
||||
|
||||
echo
|
||||
echo "Verify that osmo-hlr can open it:"
|
||||
echo osmo-hlr --database '$db' --db-check --config-file '$srcdir/osmo-hlr.cfg'
|
||||
"$osmo_hlr" --database "$db" --db-check --config-file "$cfg" >log 2>&1
|
||||
echo "rc = $?"
|
||||
cat log | sed 's@[^ "]*/@<PATH>@g'
|
||||
|
||||
if [ -n "$do_equivalence_test" ]; then
|
||||
# this part requires osmo_interact_vty.py, so this test is not part of the normal run
|
||||
set -e -x
|
||||
mint_db="$builddir/mint.db"
|
||||
rm -f "$mint_db"
|
||||
|
||||
osmo_verify_transcript_vty.py -v \
|
||||
-n OsmoHLR -p 4258 \
|
||||
-r "$osmo_hlr -c $cfg -l $mint_db" \
|
||||
"$srcdir/create_subscribers.vty"
|
||||
sqlite3 -batch "$mint_db" < "$srcdir/create_subscribers_step2.sql"
|
||||
|
||||
set +x
|
||||
test_dump="$builddir/test.dump"
|
||||
mint_dump="$builddir/mint.dump"
|
||||
|
||||
dump_sorted_schema "$db" > "$test_dump"
|
||||
dump_sorted_schema "$mint_db" > "$mint_dump"
|
||||
|
||||
echo
|
||||
echo "Newly created sorted schema is:"
|
||||
cat "$mint_dump"
|
||||
echo
|
||||
echo "Diff to upgraded schema:"
|
||||
diff -u "$mint_dump" "$test_dump"
|
||||
echo "rc=$?"
|
||||
fi
|
||||
|
||||
rm -f log
|
||||
rm -f "$db"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user