From eea325683bdd0497abd9da8c169ec9d20f0de0aa Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Fri, 22 May 2015 09:25:19 -0400 Subject: [PATCH] MT#12691 implement via-branch handling use via-branches to predict different destinations for branched SDP offers without knowing the respective to-tag ahead of time. Squashed commit of the following: commit 0e81dc98285d81cf8014034a698bc57e6af14c98 Author: Richard Fuchs Date: Thu May 21 11:06:15 2015 -0400 fix segfault due to missing reverse tagging commit cd7a26314b3406faac910897d96cd4d7586fc567 Author: Richard Fuchs Date: Thu May 21 08:57:42 2015 -0400 support branched offer with previously unseen to-tag commit 77da616dd2be230b03cc480ad6dd810b2742f5aa Author: Richard Fuchs Date: Wed Apr 29 11:15:37 2015 -0400 implement via-branch handling --- daemon/call.c | 150 +++++++++++++++++++++++++++------------ daemon/call.h | 7 +- daemon/call_interfaces.c | 26 ++++--- daemon/str.h | 8 +++ utils/ng-client | 3 +- 5 files changed, 135 insertions(+), 59 deletions(-) diff --git a/daemon/call.c b/daemon/call.c index 33ceced0a..347999159 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -2786,10 +2786,11 @@ void call_destroy(struct call *c) { } ilog(LOG_INFO, "--- Tag '"STR_FORMAT"', created " - "%u:%02u ago, in dialogue with '"STR_FORMAT"'", + "%u:%02u ago for branch '"STR_FORMAT"', in dialogue with '"STR_FORMAT"'", STR_FMT(&ml->tag), (unsigned int) (poller_now - ml->created) / 60, (unsigned int) (poller_now - ml->created) % 60, + STR_FMT(&ml->viabranch), ml->active_dialogue ? ml->active_dialogue->tag.len : 6, ml->active_dialogue ? ml->active_dialogue->tag.s : "(none)"); @@ -3149,6 +3150,7 @@ static void __call_free(void *p) { } g_hash_table_destroy(c->tags); + g_hash_table_destroy(c->viabranches); while (c->streams) { ps = c->streams->data; @@ -3171,6 +3173,7 @@ static struct call *call_create(const str *callid, struct callmaster *m) { call_buffer_init(&c->buffer); rwlock_init(&c->master_lock); c->tags = g_hash_table_new(str_hash, str_equal); + c->viabranches = g_hash_table_new(str_hash, str_equal); call_str_cpy(c, &c->callid, callid); c->created = poller_now; c->dtls_cert = dtls_cert(); @@ -3262,6 +3265,18 @@ void __monologue_tag(struct call_monologue *ml, const str *tag) { call_str_cpy(call, &ml->tag, tag); g_hash_table_insert(call->tags, &ml->tag, ml); } +void __monologue_viabranch(struct call_monologue *ml, const str *viabranch) { + struct call *call = ml->call; + + if (!viabranch) + return; + + __C_DBG("tagging monologue with viabranch '"STR_FORMAT"'", STR_FMT(viabranch)); + if (ml->viabranch.s) + g_hash_table_remove(call->viabranches, &ml->viabranch); + call_str_cpy(call, &ml->viabranch, viabranch); + g_hash_table_insert(call->viabranches, &ml->viabranch, ml); +} static void __stream_unconfirm(struct packet_stream *ps) { __unkernelize(ps); @@ -3344,84 +3359,127 @@ static void __monologue_destroy(struct call_monologue *monologue) { } /* must be called with call->master_lock held in W */ -static struct call_monologue *call_get_monologue(struct call *call, const str *fromtag) { - struct call_monologue *ret; +static struct call_monologue *call_get_monologue(struct call *call, const str *fromtag, const str *totag, + const str *viabranch) +{ + struct call_monologue *ret, *os; __C_DBG("getting monologue for tag '"STR_FORMAT"' in call '"STR_FORMAT"'", STR_FMT(fromtag), STR_FMT(&call->callid)); ret = g_hash_table_lookup(call->tags, fromtag); + /* XXX reverse the conditional */ if (ret) { __C_DBG("found existing monologue"); __monologue_unkernelize(ret); __monologue_unkernelize(ret->active_dialogue); - return ret; + + 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; + } + 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; + goto ok_check_tag; + } + goto new_branch; } + __C_DBG("creating new monologue"); ret = __monologue_create(call); __monologue_tag(ret, fromtag); /* we need both sides of the dialogue even in the initial offer, so create * another monologue without to-tag (to be filled in later) */ - ret->active_dialogue = __monologue_create(call); - +new_branch: + __C_DBG("create new \"other side\" monologue for viabranch "STR_FORMAT, STR_FMT(viabranch)); + os = __monologue_create(call); + ret->active_dialogue = os; + os->active_dialogue = ret; + __monologue_viabranch(os, viabranch); + +ok_check_tag: + if (totag && totag->s && !ret->active_dialogue->tag.s) + __monologue_tag(ret->active_dialogue, totag); return ret; } /* 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) { - struct call_monologue *ft, *ret, *tt; +static struct call_monologue *call_get_dialogue(struct call *call, const str *fromtag, const str *totag, + const str *viabranch) +{ + struct call_monologue *ft, *tt; __C_DBG("getting dialogue for tags '"STR_FORMAT"'<>'"STR_FORMAT"' in call '"STR_FORMAT"'", STR_FMT(fromtag), STR_FMT(totag), STR_FMT(&call->callid)); - /* if the to-tag is known already, return that */ + + /* 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) { + if (!tt) + return call_get_monologue(call, fromtag, totag, viabranch); + + /* if the from-tag is known already, return that */ + ft = g_hash_table_lookup(call->tags, fromtag); + if (ft) { __C_DBG("found existing dialogue"); - __monologue_unkernelize(tt); - __monologue_unkernelize(tt->active_dialogue); /* make sure that the dialogue is actually intact */ - if (!str_cmp_str(fromtag, &tt->active_dialogue->tag)) - return tt; + /* fastpath for a common case */ + if (!str_cmp_str(totag, &ft->active_dialogue->tag)) + goto done; + } + else { + /* perhaps we can determine the monologue from the viabranch */ + if (viabranch) + ft = g_hash_table_lookup(call->viabranches, viabranch); } - /* otherwise, at least the from-tag has to be known. it's an error if it isn't */ - ft = g_hash_table_lookup(call->tags, fromtag); - if (!ft) - return NULL; - - __monologue_unkernelize(ft); - - /* check for a half-complete dialogue and fill in the missing half if possible */ - ret = ft->active_dialogue; - __monologue_unkernelize(ret); - - if (!ret->tag.s) - goto tag; - - /* we may have seen both tags previously and they just need to be linked up */ - if (tt) { - ret = tt; - goto link; + 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) + ft = __monologue_create(call); } - /* this is an additional dialogue created from a single from-tag */ - ret = __monologue_create(call); + /* the fromtag monologue may be newly created, or half-complete from the totag, or + * derived from the viabranch. */ + if (!ft->tag.s) + __monologue_tag(ft, fromtag); -tag: - __monologue_tag(ret, totag); -link: - g_hash_table_insert(ret->other_tags, &ft->tag, ft); - g_hash_table_insert(ft->other_tags, &ret->tag, ret); - ret->active_dialogue = ft; - ft->active_dialogue = ret; + g_hash_table_insert(ft->other_tags, &tt->tag, tt); + g_hash_table_insert(tt->other_tags, &ft->tag, ft); + __monologue_unkernelize(ft->active_dialogue); + __monologue_unkernelize(tt->active_dialogue); + ft->active_dialogue = tt; + tt->active_dialogue = ft; - return ret; +done: + __monologue_unkernelize(ft); + __monologue_unkernelize(ft->active_dialogue); + return ft; } -struct call_monologue *call_get_mono_dialogue(struct call *call, const str *fromtag, const str *totag) { - if (!totag || !totag->s) /* offer, not answer */ - return call_get_monologue(call, fromtag); - return call_get_dialogue(call, fromtag, totag); +/* 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, + 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); } diff --git a/daemon/call.h b/daemon/call.h index c7ca2cf0d..51be41ef6 100644 --- a/daemon/call.h +++ b/daemon/call.h @@ -365,6 +365,7 @@ struct call_monologue { struct call *call; /* RO */ str tag; + str viabranch; enum tag_type tagtype; time_t created; /* RO */ time_t deleted; @@ -389,7 +390,7 @@ struct call { rwlock_t master_lock; GSList *monologues; GHashTable *tags; - //GHashTable *branches; + GHashTable *viabranches; GSList *streams; GSList *stream_fds; struct dtls_cert *dtls_cert; /* for outgoing */ @@ -485,6 +486,7 @@ void callmaster_get_all_calls(struct callmaster *m, GQueue *q); void calls_dump_redis(struct callmaster *); struct call_monologue *__monologue_create(struct call *call); void __monologue_tag(struct call_monologue *ml, const str *tag); +void __monologue_viabranch(struct call_monologue *ml, const str *viabranch); struct stream_fd *__stream_fd_new(struct udp_fd *fd, struct call *call); int __get_consecutive_ports(struct udp_fd *array, int array_len, int wanted_start_port, const struct call *c); struct packet_stream *__packet_stream_new(struct call *call); @@ -492,7 +494,8 @@ 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_monologue *call_get_mono_dialogue(struct call *call, const str *fromtag, const str *totag, + const str *viabranch); 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, diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 3b6555490..d6543b590 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -149,8 +149,9 @@ static str *call_update_lookup_udp(char **out, struct callmaster *m, enum call_o str_init(&callid, out[RE_UDP_UL_CALLID]); str_init(&viabranch, out[RE_UDP_UL_VIABRANCH]); str_init(&fromtag, out[RE_UDP_UL_FROMTAG]); + str_init(&totag, out[RE_UDP_UL_TOTAG]); if (opmode == OP_ANSWER) - str_init(&totag, out[RE_UDP_UL_TOTAG]); + str_swap(&fromtag, &totag); c = call_get_opmode(&callid, m, opmode); if (!c) { @@ -164,11 +165,11 @@ static str *call_update_lookup_udp(char **out, struct callmaster *m, enum call_o c->created_from_addr = *sin; } - monologue = call_get_mono_dialogue(c, &fromtag, &totag); + monologue = call_get_mono_dialogue(c, &fromtag, &totag, NULL); if (!monologue) goto ml_fail; - if (!totag.s || totag.len==0) { + if (opmode == OP_OFFER) { monologue->tagtype = FROM_TAG; } else { monologue->tagtype = TO_TAG; @@ -317,15 +318,16 @@ static str *call_request_lookup_tcp(char **out, struct callmaster *m, enum call_ ilog(LOG_WARNING, "No from-tag in message"); goto out2; } + str_init(&totag, g_hash_table_lookup(infohash, "totag")); if (opmode == OP_ANSWER) { - str_init(&totag, g_hash_table_lookup(infohash, "totag")); if (!totag.s) { ilog(LOG_WARNING, "No to-tag in message"); goto out2; } + str_swap(&fromtag, &totag); } - monologue = call_get_mono_dialogue(c, &fromtag, &totag); + monologue = call_get_mono_dialogue(c, &fromtag, &totag, NULL); if (!monologue) { ilog(LOG_WARNING, "Invalid dialogue association"); goto out2; @@ -644,7 +646,7 @@ static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster bencode_item_t *output, enum call_opmode opmode, const char* addr, const struct sockaddr_in6 *sin) { - str sdp, fromtag, totag = STR_NULL, callid; + str sdp, fromtag, totag = STR_NULL, callid, viabranch; char *errstr; GQueue parsed = G_QUEUE_INIT; GQueue streams = G_QUEUE_INIT; @@ -660,11 +662,13 @@ static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster return "No call-id in message"; if (!bencode_dictionary_get_str(input, "from-tag", &fromtag)) return "No from-tag in message"; + bencode_dictionary_get_str(input, "to-tag", &totag); if (opmode == OP_ANSWER) { - if (!bencode_dictionary_get_str(input, "to-tag", &totag)) + if (!totag.s) return "No to-tag in message"; + str_swap(&totag, &fromtag); } - //bencode_dictionary_get_str(input, "via-branch", &viabranch); + bencode_dictionary_get_str(input, "via-branch", &viabranch); if (sdp_parse(&sdp, &parsed)) return "Failed to parse SDP"; @@ -689,7 +693,7 @@ static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster * need to hold a ref until we're done sending the reply */ call_bencode_hold_ref(call, output); - monologue = call_get_mono_dialogue(call, &fromtag, &totag); + monologue = call_get_mono_dialogue(call, &fromtag, &totag, viabranch.s ? &viabranch : NULL); errstr = "Invalid dialogue association"; if (!monologue) { rwlock_unlock_w(&call->master_lock); @@ -697,7 +701,7 @@ static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster goto out; } - if (!totag.s || totag.len==0) { + if (opmode == OP_OFFER) { monologue->tagtype = FROM_TAG; } else { monologue->tagtype = TO_TAG; @@ -902,6 +906,8 @@ static void ng_stats_monologue(bencode_item_t *dict, const struct call_monologue sub = bencode_dictionary_add_dictionary(dict, ml->tag.s); bencode_dictionary_add_str(sub, "tag", &ml->tag); + if (ml->viabranch.s) + bencode_dictionary_add_str(sub, "via-branch", &ml->viabranch); bencode_dictionary_add_integer(sub, "created", ml->created); if (ml->active_dialogue) bencode_dictionary_add_str(sub, "in dialogue with", &ml->active_dialogue->tag); diff --git a/daemon/str.h b/daemon/str.h index 2225767c0..f1154242e 100644 --- a/daemon/str.h +++ b/daemon/str.h @@ -59,6 +59,8 @@ INLINE int str_shift(str *s, int len); INLINE int str_memcmp(const str *s, void *m); /* locate a substring within a string, returns character index or -1 */ INLINE int str_str(const str *s, const char *sub); +/* swaps the contents of two str objects */ +INLINE void str_swap(str *a, str *b); /* asprintf() analogs */ #define str_sprintf(fmt, a...) __str_sprintf(STR_MALLOC_PADDING fmt, a) @@ -231,5 +233,11 @@ INLINE int str_str(const str *s, const char *sub) { return -1; } +INLINE void str_swap(str *a, str *b) { + str t; + t = *a; + *a = *b; + *b = t; +} #endif diff --git a/utils/ng-client b/utils/ng-client index e02ff152b..5d90fdb5d 100755 --- a/utils/ng-client +++ b/utils/ng-client @@ -19,6 +19,7 @@ GetOptions( 'from-tag=s' => \$options{'from-tag'}, 'to-tag=s' => \$options{'to-tag'}, 'call-id=s' => \$options{'call-id'}, + 'via-branch=s' => \$options{'via-branch'}, 'protocol=s' => \$options{'transport protocol'}, 'trust-address' => \$options{'trust address'}, 'sip-source-address' => \$options{'sip source address'}, @@ -46,7 +47,7 @@ my $cmd = shift(@ARGV) or die; my %packet = (command => $cmd); -for my $x (split(',', 'from-tag,to-tag,call-id,transport protocol,media address,ICE,address family,TOS,DTLS')) { +for my $x (split(',', 'from-tag,to-tag,call-id,transport protocol,media address,ICE,address family,TOS,DTLS,via-branch')) { defined($options{$x}) and $packet{$x} = $options{$x}; } for my $x (split(',', 'trust address,symmetric,asymmetric,force,strict source,media handover,sip source address')) {