Initial WIP code; doens'tdo anything useful yet

master
Harald Welte 1 month ago
commit 311afac0d2
  1. 40
      .gitignore
  2. 26
      Makefile.am
  3. 121
      configure.ac
  4. 1
      contrib/Makefile.am
  5. 6
      contrib/systemd/Makefile.am
  6. 12
      contrib/systemd/osmo-isdntap.service
  7. 6
      debian/changelog
  8. 1
      debian/compat
  9. 24
      debian/control
  10. 20
      debian/copyright
  11. 3
      debian/osmo-isdntap.install
  12. 58
      debian/rules
  13. 3
      doc/Makefile.am
  14. 31
      doc/examples/Makefile.am
  15. 0
      doc/examples/osmo-isdntap.cfg
  16. 151
      git-version-gen
  17. 26
      src/Makefile.am
  18. 267
      src/dahdi.c
  19. 518
      src/isdntap.c
  20. 125
      src/isdntap.h
  21. 46
      src/log.c
  22. 34
      src/log.h
  23. 197
      src/osmo-isdntap.c
  24. 121
      src/q931.h
  25. 261
      src/q931_decode.c
  26. 179
      src/vty.c
  27. 74
      tests/Makefile.am

40
.gitignore vendored

@ -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

@ -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

@ -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
)

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

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

@ -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

@ -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

@ -0,0 +1 @@
9

24
debian/control vendored

@ -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

@ -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/>.

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

58
debian/rules vendored

@ -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

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

@ -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

@ -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:

@ -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)

@ -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,
};

@ -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

@ -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);