core/ari/pjsip: Add refer mechanism

This change adds support for refers that are not session based. It
includes a refer implementation for the PJSIP technology which results
in out-of-dialog REFERs being sent to a PJSIP endpoint. These can be
triggered using the new ARI endpoint `/endpoints/refer`.

Resolves: #71

UserNote: There is a new ARI endpoint `/endpoints/refer` for referring
an endpoint to some URI or endpoint.
This commit is contained in:
Maximilian Fridrich
2023-05-10 15:53:33 +02:00
committed by asterisk-org-access-app[bot]
parent d16046e41f
commit 51a7b18038
13 changed files with 2815 additions and 736 deletions

View File

@@ -57,6 +57,7 @@ int ast_msg_init(void); /*!< Provided by message.c */
void ast_msg_shutdown(void); /*!< Provided by message.c */
int aco_init(void); /*!< Provided by config_options.c */
int dns_core_init(void); /*!< Provided by dns_core.c */
int ast_refer_init(void); /*!< Provided by refer.c */
/*!
* \brief Initialize malloc debug phase 1.

325
include/asterisk/refer.h Normal file
View File

@@ -0,0 +1,325 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2023, Commend International
*
* Maximilian Fridrich <m.fridrich@commend.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*!
* \file
*
* \brief Out-of-call refer support
*
* \author Maximilian Fridrich <m.fridrich@commend.com>
*
* The purpose of this API is to provide support for refers that
* are not session based. The refers are passed into the Asterisk core
* to be routed through the dialplan or another interface and potentially
* sent back out through a refer technology that has been registered
* through this API.
*/
#ifndef __AST_REFER_H__
#define __AST_REFER_H__
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
/*!
* \brief A refer structure.
*
* This is an opaque type that represents a refer.
*/
struct ast_refer;
/*!
* \brief A refer technology
*
* A refer technology is capable of transmitting text refers.
*/
struct ast_refer_tech {
/*!
* \brief Name of this refer technology
*
* This is the name that comes at the beginning of a URI for refers
* that should be sent to this refer technology implementation.
* For example, refers sent to "pjsip:m.fridrich@commend.com" would be
* passed to the ast_refer_tech with a name of "pjsip".
*/
const char * const name;
/*!
* \brief Send a refer.
*
* \param refer The refer to send
*
* The fields of the ast_refer are guaranteed not to change during the
* duration of this function call.
*
* \retval 0 success
* \retval non-zero failure
*/
int (* const refer_send)(const struct ast_refer *refer);
};
/*!
* \brief Register a refer technology
*
* \retval 0 success
* \retval non-zero failure
*/
int ast_refer_tech_register(const struct ast_refer_tech *tech);
/*!
* \brief Unregister a refer technology.
*
* \retval 0 success
* \retval non-zero failure
*/
int ast_refer_tech_unregister(const struct ast_refer_tech *tech);
/*!
* \brief Allocate a refer.
*
* Allocate a refer for the purposes of passing it into the Asterisk core
* to be routed through the dialplan. This refer must be destroyed using
* ast_refer_destroy().
*
* \return A refer object. This function will return NULL if an allocation
* error occurs.
*/
struct ast_refer *ast_refer_alloc(void);
/*!
* \brief Destroy an ast_refer
*
* \retval NULL always.
*/
struct ast_refer *ast_refer_destroy(struct ast_refer *refer);
/*!
* \brief Bump a refer's ref count
*/
struct ast_refer *ast_refer_ref(struct ast_refer *refer);
/*!
* \brief Set the 'to' URI of a refer
*
* \retval 0 success
* \retval -1 failure
*/
int __attribute__((format(printf, 2, 3)))
ast_refer_set_to(struct ast_refer *refer, const char *fmt, ...);
/*!
* \brief Set the 'from' URI of a refer
*
* \retval 0 success
* \retval -1 failure
*/
int __attribute__((format(printf, 2, 3)))
ast_refer_set_from(struct ast_refer *refer, const char *fmt, ...);
/*!
* \brief Set the 'refer_to' URI of a refer
*
* \retval 0 success
* \retval -1 failure
*/
int __attribute__((format(printf, 2, 3)))
ast_refer_set_refer_to(struct ast_refer *refer, const char *fmt, ...);
/*!
* \brief Set the 'to_self' value of a refer
*
* \retval 0 success
* \retval -1 failure
*/
int ast_refer_set_to_self(struct ast_refer *refer, int val);
/*!
* \brief Set the technology associated with this refer
*
* \retval 0 success
* \retval -1 failure
*/
int __attribute__((format(printf, 2, 3)))
ast_refer_set_tech(struct ast_refer *refer, const char *fmt, ...);
/*!
* \brief Set the technology's endpoint associated with this refer
*
* \retval 0 success
* \retval -1 failure
*/
int __attribute__((format(printf, 2, 3)))
ast_refer_set_endpoint(struct ast_refer *refer, const char *fmt, ...);
/*!
* \brief Set a variable on the refer being sent to a refer tech directly.
* \note Setting a variable that already exists overwrites the existing variable value
*
* \param refer
* \param name Name of variable to set
* \param value Value of variable to set
*
* \retval 0 success
* \retval -1 failure
*/
int ast_refer_set_var_outbound(struct ast_refer *refer, const char *name, const char *value);
/*!
* \brief Get the specified variable on the refer and unlink it from the container of variables
* \note The return value must be freed by the caller.
*
* \param refer
* \param name Name of variable to get
*
* \return The value associated with variable "name". NULL if variable not found.
*/
char *ast_refer_get_var_and_unlink(struct ast_refer *refer, const char *name);
/*!
* \brief Get the specified variable on the refer
* \note The return value is valid only as long as the ast_refer is valid. Hold a reference
* to the refer if you plan on storing the return value. It is possible to re-set the
* same refer var name (with ast_refer_set_var_outbound passing the variable name)
* while holding a pointer to the result of this function.
*
* \param refer
* \param name Name of variable to get
*
* \return The value associated with variable "name". NULL if variable not found.
*/
const char *ast_refer_get_var(struct ast_refer *refer, const char *name);
/*!
* \brief Get the "refer-to" value of a refer.
* \note The return value is valid only as long as the ast_refer is valid. Hold a reference
* to the refer if you plan on storing the return value.
*
* \param refer The refer to get the "refer-to" value from
*
* \return The "refer-to" value of the refer, encoded in UTF-8.
*/
const char *ast_refer_get_refer_to(const struct ast_refer *refer);
/*!
* \brief Retrieve the source of this refer
*
* \param refer The refer to get the soure from
*
* \return The source of the refer
* \retval NULL or empty string if the refer has no source
*/
const char *ast_refer_get_from(const struct ast_refer *refer);
/*!
* \brief Retrieve the destination of this refer
*
* \param refer The refer to get the destination from
*
* \return The destination of the refer
* \retval NULL or empty string if the refer has no destination
*/
const char *ast_refer_get_to(const struct ast_refer *refer);
/*!
* \brief Retrieve the "to_self" value of this refer
*
* \param refer The refer to get the destination from
*
* \return The to_self value of the refer
*/
int ast_refer_get_to_self(const struct ast_refer *refer);
/*!
* \brief Retrieve the technology associated with this refer
*
* \param refer The refer to get the technology from
*
* \return The technology of the refer
* \retval NULL or empty string if the refer has no associated technology
*/
const char *ast_refer_get_tech(const struct ast_refer *refer);
/*!
* \brief Retrieve the endpoint associated with this refer
*
* \param refer The refer to get the endpoint from
*
* \return The endpoint associated with the refer
* \retval NULL or empty string if the refer has no associated endpoint
*/
const char *ast_refer_get_endpoint(const struct ast_refer *refer);
/*!
* \brief Send a refer directly to an endpoint.
*
* Regardless of the return value of this function, this function will take
* care of ensuring that the refer object is properly destroyed when needed.
*
* \retval 0 refer successfully queued to be sent out
* \retval non-zero failure, refer not get sent out.
*/
int ast_refer_send(struct ast_refer *refer);
/*!
* \brief Opaque iterator for refer variables
*/
struct ast_refer_var_iterator;
/*!
* \brief Create a new refer variable iterator
* \param refer A refer whose variables are to be iterated over
*
* \return An opaque pointer to the new iterator
*/
struct ast_refer_var_iterator *ast_refer_var_iterator_init(const struct ast_refer *refer);
/*!
* \brief Get the next variable name and value
*
* \param iter An iterator created with ast_refer_var_iterator_init
* \param name A pointer to the name result pointer
* \param value A pointer to the value result pointer
*
* \note The refcount to iter->current_used must be decremented by the caller
* by calling ast_refer_var_unref_current.
*
* \retval 0 No more entries
* \retval 1 Valid entry
*/
int ast_refer_var_iterator_next(struct ast_refer_var_iterator *iter, const char **name, const char **value);
/*!
* \brief Destroy a refer variable iterator
* \param iter Iterator to be destroyed
*/
void ast_refer_var_iterator_destroy(struct ast_refer_var_iterator *iter);
/*!
* \brief Unref a refer var from inside an iterator loop
*/
void ast_refer_var_unref_current(struct ast_refer_var_iterator *iter);
/*!
* @}
*/
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
#endif /* __AST_REFER_H__ */

View File

@@ -339,6 +339,20 @@ struct ast_sip_nat_hook {
void (*outgoing_external_message)(struct pjsip_tx_data *tdata, struct ast_sip_transport *transport);
};
/*! \brief Structure which contains information about a transport */
struct ast_sip_request_transport_details {
/*! \brief Type of transport */
enum ast_transport type;
/*! \brief Potential pointer to the transport itself, if UDP */
pjsip_transport *transport;
/*! \brief Potential pointer to the transport factory itself, if TCP/TLS */
pjsip_tpfactory *factory;
/*! \brief Local address for transport */
pj_str_t local_address;
/*! \brief Local port for transport */
int local_port;
};
/*!
* \brief The kind of security negotiation
*/
@@ -3506,18 +3520,65 @@ struct ast_threadpool *ast_sip_threadpool(void);
* \brief Retrieve transport state
* \since 13.7.1
*
* @param transport_id
* @returns transport_state
* \param transport_id
* \retval transport_state
*
* \note ao2_cleanup(...) or ao2_ref(..., -1) must be called on the returned object
*/
struct ast_sip_transport_state *ast_sip_get_transport_state(const char *transport_id);
/*!
* \brief Return the SIP URI of the Contact header
*
* \param tdata
* \retval Pointer to SIP URI of Contact
* \retval NULL if Contact header not found or not a SIP(S) URI
*
* \note Do not free the returned object.
*/
pjsip_sip_uri *ast_sip_get_contact_sip_uri(pjsip_tx_data *tdata);
/*!
* \brief Returns the transport state currently in use based on request transport details
*
* \param details
* \retval transport_state
*
* \note ao2_cleanup(...) or ao2_ref(..., -1) must be called on the returned object
*/
struct ast_sip_transport_state *ast_sip_find_transport_state_in_use(struct ast_sip_request_transport_details *details);
/*!
* \brief Sets request transport details based on tdata
*
* \param details pre-allocated request transport details to set
* \param tdata
* \param use_ipv6 if non-zero, ipv6 transports will be considered
* \retval 0 success
* \retval -1 failure
*/
int ast_sip_set_request_transport_details(struct ast_sip_request_transport_details *details, pjsip_tx_data *tdata, int use_ipv6);
/*!
* \brief Replace domain and port of SIP URI to point to (external) signaling address of this Asterisk instance
*
* \param uri
* \param tdata
*
* \retval 0 success
* \retval -1 failure
*
* \note Uses domain and port in Contact header if it exists, otherwise the local URI of the dialog is used if the
* message is sent within the context of a dialog. Further, NAT settings are considered - i.e. if the target
* is not in the localnet, the external_signaling_address and port are used.
*/
int ast_sip_rewrite_uri_to_local(pjsip_sip_uri *uri, pjsip_tx_data *tdata);
/*!
* \brief Retrieves all transport states
* \since 13.7.1
*
* @returns ao2_container
* \retval ao2_container
*
* \note ao2_cleanup(...) or ao2_ref(..., -1) must be called on the returned object
*/
@@ -3647,6 +3708,105 @@ int ast_sip_set_id_from_invite(struct pjsip_rx_data *rdata, struct ast_party_id
void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr,
const struct ast_party_id *id);
/*!
* \brief Retrieves an endpoint and URI from the "to" string.
*
* This URI is used as the Request URI.
*
* Expects the given 'to' to be in one of the following formats:
* Why we allow so many is a mystery.
*
* Basic:
*
* endpoint : We'll get URI from the default aor/contact
* endpoint/aor : We'll get the URI from the specific aor/contact
* endpoint@domain : We toss the domain part and just use the endpoint
*
* These all use the endpoint and specified URI:
* \verbatim
endpoint/<sip[s]:host>
endpoint/<sip[s]:user@host>
endpoint/"Bob" <sip[s]:host>
endpoint/"Bob" <sip[s]:user@host>
endpoint/sip[s]:host
endpoint/sip[s]:user@host
endpoint/host
endpoint/user@host
\endverbatim
*
* These all use the default endpoint and specified URI:
* \verbatim
<sip[s]:host>
<sip[s]:user@host>
"Bob" <sip[s]:host>
"Bob" <sip[s]:user@host>
sip[s]:host
sip[s]:user@host
\endverbatim
*
* These use the default endpoint and specified host:
* \verbatim
host
user@host
\endverbatim
*
* This form is similar to a dialstring:
* \verbatim
PJSIP/user@endpoint
\endverbatim
*
* In this case, the user will be added to the endpoint contact's URI.
* If the contact URI already has a user, it will be replaced.
*
* The ones that have the sip[s] scheme are the easiest to parse.
* The rest all have some issue.
*
* endpoint vs host : We have to test for endpoint first
* endpoint/aor vs endpoint/host : We have to test for aor first
* What if there's an aor with the same
* name as the host?
* endpoint@domain vs user@host : We have to test for endpoint first.
* What if there's an endpoint with the
* same name as the user?
*
* \param to 'To' field with possible endpoint
* \param get_default_outbound If nonzero, try to retrieve the default
* outbound endpoint if no endpoint was found.
* Otherwise, return NULL if no endpoint was found.
* \param uri Pointer to a char* which will be set to the URI.
* Always must be ast_free'd by the caller - even if the return value is NULL!
*
* \note The logic below could probably be condensed but then it wouldn't be
* as clear.
*/
struct ast_sip_endpoint *ast_sip_get_endpoint(const char *to, int get_default_outbound, char **uri);
/*!
* \brief Replace the To URI in the tdata with the supplied one
*
* \param tdata the outbound message data structure
* \param to URI to replace the To URI with. Must be a valid SIP URI.
*
* \retval 0: success, -1: failure
*/
int ast_sip_update_to_uri(pjsip_tx_data *tdata, const char *to);
/*!
* \brief Overwrite fields in the outbound 'From' header
*
* The outbound 'From' header is created/added in ast_sip_create_request with
* default data. If available that data may be info specified in the 'from_user'
* and 'from_domain' options found on the endpoint. That information will be
* overwritten with data in the given 'from' parameter.
*
* \param tdata the outbound message data structure
* \param from info to copy into the header.
* Can be either a SIP URI, or in the format user[@domain]
*
* \retval 0: success, -1: failure
*/
int ast_sip_update_from(pjsip_tx_data *tdata, char *from);
/*!
* \brief Retrieve the unidentified request security event thresholds
* \since 13.8.0