From 7249136f8f796eea8342b1f6c7fe73b45a8d20a6 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Tue, 1 Oct 2024 10:56:16 +0700 Subject: [PATCH] Update IP version choosing logic in media transport creation for generating SDP offer. (#4067) --- pjlib/include/pj/addr_resolv.h | 45 ++++++++++ pjlib/src/pj/sock_common.c | 111 ++++++++++++++++++++++++ pjsip/src/pjsua-lib/pjsua_media.c | 135 +++++++++++++++++++++++++++--- 3 files changed, 279 insertions(+), 12 deletions(-) diff --git a/pjlib/include/pj/addr_resolv.h b/pjlib/include/pj/addr_resolv.h index 90f2a57f07..ecd5e8994f 100644 --- a/pjlib/include/pj/addr_resolv.h +++ b/pjlib/include/pj/addr_resolv.h @@ -180,6 +180,51 @@ PJ_DECL(pj_status_t) pj_getaddrinfo(int af, const pj_str_t *name, unsigned *count, pj_addrinfo ai[]); +/** + * Enumeration of IP address type. + */ +typedef enum pj_addr_type +{ + /** + * Disabled: 0.0.0.0/8 or ::/128 + */ + PJ_ADDR_TYPE_DISABLED = 1, + + /** + * Loopback: 127.0.0.0/8 or ::1/128 + */ + PJ_ADDR_TYPE_LOOPBACK = 2, + + /** + * Link-local: 169.254.0.0/16 or fe80::/64 + */ + PJ_ADDR_TYPE_LINK_LOCAL = 4, + + /** + * Private: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 or fc00::/7 + */ + PJ_ADDR_TYPE_PRIVATE = 8, + + /** + * Multicast: 224.0.0.0/3 or ff00::/8 + */ + PJ_ADDR_TYPE_MULTICAST = 16, + +} pj_addr_type; + + + +/** + * Check IP address type. + * + * @param addr The IP address to check, must be IPv4 or IPv6 address. + * @param type Bit mask of address type pj_addr_type. + * + * @return PJ_TRUE if the type of specified address \addr match to + * the specified types \a type. + */ +PJ_DECL(pj_bool_t) pj_check_addr_type(const pj_sockaddr *addr, unsigned type); + /** @} */ diff --git a/pjlib/src/pj/sock_common.c b/pjlib/src/pj/sock_common.c index 5c8d8a63dd..62b08bdea1 100644 --- a/pjlib/src/pj/sock_common.c +++ b/pjlib/src/pj/sock_common.c @@ -731,6 +731,7 @@ PJ_DEF(pj_status_t) pj_sockaddr_parse( int af, unsigned options, return status; } + /* Resolve the IP address of local machine */ PJ_DEF(pj_status_t) pj_gethostip(int af, pj_sockaddr *addr) { @@ -1410,6 +1411,116 @@ PJ_DEF(pj_status_t) pj_sock_socketpair(int family, #endif +/* Check IP address type. */ +PJ_DEF(pj_bool_t) pj_check_addr_type(const pj_sockaddr *addr, unsigned type) +{ + int af; + + PJ_ASSERT_RETURN(addr && type, PJ_EINVAL); + PJ_ASSERT_RETURN(addr->addr.sa_family == PJ_AF_INET || + addr->addr.sa_family == PJ_AF_INET6, PJ_EINVAL); + + af = addr->addr.sa_family; + + if (af == PJ_AF_INET) { + enum { + /* Disabled: 0.0.0.0/8 */ + ADDR4_DISABLED = 0x00000000, + MASK4_DISABLED = 0xFF000000, + /* Loopback: 127.0.0.0/8 */ + ADDR4_LOOPBACK = 0x7f000000, + MASK4_LOOPBACK = 0xFF000000, + /* Link-local: 169.254.0.0/16 */ + ADDR4_LINKLOCAL = 0xa9fe0000, + MASK4_LINKLOCAL = 0xFFFF0000, + /* Multicast: 224.0.0.0/3 */ + ADDR4_MULTICAST = 0xE0000000, + MASK4_MULTICAST = 0xF0000000, + }; + + pj_uint32_t a = pj_ntohl(addr->ipv4.sin_addr.s_addr); + + if ((type & PJ_ADDR_TYPE_DISABLED) && + (a & (pj_uint32_t)MASK4_DISABLED)==(pj_uint32_t)ADDR4_DISABLED) + { + return PJ_TRUE; + } + + if ((type & PJ_ADDR_TYPE_LOOPBACK) && + (a & (pj_uint32_t)MASK4_LOOPBACK)==(pj_uint32_t)ADDR4_LOOPBACK) + { + return PJ_TRUE; + } + + if ((type & PJ_ADDR_TYPE_LINK_LOCAL) && + (a & (pj_uint32_t)MASK4_LINKLOCAL)==(pj_uint32_t)ADDR4_LINKLOCAL) + { + return PJ_TRUE; + } + + if (type & PJ_ADDR_TYPE_PRIVATE) { + pj_uint8_t b1 = (pj_uint8_t)(a >> 24); + pj_uint8_t b2 = (pj_uint8_t)((a >> 16) & 0x0ff);; + + /* 10.0.0.0/8 */ + if (b1 == 10) + return PJ_TRUE; + + /* 172.16.0.0/12 or 172.16.0.0-172.31.255.255 */ + if ((b1 == 172) && (b2 >= 16) && (b2 <= 31)) + return PJ_TRUE; + + /* 192.168.0.0/16 */ + if ((b1 == 192) && (b2 == 168)) + return PJ_TRUE; + } + + if ((type & PJ_ADDR_TYPE_MULTICAST) && + (a & (pj_uint32_t)MASK4_MULTICAST)==(pj_uint32_t)ADDR4_MULTICAST) + { + return PJ_TRUE; + } + + } else /* if (af == PJ_AF_INET6) */ { + + if (type & PJ_ADDR_TYPE_DISABLED) { + /* ::/128 */ + pj_uint8_t saddr[16] = {0x0,0x0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + if (pj_memcmp(saddr, &addr->ipv6.sin6_addr, 16) == 0) + return PJ_TRUE; + } + + if (type & PJ_ADDR_TYPE_LOOPBACK) { + /* ::1/128 */ + pj_uint8_t saddr[16] = {0x0,0x0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}; + if (pj_memcmp(saddr, &addr->ipv6.sin6_addr, 16) == 0) + return PJ_TRUE; + } + + if (type & PJ_ADDR_TYPE_LINK_LOCAL) { + /* fe80::/64 */ + pj_uint8_t saddr[16] = {0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + if (pj_memcmp(saddr, &addr->ipv6.sin6_addr, 8) == 0) + return PJ_TRUE; + } + + if (type & PJ_ADDR_TYPE_PRIVATE) { + /* fc00::/7 */ + if ((addr->ipv6.sin6_addr.s6_addr[0] & 0xfe) == 0xfc) + return PJ_TRUE; + } + + if (type & PJ_ADDR_TYPE_MULTICAST) { + /* ff00::/8 */ + if (addr->ipv6.sin6_addr.s6_addr[0] == 0xff) + return PJ_TRUE; + } + } + + return PJ_FALSE; +} + + /* Only need to implement these in DLL build */ #if defined(PJ_DLL) diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index 01d4e4ce8f..4c92fb6b57 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -247,7 +247,9 @@ pj_status_t pjsua_media_subsys_destroy(unsigned flags) } static int get_media_ip_version(pjsua_call_media *call_med, - const pjmedia_sdp_session *rem_sdp) + const pjmedia_sdp_session *rem_sdp, + pj_bool_t loop_tp, + pj_bool_t ice_tp) { #if PJ_HAS_IPV6 @@ -270,13 +272,118 @@ static int get_media_ip_version(pjsua_call_media *call_med, /* Use IPv6. */ return 6; } + } else { - if (ipv6_use == PJSUA_IPV6_ENABLED_PREFER_IPV6 || - ipv6_use == PJSUA_IPV6_ENABLED_USE_IPV6_ONLY) - { - /* Use IPv6. */ + pj_sockaddr addr = {{0}}; + pj_status_t status; + + /* Status: + * - not avail: no IP address, gethostip() returns error. + * - available: IP address is loopback, local-link, or disabled. + * - usable : IP address is not loopback, local-link, nor disabled. + */ + unsigned ipv6_status = 0; /* 0: not avail, 1: available, 2: usable*/ + unsigned ipv4_status = 0; /* 0: not avail, 1: available, 2: usable*/ + + /* Use IPv4 regardless. */ + if (ipv6_use == PJSUA_IPV6_DISABLED) + return 4; + + /* Use IPv6 regardless. */ + if (ipv6_use == PJSUA_IPV6_ENABLED_USE_IPV6_ONLY) return 6; + + /* For loop transport, use the preferred */ + if (loop_tp) { + if (ipv6_use == PJSUA_IPV6_ENABLED_PREFER_IPV6) + return 6; + if (ipv6_use == PJSUA_IPV6_ENABLED_PREFER_IPV4) + return 4; + + /* No preference, just use IPv4 */ + return 4; } + + + /* Check IPv6 & IPv4 availability & usability */ + + status = pj_gethostip(PJ_AF_INET6, &addr); + if (status == PJ_SUCCESS) { + /* IPv6 is available */ + ipv6_status = 1; + + /* Check if it is usable */ + if (!pj_check_addr_type(&addr, + PJ_ADDR_TYPE_DISABLED | + PJ_ADDR_TYPE_LOOPBACK | + PJ_ADDR_TYPE_LINK_LOCAL)) + { + /* If IPv6 is usable & preferred, use it */ + if (ipv6_use == PJSUA_IPV6_ENABLED_PREFER_IPV6) + return 6; + + /* IPv6 seems usable */ + ipv6_status = 2; + } + } + + /* For ICE transport, use IPv6 if available */ + if (ice_tp) { + return (ipv6_status >= 1)? 6 : 4; + } + + status = pj_gethostip(PJ_AF_INET, &addr); + if (status == PJ_SUCCESS) { + /* IPv4 is available */ + ipv4_status = 1; + + /* Check if it is usable */ + if (!pj_check_addr_type(&addr, + PJ_ADDR_TYPE_DISABLED | + PJ_ADDR_TYPE_LOOPBACK | + PJ_ADDR_TYPE_LINK_LOCAL)) + { + /* If IPv4 is usable & preferred, use it */ + if (ipv6_use == PJSUA_IPV6_ENABLED_PREFER_IPV4) + return 4; + + /* IPv4 seems usable */ + ipv4_status = 2; + } + } + + + /* Preferred IP version is not usable, fallback to any usable. + * In this point, either only one is usable (but not preferred) + * or there is no preference (IPv4 will be returned). + */ + if (ipv4_status == 2) + return 4; + if (ipv6_status == 2) + return 6; + + PJ_LOG(3,(THIS_FILE,"Call %d: Media %d: Warning, no usable " + "IP address for SDP offer", + call_med->call->index, call_med->idx)); + + /* No usable IP version, pick based on availability & preference. */ + if (ipv6_use == PJSUA_IPV6_ENABLED_PREFER_IPV6 && ipv6_status > 0) + return 6; + if (ipv6_use == PJSUA_IPV6_ENABLED_PREFER_IPV4 && ipv4_status > 0) + return 4; + + /* Preferred IP version is not available, fallback to any available. + * In this point, either only one is available (but not preferred) + * or there is no preference (IPv4 will be returned). + */ + if (ipv4_status == 1) + return 4; + if (ipv6_status == 1) + return 6; + + PJ_LOG(3,(THIS_FILE,"Call %d: Media %d: Warning, no available " + "IP address for SDP offer", + call_med->call->index, call_med->idx)); } #else PJ_UNUSED_ARG(call_med); @@ -309,7 +416,7 @@ static pj_status_t create_rtp_rtcp_sock(pjsua_call_media *call_med, pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id]; pj_sock_t sock[2]; - use_ipv6 = (get_media_ip_version(call_med, rem_sdp) == 6); + use_ipv6 = (get_media_ip_version(call_med, rem_sdp, PJ_FALSE, PJ_FALSE)==6); use_nat64 = PJ_HAS_IPV6 && (acc->cfg.nat64_opt != PJSUA_NAT64_DISABLED); af = (use_ipv6 || use_nat64) ? pj_AF_INET6() : pj_AF_INET(); @@ -762,7 +869,7 @@ static pj_status_t create_loop_media_transport( int af; pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id]; - use_ipv6 = (get_media_ip_version(call_med, rem_sdp) == 6); + use_ipv6 = (get_media_ip_version(call_med, rem_sdp, PJ_TRUE, PJ_FALSE)==6); use_nat64 = PJ_HAS_IPV6 && (acc->cfg.nat64_opt != PJSUA_NAT64_DISABLED); af = (use_ipv6 || use_nat64) ? pj_AF_INET6() : pj_AF_INET(); @@ -1020,7 +1127,8 @@ static pj_status_t create_ice_media_transport( pjmedia_sdp_session *rem_sdp; acc_cfg = &pjsua_var.acc[call_med->call->acc_id].cfg; - use_ipv6 = (get_media_ip_version(call_med, remote_sdp) == 6); + use_ipv6 = (get_media_ip_version(call_med, remote_sdp, PJ_FALSE, PJ_TRUE) + == 6); use_nat64 = PJ_HAS_IPV6 && (acc_cfg->nat64_opt != PJSUA_NAT64_DISABLED); /* Make sure STUN server resolution has completed */ @@ -2824,11 +2932,14 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, pj_bool_t use_nat64; if (rem_sdp) { - use_ipv6 = (get_media_ip_version(call_med, rem_sdp) == 6); + use_ipv6 = (get_media_ip_version(call_med, rem_sdp, + PJ_FALSE, PJ_FALSE) == 6); } else { - use_ipv6 = PJ_HAS_IPV6 && - (pjsua_var.acc[call->acc_id].cfg.ipv6_media_use != - PJSUA_IPV6_DISABLED); + //use_ipv6 = PJ_HAS_IPV6 && + // (pjsua_var.acc[call->acc_id].cfg.ipv6_media_use != + // PJSUA_IPV6_DISABLED); + use_ipv6 = (get_media_ip_version(call_med, rem_sdp, + PJ_FALSE, PJ_FALSE) == 6); } use_nat64 = PJ_HAS_IPV6 && (pjsua_var.acc[call->acc_id].cfg.nat64_opt !=