diff options
Diffstat (limited to 'modules/ctrl_tcp/ctrl_tcp.c')
-rw-r--r-- | modules/ctrl_tcp/ctrl_tcp.c | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/modules/ctrl_tcp/ctrl_tcp.c b/modules/ctrl_tcp/ctrl_tcp.c new file mode 100644 index 0000000..713f9f4 --- /dev/null +++ b/modules/ctrl_tcp/ctrl_tcp.c @@ -0,0 +1,377 @@ +/** + * @file ctrl_tcp.c TCP control interface using JSON payload + * + * Copyright (C) 2018 46 Labs LLC + */ + +#include <re.h> +#include <baresip.h> + +#include "tcp_netstring.h" + + +/** + * @defgroup ctrl_tcp ctrl_tcp + * + * Communication channel to control and monitor Baresip via JSON messages. + * + * It receives commands to be executed, sends back command responses and + * notifies about events. + * + * Command message parameters: + * + * - command : Command to be executed. + * - params : Command parameters. + * - token : Optional. Included in the response if present. + * + * Command message example: + * + \verbatim + { + "command" : "dial", + "params" : "sip:alice@atlanta.com", + "token" : "qwerasdf" + } + \endverbatim + * + * + * Response message parameters: + * + * - response : true. Identifies the message type. + * - ok: : true/false. Indicates whether the command execution succeeded. + * - data : Baresip response to the related command execution. + * - token : Present if it was included in the related command request. + * + * Response message example: + * + \verbatim + { + "response" : true, + "ok" : true, + "data" : "", + "token" : "qwerasdf" + } + \endverbatim + * + * + * Event message parameters: + * + * - event : true. Identifies the message type. + * - class : Event class. + * - type : Event ID. + * - param : Specific event information. + * + * Apart from the above, events may contain aditional parameters. + * + * Event message example: + * + \verbatim + { + "event" : "true", + "class" : "call", + "type" : "CALL_CLOSED", + "param" : "Connection reset by peer", + "accountaor" : "sip:alice@atlanta.com", + "direction" : "incoming", + "peeruri" : "sip:bob@biloxy.com", + "id" : "73a12546589651f8" + } + \endverbatim + * + * + * Sample config: + * + \verbatim + ctrl_tcp_listen 0.0.0.0:4444 # IP-address and port to listen on + \endverbatim + */ + + +enum {CTRL_PORT = 4444}; + +struct ctrl_st { + struct tcp_sock *ts; + struct tcp_conn *tc; + struct netstring *ns; +}; + +static struct ctrl_st *ctrl = NULL; /* allow only one instance */ + +static int print_handler(const char *p, size_t size, void *arg) +{ + struct mbuf *mb = arg; + + return mbuf_write_mem(mb, (uint8_t *)p, size); +} + + +static int encode_response(int cmd_error, struct mbuf *resp, const char *token) +{ + struct re_printf pf = {print_handler, resp}; + struct odict *od = NULL; + char *buf = NULL; + char m[256]; + int err; + + /* Empty response. */ + if (resp->pos == NETSTRING_HEADER_SIZE) + { + buf = mem_alloc(1, NULL); + buf[0] = '\0'; + } + else + { + resp->pos = NETSTRING_HEADER_SIZE; + err = mbuf_strdup(resp, &buf, + resp->end - NETSTRING_HEADER_SIZE); + if (err) + return err; + } + + err = odict_alloc(&od, 8); + if (err) + return err; + + err |= odict_entry_add(od, "response", ODICT_BOOL, true); + err |= odict_entry_add(od, "ok", ODICT_BOOL, (bool)!cmd_error); + + if (cmd_error && str_len(buf) == 0) + err |= odict_entry_add(od, "data", ODICT_STRING, + str_error(cmd_error, m, sizeof(m))); + else + err |= odict_entry_add(od, "data", ODICT_STRING, buf); + + if (token) + err |= odict_entry_add(od, "token", ODICT_STRING, token); + + if (err) + goto out; + + mbuf_reset(resp); + mbuf_init(resp); + resp->pos = NETSTRING_HEADER_SIZE; + + err = json_encode_odict(&pf, od); + if (err) + warning("ctrl_tcp: failed to encode response JSON (%m)\n", + err); + + out: + mem_deref(buf); + mem_deref(od); + + return err; +} + + +static bool command_handler(struct mbuf *mb, void *arg) +{ + struct ctrl_st *st = arg; + struct mbuf *resp = mbuf_alloc(2048); + struct re_printf pf = {print_handler, resp}; + struct odict *od = NULL; + const struct odict_entry *oe_cmd, *oe_prm, *oe_tok; + char buf[256]; + int err; + + err = json_decode_odict(&od, 32, (const char*)mb->buf, mb->end, 16); + if (err) { + warning("ctrl_tcp: failed to decode JSON (%m)\n", err); + goto out; + } + + oe_cmd = odict_lookup(od, "command"); + oe_prm = odict_lookup(od, "params"); + oe_tok = odict_lookup(od, "token"); + if (!oe_cmd) { + warning("ctrl_tcp: missing json entries\n"); + goto out; + } + + debug("ctrl_tcp: handle_command: cmd='%s', params:'%s', token='%s'\n", + oe_cmd ? oe_cmd->u.str : "", + oe_prm ? oe_prm->u.str : "", + oe_tok ? oe_tok->u.str : ""); + + re_snprintf(buf, sizeof(buf), "%s%s%s", + oe_cmd->u.str, + oe_prm ? " " : "", + oe_prm ? oe_prm->u.str : ""); + + resp->pos = NETSTRING_HEADER_SIZE; + + /* Relay message to long commands */ + err = cmd_process_long(baresip_commands(), + buf, + str_len(buf), + &pf, NULL); + if (err) { + warning("ctrl_tcp: error processing command (%m)\n", err); + } + + err = encode_response(err, resp, oe_tok ? oe_tok->u.str : NULL); + if (err) { + warning("ctrl_tcp: failed to encode response (%m)\n", err); + goto out; + } + + resp->pos = NETSTRING_HEADER_SIZE; + err = tcp_send(st->tc, resp); + if (err) { + warning("ctrl_tcp: failed to send the message (%m)\n", err); + } + + out: + mem_deref(resp); + mem_deref(od); + + return true; /* always handled */ +} + + +static void tcp_close_handler(int err, void *arg) +{ + struct ctrl_st *st = arg; + + (void)err; + + st->tc = mem_deref(st->tc); +} + + +static void tcp_conn_handler(const struct sa *peer, void *arg) +{ + struct ctrl_st *st = arg; + + (void)peer; + + /* only one connection allowed */ + st->tc = mem_deref(st->tc); + st->ns = mem_deref(st->ns); + + (void)tcp_accept(&st->tc, st->ts, NULL, NULL, tcp_close_handler, st); + (void)netstring_insert(&st->ns, st->tc, 0, command_handler, st); +} + + +/* + * Relay UA events + */ +static void ua_event_handler(struct ua *ua, enum ua_event ev, + struct call *call, const char *prm, void *arg) +{ + struct ctrl_st *st = arg; + struct mbuf *buf = mbuf_alloc(1024); + struct re_printf pf = {print_handler, buf}; + struct odict *od = NULL; + int err; + + buf->pos = NETSTRING_HEADER_SIZE; + + err = odict_alloc(&od, 8); + if (err) + return; + + err = odict_entry_add(od, "event", ODICT_BOOL, true); + err |= event_encode_dict(od, ua, ev, call, prm); + if (err) + goto out; + + err = json_encode_odict(&pf, od); + if (err) { + warning("ctrl_tcp: failed to encode json (%m)\n", err); + goto out; + } + + if (st->tc) { + buf->pos = NETSTRING_HEADER_SIZE; + err = tcp_send(st->tc, buf); + if (err) { + warning("ctrl_tcp: failed to send the message (%m)\n", + err); + } + } + + out: + mem_deref(buf); + mem_deref(od); +} + + +static void ctrl_destructor(void *arg) +{ + struct ctrl_st *st = arg; + + mem_deref(st->tc); + mem_deref(st->ts); + mem_deref(st->ns); +} + + +static int ctrl_alloc(struct ctrl_st **stp, const struct sa *laddr) +{ + struct ctrl_st *st; + int err; + + if (!stp) + return EINVAL; + + st = mem_zalloc(sizeof(*st), ctrl_destructor); + if (!st) + return ENOMEM; + + err = tcp_listen(&st->ts, laddr, tcp_conn_handler, st); + if (err) { + warning("ctrl_tcp: failed to listen on TCP %J (%m)\n", + laddr, err); + goto out; + } + + debug("ctrl_tcp: TCP socket listening on %J\n", laddr); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int ctrl_init(void) +{ + struct sa laddr; + int err; + + if (conf_get_sa(conf_cur(), "ctrl_tcp_listen", &laddr)) { + sa_set_str(&laddr, "0.0.0.0", CTRL_PORT); + } + + err = ctrl_alloc(&ctrl, &laddr); + if (err) + return err; + + err = uag_event_register(ua_event_handler, ctrl); + if (err) + return err; + + return 0; +} + + +static int ctrl_close(void) +{ + uag_event_unregister(ua_event_handler); + ctrl = mem_deref(ctrl); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(ctrl_tcp) = { + "ctrl_tcp", + "application", + ctrl_init, + ctrl_close +}; |