diff --git a/README.md b/README.md index ff651726e..7931add40 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 @@ -176,9 +177,11 @@ option and which are reproduced below: -b, --b2b-url=STRING XMLRPC URL of B2B UA -L, --log-level=INT Mask log priorities above this level --log-facility=daemon|local0|... Syslog facility to use for logging + --log-facility-cdr=local0|... Syslog facility to use for logging CDRs -E, --log-stderr Log on stderr instead of syslog -x, --xmlrpc-format=INT XMLRPC timeout request format to use. 0: SEMS DI, 1: call-id only --num-threads=INT Number of worker threads to create + -d, --delete-delay Delay for deleting a session from memory. --sip-source Use SIP source address by default --dtls-passive Always prefer DTLS passive role @@ -266,6 +269,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 @@ -309,6 +316,10 @@ The options are described in more detail below. The syslog facilty to use when sending log messages to the syslog daemon. Defaults to `daemon`. +* --log-facilty-cdr=daemon|local0|...|local7|... + + Same as --log-facility with the difference that only CDRs are written to this log facility. + * -E, --log-stderr Log to stderr instead of syslog. Only useful in combination with `--foreground`. @@ -335,6 +346,10 @@ The options are described in more detail below. Enabled the `DTLS=passive` flag for all calls unconditionally. +* -d, --delete-delay + + Delete the call from memory after the specified delay from memory. + * -r, --redis, -R, --redis-db, -b, --b2b-url NGCP-specific options diff --git a/daemon/Makefile b/daemon/Makefile index b5f4dfcb0..07e0da089 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -62,7 +62,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 c806b5eb6..307b9bfe9 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include "poller.h" #include "aux.h" @@ -34,10 +36,6 @@ -#ifndef DELETE_DELAY -#define DELETE_DELAY 30 -#endif - #ifndef PORT_RANDOM_MIN #define PORT_RANDOM_MIN 6 #define PORT_RANDOM_MAX 20 @@ -122,8 +120,23 @@ const struct transport_protocol transport_protocols[] = { }; const int num_transport_protocols = G_N_ELEMENTS(transport_protocols); +const char * get_term_reason_text(char *buf, enum termination_reason t) { + if (t==TIMEOUT) { buf = "TIMEOUT"; return buf; } + if (t==REGULAR) { buf = "REGULAR"; return buf; } + if (t==FORCED) { buf = "FORCED"; return buf; } + if (t==SILENT_TIMEOUT) { buf = "SILENT_TIMEOUT"; return buf; } + buf = "UNKNOWN"; + return buf; +} +const char * get_tag_type_text(char *buf, enum tag_type t) { + if (t==FROM_TAG) { buf = "FROM_TAG"; return buf; } + if (t==TO_TAG) { buf = "TO_TAG"; return buf; } + + buf = "UNKNOWN"; + return buf; +} static void determine_handler(struct packet_stream *in, const struct packet_stream *out); @@ -425,7 +438,7 @@ void kernelize(struct packet_stream *stream) { PS_SET(stream, KERNELIZED); return; - + no_kernel_warn: ilog(LOG_WARNING, "No support for kernel packet forwarding available"); no_kernel: @@ -831,6 +844,7 @@ forward: if (ret == -1) { ret = -errno; + ilog(LOG_DEBUG,"Error when sending message. Error: %s",strerror(errno)); stream->stats.errors++; mutex_lock(&cm->statspslock); cm->statsps.errors++; @@ -980,6 +994,7 @@ static int call_timer_delete_monologues(struct call *c) { } __monologue_destroy(ml); + ml->deleted = 0; if (!g_hash_table_size(c->tags)) { @@ -1014,10 +1029,15 @@ static void call_timer_iterator(void *key, void *val, void *ptr) { int good = 0; struct packet_stream *ps; struct stream_fd *sfd; + int tmp_t_reason=0; + struct call_monologue *ml; + GSList *i; rwlock_lock_r(&c->master_lock); log_info_call(c); + cm = c->callmaster; + if (c->deleted && poller_now >= c->deleted && c->last_signal <= c->deleted) goto delete; @@ -1030,8 +1050,6 @@ static void call_timer_iterator(void *key, void *val, void *ptr) { if (!c->streams) goto drop; - cm = c->callmaster; - for (it = c->streams; it; it = it->next) { ps = it->data; mutex_lock(&ps->in_lock); @@ -1055,8 +1073,11 @@ no_sfd: goto next; check = cm->conf.timeout; - if (!MEDIA_ISSET(ps->media, RECV) || !sfd) + tmp_t_reason = 1; + if (!MEDIA_ISSET(ps->media, RECV) || !sfd) { check = cm->conf.silent_timeout; + tmp_t_reason = 2; + } if (poller_now - ps->last_packet < check) good = 1; @@ -1068,6 +1089,22 @@ next: if (good) goto out; + if (c->ml_deleted) + goto out; + + for (i = c->monologues; i; i = i->next) { + ml = i->data; + memset(&ml->terminated,0,sizeof(struct timeval)); + gettimeofday(&(ml->terminated),NULL); + if (tmp_t_reason==1) { + ml->term_reason = TIMEOUT; + } else if (tmp_t_reason==2) { + ml->term_reason = SILENT_TIMEOUT; + } else { + ml->term_reason = UNKNOWN; + } + } + ilog(LOG_INFO, "Closing call due to timeout"); drop: @@ -2299,10 +2336,38 @@ static void unkernelize(struct packet_stream *p) { PS_CLEAR(p, KERNELIZED); } +void timeval_subtract (struct timeval *result, const struct timeval *a, const struct timeval *b) { + long microseconds=0; + microseconds = ((long)a->tv_sec - (long)b->tv_sec) * 1000000 + ((long)a->tv_usec - (long)b->tv_usec); + result->tv_sec = microseconds/(long)1000000; + result->tv_usec = microseconds%(long)1000000; +} + +void timeval_multiply(struct timeval *result, const struct timeval *a, const long multiplier) { + long microseconds=0; + microseconds = (((long)a->tv_sec * 1000000) + (long)a->tv_usec) * multiplier; + result->tv_sec = microseconds/(long)1000000; + result->tv_usec = microseconds%(long)1000000; +} + +void timeval_devide(struct timeval *result, const struct timeval *a, const long devisor) { + long microseconds=0; + microseconds = (((long)a->tv_sec * 1000000) + (long)a->tv_usec) / devisor; + result->tv_sec = microseconds/(long)1000000; + result->tv_usec = microseconds%(long)1000000; +} + +void timeval_add(struct timeval *result, const struct timeval *a, const struct timeval *b) { + long microseconds=0; + microseconds = ((long)a->tv_sec + (long)b->tv_sec) * (long)1000000 + ((long)a->tv_usec + (long)b->tv_usec); + result->tv_sec = microseconds/(long)1000000; + result->tv_usec = microseconds%(long)1000000; +} + /* called lock-free, but must hold a reference to the call */ void call_destroy(struct call *c) { struct callmaster *m = c->callmaster; - struct packet_stream *ps; + struct packet_stream *ps=0, *ps2=0; struct stream_fd *sfd; struct poller *p = m->poller; GSList *l; @@ -2311,6 +2376,15 @@ void call_destroy(struct call *c) { struct call_media *md; GList *k, *o; char buf[64]; + struct timeval tim_result_duration; + static const int CDRBUFLENGTH = 4096*2; + char reasonbuf[16]; memset(&reasonbuf,0,16); + char tagtypebuf[16]; memset(&tagtypebuf,0,16); + char cdrbuffer[CDRBUFLENGTH]; memset(&cdrbuffer,0,CDRBUFLENGTH); + char* cdrbufcur = cdrbuffer; + int cdrlinecnt = 0; + int found = 0; + //char tmpstreampairstatus[2]; memset(&tmpstreampairstatus,0,2); rwlock_lock_w(&m->hashlock); ret = g_hash_table_remove(m->callhash, &c->callid); @@ -2328,8 +2402,30 @@ void call_destroy(struct call *c) { ilog(LOG_INFO, "Final packet stats:"); + /* CDRs and statistics */ + cdrbufcur += sprintf(cdrbufcur,"ci=%s, ",c->callid.s); + cdrbufcur += sprintf(cdrbufcur,"created_from=%s, ", c->created_from); for (l = c->monologues; l; l = l->next) { ml = l->data; + if (_log_facility_cdr) { + memset(&tim_result_duration,0,sizeof(struct timeval)); + timeval_subtract(&tim_result_duration,&ml->terminated,&ml->started); + cdrbufcur += sprintf(cdrbufcur, "ml%i_start_time=%ld.%06lu, " + "ml%i_end_time=%ld.%06ld, " + "ml%i_duration=%ld.%06ld, " + "ml%i_termination=%s, " + "ml%i_local_tag=%s, " + "ml%i_local_tag_type=%s, " + "ml%i_remote_tag=%s, ", + cdrlinecnt, ml->started.tv_sec, ml->started.tv_usec, + cdrlinecnt, ml->terminated.tv_sec, ml->terminated.tv_usec, + cdrlinecnt, tim_result_duration.tv_sec, tim_result_duration.tv_usec, + cdrlinecnt, get_term_reason_text(reasonbuf,ml->term_reason), + cdrlinecnt, ml->tag.s, + cdrlinecnt, get_tag_type_text(tagtypebuf,ml->tagtype), + cdrlinecnt, ml->active_dialogue ? ml->active_dialogue->tag.s : "(none)"); + } + ilog(LOG_INFO, "--- Tag '"STR_FORMAT"', created " "%u:%02u ago, in dialogue with '"STR_FORMAT"'", STR_FMT(&ml->tag), @@ -2348,6 +2444,24 @@ void call_destroy(struct call *c) { continue; smart_ntop_p(buf, &ps->endpoint.ip46, sizeof(buf)); + + if (_log_facility_cdr) { + const char* protocol = (!PS_ISSET(ps, RTP) && PS_ISSET(ps, RTCP)) ? "rtcp" : "rtp"; + cdrbufcur += sprintf(cdrbufcur, + "ml%i_midx%u_%s_endpoint_ip=%s, " + "ml%i_midx%u_%s_endpoint_port=%u, " + "ml%i_midx%u_%s_local_relay_port=%u, " + "ml%i_midx%u_%s_relayed_packets=%llu, " + "ml%i_midx%u_%s_relayed_bytes=%llu, " + "ml%i_midx%u_%s_relayed_errors=%llu, ", + cdrlinecnt, md->index, protocol, buf, + cdrlinecnt, md->index, protocol, ps->endpoint.port, + cdrlinecnt, md->index, protocol, (unsigned int) (ps->sfd ? ps->sfd->fd.localport : 0), + cdrlinecnt, md->index, protocol, (unsigned long long) ps->stats.packets, + cdrlinecnt, md->index, protocol, (unsigned long long) ps->stats.bytes, + cdrlinecnt, md->index, protocol, (unsigned long long) ps->stats.errors); + } + ilog(LOG_INFO, "------ Media #%u, port %5u <> %15s:%-5hu%s, " "%llu p, %llu b, %llu e", md->index, @@ -2357,10 +2471,82 @@ void call_destroy(struct call *c) { (unsigned long long) ps->stats.packets, (unsigned long long) ps->stats.bytes, (unsigned long long) ps->stats.errors); + m->totalstats.total_relayed_packets += (unsigned long long) ps->stats.packets; + m->totalstats.total_relayed_errors += (unsigned long long) ps->stats.errors; + } + } + if (_log_facility_cdr) + ++cdrlinecnt; + } + + // --- for statistics getting one way stream or no relay at all + m->totalstats.total_nopacket_relayed_sess *= 2; + for (l = c->monologues; l; l = l->next) { + ml = l->data; + + // --- go through partner ml and search the RTP + 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, RTP) && !PS_ISSET(ps, RTCP))) { + // --- only RTP is interesting + found = 1; + break; + } } + if (found) { break; } } + found = 0; + + if (ml->active_dialogue) { + // --- go through partner ml and search the RTP + for (k = ml->active_dialogue->medias.head; k; k = k->next) { + md = k->data; + + for (o = md->streams.head; o; o = o->next) { + ps2 = o->data; + if ((PS_ISSET(ps2, RTP) && !PS_ISSET(ps2, RTCP))) { + // --- only RTP is interesting + found = 1; + break; + } + } + if (found) { break; } + } + } + + if (ps && ps2 && ps->stats.packets!=0 && ps2->stats.packets==0) + m->totalstats.total_oneway_stream_sess++; + + if (ps && ps2 && ps->stats.packets==0 && ps2->stats.packets==0) + m->totalstats.total_nopacket_relayed_sess++; + + } + m->totalstats.total_nopacket_relayed_sess /= 2; + + m->totalstats.total_managed_sess += 1; + + ml = c->monologues->data; + if (ml->term_reason==TIMEOUT) { + m->totalstats.total_timeout_sess++; + } else if (ml->term_reason==SILENT_TIMEOUT) { + m->totalstats.total_silent_timeout_sess++; + } else if (ml->term_reason==REGULAR) { + m->totalstats.total_regular_term_sess++; + } else if (ml->term_reason==FORCED) { + m->totalstats.total_forced_term_sess++; } + timeval_multiply(&m->totalstats.total_average_call_dur,&m->totalstats.total_average_call_dur,m->totalstats.total_managed_sess-1); + timeval_add(&m->totalstats.total_average_call_dur,&m->totalstats.total_average_call_dur,&tim_result_duration); + timeval_devide(&m->totalstats.total_average_call_dur,&m->totalstats.total_average_call_dur,m->totalstats.total_managed_sess); + + if (_log_facility_cdr) + /* log it */ + cdrlog(cdrbuffer); + for (l = c->streams; l; l = l->next) { ps = l->data; @@ -2557,7 +2743,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); @@ -2757,6 +2943,7 @@ int call_delete_branch(struct callmaster *m, const str *callid, const str *branc struct call_monologue *ml; int ret; const str *match_tag; + GSList *i; c = call_get(callid, m); if (!c) { @@ -2764,6 +2951,13 @@ int call_delete_branch(struct callmaster *m, const str *callid, const str *branc goto err; } + for (i = c->monologues; i; i = i->next) { + ml = i->data; + memset(&ml->terminated,0,sizeof(struct timeval)); + gettimeofday(&(ml->terminated), NULL); + ml->term_reason = REGULAR; + } + if (!fromtag || !fromtag->s || !fromtag->len) goto del_all; @@ -2795,15 +2989,15 @@ int call_delete_branch(struct callmaster *m, const str *callid, const str *branc */ ilog(LOG_INFO, "Scheduling deletion of call branch '"STR_FORMAT"' in %d seconds", - STR_FMT(&ml->tag), DELETE_DELAY); - ml->deleted = poller_now + 30; + STR_FMT(&ml->tag), m->conf.delete_delay); + ml->deleted = poller_now + m->conf.delete_delay; if (!c->ml_deleted || c->ml_deleted > ml->deleted) c->ml_deleted = ml->deleted; goto success_unlock; del_all: - ilog(LOG_INFO, "Scheduling deletion of entire call in %d seconds", DELETE_DELAY); - c->deleted = poller_now + DELETE_DELAY; + ilog(LOG_INFO, "Scheduling deletion of entire call in %d seconds", m->conf.delete_delay); + c->deleted = poller_now + m->conf.delete_delay; rwlock_unlock_w(&c->master_lock); goto success; diff --git a/daemon/call.h b/daemon/call.h index 2a37b8331..3328ed9b4 100644 --- a/daemon/call.h +++ b/daemon/call.h @@ -7,12 +7,25 @@ #include #include #include +#include #include #include #include "compat.h" +enum termination_reason { + UNKNOWN=0, + REGULAR=1, + FORCED=2, + TIMEOUT=3, + SILENT_TIMEOUT=4 +}; +enum tag_type { + UNKNOWN_TAG=0, + FROM_TAG=1, + TO_TAG=2 +}; enum stream_address_format { SAF_TCP, @@ -179,6 +192,20 @@ struct stats { u_int64_t errors; }; +struct totalstats { + time_t started; + u_int64_t total_managed_sess; + u_int64_t total_timeout_sess; + u_int64_t total_silent_timeout_sess; + u_int64_t total_regular_term_sess; + u_int64_t total_forced_term_sess; + u_int64_t total_relayed_packets; + u_int64_t total_relayed_errors; + u_int64_t total_nopacket_relayed_sess; + u_int64_t total_oneway_stream_sess; + struct timeval total_average_call_dur; +}; + struct udp_fd { int fd; u_int16_t localport; @@ -296,9 +323,13 @@ struct call_media { struct call_monologue { struct call *call; /* RO */ - str tag; + str tag; + enum tag_type tagtype; time_t created; /* RO */ time_t deleted; + struct timeval started; /* for CDR */ + struct timeval terminated; /* for CDR */ + enum termination_reason term_reason; GHashTable *other_tags; struct call_monologue *active_dialogue; @@ -328,6 +359,7 @@ struct call { time_t deleted; time_t ml_deleted; unsigned char tos; + char *created_from; }; struct local_interface { @@ -352,6 +384,7 @@ struct callmaster_config { int port_max; unsigned int timeout; unsigned int silent_timeout; + unsigned int delete_delay; struct redis *redis; char *b2b_url; unsigned char default_tos; @@ -376,6 +409,7 @@ struct callmaster { struct stats statsps; /* per second stats, running timer */ mutex_t statslock; struct stats stats; /* copied from statsps once a second */ + struct totalstats totalstats; struct poller *poller; pcre *info_re; @@ -410,6 +444,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); @@ -426,7 +461,10 @@ struct interface_address *get_interface_from_address(struct local_interface *lif const struct transport_protocol *transport_protocol(const str *s); - +void timeval_subtract (struct timeval *result, const struct timeval *a, const struct timeval *b); +void timeval_multiply(struct timeval *result, const struct timeval *a, const long multiplier); +void timeval_devide(struct timeval *result, const struct timeval *a, const long devisor); +void timeval_add(struct timeval *result, const struct timeval *a, const struct timeval *b); INLINE void *call_malloc(struct call *c, size_t l) { @@ -490,5 +528,6 @@ INLINE struct packet_stream *packet_stream_sink(struct packet_stream *ps) { return ret; } +const char * get_tag_type_text(char *buf, enum tag_type t); #endif diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index efd16f88a..73584180b 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -134,7 +134,7 @@ fail: return -1; } -static str *call_update_lookup_udp(char **out, struct callmaster *m, enum call_opmode opmode) { +static str *call_update_lookup_udp(char **out, struct callmaster *m, enum call_opmode opmode, const char* addr) { struct call *c; struct call_monologue *monologue; GQueue q = G_QUEUE_INIT; @@ -154,10 +154,21 @@ static str *call_update_lookup_udp(char **out, struct callmaster *m, enum call_o STR_FMT(&callid)); return str_sprintf("%s 0 0.0.0.0\n", out[RE_UDP_COOKIE]); } + + if (addr) { + c->created_from = call_strdup(c, addr); + } + monologue = call_get_mono_dialogue(c, &fromtag, &totag); if (!monologue) goto ml_fail; + if (!totag.s || totag.len==0) { + monologue->tagtype = FROM_TAG; + } else { + monologue->tagtype = TO_TAG; + } + if (addr_parse_udp(&sp, out)) goto addr_fail; @@ -174,6 +185,8 @@ static str *call_update_lookup_udp(char **out, struct callmaster *m, enum call_o redis_update(c, m->conf.redis); + gettimeofday(&(monologue->started), NULL); + ilog(LOG_INFO, "Returning to SIP proxy: "STR_FORMAT"", STR_FMT(ret)); goto out; @@ -194,11 +207,11 @@ out: return ret; } -str *call_update_udp(char **out, struct callmaster *m) { - return call_update_lookup_udp(out, m, OP_OFFER); +str *call_update_udp(char **out, struct callmaster *m, const char* addr) { + return call_update_lookup_udp(out, m, OP_OFFER, addr); } str *call_lookup_udp(char **out, struct callmaster *m) { - return call_update_lookup_udp(out, m, OP_ANSWER); + return call_update_lookup_udp(out, m, OP_ANSWER, NULL); } @@ -579,7 +592,7 @@ static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *inpu } static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster *m, - bencode_item_t *output, enum call_opmode opmode) + bencode_item_t *output, enum call_opmode opmode, const char* addr) { str sdp, fromtag, totag = STR_NULL, callid; char *errstr; @@ -618,6 +631,9 @@ static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster if (!call) goto out; + if (addr) { + call->created_from = call_strdup(call, addr); + } /* At least the random ICE strings are contained within the call struct, so we * need to hold a ref until we're done sending the reply */ call_bencode_hold_ref(call, output); @@ -630,6 +646,12 @@ static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster goto out; } + if (!totag.s || totag.len==0) { + monologue->tagtype = FROM_TAG; + } else { + monologue->tagtype = TO_TAG; + } + chopper = sdp_chopper_new(&sdp); bencode_buffer_destroy_add(output->buffer, (free_func_t) sdp_chopper_destroy, chopper); ret = monologue_offer_answer(monologue, &streams, &flags); @@ -640,6 +662,8 @@ static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster redis_update(call, m->conf.redis); obj_put(call); + gettimeofday(&(monologue->started), NULL); + errstr = "Error rewriting SDP"; if (ret) goto out; @@ -656,12 +680,12 @@ out: return errstr; } -const char *call_offer_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) { - return call_offer_answer_ng(input, m, output, OP_OFFER); +const char *call_offer_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output, const char* addr) { + return call_offer_answer_ng(input, m, output, OP_OFFER, addr); } const char *call_answer_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) { - return call_offer_answer_ng(input, m, output, OP_ANSWER); + return call_offer_answer_ng(input, m, output, OP_ANSWER, NULL); } const char *call_delete_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) { diff --git a/daemon/call_interfaces.h b/daemon/call_interfaces.h index b6df0dab2..209707f66 100644 --- a/daemon/call_interfaces.h +++ b/daemon/call_interfaces.h @@ -24,12 +24,12 @@ str *call_lookup_tcp(char **, struct callmaster *); void call_delete_tcp(char **, struct callmaster *); void calls_status_tcp(struct callmaster *, struct control_stream *); -str *call_update_udp(char **, struct callmaster *); +str *call_update_udp(char **, struct callmaster *, const char*); str *call_lookup_udp(char **, struct callmaster *); str *call_delete_udp(char **, struct callmaster *); str *call_query_udp(char **, struct callmaster *); -const char *call_offer_ng(bencode_item_t *, struct callmaster *, bencode_item_t *); +const char *call_offer_ng(bencode_item_t *, struct callmaster *, bencode_item_t *, const char*); const char *call_answer_ng(bencode_item_t *, struct callmaster *, bencode_item_t *); const char *call_delete_ng(bencode_item_t *, struct callmaster *, bencode_item_t *); const char *call_query_ng(bencode_item_t *, struct callmaster *, bencode_item_t *); diff --git a/daemon/cli.c b/daemon/cli.c new file mode 100644 index 000000000..162d6274d --- /dev/null +++ b/daemon/cli.c @@ -0,0 +1,366 @@ +#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_totals(char* buffer, int len, struct callmaster* m, char* replybuffer, const char* outbufend) { + int printlen=0; + printlen = snprintf(replybuffer,(outbufend-replybuffer), "\nTotal statistics (does not include current running sessions):\n\n"); + ADJUSTLEN(printlen,outbufend,replybuffer); + printlen = snprintf(replybuffer,(outbufend-replybuffer), " Uptime of rtpengine :%llu seconds\n", (unsigned long long)time(NULL)-m->totalstats.started); + ADJUSTLEN(printlen,outbufend,replybuffer); + printlen = snprintf(replybuffer,(outbufend-replybuffer), " Total managed sessions :%llu\n", (unsigned long long)m->totalstats.total_managed_sess); + ADJUSTLEN(printlen,outbufend,replybuffer); + printlen = snprintf(replybuffer,(outbufend-replybuffer), " Total timed-out sessions via TIMEOUT :%llu\n",(unsigned long long)m->totalstats.total_timeout_sess); + ADJUSTLEN(printlen,outbufend,replybuffer); + printlen = snprintf(replybuffer,(outbufend-replybuffer), " Total timed-out sessions via SILENT_TIMEOUT :%llu\n",(unsigned long long)m->totalstats.total_silent_timeout_sess); + ADJUSTLEN(printlen,outbufend,replybuffer); + printlen = snprintf(replybuffer,(outbufend-replybuffer), " Total regular terminated sessions :%llu\n",(unsigned long long)m->totalstats.total_regular_term_sess); + ADJUSTLEN(printlen,outbufend,replybuffer); + printlen = snprintf(replybuffer,(outbufend-replybuffer), " Total forced terminated sessions :%llu\n",(unsigned long long)m->totalstats.total_forced_term_sess); + ADJUSTLEN(printlen,outbufend,replybuffer); + printlen = snprintf(replybuffer,(outbufend-replybuffer), " Total relayed packets :%llu\n",(unsigned long long)m->totalstats.total_relayed_packets); + ADJUSTLEN(printlen,outbufend,replybuffer); + printlen = snprintf(replybuffer,(outbufend-replybuffer), " Total relayed packet errors :%llu\n",(unsigned long long)m->totalstats.total_relayed_errors); + ADJUSTLEN(printlen,outbufend,replybuffer); + printlen = snprintf(replybuffer,(outbufend-replybuffer), " Total number of streams with no relayed packets :%llu\n", (unsigned long long)m->totalstats.total_nopacket_relayed_sess); + ADJUSTLEN(printlen,outbufend,replybuffer); + printlen = snprintf(replybuffer,(outbufend-replybuffer), " Total number of 1-way streams :%llu\n",(unsigned long long)m->totalstats.total_oneway_stream_sess); + ADJUSTLEN(printlen,outbufend,replybuffer); + printlen = snprintf(replybuffer,(outbufend-replybuffer), " Average call duration :%ld.%06ld\n\n",m->totalstats.total_average_call_dur.tv_sec,m->totalstats.total_average_call_dur.tv_usec); + ADJUSTLEN(printlen,outbufend,replybuffer); +} + +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; + char tagtypebuf[16]; memset(&tagtypebuf,0,16); + struct timeval tim_result_duration; memset(&tim_result_duration,0,sizeof(struct timeval)); + struct timeval now; memset(&now,0,sizeof(struct timeval)); + + 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 | proxy:%s\n\n", c->callid.s , c->ml_deleted?"yes":"no", (int)c->created, c->created_from); + ADJUSTLEN(printlen,outbufend,replybuffer); + + for (l = c->monologues; l; l = l->next) { + ml = l->data; + if (!ml->terminated.tv_sec) { + gettimeofday(&now, NULL); + } else { + now = ml->terminated; + } + timeval_subtract(&tim_result_duration,&now,&ml->started); + printlen = snprintf(replybuffer,(outbufend-replybuffer), "--- Tag '"STR_FORMAT"' type: %s, callduration " + "%ld.%06ld , in dialogue with '"STR_FORMAT"'\n", + STR_FMT(&ml->tag), get_tag_type_text(tagtypebuf,ml->tagtype), + tim_result_duration.tv_sec, + tim_result_duration.tv_usec, + 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"; + static const char* LIST_TOTALS = "totals"; + + 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 | proxy:%s\n", ptrkey->s, call->ml_deleted?"yes":"no", (int)call->created, call->created_from); + 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 if (len>=strlen(LIST_TOTALS) && strncmp(buffer,LIST_TOTALS,strlen(LIST_TOTALS)) == 0) { + cli_incoming_list_totals(buffer+strlen(LIST_TOTALS), len-strlen(LIST_TOTALS), 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; + struct call_monologue *ml; + GSList *i; + + 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; + if (!c->ml_deleted) { + for (i = c->monologues; i; i = i->next) { + ml = i->data; + memset(&ml->terminated,0,sizeof(struct timeval)); + gettimeofday(&(ml->terminated), NULL); + ml->term_reason = FORCED; + } + } + 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; + } + + if (!c->ml_deleted) { + for (i = c->monologues; i; i = i->next) { + ml = i->data; + memset(&ml->terminated,0,sizeof(struct timeval)); + gettimeofday(&(ml->terminated), NULL); + ml->term_reason = FORCED; + } + } + 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/control_ng.c b/daemon/control_ng.c index 9b800ae2d..afe5a91df 100644 --- a/daemon/control_ng.c +++ b/daemon/control_ng.c @@ -111,7 +111,7 @@ static void control_ng_incoming(struct obj *obj, str *buf, struct sockaddr_in6 * if (!str_cmp(&cmd, "ping")) bencode_dictionary_add_string(resp, "result", "pong"); else if (!str_cmp(&cmd, "offer")) - errstr = call_offer_ng(dict, c->callmaster, resp); + errstr = call_offer_ng(dict, c->callmaster, resp, addr); else if (!str_cmp(&cmd, "answer")) errstr = call_answer_ng(dict, c->callmaster, resp); else if (!str_cmp(&cmd, "delete")) diff --git a/daemon/control_udp.c b/daemon/control_udp.c index b00e9d439..bab804142 100644 --- a/daemon/control_udp.c +++ b/daemon/control_udp.c @@ -83,7 +83,7 @@ static void control_udp_incoming(struct obj *obj, str *buf, struct sockaddr_in6 } if (chrtoupper(out[RE_UDP_UL_CMD][0]) == 'U') - reply = call_update_udp(out, u->callmaster); + reply = call_update_udp(out, u->callmaster, addr); else if (chrtoupper(out[RE_UDP_UL_CMD][0]) == 'L') reply = call_lookup_udp(out, u->callmaster); else if (chrtoupper(out[RE_UDP_DQ_CMD][0]) == 'D') diff --git a/daemon/log.c b/daemon/log.c index 94d9f6421..a46b5a9bb 100644 --- a/daemon/log.c +++ b/daemon/log.c @@ -57,6 +57,7 @@ static const char* const prio_str[] = { gboolean _log_stderr = 0; int _log_facility = LOG_DAEMON; +int _log_facility_cdr = 0; static GHashTable *__log_limiter; @@ -167,6 +168,13 @@ out: free(msg); } +void cdrlog(const char* cdrbuffer) { + int previous; + int mask = LOG_MASK (LOG_INFO); + previous = setlogmask(mask); + syslog(LOG_INFO | _log_facility_cdr, "%s", cdrbuffer); + setlogmask(previous); +} void log_init() { mutex_init(&__log_limiter_lock); diff --git a/daemon/log.h b/daemon/log.h index d837a4b60..9d09eba14 100644 --- a/daemon/log.h +++ b/daemon/log.h @@ -22,6 +22,7 @@ struct log_info { extern gboolean _log_stderr; extern int _log_facility; +extern int _log_facility_cdr; typedef struct _fac_code { @@ -46,14 +47,11 @@ extern unsigned int max_log_line_length; void log_init(void); void ilog(int prio, const char *fmt, ...)__attribute__ ((format (printf, 2, 3))); - +void cdrlog(const char* cdrbuffer); #include "obj.h" - - - INLINE void log_info_clear() { switch (log_info.e) { case LOG_INFO_NONE: diff --git a/daemon/main.c b/daemon/main.c index 16f4786ee..0c9dbc26f 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; @@ -104,6 +107,7 @@ static int redis_db = -1; static char *b2b_url; static enum xmlrpc_format xmlrpc_fmt = XF_SEMS; static int num_threads; +static int delete_delay = 30; static void sighandler(gpointer x) { @@ -309,8 +313,10 @@ 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; int version = 0; int sip_source = 0; @@ -322,6 +328,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" }, @@ -334,9 +341,11 @@ static void options(int *argc, char ***argv) { { "b2b-url", 'b', 0, G_OPTION_ARG_STRING, &b2b_url, "XMLRPC URL of B2B UA" , "STRING" }, { "log-level", 'L', 0, G_OPTION_ARG_INT, (void *)&log_level,"Mask log priorities above this level","INT" }, { "log-facility",0, 0, G_OPTION_ARG_STRING, &log_facility_s, "Syslog facility to use for logging", "daemon|local0|...|local7"}, + { "log-facility-cdr",0, 0, G_OPTION_ARG_STRING, &log_facility_cdr_s, "Syslog facility to use for logging CDRs", "daemon|local0|...|local7"}, { "log-stderr", 'E', 0, G_OPTION_ARG_NONE, &_log_stderr, "Log on stderr instead of syslog", NULL }, { "xmlrpc-format",'x', 0, G_OPTION_ARG_INT, &xmlrpc_fmt, "XMLRPC timeout request format to use. 0: SEMS DI, 1: call-id only", "INT" }, { "num-threads", 0, 0, G_OPTION_ARG_INT, &num_threads, "Number of worker threads to create", "INT" }, + { "delete-delay", 'd', 0, G_OPTION_ARG_INT, &delete_delay, "Delay for deleting a session from memory.", "INT" }, { "sip-source", 0, 0, G_OPTION_ARG_NONE, &sip_source, "Use SIP source address by default", NULL }, { "dtls-passive", 0, 0, G_OPTION_ARG_NONE, &dtls_passive_def,"Always prefer DTLS passive role", NULL }, { NULL, } @@ -378,6 +387,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"); @@ -407,6 +420,13 @@ static void options(int *argc, char ***argv) { } } + if (log_facility_cdr_s) { + if (!parse_log_facility(log_facility_cdr_s, &_log_facility_cdr)) { + print_available_log_facilities(); + die ("Invalid log facility for CDR '%s' (--log-facility-cdr)\n", log_facility_cdr_s); + } + } + if (_log_stderr) { write_log = log_to_stderr; max_log_line_length = 0; @@ -548,6 +568,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; @@ -589,6 +610,7 @@ no_kernel: mc.port_max = port_max; mc.timeout = timeout; mc.silent_timeout = silent_timeout; + mc.delete_delay = delete_delay; mc.default_tos = tos; mc.b2b_url = b2b_url; mc.fmt = xmlrpc_fmt; @@ -616,6 +638,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) @@ -635,6 +665,9 @@ no_kernel: ctx->m->conf = mc; callmaster_config_init(ctx->m); + ZERO(ctx->m->totalstats); + ctx->m->totalstats.started = time(NULL); + if (!foreground) daemonize(); wpidfile(); diff --git a/debian/control b/debian/control index 6d5c8123e..9a21efdd1 100644 --- a/debian/control +++ b/debian/control @@ -22,6 +22,7 @@ Depends: ${misc:Depends}, ${shlibs:Depends} Conflicts: ngcp-mediaproxy-ng-daemon Replaces: ngcp-mediaproxy-ng-daemon +Recommends: netcat-openbsd | netcat Description: Proxy for RTP and media streams used in NGCP, userspace part. This daemon handles the first stages of proxying media streams and talks to the kernel part of the proxy for eventual high-performance packet forwarding. diff --git a/debian/ngcp-rtpengine-daemon.default b/debian/ngcp-rtpengine-daemon.default index 74c8a44c1..20b659cb5 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" @@ -19,4 +20,6 @@ TABLE=0 # B2B_URL=http://127.0.0.1:8090/ # LOG_LEVEL=6 # LOG_FACILITY=daemon +# LOG_FACILITY_CDR=daemon # NUM_THREADS=5 +# DELETE_DELAY=30 \ No newline at end of file diff --git a/debian/ngcp-rtpengine-daemon.init b/debian/ngcp-rtpengine-daemon.init index d68cf0d4a..651e17f6e 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" @@ -68,7 +69,9 @@ fi OPTIONS="$OPTIONS --table=$TABLE" [ -z "$LOG_LEVEL" ] || OPTIONS="$OPTIONS --log-level=$LOG_LEVEL" [ -z "$LOG_FACILITY" ] || OPTIONS="$OPTIONS --log-facility=$LOG_FACILITY" +[ -z "$LOG_FACILITY_CDR" ] || OPTIONS="$OPTIONS --log-facility-cdr=$LOG_FACILITY_CDR" [ -z "$NUM_THREADS" ] || OPTIONS="$OPTIONS --num-threads=$NUM_THREADS" +[ -z "$DELETE_DELAY" ] || OPTIONS="$OPTIONS --delete-delay=$DELETE_DELAY" if test "$FORK" = "no" ; then OPTIONS="$OPTIONS --foreground" fi 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 2a4c94d4d..f515d5341 100644 --- a/el/rtpengine.init +++ b/el/rtpengine.init @@ -78,6 +78,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" @@ -132,6 +137,16 @@ build_opts() { then OPTS+=" --num-threads=$NUM_THREADS" fi + + if [[ -n "$DELETE_DELAY" ]] + then + OPTS+=" --delete-delay=$DELETE_DELAY" + fi + + if [[ -n "$LOG_FACILITY_CDR" ]] + then + OPTS+=" --log-facility-cdr=$LOG_FACILITY_CDR" + fi } start() { diff --git a/el/rtpengine.sysconfig b/el/rtpengine.sysconfig index a70be46fe..43c2dfaf8 100644 --- a/el/rtpengine.sysconfig +++ b/el/rtpengine.sysconfig @@ -17,6 +17,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 @@ -31,7 +32,9 @@ LISTEN_UDP=127.0.0.1:2222 # IP address and port combination for UDP # #LOG_LEVEL=6 # Log level to use #LOG_FACILITY=daemon # Syslog facility to use +#LOG_FACILITY_CDR=daemon # Syslog facility to write CDRs #NUM_THREADS=5 # How many worker threads to launch +#DELETE_DELAY=30 # Delay to delete session from memory # The following items are for use with NGCP #REDIS=127.0.0.1:6379 diff --git a/utils/rtpengine-ctl b/utils/rtpengine-ctl new file mode 100755 index 000000000..e69deb12c --- /dev/null +++ b/utils/rtpengine-ctl @@ -0,0 +1,70 @@ +#!/bin/bash +# + +host=127.0.0.1 +port=9900 +error_rc=255 + +prgname=${0##*/} +prgdir=${0%$prgname} + +showusage() { + echo "" + echo " $0 [ -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 " totals : print total statistics (does not include current sessions)" + 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: $0 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}