Added support for specifying IP address in PJSUA-LIB/pjsua.

This option can be used for example to select the IP 
interface of SIP/RTP/RTCP transports, or to specify the
public IP address of NAT/router in case port forwarding is
used.

For SIP transports, this feature works for both UDP and 
TCP transports.

Changes:
 - added public_ip field in pjsua_transport_config, and
   change SIP and media transport creation to consider this
   option.
 - added --ip-addr option in pjsua
 - added pjsip_tcp_transport_start2() which allows 
   specifying alternate TCP published address when creating
   TCP transports.




git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@742 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
Benny Prijono 2006-09-26 13:21:02 +00:00
parent f6fd889119
commit 0a5cad8dec
7 changed files with 272 additions and 45 deletions

View File

@ -90,7 +90,7 @@ static void stereo_demo();
static void usage(void)
{
puts ("Usage:");
puts (" pjsua [options]");
puts (" pjsua [options] [SIP URL to call]");
puts ("");
puts ("General options:");
puts (" --config-file=file Read the config/arguments from file.");
@ -122,6 +122,8 @@ static void usage(void)
puts (" --local-port=port Set TCP/UDP port. This implicitly enables both ");
puts (" TCP and UDP transports on the specified port, unless");
puts (" if TCP or UDP is disabled.");
puts (" --ip-addr=IP Use the specifed address as SIP and RTP addresses.");
puts (" (Hint: the IP may be the public IP of the NAT/router)");
puts (" --no-tcp Disable TCP transport.");
puts (" --no-udp Disable UDP transport.");
puts (" --outbound=url Set the URL of global outbound proxy server");
@ -158,6 +160,9 @@ static void usage(void)
puts (" --duration=SEC Set maximum call duration (default:no limit)");
puts ("");
puts ("When URL is specified, pjsua will immediately initiate call to that URL");
puts ("");
fflush(stdout);
}
@ -268,8 +273,8 @@ static pj_status_t parse_args(int argc, char *argv[],
int option_index;
enum { OPT_CONFIG_FILE, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL,
OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO,
OPT_LOCAL_PORT, OPT_PROXY, OPT_OUTBOUND_PROXY, OPT_REGISTRAR,
OPT_REG_TIMEOUT, OPT_PUBLISH, OPT_ID, OPT_CONTACT,
OPT_LOCAL_PORT, OPT_IP_ADDR, OPT_PROXY, OPT_OUTBOUND_PROXY,
OPT_REGISTRAR, OPT_REG_TIMEOUT, OPT_PUBLISH, OPT_ID, OPT_CONTACT,
OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
OPT_USE_STUN1, OPT_USE_STUN2,
OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
@ -291,6 +296,7 @@ static pj_status_t parse_args(int argc, char *argv[],
{ "clock-rate", 1, 0, OPT_CLOCK_RATE},
{ "null-audio", 0, 0, OPT_NULL_AUDIO},
{ "local-port", 1, 0, OPT_LOCAL_PORT},
{ "ip-addr", 1, 0, OPT_IP_ADDR},
{ "no-tcp", 0, 0, OPT_NO_TCP},
{ "no-udp", 0, 0, OPT_NO_UDP},
{ "proxy", 1, 0, OPT_PROXY},
@ -433,6 +439,11 @@ static pj_status_t parse_args(int argc, char *argv[],
cfg->udp_cfg.port = (pj_uint16_t)lval;
break;
case OPT_IP_ADDR: /* ip-addr */
cfg->udp_cfg.public_addr = pj_str(pj_optarg);
cfg->rtp_cfg.public_addr = pj_str(pj_optarg);
break;
case OPT_NO_UDP: /* no-udp */
if (cfg->no_tcp) {
PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
@ -917,6 +928,14 @@ static int write_settings(const struct app_config *config,
pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port);
pj_strcat2(&cfg, line);
/* IP address, if any. */
if (config->udp_cfg.public_addr.slen) {
pj_ansi_sprintf(line, "--ip-addr %.*s\n",
(int)config->udp_cfg.public_addr.slen,
config->udp_cfg.public_addr.ptr);
pj_strcat2(&cfg, line);
}
/* STUN */
if (config->udp_cfg.stun_config.stun_port1) {

View File

@ -726,6 +726,8 @@ struct pjsip_tpfactory
/** This list is managed by transport manager. */
PJ_DECL_LIST_MEMBER(struct pjsip_tpfactory);
char obj_name[PJ_MAX_OBJ_NAME]; /**< Name. */
pj_pool_t *pool; /**< Owned memory pool. */
pj_lock_t *lock; /**< Lock object. */

View File

@ -78,6 +78,43 @@ PJ_DECL(pj_status_t) pjsip_tcp_transport_start(pjsip_endpoint *endpt,
pjsip_tpfactory **p_factory);
/**
* A newer variant of #pjsip_tcp_transport_start(), which allows specifying
* the published/public address of the TCP transport.
*
* @param endpt The SIP endpoint.
* @param local Optional local address to bind, or specify the
* address to bind the server socket to. Both IP
* interface address and port fields are optional.
* If IP interface address is not specified, socket
* will be bound to PJ_INADDR_ANY. If port is not
* specified, socket will be bound to any port
* selected by the operating system.
* @param a_name Optional published address, which is the address to be
* advertised as the address of this SIP transport.
* If this argument is NULL, then the bound address
* will be used as the published address.
* @param async_cnt Number of simultaneous asynchronous accept()
* operations to be supported. It is recommended that
* the number here corresponds to the number of
* processors in the system (or the number of SIP
* worker threads).
* @param p_factory Optional pointer to receive the instance of the
* SIP TCP transport factory just created.
*
* @return PJ_SUCCESS when the transport has been successfully
* started and registered to transport manager, or
* the appropriate error code.
*/
PJ_DECL(pj_status_t) pjsip_tcp_transport_start2(pjsip_endpoint *endpt,
const pj_sockaddr_in *local,
const pjsip_host_port *a_name,
unsigned async_cnt,
pjsip_tpfactory **p_factory);
PJ_END_DECL
/**

View File

@ -724,7 +724,7 @@ PJ_INLINE(void) pjsua_stun_config_default(pjsua_stun_config *cfg)
/**
* Transport configuration for creating UDP transports for both SIP
* Transport configuration for creating transports for both SIP
* and media.
*/
typedef struct pjsua_transport_config
@ -738,9 +738,28 @@ typedef struct pjsua_transport_config
unsigned port;
/**
* Optional address where the socket should be bound.
* Optional address to advertise as the address of this transport.
* Application can specify any address or hostname for this field,
* for example it can point to one of the interface address in the
* system, or it can point to the public address of a NAT router
* where port mappings have been configured for the application.
*
* Note: this option can be used for both UDP and TCP as well!
*/
pj_in_addr ip_addr;
pj_str_t public_addr;
/**
* Optional address where the socket should be bound to. This option
* SHOULD only be used to selectively bind the socket to particular
* interface (instead of 0.0.0.0), and SHOULD NOT be used to set the
* published address of a transport (the public_addr field should be
* used for that purpose).
*
* Note that unlike public_addr field, the address (or hostname) here
* MUST correspond to the actual interface address in the host, since
* this address will be specified as bind() argument.
*/
pj_str_t bound_addr;
/**
* Flag to indicate whether STUN should be used.

View File

@ -69,7 +69,6 @@ struct pending_accept
struct tcp_listener
{
pjsip_tpfactory factory;
char obj_name[PJ_MAX_OBJ_NAME];
pj_bool_t is_registered;
pjsip_endpoint *endpt;
pjsip_tpmgr *tpmgr;
@ -184,8 +183,9 @@ static void sockaddr_to_host_port( pj_pool_t *pool,
* This is the public API to create, initialize, register, and start the
* TCP listener.
*/
PJ_DEF(pj_status_t) pjsip_tcp_transport_start( pjsip_endpoint *endpt,
PJ_DEF(pj_status_t) pjsip_tcp_transport_start2(pjsip_endpoint *endpt,
const pj_sockaddr_in *local,
const pjsip_host_port *a_name,
unsigned async_cnt,
pjsip_tpfactory **p_factory)
{
@ -200,6 +200,19 @@ PJ_DEF(pj_status_t) pjsip_tcp_transport_start( pjsip_endpoint *endpt,
/* Sanity check */
PJ_ASSERT_RETURN(endpt && async_cnt, PJ_EINVAL);
/* Verify that address given in a_name (if any) is valid */
if (a_name && a_name->host.slen) {
pj_sockaddr_in tmp;
status = pj_sockaddr_in_init(&tmp, &a_name->host,
(pj_uint16_t)a_name->port);
if (status != PJ_SUCCESS || tmp.sin_addr.s_addr == PJ_INADDR_ANY ||
tmp.sin_addr.s_addr == PJ_INADDR_NONE)
{
/* Invalid address */
return PJ_EINVAL;
}
}
pool = pjsip_endpt_create_pool(endpt, "tcplis", POOL_LIS_INIT,
POOL_LIS_INC);
@ -214,7 +227,7 @@ PJ_DEF(pj_status_t) pjsip_tcp_transport_start( pjsip_endpoint *endpt,
pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TCP);
listener->sock = PJ_INVALID_SOCKET;
pj_ansi_strcpy(listener->obj_name, "tcp");
pj_ansi_strcpy(listener->factory.obj_name, "tcplis");
status = pj_lock_create_recursive_mutex(pool, "tcplis",
&listener->factory.lock);
@ -245,25 +258,46 @@ PJ_DEF(pj_status_t) pjsip_tcp_transport_start( pjsip_endpoint *endpt,
if (status != PJ_SUCCESS)
goto on_error;
/* If the address returns 0.0.0.0, use the first interface address
* as the transport's address.
/* If published host/IP is specified, then use that address as the
* listener advertised address.
*/
if (listener_addr->sin_addr.s_addr == 0) {
pj_in_addr hostip;
if (a_name && a_name->host.slen) {
/* Copy the address */
listener->factory.addr_name = *a_name;
pj_strdup(listener->factory.pool, &listener->factory.addr_name.host,
&a_name->host);
listener->factory.addr_name.port = a_name->port;
status = pj_gethostip(&hostip);
if (status != PJ_SUCCESS)
goto on_error;
} else {
/* No published address is given, use the bound address */
listener_addr->sin_addr = hostip;
/* If the address returns 0.0.0.0, use the default
* interface address as the transport's address.
*/
if (listener_addr->sin_addr.s_addr == 0) {
pj_in_addr hostip;
status = pj_gethostip(&hostip);
if (status != PJ_SUCCESS)
goto on_error;
listener_addr->sin_addr = hostip;
}
/* Save the address name */
sockaddr_to_host_port(listener->factory.pool,
&listener->factory.addr_name, listener_addr);
}
pj_ansi_snprintf(listener->obj_name, sizeof(listener->obj_name),
"tcp:%d", (int)pj_ntohs(listener_addr->sin_port));
/* If port is zero, get the bound port */
if (listener->factory.addr_name.port == 0) {
listener->factory.addr_name.port = pj_ntohs(listener_addr->sin_port);
}
pj_ansi_snprintf(listener->factory.obj_name,
sizeof(listener->factory.obj_name),
"tcplis:%d", listener->factory.addr_name.port);
/* Save the address name */
sockaddr_to_host_port(listener->factory.pool,
&listener->factory.addr_name, listener_addr);
/* Start listening to the address */
status = pj_sock_listen(listener->sock, PJSIP_TCP_TRANSPORT_BACKLOG);
@ -319,10 +353,11 @@ PJ_DEF(pj_status_t) pjsip_tcp_transport_start( pjsip_endpoint *endpt,
listener->sock, PJ_EPENDING);
}
PJ_LOG(4,(listener->obj_name,
"SIP TCP listener ready for incoming connections at %s:%d",
pj_inet_ntoa(listener_addr->sin_addr),
(int)pj_ntohs(listener_addr->sin_port)));
PJ_LOG(4,(listener->factory.obj_name,
"SIP TCP listener ready for incoming connections at %.*s:%d",
(int)listener->factory.addr_name.host.slen,
listener->factory.addr_name.host.ptr,
listener->factory.addr_name.port));
/* Return the pointer to user */
if (p_factory) *p_factory = &listener->factory;
@ -335,6 +370,19 @@ on_error:
}
/*
* This is the public API to create, initialize, register, and start the
* TCP listener.
*/
PJ_DEF(pj_status_t) pjsip_tcp_transport_start( pjsip_endpoint *endpt,
const pj_sockaddr_in *local,
unsigned async_cnt,
pjsip_tpfactory **p_factory)
{
return pjsip_tcp_transport_start2(endpt, local, NULL, async_cnt, p_factory);
}
/* This callback is called by transport manager to destroy listener */
static pj_status_t lis_destroy(pjsip_tpfactory *factory)
{
@ -373,7 +421,7 @@ static pj_status_t lis_destroy(pjsip_tpfactory *factory)
if (listener->factory.pool) {
pj_pool_t *pool = listener->factory.pool;
PJ_LOG(4,(listener->obj_name, "SIP TCP listener destroyed"));
PJ_LOG(4,(listener->factory.obj_name, "SIP TCP listener destroyed"));
listener->factory.pool = NULL;
pj_pool_release(pool);
@ -855,7 +903,8 @@ static void on_accept_complete( pj_ioqueue_key_t *key,
/*
* Error in accept().
*/
tcp_perror(listener->obj_name, "Error in accept()", status);
tcp_perror(listener->factory.obj_name, "Error in accept()",
status);
/*
* Prevent endless accept() error loop by limiting the
@ -865,7 +914,7 @@ static void on_accept_complete( pj_ioqueue_key_t *key,
*/
++err_cnt;
if (err_cnt >= 10) {
PJ_LOG(1, (listener->obj_name,
PJ_LOG(1, (listener->factory.obj_name,
"Too many errors, listener stopping"));
}
@ -882,7 +931,7 @@ static void on_accept_complete( pj_ioqueue_key_t *key,
goto next_accept;
}
PJ_LOG(4,(listener->obj_name,
PJ_LOG(4,(listener->factory.obj_name,
"TCP listener %.*s:%d: got incoming TCP connection "
"from %s:%d, sock=%d",
(int)listener->factory.addr_name.host.slen,

View File

@ -798,6 +798,9 @@ static pj_status_t create_sip_udp_sock(pj_in_addr bound_addr,
* the name of local host.
*/
if (stun.stun_srv1.slen) {
/*
* STUN is specified, resolve the address with STUN.
*/
status = pj_stun_get_mapped_addr(&pjsua_var.cp.factory, 1, &sock,
&stun.stun_srv1,
stun.stun_port1,
@ -810,6 +813,13 @@ static pj_status_t create_sip_udp_sock(pj_in_addr bound_addr,
return status;
}
} else if (p_pub_addr->sin_addr.s_addr != 0) {
/*
* Public address is already specified, no need to resolve the
* address, only set the port.
*/
/* Do nothing */
} else {
pj_bzero(p_pub_addr, sizeof(pj_sockaddr_in));
@ -866,6 +876,7 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type,
*/
pjsua_transport_config config;
pj_sock_t sock = PJ_INVALID_SOCKET;
pj_sockaddr_in bound_addr;
pj_sockaddr_in pub_addr;
pjsip_host_port addr_name;
@ -875,9 +886,36 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type,
cfg = &config;
}
/* Create the socket and possibly resolve the address with STUN */
status = create_sip_udp_sock(cfg->ip_addr, cfg->port, cfg->use_stun,
&cfg->stun_config, &sock, &pub_addr);
/* Initialize bound address, if any */
bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
if (cfg->bound_addr.slen) {
status = pj_sockaddr_in_set_str_addr(&bound_addr,&cfg->bound_addr);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE,
"Unable to resolve transport bound address",
status);
goto on_return;
}
}
/* Initialize the public address from the config, if any */
pj_sockaddr_in_init(&pub_addr, NULL, (pj_uint16_t)cfg->port);
if (cfg->public_addr.slen) {
status = pj_sockaddr_in_set_str_addr(&pub_addr, &cfg->public_addr);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE,
"Unable to resolve transport public address",
status);
goto on_return;
}
}
/* Create the socket and possibly resolve the address with STUN
* (only when public address is not specified).
*/
status = create_sip_udp_sock(bound_addr.sin_addr, cfg->port,
cfg->use_stun, &cfg->stun_config,
&sock, &pub_addr);
if (status != PJ_SUCCESS)
goto on_return;
@ -906,6 +944,7 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type,
* Create TCP transport.
*/
pjsua_transport_config config;
pjsip_host_port a_name;
pjsip_tpfactory *tcp;
pj_sockaddr_in local_addr;
@ -921,12 +960,24 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type,
if (cfg->port)
local_addr.sin_port = pj_htons((pj_uint16_t)cfg->port);
if (cfg->ip_addr.s_addr)
local_addr.sin_addr.s_addr = cfg->ip_addr.s_addr;
if (cfg->bound_addr.slen) {
status = pj_sockaddr_in_set_str_addr(&local_addr,&cfg->bound_addr);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE,
"Unable to resolve transport bound address",
status);
goto on_return;
}
}
/* Init published name */
pj_bzero(&a_name, sizeof(pjsip_host_port));
if (cfg->public_addr.slen)
a_name.host = cfg->public_addr;
/* Create the TCP transport */
status = pjsip_tcp_transport_start(pjsua_var.endpt, &local_addr, 1,
&tcp);
status = pjsip_tcp_transport_start2(pjsua_var.endpt, &local_addr,
&a_name, 1, &tcp);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Error creating SIP TCP listener",
@ -1029,6 +1080,7 @@ PJ_DEF(pj_status_t) pjsua_transport_get_info( pjsua_transport_id id,
pjsua_transport_info *info)
{
struct transport_data *t = &pjsua_var.tpdata[id];
pj_status_t status;
pj_bzero(info, sizeof(*info));
@ -1059,6 +1111,8 @@ PJ_DEF(pj_status_t) pjsua_transport_get_info( pjsua_transport_id id,
info->local_name = tp->local_name;
info->usage_count = pj_atomic_get(tp->ref_cnt);
status = PJ_SUCCESS;
} else if (pjsua_var.tpdata[id].type == PJSIP_TRANSPORT_TCP) {
pjsip_tpfactory *factory = t->data.factory;
@ -1078,12 +1132,17 @@ PJ_DEF(pj_status_t) pjsua_transport_get_info( pjsua_transport_id id,
info->local_name = factory->addr_name;
info->usage_count = 0;
status = PJ_SUCCESS;
} else {
pj_assert(!"Unsupported transport");
status = PJ_EINVALIDOP;
}
PJSUA_UNLOCK();
return PJ_EINVALIDOP;
return status;
}
@ -1120,12 +1179,29 @@ PJ_DEF(pj_status_t) pjsua_transport_close( pjsua_transport_id id,
/* Make sure that transport exists */
PJ_ASSERT_RETURN(pjsua_var.tpdata[id].data.ptr != NULL, PJ_EINVAL);
/* Note: destroy() may not work if there are objects still referencing
* the transport.
*/
if (force) {
switch (pjsua_var.tpdata[id].type) {
case PJSIP_TRANSPORT_UDP:
return pjsip_transport_destroy(pjsua_var.tpdata[id].data.tp);
case PJSIP_TRANSPORT_TCP:
break;
}
} else {
switch (pjsua_var.tpdata[id].type) {
case PJSIP_TRANSPORT_UDP:
return pjsip_transport_shutdown(pjsua_var.tpdata[id].data.tp);
case PJSIP_TRANSPORT_TCP:
return (*pjsua_var.tpdata[id].data.factory->destroy)
(pjsua_var.tpdata[id].data.factory);
}
}
/* To be done!! */
PJ_UNUSED_ARG(force);
PJ_TODO(pjsua_transport_close);
/* Unreachable */
pj_assert(!"Unknown transport");
return PJ_EINVALIDOP;
}

View File

@ -209,6 +209,7 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
};
int i;
static pj_uint16_t rtp_port;
pj_sockaddr_in bound_addr;
pj_sockaddr_in mapped_addr[2];
pj_status_t status = PJ_SUCCESS;
pj_sock_t sock[2];
@ -219,6 +220,15 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
for (i=0; i<2; ++i)
sock[i] = PJ_INVALID_SOCKET;
bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
if (cfg->bound_addr.slen) {
status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
status);
return status;
}
}
/* Loop retry to bind RTP and RTCP sockets. */
for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) {
@ -230,7 +240,8 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
return status;
}
status = pj_sock_bind_in(sock[0], cfg->ip_addr.s_addr, rtp_port);
status = pj_sock_bind_in(sock[0], bound_addr.sin_addr.s_addr,
rtp_port);
if (status != PJ_SUCCESS) {
pj_sock_close(sock[0]);
sock[0] = PJ_INVALID_SOCKET;
@ -245,7 +256,7 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
return status;
}
status = pj_sock_bind_in(sock[1], cfg->ip_addr.s_addr,
status = pj_sock_bind_in(sock[1], bound_addr.sin_addr.s_addr,
(pj_uint16_t)(rtp_port+1));
if (status != PJ_SUCCESS) {
pj_sock_close(sock[0]);
@ -285,6 +296,20 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
pj_sock_close(sock[1]);
sock[1] = PJ_INVALID_SOCKET;
} else if (cfg->public_addr.slen) {
status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
(pj_uint16_t)rtp_port);
if (status != PJ_SUCCESS)
goto on_error;
status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
(pj_uint16_t)(rtp_port+1));
if (status != PJ_SUCCESS)
goto on_error;
break;
} else {
pj_in_addr addr;