Initial (building) start of OsmoCBC (Cell Broadcast Centre)

this is far from doing anything useful yet. Stay tuned.

Change-Id: I64a6833ce068db60d713d0fa318a1d8093e37be9
This commit is contained in:
Harald Welte 2019-05-06 13:19:26 +02:00
parent 6c8bf25d11
commit 63b7905fa3
14 changed files with 1026 additions and 29 deletions

59
.gitignore vendored Normal file
View File

@ -0,0 +1,59 @@
*.o
*.a
*.lo
*.la
.deps
Makefile
Makefile.in
config.h
config.h.in
#configure
aclocal.m4
autom4te.cache/
compile
config.log
config.status
configure
configure.lineno
depcomp
install-sh
missing
stamp-h1
# libtool
ltmain.sh
libtool
.libs
# git-version-gen magic
.tarball-version
.version
# apps and app data
src/osmo-cbc
#tests
tests/*/*_test
tests/atconfig
tests/package.m4
tests/testsuite
tests/testsuite.log
*.pc
config.*
tags
/Doxyfile
# 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

17
Makefile.am Normal file
View File

@ -0,0 +1,17 @@
AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
SUBDIRS = src
EXTRA_DIST = .version git-version-gen osmoappdesc.py doc/examples/osmo-cbc.cfg
AM_DISTCHECK_CONFIGURE_FLAGS = \
--with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
@RELMAKE@
BUILT_SOURCES = $(top_srcdir)/.version
$(top_srcdir)/.version:
echo $(VERSION) > $@-t && mv $@-t $@
dist-hook:
echo $(VERSION) > $(distdir)/.tarball-version

169
configure.ac Normal file
View File

@ -0,0 +1,169 @@
dnl Process this file with autoconf to produce a configure script
AC_INIT([osmo-cbc],
m4_esyscmd([./git-version-gen .tarball-version]),
[openbsc@lists.osmocom.org])
dnl *This* is the root dir, even if an install-sh exists in ../ or ../../
AC_CONFIG_AUX_DIR([.])
AM_INIT_AUTOMAKE([dist-bzip2])
AC_CONFIG_TESTDIR(tests)
dnl kernel style compile messages
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
dnl include release helper
RELMAKE='-include osmo-release.mk'
AC_SUBST([RELMAKE])
dnl checks for programs
AC_PROG_MAKE_SET
AC_PROG_CC
AC_PROG_INSTALL
LT_INIT
dnl check for pkg-config (explained in detail in libosmocore/configure.ac)
AC_PATH_PROG(PKG_CONFIG_INSTALLED, pkg-config, no)
if test "x$PKG_CONFIG_INSTALLED" = "xno"; then
AC_MSG_WARN([You need to install pkg-config])
fi
PKG_PROG_PKG_CONFIG([0.20])
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.0.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.0.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.0.0)
PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 0.4.0)
AC_ARG_ENABLE(sanitize,
[AS_HELP_STRING(
[--enable-sanitize],
[Compile with address sanitizer enabled],
)],
[sanitize=$enableval], [sanitize="no"])
if test x"$sanitize" = x"yes"
then
CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined"
CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined"
fi
AC_ARG_ENABLE(werror,
[AS_HELP_STRING(
[--enable-werror],
[Turn all compiler warnings into errors, with exceptions:
a) deprecation (allow upstream to mark deprecation without breaking builds);
b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds)
]
)],
[werror=$enableval], [werror="no"])
if test x"$werror" = x"yes"
then
WERROR_FLAGS="-Werror"
WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations"
WERROR_FLAGS+=" -Wno-error=cpp" # "#warning"
CFLAGS="$CFLAGS $WERROR_FLAGS"
CPPFLAGS="$CPPFLAGS $WERROR_FLAGS"
fi
# The following test is taken from WebKit's webkit.m4
saved_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS -fvisibility=hidden "
AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden])
AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])],
[ AC_MSG_RESULT([yes])
SYMBOL_VISIBILITY="-fvisibility=hidden"],
AC_MSG_RESULT([no]))
CFLAGS="$saved_CFLAGS"
AC_SUBST(SYMBOL_VISIBILITY)
CFLAGS="$CFLAGS -Wall"
CPPFLAGS="$CPPFLAGS -Wall"
AC_ARG_ENABLE(doxygen,
[AS_HELP_STRING(
[--disable-doxygen],
[Disable generation of documentation using doxygen],
)],
[doxygen=$enableval], [doxygen="yes"])
AC_PATH_PROG(DOXYGEN,doxygen,false)
AM_CONDITIONAL(HAVE_DOXYGEN, test $DOXYGEN != false && test "x$doxygen" = "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_ARG_ENABLE([external_tests],
AC_HELP_STRING([--enable-external-tests],
[Include the VTY/CTRL tests in make check [default=no]]),
[enable_ext_tests="$enableval"],[enable_ext_tests="no"])
if test "x$enable_ext_tests" = "xyes" ; then
AM_PATH_PYTHON
AC_CHECK_PROG(OSMOTESTEXT_CHECK,osmo_verify_transcript_vty.py,yes)
if test "x$OSMOTESTEXT_CHECK" != "xyes" ; then
AC_MSG_ERROR([Please install git://osmocom.org/python/osmo-python-tests to run the VTY/CTRL tests.])
fi
fi
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")
AC_MSG_RESULT([CFLAGS="$CFLAGS"])
AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"])
AC_OUTPUT(
src/Makefile
contrib/Makefile
contrib/systemd/Makefile
Makefile)

1
contrib/Makefile.am Normal file
View File

@ -0,0 +1 @@
SUBDIRS = systemd

61
contrib/jenkins.sh Executable file
View File

@ -0,0 +1,61 @@
#!/bin/sh
# jenkins build helper script for osmo-cbc. 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 !"
exit 2
fi
set -ex
base="$PWD"
deps="$base/deps"
inst="$deps/install"
export deps inst
osmo-clean-workspace.sh
mkdir "$deps" || true
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 "" --disable-doxygen
osmo-build-dep.sh libosmo-netif
# 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
echo
echo " =============================== osmo-cbc ==============================="
echo
set -x
autoreconf --install --force
./configure --enable-sanitize --enable-werror --enable-external-tests $CONFIG
$MAKE $PARALLEL_MAKE
DISTCHECK_CONFIGURE_FLAGS="--enable-external-tests $CONFIG" \
$MAKE distcheck \
|| cat-testlogs.sh
if [ "$WITH_MANUALS" = "1" ] && [ "$PUBLISH" = "1" ]; then
make -C "$base/doc/manuals" publish
fi
osmo-clean-workspace.sh

View File

@ -0,0 +1,6 @@
EXTRA_DIST = osmo-cbc.service
if HAVE_SYSTEMD
systemdsystemunit_DATA = \
osmo-cbc.service
endif

View File

@ -0,0 +1,11 @@
[Unit]
Description=Osmocom CBC (Cell Broadcasting Centre)
[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/osmo-cbc -c /etc/osmocom/osmo-cbc.cfg
RestartSec=2
[Install]
WantedBy=multi-user.target

151
git-version-gen Executable file
View File

@ -0,0 +1,151 @@
#!/bin/sh
# Print a version string.
scriptversion=2010-01-28.01
# Copyright (C) 2007-2010 Free Software Foundation, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
# It may be run two ways:
# - from a git repository in which the "git describe" command below
# produces useful output (thus requiring at least one signed tag)
# - from a non-git-repo directory containing a .tarball-version file, which
# presumes this script is invoked like "./git-version-gen .tarball-version".
# In order to use intra-version strings in your project, you will need two
# separate generated version string files:
#
# .tarball-version - present only in a distribution tarball, and not in
# a checked-out repository. Created with contents that were learned at
# the last time autoconf was run, and used by git-version-gen. Must not
# be present in either $(srcdir) or $(builddir) for git-version-gen to
# give accurate answers during normal development with a checked out tree,
# but must be present in a tarball when there is no version control system.
# Therefore, it cannot be used in any dependencies. GNUmakefile has
# hooks to force a reconfigure at distribution time to get the value
# correct, without penalizing normal development with extra reconfigures.
#
# .version - present in a checked-out repository and in a distribution
# tarball. Usable in dependencies, particularly for files that don't
# want to depend on config.h but do want to track version changes.
# Delete this file prior to any autoconf run where you want to rebuild
# files to pick up a version string change; and leave it stale to
# minimize rebuild time after unrelated changes to configure sources.
#
# It is probably wise to add these two files to .gitignore, so that you
# don't accidentally commit either generated file.
#
# Use the following line in your configure.ac, so that $(VERSION) will
# automatically be up-to-date each time configure is run (and note that
# since configure.ac no longer includes a version string, Makefile rules
# should not depend on configure.ac for version updates).
#
# AC_INIT([GNU project],
# m4_esyscmd([build-aux/git-version-gen .tarball-version]),
# [bug-project@example])
#
# Then use the following lines in your Makefile.am, so that .version
# will be present for dependencies, and so that .tarball-version will
# exist in distribution tarballs.
#
# BUILT_SOURCES = $(top_srcdir)/.version
# $(top_srcdir)/.version:
# echo $(VERSION) > $@-t && mv $@-t $@
# dist-hook:
# echo $(VERSION) > $(distdir)/.tarball-version
case $# in
1) ;;
*) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;;
esac
tarball_version_file=$1
nl='
'
# First see if there is a tarball-only version file.
# then try "git describe", then default.
if test -f $tarball_version_file
then
v=`cat $tarball_version_file` || exit 1
case $v in
*$nl*) v= ;; # reject multi-line output
[0-9]*) ;;
*) v= ;;
esac
test -z "$v" \
&& echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
fi
if test -n "$v"
then
: # use $v
elif
v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \
|| git describe --abbrev=4 HEAD 2>/dev/null` \
&& case $v in
[0-9]*) ;;
v[0-9]*) ;;
*) (exit 1) ;;
esac
then
# Is this a new git that lists number of commits since the last
# tag or the previous older version that did not?
# Newer: v6.10-77-g0f8faeb
# Older: v6.10-g0f8faeb
case $v in
*-*-*) : git describe is okay three part flavor ;;
*-*)
: git describe is older two part flavor
# Recreate the number of commits and rewrite such that the
# result is the same as if we were using the newer version
# of git describe.
vtag=`echo "$v" | sed 's/-.*//'`
numcommits=`git rev-list "$vtag"..HEAD | wc -l`
v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
;;
esac
# Change the first '-' to a '.', so version-comparing tools work properly.
# Remove the "g" in git describe's output string, to save a byte.
v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
else
v=UNKNOWN
fi
v=`echo "$v" |sed 's/^v//'`
# Don't declare a version "dirty" merely because a time stamp has changed.
git status > /dev/null 2>&1
dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
case "$dirty" in
'') ;;
*) # Append the suffix only if there isn't one already.
case $v in
*-dirty) ;;
*) v="$v-dirty" ;;
esac ;;
esac
# Omit the trailing newline, so that m4_esyscmd can use the result directly.
echo "$v" | tr -d '\012'
# Local variables:
# eval: (add-hook 'write-file-hooks 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-end: "$"
# End:

9
src/Makefile.am Normal file
View File

@ -0,0 +1,9 @@
AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/src
AM_CFLAGS=-Wall -g $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMONETIF_CFLAGS) $(COVERAGE_CFLAGS)
AM_LDFLAGS=$(COVERAGE_LDFLAGS)
bin_PROGRAMS = osmo-cbc
osmo_cbc_SOURCES = cbc_main.c cbsp_server.c cbsp_server_fsm.c
osmo_cbc_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMONETIF_LIBS)

200
src/cbc_main.c Normal file
View File

@ -0,0 +1,200 @@
/* Osmocom CBC (Cell Broacast Centre) */
/* (C) 2019 by Harald Welte <laforge@gnumonks.org>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <signal.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/select.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/signal.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/application.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/stats.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/ports.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/misc.h>
#include "internal.h"
#include "cbsp_server.h"
static void *tall_cbc_ctx;
static const struct log_info_cat log_info_cat[] = {
[DCBSP] = {
.name = "DCBSP",
.description = "Cell Broadcast Service Protocol (CBC-BSC)",
.color = "\033[1;31m",
.enabled = 1,
.loglevel = LOGL_NOTICE,
},
};
static const struct log_info log_info = {
.cat = log_info_cat,
.num_cat = ARRAY_SIZE(log_info_cat),
};
static const char cbc_copyright[] =
"Copyright (C) 2019 by Harald Welte <laforge@gnumonks.org>\r\n"
"License AGPLv3+: GNU Affero GPL Version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
"This is free software: you are free ot change and redistribute it.\r\n"
"There is NO WARRANTY, to the extent permitted by law.\r\n\r\n"
"Free Software lives by contribution. If you use this, please contribute!\r\n";
static struct vty_app_info vty_info = {
.name = "OsmoCBC",
.copyright = cbc_copyright,
.version = PACKAGE_VERSION,
.go_parent_cb = NULL,
.is_config_node = NULL,
};
static struct {
bool daemonize;
const char *config_file;
} cmdline_config = {
.daemonize = false,
.config_file = "osmo-cbc.cfg",
};
static void print_help(void)
{
/* FIXME */
}
static void handle_options(int argc, char **argv)
{
while (1) {
int option_index = 0, c;
static const struct option long_options[] = {
{ "help", 0, 0, 'h' },
{ "daemonize", 0, 0, 'D' },
{ "config-file", 1, 0, 'c' },
{ "version", 0, 0, 'V' },
{ NULL, 0, 0, 0 }
};
c = getopt_long(argc, argv, "hDc:V", long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'h':
print_help();
exit(0);
break;
case 'D':
cmdline_config.daemonize = true;
break;
case 'c':
cmdline_config.config_file = optarg;
break;
case 'V':
print_version(1);
exit(0);
break;
default:
fprintf(stderr, "Error in command line options. Exiting\n");
exit(1);
break;
}
}
}
static void signal_handler(int signal)
{
fprintf(stdout, "signal %d received\n", signal);
switch (signal){
case SIGUSR1:
talloc_report(tall_vty_ctx, stderr);
talloc_report_full(tall_cbc_ctx, stderr);
break;
case SIGUSR2:
talloc_report_full(tall_vty_ctx, stderr);
break;
default:
break;
}
}
extern int cbc_client_rx_cb(struct osmo_cbsp_cbc_client *client, struct osmo_cbsp_decoded *dec);
int main(int argc, char **argv)
{
int rc;
tall_cbc_ctx = talloc_named_const(NULL, 1, "osmo-cbc");
msgb_talloc_ctx_init(tall_cbc_ctx, 0);
osmo_init_logging2(tall_cbc_ctx, &log_info);
osmo_stats_init(tall_cbc_ctx);
vty_init(&vty_info);
handle_options(argc, argv);
logging_vty_add_cmds();
osmo_fsm_vty_add_cmds();
rc = vty_read_config_file(cmdline_config.config_file, NULL);
if (rc < 0) {
fprintf(stderr, "Failed ot parse the config file '%s'\n",
cmdline_config.config_file);
exit(1);
}
rc = telnet_init_dynif(tall_cbc_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_CBC);
if (rc < 0) {
perror("Error binding VTY port\n");
exit(1);
}
cbsp_cbc_create(tall_cbc_ctx, &cbc_client_rx_cb);
signal(SIGUSR1, &signal_handler);
signal(SIGUSR2, &signal_handler);
osmo_init_ignore_signals();
if (cmdline_config.daemonize) {
rc = osmo_daemonize();
if (rc < 0) {
perror("Error during daemonize");
exit(1);
}
}
while (1) {
rc = osmo_select_main(0);
if (rc < 0)
exit(3);
}
}

View File

@ -1,34 +1,38 @@
/* (C) 2019 by Harald Welte <laforge@gnumonks.org>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/logging.h>
#include <osmocom/gsm/cbsp.h>
#include <osmocom/gsm/protocol/gsm_48_049.h>
#include <osmocom/netif/stream.h>
/* a CBC server */
struct osmo_cbsp_cbc {
/* libosmo-netif stream server */
struct osmo_stream_srv_link *link;
/* BSCs / clients connected to this CBC */
struct llist_head clients;
};
/* a single (remote) client connected to the (local) CBC server */
struct osmo_cbsp_cbc_client {
/* entry in osmo_cbsp_cbc.clients */
struct llist_head list;
/* stream server connection for this client */
struct osmo_stream_srv *conn;
/* partially received CBSP message (rx completion pending) */
struct msgb *rx_msg;
/* receive call-back; called for every received message */
int (*rx_cb)(struct osmo_cbsp_cbc_client *client, struct osmo_cbsp_decoded *dec);
};
#include "internal.h"
#include "cbsp_server.h"
#if 0
struct osmo_cbsp_bsc {
@ -92,8 +96,9 @@ int osmo_cbsp_recv_buffered(int fd, struct msgb **rmsg, struct msgb **tmp_msg)
msg->l2h = msg->tail;
}
h = (struct cbsp_header *) msg->data;
/* then read the length as specified in the header */
len = h->len[0] << 16 | h->len[1] << 8 | h->len[0];
len = h->len[0] << 16 | h->len[1] << 8 | h->len[2];
needed = len - msgb_l2len(msg);
if (needed > 0) {
@ -142,17 +147,25 @@ discard_msg:
const char *cbsp_cbc_client_name(const struct osmo_cbsp_cbc_client *client)
{
struct osmo_fd *ofd = osmo_stream_srv_get_ofd(client->conn);
return osmo_sock_get_name2(ofd->fd);
}
/* data from BSC has arrived at CBC */
static int cbsp_cbc_read_cb(struct osmo_stream_srv *conn)
{
struct osmo_stream_srv_link *link = osmo_stream_srv_get_master(conn);
struct osmo_cbsp_cbc_client *client = osmo_stream_srv_get_data(conn);
struct osmo_cbsp_cbc *cbc = osmo_stream_srv_link_get_data(link);
struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn);
struct osmo_cbsp_decoded *decoded;
struct msgb *msg = NULL;
int rc;
LOGPCC(client, LOGL_DEBUG, "read_cb rx_msg=%p\n", client->rx_msg);
/* message de-segmentation */
rc = osmo_cbsp_recv_buffered(ofd->fd, &msg, &client->rx_msg);
if (rc <= 0) {
@ -163,16 +176,24 @@ static int cbsp_cbc_read_cb(struct osmo_stream_srv *conn)
/* lost connection with server */
} else if (rc == 0) {
/* connection closed with server */
}
/* destroy connection */
osmo_stream_srv_destroy(conn);
return -EBADF;
}
OSMO_ASSERT(msg);
LOGPCC(client, LOGL_DEBUG, "Received CBSP %s\n", msgb_hexdump(msg));
/* decode + dispatch message */
decoded = osmo_cbsp_decode(client, msg);
if (decoded) {
LOGPCC(client, LOGL_INFO, "Received CBSP %s\n",
get_value_string(cbsp_msg_type_names, decoded->msg_type));
cbc->rx_cb(client, decoded);
} else {
LOGPCC(client, LOGL_ERROR, "Unable to decode %s\n", msgb_hexdump(msg));
}
msgb_free(msg);
if (decoded)
client->rx_cb(client, decoded);
return 0;
}
@ -180,6 +201,7 @@ static int cbsp_cbc_read_cb(struct osmo_stream_srv *conn)
static int cbsp_cbc_closed_cb(struct osmo_stream_srv *conn)
{
struct osmo_cbsp_cbc_client *client = osmo_stream_srv_get_data(conn);
LOGPCC(client, LOGL_INFO, "connection closed\n");
llist_del(&client->list);
talloc_free(client);
return 0;
@ -194,9 +216,19 @@ static int cbsp_cbc_accept_cb(struct osmo_stream_srv_link *link, int fd)
client->conn = osmo_stream_srv_create(link, link, fd, cbsp_cbc_read_cb, cbsp_cbc_closed_cb, client);
if (!client->conn) {
LOGP(DCBSP, LOGL_ERROR, "Unable to create stream server for %s\n",
osmo_sock_get_name2(fd));
talloc_free(client);
return -1;
}
client->fi = osmo_fsm_inst_alloc(&cbsp_server_fsm, client, client, LOGL_DEBUG, NULL);
if (!client->fi) {
LOGPCC(client, LOGL_ERROR, "Unable to allocate FSM\n");
osmo_stream_srv_destroy(client->conn);
talloc_free(client);
return -1;
}
LOGPCC(client, LOGL_INFO, "New CBSP client connection\n");
llist_add_tail(&client->list, &cbc->clients);
return 0;
@ -205,29 +237,37 @@ static int cbsp_cbc_accept_cb(struct osmo_stream_srv_link *link, int fd)
void cbsp_cbc_client_tx(struct osmo_cbsp_cbc_client *client, struct osmo_cbsp_decoded *cbsp)
{
struct msgb *msg = osmo_cbsp_encode(cbsp);
talloc_free(cbsp);
LOGPCC(client, LOGL_INFO, "Transmitting %s\n",
get_value_string(cbsp_msg_type_names, cbsp->msg_type));
if (!msg) {
/* FIXME */
LOGPCC(client, LOGL_ERROR, "Failed to encode CBSP %s\n",
get_value_string(cbsp_msg_type_names, cbsp->msg_type));
talloc_free(cbsp);
return;
}
talloc_free(cbsp);
osmo_stream_srv_send(client->conn, msg);
}
/* initialize the CBC-side CBSP server */
struct osmo_cbsp_cbc *cbsp_cbc_create(void *ctx)
struct osmo_cbsp_cbc *cbsp_cbc_create(void *ctx, int (*rx_cb)(struct osmo_cbsp_cbc_client *client,
struct osmo_cbsp_decoded *dec))
{
struct osmo_cbsp_cbc *cbc = talloc_zero(ctx, struct osmo_cbsp_cbc);
int rc;
OSMO_ASSERT(cbc);
cbc->rx_cb = rx_cb;
INIT_LLIST_HEAD(&cbc->clients);
cbc->link = osmo_stream_srv_link_create(cbc);
osmo_stream_srv_link_set_data(cbc->link, cbc);
osmo_stream_srv_link_set_nodelay(cbc->link, true);
osmo_stream_srv_link_set_port(cbc->link, 48049);
osmo_stream_srv_link_set_port(cbc->link, CBSP_TCP_PORT);
osmo_stream_srv_link_set_accept_cb(cbc->link, cbsp_cbc_accept_cb);
rc = osmo_stream_srv_link_open(cbc->link);
OSMO_ASSERT(rc == 0);
LOGP(DCBSP, LOGL_NOTICE, "Listening for CBSP at %s\n",
osmo_stream_srv_link_get_sockname(cbc->link));
return cbc;
}

39
src/cbsp_server.h Normal file
View File

@ -0,0 +1,39 @@
#pragma once
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/cbsp.h>
#include <osmocom/netif/stream.h>
#define LOGPCC(client, level, fmt, args...) \
LOGP(DCBSP, level, "%s: " fmt, cbsp_cbc_client_name(client), ## args)
struct osmo_cbsp_cbc_client;
struct osmo_fsm_inst;
/* a CBC server */
struct osmo_cbsp_cbc {
/* libosmo-netif stream server */
struct osmo_stream_srv_link *link;
/* BSCs / clients connected to this CBC */
struct llist_head clients;
/* receive call-back; called for every received message */
int (*rx_cb)(struct osmo_cbsp_cbc_client *client, struct osmo_cbsp_decoded *dec);
};
/* a single (remote) client connected to the (local) CBC server */
struct osmo_cbsp_cbc_client {
/* entry in osmo_cbsp_cbc.clients */
struct llist_head list;
/* stream server connection for this client */
struct osmo_stream_srv *conn;
/* partially received CBSP message (rx completion pending) */
struct msgb *rx_msg;
struct osmo_fsm_inst *fi;
};
const char *cbsp_cbc_client_name(const struct osmo_cbsp_cbc_client *client);
void cbsp_cbc_client_tx(struct osmo_cbsp_cbc_client *client, struct osmo_cbsp_decoded *cbsp);
struct osmo_cbsp_cbc *cbsp_cbc_create(void *ctx, int (*rx_cb)(struct osmo_cbsp_cbc_client *client,
struct osmo_cbsp_decoded *dec));

216
src/cbsp_server_fsm.c Normal file
View File

@ -0,0 +1,216 @@
/* (C) 2019 by Harald Welte <laforge@gnumonks.org>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/core/fsm.h>
#include <osmocom/gsm/cbsp.h>
#include "cbsp_server.h"
#include "internal.h"
#define S(x) (1 << (x))
#define T_KEEPALIVE 1
#define T_KEEPALIVE_SECS 30
#define T_WAIT_KEEPALIVE_RESP 2
#define T_WAIT_KEEPALIVE_RESP_SECS 10
#define T_WAIT_RESET_RESP 3
#define T_WAIT_RESET_RESP_SECS 5
enum cbsp_server_state {
/* initial state after client TCP connection established */
CBSP_SRV_S_INIT,
/* RESET has been sent to BSC, waiting for response */
CBSP_SRV_S_RESET_PENDING,
/* Keep-Alive has been sent, waiting for response */
CBSP_SRV_S_KEEPALIVE_PENDING,
/* normal operation (idle) */
CBSP_SRV_S_IDLE,
};
static const struct value_string cbsp_server_event_names[] = {
{ CBSP_SRV_E_RX_RST_COMPL, "Rx Reset Complete" },
{ CBSP_SRV_E_RX_RST_FAIL, "Rx Reset Failure" },
{ CBSP_SRV_E_RX_KA_COMPL, "Rx Keep-Alive Complete" },
{ CBSP_SRV_E_RX_RESTART, "Rx Restart" },
{ CBSP_SRV_E_CMD_RESET, "RESET.cmd" },
{ 0, NULL }
};
static void cbsp_server_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case CBSP_SRV_E_CMD_RESET:
osmo_fsm_inst_state_chg(fi, CBSP_SRV_S_RESET_PENDING, 0, 0);
break;
default:
OSMO_ASSERT(0);
}
}
static void cbsp_server_s_reset_pending_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct osmo_cbsp_cbc_client *client = (struct osmo_cbsp_cbc_client *) fi->priv;
struct osmo_cbsp_decoded *cbspd;
if (prev_state == CBSP_SRV_S_RESET_PENDING)
return;
cbspd = talloc_zero(fi, struct osmo_cbsp_decoded);
OSMO_ASSERT(cbspd);
cbspd->msg_type = CBSP_MSGT_RESET;
cbspd->u.reset.cell_list.id_discr = CELL_IDENT_BSS;
INIT_LLIST_HEAD(&cbspd->u.reset.cell_list.list);
cbsp_cbc_client_tx(client, cbspd);
/* wait for response */
osmo_fsm_inst_state_chg(fi, CBSP_SRV_S_RESET_PENDING, T_WAIT_RESET_RESP_SECS,
T_WAIT_RESET_RESP);
}
static void cbsp_server_s_reset_pending(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case CBSP_SRV_E_RX_RST_COMPL:
osmo_fsm_inst_state_chg(fi, CBSP_SRV_S_IDLE, 0, 0);
break;
case CBSP_SRV_E_RX_RST_FAIL:
osmo_fsm_inst_state_chg(fi, CBSP_SRV_S_IDLE, 0, 0);
break;
default:
OSMO_ASSERT(0);
}
}
static void cbsp_server_s_keepalive_pending(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case CBSP_SRV_E_RX_KA_COMPL:
break;
default:
OSMO_ASSERT(0);
}
}
/* a bit of a hack to ensure the keep-aliver timer is started every time we enter
* the IDLE state, without putting the burden on the caller of
* osmo_fsm_inst_state_chg() to specify T_KEEPALIVE + T_KEEPALIVE_SECS */
static void cbsp_server_s_idle_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
fi->T = T_KEEPALIVE;
osmo_timer_schedule(&fi->timer, T_KEEPALIVE_SECS, 0);
}
static void cbsp_server_s_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
default:
OSMO_ASSERT(0);
}
}
static int cbsp_server_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct osmo_cbsp_cbc_client *client = (struct osmo_cbsp_cbc_client *) fi->priv;
struct osmo_cbsp_decoded *cbspd;
switch (fi->T) {
case T_KEEPALIVE:
/* send keepalive to peer */
cbspd = talloc_zero(fi, struct osmo_cbsp_decoded);
OSMO_ASSERT(cbspd);
cbspd->msg_type = CBSP_MSGT_KEEP_ALIVE;
cbspd->u.keep_alive.repetition_period = T_KEEPALIVE_SECS;
cbsp_cbc_client_tx(client, cbspd);
/* wait for response */
osmo_fsm_inst_state_chg(fi, CBSP_SRV_S_KEEPALIVE_PENDING, T_WAIT_KEEPALIVE_RESP_SECS,
T_WAIT_KEEPALIVE_RESP);
return 0;
case T_WAIT_KEEPALIVE_RESP:
case T_WAIT_RESET_RESP:
/* ask core to terminate FSM which will terminate TCP connection */
return 1;
default:
OSMO_ASSERT(0);
}
}
static const struct osmo_fsm_state cbsp_server_fsm_states[] = {
[CBSP_SRV_S_INIT] = {
.name = "INIT",
.in_event_mask = S(CBSP_SRV_E_CMD_RESET),
.out_state_mask = S(CBSP_SRV_S_RESET_PENDING),
.action = cbsp_server_s_init,
},
[CBSP_SRV_S_RESET_PENDING] = {
.name = "RESET_PENDING",
.in_event_mask = S(CBSP_SRV_E_RX_RST_COMPL) |
S(CBSP_SRV_E_RX_RST_FAIL),
.out_state_mask = S(CBSP_SRV_S_IDLE) |
S(CBSP_SRV_S_RESET_PENDING),
.action = cbsp_server_s_reset_pending,
.onenter = cbsp_server_s_reset_pending_onenter,
},
[CBSP_SRV_S_KEEPALIVE_PENDING] = {
.name = "KEEPALIVE_PENDING",
.in_event_mask = S(CBSP_SRV_E_RX_KA_COMPL),
.out_state_mask = S(CBSP_SRV_S_IDLE) |
S(CBSP_SRV_S_KEEPALIVE_PENDING),
.action = cbsp_server_s_keepalive_pending,
},
[CBSP_SRV_S_IDLE] = {
.name = "IDLE",
.in_event_mask = 0,
.out_state_mask = S(CBSP_SRV_S_KEEPALIVE_PENDING),
.action = cbsp_server_s_idle,
.onenter = cbsp_server_s_idle_onenter,
},
};
struct osmo_fsm cbsp_server_fsm = {
.name = "CBSP-SERVER",
.states = cbsp_server_fsm_states,
.num_states = ARRAY_SIZE(cbsp_server_fsm_states),
.timer_cb = cbsp_server_fsm_timer_cb,
.log_subsys = DCBSP,
.event_names = cbsp_server_event_names,
};
/* message was received from remote CBSP peer (BSC) */
int cbc_client_rx_cb(struct osmo_cbsp_cbc_client *client, struct osmo_cbsp_decoded *dec)
{
switch (dec->msg_type) {
case CBSP_MSGT_RESTART:
osmo_fsm_inst_dispatch(client->fi, CBSP_SRV_E_RX_RESTART, dec);
break;
default:
LOGPCC(client, LOGL_ERROR, "unknown/unhandled %s\n",
get_value_string(cbsp_msg_type_names, dec->msg_type));
break;
}
return 0;
}
static __attribute__((constructor)) void on_dso_load_cbsp_srv_fsm(void)
{
osmo_fsm_register(&cbsp_server_fsm);
}

18
src/internal.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <osmocom/core/logging.h>
#include <osmocom/core/fsm.h>
enum {
DCBSP,
};
extern struct osmo_fsm cbsp_server_fsm;
enum cbsp_server_event {
CBSP_SRV_E_RX_RST_COMPL,
CBSP_SRV_E_RX_RST_FAIL,
CBSP_SRV_E_RX_KA_COMPL,
CBSP_SRV_E_RX_RESTART,
CBSP_SRV_E_CMD_RESET,
};