diff --git a/pjsip/include/pjsip/sip_endpoint.h b/pjsip/include/pjsip/sip_endpoint.h index 4667c1a271..c370efcac0 100644 --- a/pjsip/include/pjsip/sip_endpoint.h +++ b/pjsip/include/pjsip/sip_endpoint.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify @@ -423,6 +423,18 @@ PJ_DECL(pj_status_t) pjsip_endpt_set_ext_resolver(pjsip_endpoint *endpt, */ PJ_DECL(pj_dns_resolver*) pjsip_endpt_get_resolver(pjsip_endpoint *endpt); +/** + * Get the external resolver implementation set with + * #pjsip_endpt_set_ext_resolver(). + * + * @param endpt The SIP endpoint instance. + * + * @return The external resolver implementation, or NULL when + * none is set. + */ +PJ_DECL(pjsip_ext_resolver*) pjsip_endpt_get_ext_resolver( + pjsip_endpoint *endpt); + /** * Mark a server address as failed for the specified duration, or clear the * mark. Please see #pjsip_resolver_set_server_failed() for more info. diff --git a/pjsip/include/pjsip/sip_resolve.h b/pjsip/include/pjsip/sip_resolve.h index 675b1a2ed0..060383e4e1 100644 --- a/pjsip/include/pjsip/sip_resolve.h +++ b/pjsip/include/pjsip/sip_resolve.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify @@ -313,6 +313,20 @@ PJ_DECL(pj_status_t) pjsip_resolver_set_ext_resolver( */ PJ_DECL(pj_dns_resolver*) pjsip_resolver_get_resolver(pjsip_resolver_t *res); +/** + * Get the external resolver implementation of the SIP resolver engine. + * + * Note that application normally will use #pjsip_endpt_get_ext_resolver() + * instead since it does not normally have access to the SIP resolver + * instance. + * + * @param res The SIP resolver engine. + * + * @return The external resolver implementation (may be NULL) + */ +PJ_DECL(pjsip_ext_resolver*) pjsip_resolver_get_ext_resolver( + pjsip_resolver_t *res); + /** * Mark a server address as failed for the specified duration, or clear the * mark. The mark applies to the IP address and port, whatever the transport diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index 3525cf1529..db34689a02 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify @@ -4783,8 +4783,12 @@ typedef struct pjsua_acc_config /** * Specify if source TCP port should be used as the initial Contact * address if TCP/TLS transport is used. Note that this feature will - * be automatically turned off when nameserver is configured because - * it may yield different destination address due to DNS SRV resolution. + * be automatically turned off when nameservers are configured + * (\a pjsua_config.nameserver) or when the application sets a DNS + * resolver or an external resolver on the SIP endpoint + * (#pjsip_endpt_set_resolver(), + * #pjsip_endpt_set_ext_resolver()), because they may yield a different + * destination address due to DNS SRV resolution. * Also some platforms are unable to report the local address of the * TCP socket when it is still connecting. In these cases, this * feature will also be turned off. diff --git a/pjsip/include/pjsua2/account.hpp b/pjsip/include/pjsua2/account.hpp index acf974679b..affe65ee47 100644 --- a/pjsip/include/pjsua2/account.hpp +++ b/pjsip/include/pjsua2/account.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2013-2026 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -778,8 +778,10 @@ struct AccountNatConfig : public PersistentObject /** * Specify if source TCP port should be used as the initial Contact * address if TCP/TLS transport is used. Note that this feature will - * be automatically turned off when nameserver is configured because - * it may yield different destination address due to DNS SRV resolution. + * be automatically turned off when nameservers are configured + * (UaConfig::nameserver) or when the application sets a DNS resolver + * or an external resolver on the SIP endpoint, because they may yield + * a different destination address due to DNS SRV resolution. * Also some platforms are unable to report the local address of the * TCP socket when it is still connecting. In these cases, this * feature will also be turned off. diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c index 6185862e28..05618f42a5 100644 --- a/pjsip/src/pjsip/sip_endpoint.c +++ b/pjsip/src/pjsip/sip_endpoint.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify @@ -1347,6 +1347,16 @@ PJ_DEF(pj_dns_resolver*) pjsip_endpt_get_resolver(pjsip_endpoint *endpt) return pjsip_resolver_get_resolver(endpt->resolver); } +/* + * Get the external resolver implementation. + */ +PJ_DEF(pjsip_ext_resolver*) pjsip_endpt_get_ext_resolver( + pjsip_endpoint *endpt) +{ + PJ_ASSERT_RETURN(endpt, NULL); + return pjsip_resolver_get_ext_resolver(endpt->resolver); +} + /* * Resolve */ diff --git a/pjsip/src/pjsip/sip_resolve.c b/pjsip/src/pjsip/sip_resolve.c index cb4894a7c7..6f92752f79 100644 --- a/pjsip/src/pjsip/sip_resolve.c +++ b/pjsip/src/pjsip/sip_resolve.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify @@ -190,6 +190,16 @@ PJ_DEF(pj_dns_resolver*) pjsip_resolver_get_resolver(pjsip_resolver_t *res) } +/* + * Public API to get the external resolver implementation. + */ +PJ_DEF(pjsip_ext_resolver*) pjsip_resolver_get_ext_resolver( + pjsip_resolver_t *res) +{ + return res->ext_res; +} + + /* Find a failed server entry. Must be called with the group lock held. */ static int find_failed(pjsip_resolver_t *resolver, const pj_sockaddr_t *addr) { diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c index 6d6e8f4552..d986cf1cf2 100644 --- a/pjsip/src/pjsua-lib/pjsua_acc.c +++ b/pjsip/src/pjsua-lib/pjsua_acc.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify @@ -4522,16 +4522,19 @@ static pj_status_t get_uac_addr_for_sip_uri(pjsua_acc_id acc_id, } ip_addr_ver = get_ip_addr_ver(&dinfo.addr.host); - if (ip_addr_ver == 0 && pjsua_var.ua_cfg.nameserver_count) { - /* If nameserver is configured, PJSIP will resolve destinations - * by their DNS SRV record first. On the other hand, we will - * resolve destination with DNS A record via pj_getaddrinfo(). - * They may yield different IP addresses, hence causing different - * TCP/TLS connection to be created and hence different source + if (ip_addr_ver == 0 && + (pjsip_endpt_get_resolver(pjsua_var.endpt) || + pjsip_endpt_get_ext_resolver(pjsua_var.endpt))) + { + /* With a DNS resolver or an external resolver, PJSIP may + * resolve destinations by their DNS SRV record first. On the other + * hand, we would resolve the destination with its DNS A record + * via pj_getaddrinfo(). They may yield different addresses, + * hence a different TCP/TLS connection and a different source * address. */ PJ_LOG(4,(THIS_FILE, "Warning: cannot use source TCP/TLS socket" - " address for Contact when nameserver is configured.")); + " address for Contact when a resolver is set.")); goto on_return; } diff --git a/pjsip/src/test/srv_failover_test.c b/pjsip/src/test/srv_failover_test.c index 1216b74902..6c2230c267 100644 --- a/pjsip/src/test/srv_failover_test.c +++ b/pjsip/src/test/srv_failover_test.c @@ -2085,6 +2085,103 @@ static int pjsua_restore_once_case(pj_bool_t init_fails) return rc; } +#if PJ_HAS_TCP +/* An external resolver that fails every query */ +static void no_resolve(pjsip_resolver_t *resolver, pj_pool_t *pool, + const pjsip_host_info *target, void *token, + pjsip_resolver_callback *cb) +{ + PJ_UNUSED_ARG(resolver); PJ_UNUSED_ARG(pool); PJ_UNUSED_ARG(target); + (*cb)(PJ_ERESOLVE, token, NULL); +} + +/* With an external resolver the destination may come from a DNS SRV + * record: the Contact takes the TCP listener's port, without a probing + * connection to the A record of the name. + */ +static int pjsua_contact_probe_case(void) +{ + static pjsip_ext_resolver ext = { &no_resolve }; + pjsua_config ua_cfg; + pjsua_logging_config log_cfg; + pjsua_transport_config tcfg; + pjsua_transport_info tinfo; + pjsua_transport_id tp_id; + pjsua_acc_config acc_cfg; + pjsua_acc_id acc_id; + pjsip_tpmgr *tpmgr; + pj_pool_t *pool; + pj_str_t contact, uri = pj_str("sip:localhost;transport=tcp"); + unsigned tp_cnt; + char *p; + int rc = 0; + + PJ_LOG(3,(THIS_FILE, " Contact with an external resolver")); + + if (pjsua_create() != PJ_SUCCESS) + return -3180; + pjsua_config_default(&ua_cfg); + ua_cfg.thread_cnt = 0; + pjsua_logging_config_default(&log_cfg); + log_cfg.level = 3; + log_cfg.console_level = 3; + if (pjsua_init(&ua_cfg, &log_cfg, NULL) != PJ_SUCCESS) { + pjsua_destroy(); + return -3181; + } + if (pjsip_endpt_set_ext_resolver(pjsua_get_pjsip_endpt(), &ext) != + PJ_SUCCESS) + { + pjsua_destroy(); + return -3182; + } + pjsua_transport_config_default(&tcfg); + tcfg.port = 0; + if (pjsua_transport_create(PJSIP_TRANSPORT_TCP, &tcfg, + &tp_id) != PJ_SUCCESS || + pjsua_start() != PJ_SUCCESS || + pjsua_transport_get_info(tp_id, &tinfo) != PJ_SUCCESS) + { + pjsua_destroy(); + return -3183; + } + pjsua_acc_config_default(&acc_cfg); + acc_cfg.id = pj_str("sip:test@localhost"); + if (pjsua_acc_add(&acc_cfg, PJ_FALSE, &acc_id) != PJ_SUCCESS) { + pjsua_destroy(); + return -3184; + } + + tpmgr = pjsip_endpt_get_tpmgr(pjsua_get_pjsip_endpt()); + tp_cnt = pjsip_tpmgr_get_transport_count(tpmgr); + pool = pjsua_pool_create("probe", 512, 512); + if (pjsua_acc_create_uac_contact(pool, &contact, acc_id, + &uri) != PJ_SUCCESS) + { + rc = -3185; + } else { + pj_str_t port; + + p = pj_strchr(&contact, '@'); + p = p ? pj_memchr(p, ':', contact.ptr + contact.slen - p) : NULL; + port.ptr = p ? p + 1 : NULL; + port.slen = p ? contact.ptr + contact.slen - port.ptr : 0; + if (!p || pj_strtoul(&port) != (unsigned)tinfo.local_name.port) { + PJ_LOG(1,(THIS_FILE, " Contact %.*s, listener port %d", + (int)contact.slen, contact.ptr, tinfo.local_name.port)); + rc = -3186; + } else if (pjsip_tpmgr_get_transport_count(tpmgr) != tp_cnt) { + rc = -3187; + } + } + pj_pool_release(pool); + pjsua_destroy(); + if (rc) + PJ_LOG(1,(THIS_FILE, " error: Contact case failed [%d]", rc)); + return rc; +} +#endif + int srv_failover_pjsua_test(void) { int rc; @@ -2106,6 +2203,10 @@ int srv_failover_pjsua_test(void) rc = pjsua_restore_once_case(PJ_FALSE); if (rc == 0) rc = pjsua_restore_once_case(PJ_TRUE); +#if PJ_HAS_TCP + if (rc == 0) + rc = pjsua_contact_probe_case(); +#endif restore_test_endpt(); return rc;