1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2018 The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Sara Golemon <pollita@php.net> |
16 +----------------------------------------------------------------------+
17 */
18
19 #include "php.h"
20 #include "php_network.h"
21
22 #if HAVE_ARPA_INET_H
23 # include <arpa/inet.h>
24 #endif
25
26 #if HAVE_NET_IF_H
27 # include <net/if.h>
28 #endif
29
30 #if HAVE_GETIFADDRS
31 # include <ifaddrs.h>
32 #endif
33
34 #ifdef PHP_WIN32
35 # ifndef __clang__
36 # include <intrin.h>
37 # endif
38 # include <winsock2.h>
39 # include <ws2ipdef.h>
40 # include <Ws2tcpip.h>
41 # include <iphlpapi.h>
42 #else
43 # include <netdb.h>
44 #endif
45
php_inet_ntop(const struct sockaddr * addr)46 PHPAPI zend_string* php_inet_ntop(const struct sockaddr *addr) {
47 socklen_t addrlen = sizeof(struct sockaddr_in);
48
49 if (!addr) { return NULL; }
50
51 /* Prefer inet_ntop() as it's more task-specific and doesn't have to be demangled */
52 #if HAVE_INET_NTOP
53 switch (addr->sa_family) {
54 #ifdef AF_INET6
55 case AF_INET6: {
56 zend_string *ret = zend_string_alloc(INET6_ADDRSTRLEN, 0);
57 if (inet_ntop(AF_INET6, &(((struct sockaddr_in6*)addr)->sin6_addr), ZSTR_VAL(ret), INET6_ADDRSTRLEN)) {
58 ZSTR_LEN(ret) = strlen(ZSTR_VAL(ret));
59 return ret;
60 }
61 zend_string_efree(ret);
62 break;
63 }
64 #endif
65 case AF_INET: {
66 zend_string *ret = zend_string_alloc(INET_ADDRSTRLEN, 0);
67 if (inet_ntop(AF_INET, &(((struct sockaddr_in*)addr)->sin_addr), ZSTR_VAL(ret), INET_ADDRSTRLEN)) {
68 ZSTR_LEN(ret) = strlen(ZSTR_VAL(ret));
69 return ret;
70 }
71 zend_string_efree(ret);
72 break;
73 }
74 }
75 #endif
76
77 /* Fallback on getnameinfo() */
78 switch (addr->sa_family) {
79 #ifdef AF_INET6
80 case AF_INET6:
81 addrlen = sizeof(struct sockaddr_in6);
82 /* fallthrough */
83 #endif
84 case AF_INET: {
85 zend_string *ret = zend_string_alloc(NI_MAXHOST, 0);
86 if (getnameinfo(addr, addrlen, ZSTR_VAL(ret), NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == SUCCESS) {
87 /* Also demangle numeric host with %name suffix */
88 char *colon = strchr(ZSTR_VAL(ret), '%');
89 if (colon) { *colon = 0; }
90 ZSTR_LEN(ret) = strlen(ZSTR_VAL(ret));
91 return ret;
92 }
93 zend_string_efree(ret);
94 break;
95 }
96 }
97
98 return NULL;
99 }
100
101 #if defined(PHP_WIN32) || HAVE_GETIFADDRS
iface_append_unicast(zval * unicast,zend_long flags,struct sockaddr * addr,struct sockaddr * netmask,struct sockaddr * broadcast,struct sockaddr * ptp)102 static void iface_append_unicast(zval *unicast, zend_long flags,
103 struct sockaddr *addr, struct sockaddr *netmask,
104 struct sockaddr *broadcast, struct sockaddr *ptp) {
105 zend_string *host;
106 zval u;
107
108 array_init(&u);
109 add_assoc_long(&u, "flags", flags);
110
111 if (addr) {
112 add_assoc_long(&u, "family", addr->sa_family);
113 if ((host = php_inet_ntop(addr))) {
114 add_assoc_str(&u, "address", host);
115 }
116 }
117 if ((host = php_inet_ntop(netmask))) {
118 add_assoc_str(&u, "netmask", host);
119 }
120
121 if ((host = php_inet_ntop(broadcast))) {
122 add_assoc_str(&u, "broadcast", host);
123 }
124
125 if ((host = php_inet_ntop(ptp))) {
126 add_assoc_str(&u, "ptp", host);
127 }
128
129 add_next_index_zval(unicast, &u);
130 }
131 #endif
132
133 /* {{{ proto array|false net_get_interfaces()
134 Returns an array in the form:
135 array(
136 'ifacename' => array(
137 'description' => 'Awesome interface', // Win32 only
138 'mac' => '00:11:22:33:44:55', // Win32 only
139 'mtu' => 1234, // Win32 only
140 'unicast' => array(
141 0 => array(
142 'family' => 2, // e.g. AF_INET, AF_INET6, AF_PACKET
143 'address' => '127.0.0.1',
144 'netmnask' => '255.0.0.0',
145 'broadcast' => '127.255.255.255', // POSIX only
146 'ptp' => '127.0.0.2', // POSIX only
147 ), // etc...
148 ),
149 ), // etc...
150 )
151 */
PHP_FUNCTION(net_get_interfaces)152 PHP_FUNCTION(net_get_interfaces) {
153 #ifdef PHP_WIN32
154 # define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
155 # define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
156 ULONG family = AF_UNSPEC;
157 ULONG flags = GAA_FLAG_INCLUDE_PREFIX;
158 PIP_ADAPTER_ADDRESSES pAddresses = NULL, p;
159 PIP_ADAPTER_UNICAST_ADDRESS u = NULL;
160 ULONG outBufLen = 0;
161 DWORD dwRetVal = 0;
162
163 ZEND_PARSE_PARAMETERS_NONE();
164
165 // Make an initial call to GetAdaptersAddresses to get the
166 // size needed into the outBufLen variable
167 if (GetAdaptersAddresses(family, flags, NULL, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) {
168 FREE(pAddresses);
169 pAddresses = (IP_ADAPTER_ADDRESSES *) MALLOC(outBufLen);
170 }
171
172 if (pAddresses == NULL) {
173 zend_error(E_WARNING, "Memory allocation failed for IP_ADAPTER_ADDRESSES struct");
174 RETURN_FALSE;
175 }
176
177 dwRetVal = GetAdaptersAddresses(family, flags, NULL, pAddresses, &outBufLen);
178
179 if (NO_ERROR != dwRetVal) {
180 /* TODO check GetLastError() */
181 FREE(pAddresses);
182 RETURN_FALSE;
183 }
184
185 array_init(return_value);
186 for (p = pAddresses; p; p = p->Next) {
187 zval iface, unicast;
188
189 if ((IF_TYPE_ETHERNET_CSMACD != p->IfType) && (IF_TYPE_SOFTWARE_LOOPBACK != p->IfType)) {
190 continue;
191 }
192
193 array_init(&iface);
194
195 if (p->Description) {
196 char tmp[256];
197 memset(tmp, 0, sizeof(tmp));
198 wcstombs(tmp, p->Description, sizeof(tmp));
199 add_assoc_string(&iface, "description", tmp);
200 }
201
202 if (p->PhysicalAddressLength > 0) {
203 zend_string *mac = zend_string_alloc(p->PhysicalAddressLength * 3, 0);
204 char *s = ZSTR_VAL(mac);
205 ULONG i;
206 for (i = 0; i < p->PhysicalAddressLength; ++i) {
207 s += snprintf(s, 4, "%02X:", p->PhysicalAddress[i]);
208 }
209 *(--s) = 0;
210 ZSTR_LEN(mac) = s - ZSTR_VAL(mac);
211 add_assoc_str(&iface, "mac", mac);
212 }
213
214 /* Flags could be placed at this level,
215 * but we repeat it in the unicast subarray
216 * for consistency with the POSIX version.
217 */
218 add_assoc_long(&iface, "mtu", p->Mtu);
219
220 array_init(&unicast);
221 for (u = p->FirstUnicastAddress; u; u = u->Next) {
222 switch (u->Address.lpSockaddr->sa_family) {
223 case AF_INET: {
224 ULONG mask;
225 struct sockaddr_in sin_mask;
226
227 ConvertLengthToIpv4Mask(u->OnLinkPrefixLength, &mask);
228 sin_mask.sin_family = AF_INET;
229 sin_mask.sin_addr.s_addr = mask;
230
231 iface_append_unicast(&unicast, p->Flags,
232 (struct sockaddr*)u->Address.lpSockaddr,
233 (struct sockaddr*)&sin_mask, NULL, NULL);
234 break;
235 }
236 case AF_INET6: {
237 ULONG i, j;
238 struct sockaddr_in6 sin6_mask;
239
240 memset(&sin6_mask, 0, sizeof(sin6_mask));
241 sin6_mask.sin6_family = AF_INET6;
242 for (i = u->OnLinkPrefixLength, j = 0; i > 0; i -= 8, ++j) {
243 sin6_mask.sin6_addr.s6_addr[j] = (i >= 8) ? 0xff : ((ULONG)((0xffU << (8 - i)) & 0xffU));
244 }
245
246 iface_append_unicast(&unicast, p->Flags,
247 (struct sockaddr*)u->Address.lpSockaddr,
248 (struct sockaddr*)&sin6_mask, NULL, NULL);
249 break;
250 }
251 }
252 }
253 add_assoc_zval(&iface, "unicast", &unicast);
254
255 add_assoc_zval(return_value, p->AdapterName, &iface);
256 }
257
258 FREE(pAddresses);
259 #undef MALLOC
260 #undef FREE
261 #elif HAVE_GETIFADDRS /* !PHP_WIN32 */
262 struct ifaddrs *addrs = NULL, *p;
263
264 ZEND_PARSE_PARAMETERS_NONE();
265
266 if (getifaddrs(&addrs)) {
267 php_error(E_WARNING, "getifaddrs() failed %d: %s", errno, strerror(errno));
268 RETURN_FALSE;
269 }
270
271 array_init(return_value);
272 for (p = addrs; p; p = p->ifa_next) {
273 zval *iface = zend_hash_str_find(Z_ARR_P(return_value), p->ifa_name, strlen(p->ifa_name));
274 zval *unicast;
275
276 if (!iface) {
277 zval newif;
278 array_init(&newif);
279 iface = zend_hash_str_add(Z_ARR_P(return_value), p->ifa_name, strlen(p->ifa_name), &newif);
280 }
281
282 unicast = zend_hash_str_find(Z_ARR_P(iface), "unicast", sizeof("unicast") - 1);
283 if (!unicast) {
284 zval newuni;
285 array_init(&newuni);
286 unicast = zend_hash_str_add(Z_ARR_P(iface), "unicast", sizeof("unicast") - 1, &newuni);
287 }
288
289 iface_append_unicast(unicast,
290 p->ifa_flags,
291 p->ifa_addr, p->ifa_netmask,
292 (p->ifa_flags & IFF_BROADCAST) ? p->ifa_broadaddr : NULL,
293 (p->ifa_flags & IFF_POINTOPOINT) ? p->ifa_dstaddr : NULL);
294 }
295
296 freeifaddrs(addrs);
297 #else
298 /* Should never happen as we never register the function */
299 php_error(E_WARNING, "No support for net_get_interfaces");
300 RETURN_FALSE;
301 #endif
302 }
303 /* }}} */
304