commit 311afac0d2ea164b2943482bca67f5029efa822a Author: Harald Welte Date: Thu Oct 13 19:11:06 2022 +0200 Initial WIP code; doens'tdo anything useful yet diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee0df36 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..84fa3b3 --- /dev/null +++ b/Makefile.am @@ -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 diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..bd282a6 --- /dev/null +++ b/configure.ac @@ -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 + ) diff --git a/contrib/Makefile.am b/contrib/Makefile.am new file mode 100644 index 0000000..3439c97 --- /dev/null +++ b/contrib/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = systemd diff --git a/contrib/systemd/Makefile.am b/contrib/systemd/Makefile.am new file mode 100644 index 0000000..9a109e4 --- /dev/null +++ b/contrib/systemd/Makefile.am @@ -0,0 +1,6 @@ +EXTRA_DIST = osmo-isdntap.service + +if HAVE_SYSTEMD +systemdsystemunit_DATA = \ + osmo-isdntap.service +endif diff --git a/contrib/systemd/osmo-isdntap.service b/contrib/systemd/osmo-isdntap.service new file mode 100644 index 0000000..9c12a96 --- /dev/null +++ b/contrib/systemd/osmo-isdntap.service @@ -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 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..7106ac5 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,6 @@ +osmo-isdntap (0.0.1) unstable; urgency=medium + + [ Harald Welte ] + * initial debian package + + -- Harald Welte Tue, 30 Jun 2020 18:24:51 +0100 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..a74d65a --- /dev/null +++ b/debian/control @@ -0,0 +1,24 @@ +Source: osmo-isdntap +Section: net +Priority: extra +Maintainer: Harald Welte +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 diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..bc69b3d --- /dev/null +++ b/debian/copyright @@ -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 . + diff --git a/debian/osmo-isdntap.install b/debian/osmo-isdntap.install new file mode 100644 index 0000000..4be8422 --- /dev/null +++ b/debian/osmo-isdntap.install @@ -0,0 +1,3 @@ +/etc/osmocom/osmo-isdntap.cfg +lib/systemd/system/osmo-isdntap.service +usr/bin/osmo-isdntap diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..33be3c8 --- /dev/null +++ b/debian/rules @@ -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 diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..8acb8f8 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + examples \ + $(NULL) diff --git a/doc/examples/Makefile.am b/doc/examples/Makefile.am new file mode 100644 index 0000000..7b09023 --- /dev/null +++ b/doc/examples/Makefile.am @@ -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 diff --git a/doc/examples/osmo-isdntap.cfg b/doc/examples/osmo-isdntap.cfg new file mode 100644 index 0000000..e69de29 diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 0000000..42cf3d2 --- /dev/null +++ b/git-version-gen @@ -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 . + +# 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: diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..05c0e42 --- /dev/null +++ b/src/Makefile.am @@ -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) diff --git a/src/dahdi.c b/src/dahdi.c new file mode 100644 index 0000000..84140ae --- /dev/null +++ b/src/dahdi.c @@ -0,0 +1,267 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#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, +}; diff --git a/src/isdntap.c b/src/isdntap.c new file mode 100644 index 0000000..500358c --- /dev/null +++ b/src/isdntap.c @@ -0,0 +1,518 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#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 diff --git a/src/isdntap.h b/src/isdntap.h new file mode 100644 index 0000000..86202e2 --- /dev/null +++ b/src/isdntap.h @@ -0,0 +1,125 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +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); diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..d820887 --- /dev/null +++ b/src/log.c @@ -0,0 +1,46 @@ +/* + * log.c + * + * (C) 2022 by Harald Welte + * + * 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 +#include + +#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), +}; diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..13696e9 --- /dev/null +++ b/src/log.h @@ -0,0 +1,34 @@ +/* + * log.h + * + * (C) 2022 by Harald Welte + * + * 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 + +enum { + DITAP, + DQ931, +}; + +extern const struct log_info log_info; diff --git a/src/osmo-isdntap.c b/src/osmo-isdntap.c new file mode 100644 index 0000000..d84be5d --- /dev/null +++ b/src/osmo-isdntap.c @@ -0,0 +1,197 @@ +/* + * osmo-isdntap.c + * + * (C) 2022 by Harald Welte + * + * 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 +#include +#include +#include +#include + +#define _GNU_SOURCE +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 \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; +} diff --git a/src/q931.h b/src/q931.h new file mode 100644 index 0000000..0ffe6f7 --- /dev/null +++ b/src/q931.h @@ -0,0 +1,121 @@ +#pragma once + +#include +#include +#include + +/* 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); diff --git a/src/q931_decode.c b/src/q931_decode.c new file mode 100644 index 0000000..67f8a36 --- /dev/null +++ b/src/q931_decode.c @@ -0,0 +1,261 @@ + +#include + +#include + +#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; +} diff --git a/src/vty.c b/src/vty.c new file mode 100644 index 0000000..111498c --- /dev/null +++ b/src/vty.c @@ -0,0 +1,179 @@ +/* osmo-isdntap VTY interface */ +/* (C) 2022 by Harald Welte + * 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 . + * + */ + +#define _GNU_SOURCE /* struct ucred */ +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..8dde4de --- /dev/null +++ b/tests/Makefile.am @@ -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 $@