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 <rfuchs@sipwise.com>
Date:   Thu May 21 11:06:15 2015 -0400

    fix segfault due to missing reverse tagging

commit cd7a26314b3406faac910897d96cd4d7586fc567
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu May 21 08:57:42 2015 -0400

    support branched offer with previously unseen to-tag

commit 77da616dd2be230b03cc480ad6dd810b2742f5aa
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Apr 29 11:15:37 2015 -0400

    implement via-branch handling
pull/136/head
Richard Fuchs 10 years ago
parent a6d9c78780
commit eea325683b

@ -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);
}

@ -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,

@ -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);

@ -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

@ -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')) {

Loading…
Cancel
Save