iopsys-feed/ami_tool/src/ami_tool.c
2015-06-15 21:57:39 +02:00

1597 lines
42 KiB
C

/*
* ami_tool.c
*
* ** ami connection states **
* DISCONNECTED --[connect]--> CONNECTED
*
* CONNECTED --[login failed]--> DISCONNECTED
* CONNECTED --[event DISCONNECTED]--> DISCONNECTED
* CONNECTED --[event FULLYBOOTED]--> LOGGED_IN
*
* LOGGED_IN --[BRCMPortsShow resp]--> READY
* LOGGED_IN --[event DISCONNECTED]--> DISCONNECTED
*
* READY --[event DISCONNECTED]--> DISCONNECTED
* READY --[event BRCM MODULE UL]--> LOGGED_IN
*
* - Workaround for the fact that ports in software on DG201 are enumerated opposite to LED and physical ports has been removed,
* since we don't know what hardware we are running on.
*/
#include "ami_tool.h"
#include <signal.h>
#include <libubox/blobmsg.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>
#include "ami_connection.h"
#include "ucix.h"
static bool running = false;
//TODO: all uci things here
void ucix_reload();
static struct uci_context *uci_ctx = NULL;
//TODO: all ubus things here
static bool ubus_connected = false;
static struct ubus_context *ctx = NULL;
static struct blob_buf bb;
static struct blob_buf b_led;
static SIP_PEER sip_peers[SIP_ACCOUNT_UNKNOWN + 1];
struct codec {
char *key;
char *value;
unsigned int bitrate;
struct codec *next;
};
static struct codec *codec_create();
static void codec_delete(struct codec *codec);
static void codec_cb(const char * name, void *codec);
static int rtpstart_current = 0;
static int rtpend_current = 0;
static IP* ip_list_current = NULL;
static int ip_list_length_current = 0;
const char *portno = "5038"; //AMI port number
char hostname[] = "127.0.0.1"; //AMI hostname
char username[128] = "local"; //AMI username
char password[128] = "local"; //AMI password
static int ubus_send_brcm_event(const PORT_MAP *port, const char *key, const char *value);
static int ubus_send_sip_event(const SIP_PEER *peer, const char *key, const int value);
/* Asterisk states */
void set_state(AMI_STATE state, ami_connection* con);
static int voice_led_count = 1; //Number of voice leds on board
static int fxs_line_count = 0; //Number of FXS ports on board
static int dect_line_count = 0; //Number of DECT ports on board
static Led* led_config = NULL; //Array of led configs (one for each led)
/* Forward declaration of AMI functions and callbacks */
void ami_handle_event(ami_connection* con, ami_event event);
void handle_registry_event(ami_event event);
void handle_brcm_event(ami_connection* con, ami_event event);
void handle_varset_event(ami_event event);
void handle_registry_entry_event(ami_event event);
void on_login_response(ami_connection* con, char* buf);
void on_brcm_module_show_response(ami_connection* con, char* buf);
void on_brcm_ports_show_response(ami_connection* con, char* buf);
/* For debug. Log all resolved domain names for SIP peer */
void sip_peer_list_ip(SIP_PEER *peer)
{
int i;
for (i=0; i<peer->ip_list_length; i++) {
IP ip = peer->ip_list[i];
printf("%d %s\n", ip.family, ip.addr);
}
}
/* Add IP to list for SIP peer */
void sip_peer_add_ip(SIP_PEER *peer, char *addr, int family) {
int i;
if (peer->ip_list_length >= MAX_IP_LIST_LENGTH) {
fprintf(stderr, "Could not add IP %s to peer %s, ip list is full\n", addr, peer->account.name);
return;
}
for (i=0; i < peer->ip_list_length; i++) {
IP ip = peer->ip_list[i];
if (family == ip.family && strcmp(addr, ip.addr) == 0) {
return;
}
}
strcpy(peer->ip_list[peer->ip_list_length].addr, addr);
peer->ip_list[peer->ip_list_length].family = family;
peer->ip_list_length++;
}
/*
* Determine how many voice leds are present in current hardware config
*/
int uci_get_voice_led_count()
{
/* Initialize */
struct uci_context *hw_uci_ctx = ucix_init_path("/lib/db/config/", "hw");
if(!hw_uci_ctx) {
printf("Failed to get uci context for path /lib/db/config\n");
return 1; //Assume a single voice led
}
int led_count = ucix_get_option_int(hw_uci_ctx, "hw", "board", "VoiceLeds", 1);
printf("Found %i voice leds\n", led_count);
ucix_cleanup(hw_uci_ctx);
return led_count;
}
/*
* Get the called_lines configuration for a sip peer
*/
const char* uci_get_called_lines(const SIP_PEER* peer)
{
ucix_reload();
return ucix_get_option(uci_ctx, UCI_VOICE_PACKAGE, peer->account.name, "call_lines");
}
int uci_get_rtp_port_start()
{
ucix_reload();
return ucix_get_option_int(uci_ctx, UCI_VOICE_PACKAGE, "SIP", "rtpstart", RTP_RANGE_START_DEFAULT);
}
int uci_get_rtp_port_end()
{
ucix_reload();
return ucix_get_option_int(uci_ctx, UCI_VOICE_PACKAGE, "SIP", "rtpend", RTP_RANGE_END_DEFAULT);
}
int uci_get_sip_proxy(struct list_head *proxies)
{
ucix_reload();
return ucix_get_option_list(uci_ctx, UCI_VOICE_PACKAGE, "SIP", "sip_proxy", proxies);
}
const char* uci_get_peer_host(SIP_PEER *peer)
{
ucix_reload();
int enabled = ucix_get_option_int(uci_ctx, UCI_VOICE_PACKAGE, peer->account.name, "enabled", 0);
if (enabled == 0) {
return NULL;
}
return ucix_get_option(uci_ctx, UCI_VOICE_PACKAGE, peer->account.name, "host");
}
const char* uci_get_peer_domain(SIP_PEER *peer)
{
ucix_reload();
int enabled = ucix_get_option_int(uci_ctx, UCI_VOICE_PACKAGE, peer->account.name, "enabled", 0);
if (enabled == 0) {
return NULL;
}
return ucix_get_option(uci_ctx, UCI_VOICE_PACKAGE, peer->account.name, "domain");
}
int uci_get_peer_enabled(SIP_PEER* peer)
{
ucix_reload();
return ucix_get_option_int(uci_ctx, UCI_VOICE_PACKAGE, peer->account.name, "enabled", 0);
}
struct codec *uci_get_codecs()
{
/* Create space for first codec */
struct codec *c = codec_create();
ucix_reload();
ucix_for_each_section_type(uci_ctx, UCI_VOICE_PACKAGE, "supported_codec", codec_cb, c);
return c;
}
/* Resolv name into ip (A or AAA record), update IP list for peer */
static int resolv(SIP_PEER *peer, const char *domain)
{
struct addrinfo *result;
struct addrinfo *res;
int error;
/* Resolve the domain name into a list of addresses, don't specify any services */
error = getaddrinfo(domain, NULL, NULL, &result);
if (error != 0)
{
printf("error in getaddrinfo: %s\n", gai_strerror(error));
return 1;
}
/* Loop over all returned results and convert IP from network to textual form */
for (res = result; res != NULL; res = res->ai_next)
{
char ip_addr[NI_MAXHOST];
void *in_addr;
switch (res->ai_family) {
case AF_INET: {
struct sockaddr_in *s_addr = (struct sockaddr_in *) res->ai_addr;
in_addr = &s_addr->sin_addr;
break;
}
#ifdef USE_IPV6
case AF_INET6: {
struct sockaddr_in6 *s_addr6 = (struct sockaddr_in6 *) res->ai_addr;
in_addr = &s_addr6->sin6_addr;
break;
}
#endif
default:
continue;
}
inet_ntop(res->ai_family, in_addr, (void *)&ip_addr, NI_MAXHOST);
/* Add to list of IPs if not already there */
sip_peer_add_ip(peer, ip_addr, res->ai_family);
}
freeaddrinfo(result);
return 0;
}
/* Compare two struct IP */
int ip_cmp(const void *a, const void *b)
{
const IP *ia = (const IP *)a;
const IP *ib = (const IP *)b;
if (ia->family < ib->family) {
return -1;
}
if (ib->family < ia->family) {
return 1;
}
return strcmp(ia->addr, ib->addr);
}
/* Create a set of all resolved IPs for all peers */
IP* create_ip_set(int family, int *ip_list_length) {
SIP_PEER *peer;
IP *ip_list;
*ip_list_length = 0;
ip_list = (IP *) malloc(MAX_IP_LIST_LENGTH * sizeof(struct IP));
/* This is O(n^3) but the lists are small... */
peer = sip_peers;
while (peer->account.id != SIP_ACCOUNT_UNKNOWN) {
int i;
for (i=0; i<peer->ip_list_length; i++) {
int add = 1;
int j;
if (peer->ip_list[i].family != family) {
continue;
}
for (j=0; j<*ip_list_length; j++) {
if (ip_list[j].family == peer->ip_list[i].family &&
strcmp(ip_list[j].addr, peer->ip_list[i].addr) == 0) {
/* IP alread in set */
add = 0;
break;
}
}
if (add) {
/* IP not found in set */
strcpy(ip_list[*ip_list_length].addr, peer->ip_list[i].addr);
ip_list[*ip_list_length].family = peer->ip_list[i].family;
(*ip_list_length)++;
if (*ip_list_length == MAX_IP_LIST_LENGTH) {
/* ip_list is full */
return ip_list;
}
}
}
peer++;
}
return ip_list;
}
/* Compare two IP sets */
int compare_ip_set(IP* ip_list1, int ip_list_length1, IP* ip_list2, int ip_list_length2)
{
if (ip_list1 == NULL && ip_list2 == NULL) {
return 0;
}
if (ip_list1 == NULL) {
return -1;
}
if (ip_list2 == NULL) {
return 1;
}
if (ip_list_length1 < ip_list_length2) {
return -1;
}
if (ip_list_length2 < ip_list_length1) {
return 1;
}
int i;
for(i=0; i<ip_list_length1; i++) {
int rv = strcmp(ip_list1[i].addr, ip_list2[i].addr);
if (rv) {
return rv;
}
}
return 0;
}
void write_firewall(int family)
{
char *tables_file;
char *iptables_bin;
char buf[BUFLEN];
int ip_list_length;
IP* ip_list;
/* Is there a change in IP or RTP port range? */
ip_list = create_ip_set(family, &ip_list_length);
int rtpstart = uci_get_rtp_port_start();
int rtpend = uci_get_rtp_port_end();
if (compare_ip_set(ip_list_current, ip_list_length_current, ip_list, ip_list_length) == 0 &&
rtpstart_current == rtpstart &&
rtpend_current == rtpend) {
printf("No changes in IP or RTP port range\n");
free(ip_list);
return;
}
/* Clear old firewall settings, write timestamp */
time_t rawtime;
struct tm * timeinfo;
char timebuf[BUFLEN];
time(&rawtime);
timeinfo = (struct tm*) localtime(&rawtime);
strftime(timebuf, BUFLEN, "%Y-%m-%d %H:%M:%S", timeinfo);
tables_file = IPTABLES_FILE;
iptables_bin = IPTABLES_BIN;
#ifdef USE_IPV6
if (family == AF_INET6) {
iptables_bin = IP6TABLES_BIN;
tables_file = IP6TABLES_FILE;
}
#endif
snprintf((char *)&buf, BUFLEN, "%s \"# Created by ami_tool %s\" > %s",
ECHO_BIN,
timebuf,
tables_file);
printf("%s\n", buf);
system(buf);
/* Create an iptables rule for each IP in set */
int i;
for (i=0; i<ip_list_length; i++) {
snprintf((char *)&buf, BUFLEN, "%s \"%s -I %s -s %s -j ACCEPT\" >> %s",
ECHO_BIN,
iptables_bin,
IPTABLES_CHAIN,
ip_list[i].addr,
tables_file);
printf("%s\n", buf);
system(buf);
}
if (ip_list_current) {
free(ip_list_current);
}
ip_list_current = ip_list;
ip_list_length_current = ip_list_length;
/* Open up for RTP traffic */
snprintf((char *)&buf, BUFLEN, "%s \"%s -I %s -p udp --dport %d:%d -j ACCEPT\" >> %s",
ECHO_BIN,
iptables_bin,
IPTABLES_CHAIN,
rtpstart,
rtpend,
tables_file);
printf("%s\n", buf);
system(buf);
rtpstart_current = rtpstart;
rtpend_current = rtpend;
snprintf((char *)&buf, BUFLEN, "/etc/init.d/firewall reload");
printf("%s\n", buf);
system(buf);
}
/* Resolv host and add IPs to iptables */
int handle_iptables(SIP_PEER *peer, int doResolv)
{
/* Clear old IP list */
peer->ip_list_length = 0;
if (doResolv) {
/* Get domain to resolv */
const char* domain = uci_get_peer_domain(peer);
if (domain) {
resolv(peer, domain);
}
else {
printf("Failed to get sip domain\n");
return 1;
}
const char* host = uci_get_peer_host(peer);
if (host) {
resolv(peer, host);
}
/* Get sip proxies and resolv if configured */
struct ucilist proxies;
INIT_LIST_HEAD(&proxies.list);
if (!uci_get_sip_proxy(&proxies.list)) {
struct list_head *i;
struct list_head *tmp;
list_for_each_safe(i, tmp, &proxies.list)
{
struct ucilist *proxy = list_entry(i, struct ucilist, list);
resolv(peer, proxy->val);
free(proxy->val);
free(proxy);
}
}
}
/* Write new config to firewall.sip and reload firewall */
printf("\nIPv4:\n");
write_firewall(AF_INET);
#ifdef USE_IPV6
printf("\nIPv6:\n");
write_firewall(AF_INET6);
#endif
printf("\n");
return 0;
}
void log_sip_peers()
{
const SIP_PEER *peers = sip_peers;
while (peers->account.id != SIP_ACCOUNT_UNKNOWN) {
printf("sip_peer %d:\n", peers->account.id);
printf("\tname %s:\n", peers->account.name);
printf("\tsip_registry_request_sent: %d\n", peers->sip_registry_request_sent);
printf("\tsip_registry_registered: %d\n", peers->sip_registry_registered);
printf("\n");
peers++;
}
}
static int brcm_subchannel_active(const PORT_MAP *port) {
int subchannel_id;
for (subchannel_id=0; subchannel_id<2; subchannel_id++) {
if (strcmp(port->sub[subchannel_id].state, "ONHOOK") && strcmp(port->sub[subchannel_id].state, "CALLENDED")) {
return 1;
}
return 0;
}
return 0;
}
/**********************************
* LED management
**********************************/
void manage_leds() {
if (state != READY) {
manage_led(LN_VOICE1, LS_ERROR);
manage_led(LN_VOICE2, LS_ERROR);
return;
}
int i;
for (i = 0; i < voice_led_count; i++) {
Led* led = &led_config[i];
LED_STATE new_state = get_led_state(led);
if (new_state != led->state) {
manage_led(led->name, new_state);
}
led->state = new_state;
}
}
void manage_led(LED_NAME led, LED_STATE state) {
const LED_NAME_MAP *leds = led_names;
const LED_STATE_MAP *states = led_states;
LED_CURRENT_STATE_MAP *current_state = led_current_states;
//Check and set current state
while (current_state->name != LN_UNKNOWN) {
if (current_state->name == led) {
if (current_state->state == state) {
//No need to update led
return;
}
current_state->state = state;
break;
}
current_state++;
}
//Lookup led name
while (leds->name != led) {
leds++;
if (leds->name == LN_UNKNOWN) {
printf("Unknown led name\n");
return;
}
}
//Lookup led state
while (states->state != state) {
states++;
if (states->state == LS_UNKNOWN) {
printf("Unknown led state\n");
return;
}
}
//Lookup the id of led object
uint32_t id;
if (ubus_lookup_id(ctx, leds->str, &id)) {
fprintf(stderr, "Failed to look up %s object\n", leds->str);
return;
}
//Specify the state we want to set
blob_buf_init(&b_led, 0);
blobmsg_add_string(&b_led, "state", states->str);
//Invoke state change
printf("Setting LED %s state to %s\n", leds->str, states->str);
ubus_invoke(ctx, id, "set", b_led.head, NULL, 0, 1000);
}
/*
* Calculate a new state for a Led, based on the state of governing lines and accounts.
*/
LED_STATE get_led_state(Led* led)
{
//If one of the governing lines are active, led should be in notice mode
int i;
for(i = 0; i < led->num_ports; i++) {
if (brcm_subchannel_active(led->ports[i])) {
//printf("LED %d, PORT %s is active => LS_NOTICE\n", led->name, led->ports[i]->name);
return LS_NOTICE;
}
}
//Check state of governing accounts
LED_STATE tmp = LS_OFF;
for(i = 0; i < led->num_peers; i++) {
SIP_PEER* peer = led->peers[i];
if (!peer->sip_registry_registered) {
//printf("LED %d: PEER is not registered => LS_ERROR\n", led->name);
return LS_ERROR;
}
else {
//printf("LED %d: PEER is registered => LS_OK\n", led->name);
tmp = LS_OK;
}
}
return tmp;
}
void free_led_config()
{
int i;
for (i = 0; i < voice_led_count; i++) {
Led* led = &led_config[i];
free(led->peers);
free(led->ports);
}
free(led_config);
led_config = NULL;
}
/*
* configure_leds
*
* Configure which lines and peers that should determine the state of the
* voice led(s). The configuration is then used by manage_leds.
*
* led_config is rebuilt whenever a new line config is read from chan_brcm
*/
void configure_leds()
{
if (state != READY) {
return; //No need to configure leds yet
}
if (led_config) {
free_led_config();
}
led_config = calloc(voice_led_count, sizeof(Led));
int i;
if (voice_led_count == 1) {
/*
* Single LED - all ports govern status
*/
printf("Single LED configuration\n");
PORT_MAP** all_ports = calloc(dect_line_count + fxs_line_count, sizeof(PORT_MAP*));
for (i = 0; i < (dect_line_count + fxs_line_count); i++) {
all_ports[i] = &brcm_ports[i];
}
led_config[0].state = LS_UNKNOWN;
led_config[0].name = LN_VOICE1;
led_config[0].ports = all_ports;
led_config[0].num_ports = dect_line_count + fxs_line_count;
}
else if (voice_led_count > 1) {
/*
* Two LEDs, make best use of them!
* (Assume two leds, if there are more, we currently do not use them)
*/
if (dect_line_count > 0) {
/*
* LED1 = FXS, LED2 = DECT
* dects are lower numbered, fxs higher
*/
printf("Dual LED configuration, FXS and DECT\n");
PORT_MAP** dect_ports = calloc(dect_line_count, sizeof(PORT_MAP*));
for (i = 0; i < dect_line_count; i++) {
dect_ports[i] = &brcm_ports[i];
}
led_config[1].state = LS_UNKNOWN;
led_config[1].name = LN_VOICE2;
led_config[1].ports = dect_ports;
led_config[1].num_ports = dect_line_count;
PORT_MAP** fxs_ports = calloc(fxs_line_count, sizeof(PORT_MAP*));
for (i = 0; i < fxs_line_count; i++) {
fxs_ports[i] = &brcm_ports[dect_line_count + i];
}
led_config[0].state = LS_UNKNOWN;
led_config[0].name = LN_VOICE1;
led_config[0].ports = fxs_ports;
led_config[0].num_ports = fxs_line_count;
}
else {
/*
* LED1 = FXS1, LED2 = FXS2
*/
printf("Dual LED configuration, FXS1 and FXS2\n");
PORT_MAP** fxs1 = calloc(1, sizeof(PORT_MAP*));
fxs1[0] = &brcm_ports[0];
led_config[0].state = LS_UNKNOWN;
led_config[0].name = LN_VOICE1;
led_config[0].ports = fxs1;
led_config[0].num_ports = 1;
PORT_MAP** fxs2 = calloc(1, sizeof(PORT_MAP*));
fxs2[0] = &brcm_ports[1];
led_config[1].state = LS_UNKNOWN;
led_config[1].name = LN_VOICE2;
led_config[1].ports = fxs2;
led_config[1].num_ports = 1;
}
}
//Now add all accounts that have incoming calls to one of the governing ports
for (i = 0; i < voice_led_count; i++) {
Led* led = &led_config[i];
led->peers = calloc(MAX_SIP_PEERS, sizeof(SIP_PEER*));
led->num_peers = 0;
SIP_PEER *peers = sip_peers;
while (peers->account.id != SIP_ACCOUNT_UNKNOWN) {
int is_added = 0;
/* Skip if SIP account is not enabled */
if (!uci_get_peer_enabled(peers)) {
peers++;
continue;
}
const char* call_lines = uci_get_called_lines(peers);
if (call_lines) {
char buf[20];
char *delimiter = " ";
char *value;
int line_id = -1;
strncpy(buf, call_lines, 20);
value = strtok(buf, delimiter);
//Check all ports called by this account (numbers 0 to x)
while (value != NULL) {
line_id = atoi(value);
//Check if this port is among the governing ports for this led
int j;
for (j = 0; j < led->num_ports; j++) {
if (led->ports[j]->port == line_id) {
//printf("LED %d governed by PEER %s\n", led->name, peers->account.name);
//This is a matching peer
led->peers[led->num_peers] = peers;
led->num_peers++;
is_added = 1;
break;
}
}
if (is_added) {
break; //break out here if peer has been added
}
else {
value = strtok(NULL, delimiter);
}
}
}
peers++;
}
}
}
void init_brcm_ports() {
PORT_MAP *ports;
ports = brcm_ports;
while (ports->port != PORT_UNKNOWN) {
ports->off_hook = 0;
strcpy(ports->sub[0].state, "ONHOOK");
strcpy(ports->sub[1].state, "ONHOOK");
ports++;
}
}
void init_sip_peers() {
const SIP_ACCOUNT *accounts;
accounts = sip_accounts;
for (;;) {
sip_peers[accounts->id].account.id = accounts->id;
strcpy(sip_peers[accounts->id].account.name, accounts->name);
sip_peers[accounts->id].sip_registry_registered = 0;
sip_peers[accounts->id].sip_registry_request_sent = 0;
sip_peers[accounts->id].sip_registry_time = 0;
sip_peers[accounts->id].ip_list_length = 0;
/* Init sip show registry data */
strcpy(sip_peers[accounts->id].username, "Unknown");
strcpy(sip_peers[accounts->id].domain, "Unknown");
strcpy(sip_peers[accounts->id].state, "Unknown");
sip_peers[accounts->id].port = 0;
sip_peers[accounts->id].domain_port = 0;
sip_peers[accounts->id].refresh = 0;
sip_peers[accounts->id].registration_time = 0;
/* No need to (re)initialize ubus_object (created once at startup) */
if (accounts->id == SIP_ACCOUNT_UNKNOWN) {
break;
}
accounts++;
}
}
/*********************************
* AMI functions and callbacks
*********************************/
void ami_handle_event(ami_connection* con, ami_event event)
{
switch (event.type) {
case LOGIN:
if (state == CONNECTED) {
printf("Sending login to AMI, username %s\n", username);
ami_send_login(con, username, password, on_login_response);
}
else {
printf("Got unexpected LOGIN event\n");
ami_disconnect(con);
}
break;
case REGISTRY:
handle_registry_event(event);
printf("Sending sip show registry\n");
ami_send_sip_show_registry(con, 0);
break;
case REGISTRY_ENTRY:
handle_registry_entry_event(event);
break;
case REGISTRATIONS_COMPLETE:
printf("Sip show registry complete\n");
break;
case BRCM:
handle_brcm_event(con, event);
break;
case CHANNELRELOAD:
if (event.channel_reload_event->channel_type == CHANNELRELOAD_SIP_EVENT) {
printf("SIP channel was reloaded\n");
init_sip_peers(); //SIP has reloaded, initialize sip peer structs
configure_leds(); //Reconfigure leds, as SIP channel reload may indicate a change to config
}
else {
printf("Unknown channel was reloaded\n");
}
break;
case FULLYBOOTED:
printf("Asterisk is fully booted\n");
set_state(LOGGED_IN, con);
break;
case VARSET:
handle_varset_event(event);
break;
case DISCONNECT:
printf("AMI disconnected\n");
set_state(DISCONNECTED, con);
break;
case UNKNOWN_EVENT:
break; //An event that ami_connection could not parse
default:
break; //An event that we dont handle
}
manage_leds();
}
void handle_registry_event(ami_event event)
{
const SIP_ACCOUNT* accounts = sip_accounts;
SIP_PEER *peer = &sip_peers[PORT_UNKNOWN];
char* account_name = event.registry_event->account_name;
//Lookup peer by account name
while (accounts->id != SIP_ACCOUNT_UNKNOWN) {
if (!strcmp(accounts->name, account_name)) {
peer = &sip_peers[accounts->id];
break;
}
accounts++;
}
if (peer->account.id == SIP_ACCOUNT_UNKNOWN) {
printf("Registry event for unknown account: %s\n", account_name);
return;
}
switch (event.registry_event->status) {
case REGISTRY_REGISTERED_EVENT:
printf("sip registry registered\n");
peer->sip_registry_registered = 1;
peer->sip_registry_request_sent = 0;
time(&(peer->sip_registry_time)); //Last registration time
ubus_send_sip_event(peer, "registered", peer->sip_registry_registered);
ubus_send_sip_event(peer, "registry_request_sent", peer->sip_registry_request_sent);
handle_iptables(peer, 1);
break;
case REGISTRY_UNREGISTERED_EVENT:
printf("sip registry unregistered\n");
peer->sip_registry_registered = 0;
peer->sip_registry_request_sent = 0;
ubus_send_sip_event(peer, "registered", peer->sip_registry_registered);
ubus_send_sip_event(peer, "registry_request_sent", peer->sip_registry_request_sent);
handle_iptables(peer, 0);
break;
case REGISTRY_REQUEST_SENT_EVENT:
if (peer->sip_registry_request_sent == 1) {
//This means we sent a "REGISTER" without receiving "Registered" event
peer->sip_registry_registered = 0;
handle_iptables(peer, 0);
}
peer->sip_registry_request_sent = 1;
ubus_send_sip_event(peer, "registered", peer->sip_registry_registered);
ubus_send_sip_event(peer, "registry_request_sent", peer->sip_registry_request_sent);
break;
default:
break;
}
}
void handle_registry_entry_event(ami_event event)
{
printf("Info: Got registry entry event for SIP account %s\n", event.registry_entry_event->host);
const SIP_ACCOUNT* accounts = sip_accounts;
SIP_PEER *peer = &sip_peers[PORT_UNKNOWN];
char* account_name = event.registry_entry_event->host;
//Lookup peer by account name
while (accounts->id != SIP_ACCOUNT_UNKNOWN) {
if (!strcmp(accounts->name, account_name)) {
peer = &sip_peers[accounts->id];
break;
}
accounts++;
}
if (peer->account.id == SIP_ACCOUNT_UNKNOWN) {
printf("RegistryEntry event for unknown account: %s\n", account_name);
return;
}
//Update our sip peer with event information
strncpy(peer->username, event.registry_entry_event->username, MAX_SIP_PEER_USERNAME);
peer->username[MAX_SIP_PEER_USERNAME - 1]= '\0';
strncpy(peer->domain, event.registry_entry_event->domain, MAX_SIP_PEER_DOMAIN);
peer->domain[MAX_SIP_PEER_USERNAME - 1]= '\0';
strncpy(peer->state, event.registry_entry_event->state, MAX_SIP_PEER_STATE);
peer->state[MAX_SIP_PEER_USERNAME - 1]= '\0';
peer->port = event.registry_entry_event->port;
peer->domain_port = event.registry_entry_event->domain_port;
peer->refresh = event.registry_entry_event->refresh;
peer->registration_time = event.registry_entry_event->registration_time;
}
void handle_brcm_event(ami_connection* con, ami_event event)
{
int line_id;
int subchannel_id;
switch (event.brcm_event->type) {
case BRCM_STATUS_EVENT:
printf("Got BRCM_STATUS_EVENT for %d, offhook = %d\n", event.brcm_event->status.line_id, event.brcm_event->status.off_hook);
line_id = event.brcm_event->status.line_id;
if (line_id >= 0 && line_id < PORT_ALL) {
brcm_ports[line_id].off_hook = event.brcm_event->status.off_hook;
}
else {
printf("Got BRCM Status event for unknown line %d\n", line_id);
}
break;
case BRCM_STATE_EVENT:
printf("Got BRCM_STATE_EVENT for %d.%d: %s\n", event.brcm_event->state.line_id, event.brcm_event->state.subchannel_id, event.brcm_event->state.state);
line_id = event.brcm_event->state.line_id;
subchannel_id = event.brcm_event->state.subchannel_id;
if (line_id >= 0 && line_id < PORT_ALL) {
strcpy(brcm_ports[line_id].sub[subchannel_id].state, event.brcm_event->state.state);
char* subchannel = subchannel_id ? "0" : "1";
ubus_send_brcm_event(&brcm_ports[line_id], subchannel, brcm_ports[line_id].sub[subchannel_id].state);
}
else {
printf("Got BRCM Status event for unknown line %d\n", line_id);
}
break;
case BRCM_MODULE_EVENT:
if (event.brcm_event->module_loaded) {
printf("BRCM module loaded\n");
ami_send_brcm_ports_show(con, on_brcm_ports_show_response);
}
else {
printf("BRCM module unloaded\n");
set_state(LOGGED_IN, con);
}
break;
default:
break;
}
}
void handle_varset_event(ami_event event)
{
if (event.varset_event->channel && event.varset_event->variable && event.varset_event->value) {
/* Event contained all vital parts, send ubus event */
blob_buf_init(&bb, 0);
blobmsg_add_string(&bb, event.varset_event->variable, event.varset_event->value);
ubus_send_event(ctx, event.varset_event->channel, bb.head);
}
}
/*
* Callback to handle login result
*/
void on_login_response(ami_connection* con, char* buf)
{
if (strstr(buf, "Success")) {
printf("Log in successful\n");
}
else {
printf("Log in failed\n");
ami_disconnect(con);
}
}
/*
* Callback to handle response to brcm module show action.
*/
void on_brcm_module_show_response(ami_connection* con, char* buf)
{
if (strstr(buf, "1 modules loaded")) {
printf("BRCM channel driver is loaded\n");
ami_send_brcm_ports_show(con, on_brcm_ports_show_response);
}
else {
printf("BRCM channel driver is not loaded\n");
}
}
/*
* Callback to handle response to brcm ports show
*/
void on_brcm_ports_show_response(ami_connection* con, char* buf)
{
int result = 1;
char* fxs_needle = strstr(buf, "FXS");
if (fxs_needle == NULL) {
printf("Could not find number of FXS ports\n");
result = 0;
}
else {
fxs_line_count = strtol(fxs_needle + 4, NULL, 10);
printf("Found %d FXS ports\n", fxs_line_count);
}
char* dect_needle = strstr(buf, "DECT");
if (dect_needle == NULL) {
printf("Could not find number of DECT ports\n");
result = 0;
}
else {
dect_line_count = strtol(dect_needle + 5, NULL, 10);
printf("Found %d DECT ports\n", dect_line_count);
}
if (result) {
set_state(READY, con);
}
}
/******************************
* UBUS functions and structs
******************************/
static void system_fd_set_cloexec(int fd)
{
#ifdef FD_CLOEXEC
fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
#endif
}
enum {
UBUS_ARG0,
__UBUS_ARGMAX,
};
/*
* ubus string argument policy
*/
static const struct blobmsg_policy ubus_string_argument[__UBUS_ARGMAX] = {
[UBUS_ARG0] = { .name = "info", .type = BLOBMSG_TYPE_STRING },
};
/*
* Sends asterisk.sip events
*/
static int ubus_send_sip_event(const SIP_PEER *peer, const char *key, const int value)
{
char id[BUFLEN];
char sValue[BUFLEN];
snprintf(id, BUFLEN, "asterisk.sip.%d", peer->account.id);
snprintf(sValue, BUFLEN, "%d", value);
blob_buf_init(&bb, 0);
blobmsg_add_string(&bb, key, sValue);
return ubus_send_event(ctx, id, bb.head);
}
/*
* Sends asterisk.brcm events
*/
static int ubus_send_brcm_event(const PORT_MAP *port, const char *key, const char *value)
{
char id[BUFLEN];
snprintf(id, BUFLEN, "asterisk.brcm.%d", port->port);
blob_buf_init(&bb, 0);
blobmsg_add_string(&bb, key, value);
return ubus_send_event(ctx, id, bb.head);
}
/*
* Collects and returns information on a single brcm line to a ubus message buffer
*/
static int ubus_get_brcm_line(struct blob_buf *b, int line)
{
if (line >= PORT_UNKNOWN || line >= PORT_ALL || line < 0) {
return 1;
}
const PORT_MAP *p = &(brcm_ports[line]);
blobmsg_add_string(b, "sub_0_state", p->sub[0].state);
blobmsg_add_string(b, "sub_1_state", p->sub[1].state);
return 0;
}
/*
* Collects and returns information on a single sip account to a ubus message buffer
*/
static int ubus_get_sip_account(struct blob_buf *b, int account_id)
{
if (account_id >= SIP_ACCOUNT_UNKNOWN || account_id < 0) {
return 1;
}
blobmsg_add_u8(b, "registered", sip_peers[account_id].sip_registry_registered);
blobmsg_add_u8(b, "registry_request_sent", sip_peers[account_id].sip_registry_request_sent);
//IP address(es) of the sip registrar
int i;
for (i = 0; i<sip_peers[account_id].ip_list_length; i++) {
blobmsg_add_string(b, "ip", sip_peers[account_id].ip_list[i].addr);
}
blobmsg_add_u32(b, "port", sip_peers[account_id].port);
blobmsg_add_string(b, "username", sip_peers[account_id].username);
blobmsg_add_string(b, "domain", sip_peers[account_id].domain);
blobmsg_add_u32(b, "domain_port", sip_peers[account_id].domain_port);
blobmsg_add_u32(b, "refresh_interval", sip_peers[account_id].refresh);
blobmsg_add_string(b, "state", sip_peers[account_id].state);
//Format registration time
if (sip_peers[account_id].registration_time > 0) {
struct tm* timeinfo;
char buf[80];
timeinfo = localtime(&(sip_peers[account_id].registration_time));
strftime(buf, 80, "%Y-%m-%d %H:%M:%S", timeinfo);
blobmsg_add_string(b, "registration_time", buf);
}
else {
blobmsg_add_string(b, "registration_time", "-");
}
//This is the time of last successful registration for this account,
//regardless of the current registration state (differs from registration_time)
if (sip_peers[account_id].sip_registry_time > 0) {
struct tm* timeinfo;
char buf[80];
timeinfo = localtime(&(sip_peers[account_id].sip_registry_time));
strftime(buf, 80, "%Y-%m-%d %H:%M:%S", timeinfo);
blobmsg_add_string(b, "last_successful_registration", buf);
}
else {
blobmsg_add_string(b, "last_successful_registration", "-");
}
return 0;
}
/*
* ubus callback that replies to "asterisk.brcm.X status"
*/
static int ubus_asterisk_brcm_cb (
struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_attr *tb[__UBUS_ARGMAX];
blobmsg_parse(ubus_string_argument, __UBUS_ARGMAX, tb, blob_data(msg), blob_len(msg));
blob_buf_init(&bb, 0);
PORT_MAP *port;
port = brcm_ports;
while (port->port != PORT_UNKNOWN) {
if (port->ubus_object == obj) {
ubus_get_brcm_line(&bb, port->port); //Add port status to message
break;
}
port++;
}
ubus_send_reply(ctx, req, bb.head);
return 0;
}
/*
* ubus callback that replies to "asterisk.sip.X status"
*/
static int ubus_asterisk_sip_cb (
struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_attr *tb[__UBUS_ARGMAX];
blobmsg_parse(ubus_string_argument, __UBUS_ARGMAX, tb, blob_data(msg), blob_len(msg));
blob_buf_init(&bb, 0);
SIP_PEER *peer = sip_peers;
while (peer->account.id != SIP_ACCOUNT_UNKNOWN) {
if (peer->ubus_object == obj) {
ubus_get_sip_account(&bb, peer->account.id); //Add SIP account status to message
break;
}
peer++;
}
ubus_send_reply(ctx, req, bb.head);
return 0;
}
/*
* ubus callback that replies to "asterisk.codecs status"
*/
static int ubus_codecs_cb (
struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_attr *tb[__UBUS_ARGMAX];
struct codec *codec;
struct codec *codec_tmp;
blobmsg_parse(ubus_string_argument, __UBUS_ARGMAX, tb, blob_data(msg), blob_len(msg));
blob_buf_init(&bb, 0);
codec = uci_get_codecs();
while (codec) {
/* Node with next == NULL serves as end marker */
if (codec->next == NULL) {
codec_delete(codec);
break;
}
void *table = blobmsg_open_table(&bb, codec->key);
blobmsg_add_string(&bb, "name", codec->value);
if (codec->bitrate) {
blobmsg_add_u32(&bb, "bitrate", codec->bitrate);
}
blobmsg_close_table(&bb, table);
codec_tmp = codec;
codec = codec->next;
codec_delete(codec_tmp);
}
ubus_send_reply(ctx, req, bb.head);
return 0;
}
static struct codec *codec_create()
{
struct codec *c = malloc(sizeof(struct codec));
bzero(c, sizeof(struct codec));
return c;
}
static void codec_delete(struct codec *c)
{
if (c->key) {
free(c->key);
}
if (c->value) {
free(c->value);
}
free(c);
}
/*
* uci callback, called for each "supported_codec" found
*/
static void codec_cb(const char * name, void *priv)
{
struct codec *c = (struct codec *) priv;
/* Store key/value to last codec in list */
while (c->next) {
c = c->next;
}
c->key = strdup(name);
c->value = strdup(ucix_get_option(uci_ctx, UCI_VOICE_PACKAGE, name, "name"));
const char *bitrate = ucix_get_option(uci_ctx, UCI_VOICE_PACKAGE, name, "bitrate");
c->bitrate = bitrate ? atoi(bitrate) : 0;
/* Create space for next codec */
c->next = codec_create();
}
/*
* ubus callback that replies to "asterisk status".
* Recursively reports status for all lines/accounts
*/
static int ubus_asterisk_cb (
struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_attr *tb[__UBUS_ARGMAX];
blobmsg_parse(ubus_string_argument, __UBUS_ARGMAX, tb, blob_data(msg), blob_len(msg));
blob_buf_init(&bb, 0);
SIP_PEER *peer = sip_peers;
void *sip_table = blobmsg_open_table(&bb, "sip");
while (peer->account.id != SIP_ACCOUNT_UNKNOWN) {
void *sip_account_table = blobmsg_open_table(&bb, peer->account.name);
ubus_get_sip_account(&bb, peer->account.id); //Add SIP account status to message
blobmsg_close_table(&bb, sip_account_table);
peer++;
}
blobmsg_close_table(&bb, sip_table);
PORT_MAP *port = brcm_ports;
void *brcm_table = blobmsg_open_table(&bb, "brcm");
while (port->port != PORT_UNKNOWN && port->port != PORT_ALL) {
void *line_table = blobmsg_open_table(&bb, port->name);
ubus_get_brcm_line(&bb, port->port); //Add port status to message
blobmsg_close_table(&bb, line_table);
port++;
}
blobmsg_close_table(&bb, brcm_table);
ubus_send_reply(ctx, req, bb.head);
return 0;
}
static struct ubus_method sip_object_methods[] = {
{ .name = "status", .handler = ubus_asterisk_sip_cb },
};
static struct ubus_object_type sip_object_type =
UBUS_OBJECT_TYPE("sip_object", sip_object_methods);
static struct ubus_object ubus_sip_objects[] = {
{ .name = "asterisk.sip.0", .type = &sip_object_type, .methods = sip_object_methods, .n_methods = ARRAY_SIZE(sip_object_methods) },
{ .name = "asterisk.sip.1", .type = &sip_object_type, .methods = sip_object_methods, .n_methods = ARRAY_SIZE(sip_object_methods) },
{ .name = "asterisk.sip.2", .type = &sip_object_type, .methods = sip_object_methods, .n_methods = ARRAY_SIZE(sip_object_methods) },
{ .name = "asterisk.sip.3", .type = &sip_object_type, .methods = sip_object_methods, .n_methods = ARRAY_SIZE(sip_object_methods) },
{ .name = "asterisk.sip.4", .type = &sip_object_type, .methods = sip_object_methods, .n_methods = ARRAY_SIZE(sip_object_methods) },
{ .name = "asterisk.sip.5", .type = &sip_object_type, .methods = sip_object_methods, .n_methods = ARRAY_SIZE(sip_object_methods) },
{ .name = "asterisk.sip.6", .type = &sip_object_type, .methods = sip_object_methods, .n_methods = ARRAY_SIZE(sip_object_methods) },
{ .name = "asterisk.sip.7", .type = &sip_object_type, .methods = sip_object_methods, .n_methods = ARRAY_SIZE(sip_object_methods) },
};
static struct ubus_method brcm_object_methods[] = {
{ .name = "status", .handler = ubus_asterisk_brcm_cb },
};
static struct ubus_object_type brcm_object_type =
UBUS_OBJECT_TYPE("brcm_object", brcm_object_methods);
static struct ubus_object ubus_brcm_objects[] = {
{ .name = "asterisk.brcm.0", .type = &brcm_object_type, .methods = brcm_object_methods, .n_methods = ARRAY_SIZE(brcm_object_methods) },
{ .name = "asterisk.brcm.1", .type = &brcm_object_type, .methods = brcm_object_methods, .n_methods = ARRAY_SIZE(brcm_object_methods) },
{ .name = "asterisk.brcm.2", .type = &brcm_object_type, .methods = brcm_object_methods, .n_methods = ARRAY_SIZE(brcm_object_methods) },
{ .name = "asterisk.brcm.3", .type = &brcm_object_type, .methods = brcm_object_methods, .n_methods = ARRAY_SIZE(brcm_object_methods) },
{ .name = "asterisk.brcm.4", .type = &brcm_object_type, .methods = brcm_object_methods, .n_methods = ARRAY_SIZE(brcm_object_methods) },
{ .name = "asterisk.brcm.5", .type = &brcm_object_type, .methods = brcm_object_methods, .n_methods = ARRAY_SIZE(brcm_object_methods) },
};
static struct ubus_method asterisk_object_methods[] = {
{ .name = "status", .handler = ubus_asterisk_cb },
{ .name = "codecs", .handler = ubus_codecs_cb },
};
static struct ubus_object_type asterisk_object_type =
UBUS_OBJECT_TYPE("asterisk_object", asterisk_object_methods);
static struct ubus_object ubus_asterisk_object = {
.name = "asterisk",
.type = &asterisk_object_type,
.methods = asterisk_object_methods,
.n_methods = ARRAY_SIZE(asterisk_object_methods) };
static int ubus_add_objects(struct ubus_context *ctx)
{
int ret = 0;
SIP_PEER *peer;
peer = sip_peers;
while (peer->account.id != SIP_ACCOUNT_UNKNOWN) {
peer->ubus_object = &ubus_sip_objects[peer->account.id];
ret &= ubus_add_object(ctx, peer->ubus_object);
peer++;
}
PORT_MAP *port;
port = brcm_ports;
while (port->port != PORT_ALL) {
port->ubus_object = &ubus_brcm_objects[port->port];
ret &= ubus_add_object(ctx, port->ubus_object);
port++;
}
ret &= ubus_add_object(ctx, &ubus_asterisk_object);
return ret;
}
static void ubus_connection_lost_cb(struct ubus_context *ctx)
{
fprintf(stderr, "UBUS connection lost\n");
ubus_connected = false;
}
static void sighandler(int signo)
{
if (signo == SIGINT) {
running = false;
}
}
int main(int argc, char **argv)
{
signal(SIGINT, sighandler);
state = DISCONNECTED;
fd_set fset; /* FD set */
struct timeval timeout; /* Timeout for select */
/* Count voice leds from uci HW context */
voice_led_count = uci_get_voice_led_count();
init_sip_peers();
log_sip_peers();
/* Initialize ami connection */
ami_connection* con = ami_init(ami_handle_event);
if (ami_connect(con, hostname, portno)) {
set_state(CONNECTED, con);
}
/* Initialize ubus connection and register asterisk object */
ctx = ubus_connect(NULL);
if (ctx) {
ctx->connection_lost = ubus_connection_lost_cb;
system_fd_set_cloexec(ctx->sock.fd);
int ret = ubus_add_objects(ctx);
if (ret == 0) {
ubus_connected = true;
printf("Connected to UBUS, id: %08x\n", ctx->local_id);
}
else {
ubus_free(ctx);
ctx = NULL;
}
}
running = true;
/* Main application loop */
while(running) {
FD_ZERO(&fset);
timeout.tv_sec = 1;
timeout.tv_usec = 0;
if (!ctx) {
ubus_connected = false;
}
if (ubus_connected) {
FD_SET(ctx->sock.fd, &fset);
}
if (state != DISCONNECTED) {
FD_SET(con->sd, &fset);
}
/* Wait for events from ubus or ami */
int err = select(FD_SETSIZE, &fset, NULL, NULL, &timeout);
if(err < 0) {
if (errno == EINTR) {
break;
}
fprintf(stderr, "Error: %s\n", strerror(errno));
continue;
}
if (ubus_connected) {
if (FD_ISSET(ctx->sock.fd, &fset)) {
//printf("Handling UBUS events\n");
ubus_handle_event(ctx);
}
}
else {
if (ctx) {
if (ubus_reconnect(ctx, NULL) == 0) {
printf("UBUS reconnected\n");
ubus_connected = true;
system_fd_set_cloexec(ctx->sock.fd);
}
}
else {
ctx = ubus_connect(NULL);
if (ctx) {
ctx->connection_lost = ubus_connection_lost_cb;
system_fd_set_cloexec(ctx->sock.fd);
int ret = ubus_add_objects(ctx);
if (ret == 0) {
ubus_connected = true;
printf("Connected to UBUS, id: %08x\n", ctx->local_id);
}
else {
ubus_free(ctx);
ctx = NULL;
}
}
}
}
if (state == DISCONNECTED) {
if (ami_connect(con, hostname, portno)) {
set_state(CONNECTED, con);
}
}
else {
if (FD_ISSET(con->sd, &fset)) {
ami_handle_data(con);
}
}
}
ubus_free(ctx); //Shut down UBUS connection
printf("UBUS connection closed\n");
ami_free(con); //Shut down AMI connection
printf("AMI connection closed\n");
free_led_config();
return 0;
}
void set_state(AMI_STATE new_state, ami_connection* con)
{
if (state != new_state) {
state = new_state;
switch(new_state) {
case DISCONNECTED:
printf("In state DISCONNECTED\n");
fxs_line_count = 0;
dect_line_count = 0;
break;
case CONNECTED:
printf("In state CONNECTED\n");
/* We wait for LOGIN event here, then attempt to log in */
break;
case LOGGED_IN:
printf("In state LOGGED_IN\n");
init_brcm_ports();
fxs_line_count = 0;
dect_line_count = 0;
ami_send_brcm_module_show(con, on_brcm_module_show_response);
ami_send_sip_reload(con, NULL);
break;
case READY:
printf("In state READY\n");
configure_leds();
manage_leds();
break;
default:
break;
}
}
}
/*
* Reload uci context, as any changes to config will not be read otherwise
*/
void ucix_reload()
{
if (uci_ctx) {
ucix_cleanup(uci_ctx);
}
uci_ctx = ucix_init(UCI_VOICE_PACKAGE);
}