mirror of
				https://gitea.osmocom.org/cellular-infrastructure/osmo-hlr.git
				synced 2025-11-04 06:03:28 +00:00 
			
		
		
		
	Compare commits
	
		
			188 Commits
		
	
	
		
			0.2.0
			...
			osmith/ski
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					80dcbcdeca | ||
| 
						 | 
					c1d2f10cf6 | ||
| 
						 | 
					0bb8fce2f1 | ||
| 
						 | 
					637bbfcd92 | ||
| 
						 | 
					f10463c5fc | ||
| 
						 | 
					bf7deda0fc | ||
| 
						 | 
					81b92bbe69 | ||
| 
						 | 
					3a66698d87 | ||
| 
						 | 
					276c5a7719 | ||
| 
						 | 
					80dc9ae4be | ||
| 
						 | 
					a377c41bd4 | ||
| 
						 | 
					06f5af22c8 | ||
| 
						 | 
					07e1602d2d | ||
| 
						 | 
					abdfdb8a4a | ||
| 
						 | 
					7355d0ddfe | ||
| 
						 | 
					6b305b4c30 | ||
| 
						 | 
					a7d0f87eb7 | ||
| 
						 | 
					981e126686 | ||
| 
						 | 
					7143f3a0cb | ||
| 
						 | 
					f0968798a2 | ||
| 
						 | 
					2f75803e5d | ||
| 
						 | 
					7f4dd11682 | ||
| 
						 | 
					a8045daeef | ||
| 
						 | 
					4359b885d4 | ||
| 
						 | 
					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 | 
							
								
								
									
										33
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								.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,15 +27,43 @@ m4
 | 
			
		||||
missing
 | 
			
		||||
.deps
 | 
			
		||||
 | 
			
		||||
*.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
 | 
			
		||||
src/mslookup/osmo-mslookup-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*
 | 
			
		||||
tests/db_upgrade/*.dump
 | 
			
		||||
tests/mslookup/mdns_test
 | 
			
		||||
tests/mslookup/mslookup_client_mdns_test
 | 
			
		||||
tests/mslookup/mslookup_client_test
 | 
			
		||||
tests/mslookup/mslookup_test
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,9 @@ AUTOMAKE_OPTIONS = foreign dist-bzip2
 | 
			
		||||
SUBDIRS = \
 | 
			
		||||
	doc \
 | 
			
		||||
	src \
 | 
			
		||||
	include \
 | 
			
		||||
	sql \
 | 
			
		||||
	contrib \
 | 
			
		||||
	tests \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
@@ -11,6 +13,13 @@ EXTRA_DIST = \
 | 
			
		||||
	.version \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
AM_DISTCHECK_CONFIGURE_FLAGS = \
 | 
			
		||||
	--with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
 | 
			
		||||
 | 
			
		||||
pkgconfigdir = $(libdir)/pkgconfig
 | 
			
		||||
pkgconfig_DATA = libosmo-gsup-client.pc \
 | 
			
		||||
		 libosmo-mslookup.pc
 | 
			
		||||
 | 
			
		||||
@RELMAKE@
 | 
			
		||||
 | 
			
		||||
BUILT_SOURCES = $(top_srcdir)/.version
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										98
									
								
								configure.ac
									
									
									
									
									
								
							
							
						
						
									
										98
									
								
								configure.ac
									
									
									
									
									
								
							@@ -34,11 +34,11 @@ PKG_PROG_PKG_CONFIG([0.20])
 | 
			
		||||
 | 
			
		||||
PKG_CHECK_MODULES(TALLOC, [talloc >= 2.0.1])
 | 
			
		||||
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.11.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.11.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.11.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 0.11.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.5.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.3.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.3.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.3.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.3.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.6.0)
 | 
			
		||||
 | 
			
		||||
PKG_CHECK_MODULES(SQLITE3, sqlite3)
 | 
			
		||||
 | 
			
		||||
@@ -59,6 +59,21 @@ then
 | 
			
		||||
	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],
 | 
			
		||||
@@ -92,17 +107,90 @@ 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
 | 
			
		||||
	src/mslookup/Makefile
 | 
			
		||||
	include/Makefile
 | 
			
		||||
	include/osmocom/Makefile
 | 
			
		||||
	include/osmocom/hlr/Makefile
 | 
			
		||||
	include/osmocom/mslookup/Makefile
 | 
			
		||||
	libosmo-gsup-client.pc
 | 
			
		||||
	libosmo-mslookup.pc
 | 
			
		||||
	sql/Makefile
 | 
			
		||||
	doc/manuals/Makefile
 | 
			
		||||
	contrib/Makefile
 | 
			
		||||
	contrib/systemd/Makefile
 | 
			
		||||
	contrib/dgsm/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/Makefile
 | 
			
		||||
	)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								contrib/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								contrib/Makefile.am
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
SUBDIRS = \
 | 
			
		||||
	systemd \
 | 
			
		||||
	dgsm \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
							
								
								
									
										4
									
								
								contrib/dgsm/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								contrib/dgsm/Makefile.am
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
EXTRA_DIST = \
 | 
			
		||||
	osmo-mslookup-pipe.py \
 | 
			
		||||
	osmo-mslookup-socket.py \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
							
								
								
									
										24
									
								
								contrib/dgsm/osmo-mslookup-pipe.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										24
									
								
								contrib/dgsm/osmo-mslookup-pipe.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
# vim: shiftwidth=4 tabstop=4 expandtab
 | 
			
		||||
import subprocess
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
def query_mslookup(query_str):
 | 
			
		||||
    result = {'result': 'not-found'}
 | 
			
		||||
    proc = subprocess.Popen(('osmo-mslookup-client', '-f', 'json', query_str),
 | 
			
		||||
		            stdout=subprocess.PIPE)
 | 
			
		||||
    for line in iter(proc.stdout.readline,''):
 | 
			
		||||
        if not line:
 | 
			
		||||
            break
 | 
			
		||||
        response = json.loads(line)
 | 
			
		||||
        if response.get('result') == 'result':
 | 
			
		||||
                result = response
 | 
			
		||||
        print('Response: %r' % response)
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    import sys
 | 
			
		||||
    query_str = '1000-5000@sip.voice.12345.msisdn'
 | 
			
		||||
    if len(sys.argv) > 1:
 | 
			
		||||
        query_str = sys.argv[1]
 | 
			
		||||
    print('Final result: %r' % query_mslookup(query_str))
 | 
			
		||||
							
								
								
									
										35
									
								
								contrib/dgsm/osmo-mslookup-socket.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										35
									
								
								contrib/dgsm/osmo-mslookup-socket.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
# vim: shiftwidth=4 tabstop=4 expandtab
 | 
			
		||||
import socket
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
MSLOOKUP_SOCKET_PATH = '/tmp/mslookup'
 | 
			
		||||
 | 
			
		||||
def query_mslookup_socket(query_str, socket_path=MSLOOKUP_SOCKET_PATH):
 | 
			
		||||
    mslookup_socket = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)
 | 
			
		||||
    mslookup_socket.setblocking(True)
 | 
			
		||||
    mslookup_socket.connect(socket_path)
 | 
			
		||||
    result = {'result': 'not-found'}
 | 
			
		||||
    column_names = mslookup_socket.recv(1024).decode('ascii')
 | 
			
		||||
    if not column_names:
 | 
			
		||||
        return result
 | 
			
		||||
    column_names = column_names.split('\t')
 | 
			
		||||
    mslookup_socket.sendall(query_str.encode('ascii'))
 | 
			
		||||
    while True:
 | 
			
		||||
        csv = mslookup_socket.recv(1024).decode('ascii')
 | 
			
		||||
        if not csv:
 | 
			
		||||
            break
 | 
			
		||||
        response = dict(zip(column_names, csv.split('\t')))
 | 
			
		||||
        if response.get('result') == 'result':
 | 
			
		||||
            result = response
 | 
			
		||||
        print('Response: %r' % response)
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    import sys
 | 
			
		||||
    print(
 | 
			
		||||
        '\nPlease run separately: osmo-mslookup-client --socket /tmp/mslookup -d\n')
 | 
			
		||||
    query_str = '1000-5000@sip.voice.12345.msisdn'
 | 
			
		||||
    if len(sys.argv) > 1:
 | 
			
		||||
        query_str = sys.argv[1]
 | 
			
		||||
    print('Final result: %r' % query_mslookup_socket(query_str))
 | 
			
		||||
@@ -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,9 +49,14 @@ set -x
 | 
			
		||||
 | 
			
		||||
cd "$base"
 | 
			
		||||
autoreconf --install --force
 | 
			
		||||
./configure --enable-sanitize --enable-external-tests --enable-werror
 | 
			
		||||
./configure --enable-sanitize --enable-external-tests --enable-werror $CONFIG
 | 
			
		||||
$MAKE $PARALLEL_MAKE
 | 
			
		||||
$MAKE check || cat-testlogs.sh
 | 
			
		||||
$MAKE distcheck || 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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										189
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										189
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,192 @@
 | 
			
		||||
osmo-hlr (1.2.0) unstable; urgency=medium
 | 
			
		||||
 | 
			
		||||
  [ Ruben Undheim ]
 | 
			
		||||
  * Fix test for return codes on mipsel and alpha archs
 | 
			
		||||
 | 
			
		||||
  [ Thorsten Alteholz ]
 | 
			
		||||
  * fix spelling errors detected by lintian
 | 
			
		||||
 | 
			
		||||
  [ Pau Espin Pedrol ]
 | 
			
		||||
  * tests: Fix db_test err file to expect error code name instead of value
 | 
			
		||||
 | 
			
		||||
  [ Oliver Smith ]
 | 
			
		||||
  * tests/test_nodes.vty: check less libosmocore cmds
 | 
			
		||||
  * tests/db_upgrade: disable for old sqlite versions
 | 
			
		||||
  * gitignore: add tests/db_upgrade/*.dump
 | 
			
		||||
  * gsup_client.h: fix license header: GPLv2+
 | 
			
		||||
  * tests/auc: change back to python3
 | 
			
		||||
 | 
			
		||||
  [ Neels Hofmeyr ]
 | 
			
		||||
  * fix double free in osmo_gsup_client_enc_send()
 | 
			
		||||
  * db upgrade to v2: log version 2, not 1
 | 
			
		||||
  * fix upgrade to version 2: imei column default value
 | 
			
		||||
  * add --db-check option
 | 
			
		||||
  * hlr.sql: move comment
 | 
			
		||||
  * add db_upgrade test
 | 
			
		||||
  * hlr db schema 3: hlr_number -> msc_number
 | 
			
		||||
  * db.c: code dup: add db_run_statements() for arrays of statements
 | 
			
		||||
  * move headers to include/osmocom/hlr
 | 
			
		||||
  * fix upgrade test in presence of ~/.sqliterc
 | 
			
		||||
  * db upgrade: remove some code dup
 | 
			
		||||
  * add osmo_gsup_msgb_alloc()
 | 
			
		||||
  * Makefile convenience: add VTY_TEST var to run only one test
 | 
			
		||||
  * remove gsup_test
 | 
			
		||||
  * test_nodes.vty: tweak: add some '?' checks
 | 
			
		||||
  * db v4: add column last_lu_seen_ps
 | 
			
		||||
 | 
			
		||||
  [ Harald Welte ]
 | 
			
		||||
  * AUC: Add support for setting the AMF separation bit to '1' for EUTRAN
 | 
			
		||||
  * hlr: exit(2) on unsupported positional arguments on command line
 | 
			
		||||
 | 
			
		||||
 -- Pau Espin Pedrol <pespin@sysmocom.de>  Fri, 03 Jan 2020 12:37:35 +0100
 | 
			
		||||
 | 
			
		||||
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 ]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										61
									
								
								debian/control
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										61
									
								
								debian/control
									
									
									
									
										vendored
									
									
								
							@@ -7,12 +7,13 @@ Build-Depends: debhelper (>= 9),
 | 
			
		||||
               dh-autoreconf,
 | 
			
		||||
               dh-systemd (>= 1.5),
 | 
			
		||||
               autotools-dev,
 | 
			
		||||
               python-minimal,
 | 
			
		||||
               python3-minimal,
 | 
			
		||||
               libosmocore-dev,
 | 
			
		||||
               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,59 @@ 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: libosmo-mslookup0
 | 
			
		||||
Section: libs
 | 
			
		||||
Architecture: any
 | 
			
		||||
Multi-Arch: same
 | 
			
		||||
Depends: ${shlibs:Depends},
 | 
			
		||||
         ${misc:Depends}
 | 
			
		||||
Pre-Depends: ${misc:Pre-Depends}
 | 
			
		||||
Description: Osmocom MS lookup library
 | 
			
		||||
  This shared library contains routines for looking up mobile subscribers.
 | 
			
		||||
 | 
			
		||||
Package: libosmo-mslookup-dev
 | 
			
		||||
Architecture: any
 | 
			
		||||
Multi-Arch: same
 | 
			
		||||
Depends: ${misc:Depends},
 | 
			
		||||
	 libosmo-mslookup0 (= ${binary:Version}),
 | 
			
		||||
	 libosmocore-dev
 | 
			
		||||
Pre-Depends: ${misc:Pre-Depends}
 | 
			
		||||
Description: Development headers of Osmocom MS lookup library
 | 
			
		||||
  This shared library contains routines for looking up mobile subscribers.
 | 
			
		||||
  .
 | 
			
		||||
  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.*
 | 
			
		||||
							
								
								
									
										5
									
								
								debian/libosmo-mslookup-dev.install
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								debian/libosmo-mslookup-dev.install
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
usr/include/osmocom/mslookup
 | 
			
		||||
usr/lib/*/libosmo-mslookup*.a
 | 
			
		||||
usr/lib/*/libosmo-mslookup*.so
 | 
			
		||||
usr/lib/*/libosmo-mslookup*.la
 | 
			
		||||
usr/lib/*/pkgconfig/libosmo-mslookup.pc
 | 
			
		||||
							
								
								
									
										1
									
								
								debian/libosmo-mslookup0.install
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								debian/libosmo-mslookup0.install
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
usr/lib/*/libosmo-mslookup*.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,5 +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
 | 
			
		||||
/usr/share/doc/osmo-hlr/examples/osmo-hlr.cfg /etc/osmocom/
 | 
			
		||||
/var/lib/osmocom
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								debian/osmo-hlr.service
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								debian/osmo-hlr.service
									
									
									
									
										vendored
									
									
								
							@@ -1 +0,0 @@
 | 
			
		||||
../contrib/systemd/osmo-hlr.service
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,4 @@
 | 
			
		||||
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)/$$f" && \
 | 
			
		||||
		mkdir -p "$$(dirname $$j)" && \
 | 
			
		||||
		$(INSTALL_DATA) $(srcdir)/$$f $$j; \
 | 
			
		||||
	done
 | 
			
		||||
 | 
			
		||||
uninstall-hook:
 | 
			
		||||
	@$(PRE_UNINSTALL)
 | 
			
		||||
	for f in $$($(CFG_FILES)); do \
 | 
			
		||||
		j="$(DESTDIR)$(docdir)/$$f" && \
 | 
			
		||||
		$(RM) $$j; \
 | 
			
		||||
	done
 | 
			
		||||
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
 | 
			
		||||
@@ -5,9 +5,14 @@ log stderr
 | 
			
		||||
 logging filter all 1
 | 
			
		||||
 logging color 1
 | 
			
		||||
 logging print category 1
 | 
			
		||||
  logging timestamp 1
 | 
			
		||||
 logging print category-hex 0
 | 
			
		||||
 logging print level 1
 | 
			
		||||
 logging print file basename last
 | 
			
		||||
 logging print extended-timestamp 1
 | 
			
		||||
  logging level all debug
 | 
			
		||||
 logging level main notice
 | 
			
		||||
 logging level db notice
 | 
			
		||||
 logging level auc notice
 | 
			
		||||
 logging level ss info
 | 
			
		||||
 logging level linp error
 | 
			
		||||
!
 | 
			
		||||
line vty
 | 
			
		||||
@@ -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
											
										
									
								
							
							
								
								
									
										11
									
								
								include/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								include/Makefile.am
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
SUBDIRS = osmocom
 | 
			
		||||
 | 
			
		||||
nobase_include_HEADERS = \
 | 
			
		||||
	osmocom/gsupclient/gsup_client.h \
 | 
			
		||||
	osmocom/mslookup/mdns.h \
 | 
			
		||||
	osmocom/mslookup/mdns_sock.h \
 | 
			
		||||
	osmocom/mslookup/mslookup_client_fake.h \
 | 
			
		||||
	osmocom/mslookup/mslookup_client.h \
 | 
			
		||||
	osmocom/mslookup/mslookup_client_mdns.h \
 | 
			
		||||
	osmocom/mslookup/mslookup.h \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
							
								
								
									
										4
									
								
								include/osmocom/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								include/osmocom/Makefile.am
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
SUBDIRS = \
 | 
			
		||||
	hlr \
 | 
			
		||||
	mslookup \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
							
								
								
									
										76
									
								
								include/osmocom/gsupclient/gsup_client.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								include/osmocom/gsupclient/gsup_client.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
/* 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 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/>.
 | 
			
		||||
 */
 | 
			
		||||
#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);
 | 
			
		||||
 | 
			
		||||
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 */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								include/osmocom/hlr/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								include/osmocom/hlr/Makefile.am
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
noinst_HEADERS = \
 | 
			
		||||
	auc.h \
 | 
			
		||||
	ctrl.h \
 | 
			
		||||
	db.h \
 | 
			
		||||
	gsup_router.h \
 | 
			
		||||
	gsup_server.h \
 | 
			
		||||
	hlr.h \
 | 
			
		||||
	hlr_ussd.h \
 | 
			
		||||
	hlr_vty.h \
 | 
			
		||||
	hlr_vty_subscr.h \
 | 
			
		||||
	logging.h \
 | 
			
		||||
	luop.h \
 | 
			
		||||
	rand.h \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
@@ -9,8 +9,10 @@ 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 +22,15 @@ 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_SET_LAST_LU_SEEN_PS,
 | 
			
		||||
	DB_STMT_EXISTS_BY_IMSI,
 | 
			
		||||
	DB_STMT_EXISTS_BY_MSISDN,
 | 
			
		||||
	_NUM_DB_STMT
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -33,12 +40,17 @@ 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);
 | 
			
		||||
void db_close(struct db_context *dbc);
 | 
			
		||||
struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite3_logging);
 | 
			
		||||
struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite3_logging, bool allow_upgrades);
 | 
			
		||||
 | 
			
		||||
#include <osmocom/crypt/auth.h>
 | 
			
		||||
 | 
			
		||||
@@ -54,7 +66,7 @@ int db_update_sqn(struct db_context *dbc, int64_t id,
 | 
			
		||||
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,
 | 
			
		||||
	       const uint8_t *auts);
 | 
			
		||||
	       const uint8_t *auts, bool separation_bit);
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/linuxlist.h>
 | 
			
		||||
#include <osmocom/gsm/protocol/gsm_23_003.h>
 | 
			
		||||
@@ -67,10 +79,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 +95,15 @@ struct hlr_subscriber {
 | 
			
		||||
	uint32_t	lmsi;
 | 
			
		||||
	bool		ms_purged_cs;
 | 
			
		||||
	bool		ms_purged_ps;
 | 
			
		||||
	time_t		last_lu_seen;
 | 
			
		||||
	time_t		last_lu_seen_ps;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* 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 +127,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,6 +148,7 @@ 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);
 | 
			
		||||
@@ -1,8 +1,26 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <osmocom/hlr/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 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(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);
 | 
			
		||||
@@ -6,12 +6,19 @@
 | 
			
		||||
#include <osmocom/abis/ipaccess.h>
 | 
			
		||||
#include <osmocom/gsm/gsup.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
 | 
			
		||||
 | 
			
		||||
struct osmo_gsup_conn;
 | 
			
		||||
 | 
			
		||||
/* Expects message in msg->l2h */
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
@@ -40,6 +47,7 @@ struct osmo_gsup_conn {
 | 
			
		||||
	bool supports_ps; /* client supports OSMO_GSUP_CN_DOMAIN_PS */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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_ccm_get(const struct osmo_gsup_conn *clnt, uint8_t **addr,
 | 
			
		||||
@@ -49,8 +57,14 @@ 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);
 | 
			
		||||
						 struct llist_head *lu_op_lst,
 | 
			
		||||
						 void *priv);
 | 
			
		||||
 | 
			
		||||
void osmo_gsup_server_destroy(struct osmo_gsup_server *gsups);
 | 
			
		||||
 | 
			
		||||
void osmo_gsup_configure_wildcard_apn(struct osmo_gsup_message *gsup);
 | 
			
		||||
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);
 | 
			
		||||
@@ -23,12 +23,18 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <osmocom/core/linuxlist.h>
 | 
			
		||||
 | 
			
		||||
#define HLR_DEFAULT_DB_FILE_PATH "hlr.db"
 | 
			
		||||
 | 
			
		||||
struct hlr_euse;
 | 
			
		||||
 | 
			
		||||
struct hlr {
 | 
			
		||||
	/* GSUP server pointer */
 | 
			
		||||
	struct osmo_gsup_server *gs;
 | 
			
		||||
 | 
			
		||||
	/* DB context */
 | 
			
		||||
	char *db_file_path;
 | 
			
		||||
	struct db_context *dbc;
 | 
			
		||||
 | 
			
		||||
	/* Control Interface */
 | 
			
		||||
@@ -37,8 +43,27 @@ struct hlr {
 | 
			
		||||
 | 
			
		||||
	/* Local bind addr */
 | 
			
		||||
	char *gsup_bind_addr;
 | 
			
		||||
 | 
			
		||||
	struct llist_head euse_list;
 | 
			
		||||
	struct hlr_euse *euse_default;
 | 
			
		||||
 | 
			
		||||
	/* 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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern struct hlr *g_hlr;
 | 
			
		||||
 | 
			
		||||
struct hlr_subscriber;
 | 
			
		||||
 | 
			
		||||
void osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr);
 | 
			
		||||
							
								
								
									
										61
									
								
								include/osmocom/hlr/hlr_ussd.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								include/osmocom/hlr/hlr_ussd.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/linuxlist.h>
 | 
			
		||||
#include <osmocom/gsm/gsup.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/hlr/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);
 | 
			
		||||
 | 
			
		||||
int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);
 | 
			
		||||
int rx_proc_ss_error(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);
 | 
			
		||||
 | 
			
		||||
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 osmo_gsup_conn *conn, struct ss_session *ss,
 | 
			
		||||
			   const struct osmo_gsup_message *gsup, const struct ss_request *req);
 | 
			
		||||
};
 | 
			
		||||
@@ -25,13 +25,14 @@
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/vty/vty.h>
 | 
			
		||||
#include <osmocom/vty/command.h>
 | 
			
		||||
#include "hlr.h"
 | 
			
		||||
#include <osmocom/hlr/hlr.h>
 | 
			
		||||
 | 
			
		||||
enum hlr_vty_node {
 | 
			
		||||
	HLR_NODE = _LAST_OSMOVTY_NODE + 1,
 | 
			
		||||
	GSUP_NODE,
 | 
			
		||||
	EUSE_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);
 | 
			
		||||
							
								
								
									
										3
									
								
								include/osmocom/hlr/hlr_vty_subscr.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								include/osmocom/hlr/hlr_vty_subscr.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
void hlr_vty_subscriber_init(void);
 | 
			
		||||
@@ -7,6 +7,8 @@ enum {
 | 
			
		||||
	DDB,
 | 
			
		||||
	DGSUP,
 | 
			
		||||
	DAUC,
 | 
			
		||||
	DSS,
 | 
			
		||||
	DMSLOOKUP,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern const struct log_info hlr_log_info;
 | 
			
		||||
@@ -27,7 +27,8 @@
 | 
			
		||||
#include <osmocom/core/timer.h>
 | 
			
		||||
#include <osmocom/gsm/gsup.h>
 | 
			
		||||
 | 
			
		||||
#include "db.h"
 | 
			
		||||
#include <osmocom/hlr/db.h>
 | 
			
		||||
#include <osmocom/hlr/gsup_server.h>
 | 
			
		||||
 | 
			
		||||
#define CANCEL_TIMEOUT_SECS	30
 | 
			
		||||
#define ISD_TIMEOUT_SECS	30
 | 
			
		||||
@@ -62,9 +63,6 @@ struct lu_operation {
 | 
			
		||||
	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);
 | 
			
		||||
							
								
								
									
										6
									
								
								include/osmocom/mslookup/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								include/osmocom/mslookup/Makefile.am
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
# most headers here are installed, see /include/Makefile.am
 | 
			
		||||
 | 
			
		||||
noinst_HEADERS = \
 | 
			
		||||
	mdns_msg.h \
 | 
			
		||||
	mdns_rfc.h \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
							
								
								
									
										39
									
								
								include/osmocom/mslookup/mdns.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								include/osmocom/mslookup/mdns.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
/* Copyright 2019 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 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/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*! \file mdns.h */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/mslookup/mslookup.h>
 | 
			
		||||
 | 
			
		||||
#define OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT "mdns.osmocom.org"
 | 
			
		||||
 | 
			
		||||
struct msgb *osmo_mdns_query_encode(void *ctx, uint16_t packet_id, const struct osmo_mslookup_query *query,
 | 
			
		||||
				    const char *domain_suffix);
 | 
			
		||||
 | 
			
		||||
struct osmo_mslookup_query *osmo_mdns_query_decode(void *ctx, const uint8_t *data, size_t data_len,
 | 
			
		||||
						   uint16_t *packet_id, const char *domain_suffix);
 | 
			
		||||
 | 
			
		||||
struct msgb *osmo_mdns_result_encode(void *ctx, uint16_t packet_id, const struct osmo_mslookup_query *query,
 | 
			
		||||
				     const struct osmo_mslookup_result *result, const char *domain_suffix);
 | 
			
		||||
 | 
			
		||||
int osmo_mdns_result_decode(void *ctx, const uint8_t *data, size_t data_len, uint16_t *packet_id,
 | 
			
		||||
			    struct osmo_mslookup_query *query, struct osmo_mslookup_result *result,
 | 
			
		||||
			    const char *domain_suffix);
 | 
			
		||||
							
								
								
									
										54
									
								
								include/osmocom/mslookup/mdns_msg.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								include/osmocom/mslookup/mdns_msg.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
/* Copyright 2019 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 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/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include "mdns_rfc.h"
 | 
			
		||||
 | 
			
		||||
struct osmo_mdns_record {
 | 
			
		||||
	struct llist_head list;
 | 
			
		||||
	enum osmo_mdns_rfc_record_type type;
 | 
			
		||||
	uint16_t length;
 | 
			
		||||
	uint8_t *data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct osmo_mdns_msg_request {
 | 
			
		||||
	uint16_t id;
 | 
			
		||||
	char *domain;
 | 
			
		||||
	enum osmo_mdns_rfc_record_type type;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct osmo_mdns_msg_answer {
 | 
			
		||||
	uint16_t id;
 | 
			
		||||
	char *domain;
 | 
			
		||||
	/*! list of osmo_mdns_record. */
 | 
			
		||||
	struct llist_head records;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int osmo_mdns_msg_request_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_msg_request *req);
 | 
			
		||||
struct osmo_mdns_msg_request *osmo_mdns_msg_request_decode(void *ctx, const uint8_t *data, size_t data_len);
 | 
			
		||||
 | 
			
		||||
void osmo_mdns_msg_answer_init(struct osmo_mdns_msg_answer *answer);
 | 
			
		||||
int osmo_mdns_msg_answer_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_msg_answer *ans);
 | 
			
		||||
struct osmo_mdns_msg_answer *osmo_mdns_msg_answer_decode(void *ctx, const uint8_t *data, size_t data_len);
 | 
			
		||||
int osmo_mdns_result_from_answer(struct osmo_mslookup_result *result, const struct osmo_mdns_msg_answer *ans);
 | 
			
		||||
 | 
			
		||||
struct osmo_mdns_record *osmo_mdns_record_txt_keyval_encode(void *ctx, const char *key, const char *value_fmt, ...);
 | 
			
		||||
int osmo_mdns_record_txt_keyval_decode(const struct osmo_mdns_record *rec,
 | 
			
		||||
				       char *key_buf, size_t key_size, char *value_buf, size_t value_size);
 | 
			
		||||
							
								
								
									
										113
									
								
								include/osmocom/mslookup/mdns_rfc.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								include/osmocom/mslookup/mdns_rfc.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
			
		||||
/* Copyright 2019 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 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/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/core/endian.h>
 | 
			
		||||
#include <osmocom/mslookup/mdns.h>
 | 
			
		||||
 | 
			
		||||
/* RFC 1035 2.3.4 */
 | 
			
		||||
#define OSMO_MDNS_RFC_MAX_NAME_LEN 255
 | 
			
		||||
 | 
			
		||||
/* RFC 1035 3.3 <character-string> */
 | 
			
		||||
#define OSMO_MDNS_RFC_MAX_CHARACTER_STRING_LEN 256
 | 
			
		||||
 | 
			
		||||
enum osmo_mdns_rfc_record_type {
 | 
			
		||||
	OSMO_MDNS_RFC_RECORD_TYPE_UNKNOWN = 0,
 | 
			
		||||
 | 
			
		||||
	/* RFC 1035 3.2.2 */
 | 
			
		||||
	OSMO_MDNS_RFC_RECORD_TYPE_A = 1, /* IPv4 address */
 | 
			
		||||
	OSMO_MDNS_RFC_RECORD_TYPE_TXT = 16, /* Text strings */
 | 
			
		||||
 | 
			
		||||
	/* RFC 3596 2.1 */
 | 
			
		||||
	OSMO_MDNS_RFC_RECORD_TYPE_AAAA = 28, /* IPv6 address */
 | 
			
		||||
 | 
			
		||||
	/* RFC 1035 3.2.3 */
 | 
			
		||||
	OSMO_MDNS_RFC_RECORD_TYPE_ALL = 255, /* Request only: ask for all */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum osmo_mdns_rfc_class {
 | 
			
		||||
	OSMO_MDNS_RFC_CLASS_UNKNOWN = 0,
 | 
			
		||||
 | 
			
		||||
	/* RFC 1035 3.2.4 */
 | 
			
		||||
	OSMO_MDNS_RFC_CLASS_IN = 1, /* Internet and IP networks */
 | 
			
		||||
 | 
			
		||||
	/* RFC 1035 3.2.5 */
 | 
			
		||||
	OSMO_MDNS_RFC_CLASS_ALL = 255, /* Request only: ask for all */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* RFC 1035 4.1.1 */
 | 
			
		||||
struct osmo_mdns_rfc_header {
 | 
			
		||||
#if OSMO_IS_LITTLE_ENDIAN
 | 
			
		||||
	uint16_t id;
 | 
			
		||||
	uint8_t rd:1,
 | 
			
		||||
		tc:1,
 | 
			
		||||
		aa:1,
 | 
			
		||||
		opcode:4,
 | 
			
		||||
		qr:1; /* QR (0: query, 1: response) */
 | 
			
		||||
	uint8_t rcode:4,
 | 
			
		||||
		z:3,
 | 
			
		||||
		ra:1;
 | 
			
		||||
	uint16_t qdcount; /* Number of questions */
 | 
			
		||||
	uint16_t ancount; /* Number of answers */
 | 
			
		||||
	uint16_t nscount; /* Number of authority records */
 | 
			
		||||
	uint16_t arcount; /* Number of additional records */
 | 
			
		||||
#elif OSMO_IS_BIG_ENDIAN
 | 
			
		||||
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
 | 
			
		||||
	uint16_t id;
 | 
			
		||||
	uint8_t qr:1, opcode:4, aa:1, tc:1, rd:1;
 | 
			
		||||
	uint8_t ra:1, z:3, rcode:4;
 | 
			
		||||
	uint16_t qdcount;
 | 
			
		||||
	uint16_t ancount;
 | 
			
		||||
	uint16_t nscount;
 | 
			
		||||
	uint16_t arcount;
 | 
			
		||||
#endif
 | 
			
		||||
} __attribute__ ((packed));
 | 
			
		||||
 | 
			
		||||
/* RFC 1035 4.1.2 */
 | 
			
		||||
struct osmo_mdns_rfc_question {
 | 
			
		||||
	char *domain; /* Domain to be encoded as qname (e.g. "gsup.hlr.1234567.imsi") */
 | 
			
		||||
	enum osmo_mdns_rfc_record_type qtype;
 | 
			
		||||
	enum osmo_mdns_rfc_class qclass;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* RFC 1035 4.1.3 */
 | 
			
		||||
struct osmo_mdns_rfc_record {
 | 
			
		||||
	char *domain; /* Domain to be encoded as name (e.g. "gsup.hlr.1234567.imsi") */
 | 
			
		||||
	enum osmo_mdns_rfc_record_type type;
 | 
			
		||||
	enum osmo_mdns_rfc_class class;
 | 
			
		||||
	uint32_t ttl;
 | 
			
		||||
	uint16_t rdlength;
 | 
			
		||||
	uint8_t *rdata;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
char *osmo_mdns_rfc_qname_encode(void *ctx, const char *domain);
 | 
			
		||||
char *osmo_mdns_rfc_qname_decode(void *ctx, const char *qname, size_t qname_len);
 | 
			
		||||
 | 
			
		||||
void osmo_mdns_rfc_header_encode(struct msgb *msg, const struct osmo_mdns_rfc_header *hdr);
 | 
			
		||||
int osmo_mdns_rfc_header_decode(const uint8_t *data, size_t data_len, struct osmo_mdns_rfc_header *hdr);
 | 
			
		||||
 | 
			
		||||
int osmo_mdns_rfc_question_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_rfc_question *qst);
 | 
			
		||||
struct osmo_mdns_rfc_question *osmo_mdns_rfc_question_decode(void *ctx, const uint8_t *data, size_t data_len);
 | 
			
		||||
 | 
			
		||||
int osmo_mdns_rfc_record_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_rfc_record *rec);
 | 
			
		||||
struct osmo_mdns_rfc_record *osmo_mdns_rfc_record_decode(void *ctx, const uint8_t *data, size_t data_len,
 | 
			
		||||
							 size_t *record_len);
 | 
			
		||||
							
								
								
									
										33
									
								
								include/osmocom/mslookup/mdns_sock.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								include/osmocom/mslookup/mdns_sock.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
/* Copyright 2019 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 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/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
 | 
			
		||||
struct osmo_mdns_sock {
 | 
			
		||||
	struct osmo_fd osmo_fd;
 | 
			
		||||
	struct addrinfo *ai;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct osmo_mdns_sock *osmo_mdns_sock_init(void *ctx, const char *ip, unsigned int port,
 | 
			
		||||
					   int (*cb)(struct osmo_fd *fd, unsigned int what),
 | 
			
		||||
					   void *data, unsigned int priv_nr);
 | 
			
		||||
int osmo_mdns_sock_send(const struct osmo_mdns_sock *mdns_sock, struct msgb *msg);
 | 
			
		||||
void osmo_mdns_sock_cleanup(struct osmo_mdns_sock *mdns_sock);
 | 
			
		||||
							
								
								
									
										121
									
								
								include/osmocom/mslookup/mslookup.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								include/osmocom/mslookup/mslookup.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
			
		||||
/* Copyright 2019 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 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/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*! \defgroup mslookup Distributed GSM: finding subscribers
 | 
			
		||||
 *  @{
 | 
			
		||||
 * \file mslookup.h
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include <osmocom/core/sockaddr_str.h>
 | 
			
		||||
#include <osmocom/gsm/protocol/gsm_23_003.h>
 | 
			
		||||
 | 
			
		||||
#define OSMO_MSLOOKUP_SERVICE_MAXLEN 64
 | 
			
		||||
 | 
			
		||||
bool osmo_mslookup_service_valid(const char *service);
 | 
			
		||||
 | 
			
		||||
enum osmo_mslookup_id_type {
 | 
			
		||||
	OSMO_MSLOOKUP_ID_NONE = 0,
 | 
			
		||||
	OSMO_MSLOOKUP_ID_IMSI,
 | 
			
		||||
	OSMO_MSLOOKUP_ID_MSISDN,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern const struct value_string osmo_mslookup_id_type_names[];
 | 
			
		||||
static inline const char *osmo_mslookup_id_type_name(enum osmo_mslookup_id_type val)
 | 
			
		||||
{ return get_value_string(osmo_mslookup_id_type_names, val); }
 | 
			
		||||
 | 
			
		||||
struct osmo_mslookup_id {
 | 
			
		||||
	enum osmo_mslookup_id_type type;
 | 
			
		||||
	union {
 | 
			
		||||
		char imsi[GSM23003_IMSI_MAX_DIGITS+1];
 | 
			
		||||
		char msisdn[GSM23003_MSISDN_MAX_DIGITS+1];
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int osmo_mslookup_id_cmp(const struct osmo_mslookup_id *a, const struct osmo_mslookup_id *b);
 | 
			
		||||
bool osmo_mslookup_id_valid(const struct osmo_mslookup_id *id);
 | 
			
		||||
 | 
			
		||||
enum osmo_mslookup_result_code {
 | 
			
		||||
	OSMO_MSLOOKUP_RC_NONE = 0,
 | 
			
		||||
	/*! An intermediate valid result. The request is still open for more results. */
 | 
			
		||||
	OSMO_MSLOOKUP_RC_RESULT,
 | 
			
		||||
	/*! Returned when the final request timeout has elapsed without results. */
 | 
			
		||||
	OSMO_MSLOOKUP_RC_NOT_FOUND,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern const struct value_string osmo_mslookup_result_code_names[];
 | 
			
		||||
static inline const char *osmo_mslookup_result_code_name(enum osmo_mslookup_result_code val)
 | 
			
		||||
{ return get_value_string(osmo_mslookup_result_code_names, val); }
 | 
			
		||||
 | 
			
		||||
/*! Information to request from a lookup. */
 | 
			
		||||
struct osmo_mslookup_query {
 | 
			
		||||
	/*! Which service to request, by freely invented names. For service name conventions (for voice, SMS, HLR,...),
 | 
			
		||||
	 * refer to the OsmoHLR user's manual http://ftp.osmocom.org/docs/latest/osmohlr-usermanual.pdf */
 | 
			
		||||
	char service[OSMO_MSLOOKUP_SERVICE_MAXLEN + 1];
 | 
			
		||||
	/*! IMSI or MSISDN to look up. */
 | 
			
		||||
	struct osmo_mslookup_id id;
 | 
			
		||||
 | 
			
		||||
	/*! Caller provided private data, if desired. */
 | 
			
		||||
	void *priv;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*! Result data as passed back to a lookup client that invoked an osmo_mslookup_client_request. */
 | 
			
		||||
struct osmo_mslookup_result {
 | 
			
		||||
	/*! Outcome of the request. */
 | 
			
		||||
	enum osmo_mslookup_result_code rc;
 | 
			
		||||
 | 
			
		||||
	/*! IP address and port to reach the given service via IPv4, if any. */
 | 
			
		||||
	struct osmo_sockaddr_str host_v4;
 | 
			
		||||
 | 
			
		||||
	/*! IP address and port to reach the given service via IPv6, if any. */
 | 
			
		||||
	struct osmo_sockaddr_str host_v6;
 | 
			
		||||
 | 
			
		||||
	/*! How long ago the service last verified presence of the subscriber, in seconds, or zero if the presence is
 | 
			
		||||
	 * invariable (like the home HLR record for an IMSI).
 | 
			
		||||
	 * If a subscriber has recently moved to a different location, we get multiple replies and want to choose the
 | 
			
		||||
	 * most recent one. If this were a timestamp, firstly the time zones would need to be taken care of.
 | 
			
		||||
	 * Even if we choose UTC, a service provider with an inaccurate date/time would end up affecting the result.
 | 
			
		||||
	 * The least susceptible to configuration errors or difference in local and remote clock is a value that
 | 
			
		||||
	 * indicates the actual age of the record in seconds. The time that the lookup query took to be answered should
 | 
			
		||||
	 * be neglectable here, since we would typically wait one second (or very few seconds) for lookup replies,
 | 
			
		||||
	 * while typical Location Updating periods are in the range of 15 minutes. */
 | 
			
		||||
	uint32_t age;
 | 
			
		||||
 | 
			
		||||
	/*! Whether this is the last result returned for this request. */
 | 
			
		||||
	bool last;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int osmo_mslookup_query_init_from_domain_str(struct osmo_mslookup_query *q, const char *domain);
 | 
			
		||||
 | 
			
		||||
size_t osmo_mslookup_id_name_buf(char *buf, size_t buflen, const struct osmo_mslookup_id *id);
 | 
			
		||||
char *osmo_mslookup_id_name_c(void *ctx, const struct osmo_mslookup_id *id);
 | 
			
		||||
char *osmo_mslookup_id_name_b(char *buf, size_t buflen, const struct osmo_mslookup_id *id);
 | 
			
		||||
 | 
			
		||||
size_t osmo_mslookup_result_to_str_buf(char *buf, size_t buflen,
 | 
			
		||||
				     const struct osmo_mslookup_query *query,
 | 
			
		||||
				     const struct osmo_mslookup_result *result);
 | 
			
		||||
char *osmo_mslookup_result_name_c(void *ctx,
 | 
			
		||||
				  const struct osmo_mslookup_query *query,
 | 
			
		||||
				  const struct osmo_mslookup_result *result);
 | 
			
		||||
char *osmo_mslookup_result_name_b(char *buf, size_t buflen,
 | 
			
		||||
				  const struct osmo_mslookup_query *query,
 | 
			
		||||
				  const struct osmo_mslookup_result *result);
 | 
			
		||||
 | 
			
		||||
/*! @} */
 | 
			
		||||
							
								
								
									
										132
									
								
								include/osmocom/mslookup/mslookup_client.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								include/osmocom/mslookup/mslookup_client.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
			
		||||
/* Copyright 2019 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 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/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/linuxlist.h>
 | 
			
		||||
#include <osmocom/core/timer.h>
 | 
			
		||||
#include <osmocom/core/sockaddr_str.h>
 | 
			
		||||
#include <osmocom/mslookup/mslookup.h>
 | 
			
		||||
 | 
			
		||||
struct osmo_mslookup_client;
 | 
			
		||||
struct osmo_mslookup_result;
 | 
			
		||||
 | 
			
		||||
typedef void (*osmo_mslookup_cb_t)(struct osmo_mslookup_client *client,
 | 
			
		||||
				   uint32_t request_handle,
 | 
			
		||||
				   const struct osmo_mslookup_query *query,
 | 
			
		||||
				   const struct osmo_mslookup_result *result);
 | 
			
		||||
 | 
			
		||||
/*! This handling information is passed along with a lookup request.
 | 
			
		||||
 * It tells the osmo_mslookup_client layer how to handle responses received from various mslookup methods (at the time
 | 
			
		||||
 * of writing only mDNS exists as a method, but the intention is to easily allow adding other methods in the future).
 | 
			
		||||
 * This query handling info is not seen by the individual method implementations, to clarify that it is the
 | 
			
		||||
 * osmo_mslookup_client layer that takes care of these details. */
 | 
			
		||||
struct osmo_mslookup_query_handling {
 | 
			
		||||
	/*! Wait at least this long before returning any results.
 | 
			
		||||
	 *
 | 
			
		||||
	 * If nonzero, result_cb will be called as soon as this delay has elapsed, either with the so far youngest age
 | 
			
		||||
	 * result, or with a "not found yet" result. After this delay has elapsed, receiving results will continue
 | 
			
		||||
	 * until result_timeout_milliseconds has elapsed.
 | 
			
		||||
	 *
 | 
			
		||||
	 * If zero, responses are fed to the result_cb right from the start, every time a younger aged result than
 | 
			
		||||
	 * before comes in.
 | 
			
		||||
	 *
 | 
			
		||||
	 * If a result with age == 0 is received, min_wait_milliseconds is ignored, the result is returned immediately
 | 
			
		||||
	 * and listening for responses ends.
 | 
			
		||||
	 *
 | 
			
		||||
	 * Rationale: If a subscriber has recently moved between sites, multiple results will arrive, and the youngest
 | 
			
		||||
	 * age wins. It can make sense to wait a minimum time for responses before determining the winning result.
 | 
			
		||||
	 *
 | 
			
		||||
	 * However, if no result or no valid result has arrived within a short period, the subscriber may be at a site
 | 
			
		||||
	 * that is far away or that is currently experiencing high latency. It is thus a good safety net to still
 | 
			
		||||
	 * receive results for an extended period of time.
 | 
			
		||||
	 *
 | 
			
		||||
	 * For some services, it is possible to establish links to every received result, and whichever link succeeds
 | 
			
		||||
	 * will be used (for example for SIP calls: first to pick up the call gets connected, the others are dropped
 | 
			
		||||
	 * silently).
 | 
			
		||||
	 */
 | 
			
		||||
	uint32_t min_wait_milliseconds;
 | 
			
		||||
 | 
			
		||||
	/*! Total time in milliseconds to listen for lookup responses.
 | 
			
		||||
	 *
 | 
			
		||||
	 * When this timeout elapses, osmo_mslookup_client_request_cancel() is called implicitly; Manually invoking
 | 
			
		||||
	 * osmo_mslookup_client_request_cancel() after result_timeout_milliseconds has elapsed is not necessary, but is
 | 
			
		||||
	 * still safe to do anyway.
 | 
			
		||||
	 *
 | 
			
		||||
	 * If zero, min_wait_milliseconds is also used as result_timeout_milliseconds; if that is also zero, a default
 | 
			
		||||
	 * timeout value is used.
 | 
			
		||||
	 *
 | 
			
		||||
	 * If result_timeout_milliseconds <= min_wait_milliseconds, then min_wait_milliseconds is used as
 | 
			
		||||
	 * result_timeout_milliseconds, i.e. the timeout triggers as soon as min_wait_milliseconds hits.
 | 
			
		||||
	 *
 | 
			
		||||
	 * osmo_mslookup_client_request_cancel() can be called any time to end the request.
 | 
			
		||||
	 */
 | 
			
		||||
	uint32_t result_timeout_milliseconds;
 | 
			
		||||
 | 
			
		||||
	/*! Invoked every time a result with a younger age than the previous result has arrived.
 | 
			
		||||
	 * To stop receiving results before result_timeout_milliseconds has elapsed, call
 | 
			
		||||
	 * osmo_mslookup_client_request_cancel().
 | 
			
		||||
	 */
 | 
			
		||||
	osmo_mslookup_cb_t result_cb;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
uint32_t osmo_mslookup_client_request(struct osmo_mslookup_client *client,
 | 
			
		||||
				      const struct osmo_mslookup_query *query,
 | 
			
		||||
				      const struct osmo_mslookup_query_handling *handling);
 | 
			
		||||
 | 
			
		||||
void osmo_mslookup_client_request_cancel(struct osmo_mslookup_client *client, uint32_t request_handle);
 | 
			
		||||
 | 
			
		||||
struct osmo_mslookup_client *osmo_mslookup_client_new(void *ctx);
 | 
			
		||||
bool osmo_mslookup_client_active(struct osmo_mslookup_client *client);
 | 
			
		||||
void osmo_mslookup_client_free(struct osmo_mslookup_client *client);
 | 
			
		||||
 | 
			
		||||
/*! Describe a specific mslookup client method implementation. This struct is only useful for a lookup method
 | 
			
		||||
 * implementation to add itself to an osmo_mslookup_client, see for example osmo_mslookup_client_add_mdns(). */
 | 
			
		||||
struct osmo_mslookup_client_method {
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
 | 
			
		||||
	/*! Human readable name of this lookup method. */
 | 
			
		||||
	const char *name;
 | 
			
		||||
 | 
			
		||||
	/*! Private data for the lookup method implementation. */
 | 
			
		||||
	void *priv;
 | 
			
		||||
 | 
			
		||||
	/*! Backpointer to the client this method is added to. */
 | 
			
		||||
	struct osmo_mslookup_client *client;
 | 
			
		||||
 | 
			
		||||
	/*! Launch a lookup query. Called from osmo_mslookup_client_request().
 | 
			
		||||
	 * The implementation returns results by calling osmo_mslookup_client_rx_result(). */
 | 
			
		||||
	void (*request)(struct osmo_mslookup_client_method *method,
 | 
			
		||||
			const struct osmo_mslookup_query *query,
 | 
			
		||||
			uint32_t request_handle);
 | 
			
		||||
	/*! End a lookup query. Called from osmo_mslookup_client_request_cancel(). It is guaranteed to be called
 | 
			
		||||
	 * exactly once per above request() invocation. (The API user is required to invoke
 | 
			
		||||
	 * osmo_mslookup_client_request_cancel() exactly once per osmo_mslookup_client_request().) */
 | 
			
		||||
	void (*request_cleanup)(struct osmo_mslookup_client_method *method,
 | 
			
		||||
				uint32_t request_handle);
 | 
			
		||||
 | 
			
		||||
	/*! The mslookup_client is removing this method, clean up all open requests, lists and allocations. */
 | 
			
		||||
	void (*destruct)(struct osmo_mslookup_client_method *method);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void osmo_mslookup_client_method_add(struct osmo_mslookup_client *client,
 | 
			
		||||
				     struct osmo_mslookup_client_method *method);
 | 
			
		||||
bool osmo_mslookup_client_method_del(struct osmo_mslookup_client *client,
 | 
			
		||||
				     struct osmo_mslookup_client_method *method);
 | 
			
		||||
void osmo_mslookup_client_rx_result(struct osmo_mslookup_client *client, uint32_t request_handle,
 | 
			
		||||
				    const struct osmo_mslookup_result *result);
 | 
			
		||||
							
								
								
									
										34
									
								
								include/osmocom/mslookup/mslookup_client_fake.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								include/osmocom/mslookup/mslookup_client_fake.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
/* Copyright 2019 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 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/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
/*! MS lookup fake API for testing purposes. */
 | 
			
		||||
#include <osmocom/mslookup/mslookup_client.h>
 | 
			
		||||
 | 
			
		||||
struct osmo_mslookup_fake_response {
 | 
			
		||||
	struct timeval time_to_reply;
 | 
			
		||||
	struct osmo_mslookup_id for_id;
 | 
			
		||||
	const char *for_service;
 | 
			
		||||
	struct osmo_mslookup_result result;
 | 
			
		||||
	bool sent;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct osmo_mslookup_client_method *osmo_mslookup_client_add_fake(struct osmo_mslookup_client *client,
 | 
			
		||||
								  struct osmo_mslookup_fake_response *responses,
 | 
			
		||||
								  size_t responses_len);
 | 
			
		||||
							
								
								
									
										38
									
								
								include/osmocom/mslookup/mslookup_client_mdns.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								include/osmocom/mslookup/mslookup_client_mdns.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
/* Copyright 2019 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 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/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
struct osmo_mslookup_client;
 | 
			
		||||
struct osmo_mslookup_client_method;
 | 
			
		||||
 | 
			
		||||
/*! MS Lookup mDNS server bind default IP. Taken from the Administratevly Scoped block, particularly the Organizational
 | 
			
		||||
 * Scoped range, https://tools.ietf.org/html/rfc2365 . */
 | 
			
		||||
#define OSMO_MSLOOKUP_MDNS_IP4 "239.192.23.42"
 | 
			
		||||
#define OSMO_MSLOOKUP_MDNS_IP6 "ff08::23:42" // <-- TODO: sane?
 | 
			
		||||
#define OSMO_MSLOOKUP_MDNS_PORT 4266
 | 
			
		||||
 | 
			
		||||
struct osmo_mslookup_client_method *osmo_mslookup_client_add_mdns(struct osmo_mslookup_client *client, const char *ip,
 | 
			
		||||
								  uint16_t port, int initial_packet_id,
 | 
			
		||||
								  const char *domain_suffix);
 | 
			
		||||
 | 
			
		||||
const struct osmo_sockaddr_str *osmo_mslookup_client_method_mdns_get_bind_addr(struct osmo_mslookup_client_method *dns_method);
 | 
			
		||||
 | 
			
		||||
const char *osmo_mslookup_client_method_mdns_get_domain_suffix(struct osmo_mslookup_client_method *dns_method);
 | 
			
		||||
							
								
								
									
										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}/
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								libosmo-mslookup.pc.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								libosmo-mslookup.pc.in
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
prefix=@prefix@
 | 
			
		||||
exec_prefix=@exec_prefix@
 | 
			
		||||
libdir=@libdir@
 | 
			
		||||
includedir=@includedir@
 | 
			
		||||
 | 
			
		||||
Name: Osmocom MS Lookup Library
 | 
			
		||||
Description: C Utility Library
 | 
			
		||||
Version: @VERSION@
 | 
			
		||||
Libs: -L${libdir} @TALLOC_LIBS@ -losmogsm -losmo-mslookup -losmocore
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								sql/hlr.sql
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								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,44 @@ 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,
 | 
			
		||||
	last_lu_seen_ps TIMESTAMP default NULL
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
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,3 +1,8 @@
 | 
			
		||||
SUBDIRS = \
 | 
			
		||||
	gsupclient \
 | 
			
		||||
	mslookup \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
AM_CFLAGS = \
 | 
			
		||||
	-Wall \
 | 
			
		||||
	$(LIBOSMOCORE_CFLAGS) \
 | 
			
		||||
@@ -8,9 +13,13 @@ AM_CFLAGS = \
 | 
			
		||||
	$(SQLITE3_CFLAGS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
AM_CPPFLAGS = -I$(top_srcdir)/include \
 | 
			
		||||
	-I$(top_builddir)/include \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
EXTRA_DIST = \
 | 
			
		||||
	populate_hlr_db.pl \
 | 
			
		||||
	db_bootstrap.sed \
 | 
			
		||||
	db_sql2c.sed \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
BUILT_SOURCES = \
 | 
			
		||||
@@ -19,23 +28,13 @@ BUILT_SOURCES = \
 | 
			
		||||
CLEANFILES = $(BUILT_SOURCES)
 | 
			
		||||
 | 
			
		||||
noinst_HEADERS = \
 | 
			
		||||
	auc.h \
 | 
			
		||||
	db.h \
 | 
			
		||||
	hlr.h \
 | 
			
		||||
	luop.h \
 | 
			
		||||
	gsup_router.h \
 | 
			
		||||
	gsup_server.h \
 | 
			
		||||
	logging.h \
 | 
			
		||||
	rand.h \
 | 
			
		||||
	ctrl.h \
 | 
			
		||||
	hlr_vty.h \
 | 
			
		||||
	hlr_vty_subscr.h \
 | 
			
		||||
	db_bootstrap.h \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
bin_PROGRAMS = \
 | 
			
		||||
	osmo-hlr \
 | 
			
		||||
	osmo-hlr-db-tool \
 | 
			
		||||
	osmo-euse-demo \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
osmo_hlr_SOURCES = \
 | 
			
		||||
@@ -52,6 +51,8 @@ osmo_hlr_SOURCES = \
 | 
			
		||||
	rand_urandom.c \
 | 
			
		||||
	hlr_vty.c \
 | 
			
		||||
	hlr_vty_subscr.c \
 | 
			
		||||
	gsup_send.c \
 | 
			
		||||
	hlr_ussd.c \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
osmo_hlr_LDADD = \
 | 
			
		||||
@@ -78,28 +79,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 "};" >> "$@"
 | 
			
		||||
 
 | 
			
		||||
@@ -23,8 +23,8 @@
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include <osmocom/crypt/auth.h>
 | 
			
		||||
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "rand.h"
 | 
			
		||||
#include <osmocom/hlr/logging.h>
 | 
			
		||||
#include <osmocom/hlr/rand.h>
 | 
			
		||||
 | 
			
		||||
#define hexb(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf))
 | 
			
		||||
#define hex(buf,sz) osmo_hexdump_nospc((void*)buf, sz)
 | 
			
		||||
 
 | 
			
		||||
@@ -28,9 +28,9 @@
 | 
			
		||||
#include <osmocom/gsm/gsm23003.h>
 | 
			
		||||
#include <osmocom/ctrl/ports.h>
 | 
			
		||||
 | 
			
		||||
#include "hlr.h"
 | 
			
		||||
#include "ctrl.h"
 | 
			
		||||
#include "db.h"
 | 
			
		||||
#include <osmocom/hlr/hlr.h>
 | 
			
		||||
#include <osmocom/hlr/ctrl.h>
 | 
			
		||||
#include <osmocom/hlr/db.h>
 | 
			
		||||
 | 
			
		||||
#define SEL_BY "by-"
 | 
			
		||||
#define SEL_BY_IMSI SEL_BY "imsi-"
 | 
			
		||||
@@ -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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										374
									
								
								src/db.c
									
									
									
									
									
								
							
							
						
						
									
										374
									
								
								src/db.c
									
									
									
									
									
								
							@@ -23,14 +23,18 @@
 | 
			
		||||
#include <sqlite3.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "db.h"
 | 
			
		||||
#include <osmocom/hlr/logging.h>
 | 
			
		||||
#include <osmocom/hlr/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 +44,18 @@
 | 
			
		||||
	"nam_ps," \
 | 
			
		||||
	"lmsi," \
 | 
			
		||||
	"ms_purged_cs," \
 | 
			
		||||
	"ms_purged_ps"
 | 
			
		||||
	"ms_purged_ps," \
 | 
			
		||||
	"last_lu_seen," \
 | 
			
		||||
	"last_lu_seen_ps" \
 | 
			
		||||
 | 
			
		||||
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_SEL_BY_IMEI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imei = ?",
 | 
			
		||||
	[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_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 +67,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 +79,10 @@ 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_SET_LAST_LU_SEEN_PS] = "UPDATE subscriber SET last_lu_seen_ps = 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",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void sql3_error_log_cb(void *arg, int err_code, const char *msg)
 | 
			
		||||
@@ -173,55 +186,316 @@ bool db_bind_int64(sqlite3_stmt *stmt, const char *param_name, int64_t nr)
 | 
			
		||||
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;
 | 
			
		||||
	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 SQLITE_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		/* execute the statement */
 | 
			
		||||
/* 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);
 | 
			
		||||
		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;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
	sqlite3_finalize(stmt);
 | 
			
		||||
	return (rc == SQLITE_ROW);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite_logging)
 | 
			
		||||
/* 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 last_lu_seen_ps TIMESTAMP default NULL",
 | 
			
		||||
		"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++) {
 | 
			
		||||
@@ -265,7 +539,51 @@ struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite_logg
 | 
			
		||||
		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++) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										89
									
								
								src/db_auc.c
									
									
									
									
									
								
							
							
						
						
									
										89
									
								
								src/db_auc.c
									
									
									
									
									
								
							@@ -26,10 +26,10 @@
 | 
			
		||||
 | 
			
		||||
#include <sqlite3.h>
 | 
			
		||||
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "db.h"
 | 
			
		||||
#include "auc.h"
 | 
			
		||||
#include "rand.h"
 | 
			
		||||
#include <osmocom/hlr/logging.h>
 | 
			
		||||
#include <osmocom/hlr/db.h>
 | 
			
		||||
#include <osmocom/hlr/auc.h>
 | 
			
		||||
#include <osmocom/hlr/rand.h>
 | 
			
		||||
 | 
			
		||||
#define LOGAUC(imsi, level, fmt, args ...)	LOGP(DAUC, level, "IMSI='%s': " fmt, imsi, ## args)
 | 
			
		||||
 | 
			
		||||
@@ -73,6 +73,32 @@ 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 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,
 | 
			
		||||
@@ -113,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;
 | 
			
		||||
 | 
			
		||||
		if (hexparse_stmt(aud3g->u.umts.k, sizeof(aud3g->u.umts.k), stmt, 4, "K", imsi)) {
 | 
			
		||||
			ret = -EIO;
 | 
			
		||||
			goto out;
 | 
			
		||||
		}
 | 
			
		||||
		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);
 | 
			
		||||
			ret = -EIO;
 | 
			
		||||
			goto out;
 | 
			
		||||
		}
 | 
			
		||||
		osmo_hexparse((void*)k, (void*)&aud3g->u.umts.k, sizeof(aud3g->u.umts.k));
 | 
			
		||||
 | 
			
		||||
		/* 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);
 | 
			
		||||
@@ -178,7 +189,7 @@ out:
 | 
			
		||||
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,
 | 
			
		||||
	       const uint8_t *auts)
 | 
			
		||||
	       const uint8_t *auts, bool separation_bit)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_sub_auth_data aud2g, aud3g;
 | 
			
		||||
	int64_t subscr_id;
 | 
			
		||||
@@ -198,6 +209,12 @@ int db_get_auc(struct db_context *dbc, const char *imsi,
 | 
			
		||||
		       aud3g.u.umts.ind_bitlen, aud3g.u.umts.ind);
 | 
			
		||||
		aud3g.u.umts.ind &= (1U << aud3g.u.umts.ind_bitlen) - 1;
 | 
			
		||||
	}
 | 
			
		||||
	/* the first bit (bit0) cannot be used as AMF anymore, but has been
 | 
			
		||||
	 * re-appropriated as the separation bit.  See 3GPP TS 33.102 Annex H
 | 
			
		||||
	 * together with 3GPP TS 33.401 / 33.402 / 33.501 */
 | 
			
		||||
	aud3g.u.umts.amf[0] = aud3g.u.umts.amf[0] & 0x7f;
 | 
			
		||||
	if (separation_bit)
 | 
			
		||||
		aud3g.u.umts.amf[0] |= 0x80;
 | 
			
		||||
 | 
			
		||||
	LOGAUC(imsi, LOGL_DEBUG, "Calling to generate %u vectors\n", num_vec);
 | 
			
		||||
	rc = auc_compute_vectors(vec, num_vec, &aud2g, &aud3g, rand_auts, 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);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										246
									
								
								src/db_hlr.c
									
									
									
									
									
								
							
							
						
						
									
										246
									
								
								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>
 | 
			
		||||
@@ -27,20 +33,21 @@
 | 
			
		||||
 | 
			
		||||
#include <sqlite3.h>
 | 
			
		||||
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "hlr.h"
 | 
			
		||||
#include "db.h"
 | 
			
		||||
#include "gsup_server.h"
 | 
			
		||||
#include "luop.h"
 | 
			
		||||
#include <osmocom/hlr/logging.h>
 | 
			
		||||
#include <osmocom/hlr/hlr.h>
 | 
			
		||||
#include <osmocom/hlr/db.h>
 | 
			
		||||
#include <osmocom/hlr/gsup_server.h>
 | 
			
		||||
#include <osmocom/hlr/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 +62,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);
 | 
			
		||||
@@ -135,8 +146,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 +157,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 (msisdn) {
 | 
			
		||||
		if (!db_bind_text(stmt, "$msisdn", msisdn))
 | 
			
		||||
			return -EIO;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* execute the statement */
 | 
			
		||||
	rc = sqlite3_step(stmt);
 | 
			
		||||
@@ -377,6 +391,77 @@ 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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void parse_last_lu_seen(time_t *dst, const char *last_lu_seen_str, const char *imsi, const char *label)
 | 
			
		||||
{
 | 
			
		||||
	struct tm tm = {0};
 | 
			
		||||
	time_t val;
 | 
			
		||||
	if (!last_lu_seen_str || last_lu_seen_str[0] == '\0')
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	if (strptime(last_lu_seen_str, DB_LAST_LU_SEEN_FMT, &tm) == NULL) {
 | 
			
		||||
		LOGP(DAUC, LOGL_ERROR, "IMSI-%s: Last LU Seen %s: Cannot parse timestamp '%s'\n",
 | 
			
		||||
		     imsi, label, last_lu_seen_str);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errno = 0;
 | 
			
		||||
	val = mktime(&tm);
 | 
			
		||||
	if (val == -1) {
 | 
			
		||||
		LOGP(DAUC, LOGL_ERROR, "IMSI-%s: Last LU Seen %s: Cannot convert timestamp '%s' to time_t: %s\n",
 | 
			
		||||
		     imsi, label, last_lu_seen_str, strerror(errno));
 | 
			
		||||
		val = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	*dst = val;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 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)
 | 
			
		||||
@@ -404,17 +489,22 @@ 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);
 | 
			
		||||
	parse_last_lu_seen(&subscr->last_lu_seen, (const char *)sqlite3_column_text(stmt, 14),
 | 
			
		||||
			   subscr->imsi, "CS");
 | 
			
		||||
	parse_last_lu_seen(&subscr->last_lu_seen_ps, (const char *)sqlite3_column_text(stmt, 15),
 | 
			
		||||
			   subscr->imsi, "PS");
 | 
			
		||||
 | 
			
		||||
out:
 | 
			
		||||
	db_remove_reset(stmt);
 | 
			
		||||
@@ -433,6 +523,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 +572,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.
 | 
			
		||||
@@ -505,6 +647,28 @@ int db_subscr_get_by_id(struct db_context *dbc, int64_t id,
 | 
			
		||||
	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.
 | 
			
		||||
@@ -574,6 +738,7 @@ int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
 | 
			
		||||
{
 | 
			
		||||
	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];
 | 
			
		||||
@@ -600,13 +765,54 @@ int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
 | 
			
		||||
		     ": 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
 | 
			
		||||
		       ": 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[is_ps? DB_STMT_SET_LAST_LU_SEEN_PS : 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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
@@ -23,16 +23,17 @@
 | 
			
		||||
#include <osmocom/core/linuxlist.h>
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
 | 
			
		||||
#include "gsup_server.h"
 | 
			
		||||
#include <osmocom/hlr/logging.h>
 | 
			
		||||
#include <osmocom/hlr/gsup_server.h>
 | 
			
		||||
#include <osmocom/hlr/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,6 +47,22 @@ struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs,
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! 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)
 | 
			
		||||
{
 | 
			
		||||
@@ -60,6 +77,8 @@ int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addr
 | 
			
		||||
	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);
 | 
			
		||||
@@ -75,6 +94,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",
 | 
			
		||||
			     gr->addr);
 | 
			
		||||
			llist_del(&gr->list);
 | 
			
		||||
			talloc_free(gr);
 | 
			
		||||
			num_deleted++;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										52
									
								
								src/gsup_send.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/gsup_send.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
/* (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 <osmocom/hlr/gsup_server.h>
 | 
			
		||||
#include <osmocom/hlr/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) {
 | 
			
		||||
		DEBUGP(DLGSUP, "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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -24,10 +24,18 @@
 | 
			
		||||
#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 "gsup_server.h"
 | 
			
		||||
#include "gsup_router.h"
 | 
			
		||||
#include <osmocom/hlr/gsup_server.h>
 | 
			
		||||
#include <osmocom/hlr/gsup_router.h>
 | 
			
		||||
 | 
			
		||||
struct msgb *osmo_gsup_msgb_alloc(const char *label)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg = msgb_alloc_headroom(1024+16, 16, label);
 | 
			
		||||
	OSMO_ASSERT(msg);
 | 
			
		||||
	return msg;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void osmo_gsup_server_send(struct osmo_gsup_conn *conn,
 | 
			
		||||
			     int proto_ext, struct msgb *msg_tx)
 | 
			
		||||
@@ -291,7 +299,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)
 | 
			
		||||
			struct llist_head *lu_op_lst, void *priv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_gsup_server *gsups;
 | 
			
		||||
	int rc;
 | 
			
		||||
@@ -311,6 +319,7 @@ 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)
 | 
			
		||||
@@ -335,19 +344,78 @@ void osmo_gsup_server_destroy(struct osmo_gsup_server *gsups)
 | 
			
		||||
	talloc_free(gsups);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void osmo_gsup_configure_wildcard_apn(struct osmo_gsup_message *gsup)
 | 
			
		||||
/* 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;
 | 
			
		||||
	uint8_t apn[APN_MAXLEN];
 | 
			
		||||
 | 
			
		||||
	l = osmo_apn_from_str(apn, sizeof(apn), "*");
 | 
			
		||||
	l = osmo_apn_from_str(apn_buf, apn_buf_size, "*");
 | 
			
		||||
	if (l <= 0)
 | 
			
		||||
		return;
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	gsup->pdp_infos[0].apn_enc = apn;
 | 
			
		||||
	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->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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
							
								
								
									
										403
									
								
								src/gsupclient/gsup_client.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										403
									
								
								src/gsupclient/gsup_client.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,403 @@
 | 
			
		||||
/* 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;
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
	} else {
 | 
			
		||||
		osmo_timer_del(&gsupc->ping_timer);
 | 
			
		||||
 | 
			
		||||
		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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * 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)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_gsup_client *gsupc;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	gsupc = talloc_zero(talloc_ctx, struct osmo_gsup_client);
 | 
			
		||||
	OSMO_ASSERT(gsupc);
 | 
			
		||||
	gsupc->unit_name = (const char *)ipa_dev->unit_name; /* API backwards compat */
 | 
			
		||||
	gsupc->ipa_dev = ipa_dev;
 | 
			
		||||
 | 
			
		||||
	/* 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;
 | 
			
		||||
 | 
			
		||||
	gsupc->read_cb = read_cb;
 | 
			
		||||
 | 
			
		||||
	return gsupc;
 | 
			
		||||
 | 
			
		||||
failed:
 | 
			
		||||
	osmo_gsup_client_destroy(gsupc);
 | 
			
		||||
	return 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);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										520
									
								
								src/hlr.c
									
									
									
									
									
								
							
							
						
						
									
										520
									
								
								src/hlr.c
									
									
									
									
									
								
							@@ -23,32 +23,36 @@
 | 
			
		||||
#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/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 "db.h"
 | 
			
		||||
#include "hlr.h"
 | 
			
		||||
#include "ctrl.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "gsup_server.h"
 | 
			
		||||
#include "gsup_router.h"
 | 
			
		||||
#include "rand.h"
 | 
			
		||||
#include "luop.h"
 | 
			
		||||
#include "hlr_vty.h"
 | 
			
		||||
#include <osmocom/hlr/db.h>
 | 
			
		||||
#include <osmocom/hlr/hlr.h>
 | 
			
		||||
#include <osmocom/hlr/ctrl.h>
 | 
			
		||||
#include <osmocom/hlr/logging.h>
 | 
			
		||||
#include <osmocom/hlr/gsup_server.h>
 | 
			
		||||
#include <osmocom/hlr/gsup_router.h>
 | 
			
		||||
#include <osmocom/hlr/rand.h>
 | 
			
		||||
#include <osmocom/hlr/luop.h>
 | 
			
		||||
#include <osmocom/hlr/hlr_vty.h>
 | 
			
		||||
#include <osmocom/hlr/hlr_ussd.h>
 | 
			
		||||
 | 
			
		||||
static struct hlr *g_hlr;
 | 
			
		||||
struct hlr *g_hlr;
 | 
			
		||||
static void *hlr_ctx = NULL;
 | 
			
		||||
static int quit = 0;
 | 
			
		||||
 | 
			
		||||
/* Trigger 'Insert Subscriber Data' messages to all connected GSUP clients.
 | 
			
		||||
 *
 | 
			
		||||
 * FIXME: In order to support large-scale networks this function should skip
 | 
			
		||||
 * VLRs/SGSNs which do not currently serve the subscriber.
 | 
			
		||||
 *
 | 
			
		||||
 * \param[in] subscr  A subscriber we have new data to send for.
 | 
			
		||||
 */
 | 
			
		||||
@@ -57,65 +61,84 @@ osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr)
 | 
			
		||||
{
 | 
			
		||||
        struct osmo_gsup_conn *co;
 | 
			
		||||
 | 
			
		||||
	if (g_hlr->gs == NULL)
 | 
			
		||||
	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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry(co, &g_hlr->gs->clients, list) {
 | 
			
		||||
		struct osmo_gsup_message gsup = {
 | 
			
		||||
			.message_type = OSMO_GSUP_MSGT_INSERT_DATA_REQUEST
 | 
			
		||||
		};
 | 
			
		||||
		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;
 | 
			
		||||
		uint8_t msisdn_enc[43]; /* TODO use constant; TS 24.008 10.5.4.7 */
 | 
			
		||||
		int len;
 | 
			
		||||
		struct msgb *msg_out;
 | 
			
		||||
		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(DMAIN, LOGL_ERROR,
 | 
			
		||||
			       "IMSI='%s': Cannot notify GSUP client, cannot get peer name "
 | 
			
		||||
			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;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		osmo_strlcpy(gsup.imsi, subscr->imsi, GSM23003_IMSI_MAX_DIGITS + 1);
 | 
			
		||||
 | 
			
		||||
		len = gsm48_encode_bcd_number(msisdn_enc, sizeof(msisdn_enc), 0, subscr->msisdn);
 | 
			
		||||
		if (len < 1) {
 | 
			
		||||
			LOGP(DMAIN, LOGL_ERROR, "%s: Error: cannot encode MSISDN '%s'\n",
 | 
			
		||||
			     subscr->imsi, subscr->msisdn);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		gsup.msisdn_enc = msisdn_enc;
 | 
			
		||||
		gsup.msisdn_enc_len = len;
 | 
			
		||||
 | 
			
		||||
		if (co->supports_ps) {
 | 
			
		||||
			gsup.cn_domain = OSMO_GSUP_CN_DOMAIN_PS;
 | 
			
		||||
 | 
			
		||||
			/* FIXME: PDP infos - use more fine-grained access control
 | 
			
		||||
			   instead of wildcard APN */
 | 
			
		||||
			osmo_gsup_configure_wildcard_apn(&gsup);
 | 
			
		||||
		} else if (co->supports_cs) {
 | 
			
		||||
			gsup.cn_domain = OSMO_GSUP_CN_DOMAIN_CS;
 | 
			
		||||
		} else {
 | 
			
		||||
			/* We have not yet received a location update from this subscriber .*/
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Send ISD to MSC/SGSN */
 | 
			
		||||
		msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP ISD UPDATE");
 | 
			
		||||
		msg_out = osmo_gsup_msgb_alloc("GSUP ISD UPDATE");
 | 
			
		||||
		if (msg_out == NULL) {
 | 
			
		||||
			LOGP(DMAIN, LOGL_ERROR,
 | 
			
		||||
			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 "
 | 
			
		||||
@@ -127,6 +150,78 @@ osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***********************************************************************
 | 
			
		||||
 * Send Auth Info handling
 | 
			
		||||
 ***********************************************************************/
 | 
			
		||||
@@ -138,16 +233,27 @@ static int rx_send_auth_info(struct osmo_gsup_conn *conn,
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_gsup_message gsup_out;
 | 
			
		||||
	struct msgb *msg_out;
 | 
			
		||||
	bool separation_bit = false;
 | 
			
		||||
	int num_auth_vectors = OSMO_GSUP_MAX_NUM_AUTH_INFO;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	subscr_create_on_demand(gsup->imsi);
 | 
			
		||||
 | 
			
		||||
	/* initialize return message structure */
 | 
			
		||||
	memset(&gsup_out, 0, sizeof(gsup_out));
 | 
			
		||||
	memcpy(&gsup_out.imsi, &gsup->imsi, sizeof(gsup_out.imsi));
 | 
			
		||||
 | 
			
		||||
	if (gsup->current_rat_type == OSMO_RAT_EUTRAN_SGS)
 | 
			
		||||
		separation_bit = true;
 | 
			
		||||
 | 
			
		||||
	if (gsup->num_auth_vectors > 0 &&
 | 
			
		||||
			gsup->num_auth_vectors <= OSMO_GSUP_MAX_NUM_AUTH_INFO)
 | 
			
		||||
		num_auth_vectors = gsup->num_auth_vectors;
 | 
			
		||||
 | 
			
		||||
	rc = db_get_auc(dbc, gsup->imsi, conn->auc_3g_ind,
 | 
			
		||||
			gsup_out.auth_vectors,
 | 
			
		||||
			ARRAY_SIZE(gsup_out.auth_vectors),
 | 
			
		||||
			gsup->rand, gsup->auts);
 | 
			
		||||
			num_auth_vectors,
 | 
			
		||||
			gsup->rand, gsup->auts, separation_bit);
 | 
			
		||||
	if (rc <= 0) {
 | 
			
		||||
		gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR;
 | 
			
		||||
		switch (rc) {
 | 
			
		||||
@@ -174,7 +280,7 @@ static int rx_send_auth_info(struct osmo_gsup_conn *conn,
 | 
			
		||||
		gsup_out.num_auth_vectors = rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP AUC response");
 | 
			
		||||
	msg_out = osmo_gsup_msgb_alloc("GSUP AUC response");
 | 
			
		||||
	osmo_gsup_encode(msg_out, &gsup_out);
 | 
			
		||||
	return osmo_gsup_conn_send(conn, msg_out);
 | 
			
		||||
}
 | 
			
		||||
@@ -243,29 +349,35 @@ void lu_op_rx_gsup(struct lu_operation *luop,
 | 
			
		||||
static int rx_upd_loc_req(struct osmo_gsup_conn *conn,
 | 
			
		||||
			  const struct osmo_gsup_message *gsup)
 | 
			
		||||
{
 | 
			
		||||
	struct hlr_subscriber *subscr;
 | 
			
		||||
	struct lu_operation *luop = lu_op_alloc_conn(conn);
 | 
			
		||||
	if (!luop) {
 | 
			
		||||
		LOGP(DMAIN, LOGL_ERROR, "LU REQ from conn without addr?\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	subscr = &luop->subscr;
 | 
			
		||||
 | 
			
		||||
	lu_op_statechg(luop, LU_S_LU_RECEIVED);
 | 
			
		||||
 | 
			
		||||
	if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_CS)
 | 
			
		||||
	switch (gsup->cn_domain) {
 | 
			
		||||
	case OSMO_GSUP_CN_DOMAIN_CS:
 | 
			
		||||
		conn->supports_cs = true;
 | 
			
		||||
	if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS) {
 | 
			
		||||
		conn->supports_ps = true;
 | 
			
		||||
		luop->is_ps = true;
 | 
			
		||||
	} else {
 | 
			
		||||
		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;
 | 
			
		||||
		luop->is_ps = true;
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
	llist_add(&luop->list, &g_lu_ops);
 | 
			
		||||
 | 
			
		||||
	subscr_create_on_demand(gsup->imsi);
 | 
			
		||||
 | 
			
		||||
	/* Roughly follwing "Process Update_Location_HLR" of TS 09.02 */
 | 
			
		||||
 | 
			
		||||
	/* check if subscriber is known at all */
 | 
			
		||||
@@ -298,13 +410,21 @@ static int rx_upd_loc_req(struct osmo_gsup_conn *conn,
 | 
			
		||||
		lu_op_tx_cancel_old(luop);
 | 
			
		||||
	} else
 | 
			
		||||
#endif
 | 
			
		||||
	{
 | 
			
		||||
 | 
			
		||||
	/* Store the VLR / SGSN number with the subscriber, so we know where it was last seen. */
 | 
			
		||||
	LOGP(DAUC, LOGL_DEBUG, "IMSI='%s': storing %s = %s\n",
 | 
			
		||||
	     subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number",
 | 
			
		||||
	     osmo_quote_str((const char*)luop->peer, -1));
 | 
			
		||||
	if (db_subscr_lu(g_hlr->dbc, subscr->id, (const char *)luop->peer, luop->is_ps))
 | 
			
		||||
		LOGP(DAUC, LOGL_ERROR, "IMSI='%s': Cannot update %s in the database\n",
 | 
			
		||||
		     subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number");
 | 
			
		||||
 | 
			
		||||
	/* 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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -340,22 +460,187 @@ static int rx_purge_ms_req(struct osmo_gsup_conn *conn,
 | 
			
		||||
		gsup_reply.cause = GMM_CAUSE_NET_FAIL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP AUC response");
 | 
			
		||||
	msg_out = osmo_gsup_msgb_alloc("GSUP AUC response");
 | 
			
		||||
	osmo_gsup_encode(msg_out, &gsup_reply);
 | 
			
		||||
	return osmo_gsup_conn_send(conn, msg_out);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int gsup_send_err_reply(struct osmo_gsup_conn *conn, const char *imsi,
 | 
			
		||||
				enum osmo_gsup_message_type type_in, uint8_t err_cause)
 | 
			
		||||
{
 | 
			
		||||
	int type_err = OSMO_GSUP_TO_MSGT_ERROR(type_in);
 | 
			
		||||
	struct osmo_gsup_message gsup_reply = {0};
 | 
			
		||||
	struct msgb *msg_out;
 | 
			
		||||
 | 
			
		||||
	OSMO_STRLCPY_ARRAY(gsup_reply.imsi, imsi);
 | 
			
		||||
	gsup_reply.message_type = type_err;
 | 
			
		||||
	gsup_reply.cause = err_cause;
 | 
			
		||||
	msg_out = osmo_gsup_msgb_alloc("GSUP ERR response");
 | 
			
		||||
	osmo_gsup_encode(msg_out, &gsup_reply);
 | 
			
		||||
	LOGP(DMAIN, LOGL_NOTICE, "Tx %s\n", osmo_gsup_message_type_name(type_err));
 | 
			
		||||
	return osmo_gsup_conn_send(conn, msg_out);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int rx_check_imei_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_gsup_message gsup_reply = {0};
 | 
			
		||||
	struct msgb *msg_out;
 | 
			
		||||
	char imei[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1] = {0};
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* Require IMEI */
 | 
			
		||||
	if (!gsup->imei_enc) {
 | 
			
		||||
		LOGP(DMAIN, LOGL_ERROR, "%s: missing IMEI\n", gsup->imsi);
 | 
			
		||||
		gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* 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) {
 | 
			
		||||
		LOGP(DMAIN, LOGL_ERROR, "%s: failed to decode IMEI (rc: %i)\n", gsup->imsi, rc);
 | 
			
		||||
		gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Check if IMEI is too short */
 | 
			
		||||
	if (strlen(imei) != GSM23003_IMEI_NUM_DIGITS_NO_CHK) {
 | 
			
		||||
		LOGP(DMAIN, LOGL_ERROR, "%s: wrong encoded IMEI length (IMEI: '%s', %lu, %i)\n", gsup->imsi, imei,
 | 
			
		||||
		     strlen(imei), GSM23003_IMEI_NUM_DIGITS_NO_CHK);
 | 
			
		||||
		gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
 | 
			
		||||
		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) {
 | 
			
		||||
			gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
 | 
			
		||||
			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) {
 | 
			
		||||
			gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
 | 
			
		||||
			return -1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Accept all IMEIs */
 | 
			
		||||
	gsup_reply.imei_result = OSMO_GSUP_IMEI_RESULT_ACK;
 | 
			
		||||
	gsup_reply.message_type = OSMO_GSUP_MSGT_CHECK_IMEI_RESULT;
 | 
			
		||||
	msg_out = osmo_gsup_msgb_alloc("GSUP Check_IMEI response");
 | 
			
		||||
	memcpy(gsup_reply.imsi, gsup->imsi, sizeof(gsup_reply.imsi));
 | 
			
		||||
	osmo_gsup_encode(msg_out, &gsup_reply);
 | 
			
		||||
	return osmo_gsup_conn_send(conn, msg_out);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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_conn *conn, struct msgb *msg, const struct osmo_gsup_message *gsup)
 | 
			
		||||
{
 | 
			
		||||
	int ret = -EINVAL;
 | 
			
		||||
	struct osmo_gsup_message *gsup_err;
 | 
			
		||||
 | 
			
		||||
	/* FIXME: it would be better if the msgb never were deallocated immediately by osmo_gsup_addr_send(), which a
 | 
			
		||||
	 * select-loop volatile talloc context could facilitate. Then we would still be able to access gsup-> members
 | 
			
		||||
	 * (pointing into the msgb) even after sending failed, and we wouldn't need to copy this data before sending: */
 | 
			
		||||
	/* Prepare error message (before IEs get deallocated) */
 | 
			
		||||
	gsup_err = talloc_zero(hlr_ctx, struct osmo_gsup_message);
 | 
			
		||||
	OSMO_STRLCPY_ARRAY(gsup_err->imsi, gsup->imsi);
 | 
			
		||||
	gsup_err->message_class = gsup->message_class;
 | 
			
		||||
	gsup_err->destination_name = talloc_memdup(gsup_err, gsup->destination_name, gsup->destination_name_len);
 | 
			
		||||
	gsup_err->destination_name_len = gsup->destination_name_len;
 | 
			
		||||
	gsup_err->message_type = gsup->message_type;
 | 
			
		||||
	gsup_err->session_state = gsup->session_state;
 | 
			
		||||
	gsup_err->session_id = gsup->session_id;
 | 
			
		||||
	gsup_err->source_name = talloc_memdup(gsup_err, gsup->source_name, gsup->source_name_len);
 | 
			
		||||
	gsup_err->source_name_len = gsup->source_name_len;
 | 
			
		||||
 | 
			
		||||
	/* Check for routing IEs */
 | 
			
		||||
	if (!gsup->source_name || !gsup->source_name_len || !gsup->destination_name || !gsup->destination_name_len) {
 | 
			
		||||
		LOGP_GSUP_FWD(gsup, LOGL_ERROR, "missing routing IEs\n");
 | 
			
		||||
		goto end;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Verify source name (e.g. "MSC-00-00-00-00-00-00") */
 | 
			
		||||
	if (gsup_route_find(conn->server, gsup->source_name, gsup->source_name_len) != conn) {
 | 
			
		||||
		LOGP_GSUP_FWD(gsup, LOGL_ERROR, "mismatching source name\n");
 | 
			
		||||
		goto end;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Forward message without re-encoding (so we don't remove unknown IEs) */
 | 
			
		||||
	LOGP_GSUP_FWD(gsup, LOGL_INFO, "checks passed, forwarding\n");
 | 
			
		||||
 | 
			
		||||
	/* Remove incoming IPA header to be able to prepend an outgoing IPA header */
 | 
			
		||||
	msgb_pull_to_l2(msg);
 | 
			
		||||
	ret = osmo_gsup_addr_send(g_hlr->gs, gsup->destination_name, gsup->destination_name_len, msg);
 | 
			
		||||
	/* AT THIS POINT, THE msg MAY BE DEALLOCATED and the data like gsup->imsi, gsup->source_name etc may all be
 | 
			
		||||
	 * invalid and cause segfaults. */
 | 
			
		||||
	msg = NULL;
 | 
			
		||||
	gsup = NULL;
 | 
			
		||||
	if (ret == -ENODEV)
 | 
			
		||||
		LOGP_GSUP_FWD(gsup_err, LOGL_ERROR, "destination not connected\n");
 | 
			
		||||
	else if (ret)
 | 
			
		||||
		LOGP_GSUP_FWD(gsup_err, LOGL_ERROR, "unknown error %i\n", ret);
 | 
			
		||||
 | 
			
		||||
end:
 | 
			
		||||
	/* Send error back to source */
 | 
			
		||||
	if (ret) {
 | 
			
		||||
		struct msgb *msg_err = osmo_gsup_msgb_alloc("GSUP forward ERR response");
 | 
			
		||||
		gsup_err->message_type = OSMO_GSUP_MSGT_E_ROUTING_ERROR;
 | 
			
		||||
		osmo_gsup_encode(msg_err, gsup_err);
 | 
			
		||||
		LOGP_GSUP_FWD(gsup_err, LOGL_NOTICE, "Tx %s\n", osmo_gsup_message_type_name(gsup_err->message_type));
 | 
			
		||||
		osmo_gsup_conn_send(conn, msg_err);
 | 
			
		||||
	}
 | 
			
		||||
	talloc_free(gsup_err);
 | 
			
		||||
	if (msg)
 | 
			
		||||
		msgb_free(msg);
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	static struct osmo_gsup_message gsup;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	if (!msgb_l2(msg) || !msgb_l2len(msg)) {
 | 
			
		||||
		LOGP(DMAIN, LOGL_ERROR, "missing or empty L2 data\n");
 | 
			
		||||
		msgb_free(msg);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
		msgb_free(msg);
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* 3GPP TS 23.003 Section 2.2 clearly states that an IMSI with less than 5
 | 
			
		||||
	 * digits is impossible.  Even 5 digits is a highly theoretical case */
 | 
			
		||||
	if (strlen(gsup.imsi) < 5) { /* TODO: move this check to libosmogsm/gsup.c? */
 | 
			
		||||
		LOGP(DMAIN, LOGL_ERROR, "IMSI too short: %s\n", osmo_quote_str(gsup.imsi, -1));
 | 
			
		||||
		gsup_send_err_reply(conn, gsup.imsi, gsup.message_type, GMM_CAUSE_INV_MAND_INFO);
 | 
			
		||||
		msgb_free(msg);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (gsup.destination_name_len)
 | 
			
		||||
		return read_cb_forward(conn, msg, &gsup);
 | 
			
		||||
 | 
			
		||||
	switch (gsup.message_type) {
 | 
			
		||||
	/* requests sent to us */
 | 
			
		||||
	case OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST:
 | 
			
		||||
@@ -376,6 +661,13 @@ static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
 | 
			
		||||
		LOGP(DMAIN, LOGL_ERROR, "Deleting subscriber data for IMSI %s\n",
 | 
			
		||||
		     gsup.imsi);
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_GSUP_MSGT_PROC_SS_REQUEST:
 | 
			
		||||
	case OSMO_GSUP_MSGT_PROC_SS_RESULT:
 | 
			
		||||
		rx_proc_ss_req(conn, &gsup);
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_GSUP_MSGT_PROC_SS_ERROR:
 | 
			
		||||
		rx_proc_ss_error(conn, &gsup);
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
 | 
			
		||||
	case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
 | 
			
		||||
	case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR:
 | 
			
		||||
@@ -393,6 +685,9 @@ static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
 | 
			
		||||
			lu_op_rx_gsup(luop, &gsup);
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_GSUP_MSGT_CHECK_IMEI_REQUEST:
 | 
			
		||||
		rx_check_imei_req(conn, &gsup);
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		LOGP(DMAIN, LOGL_DEBUG, "Unhandled GSUP message type %s\n",
 | 
			
		||||
		     osmo_gsup_message_type_name(gsup.message_type));
 | 
			
		||||
@@ -417,6 +712,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");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -424,10 +721,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)
 | 
			
		||||
@@ -443,11 +743,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;
 | 
			
		||||
@@ -478,6 +780,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);
 | 
			
		||||
@@ -489,20 +797,20 @@ static void handle_options(int argc, char **argv)
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void *hlr_ctx = NULL;
 | 
			
		||||
	if (argc > optind) {
 | 
			
		||||
		fprintf(stderr, "Unsupported positional arguments on command line\n");
 | 
			
		||||
		exit(2);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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");
 | 
			
		||||
@@ -529,11 +837,21 @@ 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->ss_sessions);
 | 
			
		||||
	INIT_LLIST_HEAD(&g_hlr->ussd_routes);
 | 
			
		||||
	g_hlr->db_file_path = talloc_strdup(g_hlr, HLR_DEFAULT_DB_FILE_PATH);
 | 
			
		||||
 | 
			
		||||
	/* Init default (call independent) SS session guard timeout value */
 | 
			
		||||
	g_hlr->ncss_guard_timeout = NCSS_GUARD_TIMEOUT_DEFAULT;
 | 
			
		||||
 | 
			
		||||
	rc = osmo_init_logging2(hlr_ctx, &hlr_log_info);
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
@@ -541,10 +859,11 @@ int main(int argc, char **argv)
 | 
			
		||||
		exit(1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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) {
 | 
			
		||||
@@ -554,12 +873,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();
 | 
			
		||||
@@ -568,14 +881,34 @@ int main(int argc, char **argv)
 | 
			
		||||
		exit(1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g_hlr->dbc = db_open(hlr_ctx, cmdline_opts.db_file, true);
 | 
			
		||||
	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_lu_ops, g_hlr);
 | 
			
		||||
	if (!g_hlr->gs) {
 | 
			
		||||
		LOGP(DMAIN, LOGL_FATAL, "Error starting GSUP server\n");
 | 
			
		||||
		exit(1);
 | 
			
		||||
@@ -586,6 +919,7 @@ int main(int argc, char **argv)
 | 
			
		||||
 | 
			
		||||
	osmo_init_ignore_signals();
 | 
			
		||||
	signal(SIGINT, &signal_hdlr);
 | 
			
		||||
	signal(SIGTERM, &signal_hdlr);
 | 
			
		||||
	signal(SIGUSR1, &signal_hdlr);
 | 
			
		||||
 | 
			
		||||
	if (cmdline_opts.daemonize) {
 | 
			
		||||
@@ -596,13 +930,29 @@ int main(int argc, char **argv)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	while (1) {
 | 
			
		||||
	while (!quit)
 | 
			
		||||
		osmo_select_main(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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,9 +29,9 @@
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/application.h>
 | 
			
		||||
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "db.h"
 | 
			
		||||
#include "rand.h"
 | 
			
		||||
#include <osmocom/hlr/logging.h>
 | 
			
		||||
#include <osmocom/hlr/db.h>
 | 
			
		||||
#include <osmocom/hlr/rand.h>
 | 
			
		||||
 | 
			
		||||
struct hlr_db_tool_ctx {
 | 
			
		||||
	/* DB context */
 | 
			
		||||
@@ -44,8 +44,10 @@ 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()
 | 
			
		||||
@@ -59,6 +61,7 @@ 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("Commands:\n");
 | 
			
		||||
@@ -96,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;
 | 
			
		||||
@@ -124,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);
 | 
			
		||||
@@ -295,7 +302,7 @@ void import_nitb_subscr(sqlite3 *nitb_db, sqlite3_stmt *stmt)
 | 
			
		||||
 | 
			
		||||
	snprintf(imsi_str, sizeof(imsi_str), "%" PRId64, imsi);
 | 
			
		||||
 | 
			
		||||
	rc = db_subscr_create(dbc, imsi_str);
 | 
			
		||||
	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,
 | 
			
		||||
@@ -409,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, true);
 | 
			
		||||
	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);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										643
									
								
								src/hlr_ussd.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										643
									
								
								src/hlr_ussd.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,643 @@
 | 
			
		||||
/* 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 <osmocom/hlr/hlr.h>
 | 
			
		||||
#include <osmocom/hlr/hlr_ussd.h>
 | 
			
		||||
#include <osmocom/hlr/gsup_server.h>
 | 
			
		||||
#include <osmocom/hlr/gsup_router.h>
 | 
			
		||||
#include <osmocom/hlr/logging.h>
 | 
			
		||||
#include <osmocom/hlr/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 */
 | 
			
		||||
	uint8_t *vlr_number;
 | 
			
		||||
	size_t vlr_number_len;
 | 
			
		||||
 | 
			
		||||
	/* 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 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);
 | 
			
		||||
	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(struct ss_session *ss, struct osmo_gsup_server *gs, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	struct hlr_subscriber subscr = {};
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* Use vlr_number as looked up by the caller, or look up now. */
 | 
			
		||||
	if (!ss->vlr_number) {
 | 
			
		||||
		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;
 | 
			
		||||
		}
 | 
			
		||||
		ss->vlr_number = (uint8_t *)talloc_strdup(ss, subscr.vlr_number);
 | 
			
		||||
		ss->vlr_number_len = strlen(subscr.vlr_number) + 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Check for empty string (all vlr_number strings end in "\0", because otherwise gsup_route_find() fails) */
 | 
			
		||||
	if (ss->vlr_number_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", osmo_quote_str((char *)ss->vlr_number, ss->vlr_number_len));
 | 
			
		||||
	return osmo_gsup_addr_send(gs, ss->vlr_number, ss->vlr_number_len, 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 = {0};
 | 
			
		||||
	struct msgb *resp_msg;
 | 
			
		||||
 | 
			
		||||
	resp.message_type = gsup_msg_type;
 | 
			
		||||
	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;
 | 
			
		||||
	resp.session_id = ss->session_id;
 | 
			
		||||
	if (ss_msg) {
 | 
			
		||||
		resp.ss_info = msgb_data(ss_msg);
 | 
			
		||||
		resp.ss_info_len = msgb_length(ss_msg);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp_msg = msgb_alloc_headroom(4000, 64, __func__);
 | 
			
		||||
	OSMO_ASSERT(resp_msg);
 | 
			
		||||
	osmo_gsup_encode(resp_msg, &resp);
 | 
			
		||||
	msgb_free(ss_msg);
 | 
			
		||||
 | 
			
		||||
	return ss_gsup_send(ss, g_hlr->gs, resp_msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#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_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_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 <osmocom/hlr/db.h>
 | 
			
		||||
 | 
			
		||||
static int handle_ussd_own_msisdn(struct osmo_gsup_conn *conn, 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_ussd_7bit(ss, true, req->invoke_id, buf);
 | 
			
		||||
		break;
 | 
			
		||||
	case -ENOENT:
 | 
			
		||||
		ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
 | 
			
		||||
		break;
 | 
			
		||||
	case -EIO:
 | 
			
		||||
	default:
 | 
			
		||||
		ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int handle_ussd_own_imsi(struct osmo_gsup_conn *conn, 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_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 conn_is_euse(struct osmo_gsup_conn *conn)
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
	uint8_t *addr;
 | 
			
		||||
 | 
			
		||||
	rc = osmo_gsup_conn_ccm_get(conn, &addr, IPAC_IDTAG_SERNR);
 | 
			
		||||
	if (rc <= 5)
 | 
			
		||||
		return false;
 | 
			
		||||
	if (!strncmp((char *)addr, "EUSE-", 5))
 | 
			
		||||
		return true;
 | 
			
		||||
	else
 | 
			
		||||
		return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct hlr_euse *euse_by_conn(struct osmo_gsup_conn *conn)
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
	char *addr;
 | 
			
		||||
	struct hlr *hlr = conn->server->priv;
 | 
			
		||||
 | 
			
		||||
	rc = osmo_gsup_conn_ccm_get(conn, (uint8_t **) &addr, IPAC_IDTAG_SERNR);
 | 
			
		||||
	if (rc <= 5)
 | 
			
		||||
		return NULL;
 | 
			
		||||
	if (strncmp(addr, "EUSE-", 5))
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	return euse_find(hlr, addr+5);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int handle_ss(struct ss_session *ss, 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_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 osmo_gsup_conn *conn, struct ss_session *ss,
 | 
			
		||||
			const struct osmo_gsup_message *gsup, const struct ss_request *req)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t comp_type = gsup->ss_info[0];
 | 
			
		||||
	struct msgb *msg_out;
 | 
			
		||||
	bool is_euse_originated = conn_is_euse(conn);
 | 
			
		||||
 | 
			
		||||
	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_error(ss, req->invoke_id, GSM0480_ERR_CODE_SS_NOT_AVAILABLE);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (is_euse_originated) {
 | 
			
		||||
		msg_out = osmo_gsup_msgb_alloc("GSUP USSD FW");
 | 
			
		||||
		/* Received from EUSE, Forward to VLR */
 | 
			
		||||
		osmo_gsup_encode(msg_out, gsup);
 | 
			
		||||
		ss_gsup_send(ss, conn->server, msg_out);
 | 
			
		||||
	} else {
 | 
			
		||||
		/* Received from VLR (MS) */
 | 
			
		||||
		if (ss->is_external) {
 | 
			
		||||
			/* Forward to EUSE */
 | 
			
		||||
			char addr[128];
 | 
			
		||||
			strcpy(addr, "EUSE-");
 | 
			
		||||
			osmo_strlcpy(addr+5, ss->u.euse->name, sizeof(addr)-5);
 | 
			
		||||
			conn = gsup_route_find(conn->server, (uint8_t *)addr, strlen(addr)+1);
 | 
			
		||||
			if (!conn) {
 | 
			
		||||
				LOGPSS(ss, LOGL_ERROR, "Cannot find conn for EUSE %s\n", addr);
 | 
			
		||||
				ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
 | 
			
		||||
			} else {
 | 
			
		||||
				msg_out = osmo_gsup_msgb_alloc("GSUP USSD FW");
 | 
			
		||||
				osmo_gsup_encode(msg_out, gsup);
 | 
			
		||||
				osmo_gsup_conn_send(conn, msg_out);
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			/* Handle internally */
 | 
			
		||||
			ss->u.iuse->handle_ussd(conn, ss, gsup, req);
 | 
			
		||||
			/* Release session immediately */
 | 
			
		||||
			ss_session_free(ss);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 */
 | 
			
		||||
int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
 | 
			
		||||
{
 | 
			
		||||
	struct hlr *hlr = conn->server->priv;
 | 
			
		||||
	struct ss_session *ss;
 | 
			
		||||
	struct ss_request req = {0};
 | 
			
		||||
	struct gsup_route *gsup_rt;
 | 
			
		||||
 | 
			
		||||
	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));
 | 
			
		||||
			/* FIXME: Send a Reject component? */
 | 
			
		||||
			goto out_err;
 | 
			
		||||
		}
 | 
			
		||||
	} 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));
 | 
			
		||||
		goto out_err;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
			goto out_err;
 | 
			
		||||
		}
 | 
			
		||||
		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);
 | 
			
		||||
			goto out_err;
 | 
			
		||||
		}
 | 
			
		||||
		/* Get IPA name from VLR conn and save as ss->vlr_number */
 | 
			
		||||
		if (!conn_is_euse(conn)) {
 | 
			
		||||
			gsup_rt = gsup_route_find_by_conn(conn);
 | 
			
		||||
			if (gsup_rt) {
 | 
			
		||||
				ss->vlr_number = (uint8_t *)talloc_strdup(ss, (const char *)gsup_rt->addr);
 | 
			
		||||
				ss->vlr_number_len = strlen((const char *)gsup_rt->addr) + 1;
 | 
			
		||||
				LOGPSS(ss, LOGL_DEBUG, "Destination IPA name retrieved from GSUP route: %s\n",
 | 
			
		||||
				       osmo_quote_str((const char *)ss->vlr_number, ss->vlr_number_len));
 | 
			
		||||
			} else {
 | 
			
		||||
				LOGPSS(ss, LOGL_NOTICE, "Could not find GSUP route, therefore can't set the destination"
 | 
			
		||||
							" IPA name. We'll try to look it up later, but this should not"
 | 
			
		||||
							" have happened.\n");
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (ss_op_is_ussd(req.opcode)) {
 | 
			
		||||
			if (conn_is_euse(conn)) {
 | 
			
		||||
				/* EUSE->VLR: MT USSD. EUSE is known ('conn'), VLR is to be resolved */
 | 
			
		||||
				ss->u.euse = euse_by_conn(conn);
 | 
			
		||||
			} 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(conn, ss, gsup, &req);
 | 
			
		||||
		} else {
 | 
			
		||||
			/* dispatch non-call SS to internal code */
 | 
			
		||||
			handle_ss(ss, 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);
 | 
			
		||||
			goto out_err;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* 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(conn, ss, gsup, &req);
 | 
			
		||||
		} else {
 | 
			
		||||
			/* dispatch non-call SS to internal code */
 | 
			
		||||
			handle_ss(ss, 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);
 | 
			
		||||
			goto out_err;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* 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(conn, ss, gsup, &req);
 | 
			
		||||
			} else {
 | 
			
		||||
				/* dispatch non-call SS to internal code */
 | 
			
		||||
				handle_ss(ss, 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);
 | 
			
		||||
		goto out_err;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
 | 
			
		||||
out_err:
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int rx_proc_ss_error(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
 | 
			
		||||
{
 | 
			
		||||
	LOGP(DSS, LOGL_NOTICE, "%s/0x%08x: Process SS ERROR (%s)\n", gsup->imsi, gsup->session_id,
 | 
			
		||||
		osmo_gsup_session_state_name(gsup->session_state));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										344
									
								
								src/hlr_vty.c
									
									
									
									
									
								
							
							
						
						
									
										344
									
								
								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,14 +27,18 @@
 | 
			
		||||
 | 
			
		||||
#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 "hlr_vty.h"
 | 
			
		||||
#include "hlr_vty_subscr.h"
 | 
			
		||||
 | 
			
		||||
static struct hlr *g_hlr = NULL;
 | 
			
		||||
#include <osmocom/hlr/db.h>
 | 
			
		||||
#include <osmocom/hlr/hlr.h>
 | 
			
		||||
#include <osmocom/hlr/hlr_vty.h>
 | 
			
		||||
#include <osmocom/hlr/hlr_vty_subscr.h>
 | 
			
		||||
#include <osmocom/hlr/hlr_ussd.h>
 | 
			
		||||
#include <osmocom/hlr/gsup_server.h>
 | 
			
		||||
 | 
			
		||||
struct cmd_node hlr_node = {
 | 
			
		||||
	HLR_NODE,
 | 
			
		||||
@@ -64,6 +73,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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -75,6 +105,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",
 | 
			
		||||
@@ -89,12 +146,262 @@ DEFUN(cfg_hlr_gsup_bind_ip,
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***********************************************************************
 | 
			
		||||
 * USSD Entity
 | 
			
		||||
 ***********************************************************************/
 | 
			
		||||
 | 
			
		||||
#include <osmocom/hlr/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;
 | 
			
		||||
	default:
 | 
			
		||||
	case HLR_NODE:
 | 
			
		||||
@@ -122,12 +429,13 @@ 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(cat);
 | 
			
		||||
	logging_vty_add_cmds();
 | 
			
		||||
	osmo_talloc_vty_add_cmds();
 | 
			
		||||
	osmo_stats_vty_add_cmds();
 | 
			
		||||
 | 
			
		||||
	install_element_ve(&show_gsup_conn_cmd);
 | 
			
		||||
 | 
			
		||||
	install_element(CONFIG_NODE, &cfg_hlr_cmd);
 | 
			
		||||
	install_node(&hlr_node, config_write_hlr);
 | 
			
		||||
@@ -137,5 +445,21 @@ void hlr_vty_init(struct hlr *hlr, const struct log_info *cat)
 | 
			
		||||
 | 
			
		||||
	install_element(GSUP_NODE, &cfg_hlr_gsup_bind_ip_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();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,20 +20,41 @@
 | 
			
		||||
#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>
 | 
			
		||||
#include <osmocom/vty/command.h>
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
 | 
			
		||||
#include "hlr.h"
 | 
			
		||||
#include "db.h"
 | 
			
		||||
#include <osmocom/hlr/hlr.h>
 | 
			
		||||
#include <osmocom/hlr/db.h>
 | 
			
		||||
 | 
			
		||||
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 dump_last_lu_seen(struct vty *vty, const char *domain_label, time_t last_lu_seen)
 | 
			
		||||
{
 | 
			
		||||
	char datebuf[26]; /* for ctime_r(3) */
 | 
			
		||||
	if (!last_lu_seen)
 | 
			
		||||
		return;
 | 
			
		||||
	vty_out(vty, "    last LU seen on %s: %s UTC%s", domain_label, get_datestr(&last_lu_seen, datebuf),
 | 
			
		||||
		VTY_NEWLINE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
 | 
			
		||||
{
 | 
			
		||||
@@ -45,6 +66,15 @@ static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
 | 
			
		||||
 | 
			
		||||
	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 +95,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);
 | 
			
		||||
	dump_last_lu_seen(vty, "CS", subscr->last_lu_seen);
 | 
			
		||||
	dump_last_lu_seen(vty, "PS", subscr->last_lu_seen_ps);
 | 
			
		||||
 | 
			
		||||
	if (!*subscr->imsi)
 | 
			
		||||
		return;
 | 
			
		||||
@@ -116,6 +148,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);
 | 
			
		||||
@@ -123,6 +156,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);
 | 
			
		||||
@@ -132,18 +176,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,
 | 
			
		||||
@@ -161,12 +207,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;
 | 
			
		||||
@@ -177,7 +228,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)
 | 
			
		||||
@@ -225,9 +276,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;
 | 
			
		||||
@@ -235,6 +286,9 @@ DEFUN(subscriber_msisdn,
 | 
			
		||||
	const char *id = argv[1];
 | 
			
		||||
	const char *msisdn = argv[2];
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
@@ -245,6 +299,7 @@ DEFUN(subscriber_msisdn,
 | 
			
		||||
			vty_out(vty, "%% MSISDN invalid: '%s'%s", msisdn, VTY_NEWLINE);
 | 
			
		||||
			return CMD_WARNING;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (get_subscr_by_argv(vty, id_type, id, &subscr))
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
@@ -255,11 +310,18 @@ DEFUN(subscriber_msisdn,
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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;
 | 
			
		||||
}
 | 
			
		||||
@@ -476,11 +538,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);
 | 
			
		||||
@@ -488,4 +624,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 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
void hlr_vty_subscriber_init(struct hlr *hlr);
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include <osmocom/hlr/logging.h>
 | 
			
		||||
 | 
			
		||||
const struct log_info_cat hlr_log_info_cat[] = {
 | 
			
		||||
	[DMAIN] = {
 | 
			
		||||
@@ -19,6 +19,18 @@ 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,
 | 
			
		||||
	},
 | 
			
		||||
	[DMSLOOKUP] = {
 | 
			
		||||
		.name = "DMSLOOKUP",
 | 
			
		||||
		.description = "Mobile Subscriber Lookup",
 | 
			
		||||
		.color = "\033[1;35m",
 | 
			
		||||
		.enabled = 1, .loglevel = LOGL_NOTICE,
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct log_info hlr_log_info = {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										62
									
								
								src/luop.c
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								src/luop.c
									
									
									
									
									
								
							@@ -25,13 +25,13 @@
 | 
			
		||||
#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"
 | 
			
		||||
#include <osmocom/hlr/gsup_server.h>
 | 
			
		||||
#include <osmocom/hlr/gsup_router.h>
 | 
			
		||||
#include <osmocom/hlr/logging.h>
 | 
			
		||||
#include <osmocom/hlr/luop.h>
 | 
			
		||||
 | 
			
		||||
const struct value_string lu_state_names[] = {
 | 
			
		||||
	{ LU_S_NULL,			"NULL" },
 | 
			
		||||
@@ -50,7 +50,7 @@ static void _luop_tx_gsup(struct lu_operation *luop,
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg_out;
 | 
			
		||||
 | 
			
		||||
	msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP LUOP");
 | 
			
		||||
	msg_out = osmo_gsup_msgb_alloc("GSUP LUOP");
 | 
			
		||||
	osmo_gsup_encode(msg_out, gsup);
 | 
			
		||||
 | 
			
		||||
	osmo_gsup_addr_send(luop->gsup_server, luop->peer,
 | 
			
		||||
@@ -164,23 +164,6 @@ void lu_op_statechg(struct lu_operation *luop, enum lu_state 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)
 | 
			
		||||
{
 | 
			
		||||
@@ -231,34 +214,27 @@ void lu_op_tx_cancel_old(struct lu_operation *luop)
 | 
			
		||||
/*! 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 msisdn_enc[43]; /* TODO use constant; TS 24.008 10.5.4.7 */
 | 
			
		||||
	int l;
 | 
			
		||||
	struct hlr_subscriber *subscr = &luop->subscr;
 | 
			
		||||
	struct osmo_gsup_message gsup = { };
 | 
			
		||||
	uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
 | 
			
		||||
	uint8_t apn[APN_MAXLEN];
 | 
			
		||||
	enum osmo_gsup_cn_domain cn_domain;
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
	if (luop->is_ps)
 | 
			
		||||
		cn_domain = OSMO_GSUP_CN_DOMAIN_PS;
 | 
			
		||||
	else
 | 
			
		||||
		cn_domain = OSMO_GSUP_CN_DOMAIN_CS;
 | 
			
		||||
 | 
			
		||||
	l = gsm48_encode_bcd_number(msisdn_enc, sizeof(msisdn_enc), 0,
 | 
			
		||||
				    luop->subscr.msisdn);
 | 
			
		||||
	if (l < 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(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);
 | 
			
		||||
		       "IMSI='%s': Cannot notify GSUP client; could not create gsup message "
 | 
			
		||||
		       "for %s\n", subscr->imsi, luop->peer);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	gsup.msisdn_enc = msisdn_enc;
 | 
			
		||||
	gsup.msisdn_enc_len = l;
 | 
			
		||||
 | 
			
		||||
	#pragma message "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 */
 | 
			
		||||
		osmo_gsup_configure_wildcard_apn(&gsup);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Send ISD to new VLR/SGSN */
 | 
			
		||||
	_luop_tx_gsup(luop, &gsup);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										37
									
								
								src/mslookup/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/mslookup/Makefile.am
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
# 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_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include
 | 
			
		||||
AM_CFLAGS = -fPIC -Wall $(PCSC_CFLAGS) $(TALLOC_CFLAGS) $(LIBOSMOCORE_CFLAGS)
 | 
			
		||||
AM_LDFLAGS = $(COVERAGE_LDFLAGS)
 | 
			
		||||
 | 
			
		||||
lib_LTLIBRARIES = libosmo-mslookup.la
 | 
			
		||||
 | 
			
		||||
libosmo_mslookup_la_SOURCES = \
 | 
			
		||||
	mdns.c \
 | 
			
		||||
	mdns_msg.c \
 | 
			
		||||
	mdns_rfc.c \
 | 
			
		||||
	mdns_sock.c \
 | 
			
		||||
	mslookup.c \
 | 
			
		||||
	mslookup_client.c \
 | 
			
		||||
	mslookup_client_fake.c \
 | 
			
		||||
	mslookup_client_mdns.c \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
libosmo_mslookup_la_LDFLAGS = -version-info $(LIBVERSION)
 | 
			
		||||
libosmo_mslookup_la_LIBADD = \
 | 
			
		||||
	$(LIBOSMOCORE_LIBS) \
 | 
			
		||||
	$(LIBOSMOGSM_LIBS) \
 | 
			
		||||
	$(TALLOC_LIBS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
bin_PROGRAMS = osmo-mslookup-client
 | 
			
		||||
osmo_mslookup_client_SOURCES = osmo-mslookup-client.c
 | 
			
		||||
osmo_mslookup_client_LDADD = \
 | 
			
		||||
	libosmo-mslookup.la \
 | 
			
		||||
	$(LIBOSMOCORE_LIBS) \
 | 
			
		||||
	$(TALLOC_LIBS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
osmo_mslookup_client_CFLAGS = $(TALLOC_CFLAGS) $(LIBOSMOCORE_CFLAGS)
 | 
			
		||||
							
								
								
									
										425
									
								
								src/mslookup/mdns.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										425
									
								
								src/mslookup/mdns.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,425 @@
 | 
			
		||||
/* mslookup specific functions for encoding and decoding mslookup queries/results into mDNS packets, using the high
 | 
			
		||||
 * level functions from mdns_msg.c and mdns_record.c to build the request/answer messages. */
 | 
			
		||||
 | 
			
		||||
/* Copyright 2019 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 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/hlr/logging.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/mslookup/mslookup.h>
 | 
			
		||||
#include <osmocom/mslookup/mdns_msg.h>
 | 
			
		||||
#include <osmocom/mslookup/mdns_rfc.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <inttypes.h>
 | 
			
		||||
 | 
			
		||||
static struct msgb *osmo_mdns_msgb_alloc(const char *label)
 | 
			
		||||
{
 | 
			
		||||
	return msgb_alloc(1024, label);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Combine the mslookup query service, ID and ID type into a domain string.
 | 
			
		||||
 * \param[in] domain_suffix  is appended to each domain in the queries to avoid colliding with the top-level domains
 | 
			
		||||
 *                           administrated by IANA. Example: "mdns.osmocom.org"
 | 
			
		||||
 * \returns allocated buffer with the resulting domain (i.e. "sip.voice.123.msisdn.mdns.osmocom.org") on success,
 | 
			
		||||
 * 	    NULL on failure.
 | 
			
		||||
 */
 | 
			
		||||
static char *domain_from_query(void *ctx, const struct osmo_mslookup_query *query, const char *domain_suffix)
 | 
			
		||||
{
 | 
			
		||||
	const char *id;
 | 
			
		||||
 | 
			
		||||
	/* Get id from query */
 | 
			
		||||
	switch (query->id.type) {
 | 
			
		||||
		case OSMO_MSLOOKUP_ID_IMSI:
 | 
			
		||||
			id = query->id.imsi;
 | 
			
		||||
			break;
 | 
			
		||||
		case OSMO_MSLOOKUP_ID_MSISDN:
 | 
			
		||||
			id = query->id.msisdn;
 | 
			
		||||
			break;
 | 
			
		||||
		default:
 | 
			
		||||
			LOGP(DMSLOOKUP, LOGL_ERROR, "can't encode mslookup query id type %i", query->id.type);
 | 
			
		||||
			return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return talloc_asprintf(ctx, "%s.%s.%s.%s", query->service, id, osmo_mslookup_id_type_name(query->id.type),
 | 
			
		||||
			       domain_suffix);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Split up query service, ID and ID type from a domain string into a mslookup query.
 | 
			
		||||
 * \param[in] domain  with domain_suffix, e.g. "sip.voice.123.msisdn.mdns.osmocom.org"
 | 
			
		||||
 * \param[in] domain_suffix  is appended to each domain in the queries to avoid colliding with the top-level domains
 | 
			
		||||
 *                           administrated by IANA. It is not part of the resulting struct osmo_mslookup_query, so we
 | 
			
		||||
 *                           remove it in this function. Example: "mdns.osmocom.org"
 | 
			
		||||
 */
 | 
			
		||||
int query_from_domain(struct osmo_mslookup_query *query, const char *domain, const char *domain_suffix)
 | 
			
		||||
{
 | 
			
		||||
	int domain_len = strlen(domain) - strlen(domain_suffix) - 1;
 | 
			
		||||
	char domain_buf[OSMO_MDNS_RFC_MAX_NAME_LEN];
 | 
			
		||||
 | 
			
		||||
	if (domain_len <= 0 || domain_len >= sizeof(domain_buf))
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	if (domain[domain_len] != '.' || strcmp(domain + domain_len + 1, domain_suffix) != 0)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	memcpy(domain_buf, domain, domain_len);
 | 
			
		||||
	domain_buf[domain_len] = '\0';
 | 
			
		||||
	return osmo_mslookup_query_init_from_domain_str(query, domain_buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Encode a mslookup query into a mDNS packet.
 | 
			
		||||
 * \param[in] domain_suffix  is appended to each domain in the queries to avoid colliding with the top-level domains
 | 
			
		||||
 *                           administrated by IANA. Example: "mdns.osmocom.org"
 | 
			
		||||
 * \returns msgb, or NULL on error.
 | 
			
		||||
 */
 | 
			
		||||
struct msgb *osmo_mdns_query_encode(void *ctx, uint16_t packet_id, const struct osmo_mslookup_query *query,
 | 
			
		||||
				    const char *domain_suffix)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_msg_request req = {0};
 | 
			
		||||
	struct msgb *msg = osmo_mdns_msgb_alloc(__func__);
 | 
			
		||||
 | 
			
		||||
	req.id = packet_id;
 | 
			
		||||
	req.type = OSMO_MDNS_RFC_RECORD_TYPE_ALL;
 | 
			
		||||
	req.domain = domain_from_query(ctx, query, domain_suffix);
 | 
			
		||||
	if (!req.domain)
 | 
			
		||||
		goto error;
 | 
			
		||||
	if (osmo_mdns_msg_request_encode(ctx, msg, &req))
 | 
			
		||||
		goto error;
 | 
			
		||||
	talloc_free(req.domain);
 | 
			
		||||
	return msg;
 | 
			
		||||
error:
 | 
			
		||||
	msgb_free(msg);
 | 
			
		||||
	talloc_free(req.domain);
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Decode a mDNS request packet into a mslookup query.
 | 
			
		||||
 * \param[out] packet_id  the result must be sent with the same packet_id.
 | 
			
		||||
 * \param[in] domain_suffix  is appended to each domain in the queries to avoid colliding with the top-level domains
 | 
			
		||||
 *                           administrated by IANA. Example: "mdns.osmocom.org"
 | 
			
		||||
 * \returns allocated mslookup query on success, NULL on error.
 | 
			
		||||
 */
 | 
			
		||||
struct osmo_mslookup_query *osmo_mdns_query_decode(void *ctx, const uint8_t *data, size_t data_len,
 | 
			
		||||
						   uint16_t *packet_id, const char *domain_suffix)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_msg_request *req = NULL;
 | 
			
		||||
	struct osmo_mslookup_query *query = NULL;
 | 
			
		||||
 | 
			
		||||
	req = osmo_mdns_msg_request_decode(ctx, data, data_len);
 | 
			
		||||
	if (!req)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	query = talloc_zero(ctx, struct osmo_mslookup_query);
 | 
			
		||||
	OSMO_ASSERT(query);
 | 
			
		||||
	if (query_from_domain(query, req->domain, domain_suffix) < 0)
 | 
			
		||||
		goto error_free;
 | 
			
		||||
 | 
			
		||||
	*packet_id = req->id;
 | 
			
		||||
	talloc_free(req);
 | 
			
		||||
	return query;
 | 
			
		||||
error_free:
 | 
			
		||||
	talloc_free(req);
 | 
			
		||||
	talloc_free(query);
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Parse sockaddr_str from mDNS record, so the mslookup result can be filled with it.
 | 
			
		||||
 * \param[out] sockaddr_str resulting IPv4 or IPv6 sockaddr_str.
 | 
			
		||||
 * \param[in] rec  single record of the abstracted list of mDNS records
 | 
			
		||||
 * \returns 0 on success, -EINVAL on error.
 | 
			
		||||
 */
 | 
			
		||||
static int sockaddr_str_from_mdns_record(struct osmo_sockaddr_str *sockaddr_str, struct osmo_mdns_record *rec)
 | 
			
		||||
{
 | 
			
		||||
	switch (rec->type) {
 | 
			
		||||
	case OSMO_MDNS_RFC_RECORD_TYPE_A:
 | 
			
		||||
		if (rec->length != 4) {
 | 
			
		||||
			LOGP(DMSLOOKUP, LOGL_ERROR, "unexpected length of A record\n");
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		}
 | 
			
		||||
		osmo_sockaddr_str_from_32(sockaddr_str, *(uint32_t *)rec->data, 0);
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_MDNS_RFC_RECORD_TYPE_AAAA:
 | 
			
		||||
		if (rec->length != 16) {
 | 
			
		||||
			LOGP(DMSLOOKUP, LOGL_ERROR, "unexpected length of AAAA record\n");
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		}
 | 
			
		||||
		osmo_sockaddr_str_from_in6_addr(sockaddr_str, (struct in6_addr*)rec->data, 0);
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "unexpected record type\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Encode a successful mslookup result, along with the original query and packet_id into one mDNS answer packet.
 | 
			
		||||
 *
 | 
			
		||||
 * The records in the packet are ordered as follows:
 | 
			
		||||
 * 1) "age", ip_v4/v6, "port" (only IPv4 or IPv6 present) or
 | 
			
		||||
 * 2) "age", ip_v4, "port", ip_v6, "port" (both IPv4 and v6 present).
 | 
			
		||||
 * "age" and "port" are TXT records, ip_v4 is an A record, ip_v6 is an AAAA record.
 | 
			
		||||
 *
 | 
			
		||||
 * \param[in] packet_id  as received in osmo_mdns_query_decode().
 | 
			
		||||
 * \param[in] query  the original query, so we can send the domain back in the answer (i.e. "sip.voice.1234.msisdn").
 | 
			
		||||
 * \param[in] result  holds the age, IPs and ports of the queried service.
 | 
			
		||||
 * \param[in] domain_suffix  is appended to each domain in the queries to avoid colliding with the top-level domains
 | 
			
		||||
 *                           administrated by IANA. Example: "mdns.osmocom.org"
 | 
			
		||||
 * \returns msg on success, NULL on error.
 | 
			
		||||
 */
 | 
			
		||||
struct msgb *osmo_mdns_result_encode(void *ctx, uint16_t packet_id, const struct osmo_mslookup_query *query,
 | 
			
		||||
				     const struct osmo_mslookup_result *result, const char *domain_suffix)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_msg_answer ans = {};
 | 
			
		||||
	struct osmo_mdns_record *rec_age = NULL;
 | 
			
		||||
	struct osmo_mdns_record rec_ip_v4 = {0};
 | 
			
		||||
	struct osmo_mdns_record rec_ip_v6 = {0};
 | 
			
		||||
	struct osmo_mdns_record *rec_ip_v4_port = NULL;
 | 
			
		||||
	struct osmo_mdns_record *rec_ip_v6_port = NULL;
 | 
			
		||||
	struct in_addr rec_ip_v4_in;
 | 
			
		||||
	struct in6_addr rec_ip_v6_in;
 | 
			
		||||
	struct msgb *msg = osmo_mdns_msgb_alloc(__func__);
 | 
			
		||||
	char buf[256];
 | 
			
		||||
 | 
			
		||||
	ctx = talloc_named(ctx, 0, "osmo_mdns_result_encode");
 | 
			
		||||
 | 
			
		||||
	/* Prepare answer (ans) */
 | 
			
		||||
	ans.domain = domain_from_query(ctx, query, domain_suffix);
 | 
			
		||||
	if (!ans.domain)
 | 
			
		||||
		goto error;
 | 
			
		||||
	ans.id = packet_id;
 | 
			
		||||
	INIT_LLIST_HEAD(&ans.records);
 | 
			
		||||
 | 
			
		||||
	/* Record for age */
 | 
			
		||||
	rec_age = osmo_mdns_record_txt_keyval_encode(ctx, "age", "%"PRIu32, result->age);
 | 
			
		||||
	OSMO_ASSERT(rec_age);
 | 
			
		||||
	llist_add_tail(&rec_age->list, &ans.records);
 | 
			
		||||
 | 
			
		||||
	/* Records for IPv4 */
 | 
			
		||||
	if (osmo_sockaddr_str_is_set(&result->host_v4)) {
 | 
			
		||||
		if (osmo_sockaddr_str_to_in_addr(&result->host_v4, &rec_ip_v4_in) < 0) {
 | 
			
		||||
			LOGP(DMSLOOKUP, LOGL_ERROR, "failed to encode ipv4: %s\n",
 | 
			
		||||
			     osmo_mslookup_result_name_b(buf, sizeof(buf), query, result));
 | 
			
		||||
			goto error;
 | 
			
		||||
		}
 | 
			
		||||
		rec_ip_v4.type = OSMO_MDNS_RFC_RECORD_TYPE_A;
 | 
			
		||||
		rec_ip_v4.data = (uint8_t *)&rec_ip_v4_in;
 | 
			
		||||
		rec_ip_v4.length = sizeof(rec_ip_v4_in);
 | 
			
		||||
		llist_add_tail(&rec_ip_v4.list, &ans.records);
 | 
			
		||||
 | 
			
		||||
		rec_ip_v4_port = osmo_mdns_record_txt_keyval_encode(ctx, "port", "%"PRIu16, result->host_v4.port);
 | 
			
		||||
		OSMO_ASSERT(rec_ip_v4_port);
 | 
			
		||||
		llist_add_tail(&rec_ip_v4_port->list, &ans.records);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Records for IPv6 */
 | 
			
		||||
	if (osmo_sockaddr_str_is_set(&result->host_v6)) {
 | 
			
		||||
		if (osmo_sockaddr_str_to_in6_addr(&result->host_v6, &rec_ip_v6_in) < 0) {
 | 
			
		||||
			LOGP(DMSLOOKUP, LOGL_ERROR, "failed to encode ipv6: %s\n",
 | 
			
		||||
			     osmo_mslookup_result_name_b(buf, sizeof(buf), query, result));
 | 
			
		||||
			goto error;
 | 
			
		||||
		}
 | 
			
		||||
		rec_ip_v6.type = OSMO_MDNS_RFC_RECORD_TYPE_AAAA;
 | 
			
		||||
		rec_ip_v6.data = (uint8_t *)&rec_ip_v6_in;
 | 
			
		||||
		rec_ip_v6.length = sizeof(rec_ip_v6_in);
 | 
			
		||||
		llist_add_tail(&rec_ip_v6.list, &ans.records);
 | 
			
		||||
 | 
			
		||||
		rec_ip_v6_port = osmo_mdns_record_txt_keyval_encode(ctx, "port", "%"PRIu16, result->host_v6.port);
 | 
			
		||||
		OSMO_ASSERT(rec_ip_v6_port);
 | 
			
		||||
		llist_add_tail(&rec_ip_v6_port->list, &ans.records);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (osmo_mdns_msg_answer_encode(ctx, msg, &ans)) {
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "failed to encode mDNS answer: %s\n",
 | 
			
		||||
		     osmo_mslookup_result_name_b(buf, sizeof(buf), query, result));
 | 
			
		||||
		goto error;
 | 
			
		||||
	}
 | 
			
		||||
	talloc_free(ctx);
 | 
			
		||||
	return msg;
 | 
			
		||||
error:
 | 
			
		||||
	msgb_free(msg);
 | 
			
		||||
	talloc_free(ctx);
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int decode_uint32_t(const char *str, uint32_t *val)
 | 
			
		||||
{
 | 
			
		||||
	long long int lld;
 | 
			
		||||
	char *endptr = NULL;
 | 
			
		||||
	*val = 0;
 | 
			
		||||
	errno = 0;
 | 
			
		||||
	lld = strtoll(str, &endptr, 10);
 | 
			
		||||
	if (errno || !endptr || *endptr)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	if (lld < 0 || lld > UINT32_MAX)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	*val = lld;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int decode_port(const char *str, uint16_t *port)
 | 
			
		||||
{
 | 
			
		||||
	uint32_t val;
 | 
			
		||||
	if (decode_uint32_t(str, &val))
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	if (val > 65535)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	*port = val;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Read expected mDNS records into mslookup result.
 | 
			
		||||
 *
 | 
			
		||||
 * The records in the packet must be ordered as follows:
 | 
			
		||||
 * 1) "age", ip_v4/v6, "port" (only IPv4 or IPv6 present) or
 | 
			
		||||
 * 2) "age", ip_v4, "port", ip_v6, "port" (both IPv4 and v6 present).
 | 
			
		||||
 * "age" and "port" are TXT records, ip_v4 is an A record, ip_v6 is an AAAA record.
 | 
			
		||||
 *
 | 
			
		||||
 * \param[out] result  holds the age, IPs and ports of the queried service.
 | 
			
		||||
 * \param[in] ans  abstracted mDNS answer with a list of resource records.
 | 
			
		||||
 * \returns 0 on success, -EINVAL on error.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_mdns_result_from_answer(struct osmo_mslookup_result *result, const struct osmo_mdns_msg_answer *ans)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_record *rec;
 | 
			
		||||
	char txt_key[64];
 | 
			
		||||
	char txt_value[64];
 | 
			
		||||
	bool found_age = false;
 | 
			
		||||
	bool found_ip_v4 = false;
 | 
			
		||||
	bool found_ip_v6 = false;
 | 
			
		||||
	struct osmo_sockaddr_str *expect_port_for = NULL;
 | 
			
		||||
 | 
			
		||||
	*result = (struct osmo_mslookup_result){};
 | 
			
		||||
 | 
			
		||||
	result->rc = OSMO_MSLOOKUP_RC_NONE;
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry(rec, &ans->records, list) {
 | 
			
		||||
		switch (rec->type) {
 | 
			
		||||
			case OSMO_MDNS_RFC_RECORD_TYPE_A:
 | 
			
		||||
				if (expect_port_for) {
 | 
			
		||||
					LOGP(DMSLOOKUP, LOGL_ERROR,
 | 
			
		||||
					     "'A' record found, but still expecting a 'port' value first\n");
 | 
			
		||||
					return -EINVAL;
 | 
			
		||||
				}
 | 
			
		||||
				if (found_ip_v4) {
 | 
			
		||||
					LOGP(DMSLOOKUP, LOGL_ERROR, "'A' record found twice in mDNS answer\n");
 | 
			
		||||
					return -EINVAL;
 | 
			
		||||
				}
 | 
			
		||||
				found_ip_v4 = true;
 | 
			
		||||
				expect_port_for = &result->host_v4;
 | 
			
		||||
				if (sockaddr_str_from_mdns_record(expect_port_for, rec)) {
 | 
			
		||||
					LOGP(DMSLOOKUP, LOGL_ERROR, "'A' record with invalid address data\n");
 | 
			
		||||
					return -EINVAL;
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
			case OSMO_MDNS_RFC_RECORD_TYPE_AAAA:
 | 
			
		||||
				if (expect_port_for) {
 | 
			
		||||
					LOGP(DMSLOOKUP, LOGL_ERROR,
 | 
			
		||||
					     "'AAAA' record found, but still expecting a 'port' value first\n");
 | 
			
		||||
					return -EINVAL;
 | 
			
		||||
				}
 | 
			
		||||
				if (found_ip_v6) {
 | 
			
		||||
					LOGP(DMSLOOKUP, LOGL_ERROR, "'AAAA' record found twice in mDNS answer\n");
 | 
			
		||||
					return -EINVAL;
 | 
			
		||||
				}
 | 
			
		||||
				found_ip_v6 = true;
 | 
			
		||||
				expect_port_for = &result->host_v6;
 | 
			
		||||
				if (sockaddr_str_from_mdns_record(expect_port_for, rec) != 0) {
 | 
			
		||||
					LOGP(DMSLOOKUP, LOGL_ERROR, "'AAAA' record with invalid address data\n");
 | 
			
		||||
					return -EINVAL;
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
			case OSMO_MDNS_RFC_RECORD_TYPE_TXT:
 | 
			
		||||
				if (osmo_mdns_record_txt_keyval_decode(rec, txt_key, sizeof(txt_key),
 | 
			
		||||
								       txt_value, sizeof(txt_value)) != 0) {
 | 
			
		||||
					LOGP(DMSLOOKUP, LOGL_ERROR, "failed to decode txt record\n");
 | 
			
		||||
					return -EINVAL;
 | 
			
		||||
				}
 | 
			
		||||
				if (strcmp(txt_key, "age") == 0) {
 | 
			
		||||
					if (found_age) {
 | 
			
		||||
						LOGP(DMSLOOKUP, LOGL_ERROR, "duplicate 'TXT' record for 'age'\n");
 | 
			
		||||
						return -EINVAL;
 | 
			
		||||
					}
 | 
			
		||||
					found_age = true;
 | 
			
		||||
					if (decode_uint32_t(txt_value, &result->age)) {
 | 
			
		||||
						LOGP(DMSLOOKUP, LOGL_ERROR,
 | 
			
		||||
						     "'TXT' record: invalid 'age' value ('age=%s')\n", txt_value);
 | 
			
		||||
						return -EINVAL;
 | 
			
		||||
					}
 | 
			
		||||
				} else if (strcmp(txt_key, "port") == 0) {
 | 
			
		||||
					if (!expect_port_for) {
 | 
			
		||||
						LOGP(DMSLOOKUP, LOGL_ERROR,
 | 
			
		||||
						     "'TXT' record for 'port' without previous 'A' or 'AAAA' record\n");
 | 
			
		||||
						return -EINVAL;
 | 
			
		||||
					}
 | 
			
		||||
					if (decode_port(txt_value, &expect_port_for->port)) {
 | 
			
		||||
						LOGP(DMSLOOKUP, LOGL_ERROR,
 | 
			
		||||
						     "'TXT' record: invalid 'port' value ('port=%s')\n", txt_value);
 | 
			
		||||
						return -EINVAL;
 | 
			
		||||
					}
 | 
			
		||||
					expect_port_for = NULL;
 | 
			
		||||
				} else {
 | 
			
		||||
					LOGP(DMSLOOKUP, LOGL_ERROR, "unexpected key '%s' in TXT record\n", txt_key);
 | 
			
		||||
					return -EINVAL;
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
			default:
 | 
			
		||||
				LOGP(DMSLOOKUP, LOGL_ERROR, "unexpected record type\n");
 | 
			
		||||
				return -EINVAL;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Check if everything was found */
 | 
			
		||||
	if (!found_age || !(found_ip_v4 || found_ip_v6) || expect_port_for) {
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "missing resource records in mDNS answer\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result->rc = OSMO_MSLOOKUP_RC_RESULT;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Decode a mDNS answer packet into a mslookup result, query and packet_id.
 | 
			
		||||
 * \param[out] packet_id  same ID as sent in the request packet.
 | 
			
		||||
 * \param[out] query  the original query (service, ID, ID type).
 | 
			
		||||
 * \param[out] result  holds the age, IPs and ports of the queried service.
 | 
			
		||||
 * \param[in] domain_suffix  is appended to each domain in the queries to avoid colliding with the top-level domains
 | 
			
		||||
 *                           administrated by IANA. Example: "mdns.osmocom.org"
 | 
			
		||||
 * \returns 0 on success, -EINVAL on error.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_mdns_result_decode(void *ctx, const uint8_t *data, size_t data_len, uint16_t *packet_id,
 | 
			
		||||
			    struct osmo_mslookup_query *query, struct osmo_mslookup_result *result,
 | 
			
		||||
			    const char *domain_suffix)
 | 
			
		||||
{
 | 
			
		||||
	int rc = -EINVAL;
 | 
			
		||||
	struct osmo_mdns_msg_answer *ans;
 | 
			
		||||
	ans = osmo_mdns_msg_answer_decode(ctx, data, data_len);
 | 
			
		||||
	if (!ans)
 | 
			
		||||
		goto exit_free;
 | 
			
		||||
 | 
			
		||||
	if (query_from_domain(query, ans->domain, domain_suffix) < 0)
 | 
			
		||||
		goto exit_free;
 | 
			
		||||
 | 
			
		||||
	if (osmo_mdns_result_from_answer(result, ans) < 0)
 | 
			
		||||
		goto exit_free;
 | 
			
		||||
 | 
			
		||||
	*packet_id = ans->id;
 | 
			
		||||
	rc = 0;
 | 
			
		||||
 | 
			
		||||
exit_free:
 | 
			
		||||
	talloc_free(ans);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										261
									
								
								src/mslookup/mdns_msg.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								src/mslookup/mdns_msg.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,261 @@
 | 
			
		||||
/* High level mDNS encoding and decoding functions for whole messages:
 | 
			
		||||
 * Request message (header, question)
 | 
			
		||||
 * Answer message (header, resource record 1, ... resource record N)*/
 | 
			
		||||
 | 
			
		||||
/* Copyright 2019 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 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 <errno.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <osmocom/hlr/logging.h>
 | 
			
		||||
#include <osmocom/mslookup/mdns_msg.h>
 | 
			
		||||
 | 
			
		||||
/*! Encode request message into one mDNS packet, consisting of the header section and one question section.
 | 
			
		||||
 * \returns 0 on success, -EINVAL on error.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_mdns_msg_request_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_msg_request *req)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_rfc_header hdr = {0};
 | 
			
		||||
	struct osmo_mdns_rfc_question qst = {0};
 | 
			
		||||
 | 
			
		||||
	hdr.id = req->id;
 | 
			
		||||
	hdr.qdcount = 1;
 | 
			
		||||
	osmo_mdns_rfc_header_encode(msg, &hdr);
 | 
			
		||||
 | 
			
		||||
	qst.domain = req->domain;
 | 
			
		||||
	qst.qtype = req->type;
 | 
			
		||||
	qst.qclass = OSMO_MDNS_RFC_CLASS_IN;
 | 
			
		||||
	if (osmo_mdns_rfc_question_encode(ctx, msg, &qst) != 0)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Decode request message from a mDNS packet, consisting of the header section and one question section.
 | 
			
		||||
 * \returns allocated request message on success, NULL on error.
 | 
			
		||||
 */
 | 
			
		||||
struct osmo_mdns_msg_request *osmo_mdns_msg_request_decode(void *ctx, const uint8_t *data, size_t data_len)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_rfc_header hdr = {0};
 | 
			
		||||
	size_t hdr_len = sizeof(struct osmo_mdns_rfc_header);
 | 
			
		||||
	struct osmo_mdns_rfc_question* qst = NULL;
 | 
			
		||||
	struct osmo_mdns_msg_request *ret = NULL;
 | 
			
		||||
 | 
			
		||||
	if (data_len < hdr_len || osmo_mdns_rfc_header_decode(data, hdr_len, &hdr) != 0 || hdr.qr != 0)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	qst = osmo_mdns_rfc_question_decode(ctx, data + hdr_len, data_len - hdr_len);
 | 
			
		||||
	if (!qst)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	ret = talloc_zero(ctx, struct osmo_mdns_msg_request);
 | 
			
		||||
	ret->id = hdr.id;
 | 
			
		||||
	ret->domain = talloc_strdup(ret, qst->domain);
 | 
			
		||||
	ret->type = qst->qtype;
 | 
			
		||||
 | 
			
		||||
	talloc_free(qst);
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Initialize the linked list for resource records in a answer message. */
 | 
			
		||||
void osmo_mdns_msg_answer_init(struct osmo_mdns_msg_answer *ans)
 | 
			
		||||
{
 | 
			
		||||
	*ans = (struct osmo_mdns_msg_answer){};
 | 
			
		||||
	INIT_LLIST_HEAD(&ans->records);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Encode answer message into one mDNS packet, consisting of the header section and N resource records.
 | 
			
		||||
 *
 | 
			
		||||
 * To keep things simple, this sends the domain with each resource record. Other DNS implementations make use of
 | 
			
		||||
 * "message compression", which would send a question section with the domain before the resource records, and then
 | 
			
		||||
 * point inside each resource record with an offset back to the domain in the question section (RFC 1035 4.1.4).
 | 
			
		||||
 * \returns 0 on success, -EINVAL on error.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_mdns_msg_answer_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_msg_answer *ans)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_rfc_header hdr = {0};
 | 
			
		||||
	struct osmo_mdns_record *ans_record;
 | 
			
		||||
 | 
			
		||||
	hdr.id = ans->id;
 | 
			
		||||
	hdr.qr = 1;
 | 
			
		||||
	hdr.ancount = llist_count(&ans->records);
 | 
			
		||||
	osmo_mdns_rfc_header_encode(msg, &hdr);
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry(ans_record, &ans->records, list) {
 | 
			
		||||
		struct osmo_mdns_rfc_record rec = {0};
 | 
			
		||||
 | 
			
		||||
		rec.domain = ans->domain;
 | 
			
		||||
		rec.type = ans_record->type;
 | 
			
		||||
		rec.class = OSMO_MDNS_RFC_CLASS_IN;
 | 
			
		||||
		rec.ttl = 0;
 | 
			
		||||
		rec.rdlength = ans_record->length;
 | 
			
		||||
		rec.rdata = ans_record->data;
 | 
			
		||||
 | 
			
		||||
		if (osmo_mdns_rfc_record_encode(ctx, msg, &rec) != 0)
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Decode answer message from a mDNS packet.
 | 
			
		||||
 *
 | 
			
		||||
 * Answer messages must consist of one header and one or more resource records. An additional question section or
 | 
			
		||||
 * message compression (RFC 1035 4.1.4) are not supported.
 | 
			
		||||
* \returns allocated answer message on success, NULL on error.
 | 
			
		||||
 */
 | 
			
		||||
struct osmo_mdns_msg_answer *osmo_mdns_msg_answer_decode(void *ctx, const uint8_t *data, size_t data_len)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_rfc_header hdr = {};
 | 
			
		||||
	size_t hdr_len = sizeof(struct osmo_mdns_rfc_header);
 | 
			
		||||
	struct osmo_mdns_msg_answer *ret = talloc_zero(ctx, struct osmo_mdns_msg_answer);
 | 
			
		||||
 | 
			
		||||
	/* Parse header section */
 | 
			
		||||
	if (data_len < hdr_len || osmo_mdns_rfc_header_decode(data, hdr_len, &hdr) != 0 || hdr.qr != 1)
 | 
			
		||||
		goto error;
 | 
			
		||||
	ret->id = hdr.id;
 | 
			
		||||
	data_len -= hdr_len;
 | 
			
		||||
	data += hdr_len;
 | 
			
		||||
 | 
			
		||||
	/* Parse resource records */
 | 
			
		||||
	INIT_LLIST_HEAD(&ret->records);
 | 
			
		||||
	while (data_len) {
 | 
			
		||||
		size_t record_len;
 | 
			
		||||
		struct osmo_mdns_rfc_record *rec;
 | 
			
		||||
		struct osmo_mdns_record* ret_record;
 | 
			
		||||
 | 
			
		||||
		rec = osmo_mdns_rfc_record_decode(ret, data, data_len, &record_len);
 | 
			
		||||
		if (!rec)
 | 
			
		||||
			goto error;
 | 
			
		||||
 | 
			
		||||
		/* Copy domain to ret */
 | 
			
		||||
		if (ret->domain) {
 | 
			
		||||
			if (strcmp(ret->domain, rec->domain) != 0) {
 | 
			
		||||
				LOGP(DMSLOOKUP, LOGL_ERROR, "domain mismatch in resource records ('%s' vs '%s')\n",
 | 
			
		||||
				     ret->domain, rec->domain);
 | 
			
		||||
				goto error;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
			ret->domain = talloc_strdup(ret, rec->domain);
 | 
			
		||||
 | 
			
		||||
		/* Add simplified record to ret */
 | 
			
		||||
		ret_record = talloc_zero(ret, struct osmo_mdns_record);
 | 
			
		||||
		ret_record->type = rec->type;
 | 
			
		||||
		ret_record->length = rec->rdlength;
 | 
			
		||||
		ret_record->data = talloc_memdup(ret_record, rec->rdata, rec->rdlength);
 | 
			
		||||
		llist_add_tail(&ret_record->list, &ret->records);
 | 
			
		||||
 | 
			
		||||
		data += record_len;
 | 
			
		||||
		data_len -= record_len;
 | 
			
		||||
		talloc_free(rec);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Verify record count */
 | 
			
		||||
	if (llist_count(&ret->records) != hdr.ancount) {
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "amount of parsed records (%i) doesn't match count in header (%i)\n",
 | 
			
		||||
		     llist_count(&ret->records), hdr.ancount);
 | 
			
		||||
		goto error;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ret;
 | 
			
		||||
error:
 | 
			
		||||
	talloc_free(ret);
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Get a TXT resource record, which stores a key=value string.
 | 
			
		||||
 * \returns allocated resource record on success, NULL on error.
 | 
			
		||||
 */
 | 
			
		||||
static struct osmo_mdns_record *_osmo_mdns_record_txt_encode(void *ctx, const char *key, const char *value)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_record *ret = talloc_zero(ctx, struct osmo_mdns_record);
 | 
			
		||||
	size_t len = strlen(key) + 1 + strlen(value);
 | 
			
		||||
 | 
			
		||||
	if (len > OSMO_MDNS_RFC_MAX_CHARACTER_STRING_LEN - 1)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	/* redundant len is required, see RFC 1035 3.3.14 and 3.3. */
 | 
			
		||||
	ret->data = (uint8_t *)talloc_asprintf(ctx, "%c%s=%s", (char)len, key, value);
 | 
			
		||||
	if (!ret->data)
 | 
			
		||||
		return NULL;
 | 
			
		||||
	ret->type = OSMO_MDNS_RFC_RECORD_TYPE_TXT;
 | 
			
		||||
	ret->length = len + 1;
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Get a TXT resource record, which stores a key=value string, but build value from a format string.
 | 
			
		||||
 * \returns allocated resource record on success, NULL on error.
 | 
			
		||||
 */
 | 
			
		||||
struct osmo_mdns_record *osmo_mdns_record_txt_keyval_encode(void *ctx, const char *key, const char *value_fmt, ...)
 | 
			
		||||
{
 | 
			
		||||
	va_list ap;
 | 
			
		||||
	char *value = NULL;
 | 
			
		||||
	struct osmo_mdns_record *r;
 | 
			
		||||
 | 
			
		||||
	if (!value_fmt)
 | 
			
		||||
		return _osmo_mdns_record_txt_encode(ctx, key, "");
 | 
			
		||||
 | 
			
		||||
	va_start(ap, value_fmt);
 | 
			
		||||
	value = talloc_vasprintf(ctx, value_fmt, ap);
 | 
			
		||||
	if (!value)
 | 
			
		||||
		return NULL;
 | 
			
		||||
	va_end(ap);
 | 
			
		||||
	r = _osmo_mdns_record_txt_encode(ctx, key, value);
 | 
			
		||||
	talloc_free(value);
 | 
			
		||||
	return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Decode a TXT resource record, which stores a key=value string.
 | 
			
		||||
 * \returns 0 on success, -EINVAL on error.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_mdns_record_txt_keyval_decode(const struct osmo_mdns_record *rec,
 | 
			
		||||
				       char *key_buf, size_t key_size, char *value_buf, size_t value_size)
 | 
			
		||||
{
 | 
			
		||||
	const char *key_value;
 | 
			
		||||
	const char *key_value_end;
 | 
			
		||||
	const char *sep;
 | 
			
		||||
	const char *value;
 | 
			
		||||
 | 
			
		||||
	if (rec->type != OSMO_MDNS_RFC_RECORD_TYPE_TXT)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	key_value = (const char *)rec->data;
 | 
			
		||||
	key_value_end = key_value + rec->length;
 | 
			
		||||
 | 
			
		||||
	/* Verify and then skip the redundant string length byte */
 | 
			
		||||
	if (*key_value != rec->length - 1)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	key_value++;
 | 
			
		||||
 | 
			
		||||
	if (key_value >= key_value_end)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	/* Find equals sign */
 | 
			
		||||
	sep = osmo_strnchr(key_value, key_value_end - key_value, '=');
 | 
			
		||||
	if (!sep)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	/* Parse key */
 | 
			
		||||
	osmo_print_n(key_buf, key_size, key_value, sep - key_value);
 | 
			
		||||
 | 
			
		||||
	/* Parse value */
 | 
			
		||||
	value = sep + 1;
 | 
			
		||||
	osmo_print_n(value_buf, value_size, value, key_value_end - value);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										265
									
								
								src/mslookup/mdns_rfc.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										265
									
								
								src/mslookup/mdns_rfc.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,265 @@
 | 
			
		||||
/* Low level mDNS encoding and decoding functions of the qname IE, header/question sections and resource records,
 | 
			
		||||
 * as described in these RFCs:
 | 
			
		||||
 * - RFC 1035 (Domain names - implementation and specification)
 | 
			
		||||
 * - RFC 3596 (DNS Extensions to Support IP Version 6) */
 | 
			
		||||
 | 
			
		||||
/* Copyright 2019 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 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 <string.h>
 | 
			
		||||
#include <ctype.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/core/bitvec.h>
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/mslookup/mdns_rfc.h>
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Encode/decode IEs
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*! Encode a domain string as qname (RFC 1035 4.1.2).
 | 
			
		||||
 * \param[in] domain  multiple labels separated by dots, e.g. "sip.voice.1234.msisdn".
 | 
			
		||||
 * \returns allocated buffer with length-value pairs for each label (e.g. 0x03 "sip" 0x05 "voice" ...), NULL on error.
 | 
			
		||||
 */
 | 
			
		||||
char *osmo_mdns_rfc_qname_encode(void *ctx, const char *domain)
 | 
			
		||||
{
 | 
			
		||||
	char *domain_dup;
 | 
			
		||||
	char *domain_iter;
 | 
			
		||||
	char buf[OSMO_MDNS_RFC_MAX_NAME_LEN + 2] = ""; /* len(qname) is len(domain) +1 */
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };
 | 
			
		||||
	char *label;
 | 
			
		||||
 | 
			
		||||
	if (strlen(domain) > OSMO_MDNS_RFC_MAX_NAME_LEN)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	domain_iter = domain_dup = talloc_strdup(ctx, domain);
 | 
			
		||||
	while ((label = strsep(&domain_iter, "."))) {
 | 
			
		||||
		size_t len = strlen(label);
 | 
			
		||||
 | 
			
		||||
		/* Empty domain, dot at start, two dots in a row, or ending with a dot */
 | 
			
		||||
		if (!len)
 | 
			
		||||
			goto error;
 | 
			
		||||
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "%c%s", (char)len, label);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	talloc_free(domain_dup);
 | 
			
		||||
	return talloc_strdup(ctx, buf);
 | 
			
		||||
 | 
			
		||||
error:
 | 
			
		||||
	talloc_free(domain_dup);
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Decode a domain string from a qname (RFC 1035 4.1.2).
 | 
			
		||||
 * \param[in] qname  buffer with length-value pairs for each label (e.g. 0x03 "sip" 0x05 "voice" ...)
 | 
			
		||||
 * \param[in] qname_max_len  amount of bytes that can be read at most from the memory location that qname points to.
 | 
			
		||||
 * \returns allocated buffer with domain string, multiple labels separated by dots (e.g. "sip.voice.1234.msisdn"),
 | 
			
		||||
 *	    NULL on error.
 | 
			
		||||
 */
 | 
			
		||||
char *osmo_mdns_rfc_qname_decode(void *ctx, const char *qname, size_t qname_max_len)
 | 
			
		||||
{
 | 
			
		||||
	const char *next_label, *qname_end = qname + qname_max_len;
 | 
			
		||||
	char buf[OSMO_MDNS_RFC_MAX_NAME_LEN + 1];
 | 
			
		||||
	int i = 0;
 | 
			
		||||
 | 
			
		||||
	if (qname_max_len < 1)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	while (*qname) {
 | 
			
		||||
		size_t len = *qname;
 | 
			
		||||
		next_label = qname + len + 1;
 | 
			
		||||
 | 
			
		||||
		if (next_label >= qname_end || i + len > OSMO_MDNS_RFC_MAX_NAME_LEN)
 | 
			
		||||
			return NULL;
 | 
			
		||||
 | 
			
		||||
		if (i) {
 | 
			
		||||
			/* Two dots in a row is not allowed */
 | 
			
		||||
			if (buf[i - 1] == '.')
 | 
			
		||||
				return NULL;
 | 
			
		||||
 | 
			
		||||
			buf[i] = '.';
 | 
			
		||||
			i++;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		memcpy(buf + i, qname + 1, len);
 | 
			
		||||
		i += len;
 | 
			
		||||
		qname = next_label;
 | 
			
		||||
	}
 | 
			
		||||
	buf[i] = '\0';
 | 
			
		||||
 | 
			
		||||
	return talloc_strdup(ctx, buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Encode/decode message sections
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*! Encode header section (RFC 1035 4.1.1).
 | 
			
		||||
 * \param[in] msgb  mesage buffer to which the encoded data will be appended.
 | 
			
		||||
 */
 | 
			
		||||
void osmo_mdns_rfc_header_encode(struct msgb *msg, const struct osmo_mdns_rfc_header *hdr)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_rfc_header *buf = (struct osmo_mdns_rfc_header *) msgb_put(msg, sizeof(*hdr));
 | 
			
		||||
	memcpy(buf, hdr, sizeof(*hdr));
 | 
			
		||||
 | 
			
		||||
	osmo_store16be(buf->id, &buf->id);
 | 
			
		||||
	osmo_store16be(buf->qdcount, &buf->qdcount);
 | 
			
		||||
	osmo_store16be(buf->ancount, &buf->ancount);
 | 
			
		||||
	osmo_store16be(buf->nscount, &buf->nscount);
 | 
			
		||||
	osmo_store16be(buf->arcount, &buf->arcount);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Decode header section (RFC 1035 4.1.1). */
 | 
			
		||||
int osmo_mdns_rfc_header_decode(const uint8_t *data, size_t data_len, struct osmo_mdns_rfc_header *hdr)
 | 
			
		||||
{
 | 
			
		||||
	if (data_len != sizeof(*hdr))
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	memcpy(hdr, data, data_len);
 | 
			
		||||
 | 
			
		||||
	hdr->id = osmo_load16be(&hdr->id);
 | 
			
		||||
	hdr->qdcount = osmo_load16be(&hdr->qdcount);
 | 
			
		||||
	hdr->ancount = osmo_load16be(&hdr->ancount);
 | 
			
		||||
	hdr->nscount = osmo_load16be(&hdr->nscount);
 | 
			
		||||
	hdr->arcount = osmo_load16be(&hdr->arcount);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Encode question section (RFC 1035 4.1.2).
 | 
			
		||||
 * \param[in] msgb  mesage buffer to which the encoded data will be appended.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_mdns_rfc_question_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_rfc_question *qst)
 | 
			
		||||
{
 | 
			
		||||
	char *qname;
 | 
			
		||||
	size_t qname_len;
 | 
			
		||||
	uint8_t *qname_buf;
 | 
			
		||||
 | 
			
		||||
	/* qname */
 | 
			
		||||
	qname = osmo_mdns_rfc_qname_encode(ctx, qst->domain);
 | 
			
		||||
	if (!qname)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	qname_len = strlen(qname) + 1;
 | 
			
		||||
	qname_buf = msgb_put(msg, qname_len);
 | 
			
		||||
	memcpy(qname_buf, qname, qname_len);
 | 
			
		||||
	talloc_free(qname);
 | 
			
		||||
 | 
			
		||||
	/* qtype and qclass */
 | 
			
		||||
	msgb_put_u16(msg, qst->qtype);
 | 
			
		||||
	msgb_put_u16(msg, qst->qclass);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Decode question section (RFC 1035 4.1.2). */
 | 
			
		||||
struct osmo_mdns_rfc_question *osmo_mdns_rfc_question_decode(void *ctx, const uint8_t *data, size_t data_len)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_rfc_question *ret;
 | 
			
		||||
	size_t qname_len = data_len - 4;
 | 
			
		||||
 | 
			
		||||
	if (data_len < 6)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	/* qname */
 | 
			
		||||
	ret = talloc_zero(ctx, struct osmo_mdns_rfc_question);
 | 
			
		||||
	if (!ret)
 | 
			
		||||
		return NULL;
 | 
			
		||||
	ret->domain = osmo_mdns_rfc_qname_decode(ret, (const char *)data, qname_len);
 | 
			
		||||
	if (!ret->domain) {
 | 
			
		||||
		talloc_free(ret);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* qtype and qclass */
 | 
			
		||||
	ret->qtype = osmo_load16be(data + qname_len);
 | 
			
		||||
	ret->qclass = osmo_load16be(data + qname_len + 2);
 | 
			
		||||
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Encode/decode resource records
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*! Encode one resource record (RFC 1035 4.1.3).
 | 
			
		||||
 * \param[in] msgb  mesage buffer to which the encoded data will be appended.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_mdns_rfc_record_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_rfc_record *rec)
 | 
			
		||||
{
 | 
			
		||||
	char *name;
 | 
			
		||||
	size_t name_len;
 | 
			
		||||
	uint8_t *buf;
 | 
			
		||||
 | 
			
		||||
	/* name */
 | 
			
		||||
	name = osmo_mdns_rfc_qname_encode(ctx, rec->domain);
 | 
			
		||||
	if (!name)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	name_len = strlen(name) + 1;
 | 
			
		||||
	buf = msgb_put(msg, name_len);
 | 
			
		||||
	memcpy(buf, name, name_len);
 | 
			
		||||
	talloc_free(name);
 | 
			
		||||
 | 
			
		||||
	/* type, class, ttl, rdlength */
 | 
			
		||||
	msgb_put_u16(msg, rec->type);
 | 
			
		||||
	msgb_put_u16(msg, rec->class);
 | 
			
		||||
	msgb_put_u32(msg, rec->ttl);
 | 
			
		||||
	msgb_put_u16(msg, rec->rdlength);
 | 
			
		||||
 | 
			
		||||
	/* rdata */
 | 
			
		||||
	buf = msgb_put(msg, rec->rdlength);
 | 
			
		||||
	memcpy(buf, rec->rdata, rec->rdlength);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Decode one resource record (RFC 1035 4.1.3). */
 | 
			
		||||
struct osmo_mdns_rfc_record *osmo_mdns_rfc_record_decode(void *ctx, const uint8_t *data, size_t data_len,
 | 
			
		||||
						       size_t *record_len)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_rfc_record *ret = talloc_zero(ctx, struct osmo_mdns_rfc_record);
 | 
			
		||||
	size_t name_len;
 | 
			
		||||
 | 
			
		||||
	/* name */
 | 
			
		||||
	ret->domain = osmo_mdns_rfc_qname_decode(ret, (const char *)data, data_len - 10);
 | 
			
		||||
	if (!ret->domain)
 | 
			
		||||
		goto error;
 | 
			
		||||
	name_len = strlen(ret->domain) + 2;
 | 
			
		||||
	if (name_len + 10 > data_len)
 | 
			
		||||
		goto error;
 | 
			
		||||
 | 
			
		||||
	/* type, class, ttl, rdlength */
 | 
			
		||||
	ret->type = osmo_load16be(data + name_len);
 | 
			
		||||
	ret->class = osmo_load16be(data + name_len + 2);
 | 
			
		||||
	ret->ttl = osmo_load32be(data + name_len + 4);
 | 
			
		||||
	ret->rdlength = osmo_load16be(data + name_len + 8);
 | 
			
		||||
	if (name_len + 10 + ret->rdlength > data_len)
 | 
			
		||||
		goto error;
 | 
			
		||||
 | 
			
		||||
	/* rdata */
 | 
			
		||||
	ret->rdata = talloc_memdup(ret, data + name_len + 10, ret->rdlength);
 | 
			
		||||
	if (!ret->rdata)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	*record_len = name_len + 10 + ret->rdlength;
 | 
			
		||||
	return ret;
 | 
			
		||||
error:
 | 
			
		||||
	talloc_free(ret);
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										144
									
								
								src/mslookup/mdns_sock.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								src/mslookup/mdns_sock.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
/* Copyright 2019 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 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 <errno.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
#include <netinet/in.h>
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <talloc.h>
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/hlr/logging.h>
 | 
			
		||||
#include <osmocom/mslookup/mdns_sock.h>
 | 
			
		||||
 | 
			
		||||
/*! Open socket to send and receive multicast data.
 | 
			
		||||
 *
 | 
			
		||||
 * The socket is opened with SO_REUSEADDR, so we can bind to the same IP and port multiple times. This socket receives
 | 
			
		||||
 * everything sent to that multicast IP/port, including its own data data sent from osmo_mdns_sock_send(). So whenever
 | 
			
		||||
 * sending something, the receive callback will be called with the same data and should discard it.
 | 
			
		||||
 *
 | 
			
		||||
 * \param[in] ip  multicast IPv4 or IPv6 address.
 | 
			
		||||
 * \param[in] port  port number.
 | 
			
		||||
 * \param[in] cb  callback for incoming data that will be passed to osmo_fd_setup (should read from osmo_fd->fd).
 | 
			
		||||
 * \param[in] data  userdata passed to osmo_fd (available in cb as osmo_fd->data).
 | 
			
		||||
 * \param[in] priv_nr  additional userdata integer passed to osmo_fd (available in cb as osmo_fd->priv_nr).
 | 
			
		||||
 * \returns allocated osmo_mdns_sock, NULL on error.
 | 
			
		||||
 */
 | 
			
		||||
struct osmo_mdns_sock *osmo_mdns_sock_init(void *ctx, const char *ip, unsigned int port,
 | 
			
		||||
					   int (*cb)(struct osmo_fd *fd, unsigned int what),
 | 
			
		||||
					   void *data, unsigned int priv_nr)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_sock *ret;
 | 
			
		||||
	int sock, rc;
 | 
			
		||||
	struct addrinfo hints = {0};
 | 
			
		||||
	struct ip_mreq multicast_req = {0};
 | 
			
		||||
	in_addr_t iface = INADDR_ANY;
 | 
			
		||||
	char portbuf[10];
 | 
			
		||||
	int y = 1;
 | 
			
		||||
 | 
			
		||||
	snprintf(portbuf, sizeof(portbuf) -1, "%u", port);
 | 
			
		||||
	ret = talloc_zero(ctx, struct osmo_mdns_sock);
 | 
			
		||||
	OSMO_ASSERT(ret);
 | 
			
		||||
 | 
			
		||||
	/* Fill addrinfo */
 | 
			
		||||
	hints.ai_family = PF_UNSPEC;
 | 
			
		||||
	hints.ai_socktype = SOCK_DGRAM;
 | 
			
		||||
	hints.ai_flags = (AI_PASSIVE | AI_NUMERICHOST);
 | 
			
		||||
	rc = getaddrinfo(ip, portbuf, &hints, &ret->ai);
 | 
			
		||||
	if (rc != 0) {
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "osmo_mdns_sock_init: getaddrinfo: %s\n", gai_strerror(rc));
 | 
			
		||||
		ret->ai = NULL;
 | 
			
		||||
		goto error;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Open socket */
 | 
			
		||||
	sock = socket(ret->ai->ai_family, ret->ai->ai_socktype, 0);
 | 
			
		||||
	if (sock == -1) {
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "osmo_mdns_sock_init: socket: %s\n", strerror(errno));
 | 
			
		||||
		goto error;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Set multicast options */
 | 
			
		||||
	rc = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (char*)&iface, sizeof(iface));
 | 
			
		||||
	if (rc == -1) {
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "osmo_mdns_sock_init: setsockopt: %s\n", strerror(errno));
 | 
			
		||||
		goto error;
 | 
			
		||||
	}
 | 
			
		||||
	memcpy(&multicast_req.imr_multiaddr, &((struct sockaddr_in*)(ret->ai->ai_addr))->sin_addr,
 | 
			
		||||
	       sizeof(multicast_req.imr_multiaddr));
 | 
			
		||||
	multicast_req.imr_interface.s_addr = htonl(INADDR_ANY);
 | 
			
		||||
	rc = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&multicast_req, sizeof(multicast_req));
 | 
			
		||||
	if (rc == -1) {
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "osmo_mdns_sock_init: setsockopt: %s\n", strerror(errno));
 | 
			
		||||
		goto error;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Always allow binding the same IP and port twice. This is needed in OsmoHLR (where the code becomes cleaner by
 | 
			
		||||
	 * just using a different socket for server and client code) and in the mslookup_client_mdns_test. Also for
 | 
			
		||||
	 * osmo-mslookup-client if it is running multiple times in parallel (i.e. two incoming calls almost at the same
 | 
			
		||||
	 * time need to be resolved with the simple dialplan example that just starts new processes). */
 | 
			
		||||
	rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&y, sizeof(y));
 | 
			
		||||
	if (rc == -1) {
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "osmo_mdns_sock_init: setsockopt: %s\n", strerror(errno));
 | 
			
		||||
		goto error;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Bind and register osmo_fd callback */
 | 
			
		||||
	rc = bind(sock, ret->ai->ai_addr, ret->ai->ai_addrlen);
 | 
			
		||||
	if (rc == -1) {
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "osmo_mdns_sock_init: bind: %s\n", strerror(errno));
 | 
			
		||||
		goto error;
 | 
			
		||||
	}
 | 
			
		||||
	osmo_fd_setup(&ret->osmo_fd, sock, OSMO_FD_READ, cb, data, priv_nr);
 | 
			
		||||
	if (osmo_fd_register(&ret->osmo_fd) != 0)
 | 
			
		||||
		goto error;
 | 
			
		||||
 | 
			
		||||
	return ret;
 | 
			
		||||
error:
 | 
			
		||||
	if (ret->ai)
 | 
			
		||||
		freeaddrinfo(ret->ai);
 | 
			
		||||
	talloc_free(ret);
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Send msgb over mdns_sock and consume msgb.
 | 
			
		||||
 * \returns 0 on success, -1 on error.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_mdns_sock_send(const struct osmo_mdns_sock *mdns_sock, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	size_t len = msgb_length(msg);
 | 
			
		||||
	int rc = sendto(mdns_sock->osmo_fd.fd, msgb_data(msg), len, 0, mdns_sock->ai->ai_addr,
 | 
			
		||||
			mdns_sock->ai->ai_addrlen);
 | 
			
		||||
	msgb_free(msg);
 | 
			
		||||
	return (rc == len) ? 0 : -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Tear down osmo_mdns_sock. */
 | 
			
		||||
void osmo_mdns_sock_cleanup(struct osmo_mdns_sock *mdns_sock)
 | 
			
		||||
{
 | 
			
		||||
	osmo_fd_close(&mdns_sock->osmo_fd);
 | 
			
		||||
	freeaddrinfo(mdns_sock->ai);
 | 
			
		||||
	talloc_free(mdns_sock);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										321
									
								
								src/mslookup/mslookup.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										321
									
								
								src/mslookup/mslookup.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,321 @@
 | 
			
		||||
/* Copyright 2019 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 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 <string.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <osmocom/gsm/gsm23003.h>
 | 
			
		||||
#include <osmocom/mslookup/mslookup.h>
 | 
			
		||||
 | 
			
		||||
/*! \addtogroup mslookup
 | 
			
		||||
 *
 | 
			
		||||
 * Distributed GSM: finding subscribers
 | 
			
		||||
 *
 | 
			
		||||
 * There are various aspects of the D-GSM code base in osmo-hlr.git, here is an overview:
 | 
			
		||||
 *
 | 
			
		||||
 * mslookup is the main enabler of D-GSM, a concept for connecting services between independent core network stacks.
 | 
			
		||||
 *
 | 
			
		||||
 * D-GSM consists of:
 | 
			
		||||
 * (1) mslookup client to find subscribers:
 | 
			
		||||
 *     (a) external clients like ESME, SIP PBX, ... ask osmo-hlr to tell where to send SMS, voice calls, ...
 | 
			
		||||
 *     (b) osmo-hlr's own mslookup client asks remote osmo-hlrs whether they know a given IMSI.
 | 
			
		||||
 * (2) when a subscriber was found at a remote HLR, GSUP gets forwarded there:
 | 
			
		||||
 *     (a) to deliver messages for the GSUP proxy, osmo-hlr manages many GSUP clients to establish links to remote HLRs.
 | 
			
		||||
 *     (b) osmo-hlr has a GSUP proxy layer that caches data of IMSIs that get proxied to a remote HLR.
 | 
			
		||||
 *     (c) decision making to distinguish local IMSIs from ones proxied to a remote HLR.
 | 
			
		||||
 *
 | 
			
		||||
 * (1) mslookup is a method of finding subscribers using (multicast) queries, by MSISDN or by IMSI.
 | 
			
		||||
 * It is open to various lookup methods, the first one being multicast DNS.
 | 
			
		||||
 * An mslookup client sends a request, and an mslookup server responds.
 | 
			
		||||
 * The mslookup server is implemented by osmo-hlr. mslookup clients are arbitrary programs, like an ESME or a SIP PBX.
 | 
			
		||||
 * Hence the mslookup client is public API, while the mslookup server is implemented "privately" in osmo-hlr.
 | 
			
		||||
 *
 | 
			
		||||
 * (1a) Public mslookup client: libosmo-mslookup
 | 
			
		||||
 *   src/mslookup/mslookup.c               Things useful for both client and server.
 | 
			
		||||
 *   src/mslookup/mslookup_client.c        The client API, which can use various lookup methods,
 | 
			
		||||
 *                                         and consolidates results from various responders.
 | 
			
		||||
 *   src/mslookup/mslookup_client_mdns.c   lookup method implementing multicast DNS, client side.
 | 
			
		||||
 *
 | 
			
		||||
 *   src/mslookup/osmo-mslookup-client.c   Utility program to ease invocation for (blocking) mslookup clients.
 | 
			
		||||
 *
 | 
			
		||||
 *   src/mslookup/mslookup_client_fake.c   lookup method generating fake results, for testing client implementations.
 | 
			
		||||
 *
 | 
			
		||||
 *   src/mslookup/mdns*.c                  implementation of DNS to be used by mslookup_client_mdns.c,
 | 
			
		||||
 *                                         and the mslookup_server.c.
 | 
			
		||||
 *
 | 
			
		||||
 *   contrib/dgsm/esme_dgsm.py                 Example implementation for an mslookup enabled SMS handler.
 | 
			
		||||
 *   contrib/dgsm/freeswitch_dialplan_dgsm.py  Example implementation for an mslookup enabled FreeSWITCH dialplan.
 | 
			
		||||
 *   contrib/dgsm/osmo-mslookup-pipe.py        Example for writing a python client using the osmo-mslookup-client
 | 
			
		||||
 *                                             cmdline.
 | 
			
		||||
 *   contrib/dgsm/osmo-mslookup-socket.py      Example for writing a python client using the osmo-mslookup-client
 | 
			
		||||
 *                                             unix domain socket.
 | 
			
		||||
 *
 | 
			
		||||
 * (1b) "Private" mslookup server in osmo-hlr:
 | 
			
		||||
 *   src/mslookup_server.c        Respond to mslookup queries, independent from the particular lookup method.
 | 
			
		||||
 *   src/mslookup_server_mdns.c   mDNS specific implementation for mslookup_server.c.
 | 
			
		||||
 *   src/dgsm_vty.c               Configure services that mslookup server sends to remote requests.
 | 
			
		||||
 *
 | 
			
		||||
 * (2) Proxy and GSUP clients to remote HLR instances:
 | 
			
		||||
 *
 | 
			
		||||
 * (a) Be a GSUP client to forward to a remote HLR:
 | 
			
		||||
 *  src/gsupclient/   The same API that is used by osmo-{msc,sgsn} is also used to forward GSUP to remote osmo-hlrs.
 | 
			
		||||
 *  src/remote_hlr.c  Establish links to remote osmo-hlrs, where this osmo-hlr is a client (proxying e.g. for an MSC).
 | 
			
		||||
 *
 | 
			
		||||
 * (b) Keep track of remotely handled IMSIs:
 | 
			
		||||
 *  src/proxy.c       Keep track of proxied IMSIs and cache important subscriber data.
 | 
			
		||||
 *
 | 
			
		||||
 * (c) Direct GSUP request to the right destination: either the local or a remote HLR:
 | 
			
		||||
 *  src/dgsm.c        The glue that makes osmo-hlr distinguish between local IMSIs and those that are proxied to another
 | 
			
		||||
 *                    osmo-hlr.
 | 
			
		||||
 *  src/dgsm_vty.c    Config.
 | 
			
		||||
 *
 | 
			
		||||
 *  @{
 | 
			
		||||
 * \file mslookup.c
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const struct value_string osmo_mslookup_id_type_names[] = {
 | 
			
		||||
	{ OSMO_MSLOOKUP_ID_NONE, "none" },
 | 
			
		||||
	{ OSMO_MSLOOKUP_ID_IMSI, "imsi" },
 | 
			
		||||
	{ OSMO_MSLOOKUP_ID_MSISDN, "msisdn" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct value_string osmo_mslookup_result_code_names[] = {
 | 
			
		||||
	{ OSMO_MSLOOKUP_RC_NONE, "none" },
 | 
			
		||||
	{ OSMO_MSLOOKUP_RC_RESULT, "result" },
 | 
			
		||||
	{ OSMO_MSLOOKUP_RC_NOT_FOUND, "not-found" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*! Compare two struct osmo_mslookup_id.
 | 
			
		||||
 * \returns   0 if a and b are equal,
 | 
			
		||||
 *          < 0 if a (or the ID type / start of ID) is < b,
 | 
			
		||||
 *          > 0 if a (or the ID type / start of ID) is > b.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_mslookup_id_cmp(const struct osmo_mslookup_id *a, const struct osmo_mslookup_id *b)
 | 
			
		||||
{
 | 
			
		||||
	int cmp;
 | 
			
		||||
	if (a == b)
 | 
			
		||||
		return 0;
 | 
			
		||||
	if (!a)
 | 
			
		||||
		return -1;
 | 
			
		||||
	if (!b)
 | 
			
		||||
		return 1;
 | 
			
		||||
 | 
			
		||||
	cmp = OSMO_CMP(a->type, b->type);
 | 
			
		||||
	if (cmp)
 | 
			
		||||
		return cmp;
 | 
			
		||||
 | 
			
		||||
	switch (a->type) {
 | 
			
		||||
	case OSMO_MSLOOKUP_ID_IMSI:
 | 
			
		||||
		return strncmp(a->imsi, b->imsi, sizeof(a->imsi));
 | 
			
		||||
	case OSMO_MSLOOKUP_ID_MSISDN:
 | 
			
		||||
		return strncmp(a->msisdn, b->msisdn, sizeof(a->msisdn));
 | 
			
		||||
	default:
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool osmo_mslookup_id_valid(const struct osmo_mslookup_id *id)
 | 
			
		||||
{
 | 
			
		||||
	switch (id->type) {
 | 
			
		||||
	case OSMO_MSLOOKUP_ID_IMSI:
 | 
			
		||||
		return osmo_imsi_str_valid(id->imsi);
 | 
			
		||||
	case OSMO_MSLOOKUP_ID_MSISDN:
 | 
			
		||||
		return osmo_msisdn_str_valid(id->msisdn);
 | 
			
		||||
	default:
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool osmo_mslookup_service_valid(const char *service)
 | 
			
		||||
{
 | 
			
		||||
	return strlen(service) > 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Write ID and ID type to a buffer.
 | 
			
		||||
 * \param[out] buf  nul-terminated {id}.{id_type} string (e.g. "1234.msisdn") or
 | 
			
		||||
* 		    "?.none" if the ID type is invalid.
 | 
			
		||||
 * \returns amount of bytes written to buf.
 | 
			
		||||
 */
 | 
			
		||||
size_t osmo_mslookup_id_name_buf(char *buf, size_t buflen, const struct osmo_mslookup_id *id)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	switch (id->type) {
 | 
			
		||||
	case OSMO_MSLOOKUP_ID_IMSI:
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "%s", id->imsi);
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_MSLOOKUP_ID_MSISDN:
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "%s", id->msisdn);
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "?");
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, ".%s", osmo_mslookup_id_type_name(id->type));
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Same as osmo_mslookup_id_name_buf(), but return a talloc allocated string of sufficient size. */
 | 
			
		||||
char *osmo_mslookup_id_name_c(void *ctx, const struct osmo_mslookup_id *id)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_mslookup_id_name_buf, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Same as osmo_mslookup_id_name_buf(), but directly return the char* (for printf-like string formats). */
 | 
			
		||||
char *osmo_mslookup_id_name_b(char *buf, size_t buflen, const struct osmo_mslookup_id *id)
 | 
			
		||||
{
 | 
			
		||||
	int rc = osmo_mslookup_id_name_buf(buf, buflen, id);
 | 
			
		||||
	if (rc < 0 && buflen)
 | 
			
		||||
		buf[0] = '\0';
 | 
			
		||||
	return buf;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Write mslookup result string to buffer.
 | 
			
		||||
 * \param[in] query  with the service, ID and ID type to be written to buf like a domain string, or NULL to omit.
 | 
			
		||||
 * \param[in] result with the result code, IPv4/v6 and age to be written to buf or NULL to omit.
 | 
			
		||||
 * \param[out] buf  result as flat string, which looks like the following for a valid query and result with IPv4 and v6
 | 
			
		||||
 *                  answer: "sip.voice.1234.msisdn -> ipv4: 42.42.42.42:1337 -> ipv6: [1234:5678:9ABC::]:1338 (age=1)",
 | 
			
		||||
 *                  the result part can also be " -> timeout" or " -> rc=5" depending on the result code.
 | 
			
		||||
 * \returns amount of bytes written to buf.
 | 
			
		||||
 */
 | 
			
		||||
size_t osmo_mslookup_result_to_str_buf(char *buf, size_t buflen,
 | 
			
		||||
				     const struct osmo_mslookup_query *query,
 | 
			
		||||
				     const struct osmo_mslookup_result *result)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	if (query) {
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "%s.", query->service);
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_mslookup_id_name_buf, &query->id);
 | 
			
		||||
	}
 | 
			
		||||
	if (result && result->rc == OSMO_MSLOOKUP_RC_NONE)
 | 
			
		||||
		result = NULL;
 | 
			
		||||
	if (result) {
 | 
			
		||||
		if (result->rc != OSMO_MSLOOKUP_RC_RESULT) {
 | 
			
		||||
			OSMO_STRBUF_PRINTF(sb, " %s", osmo_mslookup_result_code_name(result->rc));
 | 
			
		||||
		} else {
 | 
			
		||||
			if (result->host_v4.ip[0]) {
 | 
			
		||||
				OSMO_STRBUF_PRINTF(sb, " -> ipv4: " OSMO_SOCKADDR_STR_FMT,
 | 
			
		||||
						   OSMO_SOCKADDR_STR_FMT_ARGS(&result->host_v4));
 | 
			
		||||
			}
 | 
			
		||||
			if (result->host_v6.ip[0]) {
 | 
			
		||||
				OSMO_STRBUF_PRINTF(sb, " -> ipv6: " OSMO_SOCKADDR_STR_FMT,
 | 
			
		||||
						   OSMO_SOCKADDR_STR_FMT_ARGS(&result->host_v6));
 | 
			
		||||
			}
 | 
			
		||||
			OSMO_STRBUF_PRINTF(sb, " (age=%u)", result->age);
 | 
			
		||||
		}
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " %s", result->last ? "(last)" : "(not-last)");
 | 
			
		||||
	}
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Same as osmo_mslookup_result_to_str_buf(), but return a talloc allocated string of sufficient size. */
 | 
			
		||||
char *osmo_mslookup_result_name_c(void *ctx,
 | 
			
		||||
				  const struct osmo_mslookup_query *query,
 | 
			
		||||
				  const struct osmo_mslookup_result *result)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_mslookup_result_to_str_buf, query, result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Same as osmo_mslookup_result_to_str_buf(), but directly return the char* (for printf-like string formats). */
 | 
			
		||||
char *osmo_mslookup_result_name_b(char *buf, size_t buflen,
 | 
			
		||||
				  const struct osmo_mslookup_query *query,
 | 
			
		||||
				  const struct osmo_mslookup_result *result)
 | 
			
		||||
{
 | 
			
		||||
	int rc = osmo_mslookup_result_to_str_buf(buf, buflen, query, result);
 | 
			
		||||
	if (rc < 0 && buflen)
 | 
			
		||||
		buf[0] = '\0';
 | 
			
		||||
	return buf;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Copy part of a string to a buffer and nul-terminate it.
 | 
			
		||||
 * \returns 0 on success, negative on error.
 | 
			
		||||
 */
 | 
			
		||||
static int token(char *dest, size_t dest_size, const char *start, const char *end)
 | 
			
		||||
{
 | 
			
		||||
	int len;
 | 
			
		||||
	if (start >= end)
 | 
			
		||||
		return -10;
 | 
			
		||||
	len = end - start;
 | 
			
		||||
	if (len >= dest_size)
 | 
			
		||||
		return -11;
 | 
			
		||||
	strncpy(dest, start, len);
 | 
			
		||||
	dest[len] = '\0';
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Parse a string like "foo.moo.goo.123456789012345.msisdn" into service="foo.moo.goo", id="123456789012345" and
 | 
			
		||||
 * id_type="msisdn", placed in a struct osmo_mslookup_query.
 | 
			
		||||
 * \param q  Write parsed query to this osmo_mslookup_query.
 | 
			
		||||
 * \param domain  Human readable domain string like "sip.voice.12345678.msisdn".
 | 
			
		||||
 * \returns 0 on success, negative on error.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_mslookup_query_init_from_domain_str(struct osmo_mslookup_query *q, const char *domain)
 | 
			
		||||
{
 | 
			
		||||
	const char *last_dot;
 | 
			
		||||
	const char *second_last_dot;
 | 
			
		||||
	const char *id_type;
 | 
			
		||||
	const char *id;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	*q = (struct osmo_mslookup_query){};
 | 
			
		||||
 | 
			
		||||
	if (!domain)
 | 
			
		||||
		return -1;
 | 
			
		||||
 | 
			
		||||
	last_dot = strrchr(domain, '.');
 | 
			
		||||
 | 
			
		||||
	if (!last_dot)
 | 
			
		||||
		return -2;
 | 
			
		||||
 | 
			
		||||
	if (last_dot <= domain)
 | 
			
		||||
		return -3;
 | 
			
		||||
 | 
			
		||||
	for (second_last_dot = last_dot - 1; second_last_dot > domain && *second_last_dot != '.'; second_last_dot--);
 | 
			
		||||
	if (second_last_dot == domain || *second_last_dot != '.')
 | 
			
		||||
		return -3;
 | 
			
		||||
 | 
			
		||||
	id_type = last_dot + 1;
 | 
			
		||||
	if (!*id_type)
 | 
			
		||||
		return -4;
 | 
			
		||||
 | 
			
		||||
	q->id.type = get_string_value(osmo_mslookup_id_type_names, id_type);
 | 
			
		||||
 | 
			
		||||
	id = second_last_dot + 1;
 | 
			
		||||
	switch (q->id.type) {
 | 
			
		||||
	case OSMO_MSLOOKUP_ID_IMSI:
 | 
			
		||||
		rc = token(q->id.imsi, sizeof(q->id.imsi), id, last_dot);
 | 
			
		||||
		if (rc)
 | 
			
		||||
			return rc;
 | 
			
		||||
		if (!osmo_imsi_str_valid(q->id.imsi))
 | 
			
		||||
			return -5;
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_MSLOOKUP_ID_MSISDN:
 | 
			
		||||
		rc = token(q->id.msisdn, sizeof(q->id.msisdn), id, last_dot);
 | 
			
		||||
		if (rc)
 | 
			
		||||
			return rc;
 | 
			
		||||
		if (!osmo_msisdn_str_valid(q->id.msisdn))
 | 
			
		||||
			return -6;
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		return -7;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return token(q->service, sizeof(q->service), domain, second_last_dot);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! @} */
 | 
			
		||||
							
								
								
									
										310
									
								
								src/mslookup/mslookup_client.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										310
									
								
								src/mslookup/mslookup_client.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,310 @@
 | 
			
		||||
/* Copyright 2019 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 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/hlr/logging.h>
 | 
			
		||||
#include <osmocom/mslookup/mslookup_client.h>
 | 
			
		||||
 | 
			
		||||
/*! Lookup client's internal data for a query. */
 | 
			
		||||
struct osmo_mslookup_client {
 | 
			
		||||
	struct llist_head lookup_methods;
 | 
			
		||||
	struct llist_head requests;
 | 
			
		||||
	uint32_t next_request_handle;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*! Lookup client's internal data for a query.
 | 
			
		||||
 * The request methods only get to see the query part, and result handling is done commonly for all request methods. */
 | 
			
		||||
struct osmo_mslookup_client_request {
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
	struct osmo_mslookup_client *client;
 | 
			
		||||
	uint32_t request_handle;
 | 
			
		||||
 | 
			
		||||
	struct osmo_mslookup_query query;
 | 
			
		||||
	struct osmo_mslookup_query_handling handling;
 | 
			
		||||
	struct osmo_timer_list timeout;
 | 
			
		||||
	bool waiting_min_delay;
 | 
			
		||||
 | 
			
		||||
	struct osmo_mslookup_result result;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct osmo_mslookup_client_request *get_request(struct osmo_mslookup_client *client, uint32_t request_handle)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mslookup_client_request *r;
 | 
			
		||||
	if (!request_handle)
 | 
			
		||||
		return NULL;
 | 
			
		||||
	llist_for_each_entry(r, &client->requests, entry) {
 | 
			
		||||
		if (r->request_handle == request_handle)
 | 
			
		||||
			return r;
 | 
			
		||||
	}
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct osmo_mslookup_client *osmo_mslookup_client_new(void *ctx)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mslookup_client *client = talloc_zero(ctx, struct osmo_mslookup_client);
 | 
			
		||||
	OSMO_ASSERT(client);
 | 
			
		||||
	INIT_LLIST_HEAD(&client->lookup_methods);
 | 
			
		||||
	INIT_LLIST_HEAD(&client->requests);
 | 
			
		||||
	return client;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Return whether any lookup methods are available.
 | 
			
		||||
 * \param[in] client  Client to query.
 | 
			
		||||
 * \return true when a client is present that has at least one osmo_mslookup_client_method registered.
 | 
			
		||||
 */
 | 
			
		||||
bool osmo_mslookup_client_active(struct osmo_mslookup_client *client)
 | 
			
		||||
{
 | 
			
		||||
	if (!client)
 | 
			
		||||
		return false;
 | 
			
		||||
	if (llist_empty(&client->lookup_methods))
 | 
			
		||||
		return false;
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void _osmo_mslookup_client_method_del(struct osmo_mslookup_client_method *method)
 | 
			
		||||
{
 | 
			
		||||
	if (method->destruct)
 | 
			
		||||
		method->destruct(method);
 | 
			
		||||
	llist_del(&method->entry);
 | 
			
		||||
	talloc_free(method);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Stop and free mslookup client and all registered lookup methods.
 | 
			
		||||
 */
 | 
			
		||||
void osmo_mslookup_client_free(struct osmo_mslookup_client *client)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mslookup_client_method *m, *n;
 | 
			
		||||
	if (!client)
 | 
			
		||||
		return;
 | 
			
		||||
	llist_for_each_entry_safe(m, n, &client->lookup_methods, entry) {
 | 
			
		||||
		_osmo_mslookup_client_method_del(m);
 | 
			
		||||
	}
 | 
			
		||||
	talloc_free(client);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Add an osmo_mslookup_client_method to service MS Lookup requests.
 | 
			
		||||
 * Note, osmo_mslookup_client_method_del() will talloc_free() the method pointer, so it needs to be dynamically
 | 
			
		||||
 * allocated.
 | 
			
		||||
 * \param client  The osmo_mslookup_client instance to add to.
 | 
			
		||||
 * \param method  A fully initialized method struct, allocated by talloc.
 | 
			
		||||
 */
 | 
			
		||||
void osmo_mslookup_client_method_add(struct osmo_mslookup_client *client,
 | 
			
		||||
				     struct osmo_mslookup_client_method *method)
 | 
			
		||||
{
 | 
			
		||||
	method->client = client;
 | 
			
		||||
	llist_add_tail(&method->entry, &client->lookup_methods);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! \return false if the method was not listed, true if the method was listed, removed and talloc_free()d.
 | 
			
		||||
 */
 | 
			
		||||
bool osmo_mslookup_client_method_del(struct osmo_mslookup_client *client,
 | 
			
		||||
				     struct osmo_mslookup_client_method *method)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mslookup_client_method *m;
 | 
			
		||||
	llist_for_each_entry(m, &client->lookup_methods, entry) {
 | 
			
		||||
		if (m == method) {
 | 
			
		||||
			_osmo_mslookup_client_method_del(method);
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void osmo_mslookup_request_send_result(struct osmo_mslookup_client_request *r, bool finish)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mslookup_client *client = r->client;
 | 
			
		||||
	uint32_t request_handle = r->request_handle;
 | 
			
		||||
 | 
			
		||||
	r->result.last = finish;
 | 
			
		||||
	r->handling.result_cb(r->client, r->request_handle, &r->query, &r->result);
 | 
			
		||||
 | 
			
		||||
	/* Make sure the request struct is discarded.
 | 
			
		||||
	 * The result_cb() may already have triggered a cleanup, so query by request_handle. */
 | 
			
		||||
	if (finish)
 | 
			
		||||
		osmo_mslookup_client_request_cancel(client, request_handle);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void osmo_mslookup_client_rx_result(struct osmo_mslookup_client *client, uint32_t request_handle,
 | 
			
		||||
				    const struct osmo_mslookup_result *result)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mslookup_client_request *req = get_request(client, request_handle);
 | 
			
		||||
 | 
			
		||||
	if (!req) {
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR,
 | 
			
		||||
		     "Internal error: Got mslookup result for a request that does not exist (handle %u)\n",
 | 
			
		||||
		     req->request_handle);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Ignore incoming results that are not successful */
 | 
			
		||||
	if (result->rc != OSMO_MSLOOKUP_RC_RESULT)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	/* If we already stored an earlier successful result, keep that if its age is younger. */
 | 
			
		||||
	if (req->result.rc == OSMO_MSLOOKUP_RC_RESULT
 | 
			
		||||
	    && result->age >= req->result.age)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	req->result = *result;
 | 
			
		||||
 | 
			
		||||
	/* If age == 0, it doesn't get any better, so return the result immediately. */
 | 
			
		||||
	if (req->result.age == 0) {
 | 
			
		||||
		osmo_mslookup_request_send_result(req, true);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (req->waiting_min_delay)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	osmo_mslookup_request_send_result(req, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void _osmo_mslookup_client_request_cleanup(struct osmo_mslookup_client_request *r)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mslookup_client_method *m;
 | 
			
		||||
	osmo_timer_del(&r->timeout);
 | 
			
		||||
	llist_for_each_entry(m, &r->client->lookup_methods, entry) {
 | 
			
		||||
		if (!m->request_cleanup)
 | 
			
		||||
			continue;
 | 
			
		||||
		m->request_cleanup(m, r->request_handle);
 | 
			
		||||
	}
 | 
			
		||||
	llist_del(&r->entry);
 | 
			
		||||
	talloc_free(r);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void timeout_cb(void *data);
 | 
			
		||||
 | 
			
		||||
static void set_timer(struct osmo_mslookup_client_request *r, unsigned long milliseconds)
 | 
			
		||||
{
 | 
			
		||||
	osmo_timer_setup(&r->timeout, timeout_cb, r);
 | 
			
		||||
	osmo_timer_schedule(&r->timeout, milliseconds / 1000, (milliseconds % 1000) * 1000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void timeout_cb(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mslookup_client_request *r = data;
 | 
			
		||||
	if (r->waiting_min_delay) {
 | 
			
		||||
		/* The initial delay has passed. See if it stops here, or whether the overall timeout continues. */
 | 
			
		||||
		r->waiting_min_delay = false;
 | 
			
		||||
 | 
			
		||||
		if (r->handling.result_timeout_milliseconds <= r->handling.min_wait_milliseconds) {
 | 
			
		||||
			/* It ends here. Return a final result. */
 | 
			
		||||
			if (r->result.rc != OSMO_MSLOOKUP_RC_RESULT)
 | 
			
		||||
				r->result.rc = OSMO_MSLOOKUP_RC_NOT_FOUND;
 | 
			
		||||
			osmo_mslookup_request_send_result(r, true);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* We continue to listen for results. If one is already on record, send it now. */
 | 
			
		||||
		if (r->result.rc == OSMO_MSLOOKUP_RC_RESULT)
 | 
			
		||||
			osmo_mslookup_request_send_result(r, false);
 | 
			
		||||
 | 
			
		||||
		set_timer(r, r->handling.result_timeout_milliseconds - r->handling.min_wait_milliseconds);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	/* The final timeout has passed, finish and clean up the request. */
 | 
			
		||||
	switch (r->result.rc) {
 | 
			
		||||
	case OSMO_MSLOOKUP_RC_RESULT:
 | 
			
		||||
		/* If the rc == OSMO_MSLOOKUP_RC_RESULT, this result has already been sent.
 | 
			
		||||
		 * Don't send it again, instead send an RC_NONE, last=true result. */
 | 
			
		||||
		r->result.rc = OSMO_MSLOOKUP_RC_NONE;
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		r->result.rc = OSMO_MSLOOKUP_RC_NOT_FOUND;
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
	osmo_mslookup_request_send_result(r, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Launch a subscriber lookup with the provided query.
 | 
			
		||||
 * A request is cleared implicitly when the handling->result_cb is invoked; if the quer->priv pointer becomes invalid
 | 
			
		||||
 * before that, a request should be canceled by calling osmo_mslookup_client_request_cancel() with the returned
 | 
			
		||||
 * request_handle. A request handle of zero indicates error.
 | 
			
		||||
 * \return a nonzero request_handle that allows ending the request, or 0 on invalid query data. */
 | 
			
		||||
uint32_t osmo_mslookup_client_request(struct osmo_mslookup_client *client,
 | 
			
		||||
				      const struct osmo_mslookup_query *query,
 | 
			
		||||
				      const struct osmo_mslookup_query_handling *handling)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mslookup_client_request *r;
 | 
			
		||||
	struct osmo_mslookup_client_request *other;
 | 
			
		||||
	struct osmo_mslookup_client_method *m;
 | 
			
		||||
 | 
			
		||||
	if (!osmo_mslookup_service_valid(query->service)
 | 
			
		||||
	    || !osmo_mslookup_id_valid(&query->id)) {
 | 
			
		||||
		char buf[256];
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "Invalid query: %s\n",
 | 
			
		||||
		     osmo_mslookup_result_name_b(buf, sizeof(buf), query, NULL));
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r = talloc_zero(client, struct osmo_mslookup_client_request);
 | 
			
		||||
	OSMO_ASSERT(r);
 | 
			
		||||
 | 
			
		||||
	/* A request_handle of zero means error, so make sure we don't use a zero handle. */
 | 
			
		||||
	if (!client->next_request_handle)
 | 
			
		||||
		client->next_request_handle++;
 | 
			
		||||
	*r = (struct osmo_mslookup_client_request){
 | 
			
		||||
		.client = client,
 | 
			
		||||
		.query = *query,
 | 
			
		||||
		.handling = *handling,
 | 
			
		||||
		.request_handle = client->next_request_handle++,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (!r->handling.result_timeout_milliseconds)
 | 
			
		||||
		r->handling.result_timeout_milliseconds = r->handling.min_wait_milliseconds;
 | 
			
		||||
	if (!r->handling.result_timeout_milliseconds)
 | 
			
		||||
		r->handling.result_timeout_milliseconds = 1000;
 | 
			
		||||
 | 
			
		||||
	/* Paranoia: make sure a request_handle exists only once, by expiring an already existing one. This is unlikely
 | 
			
		||||
	 * to happen in practice: before we get near wrapping a uint32_t range, previous requests should long have
 | 
			
		||||
	 * timed out or ended. */
 | 
			
		||||
	llist_for_each_entry(other, &client->requests, entry) {
 | 
			
		||||
		if (other->request_handle != r->request_handle)
 | 
			
		||||
			continue;
 | 
			
		||||
		osmo_mslookup_request_send_result(other, true);
 | 
			
		||||
		/* we're sure it exists only once. */
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Now sure that the new request_handle does not exist a second time. */
 | 
			
		||||
	llist_add_tail(&r->entry, &client->requests);
 | 
			
		||||
 | 
			
		||||
	if (r->handling.min_wait_milliseconds) {
 | 
			
		||||
		r->waiting_min_delay = true;
 | 
			
		||||
		set_timer(r, r->handling.min_wait_milliseconds);
 | 
			
		||||
	} else {
 | 
			
		||||
		set_timer(r, r->handling.result_timeout_milliseconds);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Let the lookup implementations know */
 | 
			
		||||
	llist_for_each_entry(m, &client->lookup_methods, entry) {
 | 
			
		||||
		m->request(m, query, r->request_handle);
 | 
			
		||||
	}
 | 
			
		||||
	return r->request_handle;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! End or cancel a subscriber lookup. This *must* be invoked exactly once per osmo_mslookup_client_request() invocation,
 | 
			
		||||
 * either after a lookup has concluded or to abort an ongoing lookup.
 | 
			
		||||
 * \param[in] request_handle  The request_handle returned by an osmo_mslookup_client_request() invocation.
 | 
			
		||||
 */
 | 
			
		||||
void osmo_mslookup_client_request_cancel(struct osmo_mslookup_client *client, uint32_t request_handle)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mslookup_client_request *r = get_request(client, request_handle);
 | 
			
		||||
	if (!r)
 | 
			
		||||
		return;
 | 
			
		||||
	_osmo_mslookup_client_request_cleanup(r);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										156
									
								
								src/mslookup/mslookup_client_fake.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								src/mslookup/mslookup_client_fake.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,156 @@
 | 
			
		||||
/* Copyright 2019 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 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/hlr/logging.h>
 | 
			
		||||
#include <osmocom/mslookup/mslookup_client.h>
 | 
			
		||||
#include <osmocom/mslookup/mslookup_client_fake.h>
 | 
			
		||||
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
/* Fake mslookup method */
 | 
			
		||||
 | 
			
		||||
struct fake_lookup_state {
 | 
			
		||||
	struct osmo_mslookup_client *client;
 | 
			
		||||
	struct llist_head requests;
 | 
			
		||||
	struct osmo_timer_list async_response_timer;
 | 
			
		||||
	struct osmo_mslookup_fake_response *responses;
 | 
			
		||||
	size_t responses_len;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct fake_lookup_request {
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
	uint32_t request_handle;
 | 
			
		||||
	struct osmo_mslookup_query query;
 | 
			
		||||
	struct timeval received_at;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*! Args for osmo_timer_schedule: seconds and microseconds. */
 | 
			
		||||
#define ASYNC_RESPONSE_PERIOD 0, (1e6 / 10)
 | 
			
		||||
static void fake_lookup_async_response(void *state);
 | 
			
		||||
 | 
			
		||||
static void fake_lookup_request(struct osmo_mslookup_client_method *method,
 | 
			
		||||
				const struct osmo_mslookup_query *query,
 | 
			
		||||
				uint32_t request_handle)
 | 
			
		||||
{
 | 
			
		||||
	struct fake_lookup_state *state = method->priv;
 | 
			
		||||
	char buf[256];
 | 
			
		||||
	LOGP(DMSLOOKUP, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_mslookup_result_name_b(buf, sizeof(buf), query, NULL));
 | 
			
		||||
 | 
			
		||||
	/* A real implementation would send packets to some remote server.
 | 
			
		||||
	 * Here this is simulated: add to the list of requests, which fake_lookup_async_response() will reply upon
 | 
			
		||||
	 * according to the test data listing the replies that the test wants to generate. */
 | 
			
		||||
 | 
			
		||||
	struct fake_lookup_request *r = talloc_zero(method->client, struct fake_lookup_request);
 | 
			
		||||
	*r = (struct fake_lookup_request){
 | 
			
		||||
		.request_handle = request_handle,
 | 
			
		||||
		.query = *query,
 | 
			
		||||
	};
 | 
			
		||||
	osmo_gettimeofday(&r->received_at, NULL);
 | 
			
		||||
	llist_add_tail(&r->entry, &state->requests);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void fake_lookup_request_cleanup(struct osmo_mslookup_client_method *method,
 | 
			
		||||
					uint32_t request_handle)
 | 
			
		||||
{
 | 
			
		||||
	struct fake_lookup_state *state = method->priv;
 | 
			
		||||
 | 
			
		||||
	/* Tear down any state associated with this handle. */
 | 
			
		||||
	struct fake_lookup_request *r;
 | 
			
		||||
	llist_for_each_entry(r, &state->requests, entry) {
 | 
			
		||||
		if (r->request_handle != request_handle)
 | 
			
		||||
			continue;
 | 
			
		||||
		llist_del(&r->entry);
 | 
			
		||||
		talloc_free(r);
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_DEBUG, "%s() ok\n", __func__);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	LOGP(DMSLOOKUP, LOGL_DEBUG, "%s() FAILED\n", __func__);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void fake_lookup_async_response(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct fake_lookup_state *state = data;
 | 
			
		||||
	struct fake_lookup_request *req, *n;
 | 
			
		||||
	struct timeval now;
 | 
			
		||||
	char str[256];
 | 
			
		||||
 | 
			
		||||
	osmo_gettimeofday(&now, NULL);
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry_safe(req, n, &state->requests, entry) {
 | 
			
		||||
		struct osmo_mslookup_fake_response *resp;
 | 
			
		||||
 | 
			
		||||
		for (resp = state->responses;
 | 
			
		||||
		     (resp - state->responses) < state->responses_len;
 | 
			
		||||
		     resp++) {
 | 
			
		||||
			struct timeval diff;
 | 
			
		||||
 | 
			
		||||
			if (resp->sent)
 | 
			
		||||
				continue;
 | 
			
		||||
			if (osmo_mslookup_id_cmp(&req->query.id, &resp->for_id) != 0)
 | 
			
		||||
				continue;
 | 
			
		||||
			if (strcmp(req->query.service, resp->for_service) != 0)
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			timersub(&now, &req->received_at, &diff);
 | 
			
		||||
			if (timercmp(&diff, &resp->time_to_reply, <))
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			/* It's time to reply to this request. */
 | 
			
		||||
			LOGP(DMSLOOKUP, LOGL_DEBUG, "osmo_mslookup_client_rx_result(): %s\n",
 | 
			
		||||
			     osmo_mslookup_result_name_b(str, sizeof(str), &req->query, &resp->result));
 | 
			
		||||
			osmo_mslookup_client_rx_result(state->client, req->request_handle, &resp->result);
 | 
			
		||||
			resp->sent = true;
 | 
			
		||||
 | 
			
		||||
			/* The req will have been cleaned up now, so we must not iterate over state->responses anymore
 | 
			
		||||
			 * with this req. */
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	osmo_timer_schedule(&state->async_response_timer, ASYNC_RESPONSE_PERIOD);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct osmo_mslookup_client_method *osmo_mslookup_client_add_fake(struct osmo_mslookup_client *client,
 | 
			
		||||
								  struct osmo_mslookup_fake_response *responses,
 | 
			
		||||
								  size_t responses_len)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mslookup_client_method *method = talloc_zero(client, struct osmo_mslookup_client_method);
 | 
			
		||||
	OSMO_ASSERT(method);
 | 
			
		||||
 | 
			
		||||
	struct fake_lookup_state *state = talloc_zero(method, struct fake_lookup_state);
 | 
			
		||||
	OSMO_ASSERT(state);
 | 
			
		||||
	*state = (struct fake_lookup_state){
 | 
			
		||||
		.client = client,
 | 
			
		||||
		.responses = responses,
 | 
			
		||||
		.responses_len = responses_len,
 | 
			
		||||
	};
 | 
			
		||||
	INIT_LLIST_HEAD(&state->requests);
 | 
			
		||||
 | 
			
		||||
	*method = (struct osmo_mslookup_client_method){
 | 
			
		||||
		.name = "fake",
 | 
			
		||||
		.priv = state,
 | 
			
		||||
		.request = fake_lookup_request,
 | 
			
		||||
		.request_cleanup = fake_lookup_request_cleanup,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	osmo_timer_setup(&state->async_response_timer, fake_lookup_async_response, state);
 | 
			
		||||
	osmo_mslookup_client_method_add(client, method);
 | 
			
		||||
 | 
			
		||||
	osmo_timer_schedule(&state->async_response_timer, ASYNC_RESPONSE_PERIOD);
 | 
			
		||||
	return method;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										235
									
								
								src/mslookup/mslookup_client_mdns.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								src/mslookup/mslookup_client_mdns.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,235 @@
 | 
			
		||||
/* Copyright 2019 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 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 <string.h>
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/gsm/gsm_utils.h>
 | 
			
		||||
#include <osmocom/hlr/logging.h>
 | 
			
		||||
#include <osmocom/mslookup/mdns.h>
 | 
			
		||||
#include <osmocom/mslookup/mdns_sock.h>
 | 
			
		||||
#include <osmocom/mslookup/mslookup_client.h>
 | 
			
		||||
#include <osmocom/mslookup/mslookup_client_mdns.h>
 | 
			
		||||
 | 
			
		||||
struct osmo_mdns_method_state {
 | 
			
		||||
	/* Parameters passed by _add_method_dns() */
 | 
			
		||||
	struct osmo_sockaddr_str bind_addr;
 | 
			
		||||
	const char *domain_suffix;
 | 
			
		||||
 | 
			
		||||
	struct osmo_mdns_sock *mc;
 | 
			
		||||
 | 
			
		||||
	struct osmo_mslookup_client *client;
 | 
			
		||||
	struct llist_head requests;
 | 
			
		||||
	uint16_t next_packet_id;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct osmo_mdns_method_request {
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
	uint32_t request_handle;
 | 
			
		||||
	struct osmo_mslookup_query query;
 | 
			
		||||
	uint16_t packet_id;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int request_handle_by_query(uint32_t *request_handle, struct osmo_mdns_method_state *state,
 | 
			
		||||
				   struct osmo_mslookup_query *query, uint16_t packet_id)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_method_request *request;
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry(request, &state->requests, entry) {
 | 
			
		||||
		if (strcmp(request->query.service, query->service) != 0)
 | 
			
		||||
			continue;
 | 
			
		||||
		if (osmo_mslookup_id_cmp(&request->query.id, &query->id) != 0)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Match! */
 | 
			
		||||
		*request_handle = request->request_handle;
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
	return -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int mdns_method_recv(struct osmo_fd *osmo_fd, unsigned int what)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_method_state *state = osmo_fd->data;
 | 
			
		||||
	struct osmo_mslookup_result result;
 | 
			
		||||
	struct osmo_mslookup_query query;
 | 
			
		||||
	uint16_t packet_id;
 | 
			
		||||
	int n;
 | 
			
		||||
	uint8_t buffer[1024];
 | 
			
		||||
	uint32_t request_handle = 0;
 | 
			
		||||
	void *ctx = state;
 | 
			
		||||
 | 
			
		||||
	n = read(osmo_fd->fd, buffer, sizeof(buffer));
 | 
			
		||||
	if (n < 0) {
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "failed to read from socket\n");
 | 
			
		||||
		return n;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (osmo_mdns_result_decode(ctx, buffer, n, &packet_id, &query, &result, state->domain_suffix) < 0)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	if (request_handle_by_query(&request_handle, state, &query, packet_id) != 0)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	osmo_mslookup_client_rx_result(state->client, request_handle, &result);
 | 
			
		||||
	return n;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void mdns_method_request(struct osmo_mslookup_client_method *method, const struct osmo_mslookup_query *query,
 | 
			
		||||
				uint32_t request_handle)
 | 
			
		||||
{
 | 
			
		||||
	char buf[256];
 | 
			
		||||
	struct osmo_mdns_method_state *state = method->priv;
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	struct osmo_mdns_method_request *r = talloc_zero(method->client, struct osmo_mdns_method_request);
 | 
			
		||||
 | 
			
		||||
	*r = (struct osmo_mdns_method_request){
 | 
			
		||||
		.request_handle = request_handle,
 | 
			
		||||
		.query = *query,
 | 
			
		||||
		.packet_id = state->next_packet_id,
 | 
			
		||||
	};
 | 
			
		||||
	llist_add(&r->entry, &state->requests);
 | 
			
		||||
	state->next_packet_id++;
 | 
			
		||||
 | 
			
		||||
	msg = osmo_mdns_query_encode(method->client, r->packet_id, query, state->domain_suffix);
 | 
			
		||||
	if (!msg) {
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "Cannot encode request: %s\n",
 | 
			
		||||
		     osmo_mslookup_result_name_b(buf, sizeof(buf), query, NULL));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Send over the wire */
 | 
			
		||||
	LOGP(DMSLOOKUP, LOGL_DEBUG, "sending mDNS query: %s.%s\n", query->service,
 | 
			
		||||
	     osmo_mslookup_id_name_b(buf, sizeof(buf), &query->id));
 | 
			
		||||
	if (osmo_mdns_sock_send(state->mc, msg) == -1)
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "sending mDNS query failed\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void mdns_method_request_cleanup(struct osmo_mslookup_client_method *method, uint32_t request_handle)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_method_state *state = method->priv;
 | 
			
		||||
 | 
			
		||||
	/* Tear down any state associated with this handle. */
 | 
			
		||||
	struct osmo_mdns_method_request *r;
 | 
			
		||||
	llist_for_each_entry(r, &state->requests, entry) {
 | 
			
		||||
		if (r->request_handle != request_handle)
 | 
			
		||||
			continue;
 | 
			
		||||
		llist_del(&r->entry);
 | 
			
		||||
		talloc_free(r);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void mdns_method_destruct(struct osmo_mslookup_client_method *method)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_method_state *state = method->priv;
 | 
			
		||||
	struct osmo_mdns_method_request *e, *n;
 | 
			
		||||
	if (!state)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	/* Drop all DNS lookup request state. Triggering a timeout event and cleanup for mslookup client users will
 | 
			
		||||
	 * happen in the mslookup_client.c, we will simply stop responding from this lookup method. */
 | 
			
		||||
	llist_for_each_entry_safe(e, n, &state->requests, entry) {
 | 
			
		||||
		llist_del(&e->entry);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	osmo_mdns_sock_cleanup(state->mc);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Initialize the mDNS lookup method.
 | 
			
		||||
 * \param[in] client  the client to attach the method to.
 | 
			
		||||
 * \param[in] ip  IPv4 or IPv6 address string.
 | 
			
		||||
 * \param[in] port  The port to bind to.
 | 
			
		||||
 * \param[in] initial_packet_id  Used in the first mslookup query, then increased by one in each following query. All
 | 
			
		||||
 *				 servers answer to each query with the same packet ID. Set to -1 to use a random
 | 
			
		||||
 *				 initial ID (recommended unless you need deterministic output). This ID is for visually
 | 
			
		||||
 *				 distinguishing the packets in packet sniffers, the mslookup client uses not just the
 | 
			
		||||
 *				 ID, but all query parameters (service type, ID, ID type), to determine if a reply is
 | 
			
		||||
 *				 relevant.
 | 
			
		||||
 * \param[in] domain_suffix  is appended to each domain in the queries to avoid colliding with the top-level domains
 | 
			
		||||
 *                           administrated by IANA. Example: "mdns.osmocom.org" */
 | 
			
		||||
struct osmo_mslookup_client_method *osmo_mslookup_client_add_mdns(struct osmo_mslookup_client *client, const char *ip,
 | 
			
		||||
								  uint16_t port, int initial_packet_id,
 | 
			
		||||
								  const char *domain_suffix)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_method_state *state;
 | 
			
		||||
	struct osmo_mslookup_client_method *m;
 | 
			
		||||
 | 
			
		||||
	m = talloc_zero(client, struct osmo_mslookup_client_method);
 | 
			
		||||
	OSMO_ASSERT(m);
 | 
			
		||||
 | 
			
		||||
	state = talloc_zero(m, struct osmo_mdns_method_state);
 | 
			
		||||
	OSMO_ASSERT(state);
 | 
			
		||||
	INIT_LLIST_HEAD(&state->requests);
 | 
			
		||||
	if (osmo_sockaddr_str_from_str(&state->bind_addr, ip, port)) {
 | 
			
		||||
		LOGP(DMSLOOKUP, LOGL_ERROR, "mslookup mDNS: invalid address/port: %s %u\n",
 | 
			
		||||
		     ip, port);
 | 
			
		||||
		goto error_cleanup;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (initial_packet_id == -1) {
 | 
			
		||||
		if (osmo_get_rand_id((uint8_t *)&state->next_packet_id, 2) < 0) {
 | 
			
		||||
			LOGP(DMSLOOKUP, LOGL_ERROR, "mslookup mDNS: failed to generate random initial packet ID\n");
 | 
			
		||||
			goto error_cleanup;
 | 
			
		||||
		}
 | 
			
		||||
	} else
 | 
			
		||||
		state->next_packet_id = initial_packet_id;
 | 
			
		||||
 | 
			
		||||
	state->client = client;
 | 
			
		||||
	state->domain_suffix = domain_suffix;
 | 
			
		||||
 | 
			
		||||
	state->mc = osmo_mdns_sock_init(state, ip, port, mdns_method_recv, state, 0);
 | 
			
		||||
	if (!state->mc)
 | 
			
		||||
		goto error_cleanup;
 | 
			
		||||
 | 
			
		||||
	*m = (struct osmo_mslookup_client_method){
 | 
			
		||||
		.name = "mDNS",
 | 
			
		||||
		.priv = state,
 | 
			
		||||
		.request = mdns_method_request,
 | 
			
		||||
		.request_cleanup = mdns_method_request_cleanup,
 | 
			
		||||
		.destruct = mdns_method_destruct,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	osmo_mslookup_client_method_add(client, m);
 | 
			
		||||
	return m;
 | 
			
		||||
 | 
			
		||||
error_cleanup:
 | 
			
		||||
	talloc_free(m);
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const struct osmo_sockaddr_str *osmo_mslookup_client_method_mdns_get_bind_addr(struct osmo_mslookup_client_method *dns_method)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_method_state *state;
 | 
			
		||||
	if (!dns_method || !dns_method->priv)
 | 
			
		||||
		return NULL;
 | 
			
		||||
	state = dns_method->priv;
 | 
			
		||||
	return &state->bind_addr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const char *osmo_mslookup_client_method_mdns_get_domain_suffix(struct osmo_mslookup_client_method *dns_method)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_mdns_method_state *state;
 | 
			
		||||
	if (!dns_method || !dns_method->priv)
 | 
			
		||||
		return NULL;
 | 
			
		||||
	state = dns_method->priv;
 | 
			
		||||
	return state->domain_suffix;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										879
									
								
								src/mslookup/osmo-mslookup-client.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										879
									
								
								src/mslookup/osmo-mslookup-client.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,879 @@
 | 
			
		||||
/*! \file osmo-mslookup-client.c
 | 
			
		||||
 * Distributed GSM: find the location of subscribers, for example by multicast DNS,
 | 
			
		||||
 * to obtain HLR, SIP or SMPP server addresses (or arbitrary service names).
 | 
			
		||||
 */
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * (C) 2019 by Neels Hofmeyr <neels@hofmeyr.de>
 | 
			
		||||
 *
 | 
			
		||||
 * 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 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 <stdio.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <getopt.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <talloc.h>
 | 
			
		||||
#include <sys/un.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/application.h>
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/socket.h>
 | 
			
		||||
#include <osmocom/mslookup/mslookup_client.h>
 | 
			
		||||
#include <osmocom/mslookup/mslookup_client_mdns.h>
 | 
			
		||||
#include <osmocom/mslookup/mdns_sock.h>
 | 
			
		||||
#include <osmocom/mslookup/mdns.h>
 | 
			
		||||
 | 
			
		||||
#define CSV_HEADERS "query\tresult\tlast\tage\tv4_ip\tv4_port\tv6_ip\tv6_port"
 | 
			
		||||
 | 
			
		||||
static void print_version(void)
 | 
			
		||||
{
 | 
			
		||||
	printf("osmo-mslookup-client version %s\n", PACKAGE_VERSION);
 | 
			
		||||
	printf("\n"
 | 
			
		||||
	"Copyright (C) 2019 by sysmocom - s.f.m.c. GmbH\n"
 | 
			
		||||
	"Copyright (C) 2019 by Neels Hofmeyr <neels@hofmeyr.de>\n"
 | 
			
		||||
	"This program is free software; you can redistribute it and/or modify\n"
 | 
			
		||||
	"it under the terms of the GNU General Public License as published by\n"
 | 
			
		||||
	"the Free Software Foundation; either version 2 of the License, or\n"
 | 
			
		||||
	"(at your option) any later version.\n"
 | 
			
		||||
	"\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void print_help()
 | 
			
		||||
{
 | 
			
		||||
	print_version();
 | 
			
		||||
	printf(
 | 
			
		||||
"Standalone mslookup client for Distributed GSM\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"Receiving mslookup results means listening for responses on a socket. Often,\n"
 | 
			
		||||
"integration (e.g. FreeSwitch dialplan.py) makes it hard to select() on a socket\n"
 | 
			
		||||
"to read responses, because that interferes with the main program (e.g.\n"
 | 
			
		||||
"FreeSwitch's dialplan.py seems to be integrated with an own select() main loop\n"
 | 
			
		||||
"that interferes with osmo_select_main(), or an smpp.py uses\n"
 | 
			
		||||
"smpplib.client.listen() as main loop, etc.).\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"This program provides a trivial solution, by outsourcing the mslookup main loop\n"
 | 
			
		||||
"to a separate process. Communication is done via cmdline arg and stdout pipe or\n"
 | 
			
		||||
"a (blocking) unix domain socket, results are returned in CSV or JSON format.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"This can be done one-shot, i.e. exit as soon as the response has been\n"
 | 
			
		||||
"determined, or in daemon form, i.e. continuously listen for requests and return\n"
 | 
			
		||||
"responses.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"About running a local daemon: it is unintuitive to connect to a socket to solve\n"
 | 
			
		||||
"a problem of reading from a socket -- it seems like just more of the same\n"
 | 
			
		||||
"problem. The reasons why the daemon is in fact useful are:\n"
 | 
			
		||||
"- The osmo-mslookup-client daemon will return only those results matching\n"
 | 
			
		||||
"  requests issued on that socket connection.\n"
 | 
			
		||||
"- A program can simply blockingly recv() from the osmo-mslookup-client socket\n"
 | 
			
		||||
"  instead of needing to run osmo_select_main() so that libosmo-mslookup is able\n"
 | 
			
		||||
"  to asynchronously receive responses from remote servers.\n"
 | 
			
		||||
"- Only one long-lived multicast socket needs to be opened instead of a new\n"
 | 
			
		||||
"  socket for each request.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"Output is in CSV or json, see --format. The default is tab-separated CSV\n"
 | 
			
		||||
"with these columns:\n"
 | 
			
		||||
CSV_HEADERS "\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"One-shot operation example:\n"
 | 
			
		||||
"$ osmo-mslookup-client 1000-@sip.voice.12345.msisdn -f json\n"
 | 
			
		||||
"{\"query\": \"sip.voice.12345.msisdn\", \"result\": \"result\", \"last\": true, \"age\": 5, \"v4\": [\"1.2.3.7\", \"23\"]}\n"
 | 
			
		||||
"$\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"Daemon operation example:\n"
 | 
			
		||||
"$ osmo-mslookup-client -s /tmp/mslookup -d\n"
 | 
			
		||||
"(and a client program then connects to /tmp/mslookup, find an implementation\n"
 | 
			
		||||
"example below)\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"Integrating with calling programs can be done by:\n"
 | 
			
		||||
"- call osmo-mslookup-client with the query string as argument.\n"
 | 
			
		||||
"  It will open a multicast DNS socket, send out a query and wait for the\n"
 | 
			
		||||
"  matching response. It will print the result on stdout and exit.\n"
 | 
			
		||||
"  This method launches a new process for every mslookup query,\n"
 | 
			
		||||
"  and creates a short-lived multicast listener for each invocation.\n"
 | 
			
		||||
"  This is fine for low activity, but does not scale well.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"- invoke osmo-mslookup-client --socket /tmp/mslookup -d.\n"
 | 
			
		||||
"  Individual queries can be sent by connecting to that unix domain socket,\n"
 | 
			
		||||
"  blockingly reading the response when it arrives and disconnecting.\n"
 | 
			
		||||
"  This way only one process keeps one multicast listener open.\n"
 | 
			
		||||
"  Callers can connect to this socket without spawning processes.\n"
 | 
			
		||||
"  This is recommended for scale.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"Python example clients for {CSV,JSON}x{cmdline,socket} can be found here:\n"
 | 
			
		||||
"http://git.osmocom.org/osmo-hlr/tree/contrib/dgsm/osmo-mslookup-pipe.py\n"
 | 
			
		||||
"http://git.osmocom.org/osmo-hlr/tree/contrib/dgsm/osmo-mslookup-socket.py\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"Options:\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"[[delay-][timeout]@]service.number.id\n"
 | 
			
		||||
"	A service query string with optional individual timeout.\n"
 | 
			
		||||
"	The same format is also used on a daemon socket, if any.\n"
 | 
			
		||||
"	The timeout consists of the min-delay and the timeout numbers,\n"
 | 
			
		||||
"	corresponding to the --min-delay and --timeout options, in milliseconds.\n"
 | 
			
		||||
"	These options apply if a query string lacks own numbers.\n"
 | 
			
		||||
"	Examples:\n"
 | 
			
		||||
"		gsup.hlr.1234567.imsi		Use cmdline timeout settings\n"
 | 
			
		||||
"		5000@gsup.hlr.1234567.imsi	Return N results for 5 seconds\n"
 | 
			
		||||
"		1000-5000@sip.voice.123.msisdn	Same, but silent for first second\n"
 | 
			
		||||
"		10000-@smpp.sms.567.msisdn	Return 1 result after 10 seconds\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"--format -f csv (default)\n"
 | 
			
		||||
"	Format result lines in CSV format.\n"
 | 
			
		||||
"--no-csv-headers -H\n"
 | 
			
		||||
"	If the format is 'csv', by default, the first output line prints the\n"
 | 
			
		||||
"	CSV headers used for CSV output format. This option disables these CSV\n"
 | 
			
		||||
"	headers.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"--format -f json\n"
 | 
			
		||||
"	Format result lines in json instead of semicolon separated, like:\n"
 | 
			
		||||
"	{\"query\": \"sip.voice.12345.msisdn\", \"result\": \"ok\", \"v4\": [\"10.9.8.7\", \"5060\"]}\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"--daemon -d\n"
 | 
			
		||||
"	Keep running after a request has been serviced\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"--mdns-ip -m " OSMO_MSLOOKUP_MDNS_IP4 " -m " OSMO_MSLOOKUP_MDNS_IP6 "\n"
 | 
			
		||||
"--mdns-port -M " OSMO_STRINGIFY_VAL(OSMO_MSLOOKUP_MDNS_PORT) "\n"
 | 
			
		||||
"	Set multicast IP address / port to send mDNS requests and listen for\n"
 | 
			
		||||
"	mDNS reponses\n"
 | 
			
		||||
"--mdns-domain-suffix -D " OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT "\n"
 | 
			
		||||
"	Append this suffix to each mDNS query's domain to avoid colliding with the\n"
 | 
			
		||||
"	top-level domains administrated by IANA.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"--min-delay -t 1000 (in milliseconds)\n"
 | 
			
		||||
"	Set minimum delay to wait before returning any results.\n"
 | 
			
		||||
"	When this timeout has elapsed, the best current result is returned,\n"
 | 
			
		||||
"	if any is available.\n"
 | 
			
		||||
"	Responses arriving after the min-delay has elapsed which have a younger\n"
 | 
			
		||||
"	age than previous results are returned immediately.\n"
 | 
			
		||||
"	Note: When a response with age of zero comes in, the result is returned\n"
 | 
			
		||||
"	immediately and the request is discarded: non-daemon mode exits, daemon\n"
 | 
			
		||||
"	mode ignores later results.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"--timeout -T 1000 (in milliseconds)\n"
 | 
			
		||||
"	Set timeout after which to stop listening for responses.\n"
 | 
			
		||||
"	If this is smaller than -t, the value from -t will be used for -T as well.\n"
 | 
			
		||||
"	Note: When a response with age of zero comes in, the result is returned\n"
 | 
			
		||||
"	immediately and the request is discarded: non-daemon mode exits, daemon\n"
 | 
			
		||||
"	mode ignores later results.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"--socket -s /path/to/unix-domain-socket\n"
 | 
			
		||||
"	Listen to requests from and write responses to a UNIX domain socket.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"--send -S <query> <age> <ip1> <port1> <ip2> <port2>\n"
 | 
			
		||||
"	Do not query, but send an mslookup result. This is useful only for\n"
 | 
			
		||||
"	testing. Examples:\n"
 | 
			
		||||
"	--send foo.123.msisdn 300 23.42.17.11 1234\n"
 | 
			
		||||
"	--send foo.123.msisdn 300 2323:4242:1717:1111::42 1234\n"
 | 
			
		||||
"	--send foo.123.msisdn 300 23.42.17.11 1234 2323:4242:1717:1111::42 1234\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"--quiet -q\n"
 | 
			
		||||
"	Do not print errors to stderr, do not log to stderr.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"--help -h\n"
 | 
			
		||||
"	This help\n"
 | 
			
		||||
);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum result_format {
 | 
			
		||||
	FORMAT_CSV = 0,
 | 
			
		||||
	FORMAT_JSON,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct {
 | 
			
		||||
	bool daemon;
 | 
			
		||||
	struct osmo_sockaddr_str mdns_addr;
 | 
			
		||||
	uint32_t min_delay;
 | 
			
		||||
	uint32_t timeout;
 | 
			
		||||
	const char *socket_path;
 | 
			
		||||
	const char *format_str;
 | 
			
		||||
	const char *mdns_domain_suffix;
 | 
			
		||||
	bool csv_headers;
 | 
			
		||||
	bool send;
 | 
			
		||||
	bool quiet;
 | 
			
		||||
} cmdline_opts = {
 | 
			
		||||
	.mdns_addr = { .af=AF_INET, .ip=OSMO_MSLOOKUP_MDNS_IP4, .port=OSMO_MSLOOKUP_MDNS_PORT },
 | 
			
		||||
	.min_delay = 1000,
 | 
			
		||||
	.timeout = 1000,
 | 
			
		||||
	.csv_headers = true,
 | 
			
		||||
	.mdns_domain_suffix = OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define print_error(fmt, args...) do { \
 | 
			
		||||
		if (!cmdline_opts.quiet) \
 | 
			
		||||
			fprintf(stderr, fmt, ##args); \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
char g_buf[1024];
 | 
			
		||||
 | 
			
		||||
long long int parse_int(long long int minval, long long int maxval, const char *arg, int *rc)
 | 
			
		||||
{
 | 
			
		||||
	long long int val;
 | 
			
		||||
	char *endptr;
 | 
			
		||||
	if (rc)
 | 
			
		||||
		*rc = -1;
 | 
			
		||||
	if (!arg)
 | 
			
		||||
		return -1;
 | 
			
		||||
	errno = 0;
 | 
			
		||||
	val = strtoll(arg, &endptr, 10);
 | 
			
		||||
	if (errno || val < minval || val > maxval || *endptr)
 | 
			
		||||
		return -1;
 | 
			
		||||
	if (rc)
 | 
			
		||||
		*rc = 0;
 | 
			
		||||
	return val;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int cb_doing_nothing(struct osmo_fd *fd, unsigned int what)
 | 
			
		||||
{
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* --send: Just send a response, for manual testing. */
 | 
			
		||||
int do_send(int argc, char ** argv)
 | 
			
		||||
{
 | 
			
		||||
	/* parse args <query> <age> <v4-ip> <v4-port> <v6-ip> <v6-port> */
 | 
			
		||||
#define ARG(NR) ((argc > NR)? argv[NR] : NULL)
 | 
			
		||||
	const char *query_str = ARG(0);
 | 
			
		||||
	const char *age_str = ARG(1);
 | 
			
		||||
	const char *ip_strs[2][2] = {
 | 
			
		||||
		{ ARG(2), ARG(3) },
 | 
			
		||||
		{ ARG(4), ARG(5) },
 | 
			
		||||
	};
 | 
			
		||||
	struct osmo_mslookup_query q = {};
 | 
			
		||||
	struct osmo_mslookup_result r = { .rc = OSMO_MSLOOKUP_RC_RESULT };
 | 
			
		||||
	int i;
 | 
			
		||||
	int rc;
 | 
			
		||||
	void *ctx = talloc_named_const(NULL, 0, __func__);
 | 
			
		||||
	struct osmo_mdns_sock *sock;
 | 
			
		||||
 | 
			
		||||
	if (!query_str) {
 | 
			
		||||
		print_error("--send needs a query string like foo.123456.imsi\n");
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
	if (osmo_mslookup_query_init_from_domain_str(&q, query_str)) {
 | 
			
		||||
		print_error("Invalid query string '%s', need a query string like foo.123456.imsi\n",
 | 
			
		||||
			    query_str);
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!age_str) {
 | 
			
		||||
		print_error("--send needs an age\n");
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
	r.age = parse_int(0, UINT32_MAX, age_str, &rc);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		print_error("invalid age\n");
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < 2; i++) {
 | 
			
		||||
		struct osmo_sockaddr_str addr;
 | 
			
		||||
		uint16_t port;
 | 
			
		||||
		if (!ip_strs[i][0])
 | 
			
		||||
			continue;
 | 
			
		||||
		port = parse_int(1, 65535, ip_strs[i][1] ? : "2342", &rc);
 | 
			
		||||
		if (rc) {
 | 
			
		||||
			print_error("invalid port: %s\n", ip_strs[i][1] ? : "NULL");
 | 
			
		||||
			return 1;
 | 
			
		||||
		}
 | 
			
		||||
		if (osmo_sockaddr_str_from_str(&addr, ip_strs[i][0], port)) {
 | 
			
		||||
			print_error("invalid IP addr: %s\n", ip_strs[i][0]);
 | 
			
		||||
			return 1;
 | 
			
		||||
		}
 | 
			
		||||
		if (addr.af == AF_INET)
 | 
			
		||||
			r.host_v4 = addr;
 | 
			
		||||
		else
 | 
			
		||||
			r.host_v6 = addr;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	printf("Sending mDNS to " OSMO_SOCKADDR_STR_FMT ": %s\n", OSMO_SOCKADDR_STR_FMT_ARGS(&cmdline_opts.mdns_addr),
 | 
			
		||||
	       osmo_mslookup_result_name_c(ctx, &q, &r));
 | 
			
		||||
 | 
			
		||||
	rc = 1;
 | 
			
		||||
	sock = osmo_mdns_sock_init(ctx, cmdline_opts.mdns_addr.ip, cmdline_opts.mdns_addr.port,
 | 
			
		||||
				   cb_doing_nothing, NULL, 0);
 | 
			
		||||
	if (!sock) {
 | 
			
		||||
		print_error("unable to open mDNS socket\n");
 | 
			
		||||
		goto exit_cleanup;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	struct msgb *msg = osmo_mdns_result_encode(ctx, 0, &q, &r, cmdline_opts.mdns_domain_suffix);
 | 
			
		||||
	if (!msg) {
 | 
			
		||||
		print_error("unable to encode mDNS response\n");
 | 
			
		||||
		goto exit_cleanup;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (osmo_mdns_sock_send(sock, msg)) {
 | 
			
		||||
		print_error("unable to send mDNS message\n");
 | 
			
		||||
		goto exit_cleanup;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rc = 0;
 | 
			
		||||
exit_cleanup:
 | 
			
		||||
	osmo_mdns_sock_cleanup(sock);
 | 
			
		||||
	talloc_free(ctx);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct {
 | 
			
		||||
	void *ctx;
 | 
			
		||||
	unsigned int requests_handled;
 | 
			
		||||
	struct osmo_fd socket_ofd;
 | 
			
		||||
	struct osmo_mslookup_client *mslookup_client;
 | 
			
		||||
	struct llist_head queries;
 | 
			
		||||
	struct llist_head socket_clients;
 | 
			
		||||
	enum result_format format;
 | 
			
		||||
} globals = {
 | 
			
		||||
	.queries = LLIST_HEAD_INIT(globals.queries),
 | 
			
		||||
	.socket_clients = LLIST_HEAD_INIT(globals.socket_clients),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef void (*formatter_t)(char *buf, size_t buflen, const char *query_str, const struct osmo_mslookup_result *r);
 | 
			
		||||
 | 
			
		||||
void formatter_csv(char *buf, size_t buflen, const char *query_str, const struct osmo_mslookup_result *r)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf=buf, .len=buflen };
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "%s", query_str);
 | 
			
		||||
 | 
			
		||||
	if (!r)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "\tERROR\t\t\t\t\t\t");
 | 
			
		||||
	else {
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "\t%s", osmo_mslookup_result_code_name(r->rc));
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "\t%s", r->last ? "last" : "not-last");
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "\t%u", r->age);
 | 
			
		||||
		switch (r->rc) {
 | 
			
		||||
		case OSMO_MSLOOKUP_RC_RESULT:
 | 
			
		||||
			if (osmo_sockaddr_str_is_nonzero(&r->host_v4))
 | 
			
		||||
				OSMO_STRBUF_PRINTF(sb, "\t%s\t%u", r->host_v4.ip, r->host_v4.port);
 | 
			
		||||
			else
 | 
			
		||||
				OSMO_STRBUF_PRINTF(sb, "\t\t");
 | 
			
		||||
			if (osmo_sockaddr_str_is_nonzero(&r->host_v6))
 | 
			
		||||
				OSMO_STRBUF_PRINTF(sb, "\t%s\t%u", r->host_v6.ip, r->host_v6.port);
 | 
			
		||||
			else
 | 
			
		||||
				OSMO_STRBUF_PRINTF(sb, "\t\t");
 | 
			
		||||
			break;
 | 
			
		||||
		default:
 | 
			
		||||
			OSMO_STRBUF_PRINTF(sb, "\t\t\t\t\t");
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void formatter_json(char *buf, size_t buflen, const char *query_str, const struct osmo_mslookup_result *r)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf=buf, .len=buflen };
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "{\"query\": \"%s\"", query_str);
 | 
			
		||||
 | 
			
		||||
	if (!r)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, ", \"result\": \"ERROR\"");
 | 
			
		||||
	else {
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, ", \"result\": \"%s\"", osmo_mslookup_result_code_name(r->rc));
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, ", \"last\": %s", r->last ? "true" : "false");
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, ", \"age\": %u", r->age);
 | 
			
		||||
		if (r->rc == OSMO_MSLOOKUP_RC_RESULT) {
 | 
			
		||||
			if (osmo_sockaddr_str_is_nonzero(&r->host_v4))
 | 
			
		||||
				OSMO_STRBUF_PRINTF(sb, ", \"v4\": [\"%s\", \"%u\"]", r->host_v4.ip, r->host_v4.port);
 | 
			
		||||
			if (osmo_sockaddr_str_is_nonzero(&r->host_v6))
 | 
			
		||||
				OSMO_STRBUF_PRINTF(sb, ", \"v6\": [\"%s\", \"%u\"]", r->host_v6.ip, r->host_v6.port);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "}");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
formatter_t formatters[] = {
 | 
			
		||||
	[FORMAT_CSV] = formatter_csv,
 | 
			
		||||
	[FORMAT_JSON] = formatter_json,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void respond_str_stdout(const char *str) {
 | 
			
		||||
	fprintf(stdout, "%s\n", str);
 | 
			
		||||
	fflush(stdout);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void start_query_str(const char *query_str);
 | 
			
		||||
void start_query_strs(char **query_strs, size_t query_strs_len);
 | 
			
		||||
 | 
			
		||||
struct socket_client {
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
	struct osmo_fd ofd;
 | 
			
		||||
	char query_str[1024];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void socket_client_close(struct socket_client *c)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_fd *ofd = &c->ofd;
 | 
			
		||||
 | 
			
		||||
	close(ofd->fd);
 | 
			
		||||
	ofd->fd = -1;
 | 
			
		||||
	osmo_fd_unregister(ofd);
 | 
			
		||||
 | 
			
		||||
	llist_del(&c->entry);
 | 
			
		||||
	talloc_free(c);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void socket_client_respond_result(struct socket_client *c, const char *response)
 | 
			
		||||
{
 | 
			
		||||
	write(c->ofd.fd, response, strlen(response));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int socket_read_cb(struct osmo_fd *ofd)
 | 
			
		||||
{
 | 
			
		||||
	struct socket_client *c = ofd->data;
 | 
			
		||||
	int rc;
 | 
			
		||||
	char rxbuf[1024];
 | 
			
		||||
	char *query_with_timeout;
 | 
			
		||||
	char *query_str;
 | 
			
		||||
	char *at;
 | 
			
		||||
 | 
			
		||||
	rc = recv(ofd->fd, rxbuf, sizeof(rxbuf), 0);
 | 
			
		||||
	if (rc == 0)
 | 
			
		||||
		goto close;
 | 
			
		||||
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
		if (errno == EAGAIN)
 | 
			
		||||
			return 0;
 | 
			
		||||
		goto close;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (rc >= sizeof(c->query_str))
 | 
			
		||||
		goto close;
 | 
			
		||||
 | 
			
		||||
	rxbuf[rc] = '\0';
 | 
			
		||||
	query_with_timeout = strtok(rxbuf, "\r\n");
 | 
			
		||||
	at = strchr(query_with_timeout, '@');
 | 
			
		||||
	query_str = at ? at + 1 : query_with_timeout;
 | 
			
		||||
 | 
			
		||||
	if (c->query_str[0]) {
 | 
			
		||||
		print_error("ERROR: Only one query per client connect is allowed;"
 | 
			
		||||
			    " received '%s' and '%s' on the same connection\n",
 | 
			
		||||
			    c->query_str, query_str);
 | 
			
		||||
		formatters[globals.format](g_buf, sizeof(g_buf), query_str, NULL);
 | 
			
		||||
		socket_client_respond_result(c, g_buf);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	OSMO_STRLCPY_ARRAY(c->query_str, query_str);
 | 
			
		||||
	start_query_str(query_with_timeout);
 | 
			
		||||
	printf("query: %s\n", query_with_timeout);
 | 
			
		||||
	return rc;
 | 
			
		||||
 | 
			
		||||
close:
 | 
			
		||||
	socket_client_close(c);
 | 
			
		||||
	return -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int socket_cb(struct osmo_fd *ofd, unsigned int flags)
 | 
			
		||||
{
 | 
			
		||||
	int rc = 0;
 | 
			
		||||
 | 
			
		||||
	if (flags & BSC_FD_READ)
 | 
			
		||||
		rc = socket_read_cb(ofd);
 | 
			
		||||
	if (rc < 0)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int socket_accept(struct osmo_fd *ofd, unsigned int flags)
 | 
			
		||||
{
 | 
			
		||||
	struct socket_client *c;
 | 
			
		||||
	struct sockaddr_un un_addr;
 | 
			
		||||
	socklen_t len;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	len = sizeof(un_addr);
 | 
			
		||||
	rc = accept(ofd->fd, (struct sockaddr*)&un_addr, &len);
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
		print_error("Failed to accept a new connection\n");
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c = talloc_zero(globals.ctx, struct socket_client);
 | 
			
		||||
	OSMO_ASSERT(c);
 | 
			
		||||
	c->ofd.fd = rc;
 | 
			
		||||
	c->ofd.when = BSC_FD_READ;
 | 
			
		||||
	c->ofd.cb = socket_cb;
 | 
			
		||||
	c->ofd.data = c;
 | 
			
		||||
 | 
			
		||||
	if (osmo_fd_register(&c->ofd) != 0) {
 | 
			
		||||
		print_error("Failed to register new connection fd\n");
 | 
			
		||||
		close(c->ofd.fd);
 | 
			
		||||
		c->ofd.fd = -1;
 | 
			
		||||
		talloc_free(c);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	llist_add(&c->entry, &globals.socket_clients);
 | 
			
		||||
 | 
			
		||||
	if (globals.format == FORMAT_CSV && cmdline_opts.csv_headers)
 | 
			
		||||
		write(c->ofd.fd, CSV_HEADERS, strlen(CSV_HEADERS));
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int socket_init(const char *sock_path)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_fd *ofd = &globals.socket_ofd;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	ofd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, sock_path, OSMO_SOCK_F_BIND);
 | 
			
		||||
	if (ofd->fd < 0) {
 | 
			
		||||
		print_error("Could not create unix socket: %s: %s\n", sock_path, strerror(errno));
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ofd->when = BSC_FD_READ;
 | 
			
		||||
	ofd->cb = socket_accept;
 | 
			
		||||
 | 
			
		||||
	rc = osmo_fd_register(ofd);
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
		print_error("Could not register listen fd: %d\n", rc);
 | 
			
		||||
		close(ofd->fd);
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void socket_close()
 | 
			
		||||
{
 | 
			
		||||
	struct socket_client *c, *n;
 | 
			
		||||
	llist_for_each_entry_safe(c, n, &globals.socket_clients, entry)
 | 
			
		||||
		socket_client_close(c);
 | 
			
		||||
	if (osmo_fd_is_registered(&globals.socket_ofd)) {
 | 
			
		||||
		close(globals.socket_ofd.fd);
 | 
			
		||||
		globals.socket_ofd.fd = -1;
 | 
			
		||||
		osmo_fd_unregister(&globals.socket_ofd);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct query {
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
 | 
			
		||||
	char query_str[128];
 | 
			
		||||
	struct osmo_mslookup_query query;
 | 
			
		||||
	uint32_t handle;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void respond_result(const char *query_str, const struct osmo_mslookup_result *r)
 | 
			
		||||
{
 | 
			
		||||
	struct socket_client *c, *n;
 | 
			
		||||
	formatters[globals.format](g_buf, sizeof(g_buf), query_str, r);
 | 
			
		||||
	respond_str_stdout(g_buf);
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry_safe(c, n, &globals.socket_clients, entry) {
 | 
			
		||||
		if (!strcmp(query_str, c->query_str)) {
 | 
			
		||||
			socket_client_respond_result(c, g_buf);
 | 
			
		||||
			if (r->last)
 | 
			
		||||
				socket_client_close(c);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if (r->last)
 | 
			
		||||
		globals.requests_handled++;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void respond_err(const char *query_str)
 | 
			
		||||
{
 | 
			
		||||
	respond_result(query_str, NULL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct query *query_by_handle(uint32_t request_handle)
 | 
			
		||||
{
 | 
			
		||||
	struct query *q;
 | 
			
		||||
	llist_for_each_entry(q, &globals.queries, entry) {
 | 
			
		||||
		if (request_handle == q->handle)
 | 
			
		||||
			return q;
 | 
			
		||||
	}
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void mslookup_result_cb(struct osmo_mslookup_client *client,
 | 
			
		||||
			uint32_t request_handle,
 | 
			
		||||
			const struct osmo_mslookup_query *query,
 | 
			
		||||
			const struct osmo_mslookup_result *result)
 | 
			
		||||
{
 | 
			
		||||
	struct query *q = query_by_handle(request_handle);
 | 
			
		||||
	if (!q)
 | 
			
		||||
		return;
 | 
			
		||||
	respond_result(q->query_str, result);
 | 
			
		||||
	if (result->last) {
 | 
			
		||||
		llist_del(&q->entry);
 | 
			
		||||
		talloc_free(q);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void start_query_str(const char *query_str)
 | 
			
		||||
{
 | 
			
		||||
	struct query *q;
 | 
			
		||||
	const char *domain_str = query_str;
 | 
			
		||||
	char *at;
 | 
			
		||||
	struct osmo_mslookup_query_handling h = {
 | 
			
		||||
		.min_wait_milliseconds = cmdline_opts.min_delay,
 | 
			
		||||
		.result_timeout_milliseconds = cmdline_opts.timeout,
 | 
			
		||||
		.result_cb = mslookup_result_cb,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	at = strchr(query_str, '@');
 | 
			
		||||
	if (at) {
 | 
			
		||||
		int rc;
 | 
			
		||||
		char timeouts[16];
 | 
			
		||||
		char *dash;
 | 
			
		||||
		char *timeout;
 | 
			
		||||
 | 
			
		||||
		domain_str = at + 1;
 | 
			
		||||
 | 
			
		||||
		h.min_wait_milliseconds = h.result_timeout_milliseconds = 0;
 | 
			
		||||
 | 
			
		||||
		if (osmo_print_n(timeouts, sizeof(timeouts), query_str, at - query_str) >= sizeof(timeouts)) {
 | 
			
		||||
			print_error("ERROR: timeouts part too long in query string\n");
 | 
			
		||||
			respond_err(domain_str);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		dash = strchr(timeouts, '-');
 | 
			
		||||
		if (dash) {
 | 
			
		||||
			char min_delay[16];
 | 
			
		||||
			osmo_print_n(min_delay, sizeof(min_delay), timeouts, dash - timeouts);
 | 
			
		||||
			h.min_wait_milliseconds = parse_int(0, UINT32_MAX, min_delay, &rc);
 | 
			
		||||
			if (rc) {
 | 
			
		||||
				print_error("ERROR: invalid min-delay number: %s\n", min_delay);
 | 
			
		||||
				respond_err(domain_str);
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
			timeout = dash + 1;
 | 
			
		||||
		} else {
 | 
			
		||||
			timeout = timeouts;
 | 
			
		||||
		}
 | 
			
		||||
		if (*timeout) {
 | 
			
		||||
			h.result_timeout_milliseconds = parse_int(0, UINT32_MAX, timeout, &rc);
 | 
			
		||||
			if (rc) {
 | 
			
		||||
				print_error("ERROR: invalid timeout number: %s\n", timeout);
 | 
			
		||||
				respond_err(domain_str);
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (strlen(domain_str) >= sizeof(q->query_str)) {
 | 
			
		||||
		print_error("ERROR: query string is too long: '%s'\n", domain_str);
 | 
			
		||||
		respond_err(domain_str);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	q = talloc_zero(globals.ctx, struct query);
 | 
			
		||||
	OSMO_ASSERT(q);
 | 
			
		||||
	OSMO_STRLCPY_ARRAY(q->query_str, domain_str);
 | 
			
		||||
 | 
			
		||||
	if (osmo_mslookup_query_init_from_domain_str(&q->query, q->query_str)) {
 | 
			
		||||
		print_error("ERROR: cannot parse query string: '%s'\n", domain_str);
 | 
			
		||||
		respond_err(domain_str);
 | 
			
		||||
		talloc_free(q);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	q->handle = osmo_mslookup_client_request(globals.mslookup_client, &q->query, &h);
 | 
			
		||||
	if (!q->handle) {
 | 
			
		||||
		print_error("ERROR: cannot send query: '%s'\n", domain_str);
 | 
			
		||||
		respond_err(domain_str);
 | 
			
		||||
		talloc_free(q);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	llist_add(&q->entry, &globals.queries);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void start_query_strs(char **query_strs, size_t query_strs_len)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
	for (i = 0; i < query_strs_len; i++)
 | 
			
		||||
		start_query_str(query_strs[i]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main(int argc, char **argv)
 | 
			
		||||
{
 | 
			
		||||
	int rc = EXIT_FAILURE;
 | 
			
		||||
	globals.ctx = talloc_named_const(NULL, 0, "osmo-mslookup-client");
 | 
			
		||||
 | 
			
		||||
	osmo_init_logging2(globals.ctx, NULL);
 | 
			
		||||
	log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME);
 | 
			
		||||
	log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END);
 | 
			
		||||
	log_set_print_level(osmo_stderr_target, 1);
 | 
			
		||||
	log_set_print_category(osmo_stderr_target, 1);
 | 
			
		||||
	log_set_print_category_hex(osmo_stderr_target, 0);
 | 
			
		||||
	log_set_print_extended_timestamp(osmo_stderr_target, 1);
 | 
			
		||||
	log_set_use_color(osmo_stderr_target, 0);
 | 
			
		||||
 | 
			
		||||
	while (1) {
 | 
			
		||||
		int c;
 | 
			
		||||
		long long int val;
 | 
			
		||||
		char *endptr;
 | 
			
		||||
		int option_index = 0;
 | 
			
		||||
 | 
			
		||||
		static struct option long_options[] = {
 | 
			
		||||
			{ "format", 1, 0, 'f' },
 | 
			
		||||
			{ "no-csv-headers", 0, 0, 'H' },
 | 
			
		||||
			{ "daemon", 0, 0, 'd' },
 | 
			
		||||
			{ "mdns-ip", 1, 0, 'm' },
 | 
			
		||||
			{ "mdns-port", 1, 0, 'M' },
 | 
			
		||||
			{ "mdns-domain-suffix", 1, 0, 'D' },
 | 
			
		||||
			{ "timeout", 1, 0, 'T' },
 | 
			
		||||
			{ "min-delay", 1, 0, 't' },
 | 
			
		||||
			{ "socket", 1, 0, 's' },
 | 
			
		||||
			{ "send", 0, 0, 'S' },
 | 
			
		||||
			{ "quiet", 0, 0, 'q' },
 | 
			
		||||
			{ "help", 0, 0, 'h' },
 | 
			
		||||
			{ "version", 0, 0, 'V' },
 | 
			
		||||
			{}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
#define PARSE_INT(TARGET, MINVAL, MAXVAL) do { \
 | 
			
		||||
		int _rc; \
 | 
			
		||||
		TARGET = parse_int(MINVAL, MAXVAL, optarg, &_rc); \
 | 
			
		||||
		if (_rc) { \
 | 
			
		||||
			print_error("Invalid " #TARGET ": %s\n", optarg); \
 | 
			
		||||
			goto program_exit; \
 | 
			
		||||
		} \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
		c = getopt_long(argc, argv, "f:Hdm:M:D:t:T:s:SqhV", long_options, &option_index);
 | 
			
		||||
 | 
			
		||||
		if (c == -1)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		switch (c) {
 | 
			
		||||
		case 'f':
 | 
			
		||||
			cmdline_opts.format_str = optarg;
 | 
			
		||||
			break;
 | 
			
		||||
		case 'H':
 | 
			
		||||
			cmdline_opts.csv_headers = false;
 | 
			
		||||
			break;
 | 
			
		||||
		case 'd':
 | 
			
		||||
			cmdline_opts.daemon = true;
 | 
			
		||||
			break;
 | 
			
		||||
		case 'm':
 | 
			
		||||
			if (osmo_sockaddr_str_from_str(&cmdline_opts.mdns_addr, optarg, cmdline_opts.mdns_addr.port)
 | 
			
		||||
			    || !osmo_sockaddr_str_is_nonzero(&cmdline_opts.mdns_addr)) {
 | 
			
		||||
				print_error("Invalid mDNS IP address: %s\n", optarg);
 | 
			
		||||
				goto program_exit;
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
		case 'M':
 | 
			
		||||
			PARSE_INT(cmdline_opts.mdns_addr.port, 1, 65535);
 | 
			
		||||
			break;
 | 
			
		||||
		case 'D':
 | 
			
		||||
			cmdline_opts.mdns_domain_suffix = optarg;
 | 
			
		||||
			break;
 | 
			
		||||
		case 't':
 | 
			
		||||
			PARSE_INT(cmdline_opts.min_delay, 0, UINT32_MAX);
 | 
			
		||||
			break;
 | 
			
		||||
		case 'T':
 | 
			
		||||
			PARSE_INT(cmdline_opts.timeout, 0, UINT32_MAX);
 | 
			
		||||
			break;
 | 
			
		||||
		case 's':
 | 
			
		||||
			cmdline_opts.socket_path = optarg;
 | 
			
		||||
			break;
 | 
			
		||||
		case 'S':
 | 
			
		||||
			cmdline_opts.send = true;
 | 
			
		||||
			break;
 | 
			
		||||
		case 'q':
 | 
			
		||||
			cmdline_opts.quiet = true;
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		case 'h':
 | 
			
		||||
			print_help();
 | 
			
		||||
			rc = 0;
 | 
			
		||||
			goto program_exit;
 | 
			
		||||
		case 'V':
 | 
			
		||||
			print_version();
 | 
			
		||||
			rc = 0;
 | 
			
		||||
			goto program_exit;
 | 
			
		||||
 | 
			
		||||
		default:
 | 
			
		||||
			/* catch unknown options *as well as* missing arguments. */
 | 
			
		||||
			print_error("Error in command line options. Exiting.\n");
 | 
			
		||||
			goto program_exit;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (cmdline_opts.send) {
 | 
			
		||||
		if (cmdline_opts.daemon || cmdline_opts.format_str || cmdline_opts.socket_path) {
 | 
			
		||||
			print_error("--send option cannot have any listening related args.");
 | 
			
		||||
		}
 | 
			
		||||
		rc = do_send(argc - optind, argv + optind);
 | 
			
		||||
		goto program_exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!cmdline_opts.daemon && !(argc - optind)) {
 | 
			
		||||
		print_help();
 | 
			
		||||
		goto program_exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (cmdline_opts.daemon && !cmdline_opts.timeout) {
 | 
			
		||||
		print_error("In daemon mode, --timeout must not be zero.\n");
 | 
			
		||||
		goto program_exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (cmdline_opts.quiet)
 | 
			
		||||
		log_target_destroy(osmo_stderr_target);
 | 
			
		||||
 | 
			
		||||
	if (cmdline_opts.format_str) {
 | 
			
		||||
		if (osmo_str_startswith("json", cmdline_opts.format_str))
 | 
			
		||||
			globals.format = FORMAT_JSON;
 | 
			
		||||
		else if (osmo_str_startswith("csv", cmdline_opts.format_str))
 | 
			
		||||
			globals.format = FORMAT_CSV;
 | 
			
		||||
		else {
 | 
			
		||||
			print_error("Invalid format: %s\n", cmdline_opts.format_str);
 | 
			
		||||
			goto program_exit;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (globals.format == FORMAT_CSV && cmdline_opts.csv_headers)
 | 
			
		||||
		respond_str_stdout(CSV_HEADERS);
 | 
			
		||||
 | 
			
		||||
	globals.mslookup_client = osmo_mslookup_client_new(globals.ctx);
 | 
			
		||||
	if (!globals.mslookup_client
 | 
			
		||||
	    || !osmo_mslookup_client_add_mdns(globals.mslookup_client,
 | 
			
		||||
					      cmdline_opts.mdns_addr.ip, cmdline_opts.mdns_addr.port,
 | 
			
		||||
					      -1, cmdline_opts.mdns_domain_suffix)) {
 | 
			
		||||
		print_error("Failed to start mDNS client\n");
 | 
			
		||||
		goto program_exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (cmdline_opts.socket_path) {
 | 
			
		||||
		if (socket_init(cmdline_opts.socket_path))
 | 
			
		||||
			goto program_exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	start_query_strs(&argv[optind], argc - optind);
 | 
			
		||||
 | 
			
		||||
	while (1) {
 | 
			
		||||
		osmo_select_main_ctx(0);
 | 
			
		||||
 | 
			
		||||
		if (!cmdline_opts.daemon
 | 
			
		||||
		    && globals.requests_handled
 | 
			
		||||
		    && llist_empty(&globals.queries))
 | 
			
		||||
			break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rc = 0;
 | 
			
		||||
program_exit:
 | 
			
		||||
	osmo_mslookup_client_free(globals.mslookup_client);
 | 
			
		||||
	socket_close();
 | 
			
		||||
	log_fini();
 | 
			
		||||
	talloc_free(globals.ctx);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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 <osmocom/hlr/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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user