]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
ARI: Detect duplicate channel IDs 50/4150/3
authorMark Michelson <mmichelson@digium.com>
Mon, 17 Oct 2016 19:18:57 +0000 (14:18 -0500)
committerMark Michelson <mmichelson@digium.com>
Thu, 20 Oct 2016 17:50:02 +0000 (12:50 -0500)
ARI and AMI allow for an explicit channel ID to be specified
when originating channels. Unfortunately, there is nothing in
place to prevent someone from using the same ID for multiple
channels. Further complicating things, adding ID validation to channel
allocation makes it impossible for ARI to discern why channel allocation
failed, resulting in a vague error code being returned.

The fix for this is to institute a new method for channel errors to be
discerned. The method mirrors errno, in that when an error occurs, the
caller can consult the channel errno value to determine what the error
was. This initial iteration of the feature only introduces "unknown" and
"channel ID exists" errors. However, it's possible to add more errors as
needed.

ARI uses this feature to determine why channel allocation failed and can
return a 409 error during origination to show that a channel with the
given ID already exists.

ASTERISK-26421

Change-Id: Ibba7ae68842dab6df0c2e9c45559208bc89d3d06

include/asterisk/channel.h
include/asterisk/channel_internal.h
main/channel.c
main/channel_internal_api.c
res/ari/resource_channels.c
res/res_ari_channels.c
rest-api/api-docs/channels.json

index e42307dd92c86322af7c929204d8c8744cc33c46..a76e606f9e4e6c7587b3b0f83d73968a1e063ecc 100644 (file)
@@ -4669,4 +4669,16 @@ int ast_channel_feature_hooks_append(struct ast_channel *chan, struct ast_bridge
  */
 int ast_channel_feature_hooks_replace(struct ast_channel *chan, struct ast_bridge_features *features);
 
+enum ast_channel_error {
+       /* Unable to determine what error occurred. */
+       AST_CHANNEL_ERROR_UNKNOWN,
+       /* Channel with this ID already exists */
+       AST_CHANNEL_ERROR_ID_EXISTS,
+};
+
+/*!
+ * \brief Get error code for latest channel operation.
+ */
+enum ast_channel_error ast_channel_errno(void);
+
 #endif /* _ASTERISK_CHANNEL_H */
index d1231b400a699e5eb1dcb609c8ad2dcb3c71c05c..2316e2f24c9e128860ecf15071e07b895abafaee 100644 (file)
@@ -25,3 +25,5 @@ int ast_channel_internal_is_finalized(struct ast_channel *chan);
 void ast_channel_internal_cleanup(struct ast_channel *chan);
 int ast_channel_internal_setup_topics(struct ast_channel *chan);
 
+void ast_channel_internal_errno_set(enum ast_channel_error error);
+enum ast_channel_error ast_channel_internal_errno(void);
index c6cb925b8c20c4637c95614db19b6924c09d5bcb..278104cc98cf1251b1c6fdc268ddbeacc9f74e4f 100644 (file)
@@ -767,6 +767,27 @@ static const struct ast_channel_tech null_tech = {
 
 static void ast_channel_destructor(void *obj);
 static void ast_dummy_channel_destructor(void *obj);
+static int ast_channel_by_uniqueid_cb(void *obj, void *arg, void *data, int flags);
+
+static int does_id_conflict(const char *uniqueid)
+{
+       struct ast_channel *conflict;
+       int length = 0;
+
+       if (ast_strlen_zero(uniqueid)) {
+               return 0;
+       }
+
+       conflict = ast_channel_callback(ast_channel_by_uniqueid_cb, (char *) uniqueid, &length, OBJ_NOLOCK);
+       if (conflict) {
+               ast_log(LOG_ERROR, "Channel Unique ID '%s' already in use by channel %s(%p)\n",
+                       uniqueid, ast_channel_name(conflict), conflict);
+               ast_channel_unref(conflict);
+               return 1;
+       }
+
+       return 0;
+}
 
 /*! \brief Create a new channel structure */
 static struct ast_channel * attribute_malloc __attribute__((format(printf, 15, 0)))
@@ -940,16 +961,33 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
                ast_channel_tech_set(tmp, &null_tech);
        }
 
-       ast_channel_internal_finalize(tmp);
-
-       ast_atomic_fetchadd_int(&chancount, +1);
-
        /* You might scream "locking inversion" at seeing this but it is actually perfectly fine.
         * Since the channel was just created nothing can know about it yet or even acquire it.
         */
        ast_channel_lock(tmp);
 
-       ao2_link(channels, tmp);
+       ao2_lock(channels);
+
+       if (assignedids && (does_id_conflict(assignedids->uniqueid) || does_id_conflict(assignedids->uniqueid2))) {
+               ast_channel_internal_errno_set(AST_CHANNEL_ERROR_ID_EXISTS);
+               ao2_unlock(channels);
+               /* This is a bit unorthodox, but we can't just call ast_channel_stage_snapshot_done()
+                * because that will result in attempting to publish the channel snapshot. That causes
+                * badness in some places, such as CDRs. So we need to manually clear the flag on the
+                * channel that says that a snapshot is being cleared.
+                */
+               ast_clear_flag(ast_channel_flags(tmp), AST_FLAG_SNAPSHOT_STAGE);
+               ast_channel_unlock(tmp);
+               return ast_channel_unref(tmp);
+       }
+
+       ast_channel_internal_finalize(tmp);
+
+       ast_atomic_fetchadd_int(&chancount, +1);
+
+       ao2_link_flags(channels, tmp, OBJ_NOLOCK);
+
+       ao2_unlock(channels);
 
        if (endpoint) {
                ast_endpoint_add_channel(endpoint, tmp);
@@ -10842,3 +10880,8 @@ int ast_channel_feature_hooks_replace(struct ast_channel *chan, struct ast_bridg
 {
        return channel_feature_hooks_set_full(chan, features, 1);
 }
+
+enum ast_channel_error ast_channel_errno(void)
+{
+       return ast_channel_internal_errno();
+}
index 51d49c29f02f6023ebc0132c9fb7f158a6815368..90f59d64a78bf657f6c5247217ce4ce12aaaec0f 100644 (file)
@@ -1484,6 +1484,7 @@ static int pvt_cause_cmp_fn(void *obj, void *vstr, int flags)
 struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *file, int line, const char *function)
 {
        struct ast_channel *tmp;
+
 #if defined(REF_DEBUG)
        tmp = __ao2_alloc_debug(sizeof(*tmp), destructor,
                AO2_ALLOC_OPT_LOCK_MUTEX, "", file, line, function, 1);
@@ -1675,3 +1676,25 @@ int ast_channel_internal_setup_topics(struct ast_channel *chan)
 
        return 0;
 }
+
+AST_THREADSTORAGE(channel_errno);
+
+void ast_channel_internal_errno_set(enum ast_channel_error error)
+{
+       enum ast_channel_error *error_code = ast_threadstorage_get(&channel_errno, sizeof(*error_code));
+       if (!error_code) {
+               return;
+       }
+
+       *error_code = error;
+}
+
+enum ast_channel_error ast_channel_internal_errno(void)
+{
+       enum ast_channel_error *error_code = ast_threadstorage_get(&channel_errno, sizeof(*error_code));
+       if (!error_code) {
+               return AST_CHANNEL_ERROR_UNKNOWN;
+       }
+
+       return *error_code;
+}
index 6baac7a4e7cfacc7f71fa37ccd9004735b46c915..04db7045127547ba91088509f53d6f5edd961956 100644 (file)
@@ -1109,7 +1109,12 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
        }
 
        if (ast_dial_prerun(dial, other, format_cap)) {
-               ast_ari_response_alloc_failed(response);
+               if (ast_channel_errno() == AST_CHANNEL_ERROR_ID_EXISTS) {
+                       ast_ari_response_error(response, 409, "Conflict",
+                               "Channel with given unique ID already exists");
+               } else {
+                       ast_ari_response_alloc_failed(response);
+               }
                ast_dial_destroy(dial);
                ast_free(origination);
                ast_channel_cleanup(other);
index 9dc19cc86ea2da923207421cd94f7580d6938080..8cb3388fe654e3de5e0fa844269ea5505543780f 100644 (file)
@@ -253,6 +253,7 @@ static void ast_ari_channels_originate_cb(
        case 500: /* Internal Server Error */
        case 501: /* Not Implemented */
        case 400: /* Invalid parameters for originating a channel. */
+       case 409: /* Channel with given unique ID already exists. */
                is_valid = 1;
                break;
        default:
@@ -483,6 +484,7 @@ static void ast_ari_channels_originate_with_id_cb(
        case 500: /* Internal Server Error */
        case 501: /* Not Implemented */
        case 400: /* Invalid parameters for originating a channel. */
+       case 409: /* Channel with given unique ID already exists. */
                is_valid = 1;
                break;
        default:
index 8eaa5eb9b5b81d9118172e0e229291e252c31f95..646604654bb37f773115f72f9e2a54688898528d 100644 (file)
                                                {
                                                        "code": 400,
                                                        "reason": "Invalid parameters for originating a channel."
+                                               },
+                                               {
+                                                       "code": 409,
+                                                       "reason": "Channel with given unique ID already exists."
                                                }
                                        ]
                                }
                                                {
                                                        "code": 400,
                                                        "reason": "Invalid parameters for originating a channel."
+                                               },
+                                               {
+                                                       "code": 409,
+                                                       "reason": "Channel with given unique ID already exists."
                                                }
                                        ]
                                },