#ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <stdio.h> #include <ctype.h> #include <unistd.h> #include <signal.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <fcntl.h> #include <libpicoevent.h> #include "libvoice.h" #include "line.h" #include "line-dect.h" #include "main.h" #include "ubus.h" #ifndef UCI_CONFIG_DIR #define UCI_CONFIG_DIR "/etc/config/" #endif int log_level = LOG_INFO; static int audio_rx_fd; static int audio_tx_fd; static int event_fd; static const char event_str[] = "/tmp/endpt_event"; static const char audio_tx_str[] = "/tmp/endpt_audio_tx"; static const char audio_rx_str[] = "/tmp/endpt_audio_rx"; static pe_stream_t *event_stream; static pe_stream_t *audio_rx_stream; static pe_list_t *event_list; static pe_bus_t *audio_rx_bus; static pe_bus_t *audio_tx_bus; int picoBase = -1; // Send a media packet to Asterisk static void send_media_to_asterisk(const struct media_packet_t *packet, int size) { pe_bus_send(audio_tx_bus, (uint8_t *)packet, size); } // Send a media packet to voice engine void send_media_to_voice_engine(pe_packet_t *p) { int conIdx; struct media_packet_t *packet = (struct media_packet_t *)p->data; conIdx = voice_connection_find(packet->line, packet->connection_id); if (conIdx == -1 || (packet->rtp[0] != 0x80 && packet->rtp[0] != 0x81) || !packet->rtp_size) { ENDPT_ERR("%s: bad packet\n", __func__); return; } voice_write_media_packet(packet); } // Handle media stream from Asterisk on audio rx socket and re-posts it to internal audio rx bus static void audio_rx_stream_handler(pe_stream_t *stream __attribute__((unused)), pe_event_t *event) { if (pe_bus_receive(audio_rx_bus, (pe_event_t *)event) < 0) { exit_failure("audio_bus rx buffer full\n"); return; // Drop packets if we can't cope the pace } pe_bus_dispatch(audio_rx_bus); } // Notify main thread there is a new event. We need to // make the ubus call from the main context. This function // is executed by a libvoip thread. int send_event_main(struct line_event_t *ev, char x) { if (!event_list || !event_fd) return -1; pe_list_add(event_list, (void*)ev); /* Trigger event loop to read event from queue. */ write(event_fd, &x, sizeof(char)); return 0; } // Handler for events sent to the main thread (in // general the events originates from libvoip). static void event_stream_handler(pe_stream_t *stream __attribute__((unused)), pe_event_t *event) { struct line_event_t *ev; if(!event || !event->in || event->count <= 0) return; switch(*event->in) { case EVENT_MAIN_LINE: ev = (struct line_event_t*) pe_list_get(event_list); if(!ev) break; ENDPT_DBG("Main event handler line %d event %s\n", ev->line, ev->name); ubus_call_asterisk(ev); ubus_broadcast_event(ev); // Also broadcast event to all break; case EVENT_MAIN_QUIT: ENDPT_DBG("Shutting down on quit event\n"); ubus_disable_receive(); voice_connection_deinit(); line_dect_deinit(); voice_line_deinit(); voice_engine_shutdown(); ubus_close(); exit_succes("voicemngr exits"); break; default: ENDPT_WARN("unknown event '%c'\n", *event->in); break; } } static void fifos_init(void) { int ret; /* Create event list for event fifo. */ event_list = pe_list_new(); if (!event_list) exit_failure("pe_list_new\n"); /* Create event fifo. */ ret = mkfifo(event_str, 0666); if ((ret == -1) && (errno != EEXIST)) exit_failure("Failed to create event pipe"); event_fd = open(event_str, O_RDWR|O_NONBLOCK); if(event_fd == -1) exit_failure("Failed to open event pipe"); /* Create audio fifos. Both ends open in R/W mode to become * independent of daemon startup order and peer restart * recovery. However, later each end use unidirectional flow. */ ret = mkfifo(audio_tx_str, 0666); if ((ret == -1) && (errno != EEXIST)) exit_failure("Failed to create tx pipe"); audio_tx_fd = open(audio_tx_str, O_RDWR|O_LARGEFILE|O_NONBLOCK); if(audio_tx_fd == -1) exit_failure("Failed to open tx pipe"); ret = mkfifo(audio_rx_str, 0666); if ((ret == -1) && (errno != EEXIST)) exit_failure("Failed to create rx pipe"); audio_rx_fd = open(audio_rx_str, O_RDWR|O_LARGEFILE|O_NONBLOCK); if(audio_rx_fd == -1) exit_failure("Failed to open rx pipe"); } static void streams_and_buses_init(void) { /* Listen for callback data from libvoip in our main event loop. We need to do it this way as ubus is not thread safe. */ event_stream = pe_stream_new(event_fd); pe_stream_add_handler(event_stream, sizeof(char), event_stream_handler); pe_base_add_stream(picoBase, event_stream); /* Listen for audio frames from asterisk. */ audio_rx_stream = pe_stream_new(audio_rx_fd); pe_stream_add_handler(audio_rx_stream, 4096, audio_rx_stream_handler); pe_base_add_stream(picoBase, audio_rx_stream); audio_rx_bus = pe_bus_new(audio_rx_fd); if (!audio_rx_bus) exit_failure("Failed to create audio_rx bus"); pe_bus_add_handler(audio_rx_bus, send_media_to_voice_engine); /* Send audio frames to asterisk. */ audio_tx_bus = pe_bus_new(audio_tx_fd); if (!audio_tx_bus) exit_failure("Failed to create audio_tx bus"); } // Posix signal handler. Wake up main event loop to exit prog. static void signal_handler(int signum __attribute__((unused)), siginfo_t *info __attribute__((unused)), void *ucontext __attribute__((unused))) { if(event_fd) send_event_main(NULL, EVENT_MAIN_QUIT); } // Setup Posix signal handling static int signals_init(void) { const int signals_handled[] = { SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGPWR }; struct sigaction action; unsigned int i; memset(&action, 0, sizeof(action)); action.sa_sigaction = signal_handler; action.sa_flags = SA_SIGINFO | SA_RESTART; sigemptyset(&action.sa_mask); for(i = 0; i < ARRAY_SIZE(signals_handled); i++) { sigaddset(&action.sa_mask, signals_handled[i]); } for(i = 0; i < ARRAY_SIZE(signals_handled); i++) { if(sigaction(signals_handled[i], &action, NULL)) { perror("Error trapping signals"); return -1; } } return 0; } static void voicemngr_event_report(int line, const char *event, int data) { /* Re-send the event to the main thread, which in turn re-send * to Asterisk. We need to do this due to UBUS is not thread safe, * so all UBUS calls must be done from a single thread. */ if(!DEBUG_LOOPBACK) { // Skip when debugging struct line_event_t *msg = malloc(sizeof(struct line_event_t)); if (!msg) { ENDPT_ERR("%s: out of memory\n", __func__); return; } msg->name = event; msg->data = (data < 0 ? 0 : data); // Discard negative values msg->line = line; send_event_main(msg, EVENT_MAIN_LINE); } } static int voicemngr_get_voice_port_cfg(const char *hw_board_voice_names, struct terminal_info_t *voice_port_cfg) { int res = 0; char *duplication; char *str, *token, *saveptr; if (!hw_board_voice_names || *hw_board_voice_names == '\0') { ENDPT_ERR("%s: environmental variable hw_board_VoicePortNames is not defined or empty\n", __func__); return -1; } memset(voice_port_cfg, 0, sizeof(*voice_port_cfg)); /* Duplicate the string to prevent environmental variable's value being modified by strtok_r(). That * might cause the wrong value being returned when running the program multiple times */ duplication = strdup(hw_board_voice_names); if (!duplication) { ENDPT_ERR("%s: out of memory\n", __func__); return -1; } ENDPT_DBG("hw_board_voice_names=[%s]\n", hw_board_voice_names); for (str = duplication; (token = strtok_r(str, " ,;:\t", &saveptr)) != NULL; str = NULL) { if ((size_t)voice_port_cfg->num_voice_ports >= sizeof(voice_port_cfg->voice_ports) / sizeof(voice_port_cfg->voice_ports)[0]) { ENDPT_ERR("%s: too many voice ports\n", __func__); res = -1; break; } if (strcasestr(token, "Tel") != NULL || strcasestr(token, "FXS") != NULL) { ENDPT_INFO("%s: port %d is FXS\n", __func__, voice_port_cfg->num_voice_ports); voice_port_cfg->voice_ports[voice_port_cfg->num_voice_ports++] = VOICE_LINE_FXS; voice_port_cfg->num_fxs++; } else if (strcasestr(token, "DECT") != NULL) { ENDPT_INFO("%s: port %d is DECT\n", __func__, voice_port_cfg->num_voice_ports); voice_port_cfg->voice_ports[voice_port_cfg->num_voice_ports++] = VOICE_LINE_DECT; voice_port_cfg->num_dect++; } else { ENDPT_ERR("%s: invalid port type, %s\n", __func__, token); res = -1; break; } } free(duplication); return res; } int main(int argc, char **argv) { (void) argc; struct uci_context *context = NULL; struct uci_package *package = NULL; struct terminal_info_t voice_port_cfg; int res, has_dect; char *error; const char *hw_board_has_dect; const char *hw_board_voice_names; openlog(argv[0], LOG_PID, LOG_DAEMON); // Load the UCI package context = uci_alloc_context(); if (!context) { ENDPT_ERR("uci_alloc_context() failed\n"); return EXIT_FAILURE; } uci_set_confdir(context, UCI_CONFIG_DIR); res = uci_load(context, uciStrConfig, &package); if (res != 0 || !package) { uci_get_errorstr(context, &error, ""); ENDPT_ERR("Failed to load uci package %s, %s\n", uciStrConfig, error); free(error); uci_free_context(context); context = NULL; return EXIT_FAILURE; } config_syslog(context, package); ENDPT_INFO("syslog level is set to %d\n", log_level); setlogmask(LOG_UPTO(log_level)); // Unbuffered stdout if(isatty(STDOUT_FILENO)) setbuf(stdout, NULL); // Check that ASCII to DTMF convertion works. if(!isdigit('0') || !isalpha('a')) { ENDPT_ERR("Source has invalid encoding.\n"); goto __error_ret; } /* Does the HW has DECT populated? This is stored in the * "db", but that can't be queried via the normal uci API so we * export it as an environment var in the startup script. */ hw_board_has_dect = getenv("hw_board_hasDect"); has_dect = hw_board_has_dect && *hw_board_has_dect == '1'; hw_board_voice_names = getenv("hw_board_VoicePortNames"); if (voicemngr_get_voice_port_cfg(hw_board_voice_names, &voice_port_cfg)) goto __error_ret; // Call this first. Find out how many phone lines we have if(voice_get_terminal_info(&voice_port_cfg, &terminal_info)) goto __error_ret; if(voice_register_cb_event_report(voicemngr_event_report)) goto __error_ret; if(voice_register_cb_egress_media(send_media_to_asterisk)) goto __error_ret; if(voice_line_preinit()) goto __error_ret; if(signals_init()) goto __error_ret; if((picoBase = pe_base_new()) < 0) goto __error_ret; fifos_init(); streams_and_buses_init(); if(ubus_init()) goto __error_ret; // Get configuration if(config_init(context, package)) goto __error_ret; uci_free_context(context); context = NULL; package = NULL; if(voice_line_init(has_dect)) goto __error_ret; if(line_dect_init()) goto __error_ret; if(voice_connection_init()) goto __error_ret; // Enable UBUS when all initializations have finished if(ubus_enable_receive()) goto __error_ret; ENDPT_INFO("voicemngr has started successfully\n"); // Listen for events and dispatch them to handlers. Run forever and does not return. pe_base_dispatch(picoBase); ENDPT_WARN("pe_base_dispatch() exited unexpectedly\n"); __error_ret: if (package) uci_unload(context, package); if (context) uci_free_context(context); line_dect_deinit(); voice_line_deinit(); voice_connection_deinit(); voice_engine_shutdown(); ubus_disable_receive(); ubus_close(); return EXIT_FAILURE; }