#include "sip/include/config_parser.h"
#include "sip/include/reqresp_parser.h"
#include "sip/include/sip_utils.h"
+#include "asterisk/ccss.h"
+#include "asterisk/xml.h"
#include "sip/include/dialog.h"
#include "sip/include/dialplan_functions.h"
{ SIP_UPDATE, NO_RTP, "UPDATE", CAN_NOT_CREATE_DIALOG },
{ SIP_INFO, NO_RTP, "INFO", CAN_NOT_CREATE_DIALOG },
{ SIP_CANCEL, NO_RTP, "CANCEL", CAN_NOT_CREATE_DIALOG },
- { SIP_PUBLISH, NO_RTP, "PUBLISH", CAN_CREATE_DIALOG_UNSUPPORTED_METHOD },
+ { SIP_PUBLISH, NO_RTP, "PUBLISH", CAN_CREATE_DIALOG },
{ SIP_PING, NO_RTP, "PING", CAN_CREATE_DIALOG_UNSUPPORTED_METHOD }
};
static int global_dynamic_exclude_static = 0; /*!< Exclude static peers from contact registrations */
/*@}*/
+/*!
+ * We use libxml2 in order to parse XML that may appear in the body of a SIP message. Currently,
+ * the only usage is for parsing PIDF bodies of incoming PUBLISH requests in the call-completion
+ * event package. This variable is set at module load time and may be checked at runtime to determine
+ * if XML parsing support was found.
+ */
+static int can_parse_xml;
+
/*! \name Object counters @{
* \bug These counters are not handled in a thread-safe way ast_atomic_fetchadd_int()
* should be used to modify these values. */
static const int HASH_DIALOG_SIZE = 563;
#endif
+static const struct {
+ enum ast_cc_service_type service;
+ const char *service_string;
+} sip_cc_service_map [] = {
+ [AST_CC_NONE] = { AST_CC_NONE, "" },
+ [AST_CC_CCBS] = { AST_CC_CCBS, "BS" },
+ [AST_CC_CCNR] = { AST_CC_CCNR, "NR" },
+ [AST_CC_CCNL] = { AST_CC_CCNL, "NL" },
+};
+
+static enum ast_cc_service_type service_string_to_service_type(const char * const service_string)
+{
+ enum ast_cc_service_type service;
+ for (service = AST_CC_CCBS; service <= AST_CC_CCNL; ++service) {
+ if (!strcasecmp(service_string, sip_cc_service_map[service].service_string)) {
+ return service;
+ }
+ }
+ return AST_CC_NONE;
+}
+
+static const struct {
+ enum sip_cc_notify_state state;
+ const char *state_string;
+} sip_cc_notify_state_map [] = {
+ [CC_QUEUED] = {CC_QUEUED, "cc-state: queued"},
+ [CC_READY] = {CC_READY, "cc-state: ready"},
+};
+
+AST_LIST_HEAD_STATIC(epa_static_data_list, epa_backend);
+
+static int sip_epa_register(const struct epa_static_data *static_data)
+{
+ struct epa_backend *backend = ast_calloc(1, sizeof(*backend));
+
+ if (!backend) {
+ return -1;
+ }
+
+ backend->static_data = static_data;
+
+ AST_LIST_LOCK(&epa_static_data_list);
+ AST_LIST_INSERT_TAIL(&epa_static_data_list, backend, next);
+ AST_LIST_UNLOCK(&epa_static_data_list);
+ return 0;
+}
+
+static void cc_handle_publish_error(struct sip_pvt *pvt, const int resp, struct sip_request *req, struct sip_epa_entry *epa_entry);
+
+static void cc_epa_destructor(void *data)
+{
+ struct sip_epa_entry *epa_entry = data;
+ struct cc_epa_entry *cc_entry = epa_entry->instance_data;
+ ast_free(cc_entry);
+}
+
+static const struct epa_static_data cc_epa_static_data = {
+ .event = CALL_COMPLETION,
+ .name = "call-completion",
+ .handle_error = cc_handle_publish_error,
+ .destructor = cc_epa_destructor,
+};
+
+static const struct epa_static_data *find_static_data(const char * const event_package)
+{
+ const struct epa_backend *backend = NULL;
+
+ AST_LIST_LOCK(&epa_static_data_list);
+ AST_LIST_TRAVERSE(&epa_static_data_list, backend, next) {
+ if (!strcmp(backend->static_data->name, event_package)) {
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&epa_static_data_list);
+ return backend ? backend->static_data : NULL;
+}
+
+static struct sip_epa_entry *create_epa_entry (const char * const event_package, const char * const destination)
+{
+ struct sip_epa_entry *epa_entry;
+ const struct epa_static_data *static_data;
+
+ if (!(static_data = find_static_data(event_package))) {
+ return NULL;
+ }
+
+ if (!(epa_entry = ao2_t_alloc(sizeof(*epa_entry), static_data->destructor, "Allocate new EPA entry"))) {
+ return NULL;
+ }
+
+ epa_entry->static_data = static_data;
+ ast_copy_string(epa_entry->destination, destination, sizeof(epa_entry->destination));
+ return epa_entry;
+}
+
+/*!
+ * Used to create new entity IDs by ESCs.
+ */
+static int esc_etag_counter;
+static const int DEFAULT_PUBLISH_EXPIRES = 3600;
+
+#ifdef HAVE_LIBXML2
+static int cc_esc_publish_handler(struct sip_pvt *pvt, struct sip_request *req, struct event_state_compositor *esc, struct sip_esc_entry *esc_entry);
+
+static const struct sip_esc_publish_callbacks cc_esc_publish_callbacks = {
+ .initial_handler = cc_esc_publish_handler,
+ .modify_handler = cc_esc_publish_handler,
+};
+#endif
+
+/*!
+ * \brief The Event State Compositors
+ *
+ * An Event State Compositor is an entity which
+ * accepts PUBLISH requests and acts appropriately
+ * based on these requests.
+ *
+ * The actual event_state_compositor structure is simply
+ * an ao2_container of sip_esc_entrys. When an incoming
+ * PUBLISH is received, we can match the appropriate sip_esc_entry
+ * using the entity ID of the incoming PUBLISH.
+ */
+static struct event_state_compositor {
+ enum subscriptiontype event;
+ const char * name;
+ const struct sip_esc_publish_callbacks *callbacks;
+ struct ao2_container *compositor;
+} event_state_compositors [] = {
+#ifdef HAVE_LIBXML2
+ {CALL_COMPLETION, "call-completion", &cc_esc_publish_callbacks},
+#endif
+};
+
+static const int ESC_MAX_BUCKETS = 37;
+
+static void esc_entry_destructor(void *obj)
+{
+ struct sip_esc_entry *esc_entry = obj;
+ if (esc_entry->sched_id > -1) {
+ AST_SCHED_DEL(sched, esc_entry->sched_id);
+ }
+}
+
+static int esc_hash_fn(const void *obj, const int flags)
+{
+ const struct sip_esc_entry *entry = obj;
+ return ast_str_hash(entry->entity_tag);
+}
+
+static int esc_cmp_fn(void *obj, void *arg, int flags)
+{
+ struct sip_esc_entry *entry1 = obj;
+ struct sip_esc_entry *entry2 = arg;
+
+ return (!strcmp(entry1->entity_tag, entry2->entity_tag)) ? (CMP_MATCH | CMP_STOP) : 0;
+}
+
+static struct event_state_compositor *get_esc(const char * const event_package) {
+ int i;
+ for (i = 0; i < ARRAY_LEN(event_state_compositors); i++) {
+ if (!strcasecmp(event_package, event_state_compositors[i].name)) {
+ return &event_state_compositors[i];
+ }
+ }
+ return NULL;
+}
+
+static struct sip_esc_entry *get_esc_entry(const char * entity_tag, struct event_state_compositor *esc) {
+ struct sip_esc_entry *entry;
+ struct sip_esc_entry finder;
+
+ ast_copy_string(finder.entity_tag, entity_tag, sizeof(finder.entity_tag));
+
+ entry = ao2_find(esc->compositor, &finder, OBJ_POINTER);
+
+ return entry;
+}
+
+static int publish_expire(const void *data)
+{
+ struct sip_esc_entry *esc_entry = (struct sip_esc_entry *) data;
+ struct event_state_compositor *esc = get_esc(esc_entry->event);
+
+ ast_assert(esc != NULL);
+
+ ao2_unlink(esc->compositor, esc_entry);
+ ao2_ref(esc_entry, -1);
+ return 0;
+}
+
+static void create_new_sip_etag(struct sip_esc_entry *esc_entry, int is_linked)
+{
+ int new_etag = ast_atomic_fetchadd_int(&esc_etag_counter, +1);
+ struct event_state_compositor *esc = get_esc(esc_entry->event);
+
+ ast_assert(esc != NULL);
+ if (is_linked) {
+ ao2_unlink(esc->compositor, esc_entry);
+ }
+ snprintf(esc_entry->entity_tag, sizeof(esc_entry->entity_tag), "%d", new_etag);
+ ao2_link(esc->compositor, esc_entry);
+}
+
+static struct sip_esc_entry *create_esc_entry(struct event_state_compositor *esc, struct sip_request *req, const int expires)
+{
+ struct sip_esc_entry *esc_entry;
+ int expires_ms;
+
+ if (!(esc_entry = ao2_alloc(sizeof(*esc_entry), esc_entry_destructor))) {
+ return NULL;
+ }
+
+ esc_entry->event = esc->name;
+
+ expires_ms = expires * 1000;
+ /* Bump refcount for scheduler */
+ ao2_ref(esc_entry, +1);
+ esc_entry->sched_id = ast_sched_add(sched, expires_ms, publish_expire, esc_entry);
+
+ /* Note: This links the esc_entry into the ESC properly */
+ create_new_sip_etag(esc_entry, 0);
+
+ return esc_entry;
+}
+
+static int initialize_escs(void)
+{
+ int i, res = 0;
+ for (i = 0; i < ARRAY_LEN(event_state_compositors); i++) {
+ if (!((event_state_compositors[i].compositor) =
+ ao2_container_alloc(ESC_MAX_BUCKETS, esc_hash_fn, esc_cmp_fn))) {
+ res = -1;
+ }
+ }
+ return res;
+}
+
+static void destroy_escs(void)
+{
+ int i;
+ for (i = 0; i < ARRAY_LEN(event_state_compositors); i++) {
+ ao2_ref(event_state_compositors[i].compositor, -1);
+ }
+}
+
/*! \brief
* Here we implement the container for dialogs (sip_pvt), defining
* generic wrapper functions to ease the transition from the current
static int sipsock_read(int *id, int fd, short events, void *ignore);
static int __sip_xmit(struct sip_pvt *p, struct ast_str *data, int len);
static int __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, struct ast_str *data, int len, int fatal, int sipmethod);
+static void add_cc_call_info_to_response(struct sip_pvt *p, struct sip_request *resp);
static int __transmit_response(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable);
static int retrans_pkt(const void *data);
static int transmit_response_using_temp(ast_string_field callid, struct sockaddr_in *sin, int useglobal_nat, const int intended_method, const struct sip_request *req, const char *msg);
static void transmit_fake_auth_response(struct sip_pvt *p, int sipmethod, struct sip_request *req, enum xmittype reliable);
static int transmit_request(struct sip_pvt *p, int sipmethod, int inc, enum xmittype reliable, int newbranch);
static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, int seqno, enum xmittype reliable, int newbranch);
-static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init);
+static int transmit_publish(struct sip_epa_entry *epa_entry, enum sip_publish_type publish_type, const char * const explicit_uri);
+static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init, const char * const explicit_uri);
static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int oldsdp);
static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration);
static int transmit_info_with_vidupdate(struct sip_pvt *p);
static int transmit_refer(struct sip_pvt *p, const char *dest);
static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs, const char *vmexten);
static int transmit_notify_with_sipfrag(struct sip_pvt *p, int cseq, char *message, int terminate);
+static int transmit_cc_notify(struct ast_cc_agent *agent, struct sip_pvt *subscription, enum sip_cc_notify_state state);
static int transmit_register(struct sip_registry *r, int sipmethod, const char *auth, const char *authheader);
static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno);
static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno);
static void check_via(struct sip_pvt *p, struct sip_request *req);
static int get_rpid(struct sip_pvt *p, struct sip_request *oreq);
static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq, char **name, char **number, int *reason);
-static int get_destination(struct sip_pvt *p, struct sip_request *oreq);
+static int get_destination(struct sip_pvt *p, struct sip_request *oreq, int *cc_recall_core_id);
static int get_msg_text(char *buf, int len, struct sip_request *req, int addnewline);
static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout);
static void update_connectedline(struct sip_pvt *p, const void *data, size_t datalen);
static void initialize_initreq(struct sip_pvt *p, struct sip_request *req);
static int init_req(struct sip_request *req, int sipmethod, const char *recip);
static int reqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, int seqno, int newbranch);
-static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod);
+static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, const char * const explicit_uri);
static int init_resp(struct sip_request *resp, const char *msg);
static inline int resp_needs_contact(const char *msg, enum sipmethod method);
static int respprep(struct sip_request *resp, struct sip_pvt *p, const char *msg, const struct sip_request *req);
static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *current, struct sip_request *req, int seqno, int *nounlock);
/*------Response handling functions */
+static void handle_response_publish(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
static void handle_response_notify(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
static void handle_response_refer(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
*/
struct ast_channel_tech sip_tech_info;
-/*! \brief Working TLS connection configuration */
-static struct ast_tls_config sip_tls_cfg;
-
-/*! \brief Default TLS connection configuration */
-static struct ast_tls_config default_tls_cfg;
+static int sip_cc_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan);
+static int sip_cc_agent_start_offer_timer(struct ast_cc_agent *agent);
+static int sip_cc_agent_stop_offer_timer(struct ast_cc_agent *agent);
+static void sip_cc_agent_ack(struct ast_cc_agent *agent);
+static int sip_cc_agent_status_request(struct ast_cc_agent *agent);
+static int sip_cc_agent_start_monitoring(struct ast_cc_agent *agent);
+static int sip_cc_agent_recall(struct ast_cc_agent *agent);
+static void sip_cc_agent_destructor(struct ast_cc_agent *agent);
-/*! \brief The TCP server definition */
-static struct ast_tcptls_session_args sip_tcp_desc = {
- .accept_fd = -1,
- .master = AST_PTHREADT_NULL,
- .tls_cfg = NULL,
- .poll_timeout = -1,
- .name = "SIP TCP server",
- .accept_fn = ast_tcptls_server_root,
- .worker_fn = sip_tcp_worker_fn,
+static struct ast_cc_agent_callbacks sip_cc_agent_callbacks = {
+ .type = "SIP",
+ .init = sip_cc_agent_init,
+ .start_offer_timer = sip_cc_agent_start_offer_timer,
+ .stop_offer_timer = sip_cc_agent_stop_offer_timer,
+ .ack = sip_cc_agent_ack,
+ .status_request = sip_cc_agent_status_request,
+ .start_monitoring = sip_cc_agent_start_monitoring,
+ .callee_available = sip_cc_agent_recall,
+ .destructor = sip_cc_agent_destructor,
};
-/*! \brief The TCP/TLS server definition */
-static struct ast_tcptls_session_args sip_tls_desc = {
- .accept_fd = -1,
- .master = AST_PTHREADT_NULL,
- .tls_cfg = &sip_tls_cfg,
- .poll_timeout = -1,
- .name = "SIP TLS server",
- .accept_fn = ast_tcptls_server_root,
- .worker_fn = sip_tcp_worker_fn,
-};
+static int find_by_notify_uri_helper(void *obj, void *arg, int flags)
+{
+ struct ast_cc_agent *agent = obj;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ const char *uri = arg;
-/*! \brief Append to SIP dialog history
- \return Always returns 0 */
-#define append_history(p, event, fmt , args... ) append_history_full(p, "%-15s " fmt, event, ## args)
+ return !strcmp(agent_pvt->notify_uri, uri) ? CMP_MATCH | CMP_STOP : 0;
+}
-struct sip_pvt *dialog_ref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func)
+static struct ast_cc_agent *find_sip_cc_agent_by_notify_uri(const char * const uri)
{
- if (p)
-#ifdef REF_DEBUG
- __ao2_ref_debug(p, 1, tag, file, line, func);
-#else
- ao2_ref(p, 1);
-#endif
- else
- ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n");
- return p;
+ struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_notify_uri_helper, (char *)uri, "SIP");
+ return agent;
}
-struct sip_pvt *dialog_unref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func)
+static int find_by_subscribe_uri_helper(void *obj, void *arg, int flags)
{
- if (p)
-#ifdef REF_DEBUG
- __ao2_ref_debug(p, -1, tag, file, line, func);
-#else
- ao2_ref(p, -1);
-#endif
- return NULL;
+ struct ast_cc_agent *agent = obj;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ const char *uri = arg;
+
+ return !strcmp(agent_pvt->subscribe_uri, uri) ? CMP_MATCH | CMP_STOP : 0;
}
-/*! \brief map from an integer value to a string.
- * If no match is found, return errorstring
- */
-static const char *map_x_s(const struct _map_x_s *table, int x, const char *errorstring)
+static struct ast_cc_agent *find_sip_cc_agent_by_subscribe_uri(const char * const uri)
{
- const struct _map_x_s *cur;
-
- for (cur = table; cur->s; cur++)
- if (cur->x == x)
- return cur->s;
- return errorstring;
+ struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_subscribe_uri_helper, (char *)uri, "SIP");
+ return agent;
}
-/*! \brief map from a string to an integer value, case insensitive.
- * If no match is found, return errorvalue.
- */
-static int map_s_x(const struct _map_x_s *table, const char *s, int errorvalue)
+static int find_by_callid_helper(void *obj, void *arg, int flags)
{
- const struct _map_x_s *cur;
+ struct ast_cc_agent *agent = obj;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ struct sip_pvt *call_pvt = arg;
- for (cur = table; cur->s; cur++)
- if (!strcasecmp(cur->s, s))
- return cur->x;
- return errorvalue;
+ return !strcmp(agent_pvt->original_callid, call_pvt->callid) ? CMP_MATCH | CMP_STOP : 0;
}
-static enum AST_REDIRECTING_REASON sip_reason_str_to_code(const char *text)
+static struct ast_cc_agent *find_sip_cc_agent_by_original_callid(struct sip_pvt *pvt)
{
- enum AST_REDIRECTING_REASON ast = AST_REDIRECTING_REASON_UNKNOWN;
- int i;
+ struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_callid_helper, pvt, "SIP");
+ return agent;
+}
- for (i = 0; i < ARRAY_LEN(sip_reason_table); ++i) {
- if (!strcasecmp(text, sip_reason_table[i].text)) {
- ast = sip_reason_table[i].code;
- break;
- }
+static int sip_cc_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan)
+{
+ struct sip_cc_agent_pvt *agent_pvt = ast_calloc(1, sizeof(*agent_pvt));
+ struct sip_pvt *call_pvt = chan->tech_pvt;
+
+ if (!agent_pvt) {
+ return -1;
}
- return ast;
+ ast_assert(!strcmp(chan->tech->type, "SIP"));
+
+ ast_copy_string(agent_pvt->original_callid, call_pvt->callid, sizeof(agent_pvt->original_callid));
+ ast_copy_string(agent_pvt->original_exten, call_pvt->exten, sizeof(agent_pvt->original_exten));
+ agent_pvt->offer_timer_id = -1;
+ agent->private_data = agent_pvt;
+ sip_pvt_lock(call_pvt);
+ ast_set_flag(&call_pvt->flags[0], SIP_OFFER_CC);
+ sip_pvt_unlock(call_pvt);
+ return 0;
}
-static const char *sip_reason_code_to_str(enum AST_REDIRECTING_REASON code)
+static int sip_offer_timer_expire(const void *data)
{
- if (code >= 0 && code < ARRAY_LEN(sip_reason_table)) {
- return sip_reason_table[code].text;
- }
+ struct ast_cc_agent *agent = (struct ast_cc_agent *) data;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
- return "unknown";
+ agent_pvt->offer_timer_id = -1;
+
+ return ast_cc_failed(agent->core_id, "SIP agent %s's offer timer expired", agent->device_name);
}
-/*!
- * \brief generic function for determining if a correct transport is being
- * used to contact a peer
- *
- * this is done as a macro so that the "tmpl" var can be passed either a
- * sip_request or a sip_peer
- */
-#define check_request_transport(peer, tmpl) ({ \
- int ret = 0; \
- if (peer->socket.type == tmpl->socket.type) \
- ; \
- else if (!(peer->transports & tmpl->socket.type)) {\
- ast_log(LOG_ERROR, \
- "'%s' is not a valid transport for '%s'. we only use '%s'! ending call.\n", \
- get_transport(tmpl->socket.type), peer->name, get_transport_list(peer->transports) \
- ); \
- ret = 1; \
- } else if (peer->socket.type & SIP_TRANSPORT_TLS) { \
- ast_log(LOG_WARNING, \
- "peer '%s' HAS NOT USED (OR SWITCHED TO) TLS in favor of '%s' (but this was allowed in sip.conf)!\n", \
- peer->name, get_transport(tmpl->socket.type) \
- ); \
- } else { \
- ast_debug(1, \
- "peer '%s' has contacted us over %s even though we prefer %s.\n", \
- peer->name, get_transport(tmpl->socket.type), get_transport(peer->socket.type) \
- ); \
- }\
- (ret); \
-})
+static int sip_cc_agent_start_offer_timer(struct ast_cc_agent *agent)
+{
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ int when;
-/*! \brief
- * duplicate a list of channel variables, \return the copy.
- */
-static struct ast_variable *copy_vars(struct ast_variable *src)
+ when = ast_get_cc_offer_timer(agent->cc_params) * 1000;
+ agent_pvt->offer_timer_id = ast_sched_add(sched, when, sip_offer_timer_expire, agent);
+ return 0;
+}
+
+static int sip_cc_agent_stop_offer_timer(struct ast_cc_agent *agent)
{
- struct ast_variable *res = NULL, *tmp, *v = NULL;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
- for (v = src ; v ; v = v->next) {
- if ((tmp = ast_variable_new(v->name, v->value, v->file))) {
- tmp->next = res;
- res = tmp;
- }
- }
- return res;
+ AST_SCHED_DEL(sched, agent_pvt->offer_timer_id);
+ return 0;
}
-static void tcptls_packet_destructor(void *obj)
+static void sip_cc_agent_ack(struct ast_cc_agent *agent)
{
- struct tcptls_packet *packet = obj;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
- ast_free(packet->data);
+ sip_pvt_lock(agent_pvt->subscribe_pvt);
+ ast_set_flag(&agent_pvt->subscribe_pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ transmit_response(agent_pvt->subscribe_pvt, "200 OK", &agent_pvt->subscribe_pvt->initreq);
+ transmit_cc_notify(agent, agent_pvt->subscribe_pvt, CC_QUEUED);
+ sip_pvt_unlock(agent_pvt->subscribe_pvt);
+ agent_pvt->is_available = TRUE;
}
-static void sip_tcptls_client_args_destructor(void *obj)
+static int sip_cc_agent_status_request(struct ast_cc_agent *agent)
{
- struct ast_tcptls_session_args *args = obj;
- if (args->tls_cfg) {
- ast_free(args->tls_cfg->certfile);
- ast_free(args->tls_cfg->pvtfile);
- ast_free(args->tls_cfg->cipher);
- ast_free(args->tls_cfg->cafile);
- ast_free(args->tls_cfg->capath);
- }
- ast_free(args->tls_cfg);
- ast_free((char *) args->name);
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ enum ast_device_state state = agent_pvt->is_available ? AST_DEVICE_NOT_INUSE : AST_DEVICE_INUSE;
+ return ast_cc_agent_status_response(agent->core_id, state);
}
-static void sip_threadinfo_destructor(void *obj)
+static int sip_cc_agent_start_monitoring(struct ast_cc_agent *agent)
{
- struct sip_threadinfo *th = obj;
- struct tcptls_packet *packet;
- if (th->alert_pipe[1] > -1) {
- close(th->alert_pipe[0]);
+ /* To start monitoring just means to wait for an incoming PUBLISH
+ * to tell us that the caller has become available again. No special
+ * action is needed
+ */
+ return 0;
+}
+
+static int sip_cc_agent_recall(struct ast_cc_agent *agent)
+{
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ /* If we have received a PUBLISH beforehand stating that the caller in question
+ * is not available, we can save ourself a bit of effort here and just report
+ * the caller as busy
+ */
+ if (!agent_pvt->is_available) {
+ return ast_cc_agent_caller_busy(agent->core_id, "Caller %s is busy, reporting to the core",
+ agent->device_name);
}
- if (th->alert_pipe[1] > -1) {
- close(th->alert_pipe[1]);
+ /* Otherwise, we transmit a NOTIFY to the caller and await either
+ * a PUBLISH or an INVITE
+ */
+ sip_pvt_lock(agent_pvt->subscribe_pvt);
+ transmit_cc_notify(agent, agent_pvt->subscribe_pvt, CC_READY);
+ sip_pvt_unlock(agent_pvt->subscribe_pvt);
+ return 0;
+}
+
+static void sip_cc_agent_destructor(struct ast_cc_agent *agent)
+{
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+
+ if (!agent_pvt) {
+ /* The agent constructor probably failed. */
+ return;
}
- th->alert_pipe[0] = th->alert_pipe[1] = -1;
- while ((packet = AST_LIST_REMOVE_HEAD(&th->packet_q, entry))) {
- ao2_t_ref(packet, -1, "thread destruction, removing packet from frame queue");
+ sip_cc_agent_stop_offer_timer(agent);
+ if (agent_pvt->subscribe_pvt) {
+ sip_pvt_lock(agent_pvt->subscribe_pvt);
+ if (!ast_test_flag(&agent_pvt->subscribe_pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) {
+ /* If we haven't sent a 200 OK for the SUBSCRIBE dialog yet, then we need to send a response letting
+ * the subscriber know something went wrong
+ */
+ transmit_response(agent_pvt->subscribe_pvt, "500 Internal Server Error", &agent_pvt->subscribe_pvt->initreq);
+ }
+ sip_pvt_unlock(agent_pvt->subscribe_pvt);
+ agent_pvt->subscribe_pvt = dialog_unref(agent_pvt->subscribe_pvt, "SIP CC agent destructor: Remove ref to subscription");
}
+ ast_free(agent_pvt);
+}
- if (th->tcptls_session) {
- ao2_t_ref(th->tcptls_session, -1, "remove tcptls_session for sip_threadinfo object");
+struct ao2_container *sip_monitor_instances;
+
+static int sip_monitor_instance_hash_fn(const void *obj, const int flags)
+{
+ const struct sip_monitor_instance *monitor_instance = obj;
+ return monitor_instance->core_id;
+}
+
+static int sip_monitor_instance_cmp_fn(void *obj, void *arg, int flags)
+{
+ struct sip_monitor_instance *monitor_instance1 = obj;
+ struct sip_monitor_instance *monitor_instance2 = arg;
+
+ return monitor_instance1->core_id == monitor_instance2->core_id ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static void sip_monitor_instance_destructor(void *data)
+{
+ struct sip_monitor_instance *monitor_instance = data;
+ if (monitor_instance->subscription_pvt) {
+ sip_pvt_lock(monitor_instance->subscription_pvt);
+ monitor_instance->subscription_pvt->expiry = 0;
+ transmit_invite(monitor_instance->subscription_pvt, SIP_SUBSCRIBE, FALSE, 0, monitor_instance->subscribe_uri);
+ sip_pvt_unlock(monitor_instance->subscription_pvt);
+ dialog_unref(monitor_instance->subscription_pvt, "Unref monitor instance ref of subscription pvt");
+ }
+ if (monitor_instance->suspension_entry) {
+ monitor_instance->suspension_entry->body[0] = '\0';
+ transmit_publish(monitor_instance->suspension_entry, SIP_PUBLISH_REMOVE ,monitor_instance->notify_uri);
+ ao2_t_ref(monitor_instance->suspension_entry, -1, "Decrementing suspension entry refcount in sip_monitor_instance_destructor");
}
+ ast_string_field_free_memory(monitor_instance);
}
-/*! \brief creates a sip_threadinfo object and links it into the threadt table. */
-static struct sip_threadinfo *sip_threadinfo_create(struct ast_tcptls_session_instance *tcptls_session, int transport)
+static struct sip_monitor_instance *sip_monitor_instance_init(int core_id, const char * const subscribe_uri, const char * const peername, const char * const device_name)
{
- struct sip_threadinfo *th;
+ struct sip_monitor_instance *monitor_instance = ao2_alloc(sizeof(*monitor_instance), sip_monitor_instance_destructor);
- if (!tcptls_session || !(th = ao2_alloc(sizeof(*th), sip_threadinfo_destructor))) {
+ if (!monitor_instance) {
return NULL;
}
- th->alert_pipe[0] = th->alert_pipe[1] = -1;
-
- if (pipe(th->alert_pipe) == -1) {
- ao2_t_ref(th, -1, "Failed to open alert pipe on sip_threadinfo");
- ast_log(LOG_ERROR, "Could not create sip alert pipe in tcptls thread, error %s\n", strerror(errno));
+ if (ast_string_field_init(monitor_instance, 256)) {
+ ao2_ref(monitor_instance, -1);
return NULL;
}
- ao2_t_ref(tcptls_session, +1, "tcptls_session ref for sip_threadinfo object");
- th->tcptls_session = tcptls_session;
- th->type = transport ? transport : (tcptls_session->ssl ? SIP_TRANSPORT_TLS: SIP_TRANSPORT_TCP);
- ao2_t_link(threadt, th, "Adding new tcptls helper thread");
- ao2_t_ref(th, -1, "Decrementing threadinfo ref from alloc, only table ref remains");
- return th;
+
+ ast_string_field_set(monitor_instance, subscribe_uri, subscribe_uri);
+ ast_string_field_set(monitor_instance, peername, peername);
+ ast_string_field_set(monitor_instance, device_name, device_name);
+ monitor_instance->core_id = core_id;
+ ao2_link(sip_monitor_instances, monitor_instance);
+ return monitor_instance;
}
-/*! \brief used to indicate to a tcptls thread that data is ready to be written */
-static int sip_tcptls_write(struct ast_tcptls_session_instance *tcptls_session, const void *buf, size_t len)
+static int find_sip_monitor_instance_by_subscription_pvt(void *obj, void *arg, int flags)
{
- int res = len;
- struct sip_threadinfo *th = NULL;
- struct tcptls_packet *packet = NULL;
- struct sip_threadinfo tmp = {
- .tcptls_session = tcptls_session,
- };
- enum sip_tcptls_alert alert = TCPTLS_ALERT_DATA;
+ struct sip_monitor_instance *monitor_instance = obj;
+ return monitor_instance->subscription_pvt == arg ? CMP_MATCH | CMP_STOP : 0;
+}
- if (!tcptls_session) {
- return XMIT_ERROR;
- }
+static int find_sip_monitor_instance_by_suspension_entry(void *obj, void *arg, int flags)
+{
+ struct sip_monitor_instance *monitor_instance = obj;
+ return monitor_instance->suspension_entry == arg ? CMP_MATCH | CMP_STOP : 0;
+}
- ast_mutex_lock(&tcptls_session->lock);
+static int sip_cc_monitor_request_cc(struct ast_cc_monitor *monitor, int *available_timer_id);
+static int sip_cc_monitor_suspend(struct ast_cc_monitor *monitor);
+static int sip_cc_monitor_status_response(struct ast_cc_monitor *monitor, enum ast_device_state devstate);
+static int sip_cc_monitor_unsuspend(struct ast_cc_monitor *monitor);
+static int sip_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id);
+static void sip_cc_monitor_destructor(void *private_data);
- if ((tcptls_session->fd == -1) ||
- !(th = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread")) ||
- !(packet = ao2_alloc(sizeof(*packet), tcptls_packet_destructor)) ||
- !(packet->data = ast_str_create(len))) {
- goto tcptls_write_setup_error;
- }
+static struct ast_cc_monitor_callbacks sip_cc_monitor_callbacks = {
+ .type = "SIP",
+ .request_cc = sip_cc_monitor_request_cc,
+ .suspend = sip_cc_monitor_suspend,
+ .status_response = sip_cc_monitor_status_response,
+ .unsuspend = sip_cc_monitor_unsuspend,
+ .cancel_available_timer = sip_cc_monitor_cancel_available_timer,
+ .destructor = sip_cc_monitor_destructor,
+};
- /* goto tcptls_write_error should _NOT_ be used beyond this point */
- ast_str_set(&packet->data, 0, "%s", (char *) buf);
- packet->len = len;
+static int sip_cc_monitor_request_cc(struct ast_cc_monitor *monitor, int *available_timer_id)
+{
+ struct sip_monitor_instance *monitor_instance = monitor->private_data;
+ enum ast_cc_service_type service = monitor->service_offered;
+ int when;
- /* alert tcptls thread handler that there is a packet to be sent.
- * must lock the thread info object to guarantee control of the
- * packet queue */
- ao2_lock(th);
- if (write(th->alert_pipe[1], &alert, sizeof(alert)) == -1) {
- ast_log(LOG_ERROR, "write() to alert pipe failed: %s\n", strerror(errno));
- ao2_t_ref(packet, -1, "could not write to alert pipe, remove packet");
- packet = NULL;
- res = XMIT_ERROR;
- } else { /* it is safe to queue the frame after issuing the alert when we hold the threadinfo lock */
- AST_LIST_INSERT_TAIL(&th->packet_q, packet, entry);
+ if (!monitor_instance) {
+ return -1;
}
- ao2_unlock(th);
-
- ast_mutex_unlock(&tcptls_session->lock);
- ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo object after finding it");
- return res;
-tcptls_write_setup_error:
- if (th) {
- ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo obj, could not create packet");
- }
- if (packet) {
- ao2_t_ref(packet, -1, "could not allocate packet's data");
+ if (!(monitor_instance->subscription_pvt = sip_alloc(NULL, NULL, 0, SIP_SUBSCRIBE, NULL))) {
+ return -1;
}
- ast_mutex_unlock(&tcptls_session->lock);
- return XMIT_ERROR;
+ when = service == AST_CC_CCBS ? ast_get_ccbs_available_timer(monitor->interface->config_params) :
+ ast_get_ccnr_available_timer(monitor->interface->config_params);
+
+ sip_pvt_lock(monitor_instance->subscription_pvt);
+ create_addr(monitor_instance->subscription_pvt, monitor_instance->peername, 0, 1);
+ ast_sip_ouraddrfor(&monitor_instance->subscription_pvt->sa.sin_addr, &monitor_instance->subscription_pvt->ourip, monitor_instance->subscription_pvt);
+ monitor_instance->subscription_pvt->subscribed = CALL_COMPLETION;
+ monitor_instance->subscription_pvt->expiry = when;
+
+ transmit_invite(monitor_instance->subscription_pvt, SIP_SUBSCRIBE, FALSE, 2, monitor_instance->subscribe_uri);
+ sip_pvt_unlock(monitor_instance->subscription_pvt);
+
+ ao2_t_ref(monitor, +1, "Adding a ref to the monitor for the scheduler");
+ *available_timer_id = ast_sched_add(sched, when * 1000, ast_cc_available_timer_expire, monitor);
+ return 0;
}
-/*! \brief SIP TCP connection handler */
-static void *sip_tcp_worker_fn(void *data)
+static int construct_pidf_body(enum sip_cc_publish_state state, char *pidf_body, size_t size, const char *presentity)
{
- struct ast_tcptls_session_instance *tcptls_session = data;
+ struct ast_str *body = ast_str_alloca(size);
+ char tuple_id[32];
- return _sip_tcp_helper_thread(NULL, tcptls_session);
+ generate_random_string(tuple_id, sizeof(tuple_id));
+
+ /* We'll make this a bare-bones pidf body. In state_notify_build_xml, the PIDF
+ * body gets a lot more extra junk that isn't necessary, so we'll leave it out here.
+ */
+ ast_str_append(&body, 0, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ /* XXX The entity attribute is currently set to the peer name associated with the
+ * dialog. This is because we currently only call this function for call-completion
+ * PUBLISH bodies. In such cases, the entity is completely disregarded. For other
+ * event packages, it may be crucial to have a proper URI as the presentity so this
+ * should be revisited as support is expanded.
+ */
+ ast_str_append(&body, 0, "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"%s\">\n", presentity);
+ ast_str_append(&body, 0, "<tuple id=\"%s\">\n", tuple_id);
+ ast_str_append(&body, 0, "<status><basic>%s</basic></status>\n", state == CC_OPEN ? "open" : "closed");
+ ast_str_append(&body, 0, "</tuple>\n");
+ ast_str_append(&body, 0, "</presence>\n");
+ ast_copy_string(pidf_body, ast_str_buffer(body), size);
+ return 0;
}
-/*! \brief SIP TCP thread management function
- This function reads from the socket, parses the packet into a request
-*/
-static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct ast_tcptls_session_instance *tcptls_session)
+static int sip_cc_monitor_suspend(struct ast_cc_monitor *monitor)
{
- int res, cl;
- struct sip_request req = { 0, } , reqcpy = { 0, };
- struct sip_threadinfo *me = NULL;
- char buf[1024] = "";
- struct pollfd fds[2] = { { 0 }, { 0 }, };
- struct ast_tcptls_session_args *ca = NULL;
+ struct sip_monitor_instance *monitor_instance = monitor->private_data;
+ enum sip_publish_type publish_type;
+ struct cc_epa_entry *cc_entry;
- /* If this is a server session, then the connection has already been setup,
- * simply create the threadinfo object so we can access this thread for writing.
- *
- * if this is a client connection more work must be done.
- * 1. We own the parent session args for a client connection. This pointer needs
- * to be held on to so we can decrement it's ref count on thread destruction.
- * 2. The threadinfo object was created before this thread was launched, however
- * it must be found within the threadt table.
- * 3. Last, the tcptls_session must be started.
- */
- if (!tcptls_session->client) {
- if (!(me = sip_threadinfo_create(tcptls_session, tcptls_session->ssl ? SIP_TRANSPORT_TLS : SIP_TRANSPORT_TCP))) {
- goto cleanup;
+ if (!monitor_instance) {
+ return -1;
+ }
+
+ if (!monitor_instance->suspension_entry) {
+ /* We haven't yet allocated the suspension entry, so let's give it a shot */
+ if (!(monitor_instance->suspension_entry = create_epa_entry("call-completion", monitor_instance->peername))) {
+ ast_log(LOG_WARNING, "Unable to allocate sip EPA entry for call-completion\n");
+ ao2_ref(monitor_instance, -1);
+ return -1;
}
- ao2_t_ref(me, +1, "Adding threadinfo ref for tcp_helper_thread");
+ if (!(cc_entry = ast_calloc(1, sizeof(*cc_entry)))) {
+ ast_log(LOG_WARNING, "Unable to allocate space for instance data of EPA entry for call-completion\n");
+ ao2_ref(monitor_instance, -1);
+ return -1;
+ }
+ cc_entry->core_id = monitor->core_id;
+ monitor_instance->suspension_entry->instance_data = cc_entry;
+ publish_type = SIP_PUBLISH_INITIAL;
} else {
- struct sip_threadinfo tmp = {
- .tcptls_session = tcptls_session,
- };
+ publish_type = SIP_PUBLISH_MODIFY;
+ cc_entry = monitor_instance->suspension_entry->instance_data;
+ }
- if ((!(ca = tcptls_session->parent)) ||
- (!(me = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread"))) ||
- (!(tcptls_session = ast_tcptls_client_start(tcptls_session)))) {
- goto cleanup;
- }
+ cc_entry->current_state = CC_CLOSED;
+
+ if (ast_strlen_zero(monitor_instance->notify_uri)) {
+ /* If we have no set notify_uri, then what this means is that we have
+ * not received a NOTIFY from this destination stating that he is
+ * currently available.
+ *
+ * This situation can arise when the core calls the suspend callbacks
+ * of multiple destinations. If one of the other destinations aside
+ * from this one notified Asterisk that he is available, then there
+ * is no reason to take any suspension action on this device. Rather,
+ * we should return now and if we receive a NOTIFY while monitoring
+ * is still "suspended" then we can immediately respond with the
+ * proper PUBLISH to let this endpoint know what is going on.
+ */
+ return 0;
}
+ construct_pidf_body(CC_CLOSED, monitor_instance->suspension_entry->body, sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername);
+ return transmit_publish(monitor_instance->suspension_entry, publish_type, monitor_instance->notify_uri);
+}
- me->threadid = pthread_self();
- ast_debug(2, "Starting thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
+static int sip_cc_monitor_status_response(struct ast_cc_monitor *monitor, enum ast_device_state devstate)
+{
+ /* This will never be called because the SIP monitor will never make a status request to
+ * begin with
+ */
+ ast_log(LOG_WARNING, "sip_cc_monitor_status_response called. Something dreadfully wrong must have happened.\n");
+ return 0;
+}
- /* set up pollfd to watch for reads on both the socket and the alert_pipe */
- fds[0].fd = tcptls_session->fd;
- fds[1].fd = me->alert_pipe[0];
- fds[0].events = fds[1].events = POLLIN | POLLPRI;
+static int sip_cc_monitor_unsuspend(struct ast_cc_monitor *monitor)
+{
+ struct sip_monitor_instance *monitor_instance = monitor->private_data;
+ struct cc_epa_entry *cc_entry;
- if (!(req.data = ast_str_create(SIP_MIN_PACKET)))
- goto cleanup;
- if (!(reqcpy.data = ast_str_create(SIP_MIN_PACKET)))
- goto cleanup;
+ if (!monitor_instance) {
+ return -1;
+ }
- for (;;) {
- struct ast_str *str_save;
+ ast_assert(monitor_instance->suspension_entry != NULL);
- res = ast_poll(fds, 2, -1); /* polls for both socket and alert_pipe */
- if (res < 0) {
- ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res);
- goto cleanup;
- }
+ cc_entry = monitor_instance->suspension_entry->instance_data;
+ cc_entry->current_state = CC_OPEN;
+ if (ast_strlen_zero(monitor_instance->notify_uri)) {
+ /* This means we are being asked to unsuspend a call leg we never
+ * sent a PUBLISH on. As such, there is no reason to send another
+ * PUBLISH at this point either. We can just return instead.
+ */
+ return 0;
+ }
+ construct_pidf_body(CC_OPEN, monitor_instance->suspension_entry->body, sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername);
+ return transmit_publish(monitor_instance->suspension_entry, SIP_PUBLISH_MODIFY, monitor_instance->notify_uri);
+}
- /* handle the socket event, check for both reads from the socket fd,
- * and writes from alert_pipe fd */
- if (fds[0].revents) { /* there is data on the socket to be read */
+static int sip_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id)
+{
+ if (*sched_id != -1) {
+ AST_SCHED_DEL(sched, *sched_id);
+ ao2_t_ref(monitor, -1, "Removing scheduler's reference to the monitor");
+ }
+ return 0;
+}
- fds[0].revents = 0;
+static void sip_cc_monitor_destructor(void *private_data)
+{
+ struct sip_monitor_instance *monitor_instance = private_data;
+ ao2_unlink(sip_monitor_instances, monitor_instance);
+ ast_module_unref(ast_module_info->self);
+}
- /* clear request structure */
- str_save = req.data;
- memset(&req, 0, sizeof(req));
- req.data = str_save;
- ast_str_reset(req.data);
+static int sip_get_cc_information(struct sip_request *req, char *subscribe_uri, size_t size, enum ast_cc_service_type *service)
+{
+ char *call_info = ast_strdupa(get_header(req, "Call-Info"));
+ char *uri;
+ char *purpose;
+ char *service_str;
+ static const char cc_purpose[] = "purpose=call-completion";
+ static const int cc_purpose_len = sizeof(cc_purpose) - 1;
- str_save = reqcpy.data;
- memset(&reqcpy, 0, sizeof(reqcpy));
- reqcpy.data = str_save;
- ast_str_reset(reqcpy.data);
+ if (ast_strlen_zero(call_info)) {
+ /* No Call-Info present. Definitely no CC offer */
+ return -1;
+ }
- memset(buf, 0, sizeof(buf));
+ uri = strsep(&call_info, ";");
- if (tcptls_session->ssl) {
- set_socket_transport(&req.socket, SIP_TRANSPORT_TLS);
- req.socket.port = htons(ourport_tls);
- } else {
- set_socket_transport(&req.socket, SIP_TRANSPORT_TCP);
- req.socket.port = htons(ourport_tcp);
- }
- req.socket.fd = tcptls_session->fd;
+ while ((purpose = strsep(&call_info, ";"))) {
+ if (!strncmp(purpose, cc_purpose, cc_purpose_len)) {
+ break;
+ }
+ }
+ if (!purpose) {
+ /* We didn't find the appropriate purpose= parameter. Oh well */
+ return -1;
+ }
- /* Read in headers one line at a time */
- while (req.len < 4 || strncmp(REQ_OFFSET_TO_STR(&req, len - 4), "\r\n\r\n", 4)) {
- ast_mutex_lock(&tcptls_session->lock);
- if (!fgets(buf, sizeof(buf), tcptls_session->f)) {
- ast_mutex_unlock(&tcptls_session->lock);
- goto cleanup;
- }
- ast_mutex_unlock(&tcptls_session->lock);
- if (me->stop)
- goto cleanup;
- ast_str_append(&req.data, 0, "%s", buf);
- req.len = req.data->used;
- }
- copy_request(&reqcpy, &req);
- parse_request(&reqcpy);
- /* In order to know how much to read, we need the content-length header */
- if (sscanf(get_header(&reqcpy, "Content-Length"), "%30d", &cl)) {
- while (cl > 0) {
- size_t bytes_read;
- ast_mutex_lock(&tcptls_session->lock);
- if (!(bytes_read = fread(buf, 1, MIN(sizeof(buf) - 1, cl), tcptls_session->f))) {
- ast_mutex_unlock(&tcptls_session->lock);
- goto cleanup;
- }
- buf[bytes_read] = '\0';
- ast_mutex_unlock(&tcptls_session->lock);
- if (me->stop)
- goto cleanup;
- cl -= strlen(buf);
- ast_str_append(&req.data, 0, "%s", buf);
- req.len = req.data->used;
- }
- }
- /*! \todo XXX If there's no Content-Length or if the content-length and what
- we receive is not the same - we should generate an error */
-
- req.socket.tcptls_session = tcptls_session;
- handle_request_do(&req, &tcptls_session->remote_address);
+ /* Okay, call-completion has been offered. Let's figure out what type of service this is */
+ while ((service_str = strsep(&call_info, ";"))) {
+ if (!strncmp(service_str, "m=", 2)) {
+ break;
}
+ }
+ if (!service_str) {
+ /* So they didn't offer a particular service, We'll just go with CCBS since it really
+ * doesn't matter anyway
+ */
+ service_str = "BS";
+ } else {
+ /* We already determined that there is an "m=" so no need to check
+ * the result of this strsep
+ */
+ strsep(&service_str, "=");
+ }
- if (fds[1].revents) { /* alert_pipe indicates there is data in the send queue to be sent */
- enum sip_tcptls_alert alert;
- struct tcptls_packet *packet;
-
- fds[1].revents = 0;
-
- if (read(me->alert_pipe[0], &alert, sizeof(alert)) == -1) {
- ast_log(LOG_ERROR, "read() failed: %s\n", strerror(errno));
- continue;
- }
+ if ((*service = service_string_to_service_type(service_str)) == AST_CC_NONE) {
+ /* Invalid service offered */
+ return -1;
+ }
- switch (alert) {
- case TCPTLS_ALERT_STOP:
- goto cleanup;
- case TCPTLS_ALERT_DATA:
- ao2_lock(me);
- if (!(packet = AST_LIST_REMOVE_HEAD(&me->packet_q, entry))) {
- ast_log(LOG_WARNING, "TCPTLS thread alert_pipe indicated packet should be sent, but frame_q is empty");
- } else if (ast_tcptls_server_write(tcptls_session, ast_str_buffer(packet->data), packet->len) == -1) {
- ast_log(LOG_WARNING, "Failure to write to tcp/tls socket\n");
- }
+ ast_copy_string(subscribe_uri, get_in_brackets(uri), size);
- if (packet) {
- ao2_t_ref(packet, -1, "tcptls packet sent, this is no longer needed");
- }
- ao2_unlock(me);
- break;
- default:
- ast_log(LOG_ERROR, "Unknown tcptls thread alert '%d'\n", alert);
- }
- }
- }
+ return 0;
+}
- ast_debug(2, "Shutting down thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
+/*
+ * \brief Determine what, if any, CC has been offered and queue a CC frame if possible
+ *
+ * After taking care of some formalities to be sure that this call is eligible for CC,
+ * we first try to see if we can make use of native CC. We grab the information from
+ * the passed-in sip_request (which is always a response to an INVITE). If we can
+ * use native CC monitoring for the call, then so be it.
+ *
+ * If native cc monitoring is not possible or not supported, then we will instead attempt
+ * to use generic monitoring. Falling back to generic from a failed attempt at using native
+ * monitoring will only work if the monitor policy of the endpoint is "always"
+ *
+ * \param pvt The current dialog. Contains CC parameters for the endpoint
+ * \param req The response to the INVITE we want to inspect
+ * \param service The service to use if generic monitoring is to be used. For native
+ * monitoring, we get the service from the SIP response itself
+ */
+static void sip_handle_cc(struct sip_pvt *pvt, struct sip_request *req, enum ast_cc_service_type service)
+{
+ enum ast_cc_monitor_policies monitor_policy = ast_get_cc_monitor_policy(pvt->cc_params);
+ int core_id;
+ char interface_name[AST_CHANNEL_NAME];
-cleanup:
- if (me) {
- ao2_t_unlink(threadt, me, "Removing tcptls helper thread, thread is closing");
- ao2_t_ref(me, -1, "Removing tcp_helper_threads threadinfo ref");
- }
- if (reqcpy.data) {
- ast_free(reqcpy.data);
+ if (monitor_policy == AST_CC_MONITOR_NEVER) {
+ /* Don't bother, just return */
+ return;
}
- if (req.data) {
- ast_free(req.data);
- req.data = NULL;
+ if ((core_id = ast_cc_get_current_core_id(pvt->owner)) == -1) {
+ /* For some reason, CC is invalid, so don't try it! */
+ return;
}
- /* if client, we own the parent session arguments and must decrement ref */
- if (ca) {
- ao2_t_ref(ca, -1, "closing tcptls thread, getting rid of client tcptls_session arguments");
- }
+ ast_channel_get_device_name(pvt->owner, interface_name, sizeof(interface_name));
- if (tcptls_session) {
- ast_mutex_lock(&tcptls_session->lock);
- if (tcptls_session->f) {
- fclose(tcptls_session->f);
- tcptls_session->f = NULL;
+ if (monitor_policy == AST_CC_MONITOR_ALWAYS || monitor_policy == AST_CC_MONITOR_NATIVE) {
+ char subscribe_uri[SIPBUFSIZE];
+ char device_name[AST_CHANNEL_NAME];
+ enum ast_cc_service_type offered_service;
+ struct sip_monitor_instance *monitor_instance;
+ if (sip_get_cc_information(req, subscribe_uri, sizeof(subscribe_uri), &offered_service)) {
+ /* If CC isn't being offered to us, or for some reason the CC offer is
+ * not formatted correctly, then it may still be possible to use generic
+ * call completion since the monitor policy may be "always"
+ */
+ goto generic;
}
- if (tcptls_session->fd != -1) {
- close(tcptls_session->fd);
- tcptls_session->fd = -1;
+ ast_channel_get_device_name(pvt->owner, device_name, sizeof(device_name));
+ if (!(monitor_instance = sip_monitor_instance_init(core_id, subscribe_uri, pvt->peername, device_name))) {
+ /* Same deal. We can try using generic still */
+ goto generic;
}
- tcptls_session->parent = NULL;
- ast_mutex_unlock(&tcptls_session->lock);
+ /* We bump the refcount of chan_sip because once we queue this frame, the CC core
+ * will have a reference to callbacks in this module. We decrement the module
+ * refcount once the monitor destructor is called
+ */
+ ast_module_ref(ast_module_info->self);
+ ast_queue_cc_frame(pvt->owner, "SIP", pvt->dialstring, offered_service, monitor_instance);
+ ao2_ref(monitor_instance, -1);
+ return;
+ }
- ao2_ref(tcptls_session, -1);
- tcptls_session = NULL;
+generic:
+ if (monitor_policy == AST_CC_MONITOR_GENERIC || monitor_policy == AST_CC_MONITOR_ALWAYS) {
+ ast_queue_cc_frame(pvt->owner, AST_CC_GENERIC_MONITOR_TYPE, interface_name, service, NULL);
}
- return NULL;
}
+/*! \brief Working TLS connection configuration */
+static struct ast_tls_config sip_tls_cfg;
-/*!
- * helper functions to unreference various types of objects.
- * By handling them this way, we don't have to declare the
- * destructor on each call, which removes the chance of errors.
- */
-static void *unref_peer(struct sip_peer *peer, char *tag)
+/*! \brief Default TLS connection configuration */
+static struct ast_tls_config default_tls_cfg;
+
+/*! \brief The TCP server definition */
+static struct ast_tcptls_session_args sip_tcp_desc = {
+ .accept_fd = -1,
+ .master = AST_PTHREADT_NULL,
+ .tls_cfg = NULL,
+ .poll_timeout = -1,
+ .name = "SIP TCP server",
+ .accept_fn = ast_tcptls_server_root,
+ .worker_fn = sip_tcp_worker_fn,
+};
+
+/*! \brief The TCP/TLS server definition */
+static struct ast_tcptls_session_args sip_tls_desc = {
+ .accept_fd = -1,
+ .master = AST_PTHREADT_NULL,
+ .tls_cfg = &sip_tls_cfg,
+ .poll_timeout = -1,
+ .name = "SIP TLS server",
+ .accept_fn = ast_tcptls_server_root,
+ .worker_fn = sip_tcp_worker_fn,
+};
+
+/*! \brief Append to SIP dialog history
+ \return Always returns 0 */
+#define append_history(p, event, fmt , args... ) append_history_full(p, "%-15s " fmt, event, ## args)
+
+struct sip_pvt *dialog_ref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func)
{
- ao2_t_ref(peer, -1, tag);
- return NULL;
+ if (p)
+#ifdef REF_DEBUG
+ __ao2_ref_debug(p, 1, tag, file, line, func);
+#else
+ ao2_ref(p, 1);
+#endif
+ else
+ ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n");
+ return p;
}
-static struct sip_peer *ref_peer(struct sip_peer *peer, char *tag)
+struct sip_pvt *dialog_unref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func)
{
- ao2_t_ref(peer, 1, tag);
- return peer;
+ if (p)
+#ifdef REF_DEBUG
+ __ao2_ref_debug(p, -1, tag, file, line, func);
+#else
+ ao2_ref(p, -1);
+#endif
+ return NULL;
}
-/*! \brief maintain proper refcounts for a sip_pvt's outboundproxy
- *
- * This function sets pvt's outboundproxy pointer to the one referenced
- * by the proxy parameter. Because proxy may be a refcounted object, and
- * because pvt's old outboundproxy may also be a refcounted object, we need
- * to maintain the proper refcounts.
- *
- * \param pvt The sip_pvt for which we wish to set the outboundproxy
- * \param proxy The sip_proxy which we will point pvt towards.
- * \return Returns void
+/*! \brief map from an integer value to a string.
+ * If no match is found, return errorstring
*/
-static void ref_proxy(struct sip_pvt *pvt, struct sip_proxy *proxy)
+static const char *map_x_s(const struct _map_x_s *table, int x, const char *errorstring)
{
- struct sip_proxy *old_obproxy = pvt->outboundproxy;
- /* The sip_cfg.outboundproxy is statically allocated, and so
- * we don't ever need to adjust refcounts for it
- */
- if (proxy && proxy != &sip_cfg.outboundproxy) {
- ao2_ref(proxy, +1);
- }
- pvt->outboundproxy = proxy;
- if (old_obproxy && old_obproxy != &sip_cfg.outboundproxy) {
- ao2_ref(old_obproxy, -1);
- }
+ const struct _map_x_s *cur;
+
+ for (cur = table; cur->s; cur++)
+ if (cur->x == x)
+ return cur->s;
+ return errorstring;
}
-/*!
- * \brief Unlink a dialog from the dialogs container, as well as any other places
- * that it may be currently stored.
- *
- * \note A reference to the dialog must be held before calling this function, and this
- * function does not release that reference.
+/*! \brief map from a string to an integer value, case insensitive.
+ * If no match is found, return errorvalue.
*/
-void *dialog_unlink_all(struct sip_pvt *dialog, int lockowner, int lockdialoglist)
+static int map_s_x(const struct _map_x_s *table, const char *s, int errorvalue)
{
- struct sip_pkt *cp;
-
- dialog_ref(dialog, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done");
+ const struct _map_x_s *cur;
- ao2_t_unlink(dialogs, dialog, "unlinking dialog via ao2_unlink");
+ for (cur = table; cur->s; cur++)
+ if (!strcasecmp(cur->s, s))
+ return cur->x;
+ return errorvalue;
+}
- /* Unlink us from the owner (channel) if we have one */
- if (dialog->owner) {
- if (lockowner)
- ast_channel_lock(dialog->owner);
- ast_debug(1, "Detaching from channel %s\n", dialog->owner->name);
- dialog->owner->tech_pvt = dialog_unref(dialog->owner->tech_pvt, "resetting channel dialog ptr in unlink_all");
- if (lockowner)
- ast_channel_unlock(dialog->owner);
- }
- if (dialog->registry) {
- if (dialog->registry->call == dialog)
- dialog->registry->call = dialog_unref(dialog->registry->call, "nulling out the registry's call dialog field in unlink_all");
- dialog->registry = registry_unref(dialog->registry, "delete dialog->registry");
- }
- if (dialog->stateid > -1) {
- ast_extension_state_del(dialog->stateid, NULL);
- dialog_unref(dialog, "removing extension_state, should unref the associated dialog ptr that was stored there.");
- dialog->stateid = -1; /* shouldn't we 'zero' this out? */
- }
- /* Remove link from peer to subscription of MWI */
- if (dialog->relatedpeer && dialog->relatedpeer->mwipvt == dialog)
- dialog->relatedpeer->mwipvt = dialog_unref(dialog->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt");
- if (dialog->relatedpeer && dialog->relatedpeer->call == dialog)
- dialog->relatedpeer->call = dialog_unref(dialog->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself");
+static enum AST_REDIRECTING_REASON sip_reason_str_to_code(const char *text)
+{
+ enum AST_REDIRECTING_REASON ast = AST_REDIRECTING_REASON_UNKNOWN;
+ int i;
- /* remove all current packets in this dialog */
- while((cp = dialog->packets)) {
- dialog->packets = dialog->packets->next;
- AST_SCHED_DEL(sched, cp->retransid);
- dialog_unref(cp->owner, "remove all current packets in this dialog, and the pointer to the dialog too as part of __sip_destroy");
- if (cp->data) {
- ast_free(cp->data);
+ for (i = 0; i < ARRAY_LEN(sip_reason_table); ++i) {
+ if (!strcasecmp(text, sip_reason_table[i].text)) {
+ ast = sip_reason_table[i].code;
+ break;
}
- ast_free(cp);
}
- AST_SCHED_DEL_UNREF(sched, dialog->waitid, dialog_unref(dialog, "when you delete the waitid sched, you should dec the refcount for the stored dialog ptr"));
+ return ast;
+}
- AST_SCHED_DEL_UNREF(sched, dialog->initid, dialog_unref(dialog, "when you delete the initid sched, you should dec the refcount for the stored dialog ptr"));
-
- if (dialog->autokillid > -1)
- AST_SCHED_DEL_UNREF(sched, dialog->autokillid, dialog_unref(dialog, "when you delete the autokillid sched, you should dec the refcount for the stored dialog ptr"));
-
- if (dialog->request_queue_sched_id > -1) {
- AST_SCHED_DEL_UNREF(sched, dialog->request_queue_sched_id, dialog_unref(dialog, "when you delete the request_queue_sched_id sched, you should dec the refcount for the stored dialog ptr"));
- }
-
- AST_SCHED_DEL_UNREF(sched, dialog->provisional_keepalive_sched_id, dialog_unref(dialog, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
-
- if (dialog->t38id > -1) {
- AST_SCHED_DEL_UNREF(sched, dialog->t38id, dialog_unref(dialog, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr"));
+static const char *sip_reason_code_to_str(enum AST_REDIRECTING_REASON code)
+{
+ if (code >= 0 && code < ARRAY_LEN(sip_reason_table)) {
+ return sip_reason_table[code].text;
}
- dialog_unref(dialog, "Let's unbump the count in the unlink so the poor pvt can disappear if it is time");
- return NULL;
+ return "unknown";
}
-void *registry_unref(struct sip_registry *reg, char *tag)
-{
- ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount - 1);
- ASTOBJ_UNREF(reg, sip_registry_destroy);
- return NULL;
-}
+/*!
+ * \brief generic function for determining if a correct transport is being
+ * used to contact a peer
+ *
+ * this is done as a macro so that the "tmpl" var can be passed either a
+ * sip_request or a sip_peer
+ */
+#define check_request_transport(peer, tmpl) ({ \
+ int ret = 0; \
+ if (peer->socket.type == tmpl->socket.type) \
+ ; \
+ else if (!(peer->transports & tmpl->socket.type)) {\
+ ast_log(LOG_ERROR, \
+ "'%s' is not a valid transport for '%s'. we only use '%s'! ending call.\n", \
+ get_transport(tmpl->socket.type), peer->name, get_transport_list(peer->transports) \
+ ); \
+ ret = 1; \
+ } else if (peer->socket.type & SIP_TRANSPORT_TLS) { \
+ ast_log(LOG_WARNING, \
+ "peer '%s' HAS NOT USED (OR SWITCHED TO) TLS in favor of '%s' (but this was allowed in sip.conf)!\n", \
+ peer->name, get_transport(tmpl->socket.type) \
+ ); \
+ } else { \
+ ast_debug(1, \
+ "peer '%s' has contacted us over %s even though we prefer %s.\n", \
+ peer->name, get_transport(tmpl->socket.type), get_transport(peer->socket.type) \
+ ); \
+ }\
+ (ret); \
+})
-/*! \brief Add object reference to SIP registry */
-static struct sip_registry *registry_addref(struct sip_registry *reg, char *tag)
+/*! \brief
+ * duplicate a list of channel variables, \return the copy.
+ */
+static struct ast_variable *copy_vars(struct ast_variable *src)
{
- ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount + 1);
- return ASTOBJ_REF(reg); /* Add pointer to registry in packet */
-}
-
-/*! \brief Interface structure with callbacks used to connect to UDPTL module*/
-static struct ast_udptl_protocol sip_udptl = {
- type: "SIP",
- get_udptl_info: sip_get_udptl_peer,
- set_udptl_peer: sip_set_udptl_peer,
-};
-
-static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
- __attribute__((format(printf, 2, 3)));
-
+ struct ast_variable *res = NULL, *tmp, *v = NULL;
-/*! \brief Convert transfer status to string */
-static const char *referstatus2str(enum referstatus rstatus)
-{
- return map_x_s(referstatusstrings, rstatus, "");
+ for (v = src ; v ; v = v->next) {
+ if ((tmp = ast_variable_new(v->name, v->value, v->file))) {
+ tmp->next = res;
+ res = tmp;
+ }
+ }
+ return res;
}
-static inline void pvt_set_needdestroy(struct sip_pvt *pvt, const char *reason)
+static void tcptls_packet_destructor(void *obj)
{
- append_history(pvt, "NeedDestroy", "Setting needdestroy because %s", reason);
- pvt->needdestroy = 1;
-}
+ struct tcptls_packet *packet = obj;
-/*! \brief Initialize the initital request packet in the pvt structure.
- This packet is used for creating replies and future requests in
- a dialog */
-static void initialize_initreq(struct sip_pvt *p, struct sip_request *req)
-{
- if (p->initreq.headers)
- ast_debug(1, "Initializing already initialized SIP dialog %s (presumably reinvite)\n", p->callid);
- else
- ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
- /* Use this as the basis */
- copy_request(&p->initreq, req);
- parse_request(&p->initreq);
- if (req->debug)
- ast_verbose("Initreq: %d headers, %d lines\n", p->initreq.headers, p->initreq.lines);
+ ast_free(packet->data);
}
-/*! \brief Encapsulate setting of SIP_ALREADYGONE to be able to trace it with debugging */
-static void sip_alreadygone(struct sip_pvt *dialog)
+static void sip_tcptls_client_args_destructor(void *obj)
{
- ast_debug(3, "Setting SIP_ALREADYGONE on dialog %s\n", dialog->callid);
- dialog->alreadygone = 1;
+ struct ast_tcptls_session_args *args = obj;
+ if (args->tls_cfg) {
+ ast_free(args->tls_cfg->certfile);
+ ast_free(args->tls_cfg->pvtfile);
+ ast_free(args->tls_cfg->cipher);
+ ast_free(args->tls_cfg->cafile);
+ ast_free(args->tls_cfg->capath);
+ }
+ ast_free(args->tls_cfg);
+ ast_free((char *) args->name);
}
-/*! Resolve DNS srv name or host name in a sip_proxy structure */
-static int proxy_update(struct sip_proxy *proxy)
+static void sip_threadinfo_destructor(void *obj)
{
- /* if it's actually an IP address and not a name,
- there's no need for a managed lookup */
- if (!inet_aton(proxy->name, &proxy->ip.sin_addr)) {
- /* Ok, not an IP address, then let's check if it's a domain or host */
- /* XXX Todo - if we have proxy port, don't do SRV */
- if (ast_get_ip_or_srv(&proxy->ip, proxy->name, sip_cfg.srvlookup ? "_sip._udp" : NULL) < 0) {
- ast_log(LOG_WARNING, "Unable to locate host '%s'\n", proxy->name);
- return FALSE;
- }
+ struct sip_threadinfo *th = obj;
+ struct tcptls_packet *packet;
+ if (th->alert_pipe[1] > -1) {
+ close(th->alert_pipe[0]);
}
- proxy->last_dnsupdate = time(NULL);
- return TRUE;
-}
+ if (th->alert_pipe[1] > -1) {
+ close(th->alert_pipe[1]);
+ }
+ th->alert_pipe[0] = th->alert_pipe[1] = -1;
-/*! \brief converts ascii port to int representation. If no
- * pt buffer is provided or the pt has errors when being converted
- * to an int value, the port provided as the standard is used.
- */
-unsigned int port_str2int(const char *pt, unsigned int standard)
-{
- int port = standard;
- if (ast_strlen_zero(pt) || (sscanf(pt, "%30d", &port) != 1) || (port < 1) || (port > 65535)) {
- port = standard;
+ while ((packet = AST_LIST_REMOVE_HEAD(&th->packet_q, entry))) {
+ ao2_t_ref(packet, -1, "thread destruction, removing packet from frame queue");
}
- return port;
+ if (th->tcptls_session) {
+ ao2_t_ref(th->tcptls_session, -1, "remove tcptls_session for sip_threadinfo object");
+ }
}
-/*! \brief Allocate and initialize sip proxy */
-static struct sip_proxy *proxy_allocate(char *name, char *port, int force)
+/*! \brief creates a sip_threadinfo object and links it into the threadt table. */
+static struct sip_threadinfo *sip_threadinfo_create(struct ast_tcptls_session_instance *tcptls_session, int transport)
{
- struct sip_proxy *proxy;
+ struct sip_threadinfo *th;
- if (ast_strlen_zero(name)) {
+ if (!tcptls_session || !(th = ao2_alloc(sizeof(*th), sip_threadinfo_destructor))) {
return NULL;
}
- proxy = ao2_alloc(sizeof(*proxy), NULL);
- if (!proxy)
- return NULL;
- proxy->force = force;
- ast_copy_string(proxy->name, name, sizeof(proxy->name));
- proxy->ip.sin_port = htons(port_str2int(port, STANDARD_SIP_PORT));
- proxy_update(proxy);
- return proxy;
-}
+ th->alert_pipe[0] = th->alert_pipe[1] = -1;
-/*! \brief Get default outbound proxy or global proxy */
-static struct sip_proxy *obproxy_get(struct sip_pvt *dialog, struct sip_peer *peer)
-{
- if (peer && peer->outboundproxy) {
- if (sipdebug)
- ast_debug(1, "OBPROXY: Applying peer OBproxy to this call\n");
- append_history(dialog, "OBproxy", "Using peer obproxy %s", peer->outboundproxy->name);
- return peer->outboundproxy;
- }
- if (sip_cfg.outboundproxy.name[0]) {
- if (sipdebug)
- ast_debug(1, "OBPROXY: Applying global OBproxy to this call\n");
- append_history(dialog, "OBproxy", "Using global obproxy %s", sip_cfg.outboundproxy.name);
- return &sip_cfg.outboundproxy;
+ if (pipe(th->alert_pipe) == -1) {
+ ao2_t_ref(th, -1, "Failed to open alert pipe on sip_threadinfo");
+ ast_log(LOG_ERROR, "Could not create sip alert pipe in tcptls thread, error %s\n", strerror(errno));
+ return NULL;
}
- if (sipdebug)
- ast_debug(1, "OBPROXY: Not applying OBproxy to this call\n");
- return NULL;
+ ao2_t_ref(tcptls_session, +1, "tcptls_session ref for sip_threadinfo object");
+ th->tcptls_session = tcptls_session;
+ th->type = transport ? transport : (tcptls_session->ssl ? SIP_TRANSPORT_TLS: SIP_TRANSPORT_TCP);
+ ao2_t_link(threadt, th, "Adding new tcptls helper thread");
+ ao2_t_ref(th, -1, "Decrementing threadinfo ref from alloc, only table ref remains");
+ return th;
}
-/*! \brief returns true if 'name' (with optional trailing whitespace)
- * matches the sip method 'id'.
- * Strictly speaking, SIP methods are case SENSITIVE, but we do
- * a case-insensitive comparison to be more tolerant.
- * following Jon Postel's rule: Be gentle in what you accept, strict with what you send
- */
-static int method_match(enum sipmethod id, const char *name)
+/*! \brief used to indicate to a tcptls thread that data is ready to be written */
+static int sip_tcptls_write(struct ast_tcptls_session_instance *tcptls_session, const void *buf, size_t len)
{
- int len = strlen(sip_methods[id].text);
- int l_name = name ? strlen(name) : 0;
- /* true if the string is long enough, and ends with whitespace, and matches */
- return (l_name >= len && name[len] < 33 &&
- !strncasecmp(sip_methods[id].text, name, len));
-}
+ int res = len;
+ struct sip_threadinfo *th = NULL;
+ struct tcptls_packet *packet = NULL;
+ struct sip_threadinfo tmp = {
+ .tcptls_session = tcptls_session,
+ };
+ enum sip_tcptls_alert alert = TCPTLS_ALERT_DATA;
-/*! \brief find_sip_method: Find SIP method from header */
-static int find_sip_method(const char *msg)
-{
- int i, res = 0;
-
- if (ast_strlen_zero(msg))
- return 0;
- for (i = 1; i < ARRAY_LEN(sip_methods) && !res; i++) {
- if (method_match(i, msg))
- res = sip_methods[i].id;
+ if (!tcptls_session) {
+ return XMIT_ERROR;
}
- return res;
-}
-
-/*! \brief Parse supported header in incoming packet */
-static unsigned int parse_sip_options(struct sip_pvt *pvt, const char *supported)
-{
- char *next, *sep;
- char *temp;
- unsigned int profile = 0;
- int i, found;
- if (ast_strlen_zero(supported) )
- return 0;
- temp = ast_strdupa(supported);
+ ast_mutex_lock(&tcptls_session->lock);
- if (sipdebug)
- ast_debug(3, "Begin: parsing SIP \"Supported: %s\"\n", supported);
-
- for (next = temp; next; next = sep) {
- found = FALSE;
- if ( (sep = strchr(next, ',')) != NULL)
- *sep++ = '\0';
- next = ast_skip_blanks(next);
- if (sipdebug)
- ast_debug(3, "Found SIP option: -%s-\n", next);
- for (i = 0; i < ARRAY_LEN(sip_options); i++) {
- if (!strcasecmp(next, sip_options[i].text)) {
- profile |= sip_options[i].id;
- found = TRUE;
- if (sipdebug)
- ast_debug(3, "Matched SIP option: %s\n", next);
- break;
- }
- }
+ if ((tcptls_session->fd == -1) ||
+ !(th = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread")) ||
+ !(packet = ao2_alloc(sizeof(*packet), tcptls_packet_destructor)) ||
+ !(packet->data = ast_str_create(len))) {
+ goto tcptls_write_setup_error;
+ }
- /* This function is used to parse both Suported: and Require: headers.
- Let the caller of this function know that an unknown option tag was
- encountered, so that if the UAC requires it then the request can be
- rejected with a 420 response. */
- if (!found)
- profile |= SIP_OPT_UNKNOWN;
+ /* goto tcptls_write_error should _NOT_ be used beyond this point */
+ ast_str_set(&packet->data, 0, "%s", (char *) buf);
+ packet->len = len;
- if (!found && sipdebug) {
- if (!strncasecmp(next, "x-", 2))
- ast_debug(3, "Found private SIP option, not supported: %s\n", next);
- else
- ast_debug(3, "Found no match for SIP option: %s (Please file bug report!)\n", next);
- }
+ /* alert tcptls thread handler that there is a packet to be sent.
+ * must lock the thread info object to guarantee control of the
+ * packet queue */
+ ao2_lock(th);
+ if (write(th->alert_pipe[1], &alert, sizeof(alert)) == -1) {
+ ast_log(LOG_ERROR, "write() to alert pipe failed: %s\n", strerror(errno));
+ ao2_t_ref(packet, -1, "could not write to alert pipe, remove packet");
+ packet = NULL;
+ res = XMIT_ERROR;
+ } else { /* it is safe to queue the frame after issuing the alert when we hold the threadinfo lock */
+ AST_LIST_INSERT_TAIL(&th->packet_q, packet, entry);
}
+ ao2_unlock(th);
- if (pvt)
- pvt->sipoptions = profile;
- return profile;
-}
+ ast_mutex_unlock(&tcptls_session->lock);
+ ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo object after finding it");
+ return res;
-/*! \brief See if we pass debug IP filter */
-static inline int sip_debug_test_addr(const struct sockaddr_in *addr)
-{
- if (!sipdebug)
- return 0;
- if (debugaddr.sin_addr.s_addr) {
- if (((ntohs(debugaddr.sin_port) != 0)
- && (debugaddr.sin_port != addr->sin_port))
- || (debugaddr.sin_addr.s_addr != addr->sin_addr.s_addr))
- return 0;
+tcptls_write_setup_error:
+ if (th) {
+ ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo obj, could not create packet");
}
- return 1;
-}
-
-/*! \brief The real destination address for a write */
-static const struct sockaddr_in *sip_real_dst(const struct sip_pvt *p)
-{
- if (p->outboundproxy)
- return &p->outboundproxy->ip;
+ if (packet) {
+ ao2_t_ref(packet, -1, "could not allocate packet's data");
+ }
+ ast_mutex_unlock(&tcptls_session->lock);
- return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT) ? &p->recv : &p->sa;
+ return XMIT_ERROR;
}
-/*! \brief Display SIP nat mode */
-static const char *sip_nat_mode(const struct sip_pvt *p)
+/*! \brief SIP TCP connection handler */
+static void *sip_tcp_worker_fn(void *data)
{
- return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) ? "NAT" : "no NAT";
-}
+ struct ast_tcptls_session_instance *tcptls_session = data;
-/*! \brief Test PVT for debugging output */
-static inline int sip_debug_test_pvt(struct sip_pvt *p)
-{
- if (!sipdebug)
- return 0;
- return sip_debug_test_addr(sip_real_dst(p));
+ return _sip_tcp_helper_thread(NULL, tcptls_session);
}
-/*! \brief Return int representing a bit field of transport types found in const char *transport */
-static int get_transport_str2enum(const char *transport)
+/*! \brief SIP TCP thread management function
+ This function reads from the socket, parses the packet into a request
+*/
+static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct ast_tcptls_session_instance *tcptls_session)
{
- int res = 0;
+ int res, cl;
+ struct sip_request req = { 0, } , reqcpy = { 0, };
+ struct sip_threadinfo *me = NULL;
+ char buf[1024] = "";
+ struct pollfd fds[2] = { { 0 }, { 0 }, };
+ struct ast_tcptls_session_args *ca = NULL;
- if (ast_strlen_zero(transport)) {
- return res;
- }
+ /* If this is a server session, then the connection has already been setup,
+ * simply create the threadinfo object so we can access this thread for writing.
+ *
+ * if this is a client connection more work must be done.
+ * 1. We own the parent session args for a client connection. This pointer needs
+ * to be held on to so we can decrement it's ref count on thread destruction.
+ * 2. The threadinfo object was created before this thread was launched, however
+ * it must be found within the threadt table.
+ * 3. Last, the tcptls_session must be started.
+ */
+ if (!tcptls_session->client) {
+ if (!(me = sip_threadinfo_create(tcptls_session, tcptls_session->ssl ? SIP_TRANSPORT_TLS : SIP_TRANSPORT_TCP))) {
+ goto cleanup;
+ }
+ ao2_t_ref(me, +1, "Adding threadinfo ref for tcp_helper_thread");
+ } else {
+ struct sip_threadinfo tmp = {
+ .tcptls_session = tcptls_session,
+ };
- if (!strcasecmp(transport, "udp")) {
- res |= SIP_TRANSPORT_UDP;
- }
- if (!strcasecmp(transport, "tcp")) {
- res |= SIP_TRANSPORT_TCP;
- }
- if (!strcasecmp(transport, "tls")) {
- res |= SIP_TRANSPORT_TLS;
+ if ((!(ca = tcptls_session->parent)) ||
+ (!(me = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread"))) ||
+ (!(tcptls_session = ast_tcptls_client_start(tcptls_session)))) {
+ goto cleanup;
+ }
}
- return res;
-}
+ me->threadid = pthread_self();
+ ast_debug(2, "Starting thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
-/*! \brief Return configuration of transports for a device */
-static inline const char *get_transport_list(unsigned int transports) {
- switch (transports) {
- case SIP_TRANSPORT_UDP:
- return "UDP";
- case SIP_TRANSPORT_TCP:
- return "TCP";
- case SIP_TRANSPORT_TLS:
- return "TLS";
- case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TCP:
- return "TCP,UDP";
- case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TLS:
- return "TLS,UDP";
- case SIP_TRANSPORT_TCP | SIP_TRANSPORT_TLS:
- return "TLS,TCP";
- default:
- return transports ?
- "TLS,TCP,UDP" : "UNKNOWN";
- }
-}
+ /* set up pollfd to watch for reads on both the socket and the alert_pipe */
+ fds[0].fd = tcptls_session->fd;
+ fds[1].fd = me->alert_pipe[0];
+ fds[0].events = fds[1].events = POLLIN | POLLPRI;
-/*! \brief Return transport as string */
-static inline const char *get_transport(enum sip_transport t)
-{
- switch (t) {
- case SIP_TRANSPORT_UDP:
- return "UDP";
- case SIP_TRANSPORT_TCP:
- return "TCP";
- case SIP_TRANSPORT_TLS:
- return "TLS";
- }
+ if (!(req.data = ast_str_create(SIP_MIN_PACKET)))
+ goto cleanup;
+ if (!(reqcpy.data = ast_str_create(SIP_MIN_PACKET)))
+ goto cleanup;
- return "UNKNOWN";
-}
+ for (;;) {
+ struct ast_str *str_save;
-/*! \brief Return transport of dialog.
- \note this is based on a false assumption. We don't always use the
- outbound proxy for all requests in a dialog. It depends on the
- "force" parameter. The FIRST request is always sent to the ob proxy.
- \todo Fix this function to work correctly
-*/
-static inline const char *get_transport_pvt(struct sip_pvt *p)
-{
- if (p->outboundproxy && p->outboundproxy->transport) {
- set_socket_transport(&p->socket, p->outboundproxy->transport);
- }
+ res = ast_poll(fds, 2, -1); /* polls for both socket and alert_pipe */
+ if (res < 0) {
+ ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res);
+ goto cleanup;
+ }
- return get_transport(p->socket.type);
-}
+ /* handle the socket event, check for both reads from the socket fd,
+ * and writes from alert_pipe fd */
+ if (fds[0].revents) { /* there is data on the socket to be read */
-/*! \brief Transmit SIP message
- Sends a SIP request or response on a given socket (in the pvt)
- Called by retrans_pkt, send_request, send_response and
- __sip_reliable_xmit
- \return length of transmitted message, XMIT_ERROR on known network failures -1 on other failures.
-*/
-static int __sip_xmit(struct sip_pvt *p, struct ast_str *data, int len)
-{
- int res = 0;
- const struct sockaddr_in *dst = sip_real_dst(p);
+ fds[0].revents = 0;
- ast_debug(2, "Trying to put '%.11s' onto %s socket destined for %s:%d\n", data->str, get_transport_pvt(p), ast_inet_ntoa(dst->sin_addr), htons(dst->sin_port));
+ /* clear request structure */
+ str_save = req.data;
+ memset(&req, 0, sizeof(req));
+ req.data = str_save;
+ ast_str_reset(req.data);
- if (sip_prepare_socket(p) < 0)
- return XMIT_ERROR;
+ str_save = reqcpy.data;
+ memset(&reqcpy, 0, sizeof(reqcpy));
+ reqcpy.data = str_save;
+ ast_str_reset(reqcpy.data);
- if (p->socket.type == SIP_TRANSPORT_UDP) {
- res = sendto(p->socket.fd, data->str, len, 0, (const struct sockaddr *)dst, sizeof(struct sockaddr_in));
- } else if (p->socket.tcptls_session) {
- res = sip_tcptls_write(p->socket.tcptls_session, data->str, len);
- } else {
- ast_debug(2, "Socket type is TCP but no tcptls_session is present to write to\n");
- return XMIT_ERROR;
- }
+ memset(buf, 0, sizeof(buf));
- if (res == -1) {
- switch (errno) {
- case EBADF: /* Bad file descriptor - seems like this is generated when the host exist, but doesn't accept the UDP packet */
- case EHOSTUNREACH: /* Host can't be reached */
- case ENETDOWN: /* Interface down */
- case ENETUNREACH: /* Network failure */
- case ECONNREFUSED: /* ICMP port unreachable */
- res = XMIT_ERROR; /* Don't bother with trying to transmit again */
- }
- }
- if (res != len)
- ast_log(LOG_WARNING, "sip_xmit of %p (len %d) to %s:%d returned %d: %s\n", data, len, ast_inet_ntoa(dst->sin_addr), ntohs(dst->sin_port), res, strerror(errno));
+ if (tcptls_session->ssl) {
+ set_socket_transport(&req.socket, SIP_TRANSPORT_TLS);
+ req.socket.port = htons(ourport_tls);
+ } else {
+ set_socket_transport(&req.socket, SIP_TRANSPORT_TCP);
+ req.socket.port = htons(ourport_tcp);
+ }
+ req.socket.fd = tcptls_session->fd;
- return res;
-}
+ /* Read in headers one line at a time */
+ while (req.len < 4 || strncmp(REQ_OFFSET_TO_STR(&req, len - 4), "\r\n\r\n", 4)) {
+ ast_mutex_lock(&tcptls_session->lock);
+ if (!fgets(buf, sizeof(buf), tcptls_session->f)) {
+ ast_mutex_unlock(&tcptls_session->lock);
+ goto cleanup;
+ }
+ ast_mutex_unlock(&tcptls_session->lock);
+ if (me->stop)
+ goto cleanup;
+ ast_str_append(&req.data, 0, "%s", buf);
+ req.len = req.data->used;
+ }
+ copy_request(&reqcpy, &req);
+ parse_request(&reqcpy);
+ /* In order to know how much to read, we need the content-length header */
+ if (sscanf(get_header(&reqcpy, "Content-Length"), "%30d", &cl)) {
+ while (cl > 0) {
+ size_t bytes_read;
+ ast_mutex_lock(&tcptls_session->lock);
+ if (!(bytes_read = fread(buf, 1, MIN(sizeof(buf) - 1, cl), tcptls_session->f))) {
+ ast_mutex_unlock(&tcptls_session->lock);
+ goto cleanup;
+ }
+ buf[bytes_read] = '\0';
+ ast_mutex_unlock(&tcptls_session->lock);
+ if (me->stop)
+ goto cleanup;
+ cl -= strlen(buf);
+ ast_str_append(&req.data, 0, "%s", buf);
+ req.len = req.data->used;
+ }
+ }
+ /*! \todo XXX If there's no Content-Length or if the content-length and what
+ we receive is not the same - we should generate an error */
-/*! \brief Build a Via header for a request */
-static void build_via(struct sip_pvt *p)
-{
- /* Work around buggy UNIDEN UIP200 firmware */
- const char *rport = (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT)) ? ";rport" : "";
+ req.socket.tcptls_session = tcptls_session;
+ handle_request_do(&req, &tcptls_session->remote_address);
+ }
- /* z9hG4bK is a magic cookie. See RFC 3261 section 8.1.1.7 */
- snprintf(p->via, sizeof(p->via), "SIP/2.0/%s %s:%d;branch=z9hG4bK%08x%s",
- get_transport_pvt(p),
- ast_inet_ntoa(p->ourip.sin_addr),
- ntohs(p->ourip.sin_port), (int) p->branch, rport);
-}
+ if (fds[1].revents) { /* alert_pipe indicates there is data in the send queue to be sent */
+ enum sip_tcptls_alert alert;
+ struct tcptls_packet *packet;
-/*! \brief NAT fix - decide which IP address to use for Asterisk server?
- *
- * Using the localaddr structure built up with localnet statements in sip.conf
- * apply it to their address to see if we need to substitute our
- * externip or can get away with our internal bindaddr
- * 'us' is always overwritten.
- */
-static void ast_sip_ouraddrfor(struct in_addr *them, struct sockaddr_in *us, struct sip_pvt *p)
-{
- struct sockaddr_in theirs;
- /* Set want_remap to non-zero if we want to remap 'us' to an externally
- * reachable IP address and port. This is done if:
- * 1. we have a localaddr list (containing 'internal' addresses marked
- * as 'deny', so ast_apply_ha() will return AST_SENSE_DENY on them,
- * and AST_SENSE_ALLOW on 'external' ones);
- * 2. either stunaddr or externip is set, so we know what to use as the
- * externally visible address;
- * 3. the remote address, 'them', is external;
- * 4. the address returned by ast_ouraddrfor() is 'internal' (AST_SENSE_DENY
- * when passed to ast_apply_ha() so it does need to be remapped.
- * This fourth condition is checked later.
- */
- int want_remap;
+ fds[1].revents = 0;
- *us = internip; /* starting guess for the internal address */
- /* now ask the system what would it use to talk to 'them' */
- ast_ouraddrfor(them, &us->sin_addr);
- theirs.sin_addr = *them;
+ if (read(me->alert_pipe[0], &alert, sizeof(alert)) == -1) {
+ ast_log(LOG_ERROR, "read() failed: %s\n", strerror(errno));
+ continue;
+ }
- want_remap = localaddr &&
- (externip.sin_addr.s_addr || stunaddr.sin_addr.s_addr) &&
- ast_apply_ha(localaddr, &theirs) == AST_SENSE_ALLOW ;
+ switch (alert) {
+ case TCPTLS_ALERT_STOP:
+ goto cleanup;
+ case TCPTLS_ALERT_DATA:
+ ao2_lock(me);
+ if (!(packet = AST_LIST_REMOVE_HEAD(&me->packet_q, entry))) {
+ ast_log(LOG_WARNING, "TCPTLS thread alert_pipe indicated packet should be sent, but frame_q is empty");
+ } else if (ast_tcptls_server_write(tcptls_session, ast_str_buffer(packet->data), packet->len) == -1) {
+ ast_log(LOG_WARNING, "Failure to write to tcp/tls socket\n");
+ }
- if (want_remap &&
- (!sip_cfg.matchexterniplocally || !ast_apply_ha(localaddr, us)) ) {
- /* if we used externhost or stun, see if it is time to refresh the info */
- if (externexpire && time(NULL) >= externexpire) {
- if (stunaddr.sin_addr.s_addr) {
- ast_stun_request(sipsock, &stunaddr, NULL, &externip);
- } else {
- if (ast_parse_arg(externhost, PARSE_INADDR, &externip))
- ast_log(LOG_NOTICE, "Warning: Re-lookup of '%s' failed!\n", externhost);
- }
- externexpire = time(NULL) + externrefresh;
- }
- if (externip.sin_addr.s_addr) {
- *us = externip;
- switch (p->socket.type) {
- case SIP_TRANSPORT_TCP:
- us->sin_port = htons(externtcpport);
- break;
- case SIP_TRANSPORT_TLS:
- us->sin_port = htons(externtlsport);
+ if (packet) {
+ ao2_t_ref(packet, -1, "tcptls packet sent, this is no longer needed");
+ }
+ ao2_unlock(me);
break;
- case SIP_TRANSPORT_UDP:
- break; /* fall through */
default:
- us->sin_port = htons(STANDARD_SIP_PORT); /* we should never get here */
- }
- }
- else
- ast_log(LOG_WARNING, "stun failed\n");
- ast_debug(1, "Target address %s is not local, substituting externip\n",
- ast_inet_ntoa(*(struct in_addr *)&them->s_addr));
- } else if (p) {
- /* no remapping, but we bind to a specific address, so use it. */
- switch (p->socket.type) {
- case SIP_TRANSPORT_TCP:
- if (sip_tcp_desc.local_address.sin_addr.s_addr) {
- *us = sip_tcp_desc.local_address;
- } else {
- us->sin_port = sip_tcp_desc.local_address.sin_port;
- }
- break;
- case SIP_TRANSPORT_TLS:
- if (sip_tls_desc.local_address.sin_addr.s_addr) {
- *us = sip_tls_desc.local_address;
- } else {
- us->sin_port = sip_tls_desc.local_address.sin_port;
- }
- break;
- case SIP_TRANSPORT_UDP:
- /* fall through on purpose */
- default:
- if (bindaddr.sin_addr.s_addr) {
- *us = bindaddr;
+ ast_log(LOG_ERROR, "Unknown tcptls thread alert '%d'\n", alert);
}
}
- } else if (bindaddr.sin_addr.s_addr) {
- *us = bindaddr;
}
- ast_debug(3, "Setting SIP_TRANSPORT_%s with address %s:%d\n", get_transport(p->socket.type), ast_inet_ntoa(us->sin_addr), ntohs(us->sin_port));
-}
-/*! \brief Append to SIP dialog history with arg list */
-static __attribute__((format(printf, 2, 0))) void append_history_va(struct sip_pvt *p, const char *fmt, va_list ap)
-{
- char buf[80], *c = buf; /* max history length */
- struct sip_history *hist;
- int l;
+ ast_debug(2, "Shutting down thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
- vsnprintf(buf, sizeof(buf), fmt, ap);
- strsep(&c, "\r\n"); /* Trim up everything after \r or \n */
- l = strlen(buf) + 1;
- if (!(hist = ast_calloc(1, sizeof(*hist) + l)))
- return;
- if (!p->history && !(p->history = ast_calloc(1, sizeof(*p->history)))) {
- ast_free(hist);
- return;
+cleanup:
+ if (me) {
+ ao2_t_unlink(threadt, me, "Removing tcptls helper thread, thread is closing");
+ ao2_t_ref(me, -1, "Removing tcp_helper_threads threadinfo ref");
}
- memcpy(hist->event, buf, l);
- if (p->history_entries == MAX_HISTORY_ENTRIES) {
- struct sip_history *oldest;
- oldest = AST_LIST_REMOVE_HEAD(p->history, list);
- p->history_entries--;
- ast_free(oldest);
+ if (reqcpy.data) {
+ ast_free(reqcpy.data);
}
- AST_LIST_INSERT_TAIL(p->history, hist, list);
- p->history_entries++;
-}
-
-/*! \brief Append to SIP dialog history with arg list */
-static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
-{
- va_list ap;
- if (!p)
- return;
+ if (req.data) {
+ ast_free(req.data);
+ req.data = NULL;
+ }
- if (!p->do_history && !recordhistory && !dumphistory)
- return;
+ /* if client, we own the parent session arguments and must decrement ref */
+ if (ca) {
+ ao2_t_ref(ca, -1, "closing tcptls thread, getting rid of client tcptls_session arguments");
+ }
- va_start(ap, fmt);
- append_history_va(p, fmt, ap);
- va_end(ap);
+ if (tcptls_session) {
+ ast_mutex_lock(&tcptls_session->lock);
+ if (tcptls_session->f) {
+ fclose(tcptls_session->f);
+ tcptls_session->f = NULL;
+ }
+ if (tcptls_session->fd != -1) {
+ close(tcptls_session->fd);
+ tcptls_session->fd = -1;
+ }
+ tcptls_session->parent = NULL;
+ ast_mutex_unlock(&tcptls_session->lock);
- return;
+ ao2_ref(tcptls_session, -1);
+ tcptls_session = NULL;
+ }
+ return NULL;
}
-/*! \brief Retransmit SIP message if no answer (Called from scheduler) */
-static int retrans_pkt(const void *data)
+
+/*!
+ * helper functions to unreference various types of objects.
+ * By handling them this way, we don't have to declare the
+ * destructor on each call, which removes the chance of errors.
+ */
+static void *unref_peer(struct sip_peer *peer, char *tag)
{
- struct sip_pkt *pkt = (struct sip_pkt *)data, *prev, *cur = NULL;
- int reschedule = DEFAULT_RETRANS;
- int xmitres = 0;
-
- /* Lock channel PVT */
- sip_pvt_lock(pkt->owner);
+ ao2_t_ref(peer, -1, tag);
+ return NULL;
+}
- if (pkt->retrans < MAX_RETRANS) {
- pkt->retrans++;
- if (!pkt->timer_t1) { /* Re-schedule using timer_a and timer_t1 */
- if (sipdebug)
- ast_debug(4, "SIP TIMER: Not rescheduling id #%d:%s (Method %d) (No timer T1)\n", pkt->retransid, sip_methods[pkt->method].text, pkt->method);
- } else {
- int siptimer_a;
+static struct sip_peer *ref_peer(struct sip_peer *peer, char *tag)
+{
+ ao2_t_ref(peer, 1, tag);
+ return peer;
+}
- if (sipdebug)
- ast_debug(4, "SIP TIMER: Rescheduling retransmission #%d (%d) %s - %d\n", pkt->retransid, pkt->retrans, sip_methods[pkt->method].text, pkt->method);
- if (!pkt->timer_a)
- pkt->timer_a = 2 ;
- else
- pkt->timer_a = 2 * pkt->timer_a;
+/*! \brief maintain proper refcounts for a sip_pvt's outboundproxy
+ *
+ * This function sets pvt's outboundproxy pointer to the one referenced
+ * by the proxy parameter. Because proxy may be a refcounted object, and
+ * because pvt's old outboundproxy may also be a refcounted object, we need
+ * to maintain the proper refcounts.
+ *
+ * \param pvt The sip_pvt for which we wish to set the outboundproxy
+ * \param proxy The sip_proxy which we will point pvt towards.
+ * \return Returns void
+ */
+static void ref_proxy(struct sip_pvt *pvt, struct sip_proxy *proxy)
+{
+ struct sip_proxy *old_obproxy = pvt->outboundproxy;
+ /* The sip_cfg.outboundproxy is statically allocated, and so
+ * we don't ever need to adjust refcounts for it
+ */
+ if (proxy && proxy != &sip_cfg.outboundproxy) {
+ ao2_ref(proxy, +1);
+ }
+ pvt->outboundproxy = proxy;
+ if (old_obproxy && old_obproxy != &sip_cfg.outboundproxy) {
+ ao2_ref(old_obproxy, -1);
+ }
+}
- /* For non-invites, a maximum of 4 secs */
- siptimer_a = pkt->timer_t1 * pkt->timer_a; /* Double each time */
- if (pkt->method != SIP_INVITE && siptimer_a > 4000)
- siptimer_a = 4000;
-
- /* Reschedule re-transmit */
- reschedule = siptimer_a;
- ast_debug(4, "** SIP timers: Rescheduling retransmission %d to %d ms (t1 %d ms (Retrans id #%d)) \n", pkt->retrans +1, siptimer_a, pkt->timer_t1, pkt->retransid);
- }
+/*!
+ * \brief Unlink a dialog from the dialogs container, as well as any other places
+ * that it may be currently stored.
+ *
+ * \note A reference to the dialog must be held before calling this function, and this
+ * function does not release that reference.
+ */
+void *dialog_unlink_all(struct sip_pvt *dialog, int lockowner, int lockdialoglist)
+{
+ struct sip_pkt *cp;
- if (sip_debug_test_pvt(pkt->owner)) {
- const struct sockaddr_in *dst = sip_real_dst(pkt->owner);
- ast_verbose("Retransmitting #%d (%s) to %s:%d:\n%s\n---\n",
- pkt->retrans, sip_nat_mode(pkt->owner),
- ast_inet_ntoa(dst->sin_addr),
- ntohs(dst->sin_port), pkt->data->str);
- }
+ dialog_ref(dialog, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done");
- append_history(pkt->owner, "ReTx", "%d %s", reschedule, pkt->data->str);
- xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen);
- sip_pvt_unlock(pkt->owner);
- if (xmitres == XMIT_ERROR)
- ast_log(LOG_WARNING, "Network error on retransmit in dialog %s\n", pkt->owner->callid);
- else
- return reschedule;
- }
- /* Too many retries */
- if (pkt->owner && pkt->method != SIP_OPTIONS && xmitres == 0) {
- if (pkt->is_fatal || sipdebug) /* Tell us if it's critical or if we're debugging */
- ast_log(LOG_WARNING, "Maximum retries exceeded on transmission %s for seqno %d (%s %s) -- See doc/sip-retransmit.txt.\n",
- pkt->owner->callid, pkt->seqno,
- pkt->is_fatal ? "Critical" : "Non-critical", pkt->is_resp ? "Response" : "Request");
- } else if (pkt->method == SIP_OPTIONS && sipdebug) {
- ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s) -- See doc/sip-retransmit.txt.\n", pkt->owner->callid);
+ ao2_t_unlink(dialogs, dialog, "unlinking dialog via ao2_unlink");
+ /* Unlink us from the owner (channel) if we have one */
+ if (dialog->owner) {
+ if (lockowner)
+ ast_channel_lock(dialog->owner);
+ ast_debug(1, "Detaching from channel %s\n", dialog->owner->name);
+ dialog->owner->tech_pvt = dialog_unref(dialog->owner->tech_pvt, "resetting channel dialog ptr in unlink_all");
+ if (lockowner)
+ ast_channel_unlock(dialog->owner);
}
- if (xmitres == XMIT_ERROR) {
- ast_log(LOG_WARNING, "Transmit error :: Cancelling transmission on Call ID %s\n", pkt->owner->callid);
- append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
- } else
- append_history(pkt->owner, "MaxRetries", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
-
- pkt->retransid = -1;
+ if (dialog->registry) {
+ if (dialog->registry->call == dialog)
+ dialog->registry->call = dialog_unref(dialog->registry->call, "nulling out the registry's call dialog field in unlink_all");
+ dialog->registry = registry_unref(dialog->registry, "delete dialog->registry");
+ }
+ if (dialog->stateid > -1) {
+ ast_extension_state_del(dialog->stateid, NULL);
+ dialog_unref(dialog, "removing extension_state, should unref the associated dialog ptr that was stored there.");
+ dialog->stateid = -1; /* shouldn't we 'zero' this out? */
+ }
+ /* Remove link from peer to subscription of MWI */
+ if (dialog->relatedpeer && dialog->relatedpeer->mwipvt == dialog)
+ dialog->relatedpeer->mwipvt = dialog_unref(dialog->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt");
+ if (dialog->relatedpeer && dialog->relatedpeer->call == dialog)
+ dialog->relatedpeer->call = dialog_unref(dialog->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself");
- if (pkt->is_fatal) {
- while(pkt->owner->owner && ast_channel_trylock(pkt->owner->owner)) {
- sip_pvt_unlock(pkt->owner); /* SIP_PVT, not channel */
- usleep(1);
- sip_pvt_lock(pkt->owner);
+ /* remove all current packets in this dialog */
+ while((cp = dialog->packets)) {
+ dialog->packets = dialog->packets->next;
+ AST_SCHED_DEL(sched, cp->retransid);
+ dialog_unref(cp->owner, "remove all current packets in this dialog, and the pointer to the dialog too as part of __sip_destroy");
+ if (cp->data) {
+ ast_free(cp->data);
}
+ ast_free(cp);
+ }
- if (pkt->owner->owner && !pkt->owner->owner->hangupcause)
- pkt->owner->owner->hangupcause = AST_CAUSE_NO_USER_RESPONSE;
-
- if (pkt->owner->owner) {
- sip_alreadygone(pkt->owner);
- ast_log(LOG_WARNING, "Hanging up call %s - no reply to our critical packet (see doc/sip-retransmit.txt).\n", pkt->owner->callid);
- ast_queue_hangup_with_cause(pkt->owner->owner, AST_CAUSE_PROTOCOL_ERROR);
- ast_channel_unlock(pkt->owner->owner);
- } else {
- /* If no channel owner, destroy now */
+ AST_SCHED_DEL_UNREF(sched, dialog->waitid, dialog_unref(dialog, "when you delete the waitid sched, you should dec the refcount for the stored dialog ptr"));
- /* Let the peerpoke system expire packets when the timer expires for poke_noanswer */
- if (pkt->method != SIP_OPTIONS && pkt->method != SIP_REGISTER) {
- pvt_set_needdestroy(pkt->owner, "no response to critical packet");
- sip_alreadygone(pkt->owner);
- append_history(pkt->owner, "DialogKill", "Killing this failed dialog immediately");
- }
- }
- }
+ AST_SCHED_DEL_UNREF(sched, dialog->initid, dialog_unref(dialog, "when you delete the initid sched, you should dec the refcount for the stored dialog ptr"));
+
+ if (dialog->autokillid > -1)
+ AST_SCHED_DEL_UNREF(sched, dialog->autokillid, dialog_unref(dialog, "when you delete the autokillid sched, you should dec the refcount for the stored dialog ptr"));
- if (pkt->method == SIP_BYE) {
- /* We're not getting answers on SIP BYE's. Tear down the call anyway. */
- if (pkt->owner->owner)
- ast_channel_unlock(pkt->owner->owner);
- append_history(pkt->owner, "ByeFailure", "Remote peer doesn't respond to bye. Destroying call anyway.");
- pvt_set_needdestroy(pkt->owner, "no response to BYE");
+ if (dialog->request_queue_sched_id > -1) {
+ AST_SCHED_DEL_UNREF(sched, dialog->request_queue_sched_id, dialog_unref(dialog, "when you delete the request_queue_sched_id sched, you should dec the refcount for the stored dialog ptr"));
}
- /* Remove the packet */
- for (prev = NULL, cur = pkt->owner->packets; cur; prev = cur, cur = cur->next) {
- if (cur == pkt) {
- UNLINK(cur, pkt->owner->packets, prev);
- sip_pvt_unlock(pkt->owner);
- if (pkt->owner)
- pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
- if (pkt->data)
- ast_free(pkt->data);
- pkt->data = NULL;
- ast_free(pkt);
- return 0;
- }
+ AST_SCHED_DEL_UNREF(sched, dialog->provisional_keepalive_sched_id, dialog_unref(dialog, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
+
+ if (dialog->t38id > -1) {
+ AST_SCHED_DEL_UNREF(sched, dialog->t38id, dialog_unref(dialog, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr"));
}
- /* error case */
- ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n");
- sip_pvt_unlock(pkt->owner);
- return 0;
+
+ dialog_unref(dialog, "Let's unbump the count in the unlink so the poor pvt can disappear if it is time");
+ return NULL;
}
-/*! \brief Transmit packet with retransmits
- \return 0 on success, -1 on failure to allocate packet
-*/
-static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, struct ast_str *data, int len, int fatal, int sipmethod)
+void *registry_unref(struct sip_registry *reg, char *tag)
{
- struct sip_pkt *pkt = NULL;
- int siptimer_a = DEFAULT_RETRANS;
- int xmitres = 0;
- int respid;
+ ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount - 1);
+ ASTOBJ_UNREF(reg, sip_registry_destroy);
+ return NULL;
+}
- if (sipmethod == SIP_INVITE) {
- /* Note this is a pending invite */
- p->pendinginvite = seqno;
- }
+/*! \brief Add object reference to SIP registry */
+static struct sip_registry *registry_addref(struct sip_registry *reg, char *tag)
+{
+ ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount + 1);
+ return ASTOBJ_REF(reg); /* Add pointer to registry in packet */
+}
- /* If the transport is something reliable (TCP or TLS) then don't really send this reliably */
- /* I removed the code from retrans_pkt that does the same thing so it doesn't get loaded into the scheduler */
- /*! \todo According to the RFC some packets need to be retransmitted even if its TCP, so this needs to get revisited */
- if (!(p->socket.type & SIP_TRANSPORT_UDP)) {
- xmitres = __sip_xmit(p, data, len); /* Send packet */
- if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */
- append_history(p, "XmitErr", "%s", fatal ? "(Critical)" : "(Non-critical)");
- return AST_FAILURE;
- } else {
- return AST_SUCCESS;
- }
- }
+/*! \brief Interface structure with callbacks used to connect to UDPTL module*/
+static struct ast_udptl_protocol sip_udptl = {
+ type: "SIP",
+ get_udptl_info: sip_get_udptl_peer,
+ set_udptl_peer: sip_set_udptl_peer,
+};
- if (!(pkt = ast_calloc(1, sizeof(*pkt) + len + 1)))
- return AST_FAILURE;
- /* copy data, add a terminator and save length */
- if (!(pkt->data = ast_str_create(len))) {
- ast_free(pkt);
- return AST_FAILURE;
- }
- ast_str_set(&pkt->data, 0, "%s%s", data->str, "\0");
- pkt->packetlen = len;
- /* copy other parameters from the caller */
- pkt->method = sipmethod;
- pkt->seqno = seqno;
- pkt->is_resp = resp;
- pkt->is_fatal = fatal;
- pkt->owner = dialog_ref(p, "__sip_reliable_xmit: setting pkt->owner");
- pkt->next = p->packets;
- p->packets = pkt; /* Add it to the queue */
- if (resp) {
- /* Parse out the response code */
- if (sscanf(ast_str_buffer(pkt->data), "SIP/2.0 %30u", &respid) == 1) {
- pkt->response_code = respid;
- }
- }
- pkt->timer_t1 = p->timer_t1; /* Set SIP timer T1 */
- pkt->retransid = -1;
- if (pkt->timer_t1)
- siptimer_a = pkt->timer_t1 * 2;
+static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
- /* Schedule retransmission */
- AST_SCHED_REPLACE_VARIABLE(pkt->retransid, sched, siptimer_a, retrans_pkt, pkt, 1);
- if (sipdebug)
- ast_debug(4, "*** SIP TIMER: Initializing retransmit timer on packet: Id #%d\n", pkt->retransid);
- xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen); /* Send packet */
+/*! \brief Convert transfer status to string */
+static const char *referstatus2str(enum referstatus rstatus)
+{
+ return map_x_s(referstatusstrings, rstatus, "");
+}
- if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */
- append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
- ast_log(LOG_ERROR, "Serious Network Trouble; __sip_xmit returns error for pkt data\n");
- AST_SCHED_DEL(sched, pkt->retransid);
- p->packets = pkt->next;
- pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
- ast_free(pkt->data);
- ast_free(pkt);
- return AST_FAILURE;
- } else {
- return AST_SUCCESS;
- }
+static inline void pvt_set_needdestroy(struct sip_pvt *pvt, const char *reason)
+{
+ append_history(pvt, "NeedDestroy", "Setting needdestroy because %s", reason);
+ pvt->needdestroy = 1;
}
-/*! \brief Kill a SIP dialog (called only by the scheduler)
- * The scheduler has a reference to this dialog when p->autokillid != -1,
- * and we are called using that reference. So if the event is not
- * rescheduled, we need to call dialog_unref().
- */
-static int __sip_autodestruct(const void *data)
+/*! \brief Initialize the initital request packet in the pvt structure.
+ This packet is used for creating replies and future requests in
+ a dialog */
+static void initialize_initreq(struct sip_pvt *p, struct sip_request *req)
{
- struct sip_pvt *p = (struct sip_pvt *)data;
+ if (p->initreq.headers)
+ ast_debug(1, "Initializing already initialized SIP dialog %s (presumably reinvite)\n", p->callid);
+ else
+ ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
+ /* Use this as the basis */
+ copy_request(&p->initreq, req);
+ parse_request(&p->initreq);
+ if (req->debug)
+ ast_verbose("Initreq: %d headers, %d lines\n", p->initreq.headers, p->initreq.lines);
+}
- /* If this is a subscription, tell the phone that we got a timeout */
- if (p->subscribed) {
- transmit_state_notify(p, AST_EXTENSION_DEACTIVATED, 1, TRUE); /* Send last notification */
- p->subscribed = NONE;
- append_history(p, "Subscribestatus", "timeout");
- ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", p->callid ? p->callid : "<unknown>");
- return 10000; /* Reschedule this destruction so that we know that it's gone */
- }
+/*! \brief Encapsulate setting of SIP_ALREADYGONE to be able to trace it with debugging */
+static void sip_alreadygone(struct sip_pvt *dialog)
+{
+ ast_debug(3, "Setting SIP_ALREADYGONE on dialog %s\n", dialog->callid);
+ dialog->alreadygone = 1;
+}
- /* If there are packets still waiting for delivery, delay the destruction */
- if (p->packets) {
- if (!p->needdestroy) {
- char method_str[31];
- ast_debug(3, "Re-scheduled destruction of SIP call %s\n", p->callid ? p->callid : "<unknown>");
- append_history(p, "ReliableXmit", "timeout");
- if (sscanf(p->lastmsg, "Tx: %30s", method_str) == 1 || sscanf(p->lastmsg, "Rx: %30s", method_str) == 1) {
- if (method_match(SIP_CANCEL, method_str) || method_match(SIP_BYE, method_str)) {
- pvt_set_needdestroy(p, "autodestruct");
- }
- }
- return 10000;
- } else {
- /* They've had their chance to respond. Time to bail */
- __sip_pretend_ack(p);
+/*! Resolve DNS srv name or host name in a sip_proxy structure */
+static int proxy_update(struct sip_proxy *proxy)
+{
+ /* if it's actually an IP address and not a name,
+ there's no need for a managed lookup */
+ if (!inet_aton(proxy->name, &proxy->ip.sin_addr)) {
+ /* Ok, not an IP address, then let's check if it's a domain or host */
+ /* XXX Todo - if we have proxy port, don't do SRV */
+ if (ast_get_ip_or_srv(&proxy->ip, proxy->name, sip_cfg.srvlookup ? "_sip._udp" : NULL) < 0) {
+ ast_log(LOG_WARNING, "Unable to locate host '%s'\n", proxy->name);
+ return FALSE;
}
}
+ proxy->last_dnsupdate = time(NULL);
+ return TRUE;
+}
- if (p->subscribed == MWI_NOTIFICATION) {
- if (p->relatedpeer) {
- p->relatedpeer = unref_peer(p->relatedpeer, "__sip_autodestruct: unref peer p->relatedpeer"); /* Remove link to peer. If it's realtime, make sure it's gone from memory) */
- }
+/*! \brief converts ascii port to int representation. If no
+ * pt buffer is provided or the pt has errors when being converted
+ * to an int value, the port provided as the standard is used.
+ */
+unsigned int port_str2int(const char *pt, unsigned int standard)
+{
+ int port = standard;
+ if (ast_strlen_zero(pt) || (sscanf(pt, "%30d", &port) != 1) || (port < 1) || (port > 65535)) {
+ port = standard;
}
- /* Reset schedule ID */
- p->autokillid = -1;
-
- if (p->owner) {
- ast_log(LOG_WARNING, "Autodestruct on dialog '%s' with owner in place (Method: %s)\n", p->callid, sip_methods[p->method].text);
- ast_queue_hangup_with_cause(p->owner, AST_CAUSE_PROTOCOL_ERROR);
- } else if (p->refer && !p->alreadygone) {
- ast_debug(3, "Finally hanging up channel after transfer: %s\n", p->callid);
- transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1);
- append_history(p, "ReferBYE", "Sending BYE on transferer call leg %s", p->callid);
- sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
- } else {
- append_history(p, "AutoDestroy", "%s", p->callid);
- ast_debug(3, "Auto destroying SIP dialog '%s'\n", p->callid);
- dialog_unlink_all(p, TRUE, TRUE); /* once it's unlinked and unrefd everywhere, it'll be freed automagically */
- /* dialog_unref(p, "unref dialog-- no other matching conditions"); -- unlink all now should finish off the dialog's references and free it. */
- /* sip_destroy(p); */ /* Go ahead and destroy dialog. All attempts to recover is done */
- /* sip_destroy also absorbs the reference */
- }
- dialog_unref(p, "The ref to a dialog passed to this sched callback is going out of scope; unref it.");
- return 0;
+ return port;
}
-/*! \brief Schedule destruction of SIP dialog */
-void sip_scheddestroy(struct sip_pvt *p, int ms)
+/*! \brief Allocate and initialize sip proxy */
+static struct sip_proxy *proxy_allocate(char *name, char *port, int force)
{
- if (ms < 0) {
- if (p->timer_t1 == 0) {
- p->timer_t1 = global_t1; /* Set timer T1 if not set (RFC 3261) */
- }
- if (p->timer_b == 0) {
- p->timer_b = global_timer_b; /* Set timer B if not set (RFC 3261) */
- }
- ms = p->timer_t1 * 64;
+ struct sip_proxy *proxy;
+
+ if (ast_strlen_zero(name)) {
+ return NULL;
}
- if (sip_debug_test_pvt(p))
- ast_verbose("Scheduling destruction of SIP dialog '%s' in %d ms (Method: %s)\n", p->callid, ms, sip_methods[p->method].text);
- if (sip_cancel_destroy(p))
- ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
- if (p->do_history)
- append_history(p, "SchedDestroy", "%d ms", ms);
- p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p, "setting ref as passing into ast_sched_add for __sip_autodestruct"));
+ proxy = ao2_alloc(sizeof(*proxy), NULL);
+ if (!proxy)
+ return NULL;
+ proxy->force = force;
+ ast_copy_string(proxy->name, name, sizeof(proxy->name));
+ proxy->ip.sin_port = htons(port_str2int(port, STANDARD_SIP_PORT));
+ proxy_update(proxy);
+ return proxy;
+}
- if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_schedid > 0)
- stop_session_timer(p);
+/*! \brief Get default outbound proxy or global proxy */
+static struct sip_proxy *obproxy_get(struct sip_pvt *dialog, struct sip_peer *peer)
+{
+ if (peer && peer->outboundproxy) {
+ if (sipdebug)
+ ast_debug(1, "OBPROXY: Applying peer OBproxy to this call\n");
+ append_history(dialog, "OBproxy", "Using peer obproxy %s", peer->outboundproxy->name);
+ return peer->outboundproxy;
+ }
+ if (sip_cfg.outboundproxy.name[0]) {
+ if (sipdebug)
+ ast_debug(1, "OBPROXY: Applying global OBproxy to this call\n");
+ append_history(dialog, "OBproxy", "Using global obproxy %s", sip_cfg.outboundproxy.name);
+ return &sip_cfg.outboundproxy;
+ }
+ if (sipdebug)
+ ast_debug(1, "OBPROXY: Not applying OBproxy to this call\n");
+ return NULL;
}
-/*! \brief Cancel destruction of SIP dialog.
- * Be careful as this also absorbs the reference - if you call it
- * from within the scheduler, this might be the last reference.
+/*! \brief returns true if 'name' (with optional trailing whitespace)
+ * matches the sip method 'id'.
+ * Strictly speaking, SIP methods are case SENSITIVE, but we do
+ * a case-insensitive comparison to be more tolerant.
+ * following Jon Postel's rule: Be gentle in what you accept, strict with what you send
*/
-int sip_cancel_destroy(struct sip_pvt *p)
+static int method_match(enum sipmethod id, const char *name)
{
- int res = 0;
- if (p->autokillid > -1) {
- int res3;
+ int len = strlen(sip_methods[id].text);
+ int l_name = name ? strlen(name) : 0;
+ /* true if the string is long enough, and ends with whitespace, and matches */
+ return (l_name >= len && name[len] < 33 &&
+ !strncasecmp(sip_methods[id].text, name, len));
+}
- if (!(res3 = ast_sched_del(sched, p->autokillid))) {
- append_history(p, "CancelDestroy", "");
- p->autokillid = -1;
- dialog_unref(p, "dialog unrefd because autokillid is de-sched'd");
- }
+/*! \brief find_sip_method: Find SIP method from header */
+static int find_sip_method(const char *msg)
+{
+ int i, res = 0;
+
+ if (ast_strlen_zero(msg))
+ return 0;
+ for (i = 1; i < ARRAY_LEN(sip_methods) && !res; i++) {
+ if (method_match(i, msg))
+ res = sip_methods[i].id;
}
return res;
}
-/*! \brief Acknowledges receipt of a packet and stops retransmission
- * called with p locked*/
-int __sip_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
+/*! \brief Parse supported header in incoming packet */
+static unsigned int parse_sip_options(struct sip_pvt *pvt, const char *supported)
{
- struct sip_pkt *cur, *prev = NULL;
- const char *msg = "Not Found"; /* used only for debugging */
- int res = FALSE;
+ char *next, *sep;
+ char *temp;
+ unsigned int profile = 0;
+ int i, found;
- /* If we have an outbound proxy for this dialog, then delete it now since
- the rest of the requests in this dialog needs to follow the routing.
- If obforcing is set, we will keep the outbound proxy during the whole
- dialog, regardless of what the SIP rfc says
- */
- if (p->outboundproxy && !p->outboundproxy->force){
- ref_proxy(p, NULL);
- }
+ if (ast_strlen_zero(supported) )
+ return 0;
+ temp = ast_strdupa(supported);
- for (cur = p->packets; cur; prev = cur, cur = cur->next) {
- if (cur->seqno != seqno || cur->is_resp != resp)
- continue;
- if (cur->is_resp || cur->method == sipmethod) {
- res = TRUE;
- msg = "Found";
- if (!resp && (seqno == p->pendinginvite)) {
- ast_debug(1, "Acked pending invite %d\n", p->pendinginvite);
- p->pendinginvite = 0;
- }
- if (cur->retransid > -1) {
- if (sipdebug)
- ast_debug(4, "** SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", cur->retransid);
- }
- /* This odd section is designed to thwart a
- * race condition in the packet scheduler. There are
- * two conditions under which deleting the packet from the
- * scheduler can fail.
- *
- * 1. The packet has been removed from the scheduler because retransmission
- * is being attempted. The problem is that if the packet is currently attempting
- * retransmission and we are at this point in the code, then that MUST mean
- * that retrans_pkt is waiting on p's lock. Therefore we will relinquish the
- * lock temporarily to allow retransmission.
- *
- * 2. The packet has reached its maximum number of retransmissions and has
- * been permanently removed from the packet scheduler. If this is the case, then
- * the packet's retransid will be set to -1. The atomicity of the setting and checking
- * of the retransid to -1 is ensured since in both cases p's lock is held.
- */
- while (cur->retransid > -1 && ast_sched_del(sched, cur->retransid)) {
- sip_pvt_unlock(p);
- usleep(1);
- sip_pvt_lock(p);
+ if (sipdebug)
+ ast_debug(3, "Begin: parsing SIP \"Supported: %s\"\n", supported);
+
+ for (next = temp; next; next = sep) {
+ found = FALSE;
+ if ( (sep = strchr(next, ',')) != NULL)
+ *sep++ = '\0';
+ next = ast_skip_blanks(next);
+ if (sipdebug)
+ ast_debug(3, "Found SIP option: -%s-\n", next);
+ for (i = 0; i < ARRAY_LEN(sip_options); i++) {
+ if (!strcasecmp(next, sip_options[i].text)) {
+ profile |= sip_options[i].id;
+ found = TRUE;
+ if (sipdebug)
+ ast_debug(3, "Matched SIP option: %s\n", next);
+ break;
}
- UNLINK(cur, p->packets, prev);
- dialog_unref(cur->owner, "unref pkt cur->owner dialog from sip ack before freeing pkt");
- if (cur->data)
- ast_free(cur->data);
- ast_free(cur);
- break;
}
- }
- ast_debug(1, "Stopping retransmission on '%s' of %s %d: Match %s\n",
- p->callid, resp ? "Response" : "Request", seqno, msg);
- return res;
-}
-/*! \brief Pretend to ack all packets
- * called with p locked */
-void __sip_pretend_ack(struct sip_pvt *p)
-{
- struct sip_pkt *cur = NULL;
+ /* This function is used to parse both Suported: and Require: headers.
+ Let the caller of this function know that an unknown option tag was
+ encountered, so that if the UAC requires it then the request can be
+ rejected with a 420 response. */
+ if (!found)
+ profile |= SIP_OPT_UNKNOWN;
- while (p->packets) {
- int method;
- if (cur == p->packets) {
- ast_log(LOG_WARNING, "Have a packet that doesn't want to give up! %s\n", sip_methods[cur->method].text);
- return;
+ if (!found && sipdebug) {
+ if (!strncasecmp(next, "x-", 2))
+ ast_debug(3, "Found private SIP option, not supported: %s\n", next);
+ else
+ ast_debug(3, "Found no match for SIP option: %s (Please file bug report!)\n", next);
}
- cur = p->packets;
- method = (cur->method) ? cur->method : find_sip_method(cur->data->str);
- __sip_ack(p, cur->seqno, cur->is_resp, method);
}
+
+ if (pvt)
+ pvt->sipoptions = profile;
+ return profile;
}
-/*! \brief Acks receipt of packet, keep it around (used for provisional responses) */
-int __sip_semi_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
+/*! \brief See if we pass debug IP filter */
+static inline int sip_debug_test_addr(const struct sockaddr_in *addr)
{
- struct sip_pkt *cur;
- int res = FALSE;
-
- for (cur = p->packets; cur; cur = cur->next) {
- if (cur->seqno == seqno && cur->is_resp == resp &&
- (cur->is_resp || method_match(sipmethod, cur->data->str))) {
- /* this is our baby */
- if (cur->retransid > -1) {
- if (sipdebug)
- ast_debug(4, "*** SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", cur->retransid, sip_methods[sipmethod].text);
- }
- AST_SCHED_DEL(sched, cur->retransid);
- res = TRUE;
- break;
- }
+ if (!sipdebug)
+ return 0;
+ if (debugaddr.sin_addr.s_addr) {
+ if (((ntohs(debugaddr.sin_port) != 0)
+ && (debugaddr.sin_port != addr->sin_port))
+ || (debugaddr.sin_addr.s_addr != addr->sin_addr.s_addr))
+ return 0;
}
- ast_debug(1, "(Provisional) Stopping retransmission (but retaining packet) on '%s' %s %d: %s\n", p->callid, resp ? "Response" : "Request", seqno, res == -1 ? "Not Found" : "Found");
- return res;
+ return 1;
}
+/*! \brief The real destination address for a write */
+static const struct sockaddr_in *sip_real_dst(const struct sip_pvt *p)
+{
+ if (p->outboundproxy)
+ return &p->outboundproxy->ip;
+
+ return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT) ? &p->recv : &p->sa;
+}
-/*! \brief Copy SIP request, parse it */
-static void parse_copy(struct sip_request *dst, const struct sip_request *src)
+/*! \brief Display SIP nat mode */
+static const char *sip_nat_mode(const struct sip_pvt *p)
{
- copy_request(dst, src);
- parse_request(dst);
+ return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) ? "NAT" : "no NAT";
}
-/*! \brief add a blank line if no body */
-static void add_blank(struct sip_request *req)
+/*! \brief Test PVT for debugging output */
+static inline int sip_debug_test_pvt(struct sip_pvt *p)
{
- if (!req->lines) {
- /* Add extra empty return. add_header() reserves 4 bytes so cannot be truncated */
- ast_str_append(&req->data, 0, "\r\n");
- req->len = ast_str_strlen(req->data);
- }
+ if (!sipdebug)
+ return 0;
+ return sip_debug_test_addr(sip_real_dst(p));
}
-static int send_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp)
+/*! \brief Return int representing a bit field of transport types found in const char *transport */
+static int get_transport_str2enum(const char *transport)
{
- const char *msg = NULL;
+ int res = 0;
- if (!pvt->last_provisional || !strncasecmp(pvt->last_provisional, "100", 3)) {
- msg = "183 Session Progress";
+ if (ast_strlen_zero(transport)) {
+ return res;
}
- if (pvt->invitestate < INV_COMPLETED) {
- if (with_sdp) {
- transmit_response_with_sdp(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq, XMIT_UNRELIABLE, FALSE, FALSE);
- } else {
- transmit_response(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq);
- }
- return PROVIS_KEEPALIVE_TIMEOUT;
+ if (!strcasecmp(transport, "udp")) {
+ res |= SIP_TRANSPORT_UDP;
+ }
+ if (!strcasecmp(transport, "tcp")) {
+ res |= SIP_TRANSPORT_TCP;
+ }
+ if (!strcasecmp(transport, "tls")) {
+ res |= SIP_TRANSPORT_TLS;
}
- return 0;
+ return res;
}
-static int send_provisional_keepalive(const void *data) {
- struct sip_pvt *pvt = (struct sip_pvt *) data;
-
- return send_provisional_keepalive_full(pvt, 0);
+/*! \brief Return configuration of transports for a device */
+static inline const char *get_transport_list(unsigned int transports) {
+ switch (transports) {
+ case SIP_TRANSPORT_UDP:
+ return "UDP";
+ case SIP_TRANSPORT_TCP:
+ return "TCP";
+ case SIP_TRANSPORT_TLS:
+ return "TLS";
+ case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TCP:
+ return "TCP,UDP";
+ case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TLS:
+ return "TLS,UDP";
+ case SIP_TRANSPORT_TCP | SIP_TRANSPORT_TLS:
+ return "TLS,TCP";
+ default:
+ return transports ?
+ "TLS,TCP,UDP" : "UNKNOWN";
+ }
}
-static int send_provisional_keepalive_with_sdp(const void *data) {
- struct sip_pvt *pvt = (void *)data;
+/*! \brief Return transport as string */
+static inline const char *get_transport(enum sip_transport t)
+{
+ switch (t) {
+ case SIP_TRANSPORT_UDP:
+ return "UDP";
+ case SIP_TRANSPORT_TCP:
+ return "TCP";
+ case SIP_TRANSPORT_TLS:
+ return "TLS";
+ }
- return send_provisional_keepalive_full(pvt, 1);
+ return "UNKNOWN";
}
-static void update_provisional_keepalive(struct sip_pvt *pvt, int with_sdp)
+/*! \brief Return transport of dialog.
+ \note this is based on a false assumption. We don't always use the
+ outbound proxy for all requests in a dialog. It depends on the
+ "force" parameter. The FIRST request is always sent to the ob proxy.
+ \todo Fix this function to work correctly
+*/
+static inline const char *get_transport_pvt(struct sip_pvt *p)
{
- AST_SCHED_DEL_UNREF(sched, pvt->provisional_keepalive_sched_id, dialog_unref(pvt, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
+ if (p->outboundproxy && p->outboundproxy->transport) {
+ set_socket_transport(&p->socket, p->outboundproxy->transport);
+ }
- pvt->provisional_keepalive_sched_id = ast_sched_add(sched, PROVIS_KEEPALIVE_TIMEOUT,
- with_sdp ? send_provisional_keepalive_with_sdp : send_provisional_keepalive, dialog_ref(pvt, "Increment refcount to pass dialog pointer to sched callback"));
+ return get_transport(p->socket.type);
}
-/*! \brief Transmit response on SIP request*/
-static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
+/*! \brief Transmit SIP message
+ Sends a SIP request or response on a given socket (in the pvt)
+ Called by retrans_pkt, send_request, send_response and
+ __sip_reliable_xmit
+ \return length of transmitted message, XMIT_ERROR on known network failures -1 on other failures.
+*/
+static int __sip_xmit(struct sip_pvt *p, struct ast_str *data, int len)
{
- int res;
+ int res = 0;
+ const struct sockaddr_in *dst = sip_real_dst(p);
- add_blank(req);
- if (sip_debug_test_pvt(p)) {
- const struct sockaddr_in *dst = sip_real_dst(p);
+ ast_debug(2, "Trying to put '%.11s' onto %s socket destined for %s:%d\n", data->str, get_transport_pvt(p), ast_inet_ntoa(dst->sin_addr), htons(dst->sin_port));
- ast_verbose("\n<--- %sTransmitting (%s) to %s:%d --->\n%s\n<------------>\n",
- reliable ? "Reliably " : "", sip_nat_mode(p),
- ast_inet_ntoa(dst->sin_addr),
- ntohs(dst->sin_port), req->data->str);
- }
- if (p->do_history) {
- struct sip_request tmp = { .rlPart1 = 0, };
- parse_copy(&tmp, req);
- append_history(p, reliable ? "TxRespRel" : "TxResp", "%s / %s - %s", tmp.data->str, get_header(&tmp, "CSeq"),
- (tmp.method == SIP_RESPONSE || tmp.method == SIP_UNKNOWN) ? REQ_OFFSET_TO_STR(&tmp, rlPart2) : sip_methods[tmp.method].text);
- ast_free(tmp.data);
+ if (sip_prepare_socket(p) < 0)
+ return XMIT_ERROR;
+
+ if (p->socket.type == SIP_TRANSPORT_UDP) {
+ res = sendto(p->socket.fd, data->str, len, 0, (const struct sockaddr *)dst, sizeof(struct sockaddr_in));
+ } else if (p->socket.tcptls_session) {
+ res = sip_tcptls_write(p->socket.tcptls_session, data->str, len);
+ } else {
+ ast_debug(2, "Socket type is TCP but no tcptls_session is present to write to\n");
+ return XMIT_ERROR;
}
- /* If we are sending a final response to an INVITE, stop retransmitting provisional responses */
- if (p->initreq.method == SIP_INVITE && reliable == XMIT_CRITICAL) {
- AST_SCHED_DEL_UNREF(sched, p->provisional_keepalive_sched_id, dialog_unref(p, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
+ if (res == -1) {
+ switch (errno) {
+ case EBADF: /* Bad file descriptor - seems like this is generated when the host exist, but doesn't accept the UDP packet */
+ case EHOSTUNREACH: /* Host can't be reached */
+ case ENETDOWN: /* Interface down */
+ case ENETUNREACH: /* Network failure */
+ case ECONNREFUSED: /* ICMP port unreachable */
+ res = XMIT_ERROR; /* Don't bother with trying to transmit again */
+ }
}
+ if (res != len)
+ ast_log(LOG_WARNING, "sip_xmit of %p (len %d) to %s:%d returned %d: %s\n", data, len, ast_inet_ntoa(dst->sin_addr), ntohs(dst->sin_port), res, strerror(errno));
- res = (reliable) ?
- __sip_reliable_xmit(p, seqno, 1, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
- __sip_xmit(p, req->data, req->len);
- ast_free(req->data);
- req->data = NULL;
- if (res > 0)
- return 0;
return res;
}
-/*! \brief Send SIP Request to the other part of the dialogue
- \return see \ref __sip_xmit
-*/
-static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
+/*! \brief Build a Via header for a request */
+static void build_via(struct sip_pvt *p)
{
- int res;
-
- /* If we have an outbound proxy, reset peer address
- Only do this once.
- */
- if (p->outboundproxy) {
- p->sa = p->outboundproxy->ip;
- }
+ /* Work around buggy UNIDEN UIP200 firmware */
+ const char *rport = (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT)) ? ";rport" : "";
- add_blank(req);
- if (sip_debug_test_pvt(p)) {
- if (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT))
- ast_verbose("%sTransmitting (NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->recv.sin_addr), ntohs(p->recv.sin_port), req->data->str);
- else
- ast_verbose("%sTransmitting (no NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->sa.sin_addr), ntohs(p->sa.sin_port), req->data->str);
- }
- if (p->do_history) {
- struct sip_request tmp = { .rlPart1 = 0, };
- parse_copy(&tmp, req);
- append_history(p, reliable ? "TxReqRel" : "TxReq", "%s / %s - %s", tmp.data->str, get_header(&tmp, "CSeq"), sip_methods[tmp.method].text);
- ast_free(tmp.data);
- }
- res = (reliable) ?
- __sip_reliable_xmit(p, seqno, 0, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
- __sip_xmit(p, req->data, req->len);
- if (req->data) {
- ast_free(req->data);
- req->data = NULL;
- }
- return res;
+ /* z9hG4bK is a magic cookie. See RFC 3261 section 8.1.1.7 */
+ snprintf(p->via, sizeof(p->via), "SIP/2.0/%s %s:%d;branch=z9hG4bK%08x%s",
+ get_transport_pvt(p),
+ ast_inet_ntoa(p->ourip.sin_addr),
+ ntohs(p->ourip.sin_port), (int) p->branch, rport);
}
-static void enable_dsp_detect(struct sip_pvt *p)
+/*! \brief NAT fix - decide which IP address to use for Asterisk server?
+ *
+ * Using the localaddr structure built up with localnet statements in sip.conf
+ * apply it to their address to see if we need to substitute our
+ * externip or can get away with our internal bindaddr
+ * 'us' is always overwritten.
+ */
+static void ast_sip_ouraddrfor(struct in_addr *them, struct sockaddr_in *us, struct sip_pvt *p)
{
- int features = 0;
+ struct sockaddr_in theirs;
+ /* Set want_remap to non-zero if we want to remap 'us' to an externally
+ * reachable IP address and port. This is done if:
+ * 1. we have a localaddr list (containing 'internal' addresses marked
+ * as 'deny', so ast_apply_ha() will return AST_SENSE_DENY on them,
+ * and AST_SENSE_ALLOW on 'external' ones);
+ * 2. either stunaddr or externip is set, so we know what to use as the
+ * externally visible address;
+ * 3. the remote address, 'them', is external;
+ * 4. the address returned by ast_ouraddrfor() is 'internal' (AST_SENSE_DENY
+ * when passed to ast_apply_ha() so it does need to be remapped.
+ * This fourth condition is checked later.
+ */
+ int want_remap;
- if (p->dsp) {
- return;
- }
+ *us = internip; /* starting guess for the internal address */
+ /* now ask the system what would it use to talk to 'them' */
+ ast_ouraddrfor(them, &us->sin_addr);
+ theirs.sin_addr = *them;
- if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
- (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
- if (!p->rtp || ast_rtp_instance_dtmf_mode_set(p->rtp, AST_RTP_DTMF_MODE_INBAND)) {
- features |= DSP_FEATURE_DIGIT_DETECT;
- }
- }
+ want_remap = localaddr &&
+ (externip.sin_addr.s_addr || stunaddr.sin_addr.s_addr) &&
+ ast_apply_ha(localaddr, &theirs) == AST_SENSE_ALLOW ;
- if (ast_test_flag(&p->flags[1], SIP_PAGE2_FAX_DETECT_CNG)) {
- features |= DSP_FEATURE_FAX_DETECT;
+ if (want_remap &&
+ (!sip_cfg.matchexterniplocally || !ast_apply_ha(localaddr, us)) ) {
+ /* if we used externhost or stun, see if it is time to refresh the info */
+ if (externexpire && time(NULL) >= externexpire) {
+ if (stunaddr.sin_addr.s_addr) {
+ ast_stun_request(sipsock, &stunaddr, NULL, &externip);
+ } else {
+ if (ast_parse_arg(externhost, PARSE_INADDR, &externip))
+ ast_log(LOG_NOTICE, "Warning: Re-lookup of '%s' failed!\n", externhost);
+ }
+ externexpire = time(NULL) + externrefresh;
+ }
+ if (externip.sin_addr.s_addr) {
+ *us = externip;
+ switch (p->socket.type) {
+ case SIP_TRANSPORT_TCP:
+ us->sin_port = htons(externtcpport);
+ break;
+ case SIP_TRANSPORT_TLS:
+ us->sin_port = htons(externtlsport);
+ break;
+ case SIP_TRANSPORT_UDP:
+ break; /* fall through */
+ default:
+ us->sin_port = htons(STANDARD_SIP_PORT); /* we should never get here */
+ }
+ }
+ else
+ ast_log(LOG_WARNING, "stun failed\n");
+ ast_debug(1, "Target address %s is not local, substituting externip\n",
+ ast_inet_ntoa(*(struct in_addr *)&them->s_addr));
+ } else if (p) {
+ /* no remapping, but we bind to a specific address, so use it. */
+ switch (p->socket.type) {
+ case SIP_TRANSPORT_TCP:
+ if (sip_tcp_desc.local_address.sin_addr.s_addr) {
+ *us = sip_tcp_desc.local_address;
+ } else {
+ us->sin_port = sip_tcp_desc.local_address.sin_port;
+ }
+ break;
+ case SIP_TRANSPORT_TLS:
+ if (sip_tls_desc.local_address.sin_addr.s_addr) {
+ *us = sip_tls_desc.local_address;
+ } else {
+ us->sin_port = sip_tls_desc.local_address.sin_port;
+ }
+ break;
+ case SIP_TRANSPORT_UDP:
+ /* fall through on purpose */
+ default:
+ if (bindaddr.sin_addr.s_addr) {
+ *us = bindaddr;
+ }
+ }
+ } else if (bindaddr.sin_addr.s_addr) {
+ *us = bindaddr;
}
+ ast_debug(3, "Setting SIP_TRANSPORT_%s with address %s:%d\n", get_transport(p->socket.type), ast_inet_ntoa(us->sin_addr), ntohs(us->sin_port));
+}
- if (!features) {
- return;
- }
+/*! \brief Append to SIP dialog history with arg list */
+static __attribute__((format(printf, 2, 0))) void append_history_va(struct sip_pvt *p, const char *fmt, va_list ap)
+{
+ char buf[80], *c = buf; /* max history length */
+ struct sip_history *hist;
+ int l;
- if (!(p->dsp = ast_dsp_new())) {
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ strsep(&c, "\r\n"); /* Trim up everything after \r or \n */
+ l = strlen(buf) + 1;
+ if (!(hist = ast_calloc(1, sizeof(*hist) + l)))
+ return;
+ if (!p->history && !(p->history = ast_calloc(1, sizeof(*p->history)))) {
+ ast_free(hist);
return;
}
-
- ast_dsp_set_features(p->dsp, features);
- if (global_relaxdtmf) {
- ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
+ memcpy(hist->event, buf, l);
+ if (p->history_entries == MAX_HISTORY_ENTRIES) {
+ struct sip_history *oldest;
+ oldest = AST_LIST_REMOVE_HEAD(p->history, list);
+ p->history_entries--;
+ ast_free(oldest);
}
+ AST_LIST_INSERT_TAIL(p->history, hist, list);
+ p->history_entries++;
}
-static void disable_dsp_detect(struct sip_pvt *p)
+/*! \brief Append to SIP dialog history with arg list */
+static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
{
- if (p->dsp) {
- ast_dsp_free(p->dsp);
- p->dsp = NULL;
- }
-}
+ va_list ap;
-/*! \brief Set an option on a SIP dialog */
-static int sip_setoption(struct ast_channel *chan, int option, void *data, int datalen)
-{
- int res = -1;
- struct sip_pvt *p = chan->tech_pvt;
+ if (!p)
+ return;
- switch (option) {
- case AST_OPTION_FORMAT_READ:
- res = ast_rtp_instance_set_read_format(p->rtp, *(int *) data);
- break;
- case AST_OPTION_FORMAT_WRITE:
- res = ast_rtp_instance_set_write_format(p->rtp, *(int *) data);
- break;
- case AST_OPTION_MAKE_COMPATIBLE:
- res = ast_rtp_instance_make_compatible(chan, p->rtp, (struct ast_channel *) data);
- break;
- case AST_OPTION_DIGIT_DETECT:
- if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
- (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
- char *cp = (char *) data;
+ if (!p->do_history && !recordhistory && !dumphistory)
+ return;
- ast_debug(1, "%sabling digit detection on %s\n", *cp ? "En" : "Dis", chan->name);
- if (*cp) {
- enable_dsp_detect(p);
- } else {
- disable_dsp_detect(p);
- }
- res = 0;
- }
- break;
- default:
- break;
- }
+ va_start(ap, fmt);
+ append_history_va(p, fmt, ap);
+ va_end(ap);
- return res;
+ return;
}
-/*! \brief Query an option on a SIP dialog */
-static int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen)
+/*! \brief Retransmit SIP message if no answer (Called from scheduler) */
+static int retrans_pkt(const void *data)
{
- int res = -1;
- enum ast_t38_state state = T38_STATE_UNAVAILABLE;
- struct sip_pvt *p = (struct sip_pvt *) chan->tech_pvt;
- char *cp;
-
- switch (option) {
- case AST_OPTION_T38_STATE:
- /* Make sure we got an ast_t38_state enum passed in */
- if (*datalen != sizeof(enum ast_t38_state)) {
- ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_STATE option. Expected %d, got %d\n", (int)sizeof(enum ast_t38_state), *datalen);
- return -1;
- }
+ struct sip_pkt *pkt = (struct sip_pkt *)data, *prev, *cur = NULL;
+ int reschedule = DEFAULT_RETRANS;
+ int xmitres = 0;
+
+ /* Lock channel PVT */
+ sip_pvt_lock(pkt->owner);
- sip_pvt_lock(p);
+ if (pkt->retrans < MAX_RETRANS) {
+ pkt->retrans++;
+ if (!pkt->timer_t1) { /* Re-schedule using timer_a and timer_t1 */
+ if (sipdebug)
+ ast_debug(4, "SIP TIMER: Not rescheduling id #%d:%s (Method %d) (No timer T1)\n", pkt->retransid, sip_methods[pkt->method].text, pkt->method);
+ } else {
+ int siptimer_a;
- /* Now if T38 support is enabled we need to look and see what the current state is to get what we want to report back */
- if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT)) {
- switch (p->t38.state) {
- case T38_LOCAL_REINVITE:
- case T38_PEER_REINVITE:
- state = T38_STATE_NEGOTIATING;
- break;
- case T38_ENABLED:
- state = T38_STATE_NEGOTIATED;
- break;
- default:
- state = T38_STATE_UNKNOWN;
- }
- }
+ if (sipdebug)
+ ast_debug(4, "SIP TIMER: Rescheduling retransmission #%d (%d) %s - %d\n", pkt->retransid, pkt->retrans, sip_methods[pkt->method].text, pkt->method);
+ if (!pkt->timer_a)
+ pkt->timer_a = 2 ;
+ else
+ pkt->timer_a = 2 * pkt->timer_a;
- sip_pvt_unlock(p);
+ /* For non-invites, a maximum of 4 secs */
+ siptimer_a = pkt->timer_t1 * pkt->timer_a; /* Double each time */
+ if (pkt->method != SIP_INVITE && siptimer_a > 4000)
+ siptimer_a = 4000;
+
+ /* Reschedule re-transmit */
+ reschedule = siptimer_a;
+ ast_debug(4, "** SIP timers: Rescheduling retransmission %d to %d ms (t1 %d ms (Retrans id #%d)) \n", pkt->retrans +1, siptimer_a, pkt->timer_t1, pkt->retransid);
+ }
- *((enum ast_t38_state *) data) = state;
- res = 0;
+ if (sip_debug_test_pvt(pkt->owner)) {
+ const struct sockaddr_in *dst = sip_real_dst(pkt->owner);
+ ast_verbose("Retransmitting #%d (%s) to %s:%d:\n%s\n---\n",
+ pkt->retrans, sip_nat_mode(pkt->owner),
+ ast_inet_ntoa(dst->sin_addr),
+ ntohs(dst->sin_port), pkt->data->str);
+ }
- break;
- case AST_OPTION_DIGIT_DETECT:
- cp = (char *) data;
- *cp = p->dsp ? 1 : 0;
- ast_debug(1, "Reporting digit detection %sabled on %s\n", *cp ? "en" : "dis", chan->name);
- break;
- default:
- break;
+ append_history(pkt->owner, "ReTx", "%d %s", reschedule, pkt->data->str);
+ xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen);
+ sip_pvt_unlock(pkt->owner);
+ if (xmitres == XMIT_ERROR)
+ ast_log(LOG_WARNING, "Network error on retransmit in dialog %s\n", pkt->owner->callid);
+ else
+ return reschedule;
}
+ /* Too many retries */
+ if (pkt->owner && pkt->method != SIP_OPTIONS && xmitres == 0) {
+ if (pkt->is_fatal || sipdebug) /* Tell us if it's critical or if we're debugging */
+ ast_log(LOG_WARNING, "Maximum retries exceeded on transmission %s for seqno %d (%s %s) -- See doc/sip-retransmit.txt.\n",
+ pkt->owner->callid, pkt->seqno,
+ pkt->is_fatal ? "Critical" : "Non-critical", pkt->is_resp ? "Response" : "Request");
+ } else if (pkt->method == SIP_OPTIONS && sipdebug) {
+ ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s) -- See doc/sip-retransmit.txt.\n", pkt->owner->callid);
- return res;
-}
-
-/*! \brief Locate closing quote in a string, skipping escaped quotes.
- * optionally with a limit on the search.
- * start must be past the first quote.
- */
-const char *find_closing_quote(const char *start, const char *lim)
-{
- char last_char = '\0';
- const char *s;
- for (s = start; *s && s != lim; last_char = *s++) {
- if (*s == '"' && last_char != '\\')
- break;
}
- return s;
-}
-
-/*! \brief Send message with Access-URL header, if this is an HTML URL only! */
-static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen)
-{
- struct sip_pvt *p = chan->tech_pvt;
+ if (xmitres == XMIT_ERROR) {
+ ast_log(LOG_WARNING, "Transmit error :: Cancelling transmission on Call ID %s\n", pkt->owner->callid);
+ append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
+ } else
+ append_history(pkt->owner, "MaxRetries", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
+
+ pkt->retransid = -1;
- if (subclass != AST_HTML_URL)
- return -1;
+ if (pkt->is_fatal) {
+ while(pkt->owner->owner && ast_channel_trylock(pkt->owner->owner)) {
+ sip_pvt_unlock(pkt->owner); /* SIP_PVT, not channel */
+ usleep(1);
+ sip_pvt_lock(pkt->owner);
+ }
- ast_string_field_build(p, url, "<%s>;mode=active", data);
+ if (pkt->owner->owner && !pkt->owner->owner->hangupcause)
+ pkt->owner->owner->hangupcause = AST_CAUSE_NO_USER_RESPONSE;
+
+ if (pkt->owner->owner) {
+ sip_alreadygone(pkt->owner);
+ ast_log(LOG_WARNING, "Hanging up call %s - no reply to our critical packet (see doc/sip-retransmit.txt).\n", pkt->owner->callid);
+ ast_queue_hangup_with_cause(pkt->owner->owner, AST_CAUSE_PROTOCOL_ERROR);
+ ast_channel_unlock(pkt->owner->owner);
+ } else {
+ /* If no channel owner, destroy now */
- if (sip_debug_test_pvt(p))
- ast_debug(1, "Send URL %s, state = %d!\n", data, chan->_state);
+ /* Let the peerpoke system expire packets when the timer expires for poke_noanswer */
+ if (pkt->method != SIP_OPTIONS && pkt->method != SIP_REGISTER) {
+ pvt_set_needdestroy(pkt->owner, "no response to critical packet");
+ sip_alreadygone(pkt->owner);
+ append_history(pkt->owner, "DialogKill", "Killing this failed dialog immediately");
+ }
+ }
+ }
- switch (chan->_state) {
- case AST_STATE_RING:
- transmit_response(p, "100 Trying", &p->initreq);
- break;
- case AST_STATE_RINGING:
- transmit_response(p, "180 Ringing", &p->initreq);
- break;
- case AST_STATE_UP:
- if (!p->pendinginvite) { /* We are up, and have no outstanding invite */
- transmit_reinvite_with_sdp(p, FALSE, FALSE);
- } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
- ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
- }
- break;
- default:
- ast_log(LOG_WARNING, "Don't know how to send URI when state is %d!\n", chan->_state);
+ if (pkt->method == SIP_BYE) {
+ /* We're not getting answers on SIP BYE's. Tear down the call anyway. */
+ if (pkt->owner->owner)
+ ast_channel_unlock(pkt->owner->owner);
+ append_history(pkt->owner, "ByeFailure", "Remote peer doesn't respond to bye. Destroying call anyway.");
+ pvt_set_needdestroy(pkt->owner, "no response to BYE");
}
+ /* Remove the packet */
+ for (prev = NULL, cur = pkt->owner->packets; cur; prev = cur, cur = cur->next) {
+ if (cur == pkt) {
+ UNLINK(cur, pkt->owner->packets, prev);
+ sip_pvt_unlock(pkt->owner);
+ if (pkt->owner)
+ pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
+ if (pkt->data)
+ ast_free(pkt->data);
+ pkt->data = NULL;
+ ast_free(pkt);
+ return 0;
+ }
+ }
+ /* error case */
+ ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n");
+ sip_pvt_unlock(pkt->owner);
return 0;
}
-/*! \brief Deliver SIP call ID for the call */
-static const char *sip_get_callid(struct ast_channel *chan)
-{
- return chan->tech_pvt ? ((struct sip_pvt *) chan->tech_pvt)->callid : "";
-}
-
-/*! \brief Send SIP MESSAGE text within a call
- Called from PBX core sendtext() application */
-static int sip_sendtext(struct ast_channel *ast, const char *text)
+/*! \brief Transmit packet with retransmits
+ \return 0 on success, -1 on failure to allocate packet
+*/
+static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, struct ast_str *data, int len, int fatal, int sipmethod)
{
- struct sip_pvt *dialog = ast->tech_pvt;
- int debug = sip_debug_test_pvt(dialog);
+ struct sip_pkt *pkt = NULL;
+ int siptimer_a = DEFAULT_RETRANS;
+ int xmitres = 0;
+ int respid;
- if (!dialog)
- return -1;
- /* NOT ast_strlen_zero, because a zero-length message is specifically
- * allowed by RFC 3428 (See section 10, Examples) */
- if (!text)
- return 0;
- if(!is_method_allowed(&dialog->allowed_methods, SIP_MESSAGE)) {
- ast_debug(2, "Trying to send MESSAGE to device that does not support it.\n");
- return(0);
+ if (sipmethod == SIP_INVITE) {
+ /* Note this is a pending invite */
+ p->pendinginvite = seqno;
}
- if (debug)
- ast_verbose("Sending text %s on %s\n", text, ast->name);
- transmit_message_with_text(dialog, text);
- return 0;
-}
-/*! \brief Update peer object in realtime storage
- If the Asterisk system name is set in asterisk.conf, we will use
- that name and store that in the "regserver" field in the sippeers
- table to facilitate multi-server setups.
-*/
-static void realtime_update_peer(const char *peername, struct sockaddr_in *sin, const char *defaultuser, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms)
-{
- char port[10];
- char ipaddr[INET_ADDRSTRLEN];
- char regseconds[20];
- char *tablename = NULL;
- char str_lastms[20];
-
- const char *sysname = ast_config_AST_SYSTEM_NAME;
- char *syslabel = NULL;
+ /* If the transport is something reliable (TCP or TLS) then don't really send this reliably */
+ /* I removed the code from retrans_pkt that does the same thing so it doesn't get loaded into the scheduler */
+ /*! \todo According to the RFC some packets need to be retransmitted even if its TCP, so this needs to get revisited */
+ if (!(p->socket.type & SIP_TRANSPORT_UDP)) {
+ xmitres = __sip_xmit(p, data, len); /* Send packet */
+ if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */
+ append_history(p, "XmitErr", "%s", fatal ? "(Critical)" : "(Non-critical)");
+ return AST_FAILURE;
+ } else {
+ return AST_SUCCESS;
+ }
+ }
- time_t nowtime = time(NULL) + expirey;
- const char *fc = fullcontact ? "fullcontact" : NULL;
+ if (!(pkt = ast_calloc(1, sizeof(*pkt) + len + 1)))
+ return AST_FAILURE;
+ /* copy data, add a terminator and save length */
+ if (!(pkt->data = ast_str_create(len))) {
+ ast_free(pkt);
+ return AST_FAILURE;
+ }
+ ast_str_set(&pkt->data, 0, "%s%s", data->str, "\0");
+ pkt->packetlen = len;
+ /* copy other parameters from the caller */
+ pkt->method = sipmethod;
+ pkt->seqno = seqno;
+ pkt->is_resp = resp;
+ pkt->is_fatal = fatal;
+ pkt->owner = dialog_ref(p, "__sip_reliable_xmit: setting pkt->owner");
+ pkt->next = p->packets;
+ p->packets = pkt; /* Add it to the queue */
+ if (resp) {
+ /* Parse out the response code */
+ if (sscanf(ast_str_buffer(pkt->data), "SIP/2.0 %30u", &respid) == 1) {
+ pkt->response_code = respid;
+ }
+ }
+ pkt->timer_t1 = p->timer_t1; /* Set SIP timer T1 */
+ pkt->retransid = -1;
+ if (pkt->timer_t1)
+ siptimer_a = pkt->timer_t1 * 2;
- int realtimeregs = ast_check_realtime("sipregs");
+ /* Schedule retransmission */
+ AST_SCHED_REPLACE_VARIABLE(pkt->retransid, sched, siptimer_a, retrans_pkt, pkt, 1);
+ if (sipdebug)
+ ast_debug(4, "*** SIP TIMER: Initializing retransmit timer on packet: Id #%d\n", pkt->retransid);
- tablename = realtimeregs ? "sipregs" : "sippeers";
-
+ xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen); /* Send packet */
- snprintf(str_lastms, sizeof(str_lastms), "%d", lastms);
- snprintf(regseconds, sizeof(regseconds), "%d", (int)nowtime); /* Expiration time */
- ast_copy_string(ipaddr, ast_inet_ntoa(sin->sin_addr), sizeof(ipaddr));
- snprintf(port, sizeof(port), "%d", ntohs(sin->sin_port));
-
- if (ast_strlen_zero(sysname)) /* No system name, disable this */
- sysname = NULL;
- else if (sip_cfg.rtsave_sysname)
- syslabel = "regserver";
-
- if (fc) {
- ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
- "port", port, "regseconds", regseconds,
- deprecated_username ? "username" : "defaultuser", defaultuser,
- "useragent", useragent, "lastms", str_lastms,
- fc, fullcontact, syslabel, sysname, SENTINEL); /* note fc and syslabel _can_ be NULL */
+ if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */
+ append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
+ ast_log(LOG_ERROR, "Serious Network Trouble; __sip_xmit returns error for pkt data\n");
+ AST_SCHED_DEL(sched, pkt->retransid);
+ p->packets = pkt->next;
+ pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
+ ast_free(pkt->data);
+ ast_free(pkt);
+ return AST_FAILURE;
} else {
- ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
- "port", port, "regseconds", regseconds,
- "useragent", useragent, "lastms", str_lastms,
- deprecated_username ? "username" : "defaultuser", defaultuser,
- syslabel, sysname, SENTINEL); /* note syslabel _can_ be NULL */
+ return AST_SUCCESS;
}
}
-/*! \brief Automatically add peer extension to dial plan */
-static void register_peer_exten(struct sip_peer *peer, int onoff)
+/*! \brief Kill a SIP dialog (called only by the scheduler)
+ * The scheduler has a reference to this dialog when p->autokillid != -1,
+ * and we are called using that reference. So if the event is not
+ * rescheduled, we need to call dialog_unref().
+ */
+static int __sip_autodestruct(const void *data)
{
- char multi[256];
- char *stringp, *ext, *context;
- struct pbx_find_info q = { .stacklen = 0 };
+ struct sip_pvt *p = (struct sip_pvt *)data;
- /* XXX note that sip_cfg.regcontext is both a global 'enable' flag and
- * the name of the global regexten context, if not specified
- * individually.
- */
- if (ast_strlen_zero(sip_cfg.regcontext))
- return;
+ /* If this is a subscription, tell the phone that we got a timeout */
+ if (p->subscribed && p->subscribed != MWI_NOTIFICATION && p->subscribed != CALL_COMPLETION) {
+ transmit_state_notify(p, AST_EXTENSION_DEACTIVATED, 1, TRUE); /* Send last notification */
+ p->subscribed = NONE;
+ append_history(p, "Subscribestatus", "timeout");
+ ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", p->callid ? p->callid : "<unknown>");
+ return 10000; /* Reschedule this destruction so that we know that it's gone */
+ }
- ast_copy_string(multi, S_OR(peer->regexten, peer->name), sizeof(multi));
- stringp = multi;
- while ((ext = strsep(&stringp, "&"))) {
- if ((context = strchr(ext, '@'))) {
- *context++ = '\0'; /* split ext@context */
- if (!ast_context_find(context)) {
- ast_log(LOG_WARNING, "Context %s must exist in regcontext= in sip.conf!\n", context);
- continue;
+ /* If there are packets still waiting for delivery, delay the destruction */
+ if (p->packets) {
+ if (!p->needdestroy) {
+ char method_str[31];
+ ast_debug(3, "Re-scheduled destruction of SIP call %s\n", p->callid ? p->callid : "<unknown>");
+ append_history(p, "ReliableXmit", "timeout");
+ if (sscanf(p->lastmsg, "Tx: %30s", method_str) == 1 || sscanf(p->lastmsg, "Rx: %30s", method_str) == 1) {
+ if (method_match(SIP_CANCEL, method_str) || method_match(SIP_BYE, method_str)) {
+ pvt_set_needdestroy(p, "autodestruct");
+ }
}
+ return 10000;
} else {
- context = sip_cfg.regcontext;
- }
- if (onoff) {
- if (!ast_exists_extension(NULL, context, ext, 1, NULL)) {
- ast_add_extension(context, 1, ext, 1, NULL, NULL, "Noop",
- ast_strdup(peer->name), ast_free_ptr, "SIP");
- }
- } else if (pbx_find_extension(NULL, NULL, &q, context, ext, 1, NULL, "", E_MATCH)) {
- ast_context_remove_extension(context, ext, 1, NULL);
+ /* They've had their chance to respond. Time to bail */
+ __sip_pretend_ack(p);
}
}
-}
-
-/*! Destroy mailbox subscriptions */
-static void destroy_mailbox(struct sip_mailbox *mailbox)
-{
- if (mailbox->mailbox)
- ast_free(mailbox->mailbox);
- if (mailbox->context)
- ast_free(mailbox->context);
- if (mailbox->event_sub)
- ast_event_unsubscribe(mailbox->event_sub);
- ast_free(mailbox);
-}
-/*! Destroy all peer-related mailbox subscriptions */
-static void clear_peer_mailboxes(struct sip_peer *peer)
-{
- struct sip_mailbox *mailbox;
+ if (p->subscribed == MWI_NOTIFICATION) {
+ if (p->relatedpeer) {
+ p->relatedpeer = unref_peer(p->relatedpeer, "__sip_autodestruct: unref peer p->relatedpeer"); /* Remove link to peer. If it's realtime, make sure it's gone from memory) */
+ }
+ }
- while ((mailbox = AST_LIST_REMOVE_HEAD(&peer->mailboxes, entry)))
- destroy_mailbox(mailbox);
-}
+ /* Reset schedule ID */
+ p->autokillid = -1;
-static void sip_destroy_peer_fn(void *peer)
-{
- sip_destroy_peer(peer);
+ if (p->owner) {
+ ast_log(LOG_WARNING, "Autodestruct on dialog '%s' with owner in place (Method: %s)\n", p->callid, sip_methods[p->method].text);
+ ast_queue_hangup_with_cause(p->owner, AST_CAUSE_PROTOCOL_ERROR);
+ } else if (p->refer && !p->alreadygone) {
+ ast_debug(3, "Finally hanging up channel after transfer: %s\n", p->callid);
+ transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1);
+ append_history(p, "ReferBYE", "Sending BYE on transferer call leg %s", p->callid);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ } else {
+ append_history(p, "AutoDestroy", "%s", p->callid);
+ ast_debug(3, "Auto destroying SIP dialog '%s'\n", p->callid);
+ dialog_unlink_all(p, TRUE, TRUE); /* once it's unlinked and unrefd everywhere, it'll be freed automagically */
+ /* dialog_unref(p, "unref dialog-- no other matching conditions"); -- unlink all now should finish off the dialog's references and free it. */
+ /* sip_destroy(p); */ /* Go ahead and destroy dialog. All attempts to recover is done */
+ /* sip_destroy also absorbs the reference */
+ }
+ dialog_unref(p, "The ref to a dialog passed to this sched callback is going out of scope; unref it.");
+ return 0;
}
-/*! \brief Destroy peer object from memory */
-static void sip_destroy_peer(struct sip_peer *peer)
+/*! \brief Schedule destruction of SIP dialog */
+void sip_scheddestroy(struct sip_pvt *p, int ms)
{
- ast_debug(3, "Destroying SIP peer %s\n", peer->name);
- if (peer->outboundproxy)
- ao2_ref(peer->outboundproxy, -1);
- peer->outboundproxy = NULL;
-
- /* Delete it, it needs to disappear */
- if (peer->call) {
- dialog_unlink_all(peer->call, TRUE, TRUE);
- peer->call = dialog_unref(peer->call, "peer->call is being unset");
- }
-
-
- if (peer->mwipvt) { /* We have an active subscription, delete it */
- dialog_unlink_all(peer->mwipvt, TRUE, TRUE);
- peer->mwipvt = dialog_unref(peer->mwipvt, "unreffing peer->mwipvt");
- }
-
- if (peer->chanvars) {
- ast_variables_destroy(peer->chanvars);
- peer->chanvars = NULL;
+ if (ms < 0) {
+ if (p->timer_t1 == 0) {
+ p->timer_t1 = global_t1; /* Set timer T1 if not set (RFC 3261) */
+ }
+ if (p->timer_b == 0) {
+ p->timer_b = global_timer_b; /* Set timer B if not set (RFC 3261) */
+ }
+ ms = p->timer_t1 * 64;
}
-
- register_peer_exten(peer, FALSE);
- ast_free_ha(peer->ha);
- if (peer->selfdestruct)
- ast_atomic_fetchadd_int(&apeerobjs, -1);
- else if (peer->is_realtime) {
- ast_atomic_fetchadd_int(&rpeerobjs, -1);
- ast_debug(3, "-REALTIME- peer Destroyed. Name: %s. Realtime Peer objects: %d\n", peer->name, rpeerobjs);
- } else
- ast_atomic_fetchadd_int(&speerobjs, -1);
- clear_realm_authentication(peer->auth);
- peer->auth = NULL;
- if (peer->dnsmgr)
- ast_dnsmgr_release(peer->dnsmgr);
- clear_peer_mailboxes(peer);
+ if (sip_debug_test_pvt(p))
+ ast_verbose("Scheduling destruction of SIP dialog '%s' in %d ms (Method: %s)\n", p->callid, ms, sip_methods[p->method].text);
+ if (sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
- if (peer->socket.tcptls_session) {
- ao2_ref(peer->socket.tcptls_session, -1);
- peer->socket.tcptls_session = NULL;
- }
+ if (p->do_history)
+ append_history(p, "SchedDestroy", "%d ms", ms);
+ p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p, "setting ref as passing into ast_sched_add for __sip_autodestruct"));
- ast_string_field_free_memory(peer);
+ if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_schedid > 0)
+ stop_session_timer(p);
}
-/*! \brief Update peer data in database (if used) */
-static void update_peer(struct sip_peer *p, int expire)
+/*! \brief Cancel destruction of SIP dialog.
+ * Be careful as this also absorbs the reference - if you call it
+ * from within the scheduler, this might be the last reference.
+ */
+int sip_cancel_destroy(struct sip_pvt *p)
{
- int rtcachefriends = ast_test_flag(&p->flags[1], SIP_PAGE2_RTCACHEFRIENDS);
- if (sip_cfg.peer_rtupdate &&
- (p->is_realtime || rtcachefriends)) {
- realtime_update_peer(p->name, &p->addr, p->username, rtcachefriends ? p->fullcontact : NULL, p->useragent, expire, p->deprecated_username, p->lastms);
- }
-}
+ int res = 0;
+ if (p->autokillid > -1) {
+ int res3;
-static struct ast_variable *get_insecure_variable_from_config(struct ast_config *cfg)
-{
- struct ast_variable *var = NULL;
- struct ast_flags flags = {0};
- char *cat = NULL;
- const char *insecure;
- while ((cat = ast_category_browse(cfg, cat))) {
- insecure = ast_variable_retrieve(cfg, cat, "insecure");
- set_insecure_flags(&flags, insecure, -1);
- if (ast_test_flag(&flags, SIP_INSECURE_PORT)) {
- var = ast_category_root(cfg, cat);
- break;
+ if (!(res3 = ast_sched_del(sched, p->autokillid))) {
+ append_history(p, "CancelDestroy", "");
+ p->autokillid = -1;
+ dialog_unref(p, "dialog unrefd because autokillid is de-sched'd");
}
}
- return var;
+ return res;
}
-static const char *get_name_from_variable(struct ast_variable *var, const char *newpeername)
+/*! \brief Acknowledges receipt of a packet and stops retransmission
+ * called with p locked*/
+int __sip_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
{
- struct ast_variable *tmp;
- for (tmp = var; tmp; tmp = tmp->next) {
- if (!newpeername && !strcasecmp(tmp->name, "name"))
- newpeername = tmp->value;
+ struct sip_pkt *cur, *prev = NULL;
+ const char *msg = "Not Found"; /* used only for debugging */
+ int res = FALSE;
+
+ /* If we have an outbound proxy for this dialog, then delete it now since
+ the rest of the requests in this dialog needs to follow the routing.
+ If obforcing is set, we will keep the outbound proxy during the whole
+ dialog, regardless of what the SIP rfc says
+ */
+ if (p->outboundproxy && !p->outboundproxy->force){
+ ref_proxy(p, NULL);
}
- return newpeername;
-}
-/*! \brief realtime_peer: Get peer from realtime storage
- * Checks the "sippeers" realtime family from extconfig.conf
- * Checks the "sipregs" realtime family from extconfig.conf if it's configured.
- * This returns a pointer to a peer and because we use build_peer, we can rest
- * assured that the refcount is bumped.
-*/
-static struct sip_peer *realtime_peer(const char *newpeername, struct sockaddr_in *sin, int devstate_only)
-{
- struct sip_peer *peer;
- struct ast_variable *var = NULL;
- struct ast_variable *varregs = NULL;
- struct ast_variable *tmp;
- struct ast_config *peerlist = NULL;
- char ipaddr[INET_ADDRSTRLEN];
- char portstring[6]; /*up to 5 digits plus null terminator*/
- char *cat = NULL;
- unsigned short portnum;
- int realtimeregs = ast_check_realtime("sipregs");
-
- /* First check on peer name */
- if (newpeername) {
- if (realtimeregs)
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
-
- var = ast_load_realtime("sippeers", "name", newpeername, "host", "dynamic", SENTINEL);
- if (!var && sin)
- var = ast_load_realtime("sippeers", "name", newpeername, "host", ast_inet_ntoa(sin->sin_addr), SENTINEL);
- if (!var) {
- var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
- /*!\note
- * If this one loaded something, then we need to ensure that the host
- * field matched. The only reason why we can't have this as a criteria
- * is because we only have the IP address and the host field might be
- * set as a name (and the reverse PTR might not match).
+ for (cur = p->packets; cur; prev = cur, cur = cur->next) {
+ if (cur->seqno != seqno || cur->is_resp != resp)
+ continue;
+ if (cur->is_resp || cur->method == sipmethod) {
+ res = TRUE;
+ msg = "Found";
+ if (!resp && (seqno == p->pendinginvite)) {
+ ast_debug(1, "Acked pending invite %d\n", p->pendinginvite);
+ p->pendinginvite = 0;
+ }
+ if (cur->retransid > -1) {
+ if (sipdebug)
+ ast_debug(4, "** SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", cur->retransid);
+ }
+ /* This odd section is designed to thwart a
+ * race condition in the packet scheduler. There are
+ * two conditions under which deleting the packet from the
+ * scheduler can fail.
+ *
+ * 1. The packet has been removed from the scheduler because retransmission
+ * is being attempted. The problem is that if the packet is currently attempting
+ * retransmission and we are at this point in the code, then that MUST mean
+ * that retrans_pkt is waiting on p's lock. Therefore we will relinquish the
+ * lock temporarily to allow retransmission.
+ *
+ * 2. The packet has reached its maximum number of retransmissions and has
+ * been permanently removed from the packet scheduler. If this is the case, then
+ * the packet's retransid will be set to -1. The atomicity of the setting and checking
+ * of the retransid to -1 is ensured since in both cases p's lock is held.
*/
- if (var && sin) {
- for (tmp = var; tmp; tmp = tmp->next) {
- if (!strcasecmp(tmp->name, "host")) {
- struct hostent *hp;
- struct ast_hostent ahp;
- if (!(hp = ast_gethostbyname(tmp->value, &ahp)) || (memcmp(hp->h_addr, &sin->sin_addr, sizeof(hp->h_addr)))) {
- /* No match */
- ast_variables_destroy(var);
- var = NULL;
- }
- break;
- }
- }
+ while (cur->retransid > -1 && ast_sched_del(sched, cur->retransid)) {
+ sip_pvt_unlock(p);
+ usleep(1);
+ sip_pvt_lock(p);
}
+ UNLINK(cur, p->packets, prev);
+ dialog_unref(cur->owner, "unref pkt cur->owner dialog from sip ack before freeing pkt");
+ if (cur->data)
+ ast_free(cur->data);
+ ast_free(cur);
+ break;
}
}
+ ast_debug(1, "Stopping retransmission on '%s' of %s %d: Match %s\n",
+ p->callid, resp ? "Response" : "Request", seqno, msg);
+ return res;
+}
- if (!var && sin) { /* Then check on IP address for dynamic peers */
- ast_copy_string(ipaddr, ast_inet_ntoa(sin->sin_addr), sizeof(ipaddr));
- portnum = ntohs(sin->sin_port);
- sprintf(portstring, "%u", portnum);
- var = ast_load_realtime("sippeers", "host", ipaddr, "port", portstring, SENTINEL); /* First check for fixed IP hosts */
- if (var) {
- if (realtimeregs) {
- newpeername = get_name_from_variable(var, newpeername);
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
- }
- } else {
- if (realtimeregs)
- varregs = ast_load_realtime("sipregs", "ipaddr", ipaddr, "port", portstring, SENTINEL); /* Then check for registered hosts */
- else
- var = ast_load_realtime("sippeers", "ipaddr", ipaddr, "port", portstring, SENTINEL); /* Then check for registered hosts */
- if (varregs) {
- newpeername = get_name_from_variable(varregs, newpeername);
- var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
- }
- }
- if (!var) { /*We couldn't match on ipaddress and port, so we need to check if port is insecure*/
- peerlist = ast_load_realtime_multientry("sippeers", "host", ipaddr, SENTINEL);
- if (peerlist) {
- var = get_insecure_variable_from_config(peerlist);
- if(var) {
- if (realtimeregs) {
- newpeername = get_name_from_variable(var, newpeername);
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
- }
- } else { /*var wasn't found in the list of "hosts", so try "ipaddr"*/
- peerlist = NULL;
- cat = NULL;
- peerlist = ast_load_realtime_multientry("sippeers", "ipaddr", ipaddr, SENTINEL);
- if(peerlist) {
- var = get_insecure_variable_from_config(peerlist);
- if(var) {
- if (realtimeregs) {
- newpeername = get_name_from_variable(var, newpeername);
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
- }
- }
- }
- }
- } else {
- if (realtimeregs) {
- peerlist = ast_load_realtime_multientry("sipregs", "ipaddr", ipaddr, SENTINEL);
- if (peerlist) {
- varregs = get_insecure_variable_from_config(peerlist);
- if (varregs) {
- newpeername = get_name_from_variable(varregs, newpeername);
- var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
- }
- }
- } else {
- peerlist = ast_load_realtime_multientry("sippeers", "ipaddr", ipaddr, SENTINEL);
- if (peerlist) {
- var = get_insecure_variable_from_config(peerlist);
- if (var) {
- newpeername = get_name_from_variable(var, newpeername);
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
- }
- }
- }
- }
+/*! \brief Pretend to ack all packets
+ * called with p locked */
+void __sip_pretend_ack(struct sip_pvt *p)
+{
+ struct sip_pkt *cur = NULL;
+
+ while (p->packets) {
+ int method;
+ if (cur == p->packets) {
+ ast_log(LOG_WARNING, "Have a packet that doesn't want to give up! %s\n", sip_methods[cur->method].text);
+ return;
}
+ cur = p->packets;
+ method = (cur->method) ? cur->method : find_sip_method(cur->data->str);
+ __sip_ack(p, cur->seqno, cur->is_resp, method);
}
+}
- if (!var) {
- if (peerlist)
- ast_config_destroy(peerlist);
- return NULL;
- }
+/*! \brief Acks receipt of packet, keep it around (used for provisional responses) */
+int __sip_semi_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
+{
+ struct sip_pkt *cur;
+ int res = FALSE;
- for (tmp = var; tmp; tmp = tmp->next) {
- /* If this is type=user, then skip this object. */
- if (!strcasecmp(tmp->name, "type") &&
- !strcasecmp(tmp->value, "user")) {
- if(peerlist)
- ast_config_destroy(peerlist);
- else {
- ast_variables_destroy(var);
- ast_variables_destroy(varregs);
+ for (cur = p->packets; cur; cur = cur->next) {
+ if (cur->seqno == seqno && cur->is_resp == resp &&
+ (cur->is_resp || method_match(sipmethod, cur->data->str))) {
+ /* this is our baby */
+ if (cur->retransid > -1) {
+ if (sipdebug)
+ ast_debug(4, "*** SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", cur->retransid, sip_methods[sipmethod].text);
}
- return NULL;
- } else if (!newpeername && !strcasecmp(tmp->name, "name")) {
- newpeername = tmp->value;
+ AST_SCHED_DEL(sched, cur->retransid);
+ res = TRUE;
+ break;
}
}
-
- if (!newpeername) { /* Did not find peer in realtime */
- ast_log(LOG_WARNING, "Cannot Determine peer name ip=%s\n", ipaddr);
- if(peerlist)
- ast_config_destroy(peerlist);
- else
- ast_variables_destroy(var);
- return NULL;
+ ast_debug(1, "(Provisional) Stopping retransmission (but retaining packet) on '%s' %s %d: %s\n", p->callid, resp ? "Response" : "Request", seqno, res == -1 ? "Not Found" : "Found");
+ return res;
+}
+
+
+/*! \brief Copy SIP request, parse it */
+static void parse_copy(struct sip_request *dst, const struct sip_request *src)
+{
+ copy_request(dst, src);
+ parse_request(dst);
+}
+
+/*! \brief add a blank line if no body */
+static void add_blank(struct sip_request *req)
+{
+ if (!req->lines) {
+ /* Add extra empty return. add_header() reserves 4 bytes so cannot be truncated */
+ ast_str_append(&req->data, 0, "\r\n");
+ req->len = ast_str_strlen(req->data);
}
+}
+
+static int send_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp)
+{
+ const char *msg = NULL;
+ if (!pvt->last_provisional || !strncasecmp(pvt->last_provisional, "100", 3)) {
+ msg = "183 Session Progress";
+ }
- /* Peer found in realtime, now build it in memory */
- peer = build_peer(newpeername, var, varregs, TRUE, devstate_only);
- if (!peer) {
- if(peerlist)
- ast_config_destroy(peerlist);
- else {
- ast_variables_destroy(var);
- ast_variables_destroy(varregs);
+ if (pvt->invitestate < INV_COMPLETED) {
+ if (with_sdp) {
+ transmit_response_with_sdp(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq, XMIT_UNRELIABLE, FALSE, FALSE);
+ } else {
+ transmit_response(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq);
}
- return NULL;
+ return PROVIS_KEEPALIVE_TIMEOUT;
}
- ast_debug(3, "-REALTIME- loading peer from database to memory. Name: %s. Peer objects: %d\n", peer->name, rpeerobjs);
+ return 0;
+}
- if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) && !devstate_only) {
- /* Cache peer */
- ast_copy_flags(&peer->flags[1], &global_flags[1], SIP_PAGE2_RTAUTOCLEAR|SIP_PAGE2_RTCACHEFRIENDS);
- if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTAUTOCLEAR)) {
- AST_SCHED_REPLACE_UNREF(peer->expire, sched, sip_cfg.rtautoclear * 1000, expire_register, peer,
- unref_peer(_data, "remove registration ref"),
- unref_peer(peer, "remove registration ref"),
- ref_peer(peer, "add registration ref"));
- }
- ao2_t_link(peers, peer, "link peer into peers table");
- if (peer->addr.sin_addr.s_addr) {
- ao2_t_link(peers_by_ip, peer, "link peer into peers_by_ip table");
- }
- }
- peer->is_realtime = 1;
- if (peerlist)
- ast_config_destroy(peerlist);
- else {
- ast_variables_destroy(var);
- ast_variables_destroy(varregs);
- }
+static int send_provisional_keepalive(const void *data) {
+ struct sip_pvt *pvt = (struct sip_pvt *) data;
- return peer;
+ return send_provisional_keepalive_full(pvt, 0);
}
-/* Function to assist finding peers by name only */
-static int find_by_name(void *obj, void *arg, void *data, int flags)
-{
- struct sip_peer *search = obj, *match = arg;
- int *which_objects = data;
+static int send_provisional_keepalive_with_sdp(const void *data) {
+ struct sip_pvt *pvt = (void *)data;
- /* Usernames in SIP uri's are case sensitive. Domains are not */
- if (strcmp(search->name, match->name)) {
- return 0;
- }
+ return send_provisional_keepalive_full(pvt, 1);
+}
- switch (*which_objects) {
- case FINDUSERS:
- if (!(search->type & SIP_TYPE_USER)) {
- return 0;
- }
- break;
- case FINDPEERS:
- if (!(search->type & SIP_TYPE_PEER)) {
- return 0;
- }
- break;
- case FINDALLDEVICES:
- break;
- }
+static void update_provisional_keepalive(struct sip_pvt *pvt, int with_sdp)
+{
+ AST_SCHED_DEL_UNREF(sched, pvt->provisional_keepalive_sched_id, dialog_unref(pvt, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
- return CMP_MATCH | CMP_STOP;
+ pvt->provisional_keepalive_sched_id = ast_sched_add(sched, PROVIS_KEEPALIVE_TIMEOUT,
+ with_sdp ? send_provisional_keepalive_with_sdp : send_provisional_keepalive, dialog_ref(pvt, "Increment refcount to pass dialog pointer to sched callback"));
}
-/*!
- * \brief Locate device by name or ip address
- *
- * \param which_objects Define which objects should be matched when doing a lookup
- * by name. Valid options are FINDUSERS, FINDPEERS, or FINDALLDEVICES.
- * Note that this option is not used at all when doing a lookup by IP.
- *
- * This is used on find matching device on name or ip/port.
- * If the device was declared as type=peer, we don't match on peer name on incoming INVITEs.
- *
- * \note Avoid using this function in new functions if there is a way to avoid it,
- * since it might cause a database lookup.
- */
-static struct sip_peer *find_peer(const char *peer, struct sockaddr_in *sin, int realtime, int which_objects, int devstate_only, int transport)
+/*! \brief Transmit response on SIP request*/
+static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
{
- struct sip_peer *p = NULL;
- struct sip_peer tmp_peer;
+ int res;
- if (peer) {
- ast_copy_string(tmp_peer.name, peer, sizeof(tmp_peer.name));
- p = ao2_t_callback_data(peers, OBJ_POINTER, find_by_name, &tmp_peer, &which_objects, "ao2_find in peers table");
- } else if (sin) { /* search by addr? */
- tmp_peer.addr.sin_addr.s_addr = sin->sin_addr.s_addr;
- tmp_peer.addr.sin_port = sin->sin_port;
- tmp_peer.flags[0].flags = 0;
- tmp_peer.transports = transport;
- p = ao2_t_find(peers_by_ip, &tmp_peer, OBJ_POINTER, "ao2_find in peers_by_ip table"); /* WAS: p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, name, sip_addr_hashfunc, 1, sip_addrcmp); */
- if (!p) {
- ast_set_flag(&tmp_peer.flags[0], SIP_INSECURE_PORT);
- p = ao2_t_find(peers_by_ip, &tmp_peer, OBJ_POINTER, "ao2_find in peers_by_ip table 2"); /* WAS: p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, name, sip_addr_hashfunc, 1, sip_addrcmp); */
- if (p) {
- return p;
- }
- }
+ add_blank(req);
+ if (sip_debug_test_pvt(p)) {
+ const struct sockaddr_in *dst = sip_real_dst(p);
+
+ ast_verbose("\n<--- %sTransmitting (%s) to %s:%d --->\n%s\n<------------>\n",
+ reliable ? "Reliably " : "", sip_nat_mode(p),
+ ast_inet_ntoa(dst->sin_addr),
+ ntohs(dst->sin_port), req->data->str);
+ }
+ if (p->do_history) {
+ struct sip_request tmp = { .rlPart1 = 0, };
+ parse_copy(&tmp, req);
+ append_history(p, reliable ? "TxRespRel" : "TxResp", "%s / %s - %s", tmp.data->str, get_header(&tmp, "CSeq"),
+ (tmp.method == SIP_RESPONSE || tmp.method == SIP_UNKNOWN) ? REQ_OFFSET_TO_STR(&tmp, rlPart2) : sip_methods[tmp.method].text);
+ ast_free(tmp.data);
}
- if (!p && (realtime || devstate_only)) {
- p = realtime_peer(peer, sin, devstate_only);
+ /* If we are sending a final response to an INVITE, stop retransmitting provisional responses */
+ if (p->initreq.method == SIP_INVITE && reliable == XMIT_CRITICAL) {
+ AST_SCHED_DEL_UNREF(sched, p->provisional_keepalive_sched_id, dialog_unref(p, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
}
- return p;
+ res = (reliable) ?
+ __sip_reliable_xmit(p, seqno, 1, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
+ __sip_xmit(p, req->data, req->len);
+ ast_free(req->data);
+ req->data = NULL;
+ if (res > 0)
+ return 0;
+ return res;
}
-/*! \brief Set nat mode on the various data sockets */
-static void do_setnat(struct sip_pvt *p)
+/*! \brief Send SIP Request to the other part of the dialogue
+ \return see \ref __sip_xmit
+*/
+static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
{
- const char *mode;
- int natflags;
-
- natflags = ast_test_flag(&p->flags[1], SIP_PAGE2_SYMMETRICRTP);
- mode = natflags ? "On" : "Off";
+ int res;
- if (p->rtp) {
- ast_debug(1, "Setting NAT on RTP to %s\n", mode);
- ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_NAT, natflags);
+ /* If we have an outbound proxy, reset peer address
+ Only do this once.
+ */
+ if (p->outboundproxy) {
+ p->sa = p->outboundproxy->ip;
}
- if (p->vrtp) {
- ast_debug(1, "Setting NAT on VRTP to %s\n", mode);
- ast_rtp_instance_set_prop(p->vrtp, AST_RTP_PROPERTY_NAT, natflags);
+
+ add_blank(req);
+ if (sip_debug_test_pvt(p)) {
+ if (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT))
+ ast_verbose("%sTransmitting (NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->recv.sin_addr), ntohs(p->recv.sin_port), req->data->str);
+ else
+ ast_verbose("%sTransmitting (no NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->sa.sin_addr), ntohs(p->sa.sin_port), req->data->str);
}
- if (p->udptl) {
- ast_debug(1, "Setting NAT on UDPTL to %s\n", mode);
- ast_udptl_setnat(p->udptl, natflags);
+ if (p->do_history) {
+ struct sip_request tmp = { .rlPart1 = 0, };
+ parse_copy(&tmp, req);
+ append_history(p, reliable ? "TxReqRel" : "TxReq", "%s / %s - %s", tmp.data->str, get_header(&tmp, "CSeq"), sip_methods[tmp.method].text);
+ ast_free(tmp.data);
}
- if (p->trtp) {
- ast_debug(1, "Setting NAT on TRTP to %s\n", mode);
- ast_rtp_instance_set_prop(p->trtp, AST_RTP_PROPERTY_NAT, natflags);
+ res = (reliable) ?
+ __sip_reliable_xmit(p, seqno, 0, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
+ __sip_xmit(p, req->data, req->len);
+ if (req->data) {
+ ast_free(req->data);
+ req->data = NULL;
}
+ return res;
}
-/*! \brief Change the T38 state on a SIP dialog */
-static void change_t38_state(struct sip_pvt *p, int state)
+static void enable_dsp_detect(struct sip_pvt *p)
{
- int old = p->t38.state;
- struct ast_channel *chan = p->owner;
- struct ast_control_t38_parameters parameters = { .request_response = 0 };
-
- /* Don't bother changing if we are already in the state wanted */
- if (old == state)
- return;
-
- p->t38.state = state;
- ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, chan ? chan->name : "<none>");
+ int features = 0;
- /* If no channel was provided we can't send off a control frame */
- if (!chan)
+ if (p->dsp) {
return;
-
- /* Given the state requested and old state determine what control frame we want to queue up */
- switch (state) {
- case T38_PEER_REINVITE:
- parameters = p->t38.their_parms;
- parameters.max_ifp = ast_udptl_get_far_max_ifp(p->udptl);
- parameters.request_response = AST_T38_REQUEST_NEGOTIATE;
- ast_udptl_set_tag(p->udptl, "SIP/%s", p->username);
- break;
- case T38_ENABLED:
- parameters = p->t38.their_parms;
- parameters.max_ifp = ast_udptl_get_far_max_ifp(p->udptl);
- parameters.request_response = AST_T38_NEGOTIATED;
- ast_udptl_set_tag(p->udptl, "SIP/%s", p->username);
- break;
- case T38_DISABLED:
- if (old == T38_ENABLED) {
- parameters.request_response = AST_T38_TERMINATED;
- } else if (old == T38_LOCAL_REINVITE) {
- parameters.request_response = AST_T38_REFUSED;
- }
- break;
- case T38_LOCAL_REINVITE:
- /* wait until we get a peer response before responding to local reinvite */
- break;
}
- /* Woot we got a message, create a control frame and send it on! */
- if (parameters.request_response)
- ast_queue_control_data(chan, AST_CONTROL_T38_PARAMETERS, ¶meters, sizeof(parameters));
-}
+ if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
+ (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
+ if (!p->rtp || ast_rtp_instance_dtmf_mode_set(p->rtp, AST_RTP_DTMF_MODE_INBAND)) {
+ features |= DSP_FEATURE_DIGIT_DETECT;
+ }
+ }
-/*! \brief Set the global T38 capabilities on a SIP dialog structure */
-static void set_t38_capabilities(struct sip_pvt *p)
-{
- if (p->udptl) {
- if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL_REDUNDANCY) {
- ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_REDUNDANCY);
- } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL_FEC) {
- ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_FEC);
- } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL) {
- ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_NONE);
- }
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_FAX_DETECT_CNG)) {
+ features |= DSP_FEATURE_FAX_DETECT;
}
-}
-static void copy_socket_data(struct sip_socket *to_sock, const struct sip_socket *from_sock)
-{
- if (to_sock->tcptls_session) {
- ao2_ref(to_sock->tcptls_session, -1);
- to_sock->tcptls_session = NULL;
+ if (!features) {
+ return;
}
- if (from_sock->tcptls_session) {
- ao2_ref(from_sock->tcptls_session, +1);
+ if (!(p->dsp = ast_dsp_new())) {
+ return;
}
- *to_sock = *from_sock;
+ ast_dsp_set_features(p->dsp, features);
+ if (global_relaxdtmf) {
+ ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
+ }
}
-/*! \brief Initialize RTP portion of a dialog
- * \return -1 on failure, 0 on success
- */
-static int dialog_initialize_rtp(struct sip_pvt *dialog)
+static void disable_dsp_detect(struct sip_pvt *p)
{
- if (!sip_methods[dialog->method].need_rtp) {
- return 0;
+ if (p->dsp) {
+ ast_dsp_free(p->dsp);
+ p->dsp = NULL;
}
+}
- if (!(dialog->rtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr, NULL))) {
- return -1;
- }
+/*! \brief Set an option on a SIP dialog */
+static int sip_setoption(struct ast_channel *chan, int option, void *data, int datalen)
+{
+ int res = -1;
+ struct sip_pvt *p = chan->tech_pvt;
- if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_VIDEOSUPPORT) && (dialog->capability & AST_FORMAT_VIDEO_MASK)) {
- if (!(dialog->vrtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr, NULL))) {
- return -1;
- }
- ast_rtp_instance_set_timeout(dialog->vrtp, global_rtptimeout);
- ast_rtp_instance_set_hold_timeout(dialog->vrtp, global_rtpholdtimeout);
+ switch (option) {
+ case AST_OPTION_FORMAT_READ:
+ res = ast_rtp_instance_set_read_format(p->rtp, *(int *) data);
+ break;
+ case AST_OPTION_FORMAT_WRITE:
+ res = ast_rtp_instance_set_write_format(p->rtp, *(int *) data);
+ break;
+ case AST_OPTION_MAKE_COMPATIBLE:
+ res = ast_rtp_instance_make_compatible(chan, p->rtp, (struct ast_channel *) data);
+ break;
+ case AST_OPTION_DIGIT_DETECT:
+ if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
+ (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
+ char *cp = (char *) data;
- ast_rtp_instance_set_prop(dialog->vrtp, AST_RTP_PROPERTY_RTCP, 1);
+ ast_debug(1, "%sabling digit detection on %s\n", *cp ? "En" : "Dis", chan->name);
+ if (*cp) {
+ enable_dsp_detect(p);
+ } else {
+ disable_dsp_detect(p);
+ }
+ res = 0;
+ }
+ break;
+ default:
+ break;
}
- if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_TEXTSUPPORT)) {
- if (!(dialog->trtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr, NULL))) {
+ return res;
+}
+
+/*! \brief Query an option on a SIP dialog */
+static int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen)
+{
+ int res = -1;
+ enum ast_t38_state state = T38_STATE_UNAVAILABLE;
+ struct sip_pvt *p = (struct sip_pvt *) chan->tech_pvt;
+ char *cp;
+
+ switch (option) {
+ case AST_OPTION_T38_STATE:
+ /* Make sure we got an ast_t38_state enum passed in */
+ if (*datalen != sizeof(enum ast_t38_state)) {
+ ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_STATE option. Expected %d, got %d\n", (int)sizeof(enum ast_t38_state), *datalen);
return -1;
}
- ast_rtp_instance_set_timeout(dialog->trtp, global_rtptimeout);
- ast_rtp_instance_set_hold_timeout(dialog->trtp, global_rtpholdtimeout);
- ast_rtp_instance_set_prop(dialog->trtp, AST_RTP_PROPERTY_RTCP, 1);
- }
+ sip_pvt_lock(p);
- ast_rtp_instance_set_timeout(dialog->rtp, global_rtptimeout);
- ast_rtp_instance_set_hold_timeout(dialog->rtp, global_rtpholdtimeout);
+ /* Now if T38 support is enabled we need to look and see what the current state is to get what we want to report back */
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT)) {
+ switch (p->t38.state) {
+ case T38_LOCAL_REINVITE:
+ case T38_PEER_REINVITE:
+ state = T38_STATE_NEGOTIATING;
+ break;
+ case T38_ENABLED:
+ state = T38_STATE_NEGOTIATED;
+ break;
+ default:
+ state = T38_STATE_UNKNOWN;
+ }
+ }
- ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_RTCP, 1);
- ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833);
- ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&dialog->flags[1], SIP_PAGE2_RFC2833_COMPENSATE));
+ sip_pvt_unlock(p);
- ast_rtp_instance_set_qos(dialog->rtp, global_tos_audio, 0, "SIP RTP");
+ *((enum ast_t38_state *) data) = state;
+ res = 0;
- do_setnat(dialog);
+ break;
+ case AST_OPTION_DIGIT_DETECT:
+ cp = (char *) data;
+ *cp = p->dsp ? 1 : 0;
+ ast_debug(1, "Reporting digit detection %sabled on %s\n", *cp ? "en" : "dis", chan->name);
+ break;
+ case AST_OPTION_DEVICE_NAME:
+ if (p && p->outgoing_call) {
+ cp = (char *) data;
+ ast_copy_string(cp, p->dialstring, *datalen);
+ res = 0;
+ }
+ /* We purposely break with a return of -1 in the
+ * implied else case here
+ */
+ break;
+ default:
+ break;
+ }
- return 0;
+ return res;
}
-/*! \brief Create address structure from peer reference.
- * This function copies data from peer to the dialog, so we don't have to look up the peer
- * again from memory or database during the life time of the dialog.
- *
- * \return -1 on error, 0 on success.
- *
+/*! \brief Locate closing quote in a string, skipping escaped quotes.
+ * optionally with a limit on the search.
+ * start must be past the first quote.
*/
-static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer)
+const char *find_closing_quote(const char *start, const char *lim)
{
+ char last_char = '\0';
+ const char *s;
+ for (s = start; *s && s != lim; last_char = *s++) {
+ if (*s == '"' && last_char != '\\')
+ break;
+ }
+ return s;
+}
- /* this checks that the dialog is contacting the peer on a valid
- * transport type based on the peers transport configuration,
- * otherwise, this function bails out */
- if (dialog->socket.type && check_request_transport(peer, dialog))
- return -1;
- copy_socket_data(&dialog->socket, &peer->socket);
+/*! \brief Send message with Access-URL header, if this is an HTML URL only! */
+static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen)
+{
+ struct sip_pvt *p = chan->tech_pvt;
- if ((peer->addr.sin_addr.s_addr || peer->defaddr.sin_addr.s_addr) &&
- (!peer->maxms || ((peer->lastms >= 0) && (peer->lastms <= peer->maxms)))) {
- dialog->sa = (peer->addr.sin_addr.s_addr) ? peer->addr : peer->defaddr;
- dialog->recv = dialog->sa;
- } else
+ if (subclass != AST_HTML_URL)
return -1;
- ast_copy_flags(&dialog->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
- ast_copy_flags(&dialog->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
- dialog->capability = peer->capability;
- dialog->prefs = peer->prefs;
- if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_T38SUPPORT)) {
- if (!dialog->udptl) {
- /* t38pt_udptl was enabled in the peer and not in [general] */
- dialog->udptl = ast_udptl_new_with_bindaddr(sched, io, 0, bindaddr.sin_addr);
- }
- dialog->t38_maxdatagram = peer->t38_maxdatagram;
- set_t38_capabilities(dialog);
- } else if (dialog->udptl) {
- ast_udptl_destroy(dialog->udptl);
- dialog->udptl = NULL;
+ ast_string_field_build(p, url, "<%s>;mode=active", data);
+
+ if (sip_debug_test_pvt(p))
+ ast_debug(1, "Send URL %s, state = %d!\n", data, chan->_state);
+
+ switch (chan->_state) {
+ case AST_STATE_RING:
+ transmit_response(p, "100 Trying", &p->initreq);
+ break;
+ case AST_STATE_RINGING:
+ transmit_response(p, "180 Ringing", &p->initreq);
+ break;
+ case AST_STATE_UP:
+ if (!p->pendinginvite) { /* We are up, and have no outstanding invite */
+ transmit_reinvite_with_sdp(p, FALSE, FALSE);
+ } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
+ ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
+ }
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to send URI when state is %d!\n", chan->_state);
}
- ast_string_field_set(dialog, engine, peer->engine);
+ return 0;
+}
- if (dialog_initialize_rtp(dialog)) {
+/*! \brief Deliver SIP call ID for the call */
+static const char *sip_get_callid(struct ast_channel *chan)
+{
+ return chan->tech_pvt ? ((struct sip_pvt *) chan->tech_pvt)->callid : "";
+}
+
+/*! \brief Send SIP MESSAGE text within a call
+ Called from PBX core sendtext() application */
+static int sip_sendtext(struct ast_channel *ast, const char *text)
+{
+ struct sip_pvt *dialog = ast->tech_pvt;
+ int debug = sip_debug_test_pvt(dialog);
+
+ if (!dialog)
return -1;
+ /* NOT ast_strlen_zero, because a zero-length message is specifically
+ * allowed by RFC 3428 (See section 10, Examples) */
+ if (!text)
+ return 0;
+ if(!is_method_allowed(&dialog->allowed_methods, SIP_MESSAGE)) {
+ ast_debug(2, "Trying to send MESSAGE to device that does not support it.\n");
+ return(0);
}
+ if (debug)
+ ast_verbose("Sending text %s on %s\n", text, ast->name);
+ transmit_message_with_text(dialog, text);
+ return 0;
+}
- if (dialog->rtp) { /* Audio */
- ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833);
- ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&dialog->flags[1], SIP_PAGE2_RFC2833_COMPENSATE));
- ast_rtp_instance_set_timeout(dialog->rtp, peer->rtptimeout);
- ast_rtp_instance_set_hold_timeout(dialog->rtp, peer->rtpholdtimeout);
- /* Set Frame packetization */
- ast_rtp_codecs_packetization_set(ast_rtp_instance_get_codecs(dialog->rtp), dialog->rtp, &dialog->prefs);
- dialog->autoframing = peer->autoframing;
- }
- if (dialog->vrtp) { /* Video */
- ast_rtp_instance_set_timeout(dialog->vrtp, peer->rtptimeout);
- ast_rtp_instance_set_hold_timeout(dialog->vrtp, peer->rtpholdtimeout);
- }
- if (dialog->trtp) { /* Realtime text */
- ast_rtp_instance_set_timeout(dialog->trtp, peer->rtptimeout);
- ast_rtp_instance_set_hold_timeout(dialog->trtp, peer->rtpholdtimeout);
- }
+/*! \brief Update peer object in realtime storage
+ If the Asterisk system name is set in asterisk.conf, we will use
+ that name and store that in the "regserver" field in the sippeers
+ table to facilitate multi-server setups.
+*/
+static void realtime_update_peer(const char *peername, struct sockaddr_in *sin, const char *defaultuser, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms)
+{
+ char port[10];
+ char ipaddr[INET_ADDRSTRLEN];
+ char regseconds[20];
+ char *tablename = NULL;
+ char str_lastms[20];
- ast_string_field_set(dialog, peername, peer->name);
- ast_string_field_set(dialog, authname, peer->username);
- ast_string_field_set(dialog, username, peer->username);
- ast_string_field_set(dialog, peersecret, peer->secret);
- ast_string_field_set(dialog, peermd5secret, peer->md5secret);
- ast_string_field_set(dialog, mohsuggest, peer->mohsuggest);
- ast_string_field_set(dialog, mohinterpret, peer->mohinterpret);
- ast_string_field_set(dialog, tohost, peer->tohost);
- ast_string_field_set(dialog, fullcontact, peer->fullcontact);
- ast_string_field_set(dialog, accountcode, peer->accountcode);
- ast_string_field_set(dialog, context, peer->context);
- ast_string_field_set(dialog, cid_num, peer->cid_num);
- ast_string_field_set(dialog, cid_name, peer->cid_name);
- ast_string_field_set(dialog, mwi_from, peer->mwi_from);
- ast_string_field_set(dialog, parkinglot, peer->parkinglot);
- ast_string_field_set(dialog, engine, peer->engine);
- ref_proxy(dialog, obproxy_get(dialog, peer));
- dialog->callgroup = peer->callgroup;
- dialog->pickupgroup = peer->pickupgroup;
- dialog->allowtransfer = peer->allowtransfer;
- dialog->jointnoncodeccapability = dialog->noncodeccapability;
- dialog->rtptimeout = peer->rtptimeout;
- dialog->peerauth = peer->auth;
- dialog->maxcallbitrate = peer->maxcallbitrate;
- dialog->disallowed_methods = peer->disallowed_methods;
- if (ast_strlen_zero(dialog->tohost))
- ast_string_field_set(dialog, tohost, ast_inet_ntoa(dialog->sa.sin_addr));
- if (!ast_strlen_zero(peer->fromdomain)) {
- ast_string_field_set(dialog, fromdomain, peer->fromdomain);
- if (!dialog->initreq.headers) {
- char *c;
- char *tmpcall = ast_strdupa(dialog->callid);
- /* this sure looks to me like we are going to change the callid on this dialog!! */
- c = strchr(tmpcall, '@');
- if (c) {
- *c = '\0';
- ao2_t_unlink(dialogs, dialog, "About to change the callid -- remove the old name");
- ast_string_field_build(dialog, callid, "%s@%s", tmpcall, peer->fromdomain);
- ao2_t_link(dialogs, dialog, "New dialog callid -- inserted back into table");
- }
- }
- }
- if (!ast_strlen_zero(peer->fromuser))
- ast_string_field_set(dialog, fromuser, peer->fromuser);
- if (!ast_strlen_zero(peer->language))
- ast_string_field_set(dialog, language, peer->language);
- /* Set timer T1 to RTT for this peer (if known by qualify=) */
- /* Minimum is settable or default to 100 ms */
- /* If there is a maxms and lastms from a qualify use that over a manual T1
- value. Otherwise, use the peer's T1 value. */
- if (peer->maxms && peer->lastms)
- dialog->timer_t1 = peer->lastms < global_t1min ? global_t1min : peer->lastms;
- else
- dialog->timer_t1 = peer->timer_t1;
+ const char *sysname = ast_config_AST_SYSTEM_NAME;
+ char *syslabel = NULL;
- /* Set timer B to control transaction timeouts, the peer setting is the default and overrides
- the known timer */
- if (peer->timer_b)
- dialog->timer_b = peer->timer_b;
- else
- dialog->timer_b = 64 * dialog->timer_t1;
+ time_t nowtime = time(NULL) + expirey;
+ const char *fc = fullcontact ? "fullcontact" : NULL;
- if ((ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) ||
- (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_AUTO))
- dialog->noncodeccapability |= AST_RTP_DTMF;
- else
- dialog->noncodeccapability &= ~AST_RTP_DTMF;
- if (peer->call_limit)
- ast_set_flag(&dialog->flags[0], SIP_CALL_LIMIT);
- if (!dialog->portinuri)
- dialog->portinuri = peer->portinuri;
+ int realtimeregs = ast_check_realtime("sipregs");
+
+ tablename = realtimeregs ? "sipregs" : "sippeers";
- dialog->chanvars = copy_vars(peer->chanvars);
- return 0;
+ snprintf(str_lastms, sizeof(str_lastms), "%d", lastms);
+ snprintf(regseconds, sizeof(regseconds), "%d", (int)nowtime); /* Expiration time */
+ ast_copy_string(ipaddr, ast_inet_ntoa(sin->sin_addr), sizeof(ipaddr));
+ snprintf(port, sizeof(port), "%d", ntohs(sin->sin_port));
+
+ if (ast_strlen_zero(sysname)) /* No system name, disable this */
+ sysname = NULL;
+ else if (sip_cfg.rtsave_sysname)
+ syslabel = "regserver";
+
+ if (fc) {
+ ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
+ "port", port, "regseconds", regseconds,
+ deprecated_username ? "username" : "defaultuser", defaultuser,
+ "useragent", useragent, "lastms", str_lastms,
+ fc, fullcontact, syslabel, sysname, SENTINEL); /* note fc and syslabel _can_ be NULL */
+ } else {
+ ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
+ "port", port, "regseconds", regseconds,
+ "useragent", useragent, "lastms", str_lastms,
+ deprecated_username ? "username" : "defaultuser", defaultuser,
+ syslabel, sysname, SENTINEL); /* note syslabel _can_ be NULL */
+ }
}
-/*! \brief create address structure from device name
- * Or, if peer not found, find it in the global DNS
- * returns TRUE (-1) on failure, FALSE on success */
-static int create_addr(struct sip_pvt *dialog, const char *opeer, struct sockaddr_in *sin, int newdialog, struct sockaddr_in *remote_address)
+/*! \brief Automatically add peer extension to dial plan */
+static void register_peer_exten(struct sip_peer *peer, int onoff)
{
- &nbs