diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index 1b3549157..e3d9a33c9 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -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) { diff --git a/pjsip/include/pjsip/sip_transport.h b/pjsip/include/pjsip/sip_transport.h index f0f915cdc..4c9120e96 100644 --- a/pjsip/include/pjsip/sip_transport.h +++ b/pjsip/include/pjsip/sip_transport.h @@ -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. */ diff --git a/pjsip/include/pjsip/sip_transport_tcp.h b/pjsip/include/pjsip/sip_transport_tcp.h index 4f5664d46..db308d7d5 100644 --- a/pjsip/include/pjsip/sip_transport_tcp.h +++ b/pjsip/include/pjsip/sip_transport_tcp.h @@ -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 /** diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index 47641c92d..9ded3f150 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -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. diff --git a/pjsip/src/pjsip/sip_transport_tcp.c b/pjsip/src/pjsip/sip_transport_tcp.c index 4d9132d1e..37e21a450 100644 --- a/pjsip/src/pjsip/sip_transport_tcp.c +++ b/pjsip/src/pjsip/sip_transport_tcp.c @@ -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, diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index d26094ab6..6b39fdbb4 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -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; } diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index e37275a2c..13ff542cb 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -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; iip_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;