Michael Jerris 274a956f00 Merge up to the most recent sofia-sip darcs tree. Includes the following patches from darcs:
Tue Aug 21 09:38:59 EDT 2007  Pekka.Pessi@nokia.com
  * tport_type_udp.c: checking error while checking that MSG_TRUNC works.
Shall I pull this patch? (1/43)  [ynWvpxqadjk], or ? for help: y

Tue Aug 21 10:49:33 EDT 2007  Pekka.Pessi@nokia.com
  * nua_params.c: NUTAG_SIPS_URL() now sets the handle target, too.

  Problem reported by Jari Tenhunen.
Shall I pull this patch? (2/43)  [ynWvpxqadjk], or ? for help: y

Thu Aug 23 11:22:42 EDT 2007  Pekka.Pessi@nokia.com
  * nta.c: do not destroy INVITE transaction if it has been CANCELed

  Handle gracefully cases where the INVITE transaction is destroyed
  immediately after canceling it. The old behaviour was to left it up to the
  application to ACK the final response returned to INVITE.

  Thanks for Fabio Margarido for reporting this problem.
Shall I pull this patch? (3/43)  [ynWvpxqadjk], or ? for help: y

Thu Aug 23 13:02:01 EDT 2007  Pekka.Pessi@nokia.com
  * test_soa.c: added test with user SDP containing already rejected media
Shall I pull this patch? (4/43)  [ynWvpxqadjk], or ? for help: y

Fri Aug 24 09:41:20 EDT 2007  Pekka.Pessi@nokia.com
  * nta: added option for processing orphan responses matching with a dialog

  The orphan responses matching with the dialog can now be processed by the
  response callback.The dialog leg can be created with
  NTATAG_RESPONSE_CALLBACK() or a response callback can be later bound to the
  leg with nta_leg_bind_response().

  This is practically useful only with 200 OK responses to the INVITE that are
  retransmitted by the UAS. By default, the retransmission are catched by the
  ACK transaction (which then retransmits the ACK request message). However,
  after ACK transaction times out, the retransmitted 200 OK indicates most
  probably that the ACK request messages do not reach UAS.

  Partially fixes the sf.net bug #1750691 reported by Mikhail Zabaluev.
Shall I pull this patch? (5/43)  [ynWvpxqadjk], or ? for help: y

Fri Aug 24 09:41:20 EDT 2007  Pekka.Pessi@nokia.com
  UNDO: nta: added option for processing orphan responses matching with a dialog

  The orphan responses matching with the dialog can now be processed by the
  response callback.The dialog leg can be created with
  NTATAG_RESPONSE_CALLBACK() or a response callback can be later bound to the
  leg with nta_leg_bind_response().

  This is practically useful only with 200 OK responses to the INVITE that are
  retransmitted by the UAS. By default, the retransmission are catched by the
  ACK transaction (which then retransmits the ACK request message). However,
  after ACK transaction times out, the retransmitted 200 OK indicates most
  probably that the ACK request messages do not reach UAS.

  Partially fixes the sf.net bug #1750691 reported by Mikhail Zabaluev.
Shall I pull this patch? (6/43)  [ynWvpxqadjk], or ? for help: y

Thu Aug 30 07:00:10 EDT 2007  Pekka.Pessi@nokia.com
  * nta.c: disabled nta_msg_ackbye(). Fix for sf.net bug #1750691

  Thanks for Mikhail Zabaluev for reporting this bug.
Shall I pull this patch? (7/43)  [ynWvpxqadjk], or ? for help: y

Thu Aug 30 06:54:38 EDT 2007  Pekka.Pessi@nokia.com
  * test_nua: added test for sf.net bug #1750691
Shall I pull this patch? (8/43)  [ynWvpxqadjk], or ? for help: y

Thu Aug 30 07:03:45 EDT 2007  Pekka.Pessi@nokia.com
  * test_nua: added test for nua_bye() sending CANCEL
Shall I pull this patch? (9/43)  [ynWvpxqadjk], or ? for help: y

Fri Aug 31 12:08:09 EDT 2007  Pekka.Pessi@nokia.com
  * url.c: fixed escaping of '/' %2F, ';' %3B and '=' %3D in URL path/params

  Thanks for Fabio Margarido for reporting this bug.
Shall I pull this patch? (10/43)  [ynWvpxqadjk], or ? for help: y

Mon Sep  3 10:14:55 EDT 2007  Pekka.Pessi@nokia.com
  * url.c: do not un-escape %40 in URI parameters.

  Do not unescape %2C, %3B, %3D, or %40 in URI parameters, nor
  %2C, %2F, %3B, %3D, or %40 in URI path.

  The @ sign can be ambiguous in the SIP URL, e.g.,

  <sip:test.info;p=value@test.com>

  can be parsed in two ways:
  1) username contains test.info;param=value and host part has test.com
  2) empty username, host part test.info, URI parameter p=value@test.com

  Previously Sofia URL parser converted escaped '@' at signs (%40) in the URI
  parameters to the unescaped form. The resulting URI could be ambiguous and
  sometimes fail the syntax check if there was no '@' sign before the
  unescaped one.

  Thanks for Jan van den Bosch and Mikhail Zabaluev for reporting this bug.
Shall I pull this patch? (11/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 04:59:57 EDT 2007  Pekka.Pessi@nokia.com
  * tport.c: fixed indenting, logging
Shall I pull this patch? (12/43)  [ynWvpxqadjk], or ? for help: y

Fri Jul 13 12:47:33 EDT 2007  Pekka.Pessi@nokia.com
  * nua/test_proxy.h, nua/test_proxy.c: added support for multiple domains

  Each domain has its own registrar and authentication module.
Shall I pull this patch? (13/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 11:19:33 EDT 2007  Pekka.Pessi@nokia.com
  * test_ops.c: added timestamp to event logging
Shall I pull this patch? (14/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 11:20:12 EDT 2007  Pekka.Pessi@nokia.com
  * test_nua: fixed timing problems in testing.
Shall I pull this patch? (15/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 11:31:04 EDT 2007  Pekka.Pessi@nokia.com
  * test_ops.c: reduce su_root_step() delay to 0.1 seconds
Shall I pull this patch? (16/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 11:31:22 EDT 2007  Pekka.Pessi@nokia.com
  * test_register.c: fixed timing problem
Shall I pull this patch? (17/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 17:03:46 EDT 2007  Pekka.Pessi@nokia.com
  * test_100rel.c: fixed timing problems resulting in events being reordered
Shall I pull this patch? (18/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 12:40:53 EDT 2007  Pekka.Pessi@nokia.com
  * nua (test_init.c, test_register.c): using test_proxy domains
Shall I pull this patch? (19/43)  [ynWvpxqadjk], or ? for help: y

Thu Aug 23 12:12:32 EDT 2007  Pekka.Pessi@nokia.com
  * test_soa.c: added cleanup code
Shall I pull this patch? (20/43)  [ynWvpxqadjk], or ? for help: y

Fri Aug 24 09:35:35 EDT 2007  Pekka.Pessi@nokia.com
  * nta.c: increase lifetime of ACK transaction from T4 to T1 x 64

  nta.c creates a ACK transaction in order to restransmit ACK requests when
  ever a retransmitted 2XX response to INVITE is received. The UAS retransmits
  the 2XX responses for 64 x T1 (32 second by default).

  Partially fixes the sf.net bug #1750691 reported by Mikhail Zabaluev.
Shall I pull this patch? (21/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 10:21:04 EDT 2007  Pekka.Pessi@nokia.com
  * Makefile.am: generating libsofia-sip-ua/docs/Doxyfile.rfc before making manpages
Shall I pull this patch? (22/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 12:05:33 EDT 2007  Pekka.Pessi@nokia.com
  * sofia-sip/tport_tag.h: added TPTAG_KEEPALIVE(), TPTAG_PINGPONG(), TPTAG_PONG2PING()
Shall I pull this patch? (23/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 12:09:06 EDT 2007  Pekka.Pessi@nokia.com
  * tport: added ping-pong keepalive on TCP. replaced single tick with connection-specific timer

  Now detecting closed connections on TLS, too.

  Added tests for idle timeout, receive timeout, ping-pong timeout.
Shall I pull this patch? (24/43)  [ynWvpxqadjk], or ? for help: y

Fri Jul  6 10:19:32 EDT 2007  Pekka.Pessi@nokia.com
  * nta.c: added nta_incoming_received()
Shall I pull this patch? (25/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 11:29:56 EDT 2007  Pekka.Pessi@nokia.com
  * nua_session.c: delay transition to ready when O/A is incomplete

  Delay sending ACK and subsequent transition of call to the ready state when
  the 200 OK response to the INVITE is received if the SDP Offer/Answer
  exchange using UPDATE/PRACK was still incomplete.

  Previously, if the O/A using UPDATE or PRACK was incomplete and an 200 OK
  was received, the call setup logic regarded this as a fatal error and
  terminated the call.

  Thanks for Mike Jerris for detecting and reporting this bug.
Shall I pull this patch? (26/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 12:22:46 EDT 2007  Pekka.Pessi@nokia.com
  * test_call_reject.c: testing Retry-After
Shall I pull this patch? (27/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 12:42:51 EDT 2007  Pekka.Pessi@nokia.com
  * test_nua: using rudimentary outbound support in B's proxy.
Shall I pull this patch? (28/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 12:48:33 EDT 2007  Pekka.Pessi@nokia.com
  * nua_register.c: added some logging to nua_register_connection_closed()
Shall I pull this patch? (29/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 12:43:57 EDT 2007  Pekka.Pessi@nokia.com
  * test_nua: using AUTHTAG_MAX_NCOUNT(1) for Mr. C

  C is now challenged every time.
Shall I pull this patch? (30/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 11:05:19 EDT 2007  Pekka.Pessi@nokia.com
  * nua/test_100rel.c: fixed timing problem re response to PRACK and ACK
Shall I pull this patch? (31/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 06:02:50 EDT 2007  Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
  * DIST_SUBDIRS must include everything unconditionally
Shall I pull this patch? (32/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 13:53:04 EDT 2007  Pekka.Pessi@nokia.com
  * test_soa.c: silenced warnings
Shall I pull this patch? (33/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 16:59:48 EDT 2007  Pekka.Pessi@nokia.com
  * nua: refactored dialog refresh code
Shall I pull this patch? (34/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 16:59:48 EDT 2007  Pekka.Pessi@nokia.com
  UNDO: nua: refactored dialog refresh code
Shall I pull this patch? (35/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 12:01:25 EDT 2007  Pekka.Pessi@nokia.com
  * nua_dialog.[hc]: renamed functions setting refresh interval

Shall I pull this patch? (36/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 12:15:03 EDT 2007  Pekka.Pessi@nokia.com
  * nua_dialog.[hc], nua_stack.c: added nua_dialog_repeat_shutdown()
Shall I pull this patch? (37/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 12:19:20 EDT 2007  Pekka.Pessi@nokia.com
  * nua_dialog.h: renamed nua_remote_t as nua_dialog_peer_info_t
Shall I pull this patch? (38/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 12:23:04 EDT 2007  Pekka.Pessi@nokia.com
  * nua_stack.c: added timer to client request in order to implement Retry-After
Shall I pull this patch? (39/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 12:33:53 EDT 2007  Pekka.Pessi@nokia.com
  * nua: added backpointers to nua_dialog_usage_t and nua_dialog_state_t
Shall I pull this patch? (40/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 13:56:48 EDT 2007  Pekka.Pessi@nokia.com
  * test_nua.c: abort() in timeout alarm function if -a is given
Shall I pull this patch? (41/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 17:13:18 EDT 2007  Pekka.Pessi@nokia.com
  * nua_subnotref.c: include SIPTAG_EVENT() in the nua_i_notify tag list
Shall I pull this patch? (42/43)  [ynWvpxqadjk], or ? for help: y

Mon Sep 10 12:27:53 EDT 2007  Pekka.Pessi@nokia.com
  * nua: save Contact from target refresh request or response.

  Save the Contact header which the application has added to the target
  refresh requests or responses and use the saved contact in subsequent target
  refresh requests or responses.

  Previously the application had no way of specifying the Contact included in
  the automatic responses to target refresh requests.

  Thanks for Anthony Minessale for reporting this problem.
Shall I pull this patch? (43/43)  [ynWvpxqadjk], or ? for help: y




git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@5692 d0543943-73ff-0310-b7d9-9358b9ac24b2
2007-09-10 20:45:25 +00:00

4745 lines
115 KiB
C

/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2005 Nokia Corporation.
*
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
/**@CFILE tport.c Transport interface implementation.
*
* See tport.docs for more detailed description of tport interface.
*
* @author Pekka Pessi <Pekka.Pessi@nokia.com>
* @author Ismo Puustinen <Ismo.H.Puustinen@nokia.com>
* @author Tat Chan <Tat.Chan@nokia.com>
* @author Kai Vehmanen <kai.vehmanen@nokia.com>
* @author Martti Mela <Martti.Mela@nokia.com>
*
* @date Created: Thu Jul 20 12:54:32 2000 ppessi
*/
#include "config.h"
#include <sofia-sip/string0.h>
#include <sofia-sip/su.h>
#include <sofia-sip/su_errno.h>
#include <sofia-sip/su_alloc.h>
#include <sofia-sip/su_tagarg.h>
#include <sofia-sip/su_localinfo.h>
typedef struct tport_nat_s tport_nat_t;
#define SU_WAKEUP_ARG_T struct tport_s
#define SU_TIMER_ARG_T struct tport_s
#define SU_MSG_ARG_T union tport_su_msg_arg
#include <sofia-sip/su_wait.h>
#include <sofia-sip/msg.h>
#include <sofia-sip/msg_addr.h>
#include <sofia-sip/hostdomain.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
#include <errno.h>
#include <limits.h>
#ifndef IPPROTO_SCTP
#define IPPROTO_SCTP (132)
#endif
#include "sofia-sip/tport.h"
#include "sofia-sip/su_uniqueid.h"
#include <sofia-sip/rbtree.h>
#include "tport_internal.h"
#if HAVE_FUNC
#elif HAVE_FUNCTION
#define __func__ __FUNCTION__
#else
static char const __func__[] = "tport";
#endif
#define STACK_RECV(tp, msg, now) \
(tp)->tp_master->mr_tpac->tpac_recv((tp)->tp_master->mr_stack, (tp), \
(msg), (tp)->tp_magic, (now))
#define STACK_ERROR(tp, errcode, dstname) \
(tp)->tp_master->mr_tpac->tpac_error((tp)->tp_master->mr_stack, (tp), \
(errcode), (dstname))
#define STACK_ADDRESS(tp) \
(tp)->tp_master->mr_tpac->tpac_address((tp)->tp_master->mr_stack, (tp))
#define TP_STACK tp_master->mr_stack
/* Define macros for rbtree implementation */
#define TP_LEFT(tp) ((tp)->tp_left)
#define TP_RIGHT(tp) ((tp)->tp_right)
#define TP_PARENT(tp) ((tp)->tp_dad)
#define TP_SET_RED(tp) ((tp)->tp_black = 0)
#define TP_SET_BLACK(tp) ((tp)->tp_black = 1)
#define TP_IS_RED(tp) ((tp) && (tp)->tp_black == 0)
#define TP_IS_BLACK(tp) (!(tp) || (tp)->tp_black == 1)
#define TP_COPY_COLOR(dst, src) ((dst)->tp_black = (src)->tp_black)
#define TP_INSERT(tp) ((void)0)
#define TP_REMOVE(tp) ((tp)->tp_left = (tp)->tp_right = (tp)->tp_dad = NULL)
su_inline int tp_cmp(tport_t const *a, tport_t const *b)
{
if (a == b)
return 0;
if (a->tp_addrlen != b->tp_addrlen)
return (int)(a->tp_addrlen - b->tp_addrlen);
return memcmp(a->tp_addr, b->tp_addr, a->tp_addrlen);
}
su_inline int tprb_is_inserted(tport_t const *a)
{
return a->tp_dad != 0 || a->tp_left != 0 || a->tp_right != 0;
}
RBTREE_PROTOS(su_inline, tprb, tport_t);
RBTREE_BODIES(su_inline, tprb, tport_t,
TP_LEFT, TP_RIGHT, TP_PARENT,
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
};
struct tport_pending_s {
/* tport_pending_t *p_left, *p_right, *p_parent; */
void *p_client;
tport_pending_error_f *p_callback;
msg_t *p_msg;
unsigned short p_reported;
unsigned short p_on_success;
};
/** Return true if transport is master. */
int tport_is_master(tport_t const *self)
{
return
self &&
self->tp_master->mr_master == self;
}
/** Return true if transport is primary. */
int tport_is_primary(tport_t const *self)
{
return
self &&
self->tp_pri->pri_primary == self;
}
/** Return true if transport is secondary. */
int tport_is_secondary(tport_t const *self)
{
return
self &&
self->tp_master->mr_master != self &&
self->tp_pri->pri_primary != self;
}
/** Test if transport has been registered to su_root_t */
int tport_is_registered(tport_t const *self)
{
return self->tp_index != 0;
}
/** Test if transport is stream. */
int tport_is_stream(tport_t const *self)
{
return self->tp_addrinfo->ai_socktype == SOCK_STREAM;
}
/** Test if transport is dgram. */
int tport_is_dgram(tport_t const *self)
{
return self->tp_addrinfo->ai_socktype == SOCK_DGRAM;
}
/** Test if transport is udp. */
int tport_is_udp(tport_t const *self)
{
return self->tp_addrinfo->ai_protocol == IPPROTO_UDP;
}
/** Test if transport is tcp. */
int tport_is_tcp(tport_t const *self)
{
return self->tp_addrinfo->ai_protocol == IPPROTO_TCP;
}
/** Return 1 if transport is reliable, 0 otherwise.
*
* (Note that this is part of external API).
*/
int tport_is_reliable(tport_t const *self)
{
return self != NULL &&
(self->tp_addrinfo->ai_socktype == SOCK_STREAM ||
self->tp_addrinfo->ai_socktype == SOCK_SEQPACKET);
}
/** Return 0 if self is local, nonzero otherwise.
*
* The return valu is the
*
* @sa TPTAG_PUBLIC(), enum #tport_via.
*/
int tport_is_public(tport_t const *self)
{
return self->tp_pri->pri_public;
}
/** Return true if transport supports IPv4 */
int tport_has_ip4(tport_t const *self)
{
return self &&
(self->tp_addrinfo->ai_family == 0 ||
self->tp_addrinfo->ai_family == AF_INET);
}
#if SU_HAVE_IN6
/** Return true if transport supports IPv6 */
int tport_has_ip6(tport_t const *self)
{
return self &&
(self->tp_addrinfo->ai_family == 0 ||
self->tp_addrinfo->ai_family == AF_INET6);
}
#endif
/** Return true if transport supports TLS. */
int tport_has_tls(tport_t const *self)
{
return self && self->tp_pri->pri_has_tls;
}
/** Return true if transport is being updated. */
int tport_is_updating(tport_t const *self)
{
tport_primary_t *pri;
if (tport_is_master(self)) {
for (pri = self->tp_master->mr_primaries; pri; pri = pri->pri_next)
if (pri->pri_updating)
return 1;
}
else if (tport_is_primary(self)) {
return self->tp_pri->pri_updating;
}
return 0;
}
/** Test if transport has been closed.
*
* @since New in @VERSION_1_12_4
*/
inline int tport_is_closed(tport_t const *self)
{
return self->tp_closed;
}
/** Test if transport has been shut down.
*
* @since New in @VERSION_1_12_4
*/
inline int tport_is_shutdown(tport_t const *self)
{
return self->tp_closed || self->tp_send_close || self->tp_recv_close;
}
/** Test if transport is bound */
su_inline int tport_is_bound(tport_t const *self)
{
return self->tp_protoname != NULL;
}
/** Test if transport connection has been established. @NEW_1_12_5 */
int tport_is_connected(tport_t const *self)
{
return self->tp_is_connected;
}
/** Test if transport can be used to send message. @NEW_1_12_7 */
int tport_is_clear_to_send(tport_t const *self)
{
return
tport_is_master(self) ||
tport_is_primary(self) ||
(tport_is_secondary(self) &&
tport_is_registered(self) &&
self->tp_reusable &&
!self->tp_closed &&
!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)
{
return self->tp_params->tpp_mtu;
}
su_inline
int tport_has_sigcomp(tport_t const *self)
{
return self->tp_name->tpn_comp != NULL;
}
/** Set IP TOS for socket */
void tport_set_tos(su_socket_t socket, su_addrinfo_t *ai, int tos)
{
if (tos >= 0 &&
ai->ai_family == AF_INET &&
setsockopt(socket, IPPROTO_IP, IP_TOS, (const void*)&tos, sizeof(tos)) < 0) {
SU_DEBUG_3(("tport: setsockopt(IP_TOS): %s\n",
su_strerror(su_errno())));
}
}
static
tport_t *tport_connect(tport_primary_t *pri, su_addrinfo_t *ai,
tp_name_t const *tpn);
static int bind6only_check(tport_master_t *mr);
static
int tport_server_addrinfo(tport_master_t *mr,
char const *canon,
int family,
char const *host,
char const *service,
char const *protocol,
char const * const transports[],
su_addrinfo_t **res);
static int tport_get_local_addrinfo(tport_master_t *mr,
char const *port,
su_addrinfo_t const *hints,
su_addrinfo_t **return_ai);
int tport_getaddrinfo(char const *node, char const *service,
su_addrinfo_t const *hints,
su_addrinfo_t **res);
static void tport_freeaddrinfo(su_addrinfo_t *ai);
static
int tport_addrinfo_copy(su_addrinfo_t *dst, void *addr, socklen_t addrlen,
su_addrinfo_t const *src);
static int
tport_bind_client(tport_master_t *self, tp_name_t const *tpn,
char const * const transports[], enum tport_via public,
tagi_t *tags),
tport_bind_server(tport_master_t *, tp_name_t const *tpn,
char const * const transports[], enum tport_via public,
tagi_t *tags),
tport_setname(tport_t *, char const *, su_addrinfo_t const *, char const *),
tport_wakeup_pri(su_root_magic_t *m, su_wait_t *w, tport_t *self),
tport_wakeup(su_root_magic_t *m, su_wait_t *w, tport_t *self),
tport_base_wakeup(tport_t *self, int events),
tport_connected(su_root_magic_t *m, su_wait_t *w, tport_t *self),
tport_resolve(tport_t *self, msg_t *msg, tp_name_t const *tpn),
tport_send_error(tport_t *, msg_t *, tp_name_t const *),
tport_queue(tport_t *self, msg_t *msg),
tport_queue_rest(tport_t *self, msg_t *msg, msg_iovec_t iov[], size_t iovused),
tport_pending_error(tport_t *self, su_sockaddr_t const *dst, int error),
tport_pending_errmsg(tport_t *self, msg_t *msg, int error);
static ssize_t tport_vsend(tport_t *self, msg_t *msg, tp_name_t const *tpn,
msg_iovec_t iov[], size_t iovused,
struct sigcomp_compartment *cc);
tport_t *tport_by_addrinfo(tport_primary_t const *pri,
su_addrinfo_t const *ai,
tp_name_t const *tpn);
void tport_peer_address(tport_t *self, msg_t *msg);
static void tport_parse(tport_t *self, int complete, su_time_t now);
static tport_primary_t *tport_alloc_primary(tport_master_t *mr,
tport_vtable_t const *vtable,
tp_name_t tpn[1],
su_addrinfo_t *ai,
tagi_t const *tags,
char const **return_culprit);
static tport_primary_t *tport_listen(tport_master_t *mr,
tport_vtable_t const *vtable,
tp_name_t tpn[1],
su_addrinfo_t *ai,
tagi_t *tags);
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);
/* Stack class used when transports are being destroyed */
static
void tport_destroy_recv(tp_stack_t *stack, tport_t *tp,
msg_t *msg, tp_magic_t *magic,
su_time_t received)
{
msg_destroy(msg);
}
static
void tport_destroy_error(tp_stack_t *stack, tport_t *tp,
int errcode, char const *remote)
{
}
static
msg_t *tport_destroy_alloc(tp_stack_t *stack, int flags,
char const data[], usize_t len,
tport_t const *tp,
tp_client_t *tpc)
{
return NULL;
}
/** Name for "any" transport. @internal */
static char const tpn_any[] = "*";
/** Create the master transport.
*
* @TAGS
* TPTAG_LOG(), TPTAG_DUMP(), tags used with tport_set_params(), especially
* TPTAG_QUEUESIZE().
*/
tport_t *tport_tcreate(tp_stack_t *stack,
tp_stack_class_t const *tpac,
su_root_t *root,
tag_type_t tag, tag_value_t value, ...)
{
tport_master_t *mr;
tp_name_t *tpn;
tport_params_t *tpp;
ta_list ta;
if (!stack || !tpac || !root) {
su_seterrno(EINVAL);
return NULL;
}
mr = su_home_clone(NULL, sizeof *mr);
if (!mr)
return NULL;
SU_DEBUG_7(("%s(): %p\n", "tport_create", (void *)mr));
mr->mr_stack = stack;
mr->mr_tpac = tpac;
mr->mr_root = root;
mr->mr_master->tp_master = mr;
mr->mr_master->tp_params = tpp = mr->mr_params;
mr->mr_master->tp_reusable = 1;
tpp->tpp_mtu = UINT_MAX;
tpp->tpp_thrprqsize = THRP_PENDING;
tpp->tpp_qsize = TPORT_QUEUESIZE;
tpp->tpp_sdwn_error = 1;
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 */
tpn = mr->mr_master->tp_name;
tpn->tpn_proto = "*";
tpn->tpn_host = "*";
tpn->tpn_canon = "*";
tpn->tpn_port = "*";
ta_start(ta, tag, value);
tport_set_params(mr->mr_master, ta_tags(ta));
tport_open_log(mr, ta_args(ta));
#if HAVE_SOFIA_STUN
tport_init_stun_server(mr, ta_args(ta));
#endif
ta_end(ta);
return mr->mr_master;
}
/** Destroy the master transport. */
void tport_destroy(tport_t *self)
{
tport_master_t *mr;
static tp_stack_class_t tport_destroy_tpac[1] =
{{
sizeof tport_destroy_tpac,
/* tpac_recv */ tport_destroy_recv,
/* tpac_error */ tport_destroy_error,
/* tpac_alloc */ tport_destroy_alloc,
/* tpac_address */ NULL
}};
SU_DEBUG_7(("%s(%p)\n", __func__, (void *)self));
if (self == NULL)
return;
assert(tport_is_master(self));
if (!tport_is_master(self))
return;
mr = (tport_master_t *)self;
mr->mr_tpac = tport_destroy_tpac;
while (mr->mr_primaries)
tport_zap_primary(mr->mr_primaries);
#if HAVE_SOFIA_STUN
tport_deinit_stun_server(mr);
#endif
if (mr->mr_dump_file)
fclose(mr->mr_dump_file), mr->mr_dump_file = NULL;
if (mr->mr_timer)
su_timer_destroy(mr->mr_timer), mr->mr_timer = NULL;
su_home_zap(mr->mr_home);
}
/** Allocate a primary transport */
static
tport_primary_t *tport_alloc_primary(tport_master_t *mr,
tport_vtable_t const *vtable,
tp_name_t tpn[1],
su_addrinfo_t *ai,
tagi_t const *tags,
char const **return_culprit)
{
tport_primary_t *pri, **next;
tport_t *tp;
int save_errno;
for (next = &mr->mr_primaries; *next; next = &(*next)->pri_next)
;
assert(vtable->vtp_pri_size >= sizeof *pri);
if ((pri = su_home_clone(mr->mr_home, vtable->vtp_pri_size))) {
tport_t *tp = pri->pri_primary;
pri->pri_vtable = vtable;
pri->pri_public = vtable->vtp_public;
tp->tp_master = mr;
tp->tp_pri = pri;
tp->tp_socket = INVALID_SOCKET;
tp->tp_magic = mr->mr_master->tp_magic;
tp->tp_params = pri->pri_params;
memcpy(tp->tp_params, mr->mr_params, sizeof (*tp->tp_params));
tp->tp_reusable = mr->mr_master->tp_reusable;
if (!pri->pri_public)
tp->tp_addrinfo->ai_addr = &tp->tp_addr->su_sa;
SU_DEBUG_5(("%s(%p): new primary tport %p\n", __func__, (void *)mr,
(void *)pri));
}
*next = pri;
tp = pri->pri_primary;
if (!tp)
*return_culprit = "alloc";
else if (tport_set_params(tp, TAG_NEXT(tags)) < 0)
*return_culprit = "tport_set_params";
else if (vtable->vtp_init_primary &&
vtable->vtp_init_primary(pri, tpn, ai, tags, return_culprit) < 0)
;
else if (tport_setname(tp, vtable->vtp_name, ai, tpn->tpn_canon) == -1)
*return_culprit = "tport_setname";
else if (tpn->tpn_ident &&
!(tp->tp_name->tpn_ident = su_strdup(tp->tp_home, tpn->tpn_ident)))
*return_culprit = "alloc ident";
else
return pri; /* Success */
save_errno = su_errno();
tport_zap_primary(pri);
su_seterrno(save_errno);
return NULL;
}
/** Destroy a primary transport and its secondary transports. @internal */
static
void tport_zap_primary(tport_primary_t *pri)
{
tport_primary_t **prip;
if (pri == NULL)
return;
assert(tport_is_primary(pri->pri_primary));
if (pri->pri_vtable->vtp_deinit_primary)
pri->pri_vtable->vtp_deinit_primary(pri);
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;
*prip != pri;
prip = &(*prip)->pri_next)
assert(*prip);
*prip = pri->pri_next;
tport_zap_secondary((tport_t *)pri);
}
/**Create a primary transport object with socket.
*
* Creates a primary transport object with a server socket, and then
* registers the socket with suitable events to the root.
*
* @param dad parent (master or primary) transport object
* @param ai pointer to addrinfo structure
* @param canon canonical name of node
* @param protoname name of the protocol
*/
static
tport_primary_t *tport_listen(tport_master_t *mr,
tport_vtable_t const *vtable,
tp_name_t tpn[1],
su_addrinfo_t *ai,
tagi_t *tags)
{
tport_primary_t *pri = NULL;
int err;
int errlevel = 3;
char buf[TPORT_HOSTPORTSIZE];
char const *protoname = vtable->vtp_name;
char const *culprit = "unknown";
su_sockaddr_t *su = (void *)ai->ai_addr;
/* Log an error, return error */
#define TPORT_LISTEN_ERROR(errno, what) \
((void)(err = errno, \
((err == EADDRINUSE || err == EAFNOSUPPORT || \
err == ESOCKTNOSUPPORT || err == EPROTONOSUPPORT || \
err == ENOPROTOOPT ? 7 : 3) < SU_LOG_LEVEL ? \
su_llog(tport_log, errlevel, \
"%s(%p): %s(pf=%d %s/%s): %s\n", \
__func__, pri ? (void *)pri : (void *)mr, what, \
ai->ai_family, protoname, \
tport_hostport(buf, sizeof(buf), su, 2), \
su_strerror(err)) : (void)0), \
tport_zap_primary(pri), \
su_seterrno(err)), \
(void *)NULL)
/* Create a primary transport object for another transport. */
pri = tport_alloc_primary(mr, vtable, tpn, ai, tags, &culprit);
if (pri == NULL)
return TPORT_LISTEN_ERROR(errno, culprit);
if (pri->pri_primary->tp_socket != INVALID_SOCKET) {
int index = 0;
tport_t *tp = pri->pri_primary;
su_wait_t wait[1] = { SU_WAIT_INIT };
if (su_wait_create(wait, tp->tp_socket, tp->tp_events) == -1)
return TPORT_LISTEN_ERROR(su_errno(), "su_wait_create");
/* Register receiving or accepting function with events specified above */
index = su_root_register(mr->mr_root, wait, tport_wakeup_pri, tp, 0);
if (index == -1) {
su_wait_destroy(wait);
return TPORT_LISTEN_ERROR(su_errno(), "su_root_register");
}
tp->tp_index = index;
}
pri->pri_primary->tp_has_connection = 0;
SU_DEBUG_5(("%s(%p): %s " TPN_FORMAT "\n",
__func__, (void *)pri, "listening at",
TPN_ARGS(pri->pri_primary->tp_name)));
return pri;
}
int tport_bind_socket(int socket,
su_addrinfo_t *ai,
char const **return_culprit)
{
su_sockaddr_t *su = (su_sockaddr_t *)ai->ai_addr;
socklen_t sulen = (socklen_t)(ai->ai_addrlen);
if (bind(socket, ai->ai_addr, sulen) == -1) {
return *return_culprit = "bind", -1;
}
if (getsockname(socket, &su->su_sa, &sulen) == SOCKET_ERROR) {
return *return_culprit = "getsockname", -1;
}
ai->ai_addrlen = sulen;
#if defined (__linux__) && defined (SU_HAVE_IN6)
if (ai->ai_family == AF_INET6) {
if (!SU_SOCKADDR_INADDR_ANY(su) &&
(IN6_IS_ADDR_V4MAPPED(&su->su_sin6.sin6_addr) ||
IN6_IS_ADDR_V4COMPAT(&su->su_sin6.sin6_addr))) {
su_sockaddr_t su0[1];
memcpy(su0, su, sizeof su0);
memset(su, 0, ai->ai_addrlen = sizeof su->su_sin);
su->su_family = ai->ai_family = AF_INET;
su->su_port = su0->su_port;
#ifndef IN6_V4MAPPED_TO_INADDR
#define IN6_V4MAPPED_TO_INADDR(in6, in4) \
memcpy((in4), 12 + (uint8_t *)(in6), sizeof(struct in_addr))
#endif
IN6_V4MAPPED_TO_INADDR(&su0->su_sin6.sin6_addr, &su->su_sin.sin_addr);
}
}
#endif
return 0;
}
/** Indicate stack that a transport has been updated */
void tport_has_been_updated(tport_t *self)
{
self->tp_pri->pri_updating = 0;
if (self->tp_master->mr_tpac->tpac_address)
self->tp_master->mr_tpac->tpac_address(self->tp_master->mr_stack, self);
}
static
int tport_set_events(tport_t *self, int set, int clear)
{
int events;
if (self == NULL)
return -1;
events = (self->tp_events | set) & ~clear;
self->tp_events = events;
if (self->tp_pri->pri_vtable->vtp_set_events)
return self->tp_pri->pri_vtable->vtp_set_events(self);
SU_DEBUG_7(("tport_set_events(%p): events%s%s%s\n", (void *)self,
(events & SU_WAIT_IN) ? " IN" : "",
(events & SU_WAIT_OUT) ? " OUT" : "",
SU_WAIT_CONNECT != SU_WAIT_OUT &&
(events & SU_WAIT_CONNECT) ? " CONNECT" : ""));
return
su_root_eventmask(self->tp_master->mr_root,
self->tp_index,
self->tp_socket,
self->tp_events = events);
}
/**Allocate a secondary transport. @internal
*
* 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
* @parma accepted true if the socket was accepted from server socket
*
* @return
* Pointer to the newly created transport, or NULL upon an error.
*
* @note The socket is always closed upon error.
*/
tport_t *tport_alloc_secondary(tport_primary_t *pri,
int socket,
int accepted,
char const **return_reason)
{
tport_master_t *mr = pri->pri_master;
tport_t *self;
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));
self->tp_refs = -1; /* Freshly allocated */
self->tp_master = mr;
self->tp_pri = pri;
self->tp_params = pri->pri_params;
self->tp_accepted = accepted != 0;
self->tp_reusable = pri->pri_primary->tp_reusable;
self->tp_magic = pri->pri_primary->tp_magic;
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) {
if (pri->pri_vtable->vtp_deinit_secondary)
pri->pri_vtable->vtp_deinit_secondary(self);
su_home_zap(self->tp_home);
return NULL;
}
/* Set IP TOS if it is set in primary */
tport_set_tos(socket,
pri->pri_primary->tp_addrinfo,
pri->pri_params->tpp_tos);
}
else {
su_close(socket);
*return_reason = "malloc";
}
return self;
}
/** Create a connected transport object with socket.
*
* The function tport_connect() creates a secondary transport with a
* connected socket. It registers the socket with suitable events to the
* root.
*
* @param pri primary transport object
* @param ai pointer to addrinfo structure
* @param tpn canonical name of node
*/
static
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);
tp = tport_base_connect(pri, ai, ai, tpn);
if (tp)
tport_set_secondary_timer(tp);
return tp;
}
/**Create a connected transport object with socket.
*
* The function tport_connect() creates a secondary transport with a
* connected socket. It registers the socket with suitable events to the
* root.
*
* @param pri primary transport object
* @param ai pointer to addrinfo structure describing socket
* @param real_ai pointer to addrinfo structure describing real target
* @param tpn canonical name of node
*/
tport_t *tport_base_connect(tport_primary_t *pri,
su_addrinfo_t *ai,
su_addrinfo_t *real_ai,
tp_name_t const *tpn)
{
tport_master_t *mr = pri->pri_master;
tport_t *self = NULL;
su_socket_t s, server_socket;
su_wait_t wait[1] = { SU_WAIT_INIT };
su_wakeup_f wakeup = tport_wakeup;
int index = 0;
int events = SU_WAIT_IN | SU_WAIT_ERR;
int err;
unsigned errlevel = 3;
char buf[TPORT_HOSTPORTSIZE];
char const *what;
/* Log an error, return error */
#define TPORT_CONNECT_ERROR(errno, what) \
return \
((void)(err = errno, \
su_wait_destroy(wait), \
(SU_LOG_LEVEL >= errlevel ? \
su_llog(tport_log, errlevel, \
"%s(%p): %s(pf=%d %s/%s): %s\n", \
__func__, (void *)pri, #what, ai->ai_family, \
tpn->tpn_proto, \
tport_hostport(buf, sizeof(buf), \
(void *)ai->ai_addr, 2), \
su_strerror(err)) : (void)0), \
tport_zap_secondary(self), \
su_seterrno(err)), \
(void *)NULL)
s = su_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (s == INVALID_SOCKET)
TPORT_CONNECT_ERROR(su_errno(), "socket");
what = "tport_alloc_secondary";
if ((self = tport_alloc_secondary(pri, s, 0, &what)) == NULL)
TPORT_CONNECT_ERROR(errno, what);
self->tp_conn_orient = 1;
if ((server_socket = pri->pri_primary->tp_socket) != INVALID_SOCKET) {
su_sockaddr_t susa;
socklen_t susalen = sizeof(susa);
/* Bind this socket to same IP address as the primary server socket */
if (getsockname(server_socket, &susa.su_sa, &susalen) < 0) {
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(("%s(%p): bind(local-ip): %s\n",
__func__, (void *)self, su_strerror(su_errno())));
}
}
}
/* Set sockname for the tport */
if (tport_setname(self, tpn->tpn_proto, real_ai, tpn->tpn_canon) == -1)
TPORT_CONNECT_ERROR(su_errno(), tport_setname);
/* Try to have a non-blocking connect().
* The su_wait_create() below makes the socket non-blocking anyway. */
su_setblocking(s, 0);
if (connect(s, ai->ai_addr, (socklen_t)(ai->ai_addrlen)) == SOCKET_ERROR) {
err = su_errno();
if (!su_is_blocking(err))
TPORT_CONNECT_ERROR(err, connect);
events = SU_WAIT_CONNECT | SU_WAIT_ERR;
wakeup = tport_connected;
what = "connecting";
}
else {
what = "connected";
self->tp_is_connected = 1;
}
if (su_wait_create(wait, s, self->tp_events = events) == -1)
TPORT_CONNECT_ERROR(su_errno(), su_wait_create);
/* Register receiving function with events specified above */
if ((index = su_root_register(mr->mr_root, wait, wakeup, self, 0)) == -1)
TPORT_CONNECT_ERROR(su_errno(), su_root_register);
self->tp_index = index;
if (ai == real_ai) {
SU_DEBUG_5(("%s(%p): %s to " TPN_FORMAT "\n",
__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,
tport_hostport(buf, sizeof(buf), (void *)ai->ai_addr, 2),
TPN_ARGS(self->tp_name)));
}
tprb_append(&pri->pri_open, self);
return self;
}
/** Destroy a secondary transport. @internal */
void tport_zap_secondary(tport_t *self)
{
tport_master_t *mr;
if (self == NULL)
return;
/* Remove from rbtree */
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) &&
self->tp_pri->pri_vtable->vtp_deinit_secondary)
self->tp_pri->pri_vtable->vtp_deinit_secondary(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));
}
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));
}
if (self->tp_pused) {
SU_DEBUG_3(("%s(%p): zapped while pending\n",
__func__, (void *)self));
}
mr = self->tp_master;
#if HAVE_SOFIA_STUN
tport_stun_server_remove_socket(self);
#endif
if (self->tp_index)
su_root_deregister(mr->mr_root, self->tp_index);
self->tp_index = 0;
if (self->tp_socket != INVALID_SOCKET)
su_close(self->tp_socket);
self->tp_socket = INVALID_SOCKET;
su_home_zap(self->tp_home);
}
/** Create a new reference to a transport object. */
tport_t *tport_ref(tport_t *tp)
{
if (tp) {
if (tp->tp_refs >= 0)
tp->tp_refs++;
else if (tp->tp_refs == -1)
tp->tp_refs = 1;
}
return tp;
}
/** Destroy reference to a transport object. */
void tport_unref(tport_t *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. */
tport_t *tport_incref(tport_t *tp)
{
return tport_ref(tp);
}
/** Destroy a transport reference. */
void tport_decref(tport_t **ttp)
{
assert(ttp);
if (*ttp) {
tport_unref(*ttp);
*ttp = NULL;
}
}
/** Get transport parameters.
*
* @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, ...)
{
ta_list ta;
int n;
tport_params_t const *tpp;
tport_primary_t const *pri = self->tp_pri;
int connect;
if (self == NULL)
return su_seterrno(EINVAL);
tpp = self->tp_params;
ta_start(ta, tag, value);
connect = tpp->tpp_conn_orient
/* Only dgram primary is *not* connection-oriented */
|| !tport_is_primary(self) || !tport_is_dgram(self);
n = tl_tgets(ta_args(ta),
TPTAG_MTU((usize_t)tpp->tpp_mtu),
TPTAG_REUSE(self->tp_reusable),
TPTAG_CONNECT(connect),
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());
ta_end(ta);
return n;
}
/** Set transport parameters.
*
* @param self pointer to a transport object
* @param tag,value,... list of tags
*
* @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().
*/
int tport_set_params(tport_t *self,
tag_type_t tag, tag_value_t value, ...)
{
ta_list ta;
int n;
tport_params_t tpp[1], *tpp0;
usize_t mtu;
int connect, sdwn_error, reusable, stun_server, pong2ping;
if (self == NULL)
return su_seterrno(EINVAL);
memcpy(tpp, tpp0 = self->tp_params, sizeof tpp);
mtu = tpp->tpp_mtu;
connect = tpp->tpp_conn_orient;
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);
n = tl_gets(ta_args(ta),
TPTAG_MTU_REF(mtu),
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),
TPTAG_SIGCOMP_LIFETIME_REF(tpp->tpp_sigcomp_lifetime),
TPTAG_CONNECT_REF(connect),
TPTAG_SDWN_ERROR_REF(sdwn_error),
TPTAG_REUSE_REF(reusable),
TPTAG_STUN_SERVER_REF(stun_server),
TPTAG_TOS_REF(tpp->tpp_tos),
TAG_END());
ta_end(ta);
if (n == 0)
return 0;
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)
tpp->tpp_thrprqsize = tpp0->tpp_thrprqsize;
if (tpp->tpp_sigcomp_lifetime != 0 && tpp->tpp_sigcomp_lifetime < 30)
tpp->tpp_sigcomp_lifetime = 30;
if (tpp->tpp_qsize >= 1000)
tpp->tpp_qsize = 1000;
if (mtu > UINT_MAX)
mtu = UINT_MAX;
tpp->tpp_mtu = (unsigned)mtu;
/* Currently only primary UDP transport can *not* be connection oriented */
tpp->tpp_conn_orient = connect;
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) {
tpp0 = su_zalloc(self->tp_home, sizeof *tpp0); if (!tpp0) return -1;
self->tp_params = tpp0;
}
memcpy(tpp0, tpp, sizeof tpp);
if (tport_is_secondary(self))
tport_set_secondary_timer(self);
return n;
}
extern tport_vtable_t const tport_udp_vtable;
extern tport_vtable_t const tport_tcp_vtable;
extern tport_vtable_t const tport_tls_vtable;
extern tport_vtable_t const tport_sctp_vtable;
extern tport_vtable_t const tport_udp_client_vtable;
extern tport_vtable_t const tport_tcp_client_vtable;
extern tport_vtable_t const tport_sctp_client_vtable;
extern tport_vtable_t const tport_tls_client_vtable;
extern tport_vtable_t const tport_http_connect_vtable;
extern tport_vtable_t const tport_threadpool_vtable;
#define TPORT_NUMBER_OF_TYPES 64
tport_vtable_t const *tport_vtables[TPORT_NUMBER_OF_TYPES + 1] =
{
#if HAVE_SOFIA_NTH
&tport_http_connect_vtable,
#endif
#if HAVE_TLS
&tport_tls_client_vtable,
&tport_tls_vtable,
#endif
#if HAVE_SCTP /* SCTP is broken */
&tport_sctp_client_vtable,
&tport_sctp_vtable,
#endif
&tport_tcp_client_vtable,
&tport_tcp_vtable,
&tport_udp_client_vtable,
&tport_udp_vtable,
#if 0
&tport_threadpool_vtable,
#endif
#if HAVE_SOFIA_STUN
&tport_stun_vtable,
#endif
};
/** Register new transport vtable */
int tport_register_type(tport_vtable_t const *vtp)
{
int i;
for (i = TPORT_NUMBER_OF_TYPES; i >= 0; i--) {
if (tport_vtables[i] == NULL) {
tport_vtables[i] = vtp;
return 0;
}
}
su_seterrno(ENOMEM);
return -1;
}
/**Get a vtable for given protocol */
tport_vtable_t const *tport_vtable_by_name(char const *protoname,
enum tport_via public)
{
int i;
for (i = TPORT_NUMBER_OF_TYPES; i >= 0; i--) {
tport_vtable_t const *vtable = tport_vtables[i];
if (vtable == NULL)
continue;
if (vtable->vtp_public != public)
continue;
if (strcasecmp(vtable->vtp_name, protoname))
continue;
assert(vtable->vtp_pri_size >= sizeof (tport_primary_t));
assert(vtable->vtp_secondary_size >= sizeof (tport_t));
return vtable;
}
return NULL;
}
#if 0
tport_set_f const *tport_set_methods[TPORT_NUMBER_OF_TYPES + 1] =
{
tport_server_bind_set,
tport_client_bind_set,
tport_threadpool_set,
#if HAVE_SOFIA_NTH
tport_http_connect_set,
#endif
#if HAVE_TLS
tport_tls_set,
#endif
NULL
};
int tport_bind_set(tport_master_t *mr,
tp_name_t const *tpn,
char const * const transports[],
tagi_t const *taglist,
tport_set_t **return_set,
int set_size)
{
int i;
for (i = TPORT_NUMBER_OF_TYPES; i >= 0; i--) {
tport_set_f const *perhaps = tport_vtables[i];
int result;
if (perhaps == NULL)
continue;
result = perhaps(mr, tpn, transports, taglist, return_set, set_size);
if (result != 0)
return result;
}
return 0;
}
#endif
/** Bind transport objects.
*
* @param self pointer to a transport object
* @param tpn desired transport address
* @param transports list of protocol names supported by stack
* @param tag,value,... tagged argument list
*
* @TAGS
* TPTAG_SERVER(), TPTAG_PUBLIC(), TPTAG_IDENT(), TPTAG_HTTP_CONNECT(),
* TPTAG_CERTIFICATE(), TPTAG_TLS_VERSION(), and tags used with
* tport_set_params(), especially TPTAG_QUEUESIZE().
*/
int tport_tbind(tport_t *self,
tp_name_t const *tpn,
char const * const transports[],
tag_type_t tag, tag_value_t value, ...)
{
ta_list ta;
int server = 1, retval, public = 0;
tp_name_t mytpn[1];
tport_master_t *mr;
char const *http_connect = NULL;
if (self == NULL || tport_is_secondary(self) ||
tpn == NULL || transports == NULL) {
su_seterrno(EINVAL);
return -1;
}
*mytpn = *tpn;
if (mytpn->tpn_ident == NULL)
mytpn->tpn_ident = self->tp_ident;
ta_start(ta, tag, value);
tl_gets(ta_args(ta),
TPTAG_SERVER_REF(server),
TPTAG_PUBLIC_REF(public),
TPTAG_IDENT_REF(mytpn->tpn_ident),
TPTAG_HTTP_CONNECT_REF(http_connect),
TAG_END());
mr = self->tp_master; assert(mr);
if (http_connect && public == 0)
public = tport_type_connect;
if (public && public != tport_type_stun)
server = 0;
if (server)
retval = tport_bind_server(mr, mytpn, transports, public, ta_args(ta));
else
retval = tport_bind_client(mr, mytpn, transports, public, ta_args(ta));
ta_end(ta);
return retval;
}
/** Bind primary transport objects used by a client-only application.
* @internal
*/
int tport_bind_client(tport_master_t *mr,
tp_name_t const *tpn,
char const * const transports[],
enum tport_via public,
tagi_t *tags)
{
int i;
tport_primary_t *pri = NULL, **tbf;
tp_name_t tpn0[1] = {{ "*", "*", "*", "*", NULL, NULL }};
char const *why = "unknown";
tport_vtable_t const *vtable;
if (public == tport_type_local)
public = tport_type_client;
SU_DEBUG_5(("%s(%p) to " TPN_FORMAT "\n",
__func__, (void *)mr, TPN_ARGS(tpn)));
memset(tpn0, 0, sizeof(tpn0));
for (tbf = &mr->mr_primaries; *tbf; tbf = &(*tbf)->pri_next)
;
for (i = 0; transports[i]; i++) {
su_addrinfo_t hints[1];
char const *proto = transports[i];
if (strcmp(proto, tpn->tpn_proto) != 0 &&
strcmp(tpn->tpn_proto, tpn_any) != 0)
continue;
vtable = tport_vtable_by_name(proto, public);
if (!vtable)
continue;
/* Resolve protocol, skip unknown transport protocols */
if (getprotohints(hints, proto, AI_PASSIVE) < 0)
continue;
tpn0->tpn_proto = proto;
tpn0->tpn_comp = tpn->tpn_comp;
tpn0->tpn_ident = tpn->tpn_ident;
hints->ai_canonname = "*";
if (!(pri = tport_alloc_primary(mr, vtable, tpn0, hints, tags, &why)))
break;
pri->pri_public = tport_type_client; /* XXX */
}
if (!pri) {
SU_DEBUG_3(("tport_alloc_primary: %s failed\n", why));
tport_zap_primary(*tbf);
}
return pri ? 0 : -1;
}
/** Bind primary transport objects used by a server application. */
int tport_bind_server(tport_master_t *mr,
tp_name_t const *tpn,
char const * const transports[],
enum tport_via public,
tagi_t *tags)
{
char hostname[256];
char const *canon = NULL, *host, *service;
int error = 0, not_supported, family = 0;
tport_primary_t *pri = NULL, **tbf;
su_addrinfo_t *ai, *res = NULL;
unsigned port, port0, port1, old;
unsigned short step = 0;
bind6only_check(mr);
(void)hostname;
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 */
host = NULL;
}
#ifdef SU_HAVE_IN6
else if (tpn->tpn_host && tpn->tpn_host[0] == '[') {
/* Remove [] around IPv6 addresses. */
host = strcpy(hostname, tpn->tpn_host + 1);
hostname[strlen(hostname) - 1] = '\0';
}
#endif
else
host = tpn->tpn_host;
if (tpn->tpn_port != NULL && strlen(tpn->tpn_port) > 0 &&
strcmp(tpn->tpn_port, tpn_any) != 0)
service = tpn->tpn_port;
else
service = "";
if (host && (strcmp(host, "0.0.0.0") == 0 || strcmp(host, "0") == 0))
host = NULL, family = AF_INET;
#if SU_HAVE_IN6
else if (host && strcmp(host, "::") == 0)
host = NULL, family = AF_INET6;
#endif
if (tpn->tpn_canon && strcmp(tpn->tpn_canon, tpn_any) &&
(host || tpn->tpn_canon != tpn->tpn_host))
canon = tpn->tpn_canon;
if (tport_server_addrinfo(mr, canon, family,
host, service, tpn->tpn_proto,
transports, &res) < 0)
return -1;
for (tbf = &mr->mr_primaries; *tbf; tbf = &(*tbf)->pri_next)
;
port = port0 = port1 = ntohs(((su_sockaddr_t *)res->ai_addr)->su_port);
error = EPROTONOSUPPORT;
/*
* Loop until we can bind all the transports requested
* by the transport user to the same port.
*/
for (;;) {
for (ai = res; ai; ai = ai->ai_next) {
tp_name_t tpname[1];
su_addrinfo_t ainfo[1];
su_sockaddr_t su[1];
tport_vtable_t const *vtable;
vtable = tport_vtable_by_name(ai->ai_canonname, public);
if (!vtable)
continue;
tport_addrinfo_copy(ainfo, su, sizeof su, ai);
ainfo->ai_canonname = (char *)canon;
su->su_port = htons(port);
memcpy(tpname, tpn, sizeof tpname);
tpname->tpn_canon = canon;
tpname->tpn_host = host;
SU_DEBUG_9(("%s(%p): calling tport_listen for %s\n",
__func__, (void *)mr, ai->ai_canonname));
pri = tport_listen(mr, vtable, tpname, ainfo, tags);
if (!pri) {
switch (error = su_errno()) {
case EADDRNOTAVAIL: /* Not our address */
case ENOPROTOOPT: /* Protocol not supported */
case ESOCKTNOSUPPORT: /* Socket type not supported */
continue;
default:
break;
}
break;
}
not_supported = 0;
if (port0 == 0 && port == 0) {
port = port1 = ntohs(su->su_port);
assert(public != tport_type_server || port != 0);
}
}
if (ai == NULL)
break;
while (*tbf)
tport_zap_primary(*tbf);
if (error != EADDRINUSE || port0 != 0 || port == 0)
break;
while (step == 0) {
/* step should be relative prime to 65536 - 1024 */
/* 65536 - 1024 = 7 * 3 * 3 * 1024 */
step = su_randint(1, 65535 - 1024 - 1) | 1;
if (step % 3 == 0)
step = (step + 2) % (65536 - 1024);
if (step % 7 == 0)
step = (step + 2) % (65536 - 1024);
}
old = port; port += step; if (port >= 65536) port -= (65536 - 1024);
if (port == port1) /* All ports in use! */
break;
SU_DEBUG_3(("%s(%p): cannot bind all transports to port %u, trying %u\n",
__func__, (void *)mr, old, port));
}
tport_freeaddrinfo(res);
if (!*tbf) {
su_seterrno(error);
return -1;
}
return 0;
}
/** Check if we can bind to IPv6 separately from IPv4 bind */
static
int bind6only_check(tport_master_t *mr)
{
int retval = 0;
#if SU_HAVE_IN6
su_sockaddr_t su[1], su4[1];
socklen_t sulen, su4len;
int s6, s4;
if (mr->mr_boundserver)
return 0;
s4 = su_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
s6 = su_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
memset(su, 0, sizeof *su);
su->su_len = sulen = (sizeof su->su_sin6);
su->su_family = AF_INET6;
memset(su4, 0, sizeof *su4);
su4->su_len = su4len = (sizeof su->su_sin);
su4->su_family = AF_INET;
if (bind(s6, &su->su_sa, sulen) < 0)
;
else if (getsockname(s6, &su->su_sa, &sulen) < 0)
;
else if ((su4->su_port = su->su_port) != 0 &&
bind(s4, &su4->su_sa, su4len) == 0)
retval = 1;
su_close(s6), su_close(s4);
mr->mr_bindv6only = retval;
mr->mr_boundserver = 1;
#endif
return retval;
}
/* Number of supported transports */
#define TPORT_N (8)
/** Return list of addrinfo structures matching to
* canon/host/service/protocol
*/
static
int tport_server_addrinfo(tport_master_t *mr,
char const *canon,
int family,
char const *host,
char const *service,
char const *protocol,
char const * const transports[],
su_addrinfo_t **return_addrinfo)
{
int i, N;
su_addrinfo_t hints[TPORT_N + 1];
*return_addrinfo = NULL;
/*
* Resolve all the transports requested by the protocol
*/
for (i = 0, N = 0; transports[i] && N < TPORT_N; i++) {
su_addrinfo_t *ai = &hints[N];
if (strcasecmp(transports[i], protocol) != 0 &&
strcmp(protocol, tpn_any) != 0)
continue;
/* Resolve protocol, skip unknown transport protocols. */
if (getprotohints(ai, transports[i], AI_PASSIVE) < 0)
continue;
ai->ai_family = family;
ai->ai_next = &hints[++N];
}
if (N == 0)
return su_seterrno(EPROTONOSUPPORT);
if (transports[i] /* Too many protocols */)
return su_seterrno(ENOMEM);
hints[N - 1].ai_next = NULL;
if (host) {
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,
su_gai_strerror(error)));
return su_seterrno(error != EAI_MEMORY ? ENOENT : ENOMEM);
}
return 0;
}
return tport_get_local_addrinfo(mr, service, hints, return_addrinfo);
}
/** Convert localinfo into addrinfo */
static
int
tport_get_local_addrinfo(tport_master_t *mr,
char const *port,
su_addrinfo_t const *hints,
su_addrinfo_t **return_ai)
{
int error, family;
su_localinfo_t lihints[1] = {{ 0 }};
su_localinfo_t *li, *li_result;
su_addrinfo_t const *h;
su_addrinfo_t *ai, **prev;
su_sockaddr_t *su;
unsigned long lport = 0;
char *rest;
prev = return_ai, *prev = NULL;
if (port) {
lport = strtoul(port, &rest, 10);
if (lport >= 65536) {
su_seterrno(EINVAL);
return -1;
}
}
family = hints->ai_family;
for (h = hints->ai_next; h && family; h = h->ai_next)
if (h->ai_family != family)
family = 0;
lihints->li_flags = 0;
lihints->li_family = family;
lihints->li_scope = LI_SCOPE_GLOBAL | LI_SCOPE_SITE | LI_SCOPE_HOST;
error = su_getlocalinfo(lihints, &li_result);
if (error) {
#if SU_HAVE_IN6
SU_DEBUG_3(("%s(%p): su_getlocalinfo() for %s address: %s\n",
__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,
family == AF_INET ? "ip4" : "ip",
su_gli_strerror(error)));
#endif
su_seterrno(ENOENT);
return -1;
}
for (li = li_result; li; li = li->li_next) {
for (h = hints; h; h = h->ai_next) {
if (h->ai_family && h->ai_family != li->li_family)
continue;
ai = calloc(1, sizeof *ai + li->li_addrlen);
if (ai == NULL)
break;
*prev = ai, prev = &ai->ai_next;
ai->ai_flags = AI_PASSIVE | TP_AI_ANY;
ai->ai_family = li->li_family;
ai->ai_socktype = h->ai_socktype;
ai->ai_protocol = h->ai_protocol;
ai->ai_canonname = h->ai_canonname;
ai->ai_addr = memcpy(ai + 1, li->li_addr,
ai->ai_addrlen = li->li_addrlen);
su = (void *)ai->ai_addr;
su->su_port = htons(lport);
}
}
su_freelocalinfo(li_result);
if (li) {
tport_freeaddrinfo(*return_ai);
su_seterrno(ENOMEM);
return -1;
}
if (*return_ai == NULL) {
su_seterrno(ENOENT);
return -1;
}
return 0;
}
su_inline su_addrinfo_t *get_next_addrinfo(su_addrinfo_t **all);
/** Translate address and service.
*
* This is a getaddrinfo() supporting multiple hints in a list.
*/
int tport_getaddrinfo(char const *node, char const *service,
su_addrinfo_t const *hints,
su_addrinfo_t **res)
{
su_addrinfo_t const *h0;
su_addrinfo_t *tbf, **prev;
int error = EAI_SOCKTYPE;
int i, N;
su_addrinfo_t *all[TPORT_N + 1]; /* Lists for all supported transports */
su_addrinfo_t *results[TPORT_N + 1];
void *addr;
int addrlen;
*res = NULL;
for (N = 0, h0 = hints; h0; h0 = h0->ai_next) {
su_addrinfo_t h[1];
*h = *h0, h->ai_next = NULL, h->ai_canonname = NULL;
error = su_getaddrinfo(node, service, h, &all[N]);
results[N] = all[N];
if (error == EAI_SOCKTYPE) {
SU_DEBUG_7(("%s(): su_getaddrinfo(%s, %s) for %s: %s\n",
__func__, node ? node : "\"\"", service,
h0->ai_canonname, su_gai_strerror(error)));
continue;
}
if (error || !all[N])
break;
N++;
}
if (h0)
for (i = 0; i < N; i++)
su_freeaddrinfo(all[i]);
if (error)
return error;
/* Combine all the valid addrinfo structures to a single list */
prev = &tbf, tbf = NULL;
for (;;) {
su_addrinfo_t *ai = NULL, *ai0;
for (i = 0, h0 = hints; i < N; i++, h0 = h0->ai_next) {
if ((ai = get_next_addrinfo(&results[i])))
break;
}
if (i == N)
break;
assert(ai);
addr = SU_ADDR((su_sockaddr_t *)ai->ai_addr);
addrlen = SU_ADDRLEN((su_sockaddr_t *)ai->ai_addr);
/* Copy all the addrinfo structures with same address to the list */
for (; i < N; i++, h0 = h0->ai_next) {
while ((ai0 = get_next_addrinfo(&results[i]))) {
void *a = SU_ADDR((su_sockaddr_t *)ai0->ai_addr);
if (memcmp(addr, a, addrlen)) /* Different address */
break;
results[i] = ai0->ai_next;
ai = calloc(1, sizeof *ai + ai0->ai_addrlen);
if (ai == NULL)
goto error;
*prev = memcpy(ai, ai0, sizeof *ai); prev = &ai->ai_next; *prev = NULL;
ai->ai_addr = memcpy(ai + 1, ai0->ai_addr, ai0->ai_addrlen);
ai->ai_canonname = h0->ai_canonname;
}
}
}
for (i = 0; i < N; i++)
su_freeaddrinfo(all[i]);
*res = tbf;
return 0;
error:
for (i = 0; i < N; i++)
su_freeaddrinfo(all[i]);
tport_freeaddrinfo(tbf);
return EAI_MEMORY;
}
su_inline
su_addrinfo_t *get_next_addrinfo(su_addrinfo_t **all)
{
su_addrinfo_t *ai;
while ((ai = *all)) {
if (ai->ai_family == AF_INET)
return ai;
#if SU_HAVE_IN6
if (ai->ai_family == AF_INET6)
return ai;
#endif
*all = ai->ai_next;
}
return ai;
}
static
void tport_freeaddrinfo(su_addrinfo_t *ai)
{
su_addrinfo_t *ai_next;
while (ai) {
ai_next = ai->ai_next;
free(ai);
ai = ai_next;
}
}
static
int tport_addrinfo_copy(su_addrinfo_t *dst, void *addr, socklen_t addrlen,
su_addrinfo_t const *src)
{
if (addrlen < src->ai_addrlen)
return -1;
memcpy(dst, src, sizeof *dst);
if (src->ai_addrlen < addrlen)
memset(addr, 0, addrlen);
dst->ai_addr = memcpy(addr, src->ai_addr, src->ai_addrlen);
dst->ai_next = NULL;
return 0;
}
/** Close a transport.
*
* 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",
__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;
self->tp_recv_close = 3;
if (self->tp_params->tpp_sdwn_error && self->tp_pused)
tport_error_report(self, -1, NULL);
if (self->tp_pri->pri_vtable->vtp_shutdown)
self->tp_pri->pri_vtable->vtp_shutdown(self, 2);
else if (self->tp_socket != -1)
shutdown(self->tp_socket, 2);
if (self->tp_index)
su_root_deregister(self->tp_master->mr_root, self->tp_index);
self->tp_index = 0;
#if SU_HAVE_BSDSOCK
if (self->tp_socket != -1)
su_close(self->tp_socket);
self->tp_socket = -1;
#endif
/* Zap the queued messages */
if (self->tp_queue) {
unsigned short i, N = self->tp_params->tpp_qsize;
for (i = 0; i < N; i++) {
if (self->tp_queue[i])
msg_ref_destroy(self->tp_queue[i]), self->tp_queue[i] = NULL;
}
}
self->tp_index = 0;
self->tp_events = 0;
}
/** Shutdown a transport.
*
* The tport_shutdown() shuts down a full-duplex transport connection
* partially or completely. If @a how is 0, the further incoming data is
* shut down. If @a how is 1, further outgoing data is shut down. If @a how
* is 2, both incoming and outgoing traffic is shut down.
*
*/
int tport_shutdown(tport_t *self, int how)
{
int retval;
if (!tport_is_secondary(self))
return -1;
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 >= 2 ||
(how == 0 && self->tp_send_close) ||
(how == 1 && self->tp_recv_close > 1)) {
tport_close(self);
return 1;
}
if (self->tp_pri->pri_vtable->vtp_shutdown)
self->tp_pri->pri_vtable->vtp_shutdown(self, how);
else
shutdown(self->tp_socket, how);
if (how == 0) {
self->tp_recv_close = 2;
tport_set_events(self, 0, SU_WAIT_IN);
if (self->tp_params->tpp_sdwn_error && self->tp_pused)
tport_error_report(self, -1, NULL);
}
else if (how == 1) {
self->tp_send_close = 2;
tport_set_events(self, 0, SU_WAIT_OUT);
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]) {
tport_pending_errmsg(self, self->tp_queue[i], EPIPE);
msg_ref_destroy(self->tp_queue[i]), self->tp_queue[i] = NULL;
}
}
}
}
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;
}
/** 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_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 "));
tport_close(tp);
tport_zap_secondary(tp);
}
return 0;
}
/**Convert sockaddr_t to a transport name.
*
* @retval 0 when successful
* @retval -1 upon an error
*/
int tport_convert_addr(su_home_t *home,
tp_name_t *tpn,
char const *protoname,
char const *canon,
su_sockaddr_t const *su)
{
tp_name_t name[1] = {{ NULL }};
char const *host;
char buf[TPORT_HOSTPORTSIZE];
char port[8];
size_t canonlen = canon ? strlen(canon) : 0;
if (su == NULL)
host = "*";
else if (!SU_SOCKADDR_INADDR_ANY(su))
host = tport_hostport(buf, sizeof(buf), su, 0);
else if (canonlen && su->su_family == AF_INET &&
strspn(canon, "0123456789.") == canonlen)
host = canon;
#if SU_HAVE_IN6
else if (canonlen && su->su_family == AF_INET6 &&
strspn(canon, "0123456789abcdefABCDEF:.") == canonlen)
host = canon;
#endif
else
host = localipname(su->su_family, buf, sizeof(buf));
if (host == NULL)
return -1;
if (su == NULL)
strcpy(port, "*");
else
snprintf(port, sizeof(port), "%u", ntohs(su->su_port));
name->tpn_proto = protoname;
name->tpn_host = host;
name->tpn_canon = canon ? canon : host;
name->tpn_port = port;
return tport_name_dup(home, tpn, name);
}
/** Set transport object name. @internal
*/
static
int tport_setname(tport_t *self,
char const *protoname,
su_addrinfo_t const *ai,
char const *canon)
{
su_addrinfo_t *selfai = self->tp_addrinfo;
if (tport_convert_addr(self->tp_home, self->tp_name,
protoname, canon,
(su_sockaddr_t *)ai->ai_addr) < 0)
return -1;
if (tport_is_secondary(self))
self->tp_ident = self->tp_pri->pri_primary->tp_ident;
selfai->ai_flags = ai->ai_flags & TP_AI_MASK;
selfai->ai_family = ai->ai_family;
selfai->ai_socktype = ai->ai_socktype;
selfai->ai_protocol = ai->ai_protocol;
selfai->ai_canonname = (char *)self->tp_name->tpn_canon;
if (ai->ai_addr) {
assert(ai->ai_family), assert(ai->ai_socktype), assert(ai->ai_protocol);
memcpy(self->tp_addr, ai->ai_addr, selfai->ai_addrlen = ai->ai_addrlen);
}
return 0;
}
/**Resolve protocol name.
*
* Convert a protocol name to IP protocol number and socket type used by
* su_getaddrinfo().
*
* @param hints hints with the protocol number and socktype [OUT]
* @param proto protocol name [IN]
* @param flags hint flags
*/
static
int getprotohints(su_addrinfo_t *hints,
char const *proto,
int flags)
{
memset(hints, 0, sizeof *hints);
hints->ai_flags = flags;
hints->ai_canonname = (char *)proto;
#if HAVE_TLS
if (strcasecmp(proto, "tls") == 0)
proto = "tcp";
#endif
#if HAVE_SCTP
if (strcasecmp(proto, "sctp") == 0) {
hints->ai_protocol = IPPROTO_SCTP;
hints->ai_socktype = SOCK_STREAM;
return 0;
}
#endif
if (strcasecmp(proto, "udp") == 0) {
hints->ai_protocol = IPPROTO_UDP;
hints->ai_socktype = SOCK_DGRAM;
return 0;
}
if (strcasecmp(proto, "tcp") == 0) {
hints->ai_protocol = IPPROTO_TCP;
hints->ai_socktype = SOCK_STREAM;
return 0;
}
return -1;
}
/** Get local IP.
*
* Get primary local IP address in URI format (IPv6 address will be
* []-quoted).
*/
static
char *localipname(int pf, char *buf, size_t bufsiz)
{
su_localinfo_t *li = NULL, hints[1] = {{ LI_NUMERIC | LI_CANONNAME }};
size_t n;
int error;
hints->li_family = pf;
#if SU_HAVE_IN6
if (pf == AF_INET6) {
/* Link-local addresses are not usable on IPv6 */
hints->li_scope = LI_SCOPE_GLOBAL | LI_SCOPE_SITE /* | LI_SCOPE_HOST */;
}
#endif
if ((error = su_getlocalinfo(hints, &li))) {
#if SU_HAVE_IN6
if (error == ELI_NOADDRESS && pf == AF_INET6) {
hints->li_family = AF_INET;
error = su_getlocalinfo(hints, &li);
if (error == ELI_NOADDRESS) {
hints->li_family = AF_INET6; hints->li_scope |= LI_SCOPE_HOST;
error = su_getlocalinfo(hints, &li);
}
if (error == ELI_NOADDRESS) {
hints->li_family = AF_INET;
error = su_getlocalinfo(hints, &li);
}
}
#endif
if (error) {
SU_DEBUG_1(("tport: su_getlocalinfo: %s\n", su_gli_strerror(error)));
return NULL;
}
}
assert(li); assert(li->li_canonname);
n = strlen(li->li_canonname);
if (li->li_family == AF_INET) {
if (n >= bufsiz)
return NULL;
memcpy(buf, li->li_canonname, n + 1);
}
else {
if (n + 2 >= bufsiz)
return NULL;
memcpy(buf + 1, li->li_canonname, n);
buf[0] = '['; buf[++n] = ']'; buf[++n] = '\0';
}
su_freelocalinfo(li);
return buf;
}
/** Process errors from transport. */
void tport_error_report(tport_t *self, int errcode,
su_sockaddr_t const *addr)
{
char const *errmsg;
if (errcode == 0)
return;
else if (errcode > 0)
errmsg = su_strerror(errcode);
else
/* Should be something like ENOTCONN */
errcode = 0, errmsg = "stream closed";
if (addr && addr->su_family == AF_UNSPEC)
addr = NULL;
/* Mark this connection as unusable */
if (errcode > 0 && tport_has_connection(self))
self->tp_reusable = 0;
/* Report error */
if (addr && tport_pending_error(self, addr, errcode))
;
else if (tport_is_secondary(self) &&
tport_pending_error(self, NULL, errcode) > 0)
;
else if (self->tp_master->mr_tpac->tpac_error) {
char *dstname = NULL;
char hp[TPORT_HOSTPORTSIZE];
if (addr)
dstname = tport_hostport(hp, sizeof hp, addr, 1);
STACK_ERROR(self, errcode, dstname);
}
else {
if (tport_is_primary(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,
errmsg, self->tp_protoname, self->tp_host, self->tp_port));
}
/* Close connection */
if (!self->tp_closed && errcode > 0 && tport_has_connection(self))
tport_close(self);
}
/** Accept a new connection.
*
* The function tport_accept() accepts a new connection and creates a
* secondary transport object for the new socket.
*/
int tport_accept(tport_primary_t *pri, int events)
{
tport_t *self;
su_addrinfo_t ai[1];
su_sockaddr_t su[1];
socklen_t sulen = sizeof su;
su_socket_t s = INVALID_SOCKET, l = pri->pri_primary->tp_socket;
char const *reason = "accept";
if (events & SU_WAIT_ERR)
tport_error_event(pri->pri_primary);
if (!(events & SU_WAIT_ACCEPT))
return 0;
memcpy(ai, pri->pri_primary->tp_addrinfo, sizeof ai);
ai->ai_canonname = NULL;
s = accept(l, &su->su_sa, &sulen);
if (s < 0) {
tport_error_report(pri->pri_primary, su_errno(), NULL);
return 0;
}
ai->ai_addr = &su->su_sa, ai->ai_addrlen = sulen;
/* Alloc a new transport object, then register socket events with it */
self = tport_alloc_secondary(pri, s, 1, &reason);
if (self) {
int i;
su_root_t *root = self->tp_master->mr_root;
su_wakeup_f wakeup = tport_wakeup;
int events = SU_WAIT_IN|SU_WAIT_ERR|SU_WAIT_HUP;
su_wait_t wait[1] = { SU_WAIT_INIT };
SU_CANONIZE_SOCKADDR(su);
if (/* Create wait object with appropriate events. */
su_wait_create(wait, s, events) != -1
/* Register socket to root */
&&
(i = su_root_register(root, wait, wakeup, self, 0)) != -1) {
self->tp_index = i;
self->tp_conn_orient = 1;
self->tp_is_connected = 1;
self->tp_events = 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)));
tprb_append(&pri->pri_open, self);
/* Return succesfully */
return 0;
}
}
else
su_wait_destroy(wait);
/* Failure: shutdown socket, */
tport_close(self);
tport_zap_secondary(self);
}
/* XXX - report error ? */
return 0;
}
/** Allocate a new message object */
msg_t *tport_msg_alloc(tport_t const *self, usize_t size)
{
if (self) {
tport_master_t *mr = self->tp_master;
msg_t *msg = mr->mr_tpac->tpac_alloc(mr->mr_stack, mr->mr_log,
NULL, size, self, NULL);
if (msg) {
su_addrinfo_t *mai = msg_addrinfo(msg);
su_addrinfo_t const *tai = self->tp_addrinfo;
mai->ai_family = tai->ai_family;
mai->ai_protocol = tai->ai_protocol;
mai->ai_socktype = tai->ai_socktype;
}
return msg;
}
else {
return NULL;
}
}
/** Process events for socket waiting to be connected
*/
static int tport_connected(su_root_magic_t *magic, su_wait_t *w, tport_t *self)
{
int events = su_wait_events(w, self->tp_socket);
tport_master_t *mr = self->tp_master;
su_wait_t wait[1] = { SU_WAIT_INIT };
int error;
SU_DEBUG_7(("tport_connected(%p): events%s%s\n", (void *)self,
events & SU_WAIT_CONNECT ? " CONNECTED" : "",
events & SU_WAIT_ERR ? " ERR" : ""));
#if HAVE_POLL
assert(w->fd == self->tp_socket);
#endif
if (events & SU_WAIT_ERR)
tport_error_event(self);
if (!(events & SU_WAIT_CONNECT) || self->tp_closed) {
return 0;
}
error = su_soerror(self->tp_socket);
if (error) {
tport_error_report(self, error, NULL);
return 0;
}
self->tp_is_connected = 1;
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;
}
if (tport_has_queued(self))
tport_send_event(self);
else
tport_set_secondary_timer(self);
return 0;
}
/** Process events for primary socket */
static int tport_wakeup_pri(su_root_magic_t *m, su_wait_t *w, tport_t *self)
{
tport_primary_t *pri = self->tp_pri;
int events = su_wait_events(w, self->tp_socket);
#if HAVE_POLL
assert(w->fd == self->tp_socket);
#endif
SU_DEBUG_7(("%s(%p): events%s%s%s%s%s%s\n",
"tport_wakeup_pri", (void *)self,
events & SU_WAIT_IN ? " IN" : "",
SU_WAIT_ACCEPT != SU_WAIT_IN &&
(events & SU_WAIT_ACCEPT) ? " ACCEPT" : "",
events & SU_WAIT_OUT ? " OUT" : "",
events & SU_WAIT_HUP ? " HUP" : "",
events & SU_WAIT_ERR ? " ERR" : "",
self->tp_closed ? " (closed)" : ""));
if (pri->pri_vtable->vtp_wakeup_pri)
return pri->pri_vtable->vtp_wakeup_pri(pri, events);
else
return tport_base_wakeup(self, events);
}
/** Process events for connected socket */
static int tport_wakeup(su_root_magic_t *magic, su_wait_t *w, tport_t *self)
{
int events = su_wait_events(w, self->tp_socket);
#if HAVE_POLL
assert(w->fd == self->tp_socket);
#endif
SU_DEBUG_7(("%s(%p): events%s%s%s%s%s\n",
"tport_wakeup", (void *)self,
events & SU_WAIT_IN ? " IN" : "",
events & SU_WAIT_OUT ? " OUT" : "",
events & SU_WAIT_HUP ? " HUP" : "",
events & SU_WAIT_ERR ? " ERR" : "",
self->tp_closed ? " (closed)" : ""));
if (self->tp_pri->pri_vtable->vtp_wakeup)
return self->tp_pri->pri_vtable->vtp_wakeup(self, events);
else
return tport_base_wakeup(self, events);
}
static int tport_base_wakeup(tport_t *self, int events)
{
int error = 0;
if (events & SU_WAIT_ERR)
error = tport_error_event(self);
if ((events & SU_WAIT_OUT) && !self->tp_closed)
tport_send_event(self);
if ((events & SU_WAIT_IN) && !self->tp_closed)
tport_recv_event(self);
if ((events & SU_WAIT_HUP) && !self->tp_closed)
tport_hup_event(self);
if (error) {
if (self->tp_closed && error == EPIPE)
return 0;
tport_error_report(self, error, NULL);
}
return 0;
}
/** Stop reading from socket until tport_continue() is called. */
int tport_stall(tport_t *self)
{
return tport_set_events(self, 0, SU_WAIT_IN);
}
/** Continue reading from socket. */
int tport_continue(tport_t *self)
{
if (self == NULL || self->tp_recv_close)
return -1;
return tport_set_events(self, SU_WAIT_IN, 0);
}
/** Process "hangup" event.
*
*/
void tport_hup_event(tport_t *self)
{
SU_DEBUG_7(("%s(%p)\n", __func__, (void *)self));
if (self->tp_msg) {
su_time_t now = su_now();
msg_recv_commit(self->tp_msg, 0, 1);
tport_parse(self, 1, now);
}
if (!tport_is_secondary(self))
return;
/* End of stream */
tport_shutdown0(self, 0);
tport_set_secondary_timer(self);
}
/** Receive data available on the socket.
*
* @retval -1 error
* @retval 0 end-of-stream
* @retval 1 normal receive
* @retval 2 incomplete recv, recv again
* @retval 3 STUN keepalive, ignore
*/
su_inline
int tport_recv_data(tport_t *self)
{
return self->tp_pri->pri_vtable->vtp_recv(self);
}
/** Process "ready to receive" event.
*
*/
void tport_recv_event(tport_t *self)
{
int again;
SU_DEBUG_7(("%s(%p)\n", "tport_recv_event", (void *)self));
do {
/* Receive data from socket */
again = tport_recv_data(self);
su_time(&self->tp_rtime);
#if HAVE_SOFIA_STUN
if (again == 3) /* STUN keepalive */
return;
#endif
if (again < 0) {
int error = su_errno();
if (!su_is_blocking(error)) {
tport_error_report(self, error, NULL);
return;
}
else {
SU_DEBUG_3(("%s: recvfrom(): %s (%d)\n", __func__,
su_strerror(EAGAIN), EAGAIN));
}
}
if (again >= 0)
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_shutdown0(self, tport_has_queued(self) ? 0 : 2);
}
}
tport_set_secondary_timer(self);
}
/*
* Parse the data and feed complete messages to the stack
*/
static void tport_parse(tport_t *self, int complete, su_time_t now)
{
msg_t *msg, *next = NULL;
int n, streaming, stall = 0;
for (msg = self->tp_msg; msg; msg = next) {
n = msg_extract(msg); /* Parse message */
streaming = 0;
if (n == 0) {
if (complete)
msg_mark_as_complete(msg, MSG_FLG_ERROR), n = -1;
else if (!(streaming = msg_is_streaming(msg))) {
tport_sigcomp_accept_incomplete(self, msg);
break;
}
}
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)));
/* Do not try to read anymore from this connection? */
if (tport_is_stream(self) &&
msg_get_flags(msg, MSG_FLG_TOOLARGE | MSG_FLG_ERROR))
self->tp_recv_close = stall = 1;
if (n == -1)
next = NULL;
else if (streaming)
msg_ref_create(msg); /* Keep a reference */
else if (tport_is_stream(self))
next = msg_next(msg);
else
next = NULL;
tport_deliver(self, msg, next, self->tp_comp, now);
if (streaming && next == NULL)
break;
}
if (stall)
tport_stall(self);
if (self->tp_rlogged != msg)
self->tp_rlogged = NULL;
self->tp_msg = msg;
}
/** Deliver message to the protocol stack */
void tport_deliver(tport_t *self,
msg_t *msg,
msg_t *next,
tport_compressor_t *sc,
su_time_t now)
{
tport_t *ref;
int error;
struct tport_delivery *d;
assert(msg);
d = self->tp_master->mr_delivery;
d->d_tport = self;
d->d_msg = msg;
*d->d_from = *self->tp_name;
if (tport_is_primary(self)) {
char ipaddr[SU_ADDRSIZE + 2];
su_sockaddr_t const *su = msg_addr(msg);
#if SU_HAVE_IN6
if (su->su_family == AF_INET6) {
ipaddr[0] = '[';
inet_ntop(su->su_family, SU_ADDR(su), ipaddr + 1, sizeof(ipaddr) - 1);
strcat(ipaddr, "]");
}
else {
inet_ntop(su->su_family, SU_ADDR(su), ipaddr, sizeof(ipaddr));
}
#else
inet_ntop(su->su_family, SU_ADDR(su), ipaddr, sizeof(ipaddr));
#endif
d->d_from->tpn_canon = ipaddr;
d->d_from->tpn_host = ipaddr;
}
d->d_comp = sc;
if (!sc)
d->d_from->tpn_comp = NULL;
error = msg_has_error(msg);
if (error && !*msg_chain_head(msg)) {
/* This is badly damaged packet */
}
else if (self->tp_master->mr_log && msg != self->tp_rlogged) {
char const *via = "recv";
tport_log_msg(self, msg, via, "from", now);
self->tp_rlogged = msg;
}
SU_DEBUG_7(("%s(%p): %smsg %p ("MOD_ZU" bytes)"
" from " TPN_FORMAT " next=%p\n",
__func__, (void *)self, error ? "bad " : "",
(void *)msg, (size_t)msg_size(msg),
TPN_ARGS(d->d_from), (void *)next));
ref = tport_incref(self);
if (self->tp_pri->pri_vtable->vtp_deliver) {
self->tp_pri->pri_vtable->vtp_deliver(self, msg, now);
}
else
tport_base_deliver(self, msg, now);
tport_decref(&ref);
d->d_msg = NULL;
}
/** Pass message to the protocol stack */
void
tport_base_deliver(tport_t *self, msg_t *msg, su_time_t now)
{
STACK_RECV(self, msg, now);
}
/** Return source transport object for delivered message */
tport_t *tport_delivered_by(tport_t const *tp, msg_t const *msg)
{
if (tp && msg && msg == tp->tp_master->mr_delivery->d_msg)
return tp->tp_master->mr_delivery->d_tport;
else
return NULL;
}
/** Return source transport name for delivered message */
int tport_delivered_from(tport_t *tp, msg_t const *msg, tp_name_t name[1])
{
if (name == NULL)
return -1;
if (tp == NULL || msg == NULL || msg != tp->tp_master->mr_delivery->d_msg) {
memset(name, 0, sizeof *name);
return -1;
}
else {
*name = *tp->tp_master->mr_delivery->d_from;
return 0;
}
}
/** Return UDVM used to decompress the message. */
int
tport_delivered_with_comp(tport_t *tp, msg_t const *msg,
tport_compressor_t **return_compressor)
{
if (tp == NULL || msg == NULL || msg != tp->tp_master->mr_delivery->d_msg)
return -1;
if (return_compressor)
*return_compressor = tp->tp_master->mr_delivery->d_comp;
return 0;
}
/** Allocate message for N bytes,
* return message buffer as a iovec
*/
ssize_t tport_recv_iovec(tport_t const *self,
msg_t **in_out_msg,
msg_iovec_t iovec[msg_n_fragments],
size_t N,
int exact)
{
msg_t *msg = *in_out_msg;
ssize_t i, veclen;
int fresh;
if (N == 0)
return 0;
fresh = !msg;
/*
* Allocate a new message if needed
*/
if (!msg) {
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,
self->tp_protoname, self->tp_host, self->tp_port));
return -1;
}
}
/*
* Get enough buffer space for the incoming data
*/
veclen = msg_recv_iovec(msg, iovec, msg_n_fragments, N, exact);
if (veclen < 0) {
int err = su_errno();
if (fresh && err == ENOBUFS && msg_get_flags(msg, MSG_FLG_TOOLARGE))
veclen = msg_recv_iovec(msg, iovec, msg_n_fragments, 4096, 1);
}
if (veclen < 0) {
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,
self->tp_protoname, self->tp_host, self->tp_port,
su_strerror(err)));
su_seterrno(err);
return veclen;
}
assert(veclen <= msg_n_fragments);
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,
N, veclen));
for (i = 0; veclen > 1 && i < veclen; i++) {
SU_DEBUG_7(("\tiovec[%lu] = %lu bytes\n", (LU)i, (LU)iovec[i].mv_len));
}
return veclen;
}
int tport_recv_error_report(tport_t *self)
{
if (su_is_blocking(su_errno()))
return 1;
/* Report error */
tport_error_report(self, su_errno(), NULL);
return -1;
}
/** Send a message.
*
* The function tport_tsend() sends a message using the transport @a self.
*
* @TAGS
* TPTAG_MTU(), TPTAG_REUSE(), TPTAG_CLOSE_AFTER(), TPTAG_SDWN_AFTER(),
* TPTAG_FRESH(), TPTAG_COMPARTMENT().
*/
tport_t *tport_tsend(tport_t *self,
msg_t *msg,
tp_name_t const *_tpn,
tag_type_t tag, tag_value_t value, ...)
{
ta_list ta;
tagi_t const *t;
int reuse, sdwn_after, close_after, resolved = 0, fresh;
unsigned mtu;
su_addrinfo_t *ai;
tport_primary_t *primary;
tp_name_t tpn[1];
struct sigcomp_compartment *cc;
assert(self);
if (!self || !msg || !_tpn) {
msg_set_errno(msg, EINVAL);
return NULL;
}
*tpn = *_tpn;
SU_DEBUG_7(("tport_tsend(%p) tpn = " TPN_FORMAT "\n",
(void *)self, TPN_ARGS(tpn)));
if (tport_is_master(self)) {
primary = (tport_primary_t *)tport_primary_by_name(self, tpn);
if (!primary) {
msg_set_errno(msg, EPROTONOSUPPORT);
return NULL;
}
}
else {
primary = self->tp_pri;
}
ta_start(ta, tag, value);
reuse = primary->pri_primary->tp_reusable && self->tp_reusable;
fresh = 0;
sdwn_after = 0;
close_after = 0;
mtu = 0;
cc = NULL;
/* tl_gets() is a bit too slow here... */
for (t = ta_args(ta); t; t = tl_next(t)) {
tag_type_t tt = t->t_tag;
if (tptag_reuse == tt)
reuse = t->t_value != 0;
else if (tptag_mtu == tt)
mtu = t->t_value;
else if (tptag_sdwn_after == tt)
sdwn_after = t->t_value != 0;
else if (tptag_close_after == tt)
close_after = t->t_value != 0;
else if (tptag_fresh == tt)
fresh = t->t_value != 0;
else if (tptag_compartment == tt)
cc = (struct sigcomp_compartment *)t->t_value;
}
ta_end(ta);
fresh = fresh || !reuse;
ai = msg_addrinfo(msg);
ai->ai_flags = 0;
tpn->tpn_comp = tport_canonize_comp(tpn->tpn_comp);
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));
}
if (!tpn->tpn_comp || cc == NONE)
cc = NULL;
if (sdwn_after)
ai->ai_flags |= TP_AI_SHUTDOWN;
if (close_after)
ai->ai_flags |= TP_AI_CLOSE;
if (fresh) {
/* Select a primary protocol, make a fresh connection */
self = primary->pri_primary;
}
else if (tport_is_secondary(self) && tport_is_clear_to_send(self)) {
self = self;
}
/*
* Try to find an already open connection to the destination,
* or get a primary protocol
*/
else {
/* If primary, resolve the destination address, store it in the msg */
if (tport_resolve(primary->pri_primary, msg, tpn) < 0) {
return NULL;
}
resolved = 1;
self = tport_by_addrinfo(primary, msg_addrinfo(msg), tpn);
if (!self)
self = primary->pri_primary;
}
if (tport_is_primary(self)) {
/* If primary, resolve the destination address, store it in the msg */
if (!resolved && tport_resolve(self, msg, tpn) < 0) {
return NULL;
}
if (tport_is_connection_oriented(self)
|| self->tp_params->tpp_conn_orient) {
#if 0 && HAVE_UPNP /* We do not want to use UPnP with secondary transports! */
if (upnp_register_upnp_client(1) != 0) {
upnp_check_for_nat();
}
#endif
tpn->tpn_proto = self->tp_protoname;
if (!cc)
tpn->tpn_comp = NULL;
/* Create a secondary transport which is connected to the destination */
self = tport_connect(primary, msg_addrinfo(msg), tpn);
#if 0 && HAVE_UPNP /* We do not want to use UPnP with secondary transports! */
upnp_deregister_upnp_client(0, 0);
#endif
if (!self) {
msg_set_errno(msg, su_errno());
SU_DEBUG_9(("tport_socket failed in tsend\n"));
return NULL;
}
if (cc)
tport_sigcomp_assign(self, cc);
}
}
else if (tport_is_secondary(self)) {
cc = tport_sigcomp_assign_if_needed(self, cc);
}
if (cc == NULL)
tpn->tpn_comp = NULL;
if (tport_is_secondary(self)) {
/* Set the peer address to msg */
tport_peer_address(self, msg);
if (sdwn_after || close_after)
self->tp_reusable = 0;
}
if (self->tp_pri->pri_vtable->vtp_prepare
? self->tp_pri->pri_vtable->vtp_prepare(self, msg, tpn, cc, mtu) < 0
: tport_prepare_and_send(self, msg, tpn, cc, mtu) < 0)
return NULL;
else
return self;
}
int tport_prepare_and_send(tport_t *self, msg_t *msg,
tp_name_t const *tpn,
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);
return -1;
}
if (msg_size(msg) > (usize_t)(mtu ? mtu : tport_mtu(self))) {
msg_set_errno(msg, EMSGSIZE);
return -1;
}
/*
* If there is already an queued message,
* put this message straight in the queue
*/
if ((self->tp_queue && self->tp_queue[self->tp_qhead]) ||
/* ...or we are connecting */
(self->tp_events & (SU_WAIT_CONNECT | SU_WAIT_OUT))) {
if (tport_queue(self, msg) < 0) {
SU_DEBUG_9(("tport_queue failed in tsend\n"));
return -1;
}
return 0;
}
retval = tport_send_msg(self, msg, tpn, cc);
tport_set_secondary_timer(self);
return retval;
}
/** Send a message.
*
* @retval 0 when succesful
* @retval -1 upon an error
*/
int tport_send_msg(tport_t *self, msg_t *msg,
tp_name_t const *tpn,
struct sigcomp_compartment *cc)
{
msg_iovec_t *iov, auto_iov[40];
size_t iovlen, iovused, i, total;
size_t n;
ssize_t nerror;
int sdwn_after, close_after;
su_time_t now;
su_addrinfo_t *ai;
assert(self->tp_queue == NULL ||
self->tp_queue[self->tp_qhead] == NULL ||
self->tp_queue[self->tp_qhead] == msg);
if (self->tp_iov)
/* Use the heap-allocated I/O vector */
iov = self->tp_iov, iovlen = self->tp_iovlen;
else
/* Use the stack I/O vector */
iov = auto_iov, iovlen = sizeof(auto_iov)/sizeof(auto_iov[0]);
/* Get a iovec for message contents */
for (;;) {
iovused = msg_iovec(msg, iov, iovlen);
if (iovused <= iovlen)
break;
iov = su_realloc(self->tp_home, self->tp_iov, sizeof(*iov) * iovused);
if (iov == NULL) {
msg_set_errno(msg, errno);
return -1;
}
self->tp_iov = iov, self->tp_iovlen = iovlen = iovused;
}
assert(iovused > 0);
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));
if (nerror == -1)
return -1;
n = (size_t)nerror;
self->tp_unsent = NULL, self->tp_unsentlen = 0;
if (n > 0 && self->tp_master->mr_log && self->tp_slogged != msg) {
tport_log_msg(self, msg, "send", "to", now);
self->tp_slogged = msg;
}
for (i = 0, total = 0; i < iovused; i++) {
if (total + (size_t)iov[i].mv_len > n) {
if (tport_is_connection_oriented(self)) {
iov[i].mv_len -= (su_ioveclen_t)(n - total);
iov[i].mv_base = (char *)iov[i].mv_base + (n - total);
if (tport_queue_rest(self, msg, &iov[i], iovused - i) >= 0)
return 0;
}
else {
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,
comp ? ";comp=" : "", comp ? comp : ""));
su_seterrno(EIO);
}
return -1;
}
total += iov[i].mv_len;
}
/* We have sent a complete message */
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_shutdown0(self, close_after ? 2 : 1);
return 0;
}
static
ssize_t tport_vsend(tport_t *self,
msg_t *msg,
tp_name_t const *tpn,
msg_iovec_t iov[],
size_t iovused,
struct sigcomp_compartment *cc)
{
ssize_t n;
su_addrinfo_t *ai = msg_addrinfo(msg);
if (cc) {
n = tport_send_comp(self, msg, iov, iovused, cc, self->tp_comp);
}
else {
ai->ai_flags &= ~TP_AI_COMPRESSED;
n = self->tp_pri->pri_vtable->vtp_send(self, msg, iov, iovused);
}
if (n == 0)
return 0;
if (n == -1)
return tport_send_error(self, msg, tpn);
self->tp_stats.sent_bytes += n;
if (n > 0 && self->tp_master->mr_dump_file)
tport_dump_iovec(self, msg, n, iov, iovused, "sent", "to");
if (tport_log->log_level >= 7) {
size_t i, m = 0;
for (i = 0; i < iovused; i++)
m += iov[i].mv_len;
if (tpn == NULL || tport_is_connection_oriented(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,
tpn->tpn_port,
(ai->ai_flags & TP_AI_COMPRESSED) ? ";comp=sigcomp" : ""));
}
return n;
}
static
int tport_send_error(tport_t *self, msg_t *msg,
tp_name_t const *tpn)
{
int error = su_errno();
su_addrinfo_t *ai = msg_addrinfo(msg);
char const *comp = (ai->ai_flags & TP_AI_COMPRESSED) ? ";comp=sigcomp" : "";
if (error == EPIPE) {
/*Xyzzy*/
}
if (su_is_blocking(error)) {
SU_DEBUG_5(("tport_vsend(%p): %s with (s=%d %s/%s:%s%s)\n",
(void *)self, "EAGAIN", (int)self->tp_socket,
tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port, comp));
return 0;
}
msg_set_errno(msg, error);
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,
tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port, comp));
}
#if SU_HAVE_IN6
else if (self->tp_addrinfo->ai_family == AF_INET6) {
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,
tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port, comp,
su->su_scope_id, (unsigned)ai->ai_addrlen));
}
#endif
else {
SU_DEBUG_3(("\ttport_vsend(%p): %s with "
"(s=%d, AF=%u addrlen=%u)%s\n",
(void *)self, su_strerror(error),
(int)self->tp_socket, ai->ai_family, (unsigned)ai->ai_addrlen, comp));
}
#if 0
int i;
for (i = 0; i < iovused; i++)
SU_DEBUG_7(("\t\tiov[%d] = { %d bytes @ %p }\n",
i, iov[i].siv_len, (void *)iov[i].siv_base));
#endif
if (tport_is_connection_oriented(self)) {
tport_error_report(self, error, NULL);
if (tport_has_connection(self))
tport_close(self);
}
return -1;
}
static
int tport_queue_rest(tport_t *self,
msg_t *msg,
msg_iovec_t iov[],
size_t iovused)
{
size_t iovlen = self->tp_iovlen;
assert(tport_is_connection_oriented(self));
assert(self->tp_queue == NULL ||
self->tp_queue[self->tp_qhead] == NULL ||
self->tp_queue[self->tp_qhead] == msg);
if (tport_queue(self, msg) < 0)
return -1;
assert(self->tp_queue[self->tp_qhead] == msg);
if (self->tp_iov == NULL) {
if (iovlen < 40) iovlen = 40;
if (iovlen < iovused) iovlen = iovused;
self->tp_iov = su_alloc(self->tp_home, iovlen * sizeof(iov[0]));
self->tp_iovlen = iovlen;
if (!self->tp_iov) {
msg_set_errno(msg, errno);
return -1;
}
memcpy(self->tp_iov, iov, iovused * sizeof(iov[0]));
iov = self->tp_iov;
}
self->tp_unsent = iov;
self->tp_unsentlen = iovused;
/* the POLLOUT event is far too unreliable with SCTP */
if (self->tp_addrinfo->ai_protocol == IPPROTO_SCTP)
return 0;
/* Ask for a send event */
tport_set_events(self, SU_WAIT_OUT, 0);
return 0;
}
/** Queue a message to transport.
*
* The tport_tqueue() function queues a message in the send queue. It is
* used by an (server) application that is required to send (response)
* messages in certain order. For example, a HTTP server or proxy may
* receive multiple requests from a single TCP connection. The server is
* required to answer to the requests in same order as they are received.
* The responses are, however, sometimes generated asynchronously, that is,
* a response to a later request may be ready earlier. For that purpose, the
* HTTP protocol stack queues an empty response message immediately upon
* receiving a request. Other messages cannot be sent before the queued one.
*
* The function tport_tqsend() is used to send the completed response message.
*
* @param self pointer to transport object
* @param msg message to be inserted into queue
* @param tag,value,... tagged argument list
*
* @TAGS
* @par Currently none.
*
* @retval 0 when successful
* @retval -1 upon an error
* @ERRORS
* @ERROR EINVAL Invalid argument(s).
* @ERROR ENOMEM Memory was exhausted.
* @ERROR ENOBUFS The transport object queue was full.
*
* @deprecated Alternative interface will be provided in near future.
*
* @sa tport_tqsend()
*/
int tport_tqueue(tport_t *self, msg_t *msg,
tag_type_t tag, tag_value_t value, ...)
{
msg_unprepare(msg);
return tport_queue(self, msg);
}
/** Return number of queued messages. */
isize_t tport_queuelen(tport_t const *self)
{
isize_t retval = 0;
if (self && self->tp_queue) {
unsigned short i, N = self->tp_params->tpp_qsize;
for (i = self->tp_qhead; self->tp_queue[i]; i = (i + 1) % N)
retval++;
}
return retval;
}
static
int tport_queue(tport_t *self, msg_t *msg)
{
unsigned short qhead = self->tp_qhead;
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));
if (self->tp_queue == NULL) {
assert(N > 0);
assert(qhead == 0);
self->tp_queue = su_zalloc(self->tp_home, N * sizeof(msg));
if (!self->tp_queue) {
msg_set_errno(msg, errno);
return -1;
}
}
if (self->tp_queue[qhead] == msg)
return 0;
while (self->tp_queue[qhead]) {
qhead = (qhead + 1) % N;
if (qhead == self->tp_qhead) {
msg_set_errno(msg, ENOBUFS);
return -1;
}
}
self->tp_queue[qhead] = msg_ref_create(msg);
return 0;
}
/** Send a queued message (and queue another, if required).
*
* The function tport_tqsend() sends a message to the transport.
*
* @deprecated Alternative interface will be provided in near future.
*/
int tport_tqsend(tport_t *self, msg_t *msg, msg_t *next,
tag_type_t tag, tag_value_t value, ...)
{
unsigned short qhead;
ta_list ta;
int reuse, sdwn_after, close_after;
unsigned short N;
su_addrinfo_t *ai;
if (self == NULL)
return -1;
qhead = self->tp_qhead;
N = self->tp_params->tpp_qsize;
reuse = self->tp_reusable;
sdwn_after = 0;
close_after = 0;
ta_start(ta, tag, value);
tl_gets(ta_args(ta),
TPTAG_REUSE_REF(reuse),
TPTAG_SDWN_AFTER_REF(sdwn_after),
TPTAG_CLOSE_AFTER_REF(close_after),
TAG_END());
ta_end(ta);
/* If "next", make sure we can queue it */
if (next && self->tp_queue[qhead == 0 ? N - 1 : qhead - 1]) {
msg_set_errno(next, ENOBUFS);
return -1;
}
/* Prepare message for sending - i.e., encode it */
if (msg_prepare(msg) < 0) {
msg_set_errno(msg, errno);
return -1;
}
tport_peer_address(self, msg); /* Set addrinfo */
if (next == NULL) {
ai = msg_addrinfo(msg);
if (sdwn_after)
ai->ai_flags |= TP_AI_SHUTDOWN;
if (close_after)
ai->ai_flags |= TP_AI_CLOSE;
if (self->tp_queue[qhead] == msg) {
tport_send_queue(self);
tport_set_secondary_timer(self);
}
return 0;
}
ai = msg_addrinfo(next);
if (sdwn_after)
ai->ai_flags |= TP_AI_SHUTDOWN;
if (close_after)
ai->ai_flags |= TP_AI_CLOSE;
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)))
msg_unprepare(next);
return 0;
}
}
while (self->tp_queue[qhead] && self->tp_queue[qhead] != msg) {
qhead = (qhead + 1) % N;
if (qhead == self->tp_qhead)
break;
}
if (self->tp_queue[qhead] != msg) {
msg_set_errno(next, EINVAL);
return -1;
}
msg = msg_ref_create(next);
do {
qhead = (qhead + 1) % N;
next = self->tp_queue[qhead]; self->tp_queue[qhead] = msg; msg = next;
/* Above we made sure that there is an empty slot */
assert(!next || qhead != self->tp_qhead);
} while (next);
return 0;
}
/** Send event.
*
* Process SU_WAIT_OUT event.
*/
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));
tport_send_queue(self);
tport_set_secondary_timer(self);
}
/** Send queued messages */
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;
assert(self->tp_queue && self->tp_queue[qhead]);
msg = self->tp_queue[qhead];
iov = self->tp_unsent, self->tp_unsent = NULL;
iovused = self->tp_unsentlen, self->tp_unsentlen = 0;
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 */
return;
n = (size_t)e;
if (n > 0 && self->tp_master->mr_log && self->tp_slogged != msg) {
tport_log_msg(self, msg, "send", "to", self->tp_stime);
self->tp_slogged = msg;
}
for (i = 0, total = 0; i < iovused; i++) {
if (total + (size_t)iov[i].mv_len > n) {
iov[i].mv_len -= (su_ioveclen_t)(n - total);
iov[i].mv_base = (char *)iov[i].mv_base + (n - total);
self->tp_unsent = iov + i;
self->tp_unsentlen = iovused - i;
return;
}
total += iov[i].mv_len;
}
assert(total == n);
/* We have sent a complete message */
self->tp_queue[qhead] = NULL;
msg_destroy(msg);
self->tp_stats.sent_msgs++;
self->tp_slogged = NULL;
qhead = (qhead + 1) % N;
}
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)
return;
msg = self->tp_queue[qhead]; /* tport_send_msg() may flush queue! */
self->tp_queue[qhead] = NULL;
msg_destroy(msg);
qhead = (qhead + 1) % N;
}
/* No more send event(s)? */
tport_set_events(self, 0, SU_WAIT_OUT);
}
static int msg_select_addrinfo(msg_t *msg, su_addrinfo_t *res);
static int
tport_resolve(tport_t *self, msg_t *msg, tp_name_t const *tpn)
{
int error;
char ipaddr[TPORT_HOSTPORTSIZE];
su_addrinfo_t *res, hints[1] = {{ 0 }};
char const *host;
su_sockaddr_t *su;
hints->ai_socktype = self->tp_addrinfo->ai_socktype;
hints->ai_protocol = self->tp_addrinfo->ai_protocol;
#if HAVE_OPEN_C
if (host_is_ip_address(tpn->tpn_host))
hints->ai_flags |= AI_NUMERICHOST;
#endif
if (tpn->tpn_host[0] == '[') {
/* Remove [] around IPv6 address */
char *end;
hints->ai_flags |= AI_NUMERICHOST;
host = strncpy(ipaddr, tpn->tpn_host + 1, sizeof(ipaddr) - 1);
ipaddr[sizeof(ipaddr) - 1] = '\0';
if ((end = strchr(host, ']'))) {
*end = 0;
}
else {
SU_DEBUG_3(("tport_resolve: bad IPv6 address\n"));
msg_set_errno(msg, EINVAL);
return -1;
}
}
else
host = tpn->tpn_host;
if ((error = su_getaddrinfo(host, tpn->tpn_port, hints, &res))) {
SU_DEBUG_3(("tport_resolve: getaddrinfo(\"%s\":%s): %s\n",
tpn->tpn_host, tpn->tpn_port,
su_gai_strerror(error)));
msg_set_errno(msg, ENXIO);
return -1;
}
error = msg_select_addrinfo(msg, res);
su = (su_sockaddr_t *) msg_addrinfo(msg)->ai_addr;
#if SU_HAVE_IN6
SU_DEBUG_9(("tport_resolve addrinfo = %s%s%s:%d\n",
su->su_family == AF_INET6 ? "[" : "",
inet_ntop(su->su_family, SU_ADDR(su), ipaddr, sizeof(ipaddr)),
su->su_family == AF_INET6 ? "]" : "",
htons(su->su_port)));
#else
SU_DEBUG_9(("tport_resolve addrinfo = %s%s%s:%d\n",
"",
inet_ntop(su->su_family, SU_ADDR(su), ipaddr, sizeof(ipaddr)),
"",
htons(su->su_port)));
#endif
su_freeaddrinfo(res);
return error;
}
static int
msg_select_addrinfo(msg_t *msg, su_addrinfo_t *res)
{
su_addrinfo_t *ai, *mai = msg_addrinfo(msg);
su_sockaddr_t *su = (su_sockaddr_t *)mai->ai_addr;
for (ai = res; ai; ai = ai->ai_next) {
#if SU_HAVE_IN6
if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
continue;
#else
if (ai->ai_family != AF_INET)
continue;
#endif
if (ai->ai_protocol == 0)
continue;
if (ai->ai_addrlen > sizeof(su_sockaddr_t))
continue;
mai->ai_family = ai->ai_family;
mai->ai_socktype = ai->ai_socktype;
mai->ai_protocol = ai->ai_protocol;
if (ai->ai_addrlen < sizeof(su_sockaddr_t))
memset(su, 0, sizeof(su_sockaddr_t));
memcpy(su, ai->ai_addr, ai->ai_addrlen);
if (su_sockaddr_size(su))
mai->ai_addrlen = su_sockaddr_size(su);
else
mai->ai_addrlen = ai->ai_addrlen;
return 0;
}
msg_set_errno(msg, EAFNOSUPPORT);
return -1;
}
/** Copy peer address to msg */
void
tport_peer_address(tport_t *self, msg_t *msg)
{
su_addrinfo_t *mai = msg_addrinfo(msg);
su_addrinfo_t const *tai = self->tp_addrinfo;
void *maddr = mai->ai_addr;
int flags = mai->ai_flags;
memcpy(mai, tai, sizeof *mai);
mai->ai_addr = memcpy(maddr, tai->ai_addr, tai->ai_addrlen);
mai->ai_flags = flags;
}
/** Process error event.
*
* Return events that can be processed afterwards.
*/
int tport_error_event(tport_t *self)
{
int errcode;
su_sockaddr_t name[1] = {{ 0 }};
name->su_family = AF_UNSPEC; /* 0 */
if (tport_is_udp(self)) {
errcode = tport_udp_error(self, name);
}
else {
/* Process error event for basic transport. */
errcode = su_soerror(self->tp_socket);
}
if (errcode == 0 || errcode == EPIPE)
return errcode;
tport_error_report(self, errcode, name);
return 0;
}
/** Mark message as waiting for a response.
*
* @return Positive integer, or -1 upon an error.
*/
int tport_pend(tport_t *self,
msg_t *msg,
tport_pending_error_f *callback,
tp_client_t *client)
{
tport_pending_t *pending;
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,
self->tp_protoname, self->tp_host, self->tp_port,
self->tp_pused));
if (self->tp_released == NULL) {
unsigned i, len = 8;
if (self->tp_plen)
len = 2 * self->tp_plen;
pending = su_realloc(self->tp_home,
self->tp_pending, len * sizeof(*pending));
if (!pending) {
msg_set_errno(msg, errno);
return -1;
}
memset(pending + self->tp_plen, 0, (len - self->tp_plen) * sizeof(*pending));
for (i = self->tp_plen; i + 1 < len; i++)
pending[i].p_client = pending + i + 1;
self->tp_released = pending + self->tp_plen;
self->tp_pending = pending;
self->tp_plen = len;
}
pending = self->tp_released;
self->tp_released = pending->p_client;
pending->p_callback = callback;
pending->p_client = client;
pending->p_msg = msg;
pending->p_reported = self->tp_reported;
self->tp_pused++;
return (pending - self->tp_pending) + 1;
}
/** Mark message as no more pending */
int tport_release(tport_t *self,
int pendd,
msg_t *msg,
msg_t *reply,
tp_client_t *client,
int still_pending)
{
tport_pending_t *pending;
if (self == NULL || pendd <= 0 || pendd > (int)self->tp_plen)
return su_seterrno(EINVAL), -1;
pending = self->tp_pending + (pendd - 1);
if (pending->p_client != client ||
pending->p_msg != msg) {
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(("%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) */
if (still_pending)
return 0;
/* Just to make sure nobody uses stale data */
memset(pending, 0, sizeof(*pending));
pending->p_client = self->tp_released;
self->tp_released = pending;
self->tp_pused--;
return 0;
}
/** Report error to pending messages with destination */
int
tport_pending_error(tport_t *self, su_sockaddr_t const *dst, int error)
{
unsigned i, reported, callbacks;
tport_pending_t *pending;
msg_t *msg;
su_addrinfo_t const *ai;
assert(self);
callbacks = 0;
reported = ++self->tp_reported;
if (self->tp_pused == 0)
return 0;
for (i = 0; i < self->tp_plen; i++) {
pending = self->tp_pending + i;
if (!pending->p_callback)
continue;
if (pending->p_reported == reported)
continue;
msg = pending->p_msg;
if (dst && msg) {
ai = msg_addrinfo(msg);
if (su_cmp_sockaddr(dst, (su_sockaddr_t *)ai->ai_addr) != 0)
continue;
}
msg_set_errno(msg, error);
pending->p_reported = reported;
pending->p_callback(self->TP_STACK, pending->p_client, self, msg, error);
callbacks++;
}
return callbacks;
}
/** Report error via pending message */
int
tport_pending_errmsg(tport_t *self, msg_t *msg, int error)
{
unsigned i, reported, callbacks;
tport_pending_t *pending;
assert(self); assert(msg);
callbacks = 0;
reported = ++self->tp_reported;
msg_set_errno(msg, error);
if (self->tp_pused == 0)
return 0;
for (i = 0; i < self->tp_plen; i++) {
pending = self->tp_pending + i;
if (!pending->p_client ||
pending->p_msg != msg ||
pending->p_reported == reported)
continue;
pending->p_reported = reported;
pending->p_callback(self->TP_STACK, pending->p_client, self, msg, error);
callbacks++;
}
return callbacks;
}
/** Set transport magic. */
void tport_set_magic(tport_t *self, tp_magic_t *magic)
{
self->tp_magic = magic;
}
/** Get transport magic. */
tp_magic_t *tport_magic(tport_t const *self)
{
return self ? self->tp_magic : NULL;
}
/** Get primary transport (or self, if primary) */
tport_t *tport_parent(tport_t const *self)
{
return self ? self->tp_pri->pri_primary : NULL;
}
/** Get list of primary transports */
tport_t *tport_primaries(tport_t const *self)
{
if (self)
return self->tp_master->mr_primaries->pri_primary;
else
return NULL;
}
/** Get next transport */
tport_t *tport_next(tport_t const *self)
{
if (self == NULL)
return NULL;
else if (tport_is_master(self))
return ((tport_master_t *)self)->mr_primaries->pri_primary;
else if (tport_is_primary(self))
return ((tport_primary_t *)self)->pri_next->pri_primary;
else
return tprb_succ(self);
}
/** Get secondary transports. */
tport_t *tport_secondary(tport_t const *self)
{
if (tport_is_primary(self))
return self->tp_pri->pri_open;
else
return NULL;
}
#if 0
void tport_hints(tport_t const *self, su_addrinfo_t *hints)
{
hints->ai_protocol = self->tp_addrinfo->ai_protocol;
hints->ai_socktype = self->tp_addrinfo->ai_socktype;
}
#endif
/** Get transport address list. */
su_addrinfo_t const *tport_get_address(tport_t const *self)
{
return self ? self->tp_addrinfo : NULL;
}
/** Get transport name. */
tp_name_t const *tport_name(tport_t const *self)
{
return self->tp_name;
}
/** Get transport identifier. */
char const *tport_ident(tport_t const *self)
{
return self ? self->tp_ident : NULL;
}
/** Get transport by protocol name. */
tport_t *tport_by_protocol(tport_t const *self, char const *proto)
{
if (proto && strcmp(proto, tpn_any) != 0) {
for (; self; self = tport_next(self))
if (strcasecmp(proto, self->tp_protoname) == 0)
break;
}
return (tport_t *)self;
}
/** Get transport by protocol name. */
tport_t *tport_primary_by_name(tport_t const *tp, tp_name_t const *tpn)
{
char const *ident = tpn->tpn_ident;
char const *proto = tpn->tpn_proto;
char const *comp = tpn->tpn_comp;
int family = 0;
tport_primary_t const *self, *nocomp = NULL;
self = tp ? tp->tp_master->mr_primaries : NULL;
if (ident && strcmp(ident, tpn_any) == 0)
ident = NULL;
if (tpn->tpn_host == NULL)
family = 0;
#if SU_HAVE_IN6
else if (host_is_ip6_address(tpn->tpn_host))
family = AF_INET6;
#endif
else if (host_is_ip4_address(tpn->tpn_host))
family = AF_INET;
else
family = 0;
if (proto && strcmp(proto, tpn_any) == 0)
proto = NULL;
if (!ident && !proto && !family && !comp)
return (tport_t *)self; /* Anything goes */
comp = tport_canonize_comp(comp);
for (; self; self = self->pri_next) {
tp = self->pri_primary;
if (ident && strcmp(ident, tp->tp_ident))
continue;
if (family) {
if (family == AF_INET && !tport_has_ip4(tp))
continue;
#if SU_HAVE_IN6
if (family == AF_INET6 && !tport_has_ip6(tp))
continue;
#endif
}
if (proto && strcasecmp(proto, tp->tp_protoname))
continue;
if (comp && comp != tp->tp_name->tpn_comp) {
if (tp->tp_name->tpn_comp == NULL && nocomp == NULL)
nocomp = self;
continue;
}
break;
}
if (self)
return (tport_t *)self;
else
return (tport_t *)nocomp;
}
/** Get transport by name. */
tport_t *tport_by_name(tport_t const *self, tp_name_t const *tpn)
{
tport_t const *sub, *next;
char const *canon, *host, *port, *comp;
#if SU_HAVE_IN6
char *end, ipaddr[TPORT_HOSTPORTSIZE];
#endif
assert(self); assert(tpn);
assert(tpn->tpn_proto); assert(tpn->tpn_host); assert(tpn->tpn_port);
assert(tpn->tpn_canon);
if (!tport_is_primary(self))
self = tport_primary_by_name(self, tpn);
host = strcmp(tpn->tpn_host, tpn_any) ? tpn->tpn_host : NULL;
port = strcmp(tpn->tpn_port, tpn_any) ? tpn->tpn_port : NULL;
canon = tpn->tpn_canon;
comp = tport_canonize_comp(tpn->tpn_comp);
if (self && host && port) {
int resolved = 0, cmp;
socklen_t sulen;
su_sockaddr_t su[1];
sub = self->tp_pri->pri_open;
memset(su, 0, sizeof su);
#if SU_HAVE_IN6
if (host_is_ip6_reference(host)) {
/* Remove [] around IPv6 address */
host = strncpy(ipaddr, host + 1, sizeof(ipaddr) - 1);
ipaddr[sizeof(ipaddr) - 1] = '\0';
if ((end = strchr(host, ']')))
*end = 0;
su->su_len = sulen = (socklen_t) sizeof (struct sockaddr_in6);
su->su_family = AF_INET6;
}
else if (host_is_ip_address(host)) {
su->su_len = sulen = (socklen_t) sizeof (struct sockaddr_in6);
su->su_family = AF_INET6;
}
else
#endif
{
su->su_len = sulen = (socklen_t) sizeof (struct sockaddr_in);
su->su_family = AF_INET;
}
su->su_port = htons(strtoul(port, NULL, 10));
if (inet_pton(su->su_family, host, SU_ADDR(su)) > 0) {
resolved = 1;
next = NULL;
/* Depth-first search */
while (sub) {
cmp = (int)((size_t)sub->tp_addrlen - (size_t)sulen);
if (cmp == 0)
cmp = memcmp(sub->tp_addr, su, sulen);
if (cmp == 0) {
if (sub->tp_left) {
next = sub;
sub = sub->tp_left;
continue;
}
break;
}
else if (next) {
sub = next;
break;
}
else if (cmp > 0) {
sub = sub->tp_left;
continue;
}
else /* if (cmp < 0) */ {
sub = sub->tp_right;
continue;
}
}
}
else {
SU_DEBUG_7(("tport(%p): EXPENSIVE unresolved " TPN_FORMAT "\n",
(void *)self, TPN_ARGS(tpn)));
sub = tprb_first(sub);
}
for (; sub; sub = tprb_succ(sub)) {
if (!sub->tp_reusable)
continue;
if (!tport_is_registered(sub))
continue;
if (tport_is_shutdown(sub))
continue;
if (comp != sub->tp_name->tpn_comp)
continue;
if (resolved) {
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)));
break;
}
SU_DEBUG_7(("tport(%p): found %p by name " TPN_FORMAT "\n",
(void *)self, (void *)sub, TPN_ARGS(tpn)));
}
else if ((strcasecmp(canon, sub->tp_canon) &&
strcasecmp(host, sub->tp_host)) ||
strcmp(port, sub->tp_port))
continue;
return (tport_t *)sub;
}
}
return (tport_t *)self;
}
/** Get transport from primary by addrinfo. */
tport_t *tport_by_addrinfo(tport_primary_t const *pri,
su_addrinfo_t const *ai,
tp_name_t const *tpn)
{
tport_t const *sub, *maybe;
struct sockaddr const *sa;
int cmp;
char const *comp;
assert(pri); assert(ai);
sa = ai->ai_addr;
sub = pri->pri_open, maybe = NULL;
comp = tport_canonize_comp(tpn->tpn_comp);
/* Find leftmost (prevmost) matching tport */
while (sub) {
cmp = (int)(sub->tp_addrlen - ai->ai_addrlen);
if (cmp == 0)
cmp = memcmp(sub->tp_addr, sa, ai->ai_addrlen);
if (cmp == 0) {
if (sub->tp_left) {
maybe = sub;
sub = sub->tp_left;
continue;
}
break;
}
else if (maybe) {
sub = maybe;
break;
}
else if (cmp > 0) {
sub = sub->tp_left;
continue;
}
else /* if (cmp < 0) */ {
sub = sub->tp_right;
continue;
}
}
for (; sub; sub = tprb_succ(sub)) {
if (!sub->tp_reusable)
continue;
if (!tport_is_registered(sub))
continue;
if (tport_is_shutdown(sub))
continue;
if (comp != sub->tp_name->tpn_comp)
continue;
if (sub->tp_addrlen != ai->ai_addrlen
|| memcmp(sub->tp_addr, sa, ai->ai_addrlen)) {
sub = NULL;
break;
}
break;
}
if (sub)
SU_DEBUG_7(("%s(%p): found %p by name " TPN_FORMAT "\n",
__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)));
return (tport_t *)sub;
}
/** Get transport name from URL. */
int tport_name_by_url(su_home_t *home,
tp_name_t *tpn,
url_string_t const *us)
{
size_t n;
url_t url[1];
char *b;
n = url_xtra(us->us_url);
b = su_alloc(home, n);
if (b == NULL || url_dup(b, n, url, us->us_url) < 0) {
su_free(home, b);
return -1;
}
tpn->tpn_proto = url_tport_default(url->url_type);
tpn->tpn_canon = url->url_host;
tpn->tpn_host = url->url_host;
tpn->tpn_port = url_port(url);
if (tpn->tpn_host == NULL || tpn->tpn_host[0] == '\0' ||
tpn->tpn_port == NULL || tpn->tpn_port[0] == '\0') {
su_free(home, b);
return -1;
}
if (url->url_params) {
for (b = (char *)url->url_params; b[0]; b += n) {
n = strcspn(b, ";");
if (n > 10 && strncasecmp(b, "transport=", 10) == 0)
tpn->tpn_proto = b + 10;
else if (n > 6 && strncasecmp(b, "maddr=", 6) == 0)
tpn->tpn_host = b + 6;
if (b[n])
b[n++] = '\0';
}
}
return 0;
}
/** Check if transport named is already resolved */
int tport_name_is_resolved(tp_name_t const *tpn)
{
size_t n;
if (!tpn->tpn_host)
return 0;
if (tpn->tpn_host[0] == '[')
return 1;
n = strspn(tpn->tpn_host, ".0123456789");
if (tpn->tpn_host[n] == '\0')
return 1;
if (strchr(tpn->tpn_host, ':')) {
n = strspn(tpn->tpn_host, ":0123456789abcdefABCDEF");
if (tpn->tpn_host[n] == '\0')
return 1;
}
return 0;
}
/** Duplicate name.
*
* The tport_name_dup() function copies strings belonging to the transport
* name. It returns the copied strings via the @a dst transport name
* structure. The memory block required for copies is allocated from the
* memory @a home. Please note that only one memory block is allocated, so
* the memory can be reclainmed only by deinitializing the memory home
* itself.
*
* @retval 0 when successful
* @retval -1 upon an error
*/
int tport_name_dup(su_home_t *home,
tp_name_t *dst,
tp_name_t const *src)
{
size_t n_proto, n_host, n_port, n_canon, n_comp = 0;
char *s;
if (strcmp(src->tpn_proto, tpn_any))
n_proto = strlen(src->tpn_proto) + 1;
else
n_proto = 0;
n_host = strlen(src->tpn_host) + 1;
n_port = strlen(src->tpn_port) + 1;
if (src->tpn_comp != NULL)
n_comp = strlen(src->tpn_comp) + 1;
if (src->tpn_canon != src->tpn_host &&
strcmp(src->tpn_canon, src->tpn_host))
n_canon = strlen(src->tpn_canon) + 1;
else
n_canon = 0;
s = su_alloc(home, n_proto + n_canon + n_host + n_port + n_comp);
if (n_proto)
dst->tpn_proto = memcpy(s, src->tpn_proto, n_proto), s += n_proto;
else
dst->tpn_proto = tpn_any;
dst->tpn_host = memcpy(s, src->tpn_host, n_host), s += n_host;
dst->tpn_port = memcpy(s, src->tpn_port, n_port), s += n_port;
if (n_canon)
dst->tpn_canon = memcpy(s, src->tpn_canon, n_canon), s += n_canon;
else
dst->tpn_canon = dst->tpn_host;
if (n_comp)
dst->tpn_comp = memcpy(s, src->tpn_comp, n_comp), s += n_comp;
else
dst->tpn_comp = NULL;
return 0;
}
/** Convert a sockaddr structure into printable form. */
char *tport_hostport(char buf[], isize_t bufsize,
su_sockaddr_t const *su,
int with_port_and_brackets)
{
char *b = buf;
size_t n;
#if SU_HAVE_IN6
if (with_port_and_brackets > 1 || su->su_family == AF_INET6) {
*b++ = '['; bufsize--;
}
#endif
if (inet_ntop(su->su_family, SU_ADDR(su), b, bufsize) == NULL)
return NULL;
n = strlen(b);
if (bufsize < n + 2)
return NULL;
bufsize -= n; b += n;
#if SU_HAVE_IN6
if (with_port_and_brackets > 1 || su->su_family == AF_INET6) {
*b++ = ']'; bufsize--;
}
if (with_port_and_brackets) {
unsigned short port = ntohs(su->su_port);
if (port != 0) {
n = snprintf(b, bufsize, ":%u", port);
if (n <= 0)
return NULL;
b += n;
if (bufsize > n)
bufsize -= n;
else
bufsize = 0;
}
}
#endif
if (bufsize)
*b++ = 0;
return buf;
}