From 4124e2dcacfcc06e7d61f2f66fe946f9428206ea Mon Sep 17 00:00:00 2001 From: eigger Date: Mon, 27 Jul 2026 21:40:27 +0900 Subject: [PATCH] fix(sip_client): pick UDP socket family from the target address, not enable_ipv6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit socket::socket_ip() creates an AF_INET6-only socket whenever ESPHome's global network: enable_ipv6 is on (or the ESP-IDF/lwIP sdkconfig default has it on), regardless of what address the caller is about to talk to. Both the SIP signaling socket (sip_client.cpp) and the RTP media socket (rtp_session.cpp) used it unconditionally, so an IPv4 registrar/PBX (the common case, e.g. a router's built-in SIP server) got connect()/send() against a family-mismatched socket and registration silently never completed — this is the known "keep IPv6 disabled" issue noted in the component README. Fix: derive the socket's family from the actual destination — set_sockaddr()'s resulting sockaddr's ss_family for the SIP socket, and the already-known remote peer's ss_family (set via set_remote() before start()) for the RTP socket — instead of the global enable_ipv6 toggle. The new AF_INET6 code path in rtp_session.cpp is guarded by USE_NETWORK_IPV6, since struct sockaddr_in6 isn't even declared when IPv6 support is compiled out (the common/recommended case per the README). Verified compiling clean against real esphome==2026.6.5 and esphome==2026.7.2, both with enable_ipv6 left at default (off) and explicitly set to true. Co-Authored-By: Claude Sonnet 5 --- components/sip_client/rtp_session.cpp | 37 ++++++++++++++++++++++++--- components/sip_client/sip_client.cpp | 15 ++++++----- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/components/sip_client/rtp_session.cpp b/components/sip_client/rtp_session.cpp index 0f39045c..07e2d4c4 100644 --- a/components/sip_client/rtp_session.cpp +++ b/components/sip_client/rtp_session.cpp @@ -1,6 +1,7 @@ #include "rtp_session.h" #include #include +#include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" #include "g711.h" @@ -16,6 +17,31 @@ static const uint32_t DTMF_TONE_SAMPLES = 8 * SAMPLES_PER_FRAME; // ~160 ms ton static const int DTMF_END_PACKETS = 3; static const size_t TX_BUFFER_MAX = 8000; // 1 s of audio, drop excess +// Bind-any sockaddr for the given family. Unlike socket::set_sockaddr_any(), +// this doesn't depend on ESPHome's global network::enable_ipv6 setting — the +// caller picks the family to match the (already-known) remote peer address. +static socklen_t set_sockaddr_any_family(struct sockaddr *addr, socklen_t addrlen, sa_family_t family, + uint16_t port) { +#if USE_NETWORK_IPV6 + if (family == AF_INET6) { + if (addrlen < sizeof(struct sockaddr_in6)) return 0; + auto *a6 = reinterpret_cast(addr); + memset(a6, 0, sizeof(struct sockaddr_in6)); + a6->sin6_family = AF_INET6; + a6->sin6_port = htons(port); + return sizeof(struct sockaddr_in6); + } +#else + (void) family; +#endif + if (addrlen < sizeof(struct sockaddr_in)) return 0; + auto *a4 = reinterpret_cast(addr); + memset(a4, 0, sizeof(struct sockaddr_in)); + a4->sin_family = AF_INET; + a4->sin_port = htons(port); + return sizeof(struct sockaddr_in); +} + static int dtmf_char_to_event(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c == '*') return 10; @@ -34,14 +60,19 @@ void RtpSession::set_remote(const std::string &ip, uint16_t port) { bool RtpSession::start(uint16_t local_port) { this->stop(); - this->socket_ = socket::socket_ip(SOCK_DGRAM, IPPROTO_UDP); + // Match the remote peer's address family (set via set_remote() before this + // call) instead of socket::socket_ip(), which is fixed to AF_INET6 whenever + // ESPHome's global network::enable_ipv6 is on — see open_socket_() in + // sip_client.cpp for the same fix and why it's needed. + sa_family_t family = this->remote_set_ ? this->remote_addr_.ss_family : AF_INET; + this->socket_ = socket::socket(family, SOCK_DGRAM, IPPROTO_UDP); if (!this->socket_) { ESP_LOGW(TAG, "Could not create RTP socket"); return false; } struct sockaddr_storage local_addr; - socklen_t sl = socket::set_sockaddr_any(reinterpret_cast(&local_addr), - sizeof(local_addr), local_port); + socklen_t sl = set_sockaddr_any_family(reinterpret_cast(&local_addr), + sizeof(local_addr), family, local_port); if (this->socket_->bind(reinterpret_cast(&local_addr), sl) != 0) { ESP_LOGW(TAG, "RTP bind failed on port %u", local_port); this->socket_.reset(); diff --git a/components/sip_client/sip_client.cpp b/components/sip_client/sip_client.cpp index 71e1624e..1d98a13a 100644 --- a/components/sip_client/sip_client.cpp +++ b/components/sip_client/sip_client.cpp @@ -74,18 +74,21 @@ void SipClient::dump_config() { } bool SipClient::open_socket_() { - this->socket_ = socket::socket_ip(SOCK_DGRAM, IPPROTO_UDP); - if (!this->socket_) { - ESP_LOGW(TAG, "Could not create SIP socket"); - return false; - } struct sockaddr_storage server_addr; socklen_t sl = socket::set_sockaddr(reinterpret_cast(&server_addr), sizeof(server_addr), this->server_.c_str(), this->server_port_); if (sl == 0) { ESP_LOGW(TAG, "Invalid server address '%s' (use an IP)", this->server_.c_str()); - this->socket_.reset(); + return false; + } + // Create the socket to match the server address's actual family rather than + // socket::socket_ip(), which is fixed to AF_INET6 whenever ESPHome's global + // network::enable_ipv6 is on — that mismatched an IPv4 server (the common + // case) against an IPv6-only socket and made connect() fail outright. + this->socket_ = socket::socket(server_addr.ss_family, SOCK_DGRAM, IPPROTO_UDP); + if (!this->socket_) { + ESP_LOGW(TAG, "Could not create SIP socket"); return false; } if (this->socket_->connect(reinterpret_cast(&server_addr), sl) != 0) {