My asterisk server is dual homed (one external interface, one internal interface) and dual stacked. My endpoints are IPv4 only Panasonic KT-TPA50s. With chan_sip, this all worked fine. Once switching over to PJSIP, Asterisk gets confused and sets up UDP6 sockets instead of UDP4 sockets for the RTP sessions to the endpoints.
I’m new to the Asterisk code base, but was able to trace this down to the logic in res/res_pjsip_sdp_rtp.c:create_rtp(). When the connection comes in, the code falls back to using the global “address_rtp” for the local bind address. Since IPv6 support was detected at startup, this is an IPv6 address. Forcing address_rtp to be IPv4 (‘0.0.0.0’ instead of ‘::’) “solves” my problem.
I’m happy to dig around and work on a correct fix for this problem, but would appreciate some guidance on how this layer of the stack is supposed to determine the correct binding address/protocol to use. For example, from my poking around in the debugger, the sdp session object passed into create_rtp() is clearly for an IPv4 session. Can I rely on that?
As well by binding to “::” it is supposed to bind to both IPv4 and IPv6 on the same socket. This ensures that if DNS resolution results in multiple targets and it has to drop back to IPv4 from IPv6, RTP still works as expected. For ICE it also allows both IPv4 and IPv6 candidates to work.
As for your second question - it… depends. If the system only has an IPv4 transport configured, then the code could be tweaked to only do IPv4 since it could never use IPv6. With both configured the failover behavior I mention can occur.
The “::” binds a socket to both IPv4 and IPv6 is a Linux only extension. It doesn’t work on BSD derived OSes (e.g. I’d expect a similar failure on OS-X).
I’ll have to read up on ICE, but in my case this is an IPv4 session. So there doesn’t seem to be a reason to also accept RTP packets on the IPv6 port.
I’ll review why chan_sip gets this right and see if similar logic can be applied to pjsip.
ICE presents multiple candidates (potentially IPv4 and IPv6) which are IP/port pairs and then does a process to determine which are viable for actually sending traffic.
Yes. I am aware of what ICE is for, but had not read the RFCs to understand the negotiation rules or mechanisms for validating options. In the end, reading the RFCs wasn’t much help because what I really need to understand is the policy that Asterisk is trying to implement regarding IPv6. I couldn’t find definitive documentation while digging through the Asterisk 16.9.0 distribution.
Based on the other code in this area, it seems that create_rtp() should honor the ipv6 attribute of the endpoint. This small patch works for me.
–
Justin
--- res/res_pjsip_sdp_rtp.c.orig 2020-04-04 16:53:45.945678000 -0600
+++ res/res_pjsip_sdp_rtp.c 2020-04-04 16:53:58.726056000 -0600
@@ -62,6 +62,7 @@
/*! \brief Address for RTP */
static struct ast_sockaddr address_rtp;
+static struct ast_sockaddr address_rtp_v4;
static const char STR_AUDIO[] = "audio";
static const char STR_VIDEO[] = "video";
@@ -205,8 +206,13 @@
{
struct ast_rtp_engine_ice *ice;
struct ast_sockaddr temp_media_address;
- struct ast_sockaddr *media_address = &address_rtp;
+ struct ast_sockaddr *media_address = &address_rtp;
+ /* If the endpoint does not support IPv6, bind to an IPv4 address for RTP data. */
+ if (!session->endpoint->media.rtp.ipv6) {
+ media_address = &address_rtp_v4;
+ }
+
if (session->endpoint->media.bind_rtp_to_media_address && !ast_strlen_zero(session->endpoint->media.address)) {
if (ast_sockaddr_parse(&temp_media_address, session->endpoint->media.address, 0)) {
ast_debug(1, "Endpoint %s: Binding RTP media to %s\n",
@@ -2193,10 +2199,11 @@
*/
static int load_module(void)
{
+ ast_sockaddr_parse(&address_rtp_v4, "0.0.0.0", 0);
if (ast_check_ipv6()) {
ast_sockaddr_parse(&address_rtp, "::", 0);
} else {
- ast_sockaddr_parse(&address_rtp, "0.0.0.0", 0);
+ ast_sockaddr_copy(&address_rtp, &address_rtp_v4);
}
if (!(sched = ast_sched_context_create())) {
Asterisk used to do such a thing, as it was a configurable option. During SIPit Interop testing support for having both IPv4 and IPv6 at the same time was added, so going forward the option became a noop.
Having it configurable per-endpoint wasn’t viable because when sending a SIP request you may end up with AAAA or A records, or potentially both with failover. Therefore it was changed such that both IPv6 and IPv4 could be supported simultaneously.