Coverage Report

Created: 2025-06-13 06:43

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