Initial WIP code; doens'tdo anything useful yet

This commit is contained in:
Harald Welte 2022-10-13 19:11:06 +02:00
commit 311afac0d2
27 changed files with 2351 additions and 0 deletions

40
.gitignore vendored Normal file
View File

@ -0,0 +1,40 @@
config.*
.version
Makefile
Makefile.in
aclocal.m4
autom4te.cache
compile
configure
configure~
depcomp
install-sh
libtool
ltmain.sh
m4
missing
*.pc
.deps
.libs
debian/.debhelper
debian/autoreconf.*
debian/osmo-isdntap
debian/*.log
debian/tmp
*.la
*.lo
*.o
src/osmo-isdntap
tests/atconfig
tests/atlocal
tests/package.m4
tests/testsuite
tests/testsuite.log
tests/testsuite.dir
tests/*/*_test

26
Makefile.am Normal file
View File

@ -0,0 +1,26 @@
AUTOMAKE_OPTIONS = foreign dist-bzip2
SUBDIRS = \
contrib \
doc \
src \
tests \
$(NULL)
EXTRA_DIST = \
.version \
debian \
doc \
git-version-gen \
$(NULL)
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

121
configure.ac Normal file
View File

@ -0,0 +1,121 @@
AC_INIT([osmo-isdntap],
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([.])
dnl libtool init
LT_INIT
AM_INIT_AUTOMAKE([foreign dist-bzip2 no-dist-gzip 1.9])
AC_CONFIG_TESTDIR(tests)
CFLAGS="$CFLAGS -std=gnu11"
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_MKDIR_P
AC_PROG_CC
AC_PROG_INSTALL
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(TALLOC, [talloc >= 2.0.1])
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.6.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.6.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.6.0)
AC_CONFIG_MACRO_DIR([m4])
dnl checks for header files
AC_HEADER_STDC
AC_ARG_ENABLE(sanitize,
[AS_HELP_STRING(
[--enable-sanitize],
[Compile with address sanitizer enabled],
)],
[sanitize=$enableval], [sanitize="no"])
if test x"$sanitize" = x"yes"
then
CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined"
CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined"
fi
AC_ARG_ENABLE(werror,
[AS_HELP_STRING(
[--enable-werror],
[Turn all compiler warnings into errors, with exceptions:
a) deprecation (allow upstream to mark deprecation without breaking builds);
b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds)
]
)],
[werror=$enableval], [werror="no"])
if test x"$werror" = x"yes"
then
WERROR_FLAGS="-Werror"
WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations"
WERROR_FLAGS+=" -Wno-error=cpp" # "#warning"
CFLAGS="$CFLAGS $WERROR_FLAGS"
CPPFLAGS="$CPPFLAGS $WERROR_FLAGS"
fi
AC_ARG_ENABLE([external_tests],
AC_HELP_STRING([--enable-external-tests],
[Include the VTY/CTRL tests in make check [default=no]]),
[enable_ext_tests="$enableval"],[enable_ext_tests="no"])
if test "x$enable_ext_tests" = "xyes" ; then
AC_CHECK_PROG(PYTHON3_AVAIL,python3,yes)
if test "x$PYTHON3_AVAIL" != "xyes" ; then
AC_MSG_ERROR([Please install python3 to run the VTY/CTRL tests.])
fi
AC_CHECK_PROG(OSMOTESTEXT_CHECK,osmotestvty.py,yes)
if test "x$OSMOTESTEXT_CHECK" != "xyes" ; then
AC_MSG_ERROR([Please install https://gitea.osmocom.org/cellular-infrastructure/osmo-python-tests to run the VTY/CTRL tests.])
fi
fi
AC_MSG_CHECKING([whether to enable VTY/CTRL tests])
AC_MSG_RESULT([$enable_ext_tests])
AM_CONDITIONAL(ENABLE_EXT_TESTS, test "x$enable_ext_tests" = "xyes")
# 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_OUTPUT(
Makefile
contrib/Makefile
contrib/systemd/Makefile
doc/Makefile
doc/examples/Makefile
src/Makefile
tests/Makefile
)

1
contrib/Makefile.am Normal file
View File

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

View File

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

View File

@ -0,0 +1,12 @@
[Unit]
Description=Osmocom ISDN Tap/Trace Daemon
Wants=osmo-isdntap.service
[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/osmo-isdntap -c /etc/osmocom/osmo-isdntap.cfg
RestartSec=2
[Install]
WantedBy=multi-user.target

6
debian/changelog vendored Normal file
View File

@ -0,0 +1,6 @@
osmo-isdntap (0.0.1) unstable; urgency=medium
[ Harald Welte ]
* initial debian package
-- Harald Welte <laforge@osmocom.org> Tue, 30 Jun 2020 18:24:51 +0100

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
9

24
debian/control vendored Normal file
View File

@ -0,0 +1,24 @@
Source: osmo-isdntap
Section: net
Priority: extra
Maintainer: Harald Welte <laforge@osmocom.org>
Build-Depends: debhelper (>=9),
dh-autoreconf,
autotools-dev,
autoconf,
automake,
libtool,
pkg-config,
python3-minimal,
libosmocore-dev (>= 1.6.0),
osmo-gsm-manuals-dev (>= 1.2.0)
Standards-Version: 3.9.8
Vcs-Git: https://gitea.osmocom.org/retronetworking/osmo-isdntap
Vcs-Browser: https://gitea.osmocom.org/retronetworking/osmo-isdntap
Homepage: https://projects.osmocom.org/projects/osmo-isdntap
Package: osmo-isdntap
Architecture: any
Multi-Arch: foreign
Depends: ${misc:Depends}, ${shlibs:Depends}
Description: osmo-isdntap: Osmocom's ISDN Tap/Trace daemon

20
debian/copyright vendored Normal file
View File

@ -0,0 +1,20 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: osmo-isdntap
Source: https://gitea.osmocom.org/retronetworking/osmo-isdntap
Files: *
Copyright: 2022 Harald Welte
License: GPL-2.0+
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 Affero 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/>.

3
debian/osmo-isdntap.install vendored Normal file
View File

@ -0,0 +1,3 @@
/etc/osmocom/osmo-isdntap.cfg
lib/systemd/system/osmo-isdntap.service
usr/bin/osmo-isdntap

58
debian/rules vendored Executable file
View File

@ -0,0 +1,58 @@
#!/usr/bin/make -f
# You must remove unused comment lines for the released package.
# See debhelper(7) (uncomment to enable)
# This is an autogenerated template for debian/rules.
#
# Output every command that modifies files on the build system.
#export DH_VERBOSE = 1
#
# Copy some variable definitions from pkg-info.mk and vendor.mk
# under /usr/share/dpkg/ to here if they are useful.
#
# See FEATURE AREAS/ENVIRONMENT in dpkg-buildflags(1)
# Apply all hardening options
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
# Package maintainers to append CFLAGS
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
# Package maintainers to append LDFLAGS
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
#
# With debhelper version 9 or newer, the dh command exports
# all buildflags. So there is no need to include the
# /usr/share/dpkg/buildflags.mk file here if compat is 9 or newer.
#
# These are rarely used code. (START)
#
# The following include for *.mk magically sets miscellaneous
# variables while honoring existing values of pertinent
# environment variables:
#
# Architecture-related variables such as DEB_TARGET_MULTIARCH:
#include /usr/share/dpkg/architecture.mk
# Vendor-related variables such as DEB_VENDOR:
#include /usr/share/dpkg/vendor.mk
# Package-related variables such as DEB_DISTRIBUTION
#include /usr/share/dpkg/pkg-info.mk
#
# You may alternatively set them susing a simple script such as:
# DEB_VENDOR ?= $(shell dpkg-vendor --query Vendor)
#
# These are rarely used code. (END)
#
# main packaging script based on dh7 syntax
%:
dh $@ --with autoreconf
# debmake generated override targets
CONFIGURE_FLAGS += --with-systemdsystemunitdir=/lib/systemd/system --enable-manuals
override_dh_auto_configure:
dh_auto_configure -- $(CONFIGURE_FLAGS)
#
# Do not install libtool archive, python .pyc .pyo
#override_dh_install:
# dh_install --list-missing -X.la -X.pyc -X.pyo
# 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

3
doc/Makefile.am Normal file
View File

@ -0,0 +1,3 @@
SUBDIRS = \
examples \
$(NULL)

31
doc/examples/Makefile.am Normal file
View File

@ -0,0 +1,31 @@
OSMOCONF_FILES = \
osmo-isdntap.cfg \
$(NULL)
osmoconfdir = $(sysconfdir)/osmocom
osmoconf_DATA = $(OSMOCONF_FILES)
EXTRA_DIST = $(OSMOCONF_FILES)
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

View File

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:

26
src/Makefile.am Normal file
View File

@ -0,0 +1,26 @@
AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
AM_CFLAGS=-Wall -Wextra -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-result \
$(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOGSM_CFLAGS)
noinst_HEADERS = \
q931.h \
log.h \
$(NULL)
bin_PROGRAMS = \
osmo-isdntap \
$(NULL)
osmo_isdntap_SOURCES = \
q931_decode.c \
log.c \
isdntap.c \
osmo-isdntap.c \
dahdi.c \
vty.c \
$(NULL)
osmo_isdntap_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) \
$(LIBOSMOGSM_LIBS)

267
src/dahdi.c Normal file
View File

@ -0,0 +1,267 @@
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <dahdi/user.h>
#include <osmocom/core/utils.h>
#include "isdntap.h"
#define BLOCK_SIZE 512
/* obtain a per-span sysfs integer attribute */
static int get_span_sysfs_int(unsigned int spanno, const char *attr)
{
int res, rc;
char filename[PATH_MAX];
FILE *fp;
snprintf(filename, sizeof(filename), "/sys/bus/dahdi_spans/devices/span-%u/%s", spanno, attr);
fp = fopen(filename, "r");
if (!fp)
return -1;
rc = fscanf(fp, "%d", &res);
fclose(fp);
if (rc == EOF)
return -1;
return res;
}
/* obtain a per-span sysfs string attribute. Caller must free() return value */
static char *get_span_sysfs_str(unsigned int spanno, const char *attr)
{
char *res = NULL;
int rc;
char filename[PATH_MAX];
FILE *fp;
snprintf(filename, sizeof(filename), "/sys/bus/dahdi_spans/devices/span-%u/%s", spanno, attr);
fp = fopen(filename, "r");
if (!fp)
return NULL;
rc = fscanf(fp, "%ms", &res);
fclose(fp);
if (rc == EOF) {
free(res);
return NULL;
}
return res;
}
/* Find the DAHDI span number for the given 'name' */
static int get_span_no_by_name(const char *name)
{
unsigned int i;
/* iterate over all (up to 256 max) spans and find a matching name */
for (i = 1; i < 256; i++) {
char *name_attr = get_span_sysfs_str(i, "name");
if (!name_attr)
continue;
if (!strcmp(name_attr, name)) {
free(name_attr);
return i;
}
free(name_attr);
}
return -1;
}
/* crate a [rx or tx] mirror FD for given DAHDI channel number */
static int make_mirror(long type, int chan)
{
int res = 0;
int fd = 0;
struct dahdi_bufferinfo bi;
fd = open("/dev/dahdi/pseudo", O_RDONLY);
memset(&bi, 0, sizeof(bi));
bi.txbufpolicy = DAHDI_POLICY_IMMEDIATE;
bi.rxbufpolicy = DAHDI_POLICY_IMMEDIATE;
bi.numbufs = 32;
bi.bufsize = BLOCK_SIZE;
ioctl(fd, DAHDI_SET_BUFINFO, &bi);
res = ioctl(fd, type, &chan);
if (res) {
fprintf(stderr, "error setting channel err=%d!\n", res);
return -1;
}
return fd;
}
static int dahdi_bchan_fd_cb(struct osmo_fd *ofd, unsigned int what)
{
struct isdntap_ts *ts = ofd->data;
uint8_t buf[4096];
int rc;
OSMO_ASSERT(what & OSMO_FD_READ);
rc = read(ofd->fd, buf, sizeof(buf));
if (rc <= 0)
return -EIO;
if (ofd->priv_nr == 1) {
/* Rx Mirror */
} else {
/* Tx Mirror */
}
return isdntap_ts_rx_bchan(ts, buf, rc, 0);
}
static int dahdi_dchan_fd_cb(struct osmo_fd *ofd, unsigned int what)
{
struct isdntap_ts *ts = ofd->data;
uint8_t buf[4096];
int rc;
OSMO_ASSERT(what & OSMO_FD_READ);
rc = read(ofd->fd, buf, sizeof(buf));
if (rc <= 0)
return -EIO;
if (ofd->priv_nr == 1) {
/* Rx Mirror */
} else {
/* Tx Mirror */
}
return isdntap_ts_rx_dchan(ts, buf, rc, 0);
}
static int open_line_dahdi(struct isdntap_line *line)
{
struct isdntap_ts *sig_ts;
char *spantype;
int rc;
/* make sure we have a span number */
if (line->driver.dahdi.spanno < 0) {
if (!line->driver.dahdi.name)
return -1;
line->driver.dahdi.spanno = get_span_no_by_name(line->driver.dahdi.name);
}
if (line->driver.dahdi.spanno < 0)
return -1;
/* resolve the base channel number */
rc = get_span_sysfs_int(line->driver.dahdi.spanno, "basechan");
if (rc < 0)
return rc;
line->driver.dahdi.basechan = rc;
/* resolve the numberof channels */
rc = get_span_sysfs_int(line->driver.dahdi.spanno, "channels");
if (rc < 0)
return rc;
line->driver.dahdi.channels = rc;
/* obtain the span type */
spantype = get_span_sysfs_str(line->driver.dahdi.spanno, "spantype");
if (!spantype)
return -1;
line->driver.dahdi.spantype = talloc_strdup(line, spantype);
free(spantype);
/* note the DAHDI channel number for each timeslot */
for (int i = 0; i < line->driver.dahdi.channels; i++) {
/* FIXME: the below is very E1-specific */
struct isdntap_ts *ts = &line->ts[1+i];
ts->driver.dahdi.channo = line->driver.dahdi.basechan + i;
}
sig_ts = &line->ts[16]; // FIXME: configurable
rc = make_mirror(DAHDI_RXMIRROR, sig_ts->driver.dahdi.channo);
if (rc < 0)
return rc;
/* verify HDLC mode? */
osmo_fd_setup(&sig_ts->driver.dahdi.rx, rc, OSMO_FD_READ, dahdi_dchan_fd_cb, sig_ts, 1);
osmo_fd_register(&sig_ts->driver.dahdi.rx);
rc = make_mirror(DAHDI_TXMIRROR, sig_ts->driver.dahdi.channo);
if (rc < 0) {
close(sig_ts->driver.dahdi.rx.fd);
sig_ts->driver.dahdi.rx.fd = -1;
return rc;
}
/* verify HDLC mode? */
osmo_fd_setup(&sig_ts->driver.dahdi.tx, rc, OSMO_FD_READ, dahdi_dchan_fd_cb, sig_ts, 2);
osmo_fd_register(&sig_ts->driver.dahdi.tx);
return 0;
}
static int open_ts_dahdi(struct isdntap_ts *ts)
{
int rc;
rc = make_mirror(DAHDI_RXMIRROR, ts->driver.dahdi.channo);
if (rc < 0)
return rc;
osmo_fd_setup(&ts->driver.dahdi.rx, rc, OSMO_FD_READ, dahdi_bchan_fd_cb, ts, 1);
osmo_fd_register(&ts->driver.dahdi.rx);
rc = make_mirror(DAHDI_TXMIRROR, ts->driver.dahdi.channo);
if (rc < 0) {
osmo_fd_unregister(&ts->driver.dahdi.rx);
close(ts->driver.dahdi.rx.fd);
ts->driver.dahdi.rx.fd = -1;
return rc;
}
osmo_fd_setup(&ts->driver.dahdi.tx, rc, OSMO_FD_READ, dahdi_bchan_fd_cb, ts, 2);
osmo_fd_register(&ts->driver.dahdi.tx);
return 0;
}
static void close_ts_dahdi(struct isdntap_ts *ts)
{
if (ts->driver.dahdi.rx.fd >= 0) {
osmo_fd_unregister(&ts->driver.dahdi.rx);
close(ts->driver.dahdi.rx.fd);
ts->driver.dahdi.rx.fd = -1;
}
if (ts->driver.dahdi.tx.fd >= 0) {
osmo_fd_unregister(&ts->driver.dahdi.tx);
close(ts->driver.dahdi.tx.fd);
ts->driver.dahdi.tx.fd = -1;
}
}
static void close_line_dahdi(struct isdntap_line *line)
{
/* close all file descriptors on all timeslots */
for (int i = 0; i < line->driver.dahdi.channels; i++) {
/* FIXME: the below is very E1-specific */
struct isdntap_ts *ts = &line->ts[1+i];
close_ts_dahdi(ts);
}
}
const struct isdntap_driver dahdi_driver = {
.name = "dahdi",
.line_open = open_line_dahdi,
.line_close = close_line_dahdi,
.ts_open = open_ts_dahdi,
.ts_close = close_ts_dahdi,
};

518
src/isdntap.c Normal file
View File

@ -0,0 +1,518 @@
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <osmocom/core/isdnhdlc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/gsmtap.h>
#include <osmocom/core/hashtable.h>
#include <osmocom/gsm/tlv.h>
#include "isdntap.h"
#include "q931.h"
#include "log.h"
/***********************************************************************
* data structures
***********************************************************************/
struct isdntap_line *isdntap_line_find(struct isdntap_instance *itd, const char *name)
{
struct isdntap_line *line;
llist_for_each_entry(line, &itd->lines, list) {
if (!strcmp(name, line->name))
return line;
}
return NULL;
}
struct isdntap_line *isdntap_line_new(struct isdntap_instance *itd, const char *name)
{
struct isdntap_line *line = talloc_zero(itd, struct isdntap_line);
if (!line)
return NULL;
line->name = talloc_strdup(line, name);
hash_init(line->q931.call_state);
//line->ctrs = rate_ctr_group_alloc(line, &line_ctrg_desc, ctr_idx);
/* initialize timeslots */
for (unsigned int i = 0; i < ARRAY_SIZE(line->ts); i++) {
struct isdntap_ts *ts = &line->ts[i];
ts->line = line;
ts->num = i;
ts->mode = E1_TS_TRACE_MODE_NONE;
ts->gsmtap_subtype = GSMTAP_E1T1_LAPD;
osmo_isdnhdlc_rcv_init(&ts->hdlc.state[0].vars, 0);
osmo_isdnhdlc_rcv_init(&ts->hdlc.state[1].vars, 0);
ts->driver.dahdi.rx.fd = -1;
ts->driver.dahdi.tx.fd = -1;
ts->driver.dahdi.channo = -1;
}
llist_add_tail(&line->list, &itd->lines);
return line;
}
/***********************************************************************
* call state hash table
***********************************************************************/
/* one call / entry in the state table */
struct q931_call_state {
struct hlist_node node;
uint32_t call_ref; /* decoded call reference */
bool net2user; /* call established in Net->User direction? */
time_t create_time; /* time at which call was created */
struct q931_channel_id chan_id; /* channel identification of associated user plane */
struct q931_party_number calling_party;
struct q931_party_number called_party;
bool sending_complete; /* called party number is complete */
};
static struct q931_call_state *
calltbl_find_callref(struct isdntap_line *line, uint32_t callref)
{
struct q931_call_state *st;
hash_for_each_possible(line->q931.call_state, st, node, callref) {
if (st->call_ref == callref)
return st;
}
return NULL;
}
static struct q931_call_state *
calltbl_create_callref(struct isdntap_line *line, uint32_t callref, bool net2user)
{
struct q931_call_state *st = talloc_zero(line, struct q931_call_state);
if (!st)
return NULL;
st->call_ref = callref;
st->net2user = net2user;
st->create_time = time(NULL);
hash_add(line->q931.call_state, &st->node, callref);
return st;
}
static struct q931_call_state *
calltbl_find_or_create_callref(struct isdntap_line *line, uint32_t callref, bool net2user)
{
struct q931_call_state *cstate;
cstate = calltbl_find_callref(line, callref);
if (!cstate)
cstate = calltbl_create_callref(line, callref, false);
return cstate;
}
static void calltbl_delete_callref(struct q931_call_state *cstate)
{
hash_del(&cstate->node);
talloc_free(cstate);
}
static void calltbl_delete_all_callref(struct isdntap_line *line)
{
struct q931_call_state *cstate;
struct hlist_node *n;
int i;
hash_for_each_safe(line->q931.call_state, i, n, cstate, node) {
calltbl_delete_callref(cstate);
}
}
/* delete any call_state we have on chan_id */
static void calltbl_delete_callref_on_chan_id(struct isdntap_line *line, const struct q931_channel_id *chan_id)
{
struct q931_call_state *cstate;
struct hlist_node *n;
int i;
hash_for_each_safe(line->q931.call_state, i, n, cstate, node) {
if (cstate->chan_id.info_chan_type == chan_id->info_chan_type &&
cstate->chan_id.d_channel == chan_id->d_channel &&
cstate->chan_id.b_channel == chan_id->b_channel) {
calltbl_delete_callref(cstate);
}
}
}
/***********************************************************************
* Q.931 processing
***********************************************************************/
/* append additional digits received (overlap dialling) to called party number */
static void append_called_digits(struct q931_party_number *out, const uint8_t *buf, size_t len)
{
unsigned int idx = strlen(out->digits);
size_t i;
for (i = 0; i < len; i++) {
if (idx >= ARRAY_SIZE(out->digits))
return;
out->digits[idx] = buf[i];
idx++;
}
}
static int isdntap_q931_rx_global(struct isdntap_line *line, const struct q931_msg_parsed *parsed)
{
int rc;
switch (parsed->msg_type) {
case Q931_MSGT_RESTART:
if (TLVP_PRES_LEN(&parsed->ies, Q931_IEI_RESTART_IND, 1)) {
struct q931_channel_id chan_id;
uint8_t rest_ind = *TLVP_VAL(&parsed->ies, Q931_IEI_RESTART_IND) & 0x7;
switch (rest_ind) {
case 0: /* clear indicated channel[s] */
if (!TLVP_PRESENT(&parsed->ies, Q931_IEI_CHANNEL_ID))
return -EINVAL;
rc = q931_decode_channel_id(&chan_id,
TLVP_VAL(&parsed->ies, Q931_IEI_CHANNEL_ID),
TLVP_LEN(&parsed->ies, Q931_IEI_CHANNEL_ID));
if (rc < 0)
return rc;
/* clear any calls on selected channel */
calltbl_delete_callref_on_chan_id(line, &chan_id);
break;
case 6:
case 7: /* clear all channels */
calltbl_delete_all_callref(line);
break;
}
}
break;
}
return 0;
}
/* receive one Q.931 message for signaling analysis */
static int isdntap_q931_rx(struct isdntap_line *line, bool net2user, const uint8_t *buf, size_t len)
{
struct q931_msg_parsed parsed;
struct q931_call_state *cstate;
int rc;
rc = q931_msg_parse(&parsed, buf, len);
if (rc < 0)
return rc;
/* process special case of global call reference elsewhere */
if (parsed.call_ref == 0) {
return isdntap_q931_rx_global(line, &parsed);
}
/* Step 1: look-up (or create in case of SETUP) the call state record for call reference */
switch (parsed.msg_type) {
case Q931_MSGT_SETUP:
cstate = calltbl_find_or_create_callref(line, parsed.call_ref, false);
if (!cstate)
return -ENOMEM;
break;
default:
cstate = calltbl_find_callref(line, parsed.call_ref);
if (!cstate)
return -ENODEV;
break;
}
/* Step 2: Additional information gathering */
switch (parsed.msg_type) {
case Q931_MSGT_SETUP:
/* decode called/calling party (if any) for context */
if (TLVP_PRESENT(&parsed.ies, Q931_IEI_CALLED_PARTY_NUM)) {
q931_decode_called_party(&cstate->called_party,
TLVP_VAL(&parsed.ies, Q931_IEI_CALLED_PARTY_NUM),
TLVP_LEN(&parsed.ies, Q931_IEI_CALLED_PARTY_NUM));
}
if (TLVP_PRESENT(&parsed.ies, Q931_IEI_CALLING_PARTY_NUM)) {
q931_decode_calling_party(&cstate->calling_party,
TLVP_VAL(&parsed.ies, Q931_IEI_CALLING_PARTY_NUM),
TLVP_LEN(&parsed.ies, Q931_IEI_CALLING_PARTY_NUM));
}
if (TLVP_PRESENT(&parsed.ies, Q931_IEI_SENDING_COMPLETE))
cstate->sending_complete = true;
break;
case Q931_MSGT_INFORMATION:
/* append any additional digits to called_party */
if (TLVP_PRESENT(&parsed.ies, Q931_IEI_KEYPAD_FACILITY) &&
!cstate->sending_complete) {
append_called_digits(&cstate->called_party,
TLVP_VAL(&parsed.ies, Q931_IEI_KEYPAD_FACILITY),
TLVP_LEN(&parsed.ies, Q931_IEI_KEYPAD_FACILITY));
}
/* mark as complete, if we finally are */
if (TLVP_PRESENT(&parsed.ies, Q931_IEI_SENDING_COMPLETE))
cstate->sending_complete = true;
break;
default:
break;
}
/* Step 3: dispatch by message type; look in those that contain a ChannelIndicator */
switch (parsed.msg_type) {
case Q931_MSGT_SETUP:
case Q931_MSGT_CALL_PROCEEDING:
case Q931_MSGT_SETUP_ACK:
#if 0
case Q931_MSGT_ALERTING:
case Q931_MSGT_CONNECT:
case Q931_MSGT_RESUME_ACK:
#endif
if (TLVP_PRESENT(&parsed.ies, Q931_IEI_CHANNEL_ID)) {
rc = q931_decode_channel_id(&cstate->chan_id,
TLVP_VAL(&parsed.ies, Q931_IEI_CHANNEL_ID),
TLVP_LEN(&parsed.ies, Q931_IEI_CHANNEL_ID));
if (rc < 0)
return rc;
if (cstate->chan_id.exclusive && !cstate->chan_id.d_channel) {
/* now we know the exact B channel used */
/* TODO: start capturing */
}
}
break;
case Q931_MSGT_RELEASE:
case Q931_MSGT_RELEASE_COMPLETE:
/* forget about the call */
calltbl_delete_callref(cstate);
cstate = NULL;
break;
default:
break;
}
return 0;
}
/* trace one Q.921 / LAPD frame for signaling analysis */
static void isdntap_q921_rx(struct isdntap_line *line, bool net2user, const uint8_t *buf, size_t len)
{
uint8_t sapi, tei;
/* Parse LAPD header; Ignore anything != I frames */
/* Q.921 header: 2 bytes address; 1-2 byte control; [optional] information */
if (len < 2)
return;
/* Address field: Figure 5 / Q.921 */
sapi = buf[0] >> 2;
tei = buf[1] >> 1;
/* SAPI value for circuit-switched call control (Table 2/Q.921) */
if (sapi != 0) {
/* skip unknown SAPI */
return;
}
if (len < 3)
return;
/* Control field: Table 4/Q.921 */
if (buf[2] & 0x01) {
/* skip frames != I-frame (which have 0 as lsb of 1st octet) */
return;
}
if (len < 4)
return;
/* skip N(R) / N(S) and go directly to Q.931 payload */
isdntap_q931_rx(line, net2user, buf + 4, len - 4);
}
/* main entry point for received D-channel data on a given TS */
int isdntap_ts_rx_dchan(struct isdntap_ts *ts, const uint8_t *buf, size_t len, bool net2user)
{
isdntap_q921_rx(ts->line, net2user, buf, len);
return 0;
}
/* main entry point for received B-channel data on a given TS */
int isdntap_ts_rx_bchan(struct isdntap_ts *ts, const uint8_t *buf, size_t len, bool is_rx)
{
struct msgb *msg = msgb_alloc_c(ts->line, len, "Bchan");
uint8_t *cur;
int rc;
if (!msg)
return -ENOMEM;
cur = msgb_put(msg, len);
memcpy(cur, buf, len);
if (is_rx)
rc = osmo_wqueue_enqueue(&ts->output.file.rx, msg);
else
rc = osmo_wqueue_enqueue(&ts->output.file.tx, msg);
if (rc)
msgb_free(msg);
return 0;
}
/* helper function for opening an output file */
static int isdntap_open_file(struct isdntap_ts *ts, const char *call_label, const char *suffix)
{
char buf[PATH_MAX];
int rc;
snprintf(buf, sizeof(buf), "%s/isdntap-%s-%u-%s.raw", ts->line->inst->cfg.output_path,
call_label, ts->num, suffix);
rc = open(buf, O_CREAT|O_WRONLY|O_TRUNC|O_NONBLOCK);
if (rc < 0) {
LOGP(DITAP, LOGL_ERROR, "Error opening file %s: %s\n", buf, strerror(errno));
return rc;
}
LOGP(DITAP, LOGL_INFO, "Opened file %s\n", buf);
return rc;
}
int isdntap_ts_start_bchan(struct isdntap_ts *ts, const char *call_label)
{
int fd_rx, fd_tx;
osmo_wqueue_init(&ts->output.file.tx, 100);
fd_tx = isdntap_open_file(ts, call_label, "tx");
if (fd_tx < 0)
return fd_tx;
ts->output.file.tx.bfd.fd = fd_tx;
osmo_fd_register(&ts->output.file.tx.bfd);
osmo_wqueue_init(&ts->output.file.rx, 100);
fd_rx = isdntap_open_file(ts, call_label, "rx");
if (!fd_rx)
goto out_cleanup_tx;
ts->output.file.rx.bfd.fd = fd_rx;
osmo_fd_register(&ts->output.file.rx.bfd);
return 0;
out_cleanup_tx:
osmo_fd_unregister(&ts->output.file.tx.bfd);
ts->output.file.tx.bfd.fd = -1;
close(fd_tx);
return -1;
}
void isdntap_ts_stop_bchan(struct isdntap_ts *ts)
{
/* FIXME: ideally we'd make sure that the write queues are both fully flushed first */
if (ts->output.file.tx.bfd.fd >= 0) {
osmo_wqueue_clear(&ts->output.file.tx);
osmo_fd_unregister(&ts->output.file.tx.bfd);
close(ts->output.file.tx.bfd.fd);
ts->output.file.tx.bfd.fd = -1;
}
if (ts->output.file.rx.bfd.fd >= 0) {
osmo_wqueue_clear(&ts->output.file.rx);
osmo_fd_unregister(&ts->output.file.rx.bfd);
close(ts->output.file.rx.bfd.fd);
ts->output.file.rx.bfd.fd = -1;
}
}
/***********************************************************************
* e1d integration
***********************************************************************/
# if 0
/* trace one timeslot of a line */
void e1tap_trace_ts(struct e1tap_ts *ts, const uint8_t *tsbuf, size_t frame_count, uint8_t hdlc_idx)
{
struct e1tap_line *line = ts->line;
int oi = 0;
int rc;
OSMO_ASSERT(hdlc_idx <= 0);
switch (ts->mode) {
case E1_TS_TRACE_MODE_HDLC:
case E1_TS_TRACE_MODE_ISDN_D:
while (oi < frame_count) {
int num_consumed;
/* feed the new bytes into the HDLC decoder */
rc = osmo_isdnhdlc_decode(&ts->hdlc.state[hdlc_idx].vars,
&tsbuf[oi], frame_count-oi,
&num_consumed, ts->hdlc.state[hdlc_idx].out,
sizeof(ts->hdlc.state[hdlc_idx].out));
if (rc > 0) {
/* if HDLC decoder produced output, send it via GSMTAP */
gsmtap_send_ex(line->gti, GSMTAP_TYPE_E1T1, flags,
ts->num, ts->gsmtap_subtype, 0, 0, 0, 0,
ts->hdlc.state[hdlc_idx].out, rc);
/* feed D-channel into higher level analysis */
if (ts->mode == E1_TS_TRACE_MODE_ISDN_D)
isdntap_q921_rx(line, hdlc_idx, ts->hdlc.state[hdlc_idx].out, rc);
} else if (rc < 0) {
/* FIXME: log error */
}
oi += num_consumed;
}
break;
default:
break;
}
}
/* trace an entire line (we pass in full 32byte frames */
void e1tap_trace_line(struct e1tap_line *line, bool mux_out, const uint8_t *buf, int frame_count)
{
uint8_t tsbuf[frame_count];
uint8_t hdlc_idx;
if (!line)
return;
if (mux_out)
hdlc_idx = 1;
else
hdlc_idx = 0;
for (unsigned int tn = 1; tn < ARRAY_SIZE(line->ts); tn++) {
struct e1tap_ts *ts = line->ts[i];
/* fast path */
if (ts->mode == E1_TS_TRACE_MODE_NONE)
continue;
/* demultiplex the bytes of the given TS */
for (unsigned int f = 0; f < frame_count; f++)
tsbuf[f] = buf[32*f + tn];
e1tap_trace_ts(ts, tsbuf, frame_count, hdlc_idx);
}
}
/* USB/trunkdev <- application/OCTOI */
void
e1_trace_mux_out(struct e1_line *line, const uint8_t *buf, int frame_count)
{
return _e1_trace(line, true, buf, frame_count);
}
/* USB/trunkdev -> application/OCTOI */
void
e1_trace_mux_in(struct e1_line *line, const uint8_t *buf, int frame_count)
{
return _e1_trace(line, false, buf, frame_count);
}
#endif

125
src/isdntap.h Normal file
View File

@ -0,0 +1,125 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <osmocom/core/select.h>
#include <osmocom/core/hashtable.h>
#include <osmocom/core/isdnhdlc.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/vty/command.h>
enum {
ISDNTAP_NODE = _LAST_OSMOVTY_NODE + 1,
LINE_NODE,
};
/* number of octets in an information field */
#define Q921_N201 260
#define Q921_ADDR_SIZE 2
#define Q921_CTRL_SIZE 2
#define Q921_FCS_SIZE 2
#define HDLC_FRAME_SIZE (Q921_N201 + Q921_ADDR_SIZE + Q921_CTRL_SIZE + Q921_FCS_SIZE)
struct isdntap_line;
enum isdntap_ts_mode {
E1_TS_TRACE_MODE_NONE,
E1_TS_TRACE_MODE_RAW, /* RAW bitstream */
E1_TS_TRACE_MODE_HDLC, /* generic HDLC mode */
E1_TS_TRACE_MODE_ISDN_D, /* ISDN D-Channel (Q.921 + Q.931) */
};
/* one "complete" HDLC decoder state */
struct isdntap_hdlc_state {
struct osmo_isdnhdlc_vars vars;
uint8_t out[HDLC_FRAME_SIZE];
};
/* isdntap state for one timeslot */
struct isdntap_ts {
struct isdntap_line *line; /* back-pointer */
uint8_t num; /* timeslot number */
enum isdntap_ts_mode mode;
uint8_t gsmtap_subtype; // GSMTAP_E1T1_LAPD
struct {
/* two soft-HDLC instances, one for each direction */
struct isdntap_hdlc_state state[2];
} hdlc;
union {
struct {
struct osmo_fd rx; /* RX-mirror file descriptor */
struct osmo_fd tx; /* TX-mirror file descriptor */
int channo; /* DAHDI channel number for this TS */
} dahdi;
} driver;
union {
struct {
struct osmo_wqueue rx;
struct osmo_wqueue tx;
} file;
} output;
};
/* isdntap state for one line/span */
struct isdntap_line {
struct llist_head list; /* member in list of all isdntap_lines */
const char *name; /* human-readable name */
struct isdntap_instance *inst; /* back-pointer */
struct gsmtap_inst *gti; /* gsmtap instance through which we log */
void *priv; /* private back-pointer (e.g. to e1d line) */
struct isdntap_ts ts[32]; /* per-timeslot state */
/* signalling state */
struct {
} q921;
struct {
DECLARE_HASHTABLE(call_state, 8);
} q931;
struct rate_ctr_group *ctrs;
union {
struct {
const char *name;
const char *spantype;
int spanno;
int basechan;
int channels;
} dahdi;
} driver;
};
struct isdntap_driver {
const char *name;
int (*line_open)(struct isdntap_line *line);
void (*line_close)(struct isdntap_line *line);
int (*ts_open)(struct isdntap_ts *ts);
void (*ts_close)(struct isdntap_ts *ts);
};
/* drivers feed data into the isdntap core via this API */
int isdntap_ts_rx_dchan(struct isdntap_ts *ts, const uint8_t *buf, size_t len, bool net2user);
int isdntap_ts_rx_bchan(struct isdntap_ts *ts, const uint8_t *buf, size_t len, bool net2user);
/* DAHDI driver */
extern const struct isdntap_driver dahdi_driver;
struct isdntap_instance {
struct llist_head lines;
struct {
const char *output_path;
} cfg;
};
struct isdntap_line *isdntap_line_find(struct isdntap_instance *itd, const char *name);
struct isdntap_line *isdntap_line_new(struct isdntap_instance *itd, const char *name);
void isdntap_vty_init(struct isdntap_instance *itd);
int isdntap_ts_start_bchan(struct isdntap_ts *ts, const char *call_label);
void isdntap_ts_stop_bchan(struct isdntap_ts *ts);

46
src/log.c Normal file
View File

@ -0,0 +1,46 @@
/*
* log.c
*
* (C) 2022 by Harald Welte <laforge@osmocom.org>
*
* All Rights Reserved
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include "log.h"
static const struct log_info_cat default_categories[] = {
[DITAP] = {
.name = "DITAP",
.loglevel = LOGL_INFO,
.enabled = 1,
},
[DQ931] = {
.name = "DQ931",
.loglevel = LOGL_NOTICE,
.enabled = 1,
},
};
const struct log_info log_info = {
.cat = default_categories,
.num_cat = ARRAY_SIZE(default_categories),
};

34
src/log.h Normal file
View File

@ -0,0 +1,34 @@
/*
* log.h
*
* (C) 2022 by Harald Welte <laforge@osmocom.org>
*
* All Rights Reserved
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include <osmocom/core/logging.h>
enum {
DITAP,
DQ931,
};
extern const struct log_info log_info;

197
src/osmo-isdntap.c Normal file
View File

@ -0,0 +1,197 @@
/*
* osmo-isdntap.c
*
* (C) 2022 by Harald Welte <laforge@osmocom.org>
*
* All Rights Reserved
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <errno.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#define _GNU_SOURCE
#include <getopt.h>
#include <talloc.h>
#include <osmocom/core/application.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
#include <osmocom/core/stats.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/stats.h>
#include <osmocom/vty/misc.h>
#include <osmocom/vty/cpu_sched_vty.h>
#include "isdntap.h"
#include "log.h"
#ifndef OSMO_VTY_PORT_ITAP
#define OSMO_VTY_PORT_ITAP 4269
#endif
static const char *g_config_file = "osmo-isdntap.cfg";
static void *g_itap_ctx = NULL;
static int g_shutdown = 0;
static void sig_handler(int signo)
{
fprintf(stdout, "signal %d received\n", signo);
switch (signo) {
case SIGINT:
case SIGTERM:
fprintf(stdout, "shutting down\n");
g_shutdown = 1;
break;
case SIGABRT:
case SIGUSR1:
talloc_report(g_itap_ctx, stderr);
talloc_report_full(g_itap_ctx, stderr);
break;
case SIGUSR2:
talloc_report_full(g_itap_ctx, stderr);
break;
default:
break;
}
}
static struct vty_app_info vty_info = {
.name = "osmo-isdntap",
.version = PACKAGE_VERSION,
.go_parent_cb = vty_go_parent,
.copyright =
"(C) 2022 by Harald Welte and contributors\r\n",
"License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl-2.0.html>\r\n"
"This is free software: you are free to change and redistribute it.\r\n"
"There is NO WARRANTY, to the extent permitted by law.\r\n",
};
static void print_help(void)
{
printf(" Some useful help...\n");
printf(" -h --help This text.\n");
printf(" -d --debug option --debug=DITAP:DQ931 enable debugging.\n");
printf(" -c --config-file filename The config file to use.\n");
}
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'},
{"debug", 1, 0, 'd'},
{"config-file", 1, 0, 'c'},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, "hd:c:", long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'h':
print_help();
exit(0);
break;
case 'd':
log_parse_category_mask(osmo_stderr_target, optarg);
break;
case 'c':
g_config_file = optarg;
break;
default:
fprintf(stderr, "Error in command line options. Exiting.\n");
exit(-1);
}
}
if (argc > optind) {
fprintf(stderr, "Unsupported positional arguments on command line\n");
exit(2);
}
}
int main(int argc, char *argv[])
{
struct isdntap_instance *itd = NULL;
int rv;
/* talloc init */
g_itap_ctx = talloc_named_const(NULL, 0, "osmo-isdntap");
msgb_talloc_ctx_init(g_itap_ctx, 0);
vty_info.tall_ctx = g_itap_ctx;
/* logging init */
osmo_init_logging2(g_itap_ctx, &log_info);
/* signals init */
signal(SIGINT, &sig_handler);
signal(SIGTERM, &sig_handler);
signal(SIGABRT, &sig_handler);
signal(SIGUSR1, &sig_handler);
signal(SIGUSR2, &sig_handler);
osmo_init_ignore_signals();
/* main state */
itd = talloc_zero(g_itap_ctx, struct isdntap_instance);
OSMO_ASSERT(itd);
INIT_LLIST_HEAD(&itd->lines);
vty_init(&vty_info);
logging_vty_add_cmds();
isdntap_vty_init(itd);
rate_ctr_init(itd);
osmo_stats_init(itd);
osmo_stat_item_init(itd);
osmo_stats_vty_add_cmds();
osmo_talloc_vty_add_cmds();
osmo_fsm_vty_add_cmds();
osmo_cpu_sched_vty_init(itd);
handle_options(argc, argv);
rv = vty_read_config_file(g_config_file, NULL);
if (rv < 0) {
LOGP(DITAP, LOGL_FATAL, "Failed to parse the config file '%s'\n", g_config_file);
exit(2);
}
rv = telnet_init_dynif(g_itap_ctx, itd, vty_get_bind_addr(), OSMO_VTY_PORT_ITAP);
if (rv != 0) {
LOGP(DITAP, LOGL_FATAL, "Failed to bind VTY interface to %s:%u\n",
vty_get_bind_addr(), OSMO_VTY_PORT_ITAP);
exit(1);
}
/* main loop */
while (!g_shutdown) {
osmo_select_main(0);
}
talloc_free(itd);
return 0;
}

121
src/q931.h Normal file
View File

@ -0,0 +1,121 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <osmocom/gsm/tlv.h>
/* Table 4-2/Q.931 - Message types */
enum q931_msg_type {
/* Call establishment messages */
Q931_MSGT_ALERTING = 0x01,
Q931_MSGT_CALL_PROCEEDING = 0x02,
Q931_MSGT_CONNECT = 0x07,
Q931_MSGT_CONNECT_ACK = 0x0f,
Q931_MSGT_PROGRESS = 0x03,
Q931_MSGT_SETUP = 0x05,
Q931_MSGT_SETUP_ACK = 0x0d,
/* Call information phase messages */
Q931_MSGT_RESUME = 0x26,
Q931_MSGT_RESUME_ACK = 0x2e,
Q931_MSGT_RESUME_REJ = 0x22,
Q931_MSGT_SUSPEND = 0x25,
Q931_MSGT_SUSPEND_ACK = 0x2d,
Q931_MSGT_SUSPEND_REJ = 0x21,
Q931_MSGT_USER_INFO = 0x20,
/* Call clearing messages */
Q931_MSGT_DISCONNECT = 0x45,
Q931_MSGT_RELEASE = 0x4d,
Q931_MSGT_RELEASE_COMPLETE = 0x5a,
Q931_MSGT_RESTART = 0x46,
Q931_MSGT_RESTART_ACK = 0x4e,
/* Miscellaneous messages */
Q931_MSGT_SEGMENT = 0x60,
Q931_MSGT_CONGESTION_CTRL = 0x79,
Q931_MSGT_INFORMATION = 0x7b,
Q931_MSGT_NOTIFY = 0x6e,
Q931_MSGT_STATUS = 0x7d,
Q931_MSGT_STATUS_ENQIURY = 0x75,
};
/* Table 4-3/Q.931 */
enum q931_iei {
/* reserved */
/* shift */
Q931_IEI_MORE_DATA = 0xa0,
Q931_IEI_SENDING_COMPLETE = 0xa1,
/* contentionlevel */
/* repeat indicator */
/* Veriable length IEs */
Q931_IEI_SEGMENTED_MSG = 0x00,
Q931_IEI_BEARER_CAP = 0x04,
Q931_IEI_CAUSE = 0x08,
Q931_IEI_CALL_ID = 0x10,
Q931_IEI_CALL_STATE = 0x14,
Q931_IEI_CHANNEL_ID = 0x18,
Q931_IEI_PROGRESS_IND = 0x1e,
Q931_IEI_NETWORK_SPEC_FAC = 0x20,
Q931_IEI_NOTIFICATION_IND = 0x27,
Q931_IEI_DISPLAY = 0x28,
Q931_IEI_DATE_TIME = 0x29,
Q931_IEI_KEYPAD_FACILITY = 0x2c,
Q931_IEI_SIGNAL = 0x34,
Q931_IEI_INFORMATION_RATE = 0x40,
Q931_IEI_E2E_TRANSIT_DELAY = 0x42,
Q931_IEI_TRANSIT_DELAY_SEL_AND_IND = 0x43,
Q931_IEI_PKT_LAYER_BIN_PARAMS = 0x44,
Q931_IEI_PKT_LAYER_WIN_SIZE = 0x45,
Q931_IEI_PACKET_SIZE = 0x46,
Q931_IEI_CLOSED_USER_GROUP = 0x47,
Q931_IEI_REV_CHARGING_IND = 0x4a,
Q931_IEI_CALLING_PARTY_NUM = 0x5c,
Q931_IEI_CALLING_PARTY_SUBADDR = 0x5d,
Q931_IEI_CALLED_PARTY_NUM = 0x70,
Q931_IEI_CALLED_PARTY_SUBADDR = 0x71,
Q931_IEI_TRANSIT_NET_SEL = 0x78,
Q931_IEI_RESTART_IND = 0x79,
Q931_IEI_LOW_LAYER_COMPAT = 0x7c,
Q931_IEI_HIGH_LAYER_COMPAT = 0x7d,
Q931_IEI_ESCAPE_FOR_EXT = 0x7f,
};
struct q931_msg_parsed {
uint8_t msg_type;
uint32_t call_ref;
struct tlv_parsed ies;
};
int q931_msg_parse(struct q931_msg_parsed *out, const uint8_t *buf, size_t len);
uint32_t q931_decode_callref(const uint8_t *data, uint8_t len);
/* Q.931 Section 4.5.13 */
enum q931_info_chan_type {
Q931_INFO_CHAN_T_NONE,
Q931_INFO_CHAN_T_ANY,
Q931_INFO_CHAN_T_SPECIFIC,
};
/* decoded version of (simplified) Q.931 Channel Identification IE */
struct q931_channel_id {
bool interface_id_present;
uint32_t interface_id;
bool interface_type_pri;
bool exclusive;
bool d_channel;
enum q931_info_chan_type info_chan_type;
uint32_t b_channel; /* 1..2 on BRI; 1..31 on PRI */
};
int q931_decode_channel_id(struct q931_channel_id *out, const uint8_t *data, uint8_t len);
struct q931_party_number {
uint8_t type_of_number;
uint8_t numbering_plan_id;
uint8_t presentation_ind;
uint8_t screening_ind;
char digits[32];
};
int q931_decode_called_party(struct q931_party_number *out, const uint8_t *buf, size_t len);
int q931_decode_calling_party(struct q931_party_number *out, const uint8_t *buf, size_t len);

261
src/q931_decode.c Normal file
View File

@ -0,0 +1,261 @@
#include <errno.h>
#include <osmocom/gsm/tlv.h>
#include "q931.h"
/* Table 4-3/Q.931 */
const struct tlv_definition q931_tlv_def = {
.def = {
/* fixed-length */
[0x80] = { TLV_TYPE_SINGLE_TV, 0 },
[0x90] = { TLV_TYPE_SINGLE_TV, 0 },
[Q931_IEI_MORE_DATA] = { TLV_TYPE_T, 0 },
[Q931_IEI_SENDING_COMPLETE] = { TLV_TYPE_T, 0 },
[0xB0] = { TLV_TYPE_SINGLE_TV, 0 },
[0xD0] = { TLV_TYPE_SINGLE_TV, 0 },
/* variable-length */
[Q931_IEI_SEGMENTED_MSG] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_BEARER_CAP] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_CAUSE] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_CALL_ID] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_CALL_STATE] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_CHANNEL_ID] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_PROGRESS_IND] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_NETWORK_SPEC_FAC] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_NOTIFICATION_IND] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_DISPLAY] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_DATE_TIME] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_KEYPAD_FACILITY] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_SIGNAL] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_INFORMATION_RATE] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_E2E_TRANSIT_DELAY] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_TRANSIT_DELAY_SEL_AND_IND] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_PKT_LAYER_BIN_PARAMS] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_PKT_LAYER_WIN_SIZE] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_PACKET_SIZE] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_CLOSED_USER_GROUP] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_REV_CHARGING_IND] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_CALLING_PARTY_NUM] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_CALLING_PARTY_SUBADDR]= { TLV_TYPE_TLV, 0 },
[Q931_IEI_CALLED_PARTY_NUM] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_CALLED_PARTY_SUBADDR] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_TRANSIT_NET_SEL] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_RESTART_IND] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_LOW_LAYER_COMPAT] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_HIGH_LAYER_COMPAT] = { TLV_TYPE_TLV, 0 },
[Q931_IEI_ESCAPE_FOR_EXT] = { TLV_TYPE_TLV, 0 },
},
};
/* parse one Q.931 message for signaling analysis */
int q931_msg_parse(struct q931_msg_parsed *out, const uint8_t *buf, size_t len)
{
uint8_t cref_len;
uint32_t cref;
const uint8_t *other_ie;
size_t other_ie_len;
int rc;
memset(out, 0, sizeof(*out));
/* at least protocol discriminator + length of call-ref must be present */
if (len < 2)
return -EMSGSIZE;
/* check protocol discriminator */
if (buf[0] != 0x08)
return -EINVAL;
/* parse [variable length] call reference */
cref_len = buf[1] & 0x0F;
if (len < 2 + cref_len)
return -EMSGSIZE;
out->call_ref = q931_decode_callref(buf+1, len-1);
/* parse message type */
if (len < 2 + cref_len + 1)
return -EMSGSIZE;
out->msg_type = buf[2+cref_len] & 0x7f;
/* parse 'other IEs' */
other_ie = buf + 2 + cref_len + 1;
other_ie_len = len - (2 + cref_len + 1);
if (other_ie_len) {
rc = tlv_parse(&out->ies, &q931_tlv_def, other_ie, other_ie_len, 0, 0);
if (rc < 0)
return rc;
}
return 0;
}
/* Q.931 Section 4.3 */
uint32_t q931_decode_callref(const uint8_t *data, uint8_t len)
{
uint8_t len_of_callref;
uint32_t callref = 0;
bool flag;
if (len < 1)
return 0;
len_of_callref = data[0] & 0x0f;
if (len_of_callref = 0)
return 0;
if (len_of_callref > 4)
return 0;
if (len - 1 < len_of_callref)
return 0;
/* first octet contains flag, needs special handling */
if (data[1] & 0x80)
flag = 1;
callref = data[1] & 0x7f;
/* all further octets ... */
for (unsigned i = 1; i < len_of_callref; i++)
callref = (callref << 8) | data[i];
if (flag)
callref |= 0x80000000;
return callref;
}
/* Decode a (subset of) Q.931 channel identification IE from binary to struct representation */
int q931_decode_channel_id(struct q931_channel_id *out, const uint8_t *data, uint8_t len)
{
const uint8_t *cur = data;
memset(out, 0, sizeof(*out));
if (len < 1)
return -1;
if (! (data[0] & 0x80))
return -1;
if (data[0] & 0x40) {
out->interface_id_present = true;
cur++;
if (len < 1 + (cur - data))
return -1;
/* we only support single-octet interface ID */
if (!(*cur & 0x80))
return -1;
out->interface_id = *cur & 0x7f;
}
if (data[0] & 0x20)
out->interface_type_pri = true;
if (data[0] & 0x08)
out->exclusive = true;
if (data[0] & 0x04) {
out->d_channel = true;
} else {
switch (data[0] & 0x03) {
case 0:
out->info_chan_type = Q931_INFO_CHAN_T_NONE;
break;
case 1:
if (out->interface_type_pri == false) {
out->b_channel = 1;
} else {
/* Octet 3.2 */
cur++;
if (len < 1 + (cur - data))
return -1;
if (!(*cur & 0x80))
return -1;
/* do we care about the coding standard? */
/* we only support single channel numbers */
if (*cur & 0x10)
return -1;
/* we only support B-channels */
if (*cur & 0x0f != 0x03)
return -1;
/* Octet 3.3 */
cur++;
if (len < 1 + (cur - data))
return -1;
/* we only support single channel numbers */
if (!(*cur & 0x80))
return -1;
out->b_channel = *cur & 0x7f;
}
break;
case 2:
if (out->interface_type_pri == false)
out->b_channel = 2;
else
return -1;
break;
case 3:
out->info_chan_type = Q931_INFO_CHAN_T_ANY;
break;
}
}
return 0;
}
/* Q.931 Section 4.5.8 */
int q931_decode_called_party(struct q931_party_number *out, const uint8_t *buf, size_t len)
{
memset(out, 0, sizeof(*out));
if (len < 1)
return -1;
/* Octet 3 */
if (!(*buf & 0x80))
return -1;
out->type_of_number = (*buf >> 4) & 0x7;
out->numbering_plan_id = *buf & 0xf;
for (unsigned int i = 0; i < len - 1; i++) {
if (i >= sizeof(out->digits))
return -1;
out->digits[i] = buf[1+i] & 0x7f;
}
return 0;
}
/* Q.931 Section 4.5.10 */
int q931_decode_calling_party(struct q931_party_number *out, const uint8_t *buf, size_t len)
{
const uint8_t *cur = buf;
memset(out, 0, sizeof(*out));
if (len < 1)
return -1;
/* Octet 3 */
out->type_of_number = (*cur >> 4) & 0x7;
out->numbering_plan_id = *cur & 0xf;
if (!(*cur & 0x80)) {
/* Octet 3a */
cur++;
if (len < 2)
return -1;
if (!(*cur & 0x80))
return -1;
out->presentation_ind = (*cur >> 5) & 0x3;
out->screening_ind = *cur & 0x3;
}
cur++;
for (unsigned int i = 0; i < len - (cur - buf); i++) {
if (i >= sizeof(out->digits))
return -1;
out->digits[i] = cur[i] & 0x7f;
}
return 0;
}

179
src/vty.c Normal file
View File

@ -0,0 +1,179 @@
/* osmo-isdntap VTY interface */
/* (C) 2022 by Harald Welte <laforge@osmocom.org>
* 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/>.
*
*/
#define _GNU_SOURCE /* struct ucred */
#include <sys/socket.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/buffer.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/stats.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/misc.h>
#include <osmocom/vty/tdef_vty.h>
#include "isdntap.h"
static struct isdntap_instance *vty_itd;
static struct cmd_node isdntap_node = {
(enum node_type) ISDNTAP_NODE,
"%s(config-isdntap)# ",
1,
};
static struct cmd_node line_node = {
(enum node_type) LINE_NODE,
"%s(config-isdntap-line)# ",
1,
};
static void vty_dump_line(struct vty *vty, const struct isdntap_line *line)
{
vty_out(vty, "Line %s%s", line->name, VTY_NEWLINE);
if (1) {
vty_out(vty, " Driver: DAHDI, span_name=%s, span_type=%s, span_no=%d, "
"basechan=%d, channels=%d%s",
line->driver.dahdi.name, line->driver.dahdi.spantype,
line->driver.dahdi.spanno, line->driver.dahdi.basechan,
line->driver.dahdi.channels, VTY_NEWLINE);
}
//vty_out_rate_ctr_group(vty, " ", line->ctrs);
}
DEFUN(show_line, show_line_cmd, "show line",
SHOW_STR "Display information about an E1 Line\n")
{
struct isdntap_line *line;
llist_for_each_entry(line, &vty_itd->lines, list) {
vty_dump_line(vty, line);
}
return CMD_SUCCESS;
}
DEFUN(cfg_isdntap, cfg_isdntap_cmd, "isdntap",
"isdntap Daemon specific configuration\n")
{
vty->node = ISDNTAP_NODE;
return CMD_SUCCESS;
}
DEFUN(cfg_itd_line_dahdi, cfg_itd_line_dahdi_cmd, "line NAME dahdi",
"Configure an ISDNtap E1 line\n"
"E1 Line (Span) Name\n"
"Use the DAHDI Driver\n")
{
const char *name = argv[0];
struct isdntap_line *line;
line = isdntap_line_find(vty_itd, name);
if (!line) {
line = isdntap_line_new(vty_itd, name);
}
if (!line) {
vty_out(vty, "%% Could not create line%s", VTY_NEWLINE);
return CMD_WARNING;
}
//line->driver = E1_DRIVER_DAHDI;
vty->index = line;
vty->node = LINE_NODE;
return CMD_SUCCESS;
}
#if 0
DEFUN(cfg_e1d_if_usb_serial, cfg_e1d_if_usb_serial_cmd,
"usb-serial SERNO",
"Configure the USB serial number of an E1 interface device\n"
"iSerial string\n")
{
struct e1_intf *intf = vty->index;
if (intf->drv != E1_DRIVER_USB)
return CMD_WARNING;
osmo_talloc_replace_string(intf, &intf->usb.serial_str, argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_e1d_if_line_mode, cfg_e1d_if_line_mode_cmd,
"mode (channelized|superchannel|e1oip)",
"Configure the mode of the E1 line\n"
"Channelized (64kBps timeslot) mode\n"
"Superchannel (1xHDLC over 31x64kBps) mode\n")
{
struct e1_line *line = vty->index;
enum e1_line_mode new_mode = get_string_value(e1_line_mode_names, argv[0]);
if (line->mode != new_mode) {
/* FIXME: clean up any old state */
line->mode = new_mode;
}
return CMD_SUCCESS;
}
#endif
static int config_write_line(struct vty *vty, struct isdntap_line *line)
{
vty_out(vty, " line %s dahdi%s", line->name, VTY_NEWLINE);
//vty_out(vty, " mode %s%s", get_value_string(e1_line_mode_names, line->mode), VTY_NEWLINE);
return 0;
}
static int config_write_isdntap(struct vty *vty)
{
struct isdntap_line *line;
vty_out(vty, "isdntap%s", VTY_NEWLINE);
/* find all vpair interfaces */
llist_for_each_entry(line, &vty_itd->lines, list) {
config_write_line(vty, line);
}
return 0;
}
void isdntap_vty_init(struct isdntap_instance *itd)
{
vty_itd = itd;
install_element_ve(&show_line_cmd);
install_node(&isdntap_node, config_write_isdntap);
install_element(CONFIG_NODE, &cfg_isdntap_cmd);
//install_element(ISDNTAP_NODE, &cfg_vpair_cmd);
install_node(&line_node, NULL);
install_element(ISDNTAP_NODE, &cfg_itd_line_dahdi_cmd);
//install_element(LINE_NODE, &cfg_isdntap_if_line_cmd);
//install_element(LINE_NODE, &cfg_isdntap_if_usb_serial_cmd);
}

74
tests/Makefile.am Normal file
View File

@ -0,0 +1,74 @@
SUBDIRS = \
$(NULL)
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
$(srcdir)/package.m4: $(top_srcdir)/configure.ac
:;{ \
echo '# Signature of the current package.' && \
echo 'm4_define([AT_PACKAGE_NAME],' && \
echo ' [$(PACKAGE_NAME)])' && \
echo 'm4_define([AT_PACKAGE_TARNAME],' && \
echo ' [$(PACKAGE_TARNAME)])' && \
echo 'm4_define([AT_PACKAGE_VERSION],' && \
echo ' [$(PACKAGE_VERSION)])' && \
echo 'm4_define([AT_PACKAGE_STRING],' && \
echo ' [$(PACKAGE_STRING)])' && \
echo 'm4_define([AT_PACKAGE_BUGREPORT],' && \
echo ' [$(PACKAGE_BUGREPORT)])'; \
echo 'm4_define([AT_PACKAGE_URL],' && \
echo ' [$(PACKAGE_URL)])'; \
} >'$(srcdir)/package.m4'
EXTRA_DIST = \
testsuite.at \
$(srcdir)/package.m4 \
$(TESTSUITE) \
$(NULL)
TESTSUITE = $(srcdir)/testsuite
DISTCLEANFILES = \
atconfig \
$(NULL)
if ENABLE_EXT_TESTS
python-tests: $(BUILT_SOURCES)
$(MAKE) vty-test
osmotestvty.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
osmotestconfig.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
$(srcdir)/vty_test_runner.py -w $(abs_top_builddir) -v
$(srcdir)/ctrl_test_runner.py -w $(abs_top_builddir) -v
else
python-tests: $(BUILT_SOURCES)
echo "Not running python-based tests (determined at configure-time)"
endif
# Run a specific test with: 'make vty-test VTY_TEST=osmo-isdntap.vty'
VTY_TEST ?= *.vty
# To update the VTY script from current application behavior,
# pass -u to vty_script_runner.py by doing:
# make vty-test U=-u
vty-test:
osmo_verify_transcript_vty.py -v \
-n osmo-isdntap -p 4239 \
-r "$(top_builddir)/src/osmo-isdntap -c $(top_srcdir)/doc/examples/osmo-isdntap.cfg" \
$(U) $(srcdir)/$(VTY_TEST)
check-local: atconfig $(TESTSUITE)
$(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS)
$(MAKE) $(AM_MAKEFLAGS) python-tests
installcheck-local: atconfig $(TESTSUITE)
$(SHELL) '$(TESTSUITE)' AUTOTEST_PATH='$(bindir)' \
$(TESTSUITEFLAGS)
clean-local:
test ! -f '$(TESTSUITE)' || \
$(SHELL) '$(TESTSUITE)' --clean
AUTOM4TE = $(SHELL) $(top_srcdir)/missing --run autom4te
AUTOTEST = $(AUTOM4TE) --language=autotest
$(TESTSUITE): $(srcdir)/testsuite.at $(srcdir)/package.m4
$(AUTOTEST) -I '$(srcdir)' -o $@.tmp $@.at
mv $@.tmp $@