]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
func_hangupcause.c: Add access to Reason headers via HANGUPCAUSE() master
authorIgor Goncharovsky <igorg@iqtek.ru>
Thu, 4 Sep 2025 04:54:27 +0000 (10:54 +0600)
committergithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Tue, 7 Oct 2025 15:27:04 +0000 (15:27 +0000)
As soon as SIP call may end with several Reason headers, we
want to make all of them available through the HAGUPCAUSE() function.
This implementation uses the same ao2 hash for cause codes storage
and adds a flag to make difference between last processed sip
message and content of reason headers.

UserNote: Added a new option to HANGUPCAUSE to access additional
information about hangup reason. Reason headers from pjsip
could be read using 'tech_extended' cause type.

funcs/func_hangupcause.c
include/asterisk/channel.h
include/asterisk/frame.h
main/channel_internal_api.c
res/res_pjsip_rfc3326.c

index 0b93d8dc92bb2302ad7290c4f355bb3c78364d4a..26a3dbce3cc7abb30df2a8ba5ce6a89551d462e0 100644 (file)
@@ -55,6 +55,7 @@
                                <para>Parameter describing which type of information is requested. Types are:</para>
                                <enumlist>
                                        <enum name="tech"><para>Technology-specific cause information</para></enum>
+                                       <enum name="tech_extended"><para>Technology-specific extended list of cause information</para></enum>
                                        <enum name="ast"><para>Translated Asterisk cause code</para></enum>
                                </enumlist>
                        </parameter>
@@ -119,9 +120,10 @@ static int hangupcause_read(struct ast_channel *chan, const char *cmd, char *dat
        char *parms;
        struct ast_control_pvt_cause_code *cause_code;
        int res = 0;
+       struct ast_str *causelist;
        AST_DECLARE_APP_ARGS(args,
                AST_APP_ARG(channel);   /*!< Channel name */
-               AST_APP_ARG(type);      /*!< Type of information requested (ast or tech) */
+               AST_APP_ARG(type);      /*!< Type of information requested (ast, tech or tech_extended) */
                );
 
        /* Ensure that the buffer is empty */
@@ -139,25 +141,49 @@ static int hangupcause_read(struct ast_channel *chan, const char *cmd, char *dat
                return -1;
        }
 
-       ast_channel_lock(chan);
-       cause_code = ast_channel_dialed_causes_find(chan, args.channel);
-       ast_channel_unlock(chan);
-
-       if (!cause_code) {
-               ast_log(LOG_WARNING, "Unable to find information for channel %s\n", args.channel);
+       causelist = ast_str_create(128);
+       if (!causelist) {
+               ast_log(LOG_ERROR, "Unable to allocate buffer, cause information will be unavailable!\n");
                return -1;
        }
 
+       ast_channel_lock(chan);
+       if (!strcmp(args.type, "tech_extended")) {
+               struct ao2_iterator *cause_codes;
+               cause_codes = ast_channel_dialed_causes_find_multiple(chan, args.channel);
+               while ((cause_code = ao2_iterator_next(cause_codes))) {
+                       if (!cause_code->cause_extended) {
+                               ao2_ref(cause_code, -1);
+                               continue;
+                       }
+                       ast_str_append(&causelist, 0, "%s%s", (ast_str_strlen(causelist) ? "," : ""), cause_code->code);
+                       ao2_ref(cause_code, -1);
+               }
+               ao2_iterator_destroy(cause_codes);
+       } else {
+               cause_code = ast_channel_dialed_causes_find(chan, args.channel);
+               if (!cause_code) {
+                       ast_log(LOG_WARNING, "Unable to find information for channel '%s'\n", args.channel);
+                       ast_channel_unlock(chan);
+                       return -1;
+               }
+       }
+       ast_channel_unlock(chan);
+
        if (!strcmp(args.type, "ast")) {
                ast_copy_string(buf, ast_cause2str(cause_code->ast_cause), len);
        } else if (!strcmp(args.type, "tech")) {
                ast_copy_string(buf, cause_code->code, len);
+       } else if (!strcmp(args.type, "tech_extended")) {
+               ast_copy_string(buf, ast_str_buffer(causelist), len);
        } else {
                ast_log(LOG_WARNING, "Information type not recognized (%s)\n", args.type);
                res = -1;
        }
 
-       ao2_ref(cause_code, -1);
+       if (cause_code) {
+               ao2_cleanup(cause_code);
+       }
 
        return res;
 }
index 9624fb45541090d5b7737616549d2924a977c19d..030edca1f0bccaea181c570dcd1ee76f1a4005a4 100644 (file)
@@ -4446,6 +4446,24 @@ struct ast_str *ast_channel_dialed_causes_channels(const struct ast_channel *cha
  */
 struct ast_control_pvt_cause_code *ast_channel_dialed_causes_find(const struct ast_channel *chan, const char *chan_name);
 
+/*!
+ * \since 20.17.0, 22.8.0, 23.1.0
+ * \brief Retrieve a ref-counted cause code information structure iterator
+ *
+ * \details
+ * This function makes use of datastore operations on the channel, so
+ * it is important to lock the channel before calling this function.
+ * This function increases the ref count of the returned object, so the
+ * calling function must decrease the reference count when it is finished
+ * with the object.
+ *
+ * \param chan The channel from which to retrieve information
+ * \param chan_name The name of the channel about which to retrieve information
+ * \retval NULL on search failure
+ * \retval Pointer to a ao2_iterator object containing the desired information
+ */
+struct ao2_iterator *ast_channel_dialed_causes_find_multiple(const struct ast_channel *chan, const char *chan_name);
+
 /*!
  * \since 11
  * \brief Add cause code information to the channel
index a81ff92024b9f2f655a695c144f6bb9fc2356fc9..67c0036d68e0bdabc7dc1b929a8870dcf9e7dc4e 100644 (file)
@@ -417,6 +417,7 @@ enum ast_control_transfer {
 struct ast_control_pvt_cause_code {
        char chan_name[AST_CHANNEL_NAME];       /*!< Name of the channel that originated the cause information */
        unsigned int emulate_sip_cause:1;       /*!< Indicates whether this should be used to emulate SIP_CAUSE support */
+       unsigned int cause_extended:1;          /*!< Indicates whether this cause code was retrieved from supplementary sources */
        int ast_cause;                          /*!< Asterisk cause code associated with this message */
        char code[1];                           /*!< Tech-specific cause code information, beginning with the name of the tech */
 };
index 10723277898f3567157f75cf77cf46f3e2bccaf0..7e256c7404b4ed5de0b04ceb0504284f85af01c3 100644 (file)
@@ -1145,23 +1145,68 @@ struct ast_str *ast_channel_dialed_causes_channels(const struct ast_channel *cha
 
 struct ast_control_pvt_cause_code *ast_channel_dialed_causes_find(const struct ast_channel *chan, const char *chan_name)
 {
-       return ao2_find(chan->dialed_causes, chan_name, OBJ_KEY);
+       struct ao2_iterator causes;
+       struct ast_control_pvt_cause_code *cause_code;
+
+       causes = ao2_iterator_init(chan->dialed_causes, 0);
+       while ((cause_code = ao2_iterator_next(&causes))) {
+               if (strcmp(cause_code->chan_name, chan_name)) {
+                       ao2_ref(cause_code, -1);
+                       continue;
+               }
+               if (!cause_code->cause_extended) {
+                       ao2_iterator_destroy(&causes);
+                       return cause_code;
+               }
+               ao2_ref(cause_code, -1);
+       }
+       ao2_iterator_destroy(&causes);
+
+       return NULL;
+}
+
+struct ao2_iterator *ast_channel_dialed_causes_find_multiple(const struct ast_channel *chan, const char *chan_name)
+{
+       struct ao2_iterator *causes;
+       struct ast_control_pvt_cause_code *cause_code;
+
+       causes = ao2_find(chan->dialed_causes, chan_name, OBJ_SEARCH_KEY | OBJ_MULTIPLE);
+       while ((cause_code = ao2_iterator_next(causes))) {
+               ao2_ref(cause_code, -1);
+       }
+       ao2_iterator_destroy(causes);
+
+       return ao2_find(chan->dialed_causes, chan_name, OBJ_SEARCH_KEY | OBJ_MULTIPLE);
+}
+
+static int remove_dialstatus_cb(void *obj, void *arg, int flags)
+{
+       struct ast_control_pvt_cause_code *cause_code = obj;
+       char *str = ast_tech_to_upper(ast_strdupa(arg));
+       char *pc_str = ast_tech_to_upper(ast_strdupa(cause_code->chan_name));
+
+       if (cause_code->cause_extended) {
+               return 0;
+       }
+       return !strcmp(pc_str, str) ? CMP_MATCH | CMP_STOP : 0;
 }
 
 int ast_channel_dialed_causes_add(const struct ast_channel *chan, const struct ast_control_pvt_cause_code *cause_code, int datalen)
 {
        struct ast_control_pvt_cause_code *ao2_cause_code;
-       ao2_find(chan->dialed_causes, cause_code->chan_name, OBJ_KEY | OBJ_UNLINK | OBJ_NODATA);
-       ao2_cause_code = ao2_alloc(datalen, NULL);
+       char *arg = ast_strdupa(cause_code->chan_name);
+
+       ao2_callback(chan->dialed_causes, OBJ_MULTIPLE | OBJ_NODATA | OBJ_UNLINK, remove_dialstatus_cb, arg);
 
+       ao2_cause_code = ao2_alloc(datalen, NULL);
        if (ao2_cause_code) {
                memcpy(ao2_cause_code, cause_code, datalen);
                ao2_link(chan->dialed_causes, ao2_cause_code);
                ao2_ref(ao2_cause_code, -1);
                return 0;
-       } else {
-               return -1;
        }
+
+       return -1;
 }
 
 void ast_channel_dialed_causes_clear(const struct ast_channel *chan)
index 7c38a1632cb27d715c8c4ba7610d89301bd52f28..e4e4e1b12fc0363c8c4e02852df61ccf43b3a9f1 100644 (file)
@@ -38,7 +38,7 @@ static void rfc3326_use_reason_header(struct ast_sip_session *session, struct pj
 {
        static const pj_str_t str_reason = { "Reason", 6 };
        pjsip_generic_string_hdr *header;
-       char buf[20];
+       char buf[128];
        char *cause;
        int code_q850 = 0, code_sip = 0;
 
@@ -46,9 +46,11 @@ static void rfc3326_use_reason_header(struct ast_sip_session *session, struct pj
        for (; header;
                header = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_reason, header->next)) {
                int cause_q850, cause_sip;
+               struct ast_control_pvt_cause_code *cause_code;
+               int data_size = sizeof(*cause_code);
+
                ast_copy_pj_str(buf, &header->hvalue, sizeof(buf));
                cause = ast_skip_blanks(buf);
-
                cause_q850 = !strncasecmp(cause, "Q.850", 5);
                cause_sip = !strncasecmp(cause, "SIP", 3);
                if ((cause_q850 || cause_sip) && (cause = strstr(cause, "cause="))) {
@@ -56,6 +58,24 @@ static void rfc3326_use_reason_header(struct ast_sip_session *session, struct pj
                        if (sscanf(cause, "cause=%30d", code) != 1) {
                                *code = 0;
                        }
+
+                       /* Safe */
+                       /* Build and send the tech-specific cause information */
+                       /* size of the string making up the cause code is "SIP " + reason length */
+                       data_size += 4 + strlen(cause) + 1;
+                       cause_code = ast_alloca(data_size);
+                       memset(cause_code, 0, data_size);
+                       ast_copy_string(cause_code->chan_name, ast_channel_name(session->channel), AST_CHANNEL_NAME);
+                       snprintf(cause_code->code, data_size, "SIP %s", cause);
+
+                       cause_code->cause_extended = 1;
+                       if (code_q850) {
+                               cause_code->ast_cause = *code & 0x7;
+                       } else if (code_sip) {
+                               cause_code->ast_cause = ast_sip_hangup_sip2cause(*code);
+                       }
+                       ast_queue_control_data(session->channel, AST_CONTROL_PVT_CAUSE_CODE, cause_code, data_size);
+                       ast_channel_hangupcause_hash_set(session->channel, cause_code, data_size);
                }
        }