diff --git a/libs/sofia-sip/Makefile.am b/libs/sofia-sip/Makefile.am index a8a7e9f189..98e7707893 100644 --- a/libs/sofia-sip/Makefile.am +++ b/libs/sofia-sip/Makefile.am @@ -42,6 +42,7 @@ $(dist_man_MANS): manpages manpages: -mkdir -p man man/man1 2> /dev/null if HAVE_DOXYGEN + $(MAKE) $(AM_MAKEFLAGS) -C libsofia-sip-ua/docs built-sources @echo 'cd utils && $(DOXYGEN)' @cd utils && \ { exec 3>&1 1>&2; { $(DOXYGEN) 2>&1; echo $$? >& 3 ;} | \ diff --git a/libs/sofia-sip/libsofia-sip-ua/Makefile.am b/libs/sofia-sip/libsofia-sip-ua/Makefile.am index e3359f7d98..40acd84b06 100644 --- a/libs/sofia-sip/libsofia-sip-ua/Makefile.am +++ b/libs/sofia-sip/libsofia-sip-ua/Makefile.am @@ -26,7 +26,8 @@ endif # note: order does matter in the subdir list SUBDIRS = su features bnf sresolv sdp url msg sip $(OPT_SUBDIRS_STUN) ipt soa \ tport nta nea iptsec $(OPT_SUBDIRS_NTH) nua -DIST_SUBDIRS = $(SUBDIRS) docs +DIST_SUBDIRS = su features bnf sresolv sdp url msg sip stun ipt soa \ + tport nta nea iptsec nth http nua docs DOXYGEN = doxygen diff --git a/libs/sofia-sip/libsofia-sip-ua/docs/mainpage.docs b/libs/sofia-sip/libsofia-sip-ua/docs/mainpage.docs index 3986062ce6..f40ebce673 100644 --- a/libs/sofia-sip/libsofia-sip-ua/docs/mainpage.docs +++ b/libs/sofia-sip/libsofia-sip-ua/docs/mainpage.docs @@ -55,9 +55,10 @@ Or post to the Sofia-SIP mailing list: @section subdirs Directory Structure -In libsofia-sip-ua, there are subdirectories +In libsofia-sip-ua, there are subdirectories for different modules listed +below. -Terminal and high-level libraries utilizing for both signaling and media: +Terminal and high-level libraries used for both signaling and media: Common runtime library: - "su" - sockets, memory management, threads diff --git a/libs/sofia-sip/libsofia-sip-ua/nta/nta.c b/libs/sofia-sip/libsofia-sip-ua/nta/nta.c index 944510aa09..e6f03e994f 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nta/nta.c +++ b/libs/sofia-sip/libsofia-sip-ua/nta/nta.c @@ -2690,12 +2690,14 @@ void agent_recv_response(nta_agent_t *agent, && sip->sip_via && !sip->sip_via->v_next && agent_has_via(agent, sip->sip_via)) { agent->sa_stats->as_trless_200++; +#if nomore /* sf.net bug #1750691. Let UAS to cope with it. */ if (agent->sa_is_a_uas) { /* Orphan 200 Ok to INVITE. ACK and BYE it */ SU_DEBUG_5(("nta: %03d %s %s\n", status, phrase, "is ACK&BYE")); if (nta_msg_ackbye(agent, msg) != -1) return; } +#endif } SU_DEBUG_5(("nta: %03d %s %s\n", status, phrase, "was discarded")); @@ -3067,15 +3069,19 @@ int complete_response(msg_t *response, /** ACK and BYE an unknown 200 OK response to INVITE. * - * A UAS may still return a 2XX series response to an INVITE request after - * the client transaction has been terminated. In that case, the UAC can not - * really accept the call, but it may send a ACK request to UAS followed - * immediately by BYE using nta_msg_ackbye(). The function does not create a - * transaction objects, but just sends the ACK and BYE request messages - * according to the @RecordRoute and @Contact headers in the @a msg. + * 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) { +#if nomore sip_t *sip = sip_object(msg); msg_t *amsg = nta_msg_create(agent, 0); sip_t *asip = sip_object(amsg); @@ -3164,6 +3170,8 @@ int nta_msg_ackbye(nta_agent_t *agent, msg_t *msg) err: msg_destroy(amsg); msg_destroy(bmsg); +#endif + (void)agent; (void)msg; return -1; } @@ -5121,6 +5129,21 @@ nta_incoming_magic_t *nta_incoming_magic(nta_incoming_t *irq, return irq && irq->irq_callback == callback ? irq->irq_magic : NULL; } +/** When received */ +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, @@ -7007,6 +7030,7 @@ nta_outgoing_t *outgoing_create(nta_agent_t *agent, 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 (strncasecmp(ack_branch, "branch=", 7) == 0) orq->orq_branch = su_strdup(home, ack_branch); @@ -7313,16 +7337,11 @@ outgoing_send(nta_outgoing_t *orq, int retransmit) if (retransmit) return; - /* Set timers */ - if (orq->orq_method == sip_method_ack) { - /* ACK */ - outgoing_complete(orq); /* Timer K */ - return; - } - outgoing_trying(orq); /* Timer B / F */ - if (!orq->orq_reliable) + if (orq->orq_method == sip_method_ack) + ; + else if (!orq->orq_reliable) outgoing_set_timer(orq, agent->sa_t1); /* Timer A/E */ else if (orq->orq_try_tcp_instead && !tport_is_connected(tp)) outgoing_set_timer(orq, agent->sa_t4); /* Timer N3 */ @@ -7771,11 +7790,11 @@ void outgoing_destroy(nta_outgoing_t *orq) if (orq->orq_terminated || orq->orq_default) { outgoing_free(orq); } - /* We have to handle 200 OK statelessly => - kill transaction immediately */ + /* Application is expected to handle 200 OK statelessly + => kill transaction immediately */ else if (orq->orq_method == sip_method_invite && !orq->orq_completed - /* (unless we have to wait to send CANCEL) */ - && !orq->orq_cancel) { + /* (unless we the transaction has been canceled) */ + && !orq->orq_canceled) { orq->orq_destroyed = 1; outgoing_terminate(orq); } @@ -7927,10 +7946,14 @@ size_t outgoing_timer_bf(outgoing_queue_t *q, timeout++; SU_DEBUG_5(("nta: timer %s fired, %s %s (%u)\n", - timer, "timeout", + timer, + orq->orq_method != sip_method_ack ? "timeout" : "terminating", orq->orq_method_name, orq->orq_cseq->cs_seq)); - outgoing_timeout(orq, now); + if (orq->orq_method != sip_method_ack) + outgoing_timeout(orq, now); + else + outgoing_terminate(orq); assert(q->q_head != orq || orq->orq_timeout - now > 0); } @@ -8002,10 +8025,7 @@ int outgoing_complete(nta_outgoing_t *orq) outgoing_reset_timer(orq); /* Timer A/E */ - if (orq->orq_stateless) - return outgoing_terminate(orq); - - if (orq->orq_reliable && orq->orq_method != sip_method_ack) + if (orq->orq_stateless || orq->orq_reliable) return outgoing_terminate(orq); if (orq->orq_method == sip_method_invite) { @@ -8607,7 +8627,7 @@ int outgoing_reply(nta_outgoing_t *orq, int status, char const *phrase, (void *)orq, status, phrase)); orq->orq_status = status; if (orq->orq_queue == NULL) - outgoing_complete(orq); /* Timer D/K */ + outgoing_trying(orq); /* Timer F */ return 0; } diff --git a/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta.h b/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta.h index f61e3e0379..8778f2f982 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta.h +++ b/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta.h @@ -284,6 +284,7 @@ SOFIAPUBFUN sip_method_t nta_incoming_method(nta_incoming_t const *irq); SOFIAPUBFUN char const *nta_incoming_method_name(nta_incoming_t const *irq); SOFIAPUBFUN url_t const *nta_incoming_url(nta_incoming_t const *irq); SOFIAPUBFUN uint32_t nta_incoming_cseq(nta_incoming_t const *irq); +SOFIAPUBFUN sip_time_t nta_incoming_received(nta_incoming_t *irq, su_nanotime_t *nano); SOFIAPUBFUN int nta_incoming_set_params(nta_incoming_t *irq, tag_type_t tag, tag_value_t value, ...); diff --git a/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta_tag.h b/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta_tag.h index 246e4f290d..e635027a0b 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta_tag.h +++ b/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta_tag.h @@ -1725,15 +1725,15 @@ NTA_DLL extern tag_typedef_t ntatag_s_trless_response_ref; /** Get number of responses without matching request. * * Return number of received responses for which no matching client - * transaction was found. Such responses are processed either by the default + * transaction was found. Such responses are processed either by the * client transaction created with nta_outgoing_default(), the * #nta_message_f message callback given to nta_agent_create(), or, missing * both the default client transaction and message callback, they are * silently discarded. * - * When stack is in UA mode, the successful 2XX responses to the INVITE - * transaction are an exception: when such a response is received the stack - * tries to send an ACK and BYE requests to the originator of 2XX response. + * The NTATAG_S_TRLESS_200_REF() counter counts those successful 2XX + * responses to the INVITE without client transaction which are silently + * discarded. * * @sa nta_agent_get_stats(), nta_outgoing_default(), nta_agent_create(), * , #nta_message_f, nta_msg_ackbye(), @@ -1753,10 +1753,6 @@ NTA_DLL extern tag_typedef_t ntatag_s_trless_200_ref; * default client transaction created with nta_outgoing_default() or * #nta_message_f message callback given to nta_agent_create(). * - * When such a successful 2XX responses to the INVITE is received but it is - * not processed, the stack tries to send an ACK and BYE requests to the - * originator of 2XX response if stack is in UA mode (set by NTATAG_UA(1). - * * @sa nta_agent_get_stats(), nta_outgoing_default(), nta_agent_create(), * , #nta_message_f, nta_msg_ackbye(), * NTATAG_S_TRLESS_RESPONSE_REF(). diff --git a/libs/sofia-sip/libsofia-sip-ua/nta/test_nta_api.c b/libs/sofia-sip/libsofia-sip-ua/nta/test_nta_api.c index 30c71244a7..429d425ca0 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nta/test_nta_api.c +++ b/libs/sofia-sip/libsofia-sip-ua/nta/test_nta_api.c @@ -453,7 +453,7 @@ int api_test_params(agent_t *ag) sip_contact_t const *aliases = (void *)-1; msg_mclass_t *mclass = (void *)-1; - sip_contact_t *contact = (void *)-1; + sip_contact_t const *contact = (void *)-1; url_string_t const *default_proxy = (void *)-1; void *smime = (void *)-1; @@ -859,6 +859,7 @@ static int api_test_default(agent_t *ag) nta_incoming_t *irq; nta_outgoing_t *orq; sip_via_t via[1]; + su_nanotime_t nano; TEST_1(nta = ag->ag_agent); @@ -876,7 +877,9 @@ static int api_test_default(agent_t *ag) TEST_S(nta_incoming_method_name(irq), "*"); TEST_P(nta_incoming_url(irq), NULL); TEST(nta_incoming_cseq(irq), 0); - + + TEST(nta_incoming_received(irq, &nano), nano / 1000000000); + TEST(nta_incoming_set_params(irq, TAG_END()), 0); TEST_P(nta_incoming_getrequest(irq), NULL); @@ -944,6 +947,7 @@ static int api_test_errors(agent_t *ag) nta_agent_t *nta; su_root_t *root; su_home_t home[1]; + su_nanotime_t nano; BEGIN(); @@ -1044,6 +1048,8 @@ static int api_test_errors(agent_t *ag) TEST_P(nta_incoming_method_name(NULL), NULL); TEST_P(nta_incoming_url(NULL), NULL); TEST(nta_incoming_cseq(NULL), 0); + TEST(nta_incoming_received(NULL, &nano), 0); + TEST64(nano, 0); TEST(nta_incoming_set_params(NULL, TAG_END()), -1); diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua.docs b/libs/sofia-sip/libsofia-sip-ua/nua/nua.docs index fb241b7fe2..f27c793cb7 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua.docs +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua.docs @@ -1174,9 +1174,9 @@ follows: terminating* - - The cannot be terminated with BYE before the dialog is established with a - non-100 preliminary response. So, instead of @b BYE, stack sends a @b - CANCEL request, and enters terminating state. + The call cannot be terminated with BYE before the dialog is established + with a non-100 preliminary response. So, instead of a @b BYE, stack sends + a @b CANCEL request, and enters terminating state. However, there is a race condition and the server can respond with a succesful 2XX response before receiving CANCEL. If the server responds with diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_common.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_common.c index 6e7499636e..a37d57c6cf 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_common.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_common.c @@ -114,6 +114,7 @@ nua_handle_t *nh_create_handle(nua_t *nua, nh->nh_nua = nua; nh->nh_magic = hmagic; nh->nh_prefs = nua->nua_dhandle->nh_prefs; + nh->nh_ds->ds_owner = nh; if (nua_handle_save_tags(nh, tags) < 0) { SU_DEBUG_5(("nua(%p): creating handle %p failed\n", diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.c index 185c35c5f9..d4cbd08128 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.c @@ -43,6 +43,7 @@ #include #include +#include #define NUA_OWNER_T su_home_t @@ -127,9 +128,9 @@ void nua_dialog_store_peer_info(nua_owner_t *own, nua_dialog_state_t *ds, sip_t const *sip) { - nua_remote_t *nr = ds->ds_remote_ua; + nua_dialog_peer_info_t *nr = ds->ds_remote_ua; nua_dialog_usage_t *du; - nua_remote_t old[1]; + nua_dialog_peer_info_t old[1]; *old = *nr; @@ -189,6 +190,7 @@ int nua_dialog_remove(nua_owner_t *own, { if (ds->ds_usage == usage && (usage == NULL || usage->du_next == NULL)) { nua_dialog_store_peer_info(own, ds, NULL); /* zap peer info */ + msg_header_free(own, (msg_header_t *)ds->ds_ltarget), ds->ds_ltarget = NULL; nta_leg_destroy(ds->ds_leg), ds->ds_leg = NULL; su_free(own, (void *)ds->ds_remote_tag), ds->ds_remote_tag = NULL; ds->ds_route = 0; @@ -287,6 +289,8 @@ nua_dialog_usage_t *nua_dialog_usage_add(nua_owner_t *own, du = su_zalloc(own, sizeof *du + uclass->usage_size); if (du) { + su_home_ref(own); + du->du_dialog = ds; du->du_class = uclass; du->du_event = o; @@ -300,7 +304,6 @@ nua_dialog_usage_t *nua_dialog_usage_add(nua_owner_t *own, (void *)own, nua_dialog_usage_name(du), o ? " with event " : "", o ? o->o_type :"")); - su_home_ref(own); du->du_next = ds->ds_usage, ds->ds_usage = du; return du; @@ -450,29 +453,28 @@ void nua_dialog_deinit(nua_owner_t *own, * if @a delta is less than 5 minutes but longer than 90 seconds, 30..60 * seconds before end of interval. * - * If @a delta is 0, the refresh time is set at the end of the world - * (maximum time, for 32-bit systems sometimes during 2036). + * If @a delta is 0, the dialog usage is never refreshed. */ void nua_dialog_usage_set_refresh(nua_dialog_usage_t *du, unsigned delta) { if (delta == 0) - du->du_refresh = 0; + nua_dialog_usage_reset_refresh(du); else if (delta > 90 && delta < 5 * 60) /* refresh 30..60 seconds before deadline */ - nua_dialog_usage_refresh_range(du, delta - 60, delta - 30); + nua_dialog_usage_set_refresh_range(du, delta - 60, delta - 30); else { /* By default, refresh around half time before deadline */ unsigned min = (delta + 2) / 4; unsigned max = (delta + 2) / 4 + (delta + 1) / 2; if (min == 0) min = 1; - nua_dialog_usage_refresh_range(du, min, max); + nua_dialog_usage_set_refresh_range(du, min, max); } } /**@internal Set refresh in range min..max seconds in the future. */ -void nua_dialog_usage_refresh_range(nua_dialog_usage_t *du, - unsigned min, unsigned max) +void nua_dialog_usage_set_refresh_range(nua_dialog_usage_t *du, + unsigned min, unsigned max) { sip_time_t now = sip_now(), target; unsigned delta; @@ -493,12 +495,12 @@ void nua_dialog_usage_refresh_range(nua_dialog_usage_t *du, SU_DEBUG_7(("nua(): refresh %s after %lu seconds (in [%u..%u])\n", nua_dialog_usage_name(du), target - now, min, max)); - du->du_refresh = target; + nua_dialog_usage_set_refresh_at(du, target); } /** Set absolute refresh time */ -void nua_dialog_usage_refresh_at(nua_dialog_usage_t *du, - sip_time_t target) +void nua_dialog_usage_set_refresh_at(nua_dialog_usage_t *du, + sip_time_t target) { SU_DEBUG_7(("nua(): refresh %s after %lu seconds\n", nua_dialog_usage_name(du), target - sip_now())); @@ -512,25 +514,14 @@ void nua_dialog_usage_reset_refresh(nua_dialog_usage_t *du) du->du_refresh = 0; } -/** @internal Refresh usage or shutdown usage if @a now is 0. */ +/** @internal Refresh usage. */ void nua_dialog_usage_refresh(nua_owner_t *owner, nua_dialog_state_t *ds, nua_dialog_usage_t *du, sip_time_t now) { - if (du) { - du->du_refresh = 0; - - if (now > 0) { - assert(du->du_class->usage_refresh); - du->du_class->usage_refresh(owner, ds, du, now); - } - else { - du->du_shutdown = 1; - assert(du->du_class->usage_shutdown); - du->du_class->usage_shutdown(owner, ds, du); - } - } + assert(du && du->du_class->usage_refresh); + du->du_class->usage_refresh(owner, ds, du, now); } /** Terminate all dialog usages gracefully. */ @@ -552,18 +543,18 @@ int nua_dialog_shutdown(nua_owner_t *owner, nua_dialog_state_t *ds) return 1; } -/** (Gracefully) terminate usage. +/** Shutdown (gracefully terminate) usage. * * @retval >0 shutdown done * @retval 0 shutdown in progress * @retval <0 try again later */ int nua_dialog_usage_shutdown(nua_owner_t *owner, - nua_dialog_state_t *ds, - nua_dialog_usage_t *du) + nua_dialog_state_t *ds, + nua_dialog_usage_t *du) { if (du) { - du->du_refresh = 0; + nua_dialog_usage_reset_refresh(du); du->du_shutdown = 1; assert(du->du_class->usage_shutdown); return du->du_class->usage_shutdown(owner, ds, du); @@ -571,3 +562,41 @@ int nua_dialog_usage_shutdown(nua_owner_t *owner, else return 200; } + +/** Repeat shutdown of all usages. + * + * @note Caller must have a reference to nh + */ +int nua_dialog_repeat_shutdown(nua_owner_t *owner, nua_dialog_state_t *ds) +{ + nua_dialog_usage_t *du; + nua_server_request_t *sr, *sr_next; + + for (sr = ds->ds_sr; sr; sr = sr_next) { + sr_next = sr->sr_next; + + if (nua_server_request_is_pending(sr)) { + SR_STATUS1(sr, SIP_410_GONE); /* 410 terminates dialog */ + nua_server_respond(sr, NULL); + nua_server_report(sr); + } + } + + for (du = ds->ds_usage; du ;) { + nua_dialog_usage_t *du_next = du->du_next; + + nua_dialog_usage_shutdown(owner, ds, du); + + if (du_next == NULL) + break; + + for (du = ds->ds_usage; du; du = du->du_next) { + if (du == du_next) + break; + else if (!du->du_shutdown) + break; + } + } + + return ds->ds_usage != NULL; +} diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.h b/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.h index fc667f864b..77d264d896 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.h +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.h @@ -35,10 +35,6 @@ * @date Created: Wed Mar 8 11:38:18 EET 2006 ppessi */ -typedef struct nua_dialog_state nua_dialog_state_t; -typedef struct nua_dialog_usage nua_dialog_usage_t; -typedef struct nua_remote_s nua_remote_t; - #ifndef NUA_OWNER_T #define NUA_OWNER_T struct nua_owner_s #endif @@ -48,10 +44,13 @@ typedef NUA_OWNER_T nua_owner_t; #include #endif -typedef su_msg_r nua_saved_signal_t; - +typedef struct nua_dialog_state nua_dialog_state_t; +typedef struct nua_dialog_usage nua_dialog_usage_t; typedef struct nua_server_request nua_server_request_t; typedef struct nua_client_request nua_client_request_t; +typedef struct nua_dialog_peer_info nua_dialog_peer_info_t; + +typedef su_msg_r nua_saved_signal_t; typedef struct { sip_method_t sm_method; @@ -272,6 +271,8 @@ struct nua_client_request nta_outgoing_t *cr_orq; + su_timer_t *cr_timer; /**< Expires or retry timer */ + /*nua_event_t*/ int cr_event; /**< Request event */ sip_method_t cr_method; char const *cr_method_name; @@ -303,8 +304,10 @@ struct nua_client_request unsigned cr_dialog:1; /**< Request can initiate dialog */ /* Current state */ + unsigned cr_waiting:1; /**< Request is waiting */ unsigned cr_challenged:1; /**< Request was challenged */ unsigned cr_wait_for_cred:1; /**< Request is pending authentication */ + unsigned cr_wait_for_timer:1; /**< Request is waiting for a timer to expire */ unsigned cr_restarting:1; /**< Request is being restarted */ unsigned cr_reporting:1; /**< Reporting in progress */ unsigned cr_terminating:1; /**< Request terminates the usage */ @@ -316,6 +319,9 @@ struct nua_client_request struct nua_dialog_state { + /** Dialog owner */ + nua_owner_t *ds_owner; + /** Dialog usages. */ nua_dialog_usage_t *ds_usage; @@ -346,12 +352,13 @@ struct nua_dialog_state sip_from_t const *ds_local; /**< Local address */ sip_to_t const *ds_remote; /**< Remote address */ nta_leg_t *ds_leg; + sip_contact_t *ds_ltarget; /**< Local target */ char const *ds_remote_tag; /**< Remote tag (if any). * Should be non-NULL * if dialog is established. */ - - struct nua_remote_s { + + struct nua_dialog_peer_info { sip_allow_t *nr_allow; sip_accept_t *nr_accept; sip_require_t *nr_require; @@ -360,10 +367,6 @@ struct nua_dialog_state } ds_remote_ua[1]; }; -typedef void nh_pending_f(nua_owner_t *, - nua_dialog_usage_t *du, - sip_time_t now); - /** Virtual function pointer table for dialog usage. */ typedef struct { unsigned usage_size, usage_class_size; @@ -388,6 +391,7 @@ typedef struct { struct nua_dialog_usage { nua_dialog_usage_t *du_next; nua_usage_class const *du_class; + nua_dialog_state_t *du_dialog; nua_client_request_t *du_cr; /**< Client request bound with usage */ unsigned du_ready:1; /**< Established usage */ @@ -440,13 +444,16 @@ void nua_dialog_deinit(nua_owner_t *own, int nua_dialog_shutdown(nua_owner_t *owner, nua_dialog_state_t *ds); +int nua_dialog_repeat_shutdown(nua_owner_t *owner, + nua_dialog_state_t *ds); + void nua_dialog_usage_set_refresh(nua_dialog_usage_t *du, unsigned delta); -void nua_dialog_usage_refresh_range(nua_dialog_usage_t *du, - unsigned min, unsigned max); +void nua_dialog_usage_set_refresh_range(nua_dialog_usage_t *du, + unsigned min, unsigned max); -void nua_dialog_usage_refresh_at(nua_dialog_usage_t *du, - sip_time_t target); +void nua_dialog_usage_set_refresh_at(nua_dialog_usage_t *du, + sip_time_t target); void nua_dialog_usage_reset_refresh(nua_dialog_usage_t *du); diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_notifier.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_notifier.c index fcb37f8c68..be3376ebb4 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_notifier.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_notifier.c @@ -696,7 +696,7 @@ static int nua_notify_client_report(nua_client_request_t *cr, nua_client_resend_request(cr, 0); } else if (nu->nu_expires) { - nua_dialog_usage_refresh_at(du, nu->nu_expires); + nua_dialog_usage_set_refresh_at(du, nu->nu_expires); } } diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_params.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_params.c index 92f1d00915..25458c799b 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_params.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_params.c @@ -1158,7 +1158,7 @@ int nua_handle_save_tags(nua_handle_t *nh, tagi_t *tags) url = (url_string_t *)t->t_value; } /* NUTAG_SIPS_URL_REF(url) */ - else if (t->t_tag == nutag_url) { + else if (t->t_tag == nutag_sips_url) { url = (url_string_t *)t->t_value; } } diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_register.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_register.c index 9c31ba6ec6..d16b4e378c 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_register.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_register.c @@ -973,7 +973,7 @@ static int nua_register_client_response(nua_client_request_t *cr, nua_registration_set_ready(nr, 1); } else if (du) { - nua_dialog_usage_set_refresh(du, 0); + nua_dialog_usage_reset_refresh(du); su_free(nh->nh_home, nr->nr_route); nr->nr_route = NULL; @@ -1004,14 +1004,31 @@ void nua_register_connection_closed(tp_stack_t *sip_stack, msg_t *msg, int error) { - if (tport_release(nr->nr_tport, nr->nr_error_report_id, NULL, NULL, nr, 0) < 0) - SU_DEBUG_1(("nua_register: tport_release() failed\n")); + tp_name_t const *tpn; + int pending = nr->nr_error_report_id; + assert(tport == nr->nr_tport); + + if (!nr->nr_tport) + return; + + if (tport_release(nr->nr_tport, pending, NULL, NULL, nr, 0) < 0) + SU_DEBUG_1(("nua_register: tport_release() failed\n")); nr->nr_error_report_id = 0; + + tpn = tport_name(nr->nr_tport); + + SU_DEBUG_5(("nua_register(%p): tport to %s/%s:%s%s%s closed %s\n", + nua_dialog_usage_public(nr)->du_dialog->ds_owner, + tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port, + tpn->tpn_comp ? ";comp=" : "", + tpn->tpn_comp ? tpn->tpn_comp : "", + error != 0 ? su_strerror(error) : "")); + tport_unref(nr->nr_tport), nr->nr_tport = NULL; /* Schedule re-REGISTER immediately */ - nua_dialog_usage_refresh_at(nua_dialog_usage_public(nr), sip_now()); + nua_dialog_usage_set_refresh_range(nua_dialog_usage_public(nr), 0, 0); } diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_session.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_session.c index 6508e72d1f..a68d35a3e8 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_session.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_session.c @@ -173,6 +173,8 @@ typedef struct nua_session_usage char const *ss_oa_recv, *ss_oa_sent; } nua_session_usage_t; +static char const Offer[] = "offer", Answer[] = "answer"; + static char const *nua_session_usage_name(nua_dialog_usage_t const *du); static int nua_session_usage_add(nua_handle_t *nh, nua_dialog_state_t *ds, @@ -579,7 +581,7 @@ static int nua_invite_client_init(nua_client_request_t *cr, cr->cr_usage = du = nua_dialog_usage_for_session(nh->nh_ds); /* Errors returned by nua_invite_client_init() - are neutral to session state */ + do not change the session state */ cr->cr_neutral = 1; if (nh_is_special(nh) || @@ -640,7 +642,7 @@ static int nua_invite_client_request(nua_client_request_t *cr, invite_timeout = UINT_MAX; /* Send CANCEL if we don't get response within timeout*/ /* nua_dialog_usage_set_expires(du, invite_timeout); Xyzzy */ - nua_dialog_usage_set_refresh(du, 0); + nua_dialog_usage_reset_refresh(du); /* Add session timer headers */ if (session_timer_is_supported(ss->ss_timer)) @@ -689,10 +691,10 @@ static int nua_invite_client_request(nua_client_request_t *cr, NTATAG_REL100(ss->ss_100rel), TAG_NEXT(tags)); if (retval == 0) { - cr->cr_offer_sent = offer_sent; - ss->ss_oa_sent = offer_sent ? "offer" : NULL; + if ((cr->cr_offer_sent = offer_sent)) + ss->ss_oa_sent = Offer; - if (!cr->cr_restarting) + if (!cr->cr_restarting) /* Restart logic calls nua_invite_client_report */ signal_call_state_change(nh, ss, 0, "INVITE sent", nua_callstate_calling); } @@ -807,9 +809,9 @@ static int nua_session_client_response(nua_client_request_t *cr, sdp = NULL; } else if (cr->cr_offer_sent) { - /* case 1: incoming answer */ + /* case 1: answer to our offer */ cr->cr_answer_recv = status; - received = "answer"; + received = Answer; if (nh->nh_soa == NULL) LOG5("got SDP"); @@ -836,9 +838,9 @@ static int nua_session_client_response(nua_client_request_t *cr, sdp = NULL; } else { - /* case 2: answer to our offer */ + /* case 2: new offer */ cr->cr_offer_recv = 1, cr->cr_answer_sent = 0; - received = "offer"; + received = Offer; if (nh->nh_soa && soa_set_remote_sdp(nh->nh_soa, NULL, sdp, len) < 0) { LOG3("error parsing SDP"); @@ -868,12 +870,13 @@ static int nua_invite_client_report(nua_client_request_t *cr, tagi_t const *tags) { nua_handle_t *nh = cr->cr_owner; + nua_dialog_state_t *ds = nh->nh_ds; nua_dialog_usage_t *du = cr->cr_usage; nua_session_usage_t *ss = nua_dialog_usage_private(du); unsigned next_state; int error; - nh_referral_respond(nh, status, phrase); + nh_referral_respond(nh, status, phrase); /* XXX - restarting after 401/407 */ nua_stack_event(nh->nh_nua, nh, nta_outgoing_getresponse(orq), @@ -881,7 +884,8 @@ static int nua_invite_client_report(nua_client_request_t *cr, status, phrase, tags); - if (orq != cr->cr_orq && status != 100) + if (cr->cr_waiting) + /* Do not report call state change if waiting for restart */ return 1; if (ss == NULL) { @@ -897,7 +901,10 @@ static int nua_invite_client_report(nua_client_request_t *cr, return 1; } - if (status == 100) { + if (orq != cr->cr_orq && cr->cr_orq) { /* Being restarted */ + next_state = nua_callstate_calling; + } + else if (status == 100) { next_state = nua_callstate_calling; } else if (status < 300 && cr->cr_graceful) { @@ -953,8 +960,16 @@ static int nua_invite_client_report(nua_client_request_t *cr, /* Auto-ACK response to re-INVITE unless auto_ack is set to 0 */ (ss->ss_state == nua_callstate_ready && !NH_PISSET(nh, auto_ack))) { + nua_client_request_t *cru; - if (nua_invite_client_ack(cr, NULL) > 0) + for (cru = ds->ds_cr; cru; cru = cru->cr_next) { + if (cr != cru && cru->cr_offer_sent && !cru->cr_answer_recv) + break; + } + + if (cru) + /* A final response to UPDATE or PRACK with answer on its way? */; + else if (nua_invite_client_ack(cr, NULL) > 0) next_state = nua_callstate_ready; else next_state = nua_callstate_terminating; @@ -1129,7 +1144,7 @@ int nua_invite_client_ack(nua_client_request_t *cr, tagi_t const *tags) else if (cr->cr_offer_recv && !cr->cr_answer_sent) { if (nh->nh_soa == NULL) { if (session_get_description(sip, NULL, NULL)) - cr->cr_answer_sent = 1, ss->ss_oa_sent = "answer"; + cr->cr_answer_sent = 1, ss->ss_oa_sent = Answer; } else if (soa_generate_answer(nh->nh_soa, NULL) < 0 || session_include_description(nh->nh_soa, 1, msg, sip) < 0) { @@ -1138,23 +1153,33 @@ int nua_invite_client_ack(nua_client_request_t *cr, tagi_t const *tags) /* reason = soa_error_as_sip_reason(nh->nh_soa); */ } else { - cr->cr_answer_sent = 1, ss->ss_oa_sent = "answer"; + cr->cr_answer_sent = 1, ss->ss_oa_sent = Answer; } } if (ss == NULL || ss->ss_state >= nua_callstate_ready || reason) ; - else if (nh->nh_soa - ? soa_is_complete(nh->nh_soa) - : !(cr->cr_offer_sent && !cr->cr_answer_recv)) { - /* signal that O/A round(s) is (are) complete */ - if (nh->nh_soa) + else if (nh->nh_soa && soa_is_complete(nh->nh_soa)) { + /* signal SOA that O/A round(s) is (are) complete */ soa_activate(nh->nh_soa, NULL); } + else if (nh->nh_soa == NULL && !(cr->cr_offer_sent && !cr->cr_answer_recv)) { + ; + } else { - /* No SDP answer -> terminate call */ - status = 988, phrase = "Incomplete offer/answer"; - reason = "SIP;cause=488;text=\"Incomplete offer/answer\""; + nua_client_request_t *cru; + + /* Final response to UPDATE or PRACK may be on its way ... */ + for (cru = ds->ds_cr; cru; cru = cru->cr_next) { + if (cr != cru && cru->cr_offer_sent && !cru->cr_answer_recv) + break; + } + + if (cru == NULL) { + /* No SDP answer -> terminate call */ + status = 988, phrase = "Incomplete offer/answer"; + reason = "SIP;cause=488;text=\"Incomplete offer/answer\""; + } } if ((ack = nta_outgoing_mcreate(nh->nh_nua->nua_nta, NULL, NULL, NULL, @@ -1561,15 +1586,13 @@ static int nua_prack_client_request(nua_client_request_t *cr, cr->cr_offer_sent = offer_sent; cr->cr_answer_sent = answer_sent; - if (!cr->cr_restarting) { - if (offer_sent) - ss->ss_oa_sent = "offer"; - else if (answer_sent) - ss->ss_oa_sent = "answer"; + if (offer_sent) + ss->ss_oa_sent = Offer; + else if (answer_sent) + ss->ss_oa_sent = Answer; - if (!ss->ss_reporting) - signal_call_state_change(nh, ss, status, phrase, ss->ss_state); - } + if (!cr->cr_restarting) /* Restart logic calls nua_prack_client_report */ + signal_call_state_change(nh, ss, status, phrase, ss->ss_state); } return retval; @@ -1591,7 +1614,8 @@ static int nua_prack_client_report(nua_client_request_t *cr, tagi_t const *tags) { nua_handle_t *nh = cr->cr_owner; - nua_session_usage_t *ss = nua_dialog_usage_private(cr->cr_usage); + nua_dialog_usage_t *du = cr->cr_usage; + nua_session_usage_t *ss = nua_dialog_usage_private(du); nua_stack_event(nh->nh_nua, nh, nta_outgoing_getresponse(orq), @@ -1599,11 +1623,34 @@ static int nua_prack_client_report(nua_client_request_t *cr, status, phrase, tags); - if (!ss || orq != cr->cr_orq || cr->cr_terminated || cr->cr_graceful) + if (!ss || cr->cr_terminated || cr->cr_graceful) return 1; - if (cr->cr_offer_sent) - signal_call_state_change(nh, ss, status, phrase, ss->ss_state); + if (cr->cr_waiting) + /* Do not report call state change if restarting later */ + return 1; + + if (cr->cr_offer_sent || cr->cr_answer_sent) { + unsigned next_state = ss->ss_state; + + if (status < 200) + ; + else if (du->du_cr && du->du_cr->cr_orq && du->du_cr->cr_status >= 200) { + /* There is an un-ACK-ed INVITE there */ + assert(du->du_cr->cr_method == sip_method_invite); + if (NH_PGET(nh, auto_ack) || + /* Auto-ACK response to re-INVITE unless auto_ack is set to 0 */ + (ss->ss_state == nua_callstate_ready && !NH_PISSET(nh, auto_ack))) { + /* No UPDATE with offer/answer if PRACK with offer/answer was ongoing! */ + if (nua_invite_client_ack(du->du_cr, NULL) > 0) + next_state = nua_callstate_ready; + else + next_state = nua_callstate_terminating; + } + } + + signal_call_state_change(nh, ss, status, phrase, next_state); + } if (ss->ss_update_needed && 200 <= status && status < 300 && !SIP_IS_ALLOWED(NH_PGET(nh, appl_method), sip_method_update)) @@ -1904,7 +1951,7 @@ int nua_invite_server_preprocess(nua_server_request_t *sr) ss = nua_dialog_usage_private(sr->sr_usage); if (sr->sr_offer_recv) - ss->ss_oa_recv = "offer"; + ss->ss_oa_recv = Offer; ss->ss_100rel = NH_PGET(nh, early_media); ss->ss_precondition = sip_has_feature(request->sip_require, "precondition"); @@ -2049,9 +2096,9 @@ int nua_invite_server_respond(nua_server_request_t *sr, tagi_t const *tags) if (nh->nh_soa && session_include_description(nh->nh_soa, 1, msg, sip) < 0) SR_STATUS1(sr, SIP_500_INTERNAL_SERVER_ERROR); else if (offer) - sr->sr_offer_sent = 1 + reliable, ss->ss_oa_sent = "offer"; + sr->sr_offer_sent = 1 + reliable, ss->ss_oa_sent = Offer; else if (answer) - sr->sr_answer_sent = 1 + reliable, ss->ss_oa_sent = "answer"; + sr->sr_answer_sent = 1 + reliable, ss->ss_oa_sent = Answer; } if (reliable && sr->sr_status < 200) { @@ -2236,7 +2283,7 @@ int process_ack(nua_server_request_t *sr, int error; if (session_get_description(sip, &sdp, &len)) - recv = "answer"; + recv = Answer; if (recv) { assert(ss->ss_oa_recv == NULL); @@ -2474,14 +2521,14 @@ int nua_prack_server_init(nua_server_request_t *sr) /* XXX - check for overlap? */ if (sri->sr_offer_sent) - sr->sr_answer_recv = 1, ss->ss_oa_recv = "answer"; + sr->sr_answer_recv = 1, ss->ss_oa_recv = Answer; else - sr->sr_offer_recv = 1, ss->ss_oa_recv = "offer"; + sr->sr_offer_recv = 1, ss->ss_oa_recv = Offer; if (nh->nh_soa && soa_set_remote_sdp(nh->nh_soa, NULL, sr->sr_sdp, sr->sr_sdp_len) < 0) { SU_DEBUG_5(("nua(%p): %s server: error parsing %s\n", (void *)nh, - "PRACK", "offer")); + "PRACK", Offer)); return sr->sr_status = soa_error_as_sip_response(nh->nh_soa, &sr->sr_phrase); } @@ -2504,21 +2551,21 @@ int nua_prack_server_respond(nua_server_request_t *sr, tagi_t const *tags) if (nh->nh_soa == NULL) { if (sr->sr_offer_recv && session_get_description(sip, NULL, NULL)) - sr->sr_answer_sent = 1, ss->ss_oa_sent = "answer"; + sr->sr_answer_sent = 1, ss->ss_oa_sent = Answer; } else if ((sr->sr_offer_recv && soa_generate_answer(nh->nh_soa, NULL) < 0) || (sr->sr_answer_recv && soa_process_answer(nh->nh_soa, NULL) < 0)) { SU_DEBUG_5(("nua(%p): %s server: %s %s\n", (void *)nh, "PRACK", "error processing", - sr->sr_offer_recv ? "offer" : "answer")); + sr->sr_offer_recv ? Offer : Answer)); sr->sr_status = soa_error_as_sip_response(nh->nh_soa, &sr->sr_phrase); } else if (sr->sr_offer_recv) { if (session_include_description(nh->nh_soa, 1, msg, sip) < 0) sr_status(sr, SIP_500_INTERNAL_SERVER_ERROR); else - sr->sr_answer_sent = 1, ss->ss_oa_sent = "answer"; + sr->sr_answer_sent = 1, ss->ss_oa_sent = Answer; } } @@ -2976,20 +3023,18 @@ static int nua_update_client_request(nua_client_request_t *cr, retval = nua_base_client_request(cr, msg, sip, NULL); if (retval == 0) { + enum nua_callstate state = ss->ss_state; cr->cr_offer_sent = offer_sent; ss->ss_update_needed = 0; - if (!cr->cr_restarting) { - enum nua_callstate state = ss->ss_state; + if (state == nua_callstate_ready) + state = nua_callstate_calling; /* XXX */ - if (state == nua_callstate_ready) - state = nua_callstate_calling; - - if (offer_sent) - ss->ss_oa_sent = "offer"; + if (offer_sent) + ss->ss_oa_sent = Offer; + if (!cr->cr_restarting) /* Restart logic calls nua_update_client_report */ signal_call_state_change(nh, ss, 0, "UPDATE sent", state); - } } return retval; @@ -3057,6 +3102,7 @@ static int nua_update_client_report(nua_client_request_t *cr, nua_handle_t *nh = cr->cr_owner; nua_dialog_usage_t *du = cr->cr_usage; nua_session_usage_t *ss = nua_dialog_usage_private(du); + unsigned next_state = ss->ss_state; nua_stack_event(nh->nh_nua, nh, nta_outgoing_getresponse(orq), @@ -3064,11 +3110,32 @@ static int nua_update_client_report(nua_client_request_t *cr, status, phrase, tags); - if (!ss || orq != cr->cr_orq || - cr->cr_terminated || cr->cr_graceful || !cr->cr_offer_sent) + if (!ss || cr->cr_terminated || cr->cr_graceful) return 1; - signal_call_state_change(nh, ss, status, phrase, ss->ss_state); + if (cr->cr_waiting) + /* Do not report call state change if restarting later */ + return 1; + + if (cr->cr_offer_sent) { + if (status < 200) + ; + else if (du->du_cr && du->du_cr->cr_orq && du->du_cr->cr_status >= 200) { + /* There is an un-ACK-ed INVITE there */ + assert(du->du_cr->cr_method == sip_method_invite); + + if (NH_PGET(nh, auto_ack) || + /* Auto-ACK response to re-INVITE unless auto_ack is set to 0 */ + (ss->ss_state == nua_callstate_ready && !NH_PISSET(nh, auto_ack))) { + if (nua_invite_client_ack(du->du_cr, NULL) > 0) + next_state = nua_callstate_ready; + else + next_state = nua_callstate_terminating; + } + } + + signal_call_state_change(nh, ss, status, phrase, next_state); + } return 1; } @@ -3148,13 +3215,13 @@ int nua_update_server_init(nua_server_request_t *sr) if (nh->nh_soa && soa_set_remote_sdp(nh->nh_soa, NULL, sr->sr_sdp, sr->sr_sdp_len) < 0) { SU_DEBUG_5(("nua(%p): %s server: error parsing %s\n", (void *)nh, - "UPDATE", "offer")); + "UPDATE", Offer)); return sr->sr_status = soa_error_as_sip_response(nh->nh_soa, &sr->sr_phrase); } sr->sr_offer_recv = 1; - ss->ss_oa_recv = "offer"; + ss->ss_oa_recv = Offer; } return 0; @@ -3172,11 +3239,11 @@ int nua_update_server_respond(nua_server_request_t *sr, tagi_t const *tags) if (200 <= sr->sr_status && sr->sr_status < 300 && sr->sr_sdp) { if (nh->nh_soa == NULL) { - sr->sr_answer_sent = 1, ss->ss_oa_sent = "answer"; + sr->sr_answer_sent = 1, ss->ss_oa_sent = Answer; } else if (soa_generate_answer(nh->nh_soa, NULL) < 0) { SU_DEBUG_5(("nua(%p): %s server: %s %s\n", - (void *)nh, "UPDATE", "error processing", "offer")); + (void *)nh, "UPDATE", "error processing", Offer)); sr->sr_status = soa_error_as_sip_response(nh->nh_soa, &sr->sr_phrase); } else if (soa_activate(nh->nh_soa, NULL) < 0) { @@ -3188,7 +3255,7 @@ int nua_update_server_respond(nua_server_request_t *sr, tagi_t const *tags) sr_status(sr, SIP_500_INTERNAL_SERVER_ERROR); } else { - sr->sr_answer_sent = 1, ss->ss_oa_sent = "answer"; + sr->sr_answer_sent = 1, ss->ss_oa_sent = Answer; } } @@ -3639,14 +3706,17 @@ static void signal_call_state_change(nua_handle_t *nh, oa_recv = ss->ss_oa_recv, ss->ss_oa_recv = NULL; oa_sent = ss->ss_oa_sent, ss->ss_oa_sent = NULL; + assert(oa_sent == Offer || oa_sent == Answer || oa_sent == NULL); + assert(oa_recv == Offer || oa_recv == Answer || oa_recv == NULL); + if (oa_recv) { - offer_recv = strcasecmp(oa_recv, "offer") == 0; - answer_recv = strcasecmp(oa_recv, "answer") == 0; + offer_recv = oa_recv == Offer; + answer_recv = oa_recv == Answer; } if (oa_sent) { - offer_sent = strcasecmp(oa_sent, "offer") == 0; - answer_sent = strcasecmp(oa_sent, "answer") == 0; + offer_sent = oa_sent == Offer; + answer_sent = oa_sent == Answer; } } @@ -4061,7 +4131,7 @@ session_timer_set(nua_session_usage_t *ss) if (t->interval >= 90) low -=5, high += 5; - nua_dialog_usage_refresh_range(du, low, high); + nua_dialog_usage_set_refresh_range(du, low, high); t->timer_set = 1; } else if (t->refresher == nua_remote_refresher) { @@ -4074,7 +4144,7 @@ session_timer_set(nua_session_usage_t *ss) interval -= 32 > interval / 6 ? interval / 3 : 32 + interval / 3; - nua_dialog_usage_refresh_range(du, interval, interval); + nua_dialog_usage_set_refresh_range(du, interval, interval); t->timer_set = 1; } else { diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_stack.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_stack.c index 123187bf2d..498b5e011b 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_stack.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_stack.c @@ -46,6 +46,7 @@ #define SU_ROOT_MAGIC_T struct nua_s #define SU_MSG_ARG_T struct event_s +#define SU_TIMER_ARG_T struct nua_client_request #define NUA_SAVED_EVENT_T su_msg_t * @@ -271,9 +272,7 @@ int nua_stack_event(nua_t *nua, nua_handle_t *nh, msg_t *msg, if ((event > nua_r_authenticate && event <= nua_r_ack) || event < nua_i_error || (nh && !nh->nh_valid) - /* disable hiding all events after shutdown so that we can get state callbacks to properly tear down our calls */ - /* || (nua->nua_shutdown && event != nua_r_shutdown) */ - ) { + || (nua->nua_shutdown && event != nua_r_shutdown)) { if (msg) msg_destroy(msg); return event; @@ -486,7 +485,8 @@ void nua_stack_timer(nua_t *nua, su_timer_t *t, su_timer_arg_t *a) su_timer_set(t, nua_stack_timer, a); if (nua->nua_shutdown) { - nua_stack_shutdown(nua); + nua_stack_shutdown(nua); + return; } for (nh = nua->nua_handles; nh; nh = nh_next) { @@ -605,21 +605,10 @@ void nua_stack_shutdown(nua_t *nua) for (nh = nua->nua_handles; nh; nh = nh_next) { nua_dialog_state_t *ds = nh->nh_ds; - nua_server_request_t *sr, *sr_next; nh_next = nh->nh_next; - for (sr = ds->ds_sr; sr; sr = sr_next) { - sr_next = sr->sr_next; - - if (nua_server_request_is_pending(sr)) { - SR_STATUS1(sr, SIP_410_GONE); /* 410 terminates dialog */ - nua_server_respond(sr, NULL); - nua_server_report(sr); - } - } - - busy += nh_call_pending(nh, 0); + busy += nua_dialog_repeat_shutdown(nh, ds); if (nh->nh_soa) { soa_destroy(nh->nh_soa), nh->nh_soa = NULL; @@ -992,6 +981,7 @@ nua_stack_authenticate(nua_t *nua, nua_handle_t *nh, nua_event_t e, if (status > 0) { if (cr && cr->cr_wait_for_cred) { + cr->cr_waiting = cr->cr_wait_for_cred = 0; nua_client_restart_request(cr, cr->cr_terminating, tags); } else { @@ -1001,8 +991,8 @@ nua_stack_authenticate(nua_t *nua, nua_handle_t *nh, nua_event_t e, } } else if (cr && cr->cr_wait_for_cred) { - cr->cr_wait_for_cred = 0; - + cr->cr_waiting = cr->cr_wait_for_cred = 0; + if (status < 0) nua_client_response(cr, 900, "Cannot add credentials", NULL); else @@ -1439,9 +1429,10 @@ int nua_server_trespond(nua_server_request_t *sr, int nua_server_respond(nua_server_request_t *sr, tagi_t const *tags) { nua_handle_t *nh = sr->sr_owner; + nua_dialog_state_t *ds = nh->nh_ds; sip_method_t method = sr->sr_method; struct { msg_t *msg; sip_t *sip; } next = { NULL, NULL }; - int retval; + int retval, user_contact = 1; #if HAVE_OPEN_C /* Nice. And old arm symbian compiler; see below. */ tagi_t next_tags[2]; @@ -1496,15 +1487,26 @@ int nua_server_respond(nua_server_request_t *sr, tagi_t const *tags) sip_add_dup(msg, sip, (void *)NH_PGET(nh, allow_events)) < 0) ; else if (!sip->sip_contact && sr->sr_status < 300 && sr->sr_add_contact && - nua_registration_add_contact_to_response(nh, msg, sip, NULL, m) < 0) + (user_contact = 0, + ds->ds_ltarget + ? sip_add_dup(msg, sip, (sip_header_t *)ds->ds_ltarget) + : nua_registration_add_contact_to_response(nh, msg, sip, NULL, m)) + < 0) ; else { int term; - + sip_contact_t *ltarget = NULL; + term = sip_response_terminates_dialog(sr->sr_status, sr->sr_method, NULL); sr->sr_terminating = (term < 0) ? -1 : (term > 0 || sr->sr_terminating); + if (sr->sr_target_refresh && sr->sr_status < 300 && !sr->sr_terminating && + user_contact && sip->sip_contact) { + /* Save Contact given by application */ + ltarget = sip_contact_dup(nh->nh_home, sip->sip_contact); + } + retval = sr->sr_methods->sm_respond(sr, next_tags); if (sr->sr_status < 200) @@ -1514,6 +1516,16 @@ int nua_server_respond(nua_server_request_t *sr, tagi_t const *tags) assert(sr->sr_status >= 200 || sr->sr_response.msg); + if (ltarget) { + if (sr->sr_status < 300) { + nua_dialog_state_t *ds = nh->nh_ds; + msg_header_free(nh->nh_home, (msg_header_t *)ds->ds_ltarget); + ds->ds_ltarget = ltarget; + } + else + msg_header_free(nh->nh_home, (msg_header_t *)ltarget); + } + return retval; } @@ -1714,6 +1726,9 @@ int nua_base_server_report(nua_server_request_t *sr, tagi_t const *tags) static int nua_client_request_try(nua_client_request_t *cr); static int nua_client_request_sendmsg(nua_client_request_t *cr, msg_t *msg, sip_t *sip); +static void nua_client_restart_after(su_root_magic_t *magic, + su_timer_t *timer, + nua_client_request_t *cr); /**Create a client request. * @@ -1860,6 +1875,9 @@ void nua_client_request_destroy(nua_client_request_t *cr) cr->cr_orq = NULL; + if (cr->cr_timer) + su_timer_destroy(cr->cr_timer), cr->cr_timer = NULL; + if (cr->cr_target) su_free(nh->nh_home, cr->cr_target); @@ -2255,9 +2273,20 @@ int nua_client_request_sendmsg(nua_client_request_t *cr, msg_t *msg, sip_t *sip) * registrar is also added to the request message. */ if (cr->cr_method != sip_method_register) { + if (cr->cr_contactize && cr->cr_has_contact) { + sip_contact_t *ltarget = sip_contact_dup(nh->nh_home, sip->sip_contact); + if (ds->ds_ltarget) + msg_header_free(nh->nh_home, (msg_header_t *)ds->ds_ltarget); + ds->ds_ltarget = ltarget; + } + + if (ds->ds_ltarget && !cr->cr_has_contact) + sip_add_dup(msg, sip, (sip_header_t *)ds->ds_ltarget); + if (nua_registration_add_contact_to_request(nh, msg, sip, cr->cr_contactize && - !cr->cr_has_contact, + !cr->cr_has_contact && + !ds->ds_ltarget, !ds->ds_route) < 0) return -1; } @@ -2443,7 +2472,7 @@ int nua_client_response(nua_client_request_t *cr, /** Check if request should be restarted. * - * @retval 1 if restarted or waring for restart + * @retval 1 if restarted or waiting for restart * @retval 0 otherwise */ int nua_client_check_restart(nua_client_request_t *cr, @@ -2470,8 +2499,7 @@ int nua_base_client_check_restart(nua_client_request_t *cr, sip_t const *sip) { nua_handle_t *nh = cr->cr_owner; - - /* XXX - handle Retry-After */ + nta_outgoing_t *orq; if (status == 302 || status == 305) { sip_route_t r[1]; @@ -2520,7 +2548,6 @@ int nua_base_client_check_restart(nua_client_request_t *cr, if ((status == 401 && sip->sip_www_authenticate) || (status == 407 && sip->sip_proxy_authenticate)) { int server = 0, proxy = 0; - nta_outgoing_t *orq; if (sip->sip_www_authenticate) server = auc_challenge(&nh->nh_auth, nh->nh_home, @@ -2544,7 +2571,8 @@ int nua_base_client_check_restart(nua_client_request_t *cr, return nua_client_restart(cr, 100, "Request Authorized by Cache"); orq = cr->cr_orq, cr->cr_orq = NULL; - cr->cr_wait_for_cred = 1; + + cr->cr_waiting = cr->cr_wait_for_cred = 1; nua_client_report(cr, status, phrase, NULL, orq, NULL); nta_outgoing_destroy(orq); @@ -2552,9 +2580,44 @@ int nua_base_client_check_restart(nua_client_request_t *cr, } } + if (500 <= status && status < 600 && + sip->sip_retry_after && + sip->sip_retry_after->af_delta < 32) { + char phrase[18]; /* Retry-After: XXXX\0 */ + + if (cr->cr_timer == NULL) + cr->cr_timer = su_timer_create(su_root_task(nh->nh_nua->nua_root), 0); + + if (su_timer_set_interval(cr->cr_timer, nua_client_restart_after, cr, + sip->sip_retry_after->af_delta * 1000) < 0) + return 0; /* Too bad */ + + snprintf(phrase, sizeof phrase, "Retry After %u", + (unsigned)sip->sip_retry_after->af_delta); + + orq = cr->cr_orq, cr->cr_orq = NULL; + cr->cr_waiting = cr->cr_wait_for_timer = 1; + nua_client_report(cr, 100, phrase, NULL, orq, NULL); + nta_outgoing_destroy(orq); + return 1; + } + return 0; /* This was a final response that cannot be restarted. */ } +/** Request restarted by timer */ +static +void nua_client_restart_after(su_root_magic_t *magic, + su_timer_t *timer, + nua_client_request_t *cr) +{ + if (!cr->cr_wait_for_timer) + return; + + cr->cr_waiting = cr->cr_wait_for_timer = 0; + nua_client_restart_request(cr, cr->cr_terminating, NULL); +} + /** Restart request. * * @retval 1 if restarted @@ -2587,7 +2650,6 @@ int nua_client_restart(nua_client_request_t *cr, msg_destroy(msg); } - if (error) { cr->cr_graceful = graceful; cr->cr_terminated = terminated; diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_subnotref.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_subnotref.c index 9855fb7913..024df1cb6d 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_subnotref.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_subnotref.c @@ -389,7 +389,7 @@ static int nua_subscribe_client_response(nua_client_request_t *cr, if (eu->eu_substate == nua_substate_terminated) eu->eu_substate = nua_substate_embryonic; - nua_dialog_usage_refresh_range(du, delta, delta); + nua_dialog_usage_set_refresh_range(du, delta, delta); } else { eu->eu_substate = nua_substate_terminated; @@ -635,6 +635,7 @@ int nua_notify_server_report(nua_server_request_t *sr, tagi_t const *tags) sip_t const *sip = sr->sr_request.sip; enum nua_substate substate = nua_substate_terminated; sip_time_t delta = SIP_TIME_MAX; + sip_event_t const *o = sip->sip_event; int retry = -1; int retval; @@ -670,6 +671,7 @@ int nua_notify_server_report(nua_server_request_t *sr, tagi_t const *tags) retval = nua_base_server_treport(sr, /* can destroy sr */ NUTAG_SUBSTATE(substate), + SIPTAG_EVENT(o), TAG_NEXT(tags)); if (retval != 1 || du == NULL) @@ -681,7 +683,7 @@ int nua_notify_server_report(nua_server_request_t *sr, tagi_t const *tags) else if (retry >= 0) { /* Try to subscribe again */ /* XXX - this needs through testing */ nua_dialog_remove(nh, nh->nh_ds, du); /* tear down */ - nua_dialog_usage_refresh_range(du, retry, retry + 5); + nua_dialog_usage_set_refresh_range(du, retry, retry + 5); } else { nua_dialog_usage_set_refresh(du, delta); diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c index 592124b09b..019a600c4b 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c @@ -196,7 +196,7 @@ int test_180rel(struct context *ctx) ei = event_by_type(e->next, nua_r_invite); ep = event_by_type(e->next, nua_r_prack); if (!ep) { - run_a_until(ctx, -1, until_final_response); + run_a_until(ctx, -1, save_until_final_response); ep = event_by_type(e->next, nua_r_prack); } @@ -332,10 +332,9 @@ int test_prack_auth(struct context *ctx) struct event *e, *ep, *ei; sip_t *sip; sip_proxy_authenticate_t *au; - char const *md5 = NULL, *md5sess = NULL; if (print_headings) - printf("TEST NUA-10.1.3: Call with 100rel and 180\n"); + printf("TEST NUA-10.1.3: Call with 100rel, PRACK is challenged\n"); /* Test for authentication during 100rel @@ -396,10 +395,6 @@ int test_prack_auth(struct context *ctx) TEST(e->data->e_status, 407); TEST_1(sip = sip_object(e->data->e_msg)); TEST_1(au = sip->sip_proxy_authenticate); - TEST_1(auth_get_params(NULL, au->au_params, - "algorithm=md5", &md5, - "algorithm=md5-sess", &md5sess, - NULL) > 0); TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ @@ -415,24 +410,15 @@ int test_prack_auth(struct context *ctx) ei = event_by_type(e->next, nua_r_invite); ep = event_by_type(e->next, nua_r_prack); - if (!ep) { - run_a_until(ctx, -1, until_final_response); - ep = event_by_type(e->next, nua_r_prack); - } TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack); - if (e->data->e_status != 200 && md5 && !md5sess) { - if (e->data->e_status != 100) { - TEST(e->data->e_status, 407); - } - - TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); - TEST(callstate(e->data->e_tags), nua_callstate_proceeding); - TEST_1(!is_answer_recv(e->data->e_tags)); - TEST_1(!is_offer_sent(e->data->e_tags)); - - TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack); + if (e->data->e_status == 100 || e->data->e_status == 407) { + /* The final response to PRACK may be received after ACK is sent */ + if (!event_by_type(e->next, nua_r_prack)) + run_bc_until(ctx, -1, save_events, -1, save_until_final_response); + TEST_1(e = ep = event_by_type(e->next, nua_r_prack)); } + TEST_E(e->data->e_event, nua_r_prack); TEST(e->data->e_status, 200); TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite); @@ -441,7 +427,7 @@ int test_prack_auth(struct context *ctx) TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ TEST_1(!is_offer_answer_done(e->data->e_tags)); - TEST_1(!e->next || !ep->next || (ep->data->e_status != 200 && !e->next->next->next)); + TEST_1(!e->next || !ep->next); free_events_in_list(ctx, c->events); /* @@ -626,7 +612,7 @@ int test_183rel(struct context *ctx) ei = event_by_type(e->next, nua_r_invite); ep = event_by_type(e->next, nua_r_prack); if (!ep) { - run_a_until(ctx, -1, until_final_response); + run_a_until(ctx, -1, save_until_final_response); ep = event_by_type(e->next, nua_r_prack); } @@ -1024,7 +1010,7 @@ int test_preconditions(struct context *ctx) ei = event_by_type(e->next, nua_r_invite); ep = event_by_type(e->next, nua_r_prack); if (!ep) { - run_a_until(ctx, -1, until_final_response); + run_a_until(ctx, -1, save_until_final_response); ep = event_by_type(e->next, nua_r_prack); } @@ -1213,6 +1199,7 @@ int test_preconditions2(struct context *ctx) struct endpoint *a = &ctx->a, *b = &ctx->b; struct call *a_call = a->call, *b_call = b->call; struct event *e, *eu, *ei; + enum nua_callstate ustate, istate; if (print_headings) printf("TEST NUA-10.4.1: Call with preconditions and non-100rel 180\n"); @@ -1227,15 +1214,23 @@ int test_preconditions2(struct context *ctx) |-------PRACK------->| |<-------200---------| | | - |<-------180---------| - | | - |<------200 OK-------| + |-------UPDATE------>| + +------------------------+ + | |<-------200---------| | + | | | | + | |<-------180---------| | + | | | | + | |<------200 OK-------| | + +------------------------+ |--------ACK-------->| | | |<-------BYE---------| |-------200 OK-------| | | + Note that the boxed responses above can be re-ordered + (180 or 200 OK to INVITE is received before 200 OK to UPDATE). + ACK, however, is sent only after 200 OK to both UPDATE and INVITE. */ a_call->sdp = "m=audio 5008 RTP/AVP 8"; @@ -1280,6 +1275,7 @@ int test_preconditions2(struct context *ctx) TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); TEST(callstate(e->data->e_tags), nua_callstate_proceeding); TEST_1(is_answer_recv(e->data->e_tags)); + /* Offer is sent in PRACK */ TEST_1(is_offer_sent(e->data->e_tags)); TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack); @@ -1296,13 +1292,14 @@ int test_preconditions2(struct context *ctx) TEST_1(!is_answer_recv(e->data->e_tags)); TEST_1(is_offer_sent(e->data->e_tags)); + /* The final response to the UPDATE and INVITE can be received in any order */ eu = event_by_type(e->next, nua_r_update); ei = event_by_type(e->next, nua_r_invite); TEST_1(e = eu); TEST_E(e->data->e_event, nua_r_update); TEST(e->data->e_status, 200); TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); - TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + ustate = callstate(e->data->e_tags); TEST_1(is_answer_recv(e->data->e_tags)); TEST_1(!is_offer_sent(e->data->e_tags)); @@ -1312,14 +1309,25 @@ int test_preconditions2(struct context *ctx) TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ TEST_1(!is_offer_answer_done(e->data->e_tags)); - TEST_1(e = eu->next->next == ei ? ei->next->next : eu->next->next); + /* Final response to INVITE */ + TEST_1(ei = event_by_type(ei->next, nua_r_invite)); - TEST_E(e->data->e_event, nua_r_invite); TEST(e->data->e_status, 200); - - TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); - TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_E(ei->data->e_event, nua_r_invite); TEST(ei->data->e_status, 200); + TEST_1(e = ei->next); TEST_E(e->data->e_event, nua_i_state); + istate = callstate(e->data->e_tags); TEST_1(!is_offer_answer_done(e->data->e_tags)); - TEST_1(!e->next); + + if (eu == e->next) { + /* 200 OK to UPDATE is received after 200 OK to INVITE */ + TEST(ustate, nua_callstate_ready); + TEST(istate, nua_callstate_completing); + } + else { + /* 200 OK to UPDATE is received before 200 OK to INVITE */ + TEST(ustate, nua_callstate_proceeding); + TEST(istate, nua_callstate_ready); + } + free_events_in_list(ctx, a->events); /* @@ -1499,7 +1507,7 @@ int test_update_by_uas(struct context *ctx) if (print_headings) printf("TEST NUA-10.5.1: Call with dual UPDATE\n"); -/* Test for precondition: +/* Test for update by UAS. A B |-------INVITE------>| @@ -1510,9 +1518,11 @@ int test_update_by_uas(struct context *ctx) |<-------200---------| | | |-------UPDATE------>| - |<-------200---------| - | | - |<------UPDATE-------| + +------------------------+ + | |<-------200---------| | + | | | | + | |<------UPDATE-------| | + +------------------------+ |--------200-------->| | | |<-------180---------| @@ -1526,6 +1536,9 @@ int test_update_by_uas(struct context *ctx) |-------200 OK-------| | | + Note that the 200 OK to UPDATE from A and UPDATE from B may be re-ordered + In that case, A will respond with 500/Retry-After and B will retry UPDATE. + See do {} while () loop below. */ a_call->sdp = "m=audio 5008 RTP/AVP 8"; @@ -1612,7 +1625,7 @@ int test_update_by_uas(struct context *ctx) ei = event_by_type(e->next, nua_r_invite); ep = event_by_type(e->next, nua_r_prack); if (!ep) { - run_a_until(ctx, -1, until_final_response); + run_a_until(ctx, -1, save_until_final_response); ep = event_by_type(e->next, nua_r_prack); } @@ -1672,17 +1685,24 @@ int test_update_by_uas(struct context *ctx) TEST_1(is_answer_sent(e->data->e_tags)); /* sent UPDATE */ - TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); - TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ - TEST_1(is_offer_sent(e->data->e_tags)); - TEST_1(!is_offer_recv(e->data->e_tags)); - TEST_1(!is_answer_sent(e->data->e_tags)); - TEST_1(!is_answer_recv(e->data->e_tags)); + do { + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(!is_offer_recv(e->data->e_tags)); + TEST_1(!is_answer_sent(e->data->e_tags)); + TEST_1(!is_answer_recv(e->data->e_tags)); - TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_update); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_update); + if (e->data->e_status == 100) { + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(sip->sip_status->st_status, 500); TEST_1(sip->sip_retry_after); + } + } while (e->data->e_status == 100); + TEST(e->data->e_status, 200); TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ - TEST_1(!is_offer_sent(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); /* XXX */ TEST_1(!is_offer_recv(e->data->e_tags)); TEST_1(!is_answer_sent(e->data->e_tags)); TEST_1(is_answer_recv(e->data->e_tags)); @@ -2060,7 +2080,7 @@ int test_180rel_cancel2(struct context *ctx) int test_100rel(struct context *ctx) { - int retval; + int retval = 0; retval = test_180rel(ctx); RETURN_ON_SINGLE_FAILURE(retval); retval = test_prack_auth(ctx); RETURN_ON_SINGLE_FAILURE(retval); diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c index e5b9eb18e3..836e35e92f 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c @@ -1356,14 +1356,262 @@ int accept_upgrade(CONDITION_PARAMS) } } +/* Basic call and re-INVITE with user-specified Contact: + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<------200 OK-------| + |--------ACK-------->| + | | + |-----re-INVITE----->| + |<------200 OK-------| + |--------ACK-------->| + | | + |<-------BYE---------| + |-------200 OK------>| + | | + + Client transitions: + INIT -(C1)-> CALLING -(C2a)-> PROCEEDING -(C3+C4)-> READY + Server transitions: + INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S4)-> READY + + Both client and server save Contact from nua_invite() and nua_respond(), + respectively. + + INIT -(C1)-> CALLING -(C3a+C4)-> READY + INIT -(S3c)-> COMPLETED -(S4)-> READY + + Both client and server use saved Contact. + + B sends BYE: + READY -(T2)-> TERMINATING -(T3)-> TERMINATED + A receives BYE: + READY -(T1)-> TERMINATED + + See @page nua_call_model in nua.docs for more information +*/ + +static sip_contact_t *contact_for_b; + +int accept_call_with_contact(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + SIPTAG_CONTACT(contact_for_b), + TAG_END()); + return 0; + case nua_callstate_early: + RESPOND(ep, call, nh, SIP_200_OK, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + SIPTAG_CONTACT(contact_for_b), + TAG_END()); + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int test_basic_call_6(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t *sip; + + sip_contact_t ma[1], mb[1]; + + if (print_headings) + printf("TEST NUA-3.1: Basic call\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + *ma = *a->contact; + ma->m_display = "Alice B."; + ma->m_url->url_user = "a++a"; + + *mb = *b->contact; + mb->m_display = "Bob A."; + mb->m_url->url_user = "b++b"; + + contact_for_b = mb; + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + SIPTAG_CONTACT(ma), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call_with_contact); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_payload); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_payload); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "Bob A."); + TEST_S(sip->sip_contact->m_url->url_user, "b++b"); + /* Test that B uses application-specific contact */ + TEST_1(sip->sip_contact->m_url->url_user); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "Alice B."); + TEST_S(sip->sip_contact->m_url->url_user, "a++a"); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + /* re-INVITE */ + INVITE(a, a_call, a_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_ready, -1, until_ready); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "Bob A."); + TEST_S(sip->sip_contact->m_url->url_user, "b++b"); + /* Test that B uses application-specific contact */ + TEST_1(sip->sip_contact->m_url->url_user); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "Alice B."); + TEST_S(sip->sip_contact->m_url->url_user, "a++a"); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + BYE(b, b_call, b_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(!nua_handle_has_active_call(b_call->nh)); + + /* A transitions: + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-3.1: PASSED\n"); + + END(); +} + int test_basic_call(struct context *ctx) { - return - test_basic_call_1(ctx) + return 0 + || test_basic_call_1(ctx) || test_basic_call_2(ctx) || test_basic_call_3(ctx) || test_basic_call_4(ctx) || test_basic_call_5(ctx) + || test_basic_call_6(ctx) || test_video_call_1(ctx) ; } diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c index a3bf75e179..c3be7ad0dd 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c @@ -262,6 +262,7 @@ int test_reject_b(struct context *ctx) /* ------------------------------------------------------------------------ */ int reject_302(CONDITION_PARAMS), reject_305(CONDITION_PARAMS); +int reject_500_retry_after(CONDITION_PARAMS); int redirect_always(CONDITION_PARAMS); int reject_604(CONDITION_PARAMS); @@ -270,12 +271,21 @@ int reject_604(CONDITION_PARAMS); | | |-------INVITE------>| |<----100 Trying-----| - | | |<-----302 Other-----| |--------ACK-------->| | | |-------INVITE------>| |<----100 Trying-----| + |<--305 Use Proxy----| + |--------ACK-------->| + | | + |-------INVITE------>| + |<----100 Trying-----| + |<----500 Retry------| + |--------ACK-------->| + | | + |-------INVITE------>| + |<----100 Trying-----| | | |<----180 Ringing----| | | @@ -330,29 +340,33 @@ int reject_305(CONDITION_PARAMS) case nua_callstate_terminated: if (call) nua_handle_destroy(call->nh), call->nh = NULL; - ep->next_condition = reject_604; + ep->next_condition = reject_500_retry_after; return 0; default: return 0; } } -int redirect_always(CONDITION_PARAMS) +int reject_500_retry_after(CONDITION_PARAMS) { if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) return 0; + save_event_in_list(ctx, event, ep, call); + if (event == nua_i_invite) { - char user[30]; - sip_contact_t m[1]; - *m = *ep->contact; - snprintf(user, sizeof user, "user-%u", ep->flags.n++); - m->m_url->url_user = user; - RESPOND(ep, call, nh, SIP_302_MOVED_TEMPORARILY, - SIPTAG_CONTACT(m), TAG_END()); - nua_handle_destroy(nh); - call->nh = NULL; - return 1; + sip_retry_after_t af[1]; + sip_retry_after_init(af)->af_delta = 1; + RESPOND(ep, call, nh, 500, "Retry After", SIPTAG_RETRY_AFTER(af), TAG_END()); + } + else if (event == nua_i_state) switch (callstate(tags)) { + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + ep->next_condition = reject_604; + break; + default: + break; } return 0; @@ -381,6 +395,28 @@ int reject_604(CONDITION_PARAMS) } } +int redirect_always(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + if (event == nua_i_invite) { + char user[30]; + sip_contact_t m[1]; + *m = *ep->contact; + snprintf(user, sizeof user, "user-%u", ep->flags.n++); + m->m_url->url_user = user; + RESPOND(ep, call, nh, SIP_302_MOVED_TEMPORARILY, + SIPTAG_CONTACT(m), TAG_END()); + nua_handle_destroy(nh); + call->nh = NULL; + return 1; + } + + return 0; +} + + int test_reject_302(struct context *ctx) { BEGIN(); @@ -407,18 +443,29 @@ int test_reject_302(struct context *ctx) /* A reject-3 B - | | + | | |-------INVITE------>| |<----100 Trying-----| - | | + | | |<-----302 Other-----| |--------ACK-------->| - | | + | | |-------INVITE------>| |<----100 Trying-----| - | | + |<---305 Use Proxy---| + |--------ACK-------->| + | | + |-------INVITE------>| + |<----100 Trying-----| + |<-----500 Retry-----| + |--------ACK-------->| + | | + | | + |-------INVITE------>| + |<----100 Trying-----| + | | |<----180 Ringing----| - | | + | | |<---604 Nowhere-----| |--------ACK-------->| */ @@ -445,6 +492,12 @@ int test_reject_302(struct context *ctx) TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ TEST_1(is_offer_sent(e->data->e_tags)); TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 100); + TEST(sip_object(e->data->e_msg)->sip_status->st_status, 500); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); TEST(e->data->e_status, 180); TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ @@ -475,6 +528,13 @@ int test_reject_302(struct context *ctx) TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite); TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); TEST_1(sip = sip_object(e->data->e_msg)); TEST_1(sip->sip_request); TEST_S(sip->sip_request->rq_url->url_user, "302"); @@ -1430,8 +1490,8 @@ int test_call_timeouts(struct context *ctx) |<--------200--------| |--ACK-X | | | - |---------BYE------->| - |<-------200 OK------| + |<--------BYE--------| + |--------200 OK----->| */ @@ -1484,6 +1544,109 @@ int test_call_timeouts(struct context *ctx) if (print_headings) printf("TEST NUA-4.7.3: PASSED\n"); + if (!ctx->nat) + goto completed_4_7_4; + + if (print_headings) + printf("TEST NUA-4.7.4: 200 OK timeout after client has timed out\n"); + + if (ctx->expensive) + nua_set_params(b->nua, NTATAG_SIP_T1X64(34000), TAG_END()); + else + nua_set_params(b->nua, NTATAG_SIP_T1X64(4000), TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + TEST_1(f = test_nat_add_filter(ctx->nat, filter_ACK, NULL, nat_outbound)); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("NUA-4.7.4"), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, accept_call); + + /* + A accept_call B + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<--------200--------| Timer H' + |--------ACK-----X X--+ + | | | + |<--------200--------| | + |--------ACK-----X | | + | | | + |<--------200--------| | + | | | + |<--------200--------| | + | | | + | |<-+ + |<--------BYE--------| + |--------200 OK----->| + + */ + + /* + */ + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); + TEST_1(!e->next); + + /* + Server transitions: + -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S5)-> TERMINATING + -(S10)-> TERMINATED -X + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_error); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminating); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + TEST_1(test_nat_remove_filter(ctx->nat, f) == 0); + + if (print_headings) + printf("TEST NUA-4.7.4: PASSED\n"); + + nua_set_params(b->nua, NTATAG_SIP_T1X64(2000), TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + completed_4_7_4: + /* XXX - PRACK timeout, PRACK failing, media failing, re-INVITEs */ if (print_headings) diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c index 6e69fa465c..107247c8cc 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c @@ -109,6 +109,24 @@ int cancel_when_calling(CONDITION_PARAMS) } } +int bye_when_calling(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_calling: + BYE(ep, call, nh, TAG_END()); + return 0; + case nua_callstate_terminated: + return 1; + default: + return 0; + } +} + int cancel_when_ringing(CONDITION_PARAMS) { @@ -163,7 +181,7 @@ int test_call_cancel(struct context *ctx) b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; if (print_headings) - printf("TEST NUA-5.1: cancel call\n"); + printf("TEST NUA-5.1.1: cancel call\n"); TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); @@ -212,10 +230,65 @@ int test_call_cancel(struct context *ctx) nua_handle_destroy(b_call->nh), b_call->nh = NULL; if (print_headings) - printf("TEST NUA-5.1: PASSED\n"); + printf("TEST NUA-5.1.1: PASSED\n"); /* ------------------------------------------------------------------------ */ + if (print_headings) + printf("TEST NUA-5.1.2: cancel call (with nua_bye())\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("TEST NUA-5.1.2"), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, bye_when_calling, -1, until_terminated); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state, nua_cancel() + CALLING -(C6a)-> TERMINATED: nua_r_invite(487), nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 487); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S6a)--> TERMINATED: nua_i_cancel, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_cancel); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-5.1.2: PASSED\n"); + + /* ----------------------------------------------------------------------- */ + if (print_headings) printf("TEST NUA-5.2.1: cancel call when ringing\n"); @@ -679,20 +752,24 @@ int test_call_destroy_4(struct context *ctx) TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ TEST_1(is_offer_sent(e->data->e_tags)); - TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); - TEST(e->data->e_status, 180); - TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); - TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ - TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); - TEST(e->data->e_status, 200); - TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); - TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */ - TEST_1(is_answer_recv(e->data->e_tags)); - TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye); + TEST_1(e = e->next); if (e->data->e_event == nua_r_invite) { + TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); + } + if (e->data->e_event == nua_r_invite) { + TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(e = e->next); + } + TEST_E(e->data->e_event, nua_i_bye); TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ - TEST_1(!e->next); - TEST_1(!e->next); free_events_in_list(ctx, a->events); nua_handle_destroy(a_call->nh), a_call->nh = NULL; @@ -1445,8 +1522,6 @@ int test_bye_to_invalid_contact(struct context *ctx) TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ - TEST_1(!e->next); - free_events_in_list(ctx, b->events); TEST_1(!nua_handle_has_active_call(b_call->nh)); TEST_1(nua_handle_has_active_call(a_call->nh)); @@ -1457,19 +1532,21 @@ int test_bye_to_invalid_contact(struct context *ctx) if (print_headings) printf("TEST NUA-6.4.3: PASSED\n"); - if (!ctx->p) + if (!ctx->p) { + free_events_in_list(ctx, b->events); return 0; + } if (print_headings) printf("TEST NUA-6.4.4: Wait for re-REGISTER after connection has been closed\n"); - /* B is supposed to re-register pretty soon, wait for re-registration */ - - run_b_until(ctx, -1, save_until_final_response); + if (!e->next || (!e->next->next || !e->next->data->e_status != 200)) + /* B is supposed to re-register pretty soon, wait for re-registration */ + run_b_until(ctx, -1, save_until_final_response); seen_401 = 0; - for (e = b->events->head; e; e = e->next) { + for (e = e->next; e; e = e->next) { TEST_E(e->data->e_event, nua_r_register); TEST_1(sip = sip_object(e->data->e_msg)); diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c index 269275d9f1..e8f67d94c3 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c @@ -36,6 +36,7 @@ #include "test_nua.h" #include +#include #if HAVE_FUNC #elif HAVE_FUNCTION @@ -107,17 +108,52 @@ int test_nua_init(struct context *ctx, TEST_1(close(temp) == 0); ctx->p = test_proxy_create(ctx->root, - AUTHTAG_METHOD("Digest"), - AUTHTAG_REALM("test-proxy"), - AUTHTAG_OPAQUE("kuik"), - AUTHTAG_DB(passwd_name), - AUTHTAG_QOP("auth-int"), - AUTHTAG_ALGORITHM("md5"), - AUTHTAG_NEXT_EXPIRES(60), TAG_IF(ctx->proxy_logging, TPTAG_LOG(1)), TAG_END()); - ctx->proxy_tests = ctx->p != NULL; + if (ctx->p) { + ctx->a.domain = + test_proxy_add_domain(ctx->p, + URL_STRING_MAKE("sip:example.com")->us_url, + AUTHTAG_METHOD("Digest"), + AUTHTAG_REALM("test-proxy"), + AUTHTAG_OPAQUE("kuik"), + AUTHTAG_DB(passwd_name), + AUTHTAG_QOP("auth-int"), + AUTHTAG_ALGORITHM("md5"), + AUTHTAG_NEXT_EXPIRES(60), + TAG_END()); + + ctx->b.domain = + test_proxy_add_domain(ctx->p, + URL_STRING_MAKE("sip:example.org")->us_url, + AUTHTAG_METHOD("Digest"), + AUTHTAG_REALM("test-proxy"), + AUTHTAG_OPAQUE("kuik"), + AUTHTAG_DB(passwd_name), + AUTHTAG_QOP("auth-int"), + AUTHTAG_ALGORITHM("md5"), + AUTHTAG_NEXT_EXPIRES(60), + TAG_END()); + + test_proxy_domain_set_outbound(ctx->b.domain, 1); + + ctx->c.domain = + test_proxy_add_domain(ctx->p, + URL_STRING_MAKE("sip:example.net")->us_url, + AUTHTAG_METHOD("Digest"), + AUTHTAG_REALM("test-proxy"), + AUTHTAG_OPAQUE("kuik"), + AUTHTAG_DB(passwd_name), + AUTHTAG_QOP("auth-int"), + AUTHTAG_ALGORITHM("md5"), + AUTHTAG_NEXT_EXPIRES(60), + AUTHTAG_MAX_NCOUNT(1), + TAG_END()); + + ctx->proxy_tests = 1; + } + if (print_headings) printf("TEST NUA-2.1.1: PASSED\n"); @@ -276,6 +312,7 @@ int test_nua_init(struct context *ctx, NUTAG_INSTANCE(ctx->b.instance), /* Quicker timeout */ NTATAG_SIP_T1X64(2000), + TPTAG_KEEPALIVE(100), TAG_IF(ctx->b.logging, TPTAG_LOG(1)), TAG_END()); TEST_1(ctx->b.nua); diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c index 5798748dab..cc41308888 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c @@ -65,6 +65,8 @@ int tstflags = 0; static RETSIGTYPE sig_alarm(int s) { fprintf(stderr, "%s: FAIL! test timeout!\n", name); + if (tstflags & tst_abort) + abort(); exit(1); } #endif diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h b/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h index 5947f04f72..a77c7f54ee 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h @@ -139,6 +139,7 @@ struct context int running; + struct domain *domain; condition_function *next_condition; nua_event_t next_event, last_event; nua_t *nua; diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c index 1e89252126..355d289adb 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c @@ -146,47 +146,59 @@ void print_event(nua_event_t event, tagi_t tags[]) { tagi_t const *t; + static su_nanotime_t started = 0; + su_nanotime_t now; + char timestamp[32]; + + su_nanotime(&now); + + if (started == 0) started = now; + + now -= started; now /= 1000000; + + snprintf(timestamp, sizeof timestamp, "%03u.%03u", + (unsigned)(now / 1000), (unsigned)(now % 1000)); if (event == nua_i_state) { - fprintf(stderr, "%s.nua(%p): event %s %s\n", + fprintf(stderr, "%s %s.nua(%p): event %s %s\n", timestamp, ep->name, (void *)nh, nua_event_name(event), nua_callstate_name(callstate(tags))); } else if ((int)event >= nua_r_set_params) { t = tl_find(tags, nutag_substate); if (t) { - fprintf(stderr, "%s.nua(%p): event %s status %u %s (%s)\n", + fprintf(stderr, "%s %s.nua(%p): event %s status %u %s (%s)\n", timestamp, ep->name, (void*)nh, nua_event_name(event), status, phrase, nua_substate_name(t->t_value)); } else { - fprintf(stderr, "%s.nua(%p): event %s status %u %s\n", + fprintf(stderr, "%s %s.nua(%p): event %s status %u %s\n", timestamp, ep->name, (void *)nh, nua_event_name(event), status, phrase); } } else if (event == nua_i_notify) { t = tl_find(tags, nutag_substate); - fprintf(stderr, "%s.nua(%p): event %s %s (%s)\n", + fprintf(stderr, "%s %s.nua(%p): event %s %s (%s)\n", timestamp, ep->name, (void *)nh, nua_event_name(event), phrase, nua_substate_name(t ? t->t_value : 0)); } else if ((int)event >= 0) { - fprintf(stderr, "%s.nua(%p): event %s %s\n", + fprintf(stderr, "%s %s.nua(%p): event %s %s\n", timestamp, ep->name, (void *)nh, nua_event_name(event), phrase); } else if (status > 0) { - fprintf(stderr, "%s.nua(%p): call %s() with status %u %s\n", + fprintf(stderr, "%s %s.nua(%p): call %s() with status %u %s\n", timestamp, ep->name, (void *)nh, operation, status, phrase); } else { t = tl_find(tags, siptag_subject_str); if (t && t->t_value) { char const *subject = (char const *)t->t_value; - fprintf(stderr, "%s.nua(%p): call %s() \"%s\"\n", + fprintf(stderr, "%s %s.nua(%p): call %s() \"%s\"\n", timestamp, ep->name, (void *)nh, operation, subject); } else - fprintf(stderr, "%s.nua(%p): call %s()\n", + fprintf(stderr, "%s %s.nua(%p): call %s()\n", timestamp, ep->name, (void *)nh, operation); } @@ -292,7 +304,7 @@ void run_abc_until(struct context *ctx, memset(&c->flags, 0, sizeof c->flags); for (; a->running || b->running || c->running;) { - su_root_step(ctx->root, 1000); + su_root_step(ctx->root, 100); } } diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c index bcdb160bb3..e2b169a66b 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c @@ -35,14 +35,17 @@ #include struct proxy; -struct proxy_transaction; +struct domain; +union proxy_or_domain; +struct proxy_tr; +struct client_tr; struct registration_entry; struct binding; #define SU_ROOT_MAGIC_T struct proxy -#define NTA_LEG_MAGIC_T struct proxy -#define NTA_OUTGOING_MAGIC_T struct proxy_transaction -#define NTA_INCOMING_MAGIC_T struct proxy_transaction +#define NTA_LEG_MAGIC_T union proxy_or_domain +#define NTA_OUTGOING_MAGIC_T struct client_tr +#define NTA_INCOMING_MAGIC_T struct proxy_tr #include #include @@ -83,50 +86,74 @@ STORAGE void PREFIX ##_remove(T *node) \ } \ extern int LIST_DUMMY_VARIABLE -#include "test_proxy.h" +#include +#include struct proxy { su_home_t home[1]; + void *magic; su_root_t *parent; su_clone_r clone; tagi_t *tags; su_root_t *root; - auth_mod_t *auth; nta_agent_t *agent; url_t const *uri; - - nta_leg_t *defleg; + url_t const *rr_uri; - nta_leg_t *example_net; - nta_leg_t *example_org; - nta_leg_t *example_com; + nta_leg_t *defleg; sip_contact_t *transport_contacts; - struct proxy_transaction *stateless; - struct proxy_transaction *transactions; + struct proxy_tr *stateless; + struct proxy_tr *transactions; + + struct domain *domains; + + struct { + sip_time_t session_expires, min_se; + } prefs; +}; + +struct domain { + su_home_t home[1]; + void *magic; + struct proxy *proxy; + struct domain *next, **prev; + + url_t *uri; + + nta_leg_t *rleg, *uleg; + + auth_mod_t *auth; struct registration_entry *entries; struct { sip_time_t min_expires, expires, max_expires; - - sip_time_t session_expires, min_se; - int outbound_tcp; /**< Use inbound TCP connection as outbound */ - } prefs; -}; + int authorize; + } prefs; + + tagi_t *tags; +}; + +LIST_PROTOS(static, domain, struct domain); +static int _domain_init(void *_d); +static int domain_init(struct domain *domain); +static void domain_destroy(struct domain *domain); + +LIST_BODIES(static, domain, struct domain, next, prev); LIST_PROTOS(static, registration_entry, struct registration_entry); -static struct registration_entry *registration_entry_new(struct proxy *, +static struct registration_entry *registration_entry_new(struct domain *, url_t const *); static void registration_entry_destroy(struct registration_entry *e); struct registration_entry { struct registration_entry *next, **prev; - struct proxy *proxy; /* backpointer */ + struct domain *domain; /* backpointer */ url_t *aor; /* address-of-record */ struct binding *bindings; /* list of bindings */ sip_contact_t *contacts; @@ -145,7 +172,7 @@ struct binding static struct binding *binding_new(su_home_t *home, sip_contact_t *contact, tport_t *tport, - sip_call_id_t *call_id, + sip_call_id_t const *call_id, uint32_t cseq, sip_time_t registered, sip_time_t expires); @@ -157,51 +184,75 @@ static int binding_is_active(struct binding const *b) (b->tport == NULL || tport_is_clear_to_send(b->tport)); } -LIST_PROTOS(static, proxy_transaction, struct proxy_transaction); -struct proxy_transaction *proxy_transaction_new(struct proxy *); -static void proxy_transaction_destroy(struct proxy_transaction *t); +LIST_PROTOS(static, proxy_tr, struct proxy_tr); +struct proxy_tr *proxy_tr_new(struct proxy *); +static void proxy_tr_destroy(struct proxy_tr *t); -struct proxy_transaction +struct proxy_tr { - struct proxy_transaction *next, **prev; + struct proxy_tr *next, **prev; struct proxy *proxy; /* backpointer */ - sip_request_t *rq; /* request line */ + + struct domain *origin; /* originating domain */ + struct domain *domain; /* destination domain */ + + sip_time_t now; /* when received */ + nta_incoming_t *server; /* server transaction */ - nta_outgoing_t *client; /* client transaction */ + msg_t *msg; /* request message */ + sip_t *sip; /* request headers */ + + sip_method_t method; /* request method */ + int status; /* best status */ + url_t *target; /* request-URI */ + + struct client_tr *clients; /* client transactions */ + + struct registration_entry *entry; + /* Registration entry */ + + auth_mod_t *am; /* Authentication module */ + auth_status_t *as; /* Authentication status */ + unsigned use_auth; /* Authentication method (401/407) to use */ + + unsigned rr:1; }; +LIST_PROTOS(static, client_tr, struct client_tr); + +struct client_tr +{ + struct client_tr *next, **prev; + struct proxy_tr *t; + + int status; /* response status */ + sip_request_t *rq; /* request line */ + msg_t *msg; /* request message */ + sip_t *sip; /* request headers */ + nta_outgoing_t *client; /* transaction */ +}; + +LIST_BODIES(static, client_tr, struct client_tr, next, prev); + static sip_contact_t *create_transport_contacts(struct proxy *p); -static int proxy_request(struct proxy *proxy, +union proxy_or_domain { struct proxy proxy[1]; struct domain domain[1]; }; + +static int proxy_request(union proxy_or_domain *proxy, nta_leg_t *leg, nta_incoming_t *irq, sip_t const *sip); -static int proxy_ack_cancel(struct proxy_transaction *t, - nta_incoming_t *irq, - sip_t const *sip); - -static int proxy_response(struct proxy_transaction *t, - nta_outgoing_t *client, - sip_t const *sip); - -static int process_register(struct proxy *proxy, - nta_incoming_t *irq, - sip_t const *sip); - -static int domain_request(struct proxy *proxy, +static int domain_request(union proxy_or_domain *domain, nta_leg_t *leg, nta_incoming_t *irq, sip_t const *sip); -static int process_options(struct proxy *proxy, - nta_incoming_t *irq, +static int proxy_response(struct client_tr *client, + nta_outgoing_t *orq, sip_t const *sip); -static struct registration_entry * -registration_entry_find(struct proxy const *proxy, url_t const *uri); - static int close_tports(void *proxy); static auth_challenger_t registrar_challenger[1]; @@ -211,7 +262,8 @@ static auth_challenger_t proxy_challenger[1]; static int test_proxy_init(su_root_t *root, struct proxy *proxy) { - struct proxy_transaction *t; + struct proxy_tr *t; + struct client_tr *c; auth_challenger_t _proxy_challenger[1] = {{ @@ -232,8 +284,6 @@ test_proxy_init(su_root_t *root, struct proxy *proxy) proxy->root = root; - proxy->auth = auth_mod_create(root, TAG_NEXT(proxy->tags)); - proxy->agent = nta_agent_create(root, URL_STRING_MAKE("sip:0.0.0.0:*"), NULL, NULL, @@ -247,53 +297,32 @@ test_proxy_init(su_root_t *root, struct proxy *proxy) proxy->defleg = nta_leg_tcreate(proxy->agent, proxy_request, - proxy, + (union proxy_or_domain *)proxy, NTATAG_NO_DIALOG(1), TAG_END()); - proxy->example_net = nta_leg_tcreate(proxy->agent, - domain_request, - proxy, - NTATAG_NO_DIALOG(1), - URLTAG_URL("sip:example.net"), - TAG_END()); - proxy->example_org = nta_leg_tcreate(proxy->agent, - domain_request, - proxy, - NTATAG_NO_DIALOG(1), - URLTAG_URL("sip:example.org"), - TAG_END()); - proxy->example_com = nta_leg_tcreate(proxy->agent, - domain_request, - proxy, - NTATAG_NO_DIALOG(1), - URLTAG_URL("sip:example.com"), - TAG_END()); - - proxy->prefs.min_expires = 30; - proxy->prefs.expires = 3600; - proxy->prefs.max_expires = 3600; - proxy->prefs.session_expires = 180; proxy->prefs.min_se = 90; - proxy->prefs.outbound_tcp = 1; - - if (!proxy->defleg || - !proxy->example_net || !proxy->example_org || !proxy->example_com) + if (!proxy->defleg) return -1; + /* if (!proxy->example_net || !proxy->example_org || !proxy->example_com) + return -1; */ - t = su_zalloc(proxy->home, sizeof *t); + /* Create stateless client */ + t = su_zalloc(proxy->home, sizeof *t); + c = su_zalloc(proxy->home, sizeof *c); - if (!t) + if (!t || !c) return -1; proxy->stateless = t; t->proxy = proxy; + c->t = t, client_tr_insert(&t->clients, c); t->server = nta_incoming_default(proxy->agent); - t->client = nta_outgoing_default(proxy->agent, proxy_response, t); + c->client = nta_outgoing_default(proxy->agent, proxy_response, c); - if (!t->client || !t->server) + if (!c->client || !t->server) return -1; proxy->uri = nta_agent_contact(proxy->agent)->m_url; @@ -304,17 +333,15 @@ test_proxy_init(su_root_t *root, struct proxy *proxy) static void test_proxy_deinit(su_root_t *root, struct proxy *proxy) { - struct proxy_transaction *t; - - auth_mod_destroy(proxy->auth); + struct proxy_tr *t; if ((t = proxy->stateless)) { - nta_incoming_destroy(t->server), t->server = NULL; - nta_outgoing_destroy(t->client), t->client = NULL; + proxy->stateless = NULL; + proxy_tr_destroy(t); } - while (proxy->entries) - registration_entry_destroy(proxy->entries); + while (proxy->domains) + domain_destroy(proxy->domains); nta_agent_destroy(proxy->agent); @@ -330,6 +357,8 @@ struct proxy *test_proxy_create(su_root_t *root, if (p) { ta_list ta; + p->magic = test_proxy_create; + p->parent = root; ta_start(ta, tag, value); @@ -362,27 +391,27 @@ url_t const *test_proxy_uri(struct proxy const *p) return p ? p->uri : NULL; } -void test_proxy_set_expiration(struct proxy *p, - sip_time_t min_expires, - sip_time_t expires, - sip_time_t max_expires) +void test_proxy_domain_set_expiration(struct domain *d, + sip_time_t min_expires, + sip_time_t expires, + sip_time_t max_expires) { - if (p) { - p->prefs.min_expires = min_expires; - p->prefs.expires = expires; - p->prefs.max_expires = max_expires; + if (d) { + d->prefs.min_expires = min_expires; + d->prefs.expires = expires; + d->prefs.max_expires = max_expires; } } -void test_proxy_get_expiration(struct proxy *p, - sip_time_t *return_min_expires, - sip_time_t *return_expires, - sip_time_t *return_max_expires) +void test_proxy_domain_get_expiration(struct domain *d, + sip_time_t *return_min_expires, + sip_time_t *return_expires, + sip_time_t *return_max_expires) { - if (p) { - if (return_min_expires) *return_min_expires = p->prefs.min_expires; - if (return_expires) *return_expires = p->prefs.expires; - if (return_max_expires) *return_max_expires = p->prefs.max_expires; + if (d) { + if (return_min_expires) *return_min_expires = d->prefs.min_expires; + if (return_expires) *return_expires = d->prefs.expires; + if (return_max_expires) *return_max_expires = d->prefs.max_expires; } } @@ -407,20 +436,36 @@ void test_proxy_get_session_timer(struct proxy *p, } } -void test_proxy_set_outbound(struct proxy *p, - int use_outbound) +void test_proxy_domain_set_outbound(struct domain *d, + int use_outbound) { - if (p) { - p->prefs.outbound_tcp = use_outbound; + if (d) { + d->prefs.outbound_tcp = use_outbound; } } -void test_proxy_get_outbound(struct proxy *p, - int *return_use_outbound) +void test_proxy_domain_get_outbound(struct domain *d, + int *return_use_outbound) { - if (p) { + if (d) { if (return_use_outbound) - *return_use_outbound = p->prefs.outbound_tcp; + *return_use_outbound = d->prefs.outbound_tcp; + } +} + +void test_proxy_domain_set_authorize(struct domain *d, int authorize) +{ + if (d) { + d->prefs.authorize = authorize; + } +} + +void test_proxy_domain_get_authorize(struct domain *d, + int *return_authorize) +{ + if (d) { + if (return_authorize) + *return_authorize = d->prefs.authorize; } } @@ -441,6 +486,106 @@ int test_proxy_close_tports(struct proxy *p) /* ---------------------------------------------------------------------- */ +struct domain *test_proxy_add_domain(struct proxy *p, + url_t const *uri, + tag_type_t tag, tag_value_t value, ...) +{ + struct domain *d; + + if (p == NULL || uri == NULL) + return NULL; + + d = su_home_clone(p->home, sizeof *d); + + if (d) { + ta_list ta; + int init = 0; + + ta_start(ta, tag, value); + + d->magic = domain_init; + + d->proxy = p; + d->uri = url_hdup(d->home, uri); + d->tags = tl_adup(d->home, ta_args(ta)); + + d->prefs.min_expires = 300; + d->prefs.expires = 3600; + d->prefs.max_expires = 36000; + d->prefs.outbound_tcp = 0; + d->prefs.authorize = 0; + + if (d->uri && d->tags && + !su_task_execute(su_clone_task(p->clone), _domain_init, d, &init)) { + if (init == 0) + /* OK */; + else + d = NULL; + } + else + su_home_unref(d->home); + } + + return d; +} + +static int _domain_init(void *_d) +{ + return domain_init(_d); +} + +static int domain_init(struct domain *d) +{ + struct proxy *p = d->proxy; + url_t uri[1]; + + *uri = *d->uri; + + d->auth = auth_mod_create(p->root, TAG_NEXT(d->tags)); + + /* Leg for URIs without userpart */ + d->rleg = nta_leg_tcreate(d->proxy->agent, + domain_request, + (union proxy_or_domain *)d, + NTATAG_NO_DIALOG(1), + URLTAG_URL(uri), + TAG_END()); + + /* Leg for URIs with wildcard userpart */ + uri->url_user = "%"; + d->uleg = nta_leg_tcreate(d->proxy->agent, + domain_request, + (union proxy_or_domain *)d, + NTATAG_NO_DIALOG(1), + URLTAG_URL(uri), + TAG_END()); + + if (d->auth && d->rleg && d->uleg) { + domain_insert(&p->domains, d); + return 0; + } + + domain_destroy(d); + + return -1; +} + +static void domain_destroy(struct domain *d) +{ + while (d->entries) + registration_entry_destroy(d->entries); + + nta_leg_destroy(d->rleg), d->rleg = NULL; + nta_leg_destroy(d->uleg), d->uleg = NULL; + auth_mod_destroy(d->auth), d->auth = NULL; + + domain_remove(d); + + su_home_unref(d->home); +} + +/* ---------------------------------------------------------------------- */ + static sip_contact_t *create_transport_contacts(struct proxy *p) { su_home_t *home = p->home; @@ -474,166 +619,332 @@ static sip_contact_t *create_transport_contacts(struct proxy *p) /* ---------------------------------------------------------------------- */ -static int challenge_request(struct proxy *, nta_incoming_t *, sip_t const *); +static int proxy_tr_with(struct proxy *proxy, + struct domain *domain, + nta_incoming_t *irq, + sip_t const *sip, + int (*process)(struct proxy_tr *)); +static int proxy_transaction(struct proxy_tr *t); +static int respond_transaction(struct proxy_tr *t, + int status, char const *phrase, + tag_type_t tag, tag_value_t value, + ...); +static int validate_transaction(struct proxy_tr *t); +static int originating_transaction(struct proxy_tr *t); +static int challenge_transaction(struct proxy_tr *t); +static int session_timers(struct proxy_tr *t); +static int incoming_transaction(struct proxy_tr *t); +static int target_transaction(struct proxy_tr *t, + url_t const *target, + tport_t *tport); +static int process_register(struct proxy_tr *t); +static int process_options(struct proxy_tr *t); -/** Forward request */ -static -int proxy_request(struct proxy *proxy, - nta_leg_t *leg, - nta_incoming_t *irq, - sip_t const *sip) +static int proxy_ack_cancel(struct proxy_tr *t, + nta_incoming_t *irq, + sip_t const *sip); + +static struct registration_entry * +registration_entry_find(struct domain const *domain, url_t const *uri); + +static int proxy_request(union proxy_or_domain *pod, + nta_leg_t *leg, + nta_incoming_t *irq, + sip_t const *sip) { - url_t const *request_uri, *target; - struct proxy_transaction *t = NULL; - sip_request_t *rq = NULL; - sip_max_forwards_t *mf; + assert(pod->proxy->magic = test_proxy_init); + + return proxy_tr_with(pod->proxy, NULL, irq, sip, proxy_transaction); +} + +static int domain_request(union proxy_or_domain *pod, + nta_leg_t *leg, + nta_incoming_t *irq, + sip_t const *sip) +{ + int (*process)(struct proxy_tr *) = NULL; sip_method_t method = sip->sip_request->rq_method; - sip_session_expires_t *x = NULL, x0[1]; - sip_min_se_t *min_se = NULL, min_se0[1]; - char const *require = NULL; - tport_t *tport = NULL; + assert(pod->domain->magic = domain_init); - mf = sip->sip_max_forwards; + if (leg == pod->domain->uleg) + process = proxy_transaction; + else if (method == sip_method_register) + process = process_register; + else if (method == sip_method_options) + process = process_options; - if (mf && mf->mf_count <= 1) { - if (sip->sip_request->rq_method == sip_method_options) { - return process_options(proxy, irq, sip); - } - nta_incoming_treply(irq, SIP_483_TOO_MANY_HOPS, TAG_END()); - return 483; - } + if (process == NULL) + return 501; /* Not implemented */ - if (method != sip_method_ack && method != sip_method_cancel && - str0casecmp(sip->sip_from->a_url->url_host, "example.net") == 0) { - /* Challenge everything but CANCEL and ACK coming from Mr. C */ - int status = challenge_request(proxy, irq, sip); - if (status) - return status; - } + return proxy_tr_with(pod->domain->proxy, pod->domain, irq, sip, process); +} - if (method == sip_method_invite) { - if (proxy->prefs.min_se) { - if (!sip->sip_min_se || - sip->sip_min_se->min_delta < proxy->prefs.min_se) { - min_se = sip_min_se_init(min_se0); - min_se->min_delta = proxy->prefs.min_se; - } +static int proxy_tr_with(struct proxy *proxy, + struct domain *domain, + nta_incoming_t *irq, + sip_t const *sip, + int (*process)(struct proxy_tr *)) +{ + struct proxy_tr *t = NULL; + int status = 500; - if (sip->sip_session_expires - && sip->sip_session_expires->x_delta < proxy->prefs.min_se - && sip_has_supported(sip->sip_supported, "timer")) { - if (min_se == NULL) - min_se = sip->sip_min_se; assert(min_se); - nta_incoming_treply(irq, SIP_422_SESSION_TIMER_TOO_SMALL, - SIPTAG_MIN_SE(min_se), - TAG_END()); - return 422; - } - } + assert(proxy->magic = test_proxy_init); - if (proxy->prefs.session_expires) { - if (!sip->sip_session_expires || - sip->sip_session_expires->x_delta > proxy->prefs.session_expires) { - x = sip_session_expires_init(x0); - x->x_delta = proxy->prefs.session_expires; - if (!sip_has_supported(sip->sip_supported, "timer")) - require = "timer"; - } - } - } + t = proxy_tr_new(proxy); + if (t) { + t->proxy = proxy, t->domain = domain, t->server = irq; + t->msg = nta_incoming_getrequest(irq); + t->sip = sip_object(t->msg); - /* We don't do any route processing */ - request_uri = sip->sip_request->rq_url; + t->method = sip->sip_request->rq_method; + t->target = sip->sip_request->rq_url; + t->now = nta_incoming_received(irq, NULL); - if (!request_uri->url_host || - (strcasecmp(request_uri->url_host, "example.org") && - strcasecmp(request_uri->url_host, "example.net") && - strcasecmp(request_uri->url_host, "example.com"))) { - target = request_uri; + if (t->method != sip_method_ack && t->method != sip_method_cancel) + nta_incoming_bind(irq, proxy_ack_cancel, t); + + if (process(t) < 200) + return 0; + + proxy_tr_destroy(t); } else { - struct registration_entry *e; - struct binding *b; - - if (sip->sip_request->rq_method == sip_method_register) - return process_register(proxy, irq, sip); - - e = registration_entry_find(proxy, request_uri); - if (e == NULL) { - nta_incoming_treply(irq, SIP_404_NOT_FOUND, TAG_END()); - return 404; - } - - for (b = e->bindings; b; b = b->next) - if (binding_is_active(b)) - break; - - if (b == NULL) { - nta_incoming_treply(irq, SIP_480_TEMPORARILY_UNAVAILABLE, TAG_END()); - return 480; - } - - target = b->contact->m_url; - tport = b->tport; - } - - t = proxy_transaction_new(proxy); - if (t == NULL) { nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END()); - return 500; } - nta_incoming_bind(t->server = irq, proxy_ack_cancel, t); + + return status; +} + +/** Forward request */ +static int proxy_transaction(struct proxy_tr *t) +{ + if (originating_transaction(t)) + return t->status; + + if (validate_transaction(t)) + return t->status; + + if (session_timers(t)) + return t->status; + + if (t->domain) + return incoming_transaction(t); + + return target_transaction(t, t->target, NULL); +} - rq = sip_request_create(proxy->home, - sip->sip_request->rq_method, - sip->sip_request->rq_method_name, - (url_string_t *)target, - NULL); - if (rq == NULL) { - nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END()); - proxy_transaction_destroy(t); - return 500; - } - t->rq = rq; +static int respond_transaction(struct proxy_tr *t, + int status, char const *phrase, + tag_type_t tag, tag_value_t value, + ...) +{ + ta_list ta; + void *info = NULL, *response = NULL; - /* Forward request */ - t->client = nta_outgoing_mcreate(proxy->agent, proxy_response, t, NULL, - nta_incoming_getrequest(irq), - /* rewrite request */ - SIPTAG_REQUEST(rq), - SIPTAG_SESSION_EXPIRES(x), - SIPTAG_MIN_SE(min_se), - SIPTAG_REQUIRE_STR(require), - NTATAG_TPORT(tport), - TAG_END()); - if (t->client == NULL) { - proxy_transaction_destroy(t); - nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END()); - return 500; + ta_start(ta, tag, value); + + if (t->as) + info = t->as->as_info, response = t->as->as_response; + + if (nta_incoming_treply(t->server, t->status = status, phrase, + SIPTAG_HEADER(info), + SIPTAG_HEADER(response), + ta_tags(ta)) < 0) + t->status = status = 500; + + ta_end(ta); + + return status; +} + +static int originating_transaction(struct proxy_tr *t) +{ + struct domain *o; + char const *host; + + host = t->sip->sip_from->a_url->url_host; + if (!host) + return 0; + + for (o = t->proxy->domains; o; o = o->next) + if (strcasecmp(host, o->uri->url_host) == 0) + break; + + t->origin = o; + + if (o && o->auth && o->prefs.authorize) { + t->am = o->auth; + t->use_auth = 407; } - else if (sip->sip_request->rq_method == sip_method_ack) - proxy_transaction_destroy(t); return 0; } -static -int challenge_request(struct proxy *p, - nta_incoming_t *irq, - sip_t const *sip) +static int validate_transaction(struct proxy_tr *t) { - int status; - auth_status_t *as; - msg_t *msg; + sip_max_forwards_t *mf; - as = auth_status_new(p->home); - if (!as) + mf = t->sip->sip_max_forwards; + + if (mf && mf->mf_count <= 1) { + if (t->method == sip_method_options) + return process_options(t); + + return respond_transaction(t, SIP_483_TOO_MANY_HOPS, TAG_END()); + } + + /* Remove our routes */ + while (t->sip->sip_route && + url_has_param(t->sip->sip_route->r_url, "lr") && + url_cmp(t->proxy->rr_uri, t->sip->sip_route->r_url) == 0) { + sip_route_remove(t->msg, t->sip); + /* add record-route also to the forwarded request */ + t->rr = 1; + } + + if (t->use_auth) + return challenge_transaction(t); + + return 0; +} + +static int session_timers(struct proxy_tr *t) +{ + sip_t *sip = t->sip; + sip_session_expires_t *x = NULL, x0[1]; + sip_min_se_t *min_se = NULL, min_se0[1]; + char const *require = NULL; + + if (t->method == sip_method_invite) { + if (t->proxy->prefs.min_se) { + if (!sip->sip_min_se || + sip->sip_min_se->min_delta < t->proxy->prefs.min_se) { + min_se = sip_min_se_init(min_se0); + min_se->min_delta = t->proxy->prefs.min_se; + } + + if (sip->sip_session_expires + && sip->sip_session_expires->x_delta < t->proxy->prefs.min_se + && sip_has_supported(sip->sip_supported, "timer")) { + if (min_se == NULL) + min_se = sip->sip_min_se; assert(min_se); + return respond_transaction(t, SIP_422_SESSION_TIMER_TOO_SMALL, + SIPTAG_MIN_SE(min_se), + TAG_END()); + } + } + + if (t->proxy->prefs.session_expires) { + if (!sip->sip_session_expires || + sip->sip_session_expires->x_delta > t->proxy->prefs.session_expires) { + x = sip_session_expires_init(x0); + x->x_delta = t->proxy->prefs.session_expires; + if (!sip_has_supported(sip->sip_supported, "timer")) + require = "timer"; + } + } + + if (x || min_se || require) + sip_add_tl(t->msg, t->sip, + SIPTAG_REQUIRE_STR(require), + SIPTAG_MIN_SE(min_se), + SIPTAG_SESSION_EXPIRES(x), + TAG_END()); + } + + return 0; +} + +static int incoming_transaction(struct proxy_tr *t) +{ + struct registration_entry *e; + struct binding *b; + +#if 0 + if (sip->sip_request->rq_method == sip_method_register) + return process_register(proxy, irq, sip); +#endif + + t->entry = e = registration_entry_find(t->domain, t->target); + if (e == NULL) + return respond_transaction(t, SIP_404_NOT_FOUND, TAG_END()); + + for (b = e->bindings; b; b = b->next) { + if (binding_is_active(b)) + target_transaction(t, b->contact->m_url, b->tport); + + if (t->clients) /* XXX - enable forking */ + break; + } + + if (t->clients != NULL) + return 0; + + return respond_transaction(t, SIP_480_TEMPORARILY_UNAVAILABLE, TAG_END()); +} + +static int target_transaction(struct proxy_tr *t, + url_t const *target, + tport_t *tport) +{ + struct client_tr *c = su_zalloc(t->proxy->home, sizeof *c); + + if (c == NULL) return 500; + c->t = t; + c->msg = msg_copy(t->msg); + c->sip = sip_object(c->msg); + + if (c->msg) + c->rq = sip_request_create(msg_home(c->msg), + c->sip->sip_request->rq_method, + c->sip->sip_request->rq_method_name, + (url_string_t *)target, + NULL); + + msg_header_insert(c->msg, (msg_pub_t *)c->sip, (msg_header_t *)c->rq); + + if (t->rr && 0) { + sip_record_route_t rr[1]; + + *sip_record_route_init(rr)->r_url = *t->proxy->rr_uri; + + msg_header_add_dup(c->msg, (msg_pub_t *)c->sip, (msg_header_t *)rr); + } + + if (c->rq) + /* Forward request */ + c->client = nta_outgoing_mcreate(t->proxy->agent, + proxy_response, c, + NULL, + msg_ref_create(c->msg), + NTATAG_TPORT(tport), + TAG_END()); + + if (c->client) + return client_tr_insert(&t->clients, c), 0; + + msg_destroy(c->msg); + su_free(t->proxy->home, c); + + return 500; +} + +static int challenge_transaction(struct proxy_tr *t) +{ + auth_status_t *as; + sip_t *sip = t->sip; + + assert(t->am); + + t->as = as = auth_status_new(t->proxy->home); + if (!as) + return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR, TAG_END()); + as->as_method = sip->sip_request->rq_method_name; - msg = nta_incoming_getrequest(irq); - as->as_source = msg_addrinfo(msg); + as->as_source = msg_addrinfo(t->msg); as->as_user_uri = sip->sip_from->a_url; as->as_display = sip->sip_from->a_display; @@ -642,271 +953,196 @@ int challenge_request(struct proxy *p, as->as_body = sip->sip_payload->pl_data, as->as_bodylen = sip->sip_payload->pl_len; - auth_mod_check_client(p->auth, as, sip->sip_proxy_authorization, - proxy_challenger); + if (t->use_auth == 401) + auth_mod_check_client(t->am, as, sip->sip_authorization, + registrar_challenger); + else + auth_mod_check_client(t->am, as, sip->sip_proxy_authorization, + proxy_challenger); - if ((status = as->as_status)) { - nta_incoming_treply(irq, - as->as_status, as->as_phrase, - SIPTAG_HEADER((void *)as->as_info), - SIPTAG_HEADER((void *)as->as_response), - TAG_END()); - } - else if (as->as_match) { - msg_header_remove(msg, NULL, as->as_match); - } + if (as->as_status) + return respond_transaction(t, as->as_status, as->as_phrase, TAG_END()); - msg_destroy(msg); - su_home_unref(as->as_home); + if (as->as_match) + msg_header_remove(t->msg, (msg_pub_t *)sip, as->as_match); - return status; + return 0; } -int proxy_ack_cancel(struct proxy_transaction *t, +int proxy_ack_cancel(struct proxy_tr *t, nta_incoming_t *irq, sip_t const *sip) { - if (sip == NULL) { - proxy_transaction_destroy(t); + struct client_tr *c; + int status; + + if (sip == NULL) { /* timeout */ + proxy_tr_destroy(t); return 0; } - if (sip->sip_request->rq_method == sip_method_cancel) { - /* We don't care about response to CANCEL (or ACK) - * so we give NULL as callback pointer (and nta immediately - * destroys transaction object or marks it disposable) - */ - if (nta_outgoing_tcancel(t->client, NULL, NULL, TAG_END())) - return 200; - else - return 500; - } - else { + if (sip->sip_request->rq_method != sip_method_cancel) return 500; + + status = 200; + + for (c = t->clients; c; c = c->next) { + if (c->client && c->status < 200) + /* + * We don't care about response to CANCEL (or ACK) + * so we give NULL as callback pointer (and nta immediately + * destroys transaction object or marks it disposable) + */ + if (nta_outgoing_tcancel(c->client, NULL, NULL, TAG_END()) == NULL) + status = 500; } + + return status; } -int proxy_response(struct proxy_transaction *t, +int proxy_response(struct client_tr *c, nta_outgoing_t *client, sip_t const *sip) { int final; + assert(c->t); + if (sip) { msg_t *response = nta_outgoing_getresponse(client); final = sip->sip_status->st_status >= 200; sip_via_remove(response, sip_object(response)); - nta_incoming_mreply(t->server, response); + nta_incoming_mreply(c->t->server, response); } else { final = 1; - nta_incoming_treply(t->server, SIP_408_REQUEST_TIMEOUT, TAG_END()); + respond_transaction(c->t, SIP_408_REQUEST_TIMEOUT, TAG_END()); } if (final) - proxy_transaction_destroy(t); + proxy_tr_destroy(c->t); return 0; } -struct proxy_transaction * -proxy_transaction_new(struct proxy *proxy) +struct proxy_tr * +proxy_tr_new(struct proxy *proxy) { - struct proxy_transaction *t; + struct proxy_tr *t; t = su_zalloc(proxy->home, sizeof *t); if (t) { t->proxy = proxy; - proxy_transaction_insert(&proxy->transactions, t); + proxy_tr_insert(&proxy->transactions, t); } return t; } static -void proxy_transaction_destroy(struct proxy_transaction *t) +void proxy_tr_destroy(struct proxy_tr *t) { + struct client_tr *c; + if (t == t->proxy->stateless) return; - proxy_transaction_remove(t); + + proxy_tr_remove(t); + + if (t->as) + su_home_unref(t->as->as_home), t->as = NULL; + + while (t->clients) { + client_tr_remove(c = t->clients); + nta_outgoing_destroy(c->client), c->client = NULL; + msg_destroy(c->msg), c->msg = NULL; + su_free(t->proxy->home, c); + } + nta_incoming_destroy(t->server); - nta_outgoing_destroy(t->client); - su_free(t->proxy->home, t->rq); + su_free(t->proxy->home, t); } -LIST_BODIES(static, proxy_transaction, struct proxy_transaction, next, prev); +LIST_BODIES(static, proxy_tr, struct proxy_tr, next, prev); /* ---------------------------------------------------------------------- */ - -static -int domain_request(struct proxy *proxy, - nta_leg_t *leg, - nta_incoming_t *irq, - sip_t const *sip) +static int process_options(struct proxy_tr *t) { - sip_method_t method = sip->sip_request->rq_method; - - if (method == sip_method_register) - return process_register(proxy, irq, sip); - - if (method == sip_method_options) - return process_options(proxy, irq, sip); - - return 501; -} - -static -int process_options(struct proxy *proxy, - nta_incoming_t *irq, - sip_t const *sip) -{ - nta_incoming_treply(irq, SIP_200_OK, - SIPTAG_CONTACT(proxy->transport_contacts), - TAG_END()); - return 200; + return respond_transaction(t, SIP_200_OK, + SIPTAG_CONTACT(t->proxy->transport_contacts), + TAG_END()); } /* ---------------------------------------------------------------------- */ +static int check_received_contact(struct proxy_tr *t); +static int validate_contacts(struct proxy_tr *t); +static int check_out_of_order(struct proxy_tr *t); +static int update_bindings(struct proxy_tr *t); -static int process_register2(struct proxy *p, auth_status_t *as, - nta_incoming_t *irq, sip_t const *sip); - -static int set_status(auth_status_t *as, int status, char const *phrase); - -static int validate_contacts(struct proxy *p, auth_status_t *as, - sip_t const *sip); -static int check_out_of_order(struct proxy *p, auth_status_t *as, - struct registration_entry *e, sip_t const *); -static int binding_update(struct proxy *p, - auth_status_t *as, - struct registration_entry *e, - nta_incoming_t *irq, - sip_t const *sip); - -sip_contact_t *binding_contacts(su_home_t *home, struct binding *bindings); - -int process_register(struct proxy *proxy, - nta_incoming_t *irq, - sip_t const *sip) +int process_register(struct proxy_tr *t) { - auth_status_t *as; - msg_t *msg; - int status; + /* This is before authentication because we want to be bug-compatible */ + if (check_received_contact(t)) + return t->status; - as = auth_status_new(proxy->home); - if (!as) - return 500; + if (t->domain->auth) { + t->am = t->domain->auth, t->use_auth = 401; + if (challenge_transaction(t)) + return t->status; + } - as->as_method = sip->sip_request->rq_method_name; - msg = nta_incoming_getrequest(irq); - as->as_source = msg_addrinfo(msg); - msg_destroy(msg); + if (validate_contacts(t)) + return t->status; - as->as_user_uri = sip->sip_from->a_url; - as->as_display = sip->sip_from->a_display; + t->entry = registration_entry_find(t->domain, t->sip->sip_to->a_url); - if (sip->sip_payload) - as->as_body = sip->sip_payload->pl_data, - as->as_bodylen = sip->sip_payload->pl_len; - - process_register2(proxy, as, irq, sip); - assert(as->as_status >= 200); - - nta_incoming_treply(irq, - as->as_status, as->as_phrase, - SIPTAG_HEADER((void *)as->as_info), - SIPTAG_HEADER((void *)as->as_response), - TAG_END()); - - status = as->as_status; - - su_home_unref(as->as_home); - - return status; + if (check_out_of_order(t)) + return t->status; + + return update_bindings(t); } -static int process_register2(struct proxy *p, - auth_status_t *as, - nta_incoming_t *irq, - sip_t const *sip) +static int check_received_contact(struct proxy_tr *t) { - struct registration_entry *e = NULL; + sip_t *sip = t->sip; sip_contact_t *m = sip->sip_contact; sip_via_t *v = sip->sip_via; if (m && v && v->v_received && m->m_url->url_host && strcasecmp(v->v_received, m->m_url->url_host) && host_is_ip_address(m->m_url->url_host)) - return set_status(as, 406, "Unacceptable Contact"); + return respond_transaction(t, 406, "Unacceptable Contact", TAG_END()); - auth_mod_check_client(p->auth, as, sip->sip_authorization, - registrar_challenger); - if (as->as_status) - return as->as_status; - assert(as->as_response == NULL); + return 0; +} - if (validate_contacts(p, as, sip)) - return as->as_status; +/* Validate expiration times */ +static int validate_contacts(struct proxy_tr *t) +{ + sip_contact_t const *m = t->sip->sip_contact; + sip_expires_t const *ex = t->sip->sip_expires; + sip_date_t const *date = t->sip->sip_date; + sip_time_t expires; - e = registration_entry_find(p, sip->sip_to->a_url); - if (!sip->sip_contact) { - as->as_response = (msg_header_t *)e->contacts; - return set_status(as, SIP_200_OK); + if (m && m->m_url->url_type == url_any) { + if (!ex || ex->ex_delta || ex->ex_time || m->m_next) + return respond_transaction(t, SIP_400_BAD_REQUEST, TAG_END()); + return 0; } - if (e && check_out_of_order(p, as, e, sip)) - return as->as_status; - - if (!e) - e = registration_entry_new(p, sip->sip_to->a_url); - if (!e) - return set_status(as, SIP_500_INTERNAL_SERVER_ERROR); - - if (binding_update(p, as, e, irq, sip)) - return as->as_status; - - msg_header_free(p->home, (void *)e->contacts); - e->contacts = binding_contacts(p->home, e->bindings); - - as->as_response = (msg_header_t *)e->contacts; - - return set_status(as, SIP_200_OK); -} - -static int set_status(auth_status_t *as, int status, char const *phrase) -{ - return as->as_phrase = phrase, as->as_status = status; -} - -static int validate_contacts(struct proxy *p, - auth_status_t *as, - sip_t const *sip) -{ - sip_contact_t const *m; - sip_time_t expires; - sip_time_t now = sip_now(); - - for (m = sip->sip_contact; m; m = m->m_next) { - if (m->m_url->url_type == url_any) { - if (!sip->sip_expires || - sip->sip_expires->ex_delta || - sip->sip_expires->ex_time || - sip->sip_contact->m_next) - return set_status(as, SIP_400_BAD_REQUEST); - else - return 0; - } - - expires = sip_contact_expires(m, sip->sip_expires, sip->sip_date, - p->prefs.expires, now); + for (; m; m = m->m_next) { + expires = sip_contact_expires(m, ex, date, t->domain->prefs.expires, t->now); - if (expires > 0 && expires < p->prefs.min_expires) { - as->as_response = (msg_header_t *) - sip_min_expires_format(as->as_home, "%u", - (unsigned)p->prefs.min_expires); - return set_status(as, SIP_423_INTERVAL_TOO_BRIEF); + if (expires > 0 && expires < t->domain->prefs.min_expires) { + sip_min_expires_t me[1]; + + sip_min_expires_init(me)->me_delta = t->domain->prefs.min_expires; + + return respond_transaction(t, SIP_423_INTERVAL_TOO_BRIEF, + SIPTAG_MIN_EXPIRES(me), + TAG_END()); } } @@ -914,31 +1150,27 @@ static int validate_contacts(struct proxy *p, } /** Check for out-of-order register request */ -static -int check_out_of_order(struct proxy *p, - auth_status_t *as, - struct registration_entry *e, - sip_t const *sip) +static int check_out_of_order(struct proxy_tr *t) { struct binding const *b; - sip_call_id_t const *id; + sip_call_id_t const *id = t->sip->sip_call_id; + uint32_t cseq = t->sip->sip_cseq->cs_seq; sip_contact_t *m; - if (e == NULL || !sip->sip_contact) + if (t->entry == NULL || !t->sip->sip_contact) return 0; - id = sip->sip_call_id; - /* RFC 3261 subsection 10.3 step 6 and step 7 (p. 66): */ /* Check for reordered register requests */ - for (b = e->bindings; b; b = b->next) { + for (b = t->entry->bindings; b; b = b->next) { if (binding_is_active(b) && - strcmp(sip->sip_call_id->i_id, b->call_id->i_id) == 0 && - sip->sip_cseq->cs_seq <= b->cseq) { - for (m = sip->sip_contact; m; m = m->m_next) { + strcmp(id->i_id, b->call_id->i_id) == 0 && + cseq <= b->cseq) { + for (m = t->sip->sip_contact; m; m = m->m_next) { if (m->m_url->url_type == url_any || url_cmp_all(m->m_url, b->contact->m_url) == 0) - return set_status(as, SIP_500_INTERNAL_SERVER_ERROR); + return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR, + TAG_END()); } } } @@ -946,37 +1178,40 @@ int check_out_of_order(struct proxy *p, return 0; } - static struct registration_entry * -registration_entry_find(struct proxy const *proxy, url_t const *uri) +registration_entry_find(struct domain const *d, url_t const *uri) { struct registration_entry *e; /* Our routing table */ - for (e = proxy->entries; e; e = e->next) { + for (e = d->entries; e; e = e->next) { if (url_cmp(uri, e->aor) == 0) return e; } + return NULL; } static struct registration_entry * -registration_entry_new(struct proxy *proxy, url_t const *aor) +registration_entry_new(struct domain *d, url_t const *aor) { struct registration_entry *e; - e = su_zalloc(proxy->home, sizeof *e); + if (d == NULL) + return NULL; + + e = su_zalloc(d->home, sizeof *e); if (!e) return NULL; - e->proxy = proxy; - e->aor = url_hdup(proxy->home, aor); + e->domain = d; + e->aor = url_hdup(d->home, aor); if (!e->aor) { - su_free(proxy->home, e); + su_free(d->home, e); return NULL; } - registration_entry_insert(&proxy->entries, e); + registration_entry_insert(&d->entries, e); return e; } @@ -986,14 +1221,19 @@ registration_entry_destroy(struct registration_entry *e) { if (e) { registration_entry_remove(e); - su_free(e->proxy->home, e->aor); + su_free(e->domain->home, e->aor); while (e->bindings) - binding_destroy(e->proxy->home, e->bindings); - msg_header_free(e->proxy->home, (void *)e->contacts); - su_free(e->proxy->home, e); + binding_destroy(e->domain->home, e->bindings); + msg_header_free(e->domain->home, (void *)e->contacts); + su_free(e->domain->home, e); } } +sip_contact_t *entry_contacts(struct registration_entry *entry) +{ + return entry ? entry->contacts : NULL; +} + LIST_BODIES(static, registration_entry, struct registration_entry, next, prev); /* ---------------------------------------------------------------------- */ @@ -1003,7 +1243,7 @@ static struct binding *binding_new(su_home_t *home, sip_contact_t *contact, tport_t *tport, - sip_call_id_t *call_id, + sip_call_id_t const *call_id, uint32_t cseq, sip_time_t registered, sip_time_t expires) @@ -1046,40 +1286,48 @@ void binding_destroy(su_home_t *home, struct binding *b) su_free(home, b); } -static -int binding_update(struct proxy *p, - auth_status_t *as, - struct registration_entry *e, - nta_incoming_t *irq, - sip_t const *sip) +static int update_bindings(struct proxy_tr *t) { + struct domain *d = t->domain; struct binding *b, *old, *next, *last, *bindings = NULL, **bb = &bindings; sip_contact_t *m; + sip_call_id_t const *id = t->sip->sip_call_id; + uint32_t cseq = t->sip->sip_cseq->cs_seq; + sip_expires_t *ex = t->sip->sip_expires; + sip_date_t *date = t->sip->sip_date; sip_time_t expires; - sip_time_t now = sip_now(); tport_t *tport = NULL; + sip_contact_t *contacts = NULL, **mm = &contacts; + void *tbf; - assert(sip->sip_contact); + if (t->sip->sip_contact == NULL) { + if (t->entry) + contacts = t->entry->contacts; + goto ok200; + } - if (p->prefs.outbound_tcp && - str0casecmp(sip->sip_via->v_protocol, sip_transport_tcp) == 0) - tport = nta_incoming_transport(p->agent, irq, NULL); + if (t->entry == NULL) + t->entry = registration_entry_new(d, t->sip->sip_to->a_url); + if (t->entry == NULL) + return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR, TAG_END()); + + if (d->prefs.outbound_tcp && + str0casecmp(t->sip->sip_via->v_protocol, sip_transport_tcp) == 0) + tport = nta_incoming_transport(t->proxy->agent, t->server, NULL); /* Create new bindings */ - for (m = sip->sip_contact; m; m = m->m_next) { + for (m = t->sip->sip_contact; m; m = m->m_next) { if (m->m_url->url_type == url_any) break; - expires = sip_contact_expires(m, sip->sip_expires, sip->sip_date, - p->prefs.expires, now); + expires = sip_contact_expires(m, ex, date, d->prefs.expires, t->now); - if (expires > p->prefs.max_expires) - expires = p->prefs.max_expires; + if (expires > d->prefs.max_expires) + expires = d->prefs.max_expires; msg_header_remove_param(m->m_common, "expires"); - b = binding_new(p->home, m, tport, sip->sip_call_id, sip->sip_cseq->cs_seq, - now, now + expires); + b = binding_new(d->home, m, tport, id, cseq, t->now, t->now + expires); if (!b) break; @@ -1092,7 +1340,7 @@ int binding_update(struct proxy *p, if (m == NULL) { /* Merge new bindings with old ones */ - for (old = e->bindings; old; old = next) { + for (old = t->entry->bindings; old; old = next) { next = old->next; for (b = bindings; b != last; b = b->next) { @@ -1102,12 +1350,12 @@ int binding_update(struct proxy *p, if (strcmp(old->call_id->i_id, b->call_id->i_id) == 0) { b->registered = old->registered; } - binding_destroy(p->home, old); + binding_destroy(d->home, old); break; } } - for (bb = &e->bindings; *bb; bb = &(*bb)->next) + for (bb = &t->entry->bindings; *bb; bb = &(*bb)->next) ; if ((*bb = bindings)) @@ -1115,8 +1363,8 @@ int binding_update(struct proxy *p, } else if (m->m_url->url_type == url_any) { /* Unregister all */ - for (b = e->bindings; b; b = b->next) { - b->expires = now; + for (b = t->entry->bindings; b; b = b->next) { + b->expires = t->now; } } else { @@ -1124,34 +1372,35 @@ int binding_update(struct proxy *p, for (old = bindings; old; old = next) { next = old->next; - binding_destroy(p->home, old); + binding_destroy(d->home, old); } - return set_status(as, SIP_500_INTERNAL_SERVER_ERROR); + return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR, TAG_END()); } - return 0; -} - -sip_contact_t *binding_contacts(su_home_t *home, struct binding *bindings) -{ - sip_contact_t *retval = NULL, **mm = &retval; - struct binding *b; - sip_time_t now = sip_now(); - - for (b = bindings; b; b = b->next) { + for (b = t->entry->bindings; b; b = b->next) { char const *expires; - if (b->expires <= now) + + if (b->expires <= t->now) continue; - *mm = sip_contact_copy(home, b->contact); + + *mm = sip_contact_copy(d->home, b->contact); if (*mm) { - expires = su_sprintf(home, "expires=%u", (unsigned)(b->expires - now)); - msg_header_add_param(home, (*mm)->m_common, expires); + expires = su_sprintf(d->home, "expires=%u", + (unsigned)(b->expires - t->now)); + msg_header_add_param(d->home, (*mm)->m_common, expires); mm = &(*mm)->m_next; } } - return retval; + tbf = t->entry->contacts; + t->entry->contacts = contacts; + msg_header_free(d->home, tbf); + + ok200: + return respond_transaction(t, SIP_200_OK, + SIPTAG_CONTACT(contacts), + TAG_END()); } /* ---------------------------------------------------------------------- */ @@ -1159,16 +1408,19 @@ sip_contact_t *binding_contacts(su_home_t *home, struct binding *bindings) static int close_tports(void *_proxy) { struct proxy *p = _proxy; + struct domain *d; struct registration_entry *e; struct binding *b; /* Close all outbound transports */ - for (e = p->entries; e; e = e->next) { - for (b = e->bindings; b; b = b->next) { - if (b->tport) { - tport_shutdown(b->tport, 2); - tport_unref(b->tport); - b->tport = NULL; + for (d = p->domains; d; d = d->next) { + for (e = d->entries; e; e = e->next) { + for (b = e->bindings; b; b = b->next) { + if (b->tport) { + tport_shutdown(b->tport, 1); + tport_unref(b->tport); + b->tport = NULL; + } } } } diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h b/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h index 9f74a533a8..fa433eca2a 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h @@ -31,6 +31,7 @@ SOFIA_BEGIN_DECLS struct proxy; +struct domain; struct proxy *test_proxy_create(su_root_t *, tag_type_t, tag_value_t, ...); @@ -38,15 +39,19 @@ void test_proxy_destroy(struct proxy *); url_t const *test_proxy_uri(struct proxy const *); -void test_proxy_set_expiration(struct proxy *, - sip_time_t min_expires, - sip_time_t expires, - sip_time_t max_expires); +struct domain *test_proxy_add_domain(struct proxy *, + url_t const *domain, + tag_type_t, tag_value_t, ...); -void test_proxy_get_expiration(struct proxy *, - sip_time_t *return_min_expires, - sip_time_t *return_expires, - sip_time_t *return_max_expires); +void test_proxy_domain_set_expiration(struct domain *, + sip_time_t min_expires, + sip_time_t expires, + sip_time_t max_expires); + +void test_proxy_domain_get_expiration(struct domain *, + sip_time_t *return_min_expires, + sip_time_t *return_expires, + sip_time_t *return_max_expires); void test_proxy_set_session_timer(struct proxy *p, sip_time_t session_expires, @@ -56,6 +61,14 @@ void test_proxy_get_session_timer(struct proxy *p, sip_time_t *return_session_expires, sip_time_t *return_min_se); +void test_proxy_domain_set_authorize(struct domain *d, int authorize); +void test_proxy_domain_get_authorize(struct domain *d, int *return_authorize); + +void test_proxy_domain_set_outbound(struct domain *d, + int use_outbound); +void test_proxy_domain_get_outbound(struct domain *d, + int *return_use_outbound); + int test_proxy_close_tports(struct proxy *p); SOFIA_END_DECLS diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c index ed1ab1520d..aafdb961db 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c @@ -57,9 +57,6 @@ int test_register_to_proxy(struct context *ctx) sip_cseq_t cseq[1]; int seen_401; - if (ctx->p) - test_proxy_set_expiration(ctx->p, 5, 5, 10); - if (print_headings) printf("TEST NUA-2.3.0.1: un-REGISTER a\n"); @@ -108,7 +105,6 @@ int test_register_to_proxy(struct context *ctx) if (print_headings) printf("TEST NUA-2.3.0.3: PASSED\n"); - /* REGISTER test A B @@ -123,6 +119,8 @@ int test_register_to_proxy(struct context *ctx) if (print_headings) printf("TEST NUA-2.3.1: REGISTER a\n"); + test_proxy_domain_set_expiration(ctx->a.domain, 5, 5, 10); + TEST_1(a_reg->nh = nua_handle(a->nua, a_reg, TAG_END())); sip_cseq_init(cseq)->cs_seq = 12; @@ -142,7 +140,7 @@ int test_register_to_proxy(struct context *ctx) TEST_1(e = a->events->head); TEST_1(sip = sip_object(e->data->e_msg)); - if (ctx->nat) { + if (ctx->nat && e->data->e_status == 100) { TEST_E(e->data->e_event, nua_r_register); TEST(e->data->e_status, 100); TEST(sip->sip_status->st_status, 406); @@ -184,12 +182,16 @@ int test_register_to_proxy(struct context *ctx) TEST_1(e = a->specials->head); } + test_proxy_domain_set_expiration(ctx->a.domain, 600, 3600, 36000); + if (print_headings) printf("TEST NUA-2.3.1: PASSED\n"); if (print_headings) printf("TEST NUA-2.3.2: REGISTER b\n"); + test_proxy_domain_set_expiration(ctx->b.domain, 5, 5, 10); + TEST_1(b_reg->nh = nua_handle(b->nua, b_reg, TAG_END())); /* Test application-supplied contact */ @@ -237,14 +239,14 @@ int test_register_to_proxy(struct context *ctx) if (print_headings) printf("TEST NUA-2.3.2: PASSED\n"); - if (ctx->p) { - test_proxy_close_tports(ctx->p); - test_proxy_set_expiration(ctx->p, 600, 3600, 36000); - } + test_proxy_domain_set_expiration(ctx->b.domain, 600, 3600, 36000); if (print_headings) printf("TEST NUA-2.3.3: REGISTER c\n"); + test_proxy_domain_set_expiration(ctx->c.domain, 600, 3600, 36000); + test_proxy_domain_set_authorize(ctx->c.domain, 2); + TEST_1(c_reg->nh = nua_handle(c->nua, c_reg, TAG_END())); REGISTER(c, c_reg, c_reg->nh, SIPTAG_TO(c->to), @@ -279,7 +281,12 @@ int test_register_to_proxy(struct context *ctx) TEST_1(sip = sip_object(e->data->e_msg)); TEST(sip->sip_status->st_status, 423); TEST_1(e = e->next); - TEST(e->data->e_status, 200); + if (e->data->e_status == 100 && e->data->e_event == nua_r_register) { + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(sip->sip_status->st_status, 401); + TEST_1(e = e->next); + } + TEST(e->data->e_status, 200); TEST_E(e->data->e_event, nua_r_register); TEST_1(sip = sip_object(e->data->e_msg)); TEST_1(sip->sip_contact); TEST_S(sip->sip_contact->m_display, "C"); @@ -366,6 +373,30 @@ int test_register_to_proxy(struct context *ctx) if (print_headings) printf("TEST NUA-2.3.4: PASSED\n"); + if (!ctx->p) + return 0; + + if (print_headings) + printf("TEST NUA-2.3.5: re-REGISTER when TCP connection is closed\n"); + + test_proxy_close_tports(ctx->p); + + run_b_until(ctx, -1, save_until_final_response); + + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_r_register); + if (e->data->e_status == 100) + TEST_1(e = e->next); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_expires, "3600"); + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-2.3.5: PASSED\n"); + END(); } diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c index ead1658f96..020b1f2c4e 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c @@ -814,6 +814,7 @@ static size_t change_status_to_483(void *a, void *message, size_t len); int save_until_notified_and_responded_twice(CONDITION_PARAMS); int save_until_notify_responded_twice(CONDITION_PARAMS); +int accept_subscription_until_terminated(CONDITION_PARAMS); int test_subscribe_notify_graceful(struct context *ctx) { @@ -902,7 +903,7 @@ int test_subscribe_notify_graceful(struct context *ctx) SUBSCRIBE(a, a_call, a_call->nh, TAG_END()); run_ab_until(ctx, -1, save_until_notified_and_responded_twice, - -1, save_until_notify_responded_twice); + -1, accept_subscription_until_terminated); #if 0 /* Client events: diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c index 6c005c1fcc..e441ccb5fb 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c @@ -64,9 +64,15 @@ int save_until_notified(CONDITION_PARAMS) int save_until_notified_and_responded(CONDITION_PARAMS) { save_event_in_list(ctx, event, ep, call); + if (event == nua_i_notify) ep->flags.bit0 = 1; if (event == nua_r_subscribe || event == nua_r_unsubscribe) { - if (status >= 300) + if (status == 407) { + AUTHENTICATE(ep, call, nh, + NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"), + TAG_END()); + } + else if (status >= 300) return 1; else if (status >= 200) ep->flags.bit1 = 1; diff --git a/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c b/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c index c3d539b25b..4054f18920 100644 --- a/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c +++ b/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c @@ -1264,6 +1264,67 @@ int test_media_replace(struct context *ctx) } +int test_media_reject(struct context *ctx) +{ + BEGIN(); + int n; + + soa_session_t *a, *b; + + char const *offer = NONE, *answer = NONE; + isize_t offerlen = (isize_t)-1, answerlen = (isize_t)-1; + + sdp_session_t const *b_sdp; + + char const a_caps[] = + "v=0\r\n" + "o=left 219498671 2 IN IP4 127.0.0.2\r\n" + "c=IN IP4 127.0.0.2\r\n" + "m=audio 5008 RTP/AVP 0 8 97\r\n" + "a=rtpmap:97 GSM/8000\n" + ; + + char const b_caps[] = + "m=audio 0 RTP/AVP 96 97\n" + "a=rtpmap:96 G7231/8000\n" + "a=rtpmap:97 G729/8000\n"; + + TEST_1(a = soa_create("static", ctx->root, ctx)); + TEST_1(b = soa_create("static", ctx->root, ctx)); + + TEST(soa_set_user_sdp(a, 0, a_caps, strlen(a_caps)), 1); + TEST(soa_set_user_sdp(b, 0, b_caps, strlen(b_caps)), 1); + + n = soa_generate_offer(a, 1, test_completed); TEST(n, 0); + n = soa_get_local_sdp(a, NULL, &offer, &offerlen); TEST(n, 1); + TEST_1(offer != NULL && offer != NONE); + n = soa_set_remote_sdp(b, 0, offer, offerlen); TEST(n, 1); + n = soa_get_local_sdp(b, NULL, &answer, &answerlen); TEST(n, 0); + n = soa_generate_answer(b, test_completed); TEST(n, 0); + n = soa_get_local_sdp(b, &b_sdp, &answer, &answerlen); TEST(n, 1); + TEST_1(answer != NULL && answer != NONE); + n = soa_set_remote_sdp(a, 0, answer, -1); TEST(n, 1); + n = soa_process_answer(a, test_completed); TEST(n, 0); + + TEST_1(soa_is_complete(b)); + TEST(soa_activate(b, NULL), 0); + + TEST_1(soa_is_complete(a)); + TEST(soa_activate(a, NULL), 0); + + TEST(soa_is_audio_active(a), SOA_ACTIVE_REJECTED); + TEST(soa_is_remote_audio_active(a), SOA_ACTIVE_REJECTED); + + TEST_VOID(soa_terminate(a, NULL)); + TEST_VOID(soa_terminate(b, NULL)); + + TEST_VOID(soa_destroy(a)); + TEST_VOID(soa_destroy(b)); + + END(); +} + + int test_asynch_offer_answer(struct context *ctx) { BEGIN(); @@ -1356,6 +1417,11 @@ int test_asynch_offer_answer(struct context *ctx) int test_deinit(struct context *ctx) { BEGIN(); + + su_root_destroy(ctx->root), ctx->root = NULL; + soa_destroy(ctx->a); + soa_destroy(ctx->b); + END(); } @@ -1459,6 +1525,7 @@ int main(int argc, char *argv[]) retval |= test_static_offer_answer(ctx); SINGLE_FAILURE_CHECK(); retval |= test_codec_selection(ctx); SINGLE_FAILURE_CHECK(); retval |= test_media_replace(ctx); SINGLE_FAILURE_CHECK(); + retval |= test_media_reject(ctx); SINGLE_FAILURE_CHECK(); retval |= test_asynch_offer_answer(ctx); SINGLE_FAILURE_CHECK(); } diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/sofia-sip/tport_tag.h b/libs/sofia-sip/libsofia-sip-ua/tport/sofia-sip/tport_tag.h index 500a898319..64ee4ee916 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/sofia-sip/tport_tag.h +++ b/libs/sofia-sip/libsofia-sip-ua/tport/sofia-sip/tport_tag.h @@ -225,6 +225,66 @@ TPORT_DLL extern tag_typedef_t tptag_timeout; TPORT_DLL extern tag_typedef_t tptag_timeout_ref; #define TPTAG_TIMEOUT_REF(x) tptag_timeout_ref, tag_uint_vr(&(x)) +TPORT_DLL extern tag_typedef_t tptag_keepalive; +/**Keepalive interval in milliseconds. + * + * If 0 or UINT_MAX, do not use keepalives. Default value is 0. + * + * On TCP, the keepalive if a CR-LF-CR-LF sequence. + * + * Use with tport_tcreate(), tport_tbind(), tport_set_params(), nua_create(), + * nta_agent_create(), nta_agent_add_tport(), nth_engine_create(), or + * initial nth_site_create(). + * + * @sa TPTAG_PINGPONG(), TPTAG_PONG2PING(), TPTAG_TIMEOUT(), TPTAG_IDLE() + */ +#define TPTAG_KEEPALIVE(x) tptag_keepalive, tag_uint_v((x)) + +TPORT_DLL extern tag_typedef_t tptag_keepalive_ref; +#define TPTAG_KEEPALIVE_REF(x) tptag_keepalive_ref, tag_uint_vr(&(x)) + +TPORT_DLL extern tag_typedef_t tptag_pingpong; +/**Ping-pong interval in milliseconds. + * + * If 0 or UINT_MAX, do not check for PONGs. Default value is 0. + * + * If set, the ping-pong protocol is used on TCP connections. If pinger + * sends a ping and receives no data in the specified ping-pong interval, it + * considers the connection failed and closes it. The value recommended in + * draft-ietf-sip-outbound-10 is 10 seconds (10000 milliseconds). + * + * Use with tport_tcreate(), tport_tbind(), tport_set_params(), nua_create(), + * nta_agent_create(), nta_agent_add_tport(), nth_engine_create(), or + * initial nth_site_create(). + * + * @sa TPTAG_PONG2PING(), TPTAG_KEEPALIVE(), TPTAG_TIMEOUT(), TPTAG_IDLE(), + * draft-ietf-sip-outbound-10.txt + */ +#define TPTAG_PINGPONG(x) tptag_pingpong, tag_uint_v((x)) + +TPORT_DLL extern tag_typedef_t tptag_pingpong_ref; +#define TPTAG_PINGPONG_REF(x) tptag_pingpong_ref, tag_uint_vr(&(x)) + +TPORT_DLL extern tag_typedef_t tptag_pong2ping; +/**Respond PING with PONG. + * + * If true, respond with PONG to PING. Default value is 0 (false). + * + * If set, the ping-pong protocol is used on TCP connections. If a ping (at + * least 4 whitespace characters) is received within messages, a pong + * (CR-LF) is sent in response. + * + * Use with tport_tcreate(), tport_tbind(), tport_set_params(), nua_create(), + * nta_agent_create(), nta_agent_add_tport(), nth_engine_create(), or + * initial nth_site_create(). + * + * @sa TPTAG_PINGPONG(), TPTAG_KEEPALIVE(), TPTAG_TIMEOUT(), TPTAG_IDLE() + */ +#define TPTAG_PONG2PING(x) tptag_pong2ping, tag_bool_v((x)) + +TPORT_DLL extern tag_typedef_t tptag_pong2ping_ref; +#define TPTAG_PONG2PING_REF(x) tptag_pong2ping_ref, tag_bool_vr(&(x)) + TPORT_DLL extern tag_typedef_t tptag_sigcomp_lifetime; /**Default SigComp lifetime in seconds. * diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/test_tport.c b/libs/sofia-sip/libsofia-sip-ua/tport/test_tport.c index e7355051a2..a6cf2a3ec3 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/test_tport.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/test_tport.c @@ -33,6 +33,9 @@ * @date Created: Wed Apr 3 11:25:13 2002 ppessi */ +/* always assert()s */ +#undef NDEBUG + #include "config.h" #include @@ -49,6 +52,8 @@ typedef struct tp_test_s tp_test_t; #include #include +#include "tport_internal.h" /* Get SU_DEBUG_*() */ + #include "test_class.h" #include "test_protos.h" #include "sofia-sip/msg.h" @@ -101,6 +106,9 @@ struct tp_test_s { int tt_received; msg_t *tt_rmsg; uint8_t tt_digest[SU_MD5_DIGEST_SIZE]; + + su_addrinfo_t const *tt_tcp_addr; + tport_t *tt_tcp; }; int tstflags; @@ -359,7 +367,11 @@ static void tp_test_recv(tp_test_t *tt, tt->tt_status = 1; tt->tt_received++; - if (test_msg_md5(tt, msg)) + if (msg_has_error(msg)) { + tt->tt_status = -1; + tt->tt_rtport = tp; + } + else if (test_msg_md5(tt, msg)) msg_destroy(msg); else if (tt->tt_rmsg) msg_destroy(msg); @@ -451,7 +463,7 @@ tp_stack_class_t const tp_test_class[1] = static int init_test(tp_test_t *tt) { tp_name_t myname[1] = {{ "*", "*", "*", "*", "sigcomp" }}; -#if HAVE_NETINET_SCTP_H +#if HAVE_SCTP char const * transports[] = { "udp", "tcp", "sctp", NULL }; #else char const * transports[] = { "udp", "tcp", NULL }; @@ -635,7 +647,7 @@ static int init_test(tp_test_t *tt) for (tp = tport_primaries(tt->tt_srv_tports); tp; tp = tport_next(tp)) { TEST_1(tpn = tport_name(tp)); - if (1 || tt->tt_flags & tst_verbatim) { + if (tt->tt_flags & tst_verbatim) { char const *host = tpn->tpn_host != tpn->tpn_canon ? tpn->tpn_host : ""; printf("bound transport to %s/%s:%s%s%s%s%s\n", tpn->tpn_proto, tpn->tpn_canon, tpn->tpn_port, @@ -661,6 +673,11 @@ static int init_test(tp_test_t *tt) tt->tt_tcp_name->tpn_ident = NULL; *tt->tt_tcp_comp = *tpn; tt->tt_tcp_comp->tpn_ident = NULL; + + if (tt->tt_tcp_addr == NULL) { + tt->tt_tcp_addr = tport_get_address(tp); + tt->tt_tcp = tp; + } } else if (strcmp(tpn->tpn_proto, "sctp") == 0) { *tt->tt_sctp_name = *tpn; @@ -738,7 +755,7 @@ static int udp_test(tp_test_t *tt) TEST_1(md5 = msg_content_md5_make(home, "R6nitdrtJFpxYzrPaSXfrA==")); TEST(msg_header_insert(msg, (void *)tst, (msg_header_t *)md5), 0); - + TEST_1(sep = msg_separator_create(home)); TEST(msg_header_insert(msg, (void *)tst, (msg_header_t *)sep), 0); @@ -778,23 +795,72 @@ static int udp_test(tp_test_t *tt) END(); } +int pending_server_close, pending_client_close; + +void server_closed_callback(tp_stack_t *tt, tp_client_t *client, + tport_t *tp, msg_t *msg, int error) +{ + assert(msg == NULL); + assert(client == NULL); + if (msg == NULL) { + tport_release(tp, pending_server_close, NULL, NULL, client, 0); + pending_server_close = 0; + } +} + +void client_closed_callback(tp_stack_t *tt, tp_client_t *client, + tport_t *tp, msg_t *msg, int error) +{ + assert(msg == NULL); + assert(client == NULL); + if (msg == NULL) { + tport_release(tp, pending_client_close, NULL, NULL, client, 0); + pending_client_close = 0; + } +} + static int tcp_test(tp_test_t *tt) { BEGIN(); -#ifndef WIN32 /* Windows seems to be buffering too much */ - msg_t *msg = NULL; int i; tport_t *tp, *tp0; char ident[16]; + su_time_t started; + + /* Send a single message */ + TEST_1(!new_test_msg(tt, &msg, "tcp-first", 1, 1024)); + TEST_1(tp = tport_tsend(tt->tt_tports, msg, tt->tt_tcp_name, TAG_END())); + TEST_S(tport_name(tp)->tpn_ident, "client"); + tp0 = tport_incref(tp); + msg_destroy(msg); + + tport_set_params(tp, + TPTAG_KEEPALIVE(100), + TPTAG_PINGPONG(500), + TPTAG_IDLE(500), + TAG_END()); + + TEST(tport_test_run(tt, 5), 1); + TEST_1(!check_msg(tt, tt->tt_rmsg, "tcp-first")); + msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL; + + /* Ask for notification upon close */ + pending_client_close = tport_pend(tp0, NULL, client_closed_callback, NULL); + TEST_1(pending_client_close > 0); + tp = tt->tt_rtport; + pending_server_close = tport_pend(tp, NULL, server_closed_callback, NULL); + TEST_1(pending_server_close > 0); + +#ifndef WIN32 /* Windows seems to be buffering too much */ /* Create a large message, just to force queueing in sending end */ TEST(new_test_msg(tt, &msg, "tcp-0", 1, 16 * 64 * 1024), 0); test_create_md5(tt, msg); TEST_1(tp = tport_tsend(tt->tt_tports, msg, tt->tt_tcp_name, TAG_END())); TEST_S(tport_name(tp)->tpn_ident, "client"); - tp0 = tport_incref(tp); + TEST_P(tport_incref(tp), tp0); tport_decref(&tp); msg_destroy(msg); /* Fill up the queue */ @@ -827,10 +893,12 @@ static int tcp_test(tp_test_t *tt) msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL; } +#endif + /* This uses a new connection */ TEST_1(!new_test_msg(tt, &msg, "tcp-no-reuse", 1, 1024)); TEST_1(tp = tport_tsend(tt->tt_tports, msg, tt->tt_tcp_name, - TPTAG_REUSE(0), TAG_END())); + TPTAG_REUSE(0), TAG_END())); TEST_S(tport_name(tp)->tpn_ident, "client"); TEST_1(tport_incref(tp) != tp0); tport_decref(&tp); msg_destroy(msg); @@ -838,7 +906,7 @@ static int tcp_test(tp_test_t *tt) /* This uses the old connection */ TEST_1(!new_test_msg(tt, &msg, "tcp-reuse", 1, 1024)); TEST_1(tp = tport_tsend(tt->tt_tports, msg, tt->tt_tcp_name, - TPTAG_REUSE(1), TAG_END())); + TPTAG_REUSE(1), TAG_END())); TEST_S(tport_name(tp)->tpn_ident, "client"); TEST_1(tport_incref(tp) == tp0); tport_decref(&tp); msg_destroy(msg); @@ -863,9 +931,99 @@ static int tcp_test(tp_test_t *tt) TEST_1(!check_msg(tt, tt->tt_rmsg, "tcp-last")); msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL; + TEST_1(pending_server_close && pending_client_close); + SU_DEBUG_3(("tport_test(%p): waiting for PONG timeout\n", (void *)tp0)); + + /* Wait until notifications - + client closes when no pong is received and notifys pending, + then server closes and notifys pending */ + while (pending_server_close || pending_client_close) + su_root_step(tt->tt_root, 50); + tport_decref(&tp0); -#endif + /* Again a single message */ + TEST_1(!new_test_msg(tt, &msg, "tcp-pingpong", 1, 512)); + TEST_1(tp = tport_tsend(tt->tt_tports, msg, tt->tt_tcp_name, TAG_END())); + TEST_S(tport_name(tp)->tpn_ident, "client"); + tp0 = tport_incref(tp); + msg_destroy(msg); + + tport_set_params(tp0, + TPTAG_KEEPALIVE(250), + TPTAG_PINGPONG(200), + TAG_END()); + + TEST(tport_test_run(tt, 5), 1); + TEST_1(!check_msg(tt, tt->tt_rmsg, "tcp-pingpong")); + msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL; + + /* Ask for notifications upon close */ + pending_client_close = tport_pend(tp0, NULL, client_closed_callback, NULL); + TEST_1(pending_client_close > 0); + + tp = tt->tt_rtport; + pending_server_close = tport_pend(tp, NULL, server_closed_callback, NULL); + TEST_1(pending_server_close > 0); + + /* Now server responds with pong ... */ + TEST(tport_set_params(tp, TPTAG_PONG2PING(1), TAG_END()), 1); + + started = su_now(); + + while (pending_server_close && pending_client_close) { + su_root_step(tt->tt_root, 50); + if (su_duration(su_now(), started) > 1000) + break; + } + + /* ... and we are still pending after a second */ + TEST_1(pending_client_close && pending_server_close); + TEST_1(su_duration(su_now(), started) > 1000); + + tport_shutdown(tp0, 2); + tport_unref(tp0); + + while (pending_server_close || pending_client_close) + su_root_step(tt->tt_root, 50); + + END(); +} + +static int test_incomplete(tp_test_t *tt) +{ + BEGIN(); + + su_addrinfo_t const *ai = tt->tt_tcp_addr; + su_socket_t s; + int connected; + + TEST_1(ai != NULL); + + TEST(tport_set_params(tt->tt_tcp, TPTAG_TIMEOUT(500), TAG_END()), 1); + + s = su_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + TEST_1(s != SOCKET_ERROR); + + connected = connect(s, ai->ai_addr, ai->ai_addrlen); + + su_root_step(tt->tt_root, 50); + + TEST(send(s, "F", 1, 0), 1); + su_root_step(tt->tt_root, 50); + TEST(send(s, "O", 1, 0), 1); + su_root_step(tt->tt_root, 50); + TEST(send(s, "O", 1, 0), 1); + su_root_step(tt->tt_root, 50); + TEST(send(s, " ", 1, 0), 1); + su_root_step(tt->tt_root, 50); + + tt->tt_received = 0; + TEST(tport_test_run(tt, 5), -1); + TEST(tt->tt_received, 1); + TEST_P(tt->tt_rmsg, NULL); + + su_close(s); END(); } @@ -967,34 +1125,48 @@ static int sctp_test(tp_test_t *tt) msg_t *msg = NULL; int i, n; - tport_t *tp; + tport_t *tp, *tp0; char buffer[32]; if (!tt->tt_sctp_name->tpn_proto) return 0; /* Just a small and nice message first */ - TEST_1(!new_test_msg(tt, &msg, "sctp-small", 1, 1024)); + TEST_1(!new_test_msg(tt, &msg, "cid:sctp-first", 1, 1024)); test_create_md5(tt, msg); TEST_1(tp = tport_tsend(tt->tt_tports, msg, tt->tt_sctp_name, TAG_END())); TEST_S(tport_name(tp)->tpn_ident, "client"); msg_destroy(msg); - + + tport_set_params(tp, TPTAG_KEEPALIVE(100), TPTAG_IDLE(500), TAG_END()); + TEST(tport_test_run(tt, 5), 1); TEST_1(!check_msg(tt, tt->tt_rmsg, NULL)); test_check_md5(tt, tt->tt_rmsg); msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL; - if (1) - return 0; /* SCTP does not work reliably. Really. */ + tp0 = tport_ref(tp); + + pending_server_close = pending_client_close = 0; + + /* Ask for notification upon close */ + pending_client_close = tport_pend(tp, NULL, client_closed_callback, NULL); + TEST_1(pending_client_close > 0); + tp = tt->tt_rtport; + pending_server_close = tport_pend(tp, NULL, server_closed_callback, NULL); + TEST_1(pending_server_close > 0); + + if (0) { /* SCTP does not work reliably. Really. */ + + tt->tt_received = 0; /* Create large messages, just to force queueing in sending end */ for (n = 0; !tport_queuelen(tp); n++) { snprintf(buffer, sizeof buffer, "cid:sctp-%u", n); TEST_1(!new_test_msg(tt, &msg, buffer, 1, 32000)); test_create_md5(tt, msg); - TEST_1(tport_tsend(tp, msg, tt->tt_sctp_name, TAG_END())); + TEST_1(tp = tport_tsend(tp0, msg, tt->tt_sctp_name, TAG_END())); TEST_S(tport_name(tp)->tpn_ident, "client"); msg_destroy(msg); } @@ -1003,11 +1175,11 @@ static int sctp_test(tp_test_t *tt) for (i = 1; i < TPORT_QUEUESIZE; i++) { snprintf(buffer, sizeof buffer, "cid:sctp-%u", n + i); TEST_1(!new_test_msg(tt, &msg, buffer, 1, 1024)); - TEST_1(tport_tsend(tp, msg, tt->tt_sctp_name, TAG_END())); + TEST_1(tp = tport_tsend(tp0, msg, tt->tt_sctp_name, TAG_END())); msg_destroy(msg); } - /* This overflows the queue */ + /* Try to overflow the queue */ snprintf(buffer, sizeof buffer, "cid:sctp-%u", n + i); TEST_1(!new_test_msg(tt, &msg, buffer, 1, 1024)); TEST_1(!tport_tsend(tt->tt_tports, msg, tt->tt_sctp_name, TAG_END())); @@ -1020,13 +1192,13 @@ static int sctp_test(tp_test_t *tt) msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL; /* This uses a new connection */ - TEST_1(!new_test_msg(tt, &msg, "cid-sctp-new", 1, 1024)); + TEST_1(!new_test_msg(tt, &msg, "cid:sctp-new", 1, 1024)); TEST_1(tport_tsend(tt->tt_tports, msg, tt->tt_sctp_name, TPTAG_REUSE(0), TAG_END())); msg_destroy(msg); /* Receive every message from queue */ - for (tt->tt_received = 0; tt->tt_received < TPORT_QUEUESIZE + n;) { + for (; tt->tt_received < n + TPORT_QUEUESIZE - 1;) { TEST(tport_test_run(tt, 10), 1); /* Validate message */ TEST_1(!check_msg(tt, tt->tt_rmsg, NULL)); @@ -1034,7 +1206,7 @@ static int sctp_test(tp_test_t *tt) } /* Try to send a single message */ - TEST_1(!new_test_msg(tt, &msg, "cid:sctp-final", 1, 1024)); + TEST_1(!new_test_msg(tt, &msg, "cid:sctp-final", 1, 512)); TEST_1(tport_tsend(tt->tt_tports, msg, tt->tt_sctp_name, TAG_END())); msg_destroy(msg); @@ -1042,6 +1214,15 @@ static int sctp_test(tp_test_t *tt) TEST_1(!check_msg(tt, tt->tt_rmsg, NULL)); msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL; + } + + tport_unref(tp0); + + /* Wait until notifications - + client closes when idle and notifys pending, + then server closes and notifys pending */ + while (pending_server_close || pending_client_close) + su_root_step(tt->tt_root, 50); END(); } @@ -1055,15 +1236,35 @@ static int tls_test(tp_test_t *tt) msg_t *msg = NULL; int i; char ident[16]; + tport_t *tp, *tp0; TEST_S(dst->tpn_proto, "tls"); - tt->tt_received = 0; + /* Send a single message */ + TEST_1(!new_test_msg(tt, &msg, "tls-first", 1, 1024)); + TEST_1(tp = tport_tsend(tt->tt_tports, msg, dst, TAG_END())); + TEST_1(tp0 = tport_ref(tp)); + msg_destroy(msg); - /* Create a large message, just to force queueing in sending end */ - TEST_1(!new_test_msg(tt, &msg, "tls-0", 16, 64 * 1024)); + TEST(tport_test_run(tt, 5), 1); + + TEST_1(!check_msg(tt, tt->tt_rmsg, "tls-first")); + msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL; + + tport_set_params(tp, TPTAG_KEEPALIVE(100), TPTAG_IDLE(500), TAG_END()); + + /* Ask for notification upon close */ + pending_client_close = tport_pend(tp0, NULL, client_closed_callback, NULL); + TEST_1(pending_client_close > 0); + tp = tt->tt_rtport; + pending_server_close = tport_pend(tp, NULL, server_closed_callback, NULL); + TEST_1(pending_server_close > 0); + + /* Send a largish message */ + TEST_1(!new_test_msg(tt, &msg, "tls-0", 16, 16 * 1024)); test_create_md5(tt, msg); - TEST_1(tport_tsend(tt->tt_tports, msg, dst, TAG_END())); + TEST_1(tp = tport_tsend(tt->tt_tports, msg, dst, TAG_END())); + TEST_1(tp == tp0); msg_destroy(msg); /* Fill up the queue */ @@ -1071,25 +1272,20 @@ static int tls_test(tp_test_t *tt) snprintf(ident, sizeof ident, "tls-%u", i); TEST_1(!new_test_msg(tt, &msg, ident, 2, 512)); - TEST_1(tport_tsend(tt->tt_tports, msg, dst, TAG_END())); + TEST_1(tp = tport_tsend(tt->tt_tports, msg, dst, TAG_END())); + TEST_1(tp == tp0); msg_destroy(msg); } - /* This overflows the queue */ - TEST_1(!new_test_msg(tt, &msg, "tls-overflow", 1, 1024)); - TEST_1(!tport_tsend(tt->tt_tports, msg, dst, TAG_END())); - msg_destroy(msg); - - TEST(tport_test_run(tt, 60), 1); - - msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL; - /* This uses a new connection */ TEST_1(!new_test_msg(tt, &msg, "tls-no-reuse", 1, 1024)); - TEST_1(tport_tsend(tt->tt_tports, msg, dst, + TEST_1(tp = tport_tsend(tt->tt_tports, msg, dst, TPTAG_REUSE(0), TAG_END())); + TEST_1(tp != tp0); msg_destroy(msg); + tt->tt_received = 0; + /* Receive every message from queue */ while (tt->tt_received < TPORT_QUEUESIZE + 1) { TEST(tport_test_run(tt, 5), 1); @@ -1107,6 +1303,14 @@ static int tls_test(tp_test_t *tt) TEST_1(!check_msg(tt, tt->tt_rmsg, "tls-last")); msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL; + tport_decref(&tp0); + + /* Wait until notifications - + client closes when idle and notifys pending, + then server closes and notifys pending */ + while (pending_server_close || pending_client_close) + su_root_step(tt->tt_root, 50); + #endif END(); @@ -1363,9 +1567,24 @@ static int filter_test(tp_test_t *tt) END(); } +#if HAVE_ALARM +#include + +static RETSIGTYPE sig_alarm(int s) +{ + fprintf(stderr, "%s: FAIL! test timeout!\n", name); + exit(1); +} + +char const alarm_option[] = " [--no-alarm]"; + +#else +char const alarm_option[] = ""; +#endif + void usage(int exitcode) { - fprintf(stderr, "usage: %s [-v] [-a]\n", name); + fprintf(stderr, "usage: %s [-v] [-a]%s\n", name, alarm_option); exit(exitcode); } @@ -1373,7 +1592,9 @@ int main(int argc, char *argv[]) { int flags = 0; /* XXX */ int retval = 0; + int no_alarm = 0; int i; + tp_test_t tt[1] = {{{ SU_HOME_INIT(tt) }}}; for (i = 1; argv[i]; i++) { @@ -1381,6 +1602,8 @@ int main(int argc, char *argv[]) tstflags |= tst_verbatim; else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--abort") == 0) tstflags |= tst_abort; + else if (strcmp(argv[i], "--no-alarm") == 0) + no_alarm = 1; else usage(1); } @@ -1389,6 +1612,13 @@ int main(int argc, char *argv[]) tstflags |= tst_verbatim; #endif +#if HAVE_ALARM + if (!no_alarm) { + signal(SIGALRM, sig_alarm); + alarm(120); + } +#endif + /* Use log */ if (flags & tst_verbatim) tport_log->log_default = 9; @@ -1406,6 +1636,7 @@ int main(int argc, char *argv[]) retval |= sctp_test(tt); fflush(stdout); retval |= udp_test(tt); fflush(stdout); retval |= tcp_test(tt); fflush(stdout); + retval |= test_incomplete(tt); fflush(stdout); retval |= reuse_test(tt); fflush(stdout); retval |= tls_test(tt); fflush(stdout); if (0) /* Not yet working... */ diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport.c index 66d31569d0..846ae10210 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport.c @@ -47,7 +47,7 @@ typedef struct tport_nat_s tport_nat_t; #define SU_WAKEUP_ARG_T struct tport_s -#define SU_TIMER_ARG_T struct tport_master +#define SU_TIMER_ARG_T struct tport_s #define SU_MSG_ARG_T union tport_su_msg_arg #include @@ -127,6 +127,33 @@ RBTREE_BODIES(su_inline, tprb, tport_t, TP_IS_RED, TP_SET_RED, TP_IS_BLACK, TP_SET_BLACK, TP_COPY_COLOR, tp_cmp, TP_INSERT, TP_REMOVE); +static void tplist_insert(tport_t **list, tport_t *tp) +{ + if (*list == NULL) + *list = tp; + else + tp->tp_right = *list, (*list)->tp_left = tp, *list = tp; + + for (tp = *list; tp; tp = tp->tp_right) { + assert(tp->tp_left == NULL || tp == tp->tp_left->tp_right); + assert(tp->tp_right == NULL || tp == tp->tp_right->tp_left); + } +} + +static void tplist_remove(tport_t **list, tport_t *tp) +{ + if (*list == tp) { + *list = tp->tp_right; assert(tp->tp_left == NULL); + } + else if (tp->tp_left) { + tp->tp_left->tp_right = tp->tp_right; + } + if (tp->tp_right) { + tp->tp_right->tp_left = tp->tp_left; + } + TP_REMOVE(tp); +} + enum { /** Default per-thread read queue length */ THRP_PENDING = 8 @@ -304,6 +331,12 @@ int tport_is_clear_to_send(tport_t const *self) !self->tp_send_close); } +/** Return true if transport has message in send queue. @NEW_1_12_7 */ +int tport_has_queued(tport_t const *self) +{ + return self && self->tp_queue && self->tp_queue[self->tp_qhead]; +} + /** MTU for transport */ su_inline unsigned tport_mtu(tport_t const *self) { @@ -387,9 +420,6 @@ tport_t *tport_by_addrinfo(tport_primary_t const *pri, tp_name_t const *tpn); void tport_peer_address(tport_t *self, msg_t *msg); -static unsigned long tport_now(void); - -static void tport_tick(su_root_magic_t *, su_timer_t *, tport_master_t *mr); static void tport_parse(tport_t *self, int complete, su_time_t now); @@ -410,7 +440,6 @@ static void tport_zap_primary(tport_primary_t *); static char *localipname(int pf, char *buf, size_t bufsiz); static int getprotohints(su_addrinfo_t *hints, char const *proto, int flags); -static void tport_send_queue(tport_t *self); /* Stack class used when transports are being destroyed */ @@ -454,7 +483,6 @@ tport_t *tport_tcreate(tp_stack_t *stack, tport_master_t *mr; tp_name_t *tpn; tport_params_t *tpp; - unsigned tick; ta_list ta; if (!stack || !tpac || !root) { @@ -483,6 +511,9 @@ tport_t *tport_tcreate(tp_stack_t *stack, tpp->tpp_idle = UINT_MAX; tpp->tpp_timeout = UINT_MAX; tpp->tpp_sigcomp_lifetime = UINT_MAX; + tpp->tpp_keepalive = 0; + tpp->tpp_pingpong = 0; + tpp->tpp_pong2ping = 0; tpp->tpp_stun_server = 1; tpp->tpp_tos = -1; /* set invalid, valid values are 0-255 */ @@ -497,21 +528,10 @@ tport_t *tport_tcreate(tp_stack_t *stack, tport_set_params(mr->mr_master, ta_tags(ta)); tport_open_log(mr, ta_args(ta)); - tick = 5000; /* For testing, usually 30000 is enough */ - if (tpp->tpp_idle < 4 * tick) - tick = tpp->tpp_idle / 4; - if (tpp->tpp_timeout < 4 * tick) - tick = tpp->tpp_timeout / 4; - if (tick < 200) - tick = 200; - #if HAVE_SOFIA_STUN tport_init_stun_server(mr, ta_args(ta)); #endif - mr->mr_timer = su_timer_create(su_root_task(root), tick); - su_timer_set(mr->mr_timer, tport_tick, mr); - ta_end(ta); return mr->mr_master; @@ -640,8 +660,10 @@ void tport_zap_primary(tport_primary_t *pri) if (pri->pri_vtable->vtp_deinit_primary) pri->pri_vtable->vtp_deinit_primary(pri); - while (pri->pri_secondary) - tport_zap_secondary(pri->pri_secondary); + while (pri->pri_open) + tport_zap_secondary(pri->pri_open); + while (pri->pri_closed) + tport_zap_secondary(pri->pri_closed); /* We have just a single-linked list for primary transports */ for (prip = &pri->pri_master->mr_primaries; @@ -651,7 +673,7 @@ void tport_zap_primary(tport_primary_t *pri) *prip = pri->pri_next; - tport_zap_secondary(pri->pri_primary); + tport_zap_secondary((tport_t *)pri); } /**Create a primary transport object with socket. @@ -724,7 +746,7 @@ tport_primary_t *tport_listen(tport_master_t *mr, pri->pri_primary->tp_has_connection = 0; SU_DEBUG_5(("%s(%p): %s " TPN_FORMAT "\n", - __func__, (void *)pri, "listening at", + __func__, (void *)pri, "listening at", TPN_ARGS(pri->pri_primary->tp_name))); return pri; @@ -812,9 +834,8 @@ int tport_set_events(tport_t *self, int set, int clear) /**Allocate a secondary transport. @internal * - * The function tport_alloc_secondary() creates a secondary transport - * object. The new transport initally shares parameters structure with the - * original transport. + * Create a secondary transport object. The new transport initally shares + * parameters structure with the original transport. * * @param pri primary transport * @param socket socket for transport @@ -836,7 +857,8 @@ tport_t *tport_alloc_secondary(tport_primary_t *pri, self = su_home_clone(mr->mr_home, pri->pri_vtable->vtp_secondary_size); if (self) { - SU_DEBUG_7(("%s(%p): new secondary tport %p\n", __func__, (void *)pri, (void *)self)); + SU_DEBUG_7(("%s(%p): new secondary tport %p\n", + __func__, (void *)pri, (void *)self)); self->tp_refs = -1; /* Freshly allocated */ self->tp_master = mr; @@ -850,7 +872,10 @@ tport_t *tport_alloc_secondary(tport_primary_t *pri, self->tp_addrinfo->ai_addr = (void *)self->tp_addr; self->tp_socket = socket; - + + self->tp_timer = su_timer_create(su_root_task(mr->mr_root), 0); + self->tp_stime = self->tp_ktime = self->tp_rtime = su_now(); + if (pri->pri_vtable->vtp_init_secondary && pri->pri_vtable->vtp_init_secondary(self, socket, accepted, return_reason) < 0) { @@ -889,13 +914,18 @@ tport_t *tport_connect(tport_primary_t *pri, su_addrinfo_t *ai, tp_name_t const *tpn) { + tport_t *tp; + if (ai == NULL || ai->ai_addrlen > sizeof (pri->pri_primary->tp_addr)) return NULL; if (pri->pri_vtable->vtp_connect) return pri->pri_vtable->vtp_connect(pri, ai, tpn); - else - return tport_base_connect(pri, ai, ai, tpn); + + tp = tport_base_connect(pri, ai, ai, tpn); + if (tp) + tport_set_secondary_timer(tp); + return tp; } /**Create a connected transport object with socket. @@ -961,14 +991,14 @@ tport_t *tport_base_connect(tport_primary_t *pri, /* Bind this socket to same IP address as the primary server socket */ if (getsockname(server_socket, &susa.su_sa, &susalen) < 0) { - SU_DEBUG_3(("tport_connect: getsockname(): %s\n", - su_strerror(su_errno()))); + SU_DEBUG_3(("%s(%p): getsockname(): %s\n", + __func__, (void *)self, su_strerror(su_errno()))); } else { susa.su_port = 0; if (bind(s, &susa.su_sa, susalen) < 0) { - SU_DEBUG_3(("tport_connect: bind(local-ip): %s\n", - su_strerror(su_errno()))); + SU_DEBUG_3(("%s(%p): bind(local-ip): %s\n", + __func__, (void *)self, su_strerror(su_errno()))); } } } @@ -1005,16 +1035,16 @@ tport_t *tport_base_connect(tport_primary_t *pri, if (ai == real_ai) { SU_DEBUG_5(("%s(%p): %s to " TPN_FORMAT "\n", - __func__, (void *)self, what, TPN_ARGS(self->tp_name))); + __func__, (void *)self, what, TPN_ARGS(self->tp_name))); } else { SU_DEBUG_5(("%s(%p): %s via %s to " TPN_FORMAT "\n", - __func__, (void *)self, what, + __func__, (void *)self, what, tport_hostport(buf, sizeof(buf), (void *)ai->ai_addr, 2), TPN_ARGS(self->tp_name))); } - - tprb_append(&pri->pri_secondary, self); + + tprb_append(&pri->pri_open, self); return self; } @@ -1028,7 +1058,13 @@ void tport_zap_secondary(tport_t *self) return; /* Remove from rbtree */ - tprb_remove(&self->tp_pri->pri_secondary, self); + if (!tport_is_closed(self)) + tprb_remove(&self->tp_pri->pri_open, self); + else + tplist_remove(&self->tp_pri->pri_closed, self); + + if (self->tp_timer) + su_timer_destroy(self->tp_timer), self->tp_timer = NULL; /* Do not deinit primary as secondary! */ if (tport_is_secondary(self) && @@ -1038,21 +1074,22 @@ void tport_zap_secondary(tport_t *self) if (self->tp_msg) { msg_destroy(self->tp_msg), self->tp_msg = NULL; SU_DEBUG_3(("%s(%p): zapped partially received message\n", - __func__, (void *)self)); + __func__, (void *)self)); } - if (self->tp_queue && self->tp_queue[self->tp_qhead]) { + if (tport_has_queued(self)) { size_t n = 0, i, N = self->tp_params->tpp_qsize; for (i = self->tp_qhead; self->tp_queue[i]; i = (i + 1) % N) { msg_destroy(self->tp_queue[i]), self->tp_queue[i] = NULL; n++; } SU_DEBUG_3(("%s(%p): zapped %lu queued messages\n", - __func__, (void *)self, (LU)n)); + __func__, (void *)self, (LU)n)); } if (self->tp_pused) { - SU_DEBUG_3(("%s(%p): zapped with pending requests\n", __func__, (void *)self)); + SU_DEBUG_3(("%s(%p): zapped while pending\n", + __func__, (void *)self)); } mr = self->tp_master; @@ -1086,10 +1123,18 @@ tport_t *tport_ref(tport_t *tp) /** Destroy reference to a transport object. */ void tport_unref(tport_t *tp) { - if (tp && tp->tp_refs > 0) - if (--tp->tp_refs == 0 && tp->tp_params->tpp_idle == 0) - if (!tport_is_closed(tp)) - tport_close(tp); + if (tp == NULL || tp->tp_refs <= 0) + return; + if (--tp->tp_refs > 0) + return; + + if (!tport_is_secondary(tp)) + return; + + if (tp->tp_params->tpp_idle == 0) + tport_close(tp); + + tport_set_secondary_timer(tp); } /** Create a new reference to transport object. */ @@ -1113,6 +1158,14 @@ void tport_decref(tport_t **ttp) * * @param self pointer to a transport object * @param tag,value,... list of tags + * + * @TAGS + * TPTAG_MTU_REF(), TPTAG_QUEUESIZE_REF(), TPTAG_IDLE_REF(), + * TPTAG_TIMEOUT_REF(), TPTAG_KEEPALIVE_REF(), TPTAG_PINGPONG_REF(), + * TPTAG_PONG2PING_REF(), TPTAG_DEBUG_DROP_REF(), TPTAG_THRPSIZE_REF(), + * TPTAG_THRPRQSIZE_REF(), TPTAG_SIGCOMP_LIFETIME_REF(), + * TPTAG_CONNECT_REF(), TPTAG_SDWN_ERROR_REF(), TPTAG_REUSE_REF(), + * TPTAG_STUN_SERVER_REF(), TPTAG_PUBLIC_REF() and TPTAG_TOS_REF(). */ int tport_get_params(tport_t const *self, tag_type_t tag, tag_value_t value, ...) @@ -1140,10 +1193,15 @@ int tport_get_params(tport_t const *self, TPTAG_QUEUESIZE(tpp->tpp_qsize), TPTAG_IDLE(tpp->tpp_idle), TPTAG_TIMEOUT(tpp->tpp_timeout), + TPTAG_KEEPALIVE(tpp->tpp_keepalive), + TPTAG_PINGPONG(tpp->tpp_pingpong), + TPTAG_PONG2PING(tpp->tpp_pong2ping), TPTAG_SDWN_ERROR(tpp->tpp_sdwn_error), TPTAG_DEBUG_DROP(tpp->tpp_drop), TPTAG_THRPSIZE(tpp->tpp_thrpsize), TPTAG_THRPRQSIZE(tpp->tpp_thrprqsize), + TPTAG_SIGCOMP_LIFETIME(tpp->tpp_sigcomp_lifetime), + TPTAG_STUN_SERVER(tpp->tpp_stun_server), TAG_IF(pri, TPTAG_PUBLIC(pri ? pri->pri_public : 0)), TPTAG_TOS(tpp->tpp_tos), TAG_END()); @@ -1160,6 +1218,7 @@ int tport_get_params(tport_t const *self, * * @TAGS * TPTAG_MTU(), TPTAG_QUEUESIZE(), TPTAG_IDLE(), TPTAG_TIMEOUT(), + * TPTAG_KEEPALIVE(), TPTAG_PINGPONG(), TPTAG_PONG2PING(), * TPTAG_DEBUG_DROP(), TPTAG_THRPSIZE(), TPTAG_THRPRQSIZE(), * TPTAG_SIGCOMP_LIFETIME(), TPTAG_CONNECT(), TPTAG_SDWN_ERROR(), * TPTAG_REUSE(), TPTAG_STUN_SERVER(), and TPTAG_TOS(). @@ -1172,7 +1231,7 @@ int tport_set_params(tport_t *self, tport_params_t tpp[1], *tpp0; usize_t mtu; - int connect, sdwn_error, reusable, stun_server; + int connect, sdwn_error, reusable, stun_server, pong2ping; if (self == NULL) return su_seterrno(EINVAL); @@ -1184,6 +1243,7 @@ int tport_set_params(tport_t *self, sdwn_error = tpp->tpp_sdwn_error; reusable = self->tp_reusable; stun_server = tpp->tpp_stun_server; + pong2ping = tpp->tpp_pong2ping; ta_start(ta, tag, value); @@ -1192,6 +1252,9 @@ int tport_set_params(tport_t *self, TAG_IF(!self->tp_queue, TPTAG_QUEUESIZE_REF(tpp->tpp_qsize)), TPTAG_IDLE_REF(tpp->tpp_idle), TPTAG_TIMEOUT_REF(tpp->tpp_timeout), + TPTAG_KEEPALIVE_REF(tpp->tpp_keepalive), + TPTAG_PINGPONG_REF(tpp->tpp_pingpong), + TPTAG_PONG2PING_REF(pong2ping), TPTAG_DEBUG_DROP_REF(tpp->tpp_drop), TPTAG_THRPSIZE_REF(tpp->tpp_thrpsize), TPTAG_THRPRQSIZE_REF(tpp->tpp_thrprqsize), @@ -1208,10 +1271,10 @@ int tport_set_params(tport_t *self, if (n == 0) return 0; - if (tpp->tpp_idle > 0 && tpp->tpp_idle < 2000) - tpp->tpp_idle = 2000; - if (tpp->tpp_timeout < 1000) - tpp->tpp_timeout = 1000; + if (tpp->tpp_idle > 0 && tpp->tpp_idle < 100) + tpp->tpp_idle = 100; + if (tpp->tpp_timeout < 100) + tpp->tpp_timeout = 100; if (tpp->tpp_drop > 1000) tpp->tpp_drop = 1000; if (tpp->tpp_thrprqsize > 0) @@ -1229,6 +1292,10 @@ int tport_set_params(tport_t *self, tpp->tpp_sdwn_error = sdwn_error; self->tp_reusable = reusable; tpp->tpp_stun_server = stun_server; + tpp->tpp_pong2ping = pong2ping; + + if (memcmp(tpp0, tpp, sizeof tpp) == 0) + return n; if (tport_is_secondary(self) && self->tp_params == self->tp_pri->pri_primary->tp_params) { @@ -1238,6 +1305,9 @@ int tport_set_params(tport_t *self, memcpy(tpp0, tpp, sizeof tpp); + if (tport_is_secondary(self)) + tport_set_secondary_timer(self); + return n; } @@ -1441,7 +1511,8 @@ int tport_bind_client(tport_master_t *mr, if (public == tport_type_local) public = tport_type_client; - SU_DEBUG_5(("%s(%p) to " TPN_FORMAT "\n", __func__, (void *)mr, TPN_ARGS(tpn))); + SU_DEBUG_5(("%s(%p) to " TPN_FORMAT "\n", + __func__, (void *)mr, TPN_ARGS(tpn))); memset(tpn0, 0, sizeof(tpn0)); @@ -1503,7 +1574,8 @@ int tport_bind_server(tport_master_t *mr, (void)hostname; - SU_DEBUG_5(("%s(%p) to " TPN_FORMAT "\n", __func__, (void *)mr, TPN_ARGS(tpn))); + SU_DEBUG_5(("%s(%p) to " TPN_FORMAT "\n", + __func__, (void *)mr, TPN_ARGS(tpn))); if (tpn->tpn_host == NULL || strcmp(tpn->tpn_host, tpn_any) == 0) { /* Use a local IP address */ @@ -1618,7 +1690,7 @@ int tport_bind_server(tport_master_t *mr, break; SU_DEBUG_3(("%s(%p): cannot bind all transports to port %u, trying %u\n", - __func__, (void *)mr, old, port)); + __func__, (void *)mr, old, port)); } tport_freeaddrinfo(res); @@ -1723,7 +1795,8 @@ int tport_server_addrinfo(tport_master_t *mr, int error = tport_getaddrinfo(host, service, hints, return_addrinfo); if (error || !*return_addrinfo) { SU_DEBUG_3(("%s(%p): su_getaddrinfo(%s, %s) for %s: %s\n", - __func__, (void *)mr, host ? host : "\"\"", service, protocol, + __func__, (void *)mr, + host ? host : "\"\"", service, protocol, su_gai_strerror(error))); return su_seterrno(error != EAI_MEMORY ? ENOENT : ENOMEM); } @@ -1774,13 +1847,13 @@ tport_get_local_addrinfo(tport_master_t *mr, if (error) { #if SU_HAVE_IN6 SU_DEBUG_3(("%s(%p): su_getlocalinfo() for %s address: %s\n", - __func__, (void *)mr, + __func__, (void *)mr, family == AF_INET6 ? "ip6" : family == AF_INET ? "ip4" : "ip", su_gli_strerror(error))); #else SU_DEBUG_3(("%s(%p): su_getlocalinfo() for %s address: %s\n", - __func__, (void *)mr, + __func__, (void *)mr, family == AF_INET ? "ip4" : "ip", su_gli_strerror(error))); #endif @@ -1972,13 +2045,19 @@ int tport_addrinfo_copy(su_addrinfo_t *dst, void *addr, socklen_t addrlen, /** Close a transport. * - * The function tport_close() closes a socket associated with a transport - * object. + * Close the socket associated with a transport object. Report an error to + * all pending clients, if required. Set/reset timer, too. */ void tport_close(tport_t *self) { - SU_DEBUG_5(("%s(%p): " TPN_FORMAT "\n", "tport_close", (void *)self, - TPN_ARGS(self->tp_name))); + SU_DEBUG_5(("%s(%p): " TPN_FORMAT "\n", + __func__, (void *)self, TPN_ARGS(self->tp_name))); + + if (self->tp_closed || !tport_is_secondary(self)) + return; + + tprb_remove(&self->tp_pri->pri_open, self); + tplist_insert(&self->tp_pri->pri_closed, self); self->tp_closed = 1; self->tp_send_close = 3; @@ -2009,7 +2088,7 @@ void tport_close(tport_t *self) msg_ref_destroy(self->tp_queue[i]), self->tp_queue[i] = NULL; } } - + self->tp_index = 0; self->tp_events = 0; } @@ -2024,16 +2103,23 @@ void tport_close(tport_t *self) */ int tport_shutdown(tport_t *self, int how) { + int retval; if (!tport_is_secondary(self)) return -1; - - SU_DEBUG_7(("%s(%p, %d)\n", "tport_shutdown", (void *)self, how)); + retval = tport_shutdown0(self, how); + tport_set_secondary_timer(self); + return retval; +} + +/** Internal shutdown function */ +int tport_shutdown0(tport_t *self, int how) +{ + SU_DEBUG_7(("%s(%p, %d)\n", __func__, (void *)self, how)); if (!tport_is_tcp(self) || - how < 0 || + how < 0 || how >= 2 || (how == 0 && self->tp_send_close) || - (how == 1 && self->tp_recv_close > 1) || - how >= 2) { + (how == 1 && self->tp_recv_close > 1)) { tport_close(self); return 1; } @@ -2052,7 +2138,7 @@ int tport_shutdown(tport_t *self, int how) else if (how == 1) { self->tp_send_close = 2; tport_set_events(self, 0, SU_WAIT_OUT); - if (self->tp_queue && self->tp_queue[self->tp_qhead]) { + if (tport_has_queued(self)) { unsigned short i, N = self->tp_params->tpp_qsize; for (i = 0; i < N; i++) { if (self->tp_queue[i]) { @@ -2063,107 +2149,143 @@ int tport_shutdown(tport_t *self, int how) } } + return 0; +} + +static void tport_secondary_timer(su_root_magic_t *magic, + su_timer_t *t, + tport_t *self) +{ + su_time_t now; + + if (tport_is_closed(self)) { + if (self->tp_refs == 0) + tport_zap_secondary(self); + return; + } + + now = /* su_timer_expired(t); */ su_now(); + + if (self->tp_pri->pri_vtable->vtp_secondary_timer) + self->tp_pri->pri_vtable->vtp_secondary_timer(self, now); + else + tport_base_timer(self, now); +} + +/** Base timer for secondary transports. + * + * Closes and zaps unused transports. Sets the timer again. + */ +void tport_base_timer(tport_t *self, su_time_t now) +{ + unsigned timeout = self->tp_params->tpp_idle; + + if (timeout != UINT_MAX) { + if (self->tp_refs == 0 && + self->tp_msg == NULL && + !tport_has_queued(self) && + su_time_cmp(su_time_add(self->tp_rtime, timeout), now) < 0 && + su_time_cmp(su_time_add(self->tp_stime, timeout), now) < 0) { + SU_DEBUG_7(("%s(%p): unused for %d ms,%s zapping\n", + __func__, (void *)self, + timeout, tport_is_closed(self) ? "" : " closing and")); + if (!tport_is_closed(self)) + tport_close(self); + tport_zap_secondary(self); + return; + } + } + + tport_set_secondary_timer(self); +} + +/** Set timer for a secondary transport. + * + * This function should be called after any network activity: + * tport_base_connect(), tport_send_msg(), tport_send_queue(), + * tport_recv_data(), tport_shutdown0(), tport_close(), + * + * @retval 0 always + */ +int tport_set_secondary_timer(tport_t *self) +{ + su_time_t const infinity = { ULONG_MAX, 999999 }; + su_time_t target = infinity; + char const *why = "not specified"; + su_timer_f timer = tport_secondary_timer; + + if (!tport_is_secondary(self)) + return 0; + + if (tport_is_closed(self)) { + if (self->tp_refs == 0) { + SU_DEBUG_7(("tport(%p): set timer at %u ms because %s\n", + self, 0, "zap")); + su_timer_set_interval(self->tp_timer, timer, self, 0); + } + else + su_timer_reset(self->tp_timer); + + return 0; + } + + if (self->tp_params->tpp_idle != UINT_MAX) { + if (self->tp_refs == 0 && + self->tp_msg == NULL && !tport_has_queued(self)) { + if (su_time_cmp(self->tp_stime, self->tp_rtime) < 0) { + target = su_time_add(self->tp_rtime, self->tp_params->tpp_idle); + why = "idle since recv"; + } + else { + target = su_time_add(self->tp_stime, self->tp_params->tpp_idle); + why = "idle since send"; + } + } + } + + if (self->tp_pri->pri_vtable->vtp_next_secondary_timer) + self->tp_pri->pri_vtable-> + vtp_next_secondary_timer(self, &target, &why); + + if (su_time_cmp(target, infinity)) { + SU_DEBUG_7(("tport(%p): set timer at %ld ms because %s\n", + (void *)self, su_duration(target, su_now()), why)); + su_timer_set_at(self->tp_timer, timer, self, target); + } + else { + SU_DEBUG_9(("tport(%p): reset timer\n", (void *)self)); + su_timer_reset(self->tp_timer); + } return 0; } -su_inline -unsigned long tport_now(void) -{ - return su_now().tv_sec; -} - -/** Transport timer function. */ -static -void tport_tick(su_root_magic_t *magic, su_timer_t *t, tport_master_t *mr) -{ - tport_primary_t *dad; - tport_t *tp, *tp_next; - su_time_t now = su_now(); - int ts = (int)su_time_ms(now); - - /* Go through all primary transports */ - for (dad = mr->mr_primaries; dad; dad = dad->pri_next) { - if (dad->pri_primary->tp_addrinfo->ai_protocol == IPPROTO_SCTP) { - /* Go through all SCTP connections */ - - tp = dad->pri_secondary; - - for (tp = tprb_first(tp); tp; tp = tp_next) { - tp_next = tprb_succ(tp); - if (tp->tp_queue && tp->tp_queue[tp->tp_qhead]) { - SU_DEBUG_9(("tport_tick(%p) - trying to send to %s/%s:%s\n", - (void *)tp, tp->tp_protoname, tp->tp_host, tp->tp_port)); - tport_send_queue(tp); - } - } - } - - /* Go through all secondary transports with incomplete messages */ - for (tp = tprb_first(dad->pri_secondary); tp; tp = tp_next) { - msg_t *msg = tp->tp_msg; - int closed; - - if (msg && - tp->tp_params->tpp_timeout < INT_MAX && - (int)tp->tp_params->tpp_timeout < ts - (int)tp->tp_time && - !msg_is_streaming(msg)) { - SU_DEBUG_5(("tport_tick(%p): incomplete message idle for %d ms\n", - (void *)tp, ts - (int)tp->tp_time)); - msg_set_streaming(msg, 0); - msg_set_flags(msg, MSG_FLG_ERROR | MSG_FLG_TRUNC | MSG_FLG_TIMEOUT); - tport_deliver(tp, msg, NULL, NULL, now); - tp->tp_msg = NULL; - } - - tp_next = tprb_succ(tp); - - if (tp->tp_refs) - continue; - - closed = tport_is_closed(tp); - - if (!closed && - !(tp->tp_params->tpp_idle > 0 - && (int)tp->tp_params->tpp_idle < ts - (int)tp->tp_time)) { - continue; - } - - if (closed) { - SU_DEBUG_5(("tport_tick(%p): closed, zapping\n", (void *)tp)); - } else { - SU_DEBUG_5(("tport_tick(%p): unused for %d ms, closing and zapping\n", - (void *)tp, ts - (int)tp->tp_time)); - if (!tport_is_closed(tp)) - tport_close(tp); - } - - tport_zap_secondary(tp); - } - } - - su_timer_set(t, tport_tick, mr); -} /** Flush idle connections. */ int tport_flush(tport_t *tp) { tport_t *tp_next; + tport_primary_t *pri; if (tp == NULL) return -1; + pri = tp->tp_pri; + + while (pri->pri_closed) + tport_zap_secondary(pri->pri_closed); + /* Go through all secondary transports, zap idle ones */ - for (tp = tprb_first(tp->tp_pri->pri_secondary); tp; tp = tp_next) { + for (tp = tprb_first(tp->tp_pri->pri_open); tp; tp = tp_next) { tp_next = tprb_succ(tp); if (tp->tp_refs != 0) continue; SU_DEBUG_1(("tport_flush(%p): %szapping\n", - (void *)tp, tport_is_closed(tp) ? "" : "closing and ")); - if (!tport_is_closed(tp)) - tport_close(tp); + (void *)tp, tport_is_closed(tp) ? "" : "closing and ")); + + tport_close(tp); tport_zap_secondary(tp); } @@ -2401,10 +2523,10 @@ void tport_error_report(tport_t *self, int errcode, } else { if (tport_is_primary(self)) - SU_DEBUG_3(("%s(%p): %s (with %s)\n", __func__, (void *)self, + SU_DEBUG_3(("%s(%p): %s (with %s)\n", __func__, (void *)self, errmsg, self->tp_protoname)); else - SU_DEBUG_3(("%s(%p): %s (with %s/%s:%s)\n", __func__, (void *)self, + SU_DEBUG_3(("%s(%p): %s (with %s/%s:%s)\n", __func__, (void *)self, errmsg, self->tp_protoname, self->tp_host, self->tp_port)); } @@ -2470,9 +2592,9 @@ int tport_accept(tport_primary_t *pri, int events) if (tport_setname(self, pri->pri_protoname, ai, NULL) != -1) { SU_DEBUG_5(("%s(%p): new connection from " TPN_FORMAT "\n", - __func__, (void *)self, TPN_ARGS(self->tp_name))); + __func__, (void *)self, TPN_ARGS(self->tp_name))); - tprb_append(&pri->pri_secondary, self); + tprb_append(&pri->pri_open, self); /* Return succesfully */ return 0; @@ -2550,15 +2672,20 @@ static int tport_connected(su_root_magic_t *magic, su_wait_t *w, tport_t *self) su_root_deregister(mr->mr_root, self->tp_index); self->tp_index = -1; self->tp_events = SU_WAIT_IN | SU_WAIT_ERR | SU_WAIT_HUP; + if (su_wait_create(wait, self->tp_socket, self->tp_events) == -1 || (self->tp_index = su_root_register(mr->mr_root, wait, tport_wakeup, self, 0)) == -1) { tport_close(self); + tport_set_secondary_timer(self); + return 0; } - else if (self->tp_queue && self->tp_queue[self->tp_qhead]) { + + if (tport_has_queued(self)) tport_send_event(self); - } + else + tport_set_secondary_timer(self); return 0; } @@ -2574,7 +2701,7 @@ static int tport_wakeup_pri(su_root_magic_t *m, su_wait_t *w, tport_t *self) #endif SU_DEBUG_7(("%s(%p): events%s%s%s%s%s%s\n", - "tport_wakeup_pri", (void *)self, + "tport_wakeup_pri", (void *)self, events & SU_WAIT_IN ? " IN" : "", SU_WAIT_ACCEPT != SU_WAIT_IN && (events & SU_WAIT_ACCEPT) ? " ACCEPT" : "", @@ -2600,7 +2727,7 @@ static int tport_wakeup(su_root_magic_t *magic, su_wait_t *w, tport_t *self) #endif SU_DEBUG_7(("%s(%p): events%s%s%s%s%s\n", - "tport_wakeup", (void *)self, + "tport_wakeup", (void *)self, events & SU_WAIT_IN ? " IN" : "", events & SU_WAIT_OUT ? " OUT" : "", events & SU_WAIT_HUP ? " HUP" : "", @@ -2629,8 +2756,12 @@ static int tport_base_wakeup(tport_t *self, int events) if ((events & SU_WAIT_HUP) && !self->tp_closed) tport_hup_event(self); - if (error) + if (error) { + if (self->tp_closed && error == EPIPE) + return 0; + tport_error_report(self, error, NULL); + } return 0; } @@ -2654,7 +2785,7 @@ int tport_continue(tport_t *self) */ void tport_hup_event(tport_t *self) { - SU_DEBUG_7(("%s(%p)\n", __func__, (void *)self)); + SU_DEBUG_7(("%s(%p)\n", __func__, (void *)self)); if (self->tp_msg) { su_time_t now = su_now(); @@ -2662,8 +2793,12 @@ void tport_hup_event(tport_t *self) tport_parse(self, 1, now); } + if (!tport_is_secondary(self)) + return; + /* End of stream */ - tport_shutdown(self, 0); + tport_shutdown0(self, 0); + tport_set_secondary_timer(self); } /** Receive data available on the socket. @@ -2685,18 +2820,15 @@ int tport_recv_data(tport_t *self) */ void tport_recv_event(tport_t *self) { - su_time_t now; int again; SU_DEBUG_7(("%s(%p)\n", "tport_recv_event", (void *)self)); do { - now = su_now(); - /* Receive data from socket */ again = tport_recv_data(self); - self->tp_time = su_time_ms(now); + su_time(&self->tp_rtime); #if HAVE_SOFIA_STUN if (again == 3) /* STUN keepalive */ @@ -2708,9 +2840,6 @@ void tport_recv_event(tport_t *self) if (!su_is_blocking(error)) { tport_error_report(self, error, NULL); - /* Failure: shutdown socket */ - if (tport_has_connection(self)) - tport_close(self); return; } else { @@ -2720,17 +2849,22 @@ void tport_recv_event(tport_t *self) } if (again >= 0) - tport_parse(self, !again, now); + tport_parse(self, !again, self->tp_rtime); } while (again > 1); + if (!tport_is_secondary(self)) + return; + if (again == 0 && !tport_is_dgram(self)) { /* End of stream */ if (!self->tp_closed) { /* Don't shutdown completely if there are queued messages */ - tport_shutdown(self, self->tp_queue && self->tp_queue[self->tp_qhead] ? 0 : 2); + tport_shutdown0(self, tport_has_queued(self) ? 0 : 2); } } + + tport_set_secondary_timer(self); } /* @@ -2757,7 +2891,7 @@ static void tport_parse(tport_t *self, int complete, su_time_t now) if (msg_get_flags(msg, MSG_FLG_TOOLARGE)) SU_DEBUG_3(("%s(%p): too large message from " TPN_FORMAT "\n", - __func__, (void *)self, TPN_ARGS(self->tp_name))); + __func__, (void *)self, TPN_ARGS(self->tp_name))); /* Do not try to read anymore from this connection? */ if (tport_is_stream(self) && @@ -2934,7 +3068,7 @@ ssize_t tport_recv_iovec(tport_t const *self, if (!(*in_out_msg = msg = tport_msg_alloc(self, N))) { SU_DEBUG_7(("%s(%p): cannot allocate msg for "MOD_ZU" bytes " "from (%s/%s:%s)\n", - __func__, (void *)self, N, + __func__, (void *)self, N, self->tp_protoname, self->tp_host, self->tp_port)); return -1; } @@ -2953,7 +3087,7 @@ ssize_t tport_recv_iovec(tport_t const *self, int err = su_errno(); SU_DEBUG_7(("%s(%p): cannot get msg %p buffer for "MOD_ZU" bytes " "from (%s/%s:%s): %s\n", - __func__, (void *)self, (void *)msg, N, + __func__, (void *)self, (void *)msg, N, self->tp_protoname, self->tp_host, self->tp_port, su_strerror(err))); su_seterrno(err); @@ -2965,7 +3099,7 @@ ssize_t tport_recv_iovec(tport_t const *self, SU_DEBUG_7(("%s(%p) msg %p from (%s/%s:%s) has "MOD_ZU" bytes, " "veclen = "MOD_ZD"\n", __func__, (void *)self, - (void *)msg, self->tp_protoname, self->tp_host, self->tp_port, + (void *)msg, self->tp_protoname, self->tp_host, self->tp_port, N, veclen)); for (i = 0; veclen > 1 && i < veclen; i++) { @@ -3070,7 +3204,7 @@ tport_t *tport_tsend(tport_t *self, if (tpn->tpn_comp) { ai->ai_flags |= TP_AI_COMPRESSED; SU_DEBUG_9(("%s: compressed msg(%p) with %s\n", - __func__, (void *)msg, tpn->tpn_comp)); + __func__, (void *)msg, tpn->tpn_comp)); } if (!tpn->tpn_comp || cc == NONE) @@ -3168,6 +3302,8 @@ int tport_prepare_and_send(tport_t *self, msg_t *msg, struct sigcomp_compartment *cc, unsigned mtu) { + int retval; + /* Prepare message for sending - i.e., encode it */ if (msg_prepare(msg) < 0) { msg_set_errno(msg, errno); @@ -3193,7 +3329,11 @@ int tport_prepare_and_send(tport_t *self, msg_t *msg, return 0; } - return tport_send_msg(self, msg, tpn, cc); + retval = tport_send_msg(self, msg, tpn, cc); + + tport_set_secondary_timer(self); + + return retval; } @@ -3243,7 +3383,7 @@ int tport_send_msg(tport_t *self, msg_t *msg, assert(iovused > 0); - self->tp_time = su_time_ms(now = su_now()); + self->tp_stime = self->tp_ktime = now = su_now(); nerror = tport_vsend(self, msg, tpn, iov, iovused, cc); SU_DEBUG_9(("tport_vsend returned "MOD_ZD"\n", nerror)); @@ -3272,7 +3412,7 @@ int tport_send_msg(tport_t *self, msg_t *msg, char const *comp = tpn->tpn_comp; SU_DEBUG_1(("tport(%p): send truncated for %s/%s:%s%s%s\n", - (void *)self, tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port, + (void *)self, tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port, comp ? ";comp=" : "", comp ? comp : "")); su_seterrno(EIO); @@ -3288,14 +3428,17 @@ int tport_send_msg(tport_t *self, msg_t *msg, self->tp_slogged = NULL; self->tp_stats.sent_msgs ++; + if (!tport_is_secondary(self)) + return 0; + ai = msg_addrinfo(msg); assert(ai); close_after = (ai->ai_flags & TP_AI_CLOSE) == TP_AI_CLOSE; sdwn_after = (ai->ai_flags & TP_AI_SHUTDOWN) == TP_AI_SHUTDOWN || self->tp_send_close; if (close_after || sdwn_after) - tport_shutdown(self, close_after ? 2 : 1); - + tport_shutdown0(self, close_after ? 2 : 1); + return 0; } @@ -3339,7 +3482,7 @@ ssize_t tport_vsend(tport_t *self, tpn = self->tp_name; SU_DEBUG_7(("tport_vsend(%p): "MOD_ZU" bytes of "MOD_ZU" to %s/%s:%s%s\n", - (void *)self, n, m, tpn->tpn_proto, tpn->tpn_host, + (void *)self, n, m, tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port, (ai->ai_flags & TP_AI_COMPRESSED) ? ";comp=sigcomp" : "")); } @@ -3370,7 +3513,7 @@ int tport_send_error(tport_t *self, msg_t *msg, if (self->tp_addrinfo->ai_family == AF_INET) { SU_DEBUG_3(("tport_vsend(%p): %s with (s=%d %s/%s:%s%s)\n", - (void *)self, su_strerror(error), (int)self->tp_socket, + (void *)self, su_strerror(error), (int)self->tp_socket, tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port, comp)); } #if SU_HAVE_IN6 @@ -3378,7 +3521,7 @@ int tport_send_error(tport_t *self, msg_t *msg, su_sockaddr_t const *su = (su_sockaddr_t const *)ai->ai_addr; SU_DEBUG_3(("tport_vsend(%p): %s with " "(s=%d, IP6=%s/%s:%s%s (scope=%i) addrlen=%u)\n", - (void *)self, su_strerror(error), (int)self->tp_socket, + (void *)self, su_strerror(error), (int)self->tp_socket, tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port, comp, su->su_scope_id, (unsigned)ai->ai_addrlen)); } @@ -3386,7 +3529,7 @@ int tport_send_error(tport_t *self, msg_t *msg, else { SU_DEBUG_3(("\ttport_vsend(%p): %s with " "(s=%d, AF=%u addrlen=%u)%s\n", - (void *)self, su_strerror(error), + (void *)self, su_strerror(error), (int)self->tp_socket, ai->ai_family, (unsigned)ai->ai_addrlen, comp)); } @@ -3517,7 +3660,8 @@ int tport_queue(tport_t *self, msg_t *msg) unsigned short N = self->tp_params->tpp_qsize; SU_DEBUG_7(("tport_queue(%p): queueing %p for %s/%s:%s\n", - (void *)self, (void *)msg, self->tp_protoname, self->tp_host, self->tp_port)); + (void *)self, (void *)msg, + self->tp_protoname, self->tp_host, self->tp_port)); if (self->tp_queue == NULL) { assert(N > 0); @@ -3600,8 +3744,10 @@ int tport_tqsend(tport_t *self, msg_t *msg, msg_t *next, if (close_after) ai->ai_flags |= TP_AI_CLOSE; - if (self->tp_queue[qhead] == msg) + if (self->tp_queue[qhead] == msg) { tport_send_queue(self); + tport_set_secondary_timer(self); + } return 0; } @@ -3615,6 +3761,7 @@ int tport_tqsend(tport_t *self, msg_t *msg, msg_t *next, if (self->tp_queue[qhead] == msg) { /* XXX - what about errors? */ tport_send_msg(self, msg, self->tp_name, NULL); + tport_set_secondary_timer(self); if (!self->tp_unsent) { msg_destroy(self->tp_queue[qhead]); if ((self->tp_queue[qhead] = msg_ref_create(next))) @@ -3655,24 +3802,21 @@ void tport_send_event(tport_t *self) assert(tport_is_connection_oriented(self)); SU_DEBUG_7(("tport_send_event(%p) - ready to send to (%s/%s:%s)\n", - (void *)self, self->tp_protoname, self->tp_host, self->tp_port)); + (void *)self, self->tp_protoname, self->tp_host, self->tp_port)); tport_send_queue(self); + tport_set_secondary_timer(self); } /** Send queued messages */ -static void tport_send_queue(tport_t *self) { msg_t *msg; msg_iovec_t *iov; size_t i, iovused, n, total; unsigned short qhead = self->tp_qhead, N = self->tp_params->tpp_qsize; - su_time_t now; assert(self->tp_queue && self->tp_queue[qhead]); - self->tp_time = su_time_ms(now = su_now()); - msg = self->tp_queue[qhead]; iov = self->tp_unsent, self->tp_unsent = NULL; @@ -3680,6 +3824,9 @@ void tport_send_queue(tport_t *self) if (iov && iovused) { ssize_t e; + + self->tp_stime = self->tp_ktime = su_now(); + e = tport_vsend(self, msg, self->tp_name, iov, iovused, NULL); if (e == -1) /* XXX */ @@ -3688,7 +3835,7 @@ void tport_send_queue(tport_t *self) n = (size_t)e; if (n > 0 && self->tp_master->mr_log && self->tp_slogged != msg) { - tport_log_msg(self, msg, "send", "to", now); + tport_log_msg(self, msg, "send", "to", self->tp_stime); self->tp_slogged = msg; } @@ -3719,7 +3866,7 @@ void tport_send_queue(tport_t *self) while (msg_is_prepared(msg = self->tp_queue[self->tp_qhead = qhead])) { /* XXX - what about errors? */ tport_send_msg(self, msg, self->tp_name, NULL); - if (self->tp_unsent) + if (self->tp_unsent) return; msg = self->tp_queue[qhead]; /* tport_send_msg() may flush queue! */ @@ -3892,14 +4039,14 @@ int tport_pend(tport_t *self, { tport_pending_t *pending; - if (self == NULL || callback == NULL || client == NULL) + if (self == NULL || callback == NULL) return -1; if (msg == NULL && tport_is_primary(self)) return -1; SU_DEBUG_7(("tport_pend(%p): pending %p for %s/%s:%s (already %u)\n", - (void *)self, (void *)msg, + (void *)self, (void *)msg, self->tp_protoname, self->tp_host, self->tp_port, self->tp_pused)); @@ -3954,13 +4101,15 @@ int tport_release(tport_t *self, if (pending->p_client != client || pending->p_msg != msg) { - SU_DEBUG_1(("tport_release(%p): %u %p by %p not pending\n", (void *)self, - pendd, (void *)msg, (void *)client)); + SU_DEBUG_1(("%s(%p): %u %p by %p not pending\n", + __func__, (void *)self, + pendd, (void *)msg, (void *)client)); return su_seterrno(EINVAL), -1; } - SU_DEBUG_7(("tport_release(%p): %p by %p with %p%s\n", - (void *)self, (void *)msg, (void *)client, (void *)reply, + SU_DEBUG_7(("%s(%p): %p by %p with %p%s\n", + __func__, (void *)self, + (void *)msg, (void *)client, (void *)reply, still_pending ? " (preliminary)" : "")); /* sigcomp can here associate request (msg) with response (reply) */ @@ -4104,7 +4253,7 @@ tport_t *tport_next(tport_t const *self) tport_t *tport_secondary(tport_t const *self) { if (tport_is_primary(self)) - return self->tp_pri->pri_secondary; + return self->tp_pri->pri_open; else return NULL; } @@ -4240,7 +4389,7 @@ tport_t *tport_by_name(tport_t const *self, tp_name_t const *tpn) socklen_t sulen; su_sockaddr_t su[1]; - sub = self->tp_pri->pri_secondary; + sub = self->tp_pri->pri_open; memset(su, 0, sizeof su); @@ -4302,7 +4451,7 @@ tport_t *tport_by_name(tport_t const *self, tp_name_t const *tpn) } else { SU_DEBUG_7(("tport(%p): EXPENSIVE unresolved " TPN_FORMAT "\n", - (void *)self, TPN_ARGS(tpn))); + (void *)self, TPN_ARGS(tpn))); sub = tprb_first(sub); } @@ -4322,11 +4471,11 @@ tport_t *tport_by_name(tport_t const *self, tp_name_t const *tpn) if ((socklen_t)sub->tp_addrlen != sulen || memcmp(sub->tp_addr, su, sulen)) { SU_DEBUG_7(("tport(%p): not found by name " TPN_FORMAT "\n", - (void *)self, TPN_ARGS(tpn))); + (void *)self, TPN_ARGS(tpn))); break; } SU_DEBUG_7(("tport(%p): found %p by name " TPN_FORMAT "\n", - (void *)self, (void *)sub, TPN_ARGS(tpn))); + (void *)self, (void *)sub, TPN_ARGS(tpn))); } else if ((strcasecmp(canon, sub->tp_canon) && strcasecmp(host, sub->tp_host)) || @@ -4354,7 +4503,7 @@ tport_t *tport_by_addrinfo(tport_primary_t const *pri, sa = ai->ai_addr; - sub = pri->pri_secondary, maybe = NULL; + sub = pri->pri_open, maybe = NULL; comp = tport_canonize_comp(tpn->tpn_comp); @@ -4407,10 +4556,10 @@ tport_t *tport_by_addrinfo(tport_primary_t const *pri, if (sub) SU_DEBUG_7(("%s(%p): found %p by name " TPN_FORMAT "\n", - __func__, (void *)pri, (void *)sub, TPN_ARGS(tpn))); + __func__, (void *)pri, (void *)sub, TPN_ARGS(tpn))); else SU_DEBUG_7(("%s(%p): not found by name " TPN_FORMAT "\n", - __func__, (void *)pri, TPN_ARGS(tpn))); + __func__, (void *)pri, TPN_ARGS(tpn))); return (tport_t *)sub; } diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h b/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h index db95fdd751..b330b143b2 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h @@ -94,6 +94,9 @@ typedef struct { unsigned tpp_mtu; /**< Maximum packet size */ unsigned tpp_idle; /**< Allowed connection idle time. */ unsigned tpp_timeout; /**< Allowed idle time for message. */ + unsigned tpp_keepalive; /**< Keepalive PING interval */ + unsigned tpp_pingpong; /**< PONG-to-PING interval */ + unsigned tpp_sigcomp_lifetime; /**< SigComp compartment lifetime */ unsigned tpp_thrpsize; /**< Size of thread pool */ @@ -106,6 +109,7 @@ typedef struct { unsigned tpp_conn_orient:1; /**< Connection-orienteded */ unsigned tpp_sdwn_error:1; /**< If true, shutdown is error. */ unsigned tpp_stun_server:1; /**< If true, use stun server */ + unsigned tpp_pong2ping:1; /**< If true, respond with pong to ping */ unsigned :0; @@ -122,7 +126,7 @@ typedef struct { struct tport_s { su_home_t tp_home[1]; /**< Memory home */ - int tp_refs; /**< Number of references to tport */ + ssize_t tp_refs; /**< Number of references to tport */ unsigned tp_black:1; /**< Used by red-black-tree */ @@ -130,8 +134,13 @@ struct tport_s { unsigned tp_conn_orient:1; /**< Is connection-oriented */ unsigned tp_has_connection:1; /**< Has real connection */ unsigned tp_reusable:1; /**< Can this connection be reused */ - unsigned tp_closed : 1; /**< This transport is closed */ - /**< Remote end has sent FIN (2) or we should not just read */ + unsigned tp_closed : 1; + /**< This transport is closed. + * + * A closed transport is inserted into pri_closed list. + */ + + /** Remote end has sent FIN (2) or we should not just read */ unsigned tp_recv_close:2; /** We will send FIN (1) or have sent FIN (2) */ unsigned tp_send_close:2; @@ -150,10 +159,10 @@ struct tport_s { tp_magic_t *tp_magic; /**< Context provided by consumer */ - msg_t const *tp_rlogged; /**< Last logged when receiving */ - msg_t const *tp_slogged; /**< Last logged when sending */ + su_timer_t *tp_timer; /**< Timer object */ - unsigned tp_time; /**< When this transport was last used */ + su_time_t tp_ktime; /**< Keepalive timer updated */ + su_time_t tp_ptime; /**< Ping sent */ tp_name_t tp_name[1]; /**< Transport name. * @@ -178,16 +187,18 @@ struct tport_s { /* ==== Receive queue ================================================== */ msg_t *tp_msg; /**< Message being received */ + msg_t const *tp_rlogged; /**< Last logged when receiving */ + su_time_t tp_rtime; /**< Last time received data */ + unsigned short tp_ping; /**< Whitespace ping being received */ /* ==== Pending messages =============================================== */ - tport_pending_t *tp_pending; /**< Pending requests */ - tport_pending_t *tp_released; /**< Released pends */ + unsigned short tp_reported; /**< Report counter */ unsigned tp_plen; /**< Size of tp_pending */ unsigned tp_pused; /**< Used pends */ - unsigned short tp_reported; /**< Report counter */ - unsigned short tp_pad; - + tport_pending_t *tp_pending; /**< Pending requests */ + tport_pending_t *tp_released; /**< Released pends */ + /* ==== Send queue ===================================================== */ msg_t **tp_queue; /**< Messages being sent */ @@ -199,6 +210,9 @@ struct tport_s { msg_iovec_t *tp_iov; /**< Iovecs allocated for sending */ size_t tp_iovlen; /**< Number of allocated iovecs */ + msg_t const *tp_slogged; /**< Last logged when sending */ + su_time_t tp_stime; /**< Last time sent message */ + /* ==== Extensions ===================================================== */ tport_compressor_t *tp_comp; @@ -228,10 +242,10 @@ struct tport_primary { * tport_type_stun, etc. */ - char pri_ident[16]; tport_primary_t *pri_next; /**< Next primary tport */ - tport_t *pri_secondary; /**< Secondary tports */ + tport_t *pri_open; /**< Open secondary tports */ + tport_t *pri_closed; /**< Closed secondary tports */ unsigned pri_updating:1; /**< Currently updating address */ unsigned pri_natted:1; /**< Using natted address */ @@ -241,7 +255,6 @@ struct tport_primary { void *pri_stun_handle; tport_params_t pri_params[1]; /**< Transport parameters */ - }; /** Master structure */ @@ -307,7 +320,7 @@ struct tport_master { #endif }; -/** Virtual funtion table for transports */ +/** Virtual function table for transports */ struct tport_vtable { char const *vtp_name; @@ -344,6 +357,9 @@ struct tport_vtable int (*vtp_stun_response)(tport_t const *self, void *msg, size_t msglen, void *addr, socklen_t addrlen); + int (*vtp_next_secondary_timer)(tport_t *self, su_time_t *, + char const **return_why); + void (*vtp_secondary_timer)(tport_t *self, su_time_t); }; int tport_register_type(tport_vtable_t const *vtp); @@ -388,11 +404,16 @@ tport_t *tport_alloc_secondary(tport_primary_t *pri, int tport_accept(tport_primary_t *pri, int events); void tport_zap_secondary(tport_t *self); +int tport_set_secondary_timer(tport_t *self); +void tport_base_timer(tport_t *self, su_time_t now); + int tport_bind_socket(int socket, su_addrinfo_t *ai, char const **return_culprit); void tport_close(tport_t *self); +int tport_shutdown0(tport_t *self, int how); +int tport_has_queued(tport_t const *self); int tport_error_event(tport_t *self); void tport_recv_event(tport_t *self); @@ -414,6 +435,8 @@ int tport_send_msg(tport_t *self, msg_t *msg, tp_name_t const *tpn, struct sigcomp_compartment *cc); +void tport_send_queue(tport_t *self); + void tport_deliver(tport_t *self, msg_t *msg, msg_t *next, tport_compressor_t *comp, su_time_t now); @@ -430,6 +453,9 @@ void tport_dump_iovec(tport_t const *self, msg_t *msg, size_t n, su_iovec_t const iov[], size_t iovused, char const *what, char const *how); +int tport_tcp_ping(tport_t *self, su_time_t now); +int tport_tcp_pong(tport_t *self); + extern tport_vtable_t const tport_udp_vtable; extern tport_vtable_t const tport_udp_client_vtable; @@ -461,6 +487,15 @@ int tport_recv_stream(tport_t *self); ssize_t tport_send_stream(tport_t const *self, msg_t *msg, msg_iovec_t iov[], size_t iovused); +int tport_tcp_next_timer(tport_t *self, su_time_t *, char const **); +void tport_tcp_timer(tport_t *self, su_time_t); + +int tport_next_recv_timeout(tport_t *, su_time_t *, char const **); +void tport_recv_timeout_timer(tport_t *self, su_time_t now); + +int tport_next_keepalive(tport_t *self, su_time_t *, char const **); +void tport_keepalive_timer(tport_t *self, su_time_t now); + extern tport_vtable_t const tport_sctp_vtable; extern tport_vtable_t const tport_sctp_client_vtable; extern tport_vtable_t const tport_tls_vtable; diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_tag.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_tag.c index e775869395..b577d93c09 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_tag.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_tag.c @@ -64,6 +64,9 @@ tag_typedef_t tptag_sdwn_after = BOOLTAG_TYPEDEF(sdwn_after); tag_typedef_t tptag_close_after = BOOLTAG_TYPEDEF(sdwn_after); tag_typedef_t tptag_idle = UINTTAG_TYPEDEF(idle); tag_typedef_t tptag_timeout = UINTTAG_TYPEDEF(timeout); +tag_typedef_t tptag_keepalive = UINTTAG_TYPEDEF(keepalive); +tag_typedef_t tptag_pingpong = UINTTAG_TYPEDEF(pingpong); +tag_typedef_t tptag_pong2ping = BOOLTAG_TYPEDEF(pong2ping); tag_typedef_t tptag_sigcomp_lifetime = UINTTAG_TYPEDEF(sigcomp_lifetime); tag_typedef_t tptag_certificate = STRTAG_TYPEDEF(certificate); tag_typedef_t tptag_compartment = PTRTAG_TYPEDEF(compartment); diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_tls.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_tls.c index 0a242cc47e..73ab7324c7 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_tls.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_tls.c @@ -587,6 +587,10 @@ int tls_error(tls_t *tls, int ret, char const *who, char const *operation, return 0; case SSL_ERROR_SYSCALL: + if (SSL_get_shutdown(tls->con) & SSL_RECEIVED_SHUTDOWN) + return 0; /* EOS */ + if (errno == 0) + return 0; /* EOS */ return -1; default: @@ -665,10 +669,12 @@ int tls_want_read(tls_t *tls, int events) if (tls && (events & tls->read_events)) { int ret = tls_read(tls); - if (ret >= 0) + if (ret > 0) return 1; - else if (errno == EAGAIN) + else if (ret == 0) return 0; + else if (errno == EAGAIN) + return 2; else return -1; } diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_connect.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_connect.c index dc57ec21f6..93affa56a6 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_connect.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_connect.c @@ -201,6 +201,8 @@ static tport_t *tport_http_connect(tport_primary_t *pri, su_addrinfo_t *ai, return NULL; } + tport_set_secondary_timer(tport); + return tport; } diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_sctp.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_sctp.c index df97398c84..8c06dee251 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_sctp.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_sctp.c @@ -37,12 +37,12 @@ #include "config.h" +#if HAVE_SCTP + #include "tport_internal.h" #if HAVE_NETINET_SCTP_H #include -#undef HAVE_SCTP -#define HAVE_SCTP 1 #endif #include @@ -63,7 +63,6 @@ #define SOL_SCTP IPPROTO_SCTP #endif - enum { MAX_STREAMS = 1 }; typedef struct tport_sctp_t { @@ -81,8 +80,6 @@ typedef struct tport_sctp_t #define TP_SCTP_MSG_MAX (65536) -#if HAVE_SCTP - static int tport_sctp_init_primary(tport_primary_t *, tp_name_t tpn[1], su_addrinfo_t *, tagi_t const *, @@ -100,6 +97,9 @@ static int tport_recv_sctp(tport_t *self); static ssize_t tport_send_sctp(tport_t const *self, msg_t *msg, msg_iovec_t iov[], size_t iovused); +static int tport_sctp_next_timer(tport_t *self, su_time_t *, char const **); +static void tport_sctp_timer(tport_t *self, su_time_t); + tport_vtable_t const tport_sctp_client_vtable = { "sctp", tport_type_client, @@ -116,11 +116,14 @@ tport_vtable_t const tport_sctp_client_vtable = NULL, tport_recv_sctp, tport_send_sctp, + NULL, + NULL, + NULL, + NULL, + tport_sctp_next_timer, + tport_sctp_timer, }; -#undef NEXT_VTABLE -#define NEXT_VTABLE &tport_sctp_client_vtable - tport_vtable_t const tport_sctp_vtable = { "sctp", tport_type_local, @@ -137,11 +140,14 @@ tport_vtable_t const tport_sctp_vtable = NULL, tport_recv_sctp, tport_send_sctp, + NULL, + NULL, + NULL, + NULL, + tport_sctp_next_timer, + tport_sctp_timer, }; -#undef NEXT_VTABLE -#define NEXT_VTABLE &tport_sctp_vtable - static int tport_sctp_init_primary(tport_primary_t *pri, tp_name_t tpn[1], su_addrinfo_t *ai, @@ -263,4 +269,56 @@ static ssize_t tport_send_sctp(tport_t const *self, msg_t *msg, return su_vsend(self->tp_socket, iov, iovused, MSG_NOSIGNAL, NULL, 0); } +/** Calculate tick timer if send is pending. */ +int tport_next_sctp_send_tick(tport_t *self, + su_time_t *return_target, + char const **return_why) +{ + unsigned timeout = 100; /* Retry 10 times a second... */ + + if (tport_has_queued(self)) { + su_time_t ntime = su_time_add(self->tp_ktime, timeout); + if (su_time_cmp(ntime, *return_target) < 0) + *return_target = ntime, *return_why = "send tick"; + } + + return 0; +} + +/** Tick timer if send is pending */ +void tport_sctp_send_tick_timer(tport_t *self, su_time_t now) +{ + unsigned timeout = 100; + + /* Send timeout */ + if (tport_has_queued(self) && + su_time_cmp(su_time_add(self->tp_ktime, timeout), now) < 0) { + uint64_t bytes = self->tp_stats.sent_bytes; + su_time_t stime = self->tp_stime; + + tport_send_queue(self); + + if (self->tp_stats.sent_bytes == bytes) + self->tp_stime = stime; /* Restore send timestamp */ + } +} + +/** Calculate next timer for SCTP. */ +int tport_sctp_next_timer(tport_t *self, + su_time_t *return_target, + char const **return_why) +{ + return + tport_next_recv_timeout(self, return_target, return_why) | + tport_next_sctp_send_tick(self, return_target, return_why); +} + +/** SCTP timer. */ +void tport_sctp_timer(tport_t *self, su_time_t now) +{ + tport_sctp_send_tick_timer(self, now); + tport_recv_timeout_timer(self, now); + tport_base_timer(self, now); +} + #endif diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tcp.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tcp.c index 2916076841..f2a98a237f 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tcp.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tcp.c @@ -45,6 +45,7 @@ #endif #include +#include #include #include #include @@ -76,6 +77,12 @@ tport_vtable_t const tport_tcp_vtable = NULL, tport_recv_stream, tport_send_stream, + NULL, + NULL, + NULL, + NULL, + tport_tcp_next_timer, + tport_tcp_timer, }; tport_vtable_t const tport_tcp_client_vtable = @@ -84,7 +91,7 @@ tport_vtable_t const tport_tcp_client_vtable = sizeof (tport_primary_t), tport_tcp_init_client, NULL, - tport_accept, + NULL, NULL, sizeof (tport_t), tport_tcp_init_secondary, @@ -94,6 +101,12 @@ tport_vtable_t const tport_tcp_client_vtable = NULL, tport_recv_stream, tport_send_stream, + NULL, + NULL, + NULL, + NULL, + tport_tcp_next_timer, + tport_tcp_timer, }; static int tport_tcp_setsndbuf(int socket, int atleast); @@ -205,6 +218,20 @@ static int tport_tcp_setsndbuf(int socket, int atleast) #endif } +/** Return span of whitespace from buffer */ +static inline size_t ws_span(void *buffer, size_t len) +{ + size_t i; + char const *b = buffer; + + for (i = 0; i < len; i++) { + if (b[i] != '\r' && b[i] != '\n' && b[i] != ' ' && b[i] != '\t') + break; + } + + return i; +} + /** Receive from stream. * * @retval -1 error @@ -217,7 +244,7 @@ int tport_recv_stream(tport_t *self) { msg_t *msg; ssize_t n, N, veclen; - int err; + int err, initial; msg_iovec_t iovec[msg_n_fragments] = {{ 0 }}; N = su_getmsgsize(self->tp_socket); @@ -233,6 +260,36 @@ int tport_recv_stream(tport_t *self) return -1; } + initial = self->tp_msg == NULL; + memset(&self->tp_ptime, 0, sizeof self->tp_ptime); + + while (initial && N <= 8) { /* Check for whitespace */ + char crlf[9]; + size_t i; + + n = su_recv(self->tp_socket, crlf, N, MSG_PEEK); + + i = ws_span(crlf, n); + if (i == 0) + break; + + n = su_recv(self->tp_socket, crlf, i, 0); + if (n <= 0) + return (int)n; + + SU_DEBUG_7(("%s(%p): received keepalive\n", __func__, (void *)self)); + + N -= n, self->tp_ping += n; + + if (N == 0) { + /* outbound-10 section 3.5.1 - send pong */ + if (self->tp_ping >= 4) + tport_tcp_pong(self); + + return 1; + } + } + veclen = tport_recv_iovec(self, &self->tp_msg, iovec, N, 0); if (veclen == -1) return -1; @@ -242,11 +299,30 @@ int tport_recv_stream(tport_t *self) msg_set_address(msg, self->tp_addr, (socklen_t)(self->tp_addrlen)); n = su_vrecv(self->tp_socket, iovec, veclen, 0, NULL, NULL); + if (n == SOCKET_ERROR) return tport_recv_error_report(self); assert(n <= N); + /* Check if message contains only whitespace */ + /* This can happen if multiple PINGs are received at once */ + if (initial) { + size_t i = ws_span(iovec->siv_base, iovec->siv_len); + + if (i + self->tp_ping >= 4) + tport_tcp_pong(self); + else + self->tp_ping += i; + + if (i == iovec->siv_len && veclen == 1) { + SU_DEBUG_7(("%s(%p): received %u bytes of keepalive\n", + __func__, (void *)self, (unsigned)i)); + msg_destroy(self->tp_msg), self->tp_msg = NULL; + return 1; + } + } + /* Write the received data to the message dump file */ if (self->tp_master->mr_dump_file) tport_dump_iovec(self, msg, n, iovec, veclen, "recv", "from"); @@ -254,9 +330,13 @@ int tport_recv_stream(tport_t *self) /* Mark buffer as used */ msg_recv_commit(msg, n, n == 0); + if (n > 0) + self->tp_ping = 0; + return n != 0; } +/** Send to stream */ ssize_t tport_send_stream(tport_t const *self, msg_t *msg, msg_iovec_t iov[], size_t iovused) @@ -267,3 +347,184 @@ ssize_t tport_send_stream(tport_t const *self, msg_t *msg, #endif return su_vsend(self->tp_socket, iov, iovused, MSG_NOSIGNAL, NULL, 0); } + +/** Calculate timeout if receive is incomplete. */ +int tport_next_recv_timeout(tport_t *self, + su_time_t *return_target, + char const **return_why) +{ + unsigned timeout = self->tp_params->tpp_timeout; + + if (timeout < INT_MAX) { + /* Recv timeout */ + if (self->tp_msg) { + su_time_t ntime = su_time_add(self->tp_rtime, timeout); + if (su_time_cmp(ntime, *return_target) < 0) + *return_target = ntime, *return_why = "recv timeout"; + } + +#if 0 + /* Send timeout */ + if (tport_has_queued(self)) { + su_time_t ntime = su_time_add(self->tp_stime, timeout); + if (su_time_cmp(ntime, *return_target) < 0) + *return_target = ntime, *return_why = "send timeout"; + } +#endif + } + + return 0; +} + +/** Timeout timer if receive is incomplete */ +void tport_recv_timeout_timer(tport_t *self, su_time_t now) +{ + unsigned timeout = self->tp_params->tpp_timeout; + + if (timeout < INT_MAX) { + if (self->tp_msg && + su_time_cmp(su_time_add(self->tp_rtime, timeout), now) < 0) { + msg_t *msg = self->tp_msg; + msg_set_streaming(msg, 0); + msg_set_flags(msg, MSG_FLG_ERROR | MSG_FLG_TRUNC | MSG_FLG_TIMEOUT); + tport_deliver(self, msg, NULL, NULL, now); + self->tp_msg = NULL; + } + +#if 0 + /* Send timeout */ + if (tport_has_queued(self) && + su_time_cmp(su_time_add(self->tp_stime, timeout), now) < 0) { + stime = su_time_add(self->tp_stime, self->tp_params->tpp_timeout); + if (su_time_cmp(stime, target) < 0) + target = stime; + } +#endif + } +} + +/** Calculate next timeout for keepalive */ +int tport_next_keepalive(tport_t *self, + su_time_t *return_target, + char const **return_why) +{ + /* Keepalive timer */ + unsigned timeout = self->tp_params->tpp_keepalive; + + if (timeout != 0 && timeout != UINT_MAX) { + if (!tport_has_queued(self)) { + su_time_t ntime = su_time_add(self->tp_ktime, timeout); + if (su_time_cmp(ntime, *return_target) < 0) + *return_target = ntime, *return_why = "keepalive"; + } + } + + timeout = self->tp_params->tpp_pingpong; + if (timeout != 0) { + if (self->tp_ptime.tv_sec && !self->tp_recv_close) { + su_time_t ntime = su_time_add(self->tp_ptime, timeout); + if (su_time_cmp(ntime, *return_target) < 0) + *return_target = ntime, *return_why = "waiting for pong"; + } + } + + return 0; +} + + +/** Keepalive timer. */ +void tport_keepalive_timer(tport_t *self, su_time_t now) +{ + unsigned timeout = self->tp_params->tpp_pingpong; + + if (timeout != 0) { + if (self->tp_ptime.tv_sec && !self->tp_recv_close && + su_time_cmp(su_time_add(self->tp_ptime, timeout), now) < 0) { + SU_DEBUG_3(("%s(%p): %s to " TPN_FORMAT "%s\n", + __func__, (void *)self, + "closing connection", TPN_ARGS(self->tp_name), + " because of PONG timeout")); + tport_close(self); + return; + } + } + + timeout = self->tp_params->tpp_keepalive; + + if (timeout != 0 && timeout != UINT_MAX) { + if (su_time_cmp(su_time_add(self->tp_ktime, timeout), now) < 0) { + tport_tcp_ping(self, now); + } + } +} + +/** Send PING */ +int tport_tcp_ping(tport_t *self, su_time_t now) +{ + ssize_t n; + char *why = ""; + + if (tport_has_queued(self)) + return 0; + + n = send(self->tp_socket, "\r\n\r\n", 4, 0); + + if (n > 0) + self->tp_ktime = now; + + if (n == 4) { + if (self->tp_ptime.tv_sec == 0) + self->tp_ptime = now; + } + else if (n == -1) { + int error = su_errno(); + + why = " failed"; + + if (!su_is_blocking(error)) + tport_error_report(self, error, NULL); + else + why = " blocking"; + + return -1; + } + + SU_DEBUG_7(("%s(%p): %s to " TPN_FORMAT "%s\n", + __func__, (void *)self, + "sending PING", TPN_ARGS(self->tp_name), why)); + + return n == -1 ? -1 : 0; +} + +/** Send pong */ +int tport_tcp_pong(tport_t *self) +{ + self->tp_ping = 0; + + if (tport_has_queued(self) || !self->tp_params->tpp_pong2ping) + return 0; + + SU_DEBUG_7(("%s(%p): %s to " TPN_FORMAT "%s\n", + __func__, (void *)self, + "sending PONG", TPN_ARGS(self->tp_name), "")); + + return send(self->tp_socket, "\r\n", 2, 0); +} + +/** Calculate next timer for TCP. */ +int tport_tcp_next_timer(tport_t *self, + su_time_t *return_target, + char const **return_why) +{ + return + tport_next_recv_timeout(self, return_target, return_why) | + tport_next_keepalive(self, return_target, return_why); +} + +/** TCP timer. */ +void tport_tcp_timer(tport_t *self, su_time_t now) +{ + tport_recv_timeout_timer(self, now); + tport_keepalive_timer(self, now); + tport_base_timer(self, now); +} diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tls.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tls.c index eec8279b10..d25bfe34f0 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tls.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tls.c @@ -307,6 +307,8 @@ int tport_tls_events(tport_t *self, int events) ret = tls_want_read(tlstp->tlstp_context, events); if (ret > 0) tport_recv_event(self); + else if (ret == 0) /* End-of-stream */ + tport_shutdown0(self, 2); else if (ret < 0) tport_error_report(self, errno, NULL); } diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_udp.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_udp.c index b7c1ba51c3..db5288369f 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_udp.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_udp.c @@ -198,6 +198,9 @@ static void tport_check_trunc(tport_t *tp, su_addrinfo_t *ai) "TEST", 4, 0, (void *)ai->ai_addr, ai->ai_addrlen); + if (n != 4) + return; + for (;;) { n = su_recvfrom(tp->tp_socket, buffer, sizeof buffer, MSG_TRUNC, (void *)&su, &sulen); diff --git a/libs/sofia-sip/libsofia-sip-ua/url/torture_url.c b/libs/sofia-sip/libsofia-sip-ua/url/torture_url.c index b26f751bdc..9a05516127 100644 --- a/libs/sofia-sip/libsofia-sip-ua/url/torture_url.c +++ b/libs/sofia-sip/libsofia-sip-ua/url/torture_url.c @@ -120,11 +120,12 @@ int test_quote(void) d = url_as_string(home, u); TEST_1(d); TEST_S(d, c); - d = "sip:&=+$,;?/:&=+$,@[::1]:56001;param=+$,/:@&;another=@" + d = "sip:&=+$,;?/:&=+$,@[::1]:56001;param=+$,/:@&;another=@%40%2F" "?header=" RESERVED "&%3b%2f%3f%3a%40%26%3d%2b%24%2c"; u = url_hdup(home, (url_t *)d); TEST_1(u); TEST_S(u->url_user, "&=+$,;?/"); TEST_S(u->url_host, "[::1]"); + TEST_S(u->url_params, "param=+$,/:@&;another=@%40/"); TEST_S(u->url_headers, "header=" RESERVED "&%3B%2F%3F%3A%40%26%3D%2B%24%2C"); url_digest(hash1, sizeof(hash1), u, NULL); url_digest(hash2, sizeof(hash2), (url_t const *)d, NULL); @@ -134,10 +135,12 @@ int test_quote(void) d = url_as_string(home, u); TEST_1(d); TEST_S(d, c); - d = "http://&=+$,;:&=+$,;@host:8080/foo;param=+$,/:@&;another=@" + d = "http://&=+$,;:&=+$,;@host:8080/foo%2F%3B%3D" + ";param=+$,%2f%3b%3d/bar;param=:@&;another=@" "?query=" RESERVED; u = url_hdup(home, (url_t *)d); TEST_1(u); TEST_S(u->url_user, "&=+$,;"); TEST_S(u->url_password, "&=+$,;"); + TEST_S(u->url_path, "foo%2F%3B%3D;param=+$,%2F%3B%3D/bar;param=:@&;another=@"); url_digest(hash1, sizeof(hash1), u, NULL); url_digest(hash2, sizeof(hash2), (url_t const *)d, NULL); TEST(memcmp(hash1, hash2, sizeof(hash1)), 0); @@ -242,7 +245,7 @@ int test_sip(void) "sip:user:pass@host:32;param=1" "?From=foo@bar&To=bar@baz#unf"; char sip2url[] = - "sip:user/path;tel-param:pass@host:32;param=1" + "sip:user/path;tel-param:pass@host:32;param=1%3d%3d1" "?From=foo@bar&To=bar@baz#unf"; char sip2[sizeof(sipurl) + 32]; char sipsurl[] = @@ -305,6 +308,7 @@ int test_sip(void) TEST_1(tst = su_strdup(home, sip2url)); TEST_1(url_d(url, tst) == 0); TEST_S(url->url_user, "user/path;tel-param"); + TEST_S(url->url_params, "param=1%3D%3D1"); TEST_S(url_query_as_header_string(home, url->url_headers), "From:foo@bar\nTo:bar@baz"); diff --git a/libs/sofia-sip/libsofia-sip-ua/url/url.c b/libs/sofia-sip/libsofia-sip-ua/url/url.c index 051f81d214..312e5340fe 100644 --- a/libs/sofia-sip/libsofia-sip-ua/url/url.c +++ b/libs/sofia-sip/libsofia-sip-ua/url/url.c @@ -129,6 +129,8 @@ else if (a < 128) \ mask96 &= ~(1U << (127 - a)) +#define NUL '\0' +#define NULNULNUL '\0', '\0', '\0' #define RMASK1 0xbe19003f #define RMASK2 0x8000001e @@ -141,8 +143,10 @@ /* Internal prototypes */ static char *url_canonize(char *d, char const *s, size_t n, + unsigned syn33, char const allowed[]); static char *url_canonize2(char *d, char const *s, size_t n, + unsigned syn33, unsigned m32, unsigned m64, unsigned m96); static int url_tel_cmp_numbers(char const *A, char const *B); @@ -322,18 +326,24 @@ char *url_unescape(char *d, char const *s) /** Canonize a URL component */ static -char *url_canonize(char *d, char const *s, size_t n, char const allowed[]) +char *url_canonize(char *d, char const *s, size_t n, + unsigned syn33, + char const allowed[]) { unsigned mask32 = 0xbe19003f, mask64 = 0x8000001e, mask96 = 0x8000001d; MASKS_WITH_ALLOWED(allowed, mask32, mask64, mask96); - return url_canonize2(d, s, n, mask32, mask64, mask96); + return url_canonize2(d, s, n, syn33, mask32, mask64, mask96); } +#define SYN33(c) (1 << (c - 33)) +#define IS_SYN33(syn33, c) ((syn33 & (1 << (c - 33))) != 0) + /** Canonize a URL component (with precomputed mask) */ static -char *url_canonize2(char *d, char const * const s, size_t n, +char *url_canonize2(char *d, char const * const s, size_t n, + unsigned syn33, unsigned m32, unsigned m64, unsigned m96) { size_t i = 0; @@ -347,7 +357,7 @@ char *url_canonize2(char *d, char const * const s, size_t n, unsigned char c = s[i], h1, h2; if (c != '%') { - if (IS_EXCLUDED(c, m32, m64, m96)) + if (!IS_SYN33(syn33, c) && IS_EXCLUDED(c, m32, m64, m96)) return NULL; *d = c; continue; @@ -393,7 +403,7 @@ char *url_canonize2(char *d, char const * const s, size_t n, * be escaped. */ static -char *url_canonize3(char *d, char const * const s, size_t n, +char *url_canonize3(char *d, char const * const s, size_t n, unsigned m32, unsigned m64, unsigned m96) { size_t i = 0; @@ -575,7 +585,7 @@ int _url_d(url_t *url, char *s) char *scheme; url->url_scheme = scheme = s; s[n] = '\0'; s = s + n + 1; - if (!(scheme = url_canonize(scheme, scheme, SIZE_MAX, "+"))) + if (!(scheme = url_canonize(scheme, scheme, SIZE_MAX, 0, "+"))) return -1; n = scheme - url->url_scheme; @@ -694,7 +704,7 @@ int _url_d(url_t *url, char *s) case url_file: case url_rtsp: case url_rtspu: - if (!url_canonize2(port, port, SIZE_MAX, RESERVED_MASK)) + if (!url_canonize2(port, port, SIZE_MAX, 0, RESERVED_MASK)) return -1; /* Check that port is really numeric or wildcard */ @@ -758,14 +768,14 @@ int url_d(url_t *url, char *s) # define SIP_USER_UNRESERVED "&=+$,;?/" s = (char *)url->url_user; - if (s && !url_canonize(s, s, SIZE_MAX, SIP_USER_UNRESERVED)) + if (s && !url_canonize(s, s, SIZE_MAX, 0, SIP_USER_UNRESERVED)) return -1; /* Having different charset in user and password does not make sense */ /* but that is how it is defined in RFC 3261 */ # define SIP_PASS_UNRESERVED "&=+$," s = (char *)url->url_password; - if (s && !url_canonize(s, s, SIZE_MAX, SIP_PASS_UNRESERVED)) + if (s && !url_canonize(s, s, SIZE_MAX, 0, SIP_PASS_UNRESERVED)) return -1; } @@ -773,31 +783,37 @@ int url_d(url_t *url, char *s) # define USER_UNRESERVED "&=+$,;" s = (char *)url->url_user; - if (s && !url_canonize(s, s, SIZE_MAX, USER_UNRESERVED)) + if (s && !url_canonize(s, s, SIZE_MAX, 0, USER_UNRESERVED)) return -1; # define PASS_UNRESERVED "&=+$,;:" s = (char *)url->url_password; - if (s && !url_canonize(s, s, SIZE_MAX, PASS_UNRESERVED)) + if (s && !url_canonize(s, s, SIZE_MAX, 0, PASS_UNRESERVED)) return -1; } s = (char *)url->url_host; - if (s && !url_canonize2(s, s, SIZE_MAX, RESERVED_MASK)) + if (s && !url_canonize2(s, s, SIZE_MAX, 0, RESERVED_MASK)) return -1; /* port is canonized by _url_d() */ - - /* Allow all URI characters but ? and ; */ -# define PATH_UNRESERVED "/:@&=+$," s = (char *)url->url_path; - if (s && !url_canonize(s, s, SIZE_MAX, PATH_UNRESERVED)) + if (s && !url_canonize(s, s, SIZE_MAX, + /* Allow all URI characters but ? */ + /* Allow unescaped /;?@, - but do not convert */ + SYN33('/') | SYN33(';') | SYN33('=') | SYN33('@') | + SYN33(','), + /* Convert escaped :&+$ to unescaped */ + ":&+$")) return -1; - /* Allow all URI characters but ? */ -# define PARAMS_UNRESERVED ";" PATH_UNRESERVED s = (char *)url->url_params; - if (s && !url_canonize(s, s, SIZE_MAX, PARAMS_UNRESERVED)) + if (s && !url_canonize(s, s, SIZE_MAX, + /* Allow all URI characters but ? */ + /* Allow unescaped ;=@, - but do not convert */ + SYN33(';') | SYN33('=') | SYN33('@') | SYN33(','), + /* Convert escaped /:&+$ to unescaped */ + "/:&+$")) return -1; /* Unhex alphanumeric and unreserved URI characters */ @@ -807,7 +823,7 @@ int url_d(url_t *url, char *s) /* Allow all URI characters (including reserved ones) */ s = (char *)url->url_fragment; - if (s && !url_canonize2(s, s, SIZE_MAX, URIC_MASK)) + if (s && !url_canonize2(s, s, SIZE_MAX, 0, URIC_MASK)) return -1; return 0; @@ -1927,7 +1943,7 @@ void url_string_update(su_md5_t *md5, char const *s) su_md5_update(md5, ":", 1); } else if (n && s[n] == ':' ) { - at = url_canonize(schema, s, n, "+"); + at = url_canonize(schema, s, n, 0, "+"); type = url_get_type(schema, at - schema); su_md5_iupdate(md5, schema, at - schema);