From e7dcd6a5407a65664e7fada76fc4bb791080a7b2 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Thu, 22 Jan 2026 02:30:56 +0000 Subject: [PATCH 1/3] Fix (attempt) GH-20752: netsnmp_session_init() without system ipv6. - opening up building peername to non ipv6 supported systems. - while at it, inet_ntop should not be called with a query size as large as MAX_NAME_LEN/MAX_OID_LEN but INET6_ADDRSTRLEN max. - checking in case of non ipv6 support (AF_INET6 can be still defined in this case). --- ext/snmp/snmp.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ext/snmp/snmp.c b/ext/snmp/snmp.c index 0654a97dfc861..f54f4fd99ed43 100644 --- a/ext/snmp/snmp.c +++ b/ext/snmp/snmp.c @@ -842,6 +842,9 @@ static bool netsnmp_session_init(php_snmp_session **session_p, int version, zend struct sockaddr **res; // TODO: Do not strip and re-add the port in peername? unsigned remote_port = SNMP_PORT; +#if defined(HAVE_GETADDRINFO) + char name[INET6_ADDRSTRLEN]; +#endif *session_p = (php_snmp_session *)emalloc(sizeof(php_snmp_session)); session = *session_p; @@ -888,18 +891,17 @@ static bool netsnmp_session_init(php_snmp_session **session_p, int version, zend res = psal; while (n-- > 0) { pptr = session->peername; -#if defined(HAVE_GETADDRINFO) && defined(HAVE_IPV6) +#if defined(HAVE_GETADDRINFO) if (force_ipv6 && (*res)->sa_family != AF_INET6) { res++; continue; } if ((*res)->sa_family == AF_INET6) { - strcpy(session->peername, "udp6:["); - pptr = session->peername + strlen(session->peername); - inet_ntop((*res)->sa_family, &(((struct sockaddr_in6*)(*res))->sin6_addr), pptr, MAX_NAME_LEN); - strcat(pptr, "]"); + if (inet_ntop((*res)->sa_family, &(((struct sockaddr_in6*)(*res))->sin6_addr), name, sizeof(name))) { + snprintf(pptr, MAX_NAME_LEN, "udp6:[%s]", name); + } } else if ((*res)->sa_family == AF_INET) { - inet_ntop((*res)->sa_family, &(((struct sockaddr_in*)(*res))->sin_addr), pptr, MAX_NAME_LEN); + inet_ntop((*res)->sa_family, &(((struct sockaddr_in*)(*res))->sin_addr), pptr, INET_ADDRSTRLEN); } else { res++; continue; From 687ac23e9d5944c28bae5dc4311f8e0072b03f7e Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 28 Feb 2026 20:11:08 +0000 Subject: [PATCH 2/3] Prefer AF_INET results on IPv4-only systems per @thomasvincent suggestion. On IPv4-only systems, getaddrinfo() can still return AF_INET6 mapped addresses (e.g. ::ffff:127.0.0.1). The code would format these as udp6:[addr] peername that Net-SNMP cannot connect to. When IPv6 is not explicitly requested (no bracket notation), prefer AF_INET results. If no suitable address is found, fall back to the original hostname and let Net-SNMP resolve it itself, matching the behavior of snmprealwalk and other Net-SNMP CLI tools. --- ext/snmp/snmp.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/ext/snmp/snmp.c b/ext/snmp/snmp.c index f54f4fd99ed43..a2ebb46d2da5b 100644 --- a/ext/snmp/snmp.c +++ b/ext/snmp/snmp.c @@ -886,6 +886,10 @@ static bool netsnmp_session_init(php_snmp_session **session_p, int version, zend return false; } + /* Save hostname before clearing peername, since host_ptr may point into the same buffer */ + char saved_hostname[MAX_NAME_LEN]; + strlcpy(saved_hostname, host_ptr, MAX_NAME_LEN); + /* we have everything we need in psal, flush peername and fill it properly */ *(session->peername) = '\0'; res = psal; @@ -896,6 +900,13 @@ static bool netsnmp_session_init(php_snmp_session **session_p, int version, zend res++; continue; } + /* When IPv6 is not explicitly requested, prefer IPv4 results to avoid + issues on systems where getaddrinfo() returns AF_INET6 mapped addresses + but IPv6 networking is not actually available */ + if (!force_ipv6 && (*res)->sa_family == AF_INET6) { + res++; + continue; + } if ((*res)->sa_family == AF_INET6) { if (inet_ntop((*res)->sa_family, &(((struct sockaddr_in6*)(*res))->sin6_addr), name, sizeof(name))) { snprintf(pptr, MAX_NAME_LEN, "udp6:[%s]", name); @@ -910,13 +921,11 @@ static bool netsnmp_session_init(php_snmp_session **session_p, int version, zend break; } - if (strlen(session->peername) == 0) { - php_error_docref(NULL, E_WARNING, "Unknown failure while resolving '%s'", ZSTR_VAL(hostname)); - return false; + /* If no suitable address was found, fall back to the original hostname + and let Net-SNMP resolve it (matches behavior of snmprealwalk et al.) */ + if (session->peername[0] == '\0') { + strlcpy(session->peername, saved_hostname, MAX_NAME_LEN); } - /* XXX FIXME - There should be check for non-empty session->peername! - */ /* put back non-standard SNMP port */ if (remote_port != SNMP_PORT) { From 6a9dc748accf906d9224155f6ed0c712735e5adf Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 18 Mar 2026 18:53:49 +0000 Subject: [PATCH 3/3] Allow IPv6 fallback on dual-stack systems per @NattyNarwhal review. --- ext/snmp/snmp.c | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/ext/snmp/snmp.c b/ext/snmp/snmp.c index a2ebb46d2da5b..269148103c7d0 100644 --- a/ext/snmp/snmp.c +++ b/ext/snmp/snmp.c @@ -892,10 +892,12 @@ static bool netsnmp_session_init(php_snmp_session **session_p, int version, zend /* we have everything we need in psal, flush peername and fill it properly */ *(session->peername) = '\0'; +#if defined(HAVE_GETADDRINFO) + struct sockaddr *chosen = NULL; + struct sockaddr *ipv6_fallback = NULL; + res = psal; while (n-- > 0) { - pptr = session->peername; -#if defined(HAVE_GETADDRINFO) if (force_ipv6 && (*res)->sa_family != AF_INET6) { res++; continue; @@ -904,22 +906,35 @@ static bool netsnmp_session_init(php_snmp_session **session_p, int version, zend issues on systems where getaddrinfo() returns AF_INET6 mapped addresses but IPv6 networking is not actually available */ if (!force_ipv6 && (*res)->sa_family == AF_INET6) { + if (!ipv6_fallback) { + ipv6_fallback = *res; + } res++; continue; } - if ((*res)->sa_family == AF_INET6) { - if (inet_ntop((*res)->sa_family, &(((struct sockaddr_in6*)(*res))->sin6_addr), name, sizeof(name))) { + if ((*res)->sa_family == AF_INET || (*res)->sa_family == AF_INET6) { + chosen = *res; + break; + } + res++; + } + + /* On dual-stack systems, fall back to IPv6 if no IPv4 was found */ + if (!chosen && !force_ipv6 && ipv6_fallback) { + chosen = ipv6_fallback; + } + + if (chosen) { + pptr = session->peername; + if (chosen->sa_family == AF_INET6) { + if (inet_ntop(AF_INET6, &(((struct sockaddr_in6*)chosen)->sin6_addr), name, sizeof(name))) { snprintf(pptr, MAX_NAME_LEN, "udp6:[%s]", name); } - } else if ((*res)->sa_family == AF_INET) { - inet_ntop((*res)->sa_family, &(((struct sockaddr_in*)(*res))->sin_addr), pptr, INET_ADDRSTRLEN); - } else { - res++; - continue; + } else if (chosen->sa_family == AF_INET) { + inet_ntop(AF_INET, &(((struct sockaddr_in*)chosen)->sin_addr), pptr, INET_ADDRSTRLEN); } -#endif - break; } +#endif /* If no suitable address was found, fall back to the original hostname and let Net-SNMP resolve it (matches behavior of snmprealwalk et al.) */