|
|
|
@ -10,7 +10,7 @@ |
|
|
|
#include <arpa/inet.h> |
|
|
|
#include <assert.h> |
|
|
|
#include <errno.h> |
|
|
|
#include <stdlib.h> |
|
|
|
#include <stdbool.h> |
|
|
|
#include <time.h> |
|
|
|
#include <xmlrpc_client.h> |
|
|
|
#include <sys/wait.h> |
|
|
|
@ -378,7 +378,7 @@ fault: |
|
|
|
void kill_calls_timer(GSList *list, const char *url) { |
|
|
|
struct call *ca; |
|
|
|
GList *csl; |
|
|
|
struct call_monologue *cm, *cd; |
|
|
|
struct call_monologue *cm; |
|
|
|
char *url_prefix = NULL, *url_suffix = NULL; |
|
|
|
struct xmlrpc_helper *xh = NULL; |
|
|
|
char url_buf[128]; |
|
|
|
@ -445,25 +445,32 @@ void kill_calls_timer(GSList *list, const char *url) { |
|
|
|
case XF_KAMAILIO: |
|
|
|
for (csl = ca->monologues.head; csl; csl = csl->next) { |
|
|
|
cm = csl->data; |
|
|
|
cd = cm->active_dialogue; |
|
|
|
if (!cm->tag.s || !cm->tag.len || !cd || !cd->tag.s || !cd->tag.len) |
|
|
|
if (!cm->tag.s || !cm->tag.len) |
|
|
|
continue; |
|
|
|
|
|
|
|
str *from_tag = g_hash_table_lookup(dup_tags, &cd->tag); |
|
|
|
if (from_tag && !str_cmp_str(from_tag, &cm->tag)) |
|
|
|
continue; |
|
|
|
for (GList *sub = cm->subscribers.head; sub; sub = sub->next) { |
|
|
|
struct call_subscription *cs = sub->data; |
|
|
|
struct call_monologue *cd = cs->monologue; |
|
|
|
|
|
|
|
if (!cd->tag.s || !cd->tag.len) |
|
|
|
continue; |
|
|
|
|
|
|
|
from_tag = str_dup(&cm->tag); |
|
|
|
str *to_tag = str_dup(&cd->tag); |
|
|
|
str *from_tag = g_hash_table_lookup(dup_tags, &cd->tag); |
|
|
|
if (from_tag && !str_cmp_str(from_tag, &cm->tag)) |
|
|
|
continue; |
|
|
|
|
|
|
|
g_queue_push_tail(&xh->strings, |
|
|
|
strdup(url_buf)); |
|
|
|
g_queue_push_tail(&xh->strings, |
|
|
|
str_dup(&ca->callid)); |
|
|
|
g_queue_push_tail(&xh->strings, from_tag); |
|
|
|
g_queue_push_tail(&xh->strings, to_tag); |
|
|
|
from_tag = str_dup(&cm->tag); |
|
|
|
str *to_tag = str_dup(&cd->tag); |
|
|
|
|
|
|
|
g_hash_table_insert(dup_tags, from_tag, to_tag); |
|
|
|
g_queue_push_tail(&xh->strings, |
|
|
|
strdup(url_buf)); |
|
|
|
g_queue_push_tail(&xh->strings, |
|
|
|
str_dup(&ca->callid)); |
|
|
|
g_queue_push_tail(&xh->strings, from_tag); |
|
|
|
g_queue_push_tail(&xh->strings, to_tag); |
|
|
|
|
|
|
|
g_hash_table_insert(dup_tags, from_tag, to_tag); |
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
@ -521,7 +528,7 @@ static void call_timer(void *ptr) { |
|
|
|
struct iterator_helper hlp; |
|
|
|
GList *i, *l; |
|
|
|
struct rtpengine_list_entry *ke; |
|
|
|
struct packet_stream *ps, *sink; |
|
|
|
struct packet_stream *ps; |
|
|
|
struct stats tmpstats; |
|
|
|
int j, update; |
|
|
|
struct stream_fd *sfd; |
|
|
|
@ -639,12 +646,17 @@ static void call_timer(void *ptr) { |
|
|
|
if (diff_packets) |
|
|
|
sfd->call->foreign_media = 0; |
|
|
|
|
|
|
|
sink = packet_stream_sink(ps); |
|
|
|
|
|
|
|
if (!ke->target.non_forwarding && diff_packets) { |
|
|
|
// only check the first |
|
|
|
struct rtpengine_output_info *o = &ke->outputs[0]; |
|
|
|
if (sink && o->src_addr.family) { |
|
|
|
for (GList *l = ps->rtp_sinks.head; l; l = l->next) { |
|
|
|
struct sink_handler *sh = l->data; |
|
|
|
struct packet_stream *sink = sh->sink; |
|
|
|
|
|
|
|
if (sh->kernel_output_idx < 0 |
|
|
|
|| sh->kernel_output_idx >= ke->target.num_destinations) |
|
|
|
continue; |
|
|
|
|
|
|
|
struct rtpengine_output_info *o = &ke->outputs[sh->kernel_output_idx]; |
|
|
|
|
|
|
|
mutex_lock(&sink->out_lock); |
|
|
|
if (sink->crypto.params.crypto_suite && sink->ssrc_out |
|
|
|
&& ntohl(ke->target.ssrc) == sink->ssrc_out->parent->h.ssrc |
|
|
|
@ -1179,6 +1191,28 @@ void __rtp_stats_update(GHashTable *dst, struct codec_store *cs) { |
|
|
|
/* we leave previously added but now removed payload types in place */ |
|
|
|
} |
|
|
|
|
|
|
|
void free_sink_handler(void *p) { |
|
|
|
struct sink_handler *sh = p; |
|
|
|
g_slice_free1(sizeof(*sh), sh); |
|
|
|
} |
|
|
|
void __add_sink_handler(GQueue *q, struct packet_stream *sink) { |
|
|
|
struct sink_handler *sh = g_slice_alloc0(sizeof(*sh)); |
|
|
|
sh->sink = sink; |
|
|
|
sh->kernel_output_idx = -1; |
|
|
|
g_queue_push_tail(q, sh); |
|
|
|
} |
|
|
|
|
|
|
|
// called once before calling __init_streams once for each sink |
|
|
|
static void __reset_streams(struct call_media *media) { |
|
|
|
for (GList *l = media->streams.head; l; l = l->next) { |
|
|
|
struct packet_stream *ps = l->data; |
|
|
|
g_queue_clear_full(&ps->rtp_sinks, free_sink_handler); |
|
|
|
g_queue_clear_full(&ps->rtcp_sinks, free_sink_handler); |
|
|
|
} |
|
|
|
} |
|
|
|
// called once on media A for each sink media B |
|
|
|
// B can be NULL |
|
|
|
// XXX this function seems to do two things - stream init (with B NULL) and sink init - split up? |
|
|
|
static int __init_streams(struct call_media *A, struct call_media *B, const struct stream_params *sp, |
|
|
|
const struct sdp_ng_flags *flags) { |
|
|
|
GList *la, *lb; |
|
|
|
@ -1194,11 +1228,13 @@ static int __init_streams(struct call_media *A, struct call_media *B, const stru |
|
|
|
b = lb->data; |
|
|
|
|
|
|
|
/* RTP */ |
|
|
|
a->rtp_sink = b; |
|
|
|
// reflect media - pretent reflection also for blackhole, as otherwise |
|
|
|
// reflect media - pretend reflection also for blackhole, as otherwise |
|
|
|
// we get SSRC flip-flops on the opposite side |
|
|
|
// XXX still necessary for blackhole? |
|
|
|
if (MEDIA_ISSET(A, ECHO) || MEDIA_ISSET(A, BLACKHOLE)) |
|
|
|
a->rtp_sink = a; |
|
|
|
__add_sink_handler(&a->rtp_sinks, a); |
|
|
|
else |
|
|
|
__add_sink_handler(&a->rtp_sinks, b); |
|
|
|
PS_SET(a, RTP); /* XXX technically not correct, could be udptl too */ |
|
|
|
|
|
|
|
__rtp_stats_update(a->rtp_stats, &A->codecs); |
|
|
|
@ -1227,14 +1263,13 @@ static int __init_streams(struct call_media *A, struct call_media *B, const stru |
|
|
|
b = lb->data; |
|
|
|
} |
|
|
|
|
|
|
|
if (!MEDIA_ISSET(A, RTCP_MUX)) { |
|
|
|
a->rtcp_sink = NULL; |
|
|
|
if (!MEDIA_ISSET(A, RTCP_MUX)) |
|
|
|
PS_CLEAR(a, RTCP); |
|
|
|
} |
|
|
|
else { |
|
|
|
a->rtcp_sink = b; |
|
|
|
if (MEDIA_ISSET(A, ECHO) || MEDIA_ISSET(A, BLACKHOLE)) |
|
|
|
a->rtcp_sink = a->rtcp_sibling; |
|
|
|
__add_sink_handler(&a->rtcp_sinks, a->rtcp_sibling); |
|
|
|
else |
|
|
|
__add_sink_handler(&a->rtcp_sinks, b); |
|
|
|
PS_SET(a, RTCP); |
|
|
|
PS_CLEAR(a, IMPLICIT_RTCP); |
|
|
|
} |
|
|
|
@ -1247,10 +1282,10 @@ static int __init_streams(struct call_media *A, struct call_media *B, const stru |
|
|
|
assert(la != NULL); |
|
|
|
a = la->data; |
|
|
|
|
|
|
|
a->rtp_sink = NULL; |
|
|
|
a->rtcp_sink = b; |
|
|
|
if (MEDIA_ISSET(A, ECHO) || MEDIA_ISSET(A, BLACKHOLE)) |
|
|
|
a->rtcp_sink = a; |
|
|
|
__add_sink_handler(&a->rtcp_sinks, a); |
|
|
|
else |
|
|
|
__add_sink_handler(&a->rtcp_sinks, b); |
|
|
|
PS_CLEAR(a, RTP); |
|
|
|
PS_SET(a, RTCP); |
|
|
|
a->rtcp_sibling = NULL; |
|
|
|
@ -2211,16 +2246,68 @@ void codecs_offer_answer(struct call_media *media, struct call_media *other_medi |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* called with call->master_lock held in W */ |
|
|
|
int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams, |
|
|
|
static void __update_init_subscribers(struct call_monologue *ml, GQueue *streams, struct sdp_ng_flags *flags) { |
|
|
|
GList *sl = streams ? streams->head : NULL; |
|
|
|
|
|
|
|
// create media iterators for all subscribers |
|
|
|
GList *sub_medias[ml->subscribers.length]; |
|
|
|
unsigned int num_subs = 0; |
|
|
|
for (GList *l = ml->subscribers.head; l; l = l->next) { |
|
|
|
struct call_subscription *cs = l->data; |
|
|
|
struct call_monologue *sub_ml = cs->monologue; |
|
|
|
sub_medias[num_subs++] = sub_ml->medias.head; |
|
|
|
} |
|
|
|
// keep num_subs as shortcut to ml->subscribers.length |
|
|
|
|
|
|
|
for (GList *l = ml->medias.head; l; l = l->next) { |
|
|
|
struct call_media *media = l->data; |
|
|
|
|
|
|
|
struct stream_params *sp = NULL; |
|
|
|
if (sl) { |
|
|
|
sp = sl->data; |
|
|
|
sl = sl->next; |
|
|
|
} |
|
|
|
|
|
|
|
__ice_start(media); |
|
|
|
|
|
|
|
// update all subscribers |
|
|
|
__reset_streams(media); |
|
|
|
for (unsigned int i = 0; i < num_subs; i++) { |
|
|
|
if (!sub_medias[i]) |
|
|
|
continue; |
|
|
|
|
|
|
|
struct call_media *sub_media = sub_medias[i]->data; |
|
|
|
sub_medias[i] = sub_medias[i]->next; |
|
|
|
|
|
|
|
if (__init_streams(media, sub_media, sp, flags)) |
|
|
|
ilog(LOG_WARN, "Error initialising streams"); |
|
|
|
} |
|
|
|
|
|
|
|
// we are now ready to fire up ICE if so desired and requested |
|
|
|
ice_update(media->ice_agent, sp); // sp == NULL: update in case rtcp-mux changed |
|
|
|
|
|
|
|
recording_setup_media(media); |
|
|
|
t38_gateway_start(media->t38_gateway); |
|
|
|
|
|
|
|
if (mqtt_publish_scope() == MPS_MEDIA) |
|
|
|
mqtt_timer_start(&media->mqtt_timer, media->call, media); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* called with call->master_lock held in W */ |
|
|
|
int monologue_offer_answer(struct call_monologue *dialogue[2], GQueue *streams, |
|
|
|
struct sdp_ng_flags *flags) |
|
|
|
{ |
|
|
|
struct stream_params *sp; |
|
|
|
GList *media_iter, *ml_media, *other_ml_media; |
|
|
|
struct call_media *media, *other_media; |
|
|
|
struct call_monologue *monologue; |
|
|
|
struct endpoint_map *em; |
|
|
|
struct call *call; |
|
|
|
struct call_monologue *other_ml = dialogue[0]; |
|
|
|
struct call_monologue *monologue = dialogue[1]; |
|
|
|
|
|
|
|
/* we must have a complete dialogue, even though the to-tag (monologue->tag) |
|
|
|
* may not be known yet */ |
|
|
|
@ -2229,7 +2316,6 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams, |
|
|
|
return -1; |
|
|
|
} |
|
|
|
|
|
|
|
monologue = other_ml->active_dialogue; |
|
|
|
call = monologue->call; |
|
|
|
|
|
|
|
call->last_signal = MAX(call->last_signal, rtpe_now.tv_sec); |
|
|
|
@ -2432,9 +2518,6 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams, |
|
|
|
|
|
|
|
/* ICE stuff - must come after interface and address family selection */ |
|
|
|
__ice_offer(flags, media, other_media); |
|
|
|
__ice_start(other_media); |
|
|
|
__ice_start(media); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* we now know what's being advertised by the other side */ |
|
|
|
@ -2448,7 +2531,7 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams, |
|
|
|
* generate media (or RTCP packets) for that stream. */ |
|
|
|
__disable_streams(media, sp->num_ports); |
|
|
|
__disable_streams(other_media, sp->num_ports); |
|
|
|
goto init; |
|
|
|
continue; |
|
|
|
} |
|
|
|
if (is_addr_unspecified(&sp->rtp_endpoint.address) && !MEDIA_ISSET(other_media, TRICKLE_ICE)) { |
|
|
|
/* Zero endpoint address, equivalent to setting the media stream |
|
|
|
@ -2478,26 +2561,11 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams, |
|
|
|
if (__wildcard_endpoint_map(other_media, sp->num_ports)) |
|
|
|
goto error_ports; |
|
|
|
} |
|
|
|
|
|
|
|
init: |
|
|
|
if (__init_streams(media, other_media, NULL, NULL)) |
|
|
|
return -1; |
|
|
|
if (__init_streams(other_media, media, sp, flags)) |
|
|
|
return -1; |
|
|
|
|
|
|
|
/* we are now ready to fire up ICE if so desired and requested */ |
|
|
|
ice_update(other_media->ice_agent, sp); |
|
|
|
ice_update(media->ice_agent, NULL); /* this is in case rtcp-mux has changed */ |
|
|
|
|
|
|
|
recording_setup_media(media); |
|
|
|
t38_gateway_start(media->t38_gateway); |
|
|
|
|
|
|
|
if (mqtt_publish_scope() == MPS_MEDIA) { |
|
|
|
mqtt_timer_start(&media->mqtt_timer, call, media); |
|
|
|
mqtt_timer_start(&other_media->mqtt_timer, call, other_media); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
__update_init_subscribers(other_ml, streams, flags); |
|
|
|
__update_init_subscribers(monologue, NULL, NULL); |
|
|
|
|
|
|
|
// set ipv4/ipv6/mixed media stats |
|
|
|
if (flags && (flags->opmode == OP_OFFER || flags->opmode == OP_ANSWER)) { |
|
|
|
statistics_update_ip46_inc_dec(call, CMC_INCREMENT); |
|
|
|
@ -2515,6 +2583,73 @@ error_intf: |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void __unsubscribe_one_link(struct call_monologue *which, GList *which_cs_link) { |
|
|
|
struct call_subscription *cs = which_cs_link->data; |
|
|
|
struct call_subscription *rev_cs = cs->link->data; |
|
|
|
struct call_monologue *from = cs->monologue; |
|
|
|
ilog(LOG_DEBUG, "Unsubscribing '" STR_FORMAT_M "' from '" STR_FORMAT_M "'", |
|
|
|
STR_FMT_M(&which->tag), |
|
|
|
STR_FMT_M(&from->tag)); |
|
|
|
g_queue_delete_link(&from->subscribers, cs->link); |
|
|
|
g_queue_delete_link(&which->subscriptions, which_cs_link); |
|
|
|
g_slice_free1(sizeof(*cs), cs); |
|
|
|
g_slice_free1(sizeof(*rev_cs), rev_cs); |
|
|
|
} |
|
|
|
//static void __unsubscribe_all(struct call_monologue *which) { |
|
|
|
// while (which->subscriptions.head) |
|
|
|
// __unsubscribe_one_link(which, which->subscriptions.head); |
|
|
|
//} |
|
|
|
static bool __unsubscribe_one(struct call_monologue *which, struct call_monologue *from) { |
|
|
|
for (GList *l = which->subscriptions.head; l; l = l->next) { |
|
|
|
struct call_subscription *cs = l->data; |
|
|
|
if (cs->monologue != from) |
|
|
|
continue; |
|
|
|
__unsubscribe_one_link(which, l); |
|
|
|
return true; |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
static void __unsubscribe_all_offer_answer(struct call_monologue *ml) { |
|
|
|
for (GList *l = ml->subscriptions.head; l; ) { |
|
|
|
struct call_subscription *cs = l->data; |
|
|
|
if (!cs->offer_answer) { |
|
|
|
l = l->next; |
|
|
|
continue; |
|
|
|
} |
|
|
|
GList *next = l->next; |
|
|
|
__unsubscribe_one_link(ml, l); |
|
|
|
l = next; |
|
|
|
} |
|
|
|
} |
|
|
|
void __add_subscription(struct call_monologue *which, struct call_monologue *to, bool offer_answer) { |
|
|
|
ilog(LOG_DEBUG, "Subscribing '" STR_FORMAT_M "' to '" STR_FORMAT_M "'", |
|
|
|
STR_FMT_M(&which->tag), |
|
|
|
STR_FMT_M(&to->tag)); |
|
|
|
struct call_subscription *which_cs = g_slice_alloc0(sizeof(*which_cs)); |
|
|
|
struct call_subscription *to_rev_cs = g_slice_alloc0(sizeof(*to_rev_cs)); |
|
|
|
which_cs->monologue = to; |
|
|
|
to_rev_cs->monologue = which; |
|
|
|
// keep offer-answer subscriptions first in the list |
|
|
|
if (!offer_answer) { |
|
|
|
g_queue_push_tail(&which->subscriptions, which_cs); |
|
|
|
g_queue_push_tail(&to->subscribers, to_rev_cs); |
|
|
|
which_cs->link = to->subscribers.tail; |
|
|
|
to_rev_cs->link = which->subscriptions.tail; |
|
|
|
} |
|
|
|
else { |
|
|
|
g_queue_push_head(&which->subscriptions, which_cs); |
|
|
|
g_queue_push_head(&to->subscribers, to_rev_cs); |
|
|
|
which_cs->link = to->subscribers.head; |
|
|
|
to_rev_cs->link = which->subscriptions.head; |
|
|
|
} |
|
|
|
which_cs->offer_answer = offer_answer ? 1 : 0; |
|
|
|
to_rev_cs->offer_answer = which_cs->offer_answer; |
|
|
|
} |
|
|
|
static void __subscribe_only_one_offer_answer(struct call_monologue *which, struct call_monologue *to) { |
|
|
|
__unsubscribe_all_offer_answer(which); |
|
|
|
__add_subscription(which, to, true); |
|
|
|
} |
|
|
|
|
|
|
|
static int __rtp_stats_sort(const void *ap, const void *bp) { |
|
|
|
const struct rtp_stats *a = ap, *b = bp; |
|
|
|
|
|
|
|
@ -2602,8 +2737,8 @@ static void __call_cleanup(struct call *c) { |
|
|
|
g_queue_clear(&ps->sfds); |
|
|
|
crypto_cleanup(&ps->crypto); |
|
|
|
|
|
|
|
ps->rtp_sink = NULL; |
|
|
|
ps->rtcp_sink = NULL; |
|
|
|
g_queue_clear_full(&ps->rtp_sinks, free_sink_handler); |
|
|
|
g_queue_clear_full(&ps->rtcp_sinks, free_sink_handler); |
|
|
|
} |
|
|
|
|
|
|
|
for (GList *l = c->medias.head; l; l = l->next) { |
|
|
|
@ -2718,18 +2853,21 @@ void call_destroy(struct call *c) { |
|
|
|
// stats output only - no cleanups |
|
|
|
|
|
|
|
ilog(LOG_INFO, "--- Tag '" STR_FORMAT_M "'%s"STR_FORMAT"%s, created " |
|
|
|
"%u:%02u ago for branch '" STR_FORMAT_M "', in dialogue with '" STR_FORMAT_M "'", |
|
|
|
"%u:%02u ago for branch '" STR_FORMAT_M "'", |
|
|
|
STR_FMT_M(&ml->tag), |
|
|
|
ml->label.s ? " (label '" : "", |
|
|
|
STR_FMT(ml->label.s ? &ml->label : &STR_EMPTY), |
|
|
|
ml->label.s ? "')" : "", |
|
|
|
(unsigned int) (rtpe_now.tv_sec - ml->created) / 60, |
|
|
|
(unsigned int) (rtpe_now.tv_sec - ml->created) % 60, |
|
|
|
STR_FMT_M(&ml->viabranch), |
|
|
|
ml->active_dialogue ? rtpe_common_config_ptr->log_mark_prefix : "", |
|
|
|
ml->active_dialogue ? (int) ml->active_dialogue->tag.len : 6, |
|
|
|
ml->active_dialogue ? ml->active_dialogue->tag.s : "(none)", |
|
|
|
ml->active_dialogue ? rtpe_common_config_ptr->log_mark_suffix : ""); |
|
|
|
STR_FMT_M(&ml->viabranch)); |
|
|
|
|
|
|
|
for (GList *sub = ml->subscriptions.head; sub; sub = sub->next) { |
|
|
|
struct call_subscription *cs = sub->data; |
|
|
|
struct call_monologue *csm = cs->monologue; |
|
|
|
ilog(LOG_INFO, "--- subscribed to '" STR_FORMAT_M "'", |
|
|
|
STR_FMT_M(&csm->tag)); |
|
|
|
} |
|
|
|
|
|
|
|
for (k = ml->medias.head; k; k = k->next) { |
|
|
|
md = k->data; |
|
|
|
@ -3073,7 +3211,6 @@ void __monologue_tag(struct call_monologue *ml, const str *tag) { |
|
|
|
} |
|
|
|
void __monologue_viabranch(struct call_monologue *ml, const str *viabranch) { |
|
|
|
struct call *call = ml->call; |
|
|
|
struct call_monologue *other = ml->active_dialogue; |
|
|
|
|
|
|
|
if (!viabranch || !viabranch->len) |
|
|
|
return; |
|
|
|
@ -3081,15 +3218,25 @@ void __monologue_viabranch(struct call_monologue *ml, const str *viabranch) { |
|
|
|
__C_DBG("tagging monologue with viabranch '"STR_FORMAT"'", STR_FMT(viabranch)); |
|
|
|
if (ml->viabranch.s) { |
|
|
|
g_hash_table_remove(call->viabranches, &ml->viabranch); |
|
|
|
if (other) |
|
|
|
g_hash_table_remove(other->branches, &ml->viabranch); |
|
|
|
for (GList *sub = ml->subscribers.head; sub; sub = sub->next) { |
|
|
|
struct call_subscription *cs = sub->data; |
|
|
|
g_hash_table_remove(cs->monologue->branches, &ml->viabranch); |
|
|
|
} |
|
|
|
} |
|
|
|
call_str_cpy(call, &ml->viabranch, viabranch); |
|
|
|
g_hash_table_insert(call->viabranches, &ml->viabranch, ml); |
|
|
|
if (other) |
|
|
|
g_hash_table_insert(other->branches, &ml->viabranch, ml); |
|
|
|
for (GList *sub = ml->subscribers.head; sub; sub = sub->next) { |
|
|
|
struct call_subscription *cs = sub->data; |
|
|
|
g_hash_table_insert(cs->monologue->branches, &ml->viabranch, ml); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void __unconfirm_sinks(GQueue *q) { |
|
|
|
for (GList *l = q->head; l; l = l->next) { |
|
|
|
struct sink_handler *sh = l->data; |
|
|
|
__stream_unconfirm(sh->sink); |
|
|
|
} |
|
|
|
} |
|
|
|
/* must be called with call->master_lock held in W */ |
|
|
|
void __monologue_unkernelize(struct call_monologue *monologue) { |
|
|
|
GList *l, *m; |
|
|
|
@ -3108,14 +3255,30 @@ void __monologue_unkernelize(struct call_monologue *monologue) { |
|
|
|
for (m = media->streams.head; m; m = m->next) { |
|
|
|
stream = m->data; |
|
|
|
__stream_unconfirm(stream); |
|
|
|
if (stream->rtp_sink) |
|
|
|
__stream_unconfirm(stream->rtp_sink); |
|
|
|
if (stream->rtcp_sink) |
|
|
|
__stream_unconfirm(stream->rtcp_sink); |
|
|
|
__unconfirm_sinks(&stream->rtp_sinks); |
|
|
|
__unconfirm_sinks(&stream->rtcp_sinks); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
static void __dialogue_unkernelize(struct call_monologue *ml) { |
|
|
|
__monologue_unkernelize(ml); |
|
|
|
|
|
|
|
for (GList *sub = ml->subscriptions.head; sub; sub = sub->next) { |
|
|
|
struct call_subscription *cs = sub->data; |
|
|
|
__monologue_unkernelize(cs->monologue); |
|
|
|
} |
|
|
|
for (GList *sub = ml->subscribers.head; sub; sub = sub->next) { |
|
|
|
struct call_subscription *cs = sub->data; |
|
|
|
__monologue_unkernelize(cs->monologue); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void __unkernelize_sinks(GQueue *q) { |
|
|
|
for (GList *l = q->head; l; l = l->next) { |
|
|
|
struct sink_handler *sh = l->data; |
|
|
|
unkernelize(sh->sink); |
|
|
|
} |
|
|
|
} |
|
|
|
/* call locked in R */ |
|
|
|
void call_media_unkernelize(struct call_media *media) { |
|
|
|
GList *m; |
|
|
|
@ -3124,8 +3287,8 @@ void call_media_unkernelize(struct call_media *media) { |
|
|
|
for (m = media->streams.head; m; m = m->next) { |
|
|
|
stream = m->data; |
|
|
|
unkernelize(stream); |
|
|
|
unkernelize(stream->rtp_sink); |
|
|
|
unkernelize(stream->rtcp_sink); |
|
|
|
__unkernelize_sinks(&stream->rtp_sinks); |
|
|
|
__unkernelize_sinks(&stream->rtcp_sinks); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -3199,23 +3362,28 @@ static int monologue_destroy(struct call_monologue *ml) { |
|
|
|
|
|
|
|
/* must be called with call->master_lock held in W */ |
|
|
|
static void __fix_other_tags(struct call_monologue *one) { |
|
|
|
struct call_monologue *two; |
|
|
|
|
|
|
|
if (!one || !one->tag.len) |
|
|
|
return; |
|
|
|
two = one->active_dialogue; |
|
|
|
if (!two || !two->tag.len) |
|
|
|
return; |
|
|
|
|
|
|
|
g_hash_table_insert(one->other_tags, &two->tag, two); |
|
|
|
g_hash_table_insert(two->other_tags, &one->tag, one); |
|
|
|
for (GList *sub = one->subscribers.head; sub; sub = sub->next) { |
|
|
|
struct call_subscription *cs = sub->data; |
|
|
|
struct call_monologue *two = cs->monologue; |
|
|
|
g_hash_table_insert(one->other_tags, &two->tag, two); |
|
|
|
g_hash_table_insert(two->other_tags, &one->tag, one); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* must be called with call->master_lock held in W */ |
|
|
|
static struct call_monologue *call_get_monologue(struct call *call, const str *fromtag, const str *totag, |
|
|
|
struct call_monologue *call_get_monologue(struct call *call, const str *fromtag) { |
|
|
|
return g_hash_table_lookup(call->tags, fromtag); |
|
|
|
} |
|
|
|
|
|
|
|
/* must be called with call->master_lock held in W */ |
|
|
|
static int call_get_monologue_new(struct call_monologue *dialogue[2], struct call *call, |
|
|
|
const str *fromtag, const str *totag, |
|
|
|
const str *viabranch) |
|
|
|
{ |
|
|
|
struct call_monologue *ret, *os; |
|
|
|
struct call_monologue *ret, *os = NULL; |
|
|
|
|
|
|
|
__C_DBG("getting monologue for tag '"STR_FORMAT"' in call '"STR_FORMAT"'", |
|
|
|
STR_FMT(fromtag), STR_FMT(&call->callid)); |
|
|
|
@ -3228,27 +3396,34 @@ static struct call_monologue *call_get_monologue(struct call *call, const str *f |
|
|
|
|
|
|
|
__C_DBG("found existing monologue"); |
|
|
|
__monologue_unkernelize(ret); |
|
|
|
__monologue_unkernelize(ret->active_dialogue); |
|
|
|
for (GList *sub = ret->subscriptions.head; sub; sub = sub->next) { |
|
|
|
struct call_subscription *cs = sub->data; |
|
|
|
__monologue_unkernelize(cs->monologue); |
|
|
|
} |
|
|
|
|
|
|
|
if (!viabranch) |
|
|
|
goto ok_check_tag; |
|
|
|
|
|
|
|
/* check the viabranch. if it's not known, then this is a branched offer and we need |
|
|
|
* to create a new "other side" for this branch. */ |
|
|
|
if (!ret->active_dialogue->viabranch.s) { |
|
|
|
/* previous "other side" hasn't been tagged with the via-branch, so we'll just |
|
|
|
* use this one and tag it */ |
|
|
|
__monologue_viabranch(ret->active_dialogue, viabranch); |
|
|
|
goto ok_check_tag; |
|
|
|
for (GList *sub = ret->subscribers.head; sub; sub = sub->next) { |
|
|
|
struct call_subscription *cs = sub->data; |
|
|
|
struct call_monologue *csm = cs->monologue; |
|
|
|
/* check the viabranch. if it's not known, then this is a branched offer and we need |
|
|
|
* to create a new "other side" for this branch. */ |
|
|
|
if (!csm->viabranch.s) { |
|
|
|
/* previous "other side" hasn't been tagged with the via-branch, so we'll just |
|
|
|
* use this one and tag it */ |
|
|
|
__monologue_viabranch(csm, viabranch); |
|
|
|
goto ok_check_tag; |
|
|
|
} |
|
|
|
if (!str_cmp_str(&csm->viabranch, viabranch)) |
|
|
|
goto ok_check_tag; /* dialogue still intact */ |
|
|
|
} |
|
|
|
if (!str_cmp_str(&ret->active_dialogue->viabranch, viabranch)) |
|
|
|
goto ok_check_tag; /* dialogue still intact */ |
|
|
|
os = g_hash_table_lookup(call->viabranches, viabranch); |
|
|
|
if (os) { |
|
|
|
/* previously seen branch. use it */ |
|
|
|
__monologue_unkernelize(os); |
|
|
|
os->active_dialogue = ret; |
|
|
|
ret->active_dialogue = os; |
|
|
|
__subscribe_only_one_offer_answer(ret, os); |
|
|
|
__subscribe_only_one_offer_answer(os, ret); |
|
|
|
goto ok_check_tag; |
|
|
|
} |
|
|
|
|
|
|
|
@ -3257,21 +3432,31 @@ static struct call_monologue *call_get_monologue(struct call *call, const str *f |
|
|
|
new_branch: |
|
|
|
__C_DBG("create new \"other side\" monologue for viabranch "STR_FORMAT, STR_FMT0(viabranch)); |
|
|
|
os = __monologue_create(call); |
|
|
|
ret->active_dialogue = os; |
|
|
|
os->active_dialogue = ret; |
|
|
|
__subscribe_only_one_offer_answer(ret, os); |
|
|
|
__subscribe_only_one_offer_answer(os, ret); |
|
|
|
__monologue_viabranch(os, viabranch); |
|
|
|
|
|
|
|
ok_check_tag: |
|
|
|
os = ret->active_dialogue; |
|
|
|
if (totag && totag->s && !os->tag.s) { |
|
|
|
__monologue_tag(os, totag); |
|
|
|
__fix_other_tags(ret); |
|
|
|
} |
|
|
|
return ret; |
|
|
|
for (GList *sub = ret->subscriptions.head; sub; sub = sub->next) { |
|
|
|
struct call_subscription *cs = sub->data; |
|
|
|
struct call_monologue *csm = cs->monologue; |
|
|
|
if (!os) |
|
|
|
os = csm; |
|
|
|
if (totag && totag->s && !csm->tag.s) { |
|
|
|
__monologue_tag(csm, totag); |
|
|
|
__fix_other_tags(ret); |
|
|
|
} |
|
|
|
break; // there should only be one |
|
|
|
// XXX check if there's more than a one-to-one mapping here? |
|
|
|
} |
|
|
|
dialogue[0] = ret; |
|
|
|
dialogue[1] = os; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
/* must be called with call->master_lock held in W */ |
|
|
|
static struct call_monologue *call_get_dialogue(struct call *call, const str *fromtag, const str *totag, |
|
|
|
static int call_get_dialogue(struct call_monologue *dialogue[2], struct call *call, const str *fromtag, |
|
|
|
const str *totag, |
|
|
|
const str *viabranch) |
|
|
|
{ |
|
|
|
struct call_monologue *ft, *tt; |
|
|
|
@ -3282,7 +3467,7 @@ static struct call_monologue *call_get_dialogue(struct call *call, const str *fr |
|
|
|
/* we start with the to-tag. if it's not known, we treat it as a branched offer */ |
|
|
|
tt = g_hash_table_lookup(call->tags, totag); |
|
|
|
if (!tt) |
|
|
|
return call_get_monologue(call, fromtag, totag, viabranch); |
|
|
|
return call_get_monologue_new(dialogue, call, fromtag, totag, viabranch); |
|
|
|
|
|
|
|
/* if the from-tag is known already, return that */ |
|
|
|
ft = g_hash_table_lookup(call->tags, fromtag); |
|
|
|
@ -3290,9 +3475,26 @@ static struct call_monologue *call_get_dialogue(struct call *call, const str *fr |
|
|
|
__C_DBG("found existing dialogue"); |
|
|
|
|
|
|
|
/* make sure that the dialogue is actually intact */ |
|
|
|
/* fastpath for a common case */ |
|
|
|
if (!str_cmp_str(totag, &ft->active_dialogue->tag)) |
|
|
|
goto done; |
|
|
|
if (ft->subscriptions.length != 1 || ft->subscribers.length != 1) |
|
|
|
goto tag_setup; |
|
|
|
if (tt->subscriptions.length != 1 || tt->subscribers.length != 1) |
|
|
|
goto tag_setup; |
|
|
|
|
|
|
|
struct call_subscription *cs = ft->subscriptions.head->data; |
|
|
|
if (cs->monologue != tt) |
|
|
|
goto tag_setup; |
|
|
|
cs = ft->subscribers.head->data; |
|
|
|
if (cs->monologue != tt) |
|
|
|
goto tag_setup; |
|
|
|
|
|
|
|
cs = tt->subscriptions.head->data; |
|
|
|
if (cs->monologue != ft) |
|
|
|
goto tag_setup; |
|
|
|
cs = tt->subscribers.head->data; |
|
|
|
if (cs->monologue != ft) |
|
|
|
goto tag_setup; |
|
|
|
|
|
|
|
goto done; |
|
|
|
} |
|
|
|
else { |
|
|
|
/* perhaps we can determine the monologue from the viabranch */ |
|
|
|
@ -3303,37 +3505,44 @@ static struct call_monologue *call_get_dialogue(struct call *call, const str *fr |
|
|
|
if (!ft) { |
|
|
|
/* if we don't have a fromtag monologue yet, we can use a half-complete dialogue |
|
|
|
* from the totag if there is one. otherwise we have to create a new one. */ |
|
|
|
ft = tt->active_dialogue; |
|
|
|
if (ft->tag.s) |
|
|
|
if (tt->subscriptions.head) { |
|
|
|
struct call_subscription *cs = tt->subscriptions.head->data; |
|
|
|
ft = cs->monologue; |
|
|
|
} |
|
|
|
if (!ft || ft->tag.s) |
|
|
|
ft = __monologue_create(call); |
|
|
|
} |
|
|
|
|
|
|
|
tag_setup: |
|
|
|
/* the fromtag monologue may be newly created, or half-complete from the totag, or |
|
|
|
* derived from the viabranch. */ |
|
|
|
if (!ft->tag.s || str_cmp_str(&ft->tag, fromtag)) |
|
|
|
__monologue_tag(ft, fromtag); |
|
|
|
|
|
|
|
__monologue_unkernelize(ft->active_dialogue); |
|
|
|
__monologue_unkernelize(tt->active_dialogue); |
|
|
|
ft->active_dialogue = tt; |
|
|
|
tt->active_dialogue = ft; |
|
|
|
__dialogue_unkernelize(ft); |
|
|
|
__dialogue_unkernelize(tt); |
|
|
|
__subscribe_only_one_offer_answer(ft, tt); |
|
|
|
__subscribe_only_one_offer_answer(tt, ft); |
|
|
|
__fix_other_tags(ft); |
|
|
|
|
|
|
|
done: |
|
|
|
__monologue_unkernelize(ft); |
|
|
|
__monologue_unkernelize(ft->active_dialogue); |
|
|
|
return ft; |
|
|
|
__dialogue_unkernelize(ft); |
|
|
|
dialogue[0] = ft; |
|
|
|
dialogue[1] = tt; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
/* fromtag and totag strictly correspond to the directionality of the message, not to the actual |
|
|
|
* SIP headers. IOW, the fromtag corresponds to the monologue sending this message, even if the |
|
|
|
* tag is actually from the TO header of the SIP message (as it would be in a 200 OK) */ |
|
|
|
struct call_monologue *call_get_mono_dialogue(struct call *call, const str *fromtag, const str *totag, |
|
|
|
int call_get_mono_dialogue(struct call_monologue *dialogue[2], struct call *call, const str *fromtag, |
|
|
|
const str *totag, |
|
|
|
const str *viabranch) |
|
|
|
{ |
|
|
|
if (!totag || !totag->s) /* initial offer */ |
|
|
|
return call_get_monologue(call, fromtag, NULL, viabranch); |
|
|
|
return call_get_dialogue(call, fromtag, totag, viabranch); |
|
|
|
return call_get_monologue_new(dialogue, call, fromtag, NULL, viabranch); |
|
|
|
return call_get_dialogue(dialogue, call, fromtag, totag, viabranch); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -3403,8 +3612,11 @@ int call_delete_branch(const str *callid, const str *branch, |
|
|
|
// if the associated dialogue has an empty tag (unknown) |
|
|
|
if (match_tag == totag) { |
|
|
|
ml = g_hash_table_lookup(c->tags, fromtag); |
|
|
|
if (ml && ml->active_dialogue && ml->active_dialogue->tag.len == 0) |
|
|
|
goto do_delete; |
|
|
|
if (ml && ml->subscriptions.length == 1) { |
|
|
|
struct call_subscription *cs = ml->subscriptions.head->data; |
|
|
|
if (cs->monologue->tag.len == 0) |
|
|
|
goto do_delete; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
ilog(LOG_INFO, "Tag '"STR_FORMAT"' in delete message not found, ignoring", |
|
|
|
@ -3417,8 +3629,10 @@ do_delete: |
|
|
|
ng_call_stats(c, fromtag, totag, output, NULL); |
|
|
|
|
|
|
|
monologue_stop(ml); |
|
|
|
if (ml->active_dialogue && ml->active_dialogue->active_dialogue == ml) |
|
|
|
monologue_stop(ml->active_dialogue); |
|
|
|
for (GList *l = ml->subscribers.head; l; l = l->next) { |
|
|
|
struct call_subscription *cs = l->data; |
|
|
|
monologue_stop(cs->monologue); |
|
|
|
} |
|
|
|
|
|
|
|
if (delete_delay > 0) { |
|
|
|
ilog(LOG_INFO, "Scheduling deletion of call branch '" STR_FORMAT_M "' " |
|
|
|
|