[socket] Fix set_sockaddr() accepting malformed IPv4 addresses (#19394)

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
rwrozelle
2026-09-18 18:10:11 -05:00
committed by GitHub
co-authored by Claude Sonnet 5
parent a53efcf646
commit f94661561d
4 changed files with 167 additions and 13 deletions
+5 -1
View File
@@ -185,7 +185,11 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_
return 0;
}
#else
server->sin_addr.s_addr = inet_addr(ip_address);
// inet_aton() reports failure separately from the address, so 255.255.255.255 needs no special case
if (inet_aton(ip_address, &server->sin_addr) == 0) {
errno = EINVAL;
return 0;
}
#endif
server->sin_port = htons(port);
return sizeof(sockaddr_in);
+16
View File
@@ -0,0 +1,16 @@
# This file's presence makes pytest treat this directory as a package named "socket",
# shadowing the stdlib module -- no test_*.py (Python-level) tests can live here.
# required for cpp unit testing.
import esphome.codegen as cg
from tests.testing_helpers import ComponentManifestOverride
def override_manifest(manifest: ComponentManifestOverride) -> None:
manifest.enable_codegen()
real_to_code = manifest.to_code
async def to_code_testing(config):
await real_to_code(config)
cg.add_define("USE_NETWORK_IPV6", True)
manifest.to_code = to_code_testing
-12
View File
@@ -1,12 +0,0 @@
"""Configuration file for socket component tests."""
import pytest
from esphome.core import CORE
@pytest.fixture(autouse=True)
def reset_core():
"""Reset CORE after each test."""
yield
CORE.reset()
@@ -0,0 +1,146 @@
#include <gtest/gtest.h>
#include <cerrno>
#include <cstring>
#include "esphome/components/socket/socket.h"
#ifdef USE_HOST
namespace esphome::socket::testing {
// =========================================================================
// Independent of USE_NETWORK_IPV6
// =========================================================================
TEST(SetSockaddr, IPv4Basic) {
struct sockaddr_storage addr {};
socklen_t len = set_sockaddr((struct sockaddr *) &addr, sizeof(addr), "192.168.1.1", 6053);
ASSERT_EQ(len, sizeof(sockaddr_in));
auto *in = reinterpret_cast<sockaddr_in *>(&addr);
EXPECT_EQ(in->sin_family, AF_INET);
EXPECT_EQ(ntohs(in->sin_port), 6053);
EXPECT_EQ(in->sin_addr.s_addr, htonl(0xC0A80101));
}
TEST(SetSockaddr, IPv4Broadcast) {
// 255.255.255.255 is INADDR_NONE's bit pattern but also a valid address.
struct sockaddr_storage addr {};
socklen_t len = set_sockaddr((struct sockaddr *) &addr, sizeof(addr), "255.255.255.255", 12345);
ASSERT_EQ(len, sizeof(sockaddr_in));
auto *in = reinterpret_cast<sockaddr_in *>(&addr);
EXPECT_EQ(in->sin_addr.s_addr, ESPHOME_INADDR_NONE);
}
TEST(SetSockaddr, IPv4MalformedYieldsZeroAndEINVAL) {
struct sockaddr_storage addr {};
errno = 0;
socklen_t len = set_sockaddr((struct sockaddr *) &addr, sizeof(addr), "not-an-ip", 1);
EXPECT_EQ(len, 0u);
EXPECT_EQ(errno, EINVAL);
}
TEST(SetSockaddr, IPv4BufferTooSmallYieldsZeroAndEINVAL) {
struct sockaddr_storage addr {};
errno = 0;
socklen_t len = set_sockaddr((struct sockaddr *) &addr, sizeof(sockaddr_in) - 1, "192.168.1.1", 1);
EXPECT_EQ(len, 0u);
EXPECT_EQ(errno, EINVAL);
}
TEST(SetSockaddr, StringOverloadMatchesCStringOverload) {
struct sockaddr_storage addr_c {};
struct sockaddr_storage addr_s {};
socklen_t len_c = set_sockaddr((struct sockaddr *) &addr_c, sizeof(addr_c), "10.0.0.5", 80);
socklen_t len_s = set_sockaddr((struct sockaddr *) &addr_s, sizeof(addr_s), std::string("10.0.0.5"), 80);
ASSERT_EQ(len_c, len_s);
EXPECT_EQ(std::memcmp(&addr_c, &addr_s, len_c), 0);
}
TEST(FormatSockaddrTo, IPv4RoundTrip) {
struct sockaddr_storage addr {};
socklen_t len = set_sockaddr((struct sockaddr *) &addr, sizeof(addr), "203.0.113.7", 1);
ASSERT_GT(len, 0u);
char buf[SOCKADDR_STR_LEN];
size_t written = format_sockaddr_to((struct sockaddr *) &addr, len, buf);
EXPECT_GT(written, 0u);
EXPECT_STREQ(buf, "203.0.113.7");
}
TEST(FormatSockaddrTo, UnknownFamilyYieldsEmptyString) {
struct sockaddr addr {};
addr.sa_family = AF_UNSPEC;
char buf[SOCKADDR_STR_LEN] = {'x', '\0'};
size_t written = format_sockaddr_to(&addr, sizeof(addr), buf);
EXPECT_EQ(written, 0u);
EXPECT_STREQ(buf, "");
}
// =========================================================================
// IPv6 - this directory's __init__.py forces USE_NETWORK_IPV6 on
// =========================================================================
#if USE_NETWORK_IPV6
TEST(SetSockaddr, IPv6Basic) {
struct sockaddr_storage addr {};
socklen_t len = set_sockaddr((struct sockaddr *) &addr, sizeof(addr), "::1", 443);
ASSERT_EQ(len, sizeof(sockaddr_in6));
auto *in6 = reinterpret_cast<sockaddr_in6 *>(&addr);
EXPECT_EQ(in6->sin6_family, AF_INET6);
EXPECT_EQ(ntohs(in6->sin6_port), 443);
EXPECT_TRUE(IN6_IS_ADDR_LOOPBACK(&in6->sin6_addr));
}
TEST(SetSockaddr, IPv6MalformedYieldsZeroAndEINVAL) {
struct sockaddr_storage addr {};
errno = 0;
socklen_t len = set_sockaddr((struct sockaddr *) &addr, sizeof(addr), "gg::not-ipv6", 1);
EXPECT_EQ(len, 0u);
EXPECT_EQ(errno, EINVAL);
}
TEST(SetSockaddrAny, FillsIPv6AnyAddressWithPort) {
struct sockaddr_storage addr {};
socklen_t len = set_sockaddr_any((struct sockaddr *) &addr, sizeof(addr), 9999);
ASSERT_EQ(len, sizeof(sockaddr_in6));
auto *in6 = reinterpret_cast<sockaddr_in6 *>(&addr);
EXPECT_EQ(in6->sin6_family, AF_INET6);
EXPECT_EQ(ntohs(in6->sin6_port), 9999);
EXPECT_TRUE(IN6_IS_ADDR_UNSPECIFIED(&in6->sin6_addr));
}
TEST(FormatSockaddrTo, IPv6RoundTrip) {
struct sockaddr_storage addr {};
socklen_t len = set_sockaddr((struct sockaddr *) &addr, sizeof(addr), "2001:db8::1", 1);
ASSERT_GT(len, 0u);
char buf[SOCKADDR_STR_LEN];
size_t written = format_sockaddr_to((struct sockaddr *) &addr, len, buf);
EXPECT_GT(written, 0u);
EXPECT_STREQ(buf, "2001:db8::1");
}
TEST(FormatSockaddrTo, V4MappedFormatsAsPlainIPv4) {
struct sockaddr_storage addr {};
socklen_t len = set_sockaddr((struct sockaddr *) &addr, sizeof(addr), "::ffff:198.51.100.9", 1);
ASSERT_GT(len, 0u);
char buf[SOCKADDR_STR_LEN];
size_t written = format_sockaddr_to((struct sockaddr *) &addr, len, buf);
EXPECT_GT(written, 0u);
EXPECT_STREQ(buf, "198.51.100.9");
}
TEST(SocketIp, CreatesIPv6Socket) {
auto sock = socket_ip(SOCK_DGRAM, IPPROTO_UDP);
ASSERT_NE(sock, nullptr);
struct sockaddr_storage addr {};
socklen_t len = sizeof(addr);
ASSERT_EQ(sock->getsockname((struct sockaddr *) &addr, &len), 0);
EXPECT_EQ(addr.ss_family, AF_INET6);
}
#endif // USE_NETWORK_IPV6
} // namespace esphome::socket::testing
#endif // USE_HOST