From 7ca5a7d84ebdc1b97f49cb460f200db093b96d9d Mon Sep 17 00:00:00 2001 From: Sara Golemon Date: Tue, 21 Nov 2017 10:04:37 -0500 Subject: [PATCH] Add net_get_interfaces() --- NEWS | 1 + ext/standard/basic_functions.c | 11 +- ext/standard/basic_functions.h | 4 +- ext/standard/config.m4 | 22 +- ext/standard/config.w32 | 4 +- ext/standard/net.c | 298 ++++++++++++++++++ ext/standard/php_net.h | 31 ++ .../tests/network/net_get_interfaces_001.phpt | 30 ++ 8 files changed, 396 insertions(+), 5 deletions(-) create mode 100644 ext/standard/net.c create mode 100644 ext/standard/php_net.h create mode 100644 ext/standard/tests/network/net_get_interfaces_001.phpt diff --git a/NEWS b/NEWS index eafaef0413..b67b975e47 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ PHP NEWS thrus making it work on Windows out of the box. (Kalle) . Removed support for BeOS. (Kalle) . Add PHP_VERSION to phpinfo() . (github/MattJeevas) + . Add net_get_interfaces(). (Sara, Joe, Anatol) . Fixed bug #75031 (support append mode in temp/memory streams). (adsr) . Fixed bug #74860 (Uncaught exceptions not being formatted properly when error_log set to "syslog"). (Philip Prindeville) diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index a823ac9f81..7aec0baa69 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -962,6 +962,9 @@ ZEND_BEGIN_ARG_INFO(arginfo_gethostname, 0) ZEND_END_ARG_INFO() #endif +ZEND_BEGIN_ARG_INFO(arginfo_net_get_interfaces, 0) +ZEND_END_ARG_INFO() + #if defined(PHP_WIN32) || HAVE_DNS_SEARCH_FUNC ZEND_BEGIN_ARG_INFO_EX(arginfo_dns_check_record, 0, 0, 1) ZEND_ARG_INFO(0, host) @@ -2955,7 +2958,7 @@ const zend_function_entry basic_functions[] = { /* {{{ */ PHP_FE(fmod, arginfo_fmod) PHP_FE(intdiv, arginfo_intdiv) #ifdef HAVE_INET_NTOP - PHP_RAW_NAMED_FE(inet_ntop, php_inet_ntop, arginfo_inet_ntop) + PHP_RAW_NAMED_FE(inet_ntop, zif_inet_ntop, arginfo_inet_ntop) #endif #ifdef HAVE_INET_PTON PHP_RAW_NAMED_FE(inet_pton, php_inet_pton, arginfo_inet_pton) @@ -3060,6 +3063,10 @@ const zend_function_entry basic_functions[] = { /* {{{ */ PHP_FE(gethostname, arginfo_gethostname) #endif +#if defined(PHP_WIN32) || HAVE_GETIFADDRS + PHP_FE(net_get_interfaces, arginfo_net_get_interfaces) +#endif + #if defined(PHP_WIN32) || HAVE_DNS_SEARCH_FUNC PHP_FE(dns_check_record, arginfo_dns_check_record) @@ -3897,7 +3904,7 @@ PHP_FUNCTION(constant) #ifdef HAVE_INET_NTOP /* {{{ proto string inet_ntop(string in_addr) Converts a packed inet address to a human readable IP address string */ -PHP_NAMED_FUNCTION(php_inet_ntop) +PHP_NAMED_FUNCTION(zif_inet_ntop) { char *address; size_t address_len; diff --git a/ext/standard/basic_functions.h b/ext/standard/basic_functions.h index cfca0e03ad..c8f8bd14e5 100644 --- a/ext/standard/basic_functions.h +++ b/ext/standard/basic_functions.h @@ -56,7 +56,7 @@ PHP_FUNCTION(time_sleep_until); #endif PHP_FUNCTION(flush); #ifdef HAVE_INET_NTOP -PHP_NAMED_FUNCTION(php_inet_ntop); +PHP_NAMED_FUNCTION(zif_inet_ntop); #endif #ifdef HAVE_INET_PTON PHP_NAMED_FUNCTION(php_inet_pton); @@ -126,6 +126,8 @@ PHP_FUNCTION(sys_getloadavg); PHP_FUNCTION(is_uploaded_file); PHP_FUNCTION(move_uploaded_file); +PHP_FUNCTION(net_get_interfaces); + /* From the INI parser */ PHP_FUNCTION(parse_ini_file); PHP_FUNCTION(parse_ini_string); diff --git a/ext/standard/config.m4 b/ext/standard/config.m4 index 87ee059bd3..8642fb2aee 100644 --- a/ext/standard/config.m4 +++ b/ext/standard/config.m4 @@ -448,6 +448,26 @@ if test "$PHP_PASSWORD_ARGON2" != "no"; then ]) fi +dnl +dnl net_get_interfaces +dnl +AC_CHECK_HEADERS([net/if.h netdb.h]) +AC_MSG_CHECKING([for usable getifaddrs]) +AC_TRY_LINK([ + #include <ifaddrs.h> +],[ + struct ifaddrs *interfaces; + if (!getifaddrs(&interfaces)) { + freeifaddrs(interfaces); + } +], [ac_have_getifaddrs=yes], [ac_have_getifaddrs=no]) +if test "$ac_have_getifaddrs" = "yes" ; then + AC_DEFINE(HAVE_GETIFADDRS, 1, [whether getifaddrs is present and usable]) + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi + dnl dnl Setup extension sources dnl @@ -462,7 +482,7 @@ PHP_NEW_EXTENSION(standard, array.c base64.c basic_functions.c browscap.c crc32. http_fopen_wrapper.c php_fopen_wrapper.c credits.c css.c \ var_unserializer.c ftok.c sha1.c user_filters.c uuencode.c \ filters.c proc_open.c streamsfuncs.c http.c password.c \ - random.c,,, + random.c net.c,,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) PHP_ADD_MAKEFILE_FRAGMENT diff --git a/ext/standard/config.w32 b/ext/standard/config.w32 index ee1935fe87..fe519b8db9 100644 --- a/ext/standard/config.w32 +++ b/ext/standard/config.w32 @@ -22,13 +22,15 @@ AC_DEFINE("PHP_USE_PHP_CRYPT_R", 1); CHECK_HEADER_ADD_INCLUDE("timelib_config.h", "CFLAGS_STANDARD", "ext/date/lib"); +ADD_FLAG("LIBS_STANDARD", "iphlpapi.lib"); + EXTENSION("standard", "array.c base64.c basic_functions.c browscap.c \ crc32.c crypt.c crypt_freesec.c crypt_blowfish.c crypt_sha256.c \ crypt_sha512.c php_crypt_r.c \ cyr_convert.c datetime.c dir.c dl.c dns.c dns_win32.c exec.c \ file.c filestat.c formatted_print.c fsock.c head.c html.c image.c \ info.c iptc.c lcg.c link_win32.c mail.c math.c md5.c metaphone.c microtime.c \ - pack.c pageinfo.c quot_print.c rand.c mt_rand.c soundex.c \ + net.c pack.c pageinfo.c quot_print.c rand.c mt_rand.c soundex.c \ string.c scanf.c syslog.c type.c uniqid.c url.c var.c \ versioning.c assert.c strnatcmp.c levenshtein.c incomplete_class.c \ url_scanner_ex.c ftp_fopen_wrapper.c http_fopen_wrapper.c \ diff --git a/ext/standard/net.c b/ext/standard/net.c new file mode 100644 index 0000000000..f8d06e04cd --- /dev/null +++ b/ext/standard/net.c @@ -0,0 +1,298 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Sara Golemon <pollita@php.net> | + +----------------------------------------------------------------------+ + */ + +#include "php.h" +#include "php_network.h" + +#if HAVE_ARPA_INET_H +# include <arpa/inet.h> +#endif + +#if HAVE_NET_IF_H +# include <net/if.h> +#endif + +#if HAVE_GETIFADDRS +# include <ifaddrs.h> +# include <netdb.h> +#endif + +#ifdef PHP_WIN32 +# include <intrin.h> +# include <winsock2.h> +# include <ws2ipdef.h> +# include <Ws2tcpip.h> +# include <iphlpapi.h> +#endif + +PHPAPI zend_string* php_inet_ntop(const struct sockaddr *addr) { + socklen_t addrlen = sizeof(struct sockaddr_in); + + if (!addr) { return NULL; } + + /* Prefer inet_ntop() as it's more task-specific and doesn't have to be demangled */ +#if HAVE_INET_NTOP + switch (addr->sa_family) { +#ifdef AF_INET6 + case AF_INET6: { + zend_string *ret = zend_string_alloc(INET6_ADDRSTRLEN, 0); + if (inet_ntop(AF_INET6, &(((struct sockaddr_in6*)addr)->sin6_addr), ZSTR_VAL(ret), INET6_ADDRSTRLEN)) { + ZSTR_LEN(ret) = strlen(ZSTR_VAL(ret)); + return ret; + } + zend_string_free(ret); + break; + } +#endif + case AF_INET: { + zend_string *ret = zend_string_alloc(INET_ADDRSTRLEN, 0); + if (inet_ntop(AF_INET, &(((struct sockaddr_in*)addr)->sin_addr), ZSTR_VAL(ret), INET_ADDRSTRLEN)) { + ZSTR_LEN(ret) = strlen(ZSTR_VAL(ret)); + return ret; + } + zend_string_free(ret); + break; + } + } +#endif + + /* Fallback on getnameinfo() */ + switch (addr->sa_family) { +#ifdef AF_INET6 + case AF_INET6: + addrlen = sizeof(struct sockaddr_in6); + /* fallthrough */ +#endif + case AF_INET: { + zend_string *ret = zend_string_alloc(NI_MAXHOST, 0); + if (getnameinfo(addr, addrlen, ZSTR_VAL(ret), NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == SUCCESS) { + /* Also demangle numeric host with %name suffix */ + char *colon = strchr(ZSTR_VAL(ret), '%'); + if (colon) { *colon = 0; } + ZSTR_LEN(ret) = strlen(ZSTR_VAL(ret)); + return ret; + } + zend_string_free(ret); + break; + } + } + + return NULL; +} + +static void iface_append_unicast(zval *unicast, zend_long flags, + struct sockaddr *addr, struct sockaddr *netmask, + struct sockaddr *broadcast, struct sockaddr *ptp) { + zend_string *host; + zval u; + + array_init(&u); + add_assoc_long(&u, "flags", flags); + + if (addr) { + add_assoc_long(&u, "family", addr->sa_family); + if ((host = php_inet_ntop(addr))) { + add_assoc_str(&u, "address", host); + } + } + if ((host = php_inet_ntop(netmask))) { + add_assoc_str(&u, "netmask", host); + } + + if ((host = php_inet_ntop(broadcast))) { + add_assoc_str(&u, "broadcast", host); + } + + if ((host = php_inet_ntop(ptp))) { + add_assoc_str(&u, "ptp", host); + } + + add_next_index_zval(unicast, &u); +} + +/* {{{ proto array|false net_get_interfaces() +Returns an array in the form: +array( + 'ifacename' => array( + 'description' => 'Awesome interface', // Win32 only + 'mac' => '00:11:22:33:44:55', // Win32 only + 'mtu' => 1234, // Win32 only + 'unicast' => array( + 0 => array( + 'family' => 2, // e.g. AF_INET, AF_INET6, AF_PACKET + 'address' => '127.0.0.1', + 'netmnask' => '255.0.0.0', + 'broadcast' => '127.255.255.255', // POSIX only + 'ptp' => '127.0.0.2', // POSIX only + ), // etc... + ), + ), // etc... +) +*/ +PHP_FUNCTION(net_get_interfaces) { +#ifdef PHP_WIN32 +# define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) +# define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) + ULONG family = AF_UNSPEC; + ULONG flags = GAA_FLAG_INCLUDE_PREFIX; + PIP_ADAPTER_ADDRESSES pAddresses = NULL, p; + PIP_ADAPTER_UNICAST_ADDRESS u = NULL; + ULONG outBufLen = 0; + DWORD dwRetVal = 0; + + ZEND_PARSE_PARAMETERS_NONE(); + + // Make an initial call to GetAdaptersAddresses to get the + // size needed into the outBufLen variable + if (GetAdaptersAddresses(family, flags, NULL, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) { + FREE(pAddresses); + pAddresses = (IP_ADAPTER_ADDRESSES *) MALLOC(outBufLen); + } + + if (pAddresses == NULL) { + zend_error(E_WARNING, "Memory allocation failed for IP_ADAPTER_ADDRESSES struct"); + RETURN_FALSE; + } + + dwRetVal = GetAdaptersAddresses(family, flags, NULL, pAddresses, &outBufLen); + + if (NO_ERROR != dwRetVal) { + /* TODO check GetLastError() */ + FREE(pAddresses); + RETURN_FALSE; + } + + array_init(return_value); + for (p = pAddresses; p; p = p->Next) { + zval iface, unicast; + + if ((IF_TYPE_ETHERNET_CSMACD != p->IfType) && (IF_TYPE_SOFTWARE_LOOPBACK != p->IfType)) { + continue; + } + + array_init(&iface); + + if (p->Description) { + char tmp[256]; + memset(tmp, 0, sizeof(tmp)); + wcstombs(tmp, p->Description, sizeof(tmp)); + add_assoc_string(&iface, "description", tmp); + } + + if (p->PhysicalAddressLength > 0) { + zend_string *mac = zend_string_alloc(p->PhysicalAddressLength * 3, 0); + char *s = ZSTR_VAL(mac); + ULONG i; + for (i = 0; i < p->PhysicalAddressLength; ++i) { + s += snprintf(s, 4, "%02X:", p->PhysicalAddress[i]); + } + *(--s) = 0; + ZSTR_LEN(mac) = s - ZSTR_VAL(mac); + add_assoc_str(&iface, "mac", mac); + } + + /* Flags could be placed at this level, + * but we repeat it in the unicast subarray + * for consistency with the POSIX version. + */ + add_assoc_long(&iface, "mtu", p->Mtu); + + array_init(&unicast); + for (u = p->FirstUnicastAddress; u; u = u->Next) { + switch (u->Address.lpSockaddr->sa_family) { + case AF_INET: { + ULONG mask; + struct sockaddr_in sin_mask; + + ConvertLengthToIpv4Mask(u->OnLinkPrefixLength, &mask); + sin_mask.sin_family = AF_INET; + sin_mask.sin_addr.s_addr = mask; + + iface_append_unicast(&unicast, p->Flags, + (struct sockaddr*)u->Address.lpSockaddr, + (struct sockaddr*)&sin_mask, NULL, NULL); + break; + } + case AF_INET6: { + ULONG i, j; + struct sockaddr_in6 sin6_mask; + + memset(&sin6_mask, 0, sizeof(sin6_mask)); + sin6_mask.sin6_family = AF_INET6; + for (i = u->OnLinkPrefixLength, j = 0; i > 0; i -= 8, ++j) { + sin6_mask.sin6_addr.s6_addr[j] = (i >= 8) ? 0xff : ((ULONG)((0xffU << (8 - i)) & 0xffU)); + } + + iface_append_unicast(&unicast, p->Flags, + (struct sockaddr*)u->Address.lpSockaddr, + (struct sockaddr*)&sin6_mask, NULL, NULL); + break; + } + } + } + add_assoc_zval(&iface, "unicast", &unicast); + + add_assoc_zval(return_value, p->AdapterName, &iface); + } + + FREE(pAddresses); +#undef MALLOC +#undef FREE +#elif HAVE_GETIFADDRS /* !PHP_WIN32 */ + struct ifaddrs *addrs = NULL, *p; + + ZEND_PARSE_PARAMETERS_NONE(); + + if (getifaddrs(&addrs)) { + php_error(E_WARNING, "getifaddrs() failed %d: %s", errno, strerror(errno)); + RETURN_FALSE; + } + + array_init(return_value); + for (p = addrs; p; p = p->ifa_next) { + zval *iface = zend_hash_str_find(Z_ARR_P(return_value), p->ifa_name, strlen(p->ifa_name)); + zval *unicast; + + if (!iface) { + zval newif; + array_init(&newif); + iface = zend_hash_str_add(Z_ARR_P(return_value), p->ifa_name, strlen(p->ifa_name), &newif); + } + + unicast = zend_hash_str_find(Z_ARR_P(iface), "unicast", sizeof("unicast") - 1); + if (!unicast) { + zval newuni; + array_init(&newuni); + unicast = zend_hash_str_add(Z_ARR_P(iface), "unicast", sizeof("unicast") - 1, &newuni); + } + + iface_append_unicast(unicast, + p->ifa_flags, + p->ifa_addr, p->ifa_netmask, + (p->ifa_flags & IFF_BROADCAST) ? p->ifa_broadaddr : NULL, + (p->ifa_flags & IFF_POINTOPOINT) ? p->ifa_dstaddr : NULL); + } + + freeifaddrs(addrs); +#else + /* Should never happen as we never register the function */ + php_error(E_WARNING, "No support for net_get_interfaces"); + RETURN_FALSE; +#endif +} +/* }}} */ diff --git a/ext/standard/php_net.h b/ext/standard/php_net.h new file mode 100644 index 0000000000..ea105d6076 --- /dev/null +++ b/ext/standard/php_net.h @@ -0,0 +1,31 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Sara Golemon <pollita@php.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef PHP_NET_H +#define PHP_NET_H + +#include "php.h" +#include "php_network.h" + +PHPAPI zend_string* php_inet_ntop(const struct sockaddr *addr); + +PHP_FUNCTION(net_get_interfaces); + +#endif /* PHP_NET_H */ diff --git a/ext/standard/tests/network/net_get_interfaces_001.phpt b/ext/standard/tests/network/net_get_interfaces_001.phpt new file mode 100644 index 0000000000..522278e90d --- /dev/null +++ b/ext/standard/tests/network/net_get_interfaces_001.phpt @@ -0,0 +1,30 @@ +--TEST-- +net_get_interfaces IPv4 Loopback +--SKIPIF-- +<?php +function_exists('net_get_interfaces') || print 'skip'; +--FILE-- +<?php + +// Test that we have exactly one unicast address with the address 127.0.0.1 +// On linux this will often be named "lo", but even that isn't guaranteed. + +$ifaces = net_get_interfaces(); + +$found = false; +foreach ($ifaces as $iface) { + foreach ($iface['unicast'] as $unicast) { + if (($unicast['address'] ?? null) === '127.0.0.1') { + $found = true; + break 2; + } + } +} + +var_dump($found); +if (!$found) { + // Extra diagnostics! + var_dump($ifaces); +} +--EXPECT-- +bool(true) -- 2.50.1