mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-03 03:20:57 +00:00
bridging: Fix multiple bridging issues causing SEGVs and FRACKs.
Issues:
* The bridging core allowed multiple bridges to be created with the same
unique bridgeId at the same time. Only the last bridge created with the
duplicate name was actually saved to the core bridges container.
* The bridging core was creating a stasis topic for the bridge and saving it
in the bridge->topic field but not increasing its reference count. In the
case where two bridges were created with the same uniqueid (which is also
the topic name), the second bridge would get the _existing_ topic the first
bridge created. When the first bridge was destroyed, it would take the
topic with it so when the second bridge attempted to publish a message to
it it either FRACKed or SEGVd.
* The bridge destructor, which also destroys the bridge topic, is run from the
bridge manager thread not the caller's thread. This makes it possible for
an ARI developer to create a new one with the same uniqueid believing the
old one was destroyed when, in fact, the old one's destructor hadn't
completed. This could cause the new bridge to get the old one's topic just
before the topic was destroyed. When the new bridge attempted to publish
a message on that topic, asterisk could either FRACK or SEGV.
* The ARI bridges resource also allowed multiple bridges to be created with
the same uniqueid but it kept the duplicate bridges in its app_bridges
container. This created a situation where if you added two bridges with
the same "bridge1" uniqueid, all operations on "bridge1" were performed on
the first bridge created and the second was basically orphaned. If you
attempted to delete what you thought was the second bridge, you actually
deleted the first one created.
Changes:
* A new API `ast_bridge_topic_exists(uniqueid)` was created to determine if
a topic already exists for a bridge.
* `bridge_base_init()` in bridge.c and `ast_ari_bridges_create()` in
resource_bridges.c now call `ast_bridge_topic_exists(uniqueid)` to check
if a bridge with the requested uniqueid already exists and will fail if it
does.
* `bridge_register()` in bridges.c now checks the core bridges container to
make sure a bridge doesn't already exist with the requested uniqueid.
Although most callers of `bridge_register()` will have already called
`bridge_base_init()`, which will now fail on duplicate bridges, there
is no guarantee of this so we must check again.
* The core bridges container allocation was changed to reject duplicate
uniqueids instead of silently replacing an existing one. This is a "belt
and suspenders" check.
* A global mutex was added to bridge.c to prevent concurrent calls to
`bridge_base_init()` and `bridge_register()`.
* Even though you can no longer create multiple bridges with the same uniqueid
at the same time, it's still possible that the bridge topic might be
destroyed while a second bridge with the same uniqueid was trying to use
it. To address this, the bridging core now increments the reference count
on bridge->topic when a bridge is created and decrements it when the
bridge is destroyed.
* `bridge_create_common()` in res_stasis.c now checks the stasis app_bridges
container to make sure a bridge with the requested uniqueid doesn't already
exist. This may seem like overkill but there are so many entrypoints to
bridge creation that we need to be safe and catch issues as soon in the
process as possible.
* The stasis app_bridges container allocation was changed to reject duplicate
uniqueids instead of adding them. This is a "belt and suspenders" check.
* The `bridge show all` CLI command now shows the bridge name as well as the
bridge id.
* Response code 409 "Conflict" was added as a possible response from the ARI
bridge create resources to signal that a bridge with the requested uniqueid
already exists.
* Additional debugging was added to multiple bridging and stasis files.
Resolves: #211
(cherry picked from commit 46c9f7db8e
)
This commit is contained in:
committed by
Asterisk Development Team
parent
b4017eadaf
commit
7300a506d0
117
main/bridge.c
117
main/bridge.c
@@ -133,6 +133,8 @@ static struct ao2_container *bridges;
|
||||
|
||||
static AST_RWLIST_HEAD_STATIC(bridge_technologies, ast_bridge_technology);
|
||||
|
||||
AST_MUTEX_DEFINE_STATIC(bridge_init_lock);
|
||||
|
||||
static unsigned int optimization_id;
|
||||
|
||||
/* Initial starting point for the bridge array of channels */
|
||||
@@ -330,6 +332,8 @@ void bridge_dissolve(struct ast_bridge *bridge, int cause)
|
||||
};
|
||||
|
||||
if (bridge->dissolved) {
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": already dissolved\n",
|
||||
BRIDGE_PRINTF_VARS(bridge));
|
||||
return;
|
||||
}
|
||||
bridge->dissolved = 1;
|
||||
@@ -339,16 +343,22 @@ void bridge_dissolve(struct ast_bridge *bridge, int cause)
|
||||
}
|
||||
bridge->cause = cause;
|
||||
|
||||
ast_debug(1, "Bridge %s: dissolving bridge with cause %d(%s)\n",
|
||||
bridge->uniqueid, cause, ast_cause2str(cause));
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": dissolving with cause %d(%s)\n",
|
||||
BRIDGE_PRINTF_VARS(bridge), cause, ast_cause2str(cause));
|
||||
|
||||
AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": kicking channel %s\n",
|
||||
BRIDGE_PRINTF_VARS(bridge),
|
||||
ast_channel_name(bridge_channel->chan));
|
||||
ast_bridge_channel_leave_bridge(bridge_channel,
|
||||
BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE, cause);
|
||||
}
|
||||
|
||||
/* Must defer dissolving bridge because it is already locked. */
|
||||
ast_bridge_queue_action(bridge, &action);
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": DEFERRED_DISSOLVING queued. current refcound: %d\n",
|
||||
BRIDGE_PRINTF_VARS(bridge), ao2_ref(bridge, 0));
|
||||
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -513,27 +523,27 @@ static struct ast_bridge_technology *find_best_technology(uint32_t capabilities,
|
||||
AST_RWLIST_RDLOCK(&bridge_technologies);
|
||||
AST_RWLIST_TRAVERSE(&bridge_technologies, current, entry) {
|
||||
if (current->suspended) {
|
||||
ast_debug(1, "Bridge technology %s is suspended. Skipping.\n",
|
||||
ast_debug(2, "Bridge technology %s is suspended. Skipping.\n",
|
||||
current->name);
|
||||
continue;
|
||||
}
|
||||
if (!(current->capabilities & capabilities)) {
|
||||
ast_debug(1, "Bridge technology %s does not have any capabilities we want.\n",
|
||||
ast_debug(2, "Bridge technology %s does not have any capabilities we want.\n",
|
||||
current->name);
|
||||
continue;
|
||||
}
|
||||
if (best && current->preference <= best->preference) {
|
||||
ast_debug(1, "Bridge technology %s has less preference than %s (%u <= %u). Skipping.\n",
|
||||
ast_debug(2, "Bridge technology %s has less preference than %s (%u <= %u). Skipping.\n",
|
||||
current->name, best->name, current->preference, best->preference);
|
||||
continue;
|
||||
}
|
||||
if (current->compatible && !current->compatible(bridge)) {
|
||||
ast_debug(1, "Bridge technology %s is not compatible with properties of existing bridge.\n",
|
||||
ast_debug(2, "Bridge technology %s is not compatible with properties of existing bridge.\n",
|
||||
current->name);
|
||||
continue;
|
||||
}
|
||||
if (!ast_module_running_ref(current->mod)) {
|
||||
ast_debug(1, "Bridge technology %s is not running, skipping.\n", current->name);
|
||||
ast_debug(2, "Bridge technology %s is not running, skipping.\n", current->name);
|
||||
continue;
|
||||
}
|
||||
if (best) {
|
||||
@@ -650,8 +660,8 @@ static void destroy_bridge(void *obj)
|
||||
{
|
||||
struct ast_bridge *bridge = obj;
|
||||
|
||||
ast_debug(1, "Bridge %s: actually destroying %s bridge, nobody wants it anymore\n",
|
||||
bridge->uniqueid, bridge->v_table->name);
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": actually destroying %s bridge, nobody wants it anymore\n",
|
||||
BRIDGE_PRINTF_VARS(bridge), bridge->v_table->name);
|
||||
|
||||
if (bridge->construction_completed) {
|
||||
bridge_topics_destroy(bridge);
|
||||
@@ -693,18 +703,40 @@ static void destroy_bridge(void *obj)
|
||||
|
||||
cleanup_video_mode(bridge);
|
||||
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": destroyed\n",
|
||||
BRIDGE_PRINTF_VARS(bridge));
|
||||
ast_string_field_free_memory(bridge);
|
||||
ao2_cleanup(bridge->current_snapshot);
|
||||
bridge->current_snapshot = NULL;
|
||||
}
|
||||
|
||||
struct ast_bridge *bridge_register(struct ast_bridge *bridge)
|
||||
{
|
||||
if (bridge) {
|
||||
SCOPED_MUTEX(lock, &bridge_init_lock);
|
||||
/*
|
||||
* Although bridge_base_init() should have already checked for
|
||||
* an existing bridge with the same uniqueid, bridge_base_init()
|
||||
* and bridge_register() are two separate public APIs so we need
|
||||
* to check again here.
|
||||
*/
|
||||
struct ast_bridge *existing = ast_bridge_find_by_id(bridge->uniqueid);
|
||||
if (existing) {
|
||||
ast_log(LOG_WARNING, "Bridge " BRIDGE_PRINTF_SPEC ": already registered\n",
|
||||
BRIDGE_PRINTF_VARS(bridge));
|
||||
ao2_ref(existing, -1);
|
||||
ast_bridge_destroy(bridge, 0);
|
||||
return NULL;
|
||||
}
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": registering\n",
|
||||
BRIDGE_PRINTF_VARS(bridge));
|
||||
bridge->construction_completed = 1;
|
||||
ast_bridge_lock(bridge);
|
||||
ast_bridge_publish_state(bridge);
|
||||
ast_bridge_unlock(bridge);
|
||||
if (!ao2_link(bridges, bridge)) {
|
||||
ast_log(LOG_WARNING, "Bridge " BRIDGE_PRINTF_SPEC ": failed to link\n",
|
||||
BRIDGE_PRINTF_VARS(bridge));
|
||||
ast_bridge_destroy(bridge, 0);
|
||||
bridge = NULL;
|
||||
}
|
||||
@@ -756,29 +788,45 @@ struct ast_bridge *bridge_base_init(struct ast_bridge *self, uint32_t capabiliti
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(id)) {
|
||||
ast_string_field_set(self, uniqueid, id);
|
||||
} else {
|
||||
ast_uuid_generate_str(uuid_hold, AST_UUID_STR_LEN);
|
||||
ast_string_field_set(self, uniqueid, uuid_hold);
|
||||
{
|
||||
/*
|
||||
* We need to ensure that another bridge with the same uniqueid
|
||||
* doesn't get created before the previous bridge's destructor
|
||||
* has run and deleted the existing topic.
|
||||
*/
|
||||
SCOPED_MUTEX(lock, &bridge_init_lock);
|
||||
if (!ast_strlen_zero(id)) {
|
||||
if (ast_bridge_topic_exists(id)) {
|
||||
ast_log(LOG_WARNING, "Bridge " BRIDGE_PRINTF_SPEC ": already registered\n",
|
||||
BRIDGE_PRINTF_VARS(self));
|
||||
ast_bridge_destroy(self, 0);
|
||||
return NULL;
|
||||
}
|
||||
ast_string_field_set(self, uniqueid, id);
|
||||
} else {
|
||||
ast_uuid_generate_str(uuid_hold, AST_UUID_STR_LEN);
|
||||
ast_string_field_set(self, uniqueid, uuid_hold);
|
||||
}
|
||||
if (!(flags & AST_BRIDGE_FLAG_INVISIBLE)) {
|
||||
if (bridge_topics_init(self) != 0) {
|
||||
ast_log(LOG_WARNING, "Bridge " BRIDGE_PRINTF_SPEC ": Could not initialize topics\n",
|
||||
BRIDGE_PRINTF_VARS(self));
|
||||
ao2_ref(self, -1);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ast_string_field_set(self, creator, creator);
|
||||
if (!ast_strlen_zero(creator)) {
|
||||
ast_string_field_set(self, name, name);
|
||||
}
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": base_init\n",
|
||||
BRIDGE_PRINTF_VARS(self));
|
||||
|
||||
ast_set_flag(&self->feature_flags, flags);
|
||||
self->allowed_capabilities = capabilities;
|
||||
|
||||
if (!(flags & AST_BRIDGE_FLAG_INVISIBLE)) {
|
||||
if (bridge_topics_init(self) != 0) {
|
||||
ast_log(LOG_WARNING, "Bridge %s: Could not initialize topics\n",
|
||||
self->uniqueid);
|
||||
ao2_ref(self, -1);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Use our helper function to find the "best" bridge technology. */
|
||||
self->technology = find_best_technology(capabilities, self);
|
||||
if (!self->technology) {
|
||||
@@ -814,6 +862,8 @@ struct ast_bridge *bridge_base_init(struct ast_bridge *self, uint32_t capabiliti
|
||||
}
|
||||
|
||||
self->creationtime = ast_tvnow();
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": base_init complete\n",
|
||||
BRIDGE_PRINTF_VARS(self));
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -829,6 +879,8 @@ struct ast_bridge *bridge_base_init(struct ast_bridge *self, uint32_t capabiliti
|
||||
*/
|
||||
static void bridge_base_destroy(struct ast_bridge *self)
|
||||
{
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": destroying bridge (noop)\n",
|
||||
BRIDGE_PRINTF_VARS(self));
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -840,7 +892,11 @@ static void bridge_base_destroy(struct ast_bridge *self)
|
||||
*/
|
||||
static void bridge_base_dissolving(struct ast_bridge *self)
|
||||
{
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": unlinking bridge. Refcount: %d\n",
|
||||
BRIDGE_PRINTF_VARS(self), ao2_ref(self, 0));
|
||||
ao2_unlink(bridges, self);
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": unlinked bridge. Refcount: %d\n",
|
||||
BRIDGE_PRINTF_VARS(self), ao2_ref(self, 0));
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -952,11 +1008,15 @@ struct ast_bridge *ast_bridge_base_new(uint32_t capabilities, unsigned int flags
|
||||
|
||||
int ast_bridge_destroy(struct ast_bridge *bridge, int cause)
|
||||
{
|
||||
ast_debug(1, "Bridge %s: telling all channels to leave the party\n", bridge->uniqueid);
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": destroying. current refcount: %d\n",
|
||||
BRIDGE_PRINTF_VARS(bridge), ao2_ref(bridge, 0));
|
||||
ast_bridge_lock(bridge);
|
||||
bridge_dissolve(bridge, cause);
|
||||
ast_bridge_unlock(bridge);
|
||||
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": unreffing. current refcount: %d\n",
|
||||
BRIDGE_PRINTF_VARS(bridge), ao2_ref(bridge, 0));
|
||||
|
||||
ao2_ref(bridge, -1);
|
||||
|
||||
return 0;
|
||||
@@ -5044,8 +5104,8 @@ static char *complete_bridge_live(const char *word)
|
||||
|
||||
static char *handle_bridge_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
|
||||
{
|
||||
#define FORMAT_HDR "%-36s %5s %-15s %-15s %s\n"
|
||||
#define FORMAT_ROW "%-36s %5u %-15s %-15s %s\n"
|
||||
#define FORMAT_HDR "%-36s %-36s %5s %-15s %-15s %s\n"
|
||||
#define FORMAT_ROW "%-36s %-36s %5u %-15s %-15s %s\n"
|
||||
|
||||
struct ao2_iterator iter;
|
||||
struct ast_bridge *bridge;
|
||||
@@ -5061,7 +5121,7 @@ static char *handle_bridge_show_all(struct ast_cli_entry *e, int cmd, struct ast
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ast_cli(a->fd, FORMAT_HDR, "Bridge-ID", "Chans", "Type", "Technology", "Duration");
|
||||
ast_cli(a->fd, FORMAT_HDR, "Bridge-ID", "Name", "Chans", "Type", "Technology", "Duration");
|
||||
|
||||
iter = ao2_iterator_init(bridges, 0);
|
||||
for (; (bridge = ao2_iterator_next(&iter)); ao2_ref(bridge, -1)) {
|
||||
@@ -5071,7 +5131,8 @@ static char *handle_bridge_show_all(struct ast_cli_entry *e, int cmd, struct ast
|
||||
if (snapshot) {
|
||||
ast_format_duration_hh_mm_ss(ast_tvnow().tv_sec - snapshot->creationtime.tv_sec, print_time, sizeof(print_time));
|
||||
ast_cli(a->fd, FORMAT_ROW,
|
||||
snapshot->uniqueid,
|
||||
bridge->uniqueid,
|
||||
S_OR(bridge->name, "<unknown>"),
|
||||
snapshot->num_channels,
|
||||
S_OR(snapshot->subclass, "<unknown>"),
|
||||
S_OR(snapshot->technology, "<unknown>"),
|
||||
|
@@ -196,6 +196,26 @@ struct stasis_topic *ast_bridge_topic(struct ast_bridge *bridge)
|
||||
return bridge->topic;
|
||||
}
|
||||
|
||||
int ast_bridge_topic_exists(const char *uniqueid)
|
||||
{
|
||||
char *topic_name;
|
||||
int ret;
|
||||
|
||||
if (ast_strlen_zero(uniqueid)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = ast_asprintf(&topic_name, "bridge:%s", uniqueid);
|
||||
if (ret < 0) {
|
||||
return 0;
|
||||
}
|
||||
ret = stasis_topic_pool_topic_exists(bridge_topic_pool, topic_name);
|
||||
ast_free(topic_name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*! \brief Destructor for bridge snapshots */
|
||||
static void bridge_snapshot_dtor(void *obj)
|
||||
{
|
||||
@@ -318,6 +338,7 @@ int bridge_topics_init(struct ast_bridge *bridge)
|
||||
if (!bridge->topic) {
|
||||
return -1;
|
||||
}
|
||||
ao2_bump(bridge->topic);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -328,6 +349,14 @@ void bridge_topics_destroy(struct ast_bridge *bridge)
|
||||
struct stasis_message *msg;
|
||||
|
||||
ast_assert(bridge != NULL);
|
||||
ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": destroying topics\n",
|
||||
BRIDGE_PRINTF_VARS(bridge));
|
||||
|
||||
if (!bridge->topic) {
|
||||
ast_log(LOG_WARNING, "Bridge " BRIDGE_PRINTF_SPEC " topic is NULL\n",
|
||||
BRIDGE_PRINTF_VARS(bridge));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bridge->current_snapshot) {
|
||||
bridge->current_snapshot = ast_bridge_snapshot_create(bridge);
|
||||
@@ -351,6 +380,8 @@ void bridge_topics_destroy(struct ast_bridge *bridge)
|
||||
ao2_ref(msg, -1);
|
||||
|
||||
stasis_topic_pool_delete_topic(bridge_topic_pool, stasis_topic_name(ast_bridge_topic(bridge)));
|
||||
ao2_cleanup(bridge->topic);
|
||||
bridge->topic = NULL;
|
||||
}
|
||||
|
||||
void ast_bridge_publish_state(struct ast_bridge *bridge)
|
||||
|
Reference in New Issue
Block a user