freeswitch/libs/sofia-sip/libsofia-sip-ua/nta/nta.c

11874 lines
322 KiB
C

/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2005 Nokia Corporation.
*
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
/**@CFILE nta.c
* @brief Sofia SIP Transaction API implementation
*
* This source file has been divided into sections as follows:
* 1) agent
* 2) tport handling
* 3) dispatching messages received from network
* 4) message creation and message utility functions
* 5) stateless operation
* 6) dialogs (legs)
* 7) server transactions (incoming)
* 8) client transactions (outgoing)
* 9) resolving URLs for client transactions
* 10) 100rel reliable responses (reliable)
* 11) SigComp handling and public transport interface
*
* @author Pekka Pessi <Pekka.Pessi@nokia.com>
*
* @date Created: Tue Jun 13 02:57:51 2000 ppessi
*
* @sa
* @RFC3261, @RFC4320
*/
#include "config.h"
#include <sofia-sip/su_string.h>
/** @internal SU message argument structure type */
#define SU_MSG_ARG_T union sm_arg_u
/** @internal SU timer argument pointer type */
#define SU_TIMER_ARG_T struct nta_agent_s
#include <sofia-sip/su_alloc.h>
#include <sofia-sip/su.h>
#include <sofia-sip/su_time.h>
#include <sofia-sip/su_wait.h>
#include <sofia-sip/su_tagarg.h>
#include <sofia-sip/base64.h>
#include <sofia-sip/su_uniqueid.h>
#include <sofia-sip/sip.h>
#include <sofia-sip/sip_header.h>
#include <sofia-sip/sip_util.h>
#include <sofia-sip/sip_status.h>
#include <sofia-sip/hostdomain.h>
#include <sofia-sip/url_tag.h>
#include <sofia-sip/msg_addr.h>
#include <sofia-sip/msg_parser.h>
#include <sofia-sip/htable.h>
/* Resolver context type */
#define SRES_CONTEXT_T nta_outgoing_t
/* We are customer of tport_t */
#define TP_AGENT_T nta_agent_t
#define TP_MAGIC_T sip_via_t
#define TP_CLIENT_T nta_outgoing_t
#include "nta_internal.h"
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <limits.h>
#include <errno.h>
/* From AM_INIT/AC_INIT in our "config.h" */
char const nta_version[] = PACKAGE_VERSION;
#if HAVE_FUNC
#elif HAVE_FUNCTION
#define __func__ __FUNCTION__
#else
static char const __func__[] = "nta";
#endif
#ifndef _MSC_VER
#define NONE ((void *)-1)
#else
#define NONE ((void *)(INT_PTR)-1)
#endif
/* ------------------------------------------------------------------------- */
/** Resolving order */
enum nta_res_order_e
{
nta_res_ip6_ip4,
nta_res_ip4_ip6,
nta_res_ip6_only,
nta_res_ip4_only
};
HTABLE_DECLARE_WITH(leg_htable, lht, nta_leg_t, size_t, hash_value_t);
HTABLE_DECLARE_WITH(outgoing_htable, oht, nta_outgoing_t, size_t, hash_value_t);
HTABLE_DECLARE_WITH(incoming_htable, iht, nta_incoming_t, size_t, hash_value_t);
typedef struct outgoing_queue_t {
nta_outgoing_t **q_tail;
nta_outgoing_t *q_head;
size_t q_length;
unsigned q_timeout;
} outgoing_queue_t;
typedef struct incoming_queue_t {
nta_incoming_t **q_tail;
nta_incoming_t *q_head;
size_t q_length;
unsigned q_timeout;
} incoming_queue_t;
struct nta_agent_s
{
su_home_t sa_home[1];
su_root_t *sa_root;
su_timer_t *sa_timer;
nta_agent_magic_t *sa_magic;
nta_message_f *sa_callback;
nta_update_magic_t *sa_update_magic;
nta_update_tport_f *sa_update_tport;
nta_error_magic_t *sa_error_magic;
nta_error_tport_f *sa_error_tport;
uint32_t sa_next; /**< Timestamp for next agent_timer. */
msg_mclass_t const *sa_mclass;
uint32_t sa_flags; /**< SIP message flags */
unsigned sa_preload; /**< Memory preload for SIP messages. */
tport_t *sa_tports;
sip_contact_t *sa_contact;
sip_via_t *sa_vias; /**< @Via headers for all transports */
sip_via_t *sa_public_vias; /**< @Vias for public transports */
sip_contact_t *sa_aliases;/**< List of aliases for agent */
uint64_t sa_branch; /**< Generator for branch parameters */
uint64_t sa_tags; /**< Generator for tag parameters */
#if HAVE_SOFIA_SRESOLV
sres_resolver_t *sa_resolver; /**< DNS resolver */
enum nta_res_order_e sa_res_order; /** Resolving order (AAAA/A) */
#endif
url_t *sa_default_proxy; /**< Default outbound proxy */
unsigned sa_bad_req_mask; /**< Request error mask */
unsigned sa_bad_resp_mask; /**< Response error mask */
usize_t sa_maxsize; /**< Maximum size of incoming messages */
usize_t sa_max_proceeding; /**< Maximum size of proceeding queue */
unsigned sa_udp_mtu; /**< Maximum size of outgoing UDP requests */
unsigned sa_t1; /**< SIP T1 - initial retransmit interval (500 ms) */
unsigned sa_t2; /**< SIP T2 - maximum retransmit interval (4000 ms) */
unsigned sa_t4; /**< SIP T4 - clear message time (5000 ms) */
unsigned sa_t1x64; /**< SIP T1X64 - transaction lifetime (32 s) */
unsigned sa_progress; /**< Progress timer.
Interval between retransmitting
provisional responses. */
unsigned sa_timer_c; /**< SIP timer C.
Maximum interval between receiving
provisional responses. */
unsigned sa_graylist; /**< Graylisting period */
unsigned sa_blacklist; /**< Blacklisting period */
unsigned sa_drop_prob : 10; /**< NTA is used to test packet drop */
unsigned sa_is_a_uas : 1; /**< NTA is acting as an User Agent server */
unsigned sa_is_stateless : 1; /**< Process requests statelessly
* unless they match existing dialog.
*/
unsigned sa_user_via:1; /**< Let application provide @Via headers */
unsigned sa_extra_100:1; /**< Allow NTA to return "100 Trying" response
* even if application has not responded.
*/
unsigned sa_pass_100:1; /**< Pass the "100 Trying"
* provisional responses to the application
*/
unsigned sa_timeout_408:1; /**< A "408 Request Timeout" message
* is generated when outgoing request expires.
*/
unsigned sa_pass_408:1; /**< A "408 Request Timeout" responses
* are passed to client.
*/
unsigned sa_merge_482 : 1; /**< A "482 Request Merged" response is returned
* to merged requests.
*/
unsigned sa_cancel_2543 : 1; /**< Send a CANCEL to an INVITE without
* waiting for an provisional response.
*/
unsigned sa_cancel_487 : 1; /**< Return 487 response automatically when
* a CANCEL is received.
*/
unsigned sa_invite_100rel:1; /**< Include 100rel in INVITE requests. */
unsigned sa_timestamp : 1; /**< Insert @Timestamp in requests. */
unsigned sa_tport_ip4 : 1; /**< Transports support IPv4. */
unsigned sa_tport_ip6 : 1; /**< Transports support IPv6. */
unsigned sa_tport_udp : 1; /**< Transports support UDP. */
unsigned sa_tport_tcp : 1; /**< Transports support TCP. */
unsigned sa_tport_sctp : 1; /**< Transports support SCTP. */
unsigned sa_tport_tls : 1; /**< Transports support TLS. */
unsigned sa_tport_ws : 1; /**< Transports support WS. */
unsigned sa_tport_wss : 1; /**< Transports support WSS. */
unsigned sa_use_naptr : 1; /**< Use NAPTR lookup */
unsigned sa_use_srv : 1; /**< Use SRV lookup */
unsigned sa_srv_503 : 1; /**< SRV: choice another destination on 503 RFC 3263 */
unsigned sa_tport_threadpool:1; /**< Transports use threadpool */
unsigned sa_rport:1; /**< Use rport at client */
unsigned sa_server_rport:2; /**< Use rport at server */
unsigned sa_tcp_rport:1; /**< Use rport with tcp, too */
unsigned sa_tls_rport:1; /**< Use rport with tls, too */
unsigned sa_auto_comp:1; /**< Automatically create compartments */
unsigned sa_in_timer:1; /**< Set when executing timers */
unsigned sa_use_timer_c:1; /**< Application has set value for timer C */
unsigned :0;
#if HAVE_SMIME
sm_object_t *sa_smime;
#else
void *sa_smime;
#endif
/** @MaxForwards */
sip_max_forwards_t sa_max_forwards[1];
/** Name of SigComp algorithm */
char const *sa_algorithm;
/** Options for SigComp. */
char const *sa_sigcomp_options;
char const* const *sa_sigcomp_option_list;
char const *sa_sigcomp_option_free;
nta_compressor_t *sa_compressor;
/* Statistics */
struct {
usize_t as_recv_msg;
usize_t as_recv_request;
usize_t as_recv_response;
usize_t as_bad_message;
usize_t as_bad_request;
usize_t as_bad_response;
usize_t as_drop_request;
usize_t as_drop_response;
usize_t as_client_tr;
usize_t as_server_tr;
usize_t as_dialog_tr;
usize_t as_acked_tr;
usize_t as_canceled_tr;
usize_t as_trless_request;
usize_t as_trless_to_tr;
usize_t as_trless_response;
usize_t as_trless_200;
usize_t as_merged_request;
usize_t as_sent_msg;
usize_t as_sent_request;
usize_t as_sent_response;
usize_t as_retry_request;
usize_t as_retry_response;
usize_t as_recv_retry;
usize_t as_tout_request;
usize_t as_tout_response;
} sa_stats[1];
/** Hash of dialogs. */
leg_htable_t sa_dialogs[1];
/** Default leg */
nta_leg_t *sa_default_leg;
/** Hash of legs without dialogs. */
leg_htable_t sa_defaults[1];
/** Hash table for outgoing transactions */
outgoing_htable_t sa_outgoing[1];
nta_outgoing_t *sa_default_outgoing;
/** Hash table for incoming transactions */
incoming_htable_t sa_incoming[1];
nta_incoming_t *sa_default_incoming;
/* Queues (states) for outgoing client transactions */
struct {
/** Queue for retrying client transactions */
nta_outgoing_t *re_list;
nta_outgoing_t **re_t1; /**< Special place for T1 timer */
size_t re_length; /**< Length of sa_out.re_list */
outgoing_queue_t delayed[1];
outgoing_queue_t resolving[1];
outgoing_queue_t trying[1]; /* Timer F / Timer E */
outgoing_queue_t completed[1]; /* Timer K */
outgoing_queue_t terminated[1];
/* Special queues (states) for outgoing INVITE transactions */
outgoing_queue_t inv_calling[1]; /* Timer B/A */
outgoing_queue_t inv_proceeding[1]; /* Timer C */
outgoing_queue_t inv_completed[1]; /* Timer D */
/* Temporary queue for transactions waiting to be freed */
outgoing_queue_t *free;
} sa_out;
/* Queues (states) for incoming server transactions */
struct {
/** Queue for retransmitting response of server transactions */
nta_incoming_t *re_list;
nta_incoming_t **re_t1; /**< Special place for T1 timer */
size_t re_length; /**< Length of sa_in.re_list */
incoming_queue_t proceeding[1]; /**< Request received */
incoming_queue_t preliminary[1]; /**< 100rel sent */
incoming_queue_t completed[1]; /**< Final answer sent (non-invite). */
incoming_queue_t inv_completed[1]; /**< Final answer sent (INVITE). */
incoming_queue_t inv_confirmed[1]; /**< Final answer sent, ACK recvd. */
incoming_queue_t terminated[1]; /**< Terminated, ready to free. */
incoming_queue_t final_failed[1];
} sa_in;
/* Special task for freeing memory */
su_clone_r sa_terminator;
};
struct nta_leg_s
{
su_home_t leg_home[1];
hash_value_t leg_hash;
unsigned leg_dialog : 1;
unsigned leg_stateless : 1; /**< Process requests statelessly */
#ifdef NTA_STRICT_ROUTING
unsigned leg_contact_set : 1;
#else
unsigned leg_loose_route : 1; /**< Topmost route in set is LR */
#endif
unsigned leg_route_set : 1; /**< Route set has been saved */
unsigned leg_local_is_to : 1; /**< Backwards-compatibility. */
unsigned leg_tagged : 1; /**< Tagged after creation.
*
* Request missing @To tag matches
* a tagged leg even after tagging.
*/
unsigned:0;
nta_request_f *leg_callback;
nta_leg_magic_t *leg_magic;
nta_agent_t *leg_agent;
url_t const *leg_url; /**< Match incoming requests. */
char const *leg_method; /**< Match incoming requests. */
uint32_t leg_seq; /**< Sequence number for next transaction */
uint32_t leg_rseq; /**< Remote sequence number */
sip_call_id_t *leg_id; /**< Call ID */
sip_from_t *leg_remote; /**< Remote address (@To/@From) */
sip_to_t *leg_local; /**< Local address (@From/@To) */
sip_route_t *leg_route; /**< @Route for outgoing requests. */
sip_contact_t *leg_target; /**< Remote destination (from @Contact). */
};
struct nta_incoming_s
{
su_home_t *irq_home;
hash_value_t irq_hash;
nta_agent_t *irq_agent;
nta_ack_cancel_f *irq_callback;
nta_incoming_magic_t *irq_magic;
/* Timeout/state queue */
nta_incoming_t **irq_prev;
nta_incoming_t *irq_next;
incoming_queue_t *irq_queue;
/* Retry queue */
nta_incoming_t **irq_rprev;
nta_incoming_t *irq_rnext;
sip_method_t irq_method;
sip_request_t *irq_rq;
sip_from_t *irq_from;
sip_to_t *irq_to;
char const *irq_tag;
sip_cseq_t *irq_cseq;
sip_call_id_t *irq_call_id;
sip_via_t *irq_via;
sip_record_route_t *irq_record_route;
char const *irq_branch;
uint32_t irq_rseq;
sip_timestamp_t *irq_timestamp;
su_time_t irq_received;
uint32_t irq_timeout; /**< Timer H, I, J */
uint32_t irq_retry; /**< Timer G */
unsigned short irq_interval; /**< Next timer */
short irq_status;
unsigned irq_retries:8;
unsigned irq_default:1; /**< Default transaction */
unsigned irq_canceled:1; /**< Transaction is canceled */
unsigned irq_completed:1; /**< Transaction is completed */
unsigned irq_confirmed:1; /**< Response has been acked */
unsigned irq_terminated:1; /**< Transaction is terminated */
unsigned irq_final_failed:1; /**< Sending final response failed */
unsigned irq_destroyed :1; /**< Transaction is destroyed */
unsigned irq_in_callback:1; /**< Callback is being invoked */
unsigned irq_reliable_tp:1; /**< Transport is reliable */
unsigned irq_sigcomp_zap:1; /**< Reset SigComp */
unsigned irq_must_100rel:1; /**< 100rel is required */
unsigned irq_extra_100:1; /**< 100 Trying should be sent */
unsigned irq_tag_set:1; /**< Tag is not from request */
unsigned :0;
tp_name_t irq_tpn[1];
tport_t *irq_tport;
struct sigcomp_compartment *irq_cc;
msg_t *irq_request;
msg_t *irq_request2; /**< ACK/CANCEL */
msg_t *irq_response;
nta_reliable_t *irq_reliable; /**< List of reliable responses */
};
struct nta_reliable_s
{
nta_reliable_t *rel_next;
nta_incoming_t *rel_irq;
nta_prack_f *rel_callback;
nta_reliable_magic_t *rel_magic;
uint32_t rel_rseq;
unsigned short rel_status;
unsigned rel_pracked:1;
unsigned rel_precious:1;
msg_t *rel_response;
msg_t *rel_unsent;
};
typedef struct sipdns_resolver sipdns_resolver_t;
struct nta_outgoing_s
{
hash_value_t orq_hash; /**< Hash value */
nta_agent_t *orq_agent;
nta_response_f *orq_callback;
nta_outgoing_magic_t *orq_magic;
/* Timeout/state queue */
nta_outgoing_t **orq_prev;
nta_outgoing_t *orq_next;
outgoing_queue_t *orq_queue;
/* Retry queue */
nta_outgoing_t **orq_rprev;
nta_outgoing_t *orq_rnext;
sip_method_t orq_method;
char const *orq_method_name;
url_t const *orq_url; /**< Original RequestURI */
sip_from_t const *orq_from;
sip_to_t const *orq_to;
char const *orq_tag; /**< Tag from final response. */
sip_cseq_t const *orq_cseq;
sip_call_id_t const *orq_call_id;
msg_t *orq_request;
msg_t *orq_response;
su_time_t orq_sent; /**< When request was sent? */
unsigned orq_delay; /**< RTT estimate */
uint32_t orq_retry; /**< Timer A, E */
uint32_t orq_timeout; /**< Timer B, D, F, K */
unsigned short orq_interval; /**< Next timer A/E */
unsigned short orq_status;
unsigned char orq_retries; /**< Number of tries this far */
unsigned orq_default:1; /**< This is default transaction */
unsigned orq_inserted:1;
unsigned orq_resolved:1;
unsigned orq_via_added:1;
unsigned orq_prepared:1;
unsigned orq_canceled:1;
unsigned orq_terminated:1;
unsigned orq_destroyed:1;
unsigned orq_completed:1;
unsigned orq_delayed:1;
unsigned orq_user_tport:1; /**< Application provided tport - don't retry */
unsigned orq_try_tcp_instead:1;
unsigned orq_try_udp_instead:1;
unsigned orq_reliable:1; /**< Transport is reliable */
unsigned orq_forked:1; /**< Tagged fork */
/* Attributes */
unsigned orq_sips:1;
unsigned orq_uas:1; /**< Running this transaction as UAS */
unsigned orq_user_via:1;
unsigned orq_stateless:1;
unsigned orq_pass_100:1;
unsigned orq_sigcomp_new:1; /**< Create compartment if needed */
unsigned orq_sigcomp_zap:1; /**< Reset SigComp after completing */
unsigned orq_must_100rel:1;
unsigned orq_timestamp:1; /**< Insert @Timestamp header. */
unsigned orq_100rel:1; /**< Support 100rel */
unsigned:0; /* pad */
#if HAVE_SOFIA_SRESOLV
sipdns_resolver_t *orq_resolver;
#endif
url_t *orq_route; /**< Route URL */
tp_name_t orq_tpn[1]; /**< Where to send request */
tport_t *orq_tport;
struct sigcomp_compartment *orq_cc;
tagi_t *orq_tags; /**< Tport tag items */
char const *orq_branch; /**< Transaction branch */
char const *orq_via_branch; /**< @Via branch */
int *orq_status2b; /**< Delayed response */
nta_outgoing_t *orq_cancel; /**< Delayed CANCEL transaction */
nta_outgoing_t *orq_forking; /**< Untagged transaction */
nta_outgoing_t *orq_forks; /**< Tagged transactions */
uint32_t orq_rseq; /**< Latest incoming rseq */
int orq_pending; /**< Request is pending in tport */
};
/* ------------------------------------------------------------------------- */
/* Internal tags */
/* Delay sending of request */
#define NTATAG_DELAY_SENDING(x) ntatag_delay_sending, tag_bool_v((x))
#define NTATAG_DELAY_SENDING_REF(x) \
ntatag_delay_sending_ref, tag_bool_vr(&(x))
extern tag_typedef_t ntatag_delay_sending;
extern tag_typedef_t ntatag_delay_sending_ref;
/* Allow sending incomplete responses */
#define NTATAG_INCOMPLETE(x) ntatag_incomplete, tag_bool_v((x))
#define NTATAG_INCOMPLETE_REF(x) \
ntatag_incomplete_ref, tag_bool_vr(&(x))
extern tag_typedef_t ntatag_incomplete;
extern tag_typedef_t ntatag_incomplete_ref;
nta_compressor_vtable_t *nta_compressor_vtable = NULL;
/* Agent */
static int agent_tag_init(nta_agent_t *self);
static int agent_timer_init(nta_agent_t *agent);
static void agent_timer(su_root_magic_t *rm, su_timer_t *, nta_agent_t *);
static int agent_launch_terminator(nta_agent_t *agent);
static void agent_kill_terminator(nta_agent_t *agent);
static int agent_set_params(nta_agent_t *agent, tagi_t *tags);
static void agent_set_udp_params(nta_agent_t *self, usize_t udp_mtu);
static int agent_get_params(nta_agent_t *agent, tagi_t *tags);
/* Transport interface */
static sip_via_t const *agent_tport_via(tport_t *tport);
static int outgoing_insert_via(nta_outgoing_t *orq, sip_via_t const *);
static int nta_tpn_by_via(tp_name_t *tpn, sip_via_t const *v, int *using_rport);
static msg_t *nta_msg_create_for_transport(nta_agent_t *agent, int flags,
char const data[], usize_t dlen,
tport_t const *tport,
tp_client_t *via);
static int complete_response(msg_t *response,
int status, char const *phrase,
msg_t *request);
static int mreply(nta_agent_t *agent,
msg_t *reply,
int status, char const *phrase,
msg_t *req_msg,
tport_t *tport,
int incomplete,
int sdwn_after,
char const *to_tag,
tag_type_t tag, tag_value_t value, ...);
#define IF_SIGCOMP_TPTAG_COMPARTMENT(cc) TAG_IF(cc && cc != NONE, TPTAG_COMPARTMENT(cc)),
#define IF_SIGCOMP_TPTAG_COMPARTMENT_REF(cc) TPTAG_COMPARTMENT_REF(cc),
struct sigcomp_compartment;
struct sigcomp_compartment *
nta_compartment_ref(struct sigcomp_compartment *cc);
static
struct sigcomp_compartment *
agent_compression_compartment(nta_agent_t *sa, tport_t *tp, tp_name_t const *tpn,
int new_if_needed);
static
int agent_accept_compressed(nta_agent_t *sa, msg_t *msg,
struct sigcomp_compartment *cc);
static int agent_close_compressor(nta_agent_t *sa,
struct sigcomp_compartment *cc);
static int agent_zap_compressor(nta_agent_t *sa,
struct sigcomp_compartment *cc);
static char const * stateful_branch(su_home_t *home, nta_agent_t *);
static char const * stateless_branch(nta_agent_t *, msg_t *, sip_t const *,
tp_name_t const *tp);
#define NTA_BRANCH_PRIME SU_U64_C(0xB9591D1C361C6521)
#define NTA_TAG_PRIME SU_U64_C(0xB9591D1C361C6521)
#ifndef UINT32_MAX
#define UINT32_MAX (0xffffffffU)
#endif
HTABLE_PROTOS_WITH(leg_htable, lht, nta_leg_t, size_t, hash_value_t);
static nta_leg_t *leg_find(nta_agent_t const *sa,
char const *method_name,
url_t const *request_uri,
sip_call_id_t const *i,
char const *from_tag,
char const *to_tag);
static nta_leg_t *dst_find(nta_agent_t const *sa, url_t const *u0,
char const *method);
static void leg_recv(nta_leg_t *, msg_t *, sip_t *, tport_t *);
static void leg_free(nta_agent_t *sa, nta_leg_t *leg);
#define NTA_HASH(i, cs) ((i)->i_hash + 26839U * (uint32_t)(cs))
HTABLE_PROTOS_WITH(incoming_htable, iht, nta_incoming_t, size_t, hash_value_t);
static nta_incoming_t *incoming_create(nta_agent_t *agent,
msg_t *request,
sip_t *sip,
tport_t *tport,
char const *tag);
static int incoming_callback(nta_leg_t *leg, nta_incoming_t *irq, sip_t *sip);
static void incoming_free(nta_incoming_t *irq);
su_inline void incoming_cut_off(nta_incoming_t *irq);
su_inline void incoming_reclaim(nta_incoming_t *irq);
static void incoming_queue_init(incoming_queue_t *,
unsigned timeout);
static void incoming_queue_adjust(nta_agent_t *sa,
incoming_queue_t *queue,
unsigned timeout);
static nta_incoming_t *incoming_find(nta_agent_t const *agent,
sip_t const *sip,
sip_via_t const *v,
nta_incoming_t **merge,
nta_incoming_t **ack,
nta_incoming_t **cancel);
static int incoming_reply(nta_incoming_t *irq, msg_t *msg, sip_t *sip);
su_inline int incoming_recv(nta_incoming_t *irq, msg_t *msg, sip_t *sip,
tport_t *tport);
su_inline int incoming_ack(nta_incoming_t *irq, msg_t *msg, sip_t *sip,
tport_t *tport);
su_inline int incoming_cancel(nta_incoming_t *irq, msg_t *msg, sip_t *sip,
tport_t *tport);
static void request_merge(nta_agent_t *,
msg_t *msg, sip_t *sip, tport_t *tport,
char const *to_tag);
su_inline int incoming_timestamp(nta_incoming_t *, msg_t *, sip_t *);
static void _nta_incoming_timer(nta_agent_t *);
static nta_reliable_t *reliable_mreply(nta_incoming_t *,
nta_prack_f *, nta_reliable_magic_t *,
msg_t *, sip_t *);
static int reliable_send(nta_incoming_t *, nta_reliable_t *, msg_t *, sip_t *);
static int reliable_final(nta_incoming_t *irq, msg_t *msg, sip_t *sip);
static msg_t *reliable_response(nta_incoming_t *irq);
static nta_reliable_t *reliable_find(nta_agent_t const *, sip_t const *);
static int reliable_recv(nta_reliable_t *rel, msg_t *, sip_t *, tport_t *);
static void reliable_flush(nta_incoming_t *irq);
static void reliable_timeout(nta_incoming_t *irq, int timeout);
HTABLE_PROTOS_WITH(outgoing_htable, oht, nta_outgoing_t, size_t, hash_value_t);
static nta_outgoing_t *outgoing_create(nta_agent_t *agent,
nta_response_f *callback,
nta_outgoing_magic_t *magic,
url_string_t const *route_url,
tp_name_t const *tpn,
msg_t *msg,
tag_type_t tag, tag_value_t value, ...);
static void outgoing_queue_init(outgoing_queue_t *,
unsigned timeout);
static void outgoing_queue_adjust(nta_agent_t *sa,
outgoing_queue_t *queue,
unsigned timeout);
static void outgoing_free(nta_outgoing_t *orq);
su_inline void outgoing_cut_off(nta_outgoing_t *orq);
su_inline void outgoing_reclaim(nta_outgoing_t *orq);
static nta_outgoing_t *outgoing_find(nta_agent_t const *sa,
msg_t const *msg,
sip_t const *sip,
sip_via_t const *v);
static int outgoing_recv(nta_outgoing_t *orq, int status, msg_t *, sip_t *);
static void outgoing_default_recv(nta_outgoing_t *, int, msg_t *, sip_t *);
static void _nta_outgoing_timer(nta_agent_t *);
static int outgoing_recv_reliable(nta_outgoing_t *orq, msg_t *msg, sip_t *sip);
/* Internal message passing */
union sm_arg_u {
struct outgoing_recv_s {
nta_outgoing_t *orq;
msg_t *msg;
sip_t *sip;
int status;
} a_outgoing_recv[1];
incoming_queue_t a_incoming_queue[1];
outgoing_queue_t a_outgoing_queue[1];
};
/* Global module data */
/**@var char const NTA_DEBUG[];
*
* Environment variable determining the default debug log level.
*
* The NTA_DEBUG environment variable is used to determine the default
* debug logging level. The normal level is 3.
*
* @sa <sofia-sip/su_debug.h>, #su_log_global, #SOFIA_DEBUG
*/
#ifdef DOXYGEN
extern char const NTA_DEBUG[]; /* dummy declaration for Doxygen */
#endif
#ifndef SU_DEBUG
#define SU_DEBUG 3
#endif
/**Debug log for @b nta module.
*
* The nta_log is the log object used by @b nta module. The level of
* nta_log is set using #NTA_DEBUG environment variable.
*/
su_log_t nta_log[] = { SU_LOG_INIT("nta", "NTA_DEBUG", SU_DEBUG) };
/* ====================================================================== */
/* 1) Agent */
/**
* Create an NTA agent object.
*
* Create an NTA agent object. The agent
* object creates and binds a server socket with address specified in @e url.
* If the @e host portion of the @e url is @c "*", the agent listens to all
* addresses available on the host.
*
* When a message is received, the agent object parses it. If the result is
* a valid SIP message, the agent object passes the message to the
* application by invoking the nta_message_f @e callback function.
*
* @note
* The @e url can be either parsed url (of type url_t ()), or a valid
* SIP URL as a string.
*
* @note
* If @e url is @c NULL, the default @e url @c "sip:*" is used.
* @par
* If @e url is @c NONE (iow, (void*)-1), no server sockets are bound.
* @par
* If @p transport parameters are specified in @a url, agent uses only
* specified transport type.
*
* @par
* If an @p maddr parameter is specified in @e url, agent binds to the
* specified address, but uses @e host part of @e url when it generates
* @Contact and @Via headers. The @p maddr parameter is also included,
* unless it equals to @c INADDR_ANY (@p 0.0.0.0 or @p [::]).
*
* @param root pointer to a su_root_t used for synchronization
* @param contact_url URL that agent uses to bind the server sockets
* @param callback pointer to callback function
* @param magic pointer to user data
* @param tag,value,... tagged arguments
*
* @TAGS
* NTATAG_ALIASES(),
* NTATAG_BAD_REQ_MASK(), NTATAG_BAD_RESP_MASK(), NTATAG_BLACKLIST(),
* NTATAG_CANCEL_2543(), NTATAG_CANCEL_487(), NTATAG_CLIENT_RPORT(),
* NTATAG_DEBUG_DROP_PROB(), NTATAG_DEFAULT_PROXY(),
* NTATAG_EXTRA_100(), NTATAG_GRAYLIST(),
* NTATAG_MAXSIZE(), NTATAG_MAX_FORWARDS(), NTATAG_MERGE_482(), NTATAG_MCLASS()
* NTATAG_PASS_100(), NTATAG_PASS_408(), NTATAG_PRELOAD(), NTATAG_PROGRESS(),
* NTATAG_REL100(),
* NTATAG_SERVER_RPORT(),
* NTATAG_SIPFLAGS(),
* NTATAG_SIP_T1X64(), NTATAG_SIP_T1(), NTATAG_SIP_T2(), NTATAG_SIP_T4(),
* NTATAG_STATELESS(),
* NTATAG_TAG_3261(), NTATAG_TCP_RPORT(), NTATAG_TIMEOUT_408(),
* NTATAG_TLS_RPORT(),
* NTATAG_TIMER_C(), NTATAG_MAX_PROCEEDING(),
* NTATAG_UA(), NTATAG_UDP_MTU(), NTATAG_USER_VIA(),
* NTATAG_USE_NAPTR(), NTATAG_USE_SRV() and NTATAG_USE_TIMESTAMP().
*
* @note The value from following tags are stored, but they currently do nothing:
* NTATAG_SIGCOMP_ALGORITHM(), NTATAG_SIGCOMP_OPTIONS(), NTATAG_SMIME()
*
* @note It is possible to provide @c (url_string_t*)-1 as @a contact_url.
* In that case, no server sockets are bound.
*
* @retval handle to the agent when successful,
* @retval NULL upon an error.
*
* @sa NUTAG_
*/
nta_agent_t *nta_agent_create(su_root_t *root,
url_string_t const *contact_url,
nta_message_f *callback,
nta_agent_magic_t *magic,
tag_type_t tag, tag_value_t value, ...)
{
nta_agent_t *agent;
ta_list ta;
if (root == NULL)
return su_seterrno(EINVAL), NULL;
ta_start(ta, tag, value);
if ((agent = su_home_new(sizeof(*agent)))) {
unsigned timer_c = 0, timer_d = 32000;
agent->sa_root = root;
agent->sa_callback = callback;
agent->sa_magic = magic;
agent->sa_flags = MSG_DO_CANONIC;
agent->sa_maxsize = 2 * 1024 * 1024; /* 2 MB */
agent->sa_bad_req_mask =
/*
* Bit-wise not of these - what is left is suitable for UAs with
* 100rel, timer, events, publish
*/
(unsigned) ~(sip_mask_response | sip_mask_proxy | sip_mask_registrar |
sip_mask_pref | sip_mask_privacy);
agent->sa_bad_resp_mask =
(unsigned) ~(sip_mask_request | sip_mask_proxy | sip_mask_registrar |
sip_mask_pref | sip_mask_privacy);
agent->sa_t1 = NTA_SIP_T1;
agent->sa_t2 = NTA_SIP_T2;
agent->sa_t4 = NTA_SIP_T4;
agent->sa_t1x64 = 64 * NTA_SIP_T1;
agent->sa_timer_c = 185 * 1000;
agent->sa_graylist = 600;
agent->sa_drop_prob = 0;
agent->sa_is_a_uas = 0;
agent->sa_progress = 60 * 1000;
agent->sa_user_via = 0;
agent->sa_extra_100 = 0;
agent->sa_pass_100 = 0;
agent->sa_timeout_408 = 1;
agent->sa_pass_408 = 0;
agent->sa_merge_482 = 0;
agent->sa_cancel_2543 = 0;
agent->sa_cancel_487 = 1;
agent->sa_invite_100rel = 0;
agent->sa_timestamp = 0;
agent->sa_use_naptr = 1;
agent->sa_use_srv = 1;
agent->sa_srv_503 = 1;
agent->sa_auto_comp = 0;
agent->sa_server_rport = 1;
/* RFC 3261 section 8.1.1.6 */
sip_max_forwards_init(agent->sa_max_forwards);
if (getenv("SIPCOMPACT"))
agent->sa_flags |= MSG_DO_COMPACT;
agent_set_params(agent, ta_args(ta));
if (agent->sa_mclass == NULL)
agent->sa_mclass = sip_default_mclass();
agent->sa_in.re_t1 = &agent->sa_in.re_list;
incoming_queue_init(agent->sa_in.proceeding, 0);
incoming_queue_init(agent->sa_in.preliminary, agent->sa_t1x64); /* P1 */
incoming_queue_init(agent->sa_in.inv_completed, agent->sa_t1x64); /* H */
incoming_queue_init(agent->sa_in.inv_confirmed, agent->sa_t4); /* I */
incoming_queue_init(agent->sa_in.completed, agent->sa_t1x64); /* J */
incoming_queue_init(agent->sa_in.terminated, 0);
incoming_queue_init(agent->sa_in.final_failed, 0);
agent->sa_out.re_t1 = &agent->sa_out.re_list;
if (agent->sa_use_timer_c || !agent->sa_is_a_uas)
timer_c = agent->sa_timer_c;
if (timer_d < agent->sa_t1x64)
timer_d = agent->sa_t1x64;
outgoing_queue_init(agent->sa_out.delayed, 0);
outgoing_queue_init(agent->sa_out.resolving, 0);
outgoing_queue_init(agent->sa_out.trying, agent->sa_t1x64); /* F */
outgoing_queue_init(agent->sa_out.completed, agent->sa_t4); /* K */
outgoing_queue_init(agent->sa_out.terminated, 0);
/* Special queues (states) for outgoing INVITE transactions */
outgoing_queue_init(agent->sa_out.inv_calling, agent->sa_t1x64); /* B */
outgoing_queue_init(agent->sa_out.inv_proceeding, timer_c); /* C */
outgoing_queue_init(agent->sa_out.inv_completed, timer_d); /* D */
if (leg_htable_resize(agent->sa_home, agent->sa_dialogs, 0) < 0 ||
leg_htable_resize(agent->sa_home, agent->sa_defaults, 0) < 0 ||
outgoing_htable_resize(agent->sa_home, agent->sa_outgoing, 0) < 0 ||
incoming_htable_resize(agent->sa_home, agent->sa_incoming, 0) < 0) {
SU_DEBUG_0(("nta_agent_create: failure with %s\n", "hash tables"));
goto deinit;
}
SU_DEBUG_9(("nta_agent_create: initialized %s\n", "hash tables"));
if (contact_url != (url_string_t *)-1 &&
nta_agent_add_tport(agent, contact_url, ta_tags(ta)) < 0) {
SU_DEBUG_7(("nta_agent_create: failure with %s\n", "transport"));
goto deinit;
}
SU_DEBUG_9(("nta_agent_create: initialized %s\n", "transports"));
if (agent_tag_init(agent) < 0) {
SU_DEBUG_3(("nta_agent_create: failure with %s\n", "random identifiers"));
goto deinit;
}
SU_DEBUG_9(("nta_agent_create: initialized %s\n", "random identifiers"));
if (agent_timer_init(agent) < 0) {
SU_DEBUG_0(("nta_agent_create: failure with %s\n", "timer"));
goto deinit;
}
SU_DEBUG_9(("nta_agent_create: initialized %s\n", "timer"));
if (agent_launch_terminator(agent) == 0)
SU_DEBUG_9(("nta_agent_create: initialized %s\n", "threads"));
#if HAVE_SOFIA_SRESOLV
agent->sa_resolver = sres_resolver_create(root, NULL, ta_tags(ta));
if (!agent->sa_resolver) {
SU_DEBUG_0(("nta_agent_create: failure with %s\n", "resolver"));
}
SU_DEBUG_9(("nta_agent_create: initialized %s\n", "resolver"));
#endif
ta_end(ta);
return agent;
deinit:
nta_agent_destroy(agent);
}
ta_end(ta);
return NULL;
}
/**
* Destroy an NTA agent object.
*
* @param agent the NTA agent object to be destroyed.
*
*/
void nta_agent_destroy(nta_agent_t *agent)
{
if (agent) {
size_t i;
outgoing_htable_t *oht = agent->sa_outgoing;
incoming_htable_t *iht = agent->sa_incoming;
/* Currently, this is pretty pointless, as legs don't keep any resources */
leg_htable_t *lht;
nta_leg_t *leg;
for (i = 0, lht = agent->sa_dialogs; i < lht->lht_size; i++) {
if ((leg = lht->lht_table[i])) {
SU_DEBUG_3(("nta_agent_destroy: destroying dialog with <"
URL_PRINT_FORMAT ">\n",
URL_PRINT_ARGS(leg->leg_remote->a_url)));
leg_free(agent, leg);
}
}
for (i = 0, lht = agent->sa_defaults; i < lht->lht_size; i++) {
if ((leg = lht->lht_table[i])) {
SU_DEBUG_3(("%s: destroying leg for <"
URL_PRINT_FORMAT ">\n",
__func__, URL_PRINT_ARGS(leg->leg_url)));
leg_free(agent, leg);
}
}
if (agent->sa_default_leg)
leg_free(agent, agent->sa_default_leg);
for (i = iht->iht_size; i-- > 0; )
while (iht->iht_table[i]) {
nta_incoming_t *irq = iht->iht_table[i];
if (!irq->irq_destroyed)
SU_DEBUG_3(("%s: destroying %s server transaction from <"
URL_PRINT_FORMAT ">\n",
__func__, irq->irq_rq->rq_method_name,
URL_PRINT_ARGS(irq->irq_from->a_url)));
incoming_free(irq);
}
for (i = oht->oht_size; i-- > 0;)
while (oht->oht_table[i]) {
nta_outgoing_t *orq = oht->oht_table[i];
if (!orq->orq_destroyed)
SU_DEBUG_3(("%s: destroying %s%s client transaction to <"
URL_PRINT_FORMAT ">\n",
__func__,
(orq->orq_forking || orq->orq_forks) ? "forked " : "forking",
orq->orq_method_name,
URL_PRINT_ARGS(orq->orq_to->a_url)));
orq->orq_forks = NULL, orq->orq_forking = NULL;
outgoing_free(orq);
}
su_timer_destroy(agent->sa_timer), agent->sa_timer = NULL;
# if HAVE_SOFIA_SRESOLV
sres_resolver_destroy(agent->sa_resolver), agent->sa_resolver = NULL;
# endif
tport_destroy(agent->sa_tports), agent->sa_tports = NULL;
agent_kill_terminator(agent);
su_home_unref(agent->sa_home);
}
}
/** Return agent context. */
nta_agent_magic_t *nta_agent_magic(nta_agent_t const *agent)
{
return agent ? agent->sa_magic : NULL;
}
/** Return @Contact header.
*
* Get a @Contact header, which can be used to reach @a agent.
*
* @param agent NTA agent object
*
* User agents can insert the @Contact header in the outgoing REGISTER,
* INVITE, and ACK requests and replies to incoming INVITE and OPTIONS
* transactions.
*
* Proxies can use the @Contact header to create appropriate @RecordRoute
* headers:
* @code
* r_r = sip_record_route_create(msg_home(msg),
* sip->sip_request->rq_url,
* contact->m_url);
* @endcode
*
* @return A sip_contact_t object corresponding to the @a agent.
*/
sip_contact_t *nta_agent_contact(nta_agent_t const *agent)
{
return agent ? agent->sa_contact : NULL;
}
/** Return a list of @Via headers.
*
* Get @Via headers for all activated transport.
*
* @param agent NTA agent object
*
* @return A list of #sip_via_t objects used by the @a agent.
*/
sip_via_t *nta_agent_via(nta_agent_t const *agent)
{
return agent ? agent->sa_vias : NULL;
}
/** Return a list of public (UPnP, STUN) @Via headers.
*
* Get public @Via headers for all activated transports.
*
* @param agent NTA agent object
*
* @return A list of #sip_via_t objects used by the @a agent.
*/
sip_via_t *nta_agent_public_via(nta_agent_t const *agent)
{
return agent ? agent->sa_public_vias : NULL;
}
/** Match a @Via header @a v with @Via headers in @a agent.
*
*/
static
sip_via_t *agent_has_via(nta_agent_t const *agent, sip_via_t const *via)
{
sip_via_t const *v;
for (v = agent->sa_public_vias; v; v = v->v_next) {
if (!su_casematch(via->v_host, v->v_host))
continue;
if (!su_strmatch(via->v_port, v->v_port))
continue;
if (!su_casematch(via->v_protocol, v->v_protocol))
continue;
return (sip_via_t *)v;
}
for (v = agent->sa_vias; v; v = v->v_next) {
if (!su_casematch(via->v_host, v->v_host))
continue;
if (!su_strmatch(via->v_port, v->v_port))
continue;
if (!su_casematch(via->v_protocol, v->v_protocol))
continue;
return (sip_via_t *)v;
}
return NULL;
}
/** Return @UserAgent header.
*
* Get @UserAgent information with NTA version.
*
* @param agent NTA agent object (may be NULL)
*
* @return A string containing the @a agent version.
*/
char const *nta_agent_version(nta_agent_t const *agent)
{
return "nta" "/" VERSION;
}
/** Initialize default tag */
static int agent_tag_init(nta_agent_t *self)
{
sip_contact_t *m = self->sa_contact;
uint32_t hash = su_random();
if (m) {
if (m->m_url->url_user)
hash = 914715421U * hash + msg_hash_string(m->m_url->url_user);
if (m->m_url->url_host)
hash = 914715421U * hash + msg_hash_string(m->m_url->url_host);
if (m->m_url->url_port)
hash = 914715421U * hash + msg_hash_string(m->m_url->url_port);
if (m->m_url->url_params)
hash = 914715421U * hash + msg_hash_string(m->m_url->url_params);
}
if (hash == 0)
hash = 914715421U;
self->sa_branch = NTA_BRANCH_PRIME * (uint64_t)su_nanotime(NULL);
self->sa_branch *= hash;
self->sa_tags = NTA_TAG_PRIME * self->sa_branch;
return 0;
}
/** Initialize agent timer. */
static
int agent_timer_init(nta_agent_t *agent)
{
agent->sa_timer = su_timer_create(su_root_task(agent->sa_root),
NTA_SIP_T1 / 8);
#if 0
return su_timer_set(agent->sa_timer,
agent_timer,
agent);
#endif
return -(agent->sa_timer == NULL);
}
/**
* Agent timer routine.
*/
static
void agent_timer(su_root_magic_t *rm, su_timer_t *timer, nta_agent_t *agent)
{
su_time_t stamp = su_now();
uint32_t now = su_time_ms(stamp), next, latest;
now += now == 0;
agent->sa_next = 0;
agent->sa_in_timer = 1;
_nta_outgoing_timer(agent);
_nta_incoming_timer(agent);
agent->sa_in_timer = 0;
/* Calculate next timeout */
next = latest = now + NTA_TIME_MAX + 1;
#define NEXT_TIMEOUT(next, p, f, now) \
(void)(p && (int32_t)(p->f - (next)) < 0 && \
((next) = ((int32_t)(p->f - (now)) > 0 ? p->f : (now))))
NEXT_TIMEOUT(next, agent->sa_out.re_list, orq_retry, now);
NEXT_TIMEOUT(next, agent->sa_out.inv_completed->q_head, orq_timeout, now);
NEXT_TIMEOUT(next, agent->sa_out.completed->q_head, orq_timeout, now);
NEXT_TIMEOUT(next, agent->sa_out.inv_calling->q_head, orq_timeout, now);
if (agent->sa_out.inv_proceeding->q_timeout)
NEXT_TIMEOUT(next, agent->sa_out.inv_proceeding->q_head, orq_timeout, now);
NEXT_TIMEOUT(next, agent->sa_out.trying->q_head, orq_timeout, now);
NEXT_TIMEOUT(next, agent->sa_in.preliminary->q_head, irq_timeout, now);
NEXT_TIMEOUT(next, agent->sa_in.inv_completed->q_head, irq_timeout, now);
NEXT_TIMEOUT(next, agent->sa_in.inv_confirmed->q_head, irq_timeout, now);
NEXT_TIMEOUT(next, agent->sa_in.completed->q_head, irq_timeout, now);
NEXT_TIMEOUT(next, agent->sa_in.re_list, irq_retry, now);
if (agent->sa_next)
NEXT_TIMEOUT(next, agent, sa_next, now);
#undef NEXT_TIMEOUT
if (next == latest) {
/* Do not set timer? */
SU_DEBUG_9(("nta: timer not set\n" VA_NONE));
assert(!agent->sa_out.completed->q_head);
assert(!agent->sa_out.trying->q_head);
assert(!agent->sa_out.inv_calling->q_head);
assert(!agent->sa_out.re_list);
assert(!agent->sa_in.inv_confirmed->q_head);
assert(!agent->sa_in.preliminary->q_head);
assert(!agent->sa_in.completed->q_head);
assert(!agent->sa_in.inv_completed->q_head);
assert(!agent->sa_in.re_list);
return;
}
if (next == now) if (++next == 0) ++next;
SU_DEBUG_9(("nta: timer %s to %ld ms\n", "set next", (long)(next - now)));
agent->sa_next = next;
su_timer_set_at(timer, agent_timer, agent, su_time_add(stamp, next - now));
}
/** Add uin32_t milliseconds to the time. */
static su_time_t add_milliseconds(su_time_t t0, uint32_t ms)
{
unsigned long sec = ms / 1000, usec = (ms % 1000) * 1000;
t0.tv_usec += usec;
t0.tv_sec += sec;
if (t0.tv_usec >= 1000000) {
t0.tv_sec += 1;
t0.tv_usec -= 1000000;
}
return t0;
}
/** Calculate nonzero value for timeout.
*
* Sets or adjusts agent timer when needed.
*
* @retval 0 if offset is 0
* @retval timeout (millisecond counter) otherwise
*/
static
uint32_t set_timeout(nta_agent_t *agent, uint32_t offset)
{
su_time_t now;
uint32_t next, ms;
if (offset == 0)
return 0;
now = su_now();
ms = su_time_ms(now);
next = ms + offset;
if (next == 0) next = 1;
if (agent->sa_in_timer) /* Currently executing timer */
return next;
if (agent->sa_next == 0 || (int32_t)(agent->sa_next - next - 5L) > 0) {
/* Set timer */
if (agent->sa_next)
SU_DEBUG_9(("nta: timer %s to %ld ms\n", "shortened", (long)offset));
else
SU_DEBUG_9(("nta: timer %s to %ld ms\n", "set", (long)offset));
su_timer_set_at(agent->sa_timer, agent_timer, agent,
add_milliseconds(now, offset));
agent->sa_next = next;
}
return next;
}
/** Return current timeval. */
static
su_time_t agent_now(nta_agent_t const *agent)
{
return su_now();
}
/** Launch transaction terminator task */
static
int agent_launch_terminator(nta_agent_t *agent)
{
#ifdef TPTAG_THRPSIZE
if (agent->sa_tport_threadpool) {
su_home_threadsafe(agent->sa_home);
return su_clone_start(agent->sa_root,
agent->sa_terminator,
NULL,
NULL,
NULL);
}
#endif
return -1;
}
/** Kill transaction terminator task */
static
void agent_kill_terminator(nta_agent_t *agent)
{
su_clone_wait(agent->sa_root, agent->sa_terminator);
}
/**Set NTA Parameters.
*
* The nta_agent_set_params() function sets the stack parameters. The
* parameters determine the way NTA handles the retransmissions, how long
* NTA keeps transactions alive, does NTA apply proxy or user-agent logic to
* INVITE transactions, or how the @Via headers are generated.
*
* @note
* Setting the parameters NTATAG_MAXSIZE(), NTATAG_UDP_MTU(), NTATAG_MAX_PROCEEDING(),
* NTATAG_SIP_T1X64(), NTATAG_SIP_T1(), NTATAG_SIP_T2(), NTATAG_SIP_T4() to
* 0 selects the default value.
*
* @TAGS
* NTATAG_ALIASES(),
* NTATAG_BAD_REQ_MASK(), NTATAG_BAD_RESP_MASK(), NTATAG_BLACKLIST(),
* NTATAG_CANCEL_2543(), NTATAG_CANCEL_487(), NTATAG_CLIENT_RPORT(),
* NTATAG_DEBUG_DROP_PROB(), NTATAG_DEFAULT_PROXY(),
* NTATAG_EXTRA_100(), NTATAG_GRAYLIST(),
* NTATAG_MAXSIZE(), NTATAG_MAX_FORWARDS(), NTATAG_MERGE_482(), NTATAG_MCLASS()
* NTATAG_PASS_100(), NTATAG_PASS_408(), NTATAG_PRELOAD(), NTATAG_PROGRESS(),
* NTATAG_REL100(),
* NTATAG_SERVER_RPORT(),
* NTATAG_SIPFLAGS(),
* NTATAG_SIP_T1X64(), NTATAG_SIP_T1(), NTATAG_SIP_T2(), NTATAG_SIP_T4(),
* NTATAG_STATELESS(),
* NTATAG_TAG_3261(), NTATAG_TCP_RPORT(), NTATAG_TIMEOUT_408(),
* NTATAG_TLS_RPORT(),
* NTATAG_TIMER_C(), NTATAG_MAX_PROCEEDING(),
* NTATAG_UA(), NTATAG_UDP_MTU(), NTATAG_USER_VIA(),
* NTATAG_USE_NAPTR(), NTATAG_USE_SRV() and NTATAG_USE_TIMESTAMP().
*
* @note The value from following tags are stored, but they currently do nothing:
* NTATAG_SIGCOMP_ALGORITHM(), NTATAG_SIGCOMP_OPTIONS(), NTATAG_SMIME()
*/
int nta_agent_set_params(nta_agent_t *agent,
tag_type_t tag, tag_value_t value, ...)
{
int retval;
if (agent) {
ta_list ta;
ta_start(ta, tag, value);
retval = agent_set_params(agent, ta_args(ta));
ta_end(ta);
} else {
su_seterrno(EINVAL);
retval = -1;
}
return retval;
}
/** Internal function for setting tags */
static
int agent_set_params(nta_agent_t *agent, tagi_t *tags)
{
int n, nC, m;
unsigned bad_req_mask = agent->sa_bad_req_mask;
unsigned bad_resp_mask = agent->sa_bad_resp_mask;
usize_t maxsize = agent->sa_maxsize;
usize_t max_proceeding = agent->sa_max_proceeding;
unsigned max_forwards = agent->sa_max_forwards->mf_count;
unsigned udp_mtu = agent->sa_udp_mtu;
unsigned sip_t1 = agent->sa_t1;
unsigned sip_t2 = agent->sa_t2;
unsigned sip_t4 = agent->sa_t4;
unsigned sip_t1x64 = agent->sa_t1x64;
unsigned timer_c = agent->sa_timer_c;
unsigned timer_d = 32000;
unsigned graylist = agent->sa_graylist;
unsigned blacklist = agent->sa_blacklist;
int ua = agent->sa_is_a_uas;
unsigned progress = agent->sa_progress;
int stateless = agent->sa_is_stateless;
unsigned drop_prob = agent->sa_drop_prob;
int user_via = agent->sa_user_via;
int extra_100 = agent->sa_extra_100;
int pass_100 = agent->sa_pass_100;
int timeout_408 = agent->sa_timeout_408;
int pass_408 = agent->sa_pass_408;
int merge_482 = agent->sa_merge_482;
int cancel_2543 = agent->sa_cancel_2543;
int cancel_487 = agent->sa_cancel_487;
int invite_100rel = agent->sa_invite_100rel;
int use_timestamp = agent->sa_timestamp;
int use_naptr = agent->sa_use_naptr;
int use_srv = agent->sa_use_srv;
int srv_503 = agent->sa_srv_503;
void *smime = agent->sa_smime;
uint32_t flags = agent->sa_flags;
int rport = agent->sa_rport;
int server_rport = agent->sa_server_rport;
int tcp_rport = agent->sa_tcp_rport;
int tls_rport = agent->sa_tls_rport;
unsigned preload = agent->sa_preload;
unsigned threadpool = agent->sa_tport_threadpool;
char const *sigcomp = agent->sa_sigcomp_options;
char const *algorithm = NONE;
msg_mclass_t const *mclass = NONE;
sip_contact_t const *aliases = NONE;
url_string_t const *proxy = NONE;
tport_t *tport;
su_home_t *home = agent->sa_home;
n = tl_gets(tags,
NTATAG_ALIASES_REF(aliases),
NTATAG_BAD_REQ_MASK_REF(bad_req_mask),
NTATAG_BAD_RESP_MASK_REF(bad_resp_mask),
NTATAG_BLACKLIST_REF(blacklist),
NTATAG_CANCEL_2543_REF(cancel_2543),
NTATAG_CANCEL_487_REF(cancel_487),
NTATAG_DEBUG_DROP_PROB_REF(drop_prob),
NTATAG_DEFAULT_PROXY_REF(proxy),
NTATAG_EXTRA_100_REF(extra_100),
NTATAG_GRAYLIST_REF(graylist),
NTATAG_MAXSIZE_REF(maxsize),
NTATAG_MAX_PROCEEDING_REF(max_proceeding),
NTATAG_MAX_FORWARDS_REF(max_forwards),
NTATAG_MCLASS_REF(mclass),
NTATAG_MERGE_482_REF(merge_482),
NTATAG_PASS_100_REF(pass_100),
NTATAG_PASS_408_REF(pass_408),
NTATAG_PRELOAD_REF(preload),
NTATAG_PROGRESS_REF(progress),
NTATAG_REL100_REF(invite_100rel),
NTATAG_RPORT_REF(rport),
NTATAG_SERVER_RPORT_REF(server_rport),
NTATAG_SIGCOMP_ALGORITHM_REF(algorithm),
NTATAG_SIGCOMP_OPTIONS_REF(sigcomp),
NTATAG_SIPFLAGS_REF(flags),
NTATAG_SIP_T1X64_REF(sip_t1x64),
NTATAG_SIP_T1_REF(sip_t1),
NTATAG_SIP_T2_REF(sip_t2),
NTATAG_SIP_T4_REF(sip_t4),
#if HAVE_SOFIA_SMIME
NTATAG_SMIME_REF(smime),
#endif
NTATAG_STATELESS_REF(stateless),
NTATAG_TCP_RPORT_REF(tcp_rport),
NTATAG_TLS_RPORT_REF(tls_rport),
NTATAG_TIMEOUT_408_REF(timeout_408),
NTATAG_UA_REF(ua),
NTATAG_UDP_MTU_REF(udp_mtu),
NTATAG_USER_VIA_REF(user_via),
NTATAG_USE_NAPTR_REF(use_naptr),
NTATAG_USE_SRV_REF(use_srv),
NTATAG_USE_TIMESTAMP_REF(use_timestamp),
#ifdef TPTAG_THRPSIZE
/* If threadpool is enabled, start a separate "reaper thread" */
TPTAG_THRPSIZE_REF(threadpool),
#endif
NTATAG_SRV_503_REF(srv_503),
TAG_END());
nC = tl_gets(tags,
NTATAG_TIMER_C_REF(timer_c),
TAG_END());
n += nC;
if (mclass != NONE)
agent->sa_mclass = mclass ? mclass : sip_default_mclass();
m = 0;
for (tport = agent->sa_tports; tport; tport = tport_next(tport)) {
int m0 = tport_set_params(tport, TAG_NEXT(tags));
if (m0 < 0)
return m0;
if (m0 > m)
m = m0;
}
n += m;
if (aliases != NONE) {
sip_contact_t const *m, *m_next;
m = agent->sa_aliases;
agent->sa_aliases = sip_contact_dup(home, aliases);
for (; m; m = m_next) { /* Free old aliases */
m_next = m->m_next;
su_free(home, (void *)m);
}
}
if (proxy != NONE) {
url_t *dp = url_hdup(home, proxy->us_url);
url_sanitize(dp);
if (dp == NULL || dp->url_type == url_sip || dp->url_type == url_sips) {
if (agent->sa_default_proxy)
su_free(home, agent->sa_default_proxy);
agent->sa_default_proxy = dp;
}
else
n = -1;
}
if (algorithm != NONE)
agent->sa_algorithm = su_strdup(home, algorithm);
if (!su_strmatch(sigcomp, agent->sa_sigcomp_options)) {
msg_param_t const *l = NULL;
char *s = su_strdup(home, sigcomp);
char *s1 = su_strdup(home, s), *s2 = s1;
if (s && s2 && msg_avlist_d(home, &s2, &l) == 0 && *s2 == '\0') {
su_free(home, (void *)agent->sa_sigcomp_options);
su_free(home, (void *)agent->sa_sigcomp_option_list);
agent->sa_sigcomp_options = s;
agent->sa_sigcomp_option_free = s1;
agent->sa_sigcomp_option_list = l;
} else {
su_free(home, s);
su_free(home, s1);
su_free(home, (void *)l);
n = -1;
}
}
if (maxsize == 0) maxsize = 2 * 1024 * 1024;
if (maxsize > UINT32_MAX) maxsize = UINT32_MAX;
agent->sa_maxsize = maxsize;
if (max_proceeding == 0) max_proceeding = USIZE_MAX;
agent->sa_max_proceeding = max_proceeding;
if (max_forwards == 0) max_forwards = 70; /* Default value */
agent->sa_max_forwards->mf_count = max_forwards;
if (udp_mtu == 0) udp_mtu = 1300;
if (udp_mtu > 65535) udp_mtu = 65535;
if (agent->sa_udp_mtu != udp_mtu) {
agent->sa_udp_mtu = udp_mtu;
agent_set_udp_params(agent, udp_mtu);
}
if (sip_t1 == 0) sip_t1 = NTA_SIP_T1;
if (sip_t1 > NTA_TIME_MAX) sip_t1 = NTA_TIME_MAX;
agent->sa_t1 = sip_t1;
if (sip_t2 == 0) sip_t2 = NTA_SIP_T2;
if (sip_t2 > NTA_TIME_MAX) sip_t2 = NTA_TIME_MAX;
agent->sa_t2 = sip_t2;
if (sip_t4 == 0) sip_t4 = NTA_SIP_T4;
if (sip_t4 > NTA_TIME_MAX) sip_t4 = NTA_TIME_MAX;
if (agent->sa_t4 != sip_t4) {
incoming_queue_adjust(agent, agent->sa_in.inv_confirmed, sip_t4);
outgoing_queue_adjust(agent, agent->sa_out.completed, sip_t4);
}
agent->sa_t4 = sip_t4;
if (sip_t1x64 == 0) sip_t1x64 = NTA_SIP_T1 * 64;
if (sip_t1x64 > NTA_TIME_MAX) sip_t1x64 = NTA_TIME_MAX;
if (agent->sa_t1x64 != sip_t1x64) {
incoming_queue_adjust(agent, agent->sa_in.preliminary, sip_t1x64);
incoming_queue_adjust(agent, agent->sa_in.completed, sip_t1x64);
incoming_queue_adjust(agent, agent->sa_in.inv_completed, sip_t1x64);
outgoing_queue_adjust(agent, agent->sa_out.trying, sip_t1x64);
outgoing_queue_adjust(agent, agent->sa_out.inv_calling, sip_t1x64);
}
agent->sa_t1x64 = sip_t1x64;
if (nC == 1) {
agent->sa_use_timer_c = 1;
if (timer_c == 0)
timer_c = 185 * 1000;
agent->sa_timer_c = timer_c;
outgoing_queue_adjust(agent, agent->sa_out.inv_proceeding, timer_c);
}
if (timer_d < sip_t1x64)
timer_d = sip_t1x64;
outgoing_queue_adjust(agent, agent->sa_out.inv_completed, timer_d);
if (graylist > 24 * 60 * 60)
graylist = 24 * 60 * 60;
agent->sa_graylist = graylist;
if (blacklist > 24 * 60 * 60)
blacklist = 24 * 60 * 60;
agent->sa_blacklist = blacklist;
if (progress == 0)
progress = 60 * 1000;
agent->sa_progress = progress;
if (server_rport > 3)
server_rport = 1;
else if (server_rport < 0)
server_rport = 1;
agent->sa_server_rport = server_rport;
agent->sa_bad_req_mask = bad_req_mask;
agent->sa_bad_resp_mask = bad_resp_mask;
agent->sa_is_a_uas = ua != 0;
agent->sa_is_stateless = stateless != 0;
agent->sa_drop_prob = drop_prob < 1000 ? drop_prob : 1000;
agent->sa_user_via = user_via != 0;
agent->sa_extra_100 = extra_100 != 0;
agent->sa_pass_100 = pass_100 != 0;
agent->sa_timeout_408 = timeout_408 != 0;
agent->sa_pass_408 = pass_408 != 0;
agent->sa_merge_482 = merge_482 != 0;
agent->sa_cancel_2543 = cancel_2543 != 0;
agent->sa_cancel_487 = cancel_487 != 0;
agent->sa_invite_100rel = invite_100rel != 0;
agent->sa_timestamp = use_timestamp != 0;
agent->sa_use_naptr = use_naptr != 0;
agent->sa_use_srv = use_srv != 0;
agent->sa_srv_503 = srv_503 != 0;
agent->sa_smime = smime;
agent->sa_flags = flags & MSG_FLG_USERMASK;
agent->sa_rport = rport != 0;
agent->sa_tcp_rport = tcp_rport != 0;
agent->sa_tls_rport = tls_rport != 0;
agent->sa_preload = preload;
agent->sa_tport_threadpool = threadpool;
return n;
}
static
void agent_set_udp_params(nta_agent_t *self, usize_t udp_mtu)
{
tport_t *tp;
/* Set via fields for the tports */
for (tp = tport_primaries(self->sa_tports); tp; tp = tport_next(tp)) {
if (tport_is_udp(tp))
tport_set_params(tp,
TPTAG_TIMEOUT(2 * self->sa_t1x64),
TPTAG_MTU(udp_mtu),
TAG_END());
}
}
/**Get NTA Parameters.
*
* The nta_agent_get_params() function retrieves the stack parameters. The
* parameters determine the way NTA handles the retransmissions, how long
* NTA keeps transactions alive, does NTA apply proxy or user-agent logic to
* INVITE transactions, or how the @Via headers are generated.
*
* @TAGS
* NTATAG_ALIASES_REF(), NTATAG_BLACKLIST_REF(),
* NTATAG_CANCEL_2543_REF(), NTATAG_CANCEL_487_REF(),
* NTATAG_CLIENT_RPORT_REF(), NTATAG_CONTACT_REF(),
* NTATAG_DEBUG_DROP_PROB_REF(), NTATAG_DEFAULT_PROXY_REF(),
* NTATAG_EXTRA_100_REF(), NTATAG_GRAYLIST_REF(),
* NTATAG_MAXSIZE_REF(), NTATAG_MAX_FORWARDS_REF(), NTATAG_MCLASS_REF(),
* NTATAG_MERGE_482_REF(), NTATAG_MAX_PROCEEDING_REF(),
* NTATAG_PASS_100_REF(), NTATAG_PASS_408_REF(), NTATAG_PRELOAD_REF(),
* NTATAG_PROGRESS_REF(),
* NTATAG_REL100_REF(),
* NTATAG_SERVER_RPORT_REF(),
* NTATAG_SIGCOMP_ALGORITHM_REF(), NTATAG_SIGCOMP_OPTIONS_REF(),
* NTATAG_SIPFLAGS_REF(),
* NTATAG_SIP_T1_REF(), NTATAG_SIP_T1X64_REF(), NTATAG_SIP_T2_REF(),
* NTATAG_SIP_T4_REF(), NTATAG_SMIME_REF(), NTATAG_STATELESS_REF(),
* NTATAG_TAG_3261_REF(), NTATAG_TIMEOUT_408_REF(), NTATAG_TIMER_C_REF(),
* NTATAG_UA_REF(), NTATAG_UDP_MTU_REF(), NTATAG_USER_VIA_REF(),
* NTATAG_USE_NAPTR_REF(), NTATAG_USE_SRV_REF(),
* and NTATAG_USE_TIMESTAMP_REF().
*
*/
int nta_agent_get_params(nta_agent_t *agent,
tag_type_t tag, tag_value_t value, ...)
{
int n;
ta_list ta;
if (agent) {
ta_start(ta, tag, value);
n = agent_get_params(agent, ta_args(ta));
ta_end(ta);
return n;
}
su_seterrno(EINVAL);
return -1;
}
/** Get NTA parameters */
static
int agent_get_params(nta_agent_t *agent, tagi_t *tags)
{
return
tl_tgets(tags,
NTATAG_ALIASES(agent->sa_aliases),
NTATAG_BLACKLIST(agent->sa_blacklist),
NTATAG_CANCEL_2543(agent->sa_cancel_2543),
NTATAG_CANCEL_487(agent->sa_cancel_487),
NTATAG_CLIENT_RPORT(agent->sa_rport),
NTATAG_CONTACT(agent->sa_contact),
NTATAG_DEBUG_DROP_PROB(agent->sa_drop_prob),
NTATAG_DEFAULT_PROXY(agent->sa_default_proxy),
NTATAG_EXTRA_100(agent->sa_extra_100),
NTATAG_GRAYLIST(agent->sa_graylist),
NTATAG_MAXSIZE(agent->sa_maxsize),
NTATAG_MAX_PROCEEDING(agent->sa_max_proceeding),
NTATAG_MAX_FORWARDS(agent->sa_max_forwards->mf_count),
NTATAG_MCLASS(agent->sa_mclass),
NTATAG_MERGE_482(agent->sa_merge_482),
NTATAG_PASS_100(agent->sa_pass_100),
NTATAG_PASS_408(agent->sa_pass_408),
NTATAG_PRELOAD(agent->sa_preload),
NTATAG_PROGRESS(agent->sa_progress),
NTATAG_REL100(agent->sa_invite_100rel),
NTATAG_SERVER_RPORT((int)(agent->sa_server_rport)),
NTATAG_SIGCOMP_ALGORITHM(agent->sa_algorithm),
NTATAG_SIGCOMP_OPTIONS(agent->sa_sigcomp_options ?
agent->sa_sigcomp_options :
"sip"),
NTATAG_SIPFLAGS(agent->sa_flags),
NTATAG_SIP_T1(agent->sa_t1),
NTATAG_SIP_T1X64(agent->sa_t1x64),
NTATAG_SIP_T2(agent->sa_t2),
NTATAG_SIP_T4(agent->sa_t4),
#if HAVE_SOFIA_SMIME
NTATAG_SMIME(agent->sa_smime),
#else
NTATAG_SMIME(NULL),
#endif
NTATAG_STATELESS(agent->sa_is_stateless),
NTATAG_TAG_3261(1),
NTATAG_TIMEOUT_408(agent->sa_timeout_408),
NTATAG_TIMER_C(agent->sa_timer_c),
NTATAG_UA(agent->sa_is_a_uas),
NTATAG_UDP_MTU(agent->sa_udp_mtu),
NTATAG_USER_VIA(agent->sa_user_via),
NTATAG_USE_NAPTR(agent->sa_use_naptr),
NTATAG_USE_SRV(agent->sa_use_srv),
NTATAG_USE_TIMESTAMP(agent->sa_timestamp),
NTATAG_SRV_503(agent->sa_srv_503),
TAG_END());
}
/**Get NTA statistics.
*
* The nta_agent_get_stats() function retrieves the stack statistics.
*
* @TAGS
* NTATAG_S_ACKED_TR_REF(),
* NTATAG_S_BAD_MESSAGE_REF(),
* NTATAG_S_BAD_REQUEST_REF(),
* NTATAG_S_BAD_RESPONSE_REF(),
* NTATAG_S_CANCELED_TR_REF(),
* NTATAG_S_CLIENT_TR_REF(),
* NTATAG_S_DIALOG_TR_REF(),
* NTATAG_S_DROP_REQUEST_REF(),
* NTATAG_S_DROP_RESPONSE_REF(),
* NTATAG_S_IRQ_HASH_REF(),
* NTATAG_S_IRQ_HASH_USED_REF(),
* NTATAG_S_LEG_HASH_REF(),
* NTATAG_S_LEG_HASH_USED_REF(),
* NTATAG_S_MERGED_REQUEST_REF(),
* NTATAG_S_ORQ_HASH_REF(),
* NTATAG_S_ORQ_HASH_USED_REF(),
* NTATAG_S_RECV_MSG_REF(),
* NTATAG_S_RECV_REQUEST_REF(),
* NTATAG_S_RECV_RESPONSE_REF(),
* NTATAG_S_RECV_RETRY_REF(),
* NTATAG_S_RETRY_REQUEST_REF(),
* NTATAG_S_RETRY_RESPONSE_REF(),
* NTATAG_S_SENT_MSG_REF(),
* NTATAG_S_SENT_REQUEST_REF(),
* NTATAG_S_SENT_RESPONSE_REF(),
* NTATAG_S_SERVER_TR_REF(),
* NTATAG_S_TOUT_REQUEST_REF(),
* NTATAG_S_TOUT_RESPONSE_REF(),
* NTATAG_S_TRLESS_200_REF(),
* NTATAG_S_TRLESS_REQUEST_REF(),
* NTATAG_S_TRLESS_RESPONSE_REF(), and
* NTATAG_S_TRLESS_TO_TR_REF(),
*/
int nta_agent_get_stats(nta_agent_t *agent,
tag_type_t tag, tag_value_t value, ...)
{
int n;
ta_list ta;
if (!agent)
return su_seterrno(EINVAL), -1;
ta_start(ta, tag, value);
n = tl_tgets(ta_args(ta),
NTATAG_S_IRQ_HASH(agent->sa_incoming->iht_size),
NTATAG_S_ORQ_HASH(agent->sa_outgoing->oht_size),
NTATAG_S_LEG_HASH(agent->sa_dialogs->lht_size),
NTATAG_S_IRQ_HASH_USED(agent->sa_incoming->iht_used),
NTATAG_S_ORQ_HASH_USED(agent->sa_outgoing->oht_used),
NTATAG_S_LEG_HASH_USED(agent->sa_dialogs->lht_used),
NTATAG_S_RECV_MSG(agent->sa_stats->as_recv_msg),
NTATAG_S_RECV_REQUEST(agent->sa_stats->as_recv_request),
NTATAG_S_RECV_RESPONSE(agent->sa_stats->as_recv_response),
NTATAG_S_BAD_MESSAGE(agent->sa_stats->as_bad_message),
NTATAG_S_BAD_REQUEST(agent->sa_stats->as_bad_request),
NTATAG_S_BAD_RESPONSE(agent->sa_stats->as_bad_response),
NTATAG_S_DROP_REQUEST(agent->sa_stats->as_drop_request),
NTATAG_S_DROP_RESPONSE(agent->sa_stats->as_drop_response),
NTATAG_S_CLIENT_TR(agent->sa_stats->as_client_tr),
NTATAG_S_SERVER_TR(agent->sa_stats->as_server_tr),
NTATAG_S_DIALOG_TR(agent->sa_stats->as_dialog_tr),
NTATAG_S_ACKED_TR(agent->sa_stats->as_acked_tr),
NTATAG_S_CANCELED_TR(agent->sa_stats->as_canceled_tr),
NTATAG_S_TRLESS_REQUEST(agent->sa_stats->as_trless_request),
NTATAG_S_TRLESS_TO_TR(agent->sa_stats->as_trless_to_tr),
NTATAG_S_TRLESS_RESPONSE(agent->sa_stats->as_trless_response),
NTATAG_S_TRLESS_200(agent->sa_stats->as_trless_200),
NTATAG_S_MERGED_REQUEST(agent->sa_stats->as_merged_request),
NTATAG_S_SENT_MSG(agent->sa_stats->as_sent_msg),
NTATAG_S_SENT_REQUEST(agent->sa_stats->as_sent_request),
NTATAG_S_SENT_RESPONSE(agent->sa_stats->as_sent_response),
NTATAG_S_RETRY_REQUEST(agent->sa_stats->as_retry_request),
NTATAG_S_RETRY_RESPONSE(agent->sa_stats->as_retry_response),
NTATAG_S_RECV_RETRY(agent->sa_stats->as_recv_retry),
NTATAG_S_TOUT_REQUEST(agent->sa_stats->as_tout_request),
NTATAG_S_TOUT_RESPONSE(agent->sa_stats->as_tout_response),
TAG_END());
ta_end(ta);
return n;
}
/**Calculate a new unique tag.
*
* This function generates a series of 2**64 unique tags for @From or @To
* headers. The start of the tag series is derived from the NTP time the NTA
* agent was initialized.
*
*/
char const *nta_agent_newtag(su_home_t *home, char const *fmt, nta_agent_t *sa)
{
char tag[(8 * 8 + 4)/ 5 + 1];
if (sa == NULL)
return su_seterrno(EINVAL), NULL;
/* XXX - use a cryptographically safe func here? */
sa->sa_tags += NTA_TAG_PRIME;
msg_random_token(tag, sizeof(tag) - 1, &sa->sa_tags, sizeof(sa->sa_tags));
if (fmt && fmt[0])
return su_sprintf(home, fmt, tag);
else
return su_strdup(home, tag);
}
/**
* Calculate branch value.
*/
static char const *stateful_branch(su_home_t *home, nta_agent_t *sa)
{
char branch[(8 * 8 + 4)/ 5 + 1];
/* XXX - use a cryptographically safe func here? */
sa->sa_branch += NTA_BRANCH_PRIME;
msg_random_token(branch, sizeof(branch) - 1,
&sa->sa_branch, sizeof(sa->sa_branch));
return su_sprintf(home, "branch=z9hG4bK%s", branch);
}
#include <sofia-sip/su_md5.h>
/**
* Calculate branch value for stateless operation.
*
* XXX - should include HMAC of previous @Via line.
*/
static
char const *stateless_branch(nta_agent_t *sa,
msg_t *msg,
sip_t const *sip,
tp_name_t const *tpn)
{
su_md5_t md5[1];
uint8_t digest[SU_MD5_DIGEST_SIZE];
char branch[(SU_MD5_DIGEST_SIZE * 8 + 4)/ 5 + 1];
sip_route_t const *r;
assert(sip->sip_request);
if (!sip->sip_via)
return stateful_branch(msg_home(msg), sa);
su_md5_init(md5);
su_md5_str0update(md5, tpn->tpn_host);
su_md5_str0update(md5, tpn->tpn_port);
url_update(md5, sip->sip_request->rq_url);
if (sip->sip_call_id) {
su_md5_str0update(md5, sip->sip_call_id->i_id);
}
if (sip->sip_from) {
url_update(md5, sip->sip_from->a_url);
su_md5_stri0update(md5, sip->sip_from->a_tag);
}
if (sip->sip_to) {
url_update(md5, sip->sip_to->a_url);
/* XXX - some broken implementations include To tag in CANCEL */
/* su_md5_str0update(md5, sip->sip_to->a_tag); */
}
if (sip->sip_cseq) {
uint32_t cseq = htonl(sip->sip_cseq->cs_seq);
su_md5_update(md5, &cseq, sizeof(cseq));
}
for (r = sip->sip_route; r; r = r->r_next)
url_update(md5, r->r_url);
su_md5_digest(md5, digest);
msg_random_token(branch, sizeof(branch) - 1, digest, sizeof(digest));
return su_sprintf(msg_home(msg), "branch=z9hG4bK.%s", branch);
}
/* ====================================================================== */
/* 2) Transport interface */
/* Local prototypes */
static int agent_create_master_transport(nta_agent_t *self, tagi_t *tags);
static int agent_init_via(nta_agent_t *self, tport_t *primaries, int use_maddr);
static int agent_init_contact(nta_agent_t *self);
static void agent_recv_message(nta_agent_t *agent,
tport_t *tport,
msg_t *msg,
sip_via_t *tport_via,
su_time_t now);
static void agent_tp_error(nta_agent_t *agent,
tport_t *tport,
int errcode,
char const *remote);
static void agent_update_tport(nta_agent_t *agent, tport_t *);
/**For each transport, we have name used by tport module, SRV prefixes used
* for resolving, and NAPTR service/conversion.
*/
static
struct sipdns_tport {
char name[6]; /**< Named used by tport module */
char port[6]; /**< Default port number */
char prefix[14]; /**< Prefix for SRV domains */
char service[10]; /**< NAPTR service */
}
#define SIPDNS_TRANSPORTS (6)
const sipdns_tports[SIPDNS_TRANSPORTS] = {
{ "udp", "5060", "_sip._udp.", "SIP+D2U" },
{ "tcp", "5060", "_sip._tcp.", "SIP+D2T" },
{ "sctp", "5060", "_sip._sctp.", "SIP+D2S" },
{ "tls", "5061", "_sips._tcp.", "SIPS+D2T" },
{ "ws", "5080", "_sips._ws.", "SIP+D2W" },
{ "wss", "5081", "_sips._wss.", "SIPS+D2W" },
};
static char const * const tports_sip[] =
{
"udp", "tcp", "sctp", "ws", NULL
};
static char const * const tports_sips[] =
{
"tls", "wss", "ws", NULL
};
static tport_stack_class_t nta_agent_class[1] =
{{
sizeof(nta_agent_class),
agent_recv_message,
agent_tp_error,
nta_msg_create_for_transport,
agent_update_tport,
}};
/** Add a transport to the agent.
*
* Creates a new transport and binds it
* to the port specified by the @a uri. The @a uri must have sip: or sips:
* scheme or be a wildcard uri ("*"). The @a uri syntax allowed is as
* follows:
*
* @code url <scheme>:<host>[:<port>]<url-params> @endcode
* where <url-params> may be
* @code
* ;transport=<xxx>
* ;maddr=<actual addr>
* ;comp=sigcomp
* @endcode
*
* The scheme part determines which transports are used. "sip" implies UDP
* and TCP, "sips" TLS over TCP. In the future, more transports can be
* supported, for instance, "sip" can use SCTP or DCCP, "sips" DTLS or TLS
* over SCTP.
*
* The "host" part determines what address/domain name is used in @Contact.
* An "*" in "host" part is shorthand for any local IP address. 0.0.0.0
* means that the only the IPv4 addresses are used. [::] means that only
* the IPv6 addresses are used. If a domain name or a specific IP address
* is given as "host" part, an additional "maddr" parameter can be used to
* control which addresses are used by the stack when binding listen
* sockets for incoming requests.
*
* The "port" determines what port is used in contact, and to which port the
* stack binds in order to listen for incoming requests. Empty or missing
* port means that default port should be used (5060 for sip, 5061 for
* sips). An "*" in "port" part means any port, i.e., the stack binds to an
* ephemeral port.
*
* The "transport" parameter determines the transport protocol that is used
* and how they are preferred. If no protocol is specified, both UDP and TCP
* are used for SIP URL and TLS for SIPS URL. The preference can be
* indicated with a comma-separated list of transports, for instance,
* parameter @code transport=tcp,udp @endcode indicates that TCP is
* preferred to UDP.
*
* The "maddr" parameter determines to which address the stack binds in
* order to listen for incoming requests. An "*" in "maddr" parameter is
* shorthand for any local IP address. 0.0.0.0 means that only IPv4 sockets
* are created. [::] means that only IPv6 sockets are created.
*
* The "comp" parameter determines the supported compression protocol.
* Currently only sigcomp is supported (with suitable library).
*
* @par Examples:
* @code sip:172.21.40.24;maddr=* @endcode \n
* @code sip:172.21.40.24:50600;transport=TCP,UDP;comp=sigcomp @endcode \n
* @code sips:* @endcode
*
* @return
* On success, zero is returned. On error, -1 is returned, and @a errno is
* set appropriately.
*/
int nta_agent_add_tport(nta_agent_t *self,
url_string_t const *uri,
tag_type_t tag, tag_value_t value, ...)
{
url_t *url;
char tp[32];
char maddr[256];
char comp[32];
tp_name_t tpn[1] = {{ NULL }};
char const * const * tports = tports_sip;
int error;
ta_list ta;
if (self == NULL) {
su_seterrno(EINVAL);
return -1;
}
if (uri == NULL)
uri = (url_string_t *)"sip:*";
else if (url_string_p(uri) ?
strcmp(uri->us_str, "*") == 0 :
uri->us_url->url_type == url_any) {
uri = (url_string_t *)"sip:*:*";
}
if (!(url = url_hdup(self->sa_home, uri->us_url)) ||
(url->url_type != url_sip && url->url_type != url_sips)) {
if (url_string_p(uri))
SU_DEBUG_1(("nta: %s: invalid bind URL\n", uri->us_str));
else
SU_DEBUG_1(("nta: invalid bind URL\n" VA_NONE));
su_seterrno(EINVAL);
return -1;
}
tpn->tpn_canon = url->url_host;
tpn->tpn_host = url->url_host;
tpn->tpn_port = url_port(url);
if (url->url_type == url_sip) {
tpn->tpn_proto = "*";
tports = tports_sip;
if (!tpn->tpn_port || !tpn->tpn_port[0])
tpn->tpn_port = SIP_DEFAULT_SERV;
}
else {
assert(url->url_type == url_sips);
tpn->tpn_proto = "*";
tports = tports_sips;
if (!tpn->tpn_port || !tpn->tpn_port[0])
tpn->tpn_port = SIPS_DEFAULT_SERV;
}
if (url->url_params) {
if (url_param(url->url_params, "transport", tp, sizeof(tp)) > 0) {
if (strchr(tp, ',')) {
int i; char *t, *tps[9] = {0};
/* Split tp into transports */
for (i = 0, t = tp; t && i < 8; i++) {
tps[i] = t;
if ((t = strchr(t, ',')))
*t++ = '\0';
}
tps[i] = NULL;
tports = (char const * const *)tps;
} else {
tpn->tpn_proto = tp;
}
}
if (url_param(url->url_params, "maddr", maddr, sizeof(maddr)) > 0)
tpn->tpn_host = maddr;
if (url_param(url->url_params, "comp", comp, sizeof(comp)) > 0)
tpn->tpn_comp = comp;
if (tpn->tpn_comp &&
(nta_compressor_vtable == NULL ||
!su_casematch(tpn->tpn_comp, nta_compressor_vtable->ncv_name))) {
SU_DEBUG_1(("nta(%p): comp=%s not supported for " URL_PRINT_FORMAT "\n",
(void *)self, tpn->tpn_comp, URL_PRINT_ARGS(url)));
}
}
ta_start(ta, tag, value);
if (self->sa_tports == NULL) {
if (agent_create_master_transport(self, ta_args(ta)) < 0) {
error = su_errno();
SU_DEBUG_1(("nta: cannot create master transport: %s\n",
su_strerror(error)));
goto error;
}
}
if (tport_tbind(self->sa_tports, tpn, tports, ta_tags(ta)) < 0) {
error = su_errno();
SU_DEBUG_1(("nta: bind(%s:%s;transport=%s%s%s%s%s): %s\n",
tpn->tpn_canon, tpn->tpn_port, tpn->tpn_proto,
tpn->tpn_canon != tpn->tpn_host ? ";maddr=" : "",
tpn->tpn_canon != tpn->tpn_host ? tpn->tpn_host : "",
tpn->tpn_comp ? ";comp=" : "",
tpn->tpn_comp ? tpn->tpn_comp : "",
su_strerror(error)));
goto error;
}
else
SU_DEBUG_5(("nta: bound to (%s:%s;transport=%s%s%s%s%s)\n",
tpn->tpn_canon, tpn->tpn_port, tpn->tpn_proto,
tpn->tpn_canon != tpn->tpn_host ? ";maddr=" : "",
tpn->tpn_canon != tpn->tpn_host ? tpn->tpn_host : "",
tpn->tpn_comp ? ";comp=" : "",
tpn->tpn_comp ? tpn->tpn_comp : ""));
/* XXX - when to use maddr? */
if ((agent_init_via(self, tport_primaries(self->sa_tports), 0)) < 0) {
error = su_errno();
SU_DEBUG_1(("nta: cannot create Via headers\n" VA_NONE));
goto error;
}
else
SU_DEBUG_9(("nta: Via fields initialized\n" VA_NONE));
if ((agent_init_contact(self)) < 0) {
error = su_errno();
SU_DEBUG_1(("nta: cannot create Contact header\n" VA_NONE));
goto error;
}
else
SU_DEBUG_9(("nta: Contact header created\n" VA_NONE));
su_free(self->sa_home, url);
ta_end(ta);
return 0;
error:
ta_end(ta);
su_seterrno(error);
return -1;
}
static
int agent_create_master_transport(nta_agent_t *self, tagi_t *tags)
{
self->sa_tports =
tport_tcreate(self, nta_agent_class, self->sa_root,
TPTAG_IDLE(1800000),
TAG_NEXT(tags));
if (!self->sa_tports)
return -1;
SU_DEBUG_9(("nta: master transport created\n" VA_NONE));
return 0;
}
/** Initialize @Via headers. */
static
int agent_init_via(nta_agent_t *self, tport_t *primaries, int use_maddr)
{
sip_via_t *via = NULL, *new_via, *dup_via, *v, **vv = &via;
sip_via_t *new_vias, **next_new_via, *new_publics, **next_new_public;
tport_t *tp;
su_addrinfo_t const *ai;
su_home_t autohome[SU_HOME_AUTO_SIZE(2048)];
su_home_auto(autohome, sizeof autohome);
self->sa_tport_ip4 = 0;
self->sa_tport_ip6 = 0;
self->sa_tport_udp = 0;
self->sa_tport_tcp = 0;
self->sa_tport_sctp = 0;
self->sa_tport_tls = 0;
self->sa_tport_ws = 0;
self->sa_tport_wss = 0;
/* Set via fields for the tports */
for (tp = primaries; tp; tp = tport_next(tp)) {
int maddr;
tp_name_t tpn[1];
char const *comp = NULL;
*tpn = *tport_name(tp);
assert(tpn->tpn_proto);
assert(tpn->tpn_canon);
assert(tpn->tpn_host);
assert(tpn->tpn_port);
#if 0
if (getenv("SIP_UDP_CONNECT")
&& strcmp(tpn->tpn_proto, "udp") == 0)
tport_set_params(tp, TPTAG_CONNECT(1), TAG_END());
#endif
if (tport_has_ip4(tp)) self->sa_tport_ip4 = 1;
#if SU_HAVE_IN6
if (tport_has_ip6(tp)) self->sa_tport_ip6 = 1;
#endif
if (su_casematch(tpn->tpn_proto, "udp"))
self->sa_tport_udp = 1;
else if (su_casematch(tpn->tpn_proto, "tcp"))
self->sa_tport_tcp = 1;
else if (su_casematch(tpn->tpn_proto, "sctp"))
self->sa_tport_sctp = 1;
else if (su_casematch(tpn->tpn_proto, "ws"))
self->sa_tport_ws = 1;
else if (su_casematch(tpn->tpn_proto, "wss"))
self->sa_tport_wss = 1;
if (tport_has_tls(tp)) self->sa_tport_tls = 1;
ai = tport_get_address(tp);
for (; ai; ai = ai->ai_next) {
char host[TPORT_HOSTPORTSIZE] = "";
char sport[8];
char const *canon = ai->ai_canonname;
su_sockaddr_t *su = (void *)ai->ai_addr;
int port;
if (su) {
su_inet_ntop(su->su_family, SU_ADDR(su), host, sizeof host);
maddr = use_maddr && !su_casematch(canon, host);
port = ntohs(su->su_port);
}
else {
msg_random_token(host, 16, NULL, 0);
canon = strcat(host, ".is.invalid");
maddr = 0;
port = 0;
}
if (su_casenmatch(tpn->tpn_proto, "tls", 3)
? port == SIPS_DEFAULT_PORT
: port == SIP_DEFAULT_PORT)
port = 0;
snprintf(sport, sizeof sport, ":%u", port);
comp = tpn->tpn_comp;
SU_DEBUG_9(("nta: agent_init_via: "
"%s/%s %s%s%s%s%s%s (%s)\n",
SIP_VERSION_CURRENT, tpn->tpn_proto,
canon, port ? sport : "",
maddr ? ";maddr=" : "", maddr ? host : "",
comp ? ";comp=" : "", comp ? comp : "",
tpn->tpn_ident ? tpn->tpn_ident : "*"));
v = sip_via_format(autohome,
"%s/%s %s%s%s%s%s%s",
SIP_VERSION_CURRENT, tpn->tpn_proto,
canon, port ? sport : "",
maddr ? ";maddr=" : "", maddr ? host : "",
comp ? ";comp=" : "", comp ? comp : "");
if (v == NULL)
goto error;
v->v_comment = tpn->tpn_ident;
v->v_common->h_data = tp; /* Nasty trick */
*vv = v; vv = &(*vv)->v_next;
}
}
/* Duplicate the list bind to the transports */
new_via = sip_via_dup(self->sa_home, via);
/* Duplicate the complete list shown to the application */
dup_via = sip_via_dup(self->sa_home, via);
if (via && (!new_via || !dup_via)) {
msg_header_free(self->sa_home, (void *)new_via);
msg_header_free(self->sa_home, (void *)dup_via);
goto error;
}
new_vias = NULL, next_new_via = &new_vias;
new_publics = NULL, next_new_public = &new_publics;
/* Set via field magic for the tports */
for (tp = primaries; tp; tp = tport_next(tp)) {
assert(via->v_common->h_data == tp);
v = tport_magic(tp);
tport_set_magic(tp, new_via);
msg_header_free(self->sa_home, (void *)v);
if (tport_is_public(tp))
*next_new_public = dup_via;
else
*next_new_via = dup_via;
while (via->v_next && via->v_next->v_common->h_data == tp)
via = via->v_next, new_via = new_via->v_next, dup_via = dup_via->v_next;
via = via->v_next;
/* Break the link in via list between transports */
vv = &new_via->v_next, new_via = *vv, *vv = NULL;
vv = &dup_via->v_next, dup_via = *vv, *vv = NULL;
if (tport_is_public(tp))
while (*next_new_public) next_new_public = &(*next_new_public)->v_next;
else
while (*next_new_via) next_new_via = &(*next_new_via)->v_next;
}
assert(dup_via == NULL);
assert(new_via == NULL);
if (self->sa_tport_udp)
agent_set_udp_params(self, self->sa_udp_mtu);
v = self->sa_vias;
self->sa_vias = new_vias;
msg_header_free(self->sa_home, (void *)v);
v = self->sa_public_vias;
self->sa_public_vias = new_publics;
msg_header_free(self->sa_home, (void *)v);
su_home_deinit(autohome);
return 0;
error:
su_home_deinit(autohome);
return -1;
}
/** Initialize main contact header. */
static
int agent_init_contact(nta_agent_t *self)
{
sip_via_t const *v1, *v2;
char const *tp;
if (self->sa_contact)
return 0;
for (v1 = self->sa_vias ? self->sa_vias : self->sa_public_vias;
v1;
v1 = v1->v_next) {
if (host_is_ip_address(v1->v_host)) {
if (!host_is_local(v1->v_host))
break;
}
else if (!host_has_domain_invalid(v1->v_host)) {
break;
}
}
if (v1 == NULL)
v1 = self->sa_vias ? self->sa_vias : self->sa_public_vias;
if (!v1)
return -1;
tp = strrchr(v1->v_protocol, '/');
if (!tp++)
return -1;
v2 = v1->v_next;
if (v2 &&
su_casematch(v1->v_host, v2->v_host) &&
su_casematch(v1->v_port, v2->v_port)) {
char const *p1 = v1->v_protocol, *p2 = v2->v_protocol;
if (!su_casematch(p1, sip_transport_udp))
p1 = v2->v_protocol, p2 = v1->v_protocol;
if (su_casematch(p1, sip_transport_udp) &&
su_casematch(p2, sip_transport_tcp))
/* Do not include transport if we have both UDP and TCP */
tp = NULL;
}
self->sa_contact =
sip_contact_create_from_via_with_transport(self->sa_home, v1, NULL, tp);
if (!self->sa_contact)
return -1;
agent_tag_init(self);
return 0;
}
/** Return @Via line corresponging to tport. */
static
sip_via_t const *agent_tport_via(tport_t *tport)
{
sip_via_t *v = tport_magic(tport);
while (v && v->v_next)
v = v->v_next;
return v;
}
/** Insert @Via to a request message */
static
int outgoing_insert_via(nta_outgoing_t *orq,
sip_via_t const *via)
{
nta_agent_t *self = orq->orq_agent;
msg_t *msg = orq->orq_request;
sip_t *sip = sip_object(msg);
char const *branch = orq->orq_via_branch;
int already = orq->orq_user_via || orq->orq_via_added;
int user_via = orq->orq_user_via;
sip_via_t *v;
int clear = 0;
assert(sip); assert(via);
if (already && sip->sip_via) {
/* Use existing @Via */
v = sip->sip_via;
}
else if (msg && via && sip->sip_request &&
(v = sip_via_copy(msg_home(msg), via))) {
if (msg_header_insert(msg, (msg_pub_t *)sip, (msg_header_t *)v) < 0)
return -1;
orq->orq_via_added = 1;
}
else
return -1;
if (!v->v_rport &&
((self->sa_rport && v->v_protocol == sip_transport_udp) ||
(self->sa_tcp_rport && v->v_protocol == sip_transport_tcp) ||
(self->sa_tls_rport && v->v_protocol == sip_transport_tls)))
msg_header_add_param(msg_home(msg), v->v_common, "rport");
if (!orq->orq_tpn->tpn_comp)
msg_header_remove_param(v->v_common, "comp");
if (branch && branch != v->v_branch) {
char const *bvalue = branch + strcspn(branch, "=");
if (*bvalue) bvalue++;
if (!v->v_branch || !su_casematch(bvalue, v->v_branch))
msg_header_replace_param(msg_home(msg), v->v_common, branch);
}
if (!su_casematch(via->v_protocol, v->v_protocol))
clear = 1, v->v_protocol = via->v_protocol;
/* XXX - should we do this? */
if ((!user_via || !v->v_host) &&
!su_strmatch(via->v_host, v->v_host))
clear = 1, v->v_host = via->v_host;
if ((!user_via || !v->v_port ||
/* Replace port in user Via only if we use udp and no rport */
(v->v_protocol == sip_transport_udp && !v->v_rport &&
!orq->orq_stateless)) &&
!su_strmatch(via->v_port, v->v_port))
clear = 1, v->v_port = via->v_port;
if (clear)
msg_fragment_clear(v->v_common);
return 0;
}
/** Get destination name from @Via.
*
* If @a using_rport is non-null, try rport.
* If *using_rport is non-zero, try rport even if <protocol> is not UDP.
* If <protocol> is UDP, set *using_rport to zero.
*/
static
int nta_tpn_by_via(tp_name_t *tpn, sip_via_t const *v, int *using_rport)
{
if (!v)
return -1;
tpn->tpn_proto = sip_via_transport(v);
tpn->tpn_canon = v->v_host;
if (v->v_maddr)
tpn->tpn_host = v->v_maddr;
else if (v->v_received)
tpn->tpn_host = v->v_received;
else
tpn->tpn_host = v->v_host;
tpn->tpn_port = sip_via_port(v, using_rport);
tpn->tpn_comp = v->v_comp;
tpn->tpn_ident = NULL;
return 0;
}
/** Get transport name from URL. */
static int
nta_tpn_by_url(su_home_t *home,
tp_name_t *tpn,
char const **scheme,
char const **port,
url_string_t const *us)
{
url_t url[1];
isize_t n;
char *b;
n = url_xtra(us->us_url);
b = su_alloc(home, n);
if (b == NULL || url_dup(b, n, url, us->us_url) < 0) {
su_free(home, b);
return -1;
}
if (url->url_type != url_sip &&
url->url_type != url_sips &&
url->url_type != url_im &&
url->url_type != url_pres) {
su_free(home, b);
return -1;
}
SU_DEBUG_7(("nta: selecting scheme %s\n", url->url_scheme));
*scheme = url->url_scheme;
tpn->tpn_proto = NULL;
tpn->tpn_canon = url->url_host;
tpn->tpn_host = url->url_host;
if (url->url_params) {
for (b = (char *)url->url_params; b[0]; b += n) {
n = strcspn(b, ";");
if (n > 10 && su_casenmatch(b, "transport=", 10))
tpn->tpn_proto = b + 10;
else if (n > 5 && su_casenmatch(b, "comp=", 5))
tpn->tpn_comp = b + 5;
else if (n > 6 && su_casenmatch(b, "maddr=", 6))
tpn->tpn_host = b + 6;
if (b[n])
b[n++] = '\0';
}
}
if ((*port = url->url_port))
tpn->tpn_port = url->url_port;
tpn->tpn_ident = NULL;
if (tpn->tpn_proto) {
if (su_casematch(url->url_scheme, "sips") && su_casematch(tpn->tpn_proto, "ws")) {
tpn->tpn_proto = "wss";
}
return 1;
}
if (su_casematch(url->url_scheme, "sips"))
tpn->tpn_proto = "tls";
else
tpn->tpn_proto = "*";
return 0;
}
/** Handle transport errors. */
static
void agent_tp_error(nta_agent_t *agent,
tport_t *tport,
int errcode,
char const *remote)
{
su_llog(nta_log, 1,
"nta_agent: tport: %s%s%s\n",
remote ? remote : "", remote ? ": " : "",
su_strerror(errcode));
if (agent->sa_error_tport) {
agent->sa_error_tport(agent->sa_error_magic, agent, tport);
}
}
/** Handle updated transport addresses */
static void agent_update_tport(nta_agent_t *self, tport_t *tport)
{
/* Initialize local Vias first */
agent_init_via(self, tport_primaries(self->sa_tports), 0);
if (self->sa_update_tport) {
self->sa_update_tport(self->sa_update_magic, self);
}
else {
/* XXX - we should do something else? */
SU_DEBUG_3(("%s(%p): %s\n", "nta", (void *)self,
"transport address updated"));
}
}
/* ====================================================================== */
/* 3) Message dispatch */
static void agent_recv_request(nta_agent_t *agent,
msg_t *msg,
sip_t *sip,
tport_t *tport);
static int agent_check_request_via(nta_agent_t *agent,
msg_t *msg,
sip_t *sip,
sip_via_t *v,
tport_t *tport);
static int agent_aliases(nta_agent_t const *, url_t [], tport_t *);
static void agent_recv_response(nta_agent_t*, msg_t *, sip_t *,
sip_via_t *, tport_t*);
static void agent_recv_garbage(nta_agent_t*, msg_t*, tport_t*);
#if HAVE_SOFIA_SRESOLV
static void outgoing_resolve(nta_outgoing_t *orq,
int explicit_transport,
enum nta_res_order_e order);
su_inline void outgoing_cancel_resolver(nta_outgoing_t *orq);
su_inline void outgoing_destroy_resolver(nta_outgoing_t *orq);
static int outgoing_other_destinations(nta_outgoing_t const *orq);
static int outgoing_try_another(nta_outgoing_t *orq);
#else
#define outgoing_other_destinations(orq) (0)
#define outgoing_try_another(orq) (0)
#endif
/** Handle incoming message. */
static
void agent_recv_message(nta_agent_t *agent,
tport_t *tport,
msg_t *msg,
sip_via_t *tport_via,
su_time_t now)
{
sip_t *sip = sip_object(msg);
if (sip && sip->sip_request) {
agent_recv_request(agent, msg, sip, tport);
}
else if (sip && sip->sip_status) {
agent_recv_response(agent, msg, sip, tport_via, tport);
}
else {
agent_recv_garbage(agent, msg, tport);
}
}
/** @internal Handle incoming requests. */
static
void agent_recv_request(nta_agent_t *agent,
msg_t *msg,
sip_t *sip,
tport_t *tport)
{
nta_leg_t *leg;
nta_incoming_t *irq, *merge = NULL, *ack = NULL, *cancel = NULL;
sip_method_t method = sip->sip_request->rq_method;
char const *method_name = sip->sip_request->rq_method_name;
url_t url[1];
unsigned cseq = sip->sip_cseq ? sip->sip_cseq->cs_seq : 0;
int insane, errors, stream;
agent->sa_stats->as_recv_msg++;
agent->sa_stats->as_recv_request++;
SU_DEBUG_5(("nta: received %s " URL_PRINT_FORMAT " %s (CSeq %u)\n",
method_name,
URL_PRINT_ARGS(sip->sip_request->rq_url),
sip->sip_request->rq_version, cseq));
if (agent->sa_drop_prob && !tport_is_reliable(tport)) {
if ((unsigned)su_randint(0, 1000) < agent->sa_drop_prob) {
SU_DEBUG_5(("nta: %s (%u) is %s\n",
method_name, cseq, "dropped simulating packet loss"));
agent->sa_stats->as_drop_request++;
msg_destroy(msg);
return;
}
}
stream = tport_is_stream(tport);
/* Try to use compression on reverse direction if @Via has comp=sigcomp */
if (stream &&
sip->sip_via && sip->sip_via->v_comp &&
tport_can_send_sigcomp(tport) &&
tport_name(tport)->tpn_comp == NULL &&
tport_has_compression(tport_parent(tport), sip->sip_via->v_comp)) {
tport_set_compression(tport, sip->sip_via->v_comp);
}
if (sip->sip_flags & MSG_FLG_TOOLARGE) {
SU_DEBUG_5(("nta: %s (%u) is %s\n",
method_name, cseq, sip_413_Request_too_large));
agent->sa_stats->as_bad_request++;
mreply(agent, NULL, SIP_413_REQUEST_TOO_LARGE, msg,
tport, 1, stream, NULL,
TAG_END());
return;
}
insane = 0;
if (agent->sa_bad_req_mask != ~0U)
errors = msg_extract_errors(msg) & agent->sa_bad_req_mask;
else
errors = sip->sip_error != NULL;
if (errors ||
(sip->sip_flags & MSG_FLG_ERROR) /* Fatal error */ ||
(insane = (sip_sanity_check(sip) < 0))) {
sip_header_t const *h;
char const *badname = NULL, *phrase;
agent->sa_stats->as_bad_message++;
agent->sa_stats->as_bad_request++;
if (insane)
SU_DEBUG_5(("nta: %s (%u) %s\n", method_name, cseq,
"failed sanity check"));
for (h = (sip_header_t const *)sip->sip_error; h; h = h->sh_next) {
char const *bad;
if (h->sh_class == sip_error_class)
bad = h->sh_error->er_name;
else
bad = h->sh_class->hc_name;
if (bad)
SU_DEBUG_5(("nta: %s has bad %s header\n", method_name, bad));
if (!badname)
badname = bad;
}
if (sip->sip_via && method != sip_method_ack) {
msg_t *reply = nta_msg_create(agent, 0);
agent_check_request_via(agent, msg, sip, sip->sip_via, tport);
if (badname && reply)
phrase = su_sprintf(msg_home(reply), "Bad %s Header", badname);
else
phrase = sip_400_Bad_request;
SU_DEBUG_5(("nta: %s (%u) is %s\n", method_name, cseq, phrase));
mreply(agent, reply, 400, phrase, msg,
tport, 1, stream, NULL,
TAG_END());
}
else {
msg_destroy(msg);
if (stream) /* Send FIN */
tport_shutdown(tport, 1);
}
return;
}
if (!su_casematch(sip->sip_request->rq_version, sip_version_2_0)) {
agent->sa_stats->as_bad_request++;
agent->sa_stats->as_bad_message++;
SU_DEBUG_5(("nta: bad version %s for %s (%u)\n",
sip->sip_request->rq_version, method_name, cseq));
mreply(agent, NULL, SIP_505_VERSION_NOT_SUPPORTED, msg,
tport, 0, stream, NULL,
TAG_END());
return;
}
if (agent_check_request_via(agent, msg, sip, sip->sip_via, tport) < 0) {
agent->sa_stats->as_bad_message++;
agent->sa_stats->as_bad_request++;
SU_DEBUG_5(("nta: %s (%u) %s\n", method_name, cseq, "has invalid Via"));
msg_destroy(msg);
return;
}
/* First, try existing incoming requests */
irq = incoming_find(agent, sip, sip->sip_via,
agent->sa_merge_482 &&
!sip->sip_to->a_tag &&
method != sip_method_ack
? &merge
: NULL,
method == sip_method_ack ? &ack : NULL,
method == sip_method_cancel ? &cancel : NULL);
if (irq) {
/* Match - this is a retransmission */
SU_DEBUG_5(("nta: %s (%u) going to existing %s transaction\n",
method_name, cseq, irq->irq_rq->rq_method_name));
if (incoming_recv(irq, msg, sip, tport) >= 0)
return;
}
else if (ack) {
SU_DEBUG_5(("nta: %s (%u) is going to %s (%u)\n",
method_name, cseq,
ack->irq_cseq->cs_method_name, ack->irq_cseq->cs_seq));
if (incoming_ack(ack, msg, sip, tport) >= 0)
return;
}
else if (cancel) {
SU_DEBUG_5(("nta: %s (%u) is going to %s (%u)\n",
method_name, cseq,
cancel->irq_cseq->cs_method_name, cancel->irq_cseq->cs_seq));
if (incoming_cancel(cancel, msg, sip, tport) >= 0)
return;
}
else if (merge) {
SU_DEBUG_5(("nta: %s (%u) %s\n",
method_name, cseq, "is a merged request"));
request_merge(agent, msg, sip, tport, merge->irq_tag);
return;
}
if (method == sip_method_prack && sip->sip_rack) {
nta_reliable_t *rel = reliable_find(agent, sip);
if (rel) {
SU_DEBUG_5(("nta: %s (%u) is going to %s (%u)\n",
method_name, cseq,
rel->rel_irq->irq_cseq->cs_method_name,
rel->rel_irq->irq_cseq->cs_seq));
reliable_recv(rel, msg, sip, tport);
return;
}
}
*url = *sip->sip_request->rq_url;
url->url_params = NULL;
agent_aliases(agent, url, tport); /* canonize urls */
if (method != sip_method_subscribe && (leg = leg_find(agent,
method_name, url,
sip->sip_call_id,
sip->sip_from->a_tag,
sip->sip_to->a_tag))) {
/* Try existing dialog */
SU_DEBUG_5(("nta: %s (%u) %s\n",
method_name, cseq, "going to existing leg"));
leg_recv(leg, msg, sip, tport);
return;
}
else if (!agent->sa_is_stateless &&
(leg = dst_find(agent, url, method_name))) {
/* Dialogless legs - let application process transactions statefully */
SU_DEBUG_5(("nta: %s (%u) %s\n",
method_name, cseq, "going to a dialogless leg"));
leg_recv(leg, msg, sip, tport);
}
else if (!agent->sa_is_stateless && (leg = agent->sa_default_leg)) {
if (method == sip_method_invite &&
agent->sa_in.proceeding->q_length >= agent->sa_max_proceeding) {
SU_DEBUG_5(("nta: proceeding queue full for %s (%u)\n",
method_name, cseq));
mreply(agent, NULL, SIP_503_SERVICE_UNAVAILABLE, msg,
tport, 0, 0, NULL,
TAG_END());
return;
}
else {
SU_DEBUG_5(("nta: %s (%u) %s\n",
method_name, cseq, "going to a default leg"));
leg_recv(leg, msg, sip, tport);
}
}
else if (agent->sa_callback) {
/* Stateless processing for request */
agent->sa_stats->as_trless_request++;
SU_DEBUG_5(("nta: %s (%u) %s\n",
method_name, cseq, "to message callback"));
(void)agent->sa_callback(agent->sa_magic, agent, msg, sip);
}
else {
agent->sa_stats->as_trless_request++;
SU_DEBUG_5(("nta: %s (%u) %s\n",
method_name, cseq,
"not processed by application: returning 501"));
if (method != sip_method_ack)
mreply(agent, NULL, SIP_501_NOT_IMPLEMENTED, msg,
tport, 0, 0, NULL,
TAG_END());
else
msg_destroy(msg);
}
}
/** Check @Via header.
*
*/
static
int agent_check_request_via(nta_agent_t *agent,
msg_t *msg,
sip_t *sip,
sip_via_t *v,
tport_t *tport)
{
enum { receivedlen = sizeof("received=") - 1 };
char received[receivedlen + TPORT_HOSTPORTSIZE];
char *hostport = received + receivedlen;
char const *rport;
su_sockaddr_t const *from;
sip_via_t const *tpv = agent_tport_via(tport);
assert(tport); assert(msg); assert(sip);
assert(sip->sip_request); assert(tpv);
from = msg_addr(msg);
if (v == NULL) {
/* Make up a via line */
v = sip_via_format(msg_home(msg), "SIP/2.0/%s %s",
tport_name(tport)->tpn_proto,
tport_hostport(hostport, TPORT_HOSTPORTSIZE, from, 1));
msg_header_insert(msg, (msg_pub_t *)sip, (msg_header_t *)v);
return v ? 0 : -1;
}
if (!su_strmatch(v->v_protocol, tpv->v_protocol)) {
tport_hostport(hostport, TPORT_HOSTPORTSIZE, from, 1);
SU_DEBUG_1(("nta: Via check: invalid transport \"%s\" from %s\n",
v->v_protocol, hostport));
return -1;
}
if (v->v_received) {
/* Nasty, nasty */
tport_hostport(hostport, TPORT_HOSTPORTSIZE, from, 1);
SU_DEBUG_1(("nta: Via check: extra received=%s from %s\n",
v->v_received, hostport));
msg_header_remove_param(v->v_common, "received");
}
if (!tport_hostport(hostport, TPORT_HOSTPORTSIZE, from, 0))
return -1;
if (!su_casematch(hostport, v->v_host)) {
size_t rlen;
/* Add the "received" field */
memcpy(received, "received=", receivedlen);
if (hostport[0] == '[') {
rlen = strlen(hostport + 1) - 1;
memmove(hostport, hostport + 1, rlen);
hostport[rlen] = '\0';
}
msg_header_replace_param(msg_home(msg), v->v_common,
su_strdup(msg_home(msg), received));
SU_DEBUG_5(("nta: Via check: %s\n", received));
}
if (!agent->sa_server_rport) {
/*Xyzzy*/;
}
else if (v->v_rport) {
rport = su_sprintf(msg_home(msg), "rport=%u", ntohs(from->su_port));
msg_header_replace_param(msg_home(msg), v->v_common, rport);
}
else if (tport_is_tcp(tport)) {
rport = su_sprintf(msg_home(msg), "rport=%u", ntohs(from->su_port));
msg_header_replace_param(msg_home(msg), v->v_common, rport);
}
else if (agent->sa_server_rport == 2 ||
(agent->sa_server_rport == 3 && sip && sip->sip_user_agent &&
sip->sip_user_agent->g_string &&
(!strncasecmp(sip->sip_user_agent->g_string, "Polycom", 7) || !strncasecmp(sip->sip_user_agent->g_string, "KIRK Wireless Server", 20)))) {
rport = su_sprintf(msg_home(msg), "rport=%u", ntohs(from->su_port));
msg_header_replace_param(msg_home(msg), v->v_common, rport);
}
return 0;
}
/** @internal Handle aliases of local node.
*
* Return true if @a url is modified.
*/
static
int agent_aliases(nta_agent_t const *agent, url_t url[], tport_t *tport)
{
sip_contact_t *m;
sip_via_t const *lv;
char const *tport_port = "";
if (!url->url_host)
return 0;
if (tport)
tport_port = tport_name(tport)->tpn_port;
assert(tport_port);
for (m = agent->sa_aliases ? agent->sa_aliases : agent->sa_contact;
m;
m = m->m_next) {
if (url->url_type != m->m_url->url_type)
continue;
if (host_cmp(url->url_host, m->m_url->url_host))
continue;
if (url->url_port == NULL)
break;
if (m->m_url->url_port) {
if (strcmp(url->url_port, m->m_url->url_port))
continue;
} else {
if (strcmp(url->url_port, tport_port))
continue;
}
break;
}
if (!m)
return 0;
SU_DEBUG_7(("nta: canonizing " URL_PRINT_FORMAT " with %s\n",
URL_PRINT_ARGS(url),
agent->sa_aliases ? "aliases" : "contact"));
url->url_host = "%";
if (agent->sa_aliases) {
url->url_type = agent->sa_aliases->m_url->url_type;
url->url_scheme = agent->sa_aliases->m_url->url_scheme;
url->url_port = agent->sa_aliases->m_url->url_port;
return 1;
}
else {
/* Canonize the request URL port */
if (tport) {
lv = agent_tport_via(tport_parent(tport)); assert(lv);
if (lv->v_port)
/* Add non-default port */
url->url_port = lv->v_port;
return 1;
}
if (su_strmatch(url->url_port, url_port_default((enum url_type_e)url->url_type)) ||
su_strmatch(url->url_port, ""))
/* Remove default or empty port */
url->url_port = NULL;
return 0;
}
}
/** @internal Handle incoming responses. */
static
void agent_recv_response(nta_agent_t *agent,
msg_t *msg,
sip_t *sip,
sip_via_t *tport_via,
tport_t *tport)
{
int status = sip->sip_status->st_status;
int errors;
char const *phrase = sip->sip_status->st_phrase;
char const *method =
sip->sip_cseq ? sip->sip_cseq->cs_method_name : "<UNKNOWN>";
uint32_t cseq = sip->sip_cseq ? sip->sip_cseq->cs_seq : 0;
nta_outgoing_t *orq;
su_home_t *home;
char const *branch = NONE;
agent->sa_stats->as_recv_msg++;
agent->sa_stats->as_recv_response++;
SU_DEBUG_5(("nta: received %03d %s for %s (%u)\n",
status, phrase, method, cseq));
if (agent->sa_drop_prob && !tport_is_reliable(tport)) {
if ((unsigned)su_randint(0, 1000) < agent->sa_drop_prob) {
SU_DEBUG_5(("nta: %03d %s %s\n",
status, phrase, "dropped simulating packet loss"));
agent->sa_stats->as_drop_response++;
msg_destroy(msg);
return;
}
}
if (agent->sa_bad_resp_mask)
errors = msg_extract_errors(msg) & agent->sa_bad_resp_mask;
else
errors = sip->sip_error != NULL;
if (errors ||
sip_sanity_check(sip) < 0) {
sip_header_t const *h;
agent->sa_stats->as_bad_response++;
agent->sa_stats->as_bad_message++;
SU_DEBUG_5(("nta: %03d %s %s\n", status, phrase,
errors
? "has fatal syntax errors"
: "failed sanity check"));
for (h = (sip_header_t const *)sip->sip_error; h; h = h->sh_next) {
if (h->sh_class->hc_name) {
SU_DEBUG_5(("nta: %03d has bad %s header\n", status,
h->sh_class->hc_name));
}
}
msg_destroy(msg);
return;
}
if (!su_casematch(sip->sip_status->st_version, sip_version_2_0)) {
agent->sa_stats->as_bad_response++;
agent->sa_stats->as_bad_message++;
SU_DEBUG_5(("nta: bad version %s %03d %s\n",
sip->sip_status->st_version, status, phrase));
msg_destroy(msg);
return;
}
if (sip->sip_cseq->cs_method == sip_method_ack) {
/* Drop response messages to ACK */
agent->sa_stats->as_bad_response++;
agent->sa_stats->as_bad_message++;
SU_DEBUG_5(("nta: %03d %s %s\n", status, phrase, "is response to ACK"));
msg_destroy(msg);
return;
}
/* XXX - should check if msg should be discarded based on via? */
if ((orq = outgoing_find(agent, msg, sip, sip->sip_via))) {
SU_DEBUG_5(("nta: %03d %s %s\n",
status, phrase, "is going to a transaction"));
/* RFC3263 4.3 "503 error response" */
if(agent->sa_srv_503 && status == 503 && outgoing_other_destinations(orq)) {
SU_DEBUG_5(("%s(%p): <%03d> for <%s>, %s\n", "nta", (void *)orq, status, method, "try next after timeout"));
home = msg_home(msg);
if (agent->sa_is_stateless)
branch = stateless_branch(agent, msg, sip, orq->orq_tpn);
else
branch = stateful_branch(home, agent);
orq->orq_branch = branch;
orq->orq_via_branch = branch;
outgoing_try_another(orq);
return;
}
if (outgoing_recv(orq, status, msg, sip) == 0)
return;
}
agent->sa_stats->as_trless_response++;
if ((orq = agent->sa_default_outgoing)) {
SU_DEBUG_5(("nta: %03d %s %s\n", status, phrase,
"to the default transaction"));
outgoing_default_recv(orq, status, msg, sip);
return;
}
else if (agent->sa_callback) {
SU_DEBUG_5(("nta: %03d %s %s\n", status, phrase, "to message callback"));
/*
* Store message and transport to hook for the duration of the callback
* so that the transport can be obtained by nta_transport().
*/
(void)agent->sa_callback(agent->sa_magic, agent, msg, sip);
return;
}
if (sip->sip_cseq->cs_method == sip_method_invite
&& 200 <= sip->sip_status->st_status
&& sip->sip_status->st_status < 300
/* Exactly one Via header, belonging to us */
&& sip->sip_via && !sip->sip_via->v_next
&& agent_has_via(agent, sip->sip_via)) {
agent->sa_stats->as_trless_200++;
}
SU_DEBUG_5(("nta: %03d %s %s\n", status, phrase, "was discarded"));
msg_destroy(msg);
}
/** @internal Agent receives garbage */
static
void agent_recv_garbage(nta_agent_t *agent,
msg_t *msg,
tport_t *tport)
{
agent->sa_stats->as_recv_msg++;
agent->sa_stats->as_bad_message++;
#if SU_DEBUG >= 3
if (nta_log->log_level >= 3) {
tp_name_t tpn[1];
tport_delivered_from(tport, msg, tpn);
SU_DEBUG_3(("nta_agent: received garbage from " TPN_FORMAT "\n",
TPN_ARGS(tpn)));
}
#endif
msg_destroy(msg);
}
/* ====================================================================== */
/* 4) Message handling - create, complete, destroy */
/** Create a new message belonging to the agent */
msg_t *nta_msg_create(nta_agent_t *agent, int flags)
{
msg_t *msg;
if (agent == NULL)
return su_seterrno(EINVAL), NULL;
msg = msg_create(agent->sa_mclass, agent->sa_flags | flags);
if (agent->sa_preload)
su_home_preload(msg_home(msg), 1, agent->sa_preload);
return msg;
}
/** Create a new message for transport */
msg_t *nta_msg_create_for_transport(nta_agent_t *agent, int flags,
char const data[], usize_t dlen,
tport_t const *tport, tp_client_t *via)
{
msg_t *msg = msg_create(agent->sa_mclass, agent->sa_flags | flags);
msg_maxsize(msg, agent->sa_maxsize);
if (agent->sa_preload)
su_home_preload(msg_home(msg), 1, dlen + agent->sa_preload);
return msg;
}
/** Complete a message. */
int nta_msg_complete(msg_t *msg)
{
return sip_complete_message(msg);
}
/** Discard a message */
void nta_msg_discard(nta_agent_t *agent, msg_t *msg)
{
msg_destroy(msg);
}
/** Check if the headers are from response generated locally by NTA. */
int nta_sip_is_internal(sip_t const *sip)
{
return
sip == NULL /* No message generated */
|| (sip->sip_flags & NTA_INTERNAL_MSG) == NTA_INTERNAL_MSG;
}
/** Check if the message is internally generated by NTA. */
int nta_msg_is_internal(msg_t const *msg)
{
return msg_get_flags(msg, NTA_INTERNAL_MSG) == NTA_INTERNAL_MSG;
}
/** Check if the message is internally generated by NTA.
*
* @deprecated Use nta_msg_is_internal() instead
*/
int nta_is_internal_msg(msg_t const *msg) { return nta_msg_is_internal(msg); }
/* ====================================================================== */
/* 5) Stateless operation */
/**Forward a request or response message.
*
* @note
* The ownership of @a msg is taken over by the function even if the
* function fails.
*/
int nta_msg_tsend(nta_agent_t *agent, msg_t *msg, url_string_t const *u,
tag_type_t tag, tag_value_t value, ...)
{
int retval = -1;
ta_list ta;
sip_t *sip = sip_object(msg);
tp_name_t tpn[1] = {{ NULL }};
char const *what;
if (!sip) {
msg_destroy(msg);
return -1;
}
what =
sip->sip_status ? "nta_msg_tsend(response)" :
sip->sip_request ? "nta_msg_tsend(request)" :
"nta_msg_tsend()";
ta_start(ta, tag, value);
if (sip_add_tl(msg, sip, ta_tags(ta)) < 0)
SU_DEBUG_3(("%s: cannot add headers\n", what));
else if (sip->sip_status) {
tport_t *tport = NULL;
int *use_rport = NULL;
int retry_without_rport = 0;
struct sigcomp_compartment *cc; cc = NONE;
if (agent->sa_server_rport)
use_rport = &retry_without_rport, retry_without_rport = 1;
tl_gets(ta_args(ta),
NTATAG_TPORT_REF(tport),
IF_SIGCOMP_TPTAG_COMPARTMENT_REF(cc)
/* NTATAG_INCOMPLETE_REF(incomplete), */
TAG_END());
if (!sip->sip_separator &&
!(sip->sip_separator = sip_separator_create(msg_home(msg))))
SU_DEBUG_3(("%s: cannot create sip_separator\n", what));
else if (msg_serialize(msg, (msg_pub_t *)sip) != 0)
SU_DEBUG_3(("%s: sip_serialize() failed\n", what));
else if (!sip_via_remove(msg, sip))
SU_DEBUG_3(("%s: cannot remove Via\n", what));
else if (nta_tpn_by_via(tpn, sip->sip_via, use_rport) < 0)
SU_DEBUG_3(("%s: bad via\n", what));
else {
if (!tport)
tport = tport_by_name(agent->sa_tports, tpn);
if (!tport)
tport = tport_by_protocol(agent->sa_tports, tpn->tpn_proto);
if (retry_without_rport)
tpn->tpn_port = sip_via_port(sip->sip_via, NULL);
if (tport && tpn->tpn_comp && cc == NONE)
cc = agent_compression_compartment(agent, tport, tpn, -1);
if (tport_tsend(tport, msg, tpn,
IF_SIGCOMP_TPTAG_COMPARTMENT(cc)
TPTAG_MTU(INT_MAX), ta_tags(ta), TAG_END())) {
agent->sa_stats->as_sent_msg++;
agent->sa_stats->as_sent_response++;
retval = 0;
}
else {
SU_DEBUG_3(("%s: send fails\n", what));
}
}
}
else {
/* Send request */
if (outgoing_create(agent, NULL, NULL, u, NULL, msg_ref_create(msg),
NTATAG_STATELESS(1),
ta_tags(ta)))
retval = 0;
}
if (retval == 0)
SU_DEBUG_5(("%s\n", what));
ta_end(ta);
msg_destroy(msg);
return retval;
}
/** Reply to a request message.
*
* @param agent nta agent object
* @param req_msg request message
* @param status status code
* @param phrase status phrase (may be NULL if status code is well-known)
* @param tag, value, ... optional additional headers terminated by TAG_END()
*
* @retval 0 when succesful
* @retval -1 upon an error
*
* @note
* The ownership of @a msg is taken over by the function even if the
* function fails.
*/
int nta_msg_treply(nta_agent_t *agent,
msg_t *req_msg,
int status, char const *phrase,
tag_type_t tag, tag_value_t value, ...)
{
int retval;
ta_list ta;
ta_start(ta, tag, value);
retval = mreply(agent, NULL, status, phrase, req_msg,
NULL, 0, 0, NULL,
ta_tags(ta));
ta_end(ta);
return retval;
}
/**Reply to the request message.
*
* @note
* The ownership of @a msg is taken over by the function even if the
* function fails.
*/
int nta_msg_mreply(nta_agent_t *agent,
msg_t *reply, sip_t *sip,
int status, char const *phrase,
msg_t *req_msg,
tag_type_t tag, tag_value_t value, ...)
{
int retval = -1;
ta_list ta;
ta_start(ta, tag, value);
retval = mreply(agent, reply, status, phrase, req_msg,
NULL, 0, 0, NULL,
ta_tags(ta));
ta_end(ta);
return retval;
}
static
int mreply(nta_agent_t *agent,
msg_t *reply,
int status, char const *phrase,
msg_t *req_msg,
tport_t *tport,
int incomplete,
int sdwn_after,
char const *to_tag,
tag_type_t tag, tag_value_t value, ...)
{
ta_list ta;
sip_t *sip;
int *use_rport = NULL;
int retry_without_rport = 0;
tp_name_t tpn[1];
int retval = -1;
if (!agent)
return -1;
if (agent->sa_server_rport)
use_rport = &retry_without_rport, retry_without_rport = 1;
ta_start(ta, tag, value);
tl_gets(ta_args(ta), NTATAG_TPORT_REF(tport), TAG_END());
if (reply == NULL) {
reply = nta_msg_create(agent, 0);
}
sip = sip_object(reply);
if (!sip) {
SU_DEBUG_3(("%s: cannot create response msg\n", __func__));
}
else if (sip_add_tl(reply, sip, ta_tags(ta)) < 0) {
SU_DEBUG_3(("%s: cannot add user headers\n", __func__));
}
else if (complete_response(reply, status, phrase, req_msg) < 0 &&
!incomplete) {
SU_DEBUG_3(("%s: cannot complete message\n", __func__));
}
else if (sip->sip_status && sip->sip_status->st_status > 100 &&
sip->sip_to && !sip->sip_to->a_tag &&
(to_tag == NONE ? 0 :
to_tag != NULL
? sip_to_tag(msg_home(reply), sip->sip_to, to_tag) < 0
: sip_to_tag(msg_home(reply), sip->sip_to,
nta_agent_newtag(msg_home(reply), "tag=%s", agent)) < 0)) {
SU_DEBUG_3(("%s: cannot add To tag\n", __func__));
}
else if (nta_tpn_by_via(tpn, sip->sip_via, use_rport) < 0) {
SU_DEBUG_3(("%s: no Via\n", __func__));
}
else {
struct sigcomp_compartment *cc = NONE;
if (tport == NULL)
tport = tport_delivered_by(agent->sa_tports, req_msg);
if (!tport) {
tport_t *primary = tport_by_protocol(agent->sa_tports, tpn->tpn_proto);
tport = tport_by_name(primary, tpn);
if (!tport)
tport = primary;
}
if (retry_without_rport)
tpn->tpn_port = sip_via_port(sip->sip_via, NULL);
if (tport && tpn->tpn_comp) {
tl_gets(ta_args(ta), TPTAG_COMPARTMENT_REF(cc),
/* XXX - should also check ntatag_sigcomp_close() */
TAG_END());
if (cc == NONE)
cc = agent_compression_compartment(agent, tport, tpn, -1);
if (cc != NULL && cc != NONE &&
tport_delivered_with_comp(tport, req_msg, NULL) != -1) {
agent_accept_compressed(agent, req_msg, cc);
}
}
if (tport_tsend(tport, reply, tpn,
IF_SIGCOMP_TPTAG_COMPARTMENT(cc)
TPTAG_MTU(INT_MAX),
TPTAG_SDWN_AFTER(sdwn_after),
ta_tags(ta))) {
agent->sa_stats->as_sent_msg++;
agent->sa_stats->as_sent_response++;
retval = 0; /* Success! */
}
else {
SU_DEBUG_3(("%s: send fails\n", __func__));
}
}
msg_destroy(reply);
msg_destroy(req_msg);
ta_end(ta);
return retval;
}
/** Add headers from the request to the response message. */
static
int complete_response(msg_t *response,
int status, char const *phrase,
msg_t *request)
{
su_home_t *home = msg_home(response);
sip_t *response_sip = sip_object(response);
sip_t const *request_sip = sip_object(request);
int incomplete = 0;
if (!response_sip || !request_sip || !request_sip->sip_request)
return -1;
if (!response_sip->sip_status)
response_sip->sip_status = sip_status_create(home, status, phrase, NULL);
if (!response_sip->sip_via)
response_sip->sip_via = sip_via_dup(home, request_sip->sip_via);
if (!response_sip->sip_from)
response_sip->sip_from = sip_from_dup(home, request_sip->sip_from);
if (!response_sip->sip_to)
response_sip->sip_to = sip_to_dup(home, request_sip->sip_to);
if (!response_sip->sip_call_id)
response_sip->sip_call_id =
sip_call_id_dup(home, request_sip->sip_call_id);
if (!response_sip->sip_cseq)
response_sip->sip_cseq = sip_cseq_dup(home, request_sip->sip_cseq);
if (!response_sip->sip_record_route && request_sip->sip_record_route)
sip_add_dup(response, response_sip, (void*)request_sip->sip_record_route);
incomplete = sip_complete_message(response) < 0;
msg_serialize(response, (msg_pub_t *)response_sip);
if (incomplete ||
!response_sip->sip_status ||
!response_sip->sip_via ||
!response_sip->sip_from ||
!response_sip->sip_to ||
!response_sip->sip_call_id ||
!response_sip->sip_cseq ||
!response_sip->sip_content_length ||
!response_sip->sip_separator ||
(request_sip->sip_record_route && !response_sip->sip_record_route))
return -1;
return 0;
}
/** ACK and BYE an unknown 200 OK response to INVITE.
*
* A UAS may still return a 2XX series response to client request after the
* client transactions has been terminated. In that case, the UAC can not
* really accept the call. This function was used to accept and immediately
* terminate such a call.
*
* @deprecated This was a bad idea: see sf.net bug #1750691. It can be used
* to amplify DoS attacks. Let UAS take care of retransmission timeout and
* let it terminate the session. As of @VERSION_1_12_7, this function just
* returns -1.
*/
int nta_msg_ackbye(nta_agent_t *agent, msg_t *msg)
{
sip_t *sip = sip_object(msg);
msg_t *amsg = nta_msg_create(agent, 0);
sip_t *asip = sip_object(amsg);
msg_t *bmsg = NULL;
sip_t *bsip;
url_string_t const *ruri;
nta_outgoing_t *ack = NULL, *bye = NULL;
sip_cseq_t *cseq;
sip_request_t *rq;
sip_route_t *route = NULL, *r, r0[1];
su_home_t *home = msg_home(amsg);
if (asip == NULL)
return -1;
sip_add_tl(amsg, asip,
SIPTAG_TO(sip->sip_to),
SIPTAG_FROM(sip->sip_from),
SIPTAG_CALL_ID(sip->sip_call_id),
TAG_END());
if (sip->sip_contact) {
ruri = (url_string_t const *)sip->sip_contact->m_url;
} else {
ruri = (url_string_t const *)sip->sip_to->a_url;
}
/* Reverse (and fix) record route */
route = sip_route_reverse(home, sip->sip_record_route);
if (route && !url_has_param(route->r_url, "lr")) {
for (r = route; r->r_next; r = r->r_next)
;
/* Append r-uri */
*sip_route_init(r0)->r_url = *ruri->us_url;
r->r_next = sip_route_dup(home, r0);
/* Use topmost route as request-uri */
ruri = (url_string_t const *)route->r_url;
route = route->r_next;
}
msg_header_insert(amsg, (msg_pub_t *)asip, (msg_header_t *)route);
bmsg = msg_copy(amsg); bsip = sip_object(bmsg);
if (!(cseq = sip_cseq_create(home, sip->sip_cseq->cs_seq, SIP_METHOD_ACK)))
goto err;
else
msg_header_insert(amsg, (msg_pub_t *)asip, (msg_header_t *)cseq);
if (!(rq = sip_request_create(home, SIP_METHOD_ACK, ruri, NULL)))
goto err;
else
msg_header_insert(amsg, (msg_pub_t *)asip, (msg_header_t *)rq);
if (!(ack = nta_outgoing_mcreate(agent, NULL, NULL, NULL, amsg,
NTATAG_ACK_BRANCH(sip->sip_via->v_branch),
NTATAG_STATELESS(1),
TAG_END())))
goto err;
else
nta_outgoing_destroy(ack);
home = msg_home(bmsg);
if (!(cseq = sip_cseq_create(home, 0x7fffffff, SIP_METHOD_BYE)))
goto err;
else
msg_header_insert(bmsg, (msg_pub_t *)bsip, (msg_header_t *)cseq);
if (!(rq = sip_request_create(home, SIP_METHOD_BYE, ruri, NULL)))
goto err;
else
msg_header_insert(bmsg, (msg_pub_t *)bsip, (msg_header_t *)rq);
if (!(bye = nta_outgoing_mcreate(agent, NULL, NULL, NULL, bmsg,
NTATAG_STATELESS(1),
TAG_END())))
goto err;
msg_destroy(msg);
return 0;
err:
msg_destroy(bmsg);
msg_destroy(amsg);
return -1;
}
/**Complete a request with values from dialog.
*
* Complete a request message @a msg belonging to a dialog associated with
* @a leg. It increments the local @CSeq value, adds @CallID, @To, @From and
* @Route headers (if there is such headers present in @a leg), and creates
* a new request line object from @a method, @a method_name and @a
* request_uri.
*
* @param msg pointer to a request message object
* @param leg pointer to a #nta_leg_t object
* @param method request method number or #sip_method_unknown
* @param method_name method name (if @a method == #sip_method_unknown)
* @param request_uri request URI
*
* If @a request_uri contains query part, the query part is converted as SIP
* headers and added to the request.
*
* @retval 0 when successful
* @retval -1 upon an error
*
* @sa nta_outgoing_mcreate(), nta_outgoing_tcreate()
*/
int nta_msg_request_complete(msg_t *msg,
nta_leg_t *leg,
sip_method_t method,
char const *method_name,
url_string_t const *request_uri)
{
su_home_t *home = msg_home(msg);
sip_t *sip = sip_object(msg);
sip_to_t const *to;
uint32_t seq;
url_t reg_url[1];
url_string_t const *original = request_uri;
if (!leg || !msg || !sip)
return -1;
if (!sip->sip_route && leg->leg_route) {
if (leg->leg_loose_route) {
if (leg->leg_target) {
request_uri = (url_string_t *)leg->leg_target->m_url;
}
sip->sip_route = sip_route_dup(home, leg->leg_route);
}
else {
sip_route_t **rr;
request_uri = (url_string_t *)leg->leg_route->r_url;
sip->sip_route = sip_route_dup(home, leg->leg_route->r_next);
for (rr = &sip->sip_route; *rr; rr = &(*rr)->r_next)
;
if (leg->leg_target)
*rr = sip_route_dup(home, (sip_route_t *)leg->leg_target);
}
}
else if (leg->leg_target)
request_uri = (url_string_t *)leg->leg_target->m_url;
if (!request_uri && sip->sip_request)
request_uri = (url_string_t *)sip->sip_request->rq_url;
to = sip->sip_to ? sip->sip_to : leg->leg_remote;
if (!request_uri && to) {
if (method != sip_method_register)
request_uri = (url_string_t *)to->a_url;
else {
/* Remove user part from REGISTER requests */
*reg_url = *to->a_url;
reg_url->url_user = reg_url->url_password = NULL;
request_uri = (url_string_t *)reg_url;
}
}
if (!request_uri)
return -1;
if (method || method_name) {
sip_request_t *rq = sip->sip_request;
int use_headers =
request_uri == original || (url_t *)request_uri == rq->rq_url;
if (!rq
|| request_uri != (url_string_t *)rq->rq_url
|| method != rq->rq_method
|| !su_strmatch(method_name, rq->rq_method_name))
rq = NULL;
if (rq == NULL) {
rq = sip_request_create(home, method, method_name, request_uri, NULL);
if (msg_header_insert(msg, (msg_pub_t *)sip, (msg_header_t *)rq) < 0)
return -1;
}
/* @RFC3261 table 1 (page 152):
* Req-URI cannot contain method parameter or headers
*/
if (rq->rq_url->url_params) {
rq->rq_url->url_params =
url_strip_param_string((char *)rq->rq_url->url_params, "method");
sip_fragment_clear(rq->rq_common);
}
if (rq->rq_url->url_headers) {
if (use_headers) {
char *s = url_query_as_header_string(msg_home(msg),
rq->rq_url->url_headers);
if (!s)
return -1;
msg_header_parse_str(msg, (msg_pub_t*)sip, s);
}
rq->rq_url->url_headers = NULL, sip_fragment_clear(rq->rq_common);
}
}
if (!sip->sip_request)
return -1;
if (!sip->sip_max_forwards)
sip_add_dup(msg, sip, (sip_header_t *)leg->leg_agent->sa_max_forwards);
if (!sip->sip_from)
sip->sip_from = sip_from_dup(home, leg->leg_local);
else if (leg->leg_local && leg->leg_local->a_tag &&
(!sip->sip_from->a_tag ||
!su_casematch(sip->sip_from->a_tag, leg->leg_local->a_tag)))
sip_from_tag(home, sip->sip_from, leg->leg_local->a_tag);
if (sip->sip_from && !sip->sip_from->a_tag) {
sip_fragment_clear(sip->sip_from->a_common);
sip_from_add_param(home, sip->sip_from,
nta_agent_newtag(home, "tag=%s", leg->leg_agent));
}
if (sip->sip_to) {
if (leg->leg_remote && leg->leg_remote->a_tag)
sip_to_tag(home, sip->sip_to, leg->leg_remote->a_tag);
}
else if (leg->leg_remote) {
sip->sip_to = sip_to_dup(home, leg->leg_remote);
}
else {
sip_to_t *to = sip_to_create(home, request_uri);
if (to) sip_aor_strip(to->a_url);
sip->sip_to = to;
}
if (!sip->sip_from || !sip->sip_from || !sip->sip_to)
return -1;
method = sip->sip_request->rq_method;
method_name = sip->sip_request->rq_method_name;
if (!leg->leg_id && sip->sip_cseq)
seq = sip->sip_cseq->cs_seq;
else if (method == sip_method_ack || method == sip_method_cancel)
/* Dangerous - we may do PRACK/UPDATE meanwhile */
seq = sip->sip_cseq ? sip->sip_cseq->cs_seq : leg->leg_seq;
else if (leg->leg_seq)
seq = ++leg->leg_seq;
else if (sip->sip_cseq) /* Obtain initial value from existing CSeq header */
seq = leg->leg_seq = sip->sip_cseq->cs_seq;
else
seq = leg->leg_seq = (sip_now() >> 1) & 0x7ffffff;
if (!sip->sip_call_id) {
if (leg->leg_id)
sip->sip_call_id = sip_call_id_dup(home, leg->leg_id);
else
sip->sip_call_id = sip_call_id_create(home, NULL);
}
if (!sip->sip_cseq ||
seq != sip->sip_cseq->cs_seq ||
method != sip->sip_cseq->cs_method ||
!su_strmatch(method_name, sip->sip_cseq->cs_method_name)) {
sip_cseq_t *cseq = sip_cseq_create(home, seq, method, method_name);
if (msg_header_insert(msg, (msg_pub_t *)sip, (msg_header_t *)cseq) < 0)
return -1;
}
return 0;
}
/* ====================================================================== */
/* 6) Dialogs (legs) */
static void leg_insert(nta_agent_t *agent, nta_leg_t *leg);
static int leg_route(nta_leg_t *leg,
sip_record_route_t const *route,
sip_record_route_t const *reverse,
sip_contact_t const *contact,
int reroute);
static int leg_callback_default(nta_leg_magic_t*, nta_leg_t*,
nta_incoming_t*, sip_t const *);
#define HTABLE_HASH_LEG(leg) ((leg)->leg_hash)
HTABLE_BODIES_WITH(leg_htable, lht, nta_leg_t, HTABLE_HASH_LEG, size_t, hash_value_t);
su_inline
hash_value_t hash_istring(char const *, char const *, hash_value_t);
/**@typedef nta_request_f
*
* Callback for incoming requests
*
* This is a callback function invoked by NTA for each incoming SIP request.
*
* @param lmagic call leg context
* @param leg call leg handle
* @param ireq incoming request
* @param sip incoming request contents
*
* @retval 100..699
* NTA constructs a reply message with given error code and corresponding
* standard phrase, then sends the reply.
*
* @retval 0
* The application takes care of sending (or not sending) the reply.
*
* @retval other
* All other return values will be interpreted as
* @e 500 @e Internal @e server @e error.
*/
/**
* Create a new leg object.
*
* Creates a leg object, which is used to represent dialogs, partial dialogs
* (for example, in case of REGISTER), and destinations within a particular
* NTA object.
*
* When a leg is created, a callback pointer and a application context is
* provided. All other parameters are optional.
*
* @param agent agent object
* @param callback function which is called for each
* incoming request belonging to this leg
* @param magic call leg context
* @param tag,value,... optional extra headers in taglist
*
* When a leg representing dialog is created, the tags SIPTAG_CALL_ID(),
* SIPTAG_FROM(), SIPTAG_TO(), and SIPTAG_CSEQ() (for local @CSeq number) are used
* to establish dialog context. The SIPTAG_FROM() is used to pass local
* address (@From header when making a call, @To header when answering
* to a call) to the newly created leg. Respectively, the SIPTAG_TO() is
* used to pass remote address (@To header when making a call, @From
* header when answering to a call).
*
* If there is a (preloaded) route associated with the leg, SIPTAG_ROUTE()
* and NTATAG_TARGET() can be used. A client or server can also set the
* route using @RecordRoute and @Contact headers from a response or
* request message with the functions nta_leg_client_route() and
* nta_leg_server_route(), respectively.
*
* When a leg representing a local destination is created, the tags
* NTATAG_NO_DIALOG(1), NTATAG_METHOD(), and URLTAG_URL() are used. When a
* request with matching request-URI (URLTAG_URL()) and method
* (NTATAG_METHOD()) is received, it is passed to the callback function
* provided with the leg.
*
* @sa nta_leg_stateful(), nta_leg_bind(),
* nta_leg_tag(), nta_leg_rtag(),
* nta_leg_client_route(), nta_leg_server_route(),
* nta_leg_destroy(), nta_outgoing_tcreate(), and nta_request_f().
*
* @TAGS
* NTATAG_NO_DIALOG(), NTATAG_STATELESS(), NTATAG_METHOD(),
* URLTAG_URL(), SIPTAG_CALL_ID(), SIPTAG_CALL_ID_STR(), SIPTAG_FROM(),
* SIPTAG_FROM_STR(), SIPTAG_TO(), SIPTAG_TO_STR(), SIPTAG_ROUTE(),
* NTATAG_TARGET() and SIPTAG_CSEQ().
*
*/
nta_leg_t *nta_leg_tcreate(nta_agent_t *agent,
nta_request_f *callback,
nta_leg_magic_t *magic,
tag_type_t tag, tag_value_t value, ...)
{
sip_route_t const *route = NULL;
sip_contact_t const *contact = NULL;
sip_cseq_t const *cs = NULL;
sip_call_id_t const *i = NULL;
sip_from_t const *from = NULL;
sip_to_t const *to = NULL;
char const *method = NULL;
char const *i_str = NULL, *to_str = NULL, *from_str = NULL, *cs_str = NULL;
url_string_t const *url_string = NULL;
int no_dialog = 0;
unsigned rseq = 0;
/* RFC 3261 section 12.2.1.1 */
uint32_t seq = 0;
ta_list ta;
nta_leg_t *leg;
su_home_t *home;
url_t *url;
char const *what = NULL;
if (agent == NULL)
return su_seterrno(EINVAL), NULL;
ta_start(ta, tag, value);
tl_gets(ta_args(ta),
NTATAG_NO_DIALOG_REF(no_dialog),
NTATAG_METHOD_REF(method),
URLTAG_URL_REF(url_string),
SIPTAG_CALL_ID_REF(i),
SIPTAG_CALL_ID_STR_REF(i_str),
SIPTAG_FROM_REF(from),
SIPTAG_FROM_STR_REF(from_str),
SIPTAG_TO_REF(to),
SIPTAG_TO_STR_REF(to_str),
SIPTAG_ROUTE_REF(route),
NTATAG_TARGET_REF(contact),
NTATAG_REMOTE_CSEQ_REF(rseq),
SIPTAG_CSEQ_REF(cs),
SIPTAG_CSEQ_STR_REF(cs_str),
TAG_END());
ta_end(ta);
if (cs)
seq = cs->cs_seq;
else if (cs_str)
seq = strtoul(cs_str, (char **)&cs_str, 10);
if (i == NONE) /* Magic value, used for compatibility */
no_dialog = 1;
if (!(leg = su_home_clone(NULL, sizeof(*leg))))
return NULL;
home = leg->leg_home;
leg->leg_agent = agent;
nta_leg_bind(leg, callback, magic);
if (from) {
/* Now this is kludge */
leg->leg_local_is_to = sip_is_to((sip_header_t*)from);
leg->leg_local = sip_to_dup(home, from);
}
else if (from_str)
leg->leg_local = sip_to_make(home, from_str);
if (to && no_dialog) {
/* Remove tag, if any */
sip_to_t to0[1]; *to0 = *to; to0->a_params = NULL;
leg->leg_remote = sip_from_dup(home, to0);
}
else if (to)
leg->leg_remote = sip_from_dup(home, to);
else if (to_str)
leg->leg_remote = sip_from_make(home, to_str);
if (route && route != NONE)
leg->leg_route = sip_route_dup(home, route), leg->leg_route_set = 1;
if (contact && contact != NONE) {
sip_contact_t m[1];
sip_contact_init(m);
*m->m_url = *contact->m_url;
m->m_url->url_headers = NULL;
leg->leg_target = sip_contact_dup(home, m);
}
url = url_hdup(home, url_string->us_url);
/* Match to local hosts */
if (url && agent_aliases(agent, url, NULL)) {
url_t *changed = url_hdup(home, url);
su_free(home, url);
url = changed;
}
leg->leg_rseq = rseq;
leg->leg_seq = seq;
leg->leg_url = url;
if (from && from != NONE && leg->leg_local == NULL) {
what = "cannot duplicate local address";
goto err;
}
else if (to && to != NONE && leg->leg_remote == NULL) {
what = "cannot duplicate remote address";
goto err;
}
else if (route && route != NONE && leg->leg_route == NULL) {
what = "cannot duplicate route";
goto err;
}
else if (contact && contact != NONE && leg->leg_target == NULL) {
what = "cannot duplicate target";
goto err;
}
else if (url_string && leg->leg_url == NULL) {
what = "cannot duplicate local destination";
goto err;
}
if (!no_dialog) {
if (!leg->leg_local || !leg->leg_remote) {
/* To and/or From header missing */
if (leg->leg_remote)
what = "Missing local dialog address";
else if (leg->leg_local)
what = "Missing remote dialog address";
else
what = "Missing dialog addresses";
goto err;
}
leg->leg_dialog = 1;
if (i != NULL)
leg->leg_id = sip_call_id_dup(home, i);
else if (i_str != NULL)
leg->leg_id = sip_call_id_make(home, i_str);
else
leg->leg_id = sip_call_id_create(home, NULL);
if (!leg->leg_id) {
what = "cannot create Call-ID";
goto err;
}
leg->leg_hash = leg->leg_id->i_hash;
}
else if (url) {
/* This is "default leg" with a destination URL. */
hash_value_t hash = 0;
if (method) {
leg->leg_method = su_strdup(home, method);
}
#if 0
else if (url->url_params) {
int len = url_param(url->url_params, "method", NULL, 0);
if (len) {
char *tmp = su_alloc(home, len);
leg->leg_method = tmp;
url_param(url->url_params, "method", tmp, len);
}
}
#endif
if (url->url_user && strcmp(url->url_user, "") == 0)
url->url_user = "%"; /* Match to any user */
hash = hash_istring(url->url_scheme, ":", 0);
hash = hash_istring(url->url_host, "", hash);
hash = hash_istring(url->url_user, "@", hash);
leg->leg_hash = hash;
}
else {
/* This is "default leg" without a destination URL. */
if (agent->sa_default_leg) {
SU_DEBUG_1(("%s(): %s\n", "nta_leg_tcreate", "tried to create second default leg"));
su_seterrno(EEXIST);
goto err;
}
else {
agent->sa_default_leg = leg;
}
return leg;
}
if (url) {
/* Parameters are ignored when comparing incoming URLs */
url->url_params = NULL;
}
leg_insert(agent, leg);
SU_DEBUG_9(("%s(%p)\n", "nta_leg_tcreate", (void *)leg));
return leg;
err:
if (what)
SU_DEBUG_9(("%s(): %s\n", "nta_leg_tcreate", what));
su_home_zap(leg->leg_home);
return NULL;
}
/** Return the default leg, if any */
nta_leg_t *nta_default_leg(nta_agent_t const *agent)
{
return agent ? agent->sa_default_leg : NULL;
}
/**
* Insert a call leg to agent.
*/
static
void leg_insert(nta_agent_t *sa, nta_leg_t *leg)
{
leg_htable_t *leg_hash;
assert(leg);
assert(sa);
if (leg->leg_dialog)
leg_hash = sa->sa_dialogs;
else
leg_hash = sa->sa_defaults;
if (leg_htable_is_full(leg_hash)) {
leg_htable_resize(sa->sa_home, leg_hash, 0);
assert(leg_hash->lht_table);
SU_DEBUG_7(("nta: resized%s leg hash to "MOD_ZU"\n",
leg->leg_dialog ? "" : " default", leg_hash->lht_size));
}
/* Insert entry into hash table (before other legs with same hash) */
leg_htable_insert(leg_hash, leg);
}
/**
* Destroy a leg.
*
* @param leg leg to be destroyed
*/
void nta_leg_destroy(nta_leg_t *leg)
{
SU_DEBUG_9(("nta_leg_destroy(%p)\n", (void *)leg));
if (leg) {
leg_htable_t *leg_hash;
nta_agent_t *sa = leg->leg_agent;
assert(sa);
if (leg->leg_dialog) {
assert(sa->sa_dialogs);
leg_hash = sa->sa_dialogs;
}
else if (leg != sa->sa_default_leg) {
assert(sa->sa_defaults);
leg_hash = sa->sa_defaults;
}
else {
sa->sa_default_leg = NULL;
leg_hash = NULL;
}
if (leg_hash)
leg_htable_remove(leg_hash, leg);
leg_free(sa, leg);
}
}
static
void leg_free(nta_agent_t *sa, nta_leg_t *leg)
{
//su_free(sa->sa_home, leg);
su_home_unref((su_home_t *)leg);
}
/** Return application context for the leg */
nta_leg_magic_t *nta_leg_magic(nta_leg_t const *leg,
nta_request_f *callback)
{
if (leg)
if (!callback || leg->leg_callback == callback)
return leg->leg_magic;
return NULL;
}
/**Bind a callback function and context to a leg object.
*
* Change the callback function and context pointer attached to a leg
* object.
*
* @param leg leg object to be bound
* @param callback new callback function (or NULL if no callback is desired)
* @param magic new context pointer
*/
void nta_leg_bind(nta_leg_t *leg,
nta_request_f *callback,
nta_leg_magic_t *magic)
{
if (leg) {
if (callback)
leg->leg_callback = callback;
else
leg->leg_callback = leg_callback_default;
leg->leg_magic = magic;
}
}
/** Add a local tag to the leg.
*
* @param leg leg to be tagged
* @param tag tag to be added (if NULL, a tag generated by @b NTA is added)
*
* @return
* Pointer to tag if successful, NULL otherwise.
*/
char const *nta_leg_tag(nta_leg_t *leg, char const *tag)
{
if (!leg || !leg->leg_local)
return su_seterrno(EINVAL), NULL;
if (tag && strchr(tag, '='))
tag = strchr(tag, '=') + 1;
/* If there already is a tag,
return NULL if it does not match with new one */
if (leg->leg_local->a_tag) {
if (tag == NULL || su_casematch(tag, leg->leg_local->a_tag))
return leg->leg_local->a_tag;
else
return NULL;
}
if (tag) {
if (sip_to_tag(leg->leg_home, leg->leg_local, tag) < 0)
return NULL;
leg->leg_tagged = 1;
return leg->leg_local->a_tag;
}
tag = nta_agent_newtag(leg->leg_home, "tag=%s", leg->leg_agent);
if (!tag || sip_to_add_param(leg->leg_home, leg->leg_local, tag) < 0)
return NULL;
leg->leg_tagged = 1;
return leg->leg_local->a_tag;
}
/** Get local tag. */
char const *nta_leg_get_tag(nta_leg_t const *leg)
{
if (leg && leg->leg_local)
return leg->leg_local->a_tag;
else
return NULL;
}
/** Add a remote tag to the leg.
*
* @note No remote tag is ever generated.
*
* @param leg leg to be tagged
* @param tag tag to be added (@b must be non-NULL)
*
* @return
* Pointer to tag if successful, NULL otherwise.
*/
char const *nta_leg_rtag(nta_leg_t *leg, char const *tag)
{
/* Add a tag parameter, unless there already is a tag */
if (leg && leg->leg_remote && tag) {
if (sip_from_tag(leg->leg_home, leg->leg_remote, tag) < 0)
return NULL;
}
if (leg && leg->leg_remote)
return leg->leg_remote->a_tag;
else
return NULL;
}
/** Get remote tag. */
char const *nta_leg_get_rtag(nta_leg_t const *leg)
{
if (leg && leg->leg_remote)
return leg->leg_remote->a_tag;
else
return NULL;
}
/** Get local request sequence number. */
uint32_t nta_leg_get_seq(nta_leg_t const *leg)
{
return leg ? leg->leg_seq : 0;
}
/** Get remote request sequence number. */
uint32_t nta_leg_get_rseq(nta_leg_t const *leg)
{
return leg ? leg->leg_rseq : 0;
}
/** Save target and route set at UAC side.
*
* @sa nta_leg_client_reroute(), nta_leg_server_route(), @RFC3261 section 12.1.2
*
* @bug Allows modifying the route set after initial transaction, if initial
* transaction had no @RecordRoute headers.
*
* @deprecated Use nta_leg_client_reroute() instead.
*/
int nta_leg_client_route(nta_leg_t *leg,
sip_record_route_t const *route,
sip_contact_t const *contact)
{
return leg_route(leg, NULL, route, contact, 0);
}
/** Save target and route set at UAC side.
*
* If @a initial is true, the route set is modified even if it has been set
* earlier.
*
* @param leg pointer to dialog leg
* @param route @RecordRoute headers from response
* @param contact @Contact header from response
* @param initial true if response to initial transaction
*
* @sa nta_leg_client_route(), nta_leg_server_route(), @RFC3261 section 12.1.2
*
* @NEW_1_12_11
*/
int nta_leg_client_reroute(nta_leg_t *leg,
sip_record_route_t const *route,
sip_contact_t const *contact,
int initial)
{
return leg_route(leg, NULL, route, contact, initial ? 2 : 1);
}
/** Save target and route set at UAS side.
*
* @param leg pointer to dialog leg
* @param route @RecordRoute headers from request
* @param contact @Contact header from request
*
* @sa nta_leg_client_reroute(), @RFC3261 section 12.1.1
*/
int nta_leg_server_route(nta_leg_t *leg,
sip_record_route_t const *route,
sip_contact_t const *contact)
{
return leg_route(leg, route, NULL, contact, 1);
}
/** Return route components. */
int nta_leg_get_route(nta_leg_t *leg,
sip_route_t const **return_route,
sip_contact_t const **return_target)
{
if (!leg)
return -1;
if (return_route)
*return_route = leg->leg_route;
if (return_target)
*return_target = leg->leg_target;
return 0;
}
/** Generate @Replaces header.
*
* @since New in @VERSION_1_12_2.
*/
sip_replaces_t *
nta_leg_make_replaces(nta_leg_t *leg,
su_home_t *home,
int early_only)
{
char const *from_tag, *to_tag;
if (!leg)
return NULL;
if (!leg->leg_dialog || !leg->leg_local || !leg->leg_remote || !leg->leg_id)
return NULL;
from_tag = leg->leg_local->a_tag; if (!from_tag) from_tag = "0";
to_tag = leg->leg_remote->a_tag; if (!to_tag) to_tag = "0";
return sip_replaces_format(home, "%s;from-tag=%s;to-tag=%s%s",
leg->leg_id->i_id, from_tag, to_tag,
early_only ? ";early-only" : "");
}
/** Get dialog leg by @Replaces header.
*
* @since New in @VERSION_1_12_2.
*/
nta_leg_t *
nta_leg_by_replaces(nta_agent_t *sa, sip_replaces_t const *rp)
{
nta_leg_t *leg = NULL;
if (sa && rp && rp->rp_call_id && rp->rp_from_tag && rp->rp_to_tag) {
char const *from_tag = rp->rp_from_tag, *to_tag = rp->rp_to_tag;
sip_call_id_t id[1];
sip_call_id_init(id);
id->i_hash = msg_hash_string(id->i_id = rp->rp_call_id);
leg = leg_find(sa, NULL, NULL, id, from_tag, to_tag);
if (leg == NULL && strcmp(from_tag, "0") == 0)
leg = leg_find(sa, NULL, NULL, id, NULL, to_tag);
if (leg == NULL && strcmp(to_tag, "0") == 0)
leg = leg_find(sa, NULL, NULL, id, from_tag, NULL);
}
return leg;
}
/**@internal
* Find a leg corresponding to the request message.
*
*/
static nta_leg_t *
leg_find_call_id(nta_agent_t const *sa,
sip_call_id_t const *i)
{
hash_value_t hash = i->i_hash;
leg_htable_t const *lht = sa->sa_dialogs;
nta_leg_t **ll, *leg = NULL;
for (ll = leg_htable_hash(lht, hash);
(leg = *ll);
ll = leg_htable_next(lht, ll)) {
sip_call_id_t const *leg_i = leg->leg_id;
if (leg->leg_hash != hash)
continue;
if (strcmp(leg_i->i_id, i->i_id) != 0)
continue;
return leg;
}
return leg;
}
/** Get dialog leg by @CallID.
*
* @note Usually there should be only single dialog per @CallID on
* User-Agents. However, proxies may fork requests initiating the dialog and
* result in multiple calls per @CallID.
*
* @since New in @VERSION_1_12_9.
*/
nta_leg_t *
nta_leg_by_call_id(nta_agent_t *sa, const char *call_id)
{
nta_leg_t *leg = NULL;
if (call_id) {
sip_call_id_t id[1];
sip_call_id_init(id);
id->i_hash = msg_hash_string(id->i_id = call_id);
leg = leg_find_call_id(sa, id);
}
return leg;
}
/** Calculate a simple case-insensitive hash over a string */
su_inline
hash_value_t hash_istring(char const *s, char const *term, hash_value_t hash)
{
if (s) {
for (; *s; s++) {
unsigned char c = *s;
if ('A' <= c && c <= 'Z')
c += 'a' - 'A';
hash = 38501U * (hash + c);
}
for (s = term; *s; s++) {
unsigned char c = *s;
hash = 38501U * (hash + c);
}
}
return hash;
}
/** @internal Handle requests intended for this leg. */
static
void leg_recv(nta_leg_t *leg, msg_t *msg, sip_t *sip, tport_t *tport)
{
nta_agent_t *agent = leg->leg_agent;
nta_incoming_t *irq;
sip_method_t method = sip->sip_request->rq_method;
char const *method_name = sip->sip_request->rq_method_name;
char const *tag = NULL;
int status;
if (leg->leg_local)
tag = leg->leg_local->a_tag;
if (leg->leg_dialog)
agent->sa_stats->as_dialog_tr++;
/* RFC-3262 section 3 (page 4) */
if (agent->sa_is_a_uas && method == sip_method_prack) {
mreply(agent, NULL, 481, "No such response", msg,
tport, 0, 0, NULL,
TAG_END());
return;
}
if (!(irq = incoming_create(agent, msg, sip, tport, tag))) {
SU_DEBUG_3(("nta: leg_recv(%p): cannot create transaction for %s\n",
(void *)leg, method_name));
mreply(agent, NULL, SIP_500_INTERNAL_SERVER_ERROR, msg,
tport, 0, 0, NULL,
TAG_END());
return;
}
irq->irq_in_callback = 1;
status = incoming_callback(leg, irq, sip);
irq->irq_in_callback = 0;
if (irq->irq_destroyed) {
if (irq->irq_terminated) {
incoming_free(irq);
return;
}
if (status < 200)
status = 500;
}
if (status == 0)
return;
if (status < 100 || status > 699) {
SU_DEBUG_3(("nta_leg(%p): invalid status %03d from callback\n",
(void *)leg, status));
status = 500;
}
else if (method == sip_method_invite && status >= 200 && status < 300) {
SU_DEBUG_3(("nta_leg(%p): invalid INVITE status %03d from callback\n",
(void *)leg, status));
status = 500;
}
if (status >= 100 && irq->irq_status < 200)
nta_incoming_treply(irq, status, NULL, TAG_END());
if (status >= 200)
nta_incoming_destroy(irq);
}
/**Compare two SIP from/to fields.
*
* @retval nonzero if matching.
* @retval zero if not matching.
*/
su_inline
int addr_cmp(url_t const *a, url_t const *b)
{
if (b == NULL)
return 0;
else
return
host_cmp(a->url_host, b->url_host) ||
su_strcmp(a->url_port, b->url_port) ||
su_strcmp(a->url_user, b->url_user);
}
/** Get a leg by dialog.
*
* Search for a dialog leg from agent's hash table. The matching rules based
* on parameters are as follows:
*
* @param agent pointer to agent object
* @param request_uri if non-NULL, and there is destination URI
* associated with the dialog, these URIs must match
* @param call_id if non-NULL, must match with @CallID header contents
* @param remote_tag if there is remote tag
* associated with dialog, @a remote_tag must match
* @param remote_uri ignored
* @param local_tag if non-NULL and there is local tag associated with leg,
* it must math
* @param local_uri ignored
*
* @note
* If @a remote_tag or @a local_tag is an empty string (""), the tag is
* ignored when matching legs.
*/
nta_leg_t *nta_leg_by_dialog(nta_agent_t const *agent,
url_t const *request_uri,
sip_call_id_t const *call_id,
char const *remote_tag,
url_t const *remote_uri,
char const *local_tag,
url_t const *local_uri)
{
void *to_be_freed = NULL;
url_t *url;
url_t url0[1];
nta_leg_t *leg;
if (!agent || !call_id)
return su_seterrno(EINVAL), NULL;
if (request_uri == NULL) {
url = NULL;
}
else if (URL_IS_STRING(request_uri)) {
/* accept a string as URL */
to_be_freed = url = url_hdup(NULL, request_uri);
}
else {
*url0 = *request_uri, url = url0;
}
if (url) {
url->url_params = NULL;
agent_aliases(agent, url, NULL); /* canonize url */
}
if (remote_tag && remote_tag[0] == '\0')
remote_tag = NULL;
if (local_tag && local_tag[0] == '\0')
local_tag = NULL;
leg = leg_find(agent,
NULL, url,
call_id,
remote_tag,
local_tag);
if (to_be_freed) su_free(NULL, to_be_freed);
return leg;
}
/**@internal
* Find a leg corresponding to the request message.
*
* A leg matches to message if leg_match_request() returns true ("Call-ID",
* "To"-tag, and "From"-tag match).
*/
static
nta_leg_t *leg_find(nta_agent_t const *sa,
char const *method_name,
url_t const *request_uri,
sip_call_id_t const *i,
char const *from_tag,
char const *to_tag)
{
hash_value_t hash = i->i_hash;
leg_htable_t const *lht = sa->sa_dialogs;
nta_leg_t **ll, *leg, *loose_match = NULL;
for (ll = leg_htable_hash(lht, hash);
(leg = *ll);
ll = leg_htable_next(lht, ll)) {
sip_call_id_t const *leg_i = leg->leg_id;
char const *remote_tag = leg->leg_remote->a_tag;
char const *local_tag = leg->leg_local->a_tag;
url_t const *leg_url = leg->leg_url;
char const *leg_method = leg->leg_method;
if (leg->leg_hash != hash)
continue;
if (strcmp(leg_i->i_id, i->i_id) != 0)
continue;
/* Do not match if the incoming To has tag, but the local does not */
if (!local_tag && to_tag)
continue;
/*
* Do not match if incoming To has no tag and we have local tag
* and the tag has been there from the beginning.
*/
if (local_tag && !to_tag && !leg->leg_tagged)
continue;
/* Do not match if incoming From has no tag but remote has a tag */
if (remote_tag && !from_tag)
continue;
/* Avoid matching with itself */
if (!remote_tag != !from_tag && !local_tag != !to_tag)
continue;
if (local_tag && to_tag && !su_casematch(local_tag, to_tag) && to_tag[0])
continue;
if (remote_tag && from_tag && !su_casematch(remote_tag, from_tag) && from_tag[0])
continue;
if (leg_url && request_uri && url_cmp(leg_url, request_uri))
continue;
if (leg_method && method_name && !su_casematch(method_name, leg_method))
continue;
/* Perfect match if both local and To have tag
* or local does not have tag.
*/
if ((!local_tag || to_tag))
return leg;
if (loose_match == NULL)
loose_match = leg;
}
return loose_match;
}
/** Get leg by destination */
nta_leg_t *nta_leg_by_uri(nta_agent_t const *agent, url_string_t const *us)
{
url_t *url;
nta_leg_t *leg = NULL;
if (!agent)
return NULL;
if (!us)
return agent->sa_default_leg;
url = url_hdup(NULL, us->us_url);
if (url) {
agent_aliases(agent, url, NULL);
leg = dst_find(agent, url, NULL);
su_free(NULL, url);
}
return leg;
}
/** Find a non-dialog leg corresponding to the request uri u0 */
static
nta_leg_t *dst_find(nta_agent_t const *sa,
url_t const *u0,
char const *method_name)
{
hash_value_t hash, hash2;
leg_htable_t const *lht = sa->sa_defaults;
nta_leg_t **ll, *leg, *loose_match = NULL;
int again;
url_t url[1];
*url = *u0;
hash = hash_istring(url->url_scheme, ":", 0);
hash = hash_istring(url->url_host, "", hash);
hash2 = hash_istring("%", "@", hash);
hash = hash_istring(url->url_user, "@", hash);
/* First round, search with user name */
/* Second round, search without user name */
do {
for (ll = leg_htable_hash(lht, hash);
(leg = *ll);
ll = leg_htable_next(lht, ll)) {
if (leg->leg_hash != hash)
continue;
if (url_cmp(url, leg->leg_url))
continue;
if (!method_name) {
if (leg->leg_method)
continue;
return leg;
}
else if (leg->leg_method) {
if (!su_casematch(method_name, leg->leg_method))
continue;
return leg;
}
loose_match = leg;
}
if (loose_match)
return loose_match;
again = 0;
if (url->url_user && strcmp(url->url_user, "%")) {
url->url_user = "%";
hash = hash2;
again = 1;
}
} while (again);
return NULL;
}
/** Set leg route and target URL.
*
* Sets the leg route and contact using the @RecordRoute and @Contact
* headers.
*
* @param reroute - allow rerouting
* - if 1, follow @RFC3261 semantics
* - if 2, response to initial transaction)
*/
static
int leg_route(nta_leg_t *leg,
sip_record_route_t const *route,
sip_record_route_t const *reverse,
sip_contact_t const *contact,
int reroute)
{
su_home_t *home = leg->leg_home;
sip_route_t *r, r0[1], *old;
int route_is_set;
if (!leg)
return -1;
if (route == NULL && reverse == NULL && contact == NULL)
return 0;
sip_route_init(r0);
route_is_set = reroute ? leg->leg_route_set : leg->leg_route != NULL;
if (route_is_set && reroute <= 1) {
r = leg->leg_route;
}
else if (route) {
r = sip_route_fixdup(home, route); if (!r) return -1;
}
else if (reverse) {
r = sip_route_reverse(home, reverse); if (!r) return -1;
}
else
r = NULL;
#ifdef NTA_STRICT_ROUTING
/*
* Handle Contact according to the RFC2543bis04 sections 16.1, 16.2 and 16.4.
*/
if (contact) {
*r0->r_url = *contact->m_url;
if (!(m_r = sip_route_dup(leg->leg_home, r0)))
return -1;
/* Append, but replace last entry if it was generated from contact */
for (rr = &r; *rr; rr = &(*rr)->r_next)
if (leg->leg_contact_set && (*rr)->r_next == NULL)
break;
}
else
rr = NULL;
if (rr) {
if (*rr)
su_free(leg->leg_home, *rr);
*rr = m_r;
}
if (m_r != NULL)
leg->leg_contact_set = 1;
#else
if (r && r->r_url->url_params)
leg->leg_loose_route = url_has_param(r->r_url, "lr");
if (contact) {
sip_contact_t *target, m[1], *m0;
sip_contact_init(m);
*m->m_url = *contact->m_url;
m->m_url->url_headers = NULL;
target = sip_contact_dup(leg->leg_home, m);
if (target && target->m_url->url_params) {
/* Remove ttl, method. @RFC3261 table 1, page 152 */
char *p = (char *)target->m_url->url_params;
p = url_strip_param_string(p, "method");
p = url_strip_param_string(p, "ttl");
target->m_url->url_params = p;
}
m0 = leg->leg_target, leg->leg_target = target;
if (m0)
su_free(leg->leg_home, m0);
}
#endif
old = leg->leg_route;
leg->leg_route = r;
if (old && old != r)
msg_header_free(leg->leg_home, (msg_header_t *)old);
leg->leg_route_set = 1;
return 0;
}
/** @internal Default leg callback. */
static int
leg_callback_default(nta_leg_magic_t *magic,
nta_leg_t *leg,
nta_incoming_t *irq,
sip_t const *sip)
{
nta_incoming_treply(irq,
SIP_501_NOT_IMPLEMENTED,
TAG_END());
return 501;
}
/* ====================================================================== */
/* 7) Server-side (incoming) transactions */
#define HTABLE_HASH_IRQ(irq) ((irq)->irq_hash)
HTABLE_BODIES_WITH(incoming_htable, iht, nta_incoming_t, HTABLE_HASH_IRQ,
size_t, hash_value_t);
static void incoming_insert(nta_agent_t *agent,
incoming_queue_t *queue,
nta_incoming_t *irq);
su_inline int incoming_is_queued(nta_incoming_t const *irq);
su_inline void incoming_queue(incoming_queue_t *queue, nta_incoming_t *);
su_inline void incoming_remove(nta_incoming_t *irq);
su_inline void incoming_set_timer(nta_incoming_t *, uint32_t interval);
su_inline void incoming_reset_timer(nta_incoming_t *);
su_inline size_t incoming_mass_destroy(nta_agent_t *, incoming_queue_t *);
static int incoming_set_params(nta_incoming_t *irq, tagi_t const *tags);
su_inline
int incoming_set_compartment(nta_incoming_t *irq, tport_t *tport, msg_t *msg,
int create_if_needed);
su_inline nta_incoming_t
*incoming_call_callback(nta_incoming_t *, msg_t *, sip_t *);
su_inline int incoming_final_failed(nta_incoming_t *irq, msg_t *);
static void incoming_retransmit_reply(nta_incoming_t *irq, tport_t *tport);
/** Create a default server transaction.
*
* The default server transaction is used by a proxy to forward responses
* statelessly.
*
* @param agent pointer to agent object
*
* @retval pointer to default server transaction object
* @retval NULL if failed
*/
nta_incoming_t *nta_incoming_default(nta_agent_t *agent)
{
msg_t *msg;
su_home_t *home;
nta_incoming_t *irq;
if (agent == NULL)
return su_seterrno(EFAULT), NULL;
if (agent->sa_default_incoming)
return su_seterrno(EEXIST), NULL;
msg = nta_msg_create(agent, 0);
if (!msg)
return NULL;
irq = su_zalloc(home = msg_home(msg), sizeof(*irq));
if (!irq)
return (void)msg_destroy(msg), NULL;
irq->irq_home = home;
irq->irq_request = NULL;
irq->irq_agent = agent;
irq->irq_received = agent_now(agent);
irq->irq_method = sip_method_invalid;
irq->irq_default = 1;
agent->sa_default_incoming = irq;
return irq;
}
/** Create a server transaction.
*
* Create a server transaction for a request message. This function is used
* when an element processing requests statelessly wants to process a
* particular request statefully.
*
* @param agent pointer to agent object
* @param leg pointer to leg object (either @a agent or @a leg may be NULL)
* @param msg pointer to message object
* @param sip pointer to SIP structure (may be NULL)
* @param tag,value,... optional tagged parameters
*
* @note
* The ownership of @a msg is taken over by the function even if the
* function fails.
*
* @TAGS
* @TAG NTATAG_TPORT() specifies the transport used to receive the request
* and also default transport for sending the response.
*
* @retval nta_incoming_t pointer to the newly created server transaction
* @retval NULL if failed
*/
nta_incoming_t *nta_incoming_create(nta_agent_t *agent,
nta_leg_t *leg,
msg_t *msg,
sip_t *sip,
tag_type_t tag, tag_value_t value, ...)
{
char const *to_tag = NULL;
tport_t *tport = NULL;
ta_list ta;
nta_incoming_t *irq;
if (msg == NULL)
return NULL;
if (agent == NULL && leg != NULL)
agent = leg->leg_agent;
if (sip == NULL)
sip = sip_object(msg);
if (agent == NULL || sip == NULL || !sip->sip_request || !sip->sip_cseq)
return msg_destroy(msg), NULL;
ta_start(ta, tag, value);
tl_gets(ta_args(ta),
NTATAG_TPORT_REF(tport),
TAG_END());
ta_end(ta);
if (leg && leg->leg_local)
to_tag = leg->leg_local->a_tag;
if (tport == NULL)
tport = tport_delivered_by(agent->sa_tports, msg);
irq = incoming_create(agent, msg, sip, tport, to_tag);
if (!irq)
msg_destroy(msg);
return irq;
}
/** @internal Create a new incoming transaction object. */
static
nta_incoming_t *incoming_create(nta_agent_t *agent,
msg_t *msg,
sip_t *sip,
tport_t *tport,
char const *tag)
{
nta_incoming_t *irq = su_zalloc(msg_home(msg), sizeof(*irq));
agent->sa_stats->as_server_tr++;
if (irq) {
su_home_t *home;
incoming_queue_t *queue;
sip_method_t method = sip->sip_request->rq_method;
irq->irq_request = msg;
irq->irq_home = home = msg_home(msg_ref_create(msg));
irq->irq_agent = agent;
irq->irq_received = agent_now(agent); /* Timestamp originally from tport */
irq->irq_method = method;
irq->irq_rq = sip_request_copy(home, sip->sip_request);
irq->irq_from = sip_from_copy(home, sip->sip_from);
irq->irq_to = sip_to_copy(home, sip->sip_to);
irq->irq_call_id = sip_call_id_copy(home, sip->sip_call_id);
irq->irq_cseq = sip_cseq_copy(home, sip->sip_cseq);
irq->irq_via = sip_via_copy(home, sip->sip_via);
switch (method) {
case sip_method_ack:
case sip_method_cancel:
case sip_method_bye:
case sip_method_options:
case sip_method_register: /* Handling Path is up to application */
case sip_method_info:
case sip_method_prack:
case sip_method_publish:
break;
default:
irq->irq_record_route =
sip_record_route_copy(home, sip->sip_record_route);
}
irq->irq_branch = sip->sip_via->v_branch;
irq->irq_reliable_tp = tport_is_reliable(tport);
irq->irq_extra_100 = 0; /* Sending extra 100 trying false by default */
if (sip->sip_timestamp)
irq->irq_timestamp = sip_timestamp_copy(home, sip->sip_timestamp);
/* Tag transaction */
if (tag)
sip_to_tag(home, irq->irq_to, tag);
irq->irq_tag = irq->irq_to->a_tag;
if (method != sip_method_ack) {
int *use_rport = NULL;
int retry_without_rport = 0;
if (agent->sa_server_rport)
use_rport = &retry_without_rport, retry_without_rport = 1;
if (nta_tpn_by_via(irq->irq_tpn, irq->irq_via, use_rport) < 0)
SU_DEBUG_1(("%s: bad via\n", __func__));
}
incoming_set_compartment(irq, tport, msg, 0);
if (method == sip_method_invite) {
irq->irq_must_100rel =
sip->sip_require && sip_has_feature(sip->sip_require, "100rel");
if (irq->irq_must_100rel ||
(sip->sip_supported &&
sip_has_feature(sip->sip_supported, "100rel"))) {
irq->irq_rseq = su_randint(1, 0x7fffffff); /* Initialize rseq */
}
queue = agent->sa_in.proceeding;
if (irq->irq_reliable_tp)
incoming_set_timer(irq, agent->sa_t2 / 2); /* N1 = T2 / 2 */
else
incoming_set_timer(irq, 200); /* N1 = 200 ms */
irq->irq_tport = tport_ref(tport);
}
else if (method == sip_method_ack) {
irq->irq_status = 700; /* Never send reply to ACK */
irq->irq_completed = 1;
if (irq->irq_reliable_tp || !agent->sa_is_a_uas) {
queue = agent->sa_in.terminated;
irq->irq_terminated = 1;
}
else {
queue = agent->sa_in.completed; /* Timer J */
}
}
else {
queue = agent->sa_in.proceeding;
/* RFC 4320 (nit-actions-03):
Blacklisting on a late response occurs even over reliable transports.
Thus, if an element processing a request received over a reliable
transport is delaying its final response at all, sending a 100 Trying
well in advance of the timeout will prevent blacklisting. Sending a
100 Trying immediately will not harm the transaction as it would over
UDP, but a policy of always sending such a message results in
unneccessary traffic. A policy of sending a 100 Trying after the
period of time in which Timer E reaches T2 had this been a UDP hop is
one reasonable compromise.
*/
if (agent->sa_extra_100 && irq->irq_reliable_tp)
incoming_set_timer(irq, agent->sa_t2 / 2); /* T2 / 2 */
irq->irq_tport = tport_ref(tport);
}
irq->irq_hash = NTA_HASH(irq->irq_call_id, irq->irq_cseq->cs_seq);
incoming_insert(agent, queue, irq);
}
return irq;
}
/** @internal
* Insert incoming transaction to hash table.
*/
static void
incoming_insert(nta_agent_t *agent,
incoming_queue_t *queue,
nta_incoming_t *irq)
{
incoming_queue(queue, irq);
if (incoming_htable_is_full(agent->sa_incoming))
incoming_htable_resize(agent->sa_home, agent->sa_incoming, 0);
if (irq->irq_method != sip_method_ack)
incoming_htable_insert(agent->sa_incoming, irq);
else
/* ACK is appended - final response with tags match with it,
* not with the original INVITE transaction */
/* XXX - what about rfc2543 servers, which do not add tag? */
incoming_htable_append(agent->sa_incoming, irq);
}
/** Call callback for incoming request */
static
int incoming_callback(nta_leg_t *leg, nta_incoming_t *irq, sip_t *sip)
{
sip_method_t method = sip->sip_request->rq_method;
char const *method_name = sip->sip_request->rq_method_name;
/* RFC-3261 section 12.2.2 (page 76) */
if (leg->leg_dialog &&
irq->irq_agent->sa_is_a_uas &&
method != sip_method_ack) {
uint32_t seq = sip->sip_cseq->cs_seq;
if (leg->leg_rseq > sip->sip_cseq->cs_seq) {
SU_DEBUG_3(("nta_leg(%p): out-of-order %s (%u < %u)\n",
(void *)leg, method_name, seq, leg->leg_rseq));
return 500;
}
leg->leg_rseq = seq;
}
return leg->leg_callback(leg->leg_magic, leg, irq, sip);
}
/**
* Destroy an incoming transaction.
*
* This function does not actually free transaction object, but marks it as
* disposable. The object is freed after a timeout.
*
* @param irq incoming request object to be destroyed
*/
void nta_incoming_destroy(nta_incoming_t *irq)
{
if (irq) {
irq->irq_callback = NULL;
irq->irq_magic = NULL;
irq->irq_destroyed = 1;
if (!irq->irq_in_callback) {
if (irq->irq_terminated || irq->irq_default)
incoming_free(irq);
else if (irq->irq_status < 200)
nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
}
}
}
/** @internal
* Initialize a queue for incoming transactions.
*/
static void
incoming_queue_init(incoming_queue_t *queue, unsigned timeout)
{
memset(queue, 0, sizeof *queue);
queue->q_tail = &queue->q_head;
queue->q_timeout = timeout;
}
/** Change the timeout value of a queue */
static void
incoming_queue_adjust(nta_agent_t *sa,
incoming_queue_t *queue,
uint32_t timeout)
{
nta_incoming_t *irq;
uint32_t latest;
if (timeout >= queue->q_timeout || !queue->q_head) {
queue->q_timeout = timeout;
return;
}
latest = set_timeout(sa, queue->q_timeout = timeout);
for (irq = queue->q_head; irq; irq = irq->irq_next) {
if ((int32_t)(irq->irq_timeout - latest) > 0)
irq->irq_timeout = latest;
}
}
/** @internal
* Test if an incoming transaction is in a queue.
*/
su_inline
int incoming_is_queued(nta_incoming_t const *irq)
{
return irq && irq->irq_queue;
}
/** @internal
* Insert an incoming transaction into a queue.
*
* Insert a server transaction into a queue, and sets the corresponding
* timeout at the same time.
*/
su_inline
void incoming_queue(incoming_queue_t *queue,
nta_incoming_t *irq)
{
if (irq->irq_queue == queue) {
assert(queue->q_timeout == 0);
return;
}
if (incoming_is_queued(irq))
incoming_remove(irq);
assert(*queue->q_tail == NULL);
irq->irq_timeout = set_timeout(irq->irq_agent, queue->q_timeout);
irq->irq_queue = queue;
irq->irq_prev = queue->q_tail;
*queue->q_tail = irq;
queue->q_tail = &irq->irq_next;
queue->q_length++;
}
/** @internal
* Remove an incoming transaction from a queue.
*/
su_inline
void incoming_remove(nta_incoming_t *irq)
{
assert(incoming_is_queued(irq));
assert(irq->irq_queue->q_length > 0);
if ((*irq->irq_prev = irq->irq_next))
irq->irq_next->irq_prev = irq->irq_prev;
else
irq->irq_queue->q_tail = irq->irq_prev, assert(!*irq->irq_queue->q_tail);
irq->irq_queue->q_length--;
irq->irq_next = NULL;
irq->irq_prev = NULL;
irq->irq_queue = NULL;
irq->irq_timeout = 0;
}
su_inline
void incoming_set_timer(nta_incoming_t *irq, uint32_t interval)
{
nta_incoming_t **rq;
assert(irq);
if (interval == 0) {
incoming_reset_timer(irq);
return;
}
if (irq->irq_rprev) {
if ((*irq->irq_rprev = irq->irq_rnext))
irq->irq_rnext->irq_rprev = irq->irq_rprev;
if (irq->irq_agent->sa_in.re_t1 == &irq->irq_rnext)
irq->irq_agent->sa_in.re_t1 = irq->irq_rprev;
} else {
irq->irq_agent->sa_in.re_length++;
}
irq->irq_retry = set_timeout(irq->irq_agent, irq->irq_interval = interval);
rq = irq->irq_agent->sa_in.re_t1;
if (!(*rq) || (int32_t)((*rq)->irq_retry - irq->irq_retry) > 0)
rq = &irq->irq_agent->sa_in.re_list;
while (*rq && (int32_t)((*rq)->irq_retry - irq->irq_retry) <= 0)
rq = &(*rq)->irq_rnext;
if ((irq->irq_rnext = *rq))
irq->irq_rnext->irq_rprev = &irq->irq_rnext;
*rq = irq;
irq->irq_rprev = rq;
/* Optimization: keep special place for transactions with T1 interval */
if (interval == irq->irq_agent->sa_t1)
irq->irq_agent->sa_in.re_t1 = rq;
}
su_inline
void incoming_reset_timer(nta_incoming_t *irq)
{
if (irq->irq_rprev) {
if ((*irq->irq_rprev = irq->irq_rnext))
irq->irq_rnext->irq_rprev = irq->irq_rprev;
if (irq->irq_agent->sa_in.re_t1 == &irq->irq_rnext)
irq->irq_agent->sa_in.re_t1 = irq->irq_rprev;
irq->irq_agent->sa_in.re_length--;
}
irq->irq_interval = 0, irq->irq_retry = 0;
irq->irq_rnext = NULL, irq->irq_rprev = NULL;
}
/** @internal
* Free an incoming transaction.
*/
static
void incoming_free(nta_incoming_t *irq)
{
SU_DEBUG_9(("nta: incoming_free(%p)\n", (void *)irq));
incoming_cut_off(irq);
incoming_reclaim(irq);
}
/** Remove references to the irq */
su_inline
void incoming_cut_off(nta_incoming_t *irq)
{
nta_agent_t *agent = irq->irq_agent;
assert(agent);
if (irq->irq_default) {
if (irq == agent->sa_default_incoming)
agent->sa_default_incoming = NULL;
irq->irq_default = 0;
return;
}
if (incoming_is_queued(irq))
incoming_remove(irq);
incoming_reset_timer(irq);
incoming_htable_remove(agent->sa_incoming, irq);
if (irq->irq_cc)
nta_compartment_decref(&irq->irq_cc);
if (irq->irq_tport)
tport_decref(&irq->irq_tport);
}
/** Reclaim the memory used by irq */
su_inline
void incoming_reclaim(nta_incoming_t *irq)
{
su_home_t *home = irq->irq_home;
nta_reliable_t *rel, *rel_next;
if (irq->irq_request)
msg_destroy(irq->irq_request), irq->irq_request = NULL;
if (irq->irq_request2)
msg_destroy(irq->irq_request2), irq->irq_request2 = NULL;
if (irq->irq_response)
msg_destroy(irq->irq_response), irq->irq_response = NULL;
for (rel = irq->irq_reliable; rel; rel = rel_next) {
rel_next = rel->rel_next;
if (rel->rel_unsent)
msg_destroy(rel->rel_unsent);
su_free(irq->irq_agent->sa_home, rel);
}
irq->irq_home = NULL;
su_free(home, irq);
msg_destroy((msg_t *)home);
}
/** Queue request to be freed */
su_inline
void incoming_free_queue(incoming_queue_t *q, nta_incoming_t *irq)
{
incoming_cut_off(irq);
incoming_queue(q, irq);
}
/** Reclaim memory used by queue of requests */
static
void incoming_reclaim_queued(su_root_magic_t *rm,
su_msg_r msg,
union sm_arg_u *u)
{
incoming_queue_t *q = u->a_incoming_queue;
nta_incoming_t *irq, *irq_next;
SU_DEBUG_9(("incoming_reclaim_all(%p, %p, %p)\n",
(void *)rm, (void *)msg, (void *)u));
for (irq = q->q_head; irq; irq = irq_next) {
irq_next = irq->irq_next;
incoming_reclaim(irq);
}
}
/**Bind a callback and context to an incoming transaction object
*
* Set the callback function and context pointer attached to an incoming
* request object. The callback function will be invoked if the incoming
* request is cancelled, or if the final response to an incoming @b INVITE
* request has been acknowledged.
*
* If the callback is NULL, or no callback has been bound, NTA invokes the
* request callback of the call leg.
*
* @param irq incoming transaction
* @param callback callback function
* @param magic application context
*/
void nta_incoming_bind(nta_incoming_t *irq,
nta_ack_cancel_f *callback,
nta_incoming_magic_t *magic)
{
if (irq) {
irq->irq_callback = callback;
irq->irq_magic = magic;
}
}
/** Add a @To tag to incoming request if needed.
*
* If @a tag is NULL, a new tag is generated.
*/
char const *nta_incoming_tag(nta_incoming_t *irq, char const *tag)
{
if (!irq)
return su_seterrno(EFAULT), NULL;
if (irq->irq_default)
return su_seterrno(EINVAL), NULL;
if (tag && strchr(tag, '='))
tag = strchr(tag, '=') + 1;
if (tag && irq->irq_tag && !su_casematch(tag, irq->irq_tag))
return NULL;
if (!irq->irq_tag) {
if (tag)
tag = su_strdup(irq->irq_home, tag);
else
tag = nta_agent_newtag(irq->irq_home, NULL, irq->irq_agent);
if (!tag)
return tag;
irq->irq_tag = tag;
irq->irq_tag_set = 1;
}
return irq->irq_tag;
}
/**Get request message.
*
* Retrieve the incoming request message of the incoming transaction. Note
* that the message is not copied, but a new reference to it is created.
*
* @param irq incoming transaction handle
*
* @retval
* A pointer to request message is returned.
*/
msg_t *nta_incoming_getrequest(nta_incoming_t *irq)
{
msg_t *msg = NULL;
if (irq && !irq->irq_default)
msg = msg_ref_create(irq->irq_request);
return msg;
}
/**Get ACK or CANCEL message.
*
* Retrieve the incoming ACK or CANCEL request message of the incoming
* transaction. Note that the ACK or CANCEL message is not copied, but a new
* reference to it is created.
*
* @param irq incoming transaction handle
*
* @retval A pointer to request message is returned, or NULL if there is no
* CANCEL or ACK received.
*/
msg_t *nta_incoming_getrequest_ackcancel(nta_incoming_t *irq)
{
msg_t *msg = NULL;
if (irq && irq->irq_request2)
msg = msg_ref_create(irq->irq_request2);
return msg;
}
/**Get response message.
*
* Retrieve the response message latest sent by the server transaction. Note
* that the message is not copied, but a new reference to it is created. Use
* msg_dup() or msg_copy() to make a copy of it.
*
* @param irq incoming transaction handle
*
* @retval
* A pointer to a response message is returned.
*/
msg_t *nta_incoming_getresponse(nta_incoming_t *irq)
{
msg_t *msg = NULL;
if (irq && irq->irq_response)
msg = msg_ref_create(irq->irq_response);
return msg;
}
/** Get method of a server transaction. */
sip_method_t nta_incoming_method(nta_incoming_t const *irq)
{
return irq ? irq->irq_method : sip_method_invalid;
}
/** Get method name of a server transaction. */
char const *nta_incoming_method_name(nta_incoming_t const *irq)
{
if (irq == NULL)
return NULL;
else if (irq->irq_rq)
return irq->irq_rq->rq_method_name;
else
return "*";
}
/** Get Request-URI of a server transaction */
url_t const *nta_incoming_url(nta_incoming_t const *irq)
{
return irq && irq->irq_rq ? irq->irq_rq->rq_url : NULL;
}
/** Get sequence number of a server transaction.
*/
uint32_t nta_incoming_cseq(nta_incoming_t const *irq)
{
return irq && irq->irq_cseq ? irq->irq_cseq->cs_seq : 0;
}
/** Get local tag for incoming request */
char const *nta_incoming_gettag(nta_incoming_t const *irq)
{
return irq ? irq->irq_tag : 0;
}
/**
* Get status code of a server transaction.
*/
int nta_incoming_status(nta_incoming_t const *irq)
{
return irq ? irq->irq_status : 400;
}
/** Get application context for a server transaction.
*
* @param irq server transaction
* @param callback callback pointer
*
* Return the application context bound to the server transaction. If the @a
* callback function pointer is given, return application context only if
* the callback matches with the callback bound to the server transaction.
*
*/
nta_incoming_magic_t *nta_incoming_magic(nta_incoming_t *irq,
nta_ack_cancel_f *callback)
{
return irq && (callback == NULL || irq->irq_callback == callback)
? irq->irq_magic : NULL;
}
/** When received.
*
* Return timestamp from the reception of the initial request.
*
* @NEW_1_12_7.
*/
sip_time_t nta_incoming_received(nta_incoming_t *irq,
su_nanotime_t *return_nano)
{
su_time_t tv = { 0, 0 };
if (irq)
tv = irq->irq_received;
if (return_nano)
*return_nano = (su_nanotime_t)tv.tv_sec * 1000000000 + tv.tv_usec * 1000;
return tv.tv_sec;
}
/** Find incoming transaction. */
nta_incoming_t *nta_incoming_find(nta_agent_t const *agent,
sip_t const *sip,
sip_via_t const *v)
{
if (agent && sip && v)
return incoming_find(agent, sip, v, NULL, NULL, NULL);
else
return NULL;
}
/** Find a matching server transaction object.
*
* Check also for requests to merge, to ACK, or to CANCEL.
*/
static nta_incoming_t *incoming_find(nta_agent_t const *agent,
sip_t const *sip,
sip_via_t const *v,
nta_incoming_t **return_merge,
nta_incoming_t **return_ack,
nta_incoming_t **return_cancel)
{
sip_cseq_t const *cseq = sip->sip_cseq;
sip_call_id_t const *i = sip->sip_call_id;
sip_to_t const *to = sip->sip_to;
sip_from_t const *from = sip->sip_from;
sip_request_t *rq = sip->sip_request;
incoming_htable_t const *iht = agent->sa_incoming;
hash_value_t hash = NTA_HASH(i, cseq->cs_seq);
char const *magic_branch;
nta_incoming_t **ii, *irq;
int is_uas_ack = return_ack && agent->sa_is_a_uas;
if (v->v_branch && su_casenmatch(v->v_branch, "z9hG4bK", 7))
magic_branch = v->v_branch + 7;
else
magic_branch = NULL;
for (ii = incoming_htable_hash(iht, hash);
(irq = *ii);
ii = incoming_htable_next(iht, ii)) {
if (hash != irq->irq_hash ||
irq->irq_call_id->i_hash != i->i_hash ||
strcmp(irq->irq_call_id->i_id, i->i_id))
continue;
if (irq->irq_cseq->cs_seq != cseq->cs_seq)
continue;
if (su_strcasecmp(irq->irq_from->a_tag, from->a_tag))
continue;
if (is_uas_ack &&
irq->irq_method == sip_method_invite &&
200 <= irq->irq_status && irq->irq_status < 300 &&
su_casematch(irq->irq_tag, to->a_tag)) {
*return_ack = irq;
return NULL;
}
if (magic_branch) {
/* RFC3261 17.2.3:
*
* The request matches a transaction if branch and sent-by in topmost
* the method of the request matches the one that created the
* transaction, except for ACK, where the method of the request
* that created the transaction is INVITE.
*/
if (irq->irq_via->v_branch &&
su_casematch(irq->irq_via->v_branch + 7, magic_branch) &&
su_casematch(irq->irq_via->v_host, v->v_host) &&
su_strmatch(irq->irq_via->v_port, v->v_port)) {
if (irq->irq_method == cseq->cs_method &&
strcmp(irq->irq_cseq->cs_method_name,
cseq->cs_method_name) == 0)
return irq;
if (return_ack && irq->irq_method == sip_method_invite)
return *return_ack = irq, NULL;
if (return_cancel && irq->irq_method != sip_method_ack)
return *return_cancel = irq, NULL;
}
}
else {
/* No magic branch */
/* INVITE request matches a transaction if
the Request-URI, To tag, From tag, Call-ID, CSeq, and
top Via header match */
/* From tag, Call-ID, and CSeq number has been matched above */
/* Match top Via header field */
if (!su_casematch(irq->irq_via->v_branch, v->v_branch) ||
!su_casematch(irq->irq_via->v_host, v->v_host) ||
!su_strmatch(irq->irq_via->v_port, v->v_port))
;
/* Match Request-URI */
else if (url_cmp(irq->irq_rq->rq_url, rq->rq_url))
;
else {
/* Match CSeq */
if (irq->irq_method == cseq->cs_method &&
su_strmatch(irq->irq_cseq->cs_method_name, cseq->cs_method_name)) {
/* Match To tag */
if (!su_strcasecmp(irq->irq_to->a_tag, to->a_tag))
return irq; /* found */
}
else if (
/* Tag set by UAS */
su_strcasecmp(irq->irq_tag, to->a_tag) &&
/* Original tag */
su_strcasecmp(irq->irq_to->a_tag, to->a_tag))
;
else if (return_ack && irq->irq_method == sip_method_invite)
return *return_ack = irq, NULL;
else if (return_cancel && irq->irq_method != sip_method_ack)
return *return_cancel = irq, NULL;
}
}
/* RFC3261 - section 8.2.2.2 Merged Requests */
if (return_merge) {
if (irq->irq_cseq->cs_method == cseq->cs_method &&
strcmp(irq->irq_cseq->cs_method_name,
cseq->cs_method_name) == 0)
*return_merge = irq, return_merge = NULL;
}
}
return NULL;
}
/** Process retransmitted requests. */
su_inline
int
incoming_recv(nta_incoming_t *irq, msg_t *msg, sip_t *sip, tport_t *tport)
{
nta_agent_t *agent = irq->irq_agent;
agent->sa_stats->as_recv_retry++;
if (irq->irq_status >= 100) {
SU_DEBUG_5(("nta: re-received %s request, retransmitting %u reply\n",
sip->sip_request->rq_method_name, irq->irq_status));
incoming_retransmit_reply(irq, tport);
}
else if (irq->irq_agent->sa_extra_100 &&
irq->irq_extra_100) {
/* Agent and Irq configured to answer automatically with 100 Trying */
if (irq->irq_method == sip_method_invite ||
/*
* Send 100 trying to non-invite if at least half of T2 has expired
* since the transaction was created.
*/
su_duration(agent_now(irq->irq_agent), irq->irq_received) * 2U >
irq->irq_agent->sa_t2) {
SU_DEBUG_5(("nta: re-received %s request, sending 100 Trying\n",
sip->sip_request->rq_method_name));
nta_incoming_treply(irq, SIP_100_TRYING, NTATAG_TPORT(tport), TAG_END());
}
}
msg_destroy(msg);
return 0;
}
su_inline
int incoming_ack(nta_incoming_t *irq, msg_t *msg, sip_t *sip, tport_t *tport)
{
nta_agent_t *agent = irq->irq_agent;
/* Process ACK separately? */
if (irq->irq_status >= 200 && irq->irq_status < 300 && !agent->sa_is_a_uas)
return -1;
if (irq->irq_queue == agent->sa_in.inv_completed) {
if (!irq->irq_confirmed)
agent->sa_stats->as_acked_tr++;
irq->irq_confirmed = 1;
incoming_reset_timer(irq); /* Reset timer G */
if (!irq->irq_reliable_tp) {
incoming_queue(agent->sa_in.inv_confirmed, irq); /* Timer I */
}
else {
irq->irq_terminated = 1;
incoming_queue(agent->sa_in.terminated, irq);
}
if (!irq->irq_destroyed) {
if (!irq->irq_callback) /* Process ACK normally */
return -1;
incoming_call_callback(irq, msg, sip); /* ACK callback */
}
} else if (irq->irq_queue == agent->sa_in.proceeding ||
irq->irq_queue == agent->sa_in.preliminary)
return -1;
else
assert(irq->irq_queue == agent->sa_in.inv_confirmed ||
irq->irq_queue == agent->sa_in.terminated);
msg_destroy(msg);
return 0;
}
/** Respond to the CANCEL. */
su_inline
int incoming_cancel(nta_incoming_t *irq, msg_t *msg, sip_t *sip,
tport_t *tport)
{
nta_agent_t *agent = irq->irq_agent;
/* According to the RFC 3261, this INVITE has been destroyed */
if (irq->irq_method == sip_method_invite &&
200 <= irq->irq_status && irq->irq_status < 300) {
mreply(agent, NULL, SIP_481_NO_TRANSACTION, msg,
tport, 0, 0, NULL,
TAG_END());
return 0;
}
/* UAS MUST use same tag in final response to CANCEL and INVITE */
if (agent->sa_is_a_uas && irq->irq_tag == NULL) {
nta_incoming_tag(irq, NULL);
}
mreply(agent, NULL, SIP_200_OK, msg_ref_create(msg),
tport, 0, 0, irq->irq_tag,
TAG_END());
/* We have already sent final response */
if (irq->irq_completed || irq->irq_method != sip_method_invite) {
msg_destroy(msg);
return 0;
}
if (!irq->irq_canceled) {
irq->irq_canceled = 1;
agent->sa_stats->as_canceled_tr++;
irq = incoming_call_callback(irq, msg, sip);
}
if (irq && !irq->irq_completed && agent->sa_cancel_487)
/* Respond to the cancelled request */
nta_incoming_treply(irq, SIP_487_REQUEST_CANCELLED, TAG_END());
msg_destroy(msg);
return 0;
}
/** Merge request */
static
void request_merge(nta_agent_t *agent,
msg_t *msg, sip_t *sip, tport_t *tport,
char const *to_tag)
{
nta_incoming_t *irq;
agent->sa_stats->as_merged_request++;
irq = incoming_create(agent, msg, sip, tport, to_tag);
if (irq) {
nta_incoming_treply(irq, 482, "Request merged", TAG_END());
nta_incoming_destroy(irq);
} else {
SU_DEBUG_3(("nta: request_merge(): cannot create transaction for %s\n",
sip->sip_request->rq_method_name));
mreply(agent, NULL, 482, "Request merged", msg,
tport, 0, 0, NULL,
TAG_END());
}
}
/**@typedef nta_ack_cancel_f
*
* Callback function prototype for CANCELed/ACKed requests
*
* This is a callback function is invoked by NTA when an incoming request
* has been cancelled or an response to an incoming INVITE request has been
* acknowledged.
*
* @param magic incoming request context
* @param ireq incoming request
* @param sip ACK/CANCEL message
*
* @retval 0
* This callback function should return always 0.
*/
/** Call callback of incoming transaction */
su_inline
nta_incoming_t *
incoming_call_callback(nta_incoming_t *irq, msg_t *msg, sip_t *sip)
{
if (irq->irq_callback) {
irq->irq_in_callback = 1;
irq->irq_request2 = msg;
irq->irq_callback(irq->irq_magic, irq, sip);
irq->irq_request2 = NULL;
irq->irq_in_callback = 0;
if (irq->irq_terminated && irq->irq_destroyed)
incoming_free(irq), irq = NULL;
}
return irq;
}
/**Set server transaction parameters.
*
* Sets the server transaction parameters. Among others, parameters determine the way
* the SigComp compression is handled.
*
* @TAGS
* NTATAG_COMP(), NTATAG_SIGCOMP_CLOSE() and NTATAG_EXTRA_100().
*
* @retval number of set parameters when succesful
* @retval -1 upon an error
*/
int nta_incoming_set_params(nta_incoming_t *irq,
tag_type_t tag, tag_value_t value, ...)
{
int retval = -1;
if (irq) {
ta_list ta;
ta_start(ta, tag, value);
retval = incoming_set_params(irq, ta_args(ta));
ta_end(ta);
}
else {
su_seterrno(EINVAL);
}
return retval;
}
static
int incoming_set_params(nta_incoming_t *irq, tagi_t const *tags)
{
int retval = 0;
tagi_t const *t;
char const *comp = NONE;
struct sigcomp_compartment *cc = NONE;
if (irq->irq_default)
return retval;
for (t = tags; t; t = tl_next(t)) {
tag_type_t tt = t->t_tag;
if (ntatag_comp == tt)
comp = (char const *)t->t_value, retval++;
else if (ntatag_sigcomp_close == tt)
irq->irq_sigcomp_zap = t->t_value != 0, retval++;
else if (tptag_compartment == tt)
cc = (void *)t->t_value, retval++;
else if (ntatag_extra_100 == tt)
irq->irq_extra_100 = t->t_value != 0, retval++;
}
if (cc != NONE) {
if (cc)
agent_accept_compressed(irq->irq_agent, irq->irq_request, cc);
if (irq->irq_cc)
nta_compartment_decref(&irq->irq_cc);
irq->irq_cc = nta_compartment_ref(cc);
}
else if (comp != NULL && comp != NONE && irq->irq_cc == NULL) {
incoming_set_compartment(irq, irq->irq_tport, irq->irq_request, 1);
}
else if (comp == NULL) {
irq->irq_tpn->tpn_comp = NULL;
}
return retval;
}
su_inline
int incoming_set_compartment(nta_incoming_t *irq, tport_t *tport, msg_t *msg,
int create_if_needed)
{
if (!nta_compressor_vtable)
return 0;
if (irq->irq_cc == NULL
|| irq->irq_tpn->tpn_comp
|| tport_delivered_with_comp(tport, msg, NULL) != -1) {
struct sigcomp_compartment *cc;
cc = agent_compression_compartment(irq->irq_agent, tport, irq->irq_tpn,
create_if_needed);
if (cc)
agent_accept_compressed(irq->irq_agent, msg, cc);
irq->irq_cc = cc;
}
return 0;
}
/** Add essential headers to the response message */
static int nta_incoming_response_headers(nta_incoming_t *irq,
msg_t *msg,
sip_t *sip)
{
int clone = 0;
su_home_t *home = msg_home(msg);
if (!sip->sip_from)
clone = 1, sip->sip_from = sip_from_copy(home, irq->irq_from);
if (!sip->sip_to)
clone = 1, sip->sip_to = sip_to_copy(home, irq->irq_to);
if (!sip->sip_call_id)
clone = 1, sip->sip_call_id = sip_call_id_copy(home, irq->irq_call_id);
if (!sip->sip_cseq)
clone = 1, sip->sip_cseq = sip_cseq_copy(home, irq->irq_cseq);
if (!sip->sip_via)
clone = 1, sip->sip_via = sip_via_copy(home, irq->irq_via);
if (clone)
msg_set_parent(msg, (msg_t *)irq->irq_home);
if (!sip->sip_from || !sip->sip_to || !sip->sip_call_id || !sip->sip_cseq || !sip->sip_via)
return -1;
return 0;
}
/** Complete a response message.
*
* @param irq server transaction object
* @param msg response message to be completed
* @param status status code (in range 100 - 699)
* @param phrase status phrase (may be NULL)
* @param tag,value,... taged argument list
*
* Generate status structure based on @a status and @a phrase.
* Add essential headers to the response message:
* @From, @To, @CallID, @CSeq, @Via, and optionally
* @RecordRoute.
*/
int nta_incoming_complete_response(nta_incoming_t *irq,
msg_t *msg,
int status,
char const *phrase,
tag_type_t tag, tag_value_t value, ...)
{
su_home_t *home = msg_home(msg);
sip_t *sip = sip_object(msg);
int retval;
ta_list ta;
if (irq == NULL || sip == NULL)
return su_seterrno(EFAULT), -1;
if (status != 0 && (status < 100 || status > 699))
return su_seterrno(EINVAL), -1;
if (status != 0 && !sip->sip_status)
sip->sip_status = sip_status_create(home, status, phrase, NULL);
ta_start(ta, tag, value);
retval = sip_add_tl(msg, sip, ta_tags(ta));
ta_end(ta);
if (retval < 0)
return -1;
if (irq->irq_default)
return sip_complete_message(msg);
if (status > 100 && !irq->irq_tag) {
if (sip->sip_to)
nta_incoming_tag(irq, sip->sip_to->a_tag);
else
nta_incoming_tag(irq, NULL);
}
if (nta_incoming_response_headers(irq, msg, sip) < 0)
return -1;
if (sip->sip_status && sip->sip_status->st_status > 100 &&
irq->irq_tag && sip->sip_to && !sip->sip_to->a_tag)
if (sip_to_tag(home, sip->sip_to, irq->irq_tag) < 0)
return -1;
if (status < 300 && !sip->sip_record_route && irq->irq_record_route)
if (sip_add_dup(msg, sip, (sip_header_t *)irq->irq_record_route) < 0)
return -1;
return sip_complete_message(msg);
}
/** Create a response message for request.
*
* @NEW_1_12_5.
*/
msg_t *nta_incoming_create_response(nta_incoming_t *irq,
int status, char const *phrase)
{
msg_t *msg = NULL;
sip_t *sip;
if (irq) {
msg = nta_msg_create(irq->irq_agent, 0);
sip = sip_object(msg);
if (sip) {
if (status != 0)
sip->sip_status = sip_status_create(msg_home(msg), status, phrase, NULL);
if (nta_incoming_response_headers(irq, msg, sip) < 0)
msg_destroy(msg), msg = NULL;
}
}
return msg;
}
/**Reply to an incoming transaction request.
*
* This function creates a response message to an incoming request and sends
* it to the client.
*
* @note
* It is possible to send several non-final (1xx) responses, but only one
* final response.
*
* @param irq incoming request
* @param status status code
* @param phrase status phrase (may be NULL if status code is well-known)
* @param tag,value,... optional additional headers terminated by TAG_END()
*
* @retval 0 when succesful
* @retval -1 upon an error
*/
int nta_incoming_treply(nta_incoming_t *irq,
int status,
char const *phrase,
tag_type_t tag, tag_value_t value, ...)
{
int retval = -1;
if (irq &&
(irq->irq_status < 200 || status < 200 ||
(irq->irq_method == sip_method_invite && status < 300))) {
ta_list ta;
msg_t *msg = nta_msg_create(irq->irq_agent, 0);
ta_start(ta, tag, value);
if (!msg)
;
else if (nta_incoming_complete_response(irq, msg, status, phrase,
ta_tags(ta)) < 0)
msg_destroy(msg);
else if (incoming_set_params(irq, ta_args(ta)) < 0)
msg_destroy(msg);
else
retval = nta_incoming_mreply(irq, msg);
ta_end(ta);
if (retval < 0 && status >= 200)
incoming_final_failed(irq, NULL);
}
return retval;
}
/**
* Return a response message to client.
*
* @note
* The ownership of @a msg is taken over by the function even if the
* function fails.
*
* @retval 0 when succesful
* @retval -1 upon an error
*/
int nta_incoming_mreply(nta_incoming_t *irq, msg_t *msg)
{
sip_t *sip = sip_object(msg);
int status;
if (irq == NULL) {
msg_destroy(msg);
return -1;
}
if (msg == NULL || sip == NULL)
return -1;
if (msg == irq->irq_response)
return 0;
if (!sip->sip_status || !sip->sip_via || !sip->sip_cseq)
return incoming_final_failed(irq, msg);
assert (sip->sip_cseq->cs_method == irq->irq_method || irq->irq_default);
status = sip->sip_status->st_status;
if (!irq->irq_tag && status > 100 && !irq->irq_default)
nta_incoming_tag(irq, NULL);
if (/* (irq->irq_confirmed && status >= 200) || */
(irq->irq_completed && status >= 300)) {
SU_DEBUG_3(("%s: already %s transaction\n", __func__,
irq->irq_confirmed ? "confirmed" : "completed"));
msg_destroy(msg);
return -1;
}
if (irq->irq_must_100rel && !sip->sip_rseq && status > 100 && status < 200) {
/* This nta_reliable_t object will be destroyed by PRACK or timeout */
if (nta_reliable_mreply(irq, NULL, NULL, msg))
return 0;
return -1;
}
if (status >= 200 && irq->irq_reliable && irq->irq_reliable->rel_unsent) {
if (reliable_final(irq, msg, sip) == 0)
return 0;
}
return incoming_reply(irq, msg, sip);
}
/** Send the response message.
*
* @note The ownership of msg is handled to incoming_reply().
*/
int incoming_reply(nta_incoming_t *irq, msg_t *msg, sip_t *sip)
{
nta_agent_t *agent = irq->irq_agent;
int status = sip->sip_status->st_status;
int sending = 1;
int *use_rport = NULL;
int retry_without_rport = 0;
tp_name_t *tpn, default_tpn[1];
if (status == 408 &&
irq->irq_method != sip_method_invite &&
!agent->sa_pass_408 &&
!irq->irq_default) {
/* RFC 4320 nit-actions-03 Action 2:
A transaction-stateful SIP element MUST NOT send a response with
Status-Code of 408 to a non-INVITE request. As a consequence, an
element that can not respond before the transaction expires will not
send a final response at all.
*/
sending = 0;
}
if (irq->irq_status == 0 && irq->irq_timestamp && !sip->sip_timestamp)
incoming_timestamp(irq, msg, sip);
if (irq->irq_default) {
if (agent->sa_server_rport)
use_rport = &retry_without_rport, retry_without_rport = 1;
tpn = default_tpn;
if (nta_tpn_by_via(tpn, sip->sip_via, use_rport) < 0)
tpn = NULL;
}
else {
tpn = irq->irq_tpn;
}
if (sip_complete_message(msg) < 0)
SU_DEBUG_1(("%s: sip_complete_message() failed\n", __func__));
else if (msg_serialize(msg, (msg_pub_t *)sip) < 0)
SU_DEBUG_1(("%s: sip_serialize() failed\n", __func__));
else if (!(irq->irq_tport) &&
!(tport_decref(&irq->irq_tport),
irq->irq_tport = tpn ? tport_by_name(agent->sa_tports, tpn) : 0))
SU_DEBUG_1(("%s: no tport\n", __func__));
else {
int i, err = 0;
tport_t *tp = NULL;
incoming_queue_t *queue;
char const *method_name;
uint32_t cseq;
if (irq->irq_default) {
assert(sip->sip_cseq);
method_name = sip->sip_cseq->cs_method_name, cseq = sip->sip_cseq->cs_seq;
}
else {
method_name = irq->irq_rq->rq_method_name, cseq = irq->irq_cseq->cs_seq;
}
if (sending) {
for (i = 0; i < 3; i++) {
tp = tport_tsend(irq->irq_tport, msg, tpn,
IF_SIGCOMP_TPTAG_COMPARTMENT(irq->irq_cc)
TPTAG_MTU(INT_MAX),
TAG_END());
if (tp)
break;
err = msg_errno(msg);
SU_DEBUG_5(("%s: tport_tsend: %s%s\n",
__func__, su_strerror(err),
err == EPIPE ? "(retrying)" : ""));
if (err != EPIPE && err != ECONNREFUSED)
break;
tport_decref(&irq->irq_tport);
irq->irq_tport = tport_ref(tport_by_name(agent->sa_tports, tpn));
}
if (!tp) {
SU_DEBUG_3(("%s: tport_tsend: "
"error (%s) while sending %u %s for %s (%u)\n",
__func__, su_strerror(err),
status, sip->sip_status->st_phrase, method_name, cseq));
if (status < 200)
msg_destroy(msg);
else
incoming_final_failed(irq, msg);
return 0;
}
agent->sa_stats->as_sent_msg++;
agent->sa_stats->as_sent_response++;
}
SU_DEBUG_5(("nta: %s %u %s for %s (%u)\n",
sending ? "sent" : "not sending",
status, sip->sip_status->st_phrase, method_name, cseq));
if (irq->irq_default) {
msg_destroy(msg);
return 0;
}
incoming_reset_timer(irq);
if (status < 200) {
queue = agent->sa_in.proceeding;
if (irq->irq_method == sip_method_invite && status > 100 &&
agent->sa_progress != UINT_MAX && agent->sa_is_a_uas) {
/* Retransmit preliminary responses in regular intervals */
incoming_set_timer(irq, agent->sa_progress); /* N2 */
}
}
else {
irq->irq_completed = 1;
/* XXX - we should do this only after message has actually been sent! */
if (irq->irq_sigcomp_zap && irq->irq_cc)
agent_close_compressor(irq->irq_agent, irq->irq_cc);
if (irq->irq_method != sip_method_invite) {
irq->irq_confirmed = 1;
if (irq->irq_reliable_tp) {
irq->irq_terminated = 1;
queue = agent->sa_in.terminated ; /* J - set for 0 seconds */
} else {
queue = agent->sa_in.completed; /* J */
}
tport_decref(&irq->irq_tport);
}
else if (status >= 300 || agent->sa_is_a_uas) {
if (status < 300 || !irq->irq_reliable_tp)
incoming_set_timer(irq, agent->sa_t1); /* G */
queue = agent->sa_in.inv_completed; /* H */
}
else {
#if 1
/* Avoid bug in @RFC3261:
Keep INVITE transaction around in order to catch
retransmitted INVITEs
*/
irq->irq_confirmed = 1;
queue = agent->sa_in.inv_confirmed; /* H */
#else
irq->irq_terminated = 1;
queue = agent->sa_in.terminated;
#endif
}
}
if (irq->irq_queue != queue)
incoming_queue(queue, irq);
if (status >= 200 || irq->irq_status < 200) {
if (irq->irq_response)
msg_destroy(irq->irq_response);
assert(msg_home(msg) != irq->irq_home);
irq->irq_response = msg;
}
else {
msg_destroy(msg);
}
if (sip->sip_cseq->cs_method == irq->irq_method &&
irq->irq_status < 200 && status > irq->irq_status)
irq->irq_status = status;
return 0;
}
/*
* XXX - handling error is very problematic.
* Nobody checks return code from nta_incoming_*reply()
*/
if (status < 200) {
msg_destroy(msg);
return -1;
}
/* We could not send final response. */
return incoming_final_failed(irq, msg);
}
/** @internal Sending final response has failed.
*
* Put transaction into its own queue, try later to send the response.
*/
su_inline
int incoming_final_failed(nta_incoming_t *irq, msg_t *msg)
{
msg_destroy(msg);
if (!irq->irq_default) {
irq->irq_final_failed = 1;
incoming_queue(irq->irq_agent->sa_in.final_failed, irq);
}
return -1;
}
/** @internal Retransmit the reply */
static
void incoming_retransmit_reply(nta_incoming_t *irq, tport_t *tport)
{
msg_t *msg = NULL;
if (irq->irq_final_failed)
return;
if (tport == NULL)
tport = irq->irq_tport;
/* Answer with existing reply */
if (irq->irq_reliable && !irq->irq_reliable->rel_pracked)
msg = reliable_response(irq);
else
msg = irq->irq_response;
if (msg && tport) {
irq->irq_retries++;
if (irq->irq_retries == 2 && irq->irq_tpn->tpn_comp) {
irq->irq_tpn->tpn_comp = NULL;
if (irq->irq_cc) {
agent_close_compressor(irq->irq_agent, irq->irq_cc);
nta_compartment_decref(&irq->irq_cc);
}
}
tport = tport_tsend(tport, msg, irq->irq_tpn,
IF_SIGCOMP_TPTAG_COMPARTMENT(irq->irq_cc)
TPTAG_MTU(INT_MAX), TAG_END());
irq->irq_agent->sa_stats->as_sent_msg++;
irq->irq_agent->sa_stats->as_sent_response++;
}
}
/** @internal Create timestamp header for response */
static
int incoming_timestamp(nta_incoming_t *irq, msg_t *msg, sip_t *sip)
{
sip_timestamp_t ts[1];
su_time_t now = su_now();
char delay[32];
double diff = su_time_diff(now, irq->irq_received);
snprintf(delay, sizeof delay, "%.06f", diff);
*ts = *irq->irq_timestamp;
ts->ts_delay = delay;
return sip_add_dup(msg, sip, (sip_header_t *)ts);
}
enum {
timer_max_retransmit = 30,
timer_max_terminate = 100000,
timer_max_timeout = 100
};
/** @internal Timer routine for the incoming request. */
static void
_nta_incoming_timer(nta_agent_t *sa)
{
uint32_t now = su_time_ms(su_now());
nta_incoming_t *irq, *irq_next;
size_t retransmitted = 0, timeout = 0, terminated = 0, destroyed = 0;
size_t unconfirmed =
sa->sa_in.inv_completed->q_length +
sa->sa_in.preliminary->q_length;
size_t unterminated =
sa->sa_in.inv_confirmed->q_length +
sa->sa_in.completed->q_length;
size_t total = sa->sa_incoming->iht_used;
incoming_queue_t rq[1];
incoming_queue_init(rq, 0);
/* Handle retry queue */
while ((irq = sa->sa_in.re_list)) {
now = su_time_ms(su_now());
if ((int32_t)(irq->irq_retry - now) > 0)
break;
if (retransmitted >= timer_max_retransmit)
break;
if (irq->irq_method == sip_method_invite && irq->irq_status >= 200) {
/* Timer G */
assert(irq->irq_queue == sa->sa_in.inv_completed);
retransmitted++;
SU_DEBUG_5(("nta: timer %s fired, retransmitting %u reply\n",
"G", irq->irq_status));
incoming_retransmit_reply(irq, irq->irq_tport);
if (2U * irq->irq_interval < sa->sa_t2)
incoming_set_timer(irq, 2U * irq->irq_interval); /* G */
else
incoming_set_timer(irq, sa->sa_t2); /* G */
}
else if (irq->irq_method == sip_method_invite && irq->irq_status >= 100) {
if (irq->irq_queue == sa->sa_in.preliminary) {
/* Timer P1 - PRACK timer */
retransmitted++;
SU_DEBUG_5(("nta: timer %s fired, retransmitting %u reply\n",
"P1", irq->irq_status));
incoming_retransmit_reply(irq, irq->irq_tport);
incoming_set_timer(irq, 2 * irq->irq_interval); /* P1 */
}
else {
/* Retransmitting provisional responses */
SU_DEBUG_5(("nta: timer %s fired, retransmitting %u reply\n",
"N2", irq->irq_status));
incoming_set_timer(irq, sa->sa_progress);
retransmitted++;
incoming_retransmit_reply(irq, irq->irq_tport);
}
}
else {
/* Timer N1 */
incoming_reset_timer(irq);
if(irq->irq_extra_100) {
SU_DEBUG_5(("nta: timer N1 fired, sending %u %s\n", SIP_100_TRYING));
nta_incoming_treply(irq, SIP_100_TRYING, TAG_END());
}
else {
SU_DEBUG_5(("nta: timer N1 fired, but avoided sending %u %s\n",
SIP_100_TRYING));
}
}
}
while ((irq = sa->sa_in.final_failed->q_head)) {
incoming_remove(irq);
irq->irq_final_failed = 0;
/* Report error to application */
SU_DEBUG_5(("nta: sending final response failed, timeout %u response\n",
irq->irq_status));
reliable_timeout(irq, 0);
nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
if (!irq->irq_final_failed) /* We have taken care of the error... */
continue;
if (irq->irq_destroyed) {
incoming_free_queue(rq, irq);
continue;
}
incoming_reset_timer(irq);
irq->irq_confirmed = 1;
irq->irq_terminated = 1;
incoming_queue(sa->sa_in.terminated, irq);
}
/* Timeouts.
* For each state the request is in, there is always a queue of its own
*/
while ((irq = sa->sa_in.preliminary->q_head)) {
assert(irq->irq_status < 200);
assert(irq->irq_timeout);
now = su_time_ms(su_now());
if ((int32_t)(irq->irq_timeout - now) > 0)
break;
if (timeout >= timer_max_timeout)
break;
timeout++;
/* Timer P2 - PRACK timer */
SU_DEBUG_5(("nta: timer %s fired, %s %u response\n",
"P2", "timeout", irq->irq_status));
incoming_reset_timer(irq);
irq->irq_timeout = 0;
reliable_timeout(irq, 1);
}
while ((irq = sa->sa_in.inv_completed->q_head)) {
assert(irq->irq_status >= 200);
assert(irq->irq_timeout);
assert(irq->irq_method == sip_method_invite);
now = su_time_ms(su_now());
if ((int32_t)(irq->irq_timeout - now) > 0 ||
timeout >= timer_max_timeout ||
terminated >= timer_max_terminate)
break;
/* Timer H */
SU_DEBUG_5(("nta: timer %s fired, %s %u response\n",
"H", "timeout and terminate", irq->irq_status));
irq->irq_confirmed = 1;
irq->irq_terminated = 1;
incoming_reset_timer(irq);
if (!irq->irq_destroyed) {
timeout++;
incoming_queue(sa->sa_in.terminated, irq);
/* report timeout error to user */
incoming_call_callback(irq, NULL, NULL);
} else {
timeout++;
terminated++;
incoming_free_queue(rq, irq);
}
}
while ((irq = sa->sa_in.inv_confirmed->q_head)) {
assert(irq->irq_timeout);
assert(irq->irq_status >= 200);
assert(irq->irq_method == sip_method_invite);
now = su_time_ms(su_now());
if ((int32_t)(irq->irq_timeout - now) > 0 ||
terminated >= timer_max_terminate)
break;
/* Timer I */
SU_DEBUG_5(("nta: timer %s fired, %s %u response\n",
"I", "terminate", irq->irq_status));
terminated++;
irq->irq_terminated = 1;
if (!irq->irq_destroyed)
incoming_queue(sa->sa_in.terminated, irq);
else
incoming_free_queue(rq, irq);
}
while ((irq = sa->sa_in.completed->q_head)) {
assert(irq->irq_status >= 200);
assert(irq->irq_timeout);
assert(irq->irq_method != sip_method_invite);
now = su_time_ms(su_now());
if ((int32_t)(irq->irq_timeout - now) > 0 ||
terminated >= timer_max_terminate)
break;
/* Timer J */
SU_DEBUG_5(("nta: timer %s fired, %s %u response\n",
"J", "terminate", irq->irq_status));
terminated++;
irq->irq_terminated = 1;
if (!irq->irq_destroyed)
incoming_queue(sa->sa_in.terminated, irq);
else
incoming_free_queue(rq, irq);
}
for (irq = sa->sa_in.terminated->q_head; irq; irq = irq_next) {
irq_next = irq->irq_next;
if (irq->irq_destroyed)
incoming_free_queue(rq, irq);
}
destroyed = incoming_mass_destroy(sa, rq);
if (retransmitted || timeout || terminated || destroyed)
SU_DEBUG_5(("nta_incoming_timer: "
MOD_ZU"/"MOD_ZU" resent, "
MOD_ZU"/"MOD_ZU" tout, "
MOD_ZU"/"MOD_ZU" term, "
MOD_ZU"/"MOD_ZU" free\n",
retransmitted, unconfirmed,
timeout, unconfirmed,
terminated, unterminated,
destroyed, total));
}
/** Mass destroy server transactions */
su_inline
size_t incoming_mass_destroy(nta_agent_t *sa, incoming_queue_t *q)
{
size_t destroyed = q->q_length;
if (destroyed > 2 && *sa->sa_terminator) {
su_msg_r m = SU_MSG_R_INIT;
if (su_msg_create(m,
su_clone_task(sa->sa_terminator),
su_root_task(sa->sa_root),
incoming_reclaim_queued,
sizeof(incoming_queue_t)) == SU_SUCCESS) {
incoming_queue_t *mq = su_msg_data(m)->a_incoming_queue;
*mq = *q;
if (su_msg_send(m) == SU_SUCCESS)
q->q_length = 0;
}
}
if (q->q_length > 0)
incoming_reclaim_queued(NULL, NULL, (void *)q);
return destroyed;
}
/* ====================================================================== */
/* 8) Client-side (outgoing) transactions */
#define HTABLE_HASH_ORQ(orq) ((orq)->orq_hash)
HTABLE_BODIES_WITH(outgoing_htable, oht, nta_outgoing_t, HTABLE_HASH_ORQ,
size_t, hash_value_t);
static int outgoing_features(nta_agent_t *agent, nta_outgoing_t *orq,
msg_t *msg, sip_t *sip,
tagi_t *tags);
static void outgoing_prepare_send(nta_outgoing_t *orq);
static void outgoing_send_via(nta_outgoing_t *orq, tport_t *tp);
static void outgoing_send(nta_outgoing_t *orq, int retransmit);
static void outgoing_try_tcp_instead(nta_outgoing_t *orq);
static void outgoing_try_udp_instead(nta_outgoing_t *orq, int timeout);
static void outgoing_tport_error(nta_agent_t *agent, nta_outgoing_t *orq,
tport_t *tp, msg_t *msg, int error);
static void outgoing_print_tport_error(nta_outgoing_t *orq,
int level, char *todo,
tp_name_t const *, msg_t *, int error);
static void outgoing_insert(nta_agent_t *sa, nta_outgoing_t *orq);
static void outgoing_destroy(nta_outgoing_t *orq);
su_inline int outgoing_is_queued(nta_outgoing_t const *orq);
su_inline void outgoing_queue(outgoing_queue_t *queue,
nta_outgoing_t *orq);
su_inline void outgoing_remove(nta_outgoing_t *orq);
su_inline void outgoing_set_timer(nta_outgoing_t *orq, uint32_t interval);
static void outgoing_reset_timer(nta_outgoing_t *orq);
static size_t outgoing_timer_dk(outgoing_queue_t *q,
char const *timer,
uint32_t now);
static size_t outgoing_timer_bf(outgoing_queue_t *q,
char const *timer,
uint32_t now);
static size_t outgoing_timer_c(outgoing_queue_t *q,
char const *timer,
uint32_t now);
static void outgoing_ack(nta_outgoing_t *orq, sip_t *sip);
static msg_t *outgoing_ackmsg(nta_outgoing_t *, sip_method_t, char const *,
tag_type_t tag, tag_value_t value, ...);
static void outgoing_retransmit(nta_outgoing_t *orq);
static void outgoing_trying(nta_outgoing_t *orq);
static void outgoing_timeout(nta_outgoing_t *orq, uint32_t now);
static int outgoing_complete(nta_outgoing_t *orq);
static void outgoing_terminate_invite(nta_outgoing_t *);
static void outgoing_remove_fork(nta_outgoing_t *orq);
static int outgoing_terminate(nta_outgoing_t *orq);
static size_t outgoing_mass_destroy(nta_agent_t *sa, outgoing_queue_t *q);
static void outgoing_estimate_delay(nta_outgoing_t *orq, sip_t *sip);
static int outgoing_duplicate(nta_outgoing_t *orq,
msg_t *msg,
sip_t *sip);
static int outgoing_reply(nta_outgoing_t *orq,
int status, char const *phrase,
int delayed);
static int outgoing_default_cb(nta_outgoing_magic_t *magic,
nta_outgoing_t *request,
sip_t const *sip);
/** Create a default outgoing transaction.
*
* The default outgoing transaction is used when agent receives responses
* not belonging to any transaction.
*
* @sa nta_leg_default(), nta_incoming_default().
*/
nta_outgoing_t *nta_outgoing_default(nta_agent_t *agent,
nta_response_f *callback,
nta_outgoing_magic_t *magic)
{
nta_outgoing_t *orq;
if (agent == NULL)
return NULL;
if (agent->sa_default_outgoing)
return NULL;
orq = su_zalloc(agent->sa_home, sizeof *orq);
if (!orq)
return NULL;
orq->orq_agent = agent;
orq->orq_callback = callback;
orq->orq_magic = magic;
orq->orq_method = sip_method_invalid;
orq->orq_method_name = "*";
orq->orq_default = 1;
orq->orq_stateless = 1;
orq->orq_delay = UINT_MAX;
return agent->sa_default_outgoing = orq;
}
/**Create an outgoing request and client transaction belonging to the leg.
*
* Create a request message and pass the request message to an outgoing
* client transaction object. The request is sent to the @a route_url (if
* non-NULL), default proxy (if defined by NTATAG_DEFAULT_PROXY()), or to
* the address specified by @a request_uri. If no @a request_uri is
* specified, it is taken from route-set target or from the @To header.
*
* When NTA receives response to the request, it invokes the @a callback
* function.
*
* @param leg call leg object
* @param callback callback function (may be @c NULL)
* @param magic application context pointer
* @param route_url optional URL used to route transaction requests
* @param method method type
* @param name method name
* @param request_uri Request-URI
* @param tag, value, ... list of tagged arguments
*
* @return
* A pointer to a newly created outgoing transaction object if successful,
* and NULL otherwise.
*
* @note If NTATAG_STATELESS(1) tag is given and the @a callback is NULL,
* the transaction object is marked as destroyed from the beginning. In that
* case, the function may return @code (nta_outgoing_t *)-1 @endcode if the
* transaction is freed before returning from the function.
*
* @sa
* nta_outgoing_mcreate(), nta_outgoing_tcancel(), nta_outgoing_destroy().
*
* @TAGS
* NTATAG_STATELESS(), NTATAG_DELAY_SENDING(), NTATAG_BRANCH_KEY(),
* NTATAG_ACK_BRANCH(), NTATAG_DEFAULT_PROXY(), NTATAG_PASS_100(),
* NTATAG_USE_TIMESTAMP(), NTATAG_USER_VIA(), TPTAG_IDENT(), NTATAG_TPORT(). All
* SIP tags from <sofia-sip/sip_tag.h> can be used to manipulate the request message.
* SIP tags after SIPTAG_END() are ignored, however.
*/
nta_outgoing_t *nta_outgoing_tcreate(nta_leg_t *leg,
nta_response_f *callback,
nta_outgoing_magic_t *magic,
url_string_t const *route_url,
sip_method_t method,
char const *name,
url_string_t const *request_uri,
tag_type_t tag, tag_value_t value, ...)
{
nta_agent_t *agent;
msg_t *msg;
sip_t *sip;
nta_outgoing_t *orq = NULL;
ta_list ta;
tagi_t const *tagi;
if (leg == NULL)
return NULL;
agent = leg->leg_agent;
msg = nta_msg_create(agent, 0);
sip = sip_object(msg);
if (route_url == NULL)
route_url = (url_string_t *)agent->sa_default_proxy;
ta_start(ta, tag, value);
tagi = ta_args(ta);
if (sip_add_tagis(msg, sip, &tagi) < 0) {
if (tagi && tagi->t_tag) {
tag_type_t t = tagi->t_tag;
SU_DEBUG_5(("%s(): bad tag %s::%s\n", __func__,
t->tt_ns ? t->tt_ns : "", t->tt_name ? t->tt_name : ""));
}
}
else if (route_url == NULL && leg->leg_route &&
leg->leg_loose_route &&
!(route_url = (url_string_t *)leg->leg_route->r_url))
;
else if (nta_msg_request_complete(msg, leg, method, name, request_uri) < 0)
;
else
orq = outgoing_create(agent, callback, magic, route_url, NULL, msg,
ta_tags(ta));
ta_end(ta);
if (!orq)
msg_destroy(msg);
return orq;
}
/**Create an outgoing client transaction.
*
* Create an outgoing transaction object. The request message is passed to
* the transaction object, which sends the request to the network. The
* request is sent to the @a route_url (if non-NULL), default proxy (if
* defined by NTATAG_DEFAULT_PROXY()), or to the address specified by @a
* request_uri. If no @a request_uri is specified, it is taken from
* route-set target or from the @To header.
*
* When NTA receives response to the request, it invokes the @a callback
* function.
*
* @param agent NTA agent object
* @param callback callback function (may be @c NULL)
* @param magic application context pointer
* @param route_url optional URL used to route transaction requests
* @param msg request message
* @param tag, value, ... tagged parameter list
*
* @return
* Returns a pointer to newly created outgoing transaction object if
* successful, and NULL otherwise.
*
* @note The caller is responsible for destroying the request message @a msg
* upon failure.
*
* @note If NTATAG_STATELESS(1) tag is given and the @a callback is NULL,
* the transaction object is marked as destroyed from the beginning. In that
* case, the function may return @code (nta_outgoing_t *)-1 @endcode if the
* transaction is freed before returning from the function.
*
* @sa
* nta_outgoing_tcreate(), nta_outgoing_tcancel(), nta_outgoing_destroy().
*
* @TAGS
* NTATAG_STATELESS(), NTATAG_DELAY_SENDING(), NTATAG_BRANCH_KEY(),
* NTATAG_ACK_BRANCH(), NTATAG_DEFAULT_PROXY(), NTATAG_PASS_100(),
* NTATAG_USE_TIMESTAMP(), NTATAG_USER_VIA(), TPTAG_IDENT(), NTATAG_TPORT(). All
* SIP tags from <sofia-sip/sip_tag.h> can be used to manipulate the request message.
* SIP tags after SIPTAG_END() are ignored, however.
*/
nta_outgoing_t *nta_outgoing_mcreate(nta_agent_t *agent,
nta_response_f *callback,
nta_outgoing_magic_t *magic,
url_string_t const *route_url,
msg_t *msg,
tag_type_t tag, tag_value_t value, ...)
{
nta_outgoing_t *orq = NULL;
int cleanup = 0;
if (msg == NONE)
msg = nta_msg_create(agent, 0), cleanup = 1;
if (msg && agent) {
ta_list ta;
ta_start(ta, tag, value);
if (sip_add_tl(msg, sip_object(msg), ta_tags(ta)) >= 0)
orq = outgoing_create(agent, callback, magic, route_url, NULL, msg,
ta_tags(ta));
ta_end(ta);
}
if (!orq && cleanup)
msg_destroy(msg);
return orq;
}
/** Cancel the request. */
int nta_outgoing_cancel(nta_outgoing_t *orq)
{
nta_outgoing_t *cancel =
nta_outgoing_tcancel(orq, NULL, NULL, TAG_NULL());
return (cancel != NULL) - 1;
}
/** Cancel the request.
*
* Initiate a cancel transaction for client transaction @a orq.
*
* @param orq client transaction to cancel
* @param callback callback function (may be @c NULL)
* @param magic application context pointer
* @param tag, value, ... list of extra arguments
*
* @note The function may return @code (nta_outgoing_t *)-1 @endcode (NONE)
* if callback is NULL.
*
* @TAGS
* NTATAG_CANCEL_2534(), NTATAG_CANCEL_408() and all the tags that are
* accepted by nta_outgoing_tcreate().
*
* If NTATAG_CANCEL_408(1) or NTATAG_CANCEL_2543(1) is given, the stack
* generates a 487 response to the request internally. If
* NTATAG_CANCEL_408(1) is given, no CANCEL request is actually sent.
*
* @note
* nta_outgoing_tcancel() refuses to send a CANCEL request for non-INVITE
* requests.
*/
nta_outgoing_t *nta_outgoing_tcancel(nta_outgoing_t *orq,
nta_response_f *callback,
nta_outgoing_magic_t *magic,
tag_type_t tag, tag_value_t value, ...)
{
msg_t *msg;
int cancel_2543, cancel_408;
ta_list ta;
int delay_sending;
if (orq == NULL || orq == NONE)
return NULL;
if (orq->orq_destroyed) {
SU_DEBUG_3(("%s: trying to cancel destroyed request\n", __func__));
return NULL;
}
if (orq->orq_method != sip_method_invite) {
SU_DEBUG_3(("%s: trying to cancel non-INVITE request\n", __func__));
return NULL;
}
if (orq->orq_forking)
orq = orq->orq_forking;
if (orq->orq_status >= 200
/* && orq->orq_method != sip_method_invite ... !multicast */) {
SU_DEBUG_3(("%s: trying to cancel completed request\n", __func__));
return NULL;
}
if (orq->orq_canceled) {
SU_DEBUG_3(("%s: trying to cancel cancelled request\n", __func__));
return NULL;
}
orq->orq_canceled = 1;
#if HAVE_SOFIA_SRESOLV
if (!orq->orq_resolved) {
outgoing_destroy_resolver(orq);
outgoing_reply(orq, SIP_487_REQUEST_CANCELLED, 1);
return NULL; /* XXX - Does anyone care about reply? */
}
#endif
cancel_408 = 0; /* Don't really CANCEL, this is timeout. */
cancel_2543 = orq->orq_agent->sa_cancel_2543;
/* CANCEL may be sent only after a provisional response has been received. */
delay_sending = orq->orq_status < 100;
ta_start(ta, tag, value);
tl_gets(ta_args(ta),
NTATAG_CANCEL_408_REF(cancel_408),
NTATAG_CANCEL_2543_REF(cancel_2543),
TAG_END());
if (!cancel_408)
msg = outgoing_ackmsg(orq, SIP_METHOD_CANCEL, ta_tags(ta));
else
msg = NULL;
ta_end(ta);
if ((cancel_2543 || cancel_408) && !orq->orq_stateless)
outgoing_reply(orq, SIP_487_REQUEST_CANCELLED, 1);
if (msg) {
nta_outgoing_t *cancel;
if (cancel_2543) /* Follow RFC 2543 semantics for CANCEL */
delay_sending = 0;
cancel = outgoing_create(orq->orq_agent, callback, magic,
NULL, orq->orq_tpn, msg,
NTATAG_BRANCH_KEY(orq->orq_branch),
NTATAG_DELAY_SENDING(delay_sending),
NTATAG_USER_VIA(1),
TAG_END());
if (delay_sending)
orq->orq_cancel = cancel;
if (cancel) {
if (!delay_sending)
outgoing_complete(orq);
return cancel;
}
msg_destroy(msg);
}
return NULL;
}
/**Bind callback and application context to a client transaction.
*
* @param orq outgoing client transaction
* @param callback callback function (may be NULL)
* @param magic application context pointer
* (given as argument to @a callback)
*
* @NEW_1_12_9
*/
int
nta_outgoing_bind(nta_outgoing_t *orq,
nta_response_f *callback,
nta_outgoing_magic_t *magic)
{
if (orq && !orq->orq_destroyed) {
if (callback == NULL)
callback = outgoing_default_cb;
orq->orq_callback = callback;
orq->orq_magic = magic;
return 0;
}
return -1;
}
/**Get application context bound to a client transaction.
*
* @param orq outgoing client transaction
* @param callback callback function (may be NULL)
*
* Return the application context bound to a client transaction. If the @a
* callback function pointer is given, return application context only if
* the callback matches with the callback bound to the client transaction.
*
* @NEW_1_12_11
*/
nta_outgoing_magic_t *
nta_outgoing_magic(nta_outgoing_t const *orq,
nta_response_f *callback)
{
if (orq && (callback == NULL || callback == orq->orq_callback))
return orq->orq_magic;
else
return NULL;
}
/**
* Destroy a request object.
*
* @note
* This function does not actually free the object, but marks it as
* disposable. The object is freed after a timeout.
*/
void nta_outgoing_destroy(nta_outgoing_t *orq)
{
if (orq == NULL || orq == NONE)
return;
if (orq->orq_destroyed) {
SU_DEBUG_1(("%s(%p): %s\n", "nta_outgoing_destroy", (void *)orq,
"already destroyed"));
return;
}
outgoing_destroy(orq);
}
/** Return the request URI */
url_t const *nta_outgoing_request_uri(nta_outgoing_t const *orq)
{
return orq != NULL && orq != NONE ? orq->orq_url : NULL;
}
/** Return the URI used to route the request */
url_t const *nta_outgoing_route_uri(nta_outgoing_t const *orq)
{
return orq != NULL && orq != NONE ? orq->orq_route : NULL;
}
/** Return method of the client transaction */
sip_method_t nta_outgoing_method(nta_outgoing_t const *orq)
{
return orq != NULL && orq != NONE ? orq->orq_method : sip_method_invalid;
}
/** Return method name of the client transaction */
char const *nta_outgoing_method_name(nta_outgoing_t const *orq)
{
return orq != NULL && orq != NONE ? orq->orq_method_name : NULL;
}
/** Get sequence number of a client transaction.
*/
uint32_t nta_outgoing_cseq(nta_outgoing_t const *orq)
{
return orq != NULL && orq != NONE && orq->orq_cseq
? orq->orq_cseq->cs_seq : 0;
}
/**
* Get the status code of a client transaction.
*/
int nta_outgoing_status(nta_outgoing_t const *orq)
{
/* Return 500 Internal server error for invalid handles. */
return orq != NULL && orq != NONE ? orq->orq_status : 500;
}
/** Get the RTT delay measured using @Timestamp header. */
unsigned nta_outgoing_delay(nta_outgoing_t const *orq)
{
return orq != NULL && orq != NONE ? orq->orq_delay : UINT_MAX;
}
/** Get the branch parameter. @NEW_1_12_7. */
char const *nta_outgoing_branch(nta_outgoing_t const *orq)
{
return orq != NULL && orq != NONE && orq->orq_branch
? orq->orq_branch + strlen("branch=")
: NULL;
}
/**Get reference to response message.
*
* Retrieve the latest incoming response message to the outgoing
* transaction. Note that the message is not copied, but a new reference to
* it is created instead.
*
* @param orq outgoing transaction handle
*
* @retval
* A pointer to response message is returned, or NULL if no response message
* has been received.
*/
msg_t *nta_outgoing_getresponse(nta_outgoing_t *orq)
{
if (orq != NULL && orq != NONE)
return msg_ref_create(orq->orq_response);
else
return NULL;
}
/**Get request message.
*
* Retrieves the request message sent to the network. Note that the request
* message is @b not copied, but a new reference to it is created.
*
* @retval
* A pointer to the request message is returned, or NULL if an error
* occurred.
*/
msg_t *nta_outgoing_getrequest(nta_outgoing_t *orq)
{
if (orq != NULL && orq != NONE)
return msg_ref_create(orq->orq_request);
else
return NULL;
}
/**Create an outgoing request.
*
* Create an outgoing transaction object and send the request to the
* network. The request is sent to the @a route_url (if non-NULL), default
* proxy (if defined by NTATAG_DEFAULT_PROXY()), or to the address specified
* by @a sip->sip_request->rq_url.
*
* When NTA receives response to the request, it invokes the @a callback
* function.
*
* @param agent nta agent object
* @param callback callback function (may be @c NULL)
* @param magic application context pointer
* @param route_url optional URL used to route transaction requests
* @param msg request message
* @param tpn (optional) transport name
* @param msg request message to
* @param tag, value, ... tagged arguments
*
* @return
* Returns a pointer to newly created outgoing transaction object if
* successful, and NULL otherwise.
*
* @note If NTATAG_STATELESS(1) tag is given and the @a callback is NULL,
* the transaction object is marked as destroyed from the beginning. In that
* case, the function may return @code (nta_outgoing_t *)-1 @endcode if the
* transaction is freed before returning from the function.
*
* @TAG NTATAG_TPORT must point to an existing transport object for
* 'agent' (the passed tport is otherwise ignored).
*
* @sa
* nta_outgoing_tcreate(), nta_outgoing_tcancel(), nta_outgoing_destroy().
*/
nta_outgoing_t *outgoing_create(nta_agent_t *agent,
nta_response_f *callback,
nta_outgoing_magic_t *magic,
url_string_t const *route_url,
tp_name_t const *tpn,
msg_t *msg,
tag_type_t tag, tag_value_t value, ...)
{
nta_outgoing_t *orq;
sip_t *sip;
su_home_t *home;
char const *comp = NONE;
char const *branch = NONE;
char const *ack_branch = NONE;
char const *tp_ident;
int delay_sending = 0, sigcomp_zap = 0;
int pass_100 = agent->sa_pass_100, use_timestamp = agent->sa_timestamp;
enum nta_res_order_e res_order = agent->sa_res_order;
struct sigcomp_compartment *cc = NULL;
ta_list ta;
char const *scheme = NULL;
char const *port = NULL;
int invalid, resolved = 0, stateless = 0, user_via = agent->sa_user_via;
int invite_100rel = agent->sa_invite_100rel;
int explicit_transport = 1;
tagi_t const *t;
tport_t *override_tport = NULL;
if (!agent->sa_tport_ip6)
res_order = nta_res_ip4_only;
else if (!agent->sa_tport_ip4)
res_order = nta_res_ip6_only;
if (!callback)
callback = outgoing_default_cb;
if (!route_url)
route_url = (url_string_t *)agent->sa_default_proxy;
sip = sip_object(msg);
home = msg_home(msg);
if (!sip->sip_request || sip_complete_message(msg) < 0) {
SU_DEBUG_3(("nta: outgoing_create: incomplete request\n" VA_NONE));
return NULL;
}
if (!route_url && !tpn && sip->sip_route &&
sip->sip_route->r_url->url_params &&
url_param(sip->sip_route->r_url->url_params, "lr", NULL, 0))
route_url = (url_string_t *)sip->sip_route->r_url;
if (!(orq = su_zalloc(agent->sa_home, sizeof(*orq))))
return NULL;
tp_ident = tpn ? tpn->tpn_ident : NULL;
ta_start(ta, tag, value);
/* tl_gets() is a bit too slow here... */
for (t = ta_args(ta); t; t = tl_next(t)) {
tag_type_t tt = t->t_tag;
if (ntatag_stateless == tt)
stateless = t->t_value != 0;
else if (ntatag_delay_sending == tt)
delay_sending = t->t_value != 0;
else if (ntatag_branch_key == tt)
branch = (void *)t->t_value;
else if (ntatag_pass_100 == tt)
pass_100 = t->t_value != 0;
else if (ntatag_use_timestamp == tt)
use_timestamp = t->t_value != 0;
else if (ntatag_user_via == tt)
user_via = t->t_value != 0;
else if (ntatag_ack_branch == tt)
ack_branch = (void *)t->t_value;
else if (ntatag_default_proxy == tt)
route_url = (void *)t->t_value;
else if (tptag_ident == tt)
tp_ident = (void *)t->t_value;
else if (ntatag_comp == tt)
comp = (char const *)t->t_value;
else if (ntatag_sigcomp_close == tt)
sigcomp_zap = t->t_value != 0;
else if (tptag_compartment == tt)
cc = (void *)t->t_value;
else if (ntatag_tport == tt) {
override_tport = (tport_t *)t->t_value;
}
else if (ntatag_rel100 == tt) {
invite_100rel = t->t_value != 0;
}
}
orq->orq_agent = agent;
orq->orq_callback = callback;
orq->orq_magic = magic;
orq->orq_method = sip->sip_request->rq_method;
orq->orq_method_name = sip->sip_request->rq_method_name;
orq->orq_cseq = sip->sip_cseq;
orq->orq_to = sip->sip_to;
orq->orq_from = sip->sip_from;
orq->orq_call_id = sip->sip_call_id;
orq->orq_tags = tl_afilter(home, tport_tags, ta_args(ta));
orq->orq_delayed = delay_sending != 0;
orq->orq_pass_100 = pass_100 != 0;
orq->orq_sigcomp_zap = sigcomp_zap;
orq->orq_sigcomp_new = comp != NONE && comp != NULL;
orq->orq_timestamp = use_timestamp;
orq->orq_delay = UINT_MAX;
orq->orq_stateless = stateless != 0;
orq->orq_user_via = user_via != 0 && sip->sip_via;
orq->orq_100rel = invite_100rel;
orq->orq_uas = !stateless && agent->sa_is_a_uas;
if (cc)
orq->orq_cc = nta_compartment_ref(cc);
/* Add supported features */
outgoing_features(agent, orq, msg, sip, ta_args(ta));
ta_end(ta);
/* select the tport to use for the outgoing message */
if (override_tport) {
/* note: no ref taken to the tport as its only used once here */
if (tport_is_secondary(override_tport)) {
tpn = tport_name(override_tport);
orq->orq_user_tport = 1;
}
}
if (tpn) {
/* CANCEL or ACK to [3456]XX */
invalid = tport_name_dup(home, orq->orq_tpn, tpn);
#if 0 //HAVE_SOFIA_SRESOLV
/* We send ACK or CANCEL only if original request was really sent */
assert(tport_name_is_resolved(orq->orq_tpn));
#endif
resolved = tport_name_is_resolved(orq->orq_tpn);
orq->orq_url = url_hdup(home, sip->sip_request->rq_url);
}
else if (route_url && !orq->orq_user_tport) {
invalid = nta_tpn_by_url(home, orq->orq_tpn, &scheme, &port, route_url);
if (invalid >= 0) {
explicit_transport = invalid > 0;
if (override_tport) { /* Use transport protocol name from transport */
if (strcmp(orq->orq_tpn->tpn_proto, "*") == 0)
orq->orq_tpn->tpn_proto = tport_name(override_tport)->tpn_proto;
}
resolved = tport_name_is_resolved(orq->orq_tpn);
orq->orq_url = url_hdup(home, sip->sip_request->rq_url);
if (route_url != (url_string_t *)agent->sa_default_proxy)
orq->orq_route = url_hdup(home, route_url->us_url);
}
}
else {
invalid = nta_tpn_by_url(home, orq->orq_tpn, &scheme, &port,
(url_string_t *)sip->sip_request->rq_url);
if (invalid >= 0) {
explicit_transport = invalid > 0;
resolved = tport_name_is_resolved(orq->orq_tpn);
sip_fragment_clear(sip->sip_request->rq_common);
}
orq->orq_url = url_hdup(home, sip->sip_request->rq_url);
}
if (!override_tport)
orq->orq_tpn->tpn_ident = tp_ident;
else
orq->orq_tpn->tpn_ident = tport_name(override_tport)->tpn_ident;
if (comp == NULL)
orq->orq_tpn->tpn_comp = comp;
if (orq->orq_user_via && su_strmatch(orq->orq_tpn->tpn_proto, "*")) {
char const *proto = sip_via_transport(sip->sip_via);
if (proto) orq->orq_tpn->tpn_proto = proto;
}
if (branch && branch != NONE) {
if (su_casenmatch(branch, "branch=", 7))
branch = su_strdup(home, branch);
else
branch = su_sprintf(home, "branch=%s", branch);
}
else if (orq->orq_user_via && sip->sip_via->v_branch && orq->orq_method != sip_method_invite )
branch = su_sprintf(home, "branch=%s", sip->sip_via->v_branch);
else if (stateless)
branch = stateless_branch(agent, msg, sip, orq->orq_tpn);
else
branch = stateful_branch(home, agent);
orq->orq_branch = branch;
orq->orq_via_branch = branch;
if (orq->orq_method == sip_method_ack) {
/* Find the original INVITE which we are ACKing */
if (ack_branch != NULL && ack_branch != NONE) {
if (su_casenmatch(ack_branch, "branch=", 7))
orq->orq_branch = su_strdup(home, ack_branch);
else
orq->orq_branch = su_sprintf(home, "branch=%s", ack_branch);
}
else if (orq->orq_uas) {
/*
* ACK redirects further 2XX messages to it.
*
* Use orq_branch from INVITE, but put a different branch in topmost Via.
*/
nta_outgoing_t *invite = outgoing_find(agent, msg, sip, NULL);
if (invite) {
sip_t const *inv = sip_object(invite->orq_request);
orq->orq_branch = su_strdup(home, invite->orq_branch);
/* @RFC3261 section 13.2.2.4 -
* The ACK MUST contain the same credentials as the INVITE.
*/
if (!sip->sip_proxy_authorization && !sip->sip_authorization) {
if (inv->sip_proxy_authorization)
sip_add_dup(msg, sip, (void *)inv->sip_proxy_authorization);
if (inv->sip_authorization)
sip_add_dup(msg, sip, (void *)inv->sip_authorization);
}
}
else {
SU_DEBUG_1(("outgoing_create: ACK without INVITE\n" VA_NONE));
assert(!"INVITE found for ACK");
}
}
}
#if HAVE_SOFIA_SRESOLV
if (!resolved)
orq->orq_tpn->tpn_port = port;
orq->orq_resolved = resolved;
#else
orq->orq_resolved = resolved = 1;
#endif
orq->orq_sips = su_casematch(scheme, "sips");
if (invalid < 0 || !orq->orq_branch || msg_serialize(msg, (void *)sip) < 0) {
SU_DEBUG_3(("nta outgoing create: %s\n",
invalid < 0 ? "invalid URI" :
!orq->orq_branch ? "no branch" : "invalid message"));
outgoing_free(orq);
return NULL;
}
/* Now we are committed in sending the transaction */
orq->orq_request = msg;
agent->sa_stats->as_client_tr++;
orq->orq_hash = NTA_HASH(sip->sip_call_id, sip->sip_cseq->cs_seq);
if (orq->orq_user_tport)
outgoing_send_via(orq, override_tport);
else if (resolved)
outgoing_prepare_send(orq);
#if HAVE_SOFIA_SRESOLV
else
outgoing_resolve(orq, explicit_transport, res_order);
#endif
if (stateless &&
orq->orq_status >= 200 &&
callback == outgoing_default_cb) {
void *retval;
if (orq->orq_status < 300)
retval = (void *)-1; /* NONE */
else
retval = NULL, orq->orq_request = NULL;
outgoing_free(orq);
return retval;
}
assert(orq->orq_queue);
outgoing_insert(agent, orq);
return orq;
}
/** Prepare sending a request */
static void
outgoing_prepare_send(nta_outgoing_t *orq)
{
nta_agent_t *sa = orq->orq_agent;
tport_t *tp;
tp_name_t *tpn = orq->orq_tpn;
/* Select transport by scheme */
if (orq->orq_sips && strcmp(tpn->tpn_proto, "*") == 0)
tpn->tpn_proto = "tls";
if (!tpn->tpn_port)
tpn->tpn_port = "";
tp = tport_by_name(sa->sa_tports, tpn);
if (tpn->tpn_port[0] == '\0') {
if (orq->orq_sips || tport_has_tls(tp))
tpn->tpn_port = "5061";
else
tpn->tpn_port = "5060";
}
if (tp) {
outgoing_send_via(orq, tp);
}
else if (orq->orq_sips) {
SU_DEBUG_3(("nta outgoing create: no secure transport\n" VA_NONE));
outgoing_reply(orq, SIP_416_UNSUPPORTED_URI, 1);
}
else {
SU_DEBUG_3(("nta outgoing create: no transport protocol\n" VA_NONE));
outgoing_reply(orq, 503, "No transport", 1);
}
}
/** Send request using given transport */
static void
outgoing_send_via(nta_outgoing_t *orq, tport_t *tp)
{
tport_t *old_tp = orq->orq_tport;
orq->orq_tport = tport_ref(tp);
if (orq->orq_pending && tp != old_tp) {
tport_release(old_tp, orq->orq_pending,
orq->orq_request, NULL, orq, 0);
orq->orq_pending = 0;
}
if (old_tp) tport_unref(old_tp);
if (outgoing_insert_via(orq, agent_tport_via(tp)) < 0) {
SU_DEBUG_3(("nta outgoing create: cannot insert Via line\n" VA_NONE));
outgoing_reply(orq, 503, "Cannot insert Via", 1);
return;
}
#if HAVE_SOFIA_SMIME
{
sm_object_t *smime = sa->sa_smime;
sip_t *sip = sip_object(orq->orq_request);
if (sa->sa_smime &&
(sip->sip_request->rq_method == sip_method_invite ||
sip->sip_request->rq_method == sip_method_message)) {
msg_prepare(orq->orq_request);
if (sm_encode_message(smime, msg, sip, SM_ID_NULL) < 0) {
outgoing_tport_error(sa, orq, NULL,
orq->orq_request, su_errno());
return;
}
}
}
#endif
orq->orq_prepared = 1;
if (orq->orq_delayed) {
SU_DEBUG_5(("nta: delayed sending %s (%u)\n",
orq->orq_method_name, orq->orq_cseq->cs_seq));
outgoing_queue(orq->orq_agent->sa_out.delayed, orq);
return;
}
outgoing_send(orq, 0);
}
/** Send a request */
static void
outgoing_send(nta_outgoing_t *orq, int retransmit)
{
int err;
tp_name_t const *tpn = orq->orq_tpn;
msg_t *msg = orq->orq_request;
nta_agent_t *agent = orq->orq_agent;
tport_t *tp;
int once = 0;
su_time_t now = su_now();
tag_type_t tag = tag_skip;
tag_value_t value = 0;
struct sigcomp_compartment *cc; cc = NULL;
/* tport can be NULL if we are just switching network */
if (orq->orq_tport == NULL) {
outgoing_tport_error(agent, orq, NULL, orq->orq_request, ENETRESET);
return;
}
if (orq->orq_user_tport && !tport_is_clear_to_send(orq->orq_tport)) {
outgoing_tport_error(agent, orq, NULL, orq->orq_request, EPIPE);
return;
}
if (!retransmit)
orq->orq_sent = now;
if (orq->orq_timestamp) {
sip_t *sip = sip_object(msg);
sip_timestamp_t *ts =
sip_timestamp_format(msg_home(msg), "%lu.%06lu",
now.tv_sec, now.tv_usec);
if (ts) {
if (sip->sip_timestamp)
msg_header_remove(msg, (msg_pub_t *)sip, (msg_header_t *)sip->sip_timestamp);
msg_header_insert(msg, (msg_pub_t *)sip, (msg_header_t *)ts);
}
}
for (;;) {
if (tpn->tpn_comp == NULL) {
/* xyzzy */
}
else if (orq->orq_cc) {
cc = orq->orq_cc, orq->orq_cc = NULL;
}
else {
cc = agent_compression_compartment(agent, orq->orq_tport, tpn,
orq->orq_sigcomp_new);
}
if (orq->orq_try_udp_instead)
tag = tptag_mtu, value = 65535;
if (orq->orq_pending) {
tport_release(orq->orq_tport, orq->orq_pending,
orq->orq_request, NULL, orq, 0);
orq->orq_pending = 0;
}
tp = tport_tsend(orq->orq_tport, msg, tpn,
tag, value,
IF_SIGCOMP_TPTAG_COMPARTMENT(cc)
TAG_NEXT(orq->orq_tags));
if (tp)
break;
err = msg_errno(orq->orq_request);
if (cc)
nta_compartment_decref(&cc);
if (orq->orq_user_tport)
/* No retries */;
/* RFC3261, 18.1.1 */
else if (err == EMSGSIZE && !orq->orq_try_tcp_instead) {
if (su_casematch(tpn->tpn_proto, "udp") ||
su_casematch(tpn->tpn_proto, "*")) {
outgoing_try_tcp_instead(orq);
continue;
}
}
else if (err == ECONNREFUSED && orq->orq_try_tcp_instead) {
if (su_casematch(tpn->tpn_proto, "tcp") && msg_size(msg) <= 65535) {
outgoing_try_udp_instead(orq, 0);
continue;
}
}
else if (err == EPIPE) {
/* Connection was closed */
if (!once++) {
orq->orq_retries++;
continue;
}
}
outgoing_tport_error(agent, orq, NULL, orq->orq_request, err);
return;
}
agent->sa_stats->as_sent_msg++;
agent->sa_stats->as_sent_request++;
if (retransmit)
agent->sa_stats->as_retry_request++;
SU_DEBUG_5(("nta: %ssent %s (%u) to " TPN_FORMAT "\n",
retransmit ? "re" : "",
orq->orq_method_name, orq->orq_cseq->cs_seq,
TPN_ARGS(tpn)));
if (cc) {
if (orq->orq_cc)
nta_compartment_decref(&orq->orq_cc);
}
if (orq->orq_pending) {
assert(orq->orq_tport);
tport_release(orq->orq_tport, orq->orq_pending,
orq->orq_request, NULL, orq, 0);
orq->orq_pending = 0;
}
if (orq->orq_stateless) {
outgoing_reply(orq, 202, NULL, 202);
return;
}
if (orq->orq_method != sip_method_ack) {
orq->orq_pending = tport_pend(tp, orq->orq_request,
outgoing_tport_error, orq);
if (orq->orq_pending < 0)
orq->orq_pending = 0;
}
if (tp != orq->orq_tport) {
tport_decref(&orq->orq_tport);
orq->orq_tport = tport_ref(tp);
}
orq->orq_reliable = tport_is_reliable(tp);
if (retransmit)
return;
outgoing_trying(orq); /* Timer B / F */
if (orq->orq_method == sip_method_ack)
;
else if (!orq->orq_reliable) {
/* race condition on initial t1 timer timeout, set minimum initial timeout to 1000ms */
unsigned t1_timer = agent->sa_t1;
if (t1_timer < 1000) t1_timer = 1000;
outgoing_set_timer(orq, t1_timer); /* Timer A/E */
} else if (orq->orq_try_tcp_instead && !tport_is_connected(tp))
outgoing_set_timer(orq, agent->sa_t4); /* Timer N3 */
}
static void
outgoing_try_tcp_instead(nta_outgoing_t *orq)
{
tport_t *tp;
tp_name_t tpn[1];
assert(orq->orq_pending == 0);
*tpn = *orq->orq_tpn;
tpn->tpn_proto = "tcp";
orq->orq_try_tcp_instead = 1;
tp = tport_by_name(orq->orq_agent->sa_tports, tpn);
if (tp && tp != orq->orq_tport) {
sip_t *sip = sip_object(orq->orq_request);
sip_fragment_clear(sip->sip_via->v_common);
sip->sip_via->v_protocol = sip_transport_tcp;
SU_DEBUG_5(("nta: %s (%u) too large for UDP, trying TCP\n",
orq->orq_method_name, orq->orq_cseq->cs_seq));
orq->orq_tpn->tpn_proto = "tcp";
tport_decref(&orq->orq_tport);
orq->orq_tport = tport_ref(tp);
return;
}
/* No TCP - try again with UDP without SIP MTU limit */
tpn->tpn_proto = "udp";
orq->orq_try_udp_instead = 1;
tp = tport_by_name(orq->orq_agent->sa_tports, tpn);
if (tp && tp != orq->orq_tport) {
SU_DEBUG_5(("nta: %s (%u) exceed normal UDP size limit\n",
orq->orq_method_name, orq->orq_cseq->cs_seq));
tport_decref(&orq->orq_tport);
orq->orq_tport = tport_ref(tp);
}
}
static void
outgoing_try_udp_instead(nta_outgoing_t *orq, int timeout)
{
tport_t *tp;
tp_name_t tpn[1];
if (orq->orq_pending) {
tport_release(orq->orq_tport, orq->orq_pending,
orq->orq_request, NULL, orq, 0);
orq->orq_pending = 0;
}
*tpn = *orq->orq_tpn;
tpn->tpn_proto = "udp";
orq->orq_try_udp_instead = 1;
tp = tport_by_name(orq->orq_agent->sa_tports, tpn);
if (tp && tp != orq->orq_tport) {
sip_t *sip = sip_object(orq->orq_request);
sip_fragment_clear(sip->sip_via->v_common);
sip->sip_via->v_protocol = sip_transport_udp;
SU_DEBUG_5(("nta: %s (%u) TCP %s, trying UDP\n",
orq->orq_method_name, orq->orq_cseq->cs_seq,
timeout ? "times out" : "refused"));
orq->orq_tpn->tpn_proto = "udp";
tport_decref(&orq->orq_tport);
orq->orq_tport = tport_ref(tp);
}
}
/** @internal Report transport errors. */
void
outgoing_tport_error(nta_agent_t *agent, nta_outgoing_t *orq,
tport_t *tp, msg_t *msg, int error)
{
tp_name_t const *tpn = tp ? tport_name(tp) : orq->orq_tpn;
if (orq->orq_pending) {
assert(orq->orq_tport);
tport_release(orq->orq_tport, orq->orq_pending, orq->orq_request,
NULL, orq, 0);
orq->orq_pending = 0;
}
if (error == EPIPE && orq->orq_retries++ == 0) {
/* XXX - we should retry only if the transport is not newly created */
outgoing_print_tport_error(orq, 5, "retrying once after ",
tpn, msg, error);
outgoing_send(orq, 1);
return;
}
else if (error == ECONNREFUSED && orq->orq_try_tcp_instead) {
/* RFC3261, 18.1.1 */
if (su_casematch(tpn->tpn_proto, "tcp") && msg_size(msg) <= 65535) {
outgoing_print_tport_error(orq, 5, "retrying with UDP after ",
tpn, msg, error);
outgoing_try_udp_instead(orq, 0);
outgoing_remove(orq); /* Reset state - this is no resend! */
outgoing_send(orq, 0); /* Send */
return;
}
}
else if (error == 0) {
/*
* Server closed connection. RFC3261:
* "there is no coupling between TCP connection state and SIP
* processing."
*/
return;
}
if (outgoing_other_destinations(orq)) {
outgoing_print_tport_error(orq, 5, "trying alternative server after ",
tpn, msg, error);
outgoing_try_another(orq);
return;
}
outgoing_print_tport_error(orq, 3, "", tpn, msg, error);
outgoing_reply(orq, SIP_503_SERVICE_UNAVAILABLE, 0);
}
static
void
outgoing_print_tport_error(nta_outgoing_t *orq, int level, char *todo,
tp_name_t const *tpn, msg_t *msg, int error)
{
su_sockaddr_t const *su = msg_addr(msg);
char addr[SU_ADDRSIZE];
su_llog(nta_log, level,
"nta: %s (%u): %s%s (%u) with %s/[%s]:%u\n",
orq->orq_method_name, orq->orq_cseq->cs_seq,
todo, su_strerror(error), error,
tpn->tpn_proto,
su_inet_ntop(su->su_family, SU_ADDR(su), addr, sizeof(addr)),
htons(su->su_port));
}
/**@internal
* Add features supported.
*/
static
int outgoing_features(nta_agent_t *agent, nta_outgoing_t *orq,
msg_t *msg, sip_t *sip,
tagi_t *tags)
{
char const *supported[8];
int i;
if (orq->orq_method != sip_method_invite) /* fast path for now */
return 0;
supported[i = 0] = NULL;
if (orq->orq_method == sip_method_invite) {
int require_100rel = sip_has_feature(sip->sip_require, "100rel");
if (require_100rel) {
orq->orq_must_100rel = 1;
orq->orq_100rel = 1;
}
else if (sip_has_feature(sip->sip_supported, "100rel")) {
orq->orq_100rel = 1;
}
else if (orq->orq_100rel) {
supported[i++] = "100rel";
}
}
if (i) {
supported[i] = NULL;
if (sip->sip_supported) {
su_home_t *home = msg_home(msg);
return msg_list_append_items(home, sip->sip_supported, supported);
}
else {
sip_supported_t s[1];
sip_supported_init(s);
s->k_items = supported;
return sip_add_dup(msg, sip, (sip_header_t *)s);
}
}
return 0;
}
/**@internal
* Insert outgoing request to agent hash table
*/
static
void outgoing_insert(nta_agent_t *agent, nta_outgoing_t *orq)
{
if (outgoing_htable_is_full(agent->sa_outgoing))
outgoing_htable_resize(agent->sa_home, agent->sa_outgoing, 0);
outgoing_htable_insert(agent->sa_outgoing, orq);
orq->orq_inserted = 1;
}
/** @internal
* Initialize a queue for outgoing transactions.
*/
static void
outgoing_queue_init(outgoing_queue_t *queue, unsigned timeout)
{
memset(queue, 0, sizeof *queue);
queue->q_tail = &queue->q_head;
queue->q_timeout = timeout;
}
/** Change the timeout value of a queue */
static void
outgoing_queue_adjust(nta_agent_t *sa,
outgoing_queue_t *queue,
unsigned timeout)
{
nta_outgoing_t *orq;
uint32_t latest;
if (timeout >= queue->q_timeout || !queue->q_head) {
queue->q_timeout = timeout;
return;
}
latest = set_timeout(sa, queue->q_timeout = timeout);
for (orq = queue->q_head; orq; orq = orq->orq_next) {
if (orq->orq_timeout == 0 ||
(int32_t)(orq->orq_timeout - latest) > 0)
orq->orq_timeout = latest;
}
}
/** @internal
* Test if an outgoing transaction is in a queue.
*/
su_inline int
outgoing_is_queued(nta_outgoing_t const *orq)
{
return orq && orq->orq_queue;
}
/** @internal
* Insert an outgoing transaction into a queue.
*
* Insert a client transaction into a queue and set the corresponding
* timeout at the same time.
*/
static void
outgoing_queue(outgoing_queue_t *queue,
nta_outgoing_t *orq)
{
if (orq->orq_queue == queue) {
//assert(queue->q_timeout == 0);
return;
}
assert(!orq->orq_forked);
if (outgoing_is_queued(orq))
outgoing_remove(orq);
orq->orq_timeout = set_timeout(orq->orq_agent, queue->q_timeout);
orq->orq_queue = queue;
orq->orq_prev = queue->q_tail;
*queue->q_tail = orq;
queue->q_tail = &orq->orq_next;
queue->q_length++;
}
/** @internal
* Remove an outgoing transaction from a queue.
*/
su_inline
void outgoing_remove(nta_outgoing_t *orq)
{
assert(outgoing_is_queued(orq));
assert(orq->orq_queue->q_length > 0);
if ((*orq->orq_prev = orq->orq_next))
orq->orq_next->orq_prev = orq->orq_prev;
else
orq->orq_queue->q_tail = orq->orq_prev;
orq->orq_queue->q_length--;
orq->orq_next = NULL;
orq->orq_prev = NULL;
orq->orq_queue = NULL;
orq->orq_timeout = 0;
}
/** Set retransmit timer (orq_retry).
*
* Set the retry timer (B/D) on the outgoing request (client transaction).
*/
su_inline
void outgoing_set_timer(nta_outgoing_t *orq, uint32_t interval)
{
nta_outgoing_t **rq;
assert(orq);
if (interval == 0) {
outgoing_reset_timer(orq);
return;
}
if (orq->orq_rprev) {
/* Remove transaction from retry dequeue, re-insert it later. */
if ((*orq->orq_rprev = orq->orq_rnext))
orq->orq_rnext->orq_rprev = orq->orq_rprev;
if (orq->orq_agent->sa_out.re_t1 == &orq->orq_rnext)
orq->orq_agent->sa_out.re_t1 = orq->orq_rprev;
}
else {
orq->orq_agent->sa_out.re_length++;
}
orq->orq_retry = set_timeout(orq->orq_agent, orq->orq_interval = interval);
/* Shortcut into queue at SIP T1 */
rq = orq->orq_agent->sa_out.re_t1;
if (!(*rq) || (int32_t)((*rq)->orq_retry - orq->orq_retry) > 0)
rq = &orq->orq_agent->sa_out.re_list;
while (*rq && (int32_t)((*rq)->orq_retry - orq->orq_retry) <= 0)
rq = &(*rq)->orq_rnext;
if ((orq->orq_rnext = *rq))
orq->orq_rnext->orq_rprev = &orq->orq_rnext;
*rq = orq;
orq->orq_rprev = rq;
if (interval == orq->orq_agent->sa_t1)
orq->orq_agent->sa_out.re_t1 = rq;
}
static
void outgoing_reset_timer(nta_outgoing_t *orq)
{
if (orq->orq_rprev) {
if ((*orq->orq_rprev = orq->orq_rnext))
orq->orq_rnext->orq_rprev = orq->orq_rprev;
if (orq->orq_agent->sa_out.re_t1 == &orq->orq_rnext)
orq->orq_agent->sa_out.re_t1 = orq->orq_rprev;
orq->orq_agent->sa_out.re_length--;
}
orq->orq_interval = 0, orq->orq_retry = 0;
orq->orq_rnext = NULL, orq->orq_rprev = NULL;
}
/** @internal
* Free resources associated with the request.
*/
static
void outgoing_free(nta_outgoing_t *orq)
{
SU_DEBUG_9(("nta: outgoing_free(%p)\n", (void *)orq));
assert(orq->orq_forks == NULL && orq->orq_forking == NULL);
outgoing_cut_off(orq);
outgoing_reclaim(orq);
}
/** Remove outgoing request from hash tables */
su_inline void
outgoing_cut_off(nta_outgoing_t *orq)
{
nta_agent_t *agent = orq->orq_agent;
if (orq->orq_default)
agent->sa_default_outgoing = NULL;
if (orq->orq_inserted)
outgoing_htable_remove(agent->sa_outgoing, orq), orq->orq_inserted = 0;
if (outgoing_is_queued(orq))
outgoing_remove(orq);
#if 0
if (orq->orq_forked)
outgoing_remove_fork(orq);
#endif
outgoing_reset_timer(orq);
if (orq->orq_pending) {
tport_release(orq->orq_tport, orq->orq_pending,
orq->orq_request, NULL, orq, 0);
}
orq->orq_pending = 0;
if (orq->orq_cc)
nta_compartment_decref(&orq->orq_cc);
if (orq->orq_tport)
tport_decref(&orq->orq_tport);
}
/** Reclaim outgoing request */
su_inline
void outgoing_reclaim(nta_outgoing_t *orq)
{
if (orq->orq_status2b)
*orq->orq_status2b = -1;
if (orq->orq_request)
msg_destroy(orq->orq_request), orq->orq_request = NULL;
if (orq->orq_response)
msg_destroy(orq->orq_response), orq->orq_response = NULL;
#if HAVE_SOFIA_SRESOLV
if (orq->orq_resolver)
outgoing_destroy_resolver(orq);
#endif
su_free(orq->orq_agent->sa_home, orq);
}
/** Queue request to be freed */
su_inline
void outgoing_free_queue(outgoing_queue_t *q, nta_outgoing_t *orq)
{
outgoing_cut_off(orq);
outgoing_queue(q, orq);
}
/** Reclaim memory used by queue of requests */
static
void outgoing_reclaim_queued(su_root_magic_t *rm,
su_msg_r msg,
union sm_arg_u *u)
{
outgoing_queue_t *q = u->a_outgoing_queue;
nta_outgoing_t *orq, *orq_next;
SU_DEBUG_9(("outgoing_reclaim_all(%p, %p, %p)\n",
(void *)rm, (void *)msg, (void *)u));
for (orq = q->q_head; orq; orq = orq_next) {
orq_next = orq->orq_next;
outgoing_reclaim(orq);
}
}
/** @internal Default callback for request */
int outgoing_default_cb(nta_outgoing_magic_t *magic,
nta_outgoing_t *orq,
sip_t const *sip)
{
if (sip == NULL || sip->sip_status->st_status >= 200)
outgoing_destroy(orq);
return 0;
}
/** @internal Destroy an outgoing transaction */
void outgoing_destroy(nta_outgoing_t *orq)
{
if (orq->orq_terminated || orq->orq_default) {
if (!orq->orq_forking && !orq->orq_forks) {
outgoing_free(orq);
return;
}
}
/* Application is expected to handle 200 OK statelessly
=> kill transaction immediately */
else if (orq->orq_method == sip_method_invite && !orq->orq_completed
/* (unless transaction has been canceled) */
&& !orq->orq_canceled
/* or it has been forked */
&& !orq->orq_forking && !orq->orq_forks) {
orq->orq_destroyed = 1;
outgoing_terminate(orq);
return;
}
orq->orq_destroyed = 1;
orq->orq_callback = outgoing_default_cb;
orq->orq_magic = NULL;
}
/** @internal Outgoing transaction timer routine.
*
*/
static void
_nta_outgoing_timer(nta_agent_t *sa)
{
uint32_t now = su_time_ms(su_now());
nta_outgoing_t *orq;
outgoing_queue_t rq[1];
size_t retransmitted = 0, terminated = 0, timeout = 0, destroyed;
size_t total = sa->sa_outgoing->oht_used;
size_t trying = sa->sa_out.re_length;
size_t pending = sa->sa_out.trying->q_length +
sa->sa_out.inv_calling->q_length;
size_t completed = sa->sa_out.completed->q_length +
sa->sa_out.inv_completed->q_length;
outgoing_queue_init(sa->sa_out.free = rq, 0);
while ((orq = sa->sa_out.re_list)) {
now = su_time_ms(su_now());
if ((int32_t)(orq->orq_retry - now) > 0)
break;
if (retransmitted >= timer_max_retransmit)
break;
if (orq->orq_reliable) {
outgoing_reset_timer(orq);
if (!tport_is_connected(orq->orq_tport)) {
/*
* Timer N3: try to use UDP if trying to send via TCP
* but no connection is established within SIP T4
*/
SU_DEBUG_5(("nta: timer %s fired, %s %s (%u)\n", "N3",
"try UDP instead for",
orq->orq_method_name, orq->orq_cseq->cs_seq));
outgoing_try_udp_instead(orq, 1);
outgoing_remove(orq); /* Reset state - this is no resend! */
outgoing_send(orq, 0); /* Send */
}
continue;
}
assert(!orq->orq_reliable && orq->orq_interval != 0);
SU_DEBUG_5(("nta: timer %s fired, %s %s (%u)\n",
orq->orq_method == sip_method_invite ? "A" : "E",
"retransmit", orq->orq_method_name, orq->orq_cseq->cs_seq));
outgoing_retransmit(orq);
if (orq->orq_method == sip_method_invite ||
2U * orq->orq_interval < sa->sa_t2)
outgoing_set_timer(orq, 2U * orq->orq_interval);
else
outgoing_set_timer(orq, sa->sa_t2);
if (++retransmitted % 5 == 0)
su_root_yield(sa->sa_root); /* Handle received packets */
}
terminated
= outgoing_timer_dk(sa->sa_out.inv_completed, "D", now)
+ outgoing_timer_dk(sa->sa_out.completed, "K", now);
timeout
= outgoing_timer_bf(sa->sa_out.inv_calling, "B", now)
+ outgoing_timer_c(sa->sa_out.inv_proceeding, "C", now)
+ outgoing_timer_bf(sa->sa_out.trying, "F", now);
destroyed = outgoing_mass_destroy(sa, rq);
sa->sa_out.free = NULL;
if (retransmitted || timeout || terminated || destroyed) {
SU_DEBUG_5(("nta_outgoing_timer: "
MOD_ZU"/"MOD_ZU" resent, "
MOD_ZU"/"MOD_ZU" tout, "
MOD_ZU"/"MOD_ZU" term, "
MOD_ZU"/"MOD_ZU" free\n",
retransmitted, trying,
timeout, pending,
terminated, completed,
destroyed, total));
}
}
/** @internal Retransmit the outgoing request. */
void outgoing_retransmit(nta_outgoing_t *orq)
{
if (orq->orq_prepared && !orq->orq_delayed) {
orq->orq_retries++;
if (orq->orq_retries >= 4 && orq->orq_cc) {
orq->orq_tpn->tpn_comp = NULL;
if (orq->orq_retries == 4) {
agent_close_compressor(orq->orq_agent, orq->orq_cc);
nta_compartment_decref(&orq->orq_cc);
}
}
outgoing_send(orq, 1);
}
}
/** Trying a client transaction. */
static
void outgoing_trying(nta_outgoing_t *orq)
{
if (orq->orq_forked)
;
else if (orq->orq_method == sip_method_invite)
outgoing_queue(orq->orq_agent->sa_out.inv_calling, orq);
else
outgoing_queue(orq->orq_agent->sa_out.trying, orq);
}
/** Handle timers B and F */
static
size_t outgoing_timer_bf(outgoing_queue_t *q,
char const *timer,
uint32_t now)
{
nta_outgoing_t *orq;
size_t timeout = 0;
while ((orq = q->q_head)) {
if ((int32_t)(orq->orq_timeout - now) > 0 ||
timeout >= timer_max_timeout)
break;
timeout++;
SU_DEBUG_5(("nta: timer %s fired, %s %s (%u)\n",
timer,
orq->orq_method != sip_method_ack ? "timeout" : "terminating",
orq->orq_method_name, orq->orq_cseq->cs_seq));
if (orq->orq_method != sip_method_ack)
outgoing_timeout(orq, now);
else
outgoing_terminate(orq);
assert(q->q_head != orq || (int32_t)(orq->orq_timeout - now) > 0);
}
return timeout;
}
/** Handle timer C */
static
size_t outgoing_timer_c(outgoing_queue_t *q,
char const *timer,
uint32_t now)
{
nta_outgoing_t *orq;
size_t timeout = 0;
if (q->q_timeout == 0)
return 0;
while ((orq = q->q_head)) {
if ((int32_t)(orq->orq_timeout - now) > 0 || timeout >= timer_max_timeout)
break;
timeout++;
SU_DEBUG_5(("nta: timer %s fired, %s %s (%u)\n",
timer, "CANCEL and timeout",
orq->orq_method_name, orq->orq_cseq->cs_seq));
/*
* If the client transaction has received a provisional response, the
* proxy MUST generate a CANCEL request matching that transaction.
*/
nta_outgoing_tcancel(orq, NULL, NULL, TAG_NULL());
}
return timeout;
}
/** @internal Signal transaction timeout to the application. */
void outgoing_timeout(nta_outgoing_t *orq, uint32_t now)
{
nta_outgoing_t *cancel = NULL;
if (orq->orq_status || orq->orq_canceled)
;
else if (outgoing_other_destinations(orq)) {
SU_DEBUG_5(("%s(%p): %s\n", "nta", (void *)orq,
"try next after timeout"));
outgoing_try_another(orq);
return;
}
cancel = orq->orq_cancel, orq->orq_cancel = NULL;
orq->orq_agent->sa_stats->as_tout_request++;
outgoing_reply(orq, SIP_408_REQUEST_TIMEOUT, 0);
if (cancel)
outgoing_timeout(cancel, now);
}
/** Complete a client transaction.
*
* @return True if transaction was free()d.
*/
static int
outgoing_complete(nta_outgoing_t *orq)
{
orq->orq_completed = 1;
outgoing_reset_timer(orq); /* Timer A / Timer E */
if (orq->orq_stateless)
return outgoing_terminate(orq);
if (orq->orq_forked) {
outgoing_remove_fork(orq);
return outgoing_terminate(orq);
}
if (orq->orq_reliable) {
if (orq->orq_method != sip_method_invite || !orq->orq_uas)
return outgoing_terminate(orq);
}
if (orq->orq_method == sip_method_invite) {
if (orq->orq_queue != orq->orq_agent->sa_out.inv_completed)
outgoing_queue(orq->orq_agent->sa_out.inv_completed, orq); /* Timer D */
}
else {
outgoing_queue(orq->orq_agent->sa_out.completed, orq); /* Timer K */
}
return 0;
}
/** Handle timers D and K */
static
size_t outgoing_timer_dk(outgoing_queue_t *q,
char const *timer,
uint32_t now)
{
nta_outgoing_t *orq;
size_t terminated = 0;
while ((orq = q->q_head)) {
if ((int32_t)(orq->orq_timeout - now) > 0 ||
terminated >= timer_max_terminate)
break;
terminated++;
SU_DEBUG_5(("nta: timer %s fired, %s %s (%u)\n", timer,
"terminate", orq->orq_method_name, orq->orq_cseq->cs_seq));
if (orq->orq_method == sip_method_invite)
outgoing_terminate_invite(orq);
else
outgoing_terminate(orq);
}
return terminated;
}
/** Terminate an INVITE client transaction. */
static void
outgoing_terminate_invite(nta_outgoing_t *original)
{
nta_outgoing_t *orq = original;
while (original->orq_forks) {
orq = original->orq_forks;
original->orq_forks = orq->orq_forks;
assert(orq->orq_forking == original);
SU_DEBUG_5(("nta: timer %s fired, %s %s (%u);tag=%s\n", "D",
"terminate", orq->orq_method_name, orq->orq_cseq->cs_seq,
orq->orq_tag));
orq->orq_forking = NULL, orq->orq_forks = NULL, orq->orq_forked = 0;
if (outgoing_terminate(orq))
continue;
if (orq->orq_status < 200) {
/* Fork has timed out */
orq->orq_agent->sa_stats->as_tout_request++;
outgoing_reply(orq, SIP_408_REQUEST_TIMEOUT, 0);
}
}
if (outgoing_terminate(orq = original))
return;
if (orq->orq_status < 200) {
/* Original INVITE has timed out */
orq->orq_agent->sa_stats->as_tout_request++;
outgoing_reply(orq, SIP_408_REQUEST_TIMEOUT, 0);
}
}
static void
outgoing_remove_fork(nta_outgoing_t *orq)
{
nta_outgoing_t **slot;
for (slot = &orq->orq_forking->orq_forks;
*slot;
slot = &(*slot)->orq_forks) {
if (orq == *slot) {
*slot = orq->orq_forks;
orq->orq_forks = NULL;
orq->orq_forking = NULL;
orq->orq_forked = 0;
}
}
assert(orq == NULL);
}
/** Terminate a client transaction. */
static
int outgoing_terminate(nta_outgoing_t *orq)
{
orq->orq_terminated = 1;
if (!orq->orq_destroyed) {
outgoing_queue(orq->orq_agent->sa_out.terminated, orq);
return 0;
}
else if (orq->orq_agent->sa_out.free) {
outgoing_free_queue(orq->orq_agent->sa_out.free, orq);
return 1;
}
else {
outgoing_free(orq);
return 1;
}
}
/** Mass destroy client transactions */
static
size_t outgoing_mass_destroy(nta_agent_t *sa, outgoing_queue_t *q)
{
size_t destroyed = q->q_length;
if (destroyed > 2 && *sa->sa_terminator) {
su_msg_r m = SU_MSG_R_INIT;
if (su_msg_create(m,
su_clone_task(sa->sa_terminator),
su_root_task(sa->sa_root),
outgoing_reclaim_queued,
sizeof(outgoing_queue_t)) == SU_SUCCESS) {
outgoing_queue_t *mq = su_msg_data(m)->a_outgoing_queue;
*mq = *q;
if (su_msg_send(m) == SU_SUCCESS)
q->q_length = 0;
}
}
if (q->q_length)
outgoing_reclaim_queued(NULL, NULL, (void*)q);
return destroyed;
}
/** Find an outgoing request corresponging to a message and @Via line.
*
* Return an outgoing request object based on a message and the @Via line
* given as argument. This function is used when doing loop checking: if we
* have sent the request and it has been routed back to us.
*
* @param agent
* @param msg
* @param sip
* @param v
*/
nta_outgoing_t *nta_outgoing_find(nta_agent_t const *agent,
msg_t const *msg,
sip_t const *sip,
sip_via_t const *v)
{
if (agent == NULL || msg == NULL || sip == NULL || v == NULL) {
su_seterrno(EFAULT);
return NULL;
}
return outgoing_find(agent, msg, sip, v);
}
/**@internal
*
* Find an outgoing request corresponging to a message and @Via line.
*
*/
nta_outgoing_t *outgoing_find(nta_agent_t const *sa,
msg_t const *msg,
sip_t const *sip,
sip_via_t const *v)
{
nta_outgoing_t **oo, *orq;
outgoing_htable_t const *oht = sa->sa_outgoing;
sip_cseq_t const *cseq = sip->sip_cseq;
sip_call_id_t const *i = sip->sip_call_id;
hash_value_t hash;
sip_method_t method, method2;
unsigned short status = sip->sip_status ? sip->sip_status->st_status : 0;
if (cseq == NULL)
return NULL;
hash = NTA_HASH(i, cseq->cs_seq);
method = cseq->cs_method;
/* Get original invite when ACKing */
if (sip->sip_request && method == sip_method_ack && v == NULL)
method = sip_method_invite, method2 = sip_method_invalid;
else if (sa->sa_is_a_uas && 200 <= status && status < 300 && method == sip_method_invite)
method2 = sip_method_ack;
else
method2 = method;
for (oo = outgoing_htable_hash(oht, hash);
(orq = *oo);
oo = outgoing_htable_next(oht, oo)) {
if (orq->orq_stateless)
continue;
/* Accept terminated transactions when looking for original INVITE */
if (orq->orq_terminated && method2 != sip_method_invalid)
continue;
if (hash != orq->orq_hash)
continue;
if (orq->orq_call_id->i_hash != i->i_hash ||
strcmp(orq->orq_call_id->i_id, i->i_id))
continue;
if (orq->orq_cseq->cs_seq != cseq->cs_seq)
continue;
if (method == sip_method_unknown &&
strcmp(orq->orq_cseq->cs_method_name, cseq->cs_method_name))
continue;
if (orq->orq_method != method && orq->orq_method != method2)
continue;
if (su_strcasecmp(orq->orq_from->a_tag, sip->sip_from->a_tag))
continue;
if (orq->orq_to->a_tag &&
su_strcasecmp(orq->orq_to->a_tag, sip->sip_to->a_tag))
continue;
if (orq->orq_method == sip_method_ack && 300 <= status)
continue;
if (v && !su_casematch(orq->orq_branch + strlen("branch="), v->v_branch))
continue;
break; /* match */
}
return orq;
}
/** Process a response message. */
int outgoing_recv(nta_outgoing_t *_orq,
int status,
msg_t *msg,
sip_t *sip)
{
nta_outgoing_t *orq = _orq->orq_forking ? _orq->orq_forking : _orq;
nta_agent_t *sa = orq->orq_agent;
int internal = sip == NULL || (sip->sip_flags & NTA_INTERNAL_MSG) != 0;
assert(!internal || status >= 300);
assert(orq == _orq || orq->orq_method == sip_method_invite);
if (status < 100) status = 100;
if (!internal && orq->orq_delay == UINT_MAX)
outgoing_estimate_delay(orq, sip);
if (orq->orq_cc)
agent_accept_compressed(orq->orq_agent, msg, orq->orq_cc);
if (orq->orq_cancel) {
nta_outgoing_t *cancel;
cancel = orq->orq_cancel; orq->orq_cancel = NULL;
cancel->orq_delayed = 0;
if (status < 200) {
outgoing_send(cancel, 0);
outgoing_complete(orq);
}
else {
outgoing_reply(cancel, SIP_481_NO_TRANSACTION, 0);
}
}
if (orq->orq_pending) {
tport_release(orq->orq_tport, orq->orq_pending, orq->orq_request,
msg, orq, status < 200);
if (status >= 200)
orq->orq_pending = 0;
}
/* The state machines */
if (orq->orq_method == sip_method_invite) {
nta_outgoing_t *original = orq;
orq = _orq;
if (orq->orq_destroyed && 200 <= status && status < 300) {
if (orq->orq_uas && su_strcasecmp(sip->sip_to->a_tag, orq->orq_tag) != 0) {
/* Orphan 200 Ok to INVITE. ACK and BYE it */
SU_DEBUG_5(("nta: Orphan 200 Ok send ACK&BYE %p\n", (void *)orq));
return nta_msg_ackbye(sa, msg);
}
return -1; /* Proxy statelessly (RFC3261 section 16.11) */
}
outgoing_reset_timer(original); /* Retransmission */
if (status < 200) {
if (original->orq_status < 200)
original->orq_status = status;
if (orq->orq_status < 200)
orq->orq_status = status;
if (original->orq_queue == sa->sa_out.inv_calling) {
outgoing_queue(sa->sa_out.inv_proceeding, original);
}
else if (original->orq_queue == sa->sa_out.inv_proceeding) {
if (sa->sa_out.inv_proceeding->q_timeout) {
outgoing_remove(original);
outgoing_queue(sa->sa_out.inv_proceeding, original);
}
}
/* Handle 100rel */
if (sip && sip->sip_rseq) {
if (outgoing_recv_reliable(orq, msg, sip) < 0) {
msg_destroy(msg);
return 0;
}
}
}
else {
/* Final response */
if (status >= 300 && !internal)
outgoing_ack(original, sip);
if (!original->orq_completed) {
if (outgoing_complete(original))
return 0;
if (orq->orq_uas && sip && orq == original) {
/*
* We silently discard duplicate final responses to INVITE below
* with outgoing_duplicate()
*/
su_home_t *home = msg_home(orq->orq_request);
orq->orq_tag = su_strdup(home, sip->sip_to->a_tag);
}
}
/* Retransmission or response from another fork */
else if (orq->orq_status >= 200) {
/* Once 2xx has been received, non-2xx will not be forwarded */
if (status >= 300)
return outgoing_duplicate(orq, msg, sip);
if (orq->orq_uas) {
if (su_strcasecmp(sip->sip_to->a_tag, orq->orq_tag) == 0)
/* Catch retransmission */
return outgoing_duplicate(orq, msg, sip);
/* Orphan 200 Ok to INVITE. ACK and BYE it */
SU_DEBUG_5(("nta: Orphan 200 Ok send ACK&BYE" VA_NONE));
return nta_msg_ackbye(sa, msg);
}
}
orq->orq_status = status;
}
}
else if (orq->orq_method != sip_method_ack) {
/* Non-INVITE */
if (orq->orq_queue == sa->sa_out.trying ||
orq->orq_queue == sa->sa_out.resolving) {
/* hacked by freeswitch, this is being hit by options 404 status with 404 orq->orq_status and orq_destroyed = 1, orq_completed = 1 */
/* assert(orq->orq_status < 200); */
if (orq->orq_status >= 200) {msg_destroy(msg); return 0;}
if (status < 200) {
/* @RFC3261 17.1.2.1:
* retransmissions continue for unreliable transports,
* but at an interval of T2.
*
* @RFC4321 1.2:
* Note that Timer E is not altered during the transition
* to Proceeding.
*/
if (!orq->orq_reliable)
orq->orq_interval = sa->sa_t2;
}
else if (!outgoing_complete(orq)) {
if (orq->orq_sigcomp_zap && orq->orq_tport && orq->orq_cc)
agent_zap_compressor(orq->orq_agent, orq->orq_cc);
}
else /* outgoing_complete */ {
msg_destroy(msg);
return 0;
}
}
else {
/* Already completed or terminated */
assert(orq->orq_queue == sa->sa_out.completed ||
orq->orq_queue == sa->sa_out.terminated);
assert(orq->orq_status >= 200);
return outgoing_duplicate(orq, msg, sip);
}
orq->orq_status = status;
}
else {
/* ACK */
if (sip && (sip->sip_flags & NTA_INTERNAL_MSG) == 0)
/* Received re-transmitted final reply to INVITE, retransmit ACK */
outgoing_retransmit(orq);
msg_destroy(msg);
return 0;
}
if (100 >= status + orq->orq_pass_100) {
msg_destroy(msg);
return 0;
}
if (orq->orq_destroyed) {
msg_destroy(msg);
return 0;
}
if (orq->orq_response)
msg_destroy(orq->orq_response);
orq->orq_response = msg;
/* Call callback */
orq->orq_callback(orq->orq_magic, orq, sip);
return 0;
}
static void outgoing_default_recv(nta_outgoing_t *orq,
int status,
msg_t *msg,
sip_t *sip)
{
assert(sip->sip_cseq);
orq->orq_status = status;
orq->orq_response = msg;
orq->orq_callback(orq->orq_magic, orq, sip);
orq->orq_response = NULL;
orq->orq_status = 0;
msg_destroy(msg);
}
static void outgoing_estimate_delay(nta_outgoing_t *orq, sip_t *sip)
{
su_time_t now = su_now();
double diff = 1000 * su_time_diff(now, orq->orq_sent);
if (orq->orq_timestamp && sip->sip_timestamp) {
double diff2, delay = 0.0;
su_time_t timestamp = { 0, 0 };
char const *bad;
sscanf(sip->sip_timestamp->ts_stamp, "%lu.%lu",
&timestamp.tv_sec, &timestamp.tv_usec);
diff2 = 1000 * su_time_diff(now, timestamp);
if (diff2 < 0)
bad = "negative";
else if (diff2 > diff + 1e-3)
bad = "too large";
else {
if (sip->sip_timestamp->ts_delay)
sscanf(sip->sip_timestamp->ts_delay, "%lg", &delay);
if (1000 * delay <= diff2) {
diff = diff2 - 1000 * delay;
orq->orq_delay = (unsigned)diff;
SU_DEBUG_7(("nta_outgoing: RTT is %g ms, now is %lu.%06lu, "
"Timestamp was %s %s\n",
diff, now.tv_sec, now.tv_usec,
sip->sip_timestamp->ts_stamp,
sip->sip_timestamp->ts_delay ?
sip->sip_timestamp->ts_delay : ""));
return;
}
bad = "delay";
}
SU_DEBUG_3(("nta_outgoing: %s Timestamp %lu.%06lu %g "
"(sent %lu.%06lu, now is %lu.%06lu)\n",
bad,
timestamp.tv_sec, timestamp.tv_usec,
delay,
orq->orq_sent.tv_sec, orq->orq_sent.tv_usec,
now.tv_sec, now.tv_usec));
}
if (diff >= 0 && diff < (double)UINT_MAX) {
orq->orq_delay = (unsigned)diff;
SU_DEBUG_7(("nta_outgoing: RTT is %g ms\n", diff));
}
}
/**@typedef nta_response_f
*
* Callback for replies to outgoing requests.
*
* This is a callback function invoked by NTA when it has received a new
* reply to an outgoing request.
*
* @param magic request context
* @param request request handle
* @param sip received status message
*
* @return
* This callback function should return always 0.
*
*/
/** Process duplicate responses */
static int outgoing_duplicate(nta_outgoing_t *orq,
msg_t *msg,
sip_t *sip)
{
sip_via_t *v;
if (sip && (sip->sip_flags & NTA_INTERNAL_MSG) == 0) {
v = sip->sip_via;
SU_DEBUG_5(("nta: %u %s is duplicate response to %d %s\n",
sip->sip_status->st_status, sip->sip_status->st_phrase,
orq->orq_cseq->cs_seq, orq->orq_cseq->cs_method_name));
if (v)
SU_DEBUG_5(("\tVia: %s %s%s%s%s%s%s%s%s%s\n",
v->v_protocol, v->v_host,
SIP_STRLOG(":", v->v_port),
SIP_STRLOG(" ;received=", v->v_received),
SIP_STRLOG(" ;maddr=", v->v_maddr),
SIP_STRLOG(" ;branch=", v->v_branch)));
}
msg_destroy(msg);
return 0;
}
/** @internal ACK to a final response (300..699).
* These messages are ACK'ed via the original URL (and tport)
*/
void outgoing_ack(nta_outgoing_t *orq, sip_t *sip)
{
msg_t *ackmsg;
assert(orq);
/* Do not ack internally generated messages... */
if (sip == NULL || sip->sip_flags & NTA_INTERNAL_MSG)
return;
assert(sip); assert(sip->sip_status);
assert(sip->sip_status->st_status >= 300);
assert(orq->orq_tport);
ackmsg = outgoing_ackmsg(orq, SIP_METHOD_ACK, SIPTAG_TO(sip->sip_to), TAG_END());
if (!ackmsg)
return;
if (!outgoing_create(orq->orq_agent, NULL, NULL,
NULL, orq->orq_tpn, ackmsg,
NTATAG_BRANCH_KEY(sip->sip_via->v_branch),
NTATAG_USER_VIA(1),
NTATAG_STATELESS(1),
TAG_END()))
msg_destroy(ackmsg);
}
/** Generate messages for hop-by-hop ACK or CANCEL.
*/
msg_t *outgoing_ackmsg(nta_outgoing_t *orq, sip_method_t m, char const *mname,
tag_type_t tag, tag_value_t value, ...)
{
msg_t *msg = nta_msg_create(orq->orq_agent, 0);
su_home_t *home = msg_home(msg);
sip_t *sip = sip_object(msg);
sip_t *old = sip_object(orq->orq_request);
sip_via_t via[1];
if (!sip)
return NULL;
if (tag) {
ta_list ta;
ta_start(ta, tag, value);
sip_add_tl(msg, sip, ta_tags(ta));
/* Bug sf.net # 173323:
* Ensure that request-URI, topmost Via, From, To, Call-ID, CSeq,
* Max-Forward, Route, Accept-Contact, Reject-Contact and
* Request-Disposition are copied from original request
*/
if (sip->sip_from)
sip_header_remove(msg, sip, (void *)sip->sip_from);
if (sip->sip_to && m != sip_method_ack)
sip_header_remove(msg, sip, (void *)sip->sip_to);
if (sip->sip_call_id)
sip_header_remove(msg, sip, (void *)sip->sip_call_id);
while (sip->sip_route)
sip_header_remove(msg, sip, (void *)sip->sip_route);
while (sip->sip_accept_contact)
sip_header_remove(msg, sip, (void *)sip->sip_accept_contact);
while (sip->sip_reject_contact)
sip_header_remove(msg, sip, (void *)sip->sip_reject_contact);
if (sip->sip_request_disposition)
sip_header_remove(msg, sip, (void *)sip->sip_request_disposition);
while (sip->sip_via)
sip_header_remove(msg, sip, (void *)sip->sip_via);
if (sip->sip_max_forwards)
sip_header_remove(msg, sip, (void *)sip->sip_max_forwards);
ta_end(ta);
}
sip->sip_request =
sip_request_create(home, m, mname, (url_string_t *)orq->orq_url, NULL);
if (sip->sip_to == NULL)
sip_add_dup(msg, sip, (sip_header_t *)old->sip_to);
sip_add_dup(msg, sip, (sip_header_t *)old->sip_from);
sip_add_dup(msg, sip, (sip_header_t *)old->sip_call_id);
sip_add_dup(msg, sip, (sip_header_t *)old->sip_route);
/* @RFC3841. Bug #1326727. */
sip_add_dup(msg, sip, (sip_header_t *)old->sip_accept_contact);
sip_add_dup(msg, sip, (sip_header_t *)old->sip_reject_contact);
sip_add_dup(msg, sip, (sip_header_t *)old->sip_request_disposition);
sip_add_dup(msg, sip, (sip_header_t *)old->sip_max_forwards);
if (old->sip_via) {
/* Add only the topmost Via header */
*via = *old->sip_via; via->v_next = NULL;
sip_add_dup(msg, sip, (sip_header_t *)via);
}
sip->sip_cseq = sip_cseq_create(home, old->sip_cseq->cs_seq, m, mname);
if (sip->sip_request &&
sip->sip_to &&
sip->sip_from &&
sip->sip_call_id &&
(!old->sip_route || sip->sip_route) &&
sip->sip_cseq)
return msg;
msg_destroy(msg);
return NULL;
}
static
void outgoing_delayed_recv(su_root_magic_t *rm,
su_msg_r msg,
union sm_arg_u *u);
/** Respond internally to a transaction. */
int outgoing_reply(nta_outgoing_t *orq, int status, char const *phrase,
int delayed)
{
nta_agent_t *agent = orq->orq_agent;
msg_t *msg = NULL;
sip_t *sip = NULL;
assert(status == 202 || status >= 400);
if (orq->orq_pending)
tport_release(orq->orq_tport, orq->orq_pending,
orq->orq_request, NULL, orq, 0);
orq->orq_pending = 0;
orq->orq_delayed = 0;
if (orq->orq_method == sip_method_ack) {
if (status != delayed)
SU_DEBUG_3(("nta(%p): responding %u %s to ACK!\n",
(void *)orq, status, phrase));
orq->orq_status = status;
if (orq->orq_queue == NULL)
outgoing_trying(orq); /* Timer F */
return 0;
}
if (orq->orq_destroyed) {
if (orq->orq_status < 200)
orq->orq_status = status;
outgoing_complete(orq); /* Timer D / Timer K */
return 0;
}
if (orq->orq_stateless)
;
else if (orq->orq_queue == NULL ||
orq->orq_queue == orq->orq_agent->sa_out.resolving ||
orq->orq_queue == orq->orq_agent->sa_out.delayed)
outgoing_trying(orq);
/** Insert a dummy Via header */
if (!orq->orq_prepared) {
tport_t *tp = tport_primaries(orq->orq_agent->sa_tports);
outgoing_insert_via(orq, agent_tport_via(tp));
}
/* Create response message, if needed */
if (!orq->orq_stateless &&
!(orq->orq_callback == outgoing_default_cb) &&
!(status == 408 &&
orq->orq_method != sip_method_invite &&
!orq->orq_agent->sa_timeout_408)) {
char const *to_tag;
msg = nta_msg_create(agent, NTA_INTERNAL_MSG);
if (complete_response(msg, status, phrase, orq->orq_request) < 0) {
assert(!"complete message");
return -1;
}
sip = sip_object(msg); assert(sip->sip_flags & NTA_INTERNAL_MSG);
to_tag = nta_agent_newtag(msg_home(msg), "tag=%s", agent);
if (status > 100 &&
sip->sip_to && !sip->sip_to->a_tag &&
sip->sip_cseq->cs_method != sip_method_cancel &&
sip_to_tag(msg_home(msg), sip->sip_to, to_tag) < 0) {
assert(!"adding tag");
return -1;
}
if (status > 400 && agent->sa_blacklist) {
sip_retry_after_t af[1];
sip_retry_after_init(af)->af_delta = agent->sa_blacklist;
sip_add_dup(msg, sip, (sip_header_t *)af);
}
}
if (orq->orq_inserted && !delayed) {
outgoing_recv(orq, status, msg, sip);
return 0;
}
else if (orq->orq_stateless && orq->orq_callback == outgoing_default_cb) {
/* Xyzzy */
orq->orq_status = status;
outgoing_complete(orq);
}
else {
/*
* The thread creating outgoing transaction must return to application
* before transaction callback can be invoked. Therefore processing an
* internally generated response message must be delayed until
* transaction creation is completed.
*
* The internally generated message is transmitted using su_msg_send()
* and it is delivered back to NTA when the application next time
* executes the su_root_t event loop.
*/
nta_agent_t *agent = orq->orq_agent;
su_root_t *root = agent->sa_root;
su_msg_r su_msg = SU_MSG_R_INIT;
if (su_msg_create(su_msg,
su_root_task(root),
su_root_task(root),
outgoing_delayed_recv,
sizeof(struct outgoing_recv_s)) == SU_SUCCESS) {
struct outgoing_recv_s *a = su_msg_data(su_msg)->a_outgoing_recv;
a->orq = orq;
a->msg = msg;
a->sip = sip;
a->status = status;
orq->orq_status2b = &a->status;
if (su_msg_send(su_msg) == SU_SUCCESS) {
return 0;
}
}
}
if (msg)
msg_destroy(msg);
return -1;
}
static
void outgoing_delayed_recv(su_root_magic_t *rm,
su_msg_r msg,
union sm_arg_u *u)
{
struct outgoing_recv_s *a = u->a_outgoing_recv;
if (a->status > 0) {
a->orq->orq_status2b = 0;
if (outgoing_recv(a->orq, a->status, a->msg, a->sip) >= 0)
return;
}
msg_destroy(a->msg);
}
/* ====================================================================== */
/* 9) Resolving (SIP) URL */
#if HAVE_SOFIA_SRESOLV
struct sipdns_query;
/** DNS resolving for (SIP) URLs */
struct sipdns_resolver
{
tp_name_t sr_tpn[1]; /**< Copy of original transport name */
sres_query_t *sr_query; /**< Current DNS Query */
char const *sr_target; /**< Target for current query */
struct sipdns_query *sr_current; /**< Current query (with results) */
char **sr_results; /**< A/AAAA results to be used */
struct sipdns_query *sr_head; /**< List of intermediate results */
struct sipdns_query **sr_tail; /**< End of intermediate result list */
struct sipdns_query *sr_done; /**< Completed intermediate results */
struct sipdns_tport const *sr_tport; /**< Selected transport */
/** Transports to consider for this request */
struct sipdns_tport const *sr_tports[SIPDNS_TRANSPORTS + 1];
uint16_t sr_a_aaaa1, sr_a_aaaa2; /**< Order of A and/or AAAA queries. */
unsigned
sr_use_naptr:1,
sr_use_srv:1,
sr_use_a_aaaa:1;
};
/** Intermediate queries */
struct sipdns_query
{
struct sipdns_query *sq_next;
char const *sq_proto;
char const *sq_domain;
char sq_port[6]; /* port number */
uint16_t sq_otype; /* origin type of query data (0 means request) */
uint16_t sq_type; /* query type */
uint16_t sq_priority; /* priority or preference */
uint16_t sq_weight; /* preference or weight */
uint16_t sq_grayish; /* candidate for graylisting */
};
static int outgoing_resolve_next(nta_outgoing_t *orq);
static int outgoing_resolving(nta_outgoing_t *orq);
static int outgoing_resolving_error(nta_outgoing_t *,
int status, char const *phrase);
static void outgoing_graylist(nta_outgoing_t *orq, struct sipdns_query *sq);
static int outgoing_query_naptr(nta_outgoing_t *orq, char const *domain);
static void outgoing_answer_naptr(sres_context_t *orq, sres_query_t *q,
sres_record_t *answers[]);
struct sipdns_tport const *outgoing_naptr_tport(nta_outgoing_t *orq,
sres_record_t *answers[]);
static int outgoing_make_srv_query(nta_outgoing_t *orq);
static int outgoing_make_a_aaaa_query(nta_outgoing_t *orq);
static void outgoing_query_all(nta_outgoing_t *orq);
static int outgoing_query_srv(nta_outgoing_t *orq, struct sipdns_query *);
static void outgoing_answer_srv(sres_context_t *orq, sres_query_t *q,
sres_record_t *answers[]);
#if SU_HAVE_IN6
static int outgoing_query_aaaa(nta_outgoing_t *orq, struct sipdns_query *);
static void outgoing_answer_aaaa(sres_context_t *orq, sres_query_t *q,
sres_record_t *answers[]);
#endif
static int outgoing_query_a(nta_outgoing_t *orq, struct sipdns_query *);
static void outgoing_answer_a(sres_context_t *orq, sres_query_t *q,
sres_record_t *answers[]);
static void outgoing_query_results(nta_outgoing_t *orq,
struct sipdns_query *sq,
char *results[],
size_t rlen);
#define SIPDNS_503_ERROR 503, "DNS Error"
/** Resolve a request destination */
static void
outgoing_resolve(nta_outgoing_t *orq,
int explicit_transport,
enum nta_res_order_e res_order)
{
struct sipdns_resolver *sr = NULL;
char const *tpname = orq->orq_tpn->tpn_proto;
int tport_known = strcmp(tpname, "*") != 0;
if (orq->orq_agent->sa_resolver)
orq->orq_resolver = sr = su_zalloc(orq->orq_agent->sa_home, (sizeof *sr));
if (!sr) {
outgoing_resolving_error(orq, SIP_500_INTERNAL_SERVER_ERROR);
return;
}
*sr->sr_tpn = *orq->orq_tpn;
sr->sr_use_srv = orq->orq_agent->sa_use_srv;
sr->sr_use_naptr = orq->orq_agent->sa_use_naptr && sr->sr_use_srv;
sr->sr_use_a_aaaa = 1;
sr->sr_tail = &sr->sr_head;
/* RFC 3263:
If the TARGET was not a numeric IP address, but a port is present in
the URI, the client performs an A or AAAA record lookup of the domain
name. The result will be a list of IP addresses, each of which can
be contacted at the specific port from the URI and transport protocol
determined previously. The client SHOULD try the first record. If
an attempt should fail, based on the definition of failure in Section
4.3, the next SHOULD be tried, and if that should fail, the next
SHOULD be tried, and so on.
This is a change from RFC 2543. Previously, if the port was
explicit, but with a value of 5060, SRV records were used. Now, A
or AAAA records will be used.
*/
if (sr->sr_tpn->tpn_port)
sr->sr_use_naptr = 0, sr->sr_use_srv = 0;
/* RFC3263:
If [...] a transport was specified explicitly, the client performs an
SRV query for that specific transport,
*/
if (explicit_transport)
sr->sr_use_naptr = 0;
{
/* Initialize sr_tports */
tport_t *tport;
char const *ident = sr->sr_tpn->tpn_ident;
int i, j;
for (tport = tport_primary_by_name(orq->orq_agent->sa_tports, orq->orq_tpn);
tport;
tport = tport_next(tport)) {
tp_name_t const *tpn = tport_name(tport);
if (tport_known && !su_casematch(tpn->tpn_proto, tpname))
continue;
if (ident && (tpn->tpn_ident == NULL || strcmp(ident, tpn->tpn_ident)))
continue;
for (j = 0; j < SIPDNS_TRANSPORTS; j++)
if (su_casematch(tpn->tpn_proto, sipdns_tports[j].name))
break;
assert(j < SIPDNS_TRANSPORTS);
if (j == SIPDNS_TRANSPORTS)
/* Someone added transport but did not update sipdns_tports */
continue;
for (i = 0; i < SIPDNS_TRANSPORTS; i++) {
if (sipdns_tports + j == sr->sr_tports[i] || sr->sr_tports[i] == NULL)
break;
}
sr->sr_tports[i] = sipdns_tports + j;
if (tport_known) /* Looking for only one transport */ {
sr->sr_tport = sipdns_tports + j;
break;
}
}
/* Nothing found */
if (!sr->sr_tports[0]) {
SU_DEBUG_3(("nta(%p): transport %s is not supported%s%s\n", (void *)orq,
tpname, ident ? " by interface " : "", ident ? ident : ""));
outgoing_resolving_error(orq, SIPDNS_503_ERROR);
return;
}
}
switch (res_order) {
default:
case nta_res_ip6_ip4:
sr->sr_a_aaaa1 = sres_type_aaaa, sr->sr_a_aaaa2 = sres_type_a;
break;
case nta_res_ip4_ip6:
sr->sr_a_aaaa1 = sres_type_a, sr->sr_a_aaaa2 = sres_type_aaaa;
break;
case nta_res_ip6_only:
sr->sr_a_aaaa1 = sres_type_aaaa, sr->sr_a_aaaa2 = sres_type_aaaa;
break;
case nta_res_ip4_only:
sr->sr_a_aaaa1 = sres_type_a, sr->sr_a_aaaa2 = sres_type_a;
break;
}
outgoing_resolve_next(orq);
}
/** Resolve next destination. */
static int
outgoing_resolve_next(nta_outgoing_t *orq)
{
struct sipdns_resolver *sr = orq->orq_resolver;
if (sr == NULL) {
outgoing_resolving_error(orq, SIP_500_INTERNAL_SERVER_ERROR);
return 0;
}
if (sr->sr_results) {
/* Use existing A/AAAA results */
su_free(msg_home(orq->orq_request), sr->sr_results[0]);
sr->sr_results++;
if (sr->sr_results[0]) {
struct sipdns_query *sq = sr->sr_current; assert(sq);
if (sq->sq_proto)
orq->orq_tpn->tpn_proto = sq->sq_proto;
if (sq->sq_port[0])
orq->orq_tpn->tpn_port = sq->sq_port;
orq->orq_tpn->tpn_host = sr->sr_results[0];
outgoing_reset_timer(orq);
outgoing_queue(orq->orq_agent->sa_out.resolving, orq);
outgoing_prepare_send(orq);
return 1;
}
else {
sr->sr_current = NULL;
sr->sr_results = NULL;
}
}
if (sr->sr_head)
outgoing_query_all(orq);
else if (sr->sr_use_naptr)
outgoing_query_naptr(orq, sr->sr_tpn->tpn_host); /* NAPTR */
else if (sr->sr_use_srv)
outgoing_make_srv_query(orq); /* SRV */
else if (sr->sr_use_a_aaaa)
outgoing_make_a_aaaa_query(orq); /* A/AAAA */
else
return outgoing_resolving_error(orq, SIPDNS_503_ERROR);
return 1;
}
/** Check if can we retry other destinations? */
static int
outgoing_other_destinations(nta_outgoing_t const *orq)
{
struct sipdns_resolver *sr = orq->orq_resolver;
if (!sr)
return 0;
if (sr->sr_use_a_aaaa || sr->sr_use_srv || sr->sr_use_naptr)
return 1;
if (sr->sr_results && sr->sr_results[1])
return 1;
if (sr->sr_head)
return 1;
return 0;
}
/** Resolve a request destination */
static int
outgoing_try_another(nta_outgoing_t *orq)
{
struct sipdns_resolver *sr = orq->orq_resolver;
if (sr == NULL)
return 0;
*orq->orq_tpn = *sr->sr_tpn;
orq->orq_try_tcp_instead = 0, orq->orq_try_udp_instead = 0;
outgoing_reset_timer(orq);
outgoing_queue(orq->orq_agent->sa_out.resolving, orq);
if (orq->orq_status > 0)
/* PP: don't hack priority if a preliminary response has been received */
;
else if (orq->orq_agent->sa_graylist == 0)
/* PP: priority hacking disabled */
;
/* NetModule hack:
* Move server that did not work to end of queue in sres cache
*
* the next request does not try to use the server that is currently down
*
* @TODO: fix cases with only A or AAAA answering, or all servers down.
*/
else if (sr && sr->sr_target) {
struct sipdns_query *sq;
/* find latest A/AAAA record */
sq = sr->sr_head;
if (sq && sq->sq_type == sr->sr_a_aaaa2 && sr->sr_a_aaaa1 != sr->sr_a_aaaa2) {
sq->sq_grayish = 1;
}
else {
outgoing_graylist(orq, sr->sr_done);
}
}
return outgoing_resolve_next(orq);
}
/** Graylist SRV records */
static void outgoing_graylist(nta_outgoing_t *orq, struct sipdns_query *sq)
{
struct sipdns_resolver *sr = orq->orq_resolver;
char const *target = sq->sq_domain, *proto = sq->sq_proto;
unsigned prio = sq->sq_priority, maxprio = prio;
/* Don't know how to graylist but SRV records */
if (sq->sq_otype != sres_type_srv)
return;
SU_DEBUG_5(("nta: graylisting %s:%s;transport=%s\n", target, sq->sq_port, proto));
for (sq = sr->sr_head; sq; sq = sq->sq_next)
if (sq->sq_otype == sres_type_srv && sq->sq_priority > maxprio)
maxprio = sq->sq_priority;
for (sq = sr->sr_done; sq; sq = sq->sq_next)
if (sq->sq_otype == sres_type_srv && sq->sq_priority > maxprio)
maxprio = sq->sq_priority;
for (sq = sr->sr_done; sq; sq = sq->sq_next) {
int modified;
if (sq->sq_type != sres_type_srv || strcmp(proto, sq->sq_proto))
continue;
/* modify the SRV record(s) corresponding to the latest A/AAAA record */
modified = sres_set_cached_srv_priority(
orq->orq_agent->sa_resolver,
sq->sq_domain,
target,
sq->sq_port[0] ? (uint16_t)strtoul(sq->sq_port, NULL, 10) : 0,
orq->orq_agent->sa_graylist,
maxprio + 1);
if (modified >= 0)
SU_DEBUG_3(("nta: reduced priority of %d %s SRV records (increase value to %u)\n",
modified, sq->sq_domain, maxprio + 1));
else
SU_DEBUG_3(("nta: failed to reduce %s SRV priority\n", sq->sq_domain));
}
}
/** Cancel resolver query */
su_inline void outgoing_cancel_resolver(nta_outgoing_t *orq)
{
struct sipdns_resolver *sr = orq->orq_resolver;
assert(orq->orq_resolver);
if (sr->sr_query) /* Cancel resolver query */
sres_query_bind(sr->sr_query, NULL, NULL), sr->sr_query = NULL;
}
/** Destroy resolver */
su_inline void outgoing_destroy_resolver(nta_outgoing_t *orq)
{
struct sipdns_resolver *sr = orq->orq_resolver;
assert(orq->orq_resolver);
if (sr->sr_query) /* Cancel resolver query */
sres_query_bind(sr->sr_query, NULL, NULL), sr->sr_query = NULL;
su_free(orq->orq_agent->sa_home, sr);
orq->orq_resolver = NULL;
}
/** Check if we are resolving. If not, return 503 response. */
static
int outgoing_resolving(nta_outgoing_t *orq)
{
struct sipdns_resolver *sr = orq->orq_resolver;
assert(orq->orq_resolver);
if (!sr->sr_query) {
return outgoing_resolving_error(orq, SIPDNS_503_ERROR);
}
else {
outgoing_queue(orq->orq_agent->sa_out.resolving, orq);
return 0;
}
}
/** Return 503 response */
static
int outgoing_resolving_error(nta_outgoing_t *orq, int status, char const *phrase)
{
orq->orq_resolved = 1;
outgoing_reply(orq, status, phrase, 0);
return -1;
}
/* Query SRV records (with the given tport). */
static
int outgoing_make_srv_query(nta_outgoing_t *orq)
{
struct sipdns_resolver *sr = orq->orq_resolver;
su_home_t *home = msg_home(orq->orq_request);
struct sipdns_query *sq;
char const *host, *prefix;
int i;
size_t hlen, plen;
sr->sr_use_srv = 0;
host = sr->sr_tpn->tpn_host;
hlen = strlen(host) + 1;
for (i = 0; sr->sr_tports[i]; i++) {
if (sr->sr_tport && sr->sr_tports[i] != sr->sr_tport)
continue;
prefix = sr->sr_tports[i]->prefix;
plen = strlen(prefix);
sq = su_zalloc(home, (sizeof *sq) + plen + hlen);
if (sq) {
*sr->sr_tail = sq, sr->sr_tail = &sq->sq_next;
sq->sq_domain = memcpy(sq + 1, prefix, plen);
memcpy((char *)sq->sq_domain + plen, host, hlen);
sq->sq_proto = sr->sr_tports[i]->name;
sq->sq_type = sres_type_srv;
sq->sq_priority = 1;
sq->sq_weight = 1;
}
}
outgoing_query_all(orq);
return 0;
}
/* Query A/AAAA records. */
static
int outgoing_make_a_aaaa_query(nta_outgoing_t *orq)
{
struct sipdns_resolver *sr = orq->orq_resolver;
su_home_t *home = msg_home(orq->orq_request);
tp_name_t *tpn = orq->orq_tpn;
struct sipdns_query *sq;
assert(sr);
sr->sr_use_a_aaaa = 0;
sq = su_zalloc(home, 2 * (sizeof *sq));
if (!sq)
return outgoing_resolving(orq);
sq->sq_type = sr->sr_a_aaaa1;
sq->sq_domain = tpn->tpn_host;
sq->sq_priority = 1;
/* Append */
*sr->sr_tail = sq, sr->sr_tail = &sq->sq_next;
outgoing_query_all(orq);
return 0;
}
/** Start SRV/A/AAAA queries */
static
void outgoing_query_all(nta_outgoing_t *orq)
{
struct sipdns_resolver *sr = orq->orq_resolver;
struct sipdns_query *sq = sr->sr_head;
if (sq == NULL) {
outgoing_resolving_error(orq, SIP_500_INTERNAL_SERVER_ERROR);
return;
}
/* Remove from intermediate list */
if (!(sr->sr_head = sq->sq_next))
sr->sr_tail = &sr->sr_head;
if (sq->sq_type == sres_type_srv)
outgoing_query_srv(orq, sq);
#if SU_HAVE_IN6
else if (sq->sq_type == sres_type_aaaa)
outgoing_query_aaaa(orq, sq);
#endif
else if (sq->sq_type == sres_type_a)
outgoing_query_a(orq, sq);
else
outgoing_resolving_error(orq, SIP_500_INTERNAL_SERVER_ERROR);
}
/** Query NAPTR record. */
static
int outgoing_query_naptr(nta_outgoing_t *orq, char const *domain)
{
struct sipdns_resolver *sr = orq->orq_resolver;
sres_record_t **answers;
sr->sr_use_naptr = 0;
sr->sr_target = domain;
answers = sres_cached_answers(orq->orq_agent->sa_resolver,
sres_type_naptr, domain);
SU_DEBUG_5(("nta: for \"%s\" query \"%s\" %s%s\n",
orq->orq_tpn->tpn_host, domain, "NAPTR",
answers ? " (cached)" : ""));
if (answers) {
outgoing_answer_naptr(orq, NULL, answers);
return 0;
}
else {
sr->sr_query = sres_query(orq->orq_agent->sa_resolver,
outgoing_answer_naptr, orq,
sres_type_naptr, domain);
return outgoing_resolving(orq);
}
}
/* Process NAPTR records */
static
void outgoing_answer_naptr(sres_context_t *orq,
sres_query_t *q,
sres_record_t *answers[])
{
int i, order = -1;
size_t rlen;
su_home_t *home = msg_home(orq->orq_request);
struct sipdns_resolver *sr = orq->orq_resolver;
tp_name_t tpn[1];
struct sipdns_query *sq, *selected = NULL, **tail = &selected, **at;
assert(sr);
sr->sr_query = NULL;
*tpn = *sr->sr_tpn;
/* The NAPTR results are sorted first by Order then by Preference */
sres_sort_answers(orq->orq_agent->sa_resolver, answers);
if (sr->sr_tport == NULL)
sr->sr_tport = outgoing_naptr_tport(orq, answers);
for (i = 0; answers && answers[i]; i++) {
sres_naptr_record_t const *na = answers[i]->sr_naptr;
uint16_t type;
int valid_tport;
if (na->na_record->r_status)
continue;
if (na->na_record->r_type != sres_type_naptr)
continue;
/* Check if NAPTR matches our target */
if (!su_casenmatch(na->na_services, "SIP+", 4) &&
!su_casenmatch(na->na_services, "SIPS+", 5))
/* Not a SIP/SIPS service */
continue;
/* Use NAPTR results, don't try extra SRV/A/AAAA records */
sr->sr_use_srv = 0, sr->sr_use_a_aaaa = 0;
valid_tport = sr->sr_tport &&
su_casematch(na->na_services, sr->sr_tport->service);
SU_DEBUG_5(("nta: %s IN NAPTR %u %u \"%s\" \"%s\" \"%s\" %s%s\n",
na->na_record->r_name,
na->na_order, na->na_prefer,
na->na_flags, na->na_services,
na->na_regexp, na->na_replace,
order >= 0 && order != na->na_order ? " (out of order)" :
valid_tport ? "" : " (tport not used)"));
/* RFC 2915 p 4:
* Order
* A 16-bit unsigned integer specifying the order in which the
* NAPTR records MUST be processed to ensure the correct ordering
* of rules. Low numbers are processed before high numbers, and
* once a NAPTR is found whose rule "matches" the target, the
* client MUST NOT consider any NAPTRs with a higher value for
* order (except as noted below for the Flags field).
*/
if (order >= 0 && order != na->na_order)
continue;
if (!valid_tport)
continue;
/* OK, we found matching NAPTR */
order = na->na_order;
/*
* The "S" flag means that the next lookup should be for SRV records
* ... "A" means that the next lookup should be for either an A, AAAA,
* or A6 record.
*/
if (na->na_flags[0] == 's' || na->na_flags[0] == 'S')
type = sres_type_srv; /* SRV */
else if (na->na_flags[0] == 'a' || na->na_flags[0] == 'A')
type = sr->sr_a_aaaa1; /* A / AAAA */
else
continue;
rlen = strlen(na->na_replace) + 1;
sq = su_zalloc(home, (sizeof *sq) + rlen);
if (sq == NULL)
continue;
*tail = sq, tail = &sq->sq_next;
sq->sq_otype = sres_type_naptr;
sq->sq_priority = na->na_prefer;
sq->sq_weight = 1;
sq->sq_type = type;
sq->sq_domain = memcpy(sq + 1, na->na_replace, rlen);
sq->sq_proto = sr->sr_tport->name;
}
sres_free_answers(orq->orq_agent->sa_resolver, answers);
/* RFC2915:
Preference [...] specifies the order in which NAPTR
records with equal "order" values SHOULD be processed, low
numbers being processed before high numbers. */
at = sr->sr_tail;
while (selected) {
sq = selected, selected = sq->sq_next;
for (tail = at; *tail; tail = &(*tail)->sq_next) {
if (sq->sq_priority < (*tail)->sq_priority)
break;
if (sq->sq_priority == (*tail)->sq_priority &&
sq->sq_weight < (*tail)->sq_weight)
break;
}
/* Insert */
sq->sq_next = *tail, *tail = sq;
if (!sq->sq_next) /* Last one */
sr->sr_tail = &sq->sq_next;
}
outgoing_resolve_next(orq);
}
/* Find first supported protocol in order and preference */
struct sipdns_tport const *
outgoing_naptr_tport(nta_outgoing_t *orq, sres_record_t *answers[])
{
int i, j, order, pref;
int orders[SIPDNS_TRANSPORTS], prefs[SIPDNS_TRANSPORTS];
struct sipdns_tport const *tport;
struct sipdns_resolver *sr = orq->orq_resolver;
for (j = 0; sr->sr_tports[j]; j++) {
tport = sr->sr_tports[j];
orders[j] = 65536, prefs[j] = 65536;
/* Find transport order */
for (i = 0; answers && answers[i]; i++) {
sres_naptr_record_t const *na = answers[i]->sr_naptr;
if (na->na_record->r_status)
continue;
if (na->na_record->r_type != sres_type_naptr)
continue;
/* Check if NAPTR matches transport */
if (!su_casematch(na->na_services, tport->service))
continue;
orders[j] = na->na_order;
prefs[j] = na->na_prefer;
break;
}
}
tport = sr->sr_tports[0], order = orders[0], pref = prefs[0];
for (j = 1; sr->sr_tports[j]; j++) {
if (orders[j] <= order && prefs[j] < pref) {
tport = sr->sr_tports[j], order = orders[j], pref = prefs[j];
}
}
return tport;
}
/* Query SRV records */
static
int outgoing_query_srv(nta_outgoing_t *orq,
struct sipdns_query *sq)
{
struct sipdns_resolver *sr = orq->orq_resolver;
sres_record_t **answers;
sr->sr_target = sq->sq_domain;
sr->sr_current = sq;
answers = sres_cached_answers(orq->orq_agent->sa_resolver,
sres_type_srv, sq->sq_domain);
SU_DEBUG_5(("nta: for \"%s\" query \"%s\" %s%s\n",
orq->orq_tpn->tpn_host, sq->sq_domain, "SRV",
answers ? " (cached)" : ""));
if (answers) {
outgoing_answer_srv(orq, NULL, answers);
return 0;
}
else {
sr->sr_query = sres_query(orq->orq_agent->sa_resolver,
outgoing_answer_srv, orq,
sres_type_srv, sq->sq_domain);
return outgoing_resolving(orq);
}
}
/* Process SRV records */
static
void
outgoing_answer_srv(sres_context_t *orq, sres_query_t *q,
sres_record_t *answers[])
{
struct sipdns_resolver *sr = orq->orq_resolver;
su_home_t *home = msg_home(orq->orq_request);
struct sipdns_query *sq0, *sq, *selected = NULL, **tail = &selected, **at;
int i;
size_t tlen;
sr->sr_query = NULL;
sq0 = sr->sr_current;
assert(sq0 && sq0->sq_type == sres_type_srv);
assert(sq0->sq_domain); assert(sq0->sq_proto);
/* Sort by priority, weight? */
sres_sort_answers(orq->orq_agent->sa_resolver, answers);
for (i = 0; answers && answers[i]; i++) {
sres_srv_record_t const *srv = answers[i]->sr_srv;
if (srv->srv_record->r_status /* There was an error */ ||
srv->srv_record->r_type != sres_type_srv)
continue;
tlen = strlen(srv->srv_target) + 1;
sq = su_zalloc(home, (sizeof *sq) + tlen);
if (sq) {
*tail = sq, tail = &sq->sq_next;
sq->sq_otype = sres_type_srv;
sq->sq_type = sr->sr_a_aaaa1;
sq->sq_proto = sq0->sq_proto;
sq->sq_domain = memcpy(sq + 1, srv->srv_target, tlen);
snprintf(sq->sq_port, sizeof(sq->sq_port), "%u", srv->srv_port);
sq->sq_priority = srv->srv_priority;
sq->sq_weight = srv->srv_weight;
}
}
sres_free_answers(orq->orq_agent->sa_resolver, answers);
at = &sr->sr_head;
/* Insert sorted by priority, randomly select by weigth */
while (selected) {
unsigned long weight = 0;
unsigned N = 0;
uint16_t priority = selected->sq_priority;
/* Total weight of entries with same priority */
for (sq = selected; sq && priority == sq->sq_priority; sq = sq->sq_next) {
weight += sq->sq_weight;
N ++;
}
tail = &selected;
/* Select by weighted random. Entries with weight 0 are kept in order */
if (N > 1 && weight > 0) {
unsigned rand = su_randint(0, weight - 1);
while (rand >= (*tail)->sq_weight) {
rand -= (*tail)->sq_weight;
tail = &(*tail)->sq_next;
}
}
/* Remove selected */
sq = *tail; *tail = sq->sq_next; assert(sq->sq_priority == priority);
/* Append at *at */
sq->sq_next = *at; *at = sq; at = &sq->sq_next; if (!*at) sr->sr_tail = at;
SU_DEBUG_5(("nta: %s IN SRV %u %u %s %s (%s)\n",
sq0->sq_domain,
(unsigned)sq->sq_priority, (unsigned)sq->sq_weight,
sq->sq_port, sq->sq_domain, sq->sq_proto));
}
/* This is not needed anymore (?) */
sr->sr_current = NULL;
sq0->sq_next = sr->sr_done; sr->sr_done = sq0;
outgoing_resolve_next(orq);
}
#if SU_HAVE_IN6
/* Query AAAA records */
static
int outgoing_query_aaaa(nta_outgoing_t *orq, struct sipdns_query *sq)
{
struct sipdns_resolver *sr = orq->orq_resolver;
sres_record_t **answers;
sr->sr_target = sq->sq_domain;
sr->sr_current = sq;
answers = sres_cached_answers(orq->orq_agent->sa_resolver,
sres_type_aaaa, sq->sq_domain);
SU_DEBUG_5(("nta: for \"%s\" query \"%s\" %s%s\n",
orq->orq_tpn->tpn_host, sq->sq_domain, "AAAA",
answers ? " (cached)" : ""));
if (answers) {
outgoing_answer_aaaa(orq, NULL, answers);
return 0;
}
sr->sr_query = sres_query(orq->orq_agent->sa_resolver,
outgoing_answer_aaaa, orq,
sres_type_aaaa, sq->sq_domain);
return outgoing_resolving(orq);
}
/* Process AAAA records */
static
void outgoing_answer_aaaa(sres_context_t *orq, sres_query_t *q,
sres_record_t *answers[])
{
struct sipdns_resolver *sr = orq->orq_resolver;
su_home_t *home = msg_home(orq->orq_request);
struct sipdns_query *sq = sr->sr_current;
size_t i, j, found;
char *result, **results = NULL;
assert(sq); assert(sq->sq_type == sres_type_aaaa);
sr->sr_query = NULL;
for (i = 0, found = 0; answers && answers[i]; i++) {
sres_aaaa_record_t const *aaaa = answers[i]->sr_aaaa;
if (aaaa->aaaa_record->r_status == 0 &&
aaaa->aaaa_record->r_type == sres_type_aaaa)
found++;
}
if (found > 1)
results = su_zalloc(home, (found + 1) * (sizeof *results));
else if (found)
results = &result;
for (i = j = 0; results && answers && answers[i]; i++) {
char addr[SU_ADDRSIZE];
sres_aaaa_record_t const *aaaa = answers[i]->sr_aaaa;
if (aaaa->aaaa_record->r_status ||
aaaa->aaaa_record->r_type != sres_type_aaaa)
continue; /* There was an error */
su_inet_ntop(AF_INET6, &aaaa->aaaa_addr, addr, sizeof(addr));
if (j == 0)
SU_DEBUG_5(("nta(%p): %s IN AAAA %s\n", (void *)orq,
aaaa->aaaa_record->r_name, addr));
else
SU_DEBUG_5(("nta(%p): AAAA %s\n", (void *)orq, addr));
assert(j < found);
results[j++] = su_strdup(home, addr);
}
sres_free_answers(orq->orq_agent->sa_resolver, answers);
outgoing_query_results(orq, sq, results, found);
}
#endif /* SU_HAVE_IN6 */
/* Query A records */
static
int outgoing_query_a(nta_outgoing_t *orq, struct sipdns_query *sq)
{
struct sipdns_resolver *sr = orq->orq_resolver;
sres_record_t **answers;
sr->sr_target = sq->sq_domain;
sr->sr_current = sq;
answers = sres_cached_answers(orq->orq_agent->sa_resolver,
sres_type_a, sq->sq_domain);
SU_DEBUG_5(("nta: for \"%s\" query \"%s\" %s%s\n",
orq->orq_tpn->tpn_host, sq->sq_domain, "A",
answers ? " (cached)" : ""));
if (answers) {
outgoing_answer_a(orq, NULL, answers);
return 0;
}
sr->sr_query = sres_query(orq->orq_agent->sa_resolver,
outgoing_answer_a, orq,
sres_type_a, sq->sq_domain);
return outgoing_resolving(orq);
}
/* Process A records */
static
void outgoing_answer_a(sres_context_t *orq, sres_query_t *q,
sres_record_t *answers[])
{
struct sipdns_resolver *sr = orq->orq_resolver;
su_home_t *home = msg_home(orq->orq_request);
struct sipdns_query *sq = sr->sr_current;
int i, j, found;
char *result, **results = NULL;
assert(sq); assert(sq->sq_type == sres_type_a);
sr->sr_query = NULL;
for (i = 0, found = 0; answers && answers[i]; i++) {
sres_a_record_t const *a = answers[i]->sr_a;
if (a->a_record->r_status == 0 &&
a->a_record->r_type == sres_type_a)
found++;
}
if (found > 1)
results = su_zalloc(home, (found + 1) * (sizeof *results));
else if (found)
results = &result;
for (i = j = 0; answers && answers[i]; i++) {
char addr[SU_ADDRSIZE];
sres_a_record_t const *a = answers[i]->sr_a;
if (a->a_record->r_status ||
a->a_record->r_type != sres_type_a)
continue; /* There was an error */
su_inet_ntop(AF_INET, &a->a_addr, addr, sizeof(addr));
if (j == 0)
SU_DEBUG_5(("nta: %s IN A %s\n", a->a_record->r_name, addr));
else
SU_DEBUG_5(("nta(%p): A %s\n", (void *)orq, addr));
assert(j < found);
results[j++] = su_strdup(home, addr);
}
sres_free_answers(orq->orq_agent->sa_resolver, answers);
outgoing_query_results(orq, sq, results, found);
}
/** Store A/AAAA query results */
static void
outgoing_query_results(nta_outgoing_t *orq,
struct sipdns_query *sq,
char *results[],
size_t rlen)
{
struct sipdns_resolver *sr = orq->orq_resolver;
if (sq->sq_type == sr->sr_a_aaaa1 &&
sq->sq_type != sr->sr_a_aaaa2) {
sq->sq_type = sr->sr_a_aaaa2;
SU_DEBUG_7(("nta(%p): %s %s record still unresolved\n", (void *)orq,
sq->sq_domain, sq->sq_type == sres_type_a ? "A" : "AAAA"));
/*
* Three possible policies:
* 1) try each host for AAAA/A, then A/AAAA
* 2) try everything first for AAAA/A, then everything for A/AAAA
* 3) try one SRV record results for AAAA/A, then for A/AAAA,
* then next SRV record
*/
/* We use now policy #1 */
if (!(sq->sq_next = sr->sr_head))
sr->sr_tail = &sq->sq_next;
sr->sr_head = sq;
}
else {
sq->sq_next = sr->sr_done, sr->sr_done = sq;
if (rlen == 0 && sq->sq_grayish)
outgoing_graylist(orq, sq);
}
if (rlen > 1)
sr->sr_results = results;
else
sr->sr_current = NULL;
if (rlen > 0) {
orq->orq_resolved = 1;
orq->orq_tpn->tpn_host = results[0];
if (sq->sq_proto) orq->orq_tpn->tpn_proto = sq->sq_proto;
if (sq->sq_port[0]) orq->orq_tpn->tpn_port = sq->sq_port;
outgoing_prepare_send(orq);
} else {
outgoing_resolve_next(orq);
}
}
#endif
/* ====================================================================== */
/* 10) Reliable responses */
static nta_prack_f nta_reliable_destroyed;
/**
* Check that server transaction can be used to send reliable provisional
* responses.
*/
su_inline
int reliable_check(nta_incoming_t *irq)
{
if (irq == NULL || irq->irq_status >= 200 || !irq->irq_agent)
return 0;
if (irq->irq_reliable && irq->irq_reliable->rel_status >= 200)
return 0;
/* @RSeq is initialized to nonzero when request requires/supports 100rel */
if (irq->irq_rseq == 0)
return 0;
if (irq->irq_rseq == 0xffffffffU) /* already sent >> 2**31 responses */
return 0;
return 1;
}
/** Respond reliably.
*
* @param irq
* @param callback
* @param rmagic
* @param status
* @param phrase
* @param tag, value, ..
*/
nta_reliable_t *nta_reliable_treply(nta_incoming_t *irq,
nta_prack_f *callback,
nta_reliable_magic_t *rmagic,
int status, char const *phrase,
tag_type_t tag,
tag_value_t value, ...)
{
ta_list ta;
msg_t *msg;
sip_t *sip;
nta_reliable_t *retval = NULL;
if (!reliable_check(irq) || (status <= 100 || status >= 200))
return NULL;
msg = nta_msg_create(irq->irq_agent, 0);
sip = sip_object(msg);
if (!sip)
return NULL;
ta_start(ta, tag, value);
if (0 > nta_incoming_complete_response(irq, msg, status, phrase,
ta_tags(ta)))
msg_destroy(msg);
else if (!(retval = reliable_mreply(irq, callback, rmagic, msg, sip)))
msg_destroy(msg);
ta_end(ta);
return retval;
}
/** Respond reliably with @a msg.
*
* @note
* The stack takes over the ownership of @a msg. (It is destroyed even if
* sending the response fails.)
*
* @param irq
* @param callback
* @param rmagic
* @param msg
*/
nta_reliable_t *nta_reliable_mreply(nta_incoming_t *irq,
nta_prack_f *callback,
nta_reliable_magic_t *rmagic,
msg_t *msg)
{
sip_t *sip = sip_object(msg);
if (!reliable_check(irq)) {
msg_destroy(msg);
return NULL;
}
if (sip == NULL || !sip->sip_status || sip->sip_status->st_status <= 100) {
msg_destroy(msg);
return NULL;
}
if (sip->sip_status->st_status >= 200) {
incoming_final_failed(irq, msg);
return NULL;
}
return reliable_mreply(irq, callback, rmagic, msg, sip);
}
static
nta_reliable_t *reliable_mreply(nta_incoming_t *irq,
nta_prack_f *callback,
nta_reliable_magic_t *rmagic,
msg_t *msg,
sip_t *sip)
{
nta_reliable_t *rel;
nta_agent_t *agent;
agent = irq->irq_agent;
if (callback == NULL)
callback = nta_reliable_destroyed;
rel = su_zalloc(agent->sa_home, sizeof(*rel));
if (rel) {
rel->rel_irq = irq;
rel->rel_callback = callback;
rel->rel_magic = rmagic;
rel->rel_unsent = msg;
rel->rel_status = sip->sip_status->st_status;
rel->rel_precious = sip->sip_payload != NULL;
rel->rel_next = irq->irq_reliable;
/*
* If there already is a un-pr-acknowledged response, queue this one
* until at least one response is pr-acknowledged.
*/
if (irq->irq_reliable &&
(irq->irq_reliable->rel_next == NULL ||
irq->irq_reliable->rel_rseq == 0)) {
return irq->irq_reliable = rel;
}
if (reliable_send(irq, rel, msg_ref_create(msg), sip) < 0) {
msg_destroy(msg);
su_free(agent->sa_home, rel);
return NULL;
}
irq->irq_reliable = rel;
return callback ? rel : (nta_reliable_t *)-1;
}
msg_destroy(msg);
return NULL;
}
static
int reliable_send(nta_incoming_t *irq,
nta_reliable_t *rel,
msg_t *msg,
sip_t *sip)
{
nta_agent_t *sa = irq->irq_agent;
su_home_t *home = msg_home(msg);
sip_rseq_t rseq[1];
sip_rseq_init(rseq);
if (sip->sip_require)
msg_header_replace_param(home, sip->sip_require->k_common, "100rel");
else
sip_add_make(msg, sip, sip_require_class, "100rel");
rel->rel_rseq = rseq->rs_response = irq->irq_rseq;
sip_add_dup(msg, sip, (sip_header_t *)rseq);
if (!sip->sip_rseq || incoming_reply(irq, msg, sip) < 0) {
msg_destroy(msg);
return -1;
}
irq->irq_rseq++;
if (irq->irq_queue == sa->sa_in.preliminary)
/* Make sure we are moved to the tail */
incoming_remove(irq);
incoming_queue(sa->sa_in.preliminary, irq); /* P1 */
incoming_set_timer(irq, sa->sa_t1); /* P2 */
return 0;
}
/** Queue final response when there are unsent precious preliminary responses */
static
int reliable_final(nta_incoming_t *irq, msg_t *msg, sip_t *sip)
{
nta_reliable_t *r;
unsigned already_in_callback;
/*
* We delay sending final response if it's 2XX and
* an unpracked reliable response contains session description
*/
/* Get last unpracked response from queue */
if (sip->sip_status->st_status < 300)
for (r = irq->irq_reliable; r; r = r->rel_next)
if (r->rel_unsent && r->rel_precious) {
/* Delay sending 2XX */
reliable_mreply(irq, NULL, NULL, msg, sip);
return 0;
}
/* Flush unsent responses. */
already_in_callback = irq->irq_in_callback;
irq->irq_in_callback = 1;
reliable_flush(irq);
irq->irq_in_callback = already_in_callback;
if (!already_in_callback && irq->irq_terminated && irq->irq_destroyed) {
incoming_free(irq);
msg_destroy(msg);
return 0;
}
return 1;
}
/** Get latest reliably sent response */
static
msg_t *reliable_response(nta_incoming_t *irq)
{
nta_reliable_t *r, *rel;
/* Get last unpracked response from queue */
for (rel = NULL, r = irq->irq_reliable; r; r = r->rel_next)
if (!r->rel_pracked)
rel = r;
assert(rel);
return rel->rel_unsent;
}
/* Find un-PRACKed responses */
static
nta_reliable_t *reliable_find(nta_agent_t const *agent,
sip_t const *sip)
{
incoming_htable_t const *iht = agent->sa_incoming;
nta_incoming_t *irq, **ii;
sip_call_id_t const *i = sip->sip_call_id;
sip_rack_t const *rack = sip->sip_rack;
hash_value_t hash = NTA_HASH(i, rack->ra_cseq);
/* XXX - add own hash table for 100rel */
for (ii = incoming_htable_hash(iht, hash);
(irq = *ii);
ii = incoming_htable_next(iht, ii)) {
if (hash == irq->irq_hash &&
irq->irq_call_id->i_hash == i->i_hash &&
irq->irq_cseq->cs_seq == rack->ra_cseq &&
irq->irq_method == sip_method_invite &&
strcmp(irq->irq_call_id->i_id, i->i_id) == 0 &&
(irq->irq_to->a_tag == NULL ||
su_casematch(irq->irq_to->a_tag, sip->sip_to->a_tag)) &&
su_casematch(irq->irq_from->a_tag, sip->sip_from->a_tag)) {
nta_reliable_t const *rel;
/* Found matching INVITE */
for (rel = irq->irq_reliable; rel; rel = rel->rel_next)
if (rel->rel_rseq == rack->ra_response)
return (nta_reliable_t *)rel;
return NULL;
}
}
return NULL;
}
/** Process incoming PRACK with matching @RAck field */
static
int reliable_recv(nta_reliable_t *rel, msg_t *msg, sip_t *sip, tport_t *tp)
{
nta_incoming_t *irq = rel->rel_irq;
nta_incoming_t *pr_irq;
int status;
rel->rel_pracked = 1;
msg_ref_destroy(rel->rel_unsent), rel->rel_unsent = NULL;
pr_irq = incoming_create(irq->irq_agent, msg, sip, tp, irq->irq_tag);
if (!pr_irq) {
mreply(irq->irq_agent, NULL,
SIP_500_INTERNAL_SERVER_ERROR, msg,
tp, 0, 0, NULL,
TAG_END());
return 0;
}
if (irq->irq_status < 200) {
incoming_queue(irq->irq_agent->sa_in.proceeding, irq); /* Reset P1 */
incoming_reset_timer(irq); /* Reset P2 */
}
irq->irq_in_callback = pr_irq->irq_in_callback = 1;
status = rel->rel_callback(rel->rel_magic, rel, pr_irq, sip); rel = NULL;
irq->irq_in_callback = pr_irq->irq_in_callback = 0;
if (pr_irq->irq_completed) { /* Already sent final response */
if (pr_irq->irq_terminated && pr_irq->irq_destroyed)
incoming_free(pr_irq);
}
else if (status != 0) {
if (status < 200 || status > 299) {
SU_DEBUG_3(("nta_reliable(): invalid status %03d from callback\n",
status));
status = 200;
}
nta_incoming_treply(pr_irq, status, "OK", TAG_END());
nta_incoming_destroy(pr_irq);
}
/* If there are queued unsent reliable responses, send them all. */
while (irq->irq_reliable && irq->irq_reliable->rel_rseq == 0) {
nta_reliable_t *r;
for (r = irq->irq_reliable; r; r = r->rel_next)
if (r->rel_rseq == 0)
rel = r;
msg = rel->rel_unsent, sip = sip_object(msg);
if (sip->sip_status->st_status < 200) {
if (reliable_send(irq, rel, msg_ref_create(msg), sip) < 0) {
assert(!"send reliable response");
}
}
else {
/*
* XXX
* Final response should be delayed until a reliable provisional
* response has been pracked
*/
rel->rel_unsent = NULL, rel->rel_rseq = (uint32_t)-1;
if (incoming_reply(irq, msg, sip) < 0) {
assert(!"send delayed final response");
}
}
}
return 0;
}
/** Flush unacknowledged and unsent reliable responses */
void reliable_flush(nta_incoming_t *irq)
{
nta_reliable_t *r, *rel;
do {
for (r = irq->irq_reliable, rel = NULL; r; r = r->rel_next)
if (r->rel_unsent)
rel = r;
if (rel) {
rel->rel_pracked = 1;
msg_ref_destroy(rel->rel_unsent), rel->rel_unsent = NULL;
rel->rel_callback(rel->rel_magic, rel, NULL, NULL);
}
} while (rel);
}
void reliable_timeout(nta_incoming_t *irq, int timeout)
{
if (timeout)
SU_DEBUG_5(("nta: response timeout with %u\n", irq->irq_status));
irq->irq_in_callback = 1;
reliable_flush(irq);
if (irq->irq_callback)
irq->irq_callback(irq->irq_magic, irq, NULL);
irq->irq_in_callback = 0;
if (!timeout)
return;
if (irq->irq_completed && irq->irq_destroyed)
incoming_free(irq), irq = NULL;
else if (irq->irq_status < 200)
nta_incoming_treply(irq, 503, "Reliable Response Time-Out", TAG_END());
}
#if 0 /* Not needed, yet. */
/** Use this callback when normal leg callback is supposed to
* process incoming PRACK requests
*/
int nta_reliable_leg_prack(nta_reliable_magic_t *magic,
nta_reliable_t *rel,
nta_incoming_t *irq,
sip_t const *sip)
{
nta_agent_t *agent;
nta_leg_t *leg;
char const *method_name;
url_t url[1];
int retval;
if (irq == NULL || sip == NULL || rel == NULL ||
sip_object(irq->irq_request) != sip)
return 500;
agent = irq->irq_agent;
method_name = sip->sip_request->rq_method_name;
*url = *sip->sip_request->rq_url; url->url_params = NULL;
agent_aliases(agent, url, irq->irq_tport); /* canonize urls */
if ((leg = leg_find(irq->irq_agent,
method_name, url,
sip->sip_call_id,
sip->sip_from->a_tag,
sip->sip_to->a_tag))) {
/* Use existing dialog */
SU_DEBUG_5(("nta: %s (%u) %s\n",
method_name, sip->sip_cseq->cs_seq,
"PRACK processed by default callback, too"));
retval = leg->leg_callback(leg->leg_magic, leg, irq, sip);
}
else {
retval = 500;
}
nta_reliable_destroy(rel);
return retval;
}
#endif
/** Destroy a reliable response.
*
* Mark a reliable response object for destroyal and free it if possible.
*/
void nta_reliable_destroy(nta_reliable_t *rel)
{
if (rel == NULL || rel == NONE)
return;
if (rel->rel_callback == nta_reliable_destroyed)
SU_DEBUG_1(("%s(%p): %s\n", __func__, (void *)rel, "already destroyed"));
rel->rel_callback = nta_reliable_destroyed;
if (rel->rel_response)
return;
nta_reliable_destroyed(NULL, rel, NULL, NULL);
}
/** Free and unallocate the nta_reliable_t structure. */
static
int nta_reliable_destroyed(nta_reliable_magic_t *rmagic,
nta_reliable_t *rel,
nta_incoming_t *prack,
sip_t const *sip)
{
nta_reliable_t **prev;
assert(rel); assert(rel->rel_irq);
for (prev = &rel->rel_irq->irq_reliable; *prev; prev = &(*prev)->rel_next)
if (*prev == rel)
break;
if (!*prev) {
assert(*prev);
SU_DEBUG_1(("%s(%p): %s\n", __func__, (void *)rel, "not linked"));
return 200;
}
*prev = rel->rel_next;
if (rel->rel_unsent)
msg_destroy(rel->rel_unsent), rel->rel_unsent = NULL;
su_free(rel->rel_irq->irq_agent->sa_home, rel);
return 200;
}
/** Validate a reliable response. */
int outgoing_recv_reliable(nta_outgoing_t *orq,
msg_t *msg,
sip_t *sip)
{
short status = sip->sip_status->st_status;
char const *phrase = sip->sip_status->st_phrase;
uint32_t rseq = sip->sip_rseq->rs_response;
SU_DEBUG_7(("nta: %03u %s is reliably received with RSeq: %u\n",
status, phrase, rseq));
/* Cannot handle reliable responses unless we have a full dialog */
if (orq->orq_rseq == 0 && !orq->orq_to->a_tag) {
SU_DEBUG_5(("nta: %03u %s with initial RSeq: %u outside dialog\n",
status, phrase, rseq));
return 0;
}
if (rseq <= orq->orq_rseq) {
SU_DEBUG_3(("nta: %03u %s already received (RSeq: %u, expecting %u)\n",
status, phrase, rseq, orq->orq_rseq + 1));
return -1;
}
if (orq->orq_rseq && orq->orq_rseq + 1 != rseq) {
SU_DEBUG_3(("nta: %03d %s is not expected (RSeq: %u, expecting %u)\n",
status, sip->sip_status->st_phrase,
rseq, orq->orq_rseq + 1));
return -1;
}
return 0;
}
/** Create a tagged fork of outgoing request.
*
* When a dialog-creating INVITE request is forked, each response from
* diffent fork will create an early dialog with a distinct tag in @To
* header. When each fork should be handled separately, a tagged INVITE
* request can be used. It will only receive responses from the specified
* fork. Please note that the tagged transaction should be terminated with
* the final response from another fork, too.
*
* @param orq
* @param callback
* @param magic
* @param to_tag
* @param rseq
*
* @bug Fix the memory leak - either one of the requests is left unreleased
* for ever.
*/
nta_outgoing_t *nta_outgoing_tagged(nta_outgoing_t *orq,
nta_response_f *callback,
nta_outgoing_magic_t *magic,
char const *to_tag,
sip_rseq_t const *rseq)
{
nta_agent_t *agent;
su_home_t *home;
nta_outgoing_t *tagged;
sip_to_t *to;
if (orq == NULL || to_tag == NULL)
return NULL;
if (orq->orq_to->a_tag) {
SU_DEBUG_1(("%s: transaction %p (CSeq: %s %u) already in dialog\n", __func__,
(void *)orq, orq->orq_cseq->cs_method_name, orq->orq_cseq->cs_seq));
return NULL;
}
if (orq->orq_method != sip_method_invite) {
SU_DEBUG_1(("%s: transaction %p (CSeq: %s %u) cannot be tagged\n", __func__,
(void *)orq, orq->orq_cseq->cs_method_name, orq->orq_cseq->cs_seq));
return NULL;
}
if (orq->orq_status < 100) {
SU_DEBUG_1(("%s: transaction %p (CSeq: %s %u) still calling\n", __func__,
(void *)orq, orq->orq_cseq->cs_method_name, orq->orq_cseq->cs_seq));
return NULL;
}
assert(orq->orq_agent); assert(orq->orq_request);
agent = orq->orq_agent;
tagged = su_zalloc(agent->sa_home, sizeof(*tagged));
home = msg_home((msg_t *)orq->orq_request);
tagged->orq_hash = orq->orq_hash;
tagged->orq_agent = orq->orq_agent;
tagged->orq_callback = callback;
tagged->orq_magic = magic;
tagged->orq_method = orq->orq_method;
tagged->orq_method_name = orq->orq_method_name;
tagged->orq_url = orq->orq_url;
tagged->orq_from = orq->orq_from;
sip_to_tag(home, to = sip_to_copy(home, orq->orq_to), to_tag);
tagged->orq_to = to;
tagged->orq_tag = to->a_tag;
tagged->orq_cseq = orq->orq_cseq;
tagged->orq_call_id = orq->orq_call_id;
tagged->orq_request = msg_ref_create(orq->orq_request);
tagged->orq_response = msg_ref_create(orq->orq_response);
tagged->orq_status = orq->orq_status;
tagged->orq_via_added = orq->orq_via_added;
tagged->orq_prepared = orq->orq_prepared;
tagged->orq_reliable = orq->orq_reliable;
tagged->orq_sips = orq->orq_sips;
tagged->orq_uas = orq->orq_uas;
tagged->orq_pass_100 = orq->orq_pass_100;
tagged->orq_must_100rel = orq->orq_must_100rel;
tagged->orq_100rel = orq->orq_100rel;
tagged->orq_route = orq->orq_route;
*tagged->orq_tpn = *orq->orq_tpn;
tagged->orq_tport = tport_ref(orq->orq_tport);
if (orq->orq_cc)
tagged->orq_cc = nta_compartment_ref(orq->orq_cc);
tagged->orq_branch = orq->orq_branch;
tagged->orq_via_branch = orq->orq_via_branch;
if (tagged->orq_uas) {
tagged->orq_forking = orq;
tagged->orq_forks = orq->orq_forks;
tagged->orq_forked = 1;
orq->orq_forks = tagged;
}
outgoing_insert(agent, tagged);
return tagged;
}
/**PRACK a provisional response.
*
* Create and send a PRACK request used to acknowledge a provisional
* response.
*
* The request is sent using the route of the original request @a oorq.
*
* When NTA receives response to the prack request, it invokes the @a
* callback function.
*
* @param leg dialog object
* @param oorq original transaction request
* @param callback callback function (may be @c NULL)
* @param magic application context pointer
* @param route_url optional URL used to route transaction requests
* @param resp (optional) response message to be acknowledged
* @param tag,value,... optional
*
* @return
* If successful, return a pointer to newly created client transaction
* object for PRACK request, NULL otherwise.
*
* @sa
* nta_outgoing_tcreate(), nta_outgoing_tcancel(), nta_outgoing_destroy().
*/
nta_outgoing_t *nta_outgoing_prack(nta_leg_t *leg,
nta_outgoing_t *oorq,
nta_response_f *callback,
nta_outgoing_magic_t *magic,
url_string_t const *route_url,
sip_t const *resp,
tag_type_t tag, tag_value_t value, ...)
{
ta_list ta;
msg_t *msg;
su_home_t *home;
sip_t *sip;
sip_to_t const *to = NULL;
sip_route_t *route = NULL, r0[1];
nta_outgoing_t *orq = NULL;
sip_rack_t *rack = NULL, rack0[1];
if (!leg || !oorq) {
SU_DEBUG_1(("%s: invalid arguments\n", __func__));
return NULL;
}
sip_rack_init(rack0);
if (resp) {
if (!resp->sip_status) {
SU_DEBUG_1(("%s: invalid arguments\n", __func__));
return NULL;
}
if (resp->sip_status->st_status <= 100 ||
resp->sip_status->st_status >= 200) {
SU_DEBUG_1(("%s: %u response cannot be PRACKed\n",
__func__, resp->sip_status->st_status));
return NULL;
}
if (!resp->sip_rseq) {
SU_DEBUG_1(("%s: %u response missing RSeq\n",
__func__, resp->sip_status->st_status));
return NULL;
}
if (resp->sip_rseq->rs_response <= oorq->orq_rseq) {
SU_DEBUG_1(("%s: %u response RSeq does not match received RSeq\n",
__func__, resp->sip_status->st_status));
return NULL;
}
if (!oorq->orq_must_100rel &&
!sip_has_feature(resp->sip_require, "100rel")) {
SU_DEBUG_1(("%s: %u response does not require 100rel\n",
__func__, resp->sip_status->st_status));
return NULL;
}
if (!resp->sip_to->a_tag) {
SU_DEBUG_1(("%s: %u response has no To tag\n",
__func__, resp->sip_status->st_status));
return NULL;
}
if (su_strcasecmp(resp->sip_to->a_tag, leg->leg_remote->a_tag) ||
su_strcasecmp(resp->sip_to->a_tag, oorq->orq_to->a_tag)) {
SU_DEBUG_1(("%s: %u response To tag does not agree with dialog tag\n",
__func__, resp->sip_status->st_status));
return NULL;
}
to = resp->sip_to;
rack = rack0;
rack->ra_response = resp->sip_rseq->rs_response;
rack->ra_cseq = resp->sip_cseq->cs_seq;
rack->ra_method = resp->sip_cseq->cs_method;
rack->ra_method_name = resp->sip_cseq->cs_method_name;
}
msg = nta_msg_create(leg->leg_agent, 0);
sip = sip_object(msg); home = msg_home(msg);
if (!sip)
return NULL;
if (!leg->leg_route && resp) {
/* Insert contact into route */
if (resp->sip_contact) {
sip_route_init(r0)->r_url[0] = resp->sip_contact->m_url[0];
route = sip_route_dup(home, r0);
}
/* Reverse record route */
if (resp->sip_record_route) {
sip_route_t *r, *r_next;
for (r = sip_route_dup(home, resp->sip_record_route); r; r = r_next) {
r_next = r->r_next, r->r_next = route, route = r;
}
}
}
ta_start(ta, tag, value);
if (!resp) {
tagi_t const *t;
if ((t = tl_find(ta_args(ta), ntatag_rseq)) && t->t_value) {
rack = rack0;
rack->ra_response = (uint32_t)t->t_value;
}
if (rack) {
rack->ra_cseq = oorq->orq_cseq->cs_seq;
rack->ra_method = oorq->orq_cseq->cs_method;
rack->ra_method_name = oorq->orq_cseq->cs_method_name;
}
}
if (sip_add_tl(msg, sip,
TAG_IF(rack, SIPTAG_RACK(rack)),
TAG_IF(to, SIPTAG_TO(to)),
ta_tags(ta)) < 0)
;
else if (route && sip_add_dup(msg, sip, (sip_header_t *)route) < 0)
;
else if (!sip->sip_rack)
SU_DEBUG_1(("%s: RAck header missing\n", __func__));
else if (nta_msg_request_complete(msg, leg,
SIP_METHOD_PRACK,
(url_string_t *)oorq->orq_url) < 0)
;
else
orq = outgoing_create(leg->leg_agent, callback, magic,
route_url, NULL, msg, ta_tags(ta));
ta_end(ta);
if (!orq)
msg_destroy(msg);
else if (rack)
oorq->orq_rseq = rack->ra_response;
else if (sip->sip_rack)
oorq->orq_rseq = sip->sip_rack->ra_response;
return orq;
}
/** Get @RSeq value stored with client transaction. */
uint32_t nta_outgoing_rseq(nta_outgoing_t const *orq)
{
return orq ? orq->orq_rseq : 0;
}
/** Set @RSeq value stored with client transaction.
*
* @return 0 if rseq was set successfully
* @return -1 if rseq is invalid or orq is NULL.
*/
int nta_outgoing_setrseq(nta_outgoing_t *orq, uint32_t rseq)
{
if (orq && orq->orq_rseq <= rseq) {
orq->orq_rseq = rseq;
return 0;
}
return -1;
}
/* ------------------------------------------------------------------------ */
/* 11) SigComp handling and public transport interface */
#include <sofia-sip/nta_tport.h>
/** Return the master transport for the agent.
*
* @NEW_1_12_11
*/
tport_t *
nta_agent_tports(nta_agent_t *agent)
{
return agent ? agent->sa_tports : NULL;
}
su_inline tport_t *
nta_transport_(nta_agent_t *agent,
nta_incoming_t *irq,
msg_t *msg)
{
if (irq)
return irq->irq_tport;
else if (agent && msg)
return tport_delivered_by(agent->sa_tports, msg);
errno = EINVAL;
return NULL;
}
/** Return a new reference to the transaction transport.
*
* @note The referenced transport must be unreferenced with tport_unref()
*/
tport_t *
nta_incoming_transport(nta_agent_t *agent,
nta_incoming_t *irq,
msg_t *msg)
{
return tport_ref(nta_transport_(agent, irq, msg));
}
nta_compressor_t *nta_agent_init_sigcomp(nta_agent_t *sa)
{
if (!nta_compressor_vtable || !sa)
return NULL;
if (sa->sa_compressor == NULL) {
char const * const *l = sa->sa_sigcomp_option_list;
nta_compressor_t *comp;
comp = nta_compressor_vtable->ncv_init_agent(sa, l);
sa->sa_compressor = comp;
}
return sa->sa_compressor;
}
void nta_agent_deinit_sigcomp(nta_agent_t *sa)
{
if (nta_compressor_vtable && sa && sa->sa_compressor) {
nta_compressor_vtable->ncv_deinit_agent(sa, sa->sa_compressor);
sa->sa_compressor = NULL;
}
}
struct sigcomp_compartment *
nta_incoming_compartment(nta_incoming_t *irq)
{
if (nta_compressor_vtable && irq && irq->irq_cc)
return nta_compressor_vtable->ncv_compartment_ref(irq->irq_cc);
else
return NULL;
}
tport_t *
nta_outgoing_transport(nta_outgoing_t *orq)
{
if (orq)
return tport_ref(orq->orq_tport);
else
return NULL;
}
struct sigcomp_compartment *
nta_outgoing_compartment(nta_outgoing_t *orq)
{
if (nta_compressor_vtable && orq && orq->orq_cc)
return nta_compressor_vtable->ncv_compartment_ref(orq->orq_cc);
else
return NULL;
}
struct sigcomp_compartment *
nta_compartment_ref(struct sigcomp_compartment *cc)
{
if (nta_compressor_vtable)
return nta_compressor_vtable->ncv_compartment_ref(cc);
else
return NULL;
}
void
nta_compartment_decref(struct sigcomp_compartment **pcc)
{
if (nta_compressor_vtable && pcc && *pcc)
nta_compressor_vtable->ncv_compartment_unref(*pcc), *pcc = NULL;
}
/** Get compartment for connection, create it when needed. */
static
struct sigcomp_compartment *
agent_compression_compartment(nta_agent_t *sa,
tport_t *tp,
tp_name_t const *tpn,
int new_if_needed)
{
if (nta_compressor_vtable) {
char const * const *l = sa->sa_sigcomp_option_list;
return nta_compressor_vtable->
ncv_compartment(sa, tp, sa->sa_compressor, tpn, l, new_if_needed);
}
else
return NULL;
}
static
int agent_accept_compressed(nta_agent_t *sa, msg_t *msg,
struct sigcomp_compartment *cc)
{
if (nta_compressor_vtable) {
nta_compressor_t *msc = sa->sa_compressor;
tport_compressor_t *sc = NULL;
if (tport_delivered_with_comp(sa->sa_tports, msg, &sc) < 0)
return 0;
return nta_compressor_vtable->ncv_accept_compressed(sa, msc, sc, msg, cc);
}
else
return 0;
}
/** Close compressor (lose its state). */
static
int agent_close_compressor(nta_agent_t *sa,
struct sigcomp_compartment *cc)
{
if (nta_compressor_vtable)
return nta_compressor_vtable->ncv_close_compressor(sa, cc);
return 0;
}
/** Close both compressor and decompressor */
static
int agent_zap_compressor(nta_agent_t *sa,
struct sigcomp_compartment *cc)
{
if (nta_compressor_vtable)
return nta_compressor_vtable->ncv_zap_compressor(sa, cc);
return 0;
}
/** Bind transport update callback */
int nta_agent_bind_tport_update(nta_agent_t *agent,
nta_update_magic_t *magic,
nta_update_tport_f *callback)
{
if (!agent)
return su_seterrno(EFAULT), -1;
agent->sa_update_magic = magic;
agent->sa_update_tport = callback;
return 0;
}
/** Bind transport error callback */
int nta_agent_bind_tport_error(nta_agent_t *agent,
nta_error_magic_t *magic,
nta_error_tport_f *callback)
{
if (!agent)
return su_seterrno(EFAULT), -1;
agent->sa_error_magic = magic;
agent->sa_error_tport = callback;
return 0;
}
/** Check if public transport binding is in progress */
int nta_agent_tport_is_updating(nta_agent_t *agent)
{
return agent && tport_is_updating(agent->sa_tports);
}
/** Initiate STUN keepalive controller to TPORT */
int nta_tport_keepalive(nta_outgoing_t *orq)
{
assert(orq);
#if HAVE_SOFIA_STUN
return tport_keepalive(orq->orq_tport, msg_addrinfo(orq->orq_request),
TAG_END());
#else
return -1;
#endif
}
/** Close all transports. @since Experimental in @VERSION_1_12_2. */
int nta_agent_close_tports(nta_agent_t *agent)
{
size_t i;
outgoing_htable_t *oht = agent->sa_outgoing;
incoming_htable_t *iht = agent->sa_incoming;
for (i = oht->oht_size; i-- > 0;)
/* while */ if (oht->oht_table[i]) {
nta_outgoing_t *orq = oht->oht_table[i];
if (orq->orq_pending && orq->orq_tport)
tport_release(orq->orq_tport, orq->orq_pending, orq->orq_request,
NULL, orq, 0);
orq->orq_pending = 0;
tport_unref(orq->orq_tport), orq->orq_tport = NULL;
}
for (i = iht->iht_size; i-- > 0;)
/* while */ if (iht->iht_table[i]) {
nta_incoming_t *irq = iht->iht_table[i];
tport_unref(irq->irq_tport), irq->irq_tport = NULL;
}
tport_destroy(agent->sa_tports), agent->sa_tports = NULL;
msg_header_free(agent->sa_home, (void *)agent->sa_vias);
agent->sa_vias = NULL;
msg_header_free(agent->sa_home, (void *)agent->sa_public_vias);
agent->sa_public_vias = NULL;
return 0;
}