From 131c9e8110fc451e43141c3cc9564f6c91cdcdf3 Mon Sep 17 00:00:00 2001 From: Frederic-Philippe Metz Date: Thu, 4 Dec 2014 05:55:55 -0500 Subject: [PATCH] cli Ein Commandline interface, wo man von der console and der rtpengine bestimme Dinge abfragen kann. Author: Frederic-Philippe Metz --- README.md | 5 + daemon/Makefile | 2 +- daemon/call.c | 2 +- daemon/call.h | 1 + daemon/cli.c | 308 +++++++++++++++++++++++++++ daemon/cli.h | 18 ++ daemon/main.c | 18 ++ debian/ngcp-rtpengine-daemon.default | 1 + debian/ngcp-rtpengine-daemon.init | 1 + debian/ngcp-rtpengine-daemon.install | 1 + el/rtpengine.init | 5 + el/rtpengine.sysconfig | 1 + utils/rtpengine-ctl | 69 ++++++ 13 files changed, 430 insertions(+), 2 deletions(-) create mode 100644 daemon/cli.c create mode 100644 daemon/cli.h create mode 100755 utils/rtpengine-ctl diff --git a/README.md b/README.md index f6732b940..fca851581 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,7 @@ option and which are reproduced below: -F, --no-fallback Only start when kernel module is available -i, --interface=[NAME/]IP[!IP] Local interface for RTP -l, --listen-tcp=[IP:]PORT TCP port to listen on + -c, --listen-cli=[IP46:]PORT TCP port to listen on, CLI (command line interface) -u, --listen-udp=[IP46:]PORT UDP port to listen on -n, --listen-ng=[IP46:]PORT UDP port to listen on, NG protocol -T, --tos=INT TOS value to set on streams @@ -267,6 +268,10 @@ The options are described in more detail below. It is recommended to specify not only a local port number, but also 127.0.0.1 as interface to bind to. +* -c, --listen-cli + + TCP ip and port to listen for the CLI (command line interface). + * -t, --tos Takes an integer as argument and if given, specifies the TOS value that should be set in outgoing diff --git a/daemon/Makefile b/daemon/Makefile index 68ef67135..bed33cdc0 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -61,7 +61,7 @@ endif SRCS= main.c kernel.c poller.c aux.c control_tcp.c streambuf.c call.c control_udp.c redis.c \ bencode.c cookie_cache.c udp_listener.c control_ng.c sdp.c str.c stun.c rtcp.c \ - crypto.c rtp.c call_interfaces.c dtls.c log.c + crypto.c rtp.c call_interfaces.c dtls.c log.c cli.c OBJS= $(SRCS:.c=.o) diff --git a/daemon/call.c b/daemon/call.c index acfc0b313..9724c742d 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -2645,7 +2645,7 @@ restart: } /* returns call with master_lock held in W, or NULL if not found */ -static struct call *call_get(const str *callid, struct callmaster *m) { +struct call *call_get(const str *callid, struct callmaster *m) { struct call *ret; rwlock_lock_r(&m->hashlock); diff --git a/daemon/call.h b/daemon/call.h index 2a162c2ce..c8ca767bf 100644 --- a/daemon/call.h +++ b/daemon/call.h @@ -420,6 +420,7 @@ struct packet_stream *__packet_stream_new(struct call *call); struct call *call_get_or_create(const str *callid, struct callmaster *m); struct call *call_get_opmode(const str *callid, struct callmaster *m, enum call_opmode opmode); struct call_monologue *call_get_mono_dialogue(struct call *call, const str *fromtag, const str *totag); +struct call *call_get(const str *callid, struct callmaster *m); int monologue_offer_answer(struct call_monologue *monologue, GQueue *streams, const struct sdp_ng_flags *flags); int call_delete_branch(struct callmaster *m, const str *callid, const str *branch, const str *fromtag, const str *totag, bencode_item_t *output); diff --git a/daemon/cli.c b/daemon/cli.c new file mode 100644 index 000000000..b26e918e3 --- /dev/null +++ b/daemon/cli.c @@ -0,0 +1,308 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "poller.h" +#include "aux.h" +#include "log.h" +#include "call.h" +#include "cli.h" + + +static const char* TRUNCATED = " ... Output truncated. Increase Output Buffer ...\n"; + +#define truncate_output(x) do { x -= strlen(TRUNCATED)+1; x += sprintf(x,"%s",TRUNCATED); } while (0); + +#define ADJUSTLEN(printlen,outbuflen,replybuffer) do { if (printlen>=(outbufend-replybuffer)) \ + truncate_output(replybuffer); \ + replybuffer += (printlen>=outbufend-replybuffer)?outbufend-replybuffer:printlen; } while (0); + +static void cli_incoming_list_callid(char* buffer, int len, struct callmaster* m, char* replybuffer, const char* outbufend) { + str callid; + struct call* c=0; + struct call_monologue *ml; + struct call_media *md; + struct packet_stream *ps; + GSList *l; + GList *k, *o; + char buf[64]; + int printlen=0; + + if (len<=1) { + printlen = snprintf(replybuffer,(outbufend-replybuffer), "%s\n", "More parameters required."); + ADJUSTLEN(printlen,outbufend,replybuffer); + return; + } + ++buffer; --len; // one space + str_init_len(&callid,buffer,len); + + c = call_get(&callid, m); + + if (!c) { + printlen = snprintf(replybuffer,(outbufend-replybuffer), "\nCall Id not found (%s).\n\n",callid.s); + ADJUSTLEN(printlen,outbufend,replybuffer); + return; + } + + printlen = snprintf (replybuffer,(outbufend-replybuffer), "\ncallid: %30s | deletionmark:%4s | created:%12i\n\n", c->callid.s , c->ml_deleted?"yes":"no", (int)c->created); + ADJUSTLEN(printlen,outbufend,replybuffer); + + for (l = c->monologues; l; l = l->next) { + ml = l->data; + + printlen = snprintf(replybuffer,(outbufend-replybuffer), "--- Tag '"STR_FORMAT"', callduration " + "%u:%02u , in dialogue with '"STR_FORMAT"'\n", + STR_FMT(&ml->tag), + (unsigned int) (poller_now - ml->created) / 60, + (unsigned int) (poller_now - ml->created) % 60, + ml->active_dialogue ? ml->active_dialogue->tag.len : 6, + ml->active_dialogue ? ml->active_dialogue->tag.s : "(none)"); + ADJUSTLEN(printlen,outbufend,replybuffer); + + for (k = ml->medias.head; k; k = k->next) { + md = k->data; + + for (o = md->streams.head; o; o = o->next) { + ps = o->data; + + if (PS_ISSET(ps, FALLBACK_RTCP)) + continue; + + smart_ntop_p(buf, &ps->endpoint.ip46, sizeof(buf)); + + printlen = snprintf(replybuffer,(outbufend-replybuffer), "------ Media #%u, port %5u <> %15s:%-5hu%s, " + "%llu p, %llu b, %llu e\n", + md->index, + (unsigned int) (ps->sfd ? ps->sfd->fd.localport : 0), + buf, ps->endpoint.port, + (!PS_ISSET(ps, RTP) && PS_ISSET(ps, RTCP)) ? " (RTCP)" : "", + (unsigned long long) ps->stats.packets, + (unsigned long long) ps->stats.bytes, + (unsigned long long) ps->stats.errors); + ADJUSTLEN(printlen,outbufend,replybuffer); + } + } + } + printlen = snprintf(replybuffer,(outbufend-replybuffer), "\n"); + ADJUSTLEN(printlen,outbufend,replybuffer); + + rwlock_unlock_w(&c->master_lock); // because of call_get(..) +} + +static void cli_incoming_list(char* buffer, int len, struct callmaster* m, char* replybuffer, const char* outbufend) { + GHashTableIter iter; + gpointer key, value; + str *ptrkey; + struct call *call; + int printlen=0; + + static const char* LIST_NUMSESSIONS = "numsessions"; + static const char* LIST_SESSIONS = "sessions"; + static const char* LIST_SESSION = "session"; + + if (len<=1) { + printlen = snprintf(replybuffer, outbufend-replybuffer, "%s\n", "More parameters required."); + ADJUSTLEN(printlen,outbufend,replybuffer); + return; + } + ++buffer; --len; // one space + + if (len>=strlen(LIST_NUMSESSIONS) && strncmp(buffer,LIST_NUMSESSIONS,strlen(LIST_NUMSESSIONS)) == 0) { + rwlock_lock_r(&m->hashlock); + printlen = snprintf(replybuffer, outbufend-replybuffer, "Current Sessions on rtpengine:%i\n", g_hash_table_size(m->callhash)); + ADJUSTLEN(printlen,outbufend,replybuffer); + rwlock_unlock_r(&m->hashlock); + } else if (len>=strlen(LIST_SESSIONS) && strncmp(buffer,LIST_SESSIONS,strlen(LIST_SESSIONS)) == 0) { + rwlock_lock_r(&m->hashlock); + if (g_hash_table_size(m->callhash)==0) { + printlen = snprintf(replybuffer, outbufend-replybuffer, "No sessions on this media relay.\n"); + ADJUSTLEN(printlen,outbufend,replybuffer); + rwlock_unlock_r(&m->hashlock); + return; + } + g_hash_table_iter_init (&iter, m->callhash); + while (g_hash_table_iter_next (&iter, &key, &value)) { + ptrkey = (str*)key; + call = (struct call*)value; + printlen = snprintf(replybuffer, outbufend-replybuffer, "callid: %30s | deletionmark:%4s | created:%12i\n", ptrkey->s, call->ml_deleted?"yes":"no", (int)call->created); + ADJUSTLEN(printlen,outbufend,replybuffer); + } + rwlock_unlock_r(&m->hashlock); + } else if (len>=strlen(LIST_SESSION) && strncmp(buffer,LIST_SESSION,strlen(LIST_SESSION)) == 0) { + cli_incoming_list_callid(buffer+strlen(LIST_SESSION), len-strlen(LIST_SESSION), m, replybuffer, outbufend); + } else { + printlen = snprintf(replybuffer, outbufend-replybuffer, "%s:%s\n", "Unknown 'list' command", buffer); + ADJUSTLEN(printlen,outbufend,replybuffer); + } +} + +static void cli_incoming_terminate(char* buffer, int len, struct callmaster* m, char* replybuffer, const char* outbufend) { + str termparam; + struct call* c=0; + int printlen=0; + GHashTableIter iter; + gpointer key, value; + + if (len<=1) { + printlen = snprintf(replybuffer, outbufend-replybuffer, "%s\n", "More parameters required."); + ADJUSTLEN(printlen,outbufend,replybuffer); + return; + } + ++buffer; --len; // one space + str_init_len(&termparam,buffer,len); + + // --- terminate all calls + if (!str_memcmp(&termparam,"all")) { + while (g_hash_table_size(m->callhash)) { + g_hash_table_iter_init (&iter, m->callhash); + g_hash_table_iter_next (&iter, &key, &value); + c = (struct call*)value; + if (!c) continue; + call_destroy(c); + } + ilog(LOG_INFO,"All calls terminated by operator."); + printlen = snprintf(replybuffer, outbufend-replybuffer, "%s\n", "All calls terminated by operator."); + ADJUSTLEN(printlen,outbufend,replybuffer); + return; + } + + // --- terminate a dedicated call id + c = call_get(&termparam, m); + + if (!c) { + printlen = snprintf(replybuffer, outbufend-replybuffer, "\nCall Id not found (%s).\n\n",termparam.s); + ADJUSTLEN(printlen,outbufend,replybuffer); + return; + } + call_destroy(c); + + printlen = snprintf(replybuffer, outbufend-replybuffer, "\nCall Id (%s) successfully terminated by operator.\n\n",termparam.s); + ADJUSTLEN(printlen,outbufend,replybuffer); + ilog(LOG_WARN, "Call Id (%s) successfully terminated by operator.",termparam.s); + + rwlock_unlock_w(&c->master_lock); +} + +static void cli_incoming(int fd, void *p, uintptr_t u) { + int nfd; + struct sockaddr_in sin; + struct cli *cli = (void *) p; + socklen_t sinl; + static const int BUFLENGTH = 4096*1024; + char replybuffer[BUFLENGTH]; memset(&replybuffer,0,BUFLENGTH); + char* outbuf = replybuffer; + const char* outbufend = replybuffer+BUFLENGTH; + static const int MAXINPUT = 1024; + char inbuf[MAXINPUT]; memset(&inbuf,0,MAXINPUT); + int inlen = 0, readbytes = 0; + int rc=0; + + mutex_lock(&cli->lock); +next: + sinl = sizeof(sin); + nfd = accept(fd, (struct sockaddr *) &sin, &sinl); + if (nfd == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + sprintf(replybuffer, "Could currently not accept CLI commands. Reason:%s\n", strerror(errno)); + goto cleanup; + } + ilog(LOG_INFO, "Accept error:%s\n", strerror(errno)); + goto next; + } + + ilog(LOG_INFO, "New cli connection from " DF, DP(sin)); + + do { + readbytes = read(nfd, inbuf+inlen, MAXINPUT); + if (readbytes == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + ilog(LOG_INFO, "Could currently not read CLI commands. Reason:%s\n", strerror(errno)); + goto cleanup; + } + ilog(LOG_INFO, "Could currently not read CLI commands. Reason:%s\n", strerror(errno)); + } + inlen += readbytes; + } while (readbytes > 0); + + ilog(LOG_INFO, "Got CLI command:%s\n",inbuf); + + static const char* LIST = "list"; + static const char* TERMINATE = "terminate"; + + if (inlen>=strlen(LIST) && strncmp(inbuf,LIST,strlen(LIST)) == 0) { + cli_incoming_list(inbuf+strlen(LIST), inlen-strlen(LIST), cli->callmaster, outbuf, outbufend); + + } else if (inlen>=strlen(TERMINATE) && strncmp(inbuf,TERMINATE,strlen(TERMINATE)) == 0) { + cli_incoming_terminate(inbuf+strlen(TERMINATE), inlen-strlen(TERMINATE), cli->callmaster, outbuf, outbufend); + } else { + sprintf(replybuffer, "%s:%s\n", "Unknown or incomplete command:", inbuf); + } + + do { + rc += write( nfd, (char *)&replybuffer, strlen(replybuffer) ); + } while (rc < strlen(replybuffer)); + +cleanup: + close(nfd); + mutex_unlock(&cli->lock); +} + +static void control_closed(int fd, void *p, uintptr_t u) { + abort(); +} + +struct cli *cli_new(struct poller *p, u_int32_t ip, u_int16_t port, struct callmaster *m) { + struct cli *c; + int fd; + struct sockaddr_in sin; + struct poller_item i; + + if (!p || !m) + return NULL; + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) + return NULL; + + nonblock(fd); + reuseaddr(fd); + + ZERO(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ip; + sin.sin_port = htons(port); + if (bind(fd, (struct sockaddr *) &sin, sizeof(sin))) + goto fail; + + if (listen(fd, 5)) + goto fail; + + c = obj_alloc0("cli_udp", sizeof(*c), NULL); + c->fd = fd; + c->poller = p; + c->callmaster = m; + mutex_init(&c->lock); + + ZERO(i); + i.fd = fd; + i.closed = control_closed; + i.readable = cli_incoming; + i.obj = &c->obj; + if (poller_add_item(p, &i)) + goto fail2; + + obj_put(c); + return c; + +fail2: + obj_put(c); +fail: + close(fd); + return NULL; +} diff --git a/daemon/cli.h b/daemon/cli.h new file mode 100644 index 000000000..af562107a --- /dev/null +++ b/daemon/cli.h @@ -0,0 +1,18 @@ +#ifndef CLI_UDP_H_ +#define CLI_UDP_H_ + +#include + +struct cli { + struct obj obj; + + struct callmaster *callmaster; + int fd; + struct poller *poller; + mutex_t lock; + +}; + +struct cli *cli_new(struct poller *p, u_int32_t ip, u_int16_t port, struct callmaster *m); + +#endif /* CLI_UDP_H_ */ diff --git a/daemon/main.c b/daemon/main.c index bc35434f2..006502743 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -25,6 +25,7 @@ #include "sdp.h" #include "dtls.h" #include "call_interfaces.h" +#include "cli.h" @@ -91,6 +92,8 @@ static struct in6_addr udp_listenp; static u_int16_t udp_listenport; static struct in6_addr ng_listenp; static u_int16_t ng_listenport; +static u_int32_t cli_listenp; +static u_int16_t cli_listenport; static int tos; static int table = -1; static int no_fallback; @@ -309,6 +312,7 @@ static void options(int *argc, char ***argv) { char *listenps = NULL; char *listenudps = NULL; char *listenngs = NULL; + char *listencli = NULL; char *redisps = NULL; char *log_facility_s = NULL; char *log_facility_cdr_s = NULL; @@ -323,6 +327,7 @@ static void options(int *argc, char ***argv) { { "listen-tcp", 'l', 0, G_OPTION_ARG_STRING, &listenps, "TCP port to listen on", "[IP:]PORT" }, { "listen-udp", 'u', 0, G_OPTION_ARG_STRING, &listenudps, "UDP port to listen on", "[IP46:]PORT" }, { "listen-ng", 'n', 0, G_OPTION_ARG_STRING, &listenngs, "UDP port to listen on, NG protocol", "[IP46:]PORT" }, + { "listen-cli", 'c', 0, G_OPTION_ARG_STRING, &listencli, "UDP port to listen on, CLI", "[IP46:]PORT" }, { "tos", 'T', 0, G_OPTION_ARG_INT, &tos, "Default TOS value to set on streams", "INT" }, { "timeout", 'o', 0, G_OPTION_ARG_INT, &timeout, "RTP timeout", "SECS" }, { "silent-timeout",'s',0,G_OPTION_ARG_INT, &silent_timeout,"RTP timeout for muted", "SECS" }, @@ -380,6 +385,10 @@ static void options(int *argc, char ***argv) { die("Invalid IP or port (--listen-ng)"); } + if (listencli) {if (parse_ip_port(&cli_listenp, &cli_listenport, listencli)) + die("Invalid IP or port (--listen-cli)"); + } + if (tos < 0 || tos > 255) die("Invalid TOS value"); @@ -557,6 +566,7 @@ void create_everything(struct main_context *ctx) { struct control_tcp *ct; struct control_udp *cu; struct control_ng *cn; + struct cli *cl; int kfd = -1; void *dlh; const char **strp; @@ -625,6 +635,14 @@ no_kernel: die("Failed to open UDP control connection port"); } + cl = NULL; + if (cli_listenport) { + callmaster_exclude_port(ctx->m, cli_listenport); + cl = cli_new(ctx->p, cli_listenp, cli_listenport, ctx->m); + if (!cl) + die("Failed to open UDP CLI connection port"); + } + if (redis_ip) { dlh = dlopen(MP_PLUGIN_DIR "/rtpengine-redis.so", RTLD_NOW | RTLD_GLOBAL); if (!dlh && !g_file_test(MP_PLUGIN_DIR "/rtpengine-redis.so", G_FILE_TEST_IS_REGULAR) diff --git a/debian/ngcp-rtpengine-daemon.default b/debian/ngcp-rtpengine-daemon.default index 9cf8cab7b..713d3fe51 100644 --- a/debian/ngcp-rtpengine-daemon.default +++ b/debian/ngcp-rtpengine-daemon.default @@ -2,6 +2,7 @@ RUN_RTPENGINE=no LISTEN_TCP=25060 LISTEN_UDP=12222 LISTEN_NG=22222 +LISTEN_CLI=9900 # INTERFACES="123.234.345.456" # INTERFACES="internal/12.23.34.45 external/23.34.45.54" # INTERFACES="12.23.34.45!23.34.45.56" diff --git a/debian/ngcp-rtpengine-daemon.init b/debian/ngcp-rtpengine-daemon.init index 82637eb55..4caa2fec8 100755 --- a/debian/ngcp-rtpengine-daemon.init +++ b/debian/ngcp-rtpengine-daemon.init @@ -55,6 +55,7 @@ fi [ -z "$LISTEN_TCP" ] || OPTIONS="$OPTIONS --listen-tcp=$LISTEN_TCP" [ -z "$LISTEN_UDP" ] || OPTIONS="$OPTIONS --listen-udp=$LISTEN_UDP" [ -z "$LISTEN_NG" ] || OPTIONS="$OPTIONS --listen-ng=$LISTEN_NG" +[ -z "$LISTEN_CLI" ] || OPTIONS="$OPTIONS --listen-cli=$LISTEN_CLI" [ -z "$TIMEOUT" ] || OPTIONS="$OPTIONS --timeout=$TIMEOUT" [ -z "$SILENT_TIMEOUT" ] || OPTIONS="$OPTIONS --silent-timeout=$SILENT_TIMEOUT" [ -z "$PIDFILE" ] || OPTIONS="$OPTIONS --pidfile=$PIDFILE" diff --git a/debian/ngcp-rtpengine-daemon.install b/debian/ngcp-rtpengine-daemon.install index 11a8ccb4d..2ef4d0978 100644 --- a/debian/ngcp-rtpengine-daemon.install +++ b/debian/ngcp-rtpengine-daemon.install @@ -1 +1,2 @@ daemon/rtpengine /usr/sbin/ +utils/rtpengine-ctl /usr/sbin/ diff --git a/el/rtpengine.init b/el/rtpengine.init index 5c0c97d4b..529d2b6f7 100644 --- a/el/rtpengine.init +++ b/el/rtpengine.init @@ -92,6 +92,11 @@ build_opts() { OPTS+=" --listen-ng=$LISTEN_NG" fi + if [[ -n "$LISTEN_CLI" ]] + then + OPTS+=" --listen-cli=$LISTEN_CLI" + fi + if [[ -n "$TOS" ]] then OPTS+=" --tos=$TOS" diff --git a/el/rtpengine.sysconfig b/el/rtpengine.sysconfig index 2baa2997e..3048c0cbe 100644 --- a/el/rtpengine.sysconfig +++ b/el/rtpengine.sysconfig @@ -20,6 +20,7 @@ LISTEN_UDP=127.0.0.1:2222 # IP address and port combination for UDP # control #LISTEN_NG=127.0.0.1:2223 # IP address and port combination for NG (UDP) # control +#LISTEN_CLI=127.0.0.1:9900 # #TOS=184 # (o) TOS value to use in outgoing packets #TIMEOUT=60 # (o) Number of seconds after which a media stream is diff --git a/utils/rtpengine-ctl b/utils/rtpengine-ctl new file mode 100755 index 000000000..80a876ebb --- /dev/null +++ b/utils/rtpengine-ctl @@ -0,0 +1,69 @@ +#!/bin/bash +# + +host=127.0.0.1 +port=9900 +error_rc=255 + +prgname=${0##*/} +prgdir=${0%$prgname} + +showusage() { + echo "" + echo " rectl [ -ip -port ] " + echo "" + echo " Supported commands are:" + echo "" + echo " list [ numsessions | sessions | session ]" + echo " numsessions : prints the number of sessions" + echo " sessions : print one-liner session information" + echo " session : print detail about one session" + echo "" + echo " terminate [ all | ]" + echo " all : terminates all current sessions" + echo " : session is immediately terminated" + echo "" + echo "" + echo " Return Value:" + echo " 0 on success with ouput from server side, other values for failure." + echo "" + exit 0 +} + +if [ $# -eq 0 ]; then showusage; fi + + +command -v nc 2>&1 >/dev/null +if [ $? -ne 0 ]; then + echo "Error: rectl requires netcat to be installed." + exit 0 +fi + +while [ $# -gt 0 ]; do + case $1 in + "-?"|"-help"|"-h") + showusage + ;; + "-ip") + shift + if [ $# -gt 0 ]; then + host=$1 + else + echo "Missing parameter for option '-ip'" >&2 + fi + ;; + "-port") + shift + if [ $# -gt 0 ]; then + port=$1 + else + echo "Missing parameter for option '-port'" >&2 + fi + ;; + *) + varargs="$varargs $1" + esac + shift +done + +echo -n ${varargs} | nc ${host} ${port}