]> granicus.if.org Git - php/commitdiff
Add net_get_interfaces()
authorSara Golemon <pollita@php.net>
Tue, 21 Nov 2017 15:04:37 +0000 (10:04 -0500)
committerSara Golemon <pollita@php.net>
Mon, 27 Nov 2017 15:17:55 +0000 (10:17 -0500)
NEWS
ext/standard/basic_functions.c
ext/standard/basic_functions.h
ext/standard/config.m4
ext/standard/config.w32
ext/standard/net.c [new file with mode: 0644]
ext/standard/php_net.h [new file with mode: 0644]
ext/standard/tests/network/net_get_interfaces_001.phpt [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index eafaef041303faaeb9bff53cc63bcd45766fb2b5..b67b975e47c0d6365474bb9ef21e49314d5d0b22 100644 (file)
--- 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() <title/>. (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)
index a823ac9f819cdbc341cd4425ae53e3701cb28f35..7aec0baa6935dfef934ce62f366c071d0bda7743 100644 (file)
@@ -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;
index cfca0e03adc3d392858dc8c5ea4495608e0de0bd..c8f8bd14e536122c7db6ac6ff077812b313317f5 100644 (file)
@@ -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);
index 87ee059bd385e1e6314a2521d88fbba6c2781d52..8642fb2aeecdd62dbc62423d08dd79e41e3f37a1 100644 (file)
@@ -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
index ee1935fe87257eb4bed202d78d236913cddbe41a..fe519b8db9ca33ab96c8b69675e62bd683cff666 100644 (file)
@@ -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 (file)
index 0000000..f8d06e0
--- /dev/null
@@ -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 (file)
index 0000000..ea105d6
--- /dev/null
@@ -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 (file)
index 0000000..522278e
--- /dev/null
@@ -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)