VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/darwin/NetIf-darwin.cpp@ 82968

Last change on this file since 82968 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.9 KB
Line 
1/* $Id: NetIf-darwin.cpp 82968 2020-02-04 10:35:17Z vboxsync $ */
2/** @file
3 * Main - NetIfList, Darwin implementation.
4 */
5
6/*
7 * Copyright (C) 2008-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19
20/*********************************************************************************************************************************
21* Header Files *
22*********************************************************************************************************************************/
23#define LOG_GROUP LOG_GROUP_MAIN_HOST
24
25/*
26 * Deal with conflicts first.
27 * PVM - BSD mess, that FreeBSD has correct a long time ago.
28 * iprt/types.h before sys/param.h - prevents UINT32_C and friends.
29 */
30#include <iprt/types.h>
31#include <sys/param.h>
32#undef PVM
33
34#include <iprt/errcore.h>
35#include <iprt/alloc.h>
36
37#include <string.h>
38#include <sys/socket.h>
39#include <sys/ioctl.h>
40#include <sys/sysctl.h>
41#include <netinet/in.h>
42#include <net/if.h>
43#include <net/if_dl.h>
44#include <net/if_types.h>
45#include <net/route.h>
46#include <ifaddrs.h>
47#include <errno.h>
48#include <unistd.h>
49#include <list>
50
51#include "HostNetworkInterfaceImpl.h"
52#include "netif.h"
53#include "iokit.h"
54#include "LoggingNew.h"
55
56#if 0
57int NetIfList(std::list <ComObjPtr<HostNetworkInterface> > &list)
58{
59 int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
60 if (sock < 0)
61 {
62 Log(("NetIfList: socket() -> %d\n", errno));
63 return NULL;
64 }
65 struct ifaddrs *IfAddrs, *pAddr;
66 int rc = getifaddrs(&IfAddrs);
67 if (rc)
68 {
69 close(sock);
70 Log(("NetIfList: getifaddrs() -> %d\n", rc));
71 return VERR_INTERNAL_ERROR;
72 }
73
74 PDARWINETHERNIC pEtherNICs = DarwinGetEthernetControllers();
75 while (pEtherNICs)
76 {
77 size_t cbNameLen = strlen(pEtherNICs->szName) + 1;
78 PNETIFINFO pNew = (PNETIFINFO)RTMemAllocZ(RT_OFFSETOF(NETIFINFO, szName[cbNameLen]));
79 pNew->MACAddress = pEtherNICs->Mac;
80 pNew->enmMediumType = NETIF_T_ETHERNET;
81 pNew->Uuid = pEtherNICs->Uuid;
82 Assert(sizeof(pNew->szShortName) > sizeof(pEtherNICs->szBSDName));
83 memcpy(pNew->szShortName, pEtherNICs->szBSDName, sizeof(pEtherNICs->szBSDName));
84 pNew->szShortName[sizeof(pEtherNICs->szBSDName)] = '\0';
85 memcpy(pNew->szName, pEtherNICs->szName, cbNameLen);
86
87 struct ifreq IfReq;
88 RTStrCopy(IfReq.ifr_name, sizeof(IfReq.ifr_name), pNew->szShortName);
89 if (ioctl(sock, SIOCGIFFLAGS, &IfReq) < 0)
90 {
91 Log(("NetIfList: ioctl(SIOCGIFFLAGS) -> %d\n", errno));
92 pNew->enmStatus = NETIF_S_UNKNOWN;
93 }
94 else
95 pNew->enmStatus = (IfReq.ifr_flags & IFF_UP) ? NETIF_S_UP : NETIF_S_DOWN;
96
97 for (pAddr = IfAddrs; pAddr != NULL; pAddr = pAddr->ifa_next)
98 {
99 if (strcmp(pNew->szShortName, pAddr->ifa_name))
100 continue;
101
102 struct sockaddr_in *pIPAddr, *pIPNetMask;
103 struct sockaddr_in6 *pIPv6Addr, *pIPv6NetMask;
104
105 switch (pAddr->ifa_addr->sa_family)
106 {
107 case AF_INET:
108 if (pNew->IPAddress.u)
109 break;
110 pIPAddr = (struct sockaddr_in *)pAddr->ifa_addr;
111 Assert(sizeof(pNew->IPAddress) == sizeof(pIPAddr->sin_addr));
112 pNew->IPAddress.u = pIPAddr->sin_addr.s_addr;
113 pIPNetMask = (struct sockaddr_in *)pAddr->ifa_netmask;
114 Assert(pIPNetMask->sin_family == AF_INET);
115 Assert(sizeof(pNew->IPNetMask) == sizeof(pIPNetMask->sin_addr));
116 pNew->IPNetMask.u = pIPNetMask->sin_addr.s_addr;
117 break;
118 case AF_INET6:
119 if (pNew->IPv6Address.s.Lo || pNew->IPv6Address.s.Hi)
120 break;
121 pIPv6Addr = (struct sockaddr_in6 *)pAddr->ifa_addr;
122 Assert(sizeof(pNew->IPv6Address) == sizeof(pIPv6Addr->sin6_addr));
123 memcpy(pNew->IPv6Address.au8,
124 pIPv6Addr->sin6_addr.__u6_addr.__u6_addr8,
125 sizeof(pNew->IPv6Address));
126 pIPv6NetMask = (struct sockaddr_in6 *)pAddr->ifa_netmask;
127 Assert(pIPv6NetMask->sin6_family == AF_INET6);
128 Assert(sizeof(pNew->IPv6NetMask) == sizeof(pIPv6NetMask->sin6_addr));
129 memcpy(pNew->IPv6NetMask.au8,
130 pIPv6NetMask->sin6_addr.__u6_addr.__u6_addr8,
131 sizeof(pNew->IPv6NetMask));
132 break;
133 }
134 }
135
136 ComObjPtr<HostNetworkInterface> IfObj;
137 IfObj.createObject();
138 if (SUCCEEDED(IfObj->init(Bstr(pEtherNICs->szName), HostNetworkInterfaceType_Bridged, pNew)))
139 list.push_back(IfObj);
140 RTMemFree(pNew);
141
142 /* next, free current */
143 void *pvFree = pEtherNICs;
144 pEtherNICs = pEtherNICs->pNext;
145 RTMemFree(pvFree);
146 }
147
148 freeifaddrs(IfAddrs);
149 close(sock);
150 return VINF_SUCCESS;
151}
152#else
153
154#define ROUNDUP(a) \
155 (((a) & (sizeof(u_long) - 1)) ? (1 + ((a) | (sizeof(u_long) - 1))) : (a))
156#define ADVANCE(x, n) (x += (n)->sa_len ? ROUNDUP((n)->sa_len) : sizeof(u_long))
157
158void extractAddresses(int iAddrMask, caddr_t cp, caddr_t cplim, struct sockaddr **pAddresses)
159{
160 struct sockaddr *sa;
161
162 for (int i = 0; i < RTAX_MAX && cp < cplim; i++) {
163 if (iAddrMask & (1 << i))
164 {
165 sa = (struct sockaddr *)cp;
166
167 pAddresses[i] = sa;
168
169 ADVANCE(cp, sa);
170 }
171 else
172 pAddresses[i] = NULL;
173 }
174}
175
176void extractAddressesToNetInfo(int iAddrMask, caddr_t cp, caddr_t cplim, PNETIFINFO pInfo)
177{
178 struct sockaddr *addresses[RTAX_MAX];
179
180 extractAddresses(iAddrMask, cp, cplim, addresses);
181 switch (addresses[RTAX_IFA]->sa_family)
182 {
183 case AF_INET:
184 if (!pInfo->IPAddress.u)
185 {
186 pInfo->IPAddress.u = ((struct sockaddr_in *)addresses[RTAX_IFA])->sin_addr.s_addr;
187 pInfo->IPNetMask.u = ((struct sockaddr_in *)addresses[RTAX_NETMASK])->sin_addr.s_addr;
188 }
189 break;
190 case AF_INET6:
191 if (!pInfo->IPv6Address.s.Lo && !pInfo->IPv6Address.s.Hi)
192 {
193 memcpy(pInfo->IPv6Address.au8,
194 ((struct sockaddr_in6 *)addresses[RTAX_IFA])->sin6_addr.__u6_addr.__u6_addr8,
195 sizeof(pInfo->IPv6Address));
196 memcpy(pInfo->IPv6NetMask.au8,
197 ((struct sockaddr_in6 *)addresses[RTAX_NETMASK])->sin6_addr.__u6_addr.__u6_addr8,
198 sizeof(pInfo->IPv6NetMask));
199 }
200 break;
201 default:
202 Log(("NetIfList: Unsupported address family: %u\n", addresses[RTAX_IFA]->sa_family));
203 break;
204 }
205}
206
207static int getDefaultIfaceIndex(unsigned short *pu16Index)
208{
209 size_t cbNeeded;
210 char *pBuf, *pNext;
211 int aiMib[6];
212 struct sockaddr *addresses[RTAX_MAX];
213
214 aiMib[0] = CTL_NET;
215 aiMib[1] = PF_ROUTE;
216 aiMib[2] = 0;
217 aiMib[3] = PF_INET; /* address family */
218 aiMib[4] = NET_RT_DUMP;
219 aiMib[5] = 0;
220
221 if (sysctl(aiMib, 6, NULL, &cbNeeded, NULL, 0) < 0)
222 {
223 Log(("getDefaultIfaceIndex: Failed to get estimate for list size (errno=%d).\n", errno));
224 return RTErrConvertFromErrno(errno);
225 }
226 if ((pBuf = (char *)RTMemAlloc(cbNeeded)) == NULL)
227 return VERR_NO_MEMORY;
228 if (sysctl(aiMib, 6, pBuf, &cbNeeded, NULL, 0) < 0)
229 {
230 RTMemFree(pBuf);
231 Log(("getDefaultIfaceIndex: Failed to retrieve interface table (errno=%d).\n", errno));
232 return RTErrConvertFromErrno(errno);
233 }
234
235 char *pEnd = pBuf + cbNeeded;
236 struct rt_msghdr *pRtMsg;
237 for (pNext = pBuf; pNext < pEnd; pNext += pRtMsg->rtm_msglen)
238 {
239 pRtMsg = (struct rt_msghdr *)pNext;
240
241 if (pRtMsg->rtm_type != RTM_GET)
242 {
243 Log(("getDefaultIfaceIndex: Got message %u while expecting %u.\n",
244 pRtMsg->rtm_type, RTM_GET));
245 //rc = VERR_INTERNAL_ERROR;
246 continue;
247 }
248 if ((char*)(pRtMsg + 1) < pEnd)
249 {
250 /* Extract addresses from the message. */
251 extractAddresses(pRtMsg->rtm_addrs, (char *)(pRtMsg + 1),
252 pRtMsg->rtm_msglen + 1 + (char *)pRtMsg, addresses);
253 if ((pRtMsg->rtm_addrs & RTA_DST)
254 && (pRtMsg->rtm_addrs & RTA_NETMASK))
255 {
256 if (addresses[RTAX_DST]->sa_family != AF_INET)
257 continue;
258 struct sockaddr_in *addr = (struct sockaddr_in *)addresses[RTAX_DST];
259 struct sockaddr_in *mask = (struct sockaddr_in *)addresses[RTAX_NETMASK];
260 if ((addr->sin_addr.s_addr == INADDR_ANY) &&
261 mask &&
262 (ntohl(mask->sin_addr.s_addr) == 0L ||
263 mask->sin_len == 0))
264 {
265 *pu16Index = pRtMsg->rtm_index;
266 RTMemFree(pBuf);
267 return VINF_SUCCESS;
268 }
269 }
270 }
271 }
272 RTMemFree(pBuf);
273 return 0; /* Failed to find default interface, take the first one in the list. */
274}
275
276int NetIfList(std::list <ComObjPtr<HostNetworkInterface> > &list)
277{
278 int rc = VINF_SUCCESS;
279 size_t cbNeeded;
280 char *pBuf, *pNext;
281 int aiMib[6];
282 unsigned short u16DefaultIface = 0; /* initialized to shut up gcc */
283
284 /* Get the index of the interface associated with default route. */
285 rc = getDefaultIfaceIndex(&u16DefaultIface);
286 if (RT_FAILURE(rc))
287 return rc;
288
289 aiMib[0] = CTL_NET;
290 aiMib[1] = PF_ROUTE;
291 aiMib[2] = 0;
292 aiMib[3] = 0; /* address family */
293 aiMib[4] = NET_RT_IFLIST;
294 aiMib[5] = 0;
295
296 if (sysctl(aiMib, 6, NULL, &cbNeeded, NULL, 0) < 0)
297 {
298 Log(("NetIfList: Failed to get estimate for list size (errno=%d).\n", errno));
299 return RTErrConvertFromErrno(errno);
300 }
301 if ((pBuf = (char*)RTMemAlloc(cbNeeded)) == NULL)
302 return VERR_NO_MEMORY;
303 if (sysctl(aiMib, 6, pBuf, &cbNeeded, NULL, 0) < 0)
304 {
305 RTMemFree(pBuf);
306 Log(("NetIfList: Failed to retrieve interface table (errno=%d).\n", errno));
307 return RTErrConvertFromErrno(errno);
308 }
309
310 int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
311 if (sock < 0)
312 {
313 RTMemFree(pBuf);
314 Log(("NetIfList: socket() -> %d\n", errno));
315 return RTErrConvertFromErrno(errno);
316 }
317
318 PDARWINETHERNIC pNIC;
319 PDARWINETHERNIC pEtherNICs = DarwinGetEthernetControllers();
320
321 char *pEnd = pBuf + cbNeeded;
322 for (pNext = pBuf; pNext < pEnd;)
323 {
324 struct if_msghdr *pIfMsg = (struct if_msghdr *)pNext;
325
326 if (pIfMsg->ifm_type != RTM_IFINFO)
327 {
328 Log(("NetIfList: Got message %u while expecting %u.\n",
329 pIfMsg->ifm_type, RTM_IFINFO));
330 rc = VERR_INTERNAL_ERROR;
331 break;
332 }
333 struct sockaddr_dl *pSdl = (struct sockaddr_dl *)(pIfMsg + 1);
334
335 size_t cbNameLen = pSdl->sdl_nlen + 1;
336 Assert(pSdl->sdl_nlen < sizeof(pNIC->szBSDName));
337 for (pNIC = pEtherNICs; pNIC; pNIC = pNIC->pNext)
338 if ( !strncmp(pSdl->sdl_data, pNIC->szBSDName, pSdl->sdl_nlen)
339 && pNIC->szBSDName[pSdl->sdl_nlen] == '\0')
340 {
341 cbNameLen = strlen(pNIC->szName) + 1;
342 break;
343 }
344 PNETIFINFO pNew = (PNETIFINFO)RTMemAllocZ(RT_UOFFSETOF_DYN(NETIFINFO, szName[cbNameLen]));
345 if (!pNew)
346 {
347 rc = VERR_NO_MEMORY;
348 break;
349 }
350 memcpy(pNew->MACAddress.au8, LLADDR(pSdl), sizeof(pNew->MACAddress.au8));
351 pNew->enmMediumType = NETIF_T_ETHERNET;
352 Assert(sizeof(pNew->szShortName) > pSdl->sdl_nlen);
353 memcpy(pNew->szShortName, pSdl->sdl_data, RT_MIN(pSdl->sdl_nlen, sizeof(pNew->szShortName) - 1));
354
355 /*
356 * If we found the adapter in the list returned by
357 * DarwinGetEthernetControllers() copy the name and UUID from there.
358 */
359 if (pNIC)
360 {
361 memcpy(pNew->szName, pNIC->szName, cbNameLen);
362 pNew->Uuid = pNIC->Uuid;
363 pNew->fWireless = pNIC->fWireless;
364 }
365 else
366 {
367 memcpy(pNew->szName, pSdl->sdl_data, pSdl->sdl_nlen);
368 /* Generate UUID from name and MAC address. */
369 RTUUID uuid;
370 RTUuidClear(&uuid);
371 memcpy(&uuid, pNew->szShortName, RT_MIN(cbNameLen, sizeof(uuid)));
372 uuid.Gen.u8ClockSeqHiAndReserved = (uuid.Gen.u8ClockSeqHiAndReserved & 0x3f) | 0x80;
373 uuid.Gen.u16TimeHiAndVersion = (uuid.Gen.u16TimeHiAndVersion & 0x0fff) | 0x4000;
374 memcpy(uuid.Gen.au8Node, pNew->MACAddress.au8, sizeof(uuid.Gen.au8Node));
375 pNew->Uuid = uuid;
376 }
377
378 pNext += pIfMsg->ifm_msglen;
379 while (pNext < pEnd)
380 {
381 struct ifa_msghdr *pIfAddrMsg = (struct ifa_msghdr *)pNext;
382
383 if (pIfAddrMsg->ifam_type != RTM_NEWADDR)
384 break;
385 extractAddressesToNetInfo(pIfAddrMsg->ifam_addrs,
386 (char *)(pIfAddrMsg + 1),
387 pIfAddrMsg->ifam_msglen + (char *)pIfAddrMsg,
388 pNew);
389 pNext += pIfAddrMsg->ifam_msglen;
390 }
391
392 if (pSdl->sdl_type == IFT_ETHER)
393 {
394 struct ifreq IfReq;
395 RTStrCopy(IfReq.ifr_name, sizeof(IfReq.ifr_name), pNew->szShortName);
396 if (ioctl(sock, SIOCGIFFLAGS, &IfReq) < 0)
397 {
398 Log(("NetIfList: ioctl(SIOCGIFFLAGS) -> %d\n", errno));
399 pNew->enmStatus = NETIF_S_UNKNOWN;
400 }
401 else
402 pNew->enmStatus = (IfReq.ifr_flags & IFF_UP) ? NETIF_S_UP : NETIF_S_DOWN;
403
404 HostNetworkInterfaceType_T enmType;
405 if (strncmp(pNew->szName, RT_STR_TUPLE("vboxnet")))
406 enmType = HostNetworkInterfaceType_Bridged;
407 else
408 enmType = HostNetworkInterfaceType_HostOnly;
409
410 ComObjPtr<HostNetworkInterface> IfObj;
411 IfObj.createObject();
412 if (SUCCEEDED(IfObj->init(Bstr(pNew->szName), enmType, pNew)))
413 {
414 /* Make sure the default interface gets to the beginning. */
415 if (pIfMsg->ifm_index == u16DefaultIface)
416 list.push_front(IfObj);
417 else
418 list.push_back(IfObj);
419 }
420 }
421 RTMemFree(pNew);
422 }
423 for (pNIC = pEtherNICs; pNIC;)
424 {
425 void *pvFree = pNIC;
426 pNIC = pNIC->pNext;
427 RTMemFree(pvFree);
428 }
429 close(sock);
430 RTMemFree(pBuf);
431 return rc;
432}
433
434int NetIfGetConfigByName(PNETIFINFO pInfo)
435{
436 int rc = VINF_SUCCESS;
437 size_t cbNeeded;
438 char *pBuf, *pNext;
439 int aiMib[6];
440
441 aiMib[0] = CTL_NET;
442 aiMib[1] = PF_ROUTE;
443 aiMib[2] = 0;
444 aiMib[3] = 0; /* address family */
445 aiMib[4] = NET_RT_IFLIST;
446 aiMib[5] = 0;
447
448 if (sysctl(aiMib, 6, NULL, &cbNeeded, NULL, 0) < 0)
449 {
450 Log(("NetIfList: Failed to get estimate for list size (errno=%d).\n", errno));
451 return RTErrConvertFromErrno(errno);
452 }
453 if ((pBuf = (char*)RTMemAlloc(cbNeeded)) == NULL)
454 return VERR_NO_MEMORY;
455 if (sysctl(aiMib, 6, pBuf, &cbNeeded, NULL, 0) < 0)
456 {
457 RTMemFree(pBuf);
458 Log(("NetIfList: Failed to retrieve interface table (errno=%d).\n", errno));
459 return RTErrConvertFromErrno(errno);
460 }
461
462 int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
463 if (sock < 0)
464 {
465 RTMemFree(pBuf);
466 Log(("NetIfList: socket() -> %d\n", errno));
467 return RTErrConvertFromErrno(errno);
468 }
469
470 char *pEnd = pBuf + cbNeeded;
471 for (pNext = pBuf; pNext < pEnd;)
472 {
473 struct if_msghdr *pIfMsg = (struct if_msghdr *)pNext;
474
475 if (pIfMsg->ifm_type != RTM_IFINFO)
476 {
477 Log(("NetIfList: Got message %u while expecting %u.\n",
478 pIfMsg->ifm_type, RTM_IFINFO));
479 rc = VERR_INTERNAL_ERROR;
480 break;
481 }
482 struct sockaddr_dl *pSdl = (struct sockaddr_dl *)(pIfMsg + 1);
483
484 bool fSkip = !!strncmp(pInfo->szShortName, pSdl->sdl_data, pSdl->sdl_nlen)
485 || pInfo->szShortName[pSdl->sdl_nlen] != '\0';
486
487 pNext += pIfMsg->ifm_msglen;
488 while (pNext < pEnd)
489 {
490 struct ifa_msghdr *pIfAddrMsg = (struct ifa_msghdr *)pNext;
491
492 if (pIfAddrMsg->ifam_type != RTM_NEWADDR)
493 break;
494 if (!fSkip)
495 extractAddressesToNetInfo(pIfAddrMsg->ifam_addrs,
496 (char *)(pIfAddrMsg + 1),
497 pIfAddrMsg->ifam_msglen + (char *)pIfAddrMsg,
498 pInfo);
499 pNext += pIfAddrMsg->ifam_msglen;
500 }
501
502 if (!fSkip && pSdl->sdl_type == IFT_ETHER)
503 {
504 size_t cbNameLen = pSdl->sdl_nlen + 1;
505 memcpy(pInfo->MACAddress.au8, LLADDR(pSdl), sizeof(pInfo->MACAddress.au8));
506 pInfo->enmMediumType = NETIF_T_ETHERNET;
507 /* Generate UUID from name and MAC address. */
508 RTUUID uuid;
509 RTUuidClear(&uuid);
510 memcpy(&uuid, pInfo->szShortName, RT_MIN(cbNameLen, sizeof(uuid)));
511 uuid.Gen.u8ClockSeqHiAndReserved = (uuid.Gen.u8ClockSeqHiAndReserved & 0x3f) | 0x80;
512 uuid.Gen.u16TimeHiAndVersion = (uuid.Gen.u16TimeHiAndVersion & 0x0fff) | 0x4000;
513 memcpy(uuid.Gen.au8Node, pInfo->MACAddress.au8, sizeof(uuid.Gen.au8Node));
514 pInfo->Uuid = uuid;
515
516 struct ifreq IfReq;
517 RTStrCopy(IfReq.ifr_name, sizeof(IfReq.ifr_name), pInfo->szShortName);
518 if (ioctl(sock, SIOCGIFFLAGS, &IfReq) < 0)
519 {
520 Log(("NetIfList: ioctl(SIOCGIFFLAGS) -> %d\n", errno));
521 pInfo->enmStatus = NETIF_S_UNKNOWN;
522 }
523 else
524 pInfo->enmStatus = (IfReq.ifr_flags & IFF_UP) ? NETIF_S_UP : NETIF_S_DOWN;
525
526 return VINF_SUCCESS;
527 }
528 }
529 close(sock);
530 RTMemFree(pBuf);
531 return rc;
532}
533
534/**
535 * Retrieve the physical link speed in megabits per second. If the interface is
536 * not up or otherwise unavailable the zero speed is returned.
537 *
538 * @returns VBox status code.
539 *
540 * @param pcszIfName Interface name.
541 * @param puMbits Where to store the link speed.
542 */
543int NetIfGetLinkSpeed(const char *pcszIfName, uint32_t *puMbits)
544{
545 RT_NOREF(pcszIfName, puMbits);
546 return VERR_NOT_IMPLEMENTED;
547}
548#endif
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette