freeswitch/src/mod/applications/mod_signalwire/mod_signalwire.c

1327 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];
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;
}
}
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;
}
}
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
*/