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