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