1335 lines
45 KiB
C
1335 lines
45 KiB
C
/*
|
|
* mod_signalwire.c -- SignalWire module
|
|
*
|
|
* Copyright (c) 2018 SignalWire, Inc
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
#include <switch.h>
|
|
#include <switch_curl.h>
|
|
#include <switch_stun.h>
|
|
#include <signalwire-client-c/client.h>
|
|
|
|
#ifndef WIN32
|
|
#include <sys/utsname.h>
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
void sslLoadWindowsCACertificate();
|
|
void sslUnLoadWindowsCACertificate();
|
|
int sslContextFunction(void* curl, void* sslctx, void* userdata);
|
|
#endif
|
|
|
|
#define SW_KS_JSON_PRINT(_h, _j) do { \
|
|
char *_json = ks_json_print(_j); \
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ALERT, "--- %s ---\n%s\n---\n", _h, _json); \
|
|
ks_json_free(&_json); \
|
|
} while (0)
|
|
|
|
static int debug_level = 7;
|
|
|
|
static int signalwire_gateway_exists(void);
|
|
|
|
/* Prototypes */
|
|
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_signalwire_shutdown);
|
|
SWITCH_MODULE_LOAD_FUNCTION(mod_signalwire_load);
|
|
SWITCH_MODULE_RUNTIME_FUNCTION(mod_signalwire_runtime);
|
|
|
|
/* SWITCH_MODULE_DEFINITION(name, load, shutdown, runtime)
|
|
* Defines a switch_loadable_module_function_table_t and a static const char[] modname
|
|
*/
|
|
SWITCH_MODULE_DEFINITION(mod_signalwire, mod_signalwire_load, mod_signalwire_shutdown, mod_signalwire_runtime);
|
|
|
|
typedef enum {
|
|
SW_STATE_ADOPTION,
|
|
SW_STATE_OFFLINE,
|
|
SW_STATE_ONLINE,
|
|
SW_STATE_CONFIGURE,
|
|
SW_STATE_START_PROFILE,
|
|
SW_STATE_REGISTER,
|
|
SW_STATE_READY,
|
|
} sw_state_t;
|
|
|
|
static struct {
|
|
int ssl_verify;
|
|
ks_bool_t shutdown;
|
|
ks_bool_t restarting;
|
|
swclt_config_t *config;
|
|
char blade_bootstrap[1024];
|
|
char adoption_service[1024];
|
|
char stun_server[1024];
|
|
char adoption_token[64];
|
|
char override_context[64];
|
|
ks_size_t adoption_backoff;
|
|
ks_time_t adoption_next;
|
|
|
|
char adoption_data_local_ip[256];
|
|
char adoption_data_external_ip[256];
|
|
char adoption_data_uname[1024];
|
|
|
|
char relay_connector_id[256];
|
|
|
|
swclt_sess_t signalwire_session;
|
|
swclt_hmon_t signalwire_session_monitor;
|
|
sw_state_t state;
|
|
ks_bool_t profile_update;
|
|
ks_bool_t profile_reload;
|
|
ks_bool_t signalwire_reconnected;
|
|
switch_xml_t signalwire_profile;
|
|
char signalwire_profile_md5[SWITCH_MD5_DIGEST_STRING_SIZE];
|
|
|
|
ks_bool_t kslog_on;
|
|
|
|
switch_mutex_t *mutex; // general mutex for this mod
|
|
char gateway_ip[80];
|
|
char gateway_port[10];
|
|
} globals;
|
|
|
|
static void mod_signalwire_kslogger(const char *file, const char *func, int line, int level, const char *fmt, ...)
|
|
{
|
|
const char *fp;
|
|
va_list ap;
|
|
char buf[32768];
|
|
|
|
if (level > debug_level) return;
|
|
|
|
fp = switch_cut_path(file);
|
|
|
|
va_start(ap, fmt);
|
|
|
|
vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
|
|
va_end(ap);
|
|
|
|
switch_log_printf(SWITCH_CHANNEL_ID_LOG, fp, func, line, NULL, level, "%s\n", buf);
|
|
}
|
|
|
|
static switch_status_t switch_find_available_port(switch_port_t *port, const char *ip, int type)
|
|
{
|
|
switch_status_t ret = SWITCH_STATUS_SUCCESS;
|
|
switch_memory_pool_t *pool = NULL;
|
|
switch_sockaddr_t *addr = NULL;
|
|
switch_socket_t *sock = NULL;
|
|
switch_bool_t found = SWITCH_FALSE;
|
|
|
|
if ((ret = switch_core_new_memory_pool(&pool)) != SWITCH_STATUS_SUCCESS) {
|
|
goto done;
|
|
}
|
|
|
|
while (!found) {
|
|
if ((ret = switch_sockaddr_info_get(&addr, ip, SWITCH_UNSPEC, *port, 0, pool)) != SWITCH_STATUS_SUCCESS) {
|
|
goto done;
|
|
}
|
|
|
|
if ((ret = switch_socket_create(&sock, switch_sockaddr_get_family(addr), type, 0, pool)) != SWITCH_STATUS_SUCCESS) {
|
|
goto done;
|
|
}
|
|
|
|
if (!(found = (switch_socket_bind(sock, addr) == SWITCH_STATUS_SUCCESS))) {
|
|
*port = *port + 1;
|
|
}
|
|
|
|
switch_socket_close(sock);
|
|
}
|
|
|
|
done:
|
|
if (pool) switch_core_destroy_memory_pool(&pool);
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct response_data {
|
|
char *data;
|
|
size_t size;
|
|
};
|
|
|
|
static size_t response_data_handler(void *contents, size_t size, size_t nmemb, void *userp)
|
|
{
|
|
size_t received = size * nmemb;
|
|
struct response_data *rd = (struct response_data *)userp;
|
|
|
|
if (!rd->data) rd->data = ks_pool_alloc(NULL, received + 1);
|
|
else rd->data = ks_pool_resize(rd->data, rd->size + received + 1);
|
|
|
|
memcpy(rd->data + rd->size, contents, received);
|
|
rd->size += received;
|
|
rd->data[rd->size] = 0;
|
|
|
|
return received;
|
|
}
|
|
|
|
static void save_sip_config(const char *config)
|
|
{
|
|
char confpath[1024];
|
|
FILE *fp = NULL;
|
|
|
|
switch_snprintf(confpath, sizeof(confpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "signalwire-conf.dat");
|
|
fp = fopen(confpath, "w");
|
|
if (!fp) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to open %s to save SignalWire SIP configuration\n", confpath);
|
|
return;
|
|
}
|
|
|
|
fputs(config, fp);
|
|
fclose(fp);
|
|
}
|
|
|
|
static void load_sip_config(void)
|
|
{
|
|
char confpath[1024];
|
|
char data[32767] = { 0 };
|
|
FILE *fp = NULL;
|
|
|
|
switch_snprintf(confpath, sizeof(confpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "signalwire-conf.dat");
|
|
if (!(fp = fopen(confpath, "r"))) return;
|
|
|
|
if (!fread(data, 1, sizeof(data), fp)) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to read SignalWire SIP configuration from %s\n", confpath);
|
|
}
|
|
fclose(fp);
|
|
if (!zstr_buf(data)) {
|
|
switch_md5_string(globals.signalwire_profile_md5, (void *) data, strlen(data));
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "saved profile MD5 = \"%s\"\n", globals.signalwire_profile_md5);
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "saved profile = \"%s\"\n", (char *)data);
|
|
globals.signalwire_profile = switch_xml_parse_str_dynamic((char *)data, SWITCH_TRUE);
|
|
}
|
|
}
|
|
|
|
static ks_status_t load_credentials_from_json(ks_json_t *json)
|
|
{
|
|
ks_status_t status = KS_STATUS_SUCCESS;
|
|
ks_json_t *authentication = NULL;
|
|
const char *authentication_str = NULL;
|
|
const char *bootstrap = NULL;
|
|
const char *relay_connector_id = NULL;
|
|
|
|
if ((bootstrap = ks_json_get_object_cstr(json, "bootstrap")) == NULL) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Unable to connect to SignalWire: missing bootstrap URL\n");
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
if ((relay_connector_id = ks_json_get_object_cstr(json, "relay_connector_id")) == NULL) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Unable to connect to SignalWire: missing relay_connector_id\n");
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
if ((authentication = ks_json_get_object_item(json, "authentication")) == NULL) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Unable to connect to SignalWire: missing authentication\n");
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
// update the internal connection target, which is normally assigned in swclt_sess_create()
|
|
if (swclt_sess_target_set(globals.signalwire_session, bootstrap) != KS_STATUS_SUCCESS) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to connect to SignalWire at %s\n", bootstrap);
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
// update the relay_connector_id passed to profile configuration
|
|
strncpy(globals.relay_connector_id, relay_connector_id, sizeof(globals.relay_connector_id) - 1);
|
|
strncpy(globals.blade_bootstrap, bootstrap, sizeof(globals.blade_bootstrap) - 1);
|
|
|
|
// got adopted, update the client config authentication
|
|
authentication_str = ks_json_pprint_unformatted(NULL, authentication);
|
|
swclt_config_set_authentication(globals.config, authentication_str);
|
|
|
|
ks_pool_free(&authentication_str);
|
|
done:
|
|
|
|
return status;
|
|
}
|
|
|
|
static ks_status_t mod_signalwire_adoption_post(void)
|
|
{
|
|
ks_status_t status = KS_STATUS_SUCCESS;
|
|
switch_memory_pool_t *pool = NULL;
|
|
switch_CURL *curl = NULL;
|
|
switch_curl_slist_t *headers = NULL;
|
|
char url[1024];
|
|
char errbuf[CURL_ERROR_SIZE];
|
|
CURLcode res;
|
|
long rescode;
|
|
ks_json_t *json = ks_json_create_object();
|
|
struct response_data rd = { 0 };
|
|
char *jsonstr = NULL;
|
|
|
|
// Determine and cache adoption data values that are heavier to figure out
|
|
if (!globals.adoption_data_local_ip[0]) {
|
|
switch_find_local_ip(globals.adoption_data_local_ip, sizeof(globals.adoption_data_local_ip), NULL, AF_INET);
|
|
}
|
|
|
|
if (!globals.adoption_data_external_ip[0]) {
|
|
switch_port_t local_port = 6050;
|
|
char *error = NULL;
|
|
char *external_ip;
|
|
switch_port_t external_port;
|
|
|
|
if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "SignalWire adoption failed: could not allocate memory pool\n");
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
if (switch_find_available_port(&local_port, globals.adoption_data_local_ip, SOCK_STREAM) != SWITCH_STATUS_SUCCESS) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire adoption failed: could not get available local port\n");
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
external_ip = globals.adoption_data_local_ip;
|
|
external_port = local_port;
|
|
if (switch_stun_lookup(&external_ip, &external_port, globals.stun_server, SWITCH_STUN_DEFAULT_PORT, &error, pool) != SWITCH_STATUS_SUCCESS) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire adoption failed: stun [%s] lookup error: %s\n", globals.stun_server, error);
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
snprintf(globals.adoption_data_external_ip, sizeof(globals.adoption_data_external_ip), "%s", external_ip);
|
|
}
|
|
|
|
if (!globals.adoption_data_uname[0]) {
|
|
#ifndef WIN32
|
|
struct utsname buf;
|
|
if (uname(&buf)) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire adoption failed: could not get uname\n");
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
switch_snprintf(globals.adoption_data_uname,
|
|
sizeof(globals.adoption_data_uname),
|
|
"%s %s %s %s %s",
|
|
buf.sysname,
|
|
buf.nodename,
|
|
buf.release,
|
|
buf.version,
|
|
buf.machine);
|
|
#else
|
|
// @todo set globals.adoption_data_uname from GetVersion Win32API
|
|
#endif
|
|
}
|
|
|
|
|
|
ks_json_add_string_to_object(json, "client_uuid", globals.adoption_token);
|
|
ks_json_add_string_to_object(json, "hostname", switch_core_get_hostname());
|
|
ks_json_add_string_to_object(json, "ip", globals.adoption_data_local_ip);
|
|
ks_json_add_string_to_object(json, "ext_ip", globals.adoption_data_external_ip);
|
|
ks_json_add_string_to_object(json, "version", switch_version_full());
|
|
ks_json_add_string_to_object(json, "uname", globals.adoption_data_uname);
|
|
|
|
jsonstr = ks_json_print_unformatted(json);
|
|
ks_json_delete(&json);
|
|
|
|
switch_snprintf(url, sizeof(url), "%s/%s", globals.adoption_service, globals.adoption_token);
|
|
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG10, "Checking %s for SignalWire adoption of this FreeSWITCH\n", url);
|
|
|
|
curl = switch_curl_easy_init();
|
|
|
|
headers = switch_curl_slist_append(headers, "Accept: application/json");
|
|
headers = switch_curl_slist_append(headers, "Accept-Charset: utf-8");
|
|
headers = switch_curl_slist_append(headers, "Content-Type: application/json");
|
|
|
|
switch_curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5);
|
|
switch_curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5);
|
|
|
|
if (!strncasecmp(url, "https", 5)) {
|
|
switch_curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, globals.ssl_verify);
|
|
switch_curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, globals.ssl_verify);
|
|
}
|
|
|
|
switch_curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
switch_curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
switch_curl_easy_setopt(curl, CURLOPT_USERAGENT, "mod_signalwire/1");
|
|
switch_curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonstr);
|
|
switch_curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
|
switch_curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&rd);
|
|
switch_curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, response_data_handler);
|
|
#ifdef WIN32
|
|
curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, sslContextFunction);
|
|
#endif
|
|
|
|
if ((res = switch_curl_easy_perform(curl))) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Curl Result %d, Error: %s\n", res, errbuf);
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
switch_curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rescode);
|
|
|
|
if (rescode == 404) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE,
|
|
"Go to https://signalwire.com to set up your Connector now! Enter connection token %s\n", globals.adoption_token);
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
if (rescode != 200) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire adoption failed with HTTP code %ld, %s\n", rescode, rd.data);
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
json = ks_json_parse(rd.data);
|
|
if (!json) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Received bad SignalWire adoption response\n%s\n", rd.data);
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
if ((status = load_credentials_from_json(json)) != KS_STATUS_SUCCESS) {
|
|
goto done;
|
|
}
|
|
|
|
ks_json_delete(&json);
|
|
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire adoption of this FreeSWITCH completed\n");
|
|
|
|
// write out the data to save it for reloading in the future
|
|
{
|
|
char authpath[1024];
|
|
FILE *fp = NULL;
|
|
|
|
switch_snprintf(authpath, sizeof(authpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "adoption-auth.dat");
|
|
fp = fopen(authpath, "w");
|
|
if (!fp) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to open %s to save SignalWire creds\n", authpath);
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
fputs(rd.data, fp);
|
|
fclose(fp);
|
|
}
|
|
|
|
globals.state = SW_STATE_OFFLINE;
|
|
swclt_sess_connect(globals.signalwire_session);
|
|
|
|
done:
|
|
if (rd.data) ks_pool_free(&rd.data);
|
|
if (jsonstr) ks_json_free_ex((void **)&jsonstr);
|
|
if (json) ks_json_delete(&json);
|
|
if (curl) {
|
|
curl_easy_cleanup(curl);
|
|
if (headers) curl_slist_free_all(headers);
|
|
}
|
|
if (pool) switch_core_destroy_memory_pool(&pool);
|
|
return status;
|
|
}
|
|
|
|
#define SIGNALWIRE_SYNTAX "token | adoption | adopted | reload | update | debug <level> | kslog <on|off|logfile e.g. /tmp/ks.log>"
|
|
SWITCH_STANDARD_API(mod_signalwire_api_function)
|
|
{
|
|
int argc = 0;
|
|
char *argv[2] = { 0 };
|
|
char *buf = NULL;
|
|
|
|
|
|
if (!cmd || !(buf = strdup(cmd))) {
|
|
stream->write_function(stream, "-USAGE: signalwire %s\n", SIGNALWIRE_SYNTAX);
|
|
return SWITCH_STATUS_SUCCESS;
|
|
}
|
|
|
|
if ((argc = switch_separate_string(buf, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) {
|
|
if (!strcmp(argv[0], "token")) {
|
|
if (globals.adoption_token[0]) {
|
|
stream->write_function(stream,
|
|
" _____ _ ___ ___\n"
|
|
" / ___/(_)___ _____ ____ _/ / | / (_)_______\n"
|
|
" \\__ \\/ / __ `/ __ \\/ __ `/ /| | /| / / / ___/ _ \\\n"
|
|
" ___/ / / /_/ / / / / /_/ / / | |/ |/ / / / / __/\n"
|
|
" /____/_/\\__, /_/ /_/\\__,_/_/ |__/|__/_/_/ \\___/\n"
|
|
" /____/\n"
|
|
"\n /=====================================================================\\\n"
|
|
"| Connection Token: %s |\n"
|
|
" \\=====================================================================/\n"
|
|
" Go to https://signalwire.com to set up your Connector now!\n", globals.adoption_token);
|
|
} else {
|
|
stream->write_function(stream, "-ERR connection token not available\n");
|
|
}
|
|
goto done;
|
|
}
|
|
else if (!strcmp(argv[0], "adoption")) {
|
|
if (globals.state == SW_STATE_ADOPTION) {
|
|
globals.adoption_next = ks_time_now();
|
|
stream->write_function(stream, "+OK\n");
|
|
} else {
|
|
stream->write_function(stream, "-ERR adoption not currently pending\n");
|
|
}
|
|
goto done;
|
|
}
|
|
else if (!strcmp(argv[0], "adopted")) {
|
|
stream->write_function(stream, "+OK %s\n", globals.state == SW_STATE_ADOPTION ? "Not Adopted" : "Adopted");
|
|
goto done;
|
|
}
|
|
else if (!strcmp(argv[0], "debug")) {
|
|
if (argv[1]) {
|
|
debug_level = atoi(argv[1]);
|
|
}
|
|
|
|
stream->write_function(stream, "+OK debug %d\n", debug_level);
|
|
goto done;
|
|
} else if (!strcmp(argv[0], "kslog")) {
|
|
if (argv[1]) {
|
|
if (!strcmp(argv[1], "on")) {
|
|
ks_global_set_logger(mod_signalwire_kslogger);
|
|
} else if (!strcmp(argv[1], "off")) {
|
|
ks_global_set_logger(NULL);
|
|
}
|
|
}
|
|
|
|
stream->write_function(stream, "+OK %s\n", argv[1]);
|
|
goto done;
|
|
} else if (!strcmp(argv[0], "reload")) {
|
|
globals.profile_reload = KS_TRUE;
|
|
stream->write_function(stream, "+OK\n");
|
|
goto done;
|
|
} else if (!strcmp(argv[0], "update")) {
|
|
globals.profile_update = KS_TRUE;
|
|
stream->write_function(stream, "+OK\n");
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
stream->write_function(stream, "-USAGE: signalwire %s\n", SIGNALWIRE_SYNTAX);
|
|
|
|
done:
|
|
switch_safe_free(buf);
|
|
|
|
return SWITCH_STATUS_SUCCESS;
|
|
}
|
|
|
|
static void mod_signalwire_session_state_handler(swclt_sess_t sess, swclt_hstate_change_t *state_change_info, const char *cb_data)
|
|
{
|
|
SWCLT_HSTATE new_state = state_change_info->new_state;
|
|
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire Session State Change: %s\n", swclt_hstate_describe_change(state_change_info));
|
|
|
|
if (new_state == SWCLT_HSTATE_ONLINE) {
|
|
// Connected with NEW or RESTORED session
|
|
globals.signalwire_reconnected = KS_TRUE;
|
|
} else if (new_state == SWCLT_HSTATE_OFFLINE) {
|
|
// Disconnected
|
|
}
|
|
}
|
|
|
|
static void __on_provisioning_events(swclt_sess_t sess, blade_broadcast_rqu_t *rqu, void *cb_data)
|
|
{
|
|
if (!strcmp(rqu->event, "update")) {
|
|
globals.profile_update = KS_TRUE;
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile update requested\n");
|
|
}
|
|
}
|
|
|
|
static switch_xml_t xml_config_handler(const char *section, const char *tag_name, const char *key_name, const char *key_value, switch_event_t *params,
|
|
void *user_data)
|
|
{
|
|
char *profileName = NULL;
|
|
char *reconfigValue = NULL;
|
|
switch_xml_t signalwire_profile_dup = NULL;
|
|
|
|
if (!section || strcmp(section, "configuration")) return NULL;
|
|
if (!key_name || strcmp(key_name, "name")) return NULL;
|
|
if (!key_value || strcmp(key_value, "sofia.conf")) return NULL;
|
|
if (!params) return NULL;
|
|
profileName = switch_event_get_header(params, "profile");
|
|
if (!profileName || strcmp(profileName, "signalwire")) return NULL;
|
|
reconfigValue = switch_event_get_header(params, "reconfig");
|
|
if (!reconfigValue || strcmp(reconfigValue, "true")) return NULL;
|
|
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Received XML lookup for SignalWire SIP profile\n");
|
|
|
|
if (globals.signalwire_profile) {
|
|
signalwire_profile_dup = switch_xml_dup(globals.signalwire_profile);
|
|
}
|
|
return signalwire_profile_dup;
|
|
}
|
|
|
|
static switch_status_t mod_signalwire_load_or_generate_token(void)
|
|
{
|
|
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
|
char tokenpath[1024];
|
|
|
|
switch_snprintf(tokenpath, sizeof(tokenpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "adoption-token.dat");
|
|
if (switch_file_exists(tokenpath, NULL) != SWITCH_STATUS_SUCCESS) {
|
|
// generate first time uuid
|
|
ks_uuid_t uuid;
|
|
const char *token;
|
|
FILE *fp = fopen(tokenpath, "w");
|
|
if (!fp) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to open %s to save SignalWire connection token\n", tokenpath);
|
|
status = SWITCH_STATUS_TERM;
|
|
goto done;
|
|
}
|
|
|
|
ks_uuid(&uuid);
|
|
token = ks_uuid_str(NULL, &uuid);
|
|
|
|
fputs(token, fp);
|
|
fclose(fp);
|
|
|
|
strncpy(globals.adoption_token, token, sizeof(globals.adoption_token) - 1);
|
|
|
|
ks_pool_free(&token);
|
|
} else {
|
|
char token[64];
|
|
FILE *fp = fopen(tokenpath, "r");
|
|
if (!fp) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to open %s to read SignalWire connection token\n", tokenpath);
|
|
status = SWITCH_STATUS_TERM;
|
|
goto done;
|
|
}
|
|
if (!fgets(token, sizeof(token), fp)) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to read SignalWire connection token from %s\n", tokenpath);
|
|
fclose(fp);
|
|
status = SWITCH_STATUS_TERM;
|
|
goto done;
|
|
}
|
|
fclose(fp);
|
|
|
|
// trim newline markers in case they exist, only want the token
|
|
for (size_t len = strlen(token); len > 0 && (token[len - 1] == '\r' || token[len - 1] == '\n'); --len) {
|
|
token[len - 1] = '\0';
|
|
}
|
|
|
|
snprintf(globals.adoption_token, sizeof(globals.adoption_token), "%s", token);
|
|
}
|
|
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO,
|
|
"\n /=====================================================================\\\n"
|
|
"| Connection Token: %s |\n"
|
|
" \\=====================================================================/\n"
|
|
" Go to https://signalwire.com to set up your Connector now!\n", globals.adoption_token);
|
|
|
|
done:
|
|
return status;
|
|
}
|
|
|
|
static switch_status_t load_config()
|
|
{
|
|
char *cf = "signalwire.conf";
|
|
switch_xml_t cfg, xml;
|
|
const char *data;
|
|
|
|
globals.ssl_verify = 1;
|
|
switch_set_string(globals.blade_bootstrap, "edge.<space>.signalwire.com/api/relay/wss");
|
|
switch_set_string(globals.adoption_service, "https://adopt.signalwire.com/adoption");
|
|
switch_set_string(globals.stun_server, "stun.freeswitch.org");
|
|
|
|
if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG1, "open of %s failed\n", cf);
|
|
// don't need the config
|
|
} else {
|
|
switch_xml_t settings, param, tmp;
|
|
if ((settings = switch_xml_child(cfg, "settings"))) {
|
|
for (param = switch_xml_child(settings, "param"); param; param = param->next) {
|
|
char *var = (char *) switch_xml_attr_soft(param, "name");
|
|
char *val = (char *) switch_xml_attr_soft(param, "value");
|
|
|
|
if (!strcasecmp(var, "kslog") && !ks_zstr(val)) {
|
|
if (!strcmp(val, "off")) {
|
|
globals.kslog_on = KS_FALSE;
|
|
} else if (!strcmp(val, "on")) {
|
|
globals.kslog_on = KS_TRUE;
|
|
}
|
|
} else if (!strcasecmp(var, "blade-bootstrap") && !ks_zstr(val)) {
|
|
switch_set_string(globals.blade_bootstrap, val);
|
|
} else if (!strcasecmp(var, "adoption-service") && !ks_zstr(val)) {
|
|
switch_set_string(globals.adoption_service, val);
|
|
} else if (!strcasecmp(var, "stun-server") && !ks_zstr(val)) {
|
|
switch_set_string(globals.stun_server, val);
|
|
} else if (!strcasecmp(var, "ssl-verify")) {
|
|
globals.ssl_verify = switch_true(val) ? 1 : 0;
|
|
} else if (!strcasecmp(var, "override-context") && !ks_zstr(val)) {
|
|
switch_set_string(globals.override_context, val);
|
|
}
|
|
}
|
|
if ((tmp = switch_xml_child(settings, "authentication"))) {
|
|
const char *txt = switch_xml_txt(tmp);
|
|
if (!ks_zstr(txt)) {
|
|
swclt_config_set_authentication(globals.config, txt);
|
|
}
|
|
}
|
|
}
|
|
switch_xml_free(xml);
|
|
}
|
|
|
|
if ((data = getenv("SW_BLADE_BOOTSTRAP"))) {
|
|
switch_set_string(globals.blade_bootstrap, data);
|
|
}
|
|
|
|
if ((data = getenv("SW_ADOPTION_SERVICE"))) {
|
|
snprintf(globals.adoption_service, sizeof(globals.adoption_service), "%s", data);
|
|
}
|
|
|
|
swclt_config_load_from_env(globals.config);
|
|
|
|
return SWITCH_STATUS_SUCCESS;
|
|
}
|
|
|
|
static ks_status_t load_credentials(void)
|
|
{
|
|
ks_status_t status = KS_STATUS_SUCCESS;
|
|
char authpath[1024];
|
|
char data[2048];
|
|
FILE *fp = NULL;
|
|
ks_json_t *json = NULL;
|
|
|
|
switch_snprintf(authpath, sizeof(authpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "adoption-auth.dat");
|
|
if (!(fp = fopen(authpath, "r"))) goto done;
|
|
|
|
if (!fgets(data, sizeof(data), fp)) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to read SignalWire authentication data from %s\n", authpath);
|
|
fclose(fp);
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
fclose(fp);
|
|
|
|
json = ks_json_parse(data);
|
|
if (!json) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to parse SignalWire authentication data from %s\n", authpath);
|
|
status = KS_STATUS_FAIL;
|
|
goto done;
|
|
}
|
|
status = load_credentials_from_json(json);
|
|
ks_json_delete(&json);
|
|
|
|
done:
|
|
return status;
|
|
}
|
|
|
|
static void mod_signalwire_session_auth_failed_handler(swclt_sess_t sess)
|
|
{
|
|
char path[1024];
|
|
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire authentication failed\n");
|
|
|
|
switch_snprintf(path, sizeof(path), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "adoption-auth.dat");
|
|
unlink(path);
|
|
|
|
switch_snprintf(path, sizeof(path), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "signalwire-conf.dat");
|
|
unlink(path);
|
|
|
|
globals.restarting = KS_TRUE;
|
|
|
|
globals.adoption_backoff = 0;
|
|
globals.adoption_next = 0;
|
|
|
|
globals.state = SW_STATE_ADOPTION;
|
|
}
|
|
|
|
/* Dialplan INTERFACE */
|
|
SWITCH_STANDARD_DIALPLAN(dialplan_hunt)
|
|
{
|
|
switch_caller_extension_t *extension = NULL;
|
|
switch_channel_t *channel = switch_core_session_get_channel(session);
|
|
const char *network_ip = switch_channel_get_variable(channel, "sip_network_ip");
|
|
const char *network_port = switch_channel_get_variable(channel, "sip_network_port");
|
|
|
|
if (!caller_profile) {
|
|
if (!(caller_profile = switch_channel_get_caller_profile(channel))) {
|
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error obtaining caller profile!\n");
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (globals.override_context[0] != '\0') {
|
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Overriding dialplan context from %s to %s\n",caller_profile->context,globals.override_context);
|
|
caller_profile->context = globals.override_context;
|
|
}
|
|
|
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Processing %s <%s>->%s in context %s\n",
|
|
caller_profile->caller_id_name, caller_profile->caller_id_number, caller_profile->destination_number, caller_profile->context);
|
|
|
|
if ((extension = switch_caller_extension_new(session, "signalwire", caller_profile->destination_number)) == NULL) {
|
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Memory Error!\n");
|
|
goto done;
|
|
}
|
|
|
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "call from %s:%s\n", network_ip, network_port);
|
|
|
|
switch_mutex_lock(globals.mutex);
|
|
|
|
if (network_ip &&
|
|
!zstr_buf(globals.gateway_ip) && !strcmp(globals.gateway_ip, network_ip)) {
|
|
// good to go
|
|
char transfer_to[1024];
|
|
|
|
switch_snprintf(transfer_to, sizeof(transfer_to), "%s %s %s", caller_profile->destination_number, "XML", caller_profile->context);
|
|
switch_caller_extension_add_application(session, extension, "transfer", transfer_to);
|
|
} else {
|
|
switch_caller_extension_add_application(session, extension, "respond", "500");
|
|
}
|
|
|
|
switch_mutex_unlock(globals.mutex);
|
|
|
|
done:
|
|
return extension;
|
|
}
|
|
|
|
/**
|
|
* Module load or unload callback from core
|
|
* @param event the event
|
|
*/
|
|
static void on_module_load_unload(switch_event_t *event)
|
|
{
|
|
const char *type = switch_event_get_header(event, "type");
|
|
const char *name = switch_event_get_header(event, "name");
|
|
if (!zstr(type) && !zstr(name) && !strcmp(type, "endpoint") && !strcmp(name, "sofia")) {
|
|
globals.profile_reload = KS_TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sofia sofia::gateway_state change callback
|
|
* @param event the event
|
|
*/
|
|
static void on_sofia_gateway_state(switch_event_t *event)
|
|
{
|
|
const char *ip = switch_event_get_header(event, "Register-Network-IP");
|
|
const char *port = switch_event_get_header(event, "Register-Network-Port");
|
|
const char *state = switch_event_get_header(event, "State");
|
|
const char *gateway = switch_event_get_header(event, "Gateway");
|
|
|
|
if (!ip || !port || !state || !gateway) {
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(gateway, "signalwire")) {
|
|
switch_mutex_lock(globals.mutex);
|
|
|
|
if (!strcmp(state, "REGED")) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SignalWire SIP Gateway registered to %s:%s\n", ip, port);
|
|
switch_set_string(globals.gateway_ip, ip);
|
|
switch_set_string(globals.gateway_port, port);
|
|
} else if (!strcmp(state, "NOREG")) {
|
|
globals.gateway_ip[0] = '\0';
|
|
globals.gateway_port[0] = '\0';
|
|
}
|
|
|
|
switch_mutex_unlock(globals.mutex);
|
|
}
|
|
}
|
|
|
|
/* Macro expands to: switch_status_t mod_signalwire_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) */
|
|
SWITCH_MODULE_LOAD_FUNCTION(mod_signalwire_load)
|
|
{
|
|
switch_api_interface_t *api_interface = NULL;
|
|
switch_dialplan_interface_t *dialplan_interface;
|
|
const char *kslog_env = NULL;
|
|
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
|
|
|
memset(&globals, 0, sizeof(globals));
|
|
|
|
kslog_env = getenv("KSLOG");
|
|
if (kslog_env && kslog_env[0] && kslog_env[0] != '0') globals.kslog_on = KS_TRUE;
|
|
|
|
/* connect my internal structure to the blank pointer passed to me */
|
|
*module_interface = switch_loadable_module_create_module_interface(pool, modname);
|
|
|
|
ks_global_set_logger(mod_signalwire_kslogger);
|
|
|
|
SWITCH_ADD_API(api_interface, "signalwire", "SignalWire API", mod_signalwire_api_function, SIGNALWIRE_SYNTAX);
|
|
switch_console_set_complete("add signalwire debug");
|
|
switch_console_set_complete("add signalwire debug 1");
|
|
switch_console_set_complete("add signalwire debug 2");
|
|
switch_console_set_complete("add signalwire debug 3");
|
|
switch_console_set_complete("add signalwire debug 4");
|
|
switch_console_set_complete("add signalwire debug 5");
|
|
switch_console_set_complete("add signalwire debug 6");
|
|
switch_console_set_complete("add signalwire debug 7");
|
|
switch_console_set_complete("add signalwire kslog");
|
|
switch_console_set_complete("add signalwire kslog on");
|
|
switch_console_set_complete("add signalwire kslog off");
|
|
switch_console_set_complete("add signalwire token");
|
|
switch_console_set_complete("add signalwire adoption");
|
|
switch_console_set_complete("add signalwire adopted");
|
|
switch_console_set_complete("add signalwire update");
|
|
switch_console_set_complete("add signalwire reload");
|
|
|
|
switch_xml_bind_search_function(xml_config_handler, SWITCH_XML_SECTION_CONFIG, NULL);
|
|
|
|
ks_ssl_init_skip(KS_TRUE);
|
|
|
|
swclt_init(KS_LOG_LEVEL_DEBUG);
|
|
|
|
if (globals.kslog_on == KS_FALSE) {
|
|
ks_global_set_logger(NULL);
|
|
} else {
|
|
ks_global_set_logger(mod_signalwire_kslogger);
|
|
}
|
|
|
|
#ifdef WIN32
|
|
sslLoadWindowsCACertificate();
|
|
#endif
|
|
|
|
// Configuration
|
|
swclt_config_create(&globals.config);
|
|
load_config();
|
|
|
|
switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, pool);
|
|
|
|
switch_event_bind("mod_signalwire", SWITCH_EVENT_MODULE_LOAD, NULL, on_module_load_unload, NULL);
|
|
switch_event_bind("mod_signalwire", SWITCH_EVENT_MODULE_UNLOAD, NULL, on_module_load_unload, NULL);
|
|
switch_event_bind("mod_signalwire", SWITCH_EVENT_CUSTOM, "sofia::gateway_state", on_sofia_gateway_state, NULL);
|
|
|
|
SWITCH_ADD_DIALPLAN(dialplan_interface, "signalwire", dialplan_hunt);
|
|
|
|
// Load credentials if they exist from a prior adoption
|
|
load_credentials();
|
|
|
|
// SignalWire
|
|
swclt_sess_create(&globals.signalwire_session,
|
|
globals.blade_bootstrap,
|
|
globals.config);
|
|
swclt_sess_set_auth_failed_cb(globals.signalwire_session, mod_signalwire_session_auth_failed_handler);
|
|
|
|
if (!globals.signalwire_session) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "signalwire_session create error\n");
|
|
switch_goto_status(SWITCH_STATUS_TERM, err);
|
|
}
|
|
|
|
swclt_hmon_register(&globals.signalwire_session_monitor, globals.signalwire_session, mod_signalwire_session_state_handler, NULL);
|
|
|
|
// @todo register nodestore callbacks here if needed
|
|
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CONSOLE, "Welcome to\n"
|
|
" _____ _ ___ ___\n"
|
|
" / ___/(_)___ _____ ____ _/ / | / (_)_______\n"
|
|
" \\__ \\/ / __ `/ __ \\/ __ `/ /| | /| / / / ___/ _ \\\n"
|
|
" ___/ / / /_/ / / / / /_/ / / | |/ |/ / / / / __/\n"
|
|
" /____/_/\\__, /_/ /_/\\__,_/_/ |__/|__/_/_/ \\___/\n"
|
|
" /____/\n");
|
|
|
|
// storage_dir was missing in clean install
|
|
switch_dir_make_recursive(SWITCH_GLOBAL_dirs.storage_dir, SWITCH_DEFAULT_DIR_PERMS, pool);
|
|
|
|
if ((status = mod_signalwire_load_or_generate_token()) != SWITCH_STATUS_SUCCESS) {
|
|
goto err;
|
|
}
|
|
|
|
if (swclt_sess_has_authentication(globals.signalwire_session)) {
|
|
// Load cached profile if we already have one. We'll still connect to SignalWire and
|
|
// fetch a new profile in the background.
|
|
load_sip_config();
|
|
if (globals.signalwire_profile) {
|
|
globals.state = SW_STATE_START_PROFILE;
|
|
} else {
|
|
globals.state = SW_STATE_OFFLINE;
|
|
}
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CONSOLE, "Connecting to SignalWire\n");
|
|
swclt_sess_connect(globals.signalwire_session);
|
|
} else {
|
|
globals.state = SW_STATE_ADOPTION;
|
|
}
|
|
|
|
goto done;
|
|
|
|
err:
|
|
if (globals.signalwire_session) ks_handle_destroy(&globals.signalwire_session);
|
|
swclt_config_destroy(&globals.config);
|
|
ks_global_set_logger(NULL);
|
|
|
|
done:
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
Called when the system shuts down
|
|
Macro expands to: switch_status_t mod_signalwire_shutdown() */
|
|
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_signalwire_shutdown)
|
|
{
|
|
/* Cleanup dynamically allocated config settings */
|
|
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Disconnecting from SignalWire\n");
|
|
|
|
switch_event_unbind_callback(on_module_load_unload);
|
|
switch_event_unbind_callback(on_sofia_gateway_state);
|
|
|
|
// stop things that might try to use blade or kafka while they are shutting down
|
|
globals.shutdown = KS_TRUE;
|
|
|
|
swclt_sess_disconnect(globals.signalwire_session);
|
|
while (swclt_hstate_current_get(globals.signalwire_session) == SWCLT_HSTATE_ONLINE) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Sleeping for pending disconnect\n");
|
|
ks_sleep_ms(1000);
|
|
}
|
|
|
|
//signalwire_dialplan_shutdown();
|
|
// @todo signalwire profile unbinding and unloading
|
|
switch_xml_unbind_search_function_ptr(xml_config_handler);
|
|
|
|
// kill signalwire, so nothing more can come into the system
|
|
ks_handle_destroy(&globals.signalwire_session);
|
|
|
|
// cleanup config
|
|
swclt_config_destroy(&globals.config);
|
|
|
|
// shutdown libblade (but not libks?)
|
|
swclt_shutdown();
|
|
|
|
#ifdef WIN32
|
|
// free certificate pointers previously loaded
|
|
sslUnLoadWindowsCACertificate();
|
|
#endif
|
|
|
|
return SWITCH_STATUS_SUCCESS;
|
|
}
|
|
|
|
static void mod_signalwire_state_adoption(void)
|
|
{
|
|
// keep trying to check adoption token for authentication
|
|
if (ks_time_now() >= globals.adoption_next) {
|
|
// Use a very very simple backoff algorithm, every time we try, backoff another minute
|
|
// so that after first try we wait 1 minute, after next try we wait 2 minutes, at third
|
|
// try we are waiting 3 minutes, upto a max backoff of 15 minutes between adoption checks
|
|
if (globals.adoption_backoff < 15) globals.adoption_backoff++;
|
|
globals.adoption_next = ks_time_now() + (globals.adoption_backoff * 60 * KS_USEC_PER_SEC);
|
|
if (mod_signalwire_adoption_post() != KS_STATUS_SUCCESS) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Next SignalWire adoption check in %"SWITCH_SIZE_T_FMT" minutes\n", globals.adoption_backoff);
|
|
}
|
|
}
|
|
if (globals.signalwire_reconnected) {
|
|
// OK to continue as is
|
|
globals.signalwire_reconnected = KS_FALSE;
|
|
}
|
|
}
|
|
|
|
static void mod_signalwire_state_offline(void)
|
|
{
|
|
if (globals.signalwire_reconnected) {
|
|
globals.signalwire_reconnected = KS_FALSE;
|
|
globals.state = SW_STATE_ONLINE;
|
|
}
|
|
}
|
|
|
|
static void mod_signalwire_state_online(void)
|
|
{
|
|
globals.signalwire_reconnected = KS_FALSE;
|
|
if (!swclt_sess_provisioning_setup(globals.signalwire_session, __on_provisioning_events, NULL)) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Connected to SignalWire\n");
|
|
globals.state = SW_STATE_CONFIGURE;
|
|
} else {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Failed to connect to SignalWire\n");
|
|
ks_sleep_ms(4000);
|
|
globals.state = SW_STATE_OFFLINE;
|
|
globals.restarting = KS_TRUE;
|
|
}
|
|
}
|
|
|
|
static void mod_signalwire_state_configure(void)
|
|
{
|
|
switch_memory_pool_t *pool = NULL;
|
|
char local_ip[64];
|
|
switch_port_t local_port = 6050;
|
|
char local_endpoint[256];
|
|
char *external_ip;
|
|
switch_port_t external_port;
|
|
char external_endpoint[256];
|
|
char *error = NULL;
|
|
swclt_cmd_t cmd;
|
|
|
|
if (globals.signalwire_reconnected) {
|
|
globals.signalwire_reconnected = KS_FALSE;
|
|
globals.state = SW_STATE_ONLINE;
|
|
}
|
|
|
|
// already restarting/updating...
|
|
globals.profile_reload = KS_FALSE;
|
|
globals.profile_update = KS_FALSE;
|
|
|
|
if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "SignalWire configure failed: could not allocate memory pool\n");
|
|
goto done;
|
|
}
|
|
|
|
switch_find_local_ip(local_ip, sizeof(local_ip), NULL, AF_INET);
|
|
|
|
if (switch_find_available_port(&local_port, local_ip, SOCK_STREAM) != SWITCH_STATUS_SUCCESS) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire configure failed: could not get available local port\n");
|
|
ks_sleep_ms(4000);
|
|
goto done;
|
|
}
|
|
|
|
snprintf(local_endpoint, sizeof(local_endpoint), "%s:%u", local_ip, local_port);
|
|
|
|
external_ip = local_ip;
|
|
external_port = local_port;
|
|
|
|
if (switch_stun_lookup(&external_ip, &external_port, globals.stun_server, SWITCH_STUN_DEFAULT_PORT, &error, pool) != SWITCH_STATUS_SUCCESS) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire configure failed: stun [%s] lookup error: %s\n", globals.stun_server, error);
|
|
ks_sleep_ms(4000);
|
|
goto done;
|
|
}
|
|
|
|
snprintf(external_endpoint, sizeof(external_endpoint), "%s:%u", external_ip, external_port);
|
|
|
|
if (!swclt_sess_provisioning_configure(globals.signalwire_session, "freeswitch", local_endpoint, external_endpoint, globals.relay_connector_id, &cmd)) {
|
|
SWCLT_CMD_TYPE cmd_type;
|
|
swclt_cmd_type(cmd, &cmd_type);
|
|
if (cmd_type == SWCLT_CMD_TYPE_RESULT) {
|
|
const ks_json_t *result;
|
|
signalwire_provisioning_configure_response_t *configure_res;
|
|
|
|
swclt_cmd_result(cmd, &result);
|
|
result = ks_json_get_object_item(result, "result");
|
|
if (!SIGNALWIRE_PROVISIONING_CONFIGURE_RESPONSE_PARSE(ks_handle_pool(cmd), result, &configure_res)) {
|
|
const ks_json_t *configuration = configure_res->configuration;
|
|
const char *configuration_profile = ks_json_get_object_cstr(configuration, "profile");
|
|
if (globals.signalwire_profile) {
|
|
switch_xml_free(globals.signalwire_profile);
|
|
globals.signalwire_profile = NULL;
|
|
}
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "\"%s\"\n", configuration_profile);
|
|
globals.signalwire_profile = switch_xml_parse_str_dynamic((char *)configuration_profile, SWITCH_TRUE);
|
|
if (!globals.signalwire_profile) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to parse configuration profile\n");
|
|
} else {
|
|
char digest[SWITCH_MD5_DIGEST_STRING_SIZE] = { 0 };
|
|
switch_md5_string(digest, (void *) configuration_profile, strlen(configuration_profile));
|
|
save_sip_config(configuration_profile);
|
|
if (!signalwire_gateway_exists() || zstr_buf(globals.signalwire_profile_md5) || strcmp(globals.signalwire_profile_md5, digest)) {
|
|
// not registered or new profile - update md5 and load it
|
|
strcpy(globals.signalwire_profile_md5, digest);
|
|
globals.state = SW_STATE_START_PROFILE;
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "profile MD5 = \"%s\"\n", globals.signalwire_profile_md5);
|
|
} else {
|
|
// already registered
|
|
globals.state = SW_STATE_READY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ks_handle_destroy(&cmd);
|
|
if (globals.state == SW_STATE_CONFIGURE) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Failed to receive valid configuration from SignalWire\n");
|
|
ks_sleep_ms(4000);
|
|
} else {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Received configuration from SignalWire\n");
|
|
}
|
|
|
|
done:
|
|
if (pool) switch_core_destroy_memory_pool(&pool);
|
|
}
|
|
|
|
static int signalwire_gateway_exists(void)
|
|
{
|
|
int exists = 0;
|
|
switch_stream_handle_t stream = { 0 };
|
|
SWITCH_STANDARD_STREAM(stream);
|
|
if (switch_api_execute("sofia", "profile signalwire gwlist", NULL, &stream) == SWITCH_STATUS_SUCCESS && stream.data) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "gwlist = \"%s\"\n", (char *)stream.data);
|
|
exists = (strstr((char *)stream.data, "Invalid Profile") == NULL) &&
|
|
(strstr((char*)stream.data, "signalwire") != NULL);
|
|
}
|
|
switch_safe_free(stream.data);
|
|
return exists;
|
|
}
|
|
|
|
static int signalwire_profile_is_started(void)
|
|
{
|
|
int started = 0;
|
|
switch_stream_handle_t stream = { 0 };
|
|
SWITCH_STANDARD_STREAM(stream);
|
|
if (switch_api_execute("sofia", "status profile signalwire", NULL, &stream) == SWITCH_STATUS_SUCCESS && stream.data) {
|
|
started = (strstr((char *)stream.data, "Invalid Profile") == NULL) &&
|
|
(strstr((char *)stream.data, "signalwire") != NULL);
|
|
}
|
|
switch_safe_free(stream.data);
|
|
return started;
|
|
}
|
|
|
|
static int signalwire_profile_rescan(void)
|
|
{
|
|
int success = 0;
|
|
switch_stream_handle_t stream = { 0 };
|
|
SWITCH_STANDARD_STREAM(stream);
|
|
if (switch_api_execute("sofia", "profile signalwire rescan", NULL, &stream) == SWITCH_STATUS_SUCCESS) {
|
|
success = signalwire_profile_is_started();
|
|
}
|
|
switch_safe_free(stream.data);
|
|
return success;
|
|
}
|
|
|
|
static int signalwire_profile_start(void)
|
|
{
|
|
int success = 0;
|
|
switch_stream_handle_t stream = { 0 };
|
|
SWITCH_STANDARD_STREAM(stream);
|
|
if (switch_api_execute("sofia", "profile signalwire start", NULL, &stream) == SWITCH_STATUS_SUCCESS) {
|
|
success = signalwire_profile_is_started();
|
|
}
|
|
switch_safe_free(stream.data);
|
|
return success;
|
|
}
|
|
|
|
static void signalwire_profile_killgw(void)
|
|
{
|
|
switch_stream_handle_t stream = { 0 };
|
|
SWITCH_STANDARD_STREAM(stream);
|
|
switch_api_execute("sofia", "profile signalwire killgw signalwire", NULL, &stream);
|
|
switch_safe_free(stream.data);
|
|
}
|
|
|
|
static void mod_signalwire_state_start_profile(void)
|
|
{
|
|
if (globals.profile_update) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile update initiated\n");
|
|
globals.state = SW_STATE_CONFIGURE;
|
|
globals.profile_update = KS_FALSE;
|
|
return;
|
|
}
|
|
globals.profile_reload = KS_FALSE; // already here
|
|
|
|
// ignore SignalWire reconnections until register is attempted
|
|
|
|
if (switch_loadable_module_exists("mod_sofia") != SWITCH_STATUS_SUCCESS) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Waiting for mod_sofia to load\n");
|
|
} else if (signalwire_profile_is_started()) {
|
|
// kill gateway if already up and rescan the profile
|
|
if (signalwire_gateway_exists()) {
|
|
signalwire_profile_killgw();
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SignalWire SIP gateway killed\n");
|
|
}
|
|
if (signalwire_profile_rescan()) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SignalWire SIP profile rescanned\n");
|
|
globals.state = SW_STATE_REGISTER;
|
|
}
|
|
} else {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Starting SignalWire SIP profile\n");
|
|
signalwire_profile_start(); // assume success - it gets checked in next state
|
|
globals.state = SW_STATE_REGISTER;
|
|
}
|
|
}
|
|
|
|
static void mod_signalwire_state_register(void)
|
|
{
|
|
if (globals.profile_update) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile update initiated\n");
|
|
globals.state = SW_STATE_CONFIGURE;
|
|
globals.profile_update = KS_FALSE;
|
|
return;
|
|
} else if (globals.profile_reload) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile reload initiated\n");
|
|
globals.state = SW_STATE_START_PROFILE;
|
|
globals.profile_reload = KS_FALSE;
|
|
return;
|
|
}
|
|
// ignore SignalWire reconnections until register is attempted
|
|
|
|
if (switch_loadable_module_exists("mod_sofia") != SWITCH_STATUS_SUCCESS) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Waiting for mod_sofia to load\n");
|
|
globals.state = SW_STATE_START_PROFILE;
|
|
} else if (signalwire_gateway_exists() || signalwire_profile_rescan()) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SignalWire SIP gateway started\n");
|
|
globals.state = SW_STATE_READY;
|
|
} else {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Failed to start SignalWire SIP gateway\n");
|
|
globals.state = SW_STATE_CONFIGURE;
|
|
ks_sleep_ms(5000);
|
|
}
|
|
}
|
|
|
|
static void mod_signalwire_state_ready()
|
|
{
|
|
if (globals.profile_update) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Signalwire SIP profile update initiated\n");
|
|
globals.state = SW_STATE_CONFIGURE;
|
|
globals.profile_update = KS_FALSE;
|
|
} else if (globals.profile_reload) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile reload initiated\n");
|
|
globals.state = SW_STATE_START_PROFILE;
|
|
globals.profile_reload = KS_FALSE;
|
|
} else if (globals.signalwire_reconnected) {
|
|
globals.signalwire_reconnected = KS_FALSE;
|
|
globals.state = SW_STATE_ONLINE;
|
|
}
|
|
}
|
|
|
|
SWITCH_MODULE_RUNTIME_FUNCTION(mod_signalwire_runtime)
|
|
{
|
|
while (!globals.shutdown) {
|
|
if (globals.restarting) {
|
|
swclt_sess_disconnect(globals.signalwire_session);
|
|
while (swclt_hstate_current_get(globals.signalwire_session) == SWCLT_HSTATE_ONLINE) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Sleeping for pending disconnect\n");
|
|
ks_sleep_ms(1000);
|
|
}
|
|
|
|
// kill signalwire, so nothing more can come into the system
|
|
ks_handle_destroy(&globals.signalwire_session);
|
|
|
|
// Create a new session and start over
|
|
swclt_sess_create(&globals.signalwire_session,
|
|
globals.blade_bootstrap,
|
|
globals.config);
|
|
swclt_sess_set_auth_failed_cb(globals.signalwire_session, mod_signalwire_session_auth_failed_handler);
|
|
|
|
swclt_hmon_register(&globals.signalwire_session_monitor, globals.signalwire_session, mod_signalwire_session_state_handler, NULL);
|
|
|
|
globals.restarting = KS_FALSE;
|
|
continue;
|
|
}
|
|
|
|
switch(globals.state) {
|
|
case SW_STATE_ADOPTION: // waiting for adoption to occur
|
|
mod_signalwire_state_adoption();
|
|
break;
|
|
case SW_STATE_OFFLINE: // waiting for session to go online
|
|
mod_signalwire_state_offline();
|
|
break;
|
|
case SW_STATE_ONLINE: // provisioning service setup
|
|
mod_signalwire_state_online();
|
|
break;
|
|
case SW_STATE_CONFIGURE: // provisioning configuration
|
|
mod_signalwire_state_configure();
|
|
break;
|
|
case SW_STATE_START_PROFILE:
|
|
mod_signalwire_state_start_profile();
|
|
break;
|
|
case SW_STATE_REGISTER:
|
|
mod_signalwire_state_register();
|
|
break;
|
|
case SW_STATE_READY: // ready for runtime
|
|
mod_signalwire_state_ready();
|
|
break;
|
|
default: break;
|
|
}
|
|
ks_sleep_ms(1000);
|
|
}
|
|
|
|
return SWITCH_STATUS_TERM;
|
|
}
|
|
|
|
/* For Emacs:
|
|
* Local Variables:
|
|
* mode:c
|
|
* indent-tabs-mode:t
|
|
* tab-width:4
|
|
* c-basic-offset:4
|
|
* End:
|
|
* For VIM:
|
|
* vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet
|
|
*/
|