475 lines
14 KiB
C
475 lines
14 KiB
C
/* osmo-cc-misdn-endpoint main
|
|
*
|
|
* (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
|
|
* 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 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/>.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <sched.h>
|
|
#include "../libdebug/debug.h"
|
|
#include "../liboptions/options.h"
|
|
#include "../libg711/g711.h"
|
|
#include <mISDN/mbuffer.h>
|
|
#include "isdn.h"
|
|
#include "dss1.h"
|
|
#include "bridge.h"
|
|
#include "../libmisdn/core.h"
|
|
#include "ph_driver.h"
|
|
|
|
isdn_t *isdn_ep = NULL;
|
|
int num_kanal = 1;
|
|
|
|
static char law = 'a';
|
|
static const char *portname = NULL;
|
|
static int misdn_kernel = 0, misdn_user = 0;
|
|
static int ntmode = 0;
|
|
static int ptp = 0;
|
|
static int layer1hold = 0;
|
|
static int layer2hold = 0;
|
|
static const char *channel_out = NULL;
|
|
static const char *channel_in = NULL;
|
|
static const char *timeouts = NULL;
|
|
static int tx_delay = 0;
|
|
static int local_tones = 0;
|
|
static int debug_mISDN = 0;
|
|
static int serving_location = 1; /* private network serving local user */
|
|
static int use_hfc_bridging = 1;
|
|
static int bridging_server_only = 0;
|
|
static int pcm_slots = 32;
|
|
static int rt_prio = 1;
|
|
#define MAX_CC_ARGS 1024
|
|
static int cc_argc = 0;
|
|
static const char *cc_argv[MAX_CC_ARGS];
|
|
|
|
static void print_usage(const char *app)
|
|
{
|
|
printf("Usage: %s [--port <misdn port>] [--nt] [<options>]\n", app);
|
|
}
|
|
|
|
static void print_help()
|
|
{
|
|
/* - - */
|
|
printf(" -h --help\n");
|
|
printf(" This help\n");
|
|
printf(" --config [~/]<path to config file>\n");
|
|
printf(" Give a config file to use. If it starts with '~/', path is at home dir.\n");
|
|
printf(" Each line in config file is one option, '-' or '--' must not be given!\n");
|
|
debug_print_help();
|
|
printf(" --ulaw\n");
|
|
printf(" Use U-LAW for b-channel coding instead of alaw.\n");
|
|
printf(" -p --port <portnr> | <portname>\n");
|
|
printf(" Number or name of misdn port (see misdn_info).\n");
|
|
printf(" -s --socket <path>\n");
|
|
printf(" Path to UNIX socket that provides layer 1 connection to an ISDN\n");
|
|
printf(" interface.\n");
|
|
printf(" -n --nt\n");
|
|
printf(" The given port is configured as NT-mode, instead of TE-mode.\n");
|
|
printf(" -0 --ptp\n");
|
|
printf(" The given port is configured as point-to-point. PRI-Ports are always\n");
|
|
printf(" configured as point-to-point.\n");
|
|
printf(" -M --msn <msn1> [--msn <msn2> ...]\n");
|
|
printf(" Give one or multiple MSN numbers. If MSN numbers are defined, only\n");
|
|
printf(" MSN numbers that are defined are allowed. If a different MSN number\n");
|
|
printf(" is received from a phone, it is replaced by the first MSN number.\n");
|
|
printf(" This is only useful for NT-mode with point-to-mulipoint configuration.\n");
|
|
printf(" (Any MSN is accepted and forwarded by default.).\n");
|
|
printf(" -1 --layer-1-hold 0 | 1\n");
|
|
printf(" Keep layer 1 always active. (Default for point-to-point)\n");
|
|
printf(" -2 --layer-2-hold 0 | 1\n");
|
|
printf(" Keep layer 2 always active. (Default for point-to-point)\n");
|
|
printf(" --channel-out [force,][<number>][,...][,free][,any][,no]\n");
|
|
printf(" Channel selection list for all outgoing calls to the interface.\n");
|
|
printf(" A free channels is searched in order of appearance.\n");
|
|
printf(" force - Forces the selected port with no acceptable alternative.\n");
|
|
printf(" -> this will be automatically set for multipoint NT-mode ports\n");
|
|
printf(" <number>[,...] - List of channels to search.\n");
|
|
printf(" free - Select any free channel\n");
|
|
printf(" any - On outgoing calls, signal 'any channel acceptable'. (see DSS1)\n");
|
|
printf(" no - Signal 'no channel available' aka 'call waiting'. (see DSS1)\n");
|
|
printf(" --channel-in [<number>][,...][,free]\n");
|
|
printf(" Give list of channels to select for calls from ISDN\n");
|
|
printf(" Channel selection list for all incoming calls from the interface.\n");
|
|
printf(" A free channels is accepted if in the list.\n");
|
|
printf(" If any channel was requested, the first free channel found is selected.\n");
|
|
printf(" <number>[,...] - List of channels to accept.\n");
|
|
printf(" free - Accept any free channel\n");
|
|
|
|
printf(" --timeouts <setup>,<overlap>,<proceeding>,<alerting>,<disconnect>\n");
|
|
printf(" Alter ISDN protocol times.\n");
|
|
printf(" The default is 120 seconds for all states. Use 0 to disable.\n");
|
|
printf(" --tx-delay <ms>\n");
|
|
printf(" Give a delay in milliseconds. This is required for modem/fax. Audio\n");
|
|
printf(" toward ISDN interface is buffered with the given delay.\n");
|
|
printf(" This feature alters dejittering strategy.\n");
|
|
printf(" -T --local-tones german | oldgerman | american\n");
|
|
printf(" Send locally generated tones, if not provided by remote interface.\n");
|
|
printf(" -D --debug-misdn\n");
|
|
printf(" Enables mISDN stack debugging.\n");
|
|
printf(" --serving-location (see Q.931)\n");
|
|
printf(" 0 = user, 1 = private network serving local user (default=%d)\n", serving_location);
|
|
printf(" -B --bridging 0 | 1\n");
|
|
printf(" Enable or disable hardware bridging with HFC cards. (default = %d)\n", use_hfc_bridging);
|
|
printf(" --pcm-slots 32 | 64 | 128\n");
|
|
printf(" The number of slots must match the configured PCM bus size.\n");
|
|
printf(" (default = %d)\n", pcm_slots);
|
|
printf(" -r --realtime <prio>\n");
|
|
printf(" Set prio: 0 to disable, 99 for maximum (default = %d)\n", rt_prio);
|
|
printf(" -C --cc \"<osmo-cc arg>\" [--cc ...]\n");
|
|
printf(" Pass arguments to Osmo-CC endpoint. Use '-cc help' for description.\n");
|
|
}
|
|
|
|
#define OPT_ULAW 256
|
|
#define OPT_CHANNEL_OUT 257
|
|
#define OPT_CHANNEL_IN 258
|
|
#define OPT_TIMEOUTS 259
|
|
#define OPT_TX_DELAY 260
|
|
#define OPT_TX_GAIN 261
|
|
#define OPT_RX_GAIN 262
|
|
#define OPT_PIPELINE 263
|
|
#define OPT_DTMF 264
|
|
#define OPT_SERVING 265
|
|
#define OPT_PCM_SLOTS 266
|
|
#define OPT_BR_ONLY 267
|
|
|
|
static void add_options(void)
|
|
{
|
|
option_add('h', "help", 0);
|
|
option_add('v', "verbose", 1);
|
|
option_add(OPT_ULAW, "ulaw", 0);
|
|
option_add('p', "port", 1);
|
|
option_add('s', "socket", 1);
|
|
option_add('n', "nt", 0);
|
|
option_add('0', "ptp", 0);
|
|
option_add('M', "msn", 1);
|
|
option_add('1', "layer-1-hold", 1);
|
|
option_add('2', "layer-2-hold", 1);
|
|
option_add(OPT_CHANNEL_OUT, "channel-out", 1);
|
|
option_add(OPT_CHANNEL_IN, "channel-in", 1);
|
|
option_add(OPT_TIMEOUTS, "timeouts", 1);
|
|
option_add(OPT_TX_DELAY, "tx-delay", 1);
|
|
option_add('T', "local-tones", 1);
|
|
option_add('D', "debug-misdn", 0);
|
|
option_add(OPT_SERVING, "serving-location", 1);
|
|
option_add('B', "bridging", 1);
|
|
option_add(OPT_PCM_SLOTS, "pcm-slots", 1);
|
|
option_add(OPT_BR_ONLY, "bridging-server-only", 0);
|
|
option_add('r', "realtime", 1);
|
|
option_add('C', "cc", 1);
|
|
}
|
|
|
|
static int handle_options(int short_option, int argi, char **argv)
|
|
{
|
|
int rc;
|
|
|
|
switch (short_option) {
|
|
case 'h':
|
|
print_usage(argv[0]);
|
|
print_help();
|
|
return 0;
|
|
case 'v':
|
|
if (!strcasecmp(argv[argi], "list")) {
|
|
debug_list_cat();
|
|
return 0;
|
|
}
|
|
rc = parse_debug_opt(argv[argi]);
|
|
if (rc < 0) {
|
|
fprintf(stderr, "Failed to parse debug option, please use -h for help.\n");
|
|
return rc;
|
|
}
|
|
break;
|
|
case OPT_ULAW:
|
|
law = 'u';
|
|
break;
|
|
case 'p':
|
|
portname = options_strdup(argv[argi]);
|
|
misdn_kernel = 1;
|
|
break;
|
|
case 's':
|
|
portname = options_strdup(argv[argi]);
|
|
misdn_user = 1;
|
|
break;
|
|
case 'n':
|
|
ntmode = 1;
|
|
break;
|
|
case '0':
|
|
ptp = 1;
|
|
break;
|
|
case 'M':
|
|
isdn_add_msn(isdn_ep, argv[argi]);
|
|
break;
|
|
case '1':
|
|
layer1hold = atoi(argv[argi]);
|
|
break;
|
|
case '2':
|
|
layer2hold = atoi(argv[argi]);
|
|
break;
|
|
case OPT_CHANNEL_OUT:
|
|
channel_out = options_strdup(argv[argi]);
|
|
break;
|
|
case OPT_CHANNEL_IN:
|
|
channel_in = options_strdup(argv[argi]);
|
|
break;
|
|
case OPT_TIMEOUTS:
|
|
timeouts = options_strdup(argv[argi]);
|
|
break;
|
|
case OPT_TX_DELAY:
|
|
tx_delay = atoi(argv[argi]);
|
|
break;
|
|
case 'T':
|
|
if (!strcasecmp(argv[argi], "american"))
|
|
local_tones = TONES_TYPE_AMERICAN;
|
|
else if (!strcasecmp(argv[argi], "german"))
|
|
local_tones = TONES_TYPE_GERMAN;
|
|
else if (!strcasecmp(argv[argi], "oldgerman"))
|
|
local_tones = TONES_TYPE_OLDGERMAN;
|
|
else {
|
|
fprintf(stderr, "Invalid tones type given!\n");
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
case 'D':
|
|
debug_mISDN = 1;
|
|
break;
|
|
case OPT_SERVING:
|
|
serving_location = atoi(argv[argi]);
|
|
break;
|
|
case 'B':
|
|
use_hfc_bridging = atoi(argv[argi]);
|
|
break;
|
|
case OPT_PCM_SLOTS:
|
|
pcm_slots = strtoul(argv[argi], NULL, 10);
|
|
break;
|
|
case OPT_BR_ONLY:
|
|
bridging_server_only = 1;
|
|
break;
|
|
case 'r':
|
|
rt_prio = atoi(argv[argi]);
|
|
break;
|
|
case 'C':
|
|
if (!strcasecmp(argv[argi], "help")) {
|
|
osmo_cc_help();
|
|
return 0;
|
|
}
|
|
if (cc_argc == MAX_CC_ARGS) {
|
|
fprintf(stderr, "Too many osmo-cc args!\n");
|
|
break;
|
|
}
|
|
cc_argv[cc_argc++] = options_strdup(argv[argi]);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int quit = 0;
|
|
void sighandler(int sigset)
|
|
{
|
|
if (sigset == SIGHUP || sigset == SIGPIPE)
|
|
return;
|
|
|
|
fprintf(stderr, "\nSignal %d received.\n", sigset);
|
|
|
|
quit = 1;
|
|
}
|
|
|
|
static struct mi_ext_fn_s mi_fn;
|
|
|
|
static int mISDNlib_debug(const char *file, int line, const char *func, int __attribute__((unused)) level, const char *fmt, va_list va)
|
|
{
|
|
char text[256];
|
|
vsnprintf(text, sizeof(text) - 1, fmt, va);
|
|
text[sizeof(text) - 1] = '\0';
|
|
// PDEBUG(DDSS1, DEBUG_NOTICE, "libmisdn (file %s, line %d, func %s(), level %d): %s\n", file, line, func, level, text);
|
|
_printdebug(file, func, line, DMISDN, DEBUG_NOTICE, NULL, "%s", text);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int argi, rc;
|
|
int misdn_initialized = 0;
|
|
int ph_drv_initialized = 0;
|
|
int layer3_initialized = 0;
|
|
struct ph_socket_driver ph_drv;
|
|
const char *p;
|
|
|
|
g711_init();
|
|
|
|
isdn_ep = isdn_create();
|
|
if (!isdn_ep)
|
|
goto error;
|
|
|
|
cc_argv[cc_argc++] = options_strdup("remote auto");
|
|
|
|
/* handle options / config file */
|
|
add_options();
|
|
rc = options_config_file(argc, argv, "~/.osmocom/isdn/isdn.conf", handle_options);
|
|
if (rc < 0)
|
|
return 0;
|
|
argi = options_command_line(argc, argv, handle_options);
|
|
if (argi <= 0)
|
|
return argi;
|
|
|
|
if (bridging_server_only) {
|
|
bridge_socket_server_child(pcm_slots, 1);
|
|
return 0;
|
|
}
|
|
|
|
/* start bridge server */
|
|
if (use_hfc_bridging) {
|
|
brigde_socket_server(pcm_slots);
|
|
bridge_socket_client(isdn_ep);
|
|
}
|
|
|
|
if (!misdn_kernel && !misdn_user) {
|
|
fprintf(stderr, "You defined no mISDN port or layer 1 socket. You must define either one of them! Use '-h' for help.\n");
|
|
goto error;
|
|
}
|
|
if (misdn_kernel && misdn_user) {
|
|
fprintf(stderr, "You defined mISDN port and layer 1 socket. You must define either one of them!\n");
|
|
goto error;
|
|
}
|
|
|
|
/* init user space mISDN */
|
|
if (misdn_user) {
|
|
rc = mISDNInit((debug_mISDN) ? 0xffffffff : 0);
|
|
if (rc)
|
|
goto error;
|
|
misdn_initialized = 1;
|
|
rc = init_ph_socket_driver(&ph_drv, isdn_ep, portname, 0, ntmode, (debug_mISDN) ? 0xffffffff : 0);
|
|
if (rc)
|
|
goto error;
|
|
ph_drv_initialized = 1;
|
|
}
|
|
|
|
/* mISDNuser init and debug */
|
|
mi_fn.prt_debug = mISDNlib_debug;
|
|
init_layer3(4, &mi_fn, (misdn_user) ? 1 : 0);
|
|
layer3_initialized = 1;
|
|
mISDN_set_debug_level((debug_mISDN) ? 0xfffffeff : 0);
|
|
|
|
/* change tones to ulaw */
|
|
if (law == 'u')
|
|
isdn_tone_generate_ulaw_samples();
|
|
|
|
/* init instance */
|
|
rc = isdn_initialize(isdn_ep, (misdn_user) ? &ph_drv.ph_socket : NULL, law, portname, ntmode, ptp, layer1hold, layer2hold, channel_out, channel_in, timeouts, tx_delay, local_tones, serving_location);
|
|
if (rc) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "mISDN initializing failed!\n");
|
|
goto error;
|
|
}
|
|
|
|
rc = isdn_open(isdn_ep);
|
|
if (rc) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "mISDN open failed!\n");
|
|
goto error;
|
|
}
|
|
|
|
while ((p = strchr(portname, '/')))
|
|
portname = p + 1;
|
|
|
|
rc = osmo_cc_new(&isdn_ep->cc_ep, OSMO_CC_VERSION, isdn_ep->portname, serving_location, cc_message, NULL, isdn_ep, cc_argc, cc_argv);
|
|
if (rc < 0)
|
|
goto error;
|
|
|
|
/* real time priority */
|
|
if (rt_prio > 0) {
|
|
struct sched_param schedp;
|
|
int rc;
|
|
|
|
memset(&schedp, 0, sizeof(schedp));
|
|
schedp.sched_priority = rt_prio;
|
|
rc = sched_setscheduler(0, SCHED_RR, &schedp);
|
|
if (rc)
|
|
fprintf(stderr, "Error setting SCHED_RR with prio %d\n", rt_prio);
|
|
}
|
|
|
|
signal(SIGINT, sighandler);
|
|
signal(SIGHUP, sighandler);
|
|
signal(SIGTERM, sighandler);
|
|
signal(SIGPIPE, sighandler);
|
|
|
|
while (!quit) {
|
|
int work;
|
|
double timeout, misdn_timeout = 0.0;
|
|
|
|
do {
|
|
work = 0;
|
|
work |= osmo_cc_handle();
|
|
if (misdn_user) {
|
|
/* run workers of mISDN stacks in user space */
|
|
work |= mISDN_work();
|
|
work |= work_layer3(isdn_ep->ml3, &misdn_timeout);
|
|
}
|
|
} while (work);
|
|
/* handle all timers
|
|
* timeout is 0, if there was an event
|
|
* -> handle FDs without waiting, continue this loop
|
|
* timeout is not 0, if there was no event
|
|
* -> wait until FD or timeout
|
|
*/
|
|
timeout = process_timer();
|
|
/* reduce timer, if mISDN timer hits earlier */
|
|
if (misdn_timeout > 0.0 && misdn_timeout < timeout)
|
|
timeout = misdn_timeout;
|
|
/* wait for FD event until given timeout */
|
|
osmo_fd_select(timeout);
|
|
}
|
|
|
|
signal(SIGINT, SIG_DFL);
|
|
signal(SIGTSTP, SIG_DFL);
|
|
signal(SIGHUP, SIG_DFL);
|
|
signal(SIGTERM, SIG_DFL);
|
|
signal(SIGPIPE, SIG_DFL);
|
|
|
|
/* reset real time prio */
|
|
if (rt_prio > 0) {
|
|
struct sched_param schedp;
|
|
|
|
memset(&schedp, 0, sizeof(schedp));
|
|
schedp.sched_priority = 0;
|
|
sched_setscheduler(0, SCHED_OTHER, &schedp);
|
|
}
|
|
|
|
error:
|
|
if (isdn_ep) {
|
|
osmo_cc_delete(&isdn_ep->cc_ep);
|
|
isdn_destroy(isdn_ep);
|
|
}
|
|
|
|
if (layer3_initialized)
|
|
cleanup_layer3();
|
|
|
|
if (ph_drv_initialized)
|
|
exit_ph_socket_driver(&ph_drv);
|
|
|
|
if (misdn_initialized)
|
|
mISDN_cleanup();
|
|
|
|
options_free();
|
|
|
|
return 0;
|
|
}
|
|
|