diff options
author | Andreas Eversberg <jolly@eversberg.eu> | 2020-09-27 14:17:11 +0200 |
---|---|---|
committer | Andreas Eversberg <jolly@eversberg.eu> | 2020-12-29 19:02:56 +0100 |
commit | fde7cc2ce319bf294ded54da0822672fe33b1923 (patch) | |
tree | b3c87039dbc99fa5dc2f67a11a91ae8196e7ab4c /src/router/routing.c |
Initial GIT import
Diffstat (limited to 'src/router/routing.c')
-rw-r--r-- | src/router/routing.c | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/src/router/routing.c b/src/router/routing.c new file mode 100644 index 0000000..118ef78 --- /dev/null +++ b/src/router/routing.c @@ -0,0 +1,490 @@ +/* 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 "../libdebug/debug.h" +#include "call.h" + +extern char **environ; + +static const char *env_int(const char *name, int value) +{ + char *string = malloc(strlen(name) + 20); + + sprintf(string, "CC_%s=%d", name, value); + + return string; +} + +static const char *env_string(const char *name, const char *value) +{ + char *string = malloc(strlen(name) + strlen(value) + 8); + + sprintf(string, "CC_%s=%s", name, value); + + return string; +} + +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; +} + +/* prepare environment with setup info */ +void routing_env_msg(routing_t *routing, osmo_cc_msg_t *msg) +{ + uint8_t coding, capability, mode; + uint8_t type, plan, present, screen, reason; + char number[256]; + int rc, i; + + for (i = 0; environ[i]; i++) { + routing->envp[routing->envc++] = strdup(environ[i]); + } + + rc = osmo_cc_get_ie_bearer(msg, 0, &coding, &capability, &mode); + if (rc >= 0) { + routing->envp[routing->envc++] = env_int("BEARER_CODING", coding); + routing->envp[routing->envc++] = env_int("BEARER_CAPABILITY", capability); + routing->envp[routing->envc++] = env_int("BEARER_MODE", mode); + } + + rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, number, sizeof(number)); + if (rc >= 0) { + routing->envp[routing->envc++] = env_int("CALLING_TYPE", type); + routing->envp[routing->envc++] = env_int("CALLING_PLAN", plan); + routing->envp[routing->envc++] = env_int("CALLING_PRESENT", present); + routing->envp[routing->envc++] = env_int("CALLING_SCREEN", screen); + routing->envp[routing->envc++] = env_string("CALLING", number); + } + rc = osmo_cc_get_ie_calling_interface(msg, 0, number, sizeof(number)); + if (rc >= 0) { + routing->envp[routing->envc++] = env_string("CALLING_INTERFACE", number); + } + + rc = osmo_cc_get_ie_calling(msg, 2, &type, &plan, &present, &screen, number, sizeof(number)); + if (rc >= 0) { + routing->envp[routing->envc++] = env_int("CALLING2_TYPE", type); + routing->envp[routing->envc++] = env_int("CALLING2_PLAN", plan); + routing->envp[routing->envc++] = env_int("CALLING2_PRESENT", present); + routing->envp[routing->envc++] = env_int("CALLING2_SCREEN", screen); + routing->envp[routing->envc++] = env_string("CALLING2", number); + } + + rc = osmo_cc_get_ie_redir(msg, 0, &type, &plan, &present, &screen, &reason, number, sizeof(number)); + if (rc >= 0) { + routing->envp[routing->envc++] = env_int("REDIRECTING_TYPE", type); + routing->envp[routing->envc++] = env_int("REDIRECTING_PLAN", plan); + routing->envp[routing->envc++] = env_int("REDIRECTING_PRESENT", present); + routing->envp[routing->envc++] = env_int("REDIRECTING_SCREEN", screen); + routing->envp[routing->envc++] = env_int("REDIRECTING_REASON", reason); + routing->envp[routing->envc++] = env_string("REDIRECTING", number); + } + + rc = osmo_cc_get_ie_called(msg, 0, &type, &plan, number, sizeof(number)); + if (rc >= 0) { + routing->envp[routing->envc++] = env_int("DIALING_TYPE", type); + routing->envp[routing->envc++] = env_int("DIALING_PLAN", plan); + } else + number[0] = 0; + /* variable must always be present, so it can be updated when overlap dialing */ + routing->envc_dialing = routing->envc; + routing->envp[routing->envc++] = env_string("DIALING", number); + + 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; + routing->envp[routing->envc++] = env_string("KEYPAD", number); + + rc = osmo_cc_get_ie_complete(msg, 0); + if (rc >= 0) + routing->envp[routing->envc++] = env_int("COMPLETE", 1); + + routing->envp[routing->envc++] = NULL; +} + +/* update environment with info message */ +void routing_env_dialing(routing_t *routing, const char *number, const char *keypad) +{ + free((char *)routing->envp[routing->envc_dialing]); + routing->envp[routing->envc_dialing] = env_string("DIALING", number); + + free((char *)routing->envp[routing->envc_keypad]); + routing->envp[routing->envc_keypad] = env_string("KEYPAD", keypad); +} + +void routing_env_free(routing_t *routing) +{ + /* remove env */ + while (routing->envc) { + free((char *)routing->envp[--(routing->envc)]); + routing->envp[routing->envc] = NULL; + } +} + +/* run script */ +void 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, flags; + + rc = pipe(in_pipe); + if (rc < 0) { + epipe: + PDEBUG(DROUTER, DEBUG_ERROR, "pipe() failed: errno=%d\n", errno); + abort(); + } + rc = pipe(out_pipe); + if (rc < 0) + goto epipe; + rc = pipe(err_pipe); + if (rc < 0) + goto epipe; + + pid = fork(); + if (pid < 0) { + PDEBUG(DROUTER, DEBUG_ERROR, "fork() failed: errno=%d\n", errno); + abort(); + } + 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 */ + PDEBUG(DROUTER, DEBUG_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') { + PDEBUG(DROUTER, DEBUG_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]); + + /* make nonblocking IO */ + flags = fcntl(in_pipe[1], F_GETFL); + flags |= O_NONBLOCK; + fcntl(in_pipe[1], F_SETFL, flags); + flags = fcntl(out_pipe[0], F_GETFL); + flags |= O_NONBLOCK; + fcntl(out_pipe[0], F_SETFL, flags); + flags = fcntl(err_pipe[0], F_GETFL); + flags |= O_NONBLOCK; + fcntl(err_pipe[0], F_SETFL, flags); + + /* attach pipes and pid to routing */ + routing->script_pid = pid; + routing->script_stdin = in_pipe[1]; + routing->script_stdout = out_pipe[0]; + routing->script_stdout_child = out_pipe[1]; + routing->script_stderr = err_pipe[0]; + routing->script_stderr_child = err_pipe[1]; + + routing->routing = 1; + + PDEBUG(DROUTER, DEBUG_DEBUG, "Routing script started.\n"); +} + + +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) { + close(routing->script_stdin); + routing->script_stdin = 0; + } + if (routing->script_stdout) { + close(routing->script_stdout); + routing->script_stdout = 0; + close(routing->script_stdout_child); + routing->script_stdout_child = 0; + } + if (routing->script_stderr) { + close(routing->script_stderr); + routing->script_stderr = 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; + + PDEBUG(DROUTER, DEBUG_DEBUG, "Routing script stopped.\n"); +} + + +void routing_send(routing_t *routing, const char *string) +{ + PDEBUG(DROUTER, DEBUG_DEBUG, "Sending line to routing script: '%s'\n", string); + enqueue_string(&routing->stdin_queue, string, "\n"); +} + +static int routing_handle_stdin(routing_t *routing) +{ + char *string; + int rc; + + /* write to script */ + if (routing->stdin_queue && routing->script_stdin) { + rc = write(routing->script_stdin, routing->stdin_queue->string, strlen(routing->stdin_queue->string)); + if (rc < 0) { + if (errno == EAGAIN) + return -EAGAIN; + } + if (rc <= 0) { + close(routing->script_stdin); + routing->script_stdin = 0; + return 0; + } + string = dequeue_string(&routing->stdin_queue); + free(string); + return 0; + } + + return -EINVAL; +} + +static int routing_handle_stdout(routing_t *routing) +{ + int rc, i; + + /* read from script */ + if (routing->script_stdout) { + rc = read(routing->script_stdout, routing->stdout_buffer + routing->stdout_pos, sizeof(routing->stdout_buffer) - routing->stdout_pos); + if (rc < 0) { + if (errno == EAGAIN) { + /* if script has terminated (pid is set to 0) and stdout/stderr queue is empty */ + if (!routing->script_pid && !routing->stdout_queue && !routing->stderr_queue) { + routing_stop(routing); + routing_close(routing); + return 0; + } + return -EAGAIN; + } + } + if (rc <= 0) { + close(routing->script_stdout); + routing->script_stdout = 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)) { + PDEBUG(DROUTER, DEBUG_ERROR, "Output line from script too long, please fix!\n"); + routing->stdout_pos = 0; + } + return 0; + } + + return -EINVAL; +} + +static int routing_handle_stderr(routing_t *routing) +{ + int rc, i; + + /* read from script */ + if (routing->script_stderr) { + rc = read(routing->script_stderr, routing->stderr_buffer + routing->stderr_pos, sizeof(routing->stderr_buffer) - routing->stderr_pos); + if (rc < 0) { + if (errno == EAGAIN) { + /* if script has terminated (pid is set to 0) and stdout/stderr queue is empty */ + if (!routing->script_pid && !routing->stdout_queue && !routing->stderr_queue) { + routing_stop(routing); + routing_close(routing); + return 0; + } + return -EAGAIN; + } + } + if (rc <= 0) { + close(routing->script_stderr); + routing->script_stderr = 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)) { + PDEBUG(DROUTER, DEBUG_ERROR, "Output line from script too long, please fix!\n"); + routing->stderr_pos = 0; + } + return 0; + } + + return -EINVAL; +} + +/* handle everything that has to do with routing + * note that work may cause the call to be destroyed + */ +int routing_handle(routing_t *routing) +{ + char *string; + int rc; + + rc = routing_handle_stdin(routing); + if (rc == 0) + return 1; + rc = routing_handle_stdout(routing); + if (rc == 0) + return 1; + rc = routing_handle_stderr(routing); + if (rc == 0) + return 1; + + if (routing->stdout_queue) { + string = dequeue_string(&routing->stdout_queue); + PDEBUG(DROUTER, DEBUG_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; + } + + 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 |