osmo-cc-router/src/router/routing.c

627 lines
19 KiB
C

/* call routing
*
* (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/>.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <signal.h>
#include "../liblogging/logging.h"
#include "call.h"
extern char **environ;
static struct env {
int coding, capability, mode;
int type, plan, present, screen, reason;
char *number;
int network_type;
char *network_id;
int complete;
} env;
struct env_def {
const char *n;
char **s;
int *i;
const char *d;
const char *(*value2name)(int value);
int num;
} env_def[] = {
{ .n = "BEARER_CODING", .i = &env.coding, .d = "coding of bearer capability", .value2name = osmo_cc_coding_value2name, .num = OSMO_CC_CODING_NUM },
{ .n = "BEARER_CAPABILITY", .i = &env.capability, .d = "bearer capability", .value2name = osmo_cc_capability_value2name, .num = OSMO_CC_CAPABILITY_NUM },
{ .n = "BEARER_MODE", .i = &env.mode, .d = "mode of bearer", .value2name = osmo_cc_mode_value2name, .num = OSMO_CC_MODE_NUM },
{ .n = "CALLING_TYPE", .i = &env.type, .d = "type of calling number", .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM },
{ .n = "CALLING_PLAN", .i = &env.plan, .d = "numbering plan of calling number", .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM },
{ .n = "CALLING_PRESENT", .i = &env.present, .d = "presentation of calling number", .value2name = osmo_cc_present_value2name, .num = OSMO_CC_PRESENT_NUM },
{ .n = "CALLING_SCREEN", .i = &env.screen, .d = "screen of calling number", .value2name = osmo_cc_screen_value2name, .num = OSMO_CC_SCREEN_NUM },
{ .n = "CALLING", .s = &env.number, .d = "calling number" },
{ .n = "CALLING_INTERFACE", .s = &env.number, .d = "calling interface name" },
{ .n = "CALLING2_TYPE", .i = &env.type, .d = "type of second calling number", .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM },
{ .n = "CALLING2_PLAN", .i = &env.plan, .d = "numbering plan of awxons calling number", .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM},
{ .n = "CALLING2_PRESENT", .i = &env.present, .d = "presentation of second calling number", .value2name = osmo_cc_present_value2name, .num = OSMO_CC_PRESENT_NUM },
{ .n = "CALLING2_SCREEN", .i = &env.screen, .d = "screen of second calling number", .value2name = osmo_cc_screen_value2name, .num = OSMO_CC_SCREEN_NUM },
{ .n = "CALLING2", .s = &env.number, .d = "second calling number" },
{ .n = "NETWORK_TYPE", .i = &env.network_type, .d = "type of calling network" },
{ .n = "NETWORK_ID", .s = &env.network_id, .d = "id of subscriber at calling network" },
{ .n = "REDIRECTING_TYPE", .i = &env.type, .d = "type of redirecting number", .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM },
{ .n = "REDIRECTING_PLAN", .i = &env.plan, .d = "numbering plan of redirecting number", .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM },
{ .n = "REDIRECTING_PRESENT", .i = &env.present, .d = "presentation of redirecting number", .value2name = osmo_cc_present_value2name, .num = OSMO_CC_PRESENT_NUM },
{ .n = "REDIRECTING_SCREEN", .i = &env.screen, .d = "screen of redirecting number", .value2name = osmo_cc_screen_value2name, .num = OSMO_CC_SCREEN_NUM },
{ .n = "REDIRECTING_REASON", .i = &env.reason, .d = "reason for redirecting", .value2name = osmo_cc_redir_reason_value2name, .num = OSMO_CC_REDIR_REASON_NUM },
{ .n = "REDIRECTING", .s = &env.number, .d = "redirecting number" },
{ .n = "DIALING_TYPE", .i = &env.type, .d = "type of dialing number", .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM },
{ .n = "DIALING_PLAN", .i = &env.plan, .d = "numbering plan of dialing number", .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM },
{ .n = "DIALING", .s = &env.number, .d = "dialing number" },
{ .n = "KEYPAD", .s = &env.number, .d = "keypad dialing (May be uses for special ISDN applications.)" },
{ .n = "COMPLETE", .i = &env.complete, .d = "set to '1' if dialing is a complete number" },
{ .n = NULL }
};
static int env_set(routing_t *routing, const char *name, int index)
{
int i;
for (i = 0; env_def[i].n; i++) {
if (!strcmp(env_def[i].n, name))
break;
}
if (!env_def[i].n) {
LOGP(DROUTER, LOGL_ERROR, "Software error: Environment variable '%s' does not exist in definition, please fix!\n", name);
return index;
}
if (env_def[i].s) {
char *string = malloc(strlen(name) + strlen(*env_def[i].s) + 8);
sprintf(string, "CC_%s=%s", name, *env_def[i].s);
routing->envp[index++] = string;
}
if (env_def[i].i) {
char *string = malloc(strlen(name) + 30);
sprintf(string, "CC_%s=%d", name, *env_def[i].i);
routing->envp[index++] = string;
if (env_def[i].value2name && env_def[i].value2name(*env_def[i].i)[0] != '<') {
char *string = malloc(strlen(name) + strlen(env_def[i].value2name(*env_def[i].i)) + 16);
sprintf(string, "CC_%s_NAME=%s", name, env_def[i].value2name(*env_def[i].i));
routing->envp[index++] = string;
}
}
return index;
}
static void env_add(routing_t *routing, const char *name)
{
routing->envc = env_set(routing, name, routing->envc);
}
void env_help(void)
{
int i, j;
printf("Available environment variables at routing script:\n\n");
for (i = 0; env_def[i].n; i++) {
printf("Variable: CC_%s=<value>\n", env_def[i].n);
printf(" Description: %s\n", env_def[i].d);
if (env_def[i].value2name) {
for (j = 0; j < 256; j++) {
if (env_def[i].value2name(j)[0] != '<')
printf(" Alternative variable for value '%d': CC_%s_NAME=%s\n", j, env_def[i].n, env_def[i].value2name(j));
}
}
}
}
/* prepare environment with setup info */
void routing_env_msg(routing_t *routing, osmo_cc_msg_t *msg)
{
int rc, i;
uint8_t coding, capability, mode;
uint8_t type, plan, present, screen, reason;
char number[256];
uint8_t network_type;
char network_id[256];
for (i = 0; environ[i]; i++) {
routing->envp[routing->envc++] = strdup(environ[i]);
}
memset(&env, 0, sizeof(env));
rc = osmo_cc_get_ie_bearer(msg, 0, &coding, &capability, &mode);
if (rc >= 0) {
env.coding = coding;
env.capability = capability;
env.mode = mode;
env_add(routing, "BEARER_CODING");
env_add(routing, "BEARER_CAPABILITY");
env_add(routing, "BEARER_MODE");
}
rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, number, sizeof(number));
if (rc >= 0) {
env.type = type;
env.plan = plan;
env.present = present;
env.screen = screen;
env.number = number;
env_add(routing, "CALLING_TYPE");
env_add(routing, "CALLING_PLAN");
env_add(routing, "CALLING_PRESENT");
env_add(routing, "CALLING_SCREEN");
env_add(routing, "CALLING");
}
rc = osmo_cc_get_ie_calling_interface(msg, 0, number, sizeof(number));
if (rc >= 0) {
env.number = number;
env_add(routing, "CALLING_INTERFACE");
}
rc = osmo_cc_get_ie_calling(msg, 1, &type, &plan, &present, &screen, number, sizeof(number));
if (rc >= 0) {
env.type = type;
env.plan = plan;
env.present = present;
env.screen = screen;
env.number = number;
env_add(routing, "CALLING2_TYPE");
env_add(routing, "CALLING2_PLAN");
env_add(routing, "CALLING2_PRESENT");
env_add(routing, "CALLING2_SCREEN");
env_add(routing, "CALLING2");
}
rc = osmo_cc_get_ie_calling_network(msg, 0, &network_type, network_id, sizeof(network_id));
if (rc >= 0) {
env.network_type = network_type;
env.network_id = network_id;
env_add(routing, "NETWORK_TYPE");
env_add(routing, "NETWORK_ID");
}
rc = osmo_cc_get_ie_redir(msg, 0, &type, &plan, &present, &screen, &reason, number, sizeof(number));
if (rc >= 0) {
env.type = type;
env.plan = plan;
env.present = present;
env.screen = screen;
env.reason = reason;
env.number = number;
env_add(routing, "REDIRECTING_TYPE");
env_add(routing, "REDIRECTING_PLAN");
env_add(routing, "REDIRECTING_PRESENT");
env_add(routing, "REDIRECTING_SCREEN");
env_add(routing, "REDIRECTING_REASON");
env_add(routing, "REDIRECTING");
}
rc = osmo_cc_get_ie_called(msg, 0, &type, &plan, number, sizeof(number));
if (rc >= 0) {
env.type = type;
env.plan = plan;
env_add(routing, "DIALING_TYPE");
env_add(routing, "DIALING_PLAN");
} else
number[0] = 0;
/* variable must always be present, so it can be updated when overlap dialing */
routing->envc_dialing = routing->envc;
env.number = number;
env_add(routing, "DIALING");
rc = osmo_cc_get_ie_keypad(msg, 0, number, sizeof(number));
if (rc < 0)
number[0] = 0;
/* variable must always be present, so it can be updated when overlap dialing */
routing->envc_keypad = routing->envc;
env.number = number;
env_add(routing, "KEYPAD");
rc = osmo_cc_get_ie_complete(msg, 0);
if (rc >= 0)
env.complete = 1;
/* variable must always be present, so it can be updated when overlap dialing */
routing->envc_complete = routing->envc;
env_add(routing, "COMPLETE");
routing->envp[routing->envc++] = NULL;
}
/* update environment with info message */
void routing_env_dialing(routing_t *routing, char *number, char *keypad, int complete)
{
free((char *)routing->envp[routing->envc_dialing]);
env.number = number;
env_set(routing, "DIALING", routing->envc_dialing);
free((char *)routing->envp[routing->envc_keypad]);
env.number = keypad;
env_set(routing, "KEYPAD", routing->envc_keypad);
free((char *)routing->envp[routing->envc_complete]);
env.complete = complete;
env_set(routing, "COMPLETE", routing->envc_complete);
}
static void enqueue_string(struct string_queue **queue_p, const char *string, const char *suffix)
{
struct string_queue *queue;
queue = calloc(1, sizeof(*queue));
queue->string = malloc(strlen(string) + strlen(suffix) + 1);
strcpy(queue->string, string);
strcat(queue->string, suffix);
while (*queue_p)
queue_p = &((*queue_p)->next);
*queue_p = queue;
}
static char *dequeue_string(struct string_queue **queue_p)
{
struct string_queue *queue = *queue_p;
char *string;
if (!queue)
return NULL;
string = queue->string;
*queue_p = queue->next;
free(queue);
return string;
}
void routing_env_free(routing_t *routing)
{
/* remove env */
while (routing->envc) {
free((char *)routing->envp[--(routing->envc)]);
routing->envp[routing->envc] = NULL;
}
}
static int routing_handle_stdin(struct osmo_fd *ofd, unsigned int what);
static int routing_handle_stdout(struct osmo_fd *ofd, unsigned int what);
static int routing_handle_stderr(struct osmo_fd *ofd, unsigned int what);
/* run script */
int routing_start(routing_t *routing, const char *script, const char *shell)
{
int in_pipe[2], out_pipe[2], err_pipe[2];
pid_t pid;
char x;
int rc;
rc = pipe(in_pipe);
if (rc < 0) {
LOGP(DROUTER, LOGL_ERROR, "STDIN pipe() failed: errno=%d\n", errno);
error_in_pipe:
return -errno;
}
rc = pipe(out_pipe);
if (rc < 0) {
LOGP(DROUTER, LOGL_ERROR, "STDOUT pipe() failed: errno=%d\n", errno);
error_out_pipe:
close(in_pipe[0]);
close(in_pipe[1]);
goto error_in_pipe;
}
rc = pipe(err_pipe);
if (rc < 0) {
LOGP(DROUTER, LOGL_ERROR, "STDERR pipe() failed: errno=%d\n", errno);
error_err_pipe:
close(out_pipe[0]);
close(out_pipe[1]);
goto error_out_pipe;
}
pid = fork();
if (pid < 0) {
LOGP(DROUTER, LOGL_ERROR, "fork() failed: errno=%d\n", errno);
close(err_pipe[0]);
close(err_pipe[1]);
goto error_err_pipe;
}
if (pid == 0) {
const char *argv[] = { shell, "-c", script, NULL };
struct rlimit rlim;
int i;
/* CHILD */
/* redirect pipes to stdio */
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
dup2(in_pipe[0], STDIN_FILENO);
dup2(out_pipe[1], STDOUT_FILENO);
dup2(err_pipe[1], STDERR_FILENO);
/* close all file descriptors except std* fss */
getrlimit(RLIMIT_NOFILE, &rlim);
for (i = 3; i < (int)rlim.rlim_cur; i++)
close(i);
/* tell father that we closed all fds */
putchar('x');
fflush(stdout);
/* become script */
rc = execvpe(argv[0], (char * const*)argv, (char * const*)routing->envp);
if (rc < 0) {
printf("error \"%s\"\n", strerror(errno));
}
_exit(0);
}
/* PARENT */
LOGP(DROUTER, LOGL_DEBUG, "Running script '%s' as child %d\n", script, pid);
/* wait for clild to complete closing file descriptors */
rc = read(out_pipe[0], &x, 1);
if (rc != 1 || x != 'x') {
LOGP(DROUTER, LOGL_ERROR, "communication with child failed!\n");
kill(pid, SIGKILL);
abort();
}
/* close unused file descriptors, except the child's side
* this is because we need to keep it open to get all data from stdout/stderr
* after the child exitted. for some reason the pipe is flused on close.
*/
close(in_pipe[0]);
/* attach pipes and pid to routing */
routing->script_pid = pid;
routing->script_stdin.fd = in_pipe[1];
routing->script_stdin.cb = routing_handle_stdin;
routing->script_stdin.data = routing;
routing->script_stdin.when = 0;
osmo_fd_register(&routing->script_stdin);
routing->script_stdout.fd = out_pipe[0];
routing->script_stdout.cb = routing_handle_stdout;
routing->script_stdout.data = routing;
routing->script_stdout.when = OSMO_FD_READ;
osmo_fd_register(&routing->script_stdout);
routing->script_stdout_child = out_pipe[1];
routing->script_stderr.fd = err_pipe[0];
routing->script_stderr.cb = routing_handle_stderr;
routing->script_stderr.data = routing;
routing->script_stderr.when = OSMO_FD_READ;
osmo_fd_register(&routing->script_stderr);
routing->script_stderr_child = err_pipe[1];
routing->routing = 1;
LOGP(DROUTER, LOGL_DEBUG, "Routing script started.\n");
return 0;
}
void routing_stop(routing_t *routing)
{
char *string;
/* kill or at least try to */
if (routing->script_pid) {
kill(routing->script_pid, SIGTERM);
routing->script_pid = 0;
}
/* close files towards script */
if (routing->script_stdin.fd) {
osmo_fd_unregister(&routing->script_stdin);
close(routing->script_stdin.fd);
routing->script_stdin.fd = 0;
}
if (routing->script_stdout.fd) {
osmo_fd_unregister(&routing->script_stdout);
close(routing->script_stdout.fd);
routing->script_stdout.fd = 0;
close(routing->script_stdout_child);
routing->script_stdout_child = 0;
}
if (routing->script_stderr.fd) {
osmo_fd_unregister(&routing->script_stderr);
close(routing->script_stderr.fd);
routing->script_stderr.fd = 0;
close(routing->script_stderr_child);
routing->script_stderr_child = 0;
}
/* flush queues */
while (routing->stdin_queue) {
string = dequeue_string(&routing->stdin_queue);
free(string);
}
while (routing->stdout_queue) {
string = dequeue_string(&routing->stdout_queue);
free(string);
}
routing->stdout_pos = 0;
while (routing->stderr_queue) {
string = dequeue_string(&routing->stderr_queue);
free(string);
}
routing->stderr_pos = 0;
routing->routing = 0;
LOGP(DROUTER, LOGL_DEBUG, "Routing script stopped.\n");
}
void routing_send(routing_t *routing, const char *string)
{
LOGP(DROUTER, LOGL_DEBUG, "Sending line to routing script: '%s'\n", string);
enqueue_string(&routing->stdin_queue, string, "\n");
routing->script_stdin.when |= OSMO_FD_WRITE;
}
static int routing_handle_stdin(struct osmo_fd *ofd, unsigned int what)
{
routing_t *routing = ofd->data;
char *string;
int rc;
/* write to script */
if ((what & OSMO_FD_WRITE)) {
if (routing->stdin_queue) {
rc = write(routing->script_stdin.fd, routing->stdin_queue->string, strlen(routing->stdin_queue->string));
if (rc <= 0) {
osmo_fd_unregister(&routing->script_stdin);
close(routing->script_stdin.fd);
routing->script_stdin.fd = 0;
return 0;
}
string = dequeue_string(&routing->stdin_queue);
free(string);
}
/* as soon as empty */
if (!routing->stdin_queue)
routing->script_stdin.when &= ~OSMO_FD_WRITE;
}
return 0;
}
static int routing_handle_stdout(struct osmo_fd *ofd, unsigned int what)
{
routing_t *routing = ofd->data;
int rc, i;
/* read from script */
if ((what & OSMO_FD_READ)) {
rc = read(routing->script_stdout.fd, routing->stdout_buffer + routing->stdout_pos, sizeof(routing->stdout_buffer) - routing->stdout_pos);
if (rc <= 0) {
osmo_fd_unregister(&routing->script_stdout);
close(routing->script_stdout.fd);
routing->script_stdout.fd = 0;
return 0;
}
routing->stdout_pos += rc;
i = 0;
while (i < routing->stdout_pos) {
if (routing->stdout_buffer[i] != '\n') {
i++;
continue;
}
routing->stdout_buffer[i] = '\0';
enqueue_string(&routing->stdout_queue, routing->stdout_buffer, "");
i++;
if (i < routing->stdout_pos)
memcpy(routing->stdout_buffer, routing->stdout_buffer + i, routing->stdout_pos - i);
routing->stdout_pos -= i;
i = 0;
}
if (i == sizeof(routing->stdout_buffer)) {
LOGP(DROUTER, LOGL_ERROR, "Output line from script too long, please fix!\n");
routing->stdout_pos = 0;
}
}
return 0;
}
static int routing_handle_stderr(struct osmo_fd *ofd, unsigned int what)
{
routing_t *routing = ofd->data;
int rc, i;
/* read from script */
if ((what & OSMO_FD_READ)) {
rc = read(routing->script_stderr.fd, routing->stderr_buffer + routing->stderr_pos, sizeof(routing->stderr_buffer) - routing->stderr_pos);
if (rc <= 0) {
osmo_fd_unregister(&routing->script_stderr);
close(routing->script_stderr.fd);
routing->script_stderr.fd = 0;
return 0;
}
routing->stderr_pos += rc;
i = 0;
while (i < routing->stderr_pos) {
if (routing->stderr_buffer[i] != '\n') {
i++;
continue;
}
routing->stderr_buffer[i] = '\0';
enqueue_string(&routing->stderr_queue, routing->stderr_buffer, "");
i++;
if (i < routing->stderr_pos)
memcpy(routing->stderr_buffer, routing->stderr_buffer + i, routing->stderr_pos - i);
routing->stderr_pos -= i;
i = 0;
}
if (i == sizeof(routing->stderr_buffer)) {
LOGP(DROUTER, LOGL_ERROR, "Output line from script too long, please fix!\n");
routing->stderr_pos = 0;
}
}
return 0;
}
/* handle everything that has to do with routing */
int routing_handle(routing_t *routing)
{
char *string;
if (routing->stdout_queue) {
string = dequeue_string(&routing->stdout_queue);
LOGP(DROUTER, LOGL_DEBUG, "Routing script returned line from stdout: '%s'\n", string);
routing_receive_stdout(routing, string);
free(string);
return 1;
}
if (routing->stderr_queue) {
string = dequeue_string(&routing->stderr_queue);
routing_receive_stderr(routing, string);
free(string);
return 1;
}
/* if script has terminated (pid is set to 0) and stdout/stderr queue is empty */
if (routing->script_stdout.fd && !routing->script_pid && !routing->stdout_queue && !routing->stderr_queue) {
routing_stop(routing);
routing_close(routing);
return 1;
}
return 0;
}
#warning achja: beim router darf rtp-proxy und call nur erfolgen, wenn noch kein codec negoitiated wurde. call darf aber nach call erfolgen, wenn rtp-proxy verwendet wird. tones und record darf erfolgen wemm rtp-proxy verwendet wird